JavaServerPages篇04 —— Filter-Listener-在线人数统计

欢迎来到我的主页:【一只认真写代码的程序猿

本篇文章收录于专栏【JSP相关

如果这篇文章对你有帮助,希望点赞收藏加关注啦~

 本文所有内容相关代码都可在以下仓库中找到:

https://github.com/Echo-Nie/JSPLearn


1 过滤器Filter

1.1 Filter机理

Filter,用于在 Servlet之外对 Request 或者 Response 进行修改。用于对用户请求进行预处理,也可以对 HttpServletResponse 进行后处理。

Filter完整流程: Filter 对用户请求进行预处理,接着将请求交给 Serviet进行处理并生成响应,最后 Filter 再 对服务器响应进行后处理。在一个 web 应用中,可以开发编写多个 Filter,这些 Filter 组合 起来称之为一个 Filter 链。

大白话:Filter 作为快递站点的安检环节。包裹(用户请求)到达站点后,先要经过安检(Filter 预处理),检查是否有违禁品等。安检通过后,包裹才会被送到仓库(Servlet 处理请求)。

包裹从仓库发出后,再次经过安检(Filter 后处理),这次检查包装是否完好等,确保送到客户手中的包裹是合格的。多个安检环节就构成了安检链,类似于多个 Filter 组成的 Filter 链。

对于多个过滤器:先配置的先执行(请求时的执行顺序);响应的时候顺序相反。如下图:

在 HttpServletRequest 到达 Servlet 之前,拦截客户的 HttpServletRequest。根据需要检查HttpServletRequest,也可以修改 HttpServletRequest 头和数据。

在HttpServletResponse 到达客户端之前,拦截 HttpServletResponse。根据需要检查HttpServletResponse,也可以修改 HttpServletResponse头和数据。

1.2 FilterTest实现

通过实现一个叫做javax.servlet.Fileter的接口来实现一个过滤器,其中定义了 三个方法,init()、doFilter()、destroy()分别在相应的时机执行。后期观察生命周期。

Step1:编写 java 类实现 Filter接口,并实现其 doFiter 方法, Step2:通过@WebFilter注解设置它所能拦截的资源。

package com.ynu.edu.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
 * @ClassName FilterTest1
 * @Description 过滤器测试
 * @Author Echo-Nie
 * @Date 2025/1/17 21:48
 * @Version V1.0
 */
@WebFilter("/ser01")
public class FilterTest1 implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("FilterTest1 init...");
    }
    
    /**
    * @Author Echo-Nie
    * @Description 过滤方法
    * @Date 21:51 2025/1/17
    * @Param [servletRequest, servletResponse, filterChain]
    * @return void
    **/
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //放行资源
        filterChain.doFilter(servletRequest,servletResponse);
        //如果不放行的话这里就一直被拦截,永远不会进入Servlet01
    }

    @Override
    public void destroy() {
        System.out.println("FilterTest1 destroy...");
    }
}
package com.ynu.edu.servlet;
​
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
​
/**
 * @ClassName Servlet01
 * @Description
 * @Author Echo-Nie
 * @Date 2025/1/17 21:52
 * @Version V1.0
 */
@WebServlet("/ser01")
public class Servlet01 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Servlet01 start...");
    }
}
package com.ynu.edu.servlet;
​
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
​
/**
 * @ClassName Servlet01
 * @Description
 * @Author Echo-Nie
 * @Date 2025/1/17 21:52
 * @Version V1.0
 */
@WebServlet("/ser02")
public class Servlet02 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Servlet02 start...");
    }
}

如上图:对于FilterTest1就会有init出现,但是FilterTest2没有,因为拦截器现在只拦截了FilterTest1;带*号就可以拦截所有,实际开发中一般都是带星号

package com.ynu.edu.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
 * @ClassName FilterTest1
 * @Description 过滤器测试
 * @Author Echo-Nie
 * @Date 2025/1/17 21:48
 * @Version V1.0
 */
