Filter&Listener&文件上传

Filter&Listener&文件上传

内容介绍

web开发的三大组件(servlet,filter,listener)其中的两个
	filter:过滤器 就在在请求到达资源之前对数据进行过滤,在响应信息到达客户端之前进行过滤
	listener:监听器 主要用来监听三大域对象的创建,销毁及属性的变化操作
文件上传

一 Filter

1 概述

生活中的过滤器

地铁安检、净水器、空气净化器

javaweb中的过滤器

当用户访问服务器资源时,过滤器将请求拦截下来,完成一些特殊的功能.还可以对响应信息进行拦截过来(一般是用来做数据压缩的)

作用

一般用于完成通用的操作。如:登录验证、统一编码处理、敏感字符过滤

2 Filter(xml方式)快速入门

步骤分析

  1. 编写一个类,继承Filter接口
  2. 在web.xml中配置
    • 告诉web服务器我是谁,我在哪
    • 告诉web服务器我能处理什么路径
  3. 测试

代码实现

<!--告诉web服务器我是谁,我在哪-->
<filter>
    <filter-name>A_HelloFilter</filter-name>
    <filter-class>com.itheima.web.filter.A_HelloFilter</filter-class>
</filter>

<!--告诉web服务器我能处理什么路径-->
<filter-mapping>
    <filter-name>A_HelloFilter</filter-name>
    <url-pattern>/a/demo1</url-pattern>
</filter-mapping>
public class A_HelloFilter implements Filter {
    public void destroy() {
    }

    //执行过滤操作的方法
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {

        System.out.println("过滤器收到了请求");

        //放行
        chain.doFilter(req, resp);


        System.out.println("过滤器收到了响应");
    }

    public void init(FilterConfig config) throws ServletException {

    }
}

编写一个servlet来测试过滤器

@WebServlet(value = "/a/demo1")
public class Demo1Servlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("demo1 servlet 收到了请求");
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TGCA8A5Z-1620011645236)(Filter&Listener&文件上传.assets/image-20210503103448449.png)]

3 生命周期(了解)

void init(FilterConfig config):初始化方法,在项目启动的时候就会执行,只执行一次

void destroy():销毁,在项目被移除的时候或者服务器关闭的时候就会执行,只执行一次

void doFilter(request,response,FilterChain chain):执行过滤方法,当请求匹配上的时候就会执行

filter是一个单实例的线程不安全的对象
服务器启动的时候,服务器会创建filter对象,且调用filter的init方法,实现初始化操作
当请求来的时候,若filter能匹配到此路径,服务器就会从线程池中获取一个线程,在线程中调用filter的doFilter方法,当dofilter方法执行了放行操作的时候才会到达下一个地方.
当服务器正常关闭的时候,服务器会调用filter的destroy方法,实现销毁操作.

和servlet不同的地方,初始化方法的执行时机

  • servlet的init默认是第一次访问来的时候才执行,可以通过load-on-startup修改为服务器启动的时候执行
  • filter直接就是服务器启动的时候就会执行init方法
public class B_LifeFilter implements Filter {
    //销毁方法 在服务器关闭的时候或者项目被移除的时候 只执行一次
    public void destroy() {
        System.out.println(444);
    }

    //执行过滤的方法, 每次请求来的时候,若能匹配上请求路径就会执行一次
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        System.out.println("filter 收到了请求");

        chain.doFilter(req, resp);

        System.out.println("filter 收到了响应");
    }

    //初始化方法 项目启动的时候就会执行 只执行一次
    public void init(FilterConfig config) throws ServletException {
        System.out.println(555);
    }

}

<filter>
    <filter-name>B_LifeFilter</filter-name>
    <filter-class>com.itheima.web.filter.B_LifeFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>B_LifeFilter</filter-name>
    <url-pattern>/b/1.jsp</url-pattern>
</filter-mapping>

4 Filter配置

