package org.example.Utils.OfficeUtil;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.example.Api.LoggerHandler;
import org.example.Api.OptBaseHandler;
import org.example.Entity.Attributes.RequestCheck;
import org.example.Entity.Control.Com.SysPoPupMenuBtn;
import org.example.Impl.BaseImpl;
import org.example.Impl.ModuleImpl;
import org.example.Office.CreateWordUtil;
import org.example.Office.OfficeUtil;
import org.example.Utils.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.example.Utils.NativeExtensionUtils.*;

@RestController
@RequestMapping("/Api/ToolsHandler")
public class ToolsHandler extends OptBaseHandler {
    private static final Logger log = LoggerFactory.getLogger(ToolsHandler.class);

    @RequestMapping(value = "/**", method = {RequestMethod.GET, RequestMethod.POST})
    public void handleRequest(HttpServletRequest Request, HttpServletResponse response) throws Exception {
        // 调用 BaseHandler 的 processRequest 处理逻辑
        super.processRequest(Request);
    }

    @Autowired
    private ModuleImpl _moduleImpl;
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private DbOperator dbOperator;
    @Qualifier("baseImpl")
    @Autowired
    private BaseImpl bImpl;
    @Autowired
    private LoggerHandler loggerHandler;
    @Autowired
    private ObjectMapper objectMapper;

    protected ModuleImpl getModuleImpl() {
        // 核心延迟初始化逻辑：若_moduleImpl为null则创建实例并设置属性
        if (_moduleImpl == null) {
            _moduleImpl = new ModuleImpl(jdbcTemplate);
            // 原C#中注释的UserSessionName赋值，保留作为参考
            // _moduleImpl.setUserSessionName(getUserSessionName());
            _moduleImpl.setDbOperator(dbOperator); // 假设ModuleImpl有setDbOperator方法
        }
        return _moduleImpl;
    }

    protected void setModuleImpl(ModuleImpl value) {
        _moduleImpl = value;
    }

    private String _discopy = "auto";

    @RequestCheck(CheckLogin = false, WriteRespose = false, CheckParams = "url")
    public void ViewDoc() {
        // string inputUrl = bImpl.Request("url").TrimStart('/');
        //是否允许在浏览器复制
        int discopy = ToInt32(bImpl.Request("discopy"));
        String url = bImpl.Request("url");
        String pwd = bImpl.Request("pwd");
        boolean createnew = toBoolean(bImpl.Request("n", "0"));
        ViewFile(url, discopy, createnew, true, false, pwd);
    }

    private String ViewFile(String url, int discopy, boolean createnew) {
        return ViewFile(url, discopy, createnew, true, false, "");
    }

