java web 基础(Servlet)

一、Servlet

1.1 implements Servlet

参考博客:https://blog.csdn.net/xiaojiahao_kevin/article/details/51781946

servlet 里有四个方法:

  • init():实例化的时候,该类只会调用一次
  • destroy(): 销毁的时候,一个该类只会调用一次
  • 构造方法():
  • service():每次访问都会调用一次 service
  • getServletConfig():用于拿到 每个servlet 的配置信息
  • getServletInfo(): 返回有关其自身的基本信息,比如作者、版本和版权。
  • 启动 tomcat ,服务器不会调用 init() 方法,而是等到访问的时候再去实例化( init() 和 construct() )
public class MainServlet implements Servlet{

	public MainServlet() {
		System.out.println("MainServleet constructed");
	}
	
	@Override
	public void destroy() {
		System.out.println("destroy");
		
	}

	@Override
	public ServletConfig getServletConfig() {
		System.out.println("getServletConfig");
		return null;
	}

	@Override
	public String getServletInfo() {
		System.out.println("getServletInfo");
		return null;
	}

	@Override
	public void init(ServletConfig arg0) throws ServletException {
		System.out.println("init");
		
	}

	@Override
	public void service(ServletRequest arg0, ServletResponse arg1) throws ServletException, IOException {
		System.out.println("service");
	}

}
输出:
MainServleet constructed
init
service			: 每次访问都会调用一次 service
service
service
1.1.1 个方法的作用
a.public void init(ServletConfig config)
  • 由 servlet 容器调用,指示将该 servlet 放入服务
  • servlet 容器仅在实例化 servlet 之后调用 init 方法一次(说明先调用构造方法)
  • 在 servlet 可以接收任何请求之前,init 方法必须成功完成
b.public ServletConfig getServletConfig()
  • 该对象包含此 servlet 的初始化和启动参数
  • 返回的 ServletConfig 对象是传递给 init 方法的对象
c.public void service(ServletRequest req, ServletResponse res)
  • 由 servlet 容器调用,以允许 servlet 响应某个请求。

  • 此方法仅在 servlet 的 init() 方法成功完成之后调用。

1.2各个类(接口)

1.2.1 Interface ServletConfig

servlet 容器使用的 servlet 配置对象,该对象在初始化期间将信息传递给 servlet。

方法

StringgetInitParameter(String name)
EnumerationgetInitParameterNames()
ServletContextgetServletContext()
StringgetServletName()
1.2.2 Interface ServletContext
get/setAttribute()
String getContextPath() 
Servlet getServlet(String name) 
Enumeration getServlets()
ServletContext getContext(String uripath) 
1.2.3 Class GenericServlet

定义一般的、与协议无关的 servlet。要编写用于 Web 上的 HTTP servlet,请改为扩展 javax.servlet.http.HttpServlet

public abstract class GenericServl etextends Object implements Servlet, ServletConfig, Serializable
// 实现了 servlet 和 servletConfig 接口 , 并且是抽象的 
// 所有拥有了 Servlet 和 ServletConfig 的方法
String getInitParameter(String name) 
Enumeration getInitParameterNames() 
ServletConfig getServletConfig()  
ServletContext getServletContext() 
1.2.4 Class HttpServlet

提供将要被子类化以创建适用于 Web 站点的 HTTP servlet 的抽象类。
HttpServlet 的子类至少必须重写一个方法,该方法通常是以下这些方法之一:

  • doGet,如果 servlet 支持 HTTP GET 请求
  • doPost,用于 HTTP POST 请求
  • doPut,用于 HTTP PUT 请求
  • doDelete,用于 HTTP DELETE 请求
public abstract class HttpServletextends GenericServletimplements Serializable
    
protected  void doDelete(HttpServletRequest req, HttpServletResponse resp) 

protected  void doGet(HttpServletRequest req, HttpServletResponse resp) 