拦截路径配置

  • 精确(完全)路径匹配 例如:/demo1 /a/demo1 /1.html /a/b/demo23
  • 模糊路径匹配 例如:/a/b/* /a/* /* 注意:(/*在servlet中最好不用使用)
  • 后缀名匹配 例如: *.jsp *.do *.action

在filter中一般使用的模糊路径匹配和后缀名匹配

<filter>
    <filter-name>C_Url1Filter</filter-name>
    <filter-class>com.itheima.web.filter.C_Url1Filter</filter-class>
</filter>
<filter-mapping>
    <filter-name>C_Url1Filter</filter-name>
    <url-pattern>/c/*</url-pattern>
</filter-mapping>

<filter>
    <filter-name>C_Url2Filter</filter-name>
    <filter-class>com.itheima.web.filter.C_Url2Filter</filter-class>
</filter>
<filter-mapping>
    <filter-name>C_Url2Filter</filter-name>
    <url-pattern>*.do</url-pattern>
</filter-mapping>

filter代码都是一句输出 就省略

测试访问的路径

<a href="${pageContext.request.contextPath}/c/1.jsp">c_filter的路径配置_模糊路径 /c/*</a><br>
<a href="${pageContext.request.contextPath}/user/findAll.do">c_filter的路径配置_后缀名匹配 *.do</a>

一个路径只能有一个servlet执行-- 一个路径能对应上一个servlet,若一个路径能匹配上多个servlet的时候,路径存在优先级问题. 完全路径>模糊路径>后缀名>缺省配置(/)

但是,在filter中不存在一个路径只能有一个filter执行,一个请求的路径若能匹配上多个filter的时候,多个filter都会执行.

过滤器链

FilterChain:只有一个放行方法 doFilter(req,resp).

当一个请求可以匹配到多个过滤器的时候,请求就相当于将所有匹配到的过滤器串起来了,这个就是过滤器链.当前的过滤器若执行了放行操作,就会将请求放行到链中的下一个过滤器.若当前的过滤器是最后一个过滤器话,就可以执行目标资源(servlet,jsp,…)

过滤器链中的过滤器的执行顺序为:

  • xml中,根据filter-mapping标签在web.xml中顺序决定的
  • 注解中,根据FiIter的类名的字典(自然)顺序决定

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TdBrRYt8-1620011645248)(Filter&Listener&文件上传.assets/image-20210503103511539.png)]

拦截方式配置

通过filter-mapping标签中dispatcher标签来配置过滤拦截那种方式过来的请求,

  • 默认值(REQUEST):只拦截从浏览器直接发送过来的请求.一旦显式的配置了其他方式,默认的就失效了.
  • 若想过滤拦截转发过来的请求,只需要通过dispatcher标签配置FORWARD
  • 若想过滤拦截转发或从浏览器直接发送过来的请求的话,就需要配置两个dispatcher即可.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LpRuIhUu-1620011645250)(Filter&Listener&文件上传.assets/image-20210503102644306.png)]

测试servlet

@WebServlet(value = "/d/demo3")
public class Demo3Servlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("demo3 servlet执行了,紧接这转发请求到/a/demo1上");

        request.getRequestDispatcher("/a/demo1").forward(request,response);
    }
}

5 Filter注解方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qJfq8BeO-1620011645253)(Filter&Listener&文件上传.assets/image-20210503103528869.png)]

/*
    filterName:配置filter的名字 对我们来说用途不大
    urlPatterns和value:字符串数组 配置filter的过滤拦截路径
    dispatcherTypes:DispatcherType注解数组类型 配置拦截方式

 */
@WebFilter(filterName = "D_AnnoFilter", value = "/e/*",dispatcherTypes = {DispatcherType.FORWARD,DispatcherType.REQUEST})
public class D_AnnoFilter implements Filter {
    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        System.out.println("注解过滤器执行了");
        chain.doFilter(req, resp);
    }

    public void init(FilterConfig config) throws ServletException {

    }
}

二 Filter案例

1 解决全站乱码

需求

浏览器发出的任何请求,通过过滤器统一处理中文乱码 。

tomcat8.5处理了get请求的中文乱码,但是post的没有处理,对于post请求来说,我们之前的操作,是在获取参数之前执行 request.setCharacterEncoding(“utf-8”);