    private String ViewFile(String _url, int discopy, boolean createNew, boolean redict, boolean toPdf, String password) {
        String inputUrl = TrimStart(_url, '/');
        String attcPath = getAttcPath();
        String _discopy = ""; // 初始化_copy变量
        if (discopy == 1) {
            _discopy = "none";
        }
        String url = Trim(inputUrl, ','), webUrl = inputUrl, hpath = "";

        // URL解码处理（若包含%2开头的编码字符）
        if (url.contains("%2")) {
            url = URLDecoder.decode(url, StandardCharsets.UTF_8);
        }

        // 处理相对路径（非绝对路径、非HTTP链接）
        if (!url.contains(":/") && !url.contains(":\\") && !url.regionMatches(true, 0, "http", 0, 4)) {
            String fileVPath = WebConfigUtil.getFileVPath();
            boolean startsWithFileVPath = url.regionMatches(true, 0, fileVPath, 0, fileVPath.length());
            boolean containsFileVPath = url.toLowerCase().contains(fileVPath.toLowerCase());

            if (!startsWithFileVPath && !containsFileVPath) {
                url = "/" + fileVPath + "/" + url;
            } else {
                url = "/" + url;
            }
            inputUrl = url;
        }

        // 处理HTTP链接路径
        if (url.contains("http")) {
            String fileVPath = WebConfigUtil.getFileVPath();
            int vIndex = indexOfIgnoreCase(url, fileVPath);

            if (vIndex > -1) {
                attcPath = getModuleImpl().GetAttcPathByOAUrl(url.substring(0, vIndex - 1));
                String subUrl = url.substring(vIndex - 1).replaceFirst("^/", "");
                url = "/" + subUrl;
                String encodedUrl = URLEncoder.encode(subUrl, StandardCharsets.UTF_8).replace("%2F", "/");
                webUrl = inputUrl.substring(0, vIndex - 1) + "/" + encodedUrl;
            }
        } else if (indexOfIgnoreCase(url, WebConfigUtil.getFileVPath()) > -1) {
            String fileVPath = WebConfigUtil.getFileVPath();
            int vIndex = indexOfIgnoreCase(url, fileVPath);
            int startIndex = Math.max(0, vIndex - 1);

            String subUrl = url.substring(startIndex).replaceFirst("^/", "");
            String encodedUrl = URLEncoder.encode(subUrl, StandardCharsets.UTF_8);
            webUrl = "/" + encodedUrl;
        }

        // 补全附件路径（为空则使用默认路径）
        attcPath = (attcPath == null || attcPath.isEmpty()) ? getAttcPath() : attcPath;
        String urlpath = url;

        // 转换为绝对路径
        if (!url.contains(":/") && !url.contains(":\\")) {
            try {
                StringBuilder vpath = new StringBuilder();
                String[] pathResult = FileUtil.toAbsPath(url, attcPath, vpath);
                urlpath = pathResult[0];

                // 移除路径中的\Api（适配Windows路径）
                if (urlpath.contains("\\Api")) {
                    urlpath = urlpath.replace("\\Api", "");
                }
            } catch (Exception e) {
                if (Response != null) {
                    try {
                        ServletContext servletContext = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getServletContext();
                        String mapPath = servletContext.getRealPath("/");
                        String errorMsg = e.getMessage() + "\n" + mapPath + url;
                        Response.getWriter().print(errorMsg);
                        response.setMsg(errorMsg);
                        log.debug(String.format("异常输出失败：%s", errorMsg));
                    } catch (IOException ioException) {
                        log.debug(String.format("异常输出失败：%s", ioException.getMessage()));
                        throw new RuntimeException("异常输出失败：" + ioException.getMessage(), ioException);

                    }
                }
                return "";
            }
        }
//        urlpath = FileUtil.ensureUtf8String(urlpath);
//        attcPath = FileUtil.ensureUtf8String(attcPath);
        // 检查文件是否存在
        File file = new File(urlpath);
        if (file.exists()) {
            // 验证文件权限
            if (!FileUtil.checkFileAuthory(urlpath, attcPath)) {
                try {
                    response.setMsg("权限验证失败：未在附件目录内找到文件!");
                    Response.getWriter().print("权限验证失败：未在附件目录内找到文件!");
                } catch (IOException e) {
                    log.debug(String.format("权限验证失败：%s", e.getMessage()));
                    throw new RuntimeException("权限验证失败：" + e.getMessage(), e);
                }
                return "";
            }

            // 临时文件复制（数据库附件场景）
            String filePath = WebConfigUtil.getFilePath();
            boolean useDbAttc = WebConfigUtil.get("useDbAttc").equalsIgnoreCase("true");
            if (!urlpath.startsWith(filePath) && useDbAttc) {
                File info = new File(urlpath);
                String newDir = filePath + File.separator + "temp";
                String newFilePath = newDir + File.separator + info.getName();

                // 创建临时目录
                File newDirFile = new File(newDir);
//                if (!newDirFile.exists()) {
//                    newDirFile.mkdirs();
//                }
                try {
                    // 强制创建目录（包括父目录），并设置权限
                    if (!newDirFile.exists()) {
                        newDirFile.mkdirs(); // 替代 Files.createDirectories，兼容性更好
                        newDirFile.setWritable(true, false); // 设为可写
                        newDirFile.setReadable(true, false); // 设为可读
                        newDirFile.setExecutable(true, false);
                    }
                    // 检查权限
                    if (!newDirFile.canWrite()) {
                        log.debug(String.format("newDirFile目录无写入权限：%s", newDirFile));
                        throw new RuntimeException("目录无写入权限：" + newDirFile);
                    }
                } catch (Exception e) {
                    log.debug(String.format("newDirFile创建目录失败：%s", newDirFile));
                    throw new RuntimeException("创建目录失败：" + newDirFile, e);
                }
                // 3分钟后删除临时文件
                File finalNewFilePath = new File(newFilePath);
                if (!finalNewFilePath.exists()) {
                    new Timer().schedule(new TimerTask() {
                        @Override
                        public void run() {
                            if (finalNewFilePath.exists()) {
                                finalNewFilePath.delete();
                            }
                        }
                    }, 3 * 60 * 1000);
                }

                // 复制文件（覆盖已存在）
                try {
//                    Files.copy(Paths.get(urlpath), Paths.get(newFilePath), java.nio.file.StandardCopyOption.REPLACE_EXISTING);
                    Files.copy(Paths.get(urlpath), Paths.get(newFilePath), java.nio.file.StandardCopyOption.REPLACE_EXISTING);
//                  File urlF = new File(urlpath);
//                  File newUrlF = new File(newFilePath);

                } catch (IOException e) {
                    log.debug(String.format("文件复制失败：%s", e.getMessage()));
                    throw new RuntimeException("文件复制失败：" + e.getMessage(), e);
                }
                urlpath = newFilePath;

                // 更新webUrl
                StringBuilder webUrlBuilder = new StringBuilder();
                FileUtil.toAbsPath(urlpath, attcPath, webUrlBuilder);
                webUrl = webUrlBuilder.toString();
            }

            try {
                File info = new File(urlpath);
                String htmlPath = "", pdfViewHtml = "";
                String viewType = WebConfigUtil.get("AttcViewType").toLowerCase();

                // 获取预览文件路径
                StringBuilder htmlPathBuilder = new StringBuilder();
                hpath = WebConfigUtil.getViewDocHtmlPath(info.getAbsolutePath(), htmlPathBuilder);
                htmlPath = htmlPathBuilder.toString();

                // 生成新文件名（移除扩展名）
                String newFileName = hpath.split("/")[hpath.split("/").length - 1];
                newFileName = newFileName.replace(getFileExtension(newFileName), "");

                boolean viewTypeIsPdf = !"html".equals(viewType) && !"img".equals(viewType);
                String docFullPath = hpath.replace(htmlPath.split("/")[htmlPath.split("/").length - 1], "");

                // 创建文档目录
                File docFullPathDir = new File(docFullPath);
                try {
                    // 强制创建目录（包括父目录），并设置权限
                    if (!docFullPathDir.exists()) {
                        docFullPathDir.mkdirs(); // 替代 Files.createDirectories，兼容性更好
                        docFullPathDir.setWritable(true, false); // 设为可写
                        docFullPathDir.setReadable(true, false); // 设为可读
                        docFullPathDir.setExecutable(true, false);
                    }
                    // 检查权限
                    if (!docFullPathDir.canWrite()) {
                        log.debug(String.format("docFullPathDir目录无写入权限：%s", docFullPathDir));
                        throw new RuntimeException("目录无写入权限：" + docFullPathDir);
                    }
                } catch (Exception e) {
                    log.debug(String.format("docFullPathDir创建目录失败：%s", docFullPathDir));
                    throw new RuntimeException("创建目录失败：" + docFullPathDir, e);
                }

                // 强制创建新文件（删除旧文件）
                File hpathFile = new File(hpath);
                if (createNew && hpathFile.exists()) {
                    hpathFile.delete();
                }
                String fileExtension = Trim(getFileExtension(info.getName()).toLowerCase().trim(), '.');
                ;
                if (viewTypeIsPdf && !OfficeUtil.CheckOfficPwd(urlpath, fileExtension, password)) {
                    String msg = "";
                    if (!isNullOrEmpty(password)) {
                        msg = "密码错误！";
                    }
                    //Response.redirect($"/pages/app/app.html?xtype=classic.Ywp.view.OfficPwd&serverId={user.ServerId}&dllcoid=1&err={msg}&src={HttpUtility.UrlEncode(url)}", false);
                    // 1. 拼接 URL 参数（严格还原 C# 字符串插值逻辑）
                    int serverId = getUser().getServerId(); // 假设 User 类有 getServerId() 方法
                    String encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8); // 对应 C# HttpUtility.UrlEncode
                    // 2. 构造完整重定向地址（与 C# 路径完全一致）
                    String redirectUrl = String.format(
                            "/pages/app/app.html?xtype=classic.Ywp.view.OfficPwd&serverId=%d&dllcoid=1&err=%s&src=%s",
                            serverId,
                            msg, // C# 未编码 err 参数，此处保持一致（若需编码可添加 URLEncoder.encode(msg, "UTF-8")）
                            encodedUrl
                    );
                    // 3. 执行重定向（还原 C# Response.Redirect(Url, false) 逻辑）
                    Response.sendRedirect(redirectUrl);
                    return "";
                }
                // 已存在预览文件：直接重定向
                if (hpathFile.exists()) {
                    if (viewTypeIsPdf) {
                        pdfViewHtml = ToPdfViewPath(htmlPath, discopy);
                        getResponse().sendRedirect(pdfViewHtml);
                        return pdfViewHtml;
                    } else {
                        htmlPath = FileUtil.urlEncode(htmlPath, false);
                        String timestamp = String.valueOf(System.currentTimeMillis() % 1000);
                        getResponse().sendRedirect(htmlPath + "?t=" + timestamp);
                    }
                } else {
                    // 按文件扩展名处理
                    switch (fileExtension) {
                        case "doc":
                        case "docx":
                            try {
                                switch (viewType) {
                                    case "html":
                                        OfficeUtil.SaveToHtml(urlpath, hpath, _discopy);
                                        break;
                                    case "img":
                                        OfficeUtil.saveToImgByAspose(urlpath, hpath, fileExtension, toPdf);
                                        break;
                                    default:
                                    case "pdf":
                                        OfficeUtil.saveToPdfByAspose(urlpath, hpath, "doc", toPdf, password);
                                        pdfViewHtml = ToPdfViewPath(htmlPath, discopy);
                                        getResponse().sendRedirect(pdfViewHtml);
                                        return pdfViewHtml;
                                }
                            } catch (Exception e) {
                                OfficeUtil.SaveToImg(urlpath, hpath, fileExtension, toPdf);
                            }
                            break;

                        case "xlsx":
                        case "xls":
                            switch (viewType) {
                                case "html":
                                    OfficeUtil.SaveExcelToHtml(urlpath, hpath, htmlPath, _discopy);
                                    break;
                                case "img":
                                    OfficeUtil.SaveToImg(urlpath, hpath, fileExtension);
                                    break;
                                default:
                                case "pdf":
                                    OfficeUtil.saveToPdfByAspose(urlpath, hpath, "xls", toPdf, password);
                                    pdfViewHtml = ToPdfViewPath(htmlPath, discopy);
                                    getResponse().sendRedirect(pdfViewHtml);
                                    return pdfViewHtml;
                            }
                            break;

                        case "pdf":
                            String timestamp = String.valueOf(System.currentTimeMillis() % 1000);
                            pdfViewHtml = String.format("/Resource/Scripts/Yw/packages/pdfjs/web/viewer.html?file=%s&t=%s&discopy=%s",
                                    webUrl, timestamp, discopy);
                            getResponse().sendRedirect(pdfViewHtml);
                            return pdfViewHtml;

                        case "ppt":
                        case "pptx":
                            try {
                                OfficeUtil.saveToPdfByAspose(urlpath, hpath, "pptx", toPdf, password);
                            } catch (Exception e) {
                                OfficeUtil.savePptToPdfBySprie(urlpath, hpath);
                            }
                            break;

                        case "tif":
                            String tifDocPath = "", convertPath = "", timeDocStr = "";
                            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
                            timeDocStr = sdf.format(new Date());
                            tifDocPath = docFullPath.replace("DocHtml", "TifConverted/" + timeDocStr);

                            // 处理预览URL
                            String newWebUrl = webUrl.replace("fileRoot", "TifConverted/" + timeDocStr);
                            try {
                                newWebUrl = URLDecoder.decode(newWebUrl, "UTF-8");
                            } catch (UnsupportedEncodingException e) {
                                throw new RuntimeException("TIF URL解码失败：" + e.getMessage(), e);
                            }
                            String[] newWebUrlSplits = newWebUrl.split("/");
                            String lastNewWebUrlPart = newWebUrlSplits[newWebUrlSplits.length - 1];
                            newWebUrl = newWebUrl.replace(lastNewWebUrlPart, "/" + newFileName + ".png").replace("#", "");

                            // 创建TIF转换目录
                            File tifDocPathDir = new File(tifDocPath);
                            if (!tifDocPathDir.exists()) {
                                tifDocPathDir.mkdirs();
                            }

                            convertPath = tifDocPath + File.separator + newFileName + ".png";
                            if (!newWebUrl.startsWith("/")) {
                                newWebUrl = "/" + newWebUrl;
                            }

                            File convertPathFile = new File(convertPath);
                            if (convertPathFile.exists()) {
                                String redirectUrl = String.format("/pages/app/FileViewer.html?url=%s", newWebUrl);
                                getResponse().sendRedirect(redirectUrl);
                                return redirectUrl;
                            } else {
                                String tifBase64 = OfficeUtil.TifToBase64(urlpath);
                                OfficeUtil.base64ToPng(tifBase64, convertPath);
                                String redirectUrl = String.format("/pages/app/FileViewer.html?url=%s", newWebUrl);
                                getResponse().sendRedirect(redirectUrl);

                                // 1小时后删除目录
                                String finalTifDocPath = tifDocPath;
                                new Timer().schedule(new TimerTask() {
                                    @Override
                                    public void run() {
                                        File tifDir = new File(finalTifDocPath);
                                        if (tifDir.exists()) {
                                            deleteDir(tifDir);
                                        }
                                    }
                                }, 3600000);
                                return redirectUrl;
                            }

                        case "dwg":
                        case "dwf":
                        case "dxf":
                            String dwgDocPath = docFullPath.replace("DocHtml", "mxDwg");
                            File dwgDocPathDir = new File(dwgDocPath);
                            if (!dwgDocPathDir.exists()) {
                                dwgDocPathDir.mkdirs();
                            }

                            // 处理DWG文件名
                            String dwgName = info.getName().replace(getFileExtension(info.getName()), "");
                            dwgName = dwgName.replace(".", "_");

                            // 转换路径
                            String convertMacPath = dwgDocPath + File.separator + dwgName + ".mxweb";
                            String[] convertMacPathSplits = convertMacPath.split(File.separator);
                            String firstSplitPart = convertMacPathSplits[0];
                            String convertDwgPath = convertMacPath.replace(firstSplitPart, "");

                            File convertMacPathFile = new File(convertMacPath);
                            if (!convertMacPathFile.exists()) {
                                boolean convertOk = OfficeUtil.convertDwgToMxWeb(urlpath, dwgName, dwgDocPath);
                                if (!convertOk) {
                                    String redirectUrl = String.format("/DwgWebDefault.aspx?url=%s", webUrl);
                                    getResponse().sendRedirect(redirectUrl);
                                    return redirectUrl;
                                }
                            }

                            String dwgRedirectUrl = String.format("/pages/mxcad/index.html?url=%s", convertDwgPath);
                            getResponse().sendRedirect(dwgRedirectUrl);
                            return dwgRedirectUrl;

                        case "txt":
                            Charset encoding = FileUtil.getFileEncoding(urlpath);
                            String text = Files.readString(Paths.get(urlpath), encoding);
                            Response.getWriter().print(text);
                            return "";

                        default:
                            HttpServletResponse response = getResponse();
                            if (org.example.Utils.ImageUtil.isImg(url)) {
                                String redirectUrl = String.format("/pages/app/FileViewer.html?url=%s&discopy=%s", webUrl, discopy);
                                response.sendRedirect(redirectUrl);
                                return redirectUrl;
                            } else {
                                response.sendRedirect(webUrl);
                                return webUrl;
                            }
                    }

                    // 需要重定向时处理HTML路径
                    if (redict) {
                        htmlPath = FileUtil.urlEncode(htmlPath, false);
                        String timestamp = String.valueOf(System.currentTimeMillis() % 1000);
                        getResponse().sendRedirect(htmlPath + "?t=" + timestamp);
                    }
                }

                // 返回带时间戳的预览路径
                String timestamp = String.valueOf(System.currentTimeMillis() % 1000);
                return htmlPath + "?t=" + timestamp;

            } catch (Exception e) {
                if (redict) {
                    String errorSuffix = e.getMessage().indexOf("无法将类型为“Spire.Doc.Documents.Paragraph”的对象强制转换为类型“Spire.Doc.Fields.ShapeObject”") > -1
                            ? "，请修改文件中自定义形状转换为图片后重新上传试试！"
                            : "";
                    // try {
//                        Response.getWriter().print(String.format("该文件不支持预览%s:%s", errorSuffix, e.getMessage()));
                    response.setMsg(String.format("该文件不支持预览%s:%s", errorSuffix, e.getMessage()));
                    // } catch (IOException ioException) {
                    //  throw new RuntimeException("预览异常输出失败：" + ioException.getMessage(), ioException);
                    // }
                }
                LoggerHandler.error(this, e.getMessage(), e);
            }
        } else if (url.indexOf(",") > -1) {
            try {
                String redirectUrl = String.format("/pages/app/FileViewer.html?url=%s", _url);
                getResponse().sendRedirect(redirectUrl);
                return redirectUrl;
            } catch (IOException e) {
                throw new RuntimeException("多文件预览重定向失败：" + e.getMessage(), e);
            }
        } else if (redict) {
            //try {
            String tipMsg = String.format("未找到文件:%s", urlpath);
            response.setMsg(tipMsg);
//                getResponse().getWriter().print(tipMsg);
            // } catch (IOException e) {
            // throw new RuntimeException("文件不存在提示输出失败：" + e.getMessage(), e);
            // }
        }
        return "";
    }

    private String ToPdfViewPath(String pdfPath, int discopy) {
        try {
            // 对pdf路径进行URL编码，对应C# FileUtil.UrlEncode
            String encodedPdfPath = java.net.URLEncoder.encode(pdfPath, "UTF-8");
            // 获取当前时间的毫秒部分，对应C# DateTime.Now.Millisecond
            long timestamp = System.currentTimeMillis() % 1000;
            HttpServletRequest request = context;
            String referer = request.getHeader("Referer");
            String frontendAddress = "";

            if (referer != null && referer.contains("://")) {
                // 假设 referer 为 "http://localhost:8081/Lserp_v8/index.html"
                // 我们需要截取出 "http://localhost:8081"

                // 找到 "://" 之后第一个 "/" 的位置
                int index = referer.indexOf("/", referer.indexOf("://") + 3);
                if (index != -1) {
                    frontendAddress = referer.substring(0, index);
                } else {
                    frontendAddress = referer;
                }
            }
            // 拼接路径字符串
            String pdfViewHtml = String.format("%s/Resource/Scripts/Yw/packages/pdfjs/web/viewer.html?file=%s&t=%d&discopy=%d",
                    frontendAddress, encodedPdfPath, timestamp, discopy);
            return getResponse().encodeRedirectURL(pdfViewHtml);
        } catch (java.io.UnsupportedEncodingException e) {
            // 捕获编码异常（UTF-8为标准编码，实际很少触发）
            throw new RuntimeException("PDF路径URL编码失败：" + e.getMessage(), e);
        }
    }

    @RequestCheck(CheckLogin = false, WriteRespose = false, CheckParams = "url")
    public void LoadPage() {
        String url = bImpl.Request("url");
        if (!NativeExtensionUtils.isNullOrEmpty(url)) {
            boolean[] success = new boolean[1];
            String content = WebUtil.send(url, null, null, success, "GET");
            // 后续内容处理和响应输出逻辑不变
            try {
                URL uri = new URL(url);
                String scheme = uri.getProtocol();
                String authority = uri.getAuthority();
                String baseUrl = String.format("%s://%s/", scheme, authority);

                content = content.replace("_blank", "_self");
                content = content.replace("./", baseUrl);

                HttpServletResponse response = getResponse();
                response.reset();
                response.getWriter().print(content);

            } catch (MalformedURLException e) {
                LoggerHandler.error(this, "URL解析失败: " + url, e);
            } catch (IOException e) {
                LoggerHandler.error(this, "响应内容输出失败", e);
            }
        }
    }

    @RequestCheck(CheckLogin = true, CheckParams = "urls")
    public void ZipFiles() {
        // 1. 获取请求参数"urls"并分割（对应C# bImpl.Request("urls").Split(',')）
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        String urlsStr = attributes.getRequest().getParameter("urls");
        if (urlsStr == null || urlsStr.isEmpty()) {
            response.setSuccess(false);
            return;
        }
        String[] urls = urlsStr.split(",");
        String[] fullUrls = new String[urls.length];

        // 2. 处理每个URL，生成物理路径并检查权限
        // 获取应用物理路径（对应C# PhysicalApplicationPath）
        ServletContext servletContext = attributes.getRequest().getServletContext();
        String physicalPath = servletContext.getRealPath("/");

        for (int i = 0; i < urls.length; i++) {
            String u;
            try {
                // URL解码（对应C# HttpUtility.UrlDecode）
                u = URLDecoder.decode(urls[i], "UTF-8");
            } catch (UnsupportedEncodingException e) {
                LoggerHandler.error(this, "URL解码失败: " + urls[i], e);
                fullUrls[i] = "";
                continue;
            }

            if (u == null || u.isEmpty()) {
                fullUrls[i] = "";
                continue;
            }

            // 拼接物理路径（对齐C#拼接逻辑）
            String fullUrl;
            if (u.startsWith("/")) {
                fullUrl = physicalPath + u;
            } else {
                fullUrl = physicalPath + "/" + u;
            }
            // 处理路径分隔符（兼容Windows和Linux）
            fullUrl = fullUrl.replace("/", File.separator).replace("\\", File.separator);

            // 检查文件权限（对应C# FileUtil.CheckFileAuthory）
            if (!FileUtil.checkFileAuthory(fullUrl, getAttcPath())) {
                fullUrls[i] = "";
            } else {
                fullUrls[i] = fullUrl;
            }
        }

        // 3. 构建ZIP文件路径
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddhhmmss");
        String timestamp = sdf.format(new Date());
        String zipVPath = String.format("/downLoadTemp/%s.zip", timestamp); // 相对路径
        String zipPath = WebConfigUtil.getFilePath() + File.separator + zipVPath; // 绝对路径（对应C#拼接逻辑）
        // 处理路径中的重复分隔符
        zipPath = zipPath.replace(File.separator + File.separator, File.separator);

        // 4. 执行压缩（对应C# ZipUtil.GoZip）
        // 拼接文件路径为分号分隔的字符串（对应string.Join(";", fullUrls)）
        StringBuilder filePaths = new StringBuilder();
        for (String path : fullUrls) {
            if (path != null && !path.isEmpty()) {
                if (filePaths.length() > 0) {
                    filePaths.append(";");
                }
                filePaths.append(path);
            }
        }
        org.example.Utils.ZipUtil.GoZip(filePaths.toString(), zipPath, ""); // 调用Java版压缩工具

        // 5. 10分钟后删除ZIP文件（对应C# DateTimeUtil.SetTimeOut）
        final String finalZipPath = zipPath; // 局部变量需为final才能被内部类访问
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                File zipFile = new File(finalZipPath);
                if (zipFile.exists()) {
                    zipFile.delete();
                }
            }
        }, 10 * 60 * 1000); // 10分钟 = 600000毫秒

        // 6. 设置响应数据（对应C# response.data和response.success）
        response.setData(String.format("/%s/%s", WebConfigUtil.getFileVPath(), zipVPath));
        response.setSuccess(true);
    }

    private static String OAUrl;

    public void saveToWord() {
        int mid = ToInt32(bImpl.Request("mid"));
        if (mid <= 0) return;

        // 1. 直接调用封装好的 bImpl.Request("data")，和C#调用完全一致
        String dataStr = bImpl.Request("data");

// 2. 用 Spring 原生 ObjectMapper 解析为 Hashtable（对应 C# JSON.Decode(..., typeof(Hashtable)) as Hashtable）
        Hashtable<String, Object> record = null;
        if (dataStr != null && !dataStr.isEmpty()) {
            try {
                // 核心解析：TypeReference 指定目标类型为 Hashtable<String, Object>，避免类型擦除
                record = objectMapper.readValue(
                        dataStr,
                        new TypeReference<Hashtable<String, Object>>() {
                        }
                );
            } catch (Exception e) {
                LoggerHandler.error(this, "JSON解析为Hashtable失败", e);
                record = null; // 解析失败返回null，和 C# "as Hashtable" 安全转换行为完全一致
            }
        }

// 3. 调用 moduleImpl 方法（C# Pascal命名 → Java 驼峰命名，参数完全兼容）
        SysPoPupMenuBtn menuBtn = getModuleImpl().GetContextMenuBtn(mid, record);
        if (menuBtn != null && menuBtn.get_dllname().equals("autocreatword")) {

            // 1. 获取当前请求上下文（用于获取ServletContext，等价于C#的HttpContext.Current）
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            ServletContext servletContext = attributes.getRequest().getServletContext();

// 2. 构建tempPath（对应C# $"/{WebConfigUtil.FileVPath}/report/template"）
            String tempPath = String.format("/%s/report/template", WebConfigUtil.getFileVPath());

// 3. 构建tlp1和tlp2（对应C# Server.MapPath拼接路径）
// 注意：menuBtn.dllpar1在Java中需通过getter访问，即menuBtn.getDllpar1()
            String tlp1Path = String.format("%s/%s.doc", tempPath, menuBtn.dllpar1);
            String tlp1 = servletContext.getRealPath(tlp1Path); // 等价于Server.MapPath

            String tlp2Path = String.format("%s/%s.doc", tempPath, menuBtn.dllpar2);
            String tlp2 = servletContext.getRealPath(tlp2Path);

// 4. 构建webPath（对应C# $"/{WebConfigUtil.FileVPath}/report/{menuBtn.dllpar6}/{menuBtn.dllpar7}.doc"）
            String webPath = String.format(
                    "/%s/report/%s/%s.doc",
                    WebConfigUtil.getFileVPath(),
                    menuBtn.dllpar6,
                    menuBtn.dllpar7
            );

// 5. 构建savePath（对应C# Server.MapPath(webPath)）
            String savePath = servletContext.getRealPath(webPath);

            // 5. 删除已存在的文件
            File saveFile = new File(savePath);
            if (saveFile.exists()) {
                saveFile.delete();
            }

            // 6. 执行SQL查询，获取数据集（对应C# 分割SQL并执行）
            String[] masterSqls = menuBtn.dllpar4.replaceAll("^\\^+", "").replaceAll("\\^+$", "").split("\\^"); // 转义^避免正则匹配
            List<List<List<Map<String, Object>>>> dataSets = new ArrayList<>();

            for (String masterSql : masterSqls) {
                if (masterSql == null || masterSql.isEmpty()) {
                    continue;
                }
                try {
//                    这里的数据查询与jdbc不同,这里是List<DataSet>,且可能存在多个DataSet
                    List<List<Map<String, Object>>> tableData = dbOperator.executeDataSet(masterSql);

                    // 将当前 SQL 的结果添加到数据集列表（对应 C# dataSets.Add(...)）
                    dataSets.add(tableData);

                } catch (Exception e) {
                    // 记录异常（使用你项目中的日志工具）
                    LoggerHandler.error(this, "执行 SQL 失败: " + masterSql, e);
                }
            }

            // 7. 获取OAUrl（对应C# 三元运算符赋值逻辑）
            if (OAUrl == null || OAUrl.isEmpty()) {
                // 从数据库查询（对应 C# dbOperator.ExecuteScalar）
                try {
                    // 执行单行单列查询：queryForObject 对应 ExecuteScalar
                    // 注意：SQL 中的 "top 1" 需根据数据库调整（MySQL 用 limit 1，SQL Server 保留 top 1）
                    String querySql = "select top 1 oaurl from p_systemtab"; // 假设是 MySQL
                    Object oaUrlObj = jdbcTemplate.queryForObject(querySql, Object.class);

                    // 赋值给静态变量 OAUrl（转换为字符串，避免 null）
                    OAUrl = (oaUrlObj != null) ? oaUrlObj.toString() : "";
                } catch (Exception e) {
                    // 处理查询异常（如无数据、SQL错误等）
                    LoggerHandler.error(this, "查询OAUrl失败", e);
                    OAUrl = ""; // 异常时赋空值，避免后续空指针
                }
            }

            // 8. 生成Word文件（对应C# AutoCreatWord.CreateWordUtil.Create）
            StringBuilder errMsg = new StringBuilder(); // 模拟out参数
            boolean ok = CreateWordUtil.Create(tlp1, tlp2, menuBtn.dllpar3, dataSets, savePath, OAUrl, errMsg);
            String viewPath = "";
            if (ok) {
                // 9. 获取预览路径（调用已实现的ViewFile方法）
                viewPath = ViewFile(savePath, 0, true, false, true, "");
            } else {
                // 10. 设置错误消息
                String msg = (errMsg == null || errMsg.isEmpty()) ? "文件生成失败！" : errMsg.toString();
                response.setMsg(msg);
            }

            // 11. 设置响应数据（用HashMap模拟C#匿名对象）
            response.setSuccess(ok);
            java.util.Map<String, String> responseData = new java.util.HashMap<>();
            responseData.put("filePath", webPath);
            responseData.put("pdfPath", webPath.replace(".doc", ".pdf"));
            responseData.put("viewPath", viewPath);
            response.setData(responseData);
        }
    }

    public void wordToHtmlTpl() {
        // 1. 获取请求参数（复用已封装的bImpl.Request，与C#调用一致）
        String fileName = bImpl.Request("name");
        // 2. 分割文件名获取模板名（对应fileName.Split('.')[0]）
        String[] fileNameParts = fileName.split("\\."); // Java中.需转义
        String tplName = fileNameParts.length > 0 ? fileNameParts[0] : "";

        String folder = bImpl.Request("folder", "wordTemplate"); // 带默认值
        folder = folder.trim(); // 先去首尾空格
        if (folder.endsWith("/")) { // 对应TrimEnd('/')
            folder = folder.substring(0, folder.length() - 1);
        }


        // 3. 处理路径（复用已封装的FileUtil.ToAbsPath，用StringBuilder模拟out vpath）
        StringBuilder vpathValue = new StringBuilder(); // 用StringBuilder替代String[]，匹配方法参数类型
        String tplFolder = FileUtil.toAbsPath(folder, getAttcPath(), vpathValue)[0]; // 现在参数类型匹配

        // 后续若需要获取vpath的值，通过toString()方法：
        String vpath = vpathValue.toString(); // 等价于C#中out参数传出的vpath值
        // 4. 获取htmlFolder物理路径（对应Server.MapPath）
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        ServletContext servletContext = attributes.getRequest().getServletContext();
        String htmlFolderRelPath = "/" + WebConfigUtil.getFileVPath() + "/wordTemplate/htmlTemp/" + tplName;
        String htmlFolder = servletContext.getRealPath(htmlFolderRelPath);

        // 5. 定义文件路径（对应C#的字符串插值）
        String tplFile = tplFolder + File.separator + fileName;
        String tplFile1 = tplFolder + File.separator + tplName + ".docx";
        String hpath = htmlFolder + File.separator + tplName + ".html";

        // 6. 创建目录（对应Directory.CreateDirectory）
        File tplFolderFile = new File(tplFolder);
        if (!tplFolderFile.exists()) {
            tplFolderFile.mkdirs(); // 递归创建目录
        }
        File htmlFolderFile = new File(htmlFolder);
        if (!htmlFolderFile.exists()) {
            htmlFolderFile.mkdirs();
        }

        // 7. 处理模板文件路径（优先.doc，不存在则用.docx）
        File tplFileObj = new File(tplFile);
        if (!tplFileObj.exists()) {
            tplFile = tplFile1;
            tplFileObj = new File(tplFile);
        }

        // 8. 删除已存在的HTML文件（对应File.Delete）
        File hpathFile = new File(hpath);
        if (hpathFile.exists()) {
            hpathFile.delete();
        }

        if (tplFileObj.exists()) {
            try {
                // 9. 调用封装的OfficeUtil转换为HTML（对应SaveToHtmlByAspose）
                String fileExt = fileNameParts.length > 1 ? fileNameParts[1] : ""; // 获取文件后缀
                OfficeUtil.saveToHtmlByAspose(tplFile, hpath, fileExt);

                // 10. 读取HTML内容（对应File.ReadAllText）
                String content = Files.readString(hpathFile.toPath(), StandardCharsets.UTF_8);

                // 1. 编译第一个正则：匹配<body>标签内的内容（对应C# Regex(@"(?is)(?<=<body[^>]*?>).*?(?=\s*</body)")）
                // 正则含义：匹配<body...>开头，然后捕获中间内容，直到</body前的空白
                Pattern conmatch = Pattern.compile(
                        "<body[^>]*?>(.*?)\\s*</body",  // 核心：用(.*?)捕获<body>内的内容
                        Pattern.CASE_INSENSITIVE | Pattern.DOTALL  // 保持i和s模式
                );
                Matcher conMatcher = conmatch.matcher(content);


                // 2. 编译第二个正则：匹配所有HTML标签（对应C# Regex(@"<[^>]+>")）
                Pattern htmlReg = Pattern.compile("<[^>]+>");

                // 3. 编译第三个正则：匹配{data.或{data1.等（对应C# Regex(@"{data\d?\.")）
                // 说明：Java中{、.是特殊字符，需转义为\\{、\\.
                Pattern dataReg = Pattern.compile("\\{data\\d?\\.");
                // 提取捕获的内容（对应原逻辑）
                if (conMatcher.find()) {
                    // group(1) 对应捕获组(.*?)，即<body>内的目标内容
                    content = conMatcher.group(1);
                }

                // 获取参数列表（复用已封装的PublicUtil.GetParamValue）
                List<String> pms = PublicUtil.getParamValue(content);
                for (String pm : pms) {
                    // 替换参数中的HTML标签
                    content = content.replace(pm, htmlReg.matcher(pm).replaceAll(""));
                }

                // 13. 最终内容处理
                content = dataReg.matcher(content).replaceAll("\\{"); // 替换{data.为{
                content = content.replace("&#xa0;", ""); // 移除HTML非断空格

                // 14. 设置响应（复用已封装的response）
                response.setSuccess(true);
                response.setData(content);

            } catch (Exception e) {
                LoggerHandler.error(this, "Word转HTML处理失败", e);
                response.setSuccess(false);
                response.setData("");
            }
        } else {
            // 模板文件不存在
            response.setData("");
        }
    }

    @RequestCheck(CheckLogin = false)
    public void toEnUrl() {
        try {
            // 1. 获取请求参数"url"（复用已封装的bImpl.Request）
            String url = bImpl.Request("url");

            // 2. URL解码（对应C# HttpUtility.UrlDecode）
            // 注意：Java需指定编码（UTF-8），并处理异常
            url = URLDecoder.decode(url, StandardCharsets.UTF_8.name());

            // 3. 遍历所有请求参数（对应C# bImpl.GetAllRequest()的DictionaryEntry集合）
            // 假设bImpl.getAllRequest()返回Map<String, String>（键值对集合）
            Map<String, String> allRequest = bImpl.getAllRequest();

            // 用StringBuilder高效拼接URL（避免频繁字符串创建）
            StringBuilder urlBuilder = new StringBuilder(url);

            for (Map.Entry<String, String> entry : allRequest.entrySet()) {
                // 获取参数名并转为小写（对应C# ToLower()）
                String key = entry.getKey().toLowerCase();

                // 排除key为"url"、"method"、"action"的参数（不区分大小写）
                if (!"url".equals(key) && !"method".equals(key) && !"action".equals(key)) {
                    // 拼接参数：&key=value（对应C# $"{url}&{kv.Key}={kv.Value}"）
                    urlBuilder.append("&")
                            .append(entry.getKey())  // 保留原始key（非小写）
                            .append("=")
                            .append(entry.getValue());  // 参数值
                }
            }

            // 4. 转换为最终URL（复用PublicUtil.ToEnUrl，Java用驼峰命名）
            String resultUrl = PublicUtil.ToEnUrl(urlBuilder.toString());

            // 5. 设置响应（对应C# response.data和success）
            response.setData(resultUrl);
            response.setSuccess(true);

        } catch (Exception e) {
            // 处理异常（如URL解码失败）
            LoggerHandler.error(this, "URL处理失败", e);
            response.setSuccess(false);
            response.setData("");
        }
    }


