PathKit工具类

package com.itmc.education.common.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ClassUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

/**
 * @Author: yhf
 * @Date: 2026/4/10 11:20
 */
public final class PathKit {

    private PathKit() {
        // 工具类,私有构造禁止实例化
    }

    private static final Logger log = LoggerFactory.getLogger(PathKit.class);

    // 系统分隔符(Windows \,Linux /,自动适配)
    private static final String SEPARATOR = File.separator;

    //==================================================================================================================

    /**
     * @description: 获取【classpath根目录,即classes文件夹】
     *
     *               开发时(IDEA):src/main/java + src/main/resources
     *               编译后(IDEA):项目路径/target/classes/
     *               部署后(Tomcat):Tomcat路径/webapps/项目名/WEB-INF/classes/
     *               开发时源码java+resources = 编译后的target/classes = 部署后的WEB-INF/classes
     * @param: []
     * @return: java.lang.String
     * @date: 2026/4/10 11:24
     * @author: yhf
     **/
    public static String getClassPath() {
        String path = "";
        try {
            // 获取类加载器根路径URL
            URL resource = ClassUtils.getDefaultClassLoader().getResource("");
            if (resource == null) {
//                throw new RuntimeException("未获取到类路径资源");
                return getProjectPath() + SEPARATOR + "target" + SEPARATOR + "classes";
            }

            path = resource.getPath();
            path = decodePath(path);

            // 处理Windows路径(去掉开头/,/D:/xxx → D:/xxx)
            if (isWindows() && path.startsWith("/")) {
                path = path.substring(1);
            }
        } catch (Exception e) {
//            throw new RuntimeException("获取类路径失败", e);
            log.error("获取类路径失败:" + e.getMessage());
            path = "";
        } finally {
            return path;
        }
    }

    /**
     * @description: 获取【classpath:/static 静态资源目录】
     * @param: []
     * @return: java.lang.String
     * @date: 2026/4/10 13:57
     * @author: yhf
     **/
    public static String getStaticPath() {
        return getClassPath() + "static" + SEPARATOR;
    }

    /**
     * @description: 获取【classpath:/templates 模板目录】
     * @param: []
     * @return: java.lang.String
     * @date: 2026/4/10 13:57
     * @author: yhf
     **/
    public static String getTemplatesPath() {
        return getClassPath() + "templates" + SEPARATOR;
    }

    /**
     * @description: 获取【Web应用根目录(webapp),即web访问路径】
     * 
     *               当前Web项目在服务器上的真实物理磁盘路径(绝对路径),也就是项目部署到Tomcat后,对应文件夹在硬盘上的实际位置
     *               IDEA开发目录的src/main/webapp,编译后=web根目录,即Tomcat路径/webapp/项目名/,编译后WEB-INF也会完整转移到这里面
     *               Service直接调用getWebRootPath()会报错NPE,因为方法依赖HttpServletRequest,只能在Controller里调用
     * @param: []
     * @return: java.lang.String
     * @date: 2026/4/10 11:25
     * @author: yhf
     **/
    public static String getWebRootPath() {
        HttpServletRequest request = getRequest();
        if (request == null) {
            throw new RuntimeException("非 Web 环境,无法获取 Web 根路径");
        }
        String path = request.getServletContext().getRealPath("");
        return decodePath(path);
    }

    /**
     * @description: 获取【WEB-INF目录】
     * @param: []
     * @return: java.lang.String
     * @date: 2026/4/10 11:25
     * @author: yhf
     **/
    public static String getWebInfPath() {
        return getWebRootPath() + "WEB-INF" + SEPARATOR;
    }

    /**
     * @description: 获取【系统临时文件目录】
     *               操作系统专门用来存临时文件的目录,重启电脑可能会清空
     *               Windows  -->  C:\Users\你的用户名\AppData\Local\Temp
     *               Linux    -->  /tmp
     * @param: []
     * @return: java.lang.String
     * @date: 2026/4/10 14:01
     * @author: yhf
     **/
    public static String getTempPath() {
        return System.getProperty("java.io.tmpdir");
    }