步骤分析

  1. 编写过滤器,过滤器的过滤器路径为/*
  2. 在过滤器的过滤方法中
    • 获取请求方式
    • 若是post请求,调用request.setCharacterEncoding(“utf-8”);
    • 无论如何别忘记放行操作
  3. 测试

代码实现

@WebFilter(filterName = "EncodingFilter", value = "/*")
public class EncodingFilter implements Filter {
    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        //强转
        HttpServletRequest request = (HttpServletRequest) req;

        //- 获取请求方式
        String method = request.getMethod();

        //- 若是post请求,调用request.setCharacterEncoding("utf-8");
        if ("post".equalsIgnoreCase(method)) {
            request.setCharacterEncoding("utf-8");
        }

        //- 无论如何别忘记放行操作
        chain.doFilter(req, resp);
    }

    public void init(FilterConfig config) throws ServletException {

    }

}

测试表单和servlet自己编写一下

2 登录案例

需求

​ 实现一个基本权限验证的案例,对于一些资源(例如:购物车,订单列表等),如果用户已经登录了,才访问目标资源;如果用户没有登录过,就不能访问目标资源

步骤分析:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WTkkWZRF-1620011645256)(Filter&Listener&文件上传.assets/image-20210422101454015.png)]

  1. 完成登录案例(快速完成)
    1. 登录成功后需要将用户数据放入session
    2. 登录成功跳转首页
  2. 编写一个Filter
    1. 判断用户是否登录
    2. 如果登录成功, 放行
    3. 如果没有登录,跳转登录页面

代码实现

index.jsp

      <a href="${pageContext.request.contextPath}/login.jsp">登陆页面</a><br>
      <a href="${pageContext.request.contextPath}/cart/findAll">购物车详情页面</a><br>
      <a href="${pageContext.request.contextPath}/order/mylist">我的订单页面</a><br>
      <a href="${pageContext.request.contextPath}/product/list">商品列表页面</a><br>

login.jsp代码

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/login" method="post">
    姓名:<input type="text" name="username"><br/>
    密码:<input type="text" name="password"><br/>
    <input type="submit" value="提交"><br/>
    ${msg}
</form>
</body>
</html>


filter代码

package cn.itcast.login.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebFilter(value = {"/cart/*","/order/*"})
public class LoginFilter implements Filter {
    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        //1.强转
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;

        //2,判断用户是否登陆(session中有无用户)
        Object loginUser = request.getSession().getAttribute("loginUser");

        //3.若没有的话 跳转登录页
        if (null == loginUser) {
            request.setAttribute("msg","请先登陆");
            request.getRequestDispatcher("/login.jsp").forward(request,response);

            return;
        }

        chain.doFilter(req, resp);
    }

    public void init(FilterConfig config) throws ServletException {

    }
}

LoginServlet

package cn.itcast.login.servlet;

import cn.itcast.login.domain.User;

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;

@WebServlet(value = "/login")
public class LoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");

        String username = request.getParameter("username");
        String password = request.getParameter("password");

        if ("tom".equals(username) && "123".equals(password)) {
            //登陆成功
            User user = new User();
            user.setUsername(username);
            user.setPassword(password);

            //将用户存入session中
            request.getSession().setAttribute("loginUser",user);
            response.getWriter().print("登陆成功");
        }else {
            request.setAttribute("msg","用户名或密码错误");
            request.getRequestDispatcher("/login.jsp").forward(request,response);
        }
    }
}


其他Servlet

@WebServlet(value = "/cart/findAll")
public class CartServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.getWriter().print("cart info");
    }
}

@WebServlet(value = "/order/mylist")
public class OrderServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.getWriter().print("my order list");
    }
}

@WebServlet(value = "/product/list")
public class ProductServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.getWriter().print("product list~~");
    }
}

三 Listener

1 概述

生活中的监听器

我们很多商场有摄像头,监视着客户的一举一动。如果客户有违法行为,商场可以采取相应的措施。

javaweb中的监听器

在我们的java程序中,有时也需要监视某些事情,一旦被监视的对象发生相应的变化,我们应该采取相应的操作。

监听web三大域对象:ServletRequest,HttpSession,ServletContext

可以监听三大域对象的创建、销毁和属性变化等

监听机制:

  • 事件源:发生事件的对象
  • 事件:事情
  • 监听器:监听某个事件源发生指定事件之后,所做的操作.

场景

系统启动时初始化配置信息、统计在线人数

2 xml方式快速入门

监听器在web开发中使用的比较少,见的机会就更少了,今天我们使用ServletContextListenner来带领大家学习下监听器,因为这个监听器是监听器中使用率最高的一个,且监听器的使用方式都差不多。

我们使用这个监听器可以在项目启动和销毁的时候做一些事情,例如,在项目启动的时候加载配置文件。

步骤分析

  1. 编写一个类,实现ServletContextListener接口
  2. 重写两个方法,项目启动和项目终止方法
  3. 在web.xml中配置
    • 告诉web服务器我在哪里

代码实现

public class MyServletContextListener implements ServletContextListener {
    /*
        servletContext初始化后要做的事情(项目启动成功了)
        方法中的参数就是事件对象,可以获取事件源
     */
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("servletContext对象 创建了,项目启动了");
    }

    /*
        servletContext销毁后要做的事情(项目终止前)
     */
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("servletContext对象 销毁了,项目马上要关闭了");
    }
}

    <listener>
        <listener-class>com.itheima.web.listener.MyServletContextListener</listener-class>
    </listener>

