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);
        }
    }

    /// <summary>
    /// 是否存在审核代理表
    /// </summary>
    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<String, String> dConKeyVal = ConfigUtil.getConnectionKeyVal();
        String dbname = dConKeyVal.containsKey("database") ? dConKeyVal.get("database") : "";

        // 获取系统数据库组信息
        List<Map<String, Object>> sysDbGroup = GetSysdbGroup(0);
        List<Integer> serverIdList = new ArrayList<>();

        for (Map<String, Object> 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<Map>表示）
     */
    @Cacheable(value = "sysdbGroupCache", key = "#id", unless = "#result == null")
    public List<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> GetBaseModule(String moduleCode, String menuId) {
        String menuid = ToInt32(menuId) + "";
//        return baseModuleMapper.GetBaseModule(moduleCode, menuid);
        return getDetailJDBC().GetBaseModule(moduleCode, menuid);
    }

    /**
     * 获取基础模块信息
     * 对应 C# 的 GetBaseModule 方法，返回 List<Map<String, Object>> 模拟 DataTable
     *
     * @param moduleCode 模块编码
     * @param menuId     菜单ID
     * @return 包含查询结果的 List<Map>（每行数据一个 Map，key=列名，value=列值）
     */
    public List<Map<String, Object>> 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<String, Object> 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<Map<String, Object>> 模拟 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<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> GetCondition(String fromkey, Integer id) {
        Boolean windowsDirver = toBoolean(Request("windowsDirver", ""));
        return GetCondition(fromkey, id, windowsDirver);
    }

    /**
     * 获取账单详情列数据
     */
    public List<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> result = jdbcTemplate.queryForList(sql, moduleCode);

        return result;
    }

    /**
     * 获取单据来源列信息
     *
     * @param sourceId 来源ID
     * @param userId   用户ID
     * @return 包含单据来源列信息的列表，每个元素为一行数据的Map
     */
    public List<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> dtval = GetModuleIdFieldRow(keyName, key);
        if (dtval != null && !dtval.isEmpty()) {
            for (Map<String, Object> row : dtval) {
//                System.out.println(row+"获取的idfield");
                parmaryKey = row.get("fieldname") + "";
                leftUnionField = row.get("specname") == null ? "" : row.get("specname").toString();
                // 假设dtval是查询结果集（例如List<Map<String, Object>>类型）
                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<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> dtVal = ConversionUtils.toLowerColumnName(jdbcTemplate.queryForList(colSql.toString()));
            return dtVal;
        }
        return null;
    }

    /**
     * 从数据库获取表单数据
     */