@WebFilter("/*")
public class FilterTestAll implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("FilterTest1 init...");
    }
    
    /**
    * @Author Echo-Nie
    * @Description 过滤方法
    doFilter放行方法前,做请求拦截
    doFilter放行方法后,做响应拦截
    * @Date 21:51 2025/1/17
    * @Param [servletRequest, servletResponse, filterChain]
    * @return void
    **/
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //放行资源
        filterChain.doFilter(servletRequest,servletResponse);
        //如果不放行的话这里就一直被拦截,永远不会进入Servlet01
    }

    @Override
    public void destroy() {
        System.out.println("FilterTest1 destroy...");
    }
}

1.3 LoginFilter

  1. 预处理

    在用户登录请求到达 UserServlet 之前,检查用户是否已经登录。

    如果用户已经登录(即 session 中存在 user 对象),则直接重定向到首页 index.jsp,避免重复登录。

  2. 后处理

    在响应返回给客户端之前,设置响应头,防止浏览器缓存页面内容。

package com.ynu.edu.filter;
​
import com.ynu.edu.vo.MessageModel;
​
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
​
/**
 * @ClassName LoginFilter
 * @Description 登录过滤器
 * @Author Echo-Nie
 * @Date 2025/1/17 15:00
 * @Version V1.0
 */
@WebFilter("/login")
public class LoginFilter implements Filter {
​
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化
    }
​
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // 将ServletRequest和ServletResponse转换为HttpServletRequest和HttpServletResponse
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
​
        // 1. 预处理:检查用户是否已经登录
        HttpSession session = httpRequest.getSession();
        if (session.getAttribute("user") != null) {
            // 如果用户已经登录,直接重定向到首页,避免重复登录
            httpResponse.sendRedirect("index.jsp");
            return; // 结束过滤链,不再继续执行后续的Filter和Servlet
        }
​
        // 2. 继续执行后续的Filter和Servlet
        chain.doFilter(request, response);
​
        // 3. 后处理:在响应返回给客户端之前,可以做一些额外的工作
        httpResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
        httpResponse.setHeader("Pragma", "no-cache"); // HTTP 1.0
        httpResponse.setDateHeader("Expires", 0);
    }
​
    @Override
    public void destroy() {
        // 销毁
    }
}

1.4 非法访问拦截

需要放行的资源:

  1. 指定页面,放行 (无需登录即可访问的页面 例如:登录页面、注册页面等)

  2. 静态资源,放行 (image、js、css文件等)

  3. 指定操作,放行(无需登录即可执行的操作 例如:登录操作、注册操作)3.

  4. 登录状态,放行(判断session中用户信息是否为类

package com.ynu.edu.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

/**
 * @ClassName AuthFilter
 * @Description 用户登录验证过滤器
 * @Author Echo-Nie
 * @Date 2025/1/18
 * @Version V1.0
 */
@WebFilter("/*") // 拦截所有请求
public class AuthFilter implements Filter {

    // 放行的指定页面
    private static final List<String> ALLOWED_PAGES = Arrays.asList(
            "/login.jsp", "/register.jsp"
    );

    // 放行的静态资源路径
    private static final List<String> ALLOWED_RESOURCES = Arrays.asList(
            "/css/", "/js/", "/images/"
    );

    // 放行的指定操作
    private static final List<String> ALLOWED_ACTIONS = Arrays.asList(
            "/login", "/register"
    );

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化方法,可以在这里做一些初始化工作
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // 将 ServletRequest 和 ServletResponse 转换为 HttpServletRequest 和 HttpServletResponse
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        // 获取请求的 URI
        String requestURI = httpRequest.getRequestURI();
        String contextPath = httpRequest.getContextPath();
        String path = requestURI.substring(contextPath.length());

        // 检查是否为放行的指定页面
        if (ALLOWED_PAGES.contains(path)) {
            chain.doFilter(request, response);
            return;
        }

        // 检查是否为放行的静态资源
        if (isAllowedResource(path)) {
            chain.doFilter(request, response);
            return;
        }

        // 检查是否为放行的指定操作
        if (ALLOWED_ACTIONS.contains(path)) {
            chain.doFilter(request, response);
            return;
        }

        // 检查用户是否已登录
        HttpSession session = httpRequest.getSession(false); // 如果不存在 Session,则返回 null
        boolean isLoggedIn = (session != null && session.getAttribute("user") != null);

        if (isLoggedIn) {
            // 用户已登录,放行请求
            chain.doFilter(request, response);
        } else {
            // 用户未登录,重定向到登录页面
            httpResponse.sendRedirect(contextPath + "/login.jsp");
        }
    }

    @Override
    public void destroy() {
        // 销毁方法,可以在这里做一些清理工作
    }

    /**
     * 检查是否为放行的静态资源
     */
    private boolean isAllowedResource(String path) {
        for (String resource : ALLOWED_RESOURCES) {
            if (path.startsWith(resource)) {
                return true;
            }
        }
        return false;
    }
}