protected  void doHead(HttpServletRequest req, HttpServletResponse resp) 

protected  void doOptions(HttpServletRequest req, HttpServletResponse resp) 

protected  void doPost(HttpServletRequest req, HttpServletResponse resp) 

protected  void doPut(HttpServletRequest req, HttpServletResponse resp)  

void service(ServletRequest req, ServletResponse res) 
1.3 url-pattern 的匹配规则

下面的博客介绍的很仔细:

https://www.cnblogs.com/canger/p/6084846.html

二、实现servelt 的三种方式

2.1 implements servlet

在 1.1 中介绍了

2.2 extends GenericServlet

不能通过浏览器访问

2.3 extends HttpSevlet

public class HttpDemo extends HttpServlet {

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

}

三、源码分析

3.1 ServletConfig

每个 servlet 的 init() 方法是由 servlet 容器(服务器)调用的,所以在 init( )方法里面的 ServletConfig 实例其实是 servlet 容器实例化并传过去的

public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
}

四、类常见操作案例

4.1 ServletConfig

可以拿到每个 servlet 配置在 web.xml 中的信息

web.xml

  • 每个 servlet 可以配置多个参数组合,param-value 形式
<servlet>
  	
  	<servlet-name>httpDemo</servlet-name>
  	<servlet-class>com.bigguy.servlet.HttpDemo</servlet-class>
  	<init-param>
  		<param-name>encodeing</param-name>
  		<param-value>utf-8</param-value>
  	</init-param>
  	
  	<init-param>
  		<param-name>age</param-name>
  		<param-value>18</param-value>
  	</init-param>
  </servlet>

HttpServlet

this.getServletConfig().getInitParameterNames()  : 拿到所有 servlet 中的配置
this.getInitParameter(String name) : 通过名字拿到值
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		System.out.println("service");
  
    /*
    	因为 HttpServlet extends GenericServlet
    	GenericServlet implements ServletConfig 
    	所以 HttpServlet 可以直接使用 getInitParameterNames() 方法
    */
		Enumeration<String> parms = this.getInitParameterNames();
		
		while(parms.hasMoreElements()) {
			String name = parms.nextElement();
			
			String value = this.getInitParameter(name);
			
			System.out.println(name+" : "+value);
			
		}
}

4.2 ServletContext

4.2.1 拿到全局的配置信息

web.xml

  <context-param>
   <param-name>context-name</param-name>
   <param-value>context-jeck</param-value>
</context-param>

<context-param>
   <param-name>context-age</param-name>
   <param-value>context-12</param-value>
</context-param>

HttpServlet

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	
    	// 拿到ServletContext
		ServletContext context = this.getServletContext();
		
		// 拿到ServletContext 在 web.xml 中配置的信息
		Enumeration<String> contextParams = context.getInitParameterNames();
		
		while(contextParams.hasMoreElements()) {
			String name =contextParams.nextElement();
			String value = context.getInitParameter(name);
			System.out.println(name+" : "+value);
		}
		
	}
4.2.2 获取资源路径
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// 配置文件的相对路径
		String path = "/WEB-INF/db.properties";
		
		// 通过相对路径拿到绝对路径 
		String absolutePath = this.getServletContext().getRealPath(path);
		System.out.println(absolutePath);
		
		// 通过 Properties 拿到配置文件的信息
		Properties prop = new Properties();
		prop.load(new FileInputStream(absolutePath));
		
		String username =prop.getProperty("jdbc.username");
		String password =prop.getProperty("jdbc.password");
		
		System.out.println(username+" "+password);
		
	}
4.3 请求转发

重定向特点

  • 请求参数可以传递
  • 浏览器地址栏不变
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    System.out.println("HttpDemo");
	// 拿到 context 对象
    ServletContext context = this.getServletContext();

    RequestDispatcher dispatcher = context.getRequestDispatcher("/login");

    // 重定向
    dispatcher.forward(req, resp);
}
4.4 重定向

