commit 538b4931dd5f2cf3ecb27e7f25e59285bcd0a940 Author: czc Date: Fri Jul 3 16:23:51 2026 +0800 chore: import Java_DmK project diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9a6cd7c --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +target/ +*.rar +JavaAPP.jar +hs_err_pid*.log +.idea/ +application-local.properties +application-prod.properties +WebErp/weberp/src/main/resources/application-local.properties +WebErp/weberp/src/main/resources/application-prod.properties diff --git a/PRODUCT_SOURCE_ARCHITECTURE.docx b/PRODUCT_SOURCE_ARCHITECTURE.docx new file mode 100644 index 0000000..1e9e427 Binary files /dev/null and b/PRODUCT_SOURCE_ARCHITECTURE.docx differ diff --git a/PRODUCT_SOURCE_ARCHITECTURE.md b/PRODUCT_SOURCE_ARCHITECTURE.md new file mode 100644 index 0000000..88612f4 --- /dev/null +++ b/PRODUCT_SOURCE_ARCHITECTURE.md @@ -0,0 +1,356 @@ +# 产品源代码整体架构说明 + +本文档基于当前仓库代码结构整理,用于快速理解 LSERP-MES / WebErp 后端工程的模块边界、请求调用链、数据访问方式和主要扩展点。 + +## 1. 工程概览 + +本项目是一个基于 Spring Boot 的 Java 后端服务,代码主体位于 `WebErp/weberp`。整体采用 Maven 父子模块结构,当前父工程 `WebErp` 下只有一个可执行子模块 `weberp`。 + +| 层级 | 路径 | 说明 | +| --- | --- | --- | +| 仓库根目录 | `.` | README、历史构建产物和项目根文件 | +| Maven 父工程 | `WebErp/pom.xml` | `packaging=pom`,聚合 `weberp` 子模块,统一部分依赖版本 | +| 应用子模块 | `WebErp/weberp` | Spring Boot 可执行 JAR 模块,主类为 `org.example.WebErpApplication` | +| 主源码 | `WebErp/weberp/src/main/java/org/example` | Controller、Handler、Impl、Entity、Utils 等后端源码 | +| 配置资源 | `WebErp/weberp/src/main/resources` | `application.properties`、MyBatis 配置、Mapper XML、语言包 | +| 测试代码 | `WebErp/weberp/src/test/java/org/example` | 单元/回归测试入口 | + +关键技术栈: + +- Spring Boot Web 3.4.3:HTTP 服务和依赖注入。 +- Spring Security 3.4.3:接口访问控制。 +- MyBatis 3.5.17 / mybatis-spring-boot-starter 3.0.4:Mapper XML 数据访问。 +- JdbcTemplate / NamedParameterJdbcTemplate:大量动态 SQL 和存储过程调用。 +- HikariCP:数据库连接池。 +- PageHelper:分页插件,当前方言配置为 `dm`。 +- JJWT:Token 生成、刷新和校验辅助。 +- Redis:缓存或会话相关基础设施。 +- Aspose / Spire / iText / JavaCV / ZXing:Office、PDF、音视频、二维码等文件处理能力。 + +## 2. 总体架构图 + +```mermaid +flowchart TB + Client["Web / App / 桌面前端"] + + subgraph SpringBoot["Spring Boot 应用: weberp"] + App["WebErpApplication"] + Security["SecurityConfig
允许 /Api/* 指定入口"] + Cors["CorsConfig
跨域和凭证配置"] + + subgraph Entry["统一 API 入口层"] + AuthCtrl["AuthController
/Api/SysUserAjaxApi"] + ModuleCtrl["ModuleAjaxController
/Api/ModuleAjaxApi"] + SystemCtrl["SystemAjaxApi
/Api/SystemAjaxApi"] + FileCtrl["FileUploadController
/Api/FileUploadApi"] + ToolsCtrl["ToolsHandler
/Api/ToolsHandler"] + end + + subgraph Handler["公共请求处理层"] + BaseHandler["BaseHandler
method/action 反射分发
登录校验 / 参数校验 / 响应输出"] + OptBaseHandler["OptBaseHandler
注入 JdbcTemplate / Mapper / SQL Factory"] + RequestHandler["RequestHandler
普通参数 / pms / gzip 参数解析"] + end + + subgraph ServiceImpl["业务服务与实现层"] + ModuleService["ModuleImplService"] + AuthService["AuthService"] + ModuleImpl["ModuleImpl
模块配置 / 数据 / 审核 / 桌面"] + DataImpl["DataImpl
动态 SQL / 表结构 / 存储过程"] + SysUserImpl["SysUserImpl
登录 / 用户 / 账套"] + SystemImpl["SystemImpl
系统菜单 / 系统信息"] + FileImpl["FileImpl
文件与附件"] + MapImpl["MapImpl
区域地图"] + UpdateImpl["UpdateImpl
系统更新脚本"] + end + + subgraph Domain["领域模型和工具层"] + Entity["Entity
BaseResponse / Module / Bill / Control / Audit"] + Utils["Utils
DbOperator / JSON / Cache / JwtHelp / FileUtil / WebConfig"] + Office["Office
文档处理"] + end + + subgraph DataAccess["数据访问层"] + Factory["AllInOneSqlFactory"] + Provider["AllInOneSqlProvider
dm / kingbase 实现"] + Mappers["MyBatis Mapper
CRMapper / DMCrmMapper / PageBreaksMapper"] + Xml["resources/mapper/*.xml"] + Jdbc["JdbcTemplate
NamedParameterJdbcTemplate"] + end + end + + DB[("业务数据库
达梦 / 人大金仓 / SQL Server / MySQL 驱动")] + Redis[("Redis")] + FileStore[("文件存储目录")] + + Client --> Security + Security --> Cors + Cors --> Entry + Entry --> BaseHandler + BaseHandler --> OptBaseHandler + BaseHandler --> RequestHandler + OptBaseHandler --> ServiceImpl + ServiceImpl --> Domain + ServiceImpl --> Factory + Factory --> Provider + Provider --> Mappers + Mappers --> Xml + ServiceImpl --> Jdbc + Mappers --> DB + Jdbc --> DB + Utils --> Redis + FileImpl --> FileStore + Office --> FileStore +``` + +## 3. 请求处理架构 + +项目的 HTTP 入口不是标准的“一个 URL 对应一个业务方法”的 REST 风格,而是多个 Ajax 入口统一接收请求,再由 `BaseHandler` 根据请求参数中的 `method` 或 `action` 反射调用同名业务方法。 + +```mermaid +sequenceDiagram + participant C as 前端 + participant Ctrl as Ajax Controller + participant BH as BaseHandler + participant BI as OptBaseImpl/BaseImpl + participant Impl as 业务 Impl + participant DB as 数据库 + + C->>Ctrl: GET/POST /Api/ModuleAjaxApi?method=GetModuleData + Ctrl->>BH: processRequest(request) + BH->>BI: initSystemParams / initReqPms + BH->>BH: 读取 method/action + BH->>BH: 查找同名 public 方法 + BH->>BH: 读取 @RequestCheck + BH->>BI: 登录校验 / 参数校验 / 当前用户上下文 + BH->>Impl: 反射调用业务方法 + Impl->>DB: Mapper 或 JdbcTemplate 查询/更新 + DB-->>Impl: 数据结果 + Impl-->>BH: BaseResponse + BH-->>C: text/plain;charset=UTF-8 JSON 响应 +``` + +主要机制: + +- `Controller` 只提供统一入口,例如 `/Api/ModuleAjaxApi/**`、`/Api/SystemAjaxApi/**`、`/Api/SysUserAjaxApi/**`。 +- `BaseHandler#getMethod()` 从 `method` 或 `action` 参数获取业务方法名。 +- `BaseHandler#processRequest()` 负责参数初始化、登录检查、`@RequestCheck` 校验、缓存判断、反射调用、日志记录、Token 刷新和响应输出。 +- `RequestHandler` 兼容普通 query/form 参数、`pms` 加密参数、gzip/deflate 压缩请求体。 +- `OptBaseHandler` 在 `BaseHandler` 基础上注入 `JdbcTemplate`、`CRMapper`、`DMCrmMapper` 和 `AllInOneSqlFactory`,并创建 `OptBaseImpl` 作为业务上下文。 + +## 4. 源码包职责 + +| 包 | 主要职责 | +| --- | --- | +| `org.example` | Spring Boot 启动入口 `WebErpApplication` | +| `Api` | 公共请求处理、统一响应、登录状态、日志、单用户控制 | +| `Auth` | 登录、验证码、密码、Token、安全辅助 | +| `Config` | CORS 配置 | +| `SystemApi` | 系统信息、系统菜单、账套/系统版本等接口 | +| `ModuleApi` | 核心模块 Ajax 接口、模块 DTO、MyBatis Mapper | +| `FileUploadApi` | 文件上传、下载、附件权限与附件记录维护 | +| `Impl` | 主要业务实现层,包含模块、数据、用户、系统、文件、地图、短信、更新等实现 | +| `Impl.Sql` | 多数据库 SQL Provider 和工厂,按数据库类型生成 SQL 或调用对应 Mapper | +| `Service` | 业务接口定义,例如 `ModuleImplService`、`AuthService`、`IModuleEvent` | +| `Entity` | 响应对象、模块配置对象、单据对象、控件对象、审核对象、异常对象等领域模型 | +| `Enums` | 系统枚举、参数方向、任务类型、登录状态等 | +| `Utils` | 通用工具,包括配置读取、JSON、缓存、JWT、文件、压缩、加密、数据库操作等 | +| `Office` | Office/Word 文档生成与转换相关工具 | +| `PageBreaksApi` | 分页符相关 Mapper | + +## 5. 核心业务模块 + +### 5.1 认证与用户 + +入口:`AuthController`,路径为 `/Api/SysUserAjaxApi`。 + +核心实现: + +- `SysUserImpl implements AuthService`:登录、手机号登录、切换账套、重置密码、验证码、登录状态维护。 +- `JwtHelp` / `JwtUtils`:Token 创建、刷新、缓存和解析辅助。 +- `SingleUserHandler`:单用户登录控制和会话互斥。 +- `SafetyUtil`:安全辅助逻辑。 + +典型方法: + +- `Login` +- `CheckLogin` +- `LoginOut` +- `ChangeServer` +- `ResetPwd` +- `GenerateCaptcha` +- `SendPhoneCode` + +### 5.2 系统基础信息 + +入口:`SystemAjaxApi`,路径为 `/Api/SystemAjaxApi`。 + +核心实现: + +- `SystemImpl`:系统列表、系统信息、登录信息、菜单、数据库服务器、Web 更新信息等。 + +典型方法: + +- `GetSystems` +- `GetSystemInfo` +- `GetSysMenus` +- `GetSystemLoginInfo` +- `GetProSysType` +- `GetDbServer` +- `GetWebUpdateInfo` + +### 5.3 动态模块与单据 + +入口:`ModuleAjaxController`,路径为 `/Api/ModuleAjaxApi`。 + +这是项目最核心的业务入口,围绕“模块配置驱动”的 ERP/MES 页面和单据能力展开。前端传入 `ModuleId`、`MenuId`、`method` 等参数后,后端从系统配置表和业务表中组装模块元数据、字段、列表、明细、审核、附件、右键菜单、桌面数据等。 + +核心实现: + +- `ModuleImpl implements ModuleImplService`:模块初始化、字段数据、模块数据、单据保存、审核、附件、权限、桌面配置。 +- `DataImpl`:动态 SQL、表结构、字段、主键、存储过程、附件权限、模块配置数据读取。 +- `ModuleEventImpl`:模块数据加载、保存、状态变化、附件上传等事件扩展点。 +- `MapImpl`:区域和地图相关接口实现。 + +典型方法: + +- `GetModuleIniParams` +- `GetModuleData` +- `GetFieldData` +- `GetBillIniParams` +- `AddOrUpd` +- `Delete` +- `SaveBill` +- `GetModuleCfg` +- `GetModuleDetailsData` +- `GetAuditHistory` +- `GetRoles` / `SaveRoles` +- `GetBSDesktopData` + +### 5.4 文件与附件 + +入口:`FileUploadController`,路径为 `/Api/FileUploadApi`。 + +核心实现: + +- `FileImpl`:文件保存、校验、附件基础记录。 +- `FileUtil` / `PathUtil` / `ZipUtil`:路径处理、实际文件保存/删除、批量压缩。 +- `ModuleImpl` / `DataImpl`:附件权限检查、附件记录入库、模块关联。 +- `OfficeUtil` / `CreateWordUtil`:文档处理、预览或转换相关能力。 + +典型方法: + +- `DoWebUpload` +- `DownLoadFiels` +- `DoDelete` +- `SaveViewModule` + +## 6. 数据访问架构 + +项目同时使用 MyBatis Mapper XML 和 Spring `JdbcTemplate`。 + +```mermaid +flowchart LR + Business["业务实现
ModuleImpl / DataImpl / SysUserImpl / SystemImpl"] + Factory["AllInOneSqlFactory"] + Type["custom.database.type
dm / kingbase"] + DmProvider["DmAllInOneSqlProvider"] + KbProvider["KingbaseAllInOneSqlProvider"] + DmMapper["DMCrmMapper
DMCrmMapper.xml"] + CrMapper["CRMapper
CustomerMapper.xml"] + PageMapper["PageBreaksMapper
PageBreaks.xml"] + Jdbc["JdbcTemplate / NamedParameterJdbcTemplate"] + DB[("数据库")] + + Business --> Factory + Type --> Factory + Factory --> DmProvider + Factory --> KbProvider + DmProvider --> DmMapper + KbProvider --> CrMapper + Business --> PageMapper + Business --> Jdbc + DmMapper --> DB + CrMapper --> DB + PageMapper --> DB + Jdbc --> DB +``` + +数据访问特点: + +- `application.properties` 通过 `custom.database.type` 指定当前数据库类型,当前配置为 `dm`。 +- `AllInOneSqlFactory#createProvider()` 根据数据库类型创建 `DmAllInOneSqlProvider` 或 `KingbaseAllInOneSqlProvider`。 +- 达梦数据库主要走 `DMCrmMapper` 和 `DMCrmMapper.xml`。 +- 人大金仓或通用查询主要走 `CRMapper` 和 `CustomerMapper.xml`。 +- 大量复杂业务仍直接使用 `JdbcTemplate` 或 `NamedParameterJdbcTemplate` 拼装动态 SQL。 +- `mybatis-config.xml` 配置了 PageHelper 插件,方言为 `dm`。 + +## 7. 配置与运行边界 + +主要配置文件:`WebErp/weberp/src/main/resources/application.properties`。 + +关键配置项: + +- `server.port=8088`:服务端口。 +- `custom.database.type=dm`:当前 SQL 方言/Provider 选择。 +- `spring.datasource.*`:数据库连接、驱动和 Hikari 连接池配置。 +- `mybatis.mapper-locations=classpath:mapper/*.xml`:Mapper XML 位置。 +- `pagehelper.helper-dialect=dm`:分页方言。 +- `spring.data.redis.*`:Redis 连接配置。 +- `language=Language_CN`:语言包选择。 +- `jwt.expiration`、`SingleUser`、`RestInitPwd`、`LockErrPwd` 等:登录和安全相关开关。 + +安全边界: + +- `SecurityConfig` 当前只放行指定 `/Api/*` 入口,其余请求默认拒绝。 +- `CorsConfig` 明确列出允许的前端来源,并允许携带凭证。 +- Controller 入口放行后,业务级登录校验主要由 `BaseHandler` 和 `@RequestCheck` 决定。 + +注意:配置文件中包含数据库、Redis 等环境信息,生产环境建议通过环境变量、外部配置中心或部署平台密钥管理注入,避免将敏感连接信息写入源码仓库。 + +## 8. 模块配置驱动模型 + +从代码结构看,系统大量业务并非通过固定 Java DTO 和固定 SQL 完成,而是依赖数据库中的模块配置表、字段配置、控件配置、菜单配置和权限配置动态组装页面与数据。 + +```mermaid +flowchart TB + Front["前端请求
ModuleId / MenuId / method"] + Init["GetModuleIniParams"] + ConfigTables["模块配置表
字段 / 控件 / 菜单 / 权限 / 审核"] + EntityBuild["Entity.System / Entity.Control
组装模块模型"] + DataQuery["DataImpl / ModuleImpl
动态查询业务数据"] + Response["BaseResponse
模块结构 + 数据 + 权限"] + + Front --> Init + Init --> ConfigTables + ConfigTables --> EntityBuild + EntityBuild --> DataQuery + DataQuery --> Response + Response --> Front +``` + +这种架构的影响: + +- 新增业务模块时,可能更多依赖数据库配置和元数据,而不是新增独立 Controller。 +- `ModuleId` 是贯穿模块初始化、数据查询、附件、审核、权限的核心参数。 +- `Entity.Control` 下的 `Field`、`GridPanel`、`TreePanel`、`ComboBox` 等对象承担前端控件描述能力。 +- `BaseModule`、`BillModule`、`ModuleBaseEntity` 等模型承载基础模块和单据模块配置。 + +## 9. 扩展点 + +常见扩展方式: + +1. 新增 Ajax 方法:在对应 Controller 中新增 public 无参方法,通过前端 `method` 或 `action` 调用,并按需添加 `@RequestCheck`。 +2. 新增模块能力:优先检查是否能通过模块配置表、字段配置、菜单配置实现,再考虑修改 `ModuleImpl` 或 `DataImpl`。 +3. 新增数据库方言:实现 `AllInOneSqlProvider`,并在 `AllInOneSqlFactory` 中按新的 `custom.database.type` 分支返回 Provider。 +4. 新增 Mapper 查询:在 Mapper 接口中定义方法,并在 `resources/mapper/*.xml` 中补充对应 SQL。 +5. 新增文件处理能力:扩展 `FileUploadController`、`FileImpl`、`FileUtil` 或 `Office` 工具类。 +6. 新增业务事件:通过 `ModuleEventImpl` 的加载、保存、状态变化、附件上传等事件机制接入。 + +## 10. 维护建议 + +- 优先理解 `BaseHandler -> OptBaseHandler -> OptBaseImpl/BaseImpl` 这条公共链路,再看具体业务方法。 +- 排查接口问题时,先确认请求路径、`method/action`、`ModuleId/MenuId`、`@RequestCheck` 参数校验和登录状态。 +- 修改数据访问逻辑时,先确认当前数据库类型和实际使用的是 Mapper XML、SQL Provider 还是 JdbcTemplate 动态 SQL。 +- 对涉及附件、审核、权限、单据保存的改动,应同步验证 `ModuleImpl`、`DataImpl` 和数据库配置表的联动。 +- 生产配置建议外置,尤其是数据库地址、用户名、密码、Redis 地址、JWT 相关配置。 diff --git a/PRODUCT_SOURCE_ARCHITECTURE.pdf b/PRODUCT_SOURCE_ARCHITECTURE.pdf new file mode 100644 index 0000000..f7ace18 Binary files /dev/null and b/PRODUCT_SOURCE_ARCHITECTURE.pdf differ diff --git a/README.en.md b/README.en.md new file mode 100644 index 0000000..1c16688 --- /dev/null +++ b/README.en.md @@ -0,0 +1,63 @@ +# LSERP + +### +LSERP/ +├── src/ +│ ├── main/ +│ │ ├── java/ +│ │ │ └── com/ +│ │ │ └── example/ +│ │ │ ├── project/ # 项目核心代码包 +│ │ │ │ ├── controller/ # 控制器层 +│ │ │ │ ├── service/ # 服务层 +│ │ │ │ │ ├── service/ # 服务接口 +│ │ │ │ │ └── impl/ # 服务实现 +│ │ │ │ ├── mapper/ # DAO接口 +│ │ │ │ ├── entity/ # 实体类 +│ │ │ │ ├── converter/ # 数据转换层 +│ │ │ │ ├── dto/ # 数据传输对象 +│ │ │ │ │ └── Module/ # 自定义返回类(匹配out或自定义返回类) +│ │ │ │ └── utils/ # 工具类(项目内工具) +│ │ │ └── utils/ # 与project同级的工具包 +│ │ │ ├── PermissionUtil/ # 数据类型转换工具 +│ │ │ ├── FormParamUtil/ # 表单获取工具 +│ │ │ ├── SystemMenuUtil/ # DLL文件转换工具 +│ │ │ └── PublicUtil/ # 数据类型转换工具 +│ │ └── resources/ +│ │ ├── mapper/ # MyBatis XML映射文件 +│ │ └──application.properties # Spring Boot配置 +│ └── test/ +│ └── java/ # 单元测试 +└── pom.xml # Maven配置文件 + +结构更改 + +LSERP/ +├── src/ +│ ├── main/ +│ │ ├── java/ +│ │ │ └── org/ +│ │ │ └── example/ +│ │ │ ├── project/ # 项目核心代码包 +│ │ │ │ ├── controller/ # 控制器层 +│ │ │ │ ├── service/ # 服务层 +│ │ │ │ │ ├── service/ # 服务接口 +│ │ │ │ │ └── impl/ # 服务实现 +│ │ │ │ ├── mapper/ # DAO接口 +│ │ │ │ ├── entity/ # 实体类 +│ │ │ │ ├── converter/ # 数据转换层 +│ │ │ │ ├── dto/ # 数据传输对象 +│ │ │ │ │ └── Module/ # 自定义返回类(匹配out或自定义返回类) +│ │ │ │ └── utils/ # 工具类(项目内工具) +│ │ │ ├── utils/ # 与project同级的工具包 +│ │ │ └── Entity/ # 实体类(修改后,sql直接用List接受,不再写实体类) +│ │ │ ├── PermissionUtil/ # 数据类型转换工具 +│ │ │ ├── FormParamUtil/ # 表单获取工具 +│ │ │ ├── SystemMenuUtil/ # DLL文件转换工具 +│ │ │ └── PublicUtil/ # 数据类型转换工具 +│ │ └── resources/ +│ │ ├── mapper/ # MyBatis XML映射文件 +│ │ └──application.properties # Spring Boot配置 +│ └── test/ +│ └── java/ # 单元测试 +└── pom.xml # Maven配置文件 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0ed48c9 --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# LSERP + +#### 介绍 +1111 + +#### 软件架构 +软件架构说明 + + +#### 安装教程 + +1. xxxx +2. xxxx +3. xxxx + +#### 使用说明 + +1. xxxx +2. xxxx +3. xxxx + +#### 参与贡献 + +1. Fork 本仓库 +2. 新建 Feat_xxx 分支 +3. 提交代码 +4. 新建 Pull Request + + +#### 特技 + +1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md +2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) +3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 +4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 +5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) +6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/WebErp/.gitignore b/WebErp/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/WebErp/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### 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/WebErp/README.en.md b/WebErp/README.en.md new file mode 100644 index 0000000..7bee549 --- /dev/null +++ b/WebErp/README.en.md @@ -0,0 +1,36 @@ +# LSERP-MES + +#### Description +111 + +#### Software Architecture +Software architecture description + +#### Installation + +1. xxxx +2. xxxx +3. xxxx + +#### Instructions + +1. xxxx +2. xxxx +3. xxxx + +#### Contribution + +1. Fork the repository +2. Create Feat_xxx branch +3. Commit your code +4. Create Pull Request + + +#### Gitee Feature + +1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md +2. Gitee blog [blog.gitee.com](https://blog.gitee.com) +3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) +4. The most valuable open source project [GVP](https://gitee.com/gvp) +5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) +6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/WebErp/README.md b/WebErp/README.md new file mode 100644 index 0000000..d45cb0b --- /dev/null +++ b/WebErp/README.md @@ -0,0 +1,37 @@ +# LSERP-MES + +#### 介绍 +111 + +#### 软件架构 +软件架构说明 + + +#### 安装教程 + +1. xxxx +2. xxxx +3. xxxx + +#### 使用说明 + +1. xxxx +2. xxxx +3. xxxx + +#### 参与贡献 + +1. Fork 本仓库 +2. 新建 Feat_xxx 分支 +3. 提交代码 +4. 新建 Pull Request + + +#### 特技 + +1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md +2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) +3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 +4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 +5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) +6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/WebErp/docs/security-vulnerability-self-check-report-2026-06-25.doc b/WebErp/docs/security-vulnerability-self-check-report-2026-06-25.doc new file mode 100644 index 0000000..ac47dd3 --- /dev/null +++ b/WebErp/docs/security-vulnerability-self-check-report-2026-06-25.doc @@ -0,0 +1,26 @@ + + + + +WebErp ???????????? + + +
+

WebErp 安全漏洞自查工作自证材料

报告日期:2026-06-25

报告类型:企业安全漏洞定期自查与风险规避自证材料

适用范围:WebErp / weberp Spring Boot 后端项目

报告口径:对外自证,展示已开展的安全自查、已建设的风险规避措施、风险识别记录与后续处置安排。

1. 项目概况与扫描范围

本次自查对象为 WebErp 后端工程,核心模块为 WebErp/weberp。项目采用 Spring Boot、Maven、多数据库驱动、MyBatis、文件处理、Office 文档转换、远程资源下载、JWT 登录态、GraalJS 表达式执行等能力。

本次自查覆盖以下范围:

类别覆盖内容证据来源
依赖与构建Maven 父子 POM、依赖树、关键第三方组件版本WebErp/pom.xmlWebErp/weberp/pom.xmlweberp/target/dependency-tree.txt
配置安全数据库连接、运行配置、跨域配置、脚本执行开关application.propertiesCorsConfig.javaJsEngine.java
认证授权Spring Security 入口放行、业务层登录校验、公开接口白名单SecurityConfig.javaBaseHandler.javaPublicApiRegistry.java
SQL 安全MyBatis 动态 SQL、SQL 片段校验、字段名白名单mapper/*.xmlSqlSafetyGuard.java、SQL Provider 实现
文件与下载文件路径校验、压缩包解压、远程下载、在线预览FileUtil.javaZipUtil.javaRemoteDownloadGuard.javaToolsHandler.java
脚本与命令GraalJS 沙箱配置、系统命令执行点复核JsEngine.javaOfficeUtil.javaFormatFactoryUtil.java
自动化证据安全回归测试、依赖树生成、工具可用性确认SecurityRegressionTest.java、Maven 命令执行记录

说明:本报告为安全漏洞自查工作留档材料,不等同于第三方渗透测试报告或等保测评报告。

2. 自查方法与证据来源

本次自查采用静态代码扫描、配置审阅、依赖版本核验、关键安全控制点测试和公开漏洞库查询相结合的方式。

已执行并形成证据的检查项:

检查项方法结果
安全回归测试执行 mvn -q -Dtest=SecurityRegressionTest test通过,覆盖路径越权、Zip Slip、CORS、远程下载限制、SQL 片段校验、公开接口白名单等场景
Maven 依赖树执行 Maven dependency tree 并输出到 weberp/target/dependency-tree.txt已生成,可用于依赖版本留档
OSV 漏洞查询对关键 Maven 组件进行 OSV API 查询发现 Jackson databind 仍有一项新披露风险需跟踪
工具环境确认检查本机 osv-scannerdependency-check 命令当前环境未安装,已列入后续工具化改进
敏感配置审阅扫描配置文件与安全相关关键字发现数据库连接配置仍需脱敏入库治理,报告正文已脱敏展示

3. 风险识别记录台账

编号风险名称位置等级影响现状证据已有控制处置方案优先级
R-001第三方依赖漏洞风险com.fasterxml.jackson.core:jackson-databind:2.18.8特定反序列化忽略属性绕过场景下,可能影响对象字段保护策略OSV 查询命中 GHSA-5jmj-h7xm-6q6v;OSV 标注修复版本为 2.18.9,但当前 Maven Central 未查询到该版本,本地 Maven 解析也失败Jackson 已从旧版本升级到当前可打包版本;父 POM 已统一版本管理,避免父子 POM 版本漂移持续跟踪可用修复版本;若 2.18.9 可解析则优先小版本升级;若不可用,则评估升级到后续可用安全版本并补跑兼容测试P0
R-002配置文件中存在真实数据库连接信息weberp/src/main/resources/application.properties 第 12 行附近若配置文件被外发或提交到共享仓库,可能导致数据库访问凭证泄露配置文件中仍有真实数据库连接串;本报告仅记录脱敏形态:jdbc:dm://*:/?凭证=*已约定生产真实配置应放在部署服务器本地;报告中不展示真实地址、账号或密码立即轮换已暴露凭证;将仓库配置改为示例值;补充 .gitignore 排除本地与生产真实配置文件;对历史提交和制品进行泄露排查P0
R-003动态接口入口开放面较大SecurityConfig.javaBaseHandler.javaPublicApiRegistry.java若业务层校验缺失,可能导致未授权访问Spring Security 对动态 /Api/** 分发入口放行;业务层通过 @RequestCheck 和公开接口白名单进行控制BaseHandler 已引入 PublicApiRegistry,未列入白名单的 CheckLogin=false 方法会回落为需要登录;未匹配请求默认拒绝持续审计所有 CheckLogin=false 方法;新增公开接口必须进入白名单并经过安全复核;对模块数据、文件预览、下载接口默认保持登录态要求P0
R-004MyBatis 动态 SQL 拼接风险mapper/*.xml${...} 片段及 SQL Provider若外部输入直接进入 SQL 片段,可能造成 SQL 注入或越权查询Mapper 文件中仍存在字段名、条件片段和完整 SQL 拼接点已新增 SqlSafetyGuard;部分入口已对字段名、只读查询和条件片段进行白名单或危险关键字拦截对所有 ${...} 来源建立清单;字段名改为服务端白名单;条件片段逐步参数化;无法立即改造的入口必须先接入 SqlSafetyGuardP0
R-005远程 URL 下载与服务端请求风险FileUtil.javaToolsHandler.javaWebUtil.javaCreateWordUtil.javaDateTimeUtil.java若允许用户控制 URL,可能触发 SSRF、内网探测或下载超限RemoteDownloadGuard 已接入部分下载流程;仍存在多个 URL 请求入口需要继续统一治理RemoteDownloadGuard 限制协议、私有地址、链路本地地址、组播地址、重定向跳数和下载大小;配置项默认禁止私有地址将残留 URL 请求入口统一接入远程下载守卫;重定向每跳复核;对在线预览类接口补充域名白名单或登录态要求P0
R-006文件路径穿越与压缩包解压风险FileUtil.javaZipUtil.java文件读写或解压过程若未限制根目录,可能覆盖非授权路径已有路径规范化、根目录匹配和压缩条目路径校验;回归测试覆盖路径越权和 Zip SlipZipUtil 对解压目标路径进行标准化并校验必须位于目标目录内;FileUtil 对文件虚拟路径和物理路径做规范化处理持续保留回归测试;新增文件读写入口必须复用路径校验;压缩包处理增加大小和条目数量限制作为后续优化P1
R-007GraalJS 脚本全访问风险JsEngine.javaapplication.properties若脚本全访问开启,表达式可能访问宿主环境能力app.js.allow-all-access=false 已配置;代码中仅在显式开启时允许全访问并输出告警默认关闭 GraalJS 全访问;表达式执行使用独立上下文和缓存容量限制保持默认关闭;若业务确需开启,必须形成变更审批、日志告警和最小权限说明;补充脚本危险语句测试P1
R-008系统命令执行点需持续复核OfficeUtil.javaFormatFactoryUtil.java若命令路径或参数可被外部输入污染,可能造成命令执行风险代码中存在系统命令调用点,主要用于文件权限和格式转换已将该类入口列为重点复核项;目前未在本报告中确认存在可利用外部输入链对可执行文件路径、参数来源和工作目录进行白名单校验;禁止拼接 shell 字符串;记录命令执行审计日志P1
R-009CORS 白名单与响应头暴露需环境化治理CorsConfig.java若跨域源配置过宽,可能扩大凭证跨域风险CORS 已采用白名单并允许携带凭证;测试覆盖不暴露全部响应头和拒绝异常方法未使用通配源;限制请求方法;仅暴露必要响应头将不同环境的允许源迁移到配置项;生产环境定期审计白名单;下线临时调试源P1
R-010安全扫描工具链不完整本机工具环境无法在本机直接运行标准化 OSV Scanner 或 OWASP Dependency-Check当前命令行未安装 osv-scannerdependency-check已通过 OSV API 和 Maven 依赖树进行替代性核验在 CI 或发布机补齐依赖漏洞扫描工具;形成扫描报告归档;对高危漏洞建立阻断规则P2
R-011SQL 控制台日志与敏感信息日志风险application.properties、SQL 调试配置开启 SQL 输出时,可能在日志中暴露业务字段、查询条件或敏感参数配置中存在 MyBatis 控制台日志相关项,当前仍需按环境复核部分日志级别已关闭;安全回归测试覆盖部分响应头与令牌场景生产环境关闭 SQL 明文输出;日志脱敏账号、手机号、凭证和令牌;建立日志留存与访问控制P1

4. 已有安全控制与风险规避措施

控制领域已有措施自证说明
依赖治理Maven 父 POM 统一管理关键组件版本;子模块保留真实业务依赖降低父子 POM 版本漂移和重复依赖风险,便于后续集中升级
默认拒绝Spring Security 未匹配请求使用默认拒绝策略减少非预期接口暴露
公开接口收敛PublicApiRegistry 集中维护公开方法登录、验证码、基础系统信息等接口可公开;模块业务数据类接口默认需登录
登录态校验BaseHandler 基于 @RequestCheck 统一执行登录和参数校验对动态分发 API 提供统一拦截层
SQL 防护SqlSafetyGuard 提供字段名、只读查询和条件片段校验对高风险动态 SQL 入口先建立拦截,再逐步参数化
SSRF 防护RemoteDownloadGuard 限制协议、地址类型、重定向和下载大小对远程下载流程建立边界控制
文件路径防护文件路径规范化、根目录校验、压缩包条目路径校验防止路径穿越和 Zip Slip
CORS 控制采用具体源白名单,未使用任意源通配携带凭证场景下减少跨域滥用风险
JS 沙箱GraalJS 全访问默认关闭降低表达式执行访问宿主能力的风险
回归测试SecurityRegressionTest 覆盖多项安全控制形成可重复验证证据

5. 风险处置方案与整改优先级

优先级处置事项目标完成标准
P0轮换并移除已提交配置中的真实数据库凭证仓库配置仅保留示例值;真实配置仅存在于部署服务器本地;历史暴露凭证完成轮换
P0跟踪 Jackson databind 修复版本修复版本可解析后完成升级、打包和安全回归测试;若采用后续可用安全版本,完成兼容验证
P0动态 SQL 治理所有 ${...} 来源登记;外部输入不得直接进入 SQL 片段;关键路径完成白名单或参数化
P0远程 URL 请求统一防护所有用户可控 URL 请求入口接入 RemoteDownloadGuard 或域名白名单;重定向每跳校验
P0公开接口白名单治理CheckLogin=false 方法均有业务必要性说明;未列入公开白名单的接口必须要求登录
P1CORS 与日志生产化治理CORS 源按环境配置;生产关闭 SQL 明文输出;日志中敏感字段完成脱敏
P1命令执行点复核可执行文件路径与参数来源白名单化;禁止 shell 字符串拼接;补充审计日志
P2安全扫描工具链补齐CI 或发布流程中加入 OSV Scanner 或 OWASP Dependency-Check,并归档扫描结果

6. 定期漏洞自查机制

建议建立以下周期化机制,并将结果作为企业安全漏洞自查自证材料留档:

周期自查动作输出材料
每月依赖漏洞扫描、Maven 依赖树归档、关键漏洞公告复核依赖漏洞扫描记录、风险处置台账
每季度代码安全自查,重点检查鉴权、SQL、文件、远程请求、脚本执行、日志脱敏季度安全自查报告、整改跟踪表
每次发版前执行安全回归测试、检查配置是否含真实凭证、复核新增公开接口发版安全检查清单、测试结果截图或日志
重大漏洞公告后针对影响组件进行应急排查、临时规避、升级验证应急响应记录、修复验证记录
配置变更时复核数据库、Redis、短信、推送、跨域源等配置配置变更审批与脱敏检查记录

建议固定归档材料:

  • Maven 依赖树与关键依赖版本清单。
  • OSV Scanner 或 OWASP Dependency-Check 报告。
  • 安全回归测试结果。
  • 风险识别记录与处置状态表。
  • 配置脱敏检查记录。
  • 公开接口白名单审计记录。

7. 附件证据清单

证据项文件或命令说明
安全回归测试mvn -q -Dtest=SecurityRegressionTest test已通过,覆盖路径、压缩包、CORS、远程下载、SQL 与公开接口控制
依赖树weberp/target/dependency-tree.txt已生成,用于记录当前第三方组件版本
依赖漏洞来源OSV GHSA-5jmj-h7xm-6q6vJackson databind 新披露风险来源
鉴权控制SecurityConfig.javaBaseHandler.javaPublicApiRegistry.java入口放行、业务层校验与公开接口白名单
SQL 控制SqlSafetyGuard.javamapper/*.xml动态 SQL 风险识别与部分防护落点
下载控制RemoteDownloadGuard.javaFileUtil.java远程下载协议、地址、重定向和大小控制
文件控制FileUtil.javaZipUtil.java路径规范化、根目录校验和 Zip Slip 防护
脚本控制JsEngine.javaapplication.propertiesGraalJS 全访问默认关闭
工具环境限制osv-scanner --versiondependency-check --version当前本机未安装,建议在 CI 或发布机补齐

8. 结论

本项目已开展面向依赖、配置、认证授权、SQL、文件处理、远程下载、脚本执行、跨域和回归测试的安全漏洞自查工作,并已具备多项风险规避措施。当前风险主要集中在配置凭证治理、Jackson 新披露漏洞跟踪、动态 SQL 全量收敛、残留远程 URL 请求统一防护和生产环境安全配置固化。

上述事项已纳入整改跟踪台账。后续通过定期扫描、发版前安全检查、重大漏洞应急复核和自动化安全回归测试,持续证明企业具备安全漏洞风险识别、处置和规避能力。

+
diff --git a/WebErp/docs/security-vulnerability-self-check-report-2026-06-25.docx b/WebErp/docs/security-vulnerability-self-check-report-2026-06-25.docx new file mode 100644 index 0000000..dfeb87c Binary files /dev/null and b/WebErp/docs/security-vulnerability-self-check-report-2026-06-25.docx differ diff --git a/WebErp/docs/security-vulnerability-self-check-report-2026-06-25.md b/WebErp/docs/security-vulnerability-self-check-report-2026-06-25.md new file mode 100644 index 0000000..5b006e5 --- /dev/null +++ b/WebErp/docs/security-vulnerability-self-check-report-2026-06-25.md @@ -0,0 +1,123 @@ +# WebErp 安全漏洞自查工作自证材料 + +报告日期:2026-06-25 +报告类型:企业安全漏洞定期自查与风险规避自证材料 +适用范围:WebErp / weberp Spring Boot 后端项目 +报告口径:对外自证,展示已开展的安全自查、已建设的风险规避措施、风险识别记录与后续处置安排。 + +## 1. 项目概况与扫描范围 + +本次自查对象为 WebErp 后端工程,核心模块为 `WebErp/weberp`。项目采用 Spring Boot、Maven、多数据库驱动、MyBatis、文件处理、Office 文档转换、远程资源下载、JWT 登录态、GraalJS 表达式执行等能力。 + +本次自查覆盖以下范围: + +| 类别 | 覆盖内容 | 证据来源 | +| --- | --- | --- | +| 依赖与构建 | Maven 父子 POM、依赖树、关键第三方组件版本 | `WebErp/pom.xml`、`WebErp/weberp/pom.xml`、`weberp/target/dependency-tree.txt` | +| 配置安全 | 数据库连接、运行配置、跨域配置、脚本执行开关 | `application.properties`、`CorsConfig.java`、`JsEngine.java` | +| 认证授权 | Spring Security 入口放行、业务层登录校验、公开接口白名单 | `SecurityConfig.java`、`BaseHandler.java`、`PublicApiRegistry.java` | +| SQL 安全 | MyBatis 动态 SQL、SQL 片段校验、字段名白名单 | `mapper/*.xml`、`SqlSafetyGuard.java`、SQL Provider 实现 | +| 文件与下载 | 文件路径校验、压缩包解压、远程下载、在线预览 | `FileUtil.java`、`ZipUtil.java`、`RemoteDownloadGuard.java`、`ToolsHandler.java` | +| 脚本与命令 | GraalJS 沙箱配置、系统命令执行点复核 | `JsEngine.java`、`OfficeUtil.java`、`FormatFactoryUtil.java` | +| 自动化证据 | 安全回归测试、依赖树生成、工具可用性确认 | `SecurityRegressionTest.java`、Maven 命令执行记录 | + +说明:本报告为安全漏洞自查工作留档材料,不等同于第三方渗透测试报告或等保测评报告。 + +## 2. 自查方法与证据来源 + +本次自查采用静态代码扫描、配置审阅、依赖版本核验、关键安全控制点测试和公开漏洞库查询相结合的方式。 + +已执行并形成证据的检查项: + +| 检查项 | 方法 | 结果 | +| --- | --- | --- | +| 安全回归测试 | 执行 `mvn -q -Dtest=SecurityRegressionTest test` | 通过,覆盖路径越权、Zip Slip、CORS、远程下载限制、SQL 片段校验、公开接口白名单等场景 | +| Maven 依赖树 | 执行 Maven dependency tree 并输出到 `weberp/target/dependency-tree.txt` | 已生成,可用于依赖版本留档 | +| OSV 漏洞查询 | 对关键 Maven 组件进行 OSV API 查询 | 发现 Jackson databind 仍有一项新披露风险需跟踪 | +| 工具环境确认 | 检查本机 `osv-scanner` 与 `dependency-check` 命令 | 当前环境未安装,已列入后续工具化改进 | +| 敏感配置审阅 | 扫描配置文件与安全相关关键字 | 发现数据库连接配置仍需脱敏入库治理,报告正文已脱敏展示 | + +## 3. 风险识别记录台账 + +| 编号 | 风险名称 | 位置 | 等级 | 影响 | 现状证据 | 已有控制 | 处置方案 | 优先级 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| R-001 | 第三方依赖漏洞风险 | `com.fasterxml.jackson.core:jackson-databind:2.18.8` | 高 | 特定反序列化忽略属性绕过场景下,可能影响对象字段保护策略 | OSV 查询命中 `GHSA-5jmj-h7xm-6q6v`;OSV 标注修复版本为 `2.18.9`,但当前 Maven Central 未查询到该版本,本地 Maven 解析也失败 | Jackson 已从旧版本升级到当前可打包版本;父 POM 已统一版本管理,避免父子 POM 版本漂移 | 持续跟踪可用修复版本;若 `2.18.9` 可解析则优先小版本升级;若不可用,则评估升级到后续可用安全版本并补跑兼容测试 | P0 | +| R-002 | 配置文件中存在真实数据库连接信息 | `weberp/src/main/resources/application.properties` 第 12 行附近 | 高 | 若配置文件被外发或提交到共享仓库,可能导致数据库访问凭证泄露 | 配置文件中仍有真实数据库连接串;本报告仅记录脱敏形态:`jdbc:dm://***:****/****?凭证=***` | 已约定生产真实配置应放在部署服务器本地;报告中不展示真实地址、账号或密码 | 立即轮换已暴露凭证;将仓库配置改为示例值;补充 `.gitignore` 排除本地与生产真实配置文件;对历史提交和制品进行泄露排查 | P0 | +| R-003 | 动态接口入口开放面较大 | `SecurityConfig.java`、`BaseHandler.java`、`PublicApiRegistry.java` | 中 | 若业务层校验缺失,可能导致未授权访问 | Spring Security 对动态 `/Api/**` 分发入口放行;业务层通过 `@RequestCheck` 和公开接口白名单进行控制 | `BaseHandler` 已引入 `PublicApiRegistry`,未列入白名单的 `CheckLogin=false` 方法会回落为需要登录;未匹配请求默认拒绝 | 持续审计所有 `CheckLogin=false` 方法;新增公开接口必须进入白名单并经过安全复核;对模块数据、文件预览、下载接口默认保持登录态要求 | P0 | +| R-004 | MyBatis 动态 SQL 拼接风险 | `mapper/*.xml` 中 `${...}` 片段及 SQL Provider | 高 | 若外部输入直接进入 SQL 片段,可能造成 SQL 注入或越权查询 | Mapper 文件中仍存在字段名、条件片段和完整 SQL 拼接点 | 已新增 `SqlSafetyGuard`;部分入口已对字段名、只读查询和条件片段进行白名单或危险关键字拦截 | 对所有 `${...}` 来源建立清单;字段名改为服务端白名单;条件片段逐步参数化;无法立即改造的入口必须先接入 `SqlSafetyGuard` | P0 | +| R-005 | 远程 URL 下载与服务端请求风险 | `FileUtil.java`、`ToolsHandler.java`、`WebUtil.java`、`CreateWordUtil.java`、`DateTimeUtil.java` | 高 | 若允许用户控制 URL,可能触发 SSRF、内网探测或下载超限 | `RemoteDownloadGuard` 已接入部分下载流程;仍存在多个 URL 请求入口需要继续统一治理 | `RemoteDownloadGuard` 限制协议、私有地址、链路本地地址、组播地址、重定向跳数和下载大小;配置项默认禁止私有地址 | 将残留 URL 请求入口统一接入远程下载守卫;重定向每跳复核;对在线预览类接口补充域名白名单或登录态要求 | P0 | +| R-006 | 文件路径穿越与压缩包解压风险 | `FileUtil.java`、`ZipUtil.java` | 中 | 文件读写或解压过程若未限制根目录,可能覆盖非授权路径 | 已有路径规范化、根目录匹配和压缩条目路径校验;回归测试覆盖路径越权和 Zip Slip | `ZipUtil` 对解压目标路径进行标准化并校验必须位于目标目录内;`FileUtil` 对文件虚拟路径和物理路径做规范化处理 | 持续保留回归测试;新增文件读写入口必须复用路径校验;压缩包处理增加大小和条目数量限制作为后续优化 | P1 | +| R-007 | GraalJS 脚本全访问风险 | `JsEngine.java`、`application.properties` | 中 | 若脚本全访问开启,表达式可能访问宿主环境能力 | `app.js.allow-all-access=false` 已配置;代码中仅在显式开启时允许全访问并输出告警 | 默认关闭 GraalJS 全访问;表达式执行使用独立上下文和缓存容量限制 | 保持默认关闭;若业务确需开启,必须形成变更审批、日志告警和最小权限说明;补充脚本危险语句测试 | P1 | +| R-008 | 系统命令执行点需持续复核 | `OfficeUtil.java`、`FormatFactoryUtil.java` | 中 | 若命令路径或参数可被外部输入污染,可能造成命令执行风险 | 代码中存在系统命令调用点,主要用于文件权限和格式转换 | 已将该类入口列为重点复核项;目前未在本报告中确认存在可利用外部输入链 | 对可执行文件路径、参数来源和工作目录进行白名单校验;禁止拼接 shell 字符串;记录命令执行审计日志 | P1 | +| R-009 | CORS 白名单与响应头暴露需环境化治理 | `CorsConfig.java` | 中 | 若跨域源配置过宽,可能扩大凭证跨域风险 | CORS 已采用白名单并允许携带凭证;测试覆盖不暴露全部响应头和拒绝异常方法 | 未使用通配源;限制请求方法;仅暴露必要响应头 | 将不同环境的允许源迁移到配置项;生产环境定期审计白名单;下线临时调试源 | P1 | +| R-010 | 安全扫描工具链不完整 | 本机工具环境 | 低 | 无法在本机直接运行标准化 OSV Scanner 或 OWASP Dependency-Check | 当前命令行未安装 `osv-scanner` 与 `dependency-check` | 已通过 OSV API 和 Maven 依赖树进行替代性核验 | 在 CI 或发布机补齐依赖漏洞扫描工具;形成扫描报告归档;对高危漏洞建立阻断规则 | P2 | +| R-011 | SQL 控制台日志与敏感信息日志风险 | `application.properties`、SQL 调试配置 | 中 | 开启 SQL 输出时,可能在日志中暴露业务字段、查询条件或敏感参数 | 配置中存在 MyBatis 控制台日志相关项,当前仍需按环境复核 | 部分日志级别已关闭;安全回归测试覆盖部分响应头与令牌场景 | 生产环境关闭 SQL 明文输出;日志脱敏账号、手机号、凭证和令牌;建立日志留存与访问控制 | P1 | + +## 4. 已有安全控制与风险规避措施 + +| 控制领域 | 已有措施 | 自证说明 | +| --- | --- | --- | +| 依赖治理 | Maven 父 POM 统一管理关键组件版本;子模块保留真实业务依赖 | 降低父子 POM 版本漂移和重复依赖风险,便于后续集中升级 | +| 默认拒绝 | Spring Security 未匹配请求使用默认拒绝策略 | 减少非预期接口暴露 | +| 公开接口收敛 | `PublicApiRegistry` 集中维护公开方法 | 登录、验证码、基础系统信息等接口可公开;模块业务数据类接口默认需登录 | +| 登录态校验 | `BaseHandler` 基于 `@RequestCheck` 统一执行登录和参数校验 | 对动态分发 API 提供统一拦截层 | +| SQL 防护 | `SqlSafetyGuard` 提供字段名、只读查询和条件片段校验 | 对高风险动态 SQL 入口先建立拦截,再逐步参数化 | +| SSRF 防护 | `RemoteDownloadGuard` 限制协议、地址类型、重定向和下载大小 | 对远程下载流程建立边界控制 | +| 文件路径防护 | 文件路径规范化、根目录校验、压缩包条目路径校验 | 防止路径穿越和 Zip Slip | +| CORS 控制 | 采用具体源白名单,未使用任意源通配 | 携带凭证场景下减少跨域滥用风险 | +| JS 沙箱 | GraalJS 全访问默认关闭 | 降低表达式执行访问宿主能力的风险 | +| 回归测试 | `SecurityRegressionTest` 覆盖多项安全控制 | 形成可重复验证证据 | + +## 5. 风险处置方案与整改优先级 + +| 优先级 | 处置事项 | 目标完成标准 | +| --- | --- | --- | +| P0 | 轮换并移除已提交配置中的真实数据库凭证 | 仓库配置仅保留示例值;真实配置仅存在于部署服务器本地;历史暴露凭证完成轮换 | +| P0 | 跟踪 Jackson databind 修复版本 | 修复版本可解析后完成升级、打包和安全回归测试;若采用后续可用安全版本,完成兼容验证 | +| P0 | 动态 SQL 治理 | 所有 `${...}` 来源登记;外部输入不得直接进入 SQL 片段;关键路径完成白名单或参数化 | +| P0 | 远程 URL 请求统一防护 | 所有用户可控 URL 请求入口接入 `RemoteDownloadGuard` 或域名白名单;重定向每跳校验 | +| P0 | 公开接口白名单治理 | `CheckLogin=false` 方法均有业务必要性说明;未列入公开白名单的接口必须要求登录 | +| P1 | CORS 与日志生产化治理 | CORS 源按环境配置;生产关闭 SQL 明文输出;日志中敏感字段完成脱敏 | +| P1 | 命令执行点复核 | 可执行文件路径与参数来源白名单化;禁止 shell 字符串拼接;补充审计日志 | +| P2 | 安全扫描工具链补齐 | CI 或发布流程中加入 OSV Scanner 或 OWASP Dependency-Check,并归档扫描结果 | + +## 6. 定期漏洞自查机制 + +建议建立以下周期化机制,并将结果作为企业安全漏洞自查自证材料留档: + +| 周期 | 自查动作 | 输出材料 | +| --- | --- | --- | +| 每月 | 依赖漏洞扫描、Maven 依赖树归档、关键漏洞公告复核 | 依赖漏洞扫描记录、风险处置台账 | +| 每季度 | 代码安全自查,重点检查鉴权、SQL、文件、远程请求、脚本执行、日志脱敏 | 季度安全自查报告、整改跟踪表 | +| 每次发版前 | 执行安全回归测试、检查配置是否含真实凭证、复核新增公开接口 | 发版安全检查清单、测试结果截图或日志 | +| 重大漏洞公告后 | 针对影响组件进行应急排查、临时规避、升级验证 | 应急响应记录、修复验证记录 | +| 配置变更时 | 复核数据库、Redis、短信、推送、跨域源等配置 | 配置变更审批与脱敏检查记录 | + +建议固定归档材料: + +- Maven 依赖树与关键依赖版本清单。 +- OSV Scanner 或 OWASP Dependency-Check 报告。 +- 安全回归测试结果。 +- 风险识别记录与处置状态表。 +- 配置脱敏检查记录。 +- 公开接口白名单审计记录。 + +## 7. 附件证据清单 + +| 证据项 | 文件或命令 | 说明 | +| --- | --- | --- | +| 安全回归测试 | `mvn -q -Dtest=SecurityRegressionTest test` | 已通过,覆盖路径、压缩包、CORS、远程下载、SQL 与公开接口控制 | +| 依赖树 | `weberp/target/dependency-tree.txt` | 已生成,用于记录当前第三方组件版本 | +| 依赖漏洞来源 | [OSV GHSA-5jmj-h7xm-6q6v](https://osv.dev/vulnerability/GHSA-5jmj-h7xm-6q6v) | Jackson databind 新披露风险来源 | +| 鉴权控制 | `SecurityConfig.java`、`BaseHandler.java`、`PublicApiRegistry.java` | 入口放行、业务层校验与公开接口白名单 | +| SQL 控制 | `SqlSafetyGuard.java`、`mapper/*.xml` | 动态 SQL 风险识别与部分防护落点 | +| 下载控制 | `RemoteDownloadGuard.java`、`FileUtil.java` | 远程下载协议、地址、重定向和大小控制 | +| 文件控制 | `FileUtil.java`、`ZipUtil.java` | 路径规范化、根目录校验和 Zip Slip 防护 | +| 脚本控制 | `JsEngine.java`、`application.properties` | GraalJS 全访问默认关闭 | +| 工具环境限制 | `osv-scanner --version`、`dependency-check --version` | 当前本机未安装,建议在 CI 或发布机补齐 | + +## 8. 结论 + +本项目已开展面向依赖、配置、认证授权、SQL、文件处理、远程下载、脚本执行、跨域和回归测试的安全漏洞自查工作,并已具备多项风险规避措施。当前风险主要集中在配置凭证治理、Jackson 新披露漏洞跟踪、动态 SQL 全量收敛、残留远程 URL 请求统一防护和生产环境安全配置固化。 + +上述事项已纳入整改跟踪台账。后续通过定期扫描、发版前安全检查、重大漏洞应急复核和自动化安全回归测试,持续证明企业具备安全漏洞风险识别、处置和规避能力。 diff --git a/WebErp/pom.xml b/WebErp/pom.xml new file mode 100644 index 0000000..497884f --- /dev/null +++ b/WebErp/pom.xml @@ -0,0 +1,299 @@ + + 4.0.0 + + org.example + WebErp + 1.0-SNAPSHOT + pom + + WebErp + http://maven.apache.org + + + weberp + + + + + AsposeRepository + Aspose Official Repository + https://releases.aspose.com/java/repo/ + + true + + + false + + + + com.e-iceblue + Spire Repository + https://repo.e-iceblue.cn/repository/maven-public/ + + + + + UTF-8 + 3.4.3 + 2.18.8 + 1.5.18 + 8.3.0 + 12.8.2.jre11 + 3.5.17 + 3.0.4 + 0.11.5 + 2.10.1 + 24.1.1 + 2.3.1 + 1.0.3 + 1.4.0 + 7.2.5 + 13.5.3 + 14.8.2 + 10.10.2 + 23.6 + 3.5.1 + 1.5.10 + 1.26.0 + 2.11.5 + 4.0.3 + 8.1.4.181 + 1.4.7 + 6.0.0 + 3.8.1 + 3.13.0 + + + + + + org.springframework.boot + spring-boot-starter-web + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-data-redis + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-security + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-test + ${spring-boot.version} + + + ch.qos.logback + logback-classic + ${logback.version} + + + com.mysql + mysql-connector-j + ${mysql.version} + + + com.microsoft.sqlserver + mssql-jdbc + ${mssql-jdbc.version} + + + org.mybatis + mybatis + ${mybatis.version} + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + ${mybatis-spring-boot.version} + + + io.jsonwebtoken + jjwt-api + ${jjwt.version} + + + io.jsonwebtoken + jjwt-impl + ${jjwt.version} + + + io.jsonwebtoken + jjwt-jackson + ${jjwt.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson.version} + + + jakarta.servlet + jakarta.servlet-api + ${jakarta-servlet.version} + + + com.google.code.gson + gson + ${gson.version} + + + org.graalvm.js + js + ${graaljs.version} + pom + + + org.graalvm.js + js-scriptengine + ${graaljs.version} + + + org.graalvm.sdk + graal-sdk + ${graaljs.version} + + + javax.xml.bind + jaxb-api + ${jaxb.version} + + + com.sun.xml.bind + jaxb-impl + ${jaxb.version} + + + com.googlecode.juniversalchardet + juniversalchardet + ${juniversalchardet.version} + + + com.github.jai-imageio + jai-imageio-core + ${jai-imageio.version} + + + com.itextpdf + itext7-core + ${itext7.version} + pom + + + e-iceblue + spire.doc + ${spire-doc.version} + + + e-iceblue + spire.xls + ${spire-xls.version} + + + e-iceblue + spire.presentation + ${spire-presentation.version} + + + com.aspose + aspose-words + ${aspose.version} + jdk17 + + + com.aspose + aspose-cells + ${aspose.version} + + + com.aspose + aspose-slides + ${aspose.version} + jdk16 + + + com.google.zxing + core + ${zxing.version} + + + com.google.zxing + javase + ${zxing.version} + + + org.bytedeco + javacv-platform + ${javacv.version} + + + org.apache.commons + commons-compress + ${commons-compress.version} + + + net.lingala.zip4j + zip4j + ${zip4j.version} + + + com.zaxxer + HikariCP + ${hikaricp.version} + + + com.dameng + DmJdbcDriver8 + ${dm-jdbc.version} + + + com.github.pagehelper + pagehelper-spring-boot-starter + ${pagehelper.version} + + + junit + junit + ${junit.version} + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + + + diff --git a/WebErp/weberp/pom.xml b/WebErp/weberp/pom.xml new file mode 100644 index 0000000..a5e6d5a --- /dev/null +++ b/WebErp/weberp/pom.xml @@ -0,0 +1,244 @@ + + + 4.0.0 + + + org.example + WebErp + 1.0-SNAPSHOT + + + weberp + jar + + weberp + Spring Boot application module + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + ch.qos.logback + logback-classic + + + + com.mysql + mysql-connector-j + + + com.microsoft.sqlserver + mssql-jdbc + + + com.dameng + DmJdbcDriver8 + + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.mybatis + mybatis + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + + + com.github.pagehelper + pagehelper-spring-boot-starter + + + + io.jsonwebtoken + jjwt-api + + + io.jsonwebtoken + jjwt-impl + runtime + + + io.jsonwebtoken + jjwt-jackson + runtime + + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-annotations + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + jakarta.servlet + jakarta.servlet-api + provided + + + org.springframework.boot + spring-boot-starter-security + + + com.google.code.gson + gson + + + + org.graalvm.js + js + pom + + + org.graalvm.js + js-scriptengine + + + org.graalvm.sdk + graal-sdk + + + + javax.xml.bind + jaxb-api + + + com.sun.xml.bind + jaxb-impl + runtime + + + com.googlecode.juniversalchardet + juniversalchardet + + + com.github.jai-imageio + jai-imageio-core + + + com.itextpdf + itext7-core + pom + + + + e-iceblue + spire.doc + + + e-iceblue + spire.xls + + + e-iceblue + spire.presentation + + + com.aspose + aspose-words + jdk17 + + + com.aspose + aspose-cells + + + com.aspose + aspose-slides + jdk16 + + + + com.google.zxing + core + + + com.google.zxing + javase + + + org.bytedeco + javacv-platform + + + org.apache.commons + commons-compress + + + net.lingala.zip4j + zip4j + + + com.zaxxer + HikariCP + + + + org.springframework.boot + spring-boot-starter-test + test + + + junit + junit + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + org.example.WebErpApplication + + + + org.apache.maven.plugins + maven-compiler-plugin + + 16 + + + + + diff --git a/WebErp/weberp/src/main/img.png b/WebErp/weberp/src/main/img.png new file mode 100644 index 0000000..362487a Binary files /dev/null and b/WebErp/weberp/src/main/img.png differ diff --git a/WebErp/weberp/src/main/java/org/example/Api/BaseHandler.java b/WebErp/weberp/src/main/java/org/example/Api/BaseHandler.java new file mode 100644 index 0000000..6f1376a --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Api/BaseHandler.java @@ -0,0 +1,718 @@ +package org.example.Api; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.*; +import jakarta.servlet.http.*; +import org.example.Utils.LanguageUtil; +import org.example.Entity.BaseResponse.BaseResponse; +import org.example.Entity.CusException.CusException; +import org.example.Entity.System.LoginUserInfo; +import org.example.Entity.Attributes.*; +import org.example.Impl.BaseImpl; +import org.example.Utils.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.servlet.support.RequestContext; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.temporal.Temporal; +import java.util.*; +import java.util.zip.GZIPOutputStream; + +import org.example.Enums.LoginCode; + +import static org.example.Utils.NativeExtensionUtils.*; + +/** + * ============================================================================== + * 功能描述:BaseHandler 对外开放的接口父类 + * ============================================================================== + */ +public abstract class BaseHandler { + private static final Logger log = LoggerFactory.getLogger(BaseHandler.class); + + protected HttpServletRequest Request; + protected HttpServletResponse Response; + protected HttpSession Session; + protected Cookie Cookie; + protected ServletContext Application; + protected HttpServletRequest context; + protected String methodName, ModuleId, MenuId, MenuCode, language; + protected BaseResponse response; + protected int currPage = 0, pageSize = 0, startsize = 0; + protected boolean isCustomer = false, isWindowsDirver = true; // IsCustomer:标识为游客客户端 IsWindowsDirver:PC端访问 + protected LocalDateTime startTime, endTime; + + private static long requestCount = 0, maxPMins = 0, lastCount = 0, lastMins = 0; + private static LocalDateTime startDate = LocalDateTime.now(); + private static LocalDateTime maxPDate = LocalDateTime.now(); + + public RequestContext requestContext; + + @Autowired + private ObjectMapper objectMapper; + + protected String getUserName() { + return getUser() != null ? getUser().UserName : ""; + } + + protected String getUserId() { + return getUser() != null ? getUser().UserId : ""; + } + + protected LoginUserInfo _user; + + protected LoginUserInfo getUser() { + + return _user = getBImpl().getUser(); + + + } + + protected String getUserSessionName() { + return getBImpl().getUserSessionName(); + } + + protected String getSessionId() { + return getBImpl().getSessionId(); + } + + protected String getDriver() { + return getBImpl().getDriver(); + } + + protected String getAppDomain() throws UnsupportedEncodingException { + return getBImpl().getAppDomain(); + } + + /** + * 附件路径 + */ + protected String getAttcPath() { + return getBImpl().getAttcPath(); + } + + //【200426】未找到的提示 + protected String notFindModuleMsg; + + protected DbOperator getDbOperator() { + return getDbOprator(); + } + + protected DbOperator getDbOprator() { + return getBImpl().getDbOperator(); + } + + private BaseImpl _bImpl = null; + + protected BaseImpl getBImpl() { + if (_bImpl == null) { + _bImpl = new BaseImpl(); + } + return _bImpl; + } + + public void processRequest(HttpServletRequest ctx) throws IOException, ServletException { + boolean isDebug = toBoolean(WebConfigUtil_web.get("debug", "")); + if (!isDebug) { + // 对应C#的DEBUG编译符号判断,可根据实际环境调整 + isDebug = false; + } + + _bImpl = null; + startTime = LocalDateTime.now(); + + // 网站访问量统计 + requestCount++; + int mins = (int) Duration.between(startDate, LocalDateTime.now()).toMinutes(); + if (mins > lastMins) { + lastMins = mins; + if (requestCount - lastCount > maxPMins) { + maxPDate = LocalDateTime.now(); + maxPMins = requestCount - lastCount; + } + lastCount = requestCount; + } + + SingleUserHandler.cacheKey = (getUserSessionName()); + context = ctx; + Session = ctx.getSession(); + Application = ctx.getServletContext(); + initSystemParams(ctx); + response = new BaseResponse(); + response.setSuccess(false); + response.setMsg(""); + + methodName = getMethod(); + Class type = this.getClass(); + if (isNullOrEmpty(methodName)) { + response.setSuccess(false); + response.setMsg(LanguageUtil.WrongInterfaceName); + writeResponse(); + return; + } + + Method method = null; + try { +// out.println(method + " me"); + method = type.getMethod(methodName); + + } catch (NoSuchMethodException e) { + log.warn(String.valueOf(e.getMessage() + "Err:ApiFunctionName,Please CheckOut!!")); + response.setSuccess(false); + response.setMsg(LanguageUtil.WrongInterfaceName); + writeResponse(); + return; + } + + String mName = methodName.substring(0, 1) + + methodName.substring(methodName.length() - 2, methodName.length() - 1) + + "_" + methodName.length(); + mName = mName.toUpperCase(); +// out.println(mName + "mname"); + try { + RequestCheckAttribute webCheck = AttributeUtils.getAttribute(method); + if (webCheck == null) { + webCheck = AttributeUtils.getCustomAttribute(type, RequestCheckAttribute.class); + } + if (webCheck != null) { + webCheck.setBImpl(getBImpl()); // 现在可以正常调用setter + } + + log.debug(String.valueOf("当前方法:" + methodName + ",匹配的CheckParams:" + (webCheck != null ? webCheck.CheckParams : "null") + + ",匹配的Log:" + (webCheck != null ? webCheck.Log : "null") + + ",匹配的CheckLogin:" + (webCheck != null ? webCheck.CheckLogin : "null"))); +// out.println("webcheck是"+webCheck); + // 登录检查 + boolean declaredPublic = webCheck != null && !webCheck.CheckLogin; + boolean allowedPublic = declaredPublic && PublicApiRegistry.isPublicEndpoint(type, methodName); + boolean requiresLogin = webCheck == null || webCheck.CheckLogin || (declaredPublic && !allowedPublic); + if (requiresLogin && !checkLoginByPro()) { +// if (true && !checkLoginByPro()) { + goLogin(); + } else { + if (!isNullOrEmpty(getUserName())) { + ActiveUserUtil.active( + (getUser() != null && !isNullOrEmpty(getUser().Token) ? + getUser().Token.hashCode() + "" : getSessionId()), + getUserName() + ); + } + + String[] errMsg = new String[1]; + if (webCheck == null || webCheck.validParams(errMsg)) { + getDbOperator().setCloseCon(true); // 使用同一个连接,提高速度 + initReqPms(); +// out.println(method + " 1me " + objectMapper.writeValueAsString(response) + " re "); + if (webCheck != null && webCheck.Cache && toBoolean(WebConfigUtil_web.get("Cache", ""))) { + Method finalMethod = method; +// out.println(method + " me1.5" + objectMapper.writeValueAsString(response) + "re"); + String result = (String) webCheck.getCacheVal(this, () -> { + try { + finalMethod.invoke(this); +// out.println(finalMethod + " 2me" + objectMapper.writeValueAsString(response) + "re"); + return objectMapper.writeValueAsString(response); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + response.setEncodedResult(result); + } else { +// out.println(method + " 3me" + objectMapper.writeValueAsString(response) + "re"); + method.invoke(this); + } + log.debug(String.valueOf(response.getData() + "dataS")); +// getDbOperator().dispose(); + endTime = LocalDateTime.now(); + String logName = (webCheck == null) ? mName : (webCheck.Name != null ? webCheck.Name : mName); + + double t = Duration.between(startTime, endTime).toMillis(); + String idValue = getBImpl().Request("idValue"); + + StringBuilder msg = new StringBuilder(); + if (isNullOrEmpty(idValue)) { + msg.append(String.format("执行%s,耗时%sms", logName, t)); + if (!isNullOrEmpty(ModuleId)) { + msg.append(String.format(",模块:%s", ModuleId)); + } + if (isDebug) { + msg.append(objectMapper.writeValueAsString(getBImpl().getAllRequest())); + } + } else { + msg.append(String.format("执行%s,耗时%sms,模块:%s,主键:%s", logName, t, ModuleId, idValue)); + if (isDebug) { + msg.append(objectMapper.writeValueAsString(getBImpl().getAllRequest())); + } + } + + if (webCheck == null || webCheck.Log) { + LoggerHandler.info(this, msg.toString()); + } + + if (t > 2000) { + LoggerHandler.dbLog(this, getDbOperator(), "warn", ModuleId, logName, JSON.Encode(getBImpl().getAllRequest()), t, "", null); + } + } else { + response.setSuccess(false); + response.setMsg(errMsg[0]); + } + + if (webCheck == null || (webCheck.WriteRespose && response.isWriteResponse())) { + if (response.isSuccess() && isNullOrEmpty(response.getToken()) && + !"LoginOut".equals(methodName) && getUser() != null && !isNullOrEmpty(getUser().Token)) { + response.setToken(JwtHelp.refreshToken(getUser(), getUser().Token, 7200)); + if (!isNullOrEmpty(response.getToken())) { + getUser().Token = (response.getToken()); + BaseImpl.setSessionVal(getUserSessionName(), getUser()); + } + } + + if (!isNullOrEmpty(response.getMsg()) && + response.getMsg().contains("execute sql error")) { + response.setMsg(response.getMsg().substring( + response.getMsg().indexOf("execute sql error"))); + } + + writeResponse(); + } + } + } catch (Exception ex) { +// System.err.println("捕获到异常类型:" + ex.getClass().getName()); +// System.err.println("异常消息:" + ex.getCause().getMessage()); + log.error("Exception caught", ex); // 打印完整堆栈,查看异常来源 + + String idValue = getBImpl().Request("idValue"); + String errMsg = ExceptionSummaryUtil.summarizeForClient(ex); + boolean sqlExecuteError = ExceptionSummaryUtil.containsMessage(ex, "execute sql error"); + + log.debug(String.valueOf("Err:errMsg 方法里有异常错误,排查——>" + methodName)); + + String errStackMsg = "错误:" + errMsg; + + StringBuilder msg = new StringBuilder(); + if (isNullOrEmpty(idValue)) { + msg.append(String.format("接口:%s,%s", mName, errStackMsg)); + } else { + msg.append(String.format("接口:%s,模块:%s,主键:%s,%s", mName, ModuleId, idValue, errStackMsg)); + } + + Response.reset(); + response = new BaseResponse(); + response.setSuccess(false); + response.setMsg(LanguageUtil.ServerError + + "查看详情" + msg); + + if (isDebug) { + response.setMsg(errMsg); + } + + if (!(ex instanceof CusException) || ((CusException) ex).Log) { + if (isDebug || !sqlExecuteError) { + LoggerHandler.error(msg, ex); + } else { + LoggerHandler.dbLog(this, getDbOperator(), "error", ModuleId, methodName, + JSON.Encode(getBImpl().getAllRequest()), 0, msg.toString().replace(mName, methodName), ex); + } + } + + writeResponse(); + } finally { + if (getBImpl() != null) { + getBImpl().getReqHandler().clearPms(); + } + } + } + + /** + * 验证完后,初始化参数 + */ + protected void initReqPms() { + language = getBImpl().Request("lg", "cn"); + currPage = parseInt(getBImpl().Request("currPage", getBImpl().Request("page", "1"))); + pageSize = parseInt(getBImpl().Request("pageSize", getBImpl().Request("limit", "-1"))); + isWindowsDirver = toBoolean(getBImpl().Request("windowsDirver", "1")); + + ModuleId = getBImpl().Request("ModuleId").split(",")[0]; // PC端用 + MenuId = parseInt(getBImpl().Request("MenuId", getBImpl().Request("menuid"))) + ""; // 通用 + MenuCode = getBImpl().Request("menucode"); // 手机端用 + ModuleId = isNullOrEmpty(ModuleId) ? MenuCode : ModuleId; + + if (!isNullOrEmpty(ModuleId)) { + ModuleId = ModuleId.replace("\r", "").replace("\n", ""); + } + MenuCode = ModuleId; + + startsize = (currPage - 1) * pageSize; + startsize = Math.max(startsize, 0); + + // 【200426】未找到的msg + notFindModuleMsg = "未找到模块号【" + ModuleId + "】的配置数据,请检查相应模块的配置是否正确!可能是多余的空格、回车,数字字母写错,模块暂未配置等情况!"; + } + + protected String getMethod() { + if (requestContext != null) { + // 从请求上下文中获取路由数据(模拟RouteValueDictionary) + Map routeValues = getRouteValues(requestContext); + if (routeValues.containsKey("method")) { + Object methodValue = routeValues.get("method"); + return methodValue != null ? methodValue.toString() : ""; + } else { + return getBImpl().Request("method", getBImpl().Request("action")); + } + } + return getBImpl().Request("method", getBImpl().Request("action")); + } + + // 辅助方法:获取路由参数(根据实际框架实现) + private Map getRouteValues(RequestContext requestContext) { + // 1. 若使用Spring MVC,可通过RequestContextHolder获取请求参数 + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attributes != null) { + HttpServletRequest request = attributes.getRequest(); + // 2. 转换请求参数为Map(包含路由参数和请求参数) + return FormParamUtil.convertToMyBatisParamMap(request); + } + return new HashMap<>(); + } + +// public static void gZipEncodePage() { +// HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).Request(); +// HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse(); +// +// String acceptEncoding = request.getHeader("Accept-Encoding"); +// if (!isNullOrEmpty(acceptEncoding) && +// (acceptEncoding.contains("gzip") || acceptEncoding.contains("deflate"))) { +// +// if (acceptEncoding.contains("gzip")) { +// +// response.setHeader("Content-Encoding", "gzip"); +// response.setOutputStream(new GZIPOutputStream(response.getOutputStream())); +// +// } else if (acceptEncoding.contains("deflate")) { +// // 实现deflate压缩 +// } +// } +// } + + private static String webVer = WebConfigUtil_web.get("WebVersion", "1.0.0.0"); + + /** + * 回写数据 + */ + protected void writeResponse() throws IOException { + HttpServletResponse resp = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse(); + resp.setHeader("WebAppVer", webVer); + + if (resp.isCommitted()) { + log.debug(String.valueOf("响应已提交,跳过重复写入:" + Request.getRequestURI())); + return; + } + +// String content = isNullOrEmpty(response.getEncodedResult()) ? +// objectMapper.writeValueAsString(response) : response.getEncodedResult(); + String content = isNullOrEmpty(response.getEncodedResult()) ? + JSON.Encode(response) : response.getEncodedResult(); + + String recodings = Request.getHeader("Response-Encoding"); + boolean gzipResponse = recodings != null && recodings.contains("gzip"); + + if (Request.getHeader("Referer") != null) { + String refDom = getDomainFromUrl(Request.getHeader("Referer")); + String curUrl = getDomainFromUrl(Request.getRequestURL().toString()); + + if (!refDom.equals(curUrl)) { + resp.setHeader("Access-Control-Allow-Origin", refDom); + resp.setHeader("Access-Control-Allow-Credentials", "true"); + } + } else { + resp.setHeader("Access-Control-Allow-Origin", "*"); + } + + if (gzipResponse) { + content = URLEncoder.encode(content, StandardCharsets.UTF_8); + content = GZipUtil.gZip(content); + resp.setHeader("Response-Encoding", "gzip"); + } + + resp.setContentType("text/plain;charset=UTF-8"); + if (!Response.isCommitted()) { + try (OutputStream os = resp.getOutputStream()) { + os.write(content.getBytes(StandardCharsets.UTF_8)); + os.flush(); + } catch (Exception e) { + // 捕获写入异常,避免因流关闭/已提交导致的崩溃 + log.debug(String.valueOf("响应已提交,跳过重复写入:" + Request.getRequestURI())); + throw new IOException("写入响应流失败:" + e.getMessage(), e); + } + } + } + + private String getDomainFromUrl(String url) { + try { + URL urlObj = new URL(url); + return urlObj.getProtocol() + "://" + urlObj.getAuthority(); + } catch (Exception e) { + return ""; + } + } + + /** + * 初始化系统参数 + */ + protected void initSystemParams(HttpServletRequest ctx) { + try { + ctx.setCharacterEncoding("UTF-8"); + this.Request = ctx; + this.Response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse(); + this.Response.setContentType("text/plain"); + // 从 ctx 重新获取 Session,避免依赖 this.Session 字段 +// HttpSession session = ctx.getSession(false); // 注意:false 表示不创建新 Session +// // 用户信息初始化 +// _user = (LoginUserInfo) session.getAttribute(getUserSessionName()); +// if (getUser() == null) { +// String userId = getBImpl().Request("userid"); +// String userName = getBImpl().Request("username"); +// if (!isNullOrEmpty(userId) && !isNullOrEmpty(userName)) { +// _user = new LoginUserInfo(); +// getUser().UserId = (userId); +// getUser().UserName = (userName); +// session.setAttribute(getUserSessionName(), getUser()); +// } +// } + } catch (Exception e) { + LoggerHandler.error("inierror", e); + } + } + + /** + * 返回给客户端登录的指令 + */ + protected void goLogin() throws IOException { + String authorization = context == null ? "" : context.getHeader("Authorization"); + // 2. 统一将 null 转换为空字符串,避免后续调用方法时报错 + authorization = (authorization == null) ? "" : authorization; + if (!isNullOrEmpty(authorization) && authorization.startsWith("Bearer ")) { +// log("登录失效:" + authorization); + } + + response.setSuccess(false); + response.setMsg("response_login"); + writeResponse(); + } + + /** + * 重定向到指定页面 + */ + protected void redirect(String url, Object data) { + response.setSuccess(false); + response.setMsg("response_redirect"); + Map redirectData = new HashMap<>(); + redirectData.put("url", url); + redirectData.put("data", data); + response.setData(redirectData); + } + + /** + * 向页面输送脚本 + */ + protected void registerScript(String script, Object data) { + response.setSuccess(false); + response.setMsg("response_RegisterScript"); + Map scriptData = new HashMap<>(); + scriptData.put("script", script); + scriptData.put("data", data); + response.setData(scriptData); + } + + /** + * 检查参数是否为空,被RequestCheck类替代 + */ + protected boolean checkParam(String paramname, Object paramval) { + boolean ok = true; + if (paramval instanceof Integer) { + if ((Integer) paramval == 0) { + ok = false; + } + } else if (paramval == null || isNullOrEmpty(paramval.toString())) { + ok = false; + } + + if (!ok) { + response.setSuccess(false); + response.setMsg(String.format("%s:%s", LanguageUtil.GetString("InvalidParameter"), paramname)); + } + return ok; + } + + /** + * 检查登录状态 + */ +// @RequestCheck(CheckLogin = false) + public boolean checkLogin() { + return checkLoginByPro(); + } + + protected boolean checkLoginByPro() { + boolean isLoggedIn = getUser() != null && !"0".equals(getUser().UserId); +// out.println("isLoggedIn: " + isLoggedIn + getUser().UserName); + response.setSuccess(isLoggedIn); + response.setData(isLoggedIn ? getUser() : null); + response.setSharToken(toBoolean(WebConfigUtil_web.get("SharLogin", "1"))); + + if (ToInt32(WebConfigUtil.get("MsgBtnOrder", "0")) == 1) { + response.AppCfg = new HashMap<>(); + response.AppCfg.put("MsgBtnOrder", 1); + } + if (isLoggedIn && "CheckLogin".equals(methodName)) { + if (SingleUserHandler.hasUnLoginUser(getSessionId()) || + (!SingleUserHandler.hasSessionUser(getSessionId()) && SingleUserHandler.hasLoginUser(getUser().UserId))) { + loginOut(); + response.setSuccess(false); + response.setData(null); + } else { + SingleUserHandler.add(getSessionId(), getUser().UserId); + } + + if (response.isSuccess() && isDefaultPwd(getUser().Pwd) && toBoolean(WebConfigUtil_web.get("RestInitPwd", ""))) { + response.setMsg("请修改密码!"); + response.setOther(LoginCode.ResetPwd); + } + + if (isNullOrEmpty(getUser().Token) && !toBoolean(WebConfigUtil_web.get("SharLogin", "1"))) { + response.setToken(JwtHelp.createToken(getUser(), 7200)); + } + } +// out.println("返回的正否" + response.isSuccess()); + return response.isSuccess(); + } + + protected boolean isDefaultPwd(String pwd) { + return "51B4FCEDBC944C6F5627E732CD9560B8B1204FDB".equals(pwd) || + "E0B97F132980BB0856ECC51BA256626F3CB68D11".equals(pwd) || + "A839A93FD1A6699CAA208BB1DB9E2D25A7013E86".equals(pwd); + } + + // @RequestCheckAttribute(log = false) + public void loginOut() { + int delay = parseInt(getBImpl().Request("delay")); + String userId = "", sessionId = getSessionId(); + + if (getUser() == null) { + return; + } + userId = getUser().UserId; +// out.println(userId + "userid:" + sessionId); + if (delay > 0) { + SingleUserHandler.waitLoginOut(sessionId); + DateTimeUtil.setTimeOut(() -> { + if (SingleUserHandler.hasWaitUnLoginUser(sessionId)) { + SingleUserHandler.unLogin(sessionId); + } + }, delay * 1000); + } else { + Session.removeAttribute(getUserSessionName()); + SingleUserHandler.remove(sessionId); + JwtHelp.removeUserCache(getUser()); + CacheUtil.remove(getUser().Token); + CacheUtil.clearUserCache(getUser().UserName, getUser().UserId); + + if (getBImpl().isPhone()) { + response.setOther(0); + } + } + + response.setData(null); + response.setSuccess(true); + } + + public void unLoginOut() { + SingleUserHandler.unLogin(getSessionId()); + response.setSuccess(true); + } + + /** + * 清除服务器缓存 + */ +// @RequestCheckAttribute(checkLogin = false) + public void clearCache() { + CacheUtil.clearCache(); + response.setSuccess(true); + } + + /** + * 获取活跃用户 + */ +// @RequestCheckAttribute(checkLogin = true) + public void getActiveUser() { + Object[] users = ActiveUserUtil.getActiveUser(); + response.setTot(users == null ? 0 : users.length); + response.setData(users); + response.setSuccess(true); + } + + /** + * 获取平均请求数 + */ +// @RequestCheckAttribute(checkLogin = true) + public void getAvgRequest() { + boolean reset = toBoolean(getBImpl().Request("reset")); + int mins = Math.max((int) Duration.between((Temporal) startDate, LocalDateTime.now()).toMinutes(), 1); + + + Date StartDate = Date.from( + startDate.atZone(ZoneId.systemDefault()) // 绑定系统默认时区 + .toInstant() // 转换为 Instant + ); + + LocalDateTime now = LocalDateTime.now(); + Date nowDate = Date.from(now.atZone(ZoneId.systemDefault()).toInstant()); + Map data = new Hashtable<>(); + data.put("tot", requestCount); + data.put("start", startDate); + data.put("avg", requestCount / Math.max(1, mins)); + data.put("maxps", maxPMins); + data.put("maxPDate", maxPDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); + data.put("mins", mins); + data.put("runed", DateTimeUtil.getDateDiff(StartDate, nowDate)); + + response.setData(data); + response.setSuccess(true); + + if (reset) { + resetPCount(); + } + } + + // @RequestCheckAttribute(checkLogin = false) + public void getDate() { + response.setSuccess(true); + response.setData(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); + } + + private void resetPCount() { + startDate = LocalDateTime.now(); + requestCount = 0; + maxPMins = 0; + lastMins = 0; + } + + public boolean isReusable() { + return toBoolean(WebConfigUtil_web.get("ReusaHandler", "1")); + } + +} diff --git a/WebErp/weberp/src/main/java/org/example/Api/LoggerHandler.java b/WebErp/weberp/src/main/java/org/example/Api/LoggerHandler.java new file mode 100644 index 0000000..a3035b9 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Api/LoggerHandler.java @@ -0,0 +1,289 @@ +package org.example.Api; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.example.Entity.System.LoginUserInfo; +import org.example.Impl.BaseImpl; +import org.example.Utils.DateTimeUtil; +import org.example.Utils.DbOperator; +import org.example.Utils.NativeExtensionUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; + + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * 系统日志记录入口 + */ +@Service +public class LoggerHandler { + @Autowired + private static JdbcTemplate jdbcTemplate; + private static final Log log = LogFactory.getLog(LoggerHandler.class); + private static final Lock lockObj = new ReentrantLock(); + private static Date lastCheckDate = new Date(); + + public static void debug(Object happenobj, Object message) { + if (log.isDebugEnabled()) { + try { + log.debug(new LogInfo(happenobj).setMsg(message)); + } catch (Exception e) { + error(message, e); + } + } + } + + public static void debug(Object happenobj, Object message, Exception exception) { + if (log.isDebugEnabled()) { + try { + log.debug(new LogInfo(happenobj).setMsg(message).setException(exception)); + } catch (Exception e) { + error(message, e); + } + } + } + + public static void error(Object happenobj, Object message) { + if (log.isErrorEnabled()) { + try { + log.error(new LogInfo(happenobj).setMsg(message)); + } catch (Exception e) { + error(message, e); + } + } + } + + public static void error(Object happenobj, Object message, Exception exception) { + if (log.isErrorEnabled()) { + try { + log.error(new LogInfo(happenobj).setMsg(message).setException(exception)); + } catch (Exception ignored) { + } + } + } + + public static void fatal(Object happenobj, Object message) { + if (log.isFatalEnabled()) { + try { + log.fatal(new LogInfo(happenobj).setMsg(message)); + } catch (Exception e) { + error(message, e); + } + } + } + + public static void fatal(Object happenobj, Object message, Exception exception) { + if (log.isFatalEnabled()) { + try { + log.fatal(new LogInfo(happenobj).setMsg(message).setException(exception)); + } catch (Exception e) { + error(message, e); + } + } + } + + public static void info(Object happenobj, Object message) { + if (log.isInfoEnabled()) { + try { + log.info(new LogInfo(happenobj).setMsg(message)); + } catch (Exception e) { + error(message, e); + } + } + } + + public static void info(Object happenobj, Object message, Exception exception) { + if (log.isInfoEnabled()) { + try { + log.info(new LogInfo(happenobj).setMsg(message).setException(exception)); + } catch (Exception e) { + error(message, e); + } + } + } + + public static void warn(Object happenobj, Object message) { + if (log.isWarnEnabled()) { + try { + log.warn(new LogInfo(happenobj).setMsg(message)); + } catch (Exception e) { + error(message, e); + } + } + } + + public static void warn(Object happenobj, Object message, Exception exception) { + if (log.isWarnEnabled()) { + try { + log.warn(new LogInfo(happenobj).setMsg(message).setException(exception)); + } catch (Exception e) { + error(message, e); + } + } + } + + public static void dbLog(Object happenobj, DbOperator dbOperator, String level, String dllcoid, + String method, String args, double passT, String msg, Exception exception) { +// String conStr = dbOperator.ConnectionString; +// if (conStr == null || conStr.isEmpty()) { +// return; +// } + + LogInfo info = new LogInfo(happenobj).setMsg(msg).setException(exception); + String sql = String.format( + "insert into %s (level, operatorId, operatorName, dllcoid, className, method, args, passTime, msg) " + + "values ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s'); select @@identity;", + "p_sysWebProLogTab", + level, + info.getUserFId(), + info.getOperatorName(), + dllcoid, + info.getClassname(), + method, + args.replace("'", "\""), + passT, + msg.replace("'", "\"") + ); + +// if (dbOperator.hasLogTab()) + if (NativeExtensionUtils.isNullOrEmpty(jdbcTemplate)) { + DateTimeUtil.setTimeOut(() -> { + try { +// DbOperator _dbOperator = new DbOperator(jdbcTemplate); + String id = jdbcTemplate.queryForObject(sql, String.class) + ""; + if ("error".equals(level)) { + error(happenobj, "请到日志表p_sysWebProLogTab中查看详细信息,日志id" + id); + } + } catch (Exception ignored) { + } + }, 1); + } else if ("error".equals(level) && (new Date().getTime() - lastCheckDate.getTime()) > 600000) { + lastCheckDate = new Date(); + error(happenobj, "日志表p_sysWebProLogTab未创建,请先创建,然后重启应用程序,再在日志表中查看详细信息!创建语句如下:\n" + + "CREATE TABLE [dbo].[p_sysWebProLogTab](\n" + + " [id] [bigint] PRIMARY KEY IDENTITY(1,1) NOT NULL,\n" + + " [level] varchar(10),\n" + + " [className] varchar(100),\n" + + " [operatorId] varchar(100),\n" + + " [operatorName] varchar(100),\n" + + " [dllcoId] varchar(100) NULL,\n" + + " [passTime] decimal(18,2) NULL,\n" + + " [method] varchar(100) NULL,\n" + + " [args] text NULL,\n" + + " [msg] text NULL,\n" + + " [happenTime] datetime not NULL default getdate())"); + } + } + + static class LogInfo { + private final Object happenobj; + private Object msg; + private Exception exception; + private String classname; + private LoginUserInfo user; + private String userId; + + public LogInfo(Object happenobj) { + this.happenobj = happenobj; + } + + public LogInfo setMsg(Object msg) { + this.msg = msg; + return this; + } + + public LogInfo setException(Exception exception) { + this.exception = exception; + return this; + } + + public String getClassname() { + if (classname == null && happenobj != null) { + String hname = happenobj.getClass().getName(); + String[] parts = hname.split("\\."); + hname = parts[parts.length - 1]; + if (!hname.isEmpty()) { + classname = hname.substring(0, 1) + + hname.substring(hname.length() - 2, hname.length() - 1) + + "_" + hname.length(); + classname = classname.toUpperCase(); + } else { + classname = ""; + } + } + return classname; + } + + private LoginUserInfo getUser() { + if (user == null) { + if (happenobj != null) { + try { + Field field = happenobj.getClass().getDeclaredField("user"); + field.setAccessible(true); + user = (LoginUserInfo) field.get(happenobj); + } catch (Exception ignored) { + } + } + if (user == null) { + user = new BaseImpl().getUser(); + } + } + return user; + } + + public String getUserId() { + if (userId == null) { + String actualUserId = getUserFId(); + if (actualUserId != null && !actualUserId.isEmpty()) { + userId = actualUserId.substring(0, 1) + + "**************************".substring(0, actualUserId.length() - 1); + userId = userId.toUpperCase(); + } else { + userId = ""; + } + } + return userId; + } + + public String getUserFId() { + if (userId == null) { + LoginUserInfo u = getUser(); + if (u != null) { + userId = u.UserId != null ? u.UserId : u.UserName; + } else { + userId = ""; + } + } + return userId; + } + + public String getOperatorName() { + LoginUserInfo u = getUser(); + if (u != null) { + return u.UserName != null ? u.UserName : u.UserId; + } + return ""; + } + + public String getOperatorId() { + String name = getOperatorName(); + if (!name.isEmpty()) { + return name.substring(0, 1) + + "**************************".substring(0, name.length() - 1); + } + return ""; + } + + @Override + public String toString() { + return msg != null ? msg.toString() : ""; + } + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Api/OptBaseHandler.java b/WebErp/weberp/src/main/java/org/example/Api/OptBaseHandler.java new file mode 100644 index 0000000..e4f5eed --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Api/OptBaseHandler.java @@ -0,0 +1,59 @@ +package org.example.Api; + +import jakarta.servlet.ServletConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import org.example.Impl.BaseImpl; +import org.example.Impl.OptBaseImpl; +import org.example.Impl.Sql.factory.AllInOneSqlFactory; +import org.example.ModuleApi.ModuleAjaxApi.mapper.CRMapper; +import org.example.ModuleApi.ModuleAjaxApi.mapper.DMCrmMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.io.IOException; + +public class OptBaseHandler extends BaseHandler { + + @Autowired + protected JdbcTemplate jdbcTemplate; + + + @Value("${custom.database.type}") + protected String databaseType; + + + @Autowired + AllInOneSqlFactory allInOneSqlFactory; + + @Autowired + protected CRMapper crmapper; + + @Autowired + protected DMCrmMapper DMCrmMapper; + + + + private BaseImpl _opbImpl = null; + + @Override + protected void initSystemParams(HttpServletRequest ctx) { + super.initSystemParams((HttpServletRequest) ctx); + _opbImpl = null; + } + + @Override + protected BaseImpl getBImpl() { + // 延迟初始化:如果_opbImpl为null,则创建OptBaseImpl实例并赋值 + if (_opbImpl == null) { + _opbImpl = new OptBaseImpl(jdbcTemplate, databaseType, allInOneSqlFactory, crmapper, DMCrmMapper); + } + return _opbImpl; + } + + + +} diff --git a/WebErp/weberp/src/main/java/org/example/Api/PublicApiRegistry.java b/WebErp/weberp/src/main/java/org/example/Api/PublicApiRegistry.java new file mode 100644 index 0000000..cf7ef23 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Api/PublicApiRegistry.java @@ -0,0 +1,40 @@ +package org.example.Api; + +import java.util.Map; +import java.util.Set; + +public final class PublicApiRegistry { + private static final Map> PUBLIC_METHODS = Map.of( + "org.example.Auth.controller.AuthController", Set.of( + "Login", + "ResetPwdByVerify", + "CheckLogin", + "GetUserByName", + "GenerateCaptcha", + "SendPhoneCode", + "verifyCode", + "GetPhoneNumber", + "ResetPwdByPhone" + ), + "org.example.SystemApi.controller.SystemAjaxApi", Set.of( + "GetSystemInfo", + "GetEmaUrl", + "GetSystemLoginInfo", + "GetProSysType", + "GetDbServer", + "GetWebUpdateInfo" + ) + ); + + private PublicApiRegistry() { + } + + public static boolean isPublicEndpoint(Class handlerType, String methodName) { + if (handlerType == null || methodName == null || methodName.isBlank()) { + return false; + } + + Set methods = PUBLIC_METHODS.get(handlerType.getName()); + return methods != null && methods.contains(methodName); + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Api/RequestHandler.java b/WebErp/weberp/src/main/java/org/example/Api/RequestHandler.java new file mode 100644 index 0000000..8aed9c2 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Api/RequestHandler.java @@ -0,0 +1,264 @@ +package org.example.Api; + + +import org.example.Enums.SystemTypeEnums; +import org.example.Utils.AESUtil; +import org.example.Utils.GZipUtil; +import org.example.Impl.BaseImpl; +import jakarta.servlet.http.HttpServletRequest; +import org.example.Utils.JSON; +import org.example.Utils.WebConfigUtil_web; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.util.Enumeration; +import java.util.Hashtable; + + +/** + * ============================================================================== + * 功能描述:RequestHandler + * ============================================================================== + */ +public class RequestHandler { + protected Hashtable requestPms; + protected Hashtable zipedPms; + + public RequestHandler() { + requestPms = new Hashtable<>(); + initPms(); + } + + + /** + * 重置form表单参数 + */ + protected void initPms() { + zipedPms = null; + try { + String pms = request("pms"); +// out.println(pms + " 1pms"); + if (pms != null && !pms.isEmpty()) { + try { + if (pms.contains("%")) { + pms = java.net.URLDecoder.decode(pms, StandardCharsets.UTF_8); + } + requestPms = (Hashtable) JSON.Decode(AESUtil.mobileDecrypt(pms), Hashtable.class); + + if (requestPms != null && !requestPms.isEmpty()) { + Hashtable temp = new Hashtable<>(); + for (String key : requestPms.keySet()) { + String lkey = key.toLowerCase(); + temp.put(lkey, requestPms.get(key)); + } + requestPms = temp; + } + } catch (Exception e) { + // 异常处理,可根据需要添加日志 + } + } + + if (requestPms == null) { + requestPms = new Hashtable<>(); + } + } catch (Exception e) { + // 异常处理,可根据需要添加日志 + } + } + + public void clearPms() { + BaseImpl.setSessionVal(WebConfigUtil_web.Session_Request, ""); + } + + /** + * 经过压缩后的参数 + */ + protected Hashtable getZipedPms() { + if (zipedPms != null) { + return zipedPms; + } + + if (isContentZiped()) { + HttpServletRequest request = getHttpServletRequest(); + try (Reader reader = new InputStreamReader(request.getInputStream(), request.getCharacterEncoding())) { + StringBuilder contentBuilder = new StringBuilder(); + char[] buffer = new char[1024]; + int bytesRead; + while ((bytesRead = reader.read(buffer)) != -1) { + contentBuilder.append(buffer, 0, bytesRead); + } + String content = contentBuilder.toString(); + + if (content != null && !content.isEmpty()) { + BaseImpl.setSessionVal(WebConfigUtil_web.Session_Request, content); + } else { + Object sessionContent = BaseImpl.getSessionVal(WebConfigUtil_web.Session_Request); + content = sessionContent != null ? sessionContent.toString() : ""; + } + + byte[] buf = java.util.Base64.getDecoder().decode(content); + content = java.net.URLDecoder.decode(GZipUtil.unZip(buf), StandardCharsets.UTF_8); + + if (content != null && !content.isEmpty()) { + zipedPms = (Hashtable) JSON.Decode(content, Hashtable.class); + if (zipedPms != null && !zipedPms.isEmpty()) { + Hashtable temp = new Hashtable<>(); + for (String key : zipedPms.keySet()) { + temp.put(key.toLowerCase(), zipedPms.get(key)); + } + zipedPms = temp; + return temp; + } + } + } catch (IOException e) { + // 异常处理,可根据需要添加日志 + } + } + return null; + } + + protected void setRequestPms(String key, String val) { + requestPms.put(key, val); + } + + /** + * 客户端类型 + */ + private int clinetType; + + public int getClinetType() { + return SystemTypeEnums.LoginType.Web.getValue(); + } + + public void setClinetType(int clinetType) { + this.clinetType = clinetType; + } + + public SystemTypeEnums.LoginType getLgType() { + try { + return SystemTypeEnums.LoginType.valueOf(String.valueOf(getClinetType())); + } catch (IllegalArgumentException e) { + return SystemTypeEnums.LoginType.Web; + } + } + + /** + * 内容编码类型 + */ + private String getContentEncoding() { + HttpServletRequest request = getHttpServletRequest(); + return request != null ? request.getHeader("Content-Encoding") : null; + } + + /** + * webrequest的内容是否是压缩过的 + */ + private boolean isContentZiped() { + String contentEncoding = getContentEncoding(); + return contentEncoding != null && + (contentEncoding.contains("gzip") || contentEncoding.contains("deflate")); + } + + /** + * 获取表单参数 + * + * @param key 表单键 + * @param defaultVal 默认值 + * @return 参数值 + */ + public String request(String key, String defaultVal) { + key = key.toLowerCase(); // 参数不区分大小写 + String val = null; + + try { + HttpServletRequest request = getHttpServletRequest(); + if (request == null) { + return defaultVal; + } + } catch (Exception e) { + // 在程序未初始化完成时访问会报错 + return defaultVal; + } + + Hashtable ziped = getZipedPms(); + if (ziped != null && ziped.containsKey(key)) { + val = String.valueOf(ziped.get(key)); + } + + if ((val == null || val.isEmpty()) && requestPms.containsKey(key)) { + val = String.valueOf(requestPms.get(key)); + } + + if (val == null || val.isEmpty()) { + HttpServletRequest request = getHttpServletRequest(); + val = request != null ? request.getParameter(key) : null; + if ((val == null || val.isEmpty()) && !key.isEmpty()) { + Enumeration paramNames = null; + if (request != null) { + paramNames = request.getParameterNames(); + } + if (paramNames != null) { + while (paramNames.hasMoreElements()) { + String paramName = paramNames.nextElement(); + if (paramName.equalsIgnoreCase(key)) { + val = request.getParameter(paramName); + break; + } + } + } + } + if (val == null || val.isEmpty()) { + val = defaultVal; + } + } + + return val; + } + + public String request(String key) { + return request(key, ""); + } + + public String pmsRequest(String key, String defaultVal) { + String val = null; + if (requestPms.containsKey(key)) { + val = String.valueOf(requestPms.get(key)); + } + return val != null ? val : defaultVal; + } + + public String pmsRequest(String key) { + return pmsRequest(key, ""); + } + + public Hashtable getAllRequest() { + Hashtable req = new Hashtable<>(); + HttpServletRequest request = getHttpServletRequest(); + if (request == null) { + return req; + } + + // 添加查询参数 + Enumeration queryParams = request.getParameterNames(); + while (queryParams.hasMoreElements()) { + String key = queryParams.nextElement(); + if (key != null && !key.isEmpty()) { + req.put(key, request.getParameter(key)); + } + } + + return req; + } + + /** + * 获取当前请求对象 + */ + protected HttpServletRequest getHttpServletRequest() { + return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + } + +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Api/SingleUserHandler.java b/WebErp/weberp/src/main/java/org/example/Api/SingleUserHandler.java new file mode 100644 index 0000000..6be7e06 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Api/SingleUserHandler.java @@ -0,0 +1,265 @@ +package org.example.Api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.Resource; +import org.example.Entity.System.LoginUserInfo; +import org.example.Utils.CacheUtil; +import org.example.Utils.WebConfigUtil_web; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.io.Serializable; +import java.time.Duration; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +import static org.example.Utils.NativeExtensionUtils.toBoolean; + +@Component +public class SingleUserHandler { + + // 登录状态枚举 + public enum UserLoginState { + LOGIN(1), UNLOGIN(2), WAIT_LOGIN_OUT(3); + + private final int value; + + UserLoginState(int value) { + this.value = value; + } + } + + // 关键修改:缓存存储 全局 LoginUserInfo + 状态 + SessionId(用Map封装) + private static class CacheUserInfo implements Serializable { + private static final long serialVersionUID = 1L; + private LoginUserInfo loginUserInfo; // 全局用户信息(🔥 改动:替换内部类,使用全局LoginUserInfo) + private UserLoginState state; + private String sessionId; + + // 无参构造 + getter/setter(支持Jackson序列化) + public CacheUserInfo() { + } + + public CacheUserInfo(LoginUserInfo loginUserInfo, UserLoginState state, String sessionId) { + this.loginUserInfo = loginUserInfo; + this.state = state; + this.sessionId = sessionId; + } + + // getter 方法(🔥 改动:补全序列化必需的getter,否则Jackson无法读取字段) + public LoginUserInfo getLoginUserInfo() { + return loginUserInfo; + } + + public UserLoginState getState() { + return state; + } + + public String getSessionId() { + return sessionId; + } + + // 🔥 改动:补全setter,用于修改状态和SessionId + public void setState(UserLoginState state) { + this.state = state; + } + + public void setSessionId(String sessionId) { + this.sessionId = sessionId; + } + } + + private static boolean getUseSingleUser() { + // 每次调用都会重新执行 WebConfigUtil_web.get,拿到最新值 + return toBoolean(WebConfigUtil_web.get("SingleUser", "")); + } + + public static String cacheKey = "loginDict"; + + + private static RedisTemplate redisTemplate; // 用Redis替代原C#的CacheUtil + + private static final ReentrantLock lock = new ReentrantLock(); // 替代lock语句 + + // 获取登录用户字典 + // 获取登录用户字典 + @SuppressWarnings("unchecked") + private static Map getInfoDict() { + Object obj = CacheUtil.get(cacheKey); + if (obj == null || !(obj instanceof Map)) { + return new HashMap<>(); + } + +// Map rawMap = (Map) obj; +// Map resultMap = new HashMap<>(); +// +// // 🔥 关键修改:不用 new ObjectMapper(),用 CacheUtil 里配置好的 OBJECT_MAPPER +// ObjectMapper mapper = CacheUtil.OBJECT_MAPPER; +// +// for (Map.Entry entry : rawMap.entrySet()) { +// String sessionId = entry.getKey(); +// Object value = entry.getValue(); +// if (value instanceof LinkedHashMap) { +// // 现在用的是带“忽略大小写”配置的 mapper,能识别 userId -> UserId +// CacheUserInfo cacheUserInfo = mapper.convertValue(value, CacheUserInfo.class); +// resultMap.put(sessionId, cacheUserInfo); +// } +// } +// +// System.out.println("【getInfoDict】转换后的缓存大小:" + resultMap.size()); + return (Map) obj; + } + + // 添加登录用户(新登录会踢掉旧登录) + // 🔥 改回:参数恢复为String username(适配其他地方的调用,传的是从实例里拿的UserId/UserName) + public static void add(String sessionId, String username) { + // 🔥 改回:恢复原有参数校验(去掉luser非空校验) + if (StringUtils.isEmpty(sessionId) || StringUtils.isEmpty(username) || !getUseSingleUser()) return; + + lock.lock(); + try { + Map dict = getInfoDict(); + // 🔥 逻辑不变:按username(实际是UserId)查询(统一标识字段) + CacheUserInfo cacheUserInfo = getLoginUser(username); + + if (cacheUserInfo == null) { + // 🔥 适配:创建全局LoginUserInfo实例,设置UserId为username(其他字段保持默认) + LoginUserInfo loginUserInfo = new LoginUserInfo(); + loginUserInfo.UserId = username; // 核心:把传入的username(UserId)赋值给全局用户实例 + // 实例化CacheUserInfo,封装全局LoginUserInfo+状态+SessionId + cacheUserInfo = new CacheUserInfo(loginUserInfo, UserLoginState.LOGIN, sessionId); + } else { + dict.remove(cacheUserInfo.getSessionId()); // 移除旧会话 + cacheUserInfo.setState(UserLoginState.LOGIN); // 更新状态 + cacheUserInfo.setSessionId(sessionId); // 更新SessionId + + } + dict.put(sessionId, cacheUserInfo); + // 缓存2小时 +// redisTemplate.opsForValue().set(cacheKey, dict, 2, TimeUnit.HOURS); + CacheUtil.set(cacheKey, dict, Duration.ofDays(2), null); + } finally { + lock.unlock(); + } + } + + // 移除会话 + public static void remove(String sessionId) { + if (StringUtils.isEmpty(sessionId) || !getUseSingleUser()) return; + + lock.lock(); + try { + Map dict = getInfoDict(); // 🔥 改动:适配CacheUserInfo类型 + dict.remove(sessionId); +// redisTemplate.opsForValue().set(cacheKey, dict, 2, TimeUnit.HOURS); + CacheUtil.set(cacheKey, dict, Duration.ofDays(2), null); + } finally { + lock.unlock(); + } + } + + // 标记用户为未登录状态 + public static void unLogin(String sessionId) { + if (StringUtils.isEmpty(sessionId) || !getUseSingleUser()) return; + + lock.lock(); + try { + Map dict = getInfoDict(); // 🔥 改动:适配CacheUserInfo类型 + if (dict.containsKey(sessionId)) { + dict.get(sessionId).setState(UserLoginState.UNLOGIN); // 🔥 改动:用setter修改状态 +// redisTemplate.opsForValue().set(cacheKey, dict, 2, TimeUnit.HOURS); + CacheUtil.set(cacheKey, dict, Duration.ofDays(2), null); + } + } finally { + lock.unlock(); + } + } + + // 标记用户为等待登出状态 + public static void waitLoginOut(String sessionId) { + if (StringUtils.isEmpty(sessionId) || !getUseSingleUser()) return; + + lock.lock(); + try { + Map dict = getInfoDict(); // 🔥 改动:适配CacheUserInfo类型 + if (dict.containsKey(sessionId)) { + dict.get(sessionId).setState(UserLoginState.WAIT_LOGIN_OUT); // 🔥 改动:用setter修改状态 +// redisTemplate.opsForValue().set(cacheKey, dict, 2, TimeUnit.HOURS); + CacheUtil.set(cacheKey, dict, Duration.ofDays(2), null); + } + } finally { + lock.unlock(); + } + } + + // 根据用户名查询登录信息 + // 🔥 改动:参数保持String userId(实际传入的是username=UserId),按UserId查询 + private static CacheUserInfo getLoginUser(String userId) { + if (StringUtils.isEmpty(userId) || !getUseSingleUser()) return null; + + lock.lock(); + try { + Map dict = getInfoDict(); // 🔥 改动:适配CacheUserInfo类型 + for (CacheUserInfo info : dict.values()) { + // 🔥 改动:按全局LoginUserInfo的UserId匹配(传入的username就是UserId) + if (userId.equals(info.getLoginUserInfo().getUserId())) { + return info; + } + } + return null; + } finally { + lock.unlock(); + } + } + + // 检查用户是否已登录 + // 🔥 改动:参数保持String userId(适配外部调用,传的是UserId) + public static boolean hasLoginUser(String userId) { + if (!getUseSingleUser()) return false; + CacheUserInfo info = getLoginUser(userId); // 🔥 改动:适配新的getLoginUser返回值 + return info != null && info.getState() == UserLoginState.LOGIN; // 🔥 改动:用getter获取状态 + } + + // 检查会话是否存在 + public static boolean hasSessionUser(String sessionId) { + if (!getUseSingleUser()) return false; + Map dict = getInfoDict(); // 🔥 改动:适配CacheUserInfo类型 + return dict.containsKey(sessionId); + } + + /** + * 检查指定会话是否处于未登录状态 + * + * @param sessionId 会话ID + * @return 若启用单用户登录且会话存在且状态为未登录,返回true;否则返回false + */ + public static boolean hasUnLoginUser(String sessionId) { + if (getUseSingleUser()) { + Map dict = getInfoDict(); // 🔥 改动:适配CacheUserInfo类型 + if (dict != null && dict.containsKey(sessionId)) { + // 注意:需确保LoginUserInfo中存在对应的状态枚举(此处假设已定义) + return dict.get(sessionId).getState() == UserLoginState.UNLOGIN; // 🔥 改动:用getter获取状态 + } + } + return false; + } + + /** + * 检查指定会话是否处于等待登出状态 + * + * @param sessionId 会话ID + * @return 若启用单用户登录且会话存在且状态为等待登出,返回true;否则返回false + */ + public static boolean hasWaitUnLoginUser(String sessionId) { + if (getUseSingleUser()) { + Map dict = getInfoDict(); // 🔥 改动:适配CacheUserInfo类型 + if (dict != null && dict.containsKey(sessionId)) { + return dict.get(sessionId).getState() == UserLoginState.WAIT_LOGIN_OUT; // 🔥 改动:用getter获取状态 + } + } + return false; + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Auth/config/SecurityConfig.java b/WebErp/weberp/src/main/java/org/example/Auth/config/SecurityConfig.java new file mode 100644 index 0000000..e7be493 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Auth/config/SecurityConfig.java @@ -0,0 +1,78 @@ +package org.example.Auth.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { +// 权限设定 + http.csrf(csrf -> csrf + // 忽略指定接口的CSRF保护 + .ignoringRequestMatchers( + "/Api/FileUploadApi", + "/Api/SystemAjaxApi", + "/Api/ModuleAjaxApi", + "/Api/SysUserAjaxApi", + "/Api/ToolsHandler" + ) + ) + .authorizeHttpRequests(auth -> auth + // 仅开放指定的Api路径,允许所有访问 + .requestMatchers( +// "/Api/FileUploadApi/**", +// "/Api/SystemAjaxApi/**", +// "/Api/ModuleAjaxApi/**", +// "/Api/SysUserAjaxApi/**", +// "/Api/ToolsHandler/**", +// "/Api/FileUploadApi", + "/Api/FileUploadApi/**", + "/Api/SystemAjaxApi", + "/Api/SystemAjaxApi/**", + "/Api/ModuleAjaxApi", + "/Api/ModuleAjaxApi/**", + "/Api/SysUserAjaxApi", + "/Api/SysUserAjaxApi/**", + "/Api/ToolsHandler", + "/Api/ToolsHandler/**" + ).permitAll() + // 其他所有请求均拒绝访问(或根据需求调整为authenticated()) + .anyRequest().denyAll() + ); +// .csrf(csrf -> csrf +// .ignoringRequestMatchers( +// "/auth/login", +// "/auth/checklogin", +// "/auth/resetPwd", +// "/auth/users" +// ) +// ) +// .authorizeHttpRequests(auth -> auth +// // 公开路径,无需认证 +// .requestMatchers( +// "/auth/**", // 所有认证相关接口 +// "/system/**", // 公开资源 +// "/module/**", // 静态资源 +// "/error", +// "/Api/FileUploadApi", +// "/Api/SystemAjaxApi", +// "/Api/ModuleAjaxApi" +// ).permitAll() +// // 其他所有请求需要认证 +// .anyRequest().authenticated() +// ); +// .formLogin(form -> form +// .loginPage("/login") +// .permitAll() +// ) +// .logout(logout -> logout +// .permitAll() +// ); + + return http.build(); + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Auth/controller/AuthController.java b/WebErp/weberp/src/main/java/org/example/Auth/controller/AuthController.java new file mode 100644 index 0000000..d11b71f --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Auth/controller/AuthController.java @@ -0,0 +1,246 @@ +package org.example.Auth.controller; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import org.example.Api.BaseHandler; +import org.example.Api.OptBaseHandler; +import org.example.Entity.Attributes.RequestCheck; +import org.example.Entity.Attributes.RequestCheckAttribute; +import org.example.Entity.BaseResponse.BaseResponse; +import org.example.Enums.SystemTypeEnums; +import org.example.Impl.BaseImpl; +import org.example.Impl.SMSImpl; +import org.example.Impl.SysUserImpl; +import org.example.Service.AuthService; +import org.example.Auth.utils.JwtUtils; +import org.example.Auth.utils.SafetyUtil; +import org.example.Entity.System.LoginUserInfo; +import org.example.Utils.DataTableUtil; +import org.example.Utils.DbOperator; +import org.example.Utils.JwtHelp; +import org.example.Utils.NativeExtensionUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.Map; +import java.util.List; +import java.util.Objects; + +import static org.example.Enums.SystemTypeEnums.LoginType.Web; +import static org.example.Utils.NativeExtensionUtils.ToInt32; + +@RestController +@RequestMapping("/Api/SysUserAjaxApi") +public class AuthController extends OptBaseHandler { + @RequestMapping(value = "/**", method = {RequestMethod.GET, RequestMethod.POST}) + public void handleRequest(HttpServletRequest Request, HttpServletResponse response) throws Exception { + // 调用 BaseHandler 的 processRequest 处理逻辑 + super.processRequest(Request); + } + + @Autowired + private AuthService authService; + + @Autowired + SysUserImpl userOperator; + + protected void InitSystemParams(HttpServletRequest ctx) { + super.initSystemParams(ctx); + } + + + @RequestCheck( + CheckLogin = false, // 登录不需要先登录 + CheckParams = "username|phone", // 必须传递username或phone + Log = true // 打印日志 + ) + //对应c#的login + public void Login() { + BaseImpl bImpl = getBImpl(); + String loginAccount = bImpl.Request("username"), pwd = bImpl.Request("password"), phone = bImpl.Request("phone"), phonecode = bImpl.Request("phonecode"), seriesId = bImpl.Request("seriesId"), serverId = bImpl.Request("server"), gtClientInfo = bImpl.Request("OsClientInfo"); + // 1. 优先走「手机号+验证码」登录(与 C# 分支逻辑一致) + if (!NativeExtensionUtils.isNullOrEmpty(phone) && !NativeExtensionUtils.isNullOrEmpty(phonecode)) { + // 调用手机号登录方法(需确保 AuthService 有此方法,参数匹配) + response = userOperator.LoginByPhone(phone, phonecode,loginAccount,null); + } + // 2. 其次走「账号+密码」登录(C# 中固定 LoginType.Web,IP 传 null) + else if (!NativeExtensionUtils.isNullOrEmpty(loginAccount)) { + // 参数对应 C#:loginAccount, pwd, seriesId, serverId.ToInt32(), LoginType.Web, null, gtClientInfo + response = userOperator.Login( + loginAccount, + pwd, + seriesId, + ToInt32(serverId), + Web, // 与 C# LoginType.Web 对齐,需确保枚举值存在 + null, // C# 中此参数传 null,对应 Java 接口的 ip 参数 + gtClientInfo // 对应 C# 的 gtClientInfo + ); + } + } + + public void ChangeServer() { + BaseResponse[] response = new BaseResponse[1]; + response[0] = new BaseResponse(); + BaseImpl bImpl = getBImpl(); + int serverId = ToInt32(bImpl.Request("server")); + StringBuilder errMsg; + errMsg = new StringBuilder(); + DbOperator dbOper = userOperator.ChangeServer(serverId, errMsg); + if (dbOper != null && serverId != getUser().ServerId) { + bImpl.sysLog(String.format("{0}切换账套{1}=>{2}", getUser().UserName, getUser().ServerId, serverId), "切换账套"); + getUser().ConnectionString = dbOper.getConnectionString(); + getUser().ServerId = serverId; + LoginUserInfo _user = userOperator.OnLoginSuccess(null, getUser(), getUser().SeriesId, getUser().ServerId, getUser().UserId, dbOper.getConnectionString(), response[0], true); + BaseImpl.setSessionVal(getUserSessionName(), _user); + if (!NativeExtensionUtils.isNullOrEmpty(getDriver()) && (Objects.equals(getDriver(), "android") || Objects.equals(getDriver(), "ios"))) { + response[0].setToken(JwtHelp.createToken(_user, 2592000));//过期时间30天 + } else { + response[0].setToken(JwtHelp.createToken(_user, 3600)); + BaseImpl.setSessionVal(getUserSessionName(), _user); + } + response[0].setSuccess(true); + } else { + response[0].setMsg("切换失败:{"+errMsg+"}!"); + } + super.response=response[0]; + } + + //对应c#的getseverName,上面的那个ChangeServer牵涉到数据库的切换,所有关于数据库切换的都没整 + public void GetServerName() { + response.setData(userOperator.GetCurrentServerName()); + response.setSuccess(true); + } + + @RequestCheck(CheckParams = "oldpassword|newpassword|renewpassword") + //对应restpwd + public void ResetPwd() { + BaseImpl bImpl = getBImpl(); + String oldPwd = bImpl.Request("oldpassword"), newPwd = bImpl.Request("newpassword"), renewPwd = bImpl.Request("renewpassword"); + response = userOperator.ResetPwd(oldPwd, newPwd, renewPwd); + } + + @RequestCheck(CheckParams = "newpassword|renewpassword") + //对应restpwdcomp + public void ResetPwdComp() { + BaseImpl bImpl = getBImpl(); + String newPwd = bImpl.Request("newpassword"), renewPwd = bImpl.Request("renewpassword"); + response = userOperator.ResetPwdComp(newPwd, renewPwd); + } + + //新增 + @RequestCheck(CheckParams = "newpassword|renewpassword", CheckLogin = false) + //ResetPwdByVerify + public void ResetPwdByVerify() { + BaseImpl bImpl = getBImpl(); + String newPwd = bImpl.Request("newpassword"), renewPwd = bImpl.Request("renewpassword"),uuid = bImpl.Request("codeuuid"); + response = userOperator.ResetPwdCompByverify(newPwd, renewPwd,uuid); + } + + + public void InitializePwd() { + BaseImpl bImpl = getBImpl(); + String oldPwd = bImpl.Request("username"), newPwd = bImpl.Request("newpassword"), renewPwd = bImpl.Request("renewpassword"); + response = userOperator.InitializePwd(oldPwd, newPwd, renewPwd); + } + + /** + * 登出接口 + * 无需请求体参数,通过请求头中的 Token 识别用户 + */ + @RequestCheck( + CheckLogin = true, // 登出需要先登录 + CheckParams = "", // 不校验参数 + Log = false // 不打印日志 + ) + + public void LoginOut() { + BaseImpl bImpl = getBImpl(); + int delay = ToInt32(bImpl.Request("delay")); + if (delay <= 0 && bImpl.isPhone()) { + userOperator.LoginOut(); + } + super.loginOut(); + + } + + @RequestCheck(CheckLogin = false) + public boolean CheckLogin() { + + boolean ok = super.checkLogin(); + if (ok && getBImpl().isPhone() && getBImpl().getAppVersion() > 0) { + userOperator.UpdOsVersion(); + } + + return ok; + } + + @RequestCheck(CheckLogin = false) + public void GetUserByName() { + + String LoginAccount = getBImpl().Request("username"); + response.setData(userOperator.GetUserByName(LoginAccount, "")); + response.setSuccess(response.getData() != null); + + } + + @RequestCheck(CheckParams = "userid") + public void GetGPSLoction() { + BaseImpl bImpl = getBImpl(); + String userid = bImpl.Request("userid"), lasttime = bImpl.Request("lasttime"); + lasttime = NativeExtensionUtils.isNullOrEmpty(lasttime) ? LocalDate.now().atStartOfDay().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) : lasttime; + + response = userOperator.GetGPSLoction(userid, lasttime); + } + + @RequestCheck(CheckLogin = false) + public void GenerateCaptcha(){ + BaseImpl bImpl = getBImpl(); + String captchaCodeKey = bImpl.Request("captchaCodeKey"); + response = userOperator.GenerateCaptcha(captchaCodeKey); + } + + @RequestCheck(CheckLogin = false) + public void SendPhoneCode() + { + BaseImpl bImpl = getBImpl(); + SMSImpl sMSImpl = new SMSImpl(); + String phoneNumber= bImpl.Request("phone"); + String phonecode = bImpl.Request("phonecode"); + String imageCode = bImpl.Request("code"); + String codeKey = bImpl.Request("codekey"); + response = sMSImpl.sendPhoneCode(phoneNumber,phonecode,imageCode, codeKey); + } + @RequestCheck(CheckLogin = false) + public void verifyCode() + { + BaseImpl bImpl = getBImpl(); + String phoneNumber = bImpl.Request("phone"); + String phoneCode = bImpl.Request("phonecode"); + String username = bImpl.Request("username"); + String uuid = bImpl.Request("codeuuid"); + response = userOperator.verifyCode(phoneNumber,phoneCode, username, uuid); + } +@RequestCheck(CheckLogin = false) + public void GetPhoneNumber() + { + BaseImpl bImpl = getBImpl(); + String username = bImpl.Request("username"); + response = userOperator.GetPhoneNumber(username); + } +@RequestCheck(CheckLogin = false) + public void ResetPwdByPhone() + { + response = userOperator.ResetPwdByPhone(); + } + + +} diff --git a/WebErp/weberp/src/main/java/org/example/Auth/utils/JwtUtils.java b/WebErp/weberp/src/main/java/org/example/Auth/utils/JwtUtils.java new file mode 100644 index 0000000..4377d84 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Auth/utils/JwtUtils.java @@ -0,0 +1,87 @@ +package org.example.Auth.utils; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import org.example.Entity.System.LoginUserInfo; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import java.nio.file.LinkOption; +import java.security.Key; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +@Component +public class JwtUtils { + private final SecretKey secretKey; + private final long expiration; + + // 使用构造函数注入配置 + public JwtUtils(@Value("${jwt.expiration}") long expiration) { + // 生成安全的 512 位密钥 + this.secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS512); + this.expiration = expiration; + } + + // 生成安全的 HS512 密钥 + private static final Key SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS512); + private static final long EXPIRATION_TIME = 1000 * 60 * 60 * 24; // 24小时 + + public static String generateToken(Map user) { + Map claims = new HashMap<>(); + claims.put("UserId", user.get("UserId")); + claims.put("UserCode", user.get("LoginAccount")); + claims.put("UserName", user.get("EmpLoyeeName")); + claims.put("LoginOs", "web"); + claims.put("ServerId", 0); + claims.put("AppIndex", user.get("AppIndex")); + claims.put("ClientId", user.get("p_emp_clientid")); + claims.put("AttendanceTime", user.get("p_emp_AttendanceTime")); + claims.put("SeriesId",user.getOrDefault("p_emp_SeriesId",0)); + + return Jwts.builder() + .setClaims(claims) + .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) + .signWith(SECRET_KEY, SignatureAlgorithm.HS512) + .compact(); + } + + public static boolean validateToken(String token) { + try { + Claims claims = Jwts.parserBuilder() + .setSigningKey(SECRET_KEY) + .build() + .parseClaimsJws(token) + .getBody(); + + Date expirationDate = claims.getExpiration(); + Date now = new Date(); + + return expirationDate != null && expirationDate.after(now); + } catch (Exception e) { + return false; + } + } + + // 从令牌中获取用户名 + public static String getUsernameFromToken(String token) { + return Jwts.parserBuilder() + .setSigningKey(SECRET_KEY) + .build() + .parseClaimsJws(token) + .getBody() + .getSubject(); + } + // 新增公共方法:从 Token 中获取 claims + public Claims getClaimsFromToken(String token) { + return Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(token) + .getBody(); + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Auth/utils/SafetyUtil.java b/WebErp/weberp/src/main/java/org/example/Auth/utils/SafetyUtil.java new file mode 100644 index 0000000..cf14b06 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Auth/utils/SafetyUtil.java @@ -0,0 +1,52 @@ +package org.example.Auth.utils; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import org.springframework.stereotype.Component; + +@Component +public class SafetyUtil { + + // 加密密码 + + /** + * 密码加密加密(对应C#的SHA1加密实现) + * + * @param strPassword 原始密码 + * @return 加密后的密码字符串 + */ + public static String encryptPassword(String strPassword) { + // 拼接C#中的String.Format("2006{0}New System", strPassword) + String formattedPassword = String.format("2006%sNew System", strPassword); + + try { + // 获取SHA1加密实例(对应C#的SHA1CryptoServiceProvider) + MessageDigest sha1 = MessageDigest.getInstance("SHA-1"); + // 转换为ASCII字节数组(与C# Encoding.ASCII.GetBytes保持一致) + byte[] bytes = formattedPassword.getBytes(StandardCharsets.US_ASCII); + // 计算哈希值 + byte[] hashBytes = sha1.digest(bytes); + + // 转换为十六进制字符串(对应C# BitConverterBitConverter.ToString并去除横杠) + StringBuilder hexString = new StringBuilder(); + for (byte b : hashBytes) { + // 格式C#格式保持一致,不足两位两位则补0 + hexString.append(String.format("%02X", b)); + } + return hexString.toString(); + + } catch (NoSuchAlgorithmException e) { + // 保持与C#一致的异常处理风格 + throw new RuntimeException("SHA-1算法算法不支持", e); + } + } + + // 验证密码 + public boolean verifyPassword(String rawPassword, String encodedPassword) { + String encryptedRawPassword = encryptPassword(rawPassword); + return encryptedRawPassword.equals(encodedPassword); + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Config/CorsConfig.java b/WebErp/weberp/src/main/java/org/example/Config/CorsConfig.java new file mode 100644 index 0000000..8fbc88b --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Config/CorsConfig.java @@ -0,0 +1,51 @@ +package org.example.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 CorsConfig { + @Bean + public CorsFilter corsFilter() { + CorsConfiguration config = new CorsConfiguration(); + // 1. 允许的前端源(必须指定具体地址,不能用*,否则Cookie无法携带) + config.addAllowedOriginPattern("http://localhost:14564"); + config.addAllowedOriginPattern("http://localhost:8082"); + config.addAllowedOriginPattern("http://localhost:8081"); + config.addAllowedOriginPattern("http://127.0.0.1:8081"); + config.addAllowedOriginPattern("http://192.168.1.51:8081"); + config.addAllowedOriginPattern("http://192.168.1.52:8081"); + config.addAllowedOriginPattern("http://192.168.0.162:14564"); + config.addAllowedOriginPattern("http://192.168.222.12:8081"); + config.addAllowedOriginPattern("http://110.185.161.104:8080"); + config.addAllowedOriginPattern("http://222.211.229.79:8081"); + // 2. 允许跨域携带Cookie(核心) + config.setAllowCredentials(true); + // 3. 允许的请求方法(GET/POST等) + config.addAllowedMethod("GET"); + config.addAllowedMethod("POST"); + config.addAllowedMethod("PUT"); + config.addAllowedMethod("PATCH"); + config.addAllowedMethod("DELETE"); + config.addAllowedMethod("OPTIONS"); + // 4. 允许的请求头(如Content-Type、Authorization等) + config.addAllowedHeader("*"); + // 5. 暴露的响应头(前端需要读取的头,如Set-Cookie) + config.addExposedHeader("Authorization"); + config.addExposedHeader("Set-Cookie"); + config.addExposedHeader("Content-Disposition"); + config.addExposedHeader("Content-Length"); + config.addExposedHeader("X-Total-Count"); + // 新增:优化预检请求缓存时间(减少OPTIONS请求次数,单位:秒) + config.setMaxAge(3600L); // 1小时内不再重复发送预检请求 + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + // 对所有路径生效 + source.registerCorsConfiguration("/**", config); + + return new CorsFilter(source); + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Config/RequestCleanupFilter.java b/WebErp/weberp/src/main/java/org/example/Config/RequestCleanupFilter.java new file mode 100644 index 0000000..18d7553 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Config/RequestCleanupFilter.java @@ -0,0 +1,27 @@ +package org.example.Config; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.example.Utils.RequestUtil; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Component +public class RequestCleanupFilter extends OncePerRequestFilter { + @Override + protected void doFilterInternal( + HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain + ) throws ServletException, IOException { + try { + filterChain.doFilter(request, response); + } finally { + RequestUtil.clear(); + } + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Config/ResourceGovernanceConfig.java b/WebErp/weberp/src/main/java/org/example/Config/ResourceGovernanceConfig.java new file mode 100644 index 0000000..c340d6b --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Config/ResourceGovernanceConfig.java @@ -0,0 +1,49 @@ +package org.example.Config; + +import jakarta.annotation.PostConstruct; +import org.example.Utils.CacheUtil; +import org.example.Utils.JsEngine; +import org.example.Utils.ResourceExecutors; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ResourceGovernanceConfig { + @Value("${app.cache.max-entries:5000}") + private int cacheMaxEntries; + + @Value("${app.js-cache.max-entries:1000}") + private int jsCacheMaxEntries; + + @Value("${app.executor.file-cleanup.core-size:2}") + private int fileCleanupCoreSize; + + @Value("${app.executor.file-cleanup.max-size:4}") + private int fileCleanupMaxSize; + + @Value("${app.executor.file-cleanup.queue-capacity:200}") + private int fileCleanupQueueCapacity; + + @Value("${app.executor.push.core-size:4}") + private int pushCoreSize; + + @Value("${app.executor.push.max-size:16}") + private int pushMaxSize; + + @Value("${app.executor.push.queue-capacity:500}") + private int pushQueueCapacity; + + @PostConstruct + public void applyResourceGovernanceSettings() { + CacheUtil.setMaxEntries(cacheMaxEntries); + JsEngine.setMaxCacheEntries(jsCacheMaxEntries); + ResourceExecutors.configure( + fileCleanupCoreSize, + fileCleanupMaxSize, + fileCleanupQueueCapacity, + pushCoreSize, + pushMaxSize, + pushQueueCapacity + ); + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Config/ResourceLifecycleManager.java b/WebErp/weberp/src/main/java/org/example/Config/ResourceLifecycleManager.java new file mode 100644 index 0000000..ac20c6e --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Config/ResourceLifecycleManager.java @@ -0,0 +1,19 @@ +package org.example.Config; + +import jakarta.annotation.PreDestroy; +import org.example.Utils.DynamicJdbcTemplateRegistry; +import org.example.Utils.FileUtil; +import org.example.Utils.JsEngine; +import org.example.Utils.ResourceExecutors; +import org.springframework.stereotype.Component; + +@Component +public class ResourceLifecycleManager { + @PreDestroy + public void shutdownResources() { + FileUtil.shutdownExecutor(); + JsEngine.shutdown(); + ResourceExecutors.shutdownAll(); + DynamicJdbcTemplateRegistry.closeAll(); + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Attributes/Attribute.java b/WebErp/weberp/src/main/java/org/example/Entity/Attributes/Attribute.java new file mode 100644 index 0000000..0dfd2a9 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Attributes/Attribute.java @@ -0,0 +1,4 @@ +package org.example.Entity.Attributes; + +public interface Attribute { +} diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Attributes/AttributeUtils.java b/WebErp/weberp/src/main/java/org/example/Entity/Attributes/AttributeUtils.java new file mode 100644 index 0000000..992bc65 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Attributes/AttributeUtils.java @@ -0,0 +1,98 @@ +package org.example.Entity.Attributes; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +public class AttributeUtils { + private static final Logger log = LoggerFactory.getLogger(AttributeUtils.class); + + /** + * 模拟C#的Attribute.GetCustomAttribute方法 + * 从方法或类上获取自定义属性实例 + */ + @SuppressWarnings("unchecked") +// public static T getAttribute(Method method, Class attributeClass) { +// // 1. 先从方法的参数上查找(如果有标注) +// List attributes = findAttributes(method.getDeclaringClass(), attributeClass); +// if (!attributes.isEmpty()) { +// return attributes.get(0); +// } +// // 2. 从方法本身查找 +// attributes = findAttributes(method, attributeClass); +// return attributes.isEmpty() ? null : attributes.get(0); +// } + public static RequestCheckAttribute getAttribute(Method method) { + // 1. 检查方法上是否有@RequestCheck注解 + if (method.isAnnotationPresent(RequestCheck.class)) { + RequestCheck annotation = method.getAnnotation(RequestCheck.class); + return convertAnnotationToAttribute(annotation); + } + // 2. 若方法无注解,检查类上的注解 + Class clazz = method.getDeclaringClass(); + if (clazz.isAnnotationPresent(RequestCheck.class)) { + RequestCheck annotation = clazz.getAnnotation(RequestCheck.class); + return convertAnnotationToAttribute(annotation); + } + // 3. 若都无注解,返回null(或默认默认配置) + return null; + } + + // 将注解转换为RequestCheckAttribute实例 + private static RequestCheckAttribute convertAnnotationToAttribute(RequestCheck annotation) { + RequestCheckAttribute attribute = new RequestCheckAttribute(); + attribute.CheckLogin = annotation.CheckLogin(); + attribute.CheckParams = annotation.CheckParams(); + attribute.Log = annotation.Log(); + attribute.Verify = annotation.Verify(); + attribute.Cache = annotation.Cache(); + attribute.ExpirationPeriod = annotation.ExpirationPeriod(); + attribute.Name = annotation.Name(); + attribute.Desp = annotation.Desp(); + return attribute; + } + + public static T getCustomAttribute(Class type, Class attributeClass) { + List attributes = findAttributes(type, attributeClass); + return attributes.isEmpty() ? null : attributes.get(0); + } + + /** + * 从目标对象(类/方法)中查找自定义属性实例 + */ + private static List findAttributes(Object target, Class attributeClass) { + List result = new ArrayList<>(); + try { + // 实际项目中可改为:从类的特定字段或注解中解析属性 + // 这里模拟通过反射获取预定义的属性实例 + if (target instanceof Class) { + Class clazz = (Class) target; + // 查找类中定义的属性实例(例如通过特定命名规范的字段) + Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields) { + log.debug(String.valueOf("扫描字段:" + field.getName() + ",类型:" + field.getType().getSimpleName())); + if (attributeClass.isAssignableFrom(field.getType())) { + field.setAccessible(true); + Object value = field.get(null); // 假设是静态字段 + if (value != null) { + result.add((T) value); + log.debug(String.valueOf("找到匹配属性:" + field.getName())); + } + } + } + } else if (target instanceof Method) { + Method method = (Method) target; + // 查找方法上的属性实例(例如通过方法参数或注解) + // 这里简化处理,实际可根据需要扩展 + } + } catch (Exception e) { + log.error("Exception caught", e); + } + return result; + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Attributes/RequestCheck.java b/WebErp/weberp/src/main/java/org/example/Entity/Attributes/RequestCheck.java new file mode 100644 index 0000000..eaf8261 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Attributes/RequestCheck.java @@ -0,0 +1,34 @@ +package org.example.Entity.Attributes; + +import java.lang.annotation.*; + +// 注解作用在方法或类上 +@Target({ElementType.METHOD, ElementType.TYPE}) +// 注解保留到运行时(框架需要在运行时获取) +@Retention(RetentionPolicy.RUNTIME) +public @interface RequestCheck { + // 是否检查登录(默认true,对应RequestCheckAttribute的CheckLogin) + boolean CheckLogin() default true; + + // 需要校验的参数(对应CheckParams,默认空字符串) + String CheckParams() default ""; + + // 是否打印日志(对应Log,默认true) + boolean Log() default true; + + // 是否验证(对应Verify,默认true) + boolean Verify() default true; + + // 是否缓存(对应Cache,默认false) + boolean Cache() default false; + + // 缓存过期时间(对应ExpirationPeriod,默认10分钟) + int ExpirationPeriod() default 10; + + // 名称和描述(对应Name和Desp) + String Name() default ""; + + String Desp() default ""; + + boolean WriteRespose() default true; +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Attributes/RequestCheckAttribute.java b/WebErp/weberp/src/main/java/org/example/Entity/Attributes/RequestCheckAttribute.java new file mode 100644 index 0000000..ad393c8 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Attributes/RequestCheckAttribute.java @@ -0,0 +1,157 @@ +package org.example.Entity.Attributes; + + +import org.example.Api.BaseHandler; +import org.example.Impl.BaseImpl; +import org.example.Impl.VerifyImpl; +import org.example.Utils.CacheUtil; +import org.example.Utils.JSON; +import org.example.Utils.LanguageUtil; +import org.example.Utils.WebConfigUtil_web; + +import java.time.Duration; +import java.util.function.Supplier; + +import static org.example.Utils.NativeExtensionUtils.isNullOrEmpty; +import static org.example.Utils.NativeExtensionUtils.toBoolean; + +/** + * 功能描述:RequestCheckAttribute + * 创 建 者:wzy + * 创建日期:2017-01-05 22:33:06 + */ +public class RequestCheckAttribute implements Attribute { + /** + * 是否检查登录信息 + */ + public Boolean CheckLogin = true; + + /** + * 是否向客户端回写 + */ + public Boolean WriteRespose = true; + + /** + * 检查request的字段是否为空 + */ + public String CheckParams = ""; + + public boolean Log = true; + + public boolean Verify = true; + + public boolean Cache = false; + /// + /// 缓存失效时间,默认30分钟 + /// + public int ExpirationPeriod = 10; + + public String Name; + + public String Desp; + + + private VerifyImpl _verifyimpl; + private BaseImpl _bImpl; + + private VerifyImpl getVerifyImpl() { + if (_verifyimpl == null) { + _verifyimpl = new VerifyImpl(); + } + return _verifyimpl; + } + + public BaseImpl getBImpl() { + if (_bImpl == null) { + _bImpl = new BaseImpl(); + } + return _bImpl; + } + + public void setBImpl(BaseImpl bImpl) { + this._bImpl = bImpl; + } + + /** + * 验证参数是否有效 + * + * @param errMsg 检查参数配置 + * @return 验证结果数组,第一个元素为是否有效,第二个元素为错误信息 + */ + public Boolean validParams(String[] errMsg) { + errMsg[0] = ""; + if (!isValid()) { + return false; + } + + if (isNullOrEmpty(CheckParams)) { + return true; + } + + String[] pamNames = CheckParams.split(","); + for (String pamName : pamNames) { + if (pamName == null || pamName.isEmpty()) { + continue; + } + + String[] orPamNames = pamName.split("\\|"); // 转义|符号 + if (orPamNames.length == 1) { + if (getBImpl().Request(pamName) == null || getBImpl().Request(pamName).isEmpty()) { + errMsg[0] = String.format("%s:%s", LanguageUtil.InvalidParameter, pamName); + return false; + } + } else { + boolean hasVal = false; + for (String orName : orPamNames) { + if (getBImpl().Request(orName) != null && !getBImpl().Request(orName).isEmpty()) { + hasVal = true; + break; + } + } + + if (!hasVal) { + errMsg[0] = String.format("%s:%s", LanguageUtil.InvalidParameter, pamName.replace("|", "或")); + return false; + } + } + } + + return true; + } + + /** + * 获取缓存值 + * + * @param handler 基础处理器 + * @param getCacheValue 获取缓存值的供应商 + * @return 缓存值或新生成的值 + */ + public Object getCacheVal(BaseHandler handler, Supplier getCacheValue) { + if (Cache && toBoolean(WebConfigUtil_web.get("Cache", ""))) { + String key = String.valueOf((handler.getClass().getName() + + "_" + getBImpl().getUser().UserId + + "_" + getBImpl().getUser().UserName + + "_" + JSON.Encode(getBImpl().getAllRequest())).hashCode()); + + // 将Supplier转换为Function + return CacheUtil.getCacheItem(key, + (Void v) -> getCacheValue.get(), // 适配Function接口 + Duration.ofMinutes(ExpirationPeriod), + null, null); + } + return getCacheValue.get(); + } + + public boolean isValid() { + return validVerify(); + } + + public boolean validVerify() { + if (!Verify) { + return true; + } else { + return getVerifyImpl().verify().isSuccess(); + } + } + +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/BaseResponse/BaseResponse.java b/WebErp/weberp/src/main/java/org/example/Entity/BaseResponse/BaseResponse.java new file mode 100644 index 0000000..0446c2b --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/BaseResponse/BaseResponse.java @@ -0,0 +1,191 @@ +package org.example.Entity.BaseResponse; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import org.example.Utils.NativeExtensionUtils; +import org.example.Utils.WebConfigUtil_web; + +import java.util.Hashtable; +import java.util.Map; + +/** + * ============================================================================== + * 功能描述:BaseResponse + * ============================================================================== + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class BaseResponse { + /** + * 是否成功 + */ + private boolean success; + + public String getSql() { + return sql; + } + + public void setSql(String sql) { + this.sql = sql; + } + + private String sql; + /** + * 文字信息 + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + private String msg; + + /** + * 数据总数 + */ + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + private int tot = 0; + + /** + * 返回的主数据 + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + private Object data; + + /** + * 如有其它数据 + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + private Object other; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object attc; + @JsonInclude(JsonInclude.Include.NON_NULL) + private String token; + + private Boolean sharLogin; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Boolean getSharToken() { + if (token != null && !token.isEmpty()) { + return NativeExtensionUtils.toBoolean(WebConfigUtil_web.get("SharLogin", "1")); + } + return sharLogin; + } + + public void setSharToken(Boolean value) { + this.sharLogin = value; + } + + /** + * webversion 用于更新前端 + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + private String webVer; + + /** + * json编码后的字符串 + */ + @JsonIgnore + private String encodedResult; + + /** + * json编码后的字符串 + */ + @JsonIgnore + private boolean writeResponse = true; + + /** + * webversion 用于更新前端 + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + private Map printInfo; + + // Getters and Setters + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public int getTot() { + return tot; + } + + public void setTot(int tot) { + this.tot = tot; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object getData() { + return data; + } + + public void setData(Object data) { + this.data = data; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object getOther() { + return other; + } + + public void setOther(Object other) { + this.other = other; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getWebVer() { + return webVer; + } + + public void setWebVer(String webVer) { + this.webVer = webVer; + } + + @JsonIgnore + public String getEncodedResult() { + return encodedResult; + } + + public void setEncodedResult(String encodedResult) { + this.encodedResult = encodedResult; + } + + @JsonIgnore + public boolean isWriteResponse() { + return writeResponse; + } + + public void setWriteResponse(boolean writeResponse) { + this.writeResponse = writeResponse; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Map getPrintInfo() { + return printInfo; + } + + public void setPrintInfo(Map printInfo) { + this.printInfo = printInfo; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Map AppCfg; +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Cache.java b/WebErp/weberp/src/main/java/org/example/Entity/Cache.java new file mode 100644 index 0000000..1637041 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Cache.java @@ -0,0 +1,32 @@ +package org.example.Entity; + +import org.springframework.cache.annotation.Cacheable; +import org.springframework.core.annotation.AliasFor; + +import java.lang.annotation.*; + +/** + * 模拟C#的[Cache]注解(基于Spring Cache封装) + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Cacheable +public @interface Cache { + /** + * 缓存过期时间(单位:分钟,仅标记,实际需结合缓存管理器配置) + */ + int ExpirationPeriod() default 10; + + /** + * 缓存key(复用Spring Cache的key属性) + */ + @AliasFor(annotation = Cacheable.class, attribute = "key") + String key() default ""; + + /** + * 缓存名称(复用Spring Cache的value属性) + */ + @AliasFor(annotation = Cacheable.class, attribute = "value") + String[] value() default {"printCache"}; +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Base/Base.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Base/Base.java new file mode 100644 index 0000000..0ea5507 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Base/Base.java @@ -0,0 +1,45 @@ +package org.example.Entity.Control.Base; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import org.example.Entity.System.LoginUserInfo; +import org.example.Impl.BaseImpl; + +/** + * ============================================================================== + * 功能描述:基础控件的父类 + * ============================================================================== + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Base { + + protected String defaultXtype = "box"; + + // 控件的类型 + private String _xtype; + + public String getXtype() { + return this._xtype != null ? this._xtype : defaultXtype; + } + + public void setXtype(String value) { + _xtype = value; + } + + private LoginUserInfo _user; + + @JsonIgnore + protected LoginUserInfo getUser() { + if (_user == null)//&& HttpContext.Current != null && HttpContext.Current.Session != null + { + _user = new BaseImpl().getUser(); //HttpContext.Current.Session[Lskj.Web.Core.Util.WebConfigUtil.Session_LoginUser] as LoginUserInfo ?? new LoginUserInfo(); + } else { + return new LoginUserInfo(); + } + return _user; + } + + public void setUser(LoginUserInfo vlaue) { + this._user = vlaue; + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Base/Component.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Base/Component.java new file mode 100644 index 0000000..b5fa2c8 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Base/Component.java @@ -0,0 +1,139 @@ +package org.example.Entity.Control.Base; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.example.Utils.IPublicUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.util.ArrayList; +import java.util.List; + +/** + * ============================================================================== + * 功能描述:Component 所有Ext组件的基类 + * ============================================================================== + */ +@org.springframework.stereotype.Component +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Component extends Base { + @JsonIgnore + private IPublicUtil _util; + + // 自动注入JdbcTemplate(由Spring容器提供,已关联DataSource) + @JsonIgnore + @Autowired + protected JdbcTemplate jdbcTemplate; + + @JsonIgnore + protected IPublicUtil getUtil() { + if (_util == null) { + // 若IPublicUtil需要JdbcTemplate,通过构造函数传入已注入的实例 + _util = new IPublicUtil(jdbcTemplate); + } + return _util; + } + + public void setUtil(IPublicUtil _util) { + this._util = _util; + } + + private JdbcTemplate _dbOperator; + + @JsonIgnore + protected JdbcTemplate getDbOperator() { + if (_dbOperator == null) { + if (getUtil() != null) { + _dbOperator = _util.GetOperater(); + } + } + return _dbOperator; + } + + public void setDbOperator(JdbcTemplate value) { + this._dbOperator = value; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Integer width; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Integer height; + + + protected Boolean hidden; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Boolean getHidden() { + return this.hidden; + } + + public void setHidden(Boolean hidden) { + this.hidden = hidden; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Boolean disabled; + + + protected Integer top; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Integer getTop() { + return top; + } + + public void setTop(Integer top) { + this.top = top; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Integer getLeft() { + return left; + } + + public void setLeft(Integer left) { + this.left = left; + } + + protected Integer left; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public List items; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object defaults; + + // 添加子组件 + public void Add(Component com) { + if (this.items == null) { + this.items = new ArrayList(); + } + this.items.add(com); + } + + // 比较组件位置(替代C#的操作符重载) + public static boolean isGreater(Component left, Component right) { + final int err = 5; // +-5px误差 + // 处理left.top或right.top为null的情况(例如默认值设为0) + int leftTop = (left.getTop() != null) ? left.getTop() : 0; + int rightTop = (right.getTop() != null) ? right.getTop() : 0; + + if (leftTop < rightTop - err) { + return true; + } else if (leftTop >= rightTop - err && leftTop <= rightTop + err) { + // 若top接近,比较left属性(同样处理null) + int leftLeft = (left.getLeft() != null) ? left.getLeft() : 0; + int rightLeft = (right.getLeft() != null) ? right.getLeft() : 0; + return leftLeft < rightLeft; + } else { + return false; + } + } + + public static boolean isLess(Component left, Component right) { + return !isGreater(left, right); + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Base/RowComponent.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Base/RowComponent.java new file mode 100644 index 0000000..f052e40 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Base/RowComponent.java @@ -0,0 +1,30 @@ +package org.example.Entity.Control.Base; + + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.example.Utils.IPublicUtil; + +import java.util.Map; + +/** + * ============================================================================== + * 功能描述:RowComponent 所有Ext组件的基类,该类值有datarow 提供 + * ============================================================================== + */ +public class RowComponent extends Component { + + @JsonIgnore + public Map _row; + + public RowComponent(Map row) { + super(); + this._row = (row); + } + + public RowComponent(Map row, IPublicUtil util) { + this(row); + this.setUtil(util); + } + + +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Com/Button.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Com/Button.java new file mode 100644 index 0000000..1de7dc2 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Com/Button.java @@ -0,0 +1,391 @@ +package org.example.Entity.Control.Com; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.example.Entity.Control.Base.RowComponent; +import org.example.Utils.DataTableUtil; +import org.example.Utils.IPublicUtil; + +import java.util.Map; + +import static org.example.Utils.NativeExtensionUtils.toBoolean; + +/** + * ============================================================================== + * 功能描述:Button + * ============================================================================== + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Button extends RowComponent { + + public Button() { + super(null); + this.defaultXtype = "button"; + } + + public Button(Map row) { + super(row); + this.defaultXtype = "button"; + this._row = row; + } + + public Button(Map row, IPublicUtil util) { + super(row, util); + this.defaultXtype = "button"; + this._row = row; + this.setUtil(util); + } + + private int toolId; + private String cls; + private String iconCls; + private String text; + + private Boolean hidden; + private String actionLib; + private String action; + private String beforeMsg; + private String successMsg; + private String errorMsg; + private String handler; + private boolean formBind; + private Object menu; + private String href; + private String pm1; + private String pm2; + private String pm3; + private String pm4; + private String pm5; + private String pm6; + private String pm7; + private String pm8; + private String pm9; + private String pm10; + + // 工具ID + public int getToolId() { + if (toolId <= 0 && _row != null) { + toolId = DataTableUtil.getIntValue(_row, "id", 0); + } + return toolId; + } + + public void setToolId(int toolId) { + this.toolId = toolId; + } + + // CSS类 + public String getCls() { + return cls; + } + + public void setCls(String cls) { + this.cls = cls; + } + + // 图标类 + public String getIconCls() { + return iconCls; + } + + public void setIconCls(String iconCls) { + this.iconCls = iconCls; + } + + // 按钮文本 + public String getText() { + if (text == null && _row != null) { + text = DataTableUtil.getStringValue(_row, new String[]{"menucaption", "text"}, null); + } + return text; + } + + public void setText(String text) { + this.text = text; + } + + private String _recordCond; + + // 记录条件 + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("RecordCond") + public String getRecordCond() { + if (_recordCond == null && _row != null) { + _recordCond = (String) DataTableUtil.getRowVal(_row, "Menucond", null); + } + return _recordCond; + } + + public void setRecordCond(String recordCond) { + this._recordCond = recordCond; + } + + // 隐藏状态 + + public Boolean getHidden() { + return hidden; + } + + + public void setHidden(Boolean hidden) { + this.hidden = hidden; + } + + // 按钮类型 + @JsonProperty("BType") + public int getBType() { + return _row != null ? DataTableUtil.getIntValue(_row, "type", 0) : 0; + } + + // 动作类型 + @JsonProperty("ActionType") + public int getActionType() { + return _row != null ? DataTableUtil.getIntValue(_row, "actiontype", 0) : 0; + } + + @JsonIgnore // 动态链接库 + public String getActionLib() { + if (actionLib == null && _row != null) { + actionLib = DataTableUtil.getStringValue(_row, "library", null); + } + return actionLib; + } + + public void setActionLib(String actionLib) { + this.actionLib = actionLib; + } + + + @JsonIgnore + // 执行的SQL或存储过程 + public String getAction() { + if (action == null && _row != null) { + action = DataTableUtil.getStringValue(_row, "action", null); + } + return action; + } + + public void setAction(String action) { + this.action = action; + } + + // 执行前提示 + public String getBeforeMsg() { + if (beforeMsg == null && _row != null) { + beforeMsg = DataTableUtil.getStringValue(_row, "beforemsg", null); + } + return beforeMsg; + } + + public void setBeforeMsg(String beforeMsg) { + this.beforeMsg = beforeMsg; + } + + // 执行成功提示 + public String getSuccessMsg() { + if (successMsg == null && _row != null) { + successMsg = DataTableUtil.getStringValue(_row, "SuccessMsg", null); + } + return successMsg; + } + + public void setSuccessMsg(String successMsg) { + this.successMsg = successMsg; + } + + // 执行失败提示 + public String getErrorMsg() { + if (errorMsg == null && _row != null) { + errorMsg = DataTableUtil.getStringValue(_row, "ErrorMsg", null); + } + return errorMsg; + } + + public void setErrorMsg(String errorMsg) { + this.errorMsg = errorMsg; + } + + // 双击属性 + @JsonProperty("DoubleClick") + public boolean isDoubleClick() { + return _row != null && DataTableUtil.getBooleanValue(_row, "DoubleClick", false); + } + + // 窗口最大化 + @JsonProperty("MaxWindow") + public boolean isMaxWindow() { + return _row != null && DataTableUtil.getBooleanValue(_row, "MaxWindow", false); + } + + // 执行后刷新 + @JsonProperty("Refresh") + public boolean isRefresh() { + return toBoolean(DataTableUtil.getRowVal(_row, "Refresh", false)); + } + + // 多选 + @JsonProperty("MoreClick") + public boolean isMoreClick() { + return _row != null && DataTableUtil.getBooleanValue(_row, "moreclick", false); + } + + // 多选提示 + @JsonProperty("MoreClickTip") + public boolean isMoreClickTip() { + return _row != null && DataTableUtil.getBooleanValue(_row, "moreclicktip", false); + } + + // 处理函数 + public String getHandler() { + return handler; + } + + public void setHandler(String handler) { + this.handler = handler; + } + + // 表单绑定 + public boolean isFormBind() { + return formBind; + } + + public void setFormBind(boolean formBind) { + this.formBind = formBind; + } + + // 菜单 + public Object getMenu() { + return menu; + } + + public void setMenu(Object menu) { + this.menu = menu; + } + + // 链接 + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + // 刷新标志 + @JsonProperty("refresh") + public boolean isLowerRefresh() { + return toBoolean(DataTableUtil.getRowVal(this._row, "refresh", true)); + } + + // 参数1-10的实现 + public String getPm1() { + if (pm1 == null && _row != null) { + pm1 = DataTableUtil.getStringValue(_row, "param1", null); + } + return pm1; + } + + public void setPm1(String pm1) { + this.pm1 = pm1; + } + + public String getPm2() { + if (pm2 == null && _row != null) { + pm2 = DataTableUtil.getStringValue(_row, "param2", null); + } + return pm2; + } + + public void setPm2(String pm2) { + this.pm2 = pm2; + } + + public String getPm3() { + if (pm3 == null && _row != null) { + pm3 = DataTableUtil.getStringValue(_row, "param3", null); + } + return pm3; + } + + public void setPm3(String pm3) { + this.pm3 = pm3; + } + + public String getPm4() { + if (pm4 == null && _row != null) { + pm4 = DataTableUtil.getStringValue(_row, "param4", null); + } + return pm4; + } + + public void setPm4(String pm4) { + this.pm4 = pm4; + } + + public String getPm5() { + if (pm5 == null && _row != null) { + pm5 = DataTableUtil.getStringValue(_row, "param5", null); + } + return pm5; + } + + public void setPm5(String pm5) { + this.pm5 = pm5; + } + + public String getPm6() { + if (pm6 == null && _row != null) { + pm6 = DataTableUtil.getStringValue(_row, "param6", null); + } + return pm6; + } + + public void setPm6(String pm6) { + this.pm6 = pm6; + } + + public String getPm7() { + if (pm7 == null && _row != null) { + pm7 = DataTableUtil.getStringValue(_row, "param7", null); + } + return pm7; + } + + public void setPm7(String pm7) { + this.pm7 = pm7; + } + + public String getPm8() { + if (pm8 == null && _row != null) { + pm8 = DataTableUtil.getStringValue(_row, "param8", null); + } + return pm8; + } + + public void setPm8(String pm8) { + this.pm8 = pm8; + } + + public String getPm9() { + if (pm9 == null && _row != null) { + pm9 = DataTableUtil.getStringValue(_row, "param9", null); + } + return pm9; + } + + public void setPm9(String pm9) { + this.pm9 = pm9; + } + + public String getPm10() { + if (pm10 == null && _row != null) { + pm10 = DataTableUtil.getStringValue(_row, "param10", null); + } + return pm10; + } + + public void setPm10(String pm10) { + this.pm10 = pm10; + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Com/SysPoPupMenuBtn.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Com/SysPoPupMenuBtn.java new file mode 100644 index 0000000..b483cf1 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Com/SysPoPupMenuBtn.java @@ -0,0 +1,489 @@ +package org.example.Entity.Control.Com; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.example.Entity.Control.Base.RowComponent; +import org.example.Utils.DataTableUtil; +import org.example.Utils.IPublicUtil; +import org.example.Utils.NativeExtensionUtils; + +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; + +/** + * ============================================================================== + * 功能描述:SysPoPupMenu 右键菜单/单据工具表其他参数, + * 注意:其他参数中 以‘@’开头的,表示sql语句,以‘!’开头的表示存储过程,以#开头的表示多返回值得sql一般使用在拓展插件里面 所有以‘{}’包裹的,都要替换为对应的值 + * 备注:actiontype :0表示存储过程 需要其他参数作为参数,一一对应,1表示sql 不需要其他参数,但需要替换对应行数据 + * ============================================================================== + */ +public class SysPoPupMenuBtn extends Button { + + + public SysPoPupMenuBtn() { + super(); + } + + public SysPoPupMenuBtn(Map row) { + super(row); + this._row = row; + } + + public SysPoPupMenuBtn(Map row, IPublicUtil util) { + super(row, util); + this._row = row; + this.setUtil(util); + } + + @JsonIgnore + @Override + public String getXtype() { + return null; + } + + @Override + public void setXtype(String xtype) { + super.setXtype(xtype); + } + + @JsonIgnore + public int MenuBtnId; + + @JsonIgnore + public String dllname; + + private String __dllname; + + public String get_dllname() { + if (dllname == null && getStoreName() != null && !getStoreName().isEmpty()) { + return getStoreName().toLowerCase(); + } + if (__dllname == null) { + if (dllname == null) return ""; + String[] names = dllname.toLowerCase().split("\\."); + __dllname = names.length > 1 ? names[1] : ""; + + if (names.length >= 3) { + String[] specialNames = {"tplbatchadd", "tpladd", "wordadd", "batchadd", "tableadd", "tabadd"}; + for (String specialName : specialNames) { + if (specialName.equals(names[2]) || dllname.toLowerCase().contains(".machine")) { + __dllname = names[2]; + break; + } + } + } + } + return __dllname; + } + + + public String action; + + protected String getStoreName() { + if (actiontype == 1 && action != null && !action.trim().isEmpty()) { + return action.trim().split(" ")[0]; + } + return ""; + } + + public int actiontype; + + + @JsonIgnore + public Map Record; + + /// + /// 其他参数1 + /// + @JsonIgnore + public String dllpar1; + /// + /// 其他参数2 + /// + @JsonIgnore + public String dllpar2; + /// + /// 其他参数3 + /// + @JsonIgnore + public String dllpar3; + /// + /// 其他参数4 + /// + @JsonIgnore + public String dllpar4; + /// + /// 其他参数5 + /// + @JsonIgnore + public String dllpar5; + /// + /// 其他参数6 + /// + @JsonIgnore + public String dllpar6; + /// + /// 其他参数7 + /// + @JsonIgnore + public String dllpar7; + /// + /// 其他参数8,单据为主表sql + /// + @JsonIgnore + public String dllpar8; + /// + /// 其他参数9,,单据为明细sql + /// + @JsonIgnore + public String dllpar9; + /// + /// 其他参数10,单据为单据编号 + /// + @JsonIgnore + public String dllpar10; + @JsonIgnore + public String comfirm; + /// + /// 特殊参数是否选择人员或者步骤,用于提交或者审批 + /// + @JsonIgnore + public String selectConfirmFlag; + /// + /// 特殊参数是否选择人员或者步骤,用于提交或者审批 + /// + @JsonIgnore + public String nextSelectStepCode; + /// + /// 特殊参数是否选择人员或者步骤,用于提交或者审批 + /// + @JsonIgnore + public String nextSelectStepOper; + /// + /// 转发需要人员 + /// + @JsonIgnore + public String comfirmOpers; + /// + /// 转发备注 + /// + @JsonIgnore + public String remark; + @JsonIgnore + public boolean maxWindow; + /// + /// 右键打开方式 0:窗口 1:tab,2:内嵌 + /// + @JsonIgnore + public int showMode; + + /// + /// 1:是否在表格上展示 2:在工具栏上显示(未实现) + /// + protected int toBar; + /// + /// 批量执行 + /// + protected boolean multi; + + private String _moduleId; + + @JsonIgnore + // ModuleId属性 + @JsonProperty("ModuleId") + public String getModuleId() { + switch (get_dllname()) { + case "pubadd": + case "pubadd2": + case "pubadd3": + case "tplbatchadd": + case "tpladd": + case "wordadd": + case "batchadd": + case "tableadd": + case "tabadd": + case "machine": + case "machine2": + case "machine3": + case "pubpagedetail": + case "pubbillinfo": + case "pubchart": + case "pubmoduletd": + default: + return NativeExtensionUtils.isNullOrEmpty(dllpar1) ? _moduleId : dllpar1; + case "pubmodule": + case "report": + case "baseinfo": + case "pubbill": + case "pubmoduledetailinfo": + case "pubmoduledetail": + return NativeExtensionUtils.isNullOrEmpty(dllpar2) ? dllpar1 : dllpar2; + case "photoview": + return dllpar4; + case "pubattachment": + return dllpar3; + case "pubaccraditation": + case "baseaccraditation": + return dllpar2; + } + } + + public void setModuleId(String moduleId) { + this._moduleId = moduleId; + } + + private int _serverId; + + @JsonIgnore + @JsonProperty("ServerId") + public int getServerId() { + switch (get_dllname()) { + case "pubadd": + case "pubadd2": + case "pubadd3": + case "tplbatchadd": + case "tpladd": + case "wordadd": + case "batchadd": + case "tableadd": + case "machine": + case "machine2": + case "machine3": + case "pubpagedetail": + case "pubbillinfo": + case "pubchart": + case "pubmoduletd": + return DataTableUtil.getIntValue(dllpar7, 0); + case "pubmodule": + case "report": + case "baseinfo": + case "pubbill": + case "pubmoduledetailinfo": + case "pubmoduledetail": + return DataTableUtil.getIntValue(dllpar3, 0); + case "pubaccraditation": + case "baseaccraditation": + return DataTableUtil.getIntValue(dllpar1, 0); + default: + return 0; + } + } + + public void setServerId(int serverId) { + this._serverId = serverId; + } + + @JsonProperty("Updateable") + protected boolean isUpdateable() { + switch (get_dllname()) { + case "pubadd": + case "pubadd2": + case "pubadd3": + case "tplbatchadd": + case "tpladd": + case "wordadd": + case "batchadd": + case "tableadd": + case "tabadd": + case "machine": + case "machine2": + case "machine3": + case "photoview": + return !DataTableUtil.getBooleanValue(dllpar2, false); + case "baseinfo": + return !DataTableUtil.getBooleanValue(dllpar1, false); + default: + return true; + } + } + + @JsonIgnore + // IdValue属性 + @JsonProperty("IdValue") + public String getIdValue() { + switch (get_dllname()) { + case "pubadd": + case "pubadd2": + case "pubadd3": + case "tplbatchadd": + case "tpladd": + case "wordadd": + case "batchadd": + case "tableadd": + case "tabadd": + case "machine": + case "machine2": + case "machine3": + return dllpar3; + case "pubbillinfo": + return dllpar2; + case "pubbill": + return dllpar10; + case "photoview": + return dllpar1; + case "windowwordcard": + case "windowcard": + case "windowcuscard": + case "pubaccraditation": + case "baseaccraditation": + return dllpar4; + case "baseinfo": + return dllpar3; + default: + return dllpar2; + } + } + + + @JsonIgnore + // UnionCond属性 + @JsonProperty("UnionCond") + public String getUnionCond() { + switch (get_dllname()) { + case "pubpagedetail": + case "pubmodule": + case "pubmoduledetailinfo": + return dllpar7; + default: + return ""; + } + } + + @JsonIgnore + // UnionValue属性 + @JsonProperty("UnionValue") + public String getUnionValue() { + switch (get_dllname()) { + case "pubpagedetail": + return dllpar2; + case "pubbillinfo": + return dllpar2; + case "photoview": + case "pubattachment": + return dllpar1; + case "baseinfo": + return dllpar3; + case "pubmodule": + case "pubadd": + case "pubadd2": + case "pubadd3": + case "tplbatchadd": + case "tpladd": + case "wordadd": + case "batchadd": + case "tableadd": + case "tabadd": + case "pubmoduledetailinfo": + return dllpar5; + default: + return ""; + } + } + + @JsonIgnore + // UnionField属性 + @JsonProperty("UnionField") + public String getUnionField() { + switch (get_dllname()) { + case "pubmodule": + case "pubadd": + case "pubadd2": + case "pubadd3": + case "tplbatchadd": + case "tpladd": + case "wordadd": + case "batchadd": + case "tableadd": + case "tabadd": + case "pubmoduledetailinfo": + return dllpar4; + default: + return ""; + } + } + + @JsonIgnore + // PmStr属性 + @JsonProperty("PmStr") + public String getPmStr() { + if ("pubmoduledetailinfo".equals(get_dllname())) { + return dllpar3; + } + if ("tplbatchadd".equals(get_dllname())) return dllpar6; + if (getIdValue() != null && !getIdValue().isEmpty()) return ""; + return dllpar6; + } + + // Pms属性 + @JsonIgnore + @JsonProperty("Pms") + public Map Pms; + + // PmOffset属性 + @JsonProperty("PmOffset") + public int getPmOffset() { + if (get_dllname() == null || get_dllname().isEmpty()) return 0; + switch (get_dllname()) { + case "report": + return 3; + case "pubbill": + return 2; + default: + return 3; + } + } + + private Map _popPms; + + @JsonIgnore + // PopPms属性 + @JsonProperty("PopPms") + public Map getPopPms() { + if (_popPms != null) return _popPms; + + Map _retTab = new HashMap<>(); + if (Pms != null) { + _retTab.putAll(Pms); + } + _retTab.put("p_1", dllpar1); + _retTab.put("p_2", dllpar2); + _retTab.put("p_3", dllpar3); + _retTab.put("p_4", dllpar4); + _retTab.put("p_5", dllpar5); + _retTab.put("p_6", dllpar6); + _retTab.put("p_7", dllpar7); + _retTab.put("p_8", dllpar8); + _retTab.put("p_9", dllpar9); + _retTab.put("p_10", dllpar10); + + Map retTab = new HashMap<>(); + for (Map.Entry entry : _retTab.entrySet()) { + if (entry.getValue() != null && !entry.getValue().toString().isEmpty()) { + try { + int i = Integer.parseInt(entry.getKey().substring(2)); + if (i == 6) { + switch (get_dllname()) { + case "pubadd": + case "pubadd2": + case "pubadd3": + case "tplbatchadd": + case "tpladd": + break; + default: + retTab.put("p_" + (i - getPmOffset()), entry.getValue()); + break; + } + } else { + retTab.put("p_" + (i - getPmOffset()), entry.getValue()); + } + } catch (NumberFormatException e) { + // 忽略非数字键 + } + } + } + + _popPms = retTab; + return retTab; + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Container/ChartContainer.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Container/ChartContainer.java new file mode 100644 index 0000000..0794770 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Container/ChartContainer.java @@ -0,0 +1,54 @@ +package org.example.Entity.Control.Container; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.example.Entity.Control.Panel.Panel; +import org.example.Utils.MD5Util; + + +/** + * ============================================================================== + * 功能描述:ChartContainer + * ============================================================================== + */ +public class ChartContainer extends Panel { + + public ChartContainer() { + super(); + this.defaultXtype = "plugins.PubChart.ChartFormater"; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String ModuleId; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String DetailId; + + private String _s; + private String _ens; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getS() { + if (_ens == null && _s != null && !_s.isEmpty()) { + _ens = MD5Util.encrypt(_s); + } + return _ens; + } + + public void setS(String value) { + _s = value; + _ens = null; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object ChartCfg; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object RightMenu; + + public Integer displayRows; + + +} + diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Container/Column.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Container/Column.java new file mode 100644 index 0000000..5b58078 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Container/Column.java @@ -0,0 +1,26 @@ +package org.example.Entity.Control.Container; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.example.Entity.Control.Fields.Field; + +public class Column extends Field { + public Column() { + super(null); + this.defaultXtype = "gridcolumn"; + } + + /** + * 标题 + */ + public String text; + + /** + * 指向的数据名称 + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + public String dataIndex; + + +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Container/FieldSet.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Container/FieldSet.java new file mode 100644 index 0000000..30fdde8 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Container/FieldSet.java @@ -0,0 +1,42 @@ +package org.example.Entity.Control.Container; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.example.Entity.Control.Fields.Field; +import org.example.Utils.DataTableUtil; +import org.example.Utils.IPublicUtil; + +import java.util.Map; + +public class FieldSet extends Field { + + public FieldSet(Map row) { + super(row); + this.defaultXtype = "fieldset"; + } + + public FieldSet(Map row, IPublicUtil util) { + super(row, util); + this.defaultXtype = "fieldset"; + } + + /** + * 标题,从FieldCaption或GroupName字段获取 + */ + + public String getTitle() { + Object value = DataTableUtil.get(_row, new String[]{"FieldCaption", "GroupName"}, ""); + return value != null ? value.toString() : ""; + } + + /** + * 是否折叠,基于isexpand字段判断 + */ + public boolean isCollapsed() { + Object value = _row.getOrDefault("isexpand", "1"); + if (value == null) return false; + if (value instanceof Boolean) return !(Boolean) value; + String strValue = (String) value; + return !("1".equals(strValue) || "true".equalsIgnoreCase(strValue)); + } + +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Container/Image.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Container/Image.java new file mode 100644 index 0000000..5cccd4d --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Container/Image.java @@ -0,0 +1,27 @@ +package org.example.Entity.Control.Container; + +import org.example.Entity.Control.Fields.Field; + +import java.util.Map; + +public class Image extends Field { + + public Image(Map row) { + super(row); + this.defaultXtype = "img"; + } + + private String _src; + + public String getSrc() { + if (_src == null || _src.isEmpty()) { + _src = getDefaultval() != null ? getDefaultval() + "" : ""; + } + return _src; + } + + public void setSrc(String src) { + this._src = src; + } + +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Container/MContainer.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Container/MContainer.java new file mode 100644 index 0000000..d5f0fa5 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Container/MContainer.java @@ -0,0 +1,46 @@ +package org.example.Entity.Control.Container; + +import com.fasterxml.jackson.annotation.JsonInclude; +import org.example.Entity.Control.Base.Component; + +import java.util.Objects; + +/** + * ============================================================================== + * 功能描述:MContainer 容器,xtype 由 dllname 和 dlltype 决定,用于多标签打开的模块 + * ============================================================================== + */ +public class MContainer extends Component { + + public MContainer() { + super(); + this.defaultXtype = "MContainer"; + } + + public String mXtype; + public String title; + public String ModuleId; + public String ParentModuleId; + public String DetailId; + public Object store; + private String _leftUnionName; + + public String getLeftUnionName() { + return Objects.toString(_leftUnionName, "").toLowerCase().replace("{", "").replace("}", ""); + } + + public void setLeftUnionName(String leftUnionName) { + this._leftUnionName = leftUnionName; + } + + public String leftUnionValueName; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object MobileCards; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object QueryPms; + public Object ElseValue; + + +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Container/RowColumn.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Container/RowColumn.java new file mode 100644 index 0000000..e9fe892 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Container/RowColumn.java @@ -0,0 +1,423 @@ +package org.example.Entity.Control.Container; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.example.Entity.Control.Fields.ComboBox; +import org.example.Entity.Control.Fields.Field; +import org.example.Entity.Control.Validators.VBase; +import org.example.Entity.System.ModuleEntity; +import org.example.Enums.SystemEnums; +import org.example.Utils.DataTableUtil; +import org.example.Utils.IPublicUtil; +import org.example.Utils.NativeExtensionUtils; +import org.example.Utils.PublicUtil; + +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; + +import static org.example.Utils.NativeExtensionUtils.*; + +// 假设 Field 类是 RowColumn 的父类 +public class RowColumn extends Field { + + public RowColumn(Map _row, boolean selfEdit) { + super(_row); + this.selfEdit = selfEdit; + } + + public RowColumn(Map _row, boolean selfEdit, IPublicUtil util) { + super(_row, util); + this.selfEdit = selfEdit; + } + + private String _xtype; + + @Override + public String getXtype() { + if (_xtype == null || _xtype.isEmpty()) { + _xtype = PublicUtil.GetEColumnTypeByCType(getFdType()); + + if (getEditor() != null && !isValeqkey()) { + _xtype = "editor.editColumn"; + if (getDataSource() == null || getDataSource().isEmpty()) { + _xtype = "gridcolumn"; + } + } else if (!isEditable() && getUneditor() != null) { + _xtype = "editor.comboboxColumn"; + if (getDataSource() == null || getDataSource().isEmpty()) { + _xtype = "gridcolumn"; + } + } else if ("gridcolumn".equals(_xtype) && getFieldDbType() > 0) { + _xtype = PublicUtil.TypeToColumnType(PublicUtil.SqlxtypeToProType(getFieldDbType())); + + } + + if (getEditor() != null && isValeqkey()) { + setFormat(""); + } + } + return _xtype; + } + + @Override + public void setXtype(String xtype) { + this._xtype = xtype; + } + + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("Linknames") + public String[] linknames; + + + private Boolean _locked; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("Locked") + public Boolean getLocked() { + if (_locked == null) { + _locked = DataTableUtil.getBooleanValue(_row, "locked", false); + } + return _locked ? _locked : null; + } + + public void setLocked(Boolean locked) { + this._locked = locked; + } + + private Boolean _scanAble; + + @JsonIgnore + public Boolean getScanAble() { + if (_scanAble == null) { + _scanAble = DataTableUtil.getBooleanValue(_row, "scanAble", false); + } + return _scanAble ? _scanAble : null; + } + + public void setScanAble(Boolean scanAble) { + this._scanAble = scanAble; + } + + private Object _scanField; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("ScanField") + public Object getScanField() { + if (getScanAble() != null && getScanAble()) { + _scanField = getUtil().createControl(_row, module, false); + } + return _scanField; + } + + @JsonIgnore + public ModuleEntity module; + + + public Integer width; + + @Override + public Integer getWidth() { + return ToInt32(DataTableUtil.getIntValue(_row, "width", 100)); + } + + public Integer height; + + @Override + public Integer getHeight() { + return null; + } + + + @Override + public Boolean getHidden() { + return super.getColHidden(); + } + + + public Boolean getAutoRowspan() { + Boolean _r = DataTableUtil.getBooleanValue(_row, "rowspan", false); + return _r ? _r : null; + } + + public int ControlTabIndex; + + private SystemEnums.ControlType _FieldType; + + @JsonIgnore + public SystemEnums.ControlType getFdType() { + if (_FieldType == null) { + try { + Object obj = Enum.valueOf(SystemEnums.ControlType.class, String.valueOf(getFieldType())); + if (obj != null) { + _FieldType = (SystemEnums.ControlType) obj; + } + } catch (IllegalArgumentException e) { + _FieldType = SystemEnums.ControlType.LabText; + } + } + + if (PublicUtil.SqlxtypeToProType(getFieldDbType()) == Boolean.class) { + _FieldType = SystemEnums.ControlType.LabCheckBox; + } else if (isIsSum() && (getFormat() == null || getFormat().toLowerCase().indexOf("yy") < 0)) { + _FieldType = SystemEnums.ControlType.LabTextInt; + } + + return _FieldType != null ? _FieldType : SystemEnums.ControlType.LabText; + } + + @JsonIgnore + public int getFieldDbType() { + String val = DataTableUtil.getStringValue(_row, new String[]{"FieldDbType"}, ""); + return ToInt32(val); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("DataType") + public String getDataType() { + String datatype = DataTableUtil.getStringValue(_row, new String[]{"datatype"}, ""); + switch (datatype.toLowerCase()) { + case "varchar": + return "textfield"; + case "datetime": + return "datefield"; + default: + return ""; + } + } + + + public Boolean getDisabled() { + return null; + } + + @JsonIgnore + public String getFieldName() { + return ((String) DataTableUtil.getRowVal(_row, new String[]{"fieldname"}, "")).toLowerCase(); + } + + public String getDataIndex() { + return getFieldName(); + } + + + @JsonIgnore + @Override + public Integer getLeft() { + return super.getLeft(); + } + + @JsonIgnore + @Override + public void setLeft(Integer left) { + super.setLeft(left); + } + + + @JsonIgnore + @Override + public Integer getTop() { + return super.getTop(); + } + + @JsonIgnore + @Override + public void setTop(Integer top) { + super.setTop(top); + } + + + public String getText() { + String label = getFieldLabel(); + return (label == null || label.isEmpty()) ? getDataIndex() : label; + } + + + public Integer getTextAlign() { + int textAlign = ToInt32(DataTableUtil.get(_row, "TextAlign", getFieldLabel(), 0)); + return textAlign != 0 ? textAlign : null; + } + + private String _fieldLabel; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("FieldLabel") + public String getFieldLabel() { + String label = (String) DataTableUtil.get(_row, "FieldCaption", _fieldLabel, ""); + if (label != null && !label.isEmpty()) { + label = label.replace("\r\n", ""); + if (label.indexOf("|") > -1) { + String[] ls = label.split("\\|"); + return ls[1]; + } + } + return label; + } + + public void setFieldLabel(String fieldLabel) { + this._fieldLabel = fieldLabel; + } + + private Boolean selfEdit; + + private Boolean _editable; + + public boolean isEditable() { + if (_editable != null) { + return _editable; + } + _editable = !super.isReadOnly() && selfEdit; + return _editable; + } + + public void setEditable(boolean editable) { + this._editable = editable; + if (getEditor() != null && getEditor() instanceof Field) { + ((Field) getEditor()).setReadOnly(false); + } + } + + private Object _uneditor; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object getUneditor() { + if (_uneditor == null) { + Object _tmp = getEditor(); + } + return _uneditor; + } + + public void setUneditor(Object uneditor) { + this._uneditor = uneditor; + } + + private Object _editor; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object getEditor() { + Boolean resultdisabled = getDisabled() != null ? getDisabled() : false; + + if (getFdType() != SystemEnums.ControlType.LabCheckBox && _editor == null && !resultdisabled) { + boolean[] valeqkeyHolder = new boolean[1]; + _editor = getUtil().getGridColumnEditType(this, _row, module, valeqkeyHolder); + this._valekey = valeqkeyHolder[0]; + if (!valeqkeyHolder[0] && _editor != null && _editor instanceof ComboBox) { + _uneditor = _editor; + } + } + + if (!isEditable()) { + return null; + } + + _uneditor = null; + return _editor; + } + + public String tdCls; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("TdCls") + public String getTdCls() { + return isEditable() ? "editable" : null; + } + + private Boolean _valekey = null; + + public Boolean isValeqkey() { + if (_valekey == null && getEditor() == null) { + _valekey = true; + } + return (_valekey != null) ? _valekey : true; + } + + public void setValeqkey(Boolean valeqkey) { + this._valekey = valeqkey; + } + + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("TagId") + public Integer getTagId() { + int _tagId = DataTableUtil.getIntValue(_row, "tagid", 0); + return _tagId > 0 ? _tagId : null; + } + + private Object _fDefaultVal; + + @JsonProperty("fDefaultVal") + public Object getFDefaultVal() { + if (_fDefaultVal == null) { + _fDefaultVal = DataTableUtil.getRowVal(_row, "defaultValue", null); + if (_fDefaultVal != null && _fDefaultVal.toString().startsWith("@")) { + _fDefaultVal = ""; + } + } + return _fDefaultVal; + } + + + @JsonProperty("StoreFieldType") + public String getStoreFieldType() { + return PublicUtil.GetEFieldTypeByCType(getFdType(), getFormat()); + } + + + @JsonIgnore + public Object getStoreField() { + Object DefaultValue = getDefaultval(); + if (DefaultValue == null || DefaultValue.toString().isEmpty()) { + DefaultValue = PublicUtil.GetDefaultValByCType(getFdType()); + } + + Object Validators = null; + if (!isAllowBlank()) { + Validators = new VBase[]{new VBase("Presence")}; + } + + Object finalDefaultValue = DefaultValue; + Object finalValidators = Validators; + return new Object() { + public final String text = getText(); + public final String name = getDataIndex(); + public final String type = getStoreFieldType(); + public final Object defaultValue = finalDefaultValue; + public final String dateFormat = getFormat(); + public final Object validators = finalValidators; + }; + } + + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("BandTitle") + public String getBandTitle() { + String label = DataTableUtil.getStringValue(_row, "FieldCaption", _fieldLabel); + if (label != null && !label.isEmpty() && label.indexOf("|") > -1) { + String[] ls = label.split("\\|"); + return ls[0]; + } + return DataTableUtil.getStringValue(_row, "bandtitle", ""); + } + + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("BandFields") + public String[] getBandFields() { + if (!isNullOrEmpty(getBandTitle())) { + if (isNullOrEmpty(DataTableUtil.getStringValue(_row, "bandtitle", ""))) { + return new String[]{getDataIndex()}; + } + String fields = (String) DataTableUtil.getStringValue(_row, "bandfield", null); + if (!isNullOrEmpty(fields)) { + String[] result = Arrays.stream(fields.toLowerCase().split("[|,]")) + .filter(str -> str != null && !str.trim().isEmpty()) + .toArray(String[]::new); + return result; + } + return null; + } + return null; + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Data/DataStore.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Data/DataStore.java new file mode 100644 index 0000000..41e4de8 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Data/DataStore.java @@ -0,0 +1,77 @@ +package org.example.Entity.Control.Data; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.example.Entity.Control.Base.Base; + +import java.util.Objects; + +/** + * ============================================================================== + * 功能描述:DataStore + * ============================================================================== + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class DataStore extends Base { + public DataStore() { + super(); + this.defaultXtype = "datastore"; + } + + /** + * 此参数可以用来代替model参数. fields值应该是一个Ext.data.Field属性对象的集合 + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + public String url; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Boolean getIsApi() { + if (url != null && !url.isEmpty()) { + return true; + } + return null; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object getApi() { + if ((ApiDataNode != null && !ApiDataNode.isEmpty()) || (ApiSuccNode != null && !ApiSuccNode.isEmpty()) || (ApiSuccVal != null && !ApiSuccVal.isEmpty())) { + return new Object() { + public final Object reader = new Object() { + public final String success = ApiSuccNode; + public final String data = ApiDataNode; + public final String successVal = ApiSuccVal; + }; + }; + } + return null; + } + + @JsonIgnore + public String ApiDataNode; + + @JsonIgnore + public String ApiSuccNode; + + @JsonIgnore + public String ApiSuccVal; + + /** + * 此参数可以用来代替model参数. fields值应该是一个Ext.data.Field属性对象的集合 + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object[] fields; + + /** + * 数据 + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object data; + + /** + * 存在的参数 + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object extraParams; + +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/Checkbox.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/Checkbox.java new file mode 100644 index 0000000..d0f3bd2 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/Checkbox.java @@ -0,0 +1,35 @@ +package org.example.Entity.Control.Fields; + + +import org.example.Entity.Control.Fields.Field; +import org.example.Utils.DataTableUtil; + +import java.util.Map; + +/** + * ============================================================================== + * 功能描述:Checkbox 组件 + * ============================================================================== + */ +public class Checkbox extends Field { + + public Checkbox(Map row) { + super(row); + this.defaultXtype = "checkbox"; + } + + private Boolean _value; + + @Override + public Object getValue() { + if (_value == null) { + return DataTableUtil.toBoolean(getDefaultval(), false); + } + return _value; + } + + @Override + public void setValue(Object value) { + this._value = DataTableUtil.toBoolean(value, false); + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/ComboBox.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/ComboBox.java new file mode 100644 index 0000000..b2db769 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/ComboBox.java @@ -0,0 +1,302 @@ +package org.example.Entity.Control.Fields; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.example.Entity.Control.Fields.Field; +import org.example.Entity.Control.Container.Column; +import org.example.Enums.SystemTypeEnums; +import org.example.Utils.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.ResultSetExtractor; +import org.springframework.jdbc.core.RowCallbackHandler; +import org.springframework.stereotype.Component; + +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.example.Utils.DataTableUtil.get; + +/** + * ============================================================================== + * 功能描述:ComboBox + * ============================================================================== + */ + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ComboBox extends Field { + + public static final Pattern RECORD_PATTERN_1 = Pattern.compile("#([^#])+#"); + public static final Pattern RECORD_PATTERN_2 = Pattern.compile("\\{([^{])+}"); + + + public ComboBox(Map row) { + super(row); + this.defaultXtype = "combobox"; + } + + public ComboBox(Map row, IPublicUtil util) { + this(row); + this.setUtil(util); + } + + /** + * 联动,触发该控件刷新的上级控件名称 + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + public String[] getLinkParentNames() { + if (!isIsPostRecord()) { + return null; + } + String[] names = PublicUtil.getParamValue(getDataSource()) + .stream() + .map(str -> str.replaceAll("[{}]", "").toLowerCase()) + .toArray(String[]::new); + if (names.length == 0) { + return null; + } + return names; + } + + /** + * 是否是带参数的下拉框 + */ + @JsonIgnore + public boolean parmbox; + + /** + * 是否可输入 + */ + public boolean editable; + + /** + * 是否可以多选 + */ + public boolean multiSelect; + + /** + * 查询的参数名 + */ + public String queryParam; + + /** + * 查询时,是否需要把自身的recordpost到服务器 + */ + public boolean isIsPostRecord() { + String dataSource = getDataSource(); + if (dataSource == null || dataSource.isEmpty()) { + return false; + } + Matcher matcher1 = RECORD_PATTERN_1.matcher(dataSource); + Matcher matcher2 = RECORD_PATTERN_2.matcher(dataSource); + return matcher1.find() || matcher2.find(); + } + + public String emptyText; + + private boolean autoSelect = true; + + public boolean isAutoSelect() { + return autoSelect; + } + + public void setAutoSelect(boolean autoSelect) { + this.autoSelect = autoSelect; + } + + /** + * 其他字段 格式:字段|显示名称,字段2|显示名称 + */ + private List _otherMember; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public List getColumns() { + if (_otherMember == null) { + List list = new ArrayList<>(); + String omembers = (String) get(_row, "OtherMember", null); + + // 处理OtherMember配置的列 + if (omembers != null && !omembers.trim().isEmpty()) { + String[] ms = omembers.split(","); + for (String m : ms) { + if (m == null || m.trim().isEmpty()) { + continue; + } + String[] kv = (m + "|").split("\\|"); + Column column = new Column(); + column.text = kv.length > 1 ? kv[1].trim() : ""; // 处理没有|分隔的情况 + column.dataIndex = kv[0].trim(); + column.setWidth(1); + list.add(column); + } + if (!list.isEmpty()) { + _otherMember = list; + } else { + return null; // 对应C#的 list.Count == 0 逻辑 + } + } + // 处理数据源SQL获取的列 + else if (getDataSource() != null && !getDataSource().isEmpty() && !isReadOnly()) { + String sql = getDataSource(); + List> tbVal = null; + Set columnNames = new HashSet<>(); + + try { + // 处理SQL:移除#符号并添加and 1!=1条件 + String processedSql = sql.replace("#", ""); + SqlAnalyzer sqlAnalyzer = new SqlAnalyzer(processedSql); + Map paramMap = new HashMap<>(); + paramMap.put("a", "and 1!=1"); + sql = sqlAnalyzer.InsertWhere(paramMap, false, false, false); + + // 执行查询并收集列名(通过ResultSetMetaData) + this.getDbOperator().query( + PublicUtil.ReqSqlPmsByRow(null, null, sql, SystemTypeEnums.PmType.sql), + new ResultSetExtractor() { + @Override + public Void extractData(ResultSet rs) throws SQLException, DataAccessException { + // 无论是否有行数据,先获取元数据 + ResultSetMetaData metaData = rs.getMetaData(); + int columnCount = metaData.getColumnCount(); + for (int i = 1; i <= columnCount; i++) { + columnNames.add(metaData.getColumnName(i)); + } + // 如需处理行数据,再循环 rs.next() + return null; + } + } + ); + } catch (Exception e) { +// err.printf("解析下拉框列时执行sql错误:SQL=%s, 错误信息=%s%n", sql, e.getMessage()); + } + + // 处理列过滤和转换 + if (!columnNames.isEmpty()) { + List otherMemberList = new ArrayList<>(); + String valueMember = DataTableUtil.getStringValue(_row, "ValueMember", ""); + String displayField = getDisplayField(); + String valueField = getValueField(); + String xtype = getXtype() != null ? getXtype().toLowerCase() : ""; + for (String colName : columnNames) { + // 过滤条件:wincombobox类型或非特殊列,且列名不以_开头 + boolean isWinComboBox = "wincombobox".equals(xtype); + boolean isNotSpecialColumn = !colName.equals(displayField) + && !colName.equals(valueField) + && !colName.equals(valueMember); + boolean condition = (isWinComboBox || isNotSpecialColumn) + && !colName.startsWith("_"); + + if (condition) { + Column column = new Column(); + // 设置列文本(对应C#的三元表达式逻辑) + if ("dm".equals(colName)) { + column.text = "编码"; + } else if ("mc".equals(colName)) { + column.text = "名称"; + } else if ("remark".equals(colName)) { + column.text = "备注"; + } else { + column.text = colName; + } + // 设置数据索引和宽度 + column.dataIndex = colName; + column.setWidth(getColumnWidth(colName)); + + otherMemberList.add(column); + } + } + + // 空列集合处理 + if (otherMemberList.isEmpty()) { + _otherMember = null; + } else { + _otherMember = otherMemberList; + } + + // 单列或可编辑状态下添加显示列和值列 + if ((_otherMember != null && _otherMember.size() == 1) || this.editable) { + if (_otherMember == null) { + _otherMember = new ArrayList<>(); + } + // 添加显示列(名称) + if (displayField != null && !displayField.startsWith("_")) { + Column displayColumn = new Column(); + displayColumn.text = "名称"; + displayColumn.dataIndex = displayField; + displayColumn.setWidth(1); + _otherMember.add(0, displayColumn); + } + // 添加值列(编号) + if (displayField != null && valueField != null + && !displayField.equals(valueField) + && !valueField.startsWith("_")) { + Column valueColumn = new Column(); + valueColumn.text = "编号"; + valueColumn.dataIndex = valueField; + valueColumn.setWidth(1); + _otherMember.add(0, valueColumn); + } + } + } + } + } + return _otherMember; + } + + public void setColumns(List columns) { + this._otherMember = columns; + } + + public String getColumnText(String colName) { + switch (colName) { + case "dm": + return "编码"; + case "mc": + return "名称"; + case "remark": + return "备注"; + default: + return colName; + } + } + + private int getColumnWidth(String colName) { + if ("dm".equals(colName) || "编码".equals(colName)) { + return 1; + } else if ("mc".equals(colName) || "名称".equals(colName) + || "remark".equals(colName) || "备注".equals(colName)) { + return 2; + } else { + return 2; + } + } + + /** + * 下拉框的类型 + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + public String pickxtype; + + /** + * 下拉框的类型 + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object pickerCfg; + + /** + * 是否在获取值的时候,获取匹配的下拉值 + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + public Boolean MValue; + + @JsonIgnore + public boolean isDoNotSpelling() { + return NativeExtensionUtils.toBoolean(DataTableUtil.getIntValue(_row, "donotspelling", 0)); + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/ComboTreeBox.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/ComboTreeBox.java new file mode 100644 index 0000000..46deec0 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/ComboTreeBox.java @@ -0,0 +1,10 @@ +package org.example.Entity.Control.Fields; + +import java.util.Map; + +public class ComboTreeBox extends ComboBox { + + public ComboTreeBox(Map row) { + super(row); + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/DateField.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/DateField.java new file mode 100644 index 0000000..df421d0 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/DateField.java @@ -0,0 +1,98 @@ +package org.example.Entity.Control.Fields; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +// 假设这里存在 Field 类 +import org.example.Entity.Control.Fields.Field; +import org.example.Utils.NativeExtensionUtils; + +import java.util.regex.Pattern; + + +public class DateField extends Field { + private String _format; + + public DateField(java.util.Map row) { + super(row); + this.defaultXtype = "fields.Date"; + } + + public String getFormat() { + if (_format == null || _format.isEmpty()) { + return "yyyy-MM-dd"; + } + return _format.replace("DD", "dd"); + } + + public void setFormat(String format) { + this._format = format; + } + + @Override + public Object getValue() { + String formater = this.getFormat(); // 对应 C# 的 this.format + Date outdate; + + // 处理 defaultsource 以 "{#p_" 开头且 defaultval 为空的情况 + if (getDefaultsource() != null && getDefaultsource().startsWith("{#p_") + && (getDefaultval() == null || (getDefaultval() + "").trim().isEmpty())) { + return getDefaultsource(); + } + + // 处理两者均为空的情况 + if ((getDefaultsource() == null || getDefaultsource().trim().isEmpty()) + && (getDefaultval() == null || (getDefaultval() + "").trim().isEmpty())) { + return null; + } + + // 解析 defaultval 为日期,失败则使用当前时间 + outdate = NativeExtensionUtils.toDateTime(getDefaultval()); + if (outdate == null) { // 若工具类返回 null,兜底为当前时间 + outdate = new Date(); + } + + // 按格式返回字符串(使用 SimpleDateFormat) + if (formater == null || formater.isEmpty()) { + formater = "yyyy-MM-dd"; // 默认格式,可参考 DateField 类 + } + try { + SimpleDateFormat sdf = new SimpleDateFormat(formater); + return sdf.format(outdate); + } catch (IllegalArgumentException e) { + // 处理非法格式,返回默认格式 + return new SimpleDateFormat("yyyy-MM-dd").format(outdate); + } + + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object getMin() { + return getLimitMinValue(); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object getMax() { + if (NativeExtensionUtils.isNullOrEmpty(getLimitMaxValue())) { + return null; + } + try { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + sdf.parse(getLimitMaxValue() + ""); + return getLimitMaxValue(); + } catch (ParseException e) { + return null; + } + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object getDateType() { + String formatStr = getFormat().toLowerCase(); + return formatStr.contains("hh") ? "datetime" : null; + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/Field.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/Field.java new file mode 100644 index 0000000..4fdbef8 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/Field.java @@ -0,0 +1,1010 @@ +package org.example.Entity.Control.Fields; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.example.Entity.Control.Base.RowComponent; +import org.example.Entity.Control.Data.DataStore; +import org.example.Enums.SystemTypeEnums; +import org.example.Utils.DataTableUtil; +import org.example.Utils.IPublicUtil; +import org.example.Utils.PublicUtil; + +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +import static org.example.Utils.DataTableUtil.get; +import static org.example.Utils.NativeExtensionUtils.*; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Field extends RowComponent { + private static final Logger log = LoggerFactory.getLogger(Field.class); + + + + public Field(Map _row) { + super(_row); + this.defaultXtype = "textfield"; + } + + public Field(Map _row, IPublicUtil util) { + this(_row); + this.setUtil(util); + } + + + @JsonProperty("FieldId") + public int getFieldId() { + return ToInt32(get(_row, "id", 0)); + } + + private String _ftype; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getFtype() { + _ftype = (_ftype != null) ? _ftype : DataTableUtil.getStringValue(_row, "ftype", ""); + if (_ftype != null && _ftype.isEmpty()) { + return null; + } + return _ftype; + } + + public void setFtype(String ftype) { + this._ftype = ftype; + } + + + private String _name; + + public String getName() { + if (_name != null) { + return _name; + } else { +// return (DataTableUtil.getRowVal(this._row, new String[]{"FieldName", "id"}, _name) != null) ? DataTableUtil.getRowVal(this._row, new String[]{"FieldName", "id"}, _name) + "" : ""; + return Objects.toString(DataTableUtil.getRowVal(this._row, new String[]{"FieldName", "id"}, _name), "").toLowerCase(); + } + } + + public void setName(String name) { + this._name = name; + } + + private String _fieldLabel; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getFieldLabel() { + String label = (String) get(_row, "FieldCaption", _fieldLabel, null); + if (label != null && !label.isEmpty()) { + if (label.indexOf('|') > -1) { + String[] ls = label.split("\\|"); + LabelSuffix = ls[1]; + log.debug(String.valueOf(LabelSuffix + " " + ls[1])); + return ls[0]; + } + } + return label; + } + + public void setFieldLabel(String fieldLabel) { + this._fieldLabel = fieldLabel; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public DataStore store; + + + private String LabelSuffix; + + @JsonProperty("LabelSuffix") + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getLabelSuffix() { + return LabelSuffix; + } + + private int _labelfontsize = 12; + + protected int getLabelfontsize() { + Integer fontSize = getFontSize(); + return fontSize == null ? 12 : Math.max(fontSize, 12); + } + + private Integer _labelWidth; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Integer getLabelWidth() { + if (_labelWidth != null) { + return _labelWidth; + } +// _labelWidth = parseInt((String) get(_row, "labelwidth", null)); + Object labelWidthObj = get(_row, "labelwidth", null); + _labelWidth = parseIntSafe(labelWidthObj, 0); + if (_labelWidth != null && _labelWidth > 0) { + return _labelWidth; + } + if (getFieldLabel() == null || getFieldLabel().isEmpty()) { + return null; + } + if (getName() == null || getName().isEmpty()) { + return getWidth(); + } + Pattern reg = Pattern.compile("[^\\x00-\\xFF]"); + Matcher matcher = reg.matcher(getFieldLabel()); + int tlen = getFieldLabel().length(); + int clen = matcher.replaceAll("").length(); + int totlen = tlen - clen + clen / 2 + 1; + _labelWidth = (totlen * getLabelfontsize()) + (clen % 2 > 0 ? 6 : 0) + 6; + return _labelWidth; + } + + public void setLabelWidth(Integer labelWidth) { + this._labelWidth = labelWidth; + } + + private String _labelAlign; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getLabelAlign() { + if (_labelAlign != null) { + return _labelAlign; + } + _labelAlign = DataTableUtil.getStringValue(_row, "labelalign", null); + if (_labelAlign == null || _labelAlign.isEmpty()) { + return null; + } + return _labelAlign; + } + + public void setLabelAlign(String labelAlign) { + this._labelAlign = labelAlign; + } + + private Integer _width; + + + public Integer getWidth() { + if (_width == null) { + // 从 _row 中获取 "ControlWidth",使用 DataTableUtil 工具类(代码库中统一的取值方式) + Object value = get(_row, "ControlWidth", null); + // 转换为 Integer,处理 null 情况(参考 Field.java 中的转换逻辑) + _width = (value != null) ? Integer.parseInt(value.toString()) : null; // 或根据业务设为 null + } + return _width; + } + + + public void setWidth(Integer width) { + this._width = width; + } + + private Integer _height; + + + public Integer getHeight() { + if (_height == null) { + // 1. 先获取原始值(可能为null) + Object value = get(_row, "ControlHeight", null); + + // 2. 处理null值:设置默认值0(或根据业务需求调整) + if (value == null) { + _height = null; // 数据库为NULL时,默认值为0 + } else { + // 3. 确保非null时再转换 + _height = Integer.parseInt(value.toString()); + } + } + return _height; + } + + + public void setHeight(Integer height) { + this._height = height; + } + + + @JsonIgnore + public Integer PopupWidth; + @JsonIgnore + public Integer PopupHeight; + + @JsonProperty("hidden") + @Override + public Boolean getHidden() { + return getControlHidden(); + } + + private Boolean _uppercaseHidden; + + @JsonProperty("Hidden") + public boolean isUppercaseHidden() { + if (_uppercaseHidden == null) { + _uppercaseHidden = toBoolean(get(_row, "Disabled2", false)); + } + if (!_uppercaseHidden) { + _uppercaseHidden = getPurviewWidth() == 0; + } + return _uppercaseHidden; + } + + @JsonProperty("Hidden") + public void setUppercaseHidden(Boolean hidden) { + this._uppercaseHidden = hidden; + } + + private Boolean _issearchcontrol; + + @JsonIgnore + public boolean isIsSearchcontrol() { + if (_issearchcontrol == null && (_issearchcontrol = toBoolean(DataTableUtil.getRowVal(_row, "searchcontrol", false)))) { + return true; + } + return _issearchcontrol; + } + + public void setIssearchcontrol(Boolean issearchcontrol) { + this._issearchcontrol = issearchcontrol; + } + + + @JsonProperty("ColHidden") + public Boolean getColHidden() { + boolean colHidden = toBoolean(DataTableUtil.getRowVal(this._row, "Disabled", false)); + if (!colHidden) { + colHidden = getPurviewWidth() == 0; + } + return colHidden; + } + + private Boolean _canCopy = null; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Boolean getCanCopy() { + if (_canCopy == null) { + _canCopy = DataTableUtil.getBooleanValue(_row, "cancopy", false); + } + if (_canCopy) { + return true; + } + return null; + } + + + @JsonIgnore + public boolean isDisabled2() { + return DataTableUtil.getBooleanValue(_row, "disabled2", false); + } + + private Boolean _hidden; + + @JsonProperty("ControlHidden") + public Boolean getControlHidden() { + if (isIsSearchcontrol()) { + return isDisabled2() || (getWidth() != null && getWidth() == 0); + } + if (_hidden == null) { + _hidden = toBoolean(get(_row, "disabled2", false)) || (getWidth() != null && getWidth() == 0) || (getTop() != null && getTop() < -3) || (getLeft() != null && getLeft() < -3) || (PopupWidth != null && PopupWidth > 30 && getLeft() > PopupWidth) || (PopupHeight != null && PopupHeight > 30 && getTop() > PopupHeight); + } + if (_hidden == false) { + _hidden = getPurviewWidth() == 0; + } + return _hidden != null ? _hidden : false; + } + + + @JsonIgnore + public int getFieldType() { + return ToInt32(get(_row, "FieldType", 0)); + } + + + @JsonProperty("FieldDataType") + public int getFieldDataType() { + return ToInt32(get(_row, "FieldDataType", 0)); + } + + + @Override + public Integer getTop() { + if (top == null) { + // 1. 先获取原始值(可能为null) + Object value = get(_row, "ControlTop", null); + + // 2. 处理null值:设置默认值0(或根据业务需求调整) + if (value == null) { + top = null; // 数据库为NULL时,默认值为0 + } else { + // 3. 确保非null时再转换 + top = Integer.parseInt(value.toString()); + } + } + return top; + } + + @Override + public void setTop(Integer top) { + this.top = top; + } + + + @Override +// public Integer getLeft() { +// if (left == null) { +// // 1. 先获取原始值(可能为null) +// Object value = get(_row, "ControlLeft", null); +// +// // 2. 处理null值:设置默认值0(或根据业务需求调整) +// if (value == null) { +// left = null; +// } else { +// // 3. 确保非null时再转换 +// left = Integer.parseInt(value.toString()); +// } +// } +// return left; +// } + public Integer getLeft() { + if (left == null) { + Object value = get(_row, "ControlLeft", null); + if (value == null) { + left = 0; // 默认值 0 + } else { + try { + left = Integer.parseInt(value.toString()); + } catch (NumberFormatException e) { + left = 0; // 转换失败也给默认值 + } + } + } + return left; + } + + @Override + public void setLeft(Integer left) { + this.left = left; + } + + private Integer _tabIndex; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Integer getTabIndex() { + if (_tabIndex == null) { + _tabIndex = (Integer) get(_row, "TabOrderId", null); + } + return _tabIndex; + } + + public void setTabIndex(Integer tabIndex) { + this._tabIndex = tabIndex; + } + + private Boolean _allowblank = null; + + public boolean isAllowBlank() { + if (_allowblank == null) { + _allowblank = !toBoolean(get(_row, "Nullable", null)); + } + return _allowblank; + } + + public void setAllowBlank(Boolean allowblank) { + this._allowblank = allowblank; + } + + + private String _enableCond; + + @JsonProperty("EnableCond") + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getEnableCond() { + if (_enableCond != null && !_enableCond.isEmpty()) { + return _enableCond; + } + _enableCond = (String) get(_row, "disableCond", _enableCond, null); + _enableCond = PublicUtil.ReqSqlPms(null, null, PublicUtil.SqlToCode(_enableCond), org.example.Enums.SystemTypeEnums.PmType.ignorenull, null); + return _enableCond; + } + + public void setEnableCond(String enableCond) { + this._enableCond = enableCond; + } + + + @JsonProperty + public Integer getCondEnableType() { + int tp = ToInt32(get(_row, "disableType", null)); + if (tp == 0) { + return null; + } + return tp; + } + + public boolean enableDisValue; + + private Boolean _readOnly = null; + + public boolean isReadOnly() { + if (_readOnly == null) { + _readOnly = toBoolean(DataTableUtil.getRowVal(_row, "Edit", false)); + } + if (!_readOnly) { + _readOnly = !getOperPurview(); + } + return _readOnly; + } + + public void setReadOnly(Boolean readOnly) { + this._readOnly = readOnly; + } + + private Boolean _copyable = null; + + @JsonProperty("CopyAble") + public boolean isCopyAble() { + if (_copyable == null) { + _copyable = DataTableUtil.getBooleanValue(_row, "copy", false); + } + return _copyable; + } + + public void setCopyAble(Boolean copyable) { + this._copyable = copyable; + } + + private Boolean _multiple = null; + + @JsonProperty("Multiple") + public boolean isMultiple() { + if (_multiple == null) { + _multiple = toBoolean(get(_row, "Multiple", "0")); + } + return _multiple; + } + + public void setMultiple(Boolean multiple) { + this._multiple = multiple; + } + + private Boolean _sum = null; + + @JsonIgnore + public boolean isIsSum() { + if (_sum == null) { + _sum = DataTableUtil.getBooleanValue(_row, "sum", false); + } + return _sum; + } + + public void setSum(Boolean sum) { + this._sum = sum; + } + + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getSummaryType() { + if (isIsSum()) { + return "sum"; + } + return null; + } + + + private String _sumText; + + @JsonProperty("SumText") + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getSumText() { + return (String) get(_row, "SumText", _sumText, null); + } + + public void setSumText(String sumText) { + this._sumText = sumText; + } + + private String _sumCond; + + @JsonProperty("SumCond") + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getSumCond() { + String cond = (String) DataTableUtil.getRowVal(_row, "SumCond", _sumCond, null); + if (cond != null && !cond.isEmpty()) { + return PublicUtil.SqlToCode(cond.toLowerCase()); + } + return null; + } + + public void setSumCond(String sumCond) { + this._sumCond = sumCond; + } + + private String _sumCalc; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getSumCalc() { + String calc = (String) DataTableUtil.getRowVal(_row, "sumCalc", _sumCalc, null); + if (calc != null && !calc.isEmpty()) { + return PublicUtil.SqlToCode(calc.toLowerCase()); + } + return null; + } + + public void setSumCalc(String sumCalc) { + this._sumCalc = sumCalc; + } + + private String _dataSource; + + @JsonIgnore + @JsonProperty("DataSource") + public String getDataSource() { + if (_dataSource == null) { + _dataSource = (String) get(_row, "FieldSQL", ""); + } + return _dataSource; + } + + public void setDataSource(String dataSource) { + this._dataSource = dataSource; + } + + private String _valueField; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getValueField() { + if (_valueField != null && !_valueField.isEmpty()) { + return _valueField.toLowerCase(); + } + _valueField = (String) get(_row, "ValueMember", null); + if (_valueField != null && !_valueField.isEmpty()) { + return _valueField.toLowerCase(); + } + return "dm"; + } + + public void setValueField(String valueField) { + this._valueField = valueField; + } + + private String _displayField; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getDisplayField() { + if (_displayField != null && !_displayField.isEmpty()) { + return _displayField; + } + _displayField = (String) get(_row, "DisplayMember", null); + if (_displayField != null && !_displayField.isEmpty()) { + _displayField = _displayField.toLowerCase(); + return _displayField; + } + return "mc"; + } + + public void setDisplayField(String displayField) { + this._displayField = displayField; + } + + private String _calcExpr; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getCalcExpr() { + if (_calcExpr == null) { + _calcExpr = (String) get(_row, "CalcExpress", null); + } + if (_calcExpr != null && !_calcExpr.isEmpty()) { + return PublicUtil.SqlToCode(PublicUtil.ReqSqlPms(null, null, _calcExpr.toLowerCase(), org.example.Enums.SystemTypeEnums.PmType.ignorenull, null)); + } + return _calcExpr; + } + + + public int getCalcOrder() { + return ToInt32(get(_row, "CalcOrderId", 0)); + } + + private String _unionSQL; + + @JsonIgnore + public String getUnionSQL() { + if (_unionSQL == null) { + _unionSQL = (String) DataTableUtil.getRowVal(_row, "UnionSQL", null); + } + return _unionSQL; + } + + public void setUnionSQL(String unionSQL) { + this._unionSQL = unionSQL; + } + + private String _unionFields; + + @JsonProperty("UnionFields") + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getUnionFields() { + if (_unionFields != null && !_unionFields.isEmpty()) { + return _unionFields.toLowerCase(); + } + _unionFields = DataTableUtil.getStringValue(_row, "UnionField", null).toLowerCase(); + return _unionFields; + } + + public void setUnionFields(String unionFields) { + this._unionFields = unionFields; + } + + protected String dataFormat; + + protected String getDataFormat() { + return getFormat(); + } + + + private String _format; + + @JsonProperty + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getFormat() { + if (_format == null) { + _format = DataTableUtil.getStringValue(_row, "dataformat", null); + } + if (_format == null || _format.isEmpty()) { + _format = IPublicUtil.GetFormatByCType(getFieldType()); + } + if (_format != null && _format.indexOf("#,") > -1) { + _format = "###,###,###,###," + _format.substring(_format.indexOf(",") + 1); + } + return _format; + } + + public void setFormat(String format) { + this._format = format; + } + + + private String _addModule; + + @JsonProperty("AddModule") + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getAddModule() { + if (_addModule == null) { + _addModule = (String) get(_row, "addModuleId", null); + } + return _addModule; + } + + public void setAddModule(String addModule) { + this._addModule = addModule; + } + + private Object _enterBind; + + @JsonProperty + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object getEnterBind() { + if (_enterBind == null) { + _enterBind = get(_row, "IsAddControl", null); + } + return _enterBind; + } + + public void setEnterBind(Object enterBind) { + this._enterBind = enterBind; + } + + private String _addModuleSpec; + + @JsonProperty("AddModuleSpec") + public String getAddModuleSpec() { + if (_addModuleSpec == null) { + _addModuleSpec = (String) get(_row, "AddModuleSpec", null); + } + return _addModuleSpec; + } + + public void setAddModuleSpec(String addModuleSpec) { + this._addModuleSpec = addModuleSpec; + } + + private String _bandTitle; + + @JsonProperty("BandTitle") + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getBandTitle() { + if (_bandTitle == null) { + _bandTitle = (String) get(_row, "BandTitle", null); + } + return _bandTitle; + } + + public void setBandTitle(String bandTitle) { + this._bandTitle = bandTitle; + } + + private String _bandField; + + @JsonProperty("BandField") + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getBandField() { + if (_bandField == null) { + _bandField = (String) get(_row, "BandField", null); + } + return _bandField; + } + + public void setBandField(String bandField) { + this._bandField = bandField; + } + + private String _hintText; + + + @JsonProperty("PlaceHolder") + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getPlaceHolder() { + if (_hintText == null) { + _hintText = (String) get(_row, "HintText", null); + } + return _hintText; + } + + public void setPlaceHolder(String hintText) { + this._hintText = hintText; + } + + private String _hintColor; + + @JsonProperty("HolderColor") + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getHolderColor() { + if (_hintColor == null) { + _hintColor = (String) get(_row, "HintColor", null); + } + return _hintColor; + } + + public void setHolderColor(String hintColor) { + this._hintColor = hintColor; + } + + private String _fontColor; + + @JsonProperty("LabelColor") + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getLabelColor() { + if (_fontColor == null) { + _fontColor = (String) get(_row, "FontColor", null); + } + if ("0".equals(_fontColor)) { + return null; + } + return _fontColor; + } + + public void setLabelColor(String fontColor) { + this._fontColor = fontColor; + } + + private int _fontSize; + + @JsonProperty("FontSize") + @JsonInclude(JsonInclude.Include.NON_NULL) + public Integer getFontSize() { + _fontSize = (_fontSize <= 0) ? ToInt32(get(_row, "FontSize", null)) : _fontSize; + return _fontSize; + } + + public void setFontSize(Integer fontSize) { + this._fontSize = ToInt32(fontSize); + } + + private String _fcolor; + + @JsonProperty("FColor") + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getFColor() { + if (_fcolor == null) { + _fcolor = (String) get(_row, "FColor", null); + } + return _fcolor; + } + + + public String _bcolor; + + @JsonProperty + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getBColor() { + if (_bcolor == null) { + _bcolor = (String) get(_row, "BColor", null); + } + return _bcolor; + } + + + public Boolean _bold; + + @JsonProperty("Bold") + @JsonInclude(JsonInclude.Include.NON_NULL) + public Boolean getBold() { + if (_bold == null) { + _bold = toBoolean(DataTableUtil.getRowVal(_row, "bold", null)); + } + return _bold ? _bold : null; + } + + public void setBold(Boolean bold) { + this._bold = bold; + } + + private Boolean _operPurview; + + @JsonIgnore + public boolean getOperPurview() { + if (_operPurview != null) { + return _operPurview; + } + String strpur = (String) DataTableUtil.getRowVal(_row, "OperPurview", ""); + _operPurview = strpur.isEmpty() || strpur.contains(getUser().UserId); + return _operPurview; + } + + public void setOperPurview(Boolean operPurview) { + this._operPurview = operPurview; + } + + + @JsonIgnore + public Integer getPurviewWidth() { + String purviewWidth = Objects.toString(get(_row, "PurviewWidth", null), ""); + if (isNullOrEmpty(purviewWidth)) return -1; + else return ToInt32(purviewWidth); + } + + private Boolean _readPurview; + + @JsonIgnore + public Boolean getReadPurview() { + if (_readPurview != null) { + return _readPurview; + } + String strpur = (String) get(_row, "ReadPurview", ""); + _readPurview = strpur.isEmpty() || strpur.contains(getUser().UserId); + return _readPurview; + } + + public void setReadPurview(Boolean readPurview) { + this._readPurview = readPurview; + } + + private String _whereCond; + + @JsonIgnore + public String getWhereCond() { + if (_whereCond == null) { + _whereCond = (String) get(_row, "WhereCond", null); + } + return _whereCond; + } + + public void setWhereCond(String whereCond) { + this._whereCond = whereCond; + } + + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String[] linknames; + + private Map _popPms; + + @JsonProperty("PopPms") + @JsonInclude(JsonInclude.Include.NON_NULL) + public Map getPopPms() { + boolean linkNamesCheck = false; + if (_popPms != null && linknames != null && linknames.length > 0) { + for (String name : linknames) { + if (_popPms.containsKey(name)) { + linkNamesCheck = true; + break; // 找到一个匹配就跳出循环 + } + } + } + boolean hasPlaceholder = (getUnionSQL() != null && !getUnionSQL().isEmpty() && getUnionSQL().indexOf("{#p") > -1) + || (!getDataSource().isEmpty() && getDataSource().indexOf("{#p") > -1) || + (!getEnableCond().isEmpty() && getEnableCond().indexOf("{#p") > -1) || + (linkNamesCheck); + if (hasPlaceholder) { + return _popPms; + } + return null; + } + + public void setPopPms(Map popPms) { + this._popPms = popPms; + } + + private Object _value; + + public Object getValue() { + String defaultSource = getDefaultsource() == null ? "" : getDefaultsource(); + String defaultVal = getDefaultval() == null ? "" : getDefaultval().toString(); + return defaultSource.startsWith("{#p_") && defaultVal.isEmpty() ? defaultSource : getDefaultval(); + } + + public void setValue(Object value) { + this._value = value; + } + + private String _limitMaxValue; + + @JsonIgnore + public Object getLimitMaxValue() { + Object value = DataTableUtil.getRowVal(_row, "LimitMaxValue", null); + if (_limitMaxValue == null) { + _limitMaxValue = getUtil().GetDefaultValue(value == null ? "" : value.toString(), null, SystemTypeEnums.PmType.sql); + } + return isNullOrEmpty(_limitMaxValue) ? null : _limitMaxValue; + } + + private String _limitMinValue; + + @JsonIgnore + @JsonProperty("LimitMinValue") + public Object getLimitMinValue() { + Object value = DataTableUtil.getRowVal(_row, "LimitMinValue", null); + if (_limitMinValue == null) { + _limitMinValue = getUtil().GetDefaultValue(value == null ? "" : value.toString(), null, SystemTypeEnums.PmType.sql); + } + return isNullOrEmpty(_limitMinValue) ? null : _limitMinValue; + } + + protected String pspec; + + @JsonIgnore + public String getDefaultsource() { + return (String) get(_row, "defaultvalue", null); + } + + private Object _defaultval; + + public Object getDefaultval() { + return _defaultval; + } + + public void setDefaultval(Object defaultval) { + this._defaultval = defaultval; + } + + public void setDefaultVal(Map updrow) { + this.setDefaultval(DataTableUtil.getRowVal(updrow, getName(), "")); + } + + private int parseIntSafe(Object value, int defaultValue) { + if (value == null) { + return defaultValue; + } + + if (value instanceof Integer) { + return (Integer) value; + } + + if (value instanceof Number) { + return ((Number) value).intValue(); + } + + String str = value.toString().trim(); + if (str.isEmpty()) { + return defaultValue; + } + + try { + return Integer.parseInt(str); + } catch (NumberFormatException e) { + return defaultValue; + } + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/FileButton.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/FileButton.java new file mode 100644 index 0000000..fb0a314 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/FileButton.java @@ -0,0 +1,16 @@ +package org.example.Entity.Control.Fields; + +import java.util.Map; + +// 假设这里存在 Field 类 +import org.example.Entity.Control.Fields.Field; + +public class FileButton extends Field { + public String text; + + public FileButton(Map row) { + super(row); + this.setXtype("filebutton"); + } + +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/Hidden.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/Hidden.java new file mode 100644 index 0000000..ef35290 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/Hidden.java @@ -0,0 +1,12 @@ +package org.example.Entity.Control.Fields; + +import java.util.Map; + + +public class Hidden extends Field { + + public Hidden(Map row) { + super(row); + setXtype("hidden"); + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/ImageFiled.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/ImageFiled.java new file mode 100644 index 0000000..08dfd86 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/ImageFiled.java @@ -0,0 +1,24 @@ +package org.example.Entity.Control.Fields; + + +import java.util.Map; +import java.util.Objects; + + +// 假设存在一个工具类用于类型转换 +import org.example.Utils.NativeExtensionUtils; + +public class ImageFiled extends Field { + public boolean uFolder = true; + + public ImageFiled(Map row) { + super(row); + this.defaultXtype = "field.imgupload"; + } + + public int getFNums() { + Object limitMaxValue = getLimitMaxValue(); + String limitMaxValueStr = Objects.toString(limitMaxValue, "1"); + return NativeExtensionUtils.parseInt(limitMaxValueStr); + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/LabelCheckBox.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/LabelCheckBox.java new file mode 100644 index 0000000..4f7aa76 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/LabelCheckBox.java @@ -0,0 +1,68 @@ +package org.example.Entity.Control.Fields; + + +import org.example.Entity.Control.Base.Component; + +import org.example.Utils.NativeExtensionUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class LabelCheckBox extends Field { + private Field mainfield; + public boolean hideLabel = true; + + + public LabelCheckBox(Map row, Field field) { + super(row); + this.defaultXtype = "CheckFieldContainer"; + this.mainfield = field; + } + + @Override + public Object getDefaultval() { + return super.getDefaultval(); + } + + @Override + public void setDefaultval(Object value) { + super.setDefaultval(value); + String valueStr = value != null ? value.toString() : ""; + boolean empty = valueStr.isEmpty(); + ((Checkbox) getItems().get(0)).setValue(!empty); + Field subField = (Field) getItems().get(1); + subField.setDefaultval(valueStr); + subField.disabled = (empty); + } + + @Override + public void setTabIndex(Integer value) { + ((Field) getItems().get(1)).setTabIndex(value); + } + + private List _items; + + public List getItems() { + if (_items == null) { + _items = new ArrayList<>(); + this.mainfield.setTop(0); + this.mainfield.setLeft(18); + if (this.getWidth() != null) { + this.mainfield.setWidth(this.getWidth() - 18); + } + Checkbox cbox = new Checkbox(null); + cbox.setName(this.getName() + "_ckbox"); + cbox.setXtype("labelCheckBox"); + String mainFieldDefaultVal = this.mainfield.getDefaultval() != null ? this.mainfield.getDefaultval().toString() : ""; + String mainFieldValue = this.mainfield.getValue() != null ? this.mainfield.getValue().toString() : ""; + boolean hasValue = !mainFieldDefaultVal.isEmpty() || !mainFieldValue.isEmpty(); + cbox.setValue(hasValue); + cbox.setTop(3); + this.mainfield.disabled = (!NativeExtensionUtils.toBoolean(cbox.getValue(), false)); + _items.add(cbox); + _items.add(this.mainfield); + } + return _items; + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/LabelField.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/LabelField.java new file mode 100644 index 0000000..ce8ad49 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/LabelField.java @@ -0,0 +1,17 @@ +package org.example.Entity.Control.Fields; + + +import java.util.Map; + + +public class LabelField extends Field { + + public LabelField(Map row) { + super(row); + this.setXtype("label"); + } + + public String getText() { + return getFieldLabel(); + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/MapLocationField.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/MapLocationField.java new file mode 100644 index 0000000..e9fd667 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/MapLocationField.java @@ -0,0 +1,115 @@ +package org.example.Entity.Control.Fields; + + +import org.example.Entity.Control.Base.Component; +import org.example.Entity.Control.Com.Button; + +import org.example.Utils.DataTableUtil; +import org.example.Utils.IPublicUtil; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class MapLocationField extends Field { + private String icon; + private String glyph; + + public MapLocationField(Map row) { + super(row); + this.defaultXtype = "maplocationfield"; + } + + public MapLocationField(Map row, IPublicUtil util) { + super(row, util); + this.defaultXtype = "maplocationfield"; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public String getGlyph() { + return glyph; + } + + public void setGlyph(String glyph) { + this.glyph = glyph; + } + + @Override + public void setTabIndex(Integer value) { + if (getItems() != null && !getItems().isEmpty()) { + ((TextField) getItems().get(0)).setTabIndex(value); + } + } + + private List _items; + + public List getItems() { + if (_items == null) { + _items = new ArrayList<>(); + TextField text = new TextField(this._row); + text.setTop(0); + text.setLeft(0); + if (this.getWidth() != null) { + text.setWidth(this.getWidth() - 30); + } + + Button img = new Button(); + img.width = (25); + img.setTop(1); + img.height = (text.getHeight()); + if (text.getWidth() != null) { + img.setLeft(text.getWidth() + 1); + } + img.setHandler("OpenLocationMap"); + + Hidden longitude = new Hidden(null); + longitude.setName(this.getName() + "_longitude"); + + Hidden latitude = new Hidden(null); + latitude.setName(this.getName() + "_latitude"); + + Hidden itude = new Hidden(null); + itude.setName(this.getName() + "_itude"); + + Hidden province = new Hidden(null); + province.setName(this.getName() + "_province"); + + Hidden city = new Hidden(null); + city.setName(this.getName() + "_city"); + + Hidden district = new Hidden(null); + district.setName(this.getName() + "_district"); + + Hidden street = new Hidden(null); + street.setName(this.getName() + "_street"); + + _items.add(text); + _items.add(img); + _items.add(longitude); + _items.add(latitude); + _items.add(itude); + _items.add(province); + _items.add(city); + _items.add(district); + _items.add(street); + } + return _items; + } + + @Override + public void setDefaultVal(Map updrow) { + if (getItems() != null && !getItems().isEmpty()) { + ((TextField) getItems().get(0)).setDefaultval(DataTableUtil.getRowVal(updrow, this.getName(), "")); + ((Hidden) getItems().get(2)).setDefaultval(DataTableUtil.getRowVal(updrow, this.getName() + "_longitude", "")); + ((Hidden) getItems().get(3)).setDefaultval(DataTableUtil.getRowVal(updrow, this.getName() + "_latitude", "")); + ((Hidden) getItems().get(4)).setDefaultval(DataTableUtil.getRowVal(updrow, this.getName() + "_itude", "")); + } + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/NumberField.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/NumberField.java new file mode 100644 index 0000000..8fe6487 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/NumberField.java @@ -0,0 +1,16 @@ +package org.example.Entity.Control.Fields; + + +import org.example.Utils.IPublicUtil; + +import java.util.Map; + +public class NumberField extends TextField { + + public NumberField(Map row) { + super(row); + this.defaultXtype = "numberfield"; + } + +} + diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/TextAreaEditor.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/TextAreaEditor.java new file mode 100644 index 0000000..40b6e9e --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/TextAreaEditor.java @@ -0,0 +1,80 @@ +package org.example.Entity.Control.Fields; + + +import org.example.Entity.Control.Base.Component; +import org.example.Entity.Control.Com.Button; +import org.example.Utils.DataTableUtil; +import org.example.Utils.IPublicUtil; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class TextAreaEditor extends Field { + public String icon; + public String glyph; + + public TextAreaEditor(Map row) { + super(row); + this.setXtype("fields.textareaeditor"); + } + + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public String getGlyph() { + return glyph; + } + + public void setGlyph(String glyph) { + this.glyph = glyph; + } + + @Override + public void setTabIndex(Integer value) { + if (getItems() != null && !getItems().isEmpty()) { + ((TextField) getItems().get(0)).setTabIndex(value); + } + } + + private List _items; + + public List getItems() { + if (_items == null) { + _items = new ArrayList<>(); + TextField text = new TextField(this._row); + text.setTop(0); + text.setLeft(0); + if (this.getWidth() != null) { + text.setWidth(this.getWidth() - 30); + } + + Button img = new Button(); + img.setXtype("button.IconTextButton"); + img.width = (25); + img.setTop(1); + img.height = (text.getHeight()); + if (text.getWidth() != null) { + img.setLeft(text.getWidth() + 1); + } + img.setHandler("OpenEditor"); + + _items.add(text); + _items.add(img); + } + return _items; + } + + @Override + public void setDefaultVal(Map updrow) { + if (getItems() != null && !getItems().isEmpty()) { + ((TextField) getItems().get(0)).setDefaultval(DataTableUtil.getRowVal(updrow, this.getName(), "")); + } + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/TextField.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/TextField.java new file mode 100644 index 0000000..e4c651b --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/TextField.java @@ -0,0 +1,57 @@ +package org.example.Entity.Control.Fields; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import org.example.Utils.DataTableUtil; + +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class TextField extends Field { + private String _emptyText; + private String type; + + public TextField(Map row) { + super(row); + this.defaultXtype = "textfield"; + } + + @Override + public String getXtype() { + if (getName() == null || getName().isEmpty()) { + super.setXtype("displayfield"); + } + return super.getXtype(); + } + + @Override + public void setXtype(String xtype) { + super.setXtype(xtype); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Integer getMaxLength() { + Object m = DataTableUtil.getRowVal(_row, "limitmaxvalue", null); + return m != null && (Integer) m == 0 ? null : (Integer) m; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getEmptyText() { + return _emptyText; + } + + public void setEmptyText(String emptyText) { + this._emptyText = emptyText; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/TextareaField.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/TextareaField.java new file mode 100644 index 0000000..ab040a1 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Fields/TextareaField.java @@ -0,0 +1,30 @@ +package org.example.Entity.Control.Fields; + + +import com.fasterxml.jackson.annotation.JsonInclude; + +import org.example.Utils.IPublicUtil; + +import java.util.Map; + +public class TextareaField extends TextField { + + + public TextareaField(Map row) { + super(row); + this.defaultXtype = "textareafield"; + this._autoHeight = null; + } + + + private Boolean _autoHeight; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Boolean getAutoHeight() { + return _autoHeight; + } + + public void setAutoHeight(Boolean autoHeight) { + this._autoHeight = autoHeight; + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Panel/Chart.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Panel/Chart.java new file mode 100644 index 0000000..00d3044 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Panel/Chart.java @@ -0,0 +1,349 @@ +package org.example.Entity.Control.Panel; + + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.example.Entity.Control.Base.RowComponent; +import org.example.Enums.SystemEnums; +import org.example.Utils.DataTableUtil; +import org.example.Utils.NativeExtensionUtils; + +import java.math.BigDecimal; +import java.util.Map; +import java.util.Objects; + +import static org.example.Utils.NativeExtensionUtils.ToInt32; +import static org.example.Utils.NativeExtensionUtils.toBoolean; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Chart extends RowComponent { + + public Chart(Map row) { + super(row); + } + + @Override + public String getXtype() { + SystemEnums.ChartType chartType; + try { + chartType = SystemEnums.ChartType.fromValue(getCharttype()); + switch (chartType) { + case Line: + case Bar: + return "chart.Bar"; + case Pie: + return "chart.Pie"; + case Funnel: + return "chart.Funnel"; + default: + return "chart.Base"; + } + } catch (Exception e) { + return "chart.Base"; + } + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getSeriestype() { + try { + SystemEnums.ChartType chartType = SystemEnums.ChartType.fromValue(getCharttype()); + if (chartType != null) { + return chartType.name().toLowerCase(); + } + } catch (Exception e) { + return null; + } + return null; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object option; + + public String _charttile; + + public String getCharttitle() { + + return (String) DataTableUtil.getRowVal(this._row, "charttitle", _charttile, null); + } + + public void setCharttitle(String charttitle) { + this._charttile = charttitle; + } + + private Integer _charttype; + + @JsonIgnore + public int getCharttype() { + return ToInt32(DataTableUtil.getRowVal(this._row, "charttype", _charttype, 0)); + } + + public void setCharttype(int charttype) { + this._charttype = charttype; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getChartcolor() { + + return (String) DataTableUtil.getRowVal(this._row, "chartcolor", null); + } + + public void setChartcolor(String chartcolor) { + this._chartcolordf = (chartcolor); + } + + private String _chartcolordf; + + @JsonIgnore + public String getChartcolordf() { + + return (String) DataTableUtil.getRowVal(this._row, "chartcolordf", _chartcolordf, null); + } + + public void setChartcolordf(String chartcolordf) { + this._chartcolordf = chartcolordf; + } + + private String _xlabelfield; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getXlabelfield() { + _xlabelfield = (String) DataTableUtil.getRowVal(this._row, "xlabelfield", _xlabelfield, null); + if (_xlabelfield != null && !_xlabelfield.isEmpty()) { + return _xlabelfield.toLowerCase(); + } + return _xlabelfield; + } + + public void setXlabelfield(String xlabelfield) { + this._xlabelfield = xlabelfield; + } + + private String _yvaluefield; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getYvaluefield() { + _yvaluefield = (String) DataTableUtil.getRowVal(this._row, "yvaluefield", _yvaluefield, null); + if (_yvaluefield != null && !_yvaluefield.isEmpty()) { + _yvaluefield = _yvaluefield.toLowerCase(); + } + return _yvaluefield; + } + + public void setYvaluefield(String yvaluefield) { + this._yvaluefield = yvaluefield; + } + + private String _yvaluefield1; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getYvaluefield1() { + _yvaluefield1 = (String) DataTableUtil.getRowVal(this._row, "yvaluefield1", _yvaluefield1, null); + if (_yvaluefield1 != null && !_yvaluefield1.isEmpty()) { + _yvaluefield1 = _yvaluefield1.toLowerCase(); + } + return _yvaluefield1; + } + + public void setYvaluefield1(String yvaluefield1) { + this._yvaluefield1 = yvaluefield1; + } + + private String _yvaluefield2; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getYvaluefield2() { + _yvaluefield2 = (String) DataTableUtil.getRowVal(this._row, "yvaluefield2", _yvaluefield2, null); + if (_yvaluefield2 != null && !_yvaluefield2.isEmpty()) { + _yvaluefield2 = _yvaluefield2.toLowerCase(); + } + return _yvaluefield2; + } + + public void setYvaluefield2(String yvaluefield2) { + this._yvaluefield2 = yvaluefield2; + } + + + private String _xaxistitle; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getXaxistitle() { + + return (String) DataTableUtil.getRowVal(this._row, "xaxistitle", _xaxistitle, null); + } + + public void setXaxistitle(String xaxistitle) { + this._xaxistitle = xaxistitle; + } + + + private String _yaxistitle; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getYaxistitle() { + + return (String) DataTableUtil.getRowVal(this._row, "yaxistitle", _yaxistitle, null); + } + + public void setYaxistitle(String yaxistitle) { + this._yaxistitle = yaxistitle; + } + + private String _yaxisshared; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getYaxisshared() { + Object value = DataTableUtil.getRowVal(_row, "yaxisshared", _yaxisshared); + return value != null ? value.toString() : null; + } + + public void setYaxisshared(String yaxisshared) { + this._yaxisshared = yaxisshared; + } + + + private Integer _isabsolutely; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Integer getIsabsolutely() { + Object val = DataTableUtil.getRowVal(_row, "isabsolutely", _isabsolutely); + return val != null ? (Integer) val : null; + } + + public void setIsabsolutely(Integer isabsolutely) { + this._isabsolutely = isabsolutely; + } + + private BigDecimal _yscale; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public BigDecimal getYscale() { + Object val = DataTableUtil.getRowVal(_row, "yscale", _yscale); + return val != null ? (BigDecimal) val : null; + } + + public void setYscale(BigDecimal yscale) { + this._yscale = yscale; + } + + private String _valueVisible; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getValueVisible() { + Object value = DataTableUtil.getRowVal(_row, "valuevisible", _valueVisible); + return value != null ? value.toString() : null; + } + + public void setValueVisible(String valueVisible) { + this._valueVisible = valueVisible; + } + + private Integer _xLabelFontSize; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Integer getXLabelFontSize() { + if (_xLabelFontSize == null) { + _xLabelFontSize = ToInt32(DataTableUtil.get(this._row, "xLabelFontSize", 0)); + } + return _xLabelFontSize > 0 ? _xLabelFontSize : null; + } + + public void setXLabelFontSize(Integer xLabelFontSize) { + this._xLabelFontSize = xLabelFontSize; + } + + private Integer _xLabelRotate; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Integer getXLabelRotate() { + if (_xLabelRotate == null) { + _xLabelRotate = ToInt32(DataTableUtil.get(this._row, "xLabelRotate", 0)); + } + return _xLabelRotate > 0 ? _xLabelRotate : null; + } + + public void setXLabelRotate(Integer xLabelRotate) { + this._xLabelRotate = xLabelRotate; + } + + private Boolean _xLablVisibel; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Boolean getXLablVisibel() { + if (_xLablVisibel == null) { + _xLablVisibel = toBoolean(DataTableUtil.get(this._row, "xLablVisibel", 0)); + } + return _xLablVisibel != null && _xLablVisibel ? false : null; + } + + public void setXLablVisibel(Boolean xLablVisibel) { + this._xLablVisibel = xLablVisibel; + } + + private Boolean _legendVisible; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Boolean getLegendVisible() { + if (_legendVisible == null) { + _legendVisible = toBoolean(DataTableUtil.get(this._row, "legendvisible", 0)); + } + return _legendVisible != null && _legendVisible ? false : null; + } + + public void setLegendVisible(Boolean legendVisible) { + this._legendVisible = legendVisible; + } + + private Boolean _xLabelInterval; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Boolean getXLabelInterval() { + if (_xLabelInterval == null) { + _xLabelInterval = toBoolean(DataTableUtil.get(this._row, "labelSpaced", 0)); + } + return _xLabelInterval != null && _xLabelInterval ? _xLabelInterval : null; + } + + public void setXLabelInterval(Boolean xLabelInterval) { + this._xLabelInterval = xLabelInterval; + } + + private Integer _pieRadius; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Integer getPieRadius() { + if (_pieRadius == null) { + _pieRadius = ToInt32(DataTableUtil.get(this._row, "circlehollow", 0)); + } + return _pieRadius > 0 ? _pieRadius : null; + } + + public void setPieRadius(Integer pieRadius) { + this._pieRadius = pieRadius; + } + + private String _pieRoseType; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getPieRoseType() { + if (_pieRoseType == null) { + _pieRoseType = Objects.toString(DataTableUtil.get(this._row, "circlejagge", 0), "").toLowerCase(); + } + if (_pieRoseType != null && !_pieRoseType.isEmpty()) { + switch (_pieRoseType) { + case "radius": + return "radius"; + case "area": + return "area"; + default: + return "radius"; + } + } + return null; + } + + public void setPieRoseType(String pieRoseType) { + this._pieRoseType = pieRoseType; + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Panel/GridPanel.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Panel/GridPanel.java new file mode 100644 index 0000000..356977d --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Panel/GridPanel.java @@ -0,0 +1,209 @@ +package org.example.Entity.Control.Panel; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.example.Entity.Control.Base.Component; +import org.example.Entity.Control.Container.RowColumn; +import org.example.Entity.Control.Data.DataStore; +import org.example.Entity.Control.Fields.ComboBox; + +import java.util.*; +import java.util.stream.Collectors; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class GridPanel extends Panel { + + + public GridPanel() { + super(); + this.setXtype("gridpanel"); + } + + public boolean PageAble; + + + @JsonInclude(JsonInclude.Include.NON_NULL) // 对应Newtonsoft.Json的NullValueHandling.Ignore + private List _columns; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public List getColumns() { + return _columns; + } + + public void setColumns(List value) { + _columns = value; + + if (value != null) { + List bandNames = new ArrayList<>(); + List cols = new ArrayList<>(); + + // 筛选包含bandFields的列并处理 + List f = value.stream() + .filter(col -> col instanceof RowColumn) + .map(col -> (RowColumn) col) + .filter(column -> column.getBandFields() != null && column.getBandFields().length > 0) + .map(column -> { + String[] bfields = column.getBandFields(); + Collections.addAll(bandNames, bfields); + + // 收集关联的列 + List relatedCols = value.stream() + .filter(cl -> cl instanceof RowColumn) + .map(cl -> (RowColumn) cl) + .filter(c -> Arrays.asList(bfields).contains(c.getDataIndex())) + .collect(Collectors.toList()); + cols.addAll(relatedCols); + + return (Component) column; // 转回Component类型 + }) + .collect(Collectors.toList()); + + // 处理下拉框列的联动关系 + List comboboxColumns = value.stream() + .filter(col -> col instanceof RowColumn) + .map(col -> (RowColumn) col) + .filter(column -> column.getEditor() != null && column.getEditor() instanceof ComboBox) + .collect(Collectors.toList()); + + for (RowColumn column : comboboxColumns) { + String target = "{" + column.getDataIndex() + "}"; + String[] linkNames = comboboxColumns.stream() + .filter(_com -> column != _com) // 排除自身 + .filter(_com -> { + ComboBox comboBox = (ComboBox) _com.getEditor(); + String dataSource = comboBox.getDataSource(); + return dataSource != null && + dataSource.toLowerCase().contains(target.toLowerCase()); + }) + .map(_com -> _com.getDataIndex()) + .toArray(String[]::new); + column.linknames = (linkNames); + } + + if (f.size() <= 0) { + return; + } + + // 添加未被bandNames包含的列 + List remainingCols = value.stream() + .filter(col -> col instanceof RowColumn) + .map(col -> (RowColumn) col) + .filter(c -> !bandNames.contains(c.getDataIndex())) + .map(c -> (Component) c) // 转回Component类型 + .collect(Collectors.toList()); + cols.addAll(remainingCols); + + // 如需替换_columns,取消下面注释 + // _columns = cols; + } + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public List CardColumns; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object MobileCards; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object data; + + + private DataStore _store; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public DataStore getStore() { + if (getColumns() != null && !getColumns().isEmpty()) { + if (_store == null) { + _store = new DataStore(); + } + if (_store.fields == null) { + _store.fields = (getColumns().stream() + .filter(col -> col instanceof RowColumn) + .map(col -> ((RowColumn) col).getStoreField()) + .toArray(Object[]::new)); + } + } + return _store; + } + + public void setStore(DataStore store) { + this._store = store; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object RightMenu; + + public int SHeight; + + public String IdField; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String displayField; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object TbarItems; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public List BbarItems; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object DefaultSearchBoxData; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("Calc") + public Object getCalc() { + if (getColumns() != null && !getColumns().isEmpty()) { + Map calcDict = new HashMap<>(); + getColumns().stream() + .filter(col -> col instanceof RowColumn && ((RowColumn) col).getCalcExpr() != null && !((RowColumn) col).getCalcExpr().isEmpty()) + .sorted(Comparator.comparingInt(col -> ((RowColumn) col).getCalcOrder())) + .map(col -> (RowColumn) col) + .forEach(col -> { + if (!calcDict.containsKey(col.getFieldName())) { + calcDict.put(col.getFieldName(), col.getCalcExpr()); + } + }); + return calcDict; + } + return null; + } + + protected Boolean isSum() { + if (getColumns() != null && !getColumns().isEmpty()) { + return getColumns().stream() + .anyMatch(col -> col instanceof RowColumn && ((RowColumn) col).isIsSum()); + } + return false; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object ChartCfg; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Boolean NoGridLine = null; + + private Boolean _hideColumnHeader; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Boolean getHideColumnHeader() { + return _hideColumnHeader != null && _hideColumnHeader ? true : null; + } + + public void setHideColumnHeader(Boolean hideColumnHeader) { + this._hideColumnHeader = hideColumnHeader; + } + + private Boolean _rowNumberer = null; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Boolean getRowNumberer() { + return _rowNumberer != null && !_rowNumberer ? false : null; + } + + public void setRowNumberer(Boolean rowNumberer) { + this._rowNumberer = rowNumberer; + } + + public Integer displayRows; + +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Panel/Panel.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Panel/Panel.java new file mode 100644 index 0000000..87fc706 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Panel/Panel.java @@ -0,0 +1,26 @@ +package org.example.Entity.Control.Panel; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.example.Entity.Control.Base.Component; + +/** + * ============================================================================== + * 功能描述:Panel + * ============================================================================== + */ +public class Panel extends Component { + + public Panel() { + super(); + this.defaultXtype = "panel"; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String title; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("ElseValue") + public Object elseValue; + +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Panel/TreePanel.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Panel/TreePanel.java new file mode 100644 index 0000000..c649c96 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Panel/TreePanel.java @@ -0,0 +1,37 @@ +package org.example.Entity.Control.Panel; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.example.Entity.Control.Data.DataStore; + + +/** + * ============================================================================== + * 功能描述:TreePanel + * ============================================================================== + */ +public class TreePanel extends Panel { + + public TreePanel() { + super(); + this.defaultXtype = "treepanel"; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String displayField; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String valueField; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String parentValueField; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public DataStore store; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object TbarItems; + + +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Validators/Length.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Validators/Length.java new file mode 100644 index 0000000..220c131 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Validators/Length.java @@ -0,0 +1,34 @@ +package org.example.Entity.Control.Validators; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + + +/** + * ============================================================================== + * 功能描述:Base + * ============================================================================== + */ +public class Length extends VBase { + + public Length() { + super(); + this.type = "length"; + } + + public int min; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Integer max; + + public String bothMessage; + + public String emptyMessage; + + public String maxOnlyMessage; + + public String minOnlyMessage; + + +} diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Control/Validators/VBase.java b/WebErp/weberp/src/main/java/org/example/Entity/Control/Validators/VBase.java new file mode 100644 index 0000000..772acff --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Control/Validators/VBase.java @@ -0,0 +1,24 @@ +package org.example.Entity.Control.Validators; + + +import com.fasterxml.jackson.annotation.JsonInclude; + +/** + * ============================================================================== + * 功能描述:VBase + * 创 建 者:zyw + * 创建日期:2016-09-21 16:33:57 + * ============================================================================== + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class VBase { + public VBase() { + } + + public VBase(String type) { + this.type = type; + } + + public String type; + +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/CusException/CusException.java b/WebErp/weberp/src/main/java/org/example/Entity/CusException/CusException.java new file mode 100644 index 0000000..2d31730 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/CusException/CusException.java @@ -0,0 +1,25 @@ +package org.example.Entity.CusException; + +public class CusException extends Exception { + /** + * 是否继续执行 + */ + public boolean Continue; + /** + * 是否记录日志 + */ + public boolean Log; + + public CusException() { + super(); + } + + public CusException(String message) { + super(message); + } + + public CusException(String message, Throwable innerException) { + super(message, innerException); + } + +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/EventArgs.java b/WebErp/weberp/src/main/java/org/example/Entity/EventArgs.java new file mode 100644 index 0000000..ffadee2 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/EventArgs.java @@ -0,0 +1,14 @@ +package org.example.Entity; + + +/** + * 基础事件参数类,对应C#的EventArgs + */ +public class EventArgs { + // 可以根据需要添加通用的事件参数属性和方法 + private static final EventArgs EMPTY = new EventArgs(); + + public static EventArgs empty() { + return EMPTY; + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Entity/Node/TreeNode.java b/WebErp/weberp/src/main/java/org/example/Entity/Node/TreeNode.java new file mode 100644 index 0000000..2f9b85c --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/Node/TreeNode.java @@ -0,0 +1,138 @@ +package org.example.Entity.Node; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import org.example.Utils.DataTableUtil; +import org.example.Utils.DbOperator; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.util.List; +import java.util.Map; + +/** + * ============================================================================== + * 功能描述:TreeNode 树形结构的节点 + * 创 建 者:zyw + * 创建日期:2016-11-24 11:31:26 + * ============================================================================== + */ +public class TreeNode { + private JdbcTemplate jdbcTemplate; + private String countSql; + private String idField; + private String displayField; + private Map row; // 对应DataRow,使用数组或Map存储行数据 + private DbOperator dbOperator; + + public TreeNode() { + } + + public TreeNode(Map row, String idField, String displayField, JdbcTemplate jdbcTemplate, String countSql) { + this.row = row; + this.idField = idField; + this.displayField = displayField; + this.jdbcTemplate = jdbcTemplate; + this.countSql = countSql; + this.dbOperator = new DbOperator(jdbcTemplate); + } + + private String speciesno; + + /** + * 树的主键 + */ + public String getSpeciesno() { + if (speciesno == null || speciesno.isEmpty()) { + speciesno = DataTableUtil.getRowVal(row, new String[]{idField, "speciesno"}, "") + ""; + } + return speciesno; + } + + public void setSpeciesno(String speciesno) { + this.speciesno = speciesno; + } + + private String speciesname; + + /** + * 树的显示名 + */ + public String getSpeciesname() { + if (speciesname == null || speciesname.isEmpty()) { + speciesname = DataTableUtil.getRowVal(row, new String[]{displayField, "speciesname"}, "") + ""; + } + return speciesname; + } + + public void setSpeciesname(String speciesname) { + this.speciesname = speciesname; + } + + @JsonIgnore + public String parentno; + + private boolean checked; + + /** + * 复选框 + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + public Boolean getChecked() { + if (!checkbox) { + return null; + } + return checked; + } + + public void setChecked(Boolean checked) { + this.checked = checked != null ? checked : false; + } + + private boolean checkbox = true; + + @JsonIgnore + public boolean isCheckbox() { + return checkbox; + } + + public void setCheckbox(boolean checkbox) { + this.checkbox = checkbox; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Boolean expanded; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public List children; + + /** + * 叶节点标识 + */ + private Boolean leaf = null; + + public boolean isLeaf() { + if (leaf == null) { + if (countSql != null && !countSql.isEmpty()) { + // 执行计数查询判断是否为叶节点 + String sql = dbOperator.BuildCountSql(countSql); + Integer count = jdbcTemplate.queryForObject(sql, new Object[]{speciesno}, Integer.class); + leaf = count <= 0; + } else { + // 无计数SQL时根据子节点判断 + leaf = !(children != null && !children.isEmpty()); + } + } + return leaf != null ? leaf : false; + } + + public void setLeaf(boolean leaf) { + this.leaf = leaf; + } + + /** + * 其他需要传递给控件的参数 + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object ElseValue; + +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/System/Audit/AuditBase.java b/WebErp/weberp/src/main/java/org/example/Entity/System/Audit/AuditBase.java new file mode 100644 index 0000000..b01b18e --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/System/Audit/AuditBase.java @@ -0,0 +1,68 @@ +package org.example.Entity.System.Audit; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Map; + +import static org.example.Utils.DataTableUtil.get; +import static org.example.Utils.NativeExtensionUtils.ToInt32; +import static org.example.Utils.NativeExtensionUtils.isNullOrEmpty; + +public class AuditBase { + public AuditBase(Map row) { + super(); + this.row = row; + } + + protected Map row; + + public Integer getItemId() { + return ToInt32(get(row, "id", "0")); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("TypeCode") + public String getTypeCode() { + return (String) get(row, "typecode", ""); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("StepName") + public String getStepName() { + return (String) get(row, "stepname", ""); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("StepCode") + public String getStepCode() { + String _stepCode = get(row, "stepcode", "") + ""; + if (isNullOrEmpty(_stepCode)) return null; + return _stepCode; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("StepGroup") + public String getStepGroup() { + return (String) get(row, "StepGroup", ""); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("ViewUser") + public String getViewUser() { + return (String) get(row, "ViewUser", ""); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("OperUser") + public String getOperUser() { + return (String) get(row, "OperUser", ""); + } + + @JsonIgnore + public String getPStepCode() { + return (String) get(row, "PStepCode", ""); + } + +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/System/Audit/AuditStep.java b/WebErp/weberp/src/main/java/org/example/Entity/System/Audit/AuditStep.java new file mode 100644 index 0000000..73df3d2 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/System/Audit/AuditStep.java @@ -0,0 +1,135 @@ +package org.example.Entity.System.Audit; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import java.util.Map; + +import static org.example.Utils.DataTableUtil.get; +import static org.example.Utils.NativeExtensionUtils.toBoolean; + +public class AuditStep extends AuditBase { + + public AuditStep(Map row) { + super(row); + } + + @JsonIgnore + public String getStepAction() { + return (String) get(row, "StepAction", ""); + } + + @JsonIgnore + public String getBackAction() { + return (String) get(row, "backAction", ""); + } + + @JsonIgnore + public String getStepSql() { + return (String) get(row, "stepSql", ""); + } + + @JsonIgnore + public String getBillModifyFields() { + return (String) get(row, "billModifyFields", ""); + } + + @JsonIgnore + public String getRequiredFields() { + return (String) get(row, "requiredFields", ""); + } + + ;//+get + + @JsonIgnore + public String getRequiredDetailFields() { + return (String) get(row, "requiredDetailFields", ""); + } + + ;//+get + + @JsonIgnore + public String getDetailModifyFields() { + return (String) get(row, "detailModifyFields", ""); + } + + ;//+get + + @JsonIgnore + public String getBeforeEvent() { + return (String) get(row, "beforeEvent", ""); + } + + ;//+get + + @JsonIgnore + public String getAfterEvent() { + return (String) get(row, "afterEvent", ""); + } + + ;//+get + + @JsonIgnore + public String getStepApplyText() { + return (String) get(row, "StepApplyText", ""); + } + + ;//+get + + @JsonIgnore + public String getStepBackText() { + return (String) get(row, "stepBackText", ""); + } + + ;//+get + + @JsonIgnore + public String getStepApplyContent() { + return (String) get(row, "stepApplyContent", ""); + } + + ;//+get + + @JsonIgnore + public String getStepBackContent() { + return (String) get(row, "stepBackContent", ""); + } + + + @JsonIgnore + public String getStepCloseText() { + return (String) get(row, "stepCloseText", ""); + } + + + @JsonIgnore + public String getStepCloseContent() { + return (String) get(row, "stepCloseContent", ""); + } + + + @JsonIgnore + public String getStepCloseTip() { + return (String) get(row, "stepCloseTip", ""); + } + + ;//+get + + @JsonIgnore + public String getAuditContent() { + return (String) get(row, "auditContent", ""); + } + + ;//+get + + @JsonIgnore + public Boolean getIsLocked() { + return (Boolean) get(row, "isLocked", ""); + } + + ;//+get + + @JsonIgnore + public boolean getDisJCP() { + return toBoolean(get(row, "disjcp", "")); + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Entity/System/Audit/AuditTypeStep.java b/WebErp/weberp/src/main/java/org/example/Entity/System/Audit/AuditTypeStep.java new file mode 100644 index 0000000..1d42cb2 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/System/Audit/AuditTypeStep.java @@ -0,0 +1,65 @@ +package org.example.Entity.System.Audit; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import java.util.Map; + +import static org.example.Utils.DataTableUtil.get; +import static org.example.Utils.NativeExtensionUtils.ToInt32; + +public class AuditTypeStep extends AuditBase { + public AuditTypeStep(Map row) { + super(row); + } + + @JsonIgnore + public int getBillType() { + return ToInt32(get(row, "id", "0")); + } + + ;//+get + + @JsonIgnore + public String getStepBackCode() { + return (String) get(row, "stepBackCode", ""); + } + + ;//+get + + @JsonIgnore + public String getStepTime() { + return get(row, "StepTime", "") + ""; + } + + ;//+get + + @JsonIgnore + public Boolean getAutoStep() { + return (Boolean) get(row, "AutoStep", false); + } + + ;//+get + + @JsonIgnore + public String getTypeKey() { + return get(row, "typeKey", "") + ""; + } + + ;//+get + + @JsonIgnore + public String getStepOverCond() { + return (String) get(row, "StepOverCond", ""); + } + + ;//+get + + @JsonIgnore + public String getAutoStepCond() { + return (String) get(row, "AutoStepCond", ""); + } + + ;//+get + + +} diff --git a/WebErp/weberp/src/main/java/org/example/Entity/System/BaseDetailModule.java b/WebErp/weberp/src/main/java/org/example/Entity/System/BaseDetailModule.java new file mode 100644 index 0000000..c6393ca --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/System/BaseDetailModule.java @@ -0,0 +1,171 @@ +package org.example.Entity.System; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.springframework.boot.autoconfigure.data.redis.RedisProperties; + +import java.util.Map; + +import static org.example.Utils.DataTableUtil.get; +import static org.example.Utils.DataTableUtil.getStringValue; +import static org.example.Utils.NativeExtensionUtils.ToInt32; +import static org.example.Utils.NativeExtensionUtils.toBoolean; + +public class BaseDetailModule extends ModuleEntity { + public BaseDetailModule() { + super(); + } + + public BaseDetailModule(Map row) { + super(row); + } + + @JsonIgnore + public String getDetailId() { + + return getStringValue(basemodule, "id", null, null); + + } + + private String _unionKey; + + @JsonIgnore + public void setUnionKey(String unionKey) { + this._unionKey = unionKey; + } + + public String getUnionKey() { + return getStringValue(basemodule, "unionkey", _unionKey, null); + + } + + private String _unionMenuCode; + + public String getUnionMenuCode() { + return (String) get(basemodule, "UnionMenuCode", _unionMenuCode, null); + } + + public void setUnionMenuCode(String unionMenuCode) { + + this._unionMenuCode = unionMenuCode; + } + + + private String _unionField; + + public String getUnionField() { + return (getStringValue(basemodule, "UnionField", _unionField, null)).toLowerCase(); + } + + public void setUnionField(String unionField) { + + this._unionField = unionField; + } + + + private String _unionParentField; + + @JsonProperty("UnionParentField") + public String getUnionParentField() { + return (getStringValue(basemodule, "UnionParentField", _unionParentField, null)).toLowerCase(); + } + + public void setUnionParentField(String unionParentField) { + + this._unionParentField = unionParentField; + } + + + private String _unionValue; + + public String getUnionValue() { + return (getStringValue(basemodule, "UnionValue", _unionValue, null)).toLowerCase(); + } + + public void setUnionValue(String unionValue) { + + this._unionValue = unionValue; + } + + + private String _unionSql; + + /// +/// 明细sql +/// + public String getUnionSql() { + return (String) get(basemodule, "UnionSQL", _unionSql, null); + } + + public void setUnionSql(String unionSql) { + + + this._unionSql = unionSql; + } + + + private String _unionCond; + + /// +/// 主表关联明细的条件,有条件就不用关联字段 +/// + public String getUnionCond() { + return (String) get(basemodule, "unionCond", _unionCond, null); + } + + public void setUnionCond(String unionCond) { + + + this._unionCond = unionCond; + } + + + private Integer _unionType; + + public Integer getUnionType() { + return ToInt32(get(basemodule, "detailType", _unionType, null)); + } + + public void setUnionType(Integer unionType) { + + this._unionType = unionType; + } + + + private Boolean _refresh; + + public Boolean getRefresh() { + return toBoolean(get(basemodule, "Refresh", _refresh, null)); + } + + public void setRefresh(Boolean refresh) { + + this._refresh = refresh; + } + + + private Boolean _addFlag; + + public Boolean getAddFlag() { + return toBoolean(get(basemodule, "AddFlag", _addFlag, null)); + } + + public void setAddFlag(Boolean addFlag) { + + this._addFlag = addFlag; + } + + + private Integer _displayrows; + + public Integer getDisPlayRows() { + return ToInt32(get(basemodule, "displayrows", _displayrows, null)); + } + + public void setDisPlayRows(Integer displayrows) { + + this._displayrows = displayrows; + } + + +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/System/BaseModule.java b/WebErp/weberp/src/main/java/org/example/Entity/System/BaseModule.java new file mode 100644 index 0000000..7701243 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/System/BaseModule.java @@ -0,0 +1,982 @@ +package org.example.Entity.System; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.example.Entity.Control.Base.Component; +import org.example.Enums.SystemTypeEnums; +import org.example.Utils.DataTableUtil; +import org.example.Utils.JsEngine; +import org.example.Utils.PublicUtil; + +import java.util.Map; + +import static com.microsoft.sqlserver.jdbc.StringUtils.isEmpty; +import static io.micrometer.common.util.StringUtils.isNotEmpty; +import static org.example.Utils.DataTableUtil.get; +import static org.example.Utils.NativeExtensionUtils.*; + +public class BaseModule extends ModuleBaseEntity { + + public BaseModule(Map basemodule) { + super(basemodule); + this.basemodule = basemodule; + } + + public BaseModule() { + super(); + } + + Map basemodule; + + @JsonProperty("IdField") + @Override + public String getIdField() { + if (getUnionBillCode() == null) { + return super.getIdField(); + } + return "billid"; + } + + @Override + public void setIdField(String idField) { + super.setIdField(idField); + } + + @JsonProperty("IsSpecModule") + public boolean IsSpecModule; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String detailId; + + private String _condKey; + + @JsonIgnore + @JsonProperty("CondKey") + public String getCondKey() { + Object _fk = get(basemodule, "condkey", _condKey); + if (_fk != null && !(_fk + "").isEmpty()) { + _condKey = _fk + ""; + return _condKey; + } + return null; + } + + @JsonProperty("PageAble") + public boolean getPageAble() { + return toBoolean(DataTableUtil.get(basemodule, "pagerflag", true)); + } + + + @JsonProperty("PageSize") + public int getPageSize() { + return ToInt32(DataTableUtil.get(basemodule, "pagesize", true)); + } + + + @JsonProperty("SelectLeaf") + public int getSelectLeaf() { + return ToInt32(get(basemodule, "selectLeaf", true)); + } + + private Boolean _selfEdit; + + @JsonProperty("SelfEdit") + public boolean isSelfEdit() { + if (_selfEdit != null) { + return _selfEdit; + } + + boolean editFlag = toBoolean(get(basemodule, "EditFlag", false)); + _selfEdit = (isSaveAble() && (isUpdateAble() || isAddAble())) && editFlag; + + return _selfEdit; + } + + public void setSelfEdit(boolean value) { + this._selfEdit = value; + } + + @JsonProperty("ModuleType") + public Integer ModuleType = 0; + @JsonInclude(JsonInclude.Include.NON_NULL) + public String Title; + + @JsonProperty("Title") + public String getTitle() { + return getMenuName(); + } + + public void setTitle(String value) { + this.Title = value; + } + + private String _unionBillCode; + + @JsonProperty("UnionBillCode") + public String getUnionBillCode() { + if (_unionBillCode == null) { + Object value = get(basemodule, "UnionBillCode", _unionBillCode); + _unionBillCode = (value != null) ? value.toString() : null; + } + return _unionBillCode; + } + + private String _masterTab; + + @JsonIgnore + @JsonProperty("MasterTable") + @Override + public String getMasterTable() { + return (String) get(basemodule, "TableName", _masterTab); + } + + @Override + public void setMasterTable(String value) { + this._masterTab = value; + } + + private String _masterSql; + + @JsonIgnore + @JsonProperty("MasterSql") + public String getMasterSql() { + return (String) get(basemodule, "TableSQL", _masterSql); + } + + public void setMasterSql(String MasterSql) { + this._masterSql = MasterSql; + } + + private String _apiDataNode; + + @JsonIgnore + @JsonProperty("ApiDataNode") + public String getApiDataNode() { + return (String) get(basemodule, "apiDataNode", _apiDataNode); + } + + public void setApiDataNode(String apiDataNode) { + _apiDataNode = apiDataNode; + } + + private String _apiSuccNode; + + @JsonIgnore + @JsonProperty("ApiSuccNode") + public String getApiSuccNode() { + return (String) get(basemodule, "apiSuccNode", _apiSuccNode); + } + + public void setApiSuccNode(String apiSuccNode) { + this._apiSuccNode = apiSuccNode; + } + + private String _apiSuccVal; + + @JsonIgnore + @JsonProperty("ApiSuccVal") + public String getApiSuccVal() { + return (String) get(basemodule, "apiSuccVal", _apiSuccVal); + } + + public void setApiSuccVal(String value) { + this._apiSuccVal = value; + } + + private Integer _menuType; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("MenuType") + public Integer getMenuType() { + return (Integer) get(basemodule, "MenuType", _menuType); + } + + public void setMenuType(Integer menuType) { + _menuType = menuType; + } + + private Boolean _isReport; + + @JsonProperty("IsReport") + public boolean isIsReport() { + Object value = get(basemodule, "ReportFlag", false); + this._isReport = toBoolean(value); + return _isReport; + } + + public void setIsReport(Boolean IsReport) { + this._isReport = IsReport; + } + + private String _addCond; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("AddCond") + public String getAddCond() { + if (_addCond != null && !_addCond.isEmpty()) { + return _addCond; + } + _addCond = (String) get(basemodule, "AddCond", _addCond); + _addCond = PublicUtil.ReqSqlPmsByRow(null, null, PublicUtil.SqlToCode(_addCond), SystemTypeEnums.PmType.ignorenull); + if (_addCond != null && _addCond.indexOf("{") < 0) { + if (_addCond == null || _addCond.isEmpty()) { + _addCond = "1==1"; + } + } + return _addCond; + } + + public void setAddCond(String value) { + this._addCond = value; + } + + private Boolean _updateAble = null; + + @JsonProperty("UpdateAble") + public boolean isUpdateAble() { + if (toBoolean(!OperAble)) { + return false; + } + + if (_updateAble != null) { + return _updateAble; + } + + if (toBoolean(!isSaveAble())) { + return false; + } + if (isNotEmpty(getUpdateCond())) { + return !isIsReport(); + } else { + if (!getUpdateCond().contains("{")) { + _updateAble = toBoolean(JsEngine.Eval(getUpdateCond())); + } else { + return !isIsReport(); + } + return _updateAble != null && _updateAble && !isIsReport(); + } + } + + private String _updateCond = null; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("UpdateCond") + public String getUpdateCond() { + if (isNotEmpty(_updateCond)) { + return _updateCond; + } + + Object value = get(basemodule, "UpdateCond", _updateCond); + _updateCond = (value != null) ? value.toString() : null; + + _updateCond = PublicUtil.SqlToCode(_updateCond); + _updateCond = PublicUtil.ReqSqlPmsByRow( + null, + null, + _updateCond, + SystemTypeEnums.PmType.ignorenull + ); + + if (_updateCond != null && _updateCond.indexOf("{") < 0) { + if (isEmpty(_updateCond)) { + _updateCond = "1==1"; + } + } + return _updateCond; + } + + public void setUpdateCond(String updateCond) { + this._updateCond = updateCond; + } + + private String _deleteCond; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("DeleteCond") + public String getDeleteCond() { + if (_deleteCond != null && !_deleteCond.isEmpty()) { + return _deleteCond; + } + _deleteCond = (String) get(basemodule, "DeleteCond", _deleteCond); + _deleteCond = PublicUtil.ReqSqlPmsByRow(null, null, PublicUtil.SqlToCode(_deleteCond), + SystemTypeEnums.PmType.ignorenull); + if (_deleteCond == null || !_deleteCond.contains("{")) { + if (_deleteCond == null || _deleteCond.isEmpty()) { + _deleteCond = "1==1"; + } + } + return _deleteCond; + } + + public void setDeleteCond(String value) { + this._deleteCond = value; + } + + private String _applyCond; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("ApplyCond") + public String getApplyCond() { + return (String) get(basemodule, "ApplyCond", _applyCond); + } + + public void setApplyCond(String value) { + this._applyCond = value; + } + + private String _copyCond; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("CopyCond") + public String getCopyCond() { + return (String) get(basemodule, "CopyCond", _copyCond); + } + + public void setCopyCond(String value) { + this._copyCond = value; + } + + private Boolean _addFlag; + + @JsonProperty("AddAble") + public boolean isAddAble() { + if (!OperAble) { + return false; + } + + Object addFlagValue = get(basemodule, "AddFlag"); + if (addFlagValue == null) { + addFlagValue = (_addFlag != null) ? _addFlag : false; + } + boolean addable = toBoolean(addFlagValue); + + if (!addable) { + return false; + } + + if (isNullOrEmpty(getAddCond())) { + return !isIsReport(); + } else if (getAddCond().indexOf("{") < 0) { + Object evalResult = PublicUtil.EvalCond(PublicUtil.SqlToCode(getAddCond())); + addable = toBoolean(evalResult); + } + + return addable && !isIsReport(); + } + + public void setAddAble(Boolean value) { + this._addFlag = value; + } + + @JsonIgnore + private Boolean _saveFlag; + + @JsonProperty("SaveAble") + public boolean isSaveAble() { + return toBoolean(get(basemodule, "SaveFlag", _saveFlag, true)); + } + + public void setSaveAble(Boolean value) { + this._saveFlag = value; + } + + private Boolean _deleteFlag; + + @JsonProperty("DeleteAble") + public boolean getDeleteAble() { + if (!OperAble) return false; + boolean delable = toBoolean(get(basemodule, "DeleteFlag", _deleteFlag)); + if (!delable) { + return false; + } + if (getDeleteCond() == null || getDeleteCond().isEmpty()) { + return !isIsReport(); + } else if (!getDeleteCond().contains("{")) { + delable = toBoolean(PublicUtil.EvalCond(getDeleteCond())); + } + return delable && !isIsReport(); + } + + public void setDeleteAble(Boolean value) { + this._deleteFlag = value; + } + + private Boolean _searchFlag; + + @JsonProperty("SearchAble") + public boolean isSearchAble() { + Object value = get(basemodule, "searchable"); + if (value == null) { + value = (_searchFlag != null) ? _searchFlag : true; + } + return toBoolean(value); + } + + public void setSearchAble(Boolean value) { + this._searchFlag = value; + } + + private Boolean _saveApplyFlag; + + @JsonProperty("SaveApplyAble") + public boolean isSaveApplyAble() { + Object value = get(basemodule, "SaveApplyFlag"); + if (value == null) { + value = (_saveApplyFlag != null) ? _saveApplyFlag : true; + } + return toBoolean(value); + } + + public void setSaveApplyAble(Boolean value) { + this._saveApplyFlag = value; + } + + + private String _saveCaption; + + @JsonProperty("saveText") + public String getSaveText() { + Object value = get(basemodule, "saveCapiton", _saveCaption, null); + return value == null ? null : value.toString(); + } + + public void setSaveCaption(String value) { + this._saveCaption = value; + } + + private String _addCaption; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("addText") + public String getAddText() { + return (String) get(basemodule, "addCaption", _addCaption); + } + + public void setAddText(String value) { + this._addCaption = value; + } + + private String _modifyCaption; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("modifyText") + public String getModifyText() { + return (String) get(basemodule, "modifyCaption", _modifyCaption); + } + + public void setModifyText(String value) { + this._modifyCaption = value; + } + + private String _applyCaption; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("applyText") + public String getApplyText() { + return (String) get(basemodule, "applyCaption", _applyCaption, null); + } + + public void setApplyText(String value) { + this._applyCaption = value; + } + + private String _delCaption; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("delText") + public String getDelText() { + return (String) get(basemodule, "delCaption", _delCaption); + } + + public void setDelText(String value) { + this._delCaption = value; + } + + private String _printFile; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("PrintFile") + public String getPrintFile() { + return (String) get(basemodule, "PrintFile", _printFile); + } + + public void setPrintFile(String value) { + this._printFile = value; + } + + private Integer _printType; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("PrintType") + public Integer getPrintType() { + Object value = get(basemodule, "PrintType", _printType); + return value != null ? ToInt32(value) : null; + } + + public void setPrintType(Integer value) { + this._printType = value; + } + + private String _printSql; + + @JsonIgnore + @JsonProperty("PrintSql") + public String getPrintSql() { + return (String) get(basemodule, "PrintSQL", _printSql); + } + + public void setPrintSql(String value) { + this._printSql = value; + } + + private String _printSql2; + + @JsonIgnore + @JsonProperty("PrintSql2") + public String getPrintSql2() { + return (String) get(basemodule, "PrintSQL2", _printSql2); + } + + public void setPrintSql2(String value) { + this._printSql2 = value; + } + + private String _printSql3; + + + @JsonIgnore + @JsonProperty("PrintSql3") + public String getPrintSql3() { + return (String) get(basemodule, "PrintSQL3", _printSql3); + } + + public void setPrintSql3(String value) { + this._printSql3 = value; + } + + private Integer _leftWidth; + + @JsonInclude(JsonInclude.Include.NON_NULL) + + + @JsonProperty("LeftWidth") + public Integer getLeftWidth() { + Object value = get(basemodule, "LeftWidth", _leftWidth); + return value != null ? ToInt32(value) : null; + } + + public void setLeftWidth(Integer value) { + this._leftWidth = value; + } + + private Integer topHeight; + + @JsonProperty("TopHeight") + @JsonInclude(JsonInclude.Include.NON_NULL) + public Integer getTopHeight() { + Object value = get(basemodule, "TopHeight", topHeight, null); + return value instanceof Integer ? (Integer) value : (value != null ? ToInt32(value) : null); + } + + public void setTopHeight(Integer topHeight) { + this.topHeight = topHeight; + } + + private Integer popupWidth; + + @JsonProperty("PopupWidth") + @JsonInclude(JsonInclude.Include.NON_NULL) + public Integer getPopupWidth() { + Object value = get(basemodule, "pWidth", popupWidth, null); + return value instanceof Integer ? (Integer) value : (value != null ? ToInt32(value) : null); + } + + public void setPopupWidth(Integer popupWidth) { + this.popupWidth = popupWidth; + } + + private Integer popupHeight; + + @JsonProperty("PopupHeight") + @JsonInclude(JsonInclude.Include.NON_NULL) + public Integer getPopupHeight() { + Object value = get(basemodule, "pHeight", popupHeight, null); + return value instanceof Integer ? (Integer) value : (value != null ? ToInt32(value) : null); + } + + public void setPopupHeight(Integer popupHeight) { + this.popupHeight = popupHeight; + } + + private Integer winWidth; + + @JsonProperty("WinWidth") + @JsonInclude(JsonInclude.Include.NON_NULL) + public Integer getWinWidth() { + Object value = get(basemodule, "winWidth", winWidth, null); + return value instanceof Integer ? (Integer) value : (value != null ? ToInt32(value) : null); + } + + public void setWinWidth(Integer winWidth) { + this.winWidth = winWidth; + } + + private Integer winpHeight; + + @JsonProperty("WinHeight") + @JsonInclude(JsonInclude.Include.NON_NULL) + public Integer getWinHeight() { + Object value = get(basemodule, "winHeight", winpHeight, null); + return value instanceof Integer ? (Integer) value : (value != null ? ToInt32(value) : null); + } + + public void setWinHeight(Integer winpHeight) { + this.winpHeight = winpHeight; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Integer OrderId; + + private String _operPurview; + + @JsonIgnore + @JsonProperty("OperPurview") + public String getOperPurview() { + return (String) get(basemodule, "OperPurview", _operPurview); + } + + public void setOperPurview(String value) { + this._operPurview = value; + } + + private String _readPurview; + + @JsonIgnore + @JsonProperty("ReadPurview") + public String getReadPurview() { + return (String) get(basemodule, "ReadPurview", _readPurview); + } + + public void setReadPurview(String value) { + this._readPurview = value; + } + + private String _addModuleId; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("AddModuleId") + public String getAddModuleId() { + return (String) get(basemodule, "addmodid", _addModuleId); + } + + public void setAddModuleId(String value) { + this._addModuleId = value; + } + + private Boolean _closeAfterModify; + + @JsonProperty("UpdClose") + public Boolean getUpdClose() { + _closeAfterModify = (_closeAfterModify == null) + ? toBoolean(get(basemodule, "closeAfterModify", _closeAfterModify)) + : _closeAfterModify; + + return Boolean.TRUE.equals(_closeAfterModify) ? _closeAfterModify : null; + } + + public void setUpdClose(Boolean value) { + this._closeAfterModify = value; + } + + private Boolean _closeAfterAdd; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("AddClose") + public Boolean getAddClose() { + if (_closeAfterAdd == null) { + Object value = get(basemodule, "closeAfterAdd", _closeAfterAdd); + _closeAfterAdd = toBoolean(value); + } + + return Boolean.TRUE.equals(_closeAfterAdd) ? _closeAfterAdd : null; + } + + public void setAddClose(Boolean value) { + this._closeAfterAdd = value; + } + + private Boolean _noDetail; + + @JsonInclude(JsonInclude.Include.NON_NULL) + + @JsonProperty("NoDetail") + public Boolean getNoDetail() { + _noDetail = (_noDetail == null) + ? toBoolean(get(basemodule, "disableDetail", _noDetail)) + : _noDetail; + + return Boolean.TRUE.equals(_noDetail) ? _noDetail : null; + } + + public void setNoDetail(Boolean value) { + this._noDetail = value; + } + + private Integer _scanMode; + + @JsonInclude(JsonInclude.Include.NON_NULL) + + + @JsonProperty("ScanMode") + public Integer getScanMode() { + if (_scanMode == null) { + Object value = get(basemodule, "scanMode", null); + _scanMode = value != null ? ToInt32(value) : 0; + } + return _scanMode == 0 ? null : _scanMode; + } + + public void setScanMode(Integer value) { + this._scanMode = value; + } + + private Integer _scanNoRepet; + + @JsonInclude(JsonInclude.Include.NON_NULL) + + + @JsonProperty("ScanNoRepet") + public Integer getScanNoRepet() { + Object value = get(basemodule, "scanNoRepet", _scanNoRepet); + return value != null ? ToInt32(value) : _scanNoRepet; + } + + public void setScanNoRepet(Integer value) { + this._scanNoRepet = value; + } + + private Integer _scanUnique; + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("ScanUnique") + public Integer getScanUnique() { + Object value = get(basemodule,"ScanUnique", _scanUnique); + return value != null ? ToInt32(value) : _scanUnique; + } + + public void setScanUnique(Integer value) { + this._scanUnique = value; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("ScanChar") + public String getScanChar() { + return (String) get(basemodule, "scanChar", null); + } + + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("ScanFields") + public String getScanFields() { + return (String) get(basemodule, "scanFields", null); + } + + + private Boolean _disMobileTpl; + + @JsonIgnore + @JsonProperty("DisMobileTpl") + public Boolean getDisMobileTpl() { + if (_disMobileTpl == null) { + _disMobileTpl = toBoolean(get(basemodule, "DisMobileCard", false)); + } + return _disMobileTpl; + } + + public void setDisMobileTpl(Boolean value) { + this._disMobileTpl = value; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("UnionField") + public String getUnionField(){ + return DetailModule == null ? null : DetailModule.getUnionField(); + }; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("UnionParentField") + public String getUnionParentField() { + return DetailModule == null ? null : DetailModule.getUnionParentField(); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("LeftUnionField") + public String LeftUnionField; + + @JsonIgnore + @JsonProperty("LeftUnionFieldSql") + public String LeftUnionFieldSql; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("Left") + public Component Left; + + // @JsonIgnore + @JsonProperty("Main") + public Component Main; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("Details") + public Object Details; + + private Integer _bottomHeight; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("BottomHeight") + public Integer getBottomHeight() { + Object value = get(basemodule, "bottomHeight", _bottomHeight); + return value != null ? ToInt32(value) : null; + } + + public void setBottomHeight(Integer value) { + this._bottomHeight = value; + } + + private Integer detailPageAlign; + + @JsonProperty("DetailPageAlign") + @JsonInclude(JsonInclude.Include.NON_NULL) + public Integer getDetailPageAlign() { + Object value = get(basemodule, "detailPageAlign", detailPageAlign, null); + return value instanceof Integer ? (Integer) value : (value != null ? ToInt32(value) : null); + } + + public void setDetailPageAlign(Integer detailPageAlign) { + this.detailPageAlign = detailPageAlign; + } + + private Integer _appAutoSave; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("scanAutoSave") + public Integer getScanAutoSave() { + if (_appAutoSave == null) { + Object value = DataTableUtil.getRowVal(basemodule, "appAutoSave", null); + if (value != null) { + _appAutoSave = ToInt32(value); + } + } + return _appAutoSave; + } + + public void setScanAutoSave(Integer scanAutoSave) { + this._appAutoSave = scanAutoSave; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("MainDataColumns") + public Object MainDataColumns; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("AttCfgs") + public Object AttCfgs; + + @JsonIgnore + @JsonProperty("IsCard") + public boolean IsCard; + + private Boolean _multCheck; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("MultCheck") + public Boolean getMultCheck() { + if (_multCheck != null) return _multCheck; + _multCheck = toBoolean(get(basemodule, "MultCheck", false)); + return !_multCheck ? null : _multCheck; + } + + public void setMultCheck(Boolean value) { + this._multCheck = value; + } + + private Boolean _refreshAll; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("RefreshAll") + public Boolean getRefreshAll() { + if (_refreshAll != null) return _refreshAll; + _refreshAll = toBoolean(get(basemodule, "RefreshAll")); + if (!(boolean) _refreshAll) return null; + return _refreshAll; + } + + public void setRefreshAll(boolean value) { + _refreshAll = value; + } + + @JsonProperty("IsChart") + public boolean IsChart; + + @JsonIgnore + @JsonProperty("MainModifyFields") + public String MainModifyFields; + + @JsonProperty("IsBase") + public boolean IsBase = true; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("ModuleTabs") + public Object ModuleTabs; + + private Boolean _appPrint; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("AppPrint") + public Boolean IsAppPrint() { + _appPrint = _appPrint == null ? toBoolean(get(basemodule,"appPrint", _appPrint,null)) : _appPrint; + if(_appPrint == true){ + return _appPrint; + } + return null; + } + + public void setAppPrint(Boolean value) { + this._appPrint = value; + } + + private Integer _appPrintType; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("appPrintType") + public Integer getappPrintType() { + int t = ToInt32(get(basemodule,"appPrintType", _appPrintType,null)); + if(t>0){ + return t; + } + return null; + } + + public void setappPrintType(Integer value) { + this._appPrintType = value; + } + + private Integer _scanReadOnly; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("scanReadOnly") + public Integer getscanReadOnly() { + int t = ToInt32(get(basemodule,"scanReadOnly", _scanReadOnly,null)); + if(t>0){ + return t; + } + return null; + } + + public void setscanReadOnly(Integer value) { + this._scanReadOnly = value; + } + +// 2026.2.5 新增mrpType + public Integer mrpType; + +} diff --git a/WebErp/weberp/src/main/java/org/example/Entity/System/BillDetailModule.java b/WebErp/weberp/src/main/java/org/example/Entity/System/BillDetailModule.java new file mode 100644 index 0000000..b1af400 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/System/BillDetailModule.java @@ -0,0 +1,25 @@ +package org.example.Entity.System; + + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +//import org.bytedeco.opencv.presets.opencv_core; +import org.example.Utils.DataTableUtil; + +import java.util.Map; + +public class BillDetailModule extends ModuleEntity { + + public BillDetailModule() { + super(); + } + + public BillDetailModule(Map row) { + super(row); + } + + @Override + public String getIdField() { + return "id"; + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Entity/System/BillModule.java b/WebErp/weberp/src/main/java/org/example/Entity/System/BillModule.java new file mode 100644 index 0000000..a89d32a --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/System/BillModule.java @@ -0,0 +1,462 @@ + +package org.example.Entity.System; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import java.util.Map; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.bytedeco.opencv.presets.opencv_core; +import org.example.Entity.Control.Fields.Field; +import org.example.Enums.SystemTypeEnums; +import org.example.Utils.NativeExtensionUtils; +import org.example.Utils.PublicUtil; +import org.example.Utils.SqlAnalyzer; + +import java.util.HashMap; +import java.util.List; +import java.util.Optional; + +import static org.example.Utils.DataTableUtil.*; + +public class BillModule extends ModuleBaseEntity { + @JsonIgnore + public Map _row; + + + @JsonProperty("ModuleType") + public int ModuleType = 1; + + + @JsonIgnore + public int AuditFlag; + + @JsonIgnore + public String DetailGuid; + + @JsonIgnore + public String ComfirmRemark; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("Main") + public List Main; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("Detail") + public Object Detail; + + public BillModule() { + super(); + } + + public BillModule(Map row) { + super(row); + this._row = row; + } + + private String _detailKey; + + @JsonIgnore + @JsonProperty("DetailKey") + public String getDetailKey() { + return (String) get(this._row, "DetailKey", _detailKey); + } + + public void setDetailKey(String detailKey) { + this._detailKey = detailKey; + } + + + private String _redMenuName; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("RedMenuName") + public String getRedMenuName() { + if (_redMenuName == null) { + _redMenuName = (String) get(this._row, "RedMenuName", _redMenuName); + } + return _redMenuName; + } + + public void setRedMenuName(String redMenuName) { + this._redMenuName = redMenuName; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("HasSource") + public Boolean HasSource; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("SourceTab") + public List> SourceTab; + + + private String _billSourceIds; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("BillSourceIds") + public String getBillSourceIds() { +// if (_billSourceIds == null) { +// _billSourceIds = (String) get(this._row, "BillSourceIds", null,null); +// } + _billSourceIds = (_billSourceIds != null) ? + _billSourceIds : (get(_row, "BillSourceIds", null, null) instanceof String) ? + (String) get(_row, "BillSourceIds", null, null) : null; + return _billSourceIds; + } + + public void setBillSourceIds(String billSourceIds) { + this._billSourceIds = billSourceIds; + } + + private String IdField; + + @JsonProperty("IdField") + public String getIdField() { +// return IdField != null ? IdField : getMenuPrefix() + "billdocument_id"; + return Optional.ofNullable(super.getIdField()) + .filter(id -> !id.trim().isEmpty()) // 处理空字符串(如""、" ") + .orElse(getMenuPrefix() + "billdocument_id"); + } + + public void setIdField(String idField) { + IdField = idField; + } + + + private String _masterTab; + + @JsonIgnore + @JsonProperty("MasterTable") + public String getMasterTable() { + return (String) get(_row, "MasterTable", _masterTab); + } + + public void setMasterTable(String masterTable) { + this._masterTab = masterTable; + } + + + private String _masterSql; + + @JsonIgnore + @JsonProperty("MasterSql") + public String getMasterSql() { + _masterSql = (String) get(_row, "MasterSql", _masterSql); + if (_masterSql != null && !_masterSql.isEmpty() && getOtherCond() != null && !getOtherCond().isEmpty()) { + Map con = new HashMap<>(); + con.put("$other", getOtherCond()); + return new SqlAnalyzer(_masterSql).InsertWhere(con, false, false, false); + } + return _masterSql; + } + + public void setMasterSql(String masterSql) { + this._masterSql = masterSql; + } + + private String _detailTable; + + @JsonIgnore + @JsonProperty("DetailTable") + public String getDetailTable() { + return (String) get(_row, "DetailTable", _detailTable); + } + + public void setDetailTable(String detailTable) { + this._detailTable = detailTable; + } + + + private String _detailSql; + + @JsonIgnore + @JsonProperty("DetailSql") + public String getDetailSql() { + String value = getStringValue(_row, "DetailSQL", _detailSql, null); + return (value instanceof String) ? (String) value : null; + } + + public void setDetailSql(String detailSql) { + this._detailSql = detailSql; + } + + private String _scanSql; + + @JsonIgnore + @JsonProperty("ScanSql") + public String getScanSql() { + return (String) get(_row, "ScanSql", _scanSql); + } + + public void setScanSql(String scanSql) { + this._scanSql = scanSql; + } + + + private Integer _leftWidth; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("LeftWidth") + public Integer getLeftWidth() { + return (Integer) get(_row, "LeftWidth", _leftWidth); + } + + public void setLeftWidth(Integer leftWidth) { + this._leftWidth = leftWidth; + } + + + private Integer _topHeight; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("TopHeight") + public Integer getTopHeight() { + return (Integer) get(_row, "TopHeight", _topHeight); + } + + public void setTopHeight(Integer topHeight) { + this._topHeight = topHeight; + } + + + private String _updateCond; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("UpdateCond") + public String getUpdateCond() { + if (!OperAble) { + return "1<>1"; + } + if (_updateCond != null && !_updateCond.isEmpty()) { + return _updateCond; + } + _updateCond = (String) get(_row, "UpdateCond", _updateCond); + _updateCond = PublicUtil.ReqSqlPmsByRow(null, null, PublicUtil.SqlToCode(_updateCond), SystemTypeEnums.PmType.ignorenull); + if (_updateCond != null && _updateCond.indexOf("{") < 0) { + if (_updateCond.isEmpty()) { + _updateCond = "1==1"; + } + } + return _updateCond; + } + + public void setUpdateCond(String updateCond) { + this._updateCond = updateCond; + } + + + private String _otherCond; + + @JsonIgnore + @JsonProperty("OtherCond") + public String getOtherCond() { + return (String) get(_row, "OtherCond", _otherCond); + } + + public void setOtherCond(String otherCond) { + this._otherCond = otherCond; + } + + + private String _printFile; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("PrintFile") + public String getPrintFile() { + return (String) get(_row, "PrintFile", _printFile); + } + + public void setPrintFile(String printFile) { + this._printFile = printFile; + } + + + private Integer _printType; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("PrintType") + public Integer getPrintType() { + return (Integer) get(_row, "PrintType", _printType); + } + + public void setPrintType(Integer printType) { + this._printType = printType; + } + + private String _printSql; + + @JsonIgnore + @JsonProperty("PrintSql") + public String getPrintSql() { + return (String) get(_row, "PrintSQL", _printSql); + } + + public void setPrintSql(String printSql) { + this._printSql = printSql; + } + + + private String _printSql2; + + @JsonIgnore + public String getPrintSql2() { + return (String) get(_row, "PrintSQL2", _printSql2); + } + + public void setPrintSql2(String printSql2) { + this._printSql2 = printSql2; + } + + + private String _printSql3; + + @JsonIgnore + public String getPrintSql3() { + return (String) get(_row, "PrintSQL3", _printSql3); + } + + public void setPrintSql3(String printSql3) { + this._printSql3 = printSql3; + } + + + private Integer _billFlag; + + @JsonProperty("BillFlag") + public Integer getBillFlag() { + if (_billFlag == null) { + _billFlag = (Integer) get(_row, "BillFlag", 0); + } + return _billFlag; + } + + public void setBillFlag(Integer billFlag) { + this._billFlag = billFlag; + } + + + @JsonProperty("RedBill") + public Integer getRedBill() { + return NativeExtensionUtils.parseInt(get(_row, "redbill", 0) + ""); + } + + + private Boolean _emptyDetailFlag; + + @JsonIgnore + public Boolean getEmptyDetailSaveAble() { + return toBoolean(get(_row, "EmptyDetailFlag", _emptyDetailFlag, false)); + } + + public void setEmptyDetailSaveAble(Boolean emptyDetailSaveAble) { + this._emptyDetailFlag = emptyDetailSaveAble; + } + + private String _detailPrefix; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("DetailPrefix") + public String getDetailPrefix() { + String prefix = (String) get(_row, "detailprefix", _detailPrefix); + if (prefix != null && !prefix.isEmpty()) { + _detailPrefix = prefix.toLowerCase(); + } + return _detailPrefix; + } + + public void setDetailPrefix(String detailPrefix) { + this._detailPrefix = detailPrefix; + } + + private String _billSeq; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("BillSeq") + public String getBillSeq() { + return (String) get(_row, "billseq", _billSeq); + } + + public void setBillSeq(String billSeq) { + this._billSeq = billSeq; + } + + private String _typeCode; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("TypeCode") + public String getTypeCode() { + return (String) get(_row, "MenuCode", _typeCode); + } + + public void setTypeCode(String typeCode) { + this._typeCode = typeCode; + } + + + private Boolean _emptyRow; + + @JsonProperty("EmptyRow") + public Boolean getEmptyRow() { + if (_emptyRow == null) { + _emptyRow = toBoolean(get(_row, "addEmptyRow", _emptyRow, false)); + } + return _emptyRow; + } + + public void setEmptyRow(Boolean emptyRow) { + this._emptyRow = emptyRow; + } + + + @JsonProperty("CopyAble") + public Boolean getCopyAble() { + return getAddCopyAble(); + } + + private String _printCond; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("PrintCond") + public String getPrintCond() { + return (String) get(_row, "PrintCond", _printCond); + } + + public void setPrintCond(String printCond) { + this._printCond = printCond; + } + + @JsonIgnore + public int Rtagid; + + public Integer _comfirmFlag; + + @JsonIgnore + public Integer getComfirmFlag() { + if (_comfirmFlag == null) { + _comfirmFlag = (Integer) get(_row, "ComfirmFlag", null); + } + return _comfirmFlag; + } + + public void setComfirmFlag(Integer comfirmFlag) { + this._comfirmFlag = comfirmFlag; + } + + private String _popupUnionCode; + + @JsonProperty("PopupUnionCode") + public String getPopupUnionCode() { + return (String) get(_row, "PopupUnionCode", _popupUnionCode, null); + } + + public void setPopupUnionCode(String popupUnionCode) { + this._popupUnionCode = popupUnionCode; + } + + @JsonProperty("PopupFields") + public List> PopupFields; + +} diff --git a/WebErp/weberp/src/main/java/org/example/Entity/System/BillReferEntity.java b/WebErp/weberp/src/main/java/org/example/Entity/System/BillReferEntity.java new file mode 100644 index 0000000..0c751db --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/System/BillReferEntity.java @@ -0,0 +1,71 @@ +package org.example.Entity.System; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; + +public class BillReferEntity { + + //override + public String Fromkey; + + @JsonIgnore + public String DataSql; + + + private String _mainkey; + @JsonIgnore + public String Mainkey;//++ + private String _detailKey; + @JsonIgnore + public String DetailKey;//++ + private String _unionKey; + @JsonIgnore + public String UnionKey;//++ + private String _condKey; + @JsonIgnore + public String CondKey;//++ + private String _sourceName; + @JsonInclude(JsonInclude.Include.NON_NULL) + public String Text;//++ + private Integer _sourceType; + public Integer SourceType;//++ + private Integer _orderId; + public Integer OrderId;//++ + private String _sourceKeyField; + @JsonInclude(JsonInclude.Include.NON_NULL) + public String valueField;//++ + private String _sourceCaptionField; + @JsonInclude(JsonInclude.Include.NON_NULL) + public String displayField;//++ + private String _sourceSql; + @JsonIgnore + public String SourceSql;//++ + private String _sourceDetailSql; + @JsonIgnore + public String DetailSql;//++ + private String _unionSql; + @JsonIgnore + public String UnionSql;//++ + private String _unionCaption; + @JsonInclude(JsonInclude.Include.NON_NULL) + public String UnionCaption;//++ + private String _detailCond; + @JsonInclude(JsonInclude.Include.NON_NULL) + public String DetailCond;//++ + private String _detailCondTipMsg; + @JsonInclude(JsonInclude.Include.NON_NULL) + public String DetailCondTipMsg;//++ + private String _quickCaption; + @JsonInclude(JsonInclude.Include.NON_NULL) + public String QuickCaption;//++ + private String _quickSql; + @JsonIgnore + public String QuickSql;//++ + private String _quickTipMsg; + @JsonInclude(JsonInclude.Include.NON_NULL) + public String QuickTipMsg;//++ + private String _detailCondField; + @JsonInclude(JsonInclude.Include.NON_NULL) + public String DetailCondField;//++ + +} diff --git a/WebErp/weberp/src/main/java/org/example/Entity/System/BillSourceModule.java b/WebErp/weberp/src/main/java/org/example/Entity/System/BillSourceModule.java new file mode 100644 index 0000000..a8f28ab --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/System/BillSourceModule.java @@ -0,0 +1,219 @@ +package org.example.Entity.System; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.example.Entity.Control.Base.Component; +import org.example.Enums.SystemTypeEnums; +import org.example.Utils.PublicUtil; + +import java.util.Map; + +import static org.example.Utils.DataTableUtil.get; +import static org.example.Utils.NativeExtensionUtils.ToInt32; + +public class BillSourceModule { + protected Map _row; + + public BillSourceModule() { + } + + public BillSourceModule(Map row) { + this._row = row; + } + + private Integer _sourceId; + + @JsonProperty("SourceId") + public int getSourceId() { + return ToInt32(get(_row, "id", _sourceId, null)); + } + + public void setSourceId(int sourceId) { + this._sourceId = sourceId; + } + + private String moduleId; + + @JsonProperty("ModuleId") + public String getModuleId() { + return moduleId; + } + + public void setModuleId(String moduleId) { + this.moduleId = moduleId; + } + + private String _idField; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("IdField") + public String getIdField() { + _idField = (String) get(_row, "sourcekeyfield", _idField, null); + if (_idField != null && !_idField.isEmpty()) { + return _idField.toLowerCase(); + } + return _idField; + } + + public void setIdField(String idField) { + this._idField = idField; + } + + private String _displayField; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("DisplayField") + public String getDisplayField() { + _displayField = (String) get(_row, "sourceresult", _displayField, null); + if (_displayField != null && !_displayField.isEmpty()) { + return _displayField.toLowerCase(); + } + return _displayField; + } + + public void setDisplayField(String displayField) { + this._displayField = displayField; + } + + private String _title; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("Title") + public String getTitle() { + return (String) get(_row, "username", _title, null); + } + + public void setTitle(String title) { + this._title = title; + } + + private int _sourceType; + + @JsonProperty("SourceType") + public int getSourceType() { + return ToInt32(get(_row, "sourcetype", 0)); + } + + public void setSourceType(int sourceType) { + this._sourceType = sourceType; + } + + private String _masterSql; + + @JsonIgnore + @JsonProperty("MasterSql") + public String getMasterSql() { + return (String) get(_row, "sourcesql", _masterSql, null); + } + + public void setMasterSql(String masterSql) { + this._masterSql = masterSql; + } + + private String _detailSql; + + @JsonIgnore + @JsonProperty("DetailSql") + public String getDetailSql() { + return (String) get(_row, "detailsql", _detailSql, null); + } + + public void setDetailSql(String detailSql) { + this._detailSql = detailSql; + } + + private String _detailEnableCond; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("DetailEnableCond") + public String getDetailEnableCond() { + if (_detailEnableCond == null) { + _detailEnableCond = (String) get(_row, "DetailEnableCond", _detailEnableCond, null); + } + _detailEnableCond = PublicUtil.ReqSqlPmsByRow( + null, + null, + PublicUtil.SqlToCode(_detailEnableCond), + SystemTypeEnums.PmType.ignorenull + ); + return _detailEnableCond; + } + + public void setDetailEnableCond(String detailEnableCond) { + this._detailEnableCond = detailEnableCond; + } + + private String _detailEnableMsg; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("DetailEnableMsg") + public String getDetailEnableMsg() { + return (String) get(_row, "DetailEnableMsg", _detailEnableMsg, null); + } + + public void setDetailEnableMsg(String detailEnableMsg) { + this._detailEnableMsg = detailEnableMsg; + } + + private String _fromkey; + + @JsonIgnore + @JsonProperty("Fromkey") + public String getFromkey() { + Object fk = get(_row, "fromkey", _fromkey, null); + if (fk != null && !String.valueOf(fk).isEmpty()) { + _fromkey = String.valueOf(fk); + return _fromkey; + } + return null; + } + + public void setFromkey(String fromkey) { + this._fromkey = fromkey; + } + + private Object tbarItems; + + @JsonProperty("TbarItems") + public Object getTbarItems() { + return tbarItems; + } + + public void setTbarItems(Object tbarItems) { + this.tbarItems = tbarItems; + } + + /** + * 基础档案右边结构 + */ + + private Component main; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("Main") + public Component getMain() { + return main; + } + + public void setMain(Component main) { + this.main = main; + } + + /** + * 明细 + */ + + private Object details; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("Details") + public Object getDetails() { + return details; + } + + public void setDetails(Object details) { + this.details = details; + } +} + diff --git a/WebErp/weberp/src/main/java/org/example/Entity/System/BillStateEn.java b/WebErp/weberp/src/main/java/org/example/Entity/System/BillStateEn.java new file mode 100644 index 0000000..672ee56 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/System/BillStateEn.java @@ -0,0 +1,49 @@ +package org.example.Entity.System; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +public class BillStateEn { + public String billid; + public Integer billstate; + public Integer StepId; + @JsonIgnore + public Integer BillType; + @JsonIgnore + public String StepCode; + @JsonIgnore + public String NextStepCode; + @JsonIgnore + public String BackStepCode; + @JsonIgnore + public Boolean ViewAble; + @JsonIgnore + public Boolean OperAble; + public String Direction; + public String Remark; + public Boolean IsBack; + public Boolean Canceled; + public Boolean Finished; + public Boolean StepClosed; + private int _selectConfirmFlag = 0; + + /// + /// @selectConfirmFlag默认0,用于确认步骤选择(第一次审核传入0,传1代表用户选择后确认); + /// + public int getSelectConfirmFlag() { + return _selectConfirmFlag; + } + + public void setSelectConfirmFlag(int value) { + _selectConfirmFlag = value; + } + + public String nextSelectStepCode; + /// + /// @nextSelectStepOper,需要选择的人员列表,含多个步骤的人员列表,如果@selectConfirmFlag=0作为输出参数使用,代表需要选择的人员,为1则代表用户选择后的人员列表,作为输入参数使用,多步骤之间的人员以分号分隔; + /// + public String nextSelectStepOper; + + public String comfirmOpers; + public int comfirmFlag; +} + diff --git a/WebErp/weberp/src/main/java/org/example/Entity/System/FieldModel.java b/WebErp/weberp/src/main/java/org/example/Entity/System/FieldModel.java new file mode 100644 index 0000000..decc565 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/System/FieldModel.java @@ -0,0 +1,304 @@ +package org.example.Entity.System; + +import org.example.Utils.NativeExtensionUtils; +import org.example.Utils.PublicUtil; +import org.example.Utils.WebConfigUtil_web; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.jdbc.core.SqlParameter; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.sql.*; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.Date; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static org.example.Utils.NativeExtensionUtils.dmToBoolean; +import static org.example.Utils.NativeExtensionUtils.toBoolean; + +public class FieldModel { + private NamedParameterJdbcTemplate jdbcTemplate; + + // 数据库操作器(通过构造注入) + public void setJdbcTemplate(NamedParameterJdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + public boolean IsNullAble; + public Class ctype; + public int dbType; + + public String databaseType; + + public String getDatabaseType(){ + return databaseType = WebConfigUtil_web.get("custom.database.type"); + } + + // 获取数据库参数对象 + public SqlParameter getPm() { + if (pmFiledValue == null) { + return new SqlParameter("@" + FiledName, Types.VARCHAR); + } + // 根据实际类型设置SQL类型 + int sqlType = getSqlTypeByClass(ctype); + return new SqlParameter("@" + FiledName, sqlType); + } + + public String getPmName() { + return String.format("@%s", FiledName); + } + + public String FiledName; + public String FiledLabel; + public boolean hasSpecVal = false; + private Object _filedvalue; + + private static final String[] KEY_WORDS = {"'", "<"}; + private static final Pattern REGEX_SQL_KEYWORD = Pattern.compile( + "\\b(SELECT|UPDATE|DELETE|INSERT|FROM|WHERE|AND|OR|NOT|IN|LIKE|AS|CREATE|ALTER|DROP|TRUNCATE|CASCADE|COALESCE|IF|EXISTS|GRANT|REVOKE|LOCK|UNION|ALL|ANY|SOME|INTERSECT|EXCEPT)\\b", + Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE + ); + + private static boolean hasKeyWords(String input) { + if (input == null) return false; + for (String word : KEY_WORDS) { + if (input.contains(word)) return true; + } + return REGEX_SQL_KEYWORD.matcher(input).find() || input.startsWith("data:"); + } + + public boolean TrimSpec; + public int FieldLength; + + public boolean validLength(int[] valueLen) { + valueLen[0] = 0; + if (ctype == String.class) { + String value = (getFiledValue() + "").trim(); // 移除trim()方法的参数 + // 如果需要根据trimSpec处理不同的修剪逻辑,可以分情况处理 + if (TrimSpec) { + value = value.replace("'", ""); // 移除单引号 + } else { + value = value.trim(); // 修剪空格 + } + valueLen[0] = getStringLen(value); + return valueLen[0] <= FieldLength; + } + return true; + } + + public String ValidMsg; + + public static int getStringLen(String text) { + if (text == null) return 0; + byte[] bytes = text.getBytes(StandardCharsets.US_ASCII); + int length = 0; + for (byte b : bytes) { + // ASCII码63对应'?',表示非ASCII字符 + length += (b == 63) ? 2 : 1; + } + return length; + } + + public Object SqlFieldVal; + public Object pmFiledValue; + + // 设置字段值(核心逻辑) + public void setFiledValue(Object value) { + _filedvalue = value; + +// System.out.println("字段名:" + FiledName + ",ctype类型:" + (ctype != null ? ctype.getName() : "null")); + + // 检查是否为空 + if (isStringNullOrEmpty(objectToString(_filedvalue))) { + _filedvalue = PublicUtil.GetSqlDefaultValByType(ctype, IsNullAble); + pmFiledValue = _filedvalue; + SqlFieldVal = _filedvalue; + return; + } + + // 处理big int类型的特殊情况 + String filedStr = objectToString(_filedvalue); + if (filedStr.startsWith("0E-")) { + _filedvalue = 0; + pmFiledValue = _filedvalue; + SqlFieldVal = _filedvalue; + return; + } + + // 处理列表类型 + if (_filedvalue instanceof List) { + _filedvalue = sJoin((List) _filedvalue); + } + + // 处理字符串修剪 + String tempValue = objectToString(_filedvalue).trim(); + _filedvalue = tempValue; + + if (TrimSpec && tempValue.startsWith("'") && tempValue.endsWith("'")) { + _filedvalue = tempValue.substring(1, tempValue.length() - 1); + } + + if (!isStringNullOrEmpty(objectToString(_filedvalue))) { + // 处理数值类型 + if (ctype == int.class || ctype == Integer.class || + ctype == float.class || ctype == Float.class || + ctype == double.class || ctype == Double.class || + ctype == BigDecimal.class || + ctype == long.class || ctype == Long.class) + { + String valueStr = objectToString(_filedvalue).trim(); + if (valueStr.indexOf('E') > -1 || valueStr.indexOf('e') > -1) { + Pattern pattern = Pattern.compile("[^\\d\\.\\-Ee]"); + Matcher matcher = pattern.matcher(valueStr); + if (!matcher.find()) { + try { + _filedvalue = new BigDecimal(valueStr); + } catch (NumberFormatException e) { + handleInvalidNumberFormat(valueStr); + } + } else { + handleInvalidNumberFormat(valueStr); + } + } else { + Pattern pattern = Pattern.compile("[^\\d\\.\\-\\,]"); + Matcher matcher = pattern.matcher(valueStr); + if (matcher.find() || "-".equals(valueStr) || ".".equals(valueStr)) { + if ("true".equalsIgnoreCase(valueStr) || "false".equalsIgnoreCase(valueStr)) { + _filedvalue = "true".equalsIgnoreCase(valueStr) ? 1 : 0; + } else { + ValidMsg = String.format("字段%s输入字符串类型不正确,输入值为%s,应该为%s类型!", + FiledName, _filedvalue, ctype.getName()); + _filedvalue = 0; + } + } else { + try { + _filedvalue = new BigDecimal(valueStr.replace(",", "")); + } catch (NumberFormatException e) { + handleInvalidNumberFormat(valueStr); + } + } + } + } + // 处理日期类型 + else if (ctype == Date.class && !(value instanceof Date)) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + try { + Date dvalue = sdf.parse(objectToString(value)); + _filedvalue = sdf.format(dvalue); + } catch (ParseException e) { + ValidMsg = String.format("字段%s输入时间类型值格式不正确,输入值为%s,应该为标准时间类型!", + FiledName, _filedvalue); + } + } + // 处理布尔类型 + else if (ctype == boolean.class || ctype == Boolean.class) { + if (getDatabaseType().equals("dm")) _filedvalue = dmToBoolean(Objects.toString(_filedvalue)); + else _filedvalue = toBoolean(_filedvalue); + } + // 处理字符串类型 + else if (ctype == String.class) { + String valueStr = objectToString(_filedvalue); + int vLen = valueStr.length(); + + if (hasKeyWords(valueStr)) { + Pattern pattern = Pattern.compile("\""); + Matcher matcher = pattern.matcher(valueStr); + vLen += matcher.groupCount(); + } + if (FieldLength > 0 && vLen > FieldLength) { + int subLength = Math.min(FieldLength, valueStr.length()); + _filedvalue = valueStr.substring(0, subLength); + ValidMsg = String.format("字段%s输入字符串长度过长,应该在%s个字符范围内!现有长度%s!", + FiledName, FieldLength, vLen); + } + } + + pmFiledValue = _filedvalue; + SqlFieldVal = _filedvalue; +// if (hasKeyWords(objectToString(_filedvalue))) { +// hasSpecVal = true; +// _filedvalue = ""; +// } + +// SqlFieldVal = _filedvalue; + + // 处理数据库类型 + if (dbType == 231) { // nvarchar + SqlFieldVal = String.format("N'%s'", escapeSql(objectToString(SqlFieldVal))); + } else { + SqlFieldVal = String.format("'%s'", escapeSql(objectToString(SqlFieldVal))); + } + } else { + _filedvalue = PublicUtil.GetSqlDefaultValByType(ctype, IsNullAble); + pmFiledValue = _filedvalue; + SqlFieldVal = _filedvalue; + } + } + + // 辅助方法:处理无效的数字格式 + private void handleInvalidNumberFormat(String valueStr) { + if ("true".equalsIgnoreCase(valueStr) || "false".equalsIgnoreCase(valueStr)) { + _filedvalue = "true".equalsIgnoreCase(valueStr) ? 1 : 0; + } else { + ValidMsg = String.format("字段%s输入字符串类型不正确,输入值为%s,应该为%s类型!", + FiledName, _filedvalue, ctype.getName()); + _filedvalue = 0; + } + } + + // 辅助方法:SQL转义 + private String escapeSql(String str) { + if (str == null) { + return ""; + } + return str.replace("'", "''"); + } + + // 辅助方法:将对象转换为字符串 + private String objectToString(Object obj) { + if (obj == null) { + return ""; + } + return obj.toString(); + } + + // 辅助方法:检查字符串是否为空 + private boolean isStringNullOrEmpty(String str) { + return str == null || str.trim().isEmpty(); + } + + // 辅助方法:List转换为字符串 + private String sJoin(List list) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < list.size(); i++) { + if (i > 0) { + sb.append(","); + } + sb.append(objectToString(list.get(i))); + } + return sb.toString(); + } + + public Object getFiledValue() { + return _filedvalue; + } + + // 辅助方法:根据Java类型获取SQL类型 + private int getSqlTypeByClass(Class type) { + if (type == Integer.class) return Types.INTEGER; + if (type == Long.class) return Types.BIGINT; + if (type == String.class) return Types.VARCHAR; + if (type == Date.class) return Types.DATE; + if (type == Timestamp.class) return Types.TIMESTAMP; + if (type == Boolean.class) return Types.BOOLEAN; + if (type == BigDecimal.class) return Types.DECIMAL; + return Types.VARCHAR; + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/System/FlowStepInfo.java b/WebErp/weberp/src/main/java/org/example/Entity/System/FlowStepInfo.java new file mode 100644 index 0000000..3adc9f2 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/System/FlowStepInfo.java @@ -0,0 +1,291 @@ +package org.example.Entity.System; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.example.Utils.DataTableUtil; +import org.example.Utils.DateTimeUtil; +import org.example.Utils.NativeExtensionUtils; + +import java.util.*; + +public class FlowStepInfo extends RowBase { + protected Map[] _his; + + public FlowStepInfo(Map row, Map[] his) { + super(row); + this._his = his; + } + + public int getId() { + return NativeExtensionUtils.ToInt32(DataTableUtil.get(row, "id", 0)); + } + + @JsonProperty("StepName") + public String getStepName() { + String value = DataTableUtil.get(row, "stepName", "") + ""; + return value.trim().replace("\n", ""); + } + + @JsonProperty("stepName") + public String getStepNameWithHisInfo() { + if (getHisRec() != null) { + Map hisRec = getHisRec(); + Date happenTime = NativeExtensionUtils.toDateTime(hisRec.get("happentime")); + Date operTime = NativeExtensionUtils.toDateTime(hisRec.get("opertime")); + String difDate = DateTimeUtil.getDateDiff(happenTime, operTime); + String hisInfo = "(" + hisRec.get("operatorname") + ")→" + + (NativeExtensionUtils.isNullOrEmpty(difDate) ? "秒速通过" : difDate) + "\n" + + (hisRec.get("opertime") + "").replace(" ", "\t"); + return getStepName() + "\n" + hisInfo; + } + return getStepName(); + } + + @JsonProperty("stepCode") + public int getStepCode() { + return NativeExtensionUtils.ToInt32(DataTableUtil.get(row, "stepcode", 0)); + } + + private Integer _stepOver; + + @JsonProperty("stepOver") + public int getStepOver() { + if (_stepOver == null) { + _stepOver = NativeExtensionUtils.ToInt32(DataTableUtil.get(row, "stepover", 0)); + } + return _stepOver; + } + + public void setStepOver(int value) { + _stepOver = value; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("operUser") + public String getOperUser() { + String user = DataTableUtil.get(row, "operUser", "") + ""; + if (!NativeExtensionUtils.isNullOrEmpty(user)) { + // 使用String.replaceAll()替代trim(','),移除首尾的逗号 + return user.replaceAll("^,+,", "").replaceAll(",+$", ""); + } + return null; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("viewUser") + public String getViewUser() { + return DataTableUtil.get(row, "viewUser", "") + ""; + } + + private Boolean _mulitAudit; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("multiAudit") + public Boolean getMultiAudit() { + if (_mulitAudit == null) { + _mulitAudit = NativeExtensionUtils.toBoolean(DataTableUtil.get(row, "multiAudit", false)); + } + return _mulitAudit ? _mulitAudit : null; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("stepTime") + public Object getStepTime() { + return DataTableUtil.get(row, "stepTime", null); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("AffirmBak") + public Object getAffirmBak() { + return DataTableUtil.get(row, "AffirmBak", null); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("flowcharTagId") + public Object getFlowcharTagId() { + return DataTableUtil.get(row, "flowcharTagId", null); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("flowcharWidth") + public Object getFlowcharWidth() { + return DataTableUtil.get(row, "flowcharWidth", null); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("flowcharHeght") + public Object getFlowcharHeght() { + return DataTableUtil.get(row, "flowcharHeght", null); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("FlowCharfillcolor") + public Object getFlowCharfillcolor() { + return DataTableUtil.get(row, "FlowCharfillcolor", null); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("LeftFlowCharfillcolor") + public Object getLeftFlowCharfillcolor() { + return DataTableUtil.get(row, "LeftFlowCharfillcolor", null); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("LeftFlowCharWidth") + public Object getLeftFlowCharWidth() { + return DataTableUtil.get(row, "LeftFlowCharWidth", null); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("fontSize") + public Object getFontSize() { + return DataTableUtil.get(row, "fontSize", null); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("fontColor") + public Object getFontColor() { + return DataTableUtil.get(row, "fontColor", null); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("LeftFlowCharTagId") + public Object getLeftFlowCharTagId() { + return DataTableUtil.get(row, "LeftFlowCharTagId", null); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("fontWeight") + public Object getFontWeight() { + return DataTableUtil.get(row, "fontWeight", null); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("fontFamily") + public Object getFontFamily() { + return DataTableUtil.get(row, "fontFamily", null); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("LeftFontFamily") + public Object getLeftFontFamily() { + return DataTableUtil.get(row, "LeftFontFamily", null); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("LeftFontSize") + public Object getLeftFontSize() { + return DataTableUtil.get(row, "LeftFontSize", null); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("LeftFontColor") + public Object getLeftFontColor() { + return DataTableUtil.get(row, "LeftFontColor", null); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("LeftFontWeight") + public Object getLeftFontWeight() { + return DataTableUtil.get(row, "LeftFontWeight", null); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("LeftFlowCharHeight") + public Object getLeftFlowCharHeight() { + return DataTableUtil.get(row, "LeftFlowCharHeight", null); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("FlowCharScale") + public Object getFlowCharScale() { + return DataTableUtil.get(row, "FlowCharScale", null); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("flowcharLineHeght") + public Object getFlowcharLineHeght() { + return DataTableUtil.get(row, "flowcharLineHeght", null); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("HisRec") + public Map getHisRec() { + if (_his == null || _his.length == 0) { + return null; + } + + Map hisTab = _his[_his.length - 1]; + if (hisTab != null && _his.length > 1) { + List operatorIds = new ArrayList<>(); + List operatorNames = new ArrayList<>(); + List operAdvices = new ArrayList<>(); + + for (Map row : _his) { + operatorIds.add(row.get("operatorid") + ""); + operatorNames.add(row.get("operatorname") + ""); + operAdvices.add(row.get("operatorname") + ":" + row.get("operAdvice")); + } + + hisTab.put("operatorid", String.join(",", operatorIds)); + hisTab.put("operatorname", String.join(",", operatorNames)); + hisTab.put("operAdvice", String.join("
", operAdvices)); + } + return hisTab; + } + + @JsonIgnore + public String getNextSteps() { + return DataTableUtil.get(row, "NextStepCode", "") + ""; + } + + public void setNextSteps(String value) { + row.put("NextStepCode", value); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("NextStepCode") + public int[] getNextStepCode() { + String nextSteps = getNextSteps(); + if (NativeExtensionUtils.isNullOrEmpty(nextSteps)) { + return null; + } + + String[] codeStrs = nextSteps.split(","); + List codeList = new ArrayList<>(); + for (String codeStr : codeStrs) { + if (!NativeExtensionUtils.isNullOrEmpty(codeStr)) { + codeList.add(NativeExtensionUtils.ToInt32(codeStr)); + } + } + + if (codeList.isEmpty()) { + return null; + } + + int[] codes = new int[codeList.size()]; + for (int i = 0; i < codeList.size(); i++) { + codes[i] = codeList.get(i); + } + return codes; + } +} + +// 补充必要的基础类定义(假设存在) +abstract class RowBase { + protected Map row; + + public RowBase(Map row) { + this.row = row; + } + + protected Object GetVal(String key) { + return DataTableUtil.get(row, key, null); + } + + protected void SetVal(String key, Object value) { + row.put(key, value); + } +} + diff --git a/WebErp/weberp/src/main/java/org/example/Entity/System/LoginUserInfo.java b/WebErp/weberp/src/main/java/org/example/Entity/System/LoginUserInfo.java new file mode 100644 index 0000000..d69ad39 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/System/LoginUserInfo.java @@ -0,0 +1,108 @@ +package org.example.Entity.System; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.io.Serializable; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class LoginUserInfo { + private static final long serialVersionUID = 1L; // 显式添加序列化ID + + // 字段保持不变 + public String UserId = "0"; + public String UserName; + public String UserCode; + public String SeriesId; + public Integer ServerId = 0; + @JsonIgnore + public String DepCode; + @JsonIgnore + public String RoleId; + @JsonIgnore + public String RoleIds; + @JsonIgnore + public String InPwd = ""; + //新增 + @JsonIgnore + public Boolean IsWeekPwd; + @JsonIgnore + public String PurviewStr; + @JsonIgnore + public String Pwd; + @JsonIgnore + public String ConnectionString; + @JsonIgnore + public String ProviderName; + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("Other") + public Map Other; + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("AppIndex") + public String AppIndex; + @JsonIgnore + public Object AttacInfo; + @JsonIgnore + public Integer LoginType; + public Integer ClientId; + public Integer AttendanceTime; + @JsonIgnore + public Boolean IsClientUser = false; + @JsonIgnore + public String Token; + @JsonIgnore + public String LoginOs; + @JsonIgnore + public Boolean FromToken; + + // 1. 添加显式无参构造函数(Jackson 序列化必需) + public LoginUserInfo() {} + + // 2. 添加所有非 @JsonIgnore 字段的 getter 方法(Jackson 读取字段用) + public String getUserId() { + return UserId; + } + + public String getUserName() { + return UserName; + } + + public String getUserCode() { + return UserCode; + } + + public String getSeriesId() { + return SeriesId; + } + + public Integer getServerId() { + return ServerId; + } + + public Map getOther() { + return Other; + } + + public String getAppIndex() { + return AppIndex; + } + + public Integer getClientId() { + return ClientId; + } + + public Integer getAttendanceTime() { + return AttendanceTime; + } + + // 3. 保留原有的 getRoleIds 方法(无需修改) + @JsonIgnore + public String[] getRoleIds() { + if (RoleId == null) { + return new String[0]; + } + return RoleId.split(","); + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/System/MobileCard.java b/WebErp/weberp/src/main/java/org/example/Entity/System/MobileCard.java new file mode 100644 index 0000000..21b009d --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/System/MobileCard.java @@ -0,0 +1,366 @@ +package org.example.Entity.System; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.example.Enums.SystemTypeEnums; +import org.example.Utils.DataTableUtil; +import org.example.Utils.PublicUtil; +import org.example.Utils.NativeExtensionUtils; + +import java.util.Map; + +/** + * 移动端卡片实体类 + * 创建时间:2020-07-22 18:07:38 + */ +public class MobileCard { + private Map row; + + // 构造方法 + public MobileCard() { + } + + public MobileCard(Map row) { + this(); + this.row = row; + } + + // 分组名称 + private String groupName; + + @JsonIgnore + public String getGroupName() { + if (groupName == null) { + groupName = DataTableUtil.getStringValue(row, "groupname", ""); + } + return groupName; + } + + public void setGroupName(String groupName) { + this.groupName = groupName; + } + + // 列名 + private String colName; + + @JsonIgnore + public String getColName() { + if (colName == null) { + colName = DataTableUtil.getStringValue(row, "colname", ""); + } + return colName; + } + + public void setColName(String colName) { + this.colName = colName; + } + + // 分组可见性 + private String groupVisible; + + @JsonIgnore + public boolean isGroupVisible() { + if (groupVisible == null) { + groupVisible = DataTableUtil.getStringValue(row, "groupvisible", ""); + } + return !"0".equals(groupVisible); + } + + // 分组详情 + private String groupDetail; + + @JsonIgnore + public String getGroupDetail() { + if (groupDetail == null) { + groupDetail = DataTableUtil.getStringValue(row, "groupdetail", ""); + } + return groupDetail; + } + + // 行号 + private Integer rowId; + @JsonProperty("RowId") + public int getRowId() { + if (rowId == null) { + rowId = NativeExtensionUtils.ToInt32(DataTableUtil.getStringValue(row, "rowid", "")); + } + return rowId; + } + + public void setRowId(int rowId) { + this.rowId = rowId; + } + + private Integer _colId; + + @JsonProperty("ColId") + public int getColId() { + return (int) ( NativeExtensionUtils.ToInt32(DataTableUtil.getStringValue(row, "colid", ""))); + } + + public void setColId(int _colId) { + this._colId = _colId; + } + // 明细ID + private Integer mxId; + + @JsonIgnore + public int getMxId() { + if (mxId == null) { + mxId = NativeExtensionUtils.ToInt32(DataTableUtil.getStringValue(row, "mxid", "")); + } + return mxId; + } + + public void setMxId(int mxId) { + this.mxId = mxId; + } + + // 行高 + private Integer rowHeight; + @JsonProperty("RowHeight") + public int getRowHeight() { + if (rowHeight == null) { + rowHeight = NativeExtensionUtils.ToInt32(DataTableUtil.getStringValue(row, "rowheight", "")); + } + return rowHeight; + } + + public void setRowHeight(int rowHeight) { + this.rowHeight = rowHeight; + } + + // 宽度 + private String width; + @JsonProperty("width") + public String getWidth() { + if (width == null) { + width = DataTableUtil.getStringValue(row, "width", ""); + } + return width; + } + + public void setWidth(String width) { + this.width = width; + } + + // 下边线 + private Boolean splitLine; + @JsonProperty("SplitLine") + public boolean isSplitLine() { + if (splitLine == null) { + splitLine = NativeExtensionUtils.toBoolean(DataTableUtil.getStringValue(row, "splitline", "0")); + } + return splitLine; + } + + public void setSplitLine(boolean splitLine) { + this.splitLine = splitLine; + } + + // 行内容 + private String displayText; + + @JsonProperty("Content") + public String getContent() { + if (displayText == null) { + displayText = DataTableUtil.getStringValue(row, "displaytext", ""); + } + return displayText; + } + + public void setContent(String content) { + this.displayText = content; + } + + // 字体 + private String fontFamilyt; + @JsonProperty("FontFamily") + public String getFontFamily() { + if (fontFamilyt == null) { + fontFamilyt = DataTableUtil.getStringValue(row, "fontname", ""); + } + return fontFamilyt; + } + + public void setFontFamily(String fontFamily) { + this.fontFamilyt = fontFamily; + } + + // 字体大小 + private Integer fontSize; + @JsonProperty("FontSize") + public int getFontSize() { + if (fontSize == null) { + fontSize = NativeExtensionUtils.ToInt32(DataTableUtil.getStringValue(row, "fontsize", "")); + } + return fontSize; + } + + public void setFontSize(int fontSize) { + this.fontSize = fontSize; + } + + // 字体颜色 + private String fColor; + @JsonProperty("FColor") + public String getFColor() { + if (fColor == null) { + fColor = DataTableUtil.getStringValue(row, "fcolor", ""); + } + return fColor; + } + + public void setFColor(String fColor) { + this.fColor = fColor; + } + + // 背景颜色 + private String bgColor; + @JsonProperty("BgColor") + public String getBgColor() { + if (bgColor == null) { + bgColor = DataTableUtil.getStringValue(row, "bcolor", ""); + } + return bgColor; + } + + public void setBgColor(String bgColor) { + this.bgColor = bgColor; + } + + // dbColor属性 + private String dbColor; + @JsonProperty("DbColor") + public String getDbColor() { + if (dbColor == null) { + dbColor = DataTableUtil.getStringValue(row, "dbcolor", ""); + } + return dbColor; + } + + public void setDbColor(String dbColor) { + this.dbColor = dbColor; + } + + // dfColor属性 + private String dfColor; + @JsonProperty("DfColor") + public String getDfColor() { + if (dfColor == null) { + dfColor = DataTableUtil.getStringValue(row, "dfcolor", ""); + } + return dfColor; + } + + public void setDfColor(String dfColor) { + this.dfColor = dfColor; + } + + // 加粗 + private Boolean bold; + @JsonProperty("Bold") + public boolean isBold() { + if (bold == null) { + bold = NativeExtensionUtils.toBoolean(DataTableUtil.getStringValue(row, "fbold", "0")); + } + return bold; + } + + public void setBold(boolean bold) { + this.bold = bold; + } + + // 倾斜 + private Boolean fitalic; + @JsonProperty("Fitalic") + public boolean isFitalic() { + if (fitalic == null) { + fitalic = NativeExtensionUtils.toBoolean(DataTableUtil.getStringValue(row, "fitalic", "0")); + } + return fitalic; + } + + public void setFitalic(boolean fitalic) { + this.fitalic = fitalic; + } + + // 删除线 + private Boolean fstrikeline; + @JsonProperty("FStrikeLine") + public boolean isFStrikeLine() { + if (fstrikeline == null) { + fstrikeline = NativeExtensionUtils.toBoolean(DataTableUtil.getStringValue(row, "fstrikeline", "0")); + } + return fstrikeline; + } + + public void setFStrikeLine(boolean fstrikeline) { + this.fstrikeline = fstrikeline; + } + + // 靠右对齐 + private Boolean rightAlign; + @JsonProperty("RightAlign") + public boolean isRightAlign() { + if (rightAlign == null) { + rightAlign = NativeExtensionUtils.toBoolean(DataTableUtil.getStringValue(row, "RightAlign", "0")); + } + return rightAlign; + } + + public void setRightAlign(boolean rightAlign) { + this.rightAlign = rightAlign; + } + + // 显示类型 --0=普通,1=电话,2=QQ,3=微信,4=网站,5=地图 + private Integer displayType; + @JsonProperty("DType") + public int getDType() { + if (displayType == null) { + displayType = NativeExtensionUtils.ToInt32(DataTableUtil.getStringValue(row, "displayType", "")); + } + return displayType; + } + + public void setDType(int dType) { + this.displayType = dType; + } + + // 显示条件 + private String displayCond; + @JsonProperty("Cond") + public String getCond() { + if (displayCond != null) { + return displayCond; + } + displayCond = DataTableUtil.getStringValue(row, "displayCond", null); + if (displayCond != null && !displayCond.isEmpty()) { + displayCond = PublicUtil.ReqSqlPmsByRow( + null, + null, + PublicUtil.SqlToCode(displayCond), + SystemTypeEnums.PmType.ignorenull + ); + } + return displayCond; + } + + public void setCond(String cond) { + this.displayCond = cond; + } + + // 文字居中方式 1,2,3 左,中,右 + private Integer align; + @JsonProperty("Align") + public int getAlign() { + if (align == null) { + align = NativeExtensionUtils.ToInt32(DataTableUtil.getStringValue(row, "textAlign", "1")); + } + return align; + } + + public void setAlign(int align) { + this.align = align; + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/System/ModuleBaseEntity.java b/WebErp/weberp/src/main/java/org/example/Entity/System/ModuleBaseEntity.java new file mode 100644 index 0000000..307fc8e --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/System/ModuleBaseEntity.java @@ -0,0 +1,503 @@ +package org.example.Entity.System; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.example.Entity.Control.Com.SysPoPupMenuBtn; +import org.example.Utils.DataTableUtil; + +import java.util.ArrayList; +import java.util.Map; + + +import java.util.Optional; + +import static org.example.Utils.DataTableUtil.get; +import static org.example.Utils.NativeExtensionUtils.parseInt; +import static org.example.Utils.NativeExtensionUtils.toBoolean; +import static org.example.Utils.PublicUtil.EvalCond; +import static org.example.Utils.PublicUtil.SqlToCode; + +public class ModuleBaseEntity extends ModuleEntity { + + public ModuleBaseEntity() { + super(); + } + + public ModuleBaseEntity(Map basemodule) { + super(basemodule); + } + + public Boolean OperAble = true; + + private Boolean _applyFlag; + + @JsonProperty("ApplyAble") + public Boolean getApplyAble() { + return toBoolean(DataTableUtil.getRowVal(basemodule, "SaveApplyFlag", _applyFlag != null ? _applyFlag : true)); + } + + public void setApplyAble(Boolean value) { + this._applyFlag = value; + } + + private Boolean _backSelected; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("BackSelected") + public Boolean getBackSelected() { + _backSelected = toBoolean(basemodule.getOrDefault("BackSelected", _backSelected)); + return _backSelected != null && _backSelected ? _backSelected : null; + } + + public void setBackSelected(Boolean value) { + this._backSelected = value; + } + + private Boolean _muitlAudit; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("MuitlAudit") + public Boolean getMuitlAudit() { + _muitlAudit = toBoolean(get(basemodule, "MuitlAudit", _muitlAudit)); + return _muitlAudit != null && _muitlAudit ? _muitlAudit : null; + } + + public void setMuitlAudit(Boolean value) { + this._muitlAudit = value; + } + + private Boolean _NoFastAudit; + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("NoFastAudit") + public Boolean getNoFastAudit() { + _NoFastAudit = toBoolean(get(basemodule, "disAppFastAudit", _NoFastAudit,null)); + return (Boolean) _NoFastAudit ? _NoFastAudit : null; + } + + public void setNoFastAudit(Boolean value) { + this._NoFastAudit = value; + } + + private String _taskSql; + + @JsonIgnore + @JsonProperty("TaskSql") + public String getTaskSql() { + return (String) basemodule.getOrDefault("taskSql", _taskSql); + } + + public void setTaskSql(String value) { + this._taskSql = value; + } + + private String _countSql; + + @JsonIgnore + @JsonProperty("CountSql") + public String getCountSql() { + Object value = basemodule.get("countSql"); + return value != null ? (String) value : _countSql; + } + + public void setCountSql(String value) { + this._countSql = value; + } + + @JsonProperty("NeedCount") + public boolean getNeedCount() { + return getCountSql() != null && !getCountSql().isEmpty(); + } + + @JsonIgnore + public Boolean CondSelect = true; + + private String _overBackCond; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("OverBackCond") + public String getOverBackCond() { + return (String) basemodule.getOrDefault("OverBackCond", _overBackCond); + } + + public void setOverBackCond(String value) { + this._overBackCond = value; + } + + private String _attatchModifyCond; + + @JsonIgnore + @JsonProperty("AttatchModifyCond") + public String getAttatchModifyCond() { + return (String) basemodule.getOrDefault("attatchModifyCond", _attatchModifyCond); + } + + public void setAttatchModifyCond(String value) { + this._attatchModifyCond = value; + } + + private String _overBackSql; + + @JsonIgnore + @JsonProperty("OverBackSql") + public String getOverBackSql() { + return (String) get(basemodule, "OverBackSql", _overBackSql, null); + } + + public void setOverBackSql(String value) { + this._overBackSql = value; + } + + private String _overBackKey; + + @JsonIgnore + @JsonProperty("OverBackKey") + public String getOverBackKey() { + Object value = get(basemodule, "OverBackKey", _overBackKey); + return value != null ? value.toString() : ""; + } + + public void setOverBackKey(String value) { + this._overBackKey = value; + } + + private String _overBackOper; + + @JsonIgnore + @JsonProperty("OverBackOper") + public String getOverBackOper() { + return (String) basemodule.getOrDefault("OverBackOper", _overBackOper); + } + + public void setOverBackOper(String value) { + this._overBackOper = value; + } + + private Boolean _importFlag; + + @JsonProperty("ImportAble") + public Boolean getImportAble() { + if (!OperAble) return false; + return _importFlag != null ? _importFlag : toBoolean(basemodule.getOrDefault("ImportFlag", true)); + } + + public void setImportAble(Boolean value) { + this._importFlag = value; + } + + private String _importCond; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("ImportCond") + public String getImportCond() { + return (String) basemodule.getOrDefault("ImportCond", _importCond); + } + + public void setImportCond(String value) { + this._importCond = value; + } + + private String _exportCond; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("ExportCond") + public String getExportCond() { + return (String) get(basemodule, "ExportCond", _exportCond, null); + } + + public void setExportCond(String value) { + this._exportCond = value; + } + + private Boolean _exportFlag; + + @JsonProperty("ExportAble") + public Boolean getExportAble() { + if (!OperAble) return false; + Boolean exportAble = _exportFlag != null ? _exportFlag : toBoolean(basemodule.getOrDefault("ExportFlag", true)); + + if (!exportAble) return false; + if (getExportCond() == null || getExportCond().trim().isEmpty()) { + return exportAble; + } else if (!getExportCond().contains("{")) { + exportAble = toBoolean(EvalCond(SqlToCode(getExportCond()), null, true)); + } + + _exportFlag = exportAble; + return _exportFlag; + } + + public void setExportAble(Boolean value) { + this._exportFlag = value; + } + + + @JsonIgnore + @JsonProperty("ModuleName") + public String getModuleName() { + boolean[] isUrl = new boolean[1]; + return SystemMenu.convertToModuleName((String) basemodule.getOrDefault("dllfilename", null), 0, isUrl); + } + + + @JsonIgnore + @JsonProperty("AddDllName") + public String getAddDllName() { + return (String) basemodule.getOrDefault("adddllname", null); + } + + private String _addXtype; + + + @JsonProperty("AddXtype") + public String getAddXtype() { + if (_addXtype == null || _addXtype.isEmpty()) { + _addXtype = (String) basemodule.getOrDefault("adddllname", null); + if (_addXtype != null && !_addXtype.isEmpty()) { + _addXtype = _addXtype.trim(); + // 这里需要实现 SystemMenu.ConvertToModuleName 对应的 Java 逻辑 + boolean[] isUrl = new boolean[1]; + _addXtype = SystemMenu.convertToModuleName(_addXtype, 0, isUrl); + } + return _addXtype; + } + return _addXtype; + } + + @JsonIgnore + public String ContextMenuId; + + @JsonIgnore + public SysPoPupMenuBtn PopBtn; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Integer BillType; + + private String _dirId; + + @JsonProperty("DirId") + public String getDirId() { +// return (String) DataTableUtil.get(basemodule, "dirid", _dirId, null); + return Optional.ofNullable(get(basemodule, "dirid", _dirId, null)) + .map(Object::toString) + .orElse(null); + } + + public void setDirId(String value) { + this._dirId = value; + } + + private Integer _editType; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("EditType") + public Integer getEditType() { + return (Integer) basemodule.getOrDefault("editType", _editType); + } + + public void setEditType(Integer value) { + this._editType = value; + } + + private Boolean _cellTplflag; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("CellTplflag") + public Boolean getCellTplflag() { + Boolean flag = toBoolean(basemodule.getOrDefault("cellTplflag", _cellTplflag)); + return flag != null && flag ? flag : null; + } + + public void setCellTplflag(Boolean value) { + this._cellTplflag = value; + } + + private boolean _cardTplflag; + + // 2026.2.24新增 + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("WebCardTpl") + public Boolean getWebCardTpl() { + boolean flag = toBoolean(basemodule.getOrDefault("WebCardflag", _cardTplflag)); + if (flag) return true; + return false; + } + + public void setWebCardTpl(Boolean value) { + this._cardTplflag = value; + } + + private String _speciesNo; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("FileSpeciesNo") + public String getFileSpeciesNo() { + return get(basemodule, "FileSpeciesNo", _speciesNo) != null ? (String) get(basemodule, "FileSpeciesNo", _speciesNo) : ""; + } + + public void setFileSpeciesNo(String value) { + this._speciesNo = value; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("AttcViewType") + public Integer AttcViewType; + + private String _ftype; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("FType") + public String getFType() { + _ftype = _ftype != null ? _ftype : (String) basemodule.getOrDefault("ftype", ""); + return _ftype != null && !_ftype.isEmpty() ? _ftype : null; + } + + public void setFType(String value) { + this._ftype = value; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("CusAddTpl") + public String CusAddTpl; + + private String _addHint; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("AddHint") + public String getAddHint() { + Object value = get(basemodule, "addhint", _speciesNo, null); + return value != null ? value.toString() : ""; + } + + public void setAddHint(String value) { + this._addHint = value; + } + + private Integer _rowHeight; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("RowHeight") + public Integer getRowHeight() { + if (_rowHeight == null || _rowHeight == 0) { + Object h = basemodule.getOrDefault("rowHeight", _rowHeight); + if (h != null) { + _rowHeight = Integer.parseInt(h.toString()); + } + } + return _rowHeight; + } + + public void setRowHeight(Integer value) { + this._rowHeight = value; + } + + private Integer _attcLeafOnly; + + + @JsonProperty("AttcLeafOnly") + public Integer getAttcLeafOnly() { + return _attcLeafOnly != null ? _attcLeafOnly : parseInt(basemodule.getOrDefault("bmpSpecIsMJ", true).toString()); + } + + public void setAttcLeafOnly(Integer value) { + this._attcLeafOnly = value; + } + + private Integer _accInfoWidth; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("AccInfow") + public Integer getAccInfow() { + return (Integer) basemodule.getOrDefault("attInfow", _accInfoWidth); + } + + public void setAccInfow(Integer value) { + this._accInfoWidth = value; + } + @JsonProperty("defaultSearch") + public Boolean getDefaultSearch() { + return toBoolean(basemodule.getOrDefault("defaultSearch", true)); + } + + @JsonIgnore + public Map MasterData; + + @JsonIgnore + public ArrayList> DetailData; + + + @JsonIgnore + @JsonProperty("NewVer") + public Boolean getNewVer() { + return toBoolean(basemodule.getOrDefault("newver", false)); + } + + + @JsonIgnore + @JsonProperty("NewWFVer") + public Boolean getNewWFVer() { + return toBoolean(basemodule.getOrDefault("newwfver", false)); + } + + private Boolean _addCopyFlag; + + @JsonIgnore + public String getAttcField1(){ + return basemodule.getOrDefault("attcfield1",false)!=null?basemodule.getOrDefault("attcfield1",false).toString():""; + } + + @JsonIgnore + public String getAttcField2(){ + return basemodule.getOrDefault("attcfield2",false)!=null?basemodule.getOrDefault("attcfield2",false).toString():""; + } + + @JsonIgnore + public String getAttcField3(){ + return basemodule.getOrDefault("attcfield3",false)!=null?basemodule.getOrDefault("attcfield3",false).toString():""; + } + + + @JsonProperty("AddCopyAble") + public Boolean getAddCopyAble() { + return toBoolean(basemodule.getOrDefault("addCopy", _addCopyFlag != null ? _addCopyFlag : true)); + } + + public void setAddCopyAble(Boolean value) { + this._addCopyFlag = value; + } + + private String _desp; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("desp") + public String getDesp() { + Object value = basemodule.getOrDefault("desp", _desp); + return value != null ? value.toString() : ""; + } + + public void setDesp(String value) { + this._desp = value; + } + + private String _noDataMsg; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("noDataMsg") + public String getNoDataMsg() { + return basemodule.getOrDefault("noDataMsg",null)!=null?basemodule.getOrDefault("noDataMsg",null).toString():""; + } + + public void setNoDataMsg(String value) { + this._noDataMsg = value; + } + + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String StepApplyText; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public String StepBackText; + + @JsonInclude(JsonInclude.Include.NON_NULL) + public Object rightMenus; +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/System/ModuleColor.java b/WebErp/weberp/src/main/java/org/example/Entity/System/ModuleColor.java new file mode 100644 index 0000000..0cae420 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/System/ModuleColor.java @@ -0,0 +1,87 @@ +package org.example.Entity.System; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class ModuleColor { + private Map row; + //private IPublicUtil util; + private String _cond; + public String Cond;//+get与set + private String _fontColor; + public String FontColor;//+get与set + public Integer FontSize;//+get + private String _backColor; + public String BackColor;//+get与set + public Boolean Bold;//+get + public Boolean IncLine;//+get + public Boolean DeleteLine; + public Boolean UnderLine; + //public Arraylist ToSArray;//Arraylist需处理 + //public static String ToStyleStr(){}; + /** + * 转换为样式数组 + * @return 包含CSS样式的ArrayList + */ + public ArrayList ToSArray() { + ArrayList styles = new ArrayList<>(); + + // 处理字体颜色 + if (FontColor != null && !FontColor.isEmpty()) { + styles.add(String.format("color:'%s'", FontColor)); + } + + // 处理背景颜色 + if (BackColor != null && !BackColor.isEmpty()) { + styles.add(String.format("'background-color':'%s'", BackColor)); + } + + // 处理加粗 + if (Bold != null && Bold) { + styles.add("'font-weight':600"); + } + + // 处理字体大小 + if (FontSize != null && FontSize > 0) { + styles.add(String.format("'font-size':'%dpx'", FontSize)); + } + + // 处理斜体 + if (IncLine != null && IncLine) { + styles.add("'font-style':'italic'"); + } + + // 处理删除线和下划线(注意:删除线优先级高于下划线) + if (DeleteLine != null && DeleteLine) { + styles.add("'text-decoration':'line-through'"); + } else if (UnderLine != null && UnderLine) { + styles.add("'text-decoration':'underline'"); + } + + return styles; + } + + /** + * 将样式数组转换为样式字符串 + * @param styles 样式数组 + * @return 格式化后的CSS样式字符串,如"{color:'red', 'font-weight':600}" + */ + public static String ToStyleStr(List styles) { + if (styles == null || styles.isEmpty()) { + return null; + } + + // 拼接样式项为逗号分隔的字符串 + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < styles.size(); i++) { + sb.append(styles.get(i)); + if (i < styles.size() - 1) { + sb.append(","); + } + } + + return String.format("{%s}", sb); + } + +} diff --git a/WebErp/weberp/src/main/java/org/example/Entity/System/ModuleEntity.java b/WebErp/weberp/src/main/java/org/example/Entity/System/ModuleEntity.java new file mode 100644 index 0000000..1600043 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/System/ModuleEntity.java @@ -0,0 +1,266 @@ +package org.example.Entity.System; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.example.Utils.DataTableUtil; +import org.example.Utils.JSON; + +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; +import java.util.Objects; + +import static org.example.Utils.DataTableUtil.get; +import static org.example.Utils.NativeExtensionUtils.toBoolean; + +public class ModuleEntity { + private static final Logger log = LoggerFactory.getLogger(ModuleEntity.class); + + + @JsonIgnore + public Map basemodule = new HashMap<>(); + + public ModuleEntity() { + + } + + public ModuleEntity(Map basemodule) { + this.basemodule = basemodule; + } + + protected Map getBasemodule() { + return basemodule; + } + + private String MasterTable; // 私有字段 + + @JsonProperty("MasterTable") + public String getMasterTable() { + return MasterTable; + } + + public void setMasterTable(String value) { + this.MasterTable = value; + } + + private String _fromkey; + + @JsonIgnore + @JsonProperty("Fromkey") + public String getFromkey() { + Object _fk = basemodule.getOrDefault("fromkey", _fromkey); + if (_fk != null && !(_fk.toString().isEmpty())) { + return (_fromkey = _fk.toString()); + } + return null; + } + + public void setFromkey(String value) { + this._fromkey = value; + } + + private String _menuName; + + @JsonInclude(JsonInclude.Include.NON_NULL) + + @JsonProperty("MenuName") + public String getMenuName() { + // 先尝试获取 "menuname",如果不存在则获取 "menucaption",最后使用 _menuName 作为默认值 + Object value = basemodule.getOrDefault("menuname", + basemodule.getOrDefault("menucaption", _menuName)); + + if (value != null && !value.toString().isEmpty()) { + return (_menuName = value.toString()); + } + return null; + } + + public void setMenuName(String value) { + this._menuName = value; + } + + private String _toolsName; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("ToolsName") + public String getToolsName() { + if (_toolsName == null) { + // 2. 替换为和 C# 一致的取值对象(_row 等效对象,比如 basemodule 是 Map 的话) + Object toolsNameObj = basemodule.getOrDefault("ToolsName", _toolsName); // 替代 C# 的 _row.Get(...) + // 3. 安全转字符串(避免 NPE,对齐 C# 的 as string) + _toolsName = toolsNameObj != null ? toolsNameObj.toString() : null; + } + + // 4. 对齐 C# 的相等判断逻辑 + if (Objects.equals(_toolsName, getMenuName())) { + return null; + } + + // 5. 返回最终值 + return _toolsName; + } + + private String _moduleId; + + @JsonProperty("ModuleId") + public String getModuleId() { + Object value = get(basemodule, "MenuCode", _moduleId); + if (value != null && !value.toString().isEmpty()) { + return (_moduleId = value.toString()); + } + return null; + } + + public void setModuleId(String value) { + this._moduleId = value; + } + + private String _menuPrefix; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("MenuPrefix") + public String getMenuPrefix() { + String prefix = (String) get(basemodule, "MenuPrefix", _menuPrefix, null); + if (prefix != null && !prefix.isEmpty()) { + _menuPrefix = prefix.toLowerCase(); + } + + return _menuPrefix == null ? "" : _menuPrefix; + } + + public void setMenuPrefix(String value) { + this._menuPrefix = value; + } + + private String _menuId; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("MenuId") + public String getMenuId() { + Object value = basemodule.getOrDefault("MenuId", _menuId); + if (value != null && !value.toString().isEmpty()) { + return (_menuId = value.toString()); + } + return null; + } + + public void setMenuId(String value) { + this._menuId = value; + } + + private String IdField; + + @JsonProperty("IdField") + public String getIdField() { + return IdField; + } + + public void setIdField(String idField) { + IdField = idField; + } + + @JsonIgnore + public String IdValue; + + @JsonIgnore + public String IdentityId; + + @JsonIgnore + public Map Updrow; + + private Map _leftRecord; + + @JsonIgnore + @JsonProperty("LeftRecord") + public Map getLeftRecord() { + if (_leftRecord == null && SLeftRecord != null && !SLeftRecord.isEmpty()) { + try { + _leftRecord = (Hashtable) JSON.Decode(SLeftRecord); + } catch (Exception e) { + // 处理 JSON 解析异常 + log.error("Exception caught", e); + } + } + return _leftRecord; + } + + public void setLeftRecord(Map value) { + this._leftRecord = value; + } + + @JsonIgnore + public Map PopPms; + + @JsonIgnore + public String SLeftRecord; + + @JsonIgnore + public ModuleEntity ParentModule; + + @JsonIgnore + public BaseDetailModule DetailModule; + + @JsonIgnore + private Boolean IsAdd; + + @JsonIgnore + @JsonProperty("IsAdd") + public boolean getIsAdd() { + return IdValue == null || IdValue.trim().isEmpty(); + } + + private Boolean _noGridLine = null; + + @JsonIgnore + public Boolean NoGridLine; + + @JsonIgnore + @JsonProperty("NoGridLine") + public Boolean getNoGridLine() { + if (_noGridLine == null) { + _noGridLine = toBoolean(get(basemodule, "noGridLine", null)); + } + return _noGridLine != null && _noGridLine ? Boolean.TRUE : null; + } + + public void setNoGridLine(Boolean value) { + this._noGridLine = value; + } + + private Boolean _hideColumnHeader = null; + + @JsonIgnore + public Boolean HideColumnHeader; + + @JsonIgnore + @JsonProperty("HideColumnHeader") + public Boolean getHideColumnHeader() { + if (_hideColumnHeader == null) { + _hideColumnHeader = toBoolean(get(basemodule, "hideColumnHeader", null)); + } + return _hideColumnHeader != null && _hideColumnHeader ? Boolean.TRUE : null; + } + + public void setHideColumnHeader(Boolean value) { + this._hideColumnHeader = value; + } + + private Boolean _noRownumber = null; + + @JsonIgnore + @JsonProperty("NoRownumber") + public Boolean getNoRownumber() { + if (_noRownumber == null) { + _noRownumber = toBoolean(get(basemodule, "noRownumber", null)); + } + return _noRownumber != null && _noRownumber ? true : null; + } + + public void setNoRownumber(Boolean value) { + this._noRownumber = value; + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Entity/System/SpecNo.java b/WebErp/weberp/src/main/java/org/example/Entity/System/SpecNo.java new file mode 100644 index 0000000..db04b78 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/System/SpecNo.java @@ -0,0 +1,179 @@ +package org.example.Entity.System; + +import java.util.ArrayList; +import java.util.List; + +public class SpecNo implements Comparable { + // 字符集合(与C#保持一致,包含数字、小写字母、大写字母,注意重复的's'和'S') + private static final List valueArray; + + static { + valueArray = new ArrayList<>(); + // 添加数字 0-9 + for (char c = '0'; c <= '9'; c++) { + valueArray.add(c); + } + // 添加小写字母 a-z(包含重复的's') + for (char c = 'a'; c <= 'z'; c++) { + valueArray.add(c); + } + valueArray.add('s'); // 原C#中重复的's' + // 添加大写字母 A-Z(包含重复的'S'和'Y') + for (char c = 'A'; c <= 'Z'; c++) { + valueArray.add(c); + } + valueArray.add('S'); // 原C#中重复的'S' + valueArray.add('Y'); // 原C#中重复的'Y' + } + + private char f; + private char e; + private int findex = -1; + private int eindex = -1; + private static int count = -1; + + // 构造方法:接收int值 + public SpecNo(int val) { + int count = getCount(); + int leftIndex = val / count; + if (leftIndex > count) { + leftIndex = leftIndex / count; + } + int sIndex = val % count; + this.f = valueArray.get(leftIndex); + this.e = valueArray.get(sIndex); + } + + // 构造方法:接收字符串 + public SpecNo(String val) { + String str = "00" + (val == null ? "" : val); + int start = Math.max(0, str.length() - 2); + String sub = str.substring(start); + char[] ca = sub.toCharArray(); + this.f = ca[0]; + this.e = ca[1]; + } + + // 构造方法:接收两个字符 + public SpecNo(char f, char e) { + this.f = f; + this.e = e; + } + + // 获取字符集合长度(缓存) + private static int getCount() { + if (count < 0) { + count = valueArray.size(); + } + return count; + } + + // 获取f的索引(懒加载) + public int getFIndex() { + if (findex == -1) { + findex = valueArray.indexOf(f); + } + return findex; + } + + // 获取e的索引(懒加载) + public int getEIndex() { + if (eindex == -1) { + eindex = valueArray.indexOf(e); + } + return eindex; + } + + // 转换为int值 + public int toInt() { + return getFIndex() * getCount() + getEIndex(); + } + + // 比较方法(实现Comparable接口) + @Override + public int compareTo(SpecNo other) { + if (other == null) { + return 1; + } + if (this.getFIndex() > other.getFIndex()) { + return 1; + } else if (this.getFIndex() < other.getFIndex()) { + return -1; + } else { + return Integer.compare(this.getEIndex(), other.getEIndex()); + } + } + + // 重写equals方法 + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + SpecNo specNo = (SpecNo) obj; + return getFIndex() == specNo.getFIndex() && getEIndex() == specNo.getEIndex(); + } + + // 重写hashCode方法 + @Override + public int hashCode() { + return toString().hashCode(); + } + + // 重写toString方法 + @Override + public String toString() { + return f + String.valueOf(e); + } + + // 运算符>的替代方法 + public boolean greaterThan(SpecNo other) { + if (this.getFIndex() > other.getFIndex()) { + return true; + } else if (this.getFIndex() == other.getFIndex()) { + return this.getEIndex() > other.getEIndex(); + } + return false; + } + + // 运算符<的替代方法 + public boolean lessThan(SpecNo other) { + if (this.getFIndex() < other.getFIndex()) { + return true; + } else if (this.getFIndex() == other.getFIndex()) { + return this.getEIndex() < other.getEIndex(); + } + return false; + } + + // 运算符+的替代方法 + public SpecNo add(int value) { + int val = this.toInt() + value; + return new SpecNo(val); + } + + // 运算符-的替代方法 + public SpecNo subtract(int value) { + int val = Math.abs(this.toInt() - value); + return new SpecNo(val); + } + + // 自增(++)的替代方法 + public SpecNo increment() { + return this.add(1); + } + + // 自减(--)的替代方法 + public SpecNo decrement() { + return this.subtract(1); + } + + // 静态方法:从int转换为SpecNo(替代隐式转换) + public static SpecNo valueOf(int value) { + return new SpecNo(value); + } + + // 静态方法:从String转换为SpecNo(替代隐式转换) + public static SpecNo valueOf(String value) { + return new SpecNo(value); + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Entity/System/SystemMenu.java b/WebErp/weberp/src/main/java/org/example/Entity/System/SystemMenu.java new file mode 100644 index 0000000..0259b92 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/System/SystemMenu.java @@ -0,0 +1,615 @@ +package org.example.Entity.System; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.servlet.http.HttpServletRequest; +import org.example.Enums.SystemTypeEnums; +import org.example.Utils.DataTableUtil; +import org.example.Utils.PublicUtil; + +import java.util.*; + +import static org.example.Utils.NativeExtensionUtils.ToInt32; +import static org.example.Utils.NativeExtensionUtils.toBoolean; + +public class SystemMenu { + + private Map _row; + private List> _soure; + private LoginUserInfo user; + private String _text; + private String _moduleId; + public Integer _serverId; // C#中为public int?,Java用Integer表示可空 + private String _moduleId1; + private String _menuStruct; + private boolean _converted = false; + private String _library; + private String href; + private String idValue; + private Object pagePms; + private String _defaultImage; + private String _hoverImage; + private String _hoverMenuTip; + private String _billFlag; + private int _orderId = -1; + private List _children; + + + // =========================== 构造方法(与C#逻辑对齐)=========================== + // 私有无参构造(禁止外部直接实例化) + private SystemMenu() { + } + + // 双参数构造 + public SystemMenu(Map row, LoginUserInfo user) { + super(); + this._row = row; + this.user = user; + } + + // 三参数构造 + public SystemMenu(Map row, List> soure, LoginUserInfo user) { + super(); + this._row = row; + this._soure = soure; + this.user = user; + } + + + // =========================== 属性:ctx(替代C# HttpContext.Current)=========================== + // 注:Java Web中需通过请求上下文获取,此处模拟C#逻辑(若用Spring可替换为RequestContextHolder) + private HttpServletRequest getCtx() { + // 实际项目需根据框架调整(如Spring:RequestContextHolder.getRequestAttributes().getRequest()) + return null; + } + + + // =========================== 核心属性(按C#逻辑实现)=========================== + + /** + * MenuId:从Map取"id"/"menuid"字段,默认0 + */ + @JsonProperty("MenuId") + public Integer getMenuId() { + Object value = DataTableUtil.getRowVal(_row, new String[]{"id", "MenuId"}, 0); + return ToInt32(value); // 替代C# ToInt32(),需自定义工具方法 + } + + /** + * GroupCaption:从Map取"GroupCaption"字段,默认null + */ + @JsonProperty("GroupCaption") + public String getGroupCaption() { + Object value = DataTableUtil.getRowVal(_row, "GroupCaption", null); + return value == null ? null : value.toString(); + } + + /** + * text:JSON序列化忽略null,优先取_userText,否则从Map取"MenuCaption" + */ + @JsonInclude(JsonInclude.Include.NON_NULL) // 替代Newtonsoft NullValueHandling.Ignore + @JsonProperty("text") + public String getText() { + if (_text == null) { + Object value = DataTableUtil.getRowVal(_row, "MenuCaption", null); + _text = value == null ? null : value.toString(); + } + return _text; + } + + public void setText(String text) { + this._text = text; + } + + /** + * ModuleId:复杂逻辑(解析URL参数、提取moduleId等) + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("ModuleId") + public String getModuleId() { + // 优先使用已设置的值,否则从Map取"MenuCode" + if (_moduleId == null) { + Object value = DataTableUtil.getRowVal(_row, "MenuCode", null); + _moduleId = value == null ? null : value.toString(); + } + + // 解析包含"app.html"的URL参数(过滤username/password,提取xtype/moduleId等) + if (_moduleId != null && _moduleId.contains("app.html")) { + try { + Map queryPms = new HashMap<>(); + String[] urlParts = _moduleId.split("\\?"); // Java中?需转义 + if (urlParts.length > 1) { + String queryStr = urlParts[1]; + String[] kvPairs = queryStr.split("&"); + for (String kvStr : kvPairs) { + String[] kvs = kvStr.split("="); + // 过滤username/password,且确保键值对完整 + if (kvs.length == 2 + && !"username".equals(kvs[0].toLowerCase()) + && !"password".equals(kvs[0].toLowerCase())) { + queryPms.put(kvs[0].toLowerCase(), kvs[1]); + } + } + } + + // 提取xtype/moduleId/idValue等参数 + if (queryPms.containsKey("xtype")) { + String moduleId = queryPms.getOrDefault("moduleid", + queryPms.getOrDefault("dllcoid", "")); + if (!moduleId.isEmpty()) { + this._moduleId = moduleId; // 更新ModuleId + this._library = queryPms.get("xtype"); + this.idValue = queryPms.getOrDefault("idvalue", + queryPms.getOrDefault("id", "")); + } + this.pagePms = queryPms; + } + } catch (Exception e) { + // 原C#空catch,Java保留相同逻辑(实际项目建议日志打印) + } + } + return _moduleId; + } + + public void setModuleId(String moduleId) { + this._moduleId = moduleId; + } + + /** + * serverId:可空int,优先取已设置值,否则从Map取"serverid"(默认0) + */ + @JsonProperty("serverId") + public Integer getServerId() { + if (_serverId == null) { + Object value = _row.getOrDefault("serverid", "0"); // 假设Map有get(key, defaultValue)方法 + _serverId = ToInt32(value); + } + return _serverId > 0 ? _serverId : null; + } + + public void setServerId(Integer serverId) { + this._serverId = serverId; + } + + /** + * ModuleId1:从Map取"MenuCode1",默认null + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("ModuleId1") + public String getModuleId1() { + if (_moduleId1 == null) { + Object value = DataTableUtil.getRowVal(_row, "MenuCode1", null); + _moduleId1 = value == null ? null : value.toString(); + } + return _moduleId1; + } + + public void setModuleId1(String moduleId1) { + this._moduleId1 = moduleId1; + } + + /** + * MenuStruct:从Map取"MenuStruct",默认null + */ + @JsonProperty("MenuStruct") + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getMenuStruct() { + if (_menuStruct == null) { + Object value = DataTableUtil.getRowVal(_row, "MenuStruct", null); + _menuStruct = value == null ? null : value.toString(); + } + return _menuStruct; + } + + public void setMenuStruct(String menuStruct) { + this._menuStruct = menuStruct; + } + + /** + * ParentId:从Map取"ParentId"(默认0),转字符串返回 + */ + @JsonProperty("ParentId") + public String getParentId() { + return Objects.toString(DataTableUtil.getRowVal(_row, "ParentId", 0), ""); + } + + /** + * Level:从Map取"level"(默认0),JSON序列化忽略 + */ + @JsonIgnore // 替代Newtonsoft.Json.JsonIgnore + public int getLevel() { + Object value = DataTableUtil.getRowVal(_row, "level", 0); + return ToInt32(value); + } + + /** + * ModuleName:关联_library,含ConvertToModuleName逻辑,JSON序列化忽略null + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("ModuleName") + public String getModuleName() { + // 优先取已设置值,否则从Map取"Library" + if (_library == null) { + Object value = DataTableUtil.getRowVal(_row, "Library", null); + _library = value == null ? null : value.toString(); + } + + // 仅转换一次(_converted标记) + if (!_converted) { + boolean[] isUrl = new boolean[1]; // Java无out参数,用数组接收 + _library = convertToModuleName(_library, 1, isUrl); + _converted = true; + // 若为URL,同步设置href + if (isUrl[0]) { + this.href = _library; + } + } + return _library; + } + + public void setModuleName(String moduleName) { + this._library = moduleName; + } + + // =========================== 其他简单属性(直接映射)=========================== + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("href") + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("idValue") + public String getIdValue() { + return idValue; + } + + public void setIdValue(String idValue) { + this.idValue = idValue; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("pagePms") + public Object getPagePms() { + return pagePms; + } + + public void setPagePms(Object pagePms) { + this.pagePms = pagePms; + } + + /** + * ReadAble:用户查看权限判断(优先级:用户ID→角色ID→操作权限) + */ + @JsonProperty("ReadAble") + public boolean isReadAble() { + boolean has = true; + String readUserIds = getReadUserIds(); + + // 1. 优先判断"查看用户IDs" + if (readUserIds != null && !readUserIds.isEmpty()) { + has = ("," + readUserIds + ",").contains("," + user.UserId + ","); + } + + // 2. 无用户权限时,判断"查看角色IDs" + if ((!has || (readUserIds == null || readUserIds.isEmpty())) + && getRoleReadIds() != null && !getRoleReadIds().isEmpty()) { + has = isRoleReadAble(); + } + + // 3. 前两者无权限时,判断操作权限 + if (!has && ((getOperuserIds() != null && !getOperuserIds().isEmpty()) + || (getRoleOperIds() != null && !getRoleOperIds().isEmpty()))) { + return isOperAble(); + } + + return has; + } + + /** + * OperAble:用户操作权限判断(优先级:用户ID→角色ID) + */ + @JsonProperty("OperAble") + public boolean isOperAble() { + boolean has = true; + String operUserIds = getOperuserIds(); + + // 1. 优先判断"操作用户IDs" + if (operUserIds != null && !operUserIds.isEmpty()) { + has = ("," + operUserIds + ",").contains("," + user.UserId + ","); + } + + // 2. 无用户权限时,判断"操作角色IDs" + if ((!has || (operUserIds == null || operUserIds.isEmpty())) + && getRoleOperIds() != null && !getRoleOperIds().isEmpty()) { + return isRoleOperAble(); + } + + return has; + } + + /** + * ReadUserIds:私有属性,从Map取"ReadPurview" + */ + @JsonProperty("ReadUserIds") + private String getReadUserIds() { + Object value = DataTableUtil.getRowVal(_row, "ReadPurview", null); + return value == null ? null : value.toString(); + } + + /** + * OperuserIds:私有属性,从Map取"OperPurview" + */ + @JsonProperty("OperuserIds") + private String getOperuserIds() { + Object value = DataTableUtil.getRowVal(_row, "OperPurview", null); + return value == null ? null : value.toString(); + } + + /** + * RoleReadAble:角色查看权限判断 + */ + @JsonProperty("RoleReadAble") + public boolean isRoleReadAble() { + boolean has = true; + String roleReadIds = getRoleReadIds(); + + if (roleReadIds != null && !roleReadIds.isEmpty()) { + has = false; + String[] userRoles = user.getRoleIds(); // 假设LoginUserInfo的roleIds是String数组 + if (userRoles != null && userRoles.length > 0) { + for (String role : roleReadIds.split(",")) { + for (String userRole : userRoles) { + if (role.equals(userRole)) { + has = true; + break; + } + } + if (has) break; + } + } + } + + // 无角色查看权限时,判断角色操作权限 + if (!has && getRoleOperIds() != null && !getRoleOperIds().isEmpty()) { + return isRoleOperAble(); + } + + return has; + } + + /** + * RoleOperAble:角色操作权限判断 + */ + @JsonProperty("RoleOperAble") + public boolean isRoleOperAble() { + boolean has = true; + String roleOperIds = getRoleOperIds(); + + if (roleOperIds != null && !roleOperIds.isEmpty()) { + has = false; + String[] userRoles = user.getRoleIds(); + if (userRoles != null && userRoles.length > 0) { + for (String role : roleOperIds.split(",")) { + for (String userRole : userRoles) { + if (role.equals(userRole)) { + has = true; + break; + } + } + if (has) break; + } + } + } + + return has; + } + + /** + * RoleReadIds:私有属性,从Map取"RoleReadPurview" + */ + @JsonProperty("RoleReadIds") + private String getRoleReadIds() { + Object value = DataTableUtil.getRowVal(_row, "RoleReadPurview", null); + return value == null ? null : value.toString(); + } + + /** + * RoleOperIds:私有属性,从Map取"RoleOperPurview" + */ + @JsonProperty("RoleOperIds") + private String getRoleOperIds() { + Object value = DataTableUtil.getRowVal(_row, "RoleOperPurview", null); + return value == null ? null : value.toString(); + } + + /** + * AppUseAble:从Map取"AppUseFlag",取反返回(JSON忽略) + */ + @JsonIgnore + public boolean isAppUseAble() { + Object value = DataTableUtil.getRowVal(_row, "AppUseFlag", null); + return !toBoolean(value); // 替代C# ToBoolean() + } + + /** + * ShowCount:从Map取"needcount",转布尔值 + */ + @JsonProperty("ShowCount") + public boolean isShowCount() { + Object value = DataTableUtil.getRowVal(_row, "needcount", null); + return toBoolean(value); + } + + /** + * DefaultImage:从Map取"DefaultImage",默认null + */ + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("DefaultImage") + public String getDefaultImage() { + if (_defaultImage == null) { + Object value = DataTableUtil.getRowVal(_row, "DefaultImage", null); + _defaultImage = value == null ? null : value.toString(); + } + return _defaultImage; + } + + public void setDefaultImage(String defaultImage) { + this._defaultImage = defaultImage; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("HoverImage") + public String getHoverImage() { + if (_hoverImage == null) { + Object value = DataTableUtil.getRowVal(_row, "HoverImage", null); + _hoverImage = value != null ? value.toString() : null; // 一行完成判空+赋值 + } + return _hoverImage; + } + + public void setHoverImage(String hoverImage) { + this._hoverImage = hoverImage; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("HoverMenuTip") + public String getHoverMenuTip() { + if (_hoverMenuTip == null) { + Object value = DataTableUtil.getRowVal(_row, "HoverMenuTip", null); + _hoverMenuTip = value != null ? value.toString() : null; // 一行完成判空+赋值 + } + return _hoverMenuTip; + } + + public void setHoverMenuTip(String hoverMenuTip) { + this._hoverMenuTip = hoverMenuTip; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("BillFlag") + public String getBillFlag() { + if (_billFlag == null) { + Object value = DataTableUtil.getRowVal(_row, "BillFlag", null); + _billFlag = value != null ? value.toString() : null; // 一行完成判空+赋值 + } + return _billFlag; + } + + public void setBillFlag(String billFlag) { + this._billFlag = billFlag; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("OrderId") + public Integer getOrderId() { + if (_orderId==-1){ + Object value = DataTableUtil.getRowVal(_row, "OrderID", null); + _orderId=value != null ? ToInt32(value) : _orderId; ; + } + return _orderId; + } + + public void setOrderId(Integer orderId) { + this._orderId = orderId; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonProperty("Children") + public List getChildren() { + // 1. 对应 C#: if (_soure != null) + if (_soure != null && !_soure.isEmpty()) { + // 2. 对应 C#: _children ?? (_children = ...) → 懒加载+缓存 + if (_children == null) { + _children = new ArrayList<>(); + // 3. 遍历 _source(对应 C#: from row in _soure.AsEnumerable()) + for (Map row : _soure) { + // 4. 筛选条件:(row["ParentId"] + "") == MenuId + ""(对齐 C# 的 where 条件) + String parentId = DataTableUtil.getStringValue(row, "ParentId"); // 行的ParentId转字符串 + String currentMenuId = this.getMenuId() != null ? this.getMenuId().toString() : ""; // 当前菜单ID转字符串 + + // 匹配条件:子菜单的ParentId = 当前菜单的MenuId + if (Objects.equals(parentId, currentMenuId)) { + // 5. 对应 C#: select new SystemMenu(row, _soure, user) + SystemMenu childMenu = new SystemMenu(row, _soure, this.user); + _children.add(childMenu); + } + } + } + } + // 6. 对应 C#: _soure 为null时返回 _children + return _children; + } + + public void setChildren(List children) { + this._children = children; + } + + public static String convertToModuleName(String dllName, Integer dllType, boolean[] isUrl) { + // 初始化输出参数 + isUrl[0] = false; + + // 处理空值并转换为小写 + String name = ""; + String lwDllName = (dllName == null ? "" : dllName).trim() + .replace("\r\n", "") + .toLowerCase(); + + // 空值检查 + if (dllName == null || dllName.trim().isEmpty()) { + return ""; + } + + // 判断是否为 URL + if (dllName.startsWith("http") || dllName.startsWith("www") + || dllName.startsWith("ftp") || dllName.startsWith("/") + || dllName.indexOf(".html?") > -1) { + isUrl[0] = true; + Map pms = new HashMap<>(); + return PublicUtil.ReqSqlPms(null, null, dllName, SystemTypeEnums.PmType.sql, null); + } + + // 检查是否为 .dll 或 .lsp 文件 + if (!lwDllName.endsWith(".dll") && !lwDllName.endsWith(".lsp")) { + return dllName; + } + + // 处理特定 DLL 名称 + if (lwDllName.equals("miniblink.dll")) { + return "plugins.PubBrower.Index"; + } + if (lwDllName.equals("lskj.billinfo.dll")) { + return "plugins.pubbillinfo.Index"; + } + + // 通用处理逻辑 + name = lwDllName.replace("_", "."); + String[] dlls = name.split("\\."); // 注意:Java 中.需要转义 + + if (dlls.length == 3 && name.startsWith("lskj")) { + name = name.replace(".dll", ".index"); + } + + name = name.replace("lskj.", "plugins.").replace(".dll", ""); + + // 处理特殊名称 + if (name.contains("pubadd")) { + name = "plugins.PubModule.PubAdd"; + } else if (name.equals("pubspec")) { + name = "plugins.pubspec.index"; + } else if (name.equals("leftgridiframe")) { + name = "plugins.leftgridiframe.index"; + } else if (name.contains("pubprint.lsp")) { + name = "plugins.pubprint.index"; + } else if (name.endsWith(".lsp")) { + name = name.replace("\\", ".").replace(".lsp", ""); + } + + return name; + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Entity/System/UpdStrModule.java b/WebErp/weberp/src/main/java/org/example/Entity/System/UpdStrModule.java new file mode 100644 index 0000000..8beb2b7 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Entity/System/UpdStrModule.java @@ -0,0 +1,76 @@ +package org.example.Entity.System; + +import org.example.Utils.DataTableUtil; + +import java.util.Map; +import java.util.Objects; + +/** + * 说明: + * 创建时间:2018-12-29 11:12:05 + * 编辑人: + * 编辑时间: + * 备注: + */ +public class UpdStrModule { + protected Map row; + + public static final String GuidKeyName = "fk"; + public static final String TableNameName = "bn"; + public static final String IdFieldName = "k"; + public static final String IdValueName = "v"; + public static final String IdentityIdName = "Id"; + + private String idValue; + private String identityId; + + public UpdStrModule(Map row) { + this.row = row; + } + + public String getGuidKey() { + return DataTableUtil.get(row, GuidKeyName, "") + ""; + } + + public String getTableName() { + return DataTableUtil.get(row, TableNameName, "") + ""; + } + + public String getIdField() { + return DataTableUtil.get(row, IdFieldName, "") + ""; + } + + public String getIdValue() { + if (idValue == null) { + idValue = Objects.toString(DataTableUtil.get(row, IdValueName, ""), ""); + } + return idValue; + } + + public void setIdValue(String idValue) { + this.idValue = idValue; + } + + public String getIdentityId() { + if (identityId == null) { + identityId = DataTableUtil.get(row, IdentityIdName, "") + ""; + } + return identityId; + } + + public void setIdentityId(String identityId) { + this.identityId = identityId; + } + + public boolean Valide() { + return !isBlank(getGuidKey()) && + !isBlank(getTableName()) && + !isBlank(getIdField()) && + !(isBlank(getIdValue()) && isBlank(getIdentityId())); + } + + // 工具方法:判断字符串是否为空白(null或空字符串) + private boolean isBlank(String str) { + return str == null || str.trim().isEmpty(); + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Enums/CusGridColumnPrefix.java b/WebErp/weberp/src/main/java/org/example/Enums/CusGridColumnPrefix.java new file mode 100644 index 0000000..20b40b4 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Enums/CusGridColumnPrefix.java @@ -0,0 +1,88 @@ +package org.example.Enums; + +public class CusGridColumnPrefix { + /** + * 基础档案、报表左侧表格 + */ + public static final String BaseLeftGridView = "BaseLeftGridView_"; + + /** + * 基础档案、报表主表格 + */ + public static final String BaseMainGridView = "BaseMainGridView_"; + + /** + * 基础档案、报表右侧表格 + */ + public static final String BaseRightGridView = "BaseRightGridView_"; + + /** + * 基础档案、报表底部表格 + */ + public static final String BaseDetailGridView = "BaseDetailGridView_"; + + /** + * 基础审核主表格 + */ + public static final String BaseAuditMainGridView = "BaseAuditMainGridView_"; + + /** + * 单据来源表格 + */ + public static final String BillSourceGridView = "BillLeftGridView_"; + + /** + * 单据明细表格 + */ + public static final String BillDetailGridView = "BillDetailGridView_"; + + /** + * 单据来源明细表格 + */ + public static final String BillSourceDetailGridView = "BillSourceDetailGridView_"; + + /** + * 单据来源明细附加信息表格 + */ + public static final String BillSourceDetailAttachGridView = "BillSourceDetailAttachGridView_"; + + /** + * 单据审核主表格 + */ + public static final String AuditMainGridView = "AuditMainGridView_"; + + /** + * 单据审核弹出框明细表格 + */ + public static final String AuditPopupDetailGridView = "AuditPopupDetailGridView_"; + + /** + * 单据审核弹出选项卡数据的表格 + */ + public static final String AuditPopupBasicInfoGridView = "AuditPopupBasicInfoGridView_"; + + /** + * 单据审核弹出框附加信息表格 + */ + public static final String AuditPopupAttachGridView = "AuditPopupAttachGridView_"; + + /** + * 单据已完成单据主表格 + */ + public static final String AuditOverMainGridView = "AuditOverMainGridView_"; + + /** + * 单据已完成单据明细表格 + */ + public static final String AuditOverDetailGridView = "AuditOverDetailGridView_"; + + /** + * 附件表格 + */ + public static final String AttachGridView = "AttachGridView_"; + + /** + * 单据详情表格 + */ + public static final String BillInfoGridView = "BillInfoGridView_"; +} diff --git a/WebErp/weberp/src/main/java/org/example/Enums/CustomInOutParameter.java b/WebErp/weberp/src/main/java/org/example/Enums/CustomInOutParameter.java new file mode 100644 index 0000000..6691111 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Enums/CustomInOutParameter.java @@ -0,0 +1,17 @@ +package org.example.Enums; + +import org.springframework.jdbc.core.SqlInOutParameter; +import org.springframework.jdbc.core.SqlParameter; + +// 4. 输入输出参数子类(继承 SqlInOutParameter) +public class CustomInOutParameter extends CustomSqlParameter { + public CustomInOutParameter(String name, int sqlType, String mode) { + super(name, sqlType, mode, ParameterDirection.INPUT_OUTPUT); + } + + @Override + public SqlParameter toSpringSqlParameter() { + // 返回 Spring 原生的 SqlInOutParameter(明确 INPUT_OUTPUT) + return new SqlInOutParameter(name, sqlType); + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Enums/CustomInParameter.java b/WebErp/weberp/src/main/java/org/example/Enums/CustomInParameter.java new file mode 100644 index 0000000..08c9c56 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Enums/CustomInParameter.java @@ -0,0 +1,17 @@ +package org.example.Enums; + +import org.springframework.jdbc.core.SqlParameter; + +// 2. 输入参数子类(继承 SqlParameter) +public class CustomInParameter extends CustomSqlParameter { + public CustomInParameter(String name, int sqlType, String mode) { + super(name, sqlType, mode, ParameterDirection.INPUT); + } + + // 关键:重写方法,返回 Spring 原生的 SqlParameter(输入类型) + @Override + public SqlParameter toSpringSqlParameter() { + // 直接创建并返回原生 SqlParameter,确保方向为 INPUT + return new SqlParameter(this.name, this.sqlType); + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Enums/CustomOutParameter.java b/WebErp/weberp/src/main/java/org/example/Enums/CustomOutParameter.java new file mode 100644 index 0000000..8af789c --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Enums/CustomOutParameter.java @@ -0,0 +1,16 @@ +package org.example.Enums; + +import org.springframework.jdbc.core.SqlOutParameter; +import org.springframework.jdbc.core.SqlParameter; + +// 3. 输出参数子类(继承 SqlOutParameter) +public class CustomOutParameter extends CustomSqlParameter { + public CustomOutParameter(String name, int sqlType, String mode) { + super(name, sqlType, mode, ParameterDirection.OUTPUT); + } + + @Override + public SqlParameter toSpringSqlParameter() { + return new SqlOutParameter(this.name, this.sqlType); + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Enums/CustomSqlParameter.java b/WebErp/weberp/src/main/java/org/example/Enums/CustomSqlParameter.java new file mode 100644 index 0000000..df16238 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Enums/CustomSqlParameter.java @@ -0,0 +1,77 @@ +package org.example.Enums; + +import org.example.Enums.ParameterDirection; +import org.springframework.jdbc.core.SqlInOutParameter; +import org.springframework.jdbc.core.SqlOutParameter; +import org.springframework.jdbc.core.SqlParameter; + +// 1. 抽象基类:定义公共属性和方法 +public abstract class CustomSqlParameter { + protected String name; + protected int sqlType; + + + protected String mode; + protected ParameterDirection direction; + protected Object value; + + public CustomSqlParameter(String name, int sqlType, String mode, ParameterDirection direction) { + this.name = name; + this.sqlType = sqlType; + this.mode = mode; + this.direction = direction; + } + + public CustomSqlParameter(String name, int sqlType, String mode, ParameterDirection direction, Object value) { + this.name = name; + this.sqlType = sqlType; + this.mode = mode; + this.direction = direction; + this.value = value; + } + + // 抽象方法:返回对应的 Spring 原生参数对象(关键) + public abstract SqlParameter toSpringSqlParameter(); + + // Getter 方法 + public String getName() { + return name; + } + + public Object getValue() { + return value; + } + + public void setName(String name) { + this.name = name; + } + + public void setSqlType(int sqlType) { + this.sqlType = sqlType; + } + + public void setMode(String mode) { + this.mode = mode; + } + + public void setDirection(ParameterDirection direction) { + this.direction = direction; + } + + public void setValue(Object value) { + this.value = value; + } + + public int getSqlType() { + return sqlType; + } + + public String getMode() { + return mode; + } + + public ParameterDirection getDirection() { + return direction; + } +} + diff --git a/WebErp/weberp/src/main/java/org/example/Enums/LoginCode.java b/WebErp/weberp/src/main/java/org/example/Enums/LoginCode.java new file mode 100644 index 0000000..90b1b00 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Enums/LoginCode.java @@ -0,0 +1,17 @@ +package org.example.Enums; + +public enum LoginCode { + Redirect(0), + ResetPwd(-1), + WxRedirect(-2); + + private final int value; + + LoginCode(int value) { + this.value = value; + } + + public int getValue() { + return value; + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Enums/ParameterDirection.java b/WebErp/weberp/src/main/java/org/example/Enums/ParameterDirection.java new file mode 100644 index 0000000..aed8ba5 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Enums/ParameterDirection.java @@ -0,0 +1,16 @@ +package org.example.Enums; + +/** + * 使用JdbcTemplate直接执行查询并返回多结果集 + *

+ * // * @param querySql 包含多个结果集的SQL语句 + * + * @return 多结果集列表,每个元素为一个结果集(List>) + */ + +public enum ParameterDirection { + INPUT, // 输入参数 + OUTPUT, // 输出参数 + INPUT_OUTPUT,// 输入输出参数 + RETURN_VALUE // 返回值参数 +} diff --git a/WebErp/weberp/src/main/java/org/example/Enums/SystemEnums.java b/WebErp/weberp/src/main/java/org/example/Enums/SystemEnums.java new file mode 100644 index 0000000..6fc8aa2 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Enums/SystemEnums.java @@ -0,0 +1,394 @@ +package org.example.Enums; + +/** + * 系统枚举集合 + * 包含控件类型、消息表类型、模块类型等多种枚举 + * 创 建 者:zyw + * 创建日期:2016-06-28 15:50:32 + */ +public class SystemEnums { + /** + * 控件类型 + */ + public enum ControlType { + LabText(0, "文本框"), + LabComboxValue(1, "下拉框 获取Value值"), + LabComboxText(2, "下拉框 获取Text值"), + LabTreeType(3, "树"), + LabDate(4, "日期(年-月-日)"), + LabDateTime(44, "日期和时间(年-月-日 时-分-秒)"), + LabDateTimeShort(444, "日期短时间(年-月-日 时-分)"), + LabTime(4444, "时间(时-分-秒)"), + LabShortTime(44444, "短时间(时-分)"), + LabYearTime(444444, "时间(年-月)"), + LabAutoCompleteValue(5, "自动搜索 获取Value值"), + LabAutoCompleteText(6, "自动搜索 获取Text值"), + LabTextInt(7, "数字控件"), + LabPassword(8, "密码框"), + LabRemark(9, "备注"), + LabWWW(10, "网址"), + LabQQ(11, "QQ"), + LabPhone(12, "电话拨号(手机端拨号)"), + LabVPhone(200, "如果是web端,那么就带发短信验证码功能"), + LabMultiSelectValue(13, "多选,获取Value"), + LabMultiSelectText(14, "多选,获取Text"), + LabAutoCompleteValueParam(15, "自动搜索 获取Value值,需要带参数"), + LabAutoCompleteTextParam(16, "自动搜索 获取Text值,需要带参数"), + LabCheckBox(17, "复选框"), + LabComboxCheckListValue(18, ""), + LabComboxCheckListText(19, ""), + LabMemoEdit(20, "Memo控件"), + LabCheckComboxValue(21, ""), + LabCheckComboxText(22, ""), + LabComboxInputParam(23, "Combox获取Text值,需要带参数 宜宾项目为36"), + LabCheckDateEx(24, ""), + LabCheckAutoSeacherValue(25, ""), + LabCheckAutoSeacherText(26, ""), + LabCalcText(27, "计算器"), + LabMultiSelectValueParam(33, "多选 获取Value值,需要带参数"), + LabMultiSelectTextParam(34, "多选 获取Text值,需要带参数"), + LabComboxValueParam(35, "Combox获取Value值,需要带参数"), + LabComboxTextParam(36, "下拉列表框(带参数) 保存Value值,显示Text值"), + LabRichEdit(37, "富文本编辑框"), + LabSmartQueryReturnID(38, "智能搜索返回ID"), + LabSmartQueryReturnName(39, "智能搜索返回Name"), + LabSmartQueryReturnIDWithParam(40, "智能搜索返回ID 带参数"), + LabSmartQueryReturnNameWithParam(41, "智能搜索返回Name 带参数"), + LabProgress(96, "进度条"), + LabMap(97, "地图(手机端)"), + LabOSacn(98, "扫码(手机端) 条码"), + LabPic(99, "图片"), + LabPicEx(100, "图片,存地址"), + LabTSacn(101, "扫码(手机端) 二维码"), + LabWinBtnsValue(102, ""), + LabWinBtnsText(103, ""), + LabWinMultiBtnsValue(104, ""), + LabWinMultiBtnsText(105, ""), + LabRadioGroupValue(106, "单选框组"), + LabRadioGroupText(107, "单选框组"), + LabWinGridValue(109, ""), + LabWinGridText(110, ""), + LabWinMultiGridValue(111, ""), + LabWinMultiGridText(112, ""), + LabModuleSelectValues(42, "弹出模块选行数据,根据行数据自动填充表单字段"), + LabModuleMultiSelectValues(43, "弹出模块多选行数据,根据行数据自动填充表单字段"), + LabComboxModuleValueParam(116, "弹窗模块选择value"), + LabComboxModuleTextParam(117, "弹窗模块选择text"), + LabComTreeboxValue(118, "下拉树返回value"), + LabComTreeboxText(119, "下拉树返回text"), + LabComTreeboxValueParam(120, "下拉树返回value带参数"), + LabComTreeboxTextParam(121, "下拉树返回text带参数"), + LabComTreeboxLeafValue(122, "下拉树返回value带参数"), + LabComTreeboxLeafText(123, "下拉树返回text带参数"), + LabComTreeboxLeafValueParam(124, "下拉树返回value带参数"), + LabComTreeboxLeafTextParam(125, "下拉树返回text带参数"), + LabComStarValueParam(126, "下拉树返回value带参数"), + LabComStarTextParam(127, "下拉树返回text带参数"), + LabSignature(128, "签字框"), + LabJointRemark(129, "签字框"), + CheckboxGroupValue(130, "多选框组"), + CheckboxGroupText(131, "多选框组"), + LabMultiComTreeboxLeafValue(132, "下拉树返回value"), + LabMultiComTreeboxLeafText(133, "下拉树返回text"), + LabMultiComTreeboxLeafValueParam(134, "下拉树多选返回末级value带参数"), + LabMultiComTreeboxLeafTextParam(135, "下拉树多选返回末级text带参数"), + LabMultiComTreeboxValue(136, "下拉树多选返回value"), + LabMultiComTreeboxText(137, "下拉树多选返回text"), + LabMultiComTreeboxValueParam(138, "下拉树多选返回value带参数"), + LabMultiComTreeboxTextParam(139, "下拉树多选返回text带参数"), + ApiLabText(140, "调用接口的文本"), + + + LabLink(164,"超链接"),//超链接 + LabModuleLink(165,"超链接"),//超链接 + + LabEleScale(170,""), + LabCheckDateTimeEx(244, ""), + LabCheckDateTimeShort(2444, ""), + LabCheckTime(24444, ""), + LabCheckShortTime(244444, ""), + LabLabYearTime(2444444, ""); + + private final int value; + private final String description; + + ControlType(int value, String description) { + this.value = value; + this.description = description; + } + + public int getValue() { + return value; + } + + public String getDescription() { + return description; + } + + public static ControlType fromValue(int value) { + for (ControlType type : ControlType.values()) { + if (type.value == value) { + return type; + } + } + throw new IllegalArgumentException("未知的ControlType值: " + value); + } + } + + /** + * 消息表类型 + */ + public enum MsgTableType { + WX(3), + YunZhiJia(4); + + private final int value; + + MsgTableType(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static MsgTableType fromValue(int value) { + for (MsgTableType type : MsgTableType.values()) { + if (type.value == value) { + return type; + } + } + throw new IllegalArgumentException("未知的MsgTableType值: " + value); + } + } + + /** + * 模块类型枚举 + */ + public enum ModuleTypeEnum { + Bill, + Module, + Report, + Accraditation, + Other + } + + /** + * 模块类型 + */ + public enum ModuleType { + BaseModule, + ReportModule, + BillModule + } + + /** + * 单据菜单枚举 + */ + public enum BillMenuEnum { + BillSource, + BillSourceDetail, + BillDetail + } + + /** + * 目标类型 + */ + public enum TargetType { + ALL(0), + CS(1), + MOBILE(2), + WEB(3); + + private final int value; + + TargetType(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static TargetType fromValue(int value) { + for (TargetType type : TargetType.values()) { + if (type.value == value) { + return type; + } + } + throw new IllegalArgumentException("未知的TargetType值: " + value); + } + } + + /** + * 图表模块类型 + */ + public enum ChartType { + Line(1, "折线"), + Bar(2, "柱状图"), + Pie(3, "饼图"), + Map(4, "地图 一般为混搭模式"), + Radar(5, "雷达"), + Candlestick(6, "k线图"), + Boxplot(7, "箱线图"), + Heatmap(8, "热力图"), + Graph(9, "关系图"), + Treemap(10, "矩形树图"), + Parallel(11, "平行坐标"), + Sankey(12, "桑基图"), + Funnel(13, "漏斗"), + Gauge(14, "仪表盘"), + PictorialBar(15, "象形柱图"), + ThemeRiver(16, "主体河流"), + Scatter(17, "散点图"); + + private final int value; + private final String description; + + ChartType(int value, String description) { + this.value = value; + this.description = description; + } + + public int getValue() { + return value; + } + + public String getDescription() { + return description; + } + + public static ChartType fromValue(int value) { + for (ChartType type : ChartType.values()) { + if (type.value == value) { + return type; + } + } + throw new IllegalArgumentException("未知的ChartType值: " + value); + } + } + + /** + * 单据状态 + */ + public enum BillState { + Canceled(-1, "作废"), + Draft(0, "草稿"), + Applied(1, "提交未审核"), + Aduit(2, "审核中"), + Backed(3, "已退回"), + Over(4, "已完成"); + + private final int value; + private final String description; + + BillState(int value, String description) { + this.value = value; + this.description = description; + } + + public int getValue() { + return value; + } + + public String getDescription() { + return description; + } + + public static BillState fromValue(int value) { + for (BillState state : BillState.values()) { + if (state.value == value) { + return state; + } + } + throw new IllegalArgumentException("未知的BillState值: " + value); + } + } + + /** + * 程序事件类型 + */ + public enum OperateEvent { + BeforeModuleDataLoad(1), + BeforeModuleDataChange(3), + AfterModuleDataChange(4), + BeforeModuleStateChange(5), + AfterModuleStateChange(6), + BeforeModuleAuditStateChange(7), + AfterModuleAuditStateChange(8), + BeforeModuleContextMenu(9), + AfterModuleContextMenu(10), + BeforeModuleDataDelete(11), + AfterModuleDataDelete(12), + BeforeUploadFile(13), + AfterUploadFile(14), + Loop(99), + Timer(100); + + private final int value; + + OperateEvent(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static OperateEvent fromValue(int value) { + for (OperateEvent event : OperateEvent.values()) { + if (event.value == value) { + return event; + } + } + throw new IllegalArgumentException("未知的OperateEvent值: " + value); + } + } + + /** + * 操作类型 + */ + public enum ActionType { + None(-1, "未设置"), + Load(0, "加载数据"), + Add(1, "添加"), + Update(2, "修改"), + Delete(3, "删除"), + Obsolete(4, "作废"), + EscObsolete(5, "取消作废"), + Submit(21, "提交"), + EscSubmit(22, "取消提交"), + Audit(31, "审核"), + AuditBack(32, "反退"), + AuditClose(33, "关闭审核"), + AuditPress(34, "审核催办"), + AuditPause(35, "审核暂停待办"), + AuditForward(36, "审核转发"), + AuditHand(37, "审核转交"); + + private final int value; + private final String description; + + ActionType(int value, String description) { + this.value = value; + this.description = description; + } + + public int getValue() { + return value; + } + + public String getDescription() { + return description; + } + + public static ActionType fromValue(int value) { + for (ActionType type : ActionType.values()) { + if (type.value == value) { + return type; + } + } + throw new IllegalArgumentException("未知的ActionType值: " + value); + } + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Enums/SystemTypeEnums.java b/WebErp/weberp/src/main/java/org/example/Enums/SystemTypeEnums.java new file mode 100644 index 0000000..d852fd6 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Enums/SystemTypeEnums.java @@ -0,0 +1,77 @@ +package org.example.Enums; + +/** + * 程序类型(对应C#的FieldType枚举) + */ +public class SystemTypeEnums { + + public enum FieldType { + Unknown, + Boolean, + SByte, + Byte, + Int16, + UInt16, + Int32, + UInt32, + Int64, + UInt64, + Decimal, + Float, + Double, + SmallMoney, + Money, + SmallDateTime, + DateTime, + AnsiChar, + AnsiVarChar, + AnsiText, + AnsiVarCharMax, + Char, + VarChar, + Text, + VarCharMax, + Binary, + VarBinary, + Image, + VarBinaryMax, + Variant, + TimeStamp, + Guid + } + + + /** + * 登录类型(对应C#的LoginType枚举,保留指定数值) + */ + public enum LoginType { + CS(1), + Web(2), + Android(3), + IOS(4), + AD(5); + + private final int value; + + // 构造函数指定枚举值 + LoginType(int value) { + this.value = value; + } + + // 获取枚举值(类似C#的枚举默认值访问) + public int getValue() { + return value; + } + } + + + /** + * 替换参数类型(对应C#的PmType枚举) + */ + public enum PmType { + store, + sql, + ignorenull, // 忽略不包含的键 + program + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Enums/TaskType.java b/WebErp/weberp/src/main/java/org/example/Enums/TaskType.java new file mode 100644 index 0000000..9085d24 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Enums/TaskType.java @@ -0,0 +1,65 @@ +package org.example.Enums; + +/** + * 任务类型枚举 + * 对应原C#的 TaskType 枚举,保留所有枚举值、注释和数值映射 + */ +public enum TaskType { + /** + * 待办 + */ + DB(0), + + /** + * 已办 + */ + YB(1), + + /** + * 发起 + */ + FQ(2), + + /** + * 抄送 + */ + CS(3); + + // 枚举对应的数值(对应C#枚举的赋值,如 DB=0) + private final int value; + + /** + * 构造方法:绑定枚举值和对应的数字 + * + * @param value 枚举对应的数值 + */ + TaskType(int value) { + this.value = value; + } + + /** + * 获取枚举对应的数值(如 DB 返回 0,YB 返回 1) + * 对应C#直接访问枚举值的功能 + * + * @return 枚举的数值 + */ + public int getValue() { + return this.value; + } + + /** + * 从数值反向获取枚举(可选扩展,方便根据数据库/接口返回的数字解析枚举) + * + * @param value 枚举数值 + * @return 对应的枚举实例 + * @throws IllegalArgumentException 无匹配值时抛出异常 + */ + public static TaskType fromValue(int value) { + for (TaskType type : TaskType.values()) { + if (type.value == value) { + return type; + } + } + throw new IllegalArgumentException("无效的任务类型数值:" + value); + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/FileUploadApi/controller/FileUploadController.java b/WebErp/weberp/src/main/java/org/example/FileUploadApi/controller/FileUploadController.java new file mode 100644 index 0000000..df60c68 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/FileUploadApi/controller/FileUploadController.java @@ -0,0 +1,778 @@ +package org.example.FileUploadApi.controller; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.Part; +//import org.bytedeco.javacv.FFmpegFrameGrabber; +import org.example.Api.LoggerHandler; +import org.example.Api.OptBaseHandler; +import org.example.Entity.Attributes.RequestCheck; +import org.example.Entity.BaseResponse.BaseResponse; +import org.example.Impl.*; +import org.example.Impl.Sql.factory.AllInOneSqlFactory; +import org.example.Impl.Sql.provider.AllInOneSqlProvider; +import org.example.ModuleApi.ModuleAjaxApi.mapper.CRMapper; +import org.example.ModuleApi.ModuleAjaxApi.mapper.DMCrmMapper; +import org.example.Office.OfficeUtil; +import org.example.Utils.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Scope; +import org.springframework.http.ResponseEntity; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.MultipartHttpServletRequest; +import org.springframework.web.util.WebUtils; + +import java.io.*; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.sql.SQLException; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static org.example.Utils.ExtentionsUtil.getErrMsg; +import static org.example.Utils.FileUtil.*; +import static org.example.Utils.FormParamUtil.GetParamString; +import static org.example.Utils.NativeExtensionUtils.*; +import static org.example.Utils.RequestUtil.*; +import static org.example.Utils.RequestUtil.hasUploadedFiles; +import static org.example.Utils.UrlDecoderUtils.UrlDecode; + +@RestController +@Scope("prototype") +@RequestMapping("/Api/FileUploadApi") +public class FileUploadController extends OptBaseHandler { + private static final Logger log = LoggerFactory.getLogger(FileUploadController.class); + + + @Autowired + private FileUtil fileUtil; + + // 单一接口入口,处理所有请求 + @RequestMapping(value = "/**", method = {RequestMethod.GET, RequestMethod.POST}) + public void handleRequest(HttpServletRequest Request, HttpServletResponse response) throws Exception { + // 调用 BaseHandler 的 processRequest 处理逻辑 + super.processRequest(Request); + } + + // 存储业务方法的处理结果 +// private BaseResponse response; + + @Autowired + private FileImpl FileImpl; + @Autowired + private ModuleImpl moduleImpl; + @Autowired + private WebConfigUtil webConfigUtil; + @Qualifier("baseImpl") + @Autowired + private BaseImpl bImpl; + @Autowired + private JdbcTemplate jdbcTemplate; + @Autowired + private DbOperator dbOperator; + + @Autowired + private DataImpl dataImpl; + + private static ModuleEventImpl geteventManager() { + return ModuleEventImpl.getInstance(); + } + + // 原有的CRM模块参数方法(对应method=getcrmoduleiniparams) + public void DoCheck() { + // 实际业务逻辑 + Map finfos = new HashMap<>(); + + // 向Map中添加键值对,对应C#的集合初始化器 + finfos.put("filename", GetParamString("filename", "")); + finfos.put("menucode", GetParamString("menucode", "")); + finfos.put("key", GetParamString("key", "")); + finfos.put("value", GetParamString("value", "")); + finfos.put("fileNo", GetParamString("fileNo", "")); + // 调用Check方法,获取响应结果 + response = FileImpl.Check(finfos); + } + + public void DoData() { + Map finfos = new HashMap<>(); + + // 向Map中添加键值对,对应C#的集合初始化器 + finfos.put("username", GetParamString("filename", "")); + finfos.put("userid", GetParamString("menucode", "")); + finfos.put("key", GetParamString("key", "")); + finfos.put("value", GetParamString("value", "")); + finfos.put("filename", GetParamString("value", "")); + finfos.put("filesize", GetParamString("value", "")); + finfos.put("fileNo", GetParamString("value", "")); + finfos.put("position", GetParamString("fileNo", "")); + finfos.put("menucode", GetParamString("fileNo", "")); + + HttpServletRequest Request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + InputStream stream = null; + byte[] bytes = null; + try { + stream = Request.getInputStream(); + bytes = new byte[stream.available()]; + stream.read(bytes); + } catch (IOException e) { + log.error("Exception caught", e); + } finally { + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + log.error("Exception caught", e); + } + } + } + response = FileImpl.Save(bytes, finfos); + } + + private void DoComplete() { + Map finfos = new HashMap<>(); + + finfos.put("username", GetParamString("filename", "")); + finfos.put("userid", GetParamString("menucode", "")); + finfos.put("key", GetParamString("key", "")); + finfos.put("value", GetParamString("value", "")); + finfos.put("filename", GetParamString("value", "")); + finfos.put("filesize", GetParamString("value", "")); + finfos.put("fileNo", GetParamString("value", "")); + finfos.put("menucode", GetParamString("fileNo", "")); + response = FileImpl.AddFileInfo(finfos); + } + + protected void CheckAttcOper(int oper, String filename, long totsize, String specNo, int fileId) throws SQLException { + if (getUser().IsClientUser) { + response.setSuccess(true); + } else { +// DataImpl dataImpl = new DataImpl(jdbcTemplate); + response = dataImpl.GetAttcOperInfo(oper, filename, "", totsize, getUser().UserId, getUser().UserName, specNo, fileId, "", "", 0); + } + } + + protected void CheckAttcOper(int oper, String filename, long totsize, String specNo, int fileId, String moduleId, String idValue) throws SQLException { +// DataImpl dataImpl = new DataImpl(jdbcTemplate); + response = dataImpl.GetAttcOperInfo(oper, filename, "", totsize, getUser().UserId, getUser().UserName, specNo, fileId, moduleId, idValue, 0); + } + + protected void CheckAttcOper(int oper, String filename, long totsize, String specNo, int fileId, String moduleId, String idValue, int comfirm) throws SQLException { +// DataImpl dataImpl = new DataImpl(jdbcTemplate); + response = dataImpl.GetAttcOperInfo(oper, filename, "", totsize, getUser().UserId, getUser().UserName, specNo, fileId, moduleId, idValue, comfirm); + } + + @RequestCheck(CheckLogin = true) + public void DoWebUpload() throws SQLException, UnsupportedEncodingException, UnsupportedEncodingException { + + Request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true); + if (Request != null) { + Request.setCharacterEncoding("UTF-8"); + } + + String filename = decodeFileName(FileImpl.Request("filename", "")).replace(",", ","), + folder = FileImpl.Request("folder", "file"), + uFolder = FileImpl.Request("uFolder", ""); + String fileUrl = FileImpl.Request("url"); + log.debug(String.valueOf("filename : " + filename)); + boolean uname = toBoolean(FileImpl.Request("uname", "")), + gzip = toBoolean(FileImpl.Request("gzip", "")); + int comfirm = ToInt32(FileImpl.Request("comfirm", "")), + encode = ToInt32(FileImpl.Request("encode", "")), + existDelay = ToInt32(FileImpl.Request("existDelay", "")); + + long position = ToInt64(FileImpl.Request("position", "")), + totsize = ToInt64(FileImpl.Request("totsize", FileImpl.Request("filesize", ""))); + + String idValue = FileImpl.Request("idValue", ""), + specNo = FileImpl.Request("speciesno", ""), + stepCode = FileImpl.Request("stepCode", ""), + fileNo = FileImpl.Request("fileNo", ""); + boolean downLoadFile = !isNullOrEmpty(fileUrl) && fileUrl.startsWith("http"); + + if (isNullOrEmpty(specNo) || specNo.equalsIgnoreCase("false")) { +// DataImpl dataImpl = new DataImpl(jdbcTemplate); + // 此时 dataImpl 中的 jdbcTemplate 已初始化,可正常调用 + specNo = dataImpl.GetAttcBmpSpec(ModuleId); + specNo = isNullOrEmpty(specNo) ? "01" : specNo; + } + if (!isNullOrEmpty(idValue) && idValue.startsWith("{") && idValue.endsWith("}")) { + response.setSuccess(false); + response.setMsg("请选择一条记录进行上传!"); + return; + } + + Map finfos = new HashMap<>(); + finfos.put("username", getUser().UserName); + finfos.put("userid", getUser().UserId); + finfos.put("key", GetParamString("key", "")); + finfos.put("filename", filename); + finfos.put("filesize", totsize); + finfos.put("specNo", specNo);//文档左边树节点编号,可省略,默认模块bmpspec + finfos.put("fileNo", fileNo); + finfos.put("comfirm", comfirm); + finfos.put("stepCode", stepCode);//如果是附件,那么单据审批的步骤码,未审批就是0 + finfos.put("value", idValue);//如果是附件,那么就是记录id,如果是帮助文档,那么就是模块moduleId + finfos.put("menucode", ModuleId);//如果是附件,那么就是当前模块moduleId,如果是帮助文档,那么就是帮助文档的moduleId(2005_1 + + boolean isAttc = !isNullOrEmpty(ModuleId); + String[] dirTabId = new String[1]; + dirTabId[0] = ""; + String fileVPath = isAttc +// ? StringFormat.format(WebConfigUtil.PubModelAccFilePath, ModuleId, idValue, fileNo) + ? dataImpl.GetAcFileFolder(ModuleId, idValue, specNo, dirTabId) + : folder.startsWith("/") ? folder : StringFormat.format(WebConfigUtil_web.getPubModelFilePath(), folder, ModuleId, DateTimeFormatter.ofPattern("yyyyMMdd") + (isNullOrEmpty(uFolder) ? "" : "/" + uFolder)); + finfos.put("filePath", fileVPath); + finfos.put("dirTabId", dirTabId); + if (isAttc && (hasUploadedFiles(Request) && encode != 4 || (position == 0 && totsize > 0))) { +// String _filename = moduleImpl.GetAttcFileName(ModuleId, idValue, filename); + String _filename = filename; + if (!NativeExtensionUtils.isNullOrEmpty(_filename)) { + filename = _filename; + finfos.put("renamed", true); + finfos.put("filename", filename); + } else { + //这里对齐c#的判断中的部分 + String localPath = Paths.get(WebConfigUtil.getFilePath(), fileVPath).toString(); + Path fileNamePath = Paths.get(filename).getFileName(); + String fileName = fileNamePath == null ? "" : fileNamePath.toString(); + filename = toBoolean(WebConfigUtil.get("NAttcReName", "0")) ? filename + : NativeExtensionUtils.isNullOrEmpty(fileNo) ? FileUtil.CreateAttcFileName(localPath, fileName) + : String.format("%s_%s", fileNo, fileName); + finfos.put("filename", filename); + } +// if (isAttc) { + + PathInfo pathInfo = new PathInfo(); + pathInfo.ModuleId = ModuleId; // ModuleId 属性 = 变量 ModuleId 的值 + pathInfo.IdValue = idValue; // IdValue 属性 = 变量 idValue 的值 + pathInfo.SpecNo = specNo; // SpecNo 属性 = 变量 specNo 的值 + pathInfo.TotSize = totsize; // TotSize 属性 = 变量 totsize 的值 + pathInfo.StepCode = stepCode; // StepCode 属性 = 变量 stepCode 的值 + pathInfo.FileName = filename; // FileName 属性 = 变量 filename 的值 + pathInfo.OldFileName = filename; // OldFileName 属性 = 变量 filename 的值(与FileName相同) + geteventManager().callBeforeUploadFile(pathInfo, null); + if (comfirm != 1 && moduleImpl.CheckHasAttcFile(ModuleId, idValue, specNo, filename)) { + + response.setSuccess(false); + response.setMsg(String.format("文件%s已存在,是否覆盖?", filename)); + response.setData(9); + return; + } + CheckAttcOper(1, filename, totsize, specNo, 0, ModuleId, idValue, comfirm); + if (!response.isSuccess()) { + return; + } + } +// if (isAttc && (hasUploadedFiles(Request) || (position == 0 && totsize > 0))) { +// CheckAttcOper(1, filename, totsize, specNo, 0, ModuleId, idValue, comfirm); +// if (!response.isSuccess()) { +// return; +// } +// } + String _debugc = ""; + + + try { + if (hasUploadedFiles(Request) && encode != 4) { + log.debug(String.valueOf("是否进入if,当文件是纯二进制流的时候")); +// 该if进不去(处理纯二进制流文件时) + ArrayList resultArray = new ArrayList<>(); + for (int i = 0; i < getUploadedFileCount(Request); i++) { + MultipartFile file = ((MultipartHttpServletRequest) Request).getFiles("file").get(i); + BaseResponse res = saveFile((MultipartFile) file, fileVPath, uname ? null : file.getOriginalFilename(), "", true); + if (res.isSuccess()) { + finfos.put("fPath", ((PathInfo) res.getData()).RelativePath); + if (file != null) { + finfos.put("filename", filename = file.getOriginalFilename()); + } + if (isAttc) { + res = moduleImpl.AddAttcFileInfo(finfos); + } + if (res.isSuccess()) { + resultArray.add(res.getData()); + PathInfo pInfo = null; + if (response.getData() instanceof PathInfo) { + pInfo = (PathInfo) response.getData(); + } + String savePath = ""; + StringBuilder vpath = new StringBuilder(); + String htmlPath = WebConfigUtil_web.getViewDocHtmlPath(savePath, vpath); + if (new File(htmlPath).exists()) { + new File(htmlPath).delete(); + } + } else { + deleteFile(PathUtil.combine(fileVPath, filename), ""); + } + } + } + if (resultArray.size() > 0) { + response.setSuccess(true); + response.setData(resultArray.size() > 1 ? resultArray : resultArray.get(0)); + } else { + response.setSuccess(false); + response.setMsg("上传失败!"); + } + } else { + String content = ""; + byte[] buf = new byte[0]; + if (encode == 1) { + content = bImpl.Request("data", ""); + if (gzip) { + byte[] buff = Base64.getDecoder().decode(content); + content = GZipUtil.unZip(buff); + } + buf = Base64.getDecoder().decode(content); + } else if (encode == 2) { + //this.Debug($"接收到文件内容长度{Request.TotalBytes},tot:{totsize}"); + int len = getTotalBytes(Request); + InputStream inputStream = getInputStream(Request); + if (len > totsize) { + InputStreamReader reader = new InputStreamReader(inputStream, Request.getCharacterEncoding()); + // 直接通过字节流读取并指定编码(一行代码) + //content = new String(inputStream.readAllBytes(), Request.getCharacterEncoding() != null ? Request.getCharacterEncoding() : StandardCharsets.UTF_8.name()); + String charset = Request.getCharacterEncoding() != null ? Request.getCharacterEncoding() : StandardCharsets.UTF_8.name(); + byte[] contentBytes = ((ByteArrayInputStream) inputStream).readAllBytes(); + content = new String(contentBytes, charset); + response.setMsg("上传数据流错误,请联系开发者并提供web地址!"); + response.setSuccess(false); + } else { +// buf = new byte[len]; +// inputStream.read(buf, 0, buf.length); + buf = ((ByteArrayInputStream) inputStream).readAllBytes(); + } + } else if (encode == 3) { + InputStream stream = getInputStream(Request); + String charset = Request.getCharacterEncoding() != null ? Request.getCharacterEncoding() : StandardCharsets.UTF_8.name(); +// InputStreamReader reader = new InputStreamReader(stream, charset); +// content = _debugc = new String(stream.readAllBytes(), charset); + byte[] contentBytes = ((ByteArrayInputStream) stream).readAllBytes(); + content = _debugc = new String(contentBytes, charset); + + // 新增:空值/空白校验 + if (content == null || content.trim().isEmpty()) { + response.setSuccess(false); + response.setMsg("encode=3 上传数据为空,请检查前端提交内容,并重新上传!"); + return; + } + // 分割并过滤空元素 + String[] rawParts = content.split(","); + List validParts = new ArrayList<>(); + for (String part : rawParts) { + String trimmedPart = part.trim(); + if (!trimmedPart.isEmpty()) { + validParts.add(trimmedPart); + } + } + if (validParts.isEmpty()) { + throw new RuntimeException("encode=3 上传数据为空,原始内容:" + content); + } + + // 转换字节数组 + buf = new byte[validParts.size()]; + for (int i = 0; i < validParts.size(); i++) { + buf[i] = convertToByte(validParts.get(i)); + } + } else if (encode == 4) { + if (hasUploadedFiles(Request)) { + // Java中替换C#的MemoryStream + using + CopyTo逻辑 + MultipartFile file = ((MultipartHttpServletRequest) Request).getFiles("file").get(0); + try (InputStream inputStream = file.getInputStream(); + ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + // 复制流到内存(等效C#的InputStream.CopyTo(ms)) + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + baos.write(buffer, 0, bytesRead); + } + // 转字节数组(等效C#的ms.ToArray()) + buf = baos.toByteArray(); + } catch (IOException e) { + // 增加异常处理,避免崩溃 + response.setSuccess(false); + response.setMsg("读取文件流失败:" + e.getMessage()); + return; + } + } else//ios低版本传递不了文件 + { + response.setSuccess(false); + response.setData(-1); + return; + } + } else if (encode == 5) { + InputStream stream = null; + try { + stream = getInputStream(Request); + // 读取Request输入流的全部内容(替换C#的StreamReader.ReadToEnd) + content = new String(stream.readAllBytes(), + Request.getCharacterEncoding() != null ? Request.getCharacterEncoding() : StandardCharsets.UTF_8.name()).trim(); + + // 处理0x前缀(替换C#的StringComparison.OrdinalIgnoreCase) + if (content.startsWith("0x") || content.startsWith("0X")) { + content = content.substring(2); + } + // 补位(长度为奇数时前面加0) + if (content.length() % 2 != 0) { + content = "0" + content; + } + + // 替换C#的Enumerable.Range + Select(16进制字符串转字节数组) + buf = new byte[content.length() / 2]; + for (int i = 0; i < buf.length; i++) { + // 截取2位16进制字符串,转字节(替换C#的Convert.ToByte(...,16)) + String hexStr = content.substring(i * 2, i * 2 + 2); + buf[i] = (byte) Integer.parseInt(hexStr, 16); + } + } catch (IOException e) { + response.setSuccess(false); + response.setMsg("读取16进制流失败:" + e.getMessage()); + return; + } finally { + // 关闭流(避免资源泄漏) + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + log.error("Exception caught", e); + } + } + } + } else if (!downLoadFile) { + InputStream stream = getInputStream(Request); + InputStreamReader reader = new InputStreamReader(stream, Request.getCharacterEncoding()); + content = new String(stream.readAllBytes(), Request.getCharacterEncoding() != null ? Request.getCharacterEncoding() : StandardCharsets.UTF_8.name()); + buf = Base64.getDecoder().decode(content); + } + PathInfo pInfo = null; + + if (buf != null && buf.length > 0 || downLoadFile) { +// response = saveFile(buf, position, totsize, fileVPath, filename, FileImpl.getAppDomain(), !isAttc, comfirm == 1 || isAttc ? 1 : 0, isAttc ? "" : FileImpl.getAttcPath()); + response = saveFile(buf, position, totsize, fileVPath, filename, FileImpl.getAppDomain(), !isAttc, comfirm == 1 || isAttc ? 1 : 0, FileImpl.getAttcPath(), fileUrl); + pInfo = (PathInfo) response.getData(); + if (pInfo != null) { + pInfo.UserName = getUser().UserName; + } + } + if (isAttc && response.isSuccess() && (position + buf.length >= totsize || downLoadFile)) { + if (pInfo != null) { + //【2】这里的data获得保存的 路径 + finfos.put("fPath", pInfo.RelativePath); +// finfos.put("totTime", OfficeUtil.getVideoInfo(pInfo.SavePath)); + if (isNullOrEmpty(filename)) { + finfos.put("filename", pInfo.FileName); + } + } + response = moduleImpl.AddAttcFileInfo(finfos); + log.debug(String.valueOf("data类型: " + response.getData().getClass().getName())); + // 适配pInfo = response.data as FileUtil.PathInfo; +// pInfo = (FileUtil.PathInfo) response.getData(); + if (response.getData() instanceof FileUtil.PathInfo) { + // 如果是 PathInfo 类型,安全转换 + pInfo = (FileUtil.PathInfo) response.getData(); + // 后续使用 pInfo(非 null) + } else if (response.getData() instanceof Integer) { + pInfo = null; + } + + String savePath = ""; + if (pInfo != null) { + savePath = pInfo.SavePath; + } + if (!response.isSuccess()) { + if (pInfo != null) { + deleteFile(savePath, FileImpl.getAttcPath()); + } + } else { + StringBuilder vpath = new StringBuilder(); + String htmlPath = WebConfigUtil_web.getViewDocHtmlPath(savePath, vpath); + File file = new File(htmlPath); + if (file.exists()) { + boolean isDeleted = file.delete(); // 删除文件 + if (!isDeleted) { + // 可选:处理删除失败的情况 + log.debug(String.valueOf("文件删除失败: " + htmlPath)); + } + } + } + if (response.isSuccess() && existDelay > 0 && position + buf.length >= totsize) { + String finalFilename = filename; + DateTimeUtil.setTimeOut(() -> { + deleteFile(PathUtil.combine(fileVPath, finalFilename), getAttcPath()); + }, existDelay * 1000); + } + + } + } + } catch (Exception e) { + LoggerHandler.error(this, String.format("上传错误(模块:%s,主键:%s):%s", ModuleId, idValue, getErrMsg(e, true))); + + // 2. 设置友好的响应信息(替换通用提示) + String detailMsg = e.getMessage().contains("unmappable characters") ? String.format("文件名称/路径包含特殊字符,%s,%s,%s,请修改后重新上传", fileVPath, filename, FileImpl.getAttcPath()) : + e.getMessage().contains("Connection leak") ? "数据库连接异常,请稍后重试" : + e.getMessage().contains("readAllBytes") ? "浏览器兼容问题,请使用Chrome/Firefox上传" : + e.getMessage().contains("权限") ? "服务器文件权限不足,请联系管理员" : + getErrMsg(e); + response.setData(-1); + response.setMsg(String.format("上传失败!%s,详细的错误: %s", detailMsg, e.getMessage())); + response.setSuccess(false); + // 输出调试信息(对应 C# 的 this.Debug) +// LoggerHandler.debug(this, String.format("数据流:%s=>%s", encode, _debugc)); +// +// // 输出错误信息(对应 C# 的 this.Error) +// LoggerHandler.error(this, String.format("上传错误:%s", getErrMsg(e, true))); +// +// // 设置响应对象信息 +// response.setData(-1); +// response.setMsg(String.format("上传错误!%s", getErrMsg(e))); +// response.setSuccess(false); +// throw new RuntimeException(e); + } finally { + // 12. 清理缓存(避免内存泄漏) + RequestUtil.clearInputStreamCache(); + } + } + + public void MoveFileTo() { + String filename = bImpl.Request("filename"); + String filepath = bImpl.Request("filepath", "file"); + String newdir = bImpl.Request("newdir", ""); + String newname = bImpl.Request("newname", ""); + String specno = bImpl.Request("specno", ""); + String sourceId = bImpl.Request("sourceId", ""); + String targetId = bImpl.Request("targetId", ""); + + Map finfos = new HashMap<>(); + finfos.put("filepath", filepath); + finfos.put("newdir", newdir); + finfos.put("newname", newname); + finfos.put("filename", filename); + finfos.put("typeid", bImpl.Request("typeid", "1")); + finfos.put("ver", bImpl.Request("ver", "")); + finfos.put("specno", specno);//附件目录节点编号,方便在目录节点之间移动,目录由节点生成那种 + finfos.put("svalue", sourceId);//如果是附件,那么就是记录id,如果是帮助文档,那么就是模块moduleId + finfos.put("tvalue", targetId);//如果是附件,那么就是记录id,如果是帮助文档,那么就是模块moduleId +// 暂时用不了BaseHandler中的定义参数 +// finfos.put("menucode", ModuleId);//如果是附件,那么就是当前模块moduleId,如果是帮助文档,那么就是帮助文档的moduleId(2005_1) + +// response = moduleImpl.MoveAttcFileInfo(finfos); + } + + @RequestCheck(CheckLogin = true, CheckParams = "ModuleId,idValue,urls") + public void DownLoadFiels() throws SQLException { + String[] urls = (bImpl.Request("urls") != null) ? bImpl.Request("urls").split(";") : new String[0]; + String idValue = bImpl.Request("idValue"); + String speciesno = bImpl.Request("speciesno"); + ArrayList fullUrls = new ArrayList<>(); +// DataImpl dataImpl = new DataImpl(dbOperator); + boolean hasA = true; + for (int i = 0; i < urls.length; i++) { + String u = UrlDecode(urls[i]).split("\\?")[0]; + if (isNullOrEmpty(u)) continue; + String filename = u.replace("/", "\\").split("\\\\")[new String(u.replace("/", "\\")).split("\\\\").length - 1]; +// System.out.println("ModuleId : " + ModuleId); +// System.out.println("idValue : " + idValue); +// System.out.println("filename : " + filename); + if (!isNullOrEmpty(ModuleId) && !isNullOrEmpty(idValue) && ModuleId != "-1") { +// System.out.println("123456789"); + response = dataImpl.GetAttcOperInfo(2, filename, dataImpl.GetAttcParentId(ModuleId, idValue), 0, getUser().UserId, getUser().UserName, speciesno, 0, ModuleId, idValue, 0); + if (!response.isSuccess()) { + hasA = false; + continue; + } + } + StringBuilder vpth = new StringBuilder(); + String path = toAbsPath(u, FileImpl.getAttcPath(), vpth)[0]; + if (FileUtil.checkFileAuthory(path, getAttcPath())) { + fullUrls.add(path); + } + } + String zipDict = String.format("%s/downLoadTemp", WebConfigUtil.getFilePath()); + String zipVPath = StringFormat.format("/downLoadTemp/{0}.zip", new java.text.SimpleDateFormat("yyyyMMddhhmmss").format(new java.util.Date())); + String zipPath = String.format("%s/%s", WebConfigUtil.getFilePath(), zipVPath); + + // 创建 File 对象表示该目录 + File dir = new File(zipDict); + +// 判断目录是否不存在:!dir.exists() 表示路径不存在;!dir.isDirectory() 表示路径存在但不是目录 + if (!dir.exists() || !dir.isDirectory()) { + // 创建目录(包括所有不存在的父目录) + dir.mkdirs(); + } + if (fullUrls.size() <= 0) { + response.setSuccess(false); + response.setMsg(String.format( + "没有可下载的文件%s", + hasA ? "" : String.format(",人员对模块%s的下载权限不足,请检查!", ModuleId) + )); + return; + } + ZipUtil.GoZip(fullUrls.stream().collect(Collectors.joining(";")), zipPath, ""); + DateTimeUtil.setTimeOut(() -> { + try { + File zipFile = new File(zipPath); + if (zipFile.exists()) { // 对应 C# File.Exists(zipPath) + boolean isDeleted = zipFile.delete(); // 对应 C# File.Delete(zipPath) + if (isDeleted) { + log.debug(String.valueOf("文件已成功删除:" + zipPath)); + } else { + log.warn(String.valueOf("文件删除失败(可能被占用或无权限):" + zipPath)); + } + } else { + log.debug(String.valueOf("文件不存在,无需删除:" + zipPath)); + } + } catch (Exception e) { + // 捕获异常,避免影响主线程 + log.warn(String.valueOf("任务执行失败:" + e.getMessage())); + } + }, 600000); + response.setSuccess(true); + response.setData(StringFormat.format("/{0}/{1}", WebConfigUtil.getFileVPath(), zipVPath)); + } + + @RequestCheck(CheckLogin = true, Name = "删除附件", CheckParams = "filename|fileid") + public void DoDelete() throws SQLException, UnsupportedEncodingException { + String folder = UrlDecode(bImpl.Request("folder", "file")); + String idValue = bImpl.Request("idValue"); + String fileNo = bImpl.Request("fileNo"); + String filename = UrlDecode(bImpl.Request("filename")); + String speciesno = bImpl.Request("speciesno"); + String fileId = bImpl.Request("fileId"); + String fName = Arrays.stream(UrlDecode(bImpl.Request("filename")).split("/")).reduce((a, b) -> b).orElse(""); + CheckAttcOper(3, fName, 0, speciesno, ToInt32(fileId), ModuleId, idValue); + if (!response.isSuccess()) { + response.setMsg(isNullOrEmpty(response.getMsg()) ? "没有操作权限!" : response.getMsg()); + return; + } + folder = isNullOrEmpty(folder) ? filename.replace(fName, "") : folder; + if (!isNullOrEmpty(getAppDomain())) { + folder = folder.replace(getAppDomain(), ""); + } + Map finfos = new HashMap<>(); + finfos.put("filename", filename); + finfos.put("menucode", ModuleId); + finfos.put("key", bImpl.Request("key")); + finfos.put("value", idValue); + finfos.put("fileNo", fileNo); + + boolean isAttc = !isNullOrEmpty(ModuleId) || !isNullOrEmpty(fileId); + if (isAttc) { + if (!isNullOrEmpty(fileId)) { + log.debug(String.valueOf("1111")); + response = moduleImpl.DelAttcFile(fileId); + if (isNullOrEmpty(response.getOther() + "") && !isNullOrEmpty(fName))//第一次未删除到文件,再次删除 + { + response = moduleImpl.DelAttcFile(ModuleId, idValue, speciesno, folder, fName); + } + } else { + log.debug(String.valueOf("333333")); + response = moduleImpl.DelAttcFile(ModuleId, idValue, speciesno, folder, fName); + } + // DoDelete(); + } else if (!isNullOrEmpty(Trim(fName))) { + // string fileVPath =folder.StartsWith("/")?folder: String.Format(Web.Core.Util.WebConfigUtil.PubModelFilePath, folder, ModuleId,DateTime.Now.ToString("yyyyMMdd")); + //response = FileUtil.DeleteFile(fileVPath, filename); + response = FileUtil.deleteFile(PathUtil.combine(folder, fName), getAttcPath()); + } else { + response.setSuccess(true); + } + } + + @RequestCheck(Name = "保存显示模式", CheckParams = "ModuleId") + public void SaveViewModule() { + boolean succession = dataImpl.SaveAttcViewModule(ModuleId, getUserId(), Integer.parseInt(bImpl.Request("vtype"))); + response.setSuccess(succession); + } + +// public static long GetVideoInfo(String filePath) { +// File fileInfo = new File(filePath); +// String types = ".avi,.wmv,.mpeg,.mp4,.m4v,.mov,.asf,.flv,.f4v,.rmvb,.rm,.3gp,.vob"; +// if (!isNullOrEmpty(fileInfo.getName().lastIndexOf('.')) && types.indexOf(fileInfo.getName().lastIndexOf('.')) > -1) { +// FFmpegFrameGrabber inputFile = new FFmpegFrameGrabber(filePath); +// try { +// // 启动抓取器(相当于 Engine 初始化并准备处理) +// inputFile.start(); +// +// // 获取元数据(对应 engine.GetMetadata(inputFile)) +// System.out.println("媒体元数据:"); +// System.out.println("文件路径: " + filePath); +// System.out.println("格式: " + inputFile.getFormat()); +// System.out.println("时长: " + inputFile.getLengthInTime() + " 毫秒"); +// System.out.println("视频宽度: " + inputFile.getImageWidth()); +// System.out.println("视频高度: " + inputFile.getImageHeight()); +// System.out.println("帧率: " + inputFile.getFrameRate()); +// System.out.println("音频采样率: " + inputFile.getSampleRate()); +// +// } catch (IOException e) { +// System.err.println("获取媒体元数据失败: " + e.getMessage()); +// e.printStackTrace(); +// } finally { +// // 释放资源(对应 using 语句的自动释放) +// try { +// inputFile.stop(); +// inputFile.release(); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// } +// return ToInt32(inputFile.getLengthInTime() / 10000000); +// } else { +// return 0; +// } +// } + + // 辅助方法 等效于 C# 的 Convert.ToByte(c) + public static byte convertToByte(String c) { + if (c == null) { + log.warn(String.valueOf("convertToByte 输入为null")); + throw new NumberFormatException("输入不能为null"); + } + + String trimmed = c.trim(); + if (trimmed.isEmpty()) { + log.warn(String.valueOf("convertToByte 输入为空字符串,原始值:" + c)); + throw new NumberFormatException("输入不能为空字符串"); + } + try { + int value = Integer.parseInt(trimmed); + // 严格检查范围 + if (value < 0 || value > 255) { + log.warn(String.valueOf("convertToByte 值超出范围:" + value + "(原始值:" + c + ")")); + throw new NumberFormatException("值超出范围:" + value + "(必须在 0~255 之间)"); + } + return (byte) value; + } catch (NumberFormatException e) { + log.warn(String.valueOf("convertToByte 转换失败,原始值:" + c + ",异常:" + e.getMessage())); + throw e; // 保留异常,但增加日志便于排查 + } + } + + private String decodeFileName(String encodedFilename) { + if (encodedFilename == null || encodedFilename.isEmpty()) { + return encodedFilename; + } + + if (encodedFilename.contains("%")) { + return URLDecoder.decode(encodedFilename, StandardCharsets.UTF_8); + } + return encodedFilename; + } + + +} + diff --git a/WebErp/weberp/src/main/java/org/example/Impl/BaseImpl.java b/WebErp/weberp/src/main/java/org/example/Impl/BaseImpl.java new file mode 100644 index 0000000..b6cb03f --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Impl/BaseImpl.java @@ -0,0 +1,491 @@ +package org.example.Impl; +/** + * 功能描述:BaseImpl 所有逻辑业务类的基础类 + */ + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +import org.example.Api.RequestHandler; +import org.example.Entity.System.LoginUserInfo; +import org.example.Enums.SystemTypeEnums; +import org.example.Impl.Sql.factory.AllInOneSqlFactory; +import org.example.Impl.Sql.provider.AllInOneSqlProvider; +import org.example.ModuleApi.ModuleAjaxApi.mapper.CRMapper; +import org.example.ModuleApi.ModuleAjaxApi.mapper.DMCrmMapper; +import org.example.Utils.ConfigUtil; +import org.example.Utils.DbOperator; +import org.example.Utils.WebConfigUtil_web; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + + +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; + +import jakarta.servlet.http.HttpSession; // Jakarta EE 9+ 版本(如Spring Boot 3+) +import org.example.Utils.JwtHelp; + +import static org.example.Utils.NativeExtensionUtils.isNullOrEmpty; +import static org.example.Utils.NativeExtensionUtils.toBoolean; + +@Service +public class BaseImpl { + + @Autowired + protected JdbcTemplate jdbcTemplate; + + + @Value("${custom.database.type}") + protected String databaseType; + + + @Autowired + AllInOneSqlFactory allInOneSqlFactory; + + @Autowired + protected CRMapper crmapper; + + @Autowired + protected DMCrmMapper DMCrmMapper; + + + + + public HttpServletRequest getCtx() { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + return attributes != null ? attributes.getRequest() : null; + } + + @Autowired + private DbOperator dbOperator; + public boolean alwaysNewDbOper = false; + + /** + * 数据库操作对象 + */ + public DbOperator getDbOperator() { + if (dbOperator == null || alwaysNewDbOper) { + dbOperator = getNewOperater(); + return dbOperator; + } + return dbOperator; + } + + public void setDbOperator(DbOperator dbOperator) { + this.dbOperator = dbOperator; + } + + public DbOperator getOperater() { + return getDbOperator(); + } + + public DbOperator getNewOperater() { + return new DbOperator(jdbcTemplate); + } + + private String connectionString; + + /** + * 账套链接字符串 + */ + protected String getConnectionString() { + if (isNullOrEmpty(connectionString) && user != null && !"0".equals(user.UserId) + && !isNullOrEmpty(user.ConnectionString)) { + connectionString = user.ConnectionString; + } + return connectionString; + } + + protected String providerName; + + public String getProviderName() { + return providerName; + } + + public void setProviderName(String providerName) { + this.providerName = providerName; + } + + /** + * 核心:根据 providerName 映射 JDBC 驱动类名(支持主流数据库) + * 示例:providerName="sqlserver" → 驱动类=com.microsoft.sqlserver.jdbc.SQLServerDriver + */ + protected String getJdbcDriverByProvider() { + getLog().debug(String.valueOf("getJdbcDriverByProvider正式进入")); + getLog().debug(String.valueOf("正式进入"+ConfigUtil.getProviderName())); + // 先校验 providerName 是否已通过 setter 初始化 + if (ConfigUtil.getProviderName() == null ||ConfigUtil.getProviderName().trim().isEmpty()) { + getLog().debug(String.valueOf("进入抛出错误")); + throw new RuntimeException("providerName 未初始化,请先调用 setProviderName 方法设置!"); + } + // 映射关系(可根据你的实际数据库扩展,比如mysql、oracle) + Map driverMap = new HashMap<>(); + driverMap.put("sqlserver", "com.microsoft.sqlserver.jdbc.SQLServerDriver"); // SQL Server + driverMap.put("mysql", "com.mysql.cj.jdbc.Driver"); // MySQL 8.0+ + driverMap.put("oracle", "oracle.jdbc.driver.OracleDriver"); // Oracle + driverMap.put("dm", "dm.jdbc.driver.DmDriver"); + driverMap.put("kingbase", "com.kingbase8.Driver"); + getLog().debug(String.valueOf("getJdbcDriverByProvider内部通过0")); + // 1. 优先用完整的 providerName 匹配(如果直接传入驱动类名) + if (ConfigUtil.getProviderName() != null && ConfigUtil.getProviderName().contains(".")) { + getLog().debug(String.valueOf("getJdbcDriverByProvider内部通过1")); + return ConfigUtil.getProviderName(); + } + getLog().debug(String.valueOf("当前providerName值:" + ConfigUtil.getProviderName() + ",转小写后:" + (ConfigUtil.getProviderName() != null ? ConfigUtil.getProviderName().toLowerCase() : "null"))); + // 2. 用简写匹配(如 "sqlserver") + String driverClass = driverMap.getOrDefault(ConfigUtil.getProviderName().toLowerCase(), null); + getLog().debug(String.valueOf("getJbdc内部通过2")); + if (driverClass == null) { + throw new RuntimeException("未找到 providerName 对应的 JDBC 驱动!providerName=" +ConfigUtil.getProviderName()); + } + return driverClass; + } + + + private Boolean windowsDirver; + + public boolean isWindowsDirver() { + if (windowsDirver == null) { + HttpServletRequest ctx = getCtx(); + windowsDirver = (ctx != null) ? toBoolean(Request("windowsDirver", "1")) : true; + } + return windowsDirver; + } + + public void setWindowsDirver(boolean windowsDirver) { + this.windowsDirver = windowsDirver; + } + + private Integer currPage; + + public int getCurrPage() { +// if (currPage == null) { + HttpServletRequest ctx = getCtx(); + currPage = (ctx == null) ? 1 : toInt32(Request("currPage", Request("page", "1"))); +// } + return currPage; + } + + public void setCurrPage(int currPage) { + this.currPage = currPage; + } + + private Integer pageSize; + + public int getPageSize() { +// if (pageSize == null) { + HttpServletRequest ctx = getCtx(); + pageSize = (ctx == null) ? 1 : toInt32(Request("pageSize", Request("limit", "-1"))); +// } + return pageSize; + } + + public void setPageSize(int pageSize) { + this.pageSize = pageSize; + } + + public int getStartsize() { + int start = (getCurrPage() - 1) * getPageSize(); + return Math.max(start, 0); + } + + protected int tot = 0; + + private String userSessionName; + + public String getUserSessionName() { + userSessionName = WebConfigUtil_web.Session_LoginUser; + if (isNullOrEmpty(getAppName())) { + userSessionName = getAppName() + "_" + WebConfigUtil_web.Session_LoginUser; + } + return userSessionName; + } + + private String sessionId; + + public String getSessionId() { + HttpServletRequest ctx = getCtx(); + if (ctx == null || ctx.getSession() == null) { + return ""; + } + if (!isNullOrEmpty(getAppName())) { + sessionId = getAppName() + "_" + ctx.getSession().getId(); + } + return sessionId; + } + + /** + * 附件路径 + */ + public String getAttcPath() { + return WebConfigUtil_web.getFilePath(); + } + + private LoginUserInfo user; + + public LoginUserInfo getUser() { + if (user == null) { + user = new LoginUserInfo(); + } + HttpServletRequest ctx = getCtx(); + if (ctx == null) { + return user; + } + + // 优先使用session验证 + if (getCurrentUser().UserId != "0" && toBoolean(WebConfigUtil_web.get("SharLogin", "1")) + && !isNullOrEmpty(getCurrentUser().ConnectionString)) { + user = getCurrentUser(); + } + if (user == null || "0".equals(user.UserId)) { + try { + // 检查请求上下文是否可用 + ctx.getRequestURI(); + } catch (Exception e) { + getLog().debug(String.valueOf(e.getMessage())); + return user; + } + + String authorization = ctx.getHeader("Authorization"); + authorization = (authorization == null) ? "" : authorization; + if (!isNullOrEmpty(authorization) && authorization.startsWith("Bearer ")) { + user = JwtHelp.validToken(authorization.substring("Bearer ".length()), LoginUserInfo.class); + } + } + + return user != null ? user : new LoginUserInfo(); + } + + public void setUser(LoginUserInfo user) { + this.user = user; + } + + private Logger log; + + /** + * 日志操作对象 + */ + protected Logger getLog() { + if (log == null) { + log = LoggerFactory.getLogger(this.getClass()); + } + return log; + } + + public void sysLog(String content, String type) { + getLog().info(content); + } + + public String getLanguage() { + HttpServletRequest ctx = getCtx(); + if (ctx != null) { + String lang = Request("lg", "CN"); + return (lang).toUpperCase(); + } + return "CN"; + } + + /** + * 获取会话值 + */ + public static Object getSessionVal(String name) { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attributes != null) { + HttpSession session = attributes.getRequest().getSession(false); + if (session != null) { + return session.getAttribute(name); + } + } + return null; + } + + /** + * 设置会话值 + */ + public static void setSessionVal(String name, Object value) { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attributes != null) { + HttpSession session = attributes.getRequest().getSession(); + if (session != null) { + session.setAttribute(name, value); + } + } + } + + public LoginUserInfo getCurrentUser() { + Object userObj = getSessionVal(getUserSessionName()); + if (userObj instanceof LoginUserInfo) { + return (LoginUserInfo) userObj; + } + return new LoginUserInfo() {{ + UserId = ("0"); + }}; + } + + /** + * 超级管理员 + */ + public String userManager = "管理员"; + + public boolean isUserManager() { + return userManager.equals(getUser().UserName); + } + + public String getAppUrl() { + return WebConfigUtil_web.get("appUrl", ""); + } + + private String appName; + + public String getAppName() { + if (isNullOrEmpty(appName)) { + appName = Request("sysAppName"); + } + if (isNullOrEmpty(appName)) { + appName = "Lserp_v8"; + } + return appName; + } + + public void setAppName(String appName) { + this.appName = appName; + } + + private String driverUuId; + + public String getDriverUuId() { + if (driverUuId == null) { + driverUuId = Request("uuid"); + } + return driverUuId; + } + + private String driver; + + public String getDriver() { + if (driver == null) { + driver = (Request("driver") + "").toLowerCase(); + } + return driver; + } + + private String osModel; + + public String getOsModel() { + if (isNullOrEmpty(osModel)) { + osModel = (Request("OsModel") + "").toLowerCase(); + if (isNullOrEmpty(osModel)) { + osModel = isWindowsDirver() ? "web" : "mobileweb"; + } + } + return osModel; + } + + private String appDomain; + + public String getAppDomain() throws UnsupportedEncodingException { + if (appDomain == null) { + appDomain = java.net.URLDecoder.decode(Request("appDomain"), StandardCharsets.UTF_8); + } + return appDomain; + } + + /** + * 是否为移动设备 + */ + public boolean isPhone() { + String driver = getDriver(); + return !isNullOrEmpty(driver) && ("ios".equals(driver) || "android".equals(driver)); + } + + private int appVersion; + + public int getAppVersion() { + if (appVersion <= 0) { + appVersion = toInt32(Request("appVer", "0")); + } + return appVersion; + } + + public static String getAppPhysicsPath() { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attributes != null) { + return attributes.getRequest().getServletContext().getRealPath("/"); + } + return ""; + } + + public SystemTypeEnums.LoginType getLgType() { + return getReqHandler().getLgType(); + } + + //region 接收参数相关 + + private RequestHandler reqHandler; + + public RequestHandler getReqHandler() { + if (reqHandler == null) { + reqHandler = new RequestHandler(); + } + return reqHandler; + } + + public void setReqHandler(RequestHandler reqHandler) { + this.reqHandler = reqHandler; + } + + /** + * 获取表单参数 + */ + public String Request(String key, String defaultVal) { + return getReqHandler().request(key, defaultVal); + } + + public String Request(String key) { + return Request(key, ""); + } + + public String PmsRequest(String key, String defaultVal) { + return getReqHandler().pmsRequest(key, defaultVal); + } + + public Hashtable getAllRequest() { + Hashtable params = new Hashtable<>(); + HttpServletRequest request = getCtx(); + if (request != null) { + Enumeration paramNames = request.getParameterNames(); + while (paramNames.hasMoreElements()) { + String key = paramNames.nextElement(); + params.put(key, request.getParameter(key)); + } + } + return params; + } + + + // 工具方法:字符串转整数 + protected int toInt32(String value) { + return toInt32(value, 0); + } + + protected int toInt32(String value, int defaultValue) { + if (value == null || value.trim().isEmpty()) { + return defaultValue; + } + try { + return Integer.parseInt(value.trim()); + } catch (NumberFormatException e) { + return defaultValue; + } + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Impl/DataImpl.java b/WebErp/weberp/src/main/java/org/example/Impl/DataImpl.java new file mode 100644 index 0000000..2bae9f9 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Impl/DataImpl.java @@ -0,0 +1,6867 @@ +package org.example.Impl; + +import com.zaxxer.hikari.HikariDataSource; +import jakarta.annotation.PostConstruct; +import org.example.Api.LoggerHandler; +import org.example.Entity.BaseResponse.BaseResponse; +import org.example.Entity.Cache; +import org.example.Entity.CusException.CusException; +import org.example.Entity.System.*; +import org.example.Enums.*; +import org.example.Impl.Sql.factory.AllInOneSqlFactory; +import org.example.Impl.Sql.provider.AllInOneSqlProvider; +import org.example.ModuleApi.ModuleAjaxApi.dto.module.ModuleIdFieldDTO; +import org.example.ModuleApi.ModuleAjaxApi.mapper.CRMapper; +import org.example.ModuleApi.ModuleAjaxApi.mapper.DMCrmMapper; +import org.example.Utils.DbOperator; +import org.example.Utils.NativeExtensionUtils; +import org.example.Utils.PublicUtil; +import org.example.Utils.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.*; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.namedparam.SqlParameterSource; +import org.springframework.jdbc.core.simple.SimpleJdbcCall; +import org.springframework.jdbc.datasource.DriverManagerDataSource; +import org.springframework.stereotype.Service; + +import java.sql.*; +import java.text.MessageFormat; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.util.*; +import java.util.Date; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static org.example.Api.LoggerHandler.error; +import static org.example.Utils.ConversionUtils.toLowerColumnName; +import static org.example.Utils.DataTableUtil.get; +import static org.example.Utils.NativeExtensionUtils.*; +import static org.example.Utils.NativeExtensionUtils.isNullOrEmpty; + +@Service +public class DataImpl extends OptBaseImpl { + + // 数据库 + @Value("${custom.database.type}") + private String databaseType; + + private AllInOneSqlProvider sqlProvider; + + @Autowired + AllInOneSqlFactory allInOneSqlFactory; // 注入Spring管理的实例 + + @PostConstruct + public void initSqlProvider() { + // 工厂只创建一次,结果缓存到成员变量sqlProvider中 + this.sqlProvider = allInOneSqlFactory.createProvider(this.databaseType); + } + + public AllInOneSqlProvider getDetailJDBC() { + return this.sqlProvider; + } + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Autowired + private NamedParameterJdbcTemplate namedJdbcTemplate; + // 构造方法注入DbOperator + @Autowired + private CRMapper crmapper; + + @Autowired + private DMCrmMapper DMCrmMapper; + + @Autowired + private DbOperator dbOperator; + + public static Boolean HasServerIdCol = null; + + @Autowired + // 构造方法注入DbOperator + public DataImpl(DbOperator dbOperator) { + this.dbOperator = dbOperator; + } + + public DataImpl(JdbcTemplate dbOperator) { + this.jdbcTemplate = Objects.requireNonNull(dbOperator, "JdbcTemplate must not be null"); + } + + // @Autowired + public DataImpl(JdbcTemplate dbOperator, String databaseType, + AllInOneSqlFactory allInOneSqlFactory, CRMapper crmapper, DMCrmMapper DMCrmMapper) { + super(dbOperator, databaseType, allInOneSqlFactory, crmapper, DMCrmMapper); + this.jdbcTemplate = Objects.requireNonNull(dbOperator, "JdbcTemplate must not be null"); + this.databaseType = databaseType; + this.allInOneSqlFactory = allInOneSqlFactory; + this.sqlProvider = allInOneSqlFactory.createProvider(this.databaseType); + this.crmapper = crmapper; + this.DMCrmMapper = DMCrmMapper; + } + + public DataImpl() { + // 为dbOperator提供默认实现(根据实际情况调整) +// this.dbOperator = new DbOperator(); // 直接创建默认实例 + // 或从全局配置中获取:this.dbOperator = DbOperator.getDefaultInstance(); + } + + + private static Boolean _exitFlowExOper = null; + + private Boolean getExitFlowExOper() { + + if (_exitFlowExOper == null) { +// _exitFlowExOper = toBoolean(jdbcTemplate.queryForObject("select 1 from sysobjects where id = object_id(N'[dbo].[wms_billflowOperex]') and OBJECTPROPERTY(id, N'IsUserTable') = 1", Object.class)); + String sql = this.sqlProvider.getExitFlowExOperSql(); + _exitFlowExOper = toBoolean(jdbcTemplate.queryForList(sql, Object.class)); + } + return _exitFlowExOper; + + } + + /** + * 手动创建 JdbcTemplate,不依赖 Spring 自动配置 + */ + public static JdbcTemplate createManualJdbcTemplate() { + if (System.getProperty("java.version") != null) { + throw new IllegalStateException("JdbcTemplate must be injected; manual datasource fallback is disabled"); + } + // 1. 配置获取 + 非空校验(核心:避免空值导致创建失败) + String dbUrl = ConfigUtil.getConnectionString(); + String driverClassName = WebConfigUtil_web.get("spring.datasource.driver-class-name"); + + // 校验配置是否为空 + if (dbUrl == null || dbUrl.trim().isEmpty()) { + log.error("创建JdbcTemplate失败:dbUrl为空!ConfigUtil.getConnectionString()返回空"); + throw new RuntimeException("数据库URL配置为空,请检查配置文件"); + } + if (driverClassName == null || driverClassName.trim().isEmpty()) { + log.error("创建JdbcTemplate失败:driverClassName为空!WebConfigUtil_web未配置spring.datasource.driver-class-name"); + throw new RuntimeException("数据库驱动类配置为空,请检查配置文件"); + } + + try { + // 2. 使用HikariCP连接池(替代DriverManagerDataSource,解决连接耗尽问题) + HikariDataSource dataSource = new HikariDataSource(); + dataSource.setJdbcUrl(dbUrl); + dataSource.setDriverClassName(driverClassName); + + // 连接池核心配置(按需调整) + dataSource.setMaximumPoolSize(20); // 最大连接数 + dataSource.setMinimumIdle(2); // 最小空闲连接 + dataSource.setIdleTimeout(900000); // 空闲连接超时(5分钟) + dataSource.setConnectionTimeout(120000); // 获取连接超时(30秒) + dataSource.setMaxLifetime(1080000); // 连接最大存活时间(30分钟) + dataSource.setLeakDetectionThreshold(60000); + dataSource.setAutoCommit(true); + dataSource.setConnectionTestQuery("SELECT 1 FROM DUAL"); + // 3. 验证连接是否可用(提前发现数据库连接问题) + dataSource.getConnection().close(); // 尝试获取并关闭连接,验证配置正确性 + + // 4. 关联数据源到JdbcTemplate + JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + log.info("手动创建JdbcTemplate成功,driver: {}", driverClassName); + return jdbcTemplate; + + } catch (SQLException e) { + log.error("创建JdbcTemplate失败:数据库连接失败", e); + throw new RuntimeException("数据库连接失败(URL/用户名/密码错误或数据库未启动)", e); + } catch (Exception e) { + log.error("创建JdbcTemplate失败:未知异常", e); + throw new RuntimeException("创建数据库连接失败,请查看日志", e); + } + } + + /// + /// 是否存在审核代理表 + /// + private static Boolean _exitSystemPrivilegeAgentTab = null; + + private Boolean getExitSystemPrivilegeAgentTab() { + + { + if (_exitSystemPrivilegeAgentTab == null) { +// _exitSystemPrivilegeAgentTab = toBoolean(jdbcTemplate.queryForObject("select 1 from sysobjects where id = object_id(N'[dbo].[p_systemPrivilegeAgentTab]') and OBJECTPROPERTY(id, N'IsUserTable') = 1", Object.class)); + + String sql = this.sqlProvider.getExitSystemPrivilegeAgentTabSql(); + _exitSystemPrivilegeAgentTab = toBoolean(jdbcTemplate.queryForObject(sql, Object.class)); + } + return _exitSystemPrivilegeAgentTab; + } + } + + private static Integer defaultServerId; + + public boolean isDefaultServer() { + // 若默认服务器ID未初始化且用户信息存在,则从数据实现类获取默认服务器ID + if (defaultServerId == null && getUser() != null) { + defaultServerId = GetDefaultServerId(); + } + + // 获取用户服务器ID(转换为int) + int userServerId = NativeExtensionUtils.ToInt32(getUser().ServerId); + + // 判断是否为默认服务器 + return (getServerId() == 0 && userServerId == 0) + || (getServerId() == 0 && userServerId == defaultServerId) + || (getServerId() == defaultServerId); + } + + /** + * 获取默认服务器ID + * + * @return 默认服务器ID + */ + @Cacheable(value = "defaultServerIdCache", key = "'defaultServerId'", unless = "#result == null") + public int GetDefaultServerId() { + // 从配置工具获取数据库连接信息中获取数据库名 + Map dConKeyVal = ConfigUtil.getConnectionKeyVal(); + String dbname = dConKeyVal.containsKey("database") ? dConKeyVal.get("database") : ""; + + // 获取系统数据库组信息 + List> sysDbGroup = GetSysdbGroup(0); + List serverIdList = new ArrayList<>(); + + for (Map row : sysDbGroup) { + String rowDbName = get(row, "dbname", "") + ""; + int dbId = ToInt32(get(row, "id", 0)); + + // 找到匹配的数据库名,返回对应的ID + if (dbname.equals(rowDbName)) { + return dbId; + } + + // 收集有效的服务器ID + if (dbId > 0 && !serverIdList.contains(dbId)) { + serverIdList.add(dbId); + } + } + + return 0; + } + + /** + * 获取系统数据库组信息 + * + * @param id 数据库组ID(可选,0表示查询所有) + * @return 数据库组信息列表(模拟DataTable,使用List表示) + */ + @Cacheable(value = "sysdbGroupCache", key = "#id", unless = "#result == null") + public List> GetSysdbGroup(int id) { +// String sql; +// if (id > 0) { +// // 带ID查询,使用参数化SQL防止注入 +// sql = "select dbname as name, isnull(localIp, ip) as ip, mobileurl, showname as text " + +// "from dbo.p_sydbGroupTab where id = ?"; +// return jdbcTemplate.queryForList(sql, id); +// } else { +// // 查询所有,按orderid排序 +// sql = "select id, showname as text, dbname from dbo.p_sydbGroupTab order by orderid"; +// return jdbcTemplate.queryForList(sql); +// } + + String sql = id > 0 ? this.sqlProvider.getSysdbGroupByIdSql() : this.sqlProvider.getSysdbGroupAllSql(); + + // 3. 执行查询(保持原参数化查询,防止注入) + return id > 0 ? jdbcTemplate.queryForList(sql, id) : jdbcTemplate.queryForList(sql); + } + +// public List> GetRightMenuRows(String fromKey, int menuType, String userName, int menuId) { +// // SQL模板,保持与C#一致的占位符{0}{1}{2} +// String sql = "select orderid, id, privilegeoper, dllname library, action, " + +// "dllpar1 param1, dllpar2 param2, dllpar3 param3, dllpar4 param4, dllpar5 param5, " + +// "dllpar6 param6, dllpar7 param7, dllpar8 param8, dllpar9 param9, dllpar10 param10, " + +// "maxwindow, menuname menucaption, menucond, actiontype, beforemsg, " + +// "ifrefresh refresh, DBClickEvent dbclick, ifMoreClick multi, mergeExec [merge], " + +// "showtoolbar toBar, showMode, isCopy, beforeTab, isnull(isStartRun,0) notRec, " + +// "defailtImage icon, isnull(disabletype,0) disabletype, isnull(hintMsg,'') desp " + +// "from p_systempopupmenu where 1=1 {0} {1} " + // 确保有{0}和{1} +// "and (isnull(privilegeoper,'')='' or charindex(',{2},', ','+privilegeoper+',')>0) " + // 确保有{2} +// "order by orderid asc"; +// +// if (menuId > 0) { +// // 对应C#的menuid > 0分支,直接拼接SQL +// String formattedSql = String.format( +// sql, +// " and id='" + menuId + "'", // 直接拼接menuId(未处理注入风险) +// "", +// userName // 直接拼接userName(未处理注入风险) +// ); +// +// // 执行查询,仅对fromKey使用参数化 +// return jdbcTemplate.queryForList( +// formattedSql.replace("@fromKey", "?"), // 替换为?占位符 +// fromKey // 传递fromKey参数 +// ); +// } else { +// // 构建where条件,与C#逻辑一致 +// StringBuilder whereCond = new StringBuilder("and tab=@fromKey "); +// if (menuType == 0) { // 右键菜单 +// whereCond.append(" AND ISNULL([menutype],0)=0 "); +// } else if (menuType == 1) { // 常用功能 +// whereCond.append(" AND ISNULL([menutype],0)=1 "); +// } +// +// // 处理WindowsDirver条件(假设存在该变量) +// Boolean windowsDirver = toBoolean(Request("windowsDirver", "")); // 模拟C#中的WindowsDirver变量 +// String secondCond = windowsDirver ? "and visible=0" : "and isnull(ShowMobile,0)=1"; +// // 格式化SQL +// String sqlValue = sql +// .replace("{0}", whereCond) +// .replace("{1}", secondCond) +// .replace("{2}", userName); +// String sqlWithParam = sqlValue.replace("@fromKey", "?"); +// // 执行查询,仅对fromKey参数化 +// return toLowerColumnName(jdbcTemplate.queryForList( +// sqlWithParam.replace("@fromKey", "?"), +// fromKey) +// ); +// } +// } + + public List> GetBaseModule(String moduleCode, String menuId) { + String menuid = ToInt32(menuId) + ""; +// return baseModuleMapper.GetBaseModule(moduleCode, menuid); + return getDetailJDBC().GetBaseModule(moduleCode, menuid); + } + + /** + * 获取基础模块信息 + * 对应 C# 的 GetBaseModule 方法,返回 List> 模拟 DataTable + * + * @param moduleCode 模块编码 + * @param menuId 菜单ID + * @return 包含查询结果的 List(每行数据一个 Map,key=列名,value=列值) + */ + public List> GetBaseModule_new(String moduleCode, String menuId) { + // 1. 构建动态 WHERE 条件(对应原代码的 menuCond) + String menuCond; + if (isNullOrEmpty(menuId) || "0".equals(menuId)) { + menuCond = " dll.DllCoid=m.UrlParams "; + } else { + menuCond = " dll.DllCoid=m.UrlParams and m.MenuId=:menuId "; // 命名参数 :menuId + } + + // 2. 构建完整 SQL(对齐原 SQL,替换 SQL Server 语法为通用语法) + String sql = String.format(""" + select isnull(dll.bs_adddllname,'') adddllname, + isnull(dll.CsHasDefultSearch,0) defaultSearch, + a.column_prefix MenuPrefix, + dll.formkey fromkey, + condkey, + case when isnull(dll.displayRows,0)=-1 then 0 else 1 end pagerflag, + selfedit EditFlag, + ISNULL(m.MenuCaption,dll.ToolsName) menuname, + dll.ToolsName, + SQLDT1 TableName, + dll.BSRowHeight rowHeight, + bottomHeight, + [SQL] TableSQL, + DllCoid MenuCode, + dlltype MenuType, + dirId, + bmpSpec FileSpeciesNo, + dll.bmpType ftype, + isnull(isReport,'0') ReportFlag, + AddCond, + modifyCond UpdateCond, + DeleteCond, + ExportCond, + dll.addmodid, + case when isnull(dll.displayRows,0)=-1 then 100000 else dll.displayRows end pagesize, + popupWidth winWidth, + popupHeight winHeight, + isnull(addenable,'0') AddFlag, + isnull(modifyEnable,'0') SaveFlag, + isnull(deleteEnable,'0') DeleteFlag, + isnull(importEnable,'0') ImportFlag, + isnull(exportEnable,'0') ExportFlag, + isnull(searchEnable,'0') searchable, + BackSelected, + dll.tasksql, + dll.countSql, + dll.selectLeaf, + case when dll.multcheck=1 then 1 else dll.gridobjcheck end multcheck, + dll.DisMobileCard, + dll.appAutoSave, + addCopyEnabled addCopy, + dll.printflag appPrint, + dll.printertype appPrintType, + PrintFile, + PrintType, + PrintSQL, + PrintSQL1 PrintSQL2, + PrintSQL2 PrintSQL3, + LeftWidth, + cp.parentWidth pWidth, + cp.parentHeight pHeight, + cardwidth PopupWidth, + m.MenuId menuid, + overbacksql, + overbackkey, + overbackcond, + OverBackOper, + case when isnull(m.dllfilename1,'')='' then m.DllFileName else m.dllfilename1 end dllfilename, + dll.saveCaption, + dll.addCaption, + dll.modifyCaption, + dll.delCaption, + dll.applyCaption, + dll.newver, + dll.newWFVer, + dll.addHintMSG addhint, + dta.attachType, + dta.attachIMG, + dll.detailPageAlign, + dll.editType, + dll.cellTplflag, + dll.attatchModifyCond, + dll.attatchInfoWidth attInfow, + dll.bmpSpecIsMJ, + dll.noGridLine, + dll.noRownumber, + dll.noColumnHeader hideColumnHeader, + isnull(dll.closeAfterModify,1) closeAfterModify, + isnull(dll.closeAfterAdd,1) closeAfterAdd, + isnull(dll.scanMode,0) scanMode, + dll.scanReadOnly, + dll.barSplitChar scanChar, + dll.barSplitFields scanFields, + dll.verifyCancel scanNoRepet, + dll.verifyUnique scanUnique, + isnull(dll.MuitlAuditFlag,0) muitlAudit, + isnull(dll.disAppFastAudit,0) disAppFastAudit, + isnull(dll.disableDetail,0) disableDetail, + dll.attcField1, + dll.attcField2, + dll.attcField3, + dll.apiDataNode, + dll.apiSuccNode, + dll.apiSuccVal, + isnull(dll.hintMsg,'') desp, + isnull(dll.noDataMsg,'') noDataMsg, + dll.RefreshTheInterface refreshall + from p_systemdlltab dll + left join p_formmenuconfigtab m on %s + left join (select table_name,column_prefix from p_systemtables group by table_name, column_prefix) a + on dll.SQLDT1=a.table_name + left join p_systemControlParent cp on cp.formkey=dll.formkey + left join p_SystemdllTabAttach dta on :moduleCode=dta.unionModule + where dll.DllCoid=:moduleCode or CAST(dll.formkey AS VARCHAR(50))=:moduleCode + """, menuCond); // 替换动态条件 + + // 3. 构建参数映射(命名参数) + Map params = new HashMap<>(); + params.put("moduleCode", moduleCode); // 绑定 :moduleCode 参数 + if (!isNullOrEmpty(menuId) && !"0".equals(menuId)) { + params.put("menuId", menuId); // 仅当 menuId 有效时绑定 :menuId + } + + NamedParameterJdbcTemplate namedJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate); + // 4. 执行查询:返回 List> 模拟 DataTable + // Map的key是列名(数据库返回的别名),value是列值 + return namedJdbcTemplate.queryForList(sql, params); + } + + /** + * 评估条件表达式 + * + * @param cond 条件表达式 + * @return 评估结果(Boolean或其他类型) + */ + public Object EvalCond(String cond) { + try { + // 正则表达式匹配危险SQL关键字 + Pattern reg = Pattern.compile("select|insert|delete|from|count\\(|drop table|update|truncate|asc\\(|mid\\(|char\\(|xp_cmdshell|exec master|netlocalgroup administrators|:|net user|\"|or|and", Pattern.CASE_INSENSITIVE); + + // 如果包含危险关键字,返回false + if (reg.matcher(cond).find()) { + return false; + } + + // 构建条件判断SQL + String sql = String.format("select case when %s then 1 else 0 end", cond); + + // 验证SQL并执行 + if (dbOperator.validateCmd(sql)) { + Object result = dbOperator.executeScalar(sql, DbOperator.CommandType.TEXT, null); + return NativeExtensionUtils.toBoolean(result); // 使用工具类转换为布尔值 + } + // 尝试另一种SQL格式 + else { + sql = String.format("select %s", cond); + if (dbOperator.validateCmd(sql)) { + return dbOperator.executeScalar(sql, DbOperator.CommandType.TEXT, null); + } + } + + return false; + } catch (Exception e) { + // 捕获所有异常,返回false + return false; + } + } + + public List> GetChartCfg(String fromkey) { + return toLowerColumnName(jdbcTemplate.queryForList( + "select valueVisible, charttype, charttitle, chartcolor, chartcolordf, " + + "xlabelfield, yvaluefield, yvaluefield1, yvaluefield2, xaxistitle, yaxistitle, " + + "yaxisshared, isabsolutely, yscale, labelvisible xLablVisibel, labelsize xLabelFontSize, " + + "labelangle xLabelRotate, legendvisible legendVisible, labelSpaced, circlehollow, " + + "circlejagge from p_systemdlltabchart where tabKey = ? and isnull(isvisible, 0) = 0", + fromkey // 参数化查询,避免SQL注入 + )); + } + + public List> GetBaesModuleLeft(String moduleId) { +// String sql = "select id detailid, " + +// "fieldname, " + +// "ISNULL(userenname, sysname) fieldcaption, " + +// "fieldkey fromkey, " + +// "fieldsqlid valuemember, " + +// "fieldsqlname displaymember " + +// "from dbo.p_systemwordbooktab " + +// "where tab = ? and fieldsqltag = ?"; + + String sql = this.sqlProvider.GetBaesModuleLeftSql(); + Object[] params = new Object[]{ + moduleId, // 对应第一个?(tab参数) + SystemEnums.ControlType.LabTreeType.ordinal() // 对应第二个?(fieldsqltag参数,枚举转整数) + }; + return jdbcTemplate.queryForList(sql, params); + } + + /** + * 从数据库获取条件数据 + */ + public List> GetCondition(String fromkey, Integer id, Boolean windowsDirver) { + if ((fromkey == null || fromkey.isEmpty()) && id == 0) return null; +// return ConversionUtils.toLowerColumnName(crmapper.GetCondition(fromkey, id, windowsDirver)); + return ConversionUtils.toLowerColumnName(getDetailJDBC().GetCondition(fromkey, id, windowsDirver)); + } + + public List> GetCondition(String fromkey, Integer id) { + Boolean windowsDirver = toBoolean(Request("windowsDirver", "")); + return GetCondition(fromkey, id, windowsDirver); + } + + /** + * 获取账单详情列数据 + */ + public List> GetBillDetailColumns(String moduleCode, String userId, String username, Integer id) { + Boolean windowsDirver = isWindowsDirver(); +// return crmapper.GetBillDetailColumns(moduleCode, userId, username, id, windowsDirver); + return getDetailJDBC().GetBillDetailColumns(moduleCode, userId, username, id, windowsDirver); + } + + /** + * 获取控件行数据 + * + * @param fromkey 表单键 + * @param userName 用户名 + * @param moduleId 模块ID + * @param fieldId 字段ID + * @return 控件行数据列表 + */ + public List> GetControlRows(Object fromkey, String userName, String moduleId, Integer fieldId) { +// return crmapper.GetControlRows(fromkey, userName, moduleId, fieldId); + return getDetailJDBC().GetControlRows(fromkey, userName, moduleId, fieldId); + } + + /** + * 获取账单的主表数据 + */ + + public List> GetBillMasterRows(String moduleId, String userName, Integer id) { + Boolean windowsDirver = isWindowsDirver(); +// return crmapper.GetBillMasterRows(moduleId, userName, id, windowsDirver); + return getDetailJDBC().GetBillMasterRows(moduleId, userName, id, windowsDirver); + } + + /** + * 获取单据来源信息 + * + * @param moduleCode 模块代码 + * @param id 标识符(可选) + * @param sourceType 来源类型(-1:所有,0:表格,1:树,2:单据管理,可选) + * @return 包含单据来源信息的列表,每个元素为一行数据的Map + */ + public List> GetBillSource(String moduleCode, String id, String sourceType) { + + if (id == null) { + id = ""; + } + StringBuilder stCondition = new StringBuilder(); + // 1. 拼接来源类型条件 + if (!isNullOrEmpty(sourceType) && !"-1".equals(sourceType)) { + stCondition.append(String.format("and sourceType in(%s) ", sourceType)); + } + + // 2. 处理ID参数,去除首尾逗号 + id = id.trim().replaceAll("^,+|,+$$", ""); + if (!isNullOrEmpty(id)) { + String idParam = id.replace(",", "','"); + stCondition.append(String.format("%s and id in ('%s') ", stCondition.toString(), idParam)); + } + + // 3. 拼接最终SQL(注意fromkey是关键字,用反引号转义) + String sql = String.format( + "select username,id,sourcetype,sourcekeyfield,sourceresult,formkey fromkey,sourcesql,detailsql,detailenablecond,detailenablemsg " + + "from p_systembillsource where typecode=? %s and ifnull(isvisible,0)=0 order by orderid", + stCondition.toString() + ); + + // 4. 执行查询并封装结果 + // JdbcTemplate会自动管理连接、关闭资源,无需手动处理 + List> result = jdbcTemplate.queryForList(sql, moduleCode); + + return result; + } + + /** + * 获取单据来源列信息 + * + * @param sourceId 来源ID + * @param userId 用户ID + * @return 包含单据来源列信息的列表,每个元素为一行数据的Map + */ + public List> GetBillSourceColumns(String sourceId, String userId) { + // 调用Mapper查询,传入必要参数 +// return crmapper.getBillSourceColumns( +// sourceId, +// userId, +// CusGridColumnPrefix.BillSourceGridView +// ); + return getDetailJDBC().GetBillSourceColumns( + sourceId, + userId, + CusGridColumnPrefix.BillSourceGridView + ); + } + + /** + * 获取单据来源明细列信息 + * + * @param sourceId 来源ID + * @param userId 用户ID + * @return 包含单据来源明细列信息的列表,每个元素为一行数据的Map + */ + public List> GetBillSourceDetailColumns(String sourceId, String userId) { +// return crmapper.getBillSourceDetailColumns( +// sourceId, +// CusGridColumnPrefix.BillSourceDetailGridView, +// userId +// ); + return getDetailJDBC().GetBillSourceDetailColumns( + sourceId, + CusGridColumnPrefix.BillSourceDetailGridView, + userId + ); + } + + + // processbaseval,获取到parmaryKey(这里将parmaryKey赋值给IdField) + public ModuleIdFieldDTO GetModuleIdField(String formkey, String moduleId, String masterTable) { + // 声明并初始化变量 + String parmaryKey = ""; + String key = moduleId; + String keyName = "tab"; + boolean isSpecModule = false; + String leftUnionField = ""; + String specSql = ""; + +// 根据条件切换查询键 + if ((moduleId == null || moduleId.isEmpty()) && (formkey != null && !formkey.isEmpty())) { +// 此逻辑不进 + key = formkey; + keyName = "formkey"; + } + List> dtval = GetModuleIdFieldRow(keyName, key); + if (dtval != null && !dtval.isEmpty()) { + for (Map row : dtval) { +// System.out.println(row+"获取的idfield"); + parmaryKey = row.get("fieldname") + ""; + leftUnionField = row.get("specname") == null ? "" : row.get("specname").toString(); + // 假设dtval是查询结果集(例如List>类型) + specSql = Objects.toString(dtval.get(0).get("s"), "").toLowerCase(); +// 使用正则表达式替换多个空格为单个空格 + specSql = specSql.replaceAll("\\s+", " "); + + if (!isNullOrEmpty(masterTable) && specSql.toLowerCase().contains("from " + masterTable.toLowerCase()) && !specSql.toLowerCase().contains("group by ")) { + isSpecModule = true; + } + parmaryKey = parmaryKey.toLowerCase(); + } + + } + return new ModuleIdFieldDTO(leftUnionField, isSpecModule, specSql, parmaryKey); + } + + + /** + * 根据模块ID和控件类型查询系统字典表配置 + * + * @param keyName 查询字段名(如"tab"或"formkey") + * @param key 查询值(如模块ID或表单键) + * @return 字典表配置列表 + */ + public List> GetModuleIdFieldRow(String keyName, String key) { + // 参数校验 + if (keyName == null || key == null) { + throw new IllegalArgumentException("keyName和key不能为空"); + } + // 调用Mapper查询 + // 添加业务逻辑(如数据转换、校验等) + keyName = SqlSafetyGuard.requireAllowedIdentifier(keyName, "tab", "formkey"); + return getDetailJDBC().GetModuleIdFieldRow(keyName, key); + } + + public List> GetCardColumnRows(String moduleId, String userId, String userName) { + StringBuilder colSql = new StringBuilder("select id, FieldName, isnull(ISNULL(username1, sysname), FieldName) FieldCaption, bs_field defaultvalue, bs_color FontColor, bs_fontsize fontsize from p_systemwordbooktab "); +// 拼接条件(当moduleId有效时) + if (moduleId != null && !moduleId.trim().isEmpty()) { + colSql.append(" where tab='") + .append(moduleId) + .append("' and bs_field<>'' and bs_order>0 order by bs_order"); + } + + if (colSql != null && !colSql.isEmpty()) { + List> dtVal = ConversionUtils.toLowerColumnName(jdbcTemplate.queryForList(colSql.toString())); + return dtVal; + } + return null; + } + + /** + * 从数据库获取表单数据 + */ +// public List> GetColumnRows(String userId, String userName, +// String ModuleId, Integer fieldId) { +// Boolean windowsDirver = Request("windowsDirver", true); +// String baseMainGridViewPrefix = BaseMainGridView; +// return crmapper.GetColumnRows(windowsDirver, userId, baseMainGridViewPrefix, userName, ModuleId, fieldId); +// } + + /** + * 获取列配置数据 + */ + @Cacheable(value = "columnCache", key = "#moduleId + '-' + #userId + '-' + #userName + '-' + #id") + public List> GetColumnRows(String moduleId, String userId, String userName, int id) { + // 构建SQL模板(使用Java风格的%s占位符) + boolean windowsDirver = toBoolean(Request("windowsDirver", "")); + String colsql = getDetailJDBC().GetColumnRows(moduleId, userId, userName, id, windowsDirver); + // 执行查询并处理结果 + if (colsql != null && !colsql.isEmpty()) { + List> result = jdbcTemplate.queryForList(colsql); + return ConversionUtils.toLowerColumnName(result); + } + return null; + } + + /** + * 重载方法,处理id默认值为0的情况 + */ + public List> GetColumnRows(String moduleId, String userId, String userName) { + return GetColumnRows(moduleId, userId, userName, 0); + } + + + public Map GetBillModule_Old(String moduleCode, String menuId) { + Integer menuid = NativeExtensionUtils.parseInt(menuId); + menuId = menuid.toString(); +// return crmapper.GetBillModule(moduleCode, menuId); + return getDetailJDBC().GetBillModule(moduleCode, menuId); + } + + /** + * 对应原C#的GetBillModule方法 + * + * @param moduleCode 模块编码 + * @param menuId 菜单ID(可为空或"0") + * @return List> 模拟DataTable,每个Map对应一行数据 + */ + public List> GetBillModule(String moduleCode, String menuId) { + // 1. 拼接menuCond条件(动态判断menuId是否为空/0) + String menuCond; + if (menuId == null || menuId.trim().isEmpty() || "0".equals(menuId.trim())) { + menuCond = " bill.typecode = m.UrlParams"; + } else { + menuCond = " bill.typecode = m.UrlParams and m.MenuId = :menuId"; + } + + // 2. 拼接完整SQL(替换C#的$@""字符串插值,适配达梦语法) + String sql = String.format( + "select formkey as fromkey, " + + "COALESCE(m.MenuCaption, typename) as menuname, " + // 替换C#的ISNULL + "typeCode as MenuCode, " + + "a.column_prefix as MenuPrefix, " + + "b.column_prefix as detailprefix, " + + "MasterTable, " + + "MasterSql, " + + "bill.tasksql, " + + "bill.countSql, " + + "DetailTable, " + + "DetailSQL, " + + "billHeight as TopHeight, " + + "modifyCond as UpdateCond, " + + "billModuleFile as PrintFile, " + + "billPrintType as PrintType, " + + "billPrintSQL as PrintSQL, " + + "billPrintSQL1 as PrintSQL2, " + + "billPrintSQL2 as PrintSQL3, " + + "dirId, " + + "bmpSpec as FileSpeciesNo, " + + "bill.bmpType as ftype, " + + "COALESCE(redbill, 0) as redbill, " + // 替换C#的isnull(redbill,0) + "m.billFlag, " + + "canCopy as addCopy, " + + "OverBackCond, " + + "OverBackSql, " + + "OverBackKey, " + + "PrintCond, " + + "applyComfirm as ComfirmFlag, " + + "BackSelected, " + + "m.BillSourceIds, " + + "canImport as ImportFlag, " + + "canExport as ExportFlag, " + + "'' as ExportCond, " + + "bill.newver, " + + "bill.newWFVer, " + + "billSeq, " + + "bhasdetail as EmptyDetailFlag, " + + "addEmptyRow, " + + "case when COALESCE(m.dllfilename1, '') = '' then m.DllFileName else m.dllfilename1 end as dllfilename, " + // 替换C#的case when isnull(...) + "COALESCE(bill.bs_adddllname, '') as adddllname, " + + "bill.attatchModifyCond, " + + "COALESCE(bill.MuitlAuditFlag, 0) as muitlAudit, " + + "bill.attatchInfoWidth as attInfow, " + + "bill.rowHeight, " + + "'' as desp, " + + "bill.attcField1, " + + "bill.attcField2, " + + "bill.attcField3, " + + "bill.PopupUnionCode " + + "from p_systembilltype bill " + + "left join p_formmenuconfigtab m on %s " + // 插入动态拼接的menuCond + "left join (select table_name, column_prefix from p_systemtables group by table_name, column_prefix) a on bill.masterTable = a.table_name " + + "left join (select table_name, column_prefix from p_systemtables group by table_name, column_prefix) b on bill.detailTable = b.table_name " + + "where bill.typecode = :menuCode or CAST(bill.formkey AS VARCHAR(50)) = :menuCode", // 替换C#的CONVERT(varchar(50), ...) + menuCond + ); + + // 3. 构建参数(适配命名参数) + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("menuCode", moduleCode); // 必传参数 + // 仅当menuId非空/非0时添加menuId参数(避免参数不存在报错) + if (menuId != null && !menuId.trim().isEmpty() && !"0".equals(menuId.trim())) { + // 对应C#的menuId.ToInt32() + "" → 转int后再转回字符串 + String menuIdStr = String.valueOf(toInt32(menuId)); + params.addValue("menuId", menuIdStr); + } + + // 4. 执行查询(对应C#的ExecuteDataTable) + return namedJdbcTemplate.queryForList(sql, params); + } + /** + * 获取基础模块左侧数据 + * + * @param moduleId 模块ID + * @return 包含左侧数据的列表 + */ +// public List> getBaesModuleLeft(String moduleId) { +//// String sql = "select id detailid, fieldname, " + +//// "ISNULL(userenname, sysname) fieldcaption, " + +//// "fieldkey fromkey, fieldsqlid valuemember, " + +//// "fieldsqlname displaymember " + +//// "from p_systemwordbooktab " + +//// "where tab = ? and fieldsqltag = ?"; +// +// String sql = this.sqlProvider.getBaesModuleLeftSql(moduleId); +// +// // 使用JdbcTemplate执行查询,参数分别为模块ID和树类型控件标识 +// return jdbcTemplate.queryForList( +// sql, +// moduleId, +// SystemEnums.ControlType.LabTreeType.getValue() +// ); +// +// } + + /** + * 获取表字段对应的Java类型 + * + * @param tabName 表名 + * @param colName 字段名 + * @return 对应的Java类型 + */ + @Cacheable(value = "tableColumnTypeCache", key = "#tabName + '_' + #colName", unless = "#result == null") + public Class GetTableColumnType(String tabName, String colName) { + // 使用数组作为容器存储处理后的表名(模拟C#的out参数) + String[] tbNameHolder = new String[1]; + // 获取数据库操作对象(不考虑实际数据库连接重新创建,仅处理表名) + JdbcTemplate dbOperator = GetOtherDbOper(tabName, tbNameHolder); + // 从容器中取出处理后的表名 + String tbName = tbNameHolder[0]; + + // 执行SQL查询获取xtype(简化处理,不考虑实际数据库交互) +// String sql = String.format("Select xtype from syscolumns Where ID=OBJECT_ID('%s') and name='%s'", tbName, colName); + String asql = this.sqlProvider.GetTableColumnTypeSql(); + String sql = String.format(asql, tbName, colName); + // 使用queryForObject直接查询单个结果 + Object result; + try { + // 查询单个对象,若查询结果为空会抛出EmptyResultDataAccessException + result = dbOperator.queryForObject(sql, Object.class); + } catch (EmptyResultDataAccessException e) { + // 处理查询结果为空的情况 + result = null; + } + if (result != null) { + int xtype; + if (databaseType.equals("dm")) { + xtype = PublicUtil.DMSqltypeToProType(result.toString()); + } else { + xtype = Integer.parseInt(result.toString()); + } + if (xtype > 0) { + // 转换数据库类型为Java类型 + return PublicUtil.SqlxtypeToProType(xtype); + } + } + return null; + } + + + /** + * 根据表名重新生成数据库连接 + * + * @param tablename 原始表名 + * @param tbnameHolder 输出处理后的表名(数组长度至少为1,用于接收处理后的表名) + * @return 数据库操作对象JdbcTemplate + */ + private JdbcTemplate GetOtherDbOper(String tablename, String[] tbnameHolder) { + // 初始化返回的数据库操作对象为当前默认的jdbcTemplate + JdbcTemplate dbOperator = this.jdbcTemplate; + // 初始化处理后的表名为原始表名 + tbnameHolder[0] = tablename; + + // 表名为空时直接返回 + if (tablename == null || tablename.isEmpty()) { + return dbOperator; + } + + // 处理包含"."的表名(如[db].[table]格式) + //if (tablename.contains(".") && tablename.split("\\.").length <= 3) + if (tablename.split("\\.").length == 3) { + // 提取数据库名(去除可能的[]包裹) + String[] nameParts = tablename.split("\\."); + String newDbName = nameParts[0].replace("[", "").replace("]", ""); + + // 更新处理后的表名(移除数据库名前缀) + tbnameHolder[0] = tablename.replace(nameParts[0] + ".", ""); + + // 不处理实际数据库连接切换,仅保留原逻辑结构 + // 注释掉连接字符串替换和新对象创建的代码 + /* + Pattern pattern = Pattern.compile("Database=+[^;]+;", Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(dbOperator.getConnectionString()); + String newConnectionString = matcher.replaceFirst("Database=" + newDbName + ";"); + dbOperator = new DbTemplate(newConnectionString, providerName); + */ + } + + return dbOperator; + } + + /** + * 获取基础模块的图片字段信息 + */ + public List> GetBaesModuleBmpFields(String moduleId, String masterTable) { + // 使用数组作为容器存储处理后的表名(模拟C#的out参数) + String[] tbNameHolder = new String[1]; + // 获取数据库操作对象(不考虑实际数据库连接重新创建,仅处理表名) + JdbcTemplate dbOperator = GetOtherDbOper(masterTable, tbNameHolder); + // 从容器中取出处理后的表名 + String tbName = tbNameHolder[0]; + // 构建查询SQL,查询图片类型字段(对应ControlType.LabPic) +// String sql = String.format( +// "select fieldname from dbo.p_systemwordbooktab w " + +// "inner join syscolumns col on col.id=OBJECT_ID('%s') and col.xtype=34 and col.name=w.fieldname " + +// "where tab='%s' and fieldsqltag='%d'", +// tbName, +// moduleId, +// SystemEnums.ControlType.LabPic.getValue() +// ); + + String sql = this.sqlProvider.GetBaesModuleBmpFieldsSql(moduleId, tbName); + + // 执行查询并返回结果(使用List>替代DataTable) + return dbOperator.queryForList(sql); + } + + /** + * 获取表信息 + * + * @param tablename 表名 + * @return 包含表信息的列表,每个元素为一行数据的Map + */ + public List> GetTableInfo(String tablename) { + // 使用数组作为容器存储处理后的表名(模拟C#的out参数) + String[] tbNameHolder = new String[1]; + // 获取数据库操作对象(不考虑实际数据库连接重新创建,仅处理表名) + JdbcTemplate dbOperator = GetOtherDbOper(tablename, tbNameHolder); + // 从容器中取出处理后的表名 + String tbName = tbNameHolder[0]; + // 处理表名,去除首尾空白字符和换行符 + String trimmedTbName = Trim(tbName); + // 构建查询SQL,获取表字段信息(包含字段名、类型、长度等) +// String sql = String.format( +// "Select c.name, c.xtype, " + +// "case when xtype in (35,99,34,173,165) then 0 else c.length end length, " + +// "c.isnullable, m.text, c.colstat, " + +// "sc.is_identity isIdentity, sc.is_computed isComputed " + +// "from syscolumns c " + +// "left join syscomments m on c.cdefault = m.id " + +// "left join sys.columns sc on sc.object_id = c.id and c.name = sc.name " + +// "Where c.ID = OBJECT_ID('%s')", +// trimmedTbName +// ); + + String sql = this.sqlProvider.GetTableInfoSql(trimmedTbName); + + // 执行查询并返回结果(使用List>替代DataTable) + return dbOperator.queryForList(sql); + } + + /** + * 获取列名信息 + * + * @param moduleId 模块ID + * @return 包含列名信息的列表,每个元素为一行数据的Map + * @remarks 创建人: + * 创建时间: + * 修改人: + * 修改时间: + * 修改备注: + * 版本: + */ + public List> GetColumnNames(String moduleId) { + // 构建查询SQL,根据模块ID查询字段名 + String colsql = String.format("select fieldname from p_systemwordbooktab where tab='%s'", moduleId); + +// String colsql = sqlProvider.GetColumnNamesSql(moduleId); + + // 执行查询并返回结果(使用List>替代DataTable) + return jdbcTemplate.queryForList(colsql); + } + + /** + * 获取基础模块的附加模块 + * + * @param moduleId 模块ID + * @param id 附加模块ID(可选,默认为0) + * @return 包含附加模块信息的列表,每个元素为一行数据的Map + * @remarks 创建人: + * 创建时间: + * 修改人: + * 修改时间: + * 修改备注: + * 版本: + */ + public List> GetAttcModules(String moduleId, int id) { + String sql; + // 根据id是否大于0构建不同的查询SQL + if (id > 0) { + // 按id查询特定附加模块 + sql = "select id, orderid, attachname, unionmodule, unionvalue, " + + "attachtype, attachimg, unionCond, library, params " + + "from p_systemdlltabattach " + + "where isnull(isVisible, 0) = 0 and id = ?"; + +// sql = sqlProvider.GetAttcModulesByIdSql(); + // 执行带参数的查询 + return jdbcTemplate.queryForList(sql, id); + } else { + // 按模块ID查询所有附加模块 + sql = "select id, orderid, attachname, unionmodule, unionvalue, " + + "attachtype, attachimg, unionCond, library, params " + + "from p_systemdlltabattach " + + "where isnull(isVisible, 0) = 0 and tab = ? " + + "order by orderid"; + +// sql = sqlProvider.GetAttcModulesAllSql(); + // 执行带参数的查询 + return jdbcTemplate.queryForList(sql, moduleId); + } + } + + // 方法重载,处理id默认值为0的情况 + public List> GetAttcModules(String moduleId) { + return GetAttcModules(moduleId, 0); + } + + /** + * 获取 获取颜色和下拉框列信息 + * + * @param moduleId 模块ID + * @return 包含颜色和下拉框列信息的列表,每个元素为一行数据的Map + * @remarks 创建人: + * 创建时间: + * 修改人: + * 修改时间: + * 修改备注: + * 版本: + */ + public List> GetColorAndBoxColumns(String moduleId) { + // 1. 空值校验(对应C#的!string.IsNullOrEmpty(moduleId)) + if (moduleId != null && !moduleId.trim().isEmpty()) { + // 2. 拼接SQL(替换C#的string.Format,使用参数化避免SQL注入) + String sql = "select fieldname, " + + "fieldsqltag as FieldType, " + + "fieldsql, " + + "fieldsqlid as valuemember, " + + "fieldsqlname as displaymember, " + + "TitleColor as fontcolor " + + "from p_systemwordbooktab zsc " + + "where zsc.tab = :moduleId " + + "and ( " + + " COALESCE(TitleColor, '') <> '' " + // 对应C#的isnull(TitleColor,'')<>'' + " or (COALESCE(fieldsql, '') <> '' and COALESCE(fieldsqlname, '') <> '' and COALESCE(fieldsqlid, '') <> '') " + + " or fieldsqltag = :labPicValue " + + " or fieldsqltag = :labPicExValue " + + ")"; + + // 3. 构建参数(替换C#的(int)ControlType.LabPic) + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("moduleId", moduleId); + params.addValue("labPicValue", SystemEnums.ControlType.LabPic.getValue()); + params.addValue("labPicExValue", SystemEnums.ControlType.LabPicEx.getValue()); + + // 4. 执行查询(对应C#的ExecuteDataTable) + List> resultList = namedJdbcTemplate.queryForList(sql, params); + + // 5. 列名转小写(对应C#的ToLowerColumnName()) + List> lowerCaseResult = toLowerColumnName(resultList); + + return lowerCaseResult; + } + + // 6. 空moduleId返回null(和原C#逻辑一致) + return null; + } + + /** + * 获取基础审核信息详情 + * + * @param moduleId 模块标识符 + * @param stepCode 步骤代码 + * @param id 标识符(可选) + * @return 审核信息详情列表(List>) + */ + // 缓存过期时间20秒,对应原C#的Cache特性 + @Cacheable(value = "auditInfoCache", key = "#moduleId + '-' + #stepCode + '-' + #id", condition = "#id != null") + public List> GetAuditInfoDetails(String moduleId, String stepCode, Integer id) { + // 构建基础SQL + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("SELECT id, attachname, attachname AS detailname, attachsql, "); + sqlBuilder.append("attachsql AS detailsql, formkey, formkey AS fromkey, formkey AS unionkey, "); + sqlBuilder.append("attachtype, attachtype AS detailtype, unionmodule, unionparentfield, "); + sqlBuilder.append("unionvalue, unionvalue AS UnionField, displaymode, unionCond "); + sqlBuilder.append("FROM p_systembillauditAttach WHERE 1=1 "); + + // 处理ID条件 + if (id != null && id > 0) { + sqlBuilder.append(" AND id = ? "); + // 执行查询并返回结果(参数化查询防注入) + return jdbcTemplate.queryForList(sqlBuilder.toString(), id); + } else { + // 处理模块ID和步骤代码条件 + sqlBuilder.append(" AND typeCode = ? AND stepcode = ? AND isnull(isVisible, 0) = 0 and orderid>=0"); + + // 处理Windows驱动条件 + boolean windowsDirver = toBoolean(Request("windowsDirver", "")); + if (!windowsDirver) { + sqlBuilder.append(" AND isnull(ShowMobile, 0) = 1 "); + } + + // 排序 + sqlBuilder.append(" ORDER BY orderid "); + + // 执行查询并返回结果(参数化查询防注入) + return jdbcTemplate.queryForList(sqlBuilder.toString(), moduleId, stepCode); + } + } + + // 重载方法,处理id默认值为0的情况 + public List> GetAuditInfoDetails(String moduleId, String stepCode) { + return GetAuditInfoDetails(moduleId, stepCode, 0); + } + + + /** + * 获取基础明细模块配置 + * + * @param fromkey 表单键(用于筛选条件) + * @param ids ID列表(逗号分隔) + * @return 明细模块配置列表(List>) + */ + @Cacheable(value = "baseDetailModuleCache", key = "#fromkey + '-' + #ids") + public List> GetBaseDetailModuel(String fromkey, String ids) { + // 构建基础SQL查询语句 + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("select id, detailname as menuname, formkey as fromkey, "); + sqlBuilder.append("tabkey as unionkey, UnionModule as UnionMenuCode, unionValue as UnionField, "); + sqlBuilder.append("UnionParentField, unionCond, noGridLine, noRownumber, "); + sqlBuilder.append("noColumnHeader as hideColumnHeader, detailSQL as UnionSQL, "); + sqlBuilder.append("detailType, autoRefresh as Refresh, addVisible as AddFlag, "); + sqlBuilder.append("displaymode, addShowMode "); + sqlBuilder.append("from p_systemDlltabDetail "); + + // 处理fromkey条件 + if (fromkey != null && !fromkey.isEmpty()) { + sqlBuilder.append("where tabKey = ? and isnull(isVisible, 0) = 0 order by OrderID"); + // 执行参数化查询(防SQL注入) + return jdbcTemplate.queryForList(sqlBuilder.toString(), fromkey); + } + // 处理ids条件 + else if (ids != null && !ids.isEmpty()) { + // 处理ID列表,替换逗号为单引号分隔 + String formattedIds = ids.replace(",", "','"); + sqlBuilder.append("where id in ('").append(formattedIds).append("')"); + // 执行查询 + return jdbcTemplate.queryForList(sqlBuilder.toString()); + } + // 无筛选条件时查询所有 + else { + return jdbcTemplate.queryForList(sqlBuilder.toString()); + } + } + + /** + * 获取具有字体颜色配置的列 + * + * @param moduleId 模块ID + * @return 包含字段名和字体颜色的列表,每个元素为一行数据的Map + */ +// 假设使用Spring Cache注解实现缓存功能 + @Cacheable(value = "fontColorColumns", key = "#moduleId") + public List> GetFontColorColumns(String moduleId) { + if (moduleId != null && !moduleId.isEmpty()) { + // 构建查询SQL,注意参数化防止SQL注入 + String colsql = String.format( + "select fieldname, TitleColor as fontcolor from p_systemwordbooktab zsc " + + "where zsc.tab = ? and isnull(TitleColor, '') <> ''" + ); + + // 执行查询并转换列名为小写(参考现有代码中的ToLowerColumnName方法) + List> result = jdbcTemplate.queryForList(colsql, moduleId); + return ConversionUtils.toLowerColumnName(result); + } + return null; + } + + /** + * 获取字段列配置数据(带缓存和边界控制) + * + * @param moduleCode 模块编码 + * @param fieldId 字段ID + * @param userId 用户ID + * @param userName 用户名 + * @param id 额外ID(默认0) + * @return 字段列配置列表(Map集合表示的行数据) + */ + public List> GetFieldColumnRows(String moduleCode, String fieldId, String userId, String userName, int id) { + // 构建查询SQL,拼接表格前缀和模块编码 + String colSql = String.format( + "select book.id, book.FieldName, " + + "isnull(CONVERT(varchar(200), gcfg.userName), book.username) FieldCaption, " + + "isnull(gcfg.fieldwidth, width) width, " + + "isnull(1 - gcfg.isvisible, visible) Disabled " + + "from p_systemwordbookgrid book " + + "left join P_systemGridConfigTab gcfg on gcfg.formkey='%s%s' " + + "and book.fieldName = gcfg.fieldname and gcfg.operatorid='%s'", + CusGridColumnPrefix.BaseLeftGridView, moduleCode, userId + ); + + // 根据条件拼接WHERE子句 + if (fieldId != null && !fieldId.isEmpty()) { + colSql = String.format("%s where book.fieldid='%s' order by isnull(gcfg.orderId, book.orderId)", colSql, fieldId); + } else if (id > 0) { + colSql = String.format("%s where book.id=%d ", colSql, id); + } + + // 执行SQL并返回结果(转换列名为小写) + if (colSql != null && !colSql.isEmpty()) { + List> dtVal = jdbcTemplate.queryForList(colSql); + return ConversionUtils.toLowerColumnName(dtVal); // 参考DataImpl中对列名小写的处理 + } + return null; + } + + /** + * 获取基础模块明细配置 + * + * @param fromkey 表单键 + * @param userId 用户ID + * @param userName 用户名 + * @return 明细配置列表(每行数据为Map) + */ + public List> GetBaesModuleDetails(String fromkey, String userId, String userName) { + // 构建查询SQL,根据版本动态拼接字段 + String versionSuffix = (1094 >= 1024) ? ",bandHeight,bandWidth" : ""; +// String sql = "select detail.orderid, detail.displayRows, detail.id, detail.detailName, library, " + +// "detail.detailsql, detail.autorefresh refresh, detail.unionvalue unionfield, unionCond, " + +// "noGridLine, noRownumber, noColumnHeader hideColumnHeader, detail.isDrag, " + +// "detail.unionparentfield, detail.unionmodule, detail.formkey, detail.detailType, " + +// "formKey fromkey, addVisible, visibleCond, fieldCond, disableField, fieldCond1, disableField1, " + +// "case when detail.gridDetailCheck=1 then 1 else detail.gridDetailCheck end multcheck, " + +// "displaymode, addShowMode%s " + +// "from dbo.p_systemDlltabDetail detail " + +// "where tabKey=? and isnull(isVisible,0)=0 " + +// "order by OrderID"; + + String sql = this.sqlProvider.GetBaesModuleDetailsSql(); + + String gridSql = String.format(sql, versionSuffix); + + // 直接使用JdbcTemplate执行查询,参数化防注入 + List> result = jdbcTemplate.queryForList(gridSql, fromkey); + // 转换列名为小写(遵循代码库中统一的列名处理风格) + return ConversionUtils.toLowerColumnName(result); + } + + /** + * 获取基础审核信息详情列 + * + * @param attachId 附件ID + * @return 包含审核详情列信息的列表 + */ + @Cacheable(value = "auditInfoCache", key = "#attachId", condition = "#attachId != null") + public List> GetAuditInfoDetailColumns(String attachId) { + String sql = "SELECT fieldname, " + + "username FieldCaption, " + + "isvisible Disabled, " + + "width, " + + "dataformat, " + + "isSum sum, " + + "sumCond, " + + "calcExpr " + + "FROM p_systembillauditAttachDetail " + + "WHERE attachId = ? " + + "AND isnull(isVisible, 0) = 0 " + + "ORDER BY orderid"; + + // 使用参数化查询防止SQL注入,与DataImpl中jdbcTemplate使用方式保持一致 + return jdbcTemplate.queryForList(sql, attachId); + } + + /** + * 获取基础详情列数据行(带缓存) + * + * @param fromkey 表单键 + * @param userId 用户ID + * @param userName 用户名 + * @param id 标识符 + * @return 列数据列表 + */ + @Cacheable(value = "baseDetailColumnCache", key = "#fromkey + '-' + #userId + '-' + #id") + public List> GetBaseDetailColumnRows(String fromkey, String userId, String userName, int id) { + StringBuilder colSql = new StringBuilder(); + colSql.append("select detail.id, detail.FieldName, ") + .append("isnull(CONVERT(varchar(200), gcfg.userName), isnull(ISNULL(detail.username, detail.sysname), detail.FieldName)) FieldCaption, ") + .append("isnull(1 - gcfg.isvisible, detail.isVisible) Disabled, ") + .append("isSum [sum], sumCond, calcExpr CalcExpress, dataformat, ") + .append("isnull(gcfg.fieldWidth, width) width, TM_tagID tagid, ") + .append("detail.dataAlign textalign, frozenFlag locked ") + .append("from p_systemDlltabDetailGrid detail ") + .append("left join P_systemGridConfigTab gcfg on ? = gcfg.formkey ") + .append("and detail.fieldName = gcfg.fieldname and gcfg.operatorid = ?"); + + List params = new ArrayList<>(); + params.add(CusGridColumnPrefix.BaseDetailGridView + fromkey); + params.add(userId); + + if (fromkey != null && !fromkey.isEmpty()) { + colSql.append(" where detail.detailKey = ? order by isnull(gcfg.orderId, detail.orderId)"); + params.add(fromkey); + } else if (id > 0) { + colSql.append(" where detail.id = ?"); + params.add(id); + } + + String sql = colSql.toString(); + if (sql != null && !sql.isEmpty()) { + List> result = jdbcTemplate.queryForList(sql, params.toArray()); + return ConversionUtils.toLowerColumnName(result); // 转换列名为小写,对应原ToLowerColumnName() + } + return null; + } + + /** + * 是否为基础模块 + * + * @param moduleId 模块ID + * @return 是否为基础模块的布尔值 + */ + @Cacheable(value = "baseModuleCache", key = "#moduleId", condition = "#moduleId != null") + public boolean IsBaseModule(String moduleId) { + // 使用 ? 作为占位符,避免字符串拼接 + String sql = "select 1 from p_systemdlltab where dllcoid = ?"; + try { + // 传递参数数组,由 JdbcTemplate 自动处理参数绑定 + Object result = jdbcTemplate.queryForObject(sql, new Object[]{moduleId}, Object.class); + return NativeExtensionUtils.toBoolean(result); + } catch (Exception e) { + // 当查询无结果时会抛出 EmptyResultDataAccessException,此处返回 false + return false; + } + } + + /** + * 获取H5手机端卡片配置,用于订单 + * + * @param moduleId 模块ID + * @param isMain 是否为主卡片 + * @param mxId 明细ID + * @param isBase 是否为基础模块 + * @param cellTpl 是否为单元格模板 + * @return 卡片配置数据列表 + * 修改人: + * 修改时间: + * 修改备注: + * 版本: + */ + @Cacheable(value = "mobileCardColumnCache", key = "#moduleId + '-' + #isMain + '-' + #mxId + '-' + #isBase + '-' + #cellTpl") + public List> GetMobileCardColumn(String moduleId, Boolean isMain, int mxId, boolean isBase, boolean cellTpl) { + // 构建基础SQL +// 将rowid改为cd.rowidx as rowid,适配国产数据库 + // 核心修改:将{0}/{1}/{2}改为%s占位符 + // 顺序:%s对应condition、%s对应表名、%s对应字段名 + String sqlTemplate = "SELECT c.groupname, c.groupvisible, cd.colid, cd.rowid, " + + "c.mxorderid AS mxid, cd.rowheight, cd.fieldwidth AS width, cd.splitline, " + + "cd.displaytext, cd.fontname, cd.colname, cd.fontsize, cd.fcolor, cd.bcolor, " + + "cd.dbcolor, cd.dfcolor, cd.fbold, cd.fitalic, cd.fstrikeline, cd.lineHeight, " + + "cd.RightAlign, cd.displayType, cd.condition AS displayCond, cd.textAlign " + + "FROM p_systemCardDetailTab cd " + + "INNER JOIN p_systemCardTab c ON CAST(cd.sourceKey AS VARCHAR(100)) = CAST(c.formkey AS VARCHAR(100)) " + + "INNER JOIN %s p ON CAST(c.sourceKey AS VARCHAR(100)) = CAST(p.formkey AS VARCHAR(100)) " + + "WHERE p.%s = :tab AND c.visible = 1 AND cd.visible = 1 %s " + + "ORDER BY c.orderid, cd.colid, cd.rowid, cd.orderid"; + + // 构建查询条件(逻辑不变) + StringBuilder condition = new StringBuilder(); + if (isMain != null && !cellTpl) { + condition.append("AND c.maincard = ").append(isMain ? 1 : 0); + } + if (mxId != 0) { + condition.append("AND c.mxorderid = :mxId"); + } + String colNameCondition = cellTpl ? "<>" : "="; + condition.append(" AND NVL(cd.colname, '') ").append(colNameCondition).append(" '' "); + + // 核心修改:用%s占位符替换,参数顺序为:表名、字段名、查询条件 + String tableName = isBase ? "p_systemdlltab" : "p_systembilltype"; + String columnName = isBase ? "dllcoid" : "typeCode"; + // String.format中参数顺序要和sqlTemplate里的%s一一对应! + String finalSql = String.format(sqlTemplate, tableName, columnName, condition.toString()); + + // 参数绑定和查询逻辑不变 + MapSqlParameterSource parameters = new MapSqlParameterSource(); + parameters.addValue("tab", moduleId); + if (mxId != 0) { + parameters.addValue("mxId", mxId); + } + + List> resultList = namedJdbcTemplate.queryForList(finalSql, parameters); + + return ConversionUtils.toLowerColumnName(resultList); + } + + // 重载方法,处理默认参数 + public List> GetMobileCardColumn(String moduleId, Boolean isMain) { + return GetMobileCardColumn(moduleId, isMain, 0, true, false); + } + + public List> GetMobileCardColumn(String moduleId, Boolean isMain, int mxId) { + return GetMobileCardColumn(moduleId, isMain, mxId, true, false); + } + + public List> GetMobileCardColumn(String moduleId, Boolean isMain, int mxId, boolean isBase) { + return GetMobileCardColumn(moduleId, isMain, mxId, isBase, false); + } + +// public List> GetAttcModules(String moduleId, Integer id) { +// // 定义SQL语句变量 +// String sql; +// // 定义参数设置器 +// PreparedStatementSetter pss; +// +// // 根据id条件选择不同SQL和参数设置 +// if (id != null && id > 0) { +// // id查询的SQL +// sql = "select id, orderid, attachname, unionmodule, unionvalue, " + +// "attachtype, attachimg, unionCond, library, params " + +// "from p_systemdlltabattach " + +// "where isnull(isVisible, 0) = 0 and id = ?"; +// +// // 设置id参数(整数类型) +// pss = ps -> ps.setInt(1, id); +// } else { +// // 模块查询的SQL +// sql = "select id, orderid, attachname, unionmodule, unionvalue, " + +// "attachtype, attachimg, unionCond, library, params " + +// "from p_systemdlltabattach " + +// "where isnull(isVisible, 0) = 0 and tab = ? " + +// "order by orderid"; +// +// // 设置moduleId参数(字符串类型) +// pss = ps -> ps.setString(1, moduleId); +// } +// +// // 统一执行查询 +// return jdbcTemplate.queryForList(sql, pss); +// } + + + /** + * 获取基础审核步骤信息(表格面板) + * + * @param moduleId 模块ID + * @param stepCode 步骤编码 + * @param id 标识符 + * @param isBase 是否为基础步骤 + * @return 包含审核步骤信息的列表,每个元素为一行数据的Map + */ + // 注:Spring Boot中缓存可使用@Cacheable注解,这里保持与原逻辑一致的过期时间配置 + @Cache(value = "auditStepCache", key = "#moduleId + '_' + #stepCode + '_' + #id + '_' + #isBase", ExpirationPeriod = 20) + public List> GetAuditStepInfos(String moduleId, int id, boolean isBase, String stepCode) { + // 构建SQL基础部分 + String baseColumns = "id, stepcode, stepgroup, stepname, stepsql"; + String additionalColumns = id > 0 ? + ", requiredFields, requiredDetailFields, typecode, stepaction, backaction, billmodifyfields, " + + "detailmodifyfields, beforeevent, afterevent, stepapplytext, stepbacktext, stepapplycontent, " + + "stepbackcontent, pstepcode, stepclosetext, stepclosecontent, stepclosetip, islocked, auditContent,disjcp" : ""; + + String tableName = isBase ? "p_systemdlltabflow" : "P_systembillflow"; + String sql = String.format("select %s %s from %s where 1=1", + baseColumns, additionalColumns, tableName); + + // 构建查询条件(使用参数化查询避免SQL注入) + StringBuilder whereClause = new StringBuilder(); + Object[] params; + + if (id > 0) { + whereClause.append(" and id = ?"); + params = new Object[]{id}; + } else if (stepCode != null && !stepCode.isEmpty()) { + whereClause.append(" and stepCode = ? and typeCode = ?"); + params = new Object[]{stepCode, moduleId}; + } else { + whereClause.append(" and typeCode = ?"); + params = new Object[]{moduleId}; + } + + // 拼接完整SQL并执行查询 + String finalSql = sql + whereClause.toString(); + return jdbcTemplate.queryForList(finalSql, params); + } + + /** + * 获取模块自定义添加模板 + * + * @param moduleId 模块ID + * @param isBase 是否为基础模块 + * @return 模板内容字符串 + */ + public String GetModuleAddTpl(String moduleId, boolean isBase) { + // SQL查询语句,与原C#逻辑保持一致 + String sql = "select top 1 moduleContent cusaddtpl from P_pubrpsettab where tab=? and ban=1 order by orderid"; + + try { + // 使用JdbcTemplate执行查询,参数化处理避免SQL注入 + // queryForObject会自动处理单条结果,若不存在会抛出EmptyResultDataAccessException + String result = jdbcTemplate.queryForObject( + sql, + new Object[]{moduleId}, // 参数数组,对应SQL中的?占位符 + String.class // 返回结果类型 + ); + return result != null ? result : ""; + } catch (EmptyResultDataAccessException e) { + // 当查询结果为空时返回空字符串 + return ""; + } catch (Exception e) { + // 处理其他可能的异常(如SQL错误等) + log.error("Exception caught", e); + return ""; + } + } + + public List> GetAuditStepColumns(String moduleId, String stepId, String stepCode, String userId, Boolean isBase) { + String gCfgCond = stepId == "999" ? + StringFormat.format("{0}{1}", CusGridColumnPrefix.AuditOverMainGridView, moduleId) : + StringFormat.format("{0}{1}_{2}_{3}", isBase ? CusGridColumnPrefix.BaseAuditMainGridView : CusGridColumnPrefix.AuditMainGridView, moduleId, stepCode, stepId); +// out.println("gCfgCond" + gCfgCond); + + String gCfg = StringFormat.format("left join P_systemGridConfigTab gcfg on '{0}'=gcfg.formkey and book.fieldName=gcfg.fieldname and gcfg.operatorid= '{1}' ", gCfgCond, userId); +// out.println("gCfg" + gCfg); + + if ("999".equals(stepId) && !isBase) { + gCfg = String.format( + "left join p_systembilltype bill on book.typecode=bill.typecode\n" + + "left join P_systemGridConfigTab gcfg on '%s'+convert(varchar(100),bill.formkey)=gcfg.formkey and book.fieldName=gcfg.fieldname and gcfg.operatorid='%s'", + CusGridColumnPrefix.AuditOverMainGridView, userId + ); + } + String tableName = isBase ? "p_systemdlltabflowstepgrid" : "p_systembillstepgrid"; + String sSql = String.format( + "SELECT book.username FieldCaption, book.fieldname, isnull(gcfg.fieldwidth, book.width) width, book.dataformat FROM %s book\n" + + "%s\n" + + "WHERE book.typeCode = '%s' AND book.stepCode = '%s' AND isnull(1 - gcfg.isvisible, isnull(book.isVisible, 0)) = 0 " + + "ORDER BY isnull(gcfg.orderid, book.orderid)", + tableName, // 对应{2} + gCfg, // 对应{3} + moduleId, // 对应{0} + stepCode // 对应{1} + ); + + // 执行SQL并返回结果(Java中用List替代DataTable) + return jdbcTemplate.queryForList(sSql); + } + + public int GetConditionPanleHeight(String condKey) { + int controlTop = 0; + String sql = "select isnull(controlTop, 0) as controlTop from p_systembillsourcecond where formkey = ?"; + String sqlWithParam = sql.replace("@fromKey", "?"); + + // 执行查询,传递参数fromKey,防止SQL注入 + // queryForList返回List,每个Map代表一行数据,键为列名 +// out.println("condKey: " + condKey); + List> dtval = jdbcTemplate.queryForList( + sqlWithParam, + condKey // 参数值,对应SQL中的?,自动进行预编译处理 + ); + + // 处理查询结果 + if (dtval.isEmpty()) { + controlTop = 0; + } else { + // 计算最大值并加34 + int maxControlTop = dtval.stream() + .mapToInt(row -> { + Object value = row.get("controlTop"); + if (value instanceof Number) { + return ((Number) value).intValue(); + } else { + return 0; + } + }) + // 计算最大值(对应 .Max()) + .max() + .orElseThrow(() -> new NoSuchElementException("没有找到数据,无法计算最大值")); + + controlTop = maxControlTop + 34; + } + + return controlTop; + } + + public int GetAttcViewModule(String moduleId, String userId) { + String sql = "select viewType from P_fm_WebCusSetTab where dllcoid=? and operatorId=?"; + // 查询结果可能为null,先获取Object再转换 + Object result = jdbcTemplate.queryForObject( + sql, + new Object[]{moduleId, userId}, + new int[]{Types.VARCHAR, Types.VARCHAR}, + Object.class + ); + return ToInt32(result); + } + + /** + * 获取附件父级ID + * moduleCode&idValue(附件),2005_1&moduleCode(帮助文档) + * + * @param moduleCode 基础模块或单据编号或2005_1 + * @param moduleCodeOrIdValue 基础模块或单据编号或记录主键值 + * @return 父级ID字符串 + */ + @Cacheable(value = "attcCache", key = "#moduleCode + '_' + #moduleCodeOrIdValue") + public String GetAttcParentId(String moduleCode, String moduleCodeOrIdValue, boolean isUpload) { + // 替换空格为加号 + moduleCodeOrIdValue = moduleCodeOrIdValue.replace(" ", "+"); + + // 查询dirid + String dirid; + try { + dirid = jdbcTemplate.queryForObject( + "select dirid from v_systemdlltab where DllCoid = ?", + new Object[]{moduleCode}, + String.class + ); + } catch (Exception e) { + dirid = null; + } + + // 处理dirid为空的情况 + if (dirid == null || dirid.trim().isEmpty()) { + dirid = "0"; + } + + // 查询目录表 + List> dirDt = jdbcTemplate.queryForList( + "select dirid, dllcoid from P_fm_DirectoryTab where ParentId = ? and PID = ?", + dirid, + moduleCodeOrIdValue + ); + + String dirTabid = ""; + String dirDllcoid = ""; + + // 处理查询结果 + if (dirDt != null && !dirDt.isEmpty()) { + Map row = dirDt.get(0); + dirTabid = String.valueOf(row.get("dirid")); + dirDllcoid = String.valueOf(row.get("dllcoid")); + } + + // 处理dirTabid为空的情况 + if (dirTabid == null || dirTabid.trim().isEmpty()) { + // 执行插入并获取自增ID + String insertSql = String.format( + "INSERT INTO P_fm_DirectoryTab(sName, ParentId, creator, speciesno, relationCode, PID, dllcoid) " + + "VALUES('%s', %s, 1, '0202', 0, '%s', '%s'); SELECT @@identity", + moduleCodeOrIdValue, dirid, moduleCodeOrIdValue, moduleCode + ); + log.debug(String.valueOf("inserSql : " + insertSql)); + + dirTabid = jdbcTemplate.queryForObject(insertSql, String.class); + + // 检查ID有效性 + int dirTabidInt; + int diridInt; + try { + dirTabidInt = Integer.parseInt(dirTabid); + diridInt = Integer.parseInt(dirid); + } catch (NumberFormatException e) { + dirTabidInt = -1; + diridInt = 0; + } + + if (dirTabidInt <= diridInt) { + dirTabid = jdbcTemplate.queryForObject( + "select dirid from P_fm_DirectoryTab where ParentId = ? and PID = ? and dllcoid = ?", + new Object[]{dirid, moduleCodeOrIdValue, moduleCode}, + String.class + ); + } + } + // 处理dirDllcoid为空的情况 + else if (dirDllcoid == null || dirDllcoid.trim().isEmpty()) { + jdbcTemplate.update( + "update P_fm_DirectoryTab set dllcoid = ? where dirid = ?", + moduleCode, + dirTabid + ); + } + + return dirTabid; + } + + public String GetAttcParentId(String moduleCode, String moduleCodeOrIdValue) { + return GetAttcParentId(moduleCode, moduleCodeOrIdValue, false); + } + + /** + * 获取附件文件列表 + * + * @param moduleId 模块ID + * @param idValue ID值 + * @param dirTabId 目录表ID + * @param speciesNo 种类编号 + * @param stepCode 步骤代码 + * @return 包含附件文件信息的列表,每个元素为一行数据的Map + */ + public List> GetAttcFiles(String moduleId, String idValue, String dirTabId, + String speciesNo, String stepCode) { + // 构建步骤条件 + String stepCon = isNullOrEmpty(stepCode) ? "" : String.format("and isnull(f.stepcode,0)='%s'", stepCode); + // 构建种类条件 + String specCon = isNullOrEmpty(speciesNo) ? "" : String.format("and (f.speciesno like '%s%%')", speciesNo); + + // 基础查询SQL + String dataSql = String.format( + "select p.EmployeeName username, f.* from P_fm_FileTab f " + + "left join p_employeetab p on f.creator = p.employeeid " + + "where f.parentid = '%s' %s %s", + dirTabId, stepCon, specCon + ); + + // 处理权限控制逻辑 + if (!toBoolean(WebConfigUtil_web.get("NAttcOper", "")) && HasProductSpecies(speciesNo)) { + // 获取用户名并移除空格 + String userName = getUser().UserName.replace(" ", ""); + // 构建带权限控制的查询SQL +// String sql = "select p.EmployeeName username, f.* from P_fm_FileTab f " + +// "inner join bmp_ProductSpeciesTab op on f.speciesno = op.speciesno and " + +// "(charindex(',%s,', ',' + replace(op.uploadOper, ' ', '') + ',') > 0 or isnull(op.uploadOper, '') = '' " + +// "or charindex(',%s,', ',' + replace(op.downloadOper, ' ', '') + ',') > 0 or isnull(op.downloadOper, '') = '' " + +// "or charindex(',%s,', ',' + replace(op.deleteOper, ' ', '') + ',') > 0 or isnull(op.deleteOper, '') = '' " + +// "or charindex(',%s,', ',' + replace(op.previewOper, ' ', '') + ',') > 0 or isnull(op.previewOper, '') = '') " + +// "left join p_employeetab p on f.creator = p.employeeid " + +// "where f.parentid = '%s' %s %s"; + String sql = this.sqlProvider.GetAttcFilesSql(); + dataSql = String.format(sql, userName, userName, userName, userName, dirTabId, stepCon, specCon); + } + + // 执行查询并转换列名为小写 + List> result = jdbcTemplate.queryForList(dataSql); + return ConversionUtils.toLowerColumnName(result); + } + + /** + * 检查产品种类是否存在 + * + * @param speciesNo 种类编号 + * @return 存在返回true,否则返回false + */ + private boolean HasProductSpecies(String speciesNo) { + if (isNullOrEmpty(speciesNo)) { + return false; + } + try { + String sql = String.format("select 1 from bmp_ProductSpeciesTab where speciesno = '%s'", speciesNo); + Object result = jdbcTemplate.queryForObject(sql, Object.class); + return DataTableUtil.toBoolean(result, false); + } catch (Exception e) { + log.warn(String.valueOf("执行SQL:hasProductSpecies方法错误" + e.getMessage())); + return false; + } + } + + /** + * 获取文件的绝对路径 + * + * @param fileId 文件ID + * @return 绝对路径字符串 + */ + public String GetAbsFilePath(String fileId) { + // 构建查询SQL,调用数据库函数获取绝对路径 + String asql = this.sqlProvider.GetAbsFilePathSql(); +// String sql = String.format("select dbo.fun_fm_getAbsolutePath(%s)", fileId); + String sql = String.format(asql, fileId); + + try { + // 执行查询并获取结果 + Object result = jdbcTemplate.queryForObject(sql, Object.class); + + // 处理结果:转换为字符串,去除换行和首尾空格 + return result != null ? result.toString().replace("\n", "").trim() : ""; + } catch (EmptyResultDataAccessException e) { + // 处理查询结果为空的情况 + return ""; + } + } + + /** + * 获取文件的绝对路径 + * + * @param dirTabId 文件ID + * @return 绝对路径字符串 + */ + public String GetAcFileFolder(String dirTabId) { + // 构建查询SQL,调用数据库函数获取绝对路径 + String asql = this.sqlProvider.GetAcFileFolderSql(); + String sql = String.format(asql, dirTabId); + + try { + // 执行查询并获取结果 + Object result = jdbcTemplate.queryForObject(sql, Object.class); + + // 处理结果:转换为字符串,去除换行和首尾空格 + return result != null ? result.toString().replace("\n", "").trim() : ""; + } catch (EmptyResultDataAccessException e) { + // 处理查询结果为空的情况 + return ""; + } + } + + public static int RelativePathPmsCount; + + /** + * 获取附件文件目录 + * moduleCode&idValue(附件),2005_1&moduleCode(帮助文档) + * + * @param moduleCode 基础模块或单据编号或2005_1 + * @param moduleCodeOrIdValue 基础模块或单据编号或记录主键值 + * @param specNo 规格编号 + * @param dirTabId 输出参数:目录表ID + * @return 文件目录路径 + */ + public String GetAcFileFolder(String moduleCode, String moduleCodeOrIdValue, String specNo, String[] dirTabId) { + // 获取目录表ID(通过引用传递输出) + dirTabId[0] = ""; // 清空 StringBuilder + dirTabId[0] = (GetAttcParentId(moduleCode, moduleCodeOrIdValue, true)); + + // 初始化参数计数 + if (RelativePathPmsCount == 0) { + RelativePathPmsCount = GetPmsCount("fun_fm_getRelativePath"); + } + log.debug(String.valueOf("dirTabId[0] : " + dirTabId[0].toString())); + + String path; + if (RelativePathPmsCount > 1) { + // 带specNo参数的存储过程调用 +// String sql = "select dbo.fun_fm_getRelativePath(?, ?)"; + String sql = this.sqlProvider.GetAcFileFolderByCount(); + path = jdbcTemplate.queryForObject(sql, String.class, dirTabId[0].toString(), specNo); + } else { + // 不带specNo参数的存储过程调用 +// String sql = "select dbo.fun_fm_getRelativePath(?)"; + String sql = this.sqlProvider.GetAcFileFolderAllCount(); + path = jdbcTemplate.queryForObject(sql, String.class, dirTabId[0].toString()); + } + log.debug(String.valueOf("path : " + path)); + // 处理结果:去除换行和首尾空格 + return path != null ? path.replace("\n", "").trim() : ""; + } + + /** + * 获取函数参数个数 + * + * @param name 函数名 + * @return 参数个数 + */ + public int GetPmsCount(String name) { + // 使用参数化查询防止SQL注入,适配SQL Server系统表查询 +// String sql = "SELECT COUNT(1) FROM sys.parameters p " + +// "WHERE p.object_id = OBJECT_ID(?) AND ISNULL(p.name, '') <> ''"; +// String sql = "SELECT array_length(proargnames,1) FROM sys_proc WHERE proname = ?"; + String sql = this.sqlProvider.GetPmsCountSql(); + // 执行查询并转换为整数,默认返回0 + return jdbcTemplate.queryForObject(sql, Integer.class, name); + } + + /** + * 数据保存方法 + * + * @param module 模块实体 + * @param baseSql 基础SQL语句 + * @param execType 执行类型 1新增,2为修改 + * @param comfirmFlag 确认标志 + * @return BaseResponse 响应结果 + */ + // 修正lambda表达式参数问题,使用PreparedStatementCreatorFactory处理 + public BaseResponse BaseDataSave(BaseModule module, String baseSql, int execType, Integer comfirmFlag) { + BaseResponse response = new BaseResponse(); + try { + // 步骤1:确定存储过程名称 + String procName = "p_BaseSave"; + if (module.getNewVer() && IsExitPro("p_BaseSave70")) { + procName = "p_BaseSave70"; + } + +// // 步骤2:处理参数中的单引号(转义为两个单引号,适配SQL语法) + String escapedBaseSql = baseSql.replace("'", "''"); // 处理SQL语句中的单引号 + String escapedModuleId = module.getModuleId() != null ? module.getModuleId().replace("'", "''") : ""; + String escapedMasterTable = module.getMasterTable() != null ? module.getMasterTable().replace("'", "''") : ""; + String escapedIdField = module.getIdField() != null ? module.getIdField().replace("'", "''") : ""; + String escapedIdValue = module.IdValue != null ? module.IdValue.replace("'", "''") : ""; + String escapedOperatorId = String.valueOf(ToInt32(getUser().UserId)).replace("'", "''"); + String escapedOperatorName = getUser().UserName != null ? getUser().UserName.replace("'", "''") : ""; +// +// // 步骤3:拼接完整SQL语句(与提供的SQL格式完全一致) +// // 修正后的SQL拼接逻辑(无BEGIN/END,无分号) +//// String sql = "DECLARE @returnValue varchar(max) " +//// + "DECLARE @outputValue varchar(max) " +//// + "EXEC @returnValue = " + procName + " " +//// + "'" + escapedBaseSql + "' ," // 末尾无分号,用空格结尾 +//// + "'" + execType + "' ," +//// + "'" + escapedModuleId + "', " +//// + "'" + escapedMasterTable + "' ," +//// + "'" + escapedIdField + "' ," +//// + "'" + escapedIdValue + "' ," +//// + "'" + escapedOperatorId + "' ," +//// + "'" + escapedOperatorName + "' ," +//// + "@outputValue OUTPUT " +//// + "SELECT @@ROWCOUNT AS execcount, @returnValue AS returnValue, @outputValue AS outputValue"; +//// // 步骤4:执行SQL并获取结果 +//// out.println("sql" + sql); + +// String procedureSql = "{call p_BaseSave70(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)}"; +// Map resultSet = jdbcTemplate.execute( +// procedureSql, +// (CallableStatementCallback>) cs -> { +// // ========== 1. 注册OUT参数(核心!必须先注册) ========== +// // 位置1:DM_return(OUT INT) +// cs.registerOutParameter(1, Types.INTEGER); +// // 位置10:DM_msg(OUT VARCHAR(2000),需指定长度) +// cs.registerOutParameter(10, Types.VARCHAR, 2000); +// +// // ========== 2. 设置IN参数(按位置顺序) ========== +// // 位置2:DM_baseSql(VARCHAR(8000),注意SQL语句长度不超过8000) +// cs.setString(2, baseSql); +// // 位置3:DM_saveType(INT) +// cs.setInt(3, execType); +// // 位置4:DM_modid(VARCHAR(20)) +// cs.setString(4, escapedModuleId); +// // 位置5:DM_tablename(VARCHAR(50)) +// cs.setString(5, escapedMasterTable); +// // 位置6:DM_keyfield(VARCHAR(50)) +// cs.setString(6, escapedIdField); +// // 位置7:DM_keyvalue(VARCHAR(50)) +// cs.setString(7, escapedIdValue); +// // 位置8:DM_operatorid(INT) +// cs.setInt(8, ToInt32(getUser().UserId)); +// // 位置9:DM_operatorname(VARCHAR(50)) +// cs.setString(9, escapedOperatorName); +// // 位置11:DM_comfirmFlag(INT,默认0) +// cs.setInt(11, 0); +// +// // ========== 3. 执行存储过程 ========== +// cs.execute(); +// +// // ========== 4. 获取OUT参数的值 ========== +// int dmReturn = cs.getInt(1); // 获取DM_return +// String dmMsg = cs.getString(10); // 获取DM_msg +// +// // 处理NULL值:如果存储过程未给DM_msg赋值,getString返回null +// if (cs.wasNull()) { +// dmMsg = ""; +// } +// +// // ========== 5. 封装结果 ========== +// Map resultMap = new HashMap<>(); +// resultMap.put("DM_return", dmReturn); +// resultMap.put("DM_msg", dmMsg); +// return resultMap; +// } +// ); + + + List>> resultSet = this.sqlProvider.BaseDataSaveSql( + procName, + escapedBaseSql, + execType, + escapedModuleId, + escapedMasterTable, + escapedIdField, + escapedIdValue, + escapedOperatorId, + escapedOperatorName + ); + // 步骤5:构建结果映射(保持原变量名) +// Map resultMap = new HashMap<>(); +// resultMap.put("execcount", resultSet.get("execcount")); // 对应@@ROWCOUNT +// resultMap.put("returnValue", resultSet.get("returnValue")); // 存储过程返回值 +// resultMap.put("outputValue", resultSet.get("outputValue")); // 输出参数值 + Map resultMap = new HashMap<>(); + for (List> maps : resultSet) { + { + for (Map row : maps) { + if (row != null) { + resultMap.putAll(row); + } + } + } + } + + // 步骤6:解析结果(保持原逻辑不变) + String rMsg = (String) resultMap.get("outputValue"); + Integer rValue = (Integer) resultMap.get("returnValue"); + if (rValue == null) rValue = 0; + response.setData(rValue); +// + if (rValue == 1) { + response.setSuccess(true); + response.setMsg(isNullOrEmpty(rMsg) ? LanguageUtil.Success : rMsg); + List>> data = new ArrayList<>(); + List> rows = new ArrayList<>(); + rows.add(resultMap); + data.add(rows); + response.setOther(DecodeSaveResult(data)); + } else if (rValue == 99) { + response.setMsg(rMsg); + response.setSuccess(true); + response.setOther(rValue); + } else { + response.setSuccess(false); + String debugConfig = WebConfigUtil.get("debug"); + boolean isDebug = Boolean.parseBoolean(debugConfig); + response.setMsg(isDebug ? + String.format("过程%s返回信息:

%s

", procName, + rMsg != null ? rMsg.replace("\r", "
") : "") : + (rMsg != null ? rMsg.replace("\r", "
") : "")); + response.setData(rValue); + } + + } catch (Exception e) { + response.setSuccess(false); + log.error("Exception caught", e); + response.setMsg("存储过程执行异常:" + e.getMessage().replace("\r", "
")); + } + return response; + } + + /** + * 检查存储过程是否存在 + * + * @param proName 存储过程名称 + * @return 是否存在 + */ + @Cacheable(value = "procedureCache", key = "#proName", unless = "#result == null") + public boolean IsExitPro(String proName) { + // 构建查询存储过程是否存在的SQL(适配SQL Server) + String asql = this.sqlProvider.IsExitProSql(); + String sql = String.format( + asql, + proName + ); + + // 执行查询并转换结果为布尔值 + Object result = null; + try { + result = jdbcTemplate.queryForObject(sql, Object.class); + } catch (Exception e) { + result = null; + } + return result != null; + } + + /** + * 解析保存结果数据集 + * + * @param dataSet 包含保存结果的数据集(模拟DataSet,使用List>表示多个表) + * @return 解析后的结果字典,key为GuidKey,value为UpdStrModule对象 + */ + public Map DecodeSaveResult(List>> dataSet) { + if (dataSet == null || dataSet.isEmpty()) { + return null; + } + + Map resultMap = new HashMap<>(); + + // 遍历数据集中的每个"表"(List模拟DataTable) + for (List> table : dataSet) { + if (!table.isEmpty()) { + // 获取表中第一行数据 + Map row = table.get(0); +// out.println(row + " row"); + // 根据行数据创建UpdStrModule对象 + UpdStrModule module = new UpdStrModule(row); + // 验证通过则添加到结果字典 + if (module.Valide()) { + resultMap.put(module.getGuidKey(), module); + } + } + } + + return resultMap; + } + + public Map DecodeSaveResult(ResultSet dataSet) { + List>> dataSetList = new ArrayList<>(); + List> rows = new ArrayList<>(); + rows = toListMap(dataSet); + dataSetList.add(rows); + return DecodeSaveResult(dataSetList); + } + + /** + * 场景1:ResultSet 转为单条记录(Map) + * 适用:存储过程返回1条数据(如单据详情、配置信息) + * + * @param rs 存储过程返回的 ResultSet + * @return 单条记录(key=列名,value=列值);无数据返回空Map,异常返回null + */ + public static Map toSingleMap(ResultSet rs) { + Map resultMap = new HashMap<>(); + if (rs == null) return resultMap; + + ResultSetMetaData metaData = null; + try { + // 1. 获取元数据(列名、列数) + metaData = rs.getMetaData(); + int columnCount = metaData.getColumnCount(); + + // 2. 读取单条记录(rs.next() 定位到第一行,无数据则返回空Map) + if (rs.next()) { + for (int i = 1; i <= columnCount; i++) { + // 列名转为小写(统一格式) + String columnName = metaData.getColumnName(i).toLowerCase(); + // 列值(处理null为空白字符串) + Object columnValue = rs.getObject(i); + columnValue = Objects.requireNonNullElse(columnValue, ""); + + resultMap.put(columnName, columnValue); + } + } + return resultMap; + + } catch (SQLException e) { + log.error("Exception caught", e); + return null; // 异常时返回null,上层可捕获处理 + } finally { + // 关闭ResultSet,避免资源泄漏 + closeResultSet(rs); + } + } + + /** + * 场景2:ResultSet 转为多条记录(List>) + * 适用:存储过程返回多条数据(如单据列表、明细集合) + * + * @param rs 存储过程返回的 ResultSet + * @return 多条记录集合;无数据返回空List,异常返回null + */ + public static List> toListMap(ResultSet rs) { + List> resultList = new ArrayList<>(); + if (rs == null) return resultList; + + ResultSetMetaData metaData = null; + try { + // 1. 获取元数据 + metaData = rs.getMetaData(); + int columnCount = metaData.getColumnCount(); + + // 2. 遍历所有记录(rs.next() 逐行读取,直到无数据) + while (rs.next()) { + Map rowMap = new HashMap<>(); + for (int i = 1; i <= columnCount; i++) { + String columnName = metaData.getColumnName(i).toLowerCase(); + Object columnValue = rs.getObject(i); + columnValue = Objects.requireNonNullElse(columnValue, ""); + + rowMap.put(columnName, columnValue); + } + resultList.add(rowMap); // 加入结果集 + } + return resultList; + + } catch (SQLException e) { + log.error("Exception caught", e); + return null; + } finally { + closeResultSet(rs); + } + } + + /** + * 工具方法:安全关闭 ResultSet(避免空指针和关闭异常) + */ + private static void closeResultSet(ResultSet rs) { + if (rs != null) { + try { + if (!rs.isClosed()) { + rs.close(); + } + } catch (SQLException e) { + log.error("Exception caught", e); + } + } + } + + /** + * 获取打印SQL配置 + * + * @param moduleId 模块ID + * @param recStr 记录字符串(未使用,保留参数兼容) + * @param isBase 是否为基础模块 + * @return 包含打印SQL的结果列表(模拟DataTable,用List表示) + */ + public List> GetPrintSqls(String moduleId, String recStr, boolean isBase) { + // 定义查询参数和SQL模板 + String masterTab; + String searchPms; + String whereClause; + + if (isBase) { + // 基础模块查询配置 + masterTab = "p_systemdlltab"; + searchPms = "printSql sql1, printsql1 sql2, printsql2 sql3, printtype sql4"; + whereClause = "dllcoid = ?"; + } else { + // 单据模块查询配置 + masterTab = "p_systembilltype"; + searchPms = "billprintSql sql1, billprintsql1 sql2, billprintsql2 sql3, billprinttype sql4"; + whereClause = "typecode = ?"; + } + + // 构建完整SQL + String querySql = String.format( + "select %s from %s where %s", + searchPms, + masterTab, + whereClause + ); + + // 使用JdbcTemplate执行参数化查询 + // 注意:这里将moduleId作为参数传入,防止SQL注入 + return jdbcTemplate.queryForList(querySql, moduleId); + } + + @Cacheable(value = "systemAttcPathCache", key = "'systemAttcPath'") + public String GetSystemAttcPath() { + final String sql = "select top 1 serverattachpath from p_SystemTab"; + + // 使用JdbcTemplate执行查询获取路径 + String path = jdbcTemplate.queryForObject(sql, String.class); + if (path == null) { + path = ""; + } + + // 调试信息(实际使用时可通过日志框架输出) + String databaseName = ConfigUtil.getConnectionKeyVal().getOrDefault("database", ""); +// System.out.println("getattc" + path + ":" + databaseName); + + return path; + } + + /** + * 获取系统OA地址 + * + * @return OA地址(去除末尾的斜杠) + */ + @Cacheable(value = "systemOAUrlCache", key = "'systemOAUrl'") + public String GetSystemOAUrl() { + final String sql = "select top 1 OAUrl from p_SystemTab"; + + // 执行查询,若查询结果为null则返回空字符串 + String oaUrl = jdbcTemplate.queryForObject(sql, String.class); + if (oaUrl == null) { + return ""; + } + + // 去除末尾的斜杠 + return oaUrl.trim().endsWith("/") ? oaUrl.trim().substring(0, oaUrl.trim().length() - 1) : oaUrl.trim(); + } + + /** + * 获取临时 * @param tempId 临时ID + * + * @return 临时附件文件列表(字段名转为小写) + */ + public List> GetTempAttcFiles(String tempId) { + // 使用参数化查询防止SQL注入,替换原字符串拼接 + String sql = "select f.* from P_fm_FileTab f " + + "inner join P_fm_DirectoryTab d on f.ParentId = d.dirId " + + "where d.PID = ?"; + + // 执行查询并获取结果集 + List> result = jdbcTemplate.queryForList( + sql, + tempId + ); + + // 将列名转换为小写(对应原ToLowerColumnName()方法) + return result.stream() + .map(DataTableUtil::toLowerKeyMap) + .collect(Collectors.toList()); + } + + /** + * 获取下一审核步骤的选择标记 + * 对应C#的GetNextSelectStepFlag方法 + */ + @Cacheable(value = "nextSelectStepFlag", key = "#moduleId + '_' + #stateEn.billType + '_' + #stateEn.stepCode", condition = "#result != null") + public Map GetNextSelectStepFlag(String moduleId, BillStateEn stateEn) { + Map resultMap = new HashMap<>(); + + // 构建查询表名(根据是否为基础模块选择不同表) + String tableName = IsBaseModule(moduleId) ? "p_systemdlltabflowtypestep" : "p_systembillflowtypestep"; + + // 第一个SQL查询:获取步骤选择标记和人员选择标记 + String firstSql = String.format( + "select nextStepSelect, nextOperSelect " + + "from %s " + + "where typecode = ? " + + "and billtype = ? " + + "and stepcode = ?", + tableName + ); + + // 执行第一个查询 + List> firstResult = jdbcTemplate.queryForList( + firstSql, + moduleId, + stateEn.BillType, + stateEn.StepCode + ); + + if (!firstResult.isEmpty()) { + // 保存第一个查询结果 + Map firstRow = firstResult.get(0); + resultMap.putAll(firstRow); + + // 处理NextStepCode,去除首尾逗号并替换为','分隔的字符串 + String nextStepCodes = stateEn.NextStepCode != null + ? stateEn.NextStepCode.trim().replaceAll("^,|,$", "").replace(",", "','") + : ""; + + if (!nextStepCodes.isEmpty()) { + // 第二个SQL查询:获取多人审核标记 + String secondSql = String.format( + "select stepcode, multiAudit " + + "from %s " + + "where typecode = ? " + + "and billtype = ? " + + "and stepcode in ('%s')", + tableName, + nextStepCodes + ); + + // 执行第二个查询 + List> secondResult = jdbcTemplate.queryForList( + secondSql, + moduleId, + stateEn.BillType + ); + + // 将结果添加到返回Map中 + for (Map row : secondResult) { + String stepCode = (String) row.get("stepcode"); + Object multiAudit = row.get("multiAudit"); + resultMap.put(stepCode, multiAudit); + } + } + } + + return resultMap; + } + + /** + * 获取单据状态信息 + * + * @param module 模块对象 + * @param stepCode 步骤编码(可选) + * @return 单据状态实体 + */ + public BillStateEn GetBillState(ModuleEntity module, String stepCode) throws CusException { + // 处理默认参数(stepCode默认空字符串) + stepCode = Optional.ofNullable(stepCode).orElse(""); + + // 原逻辑:module为空或idValue为空返回null + if (module == null || isNullOrEmpty(module.IdValue)) { + return null; + } + + String dataSql = ""; + String stepJoinOn; + String stepCodeCond; + + // 1. 拼接stepJoinOn和stepCodeCond(对齐原逻辑) + if (isNullOrEmpty(stepCode)) { + stepJoinOn = "g.stepgroup=f.stepgroup and g.stepcode=f.stepcode and g.typecode=f.typecode"; + stepCodeCond = String.format("IFNULL(m.%sstepcode,'0')", module.getMenuPrefix()); // 适配MySQL:isnull→IFNULL + } else { + stepJoinOn = String.format("f.stepcode=:stepCode and f.typeCode=:moduleId"); // 命名参数(避免SQL注入) + stepCodeCond = ":stepCode"; // 命名参数 + } + + // 2. 拼接核心SQL(区分BillModule和普通Module,适配JdbcTemplate参数化) + if (module instanceof BillModule) { + dataSql = String.format( + "select m.%scancelFlag cancelflag,%s stepcode,m.%sstepcode billstepcode,m.%sbilltype billtype,m.%sstepover stepover," + + "g.nextStepCode,g.StepClosed,f.id stepid,IFNULL(o.browsers, g.viewuser) viewuser,IFNULL(o.operators, g.operuser) operuser " + + "from %s m " + + "left join P_systembillflowtypestep g on m.%sbilltype=g.billtype and IFNULL(m.%sstepover, 0)=0 and g.typecode=:moduleId and g.stepcode=%s " + + "left join P_systembillflow f on %s " + + "left join wms_billflowOper%s o on o.modid=:moduleId and o.keyvalue=m.%s and o.billtype=m.%sbilltype and o.stepcode=%s " + + "where m.%s=:billno", + module.getMenuPrefix(), + stepCodeCond, + module.getMenuPrefix(), + module.getMenuPrefix(), + module.getMenuPrefix(), + module.getMasterTable(), + module.getMenuPrefix(), + module.getMenuPrefix(), + stepCodeCond, + stepJoinOn, + getExitFlowExOper() ? "ex" : "", + module.getIdField(), + module.getMenuPrefix(), + stepCodeCond, + module.getIdField() + ); + } else { + dataSql = String.format( + "select m.%scancelFlag cancelflag,%s stepcode,m.%sbilltype billtype,m.%sstepover stepover," + + "g.nextstepcode,g.StepClosed,f.id stepid,IFNULL(o.browsers, g.viewuser) viewuser,IFNULL(o.operators, g.operuser) operuser " + + "from %s m " + + "left join p_systemdlltabflowtypestep g on m.%sbilltype=g.billtype and IFNULL(m.%sstepover, 0)=0 and g.typecode=:moduleId and g.stepcode=%s " + + "left join p_systemdlltabflow f on %s " + + "left join p_baseflowOper%s o on o.modid=:moduleId and o.keyvalue=m.%s and o.billtype=m.%sbilltype and o.stepcode=%s " + + "where m.%s=:billno", + module.getMenuPrefix(), + stepCodeCond, + module.getMenuPrefix(), + module.getMenuPrefix(), + module.getMasterTable(), + module.getMenuPrefix(), + module.getMenuPrefix(), + stepCodeCond, + stepJoinOn, + getExitFlowExOper() ? "ex" : "", + module.getIdField(), + module.getMenuPrefix(), + stepCodeCond, + module.getIdField() + ); + } + + // 3. 构建参数Map(JdbcTemplate命名参数) + Map paramMap = new HashMap<>(); + paramMap.put("billno", module.IdValue); + paramMap.put("moduleId", module.getModuleId()); + if (!isNullOrEmpty(stepCode)) { + paramMap.put("stepCode", stepCode); + } + + // 4. 执行查询(映射为Map,模拟原FirstOrDefault逻辑) + List> resultList; + try { + resultList = namedJdbcTemplate.queryForList(dataSql, paramMap); + } catch (Exception e) { + // 捕获数据库异常(如SQL错误、连接异常) + throw new RuntimeException("查询单据状态失败", e); + } + // 无数据返回null + if (resultList == null || resultList.isEmpty()) { + return null; + } + Map dict = resultList.get(0); // 取第一条数据 + + // 5. 封装BillStateEn对象(严格对齐原逻辑) + BillStateEn stateEn = new BillStateEn(); + stateEn.billid = (module.IdValue); + stateEn.Canceled = (toBoolean(dict.get("cancelflag"))); + stateEn.NextStepCode = (Optional.ofNullable(dict.get("nextstepcode")).map(Object::toString).orElse("")); + stateEn.StepCode = (isNullOrEmpty(stepCode) ? + Optional.ofNullable(dict.get("stepcode")).map(Object::toString).orElse("") : stepCode); + stateEn.BillType = (toInt32(getStringValue(dict, "billtype"))); + stateEn.Finished = (toBoolean(dict.get("stepover"))); + stateEn.StepId = (toInt32(getStringValue(dict, "stepid"))); + stateEn.StepClosed = (toBoolean(dict.get("stepclosed"))); + + // 6. 处理ViewAble(out参数→数组传参) + String[] users = new String[1]; + stateEn.ViewAble = (CheckUserAble( + Optional.ofNullable(dict.get("viewuser")).map(Object::toString).orElse(""), + getUser().getUserName(), + module.Updrow, + users + )); + + // 7. 处理OperAble + stateEn.OperAble = (CheckUserAble( + Optional.ofNullable(dict.get("operuser")).map(Object::toString).orElse(""), + getUser().getUserName(), + module.Updrow, + users + )); + + // 8. 代理权限判断(适配JdbcTemplate) + if (!stateEn.OperAble && getExitSystemPrivilegeAgentTab() && !isNullOrEmpty(users[0])) { + String agentSql = String.format( + "select 1 from p_systemPrivilegeAgentTab x " + + "where modid=:moduleId and targetuser=:targetUser and LOCATE(CONCAT(',',sourceuser,','), CONCAT(',',:users,','))>0 " + + "and ban=1 and x.starttime<=NOW() and x.endtime>=NOW() " + + "limit 1" // MySQL替代top 1 + ); + Map agentParam = new HashMap<>(); + agentParam.put("moduleId", module.getModuleId()); + agentParam.put("targetUser", getUser().getUserName()); + agentParam.put("users", users[0]); + + // 执行标量查询 + Boolean agentResult = namedJdbcTemplate.queryForObject(agentSql, agentParam, Boolean.class); + stateEn.OperAble = (toBoolean(agentResult)); + } + + return stateEn; + } + + + /** + * 检查代理权限 + */ + private boolean CheckAgentPrivilege(String moduleId, String users) { + String sql = "select count(1) from p_systemPrivilegeAgentTab x " + + "where modid = ? " + + "and targetuser = ? " + + "and charindex(',' + sourceuser + ',', ?) > 0 " + + "and ban = 1 " + + "and x.starttime <= current_timestamp " + + "and x.endtime >= current_timestamp"; + + String currentUser = getUser().UserName; + String userParam = "," + users + ","; + + Integer count = jdbcTemplate.queryForObject( + sql, + new Object[]{moduleId, currentUser, userParam}, + Integer.class + ); + + return count != null && count > 0; + } + + /** + * 检查用户是否有权限 + * + * @param users 权限用户字符串 + * @param userName 用户名 + * @param updRow 数据行 + * @param outUsers 输出参数,用于返回处理后的用户字符串 + * @return 是否有权限 + */ + private boolean CheckUserAble(String users, String userName, Map updRow, String[] outUsers) throws CusException { +// outUsers[0] = ""; +// outUsers[0] = (users); +// +// if (users != null && !users.trim().isEmpty()) { +// // 处理单引号替换 +// users = users.replace("''", "'").trim(); +// +// // 处理以@开头的权限表达式 +// if (users.startsWith("@")) { +// String trimmedUsers = users.startsWith("@") ? users.substring(1) : users; +// +//// String userSql = "select employeename + ',' from p_employeetab " + +//// "where (" + PublicUtil.ReqSqlPmsByRow(updRow, null, trimmedUsers) + ")" + +//// "FOR XML PATH('');"; +// String userSql = this.sqlProvider.CheckUserAbleSql(updRow, trimmedUsers); +// +// try { +// // 执行SQL查询获取用户列表 +// users = jdbcTemplate.queryForObject(userSql, String.class); +// // 处理查询结果为空的情况 +// if (users == null) { +// users = ""; +// } +// } catch (Exception e) { +// // 抛出自定义异常,包含原始异常信息 +// throw new CusException(e.getMessage(), e) {{ +// Log = (true); +// Continue = (true); +// }}; +// } +// } +// +// // 更新输出参数 +// outUsers[0] = users; +// +// // 检查用户名是否在权限列表中 +// String formattedUsers = "," + users.replace(";", ",") + ","; +// String targetUser = "," + userName + ","; +// return formattedUsers.contains(targetUser); +// } +// +// // 当权限用户字符串为空时默认有权限 +// return true; + // 1. 初始化out参数(对应C#的_users = users) + outUsers[0] = users; + + // 2. 非空校验(对应C#的!string.IsNullOrEmpty(users)) + if (users != null && !users.trim().isEmpty()) { + // 3. 调用GetSqlUser处理用户字符串(核心逻辑) + outUsers[0] = GetSqlUser(users, "0", userName, updRow); + + // 4. 判断权限: + // - 格式化为 ",用户1,用户2," 形式 + // - 检查是否包含当前用户名,或用户是"管理员" + String formattedUsers = "," + outUsers[0].replace(";", ",") + ","; + String formattedUserName = "," + userName + ","; + return formattedUsers.contains(formattedUserName) || "管理员".equals(userName); + } + + // 若users为空,返回默认值(原C#代码未写else逻辑,默认返回false) + return true; + } + + /** + * 获取下一选择步骤列表 + * 对应C#的[Cache(ExpirationPeriod = 10)]缓存注解,使用Spring的@Cacheable替代 + */ + @Cacheable(value = "nextSelectStepListCache", key = "#typeCode + '_' + #nextSelectStepCode", unless = "#result == null") + public List> GetNextSelectStepList(String typeCode, String nextSelectStepCode) { + // 确定表名 + String tableName = IsBaseModule(typeCode) ? "p_systemdlltabflow" : "P_systembillflow"; + + // 构建SQL(注意:此处为了保持原逻辑未做参数化,实际生产需使用参数化查询防止注入) + String asql = this.sqlProvider.GetNextSelectStepListSql(); +// String sql = String.format( +// "select stepcode, stepname from %s " + +// "where typecode='%s' " + +// "and charindex(';' + cast(stepcode as varchar) + ';', ';%s;') > 0", +// tableName, +// typeCode, +// nextSelectStepCode +// ); + String sql = String.format( + asql, + tableName, + typeCode, + nextSelectStepCode + ); + + // 执行查询,返回结果集(List对应C#的DataTable) + return jdbcTemplate.queryForList(sql); + } + + /** + * 获取下一选择操作人列表 + */ + @Cacheable(value = "nextSelectOperListCache", key = "#nextSelectStepOper", unless = "#result == null") + public List> GetNextSelectOperList(String nextSelectStepOper) { + // 处理字符串分割和去重 + // 替换原字符串处理逻辑,使用Java原生API替代StringUtils.join + String[] stepOpers = nextSelectStepOper.split(";"); +// 使用StringJoiner拼接数组为字符串 + StringJoiner joinedJoiner = new StringJoiner(","); + Arrays.stream(stepOpers).forEach(joinedJoiner::add); + String joined = joinedJoiner.toString(); + String[] splitIds = joined.split(","); + + List ids = new ArrayList<>(); + for (String id : splitIds) { + if (id != null && !id.isEmpty() && !ids.contains(id)) { + ids.add(id); + } + } + +// 构建SQL时同样使用StringJoiner处理in条件 + StringJoiner inValues = new StringJoiner("','"); + ids.forEach(inValues::add); + String sql = String.format( + "select employeeid as userid, EmployeeName as username " + + "from P_EmployeeTab " + + "where EmployeeName in ('%s') " + + "and isnull(sign, 0) = 0 " + + "and UseFlag = 1", + inValues.toString() + ); + // 执行查询 + return jdbcTemplate.queryForList(sql); + } + + /** + * 获取审核信息窗口大小 + * + * @param moduleId 模块ID + * @return 包含窗口高度、宽度和类型代码的结果集(List模拟DataTable) + */ + @Cacheable(value = "auditWindowSizeCache", key = "#moduleId", unless = "#result == null") + public List> GetAuditWindowSize(String moduleId) { + // 根据是否为基础模块执行不同SQL + if (!IsBaseModule(moduleId)) { + // 非基础模块查询语句,使用参数化查询防止SQL注入 + String sql = "select parentHeight as height, parentWidth as width, dll.typecode " + + "from p_systemControlParent c " + + "inner join p_systembilltype dll on c.formkey = dll.formkey " + + "where dll.typecode = ?"; + return jdbcTemplate.queryForList(sql, moduleId); + } else { + // 基础模块查询语句,参数化查询 + String sql = "select parentHeight as height, parentWidth as width, dll.dllcoid " + + "from p_systemControlParent c " + + "inner join p_systemdlltab dll on c.formkey = dll.formkey " + + "where dll.dllcoid = ?"; + return jdbcTemplate.queryForList(sql, moduleId); + } + } + + /** + * 基础提交/撤销操作 + * + * @param module 基础模块实体 + * @param stateEn 单据状态实体 + * @param applyType 操作类型:1-提交,2-撤销(默认1) + * @return 基础响应对象 + */ + public BaseResponse BaseApply(BaseModule module, BillStateEn stateEn, int applyType) { + BaseResponse response = new BaseResponse(); + String storeName = "p_baseApply"; + LoginUserInfo user = getUser(); + try { + Map pms = new HashMap<>(); + // 添加基础参数 + pms.put("@typeCode", module.getModuleId()); + pms.put("@billDocumentId", module.IdValue); + pms.put("@comfirmType", applyType); + pms.put("@Operatorid", user.UserId); + pms.put("@operatorname", user.UserName); + + // 检查是否需要添加@comfirmflag参数 + if (hasStoreParameter(storeName, "@comfirmflag")) { + pms.put("@comfirmflag", stateEn.comfirmFlag); + } + // 调用执行存储过程的方法 + response = ExecSelectOperStore(storeName, module, stateEn, pms); + } catch (Exception e) { + response.setSuccess(false); + response.setMsg(e.getMessage().replace("\r", "
")); + response.setData(0); + } + + return response; + } + + /** + * 获取基础流程步骤 + * + * @param idValue 标识符值 + * @param isBase 是否为基础流程 + * @return 流程步骤列表(模拟DataTable,使用List表示): + */ + public List> GetBaseFlowStep(String idValue, boolean isBase) { + // 1. 空值校验(避免无效查询) + if (idValue == null || idValue.trim().isEmpty()) { + return List.of(); // 返回空列表(替代原C#的空DataTable) + } + + // 2. 确定查询表名(根据isBase判断) + String tableName = isBase ? "p_baseflowstep" : "wms_billflowstep"; + + // 3. 拼接SQL(使用参数化避免SQL注入,适配达梦的REPLACE函数) + String sql = "select " + + // 嵌套REPLACE替换操作方向文本(对应原C#的replace嵌套) + "REPLACE(REPLACE(REPLACE(REPLACE(operdirection, 'r', '已退回'), 'f', '已审核'), 'z', '转发'), 'j', '转交') as sh, " + + "operdirection, " + + "stepname, " + + "operadvice, " + + "operatorname, " + + "applytime, " + + "stepcode, " + + "operdirection as sta " + // 别名sta + "from " + tableName + " " + + "where flowsourcekey = :idValue " + // 参数化idValue,避免注入 + "and COALESCE(autostep, 0) = 0 " + // 替换C#的isnull(autostep,0)=0 + "order by id desc"; // 按ID倒序 + + // 4. 构建参数(绑定idValue) + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("idValue", idValue.trim()); + + // 5. 执行查询(对应C#的ExecuteDataTable) + return namedJdbcTemplate.queryForList(sql, params); + } + + /** + * 获取模板id + * + * @param moduleId 模块ID + * @return 模板ID + */ + @Cacheable(value = "moduleAddTplIdCache", key = "#moduleId", unless = "#result == null") + public int GetModuleAddTplId(String moduleId) { + String sql = String.format("select top 1 id from P_pubrpsettab where tab = '%s' and ban = 1 order by orderid", moduleId); + Object result = null; + try { + result = jdbcTemplate.queryForObject(sql, Integer.class); + } catch (Exception e) { + result = 0; + log.debug(String.valueOf("GetModuleAddTplId: " + e.getMessage())); + } + + return NativeExtensionUtils.ToInt32(result); + } + + public int GetAuditStepCount(String moduleId, boolean b) { +// return crmapper.getAuditStepCount(moduleId, b); + return getDetailJDBC().GetAuditStepCount(moduleId, b); + } + + /** + * 获取单据来源项 + * + * @param moduleCode 模块代码 + * @param ids 来源ID列表(逗号分隔) + * @param sourceType 来源类型(默认"0,1") + * @return 结果集列表,每个元素为一行数据的Map + */ + public List> GetBillSourceItem(String moduleCode, String ids, String sourceType) { + // 处理默认参数 + if (sourceType == null || sourceType.isEmpty()) { + sourceType = "0,1"; + } + + ensureRepeatCheckColumn(); + + // 处理ids参数,移除首尾逗号并转换为SQL IN格式 + String processedIds = (ids == null ? "" : ids.trim()).replaceAll("^,|,$", ""); + String idInClause = processedIds.isEmpty() ? "" : + processedIds.replace(",", "','"); + + // 构建SQL语句 + String sql = String.format( + "select id, username ,REPEATCHECK from p_systembillsource " + + "where typecode = ? " + + "and (isvisible is null or isvisible = 0) " + + "and sourceType in (%s) " + + "and ('%s' = '' or id in ('%s'))", + sourceType, + processedIds, + idInClause + ); + + + // 执行查询(使用注入的NamedParameterJdbcTemplate) + return jdbcTemplate.queryForList(sql, moduleCode); + } + + private boolean repeatCheckColumnExists() { + String checkSql = + "select count(1) " + + "from all_tab_columns " + + "where upper(table_name) = upper(?) " + + "and upper(column_name) = upper(?)"; + + Integer count = jdbcTemplate.queryForObject( + checkSql, + Integer.class, + "P_SYSTEMBILLSOURCE", + "REPEATCHECK" + ); + + return count != null && count > 0; + } + + private void ensureRepeatCheckColumn() { + if (repeatCheckColumnExists()) { + return; + } + + try { + // 添加 repeatcheck 字段,int 类型,默认值 0 + jdbcTemplate.execute( + "alter table p_systembillsource add repeatcheck int default 0" + ); + + // 保险处理:如果已有数据字段为 null,则更新成 0 + jdbcTemplate.update( + "update p_systembillsource set repeatcheck = 0 where repeatcheck is null" + ); + + } catch (Exception e) { + /* + * 防止并发情况: + * A线程判断字段不存在,准备添加; + * B线程也判断字段不存在,也准备添加; + * A添加成功,B再添加就会报字段已存在。 + * + * 所以这里再查一次,如果字段已经存在,就忽略异常。 + */ + if (!repeatCheckColumnExists()) { + throw e; + } + } + } + + // 自定义子类以访问 protected 方法 + class CustomJdbcCall extends SimpleJdbcCall { + public CustomJdbcCall(JdbcTemplate jdbcTemplate) { + super(jdbcTemplate); + } + + // 提供公共方法访问父类的 protected 方法 + public List getProcedureParameters() { + return super.getCallParameters(); + } + } + + /** + * 重载方法,提供默认applyType=1 + */ + public BaseResponse BaseApply(BaseModule module, BillStateEn stateEn) { + return BaseApply(module, stateEn, 1); + } + + /** + * 处理存储过程执行结果 + */ + private BaseResponse handleProcedureResult(Map result, BaseModule module, BillStateEn + stateEn) { + BaseResponse response = new BaseResponse(); + // 根据实际存储过程返回结果结构处理 + // 示例:假设存储过程返回success和msg字段 + if (result.containsKey("success")) { + response.setSuccess((Boolean) result.get("success")); + } + if (result.containsKey("msg")) { + response.setMsg((String) result.get("msg")); + } + if (result.containsKey("data")) { + response.setData(result.get("data")); + } + return response; + } + + /** + * 删除附件文件信息 + * + * @param dirTabId 目录表ID + * @param moduleId 模块ID + * @param specNo 规格编号 + * @param fileName 文件名 + * @param fileId 文件ID(可选) + * @return 受影响的行数 + */ + public int DelAttcFileInfo(String dirTabId, String moduleId, String specNo, String fileName, String fileId) { + // 如果fileId不为空,优先按fileId删除 + if (fileId != null && !fileId.isEmpty()) { + String sql = "delete from P_fm_FileTab where fileId = ?"; + return jdbcTemplate.update(sql, fileId); + } + + // 按parentid和文件名删除 + String sql = "delete from P_fm_FileTab where parentid = ? and isnull(vname, sname) = ?"; + int result = jdbcTemplate.update(sql, dirTabId, fileName); + + // 如果删除失败且模块ID和规格编号不为空,按规格编号、模块ID和文件名删除 + if (result <= 0 && moduleId != null && !moduleId.isEmpty() + && specNo != null && !specNo.isEmpty()) { + sql = "delete from P_fm_FileTab where speciesno = ? and dllcoid = ? and isnull(vname, sname) = ?"; + result = jdbcTemplate.update(sql, specNo, moduleId, fileName); + } + + return result; + } + + // 重载方法,处理fileId为默认空字符串的情况 + public int DelAttcFileInfo(String dirTabId, String moduleId, String specNo, String fileName) { + return DelAttcFileInfo(dirTabId, moduleId, specNo, fileName, ""); + } + + /** + * 获取附件操作信息 + * + * @param operType 操作类型:1=上传,2=下载,3=删除,4=锁定,5=解除锁定,6=作废,7=取消作废,8=预览(必填) + * @param fileName 文件名图档分类,上传必填,其他填"" + * @param dirTabId 目录标签ID + * @param fileSize 文件大小,上传必填 + * @param userId 操作员ID,必填 + * @param userName 操作员姓名,必填 + * @param specNo 图档分类,上传必填,其他填"" + * @return BaseResponse 操作结果 + */ + public BaseResponse GetAttcOperInfo(int operType, String fileName, String dirTabId, + long fileSize, String userId, String userName, String specNo) throws SQLException { + return GetAttcOperInfo(operType, fileName, dirTabId, fileSize, userId, userName, specNo, + 0, "", "", 0); + } + + + /** + * 获取附件操作信息(重载方法) + * + * @param operType 操作类型 + * @param fileName 文件名 + * @param dirTabId 目录标签ID + * @param fileSize 文件大小 + * @param userId 操作员ID + * @param userName 操作员姓名 + * @param specNo 图档分类 + * @param fileId 文件ID + * @param moduleId 模块ID + * @param idValue ID值 + * @param comfirm 确认标识 + * @return BaseResponse 操作结果 + */ + /** + * 获取附件操作信息(重载方法) + */ + public BaseResponse GetAttcOperInfo(int operType, String fileName, String dirTabId, + long fileSize, String userId, String userName, String specNo, + int fileId, String moduleId, String idValue, int comfirm) throws SQLException { // 改为方法参数传入连接 + BaseResponse response = new BaseResponse(); + Connection connection = null; // 初始化连接为null + PreparedStatement pstmt = null; + ResultSet rs = null; + CallableStatement cstmt = null; + + try { + // 获取连接 + connection = jdbcTemplate.getDataSource().getConnection(); + + // 校验条件,满足则直接返回成功 + if (userId == null || userId.isEmpty() || "管理员".equals(userName) || "0".equals(userId) || + toBoolean(WebConfigUtil_web.get("NAttcOper")) || specNo == null || specNo.isEmpty()) { + response.setSuccess(true); + return response; // 即使提前return,finally块也会执行关闭逻辑 + } + + // 如果是下载或删除操作且fileId为0,则查询获取fileId + if (fileId == 0 && (operType == 2 || operType == 3)) { + String fileIdSql = "select fileId from P_fm_FileTab f where " + + "isnull(f.vname, f.sName) = ? and f.speciesno like ?"; + + pstmt = connection.prepareStatement(fileIdSql); + pstmt.setString(1, fileName); + pstmt.setString(2, specNo + "%"); + + rs = pstmt.executeQuery(); + if (rs.next()) { + fileId = rs.getInt("fileId"); + } + + // 关闭ResultSet和PreparedStatement + if (rs != null) { + rs.close(); + rs = null; + } + if (pstmt != null) { + pstmt.close(); + pstmt = null; + } + } + + // 直接设置参数存在性(根据实际存储过程修改这些值) + boolean operStoreHasMPms = true; // 假设存储过程有这些参数 + boolean operStoreHasComfirmPms = true; + + // 调用存储过程 - 根据参数情况构建调用语句 + StringBuilder procedureCall = new StringBuilder("{call p_SystemAttachOperation("); + + // 基础参数数量 + int paramCount = 7; // 输入参数 + paramCount += 2; // 输出参数(@msg, @return) + + // 添加额外参数 + if (operStoreHasMPms) { + paramCount += 2; // @dllcoid, @idValue + } + if (operStoreHasComfirmPms) { + paramCount += 1; // @comfirmflag + } + + // 构建参数占位符 + for (int i = 0; i < paramCount; i++) { + if (i > 0) { + procedureCall.append(", "); + } + procedureCall.append("?"); + } + procedureCall.append(")}"); + + cstmt = connection.prepareCall(procedureCall.toString()); + + // 设置输入参数 + int paramIndex = 1; + cstmt.setInt(paramIndex++, operType); + cstmt.setString(paramIndex++, specNo); + cstmt.setInt(paramIndex++, fileId); + cstmt.setString(paramIndex++, fileName); + cstmt.setLong(paramIndex++, fileSize); + + // 设置用户ID(添加异常处理,防止非数字userId) + try { + cstmt.setInt(paramIndex++, Integer.parseInt(userId)); + } catch (NumberFormatException e) { + throw new SQLException("用户ID格式错误:" + userId, e); + } + cstmt.setString(paramIndex++, userName); + + // 注册输出参数 + cstmt.registerOutParameter(paramIndex++, Types.VARCHAR); // @msg + cstmt.registerOutParameter(paramIndex++, Types.INTEGER); // @return + + // 添加额外参数(如果存储过程需要) + if (operStoreHasMPms) { + cstmt.setString(paramIndex++, moduleId); + cstmt.setString(paramIndex++, idValue); + } + + if (operStoreHasComfirmPms) { + cstmt.setString(paramIndex++, String.valueOf(comfirm)); + } + + // 执行存储过程 + boolean hasResults = cstmt.execute(); + + // 处理结果集 + if (hasResults) { + rs = cstmt.getResultSet(); + // 处理结果集,类似DecodeSaveResult方法的实现 + } + + // 获取输出参数值 + int returnValueIndex = paramIndex - (operStoreHasComfirmPms ? 1 : 0) - 1; + int msgIndex = returnValueIndex - 1; + + int returnValue = cstmt.getInt(returnValueIndex); + String msg = cstmt.getString(msgIndex); + + response.setData(returnValue); + + if (returnValue == 1) { + response.setSuccess(true); + response.setMsg(LanguageUtil.Success); + // 处理结果集到response.other + } else { + response.setSuccess(false); + response.setMsg((msg + ",节点编号:" + specNo).replace("\r", "
")); + response.setData(returnValue); + } + + } catch (SQLException e) { + response.setSuccess(false); + response.setMsg(e.getMessage().replace("\r", "
节点编号:" + specNo)); + response.setData(0); + log.error("Exception caught", e); + throw e; // 可选:向上抛出异常,让上层感知 + } catch (Exception e) { + response.setSuccess(false); + response.setMsg(e.getMessage().replace("\r", "
节点编号:" + specNo)); + response.setData(0); + log.error("Exception caught", e); + throw new SQLException("执行附件操作失败", e); // 包装为SQLException抛出 + } finally { + // 确保所有资源都被关闭,顺序:ResultSet -> Statement -> Connection + try { + if (rs != null) rs.close(); + } catch (SQLException e) { + log.error("Exception caught", e); + } + try { + if (pstmt != null) pstmt.close(); + } catch (SQLException e) { + log.error("Exception caught", e); + } + try { + if (cstmt != null) cstmt.close(); + } catch (SQLException e) { + log.error("Exception caught", e); + } + // 关键修复:关闭连接,归还到连接池 + try { + if (connection != null) { + connection.close(); + } + } catch (SQLException e) { + log.error("Exception caught", e); + } + } + + return response; + } + + public List> GetAttcFileInfo(String parntId, String moduleId, String name, int fileId) { + String sql = "select top 1 * from P_fm_FileTab where dllcoid= ? and parentid=? and isnull(vname, sname)= ? "; + if (fileId > 0) { + return jdbcTemplate.queryForList("select * from P_fm_FileTab where fileId= ?", fileId); + } + return jdbcTemplate.queryForList(sql, moduleId, parntId, name); + } + + /** + * 获取模块附件规格编号 + * + * @param moduleCode 模块代码 + * @return 规格编号字符串 + */ + public String GetAttcBmpSpec(String moduleCode) { +// String sql = "select bmpSpec from dbo.v_systemdlltab where DllCoid= '%s'"; + String sql = this.sqlProvider.GetAttcBmpSpecSql(); + return jdbcTemplate.queryForObject(String.format(sql, moduleCode), String.class); + } + + /** + * 获取自增列字段名 + * + * @param tabname 表名 + * @return 自增列字段名 + */ + @Cacheable(value = "identityFieldCache", key = "#tabname", unless = "#result == null") + public String GetIdentityField(String tabname) { + // 处理表名,去除首尾空白和换行符 + tabname = tabname.trim().replaceAll("[\r\n]", ""); + + String[] tbName = new String[1]; + // 获取目标数据库操作器和处理后的表名 + JdbcTemplate jdbc = GetOtherDbOper(tabname, tbName); + // 构建查询自增列的SQL +// String sql = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.columns " + +// "WHERE TABLE_NAME = '%s' AND COLUMNPROPERTY(OBJECT_ID('%s'), COLUMN_NAME, 'IsIdentity') = 1"; +// String identitySql = String.format(sql, tbName[0], tbName[0]); + String finaltbName = tbName[0]; + String identitySql = this.sqlProvider.GetIdentityFieldSql(finaltbName); + + + // 执行查询,默认自增列为"id" + String identityField = "id"; + try { + // 执行SQL并获取结果,转换为小写 + Object scalarResult = jdbc.queryForObject(identitySql, Object.class); + if (scalarResult != null) { + identityField = scalarResult.toString().toLowerCase(); + } + } catch (Exception e) { + // 异常情况下使用默认值 +// e.printStackTrace(); + identityField = "".toLowerCase(); + } + + return identityField; + } + + /** + * 单据提交/撤回操作 + * + * @param module 单据模块信息 + * @param stateEn 单据状态实体 + * @param applyType 提交类型:1-提交,2-撤回 + * @return 基础响应对象 + */ + public BaseResponse BillApply(BillModule module, BillStateEn stateEn, int applyType) { + // 记录系统日志 + String operation = (applyType == 2) ? "撤回" : "提交"; + String logContent = String.format("%s->%s%s%s", + module.getMenuName(), + getUser().UserName, + operation, + module.IdValue); + sysLog(logContent, "单据审批"); + + BaseResponse response = new BaseResponse(); + try { + // 准备存储过程参数 + Map parameters = new LinkedHashMap<>(); + parameters.put("@typeCode", module.getTypeCode()); + parameters.put("@billDocumentId", module.IdValue); + parameters.put("@operatorId", NativeExtensionUtils.parseInt(getUser().UserId)); + parameters.put("@operatorName", getUser().UserName); + parameters.put("@comfirmType", String.valueOf(applyType)); + + // 检查存储过程是否包含@comfirmflag参数 + if (hasStoreParameter("p_billApply", "@comfirmflag")) { + parameters.put("@comfirmflag", stateEn.comfirmFlag); + } + + sysLog(logContent + parameters.toString(), "单据审批"); + + return ExecSelectOperStore("p_billApply", module, stateEn, parameters); + + } catch (Exception e) { + response.setSuccess(false); + response.setMsg(String.format("提交失败%s", e.getMessage().replace("\r", "
"))); + response.setData(0); + } + return response; + } + + /** + * 执行可能需要选择人员的存储过程 + * + * @param storeName 存储过程名称 + * @param module 模块信息 + * @param stateEn 账单状态枚举 + * @param params 输入参数集合 + * @return 基础响应对象 + */// 假设该注解已在项目中定义 + public BaseResponse ExecSelectOperStore(String storeName, ModuleEntity module, + BillStateEn stateEn, Map params) throws CusException { + BaseResponse response = new BaseResponse(); + boolean isSelectOper = false; + Connection connection = null; + CallableStatement callableStmt = null; + ResultSet resultSet = null; + ResultSet targetResultSet = null; // 用于存储最终需要的结果集 + + try { + // -------------------- 1. 预处理参数(不变) -------------------- + List outParamNames = new ArrayList<>(); + outParamNames.add("RETURN_VALUE"); + if (hasStoreParameter(storeName, "p_selectConfirmFlag")) { + isSelectOper = true; + params.put("p_selectConfirmFlag", stateEn.getSelectConfirmFlag()); + outParamNames.add("@nextSelectStepCode"); + outParamNames.add("@nextSelectStepOper"); + } + +// // -------------------- 2. 拼接SQL(保持你的格式) -------------------- +// StringBuilder sqlBuilder = new StringBuilder(); +// sqlBuilder.append("DECLARE @returnValue int "); +// for (String outParam : outParamNames) { +// if (outParam.equals("@msg")) { +// sqlBuilder.append("DECLARE ").append(outParam).append(" varchar(2000) "); +// } else { +// sqlBuilder.append("DECLARE ").append(outParam).append(" varchar(500) "); +// } +// } +// sqlBuilder.append("EXEC @returnValue = [dbo].").append(storeName).append("\n"); +// +// List inputParamParts = new ArrayList<>(); +// for (Map.Entry entry : params.entrySet()) { +// String paramName = entry.getKey(); +// Object paramValue = entry.getValue(); +// String paramPart; +// if (paramValue == null || paramValue.toString().isEmpty()) { +// paramPart = "\t" + paramName + " = ''"; +// } else if (paramValue instanceof String +// || paramValue instanceof Character +// || paramValue instanceof java.util.Date) { +// String escapedValue = paramValue.toString().replace("'", "''"); +// paramPart = "\t" + paramName + " = '" + escapedValue + "'"; +// } else { +// paramPart = "\t" + paramName + " = " + paramValue.toString(); +// } +// inputParamParts.add(paramPart); +// } +// +// List outParamParts = new ArrayList<>(); +// for (String outParam : outParamNames) { +// outParamParts.add("\t" + outParam + " = " + outParam + " OUTPUT"); +// } +// +// for (int i = 0; i < inputParamParts.size(); i++) { +// sqlBuilder.append(inputParamParts.get(i)); +// if (i != inputParamParts.size() - 1 || !outParamParts.isEmpty()) { +// sqlBuilder.append(",\n"); +// } else { +// sqlBuilder.append("\n"); +// } +// } +// for (int i = 0; i < outParamParts.size(); i++) { +// sqlBuilder.append(outParamParts.get(i)); +// if (i != outParamParts.size() - 1) { +// sqlBuilder.append(",\n"); +// } +// } +// +// sqlBuilder.append("\nSELECT\n"); +// sqlBuilder.append("\t@returnValue AS returnValue"); +// for (String outParam : outParamNames) { +// sqlBuilder.append(",\n\t").append(outParam).append(" AS ").append(outParam.replace("@", "")); +// } +// sqlBuilder.append(";"); +// +// String finalSql = sqlBuilder.toString(); +// out.println("拼接的存储过程调用SQL:" + finalSql); +// +// // -------------------- 3. 执行SQL并获取正确的结果集(核心简化处理) -------------------- +// connection = jdbcTemplate.getDataSource().getConnection(); +// callableStmt = connection.prepareCall(finalSql); +// 执行并处理多结果集(仅保留找到目标结果集的逻辑,去掉冗余判断) +// boolean hasMoreResults = callableStmt.execute(); +// while (hasMoreResults) { +// resultSet = callableStmt.getResultSet(); +// if (resultSet != null) { +// // 检查当前结果集是否包含returnValue列(目标结果集) +// try { +// if (resultSet.getMetaData().getColumnCount() > 0 +// && resultSet.getMetaData().getColumnName(1).equals("returnValue")) { +// targetResultSet = resultSet; // 找到目标结果集,跳出循环 +// break; +// } +// } catch (SQLException e) { +// // 忽略无效结果集的错误 +// } +// // 非目标结果集直接关闭 +// if (resultSet != targetResultSet) { +// resultSet.close(); +// } +// } +// hasMoreResults = callableStmt.getMoreResults(); +// } +// +// // 解析目标结果集(如果找到) +// Map resultMap = new HashMap<>(); +// if (targetResultSet != null) { +// if (targetResultSet.next()) { +// resultMap.put("RETURN_VALUE", targetResultSet.getInt("returnValue")); +// for (String outParam : outParamNames) { +// String colName = outParam.replace("@", ""); +// resultMap.put(outParam, targetResultSet.getString(colName)); +// } +// } +// } + Map resultMap = this.sqlProvider.ExecSelectOperStoreSql(outParamNames, storeName, params).get(0); + String rMsg = ""; + int rVaule = -1; + // -------------------- 4. 组装响应(保持你的逻辑) -------------------- + if (resultMap != null && !resultMap.isEmpty()) { + rMsg = Objects.toString(resultMap.get("msg")); + Object vale = resultMap.get("returnCode"); + rVaule = vale == null ? -1 : ToInt32(vale); + } + // 处理未找到结果集的情况 + response.setData(rVaule); + + log.debug(String.valueOf(rVaule + "获取:returnCode")); + + if (rVaule == 1) { + response.setSuccess(true); + response.setMsg(LanguageUtil.Success); + BillStateEn staen = GetBillState(module, ""); + if (staen != null) { + Map other = new HashMap<>(); + other.put("stepCode", staen.StepCode); + response.setOther(other); + } + } else if (rVaule == -10 && isSelectOper) { + response.setSuccess(true); + response.setOther(rVaule); + + String nextSelectStepOper = (String) resultMap.get("@nextSelectStepOper"); + StringBuilder nexetSelOpers = new StringBuilder(); + if (nextSelectStepOper != null) { + String[] parts = nextSelectStepOper.split(";"); + for (String part : parts) { + String[] operParts = part.split(","); + Set uniqueOps = new LinkedHashSet<>(Arrays.asList(operParts)); + nexetSelOpers.append(String.join(",", uniqueOps)).append(";"); + } + } + + Map data = new HashMap<>(); + data.put("moduleId", module.getModuleId()); + data.put("idValue", module.IdValue); + data.put("selectConfirmFlag", 1); + data.put("nextSelectStepCode", resultMap.get("@nextSelectStepCode")); + data.put("nextSelectStepOper", nexetSelOpers.toString()); + data.put("flags", GetNextSelectStepFlag(module.getModuleId(), stateEn)); + data.put("stepList", GetNextSelectStepList(module.getModuleId(), (String) resultMap.get("@nextSelectStepCode"))); + data.put("operList", GetNextSelectOperList(nexetSelOpers.toString())); + + response.setData(data); + } else if (rVaule == 99) { + response.setMsg(rMsg); + response.setSuccess(true); + response.setOther(rVaule); + } else { + if (rVaule == 9) { + Map data = new HashMap<>(); + data.put("moduleId", module.getModuleId()); + data.put("idValue", module.IdValue); + response.setData(data); + } else { + response.setData(null); + } + response.setOther(rVaule); + response.setSuccess(false); + + boolean isDebug = NativeExtensionUtils.toBoolean(WebConfigUtil_web.get("debug", "")); + if (isDebug) { + response.setMsg(String.format("

%s

", rMsg.replace("\r", "
"))); + } else { + response.setMsg(rMsg.replace("\r", "
")); + } + } + + } catch (Exception e) { + response.setSuccess(false); + response.setMsg(e.getMessage().replace("\r", "
")); + response.setData(0); + log.error("Exception caught", e); + } finally { + // 关闭资源(按你的风格简化:仅判断非空,直接关闭,不调用isClosed()) + if (resultSet != null) { + try { + resultSet.close(); + } catch (SQLException e) { + log.error("Exception caught", e); + } + } + if (targetResultSet != null && targetResultSet != resultSet) { // 避免重复关闭 + try { + targetResultSet.close(); + } catch (SQLException e) { + log.error("Exception caught", e); + } + } + if (callableStmt != null) { + try { + callableStmt.close(); + } catch (SQLException e) { + log.error("Exception caught", e); + } + } + if (connection != null) { + try { + connection.close(); + } catch (SQLException e) { + log.error("Exception caught", e); + } + } + } + return response; + } + + /** + * 检查存储过程是否包含指定参数 + * + * @param procedureName 存储过程名称 + * @param paramName 参数名称 + * @return 是否包含该参数 + */ + private boolean hasStoreParameter(String procedureName, String paramName) { + try { + + String sql = this.sqlProvider.hasStoreParametersql(procedureName, paramName); + List> params = jdbcTemplate.queryForList( +// "SELECT parameter_name FROM information_schema.parameters " + +// "WHERE specific_name = ? AND parameter_name = ?", +// procedureName, paramName + sql + ); + return !params.isEmpty(); + } catch (Exception e) { + sysLog("检查存储过程参数失败", e.getMessage()); + return false; + } + } + + /** + * 审核单据 + * + * @param module 单据模块信息 + * @param stateEn 单据状态枚举 + * @return 基础响应对象 + */ + public BaseResponse AuditBill(BillModule module, BillStateEn stateEn) throws CusException { + BaseResponse response = new BaseResponse(); + + // 校验状态枚举对象是否为空 + if (stateEn == null) { + response.setMsg("未找到单据!"); + return response; + } + + // 判断是否为正向审核(Direction为"F") + boolean isAudit = "F".equalsIgnoreCase(stateEn.Direction); + + // 确定存储过程名称(回退使用不同的存储过程) + String storeName = stateEn.IsBack ? "p_BillAuditBack" : "p_BillAudit"; + + // 如果是回退操作,补充备注信息 + if (stateEn.IsBack) { + LoginUserInfo user = getUser(); + stateEn.Remark = String.format("由【%s】强制退回", user.UserName); + } + + // 获取当前登录用户信息 + LoginUserInfo user = getUser(); + int userId = Integer.parseInt(user.UserId); + + // 创建存储过程调用对象 + SimpleJdbcCall jdbcCall = new SimpleJdbcCall(jdbcTemplate) + .withProcedureName(storeName); + + // 构建参数源 + SqlParameterSource paramSource = new MapSqlParameterSource() + .addValue("@typeCode", module.getModuleId(), Types.VARCHAR) + .addValue("@stepCode", stateEn.StepCode, Types.INTEGER) + .addValue("@billDocumentId", module.IdValue, Types.VARCHAR) + .addValue("@operatorid", userId, Types.INTEGER) + .addValue("@operatorName", user.UserName, Types.VARCHAR) + .addValue("@auditAdvice", stateEn.Remark, Types.VARCHAR) + .addValue("@Direction", stateEn.Direction, Types.VARCHAR); + + // 非回退操作添加额外参数 + if (!stateEn.IsBack) { + // 处理返审步骤(为空则设为-1) + int backStepCode = isNullOrEmpty(stateEn.BackStepCode) + ? -1 + : Integer.parseInt(stateEn.BackStepCode); + + paramSource = ((MapSqlParameterSource) paramSource) + .addValue("@backStepCode", backStepCode, Types.INTEGER) + .addValue("@hint_opers", "", Types.VARCHAR) + .addValue("@comfirm_opers", stateEn.comfirmOpers, Types.VARCHAR); + } +// 将SqlParameterSource转换为Map + Map paramMap = new HashMap<>(); + if (paramSource instanceof MapSqlParameterSource mapSqlParam) { + // 提取MapSqlParameterSource中的参数到普通Map + mapSqlParam.getValues().forEach(paramMap::put); + } + // 执行存储过程并处理结果 + response = ExecSelectOperStore(storeName, module, stateEn, paramMap); + + // 处理响应消息 + if (response.isSuccess()) { + response.setMsg("操作成功"); + } else { +// response.setMsg("审核失败!"); + response.setMsg(String.format("审核失败!操作失败,错误原因:%s", response.getMsg())); + } + + return response; + } + + /** + * 检测是否为新版审核 + * + * @return 是否为新版审核 + */ + public boolean CheckIsMulitAudit() { +// String sql = "select case when col_length('wms_billflowOperView', 'auditoperators') is null then 0 else 1 end;"; + String sql = this.sqlProvider.CheckIsMulitAuditSql(); + return ToInt32(jdbcTemplate.queryForObject(sql, Object.class)) > 0; + } + + /** + * 获取任务移动端卡片列信息 + * + * @param moduleId 模块ID + * @param isBase 是否为基础模块 + * @return 包含卡片列信息的List>(模拟DataTable) + */ + public List> GetTaskMobileCardColumn(String moduleId, boolean isBase, boolean cellTpl) { + String sql = "select c.groupname, c.groupvisible,cd.colid,cd.rowid, c.mxorderid mxid, cd.rowheight, cd.splitline, " + + "cd.displaytext, cd.fontname, cd.colname, cd.fontsize, cd.fcolor, cd.bcolor, cd.dbcolor, " + + "cd.dfcolor, cd.fbold, cd.fitalic, cd.fstrikeline, cd.RightAlign, cd.displayType, " + + "cd.condition displayCond, cd.textAlign " + + "from p_systemCardDetailTab cd " + + "inner join p_systemCardTab c on cd.sourceKey = c.formkey " + + "inner join %s p on c.sourceKey = 'TASK_' + convert(varchar(100), p.formkey) " + + "where p.%s = ? and c.visible = 1 and cd.visible = 1 %s" + + "order by c.orderid,cd.colid,cd.rowid,cd.orderid"; + + String cond = ""; + if (cellTpl) { + cond = " and isnull(cd.colname,'')<>'' "; + } + + // 替换占位符,确定关联表和查询条件字段 + String tableName = isBase ? "p_systemdlltab" : "p_systembilltype"; + String conditionField = isBase ? "dllcoid" : "typeCode"; + sql = String.format(sql, tableName, conditionField, cond); + + // 执行查询并返回结果(使用JdbcTemplate的参数化查询防止SQL注入) + return jdbcTemplate.queryForList(sql, moduleId); + } + + /** + * @param menuId 菜单ID + * @param projectName 方案名称 + * @param userId 用户ID + * @return BaseResponse 包含操作结果的响应对象 + **/ + public BaseResponse DeleteProject(String menuId, String projectName, String userId) { + BaseResponse response = new BaseResponse(); + + String sql = "delete from P_SystemReportProjectTab " + + "where ModuleId = ? and OperatorId = ? and ProjectName = ?"; + + int rowsAffected = jdbcTemplate.update(sql, + menuId, + userId, + projectName); + + response.setSuccess(rowsAffected > 0); + return response; + } + + /** + *

说明:保存方案

+ * + * @param projectName 方案名称 + * @param projectValue 方案值,格式类似 {a:b,c:d} + * @param menuId 菜单ID + * @param userId 用户ID + * @return BaseResponse 响应对象,其中success表示操作结果,msg包含提示信息 + */ + public BaseResponse SaveProject(String projectName, String projectValue, String menuId, String userId) { + BaseResponse response = new BaseResponse(); + + // 参数非空校验 + if (isNullOrEmpty(projectName) || isNullOrEmpty(projectValue) || isNullOrEmpty(menuId)) { + return response; // 返回默认失败响应(success默认为false) + } + + // 检查方案名称是否已存在 + String checkSql = "select 1 from P_SystemReportProjectTab " + + "where ProjectName = ? and operatorId = ? and moduleId = ?"; + List> checkResult = jdbcTemplate.queryForList(checkSql, + projectName, + userId, + menuId); + + if (!checkResult.isEmpty()) { + response.setMsg("名称已存在!"); + return response; + } + + // 插入新方案 + String insertSql = "insert into P_SystemReportProjectTab " + + "(OperatorId, OperateDate, ProjectName, ProjectMode, SearchValue, ModuleId) " + + "values (?, GETDATE(), ?, 1, ?, ?)"; + int rowsAffected = jdbcTemplate.update(insertSql, + userId, + projectName, + projectValue, + menuId); + + response.setSuccess(rowsAffected > 0); + return response; + } + + /** + * 获取方案列表 + * + * @param menuId 模块ID + * @param userId 用户ID + * @return 包含方案信息的列表(每个元素为一行数据的Map) + */ + + @Cacheable(value = "schemesListCache", key = "#menuId + '_' + #userId", condition = "#menuId != null && #userId != null") + public List> GetSchemesList(String menuId, String userId) { + try { + String sql = "select projectname, searchvalue from P_SystemReportProjectTab pro " + + "where pro.ProjectMode = 1 and pro.operatorId = ? and pro.moduleid = ?"; + + // 使用DbOperator执行查询,返回List模拟DataTable + return jdbcTemplate.queryForList(sql, userId, menuId); + } catch (Exception e) { + // 异常捕获处理(保持与原代码一致的空处理) + return List.of(); // 返回空列表 + } + } + + /** + * 2020-11-19 添加单表的 平铺明细 + * + * @param moduleId 模块ID + * @param stepCode 步骤编码 + * @return 明细数据列表(模拟DataTable,使用List表示) + */ + @Cacheable(value = "auditBaseDetailsCache", key = "#moduleId + '_' + #stepCode", condition = "#result != null") + public List> GetAuditBaseDetails(String moduleId, String stepCode) { + // 1. 检查是否存在该表,无则表示没有审核平铺功能 +// String checkTableSql = "select case when exists (" + +// "select 1 from dbo.sysobjects where id = object_id(N'[dbo].[p_SystemDlltabDetailFlowTab]') " + +// "and OBJECTPROPERTY(id, N'IsUserTable') = 1) then 1 else 0 end"; + + String checkTableSql = this.sqlProvider.GetAuditBaseDetailsSql(); + + Object hasTableObj = getDbOperator().executeScalar(checkTableSql, DbOperator.CommandType.TEXT, null); + int hasTable = NativeExtensionUtils.ToInt32(hasTableObj); + + if (hasTable == 0) { + return null; + } + + // 2. 查询平铺明细配置 + String sql = "SELECT [id], [typecode], [detailKey], [detailName], [stepcode], " + + "[modifyFields], [displayMode], [addShowMode] " + + "FROM [p_SystemDlltabDetailFlowTab] " + + "WHERE typecode = ? and stepcode = ? and isnull(isvisible, 0) = 0"; + + return jdbcTemplate.queryForList(sql, moduleId, Objects.equals(stepCode, "999") ? "100" : stepCode); + } + + /** + * 根据表单键获取基础模块明细信息 + * + * @param fromkey 表单键 + * @return 明细信息列表(使用List模拟DataTable) + */ + public List> GetBaesModuleDetailsByFromkey(String fromkey) { + String gridSql = "select detail.id, detail.detailName, library, detail.detailsql, " + + "detail.autorefresh refresh, detail.unionvalue unionfield, unionCond, " + + "noGridLine, noRownumber, noColumnHeader hideColumnHeader, " + + "detail.unionparentfield, detail.unionmodule, detail.formkey, " + + "detail.detailType, formKey fromkey, displaymode, addShowMode ,detail.defaultitem" + + "from p_systemDlltabDetail detail " + + "where detail.formkey = ? " + // 使用参数化查询防止SQL注入 + "order by OrderID"; + + // 调用DbOperator执行查询,返回结果集 + return jdbcTemplate.queryForList(gridSql, fromkey); + } + + /// + /// 获取常用审批意见 + /// + /// DataTable. + /// + /// + public List> GetAuditRemark() { + String sSql = "select row_number() over (order by orderid desc) value,mix_apellation text from p_basemixinfotab a where a.tag ='1201' order by orderid"; + return jdbcTemplate.queryForList(sSql); + + } + + /** + * 基础审核操作 + * + * @param module 模块实体 + * @param stateEn 账单状态枚举 + * @return 基础响应对象 + */ + public BaseResponse BaseAudit(ModuleEntity module, BillStateEn stateEn) throws CusException { + BaseResponse response = new BaseResponse(); + + boolean isAudit = "F".equals(stateEn.Direction); + boolean isBack = stateEn.IsBack && !isAudit; // 是否在终审完后点击回退功能 + + String storename = isBack ? "p_BaseAuditBack" : "p_BaseAudit"; // 回退调用存储过程不一样 + + if (isBack) { // 回退信息 + stateEn.Remark = (String.format("由【%s】强制退回", getUser().UserName)); + } + + // 将参数列表转换为 Map + Map paramMap = new LinkedHashMap<>(); + +// 添加基础参数 + paramMap.put("@typeCode", module.getModuleId()); + paramMap.put("@stepCode", stateEn.StepCode); + paramMap.put("@billDocumentId", module.IdValue); + paramMap.put("@operatorid", NativeExtensionUtils.ToInt32(getUser().UserId)); + paramMap.put("@operatorName", getUser().UserName); + paramMap.put("@auditAdvice", stateEn.Remark); + paramMap.put("@Direction", stateEn.Direction); + paramMap.put("@msg", ""); + +// 非回退场景添加额外参数 + if (!isBack) { + int backStepCode = isNullOrEmpty(stateEn.BackStepCode) + ? -1 + : NativeExtensionUtils.ToInt32(stateEn.BackStepCode); + + paramMap.put("@backStepCode", backStepCode); + paramMap.put("@hint_opers", ""); + paramMap.put("@comfirm_opers", stateEn.comfirmOpers); + } + + return ExecSelectOperStore(storename, module, stateEn, paramMap); + } + + /** + * 获取审核退回步骤列表 + */ + @Cacheable(value = "auditBackStepsCache", key = "#moduleId + '_' + #idValue + '_' + #stepCode + '_' + #billType + '_' + #isBase") + public List> GetAuditBackSteps(String moduleId, String idValue, String stepCode, String + billType, boolean isBase) throws CusException { + // 先从基础方法获取退回步骤列表 + List> retList = GetAuditBackStepList(moduleId, idValue, stepCode, isBase); + + // 如果列表为空,执行SQL查询补充默认选项 + if (retList == null || retList.isEmpty()) { + // 确定查询的表名 + String tableName = isBase ? "p_systemdlltabflowtypestep" : "p_systembillflowtypestep"; + + // 构建SQL语句 + String sql = String.format( + "select '-1' as value, '退回至上一节点' as text " + + "union all " + + "select '0' as value, '退回至提交人' as text " + + "union all " + + "select * from (select top 1000 stepcode as value, stepname as text " + + "from %s where 1=1 " + + "and typeCode = '%s' " + + "and stepCode < '%s' " + + "and billtype = '%s' " + + "and isnull(autostep, 0) = 0 " + + "order by stepcode desc) a", + tableName, + moduleId, // 防SQL注入处理 + stepCode, + billType + ); + + try { + // 执行查询并返回结果(Java中用List替代DataTable) + return jdbcTemplate.queryForList(sql); + } catch (Exception e) { + // 抛出自定义异常并标记需要日志记录 + throw new CusException(e.getMessage(), e) {{ + Log = (true); + }}; + } + } + + return retList; + } + + /** + * 获取审核退回步骤列表 + * 缓存有效期10秒,使用自定义边界注解 + */ + @Cacheable(value = "auditBackStepCache", key = "#moduleId + '_' + #idValue + '_' + #stepCode + '_' + #isBase") + public List> GetAuditBackStepList(String moduleId, String idValue, String stepCode, + boolean isBase) throws CusException { + try { + // 创建SimpleJdbcCall调用存储过程 + SimpleJdbcCall jdbcCall = new SimpleJdbcCall(jdbcTemplate) + .withProcedureName("p_system_getBackStepCode"); // 指定存储过程名 + + // 构建输入参数 + Map inParams = new HashMap<>(); + inParams.put("@modid", moduleId); + inParams.put("@modType", isBase ? 1 : 2); + inParams.put("@keyvalue", idValue); + inParams.put("@stepcode", stepCode); + + // 执行存储过程并获取结果 + Map result = jdbcCall.execute(inParams); + + // 解析结果集(存储过程返回的数据集通常在键为"#result-set-1"的条目里) + List> storedProcResult = (List>) result.get("#result-set-1"); + + if (storedProcResult != null && !storedProcResult.isEmpty()) { + // 转换结果结构为{value, text}格式 + List> retList = new ArrayList<>(); + for (Map row : storedProcResult) { + Map item = new HashMap<>(); + // 使用工具类获取字段值,兼容不同数据库的字段名大小写 + item.put("value", (get(row, "keyid", ""))); + item.put("text", (get(row, "stepname", ""))); + retList.add(item); + } + return retList; + } + } catch (Exception e) { + // 抛出自定义异常,设置继续执行和日志标记 + CusException cusEx = new CusException((e).getMessage(), e); + cusEx.Continue = (true); + cusEx.Log = (true); + throw cusEx; + } + return null; + } + + /** + * 获取附件左边树形结构数据 + * + * @param moduleCode 基础模块或单据模块编号 + * @return 树形结构数据列表(模拟DataTable,使用List表示) + */ + // 方法边界注解(根据实际实现调整) + + /** + * 对应原C#的GetAttTreeData方法 + * + * @param moduleCode 模块编码(对应@menuCode) + * @param specNo 物种编号(可为空,非空时拼接like条件) + * @return List> 模拟DataTable,无结果时返回降级查询结果 + */ + public List> GetAttTreeData(String moduleCode, String specNo) { + // 1. 空值校验:moduleCode为空直接返回降级查询结果(和原逻辑一致) + if (moduleCode == null || moduleCode.trim().isEmpty()) { + return GetFallbackTreeData(moduleCode); + } + + // 2. 动态拼接specCond条件(解决原代码SQL注入风险) + StringBuilder specCond = new StringBuilder(); + MapSqlParameterSource mainParams = new MapSqlParameterSource(); + mainParams.addValue("menuCode", moduleCode.trim()); // 绑定核心参数 + + if (specNo != null && !specNo.trim().isEmpty()) { + specCond.append(" and spec.SpeciesNo like :specNoPattern"); + mainParams.addValue("specNoPattern", specNo.trim() + "%"); // 参数化like条件 + } + + // 3. 拼接主查询SQL(适配达梦语法:LEFT→SUBSTR、len→LENGTH) + String mainSql = String.format( + "select spec.speciesno, " + + "spec.speciesname, " + + // 达梦:LEFT(speciesno, len(speciesno)-2) → SUBSTR(字段, 1, LENGTH(字段)-2) + "SUBSTR(spec.speciesno, 1, LENGTH(spec.speciesno) - 2) as parentid, " + + "spec.remark " + + "from bmp_ProductSpeciesTab spec " + + "inner join ( " + + " select bmpspec, dllcoid " + + " from v_systemdlltab " + + " where dllcoid = :menuCode " + + " group by bmpspec, dllcoid " + + ") vd on (spec.SpeciesNo like COALESCE(vd.bmpSpec, '') || '%%') %s", // 达梦拼接用||,%%转义% + specCond.toString() + ); + + // 4. 执行主查询 + List> mainResult = namedJdbcTemplate.queryForList(mainSql, mainParams); + + // 5. 无结果时执行降级查询 + if (mainResult.isEmpty()) { + return GetFallbackTreeData(moduleCode); + } + + // 有结果返回主查询结果 + return mainResult; + } + + /** + * 降级查询方法(对应原C#的else分支SQL) + */ + private List> GetFallbackTreeData(String moduleCode) { + String fallbackSql = "select bmpspec as speciesno, " + + "bmpspec as speciesname, " + + "'01' as parentid " + + "from v_systemdlltab " + + "where dllcoid = :menuCode " + + "group by bmpspec, dllcoid"; + + MapSqlParameterSource fallbackParams = new MapSqlParameterSource(); + fallbackParams.addValue("menuCode", moduleCode == null ? "" : moduleCode.trim()); + + return namedJdbcTemplate.queryForList(fallbackSql, fallbackParams); + } + + + public List> GetFlowStepHis(String idValue, boolean isBase) { + String hisSql = String.format( + "select stepCode, stepName, happenTime, operTime, datediff(SECOND ,happenTime, operTime) as stepTme, operatorId, operatorName, operAdvice, operDirection, autoStep from %s where flowSourceKey='%s' order by id desc", + isBase ? "p_baseflowstep" : "wms_billflowstep", idValue);//order by stepcode ,opertime + List> hisDtVal = jdbcTemplate.queryForList(hisSql); + return hisDtVal; + } + + /** + * 获取流程图数据 + * + * @param module 模块基础信息 + * @return 流程图数据列表(模拟DataTable,使用List表示) + */ + public List> GetFlowChartData(ModuleBaseEntity module) { + String opertab; + String part2; + + // 根据模块类型确定表名 + if (module instanceof BaseModule) { + // 基础模块 + opertab = "p_baseflowoper"; + part2 = "P_Systemdlltabflowtypestep"; + } else { + // 单据模块 + opertab = "wms_billflowOper"; + part2 = "p_systembillflowtypestep"; + } + + // 构建查询SQL + boolean exitFlowExOper = getExitFlowExOper(); + String sSql = String.format( + "select oper1.pstepcode oversteps, step.*, " + + "isnull(oper.browsers, step.viewUser) operbrowsers, " + + "isnull(oper.operators, step.operUser) operoperators, " + + "isnull(oper.stepover, -1) stepover " + + "from %s step " + + "left join %s%s oper on oper.modid = step.typecode " + + "and oper.billtype = step.billtype " + + "and oper.stepcode = step.stepcode " + + "and oper.keyvalue = ? " + + "left join %s oper1 on oper1.modid = step.typecode " + + "and oper1.billtype = step.billtype " + + "and oper1.keyvalue = ? " + + "where step.typecode = ? " + + "and step.billtype = ? " + + "order by step.stepcode", + part2, + opertab, + exitFlowExOper ? "ex" : "", + opertab + ); + + // 执行查询获取步骤数据 + List> dtVal = jdbcTemplate.queryForList( + sSql, + module.IdValue, module.IdValue, module.getModuleId(), module.BillType); + + // 处理条件过滤 + if (!NativeExtensionUtils.isNullOrEmpty(module.IdValue) && !dtVal.isEmpty()) { + // 查询当前主表记录 + String curSql = String.format( + "select top 1 * from %s where %s = ?", + module.getMasterTable(), + module.getIdField() + ); + List> curTab = jdbcTemplate.queryForList(curSql, module.IdValue); + + if (!curTab.isEmpty()) { + Map curRow = curTab.get(0); + List> filteredList = new ArrayList<>(); + + // 过滤符合条件的步骤 + for (Map row : dtVal) { + String forkCondition = (String) (row.getOrDefault("forkCondition", "")); + // 处理SQL参数(假设PublicUtil有对应的Java实现) + String cond = PublicUtil.ReqSqlPmsByRow( + curRow, + null, + forkCondition, + SystemTypeEnums.PmType.sql, + getUser() + ); + + if (NativeExtensionUtils.isNullOrEmpty(cond)) { + filteredList.add(row); + } else { + // 验证条件是否成立 + String checkSql = String.format( + "select 1 from %s where %s = ? and %s", + module.getMasterTable(), + module.getIdField(), + cond + ); + Object result = jdbcTemplate.queryForObject(checkSql, new Object[]{module.IdValue}, Object.class); + if (NativeExtensionUtils.toBoolean(result)) { + filteredList.add(row); + } + } + } + dtVal = filteredList; + } + } + + return dtVal; + } + + /** + * 处理SQL用户字符串,支持参数替换和角色用户查询 + * + * @param users 原始用户字符串 + * @param userId 用户ID + * @param userName 用户名 + * @param updRow 数据行(对应C#的DataRow,用Map模拟) + * @return 处理后的用户字符串 + */ + protected String GetSqlUser(String users, String userId, String userName, Map updRow) { + if (!NativeExtensionUtils.isNullOrEmpty(users)) { + users = users.trim(); + + // 处理以@开头的参数化用户 + if (users.startsWith("@")) { + // 替换双单引号为单引号并修剪 + users = users.replace("''", "'").trim(); + // 移除开头的@符号(Java中无trimStart,手动实现) + String sqlParam = users.substring(1); // 直接截取从索引1开始的字符串 + // 生成SQL参数条件 + String sqlCondition = PublicUtil.ReqSqlPmsByRow(updRow, null, sqlParam, SystemTypeEnums.PmType.sql, getUser()); + // 执行查询获取用户名列表 +// String querySql = String.format( +// "SELECT EmployeeName + ',' FROM p_employeetab WHERE (%s) FOR XML PATH('')", +// sqlCondition +// ); + String sql = this.sqlProvider.GetSqlUserSql(); + String querySql = String.format( + sql, + sqlCondition + ); + if (databaseType.equals("dm")) querySql = RegexUtil.processDmServerSql(querySql); + Object result = jdbcTemplate.queryForObject(querySql, Object.class); + users = result != null ? result.toString() : ""; + // 去除首尾的逗号(Java中trim()无参数,使用字符串替换和trim()结合) + return users.replaceAll("^,+,", "").replaceAll(",+$", "").trim(); + } + // 处理包含{&角色名&}格式的角色用户 + else if (users.indexOf("{&") > -1) { + // 正则匹配{&...&}格式的角色标识 + Pattern pattern = Pattern.compile("\\{&([^}]+)&\\}"); + Matcher matcher = pattern.matcher(users); + + while (matcher.find()) { + String roleMarker = matcher.group(0); + // 提取角色名(去除{&和&}) + String roleName = roleMarker.replace("{&", "").replace("&}", ""); + // 查询角色对应的操作员 +// String roleSql = String.format( +// "SELECT aa.operatorname + ',' FROM p_systemRoleSetTab a " + +// "JOIN p_systemRoleOperSetTab aa ON a.id = aa.roleid " + +// "WHERE a.roleName = '%s' FOR XML PATH('')", +// roleName +// ); + String sql = this.sqlProvider.GetSqlUserByroleSql(); + String roleSql = String.format(sql, roleName); + + Object roleResult = jdbcTemplate.queryForObject(roleSql, Object.class); + String roleUsers = roleResult != null ? roleResult.toString() : ""; + // 处理首尾逗号 + roleUsers = roleUsers.replaceAll("^,+,", "").replaceAll(",+$", "").trim(); + // 替换角色标识为实际用户 + users = users.replace(roleMarker, roleUsers); + } + } + // 4. 过滤管理员、去重、重新拼接 + // 替换"管理员,"为空 → 分割数组 → 去重 → 拼接 + String filtered = users.replace("管理员,", ""); + String[] arr = filtered.split(","); + java.util.List distinctList = removeSameObj(arr); + return sJoin(distinctList, ","); + } + return ""; + } + + /** + * 获取流程步骤状态数据 + * + * @param moduleId 模块ID + * @param idValue 主键值 + * @param isBase 是否为基础档案 + * @return 流程步骤状态数据列表(模拟DataTable,使用List表示) + */ + protected List> GetFlowStepState(String moduleId, String idValue, boolean isBase) { + // 构建表名:根据是否为基础档案和是否存在扩展表决定 + String tableName = isBase ? "p_baseflowOper" : "wms_BillflowOper"; + if (getExitFlowExOper()) { + tableName += "ex"; + } + + // 构建查询SQL(使用参数化查询防止SQL注入) + String statusSql = String.format( + "SELECT * FROM %s WHERE keyvalue = ? AND modid = ?", + tableName + ); + + // 执行查询并返回结果(使用DbOperator执行,参数顺序与SQL中?对应) + return jdbcTemplate.queryForList( + statusSql, + idValue, moduleId); + } + + public List> GetFlowChartOption(String moduleId, int moduleType, String billType) { + + String sSql = null, + part2 = null; + StringBuilder sqlbder = new StringBuilder(); + + if (moduleType == 0) {//基础模块 + part2 = "P_Systemdlltabflowtypestepcfg"; + } else if (moduleType == 1) {// 单据模块 + part2 = "p_systembillflowtypestepcfg"; + } + sSql = String.format("select * from %s where typecode='%s' and billtype='%s'", part2, moduleId, billType); + + return jdbcTemplate.queryForList(sSql); + } + + /** + * 更新流程图配置 + * + * @param moduleId 模块ID + * @param moduleType 模块类型(0-基础模块,1-单据模块) + * @param billType 单据类型 + * @param options 配置参数 + * @param ifDelete 是否先删除原有配置 + * @return 响应结果 + */ + public BaseResponse UpdateFlowChartOption(String moduleId, int moduleType, String billType, + Map options, boolean ifDelete) { + BaseResponse resp = new BaseResponse(); + StringBuilder sqlBuilder = new StringBuilder(); + String tableName; + + // 确定表名 + if (moduleType == 0) { + tableName = "P_Systemdlltabflowtypestepcfg"; + } else if (moduleType == 1) { + tableName = "p_systembillflowtypestepcfg"; + } else { + resp.setSuccess(false); + resp.setMsg("无效的模块类型"); + return resp; + } + + // 先删除原有配置 + if (ifDelete) { + String deleteSql = String.format( + "DELETE FROM %s WHERE typecode = ? AND billType = ?", + tableName + ); + jdbcTemplate.update(deleteSql, moduleId, billType); + } + + // 组装新增/更新SQL + Set keys = options.keySet(); + for (String key : keys) { + try { + // 将配置对象转为JSON字符串 + String cfgString = JSON.Encode(options.get(key)); + + // 检查记录是否存在 + String checkSql = String.format( + "SELECT COUNT(1) FROM %s WHERE typecode = ? AND billType = ? AND cfgName = ?", + tableName + ); + Integer count = jdbcTemplate.queryForObject( + checkSql, + new Object[]{moduleId, billType, key}, + Integer.class + ); + + if (count == null || count == 0) { + // 新增记录 + sqlBuilder.append(String.format( + "INSERT INTO %s (typeCode, billType, [option], cfgName) VALUES (?, ?, ?, ?);", + tableName + )); + jdbcTemplate.update( + sqlBuilder.toString(), + moduleId, billType, cfgString, key + ); + } else { + // 更新记录 + sqlBuilder.append(String.format( + "UPDATE %s SET [option] = ? WHERE typecode = ? AND billType = ? AND cfgName = ?;", + tableName + )); + jdbcTemplate.update( + sqlBuilder.toString(), + cfgString, moduleId, billType, key + ); + } + sqlBuilder.setLength(0); // 清空StringBuilder准备下一次循环 + } catch (Exception e) { + resp.setSuccess(false); + resp.setMsg("数据库操作失败: " + e.getMessage()); + return resp; + } + } + + resp.setSuccess(true); + resp.setMsg("上传流程图options成功"); + return resp; + } + + /** + * 获取需要更新流程图的模块列表 + * + * @return 包含基础模块和单据模块列表的Map + */ + public Map GetUpdFlowChartList() { + Map result = new HashMap<>(); + + // 查询基础模块列表 + String baseSql = "SELECT DISTINCT typeCode, billType FROM P_Systemdlltabflowtypestep " + + "WHERE ISNULL(autoStep, 0) = 0 ORDER BY billType"; + List> baseModuleList = jdbcTemplate.queryForList(baseSql); + + // 查询单据模块列表 + String billSql = "SELECT DISTINCT typeCode, billType FROM p_systembillflowtypestep " + + "WHERE ISNULL(autoStep, 0) = 0 ORDER BY billType"; + List> billList = jdbcTemplate.queryForList(billSql); + + result.put("baseModuleList", baseModuleList); + result.put("billList", billList); + + return result; + } + + /** + * 检查是否存在符合条件的记录 + * + * @param pid 记录ID + * @return 存在符合条件的记录返回true,否则返回false + */ + public boolean GetPrintSta(int pid) { + // SQL语句,使用?作为参数占位符防止SQL注入 + String sql = "select 1 from p_systemWebPrintTab where id = ? and isnull(printSta, 0) = 0"; + + try { + // 执行查询,返回第一列结果 + Integer result = jdbcTemplate.queryForObject( + sql, + new Object[]{pid}, // 参数数组 + Integer.class // 返回值类型 + ); + + // 如果查询到结果(result为1)则返回true,否则返回false + return result != null && result == 1; + } catch (Exception e) { + // 查询无结果时会抛出EmptyResultDataAccessException,这里统一处理为返回false + return false; + } + } + + /** + * 获取模板打印信息 + * + * @param moduleId 模块标识 + * @param printFileName 打印文件名 + * @return 包含打印信息的数据列表(模拟DataTable,使用List表示) + */ + public List> GetWebPrintInfo(String moduleId, String printFileName) { + // 对应C#注释中的数据库更新操作,如需启用可取消注释 + + // 打印信息查询SQL + String printSql = "select id, content from p_systemwebPrint psp where tab = :tab and printname = :printname"; + + try { + // 使用NamedParameterJdbcTemplate处理参数化查询,避免SQL注入 + NamedParameterJdbcTemplate namedJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate); + + // 设置查询参数 + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("printname", printFileName); + params.addValue("tab", moduleId); + + // 执行查询并返回结果(List模拟DataTable结构) + return namedJdbcTemplate.queryForList(printSql, params); + } catch (Exception e) { + LoggerHandler.error("获取模板打印信息失败: moduleId=" + moduleId + ", printFileName=" + printFileName, e); + throw new RuntimeException("获取打印信息异常", e); + } + } + + /** + * 新增或更新Web打印模板 + */ + public String AddOrUpdWebPrint(String moduleId, String printName, String content, String id) { + // 初始化SQL和参数 + String sql; + MapSqlParameterSource params = new MapSqlParameterSource(); + + if (id == null || id.isEmpty()) { + // 新增操作 + id = "0"; + sql = "insert into p_systemwebPrint(tab, printname, content) " + + "values(:tab, :printname, :content); " + + "select last_insert_id();"; // MySQL获取自增ID方式,SQL Server使用select @@identity + + params.addValue("tab", moduleId) + .addValue("printname", printName) + .addValue("content", content); + } else { + // 更新操作 + sql = "update p_systemwebPrint set content = :content " + + "where id = :id; " + + "select :id;"; + + params.addValue("content", content) + .addValue("id", Integer.parseInt(id)) // 转换为整数类型匹配ID字段 + .addValue("tab", moduleId) // 虽然更新用不到tab,但保持参数完整性 + .addValue("printname", printName); + } + + try { + // 使用命名参数Jdbc模板执行SQL + NamedParameterJdbcTemplate namedJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate); + // 执行查询并获取结果(新增返回新ID,更新返回原ID) + Object result = namedJdbcTemplate.queryForObject(sql, params, Object.class); + return result != null ? result.toString() : id; + } catch (Exception e) { + LoggerHandler.error("新增或更新Web打印模板失败: moduleId=" + moduleId + ", printName=" + printName, e); + throw new RuntimeException("操作打印模板异常", e); + } + } + + private static List> MsgCacheTab = null; + private static LocalDate LastGetTime = LocalDate.now(); + private static Object lockObj = new Object(); + private static boolean isGetting = false; + + /** + * lserp_v8 PC网页端的 右下角消息轮询SQL 20秒一次【2020-12-2】 + * 创建人:赵云鹏 + * + * @param userId 用户ID + * @return 消息列表(List模拟DataTable) + */ + public List> GetAuditMsgTab(String userId) { + // 构建查询条件(处理userId为空的情况) + String sql = getDetailJDBC().GetAuditMsgTab(userId); + // 执行查询并返回结果 + return jdbcTemplate.queryForList(sql); + } + + + /** + * 表示消息已读的操作 + * + * @param userId 用户ID + * @param record 消息记录参数 + * @return 操作影响的行数 + */ + public int SeeOneAuditMsg(String userId, Map record) { + int tableCode = NativeExtensionUtils.ToInt32(record.get("tablecode")); + long id = NativeExtensionUtils.ToInt64(record.get("id")); + String sqlValue; + int result; + + // 根据tablecode执行不同操作 + if (tableCode == 2 && hasExistsTable("p_systemMessageTab")) { + sqlValue = String.format("UPDATE p_systemMessageTab SET ReadFlag = 1, DeleteFlag = 1 WHERE Id = %d", id); + result = jdbcTemplate.update(sqlValue); + } else if (tableCode == 1) { + sqlValue = String.format("DELETE FROM p_systemNotification WHERE Id = %d", id); + result = jdbcTemplate.update(sqlValue); + } else { + return 0; + } + + // 处理p_systemNoticeSheet表插入 + if (hasExistsTable("p_systemNoticeSheet")) { + try { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + String currentDate = sdf.format(new Date()); + String sqlValue2 = getDetailJDBC().SeeOneAuditMsg(); + sqlValue2 = MessageFormat.format(sqlValue2, id, currentDate, userId); + jdbcTemplate.update(sqlValue2); + } catch (Exception e) { + // 不处理异常,有些表可能存在问题 + } + } + + return result; + } + + /** + * 标记所有消息为已读 + * + * @param userId 用户ID + * @return 操作影响的行数 + */ + public int SeeAllAuditMsg(String userId) { + StringBuilder sqlValue = new StringBuilder(); + // 删除通知表中指定用户的未处理消息 + sqlValue.append(String.format( + "DELETE FROM p_systemNotification WHERE isnull(billdocument_id, '') <> '' " + + "AND isnull(status, 0) = 0 AND isnull(Cnt1, 0) <> 0 AND UserId = '%s';", + userId + )); + + // 更新消息表中指定用户的未删除消息 + if (hasExistsTable("p_systemMessageTab")) { + sqlValue.append(String.format( + "UPDATE p_systemMessageTab SET ReadFlag = 1, DeleteFlag = 1 " + + "WHERE DeleteFlag = 0 AND NoticeUserID = '%s';", + userId + )); + } + + return jdbcTemplate.update(sqlValue.toString()); + } + + /** + * 确认消息 + * + * @param moduleId 模块ID + * @param idValue 消息ID + * @param userId 用户ID + * @param remark 备注 + * @return 操作影响的行数 + */ + public int ConfirmMsg(String moduleId, String idValue, String userId, String remark) { + boolean isBase = IsBaseModule(moduleId); + String tableName = isBase ? "p_baseflowComfirm" : "wms_BillflowComfirm"; + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String currentTime = sdf.format(new Date()); + + // 使用命名参数防止SQL注入 + String sql = String.format( + "UPDATE %s SET comfirmFlag = 1, comfirmOper = :userId, " + + "comfirmDate = :currentTime, comfirmAdvice = :remark " + + "WHERE modid = :moduleId AND keyvalue = :idValue AND operatorid = :userId", + tableName + ); + + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("userId", userId, Types.VARCHAR); + params.addValue("currentTime", currentTime, Types.VARCHAR); + params.addValue("remark", remark, Types.VARCHAR); + params.addValue("moduleId", moduleId, Types.VARCHAR); + params.addValue("idValue", idValue, Types.VARCHAR); + + return new NamedParameterJdbcTemplate(jdbcTemplate).update(sql, params); + } + + /** + * 获取消息详情 + * + * @param id 消息ID + * @param type 消息类型 + * @return 消息详情列表(List模拟DataTable) + */ + public List> GetMsg(int id, int type) { + String sql = ""; + // 将int类型转换为MsgTableType枚举 + SystemEnums.MsgTableType msgType; + try { + msgType = SystemEnums.MsgTableType.fromValue(type); + } catch (IllegalArgumentException e) { + // 处理无效的类型值 + return null; + } + switch (msgType) { + case WX: + // 微信消息表处理(原代码未实现) + break; + case YunZhiJia: + sql = """ + SELECT + a.UserId, + emp.EmployeeName username, + emp.LoginAccount, + a.messid, + a.DLLCoid moduleid, + msg, + CASE + WHEN isnull(a.DllFileName, '') <> '' THEN a.DllFileName + WHEN isnull(b.dllfilename1, '') = '' THEN b.DllFileName + ELSE b.dllfilename1 + END dllname, + isnull(a.ModuleName, b.MenuCaption) modulename, + billdocument_id idvalue, + stepcode, + b.menuid + FROM p_systemNotification_cloud a + LEFT JOIN p_employeetab emp ON a.UserId = emp.employeeid + LEFT JOIN P_FormMenuConfigTab b ON ISNULL(a.menuid, 0) = b.MenuId + WHERE a.id = :msgId + """; + break; + } + + if (!sql.isEmpty()) { + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("msgId", id, Types.INTEGER); + List> result = new NamedParameterJdbcTemplate(jdbcTemplate).queryForList(sql, params); + return toLowerColumnName(result); // 转换列名为小写 + } + return null; + } + + // 辅助方法:检查表是否存在 + private boolean hasExistsTable(String tableName) { + String sql = getDetailJDBC().hasExistsTable(tableName); + Integer count = jdbcTemplate.queryForObject(sql, Integer.class); + return count != null && count > 0; + } + + /** + * 获取角色列表 + */ + public List> GetRoles() { + String sql = "select id, roleName, ReadPurview, EditPurview from p_systemRoleSetTab where isnull(enableflag, 0) = 0"; + return toLowerColumnName(jdbcTemplate.queryForList(sql)); + } + + /** + * 删除角色 + */ + public BaseResponse DelRoles(String ids) { + BaseResponse response = new BaseResponse(); + if (ids == null || ids.isEmpty()) { + response.setSuccess(false); + return response; + } + + // 处理ID列表,防止SQL注入(简单处理,实际应使用参数化) + String processedIds = ids.contains(",") ? ids.replace(",", "','") : ids; + String sql = String.format("update p_systemRoleSetTab set enableflag = 1 where id in ('%s')", processedIds); + + int rowsAffected = jdbcTemplate.queryForObject(sql, Integer.class); + response.setSuccess(rowsAffected > 0); + return response; + } + + /** + * 获取角色关联用户 + */ + public List> GetRoleUsers(String roleId) { + String sql = "select id, ro.roleId, roleOperatorId userId, jobNumber userCode, emp.employeename userName, department " + + "from p_systemRoleOperSetTab ro " + + "left join p_employeetab emp on ro.roleOperatorId = emp.employeeid " + + "where ro.roleId = ?"; + + return toLowerColumnName(jdbcTemplate.queryForList(sql, roleId)); + } + + /** + * 删除角色关联用户 + */ + public BaseResponse DelRoleUser(String ids) { + BaseResponse response = new BaseResponse(); + if (ids == null || ids.isEmpty()) { + response.setSuccess(false); + return response; + } + + // 处理ID列表,防止SQL注入(简单处理,实际应使用参数化) + String processedIds = ids.trim().replace(",", "','"); + String sql = String.format("delete from p_systemRoleOperSetTab where id in ('%s')", processedIds); + + int rowsAffected = jdbcTemplate.queryForObject(sql, Integer.class); + response.setSuccess(rowsAffected > 0); + return response; + } + + /** + * 更新角色权限 + */ + public BaseResponse UpdRolePurview(String roleId) { + BaseResponse response = new BaseResponse(); + try { + // 创建SimpleJdbcCall实例,指定存储过程名称 + SimpleJdbcCall jdbcCall = new SimpleJdbcCall(jdbcTemplate) + .withProcedureName("p_SystemSetRoleUserPurview") + .declareParameters( + new SqlParameter("@roleid", Types.VARCHAR), // 输入参数 + new SqlOutParameter("@msg", Types.VARCHAR) // 输出参数 + ); + + // 准备输入参数Map + Map inParams = new HashMap<>(); + inParams.put("@roleid", roleId); + + // 执行存储过程并获取结果 + Map result = jdbcCall.execute(inParams); + + // 处理输出参数 + String msg = (String) result.get("@msg"); + response.setMsg(msg); + response.setSuccess(true); + } catch (Exception e) { + response.setSuccess(false); + response.setMsg(e.getMessage()); + } + return response; + } + + /** + * 更新用户权限 + */ + public BaseResponse UpdUserPurview(String userId) { + BaseResponse response = new BaseResponse(); + try { + // 创建SimpleJdbcCall实例,指定存储过程名称 + SimpleJdbcCall jdbcCall = new SimpleJdbcCall(jdbcTemplate) + .withProcedureName("p_SystemSetUserPurview") + .declareParameters( + new SqlParameter("@operatorid", Types.VARCHAR), // 输入参数 + new SqlOutParameter("@msg", Types.VARCHAR) // 输出参数 + ); + + // 准备输入参数Map + Map inParams = new HashMap<>(); + inParams.put("@operatorid", userId); + + // 执行存储过程并获取结果 + Map result = jdbcCall.execute(inParams); + + // 处理输出参数 + String msg = (String) result.get("@msg"); + response.setMsg(msg); + // 原逻辑未显式设置success,默认保持成功状态(可根据实际业务调整) + response.setSuccess(true); + } catch (Exception e) { + response.setSuccess(false); + response.setMsg(e.getMessage()); + } + return response; + } + + /** + * 保存角色权限 + */ + public BaseResponse SaveRolePurv(String roleId) { + BaseResponse response = new BaseResponse(); + try { + // 创建SimpleJdbcCall实例 + SimpleJdbcCall jdbcCall = new SimpleJdbcCall(jdbcTemplate) + .withProcedureName("p_SystemSetRoleUserPurview") + .declareParameters( + new SqlParameter("@operatorid", Types.VARCHAR), // 输入参数 + new SqlOutParameter("@msg", Types.VARCHAR) // 输出参数 + ) + .returningResultSet("resultSet1", new ColumnMapRowMapper()) // 声明结果集 + .returningResultSet("resultSet2", new ColumnMapRowMapper()); // 支持多个结果集 + + // 准备输入参数 + Map inParams = new HashMap<>(); + inParams.put("@operatorid", roleId); + + // 执行存储过程并获取结果 + Map result = jdbcCall.execute(inParams); + + // 处理输出参数 + String rMsg = (String) result.get("@msg"); + response.setSuccess(true); + response.setMsg(rMsg == null || rMsg.isEmpty() ? LanguageUtil.Success : rMsg); + + // 处理结果集(提取所有返回的结果集) + List>> dataSets = ExtractResultSets(result); + response.setOther(DecodeSaveResult(dataSets)); + + } catch (Exception e) { + response.setSuccess(false); + response.setMsg(e.getMessage().replace("\r", "
")); + response.setData(0); + } + return response; + } + + /** + * 从SimpleJdbcCall执行结果中提取所有结果集 + */ + private List>> ExtractResultSets(Map result) { + return result.values().stream() + .filter(value -> value instanceof List) + .map(value -> (List>) value) + .collect(Collectors.toList()); + } + + /** + * 获取用户列表 + */ + public List> GetUsers(String name, String code, int userId) { + // 处理特殊字符防止SQL注入 + String sql = getDetailJDBC().GetUsers(name, code, userId); + return toLowerColumnName(jdbcTemplate.queryForList(sql)); + } + + /** + * 获取用户权限 + */ + public BaseResponse GetUserPrev(String userId, int type) { + BaseResponse response = new BaseResponse(); + try { + if (type == 0) { + Hashtable prevTab = new Hashtable<>(); + + // 获取操作权限 + BaseResponse operResponse = GetUserPrev(userId, 1); + if (operResponse.getData() != null) { + prevTab.put("operview", ((Hashtable) operResponse.getData()).get("operview")); + } + + // 获取查看权限 + BaseResponse viewResponse = GetUserPrev(userId, 2); + if (viewResponse.getData() != null) { + prevTab.put("redview", ((Hashtable) viewResponse.getData()).get("redview")); + } + + response.setData(prevTab); + } else { + // 创建SimpleJdbcCall实例 + SimpleJdbcCall jdbcCall = new SimpleJdbcCall(jdbcTemplate) + .withProcedureName("p_getAuthorityByEmpid") // 设置存储过程名称 + .declareParameters( + new SqlParameter("@typeId", Types.INTEGER), + new SqlParameter("@empid", Types.VARCHAR), + new SqlParameter("@operatorId", Types.VARCHAR), + new SqlParameter("@operatorName", Types.VARCHAR), + new SqlOutParameter("@msg", Types.VARCHAR, 2000) + ) + .returningResultSet("resultSet", new ColumnMapRowMapper()); // 声明结果集 + + // 准备输入参数 + Map inParams = new HashMap<>(); + inParams.put("@typeId", type); + inParams.put("@empid", userId); + inParams.put("@operatorId", getUser().UserId); + inParams.put("@operatorName", getUser().UserName); + + // 执行存储过程并获取结果 + Map result = jdbcCall.execute(inParams); + + // 处理输出参数 + String rMsg = (String) result.get("@msg"); + response.setSuccess(true); + response.setMsg(rMsg == null || rMsg.isEmpty() ? LanguageUtil.Success : rMsg); + + // 处理结果集 + List> dataSet = (List>) result.get("resultSet"); + if (dataSet != null && !dataSet.isEmpty()) { + String prev = dataSet.get(0).get("pid").toString(); + String key = type == 1 ? "operview" : "redview"; + response.setData(new Hashtable() {{ + put(key, prev); + }}); + } + } + } catch (Exception e) { + response.setSuccess(false); + response.setMsg(e.getMessage().replace("\r", "
")); + response.setData(0); + } + return response; + } + + /** + * 获取评论列表 + * + * @param moduleIdOrGuid 模块ID或GUID + * @param idVal 主键值 + * @param stepCode 步骤编码 + * @return 评论列表数据(用List模拟DataTable) + */ + public List> GetCommentList(String moduleIdOrGuid, String idVal, String stepCode) { + // 1. 空值校验:idVal为空直接返回null(和原C#逻辑完全一致) + if (idVal == null || idVal.trim().isEmpty()) { + return null; + } + + // 2. 处理moduleIdOrGuid空值(避免参数绑定失败) + String safeModuleId = (moduleIdOrGuid == null) ? "" : moduleIdOrGuid.trim(); + + // 3. 拼接SQL(参数化所有外部输入,适配达梦语法) + String sql = "select a.atts, " + + "a.operatorname, " + + "a.commenttype, " + + "a.comment_text as content, " + // 别名content + "a.operatedate as operatedate, " + + "a.c_address as address, " + + "a.c_address_itude as itude, " + + "e.webbmp " + + "from P_SystemCheckCommentTab a " + + "left join p_employeetab e on a.operatorid = e.employeeid " + + "where (a.moduleid = :moduleId and a.keyValue = :idVal) or a.pdataguid = :moduleId"; + + // 4. 构建参数(绑定所有外部输入,避免SQL注入) + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("moduleId", safeModuleId); // 模块ID/PDATAGUID + params.addValue("idVal", idVal.trim()); // 关键字值(已校验非空) + + // 5. 执行查询(对应原C#的ExecuteDataTable) + return namedJdbcTemplate.queryForList(sql, params); + } + + /** + * 检查是否有关于默认项的完整个人配置,无的话就加入和生成完整个人配置 + * + * @param userId 用户ID + */ + public void CheckAndFillBSDesktop(String userId) { + int operatorId = Integer.parseInt(userId); + + // 查询用户的自定义配置 + String customCfgSql = "SELECT itemCode, itemWidth, itemHeight " + + "FROM P_SystemOperFirstPageTab " + + "WHERE operatorId = ?"; + List> customCfgList = jdbcTemplate.queryForList(customCfgSql, operatorId); + + // 查询默认配置 + String defaultCfgSql = "SELECT itemCode, itemOrder, itemWidth, itemHeight, enableFlag " + + "FROM P_SystemFirstPageSetTab " + + "WHERE enableType IN (-1, 1)"; + List> defaultCfgList = jdbcTemplate.queryForList(defaultCfgSql); + + // 检查是否需要更新 + boolean needUpdate = false; + for (Map defaultRow : defaultCfgList) { + String itemCode = (String) defaultRow.get("itemCode"); + boolean exists = false; + + for (Map customRow : customCfgList) { + if (itemCode.equals(customRow.get("itemCode"))) { + exists = true; + break; + } + } + + if (!exists) { + needUpdate = true; + break; + } + } + + if (needUpdate) { + // 删除现有配置 + String deleteSql = "DELETE FROM P_SystemOperFirstPageTab WHERE operatorId = ?"; + jdbcTemplate.update(deleteSql, operatorId); + + // 插入新的配置 + String insertSql = "INSERT INTO P_SystemOperFirstPageTab " + + "(itemCode, itemOrder, itemWidth, itemHeight, enableFlag, operatorId) " + + "VALUES (?, ?, ?, ?, ?, ?)"; + + for (Map defaultRow : defaultCfgList) { + jdbcTemplate.update(insertSql, + defaultRow.get("itemCode"), + defaultRow.get("itemOrder"), + defaultRow.get("itemWidth"), + defaultRow.get("itemHeight"), + defaultRow.get("enableFlag"), + operatorId); + } + } + } + + /** + * 获得首页图标、图表、表等数据和结构 + * + * @param userId 用户ID + * @param userName 用户名 + * @return 包含首页元素信息的列表 + */ + public List> GetBSDesktopItemList(String userId, String userName) { + // SQL查询语句,使用参数化查询防止SQL注入 + String itemsSql = "SELECT a.[id]" + + ",a.[operatorId]" + + ",a.[itemCode]" + + ",ISNULL(a.[itemRowNo], b.[itemRowNo]) AS [itemRowNo]" + + ",ISNULL(a.[itemOrder], b.[itemOrder]) AS [itemOrder]" + + ",ISNULL(a.[itemWidth], b.[itemWidth]) AS [itemWidth]" + + ",ISNULL(a.[itemHeight], b.[itemHeight]) AS [itemHeight]" + + ",a.[enableFlag]" + + ",b.[itemTemplate]" + + ",b.[itemTitle]" + + ",b.[itemIMG]" + + ",b.[itemType]" + + ",b.[itemTypeFull]" + + ",b.[itemDataSource]" + + ",b.[itemLinked]" + + ",b.[itemPrivilege]" + + " FROM [P_SystemOperFirstPageTab] a" + + " INNER JOIN [P_SystemFirstPageSetTab] b ON a.itemCode = b.itemCode" + + " WHERE ISNULL(a.itemCode, '') <> ''" + + " AND b.enableType IN (-1, 1)" + + " AND (ISNULL(a.[deleted], 1) <> 1 OR a.[deleted] IS NULL)" + + " AND b.[enableFlag] = 1" + + " AND a.[enableFlag] = 1" + + " AND (ISNULL(b.itemPrivilege, '') = '' OR CHARINDEX(?, ',' + b.itemPrivilege + ',') > 0)" + + " AND a.[operatorId] = ?"; + + // 执行查询并返回结果,参数顺序:用户名参数、用户ID参数 + return jdbcTemplate.queryForList(itemsSql, "," + userName + ",", userId); + } + + /** + * 获取桌面快捷模块个人快捷功能数据 + * + * @param cardId 卡片 ID(用于区分不同快捷模块场景) + * @param userId 用户 ID(筛选当前用户的配置) + * @return 快捷功能数据列表(List> 对应 C# 的 DataTable) + */ + public List> GetDeskTopCommonUse(int cardId, String userId) { + String sql = getDetailJDBC().GetDeskTopCommonUse(cardId, userId); + return jdbcTemplate.queryForList(sql); + } + + /** + * 获取表的主键数组 + * 缓存有效期10分钟 + */ + @Cacheable(value = "primaryKeysCache", key = "#tabname", condition = "#tabname != null", unless = "#result == null") + public List GetPrimaryKeysArray(String tabname) { + // 构建查询主键的SQL(注意:此处为简化示例,实际应使用参数化查询防止SQL注入) + String primaryKeysSql = getDetailJDBC().GetPrimaryKeysArray(tabname); + // 执行查询获取主键信息 + List> primaryKeys = jdbcTemplate.queryForList(primaryKeysSql); + + // 转换结果为字符串列表 + List keys = new ArrayList<>(); + for (Map row : primaryKeys) { + Object columnName = row.get("COLUMN_NAME"); + if (columnName != null) { + keys.add(columnName.toString()); + } + } + + return keys; + } + + /** + * 获取表的主键字符串(逗号分隔) + * 缓存有效期10分钟 + */ + @Cacheable(value = "primaryKeysStrCache", key = "#tabname", condition = "#tabname != null", unless = "#result == null") + public String GetPrimaryKeys(String tabname) { + List keysA = GetPrimaryKeysArray(tabname); + // 拼接主键为逗号分隔的字符串 + return String.join(",", keysA); + } + + /** + * 获取可配置的扩展配置表数据,用于布局设置和拖动操作 + * 对应C#的GetBSDesktopExtend方法 + */ + public List> GetBSDesktopExtend(String userId, String userName) { + // SQL语句保持与C#一致的逻辑,使用参数化查询防止SQL注入 + String itemsSql = "SELECT a.[id]" + + ",a.[operatorId]" + + ",b.[itemCode]" + + ",isnull(a.[itemRowNo], b.[itemRowNo]) [itemRowNo]" + + ",isnull(a.[itemOrder], b.[itemOrder]) [itemOrder]" + + ",isnull(a.[itemWidth], b.[itemWidth]) [itemWidth]" + + ",isnull(a.[itemHeight], b.[itemHeight]) [itemHeight]" + + ",a.[itemLeft]" + + ",a.[itemTop]" + + ",case when isnull(a.[deleted],0)=1 then 1 else isnull(a.[enableFlag],0) end [enableFlag]" + + ",b.[itemTitle]" + + ",b.[itemTypeFull]" + + " FROM [P_SystemFirstPageSetTab] b" + + " left join [P_SystemOperFirstPageTab] a on a.itemCode=b.itemCode and a.operatorId=?" + + " where b.enableType in (-1, 1) and (isnull(b.itemPrivilege,'')='' or charindex(','+?+',', ','+b.itemPrivilege+',')>0 )" + + " order by a.id"; + + // 执行查询,返回List替代DataTable + return jdbcTemplate.queryForList(itemsSql, userId, userName); + } + + /** + * 获取桌面模块操作配置数据 + * 对应C#的GetDesktopModuleOper方法 + */ + public List> GetDesktopModuleOper(String userId, String dllcoid) { + // SQL语句保持与C#一致的逻辑,使用参数化查询 + String itemsSql = "SELECT a.[id]" + + ",a.[operatorId]" + + ",b.[itemCode]" + + ",isnull(a.[itemRowNo], b.[itemRowNo]) [itemRowNo]" + + ",isnull(a.[itemOrder], b.[itemOrder]) [itemOrder]" + + ",isnull(a.[itemWidth], b.[itemWidth]) [itemWidth]" + + ",isnull(a.[itemHeight], b.[itemHeight]) [itemHeight]" + + ",a.[itemLeft]" + + ",a.[itemTop]" + + ",case when isnull(a.[deleted],0)=1 then 1 else isnull(a.[enableFlag],0) end [enableFlag]" + + ",b1.[itemTitle]" + + ",b1.[itemTypeFull]" + + ",b.[queryField]" + + ",b.[condition]" + + " from [P_SystemDllFirstPageTab] b" + + " inner join [P_SystemFirstPageSetTab] b1 on b.itemCode=b1.itemCode and b1.enableType in (-1, 1)" + + " left join [P_SystemDllOperFirstPageTab] a on a.itemCode=b.itemCode and a.dllcoid=b.dllcoid and a.operatorid=?" + + " where b.dllcoid=? and b.enableFlag=1" + + " order by a.id"; + + // 转换userId为整数(对应C#的ToInt32) + int operatorId; + try { + operatorId = Integer.parseInt(userId); + } catch (NumberFormatException e) { + operatorId = 0; // 处理转换失败的情况,可根据实际业务调整 + } + + // 执行参数化查询 + return jdbcTemplate.queryForList(itemsSql, operatorId, dllcoid); + } + + /** + * 获取BS桌面模块项目列表 + * 用户仅可查看自身UserId对应的配置项,管理员可查看所有 + * + * @param dllcoid 模块标识 + * @param userId 用户ID + * @return 模块项目列表(以List形式替代DataTable) + */ + public List> GetBSDesktopModuleItemList(String dllcoid, String userId) { + // 使用参数化SQL避免注入风险,替换原C#的string.Format + String itemsSql = "SELECT a.[id]" + + ",a.[dllcoid]" + + ",a.[itemCode]" + + ",ISNULL(a.[itemRowNo], b.[itemRowNo]) [itemRowNo]" + + ",ISNULL(a.[itemOrder], b.[itemOrder]) [itemOrder]" + + ",ISNULL(a.[itemWidth], b.[itemWidth]) [itemWidth]" + + ",ISNULL(a.[itemHeight], b.[itemHeight]) [itemHeight]" + + ",a.[enableFlag]" + + ",b.[itemTemplate]" + + ",b.[itemTitle]" + + ",b.[itemIMG]" + + ",b.[itemType]" + + ",b.[itemTypeFull]" + + ",b.[itemDataSource]" + + ",b.[itemLinked]" + + ",b.[itemPrivilege]" + + ",c.[queryField]" + + ",c.[condition]" + + " FROM [P_SystemDllOperFirstPageTab] a" + + " inner join [P_SystemFirstPageSetTab] b on a.itemCode = b.itemCode" + + " left join [P_SystemDllFirstPageTab] c on a.itemCode = c.itemCode and a.dllcoid = c.dllcoid" + + " where isnull(a.itemCode, '') <> ''" + + " and b.enableType in (-1, 2)" + + " and (isnull(a.[deleted], 1) <> 1 or a.[deleted] is null)" + + " and b.[enableFlag] = 1 and a.[enableFlag] = 1 and c.[enableFlag] = 1" + + " and a.[dllcoid] = ?" + + " and a.[operatorId] = ?"; + + // 执行参数化查询,返回List替代DataTable + return jdbcTemplate.queryForList(itemsSql, dllcoid, userId); + } + + /** + * 检查并填充桌面模块操作配置 + * + * @param dllcoid 模块标识 + * @param userId 用户ID + */ + public void CheckAndFillDesktopModuleOper(String dllcoid, String userId) { + // 查询用户自定义配置 + String customCfgSql = "SELECT itemCode, itemWidth, itemHeight " + + "FROM [P_SystemDllOperFirstPageTab] " + + "WHERE [dllcoid] = ? AND [operatorId] = ?"; + List> customCfgList = jdbcTemplate.queryForList( + customCfgSql, + dllcoid, Integer.parseInt(userId)); + + // 查询默认配置 + String defaultCfgSql = "SELECT itemCode, itemOrder, itemWidth, itemHeight, enableFlag " + + "FROM [P_SystemDllFirstPageTab] " + + "WHERE [dllcoid] = ? AND [enableFlag] = 1"; + List> defaultCfgList = jdbcTemplate.queryForList( + defaultCfgSql, + dllcoid); + + // 检查是否需要更新:如果默认配置中存在用户配置没有的itemCode,则需要更新 + boolean ifUpdate = false; + for (Map defaultRow : defaultCfgList) { + String theCode = (String) defaultRow.get("itemCode"); + boolean exists = false; + + // 检查用户配置中是否存在当前itemCode + for (Map customRow : customCfgList) { + if (theCode.equals(customRow.get("itemCode"))) { + exists = true; + break; + } + } + + if (!exists) { + ifUpdate = true; + break; + } + } + + // 需要更新时,先删除用户配置再重新插入默认配置 + if (ifUpdate) { + // 删除用户现有配置 + String deleteSql = "DELETE FROM P_SystemDllOperFirstPageTab " + + "WHERE dllcoid = ? AND operatorId = ?"; + jdbcTemplate.update( + deleteSql, + dllcoid, Integer.parseInt(userId)); + + // 插入默认配置作为新的用户配置 + String insertSql = "INSERT INTO P_SystemDllOperFirstPageTab " + + "(itemCode, itemOrder, itemWidth, itemHeight, enableFlag, dllcoid, operatorId) " + + "VALUES (?, ?, ?, ?, ?, ?, ?)"; + + for (Map defaultRow : defaultCfgList) { + jdbcTemplate.update( + insertSql, + defaultRow.get("itemCode"), + defaultRow.get("itemOrder"), + defaultRow.get("itemWidth"), + defaultRow.get("itemHeight"), + defaultRow.get("enableFlag"), + dllcoid, + Integer.parseInt(userId)); + } + } + } + + /** + * 管理员用于配置的基础表数据查询,仅管理员可访问 + * + * @return 基础配置表数据列表 + */ + public List> GetBSDesktopAdminBase() { + // SQL查询语句,获取管理员配置的基础表数据 + String itemsSql = "SELECT [id]" + + ",[itemCode]" + + ",[itemTitle]" + + ",[itemIMG]" + + ",[itemRowNo]" + + ",[itemOrder]" + + ",[itemWidth]" + + ",[itemHeight]" + + ",[itemType]" + + ",[itemDataSource]" + + ",[itemLinked]" + + ",[itemPrivilege]" + + ",[enableFlag]" + + ",[enableType]" + + ",[operateDate]" + + ",[operatorName]" + + ",[itemTemplate]" + + ",[itemTypeFull]" + + " FROM [P_SystemFirstPageSetTab] ORDER BY id"; + + // 执行查询并返回结果,使用List替代DataTable + return jdbcTemplate.queryForList(itemsSql); + } + + /** + * 桌面查询结果获取 + * + * @param queryText 查询文本 + * @return 查询结果列表 + */ + public List> GetDeskQueryResult(String queryText) { + // SQL语句,保持原逻辑不变,使用参数化查询 + boolean exitTable = false; + if (Objects.equals(databaseType, "dm")) { + Integer res = jdbcTemplate.queryForObject("SELECT COUNT(1) FROM USER_TABLES WHERE TABLE_NAME = UPPER('p_systemSearchTextTab')", Integer.class); + exitTable = res > 0; + } + String sql = getDetailJDBC().GetDeskQueryResult(queryText, exitTable); + if (Objects.equals(databaseType, "dm")) { + if (exitTable) + return jdbcTemplate.queryForList(sql, queryText, queryText, queryText, queryText, queryText, queryText, queryText); + else + return jdbcTemplate.queryForList(sql, queryText, queryText, queryText); + } + // 执行参数化查询,所有?占位符均对应queryText参数 + return jdbcTemplate.queryForList(sql, queryText, queryText, queryText, + queryText, queryText, queryText, queryText, + queryText, queryText, queryText); + } + + /** + * 获取桌面左侧模块数据 + * + * @return 包含dllcoid的数据集(模拟DataTable,使用List表示) + */ + public List> GetDesktopModuleLeft() { + String sql = "SELECT DISTINCT [dllcoid] FROM [P_SystemDllFirstPageTab] WHERE ISNULL(dllcoid, '') <> ''"; + // 执行查询并返回返回结果集,列名自动转为小写(保持与项目风格一致) + return ConversionUtils.toLowerColumnName(jdbcTemplate.queryForList(sql)); + } + + /** + * 获取桌面主模块数据 + * + * @param userId 用户ID + * @param userName 用户名 + * @param dllcoid 模块标识 + * @return 主模块数据集(模拟DataTable,使用List表示) + */ + public List> GetDesktopModuleMain(String userId, String userName, String dllcoid) { + // 构建SQL查询,使用参数化查询防止SQL注入(替换原C#的String.Format拼接) + String sql = getDetailJDBC().GetDesktopModuleMain(userId, userName, dllcoid); + + // 执行参数化查询,按顺序传递参数 + List> result = jdbcTemplate.queryForList( + sql, + dllcoid, // 对应a.[dllcoid] = ? + userName // 对应CHARINDEX中的? + ); + + // 转换列名为小写(保持与项目中其他查询结果格式一致) + return ConversionUtils.toLowerColumnName(result); + } + + /** + * 模板主表初始化检查和填充 + * + * @param dllcoid 模块标识 + */ + public void CheckAndFillDesktopModuleMain(String dllcoid) { + // 查询用户自定义配置 + String customCfgSql = "SELECT itemCode, itemWidth, itemHeight " + + "FROM [P_SystemDllFirstPageTab] " + + "WHERE [dllcoid] = :dllcoid"; + + MapSqlParameterSource customParams = new MapSqlParameterSource(); + customParams.addValue("dllcoid", dllcoid); + NamedParameterJdbcTemplate namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate); + List> customCfgList = namedParameterJdbcTemplate.queryForList(customCfgSql, customParams); + + // 查询默认配置 + String defaultCfgSql = "SELECT itemCode, itemOrder, itemWidth, itemHeight, enableFlag " + + "FROM [P_SystemFirstPageSetTab] " + + "WHERE enableType IN (-1, 2)"; + List> defaultCfgList = namedParameterJdbcTemplate.queryForList(defaultCfgSql, new MapSqlParameterSource()); + + // 检查并填充缺失的配置 + for (Map dRow : defaultCfgList) { + String theCode = NativeExtensionUtils.getStringValue(dRow, "itemCode", ""); + // 检查自定义配置中是否已存在该itemCode + boolean exists = customCfgList.stream() + .anyMatch(row -> theCode.equals(NativeExtensionUtils.getStringValue(row, "itemCode", ""))); + + if (!exists) { + // 插入新配置 + String insertSql = "INSERT INTO P_SystemDllFirstPageTab " + + "(itemCode, itemOrder, itemWidth, itemHeight, enableFlag, dllcoid) " + + "VALUES (:itemCode, :itemOrder, :itemWidth, :itemHeight, :enableFlag, :dllcoid)"; + + MapSqlParameterSource insertParams = new MapSqlParameterSource(); + insertParams.addValue("itemCode", theCode); + insertParams.addValue("itemOrder", NativeExtensionUtils.parseInt(dRow.get("itemOrder") + "", 0)); + insertParams.addValue("itemWidth", NativeExtensionUtils.getStringValue(dRow, "itemWidth", "")); + insertParams.addValue("itemHeight", NativeExtensionUtils.getStringValue(dRow, "itemHeight", "")); + insertParams.addValue("enableFlag", 0); + insertParams.addValue("dllcoid", dllcoid); + + namedParameterJdbcTemplate.update(insertSql, insertParams); + } + } + } + + /** + * 获取系统列表 + * + * @param id 系统ID(可选,0表示查询所有) + * @return 系统列表数据(以List形式模拟DataTable) + */ + // 缓存配置:过期时间10秒,对应原C#的Cache特性 + @Cacheable(value = "systemListCache", key = "#id", condition = "#id != null", unless = "#result == null") + public List> GetSystems(int id) { + StringBuilder idCond = new StringBuilder(); + if (id > 0) { + // 拼接ID条件,使用参数化避免SQL注入(优化原C#直接拼接的方式) + idCond.append("and SubSysId = ?"); + } + + // 构建SQL查询语句 + String sql = String.format( + "select SubSysName as MenuCaption, SubSysId as ID, '' as Library " + + "from P_SubSystemTab " + + "where isnull(UseEd, 0) = 1 and isnull(visible, 0) = 0 %s " + + "order by orderid", + idCond.toString() + ); + + // 执行查询并返回结果(使用DbOperator的方法,返回List模拟DataTable) + if (id > 0) { + return jdbcTemplate.queryForList(sql, id); + } else { + return jdbcTemplate.queryForList(sql); + } + } + + public List> GetSystemInfo() { + //MYUh6esf4UI3aglaswRBGWAXxYFNbf6q zyw 账号,额度不够 + //aAEHg5NfO4jtmlbD1MofHxVo38KKctkO 唐元账号,js地址解析 5w每天 + try { + String bmapAK = WebConfigUtil.get("bMapAK", "MYUh6esf4UI3aglaswRBGWAXxYFNbf6q"); + + // 获取GWUpdApp配置并转换为整数 + String gwUpdAppStr = WebConfigUtil.get("GWUpdApp", "1"); + int GWUpdApp = toBoolean(gwUpdAppStr) ? 1 : 0; + + // 构建SQL查询语句 + String sql = String.format( + "select top 1 '%s' bmak, %d gwupdapp, " + + "isnull(mainmenutype,0) mainmenutype, " + + "isnull(webclientname, clientname) clientname, " + + "isnull(webclientname, clientname) logincaption, " + + "isnull(webclientname, clientname) maincaption, " + + "isnull(webclientenname,clientenname) clientenname, " + + "copyright, clientlogname, loginpopupflag, downloadaddress, " + + "mobiledevonly, multmobiledev, %s serVer, " + + "mes_address_change mesdbinfo, nmsg_time rftime ,PasswordChange ckweekpwd,displayWatermark waterMark,watermarkContent waterContent " + + "from p_SystemTab", + bmapAK, GWUpdApp, UpdateImpl.getVersion() + ); + return jdbcTemplate.queryForList(sql); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * 获取地图配置信息的哈希表 + * 对应 C# 的 virtual 方法,Java 中用 public 即可(如需重写可加 abstract 或保留 public) + * + * @return 包含 ak、code、type 三个键的 Hashtable + */ + public Map getMapInfo() { + // MYUh6esf4UI3aglaswRBGWAXxYFNbf6q zyw 账号,额度不够 + // aAEHg5NfO4jtmlbD1MofHxVo38KKctkO 唐元账号,js地址解析 5w每天 + + // 读取配置:优先读 MapAK,若为空则读 bMapAK,默认值为指定字符串 + String mapAK = WebConfigUtil.get("MapAK", WebConfigUtil.get("bMapAK", "MYUh6esf4UI3aglaswRBGWAXxYFNbf6q")); + + // 创建并初始化 Hashtable,对应 C# 的匿名初始化方式 + Hashtable mapInfo = new Hashtable<>(); + mapInfo.put("ak", mapAK); + mapInfo.put("code", WebConfigUtil.get("MapSecCode", "")); + mapInfo.put("type", WebConfigUtil.get("MapType", "baidu")); + + return mapInfo; + } + + + /** + * 获取设备宝地址对应的 MrpType 值 + * 对应 C# 特性: + * - [MBoundary]:Java 中一般用自定义注解或 Controller 层的 RequestMapping 替代(此处保留注释) + * - [Cache(ExpirationPeriod = 10)]:用 Spring Cache 实现 10 秒缓存 + * + * @return 数据库查询到的 MrpTabPageType 整数值 + */ + // 自定义 MBoundary 注解(需你根据项目实际定义,此处仅示例) + // Spring Cache 注解:缓存 10 秒,缓存键默认方法名,也可自定义 key + @Cacheable(value = "mrpTypeCache", key = "#root.methodName", condition = "#result != null") + public int GetMrpType() { + String sql = "select top 1 MrpTabPageType as mrpType from p_SystemTab"; + + // 执行查询并转换为 int(兼容各种返回值类型:Integer/BigDecimal/String/null) + Integer result = jdbcTemplate.queryForObject(sql, Integer.class); + return ToInt32(result); + } + + /// + /// 获取设备宝地址 + /// + /// + public List> GetEmaUrl() { + String SQL = "select top 1 emaurl from p_SystemTab"; + return jdbcTemplate.queryForList(SQL); + } + + public String GetUseSys() { + final String sql = "select top 1 subsysid from P_SubSystemTab where useed=1 and ISNULL(visible,0)=0 order by subsysid asc"; + return jdbcTemplate.queryForObject(sql, String.class); + } + + /** + * tagmodel 0所有,1:cs,2:app,3:web,4:wx + * + * @param parentid 父菜单ID + * @param seriesId 系列ID + * @param targetmode 目标模式 + * @return 菜单数据列表(模拟DataTable,使用List表示) + */ + @Cacheable(value = "menuCache", key = "#parentid + '_' + #seriesId + '_' + #targetmode", condition = "#result != null", unless = "#result.isEmpty()") + public List> GetSysMenus(String parentid, String seriesId, String targetmode) { + // 构建移动端条件(WindowsDirver为true时无额外条件) + String mobileCond = isWindowsDirver() ? "" : + " and b.MobileShow=1 AND isnull(b.UseEd,0) = 1 and (tm.ShowPlatform=1 or len(tm.MenuStruct)=2 ) "; + + // 处理parentid为"0"的情况 + if ("0".equals(parentid)) { + parentid = GetUseSys(); + } + + // 构建系列ID条件 + String seriesCond = NativeExtensionUtils.isNullOrEmpty(seriesId) ? "" : + String.format(" and isnull(menu.SeriesId, 1)=%s ", seriesId); + + // 处理目标模式默认值 + targetmode = NativeExtensionUtils.isNullOrEmpty(targetmode) ? "0,3" : targetmode; + String targeModeCond = String.format("isnull(tm.targetmode,0) in ('%s')", targetmode.replace(",", "','")); + + String sql; + if (!NativeExtensionUtils.isNullOrEmpty(parentid)) { + // 有父ID的查询SQL + String pcondition = String.format(" and subsysid='%s' ", parentid); + sql = String.format( + "WITH tmenu(id, MenuCaption, ParentId, level) " + + "as " + + "( " + + " SELECT menuid id, MenuCaption, 0 ParentId, 0 level " + + " FROM p_formmenuconfigtab menu " + + " WHERE ISNULL(UseFlag, 1) = 1 AND ParentMenuId <= 0 %s %s " + + " UNION ALL " + + " SELECT A.menuid id, A.MenuCaption, MenuStruct ParentId, b.level + 1 " + + " FROM p_formmenuconfigtab A, tmenu b " + + " WHERE a.ParentMenuId = b.id " + + ") " + + "SELECT " + + " tm.serverId, " + + " tm.MenuId, " + + " tm.urlparams menucode, " + + " tm.PurviewId menucode1, " + + " tm.MenuCaption, " + + " CASE WHEN ISNULL(tm.dllfilename1, '') = '' THEN tm.DllFileName ELSE tm.dllfilename1 END library, " + + " lm.level, " + + " lm.ParentId, " + + " tm.GroupCaption, " + + " CASE WHEN ISNULL(CAST(psdt.countsql AS varchar), ISNULL(CAST(psdtp.countsql AS varchar), '')) = '' THEN 0 ELSE 1 END needcount " + + "FROM p_formmenuconfigtab tm " + + "INNER JOIN tmenu lm ON tm.menuid = lm.id " + + "INNER JOIN P_SubSystemTab b ON tm.subsysid = b.SubSysId " + + "LEFT JOIN p_systemdlltab psdt ON " + + " ISNULL(tm.urlparams, tm.PurviewId) = psdt.dllcoid AND " + + " ISNULL(ISNULL(tm.urlparams, tm.PurviewId), '') != '' " + + "LEFT JOIN p_systembilltype psdtp ON " + + " ISNULL(tm.urlparams, tm.PurviewId) = psdtp.typeCode AND " + + " ISNULL(ISNULL(tm.urlparams, tm.PurviewId), '') != '' " + + "WHERE %s AND ISNULL(tm.useFlag, 1) = 1 %s " + + "ORDER BY MenuStruct", + pcondition, + NativeExtensionUtils.isNullOrEmpty(seriesId) ? "" : seriesCond, + targeModeCond, + mobileCond + ); + } else { + // 无父ID的查询SQL + sql = String.format( + "WITH tmenu(id, MenuCaption, ParentId, menuid, SubSysId, level) " + + "as " + + "( " + + " SELECT " + + " CONVERT(varchar(50), menu.SubSysId) + '_' + MenuStruct id, " + + " MenuCaption, " + + " CONVERT(varchar(50), -CONVERT(int, menu.SubSysId)) ParentId, " + + " menu.menuid, " + + " menu.SubSysId, " + + " 1 level " + + " FROM p_formmenuconfigtab menu " + + " INNER JOIN P_SubSystemTab sub ON " + + " menu.SubSysId = sub.SubSysId AND " + + " ISNULL(UseEd, 0) = 1 AND " + + " ISNULL(visible, 0) = 0 " + + " WHERE ISNULL(UseFlag, 1) = 1 AND LEN(menustruct) = 2 %s " + + " UNION ALL " + + " SELECT " + + " CONVERT(varchar(50), a.SubSysId) + '_' + A.MenuStruct id, " + + " A.MenuCaption, " + + " CONVERT(varchar(50), CONVERT(varchar(50), a.SubSysId) + '_' + " + + " SUBSTRING(a.MenuStruct, 1, CASE WHEN LEN(a.MenuStruct) > 2 THEN LEN(a.MenuStruct) - 2 ELSE 0 END)) ParentId, " + + " a.MenuId, " + + " a.SubSysId, " + + " b.level + 1 " + + " FROM p_formmenuconfigtab A, tmenu b " + + " WHERE LEN(a.MenuStruct) > 2 " + + " AND ISNULL(UseFlag, 1) = 1 " + + " AND CONVERT(varchar(50), a.SubSysId) + '_' + " + + " SUBSTRING(a.MenuStruct, 1, CASE WHEN LEN(a.MenuStruct) > 2 THEN LEN(a.MenuStruct) - 2 ELSE 0 END) = b.id " + + " AND a.SubSysId = b.SubSysId " + + ") " + + "SELECT * FROM ( " + + " SELECT " + + " tm.serverId, " + + " tm.MenuId, " + + " lm.id MenuStruct, " + + " tm.urlparams menucode, " + + " tm.PurviewId menucode1, " + + " tm.MenuCaption, " + + " CASE WHEN ISNULL(tm.dllfilename1, '') = '' THEN tm.DllFileName ELSE tm.dllfilename1 END library, " + + " lm.ParentId parentId, " + + " tm.SubSysId, " + + " lm.level, " + + " tm.GroupCaption, " + + " CASE WHEN ISNULL(CAST(psdt.countsql AS varchar), ISNULL(CAST(psdtp.countsql AS varchar), '')) = '' THEN 0 ELSE 1 END needcount " + + " FROM tmenu lm " + + " INNER JOIN p_formmenuconfigtab tm ON lm.menuid = tm.menuid AND lm.subsysid != '0' " + + " INNER JOIN P_SubSystemTab b ON tm.subsysid = b.SubSysId " + + " LEFT JOIN p_systemdlltab psdt ON " + + " ISNULL(tm.urlparams, tm.PurviewId) = psdt.dllcoid AND " + + " ISNULL(ISNULL(tm.urlparams, tm.PurviewId), '') != '' " + + " LEFT JOIN p_systembilltype psdtp ON " + + " ISNULL(tm.urlparams, tm.PurviewId) = psdtp.typeCode AND " + + " ISNULL(ISNULL(tm.urlparams, tm.PurviewId), '') != '' " + + " WHERE %s AND ISNULL(tm.useFlag, 1) = 1 AND parentid != '0' %s " + + " UNION ALL " + + " SELECT " + + " '', " + + " '' menuid, " + + " CONVERT(varchar(10), -SubSysId) MenuStruct, " + + " '' menucode, " + + " '' menucode1, " + + " subsysname MenuCaption, " + + " '' library, " + + " '' parentid, " + + " SubSysId, " + + " 0 level, " + + " NULL GroupCaption, " + + " 0 needcount " + + " FROM P_SubSystemTab " + + " WHERE ISNULL(UseEd, 0) = 1 " + + " AND ISNULL(visible, 0) = 0 " + + " AND subsysid > 0 %s " + + ") a " + + "ORDER BY SubSysId, MenuStruct ", + NativeExtensionUtils.isNullOrEmpty(seriesId) ? "" : seriesCond, + targeModeCond, + mobileCond, + isWindowsDirver() ? "" : "and mobileshow=1" + ); + } + + // 执行SQL查询并返回结果(DataTable对应List>) + return jdbcTemplate.queryForList(sql); + } + + /** + * 获取数据库服务器信息列表 + * + * @param userId 用户ID + * @param userName 用户名 + * @return 包含数据库服务器信息的Hashtable列表 + */ + public List> GetDbserverInfo(String userId, String userName) { + List> list = new ArrayList<>(); + + // 获取系统数据库组信息(对应C#的GetSysdbGroup(),返回List模拟DataTable) + List> sysDbGroupList = GetSysdbGroup(0); + + // 获取账户信息(对应C#的GetAccountInfoByStore,返回List模拟DataTable) + List> accountInfoList = GetAccountInfoByStore(userId, userName); + Hashtable countTb = new Hashtable<>(); + + // 处理账户信息,构建dbname到amount的映射 + if (accountInfoList != null && !accountInfoList.isEmpty()) { + for (Map row : accountInfoList) { + // 使用DataTableUtil工具类获取字段值,模拟C#的row.Get("dbname") + String dbname = DataTableUtil.getStringValue(row, "dbname", ""); + Object amount = DataTableUtil.get(row, "amount", 0); + if (!dbname.isEmpty()) { + countTb.put(dbname, amount); + } + } + } + + // 处理系统数据库组信息,构建返回列表 + if (sysDbGroupList != null && !sysDbGroupList.isEmpty()) { + for (Map dtRow : sysDbGroupList) { + // 检查当前dbname是否在账户信息中存在,不存在则跳过 + String dbname = DataTableUtil.getStringValue(dtRow, "dbname", ""); + if (isNullOrEmpty(dbname)) continue; + if (!countTb.isEmpty() && !countTb.containsKey(dbname)) { + continue; + } + + // 构建当前行的Hashtable + Hashtable hs = new Hashtable<>(); + hs.put("id", DataTableUtil.get(dtRow, "id", 0)); + hs.put("text", DataTableUtil.get(dtRow, "text", "")); + + // 转换amount为int,模拟C#的ToInt32() + Object totValue = countTb.get(dbname); + int tot = toInt32(totValue != null ? totValue.toString() : "0");// DataTableUtil.getIntValue(totValue != null ? totValue.toString() : "0", 0); + hs.put("tot", tot); + + list.add(hs); + } + } + + return list; + } + + /** + * 获取门店账户信息 + * + * @param userId 用户ID + * @param userName 用户名 + * @return 账户信息列表(模拟DataTable,使用List表示) + */ + public List> GetAccountInfoByStore(String userId, String userName) { + // 用户名为空时返回null + if (userName == null || userName.trim().isEmpty()) { + return null; + } + + String proName = "p_systemGetAccountTask"; + try { + // 检查存储过程是否存在 +// String checkSql = "select 1 from dbo.sysobjects where id = object_id(N'dbo.p_systemGetAccountTask') and OBJECTPROPERTY(id, N'IsProcedure') = 1"; + + String checkSql = this.sqlProvider.GetAccountInfoByStoreSql(); + Object result = jdbcTemplate.queryForObject(checkSql, Object.class); + if (result == null || !"1".equals(result.toString())) { + return null; + } + + // 执行存储过程 + SqlParameterSource params = new MapSqlParameterSource() + .addValue("@operatorid", userId, Types.INTEGER) + .addValue("@operatorname", userName, Types.VARCHAR); + + // 设置存储过程名称并执行 + Map out = new SimpleJdbcCall(jdbcTemplate) + .withProcedureName(proName) + .execute(params); + + // 提取结果集(假设返回的结果集键为"temp",与原C#的"temp"对应) + if (out.containsKey("temp")) { + Object tempResult = out.get("temp"); + if (tempResult instanceof List) { + //noinspection unchecked + return (List>) tempResult; + } + } + } catch (Exception e) { + // 捕获所有异常,按原逻辑不做处理 + } + + return null; + } + + /** + * 获取登录配置信息 + * + * @return 包含登录配置及横幅信息的Hashtable + */ + public Hashtable GetLoginCfg() { + String sql = "select top 1 id, SysName, copyright, logo from p_systemLoginCfgTab where disabled=0"; + // 执行查询获取登录配置主表信息(返回List模拟DataTable) + List> dtVal = jdbcTemplate.queryForList(sql); + + Hashtable datas = null; + + if (dtVal != null && !dtVal.isEmpty()) { + // 转换第一条记录为Hashtable + Map firstRow = dtVal.get(0); + datas = new Hashtable<>(); + for (Map.Entry entry : firstRow.entrySet()) { + datas.put(entry.getKey(), entry.getValue()); + } + + // 获取配置ID + int cfgId = DataTableUtil.getIntValue(firstRow, "id", 0); + + // 查询横幅信息 + String bannerSql = String.format("select title, desp, imgsrc from p_systemLoginBanner where disabled=0 and sysId='%d'", cfgId); + List> bannerList = jdbcTemplate.queryForList(bannerSql); + + // 将横幅信息添加到结果中 + datas.put("items", bannerList); + } + + return datas; + } + + /** + * 获取产品系统类型信息 + * + * @return 产品系统类型列表(用List>模拟DataTable) + */ + public List> GetProSysType() { + // 检查表是否存在的SQL + String checkTableSql = getDetailJDBC().GetProSysType(); + // 执行查询获取存在标志 + Object existFlagObj = jdbcTemplate.queryForObject(checkTableSql, Object.class); + int existFlag = NativeExtensionUtils.ToInt32(existFlagObj); + + // 表不存在时返回null + if (existFlag == 0) { + return null; + } + + // 表存在时查询类型数据 + String typeSql = "select [id], [SeriesName], [SeriesScript], [OperateDate], [OperatorName], [Ban] from P_SystemProductSeriesTab"; + return jdbcTemplate.queryForList(typeSql); + } + + /// + /// web服务端升级程序 + /// + /// + /// + public List> GetWebUpdateInfo(int code) { + return jdbcTemplate. + queryForList("select top 1 version ver,versioncode vcode," + + "note,download url," + + "force from [P_SystemWebUpdateTab] " + + "where versioncode>? order by createDate asc", code); + } + + public List> GetBINodeInfo(int nid) { + String sql = "select a.*,b.navigatetype ntype,b.nodename nname from BI_StructureTab a inner join BI_NodeTagTab b on a.tagid = b.id where b.id = ? and a.enableflag = 1 order by a.orderid "; + + return jdbcTemplate.queryForList(sql, nid); + } + + protected boolean CheckIsAudit() { + String sql = getDetailJDBC().CheckIsAudit(); + return ToInt32(jdbcTemplate.queryForObject(sql, Integer.class)) > 0; + } + + /** + * 保存附件显示模式(先更新,更新无数据则插入) + * + * @param moduleId 模块ID(对应C#的moduleId) + * @param userId 用户ID(对应C#的userId) + * @param viewType 视图类型(对应C#的viewType) + * @return 保存成功返回true,失败返回false + */ + public boolean SaveAttcViewModule(String moduleId, String userId, int viewType) { + // 1. 先执行更新操作:对应C#的UPDATE语句 + String updateSql = "UPDATE P_fm_WebCusSetTab " + + "SET viewType = ? " + + "WHERE dllcoid = ? AND operatorId = ?"; + + // JdbcTemplate.update()返回受影响的行数(对应C# ExecuteNonQuery()的返回值) + int updateCount = jdbcTemplate.update( + updateSql, + viewType, // 第1个?:对应C#的@type(viewType) + moduleId, // 第2个?:对应C#的@dllcoid(moduleId) + userId // 第3个?:对应C#的@userid(userId) + ); + + // 若更新成功(受影响行数>0),直接返回true + if (updateCount > 0) { + return true; + } + + // 2. 若更新无数据(updateCount=0),执行插入操作:对应C#的INSERT语句 + String insertSql = "INSERT INTO P_fm_WebCusSetTab (dllcoid, operatorId, viewType) " + + "VALUES (?, ?, ?)"; + + // 执行插入,获取受影响行数(插入成功返回1,失败返回0或抛异常) + int insertCount = jdbcTemplate.update( + insertSql, + moduleId, // 第1个?:对应C#的@dllcoid(moduleId) + userId, // 第2个?:对应C#的@userid(userId) + viewType // 第3个?:对应C#的@type(viewType) + ); + + // 插入成功(受影响行数>0)返回true,否则返回false + return insertCount > 0; + } + + public String GetBaseModuleSql(String moduleCode) { + String sql = "select [SQL] TableSQL from p_systemdlltab where DllCoid= ?"; + return jdbcTemplate.queryForObject(sql, new Object[]{moduleCode}, String.class); + } + + /** + * 检查移动设备是否已绑定 + * + * @param mobileId 移动设备ID + * @param userId 用户ID + * @param mult 是否允许多设备 + * @return 已绑定返回true,否则返回false + */ + public boolean HasBindMobileDev(String mobileId, String userId, boolean mult) { + // 构建多设备条件(与C#逻辑完全一致) + String multCond = mult ? "1<>1" : String.format("mobileId<>'%s' and userId='%s'", mobileId, userId); + + // 构建完整SQL查询(注意参数拼接需防止SQL注入,实际项目建议使用参数化查询) + String sql = String.format( + "select count(1) tot from p_systemMobileIdTab where (mobileId='%s' and userId<>'%s') or (%s)", + mobileId, userId, multCond + ); + + // 执行查询并获取结果(转为整数) + Integer count = jdbcTemplate.queryForObject(sql, Integer.class); + + // 返回计数是否大于0(与C#逻辑一致) + return count != null && count > 0; + } + + public int GetUserServerId(String userid, String userName) { +// 获取账套配置列表(对应C#的GetSysdbGroup()) + List> sysdbGroupList = GetSysdbGroup(0); + // 获取用户账号信息(对应C#的GetAccountInfoByStore) + List> accountInfoList = GetAccountInfoByStore(userid, userName); + + // 处理用户账号信息(原C#中的coutDt) + if (accountInfoList != null && accountInfoList.size() == 1) { + // 获取用户账号对应的dbname + Map accountInfo = accountInfoList.get(0); + String targetDbName = Objects.toString(accountInfo.get("dbname"), ""); + + // 从账套列表中查找匹配dbname的记录,返回其id(对应C#的LINQ查询) + return sysdbGroupList.stream() + // 过滤出dbname匹配的记录 + .filter(sysdb -> targetDbName.equals(Objects.toString(sysdb.get("dbname"), ""))) + // 提取id并转换为整数 + .map(sysdb -> { + Object idObj = sysdb.get("id"); + return idObj != null ? Integer.parseInt(idObj.toString()) : 0; + }) + // 取第一个匹配结果,无匹配则返回0 + .findFirst() + .orElse(0); + } + + // 无匹配记录时返回0 + return 0; + } + + /** + * 获取权限菜单行数据 + * + * @param fromkey 来源键 + * @param menutype 菜单类型 + * @param username 用户名 + * @param menuid 菜单ID + * @return 包含菜单数据的列表,每条记录以Map形式存在 + */ + public List> GetRightMenuRows(String fromkey, int menutype, String username, int menuid) { + // 基础SQL语句,使用命名参数替代字符串拼接 + // 基础SQL语句(达梦适配版) + String baseSql = """ + select orderid,id,privilegeoper, dllname library, action, dllpar1 param1, + dllpar2 param2, dllpar3 param3, dllpar4 param4, dllpar5 param5, + dllpar6 param6, dllpar7 param7,dllpar8 param8, dllpar9 param9, + dllpar10 param10,maxwindow, menuname menucaption,menucond,actiontype, + beforemsg,ifrefresh refresh,DBClickEvent dbclick,ifMoreClick multi, + mergeExec "merge",showtoolbar toBar,showMode,isCopy,beforeTab, + nvl(isStartRun,0) notRec,defailtImage icon,nvl(disabletype,0) disabletype, + nvl(hintMsg,'') desp,nvl(countSql,'') countSql,isMrpClickBtn + from p_systempopupmenu + where 1=1 + %s + %s + and (nvl(privilegeoper,'')='' or instr(','||privilegeoper||',', ','||:username||',')>0 ) + order by orderid asc + """; + + // 参数映射 + Map paramMap = new HashMap<>(); + paramMap.put("username", username); + paramMap.put("fromKey", fromkey); + paramMap.put("menuid", menuid); + + String whereCond1 = ""; + String whereCond2 = ""; + + if (menuid > 0) { + whereCond1 = " and id = :menuid "; + whereCond2 = ""; + } else { + whereCond1 = " and tab = :fromKey "; + + if (menutype == 0) { // 右键菜单 + whereCond1 += " AND nvl(menutype,0)=0 "; + } else if (menutype == 1) { // 常用功能 + whereCond1 += " AND nvl(menutype,0)=1 "; + } + + if (isWindowsDirver()) { + whereCond2 = " and visible=0 "; + } else { + whereCond2 = " and nvl(ShowMobile,0)=1 "; + } + } + + // 生成最终SQL + String finalSql = String.format(baseSql, whereCond1, whereCond2); + + // 执行查询 + List> result = namedJdbcTemplate.queryForList(finalSql, paramMap); + + // 转换列名为小写 + return ConversionUtils.toLowerColumnName(result); + } + + // 重载方法,提供默认menuid为0的情况 + public List> GetRightMenuRows(String fromkey, int menutype, String username) { + return GetRightMenuRows(fromkey, menutype, username, 0); + } + + /** + * 完全等价转换C#的 GetBaseModuleCfgs 方法 + * 原生JDBC实现、无框架依赖 + * + * @param moduleIds 模块ID,英文逗号分隔 例如:1001,1002,1003 + * @param menuId 菜单ID + * @return List> 等价C#的DataTable,一行数据对应一个Map + * @throws SQLException SQL异常抛出 + */ + public List> GetBaseModuleCfgs(String moduleIds, String menuId) { + // 1. 处理in条件参数:C#的 moduleIds.Replace(",","','") 等价Java写法 + String inSqlParams = moduleIds == null ? "" : moduleIds.replace(",", "','"); + + // 2. 拼接原C#的完整SQL语句,一字不差、逻辑完全一致 + String sql = "select dll.DllCoid moduleId,ISNULL(m1.MenuCaption,dll.ToolsName) title," + + "m1.UrlParams,m1.MenuId,m1.DllFileName,m.MenuCaption menuName " + + "from p_systemdlltab dll " + + "inner join p_formmenuconfigtab m1 on (m1.UrlParams=dll.DllCoid or m1.PurviewId=dll.DllCoid) and ISNULL(UseFlag,1)=1 " + + "left join p_formmenuconfigtab m on m.MenuId = ? " + + "where DllCoid in ('" + inSqlParams + "')"; + + // 4. JdbcTemplate执行查询,一行代码搞定,自动处理连接/关闭/结果集封装 + return jdbcTemplate.queryForList(sql, ToInt32(menuId)); + } + + /** + * 获取建档头部查询方案 + * 对应 C# 的 GetTreeSchemeSql 方法 + */ + @Cacheable(value = "treeSchemeSqlCache", key = "#moduleCode", condition = "#moduleCode != null") + public String getTreeSchemeSql(String moduleCode) { + // 1. 构建 SQL(命名参数避免注入) + String sql = "select TreeClickSchemeSql from p_systemdlltab where DllCoid=:menuCode"; + + // 2. 执行查询,处理空值 + String schemeSql = namedJdbcTemplate.queryForObject( + sql, + Collections.singletonMap("menuCode", moduleCode), + String.class + ); + + // 3. 空值兜底逻辑 + if (!isNullOrEmpty(schemeSql)) { + schemeSql = "select id groupid,ItemName from p_productsyssetGrouptab where speciesno = '%s'"; + } + return schemeSql; + } + + /** + * 获取方案字段数据源SQL + * 对应 C# 的 GetSchemeFieldDataSourceSql 方法 + */ + @Cacheable(value = "schemeFieldDataSourceSqlCache", key = "#id") + public String getSchemeFieldDataSourceSql(int id) { + // 注意:原C#用字符串拼接id有SQL注入风险,Java中改为命名参数 + String sql = "select DataSourceSql from p_productsyssettab where id=:id"; + + // 执行查询 + String sourceSql = namedJdbcTemplate.queryForObject( + sql, + Collections.singletonMap("id", id), + String.class + ); + + // 空值兜底 + if (!isNullOrEmpty(sourceSql)) { + sourceSql = "select b.midno as id,b.sysnamelist as sysnamelist,b.bak as bak " + + "from p_productsyssettab a join p_productsyslisttab b on a.id = b.mid " + + "where mid = :id"; // 此处保留原拼接逻辑,建议改为命名参数 + // 若要严格对齐原拼接:sourceSql = String.format(sourceSql, id); + } + return sourceSql; + } + + /** + * 获取方案联合SQL + * 对应 C# 的 GetSchemeUnionSql 方法 + */ + @Cacheable(value = "schemeUnionSqlCache", key = "#id") + public String getSchemeUnionSql(int id) { + String sql = "select RelevanceValue from p_productsyssettab where id=:id"; + + // 执行查询,空值返回空字符串 + String result = namedJdbcTemplate.queryForObject( + sql, + Collections.singletonMap("id", id), + String.class + ); + return result == null ? "" : result; + } + + /** + * 获取动态方案字段 + * 对应 C# 的 GetDynamicSchemeFields 方法(DataTable → List>) + */ + @Cacheable(value = "dynamicSchemeFieldsCache", key = "#groupId + '_' + #fieldId") + public List> getDynamicSchemeFields(int groupId, int fieldId) { + // 1. 构建动态条件 + StringBuilder idCond = new StringBuilder(); + Map params = new HashMap<>(); + if (fieldId > 0) { + idCond.append(" and id=:fieldId"); + params.put("fieldId", fieldId); + } else { + idCond.append(" and groupid = :groupId and isnull(ban,0) = 0 order by orderid"); + params.put("groupId", groupId); + } + + // 2. 构建完整SQL + String sql = String.format(""" + select id, + dbname FieldName, + sysname FieldCaption, + case when item=2 then 0 + when item=1 then 1 + when item=3 then 13 + when item=4 then 9 + when item=5 then 20 + when item=7 then 17 + when item=9 then 4 + when item=11 then 15 + when item=12 then 16 + else 0 end FieldType, + isnull(defaultName,defaultid) defaultvalue, + 5 FieldDataType, + 'id' valuemember, + 'sysnamelist' displaymember, + case when isnull(DataSourceSql,'')='' then 'select 0 id,'''' sysnamelist' else DataSourceSql end FieldSQL, + RelevanceField UnionField, + RelevanceValue UnionSQL, + DataFormat dataformat, + ProhibitEdited Edit, + isnull(musttig,0) nullable + from p_productsyssettab + where 1=1 %s + """, idCond); + + // 3. 执行查询,返回List模拟DataTable + return namedJdbcTemplate.queryForList(sql, params); + } + + // 重载方法:适配原C#的默认参数 fieldId=0 + public List> getDynamicSchemeFields(int groupId) { + return getDynamicSchemeFields(groupId, 0); + } + + /** + * 获取动态方案规则 + * 对应 C# 的 GetDynamicSchemeRule 方法(DataTable → List>) + */ + @Cacheable(value = "dynamicSchemeRuleCache", key = "#groupId") + public List> getDynamicSchemeRule(int groupId) { + // 构建SQL(替换原字符串插值为命名参数) + String sql = String.format(""" + select dbname fieldname ,dbname fxname ,fxtypedb valueType,'' preconstr,''endconstr,'' constr,0 orderid + from p_productsyssettab where groupid=:groupId and isnull(dbname,'')<>'' + union all + select * from ( + select dbname fieldname, 'productdescrip' fxname ,FxTypeZhsx valueType,ConnectorStrZhsx preconstr,EndConnectorStrZhsx endconstr,ConnectorStr constr,ProDeSort orderid + from p_productsyssettab where groupid=:groupId and ZhSxdbName=1 + union all + select dbname fieldname, 'spec' fxname ,FxTypeGg ,ConnectorStrGg ,EndConnectorStrGg ,ConnectorStr ,SpecSort from p_productsyssettab where groupid=:groupId and SpecDbName=1 + union all + select dbname fieldname,'model' fxname,FxTypeXh,ConnectorStrXh,EndConnectorStrXh,ConnectorStr,ModelSort from p_productsyssettab where groupid=:groupId and ModelDbName=1 + union all + select dbname fieldname,'grade' fxname,FxTypeCz,ConnectorStrCz,EndConnectorStrCz,ConnectorStr,GradeSort from p_productsyssettab where groupid=:groupId and CzdbName=1 + union all + select dbname fieldname,'appellation' fxname,FxTypeCz,'','','',0 from p_productsyssettab where groupid=:groupId and orderid=1 + ) a where fxname not in (select dbname from p_productsyssettab where groupid=:groupId and isnull(dbname,'')<>'') + order by orderid + """); + + // 执行查询 + return namedJdbcTemplate.queryForList(sql, Collections.singletonMap("groupId", groupId)); + } + + /** + * 获取单据弹窗字段,对应 C# 的 GetBillPopUpFields 方法 + * + * @param moduleId 模块ID + * @return List>(模拟DataTable,所有列名转为小写) + */ + public List> GetBillPopUpFields(String moduleId) { + // 1. 构建SQL(使用命名参数避免SQL注入,替换原字符串拼接) + String sql = """ + select tm_tagid, fieldname + from p_systemwordbooktab + where tab=:moduleId and tm_tagid in (1,2,3,4) + """; + + // 2. 绑定参数 + Map params = Collections.singletonMap("moduleId", moduleId); + + // 3. 执行查询,获取原始结果(列名默认是数据库返回的大小写) + List> originalResult = namedJdbcTemplate.queryForList(sql, params); + + // 4. 模拟 C# 的 ToLowerColumnName() 方法:将所有列名转为小写 + List> lowerCaseResult = new ArrayList<>(); + for (Map row : originalResult) { + Map lowerCaseRow = new HashMap<>(); + for (Map.Entry entry : row.entrySet()) { + // 列名转为小写,值保持不变 + lowerCaseRow.put(entry.getKey().toLowerCase(), entry.getValue()); + } + lowerCaseResult.add(lowerCaseRow); + } + + return lowerCaseResult; + } + + /** + * 获取历史审批步骤信息 + * 对应 C# 方法,返回 List> 模拟 DataTable + * + * @param moduleId 模块ID + * @param atStep 步骤编码 + * @param billType 单据类型(可为null,对应 C# 的 int?) + * @param isBase 是否基础表(默认true,对应 C# 的默认参数) + * @return 审批步骤信息列表(列名与原SQL一致) + */ + @Cacheable( + value = "hisAuditStepInfosCache", + key = "#moduleId + '_' + #atStep + '_' + #billType + '_' + #isBase", + condition = "#moduleId != null" + ) // 缓存20秒,缓存键包含所有入参 + public List> GetHisAuditStepInfos( + String moduleId, + int atStep, + Integer billType, // Java 用 Integer 对应 C# 的 int?(可空) + boolean isBase) { + + // 1. 确定表名(对应原逻辑的三元表达式) + String tableName = isBase ? "p_systemdlltabflowtypestep" : "p_systembillflowtypestep"; + + // 2. 构建动态SQL(避免字符串拼接,使用命名参数防注入) + String sql; + Map params = new HashMap<>(); + params.put("moduleId", moduleId); + params.put("billType", billType == null ? "" : billType); // 处理billType为null的情况 + + if (atStep == 999) { + // 分支1:atStep=999的SQL逻辑 + sql = """ + select '' sh, + '' operdirection, + stepname, + '未开始' opeadvice, + operUser operatorname, + '' applytime, + stepcode, + case when stepcode=:billType then 2 else 0 end sta, + isnull(remindOper,'')+isnull(comfirmOper,'') zfoper, + autostep, + autostepcond + from %s + where typeCode = :moduleId + and billtype=:billType + and isnull(remindOper,'')+isnull(comfirmOper,'')<>'' + order by stepcode desc + """.formatted(tableName); + } else { + // 分支2:默认SQL逻辑 + sql = """ + select '' sh, + '' operdirection, + stepname, + '未开始' opeadvice, + operUser operatorname, + '' applytime, + stepcode, + case when stepcode=:atStep then 2 else 0 end sta, + isnull(remindOper,'')+isnull(comfirmOper,'') zfoper, + autostep, + autostepcond + from %s + where typeCode = :moduleId + and stepcode>=:atStep + and billtype=:billType + order by stepcode desc + """.formatted(tableName); + params.put("atStep", atStep); // 仅非999分支绑定atStep参数 + } + + // 3. 执行查询,返回List模拟DataTable + return namedJdbcTemplate.queryForList(sql, params); + } + + // 重载方法:适配C#的默认参数 isBase = true + public List> GetHisAuditStepInfos(String moduleId, int atStep, Integer billType) { + return GetHisAuditStepInfos(moduleId, atStep, billType, true); + } + + /** + * 转换C#的GetTaskModuleInfo方法 + * + * @param moduleId 模块ID + * @param taskType 任务类型(YB/FQ/CS) + * @return List> 模拟DataTable,每个Map对应一行数据 + */ + public List> GetTaskModuleInfo(String moduleId, TaskType taskType) { + // 1. 确定SQL名称(对应C#的switch逻辑) + String sqlName = "tasksql"; + switch (taskType) { + case YB: + sqlName = "taskSQLYB"; + break; + case FQ: + sqlName = "taskSQLFQ"; + break; + case CS: + sqlName = "taskSQLCS"; + break; + default: + sqlName = "tasksql"; + break; + } + + // 2. 拼接SQL(对应C#的字符串插值 $@"") + String sql = String.format( + "select %s as tasksql, WebCardTpl, 1 as isbase from p_systemdlltab where DllCoid = :menuCode " + + "union all " + + "select %s as tasksql, 0 as WebCardTpl, 0 as isbase from p_systembilltype where typecode = :menuCode", + sqlName, sqlName + ); + + // 3. 构建参数(对应C#的DbParameter) + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("menuCode", moduleId); + + // 4. 执行查询(对应C#的ExecuteDataTable) + return namedJdbcTemplate.queryForList(sql, params); + } + + /** + * 转换C#的GetCountModuleInfo方法 + * + * @param moduleId 模块ID + * @return List> 模拟DataTable + */ + public List> GetCountModuleInfo(String moduleId) { + // 1. 拼接SQL + String sql = "select countSql, 1 as isbase from p_systemdlltab where DllCoid = :menuCode " + + "union all " + + "select countSql, 0 as isbase from p_systembilltype where typecode = :menuCode"; + + // 2. 构建参数 + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("menuCode", moduleId); + + // 3. 执行查询 + return namedJdbcTemplate.queryForList(sql, params); + } + + /** + * 对应原C#的getUserBmps方法 + * + * @param names 员工姓名列表(逗号分隔,如"张三,李四") + * @return 第一条结果的webbmp字段值(空则返回空字符串) + */ + public String GetUserBmps(String names) { + // 1. 校验入参,避免空指针 + if (names == null || names.trim().isEmpty()) { + return ""; + } + + // 2. 分割姓名成列表,用于SQL参数 + List nameList = Arrays.asList(names.split(",")); + // 过滤空姓名,避免无效的SQL参数 + nameList = nameList.stream().filter(name -> !name.trim().isEmpty()).toList(); + if (nameList.isEmpty()) { + return ""; + } + + // 3. 构建安全的SQL(使用?占位符防止SQL注入) + // 生成对应数量的?,比如3个姓名则生成 "?,?,?" + String placeholders = String.join(",", java.util.Collections.nCopies(nameList.size(), "?")); + String sql = String.format( + "SELECT employeename operatorname, webbmp FROM P_EmployeeTab WHERE employeename IN (%s)", + placeholders + ); + + try { + // 4. 执行查询,获取第一条记录的webbmp字段 + String webBmp = jdbcTemplate.queryForObject( + sql, // SQL语句 + nameList.toArray(), // 占位符对应的参数数组 + // 行映射器:将ResultSet中的字段映射为String + (ResultSet rs, int rowNum) -> rs.getString("webbmp") + ); + + // 5. 确保返回字符串(字段为空时返回空字符串) + return Optional.ofNullable(webBmp).orElse(""); + + } catch (EmptyResultDataAccessException e) { + // 无匹配记录时返回空字符串(对应C#的FirstOrDefault()) + return ""; + } catch (Exception e) { + // 捕获其他异常(如数据库连接异常),返回空字符串并打印日志 + log.warn(String.valueOf("查询员工webbmp失败:" + e.getMessage())); + return ""; + } + } + + /** + * 对应C#的GetAppPrintRows方法 + * + * @param moduleId 模块ID + * @return List> 模拟DataTable + */ + @Cache(ExpirationPeriod = 10, key = "#moduleId") // 缓存注解,key绑定moduleId + public List> getAppPrintRows(String moduleId) { + // 空值校验 + if (moduleId == null || moduleId.trim().isEmpty()) { + return List.of(); + } + + String sql = "select * from p_systemdllPrintModTab " + + "where tab = :moduleId and visible = 1 " + + "order by orderid"; + + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("moduleId", moduleId.trim()); + + return namedJdbcTemplate.queryForList(sql, params); + } + + /** + * 对应C#的GetOldAppSinglePrint方法 + * + * @param menuid 菜单ID + * @return List> 模拟DataTable + */ + @Cache(ExpirationPeriod = 10, key = "#menuid") + public List> getOldAppSinglePrint(String menuid) { + // 空值校验 + if (menuid == null || menuid.trim().isEmpty()) { + return List.of(); + } + + // 达梦适配:ISNULL→COALESCE,Round→ROUND,top 1→rownum=1 + String sql = "select " + + "case when COALESCE(pageheight, 0) > 0 then pageheight " + + " when COALESCE(parentHeight, 0) = 0 then 0 " + + " else ROUND(parentHeight / 10, 2) end as pageHeight, " + + "case when COALESCE(pagewidth, 0) > 0 then pagewidth " + + " when COALESCE(parentWidth, 0) = 0 then 0 " + + " else ROUND(parentWidth / 10, 2) end as pageWidth, " + + "direction " + + "from p_systemControlParent " + + "where formKey = ( " + + " select formKey from ( " + + " select formKey from P_systemdlltab where DllCoid = :menuid " + + " ) where rownum = 1 " + // 达梦替代top 1 + ")"; + + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("menuid", menuid.trim()); + + return namedJdbcTemplate.queryForList(sql, params); + } + + /** + * 对应C#的GetAppSinglePrint(formKey)方法 + * + * @param formKey 表单Key + * @return List> 模拟DataTable + */ + @Cache(ExpirationPeriod = 10, key = "#formKey") + public List> getAppSinglePrint(String formKey) { + // 空值校验 + if (formKey == null || formKey.trim().isEmpty()) { + return List.of(); + } + + String sql = "select " + + "case when COALESCE(pageheight, 0) > 0 then pageheight " + + " when COALESCE(parentHeight, 0) = 0 then 0 " + + " else ROUND(parentHeight / 10, 2) end as pageHeight, " + + "case when COALESCE(pagewidth, 0) > 0 then pagewidth " + + " when COALESCE(parentWidth, 0) = 0 then 0 " + + " else ROUND(parentWidth / 10, 2) end as pageWidth, " + + "direction " + + "from p_systemControlParent " + + "where formKey = :formKey"; + + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("formKey", formKey.trim()); + + return namedJdbcTemplate.queryForList(sql, params); + } + + /** + * 对应C#的GetAppOldSinglePrintItems方法 + * + * @param menuid 菜单ID + * @return List> 模拟DataTable + */ + @Cache(ExpirationPeriod = 10, key = "#menuid") + public List> getAppOldSinglePrintItems(String menuid) { + // 空值校验 + if (menuid == null || menuid.trim().isEmpty()) { + return List.of(); + } + + String sql = "select fieldname, " + + "username1 as text, " + // 别名[text]→text(达梦无需中括号) + "fieldsqlTag as fieldType, " + + "case when COALESCE(controlLeft, 0) = 0 then 0 else ROUND(controlLeft / 10, 2) end as left, " + + "case when COALESCE(controlTop, 0) = 0 then 0 else ROUND(controlTop / 10, 2) end as top, " + + "case when COALESCE(controlWidth, 0) = 0 then 0 else ROUND(controlWidth / 10, 2) end as width, " + + "case when COALESCE(controlHeight, 0) = 0 then 0 else ROUND(controlHeight / 10, 2) end as height, " + + "mobile_fontsize as fontSize " + + "from p_systemwordbooktab " + + "where tab = :menuid " + + "and controlWidth > 0 and controlHeight > 0 and addVisible = 0"; + + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("menuid", menuid.trim()); + + return namedJdbcTemplate.queryForList(sql, params); + } + + /** + * 对应C#的GetAppSinglePrintItems(formkey)方法 + * + * @param formkey 表单Key + * @return List> 模拟DataTable + */ + @Cache(ExpirationPeriod = 10, key = "#formkey") + public List> getAppSinglePrintItems(String formkey) { + // 空值校验 + if (formkey == null || formkey.trim().isEmpty()) { + return List.of(); + } + + String sql = "select fieldname, " + + "username as text, " + + "fieldTypeId as fieldType, " + + "case when COALESCE(controlLeft, 0) = 0 then 0 else ROUND(controlLeft / 10, 2) end as left, " + + "case when COALESCE(controlTop, 0) = 0 then 0 else ROUND(controlTop / 10, 2) end as top, " + + "case when COALESCE(controlWidth, 0) = 0 then 0 else ROUND(controlWidth / 10, 2) end as width, " + + "case when COALESCE(controlHeight, 0) = 0 then 0 else ROUND(controlHeight / 10, 2) end as height, " + + "COALESCE(mobile_fontsize, 12) as fontSize " + // isnull(mobile_fontsize,12) + "from p_systemControlLocation " + + "where formKey = :formkey " + + "and controlWidth > 0 and controlHeight > 0"; + + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("formkey", formkey.trim()); + + return namedJdbcTemplate.queryForList(sql, params); + } + + /** + * 对应C#的ExcuteStore方法(执行存储过程,处理返回值/输出参数) + * + * @param proName 存储过程名称 + * @param vals 输入参数数组 + * @return BaseResponse 统一响应 + */ + public BaseResponse excuteStore(String proName, Object[] vals) throws SQLException { + // 1. 初始化响应对象(对应C#: BaseResponse response = new BaseResponse() { success = true };) + BaseResponse response = new BaseResponse(); + response.setSuccess(true); + + // 2. 获取存储过程参数列表(对应C#: DbParameter[] pmList = dbOperator.GetStoreParams(proName);) + DbOperator.Parameter[] pmList = dbOperator.getStoreParams(proName); + + // 3. 创建输出参数@msg和返回值参数@return(完全匹配C#参数) + // 对应C#: DbParameter msgpm = dbOperator.GetParameter("@msg", "", DbType.String, 5000, ParameterDirection.Output); + DbOperator.Parameter msgpm = dbOperator.getParameter("@msg", "", 12, 5000, 2); + // 对应C#: DbParameter returnval = dbOperator.GetParameter("@return", -1, DbType.Int32, 4, ParameterDirection.ReturnValue); + DbOperator.Parameter returnval = dbOperator.getParameter("@return", -1, 4, 4, 2); + + // 4. 遍历参数列表绑定值(完全匹配C#的for循环逻辑) + int i = 0; // 输入参数索引 + // 对应C#: for (int j = 0; j < pmList.Length; j++) + for (int j = 0; j < pmList.length; j++) { + DbOperator.Parameter pm = pmList[j]; + // 对应C#: if (pm.Direction == ParameterDirection.ReturnValue) + if (pm.getDirection() == 2) { + pmList[j] = returnval; // 替换为返回值参数 + } + // 对应C#: else if (pmList[j].ParameterName == "@msg") + else if (Objects.equals(pmList[j].getName(), "@msg")) { + pmList[j] = msgpm; // 替换为输出参数@msg + } + // 对应C#: else { pm.Value = vals[i]; i++; } + else { + pm.setValue(vals[i]); // 绑定输入参数值 + i++; + } + } + + // 5. 执行存储过程获取DataSet(对应C#: DataSet set = dbOperator.ExecuteDataSet(...)) + ResultSet set = dbOperator.executeDataSet(proName, pmList); + + // 6. 解析返回值和输出参数(对应C#的类型转换) + // 对应C#: int rValue = returnval.Value.ToInt32(); + int rValue = ((Number) returnval.getValue()).intValue(); + // 对应C#: string rMsg = msgpm.Value.ToString(); + String rMsg = msgpm.getValue() == null ? "" : msgpm.getValue().toString(); + + // 7. 封装响应对象(完全匹配C#逻辑) + response.setOther(rValue); + // 对应C#: if (rValue == 1) + if (rValue == 1) { + response.setSuccess(true); + // 对应C#: response.msg = string.IsNullOrEmpty(rMsg) ? LanguageUtil.Success : rMsg; + response.setMsg((rMsg == null || rMsg.trim().isEmpty()) ? LanguageUtil.Success : rMsg); + // 对应C#: if (set!=null&&set.Tables.Count > 0) + if (set != null && !set.wasNull()) { + response.setData(set); // 绑定结果集 + } + } + // 对应C#: else + else { + // 对应C#: response.msg = (msgpm.Value + "").Replace("\r", "
"); + String msgValue = msgpm.getValue() == null ? "" : msgpm.getValue().toString(); + response.setMsg(msgValue.replace("\r", "
")); + } + + // 对应C#: return response; + return response; + } + + + /** + * 对应C#的DoResetPwd方法(重置密码:优先调用存储过程,失败则执行SQL更新) + * + * @param userId 员工ID + * @param enNewPwd 加密后的新密码 + * @return BaseResponse 统一响应 + */ + public BaseResponse doResetPwd(String userId, String enNewPwd) throws SQLException { + BaseResponse response = new BaseResponse(); + String proName = "pr_sytemchangepsw"; + + // 1. 检查存储过程是否存在 + if (IsExitPro(proName)) { + // 2. 调用存储过程重置密码 + Object[] vals = new Object[]{userId, enNewPwd}; + response = excuteStore(proName, vals); + if (response.isSuccess()) { + response.setMsg(LanguageUtil.Success); + response.setSuccess(true); + } + } else { + // 3. 存储过程不存在,执行SQL更新(参数化避免注入) + String updateSql = "UPDATE P_EmployeeTab " + + "SET Password = :enNewPwd " + + "WHERE EmployeeId = :userId AND COALESCE(sign, 0) = 0"; + + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("enNewPwd", enNewPwd); + params.addValue("userId", userId); + + // 执行更新并判断影响行数 + int affectedRows = namedJdbcTemplate.update(updateSql, params); + if (affectedRows > 0) { + response.setMsg(LanguageUtil.Success); + response.setSuccess(true); + } else { + response.setMsg("密码重置失败:未找到该员工或员工状态异常"); + response.setSuccess(false); + } + } + + return response; + } + + // 【备选方案】若SimpleJdbcCall不满足,可使用原生CallableStatement执行存储过程(更贴近C#逻辑) + public BaseResponse excuteStoreWithCallable(String proName, Object[] vals) { + BaseResponse response = new BaseResponse(); + response.setSuccess(true); + + // 1. 构建存储过程调用SQL(达梦格式:{?=call 存储过程名(?,?,?)}) + String callSql = "{?=call " + proName + "(?,?,?)}"; // 根据实际参数个数调整?数量 + + // 2. 执行CallableStatement + jdbcTemplate.execute((ConnectionCallback) conn -> { + try (CallableStatement cs = conn.prepareCall(callSql)) { + // 注册返回值(@return) + cs.registerOutParameter(1, Types.INTEGER); + // 注册输出参数@msg + cs.registerOutParameter(2, Types.VARCHAR, 5000); + // 绑定输入参数(userId, enNewPwd) + cs.setString(3, (String) vals[0]); // userId + cs.setString(4, (String) vals[1]); // enNewPwd + + // 执行存储过程 + boolean hasResultSet = cs.execute(); + // 处理结果集(模拟DataSet) + if (hasResultSet) { + ResultSetExtractor>> rse = (ResultSetExtractor>>) new ColumnMapRowMapper(); + // 此处简化,实际需遍历结果集 + } + + // 解析返回值和输出参数 + int rValue = cs.getInt(1); + String rMsg = cs.getString(2); + + response.setOther(rValue); + if (rValue == 1) { + response.setSuccess(true); + response.setMsg(rMsg == null || rMsg.trim().isEmpty() ? LanguageUtil.Success : rMsg); + } else { + response.setMsg(rMsg == null ? "" : rMsg.replace("\r", "
")); + response.setSuccess(false); + } + } catch (Exception e) { + response.setSuccess(false); + response.setMsg("执行存储过程失败:" + e.getMessage()); + } + return response; + }); + + return response; + } + + /** + * 获取移动端列配置(完整还原原C#逻辑) + * 注解说明: + * - @Cache:缓存注解,过期时间20(单位:分钟/秒,需和原项目一致) + * - @MBoundary:请求边界注解(接口/参数校验) + * + * @param i_menuid 菜单ID + * @return 移动端列配置列表(List对应C# DataTable) + */ + @Cache(ExpirationPeriod = 20) + public List> getMobileColumns(String i_menuid) { + // 1. 拼接SQL(完全还原原查询逻辑) + // 注:原SQL使用字符串拼接存在SQL注入风险,生产环境建议使用参数化查询 + String sqlFirst = String.format( + "select mobile_field field,mobile_color color,mobile_fontsize size,dataformat " + + "from p_systemwordbooktab " + + "where tab = '%s' " + + "and ISNULL(mobile_field,'')<>'' " + + "and ISNULL(mobile_field,'')<>'0' " + + "and isnull(showmobile1,'0')=1 " + + "order by mobile_order,mobile_field", // 保留原排序规则 + i_menuid + ); + + // 2. 执行SQL并返回结果(DataTable → List>) + return jdbcTemplate.queryForList(sqlFirst); + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Impl/FileImpl.java b/WebErp/weberp/src/main/java/org/example/Impl/FileImpl.java new file mode 100644 index 0000000..dab79a4 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Impl/FileImpl.java @@ -0,0 +1,116 @@ +package org.example.Impl; + +import org.example.Entity.BaseResponse.BaseResponse; +import org.example.Utils.*; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.nio.file.Path; +import java.sql.Types; +import java.util.Map; + +import static org.example.Utils.NativeExtensionUtils.ToInt32; +import static org.example.Utils.NativeExtensionUtils.isNullOrEmpty; + +@Service +public class FileImpl extends OptBaseImpl { + + private final JdbcTemplate jdbcTemplate; + private NamedParameterJdbcTemplate namedJdbcTemplate; + + public FileImpl(JdbcTemplate jdbcTemplate) { + super(); + this.jdbcTemplate = jdbcTemplate; + } + + private String toTempFileName(String filename) { + if (!isNullOrEmpty(filename) && !filename.endsWith("temp")) { + return StringFormat.format("{0}.{1}", filename, "temp"); + } + + return filename; + } + + public BaseResponse Check(Map fInfos) { + String menucode = String.valueOf(fInfos.get("menucode")); + String value = String.valueOf(fInfos.get("value")); + String filename = String.valueOf(fInfos.get("filename")); + String fileNo = String.valueOf(fInfos.get("fileNo")); + + BaseResponse _tempR = FileUtil.checkExistsFile(StringFormat.format(WebConfigUtil.PubModelAccFilePath, menucode, value, fileNo), toTempFileName(filename)); + BaseResponse response = FileUtil.checkExistsFile(StringFormat.format(WebConfigUtil.PubModelAccFilePath, menucode, value, fileNo), filename); + + response.setOther(("1".equals(_tempR.getOther() != null ? _tempR.getOther().toString() : "") || "1".equals(response.getOther() != null ? response.getOther().toString() : "")) ? 1 : 0); + return response; + } + + + public BaseResponse Save(byte[] buffer, Map fInfos) { + String username = (String) fInfos.getOrDefault("username", ""); + String userid = (String) fInfos.getOrDefault("userid", ""); + String menucode = (String) fInfos.getOrDefault("menucode", ""); + String key = (String) fInfos.getOrDefault("key", ""); + String value = (String) fInfos.getOrDefault("value", ""); + String filename = (String) fInfos.getOrDefault("filename", ""); + String filesize = (String) fInfos.getOrDefault("filesize", ""); + String fileNo = (String) fInfos.getOrDefault("fileNo", ""); + + int position = ToInt32(fInfos.getOrDefault("position", "")); + +// 方法中参数继承,需修改 +// return FileUtil.SaveFile(buffer, position,0, StringFormat.format(WebConfigUtil.PubModelAccFilePath, menucode, value, fileNo), filename,AppDomain); + return null; + } + + public BaseResponse AddFileInfo(Map fInfos) { + BaseResponse response = new BaseResponse(); + String menucode = (String) fInfos.get("menucode"); + String value = (String) fInfos.get("value"); + String fileNo = (String) fInfos.get("fileNo"); + String filePath = StringFormat.format((String) WebConfigUtil.PubModelAccFilePath, menucode, value, fileNo); + String filename = (String) fInfos.get("filename");//需修改 + String tempFilename = toTempFileName(filePath); + String sourceFilePath = (String) fInfos.get("sourceFilePath");//需修改 + + //需修改 +// FileUtil.MoveTo(sourceFilePath, filePath, filename, AttcPath); + + try { + String sql = "INSERT INTO dbo.T_SystemFileTab (" + + "FileName, FileSize, FilePath, SpeciesNo, MenuCode, " + + "PrimaryKey, PrimaryValue, CreateID, CreateName, CreateDate, FileState) " + + "VALUES (:FileName, :FileSize, :FilePath, :SpeciesNo, :MenuCode, " + + ":PrimaryKey, :PrimaryValue, :CreateID, :CreateName, GETDATE(), 0); " + + "SELECT @@IDENTITY AS id;"; + + String fileName = new File(fInfos.get("filename").toString()).getName(); + + // 构建参数(对应DbParameter数组) + MapSqlParameterSource params = new MapSqlParameterSource() + .addValue("FileName", fileName, Types.VARCHAR) + .addValue("FileSize", fInfos.get("filesize"), Types.VARCHAR) + .addValue("FilePath", StringFormat.format(WebConfigUtil.PubModelAccFilePath, + fInfos.get("menucode"), + fInfos.get("value"), + fInfos.get("fileNo")), Types.VARCHAR) + .addValue("SpeciesNo", fInfos.get("fileNo"), Types.VARCHAR) + .addValue("MenuCode", fInfos.get("menucode"), Types.VARCHAR) + .addValue("PrimaryKey", fInfos.get("key"), Types.VARCHAR) + .addValue("PrimaryValue", fInfos.get("value"), Types.VARCHAR) + .addValue("CreateID", Integer.parseInt(fInfos.get("userid").toString()), Types.INTEGER) + .addValue("CreateName", fInfos.get("username"), Types.VARCHAR); + int result = ToInt32(namedJdbcTemplate.queryForObject(sql, params, Object.class)); + + response.setSuccess(result > 0); + response.setOther(result); + + } catch (Exception ex) { + response.setMsg(ex.getMessage()); + } + return response; + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Impl/MapImpl.java b/WebErp/weberp/src/main/java/org/example/Impl/MapImpl.java new file mode 100644 index 0000000..ed92b18 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Impl/MapImpl.java @@ -0,0 +1,200 @@ +package org.example.Impl; + + +import org.example.Entity.BaseResponse.BaseResponse; +import org.example.Entity.Node.TreeNode; +import org.example.Utils.DbOperator; +import org.example.Utils.JSON; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.stream.Collectors; + +@Service +public class MapImpl extends OptBaseImpl { + + @Autowired + private JdbcTemplate jdbcTemplate; + + private final DbOperator dbOperator; + + @Autowired + public MapImpl(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + this.dbOperator = new DbOperator(jdbcTemplate); + } + + /** + * 根据模块ID获取地理位置标记图标路径 + * + * @param dllcoid 模块ID + * @return 图标路径 + */ + public String getMarkerIcon(String dllcoid) { + String sql = "select locationimg from dbo.p_systemdlltab where dllcoid = ?"; + Object result = jdbcTemplate.queryForObject(sql, new Object[]{dllcoid}, String.class); + return result != null ? result.toString() : ""; + } + + /** + * 根据城市名称获取地理位置信息 + * + * @param cityname 城市名称 + * @return 包含地理位置信息的响应对象 + */ + public BaseResponse getArea(String cityname) { + BaseResponse response = new BaseResponse(); + String select = "id as code, parent_id as parentcode, name, longitude as lng, latitude as lat, boundaries, level, desp"; + String sql = String.format("select %s from dbo.P_Area where name = ? or short_name = ?", select); + + List> data = jdbcTemplate.queryForList(sql, cityname, cityname); + response.setData(data); + response.setSuccess(true); + return response; + } + + /** + * 根据城市名称获取所属城市及下一级和父级相关信息 + * + * @param cityname 城市名称 + * @return 包含地理位置信息的响应对象 + */ + public BaseResponse getAreas(String cityname) { + BaseResponse response = new BaseResponse(); + String select = "id as code, parent_id as parentcode, name, longitude, latitude, boundaries, level, desp"; + String citySql = String.format("select %s from dbo.P_Area where name = ? or short_name = ?", select); + + List> cityData = jdbcTemplate.queryForList(citySql, cityname, cityname); + + if (!cityData.isEmpty()) { + Map firstRow = cityData.get(0); + String id = firstRow.get("code").toString(); + String parentId = firstRow.get("parentcode").toString(); + + String areaSql = String.format("select %s from dbo.P_Area where parent_id = ? " + + "union all " + + "select %s from dbo.P_Area where parent_id = (select parent_id from dbo.P_Area where id = ?)", select, select); + + List> areaData = jdbcTemplate.queryForList(areaSql, parentId, id); + response.setData(areaData); + response.setSuccess(true); + } + + return response; + } + + /** + * 根据城市ID获取子级城市信息 + * + * @param id 城市ID + * @return 包含子级城市信息的响应对象 + */ + public BaseResponse getAreaChildren(String id) { + BaseResponse response = new BaseResponse(); + String select = "id as code, parent_id as parentcode, name, longitude, latitude, boundaries, level, desp"; + String sql = String.format("select %s from dbo.P_Area where parent_id = ?", select); + + List> data = jdbcTemplate.queryForList(sql, id); + response.setData(data); + response.setSuccess(true); + return response; + } + + /** + * 获取地理位置的整个树形结构 + * + * @param name 城市名称 + * @return 包含树形结构的响应对象 + */ + public BaseResponse getAreasToTree(String name) { + BaseResponse response = new BaseResponse(); + String code = ""; + + // 获取城市编码 + String codeSql = "select id from dbo.P_Area where name = ?"; + try { + code = jdbcTemplate.queryForObject(codeSql, new Object[]{name}, String.class); + } catch (Exception e) { + // 未查询到对应城市时返回空响应 + return response; + } + + if (code == null || code.isEmpty()) { + return response; + } + + // 处理编码(去除末尾的0) + String trimmedCode = code.replaceAll("0+$", ""); + String sql = "select id as code, parent_id as parentcode, name, level, desp from dbo.P_Area where id like ?"; + List> areaData = jdbcTemplate.queryForList(sql, trimmedCode + "%"); + + if (!areaData.isEmpty()) { + // 转换为TreeNode列表 + List nodes = areaData.stream().map(row -> { + TreeNode node = new TreeNode(); + node.setSpeciesno(row.get("code").toString()); + node.setSpeciesname(row.get("name").toString()); + node.parentno = (row.get("parentcode") != null ? row.get("parentcode").toString() : ""); + node.setCheckbox(false); + return node; + }).collect(Collectors.toList()); + + // 设置子节点 + for (TreeNode node : nodes) { + List children = nodes.stream() + .filter(child -> child.parentno.equals(node.getSpeciesno())) + .collect(Collectors.toList()); + node.children = (children); + } + + // 获取根节点(与原始code匹配的节点) + String finalCode = code; + List rootNodes = nodes.stream() + .filter(node -> node.getSpeciesno().equals(finalCode)) + .collect(Collectors.toList()); + + response.setData(rootNodes); + response.setSuccess(true); + } + + return response; + } + + /** + * 修改地理位置数据 + * + * @param id 地理位置ID + * @param datas 修改的数据(JSON字符串) + * @return 是否修改成功 + */ + public boolean updateArea(String id, String datas) { + // 解析JSON数据为Map + Map dataMap = (Map) JSON.Decode(datas); + if (dataMap.isEmpty()) { + return false; + } + + // 构建更新SQL + StringBuilder updateSql = new StringBuilder("update P_Area set "); + List params = new ArrayList<>(); + + for (Map.Entry entry : dataMap.entrySet()) { + updateSql.append(entry.getKey()).append(" = ?, "); + params.add(entry.getValue()); + } + + // 移除末尾的逗号和空格 + if (updateSql.length() > 0) { + updateSql.setLength(updateSql.length() - 2); + } + + updateSql.append(" where id = ?"); + params.add(id); + + // 执行更新 + int rowsAffected = jdbcTemplate.update(updateSql.toString(), params.toArray()); + return rowsAffected > 0; + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Impl/ModuleEventImpl.java b/WebErp/weberp/src/main/java/org/example/Impl/ModuleEventImpl.java new file mode 100644 index 0000000..f3f2baf --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Impl/ModuleEventImpl.java @@ -0,0 +1,234 @@ +package org.example.Impl; + + +import org.example.Api.LoggerHandler; +import org.example.Entity.BaseResponse.BaseResponse; +import org.example.Entity.Control.Com.SysPoPupMenuBtn; +import org.example.Entity.EventArgs; +import org.example.Entity.System.BillStateEn; +import org.example.Entity.System.ModuleBaseEntity; +import org.example.Enums.SystemEnums; +import org.example.Service.IModuleEvent; +import org.example.Utils.FileUtil; +import org.example.Utils.WebConfigUtil; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +public class ModuleEventImpl extends OptBaseImpl { + private static final AtomicReference instance = new AtomicReference<>(null); + + public static ModuleEventImpl getInstance() { + if (instance.get() == null) { + synchronized (ModuleEventImpl.class) { + if (instance.get() == null) { + instance.set(new ModuleEventImpl()); + } + } + } + return instance.get(); + } + + // 事件监听器列表 + private final List beforeModuleDataLoadListeners = new ArrayList<>(); + private final List beforeModuleDataChangeListeners = new ArrayList<>(); + private final List afterModuleDataChangeListeners = new ArrayList<>(); + private final List beforeModuleDataDeleteListeners = new ArrayList<>(); + private final List afterModuleDataDeleteListeners = new ArrayList<>(); + private final List beforeModuleStateChangeListeners = new ArrayList<>(); + private final List afterModuleStateChangeListeners = new ArrayList<>(); + private final List beforeModuleAuditStateChangeListeners = new ArrayList<>(); + private final List afterModuleAuditStateChangeListeners = new ArrayList<>(); + private final List beforeModuleContextMenuListeners = new ArrayList<>(); + private final List afterModuleContextMenuListeners = new ArrayList<>(); + private final List beforeUploadFileListeners = new ArrayList<>(); + private final List afterUploadFileListeners = new ArrayList<>(); + private final List appStartListeners = new ArrayList<>(); + private final List appEndListeners = new ArrayList<>(); + + private ModuleEventImpl() { + loadEventHandler(); + } + + private void loadEventHandler() { + String eventCls = WebConfigUtil.get("ModuleApiHandler", ""); + if (eventCls == null || eventCls.isEmpty()) { + return; + } + + String[] classInfos = eventCls.split(";"); + for (String cls : classInfos) { + if (cls == null || cls.isEmpty() || !cls.contains(",")) { + continue; + } + + String[] clsParts = cls.split(",", 2); + if (clsParts.length < 2 || clsParts[1].isEmpty()) { + continue; + } + + String className = clsParts[0].trim(); + String assemblyName = clsParts[1].trim(); + + try { + LoggerHandler.debug(this, "加载程序集: " + assemblyName); + + // 构建程序集路径 + String appPath = System.getProperty("user.dir"); + String assemblyPath = appPath + File.separator + "bin" + File.separator + assemblyName; + File assemblyFile = new File(assemblyPath); + + if (!assemblyFile.exists()) { + LoggerHandler.error(this, "程序集不存在: " + assemblyPath); + continue; + } + + // 加载外部类 + URLClassLoader classLoader = new URLClassLoader( + new URL[]{assemblyFile.toURI().toURL()}, + Thread.currentThread().getContextClassLoader() + ); + Class eventClass = classLoader.loadClass(className); + IModuleEvent eventOper = (IModuleEvent) eventClass.getDeclaredConstructor().newInstance(); + + if (eventOper != null) { + LoggerHandler.debug(this, "加载程序集实例: " + className); + registerListener(eventOper); + } else { + LoggerHandler.error(this, "未能实例化事件类: " + className); + } + } catch (MalformedURLException e) { + LoggerHandler.error(this, "程序集路径错误: " + assemblyName + " - " + e.getMessage()); + } catch (ClassNotFoundException e) { + LoggerHandler.error(this, "未找到事件类: " + className + " - " + e.getMessage()); + } catch (Exception e) { + LoggerHandler.error(this, "加载事件程序集出错: " + cls + " - " + e.getMessage()); + } + } + } + + private void registerListener(IModuleEvent listener) { + beforeModuleDataLoadListeners.add(listener::beforeModuleDataLoad); + beforeModuleDataChangeListeners.add(listener::beforeModuleDataChange); + afterModuleDataChangeListeners.add(listener::afterModuleDataChange); + beforeModuleDataDeleteListeners.add(listener::beforeModuleDataDelete); + afterModuleDataDeleteListeners.add(listener::afterModuleDataDelete); + beforeModuleStateChangeListeners.add(listener::beforeModuleStateChange); + afterModuleStateChangeListeners.add(listener::afterModuleStateChange); + beforeModuleAuditStateChangeListeners.add(listener::beforeModuleAuditStateChange); + afterModuleAuditStateChangeListeners.add(listener::afterModuleAuditStateChange); + beforeModuleContextMenuListeners.add(listener::beforeModuleContextMenu); + afterModuleContextMenuListeners.add(listener::afterModuleContextMenu); + beforeUploadFileListeners.add(listener::beforeUploadFile); + afterUploadFileListeners.add(listener::afterUploadFile); + appStartListeners.add(listener::appStart); + appEndListeners.add(listener::appEnd); + } + + // 事件调用方法 + public void callBeforeModuleDataLoad(String moduleId) { + ModuleBaseEntity module = new ModuleBaseEntity(); + module.setModuleId(moduleId); + beforeModuleDataLoadListeners.forEach(listener -> listener.onModuleDataLoad(module)); + } + + public void callBeforeModuleDataChange(ModuleBaseEntity module, SystemEnums.ActionType aType, BaseResponse response) { + beforeModuleDataChangeListeners.forEach(listener -> listener.onModuleDataChange(module, aType, response)); + } + + public void callAfterModuleDataChange(ModuleBaseEntity module, SystemEnums.ActionType aType, BaseResponse response) { + afterModuleDataChangeListeners.forEach(listener -> listener.onModuleDataChange(module, aType, response)); + } + + public void callBeforeModuleDataDelete(ModuleBaseEntity module, BaseResponse response) { + beforeModuleDataDeleteListeners.forEach(listener -> listener.onModuleDataChange(module, SystemEnums.ActionType.Delete, response)); + } + + public void callAfterModuleDataDelete(ModuleBaseEntity module, BaseResponse response) { + afterModuleDataDeleteListeners.forEach(listener -> listener.onModuleDataChange(module, SystemEnums.ActionType.Delete, response)); + } + + public void callBeforeModuleStateChange(ModuleBaseEntity module, SystemEnums.ActionType aType, BaseResponse response) { + beforeModuleStateChangeListeners.forEach(listener -> listener.onModuleStateChange(module, aType, response)); + } + + public void callAfterModuleStateChange(ModuleBaseEntity module, SystemEnums.ActionType aType, BaseResponse response) { + afterModuleStateChangeListeners.forEach(listener -> listener.onModuleStateChange(module, aType, response)); + } + + public void callBeforeModuleAuditStateChange(ModuleBaseEntity module, BillStateEn stateEn, BaseResponse response) { + beforeModuleAuditStateChangeListeners.forEach(listener -> listener.onModuleAuditStateChange(module, stateEn, response)); + } + + public void callAfterModuleAuditStateChange(ModuleBaseEntity module, BillStateEn stateEn, BaseResponse response) { + afterModuleAuditStateChangeListeners.forEach(listener -> listener.onModuleAuditStateChange(module, stateEn, response)); + } + + public void callBeforeModuleContextMenu(SysPoPupMenuBtn btn, BaseResponse response) { + beforeModuleContextMenuListeners.forEach(listener -> listener.onModuleContextMenu(btn, response)); + } + + public void callAfterModuleContextMenu(SysPoPupMenuBtn btn, BaseResponse response) { + afterModuleContextMenuListeners.forEach(listener -> listener.onModuleContextMenu(btn, response)); + } + + public void callBeforeUploadFile(FileUtil.PathInfo info, BaseResponse response) { + beforeUploadFileListeners.forEach(listener -> listener.onFileUpload(info, response)); + } + + public void callAfterUploadFile(FileUtil.PathInfo info, BaseResponse response) { + afterUploadFileListeners.forEach(listener -> listener.onFileUpload(info, response)); + } + + public void appStart(Object sender, EventArgs e) throws SQLException { + new UpdateImpl().checkUpdate(); + appStartListeners.forEach(listener -> listener.onAppEvent(sender, e)); + } + + public void appEnd(Object sender, EventArgs e) throws SQLException { + new UpdateImpl().checkUpdate(); + appEndListeners.forEach(listener -> listener.onAppEvent(sender, e)); + } + + // 事件监听器接口定义 + @FunctionalInterface + private interface ModuleDataLoadListener { + void onModuleDataLoad(ModuleBaseEntity module); + } + + @FunctionalInterface + private interface ModuleDataChangeListener { + void onModuleDataChange(ModuleBaseEntity module, SystemEnums.ActionType aType, BaseResponse response); + } + + @FunctionalInterface + private interface ModuleStateChangeListener { + void onModuleStateChange(ModuleBaseEntity module, SystemEnums.ActionType aType, BaseResponse response); + } + + @FunctionalInterface + private interface ModuleAuditStateChangeListener { + void onModuleAuditStateChange(ModuleBaseEntity module, BillStateEn stateEn, BaseResponse response); + } + + @FunctionalInterface + private interface ModuleContextMenuListener { + void onModuleContextMenu(SysPoPupMenuBtn btn, BaseResponse response); + } + + @FunctionalInterface + private interface FileUploadListener { + void onFileUpload(FileUtil.PathInfo info, BaseResponse response); + } + + @FunctionalInterface + private interface AppListener { + void onAppEvent(Object sender, EventArgs e); + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Impl/ModuleImpl.java b/WebErp/weberp/src/main/java/org/example/Impl/ModuleImpl.java new file mode 100644 index 0000000..3f7aa65 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Impl/ModuleImpl.java @@ -0,0 +1,14893 @@ +package org.example.Impl; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.spire.xls.collections.IList; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.ArrayUtils; +import org.bytedeco.opencv.presets.opencv_core; +import org.example.Api.LoggerHandler; +import org.example.Auth.utils.SafetyUtil; +import org.example.Entity.BaseResponse.BaseResponse; +import org.example.Entity.Control.Base.Component; +import org.example.Entity.Control.Com.Button; +import org.example.Entity.Control.Com.SysPoPupMenuBtn; +import org.example.Entity.Control.Container.ChartContainer; +import org.example.Entity.Control.Container.MContainer; +import org.example.Entity.Control.Data.DataStore; +import org.example.Entity.Control.Fields.*; +import org.example.Entity.Control.Container.RowColumn; +import org.example.Entity.Control.Panel.GridPanel; +import org.example.Entity.Control.Panel.Panel; +import org.example.Entity.Control.Panel.TreePanel; +import org.example.Entity.CusException.CusException; +import org.example.Entity.System.Audit.AuditStep; +import org.example.Entity.System.BaseModule; +import org.example.Entity.System.BillModule; +import org.example.Entity.System.ModuleEntity; +import org.example.Entity.System.*; +import org.example.Enums.*; +import org.example.Impl.Sql.factory.AllInOneSqlFactory; +import org.example.Impl.Sql.provider.AllInOneSqlProvider; +import org.example.ModuleApi.ModuleAjaxApi.dto.module.ModuleIdFieldDTO; +import org.example.ModuleApi.ModuleAjaxApi.mapper.CRMapper; +import org.example.Service.ModuleImplService; +import org.example.Utils.*; +import org.example.Utils.DataTableUtil; +import org.example.Utils.Ref; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.jdbc.core.*; +import org.springframework.stereotype.Service; +import org.example.Entity.Control.Container.Column; +import org.example.Entity.Control.Panel.Chart; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.io.File; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.sql.*; +import java.text.SimpleDateFormat; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; +import java.util.*; +import java.util.Date; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.example.Utils.PrintHelper; + +import static org.apache.commons.lang3.StringUtils.join; +import static org.apache.commons.lang3.StringUtils.stripEnd; +import static org.apache.commons.lang3.math.NumberUtils.toInt; +import static org.example.Utils.DataTableUtil.get; +import static org.example.Utils.DataTableUtil.toHashTable; +import static org.example.Utils.NativeExtensionUtils.isNullOrEmpty; +import static org.example.Utils.NativeExtensionUtils.*; + +@SuppressWarnings("LanguageDetectionInspection") +@Service +public class ModuleImpl extends OptBaseImpl implements ModuleImplService { + // 定义统一入口,接收所有请求 + @Autowired + private CRMapper crmapper; + @Autowired + private JdbcTemplate jdbcTemplate; + @Autowired + private DataImpl DataImpl; + @Autowired + private IPublicUtil createControl; + @Autowired + private DbOperator dbOperator; + + @Value("${custom.database.type}") + private String databaseType; + + // 时间定义声明 + SimpleDateFormat timestampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + + @Autowired + AllInOneSqlFactory allInOneSqlFactory; // 注入Spring管理的实例 + + public AllInOneSqlProvider getDetailJDBC() { + return allInOneSqlFactory.createProvider(databaseType); + } + + private static ModuleEventImpl getEventHandler() { + + return ModuleEventImpl.getInstance(); + + } + + private int[] tot = new int[1]; + + @Autowired + // 构造方法,在依赖注入完成后初始化DbOperator + public ModuleImpl(JdbcTemplate jdbcTemplate) { + this.createControl = new IPublicUtil(jdbcTemplate);// 此时jdbcTemplate已注入 + } + + private PushImpl _pushImpl; + + private PushImpl getpushImpl() { + if (_pushImpl == null) { + return _pushImpl = new PushImpl(); + } + return _pushImpl; + } + + /// + /// 【=1=】显示,会读取两个表的 inner join,获得用户 operatorId的 显示配置 + /// + /// + /// > itemsDT = DataImpl.GetBSDesktopItemList(getUser().UserId, getUser().UserName); + //【=2=】item的 data 数据解析 + List> result = AnalyseBSDesktopData(itemsDT); + response.setSuccess(true); + response.setData(result); + + return response; + } + + List> AnalyseBSDesktopData(List> itemsDT) { + StringBuilder tempStr = new StringBuilder(); + return AnalyseBSDesktopData(itemsDT, tempStr, ""); + } + + /** + * 解析桌面数据并返回处理后的结果列表 + * + * @param itemsList 元素数据列表 + * @param queryVal 查询值 + * @return 包含处理后数据的哈希表列表 + */ + public List> AnalyseBSDesktopData( + List> itemsList, + StringBuilder msg, + String queryVal) { + + if (msg == null) { + msg = new StringBuilder(); + } else { + msg.setLength(0); + } + + List> result = new ArrayList<>(); + + for (Map oneItem : itemsList) { + Map item = toHashTable(oneItem, false, true, false); + Map oneItemHS = new HashMap<>(item); + + String itemCode = Objects.toString(oneItem.get("itemCode"), ""); + String sourceString = Objects.toString(oneItem.get("itemDataSource"), "").trim(); + int ifEnable = convertToInt(oneItem.get("enableflag"), 0); + int ifLinked = convertToInt(oneItem.get("itemLinked"), 0); + String itemTypeCode = Objects.toString(oneItem.get("itemTypeFull"), ""); + int cardId = convertToInt(oneItem.get("id"), 0); + + if (ifEnable != 1) { + continue; + } + // 处理特定类型的数据 + if ("0103".equals(itemTypeCode)) { + oneItemHS.put("sourceData", GetDeskTopCommonUse(cardId)); + } + // 处理链接数据 + else if (ifLinked == 1 && !itemCode.isEmpty() && !sourceString.isEmpty()) { + String dataStr = PublicUtil.ReqSqlPmsByRow(oneItem, null, sourceString); + try { + // 处理SQL语句 + if (sourceString.startsWith("@")) { + String sql = dataStr.substring(1); + List> data = jdbcTemplate.queryForList(sql); + oneItemHS.put("sourceData", ConversionUtils.toLowerColumnName(data)); + } + // 处理存储过程 + else if (sourceString.startsWith("!")) { + String procCall = "exec " + dataStr.replace("!", "").replace("(", " ").replace(")", ""); + List> data = jdbcTemplate.queryForList(procCall); + oneItemHS.put("sourceData", data); + } + // 处理模块编号 + else if (sourceString.length() < 20) { + BaseResponse moduleResponse = GetModuleData(sourceString, null, null, null, null, false, false, 0, 0); + if (moduleResponse.isSuccess()) { + oneItemHS.put("sourceData", moduleResponse.getData()); + } + } + // 直接使用源字符串 + else { + oneItemHS.put("sourceData", sourceString); + } + } catch (Exception ex) { + oneItemHS.put("sourceData", "数据执行过程失败:" + ex.getMessage()); + } + + // 处理查询过滤 + if (queryVal != null && !queryVal.isEmpty()) { + Object sourceData = oneItemHS.get("sourceData"); + + + if (sourceData instanceof List) { + @SuppressWarnings("unchecked") + List> theDT = (List>) sourceData; + + if (!theDT.isEmpty()) { + String cond = Objects.toString(oneItem.get("condition"), "").trim().toLowerCase(); + String queryField = Objects.toString(oneItem.get("queryField"), "").trim(); + + // 优先处理条件过滤 + if (!cond.isEmpty()) { + // 处理WHERE/AND/OR前缀 + if (cond.startsWith("where")) { + cond = cond.substring(5).trim(); + } else if (cond.startsWith("and")) { + cond = cond.substring(3).trim(); + } else if (cond.startsWith("or")) { + cond = cond.substring(2).trim(); + } + + // 替换参数 + Hashtable params = new Hashtable<>(); + params.put("value", queryVal); + cond = PublicUtil.ReqSqlPms(params, null, cond, SystemTypeEnums.PmType.sql, getUser()); + + try { + // 执行过滤 + List> queryResult = DataFilterUtil.select(theDT, cond); + oneItemHS.put("sourceData", queryResult); + } catch (Exception er) { + msg.append("; ").append(er.getMessage()); + } + } + // 处理字段查询 + else if (!queryField.isEmpty()) { + if (hasColumn(theDT, queryField)) { + List> queryResult = theDT.stream() + .filter(row -> { + String value = Objects.toString(row.get(queryField), ""); + return value.contains(queryVal); + }) + .collect(Collectors.toList()); + + oneItemHS.put("sourceData", queryResult); + } + } + } + } + } + } + + // 移除不需要的字段 + oneItemHS.remove("itemdatasource"); + result.add(oneItemHS); + } + // 按itemOrder排序 + result.sort((item1, item2) -> { + int order1 = convertToInt(item1.get("itemorder"), 0); + int order2 = convertToInt(item2.get("itemorder"), 0); + return Integer.compare(order1, order2); + }); + + return result; + } + + // 辅助方法:将值转换为整数 + private int convertToInt(Object value, int defaultValue) { + if (value == null) { + return defaultValue; + } + if (value instanceof Number) { + return ((Number) value).intValue(); + } + try { + return Integer.parseInt(value.toString().trim()); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + // 辅助方法:检查列表中是否包含指定列 + private boolean hasColumn(List> dataList, String column) { + if (dataList.isEmpty()) { + return false; + } + return dataList.get(0).containsKey(column); + } + + /** + * 获取桌面快捷模块个人快捷功能 + * + * @param cardId 卡片ID + * @return 快捷功能列表 + */ + public List GetDeskTopCommonUse(int cardId) { + // 获取数据列表,对应C#的DataTable +// List> dtVal = DataImpl.GetDeskTopCommonUse(cardId, getUser().UserId); + List> dtVal = DataImpl.GetDeskTopCommonUse(cardId, getUser().UserId); + // 处理并转换数据 + return dtVal.stream() + // 过滤条件:cardId=-99 或 权限校验通过(复刻原where逻辑) + .filter(row -> { + // 处理menuid:转为字符串,避免null(对应C# row["menuid"] + "") + String menuId = toStringSafe(row.get("menuid")); + // 权限校验:PublicUtil.CheckPurview(参数需与C#一致) + String purviewResult = PublicUtil.CheckPurview(getUser(), getUser().PurviewStr, menuId); + // 逻辑:cardId=-99 跳过权限校验;否则需权限结果非空 + return cardId == -99 || !isNullOrEmpty(purviewResult); + }) + // 转换:用Map.of()封装字段(对应C# select new 匿名对象) + .map(row -> { + // ------------------------------ + // 1. 处理所有字段值(先解决null问题,避免Map.of()抛空指针) + // ------------------------------ + // dllfilename:对应C# row["dllfilename"](转为字符串,null→空串) + String dllfilename = toStringSafe(row.get("dllfilename")); + // href:复刻原逻辑(#→#,含http→原href,否则加http://) + String hrefRaw = toStringSafe(row.get("href")); + String href = "#".equals(hrefRaw) + ? "#" + : hrefRaw.contains("http") + ? hrefRaw + : "http://" + hrefRaw; + // isa:对应C# (row["href"] + "") != "#" + boolean isa = !"#".equals(hrefRaw); + // text:对应C# row["text"](null→空串) + String text = toStringSafe(row.get("text")); + // UrlParams:对应C# row["dllcoid"](null→空串) + String urlParams = toStringSafe(row.get("dllcoid")); + // dllType:对应C# row["dlltype"](null→空串,后续转int用) + String dllTypeStr = toStringSafe(row.get("dlltype")); + // MenuId:对应C# row["menuid"](null→空串) + String menuId = toStringSafe(row.get("menuid")); + // serverId:对应C# row.Get("serverId")(null→空串,避免Map.of()报错) + String serverId = toStringSafe(row.get("serverId")); + // ShowCount:对应C# row["needcount"].ToBoolean()(0→false,1→true,null→false) + boolean showCount = toBoolean(row.get("needcount")); + + // ------------------------------ + // 2. 处理out参数isUrl(Java无out,用数组模拟) + // ------------------------------ + boolean[] isUrlHolder = new boolean[1]; // 数组长度1,存储isUrl结果 + // 调用SystemMenu.ConvertToModuleName(复刻原逻辑,dllType转int) + String pluginName = SystemMenu.convertToModuleName( + dllfilename, // 对应C# row["dllfilename"] + "" + ToInt32(dllTypeStr), // 对应C# row["dlltype"].ToInt32() + isUrlHolder // 模拟out参数,结果存在isUrlHolder[0] + ); + + // ------------------------------ + // 3. 用Map.of()封装键值对(注意:键/值均不能为null) + // ------------------------------ + return Map.of( + "dllfilename", dllfilename, + "isa", isa, + "href", href, + "text", text, + "UrlParams", urlParams, + "dllType", dllTypeStr, // 若需int类型,可改为toInt32Safe(dllTypeStr) + "MenuId", menuId, + "serverId", serverId, + "ShowCount", showCount, + "pluginName", pluginName + ); + }) + // 收集为List(对应C# .ToList()) + .collect(Collectors.toList()); + } + + /** + * 安全转为字符串:null→空串,非null→调用toString() + * 对应C#的 row["xxx"] + "" + **/ + private String toStringSafe(Object value) { + return value == null ? "" : value.toString(); + } + + /** + * 计算href属性值 + */ + private String calculateHref(Map row) { + String href = String.valueOf(row.get("href")); + if ("#".equals(href)) { + return "#"; + } + if (href.contains("http")) { + return href; + } + return "http://" + href; + } + + /** + * 计算插件名称 + */ + private String calculatePluginName(Map row) { + String dllfilename = String.valueOf(row.get("dllfilename")); + int dlltype = Integer.parseInt(String.valueOf(row.get("dlltype"))); + boolean[] isUrl = new boolean[1]; // 使用数组存储引用类型的结果 + return SystemMenu.convertToModuleName(dllfilename, dlltype, isUrl); + } + + /** + * 获取添加或更新字段 + * + * @param moduleId 模块标识符 + * @param idValue id值 + * @param leftRecord 左侧记录(JSON字符串,默认空字符串) + * @return BaseResponse + */ + public BaseResponse GetBillMasterFields(String moduleId, String idValue, String leftRecord) { + // 处理默认参数(leftRecord默认空字符串) + if (leftRecord == null) { + leftRecord = ""; + } + + BaseResponse response = new BaseResponse(); + + // 获取账单模块信息(假设BillModule对应实体类,getBillModule方法已实现) + BillModule module = GetBillModule(moduleId, getMenuId()); + if (module == null) { + return response; + } + + // 设置模块属性 + module.IdValue = (idValue); + module.SLeftRecord = (leftRecord); // 假设实体类中有setSLeftRecord方法 + + // 获取主表字段数据并设置响应 + response.setData(GetBillMasterFields(module, "")); // 假设存在重载方法处理BillModuleEntity参数 + response.setOther(module); // 存储模块信息到响应的other字段 + response.setSuccess(true); + + return response; + } + + /** + * 获取单据主表字段 + * + * @param module 单据模块实体 + * @param rowSql 行数据SQL(可选,默认空字符串) + * @return 字段列表 + */ + public List GetBillMasterFields(BillModule module, String rowSql) { + // 处理默认参数 + if (rowSql == null) { + rowSql = ""; + } + + if (module == null) { + return null; + } + + // 设置更新行数据 + SetUpdateRow(module, rowSql, module.IdValue); + // 获取主表列配置数据(从DataImpl中获取) + List> dtVal = DataImpl.GetBillMasterRows( + module.getModuleId(), + getUser().UserName, + 0 + ); + + // 创建控件字段列表 + List cols = createControl.createControl(dtVal, module); + // 处理字段属性(只读状态、默认值等) + return cols.stream().map(field -> { + // 处理主键字段的只读属性和默认值 + if (!field.getName().equalsIgnoreCase(module.getIdField())) return field; + field.setReadOnly(true); + // 如果没有ID值,生成新单据编号 + if (!isNullOrEmpty(module.IdValue)) return field; + String newBillNo = GetNewBillNo(module.getBillSeq()); + module.IdValue = (newBillNo); + field.setDefaultval(newBillNo); + return field; + }).collect(Collectors.toList()); + } + + /** + * 获取新单据编号 + * + * @param moduleId 模块ID + * @return 新单据编号 + */ + public String GetNewBillNo(String moduleId) { + String billCode = ""; + String sql = "{call P_create_billdocumentPr(?, ?)}"; // 两个参数:moduleId、userId(输入+输出) + + try { + // 获取当前用户ID(从用户上下文获取,需确保UserContext正确实现) + String userId = getUser().UserId; + // 使用JdbcTemplate的execute方法执行存储过程,通过回调获取输出参数 + jdbcTemplate.execute(sql, (CallableStatementCallback) callableStatement -> { + // 设置输入参数 + callableStatement.setString(1, moduleId); // 第一个参数:moduleId + callableStatement.setString(2, userId); // 第二个参数:userId(输入值) + // 注册输出参数(第二个参数同时作为输出) + callableStatement.registerOutParameter(2, Types.VARCHAR); + // 执行存储过程 + callableStatement.execute(); + // 获取输出参数值(第二个参数) + return callableStatement.getString(2); + }); + } catch (Exception e) { + log.error("Exception caught", e); + } + return billCode; + } + + /** + * 检查单据编号是否已存在 + * + * @param module 单据模块实体 + * @param billno 单据编号 + * @return 存在返回true,否则返回false + */ + public boolean ExistBillNo(BillModule module, String billno) { + try { + String sql = String.format( + "select count(1) from %s where %s = %s", + module.getMasterTable(), + module.getIdField(), + billno + ); + + // 执行查询并获取结果 + Object result = jdbcTemplate.queryForObject(sql, Object.class); + + // 转换结果为整数并判断是否大于0 // 构建查询SQL,使用参数占位符 + return ToInt32(result) > 0; + } catch (Exception e) { + sysLog("单据编号为空,直接创建新单据编号", "ExistBillNo"); + return false; + } + } + + /** + * 获取单据新增或更新信息 + * + * @param idVal 主键值 + * @param moduleId 模块ID + * @param menuid 菜单ID + * @param contextMenuId 上下文菜单ID(可选,默认空字符串) + * @param leftRecord 左侧记录数据(可选,默认空字符串) + * @return 单据模块信息对象 + */ + public Object GetBillAddOrUpdInfo(String idVal, String moduleId, String menuid, String contextMenuId, String leftRecord) { + // 处理默认参数 + if (contextMenuId == null) { + contextMenuId = ""; + } + if (leftRecord == null) { + leftRecord = ""; + } + + // 获取单据模块信息 + BillModule module = GetBillModule(moduleId, menuid); + if (module == null) { + return null; + } + + // 设置上下文菜单ID + module.ContextMenuId = (contextMenuId); + + // 解析左侧记录数据(JSON格式转换为Hashtable) + if (!isNullOrEmpty(leftRecord) && leftRecord.startsWith("{")) { + try { + Map leftRecordMap = (Map) JSON.Decode(leftRecord); + leftRecordMap = toLowerColumnName(leftRecordMap); + module.setLeftRecord(leftRecordMap); + } catch (Exception e) { + // 解析失败时可记录日志或做其他处理 + log.error("解析leftRecord失败: {}", leftRecord, e); + } + } + + // 处理上下文菜单按钮信息 + if (!isNullOrEmpty(module.ContextMenuId)) { + int menuBtnId = NativeExtensionUtils.parseInt(module.ContextMenuId); + SysPoPupMenuBtn popBtn = GetContextMenuBtn(menuBtnId, module.getLeftRecord()); + module.PopBtn = (popBtn); + if (popBtn != null) { + // 设置单据来源ID(对应原C#的Pm7属性) + module.setBillSourceIds(popBtn.getPm7()); + } + } + module.SourceTab = DataImpl.GetBillSourceItem(moduleId, module.getBillSourceIds(), "0,1");//module.BillFlag == 2 ? "0,1" : module.BillFlag + "" + // 获取单据来源项 +// String billFlagStr = (module.getBillFlag() == 0) ? "0,1" : String.valueOf(module.getBillFlag()); +// List> sourceTab = DataImpl.GetBillSourceItem(moduleId, module.getBillSourceIds(), billFlagStr); +// module.SourceTab = (sourceTab); + // 判断是否有来源数据 + module.HasSource = (module.SourceTab != null && !module.SourceTab.isEmpty()); + + // 设置是否可申请(根据审核步骤数量判断) + module.setApplyAble(DataImpl.GetAuditStepCount(moduleId, false) > 0); + // 设置主键值并获取主表字段 + module.IdValue = (idVal); + module.Main = (GetBillMasterFields(module, "")); + // 获取单据明细表格 + if (!isNullOrEmpty(module.getPopupUnionCode())) { + module.PopupFields = DataImpl.GetBillPopUpFields(module.getPopupUnionCode()); + } + GridPanel detail = GetBillDetail(module, idVal); + module.Detail = (detail); + + // 处理明细表格的数据源参数(当存在主键值时) + if (!isNullOrEmpty(idVal) && module.Detail != null) { + DataStore store = detail.getStore(); + // 构建额外参数 + Map extraParams = new HashMap<>(); + extraParams.put("moduleId", moduleId); + extraParams.put("idValue", idVal); + + if (store != null) { + store.extraParams = (extraParams); + } else { + // 若数据源不存在则创建新的并设置参数 + DataStore newStore = new DataStore(); + newStore.extraParams = (extraParams); + detail.setStore(newStore); + } + } + + return module; + } + + /** + * 获取单据明细表格 + * + * @param module 单据模块实体 + * @param selfEdit 是否允许自行编辑(默认true) + * @param editFields 可编辑字段(逗号分隔) + * @param requireFields 必输字段(逗号分隔) + * @return 明细表格组件 + */ + public GridPanel GetBillDetail(BillModule module, String idVal, boolean selfEdit, String editFields, String requireFields) { + // 处理默认参数 + if (editFields == null) { + editFields = ""; + } + if (requireFields == null) { + requireFields = ""; + } + + // 获取当前登录用户信息 + String userId = getUser().UserId; + String userName = getUser().UserName; + + // 查询明细列配置数据 + List> detailColsTab = DataImpl.GetBillDetailColumns( + module.getModuleId(), + userId, + userName, + 0 + ); + // 处理可编辑字段和必输字段 + if (!isNullOrEmpty(editFields) && detailColsTab.stream().anyMatch(row -> row.containsKey("Edit") || row.containsKey("edit"))) { + // 复制列配置(创建新列表避免修改原数据) + List> copiedCols = new ArrayList<>(); + for (Map row : detailColsTab) { + copiedCols.add(new HashMap<>(row)); + } + detailColsTab = copiedCols; + + // 统一转为小写便于匹配 + String lowerEditFields = "," + editFields.toLowerCase() + ","; + String lowerRequireFields = "," + requireFields.toLowerCase() + ","; + + for (Map row : detailColsTab) { + String fieldName = (String) row.getOrDefault("FieldName", ""); + String lowerFieldName = fieldName.toLowerCase(); + + // 默认设置为禁编(1表示禁编) + row.put("Edit", 1); + // 如果字段在可编辑列表中,设置为可编辑(0表示可编辑) + if (lowerEditFields.contains("," + lowerFieldName + ",")) { + row.put("Edit", 0); + } + + // 设置必输字段(1表示不能为空) + if (lowerRequireFields.contains("," + lowerFieldName + ",")) { + row.put("nullable", 1); + } + } + } + + // 转换列配置为组件列表 + List columns = ToColumns(detailColsTab, selfEdit, module); + + // 获取右键菜单 + List bbItems = null; + String menuKey = PublicUtil.GetBillConMenuKey(SystemEnums.BillMenuEnum.BillDetail) + module.getModuleId(); + Object rightMenu = GetRightMenu(menuKey, 0, new Ref(bbItems)); + + // 创建表格面板 + GridPanel grid = new GridPanel(); + grid.setColumns(columns); + grid.RightMenu = (rightMenu); + + // 处理明细SQL和数据 + String detailSql = ""; + if (!isNullOrEmpty(module.IdValue) && !isNullOrEmpty(idVal)) { + // 处理带主键的查询SQL + String paramJson = String.format("{\"%s\":\"%s\"}", module.getIdField(), module.IdValue); + detailSql = dealQuerySql( + module.getDetailSql(), + paramJson, + null, + null, + "", + "", false, false, false + ); + } else if (module.PopBtn != null) { + // 处理弹出按钮关联的SQL + detailSql = module.PopBtn.dllpar9; + String newSql = detailSql; + if (!isNullOrEmpty(detailSql)) { + List> data = jdbcTemplate.queryForList(newSql); + grid.data = DataTableUtil.toLowerColumnName(data); + } + } + return grid; + } + + public GridPanel GetBillDetail(BillModule module, String idVal) { + return GetBillDetail(module, idVal, true, "", ""); + } + + public BaseResponse GetBSDesktopModuleData(String dllcoid, String queryVal) { + BaseResponse response = new BaseResponse(); + + //【=0=】检查是否有关于默认项的完整 模板配置,无的话就加入和生产 完整模板配置。 + DataImpl.CheckAndFillDesktopModuleOper(dllcoid, getUser().UserId); + + //【=1=】自身dllcoid的 item cfg,应该是唯一,不用考虑复杂了 + List> itemsDT = DataImpl.GetBSDesktopModuleItemList(dllcoid, getUser().UserId); + + //【=2=】item的 data 数据解析 + StringBuilder errMsg = new StringBuilder(); + List> result = AnalyseBSDesktopData(itemsDT, errMsg, queryVal); + + response.setSuccess(true); + response.setMsg(errMsg.toString()); + response.setData(result); + + return response; + } + + public BaseResponse GetBSDesktopAdminBase() { + BaseResponse response = new BaseResponse(); + List> itemsDT = DataImpl.GetBSDesktopAdminBase(); + response.setData(DataTableUtil.toLowerColumnName(itemsDT)); + response.setTot(itemsDT.size()); + response.setSuccess(true); + return response; + } + + /** + * 获取桌面扩展配置 + */ + public BaseResponse GetBSDesktopExtend() { + BaseResponse response = new BaseResponse(); + // 调用DataImpl获取数据(假设返回List模拟DataTable) + List> items = DataImpl.GetBSDesktopExtend( + getUser().UserId, + getUser().UserName + ); + + List> resultList = new ArrayList<>(); + for (Map item : items) { + // 复制原数据并添加operatorid字段 + Map itemMap = new HashMap<>(item); + itemMap.put("operatorid", getUser().UserId); + resultList.add(itemMap); + } + + response.setData(DataTableUtil.toLowerColumnName(resultList)); + response.setTot(resultList.size()); + response.setSuccess(true); + return response; + } + + /** + * 获取桌面模块操作配置 + */ + public BaseResponse GetDesktopModuleOper(String dllcoid) { + BaseResponse response = new BaseResponse(); + // 调用DataImpl获取数据(假设返回List模拟DataTable) + List> items = DataImpl.GetDesktopModuleOper( + getUser().UserId, + dllcoid + ); + + List> resultList = new ArrayList<>(); + for (Map item : items) { + // 复制原数据并添加operatorid字段 + Map itemMap = new HashMap<>(item); + itemMap.put("operatorid", getUser().UserId); + resultList.add(itemMap); + } + + response.setData(resultList); + response.setTot(resultList.size()); + response.setSuccess(true); + return response; + } + + public String GetPrimaryKeys(String tabname) { + return DataImpl.GetPrimaryKeys(tabname); + } + + public List GetPrimaryKeysArray(String tabname) { + return DataImpl.GetPrimaryKeysArray(tabname); + } + + /** + * 获得近似的结果,前50条即可 + * + * @param queryText 查询文本 + * @return 基础响应对象(包含查询结果、成功状态和总数) + */ + public BaseResponse GetDeskQueryResult(String queryText) { + BaseResponse response = new BaseResponse(); + // 获取原始查询结果(使用List模拟DataTable) + List> rawResultList = DataImpl.GetDeskQueryResult(queryText); + response.setSuccess(true); + + if (rawResultList != null && !rawResultList.isEmpty()) { + // 过滤有权限的记录(对应C#的LINQ查询) + List> filteredList = new ArrayList<>(); + String purviewStr = getUser().PurviewStr; + + for (Map row : rawResultList) { + // 检查权限,非空则保留当前行 + String id = row.get("id") != null ? row.get("id").toString() : ""; + if (createControl.CheckPurview(purviewStr, id) != null && !createControl.CheckPurview(purviewStr, id).isEmpty()) { + filteredList.add(row); + } + } + + // 设置响应数据和总数 + response.setData(filteredList.isEmpty() ? null : filteredList); + response.setTot(filteredList.size()); + } else { + response.setData(null); + response.setTot(0); + } + + return response; + } + + public BaseResponse GetClientSaveConds(String moduleId) { + BaseResponse response = new BaseResponse(); + response.setSuccess(true); + if (isNullOrEmpty(moduleId)) { + response.setData(new ArrayList()); + return response; + } + + String tableExistsSql = """ + SELECT COUNT(1) + FROM ALL_TABLES + WHERE OWNER = 'LSERP_JTCS' + AND TABLE_NAME = 'P_SYSTEMCLIENTCONDTAB' + """; + + Integer count = jdbcTemplate.queryForObject(tableExistsSql, Integer.class); + + if (count == null || count == 0) { + String emptySql = """ + SELECT + CAST(NULL AS INT) id, + CAST(NULL AS VARCHAR(50)) tab, + CAST(NULL AS VARCHAR(200)) "condition", + CAST(NULL AS VARCHAR(100)) hintmsg, + CAST(NULL AS INT) orderid + FROM dual + WHERE 1 = 0 + """; + response.setData(jdbcTemplate.queryForList(emptySql)); + return response; + } + + String sql = """ + SELECT + id, + tab, + "condition", + hintmsg, + orderid + FROM P_SystemClientCondTab + WHERE tab = ? + AND NVL(disableflag, 0) = 0 + ORDER BY NVL(orderid, 0), id + """; + + response.setData(jdbcTemplate.queryForList(sql, moduleId)); + return response; + } + + protected static class ColInfos { + public String colname; + public Map colrow; + public Field field; + public int length; + public String colCnName; + public Boolean cfgNullAble; + public Boolean isnullable; + public Type dataType; + public int dbType; + } + + + @Override + public BaseModule GetBaseModule(String moduleCode, String menuId) { + BaseModule module = null; + List> dtvalue = DataImpl.GetBaseModule(moduleCode, menuId); + + if (dtvalue != null && !dtvalue.isEmpty()) { + module = new BaseModule(dtvalue.get(0)); + + String formkey = ""; + + ModuleIdFieldDTO result = DataImpl.GetModuleIdField(formkey, module.getModuleId(), module.getMasterTable()); + module.setIdField(isNullOrEmpty(module.getIdField()) ? result.getParmaryKey() : module.getIdField()); + Boolean isSpec = result.getSpecModule(); + module.IsSpecModule = isSpec; + module.LeftUnionField = result.getLeftUnionField(); + module.LeftUnionFieldSql = result.getSpecSql(); + module.setApplyAble(getDetailJDBC().GetAuditStepCount(moduleCode, true) > 0); + + if (!module.OperAble) { + module.OperAble = Objects.equals(createControl.CheckPurview(getUser().PurviewStr, ToInt32(menuId) + ""), "AllPurview"); + } + } else { + log.debug(String.valueOf("无效参数,返回值为null")); + return null; + } + return module; + } + + // 2026.2.5 新增Mrp + @Override + public BaseModule getModuleIniParams(String moduleId, String menuId, String targetModuleId, String detailId, boolean isCard, boolean isChart, String mFields, boolean atts, Boolean loadDetail, Boolean loadLeft, boolean isAttc) { + return getModuleIniParams(moduleId, menuId, targetModuleId, detailId, isCard, isChart, mFields, atts, loadDetail, loadLeft, isAttc, true); + } + + // 重新拿到处理好的resultList + @Override + public BaseModule getModuleIniParams(String moduleId, String menuId, String targetModuleId, String detailId, boolean isCard, boolean isChart, String mFields, boolean atts, Boolean loadDetail, Boolean loadLeft, boolean isAttc, boolean isMrp) { + + BaseModule module = GetBaseModule(moduleId, menuId); +// out.println("getModuleIniParams " + module); + if (module != null) { + module.IsCard = isCard; + module.IsChart = isChart; + module.MainModifyFields = mFields; + module.detailId = detailId; + + if (!isNullOrEmpty(detailId)) { + module.DetailModule = GetBaseDetailModuel(ToInt32(detailId), isAttc); + if (module.DetailModule != null && isNullOrEmpty(module.getUnionParentField())) { +// this.info("存在明细Moduleid=" + moduleId); + module.ParentModule = GetBaseModule(module.DetailModule.getUnionKey(), menuId); + if (module.ParentModule != null) { + module.DetailModule.setUnionParentField(module.ParentModule.getIdField()); + } + } + if (module.DetailModule != null) { + module.setNoGridLine(module.DetailModule.getNoGridLine()); + module.setNoRownumber(module.DetailModule.getNoRownumber()); + module.setHideColumnHeader(module.DetailModule.getHideColumnHeader()); + } + } + if (menuId == "2005_1") { + ModuleBaseEntity targModule = GetBaseModule(targetModuleId, menuId) != null + ? GetBaseModule(targetModuleId, menuId) + : GetBillModule(targetModuleId, menuId); + if (targModule != null) { + module.setFileSpeciesNo(targModule.getFileSpeciesNo()); + module.setDirId(targModule.getDirId()); + module.setAttatchModifyCond(targModule.getAttatchModifyCond()); + module.setAttcLeafOnly(targModule.getAttcLeafOnly()); + module.setAccInfow(targModule.getAccInfow()); + module.setFType(targModule.getFType()); + int viewType = DataImpl.GetAttcViewModule(targetModuleId, getUser().UserId); + if (viewType > 0) { + module.AttcViewType = viewType; + } + } + } + if (atts) { + module.AttCfgs = GetAttachModuleCfgs(moduleId); + } +// out.println("SetMain start"); + setMain(module); +// out.println("SetMain end"); + if (loadLeft) { + SetLeft(module); +// out.println("SetLeft end"); + } + if (loadDetail)//string.IsNullOrEmpty(detailId)|| + { + module.Details = GetModelDetails(module); +// out.println("GetModelDetails end"); + } + if (moduleId != "2005_1") { +// 暂未补充,日志功能 + sysLog(String.format("%s进入%s模块", getUser().UserName, module.Title), "进入模块"); + } + } + log.debug(String.valueOf("mro : " + isMrp)); + if (isMrp) { + module.mrpType = DataImpl.GetMrpType(); + } + return module; + } + + // 注:因为不能重名,所以将C#的CheckAttcAuthory -> CheckAttcAuthoryInt + public int CheckAttcAuthory(String moduleId, String idValue) { + ModuleBaseEntity module = GetModule(moduleId); + return CheckAttcAuthoryInt(module, idValue); + } + + public ModuleBaseEntity GetModule(String moduleCode) { + ModuleBaseEntity module = null; + if (DataImpl.IsBaseModule(moduleCode) && (module = GetBaseModule(moduleCode, getMenuId())) != null) { + return module; + } else { + return GetBillModule(moduleCode, getMenuId()); + } + } + + // public boolean CheckAttcAuthory(ModuleBaseEntity module, String idValue) { +// if (module == null || isNullOrEmpty(module.getAttatchModifyCond())) return true; +// module.IdValue = idValue; +// SetUpdateRow(module, "", module.IdValue); +// if (isNullOrEmpty(module.getAttatchModifyCond())) return true; + + /// / IPublicUtil util = new IPublicUtil(); +// return createControl.evalCond(module.getAttatchModifyCond(), module.Updrow, null); +// } + public int CheckAttcAuthoryInt(ModuleBaseEntity module, String idValue) { + if (module == null || isNullOrEmpty(module.getAttatchModifyCond())) { + return 1; + } + module.IdValue = idValue; + SetUpdateRow(module, "", module.IdValue); + if (isNullOrEmpty(module.getAttatchModifyCond())) { + return 1; + } + Object ok = getUtil().evalCond(module.getAttatchModifyCond(), module.Updrow, null); + return ToInt32(ok.toString()) == 2 ? 2 : toBoolean(ok.toString()) ? 1 : 0; + } + + private Object GetAttachModuleCfgs(String moduleId) { + return DataImpl.GetAttcModules(moduleId).stream() + .map(row -> { + // 直接获取并转换字段值,使用三目运算符处理空值 + String unionModule = row.getOrDefault("unionmodule", "").toString(); + String title = row.getOrDefault("attachname", "").toString(); + String id = row.getOrDefault("id", "").toString(); + String lib = row.getOrDefault("library", "").toString(); + String pms = row.getOrDefault("params", "").toString(); + + if (!unionModule.isEmpty()) { + Map moduleMap = new HashMap<>(); + moduleMap.put("moduleId", unionModule); + moduleMap.put("attcId", id); + moduleMap.put("title", title); + moduleMap.put("lib", lib); + moduleMap.put("Pms", pms); + return moduleMap; + } + return null; + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + /** + * 设置左侧面板(树或表格) + * + * @param processedresultnew 基础模块实体 + */ + private void SetLeft(BaseModule processedresultnew) { + List> dtval = DataImpl.GetBaesModuleLeft(processedresultnew.getModuleId()); + if (dtval != null && !dtval.isEmpty()) { + Map firstRow = dtval.get(0); // 获取第一行数据 +// out.println("firstRow:" + firstRow); + + String fieldLabel = String.valueOf(firstRow.get("fieldcaption")); + String fieldname = String.valueOf(firstRow.get("fieldname")).toLowerCase(); + String fromkey = String.valueOf(firstRow.getOrDefault("fromkey", "")); + String displaymember = String.valueOf(firstRow.getOrDefault("displaymember", "")).toLowerCase(); + String valuemember = String.valueOf(firstRow.getOrDefault("valuemember", "")).toLowerCase(); + String detailid = String.valueOf(firstRow.getOrDefault("detailid", "")); + + processedresultnew.LeftUnionField = fieldname; + //左边是颗树 + if (processedresultnew.getMenuType() == 1 || processedresultnew.getMenuType() == 3) { + TreePanel treepanel = new TreePanel(); + treepanel.title = fieldLabel; + treepanel.displayField = displaymember; + treepanel.valueField = valuemember; + treepanel.parentValueField = "pid"; + treepanel.width = processedresultnew.getLeftWidth() != null ? processedresultnew.getLeftWidth() : 200; + + DataStore store = new DataStore(); + Map extraParams = new HashMap<>(); + extraParams.put("id", detailid); + store.extraParams = (extraParams); + treepanel.store = store; + processedresultnew.Left = treepanel; + + } else if (processedresultnew.getMenuType() == 2) { + //左边是grid + Ref bbItems = new Ref(null); + GridPanel gridpanel = new GridPanel(); + gridpanel.title = fieldLabel; + gridpanel.width = processedresultnew.getLeftWidth() != null ? processedresultnew.getLeftWidth() : 200; +// gridpanel.IdField = displaymember ; // 这里应该是valuemember + gridpanel.IdField = valuemember; + gridpanel.displayField = displaymember; +// gridpanel.PageAble = processedresultnew.getPageAble(); + gridpanel.TbarItems = GetCondition(fromkey + "_Cond"); + gridpanel.RightMenu = GetRightMenu(fromkey, 0, bbItems); + gridpanel.setColumns(GetFieldColumns(processedresultnew.getModuleId(), detailid)); + + DataStore store = new DataStore(); + Map extraParams = new HashMap<>(); + extraParams.put("id", detailid); + store.extraParams = (extraParams); + gridpanel.setStore(store); + + processedresultnew.Left = gridpanel; + } + } + } + + public void setMain(BaseModule module) { + + Integer sheight = 0; + Object MobileCards = null; + String ModuleId = module.getModuleId(); + boolean SelfEdit = module.isSelfEdit(); + List Columns = GetColumns(ModuleId, SelfEdit); +// out.println("Columns " + Columns); + List CardColumns = null; + boolean IsWindowsDirver = toBoolean(Request("windowsDirver", "")); +// if (module.IsCard || (module.getCellTplflag() != null && module.getCellTplflag())) { +// 2026.2.5修改 + if (module.IsCard && !IsWindowsDirver && !module.getDisMobileTpl() || isWindowsDirver()) { +//2026.2.24 修改 +// if (!IsWindowsDirver && module.getDisMobileTpl() || (module.getCellTplflag() != null && module.getCellTplflag())) { +// // 以下是手机端模块,2026.2.5 +// MobileCards = GetModuleCardGroup(module.getModuleId(), true, true, 0, (module.getCellTplflag() != null && module.getCellTplflag())); +// } else { +// List> dtVal = GetCardColumnRows(module.getModuleId(), getUser().UserId, getUser().UserName); +//// IPublicUtil util = new IPublicUtil(); +// if (dtVal != null) { +// CardColumns = dtVal.stream() +// .map(rowMap -> new RowColumn(rowMap, false, createControl)) +// .collect(Collectors.toList()); +// } +// } + MobileCards = GetModuleCardGroup(module.getModuleId(), true, true, 0, isWindowsDirver() && module.getWebCardTpl() != true); + if (!isWindowsDirver() && (MobileCards == null || ((IList) MobileCards).size() == 0)) { + MobileCards = ConvertOldMobileCard(DataImpl.getMobileColumns(module.getModuleId())); + } + List> dtVal = DataImpl.GetCardColumnRows(module.getModuleId(), getUser().getUserId(), getUser().getUserName()); + CardColumns = dtVal.stream() + .map(rowMap -> new RowColumn(rowMap, false, getUtil())) + .collect(Collectors.toList()); + + } +// out.println("MainModifyFields+"+module.MainModifyFields + "DetailModule:"+module.DetailModule); + if (module.MainModifyFields != null + && !module.MainModifyFields.trim().isEmpty() + && module.DetailModule != null) { +// this.Info("存在明细Moduleid=" + module.ModuleId.ToString() + "deailid:" + module.detailId.ToString() + "modify:" + module.MainModifyFields.ToString()); + for (Component cmp : Columns) { + // 判断组件是否为RowColumn类型 + if (cmp instanceof RowColumn) { + // 强制转换为RowColumn + RowColumn rowColumn = (RowColumn) cmp; + // 先设置editable为false + rowColumn.setEditable(false); + + // 获取dataIndex字段值 + String name = rowColumn.getDataIndex(); + + // 拼接字符串并判断是否包含指定字段(与C#逻辑一致) + String mainModifyFields = module.MainModifyFields != null ? module.MainModifyFields.toLowerCase() : ""; + if ((",".concat(mainModifyFields).concat(",")).indexOf("," + name + ",") > -1) { + // 包含则设置editable为true + rowColumn.setEditable(true); + } + } + } + } + + List bbItems = new ArrayList<>(); + Object rightm = GetRightMenu(module.getModuleId(), 0, new Ref(bbItems), "toolClick"); + List btns = GetMainGridBtns(module); + List allbtns = new ArrayList<>(); + allbtns.addAll(btns); + allbtns.addAll(bbItems); +// out.println("allbtns " + allbtns); + // 对应C#的GridPanel初始化 +// 初始化赋值2026.2.24 + List finalCardColumns = CardColumns; + Object finalMobileCards = MobileCards; + + GridPanel maingrid = new GridPanel() {{ + PageAble = module.getPageAble(); + TbarItems = GetCondition(module.getCondKey()); + SHeight = sheight; + RightMenu = rightm; + BbarItems = allbtns; + CardColumns = finalCardColumns; + MobileCards = finalMobileCards; + NoGridLine = module.NoGridLine; + setHideColumnHeader(module.HideColumnHeader); + setRowNumberer(!(module.getNoRownumber() != null ? module.getNoRownumber() : false)); + setStore(new DataStore() {{ + extraParams = new HashMap() {{ + put("moduleId", module.getModuleId()); + put("detailId", module.detailId); + }}; + + ApiSuccVal = module.getApiSuccVal(); + ApiSuccNode = module.getApiSuccNode(); + ApiDataNode = module.getApiDataNode(); + }}); + }}; + +// ObjectMapper mapper = new ObjectMapper(); +// try { +// String json = mapper.writeValueAsString(Columns); // 手动序列化 +// out.println(json); // 若报错,堆栈会显示具体出错的字段 +// } catch (Exception e) { +// e.printStackTrace(); // 查看异常堆栈,定位到具体类和字段 +// } + + maingrid.setColumns(Columns); + if (module.getMasterSql() != null && module.getMasterSql().trim().startsWith("http")) { + maingrid.getStore().url = module.getMasterSql(); + } + if (module.IsChart || maingrid.getColumns() == null || maingrid.getColumns().isEmpty() || module.getMasterTable() == null || module.getMasterTable().isEmpty()) { + List> _chartrows = DataImpl.GetChartCfg(module.getFromkey()); + maingrid.ChartCfg = (_chartrows.stream().map(Chart::new).collect(Collectors.toList())); + if (!_chartrows.isEmpty()) { + module.IsChart = true; + } + } + module.Main = maingrid; + } + + private List GetMainGridBtns(BaseModule module) { + List btnlist = new ArrayList<>(); + Boolean isWindowsDirver = toBoolean(Request("windowsDirver", "")); + // 添加新增按钮 + if (module.isAddAble() && !module.isIsReport()) { + Button addBtn = new Button(); + addBtn.setRecordCond(module.getAddCond()); + addBtn.setText(module.getAddText() != null ? module.getAddText() : "新增"); + addBtn.setHandler("Add"); + addBtn.setCls("toolbar-add"); + addBtn.setIconCls("icon-add"); + btnlist.add(addBtn); + } + // 添加修改按钮 + if (module.isUpdateAble() && !module.isIsReport()) { + Button updateBtn = new Button(); + updateBtn.setRecordCond(module.getUpdateCond()); + updateBtn.setText(module.getModifyText() != null ? module.getModifyText() : "修改"); + updateBtn.setHandler("Update"); + updateBtn.setCls("toolbar-edit"); + updateBtn.setIconCls("icon-edit"); + btnlist.add(updateBtn); + } + + // 添加删除按钮 + if (module.getDeleteAble() && !module.isIsReport()) { + Button deleteBtn = new Button(); + deleteBtn.setRecordCond(module.getDeleteCond()); + deleteBtn.setText(module.getDelText() != null ? module.getDelText() : "删除"); + deleteBtn.setHandler("Delete"); + deleteBtn.setCls("toolbar-remove"); + deleteBtn.setIconCls("icon-remove"); + btnlist.add(deleteBtn); + } + + // 添加保存按钮 + if ((module.isSelfEdit() || module.getImportAble()) && !module.isIsReport()) { + Button saveBtn = new Button(); +// saveBtn.setText("保存"); + saveBtn.setText(module.getSaveText() != null ? module.getSaveText() : "保存"); + saveBtn.setHandler("Save"); + saveBtn.setCls("toolbar-save"); + saveBtn.setIconCls("icon-save"); + btnlist.add(saveBtn); + } + + // 添加提交按钮 + if (module.getApplyAble() && !module.isIsReport()) { + Button applyBtn = new Button(); + applyBtn.setText(module.getApplyText() != null ? module.getApplyText() : "提交"); + applyBtn.setRecordCond("{" + module.getMenuPrefix() + "affirmer}=='0'"); + applyBtn.setHandler("Apply"); + applyBtn.setCls("toolbar-save"); + applyBtn.setIconCls("icon-save"); + btnlist.add(applyBtn); + } + + // 添加导入按钮 + if (module.getImportAble() && isWindowsDirver) { + Button importBtn = new Button(); + importBtn.setText("导入"); + importBtn.setHandler("Import"); + importBtn.setCls("toolbar-undo"); + importBtn.setIconCls("icon-undo"); + btnlist.add(importBtn); + } + + // 添加导出按钮 + if (module.getExportAble() && isWindowsDirver) { + Button exportBtn = new Button(); + exportBtn.setText("导出"); + exportBtn.setHandler("Export"); + exportBtn.setCls("toolbar-redo"); + exportBtn.setIconCls("icon-redo"); + btnlist.add(exportBtn); + } + + if (module.getPrintFile() != null && !module.getPrintFile().trim().isEmpty() && isWindowsDirver) { + // 完全保持变量名与C#一致 + btnlist.add(new Button() {{ + List> menulist = new ArrayList<>(); + setText("打印"); + String[] printFiles = module.getPrintFile().split("\\|"); + for (String pf : printFiles) { + String text = pf.split("\\.")[0]; + Map item = new HashMap<>(); + item.put("fname", pf); + item.put("text", text); + item.put("handler", "Print"); + menulist.add(item); + } + setMenu(menulist); + setIconCls("icon-print"); + }}); + } + + // 在按钮列表首位插入tbfill组件 + if (!btnlist.isEmpty()) { + Button tbfillBtn = new Button(); + tbfillBtn.setXtype("tbfill"); + btnlist.add(0, tbfillBtn); + } + + return btnlist; + } + + private Object GetRightMenu(String fromkey, int menutype, Ref bbarItmes, String menuhandler) { + List menusitems = new ArrayList<>(); + List> rightMenuRows = GetRightMenuRows(fromkey, menutype); + for (Map row : rightMenuRows) { + Map menu = new HashMap<>(); + menu.put("orderid", row.get("orderid")); + menu.put("mid", ((Number) row.get("id")).intValue()); // 对应ToInt32() + menu.put("text", row.get("menucaption") + ""); // 转为字符串 + menu.put("menuCond", ConvertRightMenuSql(Objects.toString(row.get("menucond"), ""))); + menu.put("actiontype", ((Number) row.get("actiontype")).intValue()); + menu.put("handler", menuhandler); + menu.put("beforeMsg", PublicUtil.ReqSqlPms(null, null, Objects.toString(row.get("beforemsg"), ""), SystemTypeEnums.PmType.ignorenull, getUser())); // 注意Java方法名小写开头 + menu.put("linkEvent", toBoolean(row.get("dbclick")) ? "rowdbclick" : null); + menu.put("ifRefresh", ((Number) row.get("refresh")).intValue()); +// menu.put("multi", ((Boolean) row.get("multi")).booleanValue()); + menu.put("multi", toBoolean(row.get("multi"))); +// menu.put("toBar", ((Number) row.get("toBar")).intValue()); + menu.put("toBar", ToInt32(row.get("tobar"))); +// menu.put("merge", ((Boolean) row.get("merge")).booleanValue()); + menu.put("merge", toBoolean(row.get("merge"))); + menu.put("icon", row.get("icon") == null ? "" : row.get("icon")); + menu.put("beforeModule", Objects.toString(row.get("beforetab"), "")); +// menu.put("needRec", !((Boolean) row.get("notRec")).booleanValue()); + menu.put("needRec", !toBoolean(row.get("notrec"))); + Boolean[] hasAddress = new Boolean[1]; + menu.put("mergePms", getRightMenuMergePms(row, hasAddress)); // 调用方法,注意hasAddress的处理 + menu.put("address", hasAddress[0]); + menu.put("disabletype", ((Number) row.get("disabletype")).intValue()); +// menu.put("copy", ((Boolean) row.get("isCopy")).booleanValue()); +// 按钮是否用作copy? + menu.put("copy", toBoolean(row.get("iscopy"))); +// 2026.2.5新增 + menu.put("countAble", !isNullOrEmpty(row.get("countSql"))); + menu.put("isMrpClickBtn", toBoolean(row.get("isMrpClickBtn"))); + + menusitems.add(menu); + if (bbarItmes != null && bbarItmes.getList() != null + && (ToInt32(row.get("tobar"))) == 2) { + // 先获取Ref内部的List,再调用add方法 + bbarItmes.getList().add(menu); + } + } + return menusitems; + } + + public Object GetRightMenu(String fromkey, int menutype, Ref bbarItmes) { + String menuhandler = "rightclick"; + return GetRightMenu(fromkey, menutype, bbarItmes, menuhandler); + } + + private List getRightMenuMergePms(Map menuRow, Boolean[] hasAddress) { + // 用数组传递hasAddress的引用(模拟C#的out参数) + hasAddress[0] = false; + if (menuRow == null) { + return null; + } + + // 解析merge字段(默认"0") + Boolean merge = toBoolean(get(menuRow, "merge", "0")); + + // 拼接参数字符串 + String pmsBuder = String.valueOf(menuRow.get("action")) + + menuRow.get("param1") + + menuRow.get("param2") + + menuRow.get("param3") + + menuRow.get("param4") + + menuRow.get("param5") + + menuRow.get("param6") + + menuRow.get("param7") + + menuRow.get("param8") + + menuRow.get("param9") + + menuRow.get("param10"); + + // 获取参数名称列表 + List paramNames = PublicUtil.getParamValue(pmsBuder.toLowerCase()); + + // 判断是否包含地址相关参数 + hasAddress[0] = paramNames.contains("{mapaddress}") + || paramNames.contains("{latitude}") + || paramNames.contains("{longitude}"); + + // 移除不需要的参数 + paramNames.remove("{loginname}"); + paramNames.remove("{loginid}"); + paramNames.remove("{logintype}"); + paramNames.remove("{serverid}"); + paramNames.remove("{loginclientid}"); + paramNames.remove("{password}"); + paramNames.remove("{mapaddress}"); + paramNames.remove("{latitude}"); + paramNames.remove("{longitude}"); + + // 去除参数中的{} + List result = new ArrayList<>(); + for (String nm : paramNames) { + result.add(nm.replace("{", "").replace("}", "")); + } + return result; + } + + private String ConvertRightMenuSql(String res) { + res = PublicUtil.ReqSqlPms(null, null, res, SystemTypeEnums.PmType.ignorenull, getUser()); + + // 检查是否以@开头且不含{...}占位符 + if (res.startsWith("@") && !Pattern.compile("\\{(.*?)\\}").matcher(res).find()) { + // 获取默认值并转换为布尔值 +// IPublicUtil util = new IPublicUtil(); + boolean result = toBoolean(createControl.GetDefaultValue(res, null, SystemTypeEnums.PmType.ignorenull)); + return result ? "1=1" : "1!=1"; + } + // 转换SQL并返回 + return PublicUtil.SqlToCode(res) + ""; + } + + public List> GetRightMenuRows(String fromKey, int menuType, int menuId) { + return DataImpl.GetRightMenuRows(fromKey, menuType, getUser().UserName, menuId); + } + + /** + * 重载方法,处理menuId默认值为0的情况 + */ + public List> GetRightMenuRows(String fromKey, int menuType) { + return DataImpl.GetRightMenuRows(fromKey, menuType, getUser().UserName, 0); + } + + private List> GetCardColumnRows(String moduleId, String userId, String userName) { + StringBuilder colSql = new StringBuilder("select id, FieldName, isnull(ISNULL(username1, sysname), FieldName) FieldCaption, bs_field defaultvalue, bs_color FontColor, bs_fontsize fontsize from p_systemwordbooktab"); + +// 拼接条件(当moduleId有效时) + if (moduleId != null && !moduleId.trim().isEmpty()) { + colSql.append(" where tab='") + .append(moduleId) + .append("' and bs_field<>'' and bs_order>0 order by bs_order"); + } + if (colSql != null && !colSql.isEmpty()) { + List> dtVal = ConversionUtils.toLowerColumnName(jdbcTemplate.queryForList(colSql.toString())); + return dtVal; + } + return null; + } + + /** + * 获取模块卡片分组信息 + * + * @param moduleId 模块ID + * @param isMain 是否为主卡片 + * @param isBase 是否为基础模块 + * @param mxId 明细ID + * @param cellTpl 是否为单元格模板 + * @return 分组后的卡片信息 + */ + public Object GetModuleCardGroup(String moduleId, Boolean isMain, Boolean isBase, int mxId, boolean cellTpl) { + // 处理isBase为空的情况 + if (isBase == null) { + isBase = DataImpl.IsBaseModule(moduleId); + } + // 获取移动卡片列数据 + List> cardTab = DataImpl.GetMobileCardColumn(moduleId, isMain, mxId, isBase, cellTpl); + + // 转换为MobileCard列表 + List mobileCards = cardTab.stream() + .map(row -> new MobileCard(row)) + .collect(Collectors.toList()); + + if (cellTpl) { + // 按ColName分组并转换为字典 + return mobileCards.stream() + .collect(Collectors.groupingBy( + MobileCard::getColName, + Collectors.toList() + )); + } else { + // 按GroupName分组并构建返回对象 + return mobileCards.stream() + .collect(Collectors.groupingBy(MobileCard::getGroupName)) + .values().stream() + .map(group -> { + MobileCard firstCard = group.stream().findFirst().orElse(null); + if (firstCard == null) { + return null; + } + // 构建匿名对象(实际项目中建议使用实体类) + Map groupInfo = new HashMap<>(); + groupInfo.put("name", group.stream().findFirst().get().getGroupName()); + groupInfo.put("showText", firstCard.isGroupVisible()); + groupInfo.put("mxId", firstCard.getMxId()); + groupInfo.put("items", group); + return groupInfo; + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + } + + // 重载方法,处理默认参数 + public Object GetModuleCardGroup(String moduleId, Boolean isMain, Boolean isBase, int mxId) { + return GetModuleCardGroup(moduleId, isMain, isBase, mxId, false); + } + + public Object GetModuleCardGroup(String moduleId, Boolean isMain, Boolean isBase) { + return GetModuleCardGroup(moduleId, isMain, isBase, 0, false); + } + + + // setMian辅助方法 + public List GetColumns(String moduleId, boolean selfEdit) { + List> dtval = GetColumnRows(moduleId); +// out.println("GetColumnRows "); + if (dtval != null && !dtval.isEmpty()) { + dtval = dtval.stream().map(HashMap::new) // 复制每一行 + .collect(Collectors.toList()); + } + + // 查找并修改特定行 + if (dtval != null) { + dtval.stream().filter(row -> SystemEnums.ControlType.LabTreeType.getValue() == NativeExtensionUtils.parseInt((row.get("fieldtype")).toString())).findFirst().ifPresent(treeRow -> treeRow.put("fieldtype", SystemEnums.ControlType.LabComboxValue.getValue())); + } +// out.println("GetColumns -> ToColumns"); + return ToColumns(dtval, selfEdit); + } + + // 主方法 + public List ToColumns(List> dtval, boolean selfEdit, ModuleEntity module) { + AtomicInteger ti = new AtomicInteger(); + List columns = dtval.stream().map(row -> { + RowColumn column = new RowColumn(row, selfEdit, createControl); + column.setTabIndex(ti.getAndIncrement()); + String defaultValue = DataTableUtil.getRowVal(row, "defaultValue", "") + ""; + column.setDefaultval(createControl.GetDefaultValue(defaultValue, null, SystemTypeEnums.PmType.store)); + return column; + }).collect(Collectors.toList()); + + List hiddens = columns.stream() + .filter(com -> { + RowColumn rowCol = (RowColumn) com; + Boolean hidden = rowCol.getHidden(); + return hidden != null && hidden; // 替代 C# 的??运算符 + }) + .map(com -> { + RowColumn rowCol = (RowColumn) com; + rowCol.ControlTabIndex = (-1); // 对应原代码的 columns.Count + return (Field) com; + }) + .collect(Collectors.toList()); + + List notHiddens = columns.stream() + .filter(com -> { + RowColumn rowCol = (RowColumn) com; + Boolean hidden = rowCol.getHidden(); + return hidden == null || !hidden; // 替代 C# 的??运算符 + }) + .map(com -> (Field) com) + .collect(Collectors.toList()); + + return columns; + } + + // 方法重载 + public List ToColumns(List> dtval, boolean selfEdit) { + return ToColumns(dtval, selfEdit, null); // 调用完整参数版本,传递默认值 + } + + /** + * 获取行数据 + */ + public List> GetColumnRows(String moduleId) { + return DataImpl.GetColumnRows(moduleId, getUser().UserId, getUser().UserName, 0); + } + + public List> GetColumnRows(String moduleId, Integer id) { + return DataImpl.GetColumnRows(moduleId, getUser().UserId, getUser().UserName, id); + } + + private static final Pattern REGEX_PATTERN = Pattern.compile("[^\\d.\\-,]"); + + /** + * 公共函数接口,根据查询参数,分发具体查询sql的方法 + */ + @Override + public BaseResponse getFieldData(String moduleId, Integer fieldId, String record, String leftRecord, String pams, String keyField, String keyValue, Integer fdtype, String contextMenuId, Boolean readOnly, String userId, String userName, String baseMainGridViewPrefix, Boolean windowsDirver, String ModuleCode, String MenuId) { + BaseResponse response = new BaseResponse(); + boolean convertStr = false; + BillModule module; + List> ColumnRows = null; + String dataSource = ""; + if (fieldId > 0) { + switch (fdtype) { + case 1: + ColumnRows = DataImpl.GetCondition(null, fieldId, windowsDirver); + break; + case 2: + ColumnRows = DataImpl.GetBillDetailColumns(null, userId, userName, fieldId); + break; + case 3: + module = GetBillModule(ModuleCode, MenuId); + String Fromkey = module.getFromkey(); + ColumnRows = DataImpl.GetControlRows(Fromkey, userName, null, fieldId); + + break; + case 4: + ColumnRows = DataImpl.GetBillMasterRows(null, userName, fieldId); + break; + case 5: + ColumnRows = DataImpl.getDynamicSchemeFields(0, fieldId); + dataSource = DataImpl.getSchemeFieldDataSourceSql(fieldId); + break; + case 99: + break; //用户下拉框 + default: + ColumnRows = GetColumnRows(null, fieldId); + break; + } + } else { + switch (fieldId) { + case -99: + try { + // 直接使用 JdbcTemplate 执行 SQL 查询 + List> data = jdbcTemplate.queryForList("select speciesno,speciesname,substring(speciesno,1,len(speciesno)-2) as pid from P_EmployeeSpecTab"); + + response.setSuccess(true); + response.setData(data); + } catch (Exception e) { + // 处理异常 + response.setSuccess(false); + response.setMsg("执行 SQL 查询时出错: " + e.getMessage()); + } + break; + } + return response; + } + if (ColumnRows != null && !ColumnRows.isEmpty()) { + Map row = ColumnRows.get(0); + String fromkey = row.get("fromkey") + "_Cond"; + IPublicUtil util = new IPublicUtil(jdbcTemplate); + ComboBox field = null; +// ComboBox field = (ComboBox) util.createControl(row, null, false);// new ComboBox(fieldRow, util); +// 安全转换 + Object control = util.createControl(row, null, false); + if (control instanceof ComboBox) { + field = (ComboBox) control; + } else { + // 类型不匹配时的处理(可选) + field = null; + } + if (field == null) field = new ComboBox(row, util); + if (field != null && !isNullOrEmpty(dataSource)) { + field.setDataSource(dataSource); + } + if (keyValue != null && !keyValue.isEmpty() && !readOnly) { + ComboBox _field = new ComboBox(row, util); + keyField = keyField == field.getDisplayField() || keyField == field.getValueField() ? _field.getDisplayField() == _field.getValueField() ? field.getDisplayField() : field.getDisplayField() + "," + field.getValueField() : keyField; + if (field.multiSelect) { + keyValue = keyValue.replace("%2C", ","); + } + // 对应C#: if (_field.columns != null) + if (_field.getColumns() != null) { + keyField = ""; + StringBuilder keyFieldBuilder = new StringBuilder(); + List columns = _field.getColumns(); + for (Column col : columns) { + // 关键:C#是把新列名拼在前面,所以这里要插入到builder头部 + keyFieldBuilder.insert(0, col.dataIndex + ","); + } + // 2. 拼接displayField(拼在最前面) + // 对应C#: keyField = $"{_field.displayField},{keyField}"; + if (_field.getDisplayField() != null) { + keyFieldBuilder.insert(0, _field.getDisplayField() + ","); + } + + // 3. 拼接valueField(拼在最前面) + // 对应C#: keyField = $"{_field.valueField},{keyField}"; + if (_field.getValueField() != null) { + keyFieldBuilder.insert(0, _field.getValueField() + ","); + } + + // 4. 正则校验:判断是否需要转换字符串 + // 对应C#: if (field.displayField != field.valueField && new Regex(@"[^\d\.\-\,]").IsMatch(keyValue)) + if (field != null + && !field.getDisplayField().equals(field.getValueField()) + && keyValue != null + && REGEX_PATTERN.matcher(keyValue).find()) { + convertStr = true; // Java用数组传递布尔值(模拟引用传递) + } + + // 5. 去除末尾逗号(对应C#: keyField = keyField.Trim(',');) + keyField = keyFieldBuilder.toString().replaceAll(",$", ""); + } + } else if (keyValue != null && !keyValue.isEmpty() && readOnly) { + keyField = keyField != null && !keyField.isEmpty() ? field.getDisplayField() == field.getValueField() ? field.getDisplayField() : field.getValueField() : keyField; + } + Boolean doNotSpelling = field.isDoNotSpelling(); + if (field.getDataSource() != null && !field.getDataSource().isEmpty()) { + String querySql = field.getDataSource(); + String deledSql; + if (readOnly && !field.getDisplayField().equals(field.getValueField()) && Pattern.compile("[^\\d\\.\\-,]").matcher(keyValue).find()) { + String _testSql = new SqlAnalyzer(querySql.replace("#", "")).InsertWhere(new HashMap() {{ + put("a", "and 1!=1"); + }}, false, false, false); + // List> _tbVal = jdbcTemplate.queryForList(PublicUtil.ReqSqlPmsByRow(null, null, _testSql, SystemTypeEnums.PmType.sql, null)); +// String keyColName = null; +// // 查找目标字段名(忽略大小写) +// if (!_tbVal.isEmpty()) { +// Map firstRow = _tbVal.get(0); +// for (String colName : firstRow.keySet()) { +// if (colName.toLowerCase().equals(keyField.toLowerCase())) { +// keyColName = colName; +// break; +// } +// } +// } else { +// // 结果集为空时,通过SQL解析表名并查询元数据获取字段名 +// keyColName = getKeyColNameFromMetaData(_testSql, keyField); +// } +// +// // 判断字段是否为整数类型(兼容结果集有值和空结果集的情况) +// if (keyColName != null) { +// // 1. 若结果集有值,直接判断值的类型 +// if (!_tbVal.isEmpty()) { +// Object value = _tbVal.get(0).get(keyColName); +// if (value instanceof Integer || value instanceof Long) { +// convertStr = true; +// } +// } else { +// // 2. 结果集为空时,通过元数据判断字段类型 +// if (isIntegerColumnFromMetaData(_testSql, keyColName)) { +// convertStr = true; +// } +// } +// } + + convertStr = checkIfIntColumn(_testSql, keyField); + } + Hashtable _leftRecord = null; + Hashtable pms = null; + + if (field.getFieldType() == SystemEnums.ControlType.LabTreeType.getValue() && !"_Cond".equals(fromkey)) { + if (leftRecord != null && !leftRecord.isEmpty()) { + _leftRecord = new Hashtable<>(); + // 假设这里有 JSON 解析方法 + _leftRecord = (Hashtable) JSON.Decode(leftRecord); + } + if (pams != null && !pams.isEmpty()) { + pms = new Hashtable<>(); + // 假设这里有 JSON 解析方法 + pms = (Hashtable) JSON.Decode(pams); + } + querySql = reqSearchCondition(fromkey, field.getDataSource(), pms, _leftRecord, false); + } + + if (contextMenuId != null && !contextMenuId.isEmpty()) { + List> dtval = GetRightMenuRows("", Integer.parseInt(contextMenuId), 0, true); + SysPoPupMenuBtn btn = null; + for (Map _row : dtval) { + btn = new SysPoPupMenuBtn(); + btn.dllpar3 = PublicUtil.ReqSqlPms(_leftRecord, null, row.get("param3").toString(), SystemTypeEnums.PmType.sql, null); + if (btn.dllpar3 != null && !btn.dllpar3.isEmpty()) { + break; + } + } + if (btn != null && btn.dllpar3 != null && !btn.dllpar3.isEmpty()) { + ComboBox finalField = field; + SysPoPupMenuBtn finalBtn = btn; + querySql = new SqlAnalyzer(querySql).InsertWhere(new HashMap() {{ + put(finalField.getName(), finalBtn.dllpar3); + }}, false, false, false); + } + } + + if (convertStr && keyValue != null && !keyValue.isEmpty()) { + if (field.getColumns() != null) { + StringBuilder keyFieldBuilder = new StringBuilder(); + for (int i = 0; i < field.getColumns().size(); i++) { + keyFieldBuilder.append(field.getColumns().get(i).dataIndex); + if (i < field.getColumns().size() - 1) { + keyFieldBuilder.append(","); + } + } + keyField = keyFieldBuilder.toString(); + } else { + keyField = field.getDisplayField().equals(field.getValueField()) ? field.getDisplayField() : field.getDisplayField() + "," + field.getValueField(); + + } + } + deledSql = dealQuerySql(querySql, record, leftRecord, pams, keyField, keyValue, readOnly, convertStr, doNotSpelling); + try { + if (keyValue != null && (!keyValue.contains(",") || !deledSql.contains(";"))) { + List> dtVal = dbOperator.executeDataTable(deledSql, getStartsize(), getPageSize(), tot); + if (dtVal.size() > 1 && keyValue != null && !keyValue.isEmpty() && !keyValue.contains(",") && readOnly) { + deledSql = dealQuerySql(querySql, record, leftRecord, pams, keyField, keyValue, false, convertStr, false); + dtVal = dbOperator.executeDataTable(deledSql, getStartsize(), getPageSize(), tot); + } + response.setData(toHashTable(dtVal)); + response.setSuccess(true); + response.setTot(tot[0] != 0 ? tot[0] : dtVal.size()); + } + } catch (Exception e) { + response.setMsg(e.getMessage()); + LoggerHandler.error(this, String.format("解析字典数据出错=>%s:%d=>%s", moduleId, fieldId, e.getMessage())); +// throw new RuntimeException(String.format("解析字典数据出错=>%s:%d=>%s", moduleId, fieldId, e.getMessage()), e); + } + } + } else if (fdtype == 99) { + response.setData(new SysUserImpl().GetUserByName(keyValue, "")); + } + response.setSuccess(true); + return response; + } + + /** + * 从请求参数列表中获取属性,传参给公共的接口,查询数据库数据 + * 获取请求的参数 + * 修改为baseimpl统一获取参数 + */ + @Override + public BaseResponse getFieldDataPam() { + Integer fieldId = toInt32(Request("id", "")); + Integer fdtype = toInt32(Request("fdtype", "")); + String fieldValue = Request("fieldValue", ""); + String record = Request("record", ""), + leftRecord = Request("leftRecord", ""), + poppms = Request("poppms", ""), + pms = Request("pms", ""), + contextMenuId = Request("contextMenuId", ""), + keyField = Request("textField", ""), + keyValue = Request(keyField, ""); + Boolean windowsDirver = toBoolean(Request("windowsDirver", "")); + Boolean readOnly = toBoolean(Request("readOnly", "")); + String baseMainGridViewPrefix = Request("baseMainGridViewPrefix", "BaseMainGridView_"); + String ModuleId = Request("moduleId", ""); + String MenuCode = Request("menucode", ""); + String MenuId = Request("menuId", ""); + String ModuleCode = isNullOrEmpty(ModuleId) ? ModuleId : MenuCode; + + String userId = getUser().UserId; + String userName = getUser().UserName; + ObjectMapper objectMapper = new ObjectMapper(); + try { + if (poppms.startsWith("{")) { + if (!record.startsWith("{")) { + record = poppms; + } else { + Map r = objectMapper.readValue(record, HashMap.class); + if (r != null) { + Map poppmsMap = objectMapper.readValue(poppms, HashMap.class); + for (Map.Entry entry : poppmsMap.entrySet()) { + r.put(entry.getKey(), entry.getValue()); + } + record = objectMapper.writeValueAsString(r); + } else { + record = poppms; + } + } + } + } catch (Exception e) { + log.error("Exception caught", e); + } + return getFieldData(ModuleId, fieldId, record, leftRecord, pms, keyField, keyValue, fdtype, contextMenuId, readOnly, userId, userName, baseMainGridViewPrefix, windowsDirver, ModuleCode, MenuId); + } + + /** + * getModuleData:Api请求方法(获取请求参数),具体交给GetModuleData处理 + */ + @Override + public BaseResponse getModuleData() { + BaseResponse response = new BaseResponse(); + String record = Request("record", ""), + leftRecord = Request("leftRecord", ""), + pms = Request("pms", ""), + detailId = Request("detailId", ""), + contextMenuId = Objects.toString(Request("contextMenuId"), ""), + sourceId = Request("sourceId", ""); + Integer attcId = toInt32(Request("attcId", "")); + Boolean other = toBoolean(Request("other", "1")); + String ModuleId = Request("moduleId", "").split(",")[0]; + if (!isNullOrEmpty(sourceId)) { + //单据来源数据 + response = GetBillSourceData(ModuleId, sourceId, pms, Request("w", ""), leftRecord, toBoolean(Request("detail", ""))); + } else if (isNullOrEmpty(detailId) || NativeExtensionUtils.parseInt(detailId) <= 0) { + //基础档案 + response = GetModuleData(ModuleId, record, leftRecord, pms, Request("w", ""), toBoolean(Request("multi", "")), toBoolean(Request("grid", "")), ToInt32(contextMenuId), attcId); + if (!other) { + response.setOther(null); + } + } else { + //基础档案明细 + response = GetModuleDetailData(); + } + return response; + } + + /** + * /// + * /// Gets the source data. + * /// + * /// The module identifier. + * /// The source identifier. + * /// The _pams. + * /// The left records. + * /// if set to true [is detail]. + * /// BaseResponse. + */ + public List> GetCondition(String fromkey, Integer id) { + if ((fromkey == null || fromkey.isEmpty()) && id == 0) return null; + return DataImpl.GetCondition(fromkey, id, isWindowsDirver()); + } + + + public BaseResponse GetBillSourceData(String moduleId, String sourceId, String pams, String w, String leftRecords, Boolean isDetail) { + BaseResponse response = new BaseResponse(); + Map pms = null; + List> leftRcs = new ArrayList<>(); + + // 获取账单源模块 + List modules = GetBillSourceModule(moduleId, sourceId, "", true); + BillSourceModule module = modules.isEmpty() ? null : modules.get(0); + + if (module != null) { + // 解析参数 + if (pams != null && !pams.isEmpty()) { + pms = (Map) JSON.Decode(pams); + } + + // 解析左侧记录 + if (leftRecords != null && !leftRecords.isEmpty()) { + if (!leftRecords.startsWith("[")) { + leftRecords = "[" + leftRecords + "]"; + } + leftRcs = (ArrayList) JSON.Decode(leftRecords); + } + + // 处理SQL + String sourceSql = (isDetail ? module.getDetailSql() : module.getMasterSql()); + if (sourceSql == null) sourceSql = ""; + sourceSql = sourceSql.toLowerCase(); + + List> styles = null; + Function, Map> fillStyles = null; + int sourceType = module.getSourceType(); + if (!isDetail) { + if (sourceType != 1) { + sourceSql = reqSearchCondition(module.getFromkey(), sourceSql, pms, null, false); + } +// out.println(" sourceSql = reqSearchCondition(module.getFromkey(), sourceSql, pms, null, false)"); + // 替换#...#为1=1 + sourceSql = sourceSql.replaceAll("(#)(.|\\n)*?(#)", "1=1"); + sourceSql = PublicUtil.ReqSqlPms(null, null, sourceSql, SystemTypeEnums.PmType.sql, getUser()); + // 获取样式 +// out.println("获取样式 1"); + styles = GetStyles(PublicUtil.GetBillConMenuKey(SystemEnums.BillMenuEnum.BillSource) + module.getFromkey()); +// out.println("获取样式 2"); + if (styles != null) { + List> finalStyles = styles; + fillStyles = row -> FillStyles(row, finalStyles); +// out.println("fillStyles ENd"); + } + } else { + switch (sourceType) { + case 1: + Map where = new HashMap<>(); + List> condition = DataImpl.GetCondition(module.getFromkey(), 0, toBoolean(Request("windowsDirver", ""))); + styles = GetStyles(PublicUtil.GetBillConMenuKey(SystemEnums.BillMenuEnum.BillSourceDetail) + module.getFromkey()); + if (styles != null) { + List> finalStyles1 = styles; + fillStyles = row -> FillStyles(row, finalStyles1); + } + + if (condition != null && !condition.isEmpty()) { + sourceSql = reqSearchCondition(condition, sourceSql, pms, null, false); + } else if (pms != null) { + for (Map.Entry entry : pms.entrySet()) { + where.put(entry.getKey(), + String.format(" and %s like '%s%%'", + entry.getKey(), + entry.getValue())); + } + } + + // 处理左侧记录条件 + if (leftRcs != null && !leftRcs.isEmpty()) { + Pattern consReg = Pattern.compile( + String.format("%s\\s+like\\s+'\\{%s\\}%%'", + module.getIdField(), module.getIdField()), + Pattern.CASE_INSENSITIVE | Pattern.MULTILINE + ); + + // 收集左侧记录ID值 + List vals = new ArrayList<>(); + for (Map rec : leftRcs) { + Object idValue = rec.get(module.getIdField()); + vals.add(idValue != null ? idValue.toString() : ""); + } + + + String condStr = String.format( + leftRcs.size() > 1 ? "%s in ('%s')" : "%s like '%s%%' ", + module.getIdField(), + String.join("','", vals) + ); + + Matcher matcher = consReg.matcher(sourceSql); + sourceSql = matcher.replaceAll(condStr); + } + + // 插入where条件 + if (!where.isEmpty()) { + sourceSql = new SqlAnalyzer(sourceSql).InsertWhere(where, false, false, false); + } + + // 处理SQL参数 + Map firstLeftRec = null; + firstLeftRec = (leftRcs != null && !leftRcs.isEmpty()) ? leftRcs.get(0) : null; + sourceSql = PublicUtil.ReqSqlPms(firstLeftRec, null, sourceSql, SystemTypeEnums.PmType.sql, null); + break; + + default: + // 处理默认类型 + Map defaultLeftRec = null; + defaultLeftRec = leftRcs != null && leftRcs.size() > 0 ? leftRcs.get(0) : null; + sourceSql = PublicUtil.ReqSqlPms(defaultLeftRec, null, sourceSql, SystemTypeEnums.PmType.sql, getUser()); + break; + } + } + + // 处理额外where条件 + if (w != null && !w.isEmpty()) { + Map whereDict = new HashMap<>(); + whereDict.put("$where", w); + sourceSql = new SqlAnalyzer(sourceSql).InsertWhere(whereDict, false, false, true); + } + + // 处理查询SQL + Map dealLeftRec = (leftRcs != null && !leftRcs.isEmpty()) ? leftRcs.get(0) : null; + sourceSql = dealQuerySql(sourceSql, null, dealLeftRec, null, null, null, false, false, false); + sourceSql = PublicUtil.fixSqlCompatibility(sourceSql); +// out.println(sourceSql + "sourceSql"); + // 执行查询 + List> resultTable = dbOperator.executeDataTable(sourceSql, getStartsize(), getPageSize(), tot); + int tot1 = tot[0] != 0 ? tot[0] : resultTable.size(); +// out.println("执行查询 end"); + + // 转换结果 + response.setData(toHashTable(resultTable, null, null, styles != null && !styles.isEmpty() ? fillStyles : null, true)); + response.setTot(tot1); + response.setSuccess(true); + } + return response; + } + + /** + * 获取账单模块信息 + * + * @param moduleCode 模块代码 + * @param menuId 菜单 ID + * @return 账单模块信息 + */ + public BillModule GetBillModule(String moduleCode, String menuId) { + BillModule module = null; + List> dtValue = DataImpl.GetBillModule(moduleCode, menuId); + if (dtValue != null && !dtValue.isEmpty()) { + module = new BillModule(dtValue.get(0)); + } + if (module != null && !isNullOrEmpty(menuId) && NativeExtensionUtils.parseInt(menuId) > 0) { + module.OperAble = Objects.equals(createControl.CheckPurview(getUser().PurviewStr, menuId), "AllPurview"); + } + return module; + } + + + /** + * /// + * /// 执行条件替换 + * /// + * /// The condkey + * /// The source SQL + * /// The PMS + * /// The left record + * /// 是否只执行变量替换 + * /// System.String + * /// + * /// + */ + public String reqSearchCondition(String condkey, String sourceSql, Map pms, Map leftRecord, boolean repCond) { + if (condkey == null || condkey.isEmpty()) { + return sourceSql; + } + List> dt = DataImpl.GetCondition(condkey, 0, true); + return reqSearchCondition(dt, sourceSql, pms, leftRecord, repCond); + } + + public String reqSearchCondition(List> dt, String sourceSql, Map pms, Map leftRecord, boolean repCond) { + Map where = new HashMap<>(); + Hashtable record = new Hashtable<>(); + List Pms = new PmAnalyzer(getUser(), sourceSql).getPms(); +// out.println(dt.toString() + Pms.toString() + "reqSearchCondition"); + for (Map row : dt) { + Field field = new Field(row); + field.setIssearchcontrol(true); + if (field.isDisabled2()) { + continue; + } + String controlName = field.getName(); + int condId = field.getFieldId(); + String cond = Objects.toString(field.getWhereCond(), "").trim().toLowerCase(); + if (cond.startsWith("where")) { + cond = " and " + cond.substring(5); + } + String fieldTypeStr = Objects.toString(field.getFieldType(), ""); + SystemEnums.ControlType fieldType = null; + try { + int typeValue = Integer.parseInt(fieldTypeStr); + fieldType = SystemEnums.ControlType.fromValue(typeValue); + } catch (IllegalArgumentException e) { + // 处理无法解析的枚举值 + log.warn(String.valueOf("无法解析的 ControlType: " + fieldTypeStr)); + } +// out.println("fieldTypeStr " + fieldType + "pms" + pms); + Pattern inReg = Pattern.compile(" +in ?\\("); + String pmValue = ""; +// out.println(!pms.containsKey(controlName) + "!pms.containsKey(controlName)" + pms + pms.get(controlName)); + if (pms == null || pms.isEmpty() || !pms.containsKey(controlName)) { + if (fieldType != null && fieldType.toString().contains("LabCheck")) { + continue; + } +// IPublicUtil control = new IPublicUtil(); + pmValue = createControl.GetDefaultValue(field.getDefaultsource() != null ? field.getDefaultsource() : "" + , new ModuleEntity() {{ + setLeftRecord(leftRecord); + }}, SystemTypeEnums.PmType.sql); +// out.println(" pmValue = createControl.GetDefaultValue " + pmValue + field.getDefaultsource()); + if ("''".equals(pmValue)) { + pmValue = ""; + } + } else if ((pms.containsKey(controlName) && !(pmValue = pms.get(controlName) + "").isEmpty()) || (pms.containsKey(condId + "") && !(pmValue = pms.get(condId + "") + "").isEmpty()) || (pms.containsKey(condId + "") && !(pmValue = pms.get(condId + "") + "").isEmpty())) { + if (pmValue.contains(",") && fieldTypeStr.toLowerCase().contains("multi") && inReg.matcher(cond.toLowerCase()).find()) { + pmValue = pmValue.replace(",", "','"); + } + if (sourceSql.contains("{&" + controlName + "}")) { + sourceSql = sourceSql.replace("{&" + controlName + "}", "{" + controlName + "}"); + Hashtable valueMap = new Hashtable<>(); + valueMap.put("value", pmValue); + pmValue = PublicUtil.ReqSqlPms(valueMap, leftRecord, cond, SystemTypeEnums.PmType.sql, getUser()); + } + } + + if (Pms.contains(controlName.toLowerCase())) { + record.put(controlName.toLowerCase(), pmValue); + cond = ""; + } + if (!cond.isEmpty()) { + Hashtable valueMap = new Hashtable<>(); + valueMap.put("value", pmValue); +// cond = PublicUtil.ReqSqlPms(valueMap, leftRecord, cond, SystemTypeEnums.PmType.sql, getUser()); + cond = cond.toLowerCase().replace("{value}", pmValue); + where.put(controlName, cond); +// out.println(cond + where + "cond+where"); + } + } + + // 对where进行排序,与C#逻辑保持一致 + List> whereList = new ArrayList<>(where.entrySet()); + whereList.sort((kv1, kv2) -> { + boolean kv1HasOrderBy = kv1.getValue().contains("order by"); + boolean kv2HasOrderBy = kv2.getValue().contains("order by"); + + if (kv1HasOrderBy && !kv2HasOrderBy) { + return 1; // 含order by的放后面 + } else if (!kv1HasOrderBy && kv2HasOrderBy) { + return -1; // 不含order by的放前面 + } else { + return 0; // 相同类型保持原有顺序 + } + }); + // 将排序后的列表转换为LinkedHashMap(保持顺序) + where = new LinkedHashMap<>(); + for (Map.Entry entry : whereList) { + where.put(entry.getKey(), entry.getValue()); + } + sourceSql = PublicUtil.ReqSqlPms(record, leftRecord, sourceSql, SystemTypeEnums.PmType.ignorenull, getUser()); + if (!repCond) { + sourceSql = new SqlAnalyzer(sourceSql).InsertWhere(where, false, false, false); + } + return sourceSql; + } + + /** + * /// + * /// Gets the right menu rows. + * /// + * /// The fromkey. + * /// The menutype. + * /// The menuid. + * /// List>. + * /// + * /// + */ + public List> GetRightMenuRows(String fromkey, int menutype, int menuid, boolean windowsDirver) { + if (menuid > 0) { +// return crmapper.selectSystemPopupMenuById(menuid, fromkey, getUser().UserName); + return getDetailJDBC().selectSystemPopupMenuById(menuid, fromkey, getUser().UserName); + } else { +// return crmapper.selectSystemPopupMenuByType(menutype, fromkey, getUser().UserName, windowsDirver); + return getDetailJDBC().selectSystemPopupMenuByType(menutype, fromkey, getUser().UserName, windowsDirver); + } + } + + /** + * /// + * /// Deals the query SQL. + * /// + * /// The query SQL + * /// The _record + * /// The _left record + * /// The _pams + * /// The key field + * /// The key value + * /// true 精确匹配,去掉带#号条件,前端下拉框在readonly情况下 + * /// 是否转换字段进行匹配convert(varchar(1000),name) + * /// 匹配时是否搜索拼音(影响查询速度) + * /// System.String + */ + public String dealQuerySql(String querySql, String _record, String _leftRecord, String _pams, String keyField, String keyValue, boolean readOnly, boolean convertStr, boolean doNotSpelling) { + Map record = null, leftRecord = null, pms = null; + + if (_record != null && !_record.isEmpty()) { + record = (Map) JSON.Decode(_record); + } + if (_leftRecord != null && !_leftRecord.isEmpty()) { + leftRecord = (Map) JSON.Decode(_leftRecord); + } + if (_pams != null && !_pams.isEmpty()) { + pms = (Map) JSON.Decode(_pams); + } + + return createControl.dealQuerySql(querySql, record, leftRecord, pms, keyField, keyValue, false, readOnly, convertStr, doNotSpelling); + } + + public String dealQuerySql(String querySql, Map record, Map leftRecord, Map pams, String keyField, String keyValue, Boolean precise, Boolean readOnly, Boolean convertStr) { + return createControl.dealQuerySql(querySql, record, leftRecord, pams, keyField, keyValue, precise, readOnly, convertStr, false); + } + + //专门给附件的 + public String DealQuerySqlfor(String querySql, Map record, Map leftRecord, Map pams, String keyField, String keyValue, Boolean precise, Boolean readOnly, Boolean convertStr) { + return createControl.DealQuerySql(querySql, record, leftRecord, pams, keyField, keyValue, precise, readOnly, convertStr, false); + } + + public String dealQuerySql(String querySql, Map record, Map leftRecord, Map pams, String keyField, String keyValue) { + return dealQuerySql(querySql, record, leftRecord, pams, keyField, keyValue, false, false, false); + } + + /** + * 获取样式数据表格 + * + * @param fromkey 表格标识 + * @return 转换为小写列名的DataTable + */ + public List> GetStyles(String fromkey) { +// String sql = String.format("select condition cond,forcecolor FontColor,backcolor,dfcolor,ifBold Bold,ifItalic IncLine,ifStrickOut DeleteLine,ifUnderLine UnderLine,fontsize from p_systemwordbookcolor where tab='{0}' and isnull(useflag,0)=0 order by orderid", fromkey); + // 参数化SQL查询,避免SQL注入 + String sql = "select condition as cond, " + + "forcecolor as FontColor, " + + "backcolor, " + + "dfcolor, " + + "ifBold as Bold, " + + "ifItalic as IncLine, " + + "ifStrickOut as DeleteLine, " + + "ifUnderLine as UnderLine, " + + "fontsize " + + "from p_systemwordbookcolor " + + "where tab = ? " + + "and isnull(useflag, 0) = 0 " + + "order by orderid"; + + // 执行查询并处理结果集列名小写转换 + return jdbcTemplate.query(sql, new Object[]{fromkey}, (rs, rowNum) -> { + Map row = new HashMap<>(); + for (int i = 1; i <= rs.getMetaData().getColumnCount(); i++) { + // 列名转换为小写 + row.put(rs.getMetaData().getColumnName(i).toLowerCase(), rs.getObject(i)); + } + return row; + }); + } + + /** + * 填充样式信息 + * + * @param row 数据行(Map形式) + * @param colorDt 样式数据表(List形式) + * @return 包含行样式和单元格样式的Hashtable + */ + protected Map FillStyles(Map row, List> colorDt) { + if (colorDt == null || colorDt.isEmpty()) { + return null; + } + + Hashtable colorTab = new Hashtable<>(); + List rowStyles = new ArrayList<>(); + Hashtable cellStyles = new Hashtable<>(); + Pattern regex = Pattern.compile("\\[([^\\[])*?\\]"); + + for (Map colorRow : colorDt) { + ModuleColor color = createModuleColor(colorRow); + String cond = color.Cond; + String key = "rowcolor"; + boolean isCell = false; + + if (cond == null || cond.trim().isEmpty()) { + continue; + } + + // 处理条件中的[]占位符 + Matcher matcher = regex.matcher(cond); + List matches = new ArrayList<>(); + while (matcher.find()) { + matches.add(matcher.group()); + } + + // 获取样式数组和样式字符串 + + ArrayList styles = color.ToSArray(); // 需在ModuleColorEntity中实现ToSArray方法 + String style = ModuleColor.ToStyleStr(styles); // 需实现静态方法toStyleStr + if (style == null || style.trim().isEmpty()) { + continue; + } + + // 处理单元格样式标识 + if (!matches.isEmpty()) { + cond = cond.replace("[", "{").replace("]", "}"); + key = matches.get(0).replace("[", "").replace("]", "").toLowerCase(); + isCell = true; + } + + // 验证条件是否成立 + if (toBoolean(getUtil().evalCond(cond, row, null))) { + if (isCell) { + cellStyles.put(key, style); + } else { + rowStyles.addAll(styles); + } + } + } + + // 组装单元格样式 + if (!cellStyles.isEmpty()) { + colorTab.put("cellstyle", cellStyles); + } + + // 组装行样式(去重处理) + if (!rowStyles.isEmpty()) { + rowStyles = removeDuplicates(rowStyles); + colorTab.put("rowstyle", ModuleColor.ToStyleStr(rowStyles)); + } + + return colorTab; + } + + /** + * 创建ModuleColorEntity实例 + */ + private ModuleColor createModuleColor(Map row) { + ModuleColor color = new ModuleColor(); + color.Cond = DataTableUtil.getStringValue(row, "cond", ""); + color.FontColor = DataTableUtil.getStringValue(row, "fontcolor", ""); + color.BackColor = DataTableUtil.getStringValue(row, "backcolor", ""); + color.FontSize = DataTableUtil.getIntValue(row, "fontsize", 0); + color.Bold = DataTableUtil.getBooleanValue(row, "bold", false); + color.IncLine = DataTableUtil.getBooleanValue(row, "inclline", false); + color.DeleteLine = DataTableUtil.getBooleanValue(row, "deleteline", false); + color.UnderLine = DataTableUtil.getBooleanValue(row, "underline", false); + return color; + } + + /** + * 评估条件是否成立(使用PmAnalyzer处理条件表达式) + */ + private boolean evalCond(String cond, Map row) { + // 这里简化实现,实际应根据原有util.EvalCond逻辑实现 + PmAnalyzer analyzer = new PmAnalyzer(null, cond); + String processedCond = analyzer.FillPms(row, null); + // 假设条件处理后为"true"/"false"字符串,实际场景可能需要更复杂的表达式解析 + return "true".equalsIgnoreCase(processedCond); + } + + /** + * 移除列表中的重复对象 + */ + private List removeDuplicates(List list) { + Set set = new LinkedHashSet<>(list); + return new ArrayList<>(set); + } + + /** + * 获取单据来源模块信息 + * + * @param moduleId 模块ID + * @param id 标识符 + * @param sourceType 来源类型(-1:所有,0:表格,1:树,2:单据管理) + * @param moduleOnly 是否只返回模块基本信息 + * @return 单据来源模块列表 + */ + public List GetBillSourceModule(String moduleId, String id, String sourceType, boolean moduleOnly) { + // 假设dataImpl是已注入的数据源操作实例 + List> dtVal = DataImpl.GetBillSource(moduleId, id, sourceType); + List modules = new ArrayList<>(); + List bbItems = new ArrayList<>(); + + for (Map row : dtVal) { + BillSourceModule sourceModule = new BillSourceModule(row); + sourceModule.setModuleId(moduleId); + + if (!moduleOnly) { + // 获取工具栏项目 + sourceModule.setTbarItems(GetCondition(sourceModule.getFromkey(), 0)); +// out.println(GetCondition(sourceModule.getFromkey(), 0) + " firstStep "); + // 创建数据存储对象 + DataStore store = new DataStore(); + store.extraParams = (Map.of( + "moduleId", moduleId, + "sourceId", sourceModule.getSourceId() + )); +// out.println(store.extraParams + " store.extraParams " + sourceModule.getSourceType() + " sourceModule.getSourceType()"); + // 根据源类型创建不同的主面板 + if (sourceModule.getSourceType() == 1) { + TreePanel treePanel = new TreePanel(); + treePanel.title = (sourceModule.getTitle()); + treePanel.store = (store); + treePanel.valueField = (sourceModule.getIdField()); + treePanel.displayField = (sourceModule.getDisplayField()); + treePanel.TbarItems = (sourceModule.getTbarItems()); + sourceModule.setMain(treePanel); + } else { + GridPanel gridPanel = new GridPanel(); + gridPanel.title = (sourceModule.getTitle()); + gridPanel.PageAble = (true); + gridPanel.TbarItems = (sourceModule.getTbarItems()); + gridPanel.RightMenu = (GetRightMenu( + PublicUtil.GetBillConMenuKey(SystemEnums.BillMenuEnum.BillSource) + sourceModule.getFromkey(), + 0, + new Ref(bbItems), + "rightclick" + )); + // 获取列配置并转换 + List> columnsData = DataImpl.GetBillSourceColumns( + sourceModule.getSourceId() + "", + getUser().UserId + ); + gridPanel.setColumns(ToColumns(columnsData, false)); + gridPanel.setStore(store); + sourceModule.setMain(gridPanel); + } + + // 处理详情面板 + List> detailColumns = DataImpl.GetBillSourceDetailColumns( + sourceModule.getSourceId() + "", + getUser().UserId + ); + + GridPanel detailGrid = new GridPanel(); + detailGrid.PageAble = (true); + detailGrid.RightMenu = (GetRightMenu( + PublicUtil.GetBillConMenuKey(SystemEnums.BillMenuEnum.BillSourceDetail) + sourceModule.getFromkey(), + 0, + new Ref(bbItems), + "rightclick" + )); + detailGrid.setColumns(ToColumns(detailColumns, false)); + + // 设置详情面板的IdField + if (!detailColumns.isEmpty()) { + Map firstRow = detailColumns.get(0); + detailGrid.IdField = (firstRow.get("FieldName").toString()); + } + + // 设置详情面板的数据源 + DataStore detailStore = new DataStore(); + detailStore.extraParams = (Map.of( + "moduleId", moduleId, + "sourceId", sourceModule.getSourceId(), + "detail", 1 + )); + detailGrid.setStore(detailStore); + + sourceModule.setDetails(new GridPanel[]{detailGrid}); + } + + modules.add(sourceModule); + } +// out.println(3); + return modules; + } + + public List GetBillSourceModule(String moduleId, String id, String sourceType) { + return GetBillSourceModule(moduleId, id, sourceType, false); + } + + /** + * /// + * /// 获取查询条件 + * /// + * /// The conkey. + * /// + * /// System.Object. + */ + public Object GetCondition(String conkey, int auditFlag) { + // 获取条件字段数据(审计标志为0时调用dataImpl,否则调用审计搜索行方法) + List> dtFields = (auditFlag == 0) + ? DataImpl.GetCondition(conkey, 0, toBoolean(Request("windowsDirver", ""))) + : GetAuditSearchRows(); + if (dtFields != null && !dtFields.isEmpty()) { + List components = new ArrayList<>(); + // 创建控件并过滤掉disabled2的字段 + List preFields = createControl.createControl(dtFields, null, toBoolean(Request("windowsDirver", "")), conkey).stream() + .filter(field -> !field.isDisabled2()) + .collect(Collectors.toList()); + // 排除重复字段(根据name去重) + List fields = new ArrayList<>(); + for (Field com : preFields) { + boolean canAdd = true; + for (Field alreadyF : fields) { + if (Objects.equals(alreadyF.getName(), com.getName())) { + canAdd = false; + } + } + // 只添加非隐藏且宽度大于0的字段 + if (canAdd && com.getHidden() == false && com.getWidth() != null && com.getWidth() > 0) { + fields.add(com); + } + } +// out.println(fields + "fields"); + // 配置搜索框属性(不需要非空验证) + for (Field com : fields) { + com.setIssearchcontrol(true); +// com.setAllowBlank(true); +// 2026.2.5 修改,可以设置必填 + com.setAllowBlank(com.isAllowBlank()); + // 宽度为0时不设置默认值(原代码注释逻辑) + if (com.getWidth() == null || com.getWidth() == 0) { +// com.setWidth(175); + } + // 高度为0时设置默认21 + if (com.getHeight() == null || com.getHeight() == 0) { + com.setHeight(21); + } + } + + // 将字段添加到组件列表 + components.addAll(fields); + +// out.println(JSON.Encode(components)); + // 添加搜索按钮 + final int serBtnWidth = 70; + Button searchButton = new Button(null); + searchButton.setText("搜索"); + searchButton.width = serBtnWidth; + searchButton.setHandler("OnSearch"); + searchButton.setFormBind(true); + // 原代码中按钮位置计算逻辑(注释部分) + // 可根据需要添加:计算top/left并设置 + // searchButton.setTop(btntop); + // searchButton.setLeft(btnleft); + components.add(searchButton); + + return components; + } + return null; + } + + // 重载方法,处理auditFlag默认值为0的情况 + public Object GetCondition(String conkey) { + return GetCondition(conkey, 0); + } + + /** + * 获取审核搜索条件的模拟数据(对应C#的DataTable) + * + * @return 包含搜索条件的列表,每个元素为一行数据的Map + */ + public List> GetAuditSearchRows() { + // Java中没有DataTable,使用List>模拟 + List> resultList = new ArrayList<>(); + + // 第一行数据:开始日期 + Map row1 = new HashMap<>(); + row1.put("id", 1); + row1.put("fieldtype", 4); + row1.put("fieldname", "begin"); + row1.put("fieldcaption", "开始日期"); + row1.put("dataformat", "yyyy-MM-dd"); + row1.put("wherecond", " and bv.CreateDate>='{value}'"); + row1.put("controltop", 5); + row1.put("controlwidth", 170); + resultList.add(row1); + + // 第二行数据:结束日期 + Map row2 = new HashMap<>(); + row2.put("id", 2); + row2.put("fieldtype", 4); + row2.put("fieldname", "end"); + row2.put("fieldcaption", "结束日期"); + row2.put("dataformat", "yyyy-MM-dd"); + row2.put("wherecond", " and bv.CreateDate<='{value}'"); + row2.put("controltop", 5); + row2.put("controlwidth", 170); + resultList.add(row2); + + // 第三行数据:模糊查询 + Map row3 = new HashMap<>(); + row3.put("id", 3); + row3.put("fieldtype", 0); + row3.put("fieldname", "billid"); + row3.put("fieldcaption", "模糊查询"); + row3.put("dataformat", ""); + row3.put("wherecond", " and bv.billid like '%{value}%'"); + row3.put("controltop", 5); + row3.put("controlwidth", 170); + resultList.add(row3); + + return resultList; + } + + + /** + * 查询主表数据 + * + * @param moduleId 模块ID + * @param record 主表参数 + * @param leftRecord 父参数(parent.XX) + * @param pams 条件参数 + * @param whereClause 查询条件 + * @param multi 是否返回多数据集 + * @param grid 是否为表格模式 + * @param contextMenuId 上下文菜单ID + * @param attcId 附件ID + * @return BaseResponse 响应结果 + * @remarks + */ + public BaseResponse GetModuleData(String moduleId, String record, String leftRecord, + String pams, String whereClause, boolean multi, + boolean grid, int contextMenuId, int attcId) { + BaseResponse response = new BaseResponse(); + BaseModule module = null; + + // 处理特殊模块ID "-99" + if ("-99".equals(moduleId)) { + module = new BaseModule(); + module.setModuleId(moduleId); + module.setMasterTable("p_employeetab"); + module.setMasterSql("select employeeid,p_emp_no,employeename,departmentid,speciesno from p_employeetab where isnull(sign,0)=0 and UseFlag=1"); + } else { + // 获取基础模块信息 + String menuId = Request("menuId", ""); + module = GetBaseModule(moduleId, menuId); + } + + // 模块不存在时返回错误信息 + if (module == null) { + response.setMsg(("获取模块数据时,未找到编号为" + moduleId + "的模块,请检查配置!")); + return response; + } + + // 调用重载方法处理具体逻辑 + return GetModuleData(module, record, leftRecord, pams, whereClause, + multi, grid, contextMenuId, attcId, false); + } + + // 2026.2.24新增 + protected Object FillFilePath(String[] picFieldNames, String name, Object v) throws UnsupportedEncodingException { + String val = String.valueOf(v); + if (!isNullOrEmpty(val) && val.indexOf("http") < 0 && Arrays.asList(picFieldNames).contains(name) && (toBoolean((WebConfigUtil_web.get("useDbAttc"))) && !DataImpl.isDefaultServer() || !isNullOrEmpty(getAppDomain()))) { + String[] vals = val.split(","), newVals = new String[vals.length]; + String dbVerPath = WebConfigUtil_web.fileVPath + "_" + getUser().ServerId; + for (int i = 0; i < vals.length; i++) { + if (SiteUtil.containsVirtualDirectory(dbVerPath)) { + newVals[i] = vals[i].replace(WebConfigUtil_web.fileVPath, dbVerPath); + } else { + newVals[i] = TrimEnd(getAppDomain(), '/') + vals[i]; + } + } + return String.join(",", newVals); + } + return v; + } + + /** + * 基于模块实体查询数据 + * + * @param module 模块实体 + * @param recordStr 主表参数 + * @param leftRecordStr 父参数 + * @param pamsStr 条件参数 + * @param whereClause 查询条件 + * @param multi 是否多数据集 + * @param grid 是否表格模式 + * @param contextMenuId 右键菜单ID + * @param attcId 附加ID + * @param isTask 是否为待办 + * @return BaseResponse 响应结果 + */ + protected BaseResponse GetModuleData(BaseModule module, String recordStr, String leftRecordStr, + String pamsStr, String whereClause, boolean multi, boolean grid, + int contextMenuId, int attcId, boolean isTask) { + BaseResponse response = new BaseResponse(); + Map record = null; + Map leftRecord = null; + List> leftRecords = null; + Map pms = null; + + String querySql = module.getMasterSql(); + // 解析主表参数 + if (recordStr != null && !recordStr.isEmpty()) { + record = (Map) JSON.Decode(recordStr); + } + + // 解析父参数(处理数组格式) + if (leftRecordStr != null && !leftRecordStr.isEmpty()) { + // 确保JSON数组格式 + if (!leftRecordStr.startsWith("[")) { + leftRecordStr = "[" + leftRecordStr + "]"; + } + leftRecords = (List>) JSON.Decode(leftRecordStr); + // 取第一个元素作为leftRecord + if (leftRecords != null && !leftRecords.isEmpty()) { + leftRecord = leftRecords.get(0); + } + } + + // 解析条件参数 + if (pamsStr != null && !pamsStr.isEmpty()) { + pms = (Map) JSON.Decode(pamsStr); + } + + String noPmsVal = ""; + + // 处理右键菜单条件 + if (leftRecord != null && contextMenuId > 0) { + SysPoPupMenuBtn btn = GetContextMenuBtn(contextMenuId, leftRecord); + if (btn != null) { + SqlAnalyzer sqlAnalyzer = new SqlAnalyzer(querySql); + if (btn.getUnionCond() != null && !btn.getUnionCond().isEmpty()) { + Map whereMap = new HashMap<>(); + whereMap.put(btn.getUnionField(), btn.getUnionCond()); + querySql = sqlAnalyzer.InsertWhere(whereMap, false, false, false); + } else if ((btn.getUnionField() != null && !btn.getUnionField().isEmpty()) || + (btn.getUnionValue() != null && !btn.getUnionValue().isEmpty())) { + if (!btn.getUnionField().equals(btn.getUnionValue())) { + String where = String.format(" and %s = '%s'", btn.getUnionField(), btn.getUnionValue()); + Map whereMap = new HashMap<>(); + whereMap.put(btn.getUnionField(), where); + querySql = sqlAnalyzer.InsertWhere(whereMap, false, false, false); + } else { + noPmsVal = btn.getUnionValue().toLowerCase(); + } + } + } + } + // 处理左侧关联条件 + else if (leftRecord != null) { + Map dtVal = null; + if ("-99".equals(module.getModuleId())) { + dtVal = new HashMap<>(); + dtVal.put("fieldname", "speciesno"); + dtVal.put("valuemember", "speciesno"); + } else { + // 获取基础模块左侧配置 + List> leftRows = DataImpl.GetBaesModuleLeft(module.getModuleId()); + if (leftRows != null && !leftRows.isEmpty()) { + dtVal = leftRows.get(0); + } + } + + if (dtVal != null) { + String fieldName = dtVal.get("fieldname").toString(); + String valueMember = dtVal.get("valuemember").toString().toLowerCase(); + + // 检查SQL中是否包含父参数占位符 + if (querySql.toLowerCase().indexOf(String.format("{parent.%s}", valueMember)) == -1) { + // 获取字段类型 + Class fieldType = DataImpl.GetTableColumnType(module.getMasterTable(), fieldName); + + // 构建条件匹配列表 + List matchs = new ArrayList<>(); + for (Map rec : leftRecords) { + Object value = rec.get(valueMember); + if (value == null) continue; + + if (fieldType != null && fieldType.isPrimitive()) { + // 数值类型:使用IN条件 + matchs.add(value.toString()); + } else { + // 字符串类型:使用LIKE或精确匹配 + if (module.IsSpecModule) { + matchs.add(String.format(" %s like '%s__' or %s='%s' ", + fieldName, value, fieldName, value)); + } else { + String pattern = isTask ? " %s = '%s' " : " %s like '%s%%' "; + matchs.add(String.format(pattern, fieldName, value)); + } + } + } + + // 组装WHERE条件 + if (!matchs.isEmpty()) { + String where; + if (fieldType != null && fieldType.isPrimitive()) { + where = String.format(" and %s in (%s)", fieldName, String.join(",", matchs)); + } else { + where = String.format(" and (%s)", String.join("or", matchs)); + } + // 插入条件到SQL + SqlAnalyzer sqlAnalyzer = new SqlAnalyzer(querySql); + Map whereMap = new HashMap<>(); + whereMap.put(fieldName, where); + querySql = sqlAnalyzer.InsertWhere(whereMap, false, false, false); + } + } + } + } + + // 处理特殊模块ID "-99" + if ("-99".equals(module.getModuleId())) { + try { +// 2026.2.24 + String procedureName = "p_selectUser"; + List> result = null; + if (DataImpl.IsExitPro(procedureName)) { + //必须返回 employeeid,p_emp_no,employeename,departmentid,speciesno + String sepcVal = ""; + if (leftRecords != null && leftRecords.size() > 0) { + sepcVal = (leftRecords.get(0)).get("speciesno").toString(); + } + BaseResponse _response = DataImpl.excuteStore(procedureName, new Object[]{getUser().UserId, sepcVal}); + if (_response.isSuccess() && _response.getData() != null) { + tot[0] = ((List>>) _response.getData()).get(0).size(); + response.setData(((((List>>) _response.getData()).get(0)).stream().skip(getStartsize()).limit(getPageSize())).collect(Collectors.toList())); + } else { + response.setData(dbOperator.executeDataTable(querySql, getStartsize(), getPageSize(), tot)); + } + //转发转交获取的数据 + //response.setData(DataImpl.GetAuditRelayData(procedureName, module, querysql)); + } else { + // 执行查询并转换结果 + result = dbOperator.executeDataTable(querySql, getStartsize(), getPageSize(), tot); + } + + response.setData(toHashTable(result)); + response.setTot(tot[0] != 0 ? tot[0] : result.size()); + response.setSuccess(true); + response.setOther(module); + } catch (Exception e) { + response.setSuccess(false); + response.setMsg("查询失败:" + e.getCause().getMessage()); + } + return response; + } + + // 构建默认查询条件 + String defaultWhere = buildDefaultWhere(pms); +// out.println(defaultWhere + "defaultWhere"); + // 处理搜索条件 + querySql = reqSearchCondition(module.getCondKey(), querySql, (Hashtable) pms, (Hashtable) leftRecord, false); +// out.println("sql" + module.getCondKey()); + // 处理特殊参数替换 + if (!noPmsVal.isEmpty() && querySql.contains(noPmsVal)) { + querySql = querySql.replace("%" + noPmsVal, noPmsVal) + .replace(noPmsVal + "%", noPmsVal); + } + + // 处理查询SQL + querySql = dealQuerySql(querySql, record != null ? record : leftRecord, leftRecord, null, null, null, false, false, false); + querySql = HandleBmp(module, querySql); + + // 处理附件ID条件 + if (attcId > 0) { + List> attcModules = DataImpl.GetAttcModules("", attcId); + if (attcModules != null && !attcModules.isEmpty()) { + String cond = attcModules.get(0).get("unionCond").toString(); + if (cond != null && !cond.isEmpty()) { + cond = createControl.GetDefaultValue(cond, null, SystemTypeEnums.PmType.sql); + SqlAnalyzer sqlAnalyzer = new SqlAnalyzer(querySql); + Map whereMap = new HashMap<>(); + whereMap.put("$where", cond); + querySql = sqlAnalyzer.InsertWhere(whereMap, true, false, false); + } + } + } + + // 处理外部传入的WHERE条件 + if (whereClause != null && !whereClause.isEmpty()) { + SqlAnalyzer sqlAnalyzer = new SqlAnalyzer(querySql); + Map whereMap = new HashMap<>(); + whereMap.put("$where", whereClause); + querySql = sqlAnalyzer.InsertWhere(whereMap, true, false, false); + } + + // 处理默认WHERE条件 + if (defaultWhere != null && !defaultWhere.isEmpty()) { + SqlAnalyzer sqlAnalyzer = new SqlAnalyzer(querySql); + Map whereMap = new HashMap<>(); + whereMap.put("$where", defaultWhere); + querySql = sqlAnalyzer.InsertWhere(whereMap, true, false, false); + } + + // 处理颜色和样式列 + Map formats = null; // 可通过GetFormatColumns方法获取,此处保留null + Map fontColors = null; + List> styles = null; // Java中用List模拟DataTable + List> colorAndboxCols = DataImpl.GetColorAndBoxColumns(module.getModuleId()); + // 处理颜色和下拉框列数据 + if (colorAndboxCols != null && !colorAndboxCols.isEmpty()) { + // 过滤并转换树类型字段(对应C#的Linq查询) + Optional> treeRow = colorAndboxCols.stream() + .filter(row -> { + int fieldType = DataTableUtil.getIntValue(row, "fieldtype", 0); + return fieldType == SystemEnums.ControlType.LabTreeType.ordinal(); + }) + .findFirst(); + treeRow.ifPresent(row -> row.put("fieldtype", SystemEnums.ControlType.LabComboxValue.ordinal())); + } + // 构建下拉框列表(对应C#的ComboBox集合) + List boxs = null; + if (colorAndboxCols != null) { + boxs = colorAndboxCols.stream() + .filter(row -> { + String fieldsql = DataTableUtil.getStringValue(row, "fieldsql", ""); + return !fieldsql.isEmpty(); + }) + .map(row -> { + // 调用工具方法创建控件 + Object control = createControl.createControl(row, module, false); + // 检查是否为ComboBox类型 + if (control instanceof ComboBox) { + return (ComboBox) control; + } else { + // 处理类型不匹配的情况:可以返回null或记录日志 + // System.err.println("创建的控件不是ComboBox类型: " + control); + return null; + } + }) // 调用工具类创建控件 + .filter(box -> box != null && !box.getValueField().equals(box.getDisplayField())) + .collect(Collectors.toList()); + } + + // 处理表格模式下的字体颜色和样式 + if (grid) { + // 构建字体颜色映射(fieldname -> fontcolor) + fontColors = colorAndboxCols.stream() + .filter(row -> { + String fontColor = DataTableUtil.getStringValue(row, "fontcolor", ""); + return !fontColor.isEmpty(); + }) + .collect(Collectors.toMap( + row -> DataTableUtil.getStringValue(row, "fieldname", "").toLowerCase(), + row -> DataTableUtil.getStringValue(row, "fontcolor", "") + )); + + // 获取样式数据(模拟C#的GetStyles方法) + styles = GetStyles(module.getModuleId()); + } + Function, Map> fillStyles = null; + Function, Map> fillColor = null; +// 2026.2.24 + String[] picFields = colorAndboxCols.stream() + // 筛选:fieldtype 等于 LabPic 或 LabPicEx + .filter(row -> { + int fieldType = row.get("fieldtype") == null ? -1 : (int) row.get("fieldtype"); + return fieldType == SystemEnums.ControlType.LabPic.getValue() || fieldType == SystemEnums.ControlType.LabPicEx.getValue(); + }) + // 提取 fieldname 并转小写(空值兜底为"") + .map(row -> { + String fieldName = row.get("fieldname") == null ? "" : row.get("fieldname").toString(); + return fieldName.toLowerCase(); + }) + // 转为字符串数组 + .toArray(String[]::new); + + if (styles != null) { + List> finalStyles1 = styles; + fillStyles = row -> FillStyles(row, finalStyles1); + } + if (fontColors != null) { + Map finalfontColors = fontColors; + fillColor = row -> FillFontColor(row, finalfontColors); + } + + try { + if (multi) { + // 多数据集处理(模拟DataSet) + List>> dataSet = executeDataSet(querySql); + List tbs = new ArrayList<>(); + for (List> table : dataSet) { + // 转换为Hashtable列表(应用格式、颜色、样式) + List> hashList = toHashTable(table, formats, fillColor, fillStyles, true, true, false, (r, name, v) -> { + try { + return FillFilePath(picFields, name, v); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + }); + tbs.add(hashList); + } + response.setData(tbs); + response.setTot(tbs.size()); + + } else { + // 单表数据处理 +// out.println("executeDataTable:querySql:" + getStartsize() + getPageSize()); + List> dtVal = dbOperator.executeDataTable(querySql, getStartsize(), getPageSize(), tot); +// out.println("遍历键值对:"); +// for (Map.Entry entry : dtVal.get(0).entrySet()) { +// String key = entry.getKey(); +// Object value = entry.getValue(); +// out.println("键:" + key + ",值:" + value); +// } +// out.println("结束遍历键值对"); + // 设置模块主数据列信息 + if (dtVal != null) { + Map> columnTypes = new HashMap<>(); + if (dtVal.isEmpty()) { + columnTypes = getColumnTypes(dtVal, querySql); + module.MainDataColumns = (columnTypes.entrySet().stream() + .map(entry -> { + String columnName = entry.getKey(); + Class dataType = entry.getValue(); + // 转换为前端组件类型(即使字段值为null,仍按数据库定义的类型处理) + String xtype = PublicUtil.TypeToColumnType(dataType); + // 构建列信息对象(可替换为实际的DTO类) + Map columnInfo = new HashMap<>(); + columnInfo.put("dataIndex", columnName); + columnInfo.put("xtype", xtype); + return columnInfo; + }) + .collect(Collectors.toList())); + } else { + List> mainDataColumns = dtVal.get(0).keySet().stream() + .map(colName -> { + Map colInfo = new HashMap<>(); + colInfo.put("dataIndex", colName); + // 获取列的值 + Object value = dtVal.get(0).get(colName); + // 根据值的类型推断Class(注意处理null值) + Class valueType = value != null ? value.getClass() : Object.class; + log.debug(String.valueOf(valueType + " valueType " + value)); + // 传递正确的Class对象给工具方法 + colInfo.put("xtype", PublicUtil.TypeToColumnType(valueType)); + return colInfo; + }) + .collect(Collectors.toList()); + module.MainDataColumns = mainDataColumns; + } + } + + // 转换为Hashtable并应用样式 + List> tbs = toHashTable(dtVal, formats, fillColor, fillStyles, true, true, false, (r, name, v) -> { + try { + return FillFilePath(picFields, name, v); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + }); + // 转换下拉框值(对应C#的ConvertBoxVal) + response.setData(convertBoxVal(tbs, boxs, module.getModuleId())); + if (dtVal != null) { + response.setTot(tot[0] == 0 ? dtVal.size() : tot[0]); + } + } + + } catch (Exception e) { + response.setSuccess(false); + response.setMsg("查询失败:" + e.getMessage()); + } + response.setSuccess(true); + response.setOther(module); + return response; + } + + // 关键辅助方法:获取字段真实数据库类型(解决值为null时类型丢失问题) + private Map> getColumnTypes(List> resultList, String sql) { + Map> columnTypes = new HashMap<>(); + + // 针对SQL Server修改:使用TOP 0获取元数据,不返回实际数据 + String metaSql = RegexUtil.processDmServerSql(sql); +// System.out.println("sql,开始处理元数据" + metaSql); + try { + jdbcTemplate.query(metaSql, new ResultSetExtractor() { + @Override + public Void extractData(ResultSet rs) throws SQLException { +// System.out.println("进入ResultSetExtractor,开始处理元数据"); + + ResultSetMetaData metaData = rs.getMetaData(); + int columnCount = metaData.getColumnCount(); + +// System.out.println("发现 " + columnCount + " 个列"); + + for (int i = 1; i <= columnCount; i++) { + String columnName = metaData.getColumnName(i); + String columnLabel = metaData.getColumnLabel(i); + int sqlType = metaData.getColumnType(i); + String sqlTypeName = metaData.getColumnTypeName(i); + + // 使用System.out确保输出可见 +// System.out.println("列名: " + columnName + +// ", 别名: " + columnLabel + +// ", SQL类型: " + sqlType + +// " (" + sqlTypeName + ")"); + + Class javaType = getJavaTypeBySqlType(sqlType); + columnTypes.put(columnName, javaType); + } + return null; + } + }); + } catch (Exception e) { + // 捕获并打印所有异常 + log.warn(String.valueOf("获取元数据时发生错误: " + e.getMessage())); + log.error("Exception caught", e); + } + +// System.out.println("最终获取到的列类型数量: " + columnTypes.size()); + return columnTypes; + } + + // 映射JDBC类型到Java类型(确保时间类型被正确识别) + private Class getJavaTypeBySqlType(int sqlType) { + return switch (sqlType) { + case Types.DATE, Types.TIME, Types.TIMESTAMP, Types.TIMESTAMP_WITH_TIMEZONE -> + LocalDateTime.class; // 统一使用LocalDateTime处理时间类型 + case Types.INTEGER -> Integer.class; + case Types.BIGINT -> Long.class; + case Types.VARCHAR, Types.CHAR -> String.class; + case Types.NUMERIC, Types.DECIMAL -> BigDecimal.class; + case Types.BOOLEAN -> Boolean.class; + default -> Object.class; + }; + } + + /** + * 获取右键菜单按钮(重载方法) + * + * @param menuId 菜单ID + * @param record 记录参数 + * @return 右键菜单按钮对象 + */ + public SysPoPupMenuBtn GetContextMenuBtn(int menuId, Map record) { + return GetContextMenuBtn(menuId, record, null); + } + + /** + * 获取右键菜单按钮 + * + * @param menuId 菜单ID + * @param record 记录参数 + * @param leftRecord 左侧记录参数 + * @return 右键菜单按钮对象 + */ + public SysPoPupMenuBtn GetContextMenuBtn(int menuId, Map< + String, Object> record, Map leftRecord) { + SysPoPupMenuBtn btn = null; + List> menuRows = GetRightMenuRows("", 0, menuId); + + if (menuRows != null && !menuRows.isEmpty()) { + // 转换第一条记录为SysPoPupMenuBtn对象 + Optional btnOptional = menuRows.stream() + .map(row -> { + SysPoPupMenuBtn menuBtn = new SysPoPupMenuBtn(); + menuBtn.MenuBtnId = (menuId); + menuBtn.dllname = (row.get("library") != null ? row.get("library").toString() : ""); + menuBtn.setText(row.get("menucaption") != null ? row.get("menucaption").toString() : ""); + menuBtn.action = (row.get("action") != null ? row.get("action").toString() : ""); + menuBtn.actiontype = (row.get("actiontype") != null ? Integer.parseInt(row.get("actiontype").toString()) : 0); + menuBtn.dllpar1 = (row.get("param1") != null ? row.get("param1").toString() : ""); + menuBtn.dllpar2 = (row.get("param2") != null ? row.get("param2").toString() : ""); + menuBtn.dllpar3 = (row.get("param3") != null ? row.get("param3").toString() : ""); + menuBtn.dllpar4 = (row.get("param4") != null ? row.get("param4").toString() : ""); + menuBtn.dllpar5 = (row.get("param5") != null ? row.get("param5").toString() : ""); + menuBtn.dllpar6 = (row.get("param6") != null ? row.get("param6").toString() : ""); + menuBtn.dllpar7 = (row.get("param7") != null ? row.get("param7").toString() : ""); + menuBtn.dllpar8 = (row.get("param8") != null ? row.get("param8").toString() : ""); + menuBtn.dllpar9 = (row.get("param9") != null ? row.get("param9").toString() : ""); + menuBtn.dllpar10 = (row.get("param10") != null ? row.get("param10").toString() : ""); + menuBtn.comfirm = ("{comfirm}"); + menuBtn.selectConfirmFlag = ("{selectconfirmflag}"); + menuBtn.nextSelectStepCode = ("{nextselectstepcode}"); + menuBtn.nextSelectStepOper = ("{nextselectstepoper}"); + menuBtn.remark = ("{remark}"); + menuBtn.comfirmOpers = ("{comfirmopers}"); + menuBtn.maxWindow = (row.get("maxwindow") != null && Boolean.parseBoolean(row.get("maxwindow").toString())); + menuBtn.showMode = (row.get("showMode") != null ? Integer.parseInt(row.get("showMode").toString()) : 0); + return menuBtn; + }) + .findFirst(); + + btn = btnOptional.get(); + } + + return DealContextMenuBtn(btn, record, leftRecord); + } + + /** + * 处理右键菜单按钮参数 + * + * @param btn 右键菜单按钮对象 + * @param record 记录参数 + * @param leftRecord 左侧记录参数 + * @return 处理后的右键菜单按钮对象 + */ + private SysPoPupMenuBtn DealContextMenuBtn(SysPoPupMenuBtn + btn, Map record, Map leftRecord) { + if (btn != null) { + SystemTypeEnums.PmType pmType = SystemTypeEnums.PmType.sql; + + // 根据动作类型确定参数类型 + switch (btn.actiontype) { + case 0: + pmType = SystemTypeEnums.PmType.sql; + break; + case 1: + pmType = SystemTypeEnums.PmType.store; + break; + case 2: + case 3: + case 4: + pmType = SystemTypeEnums.PmType.program; + break; + } + + // 处理动态参数 + btn.dllname = createControl.GetDefaultValue(Objects.toString(btn.dllname, ""), record, leftRecord, SystemTypeEnums.PmType.store); + + // 处理动作参数 +// btn.action = (pmType == SystemTypeEnums.PmType.store) +// ? btn.action +// : createControl.GetDefaultValue(btn.action, record, leftRecord, pmType.equals(SystemTypeEnums.PmType.store) ? SystemTypeEnums.PmType.ignorenull : pmType); + btn.action = (pmType == SystemTypeEnums.PmType.store) + ? btn.action + : createControl.GetDefaultValue( + btn.action, + record, + leftRecord, + pmType // 直接使用pmType,因为此时pmType一定不是store + ); + // 处理其他参数 + btn.dllpar1 = (createControl.GetDefaultValue(btn.dllpar1, record, leftRecord, pmType)); + btn.dllpar2 = (createControl.GetDefaultValue(btn.dllpar2, record, leftRecord, pmType)); + btn.dllpar3 = createControl.GetDefaultValue(btn.dllpar3, record, leftRecord, pmType); + btn.dllpar4 = createControl.GetDefaultValue(btn.dllpar4, record, leftRecord, pmType); + btn.dllpar5 = createControl.GetDefaultValue(btn.dllpar5, record, leftRecord, pmType); + btn.dllpar6 = createControl.GetDefaultValue(btn.dllpar6, record, leftRecord, pmType); + btn.dllpar7 = createControl.GetDefaultValue(btn.dllpar7, record, leftRecord, pmType); + btn.dllpar8 = createControl.GetDefaultValue(btn.dllpar8, record, leftRecord, pmType); + btn.dllpar9 = createControl.GetDefaultValue(btn.dllpar9, record, leftRecord, pmType); + btn.dllpar10 = createControl.GetDefaultValue(btn.dllpar10, record, leftRecord, pmType); + + btn.comfirm = (createControl.GetDefaultValue(btn.comfirm, record, leftRecord, pmType)); + btn.selectConfirmFlag = createControl.GetDefaultValue(btn.selectConfirmFlag, record, leftRecord, pmType); + btn.nextSelectStepCode = createControl.GetDefaultValue(btn.nextSelectStepCode, record, leftRecord, pmType); + btn.nextSelectStepOper = createControl.GetDefaultValue(btn.nextSelectStepOper, record, leftRecord, pmType); + btn.comfirmOpers = createControl.GetDefaultValue(btn.comfirmOpers, record, leftRecord, pmType); + btn.remark = createControl.GetDefaultValue(btn.remark, record, leftRecord, pmType); + + // 处理参数字符串 + if (btn.getPmStr() != null && !btn.getPmStr().isEmpty()) { + String pmSql = PublicUtil.ReqSqlPms( + record, + leftRecord, + Trim(btn.getPmStr()).replaceAll("^#", ""), + SystemTypeEnums.PmType.sql, + getUser() + ); + + try { + List> result = jdbcTemplate.queryForList(pmSql); + if (result != null && !result.isEmpty()) { + btn.Pms = toHashTable(result.get(0)); + btn.dllpar6 = ""; + } + } catch (Exception e) { + // 忽略异常 + } + } + } + + return btn; + } + + /** + * 构建查询匹配字符串 + */ + public String buildDefaultWhere(Map pms) { + String defaultWhere = ""; + // 检查参数是否符合条件:非空、包含指定键且数量为4 + if (pms != null && pms.size() == 4 + && pms.containsKey("_colpm") + && pms.containsKey("_colpmoper") + && pms.containsKey("_colpmval") + && pms.containsKey("_fid")) { + + // 提取参数值并转为字符串 + String name = pms.get("_colpm").toString(); + String oper = pms.get("_colpmoper").toString(); + String val = pms.get("_colpmval").toString(); + int fid = ToInt32(pms.get("_fid").toString()); + + // 清空pms引用(与原逻辑保持一致) + pms = null; + + // 检查操作符是否合法 + List validOpers = Arrays.asList(">", "<", "=", ">=", "<=", "<>", "in", "like"); + if (!name.isEmpty() && !val.isEmpty() && validOpers.contains(oper)) { + // SQL注入防护:过滤危险关键字 + Pattern sqlInjectPattern = Pattern.compile( + "select|insert|delete|from|count\\(|drop table|update|truncate|asc\\(|mid\\(|char\\(|xp_cmdshell|exec master|netlocalgroup administrators|:|net user|\"|or|and", + Pattern.CASE_INSENSITIVE + ); + val = sqlInjectPattern.matcher(val).replaceAll("").replace("'", ""); + + if (fid > 0) { + // 处理字段ID存在的情况 + List> fieldTab = GetColumnRows(null, fid); + if (fieldTab != null && !fieldTab.isEmpty()) { + Map fieldRow = fieldTab.get(0); + ComboBox field = new ComboBox(fieldRow, createControl); + String querySql = field.getDataSource(); + // 构建子查询作为值 + val = String.format( + "(select %s from (%s) a)", + field.getValueField(), + dealQuerySql(querySql, "", "", "", field.getDisplayField(), val, !"like".equals(oper), false, false) + ); + oper = "in"; + } + } else { + // 处理不同操作符的值格式化 + switch (oper) { + case "in": + val = String.format("('%s')", val.replace(",", "','")); + break; + case "like": + // 若值中不含%,自动添加前后% + val = val.contains("%") ? val : String.format("'%s'", "%" + val + "%"); + break; + default: + val = String.format("'%s'", val); + break; + } + } + + // 构建最终的where条件 + defaultWhere = String.format("and %s %s %s", name, oper, val); + } + } + return defaultWhere; + } + + + public String DealQuerySql(String querySql, String _record, String _leftRecord, String _pams, String + keyField, String keyValue, boolean readOnly, boolean convertStr, boolean doNotSpelling) { + Map record = null, leftRecord = null, pms = null; +// IPublicUtil util = new IPublicUtil(); + if (!isNullOrEmpty(_record)) { + record = (Map) JSON.Decode(_record); + } + if (!isNullOrEmpty(_leftRecord)) { + leftRecord = (Map) JSON.Decode(_leftRecord); + } + if (!isNullOrEmpty(_pams)) { + pms = (Map) JSON.Decode(_pams); + } + return createControl.dealQuerySql(querySql, record, leftRecord, pms, keyField, keyValue, false, readOnly, convertStr, doNotSpelling); + } + + // 重载DealQuerySql + public String DealQuerySql(String querySql, String _record, String _leftRecord, String _pams, String + keyField, String keyValue) { + boolean readOnly = false; + boolean convertStr = false; + boolean doNotSpelling = false; + return DealQuerySql(querySql, _record, _leftRecord, _pams, keyField, keyValue, readOnly, convertStr, doNotSpelling); + } + + public String DealQuerySql(String + querySql, Map record, Map leftRecord, Map pams, String + keyField, String keyValue, boolean precise, boolean readOnly, boolean convertStr) { +// IPublicUtil util = new IPublicUtil(); + return createControl.dealQuerySql(querySql, record, leftRecord, pams, keyField, keyValue, precise, readOnly, convertStr); + } + + + /** + * 处理图片,在基础档案查询的时候,将二进制的图片字段去掉,并将*去掉 + * + * @param module 模块实体 + * @param sql SQL语句 + * @return 处理后的SQL语句 + */ + public String HandleBmp(BaseModule module, String sql) { + String notBmpSql = sql.toLowerCase(); + // 获取图片字段列表(使用List>替代DataTable) + List> bmpFieldList = DataImpl.GetBaesModuleBmpFields(module.getModuleId(), module.getMasterTable()); + String[] bmpFields = bmpFieldList.stream() + .map(row -> row.get("fieldname").toString().toLowerCase()) + .toArray(String[]::new); + + SqlAnalyzer analyzer = new SqlAnalyzer(sql); + analyzer.DoAnalyzeSelPms(); // 解析查询参数 + + if (bmpFields.length > 0) { + // 处理查询字段包含*的情况 + if (analyzer.QueryPart.indexOf("*") > -1) { + List hasNames = new ArrayList<>(); + // 获取表信息(使用List>替代DataTable) + List> tableInfoList = DataImpl.GetTableInfo(module.getMasterTable()); + String[] tableInfos = tableInfoList.stream() + .map(row -> row.get("name").toString().toLowerCase()) + .toArray(String[]::new); + + // 获取列名信息(使用List>替代DataTable) + List> columnNameList = DataImpl.GetColumnNames(module.getModuleId()); + String[] dtColumns = columnNameList.stream() + .map(row -> row.get("fieldname").toString().toLowerCase()) + .toArray(String[]::new); + + // 筛选符合条件的字段(排除图片字段和内部参数) + for (String name : tableInfos) { + boolean isInDtColumns = Arrays.asList(dtColumns).contains(name); + boolean isInInnerPms = analyzer.InnerPms.contains(name); + boolean isInAliasPms = analyzer.AliasPms.contains(name); + boolean isInBmpFields = Arrays.asList(bmpFields).contains(name); + + if (isInDtColumns && !isInInnerPms && !isInAliasPms && !isInBmpFields) { + hasNames.add(name); + } + } + + // 替换*为筛选后的字段列表 + if (!hasNames.isEmpty()) { + String queryPart = analyzer.QueryPart.replace("*", String.join(",", hasNames)); + notBmpSql = notBmpSql.replace(analyzer.QueryPart, queryPart); + } + } + // 处理明确指定字段的情况(移除图片字段) + else { + for (String bmpName : bmpFields) { + if (analyzer.InnerPms.contains(bmpName)) { + // 标准化SQL格式(处理换行和空格) + notBmpSql = notBmpSql.replaceAll("\r\n", " "); + notBmpSql = notBmpSql.replaceAll(",\\s+", ","); + notBmpSql = notBmpSql.replaceAll("\\s+,", ","); + + // 移除出现在不同位置的图片字段 + // 1. 出现在第一个位置 + notBmpSql = notBmpSql.replaceAll( + String.format("select\\s+%s\\s*,", bmpName), + "select " + ); + // 2. 出现在中间位置 + notBmpSql = notBmpSql.replaceAll( + String.format(",\\s+%s\\s*,", bmpName), + "," + ); + // 3. 出现在末尾位置(紧邻from前) + notBmpSql = notBmpSql.replaceAll( + String.format(",\\s+%s\\s+from", bmpName), + " from" + ); + } + } + } + } + return notBmpSql; + } + + /** + * 执行sql语句返回 C#中的DataSe + */ + private List>> executeDataSet(String querySql) { + List>> dataSets = new ArrayList<>(); + // 提升变量作用域到方法内,解决无法访问问题 + final List>[] currentDataSet = new List[]{null}; + final ResultSet[] currentResultSet = {null}; + final ResultSetMetaData[] currentMetaData = {null}; + final int[] columnCount = {0}; + + jdbcTemplate.query(querySql, new RowCallbackHandler() { + @Override + public void processRow(ResultSet rs) throws SQLException { + // 使用外部方法的变量(需声明为final或有效final) + ResultSet nonFinalCurrentResultSet = rs; + // 首次处理或切换到新结果集时初始化 + if (currentResultSet[0] == null || !rs.equals(currentResultSet[0])) { + // 保存上一个结果集(如果存在) + if (currentDataSet[0] != null) { + dataSets.add(currentDataSet[0]); + } + // 初始化新结果集 + currentResultSet[0] = rs; + currentDataSet[0] = new ArrayList<>(); + currentMetaData[0] = rs.getMetaData(); + columnCount[0] = currentMetaData[0].getColumnCount(); + } + + // 处理当前行数据 + Map rowMap = new HashMap<>(); + for (int i = 1; i <= columnCount[0]; i++) { + String columnName = currentMetaData[0].getColumnName(i); + // 处理重复列名 + if (rowMap.containsKey(columnName)) { + columnName = columnName + "_" + i; + } + rowMap.put(columnName, rs.getObject(i)); + } + currentDataSet[0].add(rowMap); + } + }); + + // 添加最后一个结果集(此时currentDataSet在方法作用域内可访问) + if (currentDataSet[0] != null && !currentDataSet[0].isEmpty()) { + dataSets.add(currentDataSet[0]); + } + + return dataSets; + } + + /** + * 根据行数据处理字段颜色 + * + * @param row 数据行 + * @param colors 颜色映射表 + * @return 处理后的颜色哈希表 + */ + protected Hashtable FillFontColor(Map row, Map colors) { + if (colors == null || colors.isEmpty()) { + return null; + } + Hashtable colorTab = new Hashtable<>(); + for (Map.Entry entry : colors.entrySet()) { + String key = entry.getKey(); + String colorValue = entry.getValue(); + // 调用CreateControl工具类的getDefaultValue方法处理颜色值 + String processedValue = createControl.GetDefaultValue(colorValue, row, null, SystemTypeEnums.PmType.sql); + colorTab.put(key, processedValue); + } + return colorTab; + } + + /** + * 转换下拉框数据 + * + * @param datas 数据列表 + * @param boxs 下拉框控件列表 + * @param moduleId 模块ID + * @return 转换后的哈希表列表 + */ + protected List> convertBoxVal(List> datas, + List boxs, + String moduleId) { + // 边界条件判断 + if (boxs == null || boxs.isEmpty() || datas == null || datas.isEmpty()) { + return datas; + } + + // 存储所有下拉框的键值对映射(控件名称 -> (值 -> 显示文本)) + Map> allDict = new HashMap<>(); + + for (ComboBox box : boxs) { + String boxName = box.getName(); + // 提取所有非空的字段值(处理多选情况) + List vals = datas.stream() + .filter(data -> data.containsKey(boxName)) + .map(data -> { + Object value = data.get(boxName); + String strVal = value != null ? value.toString() : ""; + // 多选情况下替换逗号为%2C + return box.multiSelect ? strVal : strVal.replace(",", "%2C"); + }) + .filter(str -> !str.isEmpty()) + .distinct() // 去重,替代RemoveSameObj() + .collect(Collectors.toList()); + + if (vals.isEmpty()) { + continue; + } + + // 获取下拉框数据并构建键值对映射 + Map kvs = new HashMap<>(); + kvs = getBoxDatas(kvs, datas, box, vals, moduleId); + allDict.put(boxName, kvs); + } + + // 处理每条数据,添加显示文本字段 + for (Map data : datas) { + for (Map.Entry> entry : allDict.entrySet()) { + String boxName = entry.getKey(); + Map valueMap = entry.getValue(); + + if (!data.containsKey(boxName)) { + continue; + } + + // 获取原始值并处理显示字段名称 + Object valueObj = data.get(boxName); + String idVal = valueObj != null ? valueObj.toString() : ""; + String displayName = String.format("@%s_%s", boxName, idVal.replace(",", "_")); + + // 处理多值情况(含逗号分隔) + if (idVal.contains(",")) { + StringBuilder displayVals = new StringBuilder(); + for (String val : idVal.split(",")) { + if (valueMap.containsKey(val)) { + displayVals.append(valueMap.get(val)).append(","); + } + } + // 移除末尾逗号 + if (displayVals.length() > 0) { + displayVals.setLength(displayVals.length() - 1); + } + data.put(displayName, displayVals.toString()); + } + // 单值情况 + else if (valueMap.containsKey(idVal)) { + data.put(displayName, valueMap.get(idVal)); + } else { + data.put(displayName, ""); + } + } + } + + return datas; + } + + + /** + * 获取下拉框数据并填充键值对映射(处理大数据量分批查询) + * + * @param kvs 用于存储键值对的映射(引用传递) + * @param datas 原始数据列表 + * @param box 下拉框控件 + * @param vals 需要查询的值列表 + * @param moduleId 模块ID + */ + private Map getBoxDatas(Map kvs, + List> datas, + ComboBox box, + List vals, + String moduleId) { + String sql = box.getDataSource(); + int maxSize = 1000; + + // 处理大数据量,分批查询 + if (vals.size() > maxSize) { + while (vals.size() > maxSize) { + List firstList = vals.subList(0, maxSize); + vals = vals.subList(maxSize, vals.size()); + kvs = getBoxDatas(kvs, datas, box, firstList, moduleId); + } + } + + // 处理当前批次的值 + String strVals = String.join(",", vals.stream() + .map(val -> val != null ? val.toString() : "") + .collect(Collectors.toList())); + + boolean prise = sql.indexOf("P_BaseMixInfoTab") < 0; + sql = dealQuerySql(sql, datas.get(0), null, null, box.getValueField(), strVals, prise, prise, false); + sql = sql.replace("{loginid}", DbOperator.escapeSqlParam(getUser().UserId)); + try { + // 执行查询获取下拉框数据源 + List> dtVal = jdbcTemplate.queryForList(sql); + for (Map row : dtVal) { + String key = DataTableUtil.getStringValue(row, box.getValueField(), ""); + String val = DataTableUtil.getStringValue(row, box.getDisplayField(), ""); + if (!kvs.containsKey(key) && !key.isEmpty()) { + kvs.put(key, val); + } + } + } catch (Exception e) { + String errMsg = e.getMessage() != null ? e.getMessage() : ""; + // 处理数字转换错误的情况 + if (errMsg.contains("转换成数据类型 int 时失败")) { + // 清洗非数字字符 + strVals = strVals.replaceAll("[^\\d\\.\\-\\'\\,]", "0"); + sql = dealQuerySql(sql, datas.get(0), null, null, box.getValueField(), strVals, true, true, false); + + try { + // 记录错误日志并重新尝试查询 + log.debug(String.valueOf(String.format("加载数据时解析字典数据出错%s=>%s:%s,已尝试处理并重新解析!", + moduleId, box.getName(), sql))); + + List> dtVal = jdbcTemplate.queryForList(sql); + for (Map row : dtVal) { + String key = DataTableUtil.getStringValue(row, box.getValueField(), ""); + String val = DataTableUtil.getStringValue(row, box.getDisplayField(), ""); + if (!key.isEmpty() && !kvs.containsKey(key)) { + kvs.put(key, val); + } + } + } catch (Exception ex) { + log.debug(String.valueOf(String.format("再次尝试加载数据时解析字典数据出错%s=>%s:%s,请检查", + moduleId, box.getName(), sql))); + } + } else { + log.debug(String.valueOf(String.format("加载数据时解析字典数据出错%s=>%s:%s,请检查", + moduleId, box.getName(), sql))); + } + } + return kvs; + } + + /** + * 获取模块详情数据 + * 校验参数:ModuleId, detailId + */ + public BaseResponse GetModuleDetailData() { + // 获取请求参数 + String detailIdStr = Request("detailId", ""), pms = Request("qrpms", Request("pms")); + int detailId = Integer.parseInt(detailIdStr); + String leftRecord = Request("leftRecord", pms); + + // 处理pms参数,拼接查询条件 + if (pms != null && !pms.trim().replaceAll("[{}]", "").trim().isEmpty()) { + leftRecord = leftRecord != null ? leftRecord.trim() : ""; + String trimmedLeftRecord = leftRecord.replaceAll("[{}]", "").trim(); + + if (trimmedLeftRecord.isEmpty()) { + leftRecord = "{\"$isPms\":1}"; + } else { + // 确保JSON格式正确 + if (leftRecord.endsWith("}")) { + leftRecord = leftRecord.substring(0, leftRecord.length() - 1) + ",\"$isPms\":1}"; + } else { + leftRecord += ",\"$isPms\":1}"; + } + } + } + + + // 解析布尔参数 + boolean isAuditAttc = Boolean.parseBoolean(Request("isAttc", "")); + boolean multi = Boolean.parseBoolean(Request("multi", "")); + boolean grid = Boolean.parseBoolean(Request("grid", "")); + String moduleId = Request("moduleId", ""); + // 调用模块实现类获取详情数据 + BaseResponse response = GetModuleDetailData(moduleId, detailId, leftRecord, multi, grid, isAuditAttc, pms); + + return response; + } + + /** + * 获取模块详情数据 + * + * @param moduleId 模块标识符 + * @param detailId 明细标识符 + * @param idOrPRow 基础模块标识符或父行数据(格式如{a:b}) + * @param multi 是否多数据集 + * @param grid 是否表格模式 + * @param isAuditAttc 是否为审核附件模式 + * @return BaseResponse 响应结果 + */ + public BaseResponse GetModuleDetailData(String moduleId, int detailId, String idOrPRow, + boolean multi, boolean grid, boolean isAuditAttc, String pms) { + // 获取基础模块配置 + BaseModule module = GetBaseModule(moduleId, getMenuId()); + // 获取明细模块配置 + BaseDetailModule dModule = GetBaseDetailModuel(detailId, isAuditAttc); + // 调用重载方法获取详情数据 + BaseResponse theRes = GetModuleDetailData(module, dModule, idOrPRow, multi, grid, isAuditAttc, pms); + + // 处理未找到明细SQL的提示信息 + if ("noDetailSql".equals(theRes.getMsg())) { + theRes.setMsg("未找到模块编号【" + moduleId + "】下明细编号【" + detailId + "】的明细数据SQL,请检查相应模块的配置是否正确!"); + } + + return theRes; + } + + /** + * 获取基础明细明细模块配置 + * + * @param id 明细模块ID + * @param isAuditAttc 是否为审核附件模式 + * @return 明细模块实体(BaseDetailModuleEntity)或null + */ + public BaseDetailModule GetBaseDetailModuel(int id, boolean isAuditAttc) { + // 根据审核模式获取明细数据 + List> detailData; + if (isAuditAttc) { + // 审核附件模式:调用获取审核信息明细的方法 + detailData = DataImpl.GetAuditInfoDetails("", "", id); + } else { + // 普通模式:调用获取基础明细模块的方法 + detailData = DataImpl.GetBaseDetailModuel(null, String.valueOf(id)); + } + + // 处理查询结果 + if (detailData != null && !detailData.isEmpty()) { + // 取第一条数据创建明细模块实体 + BaseDetailModule module = new BaseDetailModule(detailData.get(0)); + // 非审核模式下设置ID字段 + if (!isAuditAttc) { + // 调用DataImpl获取模块ID字段信息(通过DTO接收多返回值) + ModuleIdFieldDTO mo = DataImpl.GetModuleIdField(module.getUnionKey(), module.getModuleId(), module.getMasterTable()); + module.setIdField(mo.getParmaryKey()); + } + + return module; + } + + // 无数据时返回null + return null; + } + + public BaseDetailModule GetBaseDetailModuel(int id) { + return GetBaseDetailModuel(id, false); + } + + /** + * 获取模块明细数据 + * + * @param module 基础模块实体 + * @param dModule 明细模块实体 + * @param idOrPRow ID或行数据JSON字符串 + * @param multi 是否多数据集 + * @param grid 是否表格模式 + * @param isAuditAttc 是否为审核附件模式 + * @return 基础响应对象 + */ + public BaseResponse GetModuleDetailData( + BaseModule module, + BaseDetailModule dModule, + String idOrPRow, + boolean multi, + boolean grid, + boolean isAuditAttc, + String _pams) { + + BaseResponse response = new BaseResponse(); + if (module == null || dModule == null) { + return response; + } + + BaseModule oldModule = module; + // 如果模块ID匹配联合菜单代码,重新获取基础模块 + if (module.getModuleId().equals(dModule.getUnionMenuCode())) { + module = GetBaseModule(dModule.getUnionKey(), getMenuId()); + } + + // 确定明细SQL和相关参数 + String detailSql = isAuditAttc ? module.getMasterSql() : dModule.getUnionSql(); + String dformkey = dModule.getFromkey(); + String dmoduleId = dModule.getModuleId(); + boolean isMaster = false; + String masterCondKey = ""; + Map leftRecord = null, pms = null; + + if (idOrPRow.startsWith("{")) { + leftRecord = (Map) JSON.Decode(idOrPRow); +// leftRecord = toLowerColumnName(leftRecord); + } + if (!isNullOrEmpty(_pams)) { + pms = (Map) JSON.Decode(_pams); + if (leftRecord == null) leftRecord = pms; + else { + for (Map.Entry entry : pms.entrySet()) { + leftRecord.put(entry.getKey(), entry.getValue()); + } + } + } + // 处理联合模块逻辑 + if (dModule.getUnionMenuCode() != null && !dModule.getUnionMenuCode().isEmpty()) { +// 断点获取数据库ssql + BaseModule unionModule = GetBaseModule(dModule.getUnionMenuCode(), getMenuId()); + if (unionModule == null || unionModule.getMasterSql() == null || unionModule.getMasterSql().isEmpty()) { + return response; + } + // 断点查询数据库sql + detailSql = unionModule.getMasterSql(); + dformkey = unionModule.getModuleId(); + dmoduleId = unionModule.getModuleId(); + isMaster = true; + masterCondKey = unionModule.getCondKey(); + } + + // 校验明细SQL是否存在 + if (detailSql == null || detailSql.isEmpty()) { + response.setMsg("noDetailSql"); + return response; + } + + // 非报表模块且左侧记录为空时,从主表查询 + if (leftRecord == null && !module.isIsReport()) { + if (module.getMasterTable() != null && !module.getMasterTable().isEmpty()) { + String mainSql = dealQuerySql(module.getMasterSql(), "", null, null, module.getIdField(), idOrPRow, false, false, false); + try { + List> result = toHashTable(dbOperator.executeDataTable(mainSql, 0, 1, tot)); + if (!result.isEmpty()) { + leftRecord = result.get(0); + } + } catch (Exception e) { + // 异常处理:可根据需要添加日志记录 + } + } + } + + // 确保leftRecord不为null + leftRecord = leftRecord != null ? leftRecord : new HashMap<>(); + + // 定义空字符串变量,对应C#的 string idWhereKey = ""; + String idWhereKey = ""; + +// 这里是反起得,应该是 unionparentfield=unionfield||unionvalue; cs错了,就将错就错 +// 核心逻辑转换:拼接空字符串 + 移除首尾的{} + idWhereKey = (Objects.toString(dModule.getUnionField(), "")).replaceAll("^[{]|[}]$", ""); + + // 处理搜索条件和SQL + if (isMaster && (leftRecord != null && leftRecord.containsKey("$isPms"))) { + detailSql = reqSearchCondition(masterCondKey, detailSql, leftRecord, leftRecord, false); + } + detailSql = dealQuerySql(detailSql, leftRecord, leftRecord, null, null, null, false, false, false); +// out.println(detailSql + " // 处理搜索条件和SQL的detailsql"); + // 处理关联条件 + if (!isNullOrEmpty(idWhereKey)) { + String idWhereValKey = (isNullOrEmpty(dModule.getUnionParentField())) + ? module.getIdField() + : dModule.getUnionParentField().trim().replaceAll("^[{]|[}]$", ""); + + // 检查字段是否存在,处理配置错误 + if (!leftRecord.containsKey(idWhereValKey.toLowerCase())) { + if (leftRecord.containsKey(idWhereKey)) { + // 交换键值 + String temp = idWhereKey; + idWhereKey = idWhereValKey; + idWhereValKey = temp; + } else { + response.setSuccess(true); + response.setMsg(String.format("配置错误:未在主表中找到关联字段%s,请检查配置是否正确!", idWhereValKey)); + return response; + } + } + + // 构建WHERE条件并插入SQL + Object value1 = leftRecord.get(idWhereValKey.toLowerCase()); + // 修复方案1:添加格式校验,非法格式时返回默认值或处理逻辑 + Object value; + try { + value = (int) Double.parseDouble(value1.toString()); + } catch (NumberFormatException e) { + // 处理非数字格式的情况,例如: + value = value1.toString(); // 或其他默认值 + // 或记录日志 +// err.printf("字段值 {%s} 无法转换为数字,使用默认值-字符串\n", value1); + } +// value = (int) Double.parseDouble(value1.toString()); + String where = String.format("and %s='%s'", idWhereKey, value); + SqlAnalyzer sqlAnalyzer = new SqlAnalyzer(detailSql); + Map whereMap = new HashMap<>(); + whereMap.put(idWhereKey, where); + detailSql = sqlAnalyzer.InsertWhere(whereMap, true, false, false); +// out.println(detailSql + " // 构建WHERE条件并插入SQL的detailsql" + value); + } + + String detailIdStr = Request("detailId", ""), moduleIdStr = Request("moduleId", ""); + // 处理联合条件 + if (dModule.getUnionCond() != null && !dModule.getUnionCond().isEmpty()) { + String where = PublicUtil.ReqSqlPms(leftRecord, leftRecord, dModule.getUnionCond(), SystemTypeEnums.PmType.sql, getUser()); + SqlAnalyzer sqlAnalyzer = new SqlAnalyzer(detailSql); + Map whereMap = new HashMap<>(); + whereMap.put(idWhereKey, where); + detailSql = sqlAnalyzer.InsertWhere(whereMap, true, false, false); +// out.println(detailSql + "// 处理联合条件的detailsql"); + } + + // 处理格式化、颜色和下拉框 + Map formats = null; + Map fontColors = null; + List> styles = null; + String[] picFields; + List boxs = new ArrayList<>(); + + if (dmoduleId != null && !dmoduleId.isEmpty()) { + List> colorAndboxCols = DataImpl.GetColorAndBoxColumns(dmoduleId); + + // 处理下拉框 + BaseModule finalModule = module; + boxs = colorAndboxCols.stream() + .filter(row -> row.get("fieldsql") != null && !row.get("fieldsql").toString().isEmpty()) + .map(row -> createControl.createControl(row, finalModule, false)) // 先获取原始控件 + .filter(Objects::nonNull) // 过滤空值 + .filter(control -> control instanceof ComboBox) // 只保留ComboBox类型 + .map(control -> (ComboBox) control) + .filter(box -> box != null && !box.getValueField().equals(box.getDisplayField())) + .collect(Collectors.toList()); + + // 处理表格颜色 + if (grid) { + // 转换colorAndboxCols为字体颜色字典(fieldname为键,fontcolor为值) + fontColors = colorAndboxCols.stream() + .filter(row -> { + Object fontColor = row.get("fontcolor"); + return fontColor != null && !fontColor.toString().isEmpty(); + }) + .collect(Collectors.toMap( + row -> { + Object fieldName = row.get("fieldname"); + return fieldName != null ? fieldName.toString().toLowerCase() : ""; + }, + row -> row.get("fontcolor").toString() + )); + + // 调用获取字体颜色列的方法(假设dmoduleId是当前类的属性或方法参数) + //fontColors = GetFontColorColumns(dmoduleId); + picFields = colorAndboxCols.stream() + .filter(row -> { + int fieldType = Integer.parseInt(row.get("fieldtype").toString()); + return fieldType == SystemEnums.ControlType.LabPic.getValue() + || fieldType == SystemEnums.ControlType.LabPicEx.getValue(); + }) + .map(row -> { + String fieldName = (String) row.getOrDefault("fieldname", ""); + return fieldName.toLowerCase(); + }) + .toArray(String[]::new); + } else { + picFields = new String[0]; + } + } else { + picFields = new String[0]; + } + // 获取样式 + styles = GetStyles(dformkey); + Function, Map> fillStyles = null; + Function, Map> fillColor = null; + if (styles != null) { + List> finalStyles1 = styles; + fillStyles = row -> FillStyles(row, finalStyles1); + } + if (fontColors != null) { + Map finalfontColors = fontColors; + fillColor = row -> FillFontColor(row, finalfontColors); + } + // 处理多数据集 + if (multi) { + List>> dataSets = executeDataSet(detailSql); + List tbs = new ArrayList<>(); + + for (List> dataSet : dataSets) { + //resultList.add(toHashTable(dataSet, formats, fillColor, fillStyles, true)); + tbs.add(toHashTable(dataSet, + formats, // 格式参数(对应C#的formats) + // 三元表达式1:判断fontColors为空则传null,否则传fillColor + (fontColors == null || fontColors.isEmpty()) ? null : fillColor, + // 三元表达式2:判断styles为空/无行则传null,否则传fillStyles + (styles == null || styles == null || styles.isEmpty()) ? null : fillStyles, + true, // 布尔参数1 + true, // 布尔参数2 + false, // 布尔参数3 + // lambda回调:对应C#的 (r, name, v) => FillFilePath(picFields, name, v) + (r, name, v) -> { + try { + return fillFilePath(picFields, name, v); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + )); + } + + response.setData(tbs); + response.setTot(tbs.size()); + } else { + // 处理图表模式和分页 + boolean isChart = dModule.getUnionType() == 1; + int displayRows = dModule.getDisPlayRows() != null ? dModule.getDisPlayRows() : 0; + int queryStart = isChart ? 0 : getStartsize(); + int querySize = isChart ? -1 : (displayRows == 0 ? getPageSize() : displayRows); + + // 执行查询 + List> dtVal = dbOperator.executeDataTable(detailSql, queryStart, querySize, tot); +// out.println(dtVal + "dataVal"); + if (dtVal != null && !dtVal.isEmpty()) { + Map> columnTypes = getColumnTypes(dtVal, detailSql); + module.MainDataColumns = (columnTypes.entrySet().stream() + .map(entry -> { + String columnName = entry.getKey(); + Class dataType = entry.getValue(); + + // 转换为前端组件类型(即使字段值为null,仍按数据库定义的类型处理) + String xtype = PublicUtil.TypeToColumnType(dataType); + + // 构建列信息对象(可替换为实际的DTO类) + Map columnInfo = new HashMap<>(); + columnInfo.put("dataIndex", columnName); + columnInfo.put("xtype", xtype); + return columnInfo; + }) + .collect(Collectors.toList())); + // 设置主数据列信息 +// module.MainDataColumns = dtVal.get(0).keySet().stream() +// .map(columnName -> { +// Map columnInfo = new HashMap<>(); +// columnInfo.put("dataIndex", columnName); +// columnInfo.put("xtype", PublicUtil.TypeToColumnType((Class) getColumnType(dtVal, columnName))); +// return columnInfo; +// }) +// .collect(Collectors.toList()); + +// out.println(module.MainDataColumns + "MainDataColumns"); + // 转换数据并处理格式、颜色和样式 + List> tbs = toHashTable(dtVal, formats, (fontColors == null || fontColors.isEmpty()) ? null : fillColor, styles == null || styles.isEmpty() ? null : fillStyles, true, true, false, + (r, name, v) -> + { + try { + return fillFilePath(picFields, name, v); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + }); + response.setData(convertBoxVal(tbs, boxs, module.getModuleId() + (isNullOrEmpty(dmoduleId) ? "" : "=>" + dmoduleId))); + response.setTot(tot[0] != 0 ? tot[0] : dtVal.size()); + } + } + // 设置响应结果 + response.setSuccess(true); + response.setOther(oldModule); + response.setSql(detailSql); + return response; + } + + /** + * 获取配置了字体颜色的列,主要用在添加修改界面 + * + * @param moduleId 模块ID + * @return 字段名与字体颜色的映射关系 + * 2026.2.24注销 + */ +// public Map GetFontColorColumns(String moduleId) { +// // 调用DataImpl获取字体颜色配置列(假设存在对应的方法) +// List> colorColumns = DataImpl.GetFontColorColumns(moduleId); +// +// // 转换为字段名(小写)到字体颜色的映射 +// return colorColumns.stream() +// .collect(Collectors.toMap( +// // 键:fieldname转换为小写,空值处理为"" +// row -> { +// Object fieldNameObj = row.get("fieldname"); +// String fieldName = fieldNameObj != null ? fieldNameObj.toString() : ""; +// return fieldName.toLowerCase(); +// }, +// // 值:fontcolor,空值处理为"" +// row -> { +// Object fontColorObj = row.get("fontcolor"); +// return fontColorObj != null ? fontColorObj.toString() : ""; +// } +// )); +// } + + /** + * /// + * /// Gets the condition fields. + * /// + * /// The module identifier + * /// The detail identifier + * /// BaseResponse + * /// + */ + public BaseResponse GetCondition(String moduleId, String detailId) { + BaseResponse response = new BaseResponse(); + String formKey = ""; + if (!isNullOrEmpty(detailId)) { + BaseDetailModule module = GetBaseDetailModuel(parseInt(detailId)); + if (module != null) { + formKey = module.getFromkey() + "_Cond"; + } + } else { + BaseModule module = GetBaseModule(moduleId, getMenuId()); + formKey = module.getCondKey(); + } + if (isNullOrEmpty(formKey)) return response; + response.setData(GetCondition(formKey)); + response.setSuccess(true); + return response; + + } + + /** + * 获取字段列组件列表 + * + * @param moduleCode 模块编码 + * @param fieldId 字段ID + * @return 组件列表 + */ + public List GetFieldColumns(String moduleCode, String fieldId) { + List> dtVal = GetFieldColumnRows(moduleCode, fieldId); + return ToColumns(dtVal, false); + } + + /** + * 获取字段列配置数据(重载方法) + * + * @param moduleCode 模块编码 + * @param fieldId 字段ID + * @param id 额外ID(默认0) + * @return 字段列配置列表(Map集合表示的行数据) + */ + public List> GetFieldColumnRows(String moduleCode, String fieldId, int id) { + // 调用数据实现层方法,传入当前用户信息 + return DataImpl.GetFieldColumnRows(moduleCode, fieldId, getUser().UserId, getUser().UserName, id); + } + + // 提供默认id参数的重载 + public List> GetFieldColumnRows(String moduleCode, String fieldId) { + return GetFieldColumnRows(moduleCode, fieldId, 0); + } + + /** + * 获取基础模块的所有明细 + * + * @param module 基础模块实体 + * @return 组件列表(List) + * 版本:1.0.0.0 + */ + public Object GetModelDetails(BaseModule module) { + List tabs = new ArrayList<>(); + + // 获取基础模块明细配置数据 + List> dtVal = DataImpl.GetBaesModuleDetails(module.getFromkey(), getUser().UserId, getUser().UserName); + for (Map row : dtVal) { + Component oneCmp = GetOneModelDetail(row, module, null, false); + if (oneCmp != null) { + tabs.add(oneCmp); + } + } + return tabs; + } + + /** + * 获取一个模板的具体数据 + */ + public Component GetOneModelDetail(Map row, ModuleBaseEntity + module, Hashtable elseValueHS, boolean isAttc) { + // 初始化elseValueHS(处理默认参数) + if (elseValueHS == null) { + elseValueHS = new Hashtable<>(); + } + + // 从数据行提取基础属性 + String detailId = DataTableUtil.getStringValue(row, "id", ""); + String detailsql = DataTableUtil.getStringValue(row, "detailsql", "").trim(); + int detailType = ToInt32(get(row, "detailtype", 0)); + String formkey = DataTableUtil.getStringValue(row, "fromkey", ""); + String tabTitle = DataTableUtil.getStringValue(row, "detailname", ""); + boolean autoRefresh = DataTableUtil.getBooleanValue(row, "refresh", false); + String unionmodule = DataTableUtil.getStringValue(row, "unionmodule", "").trim(); + String unionfield = DataTableUtil.getStringValue(row, "unionfield", ""); + String library = DataTableUtil.getStringValue(row, "library", ""); + int displayMode = DataTableUtil.getIntValue(row, "displaymode", 0); + int addShowMode = DataTableUtil.getIntValue(row, "addshowmode", 0); + int addVisible = DataTableUtil.getIntValue(row, "addvisible", 0); + boolean noGridLine = DataTableUtil.getBooleanValue(row, "nogridline", false); + boolean noRownumber = DataTableUtil.getBooleanValue(row, "norownumber", false); + boolean noColumnHeader = DataTableUtil.getBooleanValue(row, "hidecolumnheader", false); + boolean isDrag = DataTableUtil.getBooleanValue(row, "isdrag", false); + String unionparentfield = DataTableUtil.getStringValue(row, "unionparentfield", "").isEmpty() + ? module.getIdField() + : DataTableUtil.getStringValue(row, "unionparentfield", ""); + int displayRows = DataTableUtil.getIntValue(row, "displayrows", 0); + int orderid = DataTableUtil.getIntValue(row, "orderid", 0); + +// 2026.2.24 + boolean defaultActive = DataTableUtil.getBooleanValue(row, "defaultItem", false); + + // 处理条件参数(转换C#的PublicUtil方法) + String visibleCond = PublicUtil.ReqSqlPms(null, null, DataTableUtil.getStringValue(row, "visiblecond", ""), SystemTypeEnums.PmType.ignorenull, null); + String fieldCond = PublicUtil.ReqSqlPms(null, null, DataTableUtil.getStringValue(row, "fieldcond", ""), SystemTypeEnums.PmType.ignorenull, null); + String disableField = DataTableUtil.getStringValue(row, "disablefield", ""); + String fieldCond1 = PublicUtil.ReqSqlPms(null, null, DataTableUtil.getStringValue(row, "fieldcond1", ""), SystemTypeEnums.PmType.ignorenull, null); + String disableField1 = DataTableUtil.getStringValue(row, "disablefield1", ""); + String fieldCond2 = PublicUtil.ReqSqlPms(null, null, DataTableUtil.getStringValue(row, "fieldcond2", ""), SystemTypeEnums.PmType.ignorenull, null); + String disableField2 = DataTableUtil.getStringValue(row, "disablefield2", ""); + String bandHeight = DataTableUtil.getStringValue(row, "bandheight", ""); + String bandWidth = DataTableUtil.getStringValue(row, "bandwidth", ""); + +// out.println("GetOneModelDetail" + "拿去数据没有问题"); + Object mobileCards = null; + boolean[] isUrls = new boolean[1]; + boolean isUrl = false; + // 处理library参数(URL转换逻辑) + if (!library.isEmpty()) { + // 转换模块名称并判断是否为URL + String convertedLibrary = SystemMenu.convertToModuleName(library, 0, isUrls); + isUrl = isUrls[0]; + if (isUrl) { + Map pmInfos = PublicUtil.UrlToDllInfo(convertedLibrary); + if (pmInfos != null) { + library = pmInfos.get("xtype"); + unionmodule = pmInfos.get("moduleid"); + // 填充elseValueHS + for (Map.Entry entry : pmInfos.entrySet()) { + elseValueHS.put(entry.getKey(), entry.getValue()); + } + } + } else { + library = convertedLibrary; + } + } +// out.println("GetOneModelDetail" + "IsCard 0" + ((BaseModule) module).IsCard); + // 处理卡片组信息(如果是BaseModule且为卡片模式) + if (module instanceof BaseModule && ((BaseModule) module).IsCard) { + mobileCards = GetModuleCardGroup(module.getModuleId(), false, true, Integer.parseInt(detailId), false); + } +// out.println("GetOneModelDetail" + "IsCard"); + // 填充elseValueHS通用参数 + if (!elseValueHS.containsKey("fieldCond")) { + // 构建条件对象(使用Map模拟匿名类) + Map fieldCondObj = new HashMap<>(); + fieldCondObj.put("displayCond", visibleCond); + + List> fieldConds = new ArrayList<>(); + fieldConds.add(Map.of("cond", fieldCond, "fields", disableField)); + fieldConds.add(Map.of("cond", fieldCond1, "fields", disableField1)); + fieldConds.add(Map.of("cond", fieldCond2, "fields", disableField2)); + fieldCondObj.put("fieldConds", fieldConds); + + elseValueHS.put("fieldCond", fieldCondObj); + } + +// 2026.2.24 + if (!elseValueHS.containsKey("defaultActive")) elseValueHS.put("defaultActive", defaultActive); + + // 添加基础参数到elseValueHS + if (!elseValueHS.containsKey("detailId")) elseValueHS.put("detailId", detailId); + if (!elseValueHS.containsKey("orderId")) elseValueHS.put("orderId", orderid); + if (!elseValueHS.containsKey("displayMode")) elseValueHS.put("displayMode", displayMode); + if (!elseValueHS.containsKey("addShowMode")) elseValueHS.put("addShowMode", addShowMode); + if (!elseValueHS.containsKey("addVisible")) elseValueHS.put("addVisible", addVisible); + if (!elseValueHS.containsKey("bandHeight") && !bandHeight.isEmpty()) + elseValueHS.put("bandHeight", bandHeight); + if (!elseValueHS.containsKey("bandWidth") && !bandWidth.isEmpty()) + elseValueHS.put("bandWidth", bandWidth); + if (!elseValueHS.containsKey("dType")) elseValueHS.put("dType", detailType); + if (!elseValueHS.containsKey("isDrag")) elseValueHS.put("isDrag", isDrag); + +// out.println(elseValueHS.toString() + "elseValueHS"); + // 获取列信息 + List columns = isAttc + ? ToColumns(DataImpl.GetAuditInfoDetailColumns(detailId), false) + : GetBaseDetailColumns(formkey); + + // 根据detailType处理不同组件类型 + if (detailType == 0 || detailType == 4) { + if (unionmodule.isEmpty()) { + // 网格面板(无关联模块) + elseValueHS.put("tabTitle", tabTitle); + elseValueHS.put("autoRefresh", autoRefresh); + elseValueHS.put("unionField", unionfield.replace("{", "").replace("}", "")); + elseValueHS.put("unionParentField", unionparentfield.replace("{", "").replace("}", "")); + + List bbItems = null; + GridPanel gridPanel = new GridPanel(); + gridPanel.setXtype(library.isEmpty() ? "PubModule.DetailGrid" : library); + gridPanel.title = (DataTableUtil.getStringValue(row, "detailname", "")); + gridPanel.RightMenu = (GetRightMenu(formkey, 0, (Ref) bbItems)); // bbItems使用引用传递 + gridPanel.setColumns(columns); + gridPanel.IdField = (columns.isEmpty() ? "" : ((Field) columns.get(0)).getName()); + gridPanel.NoGridLine = (noGridLine); + gridPanel.setRowNumberer(!noRownumber); + gridPanel.setHideColumnHeader(noColumnHeader); + + // 设置数据源 + DataStore store = new DataStore(); + store.setXtype("PubModule.Store.DetailGrid"); + Map extraParams = new HashMap<>(); + extraParams.put("DetailId", detailId); + extraParams.put("ModuleId", module.getModuleId()); + store.extraParams = (extraParams); + gridPanel.setStore(store); + + gridPanel.elseValue = (elseValueHS); + gridPanel.PageAble = (true); + gridPanel.MobileCards = (mobileCards); + gridPanel.displayRows = (displayRows); + + return gridPanel; + } else { + // 容器组件(有关联模块) + elseValueHS.put("autoRefresh", autoRefresh); + elseValueHS.put("isAttc", isAttc); + elseValueHS.put("unionField", unionfield.replace("{", "").replace("}", "")); + elseValueHS.put("unionParentField", unionparentfield.replace("{", "").replace("}", "")); + + MContainer container = new MContainer(); + container.mXtype = (library); + container.title = (tabTitle); + container.ModuleId = (unionmodule); + container.DetailId = (detailId); + container.ParentModuleId = (module.getModuleId()); + container.ElseValue = (elseValueHS); + container.MobileCards = (mobileCards); + container.QueryPms = (isUrl ? elseValueHS : null); + + return container; + } + } else if (detailType == 1) { + // 图表组件 + List> chartRows = null; + if (unionmodule.isEmpty()) { + chartRows = DataImpl.GetChartCfg(formkey); + } else { + BaseModule unionModule = GetBaseModule(unionmodule, ""); + chartRows = (unionModule != null) ? DataImpl.GetChartCfg(unionModule.getFromkey()) : null; + } + + // 转换图表配置 + List chartCfg = new ArrayList<>(); + if (chartRows != null) { + for (Map chartRow : chartRows) { + chartCfg.add(new Chart(chartRow)); + } + } + + // 填充参数 + elseValueHS.put("columns", columns); + elseValueHS.put("tabTitle", tabTitle); + elseValueHS.put("autoRefresh", autoRefresh); + + List bbItems = null; + ChartContainer chart = new ChartContainer(); + chart.title = (tabTitle); + chart.DetailId = (detailId); + chart.ModuleId = (module.getModuleId()); + chart.RightMenu = (GetRightMenu(formkey, 0, (Ref) bbItems)); + chart.elseValue = (elseValueHS); + chart.displayRows = (displayRows); + chart.ChartCfg = (chartCfg); + + if (!library.isEmpty()) { + chart.setXtype(library); + } + + return chart; + } else if (detailType == 2) { + // 地图组件 + elseValueHS.put("tabTitle", tabTitle); + elseValueHS.put("autoRefresh", autoRefresh); + + if (detailsql.contains(".html")) { + // IFrame面板(HTML链接) + String src = PublicUtil.ReqSqlPmsByRow(module.Updrow, null, detailsql, SystemTypeEnums.PmType.ignorenull, getUser()); + elseValueHS.put("src", src); + + Panel panel = new Panel(); + panel.setXtype("PubModule.IFramePanel"); + panel.title = (DataTableUtil.getStringValue(row, "detailname", "")); + panel.elseValue = (elseValueHS); + + return panel; + } else { + // 地图网格面板 + GridPanel gridPanel = new GridPanel(); + gridPanel.setXtype("map"); + gridPanel.title = (DataTableUtil.getStringValue(row, "detailname", "")); + + DataStore store = new DataStore(); + store.setXtype("PubModule.DetailGrid"); + Map extraParams = new HashMap<>(); + extraParams.put("DetailId", detailId); + extraParams.put("ModuleId", module.getModuleId()); + store.extraParams = (extraParams); + gridPanel.setStore(store); + + gridPanel.elseValue = (elseValueHS); + gridPanel.MobileCards = (mobileCards); + + return gridPanel; + } + } else if (detailType == 3) { + // 自定义组件 + elseValueHS.put("columns", columns); + + MContainer container = new MContainer(); + container.setXtype(library.isEmpty() ? "MContainer" : library); + container.title = (DataTableUtil.getStringValue(row, "detailname", "")); + container.DetailId = (detailId); + container.ModuleId = (module.getModuleId()); + container.ElseValue = (elseValueHS); + + return container; + } + + return null; + } + + /** + * 获取基础详情列组件列表 + * + * @param formkey 表单键 + * @return 组件列表 + */ + public List GetBaseDetailColumns(String formkey) { + List> dtval = GetBaseDetailColumnRows(formkey); + return ToColumns(dtval, false); + } + + /** + * 获取基础详情列数据行 + * + * @param fromkey 表单键 + * @return 列数据列表 + */ + public List> GetBaseDetailColumnRows(String fromkey) { + return DataImpl.GetBaseDetailColumnRows(fromkey, getUser().UserId, getUser().UserName, 0); + } + + + /** + * Api的getBillIniParams方法 + */ + @Override + public BaseResponse getBillIniParams() { + BaseResponse response = new BaseResponse(); + String moduleId = Request("ModuleId", ""), + menuId = Request("MenuId", ""); + BillModule module = GetBillModule(moduleId, menuId); + List theData = GetBillSourceModule(moduleId, module.getBillSourceIds(), "2"); + response.setData(theData); + response.setOther(module); + if (theData.isEmpty()) { + response.setSuccess(false); + String notFindModuleMsg = "未找到模块号【" + moduleId + "】的配置数据,请检查相应模块的配置是否正确!可能是多余的空格、回车,数字字母写错,模块暂未配置等情况!"; + response.setMsg(notFindModuleMsg); + } else { + response.setSuccess(true); + log.debug(String.format(("进入模块,%s进入%s模块"), getUser().UserName, module.getModuleName())); + } + return response; + } + + /** + * 从数据库元数据中查询字段是否为整数类型(结果集为空时使用) + */ + private boolean isIntegerColumnFromMetaData(String sql, String columnName) { + try { + // 解析SQL获取表名(简化逻辑,实际可能需要更复杂的SQL解析) + String tableName = extractTableNameFromSql(sql); + if (tableName == null || tableName.isEmpty()) { + return false; + } + + // 获取数据库连接和元数据 + Connection conn = jdbcTemplate.getDataSource().getConnection(); + DatabaseMetaData metaData = conn.getMetaData(); + + // 查询字段元数据(catalog和schema根据实际数据库配置调整,这里留空) + ResultSet rs = metaData.getColumns( + null, // catalog(数据库名,可留空) + null, // schema(模式名,如public,可留空) + tableName, // 表名 + columnName // 字段名 + ); + + if (rs.next()) { + int dataType = rs.getInt("DATA_TYPE"); // 获取JDBC类型码 + // 匹配整数类型的JDBC类型码:INTEGER(4)、BIGINT(-5)、SMALLINT(5)、TINYINT(-6) + return dataType == 4 || dataType == -5 || dataType == 5 || dataType == -6; + } + } catch (SQLException e) { + log.error("Exception caught", e); + } + return false; + } + + /** + * 从SQL中提取表名(简化版,需根据实际SQL格式调整) + * 适用于简单查询如:select * from table_name where ... + */ + private String extractTableNameFromSql(String sql) { + // 正则匹配表名(简化逻辑,复杂SQL需用SQL解析器) + Pattern pattern = Pattern.compile("from\\s+([\\w]+)", Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(sql); + if (matcher.find()) { + return matcher.group(1); + } + return null; + } + + /** + * 结果集为空时,从元数据中查找目标字段名(忽略大小写) + */ + private String getKeyColNameFromMetaData(String sql, String keyField) { + try { + String tableName = extractTableNameFromSql(sql); + if (tableName == null) { + return null; + } + + Connection conn = jdbcTemplate.getDataSource().getConnection(); + DatabaseMetaData metaData = conn.getMetaData(); + ResultSet rs = metaData.getColumns(null, null, tableName, null); // 查询所有字段 + + while (rs.next()) { + String colName = rs.getString("COLUMN_NAME"); + if (colName.toLowerCase().equals(keyField.toLowerCase())) { + return colName; + } + } + } catch (SQLException e) { + log.error("Exception caught", e); + } + return null; + } + + /** + * 检查数据库里的字段类型 + */ + // 核心逻辑实现 + public boolean checkIfIntColumn(String sql, String keyField) { + // 执行查询并获取结果集元数据(即使结果为空也能获取字段信息) + if (databaseType.equals("dm")) sql = RegexUtil.processDmServerSql(sql); + return jdbcTemplate.query(sql, rs -> { + ResultSetMetaData metaData = rs.getMetaData(); + int columnCount = metaData.getColumnCount(); + + // 遍历所有列,匹配目标字段(忽略大小写) + for (int i = 1; i <= columnCount; i++) { + String columnName = metaData.getColumnName(i); + if (columnName.toLowerCase().equals(keyField.toLowerCase())) { + // 获取字段的JDBC类型,判断是否为整数类型 + int columnType = metaData.getColumnType(i); + // 匹配整数类型的JDBC类型码:INTEGER(4)、BIGINT(-5)、SMALLINT(5)、TINYINT(-6) + return columnType == 4 || columnType == -5 || columnType == 5 || columnType == -6; + } + } + return false; // 未找到匹配字段 + }); + } + + /** + * Api的getBaseAuditStepData方法 + */ + + @Override + public BaseResponse getBaseAuditStepData() { + BaseResponse response = new BaseResponse(); + String record = Request("record", "") + "", + leftRecord = Request("leftRecord", ""), + pms = Request("pms", ""), + moduleId = Request("moduleId", ""); + int stepId = ToInt32(Request("stepId", "")); + + response = GetAuditStepData(moduleId, stepId, record, leftRecord, pms, true); + return response; + } + + /** + * 获取审批步骤、历史主表、历史明细表数据 + * + * @param moduleId 模块ID + * @param stepId 步骤标识:>0:审批步骤 0:主表 -1:明细 + * @param record 记录数据JSON字符串 + * @param leftRecord 左侧记录数据JSON字符串 + * @param pams 参数JSON字符串 + * @param isBase 是否为基础模块 + * @return BaseResponse 响应结果 + */ + public BaseResponse GetAuditStepData(String moduleId, int stepId, String record, + String leftRecord, String pams, boolean isBase) { + + BaseResponse response = new BaseResponse(); + ModuleBaseEntity module = null; + Map recordMap = new HashMap<>(); + Map leftRecordMap = new HashMap<>(); + Map pmsMap = new HashMap<>(); + String menuId = Request("menuId", ""); + // 获取模块信息 + if (isBase) { + module = GetBaseModule(moduleId, menuId); // 假设menuId为类中已定义的变量 + } + if (module == null) { + module = GetBillModule(moduleId, menuId); // 假设存在获取单据模块的方法 + isBase = false; + } + if (module == null) { + response.setMsg(LanguageUtil.GetString("InvalidCode")); + // 可添加详细错误信息 + return response; + } + + // 解析JSON参数 + if (StringUtils.hasText(record)) { + recordMap = (Map) JSON.Decode(record); // 假设使用Jackson或FastJSON + } + if (StringUtils.hasText(leftRecord)) { + leftRecordMap = (Map) JSON.Decode(leftRecord); + } + if (StringUtils.hasText(pams)) { + pmsMap = (Map) JSON.Decode(pams); + if (recordMap == null) { + recordMap = pmsMap; + } else if (pmsMap != null) { + recordMap.putAll(pmsMap); + } + } + + String querySql = ""; + + // 构建查询SQL + if (stepId > 0 && stepId != -999) { // 普通自定义审批步骤数据 + AuditStep detail = GetAuditStep(stepId, isBase, "", ""); // 假设存在获取审批步骤的方法 + if (detail != null) { + querySql = detail.getStepSql(); + } + } else if (stepId == -1) { // 单据来源明细数据 + String tableName = isBase ? "p_baseflowstep" : "wms_billflowstep"; + querySql = String.format("select stepCode, stepName, happenTime, operatorName, operTime, " + + "case when operDirection='F' then '审核' else '返退' end as operDirectionName, " + + "applyTime, operAdvice, " + + "case when autoStep=1 then '自动' else '' end as autoStep " + + "from %s a " + + "where a.flowSourceKey= {{parent.%s}} or a.flowSourceKey= {{parent.billdocument_id}} " + + "order by operTime", + tableName, module.getIdField()); + } else if (StringUtils.hasText(module.getOverBackSql())) { // 历史主表数据 + querySql = module.getOverBackSql(); + } else { // 历史主表默认查询 + String overBackCond = module.getOverBackCond(); + if (StringUtils.hasText(overBackCond)) { + overBackCond = "and " + PublicUtil.ReqSqlPms(recordMap, leftRecordMap, overBackCond, SystemTypeEnums.PmType.sql, getUser()); + } else { + overBackCond = ""; + } + stepId = -2; + + StringBuilder whereBuilder = new StringBuilder(); + if (pmsMap != null && !pmsMap.isEmpty()) { + for (Map.Entry entry : pmsMap.entrySet()) { + String key = entry.getKey().toLowerCase(); + Object value = entry.getValue(); + if (value == null || isNullOrEmpty(value.toString())) { + continue; + } + + switch (key) { + case "stepovername": + whereBuilder.append(String.format(" AND a.%s%s like '%%%s%%'", + module.getMenuPrefix(), key, value)); + break; + case "bdate": + whereBuilder.append(String.format(" AND a.%sStepOverTime >= '%s'", + module.getMenuPrefix(), value)); + break; + case "edate": + // 处理结束日期加一天 + try { + LocalDate endDate = LocalDate.parse(value.toString()); + endDate = endDate.plusDays(1); + whereBuilder.append(String.format(" AND a.%sStepOverTime <= '%s'", + module.getMenuPrefix(), endDate.toString())); + } catch (DateTimeParseException e) { + // 日期解析失败处理 + whereBuilder.append(String.format(" AND a.%sStepOverTime <= '%s'", + module.getMenuPrefix(), value)); + } + break; + case "billcode": + whereBuilder.append(String.format(" AND a.%s like '%%%s%%'", + module.getIdField(), value)); + break; + } + } + } + + // 构建主查询SQL + String flowOperTable = isBase ? "p_baseflowoper" : "wms_billflowoper"; + String systemTable = isBase ? "p_systemdlltab" : "p_systembilltype"; + String systemTableKey = isBase ? "DllCoid" : "typecode"; + + querySql = String.format("select a.%s, " + + "convert(varchar(25), a.%sOperatedate, 120) as %sOperateDate, " + + "dbo.P_GetOperator(a.%soperatorid) as %sOperatorName, " + + "a.%sstepCode, " + + "convert(varchar(25), a.%sAffirmDate, 120) as %sAffirmDate, " + + "dbo.P_GetOperator(a.%saffirmer) as %sAffirmerName, " + + "a.%sStepOverName, " + + "convert(varchar(25), a.%sStepOverTime, 120) as %sStepOverTime " + + "from %s a " + + "inner join %s w on w.keyvalue = a.%s " + + "and ((charindex(',%s,', ',' + browsers + ',') > 0 " + + "or charindex(',%s,', ',' + operators + ',') > 0) " + + "or (select count(1) from %s c where c.%s = '%s' " + + "and charindex(',%s,', ',' + c.OverBackOper + ',') > 0) > 0) " + + "where isnull(a.%sstepover, 0) = 1 %s %s", + module.getIdField(), + module.getMenuPrefix(), module.getMenuPrefix(), + module.getMenuPrefix(), module.getMenuPrefix(), + module.getMenuPrefix(), + module.getMenuPrefix(), module.getMenuPrefix(), + module.getMenuPrefix(), module.getMenuPrefix(), + module.getMenuPrefix(), + module.getMenuPrefix(), module.getMenuPrefix(), + module.getMasterTable(), + flowOperTable, module.getIdField(), + getUser().UserName, getUser().UserName, + systemTable, systemTableKey, moduleId, + getUser().UserName, + module.getMenuPrefix(), + whereBuilder, overBackCond); + } + // 处理查询条件 + if (StringUtils.hasText(querySql) && stepId != -2) { + querySql = PublicUtil.ReqSqlPms(recordMap, leftRecordMap, querySql, SystemTypeEnums.PmType.sql, getUser()); + if (StringUtils.hasText(module.getOverBackSql()) && stepId == -999) { + querySql = reqSearchCondition(module.getOverBackKey(), querySql, pmsMap, leftRecordMap, false); + } + } + // 执行查询 + if (StringUtils.hasText(querySql)) { +// int[] total = new int[1]; // 用于接收总数 + List> result = dbOperator.executeDataTable(querySql, getStartsize(), getPageSize(), tot); +// List> result = jdbcTemplate.queryForList(querySql); + response.setData(toHashTable(result)); + response.setTot(tot[0] != 0 ? tot[0] : result.size()); + } + response.setSuccess(true); + return response; + } + + /** + * 获取审批步骤明细 + * + * @param stepId 步骤ID + * @param isBase 是否为基础步骤 + * @param moduleId 模块ID(可选) + * @param stepCode 步骤编码(可选) + * @return 审批步骤对象,无数据时返回null + */ + protected AuditStep GetAuditStep(int stepId, boolean isBase, String moduleId, String stepCode) { + List> auditStepInfos = DataImpl.GetAuditStepInfos(moduleId, stepId, isBase, stepCode); + if (auditStepInfos != null && !auditStepInfos.isEmpty()) { + return new AuditStep(auditStepInfos.get(0)); + } + return null; + } + + @Override + public BaseResponse GetAuditIniParams() { + String moduleid = Request("ModuleId", "0"); + String menuid = Request("MenuId", "0"); + Boolean windowsDirver = toBoolean(Request("windowsDirver", "")); + Boolean isBase = toBoolean(Request("isBase", "1")); + BaseResponse response = new BaseResponse(); + ModuleBaseEntity module = null; + if (isBase) { + module = GetBaseModule(moduleid, menuid); +// out.println(module.getIdField()); + } + if (module == null) { + module = GetBillModule(moduleid, menuid); + isBase = false; + } + if (module == null) { + String baseMsg = LanguageUtil.GetString("InvalidCode"); + String fullMsg = baseMsg + " 未找到模块号【" + moduleid + "】以及MenuId【" + menuid + "】对应的(单据or基础模块)数据,请检查相应模块的配置是否正确!可能是多余的空格、回车,数字字母写错,模块暂未配置等情况!"; + response.setMsg(fullMsg); + response.setSuccess(false); // 建议补充设置成功状态为false + return response; + } + List> dataTable = DataImpl.GetAuditStepInfos(moduleid, 0, isBase, ""); + String idField = module.getIdField(); + if (dataTable != null && !dataTable.isEmpty()) { + Map>> grouped = dataTable.stream() + .collect(Collectors.groupingBy(row -> { + Object stepGroup = row.get("stepgroup"); + return stepGroup != null ? stepGroup.toString() : ""; // 确保键为字符串 + })); + ModuleBaseEntity finalModule = module; + Boolean finalIsBase = isBase; + + List tabs = new ArrayList<>(); + +// 处理每个分组 + for (Map.Entry>> entry : grouped.entrySet()) { + String groupKey = entry.getKey(); // 对应dict.Key + List> groupRows = entry.getValue(); // 对应dict.ToList() + + // 1. 构建当前分组的结果Map(模拟匿名对象) + Map tab = new HashMap<>(); + + // 2. 设置text + tab.put("text", groupKey); + + // 3. 设置count + int count = GetStepDataCount(groupRows, module, groupKey); + tab.put("count", count); + + // 4. 设置items(转换为GridPanel列表) + List items = new ArrayList<>(); + for (Map row : groupRows) { // 对应from row in dict.ToList() + GridPanel gridPanel = new GridPanel(); + + // 设置title:row["stepname"] + "" + Object stepname = row.get("stepname"); + gridPanel.title = stepname != null ? stepname.toString() : ""; + + // 设置IdField + gridPanel.IdField = idField; + + // 设置columns + Object rowId = row.get("id"); + Object stepcode = row.get("stepcode"); + gridPanel.setColumns( + GetAuditStepColumns( + moduleid, + rowId != null ? rowId.toString() : "", + stepcode != null ? stepcode.toString() : "", + isBase + ) + ); + + // 设置MobileCards:WindowsDirver?null: ... + if (windowsDirver) { + gridPanel.MobileCards = null; + } else { + gridPanel.MobileCards = GetAuditStepColumnCards(moduleid, stepcode != null ? stepcode.toString() : "", isBase + ); + } + + // 设置store及extraParams + DataStore store = new DataStore(); + Map extraParams = new HashMap<>(); // 模拟匿名对象 + extraParams.put("moduleId", moduleid); + extraParams.put("stepId", row.get("id")); + extraParams.put("stepCode", stepcode); + store.extraParams = extraParams; + gridPanel.setStore(store); + + items.add(gridPanel); + } + tab.put("items", items); + + // 5. 将当前分组的Map添加到tabs + tabs.add(tab); + } + // 创建Map模拟匿名对象 + Map completedTab = new HashMap<>(); + completedTab.put("text", "已完成单据"); + completedTab.put("count", GetStepDataCount(null, module, "已完成单据")); + completedTab.put("items", GetAuditHistoryInfo(module)); + tabs.add(completedTab); + + + response.setData(tabs); + response.setOther(module); + response.setSuccess(true); + } else { + response.setMsg(" 未找到模块号【" + moduleid + "】对应的审批步骤数据,请检查相应模块的配置是否正确!可能模块编号重复!"); + } + return response; + } + + /** + * Api的GetStepDataCounts方法 + */ + @Override + public BaseResponse GetStepDataCounts() { + String moduleId = Request("ModuleId", ""); + + BaseResponse response = new BaseResponse(); + response.setSuccess(true); + response.setData(GetStepDataCounts(moduleId)); + response.setMsg(""); + response.setSharToken(true); + return response; + } + + public Map GetStepDataCounts(String moduleId) { + // 获取基础模块信息,优先使用GetBaseModule,不存在则使用GetBillModule + String menuId = Request("MenuId", ""); + ModuleBaseEntity module = GetBaseModule(moduleId, menuId); + if (module == null) { + module = (ModuleBaseEntity) GetBillModule(moduleId, menuId); + } + + // 获取审核步骤信息数据列表(假设返回List>模拟DataTable) + List> dataList = DataImpl.GetAuditStepInfos(moduleId, 0, module instanceof BaseModule, ""); + Map retMap = null; + + if (dataList != null && !dataList.isEmpty()) { + // 按stepgroup分组,并转换为Map + ModuleBaseEntity finalModule = module; + retMap = dataList.stream() + // 分组:以stepgroup为键 + .collect(Collectors.groupingBy( + row -> String.valueOf(row.get("stepgroup")), + // 对每个分组计算数量 + Collectors.collectingAndThen( + Collectors.toList(), + groupList -> GetStepDataCount(groupList, finalModule, groupList.get(0).get("stepgroup").toString()) + ) + )); + } + + // 添加"已完成单据"的统计 + if (retMap != null) { + retMap.put("已完成单据", GetStepDataCount(null, module, "已完成单据")); + } + + return retMap; + } + + private int GetStepDataCount(List> rows, ModuleBaseEntity module, String groupName) { + int tot = 0; + if ("已完成单据".equals(groupName)) { + String sql = module.getOverBackSql(); + if (sql == null || sql.trim().isEmpty()) { + // 构建默认SQL查询 + String masterTable = module.getMasterTable(); + String menuPrefix = module.getMenuPrefix(); + String flowOperTable = module instanceof BaseModule ? "p_baseflowoper" : "wms_billflowoper"; + String idField = module.getIdField(); + String userName = getUser().UserName; // 假设从上下文获取用户名 + String moduleId = module.getModuleId(); + String systemTable = module instanceof BaseModule ? "p_systemdlltab" : "p_systembilltype"; + String systemTableField = module instanceof BaseModule ? "DllCoid" : "typecode"; + + sql = String.format( + "select count(1) t from %s a " + + "inner join %s w on w.keyvalue = a.%s " + + "and ((charindex(',%s,', ',' + browsers + ',') > 0 or charindex(',%s,', ',' + operators + ',') > 0) " + + "or (select count(1) from %s c where c.%s = '%s' and charindex(',%s,', ',' + c.OverBackOper + ',') > 0) > 0) " + + "where isnull(a.%sstepover, 0) = 1 ", + masterTable, flowOperTable, idField, + userName, userName, + systemTable, systemTableField, moduleId, userName, + menuPrefix + ); + + try { + // 执行SQL并获取结果 + Integer result = jdbcTemplate.queryForObject(sql, Integer.class); + tot += result != null ? result : 0; // 假设工具类转换为int + } catch (Exception e) { + log.warn(String.valueOf(e.getMessage())); // 假设存在error日志方法 + } + return tot; + } + + try { + // 处理自定义SQL:替换参数并构建计数查询 + String processedSql = PublicUtil.ReqSqlPms(null, null, sql, SystemTypeEnums.PmType.sql, getUser()); + SqlAnalyzer sqlAnalyzer = new SqlAnalyzer(processedSql); + String countSql = sqlAnalyzer.BuildCoutCmdText("count(1) tot", true); + Integer result = jdbcTemplate.queryForObject(countSql, Integer.class); + tot += result != null ? result : 0; + } catch (Exception e) { + // 忽略异常 + } + } else if (rows != null) { + for (Map row : rows) { + String sql = DataTableUtil.getStringValue(row, "stepsql", ""); + if (!sql.trim().isEmpty()) { + try { + // 处理步骤SQL:替换参数并构建计数查询 + String processedSql = PublicUtil.ReqSqlPms(null, null, sql, SystemTypeEnums.PmType.sql, getUser()); + SqlAnalyzer sqlAnalyzer = new SqlAnalyzer(processedSql); + String countSql = sqlAnalyzer.BuildCoutCmdText("count(1) tot", true); + String paramSql = countSql.replace("{loginname}", getUser().UserName); + // 使用参数化查询,第二个参数是替换的值 + Integer result = jdbcTemplate.queryForObject(paramSql, Integer.class); + tot += result != null ? result : 0; + } catch (Exception e) { + // 打印详细异常信息(关键:排查错误原因) + log.debug(String.valueOf("执行sql异常!SQL: " + ", 错误: " + e.getMessage())); + } + } + } + } + + return tot; + } + + /** + * api中 BaseResponse GetAddOrUpdFields()方法; + */ + @Override + public BaseResponse GetAddOrUpdFields() { + BaseResponse response = new BaseResponse(); + String idValue = Request("idValue", ""), + detailId = Request("detailId", ""), + contextMenuId = Objects.toString(Request("contextMenuId", ""), ""), + leftRecord = Request("leftRecord", ""); + boolean details = toBoolean(Request("details", "")), + tpl = toBoolean(Request("tpl", "")), + atts = toBoolean(Request("atts")); + response = GetAddOrUpdFields(Request("ModuleId", ""), detailId, idValue, leftRecord, ToInt32(contextMenuId), details, tpl, toBoolean(Request("isattc", "")), atts); + + return response; + } + + public BaseResponse GetAddOrUpdFields(String moduleId, String idValue, String _leftRecord) { + Hashtable leftRecord = null; + if (!isNullOrEmpty(_leftRecord)) { + leftRecord = (Hashtable) JSON.Decode(_leftRecord); + } + BaseModule module = GetBaseModule(moduleId, getMenuId()); + if (module == null) { + return new BaseResponse() { + { + setSuccess(false); + setMsg("未找到模块"); + } + }; + } + module.setLeftRecord(leftRecord); + return GetAddOrUpdFields(module, idValue, 0, "", false, false); + } + + /** + * /// + * /// + * /// + * /// + * /// + * /// + * /// + * /// 是否为审批附加模块 + * /// + */ + public BaseResponse GetAddOrUpdFields(String moduleId, String detailId, String idValue, String pIdOrPRow, + int contextMenuId, boolean details, boolean tpl, boolean isAttc, boolean atts) { + String menuId = Request("menuId", ""); + BaseModule module = GetBaseModule(moduleId, menuId);// 假设menuId为类中已定义的成员变量 + if (module == null) { + BaseResponse response = new BaseResponse(); + response.setMsg("未找到对应模块!"); + return response; + } + if (atts) { + module.AttCfgs = GetAttachModuleCfgs(moduleId); + } + if (detailId != null && !detailId.isEmpty()) { + int detailIdInt = Integer.parseInt(detailId); + module.DetailModule = (GetBaseDetailModuel(detailIdInt, isAttc)); + if (module.DetailModule == null && !isAttc) { + module.DetailModule = (GetBaseDetailModuel(detailIdInt, true)); + } + } + + if (pIdOrPRow != null && pIdOrPRow.startsWith("{")) { + try { + // 假设使用Jackson进行JSON解析 + Map leftRecord = (Map) JSON.Decode(pIdOrPRow, Map.class); + module.setLeftRecord(leftRecord); + } catch (Exception e) { + // 处理JSON解析异常 + log.error("Exception caught", e); + } + } + + if (module.getLeftRecord() == null && (idValue == null || idValue.isEmpty())) { + BaseDetailModule detailModule = module.DetailModule; + if (detailModule != null) { + BaseModule parentModule = GetBaseModule(detailModule.getUnionKey(), menuId); + if (parentModule != null) { + // 获取添加或更新数据,假设返回的data是Map类型 + BaseResponse addOrUpdResponse = GetAddOrUpdData(parentModule.getModuleId(), "", "", ""); + module.setLeftRecord((Map) addOrUpdResponse.getData()); + + module.ParentModule = (parentModule); + + // 设置关联父字段,若为空则使用父模块的IdField + String unionParentField = detailModule.getUnionParentField(); + if (unionParentField == null || unionParentField.isEmpty()) { + detailModule.setUnionParentField(parentModule.getIdField()); + } + } + } + } + return GetAddOrUpdFields(module, idValue, contextMenuId, "", details, tpl); + } + + /** + * 基础档案增删改逻辑 + */ + public BaseResponse GetAddOrUpdData(String moduleId, String detailId, String idValue, String pIdOrPRow, + int contextMenuId, boolean isAttc) { + // 调用获取字段信息的方法,对应C#中的GetAddOrUpdFields + BaseResponse response = GetAddOrUpdFields(moduleId, detailId, idValue, pIdOrPRow, contextMenuId, false, false, isAttc, false); + + if (response.isSuccess()) { + // 将返回数据转换为字段列表 + List fields = (List) response.getData(); + if (fields != null) { + // 过滤出值不为空的字段,转换为Map + Map dataMap = fields.stream() + .filter(field -> field.getValue() != null && !field.getValue().toString().isEmpty()) + .collect(Collectors.toMap(Field::getName, Field::getValue)); + + response.setData(dataMap); + // 可选:清空其他数据 + // response.setOther(null); + } + } + + return response; + } + + // 可选的方法重载(处理默认参数contextMenuId=0) + public BaseResponse GetAddOrUpdData(String moduleId, String detailId, String idValue, String pIdOrPRow) { + return GetAddOrUpdData(moduleId, detailId, idValue, pIdOrPRow, 0, false); + } + + + public BaseResponse GetAddOrUpdFields(ModuleBaseEntity module, String idValue, int contextMenuId, String sql, + boolean details, boolean tpl) { + BaseResponse response = new BaseResponse(); + if (module == null) { + return response; + } + + // 处理模板逻辑 + if (tpl) { + boolean isBaseModule = module instanceof BaseModule; + module.CusAddTpl = (DataImpl.GetModuleAddTpl(module.getModuleId(), isBaseModule)); + } + + // 处理更新数据逻辑 + if (idValue != null && !idValue.isEmpty()) { + module.IdValue = idValue; + response = SetUpdateRow(module, sql, idValue); + if (!response.isSuccess()) { + return response; + } + } + + // 处理右键菜单按钮逻辑 + SysPoPupMenuBtn btn = null; + if (contextMenuId > 0) { + btn = GetContextMenuBtn(contextMenuId, module.getLeftRecord()); + // 尝试从旧数据库操作器获取按钮配置(!!!!!以后注意更改) +// if (btn == null && getOldDbOperator() != null) { +// //右键可能是读取原账套的配置 +// DataImpl _oldDataImpl = DataImpl; +// DataImpl = new DataImpl() {{ +// dbOperator = getOldDbOperator(); +// }}; +// btn = GetContextMenuBtn(contextMenuId, module.getLeftRecord()); +// DataImpl = _oldDataImpl; +// } + if (btn == null) { + DataImpl oldDataImpl = DataImpl; + DataImpl = new DataImpl(dbOperator); + btn = GetContextMenuBtn(contextMenuId, module.getLeftRecord()); + DataImpl = oldDataImpl; // 恢复原始dataImpl + } + + if (btn != null) { + // 处理弹出参数 + Map popPms = btn.getPopPms(); + module.PopPms = (popPms); + + // 合并左侧记录 + Map leftRecord = module.getLeftRecord(); + if (leftRecord == null) { + popPms = toLowerColumnName(popPms); + module.setLeftRecord(popPms); + } else { + for (Map.Entry entry : popPms.entrySet()) { + leftRecord.put(entry.getKey().toLowerCase(), entry.getValue()); + } + leftRecord = toLowerColumnName(leftRecord); + module.setLeftRecord(leftRecord); + } + } + } + + // 获取列配置并创建控件 + List> dtVal = GetColumnRows(module.getModuleId()); +// out.println(dtVal.size() + " GetColumnRows:dtVal的数量"); + List cols = createControl.createControl(dtVal, module); + List bbItems = null; + module.rightMenus = GetRightMenu(module.getModuleId(), 0, new Ref(bbItems), "toolClick"); + + Field mapField = cols.stream() + .filter(field -> "maplocationfield".equals(field.getXtype())) + .findFirst() + .orElse(null); + if (mapField != null) { + Field mapOtherField = cols.stream() + .filter(field -> (mapField.getName() + "_longitude").equals(field.getName())) + .findFirst() + .orElse(null); + if (mapOtherField != null) { + mapOtherField.disabled = (true); // 设置disabled为true(JavaBean setter) + } + +// 2. 查找名称为 mapField.name + "_latitude" 的字段并禁用 + mapOtherField = cols.stream() + .filter(field -> (mapField.getName() + "_latitude").equals(field.getName())) + .findFirst() + .orElse(null); + if (mapOtherField != null) { + mapOtherField.disabled = (true); + } + +// 3. 查找名称为 mapField.name + "_itude" 的字段并禁用 + mapOtherField = cols.stream() + .filter(field -> (mapField.getName() + "_itude").equals(field.getName())) + .findFirst() + .orElse(null); + if (mapOtherField != null) { + mapOtherField.disabled = (true); + } + + } +// 核心逻辑:对应C#的if (cols != null && module.LeftRecord != null) + if (cols != null && module.getLeftRecord() != null) { + String unionFiled, unionParentFiled = ""; + + // 分支1:按钮有UnionField/UnionValue时的处理 + if (btn != null && + (!isNullOrEmpty(btn.getUnionField()) || !isNullOrEmpty(btn.getUnionValue()))) { + unionFiled = ""; + String targetFieldName = (btn.getUnionField() + "").trim().replace("{", "").replace("}", "").toLowerCase(); + // 查找匹配的字段(等价于FirstOrDefault) + Field field = cols.stream() + .filter(col -> targetFieldName.equals(col.getName().toLowerCase())) // 统一转小写避免大小写问题 + .findFirst() + .orElse(null); + // 设置默认值 + if (field != null) { + field.setDefaultval(btn.getUnionValue()); + } + } + // 分支2:模块有DetailModule且UnionField非空时的处理 + else if (module.DetailModule != null && !isNullOrEmpty(module.DetailModule.getUnionField())) { + // 格式化关联字段名(去大括号、转小写) + unionFiled = module.DetailModule.getUnionField().trim().replace("{", "").replace("}", "").toLowerCase(); + + // 处理关联父字段 + if (module.DetailModule.getUnionParentField() != null) { + unionParentFiled = module.DetailModule.getUnionParentField().trim().replace("{", "").replace("}", "").toLowerCase(); + } + + // 如果UnionParentField为空,补充父模块信息 + if (isNullOrEmpty(module.DetailModule.getUnionParentField())) { + // 父模块为空则获取基础模块(等价于C#的?? 空合并) + if (module.ParentModule == null) { + module.ParentModule = (GetBaseModule(module.DetailModule.getUnionKey(), getMenuId())); + } + // 赋值父模块ID字段 + unionParentFiled = (module.ParentModule == null) ? "" : module.ParentModule.getIdField(); + } + + // 查找匹配的字段并设置默认值 + Field field = cols.stream() + .filter(col -> unionFiled.equals(col.getName().toLowerCase())) + .findFirst() + .orElse(null); + // 设置默认值(module.LeftRecord[unionParentFiled] 假设是Map/对象取值) + if (field != null) { + // 注意:这里假设 module.getLeftRecord() 是 Map 类型 + // 如果是自定义对象,需替换为 module.getLeftRecord().getXXX(unionParentFiled) + field.setDefaultval(module.getLeftRecord().get(unionParentFiled)); + } + } + } + // 处理详情逻辑 + if (details && module != null && module instanceof BaseModule) { + ((BaseModule) module).Details = (GetModelDetails((BaseModule) module)); + } + + // 设置响应数据 + response.setData(cols); + response.setOther(module); + response.setSuccess(true); +// out.println("GetAddOrFields.response"); + if (!isNullOrEmpty(idValue)) { +// getEventHandler().callOnModuleDetail(module, response); + } + return response; + } + + + // 辅助方法:禁用指定名称的字段 + private void disableField(List cols, String fieldName) { + cols.stream() + .filter(field -> fieldName.equals(field.getName())) + .findFirst() + .ifPresent(field -> field.disabled = (true)); + } + + private BaseResponse SetUpdateRow(ModuleBaseEntity module, String sql, String idValue) { + BaseResponse response = new BaseResponse(); + Map updrow = null; // Java中用Map替代DataRow + if (module == null) { + return response; + } + + String dataSql = "", noValSql = ""; + Map record = null; + + // 处理BaseModule类型 + if (module instanceof BaseModule) { + BaseModule baseModule = (BaseModule) module; + if (!isNullOrEmpty(baseModule.getMasterSql()) && !isNullOrEmpty(idValue)) { + record = new HashMap<>(); + record.put(module.getIdField(), idValue); + dataSql = dealQuerySql( + isNullOrEmpty(sql) ? baseModule.getMasterSql() : sql, + record, + module.getLeftRecord(), + null, + module.getIdField(), + idValue, + true, false, false + ); + } + } + // 处理BillModule类型 + else if (module instanceof BillModule) { + BillModule billModule = (BillModule) module; + if (isNullOrEmpty(idValue) && module.PopBtn != null) { + dataSql = module.PopBtn.dllpar8; + } else if (!isNullOrEmpty(billModule.getMasterSql()) && !isNullOrEmpty(idValue)) { + record = new HashMap<>(); + record.put(module.getIdField(), idValue); + dataSql = dealQuerySql( + isNullOrEmpty(sql) ? billModule.getMasterSql() : sql, + record, + module.getLeftRecord(), + null, + module.getIdField(), + idValue, + true, false, false + ); + } + } + + // 构建noValSql + if (!isNullOrEmpty(module.getMasterTable()) && !isNullOrEmpty(idValue)) { + // 使用参数化SQL避免注入风险,这里先拼接(实际应使用占位符) + noValSql = String.format("select top 1 * from %s where %s = ?", + module.getMasterTable(), module.getIdField()); + } + + // 校验SQL是否为空 + if (isNullOrEmpty(dataSql) && isNullOrEmpty(noValSql)) { + response.setMsg(String.format(LanguageUtil.WrongMainCfg, "MasterTable Or MasterSql")); + return response; + } + + // 确定最终执行的SQL + dataSql = isNullOrEmpty(dataSql) ? noValSql : dataSql; + + // 执行查询获取数据行 + try { + // 执行SQL查询,获取结果集(这里假设dbOperator有对应的查询方法) + if (databaseType.equals("dm")) dataSql = RegexUtil.processDmServerSql(dataSql); + List> resultList = jdbcTemplate.queryForList(dataSql); // 传递参数避免SQL注入 + if (resultList != null && !resultList.isEmpty()) { + updrow = resultList.get(0); // 获取第一行数据 + // 转换列名为小写(模拟ToLowerColumnName) + updrow = toLowerColumnName(updrow); + } + } catch (Exception e) { + // 异常处理(根据实际需求添加日志) +// this.Error($"根据主表sql获取模块[{module.ModuleId}][{idValue}]行数据出错!", e); + log.error("Exception caught", e); + } + + // 当主查询失败时尝试备用SQL + if ((updrow == null || (updrow != null && updrow.size() > 1)) + && !dataSql.equals(noValSql) + && !isNullOrEmpty(noValSql)) { + try { + List> backupList = jdbcTemplate.queryForList(noValSql, idValue); // 传递参数 + if (backupList != null && !backupList.isEmpty()) { + updrow = backupList.get(0); + updrow = toLowerColumnName(updrow); + dataSql = noValSql; + } + } catch (Exception e) { + log.error("Exception caught", e); + } + } + + // 设置模块的更新行数据 + module.Updrow = (updrow); + + // 处理BillModule的特殊逻辑 + if (module instanceof BillModule && module.PopBtn != null && updrow != null) { + BillModule billModule = (BillModule) module; + Object rtagidObj = updrow.get(module.getMenuPrefix() + "rtagid"); + billModule.setBillFlag(rtagidObj != null ? ToInt32(rtagidObj.toString()) : 0); + } + + + response.setSuccess(true); + return response; + } + + //专属附件的方法 + private BaseResponse SetUpdateRowforf(ModuleBaseEntity module, String sql, String idValue) { + BaseResponse response = new BaseResponse(); + Map updrow = null; // Java中用Map替代DataRow + if (module == null) { + return response; + } + + String dataSql = "", noValSql = ""; + Map record = null; + + // 处理BaseModule类型 + if (module instanceof BaseModule) { + BaseModule baseModule = (BaseModule) module; + if (!isNullOrEmpty(baseModule.getMasterSql()) && !isNullOrEmpty(idValue)) { + record = new HashMap<>(); + record.put(module.getIdField(), idValue); + dataSql = DealQuerySqlfor( + isNullOrEmpty(sql) ? baseModule.getMasterSql() : sql, + record, + module.getLeftRecord(), + null, + module.getIdField(), + idValue, + true, false, false + ); + } + } + // 处理BillModule类型 + else if (module instanceof BillModule) { + BillModule billModule = (BillModule) module; + if (isNullOrEmpty(idValue) && module.PopBtn != null) { + dataSql = module.PopBtn.dllpar8; + } else if (!isNullOrEmpty(billModule.getMasterSql()) && !isNullOrEmpty(idValue)) { + record = new HashMap<>(); + record.put(module.getIdField(), idValue); + dataSql = DealQuerySqlfor( + isNullOrEmpty(sql) ? billModule.getMasterSql() : sql, + record, + module.getLeftRecord(), + null, + module.getIdField(), + idValue, + true, false, false + ); + } + } + + // 构建noValSql + if (!isNullOrEmpty(module.getMasterTable()) && !isNullOrEmpty(idValue)) { + // 使用参数化SQL避免注入风险,这里先拼接(实际应使用占位符) + noValSql = String.format("select top 1 * from %s where %s = ?", + module.getMasterTable(), module.getIdField()); + } + + // 校验SQL是否为空 + if (isNullOrEmpty(dataSql) && isNullOrEmpty(noValSql)) { + response.setMsg(String.format(LanguageUtil.WrongMainCfg, "MasterTable Or MasterSql")); + return response; + } + + // 确定最终执行的SQL + dataSql = isNullOrEmpty(dataSql) ? noValSql : dataSql; + + // 执行查询获取数据行 + try { + // 执行SQL查询,获取结果集(这里假设dbOperator有对应的查询方法) + if (databaseType.equals("dm")) dataSql = RegexUtil.processDmServerSql(dataSql); + List> resultList = jdbcTemplate.queryForList(dataSql); // 传递参数避免SQL注入 + if (resultList != null && !resultList.isEmpty()) { + updrow = resultList.get(0); // 获取第一行数据 + // 转换列名为小写(模拟ToLowerColumnName) + updrow = toLowerColumnName(updrow); + } + } catch (Exception e) { + // 异常处理(根据实际需求添加日志) +// this.Error($"根据主表sql获取模块[{module.ModuleId}][{idValue}]行数据出错!", e); + log.error("Exception caught", e); + } + + // 当主查询失败时尝试备用SQL + if ((updrow == null || (updrow != null && updrow.size() > 1)) + && !dataSql.equals(noValSql) + && !isNullOrEmpty(noValSql)) { + try { + List> backupList = jdbcTemplate.queryForList(noValSql, idValue); // 传递参数 + if (backupList != null && !backupList.isEmpty()) { + updrow = backupList.get(0); + updrow = toLowerColumnName(updrow); + dataSql = noValSql; + } + } catch (Exception e) { + log.error("Exception caught", e); + } + } + + // 设置模块的更新行数据 + module.Updrow = (updrow); + + // 处理BillModule的特殊逻辑 + if (module instanceof BillModule && module.PopBtn != null && updrow != null) { + BillModule billModule = (BillModule) module; + Object rtagidObj = updrow.get(module.getMenuPrefix() + "rtagid"); + billModule.setBillFlag(rtagidObj != null ? ToInt32(rtagidObj.toString()) : 0); + } + + + response.setSuccess(true); + return response; + } + + + // 辅助方法:转换Map的key为小写 + private Map toLowerColumnName(Map row) { + if (row == null) { + return null; + } + Map lowerRow = new HashMap<>(); + for (Map.Entry entry : row.entrySet()) { + lowerRow.put(entry.getKey().toLowerCase(), entry.getValue()); + } + return lowerRow; + } + + @Override + public BaseResponse GetAttcData() throws UnsupportedEncodingException { + String moduleId = Request("moduleId", ""), + idValue = Request("idValue", ""), + specNo = Request("specNo", "01"), + stepCode = Request("stepCode", ""); + return GetAttcList(moduleId, idValue, specNo, stepCode); + } + + /** + * 获取附件列表 + * + * @param moduleId 模块ID + * @param idValue 标识符值 + * @param specNo 规格编号 + * @param stepCode 步骤代码 + * @return 包含附件列表的BaseResponse + */ + public BaseResponse GetAttcList(String moduleId, String idValue, String specNo, String stepCode) throws + UnsupportedEncodingException { + // 处理idValue,移除可能的前后花括号 + idValue = (idValue == null) ? "" : idValue; + if (idValue.startsWith("{") && idValue.endsWith("}")) { + idValue = ""; + } + + BaseResponse response = new BaseResponse(); + if (specNo == "01") { + List> specDt = DataImpl.GetAttTreeData(moduleId, specNo); + Map spectb = null; + if (specDt != null && !specDt.isEmpty()) { + spectb = toHashTable(specDt.get(0)); + } + if (spectb != null) { + specNo = get(spectb, "speciesno", "").toString(); + } + } + String dirTabId = DataImpl.GetAttcParentId(moduleId, idValue); + + // 获取附件文件列表(假设返回List,对应C#的DataTable) + List> attcFiles = DataImpl.GetAttcFiles(moduleId, idValue, dirTabId, specNo, stepCode); + + // 处理每个附件的路径和名称 + for (Map row : attcFiles) { + // 获取文件名(优先vname,其次sname) + String vname = row.get("vname") != null ? row.get("vname").toString() : ""; + String sname = row.get("sname") != null ? row.get("sname").toString() : ""; + String fileName = vname.isEmpty() ? sname : vname; + String fileId = row.get("fileid") != null ? row.get("fileid").toString() : ""; + String webPath = row.get("webpath") != null ? row.get("webpath").toString() : ""; + + // 生成webPath(如果为空) + if (webPath.isEmpty()) { + String absFilePath = DataImpl.GetAbsFilePath(fileId); + String encodedPath = FileUtil.urlEncode(absFilePath, false); + webPath = String.format("/%s/%s", WebConfigUtil_web.getFileVPath(), encodedPath); + } + + // 处理文件名中的空格 + if (fileName.contains(" ")) { + String encodedFileName = FileUtil.urlEncode(fileName, false); + webPath = webPath.replace(encodedFileName, encodedFileName.replace("+", " ")); + } + + // 拼接域名(如果需要) + String appDomain = getAppDomain(); // 从配置或上下文获取实际的appDomain + if ((Boolean.parseBoolean(WebConfigUtil_web.get("useDbAttc", "false")) + && !DataImpl.isDefaultServer() + && !webPath.isEmpty()) + || !appDomain.isEmpty()) { + String dbVerPath = WebConfigUtil.getFileVPath() + "_" + getUser().getServerId(); + if (SiteUtil.containsVirtualDirectory(dbVerPath)) { + webPath = webPath.replace(WebConfigUtil.getFileVPath(), dbVerPath); + } else { + webPath = trimEnd(getAppDomain(), '/') + webPath; + } + } + // 更新行数据 + row.put("webpath", webPath); + row.put("sname", fileName); // 移除特殊字符(如需要) + } + +// sn(" response.setData(attcFiles);"); + // 设置响应数据 + response.setData(attcFiles); + response.setOther(CheckAttcAuthory(moduleId, idValue)); + response.setSuccess(true); + return response; + } + + + public ArrayList GetAuditHistoryInfo(ModuleBaseEntity module) { + ArrayList itemList = new ArrayList<>(); + Boolean isBase = module instanceof BaseModule; + Boolean WindowsDirver = toBoolean(Request("WindowsDirver", "")); + List serchFields = null; + List columns = null; +// IPublicUtil util = new IPublicUtil(); + if (!isNullOrEmpty(module.getOverBackSql())) { +// out.println("2222"); + List> dtVal = DataImpl.GetCondition(module.getOverBackKey(), 0); + serchFields = createControl.createControl(dtVal, module, true, module.getOverBackKey()); + columns = GetAuditStepColumns(module.getModuleId(), "999", "999", isBase); + } else { +// out.println("1111"); + String prefix = (module.getMenuPrefix() + "").toLowerCase(); + + columns = new ArrayList<>(); +// serchFields 操作 + Column column1 = new Column(); + column1.text = isBase ? "数据标识号" : "单据号"; + column1.dataIndex = String.format("%s", module.getIdField()); + column1.setWidth(1); + columns.add(column1); + + Column column2 = new Column(); + column2.text = "制单日期"; + column2.dataIndex = String.format("%soperatedate", prefix); + column2.setXtype("datecolumn"); + column2.setFormat("yyyy-MM-dd"); + column2.setWidth(1); + columns.add(column2); + + Column column3 = new Column(); + column3.text = "制单人"; + column3.dataIndex = String.format("%soperatorname", prefix); + column3.setWidth(1); + columns.add(column3); + + Column column4 = new Column(); + column4.text = "提交时间"; + column4.dataIndex = String.format("%soperatedate", prefix); + column4.setXtype("datecolumn"); + column4.setFormat("yyyy-MM-dd"); + column4.setWidth(1); + columns.add(column4); + + Column column5 = new Column(); + column5.text = "提交人"; + column5.dataIndex = String.format("%saffirmername", prefix); + column5.setWidth(1); + columns.add(column5); + + Column column6 = new Column(); + column6.text = "终审时间"; + column6.dataIndex = String.format("%sstepovertime", prefix); + column6.setXtype("datecolumn"); + column6.setFormat("yyyy-MM-dd"); + column6.setWidth(1); + columns.add(column6); + + Column column7 = new Column(); + column7.text = "终审人"; + column7.dataIndex = String.format("%sstepovername", prefix); + column7.setWidth(1); + columns.add(column7); + +// serchFields 操作 + serchFields = new ArrayList(); + + DateField bdateField = new DateField(null); + bdateField.setName("bdate"); + bdateField.setFieldLabel("终审时间起"); + bdateField.setFormat("yyyy-MM-dd"); + bdateField.setWidth(143); + bdateField.setLabelWidth(72); + serchFields.add(new LabelCheckBox(null, bdateField)); + + DateField edateField = new DateField(null); + edateField.setName("edate"); + edateField.setFieldLabel("终审时间止"); + edateField.setFormat("yyyy-MM-dd"); + edateField.setWidth(143); + edateField.setLabelWidth(72); + serchFields.add(new LabelCheckBox(null, edateField)); + + DataStore store = new DataStore(); + Map comboExtraParams = new HashMap<>(); + comboExtraParams.put("moduleId", module.getModuleId()); + comboExtraParams.put("id", 0); + comboExtraParams.put("fdtype", 99); + comboExtraParams.put("textField", "UserName"); + store.extraParams = comboExtraParams; + + ComboBox comboBox = new ComboBox(null); + comboBox.setName("stepovername"); + comboBox.setFieldLabel("终审人"); + comboBox.setWidth(143); + comboBox.setLabelWidth(72); + comboBox.setValueField("UserCode"); + comboBox.setDisplayField("UserName"); + comboBox.queryParam = "username"; + comboBox.store = store; + + serchFields.add(new LabelCheckBox(null, comboBox)); + + TextField billcodeField = new TextField(null); + billcodeField.setName("billcode"); + billcodeField.setFieldLabel(isBase ? "数据标识号" : "单据号"); // 根据isBase动态设置标签 + billcodeField.setWidth(143); + billcodeField.setLabelWidth(72); + serchFields.add(billcodeField); + } + + List sFields = null; + if (serchFields != null) { + sFields = new ArrayList<>(serchFields); + + Button searchButton = new Button(); + searchButton.setText("查询"); + searchButton.setHandler("search"); + sFields.add(searchButton); + + Button backButton = new Button(); + backButton.setText("返审"); + backButton.setHandler("OnBack"); + sFields.add(backButton); + } + +// GridPanel1 + GridPanel gridPanel1 = new GridPanel(); + gridPanel1.setColumns(columns); + gridPanel1.IdField = module.getIdField(); + gridPanel1.TbarItems = sFields; + gridPanel1.SHeight = DataImpl.GetConditionPanleHeight(module.getOverBackKey()); + if (WindowsDirver) { + gridPanel1.MobileCards = null; + } else { + gridPanel1.MobileCards = GetAuditStepColumnCards(module.getModuleId(), "999", isBase); + } + + // 创建并设置数据存储 + DataStore store1 = new DataStore(); + Map extraParams1 = new HashMap<>(); + extraParams1.put("stepId", -999); + extraParams1.put("moduleId", module.getModuleId()); + store1.extraParams = extraParams1; + gridPanel1.setStore(store1); + +// 设置ElseValue属性(使用Map存储匿名对象属性) + Map ElseValue = new HashMap<>(); + ElseValue.put("IsComplete", true); + gridPanel1.elseValue = ElseValue; + +// 将GridPanel1添加到itemList -- GridPanel1结束 + itemList.add(gridPanel1); + +// GridPanel2 + GridPanel gridPanel2 = new GridPanel(); + gridPanel2.IdField = module.getIdField(); + gridPanel2.title = "已完成单据明细"; + + ArrayList columns2 = new ArrayList<>(); + // 1. 步骤代码列 + Column stepCodeColumn = new Column(); + stepCodeColumn.text = "步骤代码"; + stepCodeColumn.dataIndex = "stepcode"; + stepCodeColumn.setWidth(1); + columns2.add(stepCodeColumn); + +// 2. 步骤名称列 + Column stepNameColumn = new Column(); + stepNameColumn.text = "步骤名称"; + stepNameColumn.dataIndex = "stepname"; + stepNameColumn.setWidth(1); + columns2.add(stepNameColumn); + +// 3. 发生时间列(日期类型) + Column happenTimeColumn = new Column(); + happenTimeColumn.text = "发生时间"; + happenTimeColumn.dataIndex = "happentime"; + happenTimeColumn.setXtype("datecolumn"); + happenTimeColumn.setFormat("yyyy-MM-dd"); + happenTimeColumn.setWidth(1); + columns2.add(happenTimeColumn); + +// 4. 操作时间列(日期类型) + Column operTimeColumn = new Column(); + operTimeColumn.text = "操作时间"; + operTimeColumn.dataIndex = "opertime"; + operTimeColumn.setXtype("datecolumn"); + operTimeColumn.setFormat("yyyy-MM-dd"); + operTimeColumn.setWidth(1); + columns2.add(operTimeColumn); + +// 5. 操作人员列 + Column operatorNameColumn = new Column(); + operatorNameColumn.text = "操作人员"; + operatorNameColumn.dataIndex = "operatorname"; + operatorNameColumn.setWidth(1); + columns2.add(operatorNameColumn); + +// 6. 操作方向列 + Column operDirectionColumn = new Column(); + operDirectionColumn.text = "操作方向"; + operDirectionColumn.dataIndex = "operdirectionname"; + columns2.add(operDirectionColumn); // 未指定width,使用默认值 + +// 7. 操作意见列 + Column operAdviceColumn = new Column(); + operAdviceColumn.text = "操作意见"; + operAdviceColumn.dataIndex = "operadvice"; + operAdviceColumn.setWidth(1); + columns2.add(operAdviceColumn); + +// 8. 自动审核列 + Column autoAuditColumn = new Column(); + autoAuditColumn.text = "自动审核"; + autoAuditColumn.dataIndex = "autoaudit"; + autoAuditColumn.setWidth(1); + columns2.add(autoAuditColumn); + + // 设置列配置到GridPanel + gridPanel2.setColumns(columns2); + +// 创建并配置数据存储 + DataStore store2 = new DataStore(); + Map extraParams2 = new HashMap<>(); + extraParams2.put("moduleId", module.getModuleId()); + extraParams2.put("stepId", -1); + store2.extraParams = extraParams2; + gridPanel2.setStore(store2); + +// 添加到itemList + itemList.add(gridPanel2); + + return itemList; + } + + private Object GetAuditStepColumnCards(String moduleId, String s, Boolean isBase) { + return null; + } + + private List GetAuditStepColumns(String moduleId, String stepId, String stepCode, Boolean isBase) { + List> dtval = DataImpl.GetAuditStepColumns(moduleId, stepId, stepCode, getUser().UserId, isBase); +// IPublicUtil util = new IPublicUtil(); + List list = dtval.stream() + // 对应C#的select new RowColumn(row, false, util) + .map(row -> new RowColumn(row, false, createControl)) + // 对应ToList() + .collect(Collectors.toList()); + return list; + } + + /** + * API中的AddOrUpd方法 + */ + @Override + public BaseResponse AddOrUpd() throws UnsupportedEncodingException, CusException { + Boolean isadd = null; + String add = Request("isadd", "") + ""; + int comfirmFlag = ToInt32(Request("comfirm", "") + ""); + if (!isNullOrEmpty(add)) { + isadd = toBoolean(add); + } + return AddOrUpd(Request("ModuleId", ""), Request("datas", "") + "", + Request("details", "") + "", + Request("leftRecord", "") + "", + toBoolean(Request("apply", "")), + isadd != null ? isadd : toBoolean(add), comfirmFlag, + toBoolean(Request("batch", "")) + ); + } + + public BaseResponse AddOrUpd(String moduleId, String data, String details, String leftRecord, + boolean apply, Boolean isAdd, int comfirmFlag, boolean batch) throws UnsupportedEncodingException, CusException { + // 获取基础模块信息 + BaseModule module = GetBaseModule(moduleId, Request("menuId", "")); + if (isNullOrEmpty(data) || "[]".equals(data)) { + return new BaseResponse(); + } + + // 确保数据以数组格式存在 + data = data.startsWith("[") ? data : String.format("[%s]", data); + ArrayList datas = (ArrayList) JSON.Decode(data); + // 解析明细数据 + Map detailDatas = null; + if (!isNullOrEmpty(details) && details.startsWith("{") && details.endsWith("}")) { + detailDatas = (Map) JSON.Decode(details); + } + + // 解析左侧记录数据 + Map leftRecordMap = null; + if (!isNullOrEmpty(leftRecord) && leftRecord.startsWith("{")) { + leftRecordMap = (Map) JSON.Decode(leftRecord); + } + // 调用核心处理方法 + return AddOrUpd(module, datas, detailDatas, leftRecordMap, apply, isAdd, comfirmFlag, batch); + } + + /** + * 新增或更新数据处理 + */ + @Transactional + protected BaseResponse AddOrUpd(BaseModule module, List> datas, + Map details, Map leftRecord, + boolean apply, Boolean isAdd, int comfirmFlag, boolean batch) throws UnsupportedEncodingException, CusException { + final BaseResponse[] response = {new BaseResponse()}; + + // 数据校验 + if (datas == null || datas.isEmpty()) { + return response[0]; + } + if (module == null) { + return response[0]; + } + + String tableName = module.getMasterTable(); + if (tableName == null || tableName.isEmpty()) { + response[0].setMsg(String.format("模块【%s】未配置主表名,请检查配置!", module.getModuleId())); + return response[0]; + } + + // 获取表结构信息和自增字段 + List> tableInfos = DataImpl.GetTableInfo(tableName); + String identityField = GetIdentityField(tableInfos); + +// out.println("获取表结构信息和自增字段 "); + // 获取列信息 + List> dtColumns = GetColumnRows(module.getModuleId()); + +// out.println("获取列信息 "); + List retIds = new ArrayList<>(); + List applyDatas = new ArrayList<>(); + StringBuilder errMsg = new StringBuilder(); + + if (module.getNewVer()) { + // 遍历tableInfos查找匹配的行 + Map idTabRow = null; + for (Map row : tableInfos) { + String rowName = get(row, "name", null) + "".toLowerCase(); + if (rowName.equals(module.getIdField().toLowerCase())) { + idTabRow = row; + break; + } + } + if (idTabRow != null) { + idTabRow.put("isnullable", 0); + } + } +// out.println("module.getNewVer() " + datas.size() + " " + datas); + + // 批量处理逻辑 + if ((datas.size() > 1 && batch) || (datas.size() >= 10 && toBoolean(WebConfigUtil.get("batchDetailDatas")))) { +// out.println("批量处理逻辑 : 第一个判断"); + response[0] = BuildAndSaveAoUDatas(module, datas, dtColumns, leftRecord, tableInfos, + identityField, apply, isAdd, 0, comfirmFlag); + } else { +// out.println("批量处理逻辑 : 第二个判断"); + for (Map fRow : datas) { + Map row = fRow; + List> copyChildRows = new ArrayList<>(); + boolean fromDrag = row.containsKey("$fdrag"); + boolean isCopy = row.containsKey("$copy") && toBoolean(row.get("$copy")); + String updateChildrenVal = ""; + + if (fromDrag && module.IsSpecModule) { + // 查找匹配的字段行 + Map treeRow = dtColumns.stream() + .filter(r -> DataTableUtil.getRowVal(r, "fieldname", "").toString() + .equalsIgnoreCase(module.LeftUnionField)) + .findFirst() + .orElse(null); + + Map tabTreeRow = tableInfos.stream() + .filter(r -> DataTableUtil.getRowVal(r, "name", "").toString() + .equalsIgnoreCase(module.LeftUnionField)) + .findFirst() + .orElse(null); + + if (treeRow != null && tabTreeRow != null) { + Field field = new Field(treeRow); + int xtype; + if (databaseType.equals("dm")) { + xtype = ToInt32(PublicUtil.DMSqltypeToProType(tabTreeRow.get("xtype").toString())); + } else { + xtype = ToInt32(tabTreeRow.get("xtype")); + } + // 获取树节点的特定编号 + String fieldValue = createControl.GetTreeSpecNo( + module.getMasterTable(), + module.LeftUnionField, + field.getValueField(), + field.getDataSource(), + row.get(module.LeftUnionField) + "", + PublicUtil.SqlxtypeToProType(xtype), + 0 + ); + + if (isCopy) { + String oldIdFieldValue = row.get(module.getIdField()) + "", + oldSpecValue; + // 查询原始数据行 + row = jdbcTemplate.queryForList( + String.format("select * from %s where %s='%s'", + module.getMasterTable(), + module.getIdField(), + oldIdFieldValue) + ).get(0); + oldSpecValue = row.get(module.LeftUnionField) + ""; + row.put("$is_copy", true); + row.put("$old_" + module.getIdField(), module.getIdField()); + // 查询子记录并处理 + List> childRows = jdbcTemplate.queryForList( + String.format("select * from %s where %s like '%s_%%'", + module.getMasterTable(), + module.LeftUnionField, + oldSpecValue) + ); + for (Map childRow : childRows) { + String oldUnionVal = childRow.get(module.LeftUnionField) + ""; + String newUnionVal = fieldValue + oldUnionVal.substring(oldSpecValue.length()); + childRow.put(module.LeftUnionField, newUnionVal); + childRow.put("$is_copy", true); + childRow.put("$old_" + module.getIdField(), childRow.get(module.getIdField())); + copyChildRows.add(childRow); + } + + } else { + row = fRow; + String currentUnionVal = row.get(module.LeftUnionField) + ""; + if (!currentUnionVal.equals(fieldValue)) { + // 获取需要更新的子记录值 + Object result = jdbcTemplate.queryForObject( + String.format("select %s from %s where %s='%s'", + module.LeftUnionField, + module.getMasterTable(), + module.getIdField(), + row.get(module.getIdField())), Object.class + ); + updateChildrenVal = result != null ? result.toString() : ""; + } + } + if (row == null) continue; + if (row != null) row.put(module.LeftUnionField, fieldValue); + + } else { + row = fRow; + } + } else { + row = fRow; + } + +// out.println("Datas判断 " + +// (datas.size() == 1 && details != null && !details.isEmpty()) + " " + +// (isCopy && copyChildRows != null && copyChildRows.size() > 0) +// ); + if (datas.size() == 1 && details != null && !details.isEmpty()) { + int succ = 0; + response[0] = BuildAndSaveAoUData( + module, row, dtColumns, leftRecord, tableInfos, + identityField, apply, isCopy, 0, comfirmFlag + ); + + if (!response[0].isSuccess() && !response[0].getMsg().equals(LanguageUtil.GetString("NoneField"))) { + if (!isNullOrEmpty(response[0].getMsg())) { + errMsg.append(response[0].getMsg()).append("\n"); + } + } else { + response[0].setSuccess(true); + response[0].setMsg(LanguageUtil.Success); + succ = BatchAddOrUpdDetail(module, row, details, isAdd, response[0]); + if (succ <= 0) { + errMsg.append(response[0].getMsg()).append("\n"); + } else { + retIds.add(response[0].getOther()); + module.MasterData = (row); + getEventHandler().callAfterModuleDataChange( + module, + isAdd == true ? SystemEnums.ActionType.Add : SystemEnums.ActionType.Update, + response[0]); + } + } + } else if (isCopy && copyChildRows != null && copyChildRows.size() > 0) { + // 使用JdbcTemplate的事务回调,确保操作在同一事务中 + Map finalRow = row; + jdbcTemplate.execute((ConnectionCallback) con -> { + int succ = 0; + // 构建并保存主数据(isCopy为true时传入isNew参数为true) + response[0] = BuildAndSaveAoUData( + module, + finalRow, + dtColumns, + leftRecord, + tableInfos, + identityField, + apply, + true, // isCopy ? true : isAdd 此处isCopy为true,直接传true + 0, + comfirmFlag + ); + + if (response[0].isSuccess()) { + // 收集返回的ID + retIds.add(response[0].getOther()); + + // 确保在子表操作前更新模块主数据 + module.MasterData = (finalRow); // 假设存在setter方法 + + // 触发数据变更后事件 + getEventHandler().callAfterModuleDataChange( + module, + isAdd == true ? SystemEnums.ActionType.Add : SystemEnums.ActionType.Update, // 注意枚举值大写(Java规范) + response[0] + ); + + // 执行子表复制操作 + BaseResponse childResponse = null; + try { + childResponse = AddOrUpd( + module, + copyChildRows, + null, + leftRecord, + false, + true, + comfirmFlag, + false + ); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } catch (CusException e) { + throw new RuntimeException(e); + } + succ = childResponse.isSuccess() ? 1 : 0; + } else if (!isNullOrEmpty(response[0].getMsg())) { + // 拼接错误信息 + errMsg.append(response[0].getMsg()).append("\n"); + } + return succ; + }); + } else { +// out.println("Datas判断 最后一个else:start"); + response[0] = BuildAndSaveAoUData( + module, row, dtColumns, leftRecord, tableInfos, + identityField, apply, isCopy, 0, comfirmFlag + ); + +// out.println("BuildAndSaveAoUData end" + response[0].isSuccess()); + if (response[0].isSuccess()) { + retIds.add(response[0].getOther()); + module.MasterData = (row); + getEventHandler().callAfterModuleDataChange( + module, + Optional.ofNullable(isAdd).orElse(false) ? SystemEnums.ActionType.Add : SystemEnums.ActionType.Update, + response[0] + ); + } + } + + if (response[0].isSuccess() && "99".equals(response[0].getOther() + "")) { + if ("99".equals(response[0].getData() + "")) { + response[0] = Deal99Response(response[0], null, module.getModuleId()); + } + return response[0]; + } + + if (response[0].isSuccess() && apply) { + String applyModuleId = Request("applyModuleId", module.getModuleId()); + response[0] = BaseApply(applyModuleId, module.IdValue); + + if (response[0].isSuccess()) { + if (ToInt32(response[0].getOther()) == -10) { + applyDatas.add(response[0].getData()); + } + } else if (ToInt32(response[0].getOther()) == 9) { + response[0].setSuccess(true); + Map applyData = new HashMap<>(); + applyData.put("moduleId", module.getModuleId()); + applyData.put("idValue", module.IdValue); + applyData.put("msg", response[0].getMsg()); + applyDatas.add(applyData); + } else { + response[0].setSuccess(true); + errMsg.append(String.format("数据已添加,但提交失败!%s", response[0].getMsg())).append("\n"); + } + } + + if (response[0].isSuccess() && fromDrag && !isNullOrEmpty(updateChildrenVal)) { + jdbcTemplate.update( + String.format("update %s set %s='%s'||substring(%s, length('%s')+1) where position('%s' in %s)=1", + module.getMasterTable(), + module.LeftUnionField, + row.get(module.LeftUnionField), + module.LeftUnionField, + updateChildrenVal, + updateChildrenVal, + module.LeftUnionField) + ); + } + } + } + // 处理返回结果 + if (!applyDatas.isEmpty()) { + Map data = new HashMap<>(); + data.put("code", response[0].getOther() != null ? response[0].getOther() : 0); + data.put("datas", applyDatas); + response[0].setData(data); + } + + if (!retIds.isEmpty()) { + response[0].setOther(retIds.size() == 1 ? retIds.get(0) : retIds); + } + + if (errMsg.length() > 0) { + response[0].setMsg(errMsg.toString()); + } + + return response[0]; + } + + /** + * 提交或撤销操作 + * + * @param moduleId 模块ID + * @param idValue 标识值 + * @param stateEn 单据状态实体(可为null) + * @param applyType 操作类型:1-提交,2-撤销 + * @return 基础响应对象 + */ + public BaseResponse BaseApply(String moduleId, String idValue, BillStateEn stateEn, int applyType) throws + CusException { + // 获取基础模块信息 + BaseModule module = GetBaseModule(moduleId, getMenuId()); + module.IdValue = (idValue); + + // 获取单据状态 + BillStateEn state = GetBillState(module, ""); + + // 记录系统日志 + sysLog(String.format("%s->%s%s%s", + module.getMenuName(), + getUser().UserName, + applyType == 1 ? "提交" : "撤销", + module.IdValue), + "提交数据"); + // 处理状态信息 + if (state != null) { + state.StepCode = ("0"); + if (stateEn != null) { + state.setSelectConfirmFlag(stateEn.getSelectConfirmFlag()); + state.nextSelectStepCode = (stateEn.nextSelectStepCode); + state.nextSelectStepOper = (stateEn.nextSelectStepOper); + state.comfirmFlag = (stateEn.comfirmFlag); + } + } + + // 调用状态变更前事件 + getEventHandler().callBeforeModuleStateChange( + module, + applyType == 1 ? SystemEnums.ActionType.Submit : SystemEnums.ActionType.EscSubmit, + null + ); + + // 处理提交/撤销逻辑并获取响应 + BaseResponse response = Deal99Response( + DataImpl.BaseApply(module, state, applyType), + null, + moduleId + ); + + // 调用状态变更后事件 + getEventHandler().callAfterModuleStateChange( + module, + applyType == 1 ? SystemEnums.ActionType.Submit : SystemEnums.ActionType.EscSubmit, + response + ); + + return response; + } + + /** + * 重载方法,提供默认参数(applyType默认为1-提交) + */ + public BaseResponse BaseApply(String moduleId, String idValue, BillStateEn stateEn) throws CusException { + return BaseApply(moduleId, idValue, stateEn, 1); + } + + /** + * 重载方法,提供默认参数(stateEn默认为null,applyType默认为1-提交) + */ + public BaseResponse BaseApply(String moduleId, String idValue) throws CusException { + return BaseApply(moduleId, idValue, null, 1); + } + + /// + /// 生成批量保存sql并执行 + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public BaseResponse BuildAndSaveAoUDatas( + BaseModule module, + List> rows, + List> dtColumns, + Map leftRecord, + List> dtTableColumns, + String identityField, + boolean apply, + Boolean isAdd, + int count, + int comfirmFlag) { + + BaseResponse response = new BaseResponse(); + StringBuilder sqls = new StringBuilder(); + StringBuilder errs = new StringBuilder(); + boolean isAddFlag = false; + int i = 0; + + // 遍历所有行生成SQL + for (Map row : rows) { + BaseResponse rowResponse = BuildAoUSaveSql( + module, row, dtColumns, leftRecord, dtTableColumns, + identityField, apply, isAdd, i + ); + if (rowResponse.isSuccess()) { + // 拼接SQL语句 + sqls.append(rowResponse.getData() != null ? rowResponse.getData().toString() : ""); + isAddFlag = rowResponse.getOther() != null && Boolean.parseBoolean(rowResponse.getOther().toString()); + } else { + // 收集错误信息 + if (rowResponse.getMsg() != null) { + errs.append(rowResponse.getMsg()); + } + } + i++; + } + + // 执行批量SQL + if (sqls.length() > 0) { + // 调用数据层保存方法(1=新增,2=修改) + response = DataImpl.BaseDataSave( + module, + sqls.toString(), + isAddFlag ? 1 : 2, + comfirmFlag + ); + + // 记录系统日志 + String logMsg = String.format( + "%s->%s%s@#@%s=>%s", + module.getTitle(), + getUser().UserName, + isAddFlag ? "批量添加" : "批量修改", + sqls.toString(), + JSON.Encode(rows) + ); + log.debug(String.valueOf(logMsg + "操作模块")); + } else { + // 无有效SQL时返回失败 + response.setSuccess(false); + response.setMsg(LanguageUtil.GetString("NoneField")); + } + + // 处理特殊返回值 + if (response.isSuccess() && "99".equals(response.getOther() != null ? response.getOther().toString() : "")) { + return response; + } + + // 合并错误信息 + if (errs.length() > 0) { + String finalMsg = (response.getMsg() != null ? response.getMsg() : "") + errs.toString(); + response.setMsg(finalMsg); + } + + return response; + } + + private BaseResponse BuildAoUSaveSql( + BaseModule module, + Map row, + List> dtColumns, + Map leftRecord, + List> dtTableColumns, + String identityField, + boolean apply, + Boolean isAdd, + int batchIndex) { + + BaseResponse response = new BaseResponse(); + StringBuilder errbder = new StringBuilder(); + String idField = module.getIdField(); + String idValue = row.getOrDefault(idField, "").toString(); + module.IdValue = (idValue); + + // 判断是否为新增操作 + boolean isAddFlag = CheckIsAdd(module); // 对应原CheckIsAdd方法 + if ((isAdd != null && isAdd) || isAddFlag) { + isAddFlag = true; + if (idField.equals(identityField)) { + idValue = ""; + module.IdValue = (""); + row.put(idField, ""); + } + } + + // 设置模块主数据并触发数据变更前事件 + module.MasterData = (row); + getEventHandler().callBeforeModuleDataChange( + module, + isAddFlag ? SystemEnums.ActionType.Add : SystemEnums.ActionType.Update, + null + ); + + // 获取待操作字段列表 + List fieldList = GetAOUFields_TabInfo( + module, row, dtColumns, leftRecord, dtTableColumns, + identityField, isAddFlag, null, batchIndex + ); + + // 收集字段验证错误信息 + StringBuilder errMsg = new StringBuilder(); + for (FieldModel field : fieldList) { + if (!isNullOrEmpty(field.ValidMsg)) { + if (errMsg.length() > 0) { + errMsg.append("
"); + } + errMsg.append(field.ValidMsg); + } + } + + if (!errMsg.isEmpty()) { + response.setSuccess(false); + response.setMsg(errMsg.toString()); + return response; + } + + // 旧版主键非自增且为空的校验 + if (isNullOrEmpty(module.IdValue) + && !idField.equals(identityField) + && !module.getNewVer()) { + response.setSuccess(false); + response.setMsg(String.format( + "旧版保存,主键值不能为空!请检查主键【%s】是否配置默认值!", + idField + )); + return response; + } + + // 构建保存SQL + StringBuilder updStrKeyHolder = new StringBuilder(); // 用于接收out参数 + String sql = BuildSaveSql( + module, module.getMasterTable(), identityField, + fieldList, isAddFlag, updStrKeyHolder + ); + // 设置响应结果 + response.setSuccess(true); + response.setData(sql); + response.setOther(isAddFlag); + + return response; + } + + /** + * 根据表信息,将数据组装成SQL字段模型列表 + * + * @param module 模块信息 + * @param source 数据源 + * @param dtColumns 配置的列 + * @param leftRecord 左边树或上级的选中行(用于获取默认值) + * @param dtTableColumns 表所有的列 + * @param identityField 自动增长字段 + * @param isAdd 是否为添加操作 + * @param eqDtTableColumns 等效表列信息(可选) + * @param batchIndex 批量操作索引(可选) + * @return 字段模型列表 + */ + protected List GetAOUFields_TabInfo(ModuleEntity module, + Map source, + List> dtColumns, + Map leftRecord, + List> dtTableColumns, + String identityField, + boolean isAdd, + List> eqDtTableColumns, + int batchIndex) { + String idField = module.getIdField(); +// out.println("筛选符合条件的列并转换为FieldModel " + source); + Set lowerCaseKeys = new HashSet<>(); + for (String key : source.keySet()) { + lowerCaseKeys.add(key.toLowerCase()); // 统一转为小写 + } + // 筛选符合条件的列并转换为FieldModel + return dtTableColumns.stream() + .filter(row -> { +// out.println("row + idField" + row + idField); + String colname = DataTableUtil.getStringValue(row, "name", "").toLowerCase(); + int colstat = DataTableUtil.getIntValue(row, "colstat", 0); + boolean isComputed = DataTableUtil.getBooleanValue(row, "isComputed", false); + boolean isIdentity = DataTableUtil.getBooleanValue(row, "isIdentity", false); +// boolean condition = (colstat != 1 && colstat != 4) && !( +// isIdentity || isComputed || // 排除主键(isIdentity)和自动计算列(isComputed) +// (!isAdd && (!source.containsKey(colname) || colname.equalsIgnoreCase(idField))) // 修改时:跳过无改变字段、主键字段 +// ); + // 1. 提取列状态条件(基础条件) + boolean isValidColstat = (colstat != 1 && colstat != 4); + + // 2. 提取需要排除的特殊列类型 + boolean isSpecialColumn = isIdentity || isComputed; + + // 3. 提取"修改模式下需要跳过的字段"条件 + boolean isSkipInUpdateMode = !isAdd && (!lowerCaseKeys.contains(colname) || colname.equalsIgnoreCase(idField)); + + // 4. 组合最终条件(原表达式的逻辑) + boolean condition = isValidColstat && !(isSpecialColumn || isSkipInUpdateMode); +// out.println("condition " + condition + !isAdd + !lowerCaseKeys.contains(colname) + colname.equalsIgnoreCase(idField)); + return condition; + // 排除条件:状态为1或4、自增列、计算列 +// if (colstat == 1 || colstat == 4 || isIdentity || isComputed) { +// return false; +// } +// // 修改时,跳过未改变的字段和主键字段 +// if (!isAdd) { +// boolean sourceContainsKey = source.containsKey(DataTableUtil.getStringValue(row, "name", "")); +// boolean isIdField = colname.equalsIgnoreCase(idField); +// if (!sourceContainsKey || isIdField) { +// return false; +// } +// } +// return true; + }) + .map(row -> { + String colname = DataTableUtil.getStringValue(row, "name", "").toLowerCase(); + + // 查找配置列中对应的行 + Map colrow = dtColumns.stream() + .filter(r -> DataTableUtil.getStringValue(r, "fieldname", "").toLowerCase().equals(colname)) + .findFirst() + .orElse(null); + + boolean isnullable = DataTableUtil.getBooleanValue(row, "isnullable", false); + int rowType; + if (databaseType.equals("dm")) { + rowType = PublicUtil.DMSqltypeToProType(DataTableUtil.getStringValue(row, "xtype", "")); + } else { + rowType = DataTableUtil.getIntValue(row, "xtype", 0); + } +// int rowType = DataTableUtil.getIntValue(row, "xtype", 0); + String tabDefaultVal = DataTableUtil.getStringValue(row, "text", ""); + int length = DataTableUtil.getIntValue(row, "length", 0); + + // 处理等效表列信息 + if (eqDtTableColumns != null) { + Map eqRow = eqDtTableColumns.stream() + .filter(r -> DataTableUtil.getStringValue(r, "name", "").toLowerCase().equals(colname)) + .findFirst() + .orElse(null); + if (eqRow != null) { + if (databaseType.equals("dm")) { + rowType = PublicUtil.DMSqltypeToProType(DataTableUtil.getStringValue(eqRow, "xtype", DataTableUtil.getStringValue(row, "xtype", ""))); + } else { + rowType = DataTableUtil.getIntValue(eqRow, "xtype", rowType); + } +// rowType = DataTableUtil.getIntValue(eqRow, "xtype", rowType); + } + } + + boolean cfgNullAble = true; +// String colCnName = colname; + String colCnName = DataTableUtil.getStringValue(row, "name", ""); + Object fieldValue = get(source, colCnName, ""); +// out.println(colCnName + " Object fieldValue " + fieldValue); + Field field = null; + + if (colrow != null) { + field = new Field(colrow); + if (!field.getControlHidden()) { + cfgNullAble = !DataTableUtil.getBooleanValue(colrow, "Nullable", false); + colCnName = DataTableUtil.getStringValue(colrow, "FieldCaption", colname); + } + } + + // 处理可空性 + if (!isnullable && !tabDefaultVal.isEmpty() && cfgNullAble) { + isnullable = true; + } + + // 获取数据类型 + Class dataType = PublicUtil.SqlxtypeToProType(rowType); + +// out.println("获取数据类型 " + dataType); + // 配置列不存在且非添加操作且可空且值为空时,返回null + if (colrow == null && !isAdd && isnullable && + (fieldValue == null || fieldValue.toString().trim().isEmpty())) { + return null; + } + + // 处理字段值 + if (colrow != null && field != null) { + + // 树节点类型字段处理 + if (isAdd && field.getFieldType() == SystemEnums.ControlType.LabTreeType.ordinal() && + !isNullOrEmpty(field.getDataSource()) && leftRecord != null) { + + String fieldValStr = fieldValue != null ? fieldValue.toString() : ""; + boolean needGenerate = fieldValStr.isEmpty() || batchIndex > 0; + + if (!needGenerate) { + // 检查值是否已存在 + String checkSql = String.format("select count(1) from %s where %s='%s'", + module.getMasterTable(), field.getName(), fieldValStr); + Integer count = jdbcTemplate.queryForObject(checkSql, Integer.class); + needGenerate = count != null && count > 0; + } +// out.println("树节点类型字段处理 " + needGenerate); + if (needGenerate) { + // 生成树节点编号 + Object parentValue = leftRecord.get(field.getValueField()); + String parentValStr = parentValue != null ? parentValue.toString() : ""; + fieldValue = createControl.GetTreeSpecNo( + module.getMasterTable(), + field.getName(), + field.getValueField(), + field.getDataSource(), + parentValStr, + dataType, + batchIndex + ); +// out.println("生成树节点编号 " + fieldValue); + } + } + // ID字段默认值处理 + else if (fieldValue == null || fieldValue.toString().trim().isEmpty() && + colname.equalsIgnoreCase(idField)) { + + String defaultVal = Objects.toString(field.getDefaultsource(), ""); + +// out.println("ID字段默认值处理 " + defaultVal); + if (defaultVal.toLowerCase().contains("parent.key")) { + // 替换parent.key为实际父字段 + Map pkRow = dtColumns.stream() + .filter(r -> DataTableUtil.getStringValue(r, "FieldType", "") + .equals(String.valueOf(SystemEnums.ControlType.LabTreeType.ordinal()))) + .findFirst() + .orElse(null); + + if (pkRow != null) { + String valueMember = DataTableUtil.getStringValue(pkRow, "ValueMember", ""); + defaultVal = defaultVal.toLowerCase().replace("{parent.key}", + String.format("{parent.%s}", valueMember)); + } + } + + // 获取默认值 + ModuleEntity tempModule = new ModuleEntity(); + tempModule.setLeftRecord(leftRecord); +// out.println(" // 获取默认值 1"); + fieldValue = createControl.GetDefaultValue(defaultVal, tempModule, SystemTypeEnums.PmType.store); +// out.println(" // 获取默认值 2"); + } + } + + // 设置模块ID值 + if (colname.equalsIgnoreCase(idField) && isAdd) { + module.IdValue = (fieldValue != null ? fieldValue.toString() : ""); + } + +// out.println("设置模块ID值 "); + // 密码字段加密处理 + if ("password".equals(colname) && + fieldValue != null && + fieldValue.toString().length() < 32) { + fieldValue = SafetyUtil.encryptPassword(fieldValue.toString()); + } + +// out.println("密码字段加密处理 "); + // 添加操作时,可空且值为空的字段跳过(主键除外) + if (isAdd && isnullable && + (fieldValue == null || fieldValue.toString().trim().isEmpty()) && + !colname.equalsIgnoreCase(idField)) { + return null; + } +// out.println("添加操作时,可空且值为空的字段跳过(主键除外) "); + // 创建字段模型 + FieldModel model = new FieldModel(); + model.IsNullAble = (isnullable); + model.FiledName = (colname); + model.dbType = (rowType); + model.ctype = (dataType); + model.FieldLength = (length); +// out.println("setFiledValue " + fieldValue); + model.setFiledValue(fieldValue); +// out.println("setFiledValue END"); + // 验证非空 + if (!cfgNullAble && (fieldValue == null || fieldValue.toString().trim().isEmpty())) { + model.ValidMsg = (colCnName + "不能为空!"); + } + +// out.println("验证非空 "); + // 图片类型字段处理 + if (model.ValidMsg == null && field != null && + (field.getFieldType() == SystemEnums.ControlType.LabPic.ordinal() || + field.getFieldType() == SystemEnums.ControlType.LabPicEx.ordinal())) { + + String fieldVal = model.hasSpecVal ? + (model.pmFiledValue != null ? model.pmFiledValue.toString() : "") : + (model.getFiledValue() != null ? model.getFiledValue().toString() : ""); + + if (!fieldVal.isEmpty() && fieldVal.indexOf("%") < 0) { + // 去除首尾的双引号和单引号 + String trimmedVal = fieldVal.replaceAll("^[\"']+", "").replaceAll("[\"']+$", ""); + String[] fVal = trimmedVal.split(","); + for (int i = 0; i < fVal.length; i++) { + // 对应C#: string fvali = fVal[i], host = ""; + String fvali = fVal[i]; + String host = ""; + // 对应C#: if (fvali.StartsWith("http")) + if (fvali != null && fvali.startsWith("http")) { + try { + // 对应C#: Uri uri = new Uri(fvali); + URI uri = new URI(fvali); + // 对应C#: host = $"{uri.Scheme}://{uri.Authority}"; + host = uri.getScheme() + "://" + uri.getAuthority(); + // 对应C#: fvali = HttpUtility.UrlDecode(uri.PathAndQuery).Replace(" ", "+"); + String pathAndQuery = uri.getRawPath() + (uri.getRawQuery() != null ? "?" + uri.getRawQuery() : ""); + fvali = URLDecoder.decode(pathAndQuery, StandardCharsets.UTF_8).replace(" ", "+"); + // 对应C#: OAUrl.Equals(host, StringComparison.OrdinalIgnoreCase) || ... + boolean isHostMatch = + // OAUrl和host忽略大小写相等 + getOAUrl().equalsIgnoreCase(host) + // host以OAUrl开头(忽略大小写) + || host.toLowerCase().startsWith(getOAUrl().toLowerCase()) + // OAUrl以host开头(忽略大小写) + || getOAUrl().toLowerCase().startsWith(host.toLowerCase()); + if (isHostMatch) { + host = ""; // 匹配则清空host + } + } catch (URISyntaxException e) { + // URI解析失败时,保留原fvali,host为空(容错处理) + log.error("Exception caught", e); + } + } + // 对应C#: string[] vs = fvali.Split('?'); + String[] vs = fvali != null ? fvali.split("\\?", 2) : new String[0]; // 分割为最多2部分,避免多个?的问题 + + // 对应C#: if (vs.Length > 1) + if (vs.length > 1) { + // 对应C#: fVal[i] = $"{host}{FileUtil.UrlEncode(vs[0], false)}?{vs[1]}"; + try { + fVal[i] = host + FileUtil.urlEncode(vs[0], false) + "?" + vs[1]; + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } else { + // 对应C#: fVal[i] = $"{host}{FileUtil.UrlEncode(vs[0], false)}"; + String encodeStr = null; + try { + encodeStr = vs.length > 0 ? FileUtil.urlEncode(vs[0], false) : ""; + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + fVal[i] = host + encodeStr; + } + + // 原C#注释的编码逻辑(如需启用,取消注释) + // fVal[i] = URLEncoder.encode(fVal[i].replace(" ", "%20"), StandardCharsets.UTF_8) + // .replace("%5c", "/").replace("%2f", "/").replace("%3f", "?").replace("%3d", "=").replace("%26", "&"); + } + model.setFiledValue(fVal); + } + } +// out.println("model " + JSON.Encode(model)); + return model; + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + + protected boolean CheckIsAdd(ModuleEntity module) { + if (module != null && !isNullOrEmpty(module.getMasterTable()) && !isNullOrEmpty(module.IdValue)) { + String idWhere = module.IdValue; + if (databaseType.equals("dm")) idWhere = RegexUtil.escapeSingleQuote(idWhere); + idWhere = "and " + module.getIdField() + "= '" + idWhere + "'"; + return CheckIsAdd(module.getMasterTable(), idWhere); + } + return true; + } + + protected boolean CheckIsAdd(String tabName, String idWhere) { + if (!isNullOrEmpty(tabName) && !isNullOrEmpty(idWhere)) { + String sql = String.format("select count(1) from %s where 1=1 %s", tabName, idWhere); +// out.println("CheckIsAdd.SQL " + sql); + if (databaseType.equals("dm")) sql = RegexUtil.processDmServerSql(sql); + Object result = jdbcTemplate.queryForObject(sql, Object.class); + return ToInt32(result) <= 0; + } + return true; + } + + public String GetIdentityField(List> tabInfo) { + // 遍历数据表中的每一行 + for (Map row : tabInfo) { + // 判断当前行是否为标识列(isIdentity为true) + boolean isIdentity = toBoolean(get(row, "isIdentity", false)); + if (isIdentity) { + // 获取列名并转为小写返回 + return get(row, "name", "") + "".toLowerCase(); + } + } + // 未找到标识列时返回null + return null; + } + + private BaseResponse BuildAndSaveAoUData( + BaseModule module, + Map row, + List> dtColumns, + Map leftRecord, + List> dtTableColumns, + String identityField, + boolean apply, + Boolean isAdd, + int count, + int comfirmFlag + ) { + BaseResponse response = new BaseResponse(); + String idField = module.getIdField(); + String idValue = get(row, idField, "").toString(); + module.IdValue = (idValue); + +// out.println("row " + row + " " + idField + " isAddFlag start: " + idValue); + boolean isAddFlag = CheckIsAdd(module); // 需实现checkIsAdd方法 +// out.println("isAddFlag end: " + isAddFlag + " " + isAdd); + + // 处理添加/修改状态 + if ((isAdd != null && isAdd && !isAddFlag) || isAddFlag) { + isAddFlag = true; + if (idField.equalsIgnoreCase(identityField)) { + idValue = ""; + module.IdValue = (""); + row.put(idField, ""); + } + } + + // 日志记录(实际使用时补充用户信息) + sysLog(String.format("%s->%s%s%s", module.getTitle(), getUser().UserName, isAddFlag ? "添加" : "修改", idValue), "操作模块"); + + module.MasterData = (row); + getEventHandler().callBeforeModuleDataChange( + module, + isAddFlag ? SystemEnums.ActionType.Add : SystemEnums.ActionType.Update, + null + ); + +// out.println("GetAOUFields_TabInfo 1"); + // 获取字段列表 + List fieldList = GetAOUFields_TabInfo( + module, row, dtColumns, leftRecord, dtTableColumns, identityField, isAddFlag, null, 0 + ); +// out.println("GetAOUFields_TabInfo 2" + JSON.Encode(fieldList)); + // 验证字段 + String errMsg = fieldList.stream() + .filter(field -> field.ValidMsg != null && !field.ValidMsg.isEmpty()) + .map(field -> field.ValidMsg) // 改用lambda表达式获取属性值 + .collect(Collectors.joining("
")); + + if (!errMsg.isEmpty()) { + response.setMsg(errMsg); + response.setSuccess(false); + return response; + } + + // 旧版验证 + if (module.IdValue == null || module.IdValue.isEmpty()) { + if (!idField.equalsIgnoreCase(identityField) && !module.getNewVer()) { + response.setSuccess(false); + response.setMsg(String.format( + "旧版保存,非自增主键不能为空!请检查主键%s的【defaultdata】字段是否有效,或存储过程p_BaseSave是否有效!", + idField + )); + return response; + } + } + + // 构建保存SQL + StringBuilder updStrKey = new StringBuilder(); + String opSql = BuildSaveSql( + module, module.getMasterTable(), identityField, fieldList, isAddFlag, updStrKey + ); + log.debug(String.valueOf("response+opSql: " + opSql)); + + if (opSql != null && !opSql.isEmpty()) { + response = DataImpl.BaseDataSave(module, opSql, isAddFlag ? 1 : 2, comfirmFlag); +// out.println("response+opSql: " + JSON.Encode(response)); + // 日志记录(实际使用时补充用户信息) + sysLog(String.format( + "%s->%s%s%s%s@#@%s=>%s", + module.getTitle(), getUser().UserName, isAddFlag ? "添加" : "修改", idValue, + response.isSuccess() ? "成功" : ("失败:" + response.getMsg()), + opSql, JSON.Encode(row) + ), "操作模块"); + } else { + response.setSuccess(false); + response.setMsg(LanguageUtil.GetString("NoneField")); + } + + // 特殊响应处理 + if (response.isSuccess() && "99".equals(response.getOther() + "")) { + return response; + } + + // 失败重试逻辑 + if (!response.isSuccess()) { + if (response.getMsg().contains("已经存在编号为") && !isNullOrEmpty(module.IdValue) && count <= 1)//最多执行两次 + { + module.IdValue = ""; + row.put(idField, ""); + return BuildAndSaveAoUData(module, row, dtColumns, leftRecord, dtTableColumns, identityField, apply, isAdd, ++count, comfirmFlag); + } else if (response.getMsg().contains("EXECUTE 后的事务计数指示 BEGIN 和 COMMIT 语句的数目不匹配") && count <= 1)//try again + { + return BuildAndSaveAoUData(module, row, dtColumns, leftRecord, dtTableColumns, identityField, apply, isAdd, ++count, comfirmFlag); + } + return response; + } + + // 打印信息处理 + boolean hasPrint = response.getMsg() != null && + response.getMsg().toLowerCase().startsWith("printfile="); + + if (updStrKey.length() == 0) { + response.setOther(idValue); + module.IdValue = (idValue); + if (response.isSuccess() && hasPrint) { + // 核心调用代码 + response.setPrintInfo(GetPrintInfo( + module.getModuleId(), // 对应C#的module.ModuleId + PRINT_FILE_PATTERN.matcher(response.getMsg()).replaceAll(""), // 不区分大小写替换printfile=为空 + row // 行数据参数 + )); + response.setMsg(LanguageUtil.Success); + } + return response; + } + + // 处理保存结果 + Object other = response.getOther(); + if (!(other instanceof Map)) { + return response; + } + Map saveResult = (Map) other; + UpdStrModule updStrModule = saveResult.get(updStrKey.toString()); + + // 自增主键处理 + boolean getNewIdValue = true; + if (isAddFlag && module.IdValue != null && !module.IdValue.isEmpty() && + updStrModule != null && !module.getIdField().equals(identityField) && + identityField != null && !identityField.isEmpty() && + updStrModule.getIdentityId() != null && !updStrModule.getIdentityId().isEmpty()) { + + // 使用参数化查询防止SQL注入 + String sql = "select top 1 " + identityField + " from " + module.getMasterTable() + + " where " + module.getIdField() + " = ?"; + + // 执行查询并获取结果 + Object result = jdbcTemplate.queryForObject(sql, new Object[]{module.IdValue}, Object.class); + + // 转换为字符串(处理null情况) + String identityVal = (result != null) ? result.toString() : ""; + + if (identityVal != null && !identityVal.isEmpty()) { + updStrModule.setIdValue(identityVal); + updStrModule.setIdentityId(identityVal); + getNewIdValue = false; + } + } + + // 保存字段处理 + boolean saveSuccess = SaveStrFieldByPms(fieldList, updStrModule); + if (!hasPrint) { + String msg = saveSuccess ? + LanguageUtil.Success : + "数据已保存,但因自增主键原因导致部分字段以及附件未保存成功,请重新修改保存!"; + response.setMsg(msg); + } + + // 最终处理 + if (updStrModule != null && saveSuccess) { + String newIdValue = getNewIdValue ? updStrModule.getIdValue() : module.IdValue; + + if (getNewIdValue && (isAddFlag || (module.IdValue == null || module.IdValue.isEmpty())) && + !module.getIdField().equals(identityField) && + identityField != null && !identityField.isEmpty() && + updStrModule.getIdentityId() != null && !updStrModule.getIdentityId().isEmpty()) { + + // 使用参数化查询防止SQL注入 + String sql = "select " + module.getIdField() + " from " + module.getMasterTable() + + " where " + identityField + " = ?"; + + // 执行查询并获取结果 + Object result = jdbcTemplate.queryForObject(sql, new Object[]{updStrModule.getIdentityId()}, Object.class); + + // 转换为字符串(处理null情况,与原代码"+ ""效果一致) + newIdValue = (result != null) ? result.toString() : ""; + + newIdValue = (newIdValue == null || newIdValue.isEmpty()) ? module.IdValue : newIdValue; + module.IdValue = (newIdValue); + } + + if (isAddFlag && (module.IdValue == null || module.IdValue.isEmpty()) && + updStrModule.getIdValue() != null && !updStrModule.getIdValue().isEmpty()) { + module.IdValue = (updStrModule.getIdValue()); + } + + module.IdentityId = (updStrModule.getIdentityId()); + response.setOther(newIdValue); + row.put(module.getIdField(), module.IdValue); + + // 更新临时附件信息 + if (row.containsKey("temp_attachment_id")) { + String attcTempId = row.get("temp_attachment_id") != null ? + row.get("temp_attachment_id").toString() : ""; + if (!attcTempId.isEmpty()) { + UpdateTempAttcInfo(module.getModuleId(), attcTempId, module.IdValue); + } + } + + if (response.isSuccess() && hasPrint) { + response.setPrintInfo(GetPrintInfo( + module.getModuleId(), // 对应C#的module.ModuleId + PRINT_FILE_PATTERN.matcher(response.getMsg()).replaceAll(""), // 不区分大小写替换printfile=为空 + row // 行数据参数 + )); + response.setMsg(LanguageUtil.Success); + } + } + + return response; + } + + private static final Pattern PRINT_FILE_PATTERN = Pattern.compile("printfile=", Pattern.CASE_INSENSITIVE); + + /** + * 构建保存SQL语句 + * + * @param module 模块实体 + * @param tabName 表名 + * @param identityField 自增字段名 + * @param fieldList 字段列表 + * @param isAdd 是否为新增操作 + * @param updStrKey 输出参数:更新标识键 + * @return 构建的SQL语句 + */ + protected static String BuildSaveSql(ModuleEntity module, String tabName, String identityField, + List fieldList, boolean isAdd, StringBuilder updStrKey) { + String dataBaseName = WebConfigUtil_web.get("custom.database.type"); + boolean isDM = dataBaseName.equals("dm"); + log.debug(String.valueOf("BuildSaveSql")); + String opsql = ""; + String updInfoSql = ""; + updStrKey.setLength(0); // 清空输出参数 + + // 检查是否有特殊值字段 + boolean hasSpec = fieldList.stream().anyMatch(m -> m.hasSpecVal); + + // 构建更新信息SQL模板 + + updStrKey.append(UUID.randomUUID().toString()); + updInfoSql = String.format( + "select '%%s' %s, '%%s' %s, '%%s' %s, %%s %s, %%s %s;", + UpdStrModule.GuidKeyName, + UpdStrModule.TableNameName, + UpdStrModule.IdFieldName, + UpdStrModule.IdValueName, + UpdStrModule.IdentityIdName + ); +//out.println("isAdd : " + isAdd); + + if (isAdd) { + // 构建INSERT语句 + String sqlKey = fieldList.stream() + .map(m -> "[" + m.FiledName + "]") + .collect(Collectors.joining(",")); + + String sqlValue = fieldList.stream() + .map(m -> { +// out.println(JSON.Encode(m)); + Object val = m.SqlFieldVal; + return val != null ? val.toString() : ""; + }) + .collect(Collectors.joining(",")); + + opsql = String.format("insert into %s (%s) values (%s);", tabName, sqlKey, sqlValue); + + // 处理非BillModule的情况 + if (!(module instanceof BillModule)) { + if (!isNullOrEmpty(identityField)) { + opsql += !isDM ? + String.format(updInfoSql, + updStrKey.toString(), + tabName, + identityField, + "@@identity", + "@@identity") : + String.format(updInfoSql, + updStrKey.toString(), + tabName, + identityField, + "@@identity", + "@@identity"); + } else { + opsql += isDM ? + String.format(updInfoSql, + updStrKey.toString(), + tabName, + module.getIdField(), + "'" + module.IdValue + "'", + "@@identity") : + String.format(updInfoSql, + updStrKey.toString(), + tabName, + module.getIdField(), + "'" + module.IdValue + "'", + "@@identity"); + } + } + } else { + // 构建UPDATE语句 +// String setClause = fieldList.stream() +// .map(m -> String.format("[%s]=%s", m.FiledName, m.SqlFieldVal)) +// .collect(Collectors.joining(",")); + String setClause = fieldList.stream() + .map(m -> String.format("[%s]=%s", + m.FiledName, + (isNullOrEmpty(m.SqlFieldVal)) + ? m.pmFiledValue + : m.SqlFieldVal)) + .collect(Collectors.joining(",")); + log.debug(String.valueOf("BuildSaveSql(isAdd为false)setClause : " + setClause)); + if (isNullOrEmpty(setClause)) { + return opsql; + } + + opsql = String.format("update %s set %s where %s='%s';", + tabName, + setClause, + module.getIdField(), + module.IdValue); + log.debug(String.valueOf("opsql(else) : " + opsql)); + if (hasSpec) { + opsql += String.format(updInfoSql, + updStrKey.toString(), + tabName, + module.getIdField(), + "'" + module.IdValue + "'", + "'" + module.IdValue + "'"); + } else { + updStrKey.setLength(0); + } + } + log.debug(String.valueOf("opsql : " + opsql)); + return opsql; + } + + /** + * 获取打印信息 + * + * @param moduleId 模块ID + * @param printName 打印名称 + * @param record 记录数据 + * @return 打印信息哈希表 + */ + private Map GetPrintInfo(String moduleId, String printName, Map record) { + BaseResponse response = new BaseResponse(); + Map retData = new HashMap<>(); + Map saveRec = new HashMap<>(); + + // 获取打印SQL配置(转换为Hashtable并取第一条) + List> printSqls = DataImpl.GetPrintSqls(moduleId, "", DataImpl.IsBaseModule(moduleId)); + Map dtVal = printSqls != null && !printSqls.isEmpty() ? printSqls.get(0) : null; + + // 添加数据库服务器和数据库名信息 + retData.put("dbServer", WebConfigUtil_web.get("dbServer", "")); + retData.put("dbName", ConfigUtil.toConDict(getUser().ConnectionString).get("databasename")); + + // 构建保存记录 + saveRec.put("dllcoid", moduleId); + saveRec.put("printname", printName); + saveRec.put("operatorname", getUser().UserName); + saveRec.put("operatorid", getUser().UserId); + + // 获取模块信息并设置单据ID + ModuleBaseEntity module = GetModule(moduleId); + String idValue = String.valueOf(record.get(module.getIdField())); + saveRec.put("billdocumentId", idValue); + + // 处理SQL参数 + if (dtVal != null) { + for (Map.Entry entry : dtVal.entrySet()) { + String key = entry.getKey(); + String value = String.valueOf(entry.getValue()); + if (key.startsWith("sql") && !value.isEmpty()) { + // 处理查询SQL + value = dealQuerySql(value, record, null, null, null, null, false, false, false); + saveRec.put(key, value); + } + } + } +// 将Entry集合转换为Hashtable列表 + List> hashtableList = new ArrayList<>(); + for (Map.Entry entry : saveRec.entrySet()) { + Map hashtable = new HashMap<>(); + // 假设需要将键值对分别存入Hashtable的特定字段(根据实际业务调整字段名) + hashtable.put("keyField", entry.getKey()); // 例如存入"key"字段 + hashtable.put("valueField", entry.getValue()); // 例如存入"value"字段 + hashtableList.add(hashtable); + } + // 执行添加或更新操作 + response = AddOrUpdTable(hashtableList, "p_systemWebPrintTab", "id", true, null); + if (!response.isSuccess()) { + response.setMsg("操作失败,请重试!"); + return null; + } + + // 添加打印ID到返回数据 + retData.put("printId", Objects.toString(response.getOther(), "")); + + return retData; + } + + /** + * 添加或更新表数据 + * + * @param datas 数据列表(每个元素为一行数据的Hashtable) + * @param tabName 表名 + * @param idFields ID字段(多个用逗号分隔) + * @param isAdd 是否强制添加(null表示自动判断) + * @param onFinish 操作完成后的回调函数 + * @return 操作结果响应 + */ + public BaseResponse AddOrUpdTable(List> datas, String tabName, + String idFields, Boolean isAdd, + BiConsumer, Boolean> onFinish) { + BaseResponse response = new BaseResponse(); + if (tabName == null || tabName.isEmpty()) { + return response; + } + + // 获取表结构信息 + List> dtTableColumns = DataImpl.GetTableInfo(tabName); +// out.println("dtTableColumns不应该为空(sql没问题),这里是模拟数据库数据建立的 : " + dtTableColumns); + String identityField = GetIdentityField(dtTableColumns); +// out.println("identityField应该拿到dtTableColumns中的name给其赋值 : " + identityField); + // 处理ID字段 + if (idFields == null || idFields.isEmpty()) { + idFields = identityField; + } + String[] ids = idFields.toLowerCase().split(","); + String[] identityFieldsArr = identityField.split(","); + + for (Map row : datas) { + if (row == null) continue; + + // 判断是否为新增操作 + boolean isAddOperation = CheckIsAdd(tabName, BuildIdWhere(row, ids)); + if (Boolean.TRUE.equals(isAdd) && !isAddOperation) { + isAddOperation = true; + // 清空ID字段值以触发新增 + for (String id : ids) { + if (row.containsKey(id)) { + row.put(id, ""); + } + } + } + + List fieldList = BuildActionField( + row, dtTableColumns, tabName, + isAddOperation ? identityFieldsArr : ids, + isAddOperation + ); +// out.println("构建字段模型列表 end" + fieldList); + + if (fieldList == null) continue; + + // 构建操作SQL并获取更新键 +// out.println("构建操作SQL并获取更新键 start"); + String[] updStrKey = new String[1]; + String actionSql = BuildActionSql( + fieldList, + row, + tabName, + identityField, + updStrKey, + isAddOperation ? identityField : idFields, + isAddOperation + ); +// out.println("构建操作SQL并获取更新键 end " + actionSql); + + if (databaseType.equals("dm")) actionSql = RegexUtil.processDmServerSql(actionSql); + + // 执行SQL并处理结果 +// out.println("执行SQL并处理结果 1 : " + actionSql); + List>> dataSet = dbOperator.executeDataSet(actionSql); + response.setSuccess(true); + +// out.println("执行SQL并处理结果 end " + dataSet); + + Map saveResult = DataImpl.DecodeSaveResult(dataSet); + if (saveResult == null) continue; + +// out.println(JSON.Encode(saveResult) + " " + updStrKey[0]); + UpdStrModule updStrModule = saveResult.get(updStrKey[0]); + boolean saveSuccess = SaveStrFieldByPms(fieldList, updStrModule); + response.setSuccess(saveSuccess); + + if (updStrModule != null) { + response.setOther(updStrModule.getIdValue()); + response.setMsg(saveSuccess ? LanguageUtil.Success : LanguageUtil.Fail); + + // 执行回调函数 + if (onFinish != null) { + onFinish.accept(row, saveSuccess); + } + } + } + + return response; + } + + public BaseResponse AddOrUpdTable(String data, String tabName, String idFields, Boolean + isAdd, BiConsumer, Boolean> onFinish) { + BaseResponse response = new BaseResponse(); + data = data.startsWith("[") ? data : String.format("[%s]", data); + List> datas = (List>) JSON.Decode(data);//这里必须传一个数组 + if (datas == null || datas.isEmpty()) return response; + return AddOrUpdTable(datas, tabName, idFields, isAdd, onFinish); + } + + /** + * 构建ID条件语句 + * + * @param record 数据记录哈希表 + * @param idFields ID字段数组 + * @return 拼接后的WHERE条件字符串(带前缀" and ",若为空则返回空字符串) + */ + protected String BuildIdWhere(Map record, String[] idFields) { + try { + // 校验ID字段数组有效性 + if (idFields == null || idFields.length <= 0) { + return ""; + } + + List conditions = new ArrayList<>(); + for (String idf : idFields) { + // 跳过空字段名 + if (idf == null || idf.trim().isEmpty()) { + continue; + } + + // 获取字段值(不存在则为空字符串) + Object valueObj = record.containsKey(idf) ? record.get(idf) : ""; + String value = valueObj != null ? valueObj.toString() : ""; + + // 构建条件表达式:若值以"@@"开头则不加单引号,否则加单引号 + String condition; + if (value.startsWith("@@")) { + condition = String.format("%s=%s", idf, value); + } else { + condition = String.format("%s='%s'", idf, value); + } + conditions.add(condition); + } + + // 拼接所有条件,若有条件则添加" and "前缀 + if (conditions.isEmpty()) { + return ""; + } else { + return " and " + String.join(" and ", conditions); + } + } catch (Exception e) { + // 异常时返回空字符串 + return ""; + } + } + + /** + * 构建操作字段模型列表 + * + * @param source 源数据哈希表 + * @param dtTableColumns 表结构信息(模拟DataTable,用List表示) + * @param tabName 表名 + * @param ids ID字段数组 + * @param isAdd 是否为新增操作 + * @return 字段模型列表 + */ + protected List BuildActionField(Map source, + List> dtTableColumns, + String tabName, String[] ids, boolean isAdd) { + // 转换ID数组为小写Set,便于快速判断包含关系 + Set idSet = Arrays.stream(ids) + .map(String::toLowerCase) + .collect(Collectors.toSet()); + + return dtTableColumns.stream() + .filter(row -> { + // 获取列名并转为小写 + String colName = DataTableUtil.getStringValue(row, "name", "").toLowerCase(); + // 判断是否为计算列 + boolean isComputed = toBoolean(DataTableUtil.get(row, "isComputed", false)); + // 判断是否为自增列 + boolean isIdentity = toBoolean(DataTableUtil.get(row, "isIdentity", false)); + + // 过滤条件:排除ID字段、自增列、计算列;修改时还需排除源数据中不存在的字段 + return !(idSet.contains(colName) + || isIdentity + || isComputed + || (!isAdd && !source.containsKey(colName))); + }) + .map(row -> { + String name = DataTableUtil.getStringValue(row, "name", "").toLowerCase(); + boolean isNullAble = toBoolean(DataTableUtil.get(row, "isnullable", false)); + int length = Integer.parseInt(DataTableUtil.get(row, "length", 0).toString()); +// int sqlType = DataTableUtil.getIntValue(row, "xtype", Types.VARCHAR); + int sqlType; + if (databaseType.equals("dm")) { + sqlType = PublicUtil.DMSqltypeToProType(Objects.toString(get(row, "xtype"))); + } else { + sqlType = DataTableUtil.getIntValue(row, "xtype", Types.VARCHAR); + } + // 从源数据获取字段值,不存在则为空字符串 + Object value = source.containsKey(name) ? source.get(name) : ""; + + // 处理字符串类型值(trim) + if (value instanceof String) { + value = ((String) value).trim(); + } + + // 处理空值默认值(操作人、操作时间等) + String valueStr = value != null ? value.toString() : ""; + if (valueStr.isEmpty()) { + switch (name) { + case "operatorid": + value = getUser().UserId; // 假设存在user对象及getUserId()方法 + break; + case "operatorname": + value = getUser().UserName; // 假设存在user对象及getUserName()方法 + break; + case "operatedate": +// value = new Date(); // 使用当前时间 + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + value = sdf.format(new Date()); // 格式化当前时间 + break; + } + } + + // 转换SQL类型为Java类型 + Class rowType = PublicUtil.SqlxtypeToProType(sqlType); + + // 创建字段模型 + FieldModel model = new FieldModel(); + model.ctype = (rowType); + model.FieldLength = (length); + model.IsNullAble = (isNullAble); + model.FiledName = (name); + model.setFiledValue(value); + + // 验证字段长度 + int[] valueLen = new int[1]; // 用数组存储引用类型的长度结果 + if (!model.validLength(valueLen)) { + // 表字段长度不够的处理逻辑(原代码未实现,此处保持空实现) + } + + return model; + }) + .filter(Objects::nonNull) // 过滤空模型 + .collect(Collectors.toList()); + } + + /** + * 构建操作SQL语句(新增或更新) + * + * @param fieldList 字段模型列表 + * @param source 源数据哈希表 + * @param tabName 表名 + * @param identityField 自增字段名 + * @param updStrKey 输出参数:更新操作的唯一标识键 + * @param idFields ID字段(逗号分隔字符串) + * @param isAdd 是否为新增操作 + * @return 构建的SQL语句 + */ + protected String BuildActionSql(List fieldList, + Map source, + String tabName, + String identityField, + String[] updStrKey, // 使用数组作为输出参数容器 + String idFields, + boolean isAdd) { + // 初始化输出参数(默认空字符串) + updStrKey[0] = ""; + + // 处理默认参数,Java不支持方法参数默认值,这里通过重载或直接赋值处理 + if (idFields == null) { + idFields = ""; + } + + String updateSql = ""; + String[] ids = idFields.split(","); + + // 判断是否有特殊值字段 + boolean hasSpec = fieldList.stream().anyMatch(m -> m.hasSpecVal); + + updStrKey[0] = (UUID.randomUUID().toString()); + + // 构建updInfoSql,使用%%s保留二次替换占位符 + String updInfoSql = String.format( + "select '%%s' %s, '%%s' %s, '%%s' %s, '%%s' %s;", + UpdStrModule.GuidKeyName, + UpdStrModule.TableNameName, + UpdStrModule.IdFieldName, + UpdStrModule.IdValueName + ); + if (isAdd) { + // 处理插入逻辑 + String sqlKey = fieldList.stream() + .map(m -> "[" + m.FiledName + "]") + .collect(Collectors.joining(",")); + + String sqlValue = fieldList.stream() + .map(m -> m.SqlFieldVal.toString()) + .collect(Collectors.joining(",")); + + updateSql = String.format("insert into %s (%s) values (%s);", tabName, sqlKey, sqlValue); + + // 构建updInfoSql2,注意第四个参数没有单引号 + String updInfoSql2 = String.format( + "select '%%s' %s, '%%s' %s, '%%s' %s, %%s %s;", + UpdStrModule.GuidKeyName, + UpdStrModule.TableNameName, + UpdStrModule.IdFieldName, + UpdStrModule.IdValueName + ); + + // 执行二次替换 + updateSql += String.format(updInfoSql2, + updStrKey[0], + tabName, + identityField, + "@@identity" + ); + } else { + // 处理更新逻辑 + updateSql = fieldList.stream() + .map(m -> String.format("[%s]=%s", m.FiledName, m.SqlFieldVal)) + .collect(Collectors.joining(",")); + + if (updateSql.isEmpty()) { + return updateSql; + } + + updateSql = String.format( + "update %s with(rowlock) set %s where 1=1 %s;", + tabName, + updateSql, + BuildIdWhere(source, ids) + ); + + if (hasSpec) { + // 执行二次替换 + updateSql += String.format(updInfoSql, + updStrKey[0], + tabName, + idFields, + GetIdValues(source, ids) + ); + } else { + updStrKey[0] = ""; // 清空 + } + } + + return updateSql; + } + + /** + * 获取ID字段对应的值,拼接成逗号分隔的字符串 + * + * @param record 包含字段值的哈希表 + * @param idFields ID字段数组 + * @return 拼接后的ID值字符串,异常时返回空字符串 + */ + protected String GetIdValues(Map record, String[] idFields) { + try { + // 处理空数组情况 + if (idFields == null || idFields.length <= 0) { + return ""; + } + + // 过滤非空字段,获取对应值并拼接 + return Arrays.stream(idFields) + .filter(idf -> idf != null && !idf.isEmpty()) // 排除空字段名 + .map(idf -> { + // 从哈希表获取值,不存在则返回空字符串 + Object value = record.containsKey(idf) ? record.get(idf) : ""; + // 转换为字符串(处理null值) + return value != null ? value.toString() : ""; + }) + .collect(Collectors.joining(",")); // 用逗号拼接 + } catch (Exception e) { + // 捕获所有异常,返回空字符串 + return ""; + } + } + + /** + * 将特殊字符串数据以pms形式保存 + * + * @param fieldList 字段模型列表 + * @param module 更新字符串模块对象 + * @return 操作是否成功(true表示成功或无需操作,false表示执行失败) + */ + protected boolean SaveStrFieldByPms(List fieldList, UpdStrModule module) { + String pmOpSql = ""; + if (module == null || !module.Valide()) { + return true; + } + + // 筛选出有特殊值的字段 + List stringFields = fieldList.stream() + .filter(m -> m.hasSpecVal) + .collect(Collectors.toList()); + + if (!stringFields.isEmpty()) { + // 构建参数和SQL + List params = new ArrayList<>(); + List setClauses = new ArrayList<>(); + + for (FieldModel field : stringFields) { + setClauses.add(String.format("[%s] = ?", field.FiledName)); + params.add(field.getPmName()); // 添加参数值 + } + + pmOpSql = String.join(",", setClauses); + + if (!pmOpSql.isEmpty()) { + pmOpSql = String.format( + "update %s set %s where 1=1 %s", + module.getTableName(), + pmOpSql, + BuildIdWhere(module.getIdField(), module.getIdValue()) + ); + } + + // 执行更新 + if (!pmOpSql.isEmpty()) { + int affectedRows = jdbcTemplate.update( + pmOpSql, + params.toArray() + ); + return affectedRows > 0; + } + } + + return true; + } + + /** + * 构建ID条件的WHERE子句片段 + * + * @param idFields ID字段名,多个以逗号分隔 + * @param idVals ID值,多个以逗号分隔,与字段一一对应 + * @return 构建好的WHERE条件片段(如" and id='1' and name='test'"),异常时返回空字符串 + */ + protected String BuildIdWhere(String idFields, String idVals) { + try { + // 分割字段名和值数组 + String[] ids = idFields.split(","); + String[] vals = idVals.split(","); + + List conditionList = new ArrayList<>(); + + for (int i = 0; i < ids.length; i++) { + // 获取当前索引对应的值,超出范围则用空字符串 + String currentVal = (vals.length > i) ? vals[i] : ""; + + // 构建条件:如果值以@@开头则不加单引号,否则加单引号 + String condition; + if (currentVal.startsWith("@@")) { + condition = String.format("%s=%s", ids[i], currentVal); + } else { + condition = String.format("%s='%s'", ids[i], currentVal); + } + + conditionList.add(condition); + } + + // 拼接所有条件,用and连接 + String conditions = String.join(" and ", conditionList); + + // 如果有条件则添加前缀"and ",否则返回空字符串 + return conditionList.size() > 0 ? " and " + conditions : ""; + } catch (Exception e) { + // 捕获所有异常,返回空字符串 + return ""; + } + } + + private void UpdateTempAttcInfo(String moduleId, String tempId, String newId) { + try { + // 获取临时附件文件信息 + List> infos = DataImpl.GetTempAttcFiles(tempId); + if (infos.isEmpty()) { + return; + } + + // 提取物种编号 + String specNo = String.valueOf(infos.get(0).getOrDefault("speciesno", "")); + + // 获取旧文件路径及目录ID + String[] oldDirTabIdHolder = new String[1]; // 用于接收输出参数 + String oldFilePath = DataImpl.GetAcFileFolder(moduleId, tempId, specNo, oldDirTabIdHolder); + String oldDirTabId = oldDirTabIdHolder[0]; + + // 更新目录表中的父ID和名称 + String updateDirSql = "update P_fm_DirectoryTab set sname = ?, pid = ? where pid = ?"; + jdbcTemplate.update(updateDirSql, newId, newId, tempId); + + // 获取新文件路径及目录ID + String[] dirTabIdHolder = new String[1]; // 用于接收输出参数 + String newFilePath = DataImpl.GetAcFileFolder(moduleId, newId, specNo, dirTabIdHolder); + String dirTabId = dirTabIdHolder[0]; + + // 处理文件路径信息 + StringBuilder fileName = new StringBuilder("temp.temp"); + StringBuilder oldRelPath = new StringBuilder(), oldWebPath = new StringBuilder();// 0:oldSavepath, 1:oldRelPath, 2:oldWebPath + String oldSavepath = FileUtil.getFileSavePath( + oldFilePath, null, getAppDomain(), getAttcPath(), + fileName, oldRelPath, oldWebPath// 输出参数:pathInfo[1] = oldRelPath, pathInfo[2] = oldWebPath + ); + + // 记录系统日志 + sysLog(String.format( + "%s在模块%s记录%s移动附件%s", + getUser().UserName, moduleId, newId, fileName + ), "移动临时附件"); + + // 遍历处理每个文件 + for (Map row : infos) { + String fname = String.valueOf(row.getOrDefault("sname", "")), newFName = fname; + String parentid = String.valueOf(row.getOrDefault("parentid", "")); + String oldrelpath = URLDecoder.decode(String.valueOf(row.getOrDefault("webpath", "")), StandardCharsets.UTF_8); + + //String sourcePath = oldRelPath.toString().replace(fileName, fname); + String _filename = GetAttcFileName(moduleId, newId, fname); + if (!isNullOrEmpty(_filename)) { + newFName = _filename; + } + // 移动文件 + BaseResponse response = FileUtil.moveTo( + oldRelPath.toString().replace(fileName, fname), + newFilePath, newFName, getAttcPath(), true + ); + + // 更新文件表记录 + if (!isNullOrEmpty(response.getData() + "") && (response.getData() + "" != "9")) { + String updateFileSql = "update P_fm_FileTab set parentid = ?, webpath = ? where fileId = ?"; + jdbcTemplate.update( + updateFileSql, + dirTabId, + response.getData().toString(), + String.valueOf(row.getOrDefault("fileId", "")) + ); + } + } + + // 删除历史遗留临时文件(使用参数化查询处理日期) + String deleteOldFilesSql = "delete f from P_fm_FileTab f " + + "inner join P_fm_DirectoryTab p on f.parentid = p.dirId " + + "where CHARINDEX('temp_', p.pid) = 1 " + + "and p.CreateTime < ?; " + + "delete P_fm_DirectoryTab " + + "where CHARINDEX('temp_', pid) = 1 " + + "and CreateTime < ?"; + + // 计算一天前的日期 + LocalDateTime oneDayAgo = LocalDateTime.now().minusDays(1); + Timestamp timestamp = Timestamp.valueOf(oneDayAgo); + + jdbcTemplate.update(deleteOldFilesSql, timestamp, timestamp); + + // 删除临时目录 + String filePath = WebConfigUtil_web.getFilePath(); + FileUtil.deleteFloder(filePath, dirInfo -> + dirInfo.getName().startsWith("temp_") && + dirInfo.lastModified() < System.currentTimeMillis() - 24 * 60 * 60 * 1000L + ); + + } catch (Exception e) { + log.warn(String.valueOf("附件上传时出错" + e.getMessage())); + } + } + + /** + * 获取附件文件名(核心逻辑,对应原C# GetAttcFileName) + * + * @param moduleId 模块ID + * @param idVal ID值 + * @param fileName 文件名 + * @return 处理后的附件名(无结果返回空字符串) + */ + public String GetAttcFileName(String moduleId, String idVal, String fileName) throws SQLException { + BaseResponse response = new BaseResponse(); + String proName = "p_GetAttcName"; + + // 判断存储过程是否存在 + if (DataImpl.IsExitPro(proName)) { + // 构造存储过程参数 + Object[] vals = new Object[]{moduleId, idVal, fileName, getUser().getUserId()}; + // 执行存储过程 + response = DataImpl.excuteStore(proName, vals); + + // 解析返回结果 + if (response.getData() != null) { + // C# DataSet → Java List>>(DataSet=表列表,Table=行列表) + List>> ds = (List>>) response.getData(); + + // 判断是否有数据表且有数据行 + if (ds != null && !ds.isEmpty() && !ds.get(0).isEmpty()) { + // 获取第一张表的第一行数据 + Map firstRow = ds.get(0).get(0); + + // 优先取"n"字段值 + String newName = Objects.toString(firstRow.get("n"), ""); + + // "n"字段为空时,取第一列的值 + if (newName == null || newName.trim().isEmpty()) { + // 获取第一列的列名(模拟ds.Tables[0].Columns[0].ColumnName) + String firstColumnName = firstRow.keySet().iterator().next(); + newName = Objects.toString(firstRow.get(firstColumnName), ""); + } + + return newName; + } + } + + // ------------------- 注释内的备选存储过程调用逻辑(可选实现) ------------------- + /* + // 获取存储过程参数列表 + List pmList = dbOperator.getStoreParams(proName); + + // 创建输出参数和返回值参数 + DbParameter msgpm = dbOperator.getParameter("@msg", "", DbType.STRING, 5000, ParameterDirection.OUTPUT); + DbParameter returnval = dbOperator.getParameter("@return", -1, DbType.INTEGER, 4, ParameterDirection.RETURN_VALUE); + + int i = 0; + for (int j = 0; j < pmList.size(); j++) { + DbParameter pm = pmList.get(j); + if (pm.getDirection() == ParameterDirection.RETURN_VALUE) { + pmList.set(j, returnval); + } else if (pm.getParameterName().equals("@msg")) { + pmList.set(j, msgpm); + } else { + pm.setValue(vals[i]); + i++; + } + } + + // 执行存储过程获取DataSet + List>> ds = dbOperator.executeDataSet( + proName, + CommandType.STORED_PROCEDURE, + pmList, + proName + ); + + // 解析结果(和核心逻辑一致) + if (ds != null && !ds.isEmpty() && !ds.get(0).isEmpty()) { + Map firstRow = ds.get(0).get(0); + String newName = Objects.toString(firstRow.get("n"), ""); + if (newName == null || newName.trim().isEmpty()) { + String firstColumnName = firstRow.keySet().iterator().next(); + newName = Objects.toString(firstRow.get(firstColumnName), ""); + } + return newName; + } else { + return ""; + } + */ + } + + // 存储过程不存在/无结果时返回空字符串 + return ""; + } + + /** + * 批量添加或更新明细数据 + * + * @param module 基础模块实体 + * @param parentRow 父表数据 + * @param details 明细数据集合 + * @param isAdd 是否为新增操作 + * @param response 响应对象(引用传递) + * @return 处理结果(1:成功,0:失败) + */ + public int BatchAddOrUpdDetail(BaseModule module, Map parentRow, + Map details, Boolean isAdd, BaseResponse response) throws + UnsupportedEncodingException, CusException { + // 遍历所有明细项 + for (Map.Entry entry : details.entrySet()) { + String detailModuleId = entry.getKey(); + // 跳过明细ID字段 + if (detailModuleId.endsWith("_detailId")) { + continue; + } + + // 获取明细ID + String detailIdKey = detailModuleId + "_detailId"; + Object detailIdObj = details.get(detailIdKey); + String detailId = detailIdObj != null ? detailIdObj.toString() : ""; + + if (detailId.isEmpty() || detailModuleId.isEmpty()) { + continue; + } + + // 转换明细数据为列表 + List> detailData = (List>) entry.getValue(); + List> newDetailData = new ArrayList<>(); + + if (detailData == null || detailData.isEmpty()) { + continue; + } + + // 获取明细模块信息 + BaseModule detalModule = GetBaseModule(detailModuleId, module.getMenuId()); + if (detalModule == null) { + response.setSuccess(false); + response.setMsg("未找到明细模块" + detailModuleId + ",添加失败"); + return 0; + } + + // 获取明细模块详情 + detalModule.DetailModule = GetBaseDetailModuel(Integer.parseInt(detailId)); + if (detalModule.DetailModule == null) { + continue; + } + + // 处理关联字段 + String unionField = detalModule.DetailModule.getUnionField() + .replace("{", "") + .replace("}", "") + .toLowerCase(); + String unionParentFiled = detalModule.DetailModule.getUnionParentField() + .replace("{", "") + .replace("}", "") + .toLowerCase(); + + if (unionParentFiled.isEmpty()) { + unionParentFiled = module.getIdField(); + } + + // 获取表结构信息 + List> tableInfos = DataImpl.GetTableInfo(detalModule.getMasterTable()); + String identityField = GetIdentityField(tableInfos); + List> dtColumns = GetColumnRows(detalModule.getModuleId()); + + // 获取主键字段 + String valueMember = ""; + Optional> pkRow = dtColumns.stream() + .filter(row -> { + Object fieldType = row.get("FieldType"); + return fieldType != null && + fieldType.toString().equalsIgnoreCase(String.valueOf(SystemEnums.ControlType.LabTreeType.ordinal())); + }) + .findFirst(); + + if (pkRow.isPresent()) { + valueMember = pkRow.get().getOrDefault("ValueMember", "").toString(); + } + + // 构建列信息 + List colInfos = BuildBillDetailColInfos(identityField, dtColumns, tableInfos, null); + + // 处理每条明细数据 + for (Map dData : detailData) { + if (dData == null) { + continue; + } + + StringBuilder errBuilder = new StringBuilder(); + List fieldList = GetAOUFields_ColInfo( + module, colInfos, dData, null, valueMember, identityField, + isAdd != null ? isAdd : false, errBuilder); + + if (fieldList == null || fieldList.isEmpty()) { + continue; + } + + // 设置关联字段值 + Object unionFieldValue = dData.get(unionField); + if (unionFieldValue == null || unionFieldValue.toString().isEmpty()) { + dData.put(unionField, parentRow.get(unionParentFiled)); + } + newDetailData.add(dData); + } + + // 执行添加或更新操作 + BaseResponse detailResponse = AddOrUpd( + detalModule, newDetailData, null, parentRow, false, isAdd, 0, false); + + if (!detailResponse.isSuccess()) { + response.setSuccess(false); + response.setMsg("明细模块" + detailModuleId + ",添加失败" + detailResponse.getMsg()); + return 0; + } + + // 处理特殊响应码 + if (response.isSuccess() && "99".equals(response.getOther() + "")) { + return 0; + } + } + + return 1; + } + + /** + * 获取AOU字段的列信息 + */ + protected List GetAOUFields_ColInfo(ModuleEntity module, + List colList, + Map source, + Map leftRecord, + String identityField, + String treeValueMember, + boolean isAdd, + StringBuilder errBuilder) throws UnsupportedEncodingException { + String idField = module.getIdField(); + List fieldList = new ArrayList<>(); + + for (ColInfos colInfo : colList) { + if (colInfo == null) { + continue; + } + + String colname = colInfo.colname; + Map colrow = colInfo.colrow; // 假设ColInfos中colrow为Map类型 + boolean isNullable = colInfo.isnullable; + int length = colInfo.length; + boolean cfgNullAble = colInfo.cfgNullAble; + String colCnName = colInfo.colCnName; + Field field = colInfo.field; + Class dataType = (Class) colInfo.dataType; + // 排除主键和自增主键 + if (colname.equalsIgnoreCase(identityField) || + (!isAdd && (!source.containsKey(colname) || colname.equalsIgnoreCase(idField)))) { + continue; + } + + Object fieldValue = source.containsKey(colname) ? source.get(colname) : ""; + + if (colrow != null && field != null) { + // 处理树节点编号,避免重复 + if (isAdd && field.getFieldType() == SystemEnums.ControlType.LabTreeType.ordinal() && + !isNullOrEmpty(field.getDataSource()) && leftRecord != null) { + + if (!isNullOrEmpty(field.getDataSource()) && + (isNullOrEmpty(fieldValue + "") || + ToInt32(jdbcTemplate.update(String.format("select count(1) from %s where %s='%s'", + module.getMasterTable(), + field.getName(), + fieldValue + ""), Object.class)) > 0)) { + + fieldValue = createControl.GetTreeSpecNo(module.getMasterTable(), + field.getName(), + field.getValueField(), + field.getDataSource(), + String.valueOf(leftRecord.get(field.getValueField())), + dataType, 0); + } + } + // 处理默认值 + else if (isNullOrEmpty(String.valueOf(fieldValue)) && colname.equals(idField)) { + String defaultVal = String.valueOf(field.getDefaultsource()); + if (defaultVal.contains("parent.key") && !isNullOrEmpty(treeValueMember)) { + defaultVal = defaultVal.toLowerCase().replace("{parent.key}", + String.format("{parent.%s}", treeValueMember)); + } + ModuleEntity tempModule = new ModuleEntity(); + tempModule.setLeftRecord(leftRecord); + fieldValue = createControl.GetDefaultValue(defaultVal, tempModule, SystemTypeEnums.PmType.store); + } + } + + // 设置模块ID值 + if (colname.equals(idField) && isAdd) { + module.IdValue = (String.valueOf(fieldValue)); + } + + // 密码加密处理 + if ("password".equals(colname) && String.valueOf(fieldValue).length() < 32) { + fieldValue = SafetyUtil.encryptPassword(String.valueOf(fieldValue)); + } + + // 添加时跳过可为空且值为空的字段(主键除外) + if (isAdd && isNullable && isNullOrEmpty(String.valueOf(fieldValue)) && !colname.equals(idField)) { + continue; + } + + // 构建字段模型 + FieldModel model = new FieldModel(); + model.IsNullAble = (isNullable); + model.FiledName = (colname); + model.dbType = (colInfo.dbType); + model.ctype = (dataType); + model.FieldLength = (length); + model.setFiledValue(fieldValue); + + // 验证非空 + if (!cfgNullAble && isNullOrEmpty(String.valueOf(fieldValue))) { + model.ValidMsg = (colCnName + "不能为空!"); + } + + // 处理图片字段URL编码 + if (!isNullOrEmpty(model.ValidMsg) && field != null && + (field.getFieldType() == SystemEnums.ControlType.LabPic.ordinal() || + field.getFieldType() == SystemEnums.ControlType.LabPicEx.ordinal())) { + + String fieldVal = model.hasSpecVal ? String.valueOf(model.pmFiledValue) : String.valueOf(model.getFiledValue()); + if (!isNullOrEmpty(fieldVal) && fieldVal.indexOf("%") < 0) { + // 处理引号和分割(替换原有的trim方法) + fieldVal = fieldVal.replaceAll("^[\"']+|[\"']+$", ""); + String[] fVal = fieldVal.split(","); + + for (int i = 0; i < fVal.length; i++) { + String[] vs = fVal[i].split("\\?"); + if (vs.length > 1) { + fVal[i] = FileUtil.urlEncode(vs[0], false) + "?" + vs[1]; + } else { + fVal[i] = FileUtil.urlEncode(vs[0], false); + } + } + model.setFiledValue(fVal); + } + } + + // 收集错误信息 + if (!isNullOrEmpty(model.ValidMsg)) { + errBuilder.append(model.ValidMsg).append("\n"); + } + + fieldList.add(model); + } + + return fieldList; + } + + /** + * 获取单据明细组装SQL需要的各类信息 + * + * @param identityField 自增主键(C#的string) + * @param dtColumns 列配置(C#的DataTable → Java的List>) + * @param dtTableColumns 数据库列配置(单据明细临时表) + * @param eqDtTableColumns 比较的数据库列配置(单据明细正式表,可为null) + * @return 列信息列表(List) + */ + protected List BuildBillDetailColInfos( + String identityField, + List> dtColumns, + List> dtTableColumns, + List> eqDtTableColumns) { + + // 1. 过滤dtTableColumns:排除colstat=1/4、计算列、自增列、自增主键列 + List colInfos = dtTableColumns.stream() + .filter(row -> { + // 列名(转为小写,对应C#的ToLower()) + String colname = Objects.toString(row.get("name"), "").toLowerCase(); + // colstat转换为int(对应C#的ToInt32()) + int colstat = ToInt32(row.get("colstat")); + // 是否为计算列(isComputed) + boolean isComputed = toBoolean(row.get("isComputed")); + // 是否为自增列(isIdentity) + boolean isIdentity = toBoolean(row.get("isIdentity")); + // 排除条件:colstat=1/4、计算列、自增列、等于自增主键 + return colstat != 1 + && colstat != 4 + && !isComputed + && !isIdentity + && !colname.equalsIgnoreCase(identityField); + }) + // 2. 转换过滤后的行→ColInfos对象(对应C#的Select) + .map(row -> { + String colname = Objects.toString(row.get("name"), "").toLowerCase(); + int length = ToInt32(row.get("length")); // 列长度 + String tabDefaultVal = Objects.toString(row.get("text"), ""); // 表默认值 + boolean isnullable = toBoolean(row.get("isnullable")); // 数据库可空性 + int rowType; //= ToInt32(row.get("xtype")); // 数据库类型(xtype) + if (databaseType.equals("dm")) { + rowType = PublicUtil.DMSqltypeToProType(DataTableUtil.getStringValue(row, "xtype", "")); + } else { + rowType = DataTableUtil.getIntValue(row, "xtype", 0); + } + // 3. 处理eqDtTableColumns:若不为null,从正式表列配置覆盖rowType和isnullable + if (eqDtTableColumns != null && !eqDtTableColumns.isEmpty()) { + // 查找正式表中与当前列名匹配的行(忽略大小写) + Map eqRow = eqDtTableColumns.stream() + .filter(r -> Objects.toString(r.get("name"), "").toLowerCase().equals(colname)) + .findFirst() + .orElse(null); + + if (eqRow != null) { + // 覆盖数据库类型和可空性(250122改的逻辑) + if (databaseType.equals("dm")) { + rowType = PublicUtil.DMSqltypeToProType(DataTableUtil.getStringValue(eqRow, "xtype", DataTableUtil.getStringValue(row, "xtype", ""))); + } else { + rowType = DataTableUtil.getIntValue(eqRow, "xtype", rowType); + } + //rowType = ToInt32(eqRow.get("xtype")); + isnullable = toBoolean(eqRow.get("isnullable")); + } + } + + // 4. 处理dtColumns:获取列配置信息(colrow、field、cfgNullAble、colCnName) + boolean cfgNullAble = true; // 默认配置可空 + String colCnName = colname; // 默认中文名为列名 + Field field = null; + Map colrow = null; + + if (dtColumns != null && !dtColumns.isEmpty()) { + // 查找dtColumns中与当前列名匹配的行(fieldname忽略大小写) + colrow = dtColumns.stream() + .filter(r -> Objects.toString(get(r, "fieldname", ""), "").toLowerCase().equals(colname)) + .findFirst() + .orElse(null); + + if (colrow != null) { + // 初始化Field对象(对应C#的new Field(colrow)) + field = new Field(colrow); + // 若控件未隐藏,读取配置的非空性和中文名称 + if (!field.getControlHidden()) { + // cfgNullAble = !colrow.Get("Nullable", 0).ToBoolean() + cfgNullAble = !toBoolean(get(colrow, "Nullable", 0)); + // 列中文名称(FieldCaption) + colCnName = Objects.toString(get(colrow, "FieldCaption", ""), colname); + } + } + } + + // 5. 调整isnullable:若数据库列非空但有默认值,且配置可空→设为可空 + if (!isnullable && !tabDefaultVal.isEmpty() && cfgNullAble) { + isnullable = true; + } + + // 6. 数据库类型→Java数据类型(对应C#的SqlxtypeToProType) + Class dataType = PublicUtil.SqlxtypeToProType(rowType); + + // 7. 组装ColInfos对象并返回 + ColInfos colInfo = new ColInfos(); + colInfo.colname = (colname); + colInfo.colrow = (colrow); + colInfo.field = (field); + colInfo.length = (length); + colInfo.colCnName = (colCnName); + colInfo.cfgNullAble = (cfgNullAble); + colInfo.isnullable = (isnullable); + colInfo.dataType = (dataType); + colInfo.dbType = (rowType); + + return colInfo; + }) + // 8. 转换为List(对应C#的ToList()) + .collect(Collectors.toList()); + + return colInfos; + } + + private BaseResponse Deal99Response(BaseResponse response, Map record, String moduleId) throws + CusException { + // message格式示例: 发现重要参数未进行填写,是否现在填写?;module=TEST001_01^Lskj.PubAdd3.dll^详细信息填写^1^0^TEST001_01^#select ' + QUOTENAME(@packageNo, '''') + ' as packageNo + // message拆分说明: 提示信息;模块参数(^分隔) + // 模块参数结构: 模块编号^调用模板dll^模块标题^调用标记^参数1^参数2^... + if ("99".equals(String.valueOf(response.getOther()))) { + String rMsg = response.getMsg(); + // 检查参数格式是否包含分隔符 + if (rMsg.indexOf(';') < 0) { + response.setMsg("过程返回参数配置不正确!请检查!\n" + response.getMsg()); + return response; + } + + // 拆分提示信息和模块参数 + String[] msgParts = rMsg.split(";", 2); + response.setMsg(msgParts[0]); + + // 处理模块参数,不足部分用空字符串填充(最多14个参数) + String[] params = (msgParts[1] + "^^^^^^^^^^^^").split("\\^"); // 12个^用于补充空参数 + + // 构建右键菜单按钮对象 + SysPoPupMenuBtn btn = new SysPoPupMenuBtn(); + btn.MenuBtnId = 0; + btn.dllname = getParamValue(params, 1); // 调用模板dll + btn.setText(getParamValue(params, 2)); // 模块标题 + btn.action = (getParamValue(params, 3)); // 调用标记 + btn.actiontype = 3; // 固定动作类型 + btn.dllpar1 = getParamValue(params, 4); // 参数1 + btn.dllpar2 = getParamValue(params, 5); // 参数2 + btn.dllpar3 = getParamValue(params, 6); // 参数3 + btn.dllpar4 = getParamValue(params, 7); // 参数4 + btn.dllpar5 = getParamValue(params, 8); // 参数5 + btn.dllpar6 = getParamValue(params, 9); // 参数6 + btn.dllpar7 = getParamValue(params, 10); // 参数7 + btn.dllpar8 = getParamValue(params, 11); // 参数8 + btn.dllpar9 = getParamValue(params, 12); // 参数9 + btn.dllpar10 = getParamValue(params, 13);// 参数10 + + // 处理上下文菜单按钮 + DealContextMenuBtn(btn, record, null); + + // 执行菜单点击逻辑并设置返回数据 + response.setData(GoContextMenuClick(btn, record, null, moduleId, 0).getData()); + } + return response; + } + + /** + * 安全获取参数数组中的值,避免数组越界 + */ + private String getParamValue(String[] params, int index) { + return (index < params.length) ? params[index] : ""; + } + + private SysPoPupMenuBtn DealContextMenuBtn(SysPoPupMenuBtn + btn, Map record, Hashtable leftRecord) { + // 处理leftRecord默认值(Java不支持方法参数默认值,通过重载或内部判断实现) + if (leftRecord == null) { + leftRecord = new Hashtable<>(); + } + + if (btn != null) { + SystemTypeEnums.PmType pmtype = SystemTypeEnums.PmType.sql; + + // 根据actiontype确定参数处理类型 + switch (btn.actiontype) { + case 0: + pmtype = SystemTypeEnums.PmType.sql; + break; + case 1: + pmtype = SystemTypeEnums.PmType.store; + break; + case 2: + case 3: + case 4: + pmtype = SystemTypeEnums.PmType.program; + break; + } + + // 处理dllname + btn.dllname = createControl.GetDefaultValue((btn.dllname == null ? "" : btn.dllname), record, leftRecord, SystemTypeEnums.PmType.store); + + // 处理action + if (pmtype == SystemTypeEnums.PmType.store) { + // 保持原值不变 + } else { + btn.action = createControl.GetDefaultValue(btn.action, record, leftRecord, + pmtype == SystemTypeEnums.PmType.store ? SystemTypeEnums.PmType.ignorenull : pmtype); + } + + // 处理所有参数 + btn.dllpar1 = createControl.GetDefaultValue(btn.dllpar1, record, leftRecord, pmtype); + btn.dllpar2 = createControl.GetDefaultValue(btn.dllpar2, record, leftRecord, pmtype); + btn.dllpar3 = createControl.GetDefaultValue(btn.dllpar3, record, leftRecord, pmtype); + btn.dllpar4 = createControl.GetDefaultValue(btn.dllpar4, record, leftRecord, pmtype); + btn.dllpar5 = createControl.GetDefaultValue(btn.dllpar5, record, leftRecord, pmtype); + btn.dllpar6 = createControl.GetDefaultValue(btn.dllpar6, record, leftRecord, pmtype); + btn.dllpar7 = createControl.GetDefaultValue(btn.dllpar7, record, leftRecord, pmtype); + btn.dllpar8 = createControl.GetDefaultValue(btn.dllpar8, record, leftRecord, pmtype); + btn.dllpar9 = createControl.GetDefaultValue(btn.dllpar9, record, leftRecord, pmtype); + btn.dllpar10 = createControl.GetDefaultValue(btn.dllpar10, record, leftRecord, pmtype); + + // 处理其他属性 + btn.comfirm = createControl.GetDefaultValue(btn.comfirm, record, leftRecord, pmtype); + btn.selectConfirmFlag = createControl.GetDefaultValue(btn.selectConfirmFlag, record, leftRecord, pmtype); + btn.nextSelectStepCode = createControl.GetDefaultValue(btn.nextSelectStepCode, record, leftRecord, pmtype); + btn.nextSelectStepOper = createControl.GetDefaultValue(btn.nextSelectStepOper, record, leftRecord, pmtype); + btn.comfirmOpers = createControl.GetDefaultValue(btn.comfirmOpers, record, leftRecord, pmtype); + btn.remark = createControl.GetDefaultValue(btn.remark, record, leftRecord, pmtype); + + // 处理PmStr + if (btn.getPmStr() != null && !btn.getPmStr().trim().isEmpty()) { + String pmStr = btn.getPmStr().trim().replaceAll("[\r\t]", ""); + if (pmStr.startsWith("#")) { + pmStr = pmStr.substring(1); + } + String pmSql = createControl.GetDefaultValue(pmStr, record, leftRecord, SystemTypeEnums.PmType.sql); + + try { + // 执行SQL并获取结果(假设dbOperator已注入且有对应方法) + Map resultMap = jdbcTemplate.queryForMap(pmSql); + if (resultMap != null) { + btn.Pms = (new Hashtable<>(resultMap)); + btn.dllpar6 = ""; + } + } catch (Exception e) { + // 忽略异常 + } + } + } + + return btn; + } + + /// + /// 执行右键菜单按钮点击事件 + /// + /// 按钮id + /// The module identifier. + /// The record + /// BaseResponse + /// + + private BaseResponse GoContextMenuClick(SysPoPupMenuBtn btn, Map record, + Map leftRecord, String moduleId, int menuid) throws CusException { + BaseResponse response = new BaseResponse(); + if (btn != null) { + btn.Record = record; + btn.setModuleId(moduleId); + + switch (btn.actiontype) { + case 0: + getEventHandler().callBeforeModuleContextMenu(btn, null); + response = ExcuteSql(btn); + getEventHandler().callAfterModuleContextMenu(btn, response); + break; + + case 1: + getEventHandler().callBeforeModuleContextMenu(btn, null); + response = Deal99Response(ExcuteStore(btn), record, moduleId); + if (response.isSuccess() && response.getMsg() != null && response.getMsg().toLowerCase().startsWith("printfile=")) { + String printFile = response.getMsg().replaceAll("(?i)printfile=", ""); // (?i)表示忽略大小写 + response.setPrintInfo(GetPrintInfo(moduleId, printFile, record)); + response.setMsg(LanguageUtil.Success); + } + getEventHandler().callAfterModuleContextMenu(btn, response); + break; + + case 2: + case 3: + case 4: + response.setSuccess(true); + boolean[] isUrl = new boolean[1]; // Java中用数组传递引用类型结果 + Object width = btn.dllpar9; + Object height = btn.dllpar10; + String xtype = SystemMenu.convertToModuleName(btn.dllname, 1, isUrl); + String dbServer = null; + String dbName = null; + Object printCommand = ToPrintCommand(btn); + if (xtype.contains("pubprint") && isWindowsDirver()) { + dbServer = WebConfigUtil_web.get("dbServer", ""); + Map conDict = ConfigUtil.toConDict(getUser().ConnectionString); + dbName = conDict.get("databasename"); + + if (UpdateImpl.getVersion() >= 1035) { + Map saveRec = new HashMap<>(); + saveRec.put("dllcoid", moduleId); + saveRec.put("printname", btn.dllpar2); + saveRec.put("operatorname", getUser().UserName); + saveRec.put("operatorid", getUser().UserId); + saveRec.put("contextmenuid", menuid); + saveRec.put("sql1", btn.dllpar3); + saveRec.put("sql2", btn.dllpar4); + saveRec.put("sql3", btn.dllpar6); + saveRec.put("sql4", btn.dllpar5); + + List> dataList = new ArrayList<>(); + dataList.add(saveRec); + response = AddOrUpdTable(dataList, "p_systemWebPrintTab", "id", true, null); + + if (!response.isSuccess()) { + response.setMsg("操作失败,请重试!"); + return response; + } + + Map responseData = new HashMap<>(); + responseData.put("xtype", xtype); + responseData.put("title", btn.getText()); + Map dllParms = new HashMap<>(); + dllParms.put("printId", response.getOther()); + dllParms.put("ModuleId", btn.getModuleId()); + dllParms.put("IdValue", btn.getIdValue()); + dllParms.put("Pms", btn.Pms); + dllParms.put("dbServer", dbServer); + dllParms.put("dbName", dbName); + responseData.put("DllParms", dllParms); +// responseData.put("DllParms", Map.of( +// "printId", response.getOther(), +// "moduleId", btn.getModuleId(), +// "idValue", btn.getIdValue(), +// "pms", btn.Pms, +// "dbServer", dbServer, +// "dbName", dbName +// )); + response.setData(responseData); + break; + } + } + // 处理权限相关类型 + else if (xtype.contains("accraditation")) { + xtype = xtype.replace(".index", ".windowcard"); + } + // 处理单据相关类型 + else if (xtype.contains("pubbill.index")) { + xtype = xtype.replace(".index", ".billAdd"); + btn.dllpar8 = ""; + btn.dllpar9 = ""; + width = "90%"; + height = "80%"; + } else if (xtype.contains("pubbill.sindex")) { + xtype = xtype.replace(".sindex", ".index"); + } + + // 设置窗口大小 + if (!isNullOrEmpty(btn.getModuleId()) && + (width == null || width.toString().isEmpty()) && + (height == null || height.toString().isEmpty())) { + + List> winSizeTb = DataImpl.GetAuditWindowSize(btn.getModuleId()); + if (winSizeTb != null && !winSizeTb.isEmpty()) { + Map row = winSizeTb.get(0); + width = (int) (ToInt32(row.get("width")) * 1.5); + height = (int) (ToInt32(row.get("height")) * 2); + } + } + + // 构建返回卡片数据 + Hashtable card = new Hashtable<>(); + card.put("xtype", xtype); + card.put("href", isUrl[0] ? xtype : ""); + card.put("isUrl", isUrl[0]); + card.put("title", btn.getText()); + // 使用HashMap存储DllParms + Map dllParms = new HashMap<>(); + dllParms.put("dllpar1", btn.dllpar1); + dllParms.put("dllpar2", btn.dllpar2); + dllParms.put("dllpar3", btn.dllpar3); + dllParms.put("dllpar4", btn.dllpar4); + dllParms.put("dllpar5", btn.dllpar5); + dllParms.put("dllpar6", btn.dllpar6); + dllParms.put("dllpar7", btn.dllpar7); + dllParms.put("dllpar8", btn.dllpar8); + dllParms.put("dllpar9", btn.dllpar9); + dllParms.put("dllpar10", btn.dllpar10); + dllParms.put("ModuleId", btn.getModuleId()); + dllParms.put("IdValue", btn.getIdValue()); + dllParms.put("ServerId", btn.getServerId()); + dllParms.put("Pms", btn.Pms); + dllParms.put("width", width); + dllParms.put("height", height); + dllParms.put("dbServer", dbServer); + dllParms.put("dbName", dbName); + dllParms.put("maxWindow", btn.maxWindow); + dllParms.put("showMode", btn.showMode); + dllParms.put("PmOffset", btn.getPmOffset()); + dllParms.put("printCommand", printCommand); + dllParms.put("printType", ToInt32(btn.dllpar2)); + card.put("DllParms", dllParms); + + // 处理浏览器相关逻辑 + if (btn.dllname != null && btn.dllname.toLowerCase().contains("pubbrower")) { + btn.dllpar2 = isNullOrEmpty(btn.dllpar2) ? moduleId : btn.dllpar2; + String url = btn.dllpar3 + btn.dllpar2; +// String pmsstr = url.replace("\r\n", "").split("\\?").length > 1 +// ? url.split("\\?")[1] +// : ""; + String[] splitParts = url.replace("\r\n", "").split("\\?"); + String pmsstr = splitParts.length > 0 ? splitParts[splitParts.length - 1] : ""; + Pattern pattern = Pattern.compile("(?i)(?s)/(?:[^/]*?)(.*?)(?=\\.html)"); + Matcher matcher = pattern.matcher(url); + String urlName = matcher.find() ? matcher.group(1) : ""; + + Hashtable pms = new Hashtable<>(); + if (!isNullOrEmpty(pmsstr)) { + for (String str : pmsstr.split("&")) { + if (isNullOrEmpty(str) || str.indexOf('=') < 0) continue; + String[] kvs = str.split("=", 2); // 限制分割为2部分,避免值中包含= + String key = kvs[0]; + String val = kvs.length > 1 ? kvs[1] : ""; + + if (!List.of("username", "password").contains(key)) { + pms.put(key, val.replace("'", "")); + } + } + } + card.put("itemName", urlName); + card.put("QueryPms", pms); + } + + response.setData(card); + break; + } + + // 日志记录 + if (response.isSuccess() && (btn.actiontype == 1 || btn.actiontype == 2)) { + sysLog(String.format("【%s】执行模块【%s】的右键【%s】参数:%s_%s_%s_%s_%s_%s_%s_%s_%s_%s", + getUser().UserName, moduleId, btn.getText(), + btn.action, btn.getPm1(), btn.getPm2(), btn.getPm3(), btn.getPm4(), + btn.getPm5(), btn.getPm6(), btn.getPm7(), btn.getPm8(), btn.getPm9(), btn.getPm10()), + "执行右键"); + } + } + return response; + } + + /** + * 执行SQL语句(适用于右键菜单为SQL的场景) + * + * @param btn 右键菜单按钮对象 + * @return 基础响应对象 + */ + private BaseResponse ExcuteSql(SysPoPupMenuBtn btn) { + BaseResponse response = new BaseResponse(); + int returnValue = 0; + String action = btn.action != null ? btn.action.trim() : ""; + + if (action.isEmpty()) { + response.setSuccess(false); + response.setMsg(LanguageUtil.GetString("EmptySql")); + return response; + } + + // 判断SQL类型(查询或执行) + String lowerAction = action.toLowerCase(); + if (lowerAction.startsWith("select") || lowerAction.startsWith("exec")) { + // 处理查询语句 + List> resultList = new ArrayList<>(); + try { + resultList = jdbcTemplate.queryForList(action); + returnValue = resultList.size(); + response.setData(resultList); + } catch (Exception e) { + response.setSuccess(false); + response.setMsg(LanguageUtil.GetString("SqlExecuteError") + ": " + e.getMessage()); + return response; + } + + } else { + // 处理更新语句(insert/update/delete等) + try { + returnValue = jdbcTemplate.update(action); + } catch (Exception e) { + response.setSuccess(false); + response.setMsg(LanguageUtil.GetString("SqlExecuteError") + ": " + e.getMessage()); + return response; + } + } + + // 设置响应结果 + response.setSuccess(true);//returnValue > 0); + response.setMsg(response.isSuccess() + ? LanguageUtil.GetString("Success") + : LanguageUtil.GetString("Fail")); + return response; + } + + /** + * 执行存储过程(适用于右键菜单为存储过程的场景) + * + * @param btn 右键菜单按钮对象 + * @return 基础响应对象 + */ + private BaseResponse ExcuteStore(SysPoPupMenuBtn btn) { + BaseResponse response = new BaseResponse(); + + try { + // 解析参数名称 + List paramName = PublicUtil.getParamValue(btn.action); + String[] paramStr = paramName.get(0).toLowerCase() + .replace("{", "").replace("}", "").split(","); + + String storeName = btn.action.replace(paramName.get(0), "").trim(); + boolean pm1IsStore = false; + + if (storeName.isEmpty()) { + storeName = btn.dllpar1; + pm1IsStore = true; + } + + // 获取存储过程参数 + DbOperator.Parameter[] pmList = dbOperator.getStoreParams(storeName); + + // 处理默认参数值 + String[] defPmValues = { + btn.dllpar1, btn.dllpar2, btn.dllpar3, btn.dllpar4, btn.dllpar5, + btn.dllpar6, btn.dllpar7, btn.dllpar8, btn.dllpar9, btn.dllpar10 + }; + + if (pm1IsStore) { + defPmValues = new String[]{ + btn.dllpar2, btn.dllpar3, btn.dllpar4, btn.dllpar5, btn.dllpar6, + btn.dllpar7, btn.dllpar8, btn.dllpar9, btn.dllpar10 + }; + } + + String[] pmValues = new String[Math.max(pmList.length, 10)]; + System.arraycopy(defPmValues, 0, pmValues, 0, defPmValues.length); + + // 创建输出参数 + DbOperator.Parameter msgPm = dbOperator.getParameter( + "@msg", "", Types.VARCHAR, 32000, 2 + ); + + DbOperator.Parameter returnVal = dbOperator.getParameter( + "@return", -1, Types.INTEGER, 4, 2 // -1 表示返回值 + ); + + DbOperator.Parameter nextSelectStepCode = null; + DbOperator.Parameter nextSelectStepOper = null; + DbOperator.Parameter comfirmOpers = null; + DbOperator.Parameter direction = null; + DbOperator.Parameter auditAdvice = null; + + // 检查是否有特定参数 + boolean isSelectOper = Arrays.stream(pmList) + .anyMatch(p -> p.getName().equalsIgnoreCase("@selectconfirmflag")); + + int i = 0; + for (int j = 0; j < pmList.length; j++) { + DbOperator.Parameter pm = pmList[j]; + + if (pm.getName().equals("@return") || pm.getName().equals("p_return_code") || pm.getName().equals("rtn_code")) { // 返回值参数 + pmList[j] = returnVal; + } else if (pm.getName().equals("@msg") || pm.getName().equals("p_msg") || pm.getName().equals("msg")) { + pmList[j] = msgPm; + } else { + boolean isDM = ConfigUtil.getProviderName().equals("dm"); + String name = ""; + // 查找参数索引 + if (isDM) name = pm.getName().toLowerCase().replace("p_", "@"); + else name = pm.getName().toLowerCase(); + int pmIndex = indexOf(paramStr, name); + if (pmIndex > -1) { + i = pmIndex; + } + + // 处理特定参数 + if (pm.getName().equalsIgnoreCase("@selectconfirmflag")) { + pm.setValue(btn.selectConfirmFlag == null || btn.selectConfirmFlag.isEmpty() ? "0" : btn.selectConfirmFlag); + continue; + } else if (pm.getName().equalsIgnoreCase("@comfirmflag")) { + pm.setValue(btn.comfirm == null || btn.comfirm.isEmpty() ? "0" : btn.comfirm); + continue; + } else if (pm.getName().equalsIgnoreCase("@nextselectstepcode")) { + pm.setValue(btn.nextSelectStepCode); + nextSelectStepCode = pm; + continue; + } else if (pm.getName().equalsIgnoreCase("@nextselectstepoper")) { + pm.setValue(btn.nextSelectStepOper); + nextSelectStepOper = pm; + continue; + } else if (pm.getName().equalsIgnoreCase("@comfirm_opers")) { + pm.setValue(btn.comfirmOpers); + comfirmOpers = pm; + continue; + } else if (pm.getName().equalsIgnoreCase("@auditadvice")) { + if (btn.remark != null && !btn.remark.isEmpty()) { + pm.setValue(btn.remark); + } else { + pm.setValue(pmValues[i]); + } + auditAdvice = pm; + } else if (pm.getType() == Types.INTEGER) { + pm.setValue(parseInt(pmValues[i])); + } else if (pm.getType() == Types.SMALLINT) { + pm.setValue(parseShort(pmValues[i])); + } else if (pm.getType() == Types.DECIMAL) { + pm.setValue(parseDouble(pmValues[i])); + } else if (pm.getType() == Types.BOOLEAN) { + pm.setValue(parseBoolean(pmValues[i])); + } else { + pm.setValue(pmValues[i]); + } + + if (pm.getName().equalsIgnoreCase("@direction")) { + direction = pm; + } + + i++; + } + } + + // 检查方向参数的特殊情况 + if (direction != null) { + String dirValue = direction.getValue() != null ? direction.getValue().toString().toLowerCase() : ""; + if ("z".equals(dirValue) && comfirmOpers != null && + (comfirmOpers.getValue() == null || comfirmOpers.getValue().toString().isEmpty())) { + response.setSuccess(true); + response.setOther("-11"); + return response; + } else if ("c".equals(dirValue) && auditAdvice != null && + (auditAdvice.getValue() == null || auditAdvice.getValue().toString().isEmpty())) { + response.setSuccess(true); + response.setOther("-12"); + return response; + } + } + // 执行存储过程 + List> resultSet = dbOperator.executeDMDataSet(storeName, pmList); + int returnValue = -1; + String msg = ""; + if (resultSet != null && !resultSet.isEmpty()) { + returnValue = ToInt32(resultSet.get(0).get("returncode")); + msg = Objects.toString( + getIgnoreCase(resultSet.get(0), "dm_msg", "msg"), + "" + ); + } + + if (returnValue == -10 && isSelectOper && nextSelectStepOper != null && nextSelectStepCode != null) { + response.setSuccess(true); + response.setOther(String.valueOf(returnValue)); + + // 这里省略了ModuleBase相关处理,实际应用中需要实现 + String nextSelOpersStr = ""; + if (nextSelectStepOper.getValue() != null) { + String value = nextSelectStepOper.getValue().toString(); + if (value.contains(";")) { + StringBuilder sb = new StringBuilder(); + String[] items = value.split(";"); + for (String item : items) { + String[] parts = item.split(","); + // 去重处理 + List uniqueParts = Arrays.stream(parts) + .distinct() + .collect(Collectors.toList()); + sb.append(String.join(",", uniqueParts)).append(";"); + } + nextSelOpersStr = sb.toString(); + } else { + String[] parts = value.split(","); + List uniqueParts = Arrays.stream(parts) + .distinct() + .collect(Collectors.toList()); + nextSelOpersStr = String.join(",", uniqueParts); + } + } + + // 构建响应数据对象 + Map data = new HashMap<>(); + data.put("moduleId", btn.getModuleId()); + data.put("idValue", btn.getIdValue()); + data.put("selectConfirmFlag", 1); + data.put("nextSelectStepCode", nextSelectStepCode.getValue() != null ? nextSelectStepCode.getValue().toString() : ""); + data.put("nextSelectStepOper", nextSelOpersStr); + // 这里省略了其他数据的获取,实际应用中需要实现 + + response.setData(data); + } else if (returnValue == 99) { + response.setMsg(msg); + response.setSuccess(true); + response.setOther(String.valueOf(returnValue)); + } else if (returnValue == 9) { + response.setSuccess(true); + response.setOther(String.valueOf(returnValue)); + response.setMsg(String.format("

%s

", msg.replace("\r", "
"))); + } else if (returnValue != -1) { + msg = msg.replace("\r", "
"); + response.setSuccess(true); + response.setMsg(msg.isEmpty() || "1".equals(msg) || "null".equals(msg) ? LanguageUtil.GetString("Success") : msg); + response.setData(msg); + } else { + response.setOther(String.valueOf(returnValue)); + response.setMsg(String.format("

%s

", msg.replace("\r", "
"))); + } + + } catch (Exception e) { + response.setSuccess(false); + response.setMsg("执行存储过程出错: " + e.getMessage()); + log.error("执行存储过程出错", e); + } finally { + dbOperator.dispose(); + } + + return response; + } + + private static Object getIgnoreCase(Map map, String... keys) { + if (map == null || keys == null) { + return null; + } + + for (String key : keys) { + if (map.containsKey(key)) { + return map.get(key); + } + + for (Map.Entry entry : map.entrySet()) { + if (entry.getKey() != null && entry.getKey().equalsIgnoreCase(key)) { + return entry.getValue(); + } + } + } + + return null; + } + + // 辅助方法:查找数组中元素的索引 + private int indexOf(String[] array, String value) { + for (int i = 0; i < array.length; i++) { + if (array[i].equals(value)) { + return i; + } + } + return -1; + } + + // 类型转换辅助方法 + private Integer parseInt(String value) { + try { + return value == null || value.isEmpty() ? null : Integer.parseInt(value); + } catch (NumberFormatException e) { + return null; + } + } + + private Short parseShort(String value) { + try { + return value == null || value.isEmpty() ? null : Short.parseShort(value); + } catch (NumberFormatException e) { + return null; + } + } + + private Double parseDouble(String value) { + try { + return value == null || value.isEmpty() ? null : Double.parseDouble(value); + } catch (NumberFormatException e) { + return null; + } + } + + private Boolean parseBoolean(String value) { + return value == null ? null : Boolean.parseBoolean(value); + } + + /** + * 修复:仅筛选 IN/INOUT 类型参数,严格排除 OUT/RETURN_VALUE 类型 + */ + private String[] getInParamNames(List paramList) { + List inParamNames = new ArrayList<>(); + for (CustomSqlParameter param : paramList) { + ParameterDirection direction = param.getDirection(); + // 只保留 IN(输入)和 INOUT(输入输出)类型的参数 + if (direction == ParameterDirection.INPUT || direction == ParameterDirection.INPUT_OUTPUT) { + // 打印参数名,确认是否包含 @msg(正常应不包含) + log.debug(String.valueOf("输入参数:" + param.getName())); + inParamNames.add(param.getName()); + } + } + return inParamNames.toArray(new String[0]); + } + + public BillStateEn GetBillState(ModuleEntity module, String stepCode) throws CusException { + return DataImpl.GetBillState(module, stepCode); + } + + public BillStateEn GetBillState(ModuleEntity module) throws CusException { + return DataImpl.GetBillState(module, ""); + } + + @Override + public BaseResponse Delete() { + return Delete(Request("ModuleId", ""), Request("ids", "")); + } + + /** + * 删除模块数据 + * + * @param moduleId 模块ID + * @param ids 待删除ID,逗号分隔 + * @return 基础响应对象 + */ + @Transactional + public BaseResponse Delete(String moduleId, String ids) { + BaseResponse response = new BaseResponse(); + BaseModule module = GetBaseModule(moduleId, Request("menuId", "")); // 假设menuId为类中已定义的成员变量 + + if (module == null) { + return response; + } + + if (ids == null || ids.trim().isEmpty()) { + return response; + } + + String[] idArray = ids.split(","); + + for (String id : idArray) { + StringBuilder deleteSqlBuilder = new StringBuilder(); + + // 处理特殊模块的删除逻辑 + if (module.IsSpecModule) { + // 获取左关联字段类型 + Class leftUnionFieldType = DataImpl.GetTableColumnType(module.getMasterTable(), module.LeftUnionField); + + if (leftUnionFieldType != null) { + // 值类型处理(数字类型) + if (leftUnionFieldType.isPrimitive() || Number.class.isAssignableFrom(leftUnionFieldType)) { + deleteSqlBuilder.append(String.format(""" + WITH ttype(%s,%s) AS ( + SELECT %s, %s FROM %s WHERE %s IN ('%s') + UNION ALL + SELECT A.%s, A.%s FROM %s A, ttype b WHERE a.%s = b.%s + ) + DELETE FROM %s WHERE %s IN (SELECT %s FROM ttype); + """, + module.getIdField(), module.LeftUnionField, + module.getIdField(), module.LeftUnionField, module.getMasterTable(), module.LeftUnionField, id, + module.getIdField(), module.LeftUnionField, module.getMasterTable(), module.LeftUnionField, module.getIdField(), + module.getMasterTable(), module.getIdField(), module.getIdField() + )); + } else { + // 非值类型处理(字符串类型) + String querySql = String.format( + "SELECT %s FROM %s WHERE %s IN (?)", + module.LeftUnionField, module.getMasterTable(), module.getIdField() + ); + List> specTab = jdbcTemplate.queryForList(querySql, id); + + SqlAnalyzer sqlAnalyzer = new SqlAnalyzer(module.LeftUnionFieldSql); + String whereClause = sqlAnalyzer.OldWhere == null || sqlAnalyzer.OldWhere.isEmpty() + ? "WHERE 1=1 " + : sqlAnalyzer.OldWhere; + + for (Map row : specTab) { + Object specObj = row.get(module.LeftUnionField); + if (specObj == null) { + continue; + } + String spec = specObj.toString(); + if (!spec.isEmpty()) { + deleteSqlBuilder.append(String.format( + "DELETE FROM %s %s AND %s LIKE '%s_%%';", + module.getMasterTable(), whereClause, module.LeftUnionField, spec + )); + } + } + } + } + } + + // 基础删除语句 + deleteSqlBuilder.append(String.format( + "DELETE FROM %s WHERE %s IN ('%s');", + module.getMasterTable(), module.getIdField(), id + )); + + // 执行删除前事件 + module.IdValue = (id); + getEventHandler().callBeforeModuleDataDelete(module, response); + + // 执行删除操作 + String sql = deleteSqlBuilder.toString(); + response = DataImpl.BaseDataSave(module, sql, 3, 0); + + // 记录系统日志 + String logContent = String.format( + "%s删除%s模块数据:%s%s@#@%s", + getUser().UserName, module.getTitle(), id, + response.isSuccess() ? "成功" : "失败", + sql + ); + sysLog(logContent, "操作模块"); +// out.println(logContent + "操作模块"); + // 删除关联附件 + DelAttcFile(moduleId, id); + + // 执行删除后事件 + getEventHandler().callAfterModuleDataDelete(module, response); + } + + return response; + } + + /** + * 删除模块数据关联的附件文件 + * + * @param moduleId 模块ID + * @param idValue 数据ID + * @return 操作响应结果 + */ + @Transactional + public BaseResponse DelAttcFile(String moduleId, String idValue) { + BaseResponse response = new BaseResponse(); + response.setSuccess(false); + + try { + // 获取文件目录信息 + String[] dirTabId = new String[1]; + String fileFolder = DataImpl.GetAcFileFolder(moduleId, idValue, "", dirTabId); + + // 查询附件文件信息 + List> filesInfo = DataImpl.GetAttcFiles(moduleId, idValue, dirTabId[0], null, null); + + for (Map row : filesInfo) { + // 获取文件名(优先vname,其次sname) + String name = get(row, "vname", get(row, "sname", "")) + ""; + + // 获取文件编号 + String specno = get(row, "speciesno", "") + ""; + + // 处理多路径情况 + if (!isNullOrEmpty(specno) && org.example.Impl.DataImpl.RelativePathPmsCount > 1) { + fileFolder = DataImpl.GetAcFileFolder(moduleId, idValue, specno, dirTabId); + } + + // 获取并解码文件路径 + String webPath = get(row, "webPath", "") + ""; + String filePath = URLDecoder.decode(webPath, StandardCharsets.UTF_8); + + if (!isNullOrEmpty(name)) { + // 删除文件(物理删除) + FileUtil.deleteFile(filePath, getAttcPath()); + FileUtil.deleteFile(fileFolder + "/" + name, getAttcPath()); + + // 删除数据库中的附件记录 + String fileId = get(row, "fileId", "") + ""; + DataImpl.DelAttcFileInfo(dirTabId[0], moduleId, "", name, fileId); + + // 记录系统日志 + String logContent = String.format( + "%s删除模块%s下%s的附件%s", + getUser().UserName, moduleId, idValue, name + ); + sysLog(logContent, "删除附件"); +// out.println(logContent + "删除附件"); + response.setSuccess(true); + } + } + } catch (Exception e) { + response.setSuccess(false); + response.setMsg("删除附件失败:" + e.getMessage()); + // 可添加日志记录异常 + // log.error("删除附件异常", e); + } + + return response; + } + + @Override + public BaseResponse GetCondition() { + return GetCondition(Request("ModuleId", ""), Request("detailId", "")); + } + + /** + * 检查是否存在指定的附件文件 + * + * @param moduleId 模块ID + * @param idValue ID值 + * @param specNo 规格编号 + * @param filename 文件名 + * @return 是否存在该附件文件 + */ + public boolean CheckHasAttcFile(String moduleId, String idValue, String specNo, String filename) throws + UnsupportedEncodingException { + // 检查配置是否启用了附件重命名功能 + String configValue = WebConfigUtil.get("NAttcReName", "0"); + if (!Boolean.parseBoolean(configValue)) { + return false; + } + + // 获取文件目录信息 + String[] dirTabId = new String[1]; + String filePath = DataImpl.GetAcFileFolder(moduleId, idValue, specNo, dirTabId); + + StringBuilder fileName = new StringBuilder(filename); + StringBuilder relPath = new StringBuilder(), webPath = new StringBuilder(); + String paths = FileUtil.getFileSavePath(filePath, null, getAppDomain(), getAttcPath(), fileName, relPath, + webPath); + // 查询是否存在该文件 + List> exitFileRs = DataImpl.GetAttcFileInfo(dirTabId[0], moduleId, fileName.toString(), 0); + return exitFileRs != null && !exitFileRs.isEmpty(); + } + + @Override + public BaseResponse GetModuleCfg() { + BaseResponse response = new BaseResponse(); + response.setData(GetModule(Request("moduleId"))); + response.setSuccess(true); + return response; + } + + @Override + public BaseResponse GetModuleRightMenu() { + List bbItems = null; + BaseResponse response = new BaseResponse(); + response.setData(GetRightMenu(Request("ModuleId"), 0, new Ref(bbItems))); + response.setSuccess(true); + return response; + } + + /** + * 获取基础模块所有的明细数据 + * + * @param moduleId 模块ID + * @param idOrPRow ID或父行标识 + * @param detailIds 明细ID集合 + * @return 包含明细数据的基础响应对象 + */ + @Override + public BaseResponse GetModuleDetailsData(String moduleId, String idOrPRow, String detailIds) { + BaseResponse response = new BaseResponse(); + + // 获取基础模块 + BaseModule module = GetBaseModule(moduleId, getMenuId()); + if (module == null) { + response.setMsg("未找到模块" + moduleId + ",请检查配置"); + return response; + } + + // 获取明细模块数据 + String fromKey = (detailIds == null || detailIds.isEmpty()) ? module.getFromkey() : ""; + List> dtVal = DataImpl.GetBaseDetailModuel(fromKey, detailIds); + + Map retDatas = new HashMap<>(); + int i = 0; + + // 遍历所有明细行 + for (Map row : dtVal) { + // 避免对象被污染,除了第一行外重新获取模块实例 + if (i > 0) { + module = GetBaseModule(moduleId, getMenuId()); + } + + BaseDetailModule dModule = new BaseDetailModule(row); + retDatas.put(dModule.getDetailId(), GetModuleDetailData(module, dModule, idOrPRow, false, false, false, "")); + + i++; + } + + response.setData(retDatas); + response.setSuccess(true); + + return response; + } + + /// + /// Gets the audit history. + /// + /// The module identifier. + /// The identifier value. + /// BaseResponse. + @Override + public BaseResponse GetAuditHistory(String moduleId, String idValue, String _isBase) { + boolean isBase = true; + if (!isNullOrEmpty(_isBase)) { + isBase = toBoolean(_isBase); + } else { + ModuleBaseEntity module = GetBaseModule(moduleId, getMenuId()); + if (module == null) { + isBase = false; + } + } + + BaseResponse response = new BaseResponse(); + response.setData(DataImpl.GetBaseFlowStep(idValue, isBase)); + response.setSuccess(true); + + return response; + } + + /** + * 获取附加模块,只有基础模块才有,这个只用在新版手机端订单列表上 + * + * @param moduleId 模块ID + * @param isCard 是否为卡片模式 + * @return 包含附加模块列表的响应对象 + */ + public BaseResponse GetAttachModules(String moduleId, boolean isCard) { + BaseResponse response = new BaseResponse(); + List rets = new ArrayList<>(); + + // 添加主模块初始化参数 + rets.add(getModuleIniParams(moduleId, "", null, null, isCard + , false, null, false, null, null, false)); + + // 记录开始时间 + LocalDateTime startTime = LocalDateTime.now(); + // 添加附加模块列表 + rets.addAll(GetAttachModules(moduleId)); + + // 设置响应数据 + response.setData(rets); + response.setSuccess(true); + return response; + } + + /** + * 获取附加模块列表(基于已有代码库实现) + */ + protected List GetAttachModules(String moduleId) { + // 调用DataImpl获取附加模块数据列表 + List> attcModules = DataImpl.GetAttcModules(moduleId); + + // 转换并过滤有效模块 + return attcModules.stream() + .map(row -> { + // 从数据行提取字段值 + String unionModule = get(row, "unionmodule", "").toString(); + String title = get(row, "attachname", "").toString(); + int orderId = ToInt32(get(row, "orderid", 0)); + + // 验证关联模块是否有效 + if (!unionModule.isEmpty()) { + // 获取模块初始化参数(调用已存在的方法) + BaseModule module = getModuleIniParams(unionModule, "", null, null, true, + false, null, false, null, null, false); + module.setMenuName(title); // 使用MenuName字段存储标题(参考ModuleEntity的MenuName属性) + + // 设置排序ID + if (orderId > 0) { + // 假设BaseModuleEntity有orderId字段及setter方法 + module.OrderId = (orderId); + } + return module; + } + return null; + }) + .filter(Objects::nonNull) // 过滤空值 + .collect(Collectors.toList()); + } + + /** + * 添加附件文件信息 + * + * @param fInfos 文件信息哈希表 + * @return 基础响应对象 + */ + public BaseResponse AddAttcFileInfo(Map fInfos) throws UnsupportedEncodingException { + log.debug(String.valueOf("addattcfileInfo")); + BaseResponse response = new BaseResponse(); + LocalDateTime startTime = LocalDateTime.now(); + + // 提取文件信息参数 + String moduleId = fInfos.getOrDefault("menucode", "") + ""; + String idValue = fInfos.getOrDefault("value", "") + ""; + String fileNo = fInfos.getOrDefault("fileNo", "") + ""; + String specNo = fInfos.getOrDefault("specNo", "") + ""; + String stepCode = fInfos.getOrDefault("stepCode", "") + ""; + String[] dirTabId = new String[1]; // 用于接收out参数 + dirTabId[0] = fInfos.getOrDefault("dirTabId", "") + ""; + String filename = fInfos.getOrDefault("filename", "") + ""; + String totTime = fInfos.getOrDefault("totTime", "") + ""; + String filePath = DataImpl.GetAcFileFolder(moduleId, idValue, specNo, dirTabId); // 注意:Java无out参数,需调整实现 + int comfirm = ToInt32(fInfos.getOrDefault("comfirm", 0)); + + // 构建保存路径并检查长度 + String attcPath = getAttcPath(); // 从父类获取附件路径 +// String utf8AttcPath = new String(getAttcPath().getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); +// String utf8FilePath = new String(filePath.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); +// String utf8FileNamePart = new String(Paths.get(filename).getFileName().toString().getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); +// String _savePath = Paths.get(utf8AttcPath, utf8FilePath, utf8FileNamePart).toString(); +// String _savePath = Paths.get(getAttcPath(), filePath, Paths.get(filename).getFileName().toString()).toString(); + String _savePath = String.format("%s/%s%s", attcPath, filePath, filename); + String[] msgHolder = new String[1]; // 用于接收错误信息 + + if (!FileUtil.checkSaveViewFolderLength(_savePath, msgHolder)) { + response.setSuccess(false); + response.setMsg(msgHolder[0]); + return response; + } + + // 处理文件名 + StringBuilder fileName = new StringBuilder(filename); +// StringBuilder fileName; +// boolean renameFlag = toBoolean(WebConfigUtil.get("NAttcReName", "0")); +// if (renameFlag) { +// fileName = new StringBuilder(filename); +// } else { +// if (isNullOrEmpty(fileNo)) { +// String fullDirPath = Paths.get(WebConfigUtil_web.getFilePath(), filePath).toString(); +// fileName = new StringBuilder(CreateAttcFileName(fullDirPath, Paths.get(filename).getFileName().toString())); +// } else { +// fileName = new StringBuilder(StringFormat.format("{0}_{1}", fileNo, Paths.get(filename).getFileName().toString())); +// } +// } + String sourceFilePath = fInfos.getOrDefault("fPath", "") + "", + fileSize = fInfos.getOrDefault("filesize", "") + ""; + // 处理文件编号 + if (isNullOrEmpty(specNo)) { + specNo = DataImpl.GetAttcBmpSpec(moduleId); + } + StringBuilder relPath = new StringBuilder(), webPath = new StringBuilder(); + // 获取文件保存路径信息 + String savepath = FileUtil.getFileSavePath(filePath, null, getAppDomain(), getAttcPath(), fileName, relPath, webPath); + + // 移动文件 +// response = FileUtil.moveTo( +// sourceFilePath, +// filePath, +// fileName.toString(), +// getAttcPath(), +// comfirm == 1 +// ); + +// startTime = LocalDateTime.now(); + response.setSuccess(new java.io.File(_savePath).exists()); + + boolean moveSuccess = response.isSuccess(); + if (moveSuccess) { + // 删除空目录 +// FileUtil.deleteEmptyDir(sourceFilePath); + + // 处理Web相对路径 + String webRelPath = FileUtil.urlEncode(relPath.toString(), false); + + // 构建附件信息 + Hashtable attcInfos = new Hashtable<>(); + attcInfos.put("dllcoid", moduleId); + attcInfos.put("sname", fileName); + attcInfos.put("vname", fileName); + attcInfos.put("parentid", dirTabId[0]); + attcInfos.put("filesize", fileSize); + attcInfos.put("creator", getUser().UserId); + attcInfos.put("speciesno", specNo); + attcInfos.put("fileno", getFirstPartBeforeUnderscore(fileName.toString())); + attcInfos.put("stepcode", stepCode); + attcInfos.put("webpath", webRelPath); + attcInfos.put("tottime", totTime); +// + ModuleBaseEntity module = GetModule(moduleId); + if (module != null && !isNullOrEmpty(idValue)) { + SetUpdateRowforf(module, null, idValue); +// if(module.Updrow != null) +// { +// attcInfos.put("keyvalue1", +// isNullOrEmpty(module.getAttcField1()) +// ? "" +// : module.Updrow.get(trimDoubleBrace(module.getAttcField1())) +// ); +// attcInfos.put("keyvalue2", +// isNullOrEmpty(module.getAttcField2()) +// ? "" +// : module.Updrow.get(trimDoubleBrace(module.getAttcField2())) +// ); +// attcInfos.put("keyvalue3", +// isNullOrEmpty(module.getAttcField3()) +// ? "" +// : module.Updrow.get(trimDoubleBrace(module.getAttcField3())) +// ); +// } + } + ; + + int fileId = 0; + if (comfirm == 1) { + List> exitFileList = DataImpl.GetAttcFileInfo(dirTabId[0], moduleId, fileName.toString(), 0); + if (!exitFileList.isEmpty()) { + fileId = ToInt32(exitFileList.get(0).getOrDefault("fileId", 0)); + attcInfos.put("fileid", fileId); + } + } + + // 新增或更新附件记录 + List> attcList = new ArrayList<>(); + attcList.add(attcInfos); + response = AddOrUpdTable(attcList, "P_fm_FileTab", "fileid", fileId == 0, null); + + // 记录系统日志 + sysLog(String.format( + "%s在模块%s记录%s上传附件%s%s", + getUser().UserName, + moduleId, + idValue, + fileName, + response.isSuccess() ? "成功" : "失败" + ), "上传附件"); + + if (response.isSuccess()) { + // 设置返回的路径信息 + FileUtil.PathInfo pathInfo = new FileUtil.PathInfo(); + pathInfo.WebPath = (webPath.toString()); + pathInfo.WebRelPath = (webRelPath); + pathInfo.RelativePath = (relPath.toString()); + pathInfo.SavePath = (_savePath); + pathInfo.OldFileName = (filename); + pathInfo.FileName = (fileName.toString()); + pathInfo.UserName = (getUser().UserName); + log.debug(String.valueOf("pathInfo : " + pathInfo.fileId)); + log.debug(String.valueOf("response.getOther() : " + response.getOther())); + pathInfo.fileId = ((response.getOther() + "")); + response.setData(pathInfo); + + // 触发上传后事件 + FileUtil.PathInfo eventPathInfo = new FileUtil.PathInfo(); + eventPathInfo.WebPath = (webPath.toString()); + eventPathInfo.SavePath = (_savePath); + eventPathInfo.fileId = ((response.getOther() + "")); + eventPathInfo.ModuleId = (moduleId); + eventPathInfo.IdValue = (idValue); + eventPathInfo.SpecNo = (specNo); + eventPathInfo.TotSize = (ToInt64(fileSize)); + eventPathInfo.StepCode = (stepCode); + eventPathInfo.FileName = (fileName.toString()); + eventPathInfo.OldFileName = (filename); + getEventHandler().callAfterUploadFile(eventPathInfo, response); + } else if (moveSuccess) { + // 保存失败时删除已移动的文件 + FileUtil.deleteFile(Paths.get(filePath, fileName.toString()).toString(), getAttcPath()); + } + } else { + response.setMsg("保存失败,目标文件不存在!"); + } + // 调试日志:记录保存附件耗时 + log.warn(String.valueOf(String.format("保存附件耗时 %s ms", calculateDurationMs(startTime, LocalDateTime.now())))); + return response; + } + + // 专门处理双 { 符号的修剪方法 + private static String trimDoubleBrace(String str) { + // 先去除首尾的 { 符号,连续调用两次 replace 模拟 C# 的两次 Trim('{') + String result = str.trim(); // 先做常规的空白字符修剪(可选,根据你的业务需求) + // 第一次去除首尾的 { + result = result.replaceAll("^\\{+", "").replaceAll("\\}+$", ""); + // 第二次去除首尾的 {(模拟 C# 连续两次 Trim('{')) + result = result.replaceAll("^\\{+", "").replaceAll("\\}+$", ""); + return result; + } + + /** + * 计算两个时间点之间的毫秒差 + * + * @param start 开始时间 + * @param end 结束时间 + * @return 毫秒差 + */ + private long calculateDurationMs(LocalDateTime start, LocalDateTime end) { + return Duration.between(start, end).toMillis(); + } + + /** + * 根据根据目录和文件名,生成新的文件名. + * + * @param localPath 文件存储目录 + * @param fileName 原始文件名 + * @param index 开始查找的索引 + * @return 生成的新文件名 + */ + private String CreateAttcFileName(String localPath, String fileName, int index) { + // 处理默认索引值,对应C#的可选参数 + if (index <= 0) { + index = 1; + } + + File directory = new File(localPath); + if (directory.exists() && directory.isDirectory()) { + try { + // 获取目录下所有文件,提取并处理文件名中的索引部分 + List fileIndexes = new ArrayList<>(); + File[] files = directory.listFiles(); + if (files != null) { + fileIndexes = Arrays.stream(files) + .map(file -> { + // 处理文件名,提取下划线前的部分 + String fileNamePart = file.getName().split("_")[0]; + // 去除前导零 + fileNamePart = fileNamePart.replaceAll("^0+", ""); + // 如果长度超过4,取后4位 + if (fileNamePart.length() > 4) { + fileNamePart = fileNamePart.substring(fileNamePart.length() - 4); + } + // 转换为整数,转换失败则返回-1(不参与索引检查) + try { + return Integer.parseInt(fileNamePart); + } catch (NumberFormatException e) { + return -1; + } + }) + .filter(idx -> idx != -1) // 过滤无效索引 + .sorted() // 排序 + .collect(Collectors.toList()); + } + + // 找到不重复的最小索引 + while (fileIndexes.contains(index)) { + index++; + } + } catch (Exception e) { + // 捕获所有异常,不做处理(保持与原C#代码行为一致) + } + } + + // 生成格式为"00{index}_{fileName}"的新文件名 + return StringFormat.format("00{0}_{1}", index, fileName); + } + + private String CreateAttcFileName(String localPath, String fileName) { + return CreateAttcFileName(localPath, fileName, 1); + } + + /** + * 获取文件名中下划线前的部分 + * + * @param fileName 文件名 + * @return 下划线前的部分 + */ + private String getFirstPartBeforeUnderscore(String fileName) { + if (isNullOrEmpty(fileName)) { + return ""; + } + int underscoreIndex = fileName.indexOf("_"); + return underscoreIndex > 0 ? fileName.substring(0, underscoreIndex) : fileName; + } + + public BaseResponse GetSpecificAttachModules(String moduleId) { + BaseResponse response = new BaseResponse(); + List rets = GetSpecificModules(moduleId); + response.setData(rets); + response.setSuccess(true); + return response; + } + + protected List GetSpecificModules(String moduleId) { + // 假设dataImpl.getAttcModules(moduleId)返回List>(对应DataTable) + List> moduleList = DataImpl.GetAttcModules(moduleId); + + return moduleList.stream() + .filter(row -> { + // 过滤unionmodule和attachType不为空的行 + String unionModule = row.getOrDefault("unionmodule", "").toString().trim(); + String attachType = row.getOrDefault("attachType", "").toString().trim(); + return !unionModule.isEmpty() && !attachType.isEmpty(); + }) + .map(row -> { + final String ounionModule = row.getOrDefault("unionmodule", "").toString(); + final String otitle = row.getOrDefault("attachname", "").toString(); + final String oattachType = row.getOrDefault("attachType", "").toString(); + BaseModule omodule = null; + + if ("平铺".equals(oattachType)) { + omodule = GetBaseModule(ounionModule, ""); + omodule.IsCard = (true); + setMain(omodule); + } + // 使用匿名内部类或自定义DTO替代匿名对象 + BaseModule finalOmodule = omodule; + return new Object() { + public final String title = otitle; + public final Object icoSrc = row.getOrDefault("attachimg", ""); + public final String unionModule = ounionModule; + public final String attachType = oattachType; + public final BaseModule module = finalOmodule; + }; + }) + .filter(module -> module != null) + .collect(Collectors.toList()); + } + + /** + * 获取单据明细数据 + * + * @param moduleId 模块ID + * @param idValue 主键值 + * @return 包含明细数据的响应对象 + */ + public BaseResponse GetBillDetailData(String moduleId, String idValue) { + BaseResponse response = new BaseResponse(); + response.setSuccess(false); + + // 获取单据模块信息 + BillModule module = GetBillModule(moduleId, getMenuId()); + if (module == null) { + return response; + } + // 处理查询SQL,替换参数占位符 + String result = String.format("{\"%s\":\"%s\"}", module.getIdField(), idValue); + String detailSql = dealQuerySql( + module.getDetailSql(), + result, + null, + null, + "", + "", false, false, false + ); + // 执行SQL查询并转换结果格式 + if (databaseType.equals("dm")) detailSql = RegexUtil.processDmServerSql(detailSql); + List> dataTable = toHashTable(jdbcTemplate.queryForList(detailSql), false); +// List> hashTableData = DataTableUtil.toHashTable(List>, false); + // 设置响应数据 + response.setData(dataTable); + response.setSuccess(true); + return response; + } + + /** + * 保存单据数据 + * + * @param moduleId 模块ID + * @param master 主表数据(JSON字符串:{a:b}) + * @param detail 明细数据(JSON数组:[{a:b}]) + * @param leftRecord 父记录数据 + * @param isAdd 是否为新增操作 + * @param rtagid 红蓝字标识(0:蓝字,1:红字) + * @param comfirmFlag 确认标识(0:默认保存,1:提示后确认保存) + * @param auditFlag 审核标识 + * @param comfirmRemark 确认备注 + * @return 保存结果响应对象 + */ + public BaseResponse SaveBill( + String moduleId, + String master, + String detail, + String leftRecord, + boolean isAdd, + int rtagid, + int comfirmFlag, + int auditFlag, + String comfirmRemark) { + + BaseResponse response = new BaseResponse(); + response.setSuccess(false); + + // 获取单据模块信息 + BillModule module = GetBillModule(moduleId, getMenuId()); + if (module == null) { + response.setMsg("未找到模块配置信息"); + return response; + } + + // 设置模块参数 + module.Rtagid = (rtagid); + module.AuditFlag = (auditFlag); + module.setComfirmFlag(comfirmFlag); + module.ComfirmRemark = (comfirmRemark); + + // 验证主表数据 + if (isNullOrEmpty(master)) { + response.setMsg("主表数据不能为空"); + return response; + } + + try { + // 解析主表数据 + Map masterData = (Map) JSON.Decode(master); + + String idValue = null; + if (masterData != null) { + idValue = masterData.getOrDefault(module.getIdField(), "").toString(); + } + module.DetailGuid = (UUID.randomUUID().toString()); + module.IdValue = (idValue); + + // 验证ID有效性(非新增操作) + if (isNullOrEmpty(idValue) && !isAdd) { + response.setMsg(String.format("%s%s", module.getIdField(), LanguageUtil.NotNull)); + return response; + } + + // 解析明细数据 + ArrayList> detailDataList = new ArrayList<>(); + if (!isNullOrEmpty(detail)) { + detailDataList = (ArrayList>) JSON.Decode(detail); + } + + // 新增操作验证 + if (isAdd) { + // 验证明细数据 + if ((detailDataList == null || detailDataList.isEmpty()) && !module.getEmptyDetailSaveAble()) { + response.setMsg(LanguageUtil.GetString("BillDetailNotNull")); + return response; + } + + // 处理单据编号 + if (ExistBillNo(module, idValue)) { + idValue = GetNewBillNo(module.getBillSeq()); + if (!isNullOrEmpty(idValue)) { + masterData.put(module.getIdField(), idValue); + module.IdValue = (idValue); + } else { + masterData.put(module.getIdField(), ""); + module.IdValue = (""); + if (!module.getNewVer()) { + response.setMsg("旧版单据,单据编号不能为空!"); + return response; + } + } + } + } + + // 解析父记录 + Map parentRecord = null; + if (!isNullOrEmpty(leftRecord)) { + parentRecord = (Map) JSON.Decode(leftRecord); + } + + // 处理特殊字符串字段 + Map> updStrFieldList = new HashMap<>(); + Map> detailUpdStrFieldList = new HashMap<>(); + + // 获取主表信息 + String masterTable = module.getMasterTable(); + List> masterTableInfos = DataImpl.GetTableInfo(masterTable); + + // 新版单据处理 + if (module.getNewVer()) { + Optional> idFieldRow = masterTableInfos.stream() + .filter(row -> row.get("name").toString().equalsIgnoreCase(module.getIdField())) + .findFirst(); + idFieldRow.ifPresent(row -> row.put("isnullable", 0)); + } + + // 获取主表列配置 + List> masterColumns = DataImpl.GetBillMasterRows(module.getModuleId(), getUser().UserName, 0); + String masterIdentityField = GetIdentityField(masterTableInfos); + + // 获取主表字段列表并验证 + List fieldList = GetAOUFields_TabInfo( + module, masterData, masterColumns, parentRecord, + masterTableInfos, masterIdentityField, isAdd, null, 0 + ); + + // 验证字段错误信息 + String errMsg = fieldList.stream() + .filter(field -> !isNullOrEmpty(field.ValidMsg)) + .map(F -> F.ValidMsg) + .collect(Collectors.joining("\n")); + + if (!isNullOrEmpty(errMsg)) { + response.setMsg("主表字段\n" + errMsg); + return response; + } + + // 构建主表保存SQL + StringBuilder masterUpdStrKey = new StringBuilder(); + String masterSql = BuildSaveSql( + module, masterTable, masterIdentityField, + fieldList, isAdd, masterUpdStrKey + ); + log.debug(String.valueOf("masterSql111 :+ " + masterSql)); + + if (!isNullOrEmpty(masterUpdStrKey.toString())) { + updStrFieldList.put(masterUpdStrKey.toString(), fieldList); + } + + // 构建明细SQL + BaseResponse[] res = new BaseResponse[1]; + res[0] = response; + StringBuilder detailSqlBder = BuildBillDetailSql( + module, idValue, parentRecord, detailDataList, + res[0], detailUpdStrFieldList + ); + + response = res[0]; + if (detailSqlBder == null) { + return response; + } + + // 保存模块数据引用 + module.MasterData = (masterData); + module.DetailData = (detailDataList); + + // 执行明细SQL + if (detailSqlBder != null && detailSqlBder.length() > 0) { + log.debug(String.valueOf("detailSqlBder : " + detailSqlBder)); + + if ("dm".equalsIgnoreCase(databaseType)) { + String fullSql = detailSqlBder.toString(); + + // 按分号拆分所有 SQL + String[] sqlParts = fullSql.split(";"); + + int totalRowsAffected = 0; + + try { + for (String sqlPart : sqlParts) { + String sql = sqlPart.trim(); + + // 跳过空 SQL + if (sql.isEmpty()) { + continue; + } + + String upperSql = sql.toUpperCase(); + + // 处理 INSERT + if (upperSql.startsWith("INSERT")) { + int rowsAffected = jdbcTemplate.update(sql); + totalRowsAffected += rowsAffected; + + log.debug(String.valueOf("执行INSERT:" + sql)); + log.debug(String.valueOf("INSERT受影响行数:" + rowsAffected)); + + if (rowsAffected <= 0) { + log.debug(String.valueOf("INSERT无数据插入,行数:" + rowsAffected)); + return response; + } + } + + // 处理 SELECT + else if (upperSql.startsWith("SELECT")) { + Map selectResult = jdbcTemplate.queryForMap(sql); + + log.debug(String.valueOf("执行SELECT:" + sql)); + log.debug(String.valueOf("SELECT返回结果:" + selectResult)); + + // 这里根据你的业务处理 Id + Object idObj = selectResult.get("Id"); + if (idObj != null) { + Long id = Long.valueOf(idObj.toString()); + log.debug(String.valueOf("当前插入数据ID:" + id)); + } + } + + // 其他 SQL 类型 + else { + log.debug(String.valueOf("未识别SQL,跳过:" + sql)); + } + } + + if (totalRowsAffected <= 0) { + log.debug(String.valueOf("没有任何明细数据插入")); + return response; + } + + log.debug(String.valueOf("明细总插入行数:" + totalRowsAffected)); + + } catch (Exception e) { + log.debug(String.valueOf("SQL执行异常:" + e.getMessage())); + log.error("Exception caught", e); + return response; + } + + } else { + int rowsAffected = jdbcTemplate.update(detailSqlBder.toString()); + if (rowsAffected <= 0) { + return response; + } + } + } + + // 处理主表SQL(非新增且主表SQL为空时构建空更新SQL) + if (!isAdd && isNullOrEmpty(masterSql)) { + masterSql = String.format( + "update %s set %s='%s' where %s='%s'", + module.getMasterTable(), module.getIdField(), module.IdValue, + module.getIdField(), module.IdValue + ); + } + + // 执行单据保存 + log.debug(String.valueOf("执行单据保存 masterSql222 :+ " + masterSql)); + response = BillDataSave(module, masterSql, isAdd ? SystemEnums.ActionType.Add : SystemEnums.ActionType.Update); + + // 记录系统日志 + sysLog(String.format( + "%s->%s%s%s%s@#@%s;明细:%s", + module.getMenuName(), getUser().UserName, + isAdd ? "添加" : "修改", module.IdValue, + response.isSuccess() ? "成功" : ("失败:" + response.getMsg()), + masterSql, detailSqlBder + ), "操作模块"); + + if (!response.isSuccess()) { + getEventHandler().callAfterModuleDataChange(module, isAdd ? SystemEnums.ActionType.Add : SystemEnums.ActionType.Update, response); + return response; + } + + // 处理附件临时ID更新 + String attcTempId = masterData.getOrDefault("temp_attachment_id", "").toString(); + if (!isNullOrEmpty(attcTempId)) { + UpdateTempAttcInfo(module.getModuleId(), attcTempId, idValue); + } + + // 处理特殊字符串字段保存结果 + @SuppressWarnings("unchecked") + Map saveResult = (Map) response.getOther(); + response.setOther(isNullOrEmpty(response.getMsg()) ? module.IdValue : response.getMsg()); + response.setMsg(""); + + idValue = response.getOther().toString(); + + if (saveResult != null) { + for (Map.Entry> entry : updStrFieldList.entrySet()) { + UpdStrModule updStrModule = saveResult.get(entry.getKey()); + SaveStrFieldByPms(entry.getValue(), updStrModule); + if (updStrModule != null && updStrModule.Valide()) { + module.IdValue = (updStrModule.getIdValue()); + module.IdentityId = (updStrModule.getIdentityId()); + } + } + } + + // 触发数据变更后事件 + getEventHandler().callAfterModuleDataChange(module, isAdd ? SystemEnums.ActionType.Add : SystemEnums.ActionType.Update, response); + response.setSuccess(true); + + } catch (Exception e) { + response.setMsg("保存失败:" + e.getMessage()); + log.error("保存单据异常", e); + } + + return response; + } + + protected StringBuilder BuildBillDetailSql(BillModule module, String + idValue, Map parentRecord, + ArrayList> detaildata, BaseResponse response, + Map> detailUpdStrFieldList) throws UnsupportedEncodingException { + long startTime; + double startT = 0, sqlT = 0; + + StringBuilder detailSqlBder = new StringBuilder(); + String detailIdentityField = GetIdentityField(module.getDetailTable() + "_temp"); // 获取自动增长列 +// out.println("module.getDetailTable() + \"_temp\" " + module.getDetailTable() + "_temp"); +// out.println("detailIdentityField " + detailIdentityField); + startTime = System.currentTimeMillis(); + // 获取表信息 + List> detailTableInfos = DataImpl.GetTableInfo(module.getDetailTable()); + // 获取临时表信息(240315 都不允许为空) + String sql = getDetailJDBC().BuildBillDetailSqlSql(module.getDetailTable()); + List> tempDetailTableInfos = jdbcTemplate.queryForList(sql); + // 获取单据明细列信息 + List> detialColumns = DataImpl.GetBillDetailColumns(module.getModuleId(), getUser().UserId, getUser().UserName, 0); + + List colInfos = BuildBillDetailColInfos(detailIdentityField, detialColumns, tempDetailTableInfos, detailTableInfos); + startT += (System.currentTimeMillis() - startTime); + + for (int i = 0; i < detaildata.size(); i++) { + Map detailData = detaildata.get(i); + BillDetailModule detailModule = new BillDetailModule(); + + // 设置明细数据关联主表ID和系统标识 + detailData.put(module.getDetailPrefix() + "billdocument_id", idValue); + detailData.put("sysstr", module.DetailGuid); + + startTime = System.currentTimeMillis(); + StringBuilder errBuilder = new StringBuilder(); + List detailFieldList = GetAOUFields_ColInfo( + detailModule, colInfos, detailData, parentRecord, "", detailIdentityField, true, errBuilder + ); + startT += (System.currentTimeMillis() - startTime); + + // 处理验证错误 + if (!errBuilder.isEmpty()) { + response.setMsg("明细第" + (i + 1) + "行\n" + errBuilder); + response.setSuccess(false); + return null; + } + + startTime = System.currentTimeMillis(); + StringBuilder detailUpdStrKey = new StringBuilder(); + // 构建保存SQL并获取更新键 + detailSqlBder.append(BuildSaveSql( + detailModule, module.getDetailTable() + "_temp", detailIdentityField, + detailFieldList, true, detailUpdStrKey + )); + sqlT += (System.currentTimeMillis() - startTime); + + // 保存更新字段列表 + if (detailUpdStrKey != null && !detailUpdStrKey.isEmpty()) { + detailUpdStrFieldList.put(detailUpdStrKey.toString(), detailFieldList); + } + } + + return detailSqlBder; + } + + /** + * 获取自增列字段名 + * + * @param tabname 表名 + * @return 自增列字段名 + */ + public String GetIdentityField(String tabname) { + // 委托给dataImpl处理获取自增列的逻辑 + return DataImpl.GetIdentityField(tabname); + } + + /** + * 处理单据数据保存 + * + * @param module 单据模块实体 + * @param masterSql 主表SQL + * @param aType 操作类型:1新增,2修改,3删除,4作废,5作废恢复 + * @return 基础响应对象 + */ + private BaseResponse BillDataSave(BillModule module, String masterSql, SystemEnums.ActionType aType) { + BaseResponse response = new BaseResponse(); + Connection connection = null; + Statement statement = null; + ResultSet resultSet = null; + try { + // 1. 打印参数(保留原调试逻辑) + // 2. 触发事件、确定存储过程名(保留原逻辑) + getEventHandler().callBeforeModuleDataChange(module, aType, null); + String proName = "P_BillSavePr70"; + if (!DataImpl.IsExitPro(proName)) { + proName = "P_BillSavePr_3"; + } + + // 3. 核心:参数转义(避免SQL注入+适配人大金仓语法) + // 3.1 字符串参数转义:单引号替换为两个单引号 + String escapedMasterSql = Objects.requireNonNullElse(masterSql, "").replace("'", "''"); // 关键:转义insert语句内的单引号 + String escapedIdValue = Objects.requireNonNullElse(module.IdValue, "").replace("'", "''"); + String escapedDetailGuid = Objects.requireNonNullElse(module.DetailGuid, "").replace("'", "''"); + String escapedBillSeq = Objects.requireNonNullElse(module.getBillSeq(), "").replace("'", "''"); + // 3.2 数值参数(直接转成字符串,无需单引号) + int userId = ToInt32(getUser().UserId); + int actionTypeValue = aType.getValue(); + int auditFlag = module.AuditFlag; + int comfirmFlag = module.getComfirmFlag(); + + // 4. 拼接SQL字符串(与之前验证通过的SQL格式完全一致) + StringBuilder sqlBuilder; + // 4.1 声明变量(返回值+输出参数) + sqlBuilder = getDetailJDBC().BillDataSave(proName, escapedMasterSql, escapedIdValue, escapedDetailGuid, escapedBillSeq, userId, actionTypeValue, auditFlag, comfirmFlag); + // 最终拼接好的SQL + String finalSql = sqlBuilder.toString(); + log.debug(String.valueOf("拼接后的完整SQL:" + finalSql)); // 调试用,可查看最终SQL + + + connection = jdbcTemplate.getDataSource().getConnection(); + // 创建Statement时指定ResultSet保持策略,避免切换结果集时自动关闭 + statement = connection.createStatement( + ResultSet.TYPE_SCROLL_INSENSITIVE, + ResultSet.CONCUR_READ_ONLY, + ResultSet.HOLD_CURSORS_OVER_COMMIT + ); + + // 5.1 执行SQL + boolean hasResultSet = statement.execute(finalSql); + List> resultList = new ArrayList<>(); + boolean foundTargetResultSet = false; // 标记是否找到目标结果集 + + // 5.2 循环遍历所有结果集/更新计数 + while (true) { + if (hasResultSet) { + resultSet = statement.getResultSet(); + ResultSetMetaData metaData = resultSet.getMetaData(); + int columnCount = metaData.getColumnCount(); + boolean isTargetResultSet = false; + + // 检查当前结果集是否包含returnValue列(目标结果集标识) + for (int i = 1; i <= columnCount; i++) { + String columnName = metaData.getColumnName(i).toLowerCase(); + if ("returnvalue".equals(columnName)) { + isTargetResultSet = true; + foundTargetResultSet = true; + break; + } + } + + if (isTargetResultSet) { + // 立即处理目标结果集,避免被后续操作关闭 + if (resultSet.next()) { + int returnValue = resultSet.getInt("returnValue"); + String msg = resultSet.getString("outputMsg"); + msg = Objects.requireNonNullElse(msg, ""); + + // 6. 组装返回结果(保留原业务逻辑) + response.setData(returnValue); + if (returnValue == -1) { + // 失败场景 + response.setSuccess(false); + response.setMsg(msg.replace("\r", "
")); + String deleteTempSql = String.format( + "DELETE %s_temp WHERE sysstr = '%s'", + module.getDetailTable().replace("'", "''"), + module.DetailGuid.replace("'", "''") + ); + jdbcTemplate.update(deleteTempSql); + } else { + // 成功场景 + response.setSuccess(true); + response.setMsg(msg.isEmpty() ? "" : msg.replace("\r", "
")); + List>> set = new ArrayList<>(); + set.add(resultList); + response.setOther(DataImpl.DecodeSaveResult(set)); + } + } + resultSet.close(); // 处理完目标结果集后关闭 + break; // 找到目标后退出循环 + } else { + // 处理普通结果集(存储过程返回的业务数据) + while (resultSet.next()) { + Map rowMap = new HashMap<>(); + for (int i = 1; i <= columnCount; i++) { + String columnName = metaData.getColumnName(i).toLowerCase(); + Object columnValue = resultSet.getObject(i); + columnValue = Objects.requireNonNullElse(columnValue, ""); + rowMap.put(columnName, columnValue); + } + resultList.add(rowMap); + } + resultSet.close(); // 及时关闭非目标结果集 + } + } else { + // 处理更新计数(如INSERT/UPDATE的受影响行数),无数据则跳过 + int updateCount = statement.getUpdateCount(); + if (updateCount == -1) { + // 没有更多结果,退出循环 + break; + } + } + + // 切换到下一个结果集/更新计数 + hasResultSet = statement.getMoreResults(); + // 无更多结果时退出循环 + if (!hasResultSet && statement.getUpdateCount() == -1) { + break; + } + } + + // 若未找到目标结果集(异常场景兜底) + if (!foundTargetResultSet) { + response.setSuccess(false); + response.setMsg("未获取到存储过程返回结果"); + response.setData(0); + } + + } catch (Exception e) { + // 异常处理(保留原逻辑) + response.setSuccess(false); + response.setMsg(e.getMessage().replace("\r", "
")); + response.setData(0); + log.error("Exception caught", e); + } finally { + // 关闭资源(严格按顺序:ResultSet → Statement → Connection) + if (resultSet != null) { + try { + if (!resultSet.isClosed()) { + resultSet.close(); + } + } catch (SQLException e) { + log.error("Exception caught", e); + } + } + if (statement != null) { + try { + if (!statement.isClosed()) { + statement.close(); + } + } catch (SQLException e) { + log.error("Exception caught", e); + } + } + if (connection != null) { + try { + if (!connection.isClosed()) { + connection.close(); + } + } catch (SQLException e) { + log.error("Exception caught", e); + } + } + } + return response; + + // 5. 执行SQL并处理结果 +// connection = jdbcTemplate.getDataSource().getConnection(); +// statement = connection.createStatement(); +// +// +// +// +// // 5.1 执行SQL(存储过程可能返回ResultSet,需先处理) +// boolean hasResultSet = statement.execute(finalSql); +// List> resultList = new ArrayList<>(); +// +// // 5.2 处理存储过程返回的ResultSet(如存在) +// if (hasResultSet) { +// resultSet = statement.getResultSet(); +// ResultSetMetaData metaData = resultSet.getMetaData(); +// int columnCount = metaData.getColumnCount(); +// while (resultSet.next()) { +// Map rowMap = new HashMap<>(); +// for (int i = 1; i <= columnCount; i++) { +// String columnName = metaData.getColumnName(i).toLowerCase(); +// Object columnValue = resultSet.getObject(i); +// columnValue = Objects.requireNonNullElse(columnValue, ""); +// rowMap.put(columnName, columnValue); +// } +// resultList.add(rowMap); +// } +// // 移动到下一个结果集(获取returnValue和outputMsg) +// statement.getMoreResults(); +// } +// +// // 5.3 获取返回值和输出参数(第二个结果集) +// Map resultMap = null; +// resultSet = statement.getResultSet(); +// if (resultSet.next()) { +// int returnValue = resultSet.getInt("returnValue"); +// String msg = resultSet.getString("outputMsg"); +// msg = Objects.requireNonNullElse(msg, ""); +// +// // 6. 组装返回结果(保留原逻辑) +// response.setData(returnValue); +// if (returnValue == -1) { +// // 失败场景 +// response.setSuccess(false); +// response.setMsg(msg.replace("\r", "
")); +// String deleteTempSql = String.format( +// "DELETE %s_temp WHERE sysstr = '%s'", +// module.getDetailTable().replace("'", "''"), // 转义表名 +// module.DetailGuid.replace("'", "''") // 转义条件值 +// ); +// jdbcTemplate.update(deleteTempSql); +// } else { +// // 成功场景(使用解析好的resultList) +// response.setSuccess(true); +// response.setMsg(msg.isEmpty() ? "" : msg.replace("\r", "
")); +// List>> set = new ArrayList<>(); +// set.add(resultList); +// response.setOther(DataImpl.DecodeSaveResult(set)); +// } +// } +// +// } catch (Exception e) { +// // 异常处理(保留原逻辑) +// response.setSuccess(false); +// response.setMsg(e.getMessage().replace("\r", "
")); +// response.setData(0); +// e.printStackTrace(); +// } finally { +// // 关闭资源(顺序:ResultSet → Statement → Connection) +// if (resultSet != null) { +// try { +// if (!resultSet.isClosed()) resultSet.close(); +// } catch (SQLException e) { +// e.printStackTrace(); +// } +// } +// if (statement != null) { +// try { +// if (!statement.isClosed()) statement.close(); +// } catch (SQLException e) { +// e.printStackTrace(); +// } +// } +// if (connection != null) { +// try { +// if (!connection.isClosed()) connection.close(); +// } catch (SQLException e) { +// e.printStackTrace(); +// } +// } +// } +// return response; + } + + + /** + * 单据提交/撤回操作 + * + * @param moduleId 模块ID + * @param idValue 主键值 + * @param stateEn 单据状态实体(可为null) + * @param applyType 提交类型:1-提交,2-撤回 + * @return 基础响应对象 + */ + public BaseResponse BillApply(String moduleId, String idValue, BillStateEn stateEn, int applyType) throws + CusException { + // 处理默认参数 + if (applyType < 1 || applyType > 2) { + applyType = 1; // 默认提交 + } + + // 获取单据模块信息 + BillModule module = GetBillModule(moduleId, getMenuId()); + module.IdValue = (idValue); + + // 获取单据状态 + BillStateEn state = GetBillState(module, ""); + + // 系统日志记录 + sysLog(String.format("%s->%s%s%s", + module.getMenuName(), + getUser().UserName, + applyType == 1 ? "提交" : "撤回", + module.IdValue), + "提交数据"); + + state.StepCode = ("0"); + + // 复制状态参数(如果传入了stateEn) + if (stateEn != null) { + state.setSelectConfirmFlag(stateEn.getSelectConfirmFlag()); + state.nextSelectStepCode = (stateEn.nextSelectStepCode); + state.nextSelectStepOper = (stateEn.nextSelectStepOper); + state.comfirmFlag = (stateEn.comfirmFlag); + } + + // 确定操作类型 + SystemEnums.ActionType actionType = applyType == 1 + ? SystemEnums.ActionType.Submit + : SystemEnums.ActionType.EscSubmit; + + // 触发状态变更前事件 + getEventHandler().callBeforeModuleStateChange(module, actionType, null); + + // 执行提交/撤回处理 + BaseResponse response = Deal99Response( + DataImpl.BillApply(module, state, applyType), + null, + moduleId + ); + + // 触发状态变更后事件 + getEventHandler().callAfterModuleStateChange(module, actionType, response); + + return response; + } + + /** + * 单据审核处理方法 + * + * @param module 单据模块实体 + * @param master 主表数据 + * @param detail 明细表数据 + * @param stateEn 单据状态实体 + * @return 基础响应对象 + */ + public BaseResponse BillAudit(BillModule module, String master, String detail, BillStateEn stateEn) { + final BaseResponse[] response = {new BaseResponse()}; + if (stateEn != null && module != null) { + // 判断是否为正向审核(Direction为"F") + boolean isAudit = "F".equalsIgnoreCase(stateEn.Direction); + boolean goOn = true; + + // 如果是审核操作且主表数据不为空且未确认,则先保存单据 + if (isAudit && !isNullOrEmpty(master) && module.getComfirmFlag() == 0) { + Integer comfirmFlag = module.getComfirmFlag() != null ? module.getComfirmFlag() : 0; + response[0] = SaveBill( + module.getModuleId(), + master, + detail, + null, + false, + 0, + comfirmFlag, + 1, "" + ); + goOn = response[0].isSuccess(); + } + + // 继续执行审核流程 + if (goOn) { + // 获取相关实例 +// PushImpl pushImpl = new PushImpl(); // 假设通过合适的方式获取实例 + ModuleEventImpl eventHandler = getEventHandler(); +// DataImpl dataImpl = new DataImpl(); // 假设通过合适的方式获取实例 + + // 执行推送并处理审核逻辑 + getpushImpl().billPush( + module.IdValue, + stateEn.StepCode, + getMenuId(), // 从父类BaseHandler获取MenuId + () -> { + // 触发审核状态变更前事件 + eventHandler.callBeforeModuleAuditStateChange(module, stateEn, null); + + // 执行审核操作并处理99响应 + try { + response[0] = Deal99Response(DataImpl.AuditBill(module, stateEn), null, module.getModuleId()); + } catch (CusException e) { + throw new RuntimeException(e); + } + + // 触发审核状态变更后事件 + eventHandler.callAfterModuleAuditStateChange(module, stateEn, response[0]); + + // 记录系统日志 + String operationResult = response[0].isSuccess() ? "成功" : ("失败:" + response[0].getMsg()); + String logContent = String.format( + "%s->%s%s%s%s", + module.getMenuName(), + getUser().UserName, + "审核", + module.IdValue, + operationResult + ); + sysLog(logContent, "操作模块"); + + return response[0].isSuccess(); + } + ); + } + } + return response[0]; + } + + public BaseResponse GetTaskColumns(String moduleId, String taskModuleId) { + BaseResponse response = new BaseResponse(); + response.setData(GetAuditStepColumns(taskModuleId, "", "998", DataImpl.IsBaseModule(taskModuleId))); + response.setSuccess(true); + return response; + } + + /** + * 获取字段关联数据 + * + * @param fieldId 字段ID + * @param record 记录数据 + * @param leftRecord 左侧记录数据 + * @param pams 参数 + * @param fdtype 字段类型 + * @return 基础响应对象 + */ + public BaseResponse GetFieldUnionData(int fieldId, String record, String leftRecord, String pams, int fdtype) { + BaseResponse response = new BaseResponse(); + response.setSuccess(true); + + // 字段ID无效时直接返回 + if (fieldId <= 0) { + return response; + } + + // 根据fdtype获取对应的字段数据表(使用List模拟DataTable) + LoginUserInfo user = getUser(); + List> fieldTab = null; + switch (fdtype) { + case 1: + fieldTab = DataImpl.GetCondition(null, fieldId); + break; + case 2: + fieldTab = DataImpl.GetBillDetailColumns(null, user.getUserId(), user.getUserName(), fieldId); + break; + case 3: + BillModule module = GetBillModule(getModuleCode(), getMenuId()); + fieldTab = DataImpl.GetControlRows(module.getFromkey(), user.getUserName(), null, fieldId); + break; + case 4: + fieldTab = DataImpl.GetBillMasterRows(null, user.getUserName(), fieldId); + break; + case 5: + String unionSql = DataImpl.getSchemeUnionSql(fieldId); + if (!isNullOrEmpty(unionSql)) { + String querySql = DealQuerySql(unionSql, record, leftRecord, pams, null, null); + response.setData(toHashTable(dbOperator.executeDataTable(querySql, getStartsize(), getPageSize(), tot), false)); + response.setTot(tot[0]); + response.setSuccess(true); + } + return response; + default: + fieldTab = GetColumnRows(null, fieldId); + break; + } + + // 处理字段数据 + if (fieldTab != null && !fieldTab.isEmpty()) { + // 获取第一条记录(对应DataTable的Rows[0]) + Map fieldRow = fieldTab.get(0); + Field field = new Field(fieldRow, createControl); + + // 处理关联SQL + if (field.getUnionSQL() != null && !field.getUnionSQL().isEmpty()) { + // 处理查询SQL(替换参数等逻辑) + String querySql = dealQuerySql( + field.getUnionSQL(), + record, + leftRecord, + pams, + null, + null, false, false, false + ); + + // 执行分页查询并获取总数 + int[] totHolder = new int[1]; // 用于接收总数的容器 + List> resultData = getDbOperator().executeDataTable( + querySql, + getStartsize(), + getPageSize(), + totHolder + ); + + // 转换为哈希表格式(模拟ToHashTable(false)) +// List> hashTableData = DataTableUtil.toHashTable(resultData, false); + + if (!isNullOrEmpty(resultData)) resultData = DataTableUtil.toLowerColumnName(resultData); + // 设置响应数据 + response.setData(resultData); + response.setTot(totHolder[0] != 0 ? totHolder[0] : resultData.size()); + response.setSuccess(true); + } + } + + return response; + } + + /** + * 执行加密的查询SQL + * + * @param enSql 加密的SQL + * @param record 自身替换键值对 + * @param leftRecord 父级替换键值对 + * @param pams 参数 + * @param keyField 键字段 + * @param keyValue 键值 + * @return 基础响应对象 + */ + public BaseResponse ExcQuerySql(String enSql, String record, String leftRecord, String pams, String + keyField, String keyValue) { + BaseResponse response = new BaseResponse(); + try { + // 解密SQL(假设使用AESUtil解密,根据实际加密方式调整) + String querySql = AESUtil.decrypt(enSql); + + // 处理查询SQL,替换参数 + querySql = dealQuerySql(querySql, record, leftRecord, pams, keyField, keyValue, false, false, false); + + // 执行分页查询并获取总数 + int[] totHolder = new int[1]; // 用于接收总数的容器 + List> resultData = getDbOperator().executeDataTable( + querySql, + getStartsize(), + getPageSize(), + totHolder + ); + + // 转换为哈希表格式(模拟ToHashTable()) +// List> hashTableData = DataTableUtil.toHashTable(resultData); + + // 设置响应数据 + response.setData(resultData); + response.setTot(totHolder[0] != 0 ? totHolder[0] : resultData.size()); + response.setSuccess(true); + } catch (Exception e) { + response.setSuccess(false); + response.setMsg("执行查询SQL失败: " + e.getMessage()); + // 记录异常日志 + sysLog("ExcQuerySql error", e.getMessage()); + } + return response; + } + + /** + * 处理右键菜单点击事件 + * + * @param menuid 菜单ID + * @param moduleId 模块ID + * @param rec 记录数据(JSON字符串) + * @param leftRec 左侧记录数据(JSON字符串) + * @return 基础响应对象 + */ + public BaseResponse ContextMenuClick(int menuid, String moduleId, String rec, String leftRec) throws + CusException { + // 解析记录数据JSON + Map record = (Map) JSON.Decode(rec); + + // 解析左侧记录数据JSON(仅当不为空且以{开头时) + Map leftRecord = null; + if (!isNullOrEmpty(leftRec) && leftRec.trim().startsWith("{")) { + leftRecord = (Map) JSON.Decode(leftRec); + } + + // 获取右键菜单按钮对象 + SysPoPupMenuBtn btn = GetContextMenuBtn(menuid, record, leftRecord); + + // 执行菜单点击处理逻辑 + return GoContextMenuClick(btn, record, leftRecord, moduleId, menuid); + } + + /** + * 缓存错误的待办条件SQL信息 + */ + private static Map taskModuleCondCache = new HashMap<>(); + + /** + * 获取待办任务数据 + * + * @param moduleId 模块ID + * @param taskModuleId 任务模块ID + * @param leftRecord 左侧记录数据(JSON字符串) + * @param pms 参数(JSON字符串) + * @return 基础响应对象 + */ + public BaseResponse GetTaskData(String moduleId, String taskModuleId, String leftRecord, String pms, + int taskType) { + BaseResponse response = new BaseResponse(); + BaseModule module = GetBaseModule(moduleId, ""); + boolean isBase = true; + ModuleBaseEntity taskModule = null; + if (moduleId == taskModuleId && module != null && taskType <= 0) { + taskModule = module; + } else { + TaskType ttype = TaskType.DB; + ttype = TaskType.fromValue(taskType); + + List> taskDt = DataImpl.GetTaskModuleInfo(taskModuleId, ttype); + if (taskDt != null && !taskDt.isEmpty()) { + isBase = toBoolean(get(taskDt.get(0), "isbase")); + taskModule = new ModuleBaseEntity(taskDt.get(0)); + + } + } + // 确定任务模块(优先使用当前模块,否则获取指定任务模块) + //ModuleBaseEntity taskModule = (moduleId.equals(taskModuleId) && module != null) ? module : GetModule(taskModuleId); + + if (taskModule == null) { + response.setMsg(String.format("未找到待办模块%s,请检查!", taskModuleId)); + return response; + } + + String taskSql = taskModule.getTaskSql(); + + // 处理特殊场景:未配置任务SQL且满足特定条件时构建默认待办SQL + if (isNullOrEmpty(taskSql) && moduleId.equals(taskModuleId)) { + if (module == null || !isNullOrEmpty(module.LeftUnionField) || !isWindowsDirver()) { + String mulitAuditCond = ""; + String serverIdCol = "0"; + + // 处理多审核条件 + if (DataImpl.CheckIsMulitAudit()) { + mulitAuditCond = String.format("and CHARINDEX(',%s,',','+isnull(a.auditoperators,'')+',')<=0", getUser().UserName); + } + + // 处理服务器ID字段 + if (org.example.Impl.DataImpl.HasServerIdCol) { + serverIdCol = "a.serverId"; + } + + // 构建默认待办查询SQL + taskSql = String.format("select a.*,case when a.stepover=1 then '已终审' else '需:'+P_GetFlowNextName(a.typeCode,a.stepcode) end as endflow from ( " + + "select po.employeename,a.keyvalue Billdocument_Id,a.stepcode,1 lx ,a.OperatorId,a.operatedate," + + "isnull(a1.stepover,a2.stepover) stepover,isnull( a1.billtype,a2.billtype) billtype,REPLACE(a.operators,'管理员,','') oper,a.typeCode, %s serverId " + + "from wms_billflowOperView a " + + "left join wms_billflowOper a1 on a.modid=a1.modid and a.keyvalue=a1.keyvalue " + + "left join p_baseflowOper a2 on a.modid=a2.modid and a.keyvalue=a2.keyvalue " + + "left join P_employeetab po on a.OperatorId=po.employeeid " + + "where CHARINDEX(',%s,',','+a.operators+',')>0 %s and typecode = '%s' " + + ") a " + + "where isnull(stepover,0)=0 order by operatedate desc", + serverIdCol, getUser().UserName, mulitAuditCond, moduleId); + } + } + + // 处理未配置任务SQL的情况 + if (isNullOrEmpty(taskSql)) { + if (module == null || isNullOrEmpty(module.getMasterSql())) { + response.setMsg("未配置模块任务语句,请检查!"); + return response; + } + // 使用模块主SQL获取数据 + response = GetModuleData(module, null, leftRecord, pms, null, false, false, 0, 0, true); + } else { + // 处理任务SQL查询逻辑 + Map record = null; + Map leftRec = null; + Map params = null; + List leftRecordsList = null; + String querySql = taskSql; + + // 解析参数 + if (!isNullOrEmpty(pms)) { + params = (Map) JSON.Decode(pms, Map.class); + } + + // 构建默认查询条件 + String defaultWhere = buildDefaultWhere(params); + + // 解析左侧记录 + if (!isNullOrEmpty(leftRecord)) { + // 确保JSON数组格式 + if (!leftRecord.startsWith("[")) { + leftRecord = String.format("[%s]", leftRecord); + } + leftRecordsList = (List) JSON.Decode(leftRecord, List.class); + + // 取第一个元素作为左侧记录 + if (leftRecordsList != null && !leftRecordsList.isEmpty()) { + leftRec = (Map) leftRecordsList.get(0); + } + } + + // 处理查询SQL(替换参数等) + querySql = dealQuerySql(querySql, record, leftRec, null, null, null, false, false, false); + + // 插入默认查询条件 + if (!isNullOrEmpty(defaultWhere)) { + Map whereDict = new HashMap<>(); + whereDict.put("$where", defaultWhere); + querySql = new SqlAnalyzer(querySql).InsertWhere(whereDict, false, false, false); + } + + // 保存无条件SQL用于错误重试 + String noCondSql = querySql; + + // 应用查询条件(根据缓存判断是否使用条件) + if (!(module == null || moduleId.equals(taskModuleId))) { + boolean useCondition = !taskModuleCondCache.containsKey(moduleId) || taskModuleCondCache.get(moduleId); + if (useCondition) { + querySql = reqSearchCondition(module.getCondKey(), querySql, params, leftRec, false); + } + } + try { + // 执行查询并获取结果 + // 用于接收总数的容器 + List> data = getDbOperator().executeDataTable(querySql, getStartsize(), getPageSize(), tot); + response.setData(toHashTable(data)); + taskModuleCondCache.put(moduleId, true); + } catch (Exception e) { + // 执行出错时重试无条件SQL + taskModuleCondCache.put(moduleId, false); + log.error("待办sql执行错误,预计为条件字段不匹配!已尝试执行无条件sql!", e); + List> data = getDbOperator().executeDataTable(noCondSql, getStartsize(), getPageSize(), tot); + response.setData(toHashTable(data)); + } + } + + // 设置响应结果 + response.setSuccess(true); + if (isWindowsDirver()) { + BaseResponse otherRese = GetTaskColumns(moduleId, taskModuleId); + if (getCurrPage() == 1 && taskModule.getWebCardTpl() != true) { + response.attc = GetTaskCardGroup(taskModuleId, isBase, true, false); + } + response.setOther(otherRese.getData()); + + } else if (moduleId != taskModuleId)//手机端待办总模块 taskmoduleindex 弃用 + { + // response.attc = GetTaskCardGroup(moduleId, isBase, false, true); + } + response.setTot(tot[0]); // tot变量需在当前类中定义为成员变量 + + return response; + } + + private Object GetTaskCardGroup(String moduleId, boolean isBase, boolean cellTpl, boolean defaultGroup) { + // 1. 查询卡片配置表 + List> cardTab = DataImpl.GetTaskMobileCardColumn(moduleId, isBase, cellTpl); + Object mobileCards = null; + + // 2. 有数据时处理分组 + if (!cardTab.isEmpty()) { + // 2.1 cellTpl=true:按ColName分组,返回Map(对应C#的Dictionary) + if (cellTpl) { + Map> groupMap = cardTab.stream() + .map(MobileCard::new) + .collect(Collectors.groupingBy(MobileCard::getColName)); + return groupMap; + } + + // 2.2 cellTpl=false:按GroupName分组,用Map封装匿名对象 + List> groupList = cardTab.stream() + .map(MobileCard::new) + .collect(Collectors.groupingBy(MobileCard::getGroupName)) + .entrySet().stream() + .map(entry -> { + String groupKey = entry.getKey(); + List cardList = entry.getValue(); + + // 获取第一个卡片 + MobileCard firstCard = cardList.stream().findFirst().orElse(null); + if (firstCard == null) { + return null; + } + + // 用Map模拟C#的匿名对象 { name, showText, mxId, items } + Map groupMap = new HashMap<>(); + groupMap.put("name", groupKey); // 对应name = group.Key + groupMap.put("showText", firstCard.isGroupVisible()); // 对应showText = fCard.GroupVisible + groupMap.put("mxId", firstCard.getMxId()); // 对应mxId = fCard.MxId + groupMap.put("items", cardList); // 对应items = group.ToList() + return groupMap; + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + mobileCards = groupList; + } + // 3. 无数据且defaultGroup=true:构建默认分组(用Map封装) + else if (defaultGroup) { + // 构建默认卡片列表 + List defaultItems = new ArrayList<>(); + + // 卡片1:编号 + MobileCard card1 = new MobileCard(); + card1.setBold(true); + card1.setContent("编号:{billdocument_id}"); + card1.setFontSize(16); + card1.setRowHeight(40); + card1.setRowId(1); + card1.setSplitLine(true); + defaultItems.add(card1); + + // 卡片2:申请人员 + MobileCard card2 = new MobileCard(); + card2.setContent("申请人员:{employeename}"); + card2.setFontSize(13); + card2.setRowHeight(36); + card2.setRowId(2); + defaultItems.add(card2); + + // 卡片3:申请时间 + MobileCard card3 = new MobileCard(); + card3.setContent("申请时间:{operatedate}"); + card3.setFontSize(13); + card3.setRowHeight(36); + card3.setRowId(3); + defaultItems.add(card3); + + // 卡片4:状态 + MobileCard card4 = new MobileCard(); + card4.setContent("状态:{endflow}"); + card4.setFontSize(13); + card4.setRowHeight(36); + card4.setRowId(4); + defaultItems.add(card4); + + // 用Map封装默认分组的匿名对象 + Map defaultGroupMap = new HashMap<>(); + defaultGroupMap.put("name", "分组"); + defaultGroupMap.put("showText", false); + defaultGroupMap.put("mxId", 0); + defaultGroupMap.put("items", defaultItems); + + // 封装到List中(对应C#的ArrayList) + List> defaultGroupList = new ArrayList<>(); + defaultGroupList.add(defaultGroupMap); + mobileCards = defaultGroupList; + } + + return mobileCards; + } + + /** + * 获取待办配置,手机端使用 + * + * @param moduleId 模块ID + * @return 包含待办配置信息的BaseResponse + */ + public BaseResponse GetTaskIniParams(String moduleId) { + BaseResponse response = new BaseResponse(); + ModuleBaseEntity taskModule = GetModule(moduleId); + + if (taskModule == null) { + response.setMsg(String.format("无效的目标模块%s,请检查!", moduleId)); + return response; + } + + boolean isBase = taskModule instanceof BaseModule; + List columns = GetAuditStepColumns(moduleId, "", "998", isBase); + + if (taskModule.getMuitlAudit()) { + List> dt = DataImpl.GetAuditStepInfos(moduleId, 0, isBase, ""); + AuditStep step = null; + if (dt != null && !dt.isEmpty()) { + Map row = dt.get(0); + step = new AuditStep(row); + } + if (step != null) { + taskModule.StepApplyText = step.getStepApplyText(); + taskModule.StepBackText = step.getStepBackText(); + } + } + //= operAble ? detail == null ? "审核" : detail.StepApplyText : "", + // stepBackText = operAble ? detail == null ? "反退" : detail.StepBackText : "", + + // 处理默认列信息 + if (columns == null || columns.isEmpty()) { + columns = new ArrayList<>(); + columns.add(new Column() {{ + dataIndex = "billdocument_id"; + }}); + columns.add(new Column() {{ + dataIndex = "endflow"; + }}); + columns.add(new Column() {{ + dataIndex = "employeename"; + }}); + columns.add(new Column() {{ + dataIndex = "operatedate"; + }}); + } + + // 获取移动端卡片列配置 + // List> cardTab = DataImpl.GetTaskMobileCardColumn(moduleId, isBase); + Object MobileCards = GetTaskCardGroup(moduleId, isBase, false, true); + List bbarList = new ArrayList<>(); + GridPanel maingrid = new GridPanel(); + maingrid.setColumns(columns); + maingrid.MobileCards = MobileCards; + DataStore ds = new DataStore(); + Map map = new HashMap<>(); + map.put("moduleId", moduleId); + map.put("TaskModuleId", moduleId); + ds.extraParams = map; + maingrid.setStore(ds); + //taskModule.Main = maingrid; + response.setData(maingrid); + response.setOther(taskModule); + response.setSuccess(true); + return response; + } + + public BaseResponse DeleteProject(String menuId, String projectName) { + LoginUserInfo user = getUser(); + return DataImpl.DeleteProject(menuId, projectName, user.UserId); + } + + public BaseResponse SaveProject(String projectName, String projectValue, String menuId) { + // 转换为存储格式:去除首尾的{},将:替换为=,将,替换为| + if (projectValue != null) { + projectValue = projectValue.trim() + .replaceAll("^\\{|\\}$", "") // 移除首尾的{} + .replace(":", "=") + .replace(",", "|"); + } + LoginUserInfo user = getUser(); + return DataImpl.SaveProject(projectName, projectValue, menuId, user.UserId); + } + + /** + * 获取方案列表 + * + * @param menuId 菜单ID + * @return 方案列表,每个元素为包含"name"和"vals"的Hashtable + */ + public List> GetSchemesList(String menuId) { + // 调用数据层获取方案列表数据(返回List模拟DataTable) + List> dataList = DataImpl.GetSchemesList(menuId, getUser().UserId); + + List> result = new ArrayList<>(); + for (Map row : dataList) { + Hashtable hashtable = new Hashtable<>(); + // 设置方案名称 + hashtable.put("name", row.get("projectname")); + // 转换搜索值为JSON格式并设置 + String searchValue = row.get("searchvalue") != null ? row.get("searchvalue").toString() : ""; + hashtable.put("vals", ctJson(searchValue)); + result.add(hashtable); + } + return result; + } + + /** + * 将字符串转换为JSON格式 + * 例如:a=b|c=d 转换为 {"a":"b","c":"d"} + * + * @param vals 原始字符串 + * @return JSON格式字符串 + */ + private String ctJson(String vals) { + // 替换等号为冒号,竖线为逗号,再包裹大括号包裹 + return "{" + vals.replace("=", "\":\"").replace("|", "\",\"") + "}"; + } + + /** + * 获取审批界面配置 + * + * @param moduleId 模块ID + * @param stepId 步骤ID + * @param idValue 主键值 + * @param leftRecord 父记录 + * @param isBase 是否为基础模块 + * @param stepCode 步骤编码 + * @param ver 版本号 + * @param tpl 是否使用模板 + * @return 基础响应对象 + */ + public BaseResponse GetAuditInfo(String moduleId, int stepId, String idValue, String leftRecord, + boolean isBase, String stepCode, int ver, boolean tpl) throws CusException { + BaseResponse response = new BaseResponse(); + ModuleBaseEntity module = null; + AuditStep detail = null; + List bbItems = null; + + // 获取模块信息 + if (isBase) { + module = GetBaseModule(moduleId, getMenuId()); + } + if (module == null) { + module = GetBillModule(moduleId, getMenuId()); + isBase = false; + } + if (module == null) { + response.setMsg(LanguageUtil.GetString("ModuleNotFound")); + return response; + } + + // 处理模板 + if (tpl) { + boolean isBaseModule = module instanceof BaseModule; + module.CusAddTpl = (DataImpl.GetModuleAddTpl(module.getModuleId(), isBaseModule)); + } + + // 设置模块参数 + module.IdValue = (idValue); + if (!isNullOrEmpty(leftRecord)) { + module.setLeftRecord((Hashtable) JSON.Decode(leftRecord)); + } + stepCode = "";//20251224改来直接从对应单据信息中查询stepcode,避免误传 + BillStateEn stateEn = GetBillState(module, stepCode); + if (isNullOrEmpty(stepCode)) { + stepCode = stateEn.StepCode; + } + // 获取审核步骤信息 + detail = GetAuditStep(stepId, isBase, moduleId, stepCode); + List cols = GetAuditMainCols(detail, module, isBase, idValue); + + // 验证审核数据是否存在 + if (module.Updrow == null) { + String msg = String.format("未找到编号为【%s】的审核数据,请确认是否拥有审核权限、模板编号为【%s】的审核步骤【%s】的SQL是否配置正确!", + idValue, moduleId, stepCode); + response.setSuccess(false); + response.setMsg(msg); + return response; + } + + // 处理单据状态 + //BillStateEn stateEn = GetBillState(module, stepCode); +// out.println(JSON.Encode(stateEn)); + stepId = (stepId <= 0 && stateEn != null) ? stateEn.StepId : stepId; + + String editFields = ""; + String detailEditFields = ""; + String requireFields; + String requiredDetailFiedls = ""; + + module.BillType = (stateEn != null ? stateEn.BillType : null); + + if (stateEn != null && stateEn.Finished) { + requireFields = ""; + stepCode = "999"; + stateEn.StepCode = stepCode; + } else { + if (stateEn != null && !stepCode.equals(stateEn.StepCode) && detail != null) { + detail = GetAuditStep(stepId, isBase, moduleId, stateEn.StepCode); + } + + // 处理字段权限配置 + requireFields = (detail != null) ? "," + detail.getRequiredFields().toLowerCase() + "," : ""; + editFields = (detail != null) ? detail.getBillModifyFields() + + (isNullOrEmpty(requireFields) ? "" : requireFields) : ""; + editFields = isNullOrEmpty(editFields) ? + "" : "," + editFields.trim().toLowerCase() + ","; + + requiredDetailFiedls = (detail != null) ? "," + detail.getRequiredDetailFields().toLowerCase() + "," : ""; + detailEditFields = (detail != null) ? "," + detail.getDetailModifyFields() + + (isNullOrEmpty(requiredDetailFiedls) ? "" : requiredDetailFiedls) + "," : ""; + detailEditFields = isNullOrEmpty(detailEditFields) ? + "" : "," + detailEditFields.trim().toLowerCase() + ","; + + stepCode = (detail != null) ? detail.getStepCode() : "999"; + } + + // 处理主表字段权限 + if (cols != null) { + String finalEditFields = editFields; + cols = cols.stream().map(com -> { + com.setReadOnly(true); + if (finalEditFields.contains("," + com.getName() + ",") && + stateEn != null && !stateEn.Finished) { + com.setReadOnly(false); + if (requireFields.contains("," + com.getName() + ",")) { + ((Field) com).setAllowBlank(false); + } + } + return com; + }).collect(Collectors.toList()); + } + + // 处理明细数据 + Object main = cols; + Object details; + + if (ver > 1) { + // 审批的附加明细(高版本) + ModuleBaseEntity finalModule = module; + details = DataImpl.GetAuditInfoDetails(moduleId, stepCode).stream() + .map(row -> GetOneModelDetail(row, finalModule, null, true)) + .collect(Collectors.toList()); + } else { + // 审批的附加明细(低版本) + details = DataImpl.GetAuditInfoDetails(moduleId, stepCode).stream() + .map(row -> { + Map detailMap = new HashMap<>(); + detailMap.put("title", row.get("attachname").toString()); + + List items = new ArrayList<>(); + GridPanel gridPanel = new GridPanel(); + + // 处理列配置 + List columns = DataImpl.GetAuditInfoDetailColumns(row.get("id").toString()).stream() + .map(colRow -> new RowColumn(colRow, true, createControl)) + .collect(Collectors.toList()); + gridPanel.setColumns(columns); + + // 处理数据源 + DataStore dataStore = new DataStore(); + Map extraParams = new HashMap<>(); + extraParams.put("detailId", row.get("id")); + dataStore.extraParams = (extraParams); + gridPanel.setStore(dataStore); + + // 处理右键菜单 + gridPanel.RightMenu = (GetRightMenu(row.get("formkey").toString(), 0, new Ref(bbItems), "RightMenuClick")); + + items.add(gridPanel); + detailMap.put("items", items); + return detailMap; + }).collect(Collectors.toList()); + } + + // 处理基础明细 + List baseDetails = new ArrayList<>(); + if (!isBase) { + // 单据的审批 + GridPanel billGrid = GetBillDetail((BillModule) module, "", + stateEn != null && !stateEn.Finished, + (stateEn != null && !stateEn.Finished) ? detailEditFields : "-null", + (stateEn != null && !stateEn.Finished) ? requiredDetailFiedls : "-null"); + + billGrid.TbarItems = (cols); + main = billGrid; + } else { + // 基础模块的审批 + List> infoDT = DataImpl.GetAuditBaseDetails(moduleId, stepCode); + if (infoDT != null && !infoDT.isEmpty()) { + List detailKeys = infoDT.stream() + .map(row -> row.get("detailKey").toString()) + .collect(Collectors.toList()); + + List> baseDetailsDT = DataImpl.GetBaesModuleDetailsByFromkey( + String.join("','", detailKeys)); + + for (Map baseRow : baseDetailsDT) { + Hashtable elseValHS = new Hashtable<>(); + + // 查找匹配的信息行 + Map selInfo = infoDT.stream() + .filter(row -> row.get("detailKey").toString().equalsIgnoreCase( + baseRow.get("formkey").toString())) + .findFirst() + .orElse(null); + + if (selInfo != null) { + String modifyFields = Objects.toString(get(selInfo, "modifyFields"), ""); + int displayMode = ToInt32(selInfo.get("displayMode")); + int addShowMode = ToInt32(selInfo.get("addShowMode")); + + modifyFields = isNullOrEmpty(modifyFields) ? "," : modifyFields; + elseValHS.put("modifyFields", modifyFields); + elseValHS.put("displayMode", displayMode); + elseValHS.put("addShowMode", addShowMode); + + Component oneCmp = GetOneModelDetail(baseRow, (BaseModule) module, elseValHS, false); + if (oneCmp != null) { + baseDetails.add(oneCmp); + } + } + } + } + } + + // 处理窗口尺寸 + List> winSizeTb = DataImpl.GetAuditWindowSize(module.getModuleId()); + Object width = null, height = null; + if (!winSizeTb.isEmpty()) { + width = winSizeTb.get(0).get("width"); + height = winSizeTb.get(0).get("height"); + } + Object nomalmenu = GetRightMenu(module.getModuleId(), 1, new Ref(bbItems), "RightMenuClick"); + // List bbItems = new List(); + Object rightMenu = isBase ? GetRightMenu(module.getModuleId(), 0, new Ref(bbItems), "toolClick") + : nomalmenu; + Map responseData = new HashMap<>(); + responseData.put("module", module); + responseData.put("RightMenu", rightMenu); + responseData.put("main", main); + responseData.put("details", details); + responseData.put("baseDetails", baseDetails); + responseData.put("stepId", stepId); + responseData.put("stepCode", stepCode); + responseData.put("stepName", (detail != null ? detail.getStepName() : null)); + responseData.put("idValue", idValue); + responseData.put("width", width); + responseData.put("height", height); + // 处理附加信息 + boolean operAble = stateEn != null && stateEn.OperAble; + boolean disjcp = detail == null ? false : detail.getDisJCP(); + Map otherData = new HashMap<>(); + + // 审核意见数据源 + DataStore boxStore = new DataStore(); + boxStore.data = (DataImpl.GetAuditRemark()); + otherData.put("boxStore", boxStore); + + // 步骤历史数据源 + DataStore gridStore = new DataStore(); + gridStore.data = GetAuditStepAndHis(module, module.getModuleId(), idValue, stateEn == null ? 0 : ToInt32(stateEn.StepCode), isBase, module.BillType);//DataImpl.GetBaseFlowStep(idValue, isBase)); + otherData.put("gridStore", gridStore); + + // 右键菜单 + otherData.put("normalmenu", nomalmenu);// GetRightMenu(module.getModuleId(), 1, new Ref(bbItems), "RightMenuClick")); + + // 操作按钮文本 + otherData.put("SaveText", operAble ? (isNullOrEmpty(editFields) ? "" : "保存") : ""); + otherData.put("stepApplyText", operAble ? (detail == null ? "审核" : detail.getStepApplyText()) : ""); + otherData.put("stepBackText", operAble ? (detail == null ? "反退" : detail.getStepBackText()) : ""); + otherData.put("StepClosed", operAble ? (stateEn == null ? false : stateEn.StepClosed) : false); + otherData.put("StepCloseText", operAble ? (detail == null ? "关闭" : detail.getStepCloseText()) : ""); + otherData.put("StepCloseContent", operAble ? (detail == null ? "关闭" : detail.getStepCloseContent()) : ""); + otherData.put("StepCloseTip", operAble ? (detail == null ? "是否关闭?" : detail.getStepCloseTip()) : ""); + otherData.put("auditContent", detail == null ? "" : detail.getAuditContent()); + otherData.put("disjcp", disjcp); + otherData.put("isBase", isBase); + + response.setData(responseData); + response.setOther(otherData); + response.setSuccess(true); + + // 记录系统日志 + sysLog(String.format("%s进入%s模块", getUser().UserName, module.getMenuName()), "进入模块"); + + return response; + } + + private List GetAuditMainCols(AuditStep detail, ModuleBaseEntity module, boolean isBase, String idValue) { + // 20240412确认所有审批界面取值取主表的sql + String stepSql = ""; // detail == null ? "" : detail.getStepSql(); + if (isBase) { + BaseResponse fieldResponse = GetAddOrUpdFields(module, idValue, 0, stepSql, false, false); + return (List) fieldResponse.getData(); + } else { + return GetBillMasterFields((BillModule) module, stepSql); + } + } + + /** + * 获取审批附加明细的数据 + * + * @param detailId 明细ID + * @param record 记录数据 + * @param leftRecord 左侧记录数据 + * @param pams 参数 + * @return 基础响应对象 + */ + public BaseResponse GetAuditDetailData(int detailId, String record, String leftRecord, String pams) { + BaseResponse response = new BaseResponse(); + if (detailId <= 0) { + return response; + } + + // 获取审批信息明细 + List> dtVal = DataImpl.GetAuditInfoDetails(null, null, detailId); + if (!dtVal.isEmpty()) { + Map row = dtVal.get(0); + String querySql = (String) get(row, "attachsql", ""); + String unionmodule = (String) get(row, "unionmodule", ""); + String unionCond = (String) get(row, "unioncond", ""); + + if (!isNullOrEmpty(querySql)) { + // 处理查询SQL + querySql = dealQuerySql(querySql, record, leftRecord, pams, null, null, false, false, false); + + // 执行查询并获取总数 + int[] totHolder = new int[1]; // 用于接收总数的数组 + List> _dtVal = getDbOperator().executeDataTable( + querySql, getStartsize(), getPageSize(), totHolder); + if (_dtVal != null && !_dtVal.isEmpty() && !_dtVal.get(0).isEmpty()) { + // 1. 提取所有列名(从第一行Map中获取key,对应C#的DataColumn集合) + Map firstRow = _dtVal.get(0); + // 2. 转换列信息:列名 → {dataIndex:列名, xtype:类型}(对应C#的Select逻辑) + List> mainDataColumns = firstRow.keySet().stream() + .map(columnName -> { + // 取第一行该列的值,用于推导类型(模拟C#的col.DataType) + Object columnValue = firstRow.get(columnName); + // 直接构建Map(key-value直写,无需中间变量) + return new HashMap() {{ + put("dataIndex", columnName); // 对应dataIndex = col.ColumnName + put("xtype", PublicUtil.TypeToColumnType(columnValue.getClass())); // 对应xtype = TypeToColumnType + }}; + }) + .collect(Collectors.toList()); + + // 3. 封装外层Map(对应C#的匿名对象 new { MainDataColumns = ... }) + Map otherMap = new HashMap<>(); + otherMap.put("MainDataColumns", mainDataColumns); + + // 4. 赋值给response.other(和原逻辑一致) + response.setOther(otherMap); + } + response.setData(toHashTable(_dtVal)); + response.setTot(totHolder[0]); + response.setSuccess(true); + } else if (!isNullOrEmpty(unionmodule)) { + // 处理关联模块逻辑 + BaseModule module = GetBaseModule(unionmodule, ""); + BaseDetailModule dModule = new BaseDetailModule(row); + return GetModuleDetailData(module, dModule, leftRecord, false, false, true, ""); + } else { + response.setMsg("获取明细数据失败,未配置数据源sql或关联模块!"); + } + } + return response; + } + + /** + * 审核基础模块 + * + * @param module 基础模块实体 + * @param data 数据 + * @param details 明细数据 + * @param stateEn 状态实体 + * @return 基础响应对象 + */ + public BaseResponse BaseAudit(BaseModule module, String data, String details, BillStateEn stateEn) throws + UnsupportedEncodingException, CusException { + final BaseResponse[] response = {new BaseResponse()}; + if (stateEn != null) { + boolean isAudit = "F".equalsIgnoreCase(stateEn.Direction); + boolean goOn = true; + + // 去掉事务,选人的时候会回滚,会报错 + if (isAudit && !isNullOrEmpty(data) && stateEn.comfirmFlag == 0 && stateEn.getSelectConfirmFlag() == 0) { + response[0] = AddOrUpd(module.getModuleId(), data, details, null, false, null, stateEn.comfirmFlag, false); + goOn = response[0].isSuccess() || LanguageUtil.GetString("NoneField").equals(response[0].getMsg()); + } + + if (goOn) { + getpushImpl().basePush(module.IdValue, stateEn.StepCode, getMenuId(), () -> { + getEventHandler().callBeforeModuleAuditStateChange(module, stateEn, null); + try { + response[0] = Deal99Response(DataImpl.BaseAudit(module, stateEn), module.getLeftRecord(), module.getModuleId()); + } catch (CusException e) { + throw new RuntimeException(e); + } + getEventHandler().callAfterModuleAuditStateChange(module, stateEn, response[0]); + + String logMsg = String.format("%s->%s%s%s%s", + module.getTitle(), getUser().UserName, "审核", + module.IdValue, response[0].isSuccess() ? "成功" : ("失败:" + response[0].getMsg())); + sysLog(logMsg, "操作模块"); + + return response[0].isSuccess(); + }); + + if (!response[0].isSuccess() && !isNullOrEmpty(details)) { + response[0].setMsg(response[0].getMsg() + "
提交失败,但明细数据修改成功,请重新打开该页面避免重复提交信息!"); + } + } + } + + return response[0]; + } + + public BaseResponse BatchAudit(String moduleId, String direction, String datas) throws + UnsupportedEncodingException, CusException { + BaseResponse response = new BaseResponse(); + BaseResponse oneResponse = new BaseResponse(); + // 假设使用Jackson进行JSON解析 + List> auditDatas = (List>) JSON.Decode(datas, Map.class); + ModuleEntity module = GetModule(moduleId); + + if (module == null || auditDatas == null || auditDatas.isEmpty()) { + response.setMsg("未找到对应模块或者没有可审核的数据!"); + return response; + } + + StringBuilder errs = new StringBuilder(); + int successTot = 0; + + for (Map data : auditDatas) { + String stepCode = String.valueOf(data.getOrDefault("stepcode", "")); + String idValue = String.valueOf(data.getOrDefault("idValue", "")); + + if (isNullOrEmpty(stepCode) || isNullOrEmpty(idValue)) { + errs.append(String.format("单据【%s】,没有步骤码或主键值,信息不足,审核失败!
", module.IdValue)); + continue; + } + + module.IdValue = (idValue); + BillStateEn stateEn = GetBillState(module, stepCode); + + if (stateEn == null) { + errs.append(String.format("单据【%s】,审核失败,未找到对应信息!
", module.IdValue)); + continue; + } + + // 设置状态实体属性 + stateEn.Direction = (direction); + stateEn.IsBack = (false); + stateEn.comfirmFlag = (0); + stateEn.Remark = ("批量审批"); + stateEn.BackStepCode = (""); + stateEn.setSelectConfirmFlag(0); + stateEn.nextSelectStepCode = (""); + stateEn.nextSelectStepOper = (""); + stateEn.comfirmOpers = (""); + + // 执行审核操作 + if (module instanceof BaseModule) { + oneResponse = BaseAudit((BaseModule) module, "", "", stateEn); + } else { + oneResponse = BillAudit((BillModule) module, "", "", stateEn); + } + + if (oneResponse.isSuccess()) { + successTot++; + } + + // 如果只有一条数据,直接返回该条数据的审核结果 + if (auditDatas.size() == 1) { + return oneResponse; + } + + if (!oneResponse.isSuccess()) { + errs.append(String.format("单据【%s】,审核失败,%s!
", module.IdValue, oneResponse.getMsg())); + } + } + + // 设置批量审核结果 + response.setSuccess(successTot > 0); + if (response.isSuccess()) { + response.setMsg("操作成功" + successTot + "条"); + } else { + response.setMsg("操作失败" + (!errs.isEmpty() ? ("," + errs) : "")); + } + + return response; + } + + public BaseResponse GetAuditBackSteps(String moduleId, String idValue, String stepCode, boolean isBase) throws + CusException { + BaseResponse response = new BaseResponse(); + ModuleBaseEntity module = GetModule(moduleId); + module.IdValue = idValue; + BillStateEn stateEn = GetBillState(module, stepCode); + response.setData(DataImpl.GetAuditBackSteps(moduleId, idValue, stepCode, stateEn == null ? "" : stateEn.BillType + "", isBase)); + response.setOther(module.getNewWFVer() ? 1 : 0); + response.setSuccess(true); + return response; + } + + /** + * 获取审批主界面配置 + * + * @param moduleId 模块ID + * @param isBase 是否为基础模块 + * @return 基础响应对象 + */ + public BaseResponse GetAuditIniParams(String moduleId, boolean isBase) { + BaseResponse response = new BaseResponse(); + ModuleBaseEntity module = null; + + // 根据是否为基础模块获取对应的模块信息 + if (isBase) { + module = GetBaseModule(moduleId, getMenuId()); + } + // 如果基础模块未找到,尝试获取单据模块 + if (module == null) { + module = GetBillModule(moduleId, getMenuId()); + isBase = false; + } + + // 模块未找到的错误处理 + if (module == null) { + String errorMsg = LanguageUtil.GetString("InvalidCode"); + errorMsg += " 未找到模块号【" + moduleId + "】以及MenuId【" + getMenuId() + "】对应的(单据or基础模块)数据,请检查相应模块的配置是否正确!可能是多余的空格、回车,数字字母写错,模块暂未配置等情况!"; + response.setMsg(errorMsg); + return response; + } + + // 获取审核步骤信息(DataTable转为List) + List> auditStepInfos = DataImpl.GetAuditStepInfos(moduleId, 0, isBase, ""); + String idField = module.getIdField(); + + if (auditStepInfos != null && !auditStepInfos.isEmpty()) { + // 按步骤组分组处理 + Map>> groupedByStepGroup = new HashMap<>(); + for (Map row : auditStepInfos) { + Object stepGroup = row.get("stepgroup"); + groupedByStepGroup.computeIfAbsent(stepGroup, k -> new ArrayList<>()).add(row); + } + + // 构建tabs数据 + List tabs = new ArrayList<>(); + for (Map.Entry>> entry : groupedByStepGroup.entrySet()) { + String stepGroupName = entry.getKey() != null ? entry.getKey().toString() : ""; + List> groupRows = entry.getValue(); + + // 构建当前组的items + List items = new ArrayList<>(); + for (Map row : groupRows) { + String stepName = row.get("stepname") != null ? row.get("stepname").toString() : ""; + String stepId = row.get("id") != null ? row.get("id").toString() : ""; + String stepCode = row.get("stepcode") != null ? row.get("stepcode").toString() : ""; + + // 创建网格面板 + GridPanel gridPanel = new GridPanel(); + gridPanel.title = (stepName); + gridPanel.IdField = (idField); + gridPanel.setColumns(GetAuditStepColumns(moduleId, stepId, stepCode, isBase)); + + // 移动端卡片(非Windows驱动时设置) + if (!isWindowsDirver()) { + gridPanel.MobileCards = (GetAuditStepColumnCards(moduleId, stepCode, isBase)); + } + + // 设置数据存储 + DataStore dataStore = new DataStore(); + Map extraParams = new HashMap<>(); + extraParams.put("moduleId", moduleId); + extraParams.put("stepId", row.get("id")); + extraParams.put("stepCode", stepCode); + dataStore.extraParams = (extraParams); + gridPanel.setStore(dataStore); + + items.add(gridPanel); + } + + // 添加分组到tabs + ModuleBaseEntity finalModule = module; + tabs.add(new HashMap() {{ + put("text", stepGroupName); + put("count", GetStepDataCount(groupRows, finalModule, stepGroupName)); + put("items", items); + }}); + } + + // 添加"已完成单据"标签 + ModuleBaseEntity finalModule1 = module; + tabs.add(new HashMap() {{ + put("text", "已完成单据"); + put("count", GetStepDataCount(null, finalModule1, "已完成单据")); + put("items", GetAuditHistoryInfo(finalModule1)); + }}); + + // 设置响应数据 + response.setData(tabs); + response.setOther(module); + response.setSuccess(true); + } else { + response.setMsg(" 未找到模块号【" + moduleId + "】对应的审批步骤数据,请检查相应模块的配置是否正确!可能模块编号重复!"); + } + + return response; + } + + /** + * 获取附件文件路径列表 + * + * @param moduleId 模块ID + * @param idValue 主键值 + * @param specNo 规格编号 + * @param stepCode 步骤编码 + * @param dot 连接符,默认"&" + * @return 基础响应对象,包含文件路径列表和权限检查结果 + */ + public BaseResponse GetAttcFilePaths(String moduleId, String idValue, String specNo, String stepCode, String + dot) { + // 设置默认连接符 + if (dot == null || dot.isEmpty()) { + dot = "&"; + } + + BaseResponse response = new BaseResponse(); + String[] dirTabId = new String[1]; + // 获取文件目录 + String fileFolder = DataImpl.GetAcFileFolder(moduleId, idValue, specNo, dirTabId); + + // 获取附件文件列表 + List> fileList = DataImpl.GetAttcFiles(moduleId, idValue, dirTabId[0], specNo, stepCode); + List paths = new ArrayList<>(); + + for (Map row : fileList) { + // 获取文件名相关信息 + String sname = row.get("sname") != null ? row.get("sname").toString() : ""; + String vname = row.get("vname") != null ? row.get("vname").toString() : sname; + StringBuilder fileName = new StringBuilder(vname); + String tempFileName = fileName.toString(); + StringBuilder webPath = new StringBuilder(row.get("webpath") != null ? row.get("webpath").toString() : ""); + StringBuilder relPath = new StringBuilder(); + String createTime = row.get("CreateTime") != null ? row.get("CreateTime").toString() : ""; + String userName = row.get("username") != null ? row.get("username").toString() : ""; + + // 处理webPath为空的情况 + if (webPath.isEmpty()) { + try { + String appDomain = getAppDomain(); + String attcPath = getAttcPath(); + // 调用工具类获取文件保存路径 + FileUtil.getFileSavePath(fileFolder, null, appDomain, attcPath, fileName, relPath, webPath); + + // 处理webPath中的域名部分 + if (!webPath.isEmpty()) { + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + String scheme = request.getScheme(); + String authority = request.getServerName() + ":" + request.getServerPort(); + String baseUrl = scheme + "://" + authority; + webPath = new StringBuilder(webPath.toString().replace(baseUrl, "").replace("#", "%23")); + } + } catch (UnsupportedEncodingException e) { + // 处理编码异常 + log.error("Exception caught", e); + } + } + + // 处理文件名包含空格的情况 + if (fileName != null && tempFileName.contains(" ")) { + try { + String encodedFileName = FileUtil.urlEncode(fileName.toString(), false, true); + String encodedTempFileName = FileUtil.urlEncode(tempFileName, false, true); + webPath = new StringBuilder(webPath.toString().replace(encodedFileName, encodedTempFileName)); + } catch (UnsupportedEncodingException e) { + log.error("Exception caught", e); + } + } + + // 拼接最终的webPath(包含用户和时间信息) + webPath = new StringBuilder(String.format("%s?%s%s%s%s", webPath, dot, + URLEncoder.encode(userName, StandardCharsets.UTF_8), + dot, URLEncoder.encode(createTime, StandardCharsets.UTF_8))); + paths.add(webPath.toString()); + } + + // 设置响应数据 + response.setData(String.join("", paths)); // 假设SJoin()是直接拼接,若有分隔符需调整 + response.setSuccess(true); + // 设置权限检查结果(1表示有权限,0表示无权限) + response.setOther(CheckAttcAuthory(moduleId, idValue)); + + return response; + } + + public BaseResponse GetAttcFilePaths(String moduleId, String idValue, String specNo, String stepCode) { + return GetAttcFilePaths(moduleId, idValue, specNo, stepCode, "&"); + } + + public BaseResponse GetAcctTreeData(String moduleId, String specNo) { + BaseResponse response = new BaseResponse(); + response.setData(DataImpl.GetAttTreeData(moduleId, specNo)); + response.setSuccess(true); + return response; + } + + /** + * 获取流程图数据 + * + * @param moduleId 模块ID + * @param moduleType 模块类型 + * @param billType 单据类型 + * @param idValue 主键值 + * @param autoStep 是否自动步骤 + * @return 基础响应对象,包含流程图数据和相关信息 + */ + public BaseResponse GetFlowChartData(String moduleId, int moduleType, String billType, String idValue, + boolean autoStep) throws CusException { + BaseResponse response = new BaseResponse(); + Map other = new HashMap<>(); + + // 获取模块信息 + ModuleBaseEntity module = GetModule(moduleId); + if (module == null) { + response.setMsg(String.format("无效模块%s", moduleId)); + return response; + } + + // 确定模块类型(基础模块0/单据模块1) + moduleType = (module instanceof BaseModule) ? 0 : 1; + module.BillType = (NativeExtensionUtils.parseInt(billType, 0)); + + int atStepCode = 0; + boolean stepOver = false; + + // 处理主键值不为空的情况 + if (!isNullOrEmpty(idValue)) { + module.IdValue = (idValue); + BillStateEn stenEn = GetBillState(module); + + if (stenEn != null) { + module.BillType = (stenEn.BillType); + stepOver = stenEn.Finished; + atStepCode = NativeExtensionUtils.parseInt(stenEn.StepCode, 0); + other.put("atStepCode", atStepCode); + other.put("stepover", stepOver ? 1 : 0); + } + } + + // 获取审批历史数据 + List> hisDtVal; + if (!isNullOrEmpty(idValue)) { + hisDtVal = DataImpl.GetFlowStepHis(idValue, module instanceof BaseModule); + } else { + hisDtVal = null; + } + + // 获取流程图配置数据并处理列名小写 + List> allDtVal = DataImpl.GetFlowChartData(module); + allDtVal = ConversionUtils.toLowerColumnName(allDtVal); + + // 筛选符合条件的步骤行(排除自动步骤) + List> filteredRows = null; + if (!allDtVal.isEmpty()) { + filteredRows = allDtVal.stream() + .filter(row -> autoStep || + !toBoolean(row.get("autoStep")) && + (hisDtVal == null || hisDtVal.isEmpty() || + !hisDtVal.stream().anyMatch(_r -> + Objects.equals(_r.get("stepcode"), row.get("stepcode")) && + toBoolean(_r.get("autoStep")) + )) + ) + .collect(Collectors.toList()); + } + + // 处理无有效步骤的情况 + if (filteredRows == null || filteredRows.isEmpty()) { + response.setSuccess(true); + response.setMsg("没有设置审批步骤或审批步骤都为自动审核!"); + return response; + } + + // 处理自动步骤 + List> autoRows = allDtVal.stream() + .filter(row -> toBoolean(row.get("autoStep")) || + (hisDtVal != null && !hisDtVal.isEmpty() && + hisDtVal.stream().anyMatch(_r -> + Objects.equals(_r.get("stepcode"), row.get("stepcode")) && + toBoolean(_r.get("autoStep")) + )) + ) + .collect(Collectors.toList()); + + //if (!autoRows.isEmpty()) + { + // 获取最大步骤编码 + int maxStepCode = filteredRows.stream() + .mapToInt(row -> NativeExtensionUtils.parseInt(row.get("stepcode") + "", 0)) + .max() + .orElse(0); + + for (Map row : filteredRows) { + if (module.getNewWFVer()) { + // 处理新工作流版本的下一步骤 + ArrayList nextSteps = FindNewWFVerNotAutoNSteps(row, autoRows); + if (nextSteps.size() > 0) { + row.put("nextstepcode", sJoin(nextSteps, ",")); + } + int currentStepCode = NativeExtensionUtils.parseInt(row.get("stepcode") + "", 0); + + // 自动修正未设置的下一步骤 + if (isNullOrEmpty(row.get("nextstepcode") + "") && currentStepCode < maxStepCode) { + // 2. 精准复刻C# LINQ逻辑:Where + Min(无结果时抛异常) + int minNextStep = allDtVal.stream() + // 转换为int(对应C# ToInt32()) + .mapToInt(_row -> NativeExtensionUtils.parseInt(_row.get("stepcode") + "", 0)) + // 筛选:stepcode > 当前值(对应C# Where) + .filter(code -> code > currentStepCode) + // 取最小值(无结果时抛出NoSuchElementException,和C# Min抛异常一致) + .min() + // 移除orElse(0),完全对齐C#无默认值的逻辑 + .getAsInt(); + + // 3. 赋值(对应C# row["nextstepcode"] = ...) + row.put("nextstepcode", minNextStep); + } + + // 拼接下一步骤编码 +// String nextStepCodes = nextSteps.stream() +// .map(String::valueOf) +// .collect(Collectors.joining(",")); +// row.put("nextstepcode", nextStepCodes); + } else { + // 处理旧工作流版本的父步骤 + String pcode = FindNotAutoPCode(row, autoRows); + if (!Objects.equals(pcode, row.get("pstepcode") + "")) { + row.put("pstepcode", NativeExtensionUtils.parseInt(pcode, 0)); + } + } + } + } + + // 处理已完成步骤 + List> overRows = filteredRows.stream() + .filter(row -> toBoolean(row.get("stepover"))) + .collect(Collectors.toList()); + + if (!overRows.isEmpty()) { + // 计算当前步骤编码 + int maxOverStepCode = overRows.stream() + .mapToInt(row -> NativeExtensionUtils.parseInt(row.get("stepcode") + "", 0)) + .max() + .orElse(0); + atStepCode = Math.max(atStepCode, maxOverStepCode); + + // 获取当前步骤的下一步骤 + int finalAtStepCode = atStepCode; + Map currentStepRow = filteredRows.stream() + .filter(row -> NativeExtensionUtils.parseInt(row.get("stepcode") + "", 0) == finalAtStepCode) + .findFirst() + .orElse(null); + + String[] atStepNextCode = new String[0]; + if (currentStepRow != null && !isNullOrEmpty(currentStepRow.get("nextstepcode"))) { + atStepNextCode = currentStepRow.get("nextstepcode").toString().split(","); + atStepNextCode = Arrays.stream(atStepNextCode) + .filter(s -> !isNullOrEmpty(s)) + .toArray(String[]::new); + } + + // 处理下一步骤 + List> nextStepCodeRow = null; + if (atStepNextCode.length > 0) { + String[] finalAtStepNextCode = atStepNextCode; + nextStepCodeRow = filteredRows.stream() + .filter(row -> Arrays.asList(finalAtStepNextCode).contains(row.get("stepcode") + "")) + .collect(Collectors.toList()); + } + + int _atStepCode = 0; + if (nextStepCodeRow != null && !nextStepCodeRow.isEmpty()) { + _atStepCode = nextStepCodeRow.stream() + .mapToInt(row -> NativeExtensionUtils.parseInt(row.get("stepcode") + "", 0)) + .min() + .orElse(0); + } else if (nextStepCodeRow != null && atStepNextCode.length > 0) { + int firstNextCode = NativeExtensionUtils.parseInt(atStepNextCode[0], 0); + nextStepCodeRow = filteredRows.stream() + .filter(row -> NativeExtensionUtils.parseInt(row.get("stepcode") + "", 0) > firstNextCode) + .collect(Collectors.toList()); + + if (!nextStepCodeRow.isEmpty()) { + _atStepCode = nextStepCodeRow.stream() + .mapToInt(row -> NativeExtensionUtils.parseInt(row.get("stepcode") + "", 0)) + .min() + .orElse(0); + } + } + + // 处理虚拟节点 + String stepCodeStr = String.valueOf(_atStepCode); + if (stepCodeStr.length() == 6 && stepCodeStr.startsWith("999")) { + int final_atStepCode = _atStepCode; + Map virtualStepRow = filteredRows.stream() + .filter(row -> NativeExtensionUtils.parseInt(row.getOrDefault("stepcode", "").toString(), 0) == final_atStepCode) + .findFirst() + .orElse(null); + + if (virtualStepRow != null) { + String _atStepNextCode = virtualStepRow.get("nextstepcode") + ""; + if (!isNullOrEmpty(_atStepNextCode)) { + atStepNextCode = _atStepNextCode.split(","); + _atStepCode = Arrays.stream(atStepNextCode) + .mapToInt(code -> NativeExtensionUtils.parseInt(code, 0)) + .min() + .orElse(0); + } + } + } else if (_atStepCode == 0) { + _atStepCode = atStepCode + 100; + } + + atStepCode = _atStepCode; + other.put("atStepCode", atStepCode); + other.put("atStepNextCode", Arrays.stream(atStepNextCode) + .mapToInt(Integer::parseInt) + .toArray()); + } + + // 处理审批人信息(替换SQL变量) + if (module != null && !isNullOrEmpty(idValue)) { + SetUpdateRow(module, "", idValue); + Map actRow = module.Updrow; + + for (Map row : filteredRows) { + // 获取操作人 + String operuser = DataImpl.GetSqlUser( + row.get("operoperators") + "", + getUser().UserId, + getUser().UserName, + actRow + ); + + // 处理隐藏的审批人信息 + String auditInfoNoView = WebConfigUtil.get("AuditInfoNoView", ""); + if (!isNullOrEmpty(auditInfoNoView)) { + for (String name : auditInfoNoView.split(",")) { + if (!isNullOrEmpty(name)) { + operuser = operuser.replace(name, ""); + } + } + operuser = operuser.replace(",,", ","); + } + + row.put("operUser", operuser); + row.put("viewUser", DataImpl.GetSqlUser( + row.get("operbrowsers") + "", + getUser().UserId, + getUser().UserName, + actRow + )); + } + } + + // 处理审批历史和状态信息 + if (!isNullOrEmpty(idValue)) { + other.put("history", hisDtVal); + other.put("hisData", DataImpl.GetBaseFlowStep(idValue, module instanceof BaseModule)); + other.put("status", DataImpl.GetFlowStepState(moduleId, idValue, module instanceof BaseModule)); + } + + // 设置响应数据 + other.put("newver", module.getNewWFVer()); + response.setData(ToFlowStepInfo(module, filteredRows, hisDtVal, stepOver, atStepCode)); + response.setOther(other); + response.setSuccess(true); + + return response; + } + + /** + * 查找下一步,排除掉隐藏的步骤,新版审核 + * + * @param row 当前步骤数据行(对应C#的DataRow,用Map模拟) + * @param autoRows 自动步骤数据行集合(对应C#的IEnumerable,用List模拟) + * @return 下一步骤代码列表 + */ + private ArrayList FindNewWFVerNotAutoNSteps + (Map row, List> autoRows) { + // 获取当前步骤的下一步骤代码,分割并过滤空字符串 + String nextStepCode = row.get("NextStepCode") != null ? row.get("NextStepCode").toString() : ""; + String[] nextCodes = nextStepCode.split(","); + List validNextCodes = new ArrayList<>(); + for (String code : nextCodes) { + if (!code.trim().isEmpty()) { + validNextCodes.add(code.trim()); + } + } + + ArrayList nCodes = new ArrayList<>(); + for (String code : validNextCodes) { + // 查找自动步骤中是否存在当前步骤代码 + Map autoRow = autoRows.stream() + .filter(r -> { + String stepCode = r.get("stepcode") != null ? r.get("stepcode").toString() : ""; + return stepCode.equals(code); + }) + .findFirst() + .orElse(null); + + if (autoRow != null) { + // 递归查找子步骤 + nCodes.addAll(FindNewWFVerNotAutoNSteps(autoRow, autoRows)); + } else { + // 非自动步骤直接添加 + nCodes.add(code); + } + } + return nCodes; + } + + /** + * 重新设置上一步骤,老版本审核 + * + * @param row 当前步骤数据行(对应C#的DataRow,用Map模拟) + * @param autoRows 自动步骤数据行集合(对应C#的IEnumerable,用List模拟) + * @return 非自动的上一步骤代码 + */ + private String FindNotAutoPCode(Map row, List> autoRows) { + // 获取当前步骤的上一步骤代码 + String pStepCode = DataTableUtil.getStringValue(row, "pStepCode", ""); + if (isNullOrEmpty(pStepCode)) { + return ""; + } + + // 查找自动步骤中是否存在当前上一步骤代码 + Map autoRow = autoRows.stream() + .filter(r -> { + String stepCode = DataTableUtil.getStringValue(r, "stepcode", ""); + return stepCode.equals(pStepCode); + }) + .findFirst() + .orElse(null); + + if (autoRow != null) { + // 递归查找上一步骤的非自动步骤 + return FindNotAutoPCode(autoRow, autoRows); + } + + return pStepCode; + } + + /** + * 将审批流程转换为前端需要的数据结构 + * + * @param module 模块基础信息 + * @param source 源流程步骤数据(List模拟DataTable) + * @param his 审批历史数据(List模拟DataTable) + * @param stepOver 是否步骤结束 + * @param atStepCode 当前步骤编码 + * @return 转换后的流程步骤信息列表 + */ + private List ToFlowStepInfo(ModuleBaseEntity module, List> source, + List> his, boolean stepOver, int atStepCode) { + List steps = new ArrayList<>(); + + if (module.getNewWFVer()) { + // 新版流程处理逻辑 + for (Map row : source) { + int stepCode = ToInt32(row.get("stepcode")); + boolean stepover = toBoolean(row.get("stepover"), false) || atStepCode > stepCode; + // 创建流程步骤信息(结合历史记录) + FlowStepInfo info = new FlowStepInfo(row, GetAuditHisRow(stepCode, his, stepOver || stepover, atStepCode)); + steps.add(info); + } + } else { + // 旧版流程处理逻辑 + Map> groupDict = new HashMap<>(); + FlowStepInfo pStepInfo = null; + + for (Map row : source) { + int stepCode = ToInt32(row.get("stepcode")); + int pStepCode = ToInt32(row.get("pStepCode")); + String overSteps = (row.get("oversteps") + ""); + boolean stepover = toBoolean(row.get("stepover"), false) || atStepCode > stepCode; + + // 创建流程步骤信息(结合历史记录) + FlowStepInfo info = new FlowStepInfo(row, GetAuditHisRow(stepCode, his, stepOver || stepover, atStepCode)); + + if (pStepCode > 0) { + // 处理有父步骤的情况 + // 查找同属父步骤的最大步骤编码 + int endStepCode = source.stream() + .filter(r -> ToInt32(r.get("pStepCode")) == pStepCode) + .mapToInt(r -> ToInt32(r.get("stepcode"))) + .max() + .orElse(999); + + // 查找下一阶段的最小步骤编码 + int nextStepCode = source.stream() + .filter(r -> ToInt32(r.get("stepcode")) > endStepCode) + .mapToInt(r -> ToInt32(r.get("stepcode"))) + .min() + .orElse(999); + + info.setNextSteps(String.valueOf(nextStepCode)); + + // 更新父步骤的下一步信息 + if (pStepInfo != null) { + String nextSteps = pStepInfo.getNextSteps(); + nextSteps = (nextSteps == null ? "" : nextSteps) + stepCode + ","; + pStepInfo.setNextSteps(nextSteps); + } + } else { + // 处理无父步骤的情况 + if (pStepInfo != null && isNullOrEmpty(pStepInfo.getNextSteps())) { + pStepInfo.setNextSteps(String.valueOf(stepCode)); + } + pStepInfo = info; + } + + steps.add(info); + } + } + + return steps; + } + + /** + * 获取指定步骤的审核历史记录 + * + * @param stepCode 目标步骤编码 + * @param hisSource 审核历史数据源 + * @param stepOver 是否允许步骤结束状态 + * @param atStepCode 当前步骤编码 + * @return 符合条件的审核历史记录数组,无结果时返回空数组而非null + */ + private Map[] GetAuditHisRow(int stepCode, List> hisSource, + boolean stepOver, int atStepCode) { + // 空数据源直接返回空数组 + if (hisSource == null || hisSource.isEmpty()) { + return new Map[0]; + } + + // 未结束状态且目标步骤大于等于当前步骤时返回空数组 + if (!stepOver && stepCode >= atStepCode) { + return new Map[0]; + } + + List> resultList = new ArrayList<>(); + for (Map row : hisSource) { + // 提取并转换步骤编码,默认值为-1(无效步骤) + int hisStepCode = ToInt32(get(row, "stepcode", null)); + + // 历史步骤小于目标步骤时终止循环(假设数据按步骤倒序排列) + if (hisStepCode < stepCode) { + break; + } + + // 匹配目标步骤时添加到结果集 + if (hisStepCode == stepCode) { + resultList.add(row); + } + } + + // 转换为数组并返回(避免ClassCastException) + @SuppressWarnings("unchecked") + Map[] resultArray = resultList.toArray(new Map[0]); + return resultArray; + } + + public BaseResponse GetFlowChartOption(String moduleId, int moduleType, String billType) { + BaseResponse response = new BaseResponse(); + // 假设getFlowChartOption返回的是List,模拟DataTable结构 + List> dtVal = DataImpl.GetFlowChartOption(moduleId, moduleType, billType); + + Map privateCfg = new HashMap<>(); + + for (Map row : dtVal) { + try { + // 将option字段的JSON字符串转换为Map + String optionJson = String.valueOf(row.get("option")); + Map op = (Map) JSON.Decode(optionJson); + + // 获取配置名称 + String cfgName = String.valueOf(row.get("cfgName")); + // 添加到私有配置 + privateCfg.put(cfgName, op); + } catch (Exception e) { + // 处理JSON解析异常或空值异常 + response.setSuccess(false); + response.setMsg("解析流程图配置失败: " + e.getMessage()); + return response; + } + } + + response.setData(privateCfg); + response.setSuccess(true); + return response; + } + + /** + * 更新流程图配置(包含全局配置和状态配置分离处理) + * + * @param moduleId 模块ID + * @param moduleType 模块类型 + * @param billType 单据类型 + * @param options 配置JSON字符串 + * @return 响应结果 + */ + public BaseResponse UpdateFlowChartOption(String moduleId, int moduleType, String billType, String options) { + BaseResponse response = new BaseResponse(); + try { + // 将JSON字符串转换为Map(替代C#的Hashtable) + Map ops = (Map) JSON.Decode(options); + + // 分离全局配置和状态配置 + Map globalCfg = new HashMap<>(); + Map stateCfg = new HashMap<>(); + + Set keys = null; + if (ops != null) { + keys = ops.keySet(); + } + if (keys != null) { + for (String key : keys) { + if (key.contains("flowstate")) { + // 处理状态配置(嵌套Map) + Object cfgObj = ops.get(key); + if (cfgObj instanceof Map) { + @SuppressWarnings("unchecked") + Map cfgs = (Map) cfgObj; + stateCfg.putAll(cfgs); + } + } else { + // 处理全局配置 + globalCfg.put(key, ops.get(key)); + } + } + } + + // 将全局配置添加到状态配置中 + stateCfg.put("globalCfg", globalCfg); + + // 调用数据层更新方法 + response = DataImpl.UpdateFlowChartOption(moduleId, moduleType, billType, stateCfg, true); + + } catch (Exception e) { + response.setSuccess(false); + response.setMsg("更新流程图配置失败: " + e.getMessage()); + } + return response; + } + + /** + * 部分更新流程图配置(直接传递原始配置) + * + * @param moduleId 模块ID + * @param moduleType 模块类型 + * @param billType 单据类型 + * @param options 配置JSON字符串 + * @return 响应结果 + */ + public BaseResponse UpdatePartFlowChartOption(String moduleId, int moduleType, String billType, String options) { + BaseResponse response = new BaseResponse(); + try { + // 将JSON字符串转换为Map + Map ops = (Map) JSON.Decode(options); + + // 直接调用数据层更新方法(不做配置分离) + response = DataImpl.UpdateFlowChartOption(moduleId, moduleType, billType, ops, false); + + } catch (Exception e) { + response.setSuccess(false); + response.setMsg("部分更新流程图配置失败: " + e.getMessage()); + } + return response; + } + + /** + * 获取需要生成流程图的模块列表 + * + * @return 响应结果 + */ + public BaseResponse GetUpdFlowChartList() { + BaseResponse response = new BaseResponse(); + try { + // 调用数据层获取列表 + Map dtVal = DataImpl.GetUpdFlowChartList(); + + response.setData(dtVal); + response.setSuccess(true); + response.setMsg("成功获取了需要生成流程图的模块列表。"); + + } catch (Exception e) { + response.setSuccess(false); + response.setMsg("获取流程图模块列表失败: " + e.getMessage()); + } + return response; + } + + public BaseResponse GetTimeEvent() { + BaseResponse response = new BaseResponse(); + try { + //【1126】BS里程碑事件: + List> result = jdbcTemplate.queryForList("select * from P_SystemDevHisTab"); + response.setData(result); + response.setSuccess(true); + return response; + } catch (Exception ex) { + response.setSuccess(true); + response.setData(false); + response.setMsg("数据库中无【里程碑事件】表P_SystemDevHisTab"); + return response; + } + } + + /** + * 获取甘特图模块数据 + * + * @param moduleId 模块ID + * @param record 记录参数 + * @param leftRecord 左侧记录参数 + * @param pams 额外参数 + * @param where 条件语句 + * @param multi 是否多记录 + * @param grid 是否网格模式 + * @param contextMenuId 上下文菜单ID + * @return 响应结果 + */ + public BaseResponse GanttGetModuleData(String moduleId, String record, String leftRecord, + String pams, String where, boolean multi, boolean grid, + int contextMenuId) { + BaseResponse response = new BaseResponse(); + + // 获取基础模块信息 + BaseModule module = GetBaseModule(moduleId, getMenuId()); + if (module == null) { + response.setSuccess(false); + response.setMsg("未找到该模块编号对应模块!"); + return response; + } + + // 【=1=】获取第一级数据(资源ID相关) + List> fieldsDT = GetColumnRows(moduleId); + String firstSql = ""; + + for (Map oneField : fieldsDT) { + String resourceId = DataTableUtil.getRowVal(oneField, "FieldName", "") + ""; + if (resourceId.toLowerCase().contains("resourceid")) { + firstSql = DataTableUtil.getRowVal(oneField, "fieldsql", "") + ""; + break; + } + } + + // 【++ 1】v1版本处理(未找到第一级SQL时直接返回基础数据) + if (firstSql == null || firstSql.isEmpty()) { + response = GetModuleData(module, record, leftRecord, pams, where, multi, grid, contextMenuId, 0, false); + response.setMsg("v1"); + return response; + } + + // 执行第一级SQL查询并转换为哈希表列表 + List> firstDataList = jdbcTemplate.queryForList(firstSql); + List> firstHS = new ArrayList<>(); + for (Map row : firstDataList) { + firstHS.add(new HashMap<>(row)); // 转换为可修改的HashMap + } + + // 【=2=】获取第二级数据 + BaseResponse secondRP = GetModuleData(module, record, leftRecord, pams, where, multi, grid, contextMenuId, 0, false); + BaseModule theOther = (BaseModule) secondRP.getOther(); + String prefix = theOther.getMenuPrefix(); // 例如:billdocument_id + String resourceIdStr = (prefix + "resourceid").toLowerCase(); + + // 初始化第一级数据的子项和树ID + for (int i = 0; i < firstHS.size(); i++) { + Map oneFirst = firstHS.get(i); + oneFirst.put("items", new ArrayList>()); + oneFirst.put("treeId", "_" + (i + 1)); + oneFirst.put(prefix + "resourceid", DataTableUtil.getRowVal(oneFirst, "speciesno", "")); + oneFirst.put(prefix + "subject", DataTableUtil.getRowVal(oneFirst, "speciesname", "")); + } + + // 关联第二级数据到第一级 + boolean isV2 = false; + @SuppressWarnings("unchecked") + List> secondOrigin = (List>) secondRP.getData(); + + for (int i = 0; i < secondOrigin.size(); i++) { + Map oneSecond = secondOrigin.get(i); + String subId = DataTableUtil.getRowVal(oneSecond, resourceIdStr, "") + ""; + + for (Map oneFirst : firstHS) { + String topId = DataTableUtil.getRowVal(oneFirst, "speciesno", "") + ""; + if (subId.contains(topId)) { + isV2 = true; + oneSecond.put("treeId", oneFirst.get("treeId") + "_" + (i + 1)); + @SuppressWarnings("unchecked") + List> items = (List>) oneFirst.get("items"); + items.add(oneSecond); + break; + } + } + } + + // 组装响应结果 + if (isV2) { + response.setSuccess(true); + response.setData(firstHS); + response.setTot(firstHS.size()); + } else { + response = secondRP; + response.setMsg("v1"); + } + + return response; + } + + public BaseResponse GetPrintInfo(String moduleId, String printName, String recStr) { + if (UpdateImpl.getVersion() < 1035) { + return GetPrintInfo_old(moduleId, printName, recStr); + } + BaseResponse response = new BaseResponse(); + Map record = null; + if (!isNullOrEmpty(recStr)) { + record = (Map) JSON.Decode(recStr); + } + response.setData(GetPrintInfo(moduleId, printName, record)); + response.setSuccess(true); + return response; + } + + /** + * 获取旧版打印信息 + * + * @param moduleId 模块ID + * @param printName 打印名称 + * @param recStr 记录字符串(JSON格式) + * @return 包含打印信息的响应对象 + */ + private BaseResponse GetPrintInfo_old(String moduleId, String printName, String recStr) { + BaseResponse response = new BaseResponse(); + Map retData = new HashMap<>(); + Map saveRec = new HashMap<>(); + Map record = null; + + // 解析记录JSON字符串 + if (recStr != null && !recStr.isEmpty()) { + record = (Map) JSON.Decode(recStr, Map.class); + } + + // 获取打印SQL信息 + boolean isBaseModule = DataImpl.IsBaseModule(moduleId); + List> printSqls = DataImpl.GetPrintSqls(moduleId, recStr, isBaseModule); + Map dtVal = printSqls.isEmpty() ? null : printSqls.get(0); + + // 设置基础打印信息 + retData.put("dllcoid", moduleId); + retData.put("name", printName); + retData.put("dbServer", WebConfigUtil.get("dbServer")); + retData.put("dbName", WebConfigUtil.get("dbName")); + retData.put("operatorname", getUser().UserName); + + // 处理SQL语句 + if (dtVal != null) { + for (Map.Entry entry : dtVal.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue() != null ? entry.getValue().toString() : ""; + + // 处理以"sql"开头的键对应的SQL语句 + if (key.startsWith("sql") && !value.isEmpty()) { + value = dealQuerySql(value, record, null, null, null, null, false, false, false); + retData.put(key, value); + } + } + } + + // 设置响应数据 + response.setData(retData); + response.setSuccess(true); + return response; + } + + /** + * + * /// 获取web打印状态,判断依据为数据存在则没打印,反之已打印,应用于20240830后的打印程序 + * /// + * /// + * /// + **/ + public BaseResponse GetPrintSta(int pid) { + BaseResponse response = new BaseResponse(); + response.setSuccess(true); + response.setData(DataImpl.GetPrintSta(pid)); + return response; + } + + public BaseResponse GetWebPrintInfo(String moduleId, String printName, String recStr, boolean isBase) { + BaseResponse response = new BaseResponse(); + List> dtval = DataImpl.GetWebPrintInfo(moduleId, printName); + response.setSuccess(true); + response.setData(dtval); + response.setOther(GetPrintData(moduleId, recStr, DataImpl.IsBaseModule(moduleId))); + return response; + } + + /** + * 获取打印数据 + * + * @param moduleId 模块标识 + * @param recStr 记录字符串(JSON格式) + * @param isBase 是否为基础模块 + * @return 打印数据列表(每个元素为查询结果的Map集合) + */ + private List>> GetPrintData(String moduleId, String recStr, boolean isBase) { + // 解析记录JSON字符串 + Map record = null; + if (recStr != null && !recStr.isEmpty()) { + record = (Map) JSON.Decode(recStr, Map.class); + } + + // 获取打印SQL信息 + List> printSqls = DataImpl.GetPrintSqls(moduleId, recStr, isBase); + Map dtVal = printSqls.isEmpty() ? null : printSqls.get(0); + + List>> retDict = new ArrayList<>(); + if (dtVal != null) { + for (Map.Entry entry : dtVal.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue() != null ? entry.getValue().toString() : ""; + + // 处理以"sql"开头的有效SQL语句 + if (key.startsWith("sql") && !value.isEmpty()) { + // 处理SQL语句(替换参数等) + value = dealQuerySql(value, record, null, null, null, null); + + try { + // 执行SQL查询并将结果转换为Map集合列表 + List> queryResult = jdbcTemplate.queryForList(value); + retDict.add(queryResult); + } catch (Exception e) { + // 忽略异常,对应原代码的空catch块 + } + } + } + } + return retDict; + } + + /// + /// 添加或修改web打印模板 + /// + /// The module identifier. + /// Name of the print. + /// The content. + /// The identifier. + /// System.String. + /// + /// + public String AddOrUpdWebPrint(String moduleId, String printName, String content, String id) { + return DataImpl.AddOrUpdWebPrint(moduleId, printName, content, id); + } + + /** + * 增加下载计数 + * + * @param moduleId 模块ID + * @param ids 记录ID集合(逗号分隔) + */ + public void AddDownloadCount(String moduleId, String ids) { + // 获取模块信息 + ModuleBaseEntity module = GetModule(moduleId); + + // 验证模块信息及必要字段 + if (module != null && !isNullOrEmpty(module.getMasterTable()) && !isNullOrEmpty(module.getIdField())) { + // 处理ID集合,替换逗号为','','格式(适配SQL的IN条件) + String formattedIds = ids.replace(",", "','"); + + // 构建SQL:检查字段是否存在,存在则更新下载计数 + String sql = String.format( + "if col_length('%s', 'downloadCount') is not null " + + "exec('update %s set downloadCount = isnull(downloadCount, 0) + 1 where %s in (''%s'')')", + module.getMasterTable(), + module.getMasterTable(), + module.getIdField(), + formattedIds + ); + + // 执行SQL + jdbcTemplate.execute(sql); + } + } + + /** + * 查询主表明细数据用于导出 + * + * @param moduleId 模块ID + * @param detailId 明细ID + * @param mainData 主数据列表 + * @param index 分批索引 + * @param multi 是否多记录 + * @param grid 是否表格视图 + * @param isAuditAttc 是否为审核附件 + * @return 包含导出数据的BaseResponse + */ + public BaseResponse GetExportData(String moduleId, int detailId, List> mainData, int index, + boolean multi, boolean grid, boolean isAuditAttc) { + BaseResponse response = new BaseResponse(); + BaseModule module = GetBaseModule(moduleId, getMenuId()); + BaseDetailModule dModule = GetBaseDetailModuel(detailId, isAuditAttc); + + if (module == null || dModule == null) { + return response; + } + + BaseModule oldModule = module; + // 处理联合模块 + if (module.getModuleId().equals(dModule.getUnionMenuCode())) { + module = GetBaseModule(dModule.getUnionKey(), getMenuId()); + } + + // 构建明细SQL + String detailSql = isAuditAttc ? module.getMasterSql() : dModule.getUnionSql(); + String dformkey = dModule.getFromkey(); + String dmoduleId = dModule.getModuleId(); + + // 处理联合菜单代码 + if (dModule.getUnionMenuCode() != null && !dModule.getUnionMenuCode().isEmpty()) { + BaseModule unionModule = GetBaseModule(dModule.getUnionMenuCode(), getMenuId()); + if (unionModule == null || unionModule.getMasterSql() == null || unionModule.getMasterSql().isEmpty()) { + return response; + } + detailSql = unionModule.getMasterSql(); + dformkey = unionModule.getModuleId(); + dmoduleId = unionModule.getModuleId(); + } + + if (detailSql == null || detailSql.isEmpty()) { + response.setMsg("noDetailSql"); + return response; + } + + boolean isChart = dModule.getUnionType() == 1; + String detailIdKey = trimBraces(dModule.getUnionField()); + String mainDataIdKey = (dModule.getUnionParentField() == null || dModule.getUnionParentField().isEmpty()) + ? module.getIdField() + : trimBraces(dModule.getUnionParentField()); + + Map responseData = new HashMap<>(); + // 总记录数 + + // 存在明细SQL但不存在关联字段的情况 + if (detailIdKey == null || detailIdKey.isEmpty()) { + for (int i = 0; i < mainData.size(); i++) { + int indexVal = index > 0 ? i + (index - 1) * 2000 : i; + String sql = detailSql; + // 处理查询SQL(假设DealQuerySql已实现) + sql = dealQuerySql(sql, mainData.get(i), mainData.get(i), null, null, null); + + // 执行查询获取明细数据 + List> dtVal = dbOperator.executeDataTable(sql, 0, -1, tot); + if (dtVal != null && !dtVal.isEmpty()) { + // 检查数据量限制,超过则中断 + if (dtVal.size() > 50 && mainData.size() > 1000) { + response.setOther(true); + responseData.put("stopexport", true); + response.setData(responseData); + break; // 替代goto + } + + // 转换为哈希表列表并添加关联键 +// List> hs = dtVal.toHashTable(); + for (Map h : dtVal) { + h.put("uniondetailkey", indexVal); + } + + // 合并明细数据 + if (responseData.containsKey("detailData")) { + List> existing = (List>) responseData.get("detailData"); + existing.addAll(dtVal); + responseData.put("detailData", existing); + } else { + responseData.put("detailData", new ArrayList<>(dtVal)); + } + } + + // 为主数据添加关联键 + mainData.get(i).put("unionmainkey", indexVal); + } + responseData.put("mainData", mainData); + response.setData(responseData); + response.setOther(true); + } + // 存在关联字段的情况 + else { + Map leftRecord = new HashMap<>(); + detailSql = dealQuerySql(detailSql, leftRecord, leftRecord, null, null, null); + + // 处理WHERE条件 + boolean hasWhere = detailSql.contains("where") || detailSql.contains("Where"); + if (!hasWhere) { + detailSql += " where 1=1 "; + } + + // 拼接IN条件 + StringBuilder strIn = new StringBuilder(" and " + detailIdKey + " in ( "); + for (Map data : mainData) { + Object idValue = data.get(mainDataIdKey); + strIn.append("'").append(idValue).append("',"); + } + // 移除最后一个逗号 + if (!strIn.isEmpty() && strIn.charAt(strIn.length() - 1) == ',') { + strIn.setLength(strIn.length() - 1); + } + strIn.append(" )"); + detailSql += strIn.toString(); + + // 执行查询 + List> dtVal = dbOperator.executeDataTable(detailSql, 0, -1, tot); + + // 处理下拉框字段 + List boxs = new ArrayList<>(); + List> colorAndboxCols = DataImpl.GetColorAndBoxColumns(dmoduleId); + if (colorAndboxCols != null) { + BaseModule finalModule = module; + boxs = colorAndboxCols.stream() + .filter(row -> { + Object fieldsql = row.get("fieldsql"); + return fieldsql != null && !fieldsql.toString().isEmpty(); + }) + .map(row -> createControl.createControl(row, finalModule, false)) + .filter(control -> control instanceof ComboBox) + .map(control -> (ComboBox) control) + .filter(box -> !box.getValueField().equals(box.getDisplayField())) + .collect(Collectors.toList()); + } + + // 处理查询结果 + if (dtVal != null && !dtVal.isEmpty()) { + // 设置主数据列信息 + Map firstRow = dtVal.get(0); + // 转换列信息为 MainDataColumns + module.MainDataColumns = firstRow.keySet().stream() + .map(columnName -> { + Map colInfo = new HashMap<>(); + // 设置 dataIndex 为列名 + colInfo.put("dataIndex", columnName); + // 获取列值类型,转换为 xtype(假设 PublicUtil 有 TypeToColumnType 方法) + Object value = firstRow.get(columnName); + Class dataType = value != null ? value.getClass() : Object.class; + colInfo.put("xtype", PublicUtil.TypeToColumnType(dataType)); + return colInfo; + }) + .collect(Collectors.toList()); + + // 转换为哈希表列表并处理下拉框值 +// List> tbs = toHashTable(dtVal); + response.setData(convertBoxVal(dtVal, boxs, + module.getModuleId() + (dmoduleId == null || dmoduleId.isEmpty() ? "" : "=>" + dmoduleId))); + } + } + + response.setTot(tot[0]); + response.setSuccess(true); + return response; + } + + // 辅助方法:去除字符串中的大括号 + private String trimBraces(String str) { + if (str == null || str.isEmpty()) { + return str; + } + return str.replaceAll("[{}]", "").trim(); + } + + /** + * 获取审核消息列表 + * + * @param userId 用户ID + * @return 包含消息列表的响应对象 + */ + public BaseResponse GetAuditMsgTab(String userId) { + BaseResponse response = new BaseResponse(); + // 假设DataImpl的getAuditMsgTab返回List模拟DataTable + List> dtVal = DataImpl.GetAuditMsgTab(userId); + + if (dtVal != null && !dtVal.isEmpty()) { + for (Map row : dtVal) { + String dllname = (row.get("dllname") != null) ? row.get("dllname").toString() : ""; + if (!dllname.isEmpty()) { + boolean[] tempBool = new boolean[1]; // 用数组存储基本类型的返回值 + String moduleName = SystemMenu.convertToModuleName(dllname, 1, tempBool); + row.put("dllname", moduleName); + } + } + } + + response.setData(dtVal); + response.setSuccess(true); + return response; + } + + /** + * 查看单条审核消息 + * + * @param userId 用户ID + * @param record 消息记录参数 + * @return 包含单条消息详情的响应对象 + */ + public BaseResponse SeeOneAuditMsg(String userId, Map record) { + BaseResponse response = new BaseResponse(); + // 调用数据层方法获取单条消息详情 + Object data = DataImpl.SeeOneAuditMsg(getUser().UserId, record); + response.setData(data); + response.setSuccess(true); + return response; + } + + /** + * 查看所有审核消息 + * + * @return 包含所有消息的响应对象 + */ + public BaseResponse SeeAllAuditMsg() { + BaseResponse response = new BaseResponse(); + // 调用数据层方法获取所有消息 + Object data = DataImpl.SeeAllAuditMsg(getUser().UserId); + response.setData(data); + response.setSuccess(true); + return response; + } + + /** + * 确认消息 + * + * @param moduleId 模块ID + * @param idValue 消息ID + * @param remark 备注 + * @return 确认是否成功 + */ + public boolean ComfirmMsg(String moduleId, String idValue, String remark) { + // 调用数据层方法,返回影响行数,大于0则表示成功 + int affectedRows = DataImpl.ConfirmMsg(moduleId, idValue, getUser().UserId, remark); + return affectedRows > 0; + } + + public BaseResponse GetMsg(int id, int type) { + BaseResponse response = new BaseResponse(); + // 调用DataImpl的getMsg方法获取消息数据(假设返回List模拟DataTable) + List> msgData = DataImpl.GetMsg(id, type); + Hashtable retTab = null; + + if (msgData != null && !msgData.isEmpty()) { + // 取第一行数据转换为Hashtable + Map firstRow = msgData.get(0); + retTab = new Hashtable<>(); + for (Map.Entry entry : firstRow.entrySet()) { + retTab.put(entry.getKey(), entry.getValue()); + } + + // 处理dllname相关逻辑 + String dllname = retTab.get("dllname") != null ? retTab.get("dllname").toString() : ""; + boolean[] isurl = new boolean[1]; // 用数组存储引用类型结果 + Object xtype = SystemMenu.convertToModuleName(dllname, 0, isurl); + retTab.put("xtype", xtype); + retTab.put("ModuleId", retTab.get("moduleid")); + retTab.put("idValue", retTab.get("idvalue")); + retTab.put("title", retTab.get("modulename")); + + // 提取用户信息 + String userId = retTab.get("userid") != null ? retTab.get("userid").toString() : ""; + String username = retTab.get("username") != null ? retTab.get("username").toString() : ""; + String loginaccount = retTab.get("loginaccount") != null ? retTab.get("loginaccount").toString() : ""; + + // 构建登录用户信息 + LoginUserInfo luser = new LoginUserInfo(); + luser.UserId = (userId); + luser.UserCode = (loginaccount); + luser.UserName = (username); + response.setOther(luser); + + // 检查当前用户与消息所属用户是否一致,不一致则处理 + LoginUserInfo currentUser = getUser(); + if (currentUser != null && !currentUser.UserId.equals(luser.UserId)) { + SysUserImpl sysUserImpl = new SysUserImpl(); + // 调用登录成功处理方法(注意:Java中无ref参数,通过返回值处理) + sysUserImpl.OnLoginSuccess( + null, + luser, + "0", + 0, + luser.UserId, + dbOperator.getConnectionString(), + response, + false + ); + // 注释:如需设置会话,可添加如下代码 + // setSessionVal(getUserSessionName(), luser); + } + + response.setSuccess(true); + } else { + response.setMsg("消息已失效!"); + } + + response.setData(retTab); + return response; + } + + public List> GetRoles() { + return DataImpl.GetRoles(); + } + + public BaseResponse DelRoles(String ids) { + return DataImpl.DelRoles(ids); + } + + public List> GetRoleUsers(String roleId) { + return DataImpl.GetRoleUsers(roleId); + } + + public List> GetUsers(String name, String code) { + return DataImpl.GetUsers(name, code, 0); + } + + public BaseResponse GetUserPrev(String userId) { + return DataImpl.GetUserPrev(userId, 0); + } + + /** + * 保存角色权限 + */ + public BaseResponse SaveRolePrev(String data) { + BaseResponse response = new BaseResponse(); + // 确保数据以数组格式存在 + if (!data.startsWith("[")) { + data = "[" + data + "]"; + } + + try { + // 解析JSON为Map列表 + List> datas = (List>) JSON.Decode(data); + + if (datas == null || datas.isEmpty()) { + return response; + } + + for (Map tab : datas) { + String roleId = String.valueOf(tab.getOrDefault("id", "")); + String readPrev = String.valueOf(tab.getOrDefault("readpurview", "")); + String editPrev = String.valueOf(tab.getOrDefault("editpurview", "")); + + if (roleId.isEmpty()) { + continue; + } + + // 构建更新SQL + String sql = String.format( + "update p_systemRoleSetTab set ReadPurview='%s', EditPurview='%s' where id = '%s'", + readPrev, editPrev, roleId + ); + + // 执行更新 + int rowsAffected = jdbcTemplate.queryForObject(sql, Integer.class); + response.setSuccess(rowsAffected > 0); + + if (response.isSuccess()) { + response.setMsg(DataImpl.UpdRolePurview(roleId).getMsg()); + } + } + } catch (Exception e) { + response.setSuccess(false); + response.setMsg("保存角色权限失败:" + e.getMessage()); + } + + return response; + } + + /** + * 保存用户权限 + */ + public BaseResponse SaveUserPrev(String data) { + BaseResponse response = new BaseResponse(); + // 确保数据以数组格式存在 + if (!data.startsWith("[")) { + data = "[" + data + "]"; + } + + try { + // 解析JSON为Map列表 + List> datas = (List>) JSON.Decode(data); + + if (datas == null || datas.isEmpty()) { + return response; + } + + // 获取当前登录用户信息(假设从上下文获取) + LoginUserInfo user = getUser(); + if (user == null) { + response.setSuccess(false); + response.setMsg("用户未登录"); + return response; + } + + for (Map tab : datas) { + String userId = String.valueOf(tab.getOrDefault("员工id", "")); + String readPrev = String.valueOf(tab.getOrDefault("readpurview", "")).replaceAll(",$", "").replaceAll("^,", ""); + String editPrev = String.valueOf(tab.getOrDefault("editpurview", "")).replaceAll(",$", "").replaceAll("^,", ""); + + if (userId.isEmpty()) { + continue; + } + + // 查询用户信息 + List> userTab = DataImpl.GetUsers("", "", Integer.parseInt(userId)); + if (userTab.isEmpty()) { + continue; + } + + // 获取所属部门 + String department = String.valueOf(userTab.get(0).getOrDefault("所属部门", "")); + LocalDateTime now = LocalDateTime.now(); + + // 构建SQL(存在则更新,不存在则插入) + String sql = String.format( + "if exists (select * from p_systemRoleOperSetTab s where s.roleOperatorId='%s') " + + "update p_systemRoleOperSetTab set ReadPurview='%s', EditPurview='%s' where roleOperatorId = '%s' " + + "else " + + "insert into p_systemRoleOperSetTab " + + "(roleId, roleOperatorId, operatorid, operatorname, operatedate, department, ReadPurview, EditPurview) " + + "values ('0', '%s', '%s', '%s', '%s', '%s', '%s', '%s')", + userId, readPrev, editPrev, userId, + userId, user.UserId, user.UserName, now, department, readPrev, editPrev + ); + + // 执行SQL + int rowsAffected = jdbcTemplate.queryForObject(sql, Integer.class); + response.setSuccess(rowsAffected > 0); + + if (response.isSuccess()) { + response.setMsg(DataImpl.UpdUserPurview(userId).getMsg()); + } + } + } catch (Exception e) { + response.setSuccess(false); + response.setMsg("保存用户权限失败:" + e.getMessage()); + } + + return response; + } + + public BaseResponse DelRoleUser(String ids) { + return DataImpl.DelRoleUser(ids); + } + +// public List> GetCommentList(String moduleId, String idVal, String stepCode) { +// return DataImpl.GetCommentList(moduleId, idVal, stepCode); +// } + + /** + * 获取评论列表 + * + * @param moduleId 模块ID + * @param idVal ID值 + * @param stepCode 步骤编码 + * @return 评论列表(HashMap列表) + */ + public List> GetCommentList(String moduleId, String idVal, String stepCode) { + // 获取基础模块 + BaseModule module = GetBaseModule("Comment_001", ""); + + if (module != null) { + // 第一种获取方式:通过模块SQL查询 + String querySql = module.getMasterSql(); + HashMap paramMap = new HashMap<>(); + paramMap.put("idvalue", idVal); + paramMap.put("dllcoid", moduleId); + paramMap.put("stepcode", stepCode); + + // 处理SQL参数替换 + querySql = dealQuerySql(querySql, paramMap, paramMap, null, null, null); + + // 执行查询并转换为HashMap列表 + return jdbcTemplate.queryForList(querySql); + } else { + // 第二种获取方式:通过数据接口获取 + List> dataTable = DataImpl.GetCommentList(moduleId, idVal, stepCode); + + List> tbs = toHashTable( + dataTable, + null, + null, + null, + true, + true, + false, + (row, name, value) -> { + try { + return fillFilePath(new String[]{"atts"}, name, value); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + ); + + return tbs; + } + } + + /** + * 填充/替换文件路径(完整还原原C#逻辑) + * + * @param picFieldNames 需要处理的字段名数组(如{"atts"}) + * @param name 当前处理的字段名 + * @param v 字段原始值 + * @return 处理后的文件路径值 + */ + protected Object fillFilePath(String[] picFieldNames, String name, Object v) throws UnsupportedEncodingException { + // 1. 将值转为字符串(对应C#的$"{v}") + String val = String.valueOf(v); + + // 2. 核心判断条件(完整还原原逻辑) + // 条件:值非空 + 不含http + 字段名在目标数组中 + (配置使用DB附件 且 非默认服务器) 或 AppDomain非空 + boolean condition1 = !isNullOrEmpty(val) && val.indexOf("http") < 0; + boolean condition2 = ArrayUtils.contains(picFieldNames, name); // 数组包含判断 + boolean condition3 = (toBoolean(WebConfigUtil.get("useDbAttc")) && !DataImpl.isDefaultServer()) + || !isNullOrEmpty(getAppDomain()); + + if (condition1 && condition2 && condition3) { + // 3. 分割值为数组(对应C#的Split(',')) + String[] vals = val.split(","); + String[] newVals = new String[vals.length]; + + // 4. 构建DB版本路径(对应C#的$"{WebConfigUtil.FileVPath}_{user.ServerId}") + String dbVerPath = WebConfigUtil.getFileVPath() + "_" + getUser().getServerId(); + + // 5. 遍历替换每个路径 + for (int i = 0; i < vals.length; i++) { + if (SiteUtil.containsVirtualDirectory(dbVerPath)) { + // 替换原有文件虚拟路径 + newVals[i] = vals[i].replace(WebConfigUtil.getFileVPath(), dbVerPath); + } else { + // 拼接AppDomain路径(去除末尾的/) + String trimmedAppDomain = stripEnd(getAppDomain(), "/"); + newVals[i] = trimmedAppDomain + vals[i]; + } + } + + // 6. 拼接回字符串返回 + return join(newVals, ","); + } + + // 不满足条件则返回原始值 + return v; + } + + /** + * 保存角色用户关系 + */ + public BaseResponse SaveRoleUser(String data) { + BaseResponse response = new BaseResponse(); + // 检查数据是否为空或空数组 + if (data == null || data.trim().isEmpty() || "[]".equals(data.trim())) { + return response; + } + + // 确保数据以数组格式存在 + if (!data.startsWith("[")) { + data = "[" + data + "]"; + } + + try { + // 解析JSON为Map列表 + List> datas = (List>) JSON.Decode(data); + + // 调用AddOrUpdTable方法处理数据(假设在BaseImpl中实现了该方法) + response = AddOrUpdTable(data, "p_systemRoleOperSetTab", "id", true, null); + + // 如果操作成功,更新角色权限 + if (response.isSuccess()) { + if (datas != null) { + for (Map fRow : datas) { + String roleId = String.valueOf(fRow.getOrDefault("roleid", "")); + if (!roleId.isEmpty()) { + DataImpl.UpdRolePurview(roleId); + } + } + } + } + } catch (Exception e) { + response.setSuccess(false); + response.setMsg("保存角色用户关系失败:" + e.getMessage()); + } + + return response; + } + + /** + * 判断是单据还是基础档案 + * + * @param moduleId 模块ID + * @return 基础响应对象,data包含isbase字段(1:基础档案;2:单据;-1:均不是) + */ + public BaseResponse CheckModuleOrBill(String moduleId) { + BaseResponse response = new BaseResponse(); + Map resultMap = new HashMap<>(); + + // 检查基础档案 + ModuleBaseEntity oneMod = GetBaseModule(moduleId, ""); + resultMap.put("isbase", 1); + + // 若基础档案不存在,检查单据 + if (oneMod == null) { + oneMod = GetBillModule(moduleId, ""); + resultMap.put("isbase", 2); + } + + // 若均不存在,设置为-1 + if (oneMod == null) { + resultMap.put("isbase", -1); + } + + response.setData(resultMap); + response.setSuccess(true); + return response; + } + + /** + * DLL名称转换 + * + * @param dllName DLL名称 + * @return 基础响应对象,data包含转换后的模块名称 + */ + public BaseResponse ConvertDllName(String dllName) { + BaseResponse response = new BaseResponse(); + Map resultMap = new HashMap<>(); + + // 调用SystemMenuEntity的转换方法(模拟out参数使用数组) + boolean[] tempBool = new boolean[1]; // 用数组存储布尔值,模拟引用传递 + String moduleName = SystemMenu.convertToModuleName(dllName, 1, tempBool); + + resultMap.put("moduleName", moduleName); + response.setData(resultMap); + response.setSuccess(true); + return response; + } + + /** + * 获取桌面模块左侧数据 + * + * @return 包含左侧模块数据的基础响应对象 + */ + public BaseResponse GetDesktopModuleLeft() { + BaseResponse response = new BaseResponse(); + // 获取左侧模块数据(DataImpl返回List模拟DataTable) + List> itemsList = DataImpl.GetDesktopModuleLeft(); + // 转换为哈希表结构(保持与C# ToHashTable一致的格式) + response.setData(itemsList); + response.setTot(itemsList.size()); + response.setSuccess(true); + return response; + } + + /** + * 获取桌面模块主数据 + * + * @param dllcoid 模块标识 + * @return 包含主模块数据的基础响应对象 + */ + public BaseResponse GetDesktopModuleMain(String dllcoid) { + BaseResponse response = new BaseResponse(); + + // 检查并填充个人配置(若不存在则生成默认配置) + DataImpl.CheckAndFillDesktopModuleMain(dllcoid); + + // 获取当前用户信息 + String userId = getUser().UserId; + String userName = getUser().UserName; + + // 获取主模块数据 + List> itemsList = DataImpl.GetDesktopModuleMain(userId, userName, dllcoid); + // 转换为哈希表结构 + response.setData(itemsList); + response.setTot(itemsList.size()); + response.setSuccess(true); + return response; + } + + /** + * 对常用工具添加或删除 + * + * @param cardId 卡片ID + * @param adds 要添加的菜单ID字符串(逗号分隔) + * @param dels 要删除的菜单ID字符串(逗号分隔) + * @return 操作结果响应 + */ + public BaseResponse UpdCommonUse(int cardId, String adds, String dels) { + BaseResponse response = new BaseResponse(); + + // 若添加和删除列表都为空,直接返回空响应 + if ((adds == null || adds.isEmpty()) && (dels == null || dels.isEmpty())) { + return response; + } + + // SQL模板定义 + final String addSqlTemplate = "insert into P_MessageToolLinkDllTab " + + "(cardId, EmployeeID, LMenuid, Lsubsysid, ObjDll, DllShowCaption, GroupTagid, ItemTagid, LinkModeTag) " + + "select '%d', '%s', '%s', m.subsysid, m.dllfilename, m.menucaption, '2', '999', '1' " + + "from p_formmenuconfigtab m where menuid = '%s' " + + "and isnull(m.urlparams, '') <> '' " + + "and (isnull(m.dllfilename1, '') <> '' or isnull(m.dllfilename, '') <> '') " + + "and (isnull(m.targetmode, 0) = 0 or m.targetmode = 3 or m.targetmode=2) " + + "and isnull(m.useFlag, 1) = 1;"; + + final String delSqlTemplate = "delete from P_MessageToolLinkDllTab " + + "where LMenuid = '%s' and employeeid = '%s' and cardId = '%d';"; + + // 构建SQL语句 + StringJoiner sqlJoiner = new StringJoiner(" "); + + // 处理添加操作 + if (adds != null && !adds.isEmpty()) { + String[] addMenuIds = adds.split(","); + for (String menuId : addMenuIds) { + if (menuId == null || menuId.trim().isEmpty()) { + continue; + } + // 拼接添加SQL(假设user对象存在userId属性) + String addSql = String.format( + addSqlTemplate, + cardId, + getUser().UserId, + menuId, + menuId + ); + sqlJoiner.add(addSql); + } + } + + // 处理删除操作 + if (dels != null && !dels.isEmpty()) { + String[] delMenuIds = dels.split(","); + for (String menuId : delMenuIds) { + if (menuId == null || menuId.trim().isEmpty()) { + continue; + } + // 拼接删除SQL(假设user对象存在userId属性) + String delSql = String.format( + delSqlTemplate, + menuId, + getUser().UserId, + cardId + ); + sqlJoiner.add(delSql); + } + } + + // 若没有生成有效的SQL,直接返回 + if (sqlJoiner.length() == 0) { + return response; + } + + // 执行SQL并设置响应结果 + int affectedRows = jdbcTemplate.update(sqlJoiner.toString()); + response.setSuccess(true); + response.setData(affectedRows); + // 假设存在获取桌面常用工具列表的方法 + response.setOther(GetDeskTopCommonUse(cardId)); + + return response; + } + + /** + * 按文件ID删除附件(对应C# DelAttcFile(string fileId)) + * + * @param fileId 文件ID + * @return 统一响应结果(BaseResponse) + */ + public BaseResponse DelAttcFile(String fileId) { + BaseResponse response = new BaseResponse(); + String filePath = ""; // 初始为空,对应C#注释的dataImpl.GetAbsFilePath(fileId) + + // 1. 查询文件信息(获取webpath) + // C#:dataImpl.GetAttcFileInfo("", "", "", fileId.ToInt32()) + List> fileTb = DataImpl.GetAttcFileInfo("", "", "", ToInt32(fileId)); + + // 2. 提取文件路径(webpath) + if (fileTb != null && !fileTb.isEmpty()) { + Map firstRow = fileTb.get(0); + // 转换row["webpath"]为字符串(空值返回"",对齐C# row["webpath"] + "") + filePath = Objects.toString(firstRow.get("webpath"), ""); + } + log.debug(String.valueOf("filePath : " + filePath)); + // 3. 路径为空校验(返回错误信息) + if (filePath == null || filePath.isEmpty()) { + response.setMsg(String.format( + "未找到文件的路径信息,id:【%s】,请检查函数fun_fm_getAbsolutePath,确保返回正确的文件路径!", + fileId + )); + return response; + } + + // 4. 提取文件名(从webpath中截取,对应C# LastIndexOf("/")逻辑) + int lastIndex = filePath.lastIndexOf("/"); + String fileName = lastIndex > 0 ? filePath.substring(lastIndex + 1) : filePath; + + // 5. 删除文件(调用FileUtil,传入文件路径和附件基础路径) + BaseResponse fileDelResp = FileUtil.deleteFile(filePath, getAttcPath()); + response.setOther(fileDelResp.getOther()); // 同步文件删除的other信息 + + // 6. 数据库删除文件记录(对应C# dataImpl.DelAttcFileInfo) + DataImpl.DelAttcFileInfo("", "", "", "", fileId); + + // 7. 记录系统日志(对应C# SysLog) + String logContent = String.format( + "%s删除附件【%s】,附件id:%s", + getUser().UserName, fileName, fileId + ); + sysLog(logContent, "删除附件"); + + // 8. 设置成功响应 + response.setSuccess(true); + return response; + } + +// =========================== 第二个方法:按模块/ID/文件名删除附件 =========================== + + /** + * 按模块ID、业务ID、文件名等删除附件(对应C# DelAttcFile(string moduleId, ...)) + * + * @param moduleId 模块ID + * @param idValue 业务ID(如表单ID) + * @param specno 规格编号 + * @param folder 文件夹路径 + * @param fileName 文件名 + * @return 统一响应结果(BaseResponse) + */ + public BaseResponse DelAttcFile(String moduleId, String idValue, String specno, String folder, String fileName) throws + SQLException { + BaseResponse response = new BaseResponse(); + String dirTabId = ""; // 对应C#的dirTabId(文件夹关联表ID) + String mFolder; // 从数据库获取的文件夹路径 + + // 1. 查询附件文件夹路径(out dirTabId:Java用数组接收"输出参数") + // C#:dataImpl.GetAcFileFolder(moduleId, idValue, specno, out dirTabId) + String[] dirTabIdHolder = new String[1]; // Java无out,用数组存输出值 + mFolder = DataImpl.GetAcFileFolder(moduleId, idValue, specno, dirTabIdHolder); + dirTabId = dirTabIdHolder[0]; // 提取输出的dirTabId + + // 2. 处理文件夹路径(判断是否以"/"开头,对应C# folder.StartsWith("/")) + String fileFolder = (folder != null && folder.startsWith("/")) ? folder : mFolder; + + // 3. 获取附件操作权限信息(对应C# dataImpl.GetAttcOperInfo) + response = DataImpl.GetAttcOperInfo( + 3, // 操作类型:3(删除) + fileName, // 文件名 + dirTabId, // 文件夹关联表ID + 0, // 预留参数:0 + getUser().UserId, // 用户ID + getUser().UserName, // 用户名 + "", // 预留参数:空字符串 + 0, // 预留参数:0 + moduleId, // 模块ID + idValue, // 业务ID + 0 // 预留参数:0 + ); + + // 4. 权限校验通过(success=true),执行删除逻辑 + if (response.isSuccess()) { + // 4.1 拼接文件完整路径(对应C# Path.Combine) + String fullFilePath = Paths.get(fileFolder, fileName).toString(); + // 4.2 删除物理文件(调用FileUtil) + FileUtil.deleteFile(fullFilePath, getAttcPath()); + + // 4.3 数据库删除文件记录(对应C# dataImpl.DelAttcFileInfo) + int deleteResult = DataImpl.DelAttcFileInfo(dirTabId, moduleId, specno, fileName); + + // 4.4 记录系统日志 + String logContent = String.format( + "%s删除模块%s下%s的附件%s", + getUser().UserName, moduleId, idValue, fileName + ); + sysLog(logContent, "删除附件"); + + // 4.5 设置响应信息(判断是否删除成功) + String respMsg = deleteResult > 0 ? "操作成功" : "操作失败,未找到文件数据!"; + response.setMsg(respMsg); + response.setSuccess(true); // 确保success为true(权限校验已通过) + } + + return response; + } + + /** + * 获取模块统计数据 + * + * @param moduleIds 模块 ID 列表,用逗号分隔 + * @param _pams 参数 + * @param _where 查询条件 + * @return 基础响应对象 + * 2026.2.24 + */ +// @Override + public BaseResponse GetModuleCountData2(String moduleIds, String _pams, String _where) { + BaseResponse response = new BaseResponse(); + StringBuilder stringBuilder = new StringBuilder(); + List mIds = new ArrayList<>(); + // 分割模块 ID + String[] moduleIdArray = moduleIds.split(","); + for (String moduleId : moduleIdArray) { + ModuleBaseEntity module = GetModule(moduleId); +// out.println(moduleId + " " + module + " " + module.getNeedCount()); + if (module == null) continue; + if (!module.getNeedCount()) continue; + if (module instanceof BaseModule) { + // 处理基础模块 + String querySql = module.getCountSql(); + querySql = dealQuerySql(querySql, _pams, null, null, null, null, false, false, false); +// out.println("querySql " + querySql + " " + _pams + " " + _where); + if (_where != null && !_where.isEmpty()) { + Map whereDict = new HashMap<>(); + whereDict.put("$where", _where); + querySql = new SqlAnalyzer(querySql).InsertWhere(whereDict, true); + } + // 移除末尾分号并添加分号 + stringBuilder.append(querySql.replaceAll(";$", "")).append(";"); + } else { + // 处理其他类型模块 + List> dtVal = DataImpl.GetBillSource(moduleId, "", "2"); + if (!dtVal.isEmpty()) { + BillSourceModule sourceModule = new BillSourceModule(dtVal.get(0)); + sourceModule.setModuleId(moduleId); + String sourceSql = module.getCountSql(); + // 替换 #...# 格式的内容 + sourceSql = sourceSql.replaceAll("(?s)#.*?#", "1=1"); + sourceSql = PublicUtil.ReqSqlPms(null, null, sourceSql, SystemTypeEnums.PmType.sql, getUser()); + if (_where != null && !_where.isEmpty()) { + Map whereDict = new HashMap<>(); + whereDict.put("$where", _where); + sourceSql = new SqlAnalyzer(sourceSql).InsertWhere(whereDict, false, false, true); + } + // 移除末尾分号并添加分号 + stringBuilder.append(sourceSql.replaceAll(";$", "")).append(";"); + } + } + mIds.add(moduleId); + } + if (!stringBuilder.isEmpty()) { + // 执行 SQL 并获取数据集 +// out.println(stringBuilder.toString()); + + List>> dataSet = dbOperator.executeDataSet(stringBuilder.toString()); + // out.println("dataSet " + dataSet); + Map>> datas = new HashMap<>(); + for (int i = 0; i < dataSet.size(); i++) { + if (i < mIds.size()) { + datas.put(mIds.get(i), dataSet.get(i)); + } + } + response.setData(datas); + response.setSuccess(true); + + } + return response; + } + + public BaseResponse GetModuleCountData(String moduleIds, String _pams, String _where) { + BaseResponse response = new BaseResponse(); + StringBuilder stringBuilder = new StringBuilder(); + ArrayList mIds = new ArrayList(); + for (String moduleId : moduleIds.split(",")) { + String querysql = ""; + if (moduleId.startsWith("rightmenu_")) { + List> countDt = DataImpl.GetRightMenuRows("", 0, "", ToInt32(moduleId.split("_")[moduleId.split("_").length - 1])); + if (countDt != null && countDt.size() > 0) { + querysql = (String) countDt.get(0).getOrDefault("countSql", null); + } + } else { + List> countDt = DataImpl.GetCountModuleInfo(moduleId); + ModuleBaseEntity module = null; + if (countDt != null && countDt.size() > 0) { + module = new ModuleBaseEntity(countDt.get(0)); + } + if (module == null) continue; + if (!module.getNeedCount()) continue; + querysql = module.getCountSql(); + } + if (isNullOrEmpty(querysql)) continue; + querysql = DealQuerySql(querysql, _pams, null, null, null, null); + if (!isNullOrEmpty(_where)) { + Map whereDict = new HashMap<>(); + whereDict.put("$where", _where); + querysql = new SqlAnalyzer(querysql).InsertWhere(whereDict, true); + } + stringBuilder.append(TrimEnd(querysql, ';')).append(';'); + mIds.add(moduleId); + } + if (stringBuilder.length() > 0) { + List>> set = dbOperator.executeDataSet(stringBuilder.toString()); + Hashtable datas = new Hashtable(); + for (int i = 0; i < set.size(); i++) { + datas.put(mIds.get(i), set.get(i)); + } + response.setData(datas); + response.setSuccess(true); + } + return response; + } + + public String GetAttcPathByOAUrl(String url) { + List> dt = getDataImpl().GetSysdbGroup(0); + for (int i = 0; i < dt.size(); i++) { + int serverId = ((Number) dt.get(i).get("id")).intValue(); + StringBuilder errMsg = new StringBuilder(); + DbOperator _dbOperator = new SysUserImpl().getServerDbOper(serverId, errMsg); + if (_dbOperator != null) { + DataImpl _dImpl = new DataImpl(); + _dImpl.setDbOperator(_dbOperator); // 对应C#的对象初始化器 + + String oaurl = ""; + try { + oaurl = _dImpl.GetSystemOAUrl(); + } catch (Exception e) { + continue; + } + + if (oaurl.startsWith(url)) { + return _dImpl.GetSystemAttcPath(); + } + } + } + + return null; + } + + /** + * 完全等价转换C#的 GetModuleCfgs 方法 + * + * @param moduleId 模块ID字符串(英文逗号分隔) + * @param menuid 菜单ID + * @return 组装后的BaseModule对象 + */ + public BaseModule GetModuleCfgs(String moduleId, String menuid) { + BaseModule module = new BaseModule(); + // 1. 获取基础模块配置(Java 中 DataTable 一般用 List> 替代) + List> dts = DataImpl.GetBaseModuleCfgs(moduleId, menuid); + + List> tabs = new ArrayList<>(); + List> orderTabs = new ArrayList<>(); + + // 2. 遍历数据行,处理权限和参数 + for (Map row : dts) { + // 权限校验(对应 C#: !string.IsNullOrEmpty(util.CheckPurview(...))) + String menuIdStr = row.get("MenuId") == null ? "" : row.get("MenuId").toString(); + String purviewCheckResult = createControl.CheckPurview(getUser().PurviewStr, menuIdStr); + if (purviewCheckResult != null && !purviewCheckResult.isEmpty()) { + // 行数据转 Hashtable + Map tab = (row); + // 处理 xtype(对应 SystemMenu.ConvertToModuleName) + boolean[] isUrlArr = new boolean[1]; // Java 无 out 参数,用数组模拟 + String dllFileName = tab.get("dllfilename") == null ? "" : tab.get("dllfilename").toString(); + String xtype = SystemMenu.convertToModuleName(dllFileName, 1, isUrlArr); + boolean isUrl = isUrlArr[0]; + + tab.put("xtype", xtype); + tab.remove("urlparams"); // Java Hashtable 区分大小写,与 C# 保持一致 + + // 处理 pubbrower 和 .html 后缀的 URL 参数 + String urlParams = row.get("UrlParams") == null ? "" : row.get("UrlParams").toString(); + if (xtype.toLowerCase().contains("pubbrower") && urlParams.contains(".html")) { + Map queryPms = new HashMap<>(); + + // 解析 URL 参数(split 处理,与 C# 逻辑一致) + String[] urlParts = urlParams.split("\\?"); + String paramPart = urlParts.length > 1 ? urlParts[1] : ""; + String[] kvStrs = paramPart.split("&"); + + for (String kvStr : kvStrs) { + String[] kvs = kvStr.split("="); + // 过滤 username/password,且确保键值对完整 + if (kvs.length == 2 + && !kvs[0].equalsIgnoreCase("username") + && !kvs[0].equalsIgnoreCase("password")) { + queryPms.put(kvs[0].toLowerCase(), kvs[1]); + } + } + + // 处理 moduleid/dllcoid 和 xtype + if (!queryPms.isEmpty() && queryPms.containsKey("xtype")) { + String _moduleId = ""; + if (queryPms.containsKey("moduleid")) { + _moduleId = queryPms.get("moduleid"); + } else if (queryPms.containsKey("dllcoid")) { + _moduleId = queryPms.get("dllcoid"); + } + + if (_moduleId != null && !_moduleId.isEmpty()) { + tab.put("moduleid", _moduleId); + tab.put("xtype", queryPms.get("xtype")); + } + } + } + + tab.remove("dllfilename"); + tabs.add(tab); + } + } + + // 3. 按 moduleId 拆分筛选,生成有序的 orderTabs + if (moduleId != null && !moduleId.isEmpty()) { + String[] midArray = moduleId.split(","); + for (String mid : midArray) { + // 替代 C# 的 FirstOrDefault,查找第一个匹配的 tab + Map matchedTab = null; + for (Map tb : tabs) { + String tbModuleId = tb.get("moduleid") == null ? "" : tb.get("moduleid").toString(); + if (tbModuleId.equalsIgnoreCase(mid)) { + matchedTab = tb; + break; // 找到第一个匹配项立即退出 + } + } + if (matchedTab != null) { + orderTabs.add(matchedTab); + } + } + } + + // 4. 封装返回 + module.ModuleTabs = (orderTabs); + return module; + } + + /** + * 检查扫码是否重复(完整还原原C#逻辑) + * + * @param moduleId 明细模块编号 + * @param scanVal 扫码值 + * @param idValue 主表主键 + * @param repeat 当前扫码是否重复 + * @param dataIndex 数据索引 + * @return 检查结果响应对象 + */ + public BaseResponse CheckScanRepeat(String moduleId, String scanVal, String idValue, boolean repeat, + int dataIndex) throws SQLException { + // 初始化响应对象,默认成功 + BaseResponse response = new BaseResponse(); + response.setSuccess(true); + + String proName = "p_sysappscancheck"; + + // 检查存储过程是否存在 + boolean hasPro = DataImpl.IsExitPro(proName); + if (!hasPro) { + response.setData(1); + return response; + } + + // 获取存储过程参数(对应原dbOperator.GetStoreParams) + DbOperator.Parameter[] pmList = dbOperator.getStoreParams(proName); + + // 组装参数值数组:明细模块编号,扫码值,主表主键,用户id,当前扫码是否重复,数据索引 + Object[] vals = new Object[]{ + moduleId, + scanVal, + idValue, + getUser().getUserId(), // 对应user.UserId + repeat, + dataIndex + }; + + // 执行存储过程并获取响应 + response = DataImpl.excuteStore(proName, vals); + // 将other字段值赋值给data字段 + response.setData(response.getOther()); + + return response; + } + + /** + * 获取扫码数据(完整还原原C#逻辑) + * + * @param moduleId 明细模块编号 + * @param scanVal 扫码值 + * @param idValue 主表主键 + * @return 封装后的BaseResponse响应对象 + */ + public BaseResponse GetScanDatas(String moduleId, String scanVal, String idValue) throws SQLException { + // 初始化响应对象,默认成功 + BaseResponse response = new BaseResponse(); + response.setSuccess(true); + + String proName = "p_sysappscandata"; + + // 检查存储过程是否存在 + boolean hasPro = DataImpl.IsExitPro(proName); + if (!hasPro) { + return response; + } + + // 组装参数值数组:明细模块编号,扫码值,主表主键,用户id + Object[] vals = new Object[]{ + moduleId, + scanVal, + idValue, + getUser().getUserId() // 对应C#的user.UserId + }; + + // 执行存储过程 + response = DataImpl.excuteStore(proName, vals); + + // 将DataSet的第一个DataTable转换为HashMap列表(对应ToHashTable) + if (response.getData() != null) { + // Java中DataSet对应List>>,DataTable对应List> + List>> dataSet = (List>>) response.getData(); + if (dataSet != null && !dataSet.isEmpty()) { + // 取第一个DataTable转换(对应ds.Tables[0]) + List> dataTable = dataSet.get(0); + // 转换为HashMap列表(对应ToHashTable方法) + response.setData(toHashTable(dataTable)); + } + } + + return response; + } + + /** + * 获取APP打印数据(完整还原原C#逻辑) + * + * @param _recs 记录JSON字符串 + * @param moduleId 模块ID + * @param printType 打印类型(1=命令模式,其他=数据模式) + * @return 封装后的BaseResponse响应对象 + */ + public BaseResponse GetAppPrintData(String _recs, String moduleId, int printType) { + // 初始化响应对象,默认成功 + BaseResponse response = new BaseResponse(); + response.setSuccess(true); + + // 1. 处理JSON字符串:确保以[]开头,否则补全 + if (_recs != null && !_recs.trim().startsWith("[")) { + _recs = String.format("[%s]", _recs); + } + + // 2. 解析JSON为List(对应C#的JSON.Decode + ArrayList) + ArrayList> recs = (ArrayList>) JSON.Decode(_recs); + + // 3. 初始化打印数据列表(对应C#的List) + List> htList = new ArrayList<>(); + + // 4. 获取打印配置表(新版/老版分支) + List> printTable = DataImpl.getAppPrintRows(moduleId); + if (printTable != null && !printTable.isEmpty()) { + // 新版打印逻辑 + for (Map item : printTable) { + // 获取formKey字段值 + String formKey = Objects.toString(item.get("formKey"), ""); + + // 获取单份打印配置 + Map htTable = GetSinglePrint(formKey); + + // 设置打印属性 + htTable.put("pageTitle", Objects.toString(item.get("modname"), "")); + // printCount:取最大值(至少1) + int count = toInt32(get(item, "Count", 1).toString()); + htTable.put("printCount", Math.max(1, count)); + // 获取打印项 + htTable.put("items", DataImpl.getAppSinglePrintItems(formKey)); + + htList.add(htTable); + } + } else { + // 老版本打印逻辑 + Map htTable = GetOldSinglePrint(moduleId); + htTable.put("items", DataImpl.getAppOldSinglePrintItems(moduleId)); + htList.add(htTable); + } + + // 5. 根据打印类型组装响应数据 + if (printType == 1) { + // 打印类型1:组装命令列表 + List cmds = new ArrayList<>(); + int printVersion = (printTable != null && !printTable.isEmpty()) ? 2 : 1; + for (Map rec : recs) { + cmds.add(GetModulePrintCommand(rec, htList, moduleId, printVersion)); + } + response.setData(cmds); + } else { + // 打印类型非1:组装打印数据列表 + List retList = new ArrayList<>(); + for (Map printInfo : htList) { + // 将printInfo["items"](DataTable)转换为HashMap列表 + List> layoutTab = toHashTable( + (List>) printInfo.get("items"), + true, + false, false + ); + + List printItems = new ArrayList<>(); + for (Map rec : recs) { + List> recInfoList = new ArrayList<>(); + for (Map item : layoutTab) { + // 复制原有字段 + Map tab = new HashMap<>(item); + // 填充打印文本:rec[fieldname] + String fieldName = Objects.toString(tab.get("fieldname"), ""); + tab.put("printtext", Objects.toString(rec.get(fieldName), "")); + recInfoList.add(tab); + } + + // 组装单条打印数据 + Map printItem = new HashMap<>(); + printItem.put("pageTitle", printInfo.get("pageTitle")); + printItem.put("printCount", printInfo.get("printCount")); + printItem.put("printItems", recInfoList); + printItems.add(printItem); + } + + // 组装最终打印数据 + Map retItem = new HashMap<>(); + retItem.put("pageTitle", printInfo.get("pageTitle")); + retItem.put("printItems", printItems); + retList.add(retItem); + } + response.setData(retList); + } + + return response; + } + + /** + * 获取老版单份打印模板配置(对应原C# getOldSinglePrint) + * + * @param menuid 菜单ID + * @return 打印模板配置(HashMap) + */ + public Map GetOldSinglePrint(String menuid) { + // 1. 获取老版打印配置数据表 + List> table = DataImpl.getOldAppSinglePrint(menuid); + + // 2. 初始化打印配置,设置默认值 + Map htTable = new HashMap<>(); + htTable.put("pageWidth", 50); // 默认宽度 + htTable.put("pageHeight", 30); // 默认高度 + htTable.put("pageTitle", "默认打印模版"); // 老版默认标题 + + // 3. 数据表非空且有数据时,覆盖默认值 + if (table != null && !table.isEmpty()) { + // 获取首行数据(对应C# table.Rows[0]) + Map firstRow = table.get(0); + + // 赋值页面宽度(空值时保留默认值) + htTable.put("pageWidth", Objects.requireNonNullElse(firstRow.get("pageWidth"), 50)); + // 赋值页面高度 + htTable.put("pageHeight", Objects.requireNonNullElse(firstRow.get("pageHeight"), 30)); + // 赋值打印方向 + htTable.put("direction", firstRow.get("direction")); + } + + return htTable; + } + + /** + * 获取新版单份打印模板配置(对应原C# getSinglePrint) + * + * @param formKey 表单Key + * @return 打印模板配置(HashMap) + */ + private Map GetSinglePrint(String formKey) { + // 1. 获取新版打印配置数据表 + List> table = DataImpl.getAppSinglePrint(formKey); + + // 2. 初始化打印配置,设置默认值 + Map htTable = new HashMap<>(); + htTable.put("pageWidth", 50); // 默认宽度 + htTable.put("pageHeight", 30); // 默认高度 + htTable.put("pageTitle", ""); // 新版默认标题为空 + + // 3. 数据表非空且有数据时,覆盖默认值 + if (table != null && !table.isEmpty()) { + // 获取首行数据(对应C# table.Rows[0]) + Map firstRow = table.get(0); + + // 赋值页面宽度(空值时保留默认值) + htTable.put("pageWidth", Objects.requireNonNullElse(firstRow.get("pageWidth"), 50)); + // 赋值页面高度 + htTable.put("pageHeight", Objects.requireNonNullElse(firstRow.get("pageHeight"), 30)); + // 赋值打印方向 + htTable.put("direction", firstRow.get("direction")); + // 新版标题强制设为空(和原C#逻辑一致) + htTable.put("pageTitle", ""); + } + + return htTable; + } + + /** + * 生成打印指令(完整还原原C#逻辑) + * + * @param btn 打印按钮对象 + * @return 打印指令Base64字符串 / null(条件不满足)/ 打印数据(条件满足) + */ + private Object ToPrintCommand(SysPoPupMenuBtn btn) { + // 核心条件判断:dllname包含print.lsp(忽略大小写) 且 !WindowsDirver + boolean isPrintLsp = btn.dllname != null + && btn.dllname.toLowerCase().contains("print.lsp"); + if (!(isPrintLsp && !isWindowsDirver())) { + return null; + } + + // 条件满足时,调用GetAppPrintData并返回其data字段(原代码提前返回逻辑) + String recordJson = JSON.Encode(btn.Record); // 对应JSON.Encode + int printType = toInt32(btn.dllpar2); // 对应ToInt32 + BaseResponse response = GetAppPrintData(recordJson, btn.getModuleId(), printType); + return response.getData(); + } + + /** + * 生成模块打印指令(默认版本2,对应C#可选参数ver=2) + */ + private String GetModulePrintCommand + (Map rec, List> layouthashtab, String moduleId) { + return GetModulePrintCommand(rec, layouthashtab, moduleId, 2); + } + + /** + * 生成模块打印指令(完整逻辑,对应原C#方法) + * + * @param rec 打印记录数据 + * @param layouthashtab 打印布局哈希表列表 + * @param moduleId 模块ID + * @param ver 打印版本(2=新版,1=老版) + * @return 组装后的打印指令字符串 + */ + private String GetModulePrintCommand + (Map rec, List> layouthashtab, String moduleId, int ver) { + StringBuilder stringBuilder = new StringBuilder(); + try { + // 初始化打印布局表和配置表(对应C# DataTable) + List> layoutTab = null; + List> printTable = DataImpl.getAppPrintRows(moduleId); + + if (ver == 2) { // 新版打印逻辑 + for (Map item : layouthashtab) { + // 获取打印项配置表(DataTable → List) + layoutTab = (List>) item.get("items"); + + // 安全转换页面宽高(默认50/30) + int pageWidth = parseIntSafely(item.get("pageWidth"), 50); + int pageHeight = parseIntSafely(item.get("pageHeight"), 30); + // 获取页面标题(空值转为空字符串) + String pageTitle = Objects.toString(item.get("pageTitle"), ""); + + // 添加打印页开始命令 + PrintHelper.getPrintPageStartCommandStr(stringBuilder, 0, 0, pageWidth, pageHeight, 0); + + // 遍历布局行,组装字段打印指令 + if (layoutTab != null) { + for (Map layoutrow : layoutTab) { + // 安全获取字段值(先判断是否存在,再取值) + String fieldname = getTableColumnValue(layoutrow, "fieldname"); + String text = getTableColumnValue(layoutrow, "text"); + String fieldType = getTableColumnValue(layoutrow, "fieldType"); + + // 安全转换布局参数(默认0) + int left = parseIntSafely(getTableColumnValue(layoutrow, "left"), 0); + int top = parseIntSafely(getTableColumnValue(layoutrow, "top"), 0); + int width = parseIntSafely(getTableColumnValue(layoutrow, "width"), 0); + int height = parseIntSafely(getTableColumnValue(layoutrow, "height"), 0); + int fontSize = parseIntSafely(getTableColumnValue(layoutrow, "fontSize"), 0); + + // 获取打印值(rec中存在fieldname则取值,否则为空) + String value = rec.containsKey(fieldname) ? Objects.toString(rec.get(fieldname), "") : ""; + + // 添加字段打印指令 + PrintHelper.getPrintPageStringCommandStr(stringBuilder, value, left, top, fontSize, 0); + } + } + + // 添加打印页结束和打印命令 + PrintHelper.getPrintPageEndCommandStr(stringBuilder); + PrintHelper.getPrintPagePrintCommandStr(stringBuilder); + } + } else { // 老版打印逻辑 + if (layouthashtab != null && !layouthashtab.isEmpty()) { + Map firstItem = layouthashtab.get(0); + // 获取打印项配置表 + layoutTab = (List>) firstItem.get("items"); + + // 安全转换页面宽高(默认50/30) + int pageWidth = parseIntSafely(firstItem.get("pageWidth"), 50); + int pageHeight = parseIntSafely(firstItem.get("pageHeight"), 30); + // 获取页面标题 + String pageTitle = Objects.toString(firstItem.get("pageTitle"), ""); + + // 添加打印页开始命令 + PrintHelper.getPrintPageStartCommandStr(stringBuilder, 0, 0, pageWidth, pageHeight, 0); + + // 遍历布局行,组装字段打印指令 + if (layoutTab != null) { + for (Map layoutrow : layoutTab) { + // 安全获取字段值 + String fieldname = getTableColumnValue(layoutrow, "fieldname"); + String text = getTableColumnValue(layoutrow, "text"); + String fieldType = getTableColumnValue(layoutrow, "fieldType"); + + // 安全转换布局参数 + int left = parseIntSafely(getTableColumnValue(layoutrow, "left"), 0); + int top = parseIntSafely(getTableColumnValue(layoutrow, "top"), 0); + int width = parseIntSafely(getTableColumnValue(layoutrow, "width"), 0); + int height = parseIntSafely(getTableColumnValue(layoutrow, "height"), 0); + int fontSize = parseIntSafely(getTableColumnValue(layoutrow, "fontSize"), 0); + + // 获取打印值 + String value = rec.containsKey(fieldname) ? Objects.toString(rec.get(fieldname), "") : ""; + + // 添加字段打印指令 + PrintHelper.getPrintPageStringCommandStr(stringBuilder, value, left, top, fontSize, 0); + } + } + + // 添加打印页结束和打印命令 + PrintHelper.getPrintPageEndCommandStr(stringBuilder); + PrintHelper.getPrintPagePrintCommandStr(stringBuilder); + } + } + } catch (Exception e) { + // 原代码为空catch,保留该逻辑(如需排查问题可添加日志) + // e.printStackTrace(); + } + return stringBuilder.toString(); + } + + /** + * 获取ESC指令打印命令(完整还原原C#逻辑) + * + * @param dataSource 打印数据源(List对应C# DataTable) + * @param allHeight 总高度(数组封装实现ref传递) + * @param nowHeight 当前高度(数组封装实现ref传递) + * @return ESC指令字节数组列表 + */ + private List GetScanEscCommand(List> dataSource, int[] allHeight, + int[] nowHeight) { + List bytes = new ArrayList<>(); + try { + // 列名列表(对应C# dataSource.Columns) + List columnNames = getColumnNames(dataSource); + int index = 0; + + // 遍历数据源行(对应C# dataSource.Rows) + for (Map row : dataSource) { + // 遍历列(对应C# dataSource.Columns) + for (String colName : columnNames) { + String key = colName; + // 获取单元格值(空值转为空字符串) + String value = Objects.toString(row.get(colName), ""); + + // 初始化参数数组(7个元素,对应原pramars) + String[] pramars = new String[7]; + // 拆分列名参数(列名_字体大小_加粗_宽度_对齐_换行_高度) + String[] sourceParmars = key.split("_"); + // 填充参数(不足7个则留空) + for (int i = 0; i < pramars.length; i++) { + if (i < sourceParmars.length) { + pramars[i] = sourceParmars[i]; + } else { + pramars[i] = ""; + } + } + + // 解析参数(安全转换,默认0) + int fontSizeModel = parseIntSafely(pramars[1], 0); + int boldMode = parseIntSafely(pramars[2], 0); + int positionx = parseIntSafely(pramars[3], 0); + int alginMode = parseIntSafely(pramars[4], 0); + int nextLine = parseIntSafely(pramars[5], 0); + int height = parseIntSafely(pramars[6], 0); + + // 1. 设置字体大小 + if (!isNullOrEmpty(pramars[1])) { + switch (fontSizeModel) { + case 1: + bytes.add(EscPrintHelper.selectCharacterSize(9)); + break; + case 2: + bytes.add(EscPrintHelper.selectCharacterSize(32)); + break; + case 3: + bytes.add(EscPrintHelper.selectCharacterSize(18)); + break; + case 0: + default: + bytes.add(EscPrintHelper.selectCharacterSize(0)); + break; + } + } + + // 2. 设置加粗模式 + if (!isNullOrEmpty(pramars[2])) { + bytes.add(EscPrintHelper.setBold(boldMode)); + } + + // 3. 处理字符宽度补空格 + if (!isNullOrEmpty(pramars[3])) { + if (fontSizeModel == 0) { + fontSizeModel = 9; // 默认字体大小 + } + // 计算需要补的空格数 + int charTotalWidth = value.length() * fontSizeModel; + if (charTotalWidth < positionx) { + int padRightCount = (positionx - charTotalWidth) / fontSizeModel; + // 补空格(对应C# PadRight) + value = padRight(value, padRightCount + value.length(), ' '); + } + } + + // 4. 设置对齐模式 + if (!isNullOrEmpty(pramars[4])) { + bytes.add(EscPrintHelper.setAlign(alginMode)); + } + + // 5. 添加打印值 + if (nowHeight[0] <= allHeight[0] || allHeight[0] == 0) { + bytes.add(EscPrintHelper.addValueStr(value)); + } + + // 6. 处理换行(原注释逻辑保留) + boolean isLastColumn = (index + 1) == (columnNames.size() * dataSource.size()); + if ((!isNullOrEmpty(pramars[5]) && nextLine > 0 && !isNullOrEmpty(pramars[6]) && height > 0) || isLastColumn) { + int line = allHeight[0] - nowHeight[0]; + if (line >= 0 && nextLine > 0) { + bytes.add(EscPrintHelper.printAndFeedLine(line + nextLine)); + } + // 更新高度参数(ref传递) + allHeight[0] = height; + nowHeight[0] = 1; + } else if (!isNullOrEmpty(pramars[5])) { + if (nowHeight[0] <= allHeight[0] || allHeight[0] == 0) { + bytes.add(EscPrintHelper.printAndFeedLine(nextLine)); + } + if (allHeight[0] > 0) { + nowHeight[0] += 1; + } + } + + index++; + } + } + } catch (Exception e) { + // 原代码为空catch,保留该逻辑(如需排查问题可添加日志) + // e.printStackTrace(); + } + return bytes; + } + + /** + * 获取树形方案(对应原GetTreeScheme) + * + * @param moduleId 模块ID + * @param _record 记录JSON字符串 + * @return 树形方案数据列表(Map列表) + */ + public List> GetTreeScheme(String moduleId, String _record) { + // 初始化左侧记录哈希表 + Map leftRecord = new HashMap<>(); + + // 解析JSON字符串(非空时) + if (_record != null && !_record.trim().isEmpty()) { + leftRecord = (Map) JSON.Decode(_record); + } + + // 获取树形方案SQL模板并处理 + String sql = DataImpl.getTreeSchemeSql(moduleId); + // 处理SQL模板 + 格式化替换speciesno参数 + String processedSql = dealQuerySql(sql, leftRecord, leftRecord, null, null, null); + // 获取speciesno值(空值处理) + String speciesno = Objects.toString(leftRecord.get("speciesno"), ""); + sql = String.format(processedSql, speciesno); + + // 执行SQL并转换为HashMap列表(对应ToHashTable) + List> dataTable = jdbcTemplate.queryForList(sql); + return toHashTable(dataTable); + } + + /** + * 获取方案规则(对应原GetSchemeRule) + * + * @param groupId 分组ID + * @return 封装后的BaseResponse响应对象 + */ + public BaseResponse GetSchemeRule(int groupId) { + BaseResponse response = new BaseResponse(); + + // 获取动态方案字段和规则 + List> fields = DataImpl.getDynamicSchemeFields(groupId); + List> rules = DataImpl.getDynamicSchemeRule(groupId); + + // 生成控件数据并设置响应基础信息 + response.setData(createControl.createControl(fields, null, false)); + response.setSuccess(true); + + // 规则非空时,按fxname分组 + if (rules != null && !rules.isEmpty()) { + // 按fxname分组(对应C# GroupBy + ToDictionary) + Map>> groupRules = rules.stream() + .collect(Collectors.groupingBy( + // 分组键:fxname字段值(空值转为空字符串) + row -> Objects.toString(row.get("fxname"), ""), + // 分组值:当前分组的所有行(对应CopyToDataTable) + Collectors.toList() + )); + + // 设置响应的other字段 + response.setOther(groupRules); + } + + return response; + } + + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + /** + * 移动附件文件信息 + * + * @param fInfos 附件信息哈希表 + * @return 操作响应结果 + */ + public BaseResponse MoveAttcFileInfo(Map fInfos) throws SQLException { + BaseResponse response = new BaseResponse(); + Connection conn = null; + PreparedStatement pstmt = null; + ResultSet rs = null; + + try { + // 1. 解析参数 + String moduleId = Objects.toString(fInfos.get("menucode"), ""); + String idValue = Objects.toString(fInfos.get("svalue"), ""); + String tidValue = Objects.toString(fInfos.get("tvalue"), ""); + String filename = Objects.toString(fInfos.get("filename"), ""); + String _filepath = Objects.toString(fInfos.get("filepath"), ""); + String _newdir = Objects.toString(fInfos.get("newdir"), ""); + String newname = isNullOrEmpty(Objects.toString(fInfos.get("newname"), "")) + ? filename : Objects.toString(fInfos.get("newname"), ""); + String _ver = Objects.toString(fInfos.get("ver"), ""); + String specno = Objects.toString(fInfos.get("specno"), ""); + + // 获取父目录ID + String dirTabId = DataImpl.GetAttcParentId(moduleId, idValue); + // 类型ID转换(最小值为1) + int typeid = Math.max(toInt32(Objects.toString(fInfos.get("typeid"), "0"), 0), 1); + + // 2. 查询文件信息 + List> fileTbVal = DataImpl.GetAttcFileInfo(dirTabId, moduleId, filename, 0); + if (fileTbVal == null || fileTbVal.isEmpty()) { + response.setMsg("未找到文件!"); + if (!isNullOrEmpty(_filepath) && !isNullOrEmpty(_newdir)) { + response = FileUtil.moveTo(_filepath, _newdir, newname, getAttcPath(), false); + } + return response; + } + + // 3. 提取文件信息 + Map fileRow = fileTbVal.get(0); + String fileId = Objects.toString(fileRow.get("fileid"), ""); + String filePath = URLDecoder.decode(Objects.toString(fileRow.get("webpath"), ""), "UTF-8"); + + // 4. 获取新目录路径 + _newdir = DataImpl.GetAcFileFolder(moduleId, tidValue, specno, new String[]{dirTabId}); + + // 处理目录路径格式 + if (!_newdir.trim().replaceFirst("^/", "").toLowerCase() + .startsWith(WebConfigUtil_web.fileVPath.toLowerCase())) { + _newdir = "/" + WebConfigUtil_web.fileVPath + "/" + _newdir.trim().replaceFirst("^/", ""); + } + + // 5. 检查存储过程是否存在并执行 + String proName = "BOM_TDPR"; + if (DataImpl.IsExitPro(proName)) { + conn = jdbcTemplate.getDataSource().getConnection(); // 获取数据库连接 + pstmt = conn.prepareCall("{call " + proName + "(?,?,?,?)}"); + + // 设置存储过程参数 + pstmt.setString(1, tidValue); + pstmt.setString(2, "." + FileUtil.getFileExtension(filename)); + pstmt.setInt(3, typeid); + pstmt.setString(4, _ver); + + // 执行存储过程 + rs = pstmt.executeQuery(); + Map retInfos = new HashMap<>(); + + if (rs.next()) { + // 将ResultSet转换为HashMap + ResultSetMetaData metaData = rs.getMetaData(); + int columnCount = metaData.getColumnCount(); + for (int i = 1; i <= columnCount; i++) { + retInfos.put(metaData.getColumnName(i).toLowerCase(), rs.getObject(i)); + } + + // 处理文件名和版本 + String ver = Objects.toString(retInfos.get("v"), ""); + String newFilename = Objects.toString(retInfos.get("p"), "") + .replace("\\", "/"); + + // 调试日志 + sysLog(String.format("生成文件版本%s,%s,%s,%s,%s,%s,%s", + JSON.Encode(retInfos), filename, typeid, tidValue, + FileUtil.getFileExtension(filename), JSON.Encode(fInfos), + Request("typeid", "")), "info"); + + // 移动文件 + FileUtil.moveTo(filePath, _newdir, newFilename, getAttcPath(), false); + + // 构建新的Web路径 + String[] newFileWebPaths = (_newdir + "/" + newFilename).split("/"); + StringBuilder newFileWebPath = new StringBuilder(); + for (String pathSegment : newFileWebPaths) { + if (isNullOrEmpty(pathSegment)) continue; + newFileWebPath.append("/") + .append(FileUtil.urlEncode(pathSegment, false)); + } + + // 构建更新参数 + Map attcInfos = new HashMap<>(); + attcInfos.put("fileid", fileId); + attcInfos.put("vname", newFilename); + attcInfos.put("parentid", dirTabId); + attcInfos.put("fileno", ver); + attcInfos.put("creator", getUser().getUserId()); + attcInfos.put("createtime", DATE_FORMAT.format(new Date())); + attcInfos.put("webpath", newFileWebPath.toString()); + + // 更新数据库 + response = AddOrUpdTable(Collections.singletonList(attcInfos), + "P_fm_FileTab", "fileid", false, null); + + // 记录系统日志 + sysLog(String.format("%s在模块%s记录%s上传附件%s", + getUser().getUserName(), moduleId, idValue, newFilename), "上传附件"); + } + } else { + // 存储过程不存在时的处理逻辑 + FileUtil.moveTo(filePath, _newdir, newname, getAttcPath(), false); + + // 构建新的Web路径 + String[] newFileWebPaths = (_newdir + "/" + newname).split("/"); + StringBuilder newFileWebPath = new StringBuilder(); + for (String pathSegment : newFileWebPaths) { + if (isNullOrEmpty(pathSegment)) continue; + newFileWebPath.append("/") + .append(FileUtil.urlEncode(pathSegment, false)); + } + + // 构建更新参数 + Map attcInfos = new HashMap<>(); + attcInfos.put("fileid", fileId); + attcInfos.put("sname", newname); + attcInfos.put("vname", newname); + attcInfos.put("parentid", dirTabId); + attcInfos.put("creator", getUser().getUserId()); + attcInfos.put("createtime", DATE_FORMAT.format(new Date())); + attcInfos.put("webpath", newFileWebPath.toString()); + + // 更新数据库 + response = AddOrUpdTable(Collections.singletonList(attcInfos), + "P_fm_FileTab", "fileid", false, null); + } + } catch (Exception e) { + response.setSuccess(false); + response.setMsg("移动附件失败:" + e.getMessage()); + // 记录异常日志 + log.error("Exception caught", e); + } finally { + // 关闭JDBC资源 + if (rs != null) { + rs.close(); + } + if (pstmt != null) { + pstmt.close(); + } + if (conn != null) { + conn.close(); + } + } + + return response; + } + + public Object GetAuditStepAndHis(ModuleBaseEntity module, String moduleId, String idValue, int atStep, + boolean isBase, Integer billType) { + // 1. 查询审批历史(对应C#: DataTable his = dataImpl.GetBaseFlowStep(...)) + List> his = DataImpl.GetBaseFlowStep(idValue, isBase); + + // 2. 查询审批步骤(对应C#: DataTable steps = dataImpl.GetHisAuditStepInfos(...)) + List> steps = DataImpl.GetHisAuditStepInfos(moduleId, atStep, billType, isBase); + + // 3. 转换steps为HashMap列表(对应C#的ToHashTable) + List> tabList = toHashTable(steps, + null, null, null, true, true, false, + (row, name, val) -> { + // 处理zfoper/operatorname字段 + if ("zfoper".equals(name) || "operatorname".equals(name)) { + return DataImpl.GetSqlUser( + val == null ? "" : val.toString(), + getUser().getUserId(), + getUser().getUserName(), + module.Updrow + ); + } + // 处理autostep字段 + if ("autostep".equals(name)) { + String condtion = row.get("autostepcond") == null ? "" : row.get("autostepcond").toString(); + if (!condtion.isEmpty() && toBoolean(createControl.evalCond(condtion, module.Updrow, null))) { + return 1; + } + } + return val; + } + ); + + // 4. 初始化新列表(对应C#: IList newList = new List();) + List> newList = new ArrayList<>(); + + // 5. 遍历处理每个步骤(对应C#的foreach循环) + for (Map tab : tabList) { + // 处理autostep字段:设置opeadvice为"将自动通过" + if (tab.containsKey("autostep") && toBoolean(tab.get("autostep"))) { + tab.put("opeadvice", "将自动通过"); + } + + // 获取stepcode并转换为int(对应C#: int tbStepCode = tab["stepcode"].ToInt32();) + int tbStepCode = toInt32(tab.get("stepcode").toString()); + + // 移除不需要的字段(对应C#: tab.Remove(...)) + tab.remove("autostep"); + tab.remove("autostepcond"); + + // 处理抄送逻辑(zfoper) + String zfoper = tab.get("zfoper") == null ? "" : tab.get("zfoper").toString(); + if (!zfoper.isEmpty()) { + // 构建抄送条目(对应C#的new Hashtable() { ... }) + HashMap copyItem = new HashMap<>(); + copyItem.put("stepname", "抄送"); + copyItem.put("opeadvice", "将抄送" + zfoper.length() + "人"); + copyItem.put("operatorname", zfoper); + // 处理applytime:atStep != 999&&tbStepCode>=atStep?"将抄送":"抄送" + copyItem.put("applytime", (atStep != 999 && tbStepCode >= atStep) ? "将抄送" : "抄送"); + copyItem.put("sta", 'z'); + copyItem.put("zfopers", DataImpl.GetUserBmps(zfoper)); + + tab.put("zfoper", ""); // 清空原zfoper + newList.add(copyItem); // 添加抄送条目 + } + + // atStep != 999时添加当前步骤到新列表 + if (atStep != 999) { + newList.add(tab); + } + } + + // 6. 拼接newList和his的结果(对应C#: newList.Concat(his.ToHashTable())) + List> hisList = toHashTable(his); + List> finalResult = new ArrayList<>(); + finalResult.addAll(newList); + finalResult.addAll(hisList); + + return finalResult; + } + + /** + * 将手机端以前的卡片配置,转换为新的web用的配置 + * + * @param dtVal 原始配置数据(Java 中 DataTable 常用 List> 替代) + * @return 转换后的 Web 端配置结构 + */ + private Object ConvertOldMobileCard(List> dtVal) { + // 1. 初始化卡片列表(对应 C# 的 List) + List cards = new ArrayList<>(); + + // 2. 遍历行数据(对应 C# 的 for 循环) + int rowCount = dtVal.size(); + for (int i = 0; i < rowCount; i++) { + Map row = dtVal.get(i); + + // 处理 color 字段(对应 C# 的 dtVal.Rows[i].Get("color") + "") + String color = row.get("color") == null ? "" : row.get("color").toString(); + + // 条件判断:color 为空或等于 "0" 时赋值默认颜色 + if (color.isEmpty() || "0".equals(color)) { + switch (i) { + case 0: + color = "#2b2b2b"; + break; + case 1: + color = "#efb463"; + break; + default: + color = ""; + break; + } + } + + // 3. 创建 MobileCard 对象并赋值(对应 C# 的对象初始化器) + MobileCard mobileCard = new MobileCard(); + mobileCard.setAlign(1); + mobileCard.setBgColor(""); + mobileCard.setBold(i == 0); // 第0行加粗 + mobileCard.setContent(row.get("field") == null ? "" : row.get("field").toString()); + mobileCard.setDType(0); + mobileCard.setDbColor(""); + mobileCard.setDfColor(""); + mobileCard.setFColor(color); // 字体颜色 + mobileCard.setFStrikeLine(false); + mobileCard.setFitalic(false); + mobileCard.setFontFamily("宋体"); + // 字体大小:第0行20,第1行12,其他行取size字段的整数值 + mobileCard.setFontSize(i == 0 ? 20 : (i == 1 ? 12 : Integer.parseInt(row.get("size").toString()))); + mobileCard.setRightAlign(false); + // 行高:第0行30,第1行15,其他行20 + mobileCard.setRowHeight(i == 0 ? 30 : (i == 1 ? 15 : 20)); + mobileCard.setRowId(i); + mobileCard.setSplitLine(i == 1); // 第1行显示分割线 + + // 4. 添加到卡片列表 + cards.add(mobileCard); + } + + // 5. 无卡片数据时返回 null + if (cards.isEmpty()) { + return null; + } + + // 6. 构建最终返回结构(对应 C# 的 ArrayList + 匿名对象) + // Java 无匿名对象,用 Map 模拟 + Map groupObj = new java.util.HashMap<>(); + groupObj.put("name", "分组"); + groupObj.put("showText", false); + groupObj.put("mxId", 0); + groupObj.put("items", cards); + + ArrayList result = new ArrayList<>(); + result.add(groupObj); + + return result; + } +} + + + + diff --git a/WebErp/weberp/src/main/java/org/example/Impl/OptBaseImpl.java b/WebErp/weberp/src/main/java/org/example/Impl/OptBaseImpl.java new file mode 100644 index 0000000..da4d055 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Impl/OptBaseImpl.java @@ -0,0 +1,217 @@ +package org.example.Impl; + +import org.example.Entity.BaseResponse.BaseResponse; +import org.example.Entity.System.LoginUserInfo; +import org.example.Impl.Sql.factory.AllInOneSqlFactory; +import org.example.ModuleApi.ModuleAjaxApi.mapper.CRMapper; +import org.example.ModuleApi.ModuleAjaxApi.mapper.DMCrmMapper; +import org.example.Utils.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.io.UnsupportedEncodingException; +import java.util.Objects; + +/** + * 功能描述:OptBaseImpl 操作类的父类 + */ +public class OptBaseImpl extends BaseImpl { + + public OptBaseImpl(JdbcTemplate dbOperator, String databaseType, + AllInOneSqlFactory allInOneSqlFactory, CRMapper crmapper, DMCrmMapper DMCrmMapper) { + this.jdbcTemplate = dbOperator; + this.databaseType = databaseType; + this.allInOneSqlFactory = allInOneSqlFactory; + this.crmapper = crmapper; + this.DMCrmMapper = DMCrmMapper; + } + + protected static final Logger log = LoggerFactory.getLogger(OptBaseImpl.class); + + private IPublicUtil util; + + public OptBaseImpl() { + + } + + public synchronized IPublicUtil getUtil() { + if (util == null) { + util = new IPublicUtil(jdbcTemplate); + util.setReqHandler(getReqHandler()); + util.setDbOperator(getDbOperator()); + } + return util; + } + + + private DataImpl dataImpl; + + protected synchronized DataImpl getDataImpl() { + if (dataImpl == null) { +// dataImpl = new DataImpl(getDbOperator()); + dataImpl = new DataImpl(jdbcTemplate, databaseType, allInOneSqlFactory, crmapper, DMCrmMapper); + } + return dataImpl; + } + + protected void setDataImpl(DataImpl dataImpl) { + this.dataImpl = dataImpl; + } + + private DbOperator oldDbOperator; + + public DbOperator getOldDbOperator() { + return oldDbOperator; + } + + public void setOldDbOperator(DbOperator oldDbOperator) { + this.oldDbOperator = oldDbOperator; + } + + @Autowired + private DbOperator dbOperator; + + @Override + public DbOperator getDbOperator() { + if (dbOperator != null) { + return dbOperator; + } + + DbOperator baseDbOper = super.getDbOperator(); + int serverId = getServerId(); + LoginUserInfo currentUser = getUser(); + + if (serverId > 0 && (currentUser == null || currentUser.ServerId != serverId)) { + oldDbOperator = baseDbOper; + SysUserImpl userImpl = new SysUserImpl(SpringContextHolder.getDataImpl()); + userImpl.setDbOperator(baseDbOper); + StringBuilder errMsg = new StringBuilder(); + DbOperator serverDbOper = userImpl.getServerDbOper(serverId, errMsg); + dbOperator = Objects.requireNonNullElse(serverDbOper, baseDbOper); + } else { + dbOperator = baseDbOper; + } + + return dbOperator; + } + + public void setDbOperator(DbOperator dbOperator) { + this.dbOperator = dbOperator; + } + + private Integer serverId; + + public int getServerId() { + if (serverId == null) { + serverId = (getCtx() == null) ? 0 : NativeExtensionUtils.ToInt32(Request("serverId", "0")); + } + return serverId; + } + + public void setServerId(int serverId) { + this.serverId = serverId; + } + + private SysUserImpl userImpl; + + @Override + public LoginUserInfo getUser() { + + LoginUserInfo user = super.getUser(); + if (user != null && !"0".equals(user.UserId) && user.ConnectionString == null) { + String token = user.Token; + SysUserImpl userImpl = new SysUserImpl(SpringContextHolder.getDataImpl()); + + userImpl.setJdbcTemplate(jdbcTemplate); + DbOperator dbOper = new DbOperator(jdbcTemplate); + userImpl.setDbOperator(dbOper); + if (user.ServerId > 0) { + StringBuilder errMsg = new StringBuilder(); + dbOper = userImpl.ChangeServer(user.ServerId, errMsg); + } else { + dbOper = new DbOperator(jdbcTemplate); + } + + if (dbOper != null) { + BaseResponse res = new BaseResponse(); + user = userImpl.OnLoginSuccess( + null, user, user.SeriesId, user.ServerId, + user.UserId, dbOper.getConnectionString(), res, true + ); + user.Token = (token); + user.LoginOs = (getOsModel()); + user.ConnectionString = (dbOper.getConnectionString()); + } + + super.setUser(user); + JwtHelp.setUserCache(user, isWindowsDirver() ? 7200 : 2592000); + setSessionVal(getUserSessionName(), user); + } + return user; + } + + @Override + public void setUser(LoginUserInfo user) { + super.setUser(user); + } + + private String moduleCode; + + protected String getModuleCode() { + if (moduleCode == null && getCtx() != null) { + moduleCode = Request("ModuleId", Request("menucode", "")); + } + return moduleCode; + } + + protected void setModuleCode(String moduleCode) { + this.moduleCode = moduleCode; + } + + private String menuId; + + protected String getMenuId() { + if (menuId == null && getCtx() != null) { + menuId = Request("MenuId", ""); + } + return menuId; + } + + protected String getOAUrl() { + return getDataImpl().GetSystemOAUrl(); + } + + private boolean isUseDbAttc() { + return NativeExtensionUtils.toBoolean(WebConfigUtil_web.get("useDbAttc", "")); + } + + @Override + public String getAttcPath() { + if (!isUseDbAttc() || getDataImpl().isDefaultServer()) { + return WebConfigUtil_web.getFilePath(); + } + return getDataImpl().GetSystemAttcPath(); + } + + @Override + public String getAppDomain() throws UnsupportedEncodingException { + if (!getDataImpl().isDefaultServer() && isUseDbAttc()) { + return getOAUrl(); + } + return super.getAppDomain(); + } + + @Override + public void sysLog(String content, String type) { + log.info(content); + try { +// SystemLog.setDbOperator(new DbOperator(jdbcTemplate)); +// SystemLog.info(content, getUser(), getLgType(), null, null, getModuleCode(), type); + } catch (Exception ex) { + log.error("日志写入错误: " + ex.getMessage(), ex); + } + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Impl/PushImpl.java b/WebErp/weberp/src/main/java/org/example/Impl/PushImpl.java new file mode 100644 index 0000000..3aa8ac1 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Impl/PushImpl.java @@ -0,0 +1,83 @@ +package org.example.Impl; + + +import org.example.Utils.PushHelper; +import org.example.Utils.ResourceExecutors; +import org.example.Utils.WebConfigUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Map; +import java.util.function.BooleanSupplier; + +public class PushImpl extends OptBaseImpl { + private static final Logger log = LoggerFactory.getLogger(PushImpl.class); + + public void basePush(String primaryValue, String stepCode, String menuId, BooleanSupplier exec) { + String beforeMenuName = ""; + String beforeUsers = ""; + int beforeId = 0; + + try { + if (WebConfigUtil.isPushmsg()) { + String sql = String.format( + "select operators, ToolsName as menuname, '1' + CAST(b.dllid as varchar(10)) as id " + + "from dbo.P_baseflowOper a, dbo.P_systemdlltab b " + + "where a.modid = b.DllCoid and keyvalue = '%s' and stepcode = '%s' and a.stepover = 0", + primaryValue, stepCode + ); + + List> dtTable = jdbcTemplate.queryForList(sql); + if (!dtTable.isEmpty()) { + Map row = dtTable.get(0); + beforeMenuName = row.get("menuname") != null ? row.get("menuname").toString() : ""; + beforeUsers = row.get("operators") != null ? row.get("operators").toString() : ""; + beforeId = row.get("id") != null ? Integer.parseInt(row.get("id").toString()) : 0; + } + } + } catch (Exception e) { + log.error("基础信息推送检测失败", e); + } + + if (exec.getAsBoolean() && WebConfigUtil.isPushmsg()) { + PushHelper push = new PushHelper(primaryValue, stepCode, getModuleCode(), + beforeUsers, beforeMenuName, beforeId); + ResourceExecutors.submitPush(push::push_base_oper_message); + } + } + + + public void billPush(String billId, String stepCode, String menuId, BooleanSupplier exec) { + String beforeMenuName = ""; + String beforeUsers = ""; + int beforeId = 0; + + try { + if (WebConfigUtil.isPushmsg()) { + String sql = String.format( + "select operators, typeName as menuname, '2' + CAST(b.id as varchar(10)) as id " + + "from dbo.wms_billflowOper a, dbo.p_systembilltype b " + + "where a.modid = b.typeCode and keyvalue = '%s' and stepcode = '%s' and a.stepover = 0", + billId, stepCode + ); + + List> dtTable = jdbcTemplate.queryForList(sql); + if (!dtTable.isEmpty()) { + Map row = dtTable.get(0); + beforeMenuName = row.get("menuname") != null ? row.get("menuname").toString() : ""; + beforeUsers = row.get("operators") != null ? row.get("operators").toString() : ""; + beforeId = row.get("id") != null ? Integer.parseInt(row.get("id").toString()) : 0; + } + } + } catch (Exception e) { + log.error("读取ios清除信息失败", e); + } + + if (exec.getAsBoolean() && WebConfigUtil.isPushmsg()) { + PushHelper push = new PushHelper(billId, stepCode, menuId, + beforeUsers, beforeMenuName, beforeId); + ResourceExecutors.submitPush(push::push_bill_oper_message); + } + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Impl/SMSImpl.java b/WebErp/weberp/src/main/java/org/example/Impl/SMSImpl.java new file mode 100644 index 0000000..abf9036 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Impl/SMSImpl.java @@ -0,0 +1,430 @@ +package org.example.Impl; + +import jakarta.annotation.PostConstruct; +import jakarta.servlet.http.HttpSession; +import org.example.Entity.BaseResponse.BaseResponse; +import org.example.Utils.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.SqlParameter; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Service; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.sql.SQLException; +import java.sql.Types; +import java.text.SimpleDateFormat; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.*; +import java.util.function.Function; + +import static com.mysql.cj.util.TimeUtil.DATE_FORMATTER; + +@Service +public class SMSImpl extends OptBaseImpl { + private static final int TOT_SEND_PHONE_CODE_ONE_DAY_ONE_IP = 10; + private static final Logger log = LoggerFactory.getLogger(SMSImpl.class); + + @Autowired + private UpdateImpl updateImpl; + + public SMSImpl() { + super(); +// new UpdateImpl().updatePhoneCode(); + } + + // 初始化方法,在依赖注入完成后执行 + @PostConstruct + public void init() throws SQLException { + // 在这里调用 updatePhoneCode 方法 + updateImpl.updatePhoneCode(); + } + + final int TotSendPhoneCodeOneDayOneIP = 10; + + public BaseResponse sendPhoneCode(String phone, String vcode, String imgcode, String codekey) { + BaseResponse[] response = new BaseResponse[1]; + response[0] = new BaseResponse(); + response[0].setSuccess(false); + if (NativeExtensionUtils.isNullOrEmpty(phone) || !RegexUtil.PhoneReg.matcher(phone).matches()) { + response[0].setMsg(LanguageUtil.WrongPhoneNumber); + return response[0]; + } + if (NativeExtensionUtils.isNullOrEmpty(imgcode)) { + response[0].setSuccess(false); + response[0].setMsg("请输入图片验证码"); + return response[0]; + } + int checkCodeState = validateCaptcha(imgcode, codekey); + if (checkCodeState == -1) { + response[0].setSuccess(false); + response[0].setMsg("图片验证码已失效,请刷新重试"); + return response[0]; + } else if (checkCodeState == 0) { + response[0].setSuccess(false); + response[0].setMsg("图片验证码错误"); + return response[0]; + } + Date lastTime = getLastCodeSendTime(phone); + Date now = new Date(); + if (now.getTime() - lastTime.getTime() < 5 * 60 * 1000L) { + String existVcode = getCtx().getSession().getAttribute("phonevcode") + ""; + if (NativeExtensionUtils.isNullOrEmpty(vcode)) { + response[0].setData(-2); + response[0].setMsg("验证码发送频繁请5分钟后再试"); + return response[0]; + } else if (!vcode.equals(existVcode)) { + response[0].setData(-1); + response[0].setMsg(LanguageUtil.WrongVCode); + return response[0]; + } + } + getCtx().getSession().removeAttribute("phonevcode"); + String ip = WebUtil.getIP(); + int tottodysend = getTotValidCodeInfoByIp(ip); + if (tottodysend > TotSendPhoneCodeOneDayOneIP) { + response[0].setData(-1); + response[0].setMsg(String.format("发送数量%s", LanguageUtil.LimitOfTheNumber)); + } + Map dtTable = getCurrentValidCodeInfo(phone); + String validcode; + Boolean result = false; + double radomtime = 31; + if (dtTable.size() > 0) { + Map row = dtTable; + String sendtime = row.get("sendtime").toString()+""; + validcode = row.get("validcode").toString()+""; + if(!NativeExtensionUtils.isNullOrEmpty(sendtime)){ + // 1. 解析时间字符串(等效于C#的DateTime.Parse(sendtime)) + LocalDateTime date = LocalDateTime.parse(sendtime, DATE_FORMATTER); + // 2. 获取当前时间(等效于C#的DateTime.Now) + LocalDateTime date1 = LocalDateTime.now(); + + // 3. 计算时间差(分钟)(等效于C#的(date1 - date).TotalMinutes) + radomtime = Duration.between(date, date1).toMinutes(); + + // 4. 判断是否超过30分钟,超过则重新生成6位验证码 + if (radomtime > 30) { + // 调用你之前实现的6位随机验证码生成方法(对应C#的randomnumber(6)) + validcode = randomnumber(6);; + } + } + }else{ + validcode = randomnumber(6); + } + + //这里的resultMsg + String[] resultMsg=new String[1]; + result = sendSMS(phone, validcode,resultMsg); + if (result) { + generateCode(phone, validcode, ip); + log.info(String.format("向“%s”发送短信,ip:%s", phone, ip)); + } + response[0].setData(1); + response[0].setSuccess(result); + response[0].setMsg(resultMsg[0]); + return response[0]; + } + + private int validateCaptcha(String code, String codeKey) { + Class stringType = String.class; + Object obj = CacheUtil.get(codeKey, stringType); + if (obj == null) { + return -1; + } + String storedCode = obj.toString(); + if (NativeExtensionUtils.isNullOrEmpty(storedCode)) { + return -1; + } + Boolean isValid = storedCode.equalsIgnoreCase(code); + CacheUtil.remove(codeKey); + return isValid ? 1 : 0; + } + + private String randomnumber(){ + return randomnumber(4); + } + + private String randomnumber(int length){ + Random rd = new Random(); + StringBuffer str = new StringBuffer(); + // 循环直到字符串长度达到指定值 + while (str.length() < length) { + // 生成0-9的随机整数(等效于C#的rd.Next(0, 10)) + int temp = rd.nextInt(10); + // 检查是否已包含该数字(等效于C#的!str.Contains(temp + "")) + String tempStr = String.valueOf(temp); + if (str.indexOf(tempStr) == -1) { // 不存在则追加 + str.append(temp); + } + } + + return str.toString(); + } + + + public BaseResponse sendPhoneCode(String phone, String vcode, Function getTemplate) { + BaseResponse response = new BaseResponse(); + response.setSuccess(false); + + // 验证手机号格式 + if (phone == null || phone.isEmpty() || !RegexUtil.PhoneReg.matcher(phone).matches()) { + response.setMsg(LanguageUtil.WrongPhoneNumber); + return response; + } + + // 验证模板函数 + if (getTemplate == null) { + response.setMsg(String.format("模板%s", LanguageUtil.NotNull)); + return response; + } + + // 检查5分钟内是否需要验证码 + Date lastTime = getLastCodeSendTime(phone); + long minutesDiff = (new Date().getTime() - lastTime.getTime()) / (60 * 1000); + + if (minutesDiff < 5) { + HttpSession session = getSession(); + String existVcode = (String) session.getAttribute("phonevcode"); + + if (vcode == null || vcode.isEmpty()) { + response.setData(-2); + return response; + } else if (!vcode.equals(existVcode)) { + response.setData(-1); + response.setMsg(LanguageUtil.WrongVCode); + return response; + } + } + + // 清除session中的验证码 + getSession().removeAttribute("phonevcode"); + + // 验证IP发送上限 + String ip = WebUtil.getIP(); + int todaySendCount = getTotValidCodeInfoByIp(ip); + if (todaySendCount > TOT_SEND_PHONE_CODE_ONE_DAY_ONE_IP) { + response.setData(-1); + response.setMsg(String.format("发送数量%s", LanguageUtil.LimitOfTheNumber)); + return response; + } + + // 处理验证码生成逻辑 + String validcode; + Map currentCodeInfo = getCurrentValidCodeInfo(phone); + + if (currentCodeInfo != null) { + String sendtime = (String) currentCodeInfo.get("sendtime"); + validcode = (String) currentCodeInfo.get("validcode"); + + if (sendtime != null) { + try { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + Date lastSendTime = sdf.parse(sendtime); + long minutes = (new Date().getTime() - lastSendTime.getTime()) / (60 * 1000); + + if (minutes > 30) { + validcode = randomNumber(6); + } + } catch (Exception e) { + validcode = randomNumber(6); + } + } + } else { + validcode = randomNumber(6); + } + + // 发送短信 + String[] resultMsg = new String[1]; + boolean result = sendSMS(phone, getTemplate.apply(validcode), resultMsg); + + if (result) { + generateCode(phone, validcode, ip); + log.info(String.format("向“%s”发送短信,ip:%s", phone, ip)); + } + + response.setData(1); + response.setSuccess(result); + response.setMsg(resultMsg[0]); + return response; + } + + + public boolean generateCode(String phone, String validcode, String ip) { + String sql = "INSERT INTO P_ValidCode(phone, sendtime, validcode, ip) " + + "VALUES(?, GETDATE(), ?, ?)"; + + return jdbcTemplate.update(sql, phone, validcode, ip) > 0; + } + + + public boolean sendSMS(String phone, String msg, String[] resultMsg) { + resultMsg[0] = ""; + if (WebConfigUtil.getSMSPwd() == null || WebConfigUtil.getSMSPwd().isEmpty() || + WebConfigUtil.getSMSUserName() == null || WebConfigUtil.getSMSUserName().isEmpty()) { + resultMsg[0] = "向发送短信失败,未配置短信平台用户名密码"; + log.debug(resultMsg[0]); + return false; + } +// +// String result = SMSService.SMS.postMethodToObj( +// WebConfigUtil.getSMSUserName(), +// WebConfigUtil.getSMSPwd(), +// phone, +// msg +// ); + + return true; + } + + private String randomNumber(int length) { + if (length <= 0) length = 4; + Random rd = new Random(); + StringBuilder str = new StringBuilder(); + + while (str.length() < length) { + int temp = rd.nextInt(10); + if (str.indexOf(String.valueOf(temp)) == -1) { + str.append(temp); + } + } + return str.toString(); + } + + + public int getTotValidCodeInfoByIp(String ip) { + if (ip == null || ip.isEmpty()) return 0; + + String sql = "SELECT COUNT(1) FROM P_ValidCode " + + "WHERE ip = ? " + + "AND sendtime >= ? " + + "AND sendtime < ?"; + + LocalDateTime now = LocalDateTime.now(); + LocalDateTime startOfDay = now.toLocalDate().atStartOfDay(); + LocalDateTime startOfNextDay = startOfDay.plusDays(1); + + Date start = Date.from(startOfDay.atZone(ZoneId.systemDefault()).toInstant()); + Date NextDay = Date.from(startOfNextDay.atZone(ZoneId.systemDefault()).toInstant()); + + return jdbcTemplate.queryForObject(sql, new Object[]{ip, start, NextDay}, Integer.class); + } + + + public Map getCurrentValidCodeInfo(String phone) { + String sql = "SELECT TOP 1 * FROM P_ValidCode " + + "WHERE phone = ? " + + "AND sendtime >= ? " + + "AND ISNULL(used, 0) = 0 " + + "ORDER BY sendtime DESC"; + + LocalDateTime thirtyMinutesAgo = LocalDateTime.now().minusMinutes(30); + // 转换为java.util.Date(与数据库字段类型匹配) + Date thirtyMinutesAgoDate = Date.from( + thirtyMinutesAgo.atZone(ZoneId.systemDefault()).toInstant() + ); + + try { + return jdbcTemplate.queryForMap(sql, phone, thirtyMinutesAgoDate); + } catch (Exception e) { + return null; + } + } + + + public Date getLastCodeSendTime(String phone) { + String sql = "SELECT TOP 1 sendtime FROM P_ValidCode " + + "WHERE phone = ? " + // 第一个参数:phone + "AND sendtime >= ? " + // 第二个参数:30分钟前的时间 + "ORDER BY sendtime DESC"; + + LocalDateTime thirtyMinutesAgo = LocalDateTime.now().minusMinutes(30); + // 转换为java.util.Date(与数据库字段类型匹配) + Date thirtyMinutesAgoDate = Date.from( + thirtyMinutesAgo.atZone(ZoneId.systemDefault()).toInstant() + ); + + try { + return jdbcTemplate.queryForObject(sql, new Object[]{phone, thirtyMinutesAgoDate}, Date.class); + } catch (Exception e) { + return Date.from(thirtyMinutesAgo.atZone(ZoneId.systemDefault()).toInstant()); + } + } + + + public boolean verfyCode(String phone, String code, boolean useOld) { + if (phone == null || phone.isEmpty() || code == null || code.isEmpty()) { + return false; + } + + String sql; + List params = new ArrayList<>(); + // 先添加共用参数(phone和code) + params.add(phone); + params.add(code); + + if (useOld) { + // 查询条件:10分钟内有效且匹配手机号和验证码 + sql = "SELECT TOP 1 validcode, id FROM P_ValidCode " + + "WHERE phone = ? " + + "AND validcode = ? " + + "AND sendtime >= ? " + + "ORDER BY sendtime DESC"; + // 添加时间参数(10分钟前) + LocalDateTime tenMinutesAgo = LocalDateTime.now().minusMinutes(10); + params.add(Date.from(tenMinutesAgo.atZone(ZoneId.systemDefault()).toInstant())); + } else { + // 查询条件:30分钟内有效、未使用且匹配手机号 + sql = "SELECT TOP 1 validcode, id FROM P_ValidCode " + + "WHERE ISNULL(used, 0) = 0 " + + "AND phone = ? " + + "AND sendtime >= ? " + + "ORDER BY sendtime DESC"; + // 添加时间参数(30分钟前) + LocalDateTime thirtyMinutesAgo = LocalDateTime.now().minusMinutes(30); + params.add(Date.from(thirtyMinutesAgo.atZone(ZoneId.systemDefault()).toInstant())); + } + + try { + // 执行查询(参数按顺序传入) + Map result = jdbcTemplate.queryForMap(sql, params.toArray()); + + if (useOld) { + // 旧逻辑:只要查询到记录就返回true + return true; + } else { + // 新逻辑:验证验证码并标记为已使用 + String dbCode = (String) result.get("validcode"); + if (code.equals(dbCode)) { + // 更新验证码状态(使用?占位符传递id) + String updateSql = "UPDATE P_ValidCode SET used = 1 WHERE id = ?"; + jdbcTemplate.update(updateSql, result.get("id")); + return true; + } + } + } catch (Exception e) { + // 未查询到记录或异常时返回false + } + + return false; + } + + public boolean verfyCode(String phone, String code) { + return verfyCode(phone, code, false); + } + + // 获取当前会话 + private HttpSession getSession() { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attributes == null) { + return null; + } + return attributes.getRequest().getSession(); + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Impl/Sql/dmImpl/DmAllInOneSqlProvider.java b/WebErp/weberp/src/main/java/org/example/Impl/Sql/dmImpl/DmAllInOneSqlProvider.java new file mode 100644 index 0000000..ce35a05 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Impl/Sql/dmImpl/DmAllInOneSqlProvider.java @@ -0,0 +1,1090 @@ +package org.example.Impl.Sql.dmImpl; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.example.Enums.CusGridColumnPrefix; +import org.example.Enums.SystemEnums; +import org.example.Impl.Sql.provider.AllInOneSqlProvider; +import org.example.ModuleApi.ModuleAjaxApi.mapper.DMCrmMapper; +import org.example.Utils.DMJdbcMultiResultSetUtil; +import org.example.Utils.DataTableUtil; +import org.example.Utils.PublicUtil; +import org.example.Utils.SqlSafetyGuard; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.CallableStatementCallback; +import org.springframework.jdbc.core.ConnectionCallback; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.StatementCallback; +import org.springframework.stereotype.Service; + +import javax.sql.rowset.CachedRowSet; +import java.sql.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.example.Utils.NativeExtensionUtils.*; + +@Service +public class DmAllInOneSqlProvider implements AllInOneSqlProvider { + private static final Logger log = LoggerFactory.getLogger(DmAllInOneSqlProvider.class); + + // 成员变量保存Mapper + private final DMCrmMapper dmCrmMapper; + private JdbcTemplate jdbcTemplate; + + // 构造器注入(推荐,可控且便于测试) + public DmAllInOneSqlProvider(DMCrmMapper dmCrmMapper, JdbcTemplate jdbcTemplate) { + this.dmCrmMapper = dmCrmMapper; + this.jdbcTemplate = jdbcTemplate; + } + + // region SysUserImpl + @Override + public String LoginSql() { + return "select e.EmployeeId, e.EmpLoyeeName, e.LoginAccount, e.password, " + "e.p_emp_clientid, e.p_emp_AttendanceTime, e.p_emp_logintype, " + "e.p_emp_PwdErrNum, e.p_emp_PwdLocked, e.p_emp_PwdLockDate, e.AppIndex " + "from P_EmployeeTab e " + "left join P_customertab b on e.p_emp_clientid = b.id and b.coid = ? " + "where (e.LoginAccount = ? or e.EmployeeName = ? or e.p_emp_phone = ? or isnull(b.id, '') <> '') " + "and isnull(e.sign, 0) = 0 " + "and isnull(e.UseFlag, 0) = 1"; + } +// endregion + +// region DataImpl + + @Override + public String getSysdbGroupByIdSql() { + return "select dbname as name, isnull(localIp, ip) as ip, mobileurl, showname as text " + "from p_sydbGroupTab where id = ?"; + } + + @Override + public String getSysdbGroupAllSql() { + return "select id, showname as text, dbname from p_sydbGroupTab order by orderid"; + } + + @Override + public String getExitSystemPrivilegeAgentTabSql() { + return "select 1 from ALL_TABLES where UPPER(OWNER) = UPPER(SYS_CONTEXT('USERENV','CURRENT_SCHEMA')) and UPPER(TABLE_NAME) = UPPER('p_systemPrivilegeAgentTab')"; + } + + @Override + public String getExitFlowExOperSql() { + return "select 1 " + "from ALL_TABLES " + "where UPPER(OWNER) = UPPER(SYS_CONTEXT('USERENV','CURRENT_SCHEMA'))" + " and UPPER(TABLE_NAME) = UPPER('wms_billflowOperEx')"; + } + + + @Override + public String GetBaesModuleLeftSql() { + return "select id detailid,fieldname,ISNULL(userenname,sysname) fieldcaption,fieldkey fromkey,fieldsqlid valuemember,fieldsqlname displaymember from p_systemwordbooktab where tab= ? and fieldsqltag= ?"; + } + + @Override + public String GetTableColumnTypeSql() { + return "Select TYPE$ from syscolumns Where ID=OBJECT_ID('%s') and name='%s'"; + } + + + @Override + public String GetColumnRowsSql(String moduleId, String userId, String userName, int id, boolean windowsDirver) { + return ""; + } + + @Override + public String GetBaesModuleLeftSql(String moduleId) { + return "select id detailid, " + "fieldname, " + "ISNULL(userenname, sysname) fieldcaption, " + "fieldkey fromkey, " + "fieldsqlid valuemember, " + "fieldsqlname displaymember " + "from p_systemwordbooktab " + "where tab = ? and fieldsqltag = ?"; + } + + @Override + public String getBaesModuleLeftSql(String moduleId) { + return ""; + } + + @Override + public String GetBaesModuleBmpFieldsSql(String moduleId, String tbName) { + String sql = "select fieldname from p_systemwordbooktab w " + "inner join syscolumns col on col.id=OBJECT_ID('%s') and col.TYPE$='BLOB' and col.name=w.fieldname " + "where tab='%s' and fieldsqltag='%d'"; + sql = String.format(sql, tbName, moduleId, SystemEnums.ControlType.LabPic.getValue()); + + return sql; + } + + @Override + public String GetTableInfoSql(String trimmedTbName) { + String sql = "SELECT\n" + " c.NAME AS name,\n" + " c.TYPE$ AS xtype,\n" + " CASE \n" + " WHEN c.TYPE$ IN ('BLOB','TEXT','VARBINARY') THEN 0 \n" + " ELSE c.LENGTH$ \n" + " END AS length,\n" + " CASE WHEN c.NULLABLE$ = 'N' THEN 0 ELSE 1 END AS isnullable,\n" + " CASE WHEN a.id IS NOT NULL THEN NULL ELSE c.DEFVAL END AS text,\n" + "CASE\n" + " WHEN c.INFO2 = 1 AND a.id IS NULL THEN 1\n" + "WHEN a.id IS NOT NULL AND c.INFO2 = 0 THEN 4\n" + "else 0\n" + "END AS colstat," + " CASE WHEN c.INFO2 = 1 THEN 1 ELSE 0 END AS isIdentity,\n" + " CASE WHEN a.id IS NOT NULL THEN 1 ELSE 0 END AS isComputed\n" + "FROM\n" + " SYSCOLUMNS c\n" + "LEFT JOIN syscolinfos a\n" + " ON a.id = c.id AND a.colid = c.colid\n" + "WHERE\n" + " c.ID = OBJECT_ID('%s');"; + sql = String.format(sql, trimmedTbName); + return sql; + } + + + @Override + public String GetColorAndBoxColumnsSql() { + return "select fieldname, fieldsqltag FieldType, fieldsql, " + "fieldsqlid valuemember, fieldsqlname displaymember, " + "TitleColor fontcolor " + "from p_systemwordbooktab zsc " + "where zsc.tab='%s' and " + "(isnull(TitleColor,'')<>'' or " + "(isnull(fieldsql,'')<>'' and isnull(fieldsqlname,'')<>'' and isnull(fieldsqlid,'')<>'' ))"; + } + + @Override + public String GetBaesModuleDetailsSql() { + return "select detail.orderid, detail.displayRows, detail.id, detail.detailName, library, " + "detail.detailsql, detail.autorefresh refresh, detail.unionvalue unionfield, unionCond, " + "noGridLine, noRownumber, noColumnHeader hideColumnHeader, detail.isDrag, detail.defaultitem," + "detail.unionparentfield, detail.unionmodule, detail.formkey, detail.detailType, " + "formKey fromkey, addVisible, visibleCond, fieldCond, disableField, fieldCond1, disableField1, " + "case when detail.gridDetailCheck=1 then 1 else detail.gridDetailCheck end multcheck, " + "displaymode, addShowMode%s " + "from p_systemDlltabDetail detail " + "where tabKey=? and isnull(isVisible,0)=0 " + "order by OrderID"; + } + + @Override + public String GetAttcFilesSql() { + return "select p.EmployeeName username, f.* from P_fm_FileTab f " + "inner join bmp_ProductSpeciesTab op on f.speciesno = op.speciesno and " + "(INSTR(',' || REPLACE(REPLACE(op.uploadOper, ' ', ''), ' ', '') || ',', ',%s,') > 0 or (NVL(op.uploadOper, '') = '' OR NVL(op.uploadOper, '') IS NULL) " + "or INSTR(',' || REPLACE(REPLACE(op.downloadOper, ' ', ''), ' ', '') || ',', ',%s,') > 0 or (NVL(op.downloadOper, '') = '' OR NVL(op.downloadOper, '') IS NULL) " + "or INSTR(',' || REPLACE(REPLACE(op.deleteOper, ' ', ''), ' ', '') || ',', ',%s,') > 0 or (NVL(op.deleteOper, '') = '' OR NVL(op.deleteOper, '') IS NULL) " + "or INSTR(',' || REPLACE(REPLACE(op.previewOper, ' ', ''), ' ', '') || ',', ',%s,') > 0 or (NVL(op.previewOper, '') = '' OR NVL(op.previewOper, '') IS NULL)) " + "left join p_employeetab p on f.creator = p.employeeid " + "where f.parentid = '%s' %s %s"; + } + + @Override + public String GetAbsFilePathSql() { + return "select fun_fm_getAbsolutePath(%s)"; + } + + @Override + public String GetAcFileFolderSql() { + return "select fun_fm_getRelativePath(%s)"; + } + + @Override + public String GetAcFileFolderByCount() { + return "select fun_fm_getRelativePath(?, ?)"; + } + + @Override + public String GetAcFileFolderAllCount() { + return "select fun_fm_getRelativePath(?)"; + } + + @Override + public String GetPmsCountSql() { + return "SELECT COUNT(*) FROM ALL_ARGUMENTS WHERE OBJECT_NAME = ?" + " AND TRIM(NVL(ARGUMENT_NAME, ' ')) <> ' 'AND IN_OUT <> 'OUT'"; + } + + @Override + public List>> BaseDataSaveSql(String procName, String escapedBaseSql, int execType, String escapedModuleId, String escapedMasterTable, String escapedIdField, String escapedIdValue, String escapedOperatorId, String escapedOperatorName) { + // 1. 保留原有拼接逻辑 + StringBuilder sql = new StringBuilder(); + sql.append("DECLARE ").append("DM_return INT; ").append("DM_msg VARCHAR(2000); ").append("BEGIN ").append("CALL ").append(procName).append("( ").append("DM_return, ") + .append("'").append(escapedBaseSql).append("', ")// 参数2:动态SQL(字符串,加单引号) + .append(execType).append(", ") // 参数3:execType(数值,不加单引号) + .append("'").append(escapedModuleId).append("', ")// 参数4:modid(字符串) + .append("'").append(escapedMasterTable).append("', ")// 参数5:tablename(字符串) + .append("'").append(escapedIdField).append("', ")// 参数6:keyfield(字符串) + .append("'").append(escapedIdValue).append("', ")// 参数7:keyvalue(字符串) + .append(Integer.parseInt(escapedOperatorId)).append(", ")// 参数8:operatorid(数值) + .append("'").append(escapedOperatorName).append("', ")// 参数9:operatorname(字符串) + .append("DM_msg, ") // 参数10:OUT消息(固定变量) + .append("0 ") // 参数11:confirmFlag(固定值0) + .append("); ").append("SELECT DM_return AS returnValue, DM_msg AS outputValue FROM DUAL;").append("END;"); + + // 调用工具类获取所有结果集,解决结果集不匹配问题 + List>> AllResultSets = DMJdbcMultiResultSetUtil.executeMultiResultSet(jdbcTemplate, sql.toString()); + // 筛选包含 returnValue 的目标结果集 +// Map ResultMap = new HashMap<>(); +// for (List> resultSet : AllResultSets) { +// if (!resultSet.isEmpty() && resultSet.get(0).containsKey("returnValue")) { +// ResultMap = resultSet.get(0); +// break; +// } +// } + + // 兜底处理(按需处理,与公用方法解耦) +// targetResult.putIfAbsent("returnValue", 0); +// targetResult.putIfAbsent("outputValue", ""); +// +// // (可选)保留所有结果集,方便后续扩展 +// targetResult.put("allResultSets", AllResultSets); + + return AllResultSets; + + } + + + @Override + public String IsExitProSql() { + return "select 1 from sysobjects where id = object_id('%s')AND SUBTYPE$ = 'PROC';"; + } + + @Override + public List> ExecSelectOperStoreSql(List outParamNames, String storeName, Map params) { + Connection connection = null; + CallableStatement dmCallableStmt = null; + try { + StringBuilder sql = new StringBuilder(); + if (storeName.equalsIgnoreCase("p_billApply")) { + // 清空原有内容,重新拼接(也可直接append) + sql.append("DECLARE ") + .append("rtn_code INT; ") // 对应新增的p_return_code OUT INT + .append("p_msg VARCHAR2; ") // 对应p_msg OUT VARCHAR2 + .append("BEGIN ") + .append("CALL ").append(storeName).append("( ") + // 按存储过程参数顺序拼接:先输出参数,后输入参数 + .append(" rtn_code ,") // 1. p_return_code OUT INT + .append(getParamValue(params, "@typeCode")) // 2. p_typeCode IN VARCHAR2 + .append(", ").append(getParamValue(params, "@billDocumentId")) // 3. p_billDocumentId IN VARCHAR2 + .append(", ").append(getParamValue(params, "@operatorId")) // 4. p_operatorId IN INT + .append(", ").append(getParamValue(params, "@operatorName")) // 5. p_operatorName IN VARCHAR2 + .append(", ").append(getParamValue(params, "@comfirmType")) // 6. p_comfirmType IN INT + .append(", p_msg ") // 7. p_msg OUT VARCHAR2 + .append("); ") + // 查询输出参数并返回(仅保留实际的输出参数) + .append("SELECT rtn_code AS returnCode, p_msg AS msg FROM DUAL;") + .append("END;"); + } else if (storeName.equalsIgnoreCase("p_baseApply")) { + sql.append("DECLARE ") + .append("rtn_code INT; ") // 输出:状态返回码 + .append("p_msg VARCHAR(32000); ") // 输出:返回结果 +// .append("p_nextSelectStepCode VARCHAR(100); ") // 输出:下步审核步骤 +// .append("p_nextSelectStepOper VARCHAR(8000); ") // 输出:下步审核人员 + .append("BEGIN ") + .append("CALL ").append(storeName).append("( ") + // 输入参数(从params中获取,按存储过程参数顺序) + .append(" rtn_code ,") // rtn_code OUT INT + .append(getParamValue(params, "@typeCode")) // p_typeCode IN VARCHAR(10) +// .append(", ").append(getParamValue(params, "@stepCode")) // p_stepCode IN INT + .append(", ").append(getParamValue(params, "@billDocumentId")) // p_billDocumentId IN VARCHAR(50) + .append(", ").append(getParamValue(params, "@operatorid")) // p_operatorId IN INT + .append(", ").append(getParamValue(params, "@operatorName")) // p_operatorName IN VARCHAR(20) + .append(", ").append(getParamValue(params, "@comfirmType")) // p_auditAdvice IN VARCHAR(500) +// .append(", ").append(getParamValue(params, "@Direction")) // p_Direction IN CHAR(1) + .append(", p_msg ") +// .append(", ").append(getParamValue(params, "@backStepCode", "-1")) // p_backStepCode IN INT := -1 +// .append(", ").append(getParamValue(params, "@hint_opers", "''")) // p_hint_opers IN VARCHAR(5000) := '' +// .append(", ").append(getParamValue(params, "@comfirm_opers", "''")) // p_comfirm_opers IN VARCHAR(5000) := '' +// .append(", ").append(getParamValue(params, "p_selectConfirmFlag", "0")) // p_selectConfirmFlag IN INT := 0 + // 输出参数(绑定声明的变量) + // p_msg OUT VARCHAR(3000) +// .append(", p_nextSelectStepCode ") // p_nextSelectStepCode OUT VARCHAR(100) +// .append(", p_nextSelectStepOper ") // p_nextSelectStepOper OUT VARCHAR(8000) + .append("); ") + // 查询输出参数并返回 + .append("SELECT rtn_code AS returnCode, p_msg AS msg FROM DUAL;") +// .append("p_nextSelectStepCode AS nextStepCode, p_nextSelectStepOper AS nextStepOper FROM DUAL;") + .append("END;"); + } else if (storeName.equalsIgnoreCase("p_BaseAudit")) { + sql.append("DECLARE ") + .append("rtn_code INT; ") // 输出:状态返回码(存储过程第一个OUT参数) + .append("p_msg VARCHAR(3000); ") // 输出:返回结果(存储过程第8个参数,OUT类型) + .append("BEGIN ") + // 2. CALL段:严格按存储过程参数顺序拼接,匹配IN/OUT/默认值规则 + .append("CALL ").append(storeName).append("( ") + // 核心修正:参数顺序完全匹配存储过程定义,移除多余的p_nextSelectStepCode/p_nextSelectStepOper + // 第1个参数:p_typeCode IN VARCHAR(10) + .append(getParamValue(params, "@typeCode")) + // 第2个参数:p_stepCode IN INT + .append(", ").append(getParamValue(params, "@stepCode")) + // 第3个参数:p_billDocumentId IN VARCHAR(200) + .append(", ").append(getParamValue(params, "@billDocumentId")) + // 第4个参数:p_operatorId IN INT(注意参数名统一为@operatorId,避免拼写错误operatorid) + .append(", ").append(getParamValue(params, "@operatorId")) + // 第5个参数:p_operatorName IN VARCHAR(40) + .append(", ").append(getParamValue(params, "@operatorName")) + // 第6个参数:p_auditAdvice IN VARCHAR(500) + .append(", ").append(getParamValue(params, "@auditAdvice")) + // 第7个参数:p_Direction IN CHAR(1) + .append(", ").append(getParamValue(params, "@Direction")) + // 第8个参数:p_msg OUT VARCHAR(3000)(绑定声明的p_msg变量) + .append(", p_msg ") + // 第9个参数:p_backStepCode IN VARCHAR(50) := '-1'(带默认值,无值时用'-1') + .append(", ").append(getParamValue(params, "@backStepCode", "-1")) + // 第10个参数:p_hint_opers IN VARCHAR(8000) := ''(带默认值,无值时用空字符串) + .append(", ").append(getParamValue(params, "@hint_opers", "''")) + // 第11个参数:p_comfirm_opers IN VARCHAR(8000) := ''(带默认值,无值时用空字符串) + .append(", ").append(getParamValue(params, "@comfirm_opers", "''")) + .append("); ") + // 3. SELECT段:仅返回存储过程实际的OUT参数(rtn_code/p_msg),移除多余字段 + .append("SELECT rtn_code AS returnCode, p_msg AS msg FROM DUAL;") + .append("END;"); + + } else if (storeName.equalsIgnoreCase("p_billAudit")) { + sql.append("DECLARE ") + .append("rtn_code INT; ") // 输出:返回状态码(第一个OUT参数,INT类型) + .append("p_msg VARCHAR(3000); ") // 输出:返回结果(第八个参数,OUT VARCHAR(3000)) + .append("BEGIN ") + // 2. CALL段:严格按存储过程参数顺序拼接,区分IN/OUT参数 + .append("CALL ").append(storeName).append("( ") + // 第1个参数:rtn_code OUT INT(绑定声明的rtn_code变量) + .append("rtn_code ,") + // 第2个参数:p_typeCode IN VARCHAR(10) + .append(getParamValue(params, "@typeCode")) + // 第3个参数:p_stepCode IN INT + .append(", ").append(getParamValue(params, "@stepCode")) + // 第4个参数:p_billDocumentId IN VARCHAR(50) + .append(", ").append(getParamValue(params, "@billDocumentId")) + // 第5个参数:p_operatorId IN INT(修正拼写,统一为@operatorId) + .append(", ").append(getParamValue(params, "@operatorId")) + // 第6个参数:p_operatorName IN VARCHAR(20) + .append(", ").append(getParamValue(params, "@operatorName")) + // 第7个参数:p_auditAdvice IN VARCHAR(500) + .append(", ").append(getParamValue(params, "@auditAdvice")) + // 第8个参数:p_Direction IN CHAR(1) + .append(", ").append(getParamValue(params, "@Direction")) + // 第9个参数:p_msg OUT VARCHAR(3000)(绑定声明的p_msg变量) + .append(", p_msg ") + // 第10个参数:p_backStepCode IN INT := -1(带默认值-1,INT类型) + .append(", ").append(getParamValue(params, "@backStepCode", "-1")) + // 第11个参数:p_hint_opers IN VARCHAR(5000) := ''(带默认值空字符串) + .append(", ").append(getParamValue(params, "@hint_opers", "''")) + // 第12个参数:p_comfirm_opers IN VARCHAR(5000) := ''(带默认值空字符串) + .append(", ").append(getParamValue(params, "@comfirm_opers", "''")) + .append("); ") + // 3. SELECT段:仅返回存储过程的OUT参数(rtn_code/p_msg) + .append("SELECT rtn_code AS returnCode, p_msg AS msg FROM DUAL;") + .append("END;"); + } else if (storeName.equalsIgnoreCase("p_baseAuditBack")) { + sql.append("DECLARE ") + .append("rtn_code INT; ") // 输出:状态返回码(第一个OUT参数,INT类型) + .append("p_msg VARCHAR(3000); ") // 输出:返回结果(第九个参数,OUT VARCHAR(3000)) + .append("BEGIN ") + // 2. CALL段:严格按存储过程参数顺序拼接,区分IN/OUT参数 + .append("CALL ").append(storeName).append("( ") + // 第1个参数:rtn_code OUT INT(绑定声明的rtn_code变量) + .append("rtn_code ,") + // 第2个参数:p_typeCode IN VARCHAR(10) + .append(getParamValue(params, "@typeCode")) + // 第3个参数:p_stepCode IN INT + .append(", ").append(getParamValue(params, "@stepCode")) + // 第4个参数:p_billDocumentId IN VARCHAR(200)(长度恢复为200) + .append(", ").append(getParamValue(params, "@billDocumentId")) + // 第5个参数:p_operatorId IN INT(修正拼写,统一为@operatorId) + .append(", ").append(getParamValue(params, "@operatorId")) + // 第6个参数:p_operatorName IN VARCHAR(20) + .append(", ").append(getParamValue(params, "@operatorName")) + // 第7个参数:p_auditAdvice IN VARCHAR(500) + .append(", ").append(getParamValue(params, "@auditAdvice")) + // 第8个参数:p_Direction IN CHAR(1)(仅F/R两种值) + .append(", ").append(getParamValue(params, "@Direction")) + // 第9个参数:p_msg OUT VARCHAR(3000)(绑定声明的p_msg变量) + .append(", p_msg") + .append("); ") + // 3. SELECT段:仅返回存储过程的OUT参数(rtn_code/p_msg) + .append("SELECT rtn_code AS returnCode, p_msg AS msg FROM DUAL;") + .append("END;"); + + } + log.debug(String.valueOf(sql.toString() + " ExecSelectOperStoreSql ")); + List> result = jdbcTemplate.queryForList(sql.toString()); + return result; + + } catch (Exception e) { + String errorMsg = String.format("达梦存储过程调用失败,存储过程名:%s", storeName); + throw new RuntimeException(errorMsg, e); + } finally { + // 修复资源泄漏:finally块统一关闭,无论是否异常都执行 + try { + if (dmCallableStmt != null) dmCallableStmt.close(); + } catch (SQLException ex) { + log.error("Exception caught", ex); + } + try { + if (connection != null) connection.close(); + } catch (SQLException ex) { + log.error("Exception caught", ex); + } + } + } + + private String getParamValue(Map params, String paramKey) { + return getParamValue(params, paramKey, null); + } + + private String getParamValue(Map params, String paramKey, String defaultValue) { + Object value = params != null ? DataTableUtil.get(params, paramKey) : null; + // 如果参数为空,使用默认值 + if (value == null || value.toString().isEmpty()) { + return defaultValue == null ? "NULL" : defaultValue; + } + + // 字符串类型参数加单引号,数值类型直接返回 + if (value instanceof String) { + return "'" + escapeSql(value.toString()) + "'"; // 防SQL注入转义 + } else { + return value.toString(); + } + } + + // 辅助方法:SQL转义(防止单引号导致语法错误) + private String escapeSql(String str) { + return str.replace("'", "''"); + } + + @Override + public String hasStoreParametersql(String procedureName, String paramName) { + // 处理达梦参数名:去掉开头的@符号(如果有) + String DMparamName = paramName != null && paramName.startsWith("@") ? paramName.substring(1) // 去掉开头的@ + : paramName; // 无@则原样返回 + + return String.format("SELECT ARGUMENT_NAME \n" + "FROM ALL_ARGUMENTS \n" + "WHERE OBJECT_NAME = '%s'\n" + " AND ARGUMENT_NAME = '%s';", procedureName, DMparamName); + } + + @Override + public String GetAttcBmpSpecSql() { + return "select bmpSpec from v_systemdlltab where DllCoid= '%s'"; + } + + @Override + public String GetIdentityFieldSql(String finaltbName) { + String sql = "SELECT sc.NAME AS COLUMN_NAME FROM DBA_OBJECTS do" + "JOIN SYSCOLUMNS sc ON do.OBJECT_ID = sc.ID" + "WHERE do.OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA')" + "AND do.OBJECT_NAME = '%s'" + "AND sc.INFO2 = 1"; + String identitySql = String.format(sql, finaltbName); + return identitySql; + } + + @Override + public String CheckIsMulitAuditSql() { + return "SELECT CASE WHEN EXISTS (SELECT 1 FROM DBA_OBJECTS do JOIN SYSCOLUMNS sc ON do.OBJECT_ID = sc.ID WHERE do.OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') AND do.OBJECT_NAME = 'wms_billflowOperView' AND sc.NAME = 'auditoperators' ) THEN 1 ELSE 0 END AS col_exists"; + } + + @Override + public String GetTaskMobileCardColumnSql() { + return ""; + } + + @Override + public String GetAuditBaseDetailsSql() { + return "SELECT CASE WHEN EXISTS " + "(SELECT 1 FROM DBA_OBJECTS do WHERE do.OBJECT_ID = OBJECT_ID" + "(CONCAT(SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA'), '.', 'p_SystemDlltabDetailFlowTab'))" + "AND do.OBJECT_TYPE = 'TABLE' AND do.OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA')" + "AND do.OWNER NOT IN ('SYS', 'SYSAUDITOR', 'SYSJOB')) THEN 1 ELSE 0 END AS is_user_table;"; + } + + @Override + public String GetAttTreeDataSql() { + return "select speciesno, speciesname, " + "LEFT(speciesno, LEN(speciesno) - 2) as parentid " + // 此处已修改 + "from bmp_ProductSpeciesTab spec " + "inner join (" + " select bmpspec, dllcoid " + " from v_systemdlltab " + " where dllcoid = ? " + " group by bmpspec, dllcoid" + ") vd on (spec.SpeciesNo like CONCAT(COALESCE(vd.bmpSpec, ''), '%'))"; + } + + @Override + public String GetAttTreeDataSqlisEmpty() { + return "select bmpspec as speciesno, bmpspec as speciesname, '01' as parentid " + "from v_systemdlltab " + "where dllcoid = ? " + "group by bmpspec, dllcoid"; + } + + @Override + public String GetSqlUserSql() { +// return "SELECT LISTAGG(employeename + ',', '') FROM p_employeetab WHERE (%s)"; + return "SELECT WM_CONCAT(EmployeeName) FROM p_employeetab WHERE (%s) AND EmployeeName <> '管理员'"; + + } + + @Override + public String GetSqlUserByroleSql() { + //return "SELECT LISTAGG(aa.operatorname + ',', '') FROM p_systemRoleSetTab a " + "JOIN p_systemRoleOperSetTab aa ON a.id = aa.roleid " + "WHERE a.roleName = '%s' FOR XML PATH('')"; + return "SELECT WM_CONCAT(aa.operatorname) FROM p_systemRoleSetTab a " + + "JOIN p_systemRoleOperSetTab aa ON a.id = aa.roleid " + + "WHERE a.roleName = '%s'"; + } + + @Override + public String hasExistsTableSql(String tableName) { + return ""; + } + + @Override + public String GetUsersSql() { + return ""; + } + + @Override + public String GetAccountInfoByStoreSql() { + return ""; + } + + @Override + public String GetProSysTypeSql() { + return ""; + } + + @Override + public String CheckUserAbleSql(Map updRow, String trimmedUsers) { + return "SELECT LISTAGG(employeename + ',', '') FROM p_employeetab " + "where (" + PublicUtil.ReqSqlPmsByRow(updRow, null, trimmedUsers) + ")"; + } + + @Override + public String GetNextSelectStepListSql() { + return "select stepcode, stepname from %s " + "where typecode='%s' " + "and INSTR(';%s;', ';' || CAST(stepcode AS VARCHAR2) || ';') > 0"; + } + + @Override + public String GetColumnRows(String moduleId, String userId, String userName, int id, boolean windowsDirver) { + // 构建基础SQL(适配达梦语法,修复作用域问题) + String disabledPart = windowsDirver ? "case when nvl(vislble, 0) = 1 then 1 else nvl(1-gcfg.isvisible, 0) end" : "case when nvl(ShowMobile,0)=0 then 1 else 0 end"; + + String disabled2Part = windowsDirver ? "book.addVisible" : "case when nvl(ShowMobile1,0)=0 then 1 else 0 end"; + + String scanAblePart = windowsDirver ? "0" : "book.vislble5"; + + String widthPart = windowsDirver ? "nvl(gcfg.fieldwidth,width)" : "mobilewidth"; + + // 拼接主SQL(核心修改:将where条件和order by移到子查询内) + String colsql = String.format("select * from ( " + "select book.id,book.FieldName,nvl(cast(gcfg.userName as varchar(200)), nvl(nvl(username1,sysname),book.FieldName)) FieldCaption," + "ControlWidth ,ControlHeight,%s Disabled ,%s Disabled2, fieldsqltag FieldType,addModuleId,%s scanAble," + "ControlTop,ControlLeft,book.tagid nullable,book.cancopy," + "case when nvl(book.privilegeOper,'')='' then edit when instr(','||(SELECT wm_concat(userList) FROM p_systemPrivilege pri " + "WHERE book.tab=pri.modid and instr(','||cast(pri.privTypeId as varchar(10))||',',','||book.privilegeOper||',')>0)||',',',%s,')>0 then 0 else 1 end Edit," + "isSum [sum],sumCond,sumCalc,fieldsql,fieldsqlid valuemember,fieldsqlname displaymember,calcExpr CalcExpress,calcOrder CalcOrderId,%s width," + "book.fieldkey fromkey,book.disableType,book.IsAddControl,book.dataAlign textalign," + "unionValue UnionSQL,unionFields UnionField,dataformat,BandTitle,BandFields BandField,InputHintText HintText,TitleColor FontColor," + "limitLen LimitMaxValue,defaultdate defaultvalue,TM_HeadString othermember,book.labelWidth,book.labelAlign,book.disableCond,c.TYPE$ fieldDbType," + "frozenFlag locked,(SELECT wm_concat(userList) FROM p_systemPrivilege pri WHERE book.tab=pri.modid and instr(','||cast(pri.privTypeId as varchar(10))||',',','||book.privilegeView||',')>0) userList," + "book.FontSize fontSize,book.highlightFColor fcolor,book.highlightBColor bcolor,book.highlightBold bold,book.doNotSpelling," + "book.lookupWidth pickerWidth,book.lookupFieldsWidth pickerColsWidth,book.bmptype ftype,book.bmpsource,book.ifmerge rowspan,ScanAfterEmpty scanClear " + + "from p_systemwordbooktab book " + "left join p_systemdlltab dll on book.tab=dll.dllcoid " + "left join syscolumns c on c.id=OBJECT_ID(dll.sqldt1) and c.name=book.fieldname " + "left join P_systemGridConfigTab gcfg on '%s'||cast(dll.formkey as varchar(100))=cast(gcfg.formkey as varchar(100)) and book.fieldName=gcfg.fieldname and gcfg.operatorid='%s' " + "where 1=1 and (nvl(book.privilegeView,'')='' or instr(','||(SELECT wm_concat(userList) FROM p_systemPrivilege pri " + "WHERE book.tab=pri.modid and instr(','||cast(pri.privTypeId as varchar(10))||',',','||book.privilegeView||',')>0)||',',',%s,')>0) ", // 注意:这里移除了末尾的括号,留空给后续条件 + disabledPart, disabled2Part, scanAblePart, userName, widthPart, CusGridColumnPrefix.BaseMainGridView, userId, userName); + + // 添加模块ID或ID条件(核心修改:将条件和排序移到子查询内) + if (!isNullOrEmpty(moduleId)) { + // 把book.tab条件和order by加到子查询的where后 + colsql += String.format("and book.tab='%s' order by book.orderId ", moduleId); + } else if (id > 0) { + // 把book.id条件加到子查询的where后 + colsql += String.format("and book.id=%d ", id); + } + + // 闭合子查询,添加rownum限制(外层只处理rownum) + colsql += ") where rownum <= 1000"; + + return colsql; + } + + public String GetRightMenuRows(String fromkey, int menutype, String username, int menuid, boolean isWindowsDirver) { + String baseSql = "select orderid, id, privilegeoper, dllname as library, action, " + "dllpar1 as param1, dllpar2 as param2, dllpar3 as param3, dllpar4 as param4, " + "dllpar5 as param5, dllpar6 as param6, dllpar7 as param7, dllpar8 as param8, " + "dllpar9 as param9, dllpar10 as param10, maxwindow, menuname as menucaption, " + "menucond, actiontype, beforemsg, ifrefresh as refresh, DBClickEvent as dbclick, " + "ifMoreClick as multi, mergeExec as [merge], showtoolbar as toBar, showMode, isCopy, " + "beforeTab, isnull(isStartRun, 0) as notRec, defailtImage as icon, " + "isnull(disabletype, 0) as disabletype " + +// "--isnull(countSql,'') countSql,isMrpClickBtn \\n " + ",isnull(hintMsg,'') desp,isnull(countSql,'') countSql,isMrpClickBtn from p_systempopupmenu where 1=1 %s %s " + "and (isnull(privilegeoper, '') = '' or charindex(',%s,', ',' + privilegeoper + ',') > 0) " + "order by orderid asc"; + + if (menuid > 0) { + // 构建查询条件,使用%s作为占位符 + String sql = String.format(baseSql, "and id='" + menuid + "'", // 第一个%s占位符 + "", // 第二个%s占位符 + username // 第三个%s占位符 + ); + // 执行查询,传入fromkey参数 + return sql; + } else { + // 构建where条件 + StringBuilder whereCond = new StringBuilder("and tab=? "); + + if (menutype == 0) { // 右键菜单 + whereCond.append("AND ISNULL([menutype],0)=0 "); + } else if (menutype == 1) { // 常用功能 + whereCond.append("AND ISNULL([menutype],0)=1 "); + } + + // 构建驱动条件 + String driverCondition = isWindowsDirver ? "and visible=0" : "and isnull(ShowMobile,0)=1"; + + // 格式化SQL,替换%s占位符 + String sql = String.format(baseSql, whereCond.toString(), // 第一个%s占位符 + driverCondition, // 第二个%s占位符 + username // 第三个%s占位符 + ); + return sql; + } + } + + @Override + public List> GetModuleIdFieldRow(String keyName, String key) { + keyName = SqlSafetyGuard.requireAllowedIdentifier(keyName, "tab", "formkey"); + return dmCrmMapper.getModuleIdFieldRow(keyName, key, SystemEnums.ControlType.LabTreeType.getValue()); + } + + @Override + public int GetAuditStepCount(String moduleId, boolean b) { + return dmCrmMapper.getAuditStepCount(moduleId, b); + } + + public List> GetCondition(String fromkey, Integer id, Boolean windowsDirver) { + return dmCrmMapper.GetCondition(fromkey, id, windowsDirver); + } + + @Override + public List> GetBillDetailColumns(String moduleCode, String userId, String username, Integer id, boolean window) { + return dmCrmMapper.GetBillDetailColumns(moduleCode, userId, username, id, window); + } + + @Override + public List> GetBillMasterRows(String moduleId, String userName, Integer id, boolean window) { + // return dmCrmMapper.GetBillMasterRows(moduleId, userName, id, window); + // 1. 拼接SQL的动态部分(对应C#的{0}和{1}) + String disabled2Part = window + ? "a.isVisible" + : "case when isnull(a.ShowMobile,0)=0 then 1 else 0 end"; + String controlWidthPart = window + ? "a.ControlWidth" + : "case when isnull(a.ControlWidth,0)=0 then 1 else a.ControlWidth end"; + + // 2. 拼接基础SQL(对应C#的colsql初始值) + String colsql = String.format( + "select a.id,FieldName,ISNULL(userName,fieldname) FieldCaption,%s ControlWidth,ControlHeight,ControlTop,ControlLeft,%s disabled2, " + + "fieldtypeid FieldType,nullable,edited Edit,lookupSql fieldsql,lookupKeyField valuemember,lookupResult displaymember,calcExpr CalcExpress, " + + "0 CalcOrderId,unionValue UnionSQL,unionFields UnionField,4 FieldDataType,a.addmoduleid,a.doNotSpelling, '' dataformat, " + + "InputHintText HintText,a.FontSize fontSize,TitleColor FontColor,limitLen LimitMaxValue,defaultvalue, a.labelWidth, a.labelAlign, " + + "a.highlightFColor fcolor,a.highlightBColor bcolor,a.highlightBold bold " + + "from p_systembillInfo a left join p_systembilltype b on a.typeCode=b.typeCode", + controlWidthPart, disabled2Part + ); + + // 3. 拼接WHERE条件和排序(严格对齐C#的if...else if逻辑) + if (id > 0) { + colsql = String.format("%s where a.id=%d", colsql, id); + } else if (moduleId != null && !moduleId.isEmpty()) { + // 注意:C#中直接拼接moduleId有SQL注入风险,Java中改为参数化(关键优化) + colsql = String.format("%s where b.typecode=? order by a.orderId", colsql); + } + + // 4. 执行SQL并处理结果 + List> resultList; + if (colsql != null && !colsql.isEmpty()) { + // 根据是否有moduleId参数,选择不同的执行方式(避免SQL注入) + if (moduleId != null && !moduleId.isEmpty() && id <= 0) { + // 有moduleId时,用参数化查询 + resultList = jdbcTemplate.queryForList(colsql, moduleId); + } else { + // 无参数或有id时,直接执行 + resultList = jdbcTemplate.queryForList(colsql); + } + // 对应C#的ToLowerColumnName():将Map的key转为小写 + List> lowerKeyList = new ArrayList<>(); + for (Map row : resultList) { + Map lowerRow = new HashMap<>(); + for (Map.Entry entry : row.entrySet()) { + lowerRow.put(entry.getKey().toLowerCase(), entry.getValue()); + } + lowerKeyList.add(lowerRow); + } + return lowerKeyList; + } + return null; + } + + @Override + public Map GetBillModule(String moduleCode, String menuId) { + return dmCrmMapper.GetBillModule(moduleCode, menuId); + } + + @Override + public List> GetControlRows(Object fromkey, String userName, String moduleId, Integer fieldId) { + return dmCrmMapper.GetControlRows(fromkey, userName, moduleId, fieldId); + } + + @Override + public List> selectSystemPopupMenuById(int menuid, String fromkey, String username) { + return dmCrmMapper.selectSystemPopupMenuById(menuid, fromkey, username); + } + + @Override + public List> selectSystemPopupMenuByType(int menutype, String fromkey, String username, boolean windowsDirver) { + return dmCrmMapper.selectSystemPopupMenuByType(menutype, fromkey, username, windowsDirver); + } + + @Override + public List> GetBillSource(String moduleCode, String stCondition) { + return dmCrmMapper.getBillSource(moduleCode, SqlSafetyGuard.requireSafeConditionFragment(stCondition)); + } + + @Override + public List> GetBillSourceColumns(String sourceId, String userId, String billSourceGridView) { + return dmCrmMapper.getBillSourceColumns(sourceId, userId, billSourceGridView); + } + + @Override + public List> GetBillSourceDetailColumns(String sourceId, String userId, String billSourceGridView) { + return dmCrmMapper.getBillSourceDetailColumns(sourceId, userId, billSourceGridView); + } + + @Override + public List> GetBaseModule(String moduleCode, String menuId) { + // return dmCrmMapper.GetBaseModule(moduleCode, menuId); + // 1. 动态拼接p_formmenuconfigtab m的关联条件(核心逻辑对齐) + String menuCond; + List params = new ArrayList<>(); // 存储参数,适配达梦参数化查询 + if (menuId == null || menuId.isEmpty() || "0".equals(menuId)) { + menuCond = "dll.DllCoid=m.UrlParams"; + } else { + menuCond = "dll.DllCoid=m.UrlParams and m.MenuId=?"; // 达梦支持?占位符 + params.add(menuId); // 菜单ID参数(达梦字符串参数无需额外转换) + } + + // 2. 拼接完整SQL(适配达梦语法,替换兼容函数/转义符) + String sql = String.format( + "select NVL(dll.bs_adddllname,'') adddllname ,NVL(dll.CsHasDefultSearch,0) defaultSearch," + + "a.column_prefix MenuPrefix,dll.formkey fromkey,condkey, " + + "case when NVL(dll.displayRows,0)=-1 then 0 else 1 end pagerflag,selfedit EditFlag," + + "NVL(m.MenuCaption,dll.ToolsName) menuname,dll.ToolsName,SQLDT1 TableName,dll.BSRowHeight rowHeight,bottomHeight, " + + "\"SQL\" TableSQL,DllCoid MenuCode,dlltype MenuType,dirId,bmpSpec FileSpeciesNo,dll.bmpType ftype," + + "NVL(isReport,'0') ReportFlag,AddCond,modifyCond UpdateCond,DeleteCond,ExportCond, dll.addmodid," + + "case when NVL(dll.displayRows,0)=-1 then 100000 else dll.displayRows end pagesize," + + "popupWidth winWidth,popupHeight winHeight, NVL(addenable,'0') AddFlag,NVL(modifyEnable,'0') SaveFlag," + + "NVL(deleteEnable,'0') DeleteFlag,NVL(importEnable,'0') ImportFlag,NVL(exportEnable,'0') ExportFlag," + + "NVL(searchEnable,'0') searchable,BackSelected,dll.tasksql,dll.countSql,dll.selectLeaf," + + "case when dll.multcheck=1 then 1 else dll.gridobjcheck end multcheck,dll.DisMobileCard,dll.appAutoSave, " + + "addCopyEnabled addCopy,dll.printflag appPrint,dll.printertype appPrintType,PrintFile,PrintType,PrintSQL," + + "PrintSQL1 PrintSQL2,PrintSQL2 PrintSQL3, LeftWidth,cp.parentWidth pWidth,cp.parentHeight pHeight," + + "cardwidth PopupWidth,m.MenuId menuid,overbacksql,overbackkey,overbackcond,OverBackOper," + + "case when NVL(m.dllfilename1,'')='' then m.DllFileName else m.dllfilename1 end dllfilename, " + + "dll.saveCaption,dll.addCaption,dll.modifyCaption,dll.delCaption,dll.applyCaption,dll.newver,dll.newWFVer," + + "dll.addHintMSG addhint,dta.attachType,dta.attachIMG,dll.detailPageAlign,dll.editType,dll.cellTplflag," + + "dll.attatchModifyCond,dll.attatchInfoWidth attInfow ,dll.bmpSpecIsMJ,dll.noGridLine,dll.noRownumber," + + "dll.noColumnHeader hideColumnHeader, NVL(dll.closeAfterModify,1) closeAfterModify," + + "NVL(dll.closeAfterAdd,1) closeAfterAdd,NVL(dll.scanMode,0) scanMode,dll.scanReadOnly," + + "dll.barSplitChar scanChar,dll.barSplitFields scanFields,dll.verifyCancel scanNoRepet,dll.verifyUnique scanUnique," + + "NVL(dll.MuitlAuditFlag,0) muitlAudit,NVL(dll.disAppFastAudit,0) disAppFastAudit," + + "NVL(dll.disableDetail,0) disableDetail,dll.attcField1,dll.attcField2,dll.attcField3, " + + "dll.apiDataNode,dll.apiSuccNode,dll.apiSuccVal,NVL(dll.hintMsg,'') desp," + + "NVL(dll.noDataMsg,'') noDataMsg,dll.RefreshTheInterface refreshall " + + "from p_systemdlltab dll " + + "left join p_formmenuconfigtab m on %s " + // 动态关联条件 + "left join (select table_name,column_prefix from p_systemtables group by table_name, column_prefix) a on dll.SQLDT1=a.table_name " + + "left join p_systemControlParent cp on cp.formkey=dll.formkey " + + "left join p_SystemdllTabAttach dta on ?=dta.unionModule " + // moduleCode参数化 + "where dll.DllCoid=? or CAST(dll.formkey AS VARCHAR(50))=?", // 达梦推荐CAST替代CONVERT + menuCond // 填充动态关联条件 + ); + + // 3. 添加moduleCode参数(达梦参数顺序:menuId → moduleCode → moduleCode) + params.add(moduleCode); // 对应dta.unionModule=? + params.add(moduleCode); // 对应dll.DllCoid=? + params.add(moduleCode); // 对应CAST(dll.formkey AS VARCHAR(50))=? + + // 4. 执行查询(达梦JdbcTemplate兼容) + List> resultList = jdbcTemplate.queryForList(sql, params.toArray()); + + // 5. 字段名转小写(对齐C#的ToLowerColumnName()习惯) + List> lowerKeyList = new ArrayList<>(); + for (Map row : resultList) { + Map lowerRow = new HashMap<>(); + for (Map.Entry entry : row.entrySet()) { + lowerRow.put(entry.getKey().toLowerCase(), entry.getValue()); + } + lowerKeyList.add(lowerRow); + } + + return lowerKeyList; + } + + @Override + public String GetAuditMsgTab(String userId) { + // 拼接用户条件(注意达梦字符串拼接用||,但此处是Java拼接SQL,不影响) + String userCond1 = (userId == null || userId.isEmpty()) ? "" : String.format("AND a.UserId = '%s'", userId); + String userCond2 = (userId == null || userId.isEmpty()) ? "" : String.format("AND NoticeUserID = '%s'", userId); + +// 构建适配达梦的完整SQL(替换LIMIT为达梦支持的FETCH FIRST) + String sql = String.format(""" + SELECT * FROM ( + SELECT * FROM ( + SELECT DISTINCT + '['||b.MenuCaption||']'||'=> '||COALESCE(a.billdocument_id,'')||msg AS AuditMessages, + a.UserId, + a.Created AS OperateDate, + a.id, + a.messid, + a.DLLCoid moduleid, + Cnt1 num, + CASE WHEN COALESCE(a.DllFileName,'')<>'' THEN a.DllFileName + WHEN COALESCE(b.dllfilename1,'')='' THEN b.DllFileName + ELSE b.dllfilename1 + END dllname, + b.MenuCaption modulename, + billdocument_id keyvalue, + stepcode, + COALESCE(MenuMode_Mobile,0) menumode, + b.menuid, + CASE WHEN COALESCE(c.DllCoid,'')='' + THEN COALESCE(d.defaultShowSearch,0) + ELSE COALESCE(c.defaultShowSearch,0) + END hasSearch, + 1 tablecode, + a.templetename tplname + FROM p_systemNotification a + INNER JOIN P_FormMenuConfigTab b ON COALESCE(a.menuid,0)=b.MenuId + LEFT JOIN p_systemdlltab c ON b.UrlParams = c.DllCoid + LEFT JOIN p_systembilltype d ON b.UrlParams = d.typeCode + WHERE COALESCE(a.billdocument_id,'')<>'' + AND COALESCE(a.status,0)=0 + AND COALESCE(a.Cnt1,0)<>0 + %s + ORDER BY a.Created DESC + FETCH FIRST 100 ROWS ONLY -- 达梦替换LIMIT 100 + ) t1 + UNION ALL + SELECT * FROM ( + SELECT + Msg, + NoticeUserID, + CreateDate, + id, + 1 AS messid, -- 补充别名,与t1字段对齐 + DllCoid, + 0 AS num, -- 补充别名,与t1字段对齐 + '' AS dllname,-- 补充别名,与t1字段对齐 + Title AS modulename, + MsgDetail AS keyvalue, + 0 AS stepcode, + 0 AS menumode, + 0 AS menuid, + 0 AS hasSearch, + 2 AS tablecode, + templetename AS tplname + FROM p_systemMessageTab + WHERE DeleteFlag=0 + %s + ORDER BY CreateDate DESC + FETCH FIRST 100 ROWS ONLY -- 达梦替换LIMIT 100 + ) t2 + ) cc + ORDER BY OperateDate DESC + FETCH FIRST 100 ROWS ONLY; -- 达梦替换LIMIT 100 + """, userCond1, userCond2); + return sql; + } + + @Override + public String GetDeskTopCommonUse(int cardId, String userId) { + // 适配达梦的SQL拼接逻辑 + String sql; + if (cardId < 0) { + if (cardId == -99) { + // cardId=-99的适配版本 + sql = "SELECT pmt.objdll dllfilename," + "CASE WHEN INSTR(pmt.ObjDll, 'www') > 0 THEN pmt.objdll ELSE '#' END href," + "pmt.id, pmt.DllShowCaption text,pmt.LMenuid menuid,pmt.Lsubsysid subsysid, " + "pmt.dllcoid dllcoid,'' dlltype, 0 needcount ,0 serverId " + "FROM P_MessageToolLinkDllTab pmt " + "WHERE pmt.cardId='-99' AND ISNULL(objdll,'')<>'' AND ISNULL(dllcoid,'')<>'' "; + } else { + // cardId<0且≠-99的适配版本 + sql = String.format("SELECT * FROM ( " + "SELECT DISTINCT pfmc.serverId, " + "CASE WHEN ISNULL(pfmc.dllfilename1,'')='' THEN pfmc.dllfilename ELSE pfmc.dllfilename1 END dllfilename," + "CASE WHEN INSTR(pmt.ObjDll, 'www') > 0 THEN pmt.objdll ELSE '#' END href, " + "pmt.DllShowCaption text,pmt.LMenuid menuid,pmt.Lsubsysid subsysid, " + "ISNULL(pfmc.UrlParams,ISNULL(psdt.dllcoid,psdtp.typeCode)) dllcoid,psdt.dlltype, " + "CASE WHEN ISNULL(CAST(psdt.countsql AS VARCHAR(1000)),ISNULL(CAST(psdtp.countsql AS VARCHAR(1000)),''))='' THEN 0 ELSE 1 END needcount " + "FROM P_MessageToolLinkDllTab pmt " + "--inner join tmenu lm on lm.menuid=pmt.lmenuid " + "INNER JOIN p_formmenuconfigtab pfmc ON pmt.Lmenuid=pfmc.menuid " + "LEFT JOIN p_systemdlltab psdt ON pfmc.urlparams=psdt.dllcoid AND ISNULL(pfmc.UrlParams,'') !='' " + "LEFT JOIN p_systembilltype psdtp ON pfmc.urlparams=psdtp.typeCode AND ISNULL(pfmc.UrlParams,'') !='' " + "WHERE EmployeeID='%s' AND pmt.cardId='%d' AND (ISNULL(pfmc.targetmode,0)=0 OR pfmc.targetmode=3) " + ") a WHERE ISNULL(dllfilename,'')<>'' AND ISNULL(dllcoid,'')<>'' ", userId, cardId); + } + } else { + // cardId≥0的适配版本(含CTE) + sql = String.format("WITH tmenu(id,MenuCaption,ParentId,menuid,SubSysId,level) " + "AS " + "( " + " SELECT CAST(menu.SubSysId AS VARCHAR(50))||'_'|| MenuStruct id," + " MenuCaption," + " CAST(-CAST(menu.SubSysId AS INT) AS VARCHAR(50)) ParentId," + " menu.menuid,menu.SubSysId,1 level " + " FROM p_formmenuconfigtab menu " + " INNER JOIN P_SubSystemTab sub ON menu.SubSysId=sub.SubSysId AND ISNULL(UseEd,0)=1 AND ISNULL(visible,0)=0 " + " WHERE LENGTH(menustruct)=2 AND ISNULL(menu.SeriesId, 1)=1 AND ISNULL(menu.UseFlag,1)=1 " + " UNION ALL " + " SELECT CAST(a.SubSysId AS VARCHAR(50))||'_'||A.MenuStruct id, " + " A.MenuCaption, " + " CAST(a.SubSysId AS VARCHAR(50))||'_'|| SUBSTR(a.MenuStruct,1,CASE WHEN LENGTH(a.MenuStruct)>2 THEN LENGTH(a.MenuStruct)-2 ELSE 0 END) ParentId," + " a.MenuId,a.SubSysId ,b.level+1 " + " FROM p_formmenuconfigtab A,tmenu b " + " WHERE LENGTH(a.MenuStruct)>2 AND ISNULL(UseFlag,1)=1 " + " AND CAST(a.SubSysId AS VARCHAR(50))||'_'|| SUBSTR(a.MenuStruct,1,CASE WHEN LENGTH(a.MenuStruct)>2 THEN LENGTH(a.MenuStruct)-2 ELSE 0 END) = b.id " + " AND a.SubSysId=b.SubSysId " + ") " + "SELECT * FROM ( " + " SELECT pfmc.serverId, " + " CASE WHEN ISNULL(pfmc.dllfilename1,'')='' THEN pfmc.dllfilename ELSE pfmc.dllfilename1 END dllfilename," + " CASE WHEN INSTR(pmt.ObjDll, 'www')>0 THEN pmt.objdll ELSE '#' END href,pmt.id, " + " pmt.DllShowCaption text,pmt.LMenuid menuid,pmt.Lsubsysid subsysid, " + " ISNULL(pfmc.UrlParams,ISNULL(psdt.dllcoid,psdtp.typeCode)) dllcoid,psdt.dlltype, " + " CASE WHEN ISNULL(CAST(psdt.countsql AS VARCHAR(1000)),ISNULL(CAST(psdtp.countsql AS VARCHAR(1000)),''))='' THEN 0 ELSE 1 END needcount " + " FROM P_MessageToolLinkDllTab pmt " + " INNER JOIN tmenu lm ON lm.menuid=pmt.lmenuid " + " INNER JOIN p_formmenuconfigtab pfmc ON pmt.Lmenuid=pfmc.menuid " + " LEFT JOIN p_systemdlltab psdt ON pfmc.urlparams=psdt.dllcoid AND ISNULL(pfmc.UrlParams,'') !='' " + " LEFT JOIN p_systembilltype psdtp ON pfmc.urlparams=psdtp.typeCode AND ISNULL(pfmc.UrlParams,'') !='' " + " WHERE EmployeeID='%s' AND pmt.cardId='%d' AND (ISNULL(pfmc.targetmode,0)=0 OR pfmc.targetmode=3) " + ") a WHERE ISNULL(dllfilename,'')<>'' AND ISNULL(dllcoid,'')<>'' ", userId, cardId); + } + return sql; + } + + @Override + public String GetDeskQueryResult(String queryText, boolean exitTable) { + // 步骤1:先判断p_systemSearchTextTab表是否存在(达梦语法) + String checkTableSql = "SELECT COUNT(1) FROM ALL_TABLES WHERE TABLE_NAME = 'P_SYSTEMSEARCHTEXTTAB'"; + // 步骤2:根据表存在性拼接对应SQL + String sql; + if (exitTable) { + // 表存在的适配SQL + sql = "WITH tmenu(id,ParentId,menuid,SubSysId,level) " + "AS " + "( " + " SELECT CAST(menu.SubSysId AS VARCHAR(50))||'_'|| MenuStruct id," + " CAST(-CAST(menu.SubSysId AS INT) AS VARCHAR(50)) ParentId," + " menu.menuid,menu.SubSysId,1 level " + " FROM p_formmenuconfigtab menu " + " INNER JOIN P_SubSystemTab sub ON menu.SubSysId=sub.SubSysId AND ISNULL(UseEd,0)=1 AND ISNULL(visible,0)=0 " + " WHERE ISNULL(UseFlag,1)=1 AND LENGTH(menustruct)=2 " + " UNION ALL " + " SELECT CAST(a.SubSysId AS VARCHAR(50))||'_'||A.MenuStruct id," + " CAST(a.SubSysId AS VARCHAR(50))||'_'|| SUBSTR(a.MenuStruct,1,CASE WHEN LENGTH(a.MenuStruct)>2 THEN LENGTH(a.MenuStruct)-2 ELSE 0 END) ParentId," + " a.MenuId,a.SubSysId ,b.level+1 " + " FROM p_formmenuconfigtab A,tmenu b " + " WHERE LENGTH(a.MenuStruct)>2 AND ISNULL(UseFlag,1)=1 " + " AND CAST(a.SubSysId AS VARCHAR(50))||'_'|| SUBSTR(a.MenuStruct,1,CASE WHEN LENGTH(a.MenuStruct)>2 THEN LENGTH(a.MenuStruct)-2 ELSE 0 END) = b.id " + " AND a.SubSysId=b.SubSysId " + ") " + "SELECT pm.menuid id,'SYSTEM_MENU' keyvalue, " + "urlparams src_modid,menuCaption src_modtitle,urlparams target_modid,menuCaption target_modtitle," + "CASE WHEN ISNULL(dllfilename1,'')='' THEN DllFileName ELSE dllfilename1 END target_dllname," + "'MENU' key_fieldname,'功能模块' key_fieldcnname, menuCaption key_context," + "SYSDATE operatedate,'管理员' operatorname " + "FROM p_formmenuconfigtab pm " + "INNER JOIN tmenu tm ON pm.menuid=tm.menuid " + "INNER JOIN P_SubSystemTab s ON pm.subsysid=s.subsysid AND s.useed=1 AND ISNULL(s.visible,0)=0 " + "INNER JOIN v_systemdlltab v ON pm.urlparams=v.dllcoid " + "WHERE ISNULL(pm.urlparams,'')<>'' AND (ISNULL(pm.dllfilename1,'')<>'' OR ISNULL(pm.dllfilename,'')<>'') " + "AND (ISNULL(pm.targetmode,0)=0 OR pm.targetmode=3) AND ISNULL(pm.useFlag,1)=1 " + "AND (pm.urlparams LIKE ('%'||?||'%') OR pm.menuCaption LIKE ('%'||?||'%') OR P_GetPy(pm.menuCaption) LIKE ('%'||?||'%')) " + "UNION ALL " + "SELECT * FROM ( " + " SELECT * FROM p_systemSearchTextTab WHERE keyvalue='SYSTEM_MENU' " + " AND ( key_context LIKE ('%'||?||'%') OR P_GetPy(key_context) LIKE ('%'||?||'%') " + " OR target_modid LIKE ('%'||?||'%') OR src_modid LIKE ('%'||?||'%') ) " + " ORDER BY 1 " + // 需指定排序字段(如id),达梦FETCH FIRST必须配合ORDER BY + " FETCH FIRST 50 ROWS ONLY " + // 替换TOP 50 + ") t"; + } else { + // 表不存在的适配SQL + sql = "WITH tmenu(id,ParentId,menuid,SubSysId,level) " + "AS " + "( " + " SELECT CAST(menu.SubSysId AS VARCHAR(50))||'_'|| MenuStruct id," + " CAST(-CAST(menu.SubSysId AS INT) AS VARCHAR(50)) ParentId," + " menu.menuid,menu.SubSysId,1 level " + " FROM p_formmenuconfigtab menu " + " INNER JOIN P_SubSystemTab sub ON menu.SubSysId=sub.SubSysId AND ISNULL(UseEd,0)=1 AND ISNULL(visible,0)=0 " + " WHERE ISNULL(UseFlag,1)=1 AND LENGTH(menustruct)=2 " + " UNION ALL " + " SELECT CAST(a.SubSysId AS VARCHAR(50))||'_'||A.MenuStruct id," + " CAST(a.SubSysId AS VARCHAR(50))||'_'|| SUBSTR(a.MenuStruct,1,CASE WHEN LENGTH(a.MenuStruct)>2 THEN LENGTH(a.MenuStruct)-2 ELSE 0 END) ParentId," + " a.MenuId,a.SubSysId ,b.level+1 " + " FROM p_formmenuconfigtab A,tmenu b " + " WHERE LENGTH(a.MenuStruct)>2 AND ISNULL(UseFlag,1)=1 " + " AND CAST(a.SubSysId AS VARCHAR(50))||'_'|| SUBSTR(a.MenuStruct,1,CASE WHEN LENGTH(a.MenuStruct)>2 THEN LENGTH(a.MenuStruct)-2 ELSE 0 END) = b.id " + " AND a.SubSysId=b.SubSysId " + ") " + "SELECT pm.menuid id,'SYSTEM_MENU' keyvalue, " + "urlparams src_modid,menuCaption src_modtitle,urlparams target_modid,menuCaption target_modtitle," + "CASE WHEN ISNULL(dllfilename1,'')='' THEN DllFileName ELSE dllfilename1 END target_dllname," + "'MENU' key_fieldname,'功能模块' key_fieldcnname, menuCaption key_context," + "SYSDATE operatedate,'管理员' operatorname " + "FROM p_formmenuconfigtab pm " + "INNER JOIN tmenu tm ON pm.menuid=tm.menuid " + "INNER JOIN P_SubSystemTab s ON pm.subsysid=s.subsysid AND s.useed=1 AND ISNULL(s.visible,0)=0 " + "WHERE ISNULL(pm.urlparams,'')<>'' AND (ISNULL(pm.dllfilename1,'')<>'' OR ISNULL(pm.dllfilename,'')<>'') " + "AND (ISNULL(pm.targetmode,0)=0 OR pm.targetmode=3) AND ISNULL(pm.useFlag,1)=1 " + "AND (pm.urlparams LIKE ('%'||?||'%') OR pm.menuCaption LIKE ('%'||?||'%') OR P_GetPy(pm.menuCaption) LIKE ('%'||?||'%'))"; + } + return sql; + } + + @Override + public String GetDesktopModuleMain(String userId, String userName, String dllcoid) { + String sql = "SELECT " + "a.id, " + "a.dllcoid, " + "b.itemCode, " + "ISNULL(a.itemRowNo, b.itemRowNo) itemRowNo, " + "ISNULL(a.itemOrder, b.itemOrder) itemOrder, " + "ISNULL(a.itemWidth, b.itemWidth) itemWidth, " + "ISNULL(a.itemHeight, b.itemHeight) itemHeight, " + "a.itemLeft, " + "a.itemTop, " + "CASE WHEN ISNULL(a.deleted, 0) = 1 THEN 1 ELSE ISNULL(a.enableFlag, 0) END enableFlag, " + "b.itemTitle, " + "b.itemTypeFull, " + "a.queryField, " + "a.condition " + "FROM P_SystemFirstPageSetTab b " + "LEFT JOIN P_SystemDllFirstPageTab a ON a.itemCode = b.itemCode AND a.dllcoid = ? " + "WHERE b.enableType IN (-1, 2) " + "AND (ISNULL(b.itemPrivilege, '') = '' OR INSTR(',' || b.itemPrivilege || ',', ',' || ? || ',') > 0)"; + return sql; + } + + @Override + public String CheckIsAudit() { + return "SELECT DATA_LENGTH AS col_length" + "FROM ALL_TAB_COLUMNS" + "WHERE " + " TABLE_NAME = 'P_SYSTEMCHECKTAB' " + " AND COLUMN_NAME = 'STEOVER';"; + } + + @Override + public String GetProSysType() { + return "SELECT CASE WHEN EXISTS (SELECT 1 FROM SYSOBJECTS WHERE ID = OBJECT_ID('P_SYSTEMPRODUCTSERIESTAB') AND TYPE$ = 'U') THEN 1 ELSE 0 END AS TABLE_EXISTS"; + } + + @Override + public String GetPrimaryKeysArray(String tabname) { + String primaryKeysSql = String.format("SELECT C.COLUMN_NAME " + "FROM ALL_CONSTRAINTS T " + "JOIN ALL_CONS_COLUMNS C ON T.CONSTRAINT_NAME = C.CONSTRAINT_NAME " + "WHERE " + " T.TABLE_NAME = UPPER('%s') " + " --AND T.CONSTRAINT_TYPE = 'U' " + "ORDER BY C.POSITION; ", // P = PRIMARY KEY + tabname); + return primaryKeysSql; + } + + @Override + public String GetUsers(String name, String code, int userId) { + // 替换原 sysobjects 判断逻辑为达梦兼容版本 + name = name.replace("'", "''"); + code = code.replace("'", "''"); + String sql = "DECLARE " + " v_name VARCHAR(100) := '" + name + "'; -- 替换为实际值:管理员 " + " v_code VARCHAR(100) := '" + code + "'; -- 替换为实际值:0 " + " v_userId VARCHAR(10) := '" + userId + "'; -- 原userId=001为字符串型,调整类型避免隐式转换 " + " v_sql VARCHAR(4000); " + "BEGIN " + " -- 检查视图是否存在(当前用户下) " + " IF NOT EXISTS ( " + " SELECT 1 " + " FROM ALL_VIEWS " + " WHERE OWNER = SYS_CONTEXT('USERENV','CURRENT_SCHEMA') " + " AND VIEW_NAME = 'P_EMPLOYEEBASEVIEW' " + " ) THEN " + " -- 1. 视图不存在:创建静态视图(仅保留固定筛选条件) " + " v_sql := 'CREATE VIEW P_employeeBaseView AS " + " SELECT employeeid \"员工ID\", " + " loginaccount \"员工工号\", " + " employeename \"员工姓名\", " + " departmentname \"所属部门\", " + " speciesname \"所属类别\" " + " FROM p_employeetab a " + " LEFT JOIN P_DepartmentTab b ON a.departmentid = b.departmentid " + " LEFT JOIN P_EmployeeSpecTab c ON a.speciesno = c.speciesno " + " WHERE NVL(a.sign,0)=0 AND NVL(useflag,0)=1'; " + " EXECUTE IMMEDIATE v_sql; " + " " + " -- 创建后拼接动态查询条件 " + " v_sql := 'SELECT * FROM P_employeeBaseView WHERE 1=1 " + " AND (\"员工姓名\" LIKE ''%' || v_name || '%'' OR P_GetPy(\"员工姓名\") LIKE ''%' || v_name || '%'') " + " AND \"员工工号\" LIKE ''%' || v_code || '%'' " + " AND (''' || v_userId || ''' = ''0'' OR \"员工ID\" = ''' || v_userId || ''')'; " + " ELSE " + " -- 2. 视图已存在:直接拼接动态查询条件 " + " v_sql := 'SELECT * FROM P_employeeBaseView WHERE 1=1 " + " AND (\"员工姓名\" LIKE ''%' || v_name || '%'' OR P_GetPy(\"员工姓名\") LIKE ''%' || v_name || '%'') " + " AND \"员工工号\" LIKE ''%' || v_code || '%'' " + " AND (''' || v_userId || ''' = ''0'' OR \"员工ID\" = ''' || v_userId || ''')'; " + " END IF; " + " " + " -- 执行最终查询(注:若需返回结果,需结合游标/存储过程输出参数) " + " EXECUTE IMMEDIATE v_sql; " + "END;"; + return sql; + } + + @Override + public String hasExistsTable(String tableName) { + return "SELECT COUNT(1) FROM DBA_TABLES " + "WHERE TABLE_NAME = '" + tableName + "'"; + } + + @Override + public String SeeOneAuditMsg() { + return "-- 声明变量(达梦PL/SQL规范) " + "DECLARE " + " v_type VARCHAR(200); -- 存储列类型 " + " v_constraintName VARCHAR(200);-- 存储默认约束名 " + " v_dropSql VARCHAR(500); -- 存储删除约束的动态SQL " + " v_owner VARCHAR(100); -- 表所属用户(模式名) " + " v_search_condition CLOB; -- 存储约束条件(适配低版本达梦的CLOB类型) " + " v_insert_sql VARCHAR(1000); -- 动态插入SQL(解决模式名拼接问题) " + "BEGIN " + " -- 获取当前用户(模式名) " + " SELECT SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') INTO v_owner FROM DUAL; " + " " + " -- 1. 校验表和列是否存在(避免后续查询无意义) " + " DECLARE " + " v_col_exists INT := 0; " + " BEGIN " + " SELECT 1 INTO v_col_exists " + " FROM USER_TAB_COLUMNS " + " WHERE TABLE_NAME = 'P_SYSTEMNOTICESHEET' " + " AND COLUMN_NAME = 'CREATED'; " + " EXCEPTION " + " WHEN NO_DATA_FOUND THEN " + " RAISE_APPLICATION_ERROR(-20001, '表P_SYSTEMNOTICESHEET或列CREATED不存在!'); " + " END; " + " " + " -- 2. 查询Created列的类型 " + " SELECT DATA_TYPE INTO v_type " + " FROM USER_TAB_COLUMNS " + " WHERE TABLE_NAME = 'P_SYSTEMNOTICESHEET' " + " AND COLUMN_NAME = 'CREATED'; " + " " + " -- 3. 若类型不是datetime,修改列类型(先删默认约束) " + " IF v_type <> 'DATETIME' THEN " + " -- 3.1 查询Created列的默认约束名(适配所有达梦版本) " + " BEGIN " + " SELECT a.CONSTRAINT_NAME, a.SEARCH_CONDITION " + " INTO v_constraintName, v_search_condition " + " FROM USER_CONSTRAINTS a " + " INNER JOIN USER_CONS_COLUMNS b " + " ON a.OWNER = b.OWNER " + " AND a.CONSTRAINT_NAME = b.CONSTRAINT_NAME " + " WHERE a.OWNER = v_owner " + " AND a.TABLE_NAME = 'P_SYSTEMNOTICESHEET' " + " AND b.COLUMN_NAME = 'CREATED' " + " AND a.CONSTRAINT_TYPE = 'C'; -- 达梦默认约束归类为CHECK约束 " + " " + " -- 筛选包含DEFAULT的默认约束(适配CLOB类型的模糊查询) " + " IF v_search_condition NOT LIKE '%DEFAULT%' THEN " + " v_constraintName := NULL; " + " END IF; " + " EXCEPTION " + " WHEN NO_DATA_FOUND THEN " + " v_constraintName := NULL; -- 无默认约束时置空 " + " END; " + " " + " -- 3.2 删除默认约束(动态SQL) " + " IF v_constraintName IS NOT NULL AND TRIM(v_constraintName) <> '' THEN " + " v_dropSql := 'ALTER TABLE \"' || v_owner || '\".\"P_SYSTEMNOTICESHEET\" DROP CONSTRAINT \"' || v_constraintName || '\"'; " + " EXECUTE IMMEDIATE v_dropSql; " + " END IF; " + " " + " -- 3.3 修改列类型为DATETIME(动态SQL避免模式名问题) " + " v_dropSql := 'ALTER TABLE \"' || v_owner || '\".\"P_SYSTEMNOTICESHEET\" ALTER COLUMN \"CREATED\" DATETIME NULL'; " + " EXECUTE IMMEDIATE v_dropSql; " + " END IF; " + " " + " -- 4. 插入数据:留出参数占位符(外部格式化时替换{0}/{1}/{2}) " + " v_insert_sql := 'INSERT INTO \"' || v_owner || '\".\"P_SYSTEMNOTICESHEET\" (MSG, CREATED, USERID) " + " VALUES (''{0}'', ''{1}'', ''{2}'')'; -- 占位符标记 " + " EXECUTE IMMEDIATE v_insert_sql; " + " " + " COMMIT; -- 提交事务 " + "EXCEPTION " + " WHEN OTHERS THEN " + " DBMS_OUTPUT.PUT_LINE('执行失败:' || SQLERRM || '(错误码:' || SQLCODE || ')'); " + " ROLLBACK; -- 异常回滚 " + "END; " + "/"; + } + + // endregion + +// region UpdateImpl + + @Override + public String updateSystemOtherTabSql() { + + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append(String.format("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS " + "WHERE TABLE_NAME = '%s' AND COLUMN_NAME = 'SUMCOND' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');" + "IF v_col_exists = 0 THEN EXECUTE IMMEDIATE 'ALTER TABLE %s ADD sumCond VARCHAR(500)';END IF;END;", "p_systemdlltabdetailgrid", "p_systemdlltabdetailgrid")); + + sqlBuilder.append(String.format("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS " + "WHERE TABLE_NAME = '%s' AND COLUMN_NAME = 'SUMCOND' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');" + "IF v_col_exists = 0 THEN EXECUTE IMMEDIATE 'ALTER TABLE %s ADD sumCond VARCHAR(500)';END IF;END;", "p_systembilldetail", "p_systembilldetail")); + + sqlBuilder.append(String.format("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS " + "WHERE TABLE_NAME = '%s' AND COLUMN_NAME = 'SUMCOND' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');" + "IF v_col_exists = 0 THEN EXECUTE IMMEDIATE 'ALTER TABLE %s ADD sumCond VARCHAR(500)';END IF;END;", "p_systembillauditAttachDetail", "p_systembillauditAttachDetail")); + + sqlBuilder.append(String.format("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS" + " WHERE TABLE_NAME = '%s' AND COLUMN_NAME = 'dllcoid'" + " AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');" + "IF v_col_exists = 0 THEN EXECUTE IMMEDIATE 'ALTER TABLE %s ADD dllcoid VARCHAR(100)';END IF;END;", "P_fm_DirectoryTab", "P_fm_DirectoryTab")); + + sqlBuilder.append(String.format("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS" + " WHERE TABLE_NAME = '%s' AND COLUMN_NAME = 'dllcoid'" + " AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');" + "IF v_col_exists = 0 THEN EXECUTE IMMEDIATE 'ALTER TABLE %s ADD dllcoid VARCHAR(100)';END IF;END;", "p_fm_filetab", "p_fm_filetab")); + + return sqlBuilder.toString(); + } + + @Override + public String updateBillTab_210525Sql() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append(String.format("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS" + " WHERE TABLE_NAME = '%s' AND COLUMN_NAME = 'dllcoid'" + " AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');" + "IF v_col_exists = 0 THEN EXECUTE IMMEDIATE 'ALTER TABLE %s ADD dllcoid VARCHAR(100)';END IF;END;", "p_fm_filetab", "p_fm_filetab")); + return sqlBuilder.toString(); + } + + @Override + public String updateBillTab_211022Sql() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append(String.format("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS " + "WHERE TABLE_NAME = '%s' AND COLUMN_NAME = 'bandTitle' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');" + "IF v_col_exists = 0 THEN EXECUTE IMMEDIATE 'ALTER TABLE %s ADD bandTitle VARCHAR(500)';" + "END IF;" + "END;DECLARE v_col_exists NUMBER;" + "BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = '%s' " + "AND COLUMN_NAME = 'bandFields' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');" + "IF v_col_exists = 0 THEN EXECUTE IMMEDIATE 'ALTER TABLE %s ADD bandFields VARCHAR(8000)';END IF;END;", "p_systembilldetail", "p_systembilldetail", "p_systembilldetail", "p_systembilldetail")); + return sqlBuilder.toString(); + } + + @Override + public String updateSysTabSql() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists " + "FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = 'p_SystemTab' AND COLUMN_NAME = 'clientname' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_SystemTab ADD clientname VARCHAR(100);';END IF;END;"); + sqlBuilder.append("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists " + "FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = 'p_SystemTab' AND COLUMN_NAME = 'clientenname' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_SystemTab ADD clientenname VARCHAR(200);';END IF;END;"); + sqlBuilder.append("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists " + "FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = 'p_SystemTab' AND COLUMN_NAME = 'copyright' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_SystemTab ADD copyright VARCHAR(100);';END IF;END;"); + sqlBuilder.append("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists " + "FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = 'p_SystemTab' AND COLUMN_NAME = 'clientlogname' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_SystemTab ADD clientlogname VARCHAR(200);';END IF;END;"); + sqlBuilder.append("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists " + "FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = 'p_SystemTab' AND COLUMN_NAME = 'webclientname' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_SystemTab ADD webclientname VARCHAR(100);';END IF;END;"); + sqlBuilder.append("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists " + "FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = 'p_SystemTab' AND COLUMN_NAME = 'webclientenname' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_SystemTab ADD webclientenname VARCHAR(200);';END IF;END;"); + sqlBuilder.append("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists " + "FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = 'p_SystemTab' AND COLUMN_NAME = 'serverattachpath' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_SystemTab ADD serverattachpath VARCHAR(2000);';END IF;END;"); + sqlBuilder.append("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists " + "FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = 'p_SystemTab' AND COLUMN_NAME = 'localOAUrl' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_SystemTab ADD localOAUrl VARCHAR(2000);';END IF;END;"); + return ""; + } + + @Override + public String updateSysTab_210630Sql() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists " + "FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = 'p_SystemTab' AND COLUMN_NAME = 'downloadAddress' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_SystemTab ADD downloadAddress VARCHAR(1000);';END IF;END;"); + return sqlBuilder.toString(); + } + + @Override + public String updateProductSpeciesTabSql() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS " + "WHERE TABLE_NAME = 'bmp_ProductSpeciesTab' AND COLUMN_NAME = 'uploadOper' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE bmp_ProductSpeciesTab ADD uploadOper VARCHAR(2000);';END IF;END;"); + sqlBuilder.append("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS " + "WHERE TABLE_NAME = 'bmp_ProductSpeciesTab' AND COLUMN_NAME = 'downloadOper' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE bmp_ProductSpeciesTab ADD downloadOper VARCHAR(2000);';END IF;END;"); + sqlBuilder.append("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS " + "WHERE TABLE_NAME = 'bmp_ProductSpeciesTab' AND COLUMN_NAME = 'deleteOper' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE bmp_ProductSpeciesTab ADD deleteOper VARCHAR(2000);';END IF;END;"); + sqlBuilder.append("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS " + "WHERE TABLE_NAME = 'bmp_ProductSpeciesTab' AND COLUMN_NAME = 'previewOper' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE bmp_ProductSpeciesTab ADD previewOper VARCHAR(2000);';END IF;END;"); + return sqlBuilder.toString(); + } + + @Override + public String updateSysMenuTabSql() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS " + "WHERE TABLE_NAME = 'p_formmenuconfigtab' AND COLUMN_NAME = 'dllfilename1' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE P_FormMenuConfigTab ADD DllFileName1 VARCHAR(100);" + "';END IF;END;DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists " + "FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = 'p_formmenuconfigtab' AND COLUMN_NAME = 'TargetMode' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE P_FormMenuConfigTab ADD TargetMode INT DEFAULT(0);" + "';END IF;END;DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists " + "FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = 'p_formmenuconfigtab' AND COLUMN_NAME = 'GroupCaption' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE P_FormMenuConfigTab ADD GroupCaption VARCHAR(50);" + "';END IF;END;DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists " + "FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = 'p_formmenuconfigtab' AND COLUMN_NAME = 'SeriesId' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE P_FormMenuConfigTab ADD SeriesId INT;';END IF;END;"); + return sqlBuilder.toString(); + } + + @Override + public String updateSubSysTabSql() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists " + "FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = 'P_SubSystemTab' AND COLUMN_NAME = 'subsystip' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE P_SubSystemTab ADD subsystip VARCHAR(500);';END IF;END;"); + sqlBuilder.append("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists " + "FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = 'P_SubSystemTab' AND COLUMN_NAME = 'visible' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE P_SubSystemTab ADD visible INT DEFAULT 0;';END IF;END;"); + sqlBuilder.append("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists " + "FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = 'P_SubSystemTab' AND COLUMN_NAME = 'webshow' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE P_SubSystemTab ADD webshow BIGINT DEFAULT 1;';END IF;END;"); + return sqlBuilder.toString(); + } + + @Override + public String updateBillTypeTabSql() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists " + "FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = 'p_systembilltype' AND COLUMN_NAME = 'overbacksql' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_systembilltype" + " ADD overbacksql VARCHAR(3000),overbackkey CHAR(36);" + "';END IF;END;DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists " + "FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = 'p_systembilltype' AND COLUMN_NAME = 'PopupUnionCode' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_systembilltype ADD PopupUnionCode VARCHAR(50) NULL;" + "';END IF;END;DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists " + "FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = 'p_systembilltype' AND COLUMN_NAME = 'defaultShowSearch' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_systembilltype ADD defaultShowSearch INT NULL;" + "';END IF;END;DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists " + "FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = 'p_systembilltype' AND COLUMN_NAME = 'MuitlAuditFlag' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_systembilltype ADD MuitlAuditFlag INT NULL;" + "';END IF;END;DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists " + "FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = 'p_systembilltype' AND COLUMN_NAME = 'bs_adddllname' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_systembilltype ADD bs_adddllname VARCHAR(100) NULL;" + "';END IF;END;DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists " + "FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = 'p_systembilltype' AND COLUMN_NAME = 'BackSelected' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_systembilltype ADD BackSelected BIGINT NULL;';END IF;END;"); + return ""; + } + + @Override + public String updateBillTypeTab_210901Sql() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists " + "FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = 'p_systembilltype' AND COLUMN_NAME = 'countSql ' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_systembilltype ADD countSql varchar(max) NULL;" + "';END IF;END;"); + return ""; + } + + @Override + public String updateDllTabSql() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("DECLARE v_col_exists NUMBER;" + "BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS " + "WHERE TABLE_NAME = 'p_systemdlltab' AND COLUMN_NAME = 'LocationImg' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_systemdlltab ADD LocationImg VARCHAR(100);';END IF;END;"); + sqlBuilder.append("DECLARE v_col_exists NUMBER;" + "BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS " + "WHERE TABLE_NAME = 'p_systemdlltab' AND COLUMN_NAME = 'addcaption' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_systemdlltab ADD addcaption VARCHAR(100);" + "';END IF;END;"); + sqlBuilder.append("DECLARE v_col_exists NUMBER;" + "BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS " + "WHERE TABLE_NAME = 'p_systemdlltab' AND COLUMN_NAME = 'modifycaption' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_systemdlltab ADD modifycaption VARCHAR(200);" + "';END IF;END;"); + sqlBuilder.append("DECLARE v_col_exists NUMBER;" + "BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS " + "WHERE TABLE_NAME = 'p_systemdlltab' AND COLUMN_NAME = 'applycaption' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_systemdlltab ADD applycaption VARCHAR(200);" + "';END IF;END;"); + sqlBuilder.append("DECLARE v_col_exists NUMBER;" + "BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS " + "WHERE TABLE_NAME = 'p_systemdlltab' AND COLUMN_NAME = 'addmodid' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_systemdlltab ADD addmodid VARCHAR(200);';END IF;END;"); + sqlBuilder.append("DECLARE v_col_exists NUMBER;" + "BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS " + "WHERE TABLE_NAME = 'p_systemdlltab' AND COLUMN_NAME = 'saveapplyenabled' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_systemdlltab ADD saveapplyenabled INT;';END IF;END;"); + sqlBuilder.append("DECLARE v_col_exists NUMBER;" + "BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS " + "WHERE TABLE_NAME = 'p_systemdlltab' AND COLUMN_NAME = 'addcopyenabled' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_systemdlltab ADD addcopyenabled INT NULL;';END IF;END;"); + sqlBuilder.append("DECLARE v_col_exists NUMBER;" + "BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS " + "WHERE TABLE_NAME = 'p_systemdlltab' AND COLUMN_NAME = 'bs_adddllname' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_systemdlltab ADD bs_adddllname VARCHAR(50);" + "';END IF;END;DECLARE v_col_exists NUMBER;" + "BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS " + "WHERE TABLE_NAME = 'p_systemdlltab' AND COLUMN_NAME = 'displayRows' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_systemdlltab ADD displayRows INT DEFAULT 50;" + "';END IF;END;"); + sqlBuilder.append("DECLARE v_col_exists NUMBER;" + "BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS " + "WHERE TABLE_NAME = 'p_systemdlltab' AND COLUMN_NAME = 'BSRowHeight' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_systemdlltab ADD BSRowHeight INT;';END IF;END;"); + sqlBuilder.append("DECLARE v_col_exists NUMBER;" + "BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS " + "WHERE TABLE_NAME = 'p_systemdlltab' AND COLUMN_NAME = 'popupWidth' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_systemdlltab ADD popupWidth INT,popupHeight INT;" + "';END IF;END;"); + sqlBuilder.append("DECLARE v_col_exists NUMBER;" + "BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS " + "WHERE TABLE_NAME = 'p_systemdlltab' AND COLUMN_NAME = 'BackSelected' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_systemdlltab ADD BackSelected BIGINT;';END IF;END;"); + return sqlBuilder.toString(); + } + + @Override + public String updateDllTab_210610Sql() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists " + "FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = 'p_systemdlltab' AND COLUMN_NAME = 'delCaption' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_systemdlltab ADD delCaption VARCHAR(100);" + "';END IF;END;"); + return sqlBuilder.toString(); + } + + @Override + public String updateDllTab_210624Sql() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("DECLARE v_col_exists NUMBER;" + "BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS " + "WHERE TABLE_NAME = 'p_systemdlltab' AND COLUMN_NAME = 'addHintMSG' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE p_systemdlltab ADD addHintMSG VARCHAR(1000);" + "';END IF;END;"); + return sqlBuilder.toString(); + } + + @Override + public String updateDllTab_21429Sql() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append(String.format("DECLARE v_col_exists NUMBER;" + "BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS " + "WHERE TABLE_NAME = '%s' AND COLUMN_NAME = 'tasksql' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE %s ADD tasksql VARCHAR(max);';END IF;END;", "p_systemdlltab", "p_systemdlltab")); + sqlBuilder.append(String.format("DECLARE v_col_exists NUMBER;" + "BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS " + "WHERE TABLE_NAME = '%s' AND COLUMN_NAME = 'tasksql' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE %s ADD tasksql VARCHAR(max);" + "';END IF;END;", "p_systembilltype", "p_systembilltype")); + sqlBuilder.append(String.format("DECLARE v_col_exists NUMBER;" + "BEGIN SELECT COUNT(1)INTO v_col_exists FROM ALL_TAB_COLUMNS " + "WHERE TABLE_NAME = '%s' AND COLUMN_NAME = 'newver' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE %s ADD newver INT;';END IF;END;", "p_systembilltype", "p_systembilltype")); + sqlBuilder.append(String.format("DECLARE v_col_exists NUMBER;BEGIN SELECT COUNT(1)INTO v_col_exists " + "FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = '%s' AND COLUMN_NAME = 'newWFVer' " + "AND OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');IF v_col_exists = 0 " + "THEN EXECUTE IMMEDIATE 'ALTER TABLE %s ADD newWFVer INT;" + "';END IF;END;", "p_systembilltype", "p_systembilltype")); + return sqlBuilder.toString(); + } + + @Override + public String updatePhoneCodeSql() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("DECLARE v_table_exists NUMBER;v_owner VARCHAR2(100) := SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA');" + "BEGIN SELECT COUNT(1) INTO v_table_exists FROM ALL_TABLES " + "WHERE OWNER = v_owner AND TABLE_NAME = 'P_VALIDCODE'; IF v_table_exists = 0 " + "THEN EXECUTE IMMEDIATE ' CREATE TABLE ' || v_owner || '.P_VALIDCODE( id INT IDENTITY(1,1) " + "NOT NULL PRIMARY KEY,phone VARCHAR(100) NULL, sendtime DATETIME NULL, validcode VARCHAR(10) NULL," + "type INT NULL, ip VARCHAR(20) NULL)';END IF;END;"); + return sqlBuilder.toString(); + } + + @Override + public StringBuilder BillDataSave(String proName, String escapedMasterSql, String escapedIdValue, String escapedDetailGuid, String escapedBillSeq, int userId, int actionTypeValue, int auditFlag, int comfirmFlag) { + StringBuilder sqlBuilder = new StringBuilder(); +// 修正Oracle变量声明语法(分号分隔、指定长度),保留DM_return/DM_msg变量 + sqlBuilder.append("DECLARE DM_return INT; DM_msg VARCHAR2(2000); "); + sqlBuilder.append("BEGIN "); +// 调用存储过程,严格按存储过程参数顺序传参,OUT参数直接传变量名 + sqlBuilder.append(proName).append("( "); +// 1: p_returnValue OUT INT --> 对应DM_return(OUT) + sqlBuilder.append("DM_return, "); +// 2: p_Sql IN VARCHAR2 --> 对应传入的escapedMasterSql(IN) + sqlBuilder.append("'").append(escapedMasterSql).append("', "); +// 3: p_billdocument_id IN VARCHAR2 --> 对应传入的escapedIdValue(IN) + sqlBuilder.append("'").append(escapedIdValue).append("', "); +// 4: p_tmpstr IN VARCHAR2 --> 对应传入的escapedDetailGuid(IN) + sqlBuilder.append("'").append(escapedDetailGuid).append("', "); +// 5: p_Operatorid IN INT --> 对应传入的userId(IN) + sqlBuilder.append(userId).append(", "); +// 6: p_Fbilltagid IN INT --> 对应传入的actionTypeValue(IN) + sqlBuilder.append(actionTypeValue).append(", "); +// 7: p_operateway IN VARCHAR2 --> 对应传入的escapedBillSeq(IN) + sqlBuilder.append("'").append(escapedBillSeq).append("', "); +// 8: p_msg OUT VARCHAR2 --> 对应DM_msg(OUT) + sqlBuilder.append("DM_msg, "); +// 9: p_auditFlag IN INT DEFAULT 0 --> 对应传入的auditFlag(IN,可选) + sqlBuilder.append(auditFlag).append(", "); +// 10: p_comfirmFlag IN INT DEFAULT 0 --> 对应传入的comfirmFlag(IN,可选) + sqlBuilder.append(comfirmFlag).append(" "); + sqlBuilder.append("); "); +// 查询OUT变量结果,通过DUAL表返回 + sqlBuilder.append("SELECT DM_return AS returnValue, DM_msg AS outputMsg FROM DUAL; "); + sqlBuilder.append("END; "); + return sqlBuilder; + } + + +// endregion + + // region + @Override + public String BuildBillDetailSqlSql(String getDetailTable) { +// INFO2是自增列,不是计算列,这里需要考虑colstat(目前用INFO2来表示) + String sql = String.format("Select name,TYPE$,LENGTH$ as length,0 isnullable,'' text,INFO2 from syscolumns Where ID=OBJECT_ID('%s') ", + getDetailTable + "_temp"); + return sql; + } +//endregion + +} diff --git a/WebErp/weberp/src/main/java/org/example/Impl/Sql/factory/AllInOneSqlFactory.java b/WebErp/weberp/src/main/java/org/example/Impl/Sql/factory/AllInOneSqlFactory.java new file mode 100644 index 0000000..05521c4 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Impl/Sql/factory/AllInOneSqlFactory.java @@ -0,0 +1,30 @@ +package org.example.Impl.Sql.factory; + +import org.example.Impl.Sql.dmImpl.DmAllInOneSqlProvider; +import org.example.Impl.Sql.kingbaseImpl.KingbaseAllInOneSqlProvider; +import org.example.Impl.Sql.provider.AllInOneSqlProvider; +import org.example.ModuleApi.ModuleAjaxApi.mapper.CRMapper; +import org.example.ModuleApi.ModuleAjaxApi.mapper.DMCrmMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; + +@Service +public class AllInOneSqlFactory { + @Autowired + CRMapper crMapper; + @Autowired + DMCrmMapper dmCrmMapper; + @Autowired + JdbcTemplate jdbcTemplate; + + public AllInOneSqlProvider createProvider(String databaseType) { + if ("kingbase".equals(databaseType)) { + return new KingbaseAllInOneSqlProvider(crMapper,jdbcTemplate); // 实现统一接口的人大金仓实例 + } else if ("dm".equals(databaseType)) { + return new DmAllInOneSqlProvider(dmCrmMapper,jdbcTemplate); // 实现统一接口的达梦实例 + } else { + throw new IllegalArgumentException("不支持的数据库类型:" + databaseType); + } + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Impl/Sql/kingbaseImpl/KingbaseAllInOneSqlProvider.java b/WebErp/weberp/src/main/java/org/example/Impl/Sql/kingbaseImpl/KingbaseAllInOneSqlProvider.java new file mode 100644 index 0000000..7fe95c5 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Impl/Sql/kingbaseImpl/KingbaseAllInOneSqlProvider.java @@ -0,0 +1,1249 @@ +package org.example.Impl.Sql.kingbaseImpl; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.example.Enums.CusGridColumnPrefix; +import org.example.Enums.SystemEnums; +import org.example.Impl.Sql.provider.AllInOneSqlProvider; +import org.example.ModuleApi.ModuleAjaxApi.mapper.CRMapper; +import org.example.Utils.PublicUtil; +import org.example.Utils.SqlSafetyGuard; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; + +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; + +import static org.example.Utils.NativeExtensionUtils.toBoolean; + +@Service +public class KingbaseAllInOneSqlProvider implements AllInOneSqlProvider { + private static final Logger log = LoggerFactory.getLogger(KingbaseAllInOneSqlProvider.class); + + private final CRMapper crMapper; + private final JdbcTemplate jdbcTemplate; + + public KingbaseAllInOneSqlProvider(CRMapper crMapper, JdbcTemplate jdbcTemplate) { + this.crMapper = crMapper; + this.jdbcTemplate = jdbcTemplate; + } + + + @Override + public String LoginSql() { + return "select e.EmployeeId, e.EmpLoyeeName, e.LoginAccount, e.password, " + + "e.p_emp_clientid, e.p_emp_AttendanceTime, e.p_emp_logintype, " + + "e.p_emp_PwdErrNum, e.p_emp_PwdLocked, e.p_emp_PwdLockDate, e.AppIndex " + + "from dbo.P_EmployeeTab e " + + "left join dbo.P_customertab b on e.p_emp_clientid = b.id and b.coid = ? " + + "where (e.LoginAccount = ? or e.EmployeeName = ? or e.p_emp_phone = ? or isnull(b.id, '') <> '') " + + "and isnull(e.sign, 0) = 0 " + + "and isnull(e.UseFlag, 0) = 1"; + } + + // region DataImpl + @Override + public String getSysdbGroupByIdSql() { + return "select dbname as name, isnull(localIp, ip) as ip, mobileurl, showname as text " + + "from dbo.p_sydbGroupTab where id = ?"; + } + + @Override + public String getSysdbGroupAllSql() { + return "select id, showname as text, dbname from dbo.p_sydbGroupTab order by orderid"; + } + + @Override + public String getExitSystemPrivilegeAgentTabSql() { + return "select 1 from sysobjects where id = object_id(N'[dbo].[p_systemPrivilegeAgentTab]') and OBJECTPROPERTY(id, N'IsUserTable') = 1"; + } + + @Override + public String getExitFlowExOperSql() { + return "select 1 from sysobjects where id = object_id(N'[dbo].[wms_billflowOperex]') and OBJECTPROPERTY(id, N'IsUserTable') = 1"; + } + + @Override + public String GetBaesModuleLeftSql() { + return "select id detailid, " + + "fieldname, " + + "ISNULL(userenname, sysname) fieldcaption, " + + "fieldkey fromkey, " + + "fieldsqlid valuemember, " + + "fieldsqlname displaymember " + + "from dbo.p_systemwordbooktab " + + "where tab = ? and fieldsqltag = ?"; + } + + @Override + public String GetTableColumnTypeSql() { + return "Select xtype from syscolumns Where ID=OBJECT_ID('%s') and name='%s'"; + } + + + public String GetColumnRowsSql(String moduleId, String userId, String userName, int id, boolean windowsDirver) { + + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("select book.id, book.FieldName, "); + sqlBuilder.append("coalesce(CAST(gcfg.userName AS varchar(200)), coalesce(coalesce(username1, sysname), book.FieldName)) FieldCaption, "); + sqlBuilder.append("ControlWidth, ControlHeight, %s Disabled, %s Disabled2, "); + sqlBuilder.append("fieldsqltag FieldType, addModuleId, %s scanAble, "); + sqlBuilder.append("ControlTop, ControlLeft, book.tagid nullable, book.cancopy, "); + sqlBuilder.append("case when coalesce(book.privilegeOper, '') = '' then edit "); + // 使用position替代charindex,注意参数顺序 + sqlBuilder.append("when position(','||'%s'||',' in ',' || (SELECT string_agg(userList, '') FROM p_systemPrivilege pri "); + sqlBuilder.append("WHERE book.tab = pri.modid and position(','||cast(pri.privTypeId as varchar(10))||',' in ','||book.privilegeOper||',') > 0)) > 0 then 0 else 1 end Edit, "); + sqlBuilder.append("isSum \"sum\", sumCond, sumCalc, fieldsql, fieldsqlid valuemember, "); + sqlBuilder.append("fieldsqlname displaymember, calcExpr CalcExpress, calcOrder CalcOrderId, %s width, "); + sqlBuilder.append("book.fieldkey fromkey, book.disableType, book.IsAddControl, book.dataAlign textalign, "); + sqlBuilder.append("unionValue UnionSQL, unionFields UnionField, dataformat, BandTitle, BandFields BandField, "); + sqlBuilder.append("InputHintText HintText, TitleColor FontColor, limitLen LimitMaxValue, defaultdate defaultvalue, "); + sqlBuilder.append("TM_HeadString othermember, book.labelWidth, book.labelAlign, book.disableCond, c.xtype fieldDbType, "); + sqlBuilder.append("frozenFlag locked, (SELECT string_agg(userList, '') FROM p_systemPrivilege pri "); + sqlBuilder.append("WHERE book.tab = pri.modid and position(','||cast(pri.privTypeId as varchar(10))||',' in ','||book.privilegeView||',') > 0) userList, "); + sqlBuilder.append("book.FontSize fontSize, book.highlightFColor fcolor, book.highlightBColor bcolor, "); + sqlBuilder.append("book.highlightBold bold, book.doNotSpelling, book.lookupWidth pickerWidth, "); + sqlBuilder.append("book.lookupFieldsWidth pickerColsWidth, book.bmptype ftype, book.ifmerge rowspan "); + sqlBuilder.append("from p_systemwordbooktab book "); + sqlBuilder.append("left join p_systemdlltab dll on book.tab = dll.dllcoid "); + // 使用oid获取对象ID,适配Kingbase + sqlBuilder.append("left join syscolumns c on c.id = (select oid from pg_class where relname = dll.sqldt1) and c.name = book.fieldname "); + sqlBuilder.append("left join P_systemGridConfigTab gcfg on '%s' || cast(dll.formkey as varchar(100)) = cast(gcfg.formkey as varchar(100)) "); + sqlBuilder.append("and book.fieldName = gcfg.fieldname and gcfg.operatorid = '%s' "); + sqlBuilder.append("where 1=1 and (coalesce(book.privilegeView, '') = '' or position(','||'%s'||',' in ',' || "); + sqlBuilder.append("(SELECT string_agg(userList, '') FROM p_systemPrivilege pri WHERE book.tab = pri.modid "); + sqlBuilder.append("and position(','||cast(pri.privTypeId as varchar(10))||',' in ','||book.privilegeView||',') > 0)) > 0) "); + + // 处理SQL参数,传递windowsDirver + String disabled2 = windowsDirver ? "book.addVisible" : "case when coalesce(ShowMobile1,0)=0 then 1 else 0 end"; + String disabled = windowsDirver ? "coalesce(1-gcfg.isvisible,book.vislble)" : "case when coalesce(ShowMobile,0)=0 then 1 else 0 end"; + String scanAble = windowsDirver ? "0" : "book.vislble5"; + String width = windowsDirver ? "coalesce(gcfg.fieldwidth,width)" : "mobilewidth"; + + String colsql = String.format( + sqlBuilder.toString(), + disabled, // 对应第一个%s + disabled2, // 对应第二个%s + scanAble, // 对应第三个%s + userName, // 对应第四个%s + width, // 对应第五个%s + CusGridColumnPrefix.BaseMainGridView, // 对应第六个%s + userId, // 对应第七个%s + userName // 对应第八个%s + ); + if (moduleId != null && !moduleId.isEmpty()) { + colsql = String.format("%s and book.tab = '%s' order by book.orderId limit 1000", colsql, moduleId); + } else if (id > 0) { + colsql = String.format("%s and book.id = %d limit 1000", colsql, id); + } + return colsql; + } + + @Override + public String GetBaesModuleLeftSql(String moduleId) { + return ""; + } + + @Override + public String getBaesModuleLeftSql(String moduleId) { + return ""; + } + + @Override + public String GetBaesModuleBmpFieldsSql(String moduleId, String tbName) { + String sql = String.format( + "select fieldname from dbo.p_systemwordbooktab w " + + "inner join syscolumns col on col.id=OBJECT_ID('%s') and col.xtype=34 and col.name=w.fieldname " + + "where tab='%s' and fieldsqltag='%d'", + tbName, + moduleId, + SystemEnums.ControlType.LabPic.getValue() + ); + return sql; + } + + @Override + public String GetTableInfoSql(String trimmedTbName) { + String sql = String.format( + "Select c.name, c.xtype, " + + "case when xtype in (35,99,34,173,165) then 0 else c.length end length, " + + "c.isnullable, m.text, c.colstat, " + + "sc.is_identity isIdentity, sc.is_computed isComputed " + + "from syscolumns c " + + "left join syscomments m on c.cdefault = m.id " + + "left join sys.columns sc on sc.object_id = c.id and c.name = sc.name " + + "Where c.ID = OBJECT_ID('%s')", + trimmedTbName + ); + return sql; + } + + + @Override + public String GetColorAndBoxColumnsSql() { + return "select fieldname, fieldsqltag FieldType, fieldsql, " + + "fieldsqlid valuemember, fieldsqlname displaymember, " + + "TitleColor fontcolor " + + "from dbo.p_systemwordbooktab zsc " + + "where zsc.tab='%s' and " + + "(isnull(TitleColor,'')<>'' or " + + "(isnull(fieldsql,'')<>'' and isnull(fieldsqlname,'')<>'' and isnull(fieldsqlid,'')<>'' ))"; + } + + @Override + public String GetBaesModuleDetailsSql() { + return "select detail.orderid, detail.displayRows, detail.id, detail.detailName, library, " + + "detail.detailsql, detail.autorefresh refresh, detail.unionvalue unionfield, unionCond, " + + "noGridLine, noRownumber, noColumnHeader hideColumnHeader, detail.isDrag, " + + "detail.unionparentfield, detail.unionmodule, detail.formkey, detail.detailType, " + + "formKey fromkey, addVisible, visibleCond, fieldCond, disableField, fieldCond1, disableField1, " + + "case when detail.gridDetailCheck=1 then 1 else detail.gridDetailCheck end multcheck, " + + "displaymode, addShowMode%s " + + "from dbo.p_systemDlltabDetail detail " + + "where tabKey=? and isnull(isVisible,0)=0 " + + "order by OrderID"; + } + + @Override + public String GetAttcFilesSql() { + return "select p.EmployeeName username, f.* from P_fm_FileTab f " + + "inner join bmp_ProductSpeciesTab op on f.speciesno = op.speciesno and " + + "(charindex(',%s,', ',' + replace(op.uploadOper, ' ', '') + ',') > 0 or isnull(op.uploadOper, '') = '' " + + "or charindex(',%s,', ',' + replace(op.downloadOper, ' ', '') + ',') > 0 or isnull(op.downloadOper, '') = '' " + + "or charindex(',%s,', ',' + replace(op.deleteOper, ' ', '') + ',') > 0 or isnull(op.deleteOper, '') = '' " + + "or charindex(',%s,', ',' + replace(op.previewOper, ' ', '') + ',') > 0 or isnull(op.previewOper, '') = '') " + + "left join p_employeetab p on f.creator = p.employeeid " + + "where f.parentid = '%s' %s %s"; + } + + @Override + public String GetAbsFilePathSql() { + return "select dbo.fun_fm_getAbsolutePath(%s)"; + } + + @Override + public String GetAcFileFolderSql() { + return "select dbo.fun_fm_getRelativePath(%s)"; + } + + @Override + public String GetAcFileFolderByCount() { + return "select dbo.fun_fm_getRelativePath(?, ?)"; + } + + @Override + public String GetAcFileFolderAllCount() { + return "select dbo.fun_fm_getRelativePath(?)"; + } + + @Override + public String GetPmsCountSql() { + return "SELECT array_length(proargnames,1) FROM sys_proc WHERE proname = ?"; + } + + @Override + public List>> BaseDataSaveSql(String procName, + String escapedBaseSql, + int execType, + String escapedModuleId, + String escapedMasterTable, + String escapedIdField, + String escapedIdValue, + String escapedOperatorId, + String escapedOperatorName) { + String sql = "DECLARE @returnValue varchar(max) " + + "DECLARE @outputValue varchar(max) " + + "EXEC @returnValue = " + procName + " " + + "'" + escapedBaseSql + "' ," // 末尾无分号,用空格结尾 + + "'" + execType + "' ," + + "'" + escapedModuleId + "', " + + "'" + escapedMasterTable + "' ," + + "'" + escapedIdField + "' ," + + "'" + escapedIdValue + "' ," + + "'" + escapedOperatorId + "' ," + + "'" + escapedOperatorName + "' ," + + "@outputValue OUTPUT " + + "SELECT @@ROWCOUNT AS execcount, @returnValue AS returnValue, @outputValue AS outputValue"; + // 步骤4:执行SQL并获取结果 + List> resultSet = new ArrayList<>(jdbcTemplate.queryForList(sql)); + + + return Collections.singletonList(resultSet); + } + + @Override + public String IsExitProSql() { + return "select 1 from dbo.sysobjects where id = object_id(N'[dbo].[%s]') and OBJECTPROPERTY(id, N'IsProcedure') = 1"; + } + + @Override + public List> ExecSelectOperStoreSql(List outParamNames, String storeName, Map params) { + Connection connection = null; + CallableStatement callableStmt = null; + ResultSet resultSet = null; + ResultSet targetResultSet = null; + try { + // -------------------- 2. 拼接SQL(保持你的格式) -------------------- + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("DECLARE @returnValue int "); + for (String outParam : outParamNames) { + if (outParam.equals("@msg")) { + sqlBuilder.append("DECLARE ").append(outParam).append(" varchar(2000) "); + } else { + sqlBuilder.append("DECLARE ").append(outParam).append(" varchar(500) "); + } + } + sqlBuilder.append("EXEC @returnValue = [dbo].").append(storeName).append("\n"); + + List inputParamParts = new ArrayList<>(); + for (Map.Entry entry : params.entrySet()) { + String paramName = entry.getKey(); + Object paramValue = entry.getValue(); + String paramPart; + if (paramValue == null || paramValue.toString().isEmpty()) { + paramPart = "\t" + paramName + " = ''"; + } else if (paramValue instanceof String + || paramValue instanceof Character + || paramValue instanceof java.util.Date) { + String escapedValue = paramValue.toString().replace("'", "''"); + paramPart = "\t" + paramName + " = '" + escapedValue + "'"; + } else { + paramPart = "\t" + paramName + " = " + paramValue.toString(); + } + inputParamParts.add(paramPart); + } + + List outParamParts = new ArrayList<>(); + for (String outParam : outParamNames) { + outParamParts.add("\t" + outParam + " = " + outParam + " OUTPUT"); + } + + for (int i = 0; i < inputParamParts.size(); i++) { + sqlBuilder.append(inputParamParts.get(i)); + if (i != inputParamParts.size() - 1 || !outParamParts.isEmpty()) { + sqlBuilder.append(",\n"); + } else { + sqlBuilder.append("\n"); + } + } + for (int i = 0; i < outParamParts.size(); i++) { + sqlBuilder.append(outParamParts.get(i)); + if (i != outParamParts.size() - 1) { + sqlBuilder.append(",\n"); + } + } + + sqlBuilder.append("\nSELECT\n"); + sqlBuilder.append("\t@returnValue AS returnValue"); + for (String outParam : outParamNames) { + sqlBuilder.append(",\n\t").append(outParam).append(" AS ").append(outParam.replace("@", "")); + } + sqlBuilder.append(";"); + + String finalSql = sqlBuilder.toString(); + + // -------------------- 3. 执行SQL并获取正确的结果集(核心简化处理) -------------------- + connection = jdbcTemplate.getDataSource().getConnection(); + callableStmt = connection.prepareCall(finalSql); + // 执行并处理多结果集(仅保留找到目标结果集的逻辑,去掉冗余判断) + boolean hasMoreResults = callableStmt.execute(); + while (hasMoreResults) { + resultSet = callableStmt.getResultSet(); + if (resultSet != null) { + // 检查当前结果集是否包含returnValue列(目标结果集) + try { + if (resultSet.getMetaData().getColumnCount() > 0 + && resultSet.getMetaData().getColumnName(1).equals("returnValue")) { + targetResultSet = resultSet; // 找到目标结果集,跳出循环 + break; + } + } catch (SQLException e) { + // 忽略无效结果集的错误 + } + // 非目标结果集直接关闭 + if (resultSet != targetResultSet) { + resultSet.close(); + } + } + hasMoreResults = callableStmt.getMoreResults(); + } + + // 解析目标结果集(如果找到) + Map resultMap = new HashMap<>(); + if (targetResultSet != null) { + if (targetResultSet.next()) { + resultMap.put("RETURN_VALUE", targetResultSet.getInt("returnValue")); + for (String outParam : outParamNames) { + String colName = outParam.replace("@", ""); + resultMap.put(outParam, targetResultSet.getString(colName)); + } + } + } + return (List>) resultMap; + } catch (Exception e) { + // 优化1:添加上下文信息,方便排查(明确是人大金仓SQL拼接/创建Stmt失败) + String errorMsg = String.format("人大金仓存储过程SQL拼接/创建CallableStatement失败,存储过程名:%s", storeName); + // 优化2:保留原始异常(e),让堆栈包含完整原因 + throw new RuntimeException(errorMsg, e); + + } finally { + // 优化3:关闭资源(关键!避免连接泄漏) + // 注意:如果callableStmt创建成功并返回,这里不要关闭(由上层调用方关闭) + // 只关闭异常时创建的connection(如果callableStmt没返回) + if (callableStmt == null && connection != null) { + try { + connection.close(); + } catch (SQLException ex) { + // 记录资源关闭失败的日志(不抛异常,避免覆盖原始异常) + log.error("Exception caught", ex); + } + } + } + } + + @Override + public String hasStoreParametersql(String procedureName, String paramName) { + return String.format("SELECT parameter_name FROM information_schema.parameters " + + "WHERE specific_name = '%s' AND parameter_name = '%s'", + procedureName, paramName); + } + + + @Override + public String GetAttcBmpSpecSql() { + return "select bmpSpec from dbo.v_systemdlltab where DllCoid= '%s'"; + } + + @Override + public String GetIdentityFieldSql(String finaltbName) { + String sql = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.columns WHERE TABLE_NAME = '%s' AND COLUMNPROPERTY(OBJECT_ID('%s'), COLUMN_NAME, 'IsIdentity') = 1"; + String identitySql = String.format(sql, finaltbName, finaltbName); + return identitySql; + } + + @Override + public String CheckIsMulitAuditSql() { + return "select case when col_length('wms_billflowOperView', 'auditoperators') is null then 0 else 1 end;"; + } + + @Override + public String GetTaskMobileCardColumnSql() { + return ""; + } + + @Override + public String GetAuditBaseDetailsSql() { + return "select case when exists (" + + "select 1 from dbo.sysobjects where id = object_id(N'[dbo].[p_SystemDlltabDetailFlowTab]') " + + "and OBJECTPROPERTY(id, N'IsUserTable') = 1) then 1 else 0 end"; + } + + @Override + public String GetAttTreeDataSql() { + return "select speciesno, speciesname, " + + "LEFT(speciesno, LEN(speciesno) - 2) as parentid " + + "from bmp_ProductSpeciesTab spec " + + "inner join (" + + " select bmpspec, dllcoid " + + " from dbo.v_systemdlltab " + + " where dllcoid = ? " + + " group by bmpspec, dllcoid" + + ") vd on (spec.SpeciesNo like CONCAT(COALESCE(vd.bmpSpec, ''), '%'))"; + } + + @Override + public String GetAttTreeDataSqlisEmpty() { + return "select bmpspec as speciesno, bmpspec as speciesname, '01' as parentid " + + "from dbo.v_systemdlltab " + + "where dllcoid = ? " + + "group by bmpspec, dllcoid"; + } + + @Override + public String GetSqlUserSql() { + return "SELECT EmployeeName + ',' FROM p_employeetab WHERE (%s) FOR XML PATH('')"; + } + + @Override + public String GetSqlUserByroleSql() { + return "SELECT aa.operatorname + ',' FROM p_systemRoleSetTab a " + + "JOIN p_systemRoleOperSetTab aa ON a.id = aa.roleid " + + "WHERE a.roleName = '%s' FOR XML PATH('')"; + } + + @Override + public String hasExistsTableSql(String tableName) { + return "SELECT COUNT(1) FROM sysobjects WHERE id = object_id(N'[dbo].[" + tableName + "]') AND OBJECTPROPERTY(id, N'IsUserTable') = 1"; + } + + @Override + public String GetUsersSql() { + return "if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[P_employeeBaseView]') and xtype='v') " + + "exec('select employeeid 员工ID, loginaccount 员工工号, employeename 员工姓名, departmentname 所属部门, speciesname as 所属类别 " + + "From p_employeetab a left join P_DepartmentTab b on a.departmentid = b.departmentid " + + "left join P_EmployeeSpecTab c on a.speciesno = c.speciesno " + + "where isnull(a.sign,0)=0 and isnull(useflag,0)=1 " + + "and (employeename like (''%{0}%'') OR dbo.P_GetPy(employeename) like (''%{0}%'')) " + + "and loginaccount like (''%{1}%'') " + + "and ({2}=0 or employeeid=''{2}''))' ) " + + "else " + + "exec('select * from P_employeeBaseView where 1=1 " + + "and (员工姓名 like (''%{0}%'') OR dbo.P_GetPy(员工姓名) like (''%{0}%'')) " + + "and 员工工号 like (''%{1}%'') " + + "and ({2}=0 or 员工ID=''{2}'')')"; + } + + @Override + public String GetAccountInfoByStoreSql() { + return "select 1 from dbo.sysobjects where id = object_id(N'dbo.p_systemGetAccountTask') and OBJECTPROPERTY(id, N'IsProcedure') = 1"; + } + + @Override + public String GetProSysTypeSql() { + return "if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[p_systemproductseriestab]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) select 0 else select 1"; + } + + @Override + public String CheckUserAbleSql(Map updRow, String trimmedUsers) { + return "select employeename + ',' from p_employeetab " + + "where (" + PublicUtil.ReqSqlPmsByRow(updRow, null, trimmedUsers) + ")" + + "FOR XML PATH('');"; + } + + @Override + public String GetNextSelectStepListSql() { + return "select stepcode, stepname from %s " + + "where typecode='%s' " + + "and charindex(';' + cast(stepcode as varchar) + ';', ';%s;') > 0"; + } + + public String GetColumnRows(String moduleId, String userId, String userName, int id, boolean windowsDirver) { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("select book.id, book.FieldName, "); + sqlBuilder.append("coalesce(CAST(gcfg.userName AS varchar(200)), coalesce(coalesce(username1, sysname), book.FieldName)) FieldCaption, "); + sqlBuilder.append("ControlWidth, ControlHeight, %s Disabled, %s Disabled2, "); + sqlBuilder.append("fieldsqltag FieldType, addModuleId, %s scanAble, "); + sqlBuilder.append("ControlTop, ControlLeft, book.tagid nullable, book.cancopy, "); + sqlBuilder.append("case when coalesce(book.privilegeOper, '') = '' then edit "); + // 使用position替代charindex,注意参数顺序 + sqlBuilder.append("when position(','||'%s'||',' in ',' || (SELECT string_agg(userList, '') FROM p_systemPrivilege pri "); + sqlBuilder.append("WHERE book.tab = pri.modid and position(','||cast(pri.privTypeId as varchar(10))||',' in ','||book.privilegeOper||',') > 0)) > 0 then 0 else 1 end Edit, "); + sqlBuilder.append("isSum \"sum\", sumCond, sumCalc, fieldsql, fieldsqlid valuemember, "); + sqlBuilder.append("fieldsqlname displaymember, calcExpr CalcExpress, calcOrder CalcOrderId, %s width, "); + sqlBuilder.append("book.fieldkey fromkey, book.disableType, book.IsAddControl, book.dataAlign textalign, "); + sqlBuilder.append("unionValue UnionSQL, unionFields UnionField, dataformat, BandTitle, BandFields BandField, "); + sqlBuilder.append("InputHintText HintText, TitleColor FontColor, limitLen LimitMaxValue, defaultdate defaultvalue, "); + sqlBuilder.append("TM_HeadString othermember, book.labelWidth, book.labelAlign, book.disableCond, c.xtype fieldDbType, "); + sqlBuilder.append("frozenFlag locked, (SELECT string_agg(userList, '') FROM p_systemPrivilege pri "); + sqlBuilder.append("WHERE book.tab = pri.modid and position(','||cast(pri.privTypeId as varchar(10))||',' in ','||book.privilegeView||',') > 0) userList, "); + sqlBuilder.append("book.FontSize fontSize, book.highlightFColor fcolor, book.highlightBColor bcolor, "); + sqlBuilder.append("book.highlightBold bold, book.doNotSpelling, book.lookupWidth pickerWidth, "); + sqlBuilder.append("book.lookupFieldsWidth pickerColsWidth, book.bmptype ftype, book.ifmerge rowspan "); + sqlBuilder.append("from p_systemwordbooktab book "); + sqlBuilder.append("left join p_systemdlltab dll on book.tab = dll.dllcoid "); + // 使用oid获取对象ID,适配Kingbase + sqlBuilder.append("left join syscolumns c on c.id = (select oid from pg_class where relname = dll.sqldt1) and c.name = book.fieldname "); + sqlBuilder.append("left join P_systemGridConfigTab gcfg on '%s' || cast(dll.formkey as varchar(100)) = cast(gcfg.formkey as varchar(100)) "); + sqlBuilder.append("and book.fieldName = gcfg.fieldname and gcfg.operatorid = '%s' "); + sqlBuilder.append("where 1=1 and (coalesce(book.privilegeView, '') = '' or position(','||'%s'||',' in ',' || "); + sqlBuilder.append("(SELECT string_agg(userList, '') FROM p_systemPrivilege pri WHERE book.tab = pri.modid "); + sqlBuilder.append("and position(','||cast(pri.privTypeId as varchar(10))||',' in ','||book.privilegeView||',') > 0)) > 0) "); + + // 获取Windows驱动标志 + // 处理SQL参数 + String disabled2 = windowsDirver ? "book.addVisible" : "case when coalesce(ShowMobile1,0)=0 then 1 else 0 end"; + String disabled = windowsDirver ? "coalesce(1-gcfg.isvisible,book.vislble)" : "case when coalesce(ShowMobile,0)=0 then 1 else 0 end"; + String scanAble = windowsDirver ? "0" : "book.vislble5"; + String width = windowsDirver ? "coalesce(gcfg.fieldwidth,width)" : "mobilewidth"; + + // 格式化SQL(参数顺序与%s占位符对应) + String colsql = String.format( + sqlBuilder.toString(), + disabled, // 对应第一个%s + disabled2, // 对应第二个%s + scanAble, // 对应第三个%s + userName, // 对应第四个%s + width, // 对应第五个%s + CusGridColumnPrefix.BaseMainGridView, // 对应第六个%s + userId, // 对应第七个%s + userName // 对应第八个%s + ); + + // 补充查询条件 + if (moduleId != null && !moduleId.isEmpty()) { + colsql = String.format("%s and book.tab = '%s' order by book.orderId limit 1000", colsql, moduleId); + } else if (id > 0) { + colsql = String.format("%s and book.id = %d limit 1000", colsql, id); + } + return colsql; + } + + public String GetRightMenuRows(String fromkey, int menutype, String username, int menuid, boolean isWindowsDirver) { + String baseSql = "select orderid, id, privilegeoper, dllname as library, action, " + + "dllpar1 as param1, dllpar2 as param2, dllpar3 as param3, dllpar4 as param4, " + + "dllpar5 as param5, dllpar6 as param6, dllpar7 as param7, dllpar8 as param8, " + + "dllpar9 as param9, dllpar10 as param10, maxwindow, menuname as menucaption, " + + "menucond, actiontype, beforemsg, ifrefresh as refresh, DBClickEvent as dbclick, " + + "ifMoreClick as multi, mergeExec as [merge], showtoolbar as toBar, showMode, isCopy, " + + "beforeTab, isnull(isStartRun, 0) as notRec, defailtImage as icon, " + + "isnull(disabletype, 0) as disabletype " + + "from p_systempopupmenu where 1=1 %s %s " + + "and (isnull(privilegeoper, '') = '' or charindex(',%s,', ',' + privilegeoper + ',') > 0) " + + "order by orderid asc"; + + if (menuid > 0) { + // 构建查询条件,使用%s作为占位符 + String sql = String.format(baseSql, + "and id='" + menuid + "'", // 第一个%s占位符 + "", // 第二个%s占位符 + username // 第三个%s占位符 + ); + // 执行查询,传入fromkey参数 + return sql; + } else { + // 构建where条件 + StringBuilder whereCond = new StringBuilder("and tab=? "); + + if (menutype == 0) { // 右键菜单 + whereCond.append("AND ISNULL([menutype],0)=0 "); + } else if (menutype == 1) { // 常用功能 + whereCond.append("AND ISNULL([menutype],0)=1 "); + } + + // 构建驱动条件 + String driverCondition = isWindowsDirver ? "and visible=0" : "and isnull(ShowMobile,0)=1"; + + // 格式化SQL,替换%s占位符 + String sql = String.format(baseSql, + whereCond.toString(), // 第一个%s占位符 + driverCondition, // 第二个%s占位符 + username // 第三个%s占位符 + ); + return sql; + } + } + + @Override + public List> GetModuleIdFieldRow(String keyName, String key) { + keyName = SqlSafetyGuard.requireAllowedIdentifier(keyName, "tab", "formkey"); + return crMapper.getModuleIdFieldRow(keyName, key, SystemEnums.ControlType.LabTreeType.getValue()); + } + + public int GetAuditStepCount(String moduleId, boolean b) { + return crMapper.getAuditStepCount(moduleId, b); + } + + public List> GetCondition(String fromkey, Integer id, Boolean windowsDirver) { + return crMapper.GetCondition(fromkey, id, windowsDirver); + } + + @Override + public List> GetBillDetailColumns(String moduleCode, String userId, String username, Integer id, boolean window) { + return crMapper.GetBillDetailColumns(moduleCode, userId, username, id, window); + } + + @Override + public List> GetBillMasterRows(String moduleId, String userName, Integer id, boolean window) { + return crMapper.GetBillMasterRows(moduleId, userName, id, window); + } + + @Override + public Map GetBillModule(String moduleCode, String menuId) { + return crMapper.GetBillModule(moduleCode, menuId); + } + + @Override + public List> GetControlRows(Object fromkey, String userName, String moduleId, Integer fieldId) { + return crMapper.GetControlRows(fromkey, userName, moduleId, fieldId); + } + + @Override + public List> selectSystemPopupMenuById(int menuid, String fromkey, String username) { + return crMapper.selectSystemPopupMenuById(menuid, fromkey, username); + } + + @Override + public List> selectSystemPopupMenuByType(int menutype, String fromkey, String username, boolean windowsDirver) { + return crMapper.selectSystemPopupMenuByType(menutype, fromkey, username, windowsDirver); + } + + @Override + public List> GetBillSource(String moduleCode, String stCondition) { + return crMapper.getBillSource(moduleCode, SqlSafetyGuard.requireSafeConditionFragment(stCondition)); + } + + @Override + public List> GetBillSourceColumns(String sourceId, String userId, String billSourceGridView) { + return crMapper.getBillSourceColumns(sourceId, userId, billSourceGridView); + } + + @Override + public List> GetBillSourceDetailColumns(String sourceId, String userId, String billSourceGridView) { + return crMapper.getBillSourceDetailColumns(sourceId, billSourceGridView, userId); + } + + @Override + public List> GetBaseModule(String moduleCode, String menuId) { + return crMapper.GetBaseModule(moduleCode, menuId); + } + + @Override + public String GetAuditMsgTab(String userId) { + String userCond1 = (userId == null || userId.isEmpty()) ? "" : String.format("AND a.UserId = '%s'", userId); + String userCond2 = (userId == null || userId.isEmpty()) ? "" : String.format("AND NoticeUserID = '%s'", userId); + + // 构建完整SQL(使用经过验证的正确语法) + String sql = String.format(""" + SELECT * FROM ( + SELECT * FROM ( + SELECT DISTINCT + '['||b.MenuCaption||']'||'=> '||COALESCE(a.billdocument_id,'')||msg AS AuditMessages, + a.UserId, + a.Created AS OperateDate, + a.id, + a.messid, + a.DLLCoid moduleid, + Cnt1 num, + CASE WHEN COALESCE(a.DllFileName,'')<>'' THEN a.DllFileName + WHEN COALESCE(b.dllfilename1,'')='' THEN b.DllFileName + ELSE b.dllfilename1 + END dllname, + b.MenuCaption modulename, + billdocument_id keyvalue, + stepcode, + COALESCE(MenuMode_Mobile,0) menumode, + b.menuid, + CASE WHEN COALESCE(c.DllCoid,'')='' + THEN COALESCE(d.defaultShowSearch,0) + ELSE COALESCE(c.defaultShowSearch,0) + END hasSearch, + 1 tablecode, + a.templetename tplname + FROM p_systemNotification a + INNER JOIN P_FormMenuConfigTab b ON COALESCE(a.menuid,0)=b.MenuId + LEFT JOIN p_systemdlltab c ON b.UrlParams = c.DllCoid + LEFT JOIN p_systembilltype d ON b.UrlParams = d.typeCode + WHERE COALESCE(a.billdocument_id,'')<>'' + AND COALESCE(a.status,0)=0 + AND COALESCE(a.Cnt1,0)<>0 + %s + ORDER BY a.Created DESC + LIMIT 100 + ) t1 + UNION ALL + SELECT * FROM ( + SELECT + Msg, + NoticeUserID, + CreateDate, + id, + 1, + DllCoid, + 0, + '', + Title, + MsgDetail, + 0, + 0, + 0, + 0, + 2 tablecode, + templetename tplname + FROM p_systemMessageTab + WHERE DeleteFlag=0 + %s + ORDER BY CreateDate DESC + LIMIT 100 + ) t2 + ) cc + ORDER BY OperateDate DESC + LIMIT 100;""", userCond1, userCond2); + return sql; + } + + @Override + public String GetDeskTopCommonUse(int cardId, String userId) { + String sql = String.format("WITH tmenu(id,MenuCaption,ParentId,menuid,SubSysId,level) " + + "as " + + "( " + + " SELECT convert(varchar(50), menu.SubSysId)+'_'+ MenuStruct id,MenuCaption,CONVERT(varchar(50), -CONVERT(int, menu.SubSysId)) ParentId,menu.menuid,menu.SubSysId,1 level FROM p_formmenuconfigtab menu " + + " inner join P_SubSystemTab sub on menu.SubSysId=sub.SubSysId and ISNULL(UseEd,0)=1 and ISNULL(visible,0)=0 " + + " where LEN(menustruct)=2 and isnull(menu.SeriesId, 1)=1 and ISNULL(menu.UseFlag,1)=1 " + + " UNION ALL " + + " SELECT convert(varchar(50), a.SubSysId)+'_'+A.MenuStruct id, A.MenuCaption,CONVERT(varchar(50),convert(varchar(50), a.SubSysId)+'_'+ SUBSTRING(a.MenuStruct,1,case when LEN(a.MenuStruct)>2 then LEN(a.MenuStruct)-2 else 0 end)) ParentId,a.MenuId,a.SubSysId ,b.level+1 FROM p_formmenuconfigtab A,tmenu b " + + " where LEN(a.MenuStruct)>2 and ISNULL(UseFlag,1)=1 and convert(varchar(50), a.SubSysId)+'_'+ SUBSTRING(a.MenuStruct,1,case when LEN(a.MenuStruct)>2 then LEN(a.MenuStruct)-2 else 0 end) = b.id and a.SubSysId=b.SubSysId " + + ") " + + " " + + " select * from ( " + + " select pfmc.serverId, case when isnull(pfmc.dllfilename1,'')='' then pfmc.dllfilename else pfmc.dllfilename1 end dllfilename,case when CHARINDEX('www',pmt.ObjDll)>0 then pmt.objdll else '#' end href,pmt.id, " + + " pmt.DllShowCaption text,pmt.LMenuid menuid,pmt.Lsubsysid subsysid, " + + " isnull(pfmc.UrlParams,isnull(psdt.dllcoid,psdtp.typeCode)) dllcoid,psdt.dlltype, " + + " case when isnull(cast(psdt.countsql as varchar),isnull(cast(psdtp.countsql as varchar),''))='' then 0 else 1 end needcount " + + " from P_MessageToolLinkDllTab pmt " + + " inner join tmenu lm on lm.menuid=pmt.lmenuid " + + " inner join p_formmenuconfigtab pfmc on pmt.Lmenuid=pfmc.menuid " + + " left join p_systemdlltab psdt on pfmc.urlparams=psdt.dllcoid and isnull(pfmc.UrlParams,'') !='' " + + " left join p_systembilltype psdtp on pfmc.urlparams=psdtp.typeCode and isnull(pfmc.UrlParams,'') !='' " + + " where EmployeeID='%s' and pmt.cardId='%d' and (isnull(pfmc.targetmode,0)=0 or pfmc.targetmode=3) " + + ") a where isnull(dllfilename,'')<>'' and isnull(dllcoid,'')<>'' ", userId, cardId); + + if (cardId < 0) { + if (cardId == -99) { + sql = "select pmt.objdll dllfilename,case when CHARINDEX('www',pmt.ObjDll)>0 then pmt.objdll else '#' end href,pmt.id, " + + "pmt.DllShowCaption text,pmt.LMenuid menuid,pmt.Lsubsysid subsysid, " + + "pmt.dllcoid dllcoid,'' dlltype, " + + "0 needcount ,0 serverId " + + "from P_MessageToolLinkDllTab pmt " + + "where pmt.cardId='-99' and isnull(objdll,'')<>'' and isnull(dllcoid,'')<>'' "; + } else { + sql = String.format("select * from ( " + + "select distinct pfmc.serverId, case when isnull(pfmc.dllfilename1,'')='' then pfmc.dllfilename else pfmc.dllfilename1 end dllfilename,case when CHARINDEX('www',pmt.ObjDll)>0 then pmt.objdll else '#' end href, " + + "pmt.DllShowCaption text,pmt.LMenuid menuid,pmt.Lsubsysid subsysid, " + + "isnull(pfmc.UrlParams,isnull(psdt.dllcoid,psdtp.typeCode)) dllcoid,psdt.dlltype, " + + "case when isnull(cast(psdt.countsql as varchar),isnull(cast(psdtp.countsql as varchar),''))='' then 0 else 1 end needcount " + + "from P_MessageToolLinkDllTab pmt " + + "--inner join tmenu lm on lm.menuid=pmt.lmenuid " + + "inner join p_formmenuconfigtab pfmc on pmt.Lmenuid=pfmc.menuid " + + "left join p_systemdlltab psdt on pfmc.urlparams=psdt.dllcoid and isnull(pfmc.UrlParams,'') !='' " + + "left join p_systembilltype psdtp on pfmc.urlparams=psdtp.typeCode and isnull(pfmc.UrlParams,'') !='' " + + "where EmployeeID='%s' and pmt.cardId='%d' and (isnull(pfmc.targetmode,0)=0 or pfmc.targetmode=3) " + + ") a where isnull(dllfilename,'')<>'' and isnull(dllcoid,'')<>'' ", userId, cardId); + } + } + return sql; + } + + @Override + public String GetDeskQueryResult(String queryText, boolean exitTable) { + String sql = "if exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[p_systemSearchTextTab]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) " + + "begin " + + " WITH tmenu(id,ParentId,menuid,SubSysId,level) " + + " as " + + " ( " + + " SELECT convert(varchar(50), menu.SubSysId)+'_'+ MenuStruct id,CONVERT(varchar(50), -CONVERT(int, menu.SubSysId)) ParentId,menu.menuid,menu.SubSysId,1 level FROM p_formmenuconfigtab menu " + + " inner join P_SubSystemTab sub on menu.SubSysId=sub.SubSysId and ISNULL(UseEd,0)=1 and ISNULL(visible,0)=0 " + + " where ISNULL(UseFlag,1)=1 and LEN(menustruct)=2 " + + " UNION ALL " + + " SELECT convert(varchar(50), a.SubSysId)+'_'+A.MenuStruct id,CONVERT(varchar(50),convert(varchar(50), a.SubSysId)+'_'+ SUBSTRING(a.MenuStruct,1,case when LEN(a.MenuStruct)>2 then LEN(a.MenuStruct)-2 else 0 end)) ParentId,a.MenuId,a.SubSysId ,b.level+1 FROM p_formmenuconfigtab A,tmenu b " + + " where LEN(a.MenuStruct)>2 and ISNULL(UseFlag,1)=1 and convert(varchar(50), a.SubSysId)+'_'+ SUBSTRING(a.MenuStruct,1,case when LEN(a.MenuStruct)>2 then LEN(a.MenuStruct)-2 else 0 end) = b.id and a.SubSysId=b.SubSysId " + + " ) " + + " select pm.menuid id,'SYSTEM_MENU' keyvalue, " + + " urlparams src_modid,menuCaption src_modtitle,urlparams target_modid,menuCaption target_modtitle,case when isnull(dllfilename1,'')='' then DllFileName else dllfilename1 end target_dllname,'MENU' key_fieldname,'功能模块' key_fieldcnname, menuCaption key_context,getdate() operatedate,'管理员' operatorname " + + " from p_formmenuconfigtab pm " + + " inner join tmenu tm on pm.menuid=tm.menuid " + + " inner join P_SubSystemTab s on pm.subsysid=s.subsysid and s.useed=1 and ISNULL(s.visible,0)=0 " + + " inner join v_systemdlltab v on pm.urlparams=v.dllcoid " + + " where isnull(pm.urlparams,'')<>'' and (ISNULL(pm.dllfilename1,'')<>'' or ISNULL(pm.dllfilename,'')<>'') and (isnull(pm.targetmode,0)=0 or pm.targetmode=3) and isnull(pm.useFlag,1)=1 " + + " and (pm.urlparams like ('%' + ? +'%') OR pm.menuCaption like ('%' + ? +'%') OR dbo.P_GetPy(pm.menuCaption) like ('%' + ? +'%')) " + + " union all " + + " select top 50 * from p_systemSearchTextTab where keyvalue='SYSTEM_MENU' " + + " and ( key_context like ('%' + ? +'%') OR dbo.P_GetPy(key_context) like ('%' + ? +'%') " + + " or target_modid like ('%' + ? +'%') or src_modid like ('%' + ? +'%') ) " + + "end " + + "else " + + "begin " + + " WITH tmenu(id,ParentId,menuid,SubSysId,level) " + + " as " + + " ( " + + " SELECT convert(varchar(50), menu.SubSysId)+'_'+ MenuStruct id,CONVERT(varchar(50), -CONVERT(int, menu.SubSysId)) ParentId,menu.menuid,menu.SubSysId,1 level FROM p_formmenuconfigtab menu " + + " inner join P_SubSystemTab sub on menu.SubSysId=sub.SubSysId and ISNULL(UseEd,0)=1 and ISNULL(visible,0)=0 " + + " where ISNULL(UseFlag,1)=1 and LEN(menustruct)=2 " + + " UNION ALL " + + " SELECT convert(varchar(50), a.SubSysId)+'_'+A.MenuStruct id,CONVERT(varchar(50),convert(varchar(50), a.SubSysId)+'_'+ SUBSTRING(a.MenuStruct,1,case when LEN(a.MenuStruct)>2 then LEN(a.MenuStruct)-2 else 0 end)) ParentId,a.MenuId,a.SubSysId ,b.level+1 FROM p_formmenuconfigtab A,tmenu b " + + " where LEN(a.MenuStruct)>2 and ISNULL(UseFlag,1)=1 and convert(varchar(50), a.SubSysId)+'_'+ SUBSTRING(a.MenuStruct,1,case when LEN(a.MenuStruct)>2 then LEN(a.MenuStruct)-2 else 0 end) = b.id and a.SubSysId=b.SubSysId " + + " ) " + + " select pm.menuid id,'SYSTEM_MENU' keyvalue, " + + " urlparams src_modid,menuCaption src_modtitle,urlparams target_modid,menuCaption target_modtitle,case when isnull(dllfilename1,'')='' then DllFileName else dllfilename1 end target_dllname,'MENU' key_fieldname,'功能模块' key_fieldcnname, menuCaption key_context,getdate() operatedate,'管理员' operatorname " + + " from p_formmenuconfigtab pm " + + " inner join tmenu tm on pm.menuid=tm.menuid " + + " inner join P_SubSystemTab s on pm.subsysid=s.subsysid and s.useed=1 and ISNULL(s.visible,0)=0 " + + " where isnull(pm.urlparams,'')<>'' and (ISNULL(pm.dllfilename1,'')<>'' or ISNULL(pm.dllfilename,'')<>'') and (isnull(pm.targetmode,0)=0 or pm.targetmode=3) and isnull(pm.useFlag,1)=1 " + + " and (pm.urlparams like ('%' + ? +'%') OR pm.menuCaption like ('%' + ? +'%') OR dbo.P_GetPy(pm.menuCaption) like ('%' + ? +'%')) " + + "end"; + return sql; + } + + @Override + public String GetDesktopModuleMain(String userId, String userName, String dllcoid) { + String sql = "SELECT " + + "a.[id], " + + "a.[dllcoid], " + + "b.[itemCode], " + + "ISNULL(a.[itemRowNo], b.[itemRowNo]) [itemRowNo], " + + "ISNULL(a.[itemOrder], b.[itemOrder]) [itemOrder], " + + "ISNULL(a.[itemWidth], b.[itemWidth]) [itemWidth], " + + "ISNULL(a.[itemHeight], b.[itemHeight]) [itemHeight], " + + "a.[itemLeft], " + + "a.[itemTop], " + + "CASE WHEN ISNULL(a.[deleted], 0) = 1 THEN 1 ELSE ISNULL(a.[enableFlag], 0) END [enableFlag], " + + "b.[itemTitle], " + + "b.[itemTypeFull], " + + "a.[queryField], " + + "a.[condition] " + + "FROM [P_SystemFirstPageSetTab] b " + + "LEFT JOIN [P_SystemDllFirstPageTab] a ON a.itemCode = b.itemCode AND a.[dllcoid] = ? " + + "WHERE b.enableType IN (-1, 2) " + + "AND (ISNULL(b.itemPrivilege, '') = '' OR CHARINDEX(',' + ? + ',', ',' + b.itemPrivilege + ',') > 0)"; + return sql; + } + + @Override + public String CheckIsAudit() { + return "select col_length('P_SystemCheckTab', 'stepover')"; + } + + @Override + public String GetProSysType() { + return "if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[p_systemproductseriestab]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) select 0 else select 1"; + } + + @Override + public String GetPrimaryKeysArray(String tabname) { + String primaryKeysSql = String.format("SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_NAME = '%s'", tabname); + return primaryKeysSql; + } + + @Override + public String GetUsers(String name, String code, int userId) { + String safeName = name == null ? "" : name.replace("'", ""); + String safeCode = code == null ? "" : code.replace("'", ""); + String sql = "if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[P_employeeBaseView]') and xtype='v') " + + "exec('select employeeid 员工ID, loginaccount 员工工号, employeename 员工姓名, departmentname 所属部门, speciesname as 所属类别 " + + "From p_employeetab a left join P_DepartmentTab b on a.departmentid = b.departmentid " + + "left join P_EmployeeSpecTab c on a.speciesno = c.speciesno " + + "where isnull(a.sign,0)=0 and isnull(useflag,0)=1 " + + "and (employeename like (''%{0}%'') OR dbo.P_GetPy(employeename) like (''%{0}%'')) " + + "and loginaccount like (''%{1}%'') " + + "and ({2}=0 or employeeid=''{2}''))' ) " + + "else " + + "exec('select * from P_employeeBaseView where 1=1 " + + "and (员工姓名 like (''%{0}%'') OR dbo.P_GetPy(员工姓名) like (''%{0}%'')) " + + "and 员工工号 like (''%{1}%'') " + + "and ({2}=0 or 员工ID=''{2}'')')"; + + sql = String.format(sql, safeName, safeCode, userId); + return sql; + } + + @Override + public String hasExistsTable(String tableName) { + return "SELECT COUNT(1) FROM sysobjects WHERE id = object_id(N'[dbo].[" + tableName + "]') AND OBJECTPROPERTY(id, N'IsUserTable') = 1"; + } + + @Override + public String SeeOneAuditMsg() { + return """ + declare @type varchar(200) + select @type = c.name + from sys.columns a + inner join sys.tables b on b.object_id = a.object_id + inner join sys.types c on c.system_type_id = a.system_type_id + where b.name = 'p_systemNoticeSheet' and a.name = 'Created' + + if @type <> 'datetime' + begin + declare @constraintName varchar(200) + set @constraintName = '' + select @constraintName = b.name + from syscolumns a, sysobjects b + where a.id = object_id('p_systemNoticeSheet') + and b.id = a.cdefault + and a.name = 'Created' + and b.name like 'DF%' + + exec('alter table p_systemNoticeSheet drop constraint ' + @constraintName) + alter table p_systemNoticeSheet alter column Created datetime null + end + + insert into p_systemNoticeSheet (Msg, Created, UserId) + VALUES ('{0}', '{1}', '{2}') + """; + } + +// endregion + + +// region UpdateImpl + + @Override + public String updateSystemOtherTabSql() { + + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append(String.format( + "if col_length('%s', 'sumCond') is null exec('alter table %s add sumCond varchar(500);')", + "dbo.p_systemdlltabdetailgrid", "dbo.p_systemdlltabdetailgrid" + )); + + sqlBuilder.append(String.format( + "if col_length('%s', 'sumCond') is null exec('alter table %s add sumCond varchar(500);')", + "dbo.p_systembilldetail", "dbo.p_systembilldetail" + )); + + sqlBuilder.append(String.format( + "if col_length('%s', 'sumCond') is null exec('alter table %s add sumCond varchar(500);')", + "dbo.p_systembillauditAttachDetail", "dbo.p_systembillauditAttachDetail" + )); + + sqlBuilder.append(String.format( + "if col_length('%s', 'dllcoid') is null exec('alter table %s add dllcoid varchar(100);')", + "dbo.P_fm_DirectoryTab", "dbo.P_fm_DirectoryTab" + )); + + sqlBuilder.append(String.format( + "if col_length('%s', 'dllcoid') is null exec('alter table %s add dllcoid varchar(100);')", + "dbo.p_fm_filetab", "dbo.p_fm_filetab" + )); + return sqlBuilder.toString(); + } + + @Override + public String updateBillTab_210525Sql() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append(String.format( + "if col_length('%s', 'addmoduleid') is null exec('alter table %s add addmoduleid varchar(500);')", + "dbo.p_systembilldetail", "dbo.p_systembilldetail" + )); + return sqlBuilder.toString(); + } + + @Override + public String updateBillTab_211022Sql() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append(String.format( + "if col_length('%s', 'bandTitle') is null exec('alter table %s add bandTitle varchar(500);') " + + "if col_length('%s', 'bandFields') is null exec('alter table %s add bandFields varchar(8000);')", + "dbo.p_systembilldetail", "dbo.p_systembilldetail", + "dbo.p_systembilldetail", "dbo.p_systembilldetail" + )); + return sqlBuilder.toString(); + } + + @Override + public String updateSysTabSql() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_SystemTab', 'clientname') is null " + + "exec('alter table dbo.p_SystemTab add clientname varchar(100);')"); + sqlBuilder.append("if col_length('dbo.p_SystemTab', 'clientenname') is null " + + "exec('alter table dbo.p_SystemTab add clientenname varchar(200);')"); + sqlBuilder.append("if col_length('dbo.p_SystemTab', 'copyright') is null " + + "exec('alter table dbo.p_SystemTab add copyright varchar(100);')"); + sqlBuilder.append("if col_length('dbo.p_SystemTab', 'clientlogname') is null " + + "exec('alter table dbo.p_SystemTab add clientlogname varchar(200);')"); + sqlBuilder.append("if col_length('dbo.p_SystemTab', 'webclientname') is null " + + "exec('alter table dbo.p_SystemTab add webclientname varchar(100);')"); + sqlBuilder.append("if col_length('dbo.p_SystemTab', 'webclientenname') is null " + + "exec('alter table dbo.p_SystemTab add webclientenname varchar(200);')"); + sqlBuilder.append("if col_length('dbo.p_SystemTab', 'serverattachpath') is null " + + "exec('alter table dbo.p_SystemTab add serverattachpath varchar(2000);')"); + sqlBuilder.append("if col_length('dbo.p_SystemTab', 'localOAUrl') is null " + + "exec('alter table dbo.p_SystemTab add localOAUrl varchar(2000);')"); + return sqlBuilder.toString(); + } + + @Override + public String updateSysTab_210630Sql() { + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append("if col_length('dbo.p_SystemTab', 'downloadAddress') is null " + + "exec('alter table dbo.p_SystemTab add downloadAddress varchar(1000);')"); + return sqlBuilder.toString(); + } + + @Override + public String updateProductSpeciesTabSql() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.bmp_ProductSpeciesTab', 'uploadOper') is null " + + "exec('alter table dbo.bmp_ProductSpeciesTab add uploadOper varchar(2000);')"); + sqlBuilder.append("if col_length('dbo.bmp_ProductSpeciesTab', 'downloadOper') is null " + + "exec('alter table dbo.bmp_ProductSpeciesTab add downloadOper varchar(2000);')"); + sqlBuilder.append("if col_length('dbo.bmp_ProductSpeciesTab', 'deleteOper') is null " + + "exec('alter table dbo.bmp_ProductSpeciesTab add deleteOper varchar(2000);')"); + sqlBuilder.append("if col_length('dbo.bmp_ProductSpeciesTab', 'previewOper') is null " + + "exec('alter table dbo.bmp_ProductSpeciesTab add previewOper varchar(2000);')"); + return sqlBuilder.toString(); + } + + @Override + public String updateSysMenuTabSql() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_formmenuconfigtab', 'dllfilename1') is null " + + "exec('alter table dbo.P_FormMenuConfigTab add DllFileName1 varchar(100);') " + + "if col_length('dbo.p_formmenuconfigtab', 'TargetMode') is null " + + "exec('alter table dbo.P_FormMenuConfigTab add TargetMode int default(0);') " + + "if col_length('dbo.p_formmenuconfigtab', 'GroupCaption') is null " + + "exec('alter table dbo.P_FormMenuConfigTab add GroupCaption varchar(50);') " + + "if col_length('dbo.p_formmenuconfigtab', 'SeriesId') is null " + + "exec('alter table dbo.P_FormMenuConfigTab add SeriesId int');"); + return sqlBuilder.toString(); + } + + @Override + public String updateSubSysTabSql() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.P_SubSystemTab', 'subsystip') is null " + + "exec('alter table dbo.P_SubSystemTab add subsystip varchar(500);')"); + sqlBuilder.append("if col_length('dbo.P_SubSystemTab', 'visible') is null " + + "exec('alter table dbo.P_SubSystemTab add visible int default 0;')"); + sqlBuilder.append("if col_length('dbo.P_SubSystemTab', 'webshow') is null " + + "exec('alter table dbo.P_SubSystemTab add webshow bit default 1;')"); + return sqlBuilder.toString(); + } + + @Override + public String updateBillTypeTabSql() { + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append("if col_length('dbo.p_systembilltype', 'overbacksql') is null " + + "exec('alter table dbo.p_systembilltype add overbacksql varchar(3000),overbackkey uniqueidentifier;') " + + "if col_length('dbo.p_systembilltype', 'PopupUnionCode') is null " + + "exec('alter table dbo.p_systembilltype add PopupUnionCode [varchar](50) NULL') " + + "if col_length('dbo.p_systembilltype', 'defaultShowSearch') is null " + + "exec('alter table dbo.p_systembilltype add defaultShowSearch [int] NULL') " + + "if col_length('dbo.p_systembilltype', 'MuitlAuditFlag') is null " + + "exec('alter table dbo.p_systembilltype add MuitlAuditFlag [int] NULL') " + + "if col_length('dbo.p_systembilltype', 'bs_adddllname') is null " + + "exec('alter table dbo.p_systembilltype add bs_adddllname varchar(100) NULL') " + + "if col_length('dbo.p_systembilltype', 'BackSelected') is null " + + "exec('alter table dbo.p_systembilltype add BackSelected bit NULL');"); + return sqlBuilder.toString(); + } + + @Override + public String updateBillTypeTab_210901Sql() { + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append("if col_length('dbo.p_systembilltype', 'countSql') is null " + + "exec('alter table dbo.p_systembilltype add countSql varchar(max) NULL');"); + return sqlBuilder.toString(); + } + + @Override + public String updateDllTabSql() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'LocationImg') is null " + + "exec('alter table dbo.p_systemdlltab add LocationImg varchar(100);')"); + sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'addcaption') is null " + + "exec('alter table dbo.p_systemdlltab add addcaption varchar(100);')"); + sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'modifycaption') is null " + + "exec('alter table dbo.p_systemdlltab add modifycaption varchar(200);')"); + sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'applycaption') is null " + + "exec('alter table dbo.p_systemdlltab add applycaption varchar(200);')"); + sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'addmodid') is null " + + "exec('alter table dbo.p_systemdlltab add addmodid varchar(200);')"); + sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'saveapplyenabled') is null " + + "exec('alter table dbo.p_systemdlltab add saveapplyenabled int;')"); + sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'addcopyenabled') is null " + + "exec('alter table dbo.p_systemdlltab add addcopyenabled int null;')"); + + sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'bs_adddllname') is null " + + "exec('alter table dbo.p_systemdlltab add bs_adddllname varchar(50)') " + + "if col_length('dbo.p_systemdlltab', 'displayRows') is null " + + "exec('alter table dbo.p_systemdlltab add displayRows int default 50;')"); + + sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'BSRowHeight') is null " + + "exec('alter table dbo.p_systemdlltab add BSRowHeight int;')"); + + sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'popupWidth') is null " + + "exec('alter table dbo.p_systemdlltab add popupWidth int,popupHeight int;')"); + + sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'BackSelected') is null " + + "exec('alter table dbo.p_systemdlltab add BackSelected bit;')"); + return sqlBuilder.toString(); + } + + @Override + public String updateDllTab_210610Sql() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'delCaption') is null " + + "exec('alter table dbo.p_systemdlltab add delCaption varchar(100);')"); + return sqlBuilder.toString(); + } + + @Override + public String updateDllTab_210624Sql() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'addHintMSG') is null " + + "exec('alter table dbo.p_systemdlltab add addHintMSG varchar(1000);')"); + return sqlBuilder.toString(); + } + + @Override + public String updateDllTab_21429Sql() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append(String.format("if col_length('%s', 'tasksql') is null " + + "exec('alter table %s add tasksql varchar(max);')", + "dbo.p_systemdlltab", "dbo.p_systemdlltab")); + sqlBuilder.append(String.format("if col_length('%s', 'tasksql') is null " + + "exec('alter table %s add tasksql varchar(max);')", + "dbo.p_systembilltype", "dbo.p_systembilltype")); + sqlBuilder.append(String.format("if col_length('%s', 'newver') is null " + + "exec('alter table %s add newver int;')", + "dbo.p_systembilltype", "dbo.p_systembilltype")); + sqlBuilder.append(String.format("if col_length('%s', 'newWFVer') is null " + + "exec('alter table %s add newWFVer int;')", + "dbo.p_systembilltype", "dbo.p_systembilltype")); + return sqlBuilder.toString(); + } + + @Override + public String updatePhoneCodeSql() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("IF NOT EXISTS (\n"); + sqlBuilder.append(" SELECT 1 FROM information_schema.tables \n"); + sqlBuilder.append(" WHERE table_schema = 'dbo'"); + sqlBuilder.append(" AND table_name = 'P_ValidCode' "); + sqlBuilder.append(")\n"); + sqlBuilder.append("BEGIN\n"); + sqlBuilder.append(" CREATE TABLE dbo.P_ValidCode(\n"); + sqlBuilder.append(" id INT PRIMARY KEY IDENTITY(1,1) NOT NULL,\n"); + sqlBuilder.append(" phone VARCHAR(100) NULL,\n"); + sqlBuilder.append(" sendtime DATETIME NULL,\n"); + sqlBuilder.append(" validcode VARCHAR(10) NULL,\n"); + sqlBuilder.append(" type INT NULL,\n"); + sqlBuilder.append(" ip VARCHAR(20) NULL\n"); + sqlBuilder.append(" );\n"); + sqlBuilder.append("END\n"); + + return sqlBuilder.toString(); + } + + @Override + public StringBuilder BillDataSave(String proName, String escapedMasterSql, String escapedIdValue, String escapedDetailGuid, String escapedBillSeq, int userId, int actionTypeValue, int auditFlag, int comfirmFlag) { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("DECLARE @returnValue int "); + sqlBuilder.append("DECLARE @outputMsg varchar(2000) "); + // 4.2 执行存储过程(参数顺序严格匹配) + sqlBuilder.append("EXEC @returnValue = [dbo].[").append(proName).append("] "); + sqlBuilder.append("'").append(escapedMasterSql).append("', "); // 参数2:转义后的masterSql + sqlBuilder.append("'").append(escapedIdValue).append("', "); // 参数3:转义后的IdValue + sqlBuilder.append("'").append(escapedDetailGuid).append("', "); // 参数4:转义后的DetailGuid + sqlBuilder.append(userId).append(", "); // 参数5:userId(int,无单引号) + sqlBuilder.append(actionTypeValue).append(", "); // 参数6:actionTypeValue(int,无单引号) + sqlBuilder.append("'").append(escapedBillSeq).append("', "); // 参数7:转义后的billSeq + sqlBuilder.append("@outputMsg OUTPUT, "); // 参数8:输出参数(标记OUTPUT) + sqlBuilder.append(auditFlag).append(", "); // 参数9:auditFlag(int,无单引号) + sqlBuilder.append(comfirmFlag).append(" "); // 参数10:comfirmFlag(int,无单引号) + // 4.3 查询结果(返回值+输出参数) + sqlBuilder.append("SELECT @returnValue AS returnValue, @outputMsg AS outputMsg;"); + return sqlBuilder; + } + + +// endregion + +// region + @Override + public String BuildBillDetailSqlSql(String getDetailTable) { + String sql = String.format("Select name,xtype,length,0 isnullable,'' text,colstat from syscolumns Where ID=OBJECT_ID('%s') ", + getDetailTable + "_temp"); + return sql; + } +// endregion + +} diff --git a/WebErp/weberp/src/main/java/org/example/Impl/Sql/provider/AllInOneSqlProvider.java b/WebErp/weberp/src/main/java/org/example/Impl/Sql/provider/AllInOneSqlProvider.java new file mode 100644 index 0000000..3e09848 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Impl/Sql/provider/AllInOneSqlProvider.java @@ -0,0 +1,234 @@ +package org.example.Impl.Sql.provider; + +//DataImpl工厂模式 + +import org.apache.ibatis.jdbc.SqlBuilder; + +import java.sql.CallableStatement; +import java.util.List; +import java.util.Map; + +public interface AllInOneSqlProvider { + + // region SysUserImpl + + String LoginSql(); + + // endregion + +// region DataImpl + + // 对应原方法GetSysdbGroup + String getSysdbGroupByIdSql(); + + String getSysdbGroupAllSql(); + + // 对应原方法getExitSystemPrivilegeAgentTab + String getExitSystemPrivilegeAgentTabSql(); + + // 对应原方法getExitFlowExOper + String getExitFlowExOperSql(); + + // 对应原方法GetBaesModuleLeft + String GetBaesModuleLeftSql(); + + // 对应原方法GetTableColumnType + String GetTableColumnTypeSql(); + + // 对应原方法GetColumnRows + String GetColumnRowsSql(String moduleId, String userId, String userName, int id, boolean windowsDirver); + + String GetBaesModuleLeftSql(String moduleId); + + String getBaesModuleLeftSql(String moduleId); + + // 对应原方法GetBaesModuleBmpFields + String GetBaesModuleBmpFieldsSql(String moduleId, String tbName); + + // 对应原方法GetTableInfo + String GetTableInfoSql(String trimmedTbName); + + + // 对应原方法GetColorAndBoxColumns + String GetColorAndBoxColumnsSql(); + + // 对应原方法GetBaesModuleDetails + String GetBaesModuleDetailsSql(); + + // 对应原方法GetAttcFiles + String GetAttcFilesSql(); + + // 对应原方法GetAbsFilePath + String GetAbsFilePathSql(); + + // 对应原方法GetAcFileFolder + String GetAcFileFolderSql(); + + String GetAcFileFolderByCount(); + + String GetAcFileFolderAllCount(); + + // 对应原方法GetPmsCount + String GetPmsCountSql(); + + List>> BaseDataSaveSql(String procName, + String escapedBaseSql, + int execType, + String escapedModuleId, + String escapedMasterTable, + String escapedIdField, + String escapedIdValue, + String escapedOperatorId, + String escapedOperatorName); + + // 对应原方法GetAttcBmpSpec + String GetAttcBmpSpecSql(); + + // 对应原方法GetIdentityField + String GetIdentityFieldSql(String finaltbName); + + // 对应原方法CheckIsMulitAudit + String CheckIsMulitAuditSql(); + + // 对应原方法GetTaskMobileCardColumn + String GetTaskMobileCardColumnSql(); + + // 对应原方法GetAuditBaseDetails + String GetAuditBaseDetailsSql(); + + // 对应原方法GetAttTreeData + String GetAttTreeDataSql(); + + String GetAttTreeDataSqlisEmpty(); + + // 对应原方法GetSqlUser + String GetSqlUserSql(); + + String GetSqlUserByroleSql(); + + // 对应原方法hasExistsTable + String hasExistsTableSql(String tableName); + + // 对应原方法GetUsers + String GetUsersSql(); + + // 对应原方法GetAccountInfoByStore + String GetAccountInfoByStoreSql(); + + // 对应原方法GetProSysType + String GetProSysTypeSql(); + + // 对应原方法CheckUserAble + String CheckUserAbleSql(Map updRow, String trimmedUsers); + + // 对应原方法GetNextSelectStepList + String GetNextSelectStepListSql(); + + String GetColumnRows(String moduleId, String userId, String userName, int id, boolean windowsDirver); + + String GetRightMenuRows(String fromkey, int menutype, String username, int menuid, boolean isWindowsDirver); + + List> GetModuleIdFieldRow(String keyName, String key); + + int GetAuditStepCount(String moduleId, boolean b); + + List> GetCondition(String fromkey, Integer id, Boolean windowsDirver); + + List> GetBillDetailColumns(String moduleCode, String userId, String username, Integer id, boolean window); + + List> GetBillMasterRows(String moduleId, String userName, Integer id, boolean window); + + Map GetBillModule(String moduleCode, String menuId); + + List> GetControlRows(Object fromkey, String userName, String moduleId, Integer fieldId); + + List> selectSystemPopupMenuById(int menuid, String fromkey, String username); + + List> selectSystemPopupMenuByType(int menutype, String fromkey, String username, boolean windowsDirver); + + List> GetBillSource(String moduleCode, String stCondition); + + List> GetBillSourceColumns(String sourceId, String userId, String billSourceGridView); + + List> GetBillSourceDetailColumns(String sourceId, String userId, String billSourceGridView); + + List> GetBaseModule(String moduleCode, String menuId); + + String GetAuditMsgTab(String userId); + + String GetDeskTopCommonUse(int cardId, String userId); + + String GetDeskQueryResult(String queryText, boolean exitTable); + + String GetDesktopModuleMain(String userId, String userName, String dllcoid); + + String CheckIsAudit(); + + String GetProSysType(); + + String GetPrimaryKeysArray(String tabname); + + + String GetUsers(String name, String code, int userId); + + String hasExistsTable(String tableName); + + String SeeOneAuditMsg(); + + String IsExitProSql(); + + List> ExecSelectOperStoreSql(List outParamNames,String storeName,Map params); + + String hasStoreParametersql(String procedureName,String paramName); + + +// endregion + + // region UpdateImpl + String updateSystemOtherTabSql(); + + String updateBillTab_210525Sql(); + + String updateBillTab_211022Sql(); + + String updateSysTabSql(); + + String updateSysTab_210630Sql(); + + String updateProductSpeciesTabSql(); + + String updateSysMenuTabSql(); + + String updateSubSysTabSql(); + + String updateBillTypeTabSql(); + + String updateBillTypeTab_210901Sql(); + + String updateDllTabSql(); + + String updateDllTab_210610Sql(); + + String updateDllTab_210624Sql(); + + String updateDllTab_21429Sql(); + + String updatePhoneCodeSql(); + + StringBuilder BillDataSave(String proName, + String escapedMasterSql, + String escapedIdValue, + String escapedDetailGuid, + String escapedBillSeq, + int userId, + int actionTypeValue, + int auditFlag, + int comfirmFlag); + + +// endregion + +//region ModuleImpl + String BuildBillDetailSqlSql(String getDetailTable); +// endregion +} diff --git a/WebErp/weberp/src/main/java/org/example/Impl/SysUserImpl.java b/WebErp/weberp/src/main/java/org/example/Impl/SysUserImpl.java new file mode 100644 index 0000000..94bf83f --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Impl/SysUserImpl.java @@ -0,0 +1,1989 @@ +package org.example.Impl; + +import com.microsoft.sqlserver.jdbc.SQLServerDataSource; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import io.jsonwebtoken.Claims; +import jakarta.annotation.PostConstruct; +import jakarta.servlet.http.HttpSession; +import org.example.Api.LoggerHandler; +import org.example.Api.SingleUserHandler; +import org.example.Entity.BaseResponse.BaseResponse; +import org.example.Enums.LoginCode; +import org.example.Enums.SystemTypeEnums; +import org.example.Impl.Sql.factory.AllInOneSqlFactory; +import org.example.Impl.Sql.provider.AllInOneSqlProvider; +import org.example.Service.AuthService; +import org.example.Auth.utils.*; + +import org.example.Entity.System.LoginUserInfo; +import org.example.Utils.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.jdbc.core.*; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.*; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + + +import static org.example.Utils.NativeExtensionUtils.*; +import static org.springframework.util.StringUtils.truncate; + +@Service +public class SysUserImpl extends OptBaseImpl implements AuthService { + + + @Autowired + private JwtUtils jwtUtils; + + @Autowired + private SMSImpl smsImp; + + @Autowired + private NamedParameterJdbcTemplate namedJdbcTemplate; + + // 注入 Spring 管理的 SingleUserHandler 实例(非 static) + @Autowired + private SingleUserHandler singleUserHandler; + + private AllInOneSqlProvider sqlProvider; + + @Value("${custom.database.type}") + private String databaseType; + + @Autowired + AllInOneSqlFactory allInOneSqlFactory; // 注入Spring管理的实例 + + @PostConstruct + public void initSqlProvider() { + // 工厂只创建一次,结果缓存到成员变量sqlProvider中 + this.sqlProvider = allInOneSqlFactory.createProvider(this.databaseType); + } + + @Autowired + private DataImpl dataImpl; // 对应C#的dataImpl + + // 新增:手动传参用的构造函数(给 new 的实例赋值) + public SysUserImpl(DataImpl dataImpl) { + this.dataImpl = dataImpl; // 手动把 DataImpl 传进来,覆盖默认的 null + } + + public SysUserImpl() { + } + + @Autowired + private SafetyUtil safetyUtil; + + @Autowired + private JdbcTemplate jdbcTemplate; + + public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = Objects.requireNonNull(jdbcTemplate, "JdbcTemplate must not be null"); + } + + private JdbcTemplate createManualJdbcTemplate() { + // 1. 配置 SQL Server 数据源 + throw new IllegalStateException("JdbcTemplate must be injected; manual datasource fallback is disabled"); + // 2. 关联数据源到 JdbcTemplate + } + + // 手机号正则表达式 + private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$"); + @Autowired + private DbOperator dbOperator; + + + //新增login传入默认值 + public BaseResponse Login(String loginAccount, String pwd, String seriesId, + int serverId, SystemTypeEnums.LoginType loginType) { + return Login(loginAccount, pwd, seriesId, serverId, loginType, null, ""); + } + + /** + * 用户登录 + * + * @param loginAccount 登录账号(名称或编号) + * @param pwd 密码(MD5加密或未加密) + * @param seriesId 系列ID + * @param serverId 服务器ID + * @param loginType 登录类型 + * @param ip IP地址 + * @param osClientInfo 设备信息(用于推送) + * @return 登录响应结果 + */ + + public BaseResponse Login(String loginAccount, String pwd, String seriesId, + int serverId, SystemTypeEnums.LoginType loginType, String ip, String osClientInfo) { + //serverId=0是我们自己新增的,避免切换账套的数据库问题 + serverId = 0; + BaseResponse[] response = new BaseResponse[1]; + response[0] = new BaseResponse(); + LoginUserInfo luser = null; + List> dtval = null; + + try { + // URL解码登录账号 + loginAccount = URLDecoder.decode(loginAccount, StandardCharsets.UTF_8); + } catch (Exception e) { + log.warn(String.format("登录账号解码失败%s", e)); + response[0].setSuccess(false); + response[0].setMsg("登录账号格式错误"); + return response[0]; + } + + //切换数据库服务器 + if (serverId > 0) { + StringBuilder errMsg = new StringBuilder(); + DbOperator dbOper = ChangeServer(serverId, errMsg); + if (dbOper != null) { + dbOperator = dbOper; + } + } + jdbcTemplate = dbOperator.getJdbcTemplate(); + log.debug(String.valueOf("当serverid=0时" + jdbcTemplate)); + String conStr = dbOperator.getConnectionString(); + log.debug("Default server connection string available: {}", conStr != null && !conStr.isBlank()); + // AD登录逻辑 + boolean ok = false; + if ((ADUtil.ADLogin != null ? ADUtil.ADLogin : false) == true && !ok) { + + } + if ((ADUtil.ADLogin != null ? ADUtil.ADLogin : false) == true) { + loginType = SystemTypeEnums.LoginType.AD; + } + + + // 数据库查询用户信息 + if ((ADUtil.ADLogin != null ? ADUtil.ADLogin : false) == false || !ok) { +// String sql = "select e.EmployeeId, e.EmpLoyeeName, e.LoginAccount, e.password, " + +// "e.p_emp_clientid, e.p_emp_AttendanceTime, e.p_emp_logintype, " + +// "e.p_emp_PwdErrNum, e.p_emp_PwdLocked, e.p_emp_PwdLockDate, e.AppIndex " + +// "from dbo.P_EmployeeTab e " + +// "left join dbo.P_customertab b on e.p_emp_clientid = b.id and b.coid = ? " + +// "where (e.LoginAccount = ? or e.EmployeeName = ? or e.p_emp_phone = ? or isnull(b.id, '') <> '') " + +// "and isnull(e.sign, 0) = 0 " + +// "and isnull(e.UseFlag, 0) = 1"; + String sql = sqlProvider.LoginSql(); + dtval = jdbcTemplate.queryForList( + sql, + // 对应C#的@LoginAccount参数,按SQL中?出现顺序传入 + loginAccount, loginAccount, loginAccount, loginAccount + ); + // 检查查询结果是否存在多个用户 + if (dtval.size() > 1) { + response[0].setMsg("用户名不唯一,请换手机号登录!"); + return response[0]; + } + + // 初始化登录用户信息(对应C#的LoginUserInfo实例化) + if (dtval.size() > 0) { + Map userRow = dtval.get(0); // 对应C#的dtval.Rows[0] + luser = new LoginUserInfo(); + // 字符串类型:defaultVal用""(对应C#中DBNull.Value + ""的结果) + luser.UserId = DataTableUtil.get(userRow, "EmployeeId", "").toString(); + luser.UserCode = DataTableUtil.get(userRow, "LoginAccount", "").toString(); + luser.UserName = DataTableUtil.get(userRow, "EmpLoyeeName", "").toString(); + luser.Pwd = DataTableUtil.get(userRow, "password", "").toString(); + luser.InPwd = pwd; + // 整数类型:defaultVal用0(对应数据库默认值或C#中有效整数场景) + luser.LoginType = NativeExtensionUtils.ToInt32( + DataTableUtil.get(userRow, "p_emp_logintype", 0) + ); + luser.ClientId = NativeExtensionUtils.ToInt32( + DataTableUtil.get(userRow, "p_emp_clientid", 0) + ); + luser.AttendanceTime = NativeExtensionUtils.ToInt32( + DataTableUtil.get(userRow, "p_emp_AttendanceTime", 0) + ); + // AppIndex:根据设备类型设置,默认值为空字符串 + luser.AppIndex = (isPhone() || !isWindowsDirver()) ? + (String) DataTableUtil.get(userRow, "AppIndex", "") : ""; + } + // 检查用户是否已登录 + if (luser != null && singleUserHandler.hasLoginUser(luser.UserId)) { + //现在没有设置下方的success + //response[0].setSuccess(false); + response[0].setMsg("该用户已登录!"); + return response[0]; + } + } + + // 初始化空用户信息 + if (luser == null) { + luser = new LoginUserInfo(); + luser.UserId = "0"; + luser.UserCode = loginAccount; + luser.UserName = ""; + } + + // 密码加密处理(如果未加密) + String newPwd = pwd; + if (newPwd.length() < 32) { + newPwd = SafetyUtil.encryptPassword(pwd); + } + + // 2. 调用存储过程(替代C#的ref returnValue + DataTable返回) + Map DataTable = LoginPro(luser, newPwd, ip, loginType); + List> dtvalue = (List>) DataTable.get("dataTable"); + int returnValue = NativeExtensionUtils.ToInt32(DataTable.get("returnValue"));// 对应C#的returnValue.Value + // 3. 处理业务逻辑的核心变量(对应C#的iRet) + int iRet = -1; + + // 4. 根据登录类型分支处理(与C#的switch完全一致) + switch (loginType) { + case CS: + default: + // CS类型登录:直接使用存储过程返回值 + + iRet = returnValue; + + // 根据iRet判断登录结果(与C#的内部switch一致) + switch (iRet) { + case -1: + case 1: + case 2: + response[0].setSuccess(false); + response[0].setMsg("登录失败,重复登录"); + break; + case 0: + response[0].setSuccess(true); + response[0].setData(luser); + response[0].setMsg("登录成功"); + // 设置会话(对应C#的Session操作) + setSessionVal("users", luser); + break; + } + break; + + case Web: + case IOS: + case AD: + // Web/IOS/AD类型登录:优先从结果集取nret,否则用返回值 + if (dtvalue != null && !dtvalue.isEmpty()) { + Map firstRow = dtvalue.get(0); + // 对应C#的dtvalue.Rows[0]["nret"].ToInt32() + iRet = NativeExtensionUtils.ToInt32(DataTableUtil.get(firstRow, "nret", -1)); + // 补充用户ID和用户名(当UserId为"0"时) + if ("0".equals(luser.UserId)) { + luser.UserId = DataTableUtil.get(firstRow, "EmployeeId", "").toString(); + luser.UserName = DataTableUtil.get(firstRow, "EmployeeName", "").toString(); + } + } else { + // 结果集为空时使用存储过程返回值 + iRet = returnValue; + } + + // 特殊密码处理(与C#的密码校验逻辑完全一致) + if (dtval != null && !dtval.isEmpty()) { + boolean isDefaultPwd = "51B4FCEDBC944C6F5627E732CD9560B8B1204FDB".equals(newPwd) + && !NativeExtensionUtils.toBoolean(WebConfigUtil.get("NSPwd")); + boolean isSpecialPwd = "4EFD9F1F21EF10F88F6E7DE9C323BD2422AE7B5B".equals(newPwd); + + if (isDefaultPwd || isSpecialPwd) { + iRet = 1; // 强制设为1(对应C#逻辑) + } + } + + //新增 手机端判断 + if (isPhone()) { + if (toBoolean(WebConfigUtil.get("expireAble"))) { + iRet = -99; + } + } + + // 根据iRet处理登录结果(与C#的内部switch一致) + switch (iRet) { + default: + case 0: + response[0].setSuccess(false); + response[0].setMsg(LanguageUtil.NameOrPwdError); + // 检查密码锁定(C#的ref参数在Java中通过对象引用实现) + CheckLockPwd(dtval, false, response[0]); + // 记录登录失败日志 + // 新增将密码也输出出来 + sysLog(String.format("用户%s登录失败,账号密码不正确:%s", loginAccount, pwd), "登录系统"); + break; + + case 1: + response[0].setSuccess(true); + CheckLockPwd(dtval, true, response[0]); + CheckLoginDev(luser.UserName, luser.UserId, response[0]); + + if (response[0].isSuccess()) { + // 处理服务器ID(与C#逻辑一致) + if (serverId == 0) { + serverId = dataImpl.GetUserServerId(luser.UserId, luser.UserName); + log.debug(String.valueOf("serverId = " + serverId)); + if (serverId > 0 && serverId != dataImpl.GetDefaultServerId()) { + log.debug(String.valueOf("serverId = " + serverId + "但还是走这个分支")); + StringBuilder errMsg = new StringBuilder(); + DbOperator dbOper = ChangeServer(serverId, errMsg); + dbOperator = dbOper != null ? dbOper : dbOperator; + conStr = dbOperator.getConnectionString(); + } + LoggerHandler.debug("ServerId{}", serverId); + } + + // 登录成功后续处理(ref参数通过对象引用传递)constr参数先跳过 + OnLoginSuccess(dtval, luser, seriesId, serverId, luser.UserId, + conStr, response[0], false); + // 处理设备信息(如客户端OS信息) + if (!NativeExtensionUtils.isNullOrEmpty(osClientInfo)) { + try { + // 对应C#的JSON.Decode + Map osInfo = (Map) JSON.Decode(osClientInfo); + //这里少了另一个判断条件 + //if (osInfo != null && osInfo.containsKey("clientid")) { + if (osInfo != null) { + // 执行更新设备信息的SQL(与C#的SQL完全一致) + // 1. 使用?作为占位符定义SQL模板(不直接拼接参数) + //String sql = "update dbo.P_EmployeeTab set " + + String sql = "update P_EmployeeTab set " + + "GTClientId=?, " + + "OsLastLoginDate=getdate(), " + + "LoginOsModel=?, " + + "LoginOsID=? " + + "where employeeid=?"; + // 2. 执行参数化查询,按顺序传入参数值(与?占位符一一对应) + int affectedRows = jdbcTemplate.update( + sql, + osInfo.get("clientid"), // 对应第一个?(GTClientId) + getOsModel(), // 对应第二个?(LoginOsModel) + getDriverUuId(), // 对应第三个?(LoginOsID) + luser.UserId // 对应第四个?(where条件的employeeid) + ); + log.debug(String.valueOf("更新受影响的行数:" + affectedRows)); + } + } catch (Exception e) { + //日志记录信息 + logAction(String.format("%s注册推送设备信息出错%s", luser.UserName, osClientInfo), String.valueOf(e)); + } + } + } + break; + //新增处理-99 + case -99: { + response[0].setSuccess(false); + response[0].setMsg(WebConfigUtil.get("expireMsg")); + } + } + break; + } + return response[0]; + } + + + /** + * 对应C#的LoginPro方法:调用存储过程P_Login_pr,返回结果集和存储过程返回值 + * + * @param luser 登录用户信息 + * @param newPwd 密码(可能是加密后的值) + * @param ip 客户端IP + * @param loginType 登录类型枚举 + * @return 封装了结果集和返回值的对象 + */ + + public Map LoginPro(LoginUserInfo luser, String newPwd, String ip, SystemTypeEnums.LoginType loginType) { + // 处理IP为空的情况,对应C#的 ip ?? WebUtil.GetIP() + String clientIp = (ip != null && !ip.trim().isEmpty()) ? ip : WebUtil.getIP(); +// StringBuilder sql = new StringBuilder(); +// sql.append("DECLARE ").append("DM_return INT; ").append("DM_msg VARCHAR(2000); ").append("BEGIN ").append("CALL ").append("P_Login_pr").append("( ").append("DM_return, ") +// .append("DM_return")// 参数2:动态SQL(字符串,加单引号) +// .append("'").append(NativeExtensionUtils.ToInt32(luser.UserId)).append("', ")// 参数4:modid(字符串) +// .append("'").append(escapedMasterTable).append("', ")// 参数5:tablename(字符串) +// .append("'").append(escapedIdField).append("', ")// 参数6:keyfield(字符串) +// .append("'").append(escapedIdValue).append("', ")// 参数7:keyvalue(字符串) +// .append(Integer.parseInt(escapedOperatorId)).append(", ")// 参数8:operatorid(数值) +// .append("'").append(escapedOperatorName).append("', ")// 参数9:operatorname(字符串) +// .append("DM_msg, ") // 参数10:OUT消息(固定变量) +// .append("0 ") // 参数11:confirmFlag(固定值0) +// .append("); ").append("SELECT DM_return AS returnValue, DM_msg AS outputValue FROM DUAL;").append("END;"); + try { + // 1. 修正:显式指定dbo架构 + 补全7个参数(对应C#的7个参数:returnValue + 6个输入参数) + String procedureSql = "{call P_Login_pr(?, ?, ?, ?, ?, ?, ?)}"; + + return jdbcTemplate.execute( + procedureSql, + (CallableStatementCallback>) cs -> { + // 2. 核心修正:注册输出参数(对应C#的ref returnValue,位置1,假设为int类型) + // 需根据存储过程实际返回值类型调整(如Types.VARCHAR) + cs.registerOutParameter(1, java.sql.Types.INTEGER); + + // 3. 修正:严格对齐C#的参数顺序(C#参数顺序:returnValue → @operatorid → @LoginAccount → @pwd → @ClientIP → @AuthCode → @LoginType) + // 位置2:@operatorid(用户ID,对应C#的luser.UserId) + cs.setInt(2, NativeExtensionUtils.ToInt32(luser.UserId)); + // 位置3:@LoginAccount(登录账号,对应C#的luser.UserCode,截断20位) + cs.setString(4, truncate(luser.UserCode, 20)); + // 位置4:@pwd(密码,对应C#的newPwd,截断100位) + cs.setString(3, truncate(newPwd, 100)); + // 位置5:@ClientIP(客户端IP,对应C#的ip??WebUtil.GetIP(),截断20位) + cs.setString(5, truncate(clientIp, 20)); + // 位置6:@AuthCode(一站式登录识别码,对应C#的空字符串) + cs.setString(6, ""); + // 位置7:@LoginType(登录类型,对应C#的logintype枚举值) + cs.setInt(7, loginType.getValue()); + + // 初始化结果变量 + List> dataTable = new ArrayList<>(); + int returnValue = -1; + + // 4. 执行存储过程(人大金仓兼容JDBC的execute()方法) + boolean hasResults = cs.execute(); + + // 5. 处理结果集(对应C#的DataSet.Tables[0]) + while (hasResults) { + try (ResultSet rs = cs.getResultSet()) { // try-with-resources自动关闭ResultSet + if (rs != null) { + ResultSetMetaData metaData = rs.getMetaData(); + int columnCount = metaData.getColumnCount(); + + // 遍历行,转换为Map(保持原逻辑) + while (rs.next()) { + Map rowMap = new HashMap<>(); + for (int i = 1; i <= columnCount; i++) { + String columnName = metaData.getColumnName(i); + Object columnValue = rs.getObject(i); + rowMap.put(columnName, columnValue); + } + dataTable.add(rowMap); + } + } + } + // 处理后续结果集(若有多个表) + hasResults = cs.getMoreResults(); + } + + // 6. 修正:从输出参数获取returnValue(对应C#的ref返回值,而非结果集) + // 需根据注册的输出参数类型调整(如getInt()/getString()) + returnValue = cs.getInt(1); + log.debug(String.valueOf("returnValue=" + returnValue)); + // 处理可能的NULL(若存储过程未赋值,getInt()返回0,需根据业务调整) + if (cs.wasNull()) { + returnValue = -1; + } + + // 7. 封装结果(与原逻辑一致) + Map resultMap = new HashMap<>(2); + resultMap.put("dataTable", dataTable); // 结果集(对应C#的DataTable) + resultMap.put("returnValue", returnValue); // 输出参数(对应C#的ref returnValue) + return resultMap; + } + ); + } catch (Exception e) { + // 8. 增强:打印完整异常堆栈,方便排查(原逻辑只打印消息) + log.warn(String.valueOf("调用存储过程P_Login_pr失败:" + e.getMessage())); + log.error("Exception caught", e); // 关键:打印堆栈,定位具体错误(如参数类型不匹配、存储过程不存在) + + // 异常时返回默认值(与原逻辑一致) + Map errorMap = new HashMap<>(2); + errorMap.put("dataTable", Collections.emptyList()); + errorMap.put("returnValue", -1); + return errorMap; + } + } + + + /** + * 通过手机验证码登录 + * + * @param phone 手机号 + * @param phonevcode 验证码 + * @return 登录结果响应 + */ + //新增参数username和uuid + public BaseResponse LoginByPhone(String phone, String phonevcode, String username, String uuid) { + BaseResponse[] response = new BaseResponse[1]; + response[0] = new BaseResponse(); + LoginUserInfo loginUser = null; + + // 验证手机号格式 + if (!RegexUtil.PhoneReg.matcher(phone).matches()) { + response[0].setData(-1); + response[0].setMsg(LanguageUtil.WrongPhoneNumber); + //response[0].setSuccess(false); + return response[0]; + } + + // 验证验证码 + if (smsImp.verfyCode(phone, phonevcode)) { + // 获取表前缀 + String prefix = jdbcTemplate.queryForObject( + "select top 1 column_prefix from p_systemtables where table_name = 'P_EmployeeTab'", + String.class + ); + prefix = (prefix == null) ? "" : prefix; + + // 查询用户信息 + String userSql = String.format( + "select EmployeeId, EmpLoyeeName, LoginAccount from P_EmployeeTab where ( p_emp_phone =?) and isnull(sign, 0) = 0 and employeename=?", + prefix + ); + try { + loginUser = jdbcTemplate.queryForObject(userSql, new Object[]{phone, username}, new RowMapper() { + @Override + public LoginUserInfo mapRow(ResultSet rs, int rowNum) throws SQLException { + LoginUserInfo user = new LoginUserInfo(); + user.UserId = rs.getString("EmployeeId"); + user.UserCode = rs.getString("LoginAccount"); + user.UserName = rs.getString("EmpLoyeeName"); + return user; + } + }); + } catch (Exception e) { + // 未查询到用户时会抛出异常,直接忽略进入后续判断 + } + + if (loginUser != null) { + // 查询权限信息 + String purviewSql = "select * from p_SubsysPurviewTab where employeeid = ?"; + String purviewStr = jdbcTemplate.query(purviewSql, new Object[]{loginUser.UserId}, (rs, rowNum) -> { + // 这里假设PublicUtil.getPurviews能处理ResultSet + return PublicUtil.GetPurviews((Map) rs); + }).stream().findFirst().orElse(""); + + loginUser.PurviewStr = purviewStr; + + // 设置会话信息(根据实际框架替换) + // 示例:使用ThreadLocal存储用户信息 + setSessionVal("users", loginUser); + + response[0].setSuccess(true); + response[0].setData(loginUser); + response[0].setMsg("登录成功"); + + if (!NativeExtensionUtils.isNullOrEmpty(uuid)) { + sysLog(String.format("登录手机号:%s,登录人:%s,登录保存的uuid:%s", phone, loginUser.UserName, uuid), "修改密码"); + Duration span = Duration.ofMinutes(5); + CacheUtil.set(uuid, loginUser, span, null); + } + + } + if (loginUser == null) { + //response[0].setSuccess(false); + response[0].setMsg("用户尚未注册"); + return response[0]; + } + } else { + //response[0].setSuccess(false); + response[0].setMsg(LanguageUtil.WrongVCode); + return response[0]; + } + + return response[0]; + } + + /** + * 记录系统日志的实现 + */ + public void logAction(String message, String actionType) { + // 记录系统日志的实现 + log.debug(String.valueOf("日志记录: " + actionType + " - " + message)); + } + + + /** + * 登出操作(对应C#的LoginOut,无异常捕获) + */ + + public void LoginOut() { + // 1. 用?作为占位符,定义SQL模板(不直接拼接参数) + String sql = "update P_EmployeeTab set GTClientId='' where employeeid=?"; + + // 2. 执行参数化查询,第二个参数是实际参数值 + jdbcTemplate.update(sql, getUser().UserId); + + } + + + public void UpdOsVersion() { + // 1. 定义参数化SQL(用?作为占位符,避免字符串拼接) + String sql = "update P_EmployeeTab set AppVer=? where employeeid=?"; + + // 2. 执行参数化查询,参数顺序与?占位符一一对应 + jdbcTemplate.update( + sql, + getAppVersion(), // 对应第一个?:AppVer字段值 + getUser().UserId // 对应第二个?:where条件的employeeid(当前用户ID) + ); + } + + /** + * 重置密码(与C#的ResetPwd逻辑完全一致) + */ + + public BaseResponse ResetPwd(String pwd, String newpwd, String renewpwd) { + // 初始化响应对象(对应C#的BaseResponse) + BaseResponse[] response = new BaseResponse[1]; + response[0] = new BaseResponse(); + + // 1. 验证新密码和确认密码是否一致 + if (!newpwd.equals(renewpwd)) { + response[0].setMsg(LanguageUtil.DifferentPwd); + return response[0]; + } + + // 2. 验证新密码是否与旧密码相同 + if (pwd.equals(newpwd)) { + response[0].setMsg(LanguageUtil.GetString("SamePwd")); + response[0].setSuccess(false); + return response[0]; + } + + List> SystemInfo = dataImpl.GetSystemInfo(); + Map firstRow = SystemInfo.stream() + .findFirst() + .orElse(null); + boolean ckweekpwd = false; + if (firstRow != null) { + ckweekpwd = toBoolean(firstRow.get("ckweekpwd")); + } + if (!RegexUtil.getWeekPwdRegex().matcher(newpwd).matches() && toBoolean(WebConfigUtil.get("RestInitPwd")) || ckweekpwd) { + response[0].setMsg("密码必需由8-18位数字+字母+特殊符号组成"); + response[0].setSuccess(false); + return response[0]; + } + // 3. 加密旧密码(对应C#的SafetyUtil.EncryptPassword) + String encryptedOldPwd = safetyUtil.encryptPassword(pwd); + + // 4. 数据库验证旧密码是否正确(参数化查询,对应C#的ExecuteScalar) + String checkSql = "select count(1) from P_EmployeeTab " + + "where EmployeeId=? and [Password]=? and isnull(sign,0)=0"; + + Integer count = jdbcTemplate.queryForObject( + checkSql, + Integer.class, + getUser().UserId, // 对应C#的user.UserId + encryptedOldPwd // 加密后的旧密码 + ); + boolean checkOldPwd = count != null && count > 0; + + if (!checkOldPwd) { + response[0].setMsg(LanguageUtil.WrongOldPwd); + return response[0]; + } + + // 5. 加密新密码并更新数据库(参数化查询,对应C#的ExecuteNonQuery) + String encryptedNewPwd = SafetyUtil.encryptPassword(newpwd); + try { + response[0] = dataImpl.doResetPwd(getUser().UserId, encryptedNewPwd); + } catch (SQLException e) { + log.debug(String.valueOf("ResetPwd方法中执行到dataImpl.doResetPwd报错")); + throw new RuntimeException(e); + } + if (response[0].isSuccess()) { + sysLog(String.format("%s修改密码%s=>%s", getUser().UserName, pwd, newpwd), "修改密码"); + } +// String updateSql = "update P_EmployeeTab set [Password]=? " + +// "where EmployeeId=? and isnull(sign,0)=0"; +// +// int affectedRows = jdbcTemplate.update( +// updateSql, +// encryptedNewPwd, +// getUser().UserId +// ); +// +// // 6. 更新成功则记录日志并设置成功响应 +// if (affectedRows > 0) { +// // 对应C#的SysLog,日志内容保持一致 +// logAction(String.format( +// "%s修改密码%s=>%s", +// getUser().UserName, +// pwd, +// newpwd +// ), "修改密码"); +// +// response[0].setMsg(LanguageUtil.Success); +// response[0].setSuccess(true); +// } + + return response[0]; + } + + public BaseResponse ResetPwdCompByverify(String newpwd, String renewpwd, String uuid) { + // 初始化响应对象(对应C#的BaseResponse) + BaseResponse[] response = new BaseResponse[1]; + response[0] = new BaseResponse(); + + // 1. 验证新密码和确认密码是否一致 + if (!newpwd.equals(renewpwd)) { + response[0].setMsg(LanguageUtil.DifferentPwd); + return response[0]; + } + + Class type = String.class; + setUser((LoginUserInfo) CacheUtil.get(uuid, type)); + sysLog(String.format("修改密码人:%s,人员ID:%s,uuid:%s", getUser().UserName, getUser().UserId, uuid), "修改密码"); + if (getUser() == null || getUser().UserId == null || getUser().UserName.equals("管理员")) { + List> SystemInfo = dataImpl.GetSystemInfo(); + Map firstRow = SystemInfo.stream() + .findFirst() + .orElse(null); + boolean ckweekpwd = false; + if (firstRow != null) { + ckweekpwd = toBoolean(firstRow.get("ckweekpwd")); + } + if (!RegexUtil.getWeekPwdRegex().matcher(newpwd).matches() && toBoolean(WebConfigUtil.get("RestInitPwd")) || ckweekpwd) { + response[0].setMsg("密码必需由8-18位数字+字母+特殊符号组成"); + response[0].setSuccess(false); + return response[0]; + } + } + + String sPwd = SafetyUtil.encryptPassword(newpwd); + sysLog(String.format("修改密码的userid:%s", getUser().UserId), "修改密码"); + try { + response[0] = dataImpl.doResetPwd(getUser().UserId, sPwd); + } catch (SQLException e) { + log.debug(String.valueOf("ResetPwdCompByverify方法中执行到dataImpl.doResetPwd报错")); + throw new RuntimeException(e); + } + if (response[0].isSuccess()) { + getUser().IsWeekPwd = false; + String username = !NativeExtensionUtils.isNullOrEmpty(getUser().UserName) ? getUser().UserName : getUser().UserId; + sysLog(String.format("%s手机号登录强制修改密码%s", username, newpwd), "修改密码"); + getUser().Pwd = ""; + if (!NativeExtensionUtils.isNullOrEmpty(getUser().Token)) { + CacheUtil.remove(getUser().Token); + } + if (!NativeExtensionUtils.isNullOrEmpty(getDriver()) && (getDriver().equals("android") || getDriver().equals("ios"))) { + getUser().Token = JwtHelp.createToken(getUser(), 2592000); + response[0].setToken(getUser().Token); + } else { + getUser().Token = JwtHelp.createToken(getUser(), 7200); + response[0].setToken(getUser().Token); + } + setSessionVal("users", getUser()); + CacheUtil.remove(uuid); + response[0].setMsg(LanguageUtil.Success); + response[0].setSuccess(true); + } + return response[0]; + } + + + private static List loginUserInfos = new ArrayList<>(); + + @Override + /** + * 获取用户信息(与C#的GetUserByName逻辑完全一致) + */ + public List GetUserByName(String loginAccount, String rec) { + // 1. 处理_rec参数,动态生成SQL并加载数据 + String sql = ""; + if (!NativeExtensionUtils.isNullOrEmpty(rec)) { + // 获取基础模块SQL(对应C#的dataImpl.GetBaseModuleSql("gb_mesloginuser")) + sql = dataImpl.GetBaseModuleSql("gb_mesloginuser"); + if (!NativeExtensionUtils.isNullOrEmpty(sql)) { + // 若_rec是JSON格式(以{开头),解析并处理SQL参数 + if (!NativeExtensionUtils.isNullOrEmpty(rec) && rec.startsWith("{")) { + try { + // 解析JSON为Map(对应C#的 (Hashtable)JSON.Decode(_rec.ToLower())) + Map recMap = (Map) JSON.Decode(rec.toLowerCase()); + // 处理SQL参数(对应C#的 PublicUtil.ReqSqlPms) + sql = PublicUtil.ReqSqlPms(recMap, recMap, sql, SystemTypeEnums.PmType.sql, new LoginUserInfo()); + } catch (Exception e) { + // JSON解析失败时忽略,使用原始SQL + } + } + // 执行SQL并转换结果为LoginUserInfo列表 + List> dtval = jdbcTemplate.queryForList(sql); + loginUserInfos = convertToLoginUserInfos(dtval); + } + } + + // 2. 若缓存为空,查询视图或表(对应C#的loginUserInfos为空时的逻辑) + if (loginUserInfos.isEmpty()) { + // 检查视图view_loginuser是否存在(对应C#的IF EXISTS SQL) +// String checkViewSql = "SELECT 1 FROM sys.views WHERE name = 'view_loginuser'"; + String checkViewSql = "SELECT 1\n" + + "FROM ALL_VIEWS\n" + + "WHERE \n" + + " OWNER = SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA')\n" + + " AND VIEW_NAME = UPPER('view_loginuser');"; + List> viewExists = jdbcTemplate.queryForList(checkViewSql); + + String dataSql; + if (!viewExists.isEmpty()) { + // 视图存在,查询视图 + dataSql = "select EmployeeId, LoginAccount, EmployeeName from view_loginuser"; + } else { + // 视图不存在,查询表(带条件和排序) +// dataSql = "select EmployeeId, LoginAccount, EmployeeName " + +// "from dbo.P_EmployeeTab " + +// "where isnull(sign, 0) = 0 " + +// "order by LoginAccount asc"; + dataSql = "SELECT EmployeeId, LoginAccount, EmployeeName\n" + + "FROM P_EmployeeTab \n" + + "WHERE ISNULL(sign, 0) = 0\n" + + "ORDER BY LoginAccount ASC; "; + } + + // 执行查询并更新缓存 + List> dtval = jdbcTemplate.queryForList(dataSql); + loginUserInfos = convertToLoginUserInfos(dtval); + } + + // 3. 处理空缓存兜底(对应C#的 loginUserInfos = loginUserInfos ?? new List()) + List resultList = loginUserInfos.isEmpty() ? new ArrayList<>() : new ArrayList<>(loginUserInfos); + + // 4. 按LoginAccount筛选(模糊匹配逻辑) + if (loginAccount != null && !loginAccount.isEmpty()) { + String lowerAccount = loginAccount.toLowerCase(); + resultList = resultList.stream() + .filter(user -> + // 匹配UserCode(LoginAccount) + (user.UserCode != null && user.UserCode.contains(loginAccount)) || + // 匹配UserName(EmployeeName) + (user.UserName != null && user.UserName.contains(loginAccount)) || + // 匹配UserName的拼音 + (user.UserName != null && PyUtil.GetChineseSpell(user.UserName).toLowerCase().contains(lowerAccount)) + ) + .collect(Collectors.toList()); + } + + return resultList; + } + + /** + * 将数据库查询结果转换为LoginUserInfo列表(对应C#的LINQ映射) + */ + private List convertToLoginUserInfos(List> dtval) { + return dtval.stream() + .map(row -> { + LoginUserInfo user = new LoginUserInfo(); + // 映射字段(与C#的row["EmployeeId"] + ""逻辑一致) + user.UserId = (row.get("EmployeeId") != null ? row.get("EmployeeId").toString() : ""); + user.UserCode = (row.get("LoginAccount") != null ? row.get("LoginAccount").toString() : ""); + user.UserName = (row.get("EmployeeName") != null ? row.get("EmployeeName").toString() : ""); + return user; + }) + .collect(Collectors.toList()); + } + + + //新增 + public LoginUserInfo OnLoginSuccess(List> dtval, LoginUserInfo originalLuser, + String seriesId, int serverId, String employeeId, String constr, + BaseResponse responseinfo) { + return OnLoginSuccess(dtval, originalLuser, seriesId, serverId, employeeId, constr, responseinfo, false); + } + + /** + * 登录成功后处理核心逻辑(对应C#的OnLoginSuccess方法) + * + * @param dtval 用户信息数据(Map列表形式,对应C#的DataTable) + * @param originalLuser 登录用户对象 + * @param seriesId 系列ID + * @param serverId 服务器ID + * @param employeeId 用户ID + * // * @param conStr 数据库连接字符串 + * @param responseinfo 响应对象 + * @param setOnly 是否仅设置信息(不执行额外操作) + * @return 补全后的用户对象 + */ + //constr先跳过 + public LoginUserInfo OnLoginSuccess(List> dtval, LoginUserInfo originalLuser, + String seriesId, int serverId, String employeeId, String constr, + BaseResponse responseinfo, boolean setOnly) { + log.debug("OnLoginSuccess received tenant connection string: {}", constr != null && !constr.isBlank()); + JdbcTemplate jdbcTemplate = buildJdbcTemplate(constr); + // 1. 补全用户信息(通过public字段直接访问) + if (dtval == null || dtval.isEmpty()) { + // 1.1 尝试通过用户账号查询 + if (originalLuser != null && !NativeExtensionUtils.isNullOrEmpty(originalLuser.UserCode)) { + String sql = "select EmployeeId, EmpLoyeeName, LoginAccount, password, " + + "p_emp_clientid, p_emp_logintype, AppIndex " + + "from P_EmployeeTab " + + "where LoginAccount = ? and isnull(sign, 0) = 0"; + dtval = jdbcTemplate.queryForList(sql, originalLuser.UserCode); + + } + // 1.2 尝试通过用户ID查询 + if ((dtval == null || dtval.isEmpty()) && !NativeExtensionUtils.isNullOrEmpty(employeeId)) { + String sql = "select EmployeeId, EmpLoyeeName, LoginAccount, password, " + + "p_emp_clientid, p_emp_logintype, AppIndex " + + "from P_EmployeeTab " + + "where EmployeeId = ? and isnull(sign, 0) = 0"; + dtval = jdbcTemplate.queryForList(sql, employeeId); + } + // 1.3 若查询到数据,更新用户对象(直接操作public字段) + if (dtval != null && !dtval.isEmpty()) { + Map userMap = dtval.get(0); + LoginUserInfo newLuser = new LoginUserInfo(); + // 直接赋值public字段,无需setter + newLuser.UserId = userMap.get("EmployeeId").toString(); + newLuser.UserCode = userMap.get("LoginAccount").toString(); + newLuser.UserName = userMap.get("EmpLoyeeName").toString(); + newLuser.Pwd = userMap.get("password").toString(); + // 保留原始输入密码(直接访问originalLuser的InPwd字段) + newLuser.InPwd = originalLuser != null ? originalLuser.InPwd : null; + newLuser.LoginType = NativeExtensionUtils.ToInt32(userMap.get("p_emp_logintype")); + newLuser.ClientId = NativeExtensionUtils.ToInt32(userMap.get("p_emp_clientid")); + // 根据设备类型设置AppIndex + newLuser.AppIndex = (isPhone() || !isWindowsDirver()) ? + userMap.get("AppIndex").toString() : ""; + originalLuser = newLuser; // 更新引用 + } else { + // 1.4 未查询到用户,返回失败 + responseinfo.setSuccess(false); + responseinfo.setMsg(LanguageUtil.NameOrPwdError); + //新增日志 + sysLog(String.format("用户%s,%s登录验证失败,未找到对应人员:%s", originalLuser.UserCode, employeeId, originalLuser.InPwd), "登录系统"); + return new LoginUserInfo(); + } + } + + // 2. 完善用户扩展信息(直接操作public字段) + LoginUserInfo luser = originalLuser; + luser.SeriesId = seriesId; + luser.ServerId = serverId; + luser.LoginOs = getOsModel(); + if (!NativeExtensionUtils.isNullOrEmpty(constr)) { + luser.ConnectionString = constr; + } + + // 3. 设置基础响应信息 + responseinfo.setSuccess(true); + responseinfo.setData(luser); + responseinfo.setMsg(LanguageUtil.Success); + + // 4. 检查初始密码并提示修改 + CheckWeekPwd(luser, setOnly, responseinfo); +// if (setOnly == false && isDefaultPwd(luser.Pwd) // 完全对应C#逻辑:步骤1:获取配置值(字符串);步骤2:用自定义方法转换为布尔值 +// && NativeExtensionUtils.toBoolean(WebConfigUtil.get("RestInitPwd")) +// ) { +// responseinfo.setMsg("请修改密码!"); +// // 通过getCode()获取枚举对应的整数,与setOther的参数类型匹配 +// responseinfo.setOther(LoginCode.ResetPwd); +// } + + // 5. 查询用户权限并设置 + String purviewSql = "select * from p_SubsysPurviewTab where employeeid = ?"; + List> purviewList = jdbcTemplate.queryForList(purviewSql, luser.UserId); + if (purviewList != null && !purviewList.isEmpty()) { + // 3.1 仅处理第一行数据(对应C#的dt.Rows[0]) + Map firstRow = purviewList.get(0); + + // 3.2 调用工具类处理第一行数据(对应C#的PublicUtil.GetPurviews(dt)) + luser.PurviewStr = PublicUtil.GetPurviews(firstRow); + } + + // 6. 清理用户缓存 + + CacheUtil.clearUserCache(luser.UserName, luser.UserId); + + // 7. 非仅设置模式下的后续操作 + if (!setOnly) { + // 7.1 生成JWT令牌 + int expireSeconds = (NativeExtensionUtils.isNullOrEmpty(getDriver()) && + (getDriver().equals("android") || getDriver().equals("ios"))) ? + 2592000 : 7200; + //c#中令牌的生成方式与java不同 + responseinfo.setToken(JwtHelp.createToken(luser, expireSeconds)); + // 7.2 保存用户到会话 + setSessionVal(getUserSessionName(), luser); + // 新增验证:直接从Session中读取刚存储的值,打印详细信息 + HttpSession session = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getSession(); + String sessionKey = getUserSessionName(); // 获取存储时用的key + + // 7.3 记录单点登录状态 + singleUserHandler.add(getSessionId(), luser.UserId); + + // 7.4 记录系统日志,未在OptBaseImpl重写该方法 + logAction(String.format("用户%s登录成功", luser.UserName), "登录系统"); + } + + return luser; + } + + + // 辅助方法:判断是否为初始密码 + private boolean isDefaultPwd(String pwd) { + return "51B4FCEDBC944C6F5627E732CD9560B8B1204FDB".equals(pwd) || + "E0B97F132980BB0856ECC51BA256626F3CB68D11".equals(pwd) || + "A839A93FD1A6699CAA208BB1DB9E2D25A7013E86".equals(pwd); + } + + //新增,将上方的检查密码提出来 + public void CheckWeekPwd(LoginUserInfo luser, Boolean setOnly, BaseResponse responseinfo) { + if (!luser.UserName.equals("管理员") && !luser.InPwd.equals("lserpAdmin") && !luser.InPwd.equals("Lserp_Admin")) { + //用stream流而不用自带的getFirst,防止null报错 + List> SystemInfo = dataImpl.GetSystemInfo(); + Map firstRow = SystemInfo.stream() + .findFirst() + .orElse(null); + boolean ckweekpwdPart = false; + if (firstRow != null) { + ckweekpwdPart = toBoolean(firstRow.get("ckweekpwd")); + } + Boolean ckweekpwd = toBoolean(WebConfigUtil.get("RestInitPwd")) || ckweekpwdPart; + if (!setOnly && ckweekpwd) { + Boolean isWeekPwd = luser.IsWeekPwd; + if (!NativeExtensionUtils.isNullOrEmpty(luser.InPwd)) { + isWeekPwd = luser.InPwd.length() < 32 && !RegexUtil.getWeekPwdRegex().matcher(luser.InPwd).matches(); + } + if (isDefaultPwd(luser.Pwd) || isWeekPwd) { + // 直接拼接,效果和上面完全一致 + responseinfo.setMsg("请修改密码!" + WebConfigUtil.get("WeekPwdMsg", "密码必需由8-18位数字+字母+特殊符号组成")); + responseinfo.setOther(LoginCode.ResetPwd); + luser.IsWeekPwd = true; + } + } + } + } + + public DbOperator ChangeServer(int serverId, StringBuilder errMsg) { + DbOperator _dbOperator = getServerDbOper(serverId, errMsg); + if (_dbOperator != null) { + // 对应 C# 的 new UpdateImpl() { dbOperator = _dbOperator }.CheckUpdate(); + // 步骤:1. 创建实例 → 2. 调用 setter 赋值 → 3. 调用 CheckUpdate() + UpdateImpl updateImpl = new UpdateImpl(); + updateImpl.setJdbcTemplate(_dbOperator.getJdbcTemplate()); + updateImpl.setDbOperator(_dbOperator); // _dbOperator 是你已有的 DbOperator 实例(对应 C# 的 _dbOperator) + try { + updateImpl.checkUpdate(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + return _dbOperator; + } + + /** + * 根据服务器ID获取数据库操作对象 + * + * @param serverId 服务器ID + * @return 数据库操作对象 AdsOperater,无有效配置时返回 null + */ + public DbOperator getServerDbOper(int serverId, StringBuilder errMsg) { + String connectionString; + // 1. 强制清空原有内容(避免外部残留旧值,对齐 C# errMsg = "" 的初始化) + errMsg.setLength(0); + // 仅处理 serverId 大于 0 的情况 + if (serverId > 0) { + // 查询数据库配置信息(对应 C# 的 GetSysdbGroup) + List> dbConfigTable = dataImpl.GetSysdbGroup(serverId); + + + // 检查是否查询到有效配置 + if (dbConfigTable != null && !dbConfigTable.isEmpty()) { + Map dbConfig = dbConfigTable.get(0); + + // 提取数据库名称和IP(处理可能的 null 值) + String dbName = Objects.toString(dbConfig.get("name"), ""); + String dbIp = Objects.toString(dbConfig.get("ip"), ""); + if (dbIp.contains(",")) { + String[] ipPort = dbIp.split(","); + dbIp = ipPort[0].trim() + ":" +// 提取 IP: + ipPort[1].trim(); // 提取端口: + } + + + // 获取连接配置信息(对应 C# 的 ConfigUtil.ConnecctionKeyVal) + Map connectionConfig = ConfigUtil.getConnectionKeyVal(); + connectionConfig.forEach((key, value) -> { + log.debug(String.valueOf("键:" + key + ",值:" + value)); + }); + // 获取驱动类名和JDBC前缀 + String driverClass = getJdbcDriverByProvider(); + String jdbcPrefix = getJdbcUrlPrefixByDriverClass(driverClass); + // 新增:获取数据库类型(用于动态生成连接字符串) + DbType dbType = getDbTypeByDriverClass(driverClass); + + // 判断是否使用信任连接 + if (connectionConfig.containsKey("trusted_connection") + && Boolean.parseBoolean(connectionConfig.get("trusted_connection"))) { + // 新增:调用动态方法构建信任连接字符串 + connectionString = buildTrustedConnectionString(jdbcPrefix, dbName, dbType); + } else { + // 处理连接池参数(设置默认值) + String minPool = connectionConfig.getOrDefault("min pool size", "1"); + String maxPool = connectionConfig.getOrDefault("max pool size", "512"); + String multipleRs = connectionConfig.getOrDefault("multipleactiveresultsets", "false"); + + // 转换为整数并确保最小值 + int minPoolSize = Math.max(parseInt(minPool, 1), 1); + int maxPoolSize = Math.max(parseInt(maxPool, 512), 512); + + // 处理多结果集参数(转为小写布尔值) + String multipleRsLower = Boolean.parseBoolean(multipleRs) ? "true" : "false"; + + // 获取用户名和密码 + String userId = connectionConfig.get("user"); + String password = connectionConfig.get("password"); + + // 新增:调用动态方法构建标准连接字符串 + connectionString = buildStandardConnectionString( + jdbcPrefix, dbIp, dbName, userId, password, + minPoolSize, maxPoolSize, multipleRsLower, dbType + ); + } + //创建并返回数据库操作对象 + log.debug("Tenant connection string built: {}", connectionString != null && !connectionString.isBlank()); + DbOperator _oper = new DbOperator(buildJdbcTemplate(connectionString)); + _oper.setConnectionString(connectionString); + _oper.setCurrentDataSource(_oper.getJdbcTemplate().getDataSource()); + if (_oper.connectionTest()) { + return _oper; + } else { + errMsg.append("账套连接失败!"); + } + } else { + errMsg.append("未找到账套!"); + } + } + + // 无效参数或无配置时返回 null + return null; + } + + /** + * 根据驱动类名获取对应的JDBC URL前缀 + * + * @param driverClass 驱动类全名 + * @return JDBC URL前缀 + */ + protected String getJdbcUrlPrefixByDriverClass(String driverClass) { + if (driverClass == null || driverClass.isEmpty()) { + throw new IllegalArgumentException("数据库驱动类名不能为空"); + } + + // 根据常见数据库驱动类名映射对应的URL前缀 + if (driverClass.contains("sqlserver")) { + return "jdbc:sqlserver://"; + } else if (driverClass.contains("kingbase8")) { + return "jdbc:kingbase8://"; + } else if (driverClass.contains("mysql")) { + return "jdbc:mysql://"; + } else if (driverClass.contains("postgresql")) { + return "jdbc:postgresql://"; + } else if (driverClass.contains("oracle")) { + return "jdbc:oracle:thin:@"; + } else if (driverClass.contains("dm")) { + return "jdbc:dm://"; + } else { + // 对于未知驱动,可以抛出异常或使用默认值 + throw new UnsupportedOperationException("不支持的数据库驱动类: " + driverClass); + // 或者使用默认值: return "jdbc:sqlserver://"; + } + } + + /** + * 对应 C# 的 new DbTemplate(conStr, ProviderName):构建 Java 的 JdbcTemplate + * + * @param connectionString JDBC 格式的连接字符串(你已有的 String.format 结果) + * @return 带连接池的 JdbcTemplate + */ + public JdbcTemplate buildJdbcTemplate(String connectionString) { + return DynamicJdbcTemplateRegistry.getOrCreate(connectionString, this::createDynamicJdbcTemplate); + } + + private JdbcTemplate createDynamicJdbcTemplate(String connectionString) { + try { + // 1. 关键:创建信任所有证书的 SSLContext(仅测试用) + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init( + null, + new TrustManager[]{new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + @Override + public void checkClientTrusted(X509Certificate[] certs, String authType) { + } + + @Override + public void checkServerTrusted(X509Certificate[] certs, String authType) { + } + }}, + new SecureRandom() + ); + SSLContext.setDefault(sslContext); + } catch (Exception e) { + throw new RuntimeException("Hikari 数据源初始化失败: " + e.getMessage(), e); + } + // 1. 获取 JDBC 驱动类名(通过 providerName 映射) + String driverClass = getJdbcDriverByProvider(); + // 新增:根据驱动类名获取数据库类型,用于设置适配的初始化SQL + DbType dbType = getDbTypeByDriverClass(driverClass); + // 2. 用 Hikari 连接池创建 DataSource(对应 C# 的连接池配置) + HikariConfig hikariConfig = new HikariConfig(); + hikariConfig.setJdbcUrl(connectionString); // 直接传入你的 JDBC 连接字符串 + hikariConfig.setDriverClassName(driverClass); // 映射后的驱动类 + hikariConfig.setIdleTimeout(900000); // 空闲超时(10分钟,可调整) + hikariConfig.setMaximumPoolSize(20); // 最大连接数 + hikariConfig.setMinimumIdle(2); // 最小空闲连接 + hikariConfig.setConnectionTimeout(120000); // 获取连接超时(30秒) + hikariConfig.setMaxLifetime(1080000); // 连接最大存活时间(30分钟) + hikariConfig.setLeakDetectionThreshold(60000); + hikariConfig.setAutoCommit(true); + hikariConfig.setConnectionTestQuery("SELECT 1 FROM DUAL"); + // 新增:根据数据库类型设置适配的连接初始化SQL + setDbSpecificInitSql(hikariConfig, dbType); + // 3. 初始化 DataSource(带连接池) + HikariDataSource dataSource = new HikariDataSource(hikariConfig); + // 4. 生成 JdbcTemplate(对应 C# 的 DbTemplate) + return new JdbcTemplate(dataSource); + } + +// ======================== 新增私有方法(统一放最下方) ======================== + + /** + * 数据库类型枚举(内部区分不同数据库特性) + */ + protected enum DbType { + SQL_SERVER, // SQL Server + KINGBASE, // 人大金仓 + POSTGRESQL, // PostgreSQL + MYSQL, // MySQL + ORACLE, // Oracle(预留适配) + DM // Oracle(预留适配) + } + + /** + * 根据驱动类名判断数据库类型 + * + * @param driverClass 驱动类全名 + * @return 数据库类型枚举 + */ + protected DbType getDbTypeByDriverClass(String driverClass) { + if (driverClass == null || driverClass.isEmpty()) { + throw new IllegalArgumentException("数据库驱动类名不能为空"); + } + if (driverClass.contains("sqlserver")) { + return DbType.SQL_SERVER; + } else if (driverClass.contains("kingbase8")) { + return DbType.KINGBASE; + } else if (driverClass.contains("postgresql")) { + return DbType.POSTGRESQL; + } else if (driverClass.contains("mysql")) { + return DbType.MYSQL; + } else if (driverClass.contains("oracle")) { + return DbType.ORACLE; + } else if (driverClass.contains("dm")) { + return DbType.DM; + } else { + throw new UnsupportedOperationException("不支持的数据库驱动类: " + driverClass); + } + } + + /** + * 构建适配不同数据库的信任连接字符串 + * + * @param jdbcPrefix JDBC前缀(如 jdbc:sqlserver://) + * @param dbName 数据库名 + * @param dbType 数据库类型 + * @return 信任连接字符串 + */ + private String buildTrustedConnectionString(String jdbcPrefix, String dbName, DbType dbType) { + switch (dbType) { + case SQL_SERVER: + // SQL Server 信任连接(integratedSecurity=true) + return String.format("%s.;databaseName=%s;integratedSecurity=true;", jdbcPrefix, dbName); + case KINGBASE: + case POSTGRESQL: + // 人大金仓/PostgreSQL 信任连接(依赖操作系统认证,无需密码) + return String.format("%slocalhost/%s?user=postgres&sslmode=trust;", jdbcPrefix, dbName); + case MYSQL: + // MySQL 信任连接(需提前配置 my.cnf 免密) + return String.format("%slocalhost/%s?user=root&useSSL=false&allowPublicKeyRetrieval=true;", jdbcPrefix, dbName); + case DM: + // 达梦数据库信任连接(操作系统认证,OSAUTH=TRUE) + // 达梦默认端口5236,OSAUTH=TRUE开启操作系统认证(免密) +// return String.format("%slocalhost:5236/%s?OSAUTH=TRUE&charset=utf8;", jdbcPrefix, dbName); + default: + throw new UnsupportedOperationException("不支持的数据库类型信任连接: " + dbType); + } + } + + /** + * 构建适配不同数据库的标准连接字符串 + * + * @param jdbcPrefix JDBC前缀 + * @param dbIp IP:端口(如 :) + * @param dbName 数据库名 + * @param userId 用户名 + * @param password 密码 + * @param minPoolSize 最小连接池 + * @param maxPoolSize 最大连接池 + * @param multipleRsLower 多结果集开关(true/false) + * @param dbType 数据库类型 + * @return 标准连接字符串 + */ + protected String buildStandardConnectionString( + String jdbcPrefix, String dbIp, String dbName, String userId, String password, + int minPoolSize, int maxPoolSize, String multipleRsLower, DbType dbType + ) { + switch (dbType) { + case SQL_SERVER: + // SQL Server:;分隔参数,用 databaseName/minPoolSize + return String.format( + "%s%s;" + + "databaseName=%s;" + + "persistSecurityInfo=true;" + + "user=%s;" + + "password=%s;" + + "minPoolSize=%d;" + + "maxPoolSize=%d;" + + "multipleActiveResultSets=%s;" + + "trustServerCertificate=true", + jdbcPrefix, dbIp, dbName, userId, password, + minPoolSize, maxPoolSize, multipleRsLower + ); + case KINGBASE: + // 人大金仓:?&分隔参数,用 dbname/minimumPoolSize,sslmode=require + return String.format( + "%s%s/%s?" + + "user=%s&" + + "password=%s&" + + "minimumPoolSize=%d&" + + "maximumPoolSize=%d&" + + "multipleActiveResultSets=%s&" + + "sslmode=require&" + + "currentSchema=dbo", + jdbcPrefix, dbIp, dbName, userId, password, + minPoolSize, maxPoolSize, multipleRsLower + ); + case POSTGRESQL: + // PostgreSQL:与人大金仓类似,调整sslmode + return String.format( + "%s%s/%s?" + + "user=%s&" + + "password=%s&" + + "minimumPoolSize=%d&" + + "maximumPoolSize=%d&" + + "multipleActiveResultSets=%s&" + + "sslmode=prefer", + jdbcPrefix, dbIp, dbName, userId, password, + minPoolSize, maxPoolSize, multipleRsLower + ); + case MYSQL: + // MySQL:?&分隔,用 dbname/minPoolSize,加时区和SSL参数 + return String.format( + "%s%s/%s?" + + "user=%s&" + + "password=%s&" + + "minPoolSize=%d&" + + "maxPoolSize=%d&" + + "multipleActiveResultSets=%s&" + + "useSSL=true&" + + "serverTimezone=UTC&" + + "allowPublicKeyRetrieval=true", + jdbcPrefix, dbIp, dbName, userId, password, + minPoolSize, maxPoolSize, multipleRsLower + ); + case DM: + return String.format( + "%s%s/%s?" + + "user=%s&" + + "password=%s&", + jdbcPrefix, dbIp, dbName, userId, password); + + default: + throw new UnsupportedOperationException("不支持的数据库类型标准连接: " + dbType); + } + } + + /** + * 根据数据库类型设置适配的连接初始化SQL(避免语法错误) + * + * @param hikariConfig Hikari配置对象 + * @param dbType 数据库类型 + */ + private void setDbSpecificInitSql(HikariConfig hikariConfig, DbType dbType) { + switch (dbType) { + case KINGBASE: + case POSTGRESQL: + // 人大金仓/PostgreSQL:设置默认schema + hikariConfig.setConnectionInitSql("SET search_path TO dbo"); + break; + case MYSQL: + // MySQL:设置默认字符集 + hikariConfig.setConnectionInitSql("SET NAMES utf8mb4"); + break; + case SQL_SERVER: + // SQL Server:无需初始化SQL(或按需设置语言) + // hikariConfig.setConnectionInitSql("SET LANGUAGE 简体中文"); + break; + default: + // 其他数据库默认不设置 + break; + } + } + +// (若原代码中有其他方法,需保留在新增方法上方,此处省略) + + /** + * 获取当前服务器名称(与C#命名完全一致) + */ + + public String GetCurrentServerName() { + List> dbVal = dataImpl.GetSysdbGroup(getUser().ServerId); + + // 模拟C#的Rows.Cast().FirstOrDefault() + if (dbVal != null && dbVal.size() > 0) { + return Objects.toString(dbVal.get(0).get("text"), ""); + } + return ""; + } + + /** + * 检查密码锁定(与C#命名完全一致,保留ref参数逻辑) + */ + private void CheckLockPwd(List> dtval, boolean success, BaseResponse responseinfo) { + // 模拟C#的WebConfigUtil.Get("LockErrPwd").ToBoolean() + if (NativeExtensionUtils.toBoolean(WebConfigUtil.get("LockErrPwd")) && dtval != null && !dtval.isEmpty()) { + int lockDay = NativeExtensionUtils.ToInt32(WebConfigUtil.get("ErrPwdLockDay", "1")); + int totNum = NativeExtensionUtils.ToInt32(WebConfigUtil.get("ErrPwdNum", "5")); + Map row = dtval.get(0); + int num = NativeExtensionUtils.ToInt32(DataTableUtil.get(row, "p_emp_PwdErrNum", null)); + boolean locked = NativeExtensionUtils.toBoolean(DataTableUtil.get(row, "p_emp_PwdLocked", null)); + LocalDateTime time = LocalDateTime.now(); + // 2. 标记是否解析成功(对应C#的 hasTime) + boolean hasTime = false; + + + try { + // 3. 获取"p_emp_PwdLockDate"字段值并转为字符串(对应C#的 dtval.Rows[0].Get("p_emp_PwdLockDate") + "") + String dateObj = DataTableUtil.get(row, "p_emp_PwdLockDate", "").toString(); + // 新增:如果日期字符串为null,直接标记解析失败 + if (dateObj == null || dateObj.trim().isEmpty()) { + hasTime = false; + } else { // 4. 尝试解析日期字符串(对应C#的 DateTime.TryParse) + // 注意:需根据实际日期格式调整DateTimeFormatter(常见格式如"yyyy-MM-dd HH:mm:ss") + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + time = LocalDateTime.parse(dateObj, formatter); // 解析成功则更新time + hasTime = true; // 解析成功,标记为true + } + } catch (DateTimeParseException e) { + // 解析失败时,hasTime保持false,time保持初始的当前时间(与C#逻辑一致) + hasTime = false; + } + + // 计算锁定到期时间 + LocalDateTime oneDay = time.plusDays(1); + + if (success) { + if (locked) { + if (LocalDateTime.now().isBefore(time.plusDays(lockDay))) { + responseinfo.setSuccess(false); + responseinfo.setMsg(String.format("账户已被锁定,将在%s后解锁!", time + .plusDays(lockDay).format(DateTimeFormatter + .ofPattern("yyyy-MM-dd HH:mm:ss")))); + return; + } + } + + // 重置错误次数 + if (num > 0 || locked) { + String employeeId = Objects.toString(row.get("EmployeeId"), ""); + jdbcTemplate.update( + "update P_EmployeeTab set p_emp_PwdErrNum=0,p_emp_PwdLocked=0 where EmployeeId=?", + employeeId + ); + } + responseinfo.setSuccess(true); + } else { + String errMsg = LanguageUtil.NameOrPwdError; + if (locked) { + responseinfo.setSuccess(false); + responseinfo.setMsg(String.format("账户已被锁定,将在%s后解锁!", time + .plusDays(lockDay).format(DateTimeFormatter + .ofPattern("yyyy-MM-dd HH:mm:ss")))); + return; + } else if (hasTime && LocalDateTime.now().isAfter(oneDay.toLocalDate().atStartOfDay())) { // 第二天0点解锁 + num = 0; + } + + num++; + String employeeId = row.get("EmployeeId") != null ? row.get("EmployeeId").toString() : ""; + // 使用 %d 作为整数占位符,%s 作为字符串占位符 + String updSql = String.format( + "update P_EmployeeTab set p_emp_PwdErrNum=%d, p_emp_PwdLockDate=getdate() %s where EmployeeId=?", + num, // %d:对应int类型的错误次数(去掉单引号,与字段类型匹配) + (num >= totNum ? ", p_emp_PwdLocked=1" : "") // %s:直接拼接锁定字段(无需二次format) + ); + // 直接执行SQL(无需二次format,避免占位符混淆) + jdbcTemplate.update(updSql, employeeId); + if (num >= totNum) { + errMsg = String.format("%s,已错误%d次,账号已被锁定!", LanguageUtil.NameOrPwdError, num); + } else { + if (num > 2) { + errMsg = String.format("%s,错误%d次后账号将被锁定!已错误%d次!", + LanguageUtil.NameOrPwdError, totNum, num); + } + } + + responseinfo.setSuccess(false); + responseinfo.setMsg(errMsg); + } + } + } + + /** + * 检查设备,设备唯一性(与C#逻辑完全对齐) + * + * @param userName 用户名 + * @param userId 用户ID + * @param responseinfo 响应对象(通过引用修改结果) + */ + private void CheckLoginDev(String userName, String userId, BaseResponse responseinfo) { + // 若设备ID为空或用户是管理员,则跳过检查(与C#逻辑一致) + if (NativeExtensionUtils.isNullOrEmpty(getDriverUuId()) || "管理员".equals(userName)) { + return; + } + // 打印设备信息日志(对应C#的this.Info) + logAction(String.format("[手机身份ID]:%s,[姓名]:%s,[id]:%s", getDriverUuId(), userName, userId), ""); + + // 查询系统配置(对应C#的dataImpl.GetSystemInfo()) + List> sysDtList = dataImpl.GetSystemInfo(); + if (sysDtList == null || sysDtList.isEmpty()) { + return; // 无配置时不做限制 + } + Map sysDt = sysDtList.get(0); // 取第一行配置 + + // 解析系统配置:是否启用设备绑定、是否允许多设备(对应C#的sysDt.Get("mobiledevonly").ToBoolean()) + boolean isBind = NativeExtensionUtils.toBoolean(sysDt.get("mobiledevonly")); + boolean isMult = NativeExtensionUtils.toBoolean(sysDt.get("multmobiledev")); + if (isBind) { +// 检查设备是否已绑定该用户(对应C#的dataImpl.HasBindMobileDev) + boolean hasBind = dataImpl.HasBindMobileDev(getDriverUuId(), userId, isMult); + if (!hasBind) { + // 未绑定则返回错误(对应C#的responseinfo.success = false) + responseinfo.setSuccess(false); + responseinfo.setMsg("警告!你当前正在登录他人的设备!"); + return; + } else { + // 已绑定则更新绑定记录(对应C#的dbOperator.ExecuteNonQuery) + String sql = "if not exists (select 1 from p_systemMobileIdTab where mobileId = ? and userId = ?) " + + "insert into p_systemMobileIdTab(mobileId, userId) values(?, ?)"; + jdbcTemplate.update(sql, + getDriverUuId(), + userId + ); + } + } + } + + /** + * 客户登录(对应C#的LoginByCustomer,命名保持相似性) + */ + + public BaseResponse LoginByCustomer(String docCode, String coid, String linkphone) { + BaseResponse[] response = new BaseResponse[1]; + response[0] = new BaseResponse(); + String loginAccount = coid; + String pwd = linkphone; + String sql; + + // 根据docCode是否为空构建SQL(对应C#的条件判断) + if (!NativeExtensionUtils.isNullOrEmpty(docCode)) { + sql = "select id EmployeeId, coname EmpLoyeeName, coid LoginAccount from p_customertab where docCode = ?"; + } else { + sql = "select id EmployeeId, coname EmpLoyeeName, coid LoginAccount from p_customertab where coid = ? and linkphone = ?"; + } + + // 执行SQL查询(对应C#的dbOperator.ExecuteDataTable) + List> dtval; + if (!NativeExtensionUtils.isNullOrEmpty(docCode)) { + // 带docCode参数的查询(对应@code参数) + dtval = jdbcTemplate.queryForList(sql, docCode); + } else { + // 带coid和linkphone参数的查询(对应@coid和@pwd参数) + dtval = jdbcTemplate.queryForList(sql, loginAccount, pwd); + } + + // 处理查询结果 + if (dtval != null && !dtval.isEmpty()) { + Map row = dtval.get(0); // 对应C#的Rows[0] + + // 构建登录用户信息(对应C#的LoginUserInfo初始化) + LoginUserInfo luser = new LoginUserInfo(); + luser.UserId = DataTableUtil.get(row, "EmployeeId", null) != null ? + DataTableUtil.get(row, "EmployeeId", null).toString() : ""; + luser.UserCode = DataTableUtil.get(row, "LoginAccount", null) != null ? + DataTableUtil.get(row, "LoginAccount", null).toString() : ""; + luser.UserName = DataTableUtil.get(row, "EmpLoyeeName", null) != null ? + DataTableUtil.get(row, "EmpLoyeeName", null).toString() : ""; +// luser.ConnectionString = dbOperator.getConnectionString(); // 对应dbOperator.ConnectionString + luser.IsClientUser = true; // 对应IsClientUser + + // 设置响应信息(对应C#的response赋值) + response[0].setSuccess(true); + response[0].setData(luser); + response[0].setMsg(LanguageUtil.Success); // 复用语言工具类 + + // 保存用户到会话(复用BaseImpl的SetSessionVal) + setSessionVal(getUserSessionName(), luser); + + } else { + response[0].setMsg("公司编号或验证码不正确!"); + } + + return response[0]; + } + + + /** + * AD域登录(对应C#的LoginByAD,命名保持相似性) + */ + public boolean LoginByAD(String LoginAccount, String Pwd, BaseResponse responseinfo) { + // 初始化AD配置(对应C#的ADUtil.ADLogin == null判断) + if (ADUtil.ADLogin == null) { + // 查询AD配置信息(对应dataImpl.GetADLoginInfo()) + // SQL语句与C#原版一致 + String sql = "select top 1 adlogin, adpath, aduser, adpwd from dbo.p_SystemTab"; + List> adInfo = jdbcTemplate.queryForList(sql); + if (adInfo != null && !adInfo.isEmpty()) { + Map adRow = adInfo.get(0); + // 设置AD工具类参数(对应C#的ADUtil赋值) + ADUtil.ADLogin = NativeExtensionUtils.toBoolean(DataTableUtil.get(adRow, "adlogin", null)); + ADUtil.ADPath = DataTableUtil.get(adRow, "adpath", null) != null ? + DataTableUtil.get(adRow, "adpath", null).toString() : ""; + ADUtil.ADUser = DataTableUtil.get(adRow, "aduser", null) != null ? + DataTableUtil.get(adRow, "aduser", null).toString() : ""; + ADUtil.ADPwd = DataTableUtil.get(adRow, "adpwd", null) != null ? + DataTableUtil.get(adRow, "adpwd", null).toString() : ""; + } + + // 校验AD路径配置(对应C#的string.IsNullOrEmpty判断) + if (NativeExtensionUtils.isNullOrEmpty(ADUtil.ADPath)) { + responseinfo.setMsg("AD域配置不正确,请检查!"); + return false; + } + } + + // 执行AD登录验证(对应C#的ADUtil.ADLogin == true分支) + if (ADUtil.ADLogin == true) { + ADUtil.LoginResult result = ADUtil.loginByAccount(LoginAccount, Pwd); + responseinfo.setSuccess(true); + + // 处理登录结果(对应C#的switch分支) + if (result != ADUtil.LoginResult.LOGIN_USER_OK) { + responseinfo.setSuccess(false); + switch (result) { + case LOGIN_USER_ACCOUNT_INACTIVE: + responseinfo.setMsg("登录失败,用户账号被禁用!"); + break; + case LOGIN_USER_PASSWORD_INCORRECT: + responseinfo.setMsg("登录失败,用户密码不正确!"); + break; + case LOGIN_USER_DOESNT_EXIST: + responseinfo.setMsg("登录失败,用户不存在!"); + break; + } + return false; + } + return true; + } + + return false; + } + + /** + * 重置当前用户密码(对应C#的ResetPwdComp方法) + */ + + public BaseResponse ResetPwdComp(String newpwd, String renewpwd) { + BaseResponse[] response = new BaseResponse[1]; + response[0] = new BaseResponse(); + // 验证两次输入的密码是否一致 + if (!newpwd.equals(renewpwd)) { + response[0].setMsg(LanguageUtil.DifferentPwd); + return response[0]; + } + +// // 验证密码是否过于简单 +// if ("lserpadmin".equalsIgnoreCase(newpwd) || "123456".equals(newpwd)) { +// response[0].setMsg("密码太简单,请重新设置!"); +// return response[0]; +// } + if (getUser() == null || !getUser().UserName.equals("管理员")) { + List> SystemInfo = dataImpl.GetSystemInfo(); + Map firstRow = SystemInfo.stream() + .findFirst() + .orElse(null); + boolean ckweekpwd = false; + if (firstRow != null) { + ckweekpwd = toBoolean(firstRow.get("ckweekpwd")); + } + if (!RegexUtil.getWeekPwdRegex().matcher(newpwd).matches() && toBoolean(WebConfigUtil.get("RestInitPwd")) || ckweekpwd) { + response[0].setMsg("密码必需由8-18位数字+字母+特殊符号组成"); + response[0].setSuccess(false); + return response[0]; + } + } + + + // 加密密码(对应C#的SafetyUtil.EncryptPassword) + String encryptedPwd = SafetyUtil.encryptPassword(newpwd); + try { + response[0] = dataImpl.doResetPwd(getUser().UserId, encryptedPwd); + } catch (SQLException e) { + log.debug(String.valueOf("ResetPwdComp中执行到dataImpl.doResetPwd报错")); + throw new RuntimeException(e); + } + if (response[0].isSuccess()) { + getUser().IsWeekPwd = false; + sysLog(String.format("%s登录强制修改密码%s", getUser().UserName, newpwd), "修改密码"); + getUser().Pwd = ""; + if (!NativeExtensionUtils.isNullOrEmpty(getUser().Token)) { + CacheUtil.remove(getUser().Token); + } + if (!NativeExtensionUtils.isNullOrEmpty(getDriver()) && (getDriver().equals("android") || getDriver().equals("ios"))) { + getUser().Token = JwtHelp.createToken(getUser(), 2592000); + response[0].setToken(getUser().Token); + } else { + getUser().Token = JwtHelp.createToken(getUser(), 7200); + response[0].setToken(getUser().Token); + } + setSessionVal("users", getUser()); + response[0].setMsg(LanguageUtil.Success); + response[0].setSuccess(true); + } + return response[0]; +// // 获取当前登录用户ID +// LoginUserInfo currentUser = getUser(); +// if (currentUser == null) { +// response[0].setSuccess(false); +// response[0].setData("未获取用户"); +// return response[0]; +// } +// String userId = currentUser.UserId; +// String sql = String.format("update P_EmployeeTab set [Password]='%s' where EmployeeId='%s' and isnull(sign,0)=0", encryptedPwd, userId); +//// if (dbOperator.executeNonQuery(sql) > 0) { +// // 执行密码更新操作 +// int rowsAffected = jdbcTemplate.update(sql); +// +// if (rowsAffected > 0) { +// // 记录系统日志(对应C#的SysLog) +// logAction(currentUser.UserName + "修改密码=>" + newpwd, "修改密码"); +// +// // 更新当前用户对象的密码为空并更新会话 +// currentUser.Pwd = ""; +// setSessionVal("users", currentUser); +// +// // 设置成功响应 +// response[0].setMsg(LanguageUtil.Success); +// response[0].setSuccess(true); +// } +// +// return response[0]; + } + + /** + * 初始化指定用户密码(对应C#的InitializePwd方法) + */ + + /** + * 初始化密码(对应C#的InitializePwd方法) + */ + public BaseResponse InitializePwd(String username, String newpwd, String renewpwd) { + // 初始化响应对象(对应C#的BaseResponse) + BaseResponse[] response = new BaseResponse[1]; + response[0] = new BaseResponse(); + + // 1. 验证新密码和确认密码是否一致(与C#逻辑相同) + if (!newpwd.equals(renewpwd)) { + response[0].setMsg(LanguageUtil.DifferentPwd); + return response[0]; + } + + // 2. 加密新密码(对应C#的SafetyUtil.EncryptPassword) + String encryptedNewPwd = safetyUtil.encryptPassword(newpwd); + + String userId = ""; + String sql = "select employeeid from P_EmployeeTab where (LoginAccount = ? or EmployeeName = ?) and isnull(sign,0)=0"; + + try { + // 2. 一行代码完成:查询+封装为List(?占位符传参) + List> result = jdbcTemplate.queryForList(sql, username, username); + if (!result.isEmpty()) { + Object empIdObj = result.get(0).get("employeeid"); + userId = empIdObj == null ? "" : empIdObj.toString(); + } + // 3. 极简处理:有数据取第一条的employeeid,否则返回空字符串 + } catch (Exception e) { + userId = ""; + } + if (NativeExtensionUtils.isNullOrEmpty(userId)) { + response[0].setMsg("未找到用户"); + return response[0]; + } + String sPwd = SafetyUtil.encryptPassword(newpwd); + try { + response[0] = dataImpl.doResetPwd(userId, sPwd); + } catch (SQLException e) { + log.debug(String.valueOf("InitializePwd中执行到dataImpl.doResetPwd报错")); + throw new RuntimeException(e); + } + if (response[0].isSuccess()) { + sysLog(String.format("%s初始密码为%s", username, newpwd), "修改密码"); + } + + return response[0]; + } + + /** + * 获取GPS位置信息(对应C#的GetGPSLoction方法) + * 根据用户ID和时间范围查询位置记录 + */ + + public BaseResponse GetGPSLoction(String userid, String lasttime) { + // 1. 处理时间参数(对应C#的DateTime解析逻辑) + LocalDateTime lt = null; + LocalDateTime et = LocalDateTime.now(); // 初始化结束时间为当前时间 + // 生成和解析使用相同的格式器(关键修改) + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + // 尝试解析lasttime字符串为LocalDateTime(对应C#的DateTime.TryParse) + boolean succ = false; + try { + if (lasttime != null && !lasttime.trim().isEmpty()) { + lt = LocalDateTime.parse(lasttime, formatter); + succ = true; + } + } catch (DateTimeParseException e) { + // 解析失败,保持succ为false + succ = false; + } + // 若解析成功,结束时间设为lt加1个月(对应C#的lt.AddMonths(1)) + if (succ) { + et = lt.plusMonths(1); + } + + // 2. 格式化结束时间为"yyyy-MM-dd"字符串(对应C#的et.ToString("yyyy-MM-dd")) + String enddate = et.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); + + // 3. 定义SQL查询语句(与C#保持一致,使用?作为占位符) + String sql = "select operatedate, fixdate, longitude lng, latitude lat, address " + + "from Crm_EmployeeLocation " + + "where employeeid = ? " + + "and operatedate > ? " + + "and operatedate < ? " + + "order by operatedate asc"; + + // 4. 执行查询(对应C#的dbOperator.ExecuteDataTable) + // 返回List对应C#的DataTable,每个Map代表一行数据 + List> data = jdbcTemplate.queryForList( + sql, + userid, // 对应@employeeid参数 + lasttime, // 对应@operatedate参数(直接使用原始字符串) + enddate // 对应@enddate参数(格式化后的结束时间) + ); + + // 5. 构建并返回响应对象(对应C#的BaseResponse初始化) + BaseResponse[] response = new BaseResponse[1]; + response[0] = new BaseResponse(); + response[0].setSuccess(true); + response[0].setData(data); + + return response[0]; + } + + public BaseResponse GenerateCaptcha(String codekey) { + String captchaCode = PublicUtil.GenerateRandomCode(4); + Duration timeSpan = Duration.ofMinutes(5); + CacheUtil.set(codekey, captchaCode, timeSpan, null); + ByteArrayOutputStream memoryStream = new ByteArrayOutputStream(); + try { + // 4. 生成验证码图片并写入内存流 + PublicUtil.GenerateCaptchaImage(captchaCode, memoryStream); + + // 5. 将内存流中的字节转Base64字符串(对应C#的Convert.ToBase64String) + byte[] imageBytes = memoryStream.toByteArray(); + String base64String = Base64.getEncoder().encodeToString(imageBytes); + + // 6. 拼接Base64图片前缀(和C#保持一致) + String imageData = "data:image/png;base64," + base64String; + + // 7. 构建并返回BaseResponse + BaseResponse[] response = new BaseResponse[1]; + response[0] = new BaseResponse(); + response[0].setSuccess(true); + response[0].setData(imageData); + return response[0]; + + } catch (IOException e) { + // 异常处理:返回失败响应 + BaseResponse errorResponse = new BaseResponse(); + errorResponse.setSuccess(false); + errorResponse.setData("验证码图片生成失败"); + throw new RuntimeException("生成验证码图片异常", e); + } finally { + // 8. 关闭流释放资源(对应C#的using) + try { + if (memoryStream != null) { + memoryStream.close(); + } + } catch (IOException e) { + log.error("Exception caught", e); + } + + } + } + + + public BaseResponse verifyCode(String phone, String phonecode, String username, String uuid) { + BaseResponse[] baseResponse = new BaseResponse[1]; + baseResponse[0] = new BaseResponse(); + baseResponse[0].setSuccess(true); + return LoginByPhone(phone, phonecode, username, uuid); + } + + public BaseResponse GetPhoneNumber(String name) { + BaseResponse[] response = new BaseResponse[1]; + response[0] = new BaseResponse(); + response[0].setSuccess(true); + String sql = "select top 1 p_emp_phone from p_employeetab where employeename=?"; + List> dt = jdbcTemplate.queryForList(sql, name); + if (dt.size() == 1) { + Object phoneObj = dt.get(0).get("p_emp_phone"); + // 空值处理:null则返回空字符串,否则转字符串(和C#一致) + response[0].setData(phoneObj == null ? "" : phoneObj.toString()); + } else { + response[0].setSuccess(false); + response[0].setMsg("未查询到绑定手机号"); + } + return response[0]; + } + + public BaseResponse ResetPwdByPhone() { + BaseResponse[] response = new BaseResponse[1]; + response[0] = new BaseResponse(); + response[0].setSuccess(true); + if (NativeExtensionUtils.isNullOrEmpty(WebConfigUtil.getSMSPwd()) + || NativeExtensionUtils.isNullOrEmpty(WebConfigUtil.getSMSUserName())) { + response[0].setSuccess(false); + } + return response[0]; + } + +} diff --git a/WebErp/weberp/src/main/java/org/example/Impl/SystemImpl.java b/WebErp/weberp/src/main/java/org/example/Impl/SystemImpl.java new file mode 100644 index 0000000..e39dd07 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Impl/SystemImpl.java @@ -0,0 +1,254 @@ +package org.example.Impl; + +import org.example.Entity.BaseResponse.BaseResponse; +import org.example.Entity.System.SystemMenu; +import org.example.Utils.IPublicUtil; +import org.example.Utils.JSON; +import org.example.Utils.NativeExtensionUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.stream.Collectors; + +import static org.example.Utils.NativeExtensionUtils.isNullOrEmpty; + +@Service +public class SystemImpl extends OptBaseImpl { + + @Autowired + DataImpl dataImpl; + @Autowired + IPublicUtil util; + + /** + * 获取可用的系统列表 + * + * @param id 系统ID + * @return 包含系统列表的响应对象 + */ + public BaseResponse GetSystems(int id) { + // 从数据实现类获取系统列表(Java中用List模拟DataTable) + List> systemList = dataImpl.GetSystems(id); + + // 转换为SystemMenuEntity列表(对应C#的SystemMenu) + List systemMenuList = systemList.stream() + .map(row -> new SystemMenu(row, getUser())) // 使用对应的构造函数 + .collect(Collectors.toList()); + + // 构建并返回响应对象 + BaseResponse response = new BaseResponse(); + response.setData(systemMenuList); + response.setSuccess(true); + return response; + } + + public BaseResponse GetSystemInfo() { + BaseResponse _response = new BaseResponse(); + _response.setData(dataImpl.GetSystemInfo()); + _response.setSuccess(true); + return _response; + } + + /// + /// 获取设备宝配置 + /// + /// + public BaseResponse GetEmaUrl() { + BaseResponse _response = new BaseResponse(); + _response.setData(dataImpl.GetEmaUrl()); + _response.setSuccess(true); + return _response; + } + + /** + * 获取系统菜单 + * + * @param parentid 系统父集ID + * @param targetMode 目标模式(可选) + * @return 系统菜单列表 + */ + public Object GetSysMenus(String parentid, String targetMode) { + // 从数据实现类获取菜单数据(DataTable对应List) + log.debug(String.valueOf("aaaaaa"+parentid+getUser().SeriesId+"_"+targetMode)); + List> dtval = dataImpl.GetSysMenus(parentid, getUser().SeriesId, targetMode); + List menus = new ArrayList<>(); + List retMenus = new ArrayList<>(); + // 获取菜单类型 + int menuType = GetSystemMenuType(); + // 处理父ID为空的情况 + if (parentid == null || parentid.isEmpty()) { + // 处理parentid为空的情况 + if (menuType == 3 || menuType == 4 || menuType == 0) { + // 复杂条件筛选:level不等于2 或 targetMode包含"4" 或 有权限 + menus = dtval.stream() + .filter(row -> { + // 转换row["level"]为字符串并判断是否不等于"2" + boolean levelNot2 = Objects.toString(row.get("level"), "") != "2"; + // 判断targetMode是否包含"4" + boolean targetModeHas4 = targetMode.indexOf("4") > -1; + // 检查权限(不为空则有权限) + boolean hasPurview = !isNullOrEmpty(util.CheckPurview(getUser().PurviewStr, Objects.toString(row.get("MenuId"), ""))); + return levelNot2 || targetModeHas4 || hasPurview; + }) + // 符合条件的行转换为SystemMenu对象 + .map(row -> new SystemMenu(row, getUser())) + .collect(Collectors.toList()); + } else { + // 无筛选条件,所有行都转换为SystemMenu + menus = dtval.stream() + .map(row -> new SystemMenu(row, getUser())) + .collect(Collectors.toList()); + } + } + for (SystemMenu menu : menus) { + if (menu.getLevel() >= 2 || + (menu.getLevel() == 1 && hasChildMenus(menus, menu))) { + retMenus.add(menu); + } + } +//out.println("reMenus111 : " + retMenus); + // 第二个循环:处理Level < 1且有符合条件的子菜单的菜单 + for (SystemMenu menu : menus) { + if (menu.getLevel() < 1 && hasLevel1ChildMenus(retMenus, menu)) { + retMenus.add(menu); + } + } + return retMenus; + } + + // 检查是否有子菜单(对应C#的LINQ查询) + private boolean hasChildMenus(List menus, SystemMenu menu) { + return menus.stream() + .anyMatch(m -> m.getParentId().equals(menu.getMenuStruct())); + } + + // 检查是否有Level=1的子菜单(对应C#的LINQ查询) + private boolean hasLevel1ChildMenus(List retMenus, SystemMenu menu) { + return retMenus.stream() + .anyMatch(m -> m.getLevel() == 1 && m.getParentId().equals(menu.getMenuStruct())); + } + + private int GetSystemMenuType() { + // 从数据实现类获取系统信息(DataTable对应List) + List> systemInfoList = dataImpl.GetSystemInfo(); + // 如果没有数据,返回4 + if (systemInfoList == null || systemInfoList.isEmpty()) { + return 4; + } + + // 获取第一条数据的"mainmenutype"字段并转换为int + Map firstRow = systemInfoList.get(0); + Object mainMenuTypeObj = firstRow.get("mainmenutype"); + return NativeExtensionUtils.ToInt32(mainMenuTypeObj); + } + + /** + * 获取系统登录信息 + * + * @return 包含登录相关配置的Map + */ + public Map GetSystemLoginInfo() { + // 获取系统信息并取第一条记录转换为Map + List> systemInfo = dataImpl.GetSystemInfo(); + Map sysTab = systemInfo.isEmpty() ? new HashMap<>() : systemInfo.get(0); + + Map retTab = new HashMap<>(); + + // 转换登录弹窗标识 + retTab.put("uftype", toInt32(sysTab.get("loginpopupflag") + "")); + // 获取系统类型 + retTab.put("systype", GetProSysType().getData()); + // 获取数据库服务器信息 + retTab.put("dbserver", dataImpl.GetDbserverInfo(getUser().UserId, getUser().UserName)); + // 获取客户端下载地址 + retTab.put("cslink", sysTab.get("downloadaddress")); + + // 版本号大于1020时添加登录配置 + if (UpdateImpl.getVersion() > 1020) { + retTab.put("logincfg", dataImpl.GetLoginCfg()); + } + + return retTab; + } + + /** + * 获取系统类型信息 + * + * @return 包含系统类型数据的响应对象 + */ + public BaseResponse GetProSysType() { + BaseResponse resp = new BaseResponse(); + + List> typeList = dataImpl.GetProSysType(); + if (typeList == null) { + resp.setData(false); + } else { + resp.setData(typeList); + } + + resp.setSuccess(true); + return resp; + } + + /** + * 获取数据库服务器信息 + * + * @return 包含数据库服务器信息的响应对象 + */ + public BaseResponse GetDbServer() { + BaseResponse response = new BaseResponse(); + response.setData(dataImpl.GetDbserverInfo(getUser().UserId, getUser().UserName)); + response.setSuccess(true); + return response; + } + + /** + * 获取Web更新信息 + * + * @param code 版本代码 + * @param sysRec 系统记录JSON字符串 + * @return 包含更新信息的BaseResponse + */ + public BaseResponse GetWebUpdateInfo(int code, String sysRec) { + BaseResponse response = new BaseResponse(); + response.setSuccess(true); + + // 查询Web更新信息 + List> dtVal = dataImpl.GetWebUpdateInfo(code); + if (dtVal != null && !dtVal.isEmpty()) { + // 将第一条记录转换为Hashtable存入data + Map firstRow = dtVal.get(0); + Map dataMap = new HashMap<>(); + for (Map.Entry entry : firstRow.entrySet()) { + dataMap.put(entry.getKey(), entry.getValue()); + } + response.setData(dataMap); + } + + // 处理系统记录并插入历史表 + if (sysRec != null && !sysRec.isEmpty()) { + try { + // 解析JSON为Hashtable + Map sysRecMap = (Map) JSON.Decode(sysRec); + if (sysRecMap != null) { + // 构建SQL(使用参数化查询防止注入) + String sql = "insert into dbo.P_SystemWebUpdateHisTab(version, oaurl, webver, sysinfo) " + + "values(?, ?, ?, ?)"; + + // 执行插入操作 + jdbcTemplate.update(sql, + code, + sysRecMap.getOrDefault("oaurl", ""), + sysRecMap.getOrDefault("webversion", ""), + sysRec); + } + } catch (Exception e) { + // 异常处理(根据实际需求调整,此处保持原逻辑不抛出) + log.error("Exception caught", e); + } + } + + return response; + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Impl/UpdateImpl.java b/WebErp/weberp/src/main/java/org/example/Impl/UpdateImpl.java new file mode 100644 index 0000000..581beb4 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Impl/UpdateImpl.java @@ -0,0 +1,2993 @@ +package org.example.Impl; + +import jakarta.annotation.PostConstruct; +import org.example.Impl.Sql.factory.AllInOneSqlFactory; +import org.example.Impl.Sql.provider.AllInOneSqlProvider; +import org.example.Utils.NativeExtensionUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; + +import java.sql.SQLException; + +@Service +public class UpdateImpl extends OptBaseImpl { + /* + 检查并升级升级数据库各表 + */ + @Autowired + private JdbcTemplate jdbcTemplate; + + @Value("${custom.database.type}") + private String databaseType; + + private AllInOneSqlProvider sqlProvider; + @Autowired + AllInOneSqlFactory allInOneSqlFactory; // 注入Spring管理的实例 + + @PostConstruct + public void initSqlProvider() { + // 工厂只创建一次,结果缓存到成员变量sqlProvider中 + this.sqlProvider = allInOneSqlFactory.createProvider(this.databaseType); + } + + @Autowired + + + public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + private void updateSystemOtherTab() { +// StringBuilder sqlBuilder = new StringBuilder(); +// +// sqlBuilder.append(String.format( +// "if col_length('%s', 'sumCond') is null exec('alter table %s add sumCond varchar(500);')", +// "dbo.p_systemdlltabdetailgrid", "dbo.p_systemdlltabdetailgrid" +// )); +// +// sqlBuilder.append(String.format( +// "if col_length('%s', 'sumCond') is null exec('alter table %s add sumCond varchar(500);')", +// "dbo.p_systembilldetail", "dbo.p_systembilldetail" +// )); +// +// sqlBuilder.append(String.format( +// "if col_length('%s', 'sumCond') is null exec('alter table %s add sumCond varchar(500);')", +// "dbo.p_systembillauditAttachDetail", "dbo.p_systembillauditAttachDetail" +// )); +// +// sqlBuilder.append(String.format( +// "if col_length('%s', 'dllcoid') is null exec('alter table %s add dllcoid varchar(100);')", +// "dbo.P_fm_DirectoryTab", "dbo.P_fm_DirectoryTab" +// )); +// +// sqlBuilder.append(String.format( +// "if col_length('%s', 'dllcoid') is null exec('alter table %s add dllcoid varchar(100);')", +// "dbo.p_fm_filetab", "dbo.p_fm_filetab" +// )); + + String sql = this.sqlProvider.updateSystemOtherTabSql(); + jdbcTemplate.execute(sql); + } + + private void updateBillTab_210525() { +// StringBuilder sqlBuilder = new StringBuilder(); + + // region p_systembilldetail +// sqlBuilder.append(String.format( +// "if col_length('%s', 'addmoduleid') is null exec('alter table %s add addmoduleid varchar(500);')", +// "dbo.p_systembilldetail", "dbo.p_systembilldetail" +// )); + // endregion + String sql = this.sqlProvider.updateBillTab_210525Sql(); + + jdbcTemplate.execute(sql); + } + + private void updateBillTab_211022() { +// StringBuilder sqlBuilder = new StringBuilder(); + + // region p_systembilldetail +// sqlBuilder.append(String.format( +// "if col_length('%s', 'bandTitle') is null exec('alter table %s add bandTitle varchar(500);') " + +// "if col_length('%s', 'bandFields') is null exec('alter table %s add bandFields varchar(8000);')", +// "dbo.p_systembilldetail", "dbo.p_systembilldetail", +// "dbo.p_systembilldetail", "dbo.p_systembilldetail" +// )); + // endregion + String sql = this.sqlProvider.updateBillTab_211022Sql(); + + jdbcTemplate.execute(sql); + } + + /** + * 更新系统表 + */ + + private void updateSysTab() { +// StringBuilder sqlBuilder = new StringBuilder(); + + // region p_SystemTab + // 公司名称 +// sqlBuilder.append("if col_length('dbo.p_SystemTab', 'clientname') is null " + +// "exec('alter table dbo.p_SystemTab add clientname varchar(100);')"); +// sqlBuilder.append("if col_length('dbo.p_SystemTab', 'clientenname') is null " + +// "exec('alter table dbo.p_SystemTab add clientenname varchar(200);')"); +// sqlBuilder.append("if col_length('dbo.p_SystemTab', 'copyright') is null " + +// "exec('alter table dbo.p_SystemTab add copyright varchar(100);')"); +// sqlBuilder.append("if col_length('dbo.p_SystemTab', 'clientlogname') is null " + +// "exec('alter table dbo.p_SystemTab add clientlogname varchar(200);')"); +// sqlBuilder.append("if col_length('dbo.p_SystemTab', 'webclientname') is null " + +// "exec('alter table dbo.p_SystemTab add webclientname varchar(100);')"); +// sqlBuilder.append("if col_length('dbo.p_SystemTab', 'webclientenname') is null " + +// "exec('alter table dbo.p_SystemTab add webclientenname varchar(200);')"); +// sqlBuilder.append("if col_length('dbo.p_SystemTab', 'serverattachpath') is null " + +// "exec('alter table dbo.p_SystemTab add serverattachpath varchar(2000);')"); +// sqlBuilder.append("if col_length('dbo.p_SystemTab', 'localOAUrl') is null " + +// "exec('alter table dbo.p_SystemTab add localOAUrl varchar(2000);')"); + // endregion + + String sql = this.sqlProvider.updateSysTabSql(); + + jdbcTemplate.execute(sql); + } + + private void updateSysTab_210630() { +// StringBuilder sqlBuilder = new StringBuilder(); +// +// // region p_SystemTab +// // 公司名称 +// sqlBuilder.append("if col_length('dbo.p_SystemTab', 'downloadAddress') is null " + +// "exec('alter table dbo.p_SystemTab add downloadAddress varchar(1000);')"); +// // endregion + + String sql = this.sqlProvider.updateSysTab_210630Sql(); + + jdbcTemplate.execute(sql); + } + + private void updateProductSpeciesTab() { +// StringBuilder sqlBuilder = new StringBuilder(); +// +// // region p_SystemTab +// // 公司名称 +// sqlBuilder.append("if col_length('dbo.bmp_ProductSpeciesTab', 'uploadOper') is null " + +// "exec('alter table dbo.bmp_ProductSpeciesTab add uploadOper varchar(2000);')"); +// sqlBuilder.append("if col_length('dbo.bmp_ProductSpeciesTab', 'downloadOper') is null " + +// "exec('alter table dbo.bmp_ProductSpeciesTab add downloadOper varchar(2000);')"); +// sqlBuilder.append("if col_length('dbo.bmp_ProductSpeciesTab', 'deleteOper') is null " + +// "exec('alter table dbo.bmp_ProductSpeciesTab add deleteOper varchar(2000);')"); +// sqlBuilder.append("if col_length('dbo.bmp_ProductSpeciesTab', 'previewOper') is null " + +// "exec('alter table dbo.bmp_ProductSpeciesTab add previewOper varchar(2000);')"); +// // endregion + + String sql = this.sqlProvider.updateProductSpeciesTabSql(); + + jdbcTemplate.execute(sql); + } + + /** + * 更新系统菜单表 + */ + + private void updateSysMenuTab() { +// StringBuilder sqlBuilder = new StringBuilder(); + + // region p_formmenuconfigtab + // dllfilename1 web端的模块,targetmode 菜单作用位置,targetmodel 0为全部,3 为web +// sqlBuilder.append("if col_length('dbo.p_formmenuconfigtab', 'dllfilename1') is null " + +// "exec('alter table dbo.P_FormMenuConfigTab add DllFileName1 varchar(100);') " + +// "if col_length('dbo.p_formmenuconfigtab', 'TargetMode') is null " + +// "exec('alter table dbo.P_FormMenuConfigTab add TargetMode int default(0);') " + +// "if col_length('dbo.p_formmenuconfigtab', 'GroupCaption') is null " + +// "exec('alter table dbo.P_FormMenuConfigTab add GroupCaption varchar(50);') " + +// "if col_length('dbo.p_formmenuconfigtab', 'SeriesId') is null " + +// "exec('alter table dbo.P_FormMenuConfigTab add SeriesId int');"); + // 以下升级注释掉,因为cs还未做到同步 + // if col_length('p_formmenuconfigtab', 'useFlag') is null + // exec('alter table P_FormMenuConfigTab add useFlag int'); + // endregion + + String sql = this.sqlProvider.updateSysMenuTabSql(); + + jdbcTemplate.execute(sql); + } + + /** + * 更新子系统表 + */ + + private void updateSubSysTab() { +// StringBuilder sqlBuilder = new StringBuilder(); +// +// // region P_SubSystemTab +// // used 在cs上1表示可以点,0表示禁用不可点,visible控制禁用,0启用 +// sqlBuilder.append("if col_length('dbo.P_SubSystemTab', 'subsystip') is null " + +// "exec('alter table dbo.P_SubSystemTab add subsystip varchar(500);')"); +// sqlBuilder.append("if col_length('dbo.P_SubSystemTab', 'visible') is null " + +// "exec('alter table dbo.P_SubSystemTab add visible int default 0;')"); +// // 这个好像没用210521 +// sqlBuilder.append("if col_length('dbo.P_SubSystemTab', 'webshow') is null " + +// "exec('alter table dbo.P_SubSystemTab add webshow bit default 1;')"); +// // endregion + + String sql = this.sqlProvider.updateSubSysTabSql(); + + jdbcTemplate.execute(sql); + } + + /** + * 更新单据类型表 + */ + + private void updateBillTypeTab() { +// StringBuilder sqlBuilder = new StringBuilder(); +// +// // region p_systembilltype +// sqlBuilder.append("if col_length('dbo.p_systembilltype', 'overbacksql') is null " + +// "exec('alter table dbo.p_systembilltype add overbacksql varchar(3000),overbackkey uniqueidentifier;') " + +// "if col_length('dbo.p_systembilltype', 'PopupUnionCode') is null " + +// "exec('alter table dbo.p_systembilltype add PopupUnionCode [varchar](50) NULL') " + +// "if col_length('dbo.p_systembilltype', 'defaultShowSearch') is null " + +// "exec('alter table dbo.p_systembilltype add defaultShowSearch [int] NULL') " + +// "if col_length('dbo.p_systembilltype', 'MuitlAuditFlag') is null " + +// "exec('alter table dbo.p_systembilltype add MuitlAuditFlag [int] NULL') " + +// "if col_length('dbo.p_systembilltype', 'bs_adddllname') is null " + +// "exec('alter table dbo.p_systembilltype add bs_adddllname varchar(100) NULL') " + +// "if col_length('dbo.p_systembilltype', 'BackSelected') is null " + +// "exec('alter table dbo.p_systembilltype add BackSelected bit NULL');"); +// // endregion + + String sql = this.sqlProvider.updateBillTypeTabSql(); + + jdbcTemplate.execute(sql); + } + + private void updateBillTypeTab_210901() { +// StringBuilder sqlBuilder = new StringBuilder(); +// +// // 处理p_systembilltype表 +// sqlBuilder.append("if col_length('dbo.p_systembilltype', 'countSql') is null " + +// "exec('alter table dbo.p_systembilltype add countSql varchar(max) NULL');"); + + String sql = this.sqlProvider.updateBillTypeTab_210901Sql(); + jdbcTemplate.execute(sql); + } + + /** + * 更新DLL表结构 + */ + private void updateDllTab() { +// StringBuilder sqlBuilder = new StringBuilder(); +// +// // 处理p_systemdlltab表 +// // 地理位置定位的mark图标 +// sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'LocationImg') is null " + +// "exec('alter table dbo.p_systemdlltab add LocationImg varchar(100);')"); +// sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'addcaption') is null " + +// "exec('alter table dbo.p_systemdlltab add addcaption varchar(100);')"); +// sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'modifycaption') is null " + +// "exec('alter table dbo.p_systemdlltab add modifycaption varchar(200);')"); +// sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'applycaption') is null " + +// "exec('alter table dbo.p_systemdlltab add applycaption varchar(200);')"); +// sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'addmodid') is null " + +// "exec('alter table dbo.p_systemdlltab add addmodid varchar(200);')"); +// sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'saveapplyenabled') is null " + +// "exec('alter table dbo.p_systemdlltab add saveapplyenabled int;')"); +// sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'addcopyenabled') is null " + +// "exec('alter table dbo.p_systemdlltab add addcopyenabled int null;')"); +// +// sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'bs_adddllname') is null " + +// "exec('alter table dbo.p_systemdlltab add bs_adddllname varchar(50)') " + +// "if col_length('dbo.p_systemdlltab', 'displayRows') is null " + +// "exec('alter table dbo.p_systemdlltab add displayRows int default 50;')"); +// +// sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'BSRowHeight') is null " + +// "exec('alter table dbo.p_systemdlltab add BSRowHeight int;')"); +// +// sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'popupWidth') is null " + +// "exec('alter table dbo.p_systemdlltab add popupWidth int,popupHeight int;')"); +// +// sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'BackSelected') is null " + +// "exec('alter table dbo.p_systemdlltab add BackSelected bit;')"); + + String sql = this.sqlProvider.updateDllTabSql(); + + jdbcTemplate.execute(sql); + } + + private void updateDllTab_210610() { +// StringBuilder sqlBuilder = new StringBuilder(); +// +// // 处理p_systemdlltab表 +// // 地理位置定位的mark图标 +// sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'delCaption') is null " + +// "exec('alter table dbo.p_systemdlltab add delCaption varchar(100);')"); + String sql = this.sqlProvider.updateDllTab_210610Sql(); + + jdbcTemplate.execute(sql); + } + + private void updateDllTab_210624() { +// StringBuilder sqlBuilder = new StringBuilder(); +// +// // 处理p_systemdlltab表 +// sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'addHintMSG') is null " + +// "exec('alter table dbo.p_systemdlltab add addHintMSG varchar(1000);')"); + + String sql = this.sqlProvider.updateDllTab_210624Sql(); + + jdbcTemplate.execute(sql); + } + + /** + * 待办事项模块各模块数据sql + */ + private void updateDllTab_21429() { +// StringBuilder sqlBuilder = new StringBuilder(); +// +// // 处理p_systemdlltab表 +// sqlBuilder.append(String.format("if col_length('%s', 'tasksql') is null " + +// "exec('alter table %s add tasksql varchar(max);')", +// "dbo.p_systemdlltab", "dbo.p_systemdlltab")); +// +// // 处理p_systembilltype表 +// sqlBuilder.append(String.format("if col_length('%s', 'tasksql') is null " + +// "exec('alter table %s add tasksql varchar(max);')", +// "dbo.p_systembilltype", "dbo.p_systembilltype")); +// sqlBuilder.append(String.format("if col_length('%s', 'newver') is null " + +// "exec('alter table %s add newver int;')", +// "dbo.p_systembilltype", "dbo.p_systembilltype")); +// sqlBuilder.append(String.format("if col_length('%s', 'newWFVer') is null " + +// "exec('alter table %s add newWFVer int;')", +// "dbo.p_systembilltype", "dbo.p_systembilltype")); + + String sql = this.sqlProvider.updateDllTab_21429Sql(); + + jdbcTemplate.execute(sql); + } + + /** + * 升级模块附件预览信息图表大小 + */ + private void updateDllTab_240617() { + StringBuilder sqlBuilder = new StringBuilder(); + + // 处理p_systemdlltab表 + sqlBuilder.append(String.format("if col_length('%s', 'attatchInfoWidth') is null " + + "exec('alter table %s add attatchInfoWidth int;')", + "dbo.p_systemdlltab", "dbo.p_systemdlltab")); + // sqlBuilder.append(String.format("if col_length('%s', 'attatchInfoHeight') is null " + + // "exec('alter table %s add attatchInfoHeight int;')", + // "p_systemdlltab", "p_systemdlltab")); + + // 处理p_systembilltype表 + sqlBuilder.append(String.format("if col_length('%s', 'attatchInfoWidth') is null " + + "exec('alter table %s add attatchInfoWidth int;')", + "dbo.p_systembilltype", "dbo.p_systembilltype")); + // sqlBuilder.append(String.format("if col_length('%s', 'attatchInfoHeight') is null " + + // "exec('alter table %s add attatchInfoHeight int;')", + // "p_systembilltype", "p_systembilltype")); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 升级基础模块数据源为接口时 + */ + private void updateDllTab_240619() { + StringBuilder sqlBuilder = new StringBuilder(); + + // 处理p_systemdlltab表 + sqlBuilder.append(String.format("if col_length('%s', 'apiDataNode') is null " + + "exec('alter table %s add apiDataNode varchar(100);')", + "dbo.p_systemdlltab", "dbo.p_systemdlltab")); + sqlBuilder.append(String.format("if col_length('%s', 'apiSuccNode') is null " + + "exec('alter table %s add apiSuccNode varchar(100);')", + "dbo.p_systemdlltab", "dbo.p_systemdlltab")); + sqlBuilder.append(String.format("if col_length('%s', 'apiSuccVal') is null " + + "exec('alter table %s add apiSuccVal varchar(100);')", + "dbo.p_systemdlltab", "dbo.p_systemdlltab")); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 待办事项模块各模块数据sql + */ + private void updateDllTab_231215() { + StringBuilder sqlBuilder = new StringBuilder(); + + // 处理p_systemdlltab表 + sqlBuilder.append(String.format("if col_length('%s', 'MuitlAuditFlag') is null " + + "exec('alter table %s add MuitlAuditFlag int;')", + "dbo.p_systemdlltab", "dbo.p_systemdlltab")); + + // 处理p_systembilltype表 + sqlBuilder.append(String.format("if col_length('%s', 'MuitlAuditFlag') is null " + + "exec('alter table %s add MuitlAuditFlag int;')", + "dbo.p_systembilltype", "dbo.p_systembilltype")); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 待办事项模块各模块数据sql + */ + private void updateDllTab_240117() { + StringBuilder sqlBuilder = new StringBuilder(); + + // 处理p_systemdlltab表 + sqlBuilder.append(String.format("if col_length('%s', 'disableDetail') is null " + + "exec('alter table %s add disableDetail int default 0;')", + "dbo.p_systemdlltab", "dbo.p_systemdlltab")); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + private void updateGroupTab_240120() { + StringBuilder sqlBuilder = new StringBuilder(); + + // 处理p_sydbGroupTab表 + sqlBuilder.append(String.format("if col_length('%s', 'localIp') is null " + + "exec('alter table %s add localIp varchar(100);')", + "dbo.p_sydbGroupTab", "dbo.p_sydbGroupTab")); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 待办事项模块各模块数据sql + */ + private void updateDllTab_210519() { + StringBuilder sqlBuilder = new StringBuilder(); + + // 处理p_systemdlltab表 + sqlBuilder.append(String.format("if col_length('%s', 'selectLeaf') is null " + + "exec('alter table %s add selectLeaf int default 1;')", + "dbo.p_systemdlltab", "dbo.p_systemdlltab")); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 基础档案新增复选框选择模式 + */ + private void updateDllTab_221024() { + StringBuilder sqlBuilder = new StringBuilder(); + + // 处理p_systemdlltab表 + sqlBuilder.append(String.format("if col_length('%s', 'multcheck') is null " + + "exec('alter table %s add multcheck int default 0;')", + "dbo.p_systemdlltab", "dbo.p_systemdlltab")); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + private void updateDllTab_210901() { + StringBuilder sqlBuilder = new StringBuilder(); + + // 处理p_systemdlltab表 + // 地理位置定位的mark图标 + sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'countSql') is null " + + "exec('alter table dbo.p_systemdlltab add countSql varchar(max);')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 更新DLL表详情 + *

+ * 创建时间:2021-02-02 + * 修改人: + * 修改时间: + * 修改备注: + * 版本: + */ + private void updateDllTabDetail() { + StringBuilder sqlBuilder = new StringBuilder(); + + // 处理p_systemDlltabDetail表 + sqlBuilder.append("if col_length('dbo.p_systemDlltabDetail', 'unionparentfield') is null " + + "exec('alter table dbo.p_systemDlltabDetail add unionparentfield varchar(100);')"); + sqlBuilder.append("if col_length('dbo.p_systemDlltabDetail', 'operUsers') is null " + + "exec('alter table dbo.p_systemDlltabDetail add operUsers varchar(3000);')"); + sqlBuilder.append("if col_length('dbo.p_systemDlltabDetail', 'library') is null " + + "exec('alter table dbo.p_systemDlltabDetail add library varchar(100);')"); + sqlBuilder.append("if col_length('dbo.p_systemDlltabDetail', 'addVisible') is null " + + "begin " + + "exec('alter table dbo.p_systemDlltabDetail add addVisible int default 1;') " + + "exec('update dbo.p_systemDlltabDetail set addVisible=1;') " + + "end"); + + // sumcond 20200921 + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + private void updateDllTabDetail_210430() { + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append(String.format("if col_length('%s', 'displayMode') is null " + + "exec('alter table %s add displayMode int;')", + "dbo.p_systemdlltabdetail", "dbo.p_systemdlltabdetail")); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + private void updateDllTabDetail_250426() { + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append(String.format("if col_length('%s', 'gridDetailCheck') is null " + + "exec('alter table %s add gridDetailCheck int;')", + "dbo.p_systemdlltabdetail", "dbo.p_systemdlltabdetail")); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 基础档案明细添加界面显示方式 + */ + private void updateDllTabDetail_210514() { + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append(String.format("if col_length('%s', 'addShowMode') is null " + + "exec('alter table %s add addShowMode int;')", + "dbo.p_systemdlltabdetail", "dbo.p_systemdlltabdetail")); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 明细在大屏中的高和宽 + */ + private void updateDllTabDetail_220114() { + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append(String.format("if col_length('%s', 'bandHeight') is null " + + "exec('alter table %s add bandHeight varchar(10);') " + + "if col_length('%s', 'bandWidth') is null " + + "exec('alter table %s add bandWidth varchar(10);')", + "dbo.p_systemdlltabdetail", "dbo.p_systemdlltabdetail", + "dbo.p_systemdlltabdetail", "dbo.p_systemdlltabdetail")); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 基础档案明细添加界面显示方式 + */ + private void updateDllTabDetail_210914() { + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append(String.format("if col_length('%s', 'visibleCond') is null " + + "exec('alter table %s add visibleCond varchar(200);') " + + "if col_length('%s', 'fieldCond') is null " + + "exec('alter table %s add fieldCond varchar(200);') " + + "if col_length('%s', 'disableField') is null " + + "exec('alter table %s add disableField varchar(200);') " + + "if col_length('%s', 'fieldCond1') is null " + + "exec('alter table %s add fieldCond1 varchar(200);') " + + "if col_length('%s', 'disableField1') is null " + + "exec('alter table %s add disableField1 varchar(200);') " + + "if col_length('%s', 'fieldCond2') is null " + + "exec('alter table %s add fieldCond2 varchar(200);') " + + "if col_length('%s', 'disableField2') is null " + + "exec('alter table %s add disableField2 varchar(200);') " + + "if col_length('%s', 'unionCond') is null " + + "exec('alter table %s add unionCond varchar(2000);')", + "dbo.p_systemdlltabdetail", "dbo.p_systemdlltabdetail", + "dbo.p_systemdlltabdetail", "dbo.p_systemdlltabdetail", + "dbo.p_systemdlltabdetail", "dbo.p_systemdlltabdetail", + "dbo.p_systemdlltabdetail", "dbo.p_systemdlltabdetail", + "dbo.p_systemdlltabdetail", "dbo.p_systemdlltabdetail", + "dbo.p_systemdlltabdetail", "dbo.p_systemdlltabdetail", + "dbo.p_systemdlltabdetail", "dbo.p_systemdlltabdetail", + "dbo.p_systemdlltabdetail", "dbo.p_systemdlltabdetail")); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 更新工作簿表结构 + */ + private void updateWorkBookTab() throws SQLException { + StringBuilder sqlBuilder = new StringBuilder(); + String tableName = "dbo.p_systemwordbooktab"; + + // 添加多个字段检查和添加语句 + sqlBuilder.append(String.format( + "if col_length('%s', 'InputHintText') is null exec('alter table %s add InputHintText varchar(1000);')%n", + tableName, tableName)); + sqlBuilder.append(String.format( + "if col_length('%s', 'TitleColor') is null exec('alter table %s add TitleColor varchar(100);')%n", + tableName, tableName)); + sqlBuilder.append(String.format( + "if col_length('%s', 'userenname') is null exec('alter table %s add userenname varchar(100);')%n", + tableName, tableName)); + sqlBuilder.append(String.format( + "if col_length('%s', 'bs_order') is null exec('alter table %s add bs_order int;')%n", + tableName, tableName)); + sqlBuilder.append(String.format( + "if col_length('%s', 'bs_field') is null exec('alter table %s add bs_field varchar(1000);')%n", + tableName, tableName)); + sqlBuilder.append(String.format( + "if col_length('%s', 'bs_color') is null exec('alter table %s add bs_color varchar(100);')%n", + tableName, tableName)); + sqlBuilder.append(String.format( + "if col_length('%s', 'bs_fontsize') is null exec('alter table %s add bs_fontsize varchar(100);')%n", + tableName, tableName)); + sqlBuilder.append(String.format( + "if col_length('%s', 'sumCond') is null exec('alter table %s add sumCond varchar(500);')%n", + tableName, tableName)); + sqlBuilder.append(String.format( + "if col_length('%s', 'labelWidth') is null exec('alter table %s add labelWidth int;')%n", + tableName, tableName)); + sqlBuilder.append(String.format( + "if col_length('%s', 'labelAlign') is null exec('alter table %s add labelAlign int;')%n", + tableName, tableName)); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 2021-05-27更新工作簿表 + */ + private void updateWorkBookTab_210527() throws SQLException { + StringBuilder sqlBuilder = new StringBuilder(); + String tableName = "dbo.p_systemwordbooktab"; + + sqlBuilder.append(String.format( + "if col_length('%s', 'disableCond') is null exec('alter table %s add disableCond varchar(1000);')%n", + tableName, tableName)); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 2021-09-11更新工作簿表 + */ + private void updateWorkBookTab_210911() throws SQLException { + StringBuilder sqlBuilder = new StringBuilder(); + String tableName = "dbo.p_systemwordbooktab"; + + sqlBuilder.append(String.format( + "if col_length('%s', 'disableType') is null exec('alter table %s add disableType int;')%n", + tableName, tableName)); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 2022-07-04升级字段高亮配置 + */ + private void updateWorkBookTab_220704() throws SQLException { + StringBuilder sqlBuilder = new StringBuilder(); + + // 处理p_systemwordbooktab表 + String table1 = "dbo.p_systemwordbooktab"; + sqlBuilder.append(String.format( + "if col_length('%s', 'highlightFColor') is null exec('alter table %s add highlightFColor varchar(50);')%n", + table1, table1)); + sqlBuilder.append(String.format( + "if col_length('%s', 'highlightBColor') is null exec('alter table %s add highlightBColor varchar(50);')%n", + table1, table1)); + sqlBuilder.append(String.format( + "if col_length('%s', 'highlightBold') is null exec('alter table %s add highlightBold int;')%n", + table1, table1)); + + // 处理p_systembillinfo表 + String table2 = "dbo.p_systembillinfo"; + sqlBuilder.append(String.format( + "if col_length('%s', 'highlightFColor') is null exec('alter table %s add highlightFColor varchar(50);')%n", + table2, table2)); + sqlBuilder.append(String.format( + "if col_length('%s', 'highlightBColor') is null exec('alter table %s add highlightBColor varchar(50);')%n", + table2, table2)); + sqlBuilder.append(String.format( + "if col_length('%s', 'highlightBold') is null exec('alter table %s add highlightBold int;')%n", + table2, table2)); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 2024-03-21更新工作簿表 + */ + private void updateWorkBookTab_240321() throws SQLException { + StringBuilder sqlBuilder = new StringBuilder(); + String tableName = "dbo.p_systemwordbooktab"; + + sqlBuilder.append(String.format( + "if col_length('%s', 'sumCalc') is null exec('alter table %s add sumCalc varchar(8000);')%n", + tableName, tableName)); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 2024-04-12更新通知表 + */ + private void updateNotification_240412() throws SQLException { + StringBuilder sqlBuilder = new StringBuilder(); + + // 处理p_systemNotification表 + sqlBuilder.append(String.format( + "if col_length('%s', 'templetename') is null exec('alter table %s add templetename varchar(100);')%n", + "dbo.p_systemNotification", "dbo.p_systemNotification")); + + // 处理p_systemMessageTab表 + sqlBuilder.append(String.format( + "if col_length('%s', 'templetename') is null exec('alter table %s add templetename varchar(100);')%n", + "dbo.p_systemMessageTab", "dbo.p_systemMessageTab")); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 更新通知表结构 + */ + private void updateNotificationTab() throws SQLException { + StringBuilder sqlBuilder = new StringBuilder(); + + // 创建p_systemNotification表 + sqlBuilder.append("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[p_systemNotification]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)\n"); + sqlBuilder.append("begin \n"); + sqlBuilder.append(" CREATE TABLE [dbo].[p_systemNotification](\n"); + sqlBuilder.append(" [Id] [int] IDENTITY(1,1) NOT NULL,\n"); + sqlBuilder.append(" [DLLCoid] [varchar](50) NULL,\n"); + sqlBuilder.append(" [Msg] [nvarchar](500) NULL,\n"); + sqlBuilder.append(" [Cnt] [int] NULL,\n"); + sqlBuilder.append(" [Status] [int] NULL,\n"); + sqlBuilder.append(" [Created] [datetime] NULL,\n"); + sqlBuilder.append(" [UserId] [varchar](50) NULL,\n"); + sqlBuilder.append(" [TypeId] [varchar](50) NULL,\n"); + sqlBuilder.append(" [DllFileName] [varchar](100) NULL,\n"); + sqlBuilder.append(" [ModuleName] [varchar](100) NULL,\n"); + sqlBuilder.append(" [LoginAccount] [varchar](100) NULL,\n"); + sqlBuilder.append(" [msgid] [int] NOT NULL,\n"); + sqlBuilder.append(" [cnt1] [int] NOT NULL,\n"); + sqlBuilder.append(" [msgid1] [int] NULL,\n"); + sqlBuilder.append(" [stepcode] [int] NULL,\n"); + sqlBuilder.append(" [messid] [int] NULL,\n"); + sqlBuilder.append(" [modtype] [int] NULL,\n"); + sqlBuilder.append(" [billdocument_id] [varchar](50) NULL,\n"); + sqlBuilder.append(" [menuid] [int] NULL,\n"); + sqlBuilder.append(" [confirmid] [varchar](20) NULL,\n"); + sqlBuilder.append(" CONSTRAINT [PK_p_systemNotification] PRIMARY KEY CLUSTERED \n"); + sqlBuilder.append(" (\n"); + sqlBuilder.append(" [Id] ASC\n"); + sqlBuilder.append(" ) )\n"); + sqlBuilder.append("end;\n"); + + // 添加confirmid字段 + sqlBuilder.append(String.format( + "if col_length('%s', 'confirmid') is null exec('alter table %s add confirmid varchar(20);')%n", + "dbo.p_systemNotification", "dbo.p_systemNotification")); + + // 创建p_systemNotification_History表 + sqlBuilder.append("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[p_systemNotification_History]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)\n"); + sqlBuilder.append("begin \n"); + sqlBuilder.append(" CREATE TABLE [dbo].[p_systemNotification_History](\n"); + sqlBuilder.append(" [Id] [int] IDENTITY(1,1) NOT NULL,\n"); + sqlBuilder.append(" [OperateDate] [datetime] NULL default(getdate()),\n"); + sqlBuilder.append(" [keyvalue] [varchar](50) NULL,\n"); + sqlBuilder.append(" [UserId] [varchar](50) NULL,\n"); + sqlBuilder.append(" [menuid] [int] NULL,\n"); + sqlBuilder.append(" [AuditMessages] [varchar](MAX) NULL,\n"); + sqlBuilder.append(" [messid] [int] NULL,\n"); + sqlBuilder.append(" [DLLCoid] [varchar](50) NULL,\n"); + sqlBuilder.append(" [num] [int] NOT NULL,\n"); + sqlBuilder.append(" [dllname] [varchar](100) NULL,\n"); + sqlBuilder.append(" [modulename] [varchar](200) NULL,\n"); + sqlBuilder.append(" [stepcode] [int] NULL,\n"); + sqlBuilder.append(" [menumode] [int] NULL,\n"); + sqlBuilder.append(" [hasSearch] [int] NULL,\n"); + sqlBuilder.append(" [Status] [int] NULL,\n"); + sqlBuilder.append(" [StatusMsg] [varchar](MAX) NULL,\n"); + sqlBuilder.append(" CONSTRAINT [PK_p_systemNotification_History] PRIMARY KEY CLUSTERED \n"); + sqlBuilder.append(" (\n"); + sqlBuilder.append(" [Id] ASC\n"); + sqlBuilder.append(" ) )\n"); + sqlBuilder.append("end;\n"); + + // 修改字段长度 + sqlBuilder.append("alter table dbo.p_systemNotification_History alter column keyvalue varchar(MAX);\n"); + sqlBuilder.append("alter table dbo.p_systemNotification_History alter column DLLCoid varchar(500);\n"); + sqlBuilder.append("alter table dbo.p_systemNotification_History alter column dllname varchar(MAX);\n"); + sqlBuilder.append("alter table dbo.p_systemNotification_History alter column modulename varchar(MAX);\n"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 升级消息及微信表 + */ + private void updateMessge() throws SQLException { + StringBuilder sqlBuilder = new StringBuilder(); + + // 创建p_systemMessageTab表 + sqlBuilder.append("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[p_systemMessageTab]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)\n"); + sqlBuilder.append("begin \n"); + sqlBuilder.append(" CREATE TABLE [dbo].[p_systemMessageTab](\n"); + sqlBuilder.append(" [ID] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,\n"); + sqlBuilder.append(" [Title] [varchar](50) NULL,\n"); + sqlBuilder.append(" [Msg] [varchar](500) NULL,\n"); + sqlBuilder.append(" [MsgDetail] [varchar](5000) NULL,\n"); + sqlBuilder.append(" [NoticeUserID] [int] NULL,\n"); + sqlBuilder.append(" [NoticeUserName] [varchar](50) NULL,\n"); + sqlBuilder.append(" [DllCoid] [varchar](50) NULL,\n"); + sqlBuilder.append(" [DllFileName] [varchar](50) NULL,\n"); + sqlBuilder.append(" [CreateDate] [datetime] NULL,\n"); + sqlBuilder.append(" [ReadFlag] [bit] NOT NULL,\n"); + sqlBuilder.append(" [CreateUserID] [int] NULL,\n"); + sqlBuilder.append(" [CreateUserName] [varchar](50) NULL,\n"); + sqlBuilder.append(" [DeleteFlag] [int] NULL CONSTRAINT [DF__p_systemM__Delet__744B9841] DEFAULT ((0)),\n"); + sqlBuilder.append(" CONSTRAINT [PK_p_systemMessageTab] PRIMARY KEY( [ID] ASC )\n"); + sqlBuilder.append(" )\n"); + sqlBuilder.append("end;\n"); + + // 创建微信推送表P_WX_BSclientMsg + sqlBuilder.append("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[P_WX_BSclientMsg]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)\n"); + sqlBuilder.append("begin \n"); + sqlBuilder.append(" CREATE TABLE [dbo].[P_WX_BSclientMsg](\n"); + sqlBuilder.append(" [id] [bigint] IDENTITY(1,1) NOT NULL,\n"); + sqlBuilder.append(" [AuditMessages] [varchar](max) NULL,\n"); + sqlBuilder.append(" [UserId] [varchar](500) NULL,\n"); + sqlBuilder.append(" [keyvalue] [varchar](500) NULL,\n"); + sqlBuilder.append(" [stepcode] [int] NULL,\n"); + sqlBuilder.append(" [moduleid] [varchar](500) NULL,\n"); + sqlBuilder.append(" [operDate] [datetime] NULL\n"); + sqlBuilder.append(") ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]\n"); + sqlBuilder.append("ALTER TABLE [dbo].[P_WX_BSclientMsg] ADD CONSTRAINT [DF_p_WX_BSclientMsg_operDate] DEFAULT (getdate()) FOR [operDate]\n"); + sqlBuilder.append("end;\n"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 主页firstPage配置表 + */ + private void updateFirstPageTable() throws SQLException { + StringBuilder sqlBuilder = new StringBuilder(); + + // 1. 基本配置表P_SystemFirstPageSetTab + sqlBuilder.append("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[P_SystemFirstPageSetTab]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)\n"); + sqlBuilder.append("begin \n"); + sqlBuilder.append(" CREATE TABLE [dbo].[P_SystemFirstPageSetTab](\n"); + sqlBuilder.append(" [id] [int] IDENTITY(1,1) NOT NULL,\n"); + sqlBuilder.append(" [itemCode] [varchar](20) NOT NULL,\n"); + sqlBuilder.append(" [itemTitle] [varchar](50) NULL,\n"); + sqlBuilder.append(" [itemIMG] [varchar](150) NULL,\n"); + sqlBuilder.append(" [itemRowNo] [int] NULL,\n"); + sqlBuilder.append(" [itemOrder] [int] NULL default(1),\n"); + sqlBuilder.append(" [itemWidth] [varchar](20) NULL,\n"); + sqlBuilder.append(" [itemHeight] [varchar](20) NULL,\n"); + sqlBuilder.append(" [itemType] [int] NULL,\n"); + sqlBuilder.append(" [itemTypeFull] [varchar](50) NULL,\n"); + sqlBuilder.append(" [itemDataSource] [varchar](5000) NULL,\n"); + sqlBuilder.append(" [itemLinked] [int] NULL default(1),\n"); + sqlBuilder.append(" [itemPrivilege] [varchar](500) NULL,\n"); + sqlBuilder.append(" [enableFlag] [int] NULL default(0),\n"); + sqlBuilder.append(" [enableType] [int] NOT NULL default(-1),\n"); + sqlBuilder.append(" [operateDate] [datetime] NULL default(getdate()),\n"); + sqlBuilder.append(" [operatorName] [varchar](20) NULL,\n"); + sqlBuilder.append(" [itemTemplate] [varchar](100) NULL,\n"); + sqlBuilder.append(" CONSTRAINT [PK_P_SystemFirstPageSetTab] PRIMARY KEY( [itemCode] ASC )\n"); + sqlBuilder.append(" )\n"); + sqlBuilder.append("end;\n"); + + // 添加enableType字段 + sqlBuilder.append("if col_length('dbo.P_SystemFirstPageSetTab', 'enableType') is null \n"); + sqlBuilder.append(" exec('alter table dbo.P_SystemFirstPageSetTab add enableType int;')\n"); + + // 2. 高宽权限配置表P_SystemOperFirstPageTab + sqlBuilder.append("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[P_SystemOperFirstPageTab]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)\n"); + sqlBuilder.append("begin \n"); + sqlBuilder.append(" CREATE TABLE [dbo].[P_SystemOperFirstPageTab](\n"); + sqlBuilder.append(" [id] [int] IDENTITY(1,1) NOT NULL,\n"); + sqlBuilder.append(" [operatorId] [int] NOT NULL default(-1),\n"); + sqlBuilder.append(" [itemCode] [varchar](20) NOT NULL,\n"); + sqlBuilder.append(" [itemRowNo] [int] NULL,\n"); + sqlBuilder.append(" [itemOrder] [int] NULL,\n"); + sqlBuilder.append(" [itemWidth] [varchar](20) NULL,\n"); + sqlBuilder.append(" [itemHeight] [varchar](20) NULL,\n"); + sqlBuilder.append(" [itemLeft] [varchar](20) NULL,\n"); + sqlBuilder.append(" [itemTop] [varchar](20) NULL,\n"); + sqlBuilder.append(" [operateDate] [datetime] NOT NULL default(getdate()),\n"); + sqlBuilder.append(" [operatorName] [varchar](20) NULL,\n"); + sqlBuilder.append(" [enableFlag] [int] NULL default(1),\n"); + sqlBuilder.append(" [deleted] [bit] NULL,\n"); + sqlBuilder.append(" CONSTRAINT [Unique_P_SystemOperFirstPageTab] UNIQUE ( [operatorId] ASC,[itemCode] ASC )\n"); + sqlBuilder.append(" )\n"); + sqlBuilder.append("end\n"); + + // 3. 按模块配置的桌面表P_SystemDllFirstPageTab + sqlBuilder.append("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[P_SystemDllFirstPageTab]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)\n"); + sqlBuilder.append("begin \n"); + sqlBuilder.append(" CREATE TABLE [dbo].[P_SystemDllFirstPageTab](\n"); + sqlBuilder.append(" [id] [int] IDENTITY(1,1) NOT NULL,\n"); + sqlBuilder.append(" [dllcoid] [varchar](50) NOT NULL,\n"); + sqlBuilder.append(" [itemCode] [varchar](20) NOT NULL,\n"); + sqlBuilder.append(" [itemRowNo] [int] NULL,\n"); + sqlBuilder.append(" [itemOrder] [int] NULL,\n"); + sqlBuilder.append(" [itemWidth] [varchar](20) NULL,\n"); + sqlBuilder.append(" [itemHeight] [varchar](20) NULL,\n"); + sqlBuilder.append(" [itemLeft] [varchar](20) NULL,\n"); + sqlBuilder.append(" [itemTop] [varchar](20) NULL,\n"); + sqlBuilder.append(" [operateDate] [datetime] NOT NULL default(getdate()),\n"); + sqlBuilder.append(" [operatorName] [varchar](20) NULL,\n"); + sqlBuilder.append(" [enableFlag] [int] NULL default(1),\n"); + sqlBuilder.append(" [deleted] [bit] NULL,\n"); + sqlBuilder.append(" [queryField] [varchar](50) NULL,\n"); + sqlBuilder.append(" [condition] [varchar](2000) NULL,\n"); + sqlBuilder.append(" CONSTRAINT [Unique_P_SystemDllFirstPageTab] UNIQUE ( [dllcoid] ASC,[itemCode] ASC )\n"); + sqlBuilder.append(" )\n"); + sqlBuilder.append("end\n"); + + // 4. 模块个人桌面表P_SystemDllOperFirstPageTab + sqlBuilder.append("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[P_SystemDllOperFirstPageTab]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)\n"); + sqlBuilder.append("begin \n"); + sqlBuilder.append(" CREATE TABLE [dbo].[P_SystemDllOperFirstPageTab](\n"); + sqlBuilder.append(" [id] [int] IDENTITY(1,1) NOT NULL,\n"); + sqlBuilder.append(" [operatorId] [int] NOT NULL default(-1),\n"); + sqlBuilder.append(" [dllcoid] [varchar](50) NOT NULL,\n"); + sqlBuilder.append(" [itemCode] [varchar](20) NOT NULL,\n"); + sqlBuilder.append(" [itemRowNo] [int] NULL,\n"); + sqlBuilder.append(" [itemOrder] [int] NULL,\n"); + sqlBuilder.append(" [itemWidth] [varchar](20) NULL,\n"); + sqlBuilder.append(" [itemHeight] [varchar](20) NULL,\n"); + sqlBuilder.append(" [itemLeft] [varchar](20) NULL,\n"); + sqlBuilder.append(" [itemTop] [varchar](20) NULL,\n"); + sqlBuilder.append(" [operateDate] [datetime] NOT NULL default(getdate()),\n"); + sqlBuilder.append(" [operatorName] [varchar](20) NULL,\n"); + sqlBuilder.append(" [enableFlag] [int] NULL default(1),\n"); + sqlBuilder.append(" [deleted] [bit] NULL,\n"); + sqlBuilder.append(" CONSTRAINT [Unique_P_SystemDllOperFirstPageTab] UNIQUE ( [operatorId] ASC, [dllcoid] ASC,[itemCode] ASC )\n"); + sqlBuilder.append(" )\n"); + sqlBuilder.append("end\n"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 更新手机验证码表 + */ + public void updatePhoneCode() throws SQLException { +// StringBuilder sqlBuilder = new StringBuilder(); +// +// // 关键修改1:用标准SQL判断表是否存在(Kingbase兼容版100%支持) +// // 替代SQL Server的sysobjects/OBJECTPROPERTY,避免判断失效 +// sqlBuilder.append("IF NOT EXISTS (\n"); +// sqlBuilder.append(" SELECT 1 FROM information_schema.tables \n"); +// sqlBuilder.append(" WHERE table_schema = 'dbo'"); +// sqlBuilder.append(" AND table_name = 'P_ValidCode' "); +// sqlBuilder.append(")\n"); +// sqlBuilder.append("BEGIN\n"); +// // 关键修改2:动态SQL中显式指定架构,且简化写法(避免sp_executesql的上下文问题) +// sqlBuilder.append(" CREATE TABLE dbo.P_ValidCode(\n"); // 直接写dbo.P_ValidCode,不用[](兼容版支持但更简洁) +// sqlBuilder.append(" id INT PRIMARY KEY IDENTITY(1,1) NOT NULL,\n"); +// sqlBuilder.append(" phone VARCHAR(100) NULL,\n"); +// sqlBuilder.append(" sendtime DATETIME NULL,\n"); +// sqlBuilder.append(" validcode VARCHAR(10) NULL,\n"); +// sqlBuilder.append(" type INT NULL,\n"); +// sqlBuilder.append(" ip VARCHAR(20) NULL\n"); +// sqlBuilder.append(" );\n"); +// sqlBuilder.append("END\n"); +// String sqlBuilder = this.sqlProvider.updatePhoneCodeSql(); + + String sql = this.sqlProvider.updatePhoneCodeSql(); + jdbcTemplate.execute(sql); + } + + /** + * 附件表加上审批加上审批步骤码 + */ + private void updatePfmFileTab() { + StringBuilder sqlBuilder = new StringBuilder(); + // 地理位置定位的mark图标 + sqlBuilder.append("if col_length('dbo.P_fm_FileTab', 'stepcode') is null " + + "exec('alter table dbo.P_fm_FileTab add stepcode varchar(100);')"); + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 升级公告,可选升级,现在只有宜宾使用了公告 + */ + private void updateNotic() { + StringBuilder sqlBuilder = new StringBuilder(); + + // 公告升级 + sqlBuilder.append("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[p_announcement]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) " + + "exec('CREATE TABLE [dbo].[p_announcement]( " + + "[Billdocument_Id] [varchar](50) NOT NULL, " + + "[OperatorId] [int] NULL, " + + "[Operatorname] [varchar](50) NULL, " + + "[OperateDate] [datetime] NULL, " + + "[notice] [varchar](5000) NULL, " + + "[id] [int] IDENTITY(1,1) NOT NULL, " + + "[str] [varchar](200) NULL, " + + "[ban] [int] NULL, " + + "[Affirmer] [int] NULL, " + + "[AffirmDate] [datetime] NULL, " + + "[stepcode] [int] NULL, " + + "[stepTime] [decimal](8, 2) NULL, " + + "[endTime] [datetime] NULL, " + + "[stepOver] [int] NOT NULL, " + + "[stepOverTime] [datetime] NULL, " + + "[stepOverId] [int] NULL, " + + "[stepOverName] [varchar](20) NULL, " + + "[billType] [int] NULL, " + + "[stepDirection] [varchar](50) NULL, " + + "[happentime] [datetime] NULL, " + + "[pStepCode] [varchar](100) NULL, " + + "[cancelFlag] [int] NULL, " + + "[cancelTime] [datetime] NULL, " + + "[cancelOper] [int] NULL, " + + "[bz] [int] NULL, " + + "[date] [datetime] NULL, " + + "CONSTRAINT [PK_p_announcement] PRIMARY KEY CLUSTERED " + + "( " + + "[Billdocument_Id] ASC " + + ")WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] " + + ") ON [PRIMARY] " + + "SET ANSI_PADDING OFF " + + "ALTER TABLE [dbo].[p_announcement] ADD CONSTRAINT [DF_p_announcement_Affirmer] DEFAULT ((0)) FOR [Affirmer] " + + "ALTER TABLE [dbo].[p_announcement] ADD CONSTRAINT [DF_p_announcement_stepOver] DEFAULT ((0)) FOR [stepOver] " + + "ALTER TABLE [dbo].[p_announcement] ADD CONSTRAINT [DF_p_announcement_cancelFlag] DEFAULT ((0)) FOR [cancelFlag] " + + "ALTER TABLE [dbo].[p_announcement] ADD CONSTRAINT [DF_p_announcement_bz] DEFAULT ((0)) FOR [bz]');"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 升级图表模板的拓展参数 + */ + private void updateChartExtends() { + StringBuilder sqlBuilder = new StringBuilder(); + // 升级图表模板的拓展参数 + sqlBuilder.append("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[P_SystemChartTab]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) " + + "exec(' CREATE TABLE [dbo].[P_SystemChartTab]( " + + "[id] [int] primary key IDENTITY(1,1) NOT NULL, " + + "[tabkey] uniqueidentifier not NULL, " + + "[formkey] uniqueidentifier not null default newid(), " + + "[dllpar1] [varchar](2000) NULL, " + + "[dllpar2] [varchar](2000) NULL, " + + "[dllpar3] [varchar](2000) NULL, " + + "[dllpar4] [varchar](2000) NULL, " + + "[dllpar5] [varchar](2000) NULL, " + + "[dllpar6] [varchar](2000) NULL, " + + "[dllpar7] [varchar](2000) NULL, " + + "[dllpar8] [varchar](2000) NULL, " + + "[dllpar9] [varchar](2000) NULL, " + + "[dllpar10] [varchar](2000) NULL)');"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 如果字段类型为地图,那么检测有没有地图相关字段,没有就添加 + * + * @param tabname 表名 + * @param fieldname 字段名 + */ + public void updateMapField(String tabname, String fieldname) { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append(String.format("if col_length('%s', '%s_itude') is null " + + "exec('alter table %s add %s_itude varchar(100);')", + tabname, fieldname, tabname, fieldname)); + + sqlBuilder.append(String.format("if col_length('%s', '%s_longitude') is null " + + "exec('alter table %s add %s_longitude varchar(50);')", + tabname, fieldname, tabname, fieldname)); + + sqlBuilder.append(String.format("if col_length('%s', '%s_latitude') is null " + + "exec('alter table %s add %s_latitude varchar(50);')", + tabname, fieldname, tabname, fieldname)); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 更新移动卡片表结构 + */ + private void updateMobileCard() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[P_SystemCardDetailTab]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) " + + "exec('CREATE TABLE [dbo].[P_SystemCardDetailTab]( " + + "[id] [int] IDENTITY(1,1) primary key NOT NULL, " + + "[SourceKey] [varchar](40) NULL, " + + "[rowid] [int] NULL, " + + "[rowheight] [int] NULL, " + + "[splitline] [int] NULL, " + + "[orderid] [int] NULL, " + + "[displaytext] [varchar](200) NULL, " + + "[fontname] [varchar](30) NULL, " + + "[fontsize] [int] NULL, " + + "[fcolor] [varchar](20) NULL, " + + "[bcolor] [varchar](20) NULL, " + + "[dbcolor] [varchar](20) NULL, " + + "[dfcolor] [varchar](20) NULL, " + + "[fbold] [int] NULL, " + + "[fitalic] [int] NULL, " + + "[fstrikeline] [int] NULL, " + + "[RightAlign] [int] NULL, " + + "[displayType] [int] NULL, " + + "[visible] [int] NULL " + + ")');"); + + sqlBuilder.append("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[p_systemCardTab]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) " + + "exec('CREATE TABLE [dbo].[p_systemCardTab]( " + + "[id] [int] IDENTITY(1,1) NOT NULL, " + + "[formKey] [varchar](40) NULL, " + + "[SourceKey] [varchar](40) NULL, " + + "[GroupName] [varchar](50) NULL, " + + "[orderid] [int] NULL, " + + "[maincard] [int] NULL, " + + "[mxorderid] [int] NULL, " + + "[GroupVisible] [int] NULL, " + + "[visible] [int] NULL " + + ") ');"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + private void updateMobileCard_210707() { + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append("if col_length('dbo.p_systemCardDetailTab', 'condition') is null " + + "exec('alter table dbo.p_systemCardDetailTab add condition varchar(200);')"); + + sqlBuilder.append("if col_length('dbo.p_systemCardDetailTab', 'textAlign') is null " + + "exec('alter table dbo.p_systemCardDetailTab add textAlign int;')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + private void updateMobileCard_220322() { + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append("if col_length('dbo.p_systemCardDetailTab', 'colname') is null " + + "exec('alter table dbo.p_systemCardDetailTab add colname varchar(100);')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 升级web的打印程序 + */ + private void updateWebPrint() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[p_systemwebPrint]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) " + + "exec(' create table dbo.p_systemwebPrint( " + + "id int primary key identity(1,1), " + + "printname varchar(100), " + + "content text, " + + "tab varchar(100) " + + ")');"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 检测是否存在流程图配置表,如果不存在就自动添加进去 + */ + private void updateFlowChartCfgTable() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[P_Systemdlltabflowtypestepcfg]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) " + + "exec(' create table dbo.P_Systemdlltabflowtypestepcfg( " + + "id int primary key identity(1,1), " + + "[typeCode] [varchar](10) NOT NULL, " + + "[billType] [varchar](10) NOT NULL, " + + "[option] varchar(4000), " + + "[cfgName] varchar(200), " + + ")');"); + + sqlBuilder.append("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[p_systembillflowtypestepcfg]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) " + + "exec(' create table dbo.p_systembillflowtypestepcfg( " + + "id int primary key identity(1,1), " + + "[typeCode] [varchar](10) NOT NULL, " + + "[billType] [varchar](10) NOT NULL, " + + "[option] varchar(4000), " + + "[cfgName] varchar(200), " + + ")');"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 更新字段 + */ + private void updateField() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_systembillsourcecond', 'InputHintText') is null " + + "exec('alter table dbo.p_systembillsourcecond add InputHintText varchar(1000);')"); + + sqlBuilder.append("if col_length('dbo.p_systembillsourcecond', 'TitleColor') is null " + + "exec('alter table dbo.p_systembillsourcecond add TitleColor varchar(100);')"); + + + sqlBuilder.append("if col_length('dbo.p_systemdlltabflow', 'islocked') is null " + + "exec('alter table dbo.p_systemdlltabflow add islocked varchar(1000);') " + + "if col_length('dbo.p_systemdlltabflow', 'requiredFields') is null " + + "exec('alter table dbo.p_systemdlltabflow add requiredFields varchar(2000);') " + + "if col_length('dbo.p_systemdlltabflow', 'requiredDetailFields') is null " + + "exec('alter table dbo.p_systemdlltabflow add requiredDetailFields varchar(2000);')"); + + sqlBuilder.append("if col_length('dbo.p_systembillflow', 'requiredFields') is null " + + "exec('alter table dbo.p_systembillflow add requiredFields varchar(2000);') " + + "if col_length('dbo.p_systembillflow', 'requiredDetailFields') is null " + + "exec('alter table dbo.p_systembillflow add requiredDetailFields varchar(2000);') " + + "if col_length('dbo.p_systembillflow', 'islocked') is null " + + "exec('alter table dbo.p_systembillflow add islocked int;')"); + + sqlBuilder.append("if col_length('dbo.P_fm_FileTab', 'webPath') is null " + + "exec('alter table dbo.P_fm_FileTab add webPath varchar(2000);') " + + "if col_length('dbo.P_fm_FileTab', 'stepcode') is null " + + "exec('alter table dbo.P_fm_FileTab add stepcode varchar(200);')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + private void updateDllFlow_220823() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_systemdlltabflow', 'auditContent') is null " + + "exec('alter table dbo.p_systemdlltabflow add auditContent varchar(5000)')"); + + sqlBuilder.append("if col_length('dbo.p_systembillflow', 'auditContent') is null " + + "exec('alter table dbo.p_systembillflow add auditContent varchar(5000);')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + private void updateAuditFlowTypeStepTab() { + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append("if col_length('dbo.P_systembillflowtypestep', 'nextStepCode') is null " + + "exec('alter table dbo.P_systembillflowtypestep add nextStepCode varchar(1000);')"); + + sqlBuilder.append("if col_length('dbo.p_systemdlltabflowtypestep', 'nextStepCode') is null " + + "exec('alter table dbo.p_systemdlltabflowtypestep add nextStepCode varchar(1000);')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 审核调用明细 + */ + private void updateDlltabDetailFlowTab() { + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[p_SystemDlltabDetailFlowTab]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) " + + "exec(' CREATE TABLE [dbo].[p_SystemDlltabDetailFlowTab]( " + + "[id] [int] IDENTITY(1,1) primary key NOT NULL, " + + "[typecode] [varchar](50) NULL, " + + "[detailKey] [varchar](50) NULL, " + + "[detailName] [varchar](50) NULL, " + + "[stepcode] [int] NULL, " + + "[modifyFields] [varchar](100) NULL, " + + "[isvisible] [int] NULL default 0, " + + "[displayMode] [int] NULL, " + + "[hintMsg] [varchar](2000) NULL, " + + "[eventType] [varchar](20) NULL, " + + "[addShowMode] [int] NULL default 0 " + + ")');"); + + sqlBuilder.append("if col_length('dbo.p_SystemDlltabDetailFlowTab', 'isvisible') is null " + + "exec('alter table dbo.p_SystemDlltabDetailFlowTab add isvisible bit;')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + private void updateDlltabDetailFlowTab_210430() { + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append("if col_length('dbo.p_SystemDlltabDetailFlowTab', 'displayMode') is null " + + "exec('alter table dbo.p_SystemDlltabDetailFlowTab add displayMode int;')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + private void updateDlltabDetailFlowTab_210514() { + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append("if col_length('dbo.p_SystemDlltabDetailFlowTab', 'addShowMode') is null " + + "exec('alter table dbo.p_SystemDlltabDetailFlowTab add addShowMode int;')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 更新账单字段表结构 + */ + private void updateBillField() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_systembillInfo', 'InputHintText') is null \n") + .append(" exec('alter table dbo.p_systembillInfo add InputHintText varchar(1000);');\n") + .append("if col_length('dbo.p_systembillInfo', 'TitleColor') is null \n") + .append(" exec('alter table dbo.p_systembillInfo add TitleColor varchar(100);');\n") + .append("if col_length('dbo.p_systembillInfo', 'labelWidth') is null \n") + .append(" exec('alter table dbo.p_systembillInfo add labelWidth int;');\n") + .append("if col_length('dbo.p_systembillInfo', 'labelAlign') is null \n") + .append(" exec('alter table dbo.p_systembillInfo add labelAlign int;');"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 更新模块自定义添加模块表结构 + */ + private void updateCusModuleAddTpl() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.P_pubrpsettab', 'moduleContent') is null \n") + .append(" exec('alter table dbo.P_pubrpsettab add moduleContent text;');"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 更新视图定义 + */ + private void updateView() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("IF NOT EXISTS(SELECT 1 FROM sys.views WHERE name='v_systemdlltab')\n") + .append(" if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[p_systembilltype]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)\n") + .append(" exec('CREATE view [dbo].[v_systemdlltab]\n") + .append(" as\n") + .append(" select DllCoid,ToolsName,dirid,bmpSpec,1 as modType,isReport,SQLDT1 as TableName,sql from dbo.p_systemDlltab\n") + .append(" ')\n") + .append(" else\n") + .append(" exec('CREATE view [dbo].[v_systemdlltab]\n") + .append(" as\n") + .append(" select DllCoid,ToolsName,dirid,bmpSpec,1 as modType,isReport,SQLDT1 as TableName,sql from dbo.p_systemDlltab\n") + .append(" union all\n") + .append(" select typecode,typename,dirid,bmpspec,2 as modType,0 as isReport,masterTable as TableName,masterSql from dbo.p_systembilltype\n") + .append(" ');"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 创建Web附件自定义显示方式表 + */ + private void updateAttcCusSet() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[P_fm_WebCusSetTab]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)\n") + .append(" exec('create dbo.table P_fm_WebCusSetTab(\n") + .append(" id int identity primary key ,\n") + .append(" dllcoid varchar(100),\n") + .append(" operatorId varchar(100),\n") + .append(" viewType int --附件显示模式 0:视图,1:表格\n") + .append(" );');"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 创建模板数据表 + */ + private void updateTemplateTab() { + StringBuilder sqlBuilder = new StringBuilder(); + // 模板数据 + sqlBuilder.append("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[T_TemplateData]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)\n") + .append(" exec('create table dbo.T_TemplateData(\n") + .append(" id int identity primary key ,\n") + .append(" tplName varchar(100),\n") + .append(" cmpKey varchar(100),\n") + .append(" moduleId varchar(100),\n") + .append(" dataProvider varchar(100),\n") + .append(" dataSource varchar(8000)\n") + .append(" );');"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 创建BI时间变化列表 + */ + private void updateBITab() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[BI_TimeChangeList]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)\n") + .append(" exec('CREATE TABLE [dbo].[BI_TimeChangeList](\n") + .append(" [id] [int] IDENTITY(1,1) NOT NULL,\n") + .append(" [dllcoid] [varchar](100) NULL,\n") + .append(" [loadDll] [varchar](200) NULL,\n") + .append(" [title] [varchar](300) NULL,\n") + .append(" [desp] [varchar](1000) NULL,\n") + .append(" [showTime] [int] NULL,\n") + .append(" [showMode] [int] NULL,\n") + .append(" [refreshTime] [int] NULL,\n") + .append(" CONSTRAINT [PK_BI_TimeChangeList] PRIMARY KEY CLUSTERED \n") + .append(" (\n") + .append(" [id] ASC\n") + .append(" ));');"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 更新错误信息表结构 + */ + private void updateErrInfoTab() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[p_errlogtab]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)\n") + .append(" exec('CREATE TABLE [dbo].[P_ErrLogTab](\n") + .append(" [id] [int] PRIMARY KEY IDENTITY(1,1) NOT NULL,\n") + .append(" [SubID] [int] NOT NULL default 0,\n") + .append(" [ErrID] [real] NOT NULL default 0,\n") + .append(" [Operator] [varchar](100) NOT NULL default '''',\n") + .append(" [Operatedate] [datetime] NOT NULL default getdate(),\n") + .append(" [Content] [varchar](500) NOT NULL default '''',\n") + .append(" [ErrMethod] [varchar](100) NOT NULL default '''',\n") + .append(" [ErrMsg] [varchar](8000) NOT NULL default '''',\n") + .append(" [ErrStack] [varchar](8000) NOT NULL default '''',\n") + .append(" [ErrCode] [varchar](100) NOT NULL default '''',\n") + .append(" [MsgAsert] [varchar](500) NOT NULL default '''',\n") + .append(" [MsgPic] [image] NOT NULL default '',\n") + .append(" [Ws] [varchar](30) NOT NULL default '''',\n") + .append(" [ErrParam] [varchar](8000) NULL\n") + .append(" )\n") + .append(" ');\n") + .append("else \n") + .append("begin\n") + .append(" if col_length('dbo.P_ErrLogTab', 'ErrMethod') is null \n") + .append(" exec('alter table dbo.P_ErrLogTab add ErrMethod varchar(100);');\n") + .append(" if col_length('dbo.P_ErrLogTab', 'ErrStack') is null \n") + .append(" exec('alter table dbo.P_ErrLogTab add ErrStack varchar(8000);');\n") + .append(" if col_length('dbo.P_ErrLogTab', 'ErrCode') is null \n") + .append(" exec('alter table dbo.P_ErrLogTab add ErrCode varchar(100);');\n") + .append("end"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 创建微信用户绑定客户表 + */ + private void updateWxBindCusTab() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[p_WxUserBindCustomer]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)\n") + .append(" exec('CREATE TABLE [dbo].[p_WxUserBindCustomer](\n") + .append(" [id] [int] PRIMARY KEY IDENTITY(1,1) NOT NULL,\n") + .append(" [openId] varchar(50) NOT NULL,\n") + .append(" [Coid] varchar(50) NOT NULL,\n") + .append(" [CreateTime] [datetime] not null default getdate()\n") + .append(" )\n") + .append(" ');"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 更新弹出菜单表结构 + */ + private void updatePopupMenu() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_systempopupmenu', 'dbclickevent') is null \n") + .append(" exec('alter table dbo.p_systempopupmenu add dbclickevent int;')\n") + .append("if col_length('dbo.p_systempopupmenu', 'showtoolbar') is null \n") + .append(" exec('alter table dbo.p_systempopupmenu add showtoolbar int;');"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 2021-05-07 更新弹出菜单表结构 + */ + private void updatePopupMenu_210507() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_systempopupmenu', 'showMode') is null \n") + .append(" exec('alter table dbo.p_systempopupmenu add showMode int;');"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 2022-07-20 更新弹出菜单表结构(右键合并执行) + */ + private void updatePopupMenu_220720() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_systempopupmenu', 'mergeExec') is null \n") + .append(" exec('alter table dbo.p_systempopupmenu add mergeExec int;');"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 2022-08-12 更新弹出菜单表结构 + */ + private void updatePopupMenu_220812() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_systempopupmenu', 'isCopy') is null \n") + .append(" exec('alter table dbo.p_systempopupmenu add isCopy int;');"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 2022-09-29 更新弹出菜单表结构(升级右键功能) + */ + private void updatePopupMenu_220929() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_systempopupmenu', 'beforeTab') is null \n") + .append(" exec('alter table dbo.p_systempopupmenu add beforeTab varchar(100);');"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 2022-11-23 更新弹出菜单表结构(右键是否需要行数据) + */ + private void updatePopupMenu_221123() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_systempopupmenu', 'isStartRun') is null \n") + .append(" exec('alter table dbo.p_systempopupmenu add isStartRun int;');"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 更新审核附件表结构 + */ + private void updateAuditAttach() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_systembillauditAttach', 'unionModule') is null \n") + .append(" exec('alter table dbo.p_systembillauditAttach add unionModule varchar(100);')\n") + .append("if col_length('dbo.p_systembillauditAttach', 'unionParentField') is null \n") + .append(" exec('alter table dbo.p_systembillauditAttach add unionParentField varchar(100);')\n") + .append("if col_length('dbo.p_systembillauditAttach', 'unionValue') is null \n") + .append(" exec('alter table dbo.p_systembillauditAttach add unionValue varchar(100);')\n") + .append("if col_length('dbo.p_systembillauditAttach', 'displayMode') is null \n") + .append(" exec('alter table dbo.p_systembillauditAttach add displayMode int;');"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 2021-09-18 更新审核附件表结构 + */ + private void updateAuditAttach_20210918() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_systembillauditAttach', 'unionCond') is null \n") + .append(" exec('alter table dbo.p_systembillauditAttach add unionCond varchar(2000);');"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 2021-09-18 更新DLL附件表结构 + */ + private void updateDlltabAttach_20210918() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_systemdlltabattach', 'unionCond') is null \n") + .append(" exec('alter table dbo.p_systemdlltabattach add unionCond varchar(2000);');"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 2024-11-04 更新DLL附件表结构 + */ + private void updateDlltabAttach_20241104() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_systemdlltabattach', 'library') is null \n") + .append(" exec('alter table dbo.p_systemdlltabattach add library varchar(200);')\n") + .append("if col_length('dbo.p_systemdlltabattach', 'params') is null \n") + .append(" exec('alter table dbo.p_systemdlltabattach add params varchar(8000);');"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 2021-09-27 更新图表配置表结构 + */ + private void updateSystemdlltabChart_20210927() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_systemdlltabchart', 'IsAbsolutely') is null \n") + .append(" exec('alter table dbo.p_systemdlltabchart add IsAbsolutely int;')\n") + .append("if col_length('dbo.p_systemdlltabchart', 'YScale') is null \n") + .append(" exec('alter table dbo.p_systemdlltabchart add YScale decimal(18, 8);');"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 2022-02-08 更新图表配置表结构 + */ + private void updateSystemdlltabChart_20220208() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_systemdlltabchart', 'yvaluefield1') is null \n") + .append(" exec('alter table dbo.p_systemdlltabchart add yvaluefield1 varchar(50);')\n") + .append("if col_length('dbo.p_systemdlltabchart', 'yvaluefield2') is null \n") + .append(" exec('alter table dbo.p_systemdlltabchart add yvaluefield2 varchar(50);');"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 2024-08-20 更新图表配置表结构 + */ + private void updateSystemdlltabChart_20240820() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_systemdlltabchart', 'valueVisible') is null \n") + .append(" exec('alter table dbo.p_systemdlltabchart add valueVisible int default 0;');"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + private void updateSystemdlltabChart_20241225() { + StringBuilder sqlBuilder = new StringBuilder(); + // xlabel 旋转角度 + sqlBuilder.append(String.format("if col_length('%s', 'labelangle') is null " + + "exec('alter table %s add labelangle int default 0;')", + "dbo.p_systemdlltabchart", "dbo.p_systemdlltabchart")); + // xlabel 是否显示 + sqlBuilder.append(String.format("if col_length('%s', 'labelvisible') is null " + + "exec('alter table %s add labelvisible int;')", + "dbo.p_systemdlltabchart", "dbo.p_systemdlltabchart")); + // xlabel 字体大小 + sqlBuilder.append(String.format("if col_length('%s', 'labelsize') is null " + + "exec('alter table %s add labelsize int;')", + "dbo.p_systemdlltabchart", "dbo.p_systemdlltabchart")); + + // legend显示与否 + sqlBuilder.append(String.format("if col_length('%s', 'legendvisible') is null " + + "exec('alter table %s add legendvisible int;')", + "dbo.p_systemdlltabchart", "dbo.p_systemdlltabchart")); + // x轴是否间隔显示 + sqlBuilder.append(String.format("if col_length('%s', 'labelSpaced') is null " + + "exec('alter table %s add labelSpaced int;')", + "dbo.p_systemdlltabchart", "dbo.p_systemdlltabchart")); + + // 饼图锯齿 + sqlBuilder.append(String.format("if col_length('%s', 'circlejagge') is null " + + "exec('alter table %s add circlejagge int;')", + "dbo.p_systemdlltabchart", "dbo.p_systemdlltabchart")); + + // 饼图空心 1-100 + sqlBuilder.append(String.format("if col_length('%s', 'circlehollow') is null " + + "exec('alter table %s add circlehollow int;')", + "dbo.p_systemdlltabchart", "dbo.p_systemdlltabchart")); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + private void updateSysMenuTab_250120() { + StringBuilder sqlBuilder = new StringBuilder(); + + // p_formmenuconfigtab + sqlBuilder.append("if col_length('dbo.p_formmenuconfigtab', 'serverId') is null " + + "exec('alter table dbo.P_FormMenuConfigTab add serverId int'); "); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + private void updateFiletab_250120() { + // 向filetab 添加字段 tottime + String sql = "if col_length('dbo.p_fm_filetab', 'tottime') is null " + + "exec('alter table dbo.p_fm_filetab add tottime varchar(50);')"; + jdbcTemplate.execute(sql); + } + + private void updateLogTab_20211027() { + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append("IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[DF_P_LogTab_Content]') AND type = 'D') " + + "BEGIN " + + "ALTER TABLE [dbo].[P_LogTab] DROP CONSTRAINT [DF_P_LogTab_Content] " + + "END " + + "alter table dbo.P_LogTab alter column content varchar(max); " + + "ALTER TABLE [dbo].[P_LogTab] ADD CONSTRAINT [DF_P_LogTab_Content] DEFAULT ('') FOR [Content]; " + + "alter table dbo.P_LogTab alter column object varchar(5000);"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + private void updateEmployeetab_20210928() { + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append(String.format("if col_length('%s', 'p_emp_clientid') is null " + + "exec('alter table %s add p_emp_clientid int;') " + + "if col_length('%s', 'p_emp_logintype') is null " + + "exec('alter table %s add p_emp_logintype int;')", + "dbo.p_employeetab", "dbo.p_employeetab", + "dbo.p_employeetab", "dbo.p_employeetab")); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + private void updateEmployeetab_20221207() { + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append(String.format("if col_length('%s', 'p_emp_phone') is null " + + "exec('alter table %s add p_emp_phone varchar(100);')", + "dbo.p_employeetab", "dbo.p_employeetab")); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + private void updateDllTab_202111122() { + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'newVer') is null " + + "exec('alter table dbo.p_systemdlltab add newVer int;')"); + + sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'newWFVer') is null " + + "exec('alter table dbo.p_systemdlltab add newWFVer int;')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + private void updateDllTab_20211129() { + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'detailPageAlign') is null " + + "exec('alter table dbo.p_systemdlltab add detailPageAlign int default 0')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 表格编辑方式,行编辑还是单元格编辑 + */ + private void updateDllTab_20220309() { + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'editType') is null " + + "exec('alter table dbo.p_systemdlltab add editType int default 0')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 是否启用单元格模板 + */ + private void updateDllTab_20220414() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'cellTplflag') is null " + + "exec('alter table dbo.p_systemdlltab add cellTplflag int default 0')"); + jdbcTemplate.execute(sqlBuilder.toString()); + } + + private void updateDllTab_20220524() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'attatchModifyCond') is null " + + "exec('alter table dbo.p_systemdlltab add attatchModifyCond varchar(1000)')"); + sqlBuilder.append("if col_length('dbo.p_systembilltype', 'attatchModifyCond') is null " + + "exec('alter table dbo.p_systembilltype add attatchModifyCond varchar(1000)')"); + jdbcTemplate.execute(sqlBuilder.toString()); + } + + private void updateEmployeetab_20220617() { + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append(String.format("if col_length('%s', 'p_emp_PwdErrNum') is null " + + "exec('alter table %s add p_emp_PwdErrNum int;') " + + "if col_length('%s', 'p_emp_PwdLocked') is null " + + "exec('alter table %s add p_emp_PwdLocked bit;') " + + "if col_length('%s', 'p_emp_PwdLockDate') is null " + + "exec('alter table %s add p_emp_PwdLockDate datetime;')", + "dbo.p_employeetab", "dbo.p_employeetab", + "dbo.p_employeetab", "dbo.p_employeetab", + "dbo.p_employeetab", "dbo.p_employeetab")); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + private void updateDllTab_20211129a() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_SystemdllTabAttach', 'attachType') is null " + + "exec('alter table dbo.p_SystemdllTabAttach add attachType varchar(200)')"); + sqlBuilder.append("if col_length('dbo.p_SystemdllTabAttach', 'attachIMG') is null " + + "exec('alter table dbo.p_SystemdllTabAttach add attachIMG varchar(200)')"); + jdbcTemplate.execute(sqlBuilder.toString()); + } + + private void updateWorkbooktab_20230206() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_systemwordbooktab', 'IsAddControl') is null " + + "exec('alter table dbo.p_systemwordbooktab add IsAddControl int')"); + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 文件上传类型控制 附件与字段 例 *.pdf,*.mp4 。。。。 + */ + private void updateBmpType_20240913() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_systemwordbooktab', 'bmptype') is null " + + "exec('alter table dbo.p_systemwordbooktab add bmptype varchar(100)')"); + sqlBuilder.append("if col_length('dbo.p_systembilltype', 'bmptype') is null " + + "exec('alter table dbo.p_systembilltype add bmptype varchar(100)')"); + sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'bmptype') is null " + + "exec('alter table dbo.p_systemdlltab add bmptype varchar(100)')"); + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 禁用手机卡片模式 + */ + private void updateDllTab_20230209() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'DisMobileCard') is null " + + "exec('alter table dbo.p_systemdlltab add DisMobileCard int default 0')"); + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 附件新增版本名称和版本号 + */ + private void updateFileTab20230224() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.P_fm_FileTab', 'vname') is null " + + "exec('alter table dbo.P_fm_FileTab add vname varchar(100);');"); + // 注释掉的原代码保持不变 + // sqlBuilder.append("if col_length('P_fm_FileTab', 'vercode') is null " + + // "exec('alter table P_fm_FileTab add vercode varchar(100)');"); + jdbcTemplate.execute(sqlBuilder.toString()); + } + + public void updatebSysTab20230224() { + StringBuilder sqlBuilder = new StringBuilder(); + // 1:开启设备绑定 + sqlBuilder.append(String.format("if col_length('%s', 'MobileDevOnly') is null " + + "exec('alter table %s add MobileDevOnly int;')", + "dbo.p_systemtab", "dbo.p_systemtab")); + // 1:允许多设备 + sqlBuilder.append(String.format("if col_length('%s', 'MultMobileDev') is null " + + "exec('alter table %s add MultMobileDev int;')", + "dbo.p_systemtab", "dbo.p_systemtab")); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 升级桌面快捷方式数据源各控件独立 + */ + private void updateMegToolLiknkTab_20220914() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.P_MessageToolLinkDllTab', 'cardId') is null " + + "begin " + + "exec('alter table dbo.P_MessageToolLinkDllTab add cardId int') " + + "exec('update m set m.cardId=b.id from dbo.P_MessageToolLinkDllTab m " + + "inner join [dbo].[P_SystemOperFirstPageTab] b on m.EmployeeID=b.operatorId " + + "inner join [dbo].[P_SystemFirstPageSetTab] c on b.itemCode=c.itemCode and c.itemTypeFull=''0103'' " + + "where ISNULL(m.cardid,'')='' ') " + + "end"); + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 新版登录页配置 20211118 + */ + private void updateLoginCfg() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append(String.format("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[%s]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) " + + "exec('CREATE TABLE [dbo].[%s]( " + + "[id] [int] PRIMARY KEY IDENTITY(1,1) NOT NULL, " + + "[SysName] varchar(50) NOT NULL, " + + "[Copyright] varchar(100) NOT NULL, " + + "[Logo] varchar(500) NOT NULL, " + + "[disabled] int not null default 0 " + + ") '); ", + "dbo.p_systemLoginCfgTab", "dbo.p_systemLoginCfgTab")); + + sqlBuilder.append(String.format("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[%s]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) " + + "exec('CREATE TABLE [dbo].[%s]( " + + "[id] [int] PRIMARY KEY IDENTITY(1,1) NOT NULL, " + + "[sysId] [int] not null, " + + "[Title] varchar(50) NOT NULL, " + + "[Desp] varchar(5000) NOT NULL, " + + "[ImgSrc] varchar(500) NOT NULL, " + + "[disabled] int not null default 0 " + + ") '); ", + "dbo.p_systemLoginBanner", "dbo.p_systemLoginBanner")); + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 下拉框禁用拼音查询,影响速度 + */ + private void updateFieldTab_20230314() { + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append(String.format("if col_length('%s', 'doNotSpelling') is null " + + "exec('alter table %s add doNotSpelling int default 0;')", + "dbo.p_systemwordbooktab", "dbo.p_systemwordbooktab")); + sqlBuilder.append(String.format("if col_length('%s', 'doNotSpelling') is null " + + "exec('alter table %s add doNotSpelling int default 0;')", + "dbo.p_systembillinfo", "dbo.p_systembillinfo")); + sqlBuilder.append(String.format("if col_length('%s', 'doNotSpelling') is null " + + "exec('alter table %s add doNotSpelling int default 0;')", + "dbo.p_systembilldetail", "dbo.p_systembilldetail")); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * + */ + private void updateFieldTab_20240904() { + StringBuilder sqlBuilder = new StringBuilder(); + + sqlBuilder.append(String.format("if col_length('%s', 'dataAlignment') is null " + + "exec('alter table %s add dataAlignment int default 0;')", + "dbo.p_systembilldetail", "dbo.p_systembilldetail")); + sqlBuilder.append(String.format("if col_length('%s', 'fontName') is null " + + "exec('alter table %s add fontName varchar(100);')", + "dbo.p_systembilldetail", "dbo.p_systembilldetail")); + sqlBuilder.append(String.format("if col_length('%s', 'labelAlign') is null " + + "exec('alter table %s add labelAlign int default 0;')", + "dbo.p_systembilldetail", "dbo.p_systembilldetail")); + // 兼容设置 + sqlBuilder.append(String.format("if col_length('%s', 'dataAlign') is null " + + "exec('alter table %s add dataAlign int default 0;')", + "dbo.p_systembilldetail", "dbo.p_systembilldetail")); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 新增根据用户设置访问不同的首页 + */ + private void updateEmployeeTab_20240909() { + StringBuilder sqlBuilder = new StringBuilder(); + + // 兼容设置(注释掉的原代码保持不变) + // sqlBuilder.append(String.format("if col_length('%s', 'WebIndex') is null " + + // "exec('alter table %s add WebIndex varchar(100);')", + // "p_employeetab", "p_employeetab")); + sqlBuilder.append(String.format("if col_length('%s', 'AppIndex') is null " + + "exec('alter table %s add AppIndex varchar(100);')", + "dbo.p_employeetab", "dbo.p_employeetab")); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 下拉框禁用拼音查询,影响速度 + */ + private void updateSysteTab_20230328() { + StringBuilder sqlBuilder = new StringBuilder(); + String tableName = "dbo.p_systemtab"; + + sqlBuilder.append(String.format( + "if col_length('%s', 'ADLogin') is null exec('alter table %s add ADLogin int default 0;')%n", + tableName, tableName + )); + sqlBuilder.append(String.format( + "if col_length('%s', 'ADPath') is null exec('alter table %s add ADPath varchar(100);')%n", + tableName, tableName + )); + sqlBuilder.append(String.format( + "if col_length('%s', 'ADUser') is null exec('alter table %s add ADUser varchar(100);')%n", + tableName, tableName + )); + sqlBuilder.append(String.format( + "if col_length('%s', 'ADPwd') is null exec('alter table %s add ADPwd varchar(100);')%n", + tableName, tableName + )); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 下拉框禁用拼音查询,影响速度 + */ + private void updateSysteTab_20240625() { + StringBuilder sqlBuilder = new StringBuilder(); + String tableName = "dbo.p_systemtab"; + + sqlBuilder.append(String.format( + "if col_length('%s', 'emaurl') is null exec('alter table %s add emaurl varchar(1000);')%n", + tableName, tableName + )); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 下拉框禁用拼音查询,影响速度 + */ + private void updateDllTab_20230406() { + StringBuilder sqlBuilder = new StringBuilder(); + String tableName = "dbo.p_systemdlltab"; + + sqlBuilder.append(String.format( + "if col_length('%s', 'bmpSpecIsMJ') is null exec('alter table %s add bmpSpecIsMJ int default 0;')%n", + tableName, tableName + )); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 新版web调用cs打印 打印时参数表 2022-02-21 + */ + private void updatePrintTab() { + StringBuilder sqlBuilder = new StringBuilder(); + String tableName = "dbo.p_systemWebPrintTab"; + + sqlBuilder.append(String.format( + "if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[%s]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)\n" + + "exec('CREATE TABLE [dbo].[%s](\n" + + " [id] [int] PRIMARY KEY IDENTITY(1,1) NOT NULL,\n" + + " [operatorId] varchar(100),\n" + + " [operatorName] varchar(100),\n" + + " [dllcoId] varchar(100) NULL,\n" + + " [billdocumentId] varchar(100) NULL,\n" + + " [contextMenuId] int NULL,\n" + + " [printName] varchar(100) NULL,\n" + + " [printSta] int default 0,\n" + + " [sql1] varchar(8000) NULL,\n" + + " [sql2] varchar(8000) NULL,\n" + + " [sql3] varchar(8000) NULL,\n" + + " [sql4] varchar(8000) NULL\n" + + ")')", + tableName, tableName + )); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 新版web调用cs打印 打印次数统计 2025-05-07 + */ + private void updatePrintTab250507() { + StringBuilder sqlBuilder = new StringBuilder(); + String tableName = "dbo.p_systemWebPrintTab"; + + sqlBuilder.append(String.format( + "if col_length('%s', 'printSta') is null exec('alter table %s add printSta int default 0;')%n", + tableName, tableName + )); + sqlBuilder.append(String.format( + "if col_length('%s', 'billdocumentId') is null exec('alter table %s add billdocumentId varchar(100);')%n", + tableName, tableName + )); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 程序日志表 + */ + private void updateWebProLogTab() { + StringBuilder sqlBuilder = new StringBuilder(); + String tableName = "dbo.p_sysWebProLogTab"; + + sqlBuilder.append(String.format( + "if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[%s]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)\n" + + "exec('CREATE TABLE [dbo].[%s](\n" + + " [id] [int] PRIMARY KEY IDENTITY(1,1) NOT NULL,\n" + + " [level] varchar(10),\n" + + " [className] varchar(100),\n" + + " [operatorId] varchar(100),\n" + + " [operatorName] varchar(100),\n" + + " [dllcoId] varchar(100) NULL,\n" + + " [passTime] decimal(18,2) NULL,\n" + + " [method] varchar(100) NULL,\n" + + " [args] text NULL,\n" + + " [msg] text NULL,\n" + + " [happenTime] datetime not NULL default getdate()\n" + + ")')", + tableName, tableName + )); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + private void updateEmployeeTab_20230408() { + StringBuilder sqlBuilder = new StringBuilder(); + String tableName = "dbo.p_employeetab"; + + sqlBuilder.append(String.format( + "if col_length('%s', 'GTClientId') is null exec('alter table %s add GTClientId varchar(100);')%n", + tableName, tableName + )); + sqlBuilder.append(String.format( + "if col_length('%s', 'CTClientInfo') is null exec('alter table %s add CTClientInfo varchar(8000);')%n", + tableName, tableName + )); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + private void updateEmployeeTab_20230412() { + StringBuilder sqlBuilder = new StringBuilder(); + String tableName = "dbo.p_employeetab"; + + sqlBuilder.append(String.format( + "if col_length('%s', 'AppVer') is null exec('alter table %s add AppVer varchar(100);')%n", + tableName, tableName + )); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + private void updateEmployeeTab_20230413() { + StringBuilder sqlBuilder = new StringBuilder(); + String tableName = "dbo.p_employeetab"; + + sqlBuilder.append(String.format( + "if col_length('%s', 'OsLastLoginDate') is null exec('alter table %s add OsLastLoginDate datetime;')%n", + tableName, tableName + )); + sqlBuilder.append(String.format( + "if col_length('%s', 'LoginOsModel') is null exec('alter table %s add LoginOsModel varchar(100);')%n", + tableName, tableName + )); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + private void updateEmployeeTab_20230520() { + StringBuilder sqlBuilder = new StringBuilder(); + String tableName = "dbo.p_employeetab"; + + sqlBuilder.append(String.format( + "if col_length('%s', 'LoginOsID') is null exec('alter table %s add LoginOsID varchar(100);')%n", + tableName, tableName + )); + sqlBuilder.append(String.format( + "if col_length('%s', 'FieldServiceType') is null exec('alter table %s add FieldServiceType int default 1;')%n", + tableName, tableName + )); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 增加表格文字停靠样式,表格线配置,表格编号配置 + */ + private void update_20230522() { + StringBuilder sqlBuilder = new StringBuilder(); + + // 处理p_systemwordbooktab表 + String table1 = "dbo.p_systemwordbooktab"; + sqlBuilder.append(String.format( + "if col_length('%s', 'dataAlign') is null exec('alter table %s add dataAlign varchar(100);')%n", + table1, table1 + )); + sqlBuilder.append(String.format( + "if col_length('%s', 'frozenFlag') is null exec('alter table %s add frozenFlag int;')%n", + table1, table1 + )); + + // 处理p_systemDlltabDetailGrid表 + String table2 = "p_systemDlltabDetailGrid"; + sqlBuilder.append(String.format( + "if col_length('%s', 'dataAlign') is null exec('alter table %s add dataAlign varchar(100);')%n", + table2, table2 + )); + sqlBuilder.append(String.format( + "if col_length('%s', 'frozenFlag') is null exec('alter table %s add frozenFlag int;')%n", + table2, table2 + )); + + // 处理p_systemdlltab表 + String table3 = "dbo.p_systemdlltab"; + sqlBuilder.append(String.format( + "if col_length('%s', 'noGridLine') is null exec('alter table %s add noGridLine int default 0;')%n", + table3, table3 + )); + sqlBuilder.append(String.format( + "if col_length('%s', 'noRownumber') is null exec('alter table %s add noRownumber int default 0;')%n", + table3, table3 + )); + sqlBuilder.append(String.format( + "if col_length('%s', 'noColumnHeader') is null exec('alter table %s add noColumnHeader int default 0;')%n", + table3, table3 + )); + + // 处理p_systemdlltabdetail表 + String table4 = "dbo.p_systemdlltabdetail"; + sqlBuilder.append(String.format( + "if col_length('%s', 'noGridLine') is null exec('alter table %s add noGridLine int default 0;')%n", + table4, table4 + )); + sqlBuilder.append(String.format( + "if col_length('%s', 'noRownumber') is null exec('alter table %s add noRownumber int default 0;')%n", + table4, table4 + )); + sqlBuilder.append(String.format( + "if col_length('%s', 'noColumnHeader') is null exec('alter table %s add noColumnHeader int default 0;')%n", + table4, table4 + )); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + public void updateComment_230612() { + StringBuilder sqlBuilder = new StringBuilder(); + String table1 = "dbo.P_SystemCheckCommentTab"; + + // 创建P_SystemCheckCommentTab表 + sqlBuilder.append(String.format( + "if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[%s]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)\n" + + "exec('CREATE TABLE [dbo].[%s](\n" + + " [id] [bigint] PRIMARY KEY IDENTITY(1,1) NOT NULL,\n" + + " [moduleid] varchar(100) null, --模块编号\n" + + " [keyValue] varchar(500) null, --主键值\n" + + " [stepCode] varchar(100) null, --步骤码\n" + + " [atts] varchar(8000) null,--附件\n" + + " [pdataguid] [varchar](40) NULL,--来源标识ID\n" + + " [operatorid] [int] NULL,--操作人员\n" + + " [operatorname] [varchar](20) NULL,--操作人员名称\n" + + " [commenttype] [int] NULL, --类别,1=点赞,2=评论\n" + + " [comment_text] [varchar](2000) NULL, --评论内容\n" + + " [operatedate] [datetime] NULL, --发生时间\n" + + " [c_address] [varchar](200) NULL, --发生定点定位\n" + + " [c_address_itude] [varchar](100) NULL, --地点经纬度\n" + + " [c_address_longitude] [varchar](50) NULL,--经度\n" + + " [c_address_latitude] [varchar](50) NULL--纬度\n" + + ")')", + table1, table1 + )); + + // 创建crm_ProjectPlanLogTab表 + String table2 = "dbo.crm_ProjectPlanLogTab"; + sqlBuilder.append("\n"); + sqlBuilder.append(String.format( + "if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[%s]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)\n" + + "exec('CREATE TABLE [dbo].[%s](\n" + + " [id] [int] IDENTITY(1,1) PRIMARY key NOT NULL,\n" + + " [RunningNumber] [varchar](40) NULL,\n" + + " [PRunningNumber] [varchar](40) NULL,\n" + + " [planTitle] [varchar](5000) NULL,\n" + + " [planTime] [decimal](18, 1) NULL,\n" + + " [planOper] [varchar](50) NULL,\n" + + " [PlanDate] [datetime] NULL,\n" + + " [Remark] [varchar](5000) NULL,\n" + + " [FinishStatus] [int] NULL default 0,\n" + + " [FinishBak] [varchar](5000) NULL,\n" + + " [SelfAssessment] [varchar](5000) NULL,\n" + + " [ManagerAssessment] [varchar](5000) NULL,\n" + + " [OperateDate] [datetime] NULL,\n" + + " [OperatorName] [varchar](20) NULL,\n" + + " [stepover] [int] NULL,\n" + + " [stepovertime] [datetime] NULL,\n" + + " [stepovername] [varchar](50) NULL,\n" + + " [isSubsidies] [int] NULL default 0,\n" + + " [scoreValue] [decimal](18, 2) NULL default 0,\n" + + " [yearid] [int] NULL,\n" + + " [monthid] [int] NULL,\n" + + " [weekid] [int] NULL,\n" + + " [weekname] [varchar](50) NULL,\n" + + " [ProjectNo] [varchar](20) NULL,\n" + + " [StageNo] [varchar](20) NULL,\n" + + " [StageName] [varchar](200) NULL,\n" + + " [PlanNo] [varchar](20) NULL,\n" + + " [CustomId] [varchar](50) NULL,\n" + + " [PlanDaymid] [int] NULL,\n" + + " [TopLogID] [int] NULL,\n" + + " [NextRemark] [varchar](1000) NULL,\n" + + " [insert_linktab] [varchar](100) NULL,\n" + + " [insert_wcsqtab] [varchar](100) NULL,\n" + + " [crm_hr_str3] [varchar](1000) NULL,\n" + + " [crm_hr_str3_latitude] [varchar](1000) NULL,\n" + + " [crm_hr_str3_longitude] [varchar](1000) NULL,\n" + + " [ZlogBillID] [varchar](1000) NULL,\n" + + " [work_mode] [varchar](100) NULL,\n" + + " [GroupName] [varchar](100) NULL,\n" + + " [LtoNo] [varchar](100) NULL,\n" + + " [crm_hr_str3_itude] [varchar](100) NULL,\n" + + " [completetime] [datetime] NULL,\n" + + " [entryname] [varchar](500) NULL,\n" + + " [str1] [varchar](1000) NULL,\n" + + " [str2] [varchar](1000) NULL,\n" + + " [str3] [varchar](1000) NULL,\n" + + " [str4] [varchar](1000) NULL,\n" + + " [str5] [varchar](1000) NULL,\n" + + " [billno] [varchar](20) NULL,\n" + + " [name] [varchar](200) NULL default '''',\n" + + " [sname] [varchar](500) NULL default '''',\n" + + " [colorid] [int] NULL default 0,\n" + + " [sign] [int] NULL default 0,\n" + + " [delid] [int] NULL default 0,\n" + + " [operatorid] [int] NULL default 0,\n" + + " [Affirmer] [int] NULL,\n" + + " [AffirmDate] [datetime] NULL,\n" + + " [ban] [int] NULL,\n" + + " [stepcode] [int] NULL,\n" + + " [billtype] [int] NULL,\n" + + " [happentime] [datetime] NULL,\n" + + " [endtime] [datetime] NULL,\n" + + " [stepdirection] [char](1) NULL,\n" + + " [stepoverid] [int] NULL,\n" + + " [Rtagid] [int] NULL,\n" + + " [stepTime] [decimal](10, 2) NULL,\n" + + " [cancelFlag] [int] NULL,\n" + + " [pStepCode] [varchar](100) NULL,\n" + + " [cancelTime] [datetime] NULL,\n" + + " [cancelOper] [int] NULL,\n" + + " [lskjimport_errorFlag] [varchar](20) NULL default ''0'',\n" + + " [planOperId] [varchar](50) NULL,\n" + + " atts varchar(8000) null,--附件\n" + + " rztype int default 0, --日志类型,0=日志,1=计划,2=议题\n" + + " qd_address varchar(200),--签到地址(定位)\n" + + " qd_address_itude varchar(20),--签到经纬度\n" + + " qd_address_longitude varchar(20),--签到经度\n" + + " qd_address_latitude varchar(20),--签到纬度\n" + + " qd_time datetime,--签到时间\n" + + " qt_address varchar(200),--签退地址(定位)\n" + + " qt_time datetime,--签退时间\n" + + " qt_address_itude varchar(20),--签退经纬度\n" + + " qt_address_longitude varchar(20),--签退经度\n" + + " qt_address_latitude varchar(20),--签退纬度\n" + + " share_users varchar(2000),--分享人员,多人逗号分割\n" + + " dataguid varchar(40) default newid(),--数据识别GUID,可用于关联到考勤、附件或者点赞评论等\n" + + " nextdt datetime --下次提醒时间\n" + + ")')", + table2, table2 + )); + + // 执行第一条SQL + jdbcTemplate.execute(sqlBuilder.toString()); + + // 清空builder,准备第二条SQL + sqlBuilder.setLength(0); + sqlBuilder.append(String.format( + "if col_length('%s', 'rztype') is null exec('\n" + + " alter table %s add rztype int, --日志类型,0=日志,1=计划,2=议题\n" + + " planOperId varchar(50),\n" + + " atts varchar(8000) null,--附件\n" + + " qd_address varchar(200),--签到地址(定位)\n" + + " qd_address_itude varchar(20),--签到经纬度\n" + + " qd_address_longitude varchar(20),--签到经度\n" + + " qd_address_latitude varchar(20),--签到纬度\n" + + " qd_time datetime,--签到时间\n" + + " qt_address varchar(200),--签退地址(定位)\n" + + " qt_time datetime,--签退时间\n" + + " qt_address_itude varchar(20),--签退经纬度\n" + + " qt_address_longitude varchar(20),--签退经度\n" + + " qt_address_latitude varchar(20),--签退纬度\n" + + " share_users varchar(2000),--分享人员,多人逗号分割\n" + + " dataguid varchar(40) default newid()--数据识别GUID,可用于关联到考勤、附件或者点赞评论等\n" + + "')", + table2, table2 + )); + + jdbcTemplate.execute(sqlBuilder.toString()); + + // 清空builder,准备第三条SQL + sqlBuilder.setLength(0); + String table3 = "dbo.Hr_daily_recordlistTab"; + sqlBuilder.append(String.format( + "if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[%s]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)\n" + + "exec('CREATE TABLE [dbo].[%s](\n" + + " rztype int, --日志类型,0=日志,1=计划,2=议题\n" + + " planOperId varchar(50),\n" + + " sourceModid varchar(20),--来源模块\n" + + " sourceKeyValue varchar(50),--来源主键\n" + + " atts varchar(8000) null,--附件\n" + + " qd_address varchar(200),--签到地址(定位)\n" + + " qd_address_itude varchar(20),--签到经纬度\n" + + " qd_address_longitude varchar(20),--签到经度\n" + + " qd_address_latitude varchar(20),--签到纬度\n" + + " qd_time datetime,--签到时间\n" + + " qt_address varchar(200),--签退地址(定位)\n" + + " qt_time datetime,--签退时间\n" + + " qt_address_itude varchar(20),--签退经纬度\n" + + " qt_address_longitude varchar(20),--签退经度\n" + + " qt_address_latitude varchar(20),--签退纬度\n" + + " share_users varchar(2000),--分享人员,多人逗号分割\n" + + " dataguid varchar(40) default newid()--数据识别GUID,可用于关联到考勤、附件或者点赞评论等\n" + + ")')\n" + + "else if col_length('%s', 'rztype') is null exec('\n" + + " alter table %s add rztype int, --日志类型,0=日志,1=计划,2=议题\n" + + " planOperId varchar(50),\n" + + " sourceModid varchar(20),--来源模块\n" + + " sourceKeyValue varchar(50),--来源主键\n" + + " atts varchar(8000) null,--附件\n" + + " qd_address varchar(200),--签到地址(定位)\n" + + " qd_address_itude varchar(20),--签到经纬度\n" + + " qd_address_longitude varchar(20),--签到经度\n" + + " qd_address_latitude varchar(20),--签到纬度\n" + + " qd_time datetime,--签到时间\n" + + " qt_address varchar(200),--签退地址(定位)\n" + + " qt_time datetime,--签退时间\n" + + " qt_address_itude varchar(20),--签退经纬度\n" + + " qt_address_longitude varchar(20),--签退经度\n" + + " qt_address_latitude varchar(20),--签退纬度\n" + + " share_users varchar(2000),--分享人员,多人逗号分割\n" + + " dataguid varchar(40) default newid()--数据识别GUID,可用于关联到考勤、附件或者点赞评论等\n" + + "')", + table3, table3, table3, table3 + )); + + // 添加头像字段 + sqlBuilder.append("\n"); + String table4 = "dbo.p_employeetab"; + sqlBuilder.append(String.format( + "if col_length('%s', 'webbmp') is null exec('alter table %s add webbmp varchar(1000)')", + table4, table4 + )); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 增加表格文字停靠样式,表格线配置,表格编号配置 + */ + private void update_20230704() { + StringBuilder sqlBuilder = new StringBuilder(); + String tableName = "dbo.p_systemdlltab"; + + sqlBuilder.append(String.format( + "if col_length('%s', 'closeAfterModify') is null exec('alter table %s add closeAfterModify int;')%n", + tableName, tableName + )); + sqlBuilder.append(String.format( + "if col_length('%s', 'closeAfterAdd') is null exec('alter table %s add closeAfterAdd int;')%n", + tableName, tableName + )); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 扫码相关字段更新 (202307011) + */ + private void update_202307011() { + StringBuilder sqlBuilder = new StringBuilder(); + // scanMode 扫码弹窗 1弹窗 barSplitChar 扫码分隔符 barSplitFields 扫码分割的字段 + sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'scanMode') is null \n") + .append(" exec('alter table dbo.p_systemdlltab add scanMode int;')\n") + .append("if col_length('dbo.p_systemdlltab', 'verifyCancel') is null \n") + .append(" exec('alter table dbo.p_systemdlltab add verifyCancel int;')\n") + .append("if col_length('dbo.p_systemdlltab', 'barSplitChar') is null \n") + .append(" exec('alter table dbo.p_systemdlltab add barSplitChar varchar(100);')\n") + .append("if col_length('dbo.p_systemdlltab', 'barSplitFields') is null \n") + .append(" exec('alter table dbo.p_systemdlltab add barSplitFields varchar(1000);')\n") + .append("if col_length('dbo.p_systemdlltab', 'appAutoSave') is null \n") + .append(" exec('alter table dbo.p_systemdlltab add appAutoSave int;')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 添加分组表相关字段 + */ + private void updateAddGroupTab() { + StringBuilder sqlBuilder = new StringBuilder(); + // 添加界面,分组框打开时,是否折叠 空或者1为展开 + sqlBuilder.append("if col_length('dbo.p_systemaddgroup', 'isexpand') is null \n") + .append(" exec('alter table dbo.p_systemaddgroup add isexpand int;')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 明细分页设置更新 + */ + private void update_p_systemdlldetailtab() { + StringBuilder sqlBuilder = new StringBuilder(); + // 添加界面明细分页数设置 大屏相关 + sqlBuilder.append("if col_length('dbo.p_systemdlltabdetail', 'displayRows') is null \n") + .append(" exec('alter table dbo.p_systemdlltabdetail add displayRows int default 0;')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 下拉框宽度配置更新 (240420) + */ + private void updateLookupWidth240420() { + StringBuilder sqlBuilder = new StringBuilder(); + // 添加下拉框宽度配置 + sqlBuilder.append("if col_length('dbo.p_systemwordbooktab', 'lookupWidth') is null \n") + .append(" exec('alter table dbo.p_systemwordbooktab add lookupWidth int default 0;')\n") + .append("if col_length('dbo.p_systemwordbooktab', 'lookupFieldsWidth') is null \n") + .append(" exec('alter table dbo.p_systemwordbooktab add lookupFieldsWidth varchar(50);')\n") + .append("if col_length('dbo.p_systembillinfo', 'lookupWidth') is null \n") + .append(" exec('alter table dbo.p_systembillinfo add lookupWidth int default 0;')\n") + .append("if col_length('dbo.p_systembillinfo', 'lookupFieldsWidth') is null \n") + .append(" exec('alter table dbo.p_systembillinfo add lookupFieldsWidth varchar(50);')\n") + .append("if col_length('dbo.p_systembilldetail', 'lookupWidth') is null \n") + .append(" exec('alter table dbo.p_systembilldetail add lookupWidth int default 0;')\n") + .append("if col_length('dbo.p_systembilldetail', 'lookupFieldsWidth') is null \n") + .append(" exec('alter table dbo.p_systembilldetail add lookupFieldsWidth varchar(50);')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 字体大小配置更新 (240426) + */ + private void update_bookfontsize240426() { + StringBuilder sqlBuilder = new StringBuilder(); + // 添加下拉框宽度配置 + sqlBuilder.append("if col_length('dbo.p_systemwordbooktab', 'fontSize') is null \n") + .append(" exec('alter table dbo.p_systemwordbooktab add fontSize int;')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 字体大小配置更新 (240815) + */ + private void update_bookfontsize240815() { + StringBuilder sqlBuilder = new StringBuilder(); + // 添加下拉框宽度配置 + sqlBuilder.append("if col_length('dbo.p_systembillInfo', 'fontSize') is null \n") + .append(" exec('alter table dbo.p_systembillInfo add fontSize int;')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 弹窗菜单更新 (240428) + */ + private void update_popupmenu240428() { + StringBuilder sqlBuilder = new StringBuilder(); + // 添加下拉框宽度配置 + sqlBuilder.append("if col_length('dbo.p_systempopupmenu', 'defailtImage') is null \n") + .append(" exec('alter table dbo.p_systempopupmenu add defailtImage varchar(100);')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 弹窗菜单更新 (240522) + */ + private void update_popupmenu240522() { + StringBuilder sqlBuilder = new StringBuilder(); + // 添加下拉框宽度配置 + sqlBuilder.append("if col_length('dbo.p_systempopupmenu', 'disabletype') is null \n") + .append(" exec('alter table dbo.p_systempopupmenu add disabletype int default 0;')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 条件配置更新 (240807) + */ + private void updateCondition240807() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_systembillsourcecond', 'dataformat') is null \n") + .append(" exec('alter table dbo.p_systembillsourcecond add dataformat varchar(100);')\n") + // 启用条件 + .append("if col_length('dbo.p_systembillsourcecond', 'disableCond') is null \n") + .append(" exec('alter table dbo.p_systembillsourcecond add disableCond varchar(1000);')\n") + // 启用类型 1:显示和不显示 默认0:启用和禁用 + .append("if col_length('dbo.p_systembillsourcecond', 'disableType') is null \n") + .append(" exec('alter table dbo.p_systembillsourcecond add disableType int;')\n") + .append("if col_length('dbo.p_systembillsourcecond', 'fontsize') is null \n") + .append(" exec('alter table dbo.p_systembillsourcecond add fontsize int;')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 单据行高和对齐方式更新 (20240902) + */ + private void update_20240902() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_systembilltype', 'rowHeight') is null \n") + .append(" exec('alter table dbo.p_systembilltype add rowHeight int;')\n") + .append("if col_length('dbo.p_systembillsourcegrid', 'dataAlignment') is null \n") + .append(" exec('alter table dbo.p_systembillsourcegrid add dataAlignment int default 0;')\n") + .append("if col_length('dbo.p_systembillsourcegrid', 'labelAlign') is null \n") + .append(" exec('alter table dbo.p_systembillsourcegrid add labelAlign int default 0;')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 新BI表结构创建 (240824) + */ + private void updateNewBI_240824() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[BI_NodeTagTab]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)\n") + .append("exec('CREATE TABLE [dbo].[BI_NodeTagTab](\n") + .append(" [ID] [int] primary key IDENTITY(1,1) NOT NULL,\n") + .append(" [nodeName] varchar(150) NULL,\n") + .append(" [navigateType] [int] NULL,\n") + .append(" [operatorid] [int] NULL,\n") + .append(" [operatedate] [datetime] NULL,\n") + .append(" [enableFlag] [int] NULL default 0\n") + .append(" ); \n") + .append(" EXEC sys.sp_addextendedproperty @name=N''MS_Description'', @value=N''BI显示名称或标题'' , @level0type=N''SCHEMA'',@level0name=N''dbo'', @level1type=N''TABLE'',@level1name=N''BI_NodeTagTab'', @level2type=N''COLUMN'',@level2name=N''nodeName''; \n") + .append(" EXEC sys.sp_addextendedproperty @name=N''MS_Description'', @value=N''导航类别,0-菜单导航,1=树结构导航'' , @level0type=N''SCHEMA'',@level0name=N''dbo'', @level1type=N''TABLE'',@level1name=N''BI_NodeTagTab'', @level2type=N''COLUMN'',@level2name=N''navigateType''; \n") + .append(" EXEC sys.sp_addextendedproperty @name=N''MS_Description'', @value=N''启用标记1=启用'' , @level0type=N''SCHEMA'',@level0name=N''dbo'', @level1type=N''TABLE'',@level1name=N''BI_NodeTagTab'', @level2type=N''COLUMN'',@level2name=N''enableFlag'';')\n") + .append("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[BI_StructureTab]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)\n") + .append("exec('CREATE TABLE [dbo].[BI_StructureTab](\n") + .append(" [id] [int] primary key IDENTITY(1,1) NOT NULL,\n") + .append(" [Tagid] [int] NULL,\n") + .append(" [SpeciesNo] varchar(50) NULL,\n") + .append(" [SpeciesName] varchar(50) NULL,\n") + .append(" [enableFlag] [int] NULL default 0,\n") + .append(" [Bak] varchar(150) NULL,\n") + .append(" [Modid] varchar(50) NULL,\n") + .append(" [mainVisible] [int] NULL default 0,\n") + .append(" [imgUrl] varchar(200) NULL,\n") + .append(" [visibleDetailType] [int] NULL default 0,\n") + .append(" [mainHeight] [int] NULL,\n") + .append(" [mainWidth] [int] NULL,\n") + .append(" [defaultFlag] [int] NULL default 0,\n") + .append(" [operatorid] [int] NULL,\n") + .append(" [operatedate] [datetime] NULL,\n") + .append(" [displayModid] varchar(50) NULL,\n") + .append(" [dllfilename] varchar(50) NULL,\n") + .append(" [dllparams] varchar(1000) NULL,\n") + .append(" [Orderid] [int] NULL\n") + .append(" ); \n") + .append(" EXEC sys.sp_addextendedproperty @name=N''MS_Description'', @value=N''BI标识,通过访问地址传入,名称对应BI_NodeTagTab'' , @level0type=N''SCHEMA'',@level0name=N''dbo'', @level1type=N''TABLE'',@level1name=N''BI_StructureTab'', @level2type=N''COLUMN'',@level2name=N''Tagid''; \n") + .append(" EXEC sys.sp_addextendedproperty @name=N''MS_Description'', @value=N''BI节点代码'' , @level0type=N''SCHEMA'',@level0name=N''dbo'', @level1type=N''TABLE'',@level1name=N''BI_StructureTab'', @level2type=N''COLUMN'',@level2name=N''SpeciesNo''; \n") + .append(" EXEC sys.sp_addextendedproperty @name=N''MS_Description'', @value=N''BI节点名称'' , @level0type=N''SCHEMA'',@level0name=N''dbo'', @level1type=N''TABLE'',@level1name=N''BI_StructureTab'', @level2type=N''COLUMN'',@level2name=N''SpeciesName''; \n") + .append(" EXEC sys.sp_addextendedproperty @name=N''MS_Description'', @value=N''启用标记1=启用,节点禁用后不再显示(含子节点)'' , @level0type=N''SCHEMA'',@level0name=N''dbo'', @level1type=N''TABLE'',@level1name=N''BI_StructureTab'', @level2type=N''COLUMN'',@level2name=N''enableFlag''; \n") + .append(" EXEC sys.sp_addextendedproperty @name=N''MS_Description'', @value=N''节点说明'' , @level0type=N''SCHEMA'',@level0name=N''dbo'', @level1type=N''TABLE'',@level1name=N''BI_StructureTab'', @level2type=N''COLUMN'',@level2name=N''Bak''; \n") + .append(" EXEC sys.sp_addextendedproperty @name=N''MS_Description'', @value=N''关联模块编号,配置了关联模块号'' , @level0type=N''SCHEMA'',@level0name=N''dbo'', @level1type=N''TABLE'',@level1name=N''BI_StructureTab'', @level2type=N''COLUMN'',@level2name=N''Modid''; \n") + .append(" EXEC sys.sp_addextendedproperty @name=N''MS_Description'', @value=N''显示模块标记,1代表在节点上'' , @level0type=N''SCHEMA'',@level0name=N''dbo'', @level1type=N''TABLE'',@level1name=N''BI_StructureTab'', @level2type=N''COLUMN'',@level2name=N''mainVisible''; \n") + .append(" EXEC sys.sp_addextendedproperty @name=N''MS_Description'', @value=N''首页显示模块'' , @level0type=N''SCHEMA'',@level0name=N''dbo'', @level1type=N''TABLE'',@level1name=N''BI_StructureTab'', @level2type=N''COLUMN'',@level2name=N''displayModid''; \n") + .append(" EXEC sys.sp_addextendedproperty @name=N''MS_Description'', @value=N''关联模块调用模版'' , @level0type=N''SCHEMA'',@level0name=N''dbo'', @level1type=N''TABLE'',@level1name=N''BI_StructureTab'', @level2type=N''COLUMN'',@level2name=N''dllfilename'';')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 考勤打卡限制时间更新 (20240920) + */ + private void update_20240920() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_employeetab', 'p_emp_AttendanceTime') is null \n") + .append(" exec('alter table dbo.p_employeetab add p_emp_AttendanceTime int;')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 禁用mes选账套功能更新 (20241006) + */ + private void update_20241006() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_SystemTab', 'mes_address_change') is null \n") + .append(" exec('alter table dbo.p_SystemTab add mes_address_change int;')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 系统DLL表更新 (241025) + */ + private void updateDllTab_241025() { + StringBuilder sqlBuilder = new StringBuilder(); + // p_systemdlltab + sqlBuilder.append("if col_length('dbo.p_systemdlltab', 'CsHasDefultSearch') is null \n") + .append(" exec('alter table dbo.p_systemdlltab add CsHasDefultSearch int default 0;')\n") + .append("if col_length('dbo.p_systemdlltab', 'gridobjcheck') is null \n") + .append(" exec('alter table dbo.p_systemdlltab add gridobjcheck int default 0;')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 字体颜色配置更新 (241101) + */ + private void updateBookColor_241101() { + StringBuilder sqlBuilder = new StringBuilder(); + // p_systemdlltab + sqlBuilder.append("if col_length('dbo.p_systemwordbookcolor', 'fontsize') is null \n") + .append(" exec('alter table dbo.p_systemwordbookcolor add fontsize int null;')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 业务表更新 (241119) + */ + private void updatebusiness_241119() { + StringBuilder sqlBuilder = new StringBuilder(); + // p_systemdlltab + sqlBuilder.append("if col_length('dbo.P_SystemCheckTab', 'stepover') is null \n") + .append(" exec('alter table dbo.P_SystemCheckTab add stepover int null;')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 业务表更新 (24111901) + */ + private void updateBusiness24111901() { + StringBuilder sqlBuilder = new StringBuilder(); + // p_systemdlltab + sqlBuilder.append("if col_length('dbo.LBC_FileGroupTab', 'deptids') is null \n") + .append(" exec('alter table dbo.LBC_FileGroupTab add deptids varchar(100) null;')\n") + .append("if col_length('dbo.LBC_ExamGroupTab', 'judge') is null \n") + .append(" exec('alter table dbo.LBC_ExamGroupTab add judge int null;')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 知识库文件组排序更新 (24121001) + */ + private void updateBusiness24121001() { + StringBuilder sqlBuilder = new StringBuilder(); + // p_systemdlltab + sqlBuilder.append("if col_length('dbo.LBC_FileGroupTab', 'grouporderid') is null \n") + .append(" exec('alter table dbo.LBC_FileGroupTab add grouporderid varchar(100) null;')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * 知识库附件排序更新 (24121101) + */ + private void updatebusiness_24121101() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if col_length('dbo.p_fm_filetab', 'fileorderid') is null \n") + .append(" exec('alter table dbo.p_fm_filetab add fileorderid varchar(100) null;')"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + /** + * web端自动更新表创建 (20250429) + */ + private void updateWebUpdTab() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[P_SystemWebUpdateTab]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)\n") + .append("exec('CREATE TABLE [dbo].[P_SystemWebUpdateTab](\n") + .append(" [id] [int] IDENTITY(1,1) NOT NULL primary key,\n") + .append(" [version] [varchar](100) not NULL,\n") + .append(" [versioncode] [int] not NULL,\n") + .append(" [name] [varchar](50) NULL,\n") + .append(" [note] [varchar](500) NULL,\n") + .append(" [download] [varchar](500) NULL,\n") + .append(" [desc] [varchar](50) NULL,\n") + .append(" [createDate] datetime not null default getdate(),\n") + .append(" [force] int default 0--是否强制更新 1:必须更新\n") + .append(" )');\n") + .append("if not exists (select 1 from dbo.sysobjects where id = object_id(N'[dbo].[P_SystemWebUpdateHisTab]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)\n") + .append("exec('CREATE TABLE [dbo].[P_SystemWebUpdateHisTab](\n") + .append(" [id] [int] IDENTITY(1,1) NOT NULL primary key,\n") + .append(" [version] [int] NULL,\n") + .append(" [oaurl] [varchar](100) NULL,\n") + .append(" [webver] [varchar](100) NULL,\n") + .append(" [sysinfo] [varchar](max) NULL,\n") + .append(" [createDate] datetime not null default getdate()\n") + .append(" )');"); + + jdbcTemplate.execute(sqlBuilder.toString()); + } + + private String dataVersion = ""; + + /** + * 当前程序版本 + */ + private static final String VERSION = "1.0.9.4"; + + /** + * 检查更新版本 + * + * @param ver 版本号 + * @param versionName 版本字段名,默认"WebVersion" + * @return 如果版本不一致需要更新返回true,否则返回false + */ + protected boolean checkUpdateVersion(String ver, String versionName) { + if (versionName == null || versionName.isEmpty()) { + versionName = "WebVersion"; + } + + try { + // 检查并添加版本字段 + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append(String.format("if col_length('dbo.p_SystemTab', '%s') is null " + + "exec('alter table dbo.p_SystemTab add %s varchar(100);')", + versionName, versionName)); + jdbcTemplate.execute(sqlBuilder.toString()); + + // 查询当前数据版本 + String querySql = String.format("select top 1 %s from dbo.p_SystemTab", versionName); + Object result = jdbcTemplate.queryForObject(querySql, Object.class); + dataVersion = result != null ? result.toString() : ""; + + // 版本不一致时更新 + if (!dataVersion.equals(ver)) { + String updateSql = String.format("update dbo.p_SystemTab set %s = '%s'", versionName, ver); + jdbcTemplate.execute(updateSql); + + // 处理初始版本为空的情况 + if (dataVersion.isEmpty()) { + dataVersion = "1.0.0.0"; + } + return true; + } + return false; + } catch (Exception e) { + // 日志记录可根据实际情况添加 + log.error("Exception caught", e); + return false; + } + } + + protected boolean checkUpdateVersion(String ver) { + return checkUpdateVersion(ver, "WebVersion"); + } + + /** + * 获取数据版本的数字表示(去除小数点) + */ + protected int getDataVer() { + return NativeExtensionUtils.ToInt32(dataVersion.replace(".", "")); + } + + /** + * 获取当前程序版本的数字表示(去除小数点) + */ + public static int getVersion() { + return NativeExtensionUtils.ToInt32(VERSION.replace(".", "")); + } + + /** + * 升级控制主方法 + */ + public boolean checkUpdate() throws SQLException { + if (checkUpdateVersion(VERSION)) { + int currentVersion = getDataVer(); + + // 执行各版本升级操作 + if (1000 >= currentVersion) { + updateSubSysTab(); + updateSysMenuTab(); + updateDllTab(); + updateBillTypeTab(); + updateDllTabDetail(); + updateWorkBookTab(); + updateNotificationTab(); + updateMessge(); + updateSystemOtherTab(); + updateField(); + updateBillField(); + updateView(); + updateFlowChartCfgTable(); + updateFirstPageTable(); + updateBITab(); + updateMobileCard(); + updateAttcCusSet(); + updateErrInfoTab(); + updateProductSpeciesTab(); // 20220706添加 + } + if (1002 >= currentVersion) updateSysTab(); + if (1003 > currentVersion) updateDlltabDetailFlowTab(); + if (1004 > currentVersion) updateAuditFlowTypeStepTab(); + if (1005 > currentVersion) updateCusModuleAddTpl(); + if (1006 > currentVersion) updatePopupMenu(); + if (1007 > currentVersion) updateDllTab_21429(); + if (1008 > currentVersion) { + updateAuditAttach(); + updateDlltabDetailFlowTab_210430(); + updateDllTabDetail_210430(); + updatePopupMenu_210507(); + updateDllTabDetail_210514(); + updateDlltabDetailFlowTab_210514(); + } + if (1009 > currentVersion) { + updateDllTab_210519(); + updateBillTab_210525(); + } + if (1010 > currentVersion) updateWorkBookTab_210527(); + if (1011 > currentVersion) updateDllTab_210610(); + if (1012 > currentVersion) { + updateDllTab_210624(); + updateSysTab_210630(); + } + if (1013 > currentVersion) updateMobileCard_210707(); + if (1014 > currentVersion) { + updateDllTab_210901(); + updateBillTypeTab_210901(); + } + if (1015 > currentVersion) updateWorkBookTab_210911(); + if (1016 > currentVersion) { + updateDllTabDetail_210914(); + updateAuditAttach_20210918(); + updateDlltabAttach_20210918(); + } + if (1017 > currentVersion) updateSystemdlltabChart_20210927(); + if (1018 > currentVersion) updateBillTab_211022(); + if (1019 > currentVersion) updateLogTab_20211027(); + if (1020 > currentVersion) { + updateEmployeetab_20210928(); + updateDllTab_202111122(); + } + if (1021 > currentVersion) updateLoginCfg(); + if (1022 > currentVersion) updateDllTab_20211129(); + if (1023 > currentVersion) updateDllTab_20211129a(); + if (canUpdVer(1024)) updateDllTabDetail_220114(); + if (canUpdVer(1025)) updateSystemdlltabChart_20220208(); + if (canUpdVer(1026)) updateDllTab_20220309(); + if (canUpdVer(1027)) updateMobileCard_220322(); + if (canUpdVer(1028)) updateDllTab_20220414(); + if (canUpdVer(1029)) updateDllTab_20220524(); + if (canUpdVer(1030)) updateEmployeetab_20220617(); + if (canUpdVer(1031)) updateWorkBookTab_220704(); + if (canUpdVer(1032)) updatePopupMenu_220720(); + if (canUpdVer(1033)) updatePopupMenu_220812(); + if (canUpdVer(1034)) updateDllFlow_220823(); + if (canUpdVer(1035)) updatePrintTab(); // 该版本需新版打印程序支持 + if (canUpdVer(1036)) updateMegToolLiknkTab_20220914(); + if (canUpdVer(1037)) updatePopupMenu_220929(); + if (canUpdVer(1038)) updateDllTab_221024(); + if (canUpdVer(1039)) updatePopupMenu_221123(); + if (canUpdVer(1040)) updateEmployeetab_20221207(); + if (canUpdVer(1041)) updateWorkbooktab_20230206(); + if (canUpdVer(1042)) updateDllTab_20230209(); + if (canUpdVer(1043)) { + updateFileTab20230224(); + updatebSysTab20230224(); + } + if (canUpdVer(1044)) updateFieldTab_20230314(); + if (canUpdVer(1045)) updateSysteTab_20230328(); + if (canUpdVer(1046)) updateDllTab_20230406(); + if (canUpdVer(1047)) updateEmployeeTab_20230408(); + if (canUpdVer(1048)) updateEmployeeTab_20230412(); + if (canUpdVer(1049)) updateEmployeeTab_20230413(); + if (canUpdVer(1050)) updateEmployeeTab_20230520(); + if (canUpdVer(1051)) { + update_20230522(); + updateComment_230612(); + } + if (canUpdVer(1052)) update_20230704(); + if (canUpdVer(1055)) { + update_202307011(); + updateAddGroupTab(); + } + if (canUpdVer(1056)) update_p_systemdlldetailtab(); + if (canUpdVer(1057)) updateDllTab_231215(); + if (canUpdVer(1058)) updateDllTab_240117(); + if (canUpdVer(1059)) updateGroupTab_240120(); + if (canUpdVer(1060)) updateWorkBookTab_240321(); + if (canUpdVer(1061)) updateNotification_240412(); + if (canUpdVer(1062)) updateLookupWidth240420(); + if (canUpdVer(1063)) update_bookfontsize240426(); + if (canUpdVer(1064)) update_popupmenu240428(); + if (canUpdVer(1065)) update_popupmenu240522(); + if (canUpdVer(1066)) updateDllTab_240617(); + if (canUpdVer(1067)) updateDllTab_240619(); + if (canUpdVer(1068)) updateSysteTab_20240625(); + if (canUpdVer(1069)) updateCondition240807(); + if (canUpdVer(1070)) update_bookfontsize240815(); + if (canUpdVer(1071)) updateSystemdlltabChart_20240820(); + if (canUpdVer(1074)) update_20240902(); + if (canUpdVer(1075)) updateFieldTab_20240904(); + if (canUpdVer(1076)) updateEmployeeTab_20240909(); + if (canUpdVer(1077)) { + updateBmpType_20240913(); + updateNewBI_240824(); + } + if (canUpdVer(1078)) update_20241006(); + if (canUpdVer(1079)) update_20240920(); + if (canUpdVer(1080)) updateDllTab_241025(); + if (canUpdVer(1081)) updateBookColor_241101(); + if (canUpdVer(1082)) updateDlltabAttach_20241104(); + if (canUpdVer(1084)) updatebusiness_241119(); + if (canUpdVer(1087)) updatebusiness_24121101(); + if (canUpdVer(1088)) updateSystemdlltabChart_20241225(); + if (canUpdVer(1091)) { + updateSysMenuTab_250120(); + updateFiletab_250120(); + } + if (canUpdVer(1092)) updateDllTabDetail_250426(); + if (canUpdVer(1093)) updateWebUpdTab(); + if (canUpdVer(1094)) updatePrintTab250507(); + + return true; + } + return false; + } + + /** + * 检查是否可以升级到指定版本 + * + * @param updVer 目标版本 + * @return 是否可以升级 + */ + protected boolean canUpdVer(int updVer) { + int currentVersion = getDataVer(); + return updVer > currentVersion && updVer <= getVersion(); + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Impl/VerifyImpl.java b/WebErp/weberp/src/main/java/org/example/Impl/VerifyImpl.java new file mode 100644 index 0000000..2588dab --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Impl/VerifyImpl.java @@ -0,0 +1,108 @@ +package org.example.Impl; + + +import jakarta.servlet.http.HttpSession; +import org.example.Entity.BaseResponse.BaseResponse; +import org.springframework.stereotype.Service; + + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Date; + +/** + * 功能描述:验证 + */ +@Service +public class VerifyImpl extends BaseImpl { + private static final LocalDateTime PAST_TIME = LocalDateTime.of(2029, 1, 1, 0, 0, 0); + + public BaseResponse verify() { + BaseResponse response = new BaseResponse(); + HttpSession session = getSession(); + + Object result = session.getAttribute("VerifyResult"); + if (result != null) { + response.setSuccess((boolean) result); + } else { + LocalDateTime now = LocalDateTime.now(); + if (now.isAfter(PAST_TIME)) { + response.setSuccess(false); + } else { + response.setSuccess(true); + } + } + + if (!response.isSuccess()) { + response.setMsg("劺u3离???? ??;D$ 匶3纼?]_^[脨SVW嬟嬸侢  }?  ?伷 佹 塻jh Vj ??孁??t#嬘胳uv 柢?劺uh € j ?P??3缐_^[脨SVWU嬞嬺嬭荂  jh h  U栳?孁??u伷 佹 塻jh VU杓??? t#嬘胳uv 鑕?劺uh € j ?P铻?3缐]_^[脨SVWU兡鑻鶍羟D$3蓧L$ 塂$T$塗$§uv ?雓?? 塂$?媂;rR嬅?B ;D$wE;s塡$?媓?h ;l$ v塴$ h € j ?婡P??吚u\n?萿v  ???婦$?胳uv ;u?缐億$ t婦$?婦$ +D$塆兡 "); + } + + session.setAttribute("VerifyResult", response.isSuccess()); + return response; + } + + public BaseResponse verifyByServer() { + BaseResponse response = new BaseResponse(); + // 获取应用级别的属性存储(此处假设通过ServletContext获取) + Object result = getServletContext().getAttribute("VerifyResult"); + + if (result != null) { + response.setSuccess((boolean) result); + } else { + // 以下为注释掉的原C#代码的Java对应注释 + /* + String url = "http://localhost:3322/Api/VerifyApi.ashx"; + String ips = WebUtil.getServerIp(); + String hostname = InetAddress.getLocalHost().getHostName(); + + Map pms = new HashMap<>(); + pms.put("ips", ips); + pms.put("hostname", hostname); + pms.put("constr", ConfigUtil.getConnectionString()); + pms.put("method", "ServerVerify"); + + String spms = AESUtil.encrypt(JSON.toJSONString(pms)); + Map pmdic = new HashMap<>(); + pmdic.put("pms", spms); + + boolean[] success = {false}; + String result = WebUtil.send(url, pmdic, success); + + if (StringUtils.isNotBlank(result) && success[0]) { + try { + response = JSON.parseObject(result, BaseResponse.class); + } catch (Exception e) { + // 异常处理 + } + } + */ + response.setSuccess(true); + } + + if (!response.isSuccess()) { + response.setMsg("劺u3离???? ??;D$ 匶3纼?]_^[脨SVW嬟嬸侢  }?  ?伷 佹 塻jh Vj ??孁??t#嬘胳uv 柢?劺uh € j ?P??3缐_^[脨SVWU嬞嬺嬭荂  jh h  U栳?孁??u伷 佹 塻jh VU杓??? t#嬘胳uv 鑕?劺uh € j ?P铻?3缐]_^[脨SVWU兡鑻鶍羟D$3蓧L$ 塂$T$塗$§uv ?雓?? 塂$?媂;rR嬅?B ;D$wE;s塡$?媓?h ;l$ v塴$ h € j ?婡P??吚u?萿v  ???婦$?胳uv ;u?缐億$ t婦$?婦$ +D$塆兡 "); + } + + getServletContext().setAttribute("VerifyResult", response.isSuccess()); + return response; + } + + public BaseResponse doServerVerify(String content) { + BaseResponse response = new BaseResponse(); + // String hostip = WebUtil.getIP(); + response.setSuccess(true); + return response; + } + + // 假设BaseImpl中已实现获取Session和ServletContext的方法 + // 如果未实现,需要添加以下类似方法: + protected HttpSession getSession() { + // 具体实现根据项目的Session获取方式 + return getCtx().getSession(); + } + + protected jakarta.servlet.ServletContext getServletContext() { + // 具体实现根据项目的ServletContext获取方式 + return getCtx().getServletContext(); + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/ModuleApi/BaseModule/mapper/BaseModuleMapper.java b/WebErp/weberp/src/main/java/org/example/ModuleApi/BaseModule/mapper/BaseModuleMapper.java new file mode 100644 index 0000000..e69de29 diff --git a/WebErp/weberp/src/main/java/org/example/ModuleApi/ModuleAjaxApi/controller/ModuleAjaxController.java b/WebErp/weberp/src/main/java/org/example/ModuleApi/ModuleAjaxApi/controller/ModuleAjaxController.java new file mode 100644 index 0000000..edcaeef --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/ModuleApi/ModuleAjaxApi/controller/ModuleAjaxController.java @@ -0,0 +1,1466 @@ +package org.example.ModuleApi.ModuleAjaxApi.controller; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.example.Api.OptBaseHandler; +import org.example.Entity.Attributes.RequestCheck; +import org.example.Entity.CusException.CusException; +import org.example.Entity.System.BaseModule; +import org.example.Entity.System.BillModule; +import org.example.Entity.System.BillStateEn; +import org.example.Entity.System.ModuleBaseEntity; +import org.example.Enums.SystemEnums; +import org.example.Enums.SystemTypeEnums; +import org.example.Impl.BaseImpl; +import org.example.Impl.DataImpl; +import org.example.Impl.MapImpl; +import org.example.Impl.ModuleImpl; +import org.example.Service.ModuleImplService; +import org.example.Utils.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.web.bind.annotation.*; + +import java.io.UnsupportedEncodingException; +import java.util.*; + +import static org.example.Utils.NativeExtensionUtils.*; + +@RestController +@Scope("prototype") +@RequestMapping("/Api/ModuleAjaxApi") +public class ModuleAjaxController extends OptBaseHandler { + @RequestMapping(value = "/**", method = {RequestMethod.GET, RequestMethod.POST}) + public void handleRequest(HttpServletRequest Request, HttpServletResponse response) throws Exception { + // 调用 BaseHandler 的 processRequest 处理逻辑 + super.processRequest(Request); + } + + @Autowired + ModuleImplService moduleImplService; + @Autowired + private JdbcTemplate jdbcTemplate; + @Autowired + private IPublicUtil _util; + + @Autowired + private ModuleImpl moduleImpl; + + public IPublicUtil getUtil() { + return _util == null ? _util = new IPublicUtil(jdbcTemplate) : _util; + } + + @Autowired + private MapImpl _mapImpl; + + private MapImpl getMapImpl() { + if (_mapImpl == null) + _mapImpl = new MapImpl(jdbcTemplate); + return _mapImpl; + } + + @RequestCheck(CheckLogin = false, CheckParams = "ModuleId", Cache = true) + public void GetModuleIniParams() { + BaseImpl bImpl = getBImpl(); + String targetModuleId = bImpl.Request("targetModuleId"); + int sourceId = ToInt32(bImpl.Request("sourceId")); + if (sourceId > 0) { + response.setData(moduleImpl.GetBillSourceModule(ModuleId, sourceId + "", "").get(0)); + } else { + response.setData(moduleImplService.getModuleIniParams( + bImpl.Request("ModuleId"), + bImpl.Request("MenuId"), + bImpl.Request("targetModuleId", ""), + bImpl.Request("detailId"), + toBoolean(bImpl.Request("isCard")), + toBoolean(bImpl.Request("isChart")), + bImpl.Request("mFields"), + toBoolean(bImpl.Request("atts")), + toBoolean(bImpl.Request("loadDetail", "1")), + toBoolean(bImpl.Request("loadLeft", "1")), + toBoolean(bImpl.Request("isAttc", "0")) + )); + if (Objects.equals(ModuleId, "2005_1")) { + String idVal = bImpl.Request("idValue", ""); + response.setOther(moduleImpl.CheckAttcAuthoryInt(moduleImpl.GetModule(targetModuleId), idVal)); + } + } + response.setSuccess(true); + } + + @RequestCheck(CheckParams = "_fId|id") + public void GetFieldData() { + response = moduleImplService.getFieldDataPam(); + } + + @RequestCheck(CheckLogin = false, CheckParams = "ModuleId") + public void GetModuleData() { + response = moduleImplService.getModuleData(); + } + + @RequestCheck(CheckParams = "ModuleId") + public void GetBillIniParams() { + response = moduleImplService.getBillIniParams(); + } + + @RequestCheck(CheckParams = "ModuleId") + ///

+ public void GetBaseAuditStepData() { + response = moduleImplService.getBaseAuditStepData(); + } + + @RequestCheck(CheckParams = "ModuleId") + public void GetStepDataCounts() { + response = moduleImplService.GetStepDataCounts(); + } + + public void GetAddOrUpdFields() { + response = moduleImplService.GetAddOrUpdFields(); + } + + @RequestCheck(CheckParams = "moduleId") + public void GetAttcData() throws UnsupportedEncodingException { + response = moduleImplService.GetAttcData(); + } + + @RequestCheck(CheckParams = "ModuleId") + public void AddOrUpd() throws UnsupportedEncodingException, CusException { + response = moduleImplService.AddOrUpd(); + } + + + @RequestCheck(CheckParams = "ModuleId") + public void Delete() { + response = moduleImplService.Delete(); + } + + @RequestCheck(CheckParams = "ModuleId") + public void GetCondition() { + + response = moduleImplService.GetCondition(); + } + + @RequestCheck(CheckParams = "ModuleId") + public void GetBaseAuditIniParams() { + var theRes = moduleImpl.GetAuditIniParams(ModuleId, true); + response.setData(theRes); + if (theRes.isSuccess()) { + response.setSuccess(true); + } else { + response.setSuccess(false); + response.setMsg(theRes.getMsg()); + } +// response = moduleImplService.GetAuditIniParams(); + } + +// @RequestCheck(CheckParams = "ModuleId", Cache = true) +// public void GetModuleCfg() { + + /// / response.setData(moduleImpl.GetModule(ModuleId)); + /// / response.setSuccess(true); +// response = moduleImpl.GetModuleCfg(); +// } + public void GetModulCfgs() { + response.setData(moduleImpl.GetModuleCfgs(getBImpl().Request("ModuleId"), MenuId)); + response.setSuccess(true); + } + + @RequestCheck(CheckParams = "ModuleId", Cache = true) + public void GetModuleCfg() { + response.setData(moduleImpl.GetModule(ModuleId)); + response.setSuccess(true); + } + + @RequestCheck(CheckParams = "ModuleIds", Log = false) + public void GetModuleCountData() { + String pms = getBImpl().Request("pms"), modueids = getBImpl().Request("ModuleIds"); + Map datas = new HashMap<>(); + response.setData(moduleImpl.GetModuleCountData(modueids, pms, getBImpl().Request("w")).getData()); + response.setSuccess(true); + } + + @RequestCheck(CheckLogin = false, CheckParams = "ModuleId") + public void GetModuleRightMenu() { + response = moduleImpl.GetModuleRightMenu(); + } + + @RequestCheck(CheckParams = "ModuleId") + public void GetModuleDetailsData() { + String pms = getBImpl().Request("pms"), leftRecord = getBImpl().Request("leftRecord", pms), dids = getBImpl().Request("ids"); + //将参数放在pms中来判断是否要拼接Moduleimpl中的idwherekey查询 + if (!isNullOrEmpty(pms)) { + leftRecord = leftRecord.trim(); + leftRecord = leftRecord.substring(0, leftRecord.length() - 1) + ",\"$isPms\":1}"; + } + boolean isAuditAttc = toBoolean(getBImpl().Request("isAttc")); + response = moduleImpl.GetModuleDetailsData(ModuleId, leftRecord, dids); + } + + @RequestCheck(CheckParams = "ModuleId,detailId") + public void GetModuleDetailData() { + BaseImpl bImpl = getBImpl(); + String detailId = bImpl.Request("detailId") + "", pms = bImpl.Request("qrpms", bImpl.Request("pms")), + leftRecord = bImpl.Request("leftRecord", pms); + + // 将参数放在pms中来判断是否要拼接Moduleimpl中的idwherekey查询 + if (pms != null && !pms.trim().replaceAll("[{}]", "").trim().isEmpty()) { + leftRecord = leftRecord != null ? leftRecord.trim() : ""; + if (leftRecord.isEmpty() || leftRecord.trim().replaceAll("[{}]", "").trim().isEmpty()) { + leftRecord = "{\"$isPms\":1}"; + } else { + // 去除最后一个字符(假设是'}')并添加新属性 + leftRecord = leftRecord.substring(0, leftRecord.length() - 1) + ",\"$isPms\":1}"; + } + } + + boolean isAuditAttc = NativeExtensionUtils.toBoolean(bImpl.Request("isAttc")); + boolean isMulti = NativeExtensionUtils.toBoolean(bImpl.Request("multi")); + boolean isGrid = NativeExtensionUtils.toBoolean(bImpl.Request("grid")); + int detailIdInt = NativeExtensionUtils.parseInt(detailId); + + response = moduleImpl.GetModuleDetailData(ModuleId, detailIdInt, leftRecord, isMulti, isGrid, isAuditAttc, pms); + } + + @RequestCheck(CheckParams = "ModuleId,idValue") + public void GetAuditHistory() { + response = moduleImpl.GetAuditHistory(ModuleId, getBImpl().Request("idValue"), getBImpl().Request("isBase")); + } + + public void GetAddOrUpdData() { + BaseImpl bImpl = getBImpl(); + String idValue = bImpl.Request("idValue"), + detailId = bImpl.Request("detailId"), + contextMenuId = bImpl.Request("contextMenuId") + "", + leftRecord = bImpl.Request("leftRecord"); + + response = moduleImpl.GetAddOrUpdData(ModuleId, detailId, idValue, leftRecord, ToInt32(contextMenuId), toBoolean(bImpl.Request("isattc"))); + } + + @RequestCheck(CheckParams = "ModuleId") + public void GetModuleDetail() { + response.setData(moduleImpl.GetModelDetails(moduleImpl.GetBaseModule(ModuleId, ""))); + response.setSuccess(response.getData() != null); + + } + + @RequestCheck(CheckParams = "ModuleId") + public void GetMobileAttachModules() { + response = moduleImpl.GetAttachModules(ModuleId, true); + + } + + @RequestCheck(CheckParams = "ModuleId") + public void GetAttachModules() { + response = moduleImpl.GetAttachModules(ModuleId, toBoolean(getBImpl().Request("iscard"))); + + } + + @RequestCheck(CheckParams = "ModuleId") + public void GetSpecificAttachModules() { + response = moduleImpl.GetSpecificAttachModules(ModuleId); + + } + + @RequestCheck(CheckParams = "ModuleId") + public void GetModuleCardGroup() { + BaseImpl bImpl = getBImpl(); + String isMain = bImpl.Request("main"), isBase = bImpl.Request("isBase"), mxId = bImpl.Request("detailId"); + response.setData(moduleImpl.GetModuleCardGroup(ModuleId, isNullOrEmpty(isMain) ? null : toBoolean(isMain), isNullOrEmpty(isBase) ? null : toBoolean(isMain), ToInt32(mxId))); + response.setSuccess(true); + } + + + @RequestCheck(CheckParams = "ModuleId") + public void ModuleSchemes() { + response.setData(moduleImpl.GetSchemesList(MenuId)); + response.setSuccess(true); + } + + @RequestCheck(CheckParams = "MenuId,name") + public void DeleteSchemes() { + response = moduleImpl.DeleteProject(MenuId, getBImpl().Request("name")); + } + + @RequestCheck(CheckParams = "MenuId,name,vals") + public void SaveSchemes() { + response = moduleImpl.SaveProject(getBImpl().Request("name"), getBImpl().Request("vals"), MenuId); + } + + @RequestCheck(CheckParams = "ModuleId") + public void GetTaskIniParams() { + response = moduleImpl.GetTaskIniParams(ModuleId); + } + + public void GetTaskData() { + String record = getBImpl().Request("record") + "", + leftRecord = getBImpl().Request("leftRecord"), + pms = getBImpl().Request("pms"), taskModuleId = getBImpl().Request("TaskModuleId"); + int taskType = ToInt32(getBImpl().Request("tasktype")); + if (isNullOrEmpty(taskModuleId)) { + response.setData(null); + response.setSuccess(true); + } else { + response = moduleImpl.GetTaskData(ModuleId, taskModuleId, leftRecord, pms, taskType); + } + } + + @RequestCheck(CheckParams = "ModuleId,TaskModuleId") + public void GetTaskColumns() { + response = moduleImpl.GetTaskColumns(ModuleId, getBImpl().Request("TaskModuleId")); + } + + @RequestCheck(CheckParams = "id") + public void GetFieldUnionData() { + BaseImpl bImpl = getBImpl(); + int fieldId = ToInt32(bImpl.Request("id")); + int fdtype = ToInt32(bImpl.Request("fdtype")); + String record = Objects.toString(bImpl.Request("record"), ""), + poppms = bImpl.Request("poppms"), + leftRecord = bImpl.Request("leftRecord"), + pms = bImpl.Request("pms"); + if (!isNullOrEmpty(poppms) && poppms.startsWith("{"))//右键调添加模块时候,要把右键参数传递过来,替换字段里面的{#p_n}的值 + { + if (isNullOrEmpty(record) || !record.startsWith("{")) { + record = poppms; + } else { + Map recordMap = (Map) JSON.Decode(record); + // 合并poppmsMap到recordMap(覆盖相同key) + if (recordMap != null) { + Map poppmsMap = (Map) JSON.Decode(poppms); + for (Map.Entry entry : poppmsMap.entrySet()) { + recordMap.put(entry.getKey(), entry.getValue()); + } + // 将合并后的Map重新序列化为JSON字符串 + record = JSON.Encode(recordMap); + } else { + record = poppms; + } + } + } + + response = moduleImpl.GetFieldUnionData(fieldId, record, leftRecord, pms, fdtype); + } + + @RequestCheck(CheckParams = "mid") + public void ContextMenuClick() throws CusException { + BaseImpl bImpl = getBImpl(); + response = moduleImpl.ContextMenuClick(ToInt32(bImpl.Request("mid")), + ModuleId, bImpl.Request("data") + "", bImpl.Request("leftRecord") + ""); + } + + public void ExcAuQuerySql() { + BaseImpl bImpl = getBImpl(); + String ensql = bImpl.Request("s") + "", + record = bImpl.Request("record") + "", + leftRecord = bImpl.Request("leftRecord"), + pms = bImpl.Request("pms"), + keyField = bImpl.Request("textField"), + keyValue = bImpl.Request(keyField); + response = moduleImpl.ExcQuerySql(ensql, record, leftRecord, pms, keyField, keyValue); + } + + public void GetDefaultVal() { + BaseImpl bImpl = getBImpl(); + String val = bImpl.Request("val"), _record = bImpl.Request("record"); + Hashtable record = null; + if (!isNullOrEmpty(_record)) { + record = (Hashtable) JSON.Decode(_record); + } + response.setData(PublicUtil.ReqSqlPms(record, null, val, SystemTypeEnums.PmType.sql, null)); + response.setSuccess(true); + } + + @RequestCheck(CheckParams = "ModuleId,idValue") + public void GetBillState() { + ModuleBaseEntity module = null; + String mTypeStr = getBImpl().Request("mtype"); + SystemEnums.ModuleType mType = null; // 默认值 + + // 解析模块类型 + if (mTypeStr != null && !mTypeStr.isEmpty()) { + try { + mType = SystemEnums.ModuleType.valueOf(mTypeStr.toUpperCase()); + } catch (IllegalArgumentException e) { + // 解析失败时使用默认值 + mType = SystemEnums.ModuleType.BaseModule; + } + } + + // 根据模块类型获取对应模块 + switch (mType) { + case BillModule: + module = moduleImpl.GetBillModule(ModuleId, MenuId); + break; + default: // 包括BaseModule和其他未定义类型 + module = moduleImpl.GetBaseModule(ModuleId, MenuId); + break; + } + + if (module == null) { + return; + } + + //response.success = true; + //response.data = moduleImpl.GetBillState(module, bImpl.Request("idValue")); + } + + @RequestCheck(CheckParams = "ModuleId,tpl") + public void SaveModuleAddTpl() { + String tpl = getBImpl().Request("tpl"); + int tplId = new DataImpl(jdbcTemplate).GetModuleAddTplId(ModuleId); + Map dH = new HashMap<>(); + dH.put("modulecontent", tpl); + dH.put("id", tplId); + dH.put("tab", ModuleId); + dH.put("ban", 1); + moduleImpl.AddOrUpdTable(JSON.Encode(dH), "P_pubrpsettab", "id", tplId <= 0, null); + } + + @RequestCheck(CheckParams = "ModuleId") + public void GetBillMasterFields() { + BaseImpl bImpl = getBImpl(); + response.setData(moduleImpl.GetBillMasterFields(ModuleId, bImpl.Request("idValue"), bImpl.Request("leftRecord"))); + response.setSuccess(true); + } + + @RequestCheck(CheckParams = "ModuleId") + public void GetBillAddOrUpdInfo() { + BaseImpl bImpl = getBImpl(); + String leftRecord = bImpl.Request("leftRecord"), contextMenuId = bImpl.Request("contextMenuId") + ""; +// response.setData(moduleImpl.GetBillAddOrUpdInfo(bImpl.Request("idValue"), ModuleId, MenuId, contextMenuId, leftRecord)); + String menuid = getBImpl().Request("MenuId", getBImpl().Request("menuId")); + response.setData(moduleImpl.GetBillAddOrUpdInfo(bImpl.Request("idValue"), ModuleId, menuid, contextMenuId, leftRecord)); + response.setSuccess(response.getData() != null); + if (!response.isSuccess()) { + response.setMsg("无效模块编号" + ModuleId); + } + } + + @RequestCheck(CheckParams = "ModuleId,idValue") + public void GetBillDetailData() { + response = moduleImpl.GetBillDetailData(ModuleId, getBImpl().Request("idValue")); + response.setSuccess(true); + } + + @RequestCheck(CheckParams = "ModuleId") + public void GetBillSource() { + var theData = moduleImpl.GetBillSourceModule(ModuleId, "", "0,1"); + response.setData(theData); + response.setSuccess(true); + } + + @RequestCheck(CheckParams = "ModuleId,sourceId") + public void GetBillSourceData() { + BaseImpl bImpl = getBImpl(); + response = moduleImpl.GetBillSourceData(ModuleId, bImpl.Request("sourceId"), bImpl.Request("pms"), bImpl.Request("w"), bImpl.Request("leftRecord"), toBoolean(bImpl.Request("detail"))); + } + + @RequestCheck(CheckParams = "ModuleId,master,detail") + public void SaveBill() throws CusException { + BaseImpl bImpl = getBImpl(); + boolean apply = toBoolean(bImpl.Request("apply"));//是否提交 + int applyType = ToInt32(bImpl.Request("applyType", "1"));//提交类型 1:提交 0:撤回 + int selectConfirmFlag = ToInt32(bImpl.Request("selectConfirmFlag")); + String idValue = bImpl.Request("idValue"); + if (selectConfirmFlag == 0 || !apply || isNullOrEmpty(idValue)) { + response = moduleImpl.SaveBill(ModuleId, + bImpl.Request("master"), //主表数据{} + bImpl.Request("detail"), //明细数据[] + // bImpl.Request("del"),//被删掉的原有数据 + bImpl.Request("leftRecord"), //左关联数据 + toBoolean(bImpl.Request("add")), //是否为新增 0|1,true|false + ToInt32(bImpl.Request("rtagid")),//红蓝字标识 0:蓝字 1:红字 + ToInt32(bImpl.Request("comfirm")), //The comfirm flag.0:默认保存 1:有提示,然后确认保存 + ToInt32(bImpl.Request("auditFlag")), + bImpl.Request("remark"));//comfirm remark + } else { + response.setSuccess(true); + response.setOther(idValue); + } + if (response.isSuccess() && apply && !isNullOrEmpty(response.getOther() + "")) { + String nextSelectStepCode = bImpl.Request("nextSelectStepCode"), + nextSelectStepOper = bImpl.Request("nextSelectStepOper"); + + BillStateEn stateEn = new BillStateEn(); + stateEn.setSelectConfirmFlag(selectConfirmFlag); + stateEn.nextSelectStepCode = nextSelectStepCode; + stateEn.nextSelectStepOper = nextSelectStepOper; + response = moduleImpl.BillApply(ModuleId, response.getOther() + "", stateEn, applyType); + if (response.getOther() + "" == "9") { + response.setSuccess(true); + } + } + } + + @RequestCheck(CheckParams = "ModuleId,idValue") + public void BillApply() throws CusException { + BaseImpl bImpl = getBImpl(); + int applyType = ToInt32(bImpl.Request("applyType", "1"));//提交类型 1:提交 0:撤回 + String idVal = bImpl.Request("idValue"), + nextSelectStepCode = bImpl.Request("nextSelectStepCode"), + nextSelectStepOper = bImpl.Request("nextSelectStepOper"); + int selectConfirmFlag = ToInt32(bImpl.Request("selectConfirmFlag")); + int comfirmFlag = ToInt32(bImpl.Request("comfirm")); + BillStateEn stateEn = new BillStateEn(); + stateEn.setSelectConfirmFlag(selectConfirmFlag); + stateEn.nextSelectStepCode = nextSelectStepCode; + stateEn.nextSelectStepOper = nextSelectStepOper; + stateEn.comfirmFlag = comfirmFlag; + response = moduleImpl.BillApply(ModuleId, idVal, stateEn, applyType); + } + + @RequestCheck(CheckParams = "ModuleId,idValue") + public void BillAudit() throws CusException { + BaseImpl bImpl = getBImpl(); + String idVal = bImpl.Request("idValue"), master = bImpl.Request("master"), //主表数据{} + detail = bImpl.Request("detail");//明细数据[] + int comfirm = ToInt32(bImpl.Request("comfirm")); + + BillModule module = moduleImpl.GetBillModule(ModuleId, MenuId); + module.IdValue = idVal; + module.setComfirmFlag(comfirm); + BillStateEn stateEn = GetAuditRequest(module); + response = moduleImpl.BillAudit(module, master, detail, stateEn); + } + + private BillStateEn GetAuditRequest(ModuleBaseEntity module) throws CusException { + BaseImpl bImpl = getBImpl(); + String remark = bImpl.Request("remark"), + stepCode = bImpl.Request("stepCode"), + backStepCode = bImpl.Request("backStepCode"), + nextSelectStepCode = bImpl.Request("nextSelectStepCode"), + nextSelectStepOper = bImpl.Request("nextSelectStepOper"), + comfirmOpers = bImpl.Request("comfirmOpers"); + + int selectConfirmFlag = ToInt32(bImpl.Request("selectConfirmFlag")); + + int comfirmFlag = ToInt32(bImpl.Request("comfirm")); + boolean back = toBoolean(ToInt32(bImpl.Request("back"))); + String audit = bImpl.Request("audit", "1") + "",//-1:关闭 0:回退 1:审核 + + direction = "F"; + switch (audit) { + case "-1": + direction = "Q"; + break;//关闭 + case "0": + direction = "R"; + break;//回退 + case "1": + direction = "F"; + break;//审核 + case "2": + direction = "C"; + break;//催办 + case "3": + direction = "P"; + break;//暂停待办 + case "4": + direction = "Z"; + break;//转发 + case "5": + direction = "J"; + break;//转交 + } + BillStateEn stateEn = moduleImpl.GetBillState(module, stepCode); + if (stateEn != null) { + stateEn.Direction = direction; + stateEn.IsBack = back; + stateEn.comfirmFlag = comfirmFlag; + stateEn.Remark = remark; + stateEn.BackStepCode = backStepCode; + stateEn.setSelectConfirmFlag(selectConfirmFlag); + stateEn.nextSelectStepCode = isNullOrEmpty(nextSelectStepCode) ? backStepCode : nextSelectStepCode; + stateEn.nextSelectStepOper = nextSelectStepOper; + stateEn.comfirmOpers = comfirmOpers; + } + return stateEn; + } + + /// + /// Gets the base audit information. + /// + /// + + /// + @RequestCheck(CheckParams = "ModuleId,idValue") + public void GetBaseAuditInfo() throws CusException { + BaseImpl bImpl = getBImpl(); + int stepId = ToInt32(bImpl.Request("stepId")), + ver = ToInt32(bImpl.Request("ver")); + String idValue = bImpl.Request("idValue"), + leftRecord = bImpl.Request("leftRecord"), + stepCode = bImpl.Request("stepCode"); + boolean tpl = toBoolean(bImpl.Request("tpl")); + response = moduleImpl.GetAuditInfo(ModuleId, stepId, idValue, leftRecord, true, stepCode, ver, tpl); + } + + /// + /// Gets the base audit detail data. + /// + /// + + /// + @RequestCheck(CheckParams = "detailId") + public void GetAuditDetailData() { + BaseImpl bImpl = getBImpl(); + int detailId = ToInt32(bImpl.Request("detailId")); + String record = bImpl.Request("record"), + leftRecord = bImpl.Request("leftRecord"), + pams = bImpl.Request("pams"); + response = moduleImpl.GetAuditDetailData(detailId, record, leftRecord, pams); + } + + @RequestCheck(CheckParams = "ModuleId,idValue,stepId|stepCode") + public void BaseAudit() throws UnsupportedEncodingException, CusException { + BaseImpl bImpl = getBImpl(); + String idValue = bImpl.Request("idValue") + "", + datas = bImpl.Request("datas"), details = bImpl.Request("details") + ""; + BaseModule module = moduleImpl.GetBaseModule(ModuleId, MenuId); + module.IdValue = idValue; + BillStateEn stateEn = GetAuditRequest(module); + response = moduleImpl.BaseAudit(module, datas, details, stateEn); + } + + @RequestCheck(CheckParams = "ModuleId,idValue,stepCode") + public void CommonAudit() throws UnsupportedEncodingException, CusException { + BillModule billMD = moduleImpl.GetBillModule(ModuleId, ""); + if (billMD != null) { + BillAudit(); + } else { + BaseAudit(); + } + } + + @RequestCheck(CheckParams = "ModuleId,datas") + public void BatchAudit() throws UnsupportedEncodingException, CusException { + String audit = getBImpl().Request("audit", "1") + "",//-1:关闭 0:回退 1:审核 + direction = "F"; + switch (audit) { + case "-1": + direction = "Q"; + break;//关闭 + case "0": + direction = "R"; + break;//回退 + case "1": + direction = "F"; + break;//审核 + case "2": + direction = "C"; + break;//催办 + case "3": + direction = "P"; + break;//暂停待办 + case "4": + direction = "Z"; + break;//转发 + case "5": + direction = "J"; + break;//转交 + } + response = moduleImpl.BatchAudit(ModuleId, direction, getBImpl().Request("datas")); + } + /// + /// base audit. + /// + /// + + /// + + @RequestCheck(CheckParams = "ModuleId,stepCode,idValue") + public void GetAuditBackSteps() throws CusException { + BaseImpl bImpl = getBImpl(); + String stepCode = bImpl.Request("stepCode"), idValue = bImpl.Request("idValue") + ""; + boolean isBase = toBoolean(bImpl.Request("isBase")); + response = moduleImpl.GetAuditBackSteps(ModuleId, idValue, stepCode, isBase); + } + /// + /// 提交 + /// + /// BaseResponse. + /// + + /// + @RequestCheck(CheckParams = "ModuleId,idValue") + public void BaseApply() throws CusException { + BaseImpl bImpl = getBImpl(); + String nextSelectStepCode = bImpl.Request("nextSelectStepCode"), + nextSelectStepOper = bImpl.Request("nextSelectStepOper"); + int selectConfirmFlag = ToInt32(bImpl.Request("selectConfirmFlag")), + comfirmFlag = ToInt32(bImpl.Request("comfirm") + ""); + + BillStateEn stateEn = new BillStateEn(); + stateEn.setSelectConfirmFlag(selectConfirmFlag); + stateEn.nextSelectStepCode = nextSelectStepCode; + stateEn.nextSelectStepOper = nextSelectStepOper; + stateEn.comfirmFlag = comfirmFlag; + response = moduleImpl.BaseApply(ModuleId, bImpl.Request("idValue") + "", stateEn, ToInt32(bImpl.Request("type", "1"))); + } + /// + /// Gets the bill audit ini parameters. + /// + /// + + /// + @RequestCheck(CheckParams = "ModuleId") + public void GetBillAuditIniParams() { + var theRes = moduleImpl.GetAuditIniParams(ModuleId, false); + response.setData(theRes); + if (theRes.isSuccess()) { + response.setSuccess(true); + } else { + response.setSuccess(false); + response.setMsg(theRes.getMsg()); + } + } + + /// + /// Gets the bill audit step data. + /// + /// + + /// + @RequestCheck(CheckParams = "ModuleId") + public void GetBillAuditStepData() { + BaseImpl bImpl = getBImpl(); + String record = bImpl.Request("record", ""), + leftRecord = bImpl.Request("leftRecord"), + pms = bImpl.Request("pms"); + int stepId = ToInt32(bImpl.Request("stepId")); + response = moduleImpl.GetAuditStepData(ModuleId, stepId, record, leftRecord, pms, false); + } + + + /// + /// Gets the bill audit information. + /// + /// + + /// + @RequestCheck(CheckParams = "ModuleId,idValue") + public void GetBillAuditInfo() throws CusException { + BaseImpl bImpl = getBImpl(); + int stepId = ToInt32(bImpl.Request("stepId")); + String idValue = bImpl.Request("idValue"), leftRecord = bImpl.Request("leftRecord"), stepCode = bImpl.Request("stepCode"); + boolean tpl = toBoolean(bImpl.Request("tpl")); + response = moduleImpl.GetAuditInfo(ModuleId, stepId, idValue, leftRecord, false, stepCode, 2, tpl); + } + + @RequestCheck(CheckParams = "moduleId,idValue") + public void GetAttcFiles() { + String dot = getBImpl().Request("dot", "&"); + response = moduleImpl.GetAttcFilePaths(ModuleId, getBImpl().Request("idValue"), + getBImpl().Request("specNo"), + getBImpl().Request("stepCode")); + + } + + @RequestCheck(CheckParams = "moduleId") + public void GetAttcTreeData() { + response = moduleImpl.GetAcctTreeData(ModuleId, getBImpl().Request("specNo", "")); + } + + @RequestCheck(CheckLogin = false, CheckParams = "ModuleId,ModuleType") + public void GetFlowChartData() throws CusException { + BaseImpl bImpl = getBImpl(); + int ModuleType = ToInt32(bImpl.Request("ModuleType")); + String billType = bImpl.Request("billType") + "", idValue = bImpl.Request("idValue") + ""; + boolean autostep = toBoolean(bImpl.Request("autostep")); + response = moduleImpl.GetFlowChartData(ModuleId, ModuleType, billType, idValue, autostep); + } + + @RequestCheck(CheckLogin = false, CheckParams = "ModuleId,ModuleType,billType") + public void GetFlowChartOption() { + BaseImpl bImpl = getBImpl(); + int ModuleType = ToInt32(bImpl.Request("ModuleType")); + String billType = bImpl.Request("billType") + ""; + response = moduleImpl.GetFlowChartOption(ModuleId, ModuleType, billType); + } + + @RequestCheck(CheckLogin = false, CheckParams = "ModuleId,ModuleType,billType") + public void UpdateFlowChartOption() { + BaseImpl bImpl = getBImpl(); + int ModuleType = ToInt32(bImpl.Request("ModuleType")); + String billType = bImpl.Request("billType") + "", options = bImpl.Request("options") + ""; + response = moduleImpl.UpdateFlowChartOption(ModuleId, ModuleType, billType, options); + } + + @RequestCheck(CheckLogin = false, CheckParams = "ModuleId,ModuleType,billType") + public void UpdatePartFlowChartOption() { + BaseImpl bImpl = getBImpl(); + int ModuleType = ToInt32(bImpl.Request("ModuleType")); + String billType = bImpl.Request("billType") + "", options = bImpl.Request("options") + ""; + response = moduleImpl.UpdatePartFlowChartOption(ModuleId, ModuleType, billType, options); + } + + + @RequestCheck(CheckLogin = false) + public void GetUpdFlowChartList() { + response = moduleImpl.GetUpdFlowChartList(); + } + + @RequestCheck(CheckLogin = false) + public void GetTimeEvent() { + response = moduleImpl.GetTimeEvent(); + } + + public void GanttGetModuleData() { + BaseImpl bImpl = getBImpl(); + String record = bImpl.Request("record") + "", + leftRecord = bImpl.Request("leftRecord"), + pms = bImpl.Request("pms"), + detailId = bImpl.Request("detailId") + "", + contextMenuId = bImpl.Request("contextMenuId") + "", + sourceId = bImpl.Request("sourceId") + ""; + response = moduleImpl.GanttGetModuleData(ModuleId, record, leftRecord, pms, "", toBoolean(bImpl.Request("multi")), + toBoolean(bImpl.Request("grid")), ToInt32(contextMenuId)); + } + + + @RequestCheck(CheckLogin = false) + public void EvalCond() { + String cond = getBImpl().Request("cond"); + response.setData(getUtil().evalCond(cond, null));// new DataImpl(){UserSessionName = UserSessionName,dbOperator = dbOperator}.EvalCond(cond); + response.setSuccess(true); + } + + + @RequestCheck(CheckLogin = false) + public void GetTimeChangeList() { + String sql = "select * from BI_TimeChangeList"; + response.setData(jdbcTemplate.queryForList(sql)); + response.setSuccess(true); + } + + + //region 打印相关 + + @RequestCheck(CheckParams = "ModuleId,name") + public void GetPrintInfo() { + BaseImpl bImpl = getBImpl(); + String printName = bImpl.Request("name") + "", record = bImpl.Request("record") + ""; + boolean isBase = toBoolean(bImpl.Request("base")); + response = moduleImpl.GetPrintInfo(ModuleId, printName, record); + } + + public void GetPrintSta() { + int printId = ToInt32(getBImpl().Request("pid")); + response = moduleImpl.GetPrintSta(printId); + } + + + @RequestCheck(CheckParams = "ModuleId,name") + public void GetWebPrintInfo() { + BaseImpl bImpl = getBImpl(); + String printName = bImpl.Request("name") + "", record = bImpl.Request("record") + ""; + boolean isBase = toBoolean(bImpl.Request("base")); + response = moduleImpl.GetWebPrintInfo(ModuleId, printName, record, isBase); + } + + /// + /// 添加或修改web打印模板 + /// + /// System.String. + /// + /// + + @RequestCheck(CheckParams = "ModuleId") + public void AddOrUpdWebPrint() { + BaseImpl bImpl = getBImpl(); + String idValue = bImpl.Request("idValue"), + content = bImpl.Request("content"), + printName = bImpl.Request("name"); + response.setData(moduleImpl.AddOrUpdWebPrint(ModuleId, printName, content, idValue)); + response.setSuccess(!isNullOrEmpty(response.getData() + "")); + } + + @RequestCheck(CheckParams = "ModuleId,idValue") + public void UpdDownLoadCount() { + String idValue = getBImpl().Request("idValue"); + moduleImpl.AddDownloadCount(ModuleId, idValue); + response.setSuccess(true); + } + + + /** + * 获取导出数据(主表及明细数据) + */ + @RequestCheck(CheckParams = "ModuleId") + public void GetExportData() { + // 获取请求参数 + BaseImpl bImpl = getBImpl(); + String record = bImpl.Request("record") + ""; + String leftRecord = bImpl.Request("leftRecord"); + String pms = bImpl.Request("pms"); + String detailId = bImpl.Request("detailId") + ""; + String contextMenuId = bImpl.Request("contextMenuId") + ""; + String sourceId = bImpl.Request("sourceId") + ""; + String midAnddids = bImpl.Request("midAnddids"); + + // 查询主模块数据 + response = moduleImpl.GetModuleData( + ModuleId, + record, + leftRecord, + pms, + bImpl.Request("w"), + NativeExtensionUtils.toBoolean(bImpl.Request("multi")), + NativeExtensionUtils.toBoolean(bImpl.Request("grid")), + NativeExtensionUtils.parseInt(contextMenuId), 0 + ); + + // 转换主数据列表(假设返回数据为List) + List> mainData = (List>) response.getData(); + Map responseData = new HashMap<>(); + Map midAnddidtab = new HashMap<>(); + Map ht = new HashMap<>(); + Map mainDataMap = new HashMap<>(); + List> dataList; + + // 解析midAnddids为哈希表(JSON转Map) + try { + midAnddidtab = (Map) JSON.Decode(midAnddids); + } catch (Exception e) { + // 处理JSON解析异常 + response.setSuccess(false); + response.setMsg("解析midAnddids失败: " + e.getMessage()); + return; + } + + // 处理主数据量大于2000且存在明细关联的情况(分批处理) + if (mainData.size() > 2000 && midAnddidtab != null && !midAnddidtab.isEmpty()) { + List> newData = new ArrayList<>(); + int count = mainData.size() / 2000; + int index = 1; + + // 定义外部循环标签 + outerLoop: + for (int j = 0; j < mainData.size(); j++) { + newData.add(mainData.get(j)); + + // 达到分批阈值或最后一条数据时处理 + if (j == (index * 2000) - 1 || j == mainData.size() - 1) { + for (String mid : midAnddidtab.keySet()) { + // 获取明细ID并转换为整数 + int did = NativeExtensionUtils.parseInt(midAnddidtab.get(mid).toString()); + + // 查询导出数据 + response = moduleImpl.GetExportData( + mid, + did, + newData, + index, + NativeExtensionUtils.toBoolean(bImpl.Request("multi")), + NativeExtensionUtils.toBoolean(bImpl.Request("grid")), + NativeExtensionUtils.toBoolean(bImpl.Request("isAttc")) + ); + + if (response.getOther() != null) { + Map resData = (Map) response.getData(); + + // 检查是否需要中断导出 + if (!resData.containsKey("detailData") || !resData.containsKey("mainData")) { + if (resData.containsKey("stopexport")) { + break outerLoop; // 跳出最外层循环 + } + } + + // 合并主数据 + dataList = (List>) resData.get("detailData"); + List> hashtab = (List>) resData.get("mainData"); + + if (mainDataMap.containsKey("main")) { + List> existing = (List>) mainDataMap.get("main"); + existing.addAll(hashtab); + mainDataMap.put("main", existing); + } else { + mainDataMap.put("main", hashtab); + } + } else { + dataList = (List>) response.getData(); + } + + // 合并明细数据 + Object didKey = midAnddidtab.get(mid); + if (ht.containsKey(didKey)) { + List> existingDetails = (List>) ht.get(didKey); + existingDetails.addAll(dataList); + ht.put(didKey + "", existingDetails); + } else { + ht.put(didKey + "", new ArrayList<>(dataList)); + } + } + + newData.clear(); + if (index < count) { + index++; + } + } + } + } + // 处理主数据量小于等于2000的情况 + else if (mainData.size() > 0 && midAnddidtab != null && !midAnddidtab.isEmpty()) { + // 定义循环标签 + outerLoop: + for (String mid : midAnddidtab.keySet()) { + int did = NativeExtensionUtils.parseInt(midAnddidtab.get(mid).toString()); + + response = moduleImpl.GetExportData( + mid, + did, + mainData, + 0, + NativeExtensionUtils.toBoolean(bImpl.Request("multi")), + NativeExtensionUtils.toBoolean(bImpl.Request("grid")), + NativeExtensionUtils.toBoolean(bImpl.Request("isAttc")) + ); + + if (response.getOther() != null) { + Map resData = (Map) response.getData(); + + // 检查是否需要中断导出 + if (!resData.containsKey("detailData") || !resData.containsKey("mainData")) { + if (resData.containsKey("stopexport")) { + break outerLoop; // 跳出当前循环 + } + } + + mainDataMap.put("main", resData.get("mainData")); + ht.put(midAnddidtab.get(mid) + "", resData.get("detailData")); + } else { + ht.put(midAnddidtab.get(mid) + "", response.getData()); + } + } + } + + // 组装最终响应数据 + responseData.put("mainData", mainDataMap.containsKey("main") ? mainDataMap.get("main") : mainData); + responseData.put("detailData", ht); + response.setData(responseData); + response.setSuccess(true); + } + + /** + * 将List>转换为模拟的DataTable + * + * @param list 输入的哈希表列表 + * @return 包含列名和行数据的DataTable对象 + */ + public static List> ConvertToDataTable(List> list) { +// List> dt = new ArrayList<>(); +// if (list.isEmpty()) +// return dt; +// +// for (String name : list.get(0).Keys) +// dt.Columns.Add(name); +// +// foreach(Hashtable item in list) +// dt.Rows.Add(new ArrayList(item.Values).ToArray()); +// +// return dt; + return list; + } + + + //region 地图相关接口 + + + /// + /// 获取区域城市 + /// + /// + + /// + + + @RequestCheck(CheckParams = "cityname") + public void GetArea() { + response = getMapImpl().getArea(getBImpl().Request("cityname")); + } + + + @RequestCheck(CheckParams = "cityname") + public void GetAreas() { + response = getMapImpl().getAreas(getBImpl().Request("cityname")); + } + /// + /// 加载区域树 + /// + /// + + /// + + + @RequestCheck(CheckParams = "cityname") + public void GetAreasToTree() { + response = getMapImpl().getAreasToTree(getBImpl().Request("cityname")); + } + + /// + /// 加载区域列表 + /// + /// 无 + /// + /// + /// + + /// + + + @RequestCheck(CheckParams = "id") + public void GetAreaChildren() { + response = getMapImpl().getAreaChildren(getBImpl().Request("id")); + } + /// + /// 更新区域 + /// + /// + + /// + + + @RequestCheck(CheckParams = "id,data") + public void UpdateArea() { + BaseImpl bImpl = getBImpl(); + response.setSuccess(getMapImpl().updateArea(bImpl.Request("id") + "", bImpl.Request("data") + "")); + } + + + // region 轮询有关 + //轮询审核信息 + // [RequestCheck(CheckParams = "userid")] + + @RequestCheck(Log = false) + public void GetAuditMsgTab() { + // string userid = bImpl.Request("userid") + ""; + response = moduleImpl.GetAuditMsgTab(getUser().UserId); + } + + + @RequestCheck(CheckParams = "ModuleId,idValue") + public void SeeOneAuditMsg() { + String idValue = getBImpl().Request("idValue"), remark = getBImpl().Request("remark"); + response.setData(moduleImpl.ComfirmMsg(ModuleId, idValue, remark)); + response.setSuccess(true); + } + //SeeOveOneMsg + + @RequestCheck(CheckParams = "record") + public void SeeOveOneMsg() { + String record = getBImpl().Request("record"); + Map recordHS = (Map) JSON.Decode(record); + + response = moduleImpl.SeeOneAuditMsg(getUser().UserId, recordHS); + } + + public void SeeAllAuditMsg() { + response = moduleImpl.SeeAllAuditMsg(); + } + + + @RequestCheck(CheckLogin = false) + public void GetMsg() { + BaseImpl bImpl = getBImpl(); + int msgId = ToInt32(bImpl.Request("id", bImpl.Request("msgid"))), + type = ToInt32(bImpl.Request("t", "4")); + response = moduleImpl.GetMsg(msgId, type); + } + + + // 角色权限设置 + + @RequestCheck(CheckLogin = true) + public void GetRoles() { + response.setData(moduleImpl.GetRoles()); + response.setSuccess(response.getData() != null); + } + + + @RequestCheck(CheckLogin = true, CheckParams = "roleId") + public void GetRoleUsers() { + response.setData(moduleImpl.GetRoleUsers(getBImpl().Request("roleId"))); + response.setSuccess(response.getData() != null); + } + + + @RequestCheck(CheckLogin = true) + public void GetUsers() { + BaseImpl bImpl = getBImpl(); + String name = bImpl.Request("name"), code = bImpl.Request("code"); + + response.setData(moduleImpl.GetUsers(name, code)); + response.setSuccess(response.getData() != null); + } + + + @RequestCheck(CheckLogin = true, CheckParams = "userId") + public void GetUserPrev() { + String userId = getBImpl().Request("userid"); + response = moduleImpl.GetUserPrev(userId); + } + + @RequestCheck(CheckLogin = true, CheckParams = "datas") + public void SaveRoles() { + boolean isAdd = toBoolean(getBImpl().Request("add")); + if (!isAdd) { + response = moduleImpl.SaveRolePrev(getBImpl().Request("datas") + ""); + } else { + response = moduleImpl.AddOrUpdTable(getBImpl().Request("datas") + "", "p_systemRoleSetTab", "id", null, null); + } + } + + public void SaveUserPrev() { + response = moduleImpl.SaveUserPrev(getBImpl().Request("datas") + ""); + } + + + @RequestCheck(CheckLogin = true, CheckParams = "idValue") + public void DelRole() { + response = moduleImpl.DelRoles(getBImpl().Request("idValue")); + } + + + @RequestCheck(CheckLogin = true, CheckParams = "idValue") + public void DelRoleUser() { + response = moduleImpl.DelRoleUser(getBImpl().Request("idValue")); + } + + + @RequestCheck(CheckLogin = true, CheckParams = "datas") + public void SaveRoleUser() { + response = moduleImpl.SaveRoleUser(getBImpl().Request("datas")); + } + + + // 评论 + + public void SaveComment() { + response = moduleImpl.AddOrUpdTable(getBImpl().Request("record"), "P_SystemCheckCommentTab", "id", null, null); + } + + public void GetCommentList() { + response.setData(moduleImpl.GetCommentList(ModuleId, getBImpl().Request("idValue"), getBImpl().Request("stepCode"))); + response.setSuccess(true); + } + + /** + * 桌面应用APis + */ + @RequestCheck(CheckLogin = true) + public void GetBSDesktopData() { + response = moduleImpl.GetBSDesktopData(); + } + + @RequestCheck(CheckLogin = true, CheckParams = "dllcoid") + public void GetBSDesktopModuleData() { + BaseImpl bImpl = getBImpl(); + String dllcoid = bImpl.Request("dllcoid"), queryVal = bImpl.Request("queryVal"); + response = moduleImpl.GetBSDesktopModuleData(dllcoid, queryVal); + } + + @RequestCheck(CheckLogin = true) + public void GetBSDesktopAdminBase() { + if (!Objects.equals(getUser().UserName, "管理员")) { + response.setMsg("没有权限访问!"); + response.setSuccess(false); + } else { + response = moduleImpl.GetBSDesktopAdminBase(); + } + } + + @RequestCheck(CheckLogin = true) + public void GetBSDesktopExtend() { + response = moduleImpl.GetBSDesktopExtend(); + } + + + @RequestCheck(CheckLogin = true, CheckParams = "dllcoid") + public void GetDesktopModuleOper() { + String dllcoid = getBImpl().Request("dllcoid"); + response = moduleImpl.GetDesktopModuleOper(dllcoid); + } + + /// + /// 更新BI表中配置项目 + /// + + @RequestCheck(CheckParams = "datas,table") + public void UpdBSDesktop() { + BaseImpl bImpl = getBImpl(); + String recsStr = bImpl.Request("datas"); + boolean isAdd = "1".equals(bImpl.Request("isAdd", "0")); + String idField = bImpl.Request("idField"); + String table = bImpl.Request("table", ""); + + // 定义表名映射字典 + Map tables = new HashMap<>(); + tables.put("admin", "P_SystemFirstPageSetTab"); + tables.put("extend", "P_SystemOperFirstPageTab"); + tables.put("module", "P_SystemDllFirstPageTab"); + tables.put("moduleOper", "P_SystemDllOperFirstPageTab"); + + if (tables.containsKey(table)) { + // 处理extend和module类型的记录 + if ("extend".equals(table) || "module".equals(table)) { + List> tempList = (List>) JSON.Decode(recsStr); + for (Map rec : tempList) { + rec.put("deleted", false); + } + recsStr = JSON.Encode(tempList); + } + + // 获取主键信息并处理 + List idFieldsA = moduleImpl.GetPrimaryKeysArray(tables.get(table)); +// System.out.println("idFieldsA: " + idFieldsA); + String idF = moduleImpl.GetIdentityField(tables.get(table)); +// System.out.println("idF: " + idF); + if (!idFieldsA.contains(idF)) { + idFieldsA.add(idF); + } + + // 拼接主键字符串(小写) + String idFieldsStr = String.join(",", idFieldsA).toLowerCase(); +// System.out.println("idFieldsStr: " + idFieldsStr); + response = moduleImpl.AddOrUpdTable(recsStr, tables.get(table), idFieldsStr, isAdd, null); + } else { + response.setSuccess(false); + response.setMsg("错误的table参数"); + } + } + + /** + * 删除BI表中配置项目 + */ + // 假设使用自定义注解实现参数校验 + @RequestCheck(CheckParams = "table,ids") + public void DeleteBSDesktop() { + BaseImpl bImpl = getBImpl(); + String ids = bImpl.Request("ids", ""); + String[] idsA = ids.split(","); + // 拼接SQL用的ids字符串(带单引号) + String idsStr = "'" + String.join("','", idsA) + "'"; + String table = bImpl.Request("table", ""); + + // 定义表名映射字典 + Map tables = new HashMap<>(); + tables.put("admin", "P_SystemFirstPageSetTab"); + tables.put("extend", "P_SystemOperFirstPageTab"); + tables.put("module", "P_SystemDllFirstPageTab"); + + if (tables.containsKey(table)) { + boolean result = false; + String targetTable = tables.get(table); + + switch (table) { + case "extend": + // 更新extend类型记录为删除状态 + String updateSql = String.format("update %s set [deleted]='True' where id in (%s)", targetTable, idsStr); + result = jdbcTemplate.update(updateSql) > 0; + break; + case "admin": + // 直接删除admin类型记录 + String deleteAdminSql = String.format("delete from %s where id in (%s)", targetTable, idsStr); + result = jdbcTemplate.update(deleteAdminSql) > 0; + break; + case "module": + // 直接删除module类型记录 + String deleteModuleSql = String.format("delete from %s where dllcoid in (%s)", targetTable, idsStr); + result = jdbcTemplate.update(deleteModuleSql) > 0; + break; + default: + response.setSuccess(false); + response.setMsg("不支持的table类型"); + return; + } + + response.setData(result); + response.setSuccess(true); + response.setMsg("删除成功"); + } else { + response.setSuccess(false); + response.setMsg("错误的table参数"); + } + } + + /// + /// 获得主页搜索的结果。 + /// + public void GetDeskQueryResult() { + BaseImpl bImpl = getBImpl(); + String keyField = bImpl.Request("textField"), + queryText = bImpl.Request(keyField); + response = moduleImpl.GetDeskQueryResult(queryText); + } + + /// + /// 检查点击origin模块是基础模块 还是 单据 + /// + public void CheckModuleOrBill() { + String moduleId = getBImpl().Request("moduleId"); + response = moduleImpl.CheckModuleOrBill(moduleId); + } + + public void ConvertDllName() { + String dllName = getBImpl().Request("dllName"); + response = moduleImpl.ConvertDllName(dllName); + } + + /// + /// 模板页管理数据 + /// + + @RequestCheck(CheckLogin = true) + public void GetDesktopModuleLeft() { + response = moduleImpl.GetDesktopModuleLeft(); + } + + @RequestCheck(CheckLogin = true) + public void GetDesktopModuleMain() { + String leftRecordStr = getBImpl().Request("leftRecord"); + if (!isNullOrEmpty(leftRecordStr)) { + Map leftRecordHS = (Map) JSON.Decode(leftRecordStr); + String dllcoid = leftRecordHS.get("dllcoid") + ""; + response = moduleImpl.GetDesktopModuleMain(dllcoid); + } else { + response.setData(new ArrayList<>()); + response.setSuccess(true); + } + } + + @RequestCheck(CheckLogin = true) + public void GetDeskTopCommonUse() { + String cardIds = getBImpl().Request("id") + ""; + + if (cardIds.contains(",")) { + Map datas = new HashMap<>(); + String[] idArray = cardIds.split(","); + for (String strId : idArray) { + int cardId = Integer.parseInt(strId); + datas.put(strId, moduleImpl.GetDeskTopCommonUse(cardId)); + } + response.setData(datas); + } else { + int cardId = Integer.parseInt(cardIds); + response.setData(moduleImpl.GetDeskTopCommonUse(cardId)); + } + response.setSuccess(response.getData() != null); + } + + + @RequestCheck(CheckLogin = true) + public void UpdCommonUse() { + BaseImpl bImpl = getBImpl(); + String adds = bImpl.Request("adds"), dels = bImpl.Request("dels"); + int cardId = ToInt32(bImpl.Request("id")); + response = moduleImpl.UpdCommonUse(cardId, adds, dels); + } + + + @RequestCheck(CheckParams = "ModuleId") + public void GetClientSaveConds() { + response = moduleImpl.GetClientSaveConds(ModuleId); + } +} + diff --git a/WebErp/weberp/src/main/java/org/example/ModuleApi/ModuleAjaxApi/dto/module/ModuleIdFieldDTO.java b/WebErp/weberp/src/main/java/org/example/ModuleApi/ModuleAjaxApi/dto/module/ModuleIdFieldDTO.java new file mode 100644 index 0000000..01a1f18 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/ModuleApi/ModuleAjaxApi/dto/module/ModuleIdFieldDTO.java @@ -0,0 +1,67 @@ +package org.example.ModuleApi.ModuleAjaxApi.dto.module; + +public class ModuleIdFieldDTO { + private String leftUnionField; + private Boolean IsSpecModule; + private String specSql; + private String parmaryKey; + private String AppluAble; + + // 构造函数 + + + public ModuleIdFieldDTO(String leftUnionField, Boolean isSpecModule, String specSql, String parmaryKey) { + this.leftUnionField = leftUnionField; + IsSpecModule = isSpecModule; + this.specSql = specSql; + this.parmaryKey = parmaryKey; + } + + public String getLeftUnionField() { + return leftUnionField; + } + + public void setLeftUnionField(String leftUnionField) { + this.leftUnionField = leftUnionField; + } + + public Boolean getIsSpecModule() { + return IsSpecModule; + } + + public void setIsSpecModule(Boolean isSpecModule) { + IsSpecModule = isSpecModule; + } + + public String getSpecSql() { + return specSql; + } + + public void setSpecSql(String specSql) { + this.specSql = specSql; + } + + public String getParmaryKey() { + return parmaryKey; + } + + public void setParmaryKey(String parmaryKey) { + this.parmaryKey = parmaryKey; + } + + public Boolean getSpecModule() { + return IsSpecModule; + } + + public void setSpecModule(Boolean specModule) { + IsSpecModule = specModule; + } + + public String getAppluAble() { + return AppluAble; + } + + public void setAppluAble(String appluAble) { + AppluAble = appluAble; + } +} diff --git a/WebErp/weberp/src/main/java/org/example/ModuleApi/ModuleAjaxApi/mapper/CRMapper.java b/WebErp/weberp/src/main/java/org/example/ModuleApi/ModuleAjaxApi/mapper/CRMapper.java new file mode 100644 index 0000000..9df70c7 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/ModuleApi/ModuleAjaxApi/mapper/CRMapper.java @@ -0,0 +1,170 @@ +package org.example.ModuleApi.ModuleAjaxApi.mapper; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; + +@Mapper +public interface CRMapper { + List> getModuleIdFieldRow(@Param("keyName") String keyName, @Param("tab") String key, @Param("labTreeType") int labTreeType); + + + int getAuditStepCount(@Param("moduleId") String moduleId, @Param("isBase") boolean isBase); + + + + /** + * 查询表格列配置数据 + */ + List> GetColumnRows( + @Param("windowsDirver") boolean windowsDirver, + @Param("userId") String userId, + @Param("baseMainGridViewPrefix") String baseMainGridViewPrefix, + @Param("userName") String userName, + @Param("moduleId") String moduleId, + @Param("id") int id + ); + + /** + * 从数据库获取条件数据 + */ + List> GetCondition( + @Param("fromkey") String fromkey, + @Param("id") Integer id, + @Param("windowsDirver") boolean windowsDirver); + + + + /** + * 获取账单详情列数据 + */ + List> GetBillDetailColumns( + @Param("moduleCode") String moduleCode, + @Param("userId") String userId, + @Param("username") String username, + @Param("id") int id, + @Param("windowsDirver") boolean windowsDirver); + + + + /** + * 获取账单的主表数据 + */ + List> GetBillMasterRows( + @Param("moduleId") String moduleId, + @Param("userName") String userName, + @Param("id") int id, + @Param("windowsDirver") boolean windowsDirver); + + + + /** + * 获取账单模块信息 + * + * @param moduleCode 模块代码 + * @param menuId 菜单 ID + * @return 账单模块信息 + */ + Map GetBillModule(@Param("moduleCode") String moduleCode, @Param("menuId") String menuId); + + + /** + * 获取控件行数据 + * + * @param fromkey 表单键 + * @param userName 用户名 + * @param moduleId 模块ID + * @param fieldId 字段ID + * @return 控件行数据列表 + */ + List> GetControlRows(@Param("fromkey") Object fromkey, @Param("userName") String userName, @Param("moduleId") String moduleId, @Param("fieldId") Integer fieldId); + + + + /** + * 根据菜单 ID 查询系统弹出菜单信息 + * + * @param menuid 菜单 ID + * @param fromkey 表单键 + * @param username 用户名 + * @return 菜单信息列表 + */ + List> selectSystemPopupMenuById(@Param("menuid") int menuid, @Param("fromkey") String fromkey, @Param("username") String username); + + + /** + * 根据菜单类型查询系统弹出菜单信息 + * + * @param menutype 菜单类型 + * @param fromkey 表单键 + * @param username 用户名 + * @param windowsDirver 是否为 Windows 驱动 + * @return 菜单信息列表 + */ + List> selectSystemPopupMenuByType(@Param("menutype") int menutype, + @Param("fromkey") String fromkey, + @Param("username") String username, + @Param("windowsDirver") boolean windowsDirver); + + + /** + * 查询单据来源信息 + * + * @param typeCode 模块代码(对应原C#的@typeCode参数) + * @param condition 动态拼接的查询条件 + * @return 单据来源信息列表 + */ + List> getBillSource( + @Param("typeCode") String typeCode, + @Param("condition") String condition + ); + + + /** + * 获取单据来源列信息 + * + * @param sourceId 来源ID + * @param userId 用户ID + * @param billSourceGridView 单据来源表格前缀(来自枚举) + * @return 列信息列表 + */ + List> getBillSourceColumns( + @Param("sourceId") String sourceId, + @Param("userId") String userId, + @Param("billSourceGridView") String billSourceGridView + ); + + + + /** + * 获取单据来源明细列信息 + * + * @param sourceId 来源ID + * @param billSourceDetailGridView 单据来源明细表格前缀(来自枚举) + * @param userId 用户ID + * @return 明细列信息列表 + */ + List> getBillSourceDetailColumns( + @Param("sourceId") String sourceId, + @Param("billSourceDetailGridView") String billSourceDetailGridView, + @Param("userId") String userId + ); + + + + + /** + * 客户分类设置 + * 根据模块代码和菜单ID获取基础模块配置信息 + * + * @param moduleCode 模块代码 + * @param menuId 菜单ID + * @return 模块配置信息 + */ + + List> GetBaseModule(@Param("moduleCode") String moduleCode, @Param("menuId") String menuId); + + } + diff --git a/WebErp/weberp/src/main/java/org/example/ModuleApi/ModuleAjaxApi/mapper/DMCrmMapper.java b/WebErp/weberp/src/main/java/org/example/ModuleApi/ModuleAjaxApi/mapper/DMCrmMapper.java new file mode 100644 index 0000000..83f9d4d --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/ModuleApi/ModuleAjaxApi/mapper/DMCrmMapper.java @@ -0,0 +1,64 @@ +package org.example.ModuleApi.ModuleAjaxApi.mapper; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; + +@Mapper +public interface DMCrmMapper { + + List> getModuleIdFieldRow(@Param("keyName") String keyName, @Param("tab") String key, @Param("labTreeType") int labTreeType); + + List> GetCondition( + @Param("fromkey") String fromkey, + @Param("id") Integer id, + @Param("windowsDirver") boolean windowsDirver); + + int getAuditStepCount(@Param("moduleId") String moduleId, @Param("isBase") boolean isBase); + + List> GetBillDetailColumns( + @Param("moduleCode") String moduleCode, + @Param("userId") String userId, + @Param("username") String username, + @Param("id") int id, + @Param("windowsDirver") boolean windowsDirver); + + List> GetBillMasterRows( + @Param("moduleId") String moduleId, + @Param("userName") String userName, + @Param("id") int id, + @Param("windowsDirver") boolean windowsDirver); + + Map GetBillModule(@Param("moduleCode") String moduleCode, @Param("menuId") String menuId); + + List> GetControlRows(@Param("fromkey") Object fromkey, @Param("userName") String userName, @Param("moduleId") String moduleId, @Param("fieldId") Integer fieldId); + + List> selectSystemPopupMenuById(@Param("menuid") int menuid, @Param("fromkey") String fromkey, @Param("username") String username); + + List> selectSystemPopupMenuByType(@Param("menutype") int menutype, + @Param("fromkey") String fromkey, + @Param("username") String username, + @Param("windowsDirver") boolean windowsDirver); + + List> getBillSource( + @Param("typeCode") String typeCode, + @Param("condition") String condition + ); + + List> getBillSourceColumns( + @Param("sourceId") String sourceId, + @Param("userId") String userId, + @Param("billSourceGridView") String billSourceGridView + ); + + List> getBillSourceDetailColumns( + @Param("sourceId") String sourceId, + @Param("billSourceDetailGridView") String billSourceDetailGridView, + @Param("userId") String userId + ); + + List> GetBaseModule(@Param("moduleCode") String moduleCode, @Param("menuId") String menuId); + +} diff --git a/WebErp/weberp/src/main/java/org/example/Office/CreateWordUtil.java b/WebErp/weberp/src/main/java/org/example/Office/CreateWordUtil.java new file mode 100644 index 0000000..072a137 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Office/CreateWordUtil.java @@ -0,0 +1,1734 @@ +package org.example.Office; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.spire.doc.*; +import com.spire.doc.documents.*; +import com.spire.doc.fields.DocPicture; +import com.spire.doc.fields.TextBox; +import com.spire.doc.fields.TextRange; +import com.spire.doc.formatting.CharacterFormat; +import com.spire.doc.formatting.ParagraphFormat; +import org.apache.commons.io.FilenameUtils; +import org.example.Utils.NativeExtensionUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.*; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static org.example.Utils.NativeExtensionUtils.TrimEnd; +import static org.example.Utils.NativeExtensionUtils.isNullOrEmpty; + +@Component +public class CreateWordUtil { + private static final Logger log = LoggerFactory.getLogger(CreateWordUtil.class); + + + @Autowired + private static JdbcTemplate jdbcTemplate; + + public static boolean Create(String mainTempPath, String menuTempPath, String menuTempArray, + List>>> dataSets, String savePath, String OAUrl, + StringBuilder msg) { + // 调用原始方法,传入默认参数 false + return Create(mainTempPath, menuTempPath, menuTempArray, dataSets, savePath, OAUrl, msg, false); + } + public static boolean Create(String mainTempPath, String menuTempPath, String menuTempArray, + List>>> dataSets, String savePath, String OAUrl, + StringBuilder msg, boolean createPdf) { + msg.setLength(0); // 初始化消息(对应C#的msg = "") + boolean result = false; + Document saveDocument = null; + + try { + saveDocument = new Document(); // 初始化文档对象(对应C#的new Document()) + + // 遍历数据集,调用工具类方法填充文档内容(与C# foreach逻辑一致) + for (List>> dataSet : dataSets) { // 每个元素是单个 DataSet 的替代者 + InitTempDocuemnt(saveDocument, mainTempPath, menuTempPath, menuTempArray, dataSet); + InitCreatBaseSql(saveDocument, dataSet, OAUrl); + InitCreatBaseSqlDetail(saveDocument, dataSet, OAUrl); + InitCreatTabSql(saveDocument, dataSet, OAUrl); + InitCreatTabSqlDetail(saveDocument, dataSet, OAUrl); + RemoveTableRow(saveDocument); + } + + // 确保保存目录存在(对应C#的Directory.Exists和CreateDirectory) + File saveDir = new File(savePath).getParentFile(); + if (!saveDir.exists()) { + saveDir.mkdirs(); // 递归创建目录 + } + + // 构建PDF保存路径(对应C#的fileName拼接) + String fileName = saveDir.getPath() + File.separator + + new File(savePath).getName().replaceFirst("\\.doc$", ".pdf"); + + // 保存为Word文档(对应C#的SaveToFile(savePath, FileFormat.Doc)) + saveDocument.saveToFile(savePath, FileFormat.Doc); + + // 若需要生成PDF,则额外保存(对应C#的if (createPdf)逻辑) + if (createPdf) { + saveDocument.saveToFile(fileName, FileFormat.PDF); + } + + result = true; + msg.append("生成成功"); // 对应C#的msg += "生成成功" + + } catch (Exception ex) { + // 异常处理(对应C#的catch块) + msg.append("生成失败\r\n").append(ex.getMessage()).append("\r\n"); + result = false; + } finally { + // 释放资源(对应C#的Close和Dispose) + if (saveDocument != null) { + saveDocument.close(); + saveDocument.dispose(); + } + } + + return result; + } + + + private static Document InitTempDocuemnt(Document document, String mainTempPath, String menuTempPath, String menuTempArray, List>> dataSet) { + if (document == null){ + document = new Document(); + } + boolean hasMainTemp = false; + try{ + if(!NativeExtensionUtils.isNullOrEmpty(mainTempPath) && new File(mainTempPath).exists()){ + Section mainSection = new Section(document); + mainSection.getPageSetup().setDifferentFirstPageHeaderFooter(false); + document.getSections().add(mainSection); + hasMainTemp = true; + + Document document2 = new Document(); + document2.loadFromFile(mainTempPath); + + for (Section section : (Iterable
) document2.getSections()) { + // 复制页面方向、大小、边距 + mainSection.getPageSetup().setOrientation(section.getPageSetup().getOrientation()); + mainSection.getPageSetup().setPageSize(section.getPageSetup().getPageSize()); + mainSection.getPageSetup().setMargins(section.getPageSetup().getMargins()); + // 复制页眉页脚 + CpoyHeaderFooter(section, mainSection, HeaderFooterType.Footer_Even); + CpoyHeaderFooter(section, mainSection, HeaderFooterType.Footer_First_Page); + CpoyHeaderFooter(section, mainSection, HeaderFooterType.Footer_Odd); + CpoyHeaderFooter(section, mainSection, HeaderFooterType.Header_Even); + CpoyHeaderFooter(section, mainSection, HeaderFooterType.Header_First_Page); + CpoyHeaderFooter(section, mainSection, HeaderFooterType.Header_Odd); + + // 遍历Section.Body的子对象并克隆添加 + for (int i = 0; i < section.getBody().getChildObjects().getCount(); i++) { + DocumentObject childObject = section.getBody().getChildObjects().get(i); + mainSection.getBody().getChildObjects().add(childObject.deepClone()); + } + } + } + else { + Section mainSection = new Section(document); + mainSection.getPageSetup().setDifferentFirstPageHeaderFooter(false); + document.getSections().add(mainSection); + } + if(!NativeExtensionUtils.isNullOrEmpty(menuTempPath) && new File(menuTempPath).exists()){ + if(hasMainTemp){ + document.getLastSection().getPageSetup().setDifferentFirstPageHeaderFooter(true); + } + Document document3 = new Document(); + document3.loadFromFile(menuTempPath); + if(!NativeExtensionUtils.isNullOrEmpty(menuTempPath)){ + List ModelStr = new ArrayList<>(Arrays.asList(menuTempArray.split(","))); + List AddStrList = new ArrayList<>(); + for(int i = 0; i= 2) { + List> dataTable = dataSet.get(1); + if (!dataTable.isEmpty() + && dataTable.get(0).containsKey("crm_lco_xmbg") + && dataTable.get(0).containsKey("crm_lco_order")) { + // 遍历DataTable的每一行(DataRow → Map) + for (Map item : dataTable) { + String crmLcoXmbgVal = String.valueOf(item.get("crm_lco_xmbg")); + String crmLcoOrderVal = String.valueOf(item.get("crm_lco_order")); + String modelStrVal = ModelStr.get(i); + String orderVal = String.valueOf(order); + + // 条件判断(等价于C#的Equals) + if (crmLcoXmbgVal.equals(modelStrVal) && crmLcoOrderVal.equals(orderVal)) { + item.put("crm_lco_xmbg", "XMBG_" + order + "_" + index); + } + } + } + } + } + String[] moduleIdWithPms = moduleIdStr.split("_"); + String moduleId = moduleIdWithPms[0]; + String isBreakPage = moduleIdWithPms.length > 1 ? moduleIdWithPms[1] : ""; + TextSelection startSelection = document3.findString("{" + moduleId + ":start}", false, true); + TextSelection endSelection = document3.findString("{" + moduleId + ":end}", false, true); + if (startSelection != null && endSelection != null) { + if (moduleId != null && moduleId.equalsIgnoreCase("yw")) { + Paragraph lastBreakParagraph = null; + DocumentObject lastBreak = null; + Section lastSection = document.getLastSection(); + for (Object obj : lastSection.getParagraphs()) { + if (obj instanceof Paragraph) { + Paragraph paragraph = (Paragraph) obj; + + // 内层循环变量名改为objChild(遍历getChildObjects),避免重复 + for (Object objChild : paragraph.getChildObjects()) { + if (objChild instanceof DocumentObject) { + DocumentObject docObject = (DocumentObject) objChild; + + if (docObject.getDocumentObjectType() == DocumentObjectType.Break) { + if (docObject instanceof Break) { + Break breakObject = (Break) docObject; + if (breakObject.getBreakType() == BreakType.Page_Break) { + lastBreakParagraph = paragraph; + lastBreak = docObject; + } + } + } + } + } + } + } + if(lastBreakParagraph != null && lastBreak != null){ + lastBreakParagraph.getChildObjects().remove(lastBreak); + } + } + Section section = (Section) startSelection.getAsOneRange().getOwnerParagraph().getOwnerTextBody().getOwner(); + if (section.getPageSetup().getOrientation() != document.getLastSection().getPageSetup().getOrientation()) { + // 插入分节符 + document.getLastSection().getParagraphs().get(document.getLastSection().getParagraphs().getCount() - 1) + .insertSectionBreak(SectionBreakType.No_Break); +// 设置页面方向 + document.getLastSection().getPageSetup().setOrientation(section.getPageSetup().getOrientation()); +// 设置页面大小 + document.getLastSection().getPageSetup().setPageSize(section.getPageSetup().getPageSize()); +// 设置页面边距 + document.getLastSection().getPageSetup().setMargins(section.getPageSetup().getMargins()); + } + Section mainSection = document.getLastSection(); + if(!moduleId.equalsIgnoreCase("yw")){ + CpoyHeaderFooter(section,mainSection,HeaderFooterType.Footer_Even); + CpoyHeaderFooter(section,mainSection,HeaderFooterType.Footer_Odd); + CpoyHeaderFooter(section,mainSection,HeaderFooterType.Header_Even); + CpoyHeaderFooter(section,mainSection,HeaderFooterType.Header_Odd); + } + boolean isCopying = false; + for (Object childObj : section.getBody().getChildObjects()) { + if (childObj instanceof DocumentObject) { + DocumentObject obj = (DocumentObject) childObj; + if (obj == startSelection.getAsOneRange().getOwnerParagraph()) { + isCopying = true; + continue; + } + // 判断是否为结束标记段落 + if (obj == endSelection.getAsOneRange().getOwnerParagraph()) { + break; + } + // 复制内容到目标区域 + if (isCopying) { + DocumentObject documentObject = obj.deepClone(); // 深克隆对象 + // 将克隆的内容添加到mainSection的Body子对象中 + mainSection.getBody().getChildObjects().add(documentObject); + } + } + } + } + // 先判空避免空指针,再判断长度+以XMBG开头(序数规则忽略大小写) + if (moduleIdStr != null && moduleIdStr.length() == 8 + && moduleIdStr.substring(0, 4).equalsIgnoreCase("XMBG")) { + // 替代replaceTextRange:查找文本并替换(匹配整词、忽略大小写) + TextSelection selection; + while ((selection = document.findString(moduleIdStr, false, true)) != null) { + selection.getAsOneRange().setText("XMBG_" + order + "_" + index); + } + + // 保留原有的replace方法(双重保障,也可仅用上面的循环) + document.replace(moduleIdStr, "XMBG_" + order + "_" + index, false, true); + } + } + } + } + + } catch (Exception e) { + throw new RuntimeException(e); + } + return document; + } + + + private static void InitCreatBaseSql(Document document, List>> dataSet, String OAUrl) { + for (int i = 0; i < dataSet.size(); i++) { + String data = "Data" + (i + 1); + List> dataTable = dataSet.get(i); // 对应C#的dataSet.Tables[i] + + // 获取列名集合(对应C#的dataSet.Tables[i].Columns) + Set columnNames = dataTable.isEmpty() ? Set.of() : dataTable.get(0).keySet(); + for (String dataColumn : columnNames) { // 遍历列名 + // 获取第一行数据(对应C#的dataTable.Rows[0]) + Map dataRow = dataTable.size() > 0 ? dataTable.get(0) : null; + + // 处理!{DataX.Column}占位符 + String speciesItem = "!{" + data + "." + dataColumn + "}"; + TextSelection[] speciesContains = document.findAllString(speciesItem, false, true); + + if (speciesContains != null && speciesContains.length > 0) { + String cellValue = dataRow.get(dataColumn) == null ? "" : dataRow.get(dataColumn).toString(); + + if (cellValue.equals("/")) { + replaceTextRange(document,speciesItem, "!/", false, true); + document.replace(speciesItem, "!/", false, true); + } else { + replaceTextRange(document,speciesItem, cellValue, false, true); + document.replace(speciesItem, cellValue, false, true); + } + // 处理{DataX.Column}占位符 + String item = "{" + data + "." + dataColumn + "}"; + TextSelection[] contains = document.findAllString(item, false, true); + if (contains != null && contains.length > 0) { + if(dataRow != null){ + String columnValue = dataRow.get(dataColumn) + ""; + // 处理图片列(img_前缀) + if (dataColumn.contains("img_")) { + Image image = null; + Dimension size = new Dimension(200, 200); + String picUrl = columnValue; + if (picUrl.startsWith("@") && jdbcTemplate != null){ + List> picTable = jdbcTemplate.queryForList(picUrl.substring(1)); + if (picTable != null && !picTable.isEmpty()){ + Map picRow = picTable.get(0); + String path = picRow.containsKey("value") ? (picRow.get("value") + "") : ""; + int width = 200; + int height = 200; + + if (picRow.containsKey("width")) { + try { + width = Integer.parseInt(picRow.get("width") + ""); + } catch (NumberFormatException e) { + width = 200; + } + } + if (picRow.containsKey("height")) { + try { + height = Integer.parseInt(picRow.get("height") + ""); + } catch (NumberFormatException e) { + height = 200; + } + } + size = new Dimension(width, height); + image = InitCreatImage(path, OAUrl); + } + } + else { + image = InitCreatImage(picUrl, OAUrl); + if (image == null) { + image = Base64ToImage(columnValue); + } + } + TextSelection[] selections = document.findAllString(item, false, true); + for (TextSelection selection : selections) { + if (image != null && image instanceof BufferedImage) { + // 获取占位符所在的段落和位置 + TextRange range = selection.getAsOneRange(); + Paragraph paragraph = range.getOwnerParagraph(); + int index = paragraph.getChildObjects().indexOf(range); + + // 1. 移除占位符文本 + paragraph.getChildObjects().remove(range); + + // 2. 插入图片(Java版通过appendPicture创建DocPicture) + DocPicture picture = paragraph.appendPicture((BufferedImage) image); + picture.setWidth(size.width); // 设置宽度 + picture.setHeight(size.height); // 设置高度 + + // 若需要调整图片位置(确保插入到原占位符位置) + paragraph.getChildObjects().remove(picture); // 移除append的默认位置 + paragraph.getChildObjects().insert(index, picture); // 插入到原占位符位置 + } else { + // 无图片时移除占位符 + TextRange range = selection.getAsOneRange(); + Paragraph paragraph = range.getOwnerParagraph(); + int index = paragraph.getChildObjects().indexOf(range); + paragraph.getChildObjects().remove(range); + } + } + } else if (dataColumn.contains("img1_")) { + TextSelection[] selections = document.findAllString(item, false, true); + for (TextSelection selection : selections) { + TextRange range = selection.getAsOneRange(); + Paragraph paragraph = range.getOwnerParagraph(); + Object owner = paragraph.getOwnerTextBody().getOwner(); + if (owner instanceof TextBox) { + TextBox textBox = (TextBox) owner; + textBox.getFormat().getInternalMargin().setAll(0); + Image image = InitCreatImage(columnValue, OAUrl); + if (image == null) { + image = Base64ToImage(columnValue); + } + Paragraph lastParagraph = GeObjectParagraph(document, textBox); + if (image != null && lastParagraph != null) { + // Java版需调整枚举类路径和方法名(setter) + lastParagraph.getFormat().setHorizontalAlignment(HorizontalAlignment.Center); + DocPicture picture = lastParagraph.appendPicture((BufferedImage) image); + picture.setWidth((int) textBox.getWidth()); + picture.setHeight((int) textBox.getHeight()); + picture.setTextWrappingStyle(TextWrappingStyle.In_Front_Of_Text); + picture.setTextWrappingType(TextWrappingType.Both); + picture.setHorizontalPosition(textBox.getHorizontalPosition()); + picture.setVerticalPosition(textBox.getVerticalPosition()); + lastParagraph.getChildObjects().remove(textBox); + } + else if(lastParagraph != null){ + // Java版需调整枚举类路径和方法名(setter) + lastParagraph.getFormat().setHorizontalAlignment(HorizontalAlignment.Center); + lastParagraph.getChildObjects().remove(textBox); + } + } + } + } + } + else { + replaceTextRange(document, item, dataRow.get(dataColumn) + "",false,true); + } + } + else { + replaceTextRange(document, item, "",false,true); + } + } + } + } + } + + private static void InitCreatBaseSqlDetail(Document document, List>> dataSet, String OAUrl){ + if (dataSet.size() < 2) { + return; + } + List> detailTab = dataSet.get(1); + if (!detailTab.isEmpty() && detailTab.get(0).containsKey("crm_lco_xmbg")){ + // 分组去重:按"crm_lco_xmbg"分组,取每组第一个元素(对应C#的GroupBy+First) + List> distinctTable = detailTab.stream() + .collect(Collectors.groupingBy(row -> row.get("crm_lco_xmbg").toString())) + .values().stream() + .map(group -> group.get(0)) + .collect(Collectors.toList()); + for (Map dataRow : distinctTable){ + String pargTag = dataRow.get("crm_lco_xmbg").toString(); + // 获取列名集合(对应C#的distinctTable.Columns) + Set columnNames = dataRow.keySet(); + for (String dataColumn : columnNames){ + String speciesItem = "!{" + pargTag + "." + dataColumn + "}"; + TextSelection[] speciesContains = document.findAllString(speciesItem, false, true); + if (speciesContains != null && speciesContains.length > 0) { + String cellValue = dataRow.get(dataColumn).toString(); + if ("/".equals(cellValue)) { + replaceTextRange(document, speciesItem, "!/",false,true); + document.replace(speciesItem, "!/", false, true); + } else { + replaceTextRange(document, speciesItem, cellValue, false, true); + document.replace(speciesItem, cellValue, false, true); + } + } + String item = "{" + pargTag + "." + dataColumn + "}"; + TextSelection[] contains = document.findAllString(item, false, true); + if (contains != null && contains.length > 0){ + if(dataColumn.contains("img_")){ + Image image = null; + Dimension size = new Dimension(200, 200); + String picUrl = dataRow.get(dataColumn).toString(); + if(picUrl.startsWith("@") && jdbcTemplate != null){ + List> picTable = jdbcTemplate.queryForList(picUrl.substring(1)); + if (picTable != null && picTable.size() > 0) { + Map picRow = picTable.get(0); + String path = picRow.containsKey("value") ? picRow.get("value").toString() : ""; + int width = 200; + if (picRow.containsKey("width")) { + try { + width = Integer.parseInt(picRow.get("width").toString()); + } catch (NumberFormatException e) { + width = 200; + } + } + + int height = 200; + if (picRow.containsKey("height")) { + try { + height = Integer.parseInt(picRow.get("height").toString()); + } catch (NumberFormatException e) { + height = 200; + } + } + + size = new Dimension(width, height); + image = InitCreatImage(path, OAUrl); + } + } + else { + image = InitCreatImage(picUrl,OAUrl); + if(image == null){ + image = Base64ToImage(dataRow.get(dataColumn).toString()); + } + } + TextSelection[] selections = document.findAllString(item, false, true); + for (TextSelection selection : selections) { + if (image != null) { + TextRange range = selection.getAsOneRange(); + Paragraph paragraph = range.getOwnerParagraph(); + int index = paragraph.getChildObjects().indexOf(range); + paragraph.getChildObjects().remove(range); + DocPicture picture = paragraph.appendPicture((BufferedImage) image); + picture.setWidth(size.width); + picture.setHeight(size.height); + paragraph.getChildObjects().remove(picture); + paragraph.getChildObjects().insert(index, picture); + } + } + } + else if(dataColumn.contains("img1_")){ + TextSelection[] selections = document.findAllString(item, false, true); + for (TextSelection selection : selections){ + TextRange range = selection.getAsOneRange(); + Paragraph paragraph = range.getOwnerParagraph(); + Object owner = paragraph.getOwnerTextBody().getOwner(); + if (owner instanceof TextBox) { + TextBox textBox = (TextBox) owner; + textBox.getFormat().getInternalMargin().setAll(0); + Image image = InitCreatImage(dataRow.get(dataColumn).toString(), OAUrl); + if (image == null) { + image = Base64ToImage(dataRow.get(dataColumn).toString()); + } + if (image != null){ + Paragraph lastParagraph = GeObjectParagraph(document, textBox); + lastParagraph.getFormat().setHorizontalAlignment(HorizontalAlignment.Center); + + DocPicture picture = lastParagraph.appendPicture((BufferedImage) image); + picture.setWidth((int) textBox.getWidth()); + picture.setHeight((int) textBox.getHeight()); + picture.setTextWrappingStyle(TextWrappingStyle.In_Front_Of_Text); + picture.setTextWrappingType(TextWrappingType.Both); + picture.setHorizontalPosition(textBox.getHorizontalPosition()); + picture.setVerticalPosition(textBox.getVerticalPosition()); + + lastParagraph.getChildObjects().remove(textBox); + } + } + } + } + else { + replaceTextRange(document,item,dataRow.get(dataColumn).toString(),false,true); + document.replace(item, dataRow.get(dataColumn).toString(), false, true); + } + } + } + } + } + } + + private static void InitCreatTabSql(Document document, List>> dataSet, String OAUrl){ + // Dictionary> → Java的Map>(HashMap实现) + // word表格集合 + Map> disMergeDic = new HashMap<>(); + List targetTables = new ArrayList<>(); + // 正则表达式字符串(与C#一致,Java无需转义额外字符) + String pattern = "^(?!L)\\{#data\\d+\\.(?!img_)\\w+}$"; // 匹配 {#data1.xxxxx} + String pattern1 = "^(?!L)\\{!#data\\d+\\.(?!img_)\\w+}$"; // 匹配 {!#data1.xxxxx} + // 遍历所有Section + for (Object sectionObj : document.getSections()) { + if (sectionObj instanceof Section) { + Section section = (Section) sectionObj; + + // 遍历Section中的Table + for (Object tableObj : section.getTables()) { + if (tableObj instanceof Table) { + Table table = (Table) tableObj; + // 检查第一行第一列的值是否符合模式 + boolean isBreak = false; + + // 遍历Table中的TableRow + for (Object rowObj : table.getRows()) { +// if (!(rowObj instanceof TableRow)) continue; + TableRow tableRow = (TableRow) rowObj; + + // 遍历TableRow中的TableCell + for (Object cellObj : tableRow.getCells()){ +// if (!(cellObj instanceof Cell)) continue; + TableCell tableCell = (TableCell) cellObj; +// int rowIndex = tableRow.getCells().indexOf(tableCell); + if(tableCell.getParagraphs().getCount() == 0){ + continue; + } + if (NativeExtensionUtils.isNullOrEmpty(tableCell.getParagraphs().get(0).getText())){ + continue; + } + boolean isMatch = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE) + .matcher(tableCell.getParagraphs().get(0).getText()).find() + || Pattern.compile(pattern1, Pattern.CASE_INSENSITIVE) + .matcher(tableCell.getParagraphs().get(0).getText()).find(); + if(isMatch){ + String npattern = "\\{!#data(\\d+)\\.\\w+\\}"; + Matcher nmatch = Pattern.compile(npattern, Pattern.CASE_INSENSITIVE).matcher(tableCell.getParagraphs().get(0).getText()); + if(nmatch.find()){ + if(disMergeDic.containsKey(table)){ + disMergeDic.get(table).add(tableRow.getCells().indexOf(tableCell)); + } + else { + List cellIndexs = new ArrayList<>(); + cellIndexs.add(tableRow.getCells().indexOf(tableCell)); + disMergeDic.put(table,cellIndexs); + } + if (!isBreak) { + GridTarGet gridTarGet = new GridTarGet(); + String patterner = "\\{#data(\\d+)\\.\\w+\\}"; + Pattern patterngrid = Pattern.compile(patterner, Pattern.CASE_INSENSITIVE); + Matcher match = patterngrid.matcher(tableCell.getParagraphs().get(0).getText()); + + if (match.find()) { + gridTarGet.setNumber(match.group(1)); // 提取捕获组1的数字 + } + + gridTarGet.setTargetSection(section) ; + gridTarGet.setTargetTable(table) ; + gridTarGet.setRowIndex(tableRow.getRowIndex()) ; + targetTables.add(gridTarGet); + isBreak = true; + } + } + } + } + } + } + } + } + } + for (GridTarGet gridTar : targetTables){ + try{ + if (dataSet.size() < Integer.parseInt(gridTar.number)) { + break; // 若在循环中,可终止循环 + } + // 获取对应的数据表(模拟DataSet) + List> dataTable = dataSet.get(Integer.parseInt(gridTar.number) - 1); + Section section = gridTar.getTargetSection(); + Table table = gridTar.getTargetTable(); + // 从表格指定行获取列名(提取{#dataX.xxx}中的xxx) + List columnNames = new ArrayList<>(); + for (Object objcell : table.getRows().get(gridTar.rowIndex).getCells()){ + if (objcell instanceof TableCell){ + TableCell cell = (TableCell) objcell; + if(cell.getParagraphs().getCount() == 0){continue;} + Matcher match = Pattern.compile("\\{#data\\d+\\.(\\w+)\\}", Pattern.CASE_INSENSITIVE).matcher(cell.getParagraphs().get(0).getText()); + if(match.find()){ + columnNames.add(match.group(1)); + } + } + } + for (int i = 0; i < dataTable.size(); i++) { + Map dataRow = dataTable.get(i); // 模拟DataRow + // 克隆模板行并插入表格(对应C#的Clone+Insert) + TableRow newRow = (TableRow)table.getRows().get(gridTar.rowIndex).deepClone(); + table.getRows().insert(gridTar.rowIndex + i + 1, newRow); + + for(int j = 0; j < columnNames.size(); j++){ + if (dataRow.containsKey(columnNames.get(j))){ + // 获取列名,并从数据行中获取相应的数据 + Paragraph cloneParagraph = (Paragraph)table.getRows().get(gridTar.rowIndex).getCells().get(j).getParagraphs().get(0).deepClone(); + CharacterFormat characterFormat = null; + if (cloneParagraph.getChildObjects().get(0) instanceof TextRange) { + characterFormat = ((TextRange)cloneParagraph.getChildObjects().get(0)).getCharacterFormat(); + } + ParagraphFormat paragraphFormat = newRow.getCells().get(j).getParagraphs().get(0).getFormat(); + newRow.getCells().get(j).getParagraphs().clear(); + newRow.getCells().get(j).getParagraphs().add(cloneParagraph); + String text = dataRow.get(columnNames.get(j)) + ""; + String[] result = Pattern.compile("\\r\\n|\\r|\\n").split(text); + cloneParagraph.setText(""); + for (int k = 0 ; k < result.length; k++){ + String resultText = result[k]; + if(k > 0){ + Break lineBreak = new Break(document, BreakType.Line_Break); + cloneParagraph.getChildObjects().add(lineBreak); + } + TextRange textRange = cloneParagraph.appendText(resultText); + textRange.applyCharacterFormat(characterFormat); + } + cloneParagraph.getFormat().setTextAlignment(paragraphFormat.getTextAlignment()); + cloneParagraph.getFormat().setHorizontalAlignment(paragraphFormat.getHorizontalAlignment()); + } + } + } + if (dataTable.size() > 0) { + table.getRows().remove(table.getRows().get(gridTar.rowIndex)); + } + MergeTable(disMergeDic,table,gridTar.rowIndex,gridTar.rowIndex + dataTable.size() - 1 ,table.getRows().get(gridTar.rowIndex).getCells().getCount()); + } catch (NumberFormatException e) { + throw new RuntimeException(e); + } + } + // 表格图片集合 + List pictureTables = new ArrayList<>(); + String imgpattern = "^(?!L)\\{#data\\d+\\.img_\\w+}$"; + for (Object sectionobj : document.getSections()){ + if (sectionobj instanceof Section){ + Section section = (Section) sectionobj; + + for (Object tableobj : section.getTables()){ + Table table = (Table) tableobj; + boolean isImgBreak = false; + + for (Object tableRowobj : table.getRows()){ + TableRow tableRow = (TableRow) tableRowobj; + + for (Object cellobj : tableRow.getCells()){ + TableCell tableCell = (TableCell) cellobj; + + if (tableCell.getParagraphs().getCount() == 0){continue;} + String cellText = tableCell.getParagraphs().get(0).getText(); + if (Pattern.compile(imgpattern, Pattern.CASE_INSENSITIVE).matcher(cellText).matches()){ + if (!isImgBreak){ + GridTarGet gridTarGet = new GridTarGet(); + String patterner = "\\{#data(\\d+)\\.img_\\w+\\}"; + Matcher match = Pattern.compile(patterner, Pattern.CASE_INSENSITIVE).matcher(cellText); + if (match.find()) { + gridTarGet.setNumber(match.group(1)); // 捕获组的索引 + } + gridTarGet.setTargetSection(section); + gridTarGet.setTargetTable(table); + gridTarGet.setRowIndex(tableRow.getRowIndex()); + pictureTables.add(gridTarGet); // 添加到集合中 + isImgBreak = true; + } + } + } + } + } + } + } + for (GridTarGet gridTar : pictureTables){ + try { + if (dataSet.size() < Integer.parseInt(gridTar.number)) { // 替换C#的dataSet.Tables.Count → Java模拟DataSet的size() + break; // 与C#的break逻辑一致 + } + } + catch (NumberFormatException e) { + } + List> dataTable = dataSet.get(Integer.parseInt(gridTar.number) - 1); + Section section = gridTar.targetSection; + Table table = gridTar.targetTable; + List columnNames = new ArrayList<>(); + for (Object objcell : table.getRows().get(gridTar.rowIndex).getCells()){ + if (objcell instanceof TableCell){ + TableCell cell = (TableCell) objcell; + if(cell.getParagraphs().getCount() == 0){continue;} + Matcher match = Pattern.compile("\\{#data\\d+\\.(img_\\w+)\\}", Pattern.CASE_INSENSITIVE) + .matcher(cell.getParagraphs().get(0).getText()); + if(match.find()){ + columnNames.add(match.group(1)); + } + } + } + if(dataTable.size() > 0){ + String picUrl = (!dataTable.isEmpty() && dataTable.get(0).containsKey(columnNames.get(0) + "")) + ? (dataTable.get(0).get(columnNames.get(0)) + "") + : ""; + if (!isNullOrEmpty(picUrl)){ + List picNameArray = new ArrayList<>(); + List picUrlArray = new ArrayList<>(); + List picSizeArray = new ArrayList<>(); + if(picUrl.startsWith("@") && jdbcTemplate!= null ){ + List> picTable = jdbcTemplate.queryForList(picUrl.substring(1)); + if(picTable != null){ + for (Map item : picTable){ + try{ + // 对应C#的name获取逻辑(含列存在性判断+文件名处理) + String name = item.containsKey("name") + ? FilenameUtils.getBaseName(item.get("name").toString()) + : ""; + // 对应C#的path获取逻辑 + String path = item.containsKey("value") + ? item.get("value").toString() + : ""; + int width = 200; // 默认值 + if (item.containsKey("width")) { + try { + width = Integer.parseInt(item.get("width").toString()); + } catch (NumberFormatException e) { + width = 200; // 解析失败则用默认值 + } + } + int height = 200; // 默认值 + if (item.containsKey("height")) { + try { + height = Integer.parseInt(item.get("height").toString()); + } catch (NumberFormatException e) { + height = 200; // 解析失败则用默认值 + } + } + Dimension size = new Dimension(width, height); + picNameArray.add(name); + picUrlArray.add(path); + picSizeArray.add(size); + } catch (Exception e) { + log.debug(String.valueOf(e.getMessage())); + } + } + } + } + else { + picUrlArray = new ArrayList<>(Arrays.asList(TrimEnd(picUrl, ',').split(","))); + } + int rowsCount = picUrlArray.size(); + TableRow addRow = null; + List addImages = new ArrayList<>(); + List addRows = new ArrayList<>(); + List afterAddRows = new ArrayList<>(); + for (int i = 0; i < table.getRows().getCount(); i++){ + if (i >= gridTar.getRowIndex()){ + addRows.add(table.getRows().get(i)); + } + } + for (int i = 1; i <= rowsCount; i++){ + Image image = null; + String imageUrl = picUrlArray.size() > 0 ? picUrlArray.get(i - 1) : ""; + String imageName = picNameArray.size() > 0 ? picNameArray.get(i - 1) : ""; + Dimension imageSize = picSizeArray.size() > 0 ? picSizeArray.get(i - 1) : new Dimension(200, 200); + image = InitCreatImage(imageUrl, OAUrl); + if(image == null){ + image = Base64ToImage(picUrlArray.get(i-1)); + } + if (image == null){continue;} + if (i%2 == 1){ + afterAddRows = new ArrayList<>(); + for (TableRow item : addRows){ + int Inde = addRows.indexOf(item); + TableRow newRow = item.deepClone(); + float totalWidth = 0f; + for (Object cellobj : newRow.getCells()){ + if (cellobj instanceof TableCell){ + TableCell cell = (TableCell) cellobj; + totalWidth += cell.getCellWidth(); + } + } + TableCell newCell1 = new TableCell(document); + TableCell newCell2 = new TableCell(document); + // 设置单元格宽度为原行宽度的一半 + newCell1.setCellWidth(50,CellWidthType.Percentage); + newCell1.getCellFormat().setBackColor(item.getCells().get(0).getCellFormat().getBackColor()); + newCell1.getCellFormat().setVerticalAlignment(item.getCells().get(0).getCellFormat().getVerticalAlignment()); + + newCell2.setCellWidth(50,CellWidthType.Percentage); + newCell2.getCellFormat().setBackColor(item.getCells().get(0).getCellFormat().getBackColor()); + newCell2.getCellFormat().setVerticalAlignment(item.getCells().get(0).getCellFormat().getVerticalAlignment()); + + // 将两个新的单元格添加到行中 + newRow.getCells().clear(); + newRow.getCells().add(newCell1); + newRow.getCells().add(newCell2); + table.getRows().add(newRow); + afterAddRows.add(newRow); + } + for (TableRow item : afterAddRows){ + int addRowIndex = afterAddRows.indexOf(item); + TableRow beforeRow = addRows.get(addRowIndex); + TableRow newRow = item; + int nowIndex = table.getRows().indexOf(item); + if (i%2 == 1){ + if (addRowIndex == 0){ + Paragraph paragraph = newRow.getCells().get(0).addParagraph(); + paragraph.getFormat().setHorizontalAlignment(HorizontalAlignment.Center); + DocPicture picture = paragraph.appendPicture((BufferedImage) image); + picture.setHorizontalAlignment(ShapeHorizontalAlignment.Center); + picture.setVerticalAlignment(ShapeVerticalAlignment.Center); + picture.setWidth((float) imageSize.getWidth()); + picture.setHeight((float) imageSize.getHeight()); + + } else if (addRowIndex > 0) { + for (Object paragraphItemobj : beforeRow.getCells().get(0).getParagraphs()){ + if (paragraphItemobj instanceof Paragraph){ + Paragraph paragraphItem = (Paragraph) paragraphItemobj; + Paragraph newParagraph = (Paragraph) paragraphItem.deepClone(); + if (!isNullOrEmpty(imageName)){ + newParagraph.setText(imageName); + } + newRow.getCells().get(0).getParagraphs().add(newParagraph); + } + } + } + } + if(i == rowsCount){ + table.applyHorizontalMerge(nowIndex, 0, newRow.getCells().getCount() - 1); + } + } + } else if (i%2 == 0 && afterAddRows != null) { + for (TableRow item : afterAddRows){ + int addRowIndex = afterAddRows.indexOf(item); + TableRow beforeRow = addRows.get(addRowIndex); + TableRow newRow = item; + if (image != null && addRowIndex == 0){ + Paragraph paragraph = newRow.getCells().get(0).addParagraph(); + paragraph.getFormat().setHorizontalAlignment(HorizontalAlignment.Center); + DocPicture picture = paragraph.appendPicture((BufferedImage) image); + picture.setHorizontalAlignment(ShapeHorizontalAlignment.Center); + picture.setVerticalAlignment(ShapeVerticalAlignment.Center); + picture.setWidth((float) imageSize.getWidth()); + picture.setHeight((float) imageSize.getHeight()); + } else if (addRowIndex > 0) { + for (Object paragraphItemobj : beforeRow.getCells().get(0).getParagraphs()){ + if (paragraphItemobj instanceof Paragraph){ + Paragraph paragraphItem = (Paragraph) paragraphItemobj; + Paragraph newParagraph = (Paragraph)paragraphItem.deepClone(); + if (!isNullOrEmpty(imageName)) + { + newParagraph.setText(imageName); + } + newRow.getCells().get(1).getParagraphs().add(newParagraph); + } + } + } + } + } + } + for (int i = 1; i <= addRows.size(); i++){ + table.getRows().remove(table.getRows().get(0)); + } + } + else { + section.getBody().getChildObjects().remove(table); + } + String l_imgpattern = "\\{L#data(\\d+)\\.\\w+}"; + for (Object tableRowobj : table.getRows()){ + if (tableRowobj instanceof TableRow){ + TableRow tableRow = (TableRow) tableRowobj; + + for (Object cellobj : tableRow.getCells()){ + if (cellobj instanceof TableCell){ + TableCell tableCell = (TableCell) cellobj; + if (tableCell.getParagraphs().getCount() == 0){continue;} + String cellText = tableCell.getParagraphs().get(0).getText(); + if (Pattern.compile(l_imgpattern, Pattern.CASE_INSENSITIVE).matcher(cellText).find()){ + GridTarGet sourceGridTarGet = new GridTarGet(); + Matcher match = Pattern.compile(l_imgpattern, Pattern.CASE_INSENSITIVE).matcher(cellText); + if (match.find()){ + sourceGridTarGet.number = match.group(1); + } + sourceGridTarGet.setTargetTable(table); + sourceGridTarGet.setRowIndex(tableRow.getRowIndex()); + try { + if (dataSet.size() < Integer.parseInt(sourceGridTarGet.number)) { + break; + } + } catch (NumberFormatException ignored) {} + // 当前表需要用到的数据源 + List> sourceTable = dataSet.get(Integer.parseInt(sourceGridTarGet.number) - 1); + for (Map dataColumn : sourceTable){ + String lastColumn = "{L#Data" + sourceGridTarGet.number + "." + dataColumn + "}"; + TextSelection[] selections = document.findAllString(lastColumn, false, true); + if (selections != null && selections.length > 0) { + String value = ""; + if (sourceTable.size() > 0) { + Map lastRow = sourceTable.get(sourceTable.size() - 1); + Object columnValue = lastRow.get(dataColumn); + value = columnValue != null ? columnValue.toString() : ""; + } + replaceTextRange(document,lastColumn, value, false, true); + document.replace(lastColumn, value, false, true); + break; + } + } + } + } + } + } + } + } + } + } + + + private static void InitCreatTabSqlDetail(Document document, List>> dataSet, String OAUrl) { + if (dataSet.size() < 2){ return;} + List> detailTab = dataSet.get(1); + Map> disMergeDic = new HashMap<>(); + List targetTables = new ArrayList<>(); + String pattern = "^(?!L)\\{#\\w+\\.(?!img_)\\w+}$"; + String pattern1 = "^(?!L)\\{!#\\w+\\.(?!img_)\\w+}$"; + for (Object sectionobj : document.getSections()){ + if (sectionobj instanceof Section){ + Section section = (Section) sectionobj; + + for (Object tableobj : section.getTables()){ + if (tableobj instanceof Table){ + Table table = (Table) tableobj; + boolean isBreak = false; + for (Object tableRowobj : table.getRows()){ + if (tableRowobj instanceof TableRow){ + TableRow tableRow = (TableRow) tableRowobj; + + for (Object cellobj : tableRow.getCells()){ + if (cellobj instanceof TableCell){ + TableCell tableCell = (TableCell) cellobj; + if (tableCell.getParagraphs().getCount() == 0){ + continue; + } + if (isNullOrEmpty(tableCell.getParagraphs().get(0).getText())){ + continue; + } + if (Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(tableCell.getParagraphs().get(0).getText()).find() || Pattern.compile(pattern1, Pattern.CASE_INSENSITIVE).matcher(tableCell.getParagraphs().get(0).getText()).find()){ + String npattern = "\\{!#\\w+\\.\\w+\\}"; + Matcher nmatch = Pattern.compile(npattern, Pattern.CASE_INSENSITIVE).matcher(tableCell.getParagraphs().get(0).getText()); + if (nmatch.find()) { + if (disMergeDic.containsKey(table)) { + disMergeDic.get(table).add(tableRow.getCells().indexOf(tableCell)); + } + else { + List cellIndexList = new ArrayList<>(); + cellIndexList.add(tableRow.getCells().indexOf(tableCell)); + disMergeDic.put(table, cellIndexList); + } + String originalText = tableCell.getParagraphs().get(0).getText(); + tableCell.getParagraphs().get(0).setText(originalText.replace("!#", "#")); + } + if (!isBreak){ + GridTarGet gridTarGet = new GridTarGet(); + String patterner = "\\{#(\\w+)\\.\\w+\\}"; + Matcher match = Pattern.compile(patterner, Pattern.CASE_INSENSITIVE).matcher(tableCell.getParagraphs().get(0).getText()); + if (match.find()) { + gridTarGet.setNumber(match.group(1)); // 捕获组的索引 + } + gridTarGet.setTargetTable(table); + gridTarGet.setRowIndex(tableRow.getRowIndex()); + targetTables.add(gridTarGet); // 添加到集合中 + isBreak = true; + } + } + } + } + } + } + } + } + } + } + for (GridTarGet gridTar : targetTables){ + try{ +// Clone,似乎只克隆结构,不克隆数据 + List> dataTable = new ArrayList<>(); + detailTab.stream().filter(map -> gridTar.number.equals((map.get("crm_lco_xmbg") + ""))).forEach(dataTable::add); + Table table = gridTar.getTargetTable(); + List columnNames = new ArrayList<>(); + for (Object cellobj : table.getRows().get(gridTar.rowIndex).getCells()){ + if (cellobj instanceof TableCell){ + TableCell cell = (TableCell) cellobj; + if (cell.getParagraphs().getCount() == 0){ + continue; + } + Matcher match = Pattern.compile("\\{#\\w+\\.(\\w+)\\}", Pattern.CASE_INSENSITIVE).matcher(cell.getParagraphs().get(0).getText()); + if (match.find()) { + columnNames.add(String.format("{%s}", match.group(1))); // 添加匹配到的xxxxx部分到集合中 + } else { + columnNames.add(cell.getParagraphs().get(0).getText()); + } + } + } + for (int i = 0; i < dataTable.size(); i++){ + Map dataRow = dataTable.get(i); + TableRow newRow = table.getRows().get(gridTar.rowIndex).deepClone(); + table.getRows().insert(gridTar.rowIndex + i + 1, newRow); + for(int j = 0; j < columnNames.size(); j++){ + String columnName = columnNames.get(j); + Paragraph cloneParagraph = (Paragraph) table.getRows().get(gridTar.rowIndex).getCells().get(j).getParagraphs().get(0).deepClone(); + CharacterFormat characterFormat = ((TextRange) cloneParagraph.getChildObjects().get(0)).getCharacterFormat(); + ParagraphFormat paragraphFormat = newRow.getCells().get(j).getParagraphs().get(0).getFormat(); + newRow.getCells().get(j).getParagraphs().clear(); + newRow.getCells().get(j).getParagraphs().add(cloneParagraph); + if (columnName.startsWith("{") && columnName.endsWith("}")){ + columnName = columnName.replaceAll("^\\{", "").replaceAll("\\}$", ""); + if (columnNames.contains(columnName)){ + String text = dataRow.get(columnName) + ""; + String splitPattern = "\\r\\n|\\r|\\n"; // 匹配 CRLF (\r\n), CR (\r), LF (\n) + String[] result = Pattern.compile(splitPattern).split(text); + cloneParagraph.setText(""); + for (int k = 0; k < result.length; k++) { + String resultText = result[k]; + if (k > 0) { + Break lineBreak = new Break(document, BreakType.Line_Break); + cloneParagraph.getChildObjects().add(lineBreak); + } + TextRange textRange = cloneParagraph.appendText(resultText); + textRange.applyCharacterFormat(characterFormat); + } + } + } + else { + cloneParagraph.setText(columnName); + } + cloneParagraph.getFormat().setTextAlignment(paragraphFormat.getTextAlignment()); + cloneParagraph.getFormat().setHorizontalAlignment(paragraphFormat.getHorizontalAlignment()); + } + } + if (dataTable.size() > 0){ + table.getRows().remove(table.getRows().get(gridTar.rowIndex)); + } + MergeTable(disMergeDic,table,gridTar.rowIndex,gridTar.rowIndex + dataTable.size() -1, table.getRows().get(gridTar.rowIndex).getCells().getCount()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + List pictrueTables = new ArrayList<>(); + String imgpattern = "^(?!L)\\{#\\w+\\.img_\\w+\\}$"; + for (Object sectionobj : document.getSections()){ + if (sectionobj instanceof Section){ + Section section = (Section) sectionobj; + + for (Object tableobj : section.getTables()){ + if (tableobj instanceof Table){ + Table table = (Table) tableobj; + boolean isImgBreak = false; + + for (Object tableRowobj : table.getRows()){ + if (tableRowobj instanceof TableRow){ + TableRow tableRow = (TableRow) tableRowobj; + + for (Object tableCellobj : tableRow.getCells()){ + if (tableCellobj instanceof TableCell){ + TableCell tableCell = (TableCell) tableCellobj; + if (tableCell.getParagraphs().getCount() == 0){ + continue; + } + String cellText = tableCell.getParagraphs().get(0).getText(); + if (Pattern.compile(imgpattern, Pattern.CASE_INSENSITIVE).matcher(cellText).find()) { + if (!isImgBreak) { + GridTarGet gridTarGet = new GridTarGet(); + String patterner = "\\{#(\\w+)\\.img_\\w+\\}"; + Matcher match = Pattern.compile(patterner, Pattern.CASE_INSENSITIVE).matcher(cellText); + if (match.find()) { + gridTarGet.number = match.group(1); // 捕获组的索引 + } + gridTarGet.targetSection = section; + gridTarGet.targetTable = table; + gridTarGet.rowIndex = tableRow.getRowIndex(); + pictrueTables.add(gridTarGet); // 添加到集合中 + isImgBreak = true; + } + } + } + } + } + } + } + } + } + }; + for (GridTarGet gridTar : pictrueTables){ + List> dataTable = new ArrayList<>(); + detailTab.stream().filter(map -> gridTar.getNumber().equals((map.get("crm_lco_xmbg") + ""))).forEach(dataTable::add); + Table table = gridTar.getTargetTable(); + Section section = gridTar.getTargetSection(); + List columnNames = new ArrayList<>(); + for (Object cellobj : table.getRows().get(gridTar.rowIndex).getCells()){ + if (cellobj instanceof TableCell){ + TableCell cell = (TableCell) cellobj; + if (cell.getParagraphs().getCount() == 0){ + continue; + } + Matcher match = Pattern.compile("\\{#\\w+\\.(img_\\w+)\\}", Pattern.CASE_INSENSITIVE).matcher(cell.getParagraphs().get(0).getText()); + if (match.find()) { + columnNames.add(match.group(1)); // 添加匹配到的xxxxx部分到集合中 + } + } + } + if (dataTable.size() > 0){ + String picUrl = (!dataTable.isEmpty() && dataTable.get(0).containsKey(columnNames.get(0) + "")) ? (dataTable.get(0).get(columnNames.get(0) + "") + "") : ""; + if (!picUrl.trim().isEmpty()){ + List picNameArray = new ArrayList<>(); + List picUrlArray = new ArrayList<>(); + List picSizeArray = new ArrayList<>(); + if (picUrl.startsWith("@") && jdbcTemplate!= null){ + List> picTable = jdbcTemplate.queryForList(picUrl.substring(1)); + if (picTable != null){ + for (Map item : picTable){ + try{ + String name = item.containsKey("name") ? FilenameUtils.getBaseName(item.get("name") + "") : ""; + String path = item.containsKey("value") ? (item.get("value") + "") : ""; + + int width = 200; + if (item.containsKey("width")) { + try { + width = Integer.parseInt(item.get("width") + ""); + } catch (NumberFormatException e) { + width = 200; + } + } + + int height = 200; + if (item.containsKey("height")) { + try { + height = Integer.parseInt(item.get("height") + ""); + } catch (NumberFormatException e) { + height = 200; + } + } + + Dimension size = new Dimension(width, height); + picNameArray.add(name); + picUrlArray.add(path); + picSizeArray.add(size); + + } catch (Exception e) { + log.debug(String.valueOf(e.getMessage())); + } + } + } + } + else { + picUrlArray = new ArrayList<>(Arrays.asList(TrimEnd(picUrl, ',').split(","))); + } + int rowsCount = picUrlArray.size(); + TableRow addRow = null; + List addImages = new ArrayList<>(); + List addRows = new ArrayList<>(); + List afterAddRows = new ArrayList<>(); + for (int i = 0; i < table.getRows().getCount(); i++) { + if (i >= gridTar.rowIndex) + { + addRows.add(table.getRows().get(i)); + } + } + for (int i = 1; i <= rowsCount; i++) { + Image image = null; + String imageUrl = picUrlArray.size() > 0 ? picUrlArray.get(i - 1) : ""; + String imageName = picNameArray.size() > 0 ? picNameArray.get(i - 1) : ""; + Dimension imageSize = picSizeArray.size() > 0 ? picSizeArray.get(i - 1) : new Dimension(200, 200); + image = InitCreatImage(imageUrl, OAUrl); + + if (image == null){image = Base64ToImage(picUrlArray.get(i-1));}; + if (image == null){continue;}; + if (i % 2 == 1){ + afterAddRows = new ArrayList<>(); + for (TableRow item : addRows){ + int Inde = addRows.indexOf(item); + TableRow newRow = item.deepClone(); + float totalWidth = 0f; + for (Object cellobj : newRow.getCells()){ + if (cellobj instanceof TableCell){ + TableCell cell = (TableCell) cellobj; + totalWidth += cell.getCellWidth(); + } + } + TableCell newCell1 = new TableCell(document); + TableCell newCell2 = new TableCell(document); + // 设置单元格宽度为原行宽度的一半 + newCell1.setCellWidth(50,CellWidthType.Percentage); + newCell1.getCellFormat().setBackColor(item.getCells().get(0).getCellFormat().getBackColor()); + newCell1.getCellFormat().setVerticalAlignment(item.getCells().get(0).getCellFormat().getVerticalAlignment()); + + newCell2.setCellWidth(50,CellWidthType.Percentage); + newCell2.getCellFormat().setBackColor(item.getCells().get(0).getCellFormat().getBackColor()); + newCell2.getCellFormat().setVerticalAlignment(item.getCells().get(0).getCellFormat().getVerticalAlignment()); + + // 将两个新的单元格添加到行中 + newRow.getCells().clear(); + newRow.getCells().add(newCell1); + newRow.getCells().add(newCell2); + table.getRows().add(newRow); + afterAddRows.add(newRow); + } + for(TableRow item : afterAddRows){ + int addRowIndex = afterAddRows.indexOf(item); + TableRow beforeRow = addRows.get(addRowIndex); + TableRow newRow = item; + int nowIndex = table.getRows().indexOf(item); + if (i%2 == 1){ + if (addRowIndex == 0){ + Paragraph paragraph = newRow.getCells().get(0).addParagraph(); + paragraph.getFormat().setHorizontalAlignment(HorizontalAlignment.Center); + DocPicture picture = paragraph.appendPicture((BufferedImage) image); + picture.setHorizontalAlignment(ShapeHorizontalAlignment.Center); + picture.setVerticalAlignment(ShapeVerticalAlignment.Center); + picture.setWidth((float) imageSize.getWidth()); + picture.setHeight((float) imageSize.getHeight()); + + } + else if (addRowIndex > 0) { + for (Object paragraphItemobj : beforeRow.getCells().get(0).getParagraphs()){ + if (paragraphItemobj instanceof Paragraph){ + Paragraph paragraphItem = (Paragraph) paragraphItemobj; + Paragraph newParagraph = (Paragraph) paragraphItem.deepClone(); + if (!isNullOrEmpty(imageName)){ + newParagraph.setText(imageName); + } + newRow.getCells().get(0).getParagraphs().add(newParagraph); + } + } + } + } + if(i == rowsCount){ + table.applyHorizontalMerge(nowIndex,0,newRow.getCells().getCount() - 1); + } + } + } + else if (i%2 == 0 && afterAddRows != null) { + for (TableRow item : afterAddRows){ + int addRowIndex = afterAddRows.indexOf(item); + TableRow beforeRow = addRows.get(addRowIndex); + TableRow newRow = item; + if (image != null && addRowIndex == 0){ + Paragraph paragraph = newRow.getCells().get(0).addParagraph(); + paragraph.getFormat().setHorizontalAlignment(HorizontalAlignment.Center); + DocPicture picture = paragraph.appendPicture((BufferedImage) image); + picture.setHorizontalAlignment(ShapeHorizontalAlignment.Center); + picture.setVerticalAlignment(ShapeVerticalAlignment.Center); + picture.setWidth((float) imageSize.getWidth()); + picture.setHeight((float) imageSize.getHeight()); + } else if (addRowIndex > 0) { + for (Object paragraphItemobj : beforeRow.getCells().get(0).getParagraphs()){ + if (paragraphItemobj instanceof Paragraph){ + Paragraph paragraphItem = (Paragraph) paragraphItemobj; + Paragraph newParagraph = (Paragraph)paragraphItem.deepClone(); + if (!isNullOrEmpty(imageName)) + { + newParagraph.setText(imageName); + } + newRow.getCells().get(1).getParagraphs().add(newParagraph); + } + } + } + } + } + } + for (int i = 0; i < addRows.size(); i++) + { + table.getRows().remove(table.getRows().get(0)); + } + } + else { + section.getBody().getChildObjects().remove(table); + } + String l_imgpattern = "\\{L#(\\w+)\\.\\w+}"; + for (Object tableRowobj : table.getRows()){ + if (tableRowobj instanceof TableRow){ + TableRow tableRow = (TableRow) tableRowobj; + + for (Object cellobj : tableRow.getCells()){ + if (cellobj instanceof TableCell){ + TableCell tableCell = (TableCell) cellobj; + if (tableCell.getParagraphs().getCount() == 0){continue;} + String cellText = tableCell.getParagraphs().get(0).getText(); + if (Pattern.compile(l_imgpattern, Pattern.CASE_INSENSITIVE).matcher(cellText).find()){ + GridTarGet sourceGridTarGet = new GridTarGet(); + Matcher match = Pattern.compile(l_imgpattern, Pattern.CASE_INSENSITIVE).matcher(cellText); + if (match.find()){ + sourceGridTarGet.number = match.group(1); + } + sourceGridTarGet.setTargetTable(table); + sourceGridTarGet.setRowIndex(tableRow.getRowIndex()); + try { + if (dataSet.size() < Integer.parseInt(sourceGridTarGet.number)) { + break; + } + } catch (NumberFormatException ignored) {} + // 当前表需要用到的数据源 + List> sourceTable = dataSet.get(Integer.parseInt(sourceGridTarGet.number) - 1); + for (Map dataColumn : sourceTable){ + String lastColumn = "{L#Data" + sourceGridTarGet.number + "." + dataColumn + "}"; + TextSelection[] selections = document.findAllString(lastColumn, false, true); + if (selections != null && selections.length > 0) { + String value = ""; + if (sourceTable.size() > 0) { + Map lastRow = sourceTable.get(sourceTable.size() - 1); + Object columnValue = lastRow.get(dataColumn); + value = columnValue != null ? columnValue.toString() : ""; + } + replaceTextRange(document,lastColumn, value, false, true); + document.replace(lastColumn, value, false, true); + break; + } + } + } + } + } + } + } + } + } +; } + + private static void RemoveTableRow(Document document){ + // 遍历文档中的所有Section(非泛型集合需类型转换) + for (Object sectionObj : document.getSections()) { + if (sectionObj instanceof Section) { + Section section = (Section) sectionObj; + + // 遍历Section中的所有Table + for (Object tableObj : section.getTables()) { + if (tableObj instanceof Table) { + Table table = (Table) tableObj; + List deleteRows = new ArrayList<>(); + + // 遍历Table中的所有TableRow + for (Object rowObj : table.getRows()) { + if (rowObj instanceof TableRow) { + TableRow tableRow = (TableRow) rowObj; + + // 遍历TableRow中的所有TableCell + for (Object cellObj : tableRow.getCells()) { + if (cellObj instanceof TableCell) { + TableCell tableCell = (TableCell) cellObj; + + // 判断单元格是否有段落 + if (tableCell.getParagraphs().getCount() == 0) { + continue; + } + + // 获取单元格第一段文本 + String cellText = tableCell.getParagraphs().get(0).getText(); + if ("!/".equals(cellText)) { + deleteRows.add(tableRow); + break; // 找到匹配行,跳出单元格循环 + } + } + } + } + } + + // 删除标记的行 + for (TableRow tableRow : deleteRows) { + table.getRows().remove(tableRow); + } + } + } + } + } + } + + + // 辅助方法 + private static void CpoyHeaderFooter(Section sourceSection, Section targetSection, HeaderFooterType headerFooterType) { + // 修正:通过枚举索引获取页眉/页脚(Java版HeadersFooters用索引访问) + HeaderFooter headerFooter = sourceSection.getHeadersFooters().get(headerFooterType.ordinal()); + // 判断页眉/页脚是否存在且有内容 + if (headerFooter != null && headerFooter.getChildObjects() != null && headerFooter.getChildObjects().getCount() > 0) { + // 目标页眉/页脚同样通过索引获取 + HeaderFooter targetHeaderFooter = targetSection.getHeadersFooters().get(headerFooterType.ordinal()); + targetHeaderFooter.setLinkToPrevious(false); + targetHeaderFooter.getChildObjects().clear(); + // 遍历源页眉/页脚的子对象并克隆复制 + for (Object childObj : headerFooter.getChildObjects()) { + if (childObj instanceof DocumentObject) { + DocumentObject obj = (DocumentObject) childObj; + targetHeaderFooter.getChildObjects().add(obj.deepClone()); + } + } + } + } + + /** + * 与C#原方法功能完全一致的ReplaceTextRange实现 + * 特点:强制忽略大小写(RegexOptions.IgnoreCase),与caseSensitive参数无关 + */ + public static void replaceTextRange(Document document, String searchText, String replaceText, + boolean caseSensitive, boolean wholeWord) { + // 查找所有匹配(这里的caseSensitive仅影响FindAllString的查找规则) + TextSelection[] selections = document.findAllString(searchText, caseSensitive, wholeWord); + + for (TextSelection selection : selections) { + TextRange textRange = selection.getAsOneRange(); + if (textRange != null) { + // 关键:强制使用IgnoreCase(与C#的RegexOptions.IgnoreCase一致) + String regex = Pattern.quote(searchText); // 转义特殊字符,避免正则语法冲突 + String newText = Pattern.compile(regex, Pattern.CASE_INSENSITIVE) + .matcher(textRange.getText()) + .replaceAll(replaceText); + + textRange.setText(newText); + } + } + } + + private static Image InitCreatImage(String imagePath,String OAUrl,int width,int height){ + String downUrl = ""; + try { + if (NativeExtensionUtils.isNullOrEmpty(imagePath)){ + return null; + } + downUrl = imagePath = !imagePath.startsWith("http") ? OAUrl + imagePath : imagePath; + // 下载图片字节数据(替代C# WebClient.DownloadData) + byte[] imageData = downloadImageData(downUrl); + if (imageData == null) { + return null; + } + try (ByteArrayInputStream imageStream = new ByteArrayInputStream(imageData)) { + BufferedImage image = ImageIO.read(imageStream); + if (image == null) { + return null; + } + + // 默认宽高为图片原始尺寸 + if (width == 0) width = image.getWidth(); + if (height == 0) height = image.getHeight(); + + // 计算缩放比例(保持宽高比) + int sourceWidth = image.getWidth(); + int sourceHeight = image.getHeight(); + float nPercent = 0; + float nPercentW = (float) width / sourceWidth; + float nPercentH = (float) height / sourceHeight; + + nPercent = Math.min(nPercentH, nPercentW); // 取较小比例 + + // 目标宽高 + int destWidth = (int) (sourceWidth * nPercent); + int destHeight = (int) (sourceHeight * nPercent); + + // 创建缩放后的图片(替代C# Bitmap+Graphics) + BufferedImage scaledImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + Graphics2D graphics = scaledImage.createGraphics(); + + // 设置高质量插值(替代InterpolationMode.HighQualityBicubic) + graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + graphics.drawImage(image, 0, 0, destWidth, destHeight, null); + graphics.dispose(); + + return scaledImage; + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + // 辅助方法:下载图片字节数据(替代C# WebClient.DownloadData) + private static byte[] downloadImageData(String urlStr) throws IOException { + URL url = new URL(urlStr); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); + + try (InputStream inputStream = connection.getInputStream(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + return outputStream.toByteArray(); + } finally { + connection.disconnect(); + } + } + + private static Image Base64ToImage(String base64String) { + Image image = null; + try { + // 空值/空字符串校验 + if (base64String != null && !base64String.trim().isEmpty()) { + // 正则替换去掉base64前缀(如data:image/png;base64,) + String pattern = ".*;base64,"; + Pattern regexPattern = Pattern.compile(pattern); + Matcher matcher = regexPattern.matcher(base64String); + base64String = matcher.replaceFirst(""); + + // Base64解码为字节数组(替代C#的Convert.FromBase64String) + byte[] imageBytes = Base64.getDecoder().decode(base64String); + + // 字节数组转输入流,读取为Image(替代MemoryStream+Image.FromStream) + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes)) { + BufferedImage bufferedImage = ImageIO.read(inputStream); + if (bufferedImage != null) { + image = bufferedImage; + } + } + } + } catch (Exception e) { + // 捕获所有异常,返回null(与原逻辑一致) + } + return image; + } + + private static Paragraph GeObjectParagraph(Document document, DocumentObject documentObject) { + boolean isSelect = false; + Paragraph lastParagraph = null; + for (Object secObj : document.getSections()){ + if(secObj instanceof Section){ + Section sec = (Section) secObj; + for(Object obj : sec.getBody().getChildObjects()){ + if(obj == documentObject && !isSelect){ + isSelect = true; + } + if (obj instanceof Paragraph) { + Paragraph para = (Paragraph) obj; + if (!isSelect) { + isSelect = SelectObject(documentObject, para); + } + if (isSelect) { + lastParagraph = para; + break; // 找到后跳出当前循环 + } + }else if (obj instanceof Table) { + Table table = (Table) obj; + // 遍历Table的行 + for (Object rowObj : table.getRows()) { + if (rowObj instanceof TableRow) { + TableRow row = (TableRow) rowObj; + // 遍历行的单元格 + for (Object cellObj : row.getCells()) { + if (cellObj instanceof TableCell) { + TableCell cell = (TableCell) cellObj; + // 遍历单元格的段落 + for (Object itemObj : cell.getParagraphs()) { + if (itemObj instanceof Paragraph) { + Paragraph item = (Paragraph) itemObj; + if (!isSelect) { + isSelect = SelectObject(documentObject, item); + } + if (isSelect) { + lastParagraph = item; + break; // 找到后跳出段落循环 + } + } + } + if (isSelect) { + break; // 跳出单元格循环 + } + } + } + if (isSelect) { + break; // 跳出行循环 + } + } + } + if (isSelect) { + break; // 跳出Table循环 + } + } + } + } + if (lastParagraph != null) { + break; // 找到后跳出Section循环 + } + } + return lastParagraph; + } + + private static boolean SelectObject(DocumentObject documentObject, Paragraph paragraph){ + boolean result = false; + for(Object obj : paragraph.getChildObjects()){ + DocumentObject documentObject2 = (DocumentObject) obj; + boolean flag = documentObject2 == documentObject; + if(flag){ + result = true; + break; + } + } + return result; + } + + private static Image InitCreatImage(String imagePath,String OAUrl){ + return InitCreatImage(imagePath,OAUrl,0,0); + } + + private static void MergeTable(Map> disMergeDic, Table table, int contentStartIndex, int contentEndIndex, int colCount) { + try{ + for (int i = 0; i < colCount; i++){ + if(disMergeDic.containsKey(table) && disMergeDic.get(table).contains(i)){ + continue; + } + int startIndex = contentStartIndex; + int endIndex = contentEndIndex; + for (int j = contentStartIndex; j <= contentEndIndex; j++){ + TableRow tableRow = table.getRows().get(j); + String cellValue = tableRow.getCells().get(i).getParagraphs().getCount() > 0 + ? tableRow.getCells().get(i).getParagraphs().get(0).getText() + : ""; + if (j + 1 <= contentEndIndex){ + TableRow nextTableRow = table.getRows().get(j + 1); + String nextCellValue = nextTableRow.getCells().get(i).getParagraphs().getCount() > 0 + ? nextTableRow.getCells().get(i).getParagraphs().get(0).getText() + :""; + if (!nextCellValue.equals(cellValue)){ + if(j > startIndex){ + for (int start = startIndex + 1; start <= j; start++){ + table.getRows().get(start).getCells().get(i).getParagraphs().clear(); + } + table.applyVerticalMerge(i, startIndex, j); + } + startIndex = j +1; + } + } + } + if (startIndex <= contentEndIndex){ + for (int start = startIndex + 1; start <= contentEndIndex; start++){ + table.getRows().get(start).getCells().get(i).getParagraphs().clear(); + } + table.applyVerticalMerge(i, startIndex, contentEndIndex); + } + } + } catch (Exception e) { + log.debug(String.valueOf(e.getMessage())); + } + } + + + protected static class GridTarGet { + public String number; + public Section targetSection; + public Table targetTable; + public int rowIndex; + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public Section getTargetSection() { + return targetSection; + } + + public void setTargetSection(Section targetSection) { + this.targetSection = targetSection; + } + + public Table getTargetTable() { + return targetTable; + } + + public void setTargetTable(Table targetTable) { + this.targetTable = targetTable; + } + + public int getRowIndex() { + return rowIndex; + } + + public void setRowIndex(int rowIndex) { + this.rowIndex = rowIndex; + } + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Office/OfficeUtil.java b/WebErp/weberp/src/main/java/org/example/Office/OfficeUtil.java new file mode 100644 index 0000000..350b5cf --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Office/OfficeUtil.java @@ -0,0 +1,1137 @@ +package org.example.Office; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// ====================== 1. Java原生基础类(IO、AWT、NIO等)====================== + +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.MalformedURLException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Base64; +import java.util.Timer; +import java.util.TimerTask; +import java.util.regex.Pattern; +import javax.imageio.ImageIO; +// ====================== 2. Spire.Doc相关(仅用于Word转HTML:SaveToHtml方法)====================== +import com.spire.doc.Document; +import com.spire.doc.FileFormat; +import com.spire.doc.Section; +import com.spire.doc.PageSetup; +import com.spire.doc.documents.ImageType; +import com.spire.doc.documents.MarginsF; + +// ====================== 3. Spire.XLS相关(仅用于Excel转HTML:SaveExcelToHtml方法)====================== +import com.spire.xls.Workbook; +import com.spire.xls.Worksheet; +// 注:SaveExcelToHtml中用了完整路径com.spire.xls.PageSetup,此处不重复导入 + +// ====================== 4. iText 7相关(仅用于图片转PDF:iTextSharpCreatPDF方法)====================== +import com.itextpdf.kernel.pdf.PdfDocument; +import com.itextpdf.kernel.pdf.PdfWriter; +import com.itextpdf.io.image.ImageData; +import com.itextpdf.io.image.ImageDataFactory; +import com.itextpdf.kernel.geom.PageSize; +// iText7 核心布局类(解决 Image 和 Document 冲突) +import com.itextpdf.layout.element.Image; +import com.itextpdf.layout.element.AreaBreak; +import com.itextpdf.layout.properties.AreaBreakType; +import org.bytedeco.javacv.FFmpegFrameGrabber; +import org.example.Utils.FormatFactoryUtil; + +import static org.example.Utils.NativeExtensionUtils.ToInt64; +// 注:方法中用了完整路径com.itextpdf.layout.Document和com.itextpdf.layout.element.Image,此处不重复导入 + +public class OfficeUtil { + private static final Logger log = LoggerFactory.getLogger(OfficeUtil.class); + + + private static final String ImgHtml = "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " ${0}\n" + // 占位符 ${0}:页面标题 + "\n" + + "\n" + + " ${3}\n" + // 占位符 ${3}:body 前置内容(如额外样式) + "
${1}
\n" + // ${2}:div样式,${1}:div内容 + "\n" + + ""; + + + // 1. base64ToPng(无改动) + public static void base64ToPng(String base64, String path) { + byte[] arr = Base64.getDecoder().decode(base64); + + try (ByteArrayInputStream ms = new ByteArrayInputStream(arr)) { + BufferedImage bmp = ImageIO.read(ms); + File file = new File(path); + + if (!file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + } + + if (!file.exists()) { + ImageIO.write(bmp, "png", file); + } + } catch (IOException e) { + log.error("Exception caught", e); + } + } + + + // 2. TifToBase64(无改动) + public static String TifToBase64(String tifPath) { + try (ByteArrayOutputStream memoryStream = new ByteArrayOutputStream()) { + File tifFile = new File(tifPath); + BufferedImage image = ImageIO.read(tifFile); + + ImageIO.write(image, "png", memoryStream); + memoryStream.flush(); + + byte[] imageBytes = memoryStream.toByteArray(); + return Base64.getEncoder().encodeToString(imageBytes); + } catch (IOException e) { + log.error("Exception caught", e); + return null; + } + } + + + // 3. iTextSharpCreatPDF(修复所有iText 7错误) + public static void iTextSharpCreatPDF(String imagepath, String pdfpath) throws MalformedURLException { + // 修复1:ImageDataFactory.create可能抛MalformedURLException,声明抛出 + ImageData imageData = ImageDataFactory.create(imagepath); + float percentage = 1f; + float resizedWidth = imageData.getWidth(); + float resizedHeight = imageData.getHeight(); + + // 修复2:用PageSize替代Rectangle(Document构造函数要求PageSize类型) + PageSize pageSize = new PageSize(1000, 1000); + + try (PdfWriter writer = new PdfWriter(pdfpath); + PdfDocument pdfDoc = new PdfDocument(writer); + com.itextpdf.layout.Document doc = new com.itextpdf.layout.Document(pdfDoc, pageSize)) { + + doc.setMargins(0, 0, 0, 0); + // 修复5:getPageEffectiveArea参数为PageSize,方法正常调用 + while (resizedWidth > doc.getPageEffectiveArea(pageSize).getWidth()) { + percentage *= 0.9f; + resizedHeight = imageData.getHeight() * percentage; + resizedWidth = imageData.getWidth() * percentage; + } + + com.itextpdf.layout.element.Image image = new com.itextpdf.layout.element.Image(imageData); + image.scale(percentage, percentage); + float centerX = (pageSize.getWidth() - resizedWidth) / 2; + float centerY = (pageSize.getHeight() - resizedHeight) / 2; + image.setFixedPosition(centerX, centerY); + + doc.add(image); + + } catch (IOException ioex) { + log.error("Exception caught", ioex); + } catch (Exception ex) { + log.error("Exception caught", ex); + } + } + + + // 4. SaveToHtml(无改动) + public static void SaveToHtml(String urlpath, String hpath, String disCopy) { + Document document = new Document(); + try { + document.loadFromFile(urlpath); + document.saveToFile(hpath, FileFormat.Html); + + Section section = document.getSections().get(0); + PageSetup pageSetup = section.getPageSetup(); + double width = pageSetup.getPageSize().getWidth(); + MarginsF margins = pageSetup.getMargins(); + + String style = String.format("", + width, + disCopy, + margins.getTop(), + margins.getRight(), + margins.getBottom(), + margins.getLeft()); + + File urlFile = new File(urlpath); + if (urlFile.exists()) { + String strContent = Files.readString(Paths.get(hpath), StandardCharsets.UTF_8); + + Pattern bodyPattern = Pattern.compile("(]*?>)", Pattern.CASE_INSENSITIVE | Pattern.DOTALL); + strContent = bodyPattern.matcher(strContent).replaceFirst(style); + + strContent = strContent.replace("", ""); + + Pattern charsetPattern = Pattern.compile("]+charset=+", Pattern.CASE_INSENSITIVE); + if (!charsetPattern.matcher(strContent).find()) { + strContent = strContent.replace("", ""); + } + + Pattern lineHeightPattern = Pattern.compile("]+line-height:0\\.+", Pattern.CASE_INSENSITIVE); + if (lineHeightPattern.matcher(strContent).find()) { + strContent = strContent.replace("", ""); + } + + Files.write(Paths.get(hpath), strContent.getBytes(StandardCharsets.UTF_8)); + } + } catch (Exception e) { + log.error("Exception caught", e); + throw new RuntimeException(e); + } finally { + if (document != null) { + document.dispose(); + } + } + } + + + // 5. SaveExcelToHtml(无改动) + public static void SaveExcelToHtml(String urlpath, String hpath, String htmlPath, String discopy) { + String style = ""; + double width = 0; + Workbook book = new Workbook(); + + try { + book.loadFromFile(urlpath); + String saveHtmlPath = hpath; + + for (int i = 0; i < book.getWorksheets().getCount(); i++) { + Worksheet worksheet = book.getWorksheets().get(i); + + if (i == 0) { + worksheet.saveToHtml(hpath); + } else { + saveHtmlPath = hpath.replace(".html", "_" + i + ".html"); + worksheet.saveToHtml(saveHtmlPath); + } + + com.spire.xls.PageSetup pageSetup = worksheet.getPageSetup(); + width = book.getWorksheets().get(0).getPageSetup().getPageWidth(); + + style = String.format("", + width, + discopy, + pageSetup.getTopMargin(), + pageSetup.getRightMargin(), + pageSetup.getBottomMargin(), + pageSetup.getLeftMargin()); + + if (i <= book.getWorksheets().getCount() - 2) { + String nextPageUrl = htmlPath.replace(".html", "_" + (i + 1) + ".html"); + style += "下一页"; + } + + if (i > 0) { + String prevPageUrl = (i == 1) ? htmlPath : htmlPath.replace(".html", "_" + (i - 1) + ".html"); + style += "上一页"; + } + + File urlFile = new File(urlpath); + if (urlFile.exists()) { + String strContent = Files.readString(Paths.get(saveHtmlPath), StandardCharsets.UTF_8); + + String replaceStr = "background-color:rgb(0,0,0)"; + if (strContent.indexOf(replaceStr) > 0) { + strContent = strContent.replace(replaceStr, "background-color:rgb(255,255,255)"); + } + + Pattern charsetPattern = Pattern.compile("]+charset=+", Pattern.CASE_INSENSITIVE); + if (!charsetPattern.matcher(strContent).find()) { + strContent = strContent.replace("", ""); + } + + Pattern bodyPattern = Pattern.compile("(]*?>)", Pattern.CASE_INSENSITIVE | Pattern.DOTALL); + strContent = bodyPattern.matcher(strContent).replaceFirst(style); + + Files.write(Paths.get(saveHtmlPath), strContent.getBytes(StandardCharsets.UTF_8)); + } + } + } catch (Exception e) { + log.error("Exception caught", e); + throw new RuntimeException(e); + } finally { + if (book != null) { + book.dispose(); + } + } + } + + public static void SaveToImg(String urlpath, String hpath, String extension) { + SaveToImg(urlpath, hpath, extension, false); // 重载,适配 C# 默认参数 + } + + /** + * 对齐 C# 逻辑:Word/Excel 转图片 + 生成 HTML,支持可选转 PDF + * + * @param urlpath 源文件路径(doc/docx/xls/xlsx) + * @param hpath 生成的 HTML 路径 + * @param extension 文件扩展名(小写) + * @param toPdf 是否同时生成 PDF(默认 false) + */ + public static void SaveToImg(String urlpath, String hpath, String extension, boolean toPdf) { + // 1. 路径处理(完全对齐 C# Split/Replace 逻辑) + String[] hpathParts = hpath.split("/"); + String htmlName = hpathParts[hpathParts.length - 1].replace(".html", ""); + String imgFloder = hpath.replace(".html", "_imgs/"); + + // 2. 创建图片文件夹(对齐 C# Directory.CreateDirectory) + File imgDir = new File(imgFloder); + if (!imgDir.exists()) { + imgDir.mkdirs(); + } + + String style; + + // 3. 按文件类型处理(对齐 C# switch 逻辑) + switch (extension.toLowerCase()) { + case "doc": + case "docx": + com.spire.doc.Document document = new com.spire.doc.Document(); + PdfWriter pdfWriter = null; + PdfDocument pdfDocument = null; + com.itextpdf.layout.Document layoutDoc = null; + + try { + document.loadFromFile(urlpath); + style = String.format("width: %s;margin: auto;border: 1px solid #ececec;box-sizing: border-box;box-shadow: 0 0 15px 2px rgba(0,0,0,0.1);background-color:#dddddd;", + "280mm"); + + BufferedImage[] imgs = document.saveToImages(ImageType.Bitmap); + StringBuilder imgHtml = new StringBuilder(); + + // 初始化 PDF(仅当 toPdf 为 true 时) + if (toPdf) { + String pdfPath = urlpath.replace(".docx", ".pdf").replace(".doc", ".pdf"); + pdfWriter = new PdfWriter(pdfPath); + pdfDocument = new PdfDocument(pdfWriter); + layoutDoc = new com.itextpdf.layout.Document(pdfDocument, PageSize.A4); + layoutDoc.setMargins(25, 25, 25, 25); // 对齐 C# 页边距 + } + + for (int i = 0; i < imgs.length; i++) { + int pageNum = i + 1; + BufferedImage img = imgs[i]; + String imgPath = imgFloder + pageNum + ".jpeg"; + + // 保存图片为 JPEG + try (FileOutputStream fos = new FileOutputStream(imgPath)) { + ImageIO.write(img, "jpeg", fos); + } + + imgHtml.append(String.format("", htmlName, pageNum)); + + // 图片添加到 PDF(使用 AreaBreak 新建页面) + if (toPdf && layoutDoc != null) { + if (i > 0) { + layoutDoc.add(new AreaBreak(AreaBreakType.NEXT_PAGE)); // 替代 newPage 方法 + } + ImageData imageData = ImageDataFactory.create(imgPath); + Image pdfImage = new Image(imageData); // 此处导入的是 com.itextpdf.layout.element.Image + + // 缩放适配 A4 页面 + if (pdfImage.getImageHeight() > PageSize.A4.getHeight() - 25 + || pdfImage.getImageWidth() > PageSize.A4.getWidth() - 25) { + pdfImage.scaleToFit(PageSize.A4.getWidth() - 25, PageSize.A4.getHeight() - 25); + } + layoutDoc.add(pdfImage); + } + + img.flush(); // 释放图片资源 + } + + // 生成 HTML +// String htmlContent = String.format(ImgHtml, htmlName, imgHtml, style, +// " "); + String htmlContent = ImgHtml.replace("${0}", htmlName) // 替换标题 + .replace("${1}", imgHtml.toString()) // 替换 imgHtml.ToString() + .replace("${2}", style) + .replace("${3}", " "); + Files.write(Paths.get(hpath), htmlContent.getBytes(StandardCharsets.UTF_8)); + + } catch (IOException e) { + throw new RuntimeException("Word 转图片+HTML 失败:" + e.getMessage(), e); + } finally { + // 释放 Spire Word 资源(无受检异常,直接调用) + if (document != null) { + document.dispose(); + } + + // 处理 iText7 资源关闭的受检异常 + try { + if (layoutDoc != null) { + layoutDoc.close(); + } + if (pdfWriter != null) { + pdfWriter.close(); + } + } catch (IOException e) { + // 转换为运行时异常抛出,或记录日志 + throw new RuntimeException("关闭 PDF 资源时发生 IO 异常", e); + } + } + break; + + case "xlsx": + case "xls": + com.spire.xls.Workbook book = new com.spire.xls.Workbook(); + + try { + // 加载 Excel 文件(Spire.XLS 14.8.2 兼容 API) + book.loadFromFile(urlpath); + String saveHtmlPath = hpath; + String pageHtml = ""; + + // 遍历工作表(对齐 C# Worksheets.Count 逻辑) + for (int i = 0; i < book.getWorksheets().getCount(); i++) { + com.spire.xls.Worksheet sheet = book.getWorksheets().get(i); + String imgPath = imgFloder + i + ".jpeg"; + + // 工作表转图片(Spire.XLS 14.8.2 原生方法) + sheet.saveToImage(imgPath); + + // 多工作表 HTML 路径处理(对齐 C# Replace 逻辑) + if (i > 0) { + saveHtmlPath = hpath.replace(".html", "_" + i + ".html"); + } + + // 构建样式(对齐 C# 页边距和宽度设置) + double width = book.getWorksheets().get(0).getPageSetup().getPageWidth(); + com.spire.xls.PageSetup pageSetup = sheet.getPageSetup(); + style = String.format("width: %fpt;margin: auto;border: 1px solid #ececec;padding:%fpt %fpt %fpt %fpt ;box-sizing: border-box;box-shadow: 0 0 15px 2px rgba(0,0,0,0.1); ", + width, pageSetup.getTopMargin(), pageSetup.getRightMargin(), pageSetup.getBottomMargin(), pageSetup.getLeftMargin()); + + // 上下页链接(完全对齐 C# 链接拼接逻辑) + pageHtml = ""; + if (i <= book.getWorksheets().getCount() - 2) { + pageHtml = "下一页"; + } + if (i > 0) { + pageHtml += (i == 1) + ? "上一页" + : "上一页"; + } + + // 生成 HTML 文件(对齐 C# File.WriteAllText 逻辑) +// String htmlContent = String.format(ImgHtml, htmlName, +// String.format("", htmlName, i), +// style, pageHtml); + String htmlContent = ImgHtml.replace("${0}", htmlName) // 替换标题 + .replace("${1}", String.format("", htmlName, i)) + .replace("${2}", style) + .replace("${3}", pageHtml); + Files.write(Paths.get(saveHtmlPath), htmlContent.getBytes(StandardCharsets.UTF_8)); + } + } catch (IOException e) { + throw new RuntimeException("Excel 转图片+HTML 失败:" + e.getMessage(), e); + } finally { + // 释放 Excel 资源(Spire.XLS 14.8.2 dispose 方法) + if (book != null) { + book.dispose(); + } + } + break; + + default: + throw new IllegalArgumentException("不支持的文件格式:" + extension); + } + } + + /** + * 检查办公文档密码是否有效(严格还原 C# 逻辑) + * + * @param urlpath 文档文件路径(本地路径或网络路径,需确保可访问) + * @param extension 文档扩展名(原 C# 未统一大小写,此处保留原逻辑不转换) + * @param password 待验证的密码(可为 null 或空字符串) + * @return 密码正确返回 true;密码错误/文档加密但未传密码返回 false;其他异常抛出 + * @throws Exception 非密码相关的异常(如文件不存在、格式错误等) + */ + public static boolean CheckOfficPwd(String urlpath, String extension, String password) throws Exception { + try { + switch (extension) { + case "doc": + // 检测 DOC 文件格式及加密状态(完整包名) + com.aspose.words.FileFormatInfo info = com.aspose.words.FileFormatUtil.detectFileFormat(urlpath); + // 文档加密且未提供密码 → 返回 false(还原 C# 短路逻辑) + if (info.isEncrypted() && (password == null || password.isEmpty())) { + return false; + } + // 尝试用密码打开文档(完整包名,还原 C# 匿名 LoadOptions 配置) + com.aspose.words.LoadOptions docLoadOpts = new com.aspose.words.LoadOptions(); + docLoadOpts.setPassword(password); + com.aspose.words.Document doc = new com.aspose.words.Document(urlpath, docLoadOpts); + break; + + case "xls": + // 检测 XLS 文件格式及加密状态(完整包名) + com.aspose.cells.FileFormatInfo xinfo = com.aspose.cells.FileFormatUtil.detectFileFormat(urlpath); + // 文档加密且未提供密码 → 返回 false(还原 C# 短路逻辑) + if (xinfo.isEncrypted() && (password == null || password.isEmpty())) { + return false; + } + // 还原 C# using 逻辑(Aspose.Cells.Workbook 按旧版本适配手动关闭) + com.aspose.cells.LoadOptions xlsLoadOpts = new com.aspose.cells.LoadOptions(); + xlsLoadOpts.setPassword(password); + com.aspose.cells.Workbook workbook = null; + try { + workbook = new com.aspose.cells.Workbook(urlpath, xlsLoadOpts); + } finally { + // 还原 C# using 的自动释放逻辑(手动关闭资源) + if (workbook != null) { + try { + workbook.dispose(); // 旧版本用 close(),新版本可替换为 dispose() + } catch (Exception e) { + // 忽略关闭异常,不影响原业务逻辑(还原 C# using 特性) + } + } + } + break; + + case "pptx": + // 还原 C# using 逻辑(Aspose.Slides.Presentation 适配手动关闭) + com.aspose.slides.LoadOptions pptLoadOpts = new com.aspose.slides.LoadOptions(); + pptLoadOpts.setPassword(password); + // 明确指定文件格式为 PPT(还原原 C# 注释逻辑) + pptLoadOpts.setLoadFormat(com.aspose.slides.LoadFormat.Ppt); + + com.aspose.slides.Presentation presentation = null; + try { + presentation = new com.aspose.slides.Presentation(urlpath, pptLoadOpts); + return true; // 成功打开即返回 true(还原原 C# 分支内返回逻辑) + } finally { + // 还原 C# using 的自动释放逻辑(手动关闭资源) + if (presentation != null) { + try { + presentation.dispose(); // Slides 通常用 dispose() 释放 + } catch (Exception e) { + // 忽略关闭异常,不影响原业务逻辑 + } + } + } + + default: + // 无默认处理(还原 C# 未处理默认分支的逻辑,不抛异常) + break; + } + // 所有支持的格式成功打开后返回 true(还原原 C# 最终返回逻辑) + return true; + } catch (com.aspose.words.IncorrectPasswordException e) { + // 捕获 Aspose.Words 密码错误异常 → 返回 false(还原原 C# 捕获逻辑) + return false; + } catch (Exception e) { + // 捕获所有其他异常,判断消息是否包含 "password" → 返回 false(严格还原 C# IndexOf 逻辑) + if (e.getMessage() != null && e.getMessage().contains("password")) { + return false; + } + // 非密码相关异常,向上抛出(还原原 C# throw 逻辑) + throw e; + } + } + + /** + * 使用 Aspose 将 Word/Excel/PPT 转 PDF(显式声明类路径,避免包冲突) + * + * @param urlpath 源文件路径 + * @param hpath 生成的PDF路径 + * @param extension 文件扩展名(doc/docx/xls/xlsx/ppt/pptx) + * @param toPdf 是否额外生成加密PDF(仅Word生效) + */ + public static boolean saveToPdfByAspose(String urlpath, String hpath, String extension, boolean toPdf, String password) throws Exception { + boolean IsEncrypted = false; + String generatedPdfPath = null; + switch (extension.toLowerCase()) { + case "doc": + case "docx": + com.aspose.words.FileFormatInfo info = com.aspose.words.FileFormatUtil.detectFileFormat(urlpath); + if (info.isEncrypted() && (password == null || password.isEmpty())) { + IsEncrypted = true; + } + com.aspose.words.Document wordDoc = null; + try { + // 处理 Document 构造可能抛出的异常 +// wordDoc = new com.aspose.words.Document(urlpath); + com.aspose.words.LoadOptions options = new com.aspose.words.LoadOptions(); + options.setPassword(password); + wordDoc = new com.aspose.words.Document(urlpath, options); + // 配置 PDF 保存选项 + com.aspose.words.PdfSaveOptions wdOptions = new com.aspose.words.PdfSaveOptions(); + // 直接配置加密详情(仅需用户密码、所有者密码、权限) + com.aspose.words.PdfEncryptionDetails encryptionDetails = new com.aspose.words.PdfEncryptionDetails( + "", // 用户密码(空表示无需密码打开文档) + "password" // 所有者密码(用于设置权限) + ); + encryptionDetails.setPermissions( + com.aspose.words.PdfPermissions.MODIFY_ANNOTATIONS | com.aspose.words.PdfPermissions.DOCUMENT_ASSEMBLY + ); + wdOptions.setEncryptionDetails(encryptionDetails); + wdOptions.setCompliance(com.aspose.words.PdfCompliance.PDF_A_1_B); + + // 处理 save 方法可能抛出的异常 + wordDoc.save(hpath); + setFileFullPermission(hpath); // 设置hpath文件权限 + generatedPdfPath = hpath; + if (toPdf) { + String pdfPath = urlpath.replace(".docx", ".pdf").replace(".doc", ".pdf"); + wordDoc.save(pdfPath, wdOptions); + setFileFullPermission(hpath); // 设置hpath文件权限 + generatedPdfPath = hpath; + } + } catch (Exception e) { + // 将受检异常转换为运行时异常,避免方法声明 throws + log.debug(String.format("Word 转 PDF 失败: %s urlpath: %s", e.getMessage(), urlpath)); + throw new RuntimeException("Word 转 PDF 失败:" + e.getMessage(), e); + } finally { + // Aspose.Words 无 dispose 方法,手动置空帮助GC + wordDoc = null; + } + break; + + case "xls": + case "xlsx": + com.aspose.cells.Workbook excelWorkbook = null; + try { + com.aspose.cells.LoadOptions options = new com.aspose.cells.LoadOptions(); + options.setPassword(password); + excelWorkbook = new com.aspose.cells.Workbook(urlpath, options); + com.aspose.cells.PdfSaveOptions exOptions = new com.aspose.cells.PdfSaveOptions(); + com.aspose.cells.PdfSecurityOptions securityOptions = new com.aspose.cells.PdfSecurityOptions(); + securityOptions.setExtractContentPermission(false); + securityOptions.setPrintPermission(false); + exOptions.setSecurityOptions(securityOptions); + exOptions.setAllColumnsInOnePagePerSheet(true); + exOptions.setOnePagePerSheet(true); + // 修正:Aspose.Cells 中 PdfCompliance 直接位于 com.aspose.cells 包下(无 Rendering 子包) + exOptions.setCompliance(com.aspose.cells.PdfCompliance.PDF_A_1_B); // 修正此处类路径 + excelWorkbook.save(hpath, exOptions); + setFileFullPermission(hpath); // 设置hpath文件权限 + generatedPdfPath = hpath; + IsEncrypted = excelWorkbook.getSettings().getPassword() != null; + } catch (Exception e) { + throw new RuntimeException("Excel 转 PDF 失败:" + e.getMessage(), e); + } finally { + if (excelWorkbook != null) { + excelWorkbook.dispose(); + } + } + break; + + case "ppt": + case "pptx": + com.aspose.slides.Presentation ppt = null; + try { + com.aspose.slides.LoadOptions slideLoadOptions = new com.aspose.slides.LoadOptions(); + slideLoadOptions.setPassword(password); + slideLoadOptions.setLoadFormat(com.aspose.slides.LoadFormat.Ppt); + ppt = new com.aspose.slides.Presentation(urlpath, slideLoadOptions); + IsEncrypted = ppt.getProtectionManager().isEncrypted(); + ppt.save(hpath, com.aspose.slides.SaveFormat.Pdf); + setFileFullPermission(hpath); // 设置hpath文件权限 + generatedPdfPath = hpath; + } catch (Exception e) { + throw new RuntimeException("PPT 转 PDF 失败:" + e.getMessage(), e); + } finally { + if (ppt != null) { + try { + ppt.dispose(); + } catch (Exception e) { + throw new RuntimeException("释放 PPT 资源失败:" + e.getMessage(), e); + } + } + } + break; + default: + throw new IllegalArgumentException("不支持的格式:" + extension); + } + if (IsEncrypted && generatedPdfPath != null) { + String finalHpath = generatedPdfPath; // 内部类需final变量 + new Timer(true).schedule(new TimerTask() { + @Override + public void run() { + // 对应 C# 的 File.Exists(hpath) 判断文件是否存在 + File previewFile = new File(finalHpath); + if (previewFile.exists()) { + // 对应 C# 的 File.Delete(hpath) 删除文件 + boolean deleteSuccess = previewFile.delete(); + // 可选:添加删除日志(如需要) + log.debug(String.valueOf("加密文件预览已删除:" + finalHpath + ",删除结果:" + deleteSuccess)); + } + } + }, 30000); // 延迟 30000 毫秒(30秒)执行,与 C# 一致 + } + return true; + } + + /** + * 核心方法:设置文件为所有用户可读可写权限(兼容Linux/Windows) + * + * @param filePath 文件路径 + */ + private static void setFileFullPermission(String filePath) { + File file = new File(filePath); + if (!file.exists()) { + log.debug(String.valueOf("文件不存在,无法设置权限:" + filePath)); + return; + } + // 1. Windows 系统:设置文件为可读写(移除只读属性) + if (System.getProperty("os.name").toLowerCase().contains("windows")) { + if (file.setWritable(true, false)) { // false表示所有用户可写 + log.debug(String.valueOf("Windows:设置文件可写成功 - " + filePath)); + } + if (file.setReadable(true, false)) { // false表示所有用户可读 + log.debug(String.valueOf("Windows:设置文件可读成功 - " + filePath)); + } + if (file.setExecutable(true, false)) { // false表示所有用户可读 + log.debug(String.valueOf("Windows:设置文件可读成功 - " + filePath)); + } + } + // 2. Linux/Mac 系统:执行 chmod 666 设置权限(rw-rw-rw-) + else { + try { + // 执行 chmod 666 命令 + Process process = Runtime.getRuntime().exec(new String[]{"chmod", "777", filePath}); + int exitCode = process.waitFor(); + if (exitCode == 0) { + log.debug(String.valueOf("Linux:设置文件权限 777 成功 - " + filePath)); + } else { + log.warn(String.valueOf("Linux:设置文件权限失败,退出码:" + exitCode + ",文件:" + filePath)); + } + } catch (IOException | InterruptedException e) { + log.warn(String.valueOf("设置Linux文件权限异常:" + e.getMessage())); + } + } + } + + /** + * 使用 Spire.Presentation 将PPT转PDF(显式声明类路径) + * + * @param urlpath 源PPT路径 + * @param hpath 生成的PDF路径 + */ + public static void savePptToPdfBySprie(String urlpath, String hpath) { + // 显式使用 Spire 的 Presentation(完整路径,避免与Aspose冲突) + com.spire.presentation.Presentation ppt = new com.spire.presentation.Presentation(); + try { + ppt.loadFromFile(urlpath); + // 保存为PDF(指定Spire的FileFormat) + ppt.saveToFile(hpath, com.spire.presentation.FileFormat.PDF); + } catch (Exception e) { + throw new RuntimeException("PPT转PDF失败:" + e.getMessage(), e); + } finally { + ppt.dispose(); // 释放资源 + } + } + + /** + * 使用 Aspose.Words 将Word转图片并生成HTML(显式声明类路径) + * + * @param urlpath 源Word路径 + * @param hpath 生成的HTML路径 + * @param extension 文件扩展名(仅doc/docx) + * @param toPdf 是否同时生成PDF + */ + public static void saveToImgByAspose(String urlpath, String hpath, String extension, boolean toPdf) { + // 路径处理(与 C# Split/Last/Replace 逻辑完全一致) + String[] hpathParts = hpath.split("/"); + String htmlName = hpathParts[hpathParts.length - 1].replace(".html", ""); + String imgFloder = hpath.replace(".html", "_imgs/"); + java.io.File imgDir = new java.io.File(imgFloder); + if (!imgDir.exists()) { + imgDir.mkdirs(); + } + + com.aspose.words.Document wordDoc = null; + try { + // 初始化 Word 文档(与 C# 一致) + wordDoc = new com.aspose.words.Document(urlpath); + + // 图片保存选项配置(完全对齐 C# 的 ImageSaveOptions) + com.aspose.words.ImageSaveOptions imgOptions = new com.aspose.words.ImageSaveOptions(com.aspose.words.SaveFormat.JPEG); + imgOptions.setPrettyFormat(true); + imgOptions.setJpegQuality(100); // 与 C# JpegQuality = 100 一致 + imgOptions.setResolution(200); // 与 C# Resolution = 200 一致 + + // 构建 HTML 图片标签(与 C# StringBuilder 逻辑一致) + StringBuilder imgHtml = new StringBuilder(); + String style = "margin: auto;border: 1px solid #ececec;box-sizing: border-box;box-shadow: 0 0 15px 2px rgba(0,0,0,0.1);"; + + // 可选生成 PDF(与 C# toPdf 逻辑一致) + if (toPdf) { + String pdfPath = urlpath.replace(".docx", ".pdf").replace(".doc", ".pdf"); + wordDoc.save(pdfPath, com.aspose.words.SaveFormat.PDF); + } + + // 逐页生成图片(核心修正:用 PageSet 构造函数指定页码) + int pageCount = wordDoc.getPageCount(); + for (int i = 0; i < pageCount; i++) { + // 修正:PageSet 无 singlePage 方法,直接用 new PageSet(i) 指定第 i 页 + imgOptions.setPageSet(new com.aspose.words.PageSet(i)); + String imgPath = imgFloder + i + ".jpeg"; + wordDoc.save(imgPath, imgOptions); // 逐页保存图片(与 C# 一致) + imgHtml.append(String.format("", htmlName, i)); + } + + // 生成 HTML 文件(与 C# File.WriteAllText + string.Format 逻辑一致) +// String htmlContent = String.format(ImgHtml, htmlName, imgHtml.toString(), style, ""); + String htmlContent = ImgHtml.replace("${0}", htmlName) // 替换标题 + .replace("${1}", imgHtml.toString()) // 替换 imgHtml.ToString() + .replace("${2}", style) + .replace("${3}", ""); + java.nio.file.Files.write( + java.nio.file.Paths.get(hpath), + htmlContent.getBytes(java.nio.charset.StandardCharsets.UTF_8) + ); + + } catch (Exception e) { + throw new RuntimeException("Word 转图片+HTML 失败:" + e.getMessage(), e); + } finally { + // 帮助 GC 回收资源(Java 无 using,等价于 C# 资源释放逻辑) + wordDoc = null; + } + } + + /** + * 对齐C#逻辑:Word转HTML(添加页面样式、统一编码),用Aspose.Words替代Microsoft.Office.Interop.Word + * + * @param filePath 源Word文件路径 + * @param htmlPath 生成的HTML保存路径 + */ + public static void docToHtmlByMicrosoftOffice(String filePath, String htmlPath) { + if (filePath == null || filePath.isEmpty() || htmlPath == null || htmlPath.isEmpty()) { + return; + } + + com.aspose.words.Document wordDoc = null; + try { + // 显式使用Aspose.Words打开Word文档 + wordDoc = new com.aspose.words.Document(filePath); + com.aspose.words.PageSetup pageSetup = wordDoc.getFirstSection().getPageSetup(); + + // 计算样式(对齐C# PageWidth、边距逻辑) + double width = pageSetup.getPageWidth(); + double topMargin = pageSetup.getTopMargin(); + double rightMargin = pageSetup.getRightMargin(); + double bottomMargin = pageSetup.getBottomMargin(); + double leftMargin = pageSetup.getLeftMargin(); + String style = String.format( + "body{width:%fpt;margin: auto;border: 1px solid #ececec;padding:%fpt %fpt %fpt %fpt;box-sizing: border-box;box-shadow: 0 0 15px 2px rgba(0,0,0,0.1);}", + width, topMargin, rightMargin, bottomMargin, leftMargin + ); + + // 保存为HTML(对应C# wdFormatFilteredHTML) + com.aspose.words.HtmlSaveOptions saveOptions = new com.aspose.words.HtmlSaveOptions(com.aspose.words.SaveFormat.HTML); + saveOptions.setExportImagesAsBase64(true); // 避免额外生成图片文件 + wordDoc.save(htmlPath, saveOptions); + + // 转换HTML编码和添加样式(调用下方transHTMLEncoding方法) + transHTMLEncoding(htmlPath, style); + } catch (Exception e) { + throw new RuntimeException("Word转HTML失败:" + e.getMessage(), e); + } finally { + if (wordDoc != null) { + wordDoc = null; // 帮助GC回收 + } + } + } + + /** + * 对齐C#逻辑:修改HTML编码为UTF-8,注入自定义样式 + * + * @param strFilePath HTML文件路径 + * @param style 要注入的CSS样式 + */ + private static void transHTMLEncoding(String strFilePath, String style) { + try { + // 读取HTML文件(对应C# StreamReader,编码适配系统默认) + java.io.BufferedReader sr = new java.io.BufferedReader( + new java.io.InputStreamReader(new java.io.FileInputStream(strFilePath), java.nio.charset.StandardCharsets.UTF_8) + ); + StringBuilder htmlBuilder = new StringBuilder(); + String line; + while ((line = sr.readLine()) != null) { + htmlBuilder.append(line).append("\n"); + } + sr.close(); + String html = htmlBuilder.toString(); + + // 注入样式(替换标签) + html = html.replace("", ""); + + // 添加UTF-8编码meta标签(对应C# Regex判断) + java.util.regex.Pattern charsetPattern = java.util.regex.Pattern.compile("]+charset=", java.util.regex.Pattern.CASE_INSENSITIVE); + java.util.regex.Matcher matcher = charsetPattern.matcher(html); + if (!matcher.find()) { + html = java.util.regex.Pattern.compile("", java.util.regex.Pattern.CASE_INSENSITIVE) + .matcher(html) + .replaceFirst(""); + } + + // 写入HTML文件(对应C# StreamWriter) + java.io.BufferedWriter sw = new java.io.BufferedWriter( + new java.io.OutputStreamWriter(new java.io.FileOutputStream(strFilePath), java.nio.charset.StandardCharsets.UTF_8) + ); + sw.write(html); + sw.close(); + } catch (Exception ex) { + throw new RuntimeException("HTML编码转换失败:" + ex.getMessage(), ex); + } + } + + /** + * 对齐C#逻辑:调用mxcadassembly.exe转换DWG文件为MxWeb格式 + * + * @param fileUrl 源DWG文件路径 + * @param fileName 输出文件名 + * @param dwgDocPath 输出目录路径 + * @return 转换是否成功 + */ + public static boolean convertDwgToMxWeb(String fileUrl, String fileName, String dwgDocPath) { + // 对应C# WebConfigUtil.ServerPath(需用户替换为Java项目的根路径) + String serverPath = System.getProperty("user.dir"); // 临时用项目根路径,需根据实际调整 + String exePath = new java.io.File(serverPath + "/bin/MxCad/mxcadassembly.exe").getAbsolutePath(); + + // 检查exe文件是否存在 + java.io.File exeFile = new java.io.File(exePath); + if (!exeFile.exists()) { + return false; + } + + // 构造JSON参数(对齐C# arguments格式) + String arguments = String.format( + "{\"srcpath\":\"%s\",\"outpath\":\"%s\",\"outname\":\"%s\",\"compression\":0}", + fileUrl.replace("\\", "/"), // Java路径统一用/ + dwgDocPath.replace("\\", "/"), + fileName + ); + + // 启动exe进程(对应C# Process) + java.lang.ProcessBuilder processBuilder = new java.lang.ProcessBuilder(exePath, arguments); + processBuilder.redirectErrorStream(true); // 合并错误流 + java.lang.Process process = null; + try { + process = processBuilder.start(); + process.waitFor(); // 等待进程执行完成 + return process.exitValue() == 0; // 退出码0表示成功 + } catch (Exception ex) { + throw new RuntimeException("DWG转MxWeb失败:" + ex.getMessage(), ex); + } finally { + if (process != null) { + process.destroy(); // 销毁进程 + } + } + } + + /** + * 对齐C#逻辑:用Aspose.Cells/Aspose.Words将Excel/Word转HTML + * + * @param filePath 源文件路径 + * @param savePath 生成的HTML保存路径 + * @param fType 文件类型(xlsx/xls/doc/docx) + */ + public static void saveToHtmlByAspose(String filePath, String savePath, String fType) { + switch (fType.toLowerCase()) { + case "xlsx": + case "xls": + // 显式使用Aspose.Cells处理Excel转HTML + com.aspose.cells.Workbook excelWorkbook = null; + try { + excelWorkbook = new com.aspose.cells.Workbook(filePath); + com.aspose.cells.HtmlSaveOptions xOptions = new com.aspose.cells.HtmlSaveOptions(); + xOptions.setExportGridLines(true); + xOptions.setPresentationPreference(true); + xOptions.setExportWorksheetCSSSeparately(false); + xOptions.setExportImagesAsBase64(true); + + excelWorkbook.save(savePath, xOptions); + } catch (Exception e) { + throw new RuntimeException("Excel转HTML失败:" + e.getMessage(), e); + } finally { + if (excelWorkbook != null) { + excelWorkbook.dispose(); + } + } + break; + + case "doc": + case "docx": + // 显式使用Aspose.Words处理Word转HTML + com.aspose.words.Document wordDoc = null; + try { + wordDoc = new com.aspose.words.Document(filePath); + com.aspose.words.HtmlSaveOptions options = new com.aspose.words.HtmlSaveOptions(); + options.setExportImagesAsBase64(true); + options.setCssStyleSheetType(com.aspose.words.CssStyleSheetType.INLINE); + options.setPrettyFormat(true); + + wordDoc.save(savePath, options); + } catch (Exception e) { + throw new RuntimeException("Word转HTML失败:" + e.getMessage(), e); + } finally { + wordDoc = null; + } + break; + + default: + throw new IllegalArgumentException("不支持的文件类型:" + fType); + } + } + + /** + * 对齐C#逻辑:获取视频时长(秒),用javacv-ffmpeg替代MediaToolkit + * + * @param filePath 视频文件路径 + * @return 视频时长(秒),不支持格式返回0 + */ + public static long getVideoInfo(String filePath) { + // 1. 初始化文件对象,定义支持的视频格式(完全对齐C#的types字符串) + File file = new File(filePath); + String supportTypes = ".avi,.wmv,.mpeg,.mp4,.m4v,.mov,.asf,.flv,.f4v,.rmvb,.rm,.3gp,.vob"; + + // 2. 判断文件扩展名是否在支持列表中(对齐C#的IndexOf逻辑) + String fileExtension = file.exists() ? file.getName().substring(file.getName().lastIndexOf(".")) : ""; + if (fileExtension != null && !fileExtension.isEmpty() && supportTypes.contains(fileExtension)) { + try { + // 3. 第一步:调用自定义工具类获取时长(对齐C#的FormatFactoryUtil.GetVideoTime) + long totalTime = ToInt64(FormatFactoryUtil.getVideoTime(filePath)); + if (totalTime > 0) { + return totalTime; + } + + // 4. 第二步:JavaCV兜底(对齐C#的MediaToolkit逻辑) + FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(filePath); + try { + grabber.start(); + // 转换时长(微秒转秒,对齐C#的Ticks/10000000的单位转换逻辑) + long duration = grabber.getLengthInTime() / 1000000; + return duration > 0 ? duration : 0; + } finally { + if (grabber != null) { + try { + grabber.stop(); + grabber.release(); + } catch (Exception e) { + // 忽略释放异常,对齐C#的using自动释放 + } + } + } + } catch (Exception e) { + // 捕获所有异常返回0(对齐C#的catch逻辑) + return 0; + } + } else { + // 扩展名不支持返回0 + return 0; + } + } + + /** + * 对齐C#逻辑:生成二维码,支持叠加Logo,用ZXing替代QRCodeEncoder + * + * @param str 二维码内容 + * @param scale 缩放比例(<=0时默认4) + * @param logoimgurl Logo图片路径(为空则不叠加) + * @return 生成的二维码Bitmap + */ + public static java.awt.image.BufferedImage makeQRCode(String str, int scale, String logoimgurl) { + if (str == null || str.isEmpty()) { + return null; + } + + // 处理缩放比例(对齐C#逻辑) + int qrScale = scale <= 0 ? 4 : scale; + + // 处理URL编码(对齐C#逻辑) + if (str.toLowerCase().startsWith("http")) { + try { + java.net.URI uri = new java.net.URI(str); + StringBuilder newStr = new StringBuilder(); + newStr.append(uri.getScheme()).append("://").append(uri.getAuthority()); + String[] pathParts = uri.getPath().split("/"); + for (String part : pathParts) { + if (!part.isEmpty()) { + newStr.append("/").append(java.net.URLEncoder.encode(part, "UTF-8").replace("+", "%20")); + } + } + newStr.append(uri.getQuery() != null ? "?" + uri.getQuery() : ""); + str = newStr.toString(); + } catch (Exception e) { + throw new RuntimeException("URL编码失败:" + e.getMessage(), e); + } + } + + // 生成二维码(ZXing配置,对齐C# QRCodeEncoder参数) + com.google.zxing.Writer writer = new com.google.zxing.qrcode.QRCodeWriter(); + com.google.zxing.BarcodeFormat format = com.google.zxing.BarcodeFormat.QR_CODE; + com.google.zxing.EncodeHintType errorCorrection = com.google.zxing.EncodeHintType.ERROR_CORRECTION; + com.google.zxing.qrcode.decoder.ErrorCorrectionLevel errorLevel = com.google.zxing.qrcode.decoder.ErrorCorrectionLevel.M; // 对应C# ERROR_CORRECTION.M + + java.util.Map hints = new java.util.HashMap<>(); + hints.put(errorCorrection, errorLevel); + hints.put(com.google.zxing.EncodeHintType.CHARACTER_SET, "UTF-8"); + hints.put(com.google.zxing.EncodeHintType.MARGIN, 1); + + com.google.zxing.common.BitMatrix bitMatrix; + try { + bitMatrix = writer.encode(str, format, 300, 300, hints); // 300x300基础尺寸 + } catch (com.google.zxing.WriterException e) { + throw new RuntimeException("二维码生成失败:" + e.getMessage(), e); + } + + // 转换为BufferedImage(对应C# Bitmap) + int width = bitMatrix.getWidth(); + int height = bitMatrix.getHeight(); + java.awt.image.BufferedImage qrImage = new java.awt.image.BufferedImage(width, height, java.awt.image.BufferedImage.TYPE_INT_RGB); + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + qrImage.setRGB(x, y, bitMatrix.get(x, y) ? java.awt.Color.BLACK.getRGB() : java.awt.Color.WHITE.getRGB()); + } + } + + // 叠加Logo(对齐C#逻辑) + if (logoimgurl != null && !logoimgurl.isEmpty()) { + java.io.File logoFile = new java.io.File(logoimgurl); + if (logoFile.exists()) { + int logoWh = 7 * qrScale; + java.awt.image.BufferedImage logoImg; + try { + logoImg = javax.imageio.ImageIO.read(logoFile); // 捕获IO异常 + } catch (java.io.IOException e) { + throw new RuntimeException("Logo图片读取失败:" + e.getMessage(), e); + } + + // 缩放Logo + java.awt.Image scaledLogo = logoImg.getScaledInstance(logoWh, logoWh, java.awt.Image.SCALE_SMOOTH); + java.awt.image.BufferedImage bLogo = new java.awt.image.BufferedImage(logoWh, logoWh, java.awt.image.BufferedImage.TYPE_INT_ARGB); + java.awt.Graphics2D g2d = bLogo.createGraphics(); + g2d.drawImage(scaledLogo, 0, 0, null); + g2d.dispose(); + + // 绘制Logo到二维码中心 + java.awt.Graphics g = qrImage.getGraphics(); + int x = (width - logoWh) / 2; + int y = (height - logoWh) / 2; + g.drawImage(bLogo, x, y, null); + g.dispose(); + + logoImg.flush(); + bLogo.flush(); + } + } + + return qrImage; + } + + +} diff --git a/WebErp/weberp/src/main/java/org/example/PageBreaksApi/PageBreaksMapper.java b/WebErp/weberp/src/main/java/org/example/PageBreaksApi/PageBreaksMapper.java new file mode 100644 index 0000000..bf17b90 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/PageBreaksApi/PageBreaksMapper.java @@ -0,0 +1,13 @@ +package org.example.PageBreaksApi; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; + +@Mapper +public interface PageBreaksMapper { + + List> getPageBreaks(@Param("sql") String sql); +} diff --git a/WebErp/weberp/src/main/java/org/example/Service/AuthService.java b/WebErp/weberp/src/main/java/org/example/Service/AuthService.java new file mode 100644 index 0000000..2542cb1 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Service/AuthService.java @@ -0,0 +1,21 @@ +package org.example.Service; + +import org.example.Entity.BaseResponse.BaseResponse; +import org.example.Entity.System.LoginUserInfo; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public interface AuthService { + + // 获取当前用户信息 + LoginUserInfo getCurrentUser(); + + + // 记录系统日志 + void logAction(String message, String actionType); + + + List GetUserByName(String loginAccount, String rec); +} diff --git a/WebErp/weberp/src/main/java/org/example/Service/IModuleEvent.java b/WebErp/weberp/src/main/java/org/example/Service/IModuleEvent.java new file mode 100644 index 0000000..c3073ec --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Service/IModuleEvent.java @@ -0,0 +1,145 @@ +package org.example.Service; + + +import org.example.Entity.BaseResponse.BaseResponse; +import org.example.Entity.Control.Com.SysPoPupMenuBtn; +import org.example.Entity.EventArgs; +import org.example.Entity.System.BillStateEn; +import org.example.Entity.System.ModuleBaseEntity; +import org.example.Enums.SystemEnums; +import org.example.Utils.FileUtil; + +import java.util.EventListener; + +/** + * 功能描述:IPubModuleOperations + */ +public interface IModuleEvent extends EventListener { + + /** + * 模块数据加载前事件 + * + * @param module 模块基础信息 + */ + void beforeModuleDataLoad(ModuleBaseEntity module); + + /** + * 模块数据变更前事件 + * + * @param module 模块基础信息 + * @param aType 操作类型:1新增,2修改,3删除,4作废,5作废恢复 + * @param response 基础响应对象 + */ + void beforeModuleDataChange(ModuleBaseEntity module, SystemEnums.ActionType aType, BaseResponse response); + + /** + * 模块数据变更后事件 + * + * @param module 模块基础信息 + * @param aType 操作类型:1新增,2修改,3删除,4作废,5作废恢复 + * @param response 基础响应对象 + */ + void afterModuleDataChange(ModuleBaseEntity module, SystemEnums.ActionType aType, BaseResponse response); + + /** + * 模块数据删除前事件 + * + * @param module 模块基础信息 + * @param aType 操作类型 + * @param response 基础响应对象 + */ + void beforeModuleDataDelete(ModuleBaseEntity module, SystemEnums.ActionType aType, BaseResponse response); + + /** + * 模块数据删除后事件 + * + * @param module 模块基础信息 + * @param aType 操作类型 + * @param response 基础响应对象 + */ + void afterModuleDataDelete(ModuleBaseEntity module, SystemEnums.ActionType aType, BaseResponse response); + + /** + * 模块状态变更前事件 + * + * @param module 模块基础信息 + * @param aType 操作类型:1提交 2撤回 + * @param response 基础响应对象 + */ + void beforeModuleStateChange(ModuleBaseEntity module, SystemEnums.ActionType aType, BaseResponse response); + + /** + * 模块状态变更后事件 + * + * @param module 模块基础信息 + * @param aType 操作类型:1提交 2撤回 + * @param response 基础响应对象 + */ + void afterModuleStateChange(ModuleBaseEntity module, SystemEnums.ActionType aType, BaseResponse response); + + /** + * 模块审核状态变更前事件 + * + * @param module 模块基础信息 + * @param stateEn 账单状态枚举 + * @param response 基础响应对象 + */ + void beforeModuleAuditStateChange(ModuleBaseEntity module, BillStateEn stateEn, BaseResponse response); + + /** + * 模块审核状态变更后事件 + * + * @param module 模块基础信息 + * @param stateEn 账单状态枚举 + * @param response 基础响应对象 + */ + void afterModuleAuditStateChange(ModuleBaseEntity module, BillStateEn stateEn, BaseResponse response); + + /** + * 模块上下文菜单前事件 + * + * @param btn 系统弹出菜单按钮 + * @param response 基础响应对象 + */ + void beforeModuleContextMenu(SysPoPupMenuBtn btn, BaseResponse response); + + /** + * 模块上下文菜单后事件 + * + * @param btn 系统弹出菜单按钮 + * @param response 基础响应对象 + */ + void afterModuleContextMenu(SysPoPupMenuBtn btn, BaseResponse response); + + /** + * 文件上传前事件 + * + * @param info 文件路径信息 + * @param response 基础响应对象 + */ + void beforeUploadFile(FileUtil.PathInfo info, BaseResponse response); + + /** + * 文件上传后事件 + * + * @param info 文件路径信息 + * @param response 基础响应对象 + */ + void afterUploadFile(FileUtil.PathInfo info, BaseResponse response); + + /** + * 应用程序启动事件 + * + * @param sender 事件源 + * @param e 事件参数 + */ + void appStart(Object sender, EventArgs e); + + /** + * 应用程序结束事件 + * + * @param sender 事件源 + * @param e 事件参数 + */ + void appEnd(Object sender, EventArgs e); +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Service/ModuleImplService.java b/WebErp/weberp/src/main/java/org/example/Service/ModuleImplService.java new file mode 100644 index 0000000..d16da5b --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Service/ModuleImplService.java @@ -0,0 +1,66 @@ +package org.example.Service; + +import org.example.Entity.BaseResponse.BaseResponse; +import org.example.Entity.CusException.CusException; +import org.example.Entity.System.BaseModule; + +import java.io.UnsupportedEncodingException; + +public interface ModuleImplService { + + // 传递基础数据,做数据操作 + BaseModule GetBaseModule(String moudleId, String menuIdea); + + // 重新拿到处理好的resultList +// BaseModuleEntity getModuleIniParams(String moduleId, String menuId, Boolean loadDetail, Boolean isCard, Boolean windowsDirver, String targetModuleId, String detailId, Boolean isChart, String mFields, Boolean atts, Boolean loadLeft, Boolean isAttc); + + // 重新拿到处理好的resultList + // 2026.2.5新增mrp + BaseModule getModuleIniParams(String moduleId, String menuid, String targetModuleId, String detailId, boolean isCard, boolean isChart, String mFields, boolean atts, Boolean loadDetail, Boolean loadLeft, boolean isAttc); + + BaseModule getModuleIniParams(String moduleId, String menuid, String targetModuleId, String detailId, boolean isCard, boolean isChart, String mFields, boolean atts, Boolean loadDetail, Boolean loadLeft, boolean isAttc, boolean isMrp); + + + BaseResponse getFieldData(String ModuleId, + Integer id, String record, String leftRecord, + String pms, String keyField, String keyValue, + Integer fdtype, String contextMenuId, + Boolean readOnly, + String userId, String userName, String baseMainGridViewPrefix, Boolean windowsDirver, String ModuleCode, String MenuId); + + + BaseResponse getFieldDataPam(); + + + BaseResponse getModuleData(); + + BaseResponse getBillIniParams(); + + BaseResponse getBaseAuditStepData(); + + BaseResponse GetAuditIniParams(); + + BaseResponse GetStepDataCounts(); + + BaseResponse GetAddOrUpdFields(); + + BaseResponse GetAttcData() throws UnsupportedEncodingException; + + BaseResponse AddOrUpd() throws UnsupportedEncodingException, CusException; + + BaseResponse Delete(); + + BaseResponse GetCondition(); + + BaseResponse GetModuleCfg(); + + BaseResponse GetModuleRightMenu(); + + BaseResponse GetModuleDetailsData(String moduleId, String leftRecord, String dids); + + BaseResponse GetAuditHistory(String moduleId, String idValue, String isBase); + + BaseResponse GetAddOrUpdData(String moduleId, String detailId, String idValue, String pIdOrPRow, int contextMenuId, boolean isAttc); + + BaseResponse GetModuleCountData(String moduleIds, String _pams, String _where); +} diff --git a/WebErp/weberp/src/main/java/org/example/SystemApi/controller/SystemAjaxApi.java b/WebErp/weberp/src/main/java/org/example/SystemApi/controller/SystemAjaxApi.java new file mode 100644 index 0000000..408f8d4 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/SystemApi/controller/SystemAjaxApi.java @@ -0,0 +1,122 @@ +package org.example.SystemApi.controller; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.example.Api.OptBaseHandler; +import org.example.Entity.Attributes.RequestCheck; +import org.example.Impl.BaseImpl; +import org.example.Impl.SystemImpl; +import org.example.Utils.NativeExtensionUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; + +import static org.example.Utils.NativeExtensionUtils.*; + +@RestController +@Scope("prototype") +@RequestMapping("/Api/SystemAjaxApi") +public class SystemAjaxApi extends OptBaseHandler { + private static final Logger log = LoggerFactory.getLogger(SystemAjaxApi.class); + + + @RequestMapping(value = "/**", method = {RequestMethod.GET, RequestMethod.POST}) + public void handleRequest(HttpServletRequest Request, HttpServletResponse response) throws Exception { + // 调用 BaseHandler 的 processRequest 处理逻辑 + super.processRequest(Request); + } + + @Autowired + private SystemImpl sysOperator; + + public void GetSystems() { + response = sysOperator.GetSystems(ToInt32(getBImpl().Request("id"))); + } + + /// + /// 获取系统名 + /// + @RequestCheck(CheckLogin = false) + public void GetSystemInfo() { + response = sysOperator.GetSystemInfo(); + } + + /// + /// 获取设备宝第三方API地址 + /// + @RequestCheck(CheckLogin = false) + public void GetEmaUrl() { + response = sysOperator.GetEmaUrl(); + } + + + /// + /// 获取子系统 及子系统下的菜单,2020.11.09加入 seriesId的可选参数,根据 GetProSysType是否获取数据来决定是否传递该参数 + /// + public void GetSysMenus() { + BaseImpl bImpl = getBImpl(); + boolean all = toBoolean(bImpl.Request("all")); + String pid = bImpl.Request("pid"); + pid = isNullOrEmpty(pid) ? "0" : pid; + String target = bImpl.Request("target"); + response.setData(sysOperator.GetSysMenus(all ? "" : pid, target)); + response.setSuccess(response.getData() != null); + } + + /// + /// 获取系统名 + /// + @RequestCheck(CheckLogin = false) + public void GetSystemLoginInfo() { + response.setData(sysOperator.GetSystemLoginInfo()); + response.setSuccess(true); + } + + /// + /// 获取多个系统 seriesId 的 下拉框数据,如果没有则忽略该下拉框 + /// 新版弃用 20210513,改为统一用logininfo接口获取 + /// + @RequestCheck(CheckLogin = false) + public void GetProSysType() { + response = sysOperator.GetProSysType(); + } + + /// + /// 新版弃用 20210513 改为统一用logininfo接口获取 20250113修改重新使用 + /// + @RequestCheck(CheckLogin = false) + public void GetDbServer() { + response = sysOperator.GetDbServer(); + } + + @RequestCheck(CheckLogin = false) + public void GetWebUpdateInfo() { + BaseImpl bImpl = getBImpl(); + + // 获取版本号参数并转换为int + int ver = NativeExtensionUtils.ToInt32(bImpl.Request("ver")); + + // 获取系统数据参数 + String sysData = bImpl.Request("cinfo") + ""; + // 判断是否包含URL编码的左花括号("%7b"),如果有则进行解码 + if (sysData.indexOf("%7b") > -1) { + try { + sysData = URLDecoder.decode(sysData, StandardCharsets.UTF_8); + } catch (Exception e) { + // 处理解码异常,可根据实际需求添加日志或默认处理 + log.error("Exception caught", e); + } + } + + // 调用系统操作类获取更新信息并赋值给响应对象 + response = sysOperator.GetWebUpdateInfo(ver, sysData); + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/ADUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/ADUtil.java new file mode 100644 index 0000000..f3cc439 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/ADUtil.java @@ -0,0 +1,443 @@ +package org.example.Utils; + +import javax.naming.*; +import javax.naming.directory.*; +import java.util.Hashtable; + +public class ADUtil { + public static Boolean ADLogin = null; + /** + * LDAP绑定路径 LDAP://test.com + */ + public static String ADPath; + /** + * 管理员登录帐号 + */ + public static String ADUser; + /** + * 管理员登录密码 + */ + public static String ADPwd; + + /** + * 根据用户公共名称取得用户对象 + * + * @param commonName 用户公共名称 + * @return 如果找到该用户,则返回用户对象;否则返回 null + */ + public static DirContext getDirectoryEntry(String commonName) { + DirContext ctx = getDirectoryObject(); + if (ctx == null) return null; + + try { + SearchControls controls = new SearchControls(); + controls.setSearchScope(SearchControls.SUBTREE_SCOPE); + String filter = "(&(&(objectCategory=person)(objectClass=user))(cn=" + commonName + "))"; + + NamingEnumeration results = ctx.search("", filter, controls); + if (results.hasMore()) { + SearchResult result = results.next(); + // 通过 getObject() 获取上下文对象并强制转换 + Object obj = result.getObject(); + if (obj instanceof DirContext) { + return (DirContext) obj; + } + } + return null; + } catch (NamingException e) { + return null; + } finally { + closeContext(ctx); + } + } + + /** + * 根据用户公共名称和密码取得用户对象 + * + * @param commonName 用户公共名称 + * @param password 用户密码 + * @return 如果找到该用户,则返回用户对象;否则返回 null + */ + public static DirContext getDirectoryEntry(String commonName, String password) { + DirContext ctx = getDirectoryObject(commonName, password); + if (ctx == null) return null; + + try { + SearchControls controls = new SearchControls(); + controls.setSearchScope(SearchControls.SUBTREE_SCOPE); + String filter = "(&(&(objectCategory=person)(objectClass=user))(cn=" + commonName + "))"; + + NamingEnumeration results = ctx.search("", filter, controls); + if (results.hasMore()) { + SearchResult result = results.next(); + // 通过 getObject() 获取上下文对象并强制转换 + Object obj = result.getObject(); + if (obj instanceof DirContext) { + return (DirContext) obj; + } + } + return null; + } catch (NamingException e) { + return null; + } finally { + closeContext(ctx); + } + } + + /** + * 根据用户帐号称取得用户对象 + * + * @param sAMAccountName 用户帐号名 + * @return 如果找到该用户,则返回用户对象;否则返回 null + */ + public static DirContext getDirectoryEntryByAccount(String sAMAccountName) { + DirContext ctx = getDirectoryObject(); + if (ctx == null) return null; + + try { + SearchControls controls = new SearchControls(); + controls.setSearchScope(SearchControls.SUBTREE_SCOPE); + String filter = "(&(objectCategory=user)(sAMAccountName=" + sAMAccountName + "))"; + + NamingEnumeration results = ctx.search("", filter, controls); + if (results.hasMore()) { + SearchResult result = results.next(); + return getDirectoryObject(result.getNameInNamespace()); + } + return null; + } catch (NamingException e) { + return null; + } finally { + closeContext(ctx); + } + } + + /** + * 根据用户帐号和密码取得用户对象 + * + * @param sAMAccountName 用户帐号名 + * @param password 用户密码 + * @return 如果找到该用户,则返回回用户对象;否则返回 null + */ + public static DirContext getDirectoryEntryByAccount(String sAMAccountName, String password) { + DirContext de = getDirectoryEntryByAccount(sAMAccountName); + if (de != null) { + try { + Attributes attrs = de.getAttributes(""); + Attribute cnAttr = attrs.get("cn"); + if (cnAttr != null) { + String commonName = (String) cnAttr.get(); + if (getDirectoryEntry(commonName, password) != null) { + return getDirectoryEntry(commonName, password); + } + } + } catch (NamingException e) { + return null; + } finally { + closeContext(de); + } + } + return null; + } + + /** + * 根据组名取得用户组对象 + * + * @param groupName 组名 + * @return 如果找到该组,则返回组对象;否则返回 null + */ + public static DirContext getDirectoryEntryOfGroup(String groupName) { + DirContext ctx = getDirectoryObject(); + if (ctx == null) return null; + + try { + SearchControls controls = new SearchControls(); + controls.setSearchScope(SearchControls.SUBTREE_SCOPE); + String filter = "(&(objectClass=group)(cn=" + groupName + "))"; + + NamingEnumeration results = ctx.search("", filter, controls); + if (results.hasMore()) { + SearchResult result = results.next(); + // 通过 getObject() 获取上下文对象并强制转换 + Object obj = result.getObject(); + if (obj instanceof DirContext) { + return (DirContext) obj; + } + } + return null; + } catch (NamingException e) { + return null; + } finally { + closeContext(ctx); + } + } + + /** + * 创建目录服务上下文对象 + */ + private static DirContext getDirectoryObject() { + return getDirContext(ADPath, ADUser, ADPwd, "simple"); + } + + private static DirContext getDirectoryObject(String userName, String password) { + return getDirContext(ADPath, userName, password, "simple"); + } + + private static DirContext getDirectoryObject(String domainReference) { + return getDirContext(ADPath + domainReference, ADUser, ADPwd, "simple"); + } + + private static DirContext getDirectoryObject(String domainReference, String userName, String password) { + return getDirContext(ADPath + domainReference, userName, password, "simple"); + } + + /** + * 创建LDAP连接上下文 + */ + private static DirContext getDirContext(String url, String user, String password, String authType) { + Hashtable env = new Hashtable<>(); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + env.put(Context.PROVIDER_URL, url); + env.put(Context.SECURITY_AUTHENTICATION, authType); + + if (user != null && !user.isEmpty()) { + env.put(Context.SECURITY_PRINCIPAL, user); + } + if (password != null && !password.isEmpty()) { + env.put(Context.SECURITY_CREDENTIALS, password); + } + + try { + return new InitialDirContext(env); + } catch (NamingException e) { + return null; + } + } + + /** + * 关闭上下文连接 + */ + private static void closeContext(DirContext ctx) { + if (ctx != null) { + try { + ctx.close(); + } catch (NamingException e) { + // 忽略关闭异常 + } + } + } + + /** + * 判断用户与密码是否足够以满足身份验证进而登录 + * + * @param commonName 用户公共名称 + * @param password 密码 + * @return 登录结果枚举 + */ + public static LoginResult login(String commonName, String password) { + DirContext de = getDirectoryEntry(commonName, password); + if (de != null) { + try { + // 获取用户账号控制属性 + Attribute uacAttr = de.getAttributes("").get("userAccountControl"); + if (uacAttr != null) { + int userAccountControl = Integer.parseInt(uacAttr.get().toString()); + if (!isAccountActive(userAccountControl)) { + return LoginResult.LOGIN_USER_ACCOUNT_INACTIVE; + } + } + + if (getDirectoryEntry(commonName, password) != null) { + return LoginResult.LOGIN_USER_OK; + } else { + return LoginResult.LOGIN_USER_PASSWORD_INCORRECT; + } + } catch (NamingException e) { + return LoginResult.LOGIN_USER_PASSWORD_INCORRECT; + } finally { + closeContext(de); + } + } else { + return LoginResult.LOGIN_USER_DOESNT_EXIST; + } + } + + /** + * 判断用户帐号与密码是否足够以满足身份验证进而登录 + * + * @param sAMAccountName 用户帐号 + * @param password 密码 + * @return 登录结果枚举 + */ + public static LoginResult loginByAccount(String sAMAccountName, String password) { + DirContext de = getDirectoryEntryByAccount(sAMAccountName); + if (de != null) { + try { + // 获取用户账号控制属性 + Attribute uacAttr = de.getAttributes("").get("userAccountControl"); + if (uacAttr != null) { + int userAccountControl = Integer.parseInt(uacAttr.get().toString()); + if (!isAccountActive(userAccountControl)) { + return LoginResult.LOGIN_USER_ACCOUNT_INACTIVE; + } + } + + if (getDirectoryEntryByAccount(sAMAccountName, password) != null) { + return LoginResult.LOGIN_USER_OK; + } else { + return LoginResult.LOGIN_USER_PASSWORD_INCORRECT; + } + } catch (NamingException e) { + return LoginResult.LOGIN_USER_PASSWORD_INCORRECT; + } finally { + closeContext(de); + } + } else { + return LoginResult.LOGIN_USER_DOESNT_EXIST; + } + } + + /** + * 判断用户帐号是否激活 + * + * @param userAccountControl 用户帐号属性控制器 + * @return 如果用户帐号已经激活,返回 true;否则则返回 false + */ + public static boolean isAccountActive(int userAccountControl) { + int userAccountControlDisabled = ADS_USER_FLAG_ENUM.ADS_UF_ACCOUNTDISABLE.getValue(); + int flagExists = userAccountControl & userAccountControlDisabled; + return flagExists <= 0; + } + + /** + * 用户登录验证结果 + */ + public enum LoginResult { + /** + * 正常登录 + */ + LOGIN_USER_OK(1), + /** + * 用户不存在 + */ + LOGIN_USER_DOESNT_EXIST(2), + /** + * 用户帐号被禁用 + */ + LOGIN_USER_ACCOUNT_INACTIVE(3), + /** + * 用户密码不正确 + */ + LOGIN_USER_PASSWORD_INCORRECT(4); + + private final int value; + + LoginResult(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } + + /** + * AD用户标志枚举 + */ + public enum ADS_USER_FLAG_ENUM { + /** + * 登录脚本标志 + */ + ADS_UF_SCRIPT(0X0001), + /** + * 用户帐号禁用标志 + */ + ADS_UF_ACCOUNTDISABLE(0X0002), + /** + * 主文件夹标志 + */ + ADS_UF_HOMEDIR_REQUIRED(0X0008), + /** + * 过期标志 + */ + ADS_UF_LOCKOUT(0X0010), + /** + * 用户密码不是必须的 + */ + ADS_UF_PASSWD_NOTREQD(0X0020), + /** + * 密码不能更改标志 + */ + ADS_UF_PASSWD_CANT_CHANGE(0X0040), + /** + * 使用可逆的加密保存密码 + */ + ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED(0X0080), + /** + * 本地帐号标志 + */ + ADS_UF_TEMP_DUPLICATE_ACCOUNT(0X0100), + /** + * 普通用户的默认帐号类型 + */ + ADS_UF_NORMAL_ACCOUNT(0X0200), + /** + * 跨域的信任帐号标志 + */ + ADS_UF_INTERDOMAIN_TRUST_ACCOUNT(0X0800), + /** + * 工作站信任帐号标志 + */ + ADS_UF_WORKSTATION_TRUST_ACCOUNT(0x1000), + /** + * 服务器信任帐号标志 + */ + ADS_UF_SERVER_TRUST_ACCOUNT(0X2000), + /** + * 密码永不过期标志 + */ + ADS_UF_DONT_EXPIRE_PASSWD(0X10000), + /** + * MNS 帐号标志 + */ + ADS_UF_MNS_LOGON_ACCOUNT(0X20000), + /** + * 交互式登录必须使用智能卡 + */ + ADS_UF_SMARTCARD_REQUIRED(0X40000), + /** + * 服务帐号将通过 Kerberos 委托信任 + */ + ADS_UF_TRUSTED_FOR_DELEGATION(0X80000), + /** + * 敏感帐号不能被委托 + */ + ADS_UF_NOT_DELEGATED(0X100000), + /** + * 此帐号需要 DES 加密类型 + */ + ADS_UF_USE_DES_KEY_ONLY(0X200000), + /** + * 不要进行 Kerberos 预身份验证 + */ + ADS_UF_DONT_REQUIRE_PREAUTH(0X4000000), + /** + * 用户密码过期标志 + */ + ADS_UF_PASSWORD_EXPIRED(0X800000), + /** + * 用户帐号号可委托标志 + */ + ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION(0X1000000); + + private final int value; + + ADS_USER_FLAG_ENUM(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Utils/AESUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/AESUtil.java new file mode 100644 index 0000000..15fd12e --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/AESUtil.java @@ -0,0 +1,159 @@ +package org.example.Utils; + + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +/** + * AES对称加密 + */ +public class AESUtil { + private static final String KEY = "12345678901234567890123456789012"; + private static final String MOB_KEY = "lserpasebygyc222"; + private static final String ALGORITHM = "AES"; + private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding"; // Java中PKCS7Padding对应PKCS5Padding + + public static String encrypt(String toEncrypt) { + return encrypt(toEncrypt, KEY); + } + + public static String mobileEncrypt(String toEncrypt) { + return encrypt(toEncrypt, MOB_KEY); + } + + public static String decrypt(String toDecrypt) { + return decrypt(toDecrypt, KEY); + } + + public static String mobileDecrypt(String toDecrypt) { + return decrypt(toDecrypt, MOB_KEY); + } + + /** + * AES加密(加密步骤) + * 1,加密字符串得到二进制数组; + * 2,将二进制数组转为16进制; + * 3,进行base64编码 + * + * @param toEncrypt 要加密的字符串 + * @param key 密钥 + * @return 加密后的字符串 + */ + public static String encrypt(String toEncrypt, String key) { + if (toEncrypt == null || toEncrypt.isEmpty()) { + return ""; + } + try { + // 密钥字节数组(ASCII编码,与C#保持一致) + byte[] keyBytes = key.getBytes(StandardCharsets.US_ASCII); + // 待加密内容字节数组(UTF-8编码) + byte[] sourceBytes = toEncrypt.getBytes(StandardCharsets.UTF_8); + + // 初始化AES加密器 + SecretKeySpec secretKey = new SecretKeySpec(keyBytes, ALGORITHM); + Cipher cipher = Cipher.getInstance(TRANSFORMATION); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + + // 执行加密 + byte[] cryptData = cipher.doFinal(sourceBytes); + + // 二进制转16进制字符串 + String hexCryptString = hex2To16(cryptData); + + // 16进制字符串转字节数组后进行Base64编码 + byte[] hexBytes = hexCryptString.getBytes(StandardCharsets.UTF_8); + return Base64.getEncoder().encodeToString(hexBytes); + + } catch (Exception e) { + throw new RuntimeException("AES加密失败", e); + } + } + + /** + * AES解密(解密步骤) + * 1,将BASE64字符串转为16进制数组 + * 2,将16进制数组转为字符串 + * 3,将字符串转为二进制数据 + * 4,用AES解密数据 + * + * @param toDecrypt 已加密的内容 + * @param key 密钥 + * @return 解密后的字符串 + */ + public static String decrypt(String toDecrypt, String key) { + if (toDecrypt == null || toDecrypt.isEmpty()) { + return ""; + } + try { + // 密钥字节数组(ASCII编码) + byte[] keyBytes = key.getBytes(StandardCharsets.US_ASCII); + + // 初始化AES解密器 + SecretKeySpec secretKey = new SecretKeySpec(keyBytes, ALGORITHM); + Cipher cipher = Cipher.getInstance(TRANSFORMATION); + cipher.init(Cipher.DECRYPT_MODE, secretKey); + + // Base64解码得到16进制字符串字节数组 + byte[] encryptedData = Base64.getDecoder().decode(toDecrypt); + // 转为16进制字符串 + String encryptedString = new String(encryptedData, StandardCharsets.UTF_8); + // 16进制字符串转二进制数组 + byte[] sourceBytes = hex16To2(encryptedString); + + // 执行解密 + byte[] originalData = cipher.doFinal(sourceBytes); + + // 解密后的数据转UTF-8字符串 + return new String(originalData, StandardCharsets.UTF_8); + + } catch (Exception e) { + throw new RuntimeException("AES解密失败", e); + } + } + + /** + * 二进制转16进制字符串 + * + * @param bytes 二进制数组 + * @return 16进制字符串 + */ + private static String hex2To16(byte[] bytes) { + if (bytes == null || bytes.length == 0) { + return ""; + } + StringBuilder sb = new StringBuilder(); + int maxLength = Math.min(65535, bytes.length); // 与C#保持一致的长度限制 + for (int i = 0; i < maxLength; i++) { + // 格式化为两位大写十六进制,与C#的ToString("X2")保持一致 + sb.append(String.format("%02X", bytes[i])); + } + return sb.toString(); + } + + /** + * 16进制字符串转二进制数组 + * + * @param hexString 16进制字符串 + * @return 二进制数组 + */ + private static byte[] hex16To2(String hexString) { + if (hexString == null || hexString.isEmpty()) { + return new byte[0]; + } + // 处理长度为奇数的情况,与C#保持一致 + if (hexString.length() % 2 != 0) { + hexString += " "; + } + hexString = hexString.replace(" ", ""); // 移除空格 + int length = hexString.length() / 2; + byte[] result = new byte[length]; + for (int i = 0; i < length; i++) { + int pos = i * 2; + // 截取两位16进制字符并转换为字节 + result[i] = (byte) Integer.parseInt(hexString.substring(pos, pos + 2), 16); + } + return result; + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Utils/ActiveUserUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/ActiveUserUtil.java new file mode 100644 index 0000000..97eedfa --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/ActiveUserUtil.java @@ -0,0 +1,60 @@ +package org.example.Utils; + + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Component +public class ActiveUserUtil { + public static int expirationPeriod = 15; + + + public static void active(String sessionId, String userName) { + // 缓存键前缀与原C#保持一致 + String cacheKey = "@user_" + sessionId; + // 过期时间:15分钟(原C#的TimeSpan(0, 0, ExpirationPeriod, 0)对应分钟) + CacheUtil.getCacheItem(cacheKey, + (Void v) -> userName, // 转换Lambda表达式为Function + Duration.ofMinutes(ActiveUserUtil.expirationPeriod), + null, + null); + } + + /** + * 获取所有活跃用户 + * + * @return 活跃用户数组,无数据时返回null + */ + public static Object[] getActiveUser() { + // 调用CacheUtil获取所有键以@user_开头的缓存项 + List> caches = CacheUtil.getCaches(kvs -> kvs.getKey().startsWith("@user_")); + + // 若存在缓存项则提取值并转为数组,否则返回null + return caches != null && !caches.isEmpty() + ? caches.stream().map(Map.Entry::getValue).toArray() + : null; + } + + /** + * 统计活跃用户数量(保持原C#逻辑,即使原逻辑可能存在问题) + * + * @return 活跃用户数量 + */ + public static int count() { + // 调用CacheUtil获取所有键以@user_开头的缓存项 + List> caches = CacheUtil.getCaches(kvs -> kvs.getKey().startsWith("@user_")); + + // 保持原逻辑:存在缓存项返回0,否则返回实际数量(注意:此逻辑可能不符合业务预期) + return caches != null && !caches.isEmpty() + ? 0 + : (caches != null ? caches.size() : 0); + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Utils/BlobUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/BlobUtil.java new file mode 100644 index 0000000..b8bcfe1 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/BlobUtil.java @@ -0,0 +1,219 @@ +package org.example.Utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.NClob; +import java.sql.SQLException; +import java.util.*; + +public class BlobUtil { + private static final Logger log = LoggerFactory.getLogger(BlobUtil.class); + + // ===================== 新增:判断是否包含Blob类型的私有方法 ===================== + /** + * 检查列表第一个元素是否包含Blob类型字段(避免全量遍历) + */ + private static boolean hasBlobType(List> dataList) { + if (Objects.isNull(dataList) || dataList.isEmpty()) { + return false; + } + Map firstMap = dataList.get(0); + if (Objects.isNull(firstMap) || firstMap.isEmpty()) { + return false; + } + // 遍历首元素的所有字段,判断是否有Blob类型 + for (Object value : firstMap.values()) { + if (value instanceof Blob) { + return true; // 发现Blob,立即返回true + } + } + return false; // 无Blob类型 + } + + // ===================== 新增:判断是否包含Text(Clob/NClob)类型的私有方法 ===================== + /** + * 检查列表第一个元素是否包含Clob/NClob类型字段(避免全量遍历) + */ + private static boolean hasTextType(List> dataList) { + if (Objects.isNull(dataList) || dataList.isEmpty()) { + return false; + } + Map firstMap = dataList.get(0); + if (Objects.isNull(firstMap) || firstMap.isEmpty()) { + return false; + } + // 遍历首元素的所有字段,判断是否有Clob/NClob类型 + for (Object value : firstMap.values()) { + if (value instanceof Clob || value instanceof NClob) { + return true; // 发现Text类型,立即返回true + } + } + return false; // 无Text类型 + } + + /** + * 遍历List,将所有Blob类型的值转换为byte[],并释放Blob资源 + * + * @param dataList 待处理的List,直接修改原Map(引用传递) + */ + public static void convertBlobToByteArray(List> dataList) { + // 1. 空值校验 + 新增:检查首元素是否有Blob类型,无则直接返回 + if (Objects.isNull(dataList) || dataList.isEmpty()) { + log.info("待处理的List为空,无需转换Blob"); + return; + } + if (!hasBlobType(dataList)) { + log.info("列表首元素无Blob类型字段,跳过Blob转换"); + return; + } + + // 2. 原有转换逻辑(完全不变) + for (Map dataMap : dataList) { + if (Objects.isNull(dataMap) || dataMap.isEmpty()) { + continue; + } + + Set keySet = new HashSet<>(dataMap.keySet()); + for (String key : keySet) { + Object value = dataMap.get(key); + if (value instanceof Blob) { + Blob blob = (Blob) value; + try { + byte[] byteArray = null; + if (blob.length() > 0) { + byteArray = blob.getBytes(1, (int) blob.length()); + } + dataMap.put(key, byteArray); + blob.free(); + log.debug("字段[{}]的Blob已成功转换为byte[],长度:{}", key, + byteArray == null ? 0 : byteArray.length); + } catch (SQLException e) { + log.error("字段[{}]的Blob转换为byte[]失败", key, e); + dataMap.put(key, null); + try { + blob.free(); + } catch (SQLException ex) { + log.error("释放Blob资源失败", ex); + } + } + } + } + } + } + + // ===================== 核心修改:TEXT转换为NVARCHAR对应String ===================== + /** + * 将数据库TEXT类型(NClob/Clob)转换为 适配NVARCHAR规范的Java String + * 匹配Unicode字符集,支持中文/特殊字符,解决序列化与乱码问题 + * @param dataList 业务数据集 + */ + public static void convertTextToNvarcharString(List> dataList) { + // 1. 空值校验 + 新增:检查首元素是否有Text类型,无则直接返回 + if (Objects.isNull(dataList) || dataList.isEmpty()) { + log.info("待处理的List为空,无需转换TEXT类型"); + return; + } + if (!hasTextType(dataList)) { + log.info("列表首元素无Text(Clob/NClob)类型字段,跳过Text转换"); + return; + } + + // 2. 原有转换逻辑(完全不变) + for (Map dataMap : dataList) { + if (Objects.isNull(dataMap) || dataMap.isEmpty()) { + continue; + } + Set keySet = new HashSet<>(dataMap.keySet()); + for (String key : keySet) { + Object value = dataMap.get(key); + try { + if (value instanceof NClob nclob) { + readNclobAsUnicode(key, nclob, dataMap); + } else if (value instanceof Clob clob) { + readClobAsUnicode(key, clob, dataMap); + } + } catch (Exception e) { + log.error("字段[{}] TEXT转NVARCHAR格式字符串失败", key, e); + dataMap.put(key, null); + } + } + } + } + + /** + * 读取NClob为Unicode字符串,完美匹配数据库NVARCHAR类型 + */ + private static void readNclobAsUnicode(String key, NClob nclob, Map dataMap) throws SQLException, IOException { + String content = null; + try (BufferedReader reader = new BufferedReader(nclob.getCharacterStream())) { + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line); + } + content = sb.toString(); + dataMap.put(key, content); + log.debug("字段[{}] NClob(TEXT)转换为NVARCHAR格式String成功,长度:{}", key, content.length()); + } finally { + try { + if (nclob != null) { + nclob.free(); + } + } catch (SQLException e) { + log.error("字段[{}] 释放NClob资源失败", key, e); + } + } + } + + /** + * 读取Clob为Unicode字符串,兼容通用场景 + */ + private static void readClobAsUnicode(String key, Clob clob, Map dataMap) throws SQLException, IOException { + String content = null; + try (BufferedReader reader = new BufferedReader(clob.getCharacterStream())) { + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line); + } + content = sb.toString(); + dataMap.put(key, content); + log.debug("字段[{}] Clob(TEXT)转换为NVARCHAR格式String成功,长度:{}", key, content.length()); + } finally { + try { + if (clob != null) { + clob.free(); + } + } catch (SQLException e) { + log.error("字段[{}] 释放Clob资源失败", key, e); + } + } + } + + /** + * 快捷方法:一键处理所有大对象 Blob + TEXT(NVARCHAR格式) + */ + public static void convertAllLobToStandard(List> dataList) { + // 复用新增的判断逻辑,避免重复检查 + boolean needBlobConvert = hasBlobType(dataList); + boolean needTextConvert = hasTextType(dataList); + + if (needBlobConvert) { + convertBlobToByteArray(dataList); + } else { + log.info("列表首元素无Blob类型字段,跳过Blob转换"); + } + + if (needTextConvert) { + convertTextToNvarcharString(dataList); + } else { + log.info("列表首元素无Text类型字段,跳过Text转换"); + } + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Utils/CacheUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/CacheUtil.java new file mode 100644 index 0000000..a000b1e --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/CacheUtil.java @@ -0,0 +1,622 @@ +package org.example.Utils; + +import java.time.Duration; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Function; + +/** + * 缓存操作工具类 + * 说明:基于ConcurrentHashMap实现线程安全的缓存,支持滑动过期和绝对过期策略 + */ +public class CacheUtil { + private static final int DEFAULT_MAX_ENTRIES = 5000; + private static volatile int maxEntries = DEFAULT_MAX_ENTRIES; + private static final AtomicLong accessSequence = new AtomicLong(); + + // 用于确保线程安全的锁对象 + private static final Object locker = new Object(); + + // 缓存存储的核心Map,键为缓存键,值为封装了缓存内容和过期信息的对象 + private static final ConcurrentHashMap cache = new ConcurrentHashMap<>(); + + // 定时任务线程池,用于清理过期的缓存项 + private static final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + + static { + // 每5分钟执行一次缓存清理任务 + scheduler.scheduleAtFixedRate(CacheUtil::cleanExpiredItems, 5, 5, TimeUnit.MINUTES); + + // 注册JVM关闭钩子,确保程序退出时关闭线程池 + Runtime.getRuntime().addShutdownHook(new Thread(scheduler::shutdown)); + } + + /** + * 缓存项内部类,封装缓存值和过期时间信息 + */ + private static class CacheItem { + Object value; + Long absoluteExpiration; // 绝对过期时间戳(毫秒),null表示无绝对过期 + Long slidingExpiration; // 滑动过期时间(毫秒),null表示无滑动过期 + Long lastAccessTime; // 最后访问时间戳(毫秒) + long lastAccessOrder; + + CacheItem(Object value, Long absoluteExpiration, Long slidingExpiration) { + this.value = value; + this.absoluteExpiration = absoluteExpiration; + this.slidingExpiration = slidingExpiration; + this.lastAccessTime = System.currentTimeMillis(); + this.lastAccessOrder = accessSequence.incrementAndGet(); + } + + /** + * 检查缓存项是否已过期 + */ + boolean isExpired() { + long now = System.currentTimeMillis(); + + // 检查绝对过期 + if (absoluteExpiration != null && now >= absoluteExpiration) { + return true; + } + + // 检查滑动过期 + if (slidingExpiration != null && (now - lastAccessTime) >= slidingExpiration) { + return true; + } + + return false; + } + + /** + * 更新最后访问时间(用于滑动过期策略) + */ + void updateAccessTime() { + this.lastAccessTime = System.currentTimeMillis(); + this.lastAccessOrder = accessSequence.incrementAndGet(); + } + + long effectiveExpirationTime() { + long absolute = absoluteExpiration == null ? Long.MAX_VALUE : absoluteExpiration; + long sliding = slidingExpiration == null ? Long.MAX_VALUE : lastAccessTime + slidingExpiration; + return Math.min(absolute, sliding); + } + } + + /** + * 从缓存中获取指定键的值 + * + * @param key 缓存键 + * @return 缓存值,如果不存在或已过期则返回null + */ + public static Object get(String key) { + CacheItem item = cache.get(key); + + if (item == null || item.isExpired()) { + if (item != null) { + cache.remove(key); // 移除过期项 + } + return null; + } + + // 如果有滑动过期策略,更新访问时间 + item.updateAccessTime(); + + return item.value; + } + + //新增一个get方法 + public static Object get(String key,Class type){ + CacheItem item = cache.get(key); + if (item == null || item.isExpired()) { + if (item != null) { + cache.remove(key); + } + return null; + } + item.updateAccessTime(); + return item.value; + } + + /** + * 向缓存中设置值,使用Duration表示滑动过期时间 + * + * @param key 缓存键 + * @param val 缓存值 + * @param slidingExpiration 滑动过期时间(Duration类型,null表示不使用滑动过期) + * @param absoluteExpiration 绝对过期时间(Date类型,null表示不使用绝对过期) + */ + public static void set(String key, Object val, Duration slidingExpiration, Date absoluteExpiration) { + // 校验参数合法性(键为空或值为null时不设置缓存) + if (val == null || key == null || key.trim().isEmpty()) { + return; + } + + // 处理绝对过期时间:转换为毫秒时间戳(null表示无绝对过期) + Long absoluteExpirationMillis = null; + if (absoluteExpiration != null) { + absoluteExpirationMillis = absoluteExpiration.getTime(); + } + + // 处理滑动过期时间:转换为毫秒(null表示无滑动过期) + Long slidingExpirationMillis = null; + if (slidingExpiration != null) { + // 确保滑动过期时间为正数(避免无效值) + slidingExpirationMillis = slidingExpiration.isNegative() ? null : slidingExpiration.toMillis(); + } + + // 存入缓存 + synchronized (locker) { + cleanExpiredItems(); + cache.put(key, new CacheItem(val, absoluteExpirationMillis, slidingExpirationMillis)); + enforceMaxEntries(); + } + } + + /** + * 获取缓存项,如果不存在则通过getCacheValue函数获取并缓存 + * + * @param key 缓存键 + * @param getCacheValue 获取缓存值的函数 + * @param slidingExpiration 滑动过期时间(Duration类型,null表示不使用滑动过期) + * @param absoluteExpiration 绝对过期时间(Date类型,null表示不使用绝对过期) + * @param enable 是否启用缓存 + * @return 缓存值 + */ + public static Object getCacheItem(String key, Function getCacheValue, + Duration slidingExpiration, Date absoluteExpiration, Boolean enable) { + // 校验获取值的函数不能为null + Objects.requireNonNull(getCacheValue, "getCacheValue cannot be null"); + + // 检查是否启用缓存 + boolean isCacheEnabled = determineCacheEnabled(enable); + + // 不启用缓存或键为空,直接调用函数获取值 + if (!isCacheEnabled || isInvalidKey(key)) { + return getCacheValue.apply(null); + } + + // 尝试从缓存获取 + Object val = get(key); + if (val == null) { + // 双重检查锁定,确保线程安全 + synchronized (locker) { + val = get(key); + if (val == null) { + val = getCacheValue.apply(null); + // 调用优化后的set方法,传入Duration参数 + set(key, val, slidingExpiration, absoluteExpiration); + } + } + } + + return val; + } + + /** + * 判定缓存是否启用 + */ + private static boolean determineCacheEnabled(Boolean enable) { + if (enable != null) { + return enable; + } + // 从配置获取缓存开关状态,默认不启用 + String cacheConfig = WebConfigUtil.get("Cache", "false"); + return "true".equalsIgnoreCase(cacheConfig); + } + + /** + * 判定键是否无效(null或空字符串) + */ + private static boolean isInvalidKey(String key) { + return key == null || key.trim().isEmpty(); + } + + /** + * 移除指定键的缓存 + * + * @param key 缓存键 + */ + public static void remove(String key) { + if (key != null) { + cache.remove(key); + } + } + + /** + * 清空所有缓存 + */ + public static void clearCache() { + cache.clear(); + } + + public static void setMaxEntries(int configuredMaxEntries) { + synchronized (locker) { + maxEntries = Math.max(1, configuredMaxEntries); + enforceMaxEntries(); + } + } + + public static void setMaxEntriesForTests(int configuredMaxEntries) { + setMaxEntries(configuredMaxEntries); + } + + public static int sizeForTests() { + cleanExpiredItems(); + return cache.size(); + } + + public static void resetForTests() { + synchronized (locker) { + cache.clear(); + maxEntries = DEFAULT_MAX_ENTRIES; + accessSequence.set(0); + } + } + + /** + * 清理指定用户的缓存 + * + * @param username 用户名 + * @param userId 用户ID + */ + public static void clearUserCache(String username, String userId) { + List keysToRemove = new ArrayList<>(); + + // 收集包含用户ID和用户名的缓存键 + for (String key : cache.keySet()) { + if (key.contains(userId) && key.contains(username)) { + keysToRemove.add(key); + } + } + + // 移除匹配的缓存项 + for (String key : keysToRemove) { + cache.remove(key); + } + } + + /** + * 获取符合过滤条件的缓存项 + * + * @param filter 过滤函数 + * @return 符合条件的缓存项列表 + */ + public static List> getCaches(Function, Boolean> filter) { + List> result = new ArrayList<>(); + + // 转换为包含实际值的Entry列表 + for (Map.Entry entry : cache.entrySet()) { + // 跳过过期项 + if (entry.getValue().isExpired()) { + continue; + } + + Map.Entry valueEntry = new AbstractMap.SimpleEntry<>( + entry.getKey(), entry.getValue().value + ); + + // 应用过滤条件 + if (filter == null || filter.apply(valueEntry)) { + result.add(valueEntry); + } + } + + return result; + } + + /** + * 清理所有过期的缓存项 + */ + private static void cleanExpiredItems() { + List expiredKeys = new ArrayList<>(); + + // 收集过期的缓存键 + for (Map.Entry entry : cache.entrySet()) { + if (entry.getValue().isExpired()) { + expiredKeys.add(entry.getKey()); + } + } + + // 移除过期项 + for (String key : expiredKeys) { + cache.remove(key); + } + } + + private static void enforceMaxEntries() { + while (cache.size() > maxEntries) { + cache.entrySet().stream() + .min(Comparator + .comparingLong((Map.Entry entry) -> entry.getValue().effectiveExpirationTime()) + .thenComparingLong(entry -> entry.getValue().lastAccessOrder)) + .ifPresent(entry -> cache.remove(entry.getKey())); + } + } +} + + +//package org.example.Utils; +// +//import com.fasterxml.jackson.core.JsonProcessingException; +//import com.fasterxml.jackson.databind.MapperFeature; +//import com.fasterxml.jackson.databind.ObjectMapper; +//import org.springframework.data.redis.core.StringRedisTemplate; +//import org.springframework.stereotype.Component; +// +//import jakarta.annotation.PostConstruct; +//import jakarta.annotation.Resource; +//import java.time.Duration; +//import java.util.*; +//import java.util.concurrent.TimeUnit; +//import java.util.function.Function; +// +///** +// * 缓存操作工具类(基于 Redis 实现,完全匹配原本地缓存行为,无配置键残留) +// * 说明:支持滑动过期、绝对过期策略,线程安全;缓存数据存储于 Redis,重启项目不丢失,清理时无残留 +// */ +//@Component +//public class CacheUtil { +// /** +// * Redis 客户端(Spring Boot 自动配置注入,用于操作 Redis 缓存) +// */ +// @Resource +// private StringRedisTemplate stringRedisTemplate; +// +// /** +// * Jackson 序列化工具(用于 Java 对象与 JSON 字符串的相互转换) +// */ +// public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); +// // 静态代码块初始化配置(如果没有,新增一个) +// static { +// // 🔥 核心配置:反序列化时忽略字段大小写 +// OBJECT_MAPPER.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true); +// +// // 保留你原有的其他配置(如日期格式化等,如果有的话) +// // OBJECT_MAPPER.setDateFormat(...); +// } +// +// /** +// * 静态持有者(解决静态工具类注入 Spring Bean 的问题) +// */ +// private static CacheUtil instance; +// +// /** +// * 初始化静态持有者 +// */ +// @PostConstruct +// public void init() { +// instance = this; +// instance.stringRedisTemplate = this.stringRedisTemplate; +// } +// +// /** +// * 用于确保线程安全的锁对象 +// */ +// private static final Object locker = new Object(); +// +// /** +// * 从缓存中获取指定键的值 +// * +// * @param key 缓存键(非空、非空字符串) +// * @return 缓存值(若不存在或已过期则返回 null) +// */ +// public static Object get(String key) { +// if (isInvalidKey(key)) { +// return null; +// } +// String redisKey = getRedisKey(key); +// +// // 1. 从 Redis 获取 JSON 字符串 +// String jsonValue = instance.stringRedisTemplate.opsForValue().get(redisKey); +// if (jsonValue == null) { +// return null; +// } +// +// // 2. 处理滑动过期:重置 Redis 键的过期时间(若存在滑动过期配置) +// Long ttl = instance.stringRedisTemplate.getExpire(redisKey, TimeUnit.MILLISECONDS); +// if (ttl != null && ttl > 0) { +// instance.stringRedisTemplate.expire(redisKey, ttl, TimeUnit.MILLISECONDS); +// } +// +// // 3. JSON 反序列化为 Java 对象 +// try { +// return OBJECT_MAPPER.readValue(jsonValue, Object.class); +// } catch (JsonProcessingException e) { +// remove(key); // 反序列化失败时移除无效缓存 +// return null; +// } +// } +// +// /** +// * 向缓存中设置值,支持滑动过期和绝对过期策略 +// * +// * @param key 缓存键(非空、非空字符串) +// * @param val 缓存值(非 null) +// * @param slidingExpiration 滑动过期时间(Duration 类型,null 表示不使用滑动过期;访问时会重置过期时间) +// * @param absoluteExpiration 绝对过期时间(Date 类型,null 表示不使用绝对过期;到点强制过期) +// */ +// public static void set(String key, Object val, Duration slidingExpiration, Date absoluteExpiration) { +// if (val == null || isInvalidKey(key)) { +// return; +// } +// String redisKey = getRedisKey(key); +// +// try { +// String jsonValue = OBJECT_MAPPER.writeValueAsString(val); +// +// // 优先处理绝对过期 +// if (absoluteExpiration != null) { +// long absoluteExpireMillis = absoluteExpiration.getTime() - System.currentTimeMillis(); +// if (absoluteExpireMillis > 0) { +// instance.stringRedisTemplate.opsForValue().set(redisKey, jsonValue, absoluteExpireMillis, TimeUnit.MILLISECONDS); +// return; +// } +// } +// +// // 处理滑动过期 +// if (slidingExpiration != null && !slidingExpiration.isNegative()) { +// long slidingExpireMillis = slidingExpiration.toMillis(); +// instance.stringRedisTemplate.opsForValue().set(redisKey, jsonValue, slidingExpireMillis, TimeUnit.MILLISECONDS); +// return; +// } +// +// // 无过期策略:永久存储 +// instance.stringRedisTemplate.opsForValue().set(redisKey, jsonValue); +// +// } catch (JsonProcessingException e) { +// // 序列化失败时不存储缓存 +// } +// } +// +// /** +// * 获取缓存项,若不存在则通过函数获取并缓存(支持缓存穿透处理) +// * +// * @param key 缓存键(非空、非空字符串) +// * @param getCacheValue 获取缓存值的函数(不可为 null) +// * @param slidingExpiration 滑动过期时间(Duration 类型,null 表示不使用滑动过期) +// * @param absoluteExpiration 绝对过期时间(Date 类型,null 表示不使用绝对过期) +// * @param enable 是否启用缓存(null 则从配置读取,默认不启用) +// * @return 缓存值(若缓存未命中则调用函数获取并缓存) +// */ +// public static Object getCacheItem(String key, Function getCacheValue, +// Duration slidingExpiration, Date absoluteExpiration, Boolean enable) { +// Objects.requireNonNull(getCacheValue, "getCacheValue 函数不可为 null"); +// boolean isCacheEnabled = determineCacheEnabled(enable); +// +// if (!isCacheEnabled || isInvalidKey(key)) { +// return getCacheValue.apply(null); +// } +// +// Object val = get(key); +// if (val == null) { +// synchronized (locker) { +// val = get(key); +// if (val == null) { +// val = getCacheValue.apply(null); +// set(key, val, slidingExpiration, absoluteExpiration); +// } +// } +// } +// return val; +// } +// +// /** +// * 移除指定键的缓存 +// * +// * @param key 缓存键(可 null,null 时无操作) +// */ +// public static void remove(String key) { +// if (key == null) { +// return; +// } +// instance.stringRedisTemplate.delete(getRedisKey(key)); +// } +// +// /** +// * 清空所有缓存(所有以 "cache:" 为前缀的 Redis 键) +// */ +// public static void clearCache() { +// Set cacheKeys = instance.stringRedisTemplate.keys("cache:*"); +// if (cacheKeys != null && !cacheKeys.isEmpty()) { +// instance.stringRedisTemplate.delete(cacheKeys); +// } +// } +// +// /** +// * 清理指定用户的缓存(匹配包含用户名和用户 ID 的缓存键) +// * +// * @param username 用户名(可 null) +// * @param userId 用户 ID(可 null) +// */ +// public static void clearUserCache(String username, String userId) { +// if (username == null && userId == null) { +// return; +// } +// +// String pattern = "cache:*"; +// if (username != null && userId != null) { +// pattern = "cache:*" + userId + "*" + username + "*"; +// } else if (userId != null) { +// pattern = "cache:*" + userId + "*"; +// } else if (username != null) { +// pattern = "cache:*" + username + "*"; +// } +// +// Set userCacheKeys = instance.stringRedisTemplate.keys(pattern); +// if (userCacheKeys != null && !userCacheKeys.isEmpty()) { +// instance.stringRedisTemplate.delete(userCacheKeys); +// } +// } +// +// /** +// * 获取符合过滤条件的缓存项列表 +// * +// * @param filter 过滤函数(可 null,null 时返回所有有效缓存项) +// * @return 符合条件的缓存项列表(键为原始缓存键,值为缓存对象) +// */ +// public static List> getCaches(Function, Boolean> filter) { +// List> result = new ArrayList<>(); +// Set cacheKeys = instance.stringRedisTemplate.keys("cache:*"); +// if (cacheKeys == null || cacheKeys.isEmpty()) { +// return result; +// } +// +// for (String key : cacheKeys) { +// String jsonValue = instance.stringRedisTemplate.opsForValue().get(key); +// if (jsonValue == null) { +// continue; +// } +// +// try { +// Object value = OBJECT_MAPPER.readValue(jsonValue, Object.class); +// String originalKey = key.replace("cache:", ""); +// Map.Entry entry = new AbstractMap.SimpleEntry<>(originalKey, value); +// +// if (filter == null || filter.apply(entry)) { +// result.add(entry); +// } +// } catch (JsonProcessingException e) { +// continue; +// } +// } +// return result; +// } +// +// // ------------------------------ 内部工具方法 ------------------------------ +// +// /** +// * 生成带前缀的 Redis 键(避免与其他 Redis 数据冲突) +// * +// * @param originalKey 原始缓存键 +// * @return 带 "cache:" 前缀的 Redis 键 +// */ +// private static String getRedisKey(String originalKey) { +// return "cache:" + originalKey; +// } +// +// /** +// * 判定缓存是否启用(优先取入参,否则从配置读取) +// * +// * @param enable 入参启用标识(可 null) +// * @return true 表示启用缓存,false 表示禁用 +// */ +// private static boolean determineCacheEnabled(Boolean enable) { +// if (enable != null) { +// return enable; +// } +// String cacheConfig = WebConfigUtil.get("Cache", "false"); +// return "true".equalsIgnoreCase(cacheConfig); +// } +// +// /** +// * 判定键是否无效(null 或空字符串) +// * +// * @param key 缓存键 +// * @return true 表示键无效,false 表示键有效 +// */ +// private static boolean isInvalidKey(String key) { +// return key == null || key.trim().isEmpty(); +// } +//} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/ConfigUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/ConfigUtil.java new file mode 100644 index 0000000..6b4ca62 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/ConfigUtil.java @@ -0,0 +1,217 @@ +package org.example.Utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +import jakarta.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * 配置工具类,读取并解析数据库连接配置 + * 功能对应C#的ConfigUtil + */ +@Component +public class ConfigUtil { + private static final Logger log = LoggerFactory.getLogger(ConfigUtil.class); + + + // 静态字段存储配置值 + private static String defaultConnectionString; + + // 非静态setter方法,用@Value注入值后赋值给静态字段 + @Value("${spring.datasource.url:}") + public void setDefaultConnectionString(String url) { + log.debug("Default datasource URL configured: {}", url != null && !url.isBlank()); + // 将注入的非静态值赋值给静态字段 + ConfigUtil.defaultConnectionString = url; + } + // 静态方法供外部访问 + public static String getDefaultConnectionString() { + return defaultConnectionString; + } + + // 静态字段存储默认Provider + private static String defaultProviderName; + private static String defaultProviderType; + + // 非静态setter,用于Spring注入并给静态字段赋值 + @Value("${spring.datasource.driver-class-name}") + public void setDefaultProviderType(String driverClassType) { + ConfigUtil.defaultProviderType = driverClassType; + } + + // 非静态setter,用于Spring注入并给静态字段赋值 + @Value("${custom.database.type}") + public void setDefaultProviderName(String driverClassName) { + ConfigUtil.defaultProviderName = driverClassName; + } + + // 从配置文件注入数据库超时时间 + @Value("${dbTimeout:120}") + private String dbTimeout; + + + // 全局设置的驱动类名(可动态修改) + private static String seteedProvider; + + // 连接字符串解析后的键值对缓存 + private static Map connectionKeyVal; + + // 数据库超时时间缓存 + private static int timeOut = 0; + private static String seteedconstr; + + // 2. 静态实例(用于静态方法访问非静态字段) + private static ConfigUtil instance; + + // 初始化静态实例(在Spring注入完成后执行) + @PostConstruct + public void init() { + instance = this; // 将当前实例赋值给静态变量 + } + + /** + * 设置全局连接字符串 + */ + public static void setConStr(String constr) { + seteedconstr = constr; + connectionKeyVal = null; // 重置缓存 + } + + /** + * 设置全局连接字符串和驱动类名 + */ + public static void setConStr(String constr, String provider) { + seteedconstr = constr; + seteedProvider = provider; + connectionKeyVal = null; // 重置缓存 + } + + /** + * 获取当前有效的连接字符串 + */ + public static String getConnectionString() { + if (seteedconstr != null && !seteedconstr.isEmpty()) { + return seteedconstr; + } + return instance.defaultConnectionString; + } + + /** + * 获取当前有效的驱动类名 + */ + public static String getProviderName() { + if (seteedProvider != null && !seteedProvider.isEmpty()) { + return seteedProvider; + } + return defaultProviderName; + } + + public static String getProviderType() { + if (seteedProvider != null && !seteedProvider.isEmpty()) { + return seteedProvider; + } + return defaultProviderType; + } + + /** + * 获取数据库操作超时时间(秒) + */ + public int getTimeOut() { + if (timeOut <= 0) { + // 移除非数字字符后转换为整数 + String numericTimeout = Pattern.compile("\\D").matcher(dbTimeout).replaceAll(""); + timeOut = NativeExtensionUtils.parseInt(numericTimeout, 120); + } + return timeOut; + } + + /** + * 获取连接字符串解析后的键值对(小写键) + */ + public static Map getConnectionKeyVal() { + if (connectionKeyVal == null) { + String conStr = getConnectionString(); + if (conStr == null || conStr.isEmpty()) { + connectionKeyVal = new HashMap<>(); + return connectionKeyVal; + } + connectionKeyVal = toConDict(conStr); + } + return connectionKeyVal; + } + + /** + + 将连接字符串解析为键值对(键转为小写) + 优先使用;分割解析,若解析后 user 为空,则尝试用?和 & 分割解析 + */ + public static Map toConDict (String conStr) { + Map result = new HashMap<>(); + if (conStr == null || conStr.isEmpty ()) { + return result; + } +// 1. 优先使用;分割解析 + Map semicolonResult = parseWithSemicolon (conStr); + String userValue = semicolonResult.getOrDefault ("user", "").trim (); + if (!userValue.isEmpty ()) { + return semicolonResult; + } +// 2. 若;分割解析的 user 为空,则使用?和 & 分割解析 + return parseWithAmpersand (conStr); + } + + /** + + 用;分割连接字符串解析键值对 + */ + private static Map parseWithSemicolon (String conStr) { + Map result = new HashMap<>(); + String [] parts = conStr.split (";"); + for (String part : parts) { + part = part.trim (); + if (part.isEmpty ()) { + continue; + } + int eqIndex = part.indexOf ('='); + if (eqIndex > 0) { + String key = part.substring (0, eqIndex).trim ().toLowerCase (); + String value = part.substring (eqIndex + 1).trim (); + result.put (key, value); + } + } + return result; + } + + /** + + 用?和 & 分割连接字符串解析键值对(URL 参数格式) + */ + private static Map parseWithAmpersand (String conStr) { + Map result = new HashMap<>(); +// 分割出参数部分(去掉?之前的内容) + String [] urlParts = conStr.split ("\\?", 2); // 最多分割成两部分 + String paramPart = urlParts.length > 1 ? urlParts [1] : conStr; +// 用 & 分割参数 + String [] params = paramPart.split ("&"); + for (String param : params) { + param = param.trim (); + if (param.isEmpty ()) { + continue; + } + int eqIndex = param.indexOf ('='); + if (eqIndex > 0) { + String key = param.substring (0, eqIndex).trim ().toLowerCase (); + String value = param.substring (eqIndex + 1).trim (); + result.put (key, value); + } + } + return result; + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/ConversionUtils.java b/WebErp/weberp/src/main/java/org/example/Utils/ConversionUtils.java new file mode 100644 index 0000000..4e2f611 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/ConversionUtils.java @@ -0,0 +1,17 @@ +package org.example.Utils; + +import java.util.*; + +public class ConversionUtils { + public static List> toLowerColumnName(List> data) { + if (data == null) return new ArrayList<>(); + + List> result = new ArrayList<>(); + for (Map row : data) { + Map lowerRow = new HashMap<>(); + row.forEach((key, value) -> lowerRow.put(key.toLowerCase(Locale.ROOT), value)); + result.add(lowerRow); + } + return result; + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/DMJdbcMultiResultSetUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/DMJdbcMultiResultSetUtil.java new file mode 100644 index 0000000..f886cfc --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/DMJdbcMultiResultSetUtil.java @@ -0,0 +1,100 @@ +package org.example.Utils; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.StatementCallback; + +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class DMJdbcMultiResultSetUtil { + /** + * 执行SQL语句,遍历所有结果集,封装为统一格式返回 + * @param jdbcTemplate JdbcTemplate 实例(由业务层传入,保证灵活性) + * @param sql 待执行的SQL语句(普通SELECT、PL/SQL块、存储过程调用等) + * @return List>> 所有结果集的封装结果 + * 外层List:所有结果集(按执行顺序排列) + * 内层List:单个结果集的所有行数据 + * Map:单行数据的「字段名-字段值」映射 + */ + public static List>> executeMultiResultSet(JdbcTemplate jdbcTemplate, String sql) { + // 入参校验 + if (jdbcTemplate == null) { + throw new IllegalArgumentException("JdbcTemplate 实例不能为空"); + } + if (sql == null || sql.trim().isEmpty()) { + throw new IllegalArgumentException("待执行的SQL语句不能为空"); + } + + try { + // 执行SQL,遍历并封装所有结果集 + return jdbcTemplate.execute((StatementCallback>>>) stmt -> { + // 总结果集:存放所有结果集的封装数据 + List>> totalResultSets = new ArrayList<>(); + // 执行SQL语句,获取第一个结果集的标记 + boolean hasMoreResults = stmt.execute(sql.trim()); + + // 循环遍历所有结果集(包括更新计数,保证不遗漏) + while (hasMoreResults || stmt.getUpdateCount() != -1) { + if (hasMoreResults) { + // 处理当前结果集,封装为 List> + ResultSet rs = stmt.getResultSet(); + List> singleResultSet = null; + try { + singleResultSet = packSingleResultSet(rs); + } catch (Exception e) { + throw new RuntimeException(e); + } + // 将当前结果集存入总结果集列表 + totalResultSets.add(singleResultSet); + // 关闭当前ResultSet,释放资源 + rs.close(); + } + + // 切换到下一个结果集(无更多结果集时,hasMoreResults 为 false,退出循环) + hasMoreResults = stmt.getMoreResults(); + } + + return totalResultSets; + }); + } catch (Exception e) { + // 封装异常信息,方便排查(可根据业务需求自定义异常类型) + throw new RuntimeException("执行SQL并处理多结果集失败,SQL:" + sql, e); + } + } + + /** + * 辅助方法:将单个 ResultSet 封装为 List>(贴合 queryForList 格式) + * @param rs 单个结果集 + * @return 单个结果集的封装数据 + */ + private static List> packSingleResultSet(ResultSet rs) throws Exception { + List> singleResultSet = new ArrayList<>(); + if (rs == null) { + return singleResultSet; + } + + // 获取结果集元数据(字段名、字段数量等) + ResultSetMetaData metaData = rs.getMetaData(); + int columnCount = metaData.getColumnCount(); + + // 遍历结果集的每一行,封装为 Map + while (rs.next()) { + Map rowMap = new HashMap<>(); + for (int i = 1; i <= columnCount; i++) { + // 获取字段名和字段值(字段名保持数据库返回的原始格式,可按需转换为小写/大写) + String columnName = metaData.getColumnName(i); + Object columnValue = rs.getObject(i); + // 存入单行 Map(键:字段名,值:字段值) + rowMap.put(columnName, columnValue); + } + // 将单行 Map 存入单个结果集列表 + singleResultSet.add(rowMap); + } + + return singleResultSet; + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/DataFilterUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/DataFilterUtil.java new file mode 100644 index 0000000..53a8a8d --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/DataFilterUtil.java @@ -0,0 +1,207 @@ +package org.example.Utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 等效实现 C# DataTable.Select(cond) 的 Java 工具类 + */ +public class DataFilterUtil { + + /** + * 等效 C# DataTable.Select(cond):按条件字符串过滤 List> 数据 + * + * @param theDT 待过滤的数据(对应 C# 的 DataTable) + * @param cond 筛选条件(如 "name = '张三' and age > 18",支持 =/!=/>/=/<= 运算符,and/or 连接) + * @return 符合条件的行数据(对应 C# 的 DataRow[]) + * @throws Exception 条件解析失败或数据类型不匹配时抛出 + */ + public static List> select(List> theDT, String cond) throws Exception { + // 结果集:存储符合条件的行 + List> queryResult = new ArrayList<>(); + if (theDT == null || theDT.isEmpty() || cond == null || cond.trim().isEmpty()) { + return queryResult; + } + + // 1. 解析条件字符串(简化版:支持单条件或 "and/or" 连接的多条件,暂不支持括号优先级) + // 分割条件(处理 and/or 连接,忽略前后空格) + String[] conditions = cond.replaceAll("\\s+", " ").split("(?i)\\s+(and|or)\\s+"); + // 分割逻辑运算符(如 ["and", "or"]) + Pattern opPattern = Pattern.compile("(?i)\\s+(and|or)\\s+"); + Matcher opMatcher = opPattern.matcher(cond.replaceAll("\\s+", " ")); + List logicOps = new ArrayList<>(); + while (opMatcher.find()) { + logicOps.add(opMatcher.group(1).toLowerCase()); // 转为小写统一处理(and/or) + } + + // 2. 遍历每一行数据,校验是否符合所有条件 + for (Map row : theDT) { + boolean rowMatch = true; // 标记当前行是否符合条件 + int condIndex = 0; // 条件索引 + + // 逐个校验条件(处理多条件的 and/or 逻辑) + for (String singleCond : conditions) { + singleCond = singleCond.trim(); + if (singleCond.isEmpty()) { + continue; + } + + // 解析单个条件:拆分为「字段名、运算符、值」(支持字符串值带单引号,如 name = '张三') + Pattern condPattern = Pattern.compile("^\\s*([a-zA-Z0-9_]+)\\s*(=|!=|>|<|>=|<=)\\s*('?)(.*?)\\3\\s*$"); + Matcher condMatcher = condPattern.matcher(singleCond); + if (!condMatcher.matches()) { + throw new Exception("条件格式错误:" + singleCond + "(正确格式:字段名 运算符 值,如 name = '张三' 或 age > 18)"); + } + + // 提取条件的三部分 + String field = condMatcher.group(1); // 字段名(如 "name"、"age") + String op = condMatcher.group(2); // 运算符(如 "="、">"、"!=") + String valueStr = condMatcher.group(4); // 值(如 "张三"、"18",已去掉单引号) + + // 3. 校验当前行的该字段是否符合条件 + Object fieldValue = row.get(field); // 当前行的字段值 + if (fieldValue == null) { + // 字段值为 null 时,仅支持 "is null" 类逻辑(此处简化处理:null 不匹配任何非 null 条件) + rowMatch = false; + break; + } + + // 4. 按字段类型(字符串/数值/布尔)执行比较 + boolean singleMatch = compare(fieldValue, op, valueStr); + if (!singleMatch) { + String logicOp = logicOps.get(condIndex - 1); + // 若当前条件不匹配,根据逻辑运算符更新 rowMatch + if (condIndex == 0) { + rowMatch = false; + } else { + + if ("and".equals(logicOp)) { + rowMatch = false; + } else if ("or".equals(logicOp)) { + rowMatch = rowMatch && false; // or 逻辑下,当前条件不匹配不影响之前的匹配结果 + } + } + // and 逻辑下,只要有一个条件不匹配,直接跳出当前行的校验 + if ("and".equals(logicOp) || condIndex == 0) { + break; + } + } else { + // 若当前条件匹配,根据逻辑运算符更新 rowMatch + if (condIndex > 0) { + String logicOp = logicOps.get(condIndex - 1); + if ("and".equals(logicOp)) { + rowMatch = rowMatch && true; + } else if ("or".equals(logicOp)) { + rowMatch = rowMatch || true; + } + } + } + condIndex++; + } + + // 5. 若当前行符合所有条件,加入结果集 + if (rowMatch) { + queryResult.add(row); + } + } + + return queryResult; + } + + /** + * 按数据类型执行比较(支持字符串、数值、布尔类型) + * + * @param fieldValue 行的字段值(Java 原生类型,如 String、Integer、Double、Boolean) + * @param op 运算符(=、!=、>、<、>=、<=) + * @param valueStr 条件中的值(字符串形式,需转为对应类型) + * @return 比较结果(true=符合条件,false=不符合) + * @throws Exception 类型转换失败或不支持的运算符 + */ + private static boolean compare(Object fieldValue, String op, String valueStr) throws Exception { + // 处理字符串类型(fieldValue 是 String 或可转为 String) + if (fieldValue instanceof String) { + String fieldStr = (String) fieldValue; + return compareString(fieldStr, op, valueStr); + } + + // 处理数值类型(Integer、Long、Double、Float 等) + if (fieldValue instanceof Number) { + Number fieldNum = (Number) fieldValue; + double fieldDouble = fieldNum.doubleValue(); // 统一转为 double 比较(避免精度问题) + double valueDouble = Double.parseDouble(valueStr); // 将条件值转为 double + return compareNumber(fieldDouble, op, valueDouble); + } + + // 处理布尔类型 + if (fieldValue instanceof Boolean) { + boolean fieldBool = (Boolean) fieldValue; + boolean valueBool = Boolean.parseBoolean(valueStr); + return compareBoolean(fieldBool, op, valueBool); + } + + // 不支持的类型 + throw new Exception("不支持的字段类型:" + fieldValue.getClass().getName() + "(仅支持 String、Number、Boolean)"); + } + + /** + * 字符串类型比较(支持 =、!=、包含(instr)等逻辑,此处实现基础比较) + */ + private static boolean compareString(String fieldStr, String op, String valueStr) { + return switch (op) { + case "=" -> fieldStr.equals(valueStr); + case "!=" -> !fieldStr.equals(valueStr); + case ">" -> fieldStr.compareTo(valueStr) > 0; // 字符串按字典序比较 + case "<" -> fieldStr.compareTo(valueStr) < 0; + case ">=" -> fieldStr.compareTo(valueStr) >= 0; + case "<=" -> fieldStr.compareTo(valueStr) <= 0; + default -> false; + }; + } + + /** + * 数值类型比较(支持 =、!=、>、<、>=、<=) + */ + private static boolean compareNumber(double fieldDouble, String op, double valueDouble) { + return switch (op) { + case "=" -> fieldDouble == valueDouble; + case "!=" -> fieldDouble != valueDouble; + case ">" -> fieldDouble > valueDouble; + case "<" -> fieldDouble < valueDouble; + case ">=" -> fieldDouble >= valueDouble; + case "<=" -> fieldDouble <= valueDouble; + default -> false; + }; + } + + /** + * 布尔类型比较(仅支持 =、!=) + */ + private static boolean compareBoolean(boolean fieldBool, String op, boolean valueBool) throws Exception { + return switch (op) { + case "=" -> fieldBool == valueBool; + case "!=" -> fieldBool != valueBool; + default -> throw new Exception("布尔类型仅支持 =、!= 运算符,不支持:" + op); + }; + } + + /** + * 等效C#的 FirstOrDefault 方法 - List专用 + * 根据条件匹配第一个符合的Hashtable,无则返回null + */ + public static Map firstOrDefault(List> list, String key, String matchVal) { + if (list == null || list.isEmpty() || key == null || matchVal == null) { + return null; + } + for (Map ht : list) { + Object valObj = ht.get(key); + String val = valObj == null ? "" : valObj.toString(); + if (matchVal.equalsIgnoreCase(val)) { + return ht; + } + } + return null; + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Utils/DataTableUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/DataTableUtil.java new file mode 100644 index 0000000..e424efd --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/DataTableUtil.java @@ -0,0 +1,800 @@ +package org.example.Utils; + +/** + * DataTableUtil工具类来处理数据类型转换 + */ + +import org.apache.commons.lang3.function.TriFunction; +import org.example.Entity.System.SystemMenu; + +import java.lang.reflect.Method; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class DataTableUtil { + + /** + * 需要访问row中的数据 + */ + public static int getIntValue(Map row, String columnName, int defaultValue) { + if (row == null || !row.containsKey(columnName)) { + return defaultValue; + } + columnName = columnName.toLowerCase(); + Object value = row.get(columnName); + if (value instanceof Number) { + return ((Number) value).intValue(); + } + try { + return Integer.parseInt(value.toString()); + } catch (Exception e) { + return defaultValue; + } + } + + public static String getStringValue(Map row, String columnName, String defaultValue) { + if (defaultValue == null) { + defaultValue = ""; + } + Object value = get(row, columnName, defaultValue); + return value != null ? value.toString() : defaultValue; + } + + public static String getStringValue(Map row, String columnName, String firstVal, String defaultValue) { + if (defaultValue == null) { + defaultValue = ""; + } + Object value = get(row, columnName, firstVal, defaultValue); + return value != null ? value.toString() : defaultValue; + } + + public static String getStringValue(Map row, String columnName) { + Object value = get(row, columnName); + return value != null ? value.toString() : ""; + } + + public static String getStringValue(Map row, String[] columnNames, String defaultValue) { + if (defaultValue == null) { + defaultValue = ""; + } + Object value = get(row, columnNames, defaultValue); + return value != null ? value.toString() : ""; + } + + public static boolean getBooleanValue(Map row, String columnName, boolean defaultValue) { + if (row == null || !row.containsKey(columnName)) { + return defaultValue; + } + columnName = columnName.toLowerCase(); + Object value = row.get(columnName); + if (value instanceof Boolean) { + return (Boolean) value; + } + try { + return Boolean.parseBoolean(value.toString()); + } catch (Exception e) { + return defaultValue; + } + } + + public static int getIntValue(String value, int defaultValue) { + if (value == null || value.isEmpty()) { + return defaultValue; + } + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + public static boolean getBooleanValue(String value, boolean defaultValue) { + if (value == null || value.isEmpty()) { + return defaultValue; + } + return Boolean.parseBoolean(value); + } + + /** + * 将对象转换为布尔值 + * 支持的类型包括:Boolean、String、Number、Character、null + * 其他类型将返回默认值 + */ + public static boolean toBoolean(Object value) { + return toBoolean(value, false); + } + + /** + * 将对象转换为布尔值,允许指定默认值 + * 支持的类型包括:Boolean、String、Number、Character、null + */ + public static boolean toBoolean(Object value, boolean defaultValue) { + if (value == null) { + return defaultValue; + } + + if (value instanceof Boolean) { + return (Boolean) value; + } + + if (value instanceof String) { + String str = ((String) value).trim().toLowerCase(); + if (str.isEmpty()) { + return defaultValue; + } + if ("true".equals(str) || "yes".equals(str) || "on".equals(str) || "1".equals(str)) { + return true; + } + if ("false".equals(str) || "no".equals(str) || "off".equals(str) || "0".equals(str)) { + return false; + } + return defaultValue; + } + + if (value instanceof Number) { + double num = ((Number) value).doubleValue(); + return num != 0; + } + + if (value instanceof Character) { + char c = (Character) value; + return c != '0' && c != '\0'; + } + + return defaultValue; + } + + + /** + * 获取值 + * + * @param row 数据行 + * @param name 列名 + * @param defaultVal 默认值 + * @return 获取到的值或默认值 + */ + public static Object getRowVal(Map row, String name, Object defaultVal) { + if (name == null || name.isEmpty() || row == null) { + return defaultVal; + } + + if (row.containsKey(name)) { + Object value = row.get(name); + return isEmptyValue(value) ? defaultVal : value; + } + + String lowerName = name.toLowerCase(); + Optional matchedKey = row.keySet().stream() + .filter(key -> key.toLowerCase().equals(lowerName)) + .findFirst(); + + if (matchedKey.isPresent()) { + Object value = row.get(matchedKey.get()); + return isEmptyValue(value) ? defaultVal : value; + } + + return defaultVal; + } + + /** + * 获取值 + * + * @param row 数据行 + * @param name 列名 + * @param firstVal 优先值 + * @param defaultVal 默认值 + * @return 优先值、获取到的值或默认值 + */ + public static Object getRowVal(Map row, String name, Object firstVal, Object defaultVal) { + return firstVal != null ? firstVal : getRowVal(row, name, defaultVal); + } + + /** + * 获取值 + * + * @param row 数据行 + * @param names 可能的列名数组 + * @param firstVal 优先值 + * @param defaultVal 默认值 + * @return 优先值、获取到的值或默认值 + */ + public static Object getRowVal(Map row, String[] names, Object firstVal, Object defaultVal) { + return firstVal != null ? firstVal : getRowVal(row, names, defaultVal); + } + + /** + * 获取值 + * + * @param row 数据行 + * @param names 可能的列名数组 + * @param defaultVal 默认值 + * @return 获取到的值或默认值 + */ + public static Object getRowVal(Map row, String[] names, Object defaultVal) { + if (names == null || names.length == 0 || row == null) { + return defaultVal; + } + + Object value = defaultVal; + for (String name : names) { + value = getRowVal(row, name, ""); + if (!isEmptyValue(value)) { + return value; + } + } + + return isEmptyValue(value) ? defaultVal : value; + } + + /** + * 获取值(扩展方法) + * + * @param row 数据行 + * @param name 列名 + * @param defaultVal 默认值 + * @return 获取到的值或默认值 + */ + public static Object get(Map row, String name, Object defaultVal) { + return getRowVal(row, name, defaultVal); + } + + public static Object get(Map row, String name) { + return getRowVal(row, name, null); + } + + /** + * 获取值(扩展方法) + * + * @param row 数据行 + * @param name 列名 + * @param firstVal 优先值 + * @param defaultVal 默认值 + * @return 优先值、获取到的值或默认值 + */ + public static Object get(Map row, String name, Object firstVal, Object defaultVal) { + return getRowVal(row, name, firstVal, defaultVal); + } + + /** + * 获取值(扩展方法) + * + * @param row 数据行 + * @param names 可能的列名数组 + * @param defaultVal 默认值 + * @return 获取到的值或默认值 + */ + public static Object get(Map row, String[] names, Object defaultVal) { + return getRowVal(row, names, defaultVal); + } + + /** + * 获取值(扩展方法) + * + * @param row 数据行 + * @param names 可能的列名数组 + * @param firstVal 优先值 + * @param defaultVal 默认值 + * @return 优先值、获取到的值或默认值 + */ + public static Object get(Map row, String[] names, Object firstVal, Object defaultVal) { + return getRowVal(row, names, firstVal, defaultVal); + } + + /** + * 从哈希表获取值 + * + * @param tab 哈希表 + * @param name 键名 + * @param defaultVal 默认值 + * @return 获取到的值或默认值 + */ + public static Object get(Hashtable tab, String name, Object defaultVal) { + String key = tab.keySet().stream() + .filter(k -> k.toLowerCase().equals(name.toLowerCase())) + .findFirst() + .orElse(null); + + if (key == null) { + return defaultVal != null ? defaultVal : ""; + } + + return tab.get(key); + } + + /** + * 将Map的键转换为小写 + * + * @param map 要处理的Map + * @return 键为小写的新Map + */ + public static Map toLowerKeyMap(Map map) { + if (map == null) { + return null; + } + + Map result = new HashMap<>(); + for (Map.Entry entry : map.entrySet()) { + result.put(entry.getKey().toLowerCase(), entry.getValue()); + } + return result; + } + + /** + * 将列表中的Map转换为哈希表列表 + * + * @param list 包含Map的列表 + * @return 哈希表列表 + */ + public static List> toHashTable(List> list) { + return toHashTable(list, null, true, true, false); + } + + /** + * 将列表中的Map转换为哈希表列表 + * + * @param list 包含Map的列表 + * @param ignoreNull 是否忽略空值 + * @return 哈希表列表 + */ + public static List> toHashTable(List> list, boolean ignoreNull) { + return toHashTable(list, null, ignoreNull, true, false); + } + + /** + * 将列表中的Map转换为哈希表列表 + * + * @param list 包含Map的列表 + * @param ignoreNull 是否忽略空值 + * @param lowerCase 键是否转为小写 + * @param nullToNullStr 是否将null转为空字符串 + * @return 哈希表列表 + */ + public static List> toHashTable(List> list, boolean ignoreNull, + boolean lowerCase, boolean nullToNullStr) { + return toHashTable(list, null, ignoreNull, lowerCase, nullToNullStr); + } + + /** + * 将列表中的Map转换为哈希表列表 + * + * @param list 包含Map的列表 + * @param format 格式化字典 + * @param ignoreNull 是否忽略空值 + * @param lowerCase 键是否转为小写 + * @param nullToNullStr 是否将null转为空字符串 + * @return 哈希表列表 + */ + public static List> toHashTable(List> list, Map format, + boolean ignoreNull, boolean lowerCase, boolean nullToNullStr) { + if (list == null) { + return null; + } + + return list.stream() + .map(row -> toHashTable(row, format, ignoreNull, lowerCase, nullToNullStr)) + .collect(Collectors.toList()); + } + + /** + * 将列表中的Map转换为哈希表列表 + * + * @param list 包含Map的列表 + * @param format 格式化字典 + * @param fillColor 填充颜色的函数 + * @param fillStyle 填充样式的函数 + * @param ignoreNull 是否忽略空值 + * @return 哈希表列表 + */ + public static List> toHashTable(List> list, Map format, + Function, Map> fillColor, + Function, Map> fillStyle, + boolean ignoreNull) { + if (list == null) { + return null; + } + + return list.stream() + .map(row -> toHashTable(row, format, fillColor, fillStyle, ignoreNull, true, false)) + .collect(Collectors.toList()); + } + + /** + * 将Map转换为哈希表 + * + * @param row 要转换的Map + * @return 转换后的哈希表 + */ + public static Map toHashTable(Map row) { + return toHashTable(row, null, true, true, false); + } + + /** + * 将Map转换为哈希表 + * + * @param row 要转换的Map + * @param ignoreNull 是否忽略空值 + * @param lowerCase 键是否转为小写 + * @return 转换后的哈希表 + */ + public static Map toHashTable(Map row, boolean ignoreNull, boolean lowerCase) { + return toHashTable(row, null, ignoreNull, lowerCase, false); + } + + /** + * 将Map转换为哈希表 + * + * @param row 要转换的Map + * @param ignoreNull 是否忽略空值 + * @param lowerCase 键是否转为小写 + * @param nullToNullStr 是否将null转为空字符串 + * @return 转换后的哈希表 + */ + public static Map toHashTable(Map row, boolean ignoreNull, + boolean lowerCase, boolean nullToNullStr) { + return toHashTable(row, null, ignoreNull, lowerCase, nullToNullStr); + } + + /** + * 将Map转换为哈希表 + * + * @param row 要转换的Map + * @param format 格式化字典 + * @param ignoreNull 是否忽略空值 + * @param lowerCase 键是否转为小写 + * @param nullToNullStr 是否将null转为空字符串 + * @return 转换后的哈希表 + */ + public static Map toHashTable(Map row, Map format, + boolean ignoreNull, boolean lowerCase, boolean nullToNullStr) { + return toHashTable(row, format, null, null, ignoreNull, lowerCase, nullToNullStr); + } + + /** + * 将Map转换为哈希表 + * + * @param row 要转换的Map + * @param format 格式化字典 + * @param fillColor 填充颜色的函数 + * @param fillStyle 填充样式的函数 + * @param ignoreNull 是否忽略空值 + * @param lowerCase 键是否转为小写 + * @param nullToNullStr 是否将null转为空字符串 + * @return 转换后的哈希表 + */ + public static Map toHashTable(Map row, Map format, + Function, Map> fillColor, + Function, Map> fillStyle, + boolean ignoreNull, boolean lowerCase, boolean nullToNullStr) { + if (row == null) { + return null; + } + + Map hashtable = new HashMap<>(); + + for (Map.Entry entry : row.entrySet()) { + String columnName = entry.getKey(); + Object value = entry.getValue(); + + if (ignoreNull && isEmptyValue(value)) { + continue; + } + + String key = lowerCase ? columnName.toLowerCase() : columnName; + + // 处理null值 + if (nullToNullStr && isEmptyValue(value)) { + value = ""; + } + + // 处理大数据值 + if (value != null && value.toString().startsWith("0E-")) { + value = 0; + } + + // 格式化处理(注释了原C#中的格式化逻辑,保持与原代码一致) + if (!isEmptyValue(value) && format != null && format.containsKey(key)) { + // 原C#代码中取消了后端格式化,此处保持一致 + } + + hashtable.put(key, value); + } + + // 处理颜色 + if (fillColor != null) { + Map fontColors = fillColor.apply(row); + if (fontColors != null && !fontColors.isEmpty()) { + hashtable.put("$fontcolor", fontColors); + } + } + + // 处理样式 + if (fillStyle != null) { + Map styles = fillStyle.apply(row); + if (styles != null && !styles.isEmpty()) { + hashtable.put("$styles", styles); + } + } + + return hashtable; + } + + /** + * 将List>(替代DataTable)转换为HashMap列表 + * 完全保留原方法的逻辑和参数,仅替换底层数据结构 + * + * @param table 数据源(替代原DataTable) + * @param format 格式化字典 + * @param fillColor 行颜色填充处理器 + * @param fillStyle 行样式填充处理器 + * @param ignoreNull 是否忽略空值(默认true) + * @param lowerCase 是否列名转小写(默认true) + * @param nullToNullStr 是否将null转为"null"字符串(默认false) + * @param convert 自定义值转换处理器 + * @return HashMap列表 + */ + public static List> toHashTable( + List> table, + Map format, + Function, Map> fillColor, + Function, Map> fillStyle, + Boolean ignoreNull, + Boolean lowerCase, + Boolean nullToNullStr, + TriFunction, String, Object, Object> convert + ) { + // 处理默认参数 + ignoreNull = (ignoreNull == null) ? true : ignoreNull; + lowerCase = (lowerCase == null) ? true : lowerCase; + nullToNullStr = (nullToNullStr == null) ? false : nullToNullStr; + + // null判断逻辑 + if (table == null) { + return null; + } + + List> list = new ArrayList<>(); + + // 遍历每一行(原DataRow → 现在的Map) + for (Map row : table) { + Map rowMap = toHashTableForRow( + row, format, fillColor, fillStyle, + ignoreNull, lowerCase, nullToNullStr, convert + ); + list.add(rowMap); + } + + return list; + } + + /** + * 行级转换方法(处理单个Map) + */ + private static Map toHashTableForRow( + Map row, + Map format, + Function, Map> fillColor, + Function, Map> fillStyle, + boolean ignoreNull, + boolean lowerCase, + boolean nullToNullStr, + TriFunction, String, Object, Object> convert + ) { + Map rowMap = new HashMap<>(); + + // 1. 处理格式化参数 + if (format != null && !format.isEmpty()) { + rowMap.putAll(format); + } + + // 2. 处理颜色填充 + if (fillColor != null) { + Map colorMap = fillColor.apply(row); + if (colorMap != null) { + rowMap.putAll(colorMap); + } + } + + // 3. 处理样式填充 + if (fillStyle != null) { + Map styleMap = fillStyle.apply(row); + if (styleMap != null) { + rowMap.putAll(styleMap); + } + } + + // 4. 遍历行的所有列(Map的key就是列名) + for (Map.Entry entry : row.entrySet()) { + String columnName = entry.getKey(); + // 列名转小写 + if (lowerCase) { + columnName = columnName.toLowerCase(); + } + + // 获取原始值 + Object value = entry.getValue(); + + // 5. 空值处理 + if (value == null) { + if (ignoreNull) { + continue; // 忽略空值 + } + if (nullToNullStr) { + value = "null"; // null转为"null"字符串 + } + } + + // 6. 自定义值转换(核心:比如填充文件路径) + if (convert != null) { + value = convert.apply(row, columnName, value); + } + + // 7. 放入结果Map + rowMap.put(columnName, value); + } + + return rowMap; + } + + /** + * 将哈希表数组转换为List + * + * @param tabs 哈希表数组 + * @return 转换后的List + */ + public static List> toMapList(Hashtable[] tabs) { + List> result = new ArrayList<>(); + if (tabs == null || tabs.length == 0) { + return result; + } + + Set allKeys = new HashSet<>(); + for (Hashtable tab : tabs) { + allKeys.addAll(tab.keySet()); + } + + for (Hashtable tab : tabs) { + Map row = new HashMap<>(); + for (String key : allKeys) { + Object value = tab.get(key); + row.put(key, value != null ? value.toString() : null); + } + result.add(row); + } + + return result; + } + + /** + * 将Map转换为Dictionary(Java中使用HashMap替代) + * + * @param row 要转换的Map + * @param ignoreNull 是否忽略空值 + * @return 转换后的HashMap + */ + public static Map toDict(Map row, boolean ignoreNull) { + return toDict(row, null, ignoreNull); + } + + /** + * 将Map转换为Dictionary(Java中使用HashMap替代) + * + * @param row 要转换的Map + * @param format 格式化字典 + * @param ignoreNull 是否忽略空值 + * @return 转换后的HashMap + */ + public static Map toDict(Map row, Map format, boolean ignoreNull) { + if (row == null) { + return null; + } + + Map dict = new HashMap<>(); + + for (Map.Entry entry : row.entrySet()) { + String columnName = entry.getKey(); + Object value = entry.getValue(); + + if (ignoreNull && isEmptyValue(value)) { + continue; + } + + String key = columnName.toLowerCase(); + + if (!isEmptyValue(value) && format != null && format.containsKey(key)) { + try { + String formatStr = format.get(key).toString(); + Method toStringMethod = value.getClass().getMethod("toString", String.class); + value = toStringMethod != null ? toStringMethod.invoke(value, formatStr) : value; + } catch (Exception e) { + // 忽略格式化异常 + } + } + + dict.put(key, value); + } + + return dict; + } + + /** + * 判断值是否为空 + * + * @param value 要判断的值 + * @return 是否为空 + */ + private static boolean isEmptyValue(Object value) { + if (value == null) { + return true; + } + if (value instanceof String) { + return ((String) value).trim().isEmpty(); + } + return false; + } + + /** + * 根据数据表和列名获取字段类型 + * + * @param dtVal 数据表(通常为 List> 类型) + * @param columnName 列名 + * @return 字段类型(可能是数据库类型编码 Integer 或 ControlType 枚举) + */ + public static Object getColumnType(List> dtVal, String columnName) { + if (dtVal == null || dtVal.isEmpty() || columnName == null || columnName.isEmpty()) { + return null; + } + + // 1. 尝试从数据表元信息中获取字段类型(假设存在 "FieldDbType" 列存储数据库类型编码) + for (Map row : dtVal) { + String fieldName = DataTableUtil.getStringValue(row, "fieldname", "").toLowerCase(); + if (fieldName.equals(columnName.toLowerCase())) { + // 参考 RowColumn.getFieldDbType() 的实现,返回数据库类型编码 + String dbTypeVal = DataTableUtil.getStringValue(row, "FieldDbType", ""); + return NativeExtensionUtils.parseInt(dbTypeVal); + } + } + return null; + } + + /** + * 将List中所有Map的key转换为小写 + * + * @param listMap 待处理的List数据 + * @return 转换后的List(直接修改原对象,也返回该对象方便链式调用) + */ + public static List> toLowerColumnName(List> listMap) { + // 判空处理,避免空指针异常 + if (listMap == null || listMap.isEmpty()) { + return listMap; + } + + // 遍历每一个Map对象 + for (Map map : listMap) { + if (map == null || map.isEmpty()) { + continue; + } + + // 先收集所有需要转换的key,避免遍历过程中修改Map导致ConcurrentModificationException + Set originalKeys = new HashSet<>(map.keySet()); + + // 遍历原始key,转换为小写并替换 + for (String key : originalKeys) { + String lowerKey = key.toLowerCase(); + // 如果key本身已经是小写,无需处理 + if (key.equals(lowerKey)) { + continue; + } + // 获取原值 + Object value = map.get(key); + // 移除原key + map.remove(key); + // 添加小写key和原值 + map.put(lowerKey, value); + } + } + + return listMap; + } + +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/DateTimeUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/DateTimeUtil.java new file mode 100644 index 0000000..73add60 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/DateTimeUtil.java @@ -0,0 +1,268 @@ +package org.example.Utils; + + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.*; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.*; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * ============================================================================== + * 功能描述:DateTimeUtil + * ============================================================================== + */ +public class DateTimeUtil { + + private static final Date JAN_1ST_1970 = new Date(0); // 1970-01-01 00:00:00 UTC + + /** + * 获取当前时间的毫秒数(UTC时间) + */ + public static long currentTimeMillis() { + return System.currentTimeMillis(); + } + + /** + * 将毫秒数转换为日期时间 + */ + public static Date timeSpanToDate(long timespan) { + return new Date(timespan); + } + + /** + * 获取网络日期时间(从URL响应头的Date字段) + */ + public static String getNetDateTime(String url) { + if (url == null || url.isEmpty()) { + url = "https://www.baidu.com"; + } + + HttpURLConnection connection = null; + try { + URL urlObj = new URL(url); + connection = (HttpURLConnection) urlObj.openConnection(); + connection.setConnectTimeout(3000); + connection.setReadTimeout(3000); + connection.setRequestMethod("HEAD"); + + // 获取响应头中的Date字段 + return connection.getHeaderField("Date"); + } catch (IOException e) { + return ""; + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + + // NIST时间服务器相关配置 + private static final int THRESHOLD_SECONDS = 15; + private static final String[] SERVERS = { + "129.6.15.29", + "132.163.4.101", + "132.163.4.102", + "132.163.4.103", + "128.138.140.44", + "192.43.244.18", + "131.107.1.10", + "66.243.43.21", + "216.200.93.8", + "208.184.49.9", + "207.126.98.204", + "205.188.185.33" + }; + + public static String lastHost = ""; + public static Date lastSysTime; + + /** + * 获取网络时间(NIST服务器) + */ + public static Date getNetTime() { + lastHost = ""; + Date result = null; + + for (String host : SERVERS) { + result = getNISTTime(host); + if (result != null) { + lastHost = host; + break; + } + } + + // 如果所有服务器都失败,返回系统UTC时间 + if (result == null) { + result = new Date(System.currentTimeMillis()); + } + + return result; + } + + /** + * 从NIST服务器获取时间 + */ + private static Date getNISTTime(String host) { + Socket socket = null; + BufferedReader reader = null; + + try { + socket = new Socket(host, 13); + socket.setSoTimeout(3000); + lastSysTime = new Date(System.currentTimeMillis()); // 记录系统时间 + + // 读取服务器响应 + reader = new BufferedReader( + new InputStreamReader(socket.getInputStream(), StandardCharsets.US_ASCII) + ); + String timeStr = reader.readLine(); + + if (timeStr == null || timeStr.isEmpty()) { + return null; + } + + // 验证NIST时间格式 + if (timeStr.length() < 38 || !timeStr.substring(38, 47).equals("UTC(NIST)")) { + return null; + } + if (timeStr.charAt(30) != '0') { + return null; // 服务器状态非最佳 + } + + // 解析时间字符串 + int jd = Integer.parseInt(timeStr.substring(1, 6).trim()); + int yr = Integer.parseInt(timeStr.substring(7, 9).trim()); + int mo = Integer.parseInt(timeStr.substring(10, 12).trim()); + int dy = Integer.parseInt(timeStr.substring(13, 15).trim()); + int hr = Integer.parseInt(timeStr.substring(16, 18).trim()); + int mm = Integer.parseInt(timeStr.substring(19, 21).trim()); + int sc = Integer.parseInt(timeStr.substring(22, 24).trim()); + + // 处理年份(NIST时间格式中的年份是两位数) + if (jd > 51544) { + yr += 2000; + } else { + yr += 1900; + } + + // 构建日期对象(NIST返回的是UTC时间) + Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + calendar.set(yr, mo - 1, dy, hr, mm, sc); + calendar.set(Calendar.MILLISECOND, 0); + return calendar.getTime(); + + } catch (IOException | NumberFormatException e) { + return null; + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + // 忽略关闭异常 + } + } + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + // 忽略关闭异常 + } + } + } + } + + /** + * 计算两个时间的差值(天时分秒) + */ + public static String getDateDiff(Date startTime, Date endTime) { + if (startTime == null || endTime == null) { + return ""; + } + + long diffMs = endTime.getTime() - startTime.getTime(); + if (diffMs <= 0) { + return ""; + } + + StringBuilder result = new StringBuilder(); + + // 计算天数 + long days = diffMs / (24 * 3600 * 1000); + if (days > 0) { + result.append(days).append("天"); + } + + // 剩余毫秒数 + long leave1 = diffMs % (24 * 3600 * 1000); + // 计算小时数 + long hours = leave1 / (3600 * 1000); + if (hours > 0) { + result.append(hours).append("小时"); + } + + // 剩余毫秒数 + long leave2 = leave1 % (3600 * 1000); + // 计算分钟数 + long minutes = leave2 / (60 * 1000); + if (minutes > 0) { + result.append(minutes).append("分钟"); + } + + // 剩余毫秒数 + long leave3 = leave2 % (60 * 1000); + // 计算秒数 + long seconds = Math.round(leave3 / 1000.0); + if (seconds > 0) { + result.append(seconds).append("秒"); + } + + return result.toString(); + } + + /** + * 延迟执行任务(类似JavaScript的setTimeout) + * + * @param callback 回调任务 + * @param delay 延迟时间(毫秒) + * @return 定时任务调度器(用于取消任务) + */ + public static ScheduledExecutorService setTimeOut(Runnable callback, int delay) { + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + scheduler.schedule(() -> { + try { + callback.run(); + } finally { + scheduler.shutdown(); + } + }, delay, TimeUnit.MILLISECONDS); + return scheduler; + } + + /** + * 周期性执行任务(类似JavaScript的setInterval) + * + * @param callback 回调任务 + * @param delay 间隔时间(毫秒) + * @return 定时任务调度器(用于取消任务) + */ + public static ScheduledExecutorService setInterval(Runnable callback, int delay) { + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + scheduler.scheduleAtFixedRate(callback, 0, delay, TimeUnit.MILLISECONDS); + return scheduler; + } + + /** + * 取消周期性任务 + */ + public static void clearInterval(ScheduledExecutorService scheduler) { + if (scheduler != null) { + scheduler.shutdown(); + } + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/DbOperator.java b/WebErp/weberp/src/main/java/org/example/Utils/DbOperator.java new file mode 100644 index 0000000..268cb35 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/DbOperator.java @@ -0,0 +1,1380 @@ +package org.example.Utils; + +import com.github.pagehelper.PageHelper; +import jakarta.annotation.PostConstruct; +import org.example.Enums.*; +import org.example.Impl.BaseImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.jdbc.core.*; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceUtils; +import org.springframework.stereotype.Component; +import org.example.PageBreaksApi.PageBreaksMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.sql.DataSource; +import java.sql.*; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + + +@Component +public class DbOperator { + private static final Logger log = LoggerFactory.getLogger(DbOperator.class); + private final Map slowQuerys = new ConcurrentHashMap<>(); + private boolean isLocal = true; + private boolean closeCon = true; + @Value("${custom.database.type}") + private String databaseType; + @Value("${app.sql.read-only-guard.enabled:false}") + private boolean readOnlyGuardEnabled; + @Autowired // 字段注入 + private JdbcTemplate jdbcTemplate; + @Autowired + PageBreaksMapper pagebreaksmapper; + + @Autowired // 直接注入NamedParameterJdbcTemplate + private NamedParameterJdbcTemplate namedJdbcTemplate; + // 构造注入JdbcTemplate + + // 在构造方法中初始化 + public DbOperator() { + + } + + private PreparedStatement preparedStatement; + private CallableStatement callableStatement; + + // 4. 关键:用@PostConstruct在字段注入完成后初始化currentDataSource + @PostConstruct + public void initDataSource() { + // 此时jdbcTemplate已被注入,可安全调用 + configureJdbcTemplate(this.jdbcTemplate); + log.info("SqlSafetyGuard rule version: {}, readOnlyGuardEnabled={}", + SqlSafetyGuard.RULE_VERSION, readOnlyGuardEnabled); + + // 验证数据源是否有效 + if (this.currentDataSource == null) { + throw new IllegalStateException("初始化失败:JdbcTemplate未关联数据源!请检查spring.datasource配置"); + } + log.debug(String.valueOf("currentDataSource初始化成功!")); + } + + + // 3. 初始化方法:Spring容器初始化DbOperator时自动执行,设置默认连接字符串 + @PostConstruct // 此注解确保该方法在对象创建后、依赖注入完成时自动调用 + public void initDefaultConnectionString() { + // 仅当默认连接字符串不为空时设置(避免覆盖账套切换时的字符串) + if (ConfigUtil.getDefaultConnectionString() != null && !ConfigUtil.getDefaultConnectionString().trim().isEmpty()) { + this.connectionString = ConfigUtil.getDefaultConnectionString(); + log.debug("Default datasource connection string initialized"); + } + } + + + @Autowired + public DbOperator(JdbcTemplate jdbcTemplate) { + configureJdbcTemplate(jdbcTemplate); + } + + public JdbcTemplate getJdbcTemplate() { + return jdbcTemplate; + } + + public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { + configureJdbcTemplate(jdbcTemplate); + } + + private void configureJdbcTemplate(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = Objects.requireNonNull(jdbcTemplate, "JdbcTemplate must not be null"); + this.currentDataSource = Objects.requireNonNull( + this.jdbcTemplate.getDataSource(), + "JdbcTemplate must be associated with a DataSource" + ); + this.namedJdbcTemplate = new NamedParameterJdbcTemplate(this.jdbcTemplate); + } + + private String connectionString; + + public String getConnectionString() { + log.debug("DbOperator connection string requested: {}", connectionString != null && !connectionString.isBlank()); + if (connectionString == null) { + return ConfigUtil.getDefaultConnectionString(); + } + return connectionString; + } + + public void setConnectionString(String connectionString) { + this.connectionString = connectionString; + } + + + // 基于当前ConnectionString的数据源(连接池) + private DataSource currentDataSource; + + public void setCurrentDataSource(DataSource currentDataSource) { + this.currentDataSource = currentDataSource; + } + + /** + * 对应C#的 ConnectionTest(out Exception ex) + * 修复:移除conn.open()调用,因为Java的Connection获取时已打开 + */ + public boolean connectionTest(Exception[] outParam) { + log.debug(String.valueOf("进来了")); + if (outParam == null || outParam.length == 0) { + log.debug(String.valueOf("test1")); + throw new IllegalArgumentException("outParam数组不能为空且长度需≥1"); + } + boolean isSuccess = false; + outParam[0] = null; + Connection conn = null; + + try { + log.debug(String.valueOf("test2" + currentDataSource)); + if (currentDataSource == null) { + throw new IllegalStateException("数据源未初始化,请先调用 setDataSourceConfig()"); + } + + // Java中:getConnection()直接返回已打开的连接,无需open() + conn = currentDataSource.getConnection(); + + // 验证连接是否有效(替代C#的Open()逻辑) + if (conn.isValid(3)) { // 3秒超时验证 + isSuccess = true; + log.debug("Connection test succeeded"); + } else { + throw new SQLException("获取的连接无效"); + } + + } catch (Exception e) { + isSuccess = false; + outParam[0] = e; + log.debug("Connection test failed: {}", e.getMessage()); + + } finally { + // 释放连接(归还到连接池) + if (conn != null) { + try { + conn.close(); // 对应C#的Dispose() + } catch (SQLException closeEx) { + log.debug(String.format("关闭连接时发生异常:%s%n", closeEx.getMessage())); + } + } + } + + return isSuccess; + } + + /** + * 对应C#的 ConnectionTest() + */ + public boolean connectionTest() { + Exception[] outParam = new Exception[1]; + log.debug(String.valueOf("进入测试")); + return connectionTest(outParam); + } + + // 转换参数方向 + private int getParameterDirection(int columnType) { + switch (columnType) { + case 2: + return ParameterMetaData.parameterModeOut; + case 3: + return ParameterMetaData.parameterModeInOut; + case 4: + return -1; // 表示返回值 + default: + return ParameterMetaData.parameterModeIn; + } + } + + // 创建参数 + public Parameter getParameter(String name, Object value, int type, int length, int direction) { + Parameter param = new Parameter(); + param.setName(name); + param.setValue(value); + param.setType(type); + param.setLength(length); + param.setDirection(direction); + return param; + } + + public List> executeDMDataSet(String storeName, Parameter[] pmList) { + // 入参校验 + if (storeName == null || storeName.trim().isEmpty()) { + throw new IllegalArgumentException("存储过程名称不能为空"); + } + if (pmList == null || pmList.length == 0) { + throw new IllegalArgumentException("存储过程参数列表不能为空"); + } + + // 1. 分离OUT参数和IN参数(保留原始顺序) + List outParams = Arrays.stream(pmList) + .filter(parameter -> parameter.getDirection() == 2) // 2=OUT方向 + .collect(Collectors.toList()); + // 存储参数名->参数对象的映射,方便快速取值 + Map paramMap = new LinkedHashMap<>(); + for (Parameter param : pmList) { + // 统一参数名格式(小写,去掉@/p_前缀,便于匹配) + String key = param.getName().toLowerCase().replace("@", "").replace("p_", ""); + paramMap.put(key, param); + } + + StringBuilder sql = new StringBuilder(); + sql.append("DECLARE "); + + // 遍历OUT参数,声明对应的DM变量 + for (int i = 0; i < outParams.size(); i++) { + Parameter outParam = outParams.get(i); + String varName = getVarName(outParam); // 生成变量名(如@p_msg → var_msg) + String dbType = getDMParamType(outParam); // 获取DM对应的数据库类型 + + sql.append(varName).append(" ").append(dbType).append("; "); + } + sql.append("BEGIN "); + sql.append("CALL ").append(storeName).append("("); + + // 按pmList的原始顺序拼接参数 + for (int i = 0; i < pmList.length; i++) { + Parameter param = pmList[i]; + if (i > 0) { + sql.append(", "); // 参数分隔符 + } + + // OUT参数:使用声明的变量名 + if (param.getDirection() == 2) { + sql.append(getVarName(param)); + } + // IN参数:获取参数值并格式化 + else { + String paramValue = getParamValue(paramMap, param.getName()); + sql.append(paramValue); + } + } + sql.append("); "); + + // 4. 构建SELECT段(返回OUT参数值) + if (!outParams.isEmpty()) { + sql.append("SELECT "); + for (int i = 0; i < outParams.size(); i++) { + Parameter outParam = outParams.get(i); + if (i > 0) { + sql.append(", "); + } + // 变量名 AS 别名(别名统一为参数名去掉特殊字符) + String varName = getVarName(outParam); + String alias = outParam.getName().toLowerCase().replace("@", "").replace("p_", "").replace("return", "returncode"); + sql.append(varName).append(" AS ").append(alias); + } + sql.append(" FROM DUAL; "); + } + + // 5. 结束SQL + sql.append("END;"); + + try (Connection conn = jdbcTemplate.getDataSource().getConnection()) { + // 获取数据库元数据,打印当前连接的用户名 + DatabaseMetaData metaData = conn.getMetaData(); + String currentUser = metaData.getUserName(); + log.debug(String.valueOf("当前数据库连接用户名:" + currentUser)); + log.debug(String.valueOf("数据库连接成功:" + (conn != null && !conn.isClosed()))); + } catch (SQLException e) { + throw new RuntimeException(e); + } + return jdbcTemplate.queryForList(sql.toString()); + } + + /** + * 生成OUT参数对应的变量名(避免特殊字符) + * + * @param param 参数对象 + * @return 标准化变量名(如@p_return_code → var_return_code) + */ + private String getVarName(Parameter param) { + String name = param.getName().toLowerCase() + .replace("@", "") + .replace("p_", ""); + return "var_" + name; + } + + /** + * 根据Parameter类型获取DM数据库对应的类型字符串 + * + * @param param 参数对象 + * @return DM数据库类型(如VARCHAR(32000)、INT等) + */ + private String getDMParamType(Parameter param) { + switch (param.getType()) { + case java.sql.Types.INTEGER: + return "INT"; + case java.sql.Types.SMALLINT: + return "SMALLINT"; + case java.sql.Types.BIGINT: + return "BIGINT"; + case java.sql.Types.DECIMAL: + case java.sql.Types.NUMERIC: + return "DECIMAL(18,6)"; // 默认精度,可根据实际调整 + case java.sql.Types.VARCHAR: + case java.sql.Types.CHAR: + // DM的VARCHAR最大长度可设为32000,适配大部分场景 + return "VARCHAR(" + (param.getLength() > 0 ? param.getLength() : 32000) + ")"; + case java.sql.Types.DATE: + return "DATE"; + case java.sql.Types.TIMESTAMP: + return "TIMESTAMP"; + case java.sql.Types.BOOLEAN: + return "BOOLEAN"; + default: + // 默认使用VARCHAR,适配大部分场景 + return "VARCHAR(32000)"; + } + } + + /** + * 获取IN参数的值并格式化(适配DM语法) + * + * @param paramMap 参数名->参数对象的映射 + * @param paramName 原始参数名(如@typeCode、p_billDocumentId) + * @return 格式化后的参数值(字符串加单引号,数字直接返回,null返回NULL) + */ + private String getParamValue(Map paramMap, String paramName) { + // 统一参数名格式,便于匹配 + String key = paramName.toLowerCase().replace("@", "").replace("p_", ""); + Parameter param = paramMap.get(key); + + if (param == null || param.getValue() == null) { + return "NULL"; + } + + Object value = param.getValue(); + // 根据参数类型格式化值 + switch (param.getType()) { + // 数字类型:直接返回值 + case java.sql.Types.INTEGER: + case java.sql.Types.SMALLINT: + case java.sql.Types.BIGINT: + case java.sql.Types.DECIMAL: + case java.sql.Types.DOUBLE: + case java.sql.Types.FLOAT: + return value.toString(); + + // 布尔类型:DM中BOOLEAN用TRUE/FALSE,或1/0 + case java.sql.Types.BOOLEAN: + return ((Boolean) value) ? "TRUE" : "FALSE"; + + // 字符串类型:加单引号,转义内部单引号 + case java.sql.Types.VARCHAR: + case java.sql.Types.CHAR: + String strValue = value.toString().replace("'", "''"); + return "'" + strValue + "'"; + + // 日期类型:按DM格式拼接 + case java.sql.Types.DATE: + case java.sql.Types.TIMESTAMP: + return "TO_DATE('" + value.toString() + "', 'YYYY-MM-DD HH24:MI:SS')"; + + // 其他类型默认按字符串处理 + default: + String defaultStr = value.toString().replace("'", "''"); + return "'" + defaultStr + "'"; + } + } + + // 参数类 + public class Parameter { + private String name; + private Object value; + private int type; + private int length; + private int direction; // 1:in, 2:out, 3:inout, -1:return + private int scale; + + // getter和setter方法 + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Object getValue() { + return value; + } + + public void setValue(Object value) { + this.value = value; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public int getLength() { + return length; + } + + public void setLength(int length) { + this.length = length; + } + + public int getDirection() { + return direction; + } + + public void setDirection(int direction) { + this.direction = direction; + } + + // 新增:scale的getter/setter(用于处理小数位数) + public int getScale() { + return scale; + } + + public void setScale(int scale) { + this.scale = scale; + } + } + + // 执行存储过程并返回数据集 + public ResultSet executeDataSet(String storeName, Parameter[] parameters) throws SQLException { + Connection connection = null; + CallableStatement callableStatement = null; + ResultSet resultSet = null; + + try { + connection = getConnection(); + // 1. 关闭自动提交(开启手动事务管理) + connection.setAutoCommit(false); + + // 2. 构建存储过程调用SQL(原逻辑不变) + StringBuilder callSql = new StringBuilder("{call " + storeName + "("); + for (int i = 0; i < parameters.length; i++) { + if (i > 0) callSql.append(","); + callSql.append("?"); + } + callSql.append(")}"); + + callableStatement = connection.prepareCall(callSql.toString()); + + // 3. 设置参数(原逻辑不变) + for (int i = 0; i < parameters.length; i++) { + Parameter param = parameters[i]; + int paramIndex = i + 1; + + // 注册输出参数(注意:SQL Server 部分类型需指定精度,如 DECIMAL) + if (param.getDirection() == ParameterMetaData.parameterModeOut || + param.getDirection() == ParameterMetaData.parameterModeInOut) { + // 处理特殊类型:若为 DECIMAL,需补充精度(避免 SQL Server 类型不匹配) + if (param.getType() == Types.DECIMAL) { + callableStatement.registerOutParameter(paramIndex, param.getType(), param.getScale()); + } else { + callableStatement.registerOutParameter(paramIndex, param.getType(), param.getLength()); + } + } + + // 设置输入参数值 + if (param.getDirection() == ParameterMetaData.parameterModeIn + //param.getDirection() == ParameterMetaData.parameterModeInOut + ) { + if (param.getValue() != null) { + callableStatement.setObject(paramIndex, param.getValue()); + } else { + callableStatement.setNull(paramIndex, param.getType()); + } + } + } + + log.debug(String.valueOf(callSql + " callSql")); + // 4. 执行存储过程 + boolean hasResultSet = callableStatement.execute(); + // 处理多结果集(SQL Server 存储过程可能返回多个结果集,需遍历获取最后一个) + while (callableStatement.getMoreResults()) { + if (resultSet != null) resultSet.close(); // 关闭前一个结果集,避免资源泄漏 + resultSet = callableStatement.getResultSet(); + } + // 若未获取到结果集,直接取当前结果集 + if (resultSet == null && hasResultSet) { + resultSet = callableStatement.getResultSet(); + } + // 5. 若存储过程无异常,手动提交事务(若存储过程内部已提交,可注释此句) + // 注意:若存储过程内部有 COMMIT/ROLLBACK,需与存储过程逻辑对齐,避免重复提交 + connection.commit(); + return resultSet; + + } catch (SQLException e) { + // 6. 异常时回滚事务(确保事务闭环) + if (connection != null && !connection.isClosed()) { + try { + connection.rollback(); + log.info("执行存储过程失败,已回滚事务: " + storeName + " " + e.getMessage()); + } catch (SQLException rollbackEx) { + log.info("事务回滚失败: " + storeName + " " + rollbackEx.getMessage()); + } + } + throw new SQLException("执行存储过程失败,已回滚事务: " + storeName + " " + e.getMessage()); // 重新抛出异常,让上层处理 + + } finally { + // 7. 释放资源(按 ResultSet → Statement → Connection 顺序关闭) + if (resultSet != null) try { + resultSet.close(); + } catch (SQLException e) { + log.info("ResultSet关闭失败" + e.getMessage()); + } + if (callableStatement != null) try { + callableStatement.close(); + } catch (SQLException e) { + log.info("CallableStatement关闭失败" + e.getMessage()); + } + if (connection != null) { + try { + // 恢复自动提交(避免影响后续连接复用,尤其使用连接池时) + connection.setAutoCommit(true); + connection.close(); + } catch (SQLException e) { + log.info("Connection关闭失败" + e.getMessage()); + } + } + } + } + + // 释放资源 + public void dispose() { + try { + Connection connection = getConnection(); + if (callableStatement != null) callableStatement.close(); + if (preparedStatement != null) preparedStatement.close(); + if (connection != null) connection.close(); + } catch (SQLException e) { + log.info("关闭数据库资源失败"); + } + } + + /** + * 获取存储过程参数列表 + * + * @param storeName 存储过程名称(格式:[schema.]procedureName) + * @return 存储过程参数列表 + */ + public Parameter[] getStoreParams(String storeName) throws SQLException { + List parameters = new ArrayList<>(); + ResultSet rs = null; + Connection connection = null; + PreparedStatement pstmt = null; + // 达梦数据库标识 + boolean isDM = ConfigUtil.getProviderName().equals("dm"); + // 达梦存储过程所属模式(确认是LSERP_JTCS,若查询不到可改为PUBLIC/数据库名) + String dmSchema = "LSERP_JTCS"; + // 人大金仓/其他库默认schema为null + String otherSchema = null; + try { + // 1. 获取数据库连接 + connection = jdbcTemplate.getDataSource().getConnection(); + log.debug(String.valueOf("当前解析存储过程:" + storeName + ",数据库类型:" + (isDM ? "达梦" : "其他"))); + // ===================== 核心改造:达梦端直接查系统表(替代元数据) ===================== + if (isDM) { + /* + * 达梦系统表SYS.ALL_ARGUMENTS:存储所有可访问的存储过程/函数参数 + * 关键字段: + * OWNER:存储过程所属模式(schema) + * OBJECT_NAME:存储过程名 + * ARGUMENT_NAME:参数名(NULL表示返回值) + * IN_OUT:参数方向(IN/OUT/IN OUT) + * DATA_TYPE:参数数据类型 + * DATA_LENGTH:参数长度(字节) + * POSITION:参数位置(从1开始,顺序严格对应存储过程定义) + */ + String dmSql = "SELECT " + + "ARGUMENT_NAME AS COLUMN_NAME, " + + "DATA_TYPE AS DATA_TYPE_NAME, " + + "IN_OUT AS PARAM_DIRECTION, " + + "DATA_LENGTH AS LEN, " + + "POSITION AS PARAM_POSITION, " + + "DATA_PRECISION AS PRECISION, " + + "DATA_SCALE AS SCALE " + + "FROM SYS.ALL_ARGUMENTS " + + "WHERE OWNER = ? " + + " AND OBJECT_NAME = ? " + + "ORDER BY POSITION"; // 按位置排序,保证和存储过程定义一致 + + // 预编译达梦查询SQL + pstmt = connection.prepareStatement(dmSql); + pstmt.setString(1, dmSchema); // 模式名 + pstmt.setString(2, storeName.toUpperCase()); // 达梦对象名默认大写,强制转大写避免匹配不到 + rs = pstmt.executeQuery(); + + // 遍历达梦系统表结果,解析参数 + while (rs.next()) { + String colName = rs.getString("COLUMN_NAME"); + // 过滤空参数名(部分存储过程的返回值会显示为NULL,可根据实际情况调整) + if (colName == null || colName.trim().isEmpty()) { + continue; + } + Parameter param = new Parameter(); + param.setName(colName); // 参数名:DM_retVal/DM_workno等 + // 转换达梦数据类型为JDBC Types(核心:和存储过程类型匹配) + String dmDataType = rs.getString("DATA_TYPE_NAME").toUpperCase(); + int jdbcType = convertDmTypeToJdbcType(dmDataType); + param.setType(jdbcType); + // 转换参数方向(IN/OUT/IN OUT → 1/2/3) + String dmDirection = rs.getString("PARAM_DIRECTION").toUpperCase(); + param.setDirection(convertDmDirectionToCode(dmDirection)); + // 参数长度(达梦DATA_LENGTH是真实字节长度,直接用) + int len = rs.getInt("LEN"); + param.setLength(len > 0 ? len : 0); + // 小数位数:仅DECIMAL/NUMERIC + if (jdbcType == Types.DECIMAL || jdbcType == Types.NUMERIC) { + param.setScale(rs.getInt("SCALE") > 0 ? rs.getInt("SCALE") : 0); + } + // 调试输出达梦解析的参数 + log.debug(String.valueOf("达梦系统表解析参数:" + colName + + " | 达梦类型:" + dmDataType + + " | JDBC类型:" + jdbcType + + " | 方向:" + dmDirection + + " | 长度:" + len)); + parameters.add(param); + } + } + // ===================== 其他库(人大金仓):保留原有元数据解析逻辑 ===================== + else { + // 预编译存储过程调用语句,移除冗余占位符 + String callSql = "{call " + storeName + "}"; + callableStatement = connection.prepareCall(callSql); + // 获取数据库元数据 + DatabaseMetaData metaData = connection.getMetaData(); + // 查询存储过程参数元数据 + rs = metaData.getProcedureColumns(null, otherSchema, storeName, null); + // 遍历解析参数 + while (rs.next()) { + int paramType = rs.getInt("COLUMN_TYPE"); + // 过滤有效参数:1输入/2输出/3输入输出/4返回值 + if (paramType == 1 || paramType == 2 || paramType == 3 || paramType == 4) { + Parameter param = new Parameter(); + param.setName(rs.getString("COLUMN_NAME")); + param.setType(rs.getInt("DATA_TYPE")); + param.setDirection(getParameterDirection(paramType)); + param.setLength(rs.getInt("PRECISION") > 0 ? rs.getInt("PRECISION") : 0); + if (param.getType() == Types.DECIMAL || paramType == Types.NUMERIC) { + param.setScale(rs.getInt("SCALE") > 0 ? rs.getInt("SCALE") : 0); + } + parameters.add(param); + log.debug(String.valueOf("元数据解析参数:" + rs.getString("COLUMN_NAME") + " | 类型:" + rs.getInt("DATA_TYPE"))); + } + } + } + + } catch (SQLException e) { + // 异常时取消执行,释放资源 + if (callableStatement != null) { + try { + callableStatement.cancel(); + } catch (SQLException ex) { + log.warn(String.valueOf("取消存储过程预编译失败:" + ex.getMessage())); + } + } + String errMsg = String.format("解析存储过程参数错误: %s,异常信息:%s", storeName, e.getMessage()); + log.warn(String.valueOf(errMsg)); + log.debug(String.valueOf(errMsg)); // 页面输出异常,方便调试 + throw new SQLException(errMsg, e); + } finally { + // 【统一关闭所有资源】避免泄漏,按ResultSet→PreparedStatement→CallableStatement→Connection顺序 + try { + if (rs != null) rs.close(); + } catch (SQLException ex) { + log.debug(String.valueOf("关闭ResultSet失败:" + ex.getMessage())); + } + try { + if (pstmt != null) pstmt.close(); + } catch (SQLException ex) { + log.debug(String.valueOf("关闭PreparedStatement失败:" + ex.getMessage())); + } + try { + if (callableStatement != null) callableStatement.close(); + } catch (SQLException ex) { + log.debug(String.valueOf("关闭CallableStatement失败:" + ex.getMessage())); + } + try { + if (connection != null) connection.close(); + } catch (SQLException ex) { + log.debug(String.valueOf("关闭Connection失败:" + ex.getMessage())); + } + } + + // 调试输出最终解析结果(关键:看参数总数和参数名是否正确) + Parameter[] paramArray = parameters.toArray(new Parameter[0]); + log.debug(String.valueOf("===== 解析完成 =====")); + log.debug(String.valueOf("存储过程:" + storeName)); + log.debug(String.valueOf("参数总数:" + paramArray.length)); + log.debug(String.valueOf("参数列表:")); + for (Parameter p : paramArray) { + log.debug(String.valueOf("→ " + p.getName() + " | 方向:" + p.getDirection() + " | 类型:" + p.getType() + " | 长度:" + p.getLength())); + } + return paramArray; + } + + private int convertDmTypeToJdbcType(String dmDataType) { + if (dmDataType == null) return Types.OTHER; + return switch (dmDataType) { + case "INT", "INTEGER", "NUMBER" -> Types.INTEGER; // 达梦INT/NUMBER映射JDBC INTEGER + case "VARCHAR2", "VARCHAR", "CHAR" -> Types.VARCHAR; // 达梦VARCHAR2映射JDBC VARCHAR + case "DECIMAL", "NUMERIC" -> Types.DECIMAL; + case "DATE", "TIMESTAMP" -> Types.TIMESTAMP; + default -> Types.OTHER; // 其他类型默认 + }; + } + + // ===================== 辅助方法2:达梦参数方向 → 自定义Parameter方向码(和你的Parameter类匹配) ===================== +// 达梦IN_OUT值:IN/OUT/IN OUT → 对应你的Parameter方向码(1=IN,2=OUT,3=INOUT) + private int convertDmDirectionToCode(String dmDirection) { + if (dmDirection == null) return 0; + return switch (dmDirection) { + case "IN" -> 1; // 输入参数 + case "OUT" -> 2; // 输出参数 + case "IN OUT" -> 3; // 输入输出参数 + default -> 0; // 未知方向 + }; + } + + /** + * 查询存储过程的参数定义顺序(按 parameter_id 排序,即定义顺序) + */ + private List getStoreParamOrder(String procedureName, String schema) { + List paramOrder = new ArrayList<>(); + String sql = "SELECT p.name AS PARAMETER_NAME " + + "FROM sys.parameters p " + + "JOIN sys.procedures pr ON p.object_id = pr.object_id " + + "WHERE pr.name = ? " + + (schema != null ? "AND SCHEMA_NAME(pr.schema_id) = ? " : "") + + "ORDER BY p.parameter_id"; // 按定义顺序排序(关键) + Object[] params = schema != null ? new Object[]{procedureName, schema} : new Object[]{procedureName}; + + jdbcTemplate.query(sql, rs -> { + while (rs.next()) { + paramOrder.add(rs.getString("PARAMETER_NAME")); + } + }, params); + + // 调试:打印存储过程定义的参数顺序 + log.debug(String.valueOf("存储过程 " + procedureName + " 的参数顺序:" + paramOrder)); + return paramOrder; + } + + /** + * 获取当前数据库schema(适配MySQL) + */ + private String getCurrentSchema() { + return jdbcTemplate.queryForObject("SELECT DATABASE()", String.class); + } + + /** + * 数据库类型转换为JDBC SQL类型 + */ + private int getSqlType(String dataType) { + switch (dataType.toLowerCase()) { + case "int": + case "integer": + return Types.INTEGER; + case "varchar": + case "char": + return Types.VARCHAR; + case "datetime": + case "timestamp": + return Types.TIMESTAMP; + case "decimal": + case "numeric": + return Types.DECIMAL; + case "bit": + case "boolean": + return Types.BOOLEAN; + default: + return Types.OTHER; + } + } + + public List> executeDataTable(String cmdText, Integer start, Integer count, int[] tot) { + return executeDataTable(cmdText, CommandType.TEXT, null, start, count, tot); + } + + public List> executeDataTable(String cmdText, Object[] params, Integer start, Integer count, int[] tot) { + return executeDataTable(cmdText, CommandType.TEXT, params, start, count, tot); + } + + /** + * 执行查询并返回结果集 + * + * @param cmdText SQL语句 + * @param cmdType 命令类型 + * @param params 参数数组 + * @param start 起始位置 + * @param count 记录数 + * @return 结果集列表,每个元素为一行数据的Map + */ + public List> executeDataTable(String cmdText, CommandType cmdType, Object[] params, + int start, int count, int[] tot) { + List> resultList = new ArrayList<>(); + long startTime = System.currentTimeMillis(); + String excSql = ""; + String queryKey = String.valueOf(cmdText.hashCode()); + StringBuilder indexCol = new StringBuilder(); + String indexColName = ""; + try { + // 判断是否为SQLServer且需要分页处理 + if (isSqlHandler() && (!isLocal || slowQuerys.containsKey(queryKey))) { + SqlAnalyzer sqlAnalyzer = new SqlAnalyzer(cmdText); + excSql = sqlAnalyzer.buildSplitPageCmdText1(start, count, indexCol); + indexColName = indexCol.toString(); + } + + if (excSql != null && !excSql.isEmpty()) { + try { + // 执行分页查询(返回两个结果集:总数和数据) + List>> dataSets = executePagedQuery(excSql, params); + + if (dataSets.size() > 1) { + // 获取数据结果集 + resultList = dataSets.get(1); + // 移除索引列 + if (!indexColName.isEmpty()) { + String finalIndexColName = indexColName; + resultList.forEach(row -> row.remove(finalIndexColName)); + } + } + } catch (Exception e) { + String errMsg = String.format("execute sql error:sourceSql:%s%n\t execSql:%s%n\t try executeDataTableByReader", cmdText, excSql); + if (cmdText.equals(excSql)) { + errMsg = String.format("execute sql error:sourceSql:%s%n\t try executeDataTableByReader", cmdText); + } + if (log.isErrorEnabled()) { + log.error("{}\n{}", errMsg, e.getMessage()); + } + // 失败时使用Reader方式查询 + resultList = executeDataTableByReader(cmdText, cmdType, params, start, count, tot); + } + } else { + // 非分页查询直接使用Reader方式 + resultList = executeDataTableByReader(cmdText, cmdType, params, start, count, tot); + } + + // 记录慢查询 + long endTime = System.currentTimeMillis(); + if ((endTime - startTime) / 1000.0 > 50 && !slowQuerys.containsKey(queryKey)) { + slowQuerys.put(queryKey, true); + } + // 记录执行时间超过500ms的查询 + if (endTime - startTime > 500) { + proLog(endTime - startTime, cmdText, params); + } + + return resultList; + } catch (Exception e) { + String errMsg = String.format("execute sql error:sourceSql:%s%n\t execSql:%s", cmdText, excSql); + if (cmdText.equals(excSql) || (excSql == null || excSql.isEmpty())) { + errMsg = String.format("execute sql error:%s,pms:%s,mag:%s", cmdText, pmsToString(params), e.getMessage()); + } + if (log.isErrorEnabled()) { + log.error("{}\n{}", errMsg, e.getMessage()); + } + throw new RuntimeException(errMsg, e); + } + // JdbcTemplate自动管理连接,无需手动关闭 + } + + /** + * 通过Reader方式执行查询并返回结果集 + * + * @param cmdText SQL语句 + * @param cmdType 命令类型 + * @param params 参数数组 + * @param start 起始位置 + * @param count 记录数 + * @return 结果集列表,每个元素为一行数据的Map + */ + public List> executeDataTableByReader( + String cmdText, CommandType cmdType, Object[] params, + int start, int count, int[] tot) { + + List> resultList = new ArrayList<>(); + final int[] totalCount = {0}; + final int[] currentRow = {0}; + int endCount = start + count; + boolean readCount = true; + int startpage = start / count + 1; + // 尝试获取总记录数 + try { + SqlAnalyzer sqlAnalyzer = new SqlAnalyzer(cmdText); + String countSql = sqlAnalyzer.BuildCoutCmdText("count(1) tot", true); + countSql = RegexUtil.processDmServerSql(countSql); + if (countSql != null && !countSql.isEmpty()) { + // 执行count查询获取总记录数 + totalCount[0] = jdbcTemplate.queryForObject(countSql, params, Integer.class); + tot[0] = totalCount[0]; + readCount = false; + } + } catch (Exception e) { + // 忽略count查询异常,后续通过读取数据计算总数 + log.debug("Count query failed, will calculate total by reading data", e); + } + // 执行主查询并处理结果集 + boolean finalReadCount = readCount; + try { + if (!databaseType.equals("dm")) { + jdbcTemplate.query(cmdText, params, new RowCallbackHandler() { + @Override + public void processRow(ResultSet rs) throws SQLException { + currentRow[0]++; + // 记录总记录数(当count查询失败时) + if (finalReadCount) { + totalCount[0] = currentRow[0]; + } + + // 判断是否在查询范围内 + if (count < 0 || (currentRow[0] > start && currentRow[0] <= endCount)) { + Map rowMap = new HashMap<>(); + ResultSetMetaData metaData = rs.getMetaData(); + int columnCount = metaData.getColumnCount(); + + // 处理列名和数据 + for (int i = 1; i <= columnCount; i++) { + String columnName = metaData.getColumnName(i); + // 处理重复列名 + if (rowMap.containsKey(columnName)) { + columnName = columnName + "_" + i; + } + rowMap.put(columnName, rs.getObject(i)); + } + resultList.add(rowMap); + } + // 修复后代码 + else if (!finalReadCount && currentRow[0] >= endCount) { + // 抛出自定义异常终止查询,JdbcTemplate会捕获并终止结果集处理 + throw new RuntimeException("Reached end of required record range"); + } + } + }); + } else { + List> Pagelist = new ArrayList<>(); + if (!readCount) { + cmdText = SqlSafetyGuard.sanitizeReadOnlySelectForExecution(cmdText); + cmdText = RegexUtil.processDmServerSql(cmdText); +// if (databaseType.equals("dm")) { + cmdText = RegexUtil.processDmServerSql(cmdText); + cmdText = cmdText.replace(";", ""); +// } + if (count > 0) { + PageHelper.startPage(startpage, count); + } +// cmdText = RegexUtil.removeSemicolonInBrackets(cmdText); + if (readOnlyGuardEnabled) { + cmdText = SqlSafetyGuard.requireSafeReadOnlySelect(cmdText); + } + Pagelist = pagebreaksmapper.getPageBreaks(cmdText); + BlobUtil.convertBlobToByteArray(Pagelist); + BlobUtil.convertTextToNvarcharString(Pagelist); + return Pagelist; + } else { + cmdText = SqlSafetyGuard.sanitizeReadOnlySelectForExecution(cmdText); + cmdText = RegexUtil.processDmServerSql(cmdText); + List> finalPagelist = Pagelist; + jdbcTemplate.query(cmdText, params, new RowCallbackHandler() { + @Override + public void processRow(ResultSet rs) throws SQLException { + currentRow[0]++; + // 记录总记录数(当count查询失败时) + if (finalReadCount) { + totalCount[0] = currentRow[0]; + } + + // 判断是否在查询范围内 + if (count < 0 || (currentRow[0] > start && currentRow[0] <= endCount)) { + Map rowMap = new HashMap<>(); + ResultSetMetaData metaData = rs.getMetaData(); + int columnCount = metaData.getColumnCount(); + + // 处理列名和数据 + for (int i = 1; i <= columnCount; i++) { + String columnName = metaData.getColumnName(i); + // 处理重复列名 + if (rowMap.containsKey(columnName)) { + columnName = columnName + "_" + i; + } + rowMap.put(columnName, rs.getObject(i)); + } + finalPagelist.add(rowMap); + } + // 修复后代码 + else if (!finalReadCount && currentRow[0] >= endCount) { + // 抛出自定义异常终止查询,JdbcTemplate会捕获并终止结果集处理 + throw new RuntimeException("Reached end of required record range"); + } + } + }); + return finalPagelist; + } + // return Pagelist; + } + } catch (RuntimeException e) { + // 仅忽略自定义的终止异常,其他异常正常抛出 + if (!"Reached end of required record range".equals(e.getMessage())) { + throw e; + } + // 异常消息符合预期,说明是正常终止,无需处理 + log.debug("Query terminated normally after reaching required range", e); + } + return resultList; + } + + + /** + * 执行分页查询,返回多结果集 + */ + private List>> executePagedQuery(String sql, Object[] params) { + List>> dataSets = new ArrayList<>(); + + // 使用RowMapper处理结果集,支持多结果集(需数据库驱动支持) + jdbcTemplate.query(sql, params, rs -> { + List> table = new ArrayList<>(); + ResultSetMetaData metaData = rs.getMetaData(); + int columnCount = metaData.getColumnCount(); + + while (rs.next()) { + Map row = new HashMap<>(); + for (int i = 1; i <= columnCount; i++) { + row.put(metaData.getColumnName(i), rs.getObject(i)); + } + table.add(row); + } + dataSets.add(table); + }); + + return dataSets; + } + + // 辅助方法:参数格式化 + private String pmsToString(Object[] params) { + if (params == null || params.length == 0) { + return "[]"; + } + StringBuilder sb = new StringBuilder(); + sb.append("["); + for (int i = 0; i < params.length; i++) { + sb.append(params[i]); + if (i < params.length - 1) { + sb.append(", "); + } + } + sb.append("]"); + return sb.toString(); + } + + // 辅助方法:日志记录 + private void proLog(long durationMs, String cmdText, Object[] params) { + log.info("Slow query detected: {} ms", durationMs); + log.debug("Slow query detail omitted from production logs; sqlLength={}, paramCount={}", + cmdText == null ? 0 : cmdText.length(), + params == null ? 0 : params.length); + } + + // 判断是否为SQLServer处理类型(需根据实际情况实现) + private boolean isSqlHandler() { + // 实际应用中根据数据库类型判断 + return true; + } + + /// + /// 创建读取总数的sql + /// + /// The command text. + /// The aggregate SQL. + /// System.String. + public String BuildCountSql(String cmdText, String aggSql) { + return new SqlAnalyzer(cmdText).BuildCoutCmdText(aggSql, false); + + } + + public String BuildCountSql(String cmdText) { + return BuildCountSql(cmdText, "count(1) tot"); + } + + // 命令类型枚举 + public enum CommandType { + TEXT, STORED_PROCEDURE, TABLE_DIRECT + } + + // setter方法 + public void setLocal(boolean local) { + isLocal = local; + } + + public void setCloseCon(boolean closeCon) { + this.closeCon = closeCon; + } + + /** + * 验证SQL命令是否合法(语法校验) + * + * @param cmdText 需要验证的SQL语句 + * @return 语法合法返回true,否则返回false + */ + public boolean validateCmd(String cmdText) { + Connection connection = null; + Statement statement = null; + boolean result = false; + + try { + // 获取数据库连接(由Spring管理事务时使用该方法) + connection = DataSourceUtils.getConnection(jdbcTemplate.getDataSource()); + statement = connection.createStatement(); + + // 开启SQL解析模式(仅检查语法不执行) + statement.execute("SET PARSEONLY ON"); + + try { + // 执行待验证的SQL(此时仅解析不执行) + statement.execute(cmdText); + result = true; // 无异常则语法合法 + } catch (SQLException e) { + // SQL语法错误会进入此异常 + result = false; + } finally { + // 关闭解析模式 + statement.execute("SET PARSEONLY OFF"); + } + } catch (SQLException e) { + result = false; + } finally { + // 关闭Statement + if (statement != null) { + try { + statement.close(); + } catch (SQLException e) { + } + } + + // 根据配置决定是否关闭连接 + if (closeCon && connection != null) { + DataSourceUtils.releaseConnection(connection, jdbcTemplate.getDataSource()); + } + // 不关闭连接的情况由Spring事务管理处理 + } + + return result; + } + + /** + * 执行查询并返回单行单列结果 + * + * @param cmdText SQL语句 + * @param cmdType 命令类型 + * @param pma 参数数组 + * @param isCloseCon 是否关闭连接 + * @return 查询结果 + */ + public Object executeScalar(String cmdText, CommandType cmdType, CustomSqlParameter[] pma, boolean isCloseCon) { + if (cmdText == null || cmdText.isEmpty()) { + return null; + } + + Connection connection = null; + try { + connection = getConnection(); + MapSqlParameterSource paramSource = new MapSqlParameterSource(); + if (pma != null && pma.length > 0) { + for (CustomSqlParameter param : pma) { + paramSource.addValue(param.getName(), param.getValue(), param.getSqlType()); + } + } + + Object result; + if (cmdType == CommandType.STORED_PROCEDURE) { + // 使用NamedParameterJdbcTemplate执行带命名参数的查询 + result = namedJdbcTemplate.queryForObject(cmdText, paramSource, new RowMapper() { + @Override + public Object mapRow(ResultSet rs, int rowNum) throws SQLException { + return rs.getObject(1); // 获取第一列结果,符合Scalar语义 + } + }); + } else { + // 文本命令仍使用JdbcTemplate处理位置参数 + if (pma == null || pma.length == 0) { + result = jdbcTemplate.queryForObject(cmdText, Object.class); + } else { + List params = new ArrayList<>(); + for (CustomSqlParameter param : pma) { + params.add(param.getValue()); + } + result = jdbcTemplate.queryForObject(cmdText, params.toArray(), Object.class); + } + } + + return result; + } catch (Exception e) { + // 异常处理保持不变 + String errMsg = String.format("execute sql error:%s,pms:%s,msg:%s", cmdText, pmsToString(pma), e.getMessage()); + if (log.isErrorEnabled()) { + log.error("{}\n{}", errMsg, e.getMessage()); + } + throw new RuntimeException(errMsg, e); + } finally { + // 连接关闭逻辑保持不变 + if (isCloseCon && closeCon && connection != null) { + DataSourceUtils.releaseConnection(connection, jdbcTemplate.getDataSource()); + } + } + } + + /** + * 重载方法,默认关闭连接 + */ + public Object executeScalar(String cmdText, CommandType cmdType, CustomSqlParameter[] pma) { + return executeScalar(cmdText, cmdType, pma, true); + } + + /** + * 获取数据库连接 + */ + private Connection getConnection() throws SQLException { + return DataSourceUtils.getConnection(jdbcTemplate.getDataSource()); + } + + /** + * 参数格式化工具方法 + */ + private String pmsToString(CustomSqlParameter[] params) { + if (params == null || params.length == 0) { + return "[]"; + } + StringBuilder sb = new StringBuilder(); + sb.append("["); + for (int i = 0; i < params.length; i++) { + sb.append(params[i].getName()).append("=").append(params[i].getValue()); + if (i < params.length - 1) { + sb.append(", "); + } + } + sb.append("]"); + return sb.toString(); + } + + /** + * SQL参数防注入处理(针对字符串类型参数) + */ + public static String escapeSqlParam(String param) { + if (param == null) { + return "null"; + } + // 转义单引号(防止SQL注入) + return "'" + param.replace("'", "''") + "'"; + } + + /** + * 执行多语句SQL,返回多结果集(支持UPDATE后接SELECT等场景) + * + * @param querySql 多语句SQL(如"UPDATE ...; SELECT ...;") + * @return 多结果集:外层List=多个结果集,内层List=单个结果集的行,Map=行的列名-值 + */ + public List>> executeDataSet(String querySql) { + List>> dataSets = new ArrayList<>(); + + // 使用 JdbcTemplate 执行原生JDBC,手动处理多结果集 + jdbcTemplate.execute((Connection connection) -> { + Statement statement = null; + ResultSet rs = null; + try { + // 1. 创建Statement(支持多结果集) + statement = connection.createStatement(); + // 2. 执行SQL(多语句需配置 allowMultiQueries=true) + boolean hasResults = statement.execute(querySql); + + // 3. 循环处理所有结果集(包括更新计数和查询结果) + while (true) { + if (hasResults) { + // 3.1 处理查询结果集(ResultSet) + rs = statement.getResultSet(); + List> currentDataSet = parseResultSetToMapList(rs); + if (!currentDataSet.isEmpty()) { + dataSets.add(currentDataSet); + } + } else { + // 3.2 处理更新计数(如UPDATE/INSERT/DELETE的影响行数,可忽略或记录) + int updateCount = statement.getUpdateCount(); + if (updateCount == -1) { + // 无更多结果,退出循环 + break; + } + // (可选)记录更新行数:System.out.println("影响行数:" + updateCount); + } + // 3.3 检查是否有下一个结果集 + hasResults = statement.getMoreResults(); + } + } catch (SQLException e) { + String errMsg = String.format("execute sql error:%s\r\n msg:%s", querySql, e.getMessage()); + throw new SQLException(errMsg, e); + } finally { + // 4. 关闭资源(ResultSet → Statement,Connection由JdbcTemplate管理) + if (rs != null) { + try { + rs.close(); + } catch (SQLException e) { + log.error("Exception caught", e); + } + } + if (statement != null) { + try { + statement.close(); + } catch (SQLException e) { + log.error("Exception caught", e); + } + } + } + return null; + }); + + return dataSets; + } + + /** + * 将单个ResultSet转换为 List>(行列表) + * + * @param rs 单个查询结果集 + * @return 行列表:每个Map对应一行,key=列名,value=列值 + */ + public List> parseResultSetToMapList(ResultSet rs) throws SQLException { + List> rowList = new ArrayList<>(); + if (rs == null) { + return rowList; + } + + // 获取结果集元数据(列信息) + ResultSetMetaData metaData = rs.getMetaData(); + int columnCount = metaData.getColumnCount(); + + // 遍历每一行数据 + while (rs.next()) { + Map rowMap = new HashMap<>(); + for (int i = 1; i <= columnCount; i++) { // ResultSet列索引从1开始 + String columnName = metaData.getColumnName(i); + Object columnValue = rs.getObject(i); + + // 处理重复列名(若有):在重复列名后加索引 + if (rowMap.containsKey(columnName)) { + columnName = columnName + "_" + i; + } + rowMap.put(columnName, columnValue); + } + rowList.add(rowMap); + } + return rowList; + } + + +} + diff --git a/WebErp/weberp/src/main/java/org/example/Utils/DynamicJdbcTemplateRegistry.java b/WebErp/weberp/src/main/java/org/example/Utils/DynamicJdbcTemplateRegistry.java new file mode 100644 index 0000000..ac74e9c --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/DynamicJdbcTemplateRegistry.java @@ -0,0 +1,153 @@ +package org.example.Utils; + +import jakarta.annotation.PreDestroy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +import javax.sql.DataSource; +import java.time.Duration; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; + +@Component +public class DynamicJdbcTemplateRegistry { + private static final Logger log = LoggerFactory.getLogger(DynamicJdbcTemplateRegistry.class); + private static final int DEFAULT_MAX_POOLS = 32; + private static final long DEFAULT_IDLE_CLOSE_MILLIS = Duration.ofMinutes(60).toMillis(); + private static final Map POOLS = new HashMap<>(); + + private static int maxPools = DEFAULT_MAX_POOLS; + private static long idleCloseMillis = DEFAULT_IDLE_CLOSE_MILLIS; + + @Value("${app.datasource.dynamic.max-pools:32}") + public void setConfiguredMaxPools(int configuredMaxPools) { + synchronized (DynamicJdbcTemplateRegistry.class) { + maxPools = Math.max(1, configuredMaxPools); + evictOverflow(); + } + } + + @Value("${app.datasource.dynamic.idle-close-minutes:60}") + public void setConfiguredIdleCloseMinutes(long minutes) { + synchronized (DynamicJdbcTemplateRegistry.class) { + idleCloseMillis = Duration.ofMinutes(Math.max(1, minutes)).toMillis(); + } + } + + public static synchronized JdbcTemplate getOrCreate( + String connectionString, + Function creator + ) { + if (connectionString == null || connectionString.trim().isEmpty()) { + throw new IllegalArgumentException("connectionString must not be empty"); + } + Objects.requireNonNull(creator, "creator must not be null"); + + long now = System.currentTimeMillis(); + closeIdlePools(now); + + Entry entry = POOLS.get(connectionString); + if (entry != null) { + entry.lastAccessMillis = now; + return entry.jdbcTemplate; + } + + if (POOLS.size() >= maxPools) { + evictOldest(); + } + + JdbcTemplate jdbcTemplate = Objects.requireNonNull( + creator.apply(connectionString), + "creator returned null JdbcTemplate" + ); + POOLS.put(connectionString, new Entry(jdbcTemplate, now)); + return jdbcTemplate; + } + + public static synchronized void closeIdlePools() { + closeIdlePools(System.currentTimeMillis()); + } + + public static synchronized void closeAll() { + for (Entry entry : POOLS.values()) { + close(entry.jdbcTemplate); + } + POOLS.clear(); + } + + public static synchronized int getActivePoolCount() { + return POOLS.size(); + } + + public static synchronized void configureForTests(int configuredMaxPools, Duration idleTimeout) { + closeAll(); + maxPools = Math.max(1, configuredMaxPools); + idleCloseMillis = Math.max(0, idleTimeout.toMillis()); + } + + public static synchronized void resetForTests() { + closeAll(); + maxPools = DEFAULT_MAX_POOLS; + idleCloseMillis = DEFAULT_IDLE_CLOSE_MILLIS; + } + + @PreDestroy + public void destroy() { + closeAll(); + } + + private static void closeIdlePools(long now) { + Iterator> iterator = POOLS.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry mapEntry = iterator.next(); + Entry entry = mapEntry.getValue(); + if (now - entry.lastAccessMillis >= idleCloseMillis) { + close(entry.jdbcTemplate); + iterator.remove(); + } + } + } + + private static void evictOverflow() { + while (POOLS.size() > maxPools) { + evictOldest(); + } + } + + private static void evictOldest() { + POOLS.entrySet().stream() + .min(Comparator.comparingLong(entry -> entry.getValue().lastAccessMillis)) + .ifPresent(entry -> { + close(entry.getValue().jdbcTemplate); + POOLS.remove(entry.getKey()); + }); + } + + private static void close(JdbcTemplate jdbcTemplate) { + DataSource dataSource = jdbcTemplate.getDataSource(); + if (dataSource instanceof AutoCloseable) { + try { + ((AutoCloseable) dataSource).close(); + } catch (Exception e) { + log.warn("Failed to close dynamic datasource", e); + } + } + } + + private static class Entry { + private final JdbcTemplate jdbcTemplate; + private long lastAccessMillis; + + private Entry(JdbcTemplate jdbcTemplate, long lastAccessMillis) { + this.jdbcTemplate = jdbcTemplate; + this.lastAccessMillis = lastAccessMillis; + } + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/EscPrintHelper.java b/WebErp/weberp/src/main/java/org/example/Utils/EscPrintHelper.java new file mode 100644 index 0000000..ae0c659 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/EscPrintHelper.java @@ -0,0 +1,315 @@ +package org.example.Utils; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +/** + * ESC打印指令工具类(对应原C# EscPrintHelper) + * 功能:生成各类ESC打印控制指令字节数组,提供打印指令组装工具方法 + */ +public final class EscPrintHelper { + + // ------------------- 常量定义(对应原C#静态常量) ------------------- + // 字体宽度常量(保留小数位精度) + public static final double FONT_A_WIDTH = Math.round(2.0 * 10) / 10.0; // 等价Math.Round(2.0, 1) + public static final double FONT_B_WIDTH = Math.round(1.0 * 10) / 10.0; // 等价Math.Round(1.0, 1) + + // 控制字符常量(全大写,符合Java命名规范) + public static final byte ESC = 27; // Esc + public static final byte FS = 28; // FS + public static final byte GS = 29; // GS + public static final byte DLE = 16; // DLE + public static final byte EOT = 4; // EOT + public static final byte ENQ = 5; // ENQ + public static final byte SP = 32; // SP + public static final byte HT = 9; // HT + public static final byte LF = 10; // LF + public static final byte CR = 13; // CR + public static final byte FF = 12; // FF + public static final byte CAN = 24; // CAN + + // 分隔符常量 + public static final String SEPARATOR = "-------------------------"; + + // Windows默认编码(对应C# Encoding.Default) + private static final Charset DEFAULT_CHARSET = StandardCharsets.ISO_8859_1; + + // 私有化构造方法,禁止实例化(静态工具类) + private EscPrintHelper() { + } + + // ------------------- 核心打印指令方法 ------------------- + + /** + * 初始化打印机(对应原InitPrinter) + */ + public static byte[] initPrinter() { + return new byte[]{ESC, 64}; + } + + /** + * 收据模式下开启/关闭标签模式(对应原OpenOrCloseLableModelInReceip) + */ + public static byte[] openOrCloseLableModelInReceip(boolean open) { + byte[] array = new byte[]{31, 27, 31, 0, 1, 1, (byte) 128, 0}; + array[7] = (byte) (open ? 1 : 0); + return array; + } + + /** + * 标签结束指令(对应原EndOfLable) + */ + public static byte[] endOfLable() { + return new byte[]{31, 27, 31, 0, 1, 1, (byte) 129}; + } + + /** + * 设置标签宽度(对应原SetTheLableWidth) + */ + public static byte[] setTheLableWidth(int width) { + return new byte[]{ + 31, + 27, + 31, + 0, + 1, + 1, + (byte) 131, + (byte) width + }; + } + + /** + * 设置绝对打印位置(对应原SetAbsolutePrintPosition) + */ + public static byte[] setAbsolutePrintPosition(int m, int n) { + return new byte[]{ + ESC, + 36, + (byte) m, + (byte) n + }; + } + + /** + * 选择字符大小(对应原SelectCharacterSize) + */ + public static byte[] selectCharacterSize(int n) { + return new byte[]{ + GS, + 33, + (byte) n + }; + } + + /** + * 添加打印文本(对应原AddValueStr) + */ + public static byte[] addValueStr(String value) { + if (value == null || value.trim().isEmpty()) { + return null; + } + return value.getBytes(DEFAULT_CHARSET); + } + + /** + * 打印并换行(默认1行,对应原PrintAndFeedLine()) + */ + public static byte[] printAndFeedLine() { + return printAndFeedLine(1); + } + + /** + * 打印并换行(指定行数,对应原PrintAndFeedLine(int lineNum)) + */ + public static byte[] printAndFeedLine(int lineNum) { + byte[] array = new byte[lineNum]; + for (int i = 0; i < lineNum; i++) { + array[i] = LF; + } + return array; + } + + /** + * 加粗开启(对应原BoldOn) + */ + public static byte[] boldOn() { + return new byte[]{ESC, 69, 1}; + } + + /** + * 加粗关闭(对应原BoldOff) + */ + public static byte[] boldOff() { + return new byte[]{ESC, 69, 0}; + } + + /** + * 左对齐(对应原AlignLeft) + */ + public static byte[] alignLeft() { + return new byte[]{ESC, 97, 0}; + } + + /** + * 右对齐(对应原AlignRight) + */ + public static byte[] alignRight() { + return new byte[]{ESC, 97, 2}; + } + + /** + * 居中对齐(对应原AlignCenter) + */ + public static byte[] alignCenter() { + return new byte[]{ESC, 97, 1}; + } + + /** + * 设置水平位置(对应原SetHorizontalPosition) + * 注:原代码中array[2]被重复赋值为0,已保留该逻辑 + */ + public static byte[] setHorizontalPosition(byte col) { + byte[] array = new byte[4]; + array[0] = ESC; + array[1] = 68; + array[2] = col; + array[2] = 0; // 原代码逻辑:覆盖为0 + return array; + } + + /** + * 设置加粗模式(对应原SetBold) + */ + public static byte[] setBold(int boldMode) { + switch (boldMode) { + case 0: + return boldOff(); + case 1: + return boldOn(); + default: + return null; + } + } + + /** + * 设置字体模式(对应原SetFontMode) + */ + public static byte[] setFontMode(int fontMode) { + byte[] array = new byte[]{ESC, 77, 0}; + switch (fontMode) { + case 0: + array[2] = 0; + break; + case 1: + array[2] = 1; + break; + } + return array; + } + + /** + * 设置字体大小模式(对应原SetFontSizeMode) + */ + public static byte[] setFontSizeMode(int fontSizeMode) { + byte[] array = new byte[]{ESC, 33, 0}; + switch (fontSizeMode) { + case 0: + array[2] = 0; + break; + case 1: + array[2] = 16; + break; + case 2: + array[2] = 32; + break; + } + return array; + } + + /** + * 设置对齐模式(对应原SetAlign) + */ + public static byte[] setAlign(int alignMode) { + switch (alignMode) { + case 0: + return alignLeft(); + case 1: + return alignCenter(); + case 2: + return alignRight(); + default: + return null; + } + } + + /** + * 部分切纸(对应原feedPaperCutPartial) + */ + public static byte[] feedPaperCutPartial() { + return new byte[]{GS, 86, 66, 0}; + } + + /** + * 获取文本打印示例指令(对应原GetTextPrintCommand) + */ + public static List getTextPrintCommand() { + List list = new ArrayList<>(); + list.add(openOrCloseLableModelInReceip(true)); + list.add(setTheLableWidth(40)); + list.add(initPrinter()); + list.add(setAbsolutePrintPosition(50, 0)); + list.add(selectCharacterSize(17)); + list.add(addValueStr("商品")); + list.add(setAbsolutePrintPosition(250, 0)); + list.add(addValueStr("价格")); + list.add(printAndFeedLine()); + list.add(printAndFeedLine()); + list.add(initPrinter()); + list.add(setAbsolutePrintPosition(30, 0)); + list.add(addValueStr("黄焖鸡")); + list.add(setAbsolutePrintPosition(220, 0)); + list.add(addValueStr("5元")); + list.add(printAndFeedLine()); + list.add(initPrinter()); + list.add(setAbsolutePrintPosition(30, 0)); + list.add(addValueStr("黄焖鸡呀")); + list.add(setAbsolutePrintPosition(220, 0)); + list.add(addValueStr("6元")); + list.add(printAndFeedLine()); + list.add(endOfLable()); + return list; + } + + /** + * 合并字节数组(对应原byteMerger) + */ + public static byte[] byteMerger(byte[][] byteList) { + if (byteList == null || byteList.length == 0) { + return new byte[0]; + } + + // 计算总长度 + int totalLength = 0; + for (byte[] bytes : byteList) { + if (bytes != null) { + totalLength += bytes.length; + } + } + + // 合并数组 + byte[] mergedArray = new byte[totalLength]; + int currentIndex = 0; + for (byte[] bytes : byteList) { + if (bytes != null) { + for (byte b : bytes) { + mergedArray[currentIndex++] = b; + } + } + } + + return mergedArray; + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Utils/ExceptionSummaryUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/ExceptionSummaryUtil.java new file mode 100644 index 0000000..d62ff7c --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/ExceptionSummaryUtil.java @@ -0,0 +1,204 @@ +package org.example.Utils; + +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class ExceptionSummaryUtil { + private static final int MAX_CLIENT_MESSAGE_LENGTH = 300; + private static final Pattern INVALID_TABLE_OR_VIEW = + Pattern.compile("无效的表或视图名?\\s*[\\[【]([^\\]】]+)[\\]】]"); + private static final Pattern MEMBER_ACCESS_ERROR = + Pattern.compile("(?s)(第\\s*\\d+\\s*行附近出现错误[::]?\\s*)?\\R?\\s*(无法解析的成员访问表达式\\s*\\[[^\\]]+\\])"); + private static final Pattern LINE_ERROR = + Pattern.compile("(?s)(第\\s*\\d+\\s*行附近出现错误[::]?\\s*)\\R?\\s*([^\\r\\n\\t]+)"); + private static final Pattern SQL_BLOCK = + Pattern.compile("(?is)\\bSQL\\s*\\[.*?\\];"); + private static final Pattern JAVA_CLASS_PREFIX = + Pattern.compile("\\b(?:[a-zA-Z_$][\\w$]*\\.)+[A-Za-z_$][\\w$]*(?:Exception|Error)?\\s*:?\\s*"); + + private ExceptionSummaryUtil() { + } + + public static String summarizeForClient(Throwable throwable) { + String rawMessage = collectThrowableMessages(throwable); + if (isBlank(rawMessage)) { + return "请联系管理员查看服务端日志"; + } + + String invalidTableOrView = extractInvalidTableOrView(rawMessage); + if (!isBlank(invalidTableOrView)) { + return invalidTableOrView; + } + + String knownDatabaseError = extractKnownDatabaseError(rawMessage); + if (!isBlank(knownDatabaseError)) { + return abbreviate(knownDatabaseError, MAX_CLIENT_MESSAGE_LENGTH); + } + + String normalizedMessage = removeSqlBlocks(rawMessage); + String meaningfulMessage = extractMessageAfterMarker(normalizedMessage, "mag:"); + if (isBlank(meaningfulMessage)) { + meaningfulMessage = normalizedMessage; + } + + String cleaned = cleanTechnicalNoise(meaningfulMessage); + if (isBlank(cleaned)) { + cleaned = "请联系管理员查看服务端日志"; + } + return abbreviate(cleaned, MAX_CLIENT_MESSAGE_LENGTH); + } + + public static boolean containsMessage(Throwable throwable, String keyword) { + if (isBlank(keyword)) { + return false; + } + return collectThrowableMessages(throwable).contains(keyword); + } + + private static String extractKnownDatabaseError(String message) { + Matcher memberAccessMatcher = MEMBER_ACCESS_ERROR.matcher(message); + if (memberAccessMatcher.find()) { + return buildLineAwareMessage(memberAccessMatcher.group(1), memberAccessMatcher.group(2)); + } + + Matcher lineErrorMatcher = LINE_ERROR.matcher(message); + if (lineErrorMatcher.find()) { + String reason = cleanTechnicalNoise(lineErrorMatcher.group(2)); + if (!isBlank(reason) && !reason.contains("SQL [")) { + return buildLineAwareMessage(lineErrorMatcher.group(1), reason); + } + } + + String stripped = cleanTechnicalNoise(removeSqlBlocks(message)); + if (stripped.contains("SQL语法错误")) { + return abbreviate(stripped.substring(stripped.indexOf("SQL语法错误")), MAX_CLIENT_MESSAGE_LENGTH); + } + if (stripped.contains("权限")) { + return abbreviate(stripped.substring(stripped.indexOf("权限")), MAX_CLIENT_MESSAGE_LENGTH); + } + if (stripped.contains("连接")) { + return abbreviate(stripped.substring(stripped.indexOf("连接")), MAX_CLIENT_MESSAGE_LENGTH); + } + return ""; + } + + private static String buildLineAwareMessage(String linePrefix, String reason) { + String cleanedReason = cleanTechnicalNoise(reason); + if (isBlank(cleanedReason)) { + return ""; + } + String normalizedLinePrefix = normalizeLineErrorPrefix(linePrefix); + return isBlank(normalizedLinePrefix) ? cleanedReason : normalizedLinePrefix + cleanedReason; + } + + private static String normalizeLineErrorPrefix(String linePrefix) { + if (isBlank(linePrefix)) { + return ""; + } + Matcher matcher = Pattern.compile("第\\s*(\\d+)\\s*行附近出现错误").matcher(linePrefix); + if (matcher.find()) { + return "第" + matcher.group(1) + "行附近出现错误:"; + } + return ""; + } + + private static String collectThrowableMessages(Throwable throwable) { + if (throwable == null) { + return ""; + } + + StringBuilder message = new StringBuilder(); + Set seen = new HashSet<>(); + Throwable current = throwable; + while (current != null && seen.add(current)) { + if (!isBlank(current.getMessage())) { + if (message.length() > 0) { + message.append(System.lineSeparator()); + } + message.append(current.getMessage()); + } + current = current.getCause(); + } + return message.toString(); + } + + private static String removeSqlBlocks(String message) { + return SQL_BLOCK.matcher(message).replaceAll("SQL [已省略]"); + } + + private static String extractInvalidTableOrView(String message) { + Matcher matcher = INVALID_TABLE_OR_VIEW.matcher(message); + if (matcher.find()) { + return "无效的表或视图[" + matcher.group(1).trim() + "]"; + } + return ""; + } + + private static String extractMessageAfterMarker(String message, String marker) { + int index = message.lastIndexOf(marker); + if (index < 0) { + return ""; + } + return message.substring(index + marker.length()); + } + + private static String cleanTechnicalNoise(String message) { + StringBuilder cleaned = new StringBuilder(); + String normalized = message.replace("\r", "\n"); + for (String line : normalized.split("\n")) { + String item = line.trim(); + if (isBlank(item) || isStackTraceNoise(item)) { + continue; + } + item = stripCausePrefix(item); + if (item.contains("Exception") || item.contains("Error")) { + item = JAVA_CLASS_PREFIX.matcher(item).replaceAll(""); + } + item = item.replace("execute sql error:", ""); + item = item.replace("StatementCallback;", ""); + item = item.replace("PreparedStatementCallback;", ""); + item = item.replace("bad SQL grammar [];", ""); + item = item.trim(); + if (isBlank(item) || isStackTraceNoise(item)) { + continue; + } + if (cleaned.length() > 0) { + cleaned.append(";"); + } + cleaned.append(item); + } + return cleaned.toString().replaceAll("\\s+", " ").trim(); + } + + private static String stripCausePrefix(String message) { + String result = message; + while (result.startsWith("Caused by:")) { + result = result.substring("Caused by:".length()).trim(); + } + return result; + } + + private static boolean isStackTraceNoise(String line) { + return line.startsWith("at ") + || line.startsWith("...") + || line.contains("common frames omitted") + || line.contains("StackTraceElement") + || line.contains("JdbcTemplate.java:") + || line.contains("org.springframework.jdbc.core.JdbcTemplate") + || line.contains("SQLStateSQLExceptionTranslator") + || line.contains("AbstractFallbackSQLExceptionTranslator"); + } + + private static String abbreviate(String message, int maxLength) { + if (message.length() <= maxLength) { + return message; + } + return message.substring(0, maxLength - 3) + "..."; + } + + private static boolean isBlank(String value) { + return value == null || value.trim().isEmpty(); + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/ExtentionsUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/ExtentionsUtil.java new file mode 100644 index 0000000..68fdad4 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/ExtentionsUtil.java @@ -0,0 +1,84 @@ +package org.example.Utils; + +import org.example.Entity.CusException.CusException; + +public class ExtentionsUtil { + public static String getErrMsg(Throwable e, boolean stackTrace) { + // 处理内部异常 + if (e.getCause() != null) { + String msg = getErrMsg(e.getCause(), stackTrace); + if (msg.contains("{0}")) { + // 替换占位符,对应C#的字符串替换逻辑 + String replacement = (e instanceof CusException) ? "" : "\n" + e.getMessage(); + return msg.replace("{0}", replacement); + } + return msg; + } + + // 构建基础异常信息 + StringBuilder sb = new StringBuilder(); + sb.append(e.getMessage() != null ? e.getMessage() : e.toString()); + sb.append("{0}"); + + // 追加堆栈信息(如果需要) + if (stackTrace) { + sb.append("\n").append(getStackTrace(e)); + } + + return sb.toString(); + } + + /** + * 简化版getErrMsg,不包含堆栈信息 + */ + public static String getErrMsg(Throwable e) { + return getErrMsg(e, false); + } + + /** + * 获取异常堆栈信息,对应C#的GetErrStack扩展方法 + * @param e 异常对象 + * @return 堆栈信息字符串 + */ + public static String getErrStack(Throwable e) { + StringBuilder sb = new StringBuilder(); + + // 获取当前异常的堆栈信息 + sb.append(getStackTrace(e)); + + // 递归处理内部异常 + if (e.getCause() != null) { + sb.append("\n").append(getErrStack(e.getCause())); + } + + return sb.toString(); + } + + /** + * 辅助方法:获取异常的堆栈信息字符串 + */ + private static String getStackTrace(Throwable e) { + StringBuilder sb = new StringBuilder(); + appendStackTrace(e, sb, ""); + return sb.toString(); + } + + private static void appendStackTrace(Throwable throwable, StringBuilder sb, String caption) { + if (throwable == null) { + return; + } + if (!caption.isEmpty()) { + sb.append(caption); + } + sb.append(throwable).append(System.lineSeparator()); + for (StackTraceElement element : throwable.getStackTrace()) { + sb.append("\tat ").append(element).append(System.lineSeparator()); + } + for (Throwable suppressed : throwable.getSuppressed()) { + appendStackTrace(suppressed, sb, "Suppressed: "); + } + if (throwable.getCause() != null) { + appendStackTrace(throwable.getCause(), sb, "Caused by: "); + } + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/FileUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/FileUtil.java new file mode 100644 index 0000000..d6ae311 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/FileUtil.java @@ -0,0 +1,2390 @@ +package org.example.Utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import jakarta.servlet.http.HttpServletRequest; +import org.example.Api.LoggerHandler; +import org.example.Entity.BaseResponse.BaseResponse; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.util.FileCopyUtils; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.io.*; +import java.net.URI; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.*; +import java.text.MessageFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.*; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.web.client.RequestCallback; +import org.springframework.web.client.ResponseExtractor; + +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.function.Consumer; +import java.util.stream.IntStream; + +import org.mozilla.universalchardet.UniversalDetector; +import org.springframework.web.multipart.MultipartFile; + +import static org.example.Utils.NativeExtensionUtils.*; + +/** + * 文件操作工具类,处理文件上传、移动、删除、下载等功能 + */ +@Component +public class FileUtil { + private static final Logger log = LoggerFactory.getLogger(FileUtil.class); + + + + private static final List EXCLUDE_TYPES = Arrays.asList("aspx", "ashx", "php", "asp", "bat"); + private static final List ALLOW_FILE_TYPES = Arrays.asList( + ".png", ".jpg", ".jpeg", ".gif", ".bmp", + ".flv", ".swf", ".mkv", ".avi", ".rm", ".rmvb", ".mpeg", ".mpg", + ".ogg", ".ogv", ".mov", ".wmv", ".mp4", ".webm", ".mp3", ".wav", ".mid", + ".rar", ".zip", ".tar", ".gz", ".7z", ".bz2", ".cab", ".iso", + ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".txt", ".md", ".xml" + ); + + /** + * 文件路径信息实体类 + */ + public static class PathInfo { + public String WebPath; + public String WebRelPath; + public String RelativePath; + + //@JsonIgnore + public String SavePath; + + public String OldFileName; + public String FileName; + @JsonIgnore + public String ModuleId; + @JsonIgnore + public String IdValue; + @JsonIgnore + public String SpecNo; + @JsonIgnore + public long TotSize; + @JsonIgnore + public String StepCode; + public String fileId; + public String UserName; + + // private static ILog _log; + public Date getCreateTime() { + return new Date(); // 每次调用都返回最新时间 + } + + @JsonIgnore + public Map getDatas() { + Map datas = new HashMap<>(); + datas.put("webpath", WebPath); + datas.put("savepath", SavePath); + datas.put("oldfilename", OldFileName); + datas.put("filename", FileName); + datas.put("moduleid", ModuleId); + datas.put("idvalue", IdValue); + datas.put("fileid", fileId); + datas.put("specno", SpecNo); + datas.put("totsize", TotSize); + datas.put("stepcode", StepCode); + return datas; + } + } + + /** + * 将文件转换为字节数组 + */ + public byte[] toBinary(String path) throws IOException { + try (FileInputStream fs = new FileInputStream(path)) { + return FileCopyUtils.copyToByteArray(fs); + } + } + + /** + * 将文件另存 + * + * @param sourceFilePath 原始文件全名,相对路径 + * @param newDir 新目录 + * @param newName 新文件名 + * @param absFileVPath 绝对物理路径如D://fileRoot + * @param cover 是否覆盖 + * @return 操作结果响应 + */ + public static BaseResponse moveTo(String sourceFilePath, String newDir, + String newName, String absFileVPath, boolean cover) { + BaseResponse response = new BaseResponse(); + response.setData(""); + + String absNewDir = newDir; + StringBuilder relNewDir = new StringBuilder(newDir); + + WebConfigUtil_web webConfig = new WebConfigUtil_web(); + String fileVPath = webConfig.getFileVPath(); + + // 处理源文件路径 + if (sourceFilePath != null && sourceFilePath.trim().startsWith("/") + && sourceFilePath.trim().substring(1).startsWith(fileVPath)) { + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); +// 去掉 Web 应用的实际部署位置,更换为数据库存储位置 +// sourceFilePath = request.getServletContext().getRealPath(sourceFilePath); + +// 原始 sourceFilePath:/fileRoot/2000301/KH00002/√销售部门设置 .xlsx +// 处理步骤1:去除开头的 '/' 和 'fileRoot' 前缀,保留后续子路径 + String subPath; + if (sourceFilePath.startsWith("/fileRoot/")) { + // 截取 "/fileRoot/" 之后的部分(如 "2000301/KH00002/√销售部门设置 .xlsx") + subPath = sourceFilePath.substring("/fileRoot/".length()); + } else { + // 极端情况:若路径格式异常,默认保留原路径(可根据实际需求调整) + subPath = sourceFilePath.startsWith("/") ? sourceFilePath.substring(1) : sourceFilePath; + } + +// 处理步骤2:确保 absFileVPath 以 "fileRoot" 结尾 + Path absPath = Paths.get(absFileVPath); +// 判断 absFileVPath 的最后一级是否为 "fileRoot" + if (!"fileRoot".equals(absPath.getFileName().toString())) { + // 若不是,则拼接 "fileRoot" 使其以 fileRoot 结尾 + absPath = absPath.resolve("fileRoot"); + } + +// 最终拼接:absPath(确保以fileRoot结尾) + subPath(已去除fileRoot) + sourceFilePath = absPath.resolve(subPath).toString(); + } else { + sourceFilePath = Paths.get(webConfig.getFilePath(), sourceFilePath).toString(); + } + + // 处理目标目录路径 + absNewDir = toAbsPath(newDir, absFileVPath, relNewDir)[0]; + + + // 验证文件是否存在及权限 + File sourceFile = new File(sourceFilePath); +//判断源文件是否在request中,2025.11.12去除 + // if (!sourceFile.exists() || !checkFileAuthory(sourceFilePath, absFileVPath)) { +// response.setMsg("文件上传路径不合法!"); +// return response; +// } + if (!checkFileAuthory(sourceFilePath, absFileVPath)) { + response.setMsg("文件上传路径不合法!"); + return response; + } + + // 准备文件信息 + Hashtable fileInfos = new Hashtable<>(); + fileInfos.put("Length", sourceFile.length()); + fileInfos.put("Name", sourceFile.getName()); + newName = (newName == null || newName.isEmpty()) ? sourceFile.getName() : newName; + fileInfos.put("newName", newName); + + try { + Date start = new Date(); + File targetDir = new File(absNewDir); + + // 创建目标目录 + if (!targetDir.exists()) { + boolean dirCreated = targetDir.mkdirs(); + LoggerHandler.debug(new Object(), MessageFormat.format("创建目录:{0}", absNewDir)); + } + + String newFilePath = Paths.get(absNewDir, newName).toString(); + LoggerHandler.debug(new Object(), MessageFormat.format("移动至目标:{0}", newFilePath)); + + File targetFile = new File(newFilePath); + // 处理覆盖逻辑 + if (targetFile.exists() && cover) { + boolean deleted = targetFile.delete(); + } + + if (!targetFile.exists()) { + File oldDir = sourceFile.getParentFile(); + boolean moveSuccess = false; + + try { + // 尝试移动文件 + moveSuccess = sourceFile.renameTo(targetFile); + } catch (Exception e) { + LoggerHandler.error(new Object(), MessageFormat.format("文件移动失败!{0}", e.getMessage()), e); + } + + if (targetFile.exists()) { + response.setSuccess(true); + } else { + response.setSuccess(false); + response.setMsg("文件被占用,移动失败!尝试拷贝文件!"); + LoggerHandler.debug(new Object(), "文件被占用,移动失败!尝试拷贝文件!"); + + try { + // 尝试拷贝文件 + Files.copy(sourceFile.toPath(), targetFile.toPath()); + if (targetFile.exists()) { + response.setSuccess(true); + response.setMsg("文件被占用,拷贝成功!"); + LoggerHandler.debug(new Object(), "文件被占用,拷贝成功!5分钟后自动尝试删除临时文件!"); + + // 5分钟后删除原文件 + new Timer().schedule(new TimerTask() { + @Override + public void run() { + try { + if (sourceFile.exists()) { + boolean deleted = sourceFile.delete(); + LoggerHandler.debug(new Object(), "自动尝试删除临时文件成功!"); + } + } catch (Exception e) { + LoggerHandler.error(new Object(), MessageFormat.format("文件删除失败!请手动删除{0}", e.getMessage()), e); + } + } + }, 5 * 60 * 1000); // 5分钟 + } + } catch (IOException e) { + LoggerHandler.error(new Object(), MessageFormat.format("文件拷贝失败!{0}", e.getMessage()), e); + } + } + + // 计算移动耗时 + LoggerHandler.debug(new Object(), MessageFormat.format("移动文件耗时:{0}ms", + (new Date().getTime() - start.getTime()))); + + // 删除空目录 + deleteEmptyDir(oldDir.getName()); + start = new Date(); + LoggerHandler.debug(new Object(), MessageFormat.format("删除空文件夹耗时:{0}ms", + (new Date().getTime() - start.getTime()))); + + response.setOther(fileInfos); + response.setData(urlEncode(Paths.get(relNewDir.toString(), newName).toString(), false, true)); + } else { + // 文件已存在且不覆盖 + File oldDir = sourceFile.getParentFile(); + boolean deleted = sourceFile.delete(); + deleteEmptyDir(oldDir.getName()); + + response.setSuccess(false); + response.setData(9); + response.setMsg("文件已存在!是否覆盖?"); + } + + } catch (Exception e) { + response.setMsg(e.getMessage()); + response.setSuccess(false); + LoggerHandler.error(new Object(), MessageFormat.format("移动文件失败:{0}", response.getMsg()), e); + } + + return response; + } + + /** + * 将文件保存到临时文件夹中 + * + * @param file 上传的文件 + * @param fileRelativeDir 相对路径 + * @param filename 文件名(不包含文件类型) + * @param appDomain 应用域名 + * @param toThum 是否生成缩略图 + * @return 操作结果响应 + */ + public static BaseResponse saveFile(MultipartFile file, String fileRelativeDir, String filename, + String appDomain, boolean toThum) { + BaseResponse response = new BaseResponse(); + response.setSuccess(false); + + // 处理文件名中的反斜杠 + if (filename != null && !filename.isEmpty()) { + if (filename.indexOf("\\") > -1) { + String[] parts = filename.split("\\\\"); + filename = parts[parts.length - 1]; + } + } + + // 处理相对路径 + boolean isAbsPath = fileRelativeDir != null && fileRelativeDir.startsWith("/"); + if (isAbsPath) { + fileRelativeDir = fileRelativeDir.substring(1); + } + + // 图片文件特殊处理(添加_high标识) + if (filename != null && ImageUtil.isImg(filename)) { + if (fileRelativeDir != null && !fileRelativeDir.isEmpty() + && !fileRelativeDir.endsWith("/")) { + fileRelativeDir += "_high/"; + } else { + fileRelativeDir = "_high/"; + } + } + + try { + // 获取保存目录 + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + String saveDir; + if (isAbsPath) { + saveDir = Paths.get(request.getServletContext().getRealPath("/"), fileRelativeDir).toString(); + } else { + saveDir = Paths.get(WebConfigUtil_web.getFilePath(), fileRelativeDir).toString(); + } + + // 创建目录 + File dirFile = new File(saveDir); + if (!dirFile.exists()) { + boolean mkdirs = dirFile.mkdirs(); + if (!mkdirs) { + response.setMsg("创建目录失败"); + return response; + } + } + + // 验证文件类型 + String fileType = getFileExtension(filename).toLowerCase().replace(".", ""); + if (!checkFileType(fileType)) { + response.setSuccess(false); + response.setMsg("非法文件!"); + return response; + } + + // 生成保存文件名 + StringBuilder fiName = new StringBuilder(); + if (filename == null || filename.isEmpty()) { + fiName = new StringBuilder(UUID.randomUUID() + "." + fileType); + } else { + fiName = new StringBuilder(filename); + } + + // 构建保存路径 + String savePath = Paths.get(saveDir, fiName.toString()).toString(); + + // 检查路径长度 + String[] msg = new String[1]; + if (!checkSaveViewFolderLength(savePath, msg)) { + response.setSuccess(false); + response.setMsg(msg[0]); + return response; + } + String fileRelativePath = Paths.get(fileRelativeDir, fiName.toString()).toString(); + // 处理文件已存在情况 + File targetFile = new File(savePath); + if (targetFile.exists()) { + boolean delete = targetFile.delete(); + if (!delete) { + response.setMsg("文件已存在且无法删除"); + return response; + } + } + + // 保存文件 + file.transferTo(targetFile.toPath()); + + // 处理图片旋转 + ImageUtil.translateImageByTag(savePath); + + // 处理缩略图 + boolean compThum = Boolean.parseBoolean(WebConfigUtil.get("ComprAttImg")); + if (toThum || compThum) { + toThumImg(savePath, toThum, compThum); + } + + // 处理视频转换 + convertVideo(savePath, fileType, fiName); + response.setOther(true); + // 构建响应数据 + String webPath; + if (appDomain != null && !appDomain.isEmpty()) { + webPath = appDomain + "/" + (isAbsPath ? "" : WebConfigUtil.getFileVPath()) + "/" + fileRelativePath; + } else { + webPath = WebUtil.getRequestDomain() + "/" + (isAbsPath ? "" : WebConfigUtil.getFileVPath()) + "/" + fileRelativePath; + } + + String webRelPath = "/" + (isAbsPath ? "" : WebConfigUtil.getFileVPath()) + "/" + fileRelativePath; + response.setSuccess(true); + FileUtil.PathInfo pathInfo = new FileUtil.PathInfo(); + pathInfo.WebPath = (webPath); + pathInfo.WebRelPath = (webRelPath).replace("//", "/"); + pathInfo.RelativePath = (fileRelativePath).replace("//", "/"); + pathInfo.OldFileName = (filename); + pathInfo.FileName = (fiName.toString()); + response.setData(pathInfo); + + } catch (IOException e) { + response.setMsg("文件保存失败:" + e.getMessage()); + } + + return response; + } + + /** + * 等价于C#的DownLoadFile方法:从指定URL下载文件到本地路径 + * + * @param downUrl 下载URL + * @param savePath 本地保存路径(含文件名) + * @return 下载成功返回true,失败(含异常)返回false + */ + public static boolean DownLoadFile(String downUrl, String savePath) { + if (downUrl == null || downUrl.trim().isEmpty() || savePath == null || savePath.trim().isEmpty()) { + return false; + } + + HttpClient client = HttpClient.newBuilder() + .followRedirects(HttpClient.Redirect.NEVER) + .build(); + + try { + URI currentUri = RemoteDownloadGuard.requireAllowedHttpUri(downUrl); + long maxBytes = RemoteDownloadGuard.maxDownloadBytes(); + HttpResponse response = null; + + for (int redirects = 0; redirects <= RemoteDownloadGuard.MAX_REDIRECTS; redirects++) { + HttpRequest request = HttpRequest.newBuilder() + .uri(currentUri) + .GET() + .build(); + + response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()); + int statusCode = response.statusCode(); + if (statusCode >= 300 && statusCode < 400) { + try (InputStream ignored = response.body()) { + // Close redirect response body before following the next hop. + } + Optional location = response.headers().firstValue("Location"); + if (location.isEmpty() || redirects == RemoteDownloadGuard.MAX_REDIRECTS) { + return false; + } + currentUri = RemoteDownloadGuard.requireAllowedRedirect(currentUri, location.get()); + continue; + } + + break; + } + + if (response == null || response.statusCode() < 200 || response.statusCode() >= 300) { + if (response != null) { + try (InputStream ignored = response.body()) { + // Close failed response body. + } + } + return false; + } + + OptionalLong contentLength = response.headers().firstValueAsLong("Content-Length"); + if (contentLength.isPresent() && contentLength.getAsLong() > maxBytes) { + try (InputStream ignored = response.body()) { + // Close oversized response body. + } + return false; + } + + Path saveFilePath = Paths.get(savePath); + Path parentDir = saveFilePath.getParent(); + if (parentDir != null && !Files.exists(parentDir)) { + Files.createDirectories(parentDir); + } + + // 核心修正:使用 StandardOpenOption 替代 StandardCopyOption + try (InputStream in = response.body(); + OutputStream out = Files.newOutputStream( + saveFilePath, + StandardOpenOption.CREATE, // 不存在则创建文件 + StandardOpenOption.TRUNCATE_EXISTING // 存在则清空覆盖(等价C#的覆盖行为) + )) { + copyWithMaxBytes(in, out, maxBytes); + } + + return true; + } catch (IOException e) { + return false; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return false; + } + } + + private static void copyWithMaxBytes(InputStream in, OutputStream out, long maxBytes) throws IOException { + byte[] buffer = new byte[4096]; + long totalRead = 0; + int bytesRead; + while ((bytesRead = in.read(buffer)) != -1) { + totalRead += bytesRead; + if (totalRead > maxBytes) { + throw new IOException("Remote download exceeds configured size limit"); + } + out.write(buffer, 0, bytesRead); + } + } + + /** + * 保存文件(从字节数组) + */ + public static BaseResponse saveFile_noCN(byte[] buffer, long position, long tot, String fileRelativeDir, + String filename, String appDomain, boolean toThum, int confirm, String absVPath, String downLoadUrl) { + BaseResponse response = new BaseResponse(); + response.setSuccess(false); + try { + if (!isNullOrEmpty(downLoadUrl) && isNullOrEmpty(filename)) { + filename = downLoadUrl; + } + if (!isNullOrEmpty(filename)) { + // 检查是否包含反斜杠(本地路径分隔符) + if (filename.indexOf("\\") > -1) { + // Java中反斜杠需要双重转义(\ → \\) + String[] pathParts = filename.split("\\\\"); + // 取最后一个元素(对应C#的 Last()),先判断数组长度避免越界 + if (pathParts.length > 0) { + filename = pathParts[pathParts.length - 1]; + } + } + + // 检查是否是URL(以http开头) + if (filename.startsWith("http")) { + // 按/分割URL,取最后一段 + String[] urlParts = filename.split("/"); + String lastUrlSegment = urlParts.length > 0 ? urlParts[urlParts.length - 1] : filename; + + // 按?分割,去除URL参数(取第一个元素,对应C#的 First()) + String[] paramParts = lastUrlSegment.split("\\?"); + if (paramParts.length > 0) { + filename = paramParts[0]; + } + } + } + + if (ImageUtil.isImg(filename) && toThum) { + //去除字符串末尾所有的/字符 + fileRelativeDir = fileRelativeDir.replaceAll("/+$", "") + "_high/"; + } + + // 处理文件名 +// if (filename != null && filename.contains("\\")) { +// filename = filename.substring(filename.lastIndexOf("\\") + 1); +// } +// +// // 处理图片目录 +// if (filename != null && ImageUtil.isImg(filename)) { +// fileRelativeDir = fileRelativeDir.trim() + "_high/"; +// } + + StringBuilder relPath = new StringBuilder(); + // 获取保存目录 + String saveDir = toAbsPath(fileRelativeDir, absVPath, relPath)[0]; + log.debug(String.valueOf("relPath : " + relPath)); + String utf8SaveDir = new String(saveDir.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); + + // 创建目录 +// File dir = new File(saveDir); + File dir = new File(utf8SaveDir); +// if (!dir.exists()) { +// Files.createDirectories(dir.toPath()); +// } + try { + // 强制创建目录(包括父目录),并设置权限 + if (!dir.exists()) { + dir.mkdirs(); // 替代 Files.createDirectories,兼容性更好 + dir.setWritable(true, false); // 设为可写 + dir.setReadable(true, false); // 设为可读 + dir.setExecutable(true, false); + } + // 检查权限 + if (!dir.canWrite()) { + throw new RuntimeException("目录无写入权限:" + saveDir); + } + } catch (Exception e) { + throw new RuntimeException("创建目录失败:" + saveDir, e); + } + + // 验证文件类型 + String fileType = getFileExtension(filename).toLowerCase(); + if (!checkFileType(fileType)) { + response.setSuccess(false); + response.setMsg("非法文件!"); + return response; + } + + // 处理文件名 +// StringBuilder fiName = new StringBuilder((filename == null || filename.isEmpty()) +// ? UUID.randomUUID() + "." + fileType +// : filename); + StringBuilder fiName = new StringBuilder(); + fiName.append(filename); + + String utf8FileName = new String(fiName.toString().getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); + // 保存路径 +// Path savePath = Paths.get(saveDir, fiName.toString()); + Path savePath = Paths.get(utf8SaveDir, utf8FileName); + // 处理文件已存在情况 + if (position == 0 && Files.exists(savePath)) { + if (confirm == 1) { + Files.delete(savePath); + confirm = 0; + } else { + response.setSuccess(false); + response.setMsg("文件已存在,是否覆盖?"); + response.setData(9); + return response; + } + } + + // 检查路径长度 + String[] msg = new String[1]; + if (position == 0 && !checkSaveViewFolderLength(savePath.toString(), msg)) { + response.setSuccess(false); + response.setMsg(msg[0]); + return response; + } + + // 检查权限 + if (position == 0 && !checkFileAuthory(savePath.toString(), absVPath)) { + response.setMsg("非法路径!"); + return response; + } + + // 写入文件 + boolean isFileProcessed = false; + if (buffer != null && buffer.length > 0) { // 修正:增加buffer非空判断,避免空指针 + // 修正点4:文件写入逻辑与C#等价 + boolean append = Files.exists(savePath) && position != 0; + try (FileOutputStream fos = new FileOutputStream(savePath.toString(), append)) { + fos.write(buffer); + } + isFileProcessed = true; + File targetFile = savePath.toFile(); + // 1. Linux下强制设置文件权限(所有用户可读,所有者可写,保证下载权限) + // - 644:所有者可读写,其他用户只读(生产推荐) + // - 777:所有用户可读可写可执行(测试推荐,最宽松) + targetFile.setReadable(true, false); // 所有用户可读(下载核心权限) + targetFile.setWritable(true, false); // 仅所有者可写(保证安全性) + targetFile.setExecutable(false); // 非可执行文件,关闭执行权限 + + // 文件写入完成判断 + if (position + buffer.length >= tot) { + // 修正点5:ToThumImg调用逻辑与C#一致 + boolean comprAttImg = toBoolean(WebConfigUtil.get("ComprAttImg")); + toThumImg(savePath.toString(), toThum, comprAttImg); + convertVideo(savePath.toString(), fileType, fiName); + response.setOther(true); + } + } + // C#原逻辑:buffer为空但downLoadUrl有值时下载文件 + else if (!isNullOrEmpty(downLoadUrl) && FileUtil.DownLoadFile(downLoadUrl, savePath.toString())) { + boolean comprAttImg = toBoolean(WebConfigUtil.get("ComprAttImg")); + toThumImg(savePath.toString(), toThum, comprAttImg); + convertVideo(savePath.toString(), fileType, fiName); + response.setOther(true); + isFileProcessed = true; + } + // 构建返回路径信息 + String fileRelativePath = Paths.get(relPath.toString(), fiName.toString()).toString().replace("\\", "/"); + String webPath = buildWebPath(fileRelativePath, appDomain); + String webRelPath = fileRelativePath; + + PathInfo pathInfo = new PathInfo(); + pathInfo.WebPath = webPath; + pathInfo.WebRelPath = webRelPath.replace("//", "/"); + pathInfo.RelativePath = fileRelativePath.replace("//", "/"); + pathInfo.SavePath = savePath.toString(); + pathInfo.OldFileName = filename; + pathInfo.FileName = fiName.toString(); + + response.setSuccess(true); + response.setData(pathInfo); + } catch (Exception e) { + response.setMsg(e.getMessage()); + } + return response; + } + + public static BaseResponse saveFile(byte[] buffer, long position, long tot, String fileRelativeDir, + String filename, String appDomain, boolean toThum, int confirm, String absVPath, String downLoadUrl) { + BaseResponse response = new BaseResponse(); + response.setSuccess(false); + try { + if (!isNullOrEmpty(downLoadUrl) && isNullOrEmpty(filename)) { + filename = downLoadUrl; + } + if (!isNullOrEmpty(filename)) { + if (filename.indexOf("\\") > -1) { + String[] pathParts = filename.split("\\\\"); + if (pathParts.length > 0) { + filename = pathParts[pathParts.length - 1]; + } + } + + if (filename.startsWith("http")) { + String[] urlParts = filename.split("/"); + String lastUrlSegment = urlParts.length > 0 ? urlParts[urlParts.length - 1] : filename; + String[] paramParts = lastUrlSegment.split("\\?"); + if (paramParts.length > 0) { + filename = paramParts[0]; + } + } + } + + filename = sanitizeFileName(filename); + + if (ImageUtil.isImg(filename) && toThum) { + fileRelativeDir = fileRelativeDir.replaceAll("/+$", "") + "_high/"; + } + + StringBuilder relPath = new StringBuilder(); + String saveDir = toAbsPath(fileRelativeDir, absVPath, relPath)[0]; + + String utf8SaveDir = saveDir; +// String utf8FileName = ensureUtf8String(filename); + String utf8FileName = (filename); + + File dir = new File(utf8SaveDir); + try { + if (!dir.exists()) { + dir.mkdirs(); + dir.setWritable(true, false); + dir.setReadable(true, false); + dir.setExecutable(true, false); + } + if (!dir.canWrite()) { + throw new RuntimeException("目录无写入权限:" + saveDir); + } + } catch (Exception e) { + throw new RuntimeException("创建目录失败:" + saveDir, e); + } + + String fileType = getFileExtension(filename).toLowerCase(); + if (!checkFileType(fileType)) { + response.setSuccess(false); + response.setMsg("非法文件!"); + return response; + } + + StringBuilder fiName = new StringBuilder(); + fiName.append(utf8FileName); + + +// Path savePath = Paths.get(utf8SaveDir, fiName.toString()); + String savePath = String.format("%s/%s", utf8SaveDir, filename); + String savef = String.format("%s/%s", utf8SaveDir, filename); + log.debug(String.format("savePath: %s\n,savef : %s\n", savePath, savef)); + File saveFile = new File(savef); +// if (position == 0 && Files.exists(savePath)) { + if (position == 0 && saveFile.exists()) { + if (confirm == 1) { + saveFile.delete(); + confirm = 0; + } else { + response.setSuccess(false); + response.setMsg("文件已存在,是否覆盖?"); + response.setData(9); + return response; + } + } + + String[] msg = new String[1]; + if (position == 0 && !checkSaveViewFolderLength(savePath, msg)) { + response.setSuccess(false); + response.setMsg(msg[0]); + return response; + } + + if (position == 0 && !checkFileAuthory(savePath, absVPath)) { + response.setMsg("非法路径!"); + return response; + } + + boolean isFileProcessed = false; + if (buffer != null && buffer.length > 0) { +// boolean append = Files.exists(savePath) && position != 0; + boolean append = saveFile.exists() && position != 0; + try (FileOutputStream fos = new FileOutputStream(savePath, append)) { + fos.write(buffer); + } + isFileProcessed = true; + File targetFile = saveFile; + targetFile.setReadable(true, false); + targetFile.setWritable(true, false); + targetFile.setExecutable(false); + + if (position + buffer.length >= tot) { + boolean comprAttImg = toBoolean(WebConfigUtil.get("ComprAttImg")); + toThumImg(savePath, toThum, comprAttImg); + convertVideo(savePath, fileType, fiName); + response.setOther(true); + } + } else if (!isNullOrEmpty(downLoadUrl) && FileUtil.DownLoadFile(downLoadUrl, savePath)) { + boolean comprAttImg = toBoolean(WebConfigUtil.get("ComprAttImg")); + toThumImg(savePath, toThum, comprAttImg); + convertVideo(savePath, fileType, fiName); + response.setOther(true); + isFileProcessed = true; + } + +// String fileRelativePath = String.format(relPath.toString(), fiName).replace("\\", "/"); + String fileRelativePath = Paths.get(relPath.toString(), fiName.toString()).toString().replace("\\", "/"); + String webPath = buildWebPath(fileRelativePath, appDomain); + String webRelPath = fileRelativePath; + + PathInfo pathInfo = new PathInfo(); + pathInfo.WebPath = webPath; + pathInfo.WebRelPath = webRelPath.replace("//", "/"); + pathInfo.RelativePath = fileRelativePath.replace("//", "/"); + pathInfo.SavePath = savePath; + pathInfo.OldFileName = filename; + pathInfo.FileName = fiName.toString(); + + response.setSuccess(true); + response.setData(pathInfo); + } catch (Exception e) { + LoggerHandler.error(new Object(), "保存文件失败:" + e.getMessage() + ", filename=" + filename, e); + response.setMsg("文件保存失败:" + getErrMsg(e)); + } + return response; + } + + // 文件名清理 + private static String sanitizeFileName(String filename) { + if (filename == null || filename.isEmpty()) { + return filename; + } + + String sanitized = filename.trim(); + + if (sanitized.contains("%")) { + sanitized = URLDecoder.decode(sanitized, StandardCharsets.UTF_8); + } + + sanitized = sanitized.replaceAll("[\\\\/:*?\"<>|]", "_"); + + while (sanitized.contains("__")) { + sanitized = sanitized.replace("__", "_"); + } + + return sanitized; + } + + // UTF-8 编码转换 + // ... existing code ... + + public static String ensureUtf8String(String str) { + if (str == null || str.isEmpty()) { + return str; + } + + String processed = str.trim(); + + try { + // 【第一步】先处理 URL 编码(如 %E4%B8%AD → 中) + if (processed.contains("%")) { + boolean hasUrlEncoding = false; + // 检查是否真的是 URL 编码(%后跟两个十六进制字符) + for (int i = 0; i < processed.length() - 2; i++) { + if (processed.charAt(i) == '%' && + isHexDigit(processed.charAt(i + 1)) && + isHexDigit(processed.charAt(i + 2))) { + hasUrlEncoding = true; + break; + } + } + + if (hasUrlEncoding) { + processed = URLDecoder.decode(processed, StandardCharsets.UTF_8); + } + } + + // 【第二步】再处理可能的 ISO-8859-1 编码问题 + byte[] bytes = processed.getBytes(StandardCharsets.ISO_8859_1); + String testUtf8 = new String(bytes, StandardCharsets.UTF_8); + + // 如果包含问号或明显是乱码,说明是 ISO-8859-1 编码错误 + if (testUtf8.contains("?") || (!testUtf8.equals(processed) && !looksLikeValidText(testUtf8))) { + processed = testUtf8; + } + + return processed; + + } catch (Exception e) { + LoggerHandler.error(new Object(), "UTF-8 编码转换失败:" + e.getMessage() + ", original=" + str); + return str; + } + } + + /** + * 辅助方法:判断字符串是否看起来像有效的文本(非乱码) + */ + private static boolean looksLikeValidText(String str) { + if (str == null || str.isEmpty()) { + return false; + } + + // 检查是否包含常见的有效字符(中文、英文、数字、常见符号) + int validCharCount = 0; + for (char c : str.toCharArray()) { + // 中文、英文、数字、常见标点 + if (Character.isLetterOrDigit(c) || + "._-()[] ".indexOf(c) >= 0 || + (c >= 0x4e00 && c <= 0x9fff)) { // 常用汉字范围 + validCharCount++; + } + } + + // 如果超过 70% 的字符是有效的,认为是合法文本 + return (double) validCharCount / str.length() > 0.7; + } + + + // 修改后(兼容所有 Java 版本) + private static boolean isHexDigit(char c) { + return (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'F') || + (c >= 'a' && c <= 'f'); + } + + + private static String getErrMsg(Exception e) { + if (e == null) { + return ""; + } + String msg = e.getMessage(); + if (msg == null) { + return e.getClass().getSimpleName(); + } + return msg; + } + + + public static String CreateAttcFileName(String localPath, String fileName) { + return CreateAttcFileName(localPath, fileName, 1); + } + + public static String CreateAttcFileName(String localPath, String fileName, int index) { + File dir = new File(localPath); + // 保留初始传入的index,后续循环修改这个变量 + int finalIndex = index; + + if (dir.exists() && dir.isDirectory()) { + try { + // 关键修改1:过滤仅保留文件(排除子目录),对齐C#的Directory.GetFiles + File[] files = dir.listFiles(File::isFile); + + if (files != null && files.length > 0) { + // 索引提取逻辑保持不变(和C#一致,无需修改) + int[] fileIndex = Arrays.stream(files) + .map(File::getPath) + .map(fname -> { + // 统一路径分隔符,拆分取文件名 + String name = fname.replace("/", "\\"); + String[] pathParts = name.split("\\\\"); + name = pathParts.length > 0 ? pathParts[pathParts.length - 1] : ""; + + // 按_拆分,取第一段 + String[] nameParts = name.split("_"); + name = nameParts.length > 0 ? nameParts[0] : ""; + + // 去掉开头所有0 + name = name.replaceAll("^0+", ""); + + // 长度>4取最后4位 + if (name.length() > 4) { + name = name.substring(name.length() - 4); + } + + // 转为整数(替换为你实际的NativeExtensionUtils.ToInt32) + return ToInt32(name); + }) + .mapToInt(Integer::intValue) + .toArray(); + + // 关键修改2:替换为C#的循环检查逻辑(核心对齐点) + // 用HashSet提升包含判断的效率(数组contains是O(n),HashSet是O(1)) + Set indexSet = new HashSet<>(); + for (int idx : fileIndex) { + indexSet.add(idx); + } + // 循环检查:如果当前索引已存在,就+1,直到找到不存在的索引 + while (indexSet.contains(finalIndex)) { + finalIndex += 1; + } + } + } catch (Exception e) { + // 保持和原逻辑一致的空catch + } + } + + // 生成最终文件名(和C#格式一致) + return String.format("00%d_%s", finalIndex, fileName); + } + + /** + * 获取文件保存路径信息 + */ + public static String getFileSavePath_noCN(String fileRelativeDir, String fileType, String appDomain, + String absPath, StringBuilder fileName, StringBuilder relativePath, StringBuilder webPath) { + Path savePath = null; + try { + // 处理文件名 +// if (fileName != null && fileName.toString().contains("/")) { +// fileName = new StringBuilder(fileName.toString().substring(fileName.toString().lastIndexOf("/") + 1)); +// } + + if (fileName != null && fileName.length() > 0) { + // 1. 将 StringBuilder 转为字符串处理(反斜杠替换为斜杠) +// String fileNameStr = fileName.toString().replace("\\", "/"); + String fileNameStr = new String( + fileName.toString().replace("\\", "/").getBytes(StandardCharsets.UTF_8), + StandardCharsets.UTF_8 + ); + // 2. 检查是否包含斜杠,若有则截取最后一部分 + if (fileNameStr.contains("/")) { + // 按斜杠分割,取最后一段 + fileNameStr = fileNameStr.split("/")[fileNameStr.split("/").length - 1]; + } + + // 3. 清空原 StringBuilder 并填入处理后的结果(避免创建新对象) + fileName.setLength(0); + fileName.append(fileNameStr); + } + + StringBuilder relPath = new StringBuilder(); +// 2026.3.9 修改中文路径 + String utf8FileRelativeDir = new String(fileRelativeDir.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); + String utf8AbsPath = new String(absPath.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); + String saveDir = toAbsPath(utf8FileRelativeDir, utf8AbsPath, relPath)[0]; + + saveDir = new String(saveDir.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); +// // 获取绝对路径和相对路径 +// String saveDir = toAbsPath(fileRelativeDir, absPath, relPath)[0]; +// out.println("relPath(getFileSavePath) : " + relPath); + + // 创建目录 + File dir = new File(saveDir); + if (!dir.exists()) { + Files.createDirectories(dir.toPath()); + // 关键:Linux下给中文目录加全权限(必须有x权限才能进入) + dir.setReadable(true, false); + dir.setWritable(true, false); + dir.setExecutable(true, false); + } + + // 处理文件类型和文件名 + if (fileType == null || fileType.isEmpty()) { + fileType = getFileExtension(String.valueOf(fileName)); + } + String fiName = (fileName == null || fileName.isEmpty()) + ? UUID.randomUUID() + "." + fileType + : String.valueOf(fileName); +// fileName = new StringBuilder(fiName); + fiName = new String(fiName.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); + log.debug(String.valueOf("fiName: " + fiName)); + // 构建路径 + savePath = Paths.get(saveDir, fiName); +// relativePath.append(Paths.get(relPath.toString(), fiName).toString().replace("//", "/")); + relativePath.append(Paths.get(relPath.toString(), fiName).toString() + .replace("//", "/") + .replace("\\", "/")); + log.debug(String.valueOf("relativePath(getFileSavePath) : " + relativePath)); + webPath.append(buildWebPath(relativePath.toString(), appDomain)); + + return savePath.toString(); + } catch (Exception e) { + //throw new RuntimeException("获取文件保存路径失败", e); + + throw new RuntimeException("getFileSavePath生成路径失败!relativeDir=" + savePath + "fileRelativeDir, absPath " + fileRelativeDir + absPath + ", filename=" + fileName, e); + } + } + + public static String getFileSavePath(String fileRelativeDir, String fileType, String appDomain, + String absPath, StringBuilder fileName, StringBuilder relativePath, StringBuilder webPath) { + Path savePath = null; + try { + if (fileName != null && !fileName.isEmpty()) { + String fileNameStr = fileName.toString().replace("\\", "/"); + + if (fileNameStr.contains("%")) { + fileNameStr = URLDecoder.decode(fileNameStr, StandardCharsets.UTF_8); + } + + if (fileNameStr.contains("/")) { + fileNameStr = fileNameStr.split("/")[fileNameStr.split("/").length - 1]; + } + + fileNameStr = sanitizeFileName(fileNameStr); + + fileName.setLength(0); + fileName.append(fileNameStr); + } + + StringBuilder relPath = new StringBuilder(); +// String utf8FileRelativeDir = ensureUtf8String(fileRelativeDir); +// String utf8AbsPath = ensureUtf8String(absPath); + String saveDir = toAbsPath(fileRelativeDir, absPath, relPath)[0]; + +// saveDir = ensureUtf8String(saveDir); + + File dir = new File(saveDir); + if (!dir.exists()) { + Files.createDirectories(dir.toPath()); + dir.setReadable(true, false); + dir.setWritable(true, false); + dir.setExecutable(true, false); + } + + if (fileType == null || fileType.isEmpty()) { + fileType = getFileExtension(String.valueOf(fileName)); + } + String fiName = (fileName == null || fileName.isEmpty()) + ? UUID.randomUUID() + "." + fileType + : String.valueOf(fileName); + +// fiName = ensureUtf8String(fiName); + + log.debug(String.valueOf("fiName: " + fiName)); + + savePath = Paths.get(saveDir, fiName); + + relativePath.append(Paths.get(relPath.toString(), fiName).toString() + .replace("//", "/") + .replace("\\", "/")); + log.debug(String.valueOf("relativePath(getFileSavePath) : " + relativePath)); + webPath.append(buildWebPath(relativePath.toString(), appDomain)); + + return savePath.toString(); + } catch (Exception e) { + LoggerHandler.error(new Object(), "getFileSavePath 生成路径失败!fileRelativeDir=" + fileRelativeDir + + ", absPath=" + absPath + ", filename=" + fileName, e); + throw new RuntimeException("getFileSavePath 生成路径失败!relativeDir=" + savePath + + ", fileRelativeDir=" + fileRelativeDir + ", absPath=" + absPath + + ", filename=" + fileName, e); + } + } + + /** + * 检查保存路径长度是否合法 + */ + public static boolean checkSaveViewFolderLength(String filePath, String[] msg) { + int length = checkFolderLength(filePath); + if (length > 260) { + msg[0] = (StringFormat.format("文件夹及文件名过长,不得超过260个字符,当前总长度{0}!", length)); + return false; + } + + Path dir = Paths.get(filePath); + filePath = dir.toString(); + String dirPath = filePath.substring(0, filePath.lastIndexOf(File.separator)); + length = checkFolderLength(dirPath); + if (length > 248) { + msg[0] = (String.format("文件夹过长,目录名必须少于248个字符,当前总长度{0}!", length)); + return false; + } + + // 检查预览文件路径 + StringBuilder vHtmlPath = new StringBuilder(); + String htmlPath = WebConfigUtil_web.getViewDocHtmlPath(filePath, vHtmlPath); + length = checkFolderLength(htmlPath); + if (length > 260) { + msg[0] = (String.format("文件预览路径过长,不得超过260个字符,当前总长度{0}!", length)); + return false; + } + + String htmlDirPath = htmlPath.substring(0, htmlPath.lastIndexOf("/")); +// String htmlDirPath = htmlPath.substring(0, htmlPath.lastIndexOf('/')); + length = checkFolderLength(htmlDirPath); + if (length > 248) { + msg[0] = (String.format("文件预览目录过长,必须少于248个字符,当前总长度%s!", length)); + return false; + } + return true; + } + + /** + * 检查文件类型是否合法 + */ + public static boolean checkFileType(String fileType) { + if (fileType == null || fileType.isEmpty()) { + return false; + } + fileType = fileType.toLowerCase().trim(); + if (EXCLUDE_TYPES.contains(fileType)) { + return false; + } + + String ftype = "." + fileType; + if (ALLOW_FILE_TYPES.contains(ftype)) { + return true; + } + + // 检查配置的允许类型 + String allowUpload = WebConfigUtil_web.get("allowUploadFile", ""); + if (!allowUpload.isEmpty() && Arrays.asList(allowUpload.toLowerCase().split(",")).contains(ftype)) { + return true; + } + + // 检查配置的禁止类型 + String notUpload = WebConfigUtil_web.get("notUploadFile", ""); + if (!notUpload.isEmpty() && Arrays.asList(notUpload.toLowerCase().split(",")).contains(ftype)) { + return false; + } + + return false; + } + + /** + * 检查文件夹路径长度 + */ + public static int checkFolderLength(String folder) { + String cleanFolder = folder.replace("\\", "").replace("/", "").replace(":", ""); + Pattern pattern = Pattern.compile("[^\\x00-\\xFF]"); + Matcher matcher = pattern.matcher(cleanFolder); + int chineseCount = cleanFolder.length() - matcher.replaceAll("").length(); + int asciiCount = cleanFolder.length() - chineseCount; + return chineseCount * 2 + asciiCount + 1; + } + + /** + * 检查文件是否存在 + */ + public static BaseResponse checkExistsFile(String fileRelativeDir, String filename) { + BaseResponse response = new BaseResponse(); + response.setSuccess(false); + + try { + if (filename != null && filename.contains("\\")) { + filename = filename.substring(filename.lastIndexOf("\\") + 1); + } + + boolean isAbsPath = fileRelativeDir.startsWith("/"); + if (isAbsPath) { + fileRelativeDir = fileRelativeDir.substring(1); + } + + String saveDir = Paths.get( + isAbsPath ? getRequest().getServletContext().getRealPath("/") : WebConfigUtil_web.getFilePath(), + fileRelativeDir + ).toString(); + + Path savePath = Paths.get(saveDir, filename); + if (Files.exists(savePath)) { + response.setOther(1); + response.setMsg(String.format("文件已存在: %s", filename)); + } else { + response.setSuccess(true); + } + } catch (Exception e) { + response.setMsg(e.getMessage()); + } + + return response; + } + + /** + * 完全对齐 C# 的 ToAbsPath 方法逻辑 + * + * @param filePath 输入文件路径(可能是相对/绝对路径,含编码) + * @param absPath 基础绝对路径(可为 null,null 时使用配置的 FilePath) + * @param vPath 输出参数:虚拟路径(通过 StringBuilder 传递,对应 C# 的 out 参数) + * @return 处理后的绝对路径(反斜杠分隔,尾部无斜杠) + */ +// public static String[] toAbsPath(String filePath, String absPath, StringBuilder vPath) { +// // 1. 初始化虚拟路径 vPath(对应 C# 的 vPath = "/") +// // 注:Java 无 out 参数,通过重置 StringBuilder 内容实现输出 +// if (vPath == null) { +// vPath = new StringBuilder(); +// } +// vPath.setLength(0); // 清空原有内容 +// vPath.append("/"); +// +// try { +// // 2. URL 解码(对应 C# 的 HttpUtility.UrlDecode) +// if (filePath != null && filePath.contains("%")) { +// filePath = URLDecoder.decode(filePath, StandardCharsets.UTF_8.name()); +// } +// +// // 3. 修剪路径前缀(. ~ / \)+ 统一反斜杠为斜杠(完全复刻 C# 的 TrimStart 逻辑) +// if (filePath != null) { +// // 先修剪前缀:. ~ / \(顺序与 C# 一致:TrimStart('.').TrimStart('~').TrimStart('/').TrimStart('\\')) +// String trimmed = filePath.trim(); +// trimmed = trimStart(trimmed, '.'); +// trimmed = trimStart(trimmed, '~'); +// trimmed = trimStart(trimmed, '/'); +// trimmed = trimStart(trimmed, '\\'); +// // 统一反斜杠为斜杠 +// filePath = trimmed.replace("\\", "/"); +// } +// +// // 4. 处理相对路径(无盘符时,拼接基础路径) +// if (filePath == null || filePath.indexOf(":") < 0) { // 无盘符(非绝对路径) +// // 4.1 基础路径优先级:absPath 不为空则用 absPath,否则用配置的 FilePath(对应 C# 的 WebConfigUtil.FilePath) +// if (absPath == null || absPath.isEmpty()) { +// absPath = WebConfigUtil.getFilePath(); +// } +// +// // 4.2 读取配置的虚拟路径前缀(对应 C# 的 WebConfigUtil.FileVPath) +// String fileVPath = WebConfigUtil.getFileVPath(); +// // 处理空的 fileVPath(避免后续 substring 异常) +// if (fileVPath == null) { +// fileVPath = ""; +// } +// +// // 4.3 大小写不敏感判断:filePath 是否以 fileVPath 开头,且 absPath 包含 fileVPath +// boolean startsWithVPath = false; +// boolean absContainsVPath = false; +// if (!fileVPath.isEmpty() && filePath != null) { +// // 复刻 C# 的 StringComparison.OrdinalIgnoreCase(大小写不敏感) +// startsWithVPath = filePath.toLowerCase().startsWith(fileVPath.toLowerCase()); +// absContainsVPath = absPath.toLowerCase().contains(fileVPath.toLowerCase()); +// } +// +// // 4.4 拼接路径(对应 C# 的 Path.Combine) +// Path combinedPath; +// if (startsWithVPath && absContainsVPath) { +// // 截取 filePath 中 fileVPath 之后的部分,并修剪前缀 / +// String subPath = trimStart(filePath.substring(fileVPath.length()), '/'); +// combinedPath = Paths.get(absPath, subPath); +// } else { +// // 直接拼接基础路径和 filePath +// combinedPath = Paths.get(absPath, filePath); +// } +// +// // 转换为字符串路径(统一处理分隔符) +// filePath = combinedPath.toString(); +// } +// +// // 5. 计算虚拟路径 vPath(对应 C# 的 $"/{filePath.Substring(vIndex)}") +// String fileVPath = WebConfigUtil.getFileVPath(); +// if (fileVPath == null) { +// fileVPath = ""; +// } +// int vIndex = -1; +// if (!fileVPath.isEmpty() && filePath != null) { +// // 大小写不敏感查找最后一个 fileVPath(对应 C# 的 LastIndexOf(..., OrdinalIgnoreCase)) +// vIndex = filePath.toLowerCase().lastIndexOf(fileVPath.toLowerCase()); +// } +// out.println("vIndex: " + vIndex); +// if (vIndex > -1) { +// // 截取从 vIndex 开始的子串,拼接为 "/xxx" 格式,统一分隔符为 / +// String vPathSub = filePath.substring(vIndex).replace("\\", "/"); +// vPath.setLength(0); // 清空原有内容 +// vPath.append("/").append(vPathSub); +// } +//// out.println("vPath: " + vPath); //测试vpath是否正确 +// // 6. 处理返回路径:尾部无 / + 统一为反斜杠(对应 C# 的 TrimEnd('/').Replace("/","\\")) +// String resultPath = filePath; +// if (resultPath != null) { +// // 修剪尾部 /(复刻 C# 的 TrimEnd('/')) +// resultPath = trimEnd(resultPath, '/'); +// // 统一斜杠为反斜杠 +// resultPath = resultPath.replace("/", "\\"); +// } +// +// // 返回结果:[0] 处理后的绝对路径,[1] 虚拟路径(辅助返回,vPath 已通过输出参数传递) +// return new String[]{resultPath, vPath.toString()}; +// +// } catch (Exception e) { +// // 异常时返回原始 filePath + 当前 vPath(对齐 C# 异常处理逻辑) +// String errorPath = filePath != null ? filePath : ""; +// return new String[]{errorPath, vPath.toString()}; +// } +// } + public static String[] toAbsPath(String filePath, String absPath, StringBuilder vPath) { + // 1. 虚拟路径初始化 + if (vPath == null) { + vPath = new StringBuilder(); + } + vPath.setLength(0); + vPath.append("/"); + + try { + // 2. URL解码(处理%23等特殊字符) + if (filePath != null && filePath.contains("%")) { + filePath = URLDecoder.decode(filePath, StandardCharsets.UTF_8); + } + + // 3. 路径修剪 + 统一分隔符为 /(Linux优先,Windows自动兼容) + String cleanFilePath = filePath; + if (cleanFilePath != null) { + cleanFilePath = trimStart(cleanFilePath, '.'); + cleanFilePath = trimStart(cleanFilePath, '~'); + cleanFilePath = trimStart(cleanFilePath, '/'); + cleanFilePath = trimStart(cleanFilePath, '\\'); + // 核心:统一所有分隔符为 /(Linux标准,Windows也识别) + cleanFilePath = cleanFilePath.replace("\\", "/"); + } + + // 4. 相对路径拼接(仅非Windows盘符路径执行) + if (cleanFilePath != null && cleanFilePath.indexOf(":") < 0) { + // 兜底获取基础路径 + if (absPath == null || absPath.isEmpty()) { + absPath = WebConfigUtil.getFilePath(); + } + // 基础路径也统一为 / 分隔符 + String cleanAbsPath = absPath.replace("\\", "/"); + + String fileVPath = WebConfigUtil.getFileVPath(); + fileVPath = (fileVPath == null ? "" : fileVPath.replace("\\", "/")); + + boolean startsWithVPath = false; + boolean absContainsVPath = false; + if (!fileVPath.isEmpty() && cleanFilePath != null) { + startsWithVPath = cleanFilePath.toLowerCase().startsWith(fileVPath.toLowerCase()); + absContainsVPath = cleanAbsPath.toLowerCase().contains(fileVPath.toLowerCase()); + } + + Path combinedPath; + if (startsWithVPath && absContainsVPath) { + // 正则匹配虚拟路径前缀(兼容带数字后缀的情况,如fileRoot_1) + String pattern = "^" + Pattern.quote(fileVPath) + "(?:_[0-9]+)?"; + Pattern regex = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); + Matcher match = regex.matcher(cleanFilePath); + int subLength = fileVPath.length(); + if (match.find()) { + subLength = match.end(); + } + String subPath = trimStart(cleanFilePath.substring(subLength), '/'); + combinedPath = Paths.get(cleanAbsPath, subPath); + } else { + combinedPath = Paths.get(cleanAbsPath, cleanFilePath); + } + + // 标准化路径(移除../、//,统一为 / 分隔符) + cleanFilePath = combinedPath.toAbsolutePath().normalize().toString().replace("\\", "/"); + } + + // 5. 虚拟路径计算(统一为 / 分隔符) + String fileVPath = WebConfigUtil.getFileVPath(); + fileVPath = (fileVPath == null ? "" : fileVPath.replace("\\", "/")); + int vIndex = -1; + if (!fileVPath.isEmpty() && cleanFilePath != null) { + vIndex = cleanFilePath.toLowerCase().lastIndexOf(fileVPath.toLowerCase()); + } + if (vIndex > -1) { + String vPathSub = cleanFilePath.substring(vIndex).replace("\\", "/"); + vPath.setLength(0); + vPath.append("/").append(trimStart(vPathSub, '/')); + } else { + // 匹配失败时,使用完整路径作为虚拟路径 + vPath.append(cleanFilePath == null ? "/" : cleanFilePath); + } + + // 6. 递归处理HTTP路径(递归时也统一分隔符) + if (cleanFilePath != null && cleanFilePath.startsWith("http")) { + String[] recursiveResult = toAbsPath(vPath.toString(), absPath, vPath); + // 递归结果也统一为 / 分隔符 + recursiveResult[0] = recursiveResult[0].replace("\\", "/"); + recursiveResult[1] = recursiveResult[1].replace("\\", "/"); + return recursiveResult; + } + + // 7. 最终路径处理(根据系统适配分隔符,Linux用/,Windows用\) + String resultPath = cleanFilePath; + if (resultPath != null) { + resultPath = trimEnd(resultPath, '/'); + // 核心:根据系统自动适配分隔符,不再强制转\ + if (System.getProperty("os.name").toLowerCase().contains("win")) { + resultPath = resultPath.replace("/", "\\"); // Windows才转\ + } else { + resultPath = resultPath.replace("\\", "/"); // Linux保留/ + } + } + + // 8. 最终返回(确保虚拟路径也是标准分隔符) + String vPathStr = vPath.toString().replace("\\", "/"); + return new String[]{resultPath, vPathStr}; + + } catch (Exception e) { + String errorPath = filePath != null ? filePath.replace("\\", "/") : ""; + return new String[]{errorPath, vPath.toString().replace("\\", "/")}; + } + } + + public static String[] toAbsPath_old(String filePath, String absPath, StringBuilder vPath) { + // 初始化虚拟路径,避免空指针 + if (vPath == null) { + vPath = new StringBuilder(); + } + vPath.setLength(0); + vPath.append("/"); + + try { + // 1. URL解码(处理特殊字符,如%20) + if (filePath != null && filePath.contains("%")) { + filePath = URLDecoder.decode(filePath, StandardCharsets.UTF_8.name()); + } + + // 2. 清理路径前缀 + 统一分隔符为 /(彻底抛弃\,避免Linux/Docker识别错误) + String cleanFilePath = filePath; + if (cleanFilePath != null) { + String trimmed = cleanFilePath.trim(); + // 依次修剪前缀:. ~ / \(避免路径开头的无效字符) + trimmed = trimStart(trimmed, '.'); + trimmed = trimStart(trimmed, '~'); + trimmed = trimStart(trimmed, '/'); + trimmed = trimStart(trimmed, '\\'); + // 强制统一所有分隔符为 /(Linux/Docker 唯一正确分隔符) + cleanFilePath = trimmed.replace("\\", "/"); + } + + // 3. 拼接绝对路径(核心:避免截断、保证路径完整) + if (cleanFilePath == null || !cleanFilePath.contains(":")) { // 非Windows盘符路径 + // 获取基础路径(兜底为配置的文件路径) + if (absPath == null || absPath.isEmpty()) { + absPath = WebConfigUtil.getFilePath(); // 确保该方法返回Linux路径,如 /home/Lserp/fileRoot + } + // 统一基础路径分隔符为 / + String cleanAbsPath = absPath.replace("\\", "/"); + + // 处理虚拟路径前缀(避免因fileVPath匹配错误截断路径) + String fileVPath = WebConfigUtil.getFileVPath(); + fileVPath = (fileVPath == null ? "" : fileVPath.trim().replaceAll("^/+|/+$", "")); + + boolean startsWithVPath = false; + boolean absContainsVPath = false; + String subPath = cleanFilePath; // 默认使用完整路径,不截断 + if (!fileVPath.isEmpty() && cleanFilePath != null) { + // 大小写不敏感匹配(仅匹配开头,避免中间匹配导致截断) + String lowerFilePath = cleanFilePath.toLowerCase(); + String lowerVPath = fileVPath.toLowerCase(); + startsWithVPath = lowerFilePath.startsWith(lowerVPath); + absContainsVPath = cleanAbsPath.toLowerCase().contains(lowerVPath); + + // 仅当开头匹配时才截取(避免中间匹配导致路径截丢) + if (startsWithVPath) { + subPath = cleanFilePath.substring(fileVPath.length()); + subPath = trimStart(subPath, '/'); // 移除开头多余/,避免// + } + } + + // 拼接路径(关键:用Paths.normalize保证路径完整,避免../或//) + Path combinedPath = Paths.get(cleanAbsPath, subPath == null ? "" : subPath) + .toAbsolutePath() // 强制转为绝对路径 + .normalize(); // 标准化路径(修复../、//等问题) + cleanFilePath = combinedPath.toString().replace("\\", "/"); + } + + // 4. 生成虚拟路径(核心修复:避免vIndex错误导致路径截断) + String fileVPath = WebConfigUtil.getFileVPath(); + fileVPath = (fileVPath == null ? "" : fileVPath.trim().replaceAll("^/+|/+$", "")); + int vIndex = -1; + if (!fileVPath.isEmpty() && cleanFilePath != null) { + // 仅查找fileVPath在路径中的最后一次出现(且仅匹配完整目录名) + String lowerFilePath = cleanFilePath.toLowerCase(); + String lowerVPath = fileVPath.toLowerCase(); + vIndex = lowerFilePath.lastIndexOf(lowerVPath); + // 关键:校验匹配位置是否合法(避免匹配到字符串中间,如Lserp3中的3) + if (vIndex > 0) { + // 确保匹配的是完整目录(前后是/或开头/结尾) + char prevChar = lowerFilePath.charAt(vIndex - 1); + boolean isWholeDir = prevChar == '/' || vIndex == 0; + if (!isWholeDir) { + vIndex = -1; // 非完整目录匹配,放弃截取 + } + } + } + log.debug(String.valueOf("vIndex: " + vIndex)); // 可注释,仅调试用 + + // 生成虚拟路径(仅当vIndex合法时才截取,否则用完整路径) + vPath.setLength(0); + if (vIndex > -1 && cleanFilePath != null) { + String vPathSub = cleanFilePath.substring(vIndex + fileVPath.length()).replace("\\", "/"); + vPath.append("/").append(trimStart(vPathSub, '/')); + } else { + // 匹配失败时,使用完整路径作为虚拟路径(避免截丢) + vPath.append(cleanFilePath == null ? "/" : cleanFilePath.replace("\\", "/")); + } + + // 5. 最终路径处理(保证Linux/Docker格式,无截断) + String resultPath = cleanFilePath; + if (resultPath != null) { + resultPath = trimEnd(resultPath, '/'); // 移除末尾/ + resultPath = resultPath.replace("\\", "/"); // 最终统一为/ + } + + // 返回:[0]物理绝对路径 [1]虚拟路径 + return new String[]{resultPath, vPath.toString()}; + + } catch (Exception e) { + // 异常时返回原始路径(避免空路径) + String errorPath = filePath != null ? filePath.replace("\\", "/") : ""; + vPath.setLength(0); + vPath.append("/").append(errorPath); + return new String[]{errorPath, vPath.toString()}; + } + } + + + /** + * 检查文件权限(防止路径穿越) + */ + public static boolean checkFileAuthory(String path, String attcPath) { + if (path == null || path.isEmpty()) { + return false; + } + try { + String fullPath = normalizeCanonicalPath(path); + return isUnderDirectory(fullPath, WebConfigUtil_web.getServerPath()) + || isUnderDirectory(fullPath, WebConfigUtil_web.getFilePath()) + || isUnderDirectory(fullPath, attcPath); + } catch (Exception e) { + return false; + } + } + + private static boolean isUnderDirectory(String fullPath, String allowedRoot) throws IOException { + if (allowedRoot == null || allowedRoot.trim().isEmpty()) { + return false; + } + String rootPath = normalizeCanonicalPath(allowedRoot); + return fullPath.equals(rootPath) || fullPath.startsWith(rootPath + "/"); + } + + private static String normalizeCanonicalPath(String path) throws IOException { + return new File(path).getCanonicalPath() + .replace("\\", "/") + .replaceAll("/+", "/") + .trim(); + } + + // 线程池:用于异步执行空目录删除(对应 C# 的 Task.Start()) + /** + * @param filePath 文件路径(可为虚拟路径或绝对路径) + * @param absPath 若 filePath 为虚拟路径,传递绝对盘符路径(如 "D:/data") + * @return BaseResponse 包含操作结果(success:是否成功,other:1 表示已删除) + */ + public static BaseResponse deleteFile(String filePath, String absPath) { + BaseResponse response = new BaseResponse(); + response.setSuccess(false); // 初始化为失败 + StringBuilder vPath = new StringBuilder(); // 对应 C# 的 out string vPath + + try { + // 1. 调用之前实现的 toAbsPath 方法,转换为绝对路径(对齐 C# 的 ToAbsPath) + String[] pathResult = toAbsPath(filePath, absPath, vPath); + filePath = pathResult[0]; // 处理后的绝对路径(C# 方法返回值) + + // 2. 解析文件信息(对应 C# 的 FileInfo) + File file = new File(filePath); + String dir = file.getParent(); // 文件夹路径(对应 C# 的 info.DirectoryName) + String filename = file.getName(); // 文件名(对应 C# 的 info.Name) + String savePath = file.getAbsolutePath(); // 完整路径(对应 C# 的 info.FullName) + + // 3. 图片文件特殊处理:若为图片且文件夹不含 "_high",则拼接 "_high" 后缀(原始图文件夹逻辑) + if (ImageUtil.isImg(filename) && (dir == null || dir.indexOf("_high") < 0)) { + // 修剪尾部 "/" 并拼接 "_high/"(对齐 C# 的 TrimEnd('/') + "_high/") + dir = dir == null ? "_high/" : dir.replaceAll("/$", "") + "_high/"; + // 拼接新的保存路径(对应 C# 的 Path.Combine) + savePath = Paths.get(dir, filename).toString(); + } + + // 4. 文件权限校验(对应 C# 的 CheckFileAuthory,需自行实现权限逻辑) + if (!checkFileAuthory(savePath, absPath)) { + return response; // 权限不足,返回失败 + } + + // 5. 处理文件删除逻辑 + File saveFile = new File(savePath); + if (saveFile.exists()) { + // 5.1 图片文件:同时删除缩略图(_thum 文件夹下的 .png 文件) + if (ImageUtil.isImg(file.getAbsolutePath().substring(file.getAbsolutePath().lastIndexOf(".")))) { + // 构建缩略图路径:_high 替换为 _thum,扩展名改为 .png(对齐 C# 的 Replace) + String thumSavePath = savePath.replace("_high", "_thum") + .replace(getFileExtension(file), ".png"); + File thumFile = new File(thumSavePath); + if (thumFile.exists()) { + Files.delete(thumFile.toPath()); // 删除缩略图 + // 异步删除空的缩略图目录(对应 C# 的 DeleteEmptyDir 异步调用) + String thumDir = dir == null ? "" : dir.replace("_high", "_thum"); + ResourceExecutors.submitFileCleanup(() -> deleteEmptyDir(new File(thumDir).getName())); + } + } + // 5.2 非图片文件:删除关联的 HTML、CSS、图片目录或 PDF + else { + StringBuilder vPathHtml = new StringBuilder(); // 对应 C# 的 out vpath + // 获取 HTML 文件路径(对应 C# 的 WebConfigUtil.GetViewDocHtmlPath) + String htmlPath = WebConfigUtil.getViewDocHtmlPath(savePath, vPathHtml); + String newFileName = filename; // 原逻辑保留文件名(可根据需求调整过滤字符) + + // 替换 HTML 文件名(对齐 C# 的 Replace 最后一段路径为 {newFileName}.html) + if (htmlPath != null && !htmlPath.isEmpty()) { + String[] htmlPathParts = htmlPath.split("/"); + if (htmlPathParts.length > 0) { + htmlPathParts[htmlPathParts.length - 1] = newFileName + ".html"; + htmlPath = String.join("/", htmlPathParts); + } + } + + File htmlFile = new File(htmlPath); + if (htmlFile.exists()) { + Files.delete(htmlFile.toPath()); // 删除 HTML 文件 + + // 删除关联的 CSS 文件(.html 替换为 _styles.css) + String cssPath = htmlPath.replace(".html", "_styles.css"); + File cssFile = new File(cssPath); + if (cssFile.exists()) { + Files.delete(cssFile.toPath()); + } + + // 删除关联的图片目录(.html 替换为 _images) + String imgDirPath = htmlPath.replace(".html", "_images"); + File imgDir = new File(imgDirPath); + if (imgDir.exists() && imgDir.isDirectory()) { + deleteDirRecursively(imgDir); // 递归删除目录及内容 + } + } + // 若 HTML 不存在,检查并删除 PDF 文件(.html 替换为 .pdf) + else { + htmlPath = htmlPath.replace(".html", ".pdf"); + File pdfFile = new File(htmlPath); + if (pdfFile.exists()) { + Files.delete(pdfFile.toPath()); + } + } + } + + // 5.3 删除主文件 + Files.delete(saveFile.toPath()); + // 异步删除空的主目录(对应 C# 的 DeleteEmptyDir) + String finalDir = dir; + ResourceExecutors.submitFileCleanup(() -> deleteEmptyDir(new File(finalDir).getName())); + response.setOther(1); // 标记已删除 + } + // 6. 若主文件不存在,尝试删除无 "_high" 后缀的文件 + else { + savePath = savePath.replace("_high", ""); + File noHighFile = new File(savePath); + if (noHighFile.exists()) { + Files.delete(noHighFile.toPath()); + response.setOther(1); + } + // 7. 若仍不存在,尝试删除 WMV 转 MP4 的文件(小写匹配) + else { + savePath = savePath.toLowerCase().replace("_wmv", "mp4"); + File mp4File = new File(savePath); + if (mp4File.exists()) { + Files.delete(mp4File.toPath()); + response.setOther(1); + } + } + } + + // 8. 异步删除无 "_high" 后缀的空目录 + String noHighDir = dir == null ? "" : dir.replace("_high", ""); + ResourceExecutors.submitFileCleanup(() -> deleteEmptyDir(new File(noHighDir).getName())); + + // 9. 操作成功 + response.setSuccess(true); + + } catch (Exception e) { + // 异常时保持 success=false(不抛出,避免崩溃,对齐 C# 静默处理) + log.error("Exception caught", e); // 建议生产环境替换为日志记录(如 log.error) + } + + return response; + } + + /** + * 获取文件扩展名(不含 ".",如 "png"、"txt") + * + * @param file 文件对象 + * @return 扩展名(空字符串表示无扩展名) + */ + private static String getFileExtension(File file) { + String fileName = file.getName(); + int dotIndex = fileName.lastIndexOf("."); + return dotIndex > 0 ? fileName.substring(dotIndex) : ""; + } + + // 辅助方法:获取文件扩展名 + public static String getFileExtension(String fileName) { + // 1. null 保护 + 修剪首尾空格(对齐 C# 的 Trim()) + if (fileName == null) { + return ""; + } + String trimmedFileName = fileName.trim(); + + // 2. 无点则返回空 + if (!trimmedFileName.contains(".")) { + return ""; + } + + // 3. 取最后一个点后的字符(纯扩展名) + return trimmedFileName.substring(trimmedFileName.lastIndexOf(".") + 1); + } + + /** + * 递归删除目录及所有子内容(用于删除非空目录,如 _images) + * + * @param dir 待删除的目录 + * @throws IOException 目录删除异常 + */ + private static void deleteDirRecursively(File dir) throws IOException { + if (!dir.exists()) { + return; + } + // 递归删除子文件和子目录 + File[] files = dir.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + deleteDirRecursively(file); + } else { + Files.delete(file.toPath()); + } + } + } + // 删除空目录 + Files.delete(dir.toPath()); + } + + // ------------------------------ + // 资源释放:关闭线程池(建议在应用关闭时调用) + // ------------------------------ + public static void shutdownExecutor() { + ResourceExecutors.shutdownFileCleanup(); + } + + /** + * 对齐 C# 的 DeleteEmptyDir(string dir):异步删除空目录 + * + * @param dir 目录路径字符串 + */ + public static void deleteEmptyDir(String dir) { + if (dir == null || dir.isEmpty()) { + return; + } + File dirFile = new File(dir); + // 异步执行(对应 C# 的 Task.Start()) + ResourceExecutors.submitFileCleanup(() -> deleteEmptyDir(dirFile, true)); + } + + + /** + * 对齐 C# 的 DeleteEmptyDir(DirectoryInfo dirInfo, bool delParent):递归删除空目录 + * + * @param dirFile 目录 File 对象(对应 C# 的 DirectoryInfo) + * @param delParent 是否删除父目录(递归向上清理) + */ + public static void deleteEmptyDir(File dirFile, boolean delParent) { + try { + // 目录不存在,直接返回 + if (!dirFile.exists() || !dirFile.isDirectory()) { + return; + } + + // 1. 递归清理子目录(先处理所有子目录) + File[] childDirs = dirFile.listFiles(File::isDirectory); + if (childDirs != null && childDirs.length > 0) { + for (File childDir : childDirs) { + deleteEmptyDir(childDir, false); // 子目录不递归删除父目录 + } + } + + // 2. 检查当前目录是否为空(无文件 + 无子目录) + File[] files = dirFile.listFiles(); + if (files == null || files.length == 0) { + // 3. 删除当前空目录 + Files.delete(dirFile.toPath()); + // 4. 若允许删除父目录,递归清理父目录 + if (delParent) { + File parentDir = dirFile.getParentFile(); + if (parentDir != null) { + deleteEmptyDir(parentDir, true); + } + } + } + + } catch (Exception e) { + // 对齐 C# 的 catch 静默处理(建议生产环境加日志) + log.error("Exception caught", e); + } + } + + /** + * 生成缩略图 + */ + public static String toThumImg(String filePath, boolean toThum, boolean comp) { + if (!ImageUtil.isImg(filePath)) { + return filePath; + } + + try { + File file = new File(filePath); + String fileDir = file.getParent(); + + // 压缩图片 + if (comp) { + long fileSizeKb = file.length() / 1024; + int maxSize = Math.max(100, Integer.parseInt(WebConfigUtil_web.get("ComprAttImgMaxKB", "100"))); + if (fileSizeKb > maxSize) { + int width = Math.max(300, Integer.parseInt(WebConfigUtil_web.get("ComprAttImgWidth", "300"))); + ImageUtil.format(filePath, filePath, "png", ImageUtil.ImgDeep.D8, width, width); + } + } + + // 生成缩略图 + if (toThum) { + String thumDir = fileDir.replace("_high", "") + "_thum"; + File thumDirFile = new File(thumDir); + if (!thumDirFile.exists()) { + thumDirFile.mkdirs(); + } + + String thumPath = Paths.get(thumDir, + file.getName().replace(getFileExtension(file.getName()), ".png")).toString(); + ImageUtil.format(filePath, thumPath, "png", ImageUtil.ImgDeep.D8, 300, 300); + } + + } catch (Exception e) { + // 记录日志 + } + + return filePath; + } + + /** + * 根据匹配规则删除目录 + * + * @param dir 父目录路径 + * @param match 目录匹配规则(返回true则删除该目录) + */ + public static void deleteFloder(String dir, Predicate match) { + try { + // 获取目录下的所有子目录 + File[] subDirs = new File(dir).listFiles(File::isDirectory); + if (subDirs == null) { + return; // 目录不存在或不是目录 + } + + for (File subDir : subDirs) { + try { + // 匹配规则为空或满足匹配条件时删除目录 + if (match == null || match.test(subDir)) { // 现在可以正常解析test方法 + deleteDir(subDir.getAbsolutePath()); + } + } catch (Exception e) { + // 忽略单个目录删除失败的异常 + } + } + } catch (Exception e) { + // 忽略整体遍历异常 + } + } + + /** + * 递归删除目录及其包含的所有文件和子目录 + * + * @param srcPath 要删除的目录路径 + */ + public static void deleteDir(String srcPath) { + File dir = new File(srcPath); + if (!dir.exists()) { + return; + } + + try { + // 递归删除所有子文件和子目录 + deleteDirectoryContents(dir); + // 删除空目录 + if (!dir.delete()) { + throw new IOException("无法删除目录: " + srcPath); + } + } catch (Exception e) { + throw new RuntimeException("删除目录失败: " + srcPath, e); + } + } + + /** + * 递归删除目录下的所有内容(文件和子目录) + * + * @param dir 要清理的目录 + */ + private static void deleteDirectoryContents(File dir) throws IOException { + File[] files = dir.listFiles(); + if (files == null) { + throw new IOException("无法列出目录内容: " + dir.getAbsolutePath()); + } + + for (File file : files) { + if (file.isDirectory()) { + // 递归删除子目录 + deleteDirectoryContents(file); + // 删除空的子目录 + if (!file.delete()) { + throw new IOException("无法删除子目录: " + file.getAbsolutePath()); + } + } else { + // 删除文件 + if (!file.delete()) { + throw new IOException("无法删除文件: " + file.getAbsolutePath()); + } + } + } + } + + /** + * 转换视频格式 + */ + public static String convertVideo(String sourceFile, String fileType, StringBuilder fileName) { + if ("wmv".equalsIgnoreCase(fileType)) { + String mp4Path = sourceFile.replace(fileType, "mp4"); + FormatFactoryUtil.convertVideo(sourceFile, mp4Path, false, true); + fileName = new StringBuilder(fileName.toString().replace(fileType, "mp4")); + return mp4Path; + } + return sourceFile; + } + + + /** + * 获取文件编码格式 + * + * @param filePath 文件路径 + * @return 检测到的编码格式,默认返回系统默认编码 + */ + public static Charset getFileEncoding(String filePath) { + // 默认使用系统默认编码 + Charset encoding = Charset.defaultCharset(); + File file = new File(filePath); + + try (FileInputStream fis = new FileInputStream(file)) { + // 创建编码检测器 + UniversalDetector detector = new UniversalDetector(null); + byte[] buffer = new byte[4096]; + int bytesRead; + + // 读取文件内容进行检测 + while ((bytesRead = fis.read(buffer)) > 0 && !detector.isDone()) { + detector.handleData(buffer, 0, bytesRead); + } + + // 完成检测 + detector.dataEnd(); + + // 获取检测到的编码名称 + String charsetName = detector.getDetectedCharset(); + if (charsetName != null) { + encoding = Charset.forName(charsetName); + } + + // 重置检测器状态(可选,用于重复使用) + detector.reset(); + + } catch (IOException e) { + // 处理文件读取异常,这里简单打印异常信息,实际应用中可根据需要处理 + log.error("Exception caught", e); + } + + return encoding; + } + + /** + * 对齐 C# 的 GetTextFileEncodingType 方法:检测文本文件的编码格式 + * + * @param filePath 文件路径(绝对路径) + * @return 检测到的编码(Charset 对象,对应 C# 的 Encoding) + * @throws IOException 文件读取异常(如文件不存在、无读取权限) + * @throws RuntimeException 非预期字节格式异常(对应 C# 的 Exception) + */ + public static Charset getTextFileEncodingType(String filePath) throws IOException { + // 1. 默认编码:对应 C# 的 Encoding.Default(Java 用系统默认编码) + Charset encoding = Charset.defaultCharset(); + + // 2. 读取文件字节流(对应 C# 的 FileStream + BinaryReader) + // 注:Java 无 BinaryReader,用 FileInputStream 结合 BufferedInputStream 实现字节读取 + try (InputStream fs = new FileInputStream(filePath); + BufferedInputStream bufferedIn = new BufferedInputStream(fs)) { + + // 读取文件所有字节(对应 C# 的 binaryReader.ReadBytes((int)fs.Length)) + byte[] buffer = readAllBytes(bufferedIn); + + // 3. BOM 检测:按 C# 逻辑顺序判断(UTF-8 BOM → UTF-16 BE → UTF-16 LE) + // 3.1 检测 UTF-8 BOM(头部字节:0xEF 0xBB 0xBF) + if (buffer.length >= 3 && buffer[0] == (byte) 0xEF && buffer[1] == (byte) 0xBB && buffer[2] == (byte) 0xBF) { + encoding = StandardCharsets.UTF_8; + } + // 3.2 检测 BigEndianUnicode(UTF-16 BE,头部字节:0xFE 0xFF 0x00,对应 C# 的 Encoding.BigEndianUnicode) + else if (buffer.length >= 3 && buffer[0] == (byte) 0xFE && buffer[1] == (byte) 0xFF && buffer[2] == (byte) 0x00) { + encoding = StandardCharsets.UTF_16BE; + } + // 3.3 检测 Unicode(UTF-16 LE,头部字节:0xFF 0xFE 0x41(即字符 'A'),对应 C# 的 Encoding.Unicode) + else if (buffer.length >= 3 && buffer[0] == (byte) 0xFF && buffer[1] == (byte) 0xFE && buffer[2] == (byte) 0x41) { + encoding = StandardCharsets.UTF_16LE; + } + // 3.4 无 BOM 时,检测是否为 UTF-8(对应 C# 的 IsUTF8Bytes) + else if (isUTF8Bytes(buffer)) { + encoding = StandardCharsets.UTF_8; + } + + return encoding; + } + } + + /** + * 对齐 C# 的 IsUTF8Bytes 方法:判断字节数组是否为无 BOM 的 UTF-8 格式 + * 核心逻辑:遵循 UTF-8 多字节编码规则(RFC 3629) + * + * @param data 待检测的字节数组 + * @return true:是无 BOM UTF-8,false:不是 + * @throws RuntimeException 非预期的字节格式(对应 C# 的 throw new Exception) + */ + private static boolean isUTF8Bytes(byte[] data) { + int charByteCounter = 1; // 记录当前字符还需读取的后续字节数(UTF-8 多字节字符的后续字节数) + byte curByte; // 当前分析的字节 + + for (int i = 0; i < data.length; i++) { + curByte = data[i]; + + // 情况1:当前是多字节字符的「起始字节」(需判断后续应跟随的字节数) + if (charByteCounter == 1) { + // UTF-8 单字节字符:0xxxxxxx(首位为 0),无需处理;多字节字符:1xxxxxxx(首位为 1) + if (curByte >= 0x80) { + // 计算后续应跟随的字节数:通过「起始字节的连续 1 的个数」判断(如 110xxxxx → 后续1字节,1110xxxx → 后续2字节) + while (((curByte <<= 1) & 0x80) != 0) { + charByteCounter++; + } + + // UTF-8 规则:起始字节的连续 1 个数需在 2~6 之间(对应 2~6 字节字符,实际常用 2~3 字节) + if (charByteCounter == 1 || charByteCounter > 6) { + return false; + } + } + } + // 情况2:当前是多字节字符的「后续字节」(必须满足 10xxxxxx 格式) + else { + // 后续字节规则:首位必须是 10(即 (curByte & 0xC0) == 0x80,0xC0 是 11000000,0x80 是 10000000) + if ((curByte & 0xC0) != 0x80) { + return false; + } + charByteCounter--; // 后续字节数减 1,直到回到 1(表示当前字符处理完成) + } + } + + // 遍历结束后,若仍有未处理完的后续字节(charByteCounter > 1),说明字节格式不完整 + if (charByteCounter > 1) { + throw new RuntimeException("非预期的byte格式"); + } + + return true; + } + + // ------------------------------ + // 辅助方法:读取 InputStream 所有字节(对应 C# 的 BinaryReader.ReadBytes((int)fs.Length)) + // ------------------------------ + private static byte[] readAllBytes(InputStream inputStream) throws IOException { + // 用 ByteArrayOutputStream 缓存所有字节 + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + byte[] tempBuffer = new byte[1024]; // 1KB 缓冲区(平衡性能与内存) + int bytesRead; + // 循环读取直到流结束 + while ((bytesRead = inputStream.read(tempBuffer)) != -1) { + outputStream.write(tempBuffer, 0, bytesRead); + } + outputStream.flush(); + return outputStream.toByteArray(); + } + } + + /** + * 下载远程文件到本地 + * + * @param downUrl 远程文件URL + * @param savePath 本地保存路径 + * @return 下载成功返回true,失败返回false + */ + public static boolean downloadFile(String downUrl, String savePath) { + return DownLoadFile(downUrl, savePath); + } + + + /** + * 异步下载文件 + * + * @param url 下载地址 + * @param savePath 保存路径 + * @param onProgress 进度回调(0-100) + * @param onFinish 完成回调 + * @param onFail 失败回调 + */ + public static void asyncDownload(String url, String savePath, + Consumer onProgress, + Consumer onFinish, + Consumer onFail) { + // 创建RestTemplate并设置超时 + if (startGuardedAsyncDownload(url, savePath, onProgress, onFinish, onFail)) { + return; + } + // 异步执行下载 + /* + RestTemplate restTemplate = new RestTemplate(); + new Thread(() -> { + try { + URI uri = new URI(url); + HttpHeaders headers = new HttpHeaders(); + headers.add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"); + + // 请求回调:设置请求头 + RequestCallback requestCallback = request -> { + request.getHeaders().putAll(headers); + }; + + // 响应提取器:处理文件流并计算进度 + ResponseExtractor responseExtractor = response -> { + try (InputStream in = response.getBody(); + OutputStream out = new FileOutputStream(savePath)) { + + long contentLength = response.getHeaders().getContentLength(); + byte[] buffer = new byte[4096]; + int bytesRead; + long totalRead = 0; + + while ((bytesRead = in.read(buffer)) != -1) { + out.write(buffer, 0, bytesRead); + totalRead += bytesRead; + + // 计算并回调进度 + if (contentLength > 0 && onProgress != null) { + int progress = (int) (totalRead * 100 / contentLength); + onProgress.accept(progress); + } + } + + // 确保最后进度为100% + if (contentLength > 0 && onProgress != null) { + onProgress.accept(100); + } + + return null; + } catch (IOException e) { + if (onFail != null) { + onFail.accept(e); + } + throw e; + } + }; + + // 执行下载 + restTemplate.execute(uri, HttpMethod.GET, requestCallback, responseExtractor); + + // 下载完成回调 + if (onFinish != null) { + onFinish.accept(savePath); + } + + } catch (Exception e) { + // 失败回调 + if (onFail != null) { + onFail.accept(e); + } + } + }).start(); + */ + } + + + // 辅助方法:构建Web路径 + private static boolean startGuardedAsyncDownload(String url, String savePath, + Consumer onProgress, + Consumer onFinish, + Consumer onFail) { + new Thread(() -> { + try { + if (onProgress != null) { + onProgress.accept(0); + } + if (!DownLoadFile(url, savePath)) { + throw new IOException("Remote download failed or was blocked"); + } + if (onProgress != null) { + onProgress.accept(100); + } + if (onFinish != null) { + onFinish.accept(savePath); + } + } catch (Exception e) { + if (onFail != null) { + onFail.accept(e); + } + } + }).start(); + return true; + } + + private static String buildWebPath(String relativePath, String appDomain) throws UnsupportedEncodingException { + // 1. 处理域名:确保结尾无 / + String domain = (appDomain != null && !appDomain.isEmpty()) + ? appDomain.trim().replaceAll("/$", "") // 移除结尾的 / + : WebConfigUtil_web.getFileDomain().trim().replaceAll("/$", ""); + + // 2. 处理相对路径编码(保持以 / 开头) + String encodedPath = (appDomain != null && !appDomain.isEmpty()) + ? urlEncode(relativePath, false).replace("//", "/") + : urlEncode(relativePath, false, true).replace("//", "/"); + + // 3. 直接拼接(domain 无 / 结尾 + encodedPath 以 / 开头 → 自动形成单 / 分隔) + return domain + encodedPath; + } + + // 辅助方法:URL编码 + public static String urlEncode(String content, boolean encodeChinese, boolean toPathStr) throws UnsupportedEncodingException { + String ret = content; + if (encodeChinese) { + ret = URLEncoder.encode(content, StandardCharsets.UTF_8); + + } else { + StringBuilder sb = new StringBuilder(); + for (char c : content.toCharArray()) { + if (c == ' ') { + sb.append("%20"); + } else if (!Pattern.matches("[\\u4e00-\\u9fa5]", String.valueOf(c))) { + sb.append(URLEncoder.encode(String.valueOf(c), StandardCharsets.UTF_8)); + } else { + sb.append(c); + } + } + ret = sb.toString(); + } + if (toPathStr) return ret.replace("%5C", "/").replace("%2F", "/").replace("//", "/"); + return ret; + } + + public static String urlEncode(String content, boolean encodeChinese) throws UnsupportedEncodingException { + return urlEncode(content, encodeChinese, true); + } + + // 辅助方法:获取当前请求 + private static HttpServletRequest getRequest() { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + return attributes != null ? attributes.getRequest() : null; + } + + private static Path getUtf8Path(String first, String... more) { + try { + // 强制将路径字符串按UTF-8编码为字节,再转回字符串(绕过系统locale) + String utf8First = new String(first.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); + String[] utf8More = new String[more.length]; + for (int i = 0; i < more.length; i++) { + utf8More[i] = new String(more[i].getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); + } + return Paths.get(utf8First, utf8More); + } catch (Exception e) { + return Paths.get(first, more); + } + } + + +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/FormParamUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/FormParamUtil.java new file mode 100644 index 0000000..5373fd1 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/FormParamUtil.java @@ -0,0 +1,140 @@ +package org.example.Utils; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +public class FormParamUtil { + + /** + * 临时禁用 ZipedPms 和 requestPms 字典(注释掉自定义参数缓存) + * 原因:避免干扰前端传递的原始参数 + */ + // private static final Map ZipedPms = new HashMap<>(); + // private static final Map requestPms = new HashMap<>(); + + /** + * 将表单参数转换为 MyBatis 可用的 Map + */ + public static Map convertToMyBatisParamMap(HttpServletRequest request) { + Map paramMap = new HashMap<>(); + Enumeration paramNames = request.getParameterNames(); + + while (paramNames.hasMoreElements()) { + String paramName = paramNames.nextElement(); + String[] values = request.getParameterValues(paramName); + + if (values != null && values.length > 0) { + paramMap.put(paramName, values.length == 1 ? values[0] : values); + } + } + + return paramMap; + } + + /** + * 从 HttpServletRequest 获取参数(保持原始大小写) + */ + @SuppressWarnings("unchecked") + public static T getParamValue(String key, T defaultValue, HttpServletRequest request) { + if (key == null || request == null) { + return defaultValue; + } + + // 直接使用原始参数名(不转小写) + String[] values = request.getParameterValues(key); + + // 2. 如果未找到且需要忽略大小写,再次尝试 + if ((values == null || values.length == 0) && !key.isEmpty()) { + Enumeration paramNames = request.getParameterNames(); + while (paramNames.hasMoreElements()) { + String paramName = paramNames.nextElement(); + if (paramName.equalsIgnoreCase(key)) { + values = request.getParameterValues(paramName); + break; + } + } + } + + if (values != null && values.length > 0) { + // 取第一个非空值 + for (String val : values) { + if (val != null && !val.trim().isEmpty()) { + return (T) convertValue(val, defaultValue); + } + } + // 所有值都为空时,取第一个值 + return (T) convertValue(values[0], defaultValue); + } + + return defaultValue; + } + + /** + * 安全获取可空布尔值参数(解决defaultValue为null时的类型转换问题) + */ + public static Boolean getNullableBoolean(String key, HttpServletRequest request) { + String value = getParamValue(key, (String) null, request); + if (value == null) { + return null; // 未传参数时返回null + } + + value = value.trim().toLowerCase(); + if ("true".equals(value) || "1".equals(value) || "on".equals(value)) { + return true; + } else if ("false".equals(value) || "0".equals(value) || "off".equals(value)) { + return false; + } + + return null; // 无效值时返回null(可根据需求改为抛出异常) + } + + /** + * 类型转换逻辑 + */ + private static Object convertValue(Object value, Object targetType) { + if (value == null) { + return null; + } + + String strValue = value.toString().trim(); + + try { + if (targetType instanceof Boolean) { + String lowerVal = strValue.toLowerCase(); + return "true".equals(lowerVal) || "on".equals(lowerVal) || "1".equals(lowerVal); + } else if (targetType instanceof Integer) { + return Integer.valueOf(strValue); + } else if (targetType instanceof String) { + return strValue; + } + } catch (Exception e) { + return null; + } + + return value; + } + + public static String GetParamString(String key, String defaultValue) { + HttpServletRequest request = RequestContextHolder.getRequestAttributes() != null + ? ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest() + : null; + return FormParamUtil.getParamValue(key, defaultValue, request); + } + + /** + * 临时禁用自定义参数设置方法(与字典一起注释) + */ + // public static void setZipedParam(String key, Object value) { + // ZipedPms.put(key.toLowerCase(), value); + // } + // + // public static void setRequestParam(String key, Object value) { + // requestPms.put(key.toLowerCase(), value); + // } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Utils/FormatFactoryUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/FormatFactoryUtil.java new file mode 100644 index 0000000..9132d16 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/FormatFactoryUtil.java @@ -0,0 +1,444 @@ +package org.example.Utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class FormatFactoryUtil { + private static final Logger log = LoggerFactory.getLogger(FormatFactoryUtil.class); + + // 对应 C# 的静态变量,使用 final 保证不可修改(若需动态修改可移除 final) + public static final String ffmpegPath; + public static final String ffprobePath; + + // 静态代码块初始化路径(对应 C# 静态变量的初始化逻辑) + static { + // 1. 获取服务器根路径(对应 C# WebConfigUtil.ServerPath) + String serverPath = WebConfigUtil.getServerPath(); // 假设 WebConfigUtil 有 getServerPath() 方法 + + // 2. 路径拼接:还原 Path.Combine(ServerPath, "bin", "ffmpeg\\ffmpeg.exe") + // Java 用 File.separator 替代硬编码 "\",保证 Windows 兼容性(File.separator 在 Windows 是 "\",Linux 是 "/") + ffmpegPath = new File(new File(new File(serverPath, "bin"), "ffmpeg"), "ffmpeg.exe").getAbsolutePath(); + + // 3. 拼接 ffprobe 路径:还原 Path.Combine(ServerPath, "bin", "ffmpeg\\ffprobe.exe") + ffprobePath = new File(new File(new File(serverPath, "bin"), "ffmpeg"), "ffprobe.exe").getAbsolutePath(); + } + + // 若需要兼容跨平台(可选):自动根据系统调整可执行文件后缀(Windows 是 .exe,Linux/Mac 无后缀) + public static final String ffmpegCrossPlatformPath; + public static final String ffprobeCrossPlatformPath; + + static { + String serverPath = WebConfigUtil.getServerPath(); + // 判断操作系统类型 + String os = System.getProperty("os.name").toLowerCase(); + boolean isWindows = os.contains("win"); + String ffmpegExe = isWindows ? "ffmpeg.exe" : "ffmpeg"; + String ffprobeExe = isWindows ? "ffprobe.exe" : "ffprobe"; + + // 跨平台路径拼接 + ffmpegCrossPlatformPath = new File(new File(new File(serverPath, "bin"), "ffmpeg"), ffmpegExe).getAbsolutePath(); + ffprobeCrossPlatformPath = new File(new File(new File(serverPath, "bin"), "ffmpeg"), ffprobeExe).getAbsolutePath(); + } + + // 获取ffmpeg路径(从配置工具类获取,符合项目中WebConfigUtil的使用风格) + private static String getFfmpegToolPath() { + // 假设ffmpeg路径配置在WebConfig中,实际路径根据项目调整 + return WebConfigUtil_web.getServerPath() + "tools/FormatFactory/ffmpeg.exe"; + } + + /** + * 执行外部进程 + * + * @param exePath 外部程序绝对路径(如 ffmpeg.exe) + * @param args 进程参数(命令行参数拼接字符串) + * @param wait 是否阻塞等待进程结束(对应 C# wait 参数) + * @param onExited 进程退出回调(对应 C# onExited,进程结束后执行) + * @param onRunning 进程运行中回调(仅同步等待时执行,对应 C# onRunning) + * @param success 进程成功判定函数(对应 C# success,返回 true 表示进程执行成功) + * @return 执行结果:程序不存在返回 false;异常返回 false;成功返回 true + */ + public static boolean runProcess( + String exePath, + String args, + boolean wait, + Runnable onExited, + java.util.function.Consumer onRunning, + java.util.function.Function success + ) { + // 1. 验证程序是否存在(对应 C# File.Exists(exePath)) + java.io.File exeFile = new java.io.File(exePath); + if (!exeFile.exists()) { + LoggerHandler.debug(new Object(), String.format("执行外部程序失败,未找到转换程序:%s", exePath)); + return false; + } + + Process process = null; + // 2. 构建进程启动器(对应 C# ProcessStartInfo) + java.lang.ProcessBuilder processBuilder = new java.lang.ProcessBuilder(); + processBuilder.command(exePath, args); // 命令 + 参数(args 整体作为一个参数,若需拆分需手动处理) + + // 配置进程启动参数(还原 C# StartInfo 配置) + processBuilder.redirectErrorStream(false); // 不合并错误流(C# RedirectStandardError = true 对应此配置) + processBuilder.inheritIO(); // 不继承父进程 IO(对应 UseShellExecute = false) + processBuilder.directory(exeFile.getParentFile()); // 进程工作目录设为程序所在目录 + + try { + // 3. 启动进程(对应 C# p.Start()) + process = processBuilder.start(); + + // 4. 异步捕获错误流(FFMPEG 输出通过错误流返回,还原 C# ErrorDataReceived 逻辑) + captureErrorStream(process.getErrorStream()); + + // 5. 异步捕获标准输出流(还原 C# RedirectStandardOutput = true) + captureStandardOutput(process.getInputStream()); + + // 6. 进程退出回调(对应 C# p.Exited += onExited) + if (onExited != null) { + Process finalProcess = process; + new Thread(() -> { + try { + finalProcess.waitFor(); // 等待进程结束后执行回调 + onExited.run(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + LoggerHandler.error(new Object(), "进程退出回调执行失败", e); + } + }).start(); + } + + // 7. 同步等待进程结束(对应 C# wait = true 逻辑) + if (wait) { + // 执行运行中回调(对应 C# onRunning(p)) + if (onRunning != null) { + onRunning.accept(process); + } + // 阻塞等待进程结束(对应 C# p.WaitForExit()) + process.waitFor(); + + // 判定执行结果(对应 C# success != null ? success(p) : p.ExitCode == 0) + if (success != null) { + return success.apply(process); + } else { + return process.exitValue() == 0; // 退出码 0 表示成功 + } + } + + // 异步执行时直接返回 true(对应 C# wait = false 逻辑) + return true; + + } catch (IOException e) { + LoggerHandler.error(new Object(), String.format("执行外部程序%s失败,参数:%s", exePath, args), e); + return false; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + LoggerHandler.error(new Object(), String.format("进程执行被中断,程序:%s,参数:%s", exePath, args), e); + return false; + } finally { + // 8. 释放进程资源(对应 C# p.Close() + p.Dispose()) + if (process != null) { + process.destroy(); // 销毁进程 + } + } + } + + /** + * 捕获进程错误流(FFMPEG 核心输出流,还原 C# ErrorDataReceived 事件) + * + * @param errorStream 进程错误流 + */ + private static void captureErrorStream(InputStream errorStream) { + new Thread(() -> { + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(errorStream, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + // 此处可添加错误流日志打印(对应 C# Output 方法逻辑) + // LoggerHandler.debug(new Object(), "FFMPEG 错误流输出:" + line); + } + } catch (IOException e) { + LoggerHandler.error(new Object(), "捕获进程错误流失败", e); + } + }).start(); + } + + /** + * 捕获进程标准输出流(还原 C# RedirectStandardOutput = true) + * + * @param outputStream 进程标准输出流 + */ + private static void captureStandardOutput(InputStream outputStream) { + new Thread(() -> { + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(outputStream, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + // 此处可添加标准输出流日志打印 + // LoggerHandler.debug(new Object(), "进程标准输出:" + line); + } + } catch (IOException e) { + LoggerHandler.error(new Object(), "捕获进程标准输出流失败", e); + } + }).start(); + } + + /** + * 日志工具类适配(还原 C# LoggerHandler 逻辑,需确保项目中存在该类) + */ + public static class LoggerHandler { + public static void debug(Object sender, String message) { + // 适配项目实际日志框架(如 Log4j、SLF4J) + log.debug(String.valueOf("[DEBUG] " + message)); + } + + public static void error(Object sender, String message, Throwable throwable) { + // 适配项目实际日志框架 + if (throwable == null) { + log.error(String.valueOf(message)); + return; + } + log.error(String.valueOf(message), throwable); + } + } + + /** + * 视频格式转换 + * + * @param sourceFilePath 源视频文件路径 + * @param outPutFilePath 输出视频文件路径 + * @param wait 是否阻塞等待转换完成(默认 true) + * @param delOldFile 转换完成后是否删除原文件(默认 true) + * @return 转换成功返回 true;文件不存在/转换失败返回 false + */ + public static boolean convertVideo(String sourceFilePath, String outPutFilePath, boolean wait, boolean delOldFile) { + // 1. 验证源文件是否存在(对应 C# File.Exists(sourceFilePath)) + File sourceFile = new File(sourceFilePath); + if (!sourceFile.exists()) { + return false; + } + + // 2. 构建 ffmpeg 命令参数(严格还原 C# 参数格式) + // 参数说明:-i 源文件 -y 覆盖输出文件 -b 1024k 视频比特率 -acodec copy 复制音频编码 + String args = String.format( + "-i \"%s\" -y \"%s\" -b 1024k -acodec copy", + sourceFilePath, + outPutFilePath + ); + + // 3. 调用外部 ffmpeg 进程(还原 C# RunProcess 逻辑及回调) + return runProcess( + ffmpegPath, + args, + wait, + // 进程退出回调:转换完成后删除原文件(对应 C# onExited 委托) + () -> { + if (delOldFile) { + File oldFile = new File(sourceFilePath); + if (oldFile.exists()) { + oldFile.delete(); // 对应 C# File.Delete(sourceFilePath) + } + } + }, + null, // onRunning 回调为 null(与 C# 一致) + null // success 判定为 null(使用默认退出码判定:0 为成功) + ); + } + + // 重载方法:默认 wait=true、delOldFile=true(还原 C# 可选参数特性) + public static boolean convertVideo(String sourceFilePath, String outPutFilePath) { + return convertVideo(sourceFilePath, outPutFilePath, true, true); + } + + /** + * 处理FFmpeg输出信息(对应C#的Output方法) + */ + private static void Output(String outputLine) { + if (outputLine == null || outputLine.isEmpty()) { + return; + } + // 可根据需要添加输出解析逻辑(如进度、时长等) + // 参考C#注释中的正则解析逻辑,可结合项目中的NativeExtensionUtils工具类处理 + } + + /** + * MP4 编码转换(兼容浏览器播放) + * + * @param inputPath 输入视频路径 + * @param outputPath 输出视频路径 + * @param wait 是否阻塞等待转换完成(默认 true) + * @param delOldFile 转换完成后是否删除原文件(默认 true) + * @return 转换成功返回 true;文件不存在/无需转换/失败返回 false + */ + public static boolean convertMp4Code(String inputPath, String outputPath, boolean wait, boolean delOldFile) { + // 1. 验证输入文件是否存在(对应 C# File.Exists(inputPath)) + File inputFile = new File(inputPath); + if (!inputFile.exists()) { + return false; + } + + // 2. 检查是否包含专利编码(无需转换则直接返回 true) + if (!mp4HasPatentCodec(inputPath)) { + return true; + } + + // 3. 构建 ffmpeg 命令参数(严格还原 C# 参数格式) + // 注意:Java 字符串转义需用双引号,路径包含空格时自动适配 + String args = String.format( + "-i \"%s\" -c:v libx264 -profile:v high -preset slow -crf 23 -c:a aac -b:a 128k \"%s\"", + inputPath, + outputPath + ); + + // 4. 调用外部进程(对应 C# RunProcess,还原回调逻辑) + return runProcess( + ffmpegPath, + args, + wait, + // 进程退出回调:删除原文件(对应 C# onExited 委托) + () -> { + if (delOldFile) { + File oldFile = new File(inputPath); + if (oldFile.exists()) { + oldFile.delete(); // 对应 C# File.Delete(inputPath) + } + } + }, + null, // onRunning 回调为 null(与 C# 一致) + null // success 判定为 null(使用默认退出码判定) + ); + } + + /** + * 检测视频是否包含专利编码 + * + * @param videoPath 视频文件路径 + * @return 包含专利编码返回 true;否则返回 false + */ + public static boolean mp4HasPatentCodec(String videoPath) { + // 1. 构建 ffprobe 命令参数(严格还原 C# 参数) + String args = String.format( + "-v error -select_streams v:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1 \"%s\"", + videoPath + ); + + // 2. 原子引用存储输出结果(Java 多线程安全,对应 C# 闭包变量 output) + AtomicReference outputRef = new AtomicReference<>(""); + + // 3. 调用外部进程(还原 C# RunProcess 回调逻辑) + boolean processSuccess = runProcess( + ffprobePath, + args, + true, // 必须同步等待(wait=true) + null, // onExited 为 null + // onRunning 回调:读取标准输出流(对应 C# p.StandardOutput.ReadToEnd()) + process -> { + try (java.io.BufferedReader reader = new java.io.BufferedReader( + new java.io.InputStreamReader(process.getInputStream(), java.nio.charset.StandardCharsets.UTF_8))) { + // 读取所有输出(对应 C# ReadToEnd()) + StringBuilder outputSb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + outputSb.append(line); + } + outputRef.set(outputSb.toString().trim()); + } catch (java.io.IOException e) { + LoggerHandler.error(new Object(), "读取 ffprobe 输出失败", e); + outputRef.set(""); + } + }, + // success 判定:正则匹配专利编码(还原 C# Regex.IsMatch) + process -> { + String output = outputRef.get(); + // 正则表达式:忽略大小写,匹配 h264/mpeg4/mpeg2/vc1/hevc(与 C# 一致) + Pattern pattern = Pattern.compile("(h264|mpeg4|mpeg2|vc1|hevc)", Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(output); + return matcher.find(); + } + ); + + // 4. 进程执行成功且正则匹配成功,返回 true(与 C# 逻辑一致) + return processSuccess; + } + + /** + * 获取视频时长(单位:秒) + * + * @param videoPath 视频文件路径 + * @return 成功返回时长(秒);失败返回 0 + */ + public static double getVideoTime(String videoPath) { + // 1. 构建 ffprobe 命令参数(JSON 格式输出视频信息,与 C# 一致) + String args = String.format( + "-v quiet -print_format json -show_format \"%s\"", + videoPath + ); + + // 2. 原子引用存储 JSON 输出(多线程安全) + AtomicReference outputRef = new AtomicReference<>(""); + + // 3. 调用外部进程(同步等待,读取输出) + boolean processSuccess = runProcess( + ffprobePath, + args, + true, // wait=true 同步等待 + null, // onExited 为 null + // onRunning 回调:读取标准输出流(对应 C# p.StandardOutput.ReadToEnd()) + process -> { + try (java.io.BufferedReader reader = new java.io.BufferedReader( + new java.io.InputStreamReader(process.getInputStream(), java.nio.charset.StandardCharsets.UTF_8))) { + StringBuilder outputSb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + outputSb.append(line); + } + outputRef.set(outputSb.toString().trim()); + } catch (java.io.IOException e) { + LoggerHandler.error(new Object(), "读取 ffprobe 视频信息失败", e); + outputRef.set(""); + } + }, + null // success 为 null(使用默认退出码判定) + ); + + // 4. 解析 JSON 数据(还原 C# JSON.Decode + Hashtable 逻辑) + if (processSuccess) { + String jsonOutput = outputRef.get(); + if (!jsonOutput.isEmpty()) { + try { + // 解析 JSON 为 Map(对应 C# Hashtable) + Map infoMap = (Map) JSON.Decode(jsonOutput); + // 获取 format -> duration(对应 C# (info["format"] as Hashtable)["duration"]) + Map formatMap = (Map) infoMap.get("format"); + if (formatMap != null && formatMap.containsKey("duration")) { + String durationStr = formatMap.get("duration").toString(); + // 转换为 double(对应 C# ToDouble()) + return Double.parseDouble(durationStr); + } + } catch (Exception e) { + LoggerHandler.error(new Object(), "解析视频时长 JSON 失败", e); + } + } + } + + // 5. 失败返回 0(与 C# 一致) + return 0.0; + } + + // 重载方法:默认 wait=true、delOldFile=true(还原 C# 可选参数) + public static boolean convertMp4Code(String inputPath, String outputPath) { + return convertMp4Code(inputPath, outputPath, true, true); + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/GZipUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/GZipUtil.java new file mode 100644 index 0000000..2247693 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/GZipUtil.java @@ -0,0 +1,98 @@ +package org.example.Utils; + + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +/** + * ============================================================================== + * 功能描述:GZipUtil + * 备注:gzip压缩相关 + * ============================================================================== + */ +public class GZipUtil { + + /** + * GZIP解压 + * + * @param zippedData 压缩后的字节数组 + * @return 解压后的字节数组 + */ + public static byte[] decompress(byte[] zippedData) { + if (zippedData == null || zippedData.length == 0) { + return new byte[0]; + } + + try (ByteArrayInputStream bais = new ByteArrayInputStream(zippedData); + GZIPInputStream gzipIn = new GZIPInputStream(bais); + ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + + byte[] block = new byte[1024]; + int bytesRead; + while ((bytesRead = gzipIn.read(block)) != -1) { + baos.write(block, 0, bytesRead); + } + return baos.toByteArray(); + + } catch (IOException e) { + // 捕获异常并返回空数组(与原C#逻辑一致) + return new byte[0]; + } + } + + /** + * 解压并转换为字符串 + * + * @param zippedData 压缩后的字节数组 + * @return 解压后的字符串 + */ + public static String unZip(byte[] zippedData) { + byte[] decompressedData = decompress(zippedData); + return new String(decompressedData, StandardCharsets.UTF_8); + } + + /** + * 将传入字符串以GZip算法压缩后,返回Base64编码字符 + * + * @param rawString 需要压缩的字符串 + * @return 压缩后的Base64编码的字符串 + */ + public static String gZip(String rawString) { + if (rawString == null || rawString.isEmpty()) { + return ""; + } + + byte[] rawData = rawString.getBytes(StandardCharsets.UTF_8); + byte[] zippedData = compress(rawData); + return Base64.getEncoder().encodeToString(zippedData); + } + + /** + * GZip压缩 + * + * @param rawData 原始字节数组 + * @return 压缩后的字节数组 + */ + public static byte[] compress(byte[] rawData) { + if (rawData == null || rawData.length == 0) { + return new byte[0]; + } + + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + GZIPOutputStream gzipOut = new GZIPOutputStream(baos)) { + + gzipOut.write(rawData); + gzipOut.finish(); // 确保所有数据都被写入 + return baos.toByteArray(); + + } catch (IOException e) { + // 压缩失败时返回空数组 + return new byte[0]; + } + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/IPublicUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/IPublicUtil.java new file mode 100644 index 0000000..23cba50 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/IPublicUtil.java @@ -0,0 +1,1593 @@ +package org.example.Utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.bytedeco.flycapture.FlyCapture2.TimeStamp; +import org.example.Entity.Control.Container.FieldSet; +import org.example.Entity.Control.Container.RowColumn; +import org.example.Entity.Control.Data.DataStore; +import org.example.Entity.Control.Fields.*; +import org.example.Entity.System.BaseModule; +import org.example.Entity.System.LoginUserInfo; +import org.example.Entity.System.ModuleEntity; +import org.example.Enums.SystemEnums; +import org.example.Enums.SystemTypeEnums; +import org.example.Impl.BaseImpl; +import org.example.Impl.DataImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Type; +import java.math.BigInteger; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static org.example.Utils.NativeExtensionUtils.*; + +/* ============================================================================== + * 功能描述:公共逻辑业务 + * 修改人: + * 修改日期: + * 修改备注:无 + * 版本:1.0.0.0 + * ==============================================================================*/ +@Component +public class IPublicUtil extends BaseImpl { + private static final Logger log = LoggerFactory.getLogger(IPublicUtil.class); + + @Autowired + private JdbcTemplate jdbcTemplate; + + public IPublicUtil() { + } + + @Autowired + public IPublicUtil(JdbcTemplate jdbcTemplate) { + setJdbcTemplate(jdbcTemplate); + } + + public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + super.jdbcTemplate = jdbcTemplate; + } + + @Override + public void setDbOperator(DbOperator dbOperator) { + super.setDbOperator(dbOperator); + if (dbOperator != null) { + setJdbcTemplate(dbOperator.getJdbcTemplate()); + } + } + + /** + * /// + * /// 创建具体控件 + * /// + * /// 控件行数据 + * /// The dlltab + * /// Component + * /// + * /// + **/ + public Field createControl(Map row, ModuleEntity module, Boolean createEditor) { + Field myControl = new Field(row); + Boolean disabled = NativeExtensionUtils.toBoolean(DataTableUtil.getRowVal(row, "Disabled2", false)); + Map leftRecord = module != null ? module.getLeftRecord() : null; + ModuleEntity parentModuel = module != null ? module.ParentModule : null; + if (module != null && module.PopPms != null) { + if (leftRecord == null) { + leftRecord = module.PopPms; + } else if (!leftRecord.equals(module.PopPms)) { + // 合并 PopPms 到 leftRecord + Map pms = module.PopPms; + Map leftRecordMap = leftRecord; + for (Map.Entry entry : pms.entrySet()) { + leftRecordMap.put(entry.getKey().toLowerCase(), entry.getValue()); + } + leftRecord = leftRecordMap; + } + } + + String fieldTypeId = DataTableUtil.getRowVal(row, "FieldType", "0") + ""; + SystemEnums.ControlType fieldType = null; + try { + int typeValue = Integer.parseInt(fieldTypeId); + fieldType = SystemEnums.ControlType.fromValue(typeValue); + } catch (IllegalArgumentException e) { + // 处理无法解析的枚举值 + log.warn(String.valueOf("无法解析的 ControlType: " + fieldTypeId)); + } + + if (fieldType == null) { + // 若无法解析枚举值,可返回空 Map 或抛出异常,这里返回空 Map + return null; + } + + String format = NativeExtensionUtils.getStringValue(row, "dataformat", "0"); + if (format.isEmpty()) { + format = PublicUtil.GetFormatByCType(fieldType); + } +// out.println(fieldType + " fieldTypeId= " + fieldTypeId); + ComboBox _myControl = null; + switch (fieldType) { + case LabComboxValue: // 下拉框控件 获取Value值 + case LabComboxText: // 下拉框控件 获取Text值 + case LabAutoCompleteValue: //自动筛选框 获取Value值 + case LabAutoCompleteText: // 自动筛选框 获取Text值 + case LabComboxValueParam://搜索框返回ID-带参数 + case LabComboxTextParam://搜索框返回文本-带参数 + case LabMultiSelectValue://多选,获取Value + case LabMultiSelectText://多选,获取Text + case LabCheckAutoSeacherValue://搜索框返回文本-带复选框参数 + case LabCheckAutoSeacherText://搜索框返回文本-带复选框参数 + case LabAutoCompleteValueParam://搜索框返回文本-带参数 + case LabAutoCompleteTextParam://搜索框返回文本-带参数 + case LabMultiSelectValueParam:// 多选 获取Value值,需要带参数 + case LabMultiSelectTextParam:// 多选 获取Text值,需要带参数 + case LabCheckComboxValue: // 下拉框控件带复选框 获取Value值 + case LabCheckComboxText: // 下拉框控件带复选框 获取Text值 + case LabSmartQueryReturnID://智能搜索返回ID + case LabSmartQueryReturnName://智能搜索返回Name + case LabSmartQueryReturnIDWithParam://智能搜索返回ID 带参数 + case LabSmartQueryReturnNameWithParam://智能搜索返回Name 带参数 + case LabComboxModuleValueParam://智能搜索返回ID 带参数 + case LabComboxModuleTextParam://智能搜索返回Name 带参数 + case LabComTreeboxValue: // 下拉树返回value + case LabComTreeboxText: // 下拉树返回text + case LabComTreeboxValueParam: // 下拉树返回value带参数 + case LabComTreeboxTextParam: // 下拉树返回text带参数 + case LabComTreeboxLeafValue: // 下拉树返回末级value + case LabComTreeboxLeafText: // 下拉树返回末级text带 + case LabComTreeboxLeafValueParam: // 下拉树返回末级value带参数 + case LabComTreeboxLeafTextParam: // 下拉树返回末级text带参数 + case LabMultiComTreeboxLeafValue: // 下拉树多返回末级value + case LabMultiComTreeboxLeafText: // 下拉树多返回末级text带 + case LabMultiComTreeboxLeafValueParam: // 下拉树多返回末级value带参数 + case LabMultiComTreeboxLeafTextParam: // 下拉树多返回末级text带参数 + case LabMultiComTreeboxValue: // 下拉树多返回value + case LabMultiComTreeboxText: // 下拉树多返回text + case LabMultiComTreeboxValueParam: // 下拉树多返回value带参数 + case LabMultiComTreeboxTextParam: // 下拉树多返回text带参数 + case LabRadioGroupValue: //单选框组数 + case LabRadioGroupText: //单选框组 + case CheckboxGroupValue: //多选框组 + case CheckboxGroupText: //多选框组 + case LabWinBtnsValue: //单选框组数 + case LabWinBtnsText: //单选框组 + case LabWinMultiBtnsValue: //多选框组 + case LabWinMultiBtnsText: //多选框组 + case LabWinGridValue: //单选框组数 + case LabWinGridText: //单选框组 + case LabWinMultiGridValue: //多选框组 + case LabWinMultiGridText: //多选框组 + case LabComStarValueParam: //多选框组 + case LabComStarTextParam: //多选框组 + case LabModuleSelectValues: + case LabModuleMultiSelectValues: + _myControl = new ComboBox(row); +// myControl.putAll(row); +// myControl.put("xtype", "combobox"); + String dataSource = _myControl.getDataSource(); + String displayField = _myControl.getDisplayField(); + String valueField = _myControl.getValueField(); + if (dataSource == null || dataSource.isEmpty() || (displayField == null && valueField == null)) { + myControl = new TextField(row); + break; + } + if (module != null && module.PopPms != null) { + _myControl.setPopPms(module.PopPms); + } + myControl = SetBoxPro(_myControl, row, fieldType, module); + break; + case LabTreeType:// 树控件 + if (!isWindowsDirver()) { + _myControl = new ComboBox(row); + dataSource = _myControl.getDataSource(); + displayField = _myControl.getDisplayField(); + valueField = _myControl.getValueField(); + log.debug(String.valueOf("LabTreeType" + (isNullOrEmpty(dataSource) || (isNullOrEmpty(displayField) && isNullOrEmpty(valueField))))); + if (isNullOrEmpty(dataSource) || (isNullOrEmpty(displayField) && isNullOrEmpty(valueField))) { + myControl = new Hidden(row); + break; + } + _myControl.setPopPms(module != null ? module.PopPms : null);//右键参数 + myControl = SetBoxPro(_myControl, row, fieldType, module); + log.debug(String.valueOf(myControl + " LabTreeType")); + break; + } + myControl = new Hidden(row); + String dataSourceStr = myControl.getDataSource(); + if (dataSourceStr != null && dataSourceStr.indexOf("0 _hidden") > -1) { + myControl = new TextField(row); + } + break; + case LabDate: // 日期控件 + case LabCheckDateEx: + case LabDateTime: + case LabCheckDateTimeShort: + case LabDateTimeShort: + case LabTime: + case LabCheckTime: + case LabShortTime: + case LabCheckShortTime: + case LabYearTime: + if (format.equals("0") || format.isEmpty()) { + format = GetFormatByCType(fieldType); + } + myControl = new DateField(row); + myControl.setFormat(format); + break; + case LabPassword: // 数字控件 + myControl = new TextField(row); + ((TextField) myControl).setType("password"); + break; + case LabTextInt: // 数字控件 + myControl = new NumberField(row); + break; + case LabMemoEdit: // 多文本框控件 + myControl = new TextField(row); + ((TextField) myControl).setType("triggertextarea"); + break; + case LabRemark: // 多文本框控件 + myControl = new TextareaField(row); + ((TextareaField) myControl).setAutoHeight(true); + break; + case LabJointRemark: // 多文本框控件 + myControl = new TextareaField(row); + ((TextareaField) myControl).setType("jointTextAreaField"); + break; + case LabRichEdit: // 富文本框控件 + myControl = new TextareaField(row); + ((TextareaField) myControl).setType("textareaEditor"); + break; + case LabMap: // 地图控件 + myControl = new MapLocationField(row); + ((MapLocationField) myControl).setGlyph("xf041@FontAwesome"); + if (module != null && module.getMasterTable() != null && !module.getMasterTable().isEmpty()) { + updateMapField(module.getMasterTable(), myControl.getName()); + } + myControl = new MapLocationField(row); + break; + case LabCheckBox: + myControl = new Checkbox(row); + break; + case LabPic: // 文件控件 + case LabPicEx: // 图片控件 + myControl = new ImageFiled(row); + myControl.setXtype("field.upload"); + break; + case LabCalcText: + myControl = new Field(row); + myControl.setXtype("calculatorField"); + break; + case LabVPhone: + myControl = new Field(row); + myControl.setXtype("vphonefield"); + break; + case LabPhone: + myControl = new Field(row); + myControl.setXtype("field.phone"); + break; + case LabOSacn: + case LabTSacn: + myControl = new Field(row); + myControl.setXtype("scanfield"); + break; + case LabSignature: + myControl = new Field(row); + myControl.setXtype("signaturefield"); + break; + case LabEleScale: + myControl = new Field(row); + myControl.setXtype("eleScale"); + break; + case ApiLabText: + myControl = new TextField(row); + myControl.setXtype("apifield"); + dataSource = myControl.getDataSource(); + if (dataSource != null && (dataSource.startsWith("http") || dataSource.startsWith("/"))) { + myControl.store = new DataStore(); + myControl.store.url = (dataSource); + String defaultSource = myControl.getDefaultsource(); + if (defaultSource != null && defaultSource.startsWith("{") && defaultSource.endsWith("}")) { + myControl.store.ApiDataNode = (defaultSource.substring(1, defaultSource.length() - 1)); + } + } + break; + default: // 文本框控件 + myControl = new TextField(row); + myControl.setXtype("textfield"); + break; + } + +// out.println("myControl1 " + JSON.Encode(myControl)); + + Field field = myControl; + // 处理默认值和其他属性 + String defaultSource = field.getDefaultsource(); + + if (isNullOrEmpty(defaultSource) + && fieldType == SystemEnums.ControlType.LabTreeType + && field.getDataSource() != null && !field.getDataSource().isEmpty() + && module != null && module.getLeftRecord() != null) { + // 假设 GetTreeSpecNo 方法存在 +// out.println("GetTreeSpecNo 1"); + field.setDefaultval(GetTreeSpecNo(module.getMasterTable(), field.getName(), field.getValueField(), field.getDataSource(), Objects.toString(leftRecord.get(field.getValueField()), ""), null, 0)); +// out.println("GetTreeSpecNo 2"); + + } else { + // 假设 GetDefaultValue 方法存在 + field.setDefaultval(GetDefaultValue(defaultSource, module, SystemTypeEnums.PmType.store)); + } + + + if (module != null) { + if (module instanceof BaseModule) { + BaseModule baseModule = (BaseModule) module; + field.PopupHeight = baseModule.getPopupHeight(); + field.PopupWidth = baseModule.getPopupWidth(); + } + field.setPopPms(module.PopPms); + if (module.getLeftRecord() != null && field.getDefaultval() == null) { + if ("speciesname".equals(field.getName())) { + } + } + if (module.Updrow != null) { + // 假设 SetDefaultVal 方法存在 + field.setDefaultVal(module.Updrow); +// 2026.2.27 + if (isNullOrEmpty(module.IdValue) && isNullOrEmpty(field.getDefaultval())) + field.setDefaultval(GetDefaultValue(defaultSource, null, module == null ? null : module.getLeftRecord(), SystemTypeEnums.PmType.store)); + + String labelColor = field.getLabelColor(); + if (labelColor != null && !labelColor.isEmpty()) { + field.setLabelColor(GetDefaultValue(labelColor, module, SystemTypeEnums.PmType.sql)); + } + } else if (!isNullOrEmpty(field.getLabelColor())) { + String labelColor = field.getLabelColor(); + if (labelColor != null && labelColor.indexOf("{") > -1) { + field.setLabelColor(""); + } + } + } + + myControl.setUtil(this); + myControl.setDbOperator(jdbcTemplate); + + if (myControl instanceof ComboBox) { + // 假设 SetBoxStore 方法存在 + setBoxStore((ComboBox) myControl, false, module); + } + if (fieldType.toString().contains("LabCheck") + && !"LabCheckBox".equals(fieldType.toString())) { +// out.println("if判断"); + myControl = new LabelCheckBox(row, myControl); + } + + myControl.setUtil(this); + myControl.setDbOperator(jdbcTemplate); +// out.println("myControl " + JSON.Encode(myControl)); + return myControl; + } + + /** + * 创建具体控件列表 + * + * @param tab 控件数据列表(对应C#的DataTable) + * @param module 模块实体 + * @param order 是否排序 + * @param formKey 表单键 + * @return 控件列表 + */ + public List createControl(List> tab, ModuleEntity module, boolean order, String formKey) { + if (tab == null || tab.isEmpty()) { + return null; + } + + Map datas = new HashMap<>(); + List enabalCondField = new ArrayList<>(); // 带有条件的控件,可用受条件影响 + List coms = tab.stream() + .map(row -> { +// out.println("createControl.row" + row); + Field field = createControl(row, module, false);// 调整参数 +// out.println("createControl.field" + field); + datas.put(field.getName().toLowerCase(), field.getValue()); + if (field.getEnableCond() != null && !field.getEnableCond().isEmpty()) { + enabalCondField.add(field); + } + return field; + }) + .collect(Collectors.toList()); + +// out.println("COMs" + JSON.Encode(coms)); + // 合并模块的LeftRecord数据到datas + if (module != null && module.getLeftRecord() != null) { + datas.putAll(module.getLeftRecord()); + } + // 处理带有启用条件的控件 + if (!enabalCondField.isEmpty()) { + for (Field field : enabalCondField) { + boolean disabled = !toBoolean(evalCond(field.getEnableCond(), datas, null)); + if (field.getCondEnableType() == 1) { + field.setHidden(disabled); + } else { + field.disabled = disabled; + field.enableDisValue = disabled; + } + } + } + + // 移除重复name且隐藏的控件 + Map> nameGroups = coms.stream() + .filter(field -> field instanceof Field || field.getClass().isAssignableFrom(Field.class)) + .collect(Collectors.groupingBy(Field::getName)); + + List toRemove = new ArrayList<>(); +// for (List group : nameGroups.values()) { +// if (group.size() > 1) { +// // 收集组中隐藏的控件 +// List hiddenInGroup = group.stream() +// .filter(field -> field.getHidden() != null && field.getHidden()) +// .collect(Collectors.toList()); +// toRemove.addAll(hiddenInGroup); +// } +// } +// coms.removeAll(toRemove); + +// 2026.2.27 + List> repetition = nameGroups.values().stream() + // 对应 C#: Where(group => group.Count() > 1) + .filter(group -> group.size() > 1) + // 对应 C#: Select(group => group.ToList()/* 无hidden筛选 */) + .map(ArrayList::new) // 转为ArrayList,和C#的ToList()一致 + .collect(Collectors.toList()); + +// 3. 遍历每组,按规则删除元素(逻辑完全复刻C#) + for (List list : repetition) { + int i = 0; // 对应 C# 的 int i = 0; + // 对应 C#: list.Where(_com => _com.hidden == false).FirstOrDefault() + Field noHide = list.stream() + .filter(_com -> Boolean.FALSE.equals(_com.getHidden())) // hidden==false(处理null,null不会匹配) + .findFirst() + .orElse(null); // 无匹配时返回null,对应FirstOrDefault() + + // 遍历组内每个元素,按条件删除 + for (Field com : list) { + // 对应 C#: if(noHide==null&&i >0||com!=noHide)coms.Remove(com); + if ((noHide == null && i > 0) || (com != noHide)) { + coms.remove(com); // 对应 C# 的 coms.Remove(com)(注意:有并发修改异常风险) + } + i++; // 索引自增,和C#完全一致 + } + } + + // 设置下拉框联动字段(上级值改变时影响下级) + List finalComs = coms; + coms.forEach(field -> { + if (field != null && field.getName() != null) { + List linkNames = finalComs.stream() + .filter(com -> com instanceof ComboBox) + .map(com -> (ComboBox) com) + .filter(combobox -> { + String dataSource = combobox.getDataSource(); + return dataSource != null && dataSource.toLowerCase() + .contains("{" + field.getName().toLowerCase() + "}"); + }) + .map(ComboBox::getName) + .collect(Collectors.toList()); + field.linknames = linkNames.toArray(new String[0]); + } + }); + +// out.println("1: " + coms); + // 添加分组控件 + if (module != null) { + formKey = (formKey == null || formKey.isEmpty()) ? module.getFromkey() : formKey; + coms = addGroupControl(formKey, coms); + } + + // 处理隐藏控件和排序 + List finalComs1 = coms; + List hiddens = coms.stream() + .filter(field -> field instanceof Hidden) + .peek(field -> field.setTabIndex(finalComs1.size())) + .collect(Collectors.toList()); + + List notHiddens = coms.stream() + .filter(field -> !(field instanceof Hidden)) + .collect(Collectors.toList()); + +// out.println("2: " + notHiddens); + + // 排序非隐藏控件 + if (order) { + notHiddens = orderTabFields(notHiddens); +// out.println("3: " + notHiddens); + } + + // 设置tabIndex + for (int i = 0; i < notHiddens.size(); i++) { + notHiddens.get(i).setTabIndex(i); + } + + // 合并隐藏控件和非隐藏控件(隐藏控件放前面) + hiddens.addAll(notHiddens); + coms = hiddens; +// out.println("4: " + coms); + return coms; + } + + // 重载方法,处理order默认值为true的情况 + public List createControl(List> tab, ModuleEntity module, String formKey) { + return createControl(tab, module, true, formKey); + } + + // 重载方法,处理formKey默认值为空的情况 + public List createControl(List> tab, ModuleEntity module, boolean order) { + return createControl(tab, module, order, ""); + } + + // 重载方法,处理所有默认参数 + public List createControl(List> tab, ModuleEntity module) { + return createControl(tab, module, true, ""); + } + + /** + * 根据控件类型获取默认的日期/时间格式 + * + * @param ctype 控件类型枚举 + * @return 对应的日期/时间格式字符串,如果不匹配则返回 null + */ + public static String GetFormatByCType(int ctype) { + try { + // 根据整数值获取对应的 ControlType 枚举实例 + SystemEnums.ControlType fieldType = SystemEnums.ControlType.fromValue(ctype); + return GetFormatByCType(fieldType); + } catch (IllegalArgumentException e) { + // 处理无法解析的枚举值 + log.warn(String.valueOf("无法解析的 ControlType 值: " + ctype)); + return null; + } + } + + public static String GetFormatByCType(SystemEnums.ControlType ctype) { + String format = null; + + switch (ctype) { + case LabDate: // 日期控件 + format = (format == null || format.isEmpty()) ? "yyyy-MM-dd" : format; + break; + case LabDateTime: + case LabCheckDateTimeShort: + format = (format == null || format.isEmpty()) ? "yyyy-MM-dd HH:mm:ss" : format; + break; + case LabDateTimeShort: + format = (format == null || format.isEmpty()) ? "yyyy-MM-dd HH:mm" : format; + break; + case LabTime: + case LabCheckTime: + format = (format == null || format.isEmpty()) ? "HH:mm:ss" : format; + break; + case LabShortTime: + case LabCheckShortTime: + format = (format == null || format.isEmpty()) ? "HH:mm" : format; + break; + case LabYearTime: + case LabLabYearTime: + format = (format == null || format.isEmpty()) ? "yyyy-MM" : format; + break; + default: + format = null; + break; + } + + return format; + } + + /** + * 设置组合框控件的属性 + * + * @param myControl 组合框控件(Map形式) + * @param row 数据行 + * @param ctype 控件类型 + * @param module 模块信息 + * @return 设置后的控件 + */ + public static Field SetBoxPro(ComboBox myControl, Map row, SystemEnums.ControlType ctype, ModuleEntity module) { + String ftypeName = ctype.toString(); + + if (ftypeName.contains("Text") || ftypeName.contains("ReturnName")) { + myControl.setValueField(myControl.getDisplayField()); + } + + if (ftypeName.contains("RadioGroup")) { + myControl.setXtype("radiogroup"); + } else if (ftypeName.contains("CheckboxGroup")) { + myControl.setXtype("CheckboxGroup"); + } + + if (ftypeName.contains("AutoComplete") || ftypeName.contains("AutoSeacher")) { + myControl.editable = true; // 可以输入筛选 + } + + if (ftypeName.contains("Multi")) { + myControl.multiSelect = true; // 多选 + } + + if (ftypeName.contains("Param")) { + myControl.parmbox = true; // 多选 + myControl.editable = true; // 可以输入筛选 + } + + if (ftypeName.contains("Module") && myControl.getAddModule() != null && !myControl.getAddModule().isEmpty()) { + // 弹窗模块选择 + myControl.pickxtype = "Ywp.widget.ModuleSelect"; + myControl.setXtype("WinCombobox"); + } + + if (ftypeName.contains("Win")) { + // 弹窗模块选择 + myControl.setXtype("WinCombobox"); + if (ftypeName.contains("Grid")) { + myControl.pickxtype = "picker.GridPanel"; + } + if (ftypeName.contains("Btns")) { + myControl.pickxtype = "picker.Btns"; + } + } + + if (ftypeName.contains("Star")) { + // 弹窗模块选择 + myControl.setXtype("Yw.form.field.Star"); + } + + if (ftypeName.contains("Tree") || + (myControl.getDataSource()) != null && + (myControl.getDataSource().toLowerCase().contains("_parentid") || + myControl.getDataSource().toLowerCase().contains(" pid"))) { + myControl.pickxtype = "picker.TreePanel"; + if (ftypeName.contains("Leaf")) { + // 设置选择器配置 + Map pickerCfg = new HashMap<>(); + pickerCfg.put("selectLeaf", true); + myControl.pickerCfg = pickerCfg; + } + } + + // 当数据源是本表,数据字段查询为本表 + if (module != null && + myControl.getDisplayField() != null && + myControl.getDisplayField().equals(myControl.getValueField()) && + module.getMasterTable() != null && + !module.getMasterTable().isEmpty() && + myControl.getDataSource() != null && + myControl.getDataSource().toLowerCase().contains(module.getMasterTable().toLowerCase()) && + myControl.getName() != null && + myControl.getDataSource().toLowerCase().contains(myControl.getName().toLowerCase())) { + myControl.MValue = false; + } + + return myControl; + } + + /** + * 检查表中是否存在特定列,不存在则添加 + * + * @param tableName 表名 + * @param columnName 列名 + * @param columnType 列类型 + */ + private void checkAndAddColumn(String tableName, String columnName, String columnType) { + // 查询 INFORMATION_SCHEMA 检查表中是否存在该列 + String checkSql = "SELECT COUNT(*) FROM DBA_TAB_COLUMNS " + + "WHERE TABLE_NAME = ? AND COLUMN_NAME = ?"; + + Integer count = jdbcTemplate.queryForObject( + checkSql, + new Object[]{tableName, columnName}, + Integer.class + ); + + // 如果列不存在,则添加 + if (count != null && count == 0) { + String alterSql = "ALTER TABLE " + tableName + " ADD " + columnName + " " + columnType; + jdbcTemplate.execute(alterSql); + } + } + + /** + * 更新地图字段:添加经纬度相关列 + * + * @param tabname 表名 + * @param fieldname 字段名前缀 + */ + public void updateMapField(String tabname, String fieldname) { + // 检查并添加 _itude 列 + checkAndAddColumn(tabname, fieldname + "_itude", "VARCHAR(100)"); + + // 检查并添加 _longitude 列 + checkAndAddColumn(tabname, fieldname + "_longitude", "VARCHAR(50)"); + + // 检查并添加 _latitude 列 + checkAndAddColumn(tabname, fieldname + "_latitude", "VARCHAR(50)"); + } + + + /** + * 获取树形的特殊编号 + */ + public String GetTreeSpecNo(String tabName, String fieldName, String valueField, + String treeSql, String parentNo, Type fieldType, int batchIndex) { + // 检查treeSql中是否包含表名 + if (treeSql.toLowerCase().indexOf((tabName + "").toLowerCase()) < 0) { + return parentNo; + } + + // 确定字段类型 + if (fieldType == null) { + fieldType = GetTableColumnType(tabName, fieldName); + } + + // 如果是值类型,直接返回parentNo + if (fieldType != null && isValueType(fieldType)) { + return parentNo; + } + + // 检查必要参数 + if (valueField == null || valueField.isEmpty() || treeSql == null || treeSql.isEmpty()) { + return ""; + } + + String defaultValue = ""; + // 正则表达式检查parentNo是否只包含数字、点和减号 + Pattern pattern = Pattern.compile("[^\\d\\.-]"); + if (!pattern.matcher(parentNo == null ? "" : parentNo).find()) { + // 构建查询命令 + SqlAnalyzer sqlAnalyzer = new SqlAnalyzer(treeSql); + String queryCmd = sqlAnalyzer.BuildTopText(); + + // 构建查询SQL + String sql = String.format( + "select max(cast(%s as bigint))+1 from (%s) temp where %s like '%s__'", + valueField, queryCmd, valueField, parentNo == null ? "" : parentNo + ); + + // 执行查询 + String sqlWithParams = PublicUtil.ReqSqlPms(null, null, sql, SystemTypeEnums.PmType.sql, getUser()); + Object result = jdbcTemplate.queryForObject(sqlWithParams, Object.class); + defaultValue = result != null ? result.toString() : ""; + + int spLen = 2; + // 根据parentNo前缀调整长度 + if (parentNo != null && !parentNo.isEmpty()) { + if (parentNo.startsWith("000")) { + spLen = 4; + } else if (parentNo.startsWith("00")) { + spLen = 3; + } + } + + String bw = "0000".substring(0, spLen - 1); + + // 处理默认值 + if (defaultValue == null || defaultValue.isEmpty()) { + defaultValue = String.format("%s%s1", parentNo == null ? "" : parentNo, bw); + } else { + defaultValue = bw + defaultValue; + } + + // 处理批量索引 + if (batchIndex > 0) { + try { + long value = Long.parseLong(defaultValue); + defaultValue = bw + (value + batchIndex); + } catch (NumberFormatException e) { + // 如果转换失败,保持原有值 + } + } + + return defaultValue; + } + + return parentNo; + } + + public String GetTreeSpecNo(String tabName, String fieldName, String valueField, + String treeSql, String parentNo) { + return GetTreeSpecNo(tabName, fieldName, valueField, treeSql, parentNo, null, 0); + } + + /** + * 判断类型是否为值类型 + */ + private boolean isValueType(Type type) { + if (type instanceof Class) { + Class clazz = (Class) type; + return clazz.isPrimitive() || + Number.class.isAssignableFrom(clazz) || + Boolean.class.isAssignableFrom(clazz) || + Character.class.isAssignableFrom(clazz); + } + return false; + } + + /** + * 使用静态 JdbcTemplate 查询表结构信息 + */ + public Type GetTableColumnType(String tabName, String colName) { + if (jdbcTemplate == null) { + throw new IllegalStateException("JdbcTemplate 未初始化,请确保 Spring 上下文已启动"); + } + + String tbName = null; + tbName = GetOtherDbOper(tabName, tbName); + + try { + // 查询 syscolumns 系统表获取列的 xtype + String sql = String.format("SELECT xtype FROM syscolumns WHERE ID=OBJECT_ID('%s') AND name='%s'", tbName, colName); + Object result = jdbcTemplate.queryForObject(sql, Object.class); + + if (result != null) { + int xtype = Integer.parseInt(result.toString()); + if (xtype > 0) { + // 将 SQL Server 的 xtype 转换为 Java 类型 + return NativeExtensionUtils.SqlxtypeToProType(xtype); + } + } + } catch (Exception e) { + // 处理异常,记录日志等 + log.error("Exception caught", e); + } + + return null; + } + + /** + * 根据表名获取对应的数据库操作对象 + * + * @param tablename 表名 + * @param tbname 处理后的表名(通过数组引用返回) + * @return 数据库操作对象 + */ + public String GetOtherDbOper(String tablename, String tbname) { + tbname = tablename; + if (tablename == null || tablename.isEmpty()) { + return null; + } + + if (tablename.contains(".") && tablename.split("\\.").length <= 3) { + String[] parts = tablename.split("\\."); + String newDbName = parts[0].replace("[", "").replace("]", ""); + tbname = tablename.replace(parts[0] + ".", ""); + } + + return tbname; + } + + /** + * 使用正则表达式替换连接字符串中的数据库名 + */ + public String replaceDatabaseName(String connectionString, String newDbName) { + // 匹配 "Database=数据库名;" 部分,不区分大小写 + Pattern pattern = Pattern.compile("Database=([^;]+);", Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(connectionString); + + if (matcher.find()) { + return matcher.replaceFirst("Database=" + newDbName + ";"); + } + + return connectionString; + } + + + public String GetDefaultValue(String defaultValue, ModuleEntity module, SystemTypeEnums.PmType pmType) { + Object row = null; + if (module != null) { + if (module.Updrow != null) { + row = module.Updrow; + } else { + row = module.getLeftRecord(); + } + } + Map lere = null; + if (module != null && module.getLeftRecord() != null) { + // 创建Hashtable并复制HashMap中的所有键值对 + Map temp = new Hashtable<>(); + temp.putAll(module.getLeftRecord()); // 利用Map接口的putAll方法复制数据 + lere = temp; + } else { + lere = null; + } + // 假设这里有一个重载的 GetDefaultValue 方法 + return GetDefaultValue(defaultValue, (Map) row, lere, pmType); + } + + /** + * 获取默认值,处理各种特殊格式的默认值表达式 + */ + public String GetDefaultValue(String defaultValue, Map row, Map leftRecord, SystemTypeEnums.PmType pmType) { + if (defaultValue == null || defaultValue.isEmpty()) { + return defaultValue; + } + + // 去除前后空格、制表符等 + defaultValue = defaultValue.replaceAll("^[\\r\\n]+", ""); + + // 处理 GUID + if ("guid".equalsIgnoreCase(defaultValue)) { + return UUID.randomUUID().toString(); + } + + // 处理以 @ 开头的 SQL 表达式 + if (defaultValue.startsWith("@")) { + if ("@select getdate()".equalsIgnoreCase(defaultValue.trim())) { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); + } + + pmType = SystemTypeEnums.PmType.sql; + + // 解析 SQL 参数 + if (row instanceof Map) { + defaultValue = PublicUtil.ReqSqlPmsByRow(row, leftRecord, defaultValue, pmType, getUser()); + } else if (row instanceof Map) { + defaultValue = PublicUtil.ReqSqlPms(row, leftRecord, defaultValue, pmType, getUser()); + } else { + defaultValue = PublicUtil.ReqSqlPms(null, leftRecord, defaultValue, pmType, getUser()); + } + + boolean isDm = ConfigUtil.getProviderName().equals("dm"); + if (isDm) defaultValue.replace("db_name()", "(SELECT SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') FROM DUAL)"); + // 执行 SQL 查询 + // 替换你原来的 queryForObject 代码段 + List resultList = jdbcTemplate.queryForList( + defaultValue.replace("@", ""), // 你的动态SQL(去掉@符号) + Object.class // 返回类型保持不变 + ); + +// 处理结果:空列表返回null,有结果取第一条(符合原逻辑“取单个值”的诉求) + Object val = resultList.isEmpty() ? null : resultList.get(0); + + if (val instanceof Date || val instanceof TimeStamp) { + return formatDateTime((Date) val); + } + + return val != null ? val.toString() : ""; + } + + // 处理 {parentkey} 占位符 + if (defaultValue.toLowerCase().contains("{parentkey}")) { + defaultValue = defaultValue.toLowerCase().replace("{parentkey}", ""); + } + + // 处理 {parent.key} 占位符 + if (defaultValue.toLowerCase().contains("{parent.key}")) { + defaultValue = defaultValue.toLowerCase().replace("{parent.key}", "{parent.speciesno}"); + } + + // 替换 {# 为 {parent. + defaultValue = defaultValue.replace("{#", "{parent."); + + // 再次解析参数 + if (row instanceof java.sql.ResultSet) { + defaultValue = PublicUtil.ReqSqlPmsByRow(row, leftRecord, defaultValue, pmType, getUser()); + } else if (row instanceof Map) { + defaultValue = PublicUtil.ReqSqlPms(row, leftRecord, defaultValue, pmType, getUser()); + } else { + defaultValue = PublicUtil.ReqSqlPms(null, leftRecord, defaultValue, pmType, getUser()); + } + + // 处理以 ! 开头的存储过程调用 + if (defaultValue.startsWith("!")) { + String procSql = "exec " + defaultValue.replace("!", "").replace("(", " ").replace(")", ""); + try { + // 直接执行SQL语句 + Object val = jdbcTemplate.queryForObject(procSql, Object.class); +// out.println("val: " + val + (val != null ? val.getClass() : "null")); + if (val instanceof Date) { + return formatDateTime((Date) val); + } + return val != null ? val.toString() : ""; + } catch (EmptyResultDataAccessException e) { + // 处理查询无结果的情况 + log.debug(String.valueOf("存储过程未返回结果: " + e.getMessage())); + return ""; + } catch (Exception e) { + log.debug(String.valueOf("执行存储过程出错: " + e.getMessage())); +// e.printStackTrace(); + return ""; + } + } + + return defaultValue; + } + + /** + * 格式化日期时间,去除时间部分的 00:00:00 + */ + public String formatDateTime(Date date) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String formatted = sdf.format(date); + return formatted.replace(" 00:00:00", ""); + } + + /** + * 获取操作数据库对象 + */ + public JdbcTemplate GetOperater() { + return jdbcTemplate; + } + + /** + * 处理数据库查询语句 + * 参数10个 + */ + public String dealQuerySql(String querySql, Map record, Map leftRecord, Map pms, String keyField, String keyValue, boolean precise, boolean readOnly, boolean convertStr, boolean doNotSpelling) { + List keyWhere = new ArrayList<>(); + StringBuilder sqlBuder = new StringBuilder(); + querySql = querySql == null ? "" : querySql; + + if (querySql.contains("#")) { + querySql = querySql.replaceAll("#\\s*and\\s*", "and #"); + + if (record == null || record.isEmpty()) { + if (readOnly || precise) { + querySql = querySql.replace("{#", "{").replaceAll("(#)(.|\\n)*?(#)", keyValue == null || keyValue.isEmpty() ? "1!=1" : "1=1"); + } else { + querySql = querySql.replace("{#", "{").replaceAll("(#)(.|\\n)*?(#)", "1=1"); + } + } else { + if (keyValue != null && !keyValue.isEmpty() && (readOnly || precise)) { + // querySql = querySql.replace("{#", "{").replaceAll("(#)(.|\\n)*?(#)", "1=1"); + // 获取两个#号之间存在{}替换字符则去掉 两个# + String pattern = "#(.*?)#"; +// Pattern.DOTALL 等价于 C# 的 RegexOptions.Singleline + java.util.regex.Pattern regex = java.util.regex.Pattern.compile(pattern, java.util.regex.Pattern.DOTALL); + java.util.regex.Matcher match = regex.matcher(querySql); + String val = ""; + boolean isMatchSuccess = match.find(); + if (isMatchSuccess) { // 等价于 C# 的 match.Success + val = match.group(1); // 等价于 C# 的 Groups[1].Value + } + + if (isMatchSuccess && val.indexOf("{") > -1) { // 保持和原代码一致的判断逻辑 + querySql = querySql.replace("#" + val + "#", val); // 替换 #{val}# 为 val + } else { + // 先替换 {# 为 {,再用正则替换 #...# 为 1=1 + String tempSql = querySql.replace("{#", "{"); + java.util.regex.Pattern replaceRegex = java.util.regex.Pattern.compile("(#)(.|\\n)*?(#)"); + querySql = replaceRegex.matcher(tempSql).replaceAll("1=1"); + } + } else { + querySql = querySql.replace("\\n", "\n"); // 处理转义的换行符 + + // 2. 使用更稳定的正则,兼容跨行和#前后的空格 + // 正则:#\\s*(.+?)\\s*#,并启用Pattern.DOTALL模式 + Pattern pattern = Pattern.compile("#\\s*(.+?)\\s*#", Pattern.DOTALL); + Matcher matcher = pattern.matcher(querySql); +// Pattern pattern = Pattern.compile("(#)(.|\\n)*?(#)"); +// Matcher matcher = pattern.matcher(querySql); + List matchList = new ArrayList<>(); + while (matcher.find()) { + matchList.add(matcher.group()); // 收集所有#...#内容(基于原始SQL) + } + // 2. 遍历替换(和C# foreach一致) + for (String match : matchList) { + // 用C#的Trim('#')等价逻辑:仅去掉首尾# + String cleanMatch = match.trim().replaceAll("^#|#$", ""); + querySql = querySql.replace(match, cleanMatch); + } + } + } + } + + if (leftRecord != null) { + if (record == null) { + record = new HashMap<>(); + } + for (Map.Entry entry : leftRecord.entrySet()) { + if (!record.containsKey(entry.getKey())) { + record.put(entry.getKey(), entry.getValue()); + } + } + } + + boolean isInner = keyField != null && !keyField.isEmpty() && querySql.toLowerCase().contains("{" + keyField + "}"); + + if (isInner) { + return PublicUtil.ReqSqlPms(record, leftRecord, querySql, SystemTypeEnums.PmType.sql, getUser()); + } + + if (keyValue != null && !keyValue.isEmpty() && keyField != null && !keyField.isEmpty()) { + if (keyValue.contains(",")) { + if (precise || readOnly) { + keyValue = keyValue.replace("'", "").replace(",", "','"); + keyWhere.add(buildKVWhere(keyField, keyValue, precise || readOnly, convertStr, doNotSpelling)); + } else { + String[] vs = keyValue.replace("'", "").split(","); + for (String s : vs) { + String w = buildKVWhere(keyField, s.replace("%2C", ","), precise || readOnly, convertStr, doNotSpelling); + if (w != null && !w.isEmpty()) { + keyWhere.add(w); + } + } + } + } else { + keyWhere.add(buildKVWhere(keyField, keyValue.replace("%2C", ","), precise || readOnly, convertStr, doNotSpelling)); + } + + if (keyField != null && !keyField.isEmpty()) { + // 完全等价于 C# 的 (\ ){1,} + boolean hasTop = Pattern.compile("select +top +", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE) + .matcher(querySql) + .find(); + int maxVal = 200, i = 0; + if (keyWhere.size() > maxVal) { + String kw = "1!=1"; + for (String w : keyWhere) { + kw = String.format("%s or %s", kw, w); + i++; + if (i % maxVal == 0) { + Map wd = new HashMap<>(); + wd.put(keyField, String.format(" And (%s)", kw)); + SqlAnalyzer sqlAnalyzer = new SqlAnalyzer(querySql); + sqlBuder.append(sqlAnalyzer.InsertWhere(wd, false, true, !hasTop && (precise || readOnly))); + sqlBuder.append(";"); + kw = "1!=1"; + } + } + return sqlBuder.toString(); + } else { + Map wd = new HashMap<>(); + wd.put(keyField, String.format(" And (%s)", String.join(" or ", keyWhere))); + SqlAnalyzer sqlAnalyzer = new SqlAnalyzer(querySql); + querySql = sqlAnalyzer.InsertWhere(wd, false, true, !hasTop && (precise || readOnly)); + } + } + } + + return PublicUtil.ReqSqlPms(record, leftRecord, querySql, SystemTypeEnums.PmType.sql, getUser()); + } + + //专门给附件的 + public String DealQuerySql(String querySql, Map record, Map leftRecord, Map pms, String keyField, String keyValue, boolean precise, boolean readOnly, boolean convertStr, boolean doNotSpelling) { + List keyWhere = new ArrayList<>(); + StringBuilder sqlBuder = new StringBuilder(); + querySql = querySql == null ? "" : querySql; + + if (querySql.contains("#")) { + // 对齐C#的正则替换:# 空格 and 空格 → # and + querySql = querySql.replaceAll("#\\s*and\\s*", "# and "); + + if (record == null || record.isEmpty()) { + if (readOnly || precise) { + querySql = querySql.replace("{#", "{").replaceAll("(#)(.|\\n)*?(#)", keyValue == null || keyValue.isEmpty() ? "1!=1" : "1=1"); + } else { + querySql = querySql.replace("{#", "{").replaceAll("(#)(.|\\n)*?(#)", "1=1"); + } + } else { + if (keyValue != null && !keyValue.isEmpty() && (readOnly || precise)) { + // 获取两个#号之间存在{}替换字符则去掉 两个# + String pattern = "#(.*?)#"; + // Pattern.DOTALL 等价于 C# 的 RegexOptions.Singleline + java.util.regex.Pattern regex = java.util.regex.Pattern.compile(pattern, java.util.regex.Pattern.DOTALL); + java.util.regex.Matcher match = regex.matcher(querySql); + String val = ""; + boolean isMatchSuccess = match.find(); + if (isMatchSuccess) { // 等价于 C# 的 match.Success + val = match.group(1); // 等价于 C# 的 Groups[1].Value + } + + if (isMatchSuccess && val.indexOf("{") > -1) { // 保持和原代码一致的判断逻辑 + querySql = querySql.replace("#" + val + "#", val); // 替换 #{val}# 为 val + } else { + // 先替换 {# 为 {,再用正则替换 #...# 为 1=1 + String tempSql = querySql.replace("{#", "{"); + java.util.regex.Pattern replaceRegex = java.util.regex.Pattern.compile("(#)(.|\\n)*?(#)"); + querySql = replaceRegex.matcher(tempSql).replaceAll("1=1"); + } + } else { + // ========== 关键修改1:删除转义换行符的逻辑(C#无此逻辑) ========== + // 删掉:querySql = querySql.replace("\\n", "\n"); + + // ========== 关键修改2:使用和C#完全一致的正则 ========== + // C#原正则:(@"(#)(.|\n)*?(#)") + Pattern pattern = Pattern.compile("(#)(.|\\n)*?(#)", Pattern.DOTALL); + Matcher matcher = pattern.matcher(querySql); + List matchList = new ArrayList<>(); + while (matcher.find()) { + matchList.add(matcher.group()); // 收集所有#...#内容(基于原始SQL) + } + // 遍历替换(和C# foreach一致) + for (String match : matchList) { + // ========== 关键修改3:仅去掉首尾#,不trim(对齐C#的Trim('#')) ========== + // C#: m.Value.Trim('#') → Java: 仅替换首尾的#,保留中间所有空格/换行/字符 + String cleanMatch = match.replaceAll("^#|#$", ""); + querySql = querySql.replace(match, cleanMatch); + } + } + } + } + + if (leftRecord != null) { + if (record == null) { + record = new HashMap<>(); + } + for (Map.Entry entry : leftRecord.entrySet()) { + if (!record.containsKey(entry.getKey())) { + record.put(entry.getKey(), entry.getValue()); + } + } + } + + boolean isInner = keyField != null && !keyField.isEmpty() && querySql.toLowerCase().contains("{" + keyField + "}"); + + if (isInner) { + return PublicUtil.ReqSqlPms(record, leftRecord, querySql, SystemTypeEnums.PmType.sql, getUser()); + } + + if (keyValue != null && !keyValue.isEmpty() && keyField != null && !keyField.isEmpty()) { + // ========== 关键修改4:对齐C#的逗号判断逻辑(IndexOf(',') > 0) ========== + if (keyValue.indexOf(',') > 0) { + if (precise || readOnly) { + keyValue = keyValue.replace("'", "").replace(",", "','"); + keyWhere.add(buildKVWhere(keyField, keyValue, precise || readOnly, convertStr, doNotSpelling)); + } else { + String[] vs = keyValue.replace("'", "").split(","); + for (String s : vs) { + String w = buildKVWhere(keyField, s.replace("%2C", ","), precise || readOnly, convertStr, doNotSpelling); + if (w != null && !w.isEmpty()) { + keyWhere.add(w); + } + } + } + } else { + keyWhere.add(buildKVWhere(keyField, keyValue.replace("%2C", ","), precise || readOnly, convertStr, doNotSpelling)); + } + + if (keyField != null && !keyField.isEmpty()) { + // 完全等价于 C# 的 (\ ){1,} + boolean hasTop = Pattern.compile("select +top +", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE) + .matcher(querySql) + .find(); + int maxVal = 200, i = 0; + if (keyWhere.size() > maxVal) { + String kw = "1!=1"; + for (String w : keyWhere) { + kw = String.format("%s or %s", kw, w); + i++; + if (i % maxVal == 0) { + Map wd = new HashMap<>(); + wd.put(keyField, String.format(" And (%s)", kw)); + SqlAnalyzer sqlAnalyzer = new SqlAnalyzer(querySql); + sqlBuder.append(sqlAnalyzer.InsertWhere(wd, false, true, !hasTop && (precise || readOnly))); + sqlBuder.append(";"); + kw = "1!=1"; + } + } + return sqlBuder.toString(); + } else { + Map wd = new HashMap<>(); + wd.put(keyField, String.format(" And (%s)", String.join(" or ", keyWhere))); + SqlAnalyzer sqlAnalyzer = new SqlAnalyzer(querySql); + querySql = sqlAnalyzer.InsertWhere(wd, false, true, !hasTop && (precise || readOnly)); + } + } + } + + return PublicUtil.ReqSqlPms(record, leftRecord, querySql, SystemTypeEnums.PmType.sql, getUser()); + } + + + // dealQuerySql方法重载,参数9个 + public String dealQuerySql(String querySql, Map record, Map leftRecord, Map pms, String keyField, String keyValue, boolean precise, boolean readOnly, boolean convertStr) { + boolean doNotSpelling = false; + return dealQuerySql(querySql, record, leftRecord, pms, keyField, keyValue, precise, readOnly, convertStr, doNotSpelling); + } + + /** + * 处理某些地方大小写的问题 + */ + public String buildKVWhere(String keyField, String keyValue, boolean precise, boolean convertStr, boolean doNotSpelling) { + String v = Trim(keyValue); + String keyWhere = ""; + + if (keyField.indexOf(",") > -1) { + ArrayList pms = new ArrayList<>(); + String[] keys = keyField.split(","); + for (String key : keys) { + if (key != null && !key.trim().isEmpty()) { + pms.add(buildKVWhere(key, keyValue, precise, convertStr, doNotSpelling)); + } + } + if (!pms.isEmpty()) { + return "(" + String.join(" or ", pms) + ")"; + } + } + + String _keyField = convertStr ? String.format("convert(varchar(100), %s)", keyField) : keyField; + + if (precise) { + if (v.indexOf("','") > -1) { + keyWhere = String.format("%s in ('%s')", _keyField, keyValue); + } else { + keyWhere = String.format("%s = '%s'", _keyField, v); + } + } else if (RegexUtil.ENOnlyReg.matcher(v).matches() && !doNotSpelling) { + keyWhere = String.format("%s like '%%%s%%' OR dbo.P_GetPy(%s) like '%%%s%%'", _keyField, v, _keyField, v); + } else { + keyWhere = String.format("%s like '%%%s%%'", _keyField, v); + } + + return keyWhere; + } + + /** + * + * /// 设置combobox 的store以及porxy + * /// + * /// The _my control + * /// 是否初始化一个store的data + * /// ComboBox + * /// + */ + public ComboBox setBoxStore(ComboBox myControl, boolean isIniData, ModuleEntity module) { + DataStore store = new DataStore(); + String val = ""; + if (module == null || module.Updrow == null) { + val = String.valueOf(myControl.getDefaultval()); + } else { + val = String.valueOf(module.Updrow.get(myControl.getName())); + } + + // 处理默认值逻辑 + String valueField = myControl.getValueField(); + String displayField = myControl.getDisplayField(); + if (valueField != null && !valueField.isEmpty() + && module != null + && !val.isEmpty() + && !valueField.equals(displayField)) { + + String xtype = myControl.getXtype(); + if (!"radiogroup".equals(xtype) && !"CheckboxGroup".equals(xtype)) { +// boolean prise = myControl.getDataSource().indexOf("dbo.P_BaseMixInfoTab") < 0; + boolean prise = myControl.getDataSource().indexOf("P_BaseMixInfoTab") < 0; + String sql = dealQuerySql( + myControl.getDataSource(), + module.Updrow != null ? module.Updrow : null, + null, + null, + valueField + "," + displayField, + myControl.multiSelect ? val : val.replace(",", "%2C"), + prise, + prise, false, false + ); + + try { + // 假设dbOperator存在于当前类中,用于执行SQL查询 + store.data = (jdbcTemplate.queryForList(sql)); + } catch (Exception e) { + + } + } + } + + var c = myControl.getColumns(); + // 设置查询参数 + myControl.queryParam = myControl.getDisplayField(); + Map extraParams = new HashMap<>(); + extraParams.put("moduleId", module != null ? module.getModuleId() : null); + extraParams.put("fdtype", myControl.getFieldDataType()); + extraParams.put("id", myControl.getFieldId()); + extraParams.put("_fId", myControl.getFieldId()); + extraParams.put("textField", displayField); + extraParams.put("popPms", JSON.Encode(myControl.getPopPms())); // 假设使用Jackson或FastJSON + store.extraParams = (extraParams); + myControl.store = (store); + return myControl; + } + + /** + * 评估条件表达式(带row和leftRecord参数) + * + * @param cond 条件表达式 + * @param row 数据行 + * @param leftRecord 左侧记录哈希表 + * @return 评估结果(布尔值) + */ + public Object evalCond(String cond, Map row, Map leftRecord) { + // 获取处理后的条件值 + String _cond = GetDefaultValue(cond, row, leftRecord, SystemTypeEnums.PmType.sql); + + // 处理特殊前缀条件 + if ((cond.startsWith("@") || cond.startsWith("!")) && (_cond == null || _cond.isEmpty())) { + return ""; + } + + // 转换SQL条件为可执行代码 + String code = PublicUtil.SqlToCode(_cond); + + // 执行条件评估并转换为布尔值 + Object evalResult = PublicUtil.EvalCond(code, (input, e) -> new DataImpl(new DbOperator()).EvalCond(_cond), true); + return evalResult; + } + + /** + * 评估条件表达式(带Module参数,支持默认值) + * + * @param cond 条件表达式 + * @param module 模块实体(可为null) + * @return 评估结果(对象类型) + */ + public Object evalCond(String cond, ModuleEntity module) { + // 获取处理后的条件值,使用ignorenull类型 + String _cond = GetDefaultValue(cond, module, SystemTypeEnums.PmType.ignorenull); + + // 包含占位符时直接返回原始条件 + if (_cond != null && _cond.indexOf("{") > -1) { + return _cond; + } + + // 处理特殊前缀条件 + if (cond.startsWith("@") || cond.startsWith("!")) { + return DataTableUtil.toBoolean(_cond); + } + + // 转换SQL条件为可执行代码并评估 + String code = PublicUtil.SqlToCode(_cond); + return DataTableUtil.toBoolean(PublicUtil.EvalCond(code, (input, e) -> new DataImpl(new DbOperator()).EvalCond(_cond), true)); + } + + /** + * 重载方法,默认module为null + */ + public Object evalCond(String cond) { + return evalCond(cond, null); + } + + /** + * 为控件添加分组信息 + * + * @param formKey 表单标识 + * @param controls 原始控件列表 + * @return 包含分组的控件列表 + */ + public List addGroupControl(String formKey, List controls) { + // 查询分组信息(对应C#的DataTable) +// String sql = "select * from T_SystemFieldGroupTab where unionkey = ?"; +// String sql = "select * from dbo.p_systemaddgroup where formkey= ? "; + String sql = "select * from p_systemaddgroup where formkey= ? "; + List> groupRows = jdbcTemplate.queryForList(sql, formKey); + + if (!groupRows.isEmpty()) { + List groups = new ArrayList<>(); + + // 遍历分组数据,创建FieldSet + for (Map row : groupRows) { + // 使用带util参数的构造方法,对应C#的FieldSet(item, this) + FieldSet group = new FieldSet(row, this); + group.setXtype("fieldset"); // 设置xtype属性 + groups.add(group); + } + // 将原有控件添加到分组列表后 + groups.addAll(controls); + return groups; + } + // 无分组时返回原始控件列表 + return controls; + } + + /** + * 对控件列表按位置排序,确保Tab键顺序正确 + * + * @param list 待排序的Field列表 + */ + public List orderTabFields(List list) { + int length = list.size(); + + // 选择排序算法(保持与原C#代码一致的排序逻辑) + for (int i = 0; i < length - 1; i++) { + Field field = list.get(i); + Field min = field; + int minIndex = i; + + // 寻找从i开始的最小值 + for (int j = i + 1; j < length; j++) { + Field other = list.get(j); + if (other == null) { + continue; + } + + // 原C#代码中的"other > min"对应Java的compareTo方法 + // 这里假设compareTo返回正数表示other大于min + if (org.example.Entity.Control.Base.Component.isGreater(other, min)) { + minIndex = j; + min = other; + } + } + + // 交换位置 + if (minIndex != i) { + list.set(minIndex, field); + list.set(i, min); + } + } + + return list; + } + + /** + * 根据控件类型,获取ext控件 + * + * @param gridcolumn 表格列对象 + * @param dr 数据行(对应C#的DataRow,Java中常用Map表示) + * @param module 模块对象 + * @param isValeqkey 输出参数:下拉菜单时,是否为key等于val(通过数组传递以实现类似out效果) + * @return 控件对象 + */ + public Object getGridColumnEditType(RowColumn gridcolumn, Map dr, ModuleEntity module, boolean[] isValeqkey) { + // 初始化输出参数(默认key等于val) + isValeqkey[0] = true; + + // 创建控件(第三个参数对应C#的createEditor=true) + Field component = createControl(dr, module, true); + +// out.println(component.getClass()); + // 处理下拉框类型控件 + if (component != null && component.getClass() == ComboBox.class) { + ComboBox box = (ComboBox) component; + // 判断valueField是否等于displayField + isValeqkey[0] = (box.getValueField() == (box.getDisplayField())); +// out.println("isValeqkey[0] + " + isValeqkey[0]); + } + + // 清空字段标签 + if (component != null) { + component.setFieldLabel(""); + } + + return component; + } + + /** + * 检查权限 + * + * @param kcPurview 用户权限值 + * @param menuid 模块id + * @return 权限标识("AllPurview":所有权限;"ReadPurview":只读权限;"":无权限) + */ + public String CheckPurview(String kcPurview, String menuid) { + // 管理员拥有所有权限 + LoginUserInfo user = getUser(); + if (user != null && "管理员".equals(user.UserName)) { + return "AllPurview"; + } + + // 权限值或模块ID为空时无权限 + if (NativeExtensionUtils.isNullOrEmpty(kcPurview) || NativeExtensionUtils.isNullOrEmpty(menuid)) { + return ""; + } + + // 格式化权限字符串,前后加逗号便于精确匹配 + String formattedKcPurview = String.format(",%s,", kcPurview); + String allPurview = String.format(",%s,", menuid); // 所有权限标识 + String readPurview = String.format(",%s|,", menuid); // 只读权限标识 + + // 检查权限级别 + if (formattedKcPurview.contains(allPurview)) { + return "AllPurview"; + } else if (formattedKcPurview.contains(readPurview)) { + return "ReadPurview"; + } else { + return ""; + } + } +} + + + diff --git a/WebErp/weberp/src/main/java/org/example/Utils/ImageUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/ImageUtil.java new file mode 100644 index 0000000..d87e9dc --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/ImageUtil.java @@ -0,0 +1,434 @@ +package org.example.Utils; + +import javax.imageio.ImageIO; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.stream.ImageOutputStream; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.*; +import java.util.Iterator; + +/** + * 图片处理工具类 + * 功能描述:图片格式转换、缩放、旋转、合并等操作 + * 版本:1.0.0.0 + */ +public class ImageUtil { + + /** + * 图片深度枚举 + */ + public enum ImgDeep { + D8, + D32 + } + + /** + * 格式化图片(转换格式、调整大小、设置深度) + * + * @param path 源图片路径 + * @param savePath 保存路径 + * @param format 目标图片格式(如"jpg"、"png"等) + * @param deep 图片深度 + * @param nwidth 目标宽度(0表示不调整) + * @param nheight 目标高度(0表示不调整) + * @throws IOException 图片处理异常 + */ + public static void format(String path, String savePath, String format, ImgDeep deep, int nwidth, int nheight) throws IOException { + BufferedImage srcImage; + + // 根据指定尺寸调整图片 + if (nwidth > 0 && nheight > 0) { + srcImage = reSize(path, nwidth, nheight); + } else { + srcImage = ImageIO.read(new File(path)); + } + + // 处理8位深度图片(通过GIF格式中转) + if (deep == ImgDeep.D8) { + String tempPath = savePath + ".temp"; + format(srcImage, tempPath, "gif"); // GIF为8位索引色 + format(new File(tempPath), savePath, format); + new File(tempPath).delete(); + return; + } + + // 直接保存图片 + format(srcImage, savePath, format); + } + + /** + * 重载方法,使用默认深度D32 + */ + public static void format(String path, String savePath, String format) throws IOException { + format(path, savePath, format, ImgDeep.D32, 0, 0); + } + + /** + * 调整图片大小(保持比例) + * + * @param path 源图片路径 + * @param nwidth 目标宽度 + * @param nheight 目标高度 + * @return 调整后的图片 + * @throws IOException 图片读取/处理异常 + */ + public static BufferedImage reSize(String path, int nwidth, int nheight) throws IOException { + BufferedImage source = ImageIO.read(new File(path)); + int sW = 0, sH = 0; + int originalWidth = source.getWidth(); + int originalHeight = source.getHeight(); + + // 计算缩放后的尺寸(保持比例) + if (nwidth > 0 || nheight > 0) { + if ((long) originalWidth * nheight > (long) originalHeight * nwidth) { + sW = nwidth; + sH = (nwidth * originalHeight) / originalWidth; + } else { + sH = nheight; + sW = (originalWidth * nheight) / originalHeight; + } + } else { + sW = originalWidth; + sH = originalHeight; + } + + // 创建缩放后的图片 + BufferedImage resized = new BufferedImage(sW, sH, BufferedImage.TYPE_INT_RGB); + Graphics2D g = resized.createGraphics(); + + // 设置高质量缩放参数 + g.setComposite(AlphaComposite.Src); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + g.drawImage(source, 0, 0, sW, sH, 0, 0, originalWidth, originalHeight, null); + g.dispose(); + + + return resized; + } + + /** + * 将BufferedImage保存为指定格式的图片文件 + * + * @param bmp 图片对象 + * @param filename 保存路径 + * @param format 图片格式 + * @throws IOException 保存异常 + */ + public static void format(BufferedImage bmp, String filename, String format) throws IOException { + File outputFile = new File(filename); + ImageWriter writer = getImageWriter(format); + if (writer == null) { + throw new IllegalArgumentException("不支持的图片格式: " + format); + } + + // 设置压缩质量 + ImageWriteParam param = writer.getDefaultWriteParam(); + if (param.canWriteCompressed()) { + param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + param.setCompressionQuality(1.0f); // 最高质量 + } + + try (ImageOutputStream ios = ImageIO.createImageOutputStream(outputFile)) { + writer.setOutput(ios); + writer.write(null, new javax.imageio.IIOImage(bmp, null, null), param); + } finally { + writer.dispose(); + bmp.flush(); + } + } + + /** + * 将图片文件转换格式 + * + * @param sourceFile 源文件 + * @param targetPath 目标路径 + * @param format 目标格式 + * @throws IOException 处理异常 + */ + public static void format(File sourceFile, String targetPath, String format) throws IOException { + BufferedImage image = ImageIO.read(sourceFile); + format(image, targetPath, format); + } + + /** + * 获取指定格式的ImageWriter + * + * @param format 图片格式(如"jpg"、"png") + * @return ImageWriter对象 + */ + private static ImageWriter getImageWriter(String format) { + Iterator writers = ImageIO.getImageWritersByFormatName(format); + if (writers.hasNext()) { + return writers.next(); + } + return null; + } + + /** + * 判断文件是否为图片 + * + * @param filePath 文件路径 + * @return 是否为图片 + */ + public static boolean isImg(String filePath) { + if (filePath == null || filePath.isEmpty()) { + return false; + } + + String[] imgExtensions = {".jpg", ".jpeg", ".jpe", ".png", ".gif", ".bmp", ".tif"}; + String extension = getFileExtension(filePath).toLowerCase(); + for (String ext : imgExtensions) { + if (ext.equals(extension)) { + return true; + } + } + return false; + } + + /** + * 获取文件扩展名(带点) + */ + private static String getFileExtension(String filePath) { + int lastDotIndex = filePath.lastIndexOf('.'); + if (lastDotIndex == -1) { + return ""; + } + return filePath.substring(lastDotIndex); + } + + /** + * 将BufferedImage转换为字节数组 + * + * @param bitmap 图片对象 + * @return 字节数组 + * @throws IOException 转换异常 + */ + public static byte[] bitmapToBytes(BufferedImage bitmap) throws IOException { + try (ByteArrayOutputStream ms = new ByteArrayOutputStream()) { + ImageIO.write(bitmap, "png", ms); // 使用PNG格式避免压缩损失 + return ms.toByteArray(); + } + } + + /** + * 将字节数组转换为输入流 + * + * @param bytes 字节数组 + * @return 输入流 + */ + public static InputStream bytesToStream(byte[] bytes) { + return new ByteArrayInputStream(bytes); + } + + /** + * 根据图片EXIF信息旋转图片(修复拍照方向问题) + * + * @param path 图片路径 + * @throws IOException 处理异常 + */ + public static void translateImageByTag(String path) throws IOException { + if (!isImg(path)) { + return; + } + + File file = new File(path); + BufferedImage bmp = ImageIO.read(file); + rotating(bmp, path); + bmp.flush(); + } + + /** + * 旋转图片并保存 + * + * @param img 图片对象 + * @param fullname 保存路径 + * @throws IOException 保存异常 + */ + private static void rotating(BufferedImage img, String fullname) throws IOException { + // 获取EXIF方向信息(274是方向标签ID) + int orientation = 0; + try { + // 尝试读取方向信息(Java内置API不直接支持EXIF,此处为简化实现) + // 实际项目中可使用metadata-extractor等库获取EXIF信息 + // 参考: https://github.com/drewnoakes/metadata-extractor + orientation = getExifOrientation(img); + } catch (Exception e) { + // 忽略获取方向信息失败的情况 + } + + BufferedImage rotatedImage = img; + int width = img.getWidth(); + int height = img.getHeight(); + + // 根据方向信息旋转图片 + switch (orientation) { + case 2: + rotatedImage = rotateFlip(img, RotationType.FLIP_HORIZONTAL); + break; + case 3: + rotatedImage = rotateFlip(img, RotationType.ROTATE_180); + break; + case 4: + rotatedImage = rotateFlip(img, RotationType.FLIP_VERTICAL); + break; + case 5: + rotatedImage = rotateFlip(img, RotationType.ROTATE_90_FLIP_HORIZONTAL); + break; + case 6: + rotatedImage = rotateFlip(img, RotationType.ROTATE_90); + width = img.getHeight(); + height = img.getWidth(); + break; + case 7: + rotatedImage = rotateFlip(img, RotationType.ROTATE_270_FLIP_HORIZONTAL); + break; + case 8: + rotatedImage = rotateFlip(img, RotationType.ROTATE_270); + width = img.getHeight(); + height = img.getWidth(); + break; + default: + return; // 无需旋转 + } + + // 保存旋转后的图片 + String format = getFileExtension(fullname).substring(1); // 去除点号 + format(rotatedImage, fullname, format); + rotatedImage.flush(); + } + + /** + * 旋转或翻转图片 + */ + private static BufferedImage rotateFlip(BufferedImage img, RotationType type) { + int width = img.getWidth(); + int height = img.getHeight(); + BufferedImage result; + Graphics2D g; + + switch (type) { + case ROTATE_90: + result = new BufferedImage(height, width, img.getType()); + g = result.createGraphics(); + g.rotate(Math.PI / 2, height / 2.0, height / 2.0); + g.drawImage(img, 0, -width, null); + break; + case ROTATE_180: + result = new BufferedImage(width, height, img.getType()); + g = result.createGraphics(); + g.rotate(Math.PI, width / 2.0, height / 2.0); + g.drawImage(img, -width, -height, null); + break; + case ROTATE_270: + result = new BufferedImage(height, width, img.getType()); + g = result.createGraphics(); + g.rotate(Math.PI * 3 / 2, width / 2.0, width / 2.0); + g.drawImage(img, -height, 0, null); + break; + case FLIP_HORIZONTAL: + result = new BufferedImage(width, height, img.getType()); + g = result.createGraphics(); + g.drawImage(img, width, 0, -width, height, null); + break; + case FLIP_VERTICAL: + result = new BufferedImage(width, height, img.getType()); + g = result.createGraphics(); + g.drawImage(img, 0, height, width, -height, null); + break; + case ROTATE_90_FLIP_HORIZONTAL: + result = rotateFlip(rotateFlip(img, RotationType.ROTATE_90), RotationType.FLIP_HORIZONTAL); + return result; + case ROTATE_270_FLIP_HORIZONTAL: + result = rotateFlip(rotateFlip(img, RotationType.ROTATE_270), RotationType.FLIP_HORIZONTAL); + return result; + default: + return img; + } + + g.dispose(); + return result; + } + + /** + * 旋转类型枚举 + */ + private enum RotationType { + ROTATE_90, + ROTATE_180, + ROTATE_270, + FLIP_HORIZONTAL, + FLIP_VERTICAL, + ROTATE_90_FLIP_HORIZONTAL, + ROTATE_270_FLIP_HORIZONTAL + } + + /** + * 获取EXIF方向信息(简化实现,实际需使用第三方库) + * 注意:Java内置API不直接支持EXIF读取,此处返回0 + * 推荐使用metadata-extractor库实现真正的EXIF读取 + */ + private static int getExifOrientation(BufferedImage img) { + // 实际实现参考: + // https://github.com/drewnoakes/metadata-extractor + return 0; + } + + /** + * 合并两张图片 + * + * @param imgBack 背景图片 + * @param img 叠加图片 + * @param xDeviation X轴偏移量 + * @param yDeviation Y轴偏移量 + * @return 合并后的图片 + */ + public static BufferedImage combinImage(BufferedImage imgBack, BufferedImage img, int xDeviation, int yDeviation) { + int bgWidth = imgBack.getWidth(); + int bgHeight = imgBack.getHeight(); + int imgWidth = img.getWidth(); + int imgHeight = img.getHeight(); + + // 计算缩放比例 + float scale = (float) imgHeight / imgWidth; + float bgScale = (float) bgWidth / bgHeight; + + // 调整叠加图片大小以适应背景 + if (bgScale > scale) { + imgHeight = bgHeight; + imgWidth = (int) (scale * imgHeight); + } else { + imgWidth = bgWidth; + imgHeight = (int) (imgWidth / scale); + } + + // 创建合并后的图片 + BufferedImage result = new BufferedImage(bgWidth, bgHeight, BufferedImage.TYPE_INT_RGB); + Graphics2D g = result.createGraphics(); + g.setColor(Color.WHITE); + g.fillRect(0, 0, bgWidth, bgHeight); + g.drawImage(imgBack, 0, 0, bgWidth, bgHeight, null); + + // 计算叠加位置(居中 + 偏移) + int x = (bgWidth - imgWidth) / 2 + xDeviation; + int y = (bgHeight - imgHeight) / 2 + yDeviation; + g.drawImage(img, x, y, imgWidth, imgHeight, null); + + g.dispose(); + imgBack.flush(); + img.flush(); + + return result; + } + + /** + * 重载方法,使用默认偏移量 + */ + public static BufferedImage combinImage(BufferedImage imgBack, BufferedImage img) { + return combinImage(imgBack, img, 0, 0); + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Utils/JSON.java b/WebErp/weberp/src/main/java/org/example/Utils/JSON.java new file mode 100644 index 0000000..461810f --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/JSON.java @@ -0,0 +1,357 @@ +package org.example.Utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.node.NullNode; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +// 正确导入:Jackson 核心包的 ObjectMapper +import com.fasterxml.jackson.databind.ObjectMapper; +// 同时确保导入其他依赖的类(避免连锁错误) +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.TimeZone; + +/** + * Jackson 实现的 JSON 工具类,完全替代原 Gson 版本,保持与 C# JSON 方法一致的功能 + */ +public class JSON { + private static final Logger log = LoggerFactory.getLogger(JSON.class); + + private static boolean defaultSetting = false; + public static String dateTimeFormat = "yyyy-MM-dd HH:mm:ss"; + // Jackson 核心对象映射器(全局单例,线程安全) + private static final ObjectMapper objectMapper; + private static final Object NULL_PLACEHOLDER = "null"; + // ------------------------------ + // 1. 自定义序列化器:适配原 Gson 逻辑 + // ------------------------------ + + /** + * 日期序列化器:将 Date 转为 "yyyy-MM-dd HH:mm:ss"(东八区),对应原 DateTimeSerializer + */ + private static class DateTimeSerializer extends JsonSerializer { + private final SimpleDateFormat sdf = new SimpleDateFormat(dateTimeFormat); + + { + sdf.setTimeZone(TimeZone.getTimeZone("GMT+8")); // 明确东八区,与原逻辑一致 + } + + @Override + public void serialize(Date date, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeString(sdf.format(date)); + } + } + + /** + * 字符串序列化器:空字符串/Null 按 Null 处理(后续会被过滤),对应原 TypeAdapter + */ + private static class StringSerializer extends JsonSerializer { + @Override + public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException { + if (value == null) { + gen.writeNull(); + return; + } + // 空字符串正常输出(符合你的需求) + gen.writeString(value); + } + } + + + /** + * 集合序列化器:空集合/Null 按 Null 处理(后续会被过滤),对应原 TypeAdapter + */ + private static class CollectionSerializer extends JsonSerializer> { + @Override + public void serialize(Collection collection, JsonGenerator gen, SerializerProvider provider) throws IOException { + if (collection == null) { + gen.writeNull(); // 仅 null 集合写 null(配合全局 NON_NULL 会被过滤) + return; + } + // !!删除空集合写 null 的逻辑!!空集合直接走正常数组序列化 + gen.writeStartArray(); + for (Object elem : collection) { + if (elem != null) { // 保留原逻辑:过滤集合中的 null 元素 + objectMapper.writeValue(gen, elem); + } + } + gen.writeEndArray(); + } + } + + // ------------------------------ + // 1. 自定义序列化器:新增 MapSerializer(过滤 Map 中的 null 值) + // ------------------------------ + private static class MapSerializer extends JsonSerializer> { + @Override + public void serialize(Map map, JsonGenerator gen, SerializerProvider provider) throws IOException { + if (map == null) { + gen.writeNull(); // 配合全局 NON_NULL 过滤 null Map + return; + } + gen.writeStartObject(); + // 遍历 Map,仅序列化 value 非 null 的键值对 + for (Map.Entry entry : map.entrySet()) { + Object key = entry.getKey(); + Object value = entry.getValue(); + if (value != null) { // 跳过 value 为 null 的项 + gen.writeFieldName(key.toString()); // 写键 + objectMapper.writeValue(gen, value); // 递归序列化值(兼容嵌套类型) + } + } + gen.writeEndObject(); + } + } + + /** + * 字符串反序列化器:对应原 TypeAdapter 的 read 方法 + */ + private static class StringDeserializer extends JsonDeserializer { + @Override + public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + return p.getText(); // 直接读取字符串,与原逻辑一致 + } + } + + // ------------------------------ + // 2. 初始化 ObjectMapper:复刻原 Gson 所有配置 + // ------------------------------ + static { + JsonFactory jsonFactory = new JsonFactory(); + jsonFactory.configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, false); + + objectMapper = new ObjectMapper(jsonFactory); + SimpleModule customModule = new SimpleModule("CustomModule"); + + // 1. 注册独立的 StringSerializer(不再用匿名内部类,逻辑正确) + customModule.addSerializer(String.class, new StringSerializer()); + + // 2. 注册独立的 CollectionSerializer(逻辑正确) + @SuppressWarnings("unchecked") + Class> collectionType = (Class>) (Class) Collection.class; + customModule.addSerializer(collectionType, new CollectionSerializer()); + + @SuppressWarnings("unchecked") + Class> mapType = (Class>) (Class) Map.class; + customModule.addSerializer(mapType, new MapSerializer()); + + // 3. 其他序列化器(不变) + customModule.addSerializer(Date.class, new DateTimeSerializer()); + customModule.addDeserializer(String.class, new StringDeserializer()); + customModule.addSerializer(Long.class, ToStringSerializer.instance); + customModule.addSerializer(Long.TYPE, ToStringSerializer.instance); + + // 4. 全局配置(不变,确保 NON_NULL 生效) + objectMapper + .registerModule(customModule) + .setSerializationInclusion(JsonInclude.Include.NON_NULL) // 核心:忽略所有 null 字段 + .configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false) // 忽略 Map 中的 null 值 + .configure(SerializationFeature.FAIL_ON_SELF_REFERENCES, false) + .configure(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, true) // 保留空数组 + .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false) + .enable(SerializationFeature.INDENT_OUTPUT) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + } + + // ------------------------------ + // 3. 保持原 C# 风格的默认配置方法(空实现,仅为兼容原代码结构) + // ------------------------------ + private static void defaultSetting() { + if (defaultSetting) return; + defaultSetting = true; + // Jackson 配置已在静态块初始化,此处保持与原 C# 方法结构一致 + } + + // ------------------------------ + // 4. 核心 API:与原 Gson 工具类方法名、功能完全一致 + // ------------------------------ + + /** + * 获取全局 ObjectMapper 实例(支持自定义扩展) + */ + public static ObjectMapper getObjectMapper() { + return objectMapper; + } + + /** + * 序列化:将对象转为 JSON 字符串,对应原 toJson() + */ + public static String toJson(Object obj) { + try { + return objectMapper.writeValueAsString(obj); + } catch (JsonProcessingException e) { + log.error("Exception caught", e); + return null; + } + } + + /** + * 反序列化:将 JSON 转为指定类型对象,对应原 fromJson(Class) + */ + public static T fromJson(String json, Class clazz) { + if (json == null || json.isEmpty()) { + return null; + } + try { + return objectMapper.readValue(json, clazz); + } catch (JsonProcessingException e) { + log.error("Exception caught", e); + return null; + } + } + + /** + * 反序列化:将 JSON 转为指定泛型类型(如 List、Map),对应原 fromJson(Type) + */ + public static T fromJson(String json, Type type) { + if (json == null || json.isEmpty()) { + return null; + } + // Jackson 用 TypeReference 接收泛型类型,需转换 Type 为 TypeReference + TypeReference typeRef = new TypeReference() { + @Override + public Type getType() { + return type; + } + }; + try { + return objectMapper.readValue(json, typeRef); + } catch (JsonProcessingException e) { + log.error("Exception caught", e); + return null; + } + } + + // ------------------------------ + // 5. C# 风格方法:Encode/Decode,保持原逻辑完全一致 + // ------------------------------ + + /** + * 等效于 C# 的 JSON.Encode():处理 String 类型特殊逻辑,其他类型正常序列化 + */ + public static String Encode(Object o) { + defaultSetting(); // 兼容原 C# 方法的默认配置调用 + if (o == null) { + return null; + } + if ("null".equals(o.toString().trim())) { + return null; + } + if (o instanceof String) { + return (String) o; + } + return toJson(o); + } + + /** + * 等效于 C# 的 JSON.Decode(string json):将 JSON 转为 Java 原生集合(Hashtable/ArrayList) + */ + public static Object Decode(String json) { + if (json == null || json.isEmpty()) { + return ""; + } + try { + // 第一步:解析 JSON 为 JsonNode(Jackson 的 JSON 节点模型) + JsonNode rootNode = objectMapper.readTree(json); + // 第二步:若解析结果是字符串,尝试二次解析(复刻原 Gson 逻辑) + if (rootNode.isTextual()) { + String strValue = rootNode.asText(); + rootNode = objectMapper.readTree(strValue); // 二次解析字符串中的 JSON + } + // 第三步:递归转为 Java 原生集合(Hashtable/ArrayList),与原 toObject() 逻辑一致 + return toObject(rootNode); + } catch (Exception e) { + log.error("Exception caught", e); + return null; + } + } + + /** + * 等效于 C# 的 JSON.Decode(string json, Type type):指定类型反序列化 + */ + public static Object Decode(String json, Type type) { + if (json == null || json.isEmpty()) { + return null; + } + // 复用 fromJson(Type) 逻辑,保持一致 + return fromJson(json, type); + } + + // ------------------------------ + // 6. 辅助方法:复刻原 toObject() 逻辑,将 JsonNode 转为 Java 原生集合 + // ------------------------------ + + /** + * 递归转换 JsonNode 为 Java 原生对象(Hashtable/ArrayList/基本类型),与原 Gson 逻辑完全一致 + */ + public static Object toObject(JsonNode node) { + if (node == null || node.isNull() || node instanceof NullNode) { + return NULL_PLACEHOLDER; + } + + // 处理基本类型(字符串、数字、布尔) + if (node.isValueNode()) { + if (node.isTextual()) { + String s = node.asText(); + // 处理特殊日期格式(2010-09-02T10:00:00),复刻原 Gson 逻辑 + if (s.length() == 19 && s.charAt(10) == 'T' + && s.charAt(4) == '-' && s.charAt(13) == ':') { + try { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + return sdf.parse(s); + } catch (ParseException e) { + return s; // 解析失败则返回原字符串 + } + } + return s; + } else if (node.isNumber()) { + return node.numberValue(); // 数字→Number(自动适配 int/long/double) + } else if (node.isBoolean()) { + return node.booleanValue(); // 布尔→Boolean + } + } + + // 处理 JSON 对象→Hashtable(与原 Gson 逻辑一致,不用 HashMap 是为了兼容 C# 的 Dictionary) + if (node.isObject()) { + Hashtable hashtable = new Hashtable<>(); + Iterator> fields = node.fields(); + while (fields.hasNext()) { + Map.Entry entry = fields.next(); + hashtable.put(entry.getKey(), toObject(entry.getValue())); // 递归转换子节点 + } + return hashtable; + } + + // 处理 JSON 数组→ArrayList(与原 Gson 逻辑一致) + if (node.isArray()) { + ArrayList list = new ArrayList<>(node.size()); + for (JsonNode itemNode : node) { + list.add(toObject(itemNode)); // 递归转换数组元素 + } + return list; + } + + // 5. 兜底:所有未匹配的场景返回空字符串(不再返回null) + return NULL_PLACEHOLDER; + } +} + diff --git a/WebErp/weberp/src/main/java/org/example/Utils/JsEngine.java b/WebErp/weberp/src/main/java/org/example/Utils/JsEngine.java new file mode 100644 index 0000000..2517284 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/JsEngine.java @@ -0,0 +1,336 @@ +package org.example.Utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.oracle.truffle.js.scriptengine.GraalJSScriptEngine; + +import javax.script.*; +import java.lang.reflect.Type; +import java.util.Comparator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +public class JsEngine { + private static final Logger log = LoggerFactory.getLogger(JsEngine.class); + + // 缓存结果,有效期2小时 + private static final ConcurrentHashMap cache = new ConcurrentHashMap<>(); + private static final long CACHE_DURATION_MS = 2 * 60 * 60 * 1000; // 2小时 + private static final int DEFAULT_MAX_CACHE_ENTRIES = 1000; + private static volatile int maxCacheEntries = DEFAULT_MAX_CACHE_ENTRIES; + private static final AtomicLong accessSequence = new AtomicLong(); + // 线程本地存储:每个线程拥有独立的引擎实例 + private static final ThreadLocal ENGINE_THREAD_LOCAL = ThreadLocal.withInitial(() -> { + ScriptEngineManager manager = new ScriptEngineManager(); + // 优先级:GraalJS > JavaScript(Nashorn) + ScriptEngine engine = manager.getEngineByName("GraalJS"); + if (engine == null) { + engine = manager.getEngineByName("javascript"); + } + if (engine == null) { + engine = manager.getEngineByName("JavaScript"); + } + if (engine == null) { + throw new IllegalStateException("无法初始化JavaScript引擎!请检查:\n" + + "1. JDK 8及以下需确保包含Nashorn引擎\n" + + "2. JDK 11及以上需添加GraalVM JavaScript依赖"); + } + // GraalJS 额外配置:允许全访问(根据业务场景调整) + if (engine instanceof GraalJSScriptEngine) { + boolean allowAllAccess = Boolean.parseBoolean(WebConfigUtil.get("app.js.allow-all-access", "false")); + engine.getContext().setAttribute("polyglot.js.allowAllAccess", allowAllAccess, ScriptContext.ENGINE_SCOPE); + if (allowAllAccess) { + log.warn(String.valueOf("WARNING: GraalJS all-access mode is enabled by app.js.allow-all-access=true")); + } + } + return engine; + }); + + // 定时清理过期缓存(每小时执行一次) + private static final ScheduledExecutorService CLEANER = Executors.newSingleThreadScheduledExecutor(); + + static { + // 初始化缓存清理任务 + CLEANER.scheduleAtFixedRate(() -> { + long now = System.currentTimeMillis(); + cache.entrySet().removeIf(entry -> now - entry.getValue().timestamp > CACHE_DURATION_MS); + }, 1, 1, TimeUnit.HOURS); + } + + // 错误处理函数式接口 + @FunctionalInterface + public interface ErrorHandler { + Object handle(String input, Exception e); + } + + /** + * 动态计算 JavaScript 表达式的值(支持上下文参数) + * @param statement JS表达式 + * @param contextParams 脚本执行上下文参数(可选) + * @param onError 错误处理逻辑 + * @return 执行结果 + */ + public static Object Eval(String statement, Map contextParams, ErrorHandler onError) { + if (statement == null || statement.isEmpty()) { + return ""; + } + + // 生成更安全的缓存键:脚本内容 + 上下文参数哈希(避免碰撞) + String cacheKey = generateCacheKey(statement, contextParams); + + // 检查缓存 + CachedResult cached = cache.get(cacheKey); + if (cached != null && System.currentTimeMillis() - cached.timestamp < CACHE_DURATION_MS) { + cached.updateAccessOrder(); + return cached.result; + } + + ScriptEngine engine = null; + try { + // 获取当前线程的引擎实例 + engine = ENGINE_THREAD_LOCAL.get(); + // 创建独立的上下文(避免线程内多次执行的上下文污染) + ScriptContext context = new SimpleScriptContext(); + Bindings bindings = engine.createBindings(); + + // 注入上下文参数(如果有) + if (contextParams != null && !contextParams.isEmpty()) { + bindings.putAll(contextParams); + } + context.setBindings(bindings, ScriptContext.ENGINE_SCOPE); + + // 执行JavaScript表达式 + Object result = engine.eval(statement, context); + + // 存入缓存 + putCachedResult(cacheKey, result); + + return result; + } catch (ScriptException e) { + if (onError != null) { + return onError.handle(statement, e); + } + return null; + } catch (Exception e) { + // 捕获引擎初始化/上下文创建异常 + if (onError != null) { + return onError.handle(statement, e); + } + return null; + } + } + + /** + * 简化版:无上下文参数 + 默认错误处理 + */ + public static Object Eval(String statement) { + return Eval(statement, null, null); + } + + /** + * 简化版:无上下文参数 + 自定义错误处理 + */ + public static Object Eval(String statement, ErrorHandler onError) { + return Eval(statement, null, onError); + } + + /** + * 生成缓存键:脚本内容 + 上下文参数哈希 + */ + private static String generateCacheKey(String statement, Map contextParams) { + StringBuilder keyBuilder = new StringBuilder(); + // 脚本内容(原始值,避免toLowerCase丢失语义) + keyBuilder.append(statement.trim()).append("|"); + // 上下文参数哈希(无参数则为空) + if (contextParams != null && !contextParams.isEmpty()) { + keyBuilder.append(contextParams.hashCode()); + } + return keyBuilder.toString(); + } + + // 缓存结果类 + private static class CachedResult { + final Object result; + final long timestamp; + long lastAccessOrder; + + CachedResult(Object result) { + this.result = result; + this.timestamp = System.currentTimeMillis(); + updateAccessOrder(); + } + + void updateAccessOrder() { + this.lastAccessOrder = accessSequence.incrementAndGet(); + } + } + + // 关闭线程池(应用停止时调用) + public static void setMaxCacheEntries(int configuredMaxEntries) { + maxCacheEntries = Math.max(1, configuredMaxEntries); + enforceMaxCacheEntries(); + } + + public static void setMaxCacheEntriesForTests(int configuredMaxEntries) { + setMaxCacheEntries(configuredMaxEntries); + } + + public static void putCachedResultForTests(String cacheKey, Object result) { + putCachedResult(cacheKey, result); + } + + public static int cacheSizeForTests() { + evictExpiredResults(); + return cache.size(); + } + + public static boolean hasCachedResultForTests(String cacheKey) { + return cache.containsKey(cacheKey); + } + + public static void resetCacheForTests() { + cache.clear(); + maxCacheEntries = DEFAULT_MAX_CACHE_ENTRIES; + accessSequence.set(0); + ENGINE_THREAD_LOCAL.remove(); + } + + private static void putCachedResult(String cacheKey, Object result) { + cache.put(cacheKey, new CachedResult(result)); + enforceMaxCacheEntries(); + } + + private static void evictExpiredResults() { + long now = System.currentTimeMillis(); + cache.entrySet().removeIf(entry -> now - entry.getValue().timestamp > CACHE_DURATION_MS); + } + + private static void enforceMaxCacheEntries() { + evictExpiredResults(); + while (cache.size() > maxCacheEntries) { + cache.entrySet().stream() + .min(Comparator.comparingLong(entry -> entry.getValue().lastAccessOrder)) + .ifPresent(entry -> cache.remove(entry.getKey())); + } + } + + public static void shutdown() { + CLEANER.shutdown(); + try { + if (!CLEANER.awaitTermination(5, TimeUnit.SECONDS)) { + CLEANER.shutdownNow(); + } + } catch (InterruptedException e) { + CLEANER.shutdownNow(); + } + // 清理ThreadLocal,避免内存泄漏 + ENGINE_THREAD_LOCAL.remove(); + cache.clear(); + } +} + +//package org.example.Utils; +// +//import javax.script.ScriptEngine; +//import javax.script.ScriptEngineManager; +//import javax.script.ScriptException; +//import java.util.concurrent.ConcurrentHashMap; +// +//public class JsEngine { +// // 缓存结果,有效期2小时(保留原有逻辑) +// private static final ConcurrentHashMap cache = new ConcurrentHashMap<>(); +// private static final long CACHE_DURATION_MS = 2 * 60 * 60 * 1000; // 2小时 +// +// // JavaScript 引擎管理器(保留,线程安全) +// private static final ScriptEngineManager manager = new ScriptEngineManager(); +// // 【关键删除1】移除静态的ScriptEngine实例(多线程问题根源) +// // private static final ScriptEngine engine; +// +// // 【关键删除2】移除静态代码块中初始化engine的逻辑 +// // static { ... } +// +// // 错误处理函数式接口(保留原有逻辑) +// @FunctionalInterface +// public interface ErrorHandler { +// Object handle(String input, Exception e); +// } +// +// /** +// * 动态计算 JavaScript 表达式的值(仅修复线程问题) +// */ +// public static Object Eval(String statement, ErrorHandler onError) { +// if (statement == null || statement.isEmpty()) { +// return ""; +// } +// +// // 生成缓存键(保留原有逻辑) +// String cacheKey = statement.toLowerCase().trim().hashCode() + ""; +// +// // 检查缓存(保留原有逻辑) +// CachedResult cached = cache.get(cacheKey); +// if (cached != null && System.currentTimeMillis() - cached.timestamp < CACHE_DURATION_MS) { +// return cached.result; +// } +// +// // 【关键新增1】每次执行时创建独立的ScriptEngine实例(解决多线程共享问题) +// ScriptEngine engine = getScriptEngine(); +// // 校验引擎是否初始化成功(迁移原有静态代码块的校验逻辑) +// if (engine == null) { +// String errorMsg = "无法初始化JavaScript引擎!请检查:\n" + +// "1. JDK 8及以下需确保包含Nashorn引擎\n" + +// "2. JDK 11及以上需添加GraalVM JavaScript依赖"; +// if (onError != null) { +// return onError.handle(statement, new IllegalStateException(errorMsg)); +// } +// throw new IllegalStateException(errorMsg); +// } +// +// try { +// // 执行JavaScript表达式(逻辑不变,只是engine变为方法内局部变量) +// Object result = engine.eval(statement); +// +// // 存入缓存(保留原有逻辑) +// cache.put(cacheKey, new CachedResult(result)); +// +// return result; +// } catch (ScriptException e) { +// if (onError != null) { +// return onError.handle(statement, e); +// } +// return null; +// } +// } +// +// // 【关键新增2】抽取获取ScriptEngine的方法(复用原有初始化逻辑) +// private static ScriptEngine getScriptEngine() { +// ScriptEngine engine1; +// // 优先尝试小写名称,兼容更多环境(迁移原有静态代码块的逻辑) +// engine1 = manager.getEngineByName("javascript"); +// // 若失败,尝试大写名称 +// if (engine1 == null) { +// engine1 = manager.getEngineByName("JavaScript"); +// } +// return engine1; +// } +// +// // 简化版方法(保留原有逻辑) +// public static Object Eval(String statement) { +// return Eval(statement, null); +// } +// +// // 缓存结果类(保留原有逻辑) +// private static class CachedResult { +// final Object result; +// final long timestamp; +// +// CachedResult(Object result) { +// this.result = result; +// this.timestamp = System.currentTimeMillis(); +// } +// } +//} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/JwtHelp.java b/WebErp/weberp/src/main/java/org/example/Utils/JwtHelp.java new file mode 100644 index 0000000..a0bb588 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/JwtHelp.java @@ -0,0 +1,307 @@ +package org.example.Utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.jsonwebtoken.*; +import org.example.Entity.System.LoginUserInfo; +import org.springframework.stereotype.Component; + +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.time.Duration; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; + +@Component +public class JwtHelp { + private static final Logger log = LoggerFactory.getLogger(JwtHelp.class); + + + private static final String KEY_CONTAINER_NAME = "LKSJJWT_6789_ZYW_RSA"; + private static final String SECRET = resolveJwtSecret(); + private static final int RSA_KEY_SIZE = 2048; + + private static volatile KeyPair rsaKeyPair = null; + + private static String resolveJwtSecret() { + String secret = System.getenv("JWT_SECRET"); + if (secret == null || secret.trim().isEmpty()) { + secret = System.getProperty("jwt.secret"); + } + return secret; + } + + private static KeyPair getRsaKeyPair() { + KeyPair keyPair = rsaKeyPair; + if (keyPair != null) { + return keyPair; + } + + synchronized (JwtHelp.class) { + if (rsaKeyPair == null) { + rsaKeyPair = generateRsaKeyPair(); + } + return rsaKeyPair; + } + } + + private static KeyPair generateRsaKeyPair() { + try { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(RSA_KEY_SIZE); + return keyPairGenerator.generateKeyPair(); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("Initialize RSA key pair failed", e); + } + } + + public JwtHelp() { + try { + // 生成RSA密钥对(替代C#的RSACryptoServiceProvider) + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(RSA_KEY_SIZE); + rsaKeyPair = keyPairGenerator.generateKeyPair(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("初始化RSA密钥对失败", e); + } + } + + /** + * 设置用户缓存(默认7200秒过期) + */ + public void setUserCache(LoginUserInfo user) { + setUserCache(user, 7200); + } + + /** + * 设置用户缓存 + * + * @param user 用户信息 + * @param exp 过期时间(秒) + */ + public static void setUserCache(LoginUserInfo user, int exp) { + String cacheKey = String.format("usertoken_%s_%s_%d_%s", + user.UserName, + user.UserId, + user.ServerId, + user.LoginOs); + CacheUtil.set(cacheKey, user, Duration.ofSeconds(exp), null); + } + + /** + * 从缓存获取用户信息 + */ + private static Object getUserFromCache(LoginUserInfo user) { + if (user == null) { + return null; + } + String cacheKey = String.format("usertoken_%s_%s_%d_%s", + user.UserName, + user.UserId, + user.ServerId, + user.LoginOs); + return CacheUtil.get(cacheKey); + } + + /** + * 移除用户缓存 + */ + public static void removeUserCache(LoginUserInfo user) { + if (user == null) { + return; + } + String cacheKey = String.format("usertoken_%s_%s_%d_%s", + user.UserName, + user.UserId, + user.ServerId, + user.LoginOs); + CacheUtil.remove(cacheKey); + } + + /** + * 从缓存获取Token信息 + */ + private static Object getDictFromCache(String token) { + return CacheUtil.getCacheItem(token, (key) -> { + try { + // 解析JWT令牌 + Claims claims = Jwts.parserBuilder() + .setSigningKey(getRsaKeyPair().getPublic()) + .build() + .parseClaimsJws(token) + .getBody(); + return convertClaimsToMap(claims); + } catch (Exception e) { + return new HashMap(); + } + }, Duration.ofSeconds(7200), null, true); // 2小时过期(7200秒) + } + + /** + * 生成JWT令牌 + * + * @param user 用户信息 + * @param exp 过期时间(秒) + * @return 令牌字符串 + */ + public static String createToken(LoginUserInfo user, int exp) { + if (user == null) { + return null; + } + + try { + // 计算过期时间 + Instant now = Instant.now(); + Instant expiration = now.plusSeconds(exp); + + // 构建载荷 + Map payload = new HashMap<>(); + payload.put("exp", expiration.getEpochSecond()); + payload.put("UserId", user.UserId); + payload.put("UserCode", user.UserCode); + payload.put("UserName", user.UserName); + payload.put("LoginOs", user.LoginOs); + payload.put("ServerId", user.ServerId); + + // 生成令牌 + String token = Jwts.builder() + .setClaims(payload) + .signWith(getRsaKeyPair().getPrivate(), SignatureAlgorithm.RS256) + .compact(); + + // 缓存令牌 + CacheUtil.set(token, payload, Duration.ofSeconds(7200), null); // 2小时过期 + user.Token = (token); + setUserCache(user, exp); + + return token; + } catch (Exception e) { + return ""; + } + } + + /** + * 生成JWT令牌(默认3600秒过期) + */ + public String createToken(LoginUserInfo user) { + return createToken(user, 3600); + } + + /** + * 验证令牌并转换为指定类型 + */ + public static T validToken(String token, Class clazz) { + try { + @SuppressWarnings("unchecked") + Map userInfo = (Map) getDictFromCache(token); + + if (!validDate(userInfo)) { + return null; + } + + if (userInfo != null) { + if (clazz == Map.class) { + return clazz.cast(userInfo); + } else if (clazz == LoginUserInfo.class) { + LoginUserInfo user = new LoginUserInfo(); + user.UserId = (userInfo.get("UserId").toString()); + user.UserCode = (userInfo.get("UserCode").toString()); + user.UserName = (userInfo.get("UserName").toString()); + user.LoginOs = (userInfo.get("LoginOs").toString()); + user.ServerId = (Integer.parseInt(userInfo.get("ServerId").toString())); + user.FromToken = (true); + + // 从缓存获取用户信息 + Object cacheUser = getUserFromCache(user); + if (cacheUser != null && cacheUser instanceof LoginUserInfo) { + user = (LoginUserInfo) cacheUser; + } + + user.Token = (token); + return clazz.cast(user); + } + } + return null; + } catch (ExpiredJwtException e) { + log.debug(String.valueOf("Token已过期")); + } catch (JwtException e) { + log.debug(String.valueOf("Token签名无效")); + } catch (Exception e) { + return null; + } + return null; + } + + /** + * 验证令牌过期时间 + */ + public static boolean validDate(Map userInfo) { + if (userInfo == null || !userInfo.containsKey("exp")) { + return false; + } + + long expSeconds = Long.parseLong(userInfo.get("exp").toString()); + Instant expInstant = Instant.ofEpochSecond(expSeconds); + return expInstant.isAfter(Instant.now()); + } + + /** + * 刷新令牌 + */ + public static String refreshToken(LoginUserInfo user, String token, int exp) { + @SuppressWarnings("unchecked") + Map userInfo = (Map) getDictFromCache(token); + + if (userInfo != null && userInfo.containsKey("exp")) { + long expSeconds = Long.parseLong(userInfo.get("exp").toString()); + Instant expInstant = Instant.ofEpochSecond(expSeconds); + Instant now = Instant.now(); + + // 检查是否需要刷新(已过期或即将在10分钟内过期) + if (expInstant.isBefore(now) || + expInstant.isAfter(now) && + expInstant.minusSeconds(600).isBefore(now)) { + + LoginUserInfo tokenUser = new LoginUserInfo(); + tokenUser.UserId = (userInfo.get("UserId").toString()); + tokenUser.UserCode = (userInfo.get("UserCode").toString()); + tokenUser.UserName = (userInfo.get("UserName").toString()); + tokenUser.LoginOs = (userInfo.get("LoginOs").toString()); + + Object cacheUser = getUserFromCache(tokenUser); + LoginUserInfo actualUser = (cacheUser instanceof LoginUserInfo) ? (LoginUserInfo) cacheUser : user; + + if (actualUser != null) { + CacheUtil.remove(token); + return createToken(actualUser, exp); + } + } + } else if (user != null) { + CacheUtil.remove(token); + return createToken(user, exp); + } + + return null; + } + + /** + * 刷新令牌(默认3600秒过期) + */ + public String refreshToken(LoginUserInfo user, String token) { + return refreshToken(user, token, 3600); + } + + /** + * 将Claims转换为Map + */ + private static Map convertClaimsToMap(Claims claims) { + Map map = new HashMap<>(); + for (Map.Entry entry : claims.entrySet()) { + map.put(entry.getKey(), entry.getValue()); + } + return map; + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/LanguageUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/LanguageUtil.java new file mode 100644 index 0000000..9381977 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/LanguageUtil.java @@ -0,0 +1,147 @@ +package org.example.Utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +@Component +public class LanguageUtil { + private static final Logger log = LoggerFactory.getLogger(LanguageUtil.class); + + + + static final String DEFAULT_LANGUAGE = "Language_CN"; + + // 保留原有静态变量名(必须与其他代码使用的名称一致) + public static String Fail; + public static String InvalidParameter; + public static String InvalidSql; + public static String LoadFailed; + public static String NameOrPwdError; + public static String ServerError; + public static String WrongOldPwd; + public static String Success; + public static String NoAuthory; + public static String WrongInterfaceName; + public static String WrongMainCfg; + public static String DifferentPwd; + public static String FileExistCover; + public static String NoneSelected; + public static String ErrorReason; + public static String WrongPhoneNumber; + public static String WrongVCode; + public static String Wrong; + public static String NotNull; + public static String LimitOfTheNumber; + public static String IllegalOperation; + + @Autowired + private WebConfigUtil webConfigUtil; + + // 资源束缓存(静态成员) + private static ResourceBundle lrm; + // 临时存储当前语言(静态,供全局使用) + private static String currentLanguage; + + // Spring初始化后执行 + @PostConstruct + public void init() { + // 初始化当前语言(从WebConfigUtil获取默认值) + currentLanguage = webConfigUtil.getLanguage(); + if (currentLanguage == null || currentLanguage.trim().isEmpty()) { + currentLanguage = DEFAULT_LANGUAGE; + } + // 加载默认语言资源 + reloadResourceBundle(); + // 初始化静态变量(首次加载默认语言) + refreshStaticFields(); + } + + // 新增:动态设置语言(接收BaseImpl传递的lg参数) + public static void setCurrentLanguage(String lg) { + // 根据lg参数确定目标语言资源名 + String targetLanguage; + switch (lg.toLowerCase()) { + case "en": + targetLanguage = "Language_EN"; + break; + case "cn": + default: + targetLanguage = DEFAULT_LANGUAGE; + break; + } + + // 语言未变更则无需处理 + if (targetLanguage.equals(currentLanguage)) { + return; + } + + // 更新语言并重新加载资源 + currentLanguage = targetLanguage; + reloadResourceBundle(); + // 刷新静态变量值(关键:保证其他代码使用的静态变量是最新语言) + refreshStaticFields(); + } + + // 新增:重新加载资源束 + private static void reloadResourceBundle() { + try { + // 资源文件包路径(请根据实际项目结构修改,确保与LanguageCN.class同包) + String namespace = LanguageCN.class.getPackage().getName(); + String baseName = namespace + "." + currentLanguage; + + // 重新加载资源文件(覆盖原有缓存) + lrm = ResourceBundle.getBundle(baseName); + log.debug(String.valueOf("语言资源加载成功:" + baseName)); + } catch (Exception e) { + // 加载失败时fallback到默认语言 + String defaultBaseName = LanguageCN.class.getPackage().getName() + "." + DEFAULT_LANGUAGE; + lrm = ResourceBundle.getBundle(defaultBaseName); + log.debug(String.valueOf("语言资源加载失败,使用默认:" + defaultBaseName)); + log.error("Exception caught", e); + } + } + + // 新增:刷新静态变量(保证其他代码访问的是最新语言文案) + private static void refreshStaticFields() { + Fail = GetString("Fail"); + InvalidParameter = GetString("InvalidParameter"); + InvalidSql = GetString("InvalidSql"); + LoadFailed = GetString("LoadFailed"); + NameOrPwdError = GetString("NameOrPwdError"); + ServerError = GetString("ServerError"); + WrongOldPwd = GetString("WrongOldPwd"); + Success = GetString("Success"); + NoAuthory = GetString("NoAuthory"); + WrongInterfaceName = GetString("WrongInterfaceName"); + WrongMainCfg = GetString("WrongMainCfg"); + DifferentPwd = GetString("DifferentPwd"); + FileExistCover = GetString("FileExistCover"); + NoneSelected = GetString("NoneSelected"); + ErrorReason = GetString("ErrorReason"); + WrongPhoneNumber = GetString("WrongPhoneNumber"); + WrongVCode = GetString("WrongVCode"); + Wrong = GetString("Wrong"); + NotNull = GetString("NotNull"); + LimitOfTheNumber = GetString("LimitOfTheNumber"); + IllegalOperation = GetString("IllegalOperation"); + } + + // 原有方法:获取多语言文本 + public static String GetString(String key) { + try { + return lrm.getString(key); + } catch (MissingResourceException e) { + return key; // 找不到时返回key,方便排查 + } + } +} + +// 保留原有辅助类(用于获取资源文件包路径) +class LanguageCN {} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/MD5Util.java b/WebErp/weberp/src/main/java/org/example/Utils/MD5Util.java new file mode 100644 index 0000000..36beb08 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/MD5Util.java @@ -0,0 +1,21 @@ +package org.example.Utils; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class MD5Util { + + public static String encrypt(String input) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] messageDigest = md.digest(input.getBytes()); + StringBuilder hexString = new StringBuilder(); + for (byte b : messageDigest) { + hexString.append(String.format("%02x", b)); + } + return hexString.toString(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Utils/NativeExtensionUtils.java b/WebErp/weberp/src/main/java/org/example/Utils/NativeExtensionUtils.java new file mode 100644 index 0000000..c0820c0 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/NativeExtensionUtils.java @@ -0,0 +1,697 @@ +package org.example.Utils; + +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.regex.Pattern; + +public class NativeExtensionUtils { + private NativeExtensionUtils() { + } // 私有构造,防止实例化 + + private static final Pattern NON_DIGIT_PATTERN = Pattern.compile("[^\\d\\.\\-]"); + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + /** + * 将对象转换为布尔值 + * 支持的类型包括:Boolean、String、Number、Character、null + * 其他类型将返回默认值 + */ + public static boolean toBoolean(Object value) { + return toBoolean(value, false); + } + + public static String dmToBoolean(String input) { + // 1. 处理空值/空字符串 + if (input == null || input.trim().isEmpty()) { + return ""; + } + + // 2. 统一格式化:去首尾空格 + 转小写(兼容大小写、多余空格) + String normalizedInput = input.trim().toLowerCase(); + + // 3. 精准匹配目标字符串:f 或 false + if ("f".equals(normalizedInput) || "false".equals(normalizedInput)) { + return "0"; + } + if ("t".equals(normalizedInput) || "ture".equals(normalizedInput)) { + return "1"; + } + // 4. 非目标字符串,返回格式化后的原内容(也可按需改为返回原输入/其他值) + return normalizedInput; + } + + /** + * 将对象转换为布尔值,允许指定默认值 + * 支持的类型包括:Boolean、String、Number、Character、null + */ + public static boolean toBoolean(Object value, boolean defaultValue) { + if (value == null) { + return defaultValue; + } + + if (value instanceof Boolean) { + return (Boolean) value; + } + + if (value instanceof String) { + String str = ((String) value).trim().toLowerCase(); + if (str.isEmpty()) { + return defaultValue; + } + if ("true".equals(str) || "yes".equals(str) || "on".equals(str) || "1".equals(str)) { + return true; + } + if ("false".equals(str) || "no".equals(str) || "off".equals(str) || "0".equals(str)) { + return false; + } + return defaultValue; + } + + if (value instanceof Number) { + double num = ((Number) value).doubleValue(); + return num != 0; + } + + if (value instanceof Character) { + char c = (Character) value; + return c != '0' && c != '\0'; + } + + return defaultValue; + } + + /** + * 安全地将字符串转换为int,处理null和空字符串 + * + * @param str 输入字符串 + * @return 转换后的int值,若输入为null或格式错误则返回0 + */ + public static int parseInt(String str) { + return parseInt(str, 0); + } + + /** + * 安全地将字符串转换为int,处理null和空字符串,并支持自定义默认值 + * + * @param str 输入字符串 + * @param defaultValue 转换失败时的默认值 + * @return 转换后的int值,若失败则返回默认值 + */ + public static int parseInt(String str, int defaultValue) { + if (str == null) { + return defaultValue; + } + try { + return Integer.parseInt(str.trim()); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + /** + * 检查权限(示例方法,根据C#中的PublicUtil.CheckPurview实现) + */ + public static boolean checkPurview(Object user, String purviewStr, String menuId) { + // 实现与C#中PublicUtil.CheckPurview相同的逻辑 + // 示例返回,需根据实际需求调整 + return true; + } + + public static int ToInt16(Object obj) { + return (short) ToDouble(obj); + } + + public static Integer ToInt32(Object obj) { + return (int) ToDouble(obj); + } + + public static long ToInt64(Object obj) { + return (long) ToDouble(obj); + } + + /** + * 将任意对象转换为double类型,功能与C#版本完全一致 + * + * @param obj 待转换的对象 + * @return 转换后的double值,失败则返回0 + */ + public static double ToDouble(Object obj) { + // 将对象转为字符串并去除首尾空格(与C# (obj + "").Trim()等效) + String val = Trim(obj == null ? "" : obj.toString()); + + // 空字符串处理 + if (isNullOrEmpty(val)) { + return 0; + } + + try { + // 检查是否包含非数字字符 + if (NON_DIGIT_PATTERN.matcher(val).find()) { + int oldLength = val.length(); + // 去除所有非数字字符 + String intStr = NON_DIGIT_PATTERN.matcher(val).replaceAll(""); + + // 处理后的字符串有效且长度小于原长度 + if (intStr.length() > 0 && intStr.length() < oldLength) { + return Double.parseDouble(intStr); + } + return 0; + } + + // 无无效字符,直接转换原始对象 + return Double.parseDouble(val); + } catch (Exception e) { + // 任何异常都返回0(与C# catch所有异常一致) + return 0; + } + } + + /** + * 将任意对象转换为BigDecimal(对应C#的Decimal),功能与C#版本完全一致 + * + * @param obj 待转换的对象 + * @return 转换后的BigDecimal值,失败则返回0 + */ + public static BigDecimal ToDecimal(Object obj) { + // 将对象转为字符串并去除首尾空格(与C# (obj + "").Trim()等效) + String val = (obj == null ? "" : obj.toString()).trim(); + + // 空字符串处理 + if (val.isEmpty()) { + return BigDecimal.ZERO; + } + + // 处理布尔值情况(C#中bool类型的特殊处理) + if (obj instanceof Boolean) { + return (Boolean) obj ? BigDecimal.ONE : BigDecimal.ZERO; + } + + // 处理字符串为"true"的情况(不区分大小写) + if (val.equalsIgnoreCase("true")) { + return BigDecimal.ONE; + } + + try { + // 检查是否包含非数字字符 + if (NON_DIGIT_PATTERN.matcher(val).find()) { + int oldLength = val.length(); + // 去除所有非数字字符 + String intStr = NON_DIGIT_PATTERN.matcher(val).replaceAll(""); + + // 处理后的字符串有效且长度小于原长度 + if (intStr.length() > 0 && intStr.length() < oldLength) { + return new BigDecimal(intStr); + } + return BigDecimal.ZERO; + } + + // 无无效字符,直接转换原始对象 + return new BigDecimal(val); + } catch (Exception e) { + // 任何异常都返回0(与C# catch所有异常一致) + return BigDecimal.ZERO; + } + } + + + private static class DateFormatHolder { + // 支持多种常见日期格式,提高解析成功率 + private static final SimpleDateFormat[] FORMATS = { + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US), + new SimpleDateFormat("yyyy-MM-dd", Locale.US), + new SimpleDateFormat("MM/dd/yyyy HH:mm:ss", Locale.US), + new SimpleDateFormat("MM/dd/yyyy", Locale.US), + new SimpleDateFormat("dd-MMM-yyyy", Locale.US), + new SimpleDateFormat("yyyyMMddHHmmss", Locale.US) + }; + } + + /** + * 将任意对象转换为Date,功能与C#的ToDateTime扩展方法完全一致 + * + * @param obj 待转换的对象 + * @return 转换后的Date,失败则返回最小日期值(1970-01-01 00:00:00) + */ + public static Date toDateTime(Object obj) { + // 处理空值情况 + String strValue = (obj == null) ? "" : obj.toString().trim(); + if (strValue.isEmpty()) { + return new Date(0); // 对应C#的DateTime.MinValue,使用Unix纪元起始点 + } + + try { + // 如果对象本身就是Date类型,直接返回 + if (obj instanceof Date) { + return (Date) obj; + } + + // 尝试解析字符串为日期,支持多种格式 + for (SimpleDateFormat format : DateFormatHolder.FORMATS) { + try { + return format.parse(strValue); + } catch (ParseException e) { + // 尝试下一种格式 + continue; + } + } + + // 所有格式都无法解析时返回最小日期 + return new Date(0); + } catch (Exception e) { + // 任何异常都返回最小日期 + return new Date(0); + } + } + + private static String getTrimmedString(Object obj) { + return obj == null ? "" : obj.toString().trim(); + } + + + // 核心 Trim 方法 + public static String Trim(String str, char... trimChars) { + if (str == null || str.isEmpty()) { + return str; + } + + if (trimChars == null || trimChars.length == 0) { + return TrimHelper(str, 2); // 无参数时修剪空白字符 + } + + return TrimHelper(str, trimChars, 2); // 有参数时修剪指定字符 + } + + public static String TrimStart(String str, char... trimChars) { + if (trimChars == null || trimChars.length == 0) { + return TrimHelper(str, 0); + } + + return TrimHelper(str, trimChars, 0); + } + + public static String TrimEnd(String str, char... trimChars) { + if (trimChars == null || trimChars.length == 0) { + return TrimHelper(str, 1); + } + + return TrimHelper(str, trimChars, 1); + } + + // 修剪空白字符的辅助方法 + private static String TrimHelper(String str, int trimType) { + int length = str.length(); + int start = 0; + int end = length - 1; + + // 处理头部修剪 + if (trimType != 1) { + for (start = 0; start < length; start++) { + char c = str.charAt(start); + if (!isWhiteSpace(c) && !isBOMWhitespace(c)) { + break; + } + } + } + + // 处理尾部修剪(注意:C# 代码中此处错误地使用了 this[i],应为 this[num]) + if (trimType != 0) { + for (end = length - 1; end >= start; end--) { + char c = str.charAt(end); + if (!isWhiteSpace(c) && !isBOMWhitespace(str.charAt(end))) { // 修正为 str.charAt(end) + break; + } + } + } + + return createTrimmedString(str, start, end); + } + + // 修剪指定字符的辅助方法 + private static String TrimHelper(String str, char[] trimChars, int trimType) { + int length = str.length(); + int start = 0; + int end = length - 1; + + // 处理头部修剪 + if (trimType != 1) { + for (start = 0; start < length; start++) { + char c = str.charAt(start); + if (!contains(trimChars, c)) { + break; + } + } + } + + // 处理尾部修剪 + if (trimType != 0) { + for (end = length - 1; end >= start; end--) { + char c = str.charAt(end); + if (!contains(trimChars, c)) { + break; + } + } + } + + return createTrimmedString(str, start, end); + } + + // 创建修剪后的字符串 + private static String createTrimmedString(String str, int start, int end) { + int length = end - start + 1; + if (length == str.length()) { + return str; // 无需修剪 + } + if (length <= 0) { + return ""; // 全修剪 + } + return internalSubString(str, start, length); + } + + // 内部子串截取(模拟 C# 的指针内存复制) + private static String internalSubString(String str, int startIndex, int length) { + return str.substring(startIndex, startIndex + length); + } + + // 判断字符是否为空白(完全复刻 C# 逻辑) + private static boolean isWhiteSpace(char c) { + if (isLatin1(c)) { + return isWhiteSpaceLatin1(c); + } + return Character.isWhitespace(c); + } + + // 判断是否为 Latin-1 字符 + private static boolean isLatin1(char c) { + return c <= 0xFF; + } + + // 判断 Latin-1 字符是否为空白(完全复刻 C# 逻辑) + private static boolean isWhiteSpaceLatin1(char c) { + switch (c) { + case ' ': + case '\t': + case '\n': + case '\u000B': + case '\f': + case '\r': + case '\u0085': // 下一行字符 (NEL) + case '\u00A0': // 非中断空格 + return true; + default: + return false; + } + } + + // 判断字符是否为 BOM 空白(完全复刻 C# 逻辑) + private static boolean isBOMWhitespace(char c) { + return false; // C# 中直接返回 false + } + + // 判断字符是否在数组中 + private static boolean contains(char[] array, char c) { + for (char ch : array) { + if (ch == c) { + return true; + } + } + return false; + } + + /** + * 从 Map 中获取指定键的值并转换为字符串,支持默认值 + * + * @param map 源 Map + * @param key 键名 + * @param defaultValue 默认值 + * @return 转换后的字符串值,如果键不存在或值为 null 则返回默认值 + */ + public static String getStringValue(Map map, String key, String defaultValue) { + if (map == null || key == null) { + return defaultValue; + } + + Object value = map.get(key); + if (value == null) { + return defaultValue; + } + + return value.toString(); + } + + /** + * 从 Map 中获取指定键的值并转换为字符串,默认值为 null + * + * @param map 源 Map + * @param key 键名 + * @return 转换后的字符串值,如果键不存在或值为 null 则返回 null + */ + public static String getStringValue(Map map, String key) { + return getStringValue(map, key, null); + } + + /** + * 从 Map 中获取指定键的值并转换为整数,支持默认值 + * + * @param map 源 Map + * @param key 键名 + * @param defaultValue 默认值 + * @return 转换后的整数值,如果键不存在、值为 null 或无法转换为整数则返回默认值 + */ + public static int getIntValue(Map map, String key, int defaultValue) { + if (map == null || key == null) { + return defaultValue; + } + + Object value = map.get(key); + if (value == null) { + return defaultValue; + } + + try { + if (value instanceof Number) { + return ((Number) value).intValue(); + } else { + return Integer.parseInt(value.toString()); + } + } catch (NumberFormatException e) { + return defaultValue; + } + } + + /** + * 将SQL Server的xtype转换为Java类型 + * + * @param xtype SQL Server类型代码 + * @return 对应的Java类型 + */ + public static Type SqlxtypeToProType(int xtype) { + switch (xtype) { + // 字符串类型 + case 34: // image + case 35: // text + case 99: // ntext + case 167: // varchar + case 173: // binary + case 239: // nchar + case 231: // nvarchar + case 165: // varbinary + case 175: // char + case 36: // uniqueidentifier + return String.class; + + // 日期时间类型 + case 40: // date + case 41: // time + case 42: // datetime2 + case 58: // smalldatetime + case 61: // datetime + case 189: // timestamp + case 43: // datetimeoffset + return Date.class; + + // 整数类型 + case 48: // tinyint + return Byte.class; + case 52: // smallint + return Short.class; + case 56: // int + return Integer.class; + case 127: // bigint + return Long.class; + + // 浮点类型 + case 59: // real + return Float.class; + case 62: // float + return Double.class; + + // 十进制类型 + case 106: // decimal + case 108: // numeric + return BigDecimal.class; + + // 货币类型 + case 122: // smallmoney + case 60: // money + return Double.class; + + // 布尔类型 + case 104: // bit + return Boolean.class; + + default: + return Object.class; + } + } + + /** + * 判断对象是否为null或空字符串 + */ + public static Boolean isNullOrEmpty(Object obj) { + return obj == null || (obj instanceof String && ((String) obj).isEmpty()); + } + + // ------------------------------ + // 辅助方法:复刻 C# 的 TrimStart(字符) 逻辑 + // ------------------------------ + public static String trimStart(String str, char trimChar) { + if (str == null || str.isEmpty()) { + return str; + } + int startIndex = 0; + // 从开头查找,直到找到非 trimChar 的字符 + while (startIndex < str.length() && str.charAt(startIndex) == trimChar) { + startIndex++; + } + return str.substring(startIndex); + } + + // ------------------------------ + // 辅助方法:复刻 C# 的 TrimEnd(字符) 逻辑 + // ------------------------------ + public static String trimEnd(String str, char trimChar) { + if (str == null || str.isEmpty()) { + return str; + } + int endIndex = str.length() - 1; + // 从结尾查找,直到找到非 trimChar 的字符 + while (endIndex >= 0 && str.charAt(endIndex) == trimChar) { + endIndex--; + } + return str.substring(0, endIndex + 1); + } + + /** + * 数组去重(对应C#的RemoveSameObj) + * + * @param arr 原始字符串数组 + * @return 去重后的列表(保留顺序) + */ + public static List removeSameObj(String[] arr) { + if (arr == null || arr.length == 0) { + return new ArrayList<>(); + } + Set set = new LinkedHashSet<>(); // 保留插入顺序的去重 + for (String s : arr) { + if (s != null && !s.trim().isEmpty()) { + set.add(s.trim()); + } + } + return new ArrayList<>(set); + } + + /** + * 列表拼接为字符串(对应C#的SJoin) + * + * @param list 字符串列表 + * @param separator 分隔符(如",") + * @return 拼接后的字符串 + */ + public static String sJoin(List list, String separator) { + if (list == null || list.isEmpty()) { + return ""; + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < list.size(); i++) { + sb.append(list.get(i)); + if (i < list.size() - 1) { + sb.append(separator); + } + } + return sb.toString(); + } + + /** + * 安全转换为int(对应C# int.TryParse) + * + * @param value 原始值(字符串/数字) + * @param defaultValue 默认值 + * @return 转换后的int值 + */ + public static int parseIntSafely(Object value, int defaultValue) { + if (value == null) { + return defaultValue; + } + try { + return Integer.parseInt(value.toString().trim()); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + /** + * 安全获取数据表列值(对应C# layoutrow.Table.Columns.Contains + layoutrow["字段名"]) + * + * @param row 行数据(对应DataRow) + * @param columnName 列名 + * @return 列值字符串(空值返回"") + */ + public static String getTableColumnValue(Map row, String columnName) { + if (row == null || !row.containsKey(columnName)) { + return ""; + } + return Objects.toString(row.get(columnName), ""); + } + + /** + * 获取数据源列名列表(对应C# DataTable.Columns) + * + * @param dataSource 数据源 + * @return 列名列表 + */ + public static List getColumnNames(List> dataSource) { + List columnNames = new ArrayList<>(); + if (dataSource == null || dataSource.isEmpty()) { + return columnNames; + } + // 取第一行的所有键作为列名(假设所有行列名一致) + Map firstRow = dataSource.get(0); + columnNames.addAll(firstRow.keySet()); + return columnNames; + } + + /** + * 字符串右侧补字符(对应C# String.PadRight) + * + * @param str 原字符串 + * @param length 目标长度 + * @param padChar 补全字符 + * @return 补全后的字符串 + */ + public static String padRight(String str, int length, char padChar) { + if (str == null) { + str = ""; + } + if (str.length() >= length) { + return str; + } + StringBuilder sb = new StringBuilder(str); + while (sb.length() < length) { + sb.append(padChar); + } + return sb.toString(); + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/OfficeUtil/ToolsHandler.java b/WebErp/weberp/src/main/java/org/example/Utils/OfficeUtil/ToolsHandler.java new file mode 100644 index 0000000..7394e13 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/OfficeUtil/ToolsHandler.java @@ -0,0 +1,1058 @@ +package org.example.Utils.OfficeUtil; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.ServletContext; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.example.Api.LoggerHandler; +import org.example.Api.OptBaseHandler; +import org.example.Entity.Attributes.RequestCheck; +import org.example.Entity.Control.Com.SysPoPupMenuBtn; +import org.example.Impl.BaseImpl; +import org.example.Impl.ModuleImpl; +import org.example.Office.CreateWordUtil; +import org.example.Office.OfficeUtil; +import org.example.Utils.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.*; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.example.Utils.NativeExtensionUtils.*; + +@RestController +@RequestMapping("/Api/ToolsHandler") +public class ToolsHandler extends OptBaseHandler { + private static final Logger log = LoggerFactory.getLogger(ToolsHandler.class); + + @RequestMapping(value = "/**", method = {RequestMethod.GET, RequestMethod.POST}) + public void handleRequest(HttpServletRequest Request, HttpServletResponse response) throws Exception { + // 调用 BaseHandler 的 processRequest 处理逻辑 + super.processRequest(Request); + } + + @Autowired + private ModuleImpl _moduleImpl; + @Autowired + private JdbcTemplate jdbcTemplate; + @Autowired + private DbOperator dbOperator; + @Qualifier("baseImpl") + @Autowired + private BaseImpl bImpl; + @Autowired + private LoggerHandler loggerHandler; + @Autowired + private ObjectMapper objectMapper; + + protected ModuleImpl getModuleImpl() { + // 核心延迟初始化逻辑:若_moduleImpl为null则创建实例并设置属性 + if (_moduleImpl == null) { + _moduleImpl = new ModuleImpl(jdbcTemplate); + // 原C#中注释的UserSessionName赋值,保留作为参考 + // _moduleImpl.setUserSessionName(getUserSessionName()); + _moduleImpl.setDbOperator(dbOperator); // 假设ModuleImpl有setDbOperator方法 + } + return _moduleImpl; + } + + protected void setModuleImpl(ModuleImpl value) { + _moduleImpl = value; + } + + private String _discopy = "auto"; + + @RequestCheck(CheckLogin = false, WriteRespose = false, CheckParams = "url") + public void ViewDoc() { + // string inputUrl = bImpl.Request("url").TrimStart('/'); + //是否允许在浏览器复制 + int discopy = ToInt32(bImpl.Request("discopy")); + String url = bImpl.Request("url"); + String pwd = bImpl.Request("pwd"); + boolean createnew = toBoolean(bImpl.Request("n", "0")); + ViewFile(url, discopy, createnew, true, false, pwd); + } + + private String ViewFile(String url, int discopy, boolean createnew) { + return ViewFile(url, discopy, createnew, true, false, ""); + } + + private String ViewFile(String _url, int discopy, boolean createNew, boolean redict, boolean toPdf, String password) { + String inputUrl = TrimStart(_url, '/'); + String attcPath = getAttcPath(); + String _discopy = ""; // 初始化_copy变量 + if (discopy == 1) { + _discopy = "none"; + } + String url = Trim(inputUrl, ','), webUrl = inputUrl, hpath = ""; + + // URL解码处理(若包含%2开头的编码字符) + if (url.contains("%2")) { + url = URLDecoder.decode(url, StandardCharsets.UTF_8); + } + + // 处理相对路径(非绝对路径、非HTTP链接) + if (!url.contains(":/") && !url.contains(":\\") && !url.regionMatches(true, 0, "http", 0, 4)) { + String fileVPath = WebConfigUtil.getFileVPath(); + boolean startsWithFileVPath = url.regionMatches(true, 0, fileVPath, 0, fileVPath.length()); + boolean containsFileVPath = url.toLowerCase().contains(fileVPath.toLowerCase()); + + if (!startsWithFileVPath && !containsFileVPath) { + url = "/" + fileVPath + "/" + url; + } else { + url = "/" + url; + } + inputUrl = url; + } + + // 处理HTTP链接路径 + if (url.contains("http")) { + String fileVPath = WebConfigUtil.getFileVPath(); + int vIndex = indexOfIgnoreCase(url, fileVPath); + + if (vIndex > -1) { + attcPath = getModuleImpl().GetAttcPathByOAUrl(url.substring(0, vIndex - 1)); + String subUrl = url.substring(vIndex - 1).replaceFirst("^/", ""); + url = "/" + subUrl; + String encodedUrl = URLEncoder.encode(subUrl, StandardCharsets.UTF_8).replace("%2F", "/"); + webUrl = inputUrl.substring(0, vIndex - 1) + "/" + encodedUrl; + } + } else if (indexOfIgnoreCase(url, WebConfigUtil.getFileVPath()) > -1) { + String fileVPath = WebConfigUtil.getFileVPath(); + int vIndex = indexOfIgnoreCase(url, fileVPath); + int startIndex = Math.max(0, vIndex - 1); + + String subUrl = url.substring(startIndex).replaceFirst("^/", ""); + String encodedUrl = URLEncoder.encode(subUrl, StandardCharsets.UTF_8); + webUrl = "/" + encodedUrl; + } + + // 补全附件路径(为空则使用默认路径) + attcPath = (attcPath == null || attcPath.isEmpty()) ? getAttcPath() : attcPath; + String urlpath = url; + + // 转换为绝对路径 + if (!url.contains(":/") && !url.contains(":\\")) { + try { + StringBuilder vpath = new StringBuilder(); + String[] pathResult = FileUtil.toAbsPath(url, attcPath, vpath); + urlpath = pathResult[0]; + + // 移除路径中的\Api(适配Windows路径) + if (urlpath.contains("\\Api")) { + urlpath = urlpath.replace("\\Api", ""); + } + } catch (Exception e) { + if (Response != null) { + try { + ServletContext servletContext = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getServletContext(); + String mapPath = servletContext.getRealPath("/"); + String errorMsg = e.getMessage() + "\n" + mapPath + url; + Response.getWriter().print(errorMsg); + response.setMsg(errorMsg); + log.debug(String.format("异常输出失败:%s", errorMsg)); + } catch (IOException ioException) { + log.debug(String.format("异常输出失败:%s", ioException.getMessage())); + throw new RuntimeException("异常输出失败:" + ioException.getMessage(), ioException); + + } + } + return ""; + } + } +// urlpath = FileUtil.ensureUtf8String(urlpath); +// attcPath = FileUtil.ensureUtf8String(attcPath); + // 检查文件是否存在 + File file = new File(urlpath); + if (file.exists()) { + // 验证文件权限 + if (!FileUtil.checkFileAuthory(urlpath, attcPath)) { + try { + response.setMsg("权限验证失败:未在附件目录内找到文件!"); + Response.getWriter().print("权限验证失败:未在附件目录内找到文件!"); + } catch (IOException e) { + log.debug(String.format("权限验证失败:%s", e.getMessage())); + throw new RuntimeException("权限验证失败:" + e.getMessage(), e); + } + return ""; + } + + // 临时文件复制(数据库附件场景) + String filePath = WebConfigUtil.getFilePath(); + boolean useDbAttc = WebConfigUtil.get("useDbAttc").equalsIgnoreCase("true"); + if (!urlpath.startsWith(filePath) && useDbAttc) { + File info = new File(urlpath); + String newDir = filePath + File.separator + "temp"; + String newFilePath = newDir + File.separator + info.getName(); + + // 创建临时目录 + File newDirFile = new File(newDir); +// if (!newDirFile.exists()) { +// newDirFile.mkdirs(); +// } + try { + // 强制创建目录(包括父目录),并设置权限 + if (!newDirFile.exists()) { + newDirFile.mkdirs(); // 替代 Files.createDirectories,兼容性更好 + newDirFile.setWritable(true, false); // 设为可写 + newDirFile.setReadable(true, false); // 设为可读 + newDirFile.setExecutable(true, false); + } + // 检查权限 + if (!newDirFile.canWrite()) { + log.debug(String.format("newDirFile目录无写入权限:%s", newDirFile)); + throw new RuntimeException("目录无写入权限:" + newDirFile); + } + } catch (Exception e) { + log.debug(String.format("newDirFile创建目录失败:%s", newDirFile)); + throw new RuntimeException("创建目录失败:" + newDirFile, e); + } + // 3分钟后删除临时文件 + File finalNewFilePath = new File(newFilePath); + if (!finalNewFilePath.exists()) { + new Timer().schedule(new TimerTask() { + @Override + public void run() { + if (finalNewFilePath.exists()) { + finalNewFilePath.delete(); + } + } + }, 3 * 60 * 1000); + } + + // 复制文件(覆盖已存在) + try { +// Files.copy(Paths.get(urlpath), Paths.get(newFilePath), java.nio.file.StandardCopyOption.REPLACE_EXISTING); + Files.copy(Paths.get(urlpath), Paths.get(newFilePath), java.nio.file.StandardCopyOption.REPLACE_EXISTING); +// File urlF = new File(urlpath); +// File newUrlF = new File(newFilePath); + + } catch (IOException e) { + log.debug(String.format("文件复制失败:%s", e.getMessage())); + throw new RuntimeException("文件复制失败:" + e.getMessage(), e); + } + urlpath = newFilePath; + + // 更新webUrl + StringBuilder webUrlBuilder = new StringBuilder(); + FileUtil.toAbsPath(urlpath, attcPath, webUrlBuilder); + webUrl = webUrlBuilder.toString(); + } + + try { + File info = new File(urlpath); + String htmlPath = "", pdfViewHtml = ""; + String viewType = WebConfigUtil.get("AttcViewType").toLowerCase(); + + // 获取预览文件路径 + StringBuilder htmlPathBuilder = new StringBuilder(); + hpath = WebConfigUtil.getViewDocHtmlPath(info.getAbsolutePath(), htmlPathBuilder); + htmlPath = htmlPathBuilder.toString(); + + // 生成新文件名(移除扩展名) + String newFileName = hpath.split("/")[hpath.split("/").length - 1]; + newFileName = newFileName.replace(getFileExtension(newFileName), ""); + + boolean viewTypeIsPdf = !"html".equals(viewType) && !"img".equals(viewType); + String docFullPath = hpath.replace(htmlPath.split("/")[htmlPath.split("/").length - 1], ""); + + // 创建文档目录 + File docFullPathDir = new File(docFullPath); + try { + // 强制创建目录(包括父目录),并设置权限 + if (!docFullPathDir.exists()) { + docFullPathDir.mkdirs(); // 替代 Files.createDirectories,兼容性更好 + docFullPathDir.setWritable(true, false); // 设为可写 + docFullPathDir.setReadable(true, false); // 设为可读 + docFullPathDir.setExecutable(true, false); + } + // 检查权限 + if (!docFullPathDir.canWrite()) { + log.debug(String.format("docFullPathDir目录无写入权限:%s", docFullPathDir)); + throw new RuntimeException("目录无写入权限:" + docFullPathDir); + } + } catch (Exception e) { + log.debug(String.format("docFullPathDir创建目录失败:%s", docFullPathDir)); + throw new RuntimeException("创建目录失败:" + docFullPathDir, e); + } + + // 强制创建新文件(删除旧文件) + File hpathFile = new File(hpath); + if (createNew && hpathFile.exists()) { + hpathFile.delete(); + } + String fileExtension = Trim(getFileExtension(info.getName()).toLowerCase().trim(), '.'); + ; + if (viewTypeIsPdf && !OfficeUtil.CheckOfficPwd(urlpath, fileExtension, password)) { + String msg = ""; + if (!isNullOrEmpty(password)) { + msg = "密码错误!"; + } + //Response.redirect($"/pages/app/app.html?xtype=classic.Ywp.view.OfficPwd&serverId={user.ServerId}&dllcoid=1&err={msg}&src={HttpUtility.UrlEncode(url)}", false); + // 1. 拼接 URL 参数(严格还原 C# 字符串插值逻辑) + int serverId = getUser().getServerId(); // 假设 User 类有 getServerId() 方法 + String encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8); // 对应 C# HttpUtility.UrlEncode + // 2. 构造完整重定向地址(与 C# 路径完全一致) + String redirectUrl = String.format( + "/pages/app/app.html?xtype=classic.Ywp.view.OfficPwd&serverId=%d&dllcoid=1&err=%s&src=%s", + serverId, + msg, // C# 未编码 err 参数,此处保持一致(若需编码可添加 URLEncoder.encode(msg, "UTF-8")) + encodedUrl + ); + // 3. 执行重定向(还原 C# Response.Redirect(Url, false) 逻辑) + Response.sendRedirect(redirectUrl); + return ""; + } + // 已存在预览文件:直接重定向 + if (hpathFile.exists()) { + if (viewTypeIsPdf) { + pdfViewHtml = ToPdfViewPath(htmlPath, discopy); + getResponse().sendRedirect(pdfViewHtml); + return pdfViewHtml; + } else { + htmlPath = FileUtil.urlEncode(htmlPath, false); + String timestamp = String.valueOf(System.currentTimeMillis() % 1000); + getResponse().sendRedirect(htmlPath + "?t=" + timestamp); + } + } else { + // 按文件扩展名处理 + switch (fileExtension) { + case "doc": + case "docx": + try { + switch (viewType) { + case "html": + OfficeUtil.SaveToHtml(urlpath, hpath, _discopy); + break; + case "img": + OfficeUtil.saveToImgByAspose(urlpath, hpath, fileExtension, toPdf); + break; + default: + case "pdf": + OfficeUtil.saveToPdfByAspose(urlpath, hpath, "doc", toPdf, password); + pdfViewHtml = ToPdfViewPath(htmlPath, discopy); + getResponse().sendRedirect(pdfViewHtml); + return pdfViewHtml; + } + } catch (Exception e) { + OfficeUtil.SaveToImg(urlpath, hpath, fileExtension, toPdf); + } + break; + + case "xlsx": + case "xls": + switch (viewType) { + case "html": + OfficeUtil.SaveExcelToHtml(urlpath, hpath, htmlPath, _discopy); + break; + case "img": + OfficeUtil.SaveToImg(urlpath, hpath, fileExtension); + break; + default: + case "pdf": + OfficeUtil.saveToPdfByAspose(urlpath, hpath, "xls", toPdf, password); + pdfViewHtml = ToPdfViewPath(htmlPath, discopy); + getResponse().sendRedirect(pdfViewHtml); + return pdfViewHtml; + } + break; + + case "pdf": + String timestamp = String.valueOf(System.currentTimeMillis() % 1000); + pdfViewHtml = String.format("/Resource/Scripts/Yw/packages/pdfjs/web/viewer.html?file=%s&t=%s&discopy=%s", + webUrl, timestamp, discopy); + getResponse().sendRedirect(pdfViewHtml); + return pdfViewHtml; + + case "ppt": + case "pptx": + try { + OfficeUtil.saveToPdfByAspose(urlpath, hpath, "pptx", toPdf, password); + } catch (Exception e) { + OfficeUtil.savePptToPdfBySprie(urlpath, hpath); + } + break; + + case "tif": + String tifDocPath = "", convertPath = "", timeDocStr = ""; + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); + timeDocStr = sdf.format(new Date()); + tifDocPath = docFullPath.replace("DocHtml", "TifConverted/" + timeDocStr); + + // 处理预览URL + String newWebUrl = webUrl.replace("fileRoot", "TifConverted/" + timeDocStr); + try { + newWebUrl = URLDecoder.decode(newWebUrl, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("TIF URL解码失败:" + e.getMessage(), e); + } + String[] newWebUrlSplits = newWebUrl.split("/"); + String lastNewWebUrlPart = newWebUrlSplits[newWebUrlSplits.length - 1]; + newWebUrl = newWebUrl.replace(lastNewWebUrlPart, "/" + newFileName + ".png").replace("#", ""); + + // 创建TIF转换目录 + File tifDocPathDir = new File(tifDocPath); + if (!tifDocPathDir.exists()) { + tifDocPathDir.mkdirs(); + } + + convertPath = tifDocPath + File.separator + newFileName + ".png"; + if (!newWebUrl.startsWith("/")) { + newWebUrl = "/" + newWebUrl; + } + + File convertPathFile = new File(convertPath); + if (convertPathFile.exists()) { + String redirectUrl = String.format("/pages/app/FileViewer.html?url=%s", newWebUrl); + getResponse().sendRedirect(redirectUrl); + return redirectUrl; + } else { + String tifBase64 = OfficeUtil.TifToBase64(urlpath); + OfficeUtil.base64ToPng(tifBase64, convertPath); + String redirectUrl = String.format("/pages/app/FileViewer.html?url=%s", newWebUrl); + getResponse().sendRedirect(redirectUrl); + + // 1小时后删除目录 + String finalTifDocPath = tifDocPath; + new Timer().schedule(new TimerTask() { + @Override + public void run() { + File tifDir = new File(finalTifDocPath); + if (tifDir.exists()) { + deleteDir(tifDir); + } + } + }, 3600000); + return redirectUrl; + } + + case "dwg": + case "dwf": + case "dxf": + String dwgDocPath = docFullPath.replace("DocHtml", "mxDwg"); + File dwgDocPathDir = new File(dwgDocPath); + if (!dwgDocPathDir.exists()) { + dwgDocPathDir.mkdirs(); + } + + // 处理DWG文件名 + String dwgName = info.getName().replace(getFileExtension(info.getName()), ""); + dwgName = dwgName.replace(".", "_"); + + // 转换路径 + String convertMacPath = dwgDocPath + File.separator + dwgName + ".mxweb"; + String[] convertMacPathSplits = convertMacPath.split(File.separator); + String firstSplitPart = convertMacPathSplits[0]; + String convertDwgPath = convertMacPath.replace(firstSplitPart, ""); + + File convertMacPathFile = new File(convertMacPath); + if (!convertMacPathFile.exists()) { + boolean convertOk = OfficeUtil.convertDwgToMxWeb(urlpath, dwgName, dwgDocPath); + if (!convertOk) { + String redirectUrl = String.format("/DwgWebDefault.aspx?url=%s", webUrl); + getResponse().sendRedirect(redirectUrl); + return redirectUrl; + } + } + + String dwgRedirectUrl = String.format("/pages/mxcad/index.html?url=%s", convertDwgPath); + getResponse().sendRedirect(dwgRedirectUrl); + return dwgRedirectUrl; + + case "txt": + Charset encoding = FileUtil.getFileEncoding(urlpath); + String text = Files.readString(Paths.get(urlpath), encoding); + Response.getWriter().print(text); + return ""; + + default: + HttpServletResponse response = getResponse(); + if (org.example.Utils.ImageUtil.isImg(url)) { + String redirectUrl = String.format("/pages/app/FileViewer.html?url=%s&discopy=%s", webUrl, discopy); + response.sendRedirect(redirectUrl); + return redirectUrl; + } else { + response.sendRedirect(webUrl); + return webUrl; + } + } + + // 需要重定向时处理HTML路径 + if (redict) { + htmlPath = FileUtil.urlEncode(htmlPath, false); + String timestamp = String.valueOf(System.currentTimeMillis() % 1000); + getResponse().sendRedirect(htmlPath + "?t=" + timestamp); + } + } + + // 返回带时间戳的预览路径 + String timestamp = String.valueOf(System.currentTimeMillis() % 1000); + return htmlPath + "?t=" + timestamp; + + } catch (Exception e) { + if (redict) { + String errorSuffix = e.getMessage().indexOf("无法将类型为“Spire.Doc.Documents.Paragraph”的对象强制转换为类型“Spire.Doc.Fields.ShapeObject”") > -1 + ? ",请修改文件中自定义形状转换为图片后重新上传试试!" + : ""; + // try { +// Response.getWriter().print(String.format("该文件不支持预览%s:%s", errorSuffix, e.getMessage())); + response.setMsg(String.format("该文件不支持预览%s:%s", errorSuffix, e.getMessage())); + // } catch (IOException ioException) { + // throw new RuntimeException("预览异常输出失败:" + ioException.getMessage(), ioException); + // } + } + LoggerHandler.error(this, e.getMessage(), e); + } + } else if (url.indexOf(",") > -1) { + try { + String redirectUrl = String.format("/pages/app/FileViewer.html?url=%s", _url); + getResponse().sendRedirect(redirectUrl); + return redirectUrl; + } catch (IOException e) { + throw new RuntimeException("多文件预览重定向失败:" + e.getMessage(), e); + } + } else if (redict) { + //try { + String tipMsg = String.format("未找到文件:%s", urlpath); + response.setMsg(tipMsg); +// getResponse().getWriter().print(tipMsg); + // } catch (IOException e) { + // throw new RuntimeException("文件不存在提示输出失败:" + e.getMessage(), e); + // } + } + return ""; + } + + private String ToPdfViewPath(String pdfPath, int discopy) { + try { + // 对pdf路径进行URL编码,对应C# FileUtil.UrlEncode + String encodedPdfPath = java.net.URLEncoder.encode(pdfPath, "UTF-8"); + // 获取当前时间的毫秒部分,对应C# DateTime.Now.Millisecond + long timestamp = System.currentTimeMillis() % 1000; + HttpServletRequest request = context; + String referer = request.getHeader("Referer"); + String frontendAddress = ""; + + if (referer != null && referer.contains("://")) { + // 假设 referer 为 "http://localhost:8081/Lserp_v8/index.html" + // 我们需要截取出 "http://localhost:8081" + + // 找到 "://" 之后第一个 "/" 的位置 + int index = referer.indexOf("/", referer.indexOf("://") + 3); + if (index != -1) { + frontendAddress = referer.substring(0, index); + } else { + frontendAddress = referer; + } + } + // 拼接路径字符串 + String pdfViewHtml = String.format("%s/Resource/Scripts/Yw/packages/pdfjs/web/viewer.html?file=%s&t=%d&discopy=%d", + frontendAddress, encodedPdfPath, timestamp, discopy); + return getResponse().encodeRedirectURL(pdfViewHtml); + } catch (java.io.UnsupportedEncodingException e) { + // 捕获编码异常(UTF-8为标准编码,实际很少触发) + throw new RuntimeException("PDF路径URL编码失败:" + e.getMessage(), e); + } + } + + @RequestCheck(CheckLogin = false, WriteRespose = false, CheckParams = "url") + public void LoadPage() { + String url = bImpl.Request("url"); + if (!NativeExtensionUtils.isNullOrEmpty(url)) { + boolean[] success = new boolean[1]; + String content = WebUtil.send(url, null, null, success, "GET"); + // 后续内容处理和响应输出逻辑不变 + try { + URL uri = new URL(url); + String scheme = uri.getProtocol(); + String authority = uri.getAuthority(); + String baseUrl = String.format("%s://%s/", scheme, authority); + + content = content.replace("_blank", "_self"); + content = content.replace("./", baseUrl); + + HttpServletResponse response = getResponse(); + response.reset(); + response.getWriter().print(content); + + } catch (MalformedURLException e) { + LoggerHandler.error(this, "URL解析失败: " + url, e); + } catch (IOException e) { + LoggerHandler.error(this, "响应内容输出失败", e); + } + } + } + + @RequestCheck(CheckLogin = true, CheckParams = "urls") + public void ZipFiles() { + // 1. 获取请求参数"urls"并分割(对应C# bImpl.Request("urls").Split(',')) + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + String urlsStr = attributes.getRequest().getParameter("urls"); + if (urlsStr == null || urlsStr.isEmpty()) { + response.setSuccess(false); + return; + } + String[] urls = urlsStr.split(","); + String[] fullUrls = new String[urls.length]; + + // 2. 处理每个URL,生成物理路径并检查权限 + // 获取应用物理路径(对应C# PhysicalApplicationPath) + ServletContext servletContext = attributes.getRequest().getServletContext(); + String physicalPath = servletContext.getRealPath("/"); + + for (int i = 0; i < urls.length; i++) { + String u; + try { + // URL解码(对应C# HttpUtility.UrlDecode) + u = URLDecoder.decode(urls[i], "UTF-8"); + } catch (UnsupportedEncodingException e) { + LoggerHandler.error(this, "URL解码失败: " + urls[i], e); + fullUrls[i] = ""; + continue; + } + + if (u == null || u.isEmpty()) { + fullUrls[i] = ""; + continue; + } + + // 拼接物理路径(对齐C#拼接逻辑) + String fullUrl; + if (u.startsWith("/")) { + fullUrl = physicalPath + u; + } else { + fullUrl = physicalPath + "/" + u; + } + // 处理路径分隔符(兼容Windows和Linux) + fullUrl = fullUrl.replace("/", File.separator).replace("\\", File.separator); + + // 检查文件权限(对应C# FileUtil.CheckFileAuthory) + if (!FileUtil.checkFileAuthory(fullUrl, getAttcPath())) { + fullUrls[i] = ""; + } else { + fullUrls[i] = fullUrl; + } + } + + // 3. 构建ZIP文件路径 + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddhhmmss"); + String timestamp = sdf.format(new Date()); + String zipVPath = String.format("/downLoadTemp/%s.zip", timestamp); // 相对路径 + String zipPath = WebConfigUtil.getFilePath() + File.separator + zipVPath; // 绝对路径(对应C#拼接逻辑) + // 处理路径中的重复分隔符 + zipPath = zipPath.replace(File.separator + File.separator, File.separator); + + // 4. 执行压缩(对应C# ZipUtil.GoZip) + // 拼接文件路径为分号分隔的字符串(对应string.Join(";", fullUrls)) + StringBuilder filePaths = new StringBuilder(); + for (String path : fullUrls) { + if (path != null && !path.isEmpty()) { + if (filePaths.length() > 0) { + filePaths.append(";"); + } + filePaths.append(path); + } + } + org.example.Utils.ZipUtil.GoZip(filePaths.toString(), zipPath, ""); // 调用Java版压缩工具 + + // 5. 10分钟后删除ZIP文件(对应C# DateTimeUtil.SetTimeOut) + final String finalZipPath = zipPath; // 局部变量需为final才能被内部类访问 + new Timer().schedule(new TimerTask() { + @Override + public void run() { + File zipFile = new File(finalZipPath); + if (zipFile.exists()) { + zipFile.delete(); + } + } + }, 10 * 60 * 1000); // 10分钟 = 600000毫秒 + + // 6. 设置响应数据(对应C# response.data和response.success) + response.setData(String.format("/%s/%s", WebConfigUtil.getFileVPath(), zipVPath)); + response.setSuccess(true); + } + + private static String OAUrl; + + public void saveToWord() { + int mid = ToInt32(bImpl.Request("mid")); + if (mid <= 0) return; + + // 1. 直接调用封装好的 bImpl.Request("data"),和C#调用完全一致 + String dataStr = bImpl.Request("data"); + +// 2. 用 Spring 原生 ObjectMapper 解析为 Hashtable(对应 C# JSON.Decode(..., typeof(Hashtable)) as Hashtable) + Hashtable record = null; + if (dataStr != null && !dataStr.isEmpty()) { + try { + // 核心解析:TypeReference 指定目标类型为 Hashtable,避免类型擦除 + record = objectMapper.readValue( + dataStr, + new TypeReference>() { + } + ); + } catch (Exception e) { + LoggerHandler.error(this, "JSON解析为Hashtable失败", e); + record = null; // 解析失败返回null,和 C# "as Hashtable" 安全转换行为完全一致 + } + } + +// 3. 调用 moduleImpl 方法(C# Pascal命名 → Java 驼峰命名,参数完全兼容) + SysPoPupMenuBtn menuBtn = getModuleImpl().GetContextMenuBtn(mid, record); + if (menuBtn != null && menuBtn.get_dllname().equals("autocreatword")) { + + // 1. 获取当前请求上下文(用于获取ServletContext,等价于C#的HttpContext.Current) + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + ServletContext servletContext = attributes.getRequest().getServletContext(); + +// 2. 构建tempPath(对应C# $"/{WebConfigUtil.FileVPath}/report/template") + String tempPath = String.format("/%s/report/template", WebConfigUtil.getFileVPath()); + +// 3. 构建tlp1和tlp2(对应C# Server.MapPath拼接路径) +// 注意:menuBtn.dllpar1在Java中需通过getter访问,即menuBtn.getDllpar1() + String tlp1Path = String.format("%s/%s.doc", tempPath, menuBtn.dllpar1); + String tlp1 = servletContext.getRealPath(tlp1Path); // 等价于Server.MapPath + + String tlp2Path = String.format("%s/%s.doc", tempPath, menuBtn.dllpar2); + String tlp2 = servletContext.getRealPath(tlp2Path); + +// 4. 构建webPath(对应C# $"/{WebConfigUtil.FileVPath}/report/{menuBtn.dllpar6}/{menuBtn.dllpar7}.doc") + String webPath = String.format( + "/%s/report/%s/%s.doc", + WebConfigUtil.getFileVPath(), + menuBtn.dllpar6, + menuBtn.dllpar7 + ); + +// 5. 构建savePath(对应C# Server.MapPath(webPath)) + String savePath = servletContext.getRealPath(webPath); + + // 5. 删除已存在的文件 + File saveFile = new File(savePath); + if (saveFile.exists()) { + saveFile.delete(); + } + + // 6. 执行SQL查询,获取数据集(对应C# 分割SQL并执行) + String[] masterSqls = menuBtn.dllpar4.replaceAll("^\\^+", "").replaceAll("\\^+$", "").split("\\^"); // 转义^避免正则匹配 + List>>> dataSets = new ArrayList<>(); + + for (String masterSql : masterSqls) { + if (masterSql == null || masterSql.isEmpty()) { + continue; + } + try { +// 这里的数据查询与jdbc不同,这里是List,且可能存在多个DataSet + List>> tableData = dbOperator.executeDataSet(masterSql); + + // 将当前 SQL 的结果添加到数据集列表(对应 C# dataSets.Add(...)) + dataSets.add(tableData); + + } catch (Exception e) { + // 记录异常(使用你项目中的日志工具) + LoggerHandler.error(this, "执行 SQL 失败: " + masterSql, e); + } + } + + // 7. 获取OAUrl(对应C# 三元运算符赋值逻辑) + if (OAUrl == null || OAUrl.isEmpty()) { + // 从数据库查询(对应 C# dbOperator.ExecuteScalar) + try { + // 执行单行单列查询:queryForObject 对应 ExecuteScalar + // 注意:SQL 中的 "top 1" 需根据数据库调整(MySQL 用 limit 1,SQL Server 保留 top 1) + String querySql = "select top 1 oaurl from p_systemtab"; // 假设是 MySQL + Object oaUrlObj = jdbcTemplate.queryForObject(querySql, Object.class); + + // 赋值给静态变量 OAUrl(转换为字符串,避免 null) + OAUrl = (oaUrlObj != null) ? oaUrlObj.toString() : ""; + } catch (Exception e) { + // 处理查询异常(如无数据、SQL错误等) + LoggerHandler.error(this, "查询OAUrl失败", e); + OAUrl = ""; // 异常时赋空值,避免后续空指针 + } + } + + // 8. 生成Word文件(对应C# AutoCreatWord.CreateWordUtil.Create) + StringBuilder errMsg = new StringBuilder(); // 模拟out参数 + boolean ok = CreateWordUtil.Create(tlp1, tlp2, menuBtn.dllpar3, dataSets, savePath, OAUrl, errMsg); + String viewPath = ""; + if (ok) { + // 9. 获取预览路径(调用已实现的ViewFile方法) + viewPath = ViewFile(savePath, 0, true, false, true, ""); + } else { + // 10. 设置错误消息 + String msg = (errMsg == null || errMsg.isEmpty()) ? "文件生成失败!" : errMsg.toString(); + response.setMsg(msg); + } + + // 11. 设置响应数据(用HashMap模拟C#匿名对象) + response.setSuccess(ok); + java.util.Map responseData = new java.util.HashMap<>(); + responseData.put("filePath", webPath); + responseData.put("pdfPath", webPath.replace(".doc", ".pdf")); + responseData.put("viewPath", viewPath); + response.setData(responseData); + } + } + + public void wordToHtmlTpl() { + // 1. 获取请求参数(复用已封装的bImpl.Request,与C#调用一致) + String fileName = bImpl.Request("name"); + // 2. 分割文件名获取模板名(对应fileName.Split('.')[0]) + String[] fileNameParts = fileName.split("\\."); // Java中.需转义 + String tplName = fileNameParts.length > 0 ? fileNameParts[0] : ""; + + String folder = bImpl.Request("folder", "wordTemplate"); // 带默认值 + folder = folder.trim(); // 先去首尾空格 + if (folder.endsWith("/")) { // 对应TrimEnd('/') + folder = folder.substring(0, folder.length() - 1); + } + + + // 3. 处理路径(复用已封装的FileUtil.ToAbsPath,用StringBuilder模拟out vpath) + StringBuilder vpathValue = new StringBuilder(); // 用StringBuilder替代String[],匹配方法参数类型 + String tplFolder = FileUtil.toAbsPath(folder, getAttcPath(), vpathValue)[0]; // 现在参数类型匹配 + + // 后续若需要获取vpath的值,通过toString()方法: + String vpath = vpathValue.toString(); // 等价于C#中out参数传出的vpath值 + // 4. 获取htmlFolder物理路径(对应Server.MapPath) + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + ServletContext servletContext = attributes.getRequest().getServletContext(); + String htmlFolderRelPath = "/" + WebConfigUtil.getFileVPath() + "/wordTemplate/htmlTemp/" + tplName; + String htmlFolder = servletContext.getRealPath(htmlFolderRelPath); + + // 5. 定义文件路径(对应C#的字符串插值) + String tplFile = tplFolder + File.separator + fileName; + String tplFile1 = tplFolder + File.separator + tplName + ".docx"; + String hpath = htmlFolder + File.separator + tplName + ".html"; + + // 6. 创建目录(对应Directory.CreateDirectory) + File tplFolderFile = new File(tplFolder); + if (!tplFolderFile.exists()) { + tplFolderFile.mkdirs(); // 递归创建目录 + } + File htmlFolderFile = new File(htmlFolder); + if (!htmlFolderFile.exists()) { + htmlFolderFile.mkdirs(); + } + + // 7. 处理模板文件路径(优先.doc,不存在则用.docx) + File tplFileObj = new File(tplFile); + if (!tplFileObj.exists()) { + tplFile = tplFile1; + tplFileObj = new File(tplFile); + } + + // 8. 删除已存在的HTML文件(对应File.Delete) + File hpathFile = new File(hpath); + if (hpathFile.exists()) { + hpathFile.delete(); + } + + if (tplFileObj.exists()) { + try { + // 9. 调用封装的OfficeUtil转换为HTML(对应SaveToHtmlByAspose) + String fileExt = fileNameParts.length > 1 ? fileNameParts[1] : ""; // 获取文件后缀 + OfficeUtil.saveToHtmlByAspose(tplFile, hpath, fileExt); + + // 10. 读取HTML内容(对应File.ReadAllText) + String content = Files.readString(hpathFile.toPath(), StandardCharsets.UTF_8); + + // 1. 编译第一个正则:匹配标签内的内容(对应C# Regex(@"(?is)(?<=]*?>).*?(?=\s*开头,然后捕获中间内容,直到]*?>(.*?)\\s*内的内容 + Pattern.CASE_INSENSITIVE | Pattern.DOTALL // 保持i和s模式 + ); + Matcher conMatcher = conmatch.matcher(content); + + + // 2. 编译第二个正则:匹配所有HTML标签(对应C# Regex(@"<[^>]+>")) + Pattern htmlReg = Pattern.compile("<[^>]+>"); + + // 3. 编译第三个正则:匹配{data.或{data1.等(对应C# Regex(@"{data\d?\.")) + // 说明:Java中{、.是特殊字符,需转义为\\{、\\. + Pattern dataReg = Pattern.compile("\\{data\\d?\\."); + // 提取捕获的内容(对应原逻辑) + if (conMatcher.find()) { + // group(1) 对应捕获组(.*?),即内的目标内容 + content = conMatcher.group(1); + } + + // 获取参数列表(复用已封装的PublicUtil.GetParamValue) + List pms = PublicUtil.getParamValue(content); + for (String pm : pms) { + // 替换参数中的HTML标签 + content = content.replace(pm, htmlReg.matcher(pm).replaceAll("")); + } + + // 13. 最终内容处理 + content = dataReg.matcher(content).replaceAll("\\{"); // 替换{data.为{ + content = content.replace(" ", ""); // 移除HTML非断空格 + + // 14. 设置响应(复用已封装的response) + response.setSuccess(true); + response.setData(content); + + } catch (Exception e) { + LoggerHandler.error(this, "Word转HTML处理失败", e); + response.setSuccess(false); + response.setData(""); + } + } else { + // 模板文件不存在 + response.setData(""); + } + } + + @RequestCheck(CheckLogin = false) + public void toEnUrl() { + try { + // 1. 获取请求参数"url"(复用已封装的bImpl.Request) + String url = bImpl.Request("url"); + + // 2. URL解码(对应C# HttpUtility.UrlDecode) + // 注意:Java需指定编码(UTF-8),并处理异常 + url = URLDecoder.decode(url, StandardCharsets.UTF_8.name()); + + // 3. 遍历所有请求参数(对应C# bImpl.GetAllRequest()的DictionaryEntry集合) + // 假设bImpl.getAllRequest()返回Map(键值对集合) + Map allRequest = bImpl.getAllRequest(); + + // 用StringBuilder高效拼接URL(避免频繁字符串创建) + StringBuilder urlBuilder = new StringBuilder(url); + + for (Map.Entry entry : allRequest.entrySet()) { + // 获取参数名并转为小写(对应C# ToLower()) + String key = entry.getKey().toLowerCase(); + + // 排除key为"url"、"method"、"action"的参数(不区分大小写) + if (!"url".equals(key) && !"method".equals(key) && !"action".equals(key)) { + // 拼接参数:&key=value(对应C# $"{url}&{kv.Key}={kv.Value}") + urlBuilder.append("&") + .append(entry.getKey()) // 保留原始key(非小写) + .append("=") + .append(entry.getValue()); // 参数值 + } + } + + // 4. 转换为最终URL(复用PublicUtil.ToEnUrl,Java用驼峰命名) + String resultUrl = PublicUtil.ToEnUrl(urlBuilder.toString()); + + // 5. 设置响应(对应C# response.data和success) + response.setData(resultUrl); + response.setSuccess(true); + + } catch (Exception e) { + // 处理异常(如URL解码失败) + LoggerHandler.error(this, "URL处理失败", e); + response.setSuccess(false); + response.setData(""); + } + } + + +// ------------------------------ 辅助方法 ------------------------------ + + /** + * 移除字符串开头的指定字符(对齐C# TrimStart) + */ + private String TrimStart(String str, char trimChar) { + if (str == null || str.isEmpty()) { + return str; + } + int index = 0; + while (index < str.length() && str.charAt(index) == trimChar) { + index++; + } + return str.substring(index); + } + + /** + * 移除字符串两端的指定字符(对齐C# Trim) + */ + private String Trim(String str, char trimChar) { + if (str == null || str.isEmpty()) { + return str; + } + int start = 0; + int end = str.length() - 1; + while (start <= end && str.charAt(start) == trimChar) { + start++; + } + while (end >= start && str.charAt(end) == trimChar) { + end--; + } + return str.substring(start, end + 1); + } + + + // 辅助方法:忽略大小写查找子串位置 + private static int indexOfIgnoreCase(String source, String target) { + if (source == null || target == null) return -1; + int targetLen = target.length(); + int sourceLen = source.length(); + if (targetLen == 0) return 0; + if (targetLen > sourceLen) return -1; + + for (int i = 0; i <= sourceLen - targetLen; i++) { + if (source.regionMatches(true, i, target, 0, targetLen)) { + return i; + } + } + return -1; + } + + /** + * 获取当前响应对象(Spring环境) + */ + private HttpServletResponse getResponse() { + return Response; + } + + /** + * 递归删除目录及文件 + */ + private void deleteDir(File dir) { + if (dir.isDirectory()) { + File[] files = dir.listFiles(); + if (files != null) { + for (File file : files) { + deleteDir(file); + } + } + } + dir.delete(); + } + + /** + * 获取文件扩展名(包含".") + */ + private String getFileExtension(String fileName) { + int lastDotIndex = fileName.lastIndexOf("."); + return lastDotIndex == -1 ? "" : fileName.substring(lastDotIndex); + } + + +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/PathUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/PathUtil.java new file mode 100644 index 0000000..184dee1 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/PathUtil.java @@ -0,0 +1,198 @@ +package org.example.Utils; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashSet; +import java.util.Set; + +public class PathUtil { + /** + * 拼接两个路径片段 + * @param path1 第一个路径 + * @param path2 第二个路径 + * @return 拼接后的路径 + * @throws NullPointerException 若任一参数为 null + * @throws IllegalArgumentException 若路径包含无效字符 + */ + public static String combine(String path1, String path2) { + // 校验参数非空 + if (path1 == null) { + throw new NullPointerException("path1"); + } + if (path2 == null) { + throw new NullPointerException("path2"); + } + + // 校验无效字符 + checkInvalidPathChars(path1); + checkInvalidPathChars(path2); + + // 执行拼接(无校验逻辑,依赖前置校验) + return combineNoChecks(path1, path2); + } + + /** + * 拼接三个路径片段 + * @param path1 第一个路径 + * @param path2 第二个路径 + * @param path3 第三个路径 + * @return 拼接后的路径 + * @throws NullPointerException 若任一参数为 null + * @throws IllegalArgumentException 若路径包含无效字符 + */ + public static String combine(String path1, String path2, String path3) { + // 校验参数非空 + if (path1 == null) { + throw new NullPointerException("path1"); + } + if (path2 == null) { + throw new NullPointerException("path2"); + } + if (path3 == null) { + throw new NullPointerException("path3"); + } + + // 校验无效字符 + checkInvalidPathChars(path1); + checkInvalidPathChars(path2); + checkInvalidPathChars(path3); + + // 嵌套调用双参数拼接 + return combineNoChecks(combineNoChecks(path1, path2), path3); + } + + /** + * 拼接四个路径片段 + * @param path1 第一个路径 + * @param path2 第二个路径 + * @param path3 第三个路径 + * @param path4 第四个路径 + * @return 拼接后的路径 + * @throws NullPointerException 若任一参数为 null + * @throws IllegalArgumentException 若路径包含无效字符 + */ + public static String combine(String path1, String path2, String path3, String path4) { + // 校验参数非空 + if (path1 == null) { + throw new NullPointerException("path1"); + } + if (path2 == null) { + throw new NullPointerException("path2"); + } + if (path3 == null) { + throw new NullPointerException("path3"); + } + if (path4 == null) { + throw new NullPointerException("path4"); + } + + // 校验无效字符 + checkInvalidPathChars(path1); + checkInvalidPathChars(path2); + checkInvalidPathChars(path3); + checkInvalidPathChars(path4); + + // 嵌套调用双参数拼接 + return combineNoChecks(combineNoChecks(combineNoChecks(path1, path2), path3), path4); + } + + /** + * 无校验的路径拼接(依赖外部前置校验) + * @param path1 第一个路径 + * @param path2 第二个路径 + * @return 拼接后的路径字符串 + */ + private static String combineNoChecks(String path1, String path2) { + Path combinedPath = Paths.get(path1, path2); + return combinedPath.toString(); + } + + /** + * 检查路径中是否包含无效字符 + * @param path 待检查的路径 + * @throws IllegalArgumentException 若包含无效字符 + */ +// 基础无效字符集(根据操作系统动态初始化) + private static final Set BASE_INVALID_CHARS; + // 额外无效字符集(用于严格检查模式) + private static final Set ADDITIONAL_INVALID_CHARS; + // 异常信息(对应C#的本地化资源字符串) + private static final String INVALID_PATH_CHARS_MSG = "路径中包含无效字符"; + + static { + // 判断当前操作系统类型 + boolean isWindows = System.getProperty("os.name").toLowerCase().contains("win"); + + // 初始化基础无效字符集 + BASE_INVALID_CHARS = new HashSet<>(); + + if (isWindows) { + // Windows系统基础无效字符:* ? : " < > | + String winBaseInvalid = "*?:\"<>|"; + for (char c : winBaseInvalid.toCharArray()) { + BASE_INVALID_CHARS.add(c); + } + } else { + // Unix/Linux/macOS系统基础无效字符:/ + BASE_INVALID_CHARS.add('/'); + } + + // 所有系统都禁止ASCII控制字符(0-31) + for (char c = 0; c <= 31; c++) { + BASE_INVALID_CHARS.add(c); + } + + // 初始化额外无效字符集(主要用于Windows系统) + ADDITIONAL_INVALID_CHARS = new HashSet<>(); + if (isWindows) { + // Windows系统额外检查的字符(例如管道符的特殊场景限制) + ADDITIONAL_INVALID_CHARS.add('|'); + } + } + + /** + * 检查路径是否包含无效字符(与C# CheckInvalidPathChars逻辑一致) + * @param path 待检查的路径 + * @param checkAdditional 是否启用额外检查 + * @throws NullPointerException 如果path为null + * @throws IllegalArgumentException 如果路径包含无效字符 + */ + public static void checkInvalidPathChars(String path, boolean checkAdditional) { + // 检查path是否为null + if (path == null) { + throw new NullPointerException("path"); + } + + // 检查是否包含非法字符 + if (hasIllegalCharacters(path, checkAdditional)) { + throw new IllegalArgumentException(INVALID_PATH_CHARS_MSG); + } + } + + /** + * 重载方法,默认不启用额外检查(对应C#的默认参数) + */ + public static void checkInvalidPathChars(String path) { + checkInvalidPathChars(path, false); + } + + /** + * 判断路径是否包含非法字符(模拟C# PathInternal.HasIllegalCharacters) + */ + private static boolean hasIllegalCharacters(String path, boolean checkAdditional) { + for (int i = 0; i < path.length(); i++) { + char c = path.charAt(i); + + // 检查基础无效字符 + if (BASE_INVALID_CHARS.contains(c)) { + return true; + } + + // 检查额外无效字符(如果启用) + if (checkAdditional && ADDITIONAL_INVALID_CHARS.contains(c)) { + return true; + } + } + return false; + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/PermissionsUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/PermissionsUtil.java new file mode 100644 index 0000000..6835c04 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/PermissionsUtil.java @@ -0,0 +1,4 @@ +package org.example.Utils; + +public class PermissionsUtil { +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/PmAnalyzer.java b/WebErp/weberp/src/main/java/org/example/Utils/PmAnalyzer.java new file mode 100644 index 0000000..de9fcaa --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/PmAnalyzer.java @@ -0,0 +1,326 @@ +package org.example.Utils; + +import org.example.Entity.System.LoginUserInfo; +import org.example.Enums.SystemTypeEnums; + +import java.util.*; +import java.util.regex.*; +import java.text.SimpleDateFormat; +import java.util.function.Function; +import java.util.function.Predicate; + + +class Analyzer { + String sourcestr; + String analyerstr; + private SystemTypeEnums.PmType pmtype = SystemTypeEnums.PmType.sql; + + // 正则表达式定义,注意Java的转义字符处理 + private static final Pattern noDotPmReg = Pattern.compile("\\[([%|_|,]){0,}([\\{])([^'|\\{|\\}|&|=|>|\\<| |\\[|\\]])+([\\}])([%|_|,]){0,}\\]", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + private static final Pattern DotPmReg = Pattern.compile("'([%|_|,]){0,}([\\{])([^'|\\{|\\}|&|=|>|\\<| ])+([\\}])([%|_|,]){0,}'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + private static final Pattern leftPmReg = Pattern.compile("\\{([^'\\{\\}&=<> ,])+([\\}]?)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + private static final Pattern rightPmReg = Pattern.compile("([\\{]?)([^'\\{\\}&=<> ,])+}", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + + List pms = null; + List dotPms = null; + public List noDotPms = null; + public Map pmDicts = new HashMap<>(); + public List ignorePms = new ArrayList<>(); + + public Analyzer(String analyestr) { + this.sourcestr = analyestr; + this.analyerstr = this.sourcestr; + } + + public Analyzer(String analyestr, SystemTypeEnums.PmType pmtype) { + this(analyestr); + this.pmtype = pmtype; + } + + public List getPms() { + if (pms == null) { + pms = new ArrayList<>(); + if (sourcestr == null || sourcestr.isEmpty()) { + return pms; + } + if (sourcestr.indexOf("{") < 0 && sourcestr.indexOf("}") < 0) { + return pms; + } + + dotPms = new ArrayList<>(); + noDotPms = new ArrayList<>(); + + Pattern[] regs = {noDotPmReg, DotPmReg, leftPmReg, rightPmReg}; + + for (Pattern reg : regs) { + Matcher matcher = reg.matcher(analyerstr); + while (matcher.find()) { + String pm = matcher.group().replace("|", "\\|"); + + if (pmDicts.containsKey(pm)) { + continue; + } + pmDicts.put(pm, "1"); + + if (pm.indexOf("{") < 0 && pm.indexOf("}") < 0) { + continue; + } + + // 处理trim特殊字符(C#的Trim等效实现) + String trimmedPm = pm.replaceAll("^[%_,'#]+", "").replaceAll("[%_,'#]+$", ""); + String item = trimmedPm.replace("{", "") + .replace("}", "") + .replace("[", "") + .replace("]", "") + .toLowerCase(); + + // 跳过数字参数(假设Web.Core.Util.RegexUtil.NumberReg是数字正则) + if (item.matches("^[0-9]+$")) { + continue; + } + + if (!pms.contains(item)) { + pms.add(item); + } + + // 处理双括号参数忽略 + if (analyerstr.indexOf("{{" + pm + "}}") > -1) { + ignorePms.add(item); + } + + boolean noPm = pm.startsWith("[") && pm.endsWith("]"); + + if (!noPm) { + if (pm.startsWith("'")) { + if (!dotPms.contains(item)) { + dotPms.add(item); + } + } else { + if (!sourcestr.equals(pm) && !dotPms.contains(item) + && !noDotPms.contains(item) && pmtype != SystemTypeEnums.PmType.program + && pmtype != SystemTypeEnums.PmType.ignorenull) { + noDotPms.add(item); + } + } + } + + int pmI = pms.indexOf(item); + String rmcVal = String.format("{%d}", pmI); + + // 替换pm中的trim部分为rmcVal + String replacedPm = pm.replace(trimmedPm, rmcVal) + .replaceAll("^'", "") + .replaceAll("'$", ""); + rmcVal = String.format(dotPms.contains(item) ? "'%s'" : "%s", replacedPm); + + if (noPm) { + analyerstr = analyerstr.replace(pm, rmcVal); + } else { + // 转义正则特殊字符后再编译 + String escapedPm = Pattern.quote(pm); + analyerstr = Pattern.compile(escapedPm, Pattern.CASE_INSENSITIVE) + .matcher(analyerstr) + .replaceAll(rmcVal); + } + } + } + } + return pms; + } + + // 判断字符串是否为数字 + private boolean isNumber(String str) { + if (str == null || str.isEmpty()) return false; + return str.matches("^[+-]?\\d+(\\.\\d+)?$"); + } +} + +public class PmAnalyzer { + private String sourcestr; + private LoginUserInfo user; + private SystemTypeEnums.PmType pmtype = SystemTypeEnums.PmType.sql; + private static final Pattern NDReg = Pattern.compile("[^\\d\\.\\-]"); + private static final List analyzers = new ArrayList<>(); + private Analyzer analyzer; + + public PmAnalyzer(LoginUserInfo user, String analyestr) { + this.user = user; + this.sourcestr = (analyestr == null) ? "" : analyestr; + } + + public PmAnalyzer(LoginUserInfo user, String analyestr, SystemTypeEnums.PmType pmtype) { + this(user, analyestr); + this.pmtype = pmtype; + } + + private Analyzer getAnalyzer() { + if (analyzer == null) { + // 简化处理,不使用缓存逻辑 + analyzer = new Analyzer(sourcestr, pmtype); + } + return analyzer; + } + + public List getPms() { + return getAnalyzer().getPms(); + } + + public List getIgnorePms() { + return getAnalyzer().ignorePms; + } + + public LoginUserInfo getUser() { + if (user == null) { + user = new LoginUserInfo(); + } + return user; + } + + public void setUser(LoginUserInfo user) { + this.user = user; + } + + private boolean containsKey(String pm, Map row, Map leftRow) { + if (pm.contains("parent.")) { + String key = pm.split("\\.")[1]; + return leftRow != null && leftRow.containsKey(key); + } + return row != null && row.containsKey(pm); + } + + private String dealVal(Object val) { + if (val == null) return ""; + if (val instanceof Date) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + return sdf.format((Date) val); + } + return val.toString(); + } + + public String FillPms(Map row, Map leftRow, SystemTypeEnums.PmType pmtype) { + return fillpms(pm -> { + String val = ""; + if (containsKey(pm, row, leftRow)) { + if (pm.contains("parent.")) { + String key = pm.split("\\.")[1]; + val = dealVal(leftRow.get(key)); + } else { + val = row == null ? "" : dealVal(row.get(pm)); + } + } + if (val.contains(",")) { + Pattern inReg = Pattern.compile("\\ +in\\ ?\\('\\{" + pm + "\\}'", Pattern.CASE_INSENSITIVE); + if (inReg.matcher(sourcestr).find()) { + val = val.replace(",", "','"); + return val; + } + } + + val = val.replace("''", "'").replace("'", "''"); +// val = val.replace("''", "'"); + if (getAnalyzer().noDotPms.contains(pm)) { + if (val.isEmpty() || NDReg.matcher(val).find()) { + return String.format("'%s'", val); + } + } + return val; + }, pm -> containsKey(pm, row, leftRow), pmtype); + } + + public String FillPms(Map row, Map leftRow) { + return FillPms(row, leftRow, this.pmtype); + } + + private String fillpms(Function getval, Predicate containskey, SystemTypeEnums.PmType pmtype) { + List pms = getPms(); +// System.out.println("fillpms " + pms); + if (pms == null || pms.isEmpty()) { + return sourcestr; + } + +// System.out.println("fillpms " + pms + "user: " + getUser().UserName); + // 创建参数映射,用于替换 + Map indexValues = new HashMap<>(); + for (int i = 0; i < pms.size(); i++) { + String pm = pms.get(i).replace("\\|", "|"); + String val = ""; + String valOrig = ""; + + if (!containskey.test(pm) && (pmtype == SystemTypeEnums.PmType.ignorenull || (getIgnorePms() != null && getIgnorePms().contains(pm)))) { + val = String.format("{%s}", pm); + } else { + valOrig = val = getval.apply(pm); + } + // 处理用户信息相关参数 + if (valOrig.isEmpty() || "''".equals(valOrig)) { + switch (pm) { + case "loginid": + val = getUser().UserId; // 注意使用getter方法 + break; + case "lginname": + case "loginname": + val = getUser().UserName; // 注意使用getter方法 + break; + case "loginaccount": + val = getUser().UserCode; // 注意使用getter方法 + break; + case "logintype": + val = getUser().LoginType == null ? "" : getUser().LoginType + ""; // 注意使用getter方法 + break; + case "seriesid": + val = getUser().SeriesId; // 注意使用getter方法 + break; + case "server": + case "serverid": + val = getUser().SeriesId; // 注意使用getter方法 + break; + case "pwd": + val = getUser().Pwd; // 注意使用getter方法 + break; + case "password": + val = getUser().Pwd; // 注意使用getter方法 + break; + case "clientid": + case "loginclientid": + val = getUser().ClientId == null ? "" : getUser().ClientId + ""; // 注意使用getter方法 + break; + } + + // 处理其他用户信息 + Map other = getUser().Other; // 注意使用getter方法 + if (other != null && other.containsKey(pm)) { + Object otherVal = other.get(pm); + if (otherVal instanceof Hashtable) { + Hashtable hashVal = (Hashtable) otherVal; + if (hashVal.containsKey("value")) { + val = hashVal.get("value").toString(); + } else { + val = otherVal.toString(); + } + } else { + val = otherVal.toString(); + } + } + } + + // 将参数和对应的值存入映射 + indexValues.put(i, val); // 用索引作为键 + } +// System.out.println("fillpms+foratStr "); + try { + String formatStr = getAnalyzer().analyerstr.replace("{{", "{").replace("}}", "}"); + // 按索引替换占位符 {0}, {1}, ... +// System.out.println("fillpms+formatStr " + formatStr + indexValues.toString()); + for (Map.Entry entry : indexValues.entrySet()) { + String placeholder = "{" + entry.getKey() + "}"; + String value = entry.getValue(); + formatStr = formatStr.replace(placeholder, value); + } +// System.out.println("fillpms+formatStr " + formatStr); + return formatStr; + } catch (Exception e) { + String tipStr = "请检查配置是否正确,字段配置值:【" + sourcestr + "】,处理后:【" + getAnalyzer().analyerstr + "】。"; + throw new RuntimeException(tipStr + e.getMessage(), e); + } + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/PrintHelper.java b/WebErp/weberp/src/main/java/org/example/Utils/PrintHelper.java new file mode 100644 index 0000000..5a4481c --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/PrintHelper.java @@ -0,0 +1,231 @@ +package org.example.Utils; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +/** + * 打印指令工具类(对应原C# PrintHelper) + * 功能:生成各类打印指令的16进制字符串 + */ +public final class PrintHelper { + + // 对应C# Encoding.Default(Windows默认编码) + private static final Charset DEFAULT_CHARSET = StandardCharsets.ISO_8859_1; + + /** + * 指令类型枚举(对应原C# CommandType) + */ + public enum CommandType { + A(1), + B(2); // B未指定值,默认按顺序为2(和C#一致) + + private final int value; + + CommandType(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } + + // 私有化构造方法,禁止实例化(静态工具类) + private PrintHelper() { + } + + // ------------------- 核心重载方法:GetPrintCommandStr ------------------- + + /** + * 字符串转16进制指令(对应原GetPrintCommandStr(StringBuilder, string)) + */ + public static StringBuilder getPrintCommandStr(StringBuilder stringBuilder, String value) { + if (stringBuilder == null) { + stringBuilder = new StringBuilder(); + } + // 转换为字节数组(对应C# Encoding.Default.GetBytes) + byte[] bytes = value.getBytes(DEFAULT_CHARSET); + for (byte b : bytes) { + // 16进制格式化(X2 → %02X),末尾加空格 + stringBuilder.append(String.format("%02X ", b)); + } + return stringBuilder; + } + + /** + * 整数转16进制指令(默认双字节参数,对应原isDoubleParameter = true) + */ + public static StringBuilder getPrintCommandStr(StringBuilder stringBuilder, int value) { + return getPrintCommandStr(stringBuilder, value, true); + } + + /** + * 整数转16进制指令(支持单/双字节参数,对应原GetPrintCommandStr(StringBuilder, int, bool)) + */ + public static StringBuilder getPrintCommandStr(StringBuilder stringBuilder, int value, boolean isDoubleParameter) { + if (stringBuilder == null) { + stringBuilder = new StringBuilder(); + } + + if (isDoubleParameter) { + // 双字节参数:转4位16进制,拆分后两位和前两位 + String text = String.format("%04X", value); // PadLeft(4, '0') → %04X + // 截取后两位(substring(text.Length - 2)) + String arg = text.substring(text.length() - 2); + // 截取前两位(substring(0, text.Length - 2)) + String arg2 = text.substring(0, text.length() - 2); + stringBuilder.append(arg).append(" "); + stringBuilder.append(arg2).append(" "); + } else { + // 单字节参数:转2位16进制 + String arg3 = String.format("%02X", value); // PadLeft(2, '0') → %02X + stringBuilder.append(arg3).append(" "); + } + return stringBuilder; + } + + // ------------------- 页面启停指令 ------------------- + + /** + * 打印页开始指令(无参数,对应原GetPrintPageStartCommandStr(StringBuilder)) + */ + public static StringBuilder getPrintPageStartCommandStr(StringBuilder stringBuilder) { + if (stringBuilder == null) { + stringBuilder = new StringBuilder(); + } + stringBuilder.append("1A 5B 00"); + stringBuilder.append("\r\n"); + return stringBuilder; + } + + /** + * 打印页开始指令(带参数,对应原GetPrintPageStartCommandStr(StringBuilder, int, int, int, int, int)) + */ + public static StringBuilder getPrintPageStartCommandStr(StringBuilder stringBuilder, int x, int y, int width, int height, int rotate) { + if (stringBuilder == null) { + stringBuilder = new StringBuilder(); + } + stringBuilder.append("1A 5B 01 "); + getPrintPageStartParameterStr(stringBuilder, x, y, width, height, rotate); + stringBuilder.append("\r\n"); + return stringBuilder; + } + + /** + * 打印页开始参数拼接(对应原GetPrintPageStartParameterStr) + */ + public static StringBuilder getPrintPageStartParameterStr(StringBuilder stringBuilder, int x, int y, int width, int height, int rotate) { + getPrintCommandStr(stringBuilder, x); + getPrintCommandStr(stringBuilder, y); + getPrintCommandStr(stringBuilder, width); + getPrintCommandStr(stringBuilder, height); + getPrintCommandStr(stringBuilder, rotate, false); // 单字节参数 + return stringBuilder; + } + + /** + * 打印页结束指令(对应原GetPrintPageEndCommandStr) + */ + public static StringBuilder getPrintPageEndCommandStr(StringBuilder stringBuilder) { + if (stringBuilder == null) { + stringBuilder = new StringBuilder(); + } + stringBuilder.append("1A 5D 00"); + stringBuilder.append("\r\n"); + return stringBuilder; + } + + // ------------------- 打印执行指令 ------------------- + + /** + * 打印执行指令(默认份数,对应原GetPrintPagePrintCommandStr(StringBuilder)) + */ + public static StringBuilder getPrintPagePrintCommandStr(StringBuilder stringBuilder) { + if (stringBuilder == null) { + stringBuilder = new StringBuilder(); + } + stringBuilder.append("1A 4F 00"); + stringBuilder.append("\r\n"); + return stringBuilder; + } + + /** + * 打印执行指令(指定份数,对应原GetPrintPagePrintCommandStr(StringBuilder, int)) + */ + public static StringBuilder getPrintPagePrintCommandStr(StringBuilder stringBuilder, int printNum) { + if (stringBuilder == null) { + stringBuilder = new StringBuilder(); + } + stringBuilder.append("1A 4F 01"); + // 空值处理:printNum.ToString() ?? "" → 转换为字符串,空则为空串 + String printNumStr = String.valueOf(printNum); + getPrintCommandStr(stringBuilder, printNumStr == null ? "" : printNumStr); + stringBuilder.append("\r\n"); + return stringBuilder; + } + + // ------------------- 跳页指令 ------------------- + + /** + * 跳页指令(无参数,对应原GetPrintPageSkipCommandStr(StringBuilder)) + */ + public static StringBuilder getPrintPageSkipCommandStr(StringBuilder stringBuilder) { + if (stringBuilder == null) { + stringBuilder = new StringBuilder(); + } + stringBuilder.append("1A 0C 00"); + stringBuilder.append("\r\n"); + return stringBuilder; + } + + /** + * 跳页指令(带参数,对应原GetPrintPageSkipCommandStr(StringBuilder, int, int)) + */ + public static StringBuilder getPrintPageSkipCommandStr(StringBuilder stringBuilder, int stopPosition, int offset) { + if (stringBuilder == null) { + stringBuilder = new StringBuilder(); + } + stringBuilder.append("1A 0C 01"); + getPrintCommandStr(stringBuilder, stopPosition, false); // 单字节参数 + getPrintCommandStr(stringBuilder, offset); // 双字节参数(默认) + stringBuilder.append("\r\n"); + return stringBuilder; + } + + // ------------------- 文本打印指令 ------------------- + + /** + * 文本打印指令(基础版,对应原GetPrintPageStringCommandStr(StringBuilder, string, int, int)) + */ + public static StringBuilder getPrintPageStringCommandStr(StringBuilder stringBuilder, String value, int x, int y) { + if (stringBuilder == null) { + stringBuilder = new StringBuilder(); + } + stringBuilder.append("1A 54 00"); + stringBuilder.append("\r\n"); + getPrintCommandStr(stringBuilder, x); + getPrintCommandStr(stringBuilder, y); + getPrintCommandStr(stringBuilder, value); + getPrintCommandStr(stringBuilder, "\0"); // 字符串终止符 + return stringBuilder; + } + + /** + * 文本打印指令(完整版,带字体参数,对应原GetPrintPageStringCommandStr(StringBuilder, string, int, int, int, int)) + */ + public static StringBuilder getPrintPageStringCommandStr(StringBuilder stringBuilder, String value, int x, int y, int fontHeight, int fontType) { + if (stringBuilder == null) { + stringBuilder = new StringBuilder(); + } + stringBuilder.append("1A 54 01"); + stringBuilder.append("\r\n"); + getPrintCommandStr(stringBuilder, x); + getPrintCommandStr(stringBuilder, y); + getPrintCommandStr(stringBuilder, fontHeight); + getPrintCommandStr(stringBuilder, fontType); + getPrintCommandStr(stringBuilder, value); + getPrintCommandStr(stringBuilder, "\0"); // 字符串终止符 + stringBuilder.append("\r\n"); + return stringBuilder; + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Utils/PublicUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/PublicUtil.java new file mode 100644 index 0000000..b7a7e16 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/PublicUtil.java @@ -0,0 +1,1131 @@ +package org.example.Utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +import jakarta.servlet.http.HttpSession; +import org.example.Entity.System.LoginUserInfo; +import org.example.Enums.SystemEnums; +import org.example.Enums.SystemTypeEnums; +import org.example.Impl.BaseImpl; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.imageio.ImageIO; +import javax.script.ScriptException; +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.beans.BeanInfo; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.security.Timestamp; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.*; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static org.apache.ibatis.ognl.OgnlOps.convertValue; +import static org.example.Enums.SystemEnums.BillMenuEnum.*; +import static org.example.Enums.SystemEnums.ControlType.LabDateTime; +import static org.springframework.data.config.ParsingUtils.setPropertyValue; + +public class PublicUtil { + private static final Logger log = LoggerFactory.getLogger(PublicUtil.class); + + + + // 字符串转 bool + public static Object EvalCond(String cond, JsEngine.ErrorHandler onError, boolean replaceEd) { + if (cond == null || cond.isEmpty()) { + return true; + } + if (cond.startsWith("!") || cond.startsWith("@")) { + return true; + } + if (cond.indexOf("{") > -1) { + String _cond = cond; + if (!replaceEd) { + _cond = SqlToCode(cond); + } + return JsEngine.Eval(ReqSqlPmsByRow(null, null, _cond, SystemTypeEnums.PmType.sql, null), onError); + } + return JsEngine.Eval(cond, onError); + } + + public static Object EvalCond(String cond) { + return EvalCond(cond, null, true); + } + + // 检查是否有字段属性方法 +// 反射工具方法:检查对象是否有指定属性 +// 缓存类的方法,避免重复反射(提升性能) + private static final ConcurrentHashMap, Map> METHOD_CACHE = new ConcurrentHashMap<>(); + + /** + * 检查对象是否有指定属性(支持任意类型) + */ + public static boolean hasProperty(Object obj, String propertyName) { + if (obj == null || propertyName == null || propertyName.isEmpty()) { + return false; + } + + try { + // 1. 检查标准 JavaBean 属性(通过 getter) + Class clazz = obj.getClass(); + // 尝试标准 getter: getXxx() + String getterName = "get" + capitalize(propertyName); + Method getter = clazz.getMethod(getterName); + if (getter != null) { + Object value = getter.invoke(obj); + return value != null; // 关键修改:验证值非空 + } + return false; + } catch (Exception e) { + return false; + } + } + + /** + * 设置对象属性值(适用于所有 DTO) + */ + public static void setPropertyValue(Object obj, String propertyName, Object value) { + if (obj == null || propertyName == null || propertyName.isEmpty()) { + return; + } + + try { + Class clazz = obj.getClass(); + BeanInfo beanInfo = Introspector.getBeanInfo(clazz); + PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors(); + + for (PropertyDescriptor descriptor : descriptors) { + if (descriptor.getName().equalsIgnoreCase(propertyName)) { + Method setter = descriptor.getWriteMethod(); + if (setter != null) { + // 处理基本类型的自动装箱/拆箱 + Object convertedValue = convertValue(value, setter.getParameterTypes()[0]); + setter.invoke(obj, convertedValue); + } + return; + } + } + } catch (Exception e) { + log.error("Exception caught", e); + // 处理异常:记录日志或抛出运行时异常 + } + } + + /** + * 首字母大写 + */ + private static String capitalize(String str) { + if (str == null || str.isEmpty()) return str; + return Character.toUpperCase(str.charAt(0)) + str.substring(1); + } + + + /** + * 将源对象转换为目标类型的对象(任意转任意) + * + * @param source 源对象 + * @param targetClass 目标类型的Class + * @param 目标类型的泛型 + * @return 转换后的目标对象 + */ + public static T convert(Object source, Class targetClass) { + if (source == null || targetClass == null) { + return null; + } + + try { + // 创建目标类的实例 + T target = targetClass.getDeclaredConstructor().newInstance(); + + // 1. 复制基础属性(通过 getter/setter) + Map sourceProperties = getPropertyDescriptors(source.getClass()); + Map targetProperties = getPropertyDescriptors(targetClass); + + for (String propertyName : targetProperties.keySet()) { + if (sourceProperties.containsKey(propertyName)) { + PropertyDescriptor sourcePd = sourceProperties.get(propertyName); + PropertyDescriptor targetPd = targetProperties.get(propertyName); + + if (isAssignable(sourcePd.getPropertyType(), targetPd.getPropertyType())) { + Method readMethod = sourcePd.getReadMethod(); + Method writeMethod = targetPd.getWriteMethod(); + + if (readMethod != null && writeMethod != null) { + Object value = readMethod.invoke(source); + writeMethod.invoke(target, value); + } + } + } + } + + return target; + + } catch (Exception e) { + throw new RuntimeException("对象转换失败: " + e.getMessage(), e); + } + } + + + /** + * 将列表转换为目标类型的列表 + */ + public static List convertList(List sourceList, Class targetClass) { + if (sourceList == null || sourceList.isEmpty()) { + return Collections.emptyList(); + } + + return sourceList.stream().map(source -> convert(source, targetClass)).collect(Collectors.toList()); + } + + /** + * 将 Map 转换为任意类型的 DTO + */ + public static T convertMapToDto(Map sourceMap, Class targetClass) { + if (sourceMap == null || targetClass == null) { + return null; + } + + try { + T target = targetClass.getDeclaredConstructor().newInstance(); + BeanInfo beanInfo = Introspector.getBeanInfo(targetClass); + + // 构建小写键的Map,用于大小写不敏感匹配 + Map lowerCaseSourceMap = new HashMap<>(); + for (Map.Entry entry : sourceMap.entrySet()) { + lowerCaseSourceMap.put(entry.getKey().toLowerCase(), entry.getValue()); + } + + for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) { + String propertyName = pd.getName(); + Method setter = pd.getWriteMethod(); + + if (setter != null) { + Object value = null; + + // 1. 尝试精确匹配 + if (sourceMap.containsKey(propertyName)) { + value = sourceMap.get(propertyName); + } + // 2. 尝试大小写不敏感匹配 + else { + String lowerCasePropertyName = propertyName.toLowerCase(); + if (lowerCaseSourceMap.containsKey(lowerCasePropertyName)) { + value = lowerCaseSourceMap.get(lowerCasePropertyName); + } + } + + if (value != null) { + Object convertedValue = convertType(value, pd.getPropertyType()); + setter.invoke(target, convertedValue); + } + } + } + + return target; + + } catch (Exception e) { + throw new RuntimeException("Map转换为DTO失败: " + e.getMessage(), e); + } + } + + /** + * 类型转换辅助方法 + */ + private static Object convertType(Object value, Class targetType) { + if (value == null) { + return null; + } + + // 如果类型已经匹配,直接返回 + if (targetType.isAssignableFrom(value.getClass())) { + return value; + } + + // 处理基本数据类型 + if (targetType == String.class) { + return value.toString(); + } else if (targetType == Integer.class || targetType == int.class) { + return Integer.valueOf(value.toString()); + } else if (targetType == Long.class || targetType == long.class) { + return Long.valueOf(value.toString()); + } else if (targetType == Double.class || targetType == double.class) { + return Double.valueOf(value.toString()); + } else if (targetType == Boolean.class || targetType == boolean.class) { + return Boolean.valueOf(value.toString()); + } + // 可以添加更多类型转换逻辑,如日期类型等 + + return value; // 默认不转换 + } + + /** + * 获取类的所有属性描述符 + */ + private static Map getPropertyDescriptors(Class clazz) { + Map properties = new HashMap<>(); + try { + BeanInfo beanInfo = Introspector.getBeanInfo(clazz); + for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) { + if (!"class".equals(pd.getName())) { + properties.put(pd.getName(), pd); + } + } + } catch (Exception e) { + throw new RuntimeException("获取类属性失败: " + e.getMessage(), e); + } + return properties; + } + + /** + * 检查源类型是否可以赋值给目标类型,或支持简单的类型转换 + */ + private static boolean isAssignable(Class sourceType, Class targetType) { + // 基本类型及其包装类的兼容性检查 + if (targetType.isAssignableFrom(sourceType)) { + return true; + } + + // 处理基本类型和包装类的转换 + if (sourceType.isPrimitive()) { + sourceType = getWrapperClass(sourceType); + } + + if (targetType.isPrimitive()) { + targetType = getWrapperClass(targetType); + } + + // 字符串到基本类型/包装类的转换 + if (String.class.equals(sourceType)) { + return targetType.equals(String.class) || targetType.equals(Integer.class) || targetType.equals(Long.class) || targetType.equals(Double.class) || targetType.equals(Float.class) || targetType.equals(Boolean.class); + } + + return false; + } + + /** + * 获取基本类型对应的包装类 + */ + private static Class getWrapperClass(Class primitiveType) { + if (int.class.equals(primitiveType)) return Integer.class; + if (long.class.equals(primitiveType)) return Long.class; + if (double.class.equals(primitiveType)) return Double.class; + if (float.class.equals(primitiveType)) return Float.class; + if (boolean.class.equals(primitiveType)) return Boolean.class; + if (char.class.equals(primitiveType)) return Character.class; + if (byte.class.equals(primitiveType)) return Byte.class; + if (short.class.equals(primitiveType)) return Short.class; + return primitiveType; + } + + // SqlToCode方法 + public static String SqlToCode(String str) { + // 1. 复刻C#的string.IsNullOrEmpty判断 + if (str == null || str.isEmpty()) { + return ""; + } + + // 2. 复刻前缀判断:以@或!开头直接返回原字符串 + if (str.startsWith("@") || str.startsWith("!")) { + return str; + } + + // 3. 第一步替换:双引号替换为单引号 + String result = str.replace("\"", "'"); + + // 4. 正则替换1:or → ||(匹配独立的or关键词) + String regexOr = "(?is)(?<=([ \\r\\n\\)\\'\\}]))or(?=([ \\r\\n\\(\\'\\{]))"; + result = replaceWithRegex(result, regexOr, "||"); + + // 5. 正则替换2:and → &&(匹配独立的and关键词) + String regexAnd = "(?is)(?<=([ \\r\\n\\)\\'\\}]))and(?=([ \\r\\n\\(\\'\\{]))"; + result = replaceWithRegex(result, regexAnd, "&&"); + + // 6. 正则替换3:多个等号 → == + String regexEqual = "(?is)(?<=([ \\r\\n\\)\\'])?[^> → != + String regexNotEqual = "(?is)(?<= |\\r\\n|\\)|'?)<>(?= |\\r\\n|\\(|'?)"; + result = replaceWithRegex(result, regexNotEqual, "!="); + + // 8. 正则替换5:>== → >= + String regexGreaterEqual = "(?is)(?<=([ \\r\\n\\)\\']))>==(?=([ \\r\\n\\(\\']))"; + result = replaceWithRegex(result, regexGreaterEqual, ">="); + + // 9. 正则替换6:<== → <= + String regexLessEqual = "(?is)(?<=([ \\r\\n\\)\\']))<==(?=([ \\r\\n\\(\\']))"; + result = replaceWithRegex(result, regexLessEqual, "<="); + + return result; + } + + private static String replaceWithRegex(String input, String regex, String replacement) { + // 编译正则:对应C\#的RegexOptions.IgnoreCase + RegexOptions.Multiline + // (?i)=IgnoreCase,(?s)=DOTALL,Pattern.MULTILINE对应C\#的Multiline + Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); + Matcher matcher = pattern.matcher(input); + return matcher.replaceAll(replacement); + } + + // ReqSqlPmsByRow方法 + public static String ReqSqlPmsByRow(Map rowMap, Map leftMap, String param, SystemTypeEnums.PmType pmType) { + return ReqSqlPmsByRow(rowMap, leftMap, param, pmType, null); + } + + public static String ReqSqlPmsByRow(Map rowMap, Map leftMap, String param) { + return ReqSqlPmsByRow(rowMap, leftMap, param, SystemTypeEnums.PmType.sql, null); + } + + public static String ReqSqlPmsByRow(Map rowMap, Map leftMap, String param, SystemTypeEnums.PmType pmType, LoginUserInfo user) { + if (user == null) { + user = new BaseImpl().getUser(); + } + return new PmAnalyzer(user, param, pmType).FillPms(rowMap, leftMap, pmType); + } + + public static String ReqSqlPms(Map rowMap, Map leftMap, String param, SystemTypeEnums.PmType pmType, LoginUserInfo user) { + if (user == null) { + user = new BaseImpl().getUser(); + } + return new PmAnalyzer(user, param, pmType).FillPms(rowMap, leftMap, pmType); + } + + public static List getParamValue(String param) { + String sqlParamRegex = "\\{([^{|}])+}"; + List list = new ArrayList<>(); + Pattern pattern = Pattern.compile(sqlParamRegex); + Matcher matcher = pattern.matcher(param); + while (matcher.find()) { + String value = matcher.group(); + if (!list.contains(value)) { + list.add(value); + } + } + return list; + } + + // 获取用户权限信息 + public static String GetPurviews(Map dt) { + StringBuilder kcPurview = new StringBuilder(); + for (Map.Entry entry : dt.entrySet()) { + String columnName = entry.getKey(); + String purview = Objects.toString(entry.getValue(), ""); + if (!purview.isEmpty() && columnName.toLowerCase().indexOf("purview") > -1) { + kcPurview.append(purview).append(","); + } + } + return kcPurview.toString(); + } + + // 检查权限 + public static String CheckPurview(LoginUserInfo user, String kcPurview, String menuid) { + if (user == null && RequestContextHolder.getRequestAttributes() != null) { + HttpSession session = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getSession(); + if (session != null) { + user = new BaseImpl().getUser(); + } + } + if (user != null && "管理员".equals(user.UserName)) { + return "AllPurview"; + } + if (kcPurview == null || kcPurview.isEmpty() || menuid == null || menuid.isEmpty()) { + return ""; + } + kcPurview = "," + kcPurview + ","; + String allPurview = "," + menuid + ","; + String readPurview = "," + menuid + "|,"; + if (!kcPurview.contains(allPurview)) { + if (!kcPurview.contains(readPurview)) { + return ""; + } else { + return "ReadPurview"; + } + } else { + return "AllPurview"; + } + } + + // 获取单据右键菜单的查询前缀 + public static String GetBillConMenuKey(SystemEnums.BillMenuEnum _enum) { + switch (_enum) { + case BillSource: + return "BILLSOURCE_"; + case BillSourceDetail: + return "SOURCEDETAIL_"; + case BillDetail: + return "BILLDETAIL_"; + default: + return ""; + } + } + + // ext 时间格式 format 转换为对应的程序的 format + public static String ExtDateFormatToPro(String sourceformat) { + if (sourceformat == null || sourceformat.isEmpty()) { + return null; + } + String formater = sourceformat.replaceAll("y+", "yyyy"); + formater = formater.replaceAll("m+", "MM"); + formater = formater.replaceAll("d+", "dd"); + formater = formater.replaceAll("D+", "DD"); + formater = formater.replaceAll("s+", "ss"); + formater = formater.replaceAll("h+", "hh"); + formater = formater.replaceAll("H+", "HH"); + formater = formater.replaceAll("i+", "MM"); + return formater; + } + + // 程序的 format 转换为对应的 ext 时间格式 + public static String ProDateFormatToExt(String sourceformat) { + if (sourceformat == null || sourceformat.isEmpty()) { + return null; + } + String formater = sourceformat.replaceAll("y+", "Y"); + formater = formater.replaceAll("m+", "i"); + formater = formater.replaceAll("M+", "m"); + formater = formater.replaceAll("d+", "d"); + formater = formater.replaceAll("D+", "D"); + formater = formater.replaceAll("s+", "s"); + formater = formater.replaceAll("h+", "h"); + formater = formater.replaceAll("H+", "H"); + return formater; + } + + // 获取一些默认的格式 + public static String GetFormatByCType(int ctype) { + SystemEnums.ControlType _FieldType = SystemEnums.ControlType.values()[ctype]; + return GetFormatByCType(_FieldType); + } + + public static String GetFormatByCType(SystemEnums.ControlType ctype) { + String format = null; + switch (ctype) { + case LabDate: + format = format == null ? "yyyy-MM-dd" : format; + break; + case LabDateTime: + case LabCheckDateTimeShort: + format = format == null ? "yyyy-MM-dd HH:mm:ss" : format; + break; + case LabDateTimeShort: + format = format == null ? "yyyy-MM-dd HH:mm" : format; + break; + case LabTime: + case LabCheckTime: + format = format == null ? "HH:mm:ss" : format; + break; + case LabShortTime: + case LabCheckShortTime: + format = format == null ? "HH:mm" : format; + break; + case LabYearTime: + case LabLabYearTime: + format = format == null ? "yyyy-MM" : format; + break; + default: + format = null; + break; + } + return format; + } + + // ctype 转换为程序类型字符串 + public static SystemTypeEnums.FieldType GetProTypeByCType(SystemEnums.ControlType ctype) { + switch (ctype) { + case LabQQ: + case LabWWW: + case LabText: + case LabRemark: + case LabComboxText: + case LabComboxValue: + case LabMultiSelectText: + case LabMultiSelectValue: + case LabComboxTextParam: + case LabComboxValueParam: + return SystemTypeEnums.FieldType.VarChar; + case LabCheckDateTimeShort: + case LabCheckTime: + case LabCheckShortTime: + case LabDate: + case LabDateTime: + case LabDateTimeShort: + case LabTime: + case LabShortTime: + return SystemTypeEnums.FieldType.DateTime; + case LabPhone: + case LabTextInt: + return SystemTypeEnums.FieldType.Int32; + default: + return SystemTypeEnums.FieldType.Text; + } + } + + // 转换为 ext column 类型 + public static String GetEColumnTypeByCType(SystemEnums.ControlType ctype) { + switch (ctype) { + case LabCheckDateTimeShort: + case LabCheckTime: + case LabCheckShortTime: + case LabDate: + case LabDateTime: + case LabDateTimeShort: + case LabTime: + case LabShortTime: + return "datecolumn"; + case LabPicEx: + return "filecolumn"; + case LabTextInt: + return "numbercolumn"; + case LabProgress: + return "progresscolumn"; + case LabCheckBox: + return "checkcolumn"; + default: + return "gridcolumn"; + } + } + + public static String TypeToColumnType(Class ctype) { + if (Date.class.isAssignableFrom(ctype) || + LocalDate.class.isAssignableFrom(ctype) || + LocalDateTime.class.isAssignableFrom(ctype) || + Timestamp.class.isAssignableFrom(ctype)) { + return "datecolumn"; + } +// if (ctype == Timestamp.class) { +// return "datecolumn"; +// } + if (ctype == Integer.class || ctype == Float.class || ctype == Double.class || ctype == Long.class || ctype == java.math.BigDecimal.class) { + return "numbercolumn"; + } + if (ctype == Boolean.class) { + return "checkcolumn"; + } + return "gridcolumn"; + } + + // 转换为 ext data.field 类型 + public static String GetEFieldTypeByCType(SystemEnums.ControlType ctype, String format) { + switch (ctype) { + case LabCheckDateTimeShort: + case LabCheckTime: + case LabCheckShortTime: + case LabDate: + case LabDateTime: + case LabDateTimeShort: + case LabTime: + case LabShortTime: + return "date"; + case LabTextInt: + if ((format + "").indexOf(".") > -1 || format == null || format.isEmpty()) { + return "float"; + } + return "int"; + case LabCheckBox: + return "bool"; + default: + return "auto"; + } + } + + // 获取各类型的默认值 + public static Object GetDefaultValByCType(SystemEnums.ControlType ctype) { + switch (ctype) { + case LabCheckDateTimeShort: + case LabCheckTime: + case LabCheckShortTime: + case LabDate: + case LabDateTime: + case LabDateTimeShort: + case LabTime: + case LabShortTime: + return new Date(System.currentTimeMillis()); + case LabTextInt: + return 0; + case LabCheckBox: + return true; + default: + return ""; + } + } + + // 获取 sql 操作的各类型的默认值 + public static String GetSqlDefaultValByCType(SystemEnums.ControlType ctype) { + switch (ctype) { + case LabCheckDateTimeShort: + case LabCheckTime: + case LabCheckShortTime: + case LabDate: + case LabDateTime: + case LabDateTimeShort: + case LabTime: + case LabShortTime: + return "null"; + case LabTextInt: + return "0"; + default: + return "''"; + } + } + + /** + * 根据数据类型获取SQL默认值 + * + * @param type 数据类型Class对象 + * @param nullAble 是否允许为null + * @return SQL默认值字符串 + */ + public static String GetSqlDefaultValByType(Class type, boolean nullAble) { + String value; + if (type == null) { + return "''"; + } + if (type == Timestamp.class || type.getSuperclass() == Timestamp.class) { + value = "null"; + if (!nullAble) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + return "'" + sdf.format(new Date(System.currentTimeMillis())) + "'"; + } + } // 处理值类型(对应C#中ValueType) + else if (isValueType(type)) { + value = "0"; + } + // 引用类型默认返回空字符串 + else { + value = "''"; + } + + return value; + } + + /** + * 判断子类关系(模拟C#的IsSubclassOf) + * + * @param child 子类 + * @param parent 父类 + * @return 是否为子类 + */ + private static boolean isSubclassOf(Class child, Class parent) { + if (child == null || parent == null) { + return false; + } + return parent.isAssignableFrom(child) && !child.equals(parent); + } + + /** + * 判断是否为值类型(模拟C#的ValueType) + * Java中值类型包括基本类型及其包装类、枚举等 + * + * @param type 类型 + * @return 是否为值类型 + */ + private static boolean isValueType(Class type) { + if (type == null) { + return false; + } + // 基本类型(int, long, double等) + if (type.isPrimitive()) { + return true; + } + // 基本类型包装类(Integer, Long, Double等) + if (type == Integer.class || type == Long.class || type == Double.class || + type == Float.class || type == Short.class || type == Byte.class || + type == Boolean.class || type == Character.class) { + return true; + } + // 枚举类型 + if (type.isEnum()) { + return true; + } + // 其他值类型判断(根据实际需求扩展) + return false; + } + + //达梦的适配xtype + public static int DMSqltypeToProType(String type) { + switch (type) { + case "DATETIME": + return 40; + case "BLOB": + return 34; + case "TEXT": + return 35; + //99没有查到 + case "VARCHAR": + return 167; + //173没有用户表 + case "NCHAR": + return 173; + case "NVARCHAR": + return 231; + case "VARBINARY": + return 165; + case "CHAR": + return 175; + //36在达梦中为VARCHAR + //8018为人大金仓的 + //8016为人大金仓的 +// return String.class; + + case "DATE": + return 40; + case "TIME": + return 41; + //42对应为TIMESTAMP + //58没有查到 + case "TIMESTAMP"://对应61 + return 42; + case "BINARY"://对应189 + return 189; + //43没有用户表 + //7881为人大金仓的 + //7754为人大金仓的 +// return Timestamp.class; + + case "SMALLINT": + return 48; + //52在达梦中为SMALLINT + case "INT": + return 56; + //5063为人大金仓的 +// return Integer.class; + + case "REAL": + return 59; + case "DOUBLE": + return 62; +// return Float.class; + + case "DECIMAL": + return 106; + case "NUMERIC": + return 108; + //122没有查到 + //60在达梦中为DECIMAL + //5067为人大金仓的 +// return Double.class; + + case "BIT": + return 104; +// return Boolean.class; + + case "BIGINT": + return 127; +// return Long.class; +// + default: + return 0; +// return String.class; + + } + } + + // 将数据库 xtype 类型转换为程序类型 + public static Class SqlxtypeToProType(int xtype) { + switch (xtype) { + case 34: + case 35: + case 99: + case 167: + case 173: + case 239: + case 231: + case 165: + case 175: + case 36: + case 8018: + case 8016: + return String.class; + case 40: + case 41: + case 42: + case 58: + case 61: + case 189: + case 43: + case 7881: + case 7754: + return Timestamp.class; + case 48: + case 52: + case 56: + case 5063: + return Integer.class; + case 59: + case 62: + return Float.class; + case 106: + case 108: + case 122: + case 60: + case 5067: + return Double.class; + case 104: + return Boolean.class; + case 127: + return Long.class; + default: + return String.class; + } + } + + // URL 转换为 Dll 信息 + public static Map UrlToDllInfo(String url) { + if (url.indexOf("app.html") > -1) { + Map queryPms = new HashMap<>(); + String[] parts = url.split("\\?"); + if (parts.length > 1) { + String[] params = parts[1].split("&"); + for (String param : params) { + String[] kv = param.split("="); + if (kv.length > 1) { + if (!kv[0].toLowerCase().equals("username") && !kv[0].toLowerCase().equals("password")) { + queryPms.put(kv[0].toLowerCase(), kv[1]); + } + } + } + if (queryPms.containsKey("xtype")) { + queryPms.put("moduleid", queryPms.getOrDefault("moduleid", queryPms.getOrDefault("dllcoid", ""))); + queryPms.put("idValue", queryPms.getOrDefault("idvalue", queryPms.getOrDefault("id", ""))); + } + } + return queryPms; + } + return null; + } + + // 加带有账号密码的链接转换为加密链接 + public static String ToEnUrl(String url) { + String[] urlParams = url.split("\\?"); + if (urlParams.length > 1) { + String[] queryParams = urlParams[1].split("&"); + List qpLS = new ArrayList<>(); + Map pms = new HashMap<>(); + for (String q : queryParams) { + String[] eqParams = q.split("="); + if (eqParams.length > 1) { + if (eqParams[0].toLowerCase().equals("username") || eqParams[0].toLowerCase().equals("password")) { + pms.put(eqParams[0], eqParams[1]); + } else { + qpLS.add(eqParams[0] + "=" + java.net.URLEncoder.encode(eqParams[1])); + } + } + } + if (!pms.isEmpty()) { + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.DAY_OF_YEAR, 1); + long timestamp = calendar.getTimeInMillis() / 1000; + pms.put("exp", timestamp); + } + // 这里需要实现 AESUtil.MobileEncrypt 和 JSON.Encode 的逻辑 + String encryptedPms = ""; + try { + encryptedPms = java.net.URLEncoder.encode(encryptedPms); + } catch (Exception e) { + log.error("Exception caught", e); + } + return urlParams[0] + "?" + String.join("&", qpLS) + "&pms=" + encryptedPms; + } + return url; + } + + // 注意:方法必须返回处理后的新字符串,不能是void! + public static String fixSqlCompatibility(String sql) { + if (sql == null || sql.isEmpty()) { + return sql; + } + log.debug(String.valueOf("sql: " + sql)); + // 1. 移除with(nolock)(兼容:with(nolock)、WITH(NOLOCK)、with ( nolock ) 等格式) + String sqlAfterRemoveNolock = sql.replaceAll("(?i)with\\s*\\(\\s*nolock\\s*\\)", ""); + log.debug(String.valueOf("移除with(nolock)后:" + sqlAfterRemoveNolock)); // 加日志验证这一步是否生效 + + // 2. 替换convert日期转换为cast(解决格式码120的兼容性问题) + Pattern convertPattern = Pattern.compile( + "convert\\(date,\\s*convert\\(varchar\\(10\\),\\s*(\\w+\\.\\w+)\\s*,\\s*120\\)\\s*\\)", + Pattern.CASE_INSENSITIVE + ); + String sqlAfterFixConvert = convertPattern.matcher(sqlAfterRemoveNolock).replaceAll("cast($1 as date)"); + log.debug(String.valueOf("修复convert后:" + sqlAfterFixConvert)); // 加日志验证这一步 + + // 3. 给表名加dbo.前缀(处理from/join后的表) + String sqlAfterAddDbo = sqlAfterFixConvert + .replaceAll("(?i)from\\s+(\\w+)(\\s+\\w+)?", "from dbo.$1$2") // from 表名 + .replaceAll("(?i)join\\s+(\\w+)(\\s+\\w+)?", "join dbo.$1$2"); // join 表名 + log.debug(String.valueOf("添加dbo.前缀后:" + sqlAfterAddDbo)); // 加日志验证这一步 + return sqlAfterAddDbo; + } + + public static String EvalCondtionToCode(String str) { + if (NativeExtensionUtils.isNullOrEmpty(str)) { + return str; + } + str = str.replace(" ", "") + .replace("''<", "0<") + .replace("''>", "0>") + .replace(">''", ">0") + .replace("<''", "<0") + .replace("--", "+") + .replace("!===", "!=") + .replace(">==", ">=") + .replace("<==", "<="); + return str; + } + + public static String GenerateRandomCode(int length) { + final String chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; + var random = new Random(); + // 用StringBuilder高效拼接字符(Java中替代string拼接) + StringBuilder sb = new StringBuilder(length); + + // 核心逻辑:循环length次,每次随机选一个字符 + for (int i = 0; i < length; i++) { + int randomIndex = random.nextInt(chars.length()); + sb.append(chars.charAt(randomIndex)); + } + + // 转成最终字符串返回 + return sb.toString(); + } + + // + + /** + * 生成验证码图片,输出到OutputStream(对应C#的MemoryStream) + * + * @param code 验证码字符串(如之前生成的随机码) + * @param outputStream 输出流(MemoryStream对应Java的ByteArrayOutputStream) + * @throws IOException 图片写入异常 + */ + public static void GenerateCaptchaImage(String code, OutputStream outputStream) throws IOException { + int charSpacing = 15; // 字符间距 + int startX = 5; + // 1. 创建BufferedImage(对应C#的Bitmap) + BufferedImage image = new BufferedImage(120, 40, BufferedImage.TYPE_INT_RGB); + // 2. 获取Graphics2D(对应C#的Graphics) + Graphics2D graphics = image.createGraphics(); + Random random = new Random(); + + try { + // ===== 1. 配置绘图质量(对应C#的SmoothingMode/CompositingQuality)===== + // 抗锯齿(对应C#的SmoothingMode.AntiAlias) + graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + // 高质量插值(对应C#的CompositingQuality.HighQuality) + graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + // 整体渲染质量优先 + graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + + // ===== 2. 设置白色背景(对应C#的Clear(Color.White))===== + graphics.setColor(Color.WHITE); + graphics.fillRect(0, 0, 120, 40); + + // ===== 3. 绘制15条随机干扰线(对应C#的干扰线逻辑)===== + for (int i = 0; i < 15; i++) { + int x1 = random.nextInt(120); + int y1 = random.nextInt(40); + int x2 = random.nextInt(120); + int y2 = random.nextInt(40); + + // 生成随机颜色(对应C#的Color.FromArgb(150-255, 0-100, 0-100, 0-100)) + Color randomColor = new Color( + random.nextInt(106) + 150, // 150~255 + random.nextInt(101), // 0~100 + random.nextInt(101), // 0~100 + random.nextInt(101) // 0~100 + ); + // 随机线宽(1~2,对应C#的random.Next(1,3)) + Stroke stroke = new BasicStroke(random.nextInt(2) + 1); + graphics.setStroke(stroke); + graphics.setColor(randomColor); + graphics.drawLine(x1, y1, x2, y2); + } + + // ===== 4. 绘制100个矩形干扰点(对应C#的矩形干扰点)===== + for (int i = 0; i < 100; i++) { + int x = random.nextInt(120); + int y = random.nextInt(40); + + Color randomColor = new Color( + random.nextInt(106) + 150, // 150~255 + random.nextInt(256), + random.nextInt(256), + random.nextInt(256) + ); + graphics.setColor(randomColor); + graphics.fillRect(x, y, 2, 2); // 2x2的矩形点 + } + + // ===== 5. 绘制验证码文字(核心:倾斜+阴影+间距)===== + // 基础字体(对应C#的Arial 16号粗体) + Font baseFont = new Font("Arial", Font.BOLD, 16); + int currentX = startX; + + for (int i = 0; i < code.length(); i++) { + char c = code.charAt(i); + // 随机倾斜值(-0.3~0.3,对应C#的random.Next(-3,4)*0.1f) + float shear = random.nextInt(7) - 3; // -3~3 + shear *= 0.1f; + + // 创建倾斜变换(对应C#的Matrix.Shear) + AffineTransform transform = new AffineTransform(); + transform.shear(shear, 0); // x轴倾斜,y轴不变 + + // 应用倾斜变换到字体(对应C#的shearedFont) + Font shearedFont = baseFont.deriveFont(transform); + graphics.setFont(shearedFont); + + // 绘制主文字(黑色,对应C#的SolidBrush(Color.Black)) + graphics.setColor(Color.BLACK); + graphics.drawString(String.valueOf(c), currentX, 8); + + // 绘制模糊阴影(对应C#的模糊效果:偏移1像素,半透明灰色) + graphics.setColor(new Color(128, 128, 128, 100)); // ARGB,100是透明度 + graphics.drawString(String.valueOf(c), currentX + 1, 9); + graphics.drawString(String.valueOf(c), currentX - 1, 7); + + // 重置变换(避免影响下一个字符) + graphics.setTransform(new AffineTransform()); + + // 增加字符间距 + currentX += charSpacing; + } + + // ===== 6. 绘制50个椭圆前景干扰点(对应C#的前景干扰点)===== + for (int i = 0; i < 50; i++) { + int x = random.nextInt(120); + int y = random.nextInt(40); + + Color randomColor = new Color( + random.nextInt(101) + 50, // 50~150 + random.nextInt(256), + random.nextInt(256), + random.nextInt(256) + ); + graphics.setColor(randomColor); + graphics.fillOval(x, y, 1, 1); // 1x1的椭圆点 + } + + // ===== 7. 保存图片到输出流(PNG格式,对应C#的bitmap.Save)===== + ImageIO.write(image, "PNG", outputStream); + } finally { + // 释放资源(对应C#的using) + graphics.dispose(); + image.flush(); + } + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/PushHelper.java b/WebErp/weberp/src/main/java/org/example/Utils/PushHelper.java new file mode 100644 index 0000000..fb1291f --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/PushHelper.java @@ -0,0 +1,381 @@ +package org.example.Utils; + + +import org.example.Impl.BaseImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Component; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; + +/** + * 功能说明:消息推送类 + **/ +public class PushHelper extends BaseImpl { + private static final Logger log = LoggerFactory.getLogger(PushHelper.class); + + private String billid; + private String stepcode; + private String menuid; + private String beforeusers; + private String beforemenuname; + private int beforeid; + private Map pushMessages = new HashMap<>(); + + + public PushHelper(String billid, String stepcode, String menuid, String beforeusers, String beforemenuname, int beforeid) { + this.billid = billid; + this.menuid = menuid; + this.stepcode = stepcode; + this.beforeusers = beforeusers; + this.beforemenuname = beforemenuname; + this.beforeid = beforeid; + } + + /** + * 推送单据审核确认消息 + */ + public void push_bill_comfirm_message() { + + } + + /** + * 推送单据审核消息 + * + * @remarks 创建人:龚宇超 + * 创建日期:2017-07-10 + * 修改人: + * 修改日期: + * 修改备注:无 + * 版本:1.0 + */ + public void push_bill_oper_message() { + try { + pushMessages.clear(); + + // 极光推送key + List> systemTabList = jdbcTemplate.queryForList("select * from dbo.p_systemtab"); + if (systemTabList.isEmpty()) { + return; + } + Map systemTab = systemTabList.get(0); + String appKey = (String) systemTab.get("JPushAppKey"); + String masterSecret = (String) systemTab.get("JPushMaster_Secret"); + String clientCode = (String) systemTab.get("ClientCode"); + + if (appKey == null || appKey.isEmpty()) { + return; + } + + String sql = String.format("select operators, typeName as menuname, '2' + CAST(b.id as varchar(10)) as id " + + "from dbo.wms_billflowOper a, dbo.p_systembilltype b " + + "where a.modid = b.typeCode and keyvalue = '%s' and stepcode = '%s' and a.stepover = 0", + billid, stepcode); + List> dtTable = jdbcTemplate.queryForList(sql); + + // 清空上一步人员消息 + if (beforeusers != null && !beforeusers.isEmpty() && beforemenuname != null && !beforemenuname.isEmpty() && beforeid > 0) { + String userSql = String.format("select employeeid as userid, employeename as username " + + "from dbo.P_employeeTab where employeename in (%s)", + "'" + beforeusers.replace(",", "','") + "'"); + List> userList = jdbcTemplate.queryForList(userSql); + + for (Map user : userList) { + String uid = (String) user.get("userid"); + String username = (String) user.get("username"); + + addPushSource(appKey, masterSecret, clientCode, beforeid, 0, beforemenuname, menuid, uid); + } + } else { + // 单据提交处理 + String userSql = String.format("select operators, typeName as menuname, '2' + CAST(b.id as varchar(10)) as id " + + "from dbo.wms_billflowOper a, dbo.p_systembilltype b " + + "where a.modid = b.typeCode and keyvalue = '%s' and a.stepover = 0", + billid); + List> userList = jdbcTemplate.queryForList(userSql); + + if (!userList.isEmpty()) { + Map firstRow = userList.get(0); + beforeusers = (String) firstRow.get("operators"); + beforemenuname = (String) firstRow.get("menuname"); + beforeid = Integer.parseInt((String) firstRow.get("id")); + } + } + + // 推送本次人员消息 + if (dtTable != null && !dtTable.isEmpty()) { + Map firstRow = dtTable.get(0); + String operators = (String) firstRow.get("operators"); + String menuname = (String) firstRow.get("menuname"); + int id = Integer.parseInt((String) firstRow.get("id")); + + String userSql = String.format("select employeeid as userid, employeename as username " + + "from dbo.P_employeeTab where employeename in (%s)", + "'" + operators.replace(",", "','") + "'"); + List> userList = jdbcTemplate.queryForList(userSql); + + for (Map user : userList) { + String uid = (String) user.get("userid"); + String username = (String) user.get("username"); + + String countSql = String.format("select COUNT(1) as total from dbo.wms_BillflowOper " + + "where stepOver = 0 and modid = '%s' and CHARINDEX(',%s,', ',' + operators + ',') > 0", + menuid, username); + List> countList = jdbcTemplate.queryForList(countSql); + int total = countList.isEmpty() ? 0 : Integer.parseInt(countList.get(0).get("total").toString()); + + addPushSource(appKey, masterSecret, clientCode, beforeid, total, beforemenuname, menuid, uid); + } + } else { + // 重新计算人员消息 + if (beforeusers != null && !beforeusers.isEmpty() && beforemenuname != null && !beforemenuname.isEmpty() && beforeid > 0) { + String userSql = String.format("select employeeid as userid, employeename as username " + + "from dbo.P_employeeTab where employeename in (%s)", + "'" + beforeusers.replace(",", "','") + "'"); + List> userList = jdbcTemplate.queryForList(userSql); + + for (Map user : userList) { + String uid = (String) user.get("userid"); + String username = (String) user.get("username"); + + String countSql = String.format("select COUNT(1) as total from dbo.wms_BillflowOper " + + "where stepOver = 0 and modid = '%s' and CHARINDEX(',%s,', ',' + operators + ',') > 0", + menuid, username); + List> countList = jdbcTemplate.queryForList(countSql); + int total = countList.isEmpty() ? 0 : Integer.parseInt(countList.get(0).get("total").toString()); + + addPushSource(appKey, masterSecret, clientCode, beforeid, total, beforemenuname, menuid, uid); + } + } + } + } catch (Exception e) { + log.error("推送单据审核消息异常", e); + } + + ResourceExecutors.submitPush(this::push_all_message); + } + + /** + * 推送基础档案审核确认消息 + * + * @remarks 创建人:龚宇超 + * 创建日期:2017-07-10 + * 修改人: + * 修改日期: + * 修改备注:无 + * 版本:1.0 + */ + public void push_base_comfirm_message() { + + } + + /** + * 推送基础档案审核消息 + * + * @remarks 创建人:龚宇超 + * 创建日期:2017-07-10 + * 修改人: + * 修改日期: + * 修改备注:无 + * 版本:1.0 + */ + public void push_base_oper_message() { + try { + pushMessages.clear(); + + // 极光推送key + List> systemTabList = jdbcTemplate.queryForList("select * from dbo.p_systemtab"); + if (systemTabList.isEmpty()) { + return; + } + Map systemTab = systemTabList.get(0); + String appKey = (String) systemTab.get("JPushAppKey"); + String masterSecret = (String) systemTab.get("JPushMaster_Secret"); + String clientCode = (String) systemTab.get("ClientCode"); + + if (appKey == null || appKey.isEmpty()) { + return; + } + + String sql = String.format("select operators, ToolsName as menuname, '1' + CAST(b.dllid as varchar(10)) as id " + + "from dbo.P_baseflowOper a, dbo.P_systemdlltab b " + + "where a.modid = b.DllCoid and keyvalue = '%s' and stepcode = '%s' and a.stepover = 0", + billid, stepcode); + List> dtTable = jdbcTemplate.queryForList(sql); + + // 清空上一步人员消息 + if (beforeusers != null && !beforeusers.isEmpty() && beforemenuname != null && !beforemenuname.isEmpty() && beforeid > 0) { + String userSql = String.format("select employeeid as userid, employeename as username " + + "from dbo.P_employeeTab where employeename in (%s)", + "'" + beforeusers.replace(",", "','") + "'"); + List> userList = jdbcTemplate.queryForList(userSql); + + for (Map user : userList) { + String uid = (String) user.get("userid"); + addPushSource(appKey, masterSecret, clientCode, beforeid, 0, beforemenuname, menuid, uid); + } + } else { + // 单据提交处理 + String userSql = String.format("select operators, ToolsName as menuname, '1' + CAST(b.dllid as varchar(10)) as id " + + "from dbo.P_baseflowOper a, dbo.P_systemdlltab b " + + "where a.modid = b.DllCoid and keyvalue = '%s' and a.stepover = 0", + billid); + List> userList = jdbcTemplate.queryForList(userSql); + + if (!userList.isEmpty()) { + Map firstRow = userList.get(0); + beforeusers = (String) firstRow.get("operators"); + beforemenuname = (String) firstRow.get("menuname"); + beforeid = Integer.parseInt((String) firstRow.get("id")); + } + } + + // 推送本次人员消息 + if (dtTable != null && !dtTable.isEmpty()) { + Map firstRow = dtTable.get(0); + String operators = (String) firstRow.get("operators"); + String menuname = (String) firstRow.get("menuname"); + int id = Integer.parseInt((String) firstRow.get("id")); + + String userSql = String.format("select employeeid as userid, employeename as username " + + "from dbo.P_employeeTab where employeename in (%s)", + "'" + operators.replace(",", "','") + "'"); + List> userList = jdbcTemplate.queryForList(userSql); + + for (Map user : userList) { + String uid = (String) user.get("userid"); + String username = (String) user.get("username"); + + String countSql = String.format("select COUNT(1) as total from dbo.P_baseflowOper " + + "where stepOver = 0 and modid = '%s' and CHARINDEX(',%s,', ',' + operators + ',') > 0", + menuid, username); + List> countList = jdbcTemplate.queryForList(countSql); + int total = countList.isEmpty() ? 0 : Integer.parseInt(countList.get(0).get("total").toString()); + + addPushSource(appKey, masterSecret, clientCode, beforeid, total, beforemenuname, menuid, uid); + } + } else { + // 终审重新计算人员 + if (beforeusers != null && !beforeusers.isEmpty() && beforemenuname != null && !beforemenuname.isEmpty() && beforeid > 0) { + String userSql = String.format("select employeeid as userid, employeename as username " + + "from dbo.P_employeeTab where employeename in (%s)", + "'" + beforeusers.replace(",", "','") + "'"); + List> userList = jdbcTemplate.queryForList(userSql); + + for (Map user : userList) { + String uid = (String) user.get("userid"); + String username = (String) user.get("username"); + + String countSql = String.format("select COUNT(1) as total from dbo.P_baseflowOper " + + "where stepOver = 0 and modid = '%s' and CHARINDEX(',%s,', ',' + operators + ',') > 0", + menuid, username); + List> countList = jdbcTemplate.queryForList(countSql); + int total = countList.isEmpty() ? 0 : Integer.parseInt(countList.get(0).get("total").toString()); + + addPushSource(appKey, masterSecret, clientCode, beforeid, total, beforemenuname, menuid, uid); + } + } + } + } catch (Exception ex) { + log.error("推送基础档案审核消息异常", ex); + } + + ResourceExecutors.submitPush(this::push_all_message); + } + + /** + * 去除重复消息通知 + * + * @param appkey 应用key + * @param appsecret 应用密钥 + * @param appclict 客户端标识 + * @param id 消息ID + * @param total 总数 + * @param menuname 菜单名称 + * @param menuid 菜单ID + * @param uid 用户ID + */ + private void addPushSource(String appkey, String appsecret, String appclict, int id, int total, String menuname, String menuid, String uid) { + if (pushMessages.containsKey(uid)) { + PushMessage push = pushMessages.get(uid); + if (push.total == 0) { + push.total = total; + } + } else { + PushMessage message = new PushMessage(); + message.appkey = appkey; + message.appsecret = appsecret; + message.appclict = appclict; + message.id = beforeid; + message.total = total; + message.menuname = beforemenuname; + message.menuid = menuid; + message.uid = uid; + pushMessages.put(uid, message); + } + } + + /** + * 推送消息 + * + * @remarks 创建人:龚宇超 + * 创建日期:2017-07-10 + * 修改人: + * 修改日期: + * 修改备注:无 + * 版本:1.0 + */ + private void push_all_message() { +// for (PushMessage item : pushMessages.values()) { +// // 推送android、ios消息 +// JPushClient jPushClient = new JPushClient(item.appkey, item.appsecret); +// +// Map extras = new HashMap<>(); +// extras.put("id", item.id); // 1.基础档案审批通知,2.单据审核通知 +// extras.put("title", item.menuname); +// extras.put("total", item.total); +// extras.put("desc", String.format("您有%d条数据需要审批.", item.total)); +// extras.put("menuid", menuid); +// +// // 发送android消息 +// try { +// PushPayload androidPayload = JPushApi.pushObjectAndroidMessage("1002", item.appclict + "_" + item.uid, extras); +// MessageResult androidResult = jPushClient.sendPush(androidPayload); +// } catch (Exception ex) { +// log.error("发送安卓消息错误", ex); +// } +// +// // 发送ios消息 +// try { +// PushPayload iosPayload = JPushApi.pushObjectIosAliasTitleWithContent( +// item.menuname, +// String.format("您有%d条数据需要审批.", item.total), +// item.appclict + "_" + item.uid, +// extras +// ); +// MessageResult iosResult = jPushClient.sendPush(iosPayload); +// } catch (Exception ex) { +// log.error("发送IOS消息错误", ex); +// } +// } + } + + /** + * 推送实体 + */ + public static class PushMessage { + public String appkey; + public String appsecret; + public String appclict; + public int id; + public int total; + public String menuname; + public String menuid; + public String uid; + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/PyUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/PyUtil.java new file mode 100644 index 0000000..b1b66b7 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/PyUtil.java @@ -0,0 +1,313 @@ +package org.example.Utils; + +public class PyUtil { + private static String strChineseFirstPY = + "YDYQSXMWZSSXJBYMGCCZQPSSQBYCDSCDQLDYLYBSSJGYZZJJFKCCLZDHWDWZJLJPFYYNWJJTMYHZWZHFLZPPQHGSCYYYNJQYXXGJ" + + "HHSDSJNKKTMOMLCRXYPSNQSECCQZGGLLYJLMYZZSECYKYYHQWJSSGGYXYZYJWWKDJHYCHMYXJTLXJYQBYXZLDWRDJRWYSRLDZJPC" + + "BZJJBRCFTLECZSTZFXXZHTRQHYBDLYCZSSYMMRFMYQZPWWJJYFCRWFDFZQPYDDWYXKYJAWJFFXYPSFTZYHHYZYSWCJYXSCLCXXWZ" + + "ZXNBGNNXBXLZSZSBSGPYSYZDHMDZBQBZCWDZZYYTZHBTSYYBZGNTNXQYWQSKBPHHLXGYBFMJEBJHHGQTJCYSXSTKZHLYCKGLYSMZ" + + "XYALMELDCCXGZYRJXSDLTYZCQKCNNJWHJTZZCQLJSTSTBNXBTYXCEQXGKWJYFLZQLYHYXSPSFXLMPBYSXXXYDJCZYLLLSJXFHJXP" + + "JBTFFYABYXBHZZBJYZLWLCZGGBTSSMDTJZXPTHYQTGLJSCQFZKJZJQNLZWLSLHDZBWJNCJZYZSQQYCQYRZCJJWYBRTWPYFTWEXCS" + + "KDZCTBZHYZZYYJXZCFFZZMJYXXSDZZOTTBZLQWFCKSZSXFYRLNYJMBDTHJXSQQCCSBXYYTSYFBXDZTGBCNSLCYZZPSAZYZZSCJCS" + + "HZQYDXLBPJLLMQXTYDZXSQJTZPXLCGLQTZWJBHCTSYJSFXYEJJTLBGXSXJMYJQQPFZASYJNTYDJXKJCDJSZCBARTDCLYJQMWNQNC" + + "LLLKBYBZZSYHQQLTWLCCXTXLLZNTYLNEWYZYXCZXXGRKRMTCNDNJTSYYSSDQDGHSDBJGHRWRQLYBGLXHLGTGXBQJDZPYJSJYJCTM" + + "RNYMGRZJCZGJMZMGXMPRYXKJNYMSGMZJYMKMFXMLDTGFBHCJHKYLPFMDXLQJJSMTQGZSJLQDLDGJYCALCMZCSDJLLNXDJFFFFJCZ" + + "FMZFFPFKHKGDPSXKTACJDHHZDDCRRCFQYJKQCCWJDXHWJLYLLZGCFCQDSMLZPBJJPLSBCJGGDCKKDEZSQCCKJGCGKDJTJDLZYCXK" + + "LQSCGJCLTFPCQCZGWPJDQYZJJBYJHSJDZWGFSJGZKQCCZLLPSPKJGQJHZZLJPLGJGJJTHJJYJZCZMLZLYQBGJWMLJKXZDZNJQSYZ" + + "MLJLLJKYWXMKJLHSKJGBMCLYYMKXJQLBMLLKMDXXKWYXYSLMLPSJQQJQXYXFJTJDXMXXLLCXQBSYJBGWYMBGGBCYXPJYGPEPFGDJ" + + "GBHBNSQJYZJKJKHXQFGQZKFHYGKHDKLLSDJQXPQYKYBNQSXQNSZSWHBSXWHXWBZZXDMNSJBSBKBBZKLYLXGWXDRWYQZMYWSJQLCJ" + + "XXJXKJEQXSCYETLZHLYYYSDZPAQYZCMTLSHTZCFYZYXYLJSDCJQAGYSLCQLYYYSHMRQQKLDXZSCSSSYDYCJYSFSJBFRSSZQSBXXP" + + "XJYSDRCKGJLGDKZJZBDKTCSYQPYHSTCLDJDHMXMCGXYZHJDDTMHLTXZXYLYMOHYJCLTYFBQQXPFBDFHHTKSQHZYYWCNXXCRWHOWG" + + "YJLEGWDQCWGFJYCSNTMYTOLBYGWQWESJPWNMLRYDZSZTXYQPZGCWXHNGPYXSHMYQJXZTDPPBFYHZHTJYFDZWKGKZBLDNTSXHQEEG" + + "ZZYLZMMZYJZGXZXKHKSTXNXXWYLYAPSTHXDWHZYMPXAGKYDXBHNHXKDPJNMYHYLPMGOCSLNZHKXXLPZZLBMLSFBHHGYGYYGGBHSC" + + "YAQTYWLXTZQCEZYDQDQMMHTKLLSZHLSJZWFYHQSWSCWLQAZYNYTLSXTHAZNKZZSZZLAXXZWWCTGQQTDDYZTCCHYQZFLXPSLZYGPZ" + + "SZNGLNDQTBDLXGTCTAJDKYWNSYZLJHHZZCWNYYZYWMHYCHHYXHJKZWSXHZYXLYSKQYSPSLYZWMYPPKBYGLKZHTYXAXQSYSHXASMC" + + "HKDSCRSWJPWXSGZJLWWSCHSJHSQNHCSEGNDAQTBAALZZMSSTDQJCJKTSCJAXPLGGXHHGXXZCXPDMMHLDGTYBYSJMXHMRCPXXJZCK" + + "ZXSHMLQXXTTHXWZFKHCCZDYTCJYXQHLXDHYPJQXYLSYYDZOZJNYXQEZYSQYAYXWYPDGXDDXSPPYZNDLTWRHXYDXZZJHTCXMCZLHP" + + "YYYYMHZLLHNXMYLLLMDCPPXHMXDKYCYRDLTXJCHHZZXZLCCLYLNZSHZJZZLNNRLWHYQSNJHXYNTTTKYJPYCHHYEGKCTTWLGQRLGG" + + "TGTYGYHPYHYLQYQGCWYQKPYYYTTTTLHYHLLTYTTSPLKYZXGZWGPYDSSZZDQXSKCQNMJJZZBXYQMJRTFFBTKHZKBXLJJKDXJTLBWF" + + "ZPPTKQTZTGPDGNTPJYFALQMKGXBDCLZFHZCLLLLADPMXDJHLCCLGYHDZFGYDDGCYYFGYDXKSSEBDHYKDKDKHNAXXYBPBYYHXZQGA" + + "FFQYJXDMLJCSQZLLPCHBSXGJYNDYBYQSPZWJLZKSDDTACTBXZDYZYPJZQSJNKKTKNJDJGYYPGTLFYQKASDNTCYHBLWDZHBBYDWJR" + + "YGKZYHEYYFJMSDTYFZJJHGCXPLXHLDWXXJKYTCYKSSSMTWCTTQZLPBSZDZWZXGZAGYKTYWXLHLSPBCLLOQMMZSSLCMBJCSZZKYDC" + + "ZJGQQDSMCYTZQQLWZQZXSSFPTTFQMDDZDSHDTDWFHTDYZJYQJQKYPBDJYYXTLJHDRQXXXHAYDHRJLKLYTWHLLRLLRCXYLBWSRSZZ" + + "SYMKZZHHKYHXKSMDSYDYCJPBZBSQLFCXXXNXKXWYWSDZYQOGGQMMYHCDZTTFJYYBGSTTTYBYKJDHKYXBELHTYPJQNFXFDYKZHQKZ" + + "BYJTZBXHFDXKDASWTAWAJLDYJSFHBLDNNTNQJTJNCHXFJSRFWHZFMDRYJYJWZPDJKZYJYMPCYZNYNXFBYTFYFWYGDBNZZZDNYTXZ" + + "EMMQBSQEHXFZMBMFLZZSRXYMJGSXWZJSPRYDJSJGXHJJGLJJYNZZJXHGXKYMLPYYYCXYTWQZSWHWLYRJLPXSLSXMFSWWKLCTNXNY" + + "NPSJSZHDZEPTXMYYWXYYSYWLXJQZQXZDCLEEELMCPJPCLWBXSQHFWWTFFJTNQJHJQDXHWLBYZNFJLALKYYJLDXHHYCSTYYWNRJYX" + + "YWTRMDRQHWQCMFJDYZMHMYYXJWMYZQZXTLMRSPWWCHAQBXYGZYPXYYRRCLMPYMGKSJSZYSRMYJSNXTPLNBAPPYPYLXYYZKYNLDZY" + + "JZCZNNLMZHHARQMPGWQTZMXXMLLHGDZXYHXKYXYCJMFFYYHJFSBSSQLXXNDYCANNMTCJCYPRRNYTYQNYYMBMSXNDLYLYSLJRLXYS" + + "XQMLLYZLZJJJKYZZCSFBZXXMSTBJGNXYZHLXNMCWSCYZYFZLXBRNNNYLBNRTGZQYSATSWRYHYJZMZDHZGZDWYBSSCSKXSYHYTXXG" + + "CQGXZZSHYXJSCRHMKKBXCZJYJYMKQHZJFNBHMQHYSNJNZYBKNQMCLGQHWLZNZSWXKHLJHYYBQLBFCDSXDLDSPFZPSKJYZWZXZDDX" + + "JSMMEGJSCSSMGCLXXKYYYLNYPWWWGYDKZJGGGZGGSYCKNJWNJPCXBJJTQTJWDSSPJXZXNZXUMELPXFSXTLLXCLJXJJLJZXCTPSWX" + + "LYDHLYQRWHSYCSQYYBYAYWJJJQFWQCQQCJQGXALDBZZYJGKGXPLTZYFXJLTPADKYQHPMATLCPDCKBMTXYBHKLENXDLEEGQDYMSAW" + + "HZMLJTWYGXLYQZLJEEYYBQQFFNLYXRDSCTGJGXYYNKLLYQKCCTLHJLQMKKZGCYYGLLLJDZGYDHZWXPYSJBZKDZGYZZHYWYFQYTYZ" + + "SZYEZZLYMHJJHTSMQWYZLKYYWZCSRKQYTLTDXWCTYJKLWSQZWBDCQYNCJSRSZJLKCDCDTLZZZACQQZZDDXYPLXZBQJYLZLLLQDDZ" + + "QJYJYJZYXNYYYNYJXKXDAZWYRDLJYYYRJLXLLDYXJCYWYWNQCCLDDNYYYNYCKCZHXXCCLGZQJGKWPPCQQJYSBZZXYJSQPXJPZBSB" + + "DSFNSFPZXHDWZTDWPPTFLZZBZDMYYPQJRSDZSQZSQXBDGCPZSWDWCSQZGMDHZXMWWFYBPDGPHTMJTHZSMMBGZMBZJCFZWFZBBZMQ" + + "CFMBDMCJXLGPNJBBXGYHYYJGPTZGZMQBQTCGYXJXLWZKYDPDYMGCFTPFXYZTZXDZXTGKMTYBBCLBJASKYTSSQYYMSZXFJEWLXLLS" + + "ZBQJJJAKLYLXLYCCTSXMCWFKKKBSXLLLLJYXTYLTJYYTDPJHNHNNKBYQNFQYYZBYYESSESSGDYHFHWTCJBSDZZTFDMXHCNJZYMQW" + + "SRYJDZJQPDQBBSTJGGFBKJBXTGQHNGWJXJGDLLTHZHHYYYYYYSXWTYYYCCBDBPYPZYCCZYJPZYWCBDLFWZCWJDXXHYHLHWZZXJTC" + + "ZLCDPXUJCZZZLYXJJTXPHFXWPYWXZPTDZZBDZCYHJHMLXBQXSBYLRDTGJRRCTTTHYTCZWMXFYTWWZCWJWXJYWCSKYBZSCCTZQNHX" + + "NWXXKHKFHTSWOCCJYBCMPZZYKBNNZPBZHHZDLSYDDYTYFJPXYNGFXBYQXCBHXCPSXTYZDMKYSNXSXLHKMZXLYHDHKWHXXSSKQYHH" + + "CJYXGLHZXCSNHEKDTGZXQYPKDHEXTYKCNYMYYYPKQYYYKXZLTHJQTBYQHXBMYHSQCKWWYLLHCYYLNNEQXQWMCFBDCCMLJGGXDQKT" + + "LXKGNQCDGZJWYJJLYHHQTTTNWCHMXCXWHWSZJYDJCCDBQCDGDNYXZTHCQRXCBHZTQCBXWGQWYYBXHMBYMYQTYEXMQKYAQYRGYZSL" + + "FYKKQHYSSQYSHJGJCNXKZYCXSBXYXHYYLSTYCXQTHYSMGSCPMMGCCCCCMTZTASMGQZJHKLOSQYLSWTMXSYQKDZLJQQYPLSYCZTCQ" + + "QPBBQJZCLPKHQZYYXXDTDDTSJCXFFLLCHQXMJLWCJCXTSPYCXNDTJSHJWXDQQJSKXYAMYLSJHMLALYKXCYYDMNMDQMXMCZNNCYBZ" + + "KKYFLMCHCMLHXRCJJHSYLNMTJZGZGYWJXSRXCWJGJQHQZDQJDCJJZKJKGDZQGJJYJYLXZXXCDQHHHEYTMHLFSBDJSYYSHFYSTCZQ" + + "LPBDRFRZTZYKYWHSZYQKWDQZRKMSYNBCRXQBJYFAZPZZEDZCJYWBCJWHYJBQSZYWRYSZPTDKZPFPBNZTKLQYHBBZPNPPTYZZYBQN" + + "YDCPJMMCYCQMCYFZZDCMNLFPBPLNGQJTBTTNJZPZBBZNJKLJQYLNBZQHKSJZNGGQSZZKYXSHPZSNBCGZKDDZQANZHJKDRTLZLSWJ" + + "LJZLYWTJNDJZJHXYAYNCBGTZCSSQMNJPJYTYSWXZFKWJQTKHTZPLBHSNJZSYZBWZZZZLSYLSBJHDWWQPSLMMFBJDWAQYZTCJTBNN" + + "WZXQXCDSLQGDSDPDZHJTQQPSWLYYJZLGYXYZLCTCBJTKTYCZJTQKBSJLGMGZDMCSGPYNJZYQYYKNXRPWSZXMTNCSZZYXYBYHYZAX" + + "YWQCJTLLCKJJTJHGDXDXYQYZZBYWDLWQCGLZGJGQRQZCZSSBCRPCSKYDZNXJSQGXSSJMYDNSTZTPBDLTKZWXQWQTZEXNQCZGWEZK" + + "SSBYBRTSSSLCCGBPSZQSZLCCGLLLZXHZQTHCZMQGYZQZNMCOCSZJMMZSQPJYGQLJYJPPLDXRGZYXCCSXHSHGTZNLZWZKJCXTCFCJ" + + "XLBMQBCZZWPQDNHXLJCTHYZLGYLNLSZZPCXDSCQQHJQKSXZPBAJYEMSMJTZDXLCJYRYYNWJBNGZZTMJXLTBSLYRZPYLSSCNXPHLL" + + "HYLLQQZQLXYMRSYCXZLMMCZLTZSDWTJJLLNZGGQXPFSKYGYGHBFZPDKMWGHCXMSGDXJMCJZDYCABXJDLNBCDQYGSKYDQTXDJJYXM" + + "SZQAZDZFSLQXYJSJZYLBTXXWXQQZBJZUFBBLYLWDSLJHXJYZJWTDJCZFQZQZZDZSXZZQLZCDZFJHYSPYMPQZMLPPLFFXJJNZZYLS" + + "JEYQZFPFZKSYWJJJHRDJZZXTXXGLGHYDXCSKYSWMMZCWYBAZBJKSHFHJCXMHFQHYXXYZFTSJYZFXYXPZLCHMZMBXHZZSXYFYMNCW" + + "DABAZLXKTCSHHXKXJJZJSTHYGXSXYYHHHJWXKZXSSBZZWHHHCWTZZZPJXSNXQQJGZYZYWLLCWXZFXXYXYHXMKYYSWSQMNLNAYCYS" + + "PMJKHWCQHYLAJJMZXHMMCNZHBHXCLXTJPLTXYJHDYYLTTXFSZHYXXSJBJYAYRSMXYPLCKDUYHLXRLNLLSTYZYYQYGYHHSCCSMZCT" + + "ZQXKYQFPYYRPFFLKQUNTSZLLZMWWTCQQYZWTLLMLMPWMBZSSTZRBPDDTLQJJBXZCSRZQQYGWCSXFWZLXCCRSZDZMCYGGDZQSGTJS" + + "WLJMYMMZYHFBJDGYXCCPSHXNZCSBSJYJGJMPPWAFFYFNXHYZXZYLREMZGZCYZSSZDLLJCSQFNXZKPTXZGXJJGFMYYYSNBTYLBNLH" + + "PFZDCYFBMGQRRSSSZXYSGTZRNYDZZCDGPJAFJFZKNZBLCZSZPSGCYCJSZLMLRSZBZZLDLSLLYSXSQZQLYXZLSKKBRXBRBZCYCXZZ" + + "ZEEYFGKLZLYYHGZSGZLFJHGTGWKRAAJYZKZQTSSHJJXDCYZUYJLZYRZDQQHGJZXSSZBYKJPBFRTJXLLFQWJHYLQTYMBLPZDXTZYG" + + "BDHZZRBGXHWNJTJXLKSCFSMWLSDQYSJTXKZSCFWJLBXFTZLLJZLLQBLSQMQQCGCZFPBPHZCZJLPYYGGDTGWDCFCZQYYYQYSSCLXZ" + + "SKLZZZGFFCQNWGLHQYZJJCZLQZZYJPJZZBPDCCMHJGXDQDGDLZQMFGPSYTSDYFWWDJZJYSXYYCZCYHZWPBYKXRYLYBHKJKSFXTZJ" + + "MMCKHLLTNYYMSYXYZPYJQYCSYCWMTJJKQYRHLLQXPSGTLYYCLJSCPXJYZFNMLRGJJTYZBXYZMSJYJHHFZQMSYXRSZCWTLRTQZSST" + + "KXGQKGSPTGCZNJSJCQCXHMXGGZTQYDJKZDLBZSXJLHYQGGGTHQSZPYHJHHGYYGKGGCWJZZYLCZLXQSFTGZSLLLMLJSKCTBLLZZSZ" + + "MMNYTPZSXQHJCJYQXYZXZQZCPSHKZZYSXCDFGMWQRLLQXRFZTLYSTCTMJCXJJXHJNXTNRZTZFQYHQGLLGCXSZSJDJLJCYDSJTLNY" + + "XHSZXCGJZYQPYLFHDJSBPCCZHJJJQZJQDYBSSLLCMYTTMQTBHJQNNYGKYRQYQMZGCJKPDCGMYZHQLLSLLCLMHOLZGDYYFZSLJCQZ" + + "LYLZQJESHNYLLJXGJXLYSYYYXNBZLJSSZCQQCJYLLZLTJYLLZLLBNYLGQCHXYYXOXCXQKYJXXXYKLXSXXYQXCYKQXQCSGYXXYQXY" + + "GYTQOHXHXPYXXXULCYEYCHZZCBWQBBWJQZSCSZSSLZYLKDESJZWMYMCYTSDSXXSCJPQQSQYLYYZYCMDJDZYWCBTJSYDJKCYDDJLB" + + "DJJSODZYSYXQQYXDHHGQQYQHDYXWGMMMAJDYBBBPPBCMUUPLJZSMTXERXJMHQNUTPJDCBSSMSSSTKJTSSMMTRCPLZSZMLQDSDMJM" + + "QPNQDXCFYNBFSDQXYXHYAYKQYDDLQYYYSSZBYDSLNTFQTZQPZMCHDHCZCWFDXTMYQSPHQYYXSRGJCWTJTZZQMGWJJTJHTQJBBHWZ" + + "PXXHYQFXXQYWYYHYSCDYDHHQMNMTMWCPBSZPPZZGLMZFOLLCFWHMMSJZTTDHZZYFFYTZZGZYSKYJXQYJZQBHMBZZLYGHGFMSHPZF" + + "ZSNCLPBQSNJXZSLXXFPMTYJYGBXLLDLXPZJYZJYHHZCYWHJYLSJEXFSZZYWXKZJLUYDTMLYMQJPWXYHXSKTQJEZRPXXZHHMHWQPW" + + "QLYJJQJJZSZCPHJLCHHNXJLQWZJHBMZYXBDHHYPZLHLHLGFWLCHYYTLHJXCJMSCPXSTKPNHQXSRTYXXTESYJCTLSSLSTDLLLWWYH" + + "DHRJZSFGXTSYCZYNYHTDHWJSLHTZDQDJZXXQHGYLTZPHCSQFCLNJTCLZPFSTPDYNYLGMJLLYCQHYSSHCHYLHQYQTMZYPBYWRFQYK" + + "QSYSLZDQJMPXYYSSRHZJNYWTQDFZBWWTWWRXCWHGYHXMKMYYYQMSMZHNGCEPMLQQMTCWCTMMPXJPJJHFXYYZSXZHTYBMSTSYJTTQ" + + "QQYYLHYNPYQZLCYZHZWSMYLKFJXLWGXYPJYTYSYXYMZCKTTWLKSMZSYLMPWLZWXWQZSSAQSYXYRHSSNTSRAPXCPWCMGDXHXZDZYF" + + "JHGZTTSBJHGYZSZYSMYCLLLXBTYXHBBZJKSSDMALXHYCFYGMQYPJYCQXJLLLJGSLZGQLYCJCCZOTYXMTMTTLLWTGPXYMZMKLPSZZ" + + "ZXHKQYSXCTYJZYHXSHYXZKXLZWPSQPYHJWPJPWXQQYLXSDHMRSLZZYZWTTCYXYSZZSHBSCCSTPLWSSCJCHNLCGCHSSPHYLHFHHXJ" + + "SXYLLNYLSZDHZXYLSXLWZYKCLDYAXZCMDDYSPJTQJZLNWQPSSSWCTSTSZLBLNXSMNYYMJQBQHRZWTYYDCHQLXKPZWBGQYBKFCMZW" + + "PZLLYYLSZYDWHXPSBCMLJBSCGBHXLQHYRLJXYSWXWXZSLDFHLSLYNJLZYFLYJYCDRJLFSYZFSLLCQYQFGJYHYXZLYLMSTDJCYHBZ" + + "LLNWLXXYGYYHSMGDHXXHHLZZJZXCZZZCYQZFNGWPYLCPKPYYPMCLQKDGXZGGWQBDXZZKZFBXXLZXJTPJPTTBYTSZZDWSLCHZHSLT" + + "YXHQLHYXXXYYZYSWTXZKHLXZXZPYHGCHKCFSYHUTJRLXFJXPTZTWHPLYXFCRHXSHXKYXXYHZQDXQWULHYHMJTBFLKHTXCWHJFWJC" + + "FPQRYQXCYYYQYGRPYWSGSUNGWCHKZDXYFLXXHJJBYZWTSXXNCYJJYMSWZJQRMHXZWFQSYLZJZGBHYNSLBGTTCSYBYXXWXYHXYYXN" + + "SQYXMQYWRGYQLXBBZLJSYLPSYTJZYHYZAWLRORJMKSCZJXXXYXCHDYXRYXXJDTSQFXLYLTSFFYXLMTYJMJUYYYXLTZCSXQZQHZXL" + + "YYXZHDNBRXXXJCTYHLBRLMBRLLAXKYLLLJLYXXLYCRYLCJTGJCMTLZLLCYZZPZPCYAWHJJFYBDYYZSMPCKZDQYQPBPCJPDCYZMDP" + + "BCYYDYCNNPLMTMLRMFMMGWYZBSJGYGSMZQQQZTXMKQWGXLLPJGZBQCDJJJFPKJKCXBLJMSWMDTQJXLDLPPBXCWRCQFBFQJCZAHZG" + + "MYKPHYYHZYKNDKZMBPJYXPXYHLFPNYYGXJDBKXNXHJMZJXSTRSTLDXSKZYSYBZXJLXYSLBZYSLHXJPFXPQNBYLLJQKYGZMCYZZYM" + + "CCSLCLHZFWFWYXZMWSXTYNXJHPYYMCYSPMHYSMYDYSHQYZCHMJJMZCAAGCFJBBHPLYZYLXXSDJGXDHKXXTXXNBHRMLYJSLTXMRHN" + + "LXQJXYZLLYSWQGDLBJHDCGJYQYCMHWFMJYBMBYJYJWYMDPWHXQLDYGPDFXXBCGJSPCKRSSYZJMSLBZZJFLJJJLGXZGYXYXLSZQYX" + + "BEXYXHGCXBPLDYHWETTWWCJMBTXCHXYQXLLXFLYXLLJLSSFWDPZSMYJCLMWYTCZPCHQEKCQBWLCQYDPLQPPQZQFJQDJHYMMCXTXD" + + "RMJWRHXCJZYLQXDYYNHYYHRSLSRSYWWZJYMTLTLLGTQCJZYABTCKZCJYCCQLJZQXALMZYHYWLWDXZXQDLLQSHGPJFJLJHJABCQZD" + + "JGTKHSSTCYJLPSWZLXZXRWGLDLZRLZXTGSLLLLZLYXXWGDZYGBDPHZPBRLWSXQBPFDWOFMWHLYPCBJCCLDMBZPBZZLCYQXLDOMZB" + + "LZWPDWYYGDSTTHCSQSCCRSSSYSLFYBFNTYJSZDFNDPDHDZZMBBLSLCMYFFGTJJQWFTMTPJWFNLBZCMMJTGBDZLQLPYFHYYMJYLSD" + + "CHDZJWJCCTLJCLDTLJJCPDDSQDSSZYBNDBJLGGJZXSXNLYCYBJXQYCBYLZCFZPPGKCXZDZFZTJJFJSJXZBNZYJQTTYJYHTYCZHYM" + + "DJXTTMPXSPLZCDWSLSHXYPZGTFMLCJTYCBPMGDKWYCYZCDSZZYHFLYCTYGWHKJYYLSJCXGYWJCBLLCSNDDBTZBSCLYZCZZSSQDLL" + + "MQYYHFSLQLLXFTYHABXGWNYWYYPLLSDLDLLBJCYXJZMLHLJDXYYQYTDLLLBUGBFDFBBQJZZMDPJHGCLGMJJPGAEHHBWCQXAXHHHZ" + + "CHXYPHJAXHLPHJPGPZJQCQZGJJZZUZDMQYYBZZPHYHYBWHAZYJHYKFGDPFQSDLZMLJXKXGALXZDAGLMDGXMWZQYXXDXXPFDMMSSY" + + "MPFMDMMKXKSYZYSHDZKXSYSMMZZZMSYDNZZCZXFPLSTMZDNMXCKJMZTYYMZMZZMSXHHDCZJEMXXKLJSTLWLSQLYJZLLZJSSDPPMH" + + "NLZJCZYHMXXHGZCJMDHXTKGRMXFWMCGMWKDTKSXQMMMFZZYDKMSCLCMPCGMHSPXQPZDSSLCXKYXTWLWJYAHZJGZQMCSNXYYMMPML" + + "KJXMHLMLQMXCTKZMJQYSZJSYSZHSYJZJCDAJZYBSDQJZGWZQQXFKDMSDJLFWEHKZQKJPEYPZYSZCDWYJFFMZZYLTTDZZEFMZLBNP" + + "PLPLPEPSZALLTYLKCKQZKGENQLWAGYXYDPXLHSXQQWQCQXQCLHYXXMLYCCWLYMQYSKGCHLCJNSZKPYZKCQZQLJPDMDZHLASXLBYD" + + "WQLWDNBQCRYDDZTJYBKBWSZDXDTNPJDTCTQDFXQQMGNXECLTTBKPWSLCTYQLPWYZZKLPYGZCQQPLLKCCYLPQMZCZQCLJSLQZDJXL" + + "DDHPZQDLJJXZQDXYZQKZLJCYQDYJPPYPQYKJYRMPCBYMCXKLLZLLFQPYLLLMBSGLCYSSLRSYSQTMXYXZQZFDZUYSYZTFFMZZSMZQ" + + "HZSSCCMLYXWTPZGXZJGZGSJSGKDDHTQGGZLLBJDZLCBCHYXYZHZFYWXYZYMSDBZZYJGTSMTFXQYXQSTDGSLNXDLRYZZLRYYLXQHT" + + "XSRTZNGZXBNQQZFMYKMZJBZYMKBPNLYZPBLMCNQYZZZSJZHJCTZKHYZZJRDYZHNPXGLFZTLKGJTCTSSYLLGZRZBBQZZKLPKLCZYS" + + "SUYXBJFPNJZZXCDWXZYJXZZDJJKGGRSRJKMSMZJLSJYWQSKYHQJSXPJZZZLSNSHRNYPZTWCHKLPSRZLZXYJQXQKYSJYCZTLQZYBB" + + "YBWZPQDWWYZCYTJCJXCKCWDKKZXSGKDZXWWYYJQYYTCYTDLLXWKCZKKLCCLZCQQDZLQLCSFQCHQHSFSMQZZLNBJJZBSJHTSZDYSJ" + + "QJPDLZCDCWJKJZZLPYCGMZWDJJBSJQZSYZYHHXJPBJYDSSXDZNCGLQMBTSFSBPDZDLZNFGFJGFSMPXJQLMBLGQCYYXBQKDJJQYRF" + + "KZTJDHCZKLBSDZCFJTPLLJGXHYXZCSSZZXSTJYGKGCKGYOQXJPLZPBPGTGYJZGHZQZZLBJLSQFZGKQQJZGYCZBZQTLDXRJXBSXXP" + + "ZXHYZYCLWDXJJHXMFDZPFZHQHQMQGKSLYHTYCGFRZGNQXCLPDLBZCSCZQLLJBLHBZCYPZZPPDYMZZSGYHCKCPZJGSLJLNSCDSLDL" + + "XBMSTLDDFJMKDJDHZLZXLSZQPQPGJLLYBDSZGQLBZLSLKYYHZTTNTJYQTZZPSZQZTLLJTYYLLQLLQYZQLBDZLSLYYZYMDFSZSNHL" + + "XZNCZQZPBWSKRFBSYZMTHBLGJPMCZZLSTLXSHTCSYZLZBLFEQHLXFLCJLYLJQCBZLZJHHSSTBRMHXZHJZCLXFNBGXGTQJCZTMSFZ" + + "KJMSSNXLJKBHSJXNTNLZDNTLMSJXGZJYJCZXYJYJWRWWQNZTNFJSZPZSHZJFYRDJSFSZJZBJFZQZZHZLXFYSBZQLZSGYFTZDCSZX" + + "ZJBQMSZKJRHYJZCKMJKHCHGTXKXQGLXPXFXTRTYLXJXHDTSJXHJZJXZWZLCQSBTXWXGXTXXHXFTSDKFJHZYJFJXRZSDLLLTQSQQZ" + + "QWZXSYQTWGWBZCGZLLYZBCLMQQTZHZXZXLJFRMYZFLXYSQXXJKXRMQDZDMMYYBSQBHGZMWFWXGMXLZPYYTGZYCCDXYZXYWGSYJYZ" + + "NBHPZJSQSYXSXRTFYZGRHZTXSZZTHCBFCLSYXZLZQMZLMPLMXZJXSFLBYZMYQHXJSXRXSQZZZSSLYFRCZJRCRXHHZXQYDYHXSJJH" + + "ZCXZBTYNSYSXJBQLPXZQPYMLXZKYXLXCJLCYSXXZZLXDLLLJJYHZXGYJWKJRWYHCPSGNRZLFZWFZZNSXGXFLZSXZZZBFCSYJDBRJ" + + "KRDHHGXJLJJTGXJXXSTJTJXLYXQFCSGSWMSBCTLQZZWLZZKXJMLTMJYHSDDBXGZHDLBMYJFRZFSGCLYJBPMLYSMSXLSZJQQHJZFX" + + "GFQFQBPXZGYYQXGZTCQWYLTLGWSGWHRLFSFGZJMGMGBGTJFSYZZGZYZAFLSSPMLPFLCWBJZCLJJMZLPJJLYMQDMYYYFBGYGYZMLY" + + "ZDXQYXRQQQHSYYYQXYLJTYXFSFSLLGNQCYHYCWFHCCCFXPYLYPLLZYXXXXXKQHHXSHJZCFZSCZJXCPZWHHHHHAPYLQALPQAFYHXD" + + "YLUKMZQGGGDDESRNNZLTZGCHYPPYSQJJHCLLJTOLNJPZLJLHYMHEYDYDSQYCDDHGZUNDZCLZYZLLZNTNYZGSLHSLPJJBDGWXPCDU" + + "TJCKLKCLWKLLCASSTKZZDNQNTTLYYZSSYSSZZRYLJQKCQDHHCRXRZYDGRGCWCGZQFFFPPJFZYNAKRGYWYQPQXXFKJTSZZXSWZDDF" + + "BBXTBGTZKZNPZZPZXZPJSZBMQHKCYXYLDKLJNYPKYGHGDZJXXEAHPNZKZTZCMXCXMMJXNKSZQNMNLWBWWXJKYHCPSTMCSQTZJYXT" + + "PCTPDTNNPGLLLZSJLSPBLPLQHDTNJNLYYRSZFFJFQWDPHZDWMRZCCLODAXNSSNYZRESTYJWJYJDBCFXNMWTTBYLWSTSZGYBLJPXG" + + "LBOCLHPCBJLTMXZLJYLZXCLTPNCLCKXTPZJSWCYXSFYSZDKNTLBYJCYJLLSTGQCBXRYZXBXKLYLHZLQZLNZCXWJZLJZJNCJHXMNZ" + + "ZGJZZXTZJXYCYYCXXJYYXJJXSSSJSTSSTTPPGQTCSXWZDCSYFPTFBFHFBBLZJCLZZDBXGCXLQPXKFZFLSYLTUWBMQJHSZBMDDBCY" + + "SCCLDXYCDDQLYJJWMQLLCSGLJJSYFPYYCCYLTJANTJJPWYCMMGQYYSXDXQMZHSZXPFTWWZQSWQRFKJLZJQQYFBRXJHHFWJJZYQAZ" + + "MYFRHCYYBYQWLPEXCCZSTYRLTTDMQLYKMBBGMYYJPRKZNPBSXYXBHYZDJDNGHPMFSGMWFZMFQMMBCMZZCJJLCNUXYQLMLRYGQZCY" + + "XZLWJGCJCGGMCJNFYZZJHYCPRRCMTZQZXHFQGTJXCCJEAQCRJYHPLQLSZDJRBCQHQDYRHYLYXJSYMHZYDWLDFRYHBPYDTSSCNWBX" + + "GLPZMLZZTQSSCPJMXXYCSJYTYCGHYCJWYRXXLFEMWJNMKLLSWTXHYYYNCMMCWJDQDJZGLLJWJRKHPZGGFLCCSCZMCBLTBHBQJXQD" + + "SPDJZZGKGLFQYWBZYZJLTSTDHQHCTCBCHFLQMPWDSHYYTQWCNZZJTLBYMBPDYYYXSQKXWYYFLXXNCWCXYPMAELYKKJMZZZBRXYYQ" + + "JFLJPFHHHYTZZXSGQQMHSPGDZQWBWPJHZJDYSCQWZKTXXSQLZYYMYSDZGRXCKKUJLWPYSYSCSYZLRMLQSYLJXBCXTLWDQZPCYCYK" + + "PPPNSXFYZJJRCEMHSZMSXLXGLRWGCSTLRSXBZGBZGZTCPLUJLSLYLYMTXMTZPALZXPXJTJWTCYYZLBLXBZLQMYLXPGHDSLSSDMXM" + + "BDZZSXWHAMLCZCPJMCNHJYSNSYGCHSKQMZZQDLLKABLWJXSFMOCDXJRRLYQZKJMYBYQLYHETFJZFRFKSRYXFJTWDSXXSYSQJYSLY" + + "XWJHSNLXYYXHBHAWHHJZXWMYLJCSSLKYDZTXBZSYFDXGXZJKHSXXYBSSXDPYNZWRPTQZCZENYGCXQFJYKJBZMLJCMQQXUOXSLYXX" + + "LYLLJDZBTYMHPFSTTQQWLHOKYBLZZALZXQLHZWRRQHLSTMYPYXJJXMQSJFNBXYXYJXXYQYLTHYLQYFMLKLJTMLLHSZWKZHLJMLHL" + + "JKLJSTLQXYLMBHHLNLZXQJHXCFXXLHYHJJGBYZZKBXSCQDJQDSUJZYYHZHHMGSXCSYMXFEBCQWWRBPYYJQTYZCYQYQQZYHMWFFHG" + + "ZFRJFCDPXNTQYZPDYKHJLFRZXPPXZDBBGZQSTLGDGYLCQMLCHHMFYWLZYXKJLYPQHSYWMQQGQZMLZJNSQXJQSYJYCBEHSXFSZPXZ" + + "WFLLBCYYJDYTDTHWZSFJMQQYJLMQXXLLDTTKHHYBFPWTYYSQQWNQWLGWDEBZWCMYGCULKJXTMXMYJSXHYBRWFYMWFRXYQMXYSZTZ" + + "ZTFYKMLDHQDXWYYNLCRYJBLPSXCXYWLSPRRJWXHQYPHTYDNXHHMMYWYTZCSQMTSSCCDALWZTCPQPYJLLQZYJSWXMZZMMYLMXCLMX" + + "CZMXMZSQTZPPQQBLPGXQZHFLJJHYTJSRXWZXSCCDLXTYJDCQJXSLQYCLZXLZZXMXQRJMHRHZJBHMFLJLMLCLQNLDXZLLLPYPSYJY" + + "SXCQQDCMQJZZXHNPNXZMEKMXHYKYQLXSXTXJYYHWDCWDZHQYYBGYBCYSCFGPSJNZDYZZJZXRZRQJJYMCANYRJTLDPPYZBSTJKXXZ" + + "YPFDWFGZZRPYMTNGXZQBYXNBUFNQKRJQZMJEGRZGYCLKXZDSKKNSXKCLJSPJYYZLQQJYBZSSQLLLKJXTBKTYLCCDDBLSPPFYLGYD" + + "TZJYQGGKQTTFZXBDKTYYHYBBFYTYYBCLPDYTGDHRYRNJSPTCSNYJQHKLLLZSLYDXXWBCJQSPXBPJZJCJDZFFXXBRMLAZHCSNDLBJ" + + "DSZBLPRZTSWSBXBCLLXXLZDJZSJPYLYXXYFTFFFBHJJXGBYXJPMMMPSSJZJMTLYZJXSWXTYLEDQPJMYGQZJGDJLQJWJQLLSJGJGY" + + "GMSCLJJXDTYGJQJQJCJZCJGDZZSXQGSJGGCXHQXSNQLZZBXHSGZXCXYLJXYXYYDFQQJHJFXDHCTXJYRXYSQTJXYEFYYSSYYJXNCY" + + "ZXFXMSYSZXYYSCHSHXZZZGZZZGFJDLTYLNPZGYJYZYYQZPBXQBDZTZCZYXXYHHSQXSHDHGQHJHGYWSZTMZMLHYXGEBTYLZKQWYTJ" + + "ZRCLEKYSTDBCYKQQSAYXCJXWWGSBHJYZYDHCSJKQCXSWXFLTYNYZPZCCZJQTZWJQDZZZQZLJJXLSBHPYXXPSXSHHEZTXFPTLQYZZ" + + "XHYTXNCFZYYHXGNXMYWXTZSJPTHHGYMXMXQZXTSBCZYJYXXTYYZYPCQLMMSZMJZZLLZXGXZAAJZYXJMZXWDXZSXZDZXLEYJJZQBH" + + "ZWZZZQTZPSXZTDSXJJJZNYAZPHXYYSRNQDTHZHYYKYJHDZXZLSWCLYBZYECWCYCRYLCXNHZYDZYDYJDFRJJHTRSQTXYXJRJHOJYN" + + "XELXSFSFJZGHPZSXZSZDZCQZBYYKLSGSJHCZSHDGQGXYZGXCHXZJWYQWGYHKSSEQZZNDZFKWYSSTCLZSTSYMCDHJXXYWEYXCZAYD" + + "MPXMDSXYBSQMJMZJMTZQLPJYQZCGQHXJHHLXXHLHDLDJQCLDWBSXFZZYYSCHTYTYYBHECXHYKGJPXHHYZJFXHWHBDZFYZBCAPNPG" + + "NYDMSXHMMMMAMYNBYJTMPXYYMCTHJBZYFCGTYHWPHFTWZZEZSBZEGPFMTSKFTYCMHFLLHGPZJXZJGZJYXZSBBQSCZZLZCCSTPGXM" + + "JSFTCCZJZDJXCYBZLFCJSYZFGSZLYBCWZZBYZDZYPSWYJZXZBDSYUXLZZBZFYGCZXBZHZFTPBGZGEJBSTGKDMFHYZZJHZLLZZGJQ" + + "ZLSFDJSSCBZGPDLFZFZSZYZYZSYGCXSNXXCHCZXTZZLJFZGQSQYXZJQDCCZTQCDXZJYQJQCHXZTDLGSCXZSYQJQTZWLQDQZTQCHQ" + + "QJZYEZZZPBWKDJFCJPZTYPQYQTTYNLMBDKTJZPQZQZZFPZSBNJLGYJDXJDZZKZGQKXDLPZJTCJDQBXDJQJSTCKNXBXZMSLYJCQMT" + + "JQWWCJQNJNLLLHJCWQTBZQYDZCZPZZDZYDDCYZZZCCJTTJFZDPRRTZTJDCQTQZDTJNPLZBCLLCTZSXKJZQZPZLBZRBTJDCXFCZDB" + + "CCJJLTQQPLDCGZDBBZJCQDCJWYNLLZYZCCDWLLXWZLXRXNTQQCZXKQLSGDFQTDDGLRLAJJTKUYMKQLLTZYTDYYCZGJWYXDXFRSKS" + + "TQTENQMRKQZHHQKDLDAZFKYPBGGPZREBZZYKZZSPEGJXGYKQZZZSLYSYYYZWFQZYLZZLZHWCHKYPQGNPGBLPLRRJYXCCSYYHSFZF" + + "YBZYYTGZXYLXCZWXXZJZBLFFLGSKHYJZEYJHLPLLLLCZGXDRZELRHGKLZZYHZLYQSZZJZQLJZFLNBHGWLCZCFJYSPYXZLZLXGCCP" + + "ZBLLCYBBBBUBBCBPCRNNZCZYRBFSRLDCGQYYQXYGMQZWTZYTYJXYFWTEHZZJYWLCCNTZYJJZDEDPZDZTSYQJHDYMBJNYJZLXTSST" + + "PHNDJXXBYXQTZQDDTJTDYYTGWSCSZQFLSHLGLBCZPHDLYZJYCKWTYTYLBNYTSDSYCCTYSZYYEBHEXHQDTWNYGYCLXTSZYSTQMYGZ" + + "AZCCSZZDSLZCLZRQXYYELJSBYMXSXZTEMBBLLYYLLYTDQYSHYMRQWKFKBFXNXSBYCHXBWJYHTQBPBSBWDZYLKGZSKYHXQZJXHXJX" + + "GNLJKZLYYCDXLFYFGHLJGJYBXQLYBXQPQGZTZPLNCYPXDJYQYDYMRBESJYYHKXXSTMXRCZZYWXYQYBMCLLYZHQYZWQXDBXBZWZMS" + + "LPDMYSKFMZKLZCYQYCZLQXFZZYDQZPZYGYJYZMZXDZFYFYTTQTZHGSPCZMLCCYTZXJCYTJMKSLPZHYSNZLLYTPZCTZZCKTXDHXXT" + + "QCYFKSMQCCYYAZHTJPCYLZLYJBJXTPNYLJYYNRXSYLMMNXJSMYBCSYSYLZYLXJJQYLDZLPQBFZZBLFNDXQKCZFYWHGQMRDSXYCYT" + + "XNQQJZYYPFZXDYZFPRXEJDGYQBXRCNFYYQPGHYJDYZXGRHTKYLNWDZNTSMPKLBTHBPYSZBZTJZSZZJTYYXZPHSSZZBZCZPTQFZMY" + + "FLYPYBBJQXZMXXDJMTSYSKKBJZXHJCKLPSMKYJZCXTMLJYXRZZQSLXXQPYZXMKYXXXJCLJPRMYYGADYSKQLSNDHYZKQXZYZTCGHZ" + + "TLMLWZYBWSYCTBHJHJFCWZTXWYTKZLXQSHLYJZJXTMPLPYCGLTBZZTLZJCYJGDTCLKLPLLQPJMZPAPXYZLKKTKDZCZZBNZDYDYQZ" + + "JYJGMCTXLTGXSZLMLHBGLKFWNWZHDXUHLFMKYSLGXDTWWFRJEJZTZHYDXYKSHWFZCQSHKTMQQHTZHYMJDJSKHXZJZBZZXYMPAGQM" + + "STPXLSKLZYNWRTSQLSZBPSPSGZWYHTLKSSSWHZZLYYTNXJGMJSZSUFWNLSOZTXGXLSAMMLBWLDSZYLAKQCQCTMYCFJBSLXCLZZCL" + + "XXKSBZQCLHJPSQPLSXXCKSLNHPSFQQYTXYJZLQLDXZQJZDYYDJNZPTUZDSKJFSLJHYLZSQZLBTXYDGTQFDBYAZXDZHZJNHHQBYKN" + + "XJJQCZMLLJZKSPLDYCLBBLXKLELXJLBQYCXJXGCNLCQPLZLZYJTZLJGYZDZPLTQCSXFDMNYCXGBTJDCZNBGBQYQJWGKFHTNPYQZQ" + + "GBKPBBYZMTJDYTBLSQMPSXTBNPDXKLEMYYCJYNZCTLDYKZZXDDXHQSHDGMZSJYCCTAYRZLPYLTLKXSLZCGGEXCLFXLKJRTLQJAQZ" + + "NCMBYDKKCXGLCZJZXJHPTDJJMZQYKQSECQZDSHHADMLZFMMZBGNTJNNLGBYJBRBTMLBYJDZXLCJLPLDLPCQDHLXZLYCBLCXZZJAD" + + "JLNZMMSSSMYBHBSQKBHRSXXJMXSDZNZPXLGBRHWGGFCXGMSKLLTSJYYCQLTSKYWYYHYWXBXQYWPYWYKQLSQPTNTKHQCWDQKTWPXX" + + "HCPTHTWUMSSYHBWCRWXHJMKMZNGWTMLKFGHKJYLSYYCXWHYECLQHKQHTTQKHFZLDXQWYZYYDESBPKYRZPJFYYZJCEQDZZDLATZBB" + + "FJLLCXDLMJSSXEGYGSJQXCWBXSSZPDYZCXDNYXPPZYDLYJCZPLTXLSXYZYRXCYYYDYLWWNZSAHJSYQYHGYWWAXTJZDAXYSRLTDPS" + + "SYYFNEJDXYZHLXLLLZQZSJNYQYQQXYJGHZGZCYJCHZLYCDSHWSHJZYJXCLLNXZJJYYXNFXMWFPYLCYLLABWDDHWDXJMCXZTZPMLQ" + + "ZHSFHZYNZTLLDYWLSLXHYMMYLMBWWKYXYADTXYLLDJPYBPWUXJMWMLLSAFDLLYFLBHHHBQQLTZJCQJLDJTFFKMMMBYTHYGDCQRDD" + + "WRQJXNBYSNWZDBYYTBJHPYBYTTJXAAHGQDQTMYSTQXKBTZPKJLZRBEQQSSMJJBDJOTGTBXPGBKTLHQXJJJCTHXQDWJLWRFWQGWSH" + + "CKRYSWGFTGYGBXSDWDWRFHWYTJJXXXJYZYSLPYYYPAYXHYDQKXSHXYXGSKQHYWFDDDPPLCJLQQEEWXKSYYKDYPLTJTHKJLTCYYHH" + + "JTTPLTZZCDLTHQKZXQYSTEEYWYYZYXXYYSTTJKLLPZMCYHQGXYHSRMBXPLLNQYDQHXSXXWGDQBSHYLLPJJJTHYJKYPPTHYYKTYEZ" + + "YENMDSHLCRPQFDGFXZPSFTLJXXJBSWYYSKSFLXLPPLBBBLBSFXFYZBSJSSYLPBBFFFFSSCJDSTZSXZRYYSYFFSYZYZBJTBCTSBSD" + + "HRTJJBYTCXYJEYLXCBNEBJDSYXYKGSJZBXBYTFZWGENYHHTHZHHXFWGCSTBGXKLSXYWMTMBYXJSTZSCDYQRCYTWXZFHMYMCXLZNS" + + "DJTTTXRYCFYJSBSDYERXJLJXBBDEYNJGHXGCKGSCYMBLXJMSZNSKGXFBNBPTHFJAAFXYXFPXMYPQDTZCXZZPXRSYWZDLYBBKTYQP" + + "QJPZYPZJZNJPZJLZZFYSBTTSLMPTZRTDXQSJEHBZYLZDHLJSQMLHTXTJECXSLZZSPKTLZKQQYFSYGYWPCPQFHQHYTQXZKRSGTTSQ" + + "CZLPTXCDYYZXSQZSLXLZMYCPCQBZYXHBSXLZDLTCDXTYLZJYYZPZYZLTXJSJXHLPMYTXCQRBLZSSFJZZTNJYTXMYJHLHPPLCYXQJ" + + "QQKZZSCPZKSWALQSBLCCZJSXGWWWYGYKTJBBZTDKHXHKGTGPBKQYSLPXPJCKBMLLXDZSTBKLGGQKQLSBKKTFXRMDKBFTPZFRTBBR" + + "FERQGXYJPZSSTLBZTPSZQZSJDHLJQLZBPMSMMSXLQQNHKNBLRDDNXXDHDDJCYYGYLXGZLXSYGMQQGKHBPMXYXLYTQWLWGCPBMQXC" + + "YZYDRJBHTDJYHQSHTMJSBYPLWHLZFFNYPMHXXHPLTBQPFBJWQDBYGPNZTPFZJGSDDTQSHZEAWZZYLLTYYBWJKXXGHLFKXDJTMSZS" + + "QYNZGGSWQSPHTLSSKMCLZXYSZQZXNCJDQGZDLFNYKLJCJLLZLMZZNHYDSSHTHZZLZZBBHQZWWYCRZHLYQQJBEYFXXXWHSRXWQHWP" + + "SLMSSKZTTYGYQQWRSLALHMJTQJSMXQBJJZJXZYZKXBYQXBJXSHZTSFJLXMXZXFGHKZSZGGYLCLSARJYHSLLLMZXELGLXYDJYTLFB" + + "HBPNLYZFBBHPTGJKWETZHKJJXZXXGLLJLSTGSHJJYQLQZFKCGNNDJSSZFDBCTWWSEQFHQJBSAQTGYPQLBXBMMYWXGSLZHGLZGQYF" + + "LZBYFZJFRYSFMBYZHQGFWZSYFYJJPHZBYYZFFWODGRLMFTWLBZGYCQXCDJYGZYYYYTYTYDWEGAZYHXJLZYYHLRMGRXXZCLHNELJJ" + + "TJTPWJYBJJBXJJTJTEEKHWSLJPLPSFYZPQQBDLQJJTYYQLYZKDKSQJYYQZLDQTGJQYZJSUCMRYQTHTEJMFCTYHYPKMHYZWJDQFHY" + + "YXWSHCTXRLJHQXHCCYYYJLTKTTYTMXGTCJTZAYYOCZLYLBSZYWJYTSJYHBYSHFJLYGJXXTMZYYLTXXYPZLXYJZYZYYPNHMYMDYYL" + + "BLHLSYYQQLLNJJYMSOYQBZGDLYXYLCQYXTSZEGXHZGLHWBLJHEYXTWQMAKBPQCGYSHHEGQCMWYYWLJYJHYYZLLJJYLHZYHMGSLJL" + + "JXCJJYCLYCJPCPZJZJMMYLCQLNQLJQJSXYJMLSZLJQLYCMMHCFMMFPQQMFYLQMCFFQMMMMHMZNFHHJGTTHHKHSLNCHHYQDXTMMQD" + + "CYZYXYQMYQYLTDCYYYZAZZCYMZYDLZFFFMMYCQZWZZMABTBYZTDMNZZGGDFTYPCGQYTTSSFFWFDTZQSSYSTWXJHXYTSXXYLBYQHW" + + "WKXHZXWZNNZZJZJJQJCCCHYYXBZXZCYZTLLCQXYNJYCYYCYNZZQYYYEWYCZDCJYCCHYJLBTZYYCQWMPWPYMLGKDLDLGKQQBGYCHJ" + + "XY"; + + /** + * 将一串中文转化为拼音首字母(与C# GetChineseSpell完全一致) + * + * @param strText 指定汉字 + * @return 拼音首字母(如:"影响"返回"YX") + */ + public static String GetChineseSpell(String strText) { + // 处理null或空字符串,直接返回原值 + if (strText == null || strText.isEmpty()) { + return strText; + } + + StringBuilder myStr = new StringBuilder(); + // 遍历每个字符(对应C#的foreach (char vChar in strText)) + for (int i = 0; i < strText.length(); i++) { + char vChar = strText.charAt(i); + + // 若为字母,转为大写后添加 + if ((vChar >= 'a' && vChar <= 'z') || (vChar >= 'A' && vChar <= 'Z')) { + myStr.append(Character.toUpperCase(vChar)); + } + // 若为汉字(Unicode范围:19968-40869),从映射表取首字母 + else if ((int) vChar >= 19968 && (int) vChar <= 40869) { + int index = (int) vChar - 19968; + // 防止索引越界(兼容映射表长度异常场景) + if (index >= 0 && index < strChineseFirstPY.length()) { + myStr.append(strChineseFirstPY.charAt(index)); + } + } + // 其他字符(如数字、符号)忽略,与C#逻辑一致 + } + + return myStr.toString(); + } + + /** + * 得到单个汉字拼音的首字母(与C# GetFirstPinyin完全一致) + * + * @param strText 指定汉字 + * @return 单个首字母(如:"影响"返回"Y") + */ + private static String GetFirstPinyin(String strText) { + if (strText == null || strText.isEmpty()) { + return strText; + } + + String myStr = ""; + // 取第一个字符(对应C#的strText.ToCharArray()[0]) + char vChar = strText.charAt(0); + + // 若为字母,直接返回 + if ((vChar >= 'a' && vChar <= 'z') || (vChar >= 'A' && vChar <= 'Z')) { + myStr = String.valueOf(vChar); + } + // 若为汉字,取首字母 + else if ((int) vChar >= 19968 && (int) vChar <= 40869) { + int index = (int) vChar - 19968; + if (index >= 0 && index < strChineseFirstPY.length()) { + myStr = String.valueOf(strChineseFirstPY.charAt(index)); + } + } + + return myStr; + } + + /** + * 显示第一个汉字的首字母+原字符串(与C# AddFirstPinyin完全一致) + * + * @param str 指定汉字 + * @return 首字母+原字符串(如:"影响"返回"Y影响") + */ + private static String AddFirstPinyin(String str) { + if (str == null || str.isEmpty()) { + return ""; + } + + char vChar = str.charAt(0); + // 若为字母,直接返回原字符串 + if ((vChar >= 'a' && vChar <= 'z') || (vChar >= 'A' && vChar <= 'Z')) { + return str; + } + // 若为汉字,首字母+原字符串 + else if ((int) vChar >= 19968 && (int) vChar <= 40869) { + int index = (int) vChar - 19968; + String strNew = ""; + if (index >= 0 && index < strChineseFirstPY.length()) { + strNew = String.valueOf(strChineseFirstPY.charAt(index)); + } + return strNew + str; + } + // 其他字符,返回原字符串 + else { + return str; + + + } + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Utils/Ref.java b/WebErp/weberp/src/main/java/org/example/Utils/Ref.java new file mode 100644 index 0000000..488fdea --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/Ref.java @@ -0,0 +1,19 @@ +package org.example.Utils; + +import java.util.List; + +public class Ref { + private List list; + + public Ref (List list) { + this.list = list; + } + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/RegexUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/RegexUtil.java new file mode 100644 index 0000000..641ebd1 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/RegexUtil.java @@ -0,0 +1,463 @@ +package org.example.Utils; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 正则表达式工具类:SQL Server 转 达梦数据库 SQL 适配工具 + * 核心解决:STUFF+FOR XML PATH、ISNULL、WITH(NOLOCK)、dbo.前缀、单引号别名、分号解析、中括号等兼容问题 + */ +public class RegexUtil { + /** + * 检测字符串是否包含中文字符 + */ + public static final Pattern HasCNReg = Pattern.compile("[\u4e00-\u9fa5]"); + + /** + * 检测字符串是否包含英文字母 + */ + public static final Pattern HasENReg = Pattern.compile("[a-zA-Z]"); + + /** + * 验证字符串是否仅由英文字母组成 + */ + public static final Pattern ENOnlyReg = Pattern.compile("^[a-zA-Z]+$"); + + /** + * 检测字符串是否包含数字 + */ + public static final Pattern HasNumReg = Pattern.compile("\\d"); + + /** + * 验证用户名格式(4-30位,支持中文、字母、数字、下划线和非空白字符) + */ + public static final Pattern UserNameReg = Pattern.compile("^[\\w|\\S|(\\u4e00-\\u9fa5)]{4,30}$"); + + /** + * 验证密码格式(6-20位,支持字母、数字、下划线和非空白字符) + */ + public static final Pattern PasswordReg = Pattern.compile("^[\\w|\\S]{6,20}$"); + + /** + * 验证支付密码格式(6位数字) + */ + public static final Pattern PayPasswordReg = Pattern.compile("[\\d]{6,6}$"); + + /** + * 验证手机号码格式(11位数字) + */ + public static final Pattern PhoneReg = Pattern.compile("^[0-9]{11,11}$"); + + /** + * 验证字符串是否仅由数字组成 + */ + public static final Pattern NumberReg = Pattern.compile("^[0-9]+$"); + + /** + * 验证手机验证码格式(4位数字) + */ + public static final Pattern PhoneCodeReg = Pattern.compile("^[0-9]{4,4}$"); + + /** + * 验证通用验证码格式(4位字母数字组合) + */ + public static final Pattern CheckCodeReg = Pattern.compile("^[0-9a-zA-Z]{4,4}$"); + + /** + * 验证邮箱地址格式 + */ + public static final Pattern EmailReg = Pattern.compile("^[\\w-]+(\\.[\\w-]+)*@[\\w-]+(\\.[\\w-]+)+$"); + + /** + * 验证时间格式(YYYY-MM-DD 或 YYYY/MM/DD 或 YYYY:MM:DD) + */ + public static final Pattern TimeReg = Pattern.compile("^2([0-9]{3})([-|/|:]{1})([0-9]{1,2})([-|/|:]{1})([0-9]{1,2})$"); + + + public static Pattern getWeekPwdRegex() { + String regStr = WebConfigUtil.get("WeekPwdReg", "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[^a-zA-Z0-9]).{8,18}"); + // RegexOptions.Multiline → Pattern.MULTILINE + // RegexOptions.IgnorePatternWhitespace → Pattern.COMMENTS + return Pattern.compile(regStr, Pattern.MULTILINE | Pattern.COMMENTS); + } + + public static void setWeekPwdRegex(Pattern weekPwdRegex) { + WeekPwdRegex = weekPwdRegex; + } + + private static Pattern WeekPwdRegex; + + // -------------------------- 达梦SQL适配核心正则 -------------------------- + // 1. 移除dbo.前缀(忽略大小写) + private static final Pattern DBO_PREFIX_PATTERN = Pattern.compile("\\bdbo\\.", Pattern.CASE_INSENSITIVE); + // 匹配 model:显式兼容 a.model、model.aaa、as model、model as 等所有场景 +// 正则说明: +// (?<=[^a-zA-Z0-9_\"]|\\.) 正向后顾:model前面是「非字母/数字/下划线/双引号」 或 「.点号」 +// model 匹配model关键字 +// (?=[^a-zA-Z0-9_\"]|\\.) 正向前瞻:model后面是「非字母/数字/下划线/双引号」 或 「.点号」 +// Pattern.CASE_INSENSITIVE 忽略大小写(兼容 MODEL、Model 等写法) + private static final Pattern INDEPENDENT_MODEL_PATTERN = Pattern.compile( + "(?<=[^a-zA-Z0-9_\"]|\\.)model(?=[^a-zA-Z0-9_\"]|\\.)", + Pattern.CASE_INSENSITIVE + ); + + // 匹配 new:逻辑和 model 完全一致,仅替换关键字为 new + private static final Pattern INDEPENDENT_NEW_PATTERN = Pattern.compile( + "(?<=[^a-zA-Z0-9_\"]|\\.)new(?=[^a-zA-Z0-9_\"]|\\.)", + Pattern.CASE_INSENSITIVE + ); + + //匹配list + private static final Pattern INDEPENDENT_list_PATTERN = Pattern.compile( + "(?<=[^a-zA-Z0-9_\"]|\\.)list(?=[^a-zA-Z0-9_\"]|\\.)", + Pattern.CASE_INSENSITIVE + ); + + //匹配RowNum + + private static final Pattern INDEPENDENT_RowNum_PATTERN = Pattern.compile( + "(?<=[^a-zA-Z0-9_\"]|\\.)RowNum(?=[^a-zA-Z0-9_\"]|\\.)", + Pattern.CASE_INSENSITIVE + ); + + // 3. 匹配as后单引号包裹的列别名(如 as '到访位置') + private static final Pattern ALIAS_SINGLE_QUOTE_PATTERN = Pattern.compile("(?i)(as)\\s*'([^']+)'"); + // 4. 匹配SQLServer的with(rowlock)行锁语法 + private static final Pattern SQLSERVER_ROWLOCK_PATTERN = Pattern.compile("(?i)\\s+with\\s*\\(\\s*rowlock\\s*\\)"); + + // 修改后的sql有问题,暂时弃用 2026.1.23 + // 5. 匹配STUFF+FOR XML PATH的完整拼接块(增强版,兼容TYPE/MAX/数字长度) + // 核心正则:匹配 STUFF 开始到 AS 前的完整片段 + // 核心:STUFF.*?(?=\\s+AS) → 非贪婪匹配STUFF后所有字符,直到遇到" AS"(正向预查,不包含AS) + + private static final Pattern STUFF_TO_AS_PATTERN = Pattern.compile( + "STUFF.*?(?=\\s+AS)", + Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE + ); + + // 6. 匹配WITH(NOLOCK)表提示 + private static final Pattern NOLOCK_PATTERN = Pattern.compile("\\s+WITH\\s*\\(\\s*NOLOCK\\s*\\)", Pattern.CASE_INSENSITIVE); + // 7. 匹配ISNULL函数 + private static final Pattern ISNULL_PATTERN = Pattern.compile("ISNULL\\s*\\(\\s*(.+?)\\s*,\\s*(.+?)\\s*\\)", Pattern.CASE_INSENSITIVE); + // 8. 匹配NVARCHAR(MAX) + private static final Pattern NVARCHAR_MAX_PATTERN = Pattern.compile("NVARCHAR\\s*\\(\\s*MAX\\s*\\)", Pattern.CASE_INSENSITIVE); + // 9. 匹配多余空白字符(空格/换行/制表符) + private static final Pattern BLANK_SPACE_PATTERN = Pattern.compile("\\s+"); + // 10.匹配.后的至少含有一个中文字符 +// private static final Pattern DOT_CN_IDENTIFIER_PATTERN = Pattern.compile("(?<=\\.)[^,,\\s]*[\\u4e00-\\u9fa5][^,,\\s]*"); + + + // 匹配--开头的单行注释(兼容换行/空格) + private static final Pattern SQL_LINE_COMMENT_PATTERN = Pattern.compile( + "--.*?(?=\\r|\\n|$)", + Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE + ); + + // 仅匹配--开头的注释内容 +// private static final Pattern EXACT_COMMENT_PATTERN = Pattern.compile("--[^\\s]*"); + private static final Pattern COMMENT_PATTERN = Pattern.compile("(?= 16 && second <= 31) + || (first == 192 && second == 168) + || (first == 169 && second == 254) + || (first == 100 && second >= 64 && second <= 127) + || (first == 198 && (second == 18 || second == 19)) + || (first == 169 && second == 254 && third == 169 && fourth == 254); + } + + return raw.length == 16 && ((raw[0] & 0xfe) == 0xfc); + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/RequestUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/RequestUtil.java new file mode 100644 index 0000000..9f635a7 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/RequestUtil.java @@ -0,0 +1,569 @@ +package org.example.Utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.Part; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +//附件工具类,(转换C#反编译方法) +public class RequestUtil { + private static final Logger log = LoggerFactory.getLogger(RequestUtil.class); + + + // 文件上传,获取文件名等工具 +// 请求级别的流缓存(ThreadLocal避免多请求串流) + private static final ThreadLocal rawContentCache = new ThreadLocal<>(); + private static final ThreadLocal readEntityBodyMode = new ThreadLocal<>(); + + /** + * (类似C#的Request.Files.Count) + * 获取文件数量 + * + * @param request 请求对象 + */ + public static boolean hasUploadedFiles(HttpServletRequest request) { + String contentType = request.getContentType(); + + // 处理multipart/form-data类型 + if (contentType != null && contentType.startsWith("multipart/form-data")) { + return hasMultipartFiles(request); + } + // 处理application/octet-stream类型 + else if (contentType != null && contentType.equals("application/octet-stream")) { +// C#对应Request.Files.Count 始终为 0,纯二进制流 +// return request.getContentLength() > 0; + return false; + } + + return false; + } + + /** + * 获取所有有效上传文件的文件名 + */ + public static List getUploadedFileNames(HttpServletRequest request) { + List fileNames = new ArrayList<>(); + String contentType = request.getContentType(); + + // 处理multipart/form-data类型 + if (contentType != null && contentType.startsWith("multipart/form-data")) { + try { + for (Part part : request.getParts()) { + String fileName = getSubmittedFileName(part); + if (fileName != null && !fileName.isEmpty() && part.getSize() > 0) { + fileNames.add(fileName); + } + } + } catch (Exception e) { + log.error("Exception caught", e); + } + } + // 处理application/octet-stream类型(生成默认文件名) + else if (contentType != null && contentType.equals("application/octet-stream") && request.getContentLength() > 0) { + // 二进制流没有文件名,生成一个UUID作为文件名 + fileNames.add(generateDefaultFileName()); + } + + return fileNames; + } + + /** + * 保存上传的文件到指定目录 + * + * @param request 请求对象 + * @param saveDir 保存目录路径 + * @return 成功保存的文件名列表 + */ + public static List saveUploadedFiles(HttpServletRequest request, String saveDir) { + List savedFiles = new ArrayList<>(); + String contentType = request.getContentType(); + + // 确保保存目录存在 + try { + Files.createDirectories(Paths.get(saveDir)); + } catch (IOException e) { + log.error("Exception caught", e); + return savedFiles; + } + + // 处理multipart/form-data类型 + if (contentType != null && contentType.startsWith("multipart/form-data")) { + try { + for (Part part : request.getParts()) { + String fileName = getSubmittedFileName(part); + if (fileName != null && !fileName.isEmpty() && part.getSize() > 0) { + // 构建完整保存路径 + String savePath = saveDir.endsWith("/") ? saveDir + fileName : saveDir + "/" + fileName; + part.write(savePath); + savedFiles.add(fileName); + } + } + } catch (Exception e) { + log.error("Exception caught", e); + } + } + // 处理application/octet-stream类型 + else if (contentType != null && contentType.equals("application/octet-stream") && request.getContentLength() > 0) { + try { + String fileName = generateDefaultFileName(); + String savePath = saveDir.endsWith("/") ? saveDir + fileName : saveDir + "/" + fileName; + // 直接从输入流读取并保存 + try (InputStream in = request.getInputStream()) { + Files.copy(in, Paths.get(savePath)); + savedFiles.add(fileName); + } + } catch (Exception e) { + log.error("Exception caught", e); + } + } + + return savedFiles; + } + + /** + * 获取上传文件的原始文件名(兼容不同 Servlet 版本) + */ + public static String getSubmittedFileName(Part part) { + // Jakarta Servlet 3.1+ 原生方法 + String fileName = part.getSubmittedFileName(); + if (fileName == null) { + // 从请求头解析文件名(兼容旧版本) + String contentDisposition = part.getHeader("Content-Disposition"); + if (contentDisposition != null) { + for (String token : contentDisposition.split(";")) { + token = token.trim(); + if (token.startsWith("filename=")) { + return token.substring("filename=".length()) + .replace("\"", "") + .trim(); + } + } + } + return ""; + } + return fileName; + } + + /** + * (类似C#的Request.Count) + * 获取文件数量 + * + * @param request 请求对象 + */ + public static int getUploadedFileCount(HttpServletRequest request) { + String contentType = request.getContentType(); + + // 处理multipart/form-data类型 + if (contentType != null && contentType.startsWith("multipart/form-data")) { + int count = 0; + try { + for (Part part : request.getParts()) { + // 检查是否为有效文件(有文件名且大小 > 0) + String fileName = getSubmittedFileName(part); + if (fileName != null && !fileName.isEmpty() && part.getSize() > 0) { + count++; // 符合条件则计数+1 + } + } + } catch (Exception e) { + log.error("Exception caught", e); + } + return count; + } + // 处理application/octet-stream类型(要么1个要么0个) + else if (contentType != null && contentType.equals("application/octet-stream")) { + return request.getContentLength() > 0 ? 1 : 0; + } + + return 0; + } + + /** + * 按索引获取上传的文件信息综合体(同时支持multipart和octet-stream) + * 功能与C#的Request.Files[i]完全一致 + * + * @param request 请求对象 + * @param index 索引(从0开始) + * @return 文件信息综合体(multipart返回Part,octet-stream返回OctetStreamFile) + */ + public static Object getUploadedFileByIndex(HttpServletRequest request, int index) { + String contentType = request.getContentType(); + if (contentType == null) { + return null; + } + + // 处理multipart/form-data类型(通过Part返回) + if (contentType.startsWith("multipart/form-data")) { + try { + List validFiles = new ArrayList<>(); + for (Part part : request.getParts()) { + String fileName = getSubmittedFileName(part); + if (fileName != null) { // 筛选文件类型Part + validFiles.add(part); + } + } + if (index >= 0 && index < validFiles.size()) { + return validFiles.get(index); // Part是multipart格式的文件信息综合体 + } + } catch (Exception e) { + log.error("Exception caught", e); + } + return null; + } + + // 处理application/octet-stream类型(通过自定义类返回) + if (contentType.equals("application/octet-stream")) { + // octet-stream通常一次请求只传一个文件,索引只能为0 + if (index != 0) { + return null; + } + + try { + // 从请求头获取文件名(前端需通过X-FileName传递,如无则用默认名) + String fileName = request.getHeader("X-FileName"); + log.debug(String.valueOf("fileName : " + fileName)); + if (fileName == null || fileName.trim().isEmpty()) { + fileName = "unknown_octet_stream.bin"; + } + + // 构建文件信息综合体(包含元信息和二进制流) + return new OctetStreamFile( + fileName, + contentType, + request.getContentLengthLong(), + request.getInputStream() + ); + } catch (IOException e) { + log.error("Exception caught", e); + return null; + } + } + + // 不支持的Content-Type + return null; + } + + /** + * 自定义类:封装application/octet-stream类型的文件信息综合体 + * 功能与Part和C#的HttpPostedFile对齐 + */ + public static class OctetStreamFile { + private final String fileName; + private final String contentType; + private final long contentLength; + private final java.io.InputStream inputStream; + + public OctetStreamFile(String fileName, String contentType, long contentLength, java.io.InputStream inputStream) { + this.fileName = fileName; + this.contentType = contentType; + this.contentLength = contentLength; + this.inputStream = inputStream; + } + + // 元信息获取方法(对应C#的HttpPostedFile属性) + public String getFileName() { + return fileName; + } + + public String getContentType() { + return contentType; + } + + public long getContentLength() { + return contentLength; + } + + // 核心:获取文件二进制流(对应C#的InputStream) + public java.io.InputStream getInputStream() { + return inputStream; + } + + // 保存文件到磁盘(对应C#的SaveAs方法) + public void saveAs(String filePath) throws IOException { + try (java.io.InputStream is = inputStream; + java.io.FileOutputStream fos = new java.io.FileOutputStream(filePath)) { + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = is.read(buffer)) != -1) { + fos.write(buffer, 0, bytesRead); + } + } + } + } + + // 线程局部变量存储已读取的实体流(对应C#的_readEntityBodyStream) + private static final ThreadLocal cachedEntityStream = new ThreadLocal<>(); + + /** + * 获取请求总字节数(类似C# TotalBytes逻辑) + * 优先级:已缓存的实体流 > 原始输入流,流为null时返回0 + * + * @param request HTTP请求对象 + * @return 请求总字节数 + * @throws IOException 流操作异常 + */ + public static int getTotalBytes(HttpServletRequest request) throws IOException { + // 1. 优先获取已缓存的实体流(对应C#的_readEntityBodyStream) + InputStream stream = cachedEntityStream.get(); + + // 2. 若缓存流不存在,使用原始输入流并缓存 + if (stream == null) { + // 读取原始输入流内容到字节数组(因Servlet流只能读一次,需缓存) + try (InputStream originalStream = request.getInputStream(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = originalStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + + // 将字节数组转为输入流并缓存 + byte[] contentBytes = outputStream.toByteArray(); + stream = new ByteArrayInputStream(contentBytes); + cachedEntityStream.set((ByteArrayInputStream) stream); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + // 3. 若流仍为null,返回0;否则返回流长度(对应C#的stream.Length) + if (stream == null) { + return 0; + } + + // ByteArrayInputStream的available()返回总字节数,等价于C#的stream.Length + return stream instanceof ByteArrayInputStream ? ((ByteArrayInputStream) stream).available() : 0; + } + + /** + * 获取当前使用的流(供后续读取内容使用,避免重复读取原始流) + * 对应C#中使用TotalBytes后仍可读取流内容的场景 + * + * @return 已缓存的流或原始流 + */ + public static InputStream getCurrentStream() { + return cachedEntityStream.get(); + } + + /** + * 清理线程缓存的流(必须在请求处理结束后调用,防止内存泄漏) + */ + public static void clearCachedStream() { + cachedEntityStream.remove(); + } + + + // 模拟C#的_readEntityBodyMode枚举(读取实体的模式) + public enum ReadEntityBodyMode { + Bufferless, // 无缓冲模式(不允许获取缓冲流) + Buffered // 缓冲模式(允许获取缓冲流) + } + + // 线程局部变量存储缓存的输入流(对应C#的_inputStream) + private static final ThreadLocal cachedInputStream = new ThreadLocal<>(); + // 线程局部变量存储读取模式(对应C#的_readEntityBodyMode) + //private static final ThreadLocal readEntityBodyMode = ThreadLocal.withInitial(() -> ReadEntityBodyMode.Buffered); + // 线程局部变量标记是否有有效的工作器请求(对应C#的_wr) + private static final ThreadLocal hasWorkerRequest = new ThreadLocal<>(); + + + /** + * 获取请求输入流(类似C#的InputStream属性) + * + * @param request HTTP请求对象 + * @return 缓存的或新创建的输入流 + * @throws ServletException 当模式为Bufferless时抛出(对应C#的HttpException) + * @throws IOException 流操作异常 + */ + public static InputStream getInputStream1(HttpServletRequest request) throws ServletException, IOException { + // 1. 检查缓存的输入流是否已存在,存在则直接返回(对应C#的if (_inputStream != null) return _inputStream) + InputStream inputStream = cachedInputStream.get(); + if (inputStream != null) { + return inputStream; + } + + // 2. 检查读取模式,若为Bufferless则抛出异常(对应C#的_readEntityBodyMode == Bufferless判断) + if (readEntityBodyMode.get() == ReadEntityBodyMode.Bufferless) { + throw new ServletException("与无缓冲输入流不兼容(Incompatible with get_bufferless_input_stream)"); + } + + // 3. 获取完整的原始内容(对应C#的GetEntireRawContent()) + byte[] rawContent = getEntireRawContent(request); + + // 4. 根据原始内容创建输入流并缓存(对应C#的创建HttpInputStream逻辑) + if (rawContent != null) { + inputStream = new ByteArrayInputStream(rawContent); + } else { + inputStream = new ByteArrayInputStream(new byte[0]); // 空流 + } + cachedInputStream.set(inputStream); + + return inputStream; + } + + /** + * 安全获取请求输入流(解决缓存复用、空流、重置问题) + * + * @param request HttpServletRequest + * @return 可重置的输入流 + * @throws ServletException 无缓冲模式异常 + * @throws IOException 流读取异常 + */ + public static InputStream getInputStream(HttpServletRequest request) throws ServletException, IOException { + // 1. 检查无缓冲模式(直接抛异常) + if (readEntityBodyMode.get() == ReadEntityBodyMode.Bufferless) { + throw new ServletException("与无缓冲输入流不兼容(Incompatible with get_bufferless_input_stream)"); + } + + // 2. 优先从缓存获取原始字节数组(避免重复读取请求体) + byte[] rawContent = rawContentCache.get(); + if (rawContent == null) { + // 3. 读取完整请求体并缓存(仅读取一次) + rawContent = getEntireRawContent(request); + // 空值校验:避免返回null,用空字节数组并记录日志 + if (rawContent == null) { + rawContent = new byte[0]; + log.warn(String.valueOf("警告:getEntireRawContent 返回null,已替换为空字节数组(请求URI:" + request.getRequestURI() + ")")); + } + rawContentCache.set(rawContent); + } + + // 4. 创建可重置的ByteArrayInputStream(每次返回新流) + ByteArrayInputStream inputStream = new ByteArrayInputStream(rawContent); + + // 5. 空流日志提示(便于排查上传空文件问题) + if (rawContent.length == 0) { + String encode = request.getParameter("encode"); + String filename = request.getParameter("filename"); + log.warn(String.valueOf(String.format("警告:请求体为空(encode=%s,filename=%s)", + encode == null ? "未知" : encode, + filename == null ? "未知" : filename))); + } + + return inputStream; + } + + /** + * 获取完整的原始请求内容(模拟C#的GetEntireRawContent()) + * + * @param request HTTP请求对象 + * @return 原始内容的字节数组(null表示无内容) + * @throws IOException 流操作异常 + */ + private static byte[] getEntireRawContent1(HttpServletRequest request) throws IOException { + // 若没有有效的工作器请求(_wr == null),返回null(对应C#逻辑) + if (Boolean.FALSE.equals(hasWorkerRequest.get())) { + return null; + } + + // 读取原始输入流的全部内容(模拟获取完整原始内容) + try (InputStream originalStream = request.getInputStream(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = originalStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + return outputStream.toByteArray(); + } + } + + /** + * 读取完整请求体(兼容所有JDK版本,避免readAllBytes()兼容问题) + * + * @param request HttpServletRequest + * @return 完整请求体字节数组 + * @throws IOException 流读取异常 + */ + private static byte[] getEntireRawContent(HttpServletRequest request) throws IOException { + try (InputStream inputStream = request.getInputStream(); + ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + baos.write(buffer, 0, bytesRead); + } + return baos.toByteArray(); + } catch (IOException e) { + log.warn(String.valueOf("读取请求体失败:" + e.getMessage())); + throw e; + } + } + + /** + * 清理流缓存(上传完成后调用,避免内存泄漏) + */ + public static void clearInputStreamCache() { + rawContentCache.remove(); + readEntityBodyMode.remove(); + } + // ------------------- 辅助方法(用于设置状态) ------------------- + + /** + * 设置读取实体的模式(对应C#的_readEntityBodyMode) + * + * @param mode 读取模式 + */ + public static void setReadEntityBodyMode(ReadEntityBodyMode mode) { + readEntityBodyMode.set(mode); + } + + /** + * 设置是否有有效的工作器请求(对应C#的_wr) + * + * @param has 布尔值 + */ + public static void setHasWorkerRequest(boolean has) { + hasWorkerRequest.set(has); + } + + /** + * 清理线程缓存的资源(必须在请求处理结束后调用) + */ + public static void clear() { + rawContentCache.remove(); + cachedInputStream.remove(); + readEntityBodyMode.remove(); + hasWorkerRequest.remove(); + clearCachedStream(); // 同时清理实体流缓存 + } + + /** + * 生成默认文件名(用于application/octet-stream类型) + */ + private static String generateDefaultFileName() { + // 使用UUID生成唯一文件名,默认后缀为bin + return UUID.randomUUID().toString() + ".bin"; + } + + /** + * 检查请求是否为multipart/form-data类型 + */ + private static boolean hasMultipartFiles(HttpServletRequest request) { + try { + for (Part part : request.getParts()) { + String fileName = getSubmittedFileName(part); + if (fileName != null && !fileName.isEmpty() && part.getSize() > 0) { + return true; + } + } + } catch (Exception e) { + log.error("Exception caught", e); + } + return false; + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/ResourceExecutors.java b/WebErp/weberp/src/main/java/org/example/Utils/ResourceExecutors.java new file mode 100644 index 0000000..a092cdb --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/ResourceExecutors.java @@ -0,0 +1,162 @@ +package org.example.Utils; + +import java.util.Objects; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +public final class ResourceExecutors { + private static final int DEFAULT_FILE_CORE_SIZE = 2; + private static final int DEFAULT_FILE_MAX_SIZE = 4; + private static final int DEFAULT_FILE_QUEUE_CAPACITY = 200; + private static final int DEFAULT_PUSH_CORE_SIZE = 4; + private static final int DEFAULT_PUSH_MAX_SIZE = 16; + private static final int DEFAULT_PUSH_QUEUE_CAPACITY = 500; + private static final long KEEP_ALIVE_SECONDS = 60L; + + private static ThreadPoolExecutor fileCleanupExecutor = newExecutor( + "file-cleanup", + DEFAULT_FILE_CORE_SIZE, + DEFAULT_FILE_MAX_SIZE, + DEFAULT_FILE_QUEUE_CAPACITY + ); + private static ThreadPoolExecutor pushExecutor = newExecutor( + "push", + DEFAULT_PUSH_CORE_SIZE, + DEFAULT_PUSH_MAX_SIZE, + DEFAULT_PUSH_QUEUE_CAPACITY + ); + + private ResourceExecutors() { + } + + public static synchronized void configure( + int fileCoreSize, + int fileMaxSize, + int fileQueueCapacity, + int pushCoreSize, + int pushMaxSize, + int pushQueueCapacity + ) { + replaceExecutors( + fileCoreSize, + fileMaxSize, + fileQueueCapacity, + pushCoreSize, + pushMaxSize, + pushQueueCapacity + ); + } + + public static Future submitFileCleanup(Runnable task) { + return fileCleanupExecutor.submit(Objects.requireNonNull(task, "task must not be null")); + } + + public static Future submitPush(Runnable task) { + return pushExecutor.submit(Objects.requireNonNull(task, "task must not be null")); + } + + public static synchronized void shutdownFileCleanup() { + shutdown(fileCleanupExecutor); + } + + public static synchronized void shutdownAll() { + shutdown(fileCleanupExecutor); + shutdown(pushExecutor); + } + + public static synchronized void configureForTests( + int fileCoreSize, + int fileMaxSize, + int fileQueueCapacity, + int pushCoreSize, + int pushMaxSize, + int pushQueueCapacity + ) { + replaceExecutors( + fileCoreSize, + fileMaxSize, + fileQueueCapacity, + pushCoreSize, + pushMaxSize, + pushQueueCapacity + ); + } + + public static synchronized void resetForTests() { + replaceExecutors( + DEFAULT_FILE_CORE_SIZE, + DEFAULT_FILE_MAX_SIZE, + DEFAULT_FILE_QUEUE_CAPACITY, + DEFAULT_PUSH_CORE_SIZE, + DEFAULT_PUSH_MAX_SIZE, + DEFAULT_PUSH_QUEUE_CAPACITY + ); + } + + public static ThreadPoolExecutor fileCleanupExecutorForTests() { + return fileCleanupExecutor; + } + + public static ThreadPoolExecutor pushExecutorForTests() { + return pushExecutor; + } + + private static void replaceExecutors( + int fileCoreSize, + int fileMaxSize, + int fileQueueCapacity, + int pushCoreSize, + int pushMaxSize, + int pushQueueCapacity + ) { + ThreadPoolExecutor oldFileExecutor = fileCleanupExecutor; + ThreadPoolExecutor oldPushExecutor = pushExecutor; + fileCleanupExecutor = newExecutor("file-cleanup", fileCoreSize, fileMaxSize, fileQueueCapacity); + pushExecutor = newExecutor("push", pushCoreSize, pushMaxSize, pushQueueCapacity); + shutdown(oldFileExecutor); + shutdown(oldPushExecutor); + } + + private static ThreadPoolExecutor newExecutor(String name, int coreSize, int maxSize, int queueCapacity) { + int safeCore = Math.max(1, coreSize); + int safeMax = Math.max(safeCore, maxSize); + int safeQueueCapacity = Math.max(1, queueCapacity); + ThreadPoolExecutor executor = new ThreadPoolExecutor( + safeCore, + safeMax, + KEEP_ALIVE_SECONDS, + TimeUnit.SECONDS, + new ArrayBlockingQueue<>(safeQueueCapacity), + new NamedDaemonThreadFactory("resource-" + name), + new ThreadPoolExecutor.CallerRunsPolicy() + ); + executor.allowCoreThreadTimeOut(false); + return executor; + } + + private static void shutdown(ThreadPoolExecutor executor) { + if (executor != null && !executor.isShutdown()) { + executor.shutdown(); + } + } + + private static class NamedDaemonThreadFactory implements ThreadFactory { + private final String prefix; + private final AtomicInteger sequence = new AtomicInteger(1); + + private NamedDaemonThreadFactory(String prefix) { + this.prefix = prefix; + } + + @Override + public Thread newThread(Runnable runnable) { + Thread thread = new Thread(runnable, prefix + "-" + sequence.getAndIncrement()); + thread.setDaemon(true); + return thread; + } + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/SiteUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/SiteUtil.java new file mode 100644 index 0000000..9db1b1c --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/SiteUtil.java @@ -0,0 +1,241 @@ +package org.example.Utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +import jakarta.servlet.ServletContext; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.File; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 站点帮助类(对应原C# SiteUtil) + * 功能:检测Web应用是否包含指定虚拟目录(带缓存) + * 创建者:zyw + * 创建日期:2016-09-12 13:40:19 + */ +public class SiteUtil { + private static final Logger log = LoggerFactory.getLogger(SiteUtil.class); + + // 缓存存储虚拟目录检测结果(线程安全的ConcurrentHashMap替代C# Dictionary) + private static final Map CACHE = new ConcurrentHashMap<>(); + // 同步锁对象(替代C#的lock) + private static final Object LOCK_OBJECT = new Object(); + // 缓存过期时间(毫秒),默认30秒 + private static int cacheTimeoutMs = 30000; + + // 私有化构造方法,确保工具类不可实例化 + private SiteUtil() { + } + + /** + * 准确检测当前Web应用是否包含指定虚拟目录(带缓存) + * + * @param virtualDirectoryName 要检测的虚拟目录名称 + * @return 如果存在返回true,否则返回false + */ + public static boolean containsVirtualDirectory(String virtualDirectoryName) { + // 空值/空白判断 + if (virtualDirectoryName == null || virtualDirectoryName.trim().isEmpty()) { + return false; + } + + // 标准化虚拟目录名称作为缓存键 + String normalizedKey = normalizeVirtualDirectoryName(virtualDirectoryName); + + synchronized (LOCK_OBJECT) { + // 检查缓存 + CacheEntry cachedEntry = CACHE.get(normalizedKey); + if (cachedEntry != null) { + if (!cachedEntry.isExpired()) { + // 缓存未过期,直接返回结果 + return cachedEntry.exists; + } else { + // 缓存已过期,移除缓存 + CACHE.remove(normalizedKey); + } + } + + // 缓存未命中或已过期,执行实际检测 + boolean result = containsVirtualDir(virtualDirectoryName); + + // 存储到缓存 + CACHE.put(normalizedKey, new CacheEntry( + result, + System.currentTimeMillis(), + cacheTimeoutMs + )); + + return result; + } + } + + /** + * 最终的检测方法 - 结合多种验证手段 + * + * @param virtualDirectoryName 虚拟目录名称 + * @return 是否存在该虚拟目录 + */ + public static boolean containsVirtualDir(String virtualDirectoryName) { + // 获取当前请求上下文(替代C#的HttpContext.Current) + HttpServletRequest request = RequestContextHolder.getRequest(); + HttpServletResponse response = RequestContextHolder.getResponse(); + if (request == null) { + // System.out.println("错误: 不在Web应用上下文中,无法检测虚拟目录"); + return false; + } + + // 空值/空白判断 + if (virtualDirectoryName == null || virtualDirectoryName.trim().isEmpty()) { + return false; + } + + // 清理虚拟目录名称(去除开头的/) + String cleanVirtualDirName = virtualDirectoryName.trim().replaceFirst("^/+", ""); + + try { + ServletContext servletContext = request.getServletContext(); + + // 尝试映射虚拟目录路径(替代C#的Server.MapPath) + String virtualDirPath = "/" + cleanVirtualDirName; + String mappedPath = servletContext.getRealPath(virtualDirPath); + + // 获取应用根目录 + String appRootPath = servletContext.getRealPath("/") + cleanVirtualDirName; + + // 关键判断逻辑(和原C#完全一致): + // 1. 映射路径不能为空 + // 2. 映射路径不能等于应用根目录(除非虚拟目录就是根目录) + if (mappedPath == null || mappedPath.isEmpty()) { + return false; + } + + // 构建根目录文件对象(用于判断目录是否存在) + File appRootFile = new File(appRootPath); + + // 核心判断:映射路径不等于应用根目录 或 应用根目录存在 或 虚拟目录名为空(根目录) + return !(mappedPath.equalsIgnoreCase(appRootPath) + && !appRootFile.exists() + && !cleanVirtualDirName.isEmpty()); + + } catch (Exception ex) { + log.debug(String.valueOf("检测过程中发生异常: " + ex.getMessage())); + return false; + } + } + + /** + * 标准化虚拟目录名称作为缓存键 + * + * @param virtualDirectoryName 原始虚拟目录名称 + * @return 标准化后的缓存键 + */ + private static String normalizeVirtualDirectoryName(String virtualDirectoryName) { + if (virtualDirectoryName == null || virtualDirectoryName.trim().isEmpty()) { + return ""; + } + // 去除开头的/,并转为小写 + return virtualDirectoryName.trim().replaceFirst("^/+", "").toLowerCase(); + } + + /** + * 缓存条目类(对应原C#的CacheEntry) + */ + private static class CacheEntry { + // 是否存在虚拟目录 + private boolean exists; + // 缓存时间戳(毫秒) + private long timestamp; + // 过期时间(毫秒) + private int timeoutMs; + + public CacheEntry(boolean exists, long timestamp, int timeoutMs) { + this.exists = exists; + this.timestamp = timestamp; + this.timeoutMs = timeoutMs; + } + + /** + * 判断缓存是否过期 + * + * @return 过期返回true,否则false + */ + public boolean isExpired() { + return (System.currentTimeMillis() - timestamp) > timeoutMs; + } + + // getter + public boolean isExists() { + return exists; + } + } + + /** + * 请求上下文持有器(用于获取当前HttpServletRequest) + * 需结合Spring MVC等框架使用,若未使用框架可替换为自定义实现 + */ + public static class RequestContextHolder { + // ThreadLocal存储当前请求(线程安全) + private static final ThreadLocal REQUEST_HOLDER = new ThreadLocal<>(); + private static final ThreadLocal RESPONSE_HOLDER = new ThreadLocal<>(); + + /** + * 设置当前请求 + * + * @param request HttpServletRequest + */ + public static void setRequest(HttpServletRequest request) { + REQUEST_HOLDER.set(request); + } + + /** + * 设置当前响应 + * + * @param response HttpServletResponse + */ + public static void setResponse(HttpServletResponse response) { + RESPONSE_HOLDER.set(response); + } + + /** + * 获取当前请求 + * + * @return 当前HttpServletRequest,无则返回null + */ + public static HttpServletRequest getRequest() { + return REQUEST_HOLDER.get(); + } + + /** + * 获取当前响应 + * + * @return 当前HttpServletResponse,无则返回null + */ + public static HttpServletResponse getResponse() { + return RESPONSE_HOLDER.get(); + } + + /** + * 清除当前线程的上下文 + */ + public static void clear() { + REQUEST_HOLDER.remove(); + RESPONSE_HOLDER.remove(); + } + } + + // ------------------- 可选配置方法 ------------------- + + /** + * 设置缓存过期时间 + * + * @param timeoutMs 过期时间(毫秒) + */ + public static void setCacheTimeoutMs(int timeoutMs) { + cacheTimeoutMs = timeoutMs; + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Utils/SpringContextHolder.java b/WebErp/weberp/src/main/java/org/example/Utils/SpringContextHolder.java new file mode 100644 index 0000000..fc7fe3d --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/SpringContextHolder.java @@ -0,0 +1,24 @@ +package org.example.Utils; + +import org.example.Impl.DataImpl; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +// 这个类会被 Spring 管理,用于保存上下文 +@Component +public class SpringContextHolder implements ApplicationContextAware { + private static ApplicationContext applicationContext; + + // Spring 初始化时自动调用,保存上下文 + @Override + public void setApplicationContext(ApplicationContext ctx) throws BeansException { + applicationContext = ctx; + } + + // 静态方法:获取 Spring 管理的 Bean(这里专门获取 DataImpl) + public static DataImpl getDataImpl() { + return applicationContext.getBean(DataImpl.class); + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/SqlAnalyzer.java b/WebErp/weberp/src/main/java/org/example/Utils/SqlAnalyzer.java new file mode 100644 index 0000000..d1eea2a --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/SqlAnalyzer.java @@ -0,0 +1,656 @@ +package org.example.Utils; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.example.Utils.NativeExtensionUtils.Trim; + +public class SqlAnalyzer { + private String cmdText; + private String mainPart; + private String allMainPart; + private String orderBy; + private String groupBy; + private String having; + public String OldWhere; + private String fromPart; + public String QueryPart; + public ArrayList InnerPms; + private HashMap dotPms; + private ArrayList dotPmNames; + public ArrayList AliasPms; + + private int sIndex; + private int withIndex; + private int tIndex; + private int fIndex; + private boolean _isUnion; + private boolean analyzedPms = false; + + // 预编译正则表达式(对应C#的静态Regex) + // 修复后的正则表达式:限制后行断言中的数字位数 + private static final Pattern QUERY_PART_REG = Pattern.compile("(?is)(?<=select\\ ).*?(?=from)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + private static final Pattern TOP_QUERY_PART_REG = Pattern.compile("(?is)(?<=top (\\d){1,10} ).*?(?=from)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + private static final Pattern UNION_QUERY_PART_REG = Pattern.compile("(?is)(?<=select\\ ).*?(?=union)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + private static final Pattern FROM_PART_REG = Pattern.compile("(?is)(?<=from\\ )[^)]+(?= |\\))", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + private static final Pattern SEL_X_FROM_REG = Pattern.compile("select( ){1,}\\*( ){1,}from( ){0,}(\\r\\n){0,}\\(+", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + + public SqlAnalyzer(String source) { + this.cmdText = source; + String cmdTextLower = Trim(source).toLowerCase(); + this._isSelect = !cmdTextLower.contains("exec "); + + // 初始化成员变量 + this.mainPart = ""; + this.allMainPart = ""; + this.orderBy = ""; + this.groupBy = ""; + this.having = ""; + this.OldWhere = ""; + this.fromPart = ""; + this.QueryPart = ""; + this.InnerPms = new ArrayList<>(); + this.dotPms = new HashMap<>(); + this.dotPmNames = new ArrayList<>(); + this.AliasPms = new ArrayList<>(); + + this.doAnalyze(); + } + + + // Getter方法(对应C\#的属性) + private Boolean _isSelect; + + public Boolean isIsSelectString() { + return _isSelect; + } + + private String _cmdText; + + public String getCmdText() { + return cmdText; + } + + public String getMainPart() { + return mainPart; + } + + public String getAllMainPart() { + return allMainPart; + } + + public String getOrderBy() { + return orderBy; + } + + public String getGroupBy() { + return groupBy; + } + + public String getHaving() { + return having; + } + + public String getOldWhere() { + return OldWhere; + } + + public String getFromPart() { + return fromPart; + } + + public String getQueryPart() { + return QueryPart; + } + + public ArrayList getInnerPms() { + return InnerPms; + } + + public HashMap getDotPms() { + return dotPms; + } + + public ArrayList getDotPmNames() { + return dotPmNames; + } + + public ArrayList getAliasPms() { + return AliasPms; + } + + + /** + * 核心分析方法:清理SQL并解析结构 + */ + private void doAnalyze() { + if (cmdText == null || cmdText.isEmpty() || !_isSelect) { + mainPart = cmdText; + return; + } + + String cmdText = this.cmdText; + + // 清理SQL:去除注释、多余空格、换行等 + cmdText = Pattern.compile("ORDER\\ +BY", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE).matcher(cmdText).replaceAll("order by"); + cmdText = Pattern.compile("/\\*[^/\\*]+\\*/", Pattern.MULTILINE).matcher(cmdText).replaceAll(""); // 去除/* */注释 + cmdText = Pattern.compile("--[^\\r\\n].*\\r\\n", Pattern.MULTILINE).matcher(cmdText).replaceAll("\r\n"); // 去除--注释 + cmdText = Pattern.compile("\\r\\n", Pattern.MULTILINE).matcher(cmdText).replaceAll(" "); // 换行转空格 + cmdText = Pattern.compile("\\r", Pattern.MULTILINE).matcher(cmdText).replaceAll(" "); + cmdText = Pattern.compile("\\ +", Pattern.MULTILINE).matcher(cmdText).replaceAll(" "); // 多个空格合并 + cmdText = Pattern.compile("\\t+", Pattern.MULTILINE).matcher(cmdText).replaceAll(" "); // 制表符转空格 + cmdText = Pattern.compile(",\\ ", Pattern.MULTILINE).matcher(cmdText).replaceAll(","); // 逗号后空格清理 + cmdText = Pattern.compile("\\ ,", Pattern.MULTILINE).matcher(cmdText).replaceAll(","); // 逗号前空格清理 + cmdText = Pattern.compile("( |\\r\\n)*\\,( |\\r\\n)*", Pattern.MULTILINE).matcher(cmdText).replaceAll(",").trim(); + + this.cmdText = cmdText; + + // 解析SQL各部分 + doAnalyzePart(); + + // 计算关键索引(WITH/SELECT/FROM) + withIndex = getRegIndex(cmdText, "with"); + sIndex = getRegIndex(cmdText, withIndex >= 0 ? "\\)\\ +select" : "select", false, 0); + fIndex = getRegIndex(cmdText, "from", false, sIndex); + + // 提取查询字段部分(QueryPart)和FROM部分(FromPart) + Matcher m; + if ((m = QUERY_PART_REG.matcher(mainPart)).find() || (m = UNION_QUERY_PART_REG.matcher(mainPart)).find()) { + QueryPart = m.group(); + } + if ((m = TOP_QUERY_PART_REG.matcher(mainPart)).find()) { + QueryPart = m.group(); + } + if ((m = FROM_PART_REG.matcher(mainPart)).find()) { + fromPart = m.group(); + } + } + + + /** + * 查找正则匹配的索引位置 + * + * @param source 源字符串 + * @param regText 正则表达式 + * @param last 是否取最后一个匹配 + * @param startIndex 起始查找位置 + * @return 匹配的索引(-1表示未找到) + */ + private int getRegIndex(String source, String regText, boolean last, int startIndex) { + if (source == null || source.isEmpty() || regText == null || regText.isEmpty()) { + return -1; + } + + Pattern pattern = Pattern.compile("( |\\r\\n|\\()+" + regText + "( |\\r\\n|\\()", Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(" " + source); // 前缀加空格避免边界问题 + + List indices = new ArrayList<>(); + while (matcher.find()) { + int index = matcher.start(); + if (index >= startIndex) { + indices.add(index); + } + } + + if (indices.isEmpty()) { + return -1; + } + + return last ? indices.get(indices.size() - 1) : indices.get(0); + } + + // 简化调用:默认不取最后,起始位置0 + private int getRegIndex(String source, String regText) { + return getRegIndex(source, regText, false, 0); + } + + private int getRegIndex(String source, String regText, boolean last) { + return getRegIndex(source, regText, last, 0); + } + + + /** + * 处理SQL中的括号(替换括号内内容为占位符,避免解析干扰) + */ + private String dealCmdStr() { + Pattern reg = Pattern.compile("\\(([^\\(|\\)]*)\\)"); + String cmd = cmdText; + Matcher matcher = reg.matcher(cmd); + + while (matcher.find()) { + String val = matcher.group(); + // 用同等长度的"1"替换括号内内容(保持索引位置大致准确) + StringBuilder replacement = new StringBuilder(); + for (int i = 0; i < val.length(); i++) { + replacement.append("1"); + } + cmd = cmd.replace(val, replacement.toString()); + } + return cmd; + } + + + /** + * 解析SQL的GROUP BY/ORDER BY/WHERE等部分 + */ + private void doAnalyzePart() { + String cmd = dealCmdStr(); + + // 计算各关键字的索引 + int oIndex = getRegIndex(cmd, "order by", true, 0); // ORDER BY索引 + int gIndex = getRegIndex(cmd, "group by", true, 0); // GROUP BY索引 + int uIndex = getRegIndex(cmd, "union", false, 0); // UNION索引 + int jIndex = getRegIndex(cmd, "join", true, 0); // JOIN索引 + int hIndex = getRegIndex(cmd, "having", true, 0); // HAVING索引 + int kIndex = getRegIndex(cmd, "on", true, 0); // ON索引 + int khIndex = getRegIndex(cmd, "\\)", false, uIndex); // 右括号索引 + int wIndex = getRegIndex(cmd, "where", true, 0); // WHERE索引 + tIndex = getRegIndex(cmd, "select top", true, 0); // SELECT TOP索引 + + // 处理UNION查询 + if (uIndex > 0 && uIndex > kIndex && uIndex > wIndex && !(khIndex > 0 && wIndex > khIndex)) { + doAnalyzeUnion(); + return; + } + + // 解析GROUP BY和HAVING + if (gIndex > kIndex && gIndex > wIndex) { + groupBy = cmdText.substring(gIndex); + if (oIndex > gIndex) { + groupBy = cmdText.substring(gIndex, oIndex); + } + if (hIndex > gIndex) { + groupBy = cmdText.substring(gIndex, hIndex); + having = (oIndex > hIndex) ? cmdText.substring(hIndex, oIndex) : cmdText.substring(hIndex); + } + } + + // 解析ORDER BY + if (oIndex > kIndex && oIndex > wIndex) { + orderBy = cmdText.substring(oIndex); + } + + // 解析WHERE条件 + if (wIndex > 0 && wIndex > kIndex) { + OldWhere = cmdText.substring(wIndex); + if (oIndex > 0 && oIndex > wIndex) { + OldWhere = cmdText.substring(wIndex, oIndex); + } + if (gIndex > 0 && gIndex > wIndex) { + OldWhere = cmdText.substring(wIndex, gIndex); + } + + int mpIndex = wIndex; + mainPart = cmdText.substring(0, mpIndex); + + // 处理嵌套WHERE的情况 + if (mpIndex < wIndex) { + int innerWIndex = getRegIndex(mainPart, "where", true, 0); + if (innerWIndex > 0 && innerWIndex > kIndex) { + if (oIndex > 0 && oIndex > innerWIndex) { + OldWhere = mainPart.substring(innerWIndex, oIndex); + } + if (gIndex > 0 && gIndex > innerWIndex) { + OldWhere = mainPart.substring(innerWIndex, gIndex); + } + mainPart = mainPart.substring(0, innerWIndex); + } + } + } else { + mainPart = cmdText; + if (!orderBy.isEmpty()) { + mainPart = cmdText.substring(0, oIndex); + } + if (!groupBy.isEmpty()) { + mainPart = cmdText.substring(0, gIndex); + } + } + + allMainPart = mainPart; + + // 处理"select * from"的特殊情况 + if (mainPart.trim().toLowerCase().startsWith("select * from")) { + mainPart = SEL_X_FROM_REG.matcher(mainPart).replaceAll(""); + } + } + + + /** + * 处理UNION查询(标记为UNION并简化处理) + */ + private void doAnalyzeUnion() { + allMainPart = mainPart = cmdText; + _isUnion = true; + } + + + /** + * 分析查询字段(提取内置字段、别名、表.字段等) + */ + public void DoAnalyzeSelPms() { + if (QueryPart == null || QueryPart.isEmpty()) { + return; + } + analyzedPms = true; + + String QueryPart = Pattern.compile(" as ", Pattern.MULTILINE).matcher(this.QueryPart).replaceAll(" "); + + // 初始化集合 + InnerPms.clear(); + AliasPms.clear(); + dotPms.clear(); + dotPmNames.clear(); + + // 正则表达式:匹配表.字段(如t1.id) + Pattern dotReg = Pattern.compile("[^\\(|\\)|\\+|\\-|\\*|\\/|\\,| |\\.]+?\\.[^\\(|\\)|\\+|\\-|\\*|\\/|\\,| |\\.|=]+"); + // 匹配内置字段(逗号间的字段) + Pattern innerReg = Pattern.compile("(,){1}\\w+(,){1}"); + // 匹配别名字段(如... as alias) + Pattern aliasReg = Pattern.compile("(\\)| )+\\w+(,)"); + + Matcher dotMatcher = dotReg.matcher(QueryPart); + Matcher innerMatcher = innerReg.matcher("," + QueryPart + ","); + Matcher innerMatcher1 = innerReg.matcher("," + "zw_field_name" + "," + QueryPart + ","); // 占位符辅助匹配 + Matcher aliasMatcher = aliasReg.matcher(QueryPart + ","); + + // 提取表.字段(dotPms) + while (dotMatcher.find()) { + String val = dotMatcher.group(); + String[] parts = val.split("\\."); + if (parts.length == 2) { + String col = parts[1].toLowerCase(); + if (!dotPms.containsKey(col)) { + dotPms.put(col, parts[0]); + dotPmNames.add(val); + } + } + } + + // 提取别名字段(AliasPms) + while (aliasMatcher.find()) { + String val = aliasMatcher.group().trim(); + val = val.replaceAll("[ )?,]+", ""); // 去除多余字符 + if (!val.isEmpty() && !AliasPms.contains(val)) { + AliasPms.add(val); + } + } + + // 提取内置字段(InnerPms) + while (innerMatcher.find()) { + String val = innerMatcher.group().replaceAll(",", "").trim(); + if (!val.isEmpty() && !InnerPms.contains(val) && !AliasPms.contains(val)) { + InnerPms.add(val); + } + } + + // 辅助占位符匹配 + while (innerMatcher1.find()) { + String val = innerMatcher1.group().replaceAll(",", "").trim(); + if (!val.isEmpty() && !InnerPms.contains(val) && !AliasPms.contains(val)) { + InnerPms.add(val); + } + } + + // 处理通配符* + if (QueryPart.equals("*")) { + InnerPms.add("*"); + } + + // 移除占位符 + InnerPms.remove("zw_field_name"); + } + + + /** + * 检查字符串是否包含表.字段 + */ + private boolean hasDotPms(String str) { + for (String name : dotPmNames) { + if (str.toLowerCase().contains(name.toLowerCase())) { + return true; + } + } + Pattern dotReg = Pattern.compile("[^\\(|\\)|\\+|\\-|\\*|\\/|\\,| |\\.]+?\\.[^\\(|\\)|\\+|\\-|\\*|\\/|\\,| |\\.|=]+"); + Matcher matcher = dotReg.matcher(str); + if (matcher.find()) { + String val = matcher.group(); + String[] parts = val.split("\\."); + if (parts.length == 2 && dotPms.containsValue(parts[0])) { + return true; + } + } + return false; + } + + + /** + * 检查别名是否包含指定字段 + */ + private boolean aliasContains(String key, String val) { + key = key.toLowerCase(); + if (AliasPms.contains(key)) { + return true; + } + String lowerVal = val.toLowerCase(); + for (String alias : AliasPms) { + if (lowerVal.contains(" " + alias + " ")) { + return true; + } + } + return false; + } + + + /** + * 动态插入WHERE条件 + * + * @param where 条件键值对 + * @param inner 是否内置条件 + * @param ot 是否外置条件 + * @param removeOldWhere 是否移除原WHERE + * @return 处理后的SQL + */ + public String InsertWhere(Map where, boolean inner, boolean ot, boolean removeOldWhere) { + // 边界条件判断 + if (where == null || where.isEmpty()) { + return cmdText; + } + if (QueryPart == null || QueryPart.isEmpty()) { + return cmdText; + } + if (!analyzedPms) { + DoAnalyzeSelPms(); // 假设存在此方法,用于解析查询参数 + } + + // 分类where条件为内部条件和外部条件 + Map dinnerwhere = new LinkedHashMap<>(); + Map doutwhere = new LinkedHashMap<>(); + + for (Map.Entry entry : where.entrySet()) { + String key = entry.getKey().toLowerCase(); + String value = entry.getValue(); + + if (!_isUnion) { + // 判断是否属于外部条件 + if ((aliasContains(key, value) || ot) && !cmdText.trim().toLowerCase().startsWith("select * from")) { + doutwhere.put(key, value); + } else { + dinnerwhere.put(key, value); + } + } else { + // 包含UNION时,全部放入外部条件 + doutwhere.put(key, value); + } + } + + // 处理原有WHERE子句 + String oldWhereStr = (OldWhere == null || OldWhere.trim().equalsIgnoreCase("where")) ? "" : OldWhere; + String innerWhere = String.join(" ", dinnerwhere.values()).trim(); + String outWhere = String.join(" ", doutwhere.values()).trim(); + + // 处理需要移除旧WHERE或旧WHERE为空的情况2026.2.27 +// if ((removeOldWhere || (oldWhereStr.isEmpty() && getRegIndex(cmdText, "where", true) < 0 +// && (!innerWhere.isEmpty() || (inner && !outWhere.isEmpty()))) && !_isUnion)) { +// oldWhereStr = "where 1=1 "; +// } + if (removeOldWhere || (oldWhereStr.isEmpty() && getRegIndex(cmdText, "where", true) < 0 + && (!innerWhere.isEmpty() || (inner && !outWhere.isEmpty()))) && !_isUnion) { + oldWhereStr = "where 1=1 "; + } + + // 处理包含ORDER BY的内部条件 + if (innerWhere.toLowerCase().contains("order by") && allMainPart.toLowerCase().indexOf(" top ") == -1) { + oldWhereStr = String.format("%s %s %s", oldWhereStr, outWhere, innerWhere).trim(); + inner = true; + } + // 处理包含GROUP BY且有JOIN的特殊情况 + else if (outWhere.isEmpty() && (getRegIndex(groupBy + orderBy, "where", true) > -1) + && (getRegIndex(cmdText, "join", true) > -1)) { + return String.format("%s%n %s%n %s%n %s%n %s %s", + allMainPart, oldWhereStr, groupBy, having, orderBy, innerWhere); + } + // 常规条件合并 + else { + oldWhereStr = String.format("%s %s %s", oldWhereStr, innerWhere, inner ? outWhere : "").trim(); + } + + // 清理ORDER BY中的重复内容 + if (oldWhereStr.toLowerCase().contains("order by")) { + orderBy = orderBy.toLowerCase().replace("order by", "").trim(); + } + + // 生成最终SQL + if (outWhere.isEmpty() || inner) { + return String.format("%s%n %s%n %s%n %s%n %s", + allMainPart, oldWhereStr, groupBy, having, orderBy); + } + + // 处理SELECT * FROM的特殊情况 + if (cmdText.trim().toLowerCase().startsWith("select * from ")) { + if (!orderBy.isEmpty() || !groupBy.isEmpty()) { + return String.format("%s%n %s%n %s%n %s%n %s%n%s", + allMainPart, + (OldWhere == null || OldWhere.isEmpty() || oldWhereStr.isEmpty()) ? "where 1=1 " : oldWhereStr, + outWhere, groupBy, having, orderBy); + } else { + return String.format("%s %s %s", cmdText, + (OldWhere == null || OldWhere.isEmpty() || oldWhereStr.isEmpty()) ? "where 1=1 " : "", + outWhere); + } + } + + // 默认情况:嵌套子查询处理外部条件 + return String.format("select * from (%s%n %s%n %s%n %s%n%s%n) PM_TEMP where 1=1 %s", + BuildTopText(allMainPart, ""), oldWhereStr, groupBy, having, orderBy, outWhere); + //return String.format("%s%n %s%n %s%n %s%n%s%n %s", + //buildTopText(allMainPart, ""), oldWhereStr, groupBy, having, orderBy, outWhere); + } + + + public String InsertWhere(Map where, boolean inner) { + return InsertWhere(where, inner, false, false); + } + + /** + * 构建分页查询SQL + */ + public String buildSplitPageCmdText(int start, int pageSize, StringBuilder indexColName) { + indexColName.setLength(0); + indexColName.append("__RowIndex"); + if (withIndex >= 0 || !_isSelect) { + return ""; + } + + String tempSql = "select *, ROW_NUMBER() over(order by getdate()) " + indexColName + " into #__tempdata from({0});\n" + + "select count(1) from #__tempdata;\n" + + "select top {1} * from #__tempdata where " + indexColName + ">{2};drop table #__tempdata;"; + + String cmdText = BuildTopText(null, ""); + if (pageSize < 0) { + pageSize = 1000000000; + } + return String.format(tempSql, cmdText.trim(), pageSize, start); + } + + /** + * 构建分页查询SQL语句(方式1) + * + * @param start 起始位置 + * @param pageSize 每页条数 + * @param indexColName 输出参数:行索引列名 + * @return 分页查询SQL + */ + public String buildSplitPageCmdText1(int start, int pageSize, StringBuilder indexColName) { + indexColName.setLength(0); // 清空 StringBuilder + indexColName.append("__RowIndex"); // 设置行索引列名 + + // 如果包含 WITH 子句或不是 SELECT 语句,返回空字符串 + if (withIndex >= 0 || !_isSelect) { + return ""; + } + + // 处理默认页大小 + if (pageSize < 0) { + pageSize = 1000000000; + } + + // 构建包含行号的子查询SQL + String cmdText = BuildTopText(null, "ROW_NUMBER() over(order by getdate()) __RowIndex,"); + // 去除可能的末尾分号 + cmdText = cmdText.replaceAll(";$", ""); + + // 分页SQL模板 + String tempSql = "{0}select top {2} * from ({1}) __temp where {4} > {3}"; + + // 格式化并返回最终SQL + return String.format(tempSql, + BuildCoutCmdText(), + cmdText, + pageSize, + start, + indexColName.toString()); + } + + /** + * 构建带TOP的SQL(避免分页时的排序问题) + */ + public String BuildTopText(String cmdText, String otherQuery) { + String targetCmd = (cmdText == null) ? this.cmdText : cmdText; + if (sIndex < 0) { + return targetCmd; + } + + if (targetCmd.toLowerCase().startsWith("select * from") || (!orderBy.isEmpty() && tIndex < 0)) { + targetCmd = targetCmd.substring(0, sIndex) + " select top 1000000000 " + otherQuery + " " + targetCmd.substring(sIndex + "select".length()); + } else if (!otherQuery.isEmpty()) { + targetCmd = targetCmd.substring(0, sIndex) + " select " + otherQuery + " " + targetCmd.substring(sIndex + "select".length()); + } + + return targetCmd; + } + + public String BuildTopText() { + return BuildTopText(null, ""); + } + + /** + * 构建计数查询SQL(如count(1)) + */ + public String BuildCoutCmdText(String aggCmd, boolean outer) { + if (!_isSelect) { + return ""; + } + if (_isUnion || !groupBy.isEmpty() || sIndex > fIndex || outer) { + String sql = "select " + aggCmd + " from (" + cmdText.substring(0, cmdText.length() - orderBy.length()) + " ) a;"; + return sql; + } else { + return "select " + aggCmd + "\r\n from " + fromPart + "\r\n " + OldWhere + "\r\n " + groupBy + "\r\n " + having + ";"; + } + } + + // 简化调用:默认count(1) tot,非外部查询 + public String BuildCoutCmdText() { + return BuildCoutCmdText("count(1) tot", false); + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Utils/SqlSafetyGuard.java b/WebErp/weberp/src/main/java/org/example/Utils/SqlSafetyGuard.java new file mode 100644 index 0000000..d79fe9a --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/SqlSafetyGuard.java @@ -0,0 +1,294 @@ +package org.example.Utils; + +import java.util.Arrays; +import java.util.Locale; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public final class SqlSafetyGuard { + public static final String RULE_VERSION = "readonly-select-v3-configurable"; + + private static final Pattern QUALIFIED_IDENTIFIER = + Pattern.compile("[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)*"); + private static final Pattern SQL_TERMINATOR_OR_COMMENT = + Pattern.compile("(?s)(;|--|/\\*|\\*/)"); + private static final Pattern READ_ONLY_DANGEROUS_KEYWORDS = + Pattern.compile("(?i)\\b(insert|update|delete|drop|alter|truncate|merge|exec|execute|call|create|grant|revoke|replace|into|outfile|load_file|xp_cmdshell|shutdown|waitfor|sleep|benchmark)\\b"); + private static final Pattern CONDITION_DANGEROUS_KEYWORDS = + Pattern.compile("(?i)\\b(insert|update|delete|drop|alter|truncate|merge|exec|execute|call|create|grant|revoke|replace|union|into|outfile|load_file|xp_cmdshell|shutdown|waitfor|sleep|benchmark)\\b"); + + private SqlSafetyGuard() { + } + + public static boolean isSafeIdentifier(String value) { + return value != null && QUALIFIED_IDENTIFIER.matcher(value.trim()).matches(); + } + + public static String requireSafeIdentifier(String value) { + String trimmed = value == null ? "" : value.trim(); + if (!isSafeIdentifier(trimmed)) { + throw new IllegalArgumentException("Unsafe SQL identifier: " + trimmed); + } + return trimmed; + } + + public static String requireAllowedIdentifier(String value, String... allowedValues) { + String trimmed = requireSafeIdentifier(value); + Set allowed = Arrays.stream(allowedValues) + .map(v -> v.toLowerCase(Locale.ROOT)) + .collect(Collectors.toSet()); + if (!allowed.contains(trimmed.toLowerCase(Locale.ROOT))) { + throw new IllegalArgumentException("SQL identifier is not allowed: " + trimmed); + } + return trimmed; + } + + public static boolean isSafeReadOnlySelect(String sql) { + if (sql == null) { + return false; + } + + String trimmed = sanitizeReadOnlySelectForExecution(sql); + if (trimmed == null) { + return false; + } + trimmed = trimmed.trim(); + if (trimmed.isEmpty()) { + return false; + } + + String normalized = trimmed.toLowerCase(Locale.ROOT); + return startsWithReadOnlyQueryKeyword(normalized) + && !containsDangerousReadOnlyQueryToken(trimmed); + } + + public static String requireSafeReadOnlySelect(String sql) { + String strippedSql = sanitizeReadOnlySelectForExecution(sql); + if (strippedSql == null || !isSafeReadOnlySelect(strippedSql)) { + throw new IllegalArgumentException("Only read-only SELECT SQL is allowed"); + } + return strippedSql.trim(); + } + + public static String sanitizeReadOnlySelectForExecution(String sql) { + return stripSqlComments(sql); + } + + public static boolean isSafeConditionFragment(String fragment) { + if (fragment == null || fragment.trim().isEmpty()) { + return true; + } + return !containsDangerousConditionToken(fragment); + } + + public static String requireSafeConditionFragment(String fragment) { + String safeFragment = fragment == null ? "" : fragment.trim(); + if (!isSafeConditionFragment(safeFragment)) { + throw new IllegalArgumentException("Unsafe SQL condition fragment"); + } + return safeFragment; + } + + private static boolean containsDangerousConditionToken(String sql) { + return SQL_TERMINATOR_OR_COMMENT.matcher(sql).find() + || CONDITION_DANGEROUS_KEYWORDS.matcher(sql).find(); + } + + private static boolean startsWithReadOnlyQueryKeyword(String normalizedSql) { + return startsWithKeywordAndWhitespace(normalizedSql, "select") + || startsWithKeywordAndWhitespace(normalizedSql, "with"); + } + + private static boolean startsWithKeywordAndWhitespace(String normalizedSql, String keyword) { + return normalizedSql.length() > keyword.length() + && normalizedSql.startsWith(keyword) + && Character.isWhitespace(normalizedSql.charAt(keyword.length())); + } + + private static boolean containsDangerousReadOnlyQueryToken(String sql) { + return containsStatementTerminatorOutsideString(sql) + || READ_ONLY_DANGEROUS_KEYWORDS.matcher(maskStringLiterals(sql)).find(); + } + + private static boolean containsStatementTerminatorOutsideString(String sql) { + boolean inString = false; + for (int i = 0; i < sql.length(); i++) { + char current = sql.charAt(i); + if (inString) { + if (current == '\'') { + if (i + 1 < sql.length() && sql.charAt(i + 1) == '\'') { + i++; + } else { + inString = false; + } + } + continue; + } + + if (current == '\'') { + inString = true; + } else if (current == ';') { + return true; + } + } + return inString; + } + + private static String maskStringLiterals(String sql) { + StringBuilder masked = new StringBuilder(sql.length()); + boolean inString = false; + for (int i = 0; i < sql.length(); i++) { + char current = sql.charAt(i); + if (inString) { + if (current == '\'') { + masked.append(' '); + if (i + 1 < sql.length() && sql.charAt(i + 1) == '\'') { + masked.append(' '); + i++; + } else { + inString = false; + } + } else if (current == '\r' || current == '\n') { + masked.append(current); + } else { + masked.append(' '); + } + continue; + } + + if (current == '\'') { + inString = true; + masked.append(' '); + } else { + masked.append(current); + } + } + return masked.toString(); + } + + private static String stripSqlComments(String sql) { + if (sql == null) { + return null; + } + + StringBuilder stripped = new StringBuilder(sql.length()); + boolean inString = false; + for (int i = 0; i < sql.length(); i++) { + char current = sql.charAt(i); + + if (inString) { + stripped.append(current); + if (current == '\'') { + if (i + 1 < sql.length() && sql.charAt(i + 1) == '\'') { + stripped.append(sql.charAt(i + 1)); + i++; + } else { + inString = false; + } + } + continue; + } + + if (current == '\'') { + inString = true; + stripped.append(current); + continue; + } + + if (current == '-' && i + 1 < sql.length() && sql.charAt(i + 1) == '-') { + int legacyCommentEnd = legacyInlineDisabledColumnEnd(sql, i, stripped); + if (legacyCommentEnd >= 0) { + i = legacyCommentEnd; + continue; + } + + stripped.append(' '); + i += 2; + while (i < sql.length() && sql.charAt(i) != '\r' && sql.charAt(i) != '\n') { + i++; + } + if (i < sql.length()) { + stripped.append(sql.charAt(i)); + if (sql.charAt(i) == '\r' && i + 1 < sql.length() && sql.charAt(i + 1) == '\n') { + stripped.append(sql.charAt(i + 1)); + i++; + } + } + continue; + } + + if (current == '/' && i + 1 < sql.length() && sql.charAt(i + 1) == '*') { + stripped.append(' '); + i += 2; + boolean closed = false; + while (i + 1 < sql.length()) { + if (sql.charAt(i) == '*' && sql.charAt(i + 1) == '/') { + i++; + closed = true; + break; + } + if (sql.charAt(i) == '\r' || sql.charAt(i) == '\n') { + stripped.append(sql.charAt(i)); + } + i++; + } + if (!closed) { + return null; + } + continue; + } + + stripped.append(current); + } + + return inString ? null : stripped.toString(); + } + + private static int legacyInlineDisabledColumnEnd(String sql, int commentStart, StringBuilder stripped) { + if (!previousNonWhitespaceIsComma(stripped)) { + return -1; + } + + int i = commentStart + 2; + if (i >= sql.length() || Character.isWhitespace(sql.charAt(i))) { + return -1; + } + + boolean hasIdentifier = false; + while (i < sql.length()) { + char current = sql.charAt(i); + if (current == ',') { + return hasIdentifier ? i : -1; + } + if (current == '\r' || current == '\n') { + return -1; + } + if (!isLegacyDisabledColumnChar(current)) { + return -1; + } + if (!Character.isWhitespace(current)) { + hasIdentifier = true; + } + i++; + } + return -1; + } + + private static boolean previousNonWhitespaceIsComma(StringBuilder value) { + for (int i = value.length() - 1; i >= 0; i--) { + if (!Character.isWhitespace(value.charAt(i))) { + return value.charAt(i) == ','; + } + } + return false; + } + + private static boolean isLegacyDisabledColumnChar(char value) { + return Character.isLetterOrDigit(value) + || Character.isWhitespace(value) + || value == '_' + || value == '.' + || value == '*'; + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/StringFormat.java b/WebErp/weberp/src/main/java/org/example/Utils/StringFormat.java new file mode 100644 index 0000000..0841ef3 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/StringFormat.java @@ -0,0 +1,42 @@ +package org.example.Utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** + * 模拟C# string.Format行为 + * @param format 格式字符串,包含 {0}, {1} 等占位符 + * @param args 替换参数数组 + * @return 格式化后的字符串 + * @throws IllegalArgumentException 当格式或参数无效时抛出 + */ + public class StringFormat { + + // 仅匹配 {数字} 格式的占位符(如 {0}, {1}) + private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{(\\d+)\\}"); + + public static String format(String format, Object... args) { + if (format == null) { + throw new IllegalArgumentException("format cannot be null"); + } + if (args == null) { + throw new IllegalArgumentException("args cannot be null"); + } + + Matcher matcher = PLACEHOLDER_PATTERN.matcher(format); + StringBuffer result = new StringBuffer(); + + while (matcher.find()) { + int index = Integer.parseInt(matcher.group(1)); + if (index < 0 || index >= args.length) { + throw new IllegalArgumentException("Index " + index + " out of bounds"); + } + String replacement = args[index] == null ? "null" : args[index].toString(); + matcher.appendReplacement(result, Matcher.quoteReplacement(replacement)); + } + + matcher.appendTail(result); + return result.toString(); + } + } diff --git a/WebErp/weberp/src/main/java/org/example/Utils/UrlDecoderUtils.java b/WebErp/weberp/src/main/java/org/example/Utils/UrlDecoderUtils.java new file mode 100644 index 0000000..68c81b0 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/UrlDecoderUtils.java @@ -0,0 +1,53 @@ +package org.example.Utils; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +public class UrlDecoderUtils { + /** + * 与C# HttpUtility.UrlDecode(string)对应,方法名保持一致 + */ + public static String UrlDecode(String str) { + if (str == null) { + return null; + } + return UrlDecode(str, StandardCharsets.UTF_8); + } + + /** + * 与C# HttpUtility.UrlDecode(string, Encoding)对应,方法名保持一致 + */ + public static String UrlDecode(String str, Charset encoding) { + if (str == null) { + return null; + } + try { + return URLDecoder.decode(str, encoding.name()); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("不支持的编码格式: " + encoding.name(), e); + } + } + + /** + * 与C# HttpUtility.UrlDecode(byte[])对应,方法名保持一致 + */ + public static String UrlDecode(byte[] bytes, Charset encoding) { + if (bytes == null) { + return null; + } + return UrlDecode(bytes, 0, bytes.length, encoding); + } + + /** + * 与C# HttpUtility.UrlDecode(byte[], int, int, Encoding)对应,方法名保持一致 + */ + public static String UrlDecode(byte[] bytes, int offset, int count, Charset encoding) { + if (bytes == null) { + return null; + } + String str = new String(bytes, offset, count, encoding); + return UrlDecode(str, encoding); + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/WebConfigUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/WebConfigUtil.java new file mode 100644 index 0000000..fab5c2c --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/WebConfigUtil.java @@ -0,0 +1,185 @@ +package org.example.Utils; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.util.Arrays; +import java.util.List; + +/** + * Web配置工具类,读取并提供系统配置参数 + * 创 建 者:wzy + * 创建日期:2015/12/16 15:32:49 + */ +@Component +public class WebConfigUtil extends WebConfigUtil_web { + + // 从配置文件读取推送消息开关(默认false) + @Value("${pushmsg:false}") + private static boolean pushmsg; + + // 数据库端口 + @Value("${dbport:}") + private String dbport; + + // 短信用户名(密文) + @Value("${SMSUserName:}") + private static String SMSUserName; + + // 短信平台密码(密文) + @Value("${SMSPwd:}") + private static String SMSPwd; + + // 基础模块上传图片临时文件夹路径 0填充模块标识,1填充用户id + public static final String PubModelImgTempPath = "Static/Upload/temp_{1}/temp_{0}"; + + // 基础模块用户上传图片临时文件夹路径 0填充用户id + public static final String PubModelImgCuUserTempPath = "Static/Upload/temp_{0}/"; + + // 基础模块附件文件夹,格式:FileRoot\menuCode\Value\fileno\filename + public static final String PubModelAccFilePath = "{0}/{1}/{2}/"; + + // 引导页默认颜色 + private static final List IndexDefaultColors = Arrays.asList( + "#55bf07", "#01caa2", "#bb6aee", "#199efb", + "#33b4fe", "#8bc91e", "#1cabed", "#0fc37d" + ); + + // 引导页默认图标 + private static final List IndexDefaultIcons = Arrays.asList( + "/Resource/Images/indexicon_0.png", "/Resource/Images/indexicon_1.png", + "/Resource/Images/indexicon_2.png", "/Resource/Images/indexicon_3.png", + "/Resource/Images/indexicon_4.png", "/Resource/Images/indexicon_5.png", + "/Resource/Images/indexicon_6.png" + ); + + // 打印连接字符串(懒加载) + private String _PrintConStr; + + // 数据库连接字符串(从配置文件注入) + @Value("${spring.datasource.url:}") + private String connectionString; + + // Getter方法 + public static boolean isPushmsg() { + return pushmsg; + } + + public String getDbport() { + return dbport; + } + + public static String getSMSUserName() { + return SMSUserName; + } + + public static String getSMSPwd() { + return SMSPwd; + } + + public static List getIndexDefaultColors() { + return IndexDefaultColors; + } + + public static List getIndexDefaultIcons() { + return IndexDefaultIcons; + } + + /** + * 获取打印用的数据库连接字符串(转换为Delphi兼容格式) + */ + public String getPrintConStr() { + if (_PrintConStr == null) { + _PrintConStr = sqlToDephiConStr(ConfigUtil.getConnectionString()); + } + return _PrintConStr; + } + + /** + * 将标准SQL连接字符串转换为Delphi兼容格式 + */ + private String sqlToDephiConStr(String connectionString) { + if (connectionString == null || connectionString.trim().isEmpty()) { + return ""; + } + + StringBuilder newConStr = new StringBuilder(); + newConStr.append("Provider=SQLOLEDB.1;"); + + String[] parts = connectionString.split(";"); + for (String part : parts) { + part = part.trim(); + if (part.isEmpty()) { + continue; + } + + String[] kv = part.split("=", 2); // 最多分割为2部分 + if (kv.length != 2) { + continue; + } + + String key = kv[0].trim().toLowerCase(); + String val = kv[1].trim(); + + // 转换关键字并处理特殊值 + switch (key) { + case "server": + key = "Data Source"; + // 如果是本地服务器,替换为当前主机+端口 + if (val.equals(".")) { + String host = getCurrentHost(); + val = host + (dbport != null && !dbport.isEmpty() ? "," + dbport : ""); + } + break; + case "database": + key = "Initial Catalog"; + break; + case "max pool size": + // 移除该参数 + key = ""; + break; + // 其他参数保持不变 + } + + if (!key.isEmpty()) { + newConStr.append(key).append("=").append(val).append(";"); + } + } + + return newConStr.toString(); + } + + /** + * 获取当前请求的主机名 + * + * @return 主机名 + */ + private static String getCurrentHost() { + // 从Spring Web上下文获取当前请求 + ServletRequestAttributes requestAttributes = + (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + + if (requestAttributes != null) { + HttpServletRequest request = requestAttributes.getRequest(); + return request.getServerName(); // 返回服务器主机名 + } + + // 非Web环境下返回默认值 + return "localhost"; + } + + /** + * 获取配置节(Spring环境中通常通过@ConfigurationProperties实现) + * + * @param name 配置节名称 + * @return 配置节对象 + */ + public static Object getSection(String name) { + // Spring环境中建议通过@ConfigurationProperties或@Value注解获取配置 + // 此处为兼容C#方法签名的简化实现 + throw new UnsupportedOperationException("Spring环境中请使用@ConfigurationProperties获取配置节"); + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Utils/WebConfigUtil_web.java b/WebErp/weberp/src/main/java/org/example/Utils/WebConfigUtil_web.java new file mode 100644 index 0000000..468f38a --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/WebConfigUtil_web.java @@ -0,0 +1,277 @@ +package org.example.Utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +import jakarta.annotation.PostConstruct; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Lazy; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.io.File; +import java.nio.file.Paths; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Component +@Lazy(false) +public class WebConfigUtil_web implements ApplicationContextAware { + private static final Logger log = LoggerFactory.getLogger(WebConfigUtil_web.class); + + private static Environment environment; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) { + log.debug(String.valueOf("【WebConfigUtil_web】setApplicationContext 执行 → applicationContext是否为空:" + (applicationContext != null ? "非空" : "空"))); + if (applicationContext != null) { + WebConfigUtil_web.environment = applicationContext.getEnvironment(); + log.debug(String.valueOf("【WebConfigUtil_web】environment 赋值完成 → 是否为空:" + (WebConfigUtil_web.environment != null ? "非空" : "空"))); + // 顺便打印 SingleUser 配置(验证是否能读到) + if (WebConfigUtil_web.environment != null) { + log.debug(String.valueOf("【WebConfigUtil_web】读到的 SingleUser 配置:" + WebConfigUtil_web.environment.getProperty("SingleUser"))); + } + } + } + + public static String language; + + // 从application.properties读取配置 + @Value("${language:zh-CN}") + private String languageInstance; + + // 初始化方法:在实例创建后将配置值赋值给静态字段 + @PostConstruct + public void init() { + // 将实例字段的值同步到静态字段 + WebConfigUtil_web.language = this.languageInstance; + } + + @Value("${appname:WebERP}") + private String appName; + + @Value("${jpappkey:}") + private String jpAppKey; + + @Value("${jpsecret:}") + private String jpSecret; + + @Value("${updImgPath:WebUpload/Image/{0}/{1}/}") + private String pubModelImgPath; + + @Value("${updFilePath:WebUpload/{0}/{1}/{2}/}") + private static String pubModelFilePath; + +// @Value("${fileVPath:fileRoot}") +// private static String fileVPath; + + public static String fileVPath; + + @Value("${fileVPath:fileRoot}") // 兜底语法在此处生效 + public void setFileVPath(String fileVPath) { + WebConfigUtil_web.fileVPath = fileVPath; // 给静态变量赋值 + } + + @Value("${filePath:}") + private static String cfgFilePath = "/home/Lserp"; + + @Value("${fileDomain:}") + private static String fileDomain; + + // 静态常量 + public static final String SysTabAppCacheName = "SysTabAppCache"; + public static final String Session_Purview = "Session_Purview"; + public static final String Session_Users = "Session_Users"; + public static final String Session_LoginUser = "Session_LoginUsers"; + public static final String Session_LoginUsersSessionName = "Session_LoginUsersSessionName"; + public static final String Session_ConStr = "Session_ConStr"; + public static final String Session_Request = "Session_Request"; + public static final String weburl_bs_web = ""; + + // 缓存路径变量 + private static String filePath; + private static String serverPath; + private static String fileDomian; + + // 获取当前请求 + private static HttpServletRequest getRequest() { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + return attributes != null ? attributes.getRequest() : null; + } + + // 网站根路径(对应ServerPath) + public static String getServerPath() { + if (serverPath == null) { + HttpServletRequest request = getRequest(); + if (request != null) { + serverPath = request.getServletContext().getRealPath("/"); + } else { + serverPath = new File("").getAbsolutePath() + File.separator; + } + } + return serverPath; + } + + // 文件上传物理路径(对应FilePath) + public static String getFilePath() { +// if (filePath == null) { +// if (StringUtils.hasText(cfgFilePath)) { +// filePath = cfgFilePath; +// } else { +// HttpServletRequest request = getRequest(); +// if (request != null) { +// // 映射虚拟路径到物理路径 +// filePath = request.getServletContext().getRealPath( +// fileVPath.startsWith("/") ? fileVPath : "/" + fileVPath +// ); +// } else { +// // 非Web环境下的默认路径 +// filePath = Paths.get(getServerPath(), fileVPath).toString(); +// } +// } +// // 确保路径包含fileVPath +// if (!filePath.contains(fileVPath)) { +// filePath = Paths.get(filePath, fileVPath).toString(); +// } +// } + filePath = "/home/Lserp/fileRoot"; + return filePath; + } + + // 网站基础URL(对应WebUrl) + public static String getWebUrl() { +// HttpServletRequest request = getRequest(); +// if (request != null) { +// return String.format("%s://%s:%d", +// request.getScheme(), +// request.getServerName(), +// request.getServerPort() +// ); +// } +// return ""; + HttpServletRequest request = getRequest(); + if (request != null) { + // 获取Referer头(前端页面地址) + String referer = request.getHeader("Origin"); + if (referer != null && !referer.isEmpty()) { + return referer; + } + + // 兼容原有逻辑 + return String.format("%s://%s:%d", + request.getScheme(), + request.getServerName(), + request.getServerPort() + ); + } + return ""; + } + + // 文件域名(对应FileDomian) + public static String getFileDomain() { + if (fileDomian == null) { + if (StringUtils.hasText(fileDomain)) { + fileDomian = fileDomain; + } else { + fileDomian = getWebUrl(); + } + } + return fileDomian; + } + + // 处理文档HTML路径(对应GetViewDocHtmlPath) + public static String getViewDocHtmlPath(String filePath, StringBuilder vHtmlPath) { + Pattern pattern = Pattern.compile(fileVPath, Pattern.CASE_INSENSITIVE); +// System.out.println("123"); + Matcher matcher = pattern.matcher(filePath); +// System.out.println("1324"); + String docHtmlPath = matcher.replaceAll("DocHtml"); + + String[] parts = docHtmlPath.split("\\\\"); + String fileName = parts[parts.length - 1]; + String newName = fileName.replace(".", ""); + docHtmlPath = docHtmlPath.replace(fileName, newName) + ".pdf"; + int index = docHtmlPath.indexOf("DocHtml"); + if (index > 0) { + vHtmlPath.append(docHtmlPath.substring(index - 1).replace("\\", "/")); + } else { + vHtmlPath.append(docHtmlPath.replace("\\", "/")); + } + +// HttpServletRequest request = getRequest(); +// if (request != null) { +// String referer = request.getHeader("Referer"); +// if (referer != null && !referer.isEmpty()) { +// return referer.substring(0,referer.length()-1) + vHtmlPath.toString(); +// } +// return request.getServletContext().getRealPath(vHtmlPath.toString()); +// return String.format("%s://%s:%d/%s", +// request.getScheme(), +// request.getServerName(), +// request.getServerPort(), +// vHtmlPath.toString()); +// } else { +// return Paths.get(getServerPath(), vHtmlPath.toString()).toString(); +// } + return cfgFilePath + vHtmlPath.toString(); + } + + /** + * 获取配置值,当配置不存在时返回默认值 + * + * @param key 配置键名 + * @param defaultVal 默认值 + * @return 配置值或默认值 + */ + public static String get(String key, String defaultVal) { + if (environment == null) { + return defaultVal; + } + // 获取配置,不存在则返回默认值 + return environment.getProperty(key, defaultVal); + } + + /** + * 重载方法,默认值为空字符串 + */ + public static String get(String key) { + return get(key, ""); + } + + // Getter方法(省略部分,按需添加) + public String getLanguage() { + return language; + } + + public String getAppName() { + return appName; + } + + public String getJpAppKey() { + return jpAppKey; + } + + public String getJpSecret() { + return jpSecret; + } + + public String getPubModelImgPath() { + return pubModelImgPath; + } + + public static String getPubModelFilePath() { + return pubModelFilePath; + } + + public static String getFileVPath() { + return fileVPath; + } +} + diff --git a/WebErp/weberp/src/main/java/org/example/Utils/WebUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/WebUtil.java new file mode 100644 index 0000000..c187a76 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/WebUtil.java @@ -0,0 +1,945 @@ +package org.example.Utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.http.*; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.xml.bind.JAXBException; +import java.io.*; +import java.net.*; +import java.net.HttpCookie; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.zip.GZIPInputStream; +import java.util.regex.Pattern; + +/** + * ============================================================================== + * 功能描述:WebUtil + * 创 建 者:wzy + * 创建日期:2017-01-19 11:01:32 + * ============================================================================== + */ +enum SendDataType { + TEXT(0), JSON(1), XML(2), SCRIPT(3); + + private final int value; + + SendDataType(int value) { + this.value = value; + } + + public int getValue() { + return value; + } +} + +enum InfoType { + DEBUG(0), INFO(1), WARN(2), FAIL(3), ERROR(4); + + private final int value; + + InfoType(int value) { + this.value = value; + } + + public int getValue() { + return value; + } +} + +class RequestParams { + private static final Logger log = LoggerFactory.getLogger(RequestParams.class); + + private final String url; + private String method; + private CookieStore cookieStore; + private boolean success; + private String result; + private Object sendData; + private String strData; + private SendDataType dataType = SendDataType.TEXT; + private SendDataType resDataType = SendDataType.TEXT; + private Action showInfo; + private Action onSuccess; + private Action beforeSend; + private Runnable onStart; + private Runnable onFail; + private int timeout = 60000; + private int readWriteTimeout = 60000; + private int timeOutCount = 0; + private String contentType; + private String userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0"; + private String cookieInfo; + private Map header; + private String encoding; + private boolean allowAutoRedirect = false; + private boolean keepAlive = true; + private String referer; + + public RequestParams(String url) { + this.url = url; + } + + public String getUrl() { + return url; + } + + public String getMethod() { + if (method == null || method.isEmpty()) { + switch (dataType) { + default: + method = "GET"; + break; + case XML: + method = "POST"; + break; + } + } + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public CookieStore getCookieStore() { + return cookieStore; + } + + public void setCookieStore(CookieStore cookieStore) { + this.cookieStore = cookieStore; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getResult() { + return result; + } + + public void setResult(String result) { + this.result = result; + } + + public JsonObject getJResult() { + if (result != null && !result.isEmpty() && (result.startsWith("{") || dataType == SendDataType.JSON)) { + try { + return new Gson().fromJson(result, JsonObject.class); + } catch (Exception e) { + return null; + } + } + return null; + } + + public T getCusResult(Class clazz) { + if (dataType == SendDataType.JSON && result != null && !result.isEmpty()) { + return new Gson().fromJson(result, clazz); + } + return null; + } + + public Object getSendData() throws JAXBException, IOException { + if (sendData != null && (sendData instanceof byte[] || sendData instanceof InputStream)) { + return sendData; + } + return getSendStr(sendData); + } + + public void setSendData(Object sendData) { + this.sendData = sendData; + } + + public String getStrData() { + try { + return URLEncoder.encode(strData, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + return strData; + } + } + + public void setStrData(String strData) { + this.strData = strData; + } + + public SendDataType getDataType() { + return dataType; + } + + public void setDataType(SendDataType dataType) { + this.dataType = dataType; + } + + public SendDataType getResDataType() { + return resDataType; + } + + public void setResDataType(SendDataType resDataType) { + this.resDataType = resDataType; + } + + public Action getShowInfo() { + return showInfo; + } + + public void setShowInfo(Action showInfo) { + this.showInfo = showInfo; + } + + public Action getOnSuccess() { + return onSuccess; + } + + public void setOnSuccess(Action onSuccess) { + this.onSuccess = onSuccess; + } + + public Action getBeforeSend() { + return beforeSend; + } + + public void setBeforeSend(Action beforeSend) { + this.beforeSend = beforeSend; + } + + public Runnable getOnStart() { + return onStart; + } + + public void setOnStart(Runnable onStart) { + this.onStart = onStart; + } + + public Runnable getOnFail() { + return onFail; + } + + public void setOnFail(Runnable onFail) { + this.onFail = onFail; + } + + public int getTimeout() { + return timeout; + } + + public void setTimeout(int timeout) { + this.timeout = timeout; + } + + public int getReadWriteTimeout() { + return readWriteTimeout; + } + + public void setReadWriteTimeout(int readWriteTimeout) { + this.readWriteTimeout = readWriteTimeout; + } + + public int getTimeOutCount() { + return timeOutCount; + } + + public void setTimeOutCount(int timeOutCount) { + this.timeOutCount = timeOutCount; + } + + public String getContentType() { + if (contentType == null || contentType.isEmpty()) { + switch (dataType) { + default: + contentType = "application/x-www-form-urlencoded;charset=utf-8"; + break; + case XML: + contentType = "text/xml"; + break; + case JSON: + contentType = "application/json;charset=utf-8"; + break; + } + } + return contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public String getUserAgent() { + return userAgent; + } + + public void setUserAgent(String userAgent) { + this.userAgent = userAgent; + } + + public String getCookieInfo() { + return cookieInfo; + } + + public void setCookieInfo(String cookieInfo) { + this.cookieInfo = cookieInfo; + try { + String host = new URL(url).getHost(); + if (cookieStore == null) { + cookieStore = new CookieManager().getCookieStore(); + } + updateCookie(cookieInfo, host); + } catch (MalformedURLException e) { + log.error("Exception caught", e); + } + } + + public Map getHeader() { + return header; + } + + public void setHeader(Map header) { + this.header = header; + } + + public String getEncoding() { + return encoding; + } + + public void setEncoding(String encoding) { + this.encoding = encoding; + } + + public boolean isAllowAutoRedirect() { + return allowAutoRedirect; + } + + public void setAllowAutoRedirect(boolean allowAutoRedirect) { + this.allowAutoRedirect = allowAutoRedirect; + } + + public boolean isKeepAlive() { + return keepAlive; + } + + public void setKeepAlive(boolean keepAlive) { + this.keepAlive = keepAlive; + } + + public String getReferer() { + return referer; + } + + public void setReferer(String referer) { + this.referer = referer; + } + + public String getAccept() { + switch (dataType) { + default: + return "text/html, */*; q=0.01"; + case TEXT: + return "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"; + case SCRIPT: + return "/"; + case JSON: + return "application/json, text/javascript, */*; q=0.01"; + } + } + + public String getAuthority() { + try { + URL urlObj = new URL(url); + return urlObj.getAuthority(); + } catch (MalformedURLException e) { + return ""; + } + } + + public String getHost() { + try { + URL urlObj = new URL(url); + return urlObj.getHost(); + } catch (MalformedURLException e) { + return ""; + } + } + + public void showMsg(String msg, InfoType infoType) { + if (showInfo != null) { + showInfo.execute(msg, infoType); + } + } + + private String getSendStr(Object sendData) throws JAXBException, IOException { + switch (dataType) { + default: + return toQueryStr(sendData); + case XML: + // 假设XmlUtil存在XML序列化方法 + return XmlUtil.xmlSerialize(sendData, StandardCharsets.UTF_8); + } + } + + private String toQueryStr(Object sendData) { + if (sendData == null) { + return ""; + } + + if (sendData instanceof String) { + String sendDataStr = (String) sendData; + return processQueryString(sendDataStr); + } else if (sendData instanceof List) { + List list = (List) sendData; + List stringList = new ArrayList<>(); + for (Object obj : list) { + stringList.add(obj.toString()); + } + return String.join("&", stringList); + } else if (sendData instanceof JsonObject || dataType == SendDataType.JSON) { + return new Gson().toJson(sendData); + } else if (sendData instanceof Map) { + Map map = (Map) sendData; + MultiValueMap params = new LinkedMultiValueMap<>(); + for (Map.Entry entry : map.entrySet()) { + params.add(entry.getKey().toString(), entry.getValue().toString()); + } + return buildQuery(params, "utf8", getMethod(), getContentType()); + } + + return ""; + } + + private String processQueryString(String query) { + if (query.isEmpty()) { + return ""; + } + + if (dataType == SendDataType.JSON || getContentType().toLowerCase().contains("application/json")) { + return query; + } + + List paramList = new ArrayList<>(); + String[] keyValues = query.split("&"); + for (String keyVal : keyValues) { + int eqIndex = keyVal.indexOf("="); + if (eqIndex > -1) { + String key = keyVal.substring(0, eqIndex); + String value = keyVal.substring(eqIndex + 1).trim(); + try { + paramList.add(key + "=" + URLEncoder.encode(value, StandardCharsets.UTF_8.name())); + } catch (UnsupportedEncodingException e) { + paramList.add(key + "=" + value); + } + } else { + paramList.add(keyVal); + } + } + paramList.add(String.valueOf(new Random().nextInt(900) + 100)); + return String.join("&", paramList); + } + + public static String buildQuery(MultiValueMap parameters, String encode, String method, String contentType) { + if (parameters == null || parameters.isEmpty()) { + return ""; + } + + List params = new ArrayList<>(); + for (Map.Entry> entry : parameters.entrySet()) { + String name = entry.getKey(); + for (String value : entry.getValue()) { + if (name != null && !name.isEmpty()) { + String encodedValue = value; + if (method.equalsIgnoreCase("get") || + (method.equalsIgnoreCase("post") && + contentType.toLowerCase().contains("application/x-www-form-urlencoded"))) { + try { + if ("gb2312".equals(encode)) { + encodedValue = URLEncoder.encode(value, "GB2312"); + } else if ("utf8".equals(encode)) { + encodedValue = URLEncoder.encode(value, StandardCharsets.UTF_8.name()); + } + } catch (UnsupportedEncodingException e) { + // 编码失败使用原始值 + } + } + params.add(name + "=" + encodedValue); + } + } + } + + if (method.equalsIgnoreCase("get")) { + params.add(String.valueOf(new Random().nextInt(900) + 100)); + } + + return String.join("&", params); + } + + private void updateCookie(String cookieInfo, String host) { + if (cookieInfo == null || cookieInfo.isEmpty() || host == null) { + return; + } + + String[] cookies = cookieInfo.split(";"); + for (String cookie : cookies) { + cookie = cookie.trim(); + if (cookie.isEmpty()) { + continue; + } + + String[] parts = cookie.split("=", 2); + if (parts.length < 2) { + continue; + } + + String name = parts[0].trim(); + String value = parts[1].trim(); + + try { + URL url = new URL("http", host, 80, "/"); + HttpCookie httpCookie = new HttpCookie(name, value); + httpCookie.setDomain(host); + httpCookie.setPath("/"); + cookieStore.add(url.toURI(), httpCookie); + } catch (Exception e) { + log.error("Exception caught", e); + } + } + } +} + +// 保留此定义(通用双参数接口) +@FunctionalInterface +interface Action { + void execute(T t, U u); +} + +public class WebUtil { + + private static final Logger log = LoggerFactory.getLogger(WebUtil.class); + private static final RestTemplate restTemplate = new RestTemplate(); + private static final Pattern IP_PATTERN = Pattern.compile("^((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)$"); + + /** + * 获取指定链接的网页内容 + */ + public static String getContentByUrl(String url) { + HttpURLConnection connection = null; + InputStream inputStream = null; + BufferedReader reader = null; + + try { + URL urlObj = new URL(url); + connection = (HttpURLConnection) urlObj.openConnection(); + connection.setRequestMethod("GET"); + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); + + inputStream = connection.getInputStream(); + reader = new BufferedReader(new InputStreamReader(inputStream, "GBK")); + + StringBuilder content = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + content.append(line); + } + return content.toString(); + } catch (Exception e) { + return ""; + } finally { + closeResources(reader, inputStream, connection); + } + } + + /** + * 发送HTTP请求(GET/POST) + */ + public static String send(String url, Map parameters, CookieStore cookieStore, boolean[] success, String method) { + if (success == null || success.length == 0) { + success = new boolean[1]; + } + success[0] = false; + cookieStore = cookieStore != null ? cookieStore : new CookieManager().getCookieStore(); + + try { + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Arrays.asList(MediaType.TEXT_HTML, MediaType.APPLICATION_XHTML_XML, MediaType.APPLICATION_XML, MediaType.ALL)); + headers.add("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2"); + headers.add("Accept-Encoding", "gzip, deflate"); + headers.add("Upgrade-Insecure-Request", "1"); + headers.add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; …) Gecko/20100101 Firefox/63.0"); + + MultiValueMap params = new LinkedMultiValueMap<>(); + if (parameters != null) { + parameters.forEach(params::add); + } + + HttpEntity> requestEntity; + String requestUrl = url; + + if ("POST".equalsIgnoreCase(method)) { + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + requestEntity = new HttpEntity<>(params, headers); + } else { + requestUrl = buildGetUrl(url, params); + requestEntity = new HttpEntity<>(headers); + } + + ResponseEntity response = restTemplate.exchange( + requestUrl, + "POST".equalsIgnoreCase(method) ? HttpMethod.POST : HttpMethod.GET, + requestEntity, + String.class + ); + + success[0] = response.getStatusCode().is2xxSuccessful(); + return response.getBody() != null ? response.getBody() : ""; + } catch (RestClientException e) { + return e.getMessage(); + } + } + + /** + * 使用RequestParams发送HTTP请求 + */ + public static String send(RequestParams pms) { + if (pms == null) { + return "无效参数"; + } + + if (pms.getOnStart() != null) { + pms.getOnStart().run(); + } + + HttpURLConnection connection = null; + InputStream inputStream = null; + OutputStream outputStream = null; + BufferedReader reader = null; + + try { + String urlStr = pms.getUrl(); + String method = pms.getMethod().toUpperCase(); + boolean isPost = "POST".equals(method); + + // 处理GET请求参数 + if (!isPost && pms.getSendData() != null) { + String query = pms.getSendData().toString(); + urlStr = buildGetUrl(urlStr, query); + } + + URL url = new URL(urlStr); + connection = (HttpURLConnection) url.openConnection(); + + // 设置基础连接属性 + connection.setRequestMethod(method); + connection.setConnectTimeout(pms.getTimeout()); + connection.setReadTimeout(pms.getReadWriteTimeout()); + connection.setInstanceFollowRedirects(pms.isAllowAutoRedirect()); + connection.setUseCaches(false); + connection.setDoInput(true); + + // 设置请求头 + setRequestHeaders(connection, pms); + + // 处理POST请求数据 + if (isPost) { + connection.setDoOutput(true); + connection.setRequestProperty("Content-Type", pms.getContentType()); + + // 处理100-Continue + System.setProperty("sun.net.http.allowRestrictedHeaders", "true"); + connection.setRequestProperty("Expect", "100-continue"); + + Object sendData = pms.getSendData(); + if (sendData != null) { + byte[] postData; + if (sendData instanceof byte[]) { + postData = (byte[]) sendData; + } else if (sendData instanceof InputStream) { + try (InputStream is = (InputStream) sendData) { + postData = is.readAllBytes(); + } + } else { + postData = sendData.toString().getBytes(StandardCharsets.UTF_8); + } + + connection.setRequestProperty("Content-Length", String.valueOf(postData.length)); + outputStream = connection.getOutputStream(); + outputStream.write(postData); + outputStream.flush(); + } + } + + // 执行前置回调 + if (pms.getBeforeSend() != null) { + pms.getBeforeSend().execute(connection, "额外参数值"); + } + + // 获取响应 + int responseCode = connection.getResponseCode(); + inputStream = responseCode >= 400 ? connection.getErrorStream() : connection.getInputStream(); + + // 处理gzip压缩 + String contentEncoding = connection.getContentEncoding(); + if (contentEncoding != null && contentEncoding.contains("gzip")) { + inputStream = new GZIPInputStream(inputStream); + } + + // 处理Cookie + String setCookie = connection.getHeaderField("Set-Cookie"); + if (setCookie != null) { + pms.setCookieInfo(setCookie); + } + + // 处理响应数据 + String encoding = connection.getContentEncoding(); + if (encoding == null || encoding.isEmpty()) { + encoding = "UTF-8"; + } + + reader = new BufferedReader(new InputStreamReader(inputStream, encoding)); + StringBuilder result = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + result.append(line); + } + + // 执行成功回调 + if (pms.getOnSuccess() != null) { + pms.getOnSuccess().execute(inputStream, "额外参数值"); + } + + pms.setSuccess(responseCode >= 200 && responseCode < 300); + pms.setResult(result.toString()); + return pms.getResult(); + } catch (Exception e) { + pms.setSuccess(false); + pms.setResult(e.getMessage()); + pms.showMsg(e.getMessage(), InfoType.ERROR); + return e.getMessage(); + } finally { + closeResources(reader, inputStream, outputStream, connection); + if (pms.getOnFail() != null && !pms.isSuccess()) { + pms.getOnFail().run(); + } + } + } + + private static void setRequestHeaders(HttpURLConnection connection, RequestParams pms) { + connection.setRequestProperty("Accept", pms.getAccept()); + connection.setRequestProperty("User-Agent", pms.getUserAgent()); + connection.setRequestProperty("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2"); + connection.setRequestProperty("Accept-Encoding", "gzip,deflate"); + connection.setRequestProperty("Upgrade-Insecure-Request", "1"); + + if (pms.getHost() != null && !pms.getHost().isEmpty()) { + connection.setRequestProperty("Host", pms.getHost()); + } + + if (pms.getReferer() != null && !pms.getReferer().isEmpty()) { + connection.setRequestProperty("Referer", pms.getReferer()); + } + + if (pms.getHeader() != null && !pms.getHeader().isEmpty()) { + pms.getHeader().forEach(connection::setRequestProperty); + } + + // 处理Cookie + if (pms.getCookieStore() != null) { + try { + URL url = new URL(pms.getUrl()); + List cookies = pms.getCookieStore().get(url.toURI()); + if (cookies != null && !cookies.isEmpty()) { + StringBuilder cookieStr = new StringBuilder(); + for (HttpCookie cookie : cookies) { + if (cookieStr.length() > 0) { + cookieStr.append("; "); + } + cookieStr.append(cookie.getName()).append("=").append(cookie.getValue()); + } + connection.setRequestProperty("Cookie", cookieStr.toString()); + } + } catch (Exception e) { + log.error("Exception caught", e); + } + } + } + + private static String buildGetUrl(String baseUrl, String query) { + if (query == null || query.isEmpty()) { + return baseUrl; + } + return baseUrl.contains("?") ? baseUrl + "&" + query : baseUrl + "?" + query; + } + + private static String buildGetUrl(String baseUrl, MultiValueMap params) { + if (params == null || params.isEmpty()) { + return baseUrl; + } + return buildGetUrl(baseUrl, buildQuery(params, "utf8")); + } + + private static String buildQuery(MultiValueMap parameters, String encode) { + if (parameters == null || parameters.isEmpty()) { + return ""; + } + + StringBuilder query = new StringBuilder(); + boolean first = true; + + for (Map.Entry> entry : parameters.entrySet()) { + String name = entry.getKey(); + for (String value : entry.getValue()) { + if (name == null || name.isEmpty()) { + continue; + } + + if (!first) { + query.append("&"); + } + first = false; + + try { + query.append(URLEncoder.encode(name, encode)); + query.append("="); + if (value != null) { + query.append(URLEncoder.encode(value, encode)); + } + } catch (UnsupportedEncodingException e) { + query.append(name).append("=").append(value); + } + } + } + + return query.toString(); + } + + /** + * 获取服务器IP地址 + */ + public static String getServerIp() { + try { + String hostName = InetAddress.getLocalHost().getHostName(); + InetAddress[] addresses = InetAddress.getAllByName(hostName); + + StringBuilder ips = new StringBuilder(); + for (InetAddress addr : addresses) { + if (ips.length() > 0) { + ips.append(","); + } + ips.append(addr.getHostAddress()); + } + return ips.toString(); + } catch (UnknownHostException e) { + return ""; + } + } + + /** + * 获取服务器公网IP + */ + public static String getServerOutIp() { + RequestParams pms = new RequestParams("https://www.ip.cn/api/index?ip=&type=0"); + pms.setDataType(SendDataType.JSON); + send(pms); + JsonObject result = pms.getJResult(); + return result != null && result.has("ip") ? result.get("ip").getAsString() : ""; + } + + /** + * 获取客户端IP地址 + */ + public static String getIP() { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = attributes.getRequest(); + if (request == null) { + return ""; + } + + try { + String ip = request.getHeader("HTTP_X_FORWARDED_FOR"); + if (ip != null && !ip.isEmpty()) { + String[] ips = ip.split(","); + if (ips.length > 0) { + ip = ips[0].trim(); + } + } + + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("REMOTE_ADDR"); + } + + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + + return isIP(ip) ? ip : ""; + } catch (Exception e) { + return ""; + } + } + + /** + * 检查IP地址格式 + */ + public static boolean isIP(String ip) { + return ip != null && IP_PATTERN.matcher(ip).matches(); + } + + /** + * 获取请求域名 + */ + public static String getRequestDomain() { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = attributes.getRequest(); + if (request == null) { + return ""; + } + String scheme = request.getScheme(); + String serverName = request.getServerName(); + int serverPort = request.getServerPort(); + + String authority = serverName + (serverPort == 80 || serverPort == 443 ? "" : ":" + serverPort); + return scheme + "://" + authority; + } + + /** + * 关闭资源工具方法 + */ + private static void closeResources(Closeable... resources) { + for (Closeable resource : resources) { + if (resource != null) { + try { + resource.close(); + } catch (IOException e) { + // 忽略关闭异常 + } + } + } + } + + private static void closeResources(BufferedReader reader, InputStream is, HttpURLConnection connection) { + closeResources(reader, is); + if (connection != null) { + connection.disconnect(); + } + } + + private static void closeResources(BufferedReader reader, InputStream is, OutputStream os, HttpURLConnection connection) { + closeResources(reader, is, os); + if (connection != null) { + connection.disconnect(); + } + } +} diff --git a/WebErp/weberp/src/main/java/org/example/Utils/XmlUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/XmlUtil.java new file mode 100644 index 0000000..702b7e4 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/XmlUtil.java @@ -0,0 +1,188 @@ +package org.example.Utils; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import java.io.*; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.function.Function; + +public class XmlUtil { + private static final Charset DEFAULT_ENCODING = Charset.forName("GB2312"); + + /** + * 将对象序列化为XML字符串 + * + * @param obj 要序列化的对象 + * @param encoding 编码方式 + * @return 序列化后的XML字符串 + * @throws JAXBException JAXB处理异常 + * @throws IOException IO异常 + */ + public static String xmlSerialize(Object obj, Charset encoding) throws JAXBException, IOException { + if (obj == null) { + throw new IllegalArgumentException("对象不能为空"); + } + Charset useEncoding = (encoding != null) ? encoding : DEFAULT_ENCODING; + + JAXBContext context = JAXBContext.newInstance(obj.getClass()); + Marshaller marshaller = context.createMarshaller(); + // 格式化输出 + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + // 设置编码 + marshaller.setProperty(Marshaller.JAXB_ENCODING, useEncoding.name()); + // 去除默认XML声明(如需保留可删除此行) + marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true); + + StringWriter writer = new StringWriter(); + // 手动写入XML声明(因为关闭了JAXB_FRAGMENT) + writer.write("\n"); + marshaller.marshal(obj, writer); + return writer.toString(); + } + + /** + * 将对象序列化为XML并写入文件 + * + * @param obj 要序列化的对象 + * @param path 文件路径 + * @param header XML头部(可为null) + * @param encoding 编码方式 + * @throws JAXBException JAXB处理异常 + * @throws IOException IO异常 + */ + public static void xmlSerializeToFile(Object obj, String path, String header, Charset encoding) throws JAXBException, IOException { + if (path == null || path.isEmpty()) { + throw new IllegalArgumentException("文件路径不能为空"); + } + Charset useEncoding = (encoding != null) ? encoding : DEFAULT_ENCODING; + + File file = new File(path); + // 处理文件不存在的情况 + if (!file.exists()) { + File parentDir = file.getParentFile(); + if (parentDir != null && !parentDir.exists()) { + parentDir.mkdirs(); + } + // 写入默认头部 + String defaultHeader = "\n" + + "\n"; + String useHeader = (header != null && !header.isEmpty()) ? header : defaultHeader; + Files.write(Paths.get(path), useHeader.getBytes(useEncoding)); + } + + // 序列化对象到文件 + JAXBContext context = JAXBContext.newInstance(obj.getClass()); + Marshaller marshaller = context.createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + marshaller.setProperty(Marshaller.JAXB_ENCODING, useEncoding.name()); + marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true); + + // 关键修改:将 XMLStreamWriter 声明为 AutoCloseable + try (FileOutputStream fos = new FileOutputStream(file); + OutputStreamWriter osw = new OutputStreamWriter(fos, useEncoding); + AutoCloseable xmlWriter = (AutoCloseable) XMLOutputFactory.newInstance().createXMLStreamWriter(osw)) { + + // 强制转换为 XMLStreamWriter 使用其方法 + XMLStreamWriter writer = (XMLStreamWriter) xmlWriter; + // 写入XML声明 + writer.writeStartDocument(useEncoding.name(), "1.0"); + writer.writeCharacters("\n"); + marshaller.marshal(obj, writer); + writer.writeEndDocument(); + } catch (XMLStreamException | ClassCastException e) { + throw new RuntimeException(e); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * 从XML字符串反序列化对象 + * + * @param xml XML字符串 + * @param clazz 目标类型 + * @param encoding 编码方式 + * @param 目标类型泛型 + * @return 反序列化后的对象 + * @throws JAXBException JAXB处理异常 + */ + public static T xmlDeserialize(String xml, Class clazz, Charset encoding) throws JAXBException { + if (xml == null || xml.isEmpty()) { + throw new IllegalArgumentException("XML字符串不能为空"); + } + Charset useEncoding = (encoding != null) ? encoding : DEFAULT_ENCODING; + + JAXBContext context = JAXBContext.newInstance(clazz); + Unmarshaller unmarshaller = context.createUnmarshaller(); + try (InputStream is = new ByteArrayInputStream(xml.getBytes(useEncoding))) { + return clazz.cast(unmarshaller.unmarshal(is)); + } catch (IOException e) { + throw new JAXBException("反序列化输入流处理失败", e); + } + } + + /** + * 从文件反序列化对象 + * + * @param path 文件路径 + * @param clazz 目标类型 + * @param encoding 编码方式 + * @param 目标类型泛型 + * @return 反序列化后的对象 + * @throws JAXBException JAXB处理异常 + * @throws IOException IO异常 + */ + public static T xmlDeserializeFromFile(String path, Class clazz, Charset encoding) throws JAXBException, IOException { + if (path == null || path.isEmpty()) { + throw new IllegalArgumentException("文件路径不能为空"); + } + File file = new File(path); + if (!file.exists()) { + return null; + } + Charset useEncoding = (encoding != null) ? encoding : DEFAULT_ENCODING; + + String xml = new String(Files.readAllBytes(Paths.get(path)), useEncoding); + return xml.isEmpty() ? null : xmlDeserialize(xml, clazz, useEncoding); + } + + /** + * 从文件反序列化对象(带预处理函数) + * + * @param path 文件路径 + * @param type 目标类型 + * @param encoding 编码方式 + * @param beforeDeserialize 反序列化前的XML预处理函数 + * @return 反序列化后的对象 + * @throws JAXBException JAXB处理异常 + * @throws IOException IO异常 + */ + public static Object xmlDeserializeFromFile(String path, Class type, Charset encoding, + Function beforeDeserialize) throws JAXBException, IOException { + if (path == null || path.isEmpty()) { + throw new IllegalArgumentException("文件路径不能为空"); + } + File file = new File(path); + if (!file.exists()) { + return null; + } + Charset useEncoding = (encoding != null) ? encoding : DEFAULT_ENCODING; + + String xml = new String(Files.readAllBytes(Paths.get(path)), useEncoding); + if (xml.isEmpty()) { + return null; + } + // 应用预处理函数 + if (beforeDeserialize != null) { + xml = beforeDeserialize.apply(xml); + } + return xmlDeserialize(xml, type, useEncoding); + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/main/java/org/example/Utils/ZipUtil.java b/WebErp/weberp/src/main/java/org/example/Utils/ZipUtil.java new file mode 100644 index 0000000..f40bc1e --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/Utils/ZipUtil.java @@ -0,0 +1,426 @@ +package org.example.Utils; + +import net.lingala.zip4j.ZipFile; +import net.lingala.zip4j.model.FileHeader; +import net.lingala.zip4j.model.ZipParameters; +import net.lingala.zip4j.model.enums.CompressionLevel; +import net.lingala.zip4j.model.enums.EncryptionMethod; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.function.Consumer; +import java.util.zip.CRC32; + +/** + * 与C# Lskj.Web.Core.Util.ZipUtil完全对齐的Java实现 + * 修复了ZipFile构造函数错误,确保所有方法正常工作 + */ +public class ZipUtil { + // 对应C#的ShowInfo事件(进度通知) + public static Consumer ShowInfo; + + // ------------------------------------------------------------------------ + // 一、压缩文件夹(无密码) + // ------------------------------------------------------------------------ + public static void CompressDirectory(String dirPath, String fileName, int level, int bufferSize) throws IOException { + File dir = new File(dirPath); + if (!dir.exists() || !dir.isDirectory()) { + throw new FileNotFoundException("待压缩目录不存在或不是有效目录:" + dirPath); + } + + byte[] buffer = new byte[bufferSize]; + // 通过文件路径创建ZipFile(修复构造函数错误) + try (ZipFile zipFile = new ZipFile(fileName)) { + ZipParameters parameters = new ZipParameters(); + parameters.setCompressionLevel(intToCompressionLevel(level)); + parameters.setIncludeRootFolder(false); + + CompressDirectory(dirPath, dirPath, zipFile, parameters, buffer); + } + } + + private static void CompressDirectory(String root, String path, ZipFile zipFile, ZipParameters parameters, byte[] buffer) throws IOException { + root = root.trim().replaceAll("[/\\\\]+$", "") + File.separator; + File currentDir = new File(path); + + // 压缩当前目录下的文件 + File[] files = currentDir.listFiles(File::isFile); + if (files != null) { + for (File file : files) { + String relativePath = file.getCanonicalPath().replace(root, "").replace(File.separator, "/"); + parameters.setFileNameInZip(relativePath); + + try (FileInputStream fis = new FileInputStream(file)) { + zipFile.addStream(fis, parameters); + } + } + } + + // 递归压缩子目录 + File[] subDirs = currentDir.listFiles(File::isDirectory); + if (subDirs != null) { + for (File subDir : subDirs) { + String subRelativePath = subDir.getCanonicalPath().replace(root, "").replace(File.separator, "/") + "/"; + parameters.setFileNameInZip(subRelativePath); + zipFile.addFolder(subDir, parameters); + + CompressDirectory(root, subDir.getCanonicalPath(), zipFile, parameters, buffer); + } + } + } + + // ------------------------------------------------------------------------ + // 二、基础解压缩(无密码) + // ------------------------------------------------------------------------ + public static void Extract(String zipFilePath, String extractPath, int bufferSize) throws IOException { + extractPath = extractPath.trim().replaceAll("[/\\\\]+$", "") + File.separator; + File targetDir = new File(extractPath); + if (!targetDir.exists()) { + targetDir.mkdirs(); + } + + // 通过文件路径创建ZipFile(修复构造函数错误) + try (ZipFile zipFile = new ZipFile(zipFilePath)) { + Path targetRoot = Paths.get(extractPath).toAbsolutePath().normalize(); + for (FileHeader fileHeader : zipFile.getFileHeaders()) { + String entryName = fileHeader.getFileName(); + Path targetPath = resolveZipEntryPath(targetRoot, entryName); + + if (entryName.endsWith("/") || entryName.endsWith("\\")) { + Files.createDirectories(targetPath); + continue; + } + + Path parent = targetPath.getParent(); + if (parent != null) { + Files.createDirectories(parent); + } + try (OutputStream os = Files.newOutputStream(targetPath); + InputStream is = zipFile.getInputStream(fileHeader)) { + + byte[] data = new byte[bufferSize]; + int size; + while ((size = is.read(data)) > 0) { + os.write(data, 0, size); + } + } + } + } + } + + // ------------------------------------------------------------------------ + // 三、带密码压缩相关方法 + // ------------------------------------------------------------------------ + private static boolean ZipFileDictory(String folderToZip, ZipFile zipFile, String parentFolderName, ZipParameters parameters) throws IOException { + boolean res = true; + File folder = new File(folderToZip); + if (!folder.exists() || !folder.isDirectory()) { + return false; + } + + CRC32 crc = new CRC32(); + try { + // 创建当前文件夹条目 + String dirEntryName = Paths.get(parentFolderName, folder.getName()).toString().replace(File.separator, "/") + "/"; + parameters.setFileNameInZip(dirEntryName); + zipFile.addFolder(folder, parameters); + + // 压缩文件(带CRC校验) + File[] files = folder.listFiles(File::isFile); + if (files != null) { + for (File file : files) { + byte[] fileBytes = Files.readAllBytes(file.toPath()); + crc.reset(); + crc.update(fileBytes); + long expectedCrc = crc.getValue(); + + String fileEntryName = Paths.get(parentFolderName, folder.getName(), file.getName()).toString().replace(File.separator, "/"); + parameters.setFileNameInZip(fileEntryName); + + try (FileInputStream fis = new FileInputStream(file)) { + zipFile.addStream(fis, parameters); + } + + // 验证CRC + FileHeader fileHeader = zipFile.getFileHeader(fileEntryName); + if (fileHeader.getCrc() != expectedCrc) { + throw new IOException("文件CRC校验失败:" + file.getName()); + } + } + } + + // 递归压缩子文件夹 + File[] subFolders = folder.listFiles(File::isDirectory); + if (subFolders != null) { + String newParent = Paths.get(parentFolderName, folder.getName()).toString(); + for (File subFolder : subFolders) { + if (!ZipFileDictory(subFolder.getCanonicalPath(), zipFile, newParent, parameters)) { + return false; + } + } + } + } catch (Exception e) { + res = false; + throw e; + } finally { + System.gc(); + } + return res; + } + + private static boolean ZipFileDictory(String folderToZip, String zipedFile, String password) throws IOException { + if (!new File(folderToZip).exists()) { + return false; + } + + // 通过路径和密码创建ZipFile(修复构造函数错误) + char[] passwordChars = (password != null && !password.isEmpty()) ? password.toCharArray() : null; + try (ZipFile zipFile = new ZipFile(zipedFile, passwordChars)) { + ZipParameters parameters = new ZipParameters(); + parameters.setCompressionLevel(CompressionLevel.NORMAL); + parameters.setIncludeRootFolder(false); + + if (passwordChars != null) { + parameters.setEncryptFiles(true); + parameters.setEncryptionMethod(EncryptionMethod.ZIP_STANDARD); + } + + return ZipFileDictory(folderToZip, zipFile, "", parameters); + } + } + + private static boolean ZipManyFilesDictorys(String folderToZip, String zipedFile, String password) throws IOException { + String[] filesOrDirs = folderToZip.split(";"); + if (filesOrDirs.length == 0) { + return false; + } + + Path zipPath = Paths.get(zipedFile); + if (!Files.exists(zipPath.getParent())) { + Files.createDirectories(zipPath.getParent()); + } + + // 通过路径和密码创建ZipFile(修复构造函数错误) + char[] passwordChars = (password != null && !password.isEmpty()) ? password.toCharArray() : null; + try (ZipFile zipFile = new ZipFile(zipedFile, passwordChars)) { + ZipParameters parameters = new ZipParameters(); + parameters.setCompressionLevel(CompressionLevel.NORMAL); + + if (passwordChars != null) { + parameters.setEncryptFiles(true); + parameters.setEncryptionMethod(EncryptionMethod.ZIP_STANDARD); + } + + for (String path : filesOrDirs) { + path = path.trim(); + if (path.isEmpty()) continue; + + File target = new File(path); + if (target.isDirectory()) { + if (!ZipFileDictory(path, zipFile, "", parameters)) return false; + } else if (target.isFile()) { + if (!ZipFileWithStream(path, zipFile, parameters)) return false; + } + } + return true; + } + } + + private static boolean ZipFileWithStream(String fileToZip, ZipFile zipFile, ZipParameters parameters) throws IOException { + File file = new File(fileToZip); + if (!file.exists() || file.isDirectory()) { + throw new FileNotFoundException("指定要压缩的文件不存在:" + fileToZip); + } + + try { + parameters.setFileNameInZip(file.getName()); + try (FileInputStream fis = new FileInputStream(file)) { + zipFile.addStream(fis, parameters); + } + return true; + } catch (Exception e) { + throw e; + } finally { + System.gc(); + } + } + + private static boolean ZipFile(String fileToZip, String zipedFile, String password) throws IOException { + File file = new File(fileToZip); + if (!file.exists() || file.isDirectory()) { + throw new FileNotFoundException("指定要压缩的文件不存在:" + fileToZip); + } + + Path zipPath = Paths.get(zipedFile); + if (!Files.exists(zipPath.getParent())) { + Files.createDirectories(zipPath.getParent()); + } + + // 通过路径和密码创建ZipFile(修复构造函数错误) + char[] passwordChars = (password != null && !password.isEmpty()) ? password.toCharArray() : null; + try (ZipFile zipFile = new ZipFile(zipedFile, passwordChars)) { + ZipParameters parameters = new ZipParameters(); + parameters.setCompressionLevel(CompressionLevel.NORMAL); + parameters.setFileNameInZip(file.getName()); + + if (passwordChars != null) { + parameters.setEncryptFiles(true); + parameters.setEncryptionMethod(EncryptionMethod.ZIP_STANDARD); + } + + try (FileInputStream fis = new FileInputStream(file)) { + zipFile.addStream(fis, parameters); + } + return true; + } catch (Exception e) { + throw e; + } finally { + System.gc(); + } + } + + // ------------------------------------------------------------------------ + // 四、对外核心压缩入口 + // ------------------------------------------------------------------------ + public static boolean GoZip(String fileToZip, String zipedFile, String password) { + if (fileToZip == null || fileToZip.trim().isEmpty()) { + return false; + } + fileToZip = fileToZip.trim(); + + try { + if (IsFilesOrFolders(fileToZip)) { + return ZipManyFilesDictorys(fileToZip, zipedFile, password); + } + + File target = new File(fileToZip); + if (target.isDirectory()) { + return ZipFileDictory(fileToZip, zipedFile, password); + } else if (target.isFile()) { + return ZipFile(fileToZip, zipedFile, password); + } else { + return false; + } + } catch (Exception e) { + if (ShowInfo != null) { + ShowInfo.accept(String.format("%s: unzip failed: %s", getCurrentTime(), e.getMessage())); + } + return false; + } + } + + private static boolean IsFilesOrFolders(String fileFolders) { + return fileFolders.split(";").length > 1; + } + + // ------------------------------------------------------------------------ + // 五、带密码解压 + // ------------------------------------------------------------------------ + public static boolean UnZip(String fileToUpZip, String zipedFolder, String password) { + File zipFile = new File(fileToUpZip); + if (!zipFile.exists()) { + return false; + } + + File targetDir = new File(zipedFolder); + if (!targetDir.exists()) { + targetDir.mkdirs(); + } + + // 通过路径和密码创建ZipFile(修复构造函数错误) + char[] passwordChars = (password != null && !password.isEmpty()) ? password.toCharArray() : null; + try (ZipFile zip = new ZipFile(fileToUpZip, passwordChars)) { + Path targetRoot = Paths.get(zipedFolder).toAbsolutePath().normalize(); + for (FileHeader fileHeader : zip.getFileHeaders()) { + String entryName = fileHeader.getFileName(); + Path targetPath = resolveZipEntryPath(targetRoot, entryName); + + if (entryName.endsWith("/") || entryName.endsWith("\\")) { + Files.createDirectories(targetPath); + if (ShowInfo != null) { + ShowInfo.accept(String.format("%s:创建文件夹%s", getCurrentTime(), targetPath)); + } + continue; + } + + if (ShowInfo != null) { + ShowInfo.accept(String.format("%s:解压文件%s", getCurrentTime(), targetPath)); + } + + Path parent = targetPath.getParent(); + if (parent != null) { + Files.createDirectories(parent); + } + try (OutputStream os = Files.newOutputStream(targetPath); + InputStream is = zip.getInputStream(fileHeader)) { + + int size = 2048; + byte[] data = new byte[size]; + while ((size = is.read(data)) > 0) { + os.write(data, 0, size); + } + } + } + return true; + } catch (Exception e) { + if (ShowInfo != null) { + ShowInfo.accept(String.format("%s: unzip failed: %s", getCurrentTime(), e.getMessage())); + } + return false; + } + } + + private static Path resolveZipEntryPath(Path targetRoot, String entryName) throws IOException { + if (entryName == null || entryName.trim().isEmpty()) { + throw new IOException("压缩包条目名称为空"); + } + Path targetPath = targetRoot.resolve(entryName.replace("\\", "/")).normalize(); + if (!targetPath.startsWith(targetRoot)) { + throw new IOException("压缩包条目路径非法:" + entryName); + } + return targetPath; + } + + // ------------------------------------------------------------------------ + // 六、统计ZIP条目数(核心修复点) + // ------------------------------------------------------------------------ + public static int GetZipFileCount(String fileToUpZip) { + File zipFile = new File(fileToUpZip); + if (!zipFile.exists()) { + return 0; + } + + // 修复:通过File对象创建ZipFile,而非FileInputStream + try (ZipFile zip = new ZipFile(zipFile)) { + return zip.getFileHeaders().size(); + } catch (Exception e) { + if (ShowInfo != null) { + ShowInfo.accept(String.format("%s: read zip failed: %s", getCurrentTime(), e.getMessage())); + } + return 0; + } + } + + // ------------------------------------------------------------------------ + // 工具方法 + // ------------------------------------------------------------------------ + private static CompressionLevel intToCompressionLevel(int intLevel) { + return switch (intLevel) { + case 0 -> CompressionLevel.NO_COMPRESSION; + case 1 -> CompressionLevel.FASTEST; + case 2, 3 -> CompressionLevel.FAST; + case 4, 5, 6 -> CompressionLevel.NORMAL; + case 7, 8 -> CompressionLevel.MAXIMUM; + case 9 -> CompressionLevel.ULTRA; + default -> CompressionLevel.NORMAL; + }; + } + + private static String getCurrentTime() { + return new SimpleDateFormat("HH:mm:ss").format(new Date()); + } +} diff --git a/WebErp/weberp/src/main/java/org/example/WebErpApplication.java b/WebErp/weberp/src/main/java/org/example/WebErpApplication.java new file mode 100644 index 0000000..62851b6 --- /dev/null +++ b/WebErp/weberp/src/main/java/org/example/WebErpApplication.java @@ -0,0 +1,25 @@ +package org.example; + +import org.example.Utils.SqlSafetyGuard; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import java.sql.SQLException; + +/** + * Hello world! + * + */ +@SpringBootApplication +public class WebErpApplication { + private static final Logger log = LoggerFactory.getLogger(WebErpApplication.class); + + public static void main(String[] args) throws SQLException { + SpringApplication.run(WebErpApplication.class, args); + log.info("SqlSafetyGuard rule version: {}", SqlSafetyGuard.RULE_VERSION); + log.debug(String.valueOf("当前类路径:" + WebErpApplication.class.getResource("/").getPath())); + } +} diff --git a/WebErp/weberp/src/main/resources/application-dev.properties b/WebErp/weberp/src/main/resources/application-dev.properties new file mode 100644 index 0000000..cd4ec9d --- /dev/null +++ b/WebErp/weberp/src/main/resources/application-dev.properties @@ -0,0 +1,6 @@ +# Development-only SQL diagnostics. Do not enable these levels in production. +logging.level.org.apache.ibatis=DEBUG +logging.level.org.mybatis=DEBUG +logging.level.org.springframework.jdbc.core=DEBUG +logging.level.org.springframework.jdbc.core.StatementCreatorUtils=TRACE +logging.level.org.example.System_Customer.ModuleAjaxApi.mapper=DEBUG diff --git a/WebErp/weberp/src/main/resources/application.properties b/WebErp/weberp/src/main/resources/application.properties new file mode 100644 index 0000000..de9998e --- /dev/null +++ b/WebErp/weberp/src/main/resources/application.properties @@ -0,0 +1,105 @@ + +server.port=8088 + +server.servlet.encoding.charset=UTF-8 +server.servlet.encoding.enabled=true +server.servlet.encoding.force=true + +# \u6570\u636E\u5E93\u7C7B\u578B\u914D\u7F6E\uFF1Akingbase\uFF08\u4EBA\u5927\u91D1\u4ED3\uFF09\u3001dm\uFF08\u8FBE\u68A6\uFF09 +custom.database.type=dm +# 1. \u6570\u636E\u6E90\u914D\u7F6E +# \u4ED3\u5E93\u5185\u53EA\u4FDD\u7559\u672C\u5730/\u793A\u4F8B\u914D\u7F6E\uFF1B\u771F\u5B9E\u751F\u4EA7\u8FDE\u63A5\u4E32\u5199\u5165 application-prod.properties\uFF0C\u4EC5\u653E\u5728\u90E8\u7F72\u670D\u52A1\u5668\u672C\u5730\u3002 +spring.datasource.url=${SPRING_DATASOURCE_URL:} +#spring.datasource.username=${SPRING_DATASOURCE_USERNAME:} +#spring.datasource.password=${SPRING_DATASOURCE_PASSWORD:} +spring.datasource.driver-class-name=dm.jdbc.driver.DmDriver +# MyBatis ????????????? mapper ??? +mybatis.mapper-locations=classpath:mapper/*.xml +mybatis.type-aliases-package=org.example.entity + +## MyBatis ?????? MyBatis ????? MyBatis - Plus ?? +#mybatis.configuration.database-id=sqlserver +## ?? MyBatis - Plus ?? +#mybatis-plus.configuration.database-id=sqlserver + +#mybatis.configuration.default-schema=dbo + +# \u914D\u7F6E\u8FBE\u68A6\u6570\u636E\u5E93\u65B9\u8A00\uFF08PageHelper\u652F\u6301dm\u65B9\u8A00\uFF09 +pagehelper.helper-dialect=dm +# \u5F00\u542F\u5408\u7406\u5316\u5206\u9875\uFF08\u53EF\u9009\uFF0C\u5904\u7406\u9875\u7801\u8D8A\u754C\uFF09 +pagehelper.reasonable=true + +# MyBatis logging uses SLF4J. SQL details are enabled only in application-dev.properties. +mybatis.configuration.log-impl=org.apache.ibatis.logging.slf4j.Slf4jImpl +logging.level.org.apache.ibatis=INFO +logging.level.org.mybatis=INFO +logging.level.org.springframework.jdbc.core=INFO +logging.level.org.example.System_Customer.ModuleAjaxApi.mapper=OFF +logging.level.org.apache.ibatis.executor.resultset.DefaultResultSetHandler=OFF + +#jwt.secret=${JWT_SECRET:} +# ?????????? 1 ?????? +jwt.expiration=10000 +-XX:+EnableJVMCI +spring.jackson.default-property-inclusion=NON_NULL + +# Redis????? +spring.data.redis.host=192.168.0.102 +# Redis????? +spring.data.redis.port=6379 +# Redis?????) +# ?????????? +spring.data.redis.timeout=3000 +## \u5173\u952E\uFF1A\u542F\u7528Redis\u5B58\u50A8Session\uFF08\u9ED8\u8BA4\u662F\u5185\u5B58\u5B58\u50A8\uFF09 +#spring.session.store-type=redis +#spring.session.store-type=memory +# +## Session\u8FC7\u671F\u65F6\u95F4\uFF08\u5EFA\u8BAE\u548CJWT\u7F13\u5B58\u4E00\u81F4\uFF0C\u6BD4\u59822\u5C0F\u65F6\uFF0C\u5355\u4F4Ds\uFF09 +#server.servlet.session.timeout=7200s + + +# ???????????language=en_US +language=Language_CN +# ???????????language=en_US +# ?? RestInitPwd ?????????? true/false/1/on ? +RestInitPwd=${REST_INIT_PWD:} +# ???????? +LockErrPwd=${LOCK_ERR_PWD:} +ErrPwdLockDay=1 +ErrPwdNum=5 +SingleUser=false +dbServer=192.168.0.1 + +#\u8DEF\u5F84\u914D\u7F6E +#fileVPath=null +spring.datasource.hikari.maximum-pool-size=20 +spring.datasource.hikari.minimum-idle=2 +spring.datasource.hikari.idle-timeout=900000 +spring.datasource.hikari.connection-timeout=120000 +spring.datasource.hikari.max-lifetime=1080000 +spring.datasource.hikari.pool-name=MyHikariPool +spring.datasource.hikari.leak-detection-threshold=60000 +spring.datasource.hikari.connection-test-query=SELECT 1 FROM DUAL +spring.datasource.hikari.auto-commit=true +MsgBtnOrder=1 +useDbAttc=true +NAttcReName = true +#server.servlet.session.cookie.same-site=Lax +##HTTP\u8DE8\u57DF\u573A\u666F\u517C\u5BB9\uFF0C\u6D4F\u89C8\u5668\u5141\u8BB8\u643A\u5E26Cookie +#server.servlet.session.cookie.secure=false +## HTTP\u534F\u8BAE\u4E0B\u5173\u95EDSecure\uFF08\u5426\u5219\u6D4F\u89C8\u5668\u4E0D\u5B58\u50A8Cookie\uFF09 + +app.datasource.dynamic.max-pools=32 +app.datasource.dynamic.idle-close-minutes=60 +app.cache.max-entries=5000 +app.js-cache.max-entries=1000 +app.executor.file-cleanup.core-size=2 +app.executor.file-cleanup.max-size=4 +app.executor.file-cleanup.queue-capacity=200 +app.executor.push.core-size=4 +app.executor.push.max-size=16 +app.executor.push.queue-capacity=500 +app.remote-download.max-bytes=104857600 +app.remote-download.allow-private-address=false +app.js.allow-all-access=false +app.sql.read-only-guard.enabled=false diff --git a/WebErp/weberp/src/main/resources/mapper/CustomerMapper.xml b/WebErp/weberp/src/main/resources/mapper/CustomerMapper.xml new file mode 100644 index 0000000..de9d760 --- /dev/null +++ b/WebErp/weberp/src/main/resources/mapper/CustomerMapper.xml @@ -0,0 +1,840 @@ + + + + + + + + + + + + + + + + + select top 1000 + book.id, book.FieldName, + isnull(CONVERT(varchar (200), gcfg.userName), + isnull(ISNULL(username1, sysname), book.FieldName)) FieldCaption, + ControlWidth, + ControlHeight, + ${windowsDirver ? 'isnull(1-gcfg.isvisible,book.vislble)' : 'case when isnull(ShowMobile,0)=0 then 1 else 0 end'} Disabled, + ${windowsDirver ? 'book.addVisible' : 'case when isnull(ShowMobile1,0)=0 then 1 else 0 end'} Disabled2, + fieldsqltag FieldType, + addModuleId, + ${windowsDirver ? '0' : 'book.vislble5'} scanAble, + ControlTop, + ControlLeft, + book.tagid nullable, + book.cancopy, + case + when isnull(book.privilegeOper, '') = '' then edit + -- 修复此处的charindex参数,移除convert函数后的多余逗号 + when charIndex(',1,', ',' + (SELECT userList[data()] + FROM dbo.p_systemPrivilege pri + WHERE book.tab = pri.modid + AND charIndex(',' + convert(varchar (10), pri.privTypeId) + ',', + ',' + book.privilegeOper + ',') > 0 + FOR XML PATH ('') ) + ',') > 0 then 0 + else 1 end Edit, + isSum[sum], + sumCond, + sumCalc, + fieldsql, + fieldsqlid valuemember, + fieldsqlname displaymember, + calcExpr CalcExpress, + calcOrder CalcOrderId, + ${windowsDirver ? 'isnull(gcfg.fieldwidth,width)' : 'mobilewidth'} width, + book.fieldkey fromkey, + book.disableType, + book.IsAddControl, + book.dataAlign textalign, + unionValue UnionSQL, + unionFields UnionField, + dataformat, + BandTitle, + BandFields BandField, + InputHintText HintText, + TitleColor FontColor, + limitLen LimitMaxValue, + defaultdate defaultvalue, + TM_HeadString othermember, + book.labelWidth, + book.labelAlign, + book.disableCond, + c.xtype fieldDbType, + frozenFlag locked, + -- 修复此处的charindex参数,移除convert函数后的多余逗号 + (SELECT userList[data()] + FROM dbo.p_systemPrivilege pri + WHERE book.tab = pri.modid + AND charIndex(',', '+' + convert(varchar (10), pri.privTypeId) + -- 移除这里的逗号 + ',', ',' + book.privilegeView + ',') > 0 + FOR XML PATH('') ) userList, book.FontSize fontSize, book.highlightFColor fcolor, book.highlightBColor bcolor, book.highlightBold bold, book.doNotSpelling, book.lookupWidth pickerWidth, book.lookupFieldsWidth pickerColsWidth, book.bmptype ftype, book.ifmerge rowspan + from dbo.p_systemwordbooktab book + left join dbo.p_systemdlltab dll + on book.tab = dll.dllcoid + left join syscolumns c on c.id = OBJECT_ID(dll.sqldt1) and c.name = book.fieldname + left join dbo.P_systemGridConfigTab gcfg on '${baseMainGridViewPrefix}' + convert (varchar (100), dll.formkey) = convert (varchar (100), gcfg.formkey) + and book.FieldName = gcfg.fieldname + and gcfg.operatorid = '${userId}' + where 1 = 1 + and (isnull(book.privilegeView + , '') = '' + or charIndex(',${userName},' + , ',' + ( + SELECT userList[data ()] + FROM dbo.p_systemPrivilege pri + WHERE book.tab = pri.modid + -- 修复此处的charindex参数,移除convert函数后的多余逗号 + AND charIndex(',' + , '+' + convert (varchar (10) + , pri.privTypeId) + -- 移除这里的逗号 + ',' + , ',' + book.privilegeView + ',') + > 0 + FOR XML PATH ('') + ) + ',') + > 0) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebErp/weberp/src/main/resources/mapper/DMCrmMapper.xml b/WebErp/weberp/src/main/resources/mapper/DMCrmMapper.xml new file mode 100644 index 0000000..46be546 --- /dev/null +++ b/WebErp/weberp/src/main/resources/mapper/DMCrmMapper.xml @@ -0,0 +1,710 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebErp/weberp/src/main/resources/mapper/PageBreaks.xml b/WebErp/weberp/src/main/resources/mapper/PageBreaks.xml new file mode 100644 index 0000000..266a67a --- /dev/null +++ b/WebErp/weberp/src/main/resources/mapper/PageBreaks.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/WebErp/weberp/src/main/resources/mybatis-config.xml b/WebErp/weberp/src/main/resources/mybatis-config.xml new file mode 100644 index 0000000..b166e62 --- /dev/null +++ b/WebErp/weberp/src/main/resources/mybatis-config.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/WebErp/weberp/src/main/resources/org/example/Utils/Language_CN.properties b/WebErp/weberp/src/main/resources/org/example/Utils/Language_CN.properties new file mode 100644 index 0000000..eedd847 --- /dev/null +++ b/WebErp/weberp/src/main/resources/org/example/Utils/Language_CN.properties @@ -0,0 +1,26 @@ +BillDetailNotNull=\u5355\u636E\u660E\u7EC6\u4E0D\u80FD\u4E3A\u7A7A +BillNoExist=\u5355\u636E\u53F7\u5DF2\u7ECF\u5B58\u5728\uFF0C\u662F\u5426\u91CD\u65B0\u751F\u6210\uFF1F +DifferentPwd=\u4E24\u6B21\u8F93\u5165\u7684\u5BC6\u7801\u4E0D\u4E00\u81F4 +ErrorReason=\u9519\u8BEF\u539F\u56E0 +Fail=\u64CD\u4F5C\u5931\u8D25 +FileExistCover=\u6587\u4EF6[{0}]\u5DF2\u5B58\u5728\uFF0C\u662F\u5426\u8986\u76D6\uFF1F +InvalidCode=\u65E0\u6548\u7F16\u7801 +InvalidParameter=\u65E0\u6548\u53C2\u6570 +InvalidSql=\u65E0\u6548\u7684sql\u8BED\u53E5 +LimitOfTheNumber=\u6570\u91CF\u8FBE\u5230\u9650\u5236 +LoadFailed=\u52A0\u8F7D\u5931\u8D25 +ModuleNotFound=\u672A\u627E\u5230\u6A21\u5757 +NameOrPwdError=\u8D26\u53F7\u6216\u5BC6\u7801\u4E0D\u6B63\u786E +NoAuthory=\u6CA1\u6709\u6743\u9650\u64CD\u4F5C +NoneField=\u6CA1\u6709\u9700\u8981\u4FEE\u6539\u7684\u5B57\u6BB5 +NoneSelected=\u672A\u9009\u4E2D\u64CD\u4F5C\u5BF9\u8C61 +NotNull=\u4E0D\u80FD\u4E3A\u7A7A +SamePwd=\u65B0\u5BC6\u7801\u548C\u65E7\u5BC6\u7801\u4E00\u81F4 +ServerError=\u670D\u52A1\u5668\u51FA\u4E86\u4E00\u70B9\u5C0F\u6545\u969C... +Success=\u64CD\u4F5C\u6210\u529F +Wrong=\u9519\u8BEF\u7684 +WrongInterfaceName=\u9519\u8BEF\u7684\u63A5\u53E3\u540D\u79F0 +WrongMainCfg=\u4E3B\u914D\u7F6E{0}\u9519\u8BEF +WrongOldPwd=\u539F\u59CB\u5BC6\u7801\u9519\u8BEF +WrongPhoneNumber=\u65E0\u6548\u7684\u624B\u673A\u53F7\u7801 +WrongVCode=\u9A8C\u8BC1\u7801\u9519\u8BEF diff --git a/WebErp/weberp/src/main/resources/org/example/Utils/Language_EN.properties b/WebErp/weberp/src/main/resources/org/example/Utils/Language_EN.properties new file mode 100644 index 0000000..ecf5104 --- /dev/null +++ b/WebErp/weberp/src/main/resources/org/example/Utils/Language_EN.properties @@ -0,0 +1,26 @@ +BillDetailNotNull=bill No was exist,create new? +BillNoExist=bill No was exist,create new? +DifferentPwd=The new password and old password is not consistent +ErrorReason=The reason for the error +Fail=Operation Failed +FileExistCover=File[{0}]was existed?cover it? +InvalidCode=Invalid Code +InvalidParameter=Invalid Parameter +InvalidSql=Invalid Sql +LimitOfTheNumber=limit of the number of +LoadFailed=Load failed +ModuleNotFound=Module Not Found +NameOrPwdError=Name Or Pwd Error +NoAuthory=No Authory +NoneField=None Field to Update +NoneSelected=None Selected +NotNull=not null +SamePwd=Same Password +ServerError=Server Error +Success=Success +Wrong=wrong +WrongInterfaceName=Wrong Interface Name +WrongMainCfg=Wrong Main Config:{0} +WrongOldPwd=The old password is not correct +WrongPhoneNumber=The Wrong Phone Number +WrongVCode=Wrong verification code diff --git a/WebErp/weberp/src/test/java/org/example/AppTest.java b/WebErp/weberp/src/test/java/org/example/AppTest.java new file mode 100644 index 0000000..d5f435d --- /dev/null +++ b/WebErp/weberp/src/test/java/org/example/AppTest.java @@ -0,0 +1,38 @@ +package org.example; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Unit test for simple App. + */ +public class AppTest + extends TestCase +{ + /** + * Create the test case + * + * @param testName name of the test case + */ + public AppTest( String testName ) + { + super( testName ); + } + + /** + * @return the suite of tests being tested + */ + public static Test suite() + { + return new TestSuite( AppTest.class ); + } + + /** + * Rigourous Test :-) + */ + public void testApp() + { + assertTrue( true ); + } +} diff --git a/WebErp/weberp/src/test/java/org/example/ExceptionSummaryUtilTest.java b/WebErp/weberp/src/test/java/org/example/ExceptionSummaryUtilTest.java new file mode 100644 index 0000000..f440b37 --- /dev/null +++ b/WebErp/weberp/src/test/java/org/example/ExceptionSummaryUtilTest.java @@ -0,0 +1,91 @@ +package org.example; + +import org.example.Utils.ExceptionSummaryUtil; +import org.junit.jupiter.api.Test; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ExceptionSummaryUtilTest { + + @Test + void summarizesDmInvalidTableErrorForClient() { + RuntimeException exception = new RuntimeException("invoke failed", + new RuntimeException(""" + org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; + bad SQL grammar []; nested exception is dm.jdbc.driver.DMException: 第53 行附近出现错误: + 无效的表或视图名[MRP_CalcMaterialsumListTab] + at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:476) + ... 103 common frames omitted + """)); + + String summary = ExceptionSummaryUtil.summarizeForClient(exception); + + assertEquals("无效的表或视图[MRP_CalcMaterialsumListTab]", summary); + } + + @Test + void clientSummaryDoesNotExposeStackTraceNoise() { + RuntimeException exception = new RuntimeException("wrapper", + new RuntimeException(""" + execute sql error:select * from demo,pms:[],mag:org.springframework.jdbc.BadSqlGrammarException + at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:476) + Caused by: java.sql.SQLException: SQL语法错误: 缺少关键字 + ... 103 common frames omitted + """)); + + String summary = ExceptionSummaryUtil.summarizeForClient(exception); + + assertFalse(summary.contains("org.springframework")); + assertFalse(summary.contains("JdbcTemplate")); + assertFalse(summary.contains("StackTraceElement")); + assertFalse(summary.contains("common frames omitted")); + assertFalse(summary.contains("\tat ")); + assertTrue(summary.length() <= 300); + assertTrue(summary.contains("SQL语法错误") || summary.contains("execute sql error")); + } + + @Test + void summarizesDeepDmMemberAccessErrorInsteadOfSpringSqlWrapper() { + RuntimeException exception = new RuntimeException("java.lang.reflect.InvocationTargetException", + new RuntimeException(""" + org.springframework.dao.DataIntegrityViolationException: StatementCallback; SQL [SELECT + '订单MRP分析' as mps_lpt_sourcename, + itemno as mps_lpt_tm, + a.htids as mps_lpt_htids, + a.htnames as mps_lpt_htnames + from MRP_CalcMaterialsumListTab a + where charindex(','+cast(a.id as varchar)+',',',16921,')>0]; 第53 行附近出现错误: + 无法解析的成员访问表达式[a.htids] + at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:476) + ... 103 common frames omitted + Caused by: dm.jdbc.driver.DMException: 第53 行附近出现错误: + 无法解析的成员访问表达式[a.htids] + at dm.jdbc.driver.DBError.throwException(SourceFile:793) + """)); + + String summary = ExceptionSummaryUtil.summarizeForClient(exception); + + assertTrue(summary.contains("第53行附近出现错误")); + assertTrue(summary.contains("无法解析的成员访问表达式[a.htids]")); + assertFalse(summary.contains("StatementCallback")); + assertFalse(summary.contains("SQL [SELECT")); + assertFalse(summary.contains("MRP_CalcMaterialsumListTab")); + assertFalse(summary.contains("JdbcTemplate")); + assertFalse(summary.contains("org.springframework")); + assertFalse(summary.contains("common frames omitted")); + assertTrue(summary.length() <= 300); + } + + @Test + void baseHandlerDoesNotPutStackTraceArrayIntoClientResponse() throws Exception { + String source = Files.readString(Path.of("src/main/java/org/example/Api/BaseHandler.java")); + + assertFalse(source.contains("Arrays.toString(ex.getCause().getStackTrace())")); + assertFalse(source.contains("response.setMsg(errMsg.substring(errMsg.indexOf(\"execute sql error\")))")); + } +} diff --git a/WebErp/weberp/src/test/java/org/example/JdbcTemplateReuseRegressionTest.java b/WebErp/weberp/src/test/java/org/example/JdbcTemplateReuseRegressionTest.java new file mode 100644 index 0000000..1c29978 --- /dev/null +++ b/WebErp/weberp/src/test/java/org/example/JdbcTemplateReuseRegressionTest.java @@ -0,0 +1,30 @@ +package org.example; + +import org.example.Entity.Control.Base.Component; +import org.junit.jupiter.api.Test; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DriverManagerDataSource; + +import static org.junit.jupiter.api.Assertions.assertSame; + +class JdbcTemplateReuseRegressionTest { + + @Test + void componentReusesInjectedJdbcTemplateWhenResolvingDbOperator() { + JdbcTemplate jdbcTemplate = new JdbcTemplate(new DriverManagerDataSource()); + TestComponent component = new TestComponent(); + component.setJdbcTemplate(jdbcTemplate); + + assertSame(jdbcTemplate, component.dbOperator()); + } + + private static class TestComponent extends Component { + void setJdbcTemplate(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + JdbcTemplate dbOperator() { + return getDbOperator(); + } + } +} diff --git a/WebErp/weberp/src/test/java/org/example/JdbcVersionChecker.java b/WebErp/weberp/src/test/java/org/example/JdbcVersionChecker.java new file mode 100644 index 0000000..0842b99 --- /dev/null +++ b/WebErp/weberp/src/test/java/org/example/JdbcVersionChecker.java @@ -0,0 +1,49 @@ +package org.example; + +import org.example.Auth.utils.SafetyUtil; +import org.example.Utils.ConfigUtil; + +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +import static org.example.Utils.PublicUtil.GenerateCaptchaImage; + +public class JdbcVersionChecker { + public static void main(String[] args) { + // 1. 生成6位验证码(复用之前的RandomCodeGenerator类) + String code = "8K792B"; // 可替换为RandomCodeGenerator.generateRandomCode(6) + System.out.println("验证码:" + code); + + // 2. 生成验证码图片到内存流(对应C#的MemoryStream) + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + try { + GenerateCaptchaImage(code, stream); + } catch (IOException e) { + throw new RuntimeException(e); + } + +// 可选:将内存流写入文件,验证效果 + FileOutputStream fileOut = null; + try { + fileOut = new FileOutputStream("captcha.png"); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + try { + stream.writeTo(fileOut); + } catch (IOException e) { + throw new RuntimeException(e); + } + try { + fileOut.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + System.out.println("验证码图片已保存为 captcha.png"); + } +} \ No newline at end of file diff --git a/WebErp/weberp/src/test/java/org/example/LoggingHygieneRegressionTest.java b/WebErp/weberp/src/test/java/org/example/LoggingHygieneRegressionTest.java new file mode 100644 index 0000000..97d2415 --- /dev/null +++ b/WebErp/weberp/src/test/java/org/example/LoggingHygieneRegressionTest.java @@ -0,0 +1,86 @@ +package org.example; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class LoggingHygieneRegressionTest { + + @Test + void productionJavaDoesNotUseConsoleLogging() throws IOException { + Path sourceRoot = moduleRoot().resolve("src/main/java"); + List violations; + try (Stream files = Files.walk(sourceRoot)) { + violations = files + .filter(path -> path.toString().endsWith(".java")) + .flatMap(path -> activeLines(path).stream()) + .filter(line -> line.contains("System.out") + || line.contains("System.err") + || line.contains("printStackTrace")) + .toList(); + } + + assertEquals(List.of(), violations); + } + + @Test + void productionJavaDoesNotUseStaticConsoleAliases() throws IOException { + Path sourceRoot = moduleRoot().resolve("src/main/java"); + List violations; + try (Stream files = Files.walk(sourceRoot)) { + violations = files + .filter(path -> path.toString().endsWith(".java")) + .flatMap(path -> activeLines(path).stream()) + .filter(line -> line.contains("import static java.lang.System.out") + || line.contains("import static java.lang.System.err") + || line.matches(".*\\b(out|err)\\.(print|println|printf)\\s*\\(.*")) + .toList(); + } + + assertEquals(List.of(), violations); + } + + @Test + void mybatisUsesSlf4jInsteadOfStdoutLogging() throws IOException { + String applicationProperties = Files.readString(moduleRoot().resolve("src/main/resources/application.properties")); + + assertFalse(applicationProperties.contains("org.apache.ibatis.logging.stdout.StdOutImpl")); + assertTrue(applicationProperties.contains("mybatis.configuration.log-impl=org.apache.ibatis.logging.slf4j.Slf4jImpl")); + } + + private static Path moduleRoot() { + Path current = Path.of("").toAbsolutePath(); + if (Files.exists(current.resolve("src/main/java"))) { + return current; + } + Path childModule = current.resolve("weberp"); + if (Files.exists(childModule.resolve("src/main/java"))) { + return childModule; + } + throw new IllegalStateException("Cannot locate weberp module root from " + current); + } + + private static List activeLines(Path path) { + try { + List lines = Files.readAllLines(path); + return IntStream.range(0, lines.size()) + .filter(index -> { + String stripped = lines.get(index).stripLeading(); + return !stripped.startsWith("//") && !stripped.startsWith("*"); + }) + .mapToObj(index -> path + ":" + (index + 1) + ":" + lines.get(index)) + .toList(); + } catch (IOException ex) { + throw new IllegalStateException("Cannot read " + path, ex); + } + } +} diff --git a/WebErp/weberp/src/test/java/org/example/ResourceGovernanceRegressionTest.java b/WebErp/weberp/src/test/java/org/example/ResourceGovernanceRegressionTest.java new file mode 100644 index 0000000..a8228bd --- /dev/null +++ b/WebErp/weberp/src/test/java/org/example/ResourceGovernanceRegressionTest.java @@ -0,0 +1,214 @@ +package org.example; + +import org.example.Utils.CacheUtil; +import org.example.Utils.DbOperator; +import org.example.Utils.DynamicJdbcTemplateRegistry; +import org.example.Utils.JsEngine; +import org.example.Utils.RequestUtil; +import org.example.Utils.ResourceExecutors; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.datasource.AbstractDataSource; +import org.springframework.jdbc.datasource.DriverManagerDataSource; + +import javax.sql.DataSource; +import java.io.ByteArrayInputStream; +import java.lang.reflect.Field; +import java.sql.Connection; +import java.sql.SQLException; +import java.time.Duration; +import java.util.concurrent.ThreadPoolExecutor; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ResourceGovernanceRegressionTest { + + @AfterEach + void resetStaticState() { + DynamicJdbcTemplateRegistry.resetForTests(); + CacheUtil.resetForTests(); + JsEngine.resetCacheForTests(); + ResourceExecutors.resetForTests(); + RequestUtil.clear(); + } + + @Test + void dbOperatorConstructorInitializesDerivedJdbcState() throws Exception { + DataSource dataSource = new DriverManagerDataSource(); + JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + + DbOperator dbOperator = new DbOperator(jdbcTemplate); + + assertSame(jdbcTemplate, dbOperator.getJdbcTemplate()); + assertSame(dataSource, readField(dbOperator, "currentDataSource")); + NamedParameterJdbcTemplate named = readField(dbOperator, "namedJdbcTemplate"); + assertSame(jdbcTemplate.getDataSource(), named.getJdbcTemplate().getDataSource()); + } + + @Test + void dynamicRegistryReusesJdbcTemplateForSameConnectionString() { + DynamicJdbcTemplateRegistry.configureForTests(32, Duration.ofMinutes(60)); + CloseTrackingDataSource dataSource = new CloseTrackingDataSource(); + + JdbcTemplate first = DynamicJdbcTemplateRegistry.getOrCreate( + "jdbc:test://tenant-a", + ignored -> new JdbcTemplate(dataSource) + ); + JdbcTemplate second = DynamicJdbcTemplateRegistry.getOrCreate( + "jdbc:test://tenant-a", + ignored -> new JdbcTemplate(new CloseTrackingDataSource()) + ); + + assertSame(first, second); + assertEquals(1, DynamicJdbcTemplateRegistry.getActivePoolCount()); + assertFalse(dataSource.closed); + } + + @Test + void dynamicRegistryClosesOldestPoolWhenLimitIsExceeded() { + DynamicJdbcTemplateRegistry.configureForTests(1, Duration.ofMinutes(60)); + CloseTrackingDataSource firstDataSource = new CloseTrackingDataSource(); + CloseTrackingDataSource secondDataSource = new CloseTrackingDataSource(); + + DynamicJdbcTemplateRegistry.getOrCreate("jdbc:test://tenant-a", ignored -> new JdbcTemplate(firstDataSource)); + DynamicJdbcTemplateRegistry.getOrCreate("jdbc:test://tenant-b", ignored -> new JdbcTemplate(secondDataSource)); + + assertTrue(firstDataSource.closed); + assertFalse(secondDataSource.closed); + assertEquals(1, DynamicJdbcTemplateRegistry.getActivePoolCount()); + } + + @Test + void dynamicRegistryClosesIdlePools() { + DynamicJdbcTemplateRegistry.configureForTests(32, Duration.ZERO); + CloseTrackingDataSource dataSource = new CloseTrackingDataSource(); + + DynamicJdbcTemplateRegistry.getOrCreate("jdbc:test://tenant-a", ignored -> new JdbcTemplate(dataSource)); + DynamicJdbcTemplateRegistry.closeIdlePools(); + + assertTrue(dataSource.closed); + assertEquals(0, DynamicJdbcTemplateRegistry.getActivePoolCount()); + } + + @Test + void requestClearRemovesEveryThreadLocalCache() throws Exception { + setThreadLocal("rawContentCache", new byte[]{1, 2, 3}); + setThreadLocal("cachedEntityStream", new ByteArrayInputStream(new byte[]{4})); + setThreadLocal("cachedInputStream", new ByteArrayInputStream(new byte[]{5})); + setThreadLocal("readEntityBodyMode", RequestUtil.ReadEntityBodyMode.Buffered); + setThreadLocal("hasWorkerRequest", Boolean.FALSE); + + RequestUtil.clear(); + + assertThreadLocalEmpty("rawContentCache"); + assertThreadLocalEmpty("cachedEntityStream"); + assertThreadLocalEmpty("cachedInputStream"); + assertThreadLocalEmpty("readEntityBodyMode"); + assertThreadLocalEmpty("hasWorkerRequest"); + } + + @Test + void cacheUtilEvictsOldEntriesWhenCapacityIsExceeded() { + CacheUtil.setMaxEntriesForTests(2); + + CacheUtil.set("a", "one", Duration.ofMinutes(10), null); + CacheUtil.set("b", "two", Duration.ofMinutes(10), null); + CacheUtil.set("c", "three", Duration.ofMinutes(10), null); + + assertEquals(2, CacheUtil.sizeForTests()); + assertEquals("two", CacheUtil.get("b")); + assertEquals("three", CacheUtil.get("c")); + } + + @Test + void jsEngineEvictsOldEntriesWhenCapacityIsExceeded() { + JsEngine.setMaxCacheEntriesForTests(2); + + JsEngine.putCachedResultForTests("a", 1); + JsEngine.putCachedResultForTests("b", 2); + JsEngine.putCachedResultForTests("c", 3); + + assertEquals(2, JsEngine.cacheSizeForTests()); + assertFalse(JsEngine.hasCachedResultForTests("a")); + assertTrue(JsEngine.hasCachedResultForTests("b")); + assertTrue(JsEngine.hasCachedResultForTests("c")); + } + + @Test + void jsEngineEvaluatesSimpleExpression() { + Object result = JsEngine.Eval("1 + 1"); + + assertEquals("2", String.valueOf(result)); + } + + @Test + void jsEngineBlocksJavaAccessByDefault() { + Object result = JsEngine.Eval("Java.type('java.lang.System').getProperty('user.home')"); + + assertEquals(null, result); + } + + @Test + void resourceExecutorsUseBoundedQueues() { + ResourceExecutors.configureForTests(2, 4, 200, 4, 16, 500); + + ThreadPoolExecutor fileExecutor = ResourceExecutors.fileCleanupExecutorForTests(); + ThreadPoolExecutor pushExecutor = ResourceExecutors.pushExecutorForTests(); + + assertEquals(2, fileExecutor.getCorePoolSize()); + assertEquals(4, fileExecutor.getMaximumPoolSize()); + assertEquals(200, fileExecutor.getQueue().remainingCapacity()); + assertEquals(4, pushExecutor.getCorePoolSize()); + assertEquals(16, pushExecutor.getMaximumPoolSize()); + assertEquals(500, pushExecutor.getQueue().remainingCapacity()); + } + + @SuppressWarnings("unchecked") + private static void setThreadLocal(String fieldName, Object value) throws Exception { + ThreadLocal threadLocal = readStaticField(RequestUtil.class, fieldName); + threadLocal.set(value); + } + + private static void assertThreadLocalEmpty(String fieldName) throws Exception { + ThreadLocal threadLocal = readStaticField(RequestUtil.class, fieldName); + assertEquals(null, threadLocal.get()); + } + + @SuppressWarnings("unchecked") + private static T readField(Object target, String fieldName) throws Exception { + Field field = target.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(target); + } + + @SuppressWarnings("unchecked") + private static T readStaticField(Class type, String fieldName) throws Exception { + Field field = type.getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(null); + } + + private static class CloseTrackingDataSource extends AbstractDataSource implements AutoCloseable { + boolean closed; + + @Override + public Connection getConnection() throws SQLException { + throw new SQLException("No real connection in resource lifecycle tests"); + } + + @Override + public Connection getConnection(String username, String password) throws SQLException { + throw new SQLException("No real connection in resource lifecycle tests"); + } + + @Override + public void close() { + closed = true; + } + } +} diff --git a/WebErp/weberp/src/test/java/org/example/SecurityRegressionTest.java b/WebErp/weberp/src/test/java/org/example/SecurityRegressionTest.java new file mode 100644 index 0000000..d393a7c --- /dev/null +++ b/WebErp/weberp/src/test/java/org/example/SecurityRegressionTest.java @@ -0,0 +1,295 @@ +package org.example; + +import jakarta.servlet.ServletContext; +import org.example.Api.PublicApiRegistry; +import org.example.Auth.controller.AuthController; +import org.example.Entity.System.LoginUserInfo; +import org.example.Config.CorsConfig; +import org.example.Impl.BaseImpl; +import org.example.ModuleApi.ModuleAjaxApi.controller.ModuleAjaxController; +import org.example.SystemApi.controller.SystemAjaxApi; +import org.example.Utils.FileUtil; +import org.example.Utils.JwtHelp; +import org.example.Utils.RemoteDownloadGuard; +import org.example.Utils.SqlSafetyGuard; +import org.example.Utils.ZipUtil; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.springframework.http.HttpHeaders; +import org.springframework.mock.web.MockFilterChain; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.lang.reflect.Field; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class SecurityRegressionTest { + + @TempDir + Path tempDir; + + @AfterEach + void clearRequestContext() { + RequestContextHolder.resetRequestAttributes(); + } + + @Test + void fileAuthorityRejectsPathThatOnlyContainsAllowedRootName() throws IOException { + Path allowedRootNameInsideAnotherDirectory = tempDir.resolve("home").resolve("Lserp").resolve("fileRoot_evil"); + Path file = allowedRootNameInsideAnotherDirectory.resolve("payload.txt"); + Files.createDirectories(file.getParent()); + Files.writeString(file, "payload"); + + assertFalse(FileUtil.checkFileAuthory(file.toString(), "")); + } + + @Test + void unzipRejectsEntriesOutsideTargetDirectory() throws IOException { + Path zip = tempDir.resolve("archive.zip"); + Path target = tempDir.resolve("extract"); + Path escaped = tempDir.resolve("escaped.txt"); + + try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(zip))) { + zos.putNextEntry(new ZipEntry("../escaped.txt")); + zos.write("escaped".getBytes()); + zos.closeEntry(); + } + + assertFalse(ZipUtil.UnZip(zip.toString(), target.toString(), "")); + assertFalse(Files.exists(escaped)); + } + + @Test + void iosAndAndroidDriversAreRecognizedAsPhoneClients() { + bindRequestWithDriver("ios"); + assertTrue(new BaseImpl().isPhone()); + + bindRequestWithDriver("android"); + assertTrue(new BaseImpl().isPhone()); + } + + @Test + void userConnectionStringIsUsedWhenAccountHasOne() { + LoginUserInfo user = new LoginUserInfo(); + user.UserId = "1001"; + user.ConnectionString = "jdbc:sqlserver://db;DatabaseName=tenant"; + + TestableBaseImpl base = new TestableBaseImpl(); + base.setUser(user); + + assertEquals(user.ConnectionString, base.connectionString()); + } + + @Test + void jwtHelpStaticCreationInitializesSigningKeyWhenSpringConstructorHasNotRun() throws Exception { + resetJwtHelpKeyPair(); + LoginUserInfo user = new LoginUserInfo(); + user.UserId = "1001"; + user.UserCode = "zhangsan"; + user.UserName = "test-user"; + user.LoginOs = "web"; + user.ServerId = 0; + + String token = JwtHelp.createToken(user, 60); + LoginUserInfo parsed = JwtHelp.validToken(token, LoginUserInfo.class); + + assertFalse(token == null || token.isBlank()); + assertNotNull(parsed); + assertEquals(user.UserId, parsed.UserId); + } + + @Test + void corsWithCredentialsDoesNotExposeEveryResponseHeader() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/Api/SystemAjaxApi"); + request.addHeader(HttpHeaders.ORIGIN, "http://localhost:8081"); + MockHttpServletResponse response = new MockHttpServletResponse(); + + new CorsConfig().corsFilter().doFilter(request, response, new MockFilterChain()); + + assertEquals("http://localhost:8081", response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); + assertNotEquals("*", response.getHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS)); + assertTrue(response.getHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS).contains("Authorization")); + } + + @Test + void corsRejectsUnexpectedHttpMethods() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", "/Api/SystemAjaxApi"); + request.addHeader(HttpHeaders.ORIGIN, "http://localhost:8081"); + request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "TRACE"); + MockHttpServletResponse response = new MockHttpServletResponse(); + + new CorsConfig().corsFilter().doFilter(request, response, new MockFilterChain()); + + assertEquals(403, response.getStatus()); + assertFalse(response.containsHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS)); + } + + @Test + void remoteDownloadGuardRejectsPrivateAndMetadataAddresses() { + assertFalse(RemoteDownloadGuard.isAllowedHttpUrl("http://127.0.0.1/file.txt")); + assertFalse(RemoteDownloadGuard.isAllowedHttpUrl("http://localhost/file.txt")); + assertFalse(RemoteDownloadGuard.isAllowedHttpUrl("http://192.168.1.20/file.txt")); + assertFalse(RemoteDownloadGuard.isAllowedHttpUrl("http://10.0.0.5/file.txt")); + assertFalse(RemoteDownloadGuard.isAllowedHttpUrl("http://172.16.0.10/file.txt")); + assertFalse(RemoteDownloadGuard.isAllowedHttpUrl("http://169.254.169.254/latest/meta-data/")); + assertFalse(RemoteDownloadGuard.isAllowedHttpUrl("file:///etc/passwd")); + } + + @Test + void remoteDownloadGuardAllowsPublicHttpAndHttpsUrls() { + assertTrue(RemoteDownloadGuard.isAllowedHttpUrl("http://8.8.8.8/file.txt")); + assertTrue(RemoteDownloadGuard.isAllowedHttpUrl("https://8.8.4.4/file.txt")); + } + + @Test + void sqlSafetyGuardAcceptsSafeIdentifiersAndReadOnlySelects() { + assertTrue(SqlSafetyGuard.isSafeIdentifier("Field_Name1")); + assertTrue(SqlSafetyGuard.isSafeIdentifier("dbo.Field_Name1")); + assertTrue(SqlSafetyGuard.isSafeReadOnlySelect("select id, name from users where id = #{id}")); + assertTrue(SqlSafetyGuard.isSafeReadOnlySelect("with active_users as (select id from users) select id from active_users")); + assertTrue(SqlSafetyGuard.isSafeReadOnlySelect("select id from users union all select id from archived_users")); + assertTrue(SqlSafetyGuard.isSafeReadOnlySelect(""" + select + id, + name + from users + """)); + assertTrue(SqlSafetyGuard.isSafeReadOnlySelect(""" + select id, name -- optional display columns + from users + where note = '-- this is data, not a comment' + """)); + + String sanitized = SqlSafetyGuard.requireSafeReadOnlySelect(""" + select id /* optional id column */, name + from users -- optional source note + where note = '/* this is data, not a comment */' + """); + assertFalse(sanitized.contains("optional id column")); + assertFalse(sanitized.contains("optional source note")); + assertTrue(sanitized.contains("'/* this is data, not a comment */'")); + } + + @Test + void sqlSafetyGuardKeepsMainQueryAfterLegacyInlineDisabledColumns() { + String crmSql = "select case when ISNULL(a.crm_mpo_Affirmer,0) = 0 then '未提交' else '需:' + flow.stepName end as ztName, " + + "a.crm_mpo_billdocument_id as Repeat_crm_mpo_billdocument_id, " + + "a.crm_mpo_wasteoper, --mx.mxs, a.crm_mpo_OldSystemBill, crm_mpo_htname, crm_mpo_HtMoney, " + + "--crm_lpo_summoney, --crm_lpo_nosummoney, crm_mpo_lyzlbzjpaytype, crm_mpo_ltono, " + + "mx.*,(SELECT LISTAGG(p.spec,',') WITHIN GROUP (order by p.spec) " + + "FROM Crm_BillPolisttab bp LEFT JOIN p_ProductTab p ON p.productid = bp.crm_lpo_productid " + + "WHERE bp.crm_lpo_billdocument_id = a.crm_mpo_billdocument_id) AS cpxh " + + "FROM Crm_BillPoMaintab a left join p_systembillflow flow with(nolock) " + + "on a.crm_mpo_stepcode = flow.stepCode left join (select count(1) as mxs, crm_mpo_billdocument_id " + + "from view_CRM_billpolisttab group by crm_mpo_billdocument_id) mx " + + "on a.crm_mpo_billdocument_id = mx.crm_mpo_billdocument_id " + + "where 1 = 1 and crm_mpo_ltono='SJ2026060006'"; + + String sanitized = SqlSafetyGuard.requireSafeReadOnlySelect(crmSql); + + assertTrue(SqlSafetyGuard.isSafeReadOnlySelect(crmSql)); + assertTrue(sanitized.contains("FROM Crm_BillPoMaintab")); + assertTrue(sanitized.contains("left join p_systembillflow")); + assertTrue(sanitized.contains("where 1 = 1 and crm_mpo_ltono='SJ2026060006'")); + assertTrue(sanitized.contains("a.crm_mpo_OldSystemBill")); + assertTrue(sanitized.contains("crm_mpo_lyzlbzjpaytype")); + assertFalse(sanitized.contains("--mx.mxs")); + assertFalse(sanitized.contains("--crm_lpo_summoney")); + assertFalse(sanitized.contains("--crm_lpo_nosummoney")); + } + + @Test + void sqlSafetyGuardKeepsCrmQueryBodyAfterMultilineDisabledColumns() { + String crmSql = """ + select + case when ISNULL(a.crm_mpo_Affirmer,0) = 0 then 'draft' else flow.stepName end as ztName, + a.crm_mpo_billdocument_id as Repeat_crm_mpo_billdocument_id, + a.crm_mpo_wasteoper, + --mx.mxs, + a.crm_mpo_OldSystemBill, + crm_mpo_htname, + crm_mpo_HtMoney, + --crm_lpo_summoney, + --crm_lpo_nosummoney, + crm_mpo_lyzlbzjpaytype, + crm_mpo_ltono, + mx.*, + (SELECT LISTAGG(p.spec,',') WITHIN GROUP (order by p.spec) + FROM Crm_BillPolisttab bp + LEFT JOIN p_ProductTab p ON p.productid = bp.crm_lpo_productid + WHERE bp.crm_lpo_billdocument_id = a.crm_mpo_billdocument_id) AS cpxh + FROM Crm_BillPoMaintab a + left join p_systembillflow flow with(nolock) + on a.crm_mpo_stepcode = flow.stepCode and flow.typeCode = '121002' + left join ( + select count(1) as mxs, crm_mpo_billdocument_id + from view_CRM_billpolisttab + group by crm_mpo_billdocument_id + ) mx on a.crm_mpo_billdocument_id = mx.crm_mpo_billdocument_id + where 1 = 1 and crm_mpo_ltono='SJ2026060006' + """; + + String sanitized = SqlSafetyGuard.requireSafeReadOnlySelect(crmSql); + + assertTrue(sanitized.contains("FROM Crm_BillPoMaintab")); + assertTrue(sanitized.contains("left join p_systembillflow")); + assertTrue(sanitized.contains("where 1 = 1 and crm_mpo_ltono='SJ2026060006'")); + assertFalse(sanitized.contains("--mx.mxs")); + assertFalse(sanitized.contains("--crm_lpo_summoney")); + assertFalse(sanitized.contains("--crm_lpo_nosummoney")); + } + + @Test + void sqlSafetyGuardRejectsDangerousIdentifiersAndFragments() { + assertFalse(SqlSafetyGuard.isSafeIdentifier("name;drop")); + assertFalse(SqlSafetyGuard.isSafeIdentifier("1name")); + assertFalse(SqlSafetyGuard.isSafeReadOnlySelect("select * from users; drop table users")); + assertFalse(SqlSafetyGuard.isSafeReadOnlySelect("delete from users")); + assertFalse(SqlSafetyGuard.isSafeReadOnlySelect("select * into backup_users from users")); + assertFalse(SqlSafetyGuard.isSafeReadOnlySelect("select * from users where name = 'x' exec xp_cmdshell 'whoami'")); + assertFalse(SqlSafetyGuard.isSafeConditionFragment("and status = 1 -- bypass")); + assertFalse(SqlSafetyGuard.isSafeConditionFragment("union select password from users")); + } + + @Test + void publicApiRegistryKeepsOnlyBootstrapEndpointsPublic() { + assertTrue(PublicApiRegistry.isPublicEndpoint(AuthController.class, "Login")); + assertTrue(PublicApiRegistry.isPublicEndpoint(AuthController.class, "GenerateCaptcha")); + assertTrue(PublicApiRegistry.isPublicEndpoint(SystemAjaxApi.class, "GetSystemLoginInfo")); + assertFalse(PublicApiRegistry.isPublicEndpoint(ModuleAjaxController.class, "GetModuleData")); + assertFalse(PublicApiRegistry.isPublicEndpoint(ModuleAjaxController.class, "GetModuleRightMenu")); + } + + private static void bindRequestWithDriver(String driver) { + ServletContext servletContext = new MockServletContext(); + MockHttpServletRequest request = new MockHttpServletRequest(servletContext); + request.addParameter("driver", driver); + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); + } + + private static void resetJwtHelpKeyPair() throws Exception { + Field field = JwtHelp.class.getDeclaredField("rsaKeyPair"); + field.setAccessible(true); + field.set(null, null); + } + + private static class TestableBaseImpl extends BaseImpl { + String connectionString() { + return getConnectionString(); + } + } +} diff --git a/docs/security-vulnerability-self-check-report-2026-06-24.md b/docs/security-vulnerability-self-check-report-2026-06-24.md new file mode 100644 index 0000000..db03f39 --- /dev/null +++ b/docs/security-vulnerability-self-check-report-2026-06-24.md @@ -0,0 +1,177 @@ +# 安全漏洞自查工作自证材料 + +报告日期:2026-06-24 +项目名称:LSERP-MES / WebErp Java 后端 +报告用途:企业定期安全漏洞自查、风险识别记录、风险规避与处置方案留档 +报告性质:代码与配置层面的自查材料,不替代第三方渗透测试、等保测评或生产环境安全审计。 + +## 1. 项目概况与扫描范围 + +本次自查覆盖当前工作区 `E:\JavaBackend\Java_DmK` 下的 Java 后端项目,重点模块为: + +| 范围 | 说明 | +| --- | --- | +| `WebErp/pom.xml` | Maven 父模块、依赖管理、仓库配置 | +| `WebErp/weberp/pom.xml` | Spring Boot 子模块、运行依赖、构建配置 | +| `WebErp/weberp/src/main/resources/application.properties` | 应用端口、数据源、Redis、JWT、连接池及资源治理配置 | +| `WebErp/weberp/src/main/java/org/example` | 认证授权、接口分发、文件上传下载、SQL、脚本、XML、资源管理等业务代码 | +| `WebErp/weberp/src/main/resources/mapper` | MyBatis XML 映射与动态 SQL | +| `WebErp/weberp/src/test/java/org/example` | 安全与资源治理回归测试 | + +本次自查重点覆盖以下风险域: + +- 第三方组件与依赖漏洞。 +- 明文敏感配置与凭证泄露风险。 +- 认证授权、CSRF、CORS、接口开放面。 +- SQL 注入与动态 SQL 拼接。 +- 文件上传、下载、路径穿越、压缩包解压。 +- 脚本执行、XML 反序列化、系统命令调用。 +- 资源泄漏、缓存与线程池治理。 + +## 2. 自查方法与证据来源 + +| 方法 | 证据说明 | +| --- | --- | +| 静态文件扫描 | 使用 `rg` 对认证、文件、SQL、脚本、XML、敏感配置等关键字进行扫描。 | +| 依赖清单抽取 | 从 `WebErp/pom.xml`、`WebErp/weberp/pom.xml` 提取直接声明的 Maven 依赖版本。 | +| OSV 漏洞库核验 | 使用 OSV API 对已声明依赖版本进行公开漏洞匹配。 | +| 代码证据复核 | 抽查 Spring Security、CORS、文件工具、Zip 工具、JWT、脚本执行、测试用例等关键文件。 | +| 回归测试证据核对 | 识别已有安全回归测试覆盖点,包括路径穿越、Zip Slip、CORS、JWT、资源治理等。 | + +环境限制: + +- 当前扫描环境未发现可用 `mvn` 命令,因此未执行 `mvn test`、Maven 依赖树生成或 OWASP Dependency-Check。 +- Git 状态读取触发 safe.directory 所有权保护,本次报告不依赖 Git 状态作为证据。 +- OSV 查询基于 POM 中直接声明或可解析的依赖,不代表完整运行时传递依赖的最终结论;后续应补充完整依赖树扫描。 + +## 3. 风险识别记录台账 + +| 编号 | 风险名称 | 位置 | 等级 | 影响 | 现状证据 | 处置方案 | 优先级 | +| --- | --- | --- | --- | --- | --- | --- | --- | +| R-001 | Jackson 组件存在公开漏洞 | `WebErp/pom.xml`、`WebErp/weberp/pom.xml` 中 Jackson 2.18.3 | 高 | 可能导致反序列化绕过、SSRF、拒绝服务等风险,取决于业务是否启用相关 Jackson 特性和输入面。 | OSV 命中 `jackson-core`、`jackson-databind` 多条风险:GHSA-72hv-8253-57qq、GHSA-5jmj-h7xm-6q6v、GHSA-hgj6-7826-r7m5、GHSA-j3rv-43j4-c7qm、GHSA-rmj7-2vxq-3g9f。 | 将 Jackson 统一升级到已修复版本,建议优先评估 2.18.9 或与 Spring Boot 版本兼容的更高补丁版本;升级后回归 JSON 序列化、接口响应、文件预览和鉴权流程。 | P0,建议 2026-07-01 前完成 | +| R-002 | SQL Server JDBC 驱动存在公开漏洞 | `WebErp/pom.xml`、`WebErp/weberp/pom.xml` 中 `mssql-jdbc` 12.8.1.jre11 | 高 | 可能受不当输入校验问题影响;若系统连接 SQL Server 或保留相关驱动运行时可用,则存在组件风险。 | OSV 命中 GHSA-m494-w24q-6f7w / CVE-2025-59250,修复版本包含 12.8.2.jre11、12.10.2.jre11 等。 | 若仍需 SQL Server 支持,升级到至少 12.8.2.jre11,建议评估 12.10.2.jre11;若不再使用,移除依赖和相关配置。 | P0,建议 2026-07-01 前完成 | +| R-003 | 数据库连接串包含明文账号密码 | `WebErp/weberp/src/main/resources/application.properties:16` 及注释历史连接串 | 高 | 代码仓库、构建包或日志泄露时可能造成数据库凭证泄露和横向访问风险。 | 配置文件中存在形如 `jdbc:dm://***:****/***?user=***&password=***&host={hostname}` 的连接串;报告已脱敏,不展示真实 IP、账号和密码。 | 迁移到环境变量、启动参数、KMS/密钥管理服务或配置中心;清理注释中的历史账号密码;轮换已暴露账号密码;限制数据库账号权限和来源 IP。 | P0,建议 2026-07-01 前完成 | +| R-004 | 认证入口开放面较大,依赖业务层二次校验 | `SecurityConfig.java:14-44`、各 Controller 的 `@RequestCheck` | 高 | Spring Security 对多组 `/Api/**` 路径 `permitAll`,若业务层注解或反射分发漏配,可能形成未授权访问。 | `SecurityConfig` 仅对未匹配请求 `denyAll`;业务方法依赖 `@RequestCheck(CheckLogin = true/false)` 控制登录态。 | 梳理免登录白名单,仅保留登录、验证码、公开配置等必要接口;对反射分发方法建立默认强制登录策略;为高风险操作增加服务端权限校验测试。 | P0,建议 2026-07-05 前完成 | +| R-005 | CSRF 对核心 API 路径豁免 | `SecurityConfig.java:14-22` | 中 | 如浏览器 Cookie 或会话凭证可跨站携带,写操作接口可能被跨站请求利用。 | 多个 `/Api/**` 路径被配置为 CSRF 忽略;系统同时配置 CORS 凭证支持。 | 对 Cookie 会话场景启用 CSRF Token;若完全使用 Bearer Token,确保 Token 不存放于可被跨站自动携带的 Cookie;对写操作要求 `Authorization` 并校验 Origin/Referer。 | P1,建议 2026-07-10 前完成 | +| R-006 | CORS 允许携带凭证且允许请求头通配 | `CorsConfig.java:15-41` | 中 | 跨域来源若配置过宽,可能扩大凭证使用面和数据读取面。 | CORS 配置指定多个来源、`setAllowCredentials(true)`、`addAllowedHeader("*")`,已有测试覆盖拒绝 TRACE 方法和不暴露所有响应头。 | 将来源列表改为环境化配置;生产环境只保留正式前端域名;限制允许请求头;保留并扩展 CORS 回归测试。 | P1,建议 2026-07-10 前完成 | +| R-007 | MyBatis `${}` 动态拼接存在 SQL 注入风险 | `mapper/PageBreaks.xml`、`mapper/DMCrmMapper.xml`、`mapper/CustomerMapper.xml` | 高 | `${}` 会直接拼接 SQL 文本,若字段名、条件、排序或 SQL 片段来自用户输入,可能导致 SQL 注入。 | 扫描发现 `${sql}`、`${keyName}`、`${condition}` 等动态拼接;部分业务代码也存在大量 `String.format` / 拼接式 SQL。 | 将值参数改为 `#{}`;对表名、字段名、排序、条件片段使用服务端白名单枚举;禁止前端直接传 SQL;对无法立即改造的动态 SQL 增加 `SqlAnalyzer` 校验和审计日志。 | P0,建议 2026-07-05 前完成 | +| R-008 | 远程 URL 下载文件可能形成 SSRF | `FileUploadController.java`、`FileUtil.DownLoadFile`、`downloadFile`、`asyncDownload` | 高 | 若用户可控 URL 被服务端下载,可能访问内网、云元数据地址或敏感管理端口。 | 文件上传逻辑支持 `url` 参数触发远程下载,工具类包含多处 HTTP 下载方法。 | 增加 URL 协议、域名、IP 网段、端口白名单;阻断内网地址、回环地址、链路本地地址、云元数据地址;限制重定向和文件大小;记录下载审计日志。 | P0,建议 2026-07-05 前完成 | +| R-009 | 文件上传类型与路径风险需持续治理 | `FileUtil.java`、`FileUploadController.java` | 中 | 文件类型、文件名、路径校验缺陷可能导致任意文件写入、恶意文件上传或越权读取。 | 已存在扩展名白名单、危险类型黑名单、路径权限校验;测试覆盖伪造根目录名绕过场景。 | 保持服务端真实路径归一化校验;增加 MIME/文件魔数校验;上传目录禁止脚本执行;限制单文件大小和总量;对下载、删除、移动操作统一权限校验。 | P1,建议 2026-07-15 前完成 | +| R-010 | Zip Slip 解压路径穿越风险已识别并有控制 | `ZipUtil.java`、`SecurityRegressionTest.java` | 中 | 恶意压缩包可能通过 `../` 写出目标目录,覆盖系统文件。 | `ZipUtil` 使用目标根路径 normalize 后校验 `startsWith(targetRoot)`;测试覆盖 `../escaped.txt` 解压逃逸。 | 保留现有校验;补充压缩包大小、条目数、单文件大小、解压后总大小限制,防止 Zip Bomb。 | P1,建议 2026-07-15 前完成 | +| R-011 | GraalJS 允许全访问,动态脚本执行面较高 | `JsEngine.java:39-40`、`JsEngine.java:99` | 高 | 若脚本内容或上下文参数可被用户控制,可能造成任意 Java 访问、敏感信息读取或命令执行链。 | `polyglot.js.allowAllAccess` 被设置为 `true`,并执行 `engine.eval(statement, context)`。 | 默认关闭 `allowAllAccess`;只允许执行受控表达式;对脚本来源进行白名单管理;限制执行时间和可访问对象;为脚本执行增加审计日志和安全测试。 | P0,建议 2026-07-05 前完成 | +| R-012 | XML 反序列化需禁用外部实体与 DTD | `XmlUtil.java` | 中 | XML 输入若来自不可信来源,可能存在 XXE、SSRF、文件读取等风险。 | 使用 JAXB `Unmarshaller` 反序列化 XML,当前未显式展示禁用外部实体和 DTD 的配置。 | 使用安全 XML 解析器配置,禁用 DTD、外部实体、外部 schema;限制 XML 大小;对不可信 XML 输入增加测试样例。 | P1,建议 2026-07-15 前完成 | +| R-013 | 系统命令调用需白名单与参数隔离 | `OfficeUtil.java`、`FormatFactoryUtil.java` | 中 | 外部程序路径或参数若可控,可能形成命令执行、权限扩大或路径劫持风险。 | 扫描发现 `Runtime.getRuntime().exec`、`ProcessBuilder` 用于文件权限或格式转换。 | 固定可执行文件路径;参数使用数组传递且禁止拼接 shell;校验文件路径位于工作目录;记录执行日志和失败告警;避免 `chmod 777` 等过宽权限。 | P1,建议 2026-07-15 前完成 | +| R-014 | 资源治理已有控制但需纳入定期复核 | `ResourceGovernanceConfig.java`、`RequestCleanupFilter.java`、`ResourceGovernanceRegressionTest.java` | 低 | 缓存、线程池、动态数据源若失控,可能导致内存泄漏、连接泄漏或拒绝服务。 | 已有缓存容量、JS 缓存容量、线程池队列、动态连接池关闭等配置和回归测试。 | 保留容量上限;接入运行监控;对连接池活跃数、队列积压、缓存命中率和异常关闭建立告警。 | P2,建议 2026-07-31 前完成 | +| R-015 | 构建与依赖治理流程不完整 | 当前环境与 POM | 中 | 缺少稳定的自动化安全扫描会导致漏洞发现滞后。 | 当前机器无 `mvn`;未能生成完整依赖树;OSV 仅覆盖可解析直接依赖。 | 在 CI 中增加 `mvn test`、`mvn dependency:tree`、OSV Scanner 或 OWASP Dependency-Check;每次发版前输出依赖漏洞报告。 | P1,建议 2026-07-15 前完成 | + +## 4. 公开漏洞核验结果 + +本次 OSV 查询命中以下依赖风险: + +| 组件 | 当前版本 | 风险编号 | 等级 | 摘要 | 修复版本建议 | +| --- | --- | --- | --- | --- | --- | +| `com.fasterxml.jackson.core:jackson-core` | 2.18.3 | [GHSA-72hv-8253-57qq](https://osv.dev/vulnerability/GHSA-72hv-8253-57qq) | 中 | 异步解析器数字长度约束绕过,可能导致拒绝服务。 | 2.18.6 或更高兼容补丁版 | +| `com.fasterxml.jackson.core:jackson-databind` | 2.18.3 | [GHSA-5jmj-h7xm-6q6v](https://osv.dev/vulnerability/GHSA-5jmj-h7xm-6q6v) | 中 | `@JsonIgnoreProperties` 大小写反序列化绕过。 | 2.18.9 或更高兼容补丁版 | +| `com.fasterxml.jackson.core:jackson-databind` | 2.18.3 | [GHSA-hgj6-7826-r7m5](https://osv.dev/vulnerability/GHSA-hgj6-7826-r7m5) | 中 | `InetSocketAddress` 反序列化可能触发 DNS 解析,引入 SSRF 风险。 | 2.18.8 或更高兼容补丁版 | +| `com.fasterxml.jackson.core:jackson-databind` | 2.18.3 | [GHSA-j3rv-43j4-c7qm](https://osv.dev/vulnerability/GHSA-j3rv-43j4-c7qm) | 高 | 多态类型校验绕过,可能导致任意类实例化。 | 2.18.8 或更高兼容补丁版 | +| `com.fasterxml.jackson.core:jackson-databind` | 2.18.3 | [GHSA-rmj7-2vxq-3g9f](https://osv.dev/vulnerability/GHSA-rmj7-2vxq-3g9f) | 高 | `BasicPolymorphicTypeValidator` 数组子类型白名单绕过。 | 2.18.8 或更高兼容补丁版 | +| `com.microsoft.sqlserver:mssql-jdbc` | 12.8.1.jre11 | [GHSA-m494-w24q-6f7w](https://osv.dev/vulnerability/GHSA-m494-w24q-6f7w) / CVE-2025-59250 | 高 | SQL Server JDBC 驱动输入校验问题。 | 至少 12.8.2.jre11,或升级到 12.10.2.jre11 等兼容修复版本 | + +依赖处置原则: + +- 同一组件族统一版本,避免 `dependencyManagement` 与子模块显式版本不一致。 +- 优先通过 Spring Boot BOM 或统一属性管理版本。 +- 升级前备份当前依赖树;升级后执行单元测试、接口冒烟测试、文件预览/转换测试、登录鉴权测试。 +- 对暂不能升级的漏洞记录风险接受原因、补偿控制和复核日期。 + +## 5. 已有安全控制与规避措施 + +| 控制项 | 当前证据 | 有效性说明 | +| --- | --- | --- | +| 默认拒绝未匹配请求 | `SecurityConfig` 中 `anyRequest().denyAll()` | 可降低非白名单路径暴露面。 | +| 接口级登录校验注解 | `@RequestCheck` 默认 `CheckLogin = true` | 业务层可对方法级别实施登录态、参数、日志控制。 | +| CORS 源白名单 | `CorsConfig` 中使用具体来源模式 | 避免使用 `*` 携带凭证;仍需生产环境收敛来源。 | +| CORS 方法限制 | 已允许常用方法并测试拒绝 TRACE | `SecurityRegressionTest` 覆盖异常方法拒绝。 | +| 响应头暴露收敛 | 暴露固定响应头,不使用 `*` | 已有测试验证不暴露所有响应头。 | +| 文件扩展名黑白名单 | `FileUtil` 中定义允许和禁止类型 | 可阻止常见脚本文件上传;仍需补充 MIME/魔数校验。 | +| 文件路径权限校验 | `FileUtil.checkFileAuthory` | 已有测试覆盖伪造根目录名绕过。 | +| Zip Slip 防护 | `ZipUtil.resolveZipEntryPath` | 解压路径规范化后校验目标目录边界。 | +| JWT 签名初始化 | `JwtHelp` 使用 RSA 签名并有静态初始化测试 | 已有测试覆盖 Spring 构造器未运行时的令牌生成。 | +| 资源治理配置 | 缓存、JS 缓存、线程池、动态连接池上限 | 已有回归测试覆盖容量限制和动态连接池关闭。 | + +## 6. 风险处置方案与整改优先级 + +### P0:立即整改 + +整改目标:消除高危依赖、凭证泄露、未授权、SQL 注入、SSRF 和高危脚本执行面。 + +| 事项 | 责任建议 | 截止建议 | 验收标准 | +| --- | --- | --- | --- | +| 升级 Jackson 与 mssql-jdbc | 后端负责人 | 2026-07-01 | OSV/Dependency-Check 不再命中对应漏洞;回归测试通过。 | +| 移除明文数据库凭证 | 运维与后端负责人 | 2026-07-01 | POM、properties、日志和文档中无真实密码;已轮换暴露账号。 | +| 收敛 `permitAll` 与免登录接口 | 后端负责人 | 2026-07-05 | 白名单清单明确;高风险接口必须登录和鉴权;新增未授权访问测试。 | +| 动态 SQL 白名单治理 | 后端负责人 | 2026-07-05 | 用户输入值全部参数化;字段/表名/排序仅来自服务端白名单。 | +| 远程 URL 下载 SSRF 防护 | 后端负责人 | 2026-07-05 | 阻断内网、回环、链路本地、云元数据地址;新增 SSRF 单元测试。 | +| 关闭或限制 GraalJS 全访问 | 架构与后端负责人 | 2026-07-05 | 默认不允许 Java 全访问;脚本来源、对象访问和执行时间受控。 | + +### P1:短期治理 + +整改目标:降低中危配置、解析、上传和命令执行风险。 + +| 事项 | 截止建议 | 验收标准 | +| --- | --- | --- | +| 生产 CORS 来源环境化和最小化 | 2026-07-10 | 生产仅保留正式域名;测试覆盖非法 Origin。 | +| CSRF 策略复核 | 2026-07-10 | Cookie 会话启用 CSRF;Bearer Token 场景明确禁止跨站自动携带凭证。 | +| XML 安全解析配置 | 2026-07-15 | 禁用 DTD、外部实体和外部 schema;新增 XXE 测试。 | +| 文件上传魔数、大小、目录策略 | 2026-07-15 | 上传目录不可执行;大小限制、MIME/魔数校验和审计日志上线。 | +| 系统命令调用白名单 | 2026-07-15 | 外部程序路径固定;参数数组化;无 shell 拼接;避免过宽文件权限。 | +| CI 安全扫描补齐 | 2026-07-15 | CI 生成测试报告、依赖树、漏洞扫描报告。 | + +### P2:持续优化 + +整改目标:将安全自查固化为周期机制。 + +| 事项 | 截止建议 | 验收标准 | +| --- | --- | --- | +| 资源治理监控 | 2026-07-31 | 连接池、线程池、缓存、队列积压均有指标和告警。 | +| 安全基线文档 | 2026-07-31 | 形成接口鉴权、SQL、安全配置、上传下载、依赖升级基线。 | +| 安全回归用例扩展 | 2026-07-31 | 覆盖未授权、SQL 注入、SSRF、XXE、文件魔数、上传大小限制。 | + +## 7. 定期漏洞自查机制建议 + +建议建立以下定期自查制度: + +| 周期 | 自查内容 | 输出材料 | +| --- | --- | --- | +| 每周 | 依赖漏洞库增量扫描,关注高危/严重漏洞。 | 依赖漏洞扫描记录、风险接受记录。 | +| 每月 | 代码安全关键字扫描,复核认证、SQL、文件、脚本、XML、命令执行。 | 风险识别台账、整改闭环清单。 | +| 每次发版前 | `mvn test`、依赖树、OSV/Dependency-Check、关键接口安全回归。 | 发版安全检查表、测试报告。 | +| 每季度 | 安全基线复核、配置脱敏检查、凭证轮换、最小权限复核。 | 季度安全自查报告、凭证轮换记录。 | +| 重大漏洞公告后 | 针对公告组件和受影响版本快速排查。 | 专项漏洞排查记录、应急处置记录。 | + +建议保留的自证材料: + +- 自查日期、范围、负责人、扫描工具和命令记录。 +- 依赖漏洞扫描原始结果和处置结论。 +- 风险台账:风险编号、等级、影响、证据、责任人、计划完成日期、闭环状态。 +- 整改前后代码或配置差异。 +- 回归测试报告、构建报告、上线验证记录。 +- 风险接受审批记录和下次复核日期。 + +## 8. 后续验证清单 + +| 检查项 | 命令或动作 | 预期结果 | +| --- | --- | --- | +| Maven 测试 | `mvn test` | 全部测试通过,无失败和错误。 | +| 依赖树 | `mvn dependency:tree` | 输出完整直接与传递依赖,归档为发版证据。 | +| OSV 扫描 | `osv-scanner -r .` 或 CI 中等效工具 | 无高危/严重未处置漏洞;中低危有处置计划。 | +| OWASP Dependency-Check | `dependency-check` Maven 插件或 CI 任务 | 生成 HTML/JSON 报告并归档。 | +| 敏感信息扫描 | secret 扫描工具或 `rg` 关键字扫描 | 无真实密码、Token、Secret、公网连接串。 | +| 安全回归 | 现有 JUnit + 新增未授权/SSRF/XXE/SQL 注入测试 | 关键安全用例稳定通过。 | + +## 9. 本次自查结论 + +本次自查确认项目已具备一定安全治理基础,包括 Spring Security 默认拒绝策略、业务层登录校验注解、CORS 回归测试、文件路径校验、Zip Slip 防护、JWT 初始化测试和资源治理回归测试。 + +同时,本次自查识别出若干需要优先处置的风险:高危依赖版本、明文数据库凭证、接口开放面依赖业务层校验、动态 SQL 拼接、远程 URL 下载 SSRF 风险以及 GraalJS 全访问脚本执行风险。建议将 P0 风险纳入最近一次安全整改,完成后补跑 Maven 测试、依赖树、OSV/OWASP 扫描,并更新本报告形成闭环记录。 +