//    public List<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> result = jdbcTemplate.queryForList(colsql);
            return ConversionUtils.toLowerColumnName(result);
        }
        return null;
    }

    /**
     * 重载方法，处理id默认值为0的情况
     */
    public List<Map<String, Object>> GetColumnRows(String moduleId, String userId, String userName) {
        return GetColumnRows(moduleId, userId, userName, 0);
    }


    public Map<String, Object> 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<Map < String, Object>> 模拟DataTable，每个Map对应一行数据
     */
    public List<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>>替代DataTable）
        return dbOperator.queryForList(sql);
    }

    /**
     * 获取表信息
     *
     * @param tablename 表名
     * @return 包含表信息的列表，每个元素为一行数据的Map
     */
    public List<Map<String, Object>> 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<Map<String, Object>>替代DataTable）
        return dbOperator.queryForList(sql);
    }

    /**
     * 获取列名信息
     *
     * @param moduleId 模块ID
     * @return 包含列名信息的列表，每个元素为一行数据的Map
     * @remarks 创建人：
     * 创建时间：
     * 修改人：
     * 修改时间：
     * 修改备注：
     * 版本：
     */
    public List<Map<String, Object>> GetColumnNames(String moduleId) {
        // 构建查询SQL，根据模块ID查询字段名
        String colsql = String.format("select fieldname from p_systemwordbooktab where tab='%s'", moduleId);

//        String colsql = sqlProvider.GetColumnNamesSql(moduleId);

        // 执行查询并返回结果（使用List<Map<String, Object>>替代DataTable）
        return jdbcTemplate.queryForList(colsql);
    }

    /**
     * 获取基础模块的附加模块
     *
     * @param moduleId 模块ID
     * @param id       附加模块ID（可选，默认为0）
     * @return 包含附加模块信息的列表，每个元素为一行数据的Map
     * @remarks 创建人：
     * 创建时间：
     * 修改人：
     * 修改时间：
     * 修改备注：
     * 版本：
     */
    public List<Map<String, Object>> 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<Map<String, Object>> GetAttcModules(String moduleId) {
        return GetAttcModules(moduleId, 0);
    }

    /**
     * 获取 获取颜色和下拉框列信息
     *
     * @param moduleId 模块ID
     * @return 包含颜色和下拉框列信息的列表，每个元素为一行数据的Map
     * @remarks 创建人：
     * 创建时间：
     * 修改人：
     * 修改时间：
     * 修改备注：
     * 版本：
     */
    public List<Map<String, Object>> 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<Map<String, Object>> resultList = namedJdbcTemplate.queryForList(sql, params);

            // 5. 列名转小写（对应C#的ToLowerColumnName()）
            List<Map<String, Object>> lowerCaseResult = toLowerColumnName(resultList);

            return lowerCaseResult;
        }

        // 6. 空moduleId返回null（和原C#逻辑一致）
        return null;
    }

    /**
     * 获取基础审核信息详情
     *
     * @param moduleId 模块标识符
     * @param stepCode 步骤代码
     * @param id       标识符（可选）
     * @return 审核信息详情列表（List<Map<String, Object>>）
     */
    // 缓存过期时间20秒，对应原C#的Cache特性
    @Cacheable(value = "auditInfoCache", key = "#moduleId + '-' + #stepCode + '-' + #id", condition = "#id != null")
    public List<Map<String, Object>> 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<Map<String, Object>> GetAuditInfoDetails(String moduleId, String stepCode) {
        return GetAuditInfoDetails(moduleId, stepCode, 0);
    }


    /**
     * 获取基础明细模块配置
     *
     * @param fromkey 表单键（用于筛选条件）
     * @param ids     ID列表（逗号分隔）
     * @return 明细模块配置列表（List<Map<String, Object>>）
     */
    @Cacheable(value = "baseDetailModuleCache", key = "#fromkey + '-' + #ids")
    public List<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> dtVal = jdbcTemplate.queryForList(colSql);
            return ConversionUtils.toLowerColumnName(dtVal); // 参考DataImpl中对列名小写的处理
        }
        return null;
    }

    /**
     * 获取基础模块明细配置
     *
     * @param fromkey  表单键
     * @param userId   用户ID
     * @param userName 用户名
     * @return 明细配置列表（每行数据为Map<String, Object>）
     */
    public List<Map<String, Object>> 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<Map<String, Object>> result = jdbcTemplate.queryForList(gridSql, fromkey);
        // 转换列名为小写（遵循代码库中统一的列名处理风格）
        return ConversionUtils.toLowerColumnName(result);
    }

    /**
     * 获取基础审核信息详情列
     *
     * @param attachId 附件ID
     * @return 包含审核详情列信息的列表
     */
    @Cacheable(value = "auditInfoCache", key = "#attachId", condition = "#attachId != null")
    public List<Map<String, Object>> 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<Map<String, Object>> 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<Object> 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<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> resultList = namedJdbcTemplate.queryForList(finalSql, parameters);

        return ConversionUtils.toLowerColumnName(resultList);
    }

    // 重载方法，处理默认参数
    public List<Map<String, Object>> GetMobileCardColumn(String moduleId, Boolean isMain) {
        return GetMobileCardColumn(moduleId, isMain, 0, true, false);
    }

    public List<Map<String, Object>> GetMobileCardColumn(String moduleId, Boolean isMain, int mxId) {
        return GetMobileCardColumn(moduleId, isMain, mxId, true, false);
    }

    public List<Map<String, Object>> GetMobileCardColumn(String moduleId, Boolean isMain, int mxId, boolean isBase) {
        return GetMobileCardColumn(moduleId, isMain, mxId, isBase, false);
    }