3 注解方式

只需要在类上添加一个注解即可 @WebListener

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qp3uibac-1620011645258)(Filter&Listener&文件上传.assets/image-20210503103553315.png)]

四 Listener案例:统计在线人数

1 需求

有用户使用网站,在线人数就+1;用户不再使用网站,在线人数就-1 ;访问一个servlet可以知道目前有几个用户在访问网站

监听session的创建和销毁

2 步骤分析

  1. 先创建一个CountServlet,来展示在线人数
    • 获取ServletContext中人数
    • 将人数输出到页面
  2. 再创建一个OnlineListener,实现HttpSessionListener
    • 在创建session的方法中,每创建一个session,就把在线人数+1
    • 在销毁session的方法中,每销毁一个session,就把在线人数-1

3 代码实现

@WebServlet(value = "/count")
public class CountServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //- 获取ServletContext中人数
        Integer count = (Integer) getServletContext().getAttribute("count");

        //- 将人数输出到页面
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().print("当前在线人数为:" + (count==null?0:count));
    }
}

@WebListener
public class OnlineListener implements HttpSessionListener {
    //创建session之后的操作
    public void sessionCreated(HttpSessionEvent httpSessionEvent) {
        //获取serveltcontext对象,获取人数
        HttpSession session = httpSessionEvent.getSession();
        ServletContext context = session.getServletContext();

        Integer count = (Integer) context.getAttribute("count");

        //将人数+1
        //将最新的人数放入域中
        context.setAttribute("count",count==null?1:++count);
    }

    //  销毁session之后的操作
    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
        //获取serveltcontext对象,获取人数
        HttpSession session = httpSessionEvent.getSession();
        ServletContext context = session.getServletContext();

        Integer count = (Integer) context.getAttribute("count");

        //将人数-1
        count--;

        //将最新的人数放入域中
        context.setAttribute("count",count);
    }
}


五 案例:文件上传

1 需求

在一个页面上,我们可以输入用户名,还可以选择要上传的文件.点击提交按钮,就可以将用户输入的内容和选择的文件保存到服务器上.

2 技术分析

文件上传:

​ 将客户的数据通过网络复制到服务器上

文件上传对浏览器要求:

  1. 表单的提交方式必须是post
  2. 表单的enctype属性值必须是multipart/form-data(多部分表单数据)
  3. 在表单中提交文件选择框,且需要提供name属性

注意:一旦表单使用multipart/form-data属性值,后台使用原生的方式获取参数的方法就都失效了

<form action="${pageContext.request.contextPath}/upload" method="post" enctype="multipart/form-data">
    用户名:<input type="text" name="username"><br>
    文件:<input type="file" name="ff"><br>
    <input type="submit" value="提交">
</form>

常见的文件上传技术:

  • 使用apache提供的工具:commons-fileupload (代码写起来比较复杂)-代码不需要大家写
  • 使用servlet3.0开始提供的文件上传方式
  • 使用springmvc框架(非常简单,底层封装了commons-fileupload)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w2Gi7sAX-1620011645259)(Filter&Listener&文件上传.assets/image-20210503103613916.png)]

若表单提交的时候 enctype属性设置为multipart/form-data ,request原生的获取参数的方法都失效了.

建议使用工具类commons-fileupload(apache提供)来完成操作

3 步骤分析