特点:

  • 请求参数失效( request域 )
  • 地址栏改变
  • resp.sendRedirect() 后 return;
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		
    System.out.println("HttpDemo");
    ServletContext context = this.getServletContext();

    resp.sendRedirect("login");
	
    // 一定需要 return 否则还会执行下面的代码

    // System.out.println("after sendRedirect()!");

    return;

}

五、HttpServletRequest and HttpServletResponse

5.1 HttpServletRequest

  • 代表了 http 协议中的一次请求
    • 可以拿到请求的所有信息(请求消息头,请求地址… )
    • 进行请求转发
    • 拿到请求的参数(form 表单的参数)
public interface HttpServletRequest extends ServletRequest{}

5.1.1 请求转发

5.1.2 请求包含

执行完一个 servlet 的service 代码,转过去执行另一个servlet 的service 代码

RefreshDemo

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    System.out.println("demo1 start!");

    // 包含 /httpdemo ,意思是马上跳过去执行httpdemo 的方法
    req.getRequestDispatcher("/httpdemo").forward(req, resp);

    // 转过来执行下面代码 
    System.out.println("demo1 end!");

}

HttpDemo

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    System.out.println("demo1 start!");

    // 包含 /httpdemo ,意思是马上跳过去执行httpdemo 的方法
    req.getRequestDispatcher("/httpdemo").forward(req, resp);

    System.out.println("demo1 end!");

}

输出

demo1 start!
HttpDemo
demo1 end!

5.2 HttpServletResponse

  • 代表了 http 协议中的一次响应
    • 输出数据到浏览器中
    • 进行重定向
5.2.1 编码问题

tomcat服务器:默认以 iso-8859 来解析文本( HttpServletResponse 是 tomcat 实例化然后传递给 service 方法中的 response 引用,此时 response 默认向浏览器输出字符串的编码为 ISO-8859 )

  • response.setCharacterEncoding(" UTF-8 "); // 改变 服务器的响应编码
  • response.setHead(“content-type”,“UTF-8”); 设置响应消息头,告知客户端(浏览器)应该用 UTF-8编码来解析response的”响应内容“
  • response.setContentType(“text/html;charset=utf-8”):将上面两个两个方法合二为一

浏览器一般:默认用 GBK 来解码

两个编码不同一造成乱码(或者查错码表)

a.字符流
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

   // resp.setHeader("Content-Type", "text/html;charset=utf-8");
   // resp.setCharacterEncoding("utf-8");
    resp.setCharacterEncoding("utf-8");

    PrintWriter pw = resp.getWriter();

    pw.write("中国 ");
}
b.字节流
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	// 告诉浏览器以 gbk解码 	
    resp.setHeader("Content-Type", "text/html;charset=gbk");
    //		resp.setCharacterEncoding("utf-8");

    ServletOutputStream out = resp.getOutputStream();

    // 输出二进制数据,以 平台默认(操作系统)的字符编码:windows 系统为 GBK
    // 查看 jdk 的 api 可知
    out.write("中国".getBytes());

}
5.2.2 文件下载
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		
		// 输入流 , 将图片放在 src 根目录下,当部署到服务器后,src 的内容放在 /WEB-INF/classes/ 下 
		String path = req.getServletContext().getRealPath("/WEB-INF/classes/中国.jpg");
		System.out.println(path);
		
		// 在 windows 系统中的分隔符 : //
		// 这里动态指定文件分隔符 
		String filename = path.substring(path.lastIndexOf(File.separatorChar)+1);
		System.out.println(filename);
		InputStream in = new FileInputStream(path);
		
		// 输出流
		ServletOutputStream out = resp.getOutputStream();
		
		// 文件名有中文,采用该方法,将不安全编码转为安全编码
		filename = URLEncoder.encode(filename, "UTF-8");
		
		// 告诉浏览器下载该文件
		resp.setHeader("content-disposition", "attachment;filename="+filename);
		
		// 指定下载的格式
		resp.setHeader("content-type", "image/jpeg");
		
		
		int len = 0;
		byte []b = new byte[1024];
		while((len=in.read(b))!=-1) {
			out.write(b, 0, len);
		}
	}
