自定义MVC

文章目录

一、什么是MVC

二、初版增删改查

三、反射版增删改查

四、MVC原理图

👀优化上述代码

1)让中央控制器动态加载存储子控制器

2)参数传递封装优化

BookAction类实现ModelDriven接口

3)对于方法执行结果转发重定向优化

4)框架配置可变 


一、什么是MVC

自定义MVC的概念(不太懂去看看这篇,比较详细的介绍了MVChttps://blog.csdn.net/weixin_67450855/article/details/124335875?spm=1001.2014.3001.5502):
MVC全称名是Model(模型层) View(视图层) controller(控制层)

MVC模式分工明确各司其职,同时也具有高类聚低耦合的特点。

 为什么会有自定义MVC:
当我们自己项目要去写这么多servlet以及dao包中的方法时,往往都会出现一个共同的问题,那就是代码思路基本重复,从而就诞生了自定义MVC,帮助我们简化代码,减少类。 

二、初版增删改查

Servlet中的内容 

新建一个jsp页面 

<h3>目前增删改查的方法</h3>
<a href="${pageContext.request.contextPath }/book/add">增加</a>
<a href="${pageContext.request.contextPath }/book/del">删除</a>
<a href="${pageContext.request.contextPath }/book/edit">修改</a>
<a href="${pageContext.request.contextPath }/book/list">查询</a>

运行效果

 上述方法有什么问题吗?🤔⬇⬇

关于单个实体/表操作场景越多,需要新建的类也就越多,造成项目中的类数量过于庞大

我们可以用反射来解决这个问题👀👀

三、反射版增删改查

新建一个Servlet在 BookServlet 中进行代码操作

package com.hemingxiang.web;

import java.io.IOException;
import java.lang.reflect.Method;

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 com.sun.net.httpserver.HttpServer;
@WebServlet("/book.action")
public class BookServlet extends HttpServlet{

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doPost(req, resp);
	}
	
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		//为了区分当前请求的目的,增删查改的目的,就从前台讲要调用的方法名传递到后台
		if("add".equals(methodName)) {//如果前台传递到后是add的请求就调用增加方法
			add(req,resp);
		}
		//如果前台传递到后是del的请求就调用删除方法
		else if("del".equals(methodName)) {
			del(req,resp);
		}
		else if("edit".equals(methodName)) {
			edit(req,resp);
		}
		else if("list".equals(methodName)) {
			list(req,resp);
		}
		else if("load".equals(methodName)) {
			load(req,resp);
		}
	}

	private void list(HttpServletRequest req, HttpServletResponse resp) {
		System.out.println("在同一个servlet中调用list方法");
	}

	private void edit(HttpServletRequest req, HttpServletResponse resp) {
		System.out.println("在同一个servlet中调用edit方法");
	}

	private void del(HttpServletRequest req, HttpServletResponse resp) {
		System.out.println("在同一个servlet中调用del方法");
	}

	private void add(HttpServletRequest req, HttpServletResponse resp) {
		System.out.println("在同一个servlet中调用add方法");
	}
}

 在原有的jsp页面添加

<h3>类数量过多问题优化</h3>
<a href="${pageContext.request.contextPath }/book.action?methodName=add">增加</a>
<a href="${pageContext.request.contextPath }/book.action?methodName=del">删除</a>
<a href="${pageContext.request.contextPath }/book.action?methodName=edit">修改</a>
<a href="${pageContext.request.contextPath }/book.action?methodName=list">查询</a>
<a href="${pageContext.request.contextPath }/book.action?methodName=load">回显</a>

运行效果同上,代码使用方法不同

 当新增了业务,除了需要添加该业务对应的方法(load),同时还要改动原有的代码,对BookServlet进行优化

优化源码

@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		//为了区分当前请求的目的,增删查改的目的,就从前台讲要调用的方法名传递到后台
		String methodName=req.getParameter("methodName");
		//methodName可能是add/del/edit/list/load...
		//前台传递说明方法就调用当前类的对应方法
		try {
			Method m = this.getClass().getDeclaredMethod(methodName, HttpServletRequest.class,HttpServletResponse.class);
			m.setAccessible(true);
			//调用当前类实例的methodName方法,你传什么我调什么
			m.invoke(this, req,resp);
		} catch (Exception e) {
			e.printStackTrace();
		}


    private void load(HttpServletRequest req, HttpServletResponse resp) {
		System.out.println("在同一个servlet中调用load方法");
	}

 此时我们在实验一遍,在jsp页面中添加相同代码,将BookServlet中代码拷贝到对应的orderServlet中,运行还是同上⬇⬇⬇⬇