    /**
     * @description: 获取【用户目录】
     *               当前电脑用户目录,即登录操作系统的用户文件夹
     *               Windows  -->  C:\Users\你的用户名
     *               Linux    -->  /home/你的用户名
     * @param: []
     * @return: java.lang.String
     * @date: 2026/4/10 14:02
     * @author: yhf
     **/
    public static String getUserHomePath() {
        return System.getProperty("user.home");
    }

    /**
     * @description: 获取【项目根目录】
     *               当前项目所在的文件夹路径,就是打开IDEA时最外层的项目文件夹,即src的父级目录
     * @param: []
     * @return: java.lang.String
     * @date: 2026/4/10 11:25
     * @author: yhf
     **/
    public static String getProjectPath() {
        return System.getProperty("user.dir");
    }

    //==================================================================================================================

    /**
     * 判断字符串中是否包含中文
     */
    private static boolean containsChinese(String str) {
        if (str == null || str.isEmpty()) {
            return false;
        }
        // 正则:匹配中文字符范围
        return str.matches(".*[\\u4e00-\\u9fa5].*");
    }

    /**
     * 判断路径是否被 URL 编码(含 %xx)
     */
    private static boolean hasUrlEncoded(String path) {
        if (path == null || path.isEmpty()) {
            return false;
        }
        //有 %20、%E4、%B8 这类 → 返回 true → 必须解码
        return path != null && path.matches(".*%[0-9A-Fa-f]{2}.*");
    }

    /**
     * 自动解码中文路径,解码解决"中文乱码"问题
     */
    private static String decodePath(String path) {
        if (hasUrlEncoded(path)) {
            try {
                path = URLDecoder.decode(path, StandardCharsets.UTF_8.name());
            } catch (Exception ignored) {}
        }
        return path;
    }

    /**
     * 判断当前是否为windows环境
     */
    private static boolean isWindows() {
        return System.getProperty("os.name").toLowerCase().contains("win");
    }

    /**
     * 获取HttpServletRequest
     */
    private static HttpServletRequest getRequest() {
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            return attributes == null ? null : attributes.getRequest();
        } catch (Exception e) {
            return null;
        }
    }

    /*
    1.在 resources 下放了一个文件:
    src/main/resources/application.yml
    2.运行时,它的真实路径是:
    classpath:/application.yml
    3.用工具类获取:
    String path = getClassPath() + "application.yml";

    src/main/java + resources
    编译后 → classes(classpath)            获取用:getClassPath()

    src/main/webapp
    编译后 → Tomcat/webapps/项目名/          获取用:getWebRootPath()

    src/main/webapp/WEB-INF
    编译后 → Tomcat/webapps/项目名/WEB-INF   获取用:getWebInfPath()

    在【传统war部署 + Tomcat】环境下,三者完全等价!
    getWebRootPath() + "WEB-INF" + File.separator + "classes"
    等价于getWebInfPath() + "classes"
    等价于getClassPath()

    Web根目录 (getWebRootPath())
        └── WEB-INF/ (getWebInfPath())
            └── classes/ (getClassPath())

    在【SpringBoot jar包运行】环境下:前两个会报错/失效,只有getClassPath()永远有效!
     */

}

升级版

package com.itmc.education.common.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.servlet.ServletContext;
import java.io.File;

/**
 * @Author: yhf
 * @Date: 2026/7/22 9:43
 */
@Component
@Order(1) // 数字越小,越先初始化
public class WebRootPathHolder {

    public static String WEB_ROOT_PATH;

    // Spring自动注入ServletContext,不用request
    @Autowired
    private ServletContext servletContext;

    // Bean初始化完成后赋值,只执行一次
    @PostConstruct
    public void init() {
        // 拿到 /webapps/项目名 物理路径
        WEB_ROOT_PATH = servletContext.getRealPath("");
    }

