• springboot获取不到客户端ip问题排查


    一、现象

    springboot从2.0.2升级到 2.5.7后线上环境无法通过request.getHeader("x-forwarded-for")获取客户端ip地址,测试环境正常,开发环境也异常

    二、结论

    springboot 2.5.7版本中CloudPlatform多了Kubernetes platform的类型识别,如果使用的是内嵌的tomcat,在k8s环境中会自动添加了tomcat的RemoteIpValve,线上环境的httpHeader(x-forwarded-for)只有一个,没有代理ip信息,按RemoteIpValve的逻辑,x-forwarded-for头信息会被删除。

    三、排查流程

    1、抓包看请求与现象

    dev环境抓包

    可以看到抓包中有x-forwarded-for,但是一个内网ip

    2、本地调试

    使用develop分支本地模拟dev环境请求,debug发现可以获取到ip地址

    3、搜索springboot丢失x-forwarded-for的原因

    搜索到文章loveyu.org/5951.html

    看到了删除header的调用 removeHeader方法,但没有写是哪个类,搜索代码发现是内嵌tomcat的RemoteIpValve类中调用,大致看了下逻辑其中有删除header x-forwarded-for的代码,但打断点debug发现不会走到RemoteIpValve中

    4、查看RemoteIpValve的执行逻辑

    查看上面文章提到的配置:server.forward-headers-strategy

    通过搜索发现,是在spring的配置类org.springframework.boot.autoconfigure.web.ServerProperties中

    1.  /**
    2.   * Strategy for handling X-Forwarded-* headers.
    3.   */
    4.  private ForwardHeadersStrategy forwardHeadersStrategy;
    5. 复制代码

    get方法会在TomcatWebServerFactoryCustomizer类的getOrDeduceUseForwardHeaders方法中调用

    1.  private void customizeRemoteIpValve(ConfigurableTomcatWebServerFactory factory) {
    2.     Remoteip remoteIpProperties = this.serverProperties.getTomcat().getRemoteip();
    3.     String protocolHeader = remoteIpProperties.getProtocolHeader();
    4.     String remoteIpHeader = remoteIpProperties.getRemoteIpHeader();
    5.     // For back compatibility the valve is also enabled if protocol-header is set
    6.     if (StringUtils.hasText(protocolHeader) || StringUtils.hasText(remoteIpHeader)
    7.           || getOrDeduceUseForwardHeaders()) {
    8.       //通过配置添加RemoteIpValve
    9.       RemoteIpValve valve = new RemoteIpValve();
    10.       valve.setProtocolHeader(StringUtils.hasLength(protocolHeader) ? protocolHeader : "X-Forwarded-Proto");
    11.       if (StringUtils.hasLength(remoteIpHeader)) {
    12.           valve.setRemoteIpHeader(remoteIpHeader);
    13.       }
    14.       // The internal proxies default to a list of "safe" internal IP addresses
    15.       valve.setInternalProxies(remoteIpProperties.getInternalProxies());
    16.       try {
    17.           valve.setHostHeader(remoteIpProperties.getHostHeader());
    18.       }
    19.       catch (NoSuchMethodError ex) {
    20.           // Avoid failure with war deployments to Tomcat 8.5 before 8.5.44 and
    21.           // Tomcat 9 before 9.0.23
    22.       }
    23.       valve.setPortHeader(remoteIpProperties.getPortHeader());
    24.       valve.setProtocolHeaderHttpsValue(remoteIpProperties.getProtocolHeaderHttpsValue());
    25.       // ... so it's safe to add this valve by default.
    26.       factory.addEngineValves(valve);
    27.     }
    28.  }
    29.  
    30.  private boolean getOrDeduceUseForwardHeaders() {
    31.     if (this.serverProperties.getForwardHeadersStrategy() == null) {
    32.       CloudPlatform platform = CloudPlatform.getActive(this.environment);
    33.       return platform != null && platform.isUsingForwardHeaders();
    34.     }
    35.     return this.serverProperties.getForwardHeadersStrategy().equals(ServerProperties.ForwardHeadersStrategy.NATIVE);
    36.  }
    37. 复制代码

    getOrDeduceUseForwardHeaders方法逻辑

    1、如果没有配置forwardHeadersStrategy则判断目前的环境

    org.springframework.boot.cloud.CloudPlatform#getActive

    1.  public static CloudPlatform getActive(Environment environment) {
    2.     if (environment != null) {
    3.       for (CloudPlatform cloudPlatform : values()) {
    4.           if (cloudPlatform.isActive(environment)) {
    5.             return cloudPlatform;
    6.           }
    7.       }
    8.     }
    9.     return null;
    10.  }
    11. 复制代码

    CloudPlatform枚举类中可以看到比之前的springboot版本多了KUBERNETES的枚举,也就是在k8s环境CloudPlatform.getActive(this.environment)返回的不为空,isUsingForwardHeaders返回也为true

    1.  public boolean isUsingForwardHeaders() {
    2.     return true;
    3.  }
    4. 复制代码

    2、如果配置了则判断是否为NATIVE

    新版本通过k8s环境判断getOrDeduceUseForwardHeaders方法返回true

    getOrDeduceUseForwardHeaders返回为true,在customizeRemoteIpValve方法中就会添加RemoteIpValve

    5、为什么测试环境没事,dev和线上都有问题

    通过arthus查看dev和测试环境的调用栈,发现调用栈不同

    dev调用栈

    1.  `---ts=2022-11-14 20:45:50;thread_name=http-nio-8080-exec-1;id=5b;is_daemon=true;priority=5;TCCL=org.springframework.boot.loader.LaunchedURLClassLoader@3d24753a
    2.     `---[1.786075ms] org.apache.catalina.valves.RemoteIpValve:invoke()
    3.         +---[0.54% 0.009643ms ] org.apache.catalina.connector.Request:getRemoteAddr() #613
    4.         +---[0.17% 0.002994ms ] org.apache.catalina.connector.Request:getRemoteHost() #614
    5.         +---[0.19% 0.003369ms ] org.apache.catalina.connector.Request:getScheme() #615
    6.         +---[0.15% 0.002714ms ] org.apache.catalina.connector.Request:isSecure() #616
    7.         +---[0.17% 0.003068ms ] org.apache.catalina.connector.Request:getServerName() #617
    8.         +---[0.15% 0.002709ms ] org.apache.catalina.valves.RemoteIpValve:isChangeLocalName() #618
    9.         +---[0.16% 0.002895ms ] org.apache.catalina.connector.Request:getServerPort() #619
    10.         +---[0.22% 0.003994ms ] org.apache.catalina.connector.Request:getLocalPort() #620
    11.         +---[0.18% 0.00325ms ] org.apache.catalina.connector.Request:getHeader() #621
    12.         +---[0.16% 0.002793ms ] org.apache.catalina.connector.Request:getHeader() #622
    13.         +---[0.21% 0.00372ms ] org.apache.catalina.connector.Request:getHeaders() #632
    14.         +---[0.18% 0.003182ms ] org.apache.catalina.valves.RemoteIpValve:commaDelimitedListToStringArray() #640
    15.         +---[0.14% 0.002488ms ] org.apache.catalina.connector.Request:getHeader() #700
    16.         +---[0.13% 0.002356ms ] org.apache.catalina.connector.Request:getHeader() #716
    17.         +---[0.28% 0.004932ms ] org.apache.catalina.connector.Request:setAttribute() #736
    18.         +---[0.14% 0.002555ms ] org.apache.juli.logging.Log:isDebugEnabled() #738
    19.         +---[0.10% 0.001796ms ] org.apache.catalina.connector.Request:getRemoteAddr() #756
    20.         +---[0.15% 0.002711ms ] org.apache.catalina.connector.Request:setAttribute() #755
    21.         +---[0.13% 0.002321ms ] org.apache.catalina.connector.Request:getRemoteAddr() #758
    22.         +---[0.12% 0.002186ms ] org.apache.catalina.connector.Request:setAttribute() #757
    23.         +---[0.11% 0.001972ms ] org.apache.catalina.connector.Request:getRemoteHost() #760
    24.         +---[0.12% 0.002058ms ] org.apache.catalina.connector.Request:setAttribute() #759
    25.         +---[0.19% 0.003334ms ] org.apache.catalina.connector.Request:getProtocol() #762
    26.         +---[0.12% 0.00218ms ] org.apache.catalina.connector.Request:setAttribute() #761
    27.         +---[0.12% 0.002192ms ] org.apache.catalina.connector.Request:getServerName() #764
    28.         +---[0.12% 0.00212ms ] org.apache.catalina.connector.Request:setAttribute() #763
    29.         +---[0.11% 0.002021ms ] org.apache.catalina.connector.Request:getServerPort() #766
    30.         +---[0.14% 0.002423ms ] org.apache.catalina.connector.Request:setAttribute() #765
    31.         +---[0.39% 0.007047ms ] org.apache.catalina.valves.RemoteIpValve:getNext() #769
    32.         +---[79.19% 1.414379ms ] org.apache.catalina.Valve:invoke() #769
    33.         +---[0.23% 0.00409ms ] org.apache.catalina.connector.Request:setRemoteAddr() #771
    34.         +---[0.16% 0.002866ms ] org.apache.catalina.connector.Request:setRemoteHost() #772
    35.         +---[0.15% 0.002725ms ] org.apache.catalina.connector.Request:setSecure() #773
    36.         +---[0.20% 0.00363ms ] org.apache.catalina.connector.Request:getCoyoteRequest() #774
    37.         +---[0.19% 0.003448ms ] org.apache.coyote.Request:scheme() #774
    38.         +---[0.14% 0.002545ms ] org.apache.tomcat.util.buf.MessageBytes:setString() #774
    39.         +---[0.16% 0.002889ms ] org.apache.catalina.connector.Request:getCoyoteRequest() #775
    40.         +---[0.19% 0.00342ms ] org.apache.coyote.Request:serverName() #775
    41.         +---[0.17% 0.00297ms ] org.apache.tomcat.util.buf.MessageBytes:setString() #775
    42.         +---[0.22% 0.003904ms ] org.apache.catalina.valves.RemoteIpValve:isChangeLocalName() #776
    43.         +---[0.20% 0.00363ms ] org.apache.catalina.connector.Request:setServerPort() #779
    44.         +---[0.17% 0.003065ms ] org.apache.catalina.connector.Request:setLocalPort() #780
    45.         +---[0.16% 0.002877ms ] org.apache.catalina.connector.Request:getCoyoteRequest() #782
    46.         +---[0.18% 0.003204ms ] org.apache.coyote.Request:getMimeHeaders() #782
    47.         +---[0.22% 0.003937ms ] org.apache.tomcat.util.http.MimeHeaders:removeHeader() #784
    48.         `---[0.17% 0.003077ms ] org.apache.tomcat.util.http.MimeHeaders:removeHeader() #790
    49. 复制代码

    测试环境调用栈

    1.  `---ts=2022-11-14 21:02:40;thread_name=http-nio-8080-exec-5;id=aa;is_daemon=true;priority=5;TCCL=org.springframework.boot.loader.LaunchedURLClassLoader@42f85fa4
    2.     `---[100.85578ms] org.apache.catalina.valves.RemoteIpValve:invoke()
    3.         +---[0.02% 0.016252ms ] org.apache.catalina.connector.Request:getRemoteAddr() #613
    4.         +---[0.00% 0.002572ms ] org.apache.catalina.connector.Request:getRemoteHost() #614
    5.         +---[0.00% 0.001721ms ] org.apache.catalina.connector.Request:getScheme() #615
    6.         +---[0.00% 0.001826ms ] org.apache.catalina.connector.Request:isSecure() #616
    7.         +---[0.00% 0.001719ms ] org.apache.catalina.connector.Request:getServerName() #617
    8.         +---[0.00% 0.002139ms ] org.apache.catalina.valves.RemoteIpValve:isChangeLocalName() #618
    9.         +---[0.00% 0.004452ms ] org.apache.catalina.connector.Request:getServerPort() #619
    10.         +---[0.00% 0.002919ms ] org.apache.catalina.connector.Request:getLocalPort() #620
    11.         +---[0.00% 0.001888ms ] org.apache.catalina.connector.Request:getHeader() #621
    12.         +---[0.00% 0.003649ms ] org.apache.catalina.connector.Request:getHeader() #622
    13.         +---[0.00% 0.0024ms ] org.apache.catalina.connector.Request:getHeaders() #632
    14.         +---[0.01% 0.011289ms ] org.apache.catalina.valves.RemoteIpValve:commaDelimitedListToStringArray() #640
    15.         +---[0.00% 0.001824ms ] org.apache.catalina.connector.Request:setRemoteAddr() #667
    16.         +---[0.00% 0.001824ms ] org.apache.catalina.connector.Request:getConnector() #668
    17.         +---[0.00% 0.002051ms ] org.apache.catalina.connector.Connector:getEnableLookups() #668
    18.         +---[0.00% 0.001576ms ] org.apache.catalina.connector.Request:setRemoteHost() #682
    19.         +---[0.00% 0.001515ms ] org.apache.catalina.connector.Request:getCoyoteRequest() #686
    20.         +---[0.00% 0.00188ms ] org.apache.coyote.Request:getMimeHeaders() #686
    21.         +---[0.00% 0.001862ms ] org.apache.tomcat.util.http.MimeHeaders:removeHeader() #686
    22.         +---[0.00% 0.004026ms ] org.apache.tomcat.util.buf.StringUtils:join() #694
    23.         +---[0.00% 0.001333ms ] org.apache.catalina.connector.Request:getCoyoteRequest() #695
    24.         +---[0.00% 0.001517ms ] org.apache.coyote.Request:getMimeHeaders() #695
    25.         +---[0.00% 0.001971ms ] org.apache.tomcat.util.http.MimeHeaders:setValue() #695
    26.         +---[0.00% 0.001817ms ] org.apache.tomcat.util.buf.MessageBytes:setString() #695
    27.         +---[0.00% 0.00168ms ] org.apache.catalina.connector.Request:getHeader() #700
    28.         +---[0.00% 0.003129ms ] org.apache.catalina.valves.RemoteIpValve:isForwardedProtoHeaderValueSecure() #704
    29.         +---[0.00% 0.001654ms ] org.apache.catalina.connector.Request:setSecure() #709
    30.         +---[0.00% 0.001344ms ] org.apache.catalina.connector.Request:getCoyoteRequest() #710
    31.         +---[0.00% 0.002058ms ] org.apache.coyote.Request:scheme() #710
    32.         +---[0.00% 0.001186ms ] org.apache.tomcat.util.buf.MessageBytes:setString() #710
    33.         +---[0.00% 0.002904ms ] org.apache.catalina.valves.RemoteIpValve:setPorts() #711
    34.         +---[0.00% 0.001491ms ] org.apache.catalina.connector.Request:getHeader() #716
    35.         +---[0.00% 0.003306ms ] org.apache.tomcat.util.http.parser.Host:parse() #719
    36.         +---[0.00% 0.001296ms ] org.apache.catalina.connector.Request:getCoyoteRequest() #725
    37.         +---[0.00% 0.001337ms ] org.apache.coyote.Request:serverName() #725
    38.         +---[0.00% 0.001271ms ] org.apache.tomcat.util.buf.MessageBytes:setString() #725
    39.         +---[0.00% 0.001253ms ] org.apache.catalina.valves.RemoteIpValve:isChangeLocalName() #726
    40.         +---[0.00% 0.003314ms ] org.apache.catalina.connector.Request:setAttribute() #736
    41.         +---[0.02% 0.015683ms ] org.apache.juli.logging.Log:isDebugEnabled() #738
    42.         +---[0.00% 0.00135ms ] org.apache.catalina.connector.Request:getRemoteAddr() #756
    43.         +---[0.00% 0.001719ms ] org.apache.catalina.connector.Request:setAttribute() #755
    44.         +---[0.00% 0.001249ms ] org.apache.catalina.connector.Request:getRemoteAddr() #758
    45.         +---[0.00% 0.001294ms ] org.apache.catalina.connector.Request:setAttribute() #757
    46.         +---[0.00% 0.00128ms ] org.apache.catalina.connector.Request:getRemoteHost() #760
    47.         +---[0.00% 0.001485ms ] org.apache.catalina.connector.Request:setAttribute() #759
    48.         +---[0.00% 0.002571ms ] org.apache.catalina.connector.Request:getProtocol() #762
    49.         +---[0.00% 0.001857ms ] org.apache.catalina.connector.Request:setAttribute() #761
    50.         +---[0.01% 0.008126ms ] org.apache.catalina.connector.Request:getServerName() #764
    51.         +---[0.00% 0.001972ms ] org.apache.catalina.connector.Request:setAttribute() #763
    52.         +---[0.00% 0.001452ms ] org.apache.catalina.connector.Request:getServerPort() #766
    53.         +---[0.00% 0.001782ms ] org.apache.catalina.connector.Request:setAttribute() #765
    54.         +---[0.00% 0.001442ms ] org.apache.catalina.valves.RemoteIpValve:getNext() #769
    55.         +---[99.65% 100.500217ms ] org.apache.catalina.Valve:invoke() #769
    56.         +---[0.00% 0.002653ms ] org.apache.catalina.connector.Request:setRemoteAddr() #771
    57.         +---[0.00% 0.001491ms ] org.apache.catalina.connector.Request:setRemoteHost() #772
    58.         +---[0.00% 0.00171ms ] org.apache.catalina.connector.Request:setSecure() #773
    59.         +---[0.00% 0.001662ms ] org.apache.catalina.connector.Request:getCoyoteRequest() #774
    60.         +---[0.00% 0.00229ms ] org.apache.coyote.Request:scheme() #774
    61.         +---[0.00% 0.001822ms ] org.apache.tomcat.util.buf.MessageBytes:setString() #774
    62.         +---[0.00% 0.00142ms ] org.apache.catalina.connector.Request:getCoyoteRequest() #775
    63.         +---[0.00% 0.002272ms ] org.apache.coyote.Request:serverName() #775
    64.         +---[0.00% 0.001255ms ] org.apache.tomcat.util.buf.MessageBytes:setString() #775
    65.         +---[0.00% 0.002824ms ] org.apache.catalina.valves.RemoteIpValve:isChangeLocalName() #776
    66.         +---[0.00% 0.001433ms ] org.apache.catalina.connector.Request:setServerPort() #779
    67.         +---[0.00% 0.001558ms ] org.apache.catalina.connector.Request:setLocalPort() #780
    68.         +---[0.00% 0.001666ms ] org.apache.catalina.connector.Request:getCoyoteRequest() #782
    69.         +---[0.00% 0.001526ms ] org.apache.coyote.Request:getMimeHeaders() #782
    70.         +---[0.00% 0.002167ms ] org.apache.tomcat.util.http.MimeHeaders:removeHeader() #784
    71.         +---[0.00% 0.002147ms ] org.apache.tomcat.util.http.MimeHeaders:setValue() #792
    72.         `---[0.00% 0.001765ms ] org.apache.tomcat.util.buf.MessageBytes:setString() #792
    73. 复制代码

    对比代码发现dev环境的确执行了删除header的操作

    简单解释X-Forwarded-For的作用

    例如真正的客户端是Client1,通过代理服务器proxy1,proxy2,到达服务器,在Tomcat中执行获取客户端地址的方法:request.getRemoteAddr,获得的IP地址是proxy2的,也就是负载均衡的地址; 而如果你想要获取Client1的地址,也是可以获取到的,就是通过X-Forwarded-For字段; X-Forwarded-For:简称XFF头,它代表客户端,只有在通过了HTTP 代理或者负载均衡服务器时才会添加该项。 X-Forwarded-For内置在Http协议头中,刚刚的场景X-Forwarded-For取值为:client1, proxy1, proxy2

    仔细探究RemoteIpValve代码逻辑 和目前问题相关的大致功能为:解析X-Forwarded-for请求头,将其中的远端地址设置到RemoteAddr中,判断是否要删除X-Forwarded-for请求头 具体逻辑为: 对于列表中的每个ip,如果属于内网地址则跳过,否则将此ip设置为远程ip,停止循环

    1.  //最近一跳代理地址
    2.  final String originalRemoteAddr = request.getRemoteAddr();
    3.  ...
    4.  //X-Forwarded-For头信息
    5.  final String originalRemoteIpHeader = request.getHeader(remoteIpHeader);
    6.  //最近一跳代理地址是否为内网
    7.  // 内网正则判断为 Pattern internalProxies = Pattern.compile(
    8.          "10\.\d{1,3}\.\d{1,3}\.\d{1,3}|" +
    9.          "192\.168\.\d{1,3}\.\d{1,3}|" +
    10.          "169\.254\.\d{1,3}\.\d{1,3}|" +
    11.          "127\.\d{1,3}\.\d{1,3}\.\d{1,3}|" +
    12.          "172\.1[6-9]{1}\.\d{1,3}\.\d{1,3}|" +
    13.          "172\.2[0-9]{1}\.\d{1,3}\.\d{1,3}|" +
    14.          "172\.3[0-1]{1}\.\d{1,3}\.\d{1,3}|" +
    15.          "0:0:0:0:0:0:0:1|::1");
    16.  //originalRemoteAddr 所有环境都为内网地址,返回true
    17.  boolean isInternal = internalProxies != null &&
    18.          internalProxies.matcher(originalRemoteAddr).matches();
    19.  //trustedProxies为空
    20.  if (isInternal || (trustedProxies != null &&
    21.          trustedProxies.matcher(originalRemoteAddr).matches())) {
    22.      String remoteIp = null;
    23.      Deque<String> proxiesHeaderValue = new LinkedList<>();
    24.      StringBuilder concatRemoteIpHeaderValue = new StringBuilder();
    25.  
    26.      for (Enumeration<String> e = request.getHeaders(remoteIpHeader); e.hasMoreElements();) {
    27.          if (concatRemoteIpHeaderValue.length() > 0) {
    28.              concatRemoteIpHeaderValue.append(", ");
    29.         }
    30.  
    31.          concatRemoteIpHeaderValue.append(e.nextElement());
    32.     }
    33.      //X-Forwarded-For内的地址转换为数组
    34.      String[] remoteIpHeaderValue = commaDelimitedListToStringArray(concatRemoteIpHeaderValue.toString());
    35.      int idx;
    36.      if (!isInternal) {
    37.          proxiesHeaderValue.addFirst(originalRemoteAddr);
    38.     }
    39.      //从最后的地址循环
    40.      for (idx = remoteIpHeaderValue.length - 1; idx >= 0; idx--) {
    41.          String currentRemoteIp = remoteIpHeaderValue[idx];
    42.          remoteIp = currentRemoteIp;
    43.          //如果是内网地址则跳过
    44.          if (internalProxies !=null && internalProxies.matcher(currentRemoteIp).matches()) {
    45.              
    46.          //trustedProxies目前配置为空
    47.         } else if (trustedProxies != null &&
    48.                  trustedProxies.matcher(currentRemoteIp).matches()) {
    49.              proxiesHeaderValue.addFirst(currentRemoteIp);
    50.         } else {
    51.          //找到第一个不是内网地址的idx,但如果只有一个地址,则idx会变成负数
    52.              idx--;
    53.              break;
    54.         }
    55.     }
    56.      //重新构建客户端地址的list,但idx必须不小于0
    57.      LinkedList<String> newRemoteIpHeaderValue = new LinkedList<>();
    58.      for (; idx >= 0; idx--) {
    59.          String currentRemoteIp = remoteIpHeaderValue[idx];
    60.          newRemoteIpHeaderValue.addFirst(currentRemoteIp);
    61.     }
    62.      
    63.  ...
    64.      //如果newRemoteIpHeaderValue为空则删除X-Forwarded-For
    65.      if (newRemoteIpHeaderValue.size() == 0) {
    66.           request.getCoyoteRequest().getMimeHeaders().removeHeader(remoteIpHeader);
    67.     }
    68. 复制代码

    总结一下逻辑

    1.  X-Forwarded-For中的地址集合从后往前取,在至少有两个地址并且最后的地址是内网地址的情况下不会删除X-Forwarded-For请求头,如果只有一个地址无论是不是内网地址都会删除这个请求头
    2. 复制代码

    看一下dev、test、线上的数据

    1.  //test环境 172.31.0.203为内网地址
    2.  X-Forwarded-For: 204.187.160.86, 100.118.125.137, 172.31.0.203
    3.  //dev环境172.30.1.118为内网地址
    4.  X-Forwarded-For: 172.30.1.118
    5.  //线上环境 117.136.68.19为外网地址,但只有一个ip,没有代理的地址
    6.  X-Forwarded-For: 117.136.68.19
    7. 复制代码

    所以dev和线上会被删去请求头

    处理

    在wb-base-component-starter中添加公共配置

    1.  server:
    2.     forward-headers-strategy: none
    3. 复制代码

    不加载tomcat的RemoteIpValve

     

  • 相关阅读:
    webpack学习记录
    使用GD32F207的高级定时器来产生PWM波出现的隐藏BUG
    Spring Boot 整合Hibernate Validator
    python提取文件中文件的名称,并导入到Excel中
    HTML元素大全(1)
    某设计院后备人才管理体系建设项目成功案例纪实
    烽火HG680-KA_310_免费升级刷机固件包及教程
    JVM P3 垃圾回收,垃圾回收器
    字符串统计
    PCL可视化只有顶点彩色信息的OBJ文件
  • 原文地址:https://blog.csdn.net/m0_71777195/article/details/127978440