<h3>订单类增删改查</h3>
<a href="${pageContext.request.contextPath }/order.action?methodName=add">增加</a>
<a href="${pageContext.request.contextPath }/order.action?methodName=del">删除</a>
<a href="${pageContext.request.contextPath }/order.action?methodName=edit">修改</a>
<a href="${pageContext.request.contextPath }/order.action?methodName=list">查询</a>
<a href="${pageContext.request.contextPath }/order.action?methodName=load">回显</a>

 抛出两个问题:反射相关代码,在每一个实体类对应的servlet中都存在
                          每一个servlet中都有doget,dopost方法

⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇

 四、MVC原理图

 接下来我们根据这张图,来部署代码解决上面的两个问题⬆⬆⬆⬆⬆⬆⬆⬆⬆⬆

 中央控制器(对应图中ActionServlet)

package com.hemingxiang.framework;

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

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 com.hemingxiang.web.BookAction;
/**
 * 中央控制器
 * 	主要职能:接收浏览器请求,找到对应的处理人
 * 谁来发请求,中央控制器都会接收,但不处理任何业务逻辑,找对应处理人
 * @author Administrator
 *
 */
@WebServlet("*.action")
public class DispatcherServlet extends HttpServlet{

	//在map集合中找到那个变量 action
	private Map<String, Action> actions=new HashMap<String, Action>();
	//map中最初没有值,没有值怎么拿的到 所以初始化一下
	//程序启动时,只会加载一次
	@Override
	public void init() throws ServletException {
		actions.put("/book", new BookAction());
		//actions.put("/order", new BookAction());
	}
	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doPost(req, resp);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		//拿地址http://localhost:8080/mvc/book.action?methodName=list
		String uri = req.getRequestURI();
		//要拿到 /book 就是最后一个 / 到最后一个 . 为止
		uri=uri.substring(uri.lastIndexOf("/"), uri.lastIndexOf("."));
		
		Action action = actions.get(uri);
        action.execute(req, resp);	
	}
}

 

 Action

package com.hemingxiang.framework;

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

/**
 * 子控制器:
 * 	对应请求的处理人
 * @author Administrator
 *
 */
public interface Action {

	 void execute(HttpServletRequest req, HttpServletResponse resp);
	
}

ActionSupport实现Action

package com.hemingxiang.framework;

import java.lang.reflect.Method;

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

public class ActionSupport implements Action{