2 监听器Listener

监听器是Servlet 中一种的特殊的类,能帮助监听 web 中的特定事件,比如 ServletContextHttpSession,ServletRequest 的创建和销毁;变量的创建、销毁和修改等。可以在某些动作前后增加处理,实现监控。


2.1 监听器的作用

监听器用于监听 Web 应用中的事件,例如:

  • 请求的创建和销毁。

  • Session 的创建和销毁。

  • 应用上下文(ServletContext)的初始化和销毁。

  • 属性的添加、修改和删除。

通过监听器,开发者可以在这些事件发生时执行自定义的逻辑,例如初始化资源、记录日志、统计在线用户等。


2.2 监听器的分类

监听器分为三类,共八种:

(1) 监听生命周期

这类监听器用于监听对象的创建和销毁事件。

  1. ServletRequestListener

    监听 HTTP 请求的创建和销毁。

    主要方法:

    requestInitialized(ServletRequestEvent sre):请求创建时触发。

    requestDestroyed(ServletRequestEvent sre):请求销毁时触发。

  2. HttpSessionListener

    监听 Session 的创建和销毁。

    主要方法:

    sessionCreated(HttpSessionEvent se):Session 创建时触发。

    sessionDestroyed(HttpSessionEvent se):Session 销毁时触发。

  3. ServletContextListener

    监听应用上下文(ServletContext)的初始化和销毁。

    主要方法:

    contextInitialized(ServletContextEvent sce):应用启动时触发。

    contextDestroyed(ServletContextEvent sce):应用关闭时触发。


(2) 监听值的变化

这类监听器用于监听属性的添加、修改和删除事件。

  1. ServletRequestAttributeListener

    监听请求属性(request.setAttribute())的变化。

    主要方法:

    attributeAdded(ServletRequestAttributeEvent srae):属性添加时触发。

    attributeRemoved(ServletRequestAttributeEvent srae):属性删除时触发。

    attributeReplaced(ServletRequestAttributeEvent srae):属性修改时触发。

  2. HttpSessionAttributeListener

    监听 Session 属性(session.setAttribute())的变化。

    主要方法:

    attributeAdded(HttpSessionBindingEvent se):属性添加时触发。

    attributeRemoved(HttpSessionBindingEvent se):属性删除时触发。

    attributeReplaced(HttpSessionBindingEvent se):属性修改时触发。

  3. ServletContextAttributeListener

    监听应用上下文属性(servletContext.setAttribute())的变化。

    主要方法:

    attributeAdded(ServletContextAttributeEvent scae):属性添加时触发。

    attributeRemoved(ServletContextAttributeEvent scae):属性删除时触发。

    attributeReplaced(ServletContextAttributeEvent scae):属性修改时触发。


(3) 针对 Session 中的对象

这类监听器用于监听 Session 中 Java 对象(JavaBean)的绑定和解绑事件。

  1. HttpSessionBindingListener

    监听 Java 对象绑定到 Session 或从 Session 中解绑的事件。

    需要 JavaBean 直接实现该接口。

    主要方法:

    valueBound(HttpSessionBindingEvent event):对象绑定到 Session 时触发。

    valueUnbound(HttpSessionBindingEvent event):对象从 Session 中解绑时触发。

  2. HttpSessionActivationListener

    监听 Session 中 Java 对象的激活和钝化事件(在分布式环境中使用)。

    需要 JavaBean 直接实现该接口。

    主要方法:

    sessionWillPassivate(HttpSessionEvent se):对象钝化时触发。

    sessionDidActivate(HttpSessionEvent se):对象激活时触发。