5.2.3 验证码
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	
    	// 指定时间刷新页面
		// resp.setHeader("refresh", "3");
    
		// 设置浏览器不缓存 
		resp.addHeader("pragma", "no-cache");
		resp.addHeader("cache-control", "no-cache");
		resp.addHeader("expires", "0");
		
		ValidateCode vc = new ValidateCode(200, 50, 4, 4);
		
		String code = vc.getCode();
		System.out.println(code);
		
		vc.write(resp.getOutputStream());
		
	}
5.2.4 指定时间跳转到页面(定时刷新)
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		
		resp.setContentType("text/html;charset=utf-8");
		
		resp.getWriter().println("注册成功,三秒跳到主页");
		
		String url = req.getContextPath()+"/index.jsp";
		
		resp.setHeader("refresh", "3;url="+url);
		
	}

六、回话技术

6.1 Cookie

  • 客户端技术
  • 读入 cookie:
    • request.getCookie()
  • 写入 cookie:
    • response.addCookie()
  • 方法
    • setMaxAge(): 设置最大的生存时间
      • 正数:表示生存时间,单位为 “秒”,存在磁盘中
      • 负数:表示只存在内存中,浏览器关闭, cookie 就没了
      • 0 :删除 cookie(不关闭 浏览器 cookie 也没了)
  • Cookie 是根据访问的路径决定是否携带 cookie到服务器的
    • 保证了安全性, request.getCookies() 拿到的只是该路径下的cookies 而不是浏览器中所有的 cookie
  • path: 默认就是写 cookie 那个程序的访问路径的上一层路径
    • 如:http://localhost:8080/day10/servlet/cookieDemo
    • Path: /day10/servlet/
    • 当客户端访问服务器其它资源时,根据访问路径来决定是否带着cookie到服务器
    • 当访问的路径是以cookie中path开头的路径,就带cookie,否则就不带。
    • 设置path:
      • cookie.setPath("/") :表示在项目的根目录,所有的请求都能访问该cookie
        • localhost:/ServletDemo/a
        • localhost:/ServletDemo/a/b :都能够访问得到
      • cookie.setPath(req.getContextPath()):和上面效果一样
a.记录上次访问的时间
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	
		PrintWriter pw = resp.getWriter();
		
		// 通过 request 拿到 cookies[]
		Cookie[] cookies = req.getCookies();
		
		if( cookies!=null && cookies.length>0) {
			for (Cookie cookie : cookies) {
				if( "lastAccessTime".equals(cookie.getName())) {
					// 通过 name 拿到 value
					String ltime = cookie.getValue();
					Time time = new Time(new Long(ltime));
					pw.write(time.toLocaleString());
				}
			}
		}
		
		// 都是保存的是 string
		Cookie cookie =  new Cookie("lastAccessTime", System.currentTimeMillis()+"");
		// 将该 cookie 添加到客户端浏览中
		resp.addCookie(cookie);

	}
b.清除 cookie
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	
		Cookie cookie =  new Cookie("lastAccessTime","");
		
		// 设置 cookie 的路径防止删错 cookie
		cookie.setPath("/");
		// 设置有效时间 :0 表示清除 cookie
		cookie.setMaxAge(0);		// 单位为 秒
		// 将该 cookie 添加到客户端浏览中
		resp.addCookie(cookie);
		
	}
c.cookie 有效条件
cookie 有效的前提:
	1. 同一个会话(同一个浏览器窗口)
	2. 另外一个浏览器窗口,如果cookie 还有效的话,需要设置 cookie 的path,并且在有效时间内,cookie存在磁盘里面了 