//    public List<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> 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<Map>替代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>，每个Map代表一行数据，键为列名
//        out.println("condKey: " + condKey);
        List<Map<String, Object>> 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<Map<String, Object>> 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<String, Object> 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<Map<String, Object>> 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<Map<String, Object>> 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<String, Object> resultSet = jdbcTemplate.execute(
//                    procedureSql,
//                    (CallableStatementCallback<Map<String, Object>>) 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<String, Object> resultMap = new HashMap<>();
//                        resultMap.put("DM_return", dmReturn);
//                        resultMap.put("DM_msg", dmMsg);
//                        return resultMap;
//                    }
//            );


            List<List<Map<String, Object>>> resultSet = this.sqlProvider.BaseDataSaveSql(
                    procName,
                    escapedBaseSql,
                    execType,
                    escapedModuleId,
                    escapedMasterTable,
                    escapedIdField,
                    escapedIdValue,
                    escapedOperatorId,
                    escapedOperatorName
            );
            // 步骤5：构建结果映射（保持原变量名）
//            Map<String, Object> resultMap = new HashMap<>();
//            resultMap.put("execcount", resultSet.get("execcount")); // 对应@@ROWCOUNT
//            resultMap.put("returnValue", resultSet.get("returnValue")); // 存储过程返回值
//            resultMap.put("outputValue", resultSet.get("outputValue")); // 输出参数值
            Map<String, Object> resultMap = new HashMap<>();
            for (List<Map<String, Object>> maps : resultSet) {
                {
                    for (Map<String, Object> 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<List<Map<String, Object>>> data = new ArrayList<>();
                List<Map<String, Object>> 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返回信息：<p style='color:red;'>%s</p >", procName,
                                rMsg != null ? rMsg.replace("\r", "<br />") : "") :
                        (rMsg != null ? rMsg.replace("\r", "<br />") : ""));
                response.setData(rValue);
            }

        } catch (Exception e) {
            response.setSuccess(false);
            log.error("Exception caught", e);
            response.setMsg("存储过程执行异常：" + e.getMessage().replace("\r", "<br />"));
        }
        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<Map<String, Object>>表示多个表）
     * @return 解析后的结果字典，key为GuidKey，value为UpdStrModule对象
     */
    public Map<String, UpdStrModule> DecodeSaveResult(List<List<Map<String, Object>>> dataSet) {
        if (dataSet == null || dataSet.isEmpty()) {
            return null;
        }

        Map<String, UpdStrModule> resultMap = new HashMap<>();

        // 遍历数据集中的每个"表"（List<Map>模拟DataTable）
        for (List<Map<String, Object>> table : dataSet) {
            if (!table.isEmpty()) {
                // 获取表中第一行数据
                Map<String, Object> 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<String, UpdStrModule> DecodeSaveResult(ResultSet dataSet) {
        List<List<Map<String, Object>>> dataSetList = new ArrayList<>();
        List<Map<String, Object>> rows = new ArrayList<>();
        rows = toListMap(dataSet);
        dataSetList.add(rows);
        return DecodeSaveResult(dataSetList);
    }

    /**
     * 场景1：ResultSet 转为单条记录（Map<String, Object>）
     * 适用：存储过程返回1条数据（如单据详情、配置信息）
     *
     * @param rs 存储过程返回的 ResultSet
     * @return 单条记录（key=列名，value=列值）；无数据返回空Map，异常返回null
     */
    public static Map<String, Object> toSingleMap(ResultSet rs) {
        Map<String, Object> 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<Map<String, Object>>）
     * 适用：存储过程返回多条数据（如单据列表、明细集合）
     *
     * @param rs 存储过程返回的 ResultSet
     * @return 多条记录集合；无数据返回空List，异常返回null
     */
    public static List<Map<String, Object>> toListMap(ResultSet rs) {
        List<Map<String, Object>> 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<String, Object> 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<Map>表示）
     */
    public List<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> 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<String, Object> GetNextSelectStepFlag(String moduleId, BillStateEn stateEn) {
        Map<String, Object> 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<Map<String, Object>> firstResult = jdbcTemplate.queryForList(
                firstSql,
                moduleId,
                stateEn.BillType,
                stateEn.StepCode
        );

        if (!firstResult.isEmpty()) {
            // 保存第一个查询结果
            Map<String, Object> 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<Map<String, Object>> secondResult = jdbcTemplate.queryForList(
                        secondSql,
                        moduleId,
                        stateEn.BillType
                );

                // 将结果添加到返回Map中
                for (Map<String, Object> 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<String, Object> paramMap = new HashMap<>();
        paramMap.put("billno", module.IdValue);
        paramMap.put("moduleId", module.getModuleId());
        if (!isNullOrEmpty(stepCode)) {
            paramMap.put("stepCode", stepCode);
        }

        // 4. 执行查询（映射为Map，模拟原FirstOrDefault逻辑）
        List<Map<String, Object>> resultList;
        try {
            resultList = namedJdbcTemplate.queryForList(dataSql, paramMap);
        } catch (Exception e) {
            // 捕获数据库异常（如SQL错误、连接异常）
            throw new RuntimeException("查询单据状态失败", e);
        }
        // 无数据返回null
        if (resultList == null || resultList.isEmpty()) {
            return null;
        }
        Map<String, Object> 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<String, Object> 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<String, Object> 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<Map<String, Object>> 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<Map>对应C#的DataTable）
        return jdbcTemplate.queryForList(sql);
    }

    /**
     * 获取下一选择操作人列表
     */
    @Cacheable(value = "nextSelectOperListCache", key = "#nextSelectStepOper", unless = "#result == null")
    public List<Map<String, Object>> 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<String> 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<Map>模拟DataTable）
     */
    @Cacheable(value = "auditWindowSizeCache", key = "#moduleId", unless = "#result == null")
    public List<Map<String, Object>> 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<String, Object> 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", "<br />"));
            response.setData(0);
        }

        return response;
    }

    /**
     * 获取基础流程步骤
     *
     * @param idValue 标识符值
     * @param isBase  是否为基础流程
     * @return 流程步骤列表（模拟DataTable，使用List<Map>表示）：
     */
    public List<Map<String, Object>> 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<Map<String, Object>> 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<SqlParameter> getProcedureParameters() {
            return super.getCallParameters();
        }
    }

    /**
     * 重载方法，提供默认applyType=1
     */
    public BaseResponse BaseApply(BaseModule module, BillStateEn stateEn) {
        return BaseApply(module, stateEn, 1);
    }

    /**
     * 处理存储过程执行结果
     */
    private BaseResponse handleProcedureResult(Map<String, Object> 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", "<br />"));
                response.setData(returnValue);
            }

        } catch (SQLException e) {
            response.setSuccess(false);
            response.setMsg(e.getMessage().replace("\r", "<br />节点编号：" + specNo));
            response.setData(0);
            log.error("Exception caught", e);
            throw e; // 可选：向上抛出异常，让上层感知
        } catch (Exception e) {
            response.setSuccess(false);
            response.setMsg(e.getMessage().replace("\r", "<br />节点编号：" + 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<Map<String, Object>> 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<String, Object> 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", "<br />")));
            response.setData(0);
        }
        return response;
    }

    /**
     * 执行可能需要选择人员的存储过程
     *
     * @param storeName 存储过程名称
     * @param module    模块信息
     * @param stateEn   账单状态枚举
     * @param params    输入参数集合
     * @return 基础响应对象
     */// 假设该注解已在项目中定义
    public BaseResponse ExecSelectOperStore(String storeName, ModuleEntity module,
                                            BillStateEn stateEn, Map<String, Object> 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<String> 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<String> inputParamParts = new ArrayList<>();
//            for (Map.Entry<String, Object> 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<String> 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<String, Object> 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<String, Object> 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<String, Object> 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<String> uniqueOps = new LinkedHashSet<>(Arrays.asList(operParts));
                        nexetSelOpers.append(String.join(",", uniqueOps)).append(";");
                    }
                }

                Map<String, Object> 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<String, Object> 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("<p style='color:red;'>%s</p>", rMsg.replace("\r", "<br />")));
                } else {
                    response.setMsg(rMsg.replace("\r", "<br />"));
                }
            }

        } catch (Exception e) {
            response.setSuccess(false);
            response.setMsg(e.getMessage().replace("\r", "<br />"));
            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<Map<String, Object>> 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<String, Object>
        Map<String, Object> 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<Map < String, Object>>（模拟DataTable）
     */
    public List<Map<String, Object>> 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;
    }

    /**
     * <p>说明：保存方案</p>
     *
     * @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<Map<String, Object>> 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<Map<String, Object>> 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<Map>模拟DataTable
            return jdbcTemplate.queryForList(sql, userId, menuId);
        } catch (Exception e) {
            // 异常捕获处理（保持与原代码一致的空处理）
            return List.of(); // 返回空列表
        }
    }

    /**
     * 2020-11-19 添加单表的 平铺明细
     *
     * @param moduleId 模块ID
     * @param stepCode 步骤编码
     * @return 明细数据列表（模拟DataTable，使用List<Map>表示）
     */
    @Cacheable(value = "auditBaseDetailsCache", key = "#moduleId + '_' + #stepCode", condition = "#result != null")
    public List<Map<String, Object>> 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<Map>模拟DataTable）
     */
    public List<Map<String, Object>> 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);
    }

    /// <summary>
    /// 获取常用审批意见
    /// </summary>
    /// <returns>DataTable.</returns>
    /// <remarks>
    /// </remarks>
    public List<Map<String, Object>> 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<String, Object>
        Map<String, Object> 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<Map<String, Object>> GetAuditBackSteps(String moduleId, String idValue, String stepCode, String
            billType, boolean isBase) throws CusException {
        // 先从基础方法获取退回步骤列表
        List<Map<String, Object>> 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<Map>替代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<Map<String, Object>> GetAuditBackStepList(String moduleId, String idValue, String stepCode,
                                                          boolean isBase) throws CusException {
        try {
            // 创建SimpleJdbcCall调用存储过程
            SimpleJdbcCall jdbcCall = new SimpleJdbcCall(jdbcTemplate)
                    .withProcedureName("p_system_getBackStepCode"); // 指定存储过程名

            // 构建输入参数
            Map<String, Object> inParams = new HashMap<>();
            inParams.put("@modid", moduleId);
            inParams.put("@modType", isBase ? 1 : 2);
            inParams.put("@keyvalue", idValue);
            inParams.put("@stepcode", stepCode);

            // 执行存储过程并获取结果
            Map<String, Object> result = jdbcCall.execute(inParams);

            // 解析结果集（存储过程返回的数据集通常在键为"#result-set-1"的条目里）
            List<Map<String, Object>> storedProcResult = (List<Map<String, Object>>) result.get("#result-set-1");

            if (storedProcResult != null && !storedProcResult.isEmpty()) {
                // 转换结果结构为{value, text}格式
                List<Map<String, Object>> retList = new ArrayList<>();
                for (Map<String, Object> row : storedProcResult) {
                    Map<String, Object> 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<Map>表示）
     */
    // 方法边界注解（根据实际实现调整）

    /**
     * 对应原C#的GetAttTreeData方法
     *
     * @param moduleCode 模块编码（对应@menuCode）
     * @param specNo     物种编号（可为空，非空时拼接like条件）
     * @return List<Map < String, Object>> 模拟DataTable，无结果时返回降级查询结果
     */
    public List<Map<String, Object>> 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<Map<String, Object>> mainResult = namedJdbcTemplate.queryForList(mainSql, mainParams);

        // 5. 无结果时执行降级查询
        if (mainResult.isEmpty()) {
            return GetFallbackTreeData(moduleCode);
        }

        // 有结果返回主查询结果
        return mainResult;
    }

    /**
     * 降级查询方法（对应原C#的else分支SQL）
     */
    private List<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> hisDtVal = jdbcTemplate.queryForList(hisSql);
        return hisDtVal;
    }

    /**
     * 获取流程图数据
     *
     * @param module 模块基础信息
     * @return 流程图数据列表（模拟DataTable，使用List<Map>表示）
     */
    public List<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> curTab = jdbcTemplate.queryForList(curSql, module.IdValue);

            if (!curTab.isEmpty()) {
                Map<String, Object> curRow = curTab.get(0);
                List<Map<String, Object>> filteredList = new ArrayList<>();

                // 过滤符合条件的步骤
                for (Map<String, Object> 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<String, Object> 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<String> distinctList = removeSameObj(arr);
            return sJoin(distinctList, ",");
        }
        return "";
    }

    /**
     * 获取流程步骤状态数据
     *
     * @param moduleId 模块ID
     * @param idValue  主键值
     * @param isBase   是否为基础档案
     * @return 流程步骤状态数据列表（模拟DataTable，使用List<Map>表示）
     */
    protected List<Map<String, Object>> 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<Map<String, Object>> 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<String, Object> 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<String> 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<String, Object> GetUpdFlowChartList() {
        Map<String, Object> result = new HashMap<>();

        // 查询基础模块列表
        String baseSql = "SELECT DISTINCT typeCode, billType FROM P_Systemdlltabflowtypestep " +
                "WHERE ISNULL(autoStep, 0) = 0 ORDER BY billType";
        List<Map<String, Object>> baseModuleList = jdbcTemplate.queryForList(baseSql);

        // 查询单据模块列表
        String billSql = "SELECT DISTINCT typeCode, billType FROM p_systembillflowtypestep " +
                "WHERE ISNULL(autoStep, 0) = 0 ORDER BY billType";
        List<Map<String, Object>> 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<Map>表示）
     */
    public List<Map<String, Object>> 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<Map>模拟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<Map<String, Object>> 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<Map>模拟DataTable）
     */
    public List<Map<String, Object>> 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<String, Object> 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<Map>模拟DataTable）
     */
    public List<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> 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<String, Object> inParams = new HashMap<>();
            inParams.put("@roleid", roleId);

            // 执行存储过程并获取结果
            Map<String, Object> 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<String, Object> inParams = new HashMap<>();
            inParams.put("@operatorid", userId);

            // 执行存储过程并获取结果
            Map<String, Object> 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<String, Object> inParams = new HashMap<>();
            inParams.put("@operatorid", roleId);

            // 执行存储过程并获取结果
            Map<String, Object> result = jdbcCall.execute(inParams);

            // 处理输出参数
            String rMsg = (String) result.get("@msg");
            response.setSuccess(true);
            response.setMsg(rMsg == null || rMsg.isEmpty() ? LanguageUtil.Success : rMsg);

            // 处理结果集（提取所有返回的结果集）
            List<List<Map<String, Object>>> dataSets = ExtractResultSets(result);
            response.setOther(DecodeSaveResult(dataSets));

        } catch (Exception e) {
            response.setSuccess(false);
            response.setMsg(e.getMessage().replace("\r", "<br />"));
            response.setData(0);
        }
        return response;
    }

    /**
     * 从SimpleJdbcCall执行结果中提取所有结果集
     */
    private List<List<Map<String, Object>>> ExtractResultSets(Map<String, Object> result) {
        return result.values().stream()
                .filter(value -> value instanceof List)
                .map(value -> (List<Map<String, Object>>) value)
                .collect(Collectors.toList());
    }

    /**
     * 获取用户列表
     */
    public List<Map<String, Object>> 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<String, Object> 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<String, Object> inParams = new HashMap<>();
                inParams.put("@typeId", type);
                inParams.put("@empid", userId);
                inParams.put("@operatorId", getUser().UserId);
                inParams.put("@operatorName", getUser().UserName);

                // 执行存储过程并获取结果
                Map<String, Object> result = jdbcCall.execute(inParams);

                // 处理输出参数
                String rMsg = (String) result.get("@msg");
                response.setSuccess(true);
                response.setMsg(rMsg == null || rMsg.isEmpty() ? LanguageUtil.Success : rMsg);

                // 处理结果集
                List<Map<String, Object>> dataSet = (List<Map<String, Object>>) 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<String, Object>() {{
                        put(key, prev);
                    }});
                }
            }
        } catch (Exception e) {
            response.setSuccess(false);
            response.setMsg(e.getMessage().replace("\r", "<br />"));
            response.setData(0);
        }
        return response;
    }

    /**
     * 获取评论列表
     *
     * @param moduleIdOrGuid 模块ID或GUID
     * @param idVal          主键值
     * @param stepCode       步骤编码
     * @return 评论列表数据（用List<Map>模拟DataTable）
     */
    public List<Map<String, Object>> 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<Map<String, Object>> customCfgList = jdbcTemplate.queryForList(customCfgSql, operatorId);

        // 查询默认配置
        String defaultCfgSql = "SELECT itemCode, itemOrder, itemWidth, itemHeight, enableFlag " +
                "FROM P_SystemFirstPageSetTab " +
                "WHERE enableType IN (-1, 1)";
        List<Map<String, Object>> defaultCfgList = jdbcTemplate.queryForList(defaultCfgSql);

        // 检查是否需要更新
        boolean needUpdate = false;
        for (Map<String, Object> defaultRow : defaultCfgList) {
            String itemCode = (String) defaultRow.get("itemCode");
            boolean exists = false;

            for (Map<String, Object> 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<String, Object> 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<Map<String, Object>> 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<Map<String, Object>> 对应 C# 的 DataTable）
     */
    public List<Map<String, Object>> 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<String> GetPrimaryKeysArray(String tabname) {
        // 构建查询主键的SQL（注意：此处为简化示例，实际应使用参数化查询防止SQL注入）
        String primaryKeysSql = getDetailJDBC().GetPrimaryKeysArray(tabname);
        // 执行查询获取主键信息
        List<Map<String, Object>> primaryKeys = jdbcTemplate.queryForList(primaryKeysSql);

        // 转换结果为字符串列表
        List<String> keys = new ArrayList<>();
        for (Map<String, Object> 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<String> keysA = GetPrimaryKeysArray(tabname);
        // 拼接主键为逗号分隔的字符串
        return String.join(",", keysA);
    }

    /**
     * 获取可配置的扩展配置表数据，用于布局设置和拖动操作
     * 对应C#的GetBSDesktopExtend方法
     */
    public List<Map<String, Object>> 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<Map>替代DataTable
        return jdbcTemplate.queryForList(itemsSql, userId, userName);
    }

    /**
     * 获取桌面模块操作配置数据
     * 对应C#的GetDesktopModuleOper方法
     */
    public List<Map<String, Object>> 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<Map>形式替代DataTable）
     */
    public List<Map<String, Object>> 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<Map>替代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<Map<String, Object>> 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<Map<String, Object>> defaultCfgList = jdbcTemplate.queryForList(
                defaultCfgSql,
                dllcoid);

        // 检查是否需要更新：如果默认配置中存在用户配置没有的itemCode，则需要更新
        boolean ifUpdate = false;
        for (Map<String, Object> defaultRow : defaultCfgList) {
            String theCode = (String) defaultRow.get("itemCode");
            boolean exists = false;

            // 检查用户配置中是否存在当前itemCode
            for (Map<String, Object> 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<String, Object> 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<Map<String, Object>> 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<Map>替代DataTable
        return jdbcTemplate.queryForList(itemsSql);
    }

    /**
     * 桌面查询结果获取
     *
     * @param queryText 查询文本
     * @return 查询结果列表
     */
    public List<Map<String, Object>> 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<Map>表示）
     */
    public List<Map<String, Object>> 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<Map>表示）
     */
    public List<Map<String, Object>> GetDesktopModuleMain(String userId, String userName, String dllcoid) {
        // 构建SQL查询，使用参数化查询防止SQL注入（替换原C#的String.Format拼接）
        String sql = getDetailJDBC().GetDesktopModuleMain(userId, userName, dllcoid);

        // 执行参数化查询，按顺序传递参数
        List<Map<String, Object>> 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<Map<String, Object>> customCfgList = namedParameterJdbcTemplate.queryForList(customCfgSql, customParams);

        // 查询默认配置
        String defaultCfgSql = "SELECT itemCode, itemOrder, itemWidth, itemHeight, enableFlag " +
                "FROM [P_SystemFirstPageSetTab] " +
                "WHERE enableType IN (-1, 2)";
        List<Map<String, Object>> defaultCfgList = namedParameterJdbcTemplate.queryForList(defaultCfgSql, new MapSqlParameterSource());

        // 检查并填充缺失的配置
        for (Map<String, Object> 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<Map>形式模拟DataTable）
     */
    // 缓存配置：过期时间10秒，对应原C#的Cache特性
    @Cacheable(value = "systemListCache", key = "#id", condition = "#id != null", unless = "#result == null")
    public List<Map<String, Object>> 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<Map>模拟DataTable）
        if (id > 0) {
            return jdbcTemplate.queryForList(sql, id);
        } else {
            return jdbcTemplate.queryForList(sql);
        }
    }

    public List<Map<String, Object>> 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<String, String> getMapInfo() {
        // MYUh6esf4UI3aglaswRBGWAXxYFNbf6q zyw 账号，额度不够
        // aAEHg5NfO4jtmlbD1MofHxVo38KKctkO 唐元账号，js地址解析 5w每天

        // 读取配置：优先读 MapAK，若为空则读 bMapAK，默认值为指定字符串
        String mapAK = WebConfigUtil.get("MapAK", WebConfigUtil.get("bMapAK", "MYUh6esf4UI3aglaswRBGWAXxYFNbf6q"));

        // 创建并初始化 Hashtable，对应 C# 的匿名初始化方式
        Hashtable<String, String> 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);
    }

    /// <summary>
    /// 获取设备宝地址
    /// </summary>
    /// <returns></returns>
    public List<Map<String, Object>> 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<Map>表示）
     */
    @Cacheable(value = "menuCache", key = "#parentid + '_' + #seriesId + '_' + #targetmode", condition = "#result != null", unless = "#result.isEmpty()")
    public List<Map<String, Object>> 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<Map<String, Object>>）
        return jdbcTemplate.queryForList(sql);
    }

    /**
     * 获取数据库服务器信息列表
     *
     * @param userId   用户ID
     * @param userName 用户名
     * @return 包含数据库服务器信息的Hashtable列表
     */
    public List<Hashtable<String, Object>> GetDbserverInfo(String userId, String userName) {
        List<Hashtable<String, Object>> list = new ArrayList<>();

        // 获取系统数据库组信息（对应C#的GetSysdbGroup()，返回List<Map>模拟DataTable）
        List<Map<String, Object>> sysDbGroupList = GetSysdbGroup(0);

        // 获取账户信息（对应C#的GetAccountInfoByStore，返回List<Map>模拟DataTable）
        List<Map<String, Object>> accountInfoList = GetAccountInfoByStore(userId, userName);
        Hashtable<String, Object> countTb = new Hashtable<>();

        // 处理账户信息，构建dbname到amount的映射
        if (accountInfoList != null && !accountInfoList.isEmpty()) {
            for (Map<String, Object> 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<String, Object> dtRow : sysDbGroupList) {
                // 检查当前dbname是否在账户信息中存在，不存在则跳过
                String dbname = DataTableUtil.getStringValue(dtRow, "dbname", "");
                if (isNullOrEmpty(dbname)) continue;
                if (!countTb.isEmpty() && !countTb.containsKey(dbname)) {
                    continue;
                }

                // 构建当前行的Hashtable
                Hashtable<String, Object> 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<Map>表示）
     */
    public List<Map<String, Object>> 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<String, Object> 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<Map<String, Object>>) tempResult;
                }
            }
        } catch (Exception e) {
            // 捕获所有异常，按原逻辑不做处理
        }

        return null;
    }

    /**
     * 获取登录配置信息
     *
     * @return 包含登录配置及横幅信息的Hashtable
     */
    public Hashtable<String, Object> GetLoginCfg() {
        String sql = "select top 1 id, SysName, copyright, logo from p_systemLoginCfgTab where disabled=0";
        // 执行查询获取登录配置主表信息（返回List<Map>模拟DataTable）
        List<Map<String, Object>> dtVal = jdbcTemplate.queryForList(sql);

        Hashtable<String, Object> datas = null;

        if (dtVal != null && !dtVal.isEmpty()) {
            // 转换第一条记录为Hashtable
            Map<String, Object> firstRow = dtVal.get(0);
            datas = new Hashtable<>();
            for (Map.Entry<String, Object> 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<Map<String, Object>> bannerList = jdbcTemplate.queryForList(bannerSql);

            // 将横幅信息添加到结果中
            datas.put("items", bannerList);
        }

        return datas;
    }

    /**
     * 获取产品系统类型信息
     *
     * @return 产品系统类型列表（用List<Map<String, Object>>模拟DataTable）
     */
    public List<Map<String, Object>> 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);
    }

    /// <summary>
    /// web服务端升级程序
    /// </summary>
    /// <param name="code"></param>
    /// <returns></returns>
    public List<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> sysdbGroupList = GetSysdbGroup(0);
        // 获取用户账号信息（对应C#的GetAccountInfoByStore）
        List<Map<String, Object>> accountInfoList = GetAccountInfoByStore(userid, userName);

        // 处理用户账号信息（原C#中的coutDt）
        if (accountInfoList != null && accountInfoList.size() == 1) {
            // 获取用户账号对应的dbname
            Map<String, Object> 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<Map<String, Object>> 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<String, Object> 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<Map<String, Object>> result = namedJdbcTemplate.queryForList(finalSql, paramMap);

        // 转换列名为小写
        return ConversionUtils.toLowerColumnName(result);
    }

    // 重载方法，提供默认menuid为0的情况
    public List<Map<String, Object>> 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<Map < String, Object>> 等价C#的DataTable，一行数据对应一个Map
     * @throws SQLException SQL异常抛出
     */
    public List<Map<String, Object>> 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<Map<String, Object>>）
     */
    @Cacheable(value = "dynamicSchemeFieldsCache", key = "#groupId + '_' + #fieldId")
    public List<Map<String, Object>> getDynamicSchemeFields(int groupId, int fieldId) {
        // 1. 构建动态条件
        StringBuilder idCond = new StringBuilder();
        Map<String, Object> 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<Map>模拟DataTable
        return namedJdbcTemplate.queryForList(sql, params);
    }

    // 重载方法：适配原C#的默认参数 fieldId=0
    public List<Map<String, Object>> getDynamicSchemeFields(int groupId) {
        return getDynamicSchemeFields(groupId, 0);
    }

    /**
     * 获取动态方案规则
     * 对应 C# 的 GetDynamicSchemeRule 方法（DataTable → List<Map<String, Object>>）
     */
    @Cacheable(value = "dynamicSchemeRuleCache", key = "#groupId")
    public List<Map<String, Object>> 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<Map < String, Object>>（模拟DataTable，所有列名转为小写）
     */
    public List<Map<String, Object>> 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<String, Object> params = Collections.singletonMap("moduleId", moduleId);

        // 3. 执行查询，获取原始结果（列名默认是数据库返回的大小写）
        List<Map<String, Object>> originalResult = namedJdbcTemplate.queryForList(sql, params);

        // 4. 模拟 C# 的 ToLowerColumnName() 方法：将所有列名转为小写
        List<Map<String, Object>> lowerCaseResult = new ArrayList<>();
        for (Map<String, Object> row : originalResult) {
            Map<String, Object> lowerCaseRow = new HashMap<>();
            for (Map.Entry<String, Object> entry : row.entrySet()) {
                // 列名转为小写，值保持不变
                lowerCaseRow.put(entry.getKey().toLowerCase(), entry.getValue());
            }
            lowerCaseResult.add(lowerCaseRow);
        }

        return lowerCaseResult;
    }

    /**
     * 获取历史审批步骤信息
     * 对应 C# 方法，返回 List<Map<String, Object>> 模拟 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<Map<String, Object>> 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<String, Object> 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<Map>模拟DataTable
        return namedJdbcTemplate.queryForList(sql, params);
    }

    // 重载方法：适配C#的默认参数 isBase = true
    public List<Map<String, Object>> 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<Map < String, Object>> 模拟DataTable，每个Map对应一行数据
     */
    public List<Map<String, Object>> 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<Map < String, Object>> 模拟DataTable
     */
    public List<Map<String, Object>> 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<String> 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<Map < String, Object>> 模拟DataTable
     */
    @Cache(ExpirationPeriod = 10, key = "#moduleId") // 缓存注解，key绑定moduleId
    public List<Map<String, Object>> 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<Map < String, Object>> 模拟DataTable
     */
    @Cache(ExpirationPeriod = 10, key = "#menuid")
    public List<Map<String, Object>> 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<Map < String, Object>> 模拟DataTable
     */
    @Cache(ExpirationPeriod = 10, key = "#formKey")
    public List<Map<String, Object>> 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<Map < String, Object>> 模拟DataTable
     */
    @Cache(ExpirationPeriod = 10, key = "#menuid")
    public List<Map<String, Object>> 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<Map < String, Object>> 模拟DataTable
     */
    @Cache(ExpirationPeriod = 10, key = "#formkey")
    public List<Map<String, Object>> 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", "<br />");
            String msgValue = msgpm.getValue() == null ? "" : msgpm.getValue().toString();
            response.setMsg(msgValue.replace("\r", "<br />"));
        }

        // 对应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<BaseResponse>) 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<List<Map<String, Object>>> rse = (ResultSetExtractor<List<Map<String, Object>>>) 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", "<br />"));
                    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<Map>对应C# DataTable）
     */
    @Cache(ExpirationPeriod = 20)
    public List<Map<String, Object>> 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<Map<String, Object>>）
        return jdbcTemplate.queryForList(sqlFirst);
    }
}