// ------------------------------ 辅助方法 ------------------------------

    /**
     * 移除字符串开头的指定字符（对齐C# TrimStart）
     */
    private String TrimStart(String str, char trimChar) {
        if (str == null || str.isEmpty()) {
            return str;
        }
        int index = 0;
        while (index < str.length() && str.charAt(index) == trimChar) {
            index++;
        }
        return str.substring(index);
    }

    /**
     * 移除字符串两端的指定字符（对齐C# Trim）
     */
    private String Trim(String str, char trimChar) {
        if (str == null || str.isEmpty()) {
            return str;
        }
        int start = 0;
        int end = str.length() - 1;
        while (start <= end && str.charAt(start) == trimChar) {
            start++;
        }
        while (end >= start && str.charAt(end) == trimChar) {
            end--;
        }
        return str.substring(start, end + 1);
    }


    // 辅助方法：忽略大小写查找子串位置
    private static int indexOfIgnoreCase(String source, String target) {
        if (source == null || target == null) return -1;
        int targetLen = target.length();
        int sourceLen = source.length();
        if (targetLen == 0) return 0;
        if (targetLen > sourceLen) return -1;

        for (int i = 0; i <= sourceLen - targetLen; i++) {
            if (source.regionMatches(true, i, target, 0, targetLen)) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 获取当前响应对象（Spring环境）
     */
    private HttpServletResponse getResponse() {
        return Response;
    }

    /**
     * 递归删除目录及文件
     */
    private void deleteDir(File dir) {
        if (dir.isDirectory()) {
            File[] files = dir.listFiles();
            if (files != null) {
                for (File file : files) {
                    deleteDir(file);
                }
            }
        }
        dir.delete();
    }

    /**
     * 获取文件扩展名（包含"."）
     */
    private String getFileExtension(String fileName) {
        int lastDotIndex = fileName.lastIndexOf(".");
        return lastDotIndex == -1 ? "" : fileName.substring(lastDotIndex);
    }


}