2.3 监听器场景

  1. 统计在线用户

    使用 HttpSessionListener 监听 Session 的创建和销毁,统计在线用户数量。

  2. 初始化全局资源

    使用 ServletContextListener 在应用启动时初始化全局资源(如数据库连接池)。

  3. 记录请求日志

    使用 ServletRequestListener 记录每个请求的访问日志。

  4. 监听属性变化

    使用 HttpSessionAttributeListener 监听 Session 中属性的变化,实现特定业务逻辑。

  5. 对象绑定和解绑事件

    使用 HttpSessionBindingListener 监听 Java 对象在 Session 中的绑定和解绑事件。


2.4 监听器的配置

  1. 注解配置

    在 Servlet 3.0 及以上版本中,可以使用 @WebListener 注解配置监听器。

    @WebListener
    public class MySessionListener implements HttpSessionListener {
        @Override
        public void sessionCreated(HttpSessionEvent se) {
            System.out.println("Session created: " + se.getSession().getId());
        }
    ​
        @Override
        public void sessionDestroyed(HttpSessionEvent se) {
            System.out.println("Session destroyed: " + se.getSession().getId());
        }
    }
  2. XML 配置

    web.xml 中配置监听器。

    <listener>
        <listener-class>com.ynu.edu.listener.MySessionListener</listener-class>
    </listener>

2.5 测试样例

Listener类:

package com.ynu.edu.listener;
​
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
​
/**
 * @ClassName Listener01
 * @Description 监听器01号
 * @Author Echo-Nie
 * @Date 2025/1/17 23:04
 * @Version V1.0
 */
@WebListener
public class Listener01 implements HttpSessionListener {
    @Override
    public void sessionCreated(HttpSessionEvent se) {
        System.out.println("Session Created...");
    }
​
    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        System.out.println("Session Destroy...");
    }
}

创建Session:

package com.ynu.edu.servlet;
​
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
​
/**
 * @ClassName ServletListener01
 * @Description
 * @Author Echo-Nie
 * @Date 2025/1/17 23:06
 * @Version V1.0
 */
@WebServlet("/s01")
public class ServletListener01 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Servlet-Listener01 Create...");
        HttpSession httpSession = req.getSession();
​
    }
}

销毁Session:

package com.ynu.edu.servlet;
​
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
​
/**
 * @ClassName ServletListener01
 * @Description
 * @Author Echo-Nie
 * @Date 2025/1/17 23:06
 * @Version V1.0
 */
@WebServlet("/s02")
public class ServletListener02 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Servlet-Listener01 Destroy...");
        req.getSession().invalidate();
​
    }
}

3 在线人数监控

3.1 开发逻辑

在线人数统计

当用户访问网站时,会创建一个 Session,OnlineUserListener 监听到 Session 创建事件,增加在线人数。

当用户退出或 Session 超时时,OnlineUserListener 监听到 Session 销毁事件,减少在线人数。

显示在线人数

用户可以通过点击链接访问 /onlineUsers,查看当前在线人数。

退出功能

用户点击“退出”按钮后,LogoutServlet 会销毁当前 Session 并减少在线人数,然后重定向到首页。

代码编写:

