Servlet介绍(二)

Servlet介绍(二)

——————————接上文Servlet介绍(一)———————————————————

这里着重来说一下服务器获取请求参数的几种方法,首先是通过getParameter方法获取请求参数的值,第二种通过getParameterNames获取所有请求参数的名称,返回一个Enumeration 类型,然后循环迭代取值,第三种是通过getParameterValues获取同一参数名的值,该方法返回一个String数组,第四种,也是最重要的一种是通过getParameterMap方法将请求参数名和值封装到一个map对象中,返回Map<String,String[]>对象,注意键的值是一个String数组(其目的是防止同一参数名称有多个值),若引入了BeanUtils库,则调用其populate方法可将参数封装到一个对象中,实际上,Struts2框架也是通过此方法将值封装至对象,最后一种通过request的getInputStream来获取数据:

    InputStream in=null;
        int len=0;
        byte[] buf=new byte[1024];
        try {
            in=req.getInputStream();
            while((len=in.read(buf))!=-1){
                System.out.println(new String(buf,0,len));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

但是这种获取数据的方法只能获取由post方式提交的数据(因为其只读请求体的数据),其API中写道:

Retrieves the body of the request as binary data using a ServletInputStream. Either this method or getReader() may be called to read the body, not both.

和response同样,其不能同时调用getInputStream和getReader方法。

request用这种方法来获取请求参数很少用,其主要作用是用来进行文件上传:

        InputStream in=null;
        OutputStream out=null;
        int len=0;
        byte[] buf=new byte[1024];
        try {
             in=req.getInputStream();
             out=new FileOutputStream("TestServlet\\download");
             while((len=in.read(buf))!=-1){
                 out.write(buf, 0, len);
             }
        } catch (IOException e) {
            e.printStackTrace();
        }

同response一样,request在发送请求参数的时候如果有中文,也会出现乱码,因此我们在获取request参数之前要设置编码方式:

    try {
            req.setCharacterEncoding("UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        String name=req.getParameter("name");

注意ServletAPI中对setCharacterEncoding的描述为:

Overrides the name of the character encoding used in the body of this request. This method must be called prior to reading request parameters or reading input using getReader().

说明其只对post提交方式的数据进行编码,而不对get方式的数据进行编码,其采用的编码表是根据当前页面的编码方式。

如要get方式提交的数据中含有中文,出现乱码,其原因与post提交产生乱码的原因一样,是request在获取请求由UTF-8(可由页面控制编码方式)编码的参数的时候查的编码表是ISO8859-1,此时则手动将ISO8859-1的编码的数据转化为字节再通过字符串的编码设置转化为UTF-8:

    String name=req.getParameter("name");
        try {
            name=new String(name.getBytes("ISO8859-1"),"UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

还有一点,若是URL后面链接的数据还有中文,则要对其就行URL编码。

request对象和servletContext对象一样,也能实现请求转发,这里的请求转发指的是一个web资源收到客户端的请求后,通知服务器去调用另一个web资源进行处理。其应用场景主要是MVC设计模式,即Servlet(C层)处理数据后将数据封装成对象(M层,类似javaBean),通过jsp来显示(V层)。

在上文中讲到ServletContext域,其作用范围是整个web应用,现在这里是request域,其作用范围是当前的request请求,一般来说,是把数据放在request域中,其原因是每个访问者都拥有各自的request域(request对象是服务器分别对每次请求生成的),互不干扰,而ServletContext域的数据会被所有的访问者访问到,若其中一个人修改了其中的数据,则整个应用的ServletContext都被修改了。

    req.setAttribute("name", "siege");
        try {
            req.getRequestDispatcher("/dispacher.jsp").forward(req, resp);
        } catch (IOException e) {
            e.printStackTrace();
        }

在request域中设置了name属性,则在dispacher.jsp中可以通过EL表达式或者request域中获取:

    ${name }
    <hr>
    <%
    String name=(String)request.getAttribute("name");
    out.write(name);
    %>

forward方法用于将请求转发到RequestDispatcher对象封装的资源,如果在调用forward方法之前,在Servlet程序中写入的部分内容已经被真正地传送到了客户端,forward方法将抛出IllgalStateException 异常。如果在调用forward方法之前向Servlet引擎的缓冲区(response)中写入了内容,只要写到缓冲区中的内容还没有被真正输出到客户端,forward方法就可以被正常执行,原来写入到输出缓冲区的内容会被清空,但是,已经写入到HttpServletResponse对象的响应头字段信息保持有效。

try {
            resp.getOutputStream().write("name".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            req.getRequestDispatcher("/dispacher.jsp").forward(req, resp);
        } catch (IOException e) {
            e.printStackTrace();
        }

上述代码不会输出”name“,其原因就是response对象中数据被清空了。

请求转发客户端只有一次请求,而服务器有多个资源被调用,浏览器地址栏没有变化。

web工程中各种地址的写法

web应用中经常需要些地址,地址的写法主要取决于该地址主要是给谁使用的,下面介绍几种常见的需要写地址的地方。

首先,都以/开头,如果是给服务器使用的,则代表当前应用,若是给浏览器使用的,则代表当前网站。

  • request.getRequestDispatcher(“/dispacher.jsp”).forward(req, resp);

    此地址是给服务器使用的,直接写上地址即可。

  • response.sendRedirect(“/TestServlet/dispacher.jsp”)

    此地址给浏览器使用,写上项目名加上资源即可。

  • this.getServletContext().getRealPath(“/dispacher.jsp”);

    此地址是给服务器使用的。

  • this.getServletContext().getResourceAsStream(“/WEB-INF/xxx.properties”);

    此地址是给服务器使用的,xxx.properties是在src目录下的,不过最后还是要发布在WEB-INF下面。

  • <a href="/TestServlet/dispacher.jsp">

    此超链接的地址是给网站使用的。

  • <form action="/TestServlet/FirstServlet">

    form表单是给网站使用的。

还有一点,获取URL使用的是斜杠(/),获取硬盘上的资源使用反斜杠(\)。

当某些情况下,不能以斜杠开头,那只能用相对路径来表示了。

request的还有一个作用就是防盗链,通过获取请求头(referer)来判断其是否为null或者其是不是以本站网址开头的,这样就能判断是不是盗链。就可以进行相应的处理(比如如果是盗链则跳转到看广告的页面,再通过a标签来进入刚才的请求页面)。

cookie和session

cookie和session是保存会话数据的两种技术。

cookie是客户端技术,程序把每个用户的数据以cookie的形式写给用户各自的浏览器,当用户使用浏览器再去访问服务器中的web资源时,就会带着各自的数据去。这样,web资源处理的就是各自的数据了。Servlet API中关于cookie的部分描述:

The servlet sends cookies to the browser by using the HttpServletResponse.addCookie(javax.servlet.http.Cookie) method, which adds fields to HTTP response headers to send cookies to the browser, one at a time. The browser is expected to support 20 cookies for each Web server, 300 cookies total, and may limit cookie size to 4 KB each

其中对cookie的大小,总数做了限制(浏览器一般只允许存放300个cookie,每个站点最多存放20个cookie,每个cookie的大小限制为4KB)。与Cookie类相关的方法有:

  • getValue()
  • setMaxAge(int expiry)
  • setValue(java.lang.String newValue)
  • setPath(java.lang.String uri)
  • getName()
  • setDomain(java.lang.String pattern)
  • getDomain()

其中若不设置cookie的maxAge(以秒为单位),则该cookie只在当前进程有效(存储在浏览器的内存中),默认情况下它是一个会话级别的cookie,即关闭浏览器之后cookie就被删除了,setPath方法设置访问路径后,则当访问该目录下的资源时,才会带着cookie去请求服务器,若不设置访问路径,则cookie的有效路径是写cookie的Servlet的所在目录的路径。对于设置Domain,一般来说,浏览器会禁止第三方cookie。设置好cookie后,通过response的addCookie方法将cookie写入客户端硬盘。

删除cookie只需要将cookie的maxAge设为0即可,同时要注意删除时路径要保持一致,否则不会删除cookie。


Cookie[] cookies=req.getCookies();
        for(int i=0;i<cookies.length && cookies!=null;i++){
            System.out.println(cookies[i].getName()+":"+cookies[i].getValue());
        }

        Cookie cookie=new Cookie("time", new Date()+"");
        cookie.setMaxAge(24*3600);
        cookie.setPath("/TestServlet");
        resp.addCookie(cookie);

下面利用cookie技术模拟了一个关于看过那些商品的实例:

package com;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class FirstServlet  extends HttpServlet{

    /**
     * 
     */
    private static final long serialVersionUID = 1L;



    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
      resp.setContentType("text/html;charset=UTF-8");
      PrintWriter out=resp.getWriter();
      out.write("本网站有如下商品");
      out.write("<br/>");
      Map<String,Book> map=Db.getAllBooks();
      for(Map.Entry<String, Book> entry:map.entrySet()){
          Book book=entry.getValue();
          out.print("<a href='/TestServlet/CookieDemo1?id="+book.getId()+"'"+" target='_blank'"+">"+book.getName()+"</a><br/>");
      }

      Cookie[] cookies=req.getCookies();

      out.print("<br/>您曾经看过如下商品:<br/>");
      for(int i=0;cookies!=null && i<cookies.length;i++){
          if(cookies[i].getName().equals("bookHistory")){
              String ids[]=cookies[i].getValue().split("\\,");
              for(String id:ids){
                  Book book=Db.getAllBooks().get(id);
                  out.write(book.getName()+"<br/>");
              }
          }
      }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doGet(req,resp);
    }
    @Override
    public void destroy() {
        super.destroy();
        System.out.println("servlet destroy!!!!");
    }
}



class Db{
    private static Map<String,Book> map=new LinkedHashMap<String,Book>();
    static{
        map.put("1", new Book("1","javaweb开发","老张","good book"));
        map.put("2", new Book("2","java开发","老fang","good book"));
        map.put("3", new Book("3","android开发","老li","good book"));
        map.put("4", new Book("4","spring开发","老bi","good book"));
        map.put("5", new Book("5","struts2开发","老wang","good book"));
    }

    public static Map<String,Book> getAllBooks(){
        return map;
    }
}

class Book{

    public Book(String id, String name, String author, String description) {
        super();
        this.id = id;
        this.name = name;
        this.author = author;
        this.description = description;
    }

    public Book() {
        super();
    }

    private String id;
    private String name;
    private String author;
    private String description;
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAuthor() {
        return author;
    }
    public void setAuthor(String author) {
        this.author = author;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }

}
package com;

import java.io.IOException;
import java.io.Writer;
import java.util.Arrays;
import java.util.LinkedList;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CookieDemo1  extends  HttpServlet{

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        String id=req.getParameter("id");
        Book book=Db.getAllBooks().get(id);

        resp.setContentType("text/html;charset=Utf-8");
        Writer out=resp.getWriter();
        out.write(book.getId()+"<br/>");
        out.write(book.getName()+"<br/>");
        out.write(book.getAuthor()+"<br/>");
        out.write(book.getDescription()+"<br/>");


        String cookieValue=buildCookie(id,req);
        Cookie cookie=new Cookie("bookHistory",cookieValue);
        cookie.setMaxAge(30*24*3600);
        cookie.setPath("/TestServlet");
        resp.addCookie(cookie);
    }



    /** 
    * @Title: buildCookie 
    * @Description: 此方法用来生成cookie的值,循环遍历后若没有名为bookHistory的cookie(只有别的cookie),则cookieValue为null,
    *       对其赋值返回即可,若有名为bookHistory的cookie,将其分割成数组再转化为LinkedList集合,然后判断之前是否存过该id,若存过,
    *       把其排在最前面(因为客户端多次访问该书,说明对其感兴趣,因此放在前面),此外我们还要对cookie中的值作10个以内的限制,且把最
    *       新添加的放在最前面,但还有不足的地方是我们不能对每本书看的次数多少来对其进行排序。(使用LinkedList的原因时增删数据效率高)
    * @param id 书id
    * @param req 请求对象request,根据其来获得访问时带过来的cookie
    * @return String 返回值为cookie的值
    * @throws 
    */
    private String buildCookie(String id, HttpServletRequest req) {
        Cookie[] cookies=req.getCookies();
        String cookieValue=null;
        for(int i=0;cookies!=null && i<cookies.length;i++){
            if(cookies[i].getName().equals("bookHistory")){
                cookieValue=cookies[i].getValue();
            }
        }
        if(cookieValue==null){
            return id;
        }
        LinkedList<String> linkedList=new LinkedList<String>(Arrays.asList(cookieValue.split("\\,")));
        if(linkedList.contains(id)){
                linkedList.remove(id);
        }else{
            if(linkedList.size()>=10){
                linkedList.removeLast();
            }
        }
        linkedList.addFirst(id);
        StringBuilder sb=new StringBuilder();
        for(String bookId:linkedList){
            sb=sb.append(bookId+",");
        }
        return sb.deleteCharAt(sb.length()-1).toString();

    }



    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doGet(req, resp);
    }

}

将上述两个servlet在web.xml中配置好,访问即可。需要说明的是,本例的难点在于cookie值的生成,要注意各种情况,同时还要注意效率。

session是服务器端技术(由服务器创建session对象),利用这个技术,服务器在运行时可以为每一个用户的浏览器创建一个独享的session对象(一个浏览器独占一个session),由于session为用户浏览器独享,所以用户在访问服务器的web资源时,可以把各自的数据放在各自的session中,当用户再去访问服务器的其他web资源时,其他web资源再从用户各自的session中取出数据为用户服务。session对象可request对象的getSession方法获得。

session对象是在我们第一次访问时服务器(即执行request.getSession()方法时),服务器帮我们创建session对象,注意是第一次访问,之后不在创建(因为已经存在了),这是session的创建。当浏览器关闭后,session对象不会立即被服务器摧毁,其默认时间是30分钟之后销毁,我们可以在web.xml中配置session的默认值:

<session-config>
        <session-timeout>30</session-timeout>
</session-config>

我们也可以在代码中摧毁session:

session.invalidate();

request.getSession()还有个重载方法,request.getSession(boolean create),若值为false,则表示只获取session,不创建session,此方法一般在显示购物车时使用(防止用户未购买任何商品而直接点击显示购物车,此时只获取session,不创建session)。

session的实现原理是基于cookie上的,在首次执行request.getSession()代码时,服务器创建好session对象后,然后将session的id以cookie的形式写给浏览器(此cookie名为JSESSIONID,未设置有效期,即浏览器关闭之后cookie消失),当用户再次访问时(指未关闭浏览器进行的访问),会带着JSESSIONID的cookie访问web资源,服务器根据其JSESSIONID找到其对应的session对象对其进行服务。若我们要实现在我们关闭浏览器之后再打开仍然能够获得上次的session对象,只需要设置JSESSIONID的cookie的有效期(maxAge)即可。

HttpSession session=req.getSession();
String jsessionid=session.getId();
Cookie cookie=new Cookie("JSESSIONID",jsessionid);
cookie.setMaxAge(30*60);
cookie.setPath("/TestServlet/");
resp.addCookie(cookie);
session.setAttribute("name", "洗衣机");
HttpSession session=req.getSession(false);
String name=(String) session.getAttribute("name");
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().write("您购买的商品为:"+name);

注:以上代码均在两个servlet中覆盖doGet方法中编写的

这里要注意的一点是request.getSession()创建session时会立即向浏览器写入名JSESSIONID的cookie,我们只是重新覆盖了这个cookie,应此在chrome浏览器下会出现这里的响应头:

Set-Cookie:JSESSIONID=B2627A1369482C131A0F76423C8B1D49; Path=/TestServlet/; HttpOnly
Set-Cookie:JSESSIONID=B2627A1369482C131A0F76423C8B1D49; Expires=Sat, 04-Apr-2015 08:07:23 GMT; Path=/TestServlet/

注意一定要设置cookie路径和maxAge。还要强调一点,访问jsp页面时,服务器自动会向浏览器写入一个名JSESSIONID的cookie。

session既然是基于cookie技术实现的,当用户禁用cookie,此时无法保存用户的session信息,解决的办法就是URL重写

再写一个servlet,覆写doGet方法:

    response.setContentType("text/html;charset=UTF-8");
    String url1="/TestServlet/FirstServlet";
    String url2="/TestServlet/SessionDemo1";

    request.getSession();
    PrintWriter out=response.getWriter();

    out.write("<a href='"+response.encodeURL(url1)+"'>购买</a>");
    out.write("<br/>");
    out.write("<a href='"+response.encodeURL(url2)+"'>结账</a>");

在浏览器中访问该Servlet,查看源文件:

<a href='/TestServlet/FirstServlet;
jsessionid=4604483E0F69D8637D91FC091370E6A5'>购买</a><br/>
<a href='/TestServlet/SessionDemo1;
jsessionid=4604483E0F69D8637D91FC091370E6A5'>结账</a>

其将jsessionid附在超链接后面了,当我们点击该超链接访问FirstServlet时,服务器根据它的jsessionid找到对应的session对象对其进行服务。

当我们允许cookie时,则在超链接后面不添加jsessionid了。其内部是由response的encodeURL来实现的,其方法说明如下:

Encodes the specified URL by including the session ID in it, or, if encoding is not needed, returns the URL unchanged. The implementation of this method includes the logic to determine whether the session ID needs to be encoded in the URL. For example, if the browser supports cookies, or session tracking is turned off, URL encoding is unnecessary.
For robust session tracking, all URLs emitted by a servlet should be run through this method. Otherwise, URL rewriting cannot be used with browsers which do not support cookies.

此方法解决了用户禁用cookie导致无法获取session信息,但是还没有解决当用户关闭浏览器之后再打开来获取session的信息的问题,这是此方法的局限性。

下面利用session显示用户购买的商品:

package com;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class FirstServlet  extends HttpServlet{

    /**
     * 
     */
    private static final long serialVersionUID = 1L;



    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {

        req.getSession();
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter out =resp.getWriter();
        out.print("本网站有如下商品<br/>");
        Map<String,Book> map=Db.getAll();
        for(Map.Entry<String,Book> entry:map.entrySet()){
            String id=entry.getKey();
            Book book=entry.getValue();
            out.print(book.getId()+"<br/>");
            out.print(book.getName()+"<br/>");
            out.print(book.getAuthor()+"<br/>");
            out.print(book.getDescription()+"<br/>");
            String url=resp.encodeURL("/TestServlet/SessionDemo1?id="+id);
            out.print("<a href='"+url+"' target='_blank'>购买</a>"+"<br/>");
        }

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doGet(req,resp);
    }
    @Override
    public void destroy() {
        super.destroy();
        System.out.println("servlet destroy!!!!");
    }
}

class Db{
    private static Map<String,Book> map=new HashMap<String,Book>();
    static{
        map.put("1", new Book("1","Java web 开发","laozhang","一本好书"));
        map.put("2", new Book("2","Java开发","laoli","一本好书"));
        map.put("3", new Book("3","spring开发","laowang","一本好书"));
        map.put("4", new Book("4","Struts开发","laofang","一本好书"));
        map.put("5", new Book("5","Andriod 开发","laobi","一本好书"));
    }
    public static Map<String,Book> getAll(){
        return map;
    }
}

class Book implements Serializable{

    private static final long serialVersionUID = 1L;

    private String id;
    private String name;
    private String author;
    private String description;
    /**
     * @param id
     * @param name
     * @param author
     * @param description
     */
    public Book(String id, String name, String author, String description) {
        super();
        this.id = id;
        this.name = name;
        this.author = author;
        this.description = description;
    }
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAuthor() {
        return author;
    }
    public void setAuthor(String author) {
        this.author = author;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }

}
package com;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class SessionDemo1 extends HttpServlet{

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        String id=req.getParameter("id");
        Book book=Db.getAll().get(id);
        HttpSession session=req.getSession(false);
        List list=(List) session.getAttribute("list");
        if(list==null){
            list=new ArrayList();
        }
        list.add(book);
        session.setAttribute("list", list);
        //req.getRequestDispatcher("/ListCartServlet").forward(req,resp);
        String url=resp.encodeRedirectURL(req.getContextPath()+"/ListCartServlet");
        resp.sendRedirect(url);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        super.doPost(req, resp);
    }

}
package com;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

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;


//@WebServlet("/ListCartServlet")注意要自己在web.xml中配置,则注释掉该注解,否则在web.xml中配置与其一样的会报错
public class ListCartServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;




    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out=response.getWriter();
        HttpSession session=request.getSession(false);
        if(session==null){
            out.print("您没有购买任何商品");
            return ;
        }
        out.print("您购买了如下商品:<br/>");
        List list=(List) session.getAttribute("list");
        for(int i=0;list!=null && i<list.size();i++){
            out.print(((Book)list.get(i)).getName()+"<br/>");
        }

    }
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    }

}

此显示商品的功能中,需要注意的地方:

第一,在点击购买后显示购买的商品,只能用重定向,不能用转发(若用转发,刷新时会出现重复购买)
第二,本功能中考虑到了用户禁用cookie的情况,采取重写URL的办法来获取session的信息

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值