	@Override
	public void execute(HttpServletRequest req, HttpServletResponse resp) {
		//为了区分当前请求的目的,增删查改的目的,就从前台讲要调用的方法名传递到后台
		String methodName=req.getParameter("methodName");
		//methodName可能是add/del/edit/list/load...
		//前台传递说明方法就调用当前类的对应方法
		try {
			Method m = this.getClass().getDeclaredMethod(methodName, HttpServletRequest.class,HttpServletResponse.class);
			m.setAccessible(true);
			//调用当前类实例的methodName方法,你传什么我调什么
			m.invoke(this, req,resp);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

BookAction继承ActionSupport

package com.hemingxiang.web;

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

import com.hemingxiang.framework.Action;
import com.hemingxiang.framework.ActionSupport;

public class BookAction extends ActionSupport{
	
	private void list(HttpServletRequest req, HttpServletResponse resp) {
		System.out.println("在同一个servlet中调用list方法");
	}
	
	private void load(HttpServletRequest req, HttpServletResponse resp) {
		System.out.println("在同一个servlet中调用load方法");
	}

	private void edit(HttpServletRequest req, HttpServletResponse resp) {
		System.out.println("在同一个servlet中调用edit方法");
	}

	private void del(HttpServletRequest req, HttpServletResponse resp) {
		System.out.println("在同一个servlet中调用del方法");
	}

	private void add(HttpServletRequest req, HttpServletResponse resp) {
		System.out.println("在同一个servlet中调用add方法");
	}
}

👀优化上述代码

1)让中央控制器动态加载存储子控制器

自定义MVC的工作原理说明了,其中,中央控制器起到了接收浏览器请求,找到对应的处理人的一个作用,但是还存在漏洞⬇⬇⬇⬇⬇⬇

比如说:在每一次顾客访问前台时,有很多部门,比如说设计部门,管理部门等每当访问一次,就要new一个此类

public void init() throws ServletException {
 actions.put("/book", new BookAction());
 actions.put("/order", new OrderAction());
 }

 该怎么解决这个问题嘞?我们可以用到(使代码更加灵活XML建模https://blog.csdn.net/weixin_67450855/article/details/125292837?spm=1001.2014.3001.5502

新建一个config.xml

<config>

	<action path="/book" type="com.hemingxiang.web.BookAction">
		<forward name="failed" path="/login.jsp" redirect="false" />
		<forward name="success" path="/main.jsp" redirect="true" />
	</action>
	
</config>

再对中央控制器进行优化,上面我们是这样编写代码的

现在我们根据下面3点来部署代码

  • 通过url来找到config文件中对应的action对象
  • 然后通过该对象来取到路径名servlet.BookAction
  • 找到对应的方法执行

改进中央控制器DispatcherServlet源码 :

package com.hemingxiang.framework;
 
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
 
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 com.hemingxiang.web.BookAction;
 
/**
 * 中央控制器:
 * 主要职能:接收浏览器请求,找到对应的处理人
 * 谁来发请求,中央控制器都会接收,但不处理任何业务逻辑,找对应处理人
 * @author Administrator
 *
 */
@WebServlet("*.action")
public class DispatcherServlet extends HttpServlet{
 
	/**
	 * 通过建模我们可以知道,最终ConfigModel对象会包含config.xml中的所有子控制器信息
	 * 同时为了解决中央控制器能够动态加载保存子控制器的信息,那么我们只需要引入configModel对象即可
	 */
	private ConfigModel configModel;
	
//	程序启动时,只会加载一次
	@Override
	public void init() throws ServletException {
 
		try {
			configModel=ConfigModelFactory.build();
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
 
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doPost(req, resp);
	}
	
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		//http:localhost:8080/mvc/book.action?methodName=list
		String uri=req.getRequestURI();
//		拿到/book,就是最后一个“/”到最后一个“.”为止
		uri=uri.substring(uri.lastIndexOf("/"),uri.lastIndexOf("."));
 
//		相比于上一种从map集合获取子控制器,当前需要获取config.xml中的全路径名,然后反射实例化
		ActionModel actionModel = configModel.pop(uri);
		if(actionModel==null) {
			throw new RuntimeException("action 配置错误");
		}
		String type = actionModel.getType();
//		type是Action子控制器的全路径名
		try {
			Action action= (Action) Class.forName(type).newInstance();
			action.execute(req, resp);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

 在原Demo1测试,点击增加后

 注意一个小问题,config.xml中的路径名path,不正确的话会报配置错误,也就是

 2)参数传递封装优化

在Demo1中添加(以增加为列):

<h3>参数传递封装优化</h3>
<a href="${pageContext.request.contextPath }/book.action?methodName=add & bid=989898 & bname=laoliu & price=89">增加</a>
<a href="${pageContext.request.contextPath }/book.action?methodName=del">删除</a>
<a href="${pageContext.request.contextPath }/book.action?methodName=edit">修改</a>
<a href="${pageContext.request.contextPath }/book.action?methodName=list">查询</a>
<a href="${pageContext.request.contextPath }/book.action?methodName=load">回显</a>

⬆⬆⬆ jsp页面中的值拿到BookAction中

package com.hemingxiang.web;

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

import com.hemingxiang.entity.Book;
import com.hemingxiang.framework.Action;
import com.hemingxiang.framework.ActionSupport;

public class BookAction extends ActionSupport{
	
	private void list(HttpServletRequest req, HttpServletResponse resp) {
		System.out.println("在同一个servlet中调用list方法");
	}
	
	private void load(HttpServletRequest req, HttpServletResponse resp) {
		System.out.println("在同一个servlet中调用load方法");
	}

	private void edit(HttpServletRequest req, HttpServletResponse resp) {
		System.out.println("在同一个servlet中调用edit方法");
	}

	private void del(HttpServletRequest req, HttpServletResponse resp) {
		System.out.println("在同一个servlet中调用del方法");
	}

	private void add(HttpServletRequest req, HttpServletResponse resp) {
		String bid = req.getParameter("bid");
		String bname = req.getParameter("bname");
		String price = req.getParameter("price");
		Book book=new Book();
		book.setBid(Integer.valueOf(bid));
		book.setBname(bname);
		book.setPrice(Float.valueOf(price));
		System.out.println("在同一个servlet中调用add方法");
	}
}

 新建一个Book实体类

package com.hemingxiang.entity;

public class Book {
	
	private int bid;
	private String bname;
	private float price;
	
	public int getBid() {
		return bid;
	}
	public void setBid(int bid) {
		this.bid = bid;
	}
	public String getBname() {
		return bname;
	}
	public void setBname(String bname) {
		this.bname = bname;
	}
	public float getPrice() {
		return price;
	}
	public void setPrice(float price) {
		this.price = price;
	}
	
	public Book() {
		super();
	}
	public Book(int bid, String bname, float price) {
		super();
		this.bid = bid;
		this.bname = bname;
		this.price = price;
	}
	@Override
	public String toString() {
		return "Book [bid=" + bid + ", bname=" + bname + ", price=" + price + "]";
	}
}

这样做可行,但是界面联合了多张表的话,那么在传递参数时就不止3个属性,反复的写String bname = req.getParameter("bname"); 

解决方案:建立一个模型驱动接口,使BookAction实现该接口,在中央控制器中将所有要接收的参数封装到模型接口中,从而达到简便的效果

ModelDriven类(驱动接口类)

package com.hemingxiang.framework;
 
/**
 * 模型驱动接口,接收前台JSP传递的参数,并且封装到实体类中
 * @author Administrator
 *
 * @param <T>
 */
public interface ModelDriven<T> {
//	拿到将要封装的类实例   ModelDriven.getModel() ---> new Book();
	T getModel();
}

中央控制器DispatcherServlet

@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		//http:localhost:8080/mvc/book.action?methodName=list
		String uri=req.getRequestURI();
//		拿到/book,就是最后一个“/”到最后一个“.”为止
		uri=uri.substring(uri.lastIndexOf("/"),uri.lastIndexOf("."));
//		Action action = actions.get(uri);
//		相比于上一种从map集合获取子控制器,当前需要获取config.xml中的全路径名,然后反射实例化
		ActionModel actionModel = configModel.pop(uri);
		if(actionModel==null) {
			throw new RuntimeException("action 配置错误");
		}
		String type = actionModel.getType();
//		type是Action子控制器的全路径名
		try {
			Action action= (Action) Class.forName(type).newInstance();
//			action是bookAction
			if(action instanceof ModelDriven) {
				ModelDriven md=(ModelDriven) action;
//				model指的是bookAction中的book实例
				Object model = md.getModel();
//				要给model中的属性赋值,要接收前端jsp参数   req.getParameterMap()
//				PropertyUtils.getProperty(bean, name)
//				将前端所有的参数值封装进实体类
				BeanUtils.populate(model, req.getParameterMap());
				System.out.println(model);
			}
				
//			正式调用方法前,book中的属性要被赋值
			action.execute(req, resp);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

BookAction类实现ModelDriven接口

package com.hemingxiang.web;

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

import com.hemingxiang.entity.Book;
import com.hemingxiang.framework.Action;
import com.hemingxiang.framework.ActionSupport;

public class BookAction extends ActionSupport{
	
	private void list(HttpServletRequest req, HttpServletResponse resp) {
		System.out.println("在同一个servlet中调用list方法");
	}
	
	private void load(HttpServletRequest req, HttpServletResponse resp) {
		System.out.println("在同一个servlet中调用load方法");
	}

	private void edit(HttpServletRequest req, HttpServletResponse resp) {
		System.out.println("在同一个servlet中调用edit方法");
	}

	private void del(HttpServletRequest req, HttpServletResponse resp) {
		System.out.println("在同一个servlet中调用del方法");
	}

	private void add(HttpServletRequest req, HttpServletResponse resp) {
		//String bid = req.getParameter("bid");
		//String bname = req.getParameter("bname");
		//String price = req.getParameter("price");
		//Book book=new Book();
		//book.setBid(Integer.valueOf(bid));
		//book.setBname(bname);
		//book.setPrice(Float.valueOf(price));
		System.out.println("在同一个servlet中调用add方法");
	}

    @Override
	public Book getModel() {
		return null;
	}
}

解决参数冗余问题关键代码(将该对象要接受的参数值封装到对应的对象中):
        BeanUtils.populate(bean, req.getParameterMap());

 3)对于方法执行结果转发重定向优化

例:跳转增加页面时是转发,跳转查询界面是重定向

原Demo1

<a href="${pageContext.request.contextPath }/book.action?methodName=add & bid=989898 & bname=laoliu & price=89">增加</a>
<a href="${pageContext.request.contextPath }/book.action?methodName=list">查询</a>
 

 config.xml

<config>

	<action path="/book" type="com.hemingxiang.web.BookAction">
		<forward name="failed" path="/Demo2.jsp" redirect="false" />
		<forward name="success" path="/Demo3.jsp" redirect="true" />
	</action>
	
</config>

Demo2

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
转发页面
</body>
</html>

Demo3

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
重定向
</body>
</html>

思路: 

  • 当点击增加或者编辑时,首先跳转的是中央控制器类(DispatchServlet):获取到url,url将决定要跳到哪一个实体类,
  • 之后进入ActionSupport(子控制器接口实现类)通过methodname要调用什么方法,
  • 再然后进入到BookAction中调用methodname方法,找到对应的返回值,
  • 通过返回值在进入到config文件找到path属性,之后在中央控制器中进行判断,来决定是重定向还是转发。

 中央控制器DispatcherServlet

String result = action.execute(req, resp);
			ForwardModel forwardModel = actionModel.pop(result);
//			if(forwardModel==null)
//				throw new RuntimeException("forward config error");
			String path = forwardModel.getPath();
//			拿到是否需要转发的配置
			boolean redirect = forwardModel.isRedirect();
			if(redirect)
				//${pageContext.request.contextPath}
				resp.sendRedirect(req.getServletContext().getContextPath()+path);
			else
				req.getRequestDispatcher(path).forward(req, resp);
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

 子控制器Action

package com.hemingxiang.framework;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
/**
 * 子控制器:
 * 对应请求的处理人
 * @author  Administrator
 *
 */
public interface Action {
	String execute(HttpServletRequest req, HttpServletResponse resp);
}
 

 ActionSupport

package com.hemingxiang.framework;
 
import java.lang.reflect.Method;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
public class ActionSupport implements Action{
 
	@Override
	public String execute(HttpServletRequest req, HttpServletResponse resp) {
		String methodName = req.getParameter("methodName");
//		methodName可能是多种方法
//		前台传递什么方法就调用当前类的对应方法
		try {
			Method m=this.getClass()// BookServlet.Class
			.getDeclaredMethod(methodName, HttpServletRequest.class,HttpServletResponse.class);
			m.setAccessible(true);
//			调用当前类实例的 methodName 方法
			return (String) m.invoke(this, req,resp);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
}

 BookAction图中标记的为返回值

运行结果: 

4)框架配置可变 

如果将config.xml文件名改成mvc.xml,那该程序是否还可以因运行

答案肯定是不可以滴~

因为 ConfigModelFactory 类里定义了它的默认路径名⬇⬇⬇⬇⬇

 在DispatcherServlet里的init初始化里设置它的配置地址

@Override
	public void init() throws ServletException {
//		actions.put("/book", new BookAction());
//		actions.put("/order", new BookAction());
		try {
			//配置地址
//			getInitParameter的作用是拿到web.xml中的servlet信息配置的参数
			String configLocation = this.getInitParameter("configLocation");
			if(configLocation==null||"".equals(configLocation))
				configModel=ConfigModelFactory.build();
			else
				configModel=ConfigModelFactory.build(configLocation);	
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

 Web-XML

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
  <display-name>MVC</display-name>
	<servlet>
		<servlet-name>mvc</servlet-name>
		<servlet-class>com.hemingxiang.framework.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>configLocation</param-name>
			<param-value>/yangzong.xml</param-value>
		</init-param>
	</servlet>
	<servlet-mapping>
	<url-pattern>*.action</url-pattern>
	</servlet-mapping>
</web-app>

本次文章所用到的类与包

--------------------------------------------------------------------------------------------------------

beybey~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值