核心工具类:计数功能( OnlineUserCounter

监听器:实时更新逻辑( OnlineUserListener

Servlet:业务逻辑登录登出( OnlineUserServletLogoutServlet

前端页面:用户交互( index.jsp

测试和调试


3.2 代码层

index.jsp

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>在线人数监控</title>
</head>
<body>
    <h1>欢迎访问在线人数监控系统</h1>
    <p><a href="onlineUsers">查看当前在线人数</a></p>
    <form action="logout" method="post">
        <button type="submit">退出</button>
    </form>
</body>
</html>

LogoutServlet:

package com.ynu.edu.servlet;
​
import com.ynu.edu.util.OnlineUserCounter;
​
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
​
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
​
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取当前 Session
        HttpSession session = req.getSession(false);
        if (session != null) {
            // 销毁 Session
            session.invalidate();
            // 减少在线人数
            OnlineUserCounter.decrement();
        }
​
        // 重定向到首页
        resp.sendRedirect("index.jsp");
    }
}

OnlineUserListener:

package com.ynu.edu.listener;
​
import com.ynu.edu.util.OnlineUserCounter;
​
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
​
@WebListener
public class OnlineUserListener implements HttpSessionListener {
​
    @Override
    public void sessionCreated(HttpSessionEvent se) {
        // Session 创建时,增加在线人数
        OnlineUserCounter.increment();
        System.out.println("Session 创建,当前在线人数: " + OnlineUserCounter.getOnlineUsers());
    }
​
    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        // Session 销毁时,减少在线人数
        OnlineUserCounter.decrement();
        System.out.println("Session 销毁,当前在线人数: " + OnlineUserCounter.getOnlineUsers());
    }
}

OnlineUserCounter:

package com.ynu.edu.util;
​
import java.util.concurrent.atomic.AtomicInteger;
​
public class OnlineUserCounter {
    private static final AtomicInteger onlineUsers = new AtomicInteger(0);
​
    // 增加在线人数
    public static void increment() {
        onlineUsers.incrementAndGet();
    }
​
    // 减少在线人数
    public static void decrement() {
        onlineUsers.decrementAndGet();
    }
​
    // 获取当前在线人数
    public static int getOnlineUsers() {
        return onlineUsers.get();
    }
}

内容概要:本文详细介绍了如何使用Matlab对地表水源热泵系统进行建模,并采用粒子群算法来优化每小时的制冷量和制热量。首先,文章解释了地表水源热泵的工作原理及其重要性,随后展示了如何设定基本参数并构建热泵机组的基础模型。接着,文章深入探讨了粒子群算法的具体实现步骤,包括参数设置、粒子初始化、适应度评估以及粒子位置和速度的更新规则。为了确保优化的有效性和实用性,文中还讨论了如何处理实际应用中的约束条件,如设备的最大能力和制冷/制热模式之间的互斥关系。此外,作者分享了一些实用技巧,例如引入混合优化方法以加快收敛速度,以及在目标函数中加入额外的惩罚项来减少不必要的模式切换。最终,通过对优化结果的可视化分析,验证了所提出的方法能够显著降低能耗并提高系统的运行效率。 适用人群:从事暖通空调系统设计、优化及相关领域的工程师和技术人员,尤其是那些希望深入了解地表水源热泵系统特性和优化方法的专业人士。 使用场景及目标:适用于需要对地表水源热泵系统进行精确建模和优化的情景,旨在找到既满足建筑负荷需求又能使机组运行在最高效率点的制冷/制热量组合。主要目标是在保证室内舒适度的前提下,最大限度地节约能源并延长设备使用寿命。 其他说明:文中提供的Matlab代码片段可以帮助读者更好地理解和复现整个建模和优化过程。同时,作者强调了在实际工程项目中灵活调整相关参数的重要性,以便获得更好的优化效果。
内容概要:本文详细介绍了如何利用Blender和Python为污水处理厂创建高精度3D渲染效果图及其背后的参数化建模方法。首先,作者展示了如何通过Python代码管理复杂的设备数据结构(如嵌套字典),并将其应用于3D模型中,确保每个工艺段的设备参数能够准确反映在渲染图中。接着,文章深入探讨了具体的材质处理技巧,比如使用噪声贴图和溅水遮罩来增强金属表面的真实感,以及如何优化渲染性能,如采用256采样+自适应采样+OpenImageDenoise的降噪组合拳,将渲染时间缩短至原来的三分之一。此外,文中还涉及到了一些高级特性,如通过Houdini的粒子系统模拟鸟类飞行路径,或者利用Three.js实现交互式的在线展示。最后,作者强调了参数化建模的重要性,它不仅提高了工作效率,还能更好地满足客户需求,尤其是在面对紧急的设计变更时。 适合人群:从事污水处理工程设计的专业人士,尤其是那些希望提升自己3D建模技能和提高工作效率的人。 使用场景及目标:适用于需要快速生成高质量污水处理厂设计方案的场合,特别是在投标阶段或向客户展示初步概念时。通过这种方式,设计师可以在短时间内制作出逼真的效果图,帮助客户直观理解设计方案,并且可以根据客户的反馈迅速调整模型参数,从而加快决策过程。 其他说明:除了技术细节外,本文还分享了许多实用的经验和技巧,如如何平衡美观与效率之间的关系,以及怎样应对实际项目中的各种挑战。对于想要深入了解这一领域的读者来说,这是一份非常有价值的学习资料。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Echo-Nie

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值