d.cookie 记住用户名案例

login

  • 当做登入界面
  • 注意
    • resp.setContentType(“text/html;charset=UTF-8”),以 html 的格式来解析页面字符串
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		
		// 设置响应消息头 ,以 html 的格式来解析字符串,否则在页面只是显示字符串
		resp.setContentType("text/html;charset=UTF-8");
		
		PrintWriter pw = resp.getWriter();
		
		String username = "";
		String check ="";
		
		Cookie[] cookies = req.getCookies();
		
		for(int j=0; cookies!=null && j< cookies.length; j++) {
			if("username".equals(cookies[j].getName())) {
				// 表示找到该 cookie
				username = cookies[j].getValue();
				// 能找到 cookie 说明上次的的复选框是“√”上了的
				check ="checked='checked'";
				break;			// 找到就跳出
				
			}
		}
		
		pw.print("<form action='"+req.getContextPath()+"/dologin' >");
		
		pw.print("用户名:<input type='text' name='username' value='"+username+"' /> <br/>");
		pw.print("密码:<input type='text' name='password' /> <br/>");
		pw.println("记住用户名:<input type='checkbox' name='remenber' "+check+" /><br/>");
		pw.println("登入:<input type='submit' value='登入' /><br/>");
		
		pw.print("</form>");
		
	}

DoLogin

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		
		String username = req.getParameter("username");
		String password = req.getParameter("password");
    
    	// 如果选中,值为 on 
		String remenber = req.getParameter("remenber");
		Cookie cookie = new Cookie("username", username);
		cookie.setPath("/");

		if(remenber!=null) {
			// 表示勾选了“记住用户名”操作
			// 加入 cookie
			// 设置 cookie 有效时间为 5 分钟
			cookie.setMaxAge(60*5);
			
			// 加入 cookie
			resp.addCookie(cookie);
			
		}else {
			// 清除 cookie
			cookie.setMaxAge(0);
		}
		
	}

6.2 Session

服务端的会话管理

1. HttpSession request.getSession():内部执行原理

1、获取名称为JSESSIONID的cookie的值。

2、没有这样的cookie,创建一个新的HttpSession对象,分配一个唯一的SessionID,并且向客户端写了一个名字为JSESSIONID=sessionID的cookie

3、有这样的Cookie,获取cookie的值(即HttpSession对象的值),从服务器的内存中根据ID找那个HttpSession对象:

找到了:取出继续为你服务。

找不到:从2开始。

七、监听器和拦截器

7-1 监听器

1.什么是监听器

Javaweb中的监听器是用于监听web常见对象HttpServletRequest,HttpSession,ServletContext

  • 监听器的 init() 方法会在web容器启动时调用

2.监听器的作用

  • 监听web对象创建与销毁.
  • 监听web对象的属性变化
  • 监听session绑定javaBean操作.

3.web监听器介绍

在 eclipse 中创建 listener 中有如下图

1.监听web对象创建与销毁的监听器

  • ServletContextListener
  • HttpSessionListener
  • ServletRequestListener

2.监听web对象属性变化

添加 / 删除 / 修改属性 都会被监听到

  • ServletContextAttributeListener
  • HttpSessionAttributeListener
  • ServletRequestAttributeListener

3.监听session绑定javaBean

  • HttpSessionBindingListener
  • HttpSessionActivationListener
7.1 监听器例子
7.1.1 监听web对象创建与销毁的监听器

a. MyServletContextListener

public class MyServletContextListener implements ServletContextListener {

	@Override
	public void contextInitialized(ServletContextEvent sce) {
		System.out.println("contextInitialized");
	}

	@Override
	public void contextDestroyed(ServletContextEvent sce) {
		System.out.println("contextDestroyed");
	}

}

b.MyRequestListener

每一次的访问 servlet 都会被监听到,执行 下面两个方法

public class MyRequestListener implements ServletRequestListener{