    public static File getChildDir(String child) {
        File childDir = new File(WEB_ROOT_PATH, child);
        if (!childDir.exists()) {
            childDir.mkdirs();
        }
        return childDir;
    }

}
package com.itmc.education.common.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ClassUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

/**
 * @Author: yhf
 * @Date: 2026/4/10 11:20
 */
public final class PathKit {

    private PathKit() {
        // 工具类,私有构造禁止实例化
    }

    private static final Logger log = LoggerFactory.getLogger(PathKit.class);

    // 系统分隔符(Windows \,Linux /,自动适配)
    private static final String SEPARATOR = File.separator;

    //==================================================================================================================

    /**
     * @description: 获取【classpath根目录,即classes文件夹】
     *
     *               开发时(IDEA):src/main/java + src/main/resources
     *               编译后(IDEA):项目路径/target/classes/
     *               部署后(Tomcat):Tomcat路径/webapps/项目名/WEB-INF/classes/
     *               开发时源码java+resources = 编译后的target/classes = 部署后的WEB-INF/classes
     * @param: []
     * @return: java.lang.String
     * @date: 2026/4/10 11:24
     * @author: yhf
     **/
    public static String getClassPath() {
        String path = "";
        try {
            // 获取类加载器根路径URL
            URL resource = ClassUtils.getDefaultClassLoader().getResource("");
            if (resource == null) {
//                throw new RuntimeException("未获取到类路径资源");
                return getProjectPath() + SEPARATOR + "target" + SEPARATOR + "classes";
            }

            path = resource.getPath();
            path = decodePath(path);

            // 处理Windows路径(去掉开头/,/D:/xxx → D:/xxx)
            if (isWindows() && path.startsWith("/")) {
                path = path.substring(1);
            }
        } catch (Exception e) {
//            throw new RuntimeException("获取类路径失败", e);
            log.error("获取类路径失败:" + e.getMessage());
            path = "";
        } finally {
            return path;
        }
    }

    /**
     * @description: 获取【classpath:/static 静态资源目录】
     * @param: []
     * @return: java.lang.String
     * @date: 2026/4/10 13:57
     * @author: yhf
     **/
    public static String getStaticPath() {
        return getClassPath() + "static" + SEPARATOR;
    }

    /**
     * @description: 获取【classpath:/templates 模板目录】
     * @param: []
     * @return: java.lang.String
     * @date: 2026/4/10 13:57
     * @author: yhf
     **/
    public static String getTemplatesPath() {
        return getClassPath() + "templates" + SEPARATOR;
    }

    /**
     * @description: 获取【Web应用根目录(webapp),即web访问路径】
     *
     *               当前Web项目在服务器上的真实物理磁盘路径(绝对路径),也就是项目部署到Tomcat后,对应文件夹在硬盘上的实际位置
     *               IDEA开发目录的src/main/webapp,编译后=web根目录,即Tomcat路径/webapp/项目名/,编译后WEB-INF也会完整转移到这里面
     *               Service直接调用getWebRootPath()会报错NPE,因为方法依赖HttpServletRequest,只能在Controller里调用
     * @param: []
     * @return: java.lang.String
     * @date: 2026/4/10 11:25
     * @author: yhf
     **/
    public static String getWebRootPath(HttpServletRequest request) {
        if (request == null) {
            log.info("非 Web 环境,无法获取 Web 根路径,尝试获取 WEB_ROOT_PATH");
            String root = WebRootPathHolder.WEB_ROOT_PATH;
            if (root == null) {
                throw new RuntimeException("非Web环境,且WEB_ROOT_PATH未初始化");
            }
            return decodePath(root);
        }
        String path = request.getServletContext().getRealPath("");
        return decodePath(path);
    }
    public static String getWebRootPath() {
        return getWebRootPath(null);
    }

    /**
     * @description: 获取【WEB-INF目录】
     * @param: []
     * @return: java.lang.String
     * @date: 2026/4/10 11:25
     * @author: yhf
     **/
    public static String getWebInfPath(HttpServletRequest request) {
        return getWebRootPath(request) + "WEB-INF" + SEPARATOR;
    }
    public static String getWebInfPath() {
        return getWebRootPath() + "WEB-INF" + SEPARATOR;
    }