使用commons-fileupload完成上传

  1. 导入jar包(昨天资料),commons-fileupload和commons-io
  2. 在servlet编写代码
    1. 创建磁盘文件项工厂对象
    2. 创建核心上传对象,使用工厂对象作为参数
    3. 使用核心上传对象 解析 请求,获取文件项集合(list)
    4. 遍历文件项集合,获取每个文件项
      • 判断是否是普通上传项,若是:获取name属性值,获取用户输入的内容
      • 若是文件上传项,获取name属性,获取文件名字,获取文件输入流
        • 创建输出流,俩流拷贝,保存文件,关闭流,清理临时文件.

4 代码实现-复制或照抄

public class UUIDUtils {    public static String getId(){        return UUID.randomUUID().toString();    }}

@WebServlet(value = "/upload")public class UploadServlet extends HttpServlet {    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        doGet(request, response);    }    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        try {            //1. 创建磁盘文件项工厂对象            DiskFileItemFactory factory = new DiskFileItemFactory();            //2. 创建核心上传对象,使用工厂对象作为参数            ServletFileUpload upload = new ServletFileUpload(factory);            //3. 使用核心上传对象 解析 请求,获取文件项集合(list)            List<FileItem> list = upload.parseRequest(request);            //4. 遍历文件项集合,获取每个文件项            if (list!=null && list.size()>0) {                for (FileItem fi : list) {                    if (fi.isFormField()) {                        //普通上传项,                        // 获取name属性值,                        String name = fi.getFieldName();                        // 获取用户输入的内容                        String value = fi.getString("utf-8");                        System.out.println("普通上传项:"+name+"=="+value);                    }else {                        //文件上传项                        // 获取name属性值,                        String name = fi.getFieldName();                        // 获取文件名字                        String filename = fi.getName();                        //为了避免出现同名文件覆盖的问题,在原始的文件名前拼接uuid值                        filename = UUIDUtils.getId()+"_"+filename;                        // 获取文件                        InputStream is = fi.getInputStream();                        // 获取上传文件 存放的目录                        File dirFile = new File("e:/upload");                        //判断目录是否存在,若不再则创建                        if (!dirFile.exists()) {                            dirFile.mkdirs();                        }                        // 创建输出流                        FileOutputStream os = new FileOutputStream(new File(dirFile, filename));                        // 拷贝流                        IOUtils.copy(is,os);                        // 关闭流                        os.close();                        is.close();                        //删除临时文件                        fi.delete();                    }                }            }        } catch (FileUploadException e) {            e.printStackTrace();        }    }}

六 总结

Filter

过滤器

作用:在请求达到目标资源之前对请求进行过滤拦截,或者也可以在响应达到浏览器之前,对响应进行过滤拦截

实现步骤:

  1. 编写一个类,实现javax.servlet.Filter接口
  2. 重写所有方法,重点关注doFilter方法(过滤拦截),别忘记放行
  3. 在web.xml中进行配置

了解下filter的生命周期

掌握filter的url的配置,一般不使用精确路径

  • 精确路径
  • 模糊路径
  • 后缀名

过滤器链:

  • 若一个路径能匹配到多个过滤器的时候,多个过滤器执行是有顺序
    • 若是xml方式,是按照web.xml中filter-mapping的顺序决定的
    • 若是注解方式,是按照类名的自然(字典)顺序决定的(自己测试)

拦截方式配置

  • 默认只过滤从浏览器直接发送过来的请求

  • 一旦显式的声明了其他方式的,默认值就失效了

  • 若想过滤器多种方式的请求,在filter-mappbing标签中配置多个dispatcher标签

    ​ REQUEST FORWORD

注解配置

  • 在类上添加一个注解 @WebFilter(value=“路径配置”)

Listener

监听器

监听三大域对象的创建,销毁和属性变化等

记住:ServletContextListener,监听项目的启动和关闭,可以在项目启动的时候加载项目的配置文件(spring框架中)

文件上传

对浏览器的要求:

  1. 表单提交方式为post
  2. 表单enctype属性为 multipart/form-data
  3. 在表单中提供文件选择框

例子中我们使用的apache的commons-fileupload

扩展:uuid 随机唯一的字符串,在一台电脑上生成的字符串好多年不会出现重复的数据.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值