	public void requestDestroyed(ServletRequestEvent sre) {
		System.out.println("request Destroyed!");
	}

	@Override
	public void requestInitialized(ServletRequestEvent sre) {
		System.out.println("request Initialized!");
		
	}
	
}

c.MyHttpSessionListener

  • 创建:取决于是否含有 jsessinid ,如果有可能会获取一个已经存在的 session,否则重新创建一个
  • 销毁:
    • 默认超时 30分钟
    • 关闭服务器
    • session.invadate() 方法
    • setMaxInactiveInterval( int time ):可以设置超时时间
public class MyHttpSessionListener implements HttpSessionListener{

	@Override
	public void sessionCreated(HttpSessionEvent se) {
		System.out.println("sessionCreated");
	}

	@Override
	public void sessionDestroyed(HttpSessionEvent se) {
		System.out.println("session Destroyed!");
		
	}
}
7.1.2 监听web对象属性变化

a. ServletRequestAttributeListener

监听器可以在 web.xml 中配置,也可以用 @WebListener 声明

ServletRequestAttributeEvent 对象可以拿到 “属性名 – value"

@WebListener
public class MyServletRequestAttributeListener implements ServletRequestAttributeListener {
 
    // 每次 req 域中remove属性都会触发该方法
    public void attributeRemoved(ServletRequestAttributeEvent srae)  { 
    	System.out.println("attributeRemoved");
         
    }

    // 每次 req 域中增加属性都会触发该方法
    public void attributeAdded(ServletRequestAttributeEvent srae)  { 
    	System.out.println("attributeAdded");
        // username : jeck
        // password : 1234
    	System.out.println(srae.getName()+" : "+srae.getValue());
    }
	
    // 每次 req 域中 覆盖 属性都会触发该方法
    public void attributeReplaced(ServletRequestAttributeEvent srae)  { 
         
    }
	
}

触发事件的servlet

public class LoginServlet extends HttpServlet{

	protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		
		String age = req.getParameter("age");
		String username = req.getParameter("username");
		String password = req.getParameter("password");
        
        // 触发事件
		req.setAttribute("username", username);
		req.setAttribute("password", password);
	
	}
}

7-2 拦截器

作用:在转到对于的servlet 之前,被 filter 拦截进行处理

拦截器可以配置在 web.xml 中也可以用 @WebFilter(url) 注解

  • web.xml
    • 方便控制 拦截器先后顺序(通过 的先后顺序决定)
  • @WebFilter(url):通过文件名的ascii 字母优先顺序控制
  • 须知:
    • web 容器启动时,调用 filter 的 init() 方法
    • 多个拦截器拦截一个请求时,形成一条拦截器链(filter chain ),拦截器链的执行顺序由 的先后顺序决定

例子

chain.doFilter(request,response)

  • 表示放行,如果匹配多个拦截器形成拦截器链则表示放行到下个拦截器
  • 如果不在 doFilter()方法中调用chain.doFilter(request,response)则请求只会到达这个方法,不会到达指定的 servlet
@WebFilter("/*")
public class LoginFilter implements Filter {

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		
		System.out.println("LoginFilter doFilter");
		// 表示放行,否则只会执行到这里 
		chain.doFilter(request, response);
	}

	public void init(FilterConfig fConfig) throws ServletException {
		
		System.out.println("LoginFilter init()");
		
	}

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

}

登入拦截器

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		
		chain.doFilter(request, response);
		
		HttpServletRequest req = (HttpServletRequest)request;
		
		HttpSession session = req.getSession();
		
		if( session.getAttribute("user")==null) {
			System.out.println("用户还没有登入,不可以跳转到其他的页面!");
			
			HttpServletResponse resp = (HttpServletResponse)response;
			// 跳转到登入界面
			resp.sendRedirect("/login.jsp");
			return;
			
		}else {
			chain.doFilter(request, response);
		}
	}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值