    /**
     * @description: 获取【系统临时文件目录】
     *               操作系统专门用来存临时文件的目录,重启电脑可能会清空
     *               Windows  -->  C:\Users\你的用户名\AppData\Local\Temp
     *               Linux    -->  /tmp
     * @param: []
     * @return: java.lang.String
     * @date: 2026/4/10 14:01
     * @author: yhf
     **/
    public static String getTempPath() {
        return System.getProperty("java.io.tmpdir");
    }

    /**
     * @description: 获取【用户目录】
     *               当前电脑用户目录,即登录操作系统的用户文件夹
     *               Windows  -->  C:\Users\你的用户名
     *               Linux    -->  /home/你的用户名
     * @param: []
     * @return: java.lang.String
     * @date: 2026/4/10 14:02
     * @author: yhf
     **/
    public static String getUserHomePath() {
        return System.getProperty("user.home");
    }

    /**
     * @description: 获取【项目根目录】
     *               当前项目所在的文件夹路径,就是打开IDEA时最外层的项目文件夹,即src的父级目录
     * @param: []
     * @return: java.lang.String
     * @date: 2026/4/10 11:25
     * @author: yhf
     **/
    public static String getProjectPath() {
        return System.getProperty("user.dir");
    }

    //==================================================================================================================

    /**
     * 判断字符串中是否包含中文
     */
    private static boolean containsChinese(String str) {
        if (str == null || str.isEmpty()) {
            return false;
        }
        // 正则:匹配中文字符范围
        return str.matches(".*[\\u4e00-\\u9fa5].*");
    }

    /**
     * 判断路径是否被 URL 编码(含 %xx)
     */
    private static boolean hasUrlEncoded(String path) {
        if (path == null || path.isEmpty()) {
            return false;
        }
        //有 %20、%E4、%B8 这类 → 返回 true → 必须解码
        return path != null && path.matches(".*%[0-9A-Fa-f]{2}.*");
    }

    /**
     * 自动解码中文路径,解码解决"中文乱码"问题
     */
    private static String decodePath(String path) {
        if (hasUrlEncoded(path)) {
            try {
                path = URLDecoder.decode(path, StandardCharsets.UTF_8.name());
            } catch (Exception ignored) {}
        }
        return path;
    }

    /**
     * 判断当前是否为windows环境
     */
    private static boolean isWindows() {
        return System.getProperty("os.name").toLowerCase().contains("win");
    }

    /**
     * 获取HttpServletRequest
     */
    public static HttpServletRequest getRequest() {
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            return attributes == null ? null : attributes.getRequest();
        } catch (Exception e) {
            return null;
        }
    }

    /*
    1.在 resources 下放了一个文件:
    src/main/resources/application.yml
    2.运行时,它的真实路径是:
    classpath:/application.yml
    3.用工具类获取:
    String path = getClassPath() + "application.yml";

    src/main/java + resources
    编译后 → classes(classpath)            获取用:getClassPath()

    src/main/webapp
    编译后 → Tomcat/webapps/项目名/          获取用:getWebRootPath()

    src/main/webapp/WEB-INF
    编译后 → Tomcat/webapps/项目名/WEB-INF   获取用:getWebInfPath()

    在【传统war部署 + Tomcat】环境下,三者完全等价!
    getWebRootPath() + "WEB-INF" + File.separator + "classes"
    等价于getWebInfPath() + "classes"
    等价于getClassPath()

    Web根目录 (getWebRootPath())
        └── WEB-INF/ (getWebInfPath())
            └── classes/ (getClassPath())

    在【SpringBoot jar包运行】环境下:前两个会报错/失效,只有getClassPath()永远有效!
     */

}
posted @ 2026-04-10 16:18  yub4by  阅读(15)  评论(0)    收藏  举报