Spring框架的实现

MVC(Model + View + Controller)
M-Model模型:职责是负责业务逻辑。包含两层:业务数据和业务处理逻辑。比如实体类、DAO、Service都属于模型层
V-View视图:职责是负责显示界面和用户交互(收集用户信息)属于视图的组件是不包含业务逻辑和控制逻辑的JSP。
C-Controller控制器:控制器是模型层M和视图层V之间的桥梁,用于控制流程,比如:在Servlet项目中的单一控制器ActionServlet
此案例演示了Spring框架里的getBean两种重载的方法

原型开发与测试:

访问login.do


1.编写配置文件 conf/context.xml

<?xml version="1.0" encoding="UTF-8"?>
 <beans>
     <!-- 声明控制器组件 -->
     <bean class="cn.tedu.tstore.controller.LoginController"/>
 </beans>

编写文件conf.properties

#properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/tedustore
username=root
password=
MaxActive=5

2.web.xml配置文件

<servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>cn.tedu.base.web.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>config</param-name>
        <param-value>conf/context.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>*.do</url-pattern>
</servlet-mapping>
3.数据库连接类:
package cn.tedu.tstore.util;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import org.apache.commons.dbcp.BasicDataSource;

public class DBUtil {
	//引用ThreadLocal用于将连接对象绑定到当前线程
	private static ThreadLocal<Connection> c = new ThreadLocal<Connection>();
	private static BasicDataSource dataSource;
	private static String driver;
	private static String url;
	private static String username;
	private static String password;
	//读取配置文件,获取4个连接参数 conf.properties
	static{
		
		try {
			//读取resource中的配置文件
			String file="conf.properties";
			InputStream in = DBUtil.class.getClassLoader().getResourceAsStream(file);
			Properties config=new Properties();
			config.load(in);
			driver=config.getProperty("driver");
			url=config.getProperty("url");
			username=config.getProperty("username");
			password=config.getProperty("password");
			//打桩!!!
			
			int max=Integer.parseInt(config.getProperty("MaxActive"));
			dataSource = new BasicDataSource();
			dataSource.setDriverClassName(driver);
			dataSource.setUrl(url);
			dataSource.setUsername(username);
			dataSource.setPassword(password);
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
		
	}
	
	public static Connection getConnection()
		throws SQLException {
		try {
			//从当前线程中获取当前的conn对象,
			//如果第一次调用,则返回null
			Connection conn = c.get();
			//第一次没有连接时候,立即创建连接
			if(conn==null) {
				conn=dataSource.getConnection();
				//将连接对象保存到当前线程中
				c.set(conn);
			}
			return conn;
		} catch (Exception e) {
			e.printStackTrace();
			throw new SQLException(e);
		}
	}
	public static void begin() {
		Connection conn = c.get();
		if(conn!=null){
			try {
				conn.setAutoCommit(false);
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	public static void commit(){
		//从当前线程中获取连接对象,并且提交事务
		Connection conn = c.get();
		if(conn!=null){
			try {
				conn.commit();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	public static void rollback(){
		Connection conn = c.get();
		if(conn!=null){
			try {
				conn.rollback();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	public static void close(){
		Connection conn = c.get();
		if(conn!=null){
			try {
				conn.close();
				//在连接关闭以后,将连接对象从当前线程中删除
				c.remove();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
}

4.用户类:

package cn.tedu.tstore.entity;

import java.io.Serializable;
import java.util.Date;

public class User implements Serializable {
	
	private Integer id;
	private String username;
	private String password;
	private String email;
	private String mobile;
	private Date createTime;
	
	public User() {
	}

	public User(Integer id, String username, String password, String email, String mobile, Date createTime) {
		super();
		this.id = id;
		this.username = username;
		this.password = password;
		this.email = email;
		this.mobile = mobile;
		this.createTime = createTime;
	}
	

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getMobile() {
		return mobile;
	}

	public void setMobile(String mobile) {
		this.mobile = mobile;
	}

	public Date getCreateTime() {
		return createTime;
	}

	public void setCreateTime(Date createTime) {
		this.createTime = createTime;
	}

	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		User other = (User) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;
		return true;
	}

	public String toString() {
		return "User [id=" + id + ", username=" + username + ", password=" + password + ", email=" + email + ", mobile=" + mobile + ", createTime=" + createTime + "]";
	}

	
}

5.DAO类:

package cn.tedu.tstore.dao;

import cn.tedu.tstore.entity.User;

public interface UserDao {
	User findByName(String username) throws SQLException;
}

package cn.tedu.tstore.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import cn.tedu.tstore.entity.User;
import cn.tedu.tstore.util.DBUtil;

public class UserDaoImpl implements UserDao{
	private User mapRow(ResultSet rs) throws SQLException {
		User user=new User();
		user.setId(rs.getInt("id"));
		user.setUsername(rs.getString("username"));
		user.setPassword(rs.getString("password"));
		user.setEmail(rs.getString("email"));
		user.setMobile(rs.getString("mobile"));
		user.setCreateTime(rs.getTimestamp("create_time"));
		return user;
	}
	public User findByName(String username) throws SQLException {
		Connection conn = DBUtil.getConnection();
		String sql = "select id,username,password,email,mobile,create_time from user where username=?";
		PreparedStatement ps = conn.prepareStatement(sql);
		ps.setString(1, username);
		ResultSet rs = ps.executeQuery();
		while (rs.next()) {
			User user = mapRow(rs);
			return user;
		}
		return null;
	}
}

package cn.tedu.tstore.dao;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import cn.tedu.tstore.util.DBUtil;

public class ConnectionHandler implements InvocationHandler{
	private UserDao dao;
	public ConnectionHandler(UserDao dao) {
		this.dao=dao;
	}
	public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
		try {
			//打开连接
			DBUtil.getConnection();
			DBUtil.begin();
			//利用反射API执行dao的方法
			Object val = method.invoke(dao, args);
			DBUtil.commit();
			return val;
		}catch (InvocationTargetException e) {
			//发生InvocationTargetException时候,是
			//dao中被调用的方法出现了异常如 SQLException
			//找到实际dao的方法抛出的异常
			Throwable ex = e.getTargetException();
			DBUtil.rollback();
			throw ex;
		}catch(Exception e){
			e.printStackTrace();
		}finally {
			DBUtil.close();
		}
		return null;
	}
}
6.开发注解:
package cn.tedu.base.web;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
	String value();
}

package cn.tedu.base.web;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//注解将保留到程序运行期间
@Retention(RetentionPolicy.RUNTIME)
//注解只能在方法参数上使用
@Target(ElementType.PARAMETER)
public @interface Param {
	String value();
}

7.Map表类:

package cn.tedu.base.web;

import java.util.HashMap;

/**
 * 用于从控制器带回需要到JSP中显示的数据
 * 增加这个类名的目的就是为了使用反射API时候检查
 * 方法参数的类型
 */
public class ModelMap<K,V> extends HashMap<K,V>{

	private static final long serialVersionUID = 1L;
	
}

package cn.tedu.base.web;

import java.util.HashMap;

public class SessionMap<K, V> extends HashMap<K, V> {
	private static final long serialVersionUID = 1L;
}

8.JSP文件:

WEB-INF下建文件夹jsp里面有login.jsp和ok.jsp,其中login.jsp在webapp也有一份

ok.jsp:

<%@ 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>
	<h1>OK</h1>
</body>
</html>

login.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="base" scope="request" value="${pageContext.request.contextPath}"></c:set>
<!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>欢迎登录</title>
</head>
<body>
	<form action="${base}/login.do" method="post">
      <div>
        <input type="text" placeholder="User Name" name="username" value="${username}">${error}
      </div>
      <div>
        <input type="password" placeholder="Password" name="password">
      </div>
      <div class="row">
        <div>
          <button type="submit">登录</button>
        </div>
      </div>
    </form>
</body>
</html>

9.开发控制器类

package cn.tedu.tstore.controller;

import java.sql.SQLException;

import org.omg.CORBA.CTX_RESTRICT_SCOPE;

import cn.tedu.base.web.ApplicationContext;
import cn.tedu.base.web.ModelMap;
import cn.tedu.base.web.Param;
import cn.tedu.base.web.RequestMapping;
import cn.tedu.base.web.SessionMap;
import cn.tedu.tstore.dao.UserDao;
import cn.tedu.tstore.dao.UserDaoImpl;
import cn.tedu.tstore.entity.User;

public class LoginController {
	@RequestMapping("/login.do")
	public String login(@Param("username") String username,@Param("password") String password,
			ModelMap<String,Object> map,SessionMap<String, Object> session,ApplicationContext ctx) throws SQLException {
		//UserDao userDao = new UserDaoImpl();
		UserDao userDao = ctx.getBean("userDaoImpl",UserDao.class);
		User user = userDao.findByName(username);
		if(user==null) {
			map.put("error", "没有注册");
			return "login";// /WEB-INF/jsp/login.jsp
		}
		if(user.getPassword().equals(password)) {
			map.put("msg", "欢迎回来: "+user.getUsername());
			session.put("user", user);
			//return "msg";
			return "ok";
		}
		map.put("error", "密码错误");
		return "login";
	}
}

10.对象方法设置获取类:

package cn.tedu.base.web;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;

public class UrlHandler {
	private Object obj;
	private Method method;
	public UrlHandler() {
		
	}
	public UrlHandler(Object obj, Method method) {
		super();
		this.obj = obj;
		this.method = method;
	}
	public Object getObj() {
		return obj;
	}
	public void setObj(Object obj) {
		this.obj = obj;
	}
	public Method getMethod() {
		return method;
	}
	public void setMethod(Method method) {
		this.method = method;
	}
	
	/**
	 * UrlHandler 中添加执行控制器方法的方法
	 * 在当前控制器对象obj的当前方法method
	 * 参数map用于封装返回到request中的数据
	 * @throws InvocationTargetException 
	 * @throws IllegalArgumentException 
	 * @throws IllegalAccessException 
	 */
	public String execute(Map<String,String[]> paramMap,ModelMap<String,Object> map,
			SessionMap<String, Object> session,ApplicationContext ctx) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		//检查当前方法使用包含ModelMap类型的参数
		//如果包含ModelMap就传递参数调用方法
		//如果不包含参数就不传递参数调用方法
		
		//					String String[] String String[]
		//paramMap = {"user"->{"Tom"},"age"->{"10"}}
		
		//检查当前方法method的参数(Parameter)类型
		Class[] patamTypes = method.getParameterTypes();
		//如果参数长度为0,则说明method方法没有参数
		if(patamTypes.length==0) {
			Object val = method.invoke(obj);
			return val.toString();
		}
		//有参数的情况,检查参数的类型,根据参数的类型
		//顺序创建目标参数数组
		Object[] param = new Object[patamTypes.length];
		
		//检测每个参数上的注解
		Annotation[][] anns = method.getParameterAnnotations();
		
		//根据参数类型来拼凑参数
		//param    = {1,0.5,map}
		//patamTypes = {int.class,double.class,ModelMap.class}
		for(int i=0; i<patamTypes.length;i++) {
			//获取当前参数的注解ann
			Annotation[] ann = anns[i];
			//获取Patam 注解
			Param p = null;//没有注解
			if(ann.length==1) {
				p = (Param)ann[0];//有Param 注解
			}
			if(p!=null){
	 			String key=p.value();
	 			//key 为 user 或 age 
	 			//根据key获取表单中的参数值
	 			String[] paramValues=
	 					paramMap.get(key);
	 			if(paramValues==null){
	 				continue;
	 			}
	 			//paramValues的参数情况参考如下:
	 			//paramValues={"Tom"}
	 			//paramValues={"10"}
	 			//需要根据目标参数的类型,进行类型转换
	 			Class type=patamTypes[i];
	 			if(type == String.class){
	 				//如果是字符串类型,就不需要转换类型了
	 				param[i]=paramValues[0];
	 			}else if(type == int.class || 
	 					type==Integer.class){
	 				//将paramValues中的数据转换为 整数
	 				//填加到目标参数的数组中
	 				param[i]=Integer.parseInt(
	 					paramValues[0]);
	 			}else if(type==double.class||
	 					type==Double.class){
	 				param[i]=Double.parseDouble(
		 					paramValues[0]);
	 			}else if(type==float.class||
	 					type==Float.class){
	 				param[i]=Float.parseFloat(
		 					paramValues[0]);
	 			}else if(type==long.class||
	 					type==Long.class){
	 				param[i]=Long.parseLong(
		 					paramValues[0]);
	 			}else if(type==String[].class){
	 				//处理字符串数组情况
	 				param[i]=paramValues;
	 			}
	 		}else{//p == null的情况下要处理ModelMap
		 		//type是按照“顺序”出现的,目标中参数的类型
		 		Class type=patamTypes[i];
		 		if(type==ModelMap.class){
		 			//如果参数类型是ModelMap则传递参数map
		 			param[i]=map;
		 		}else if(type==SessionMap.class){
		 			//注入Session集合
		 			param[i]=session;
		 		}else if(type==ApplicationContext.class){
		 			param[i]=ctx;
		 		}
	 		}
		}
		
		Object val = method.invoke(obj,param);
		return val.toString();
	}
	
	@Override
	public String toString() {
		return "UrlHandler [obj=" + obj + ", method=" + method + "]";
	}
	
}

11.解析context.xml文件类:

package cn.tedu.base.web;

import java.io.InputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

/**
 * Application 应用
 * Context 上下文,管家
 * @author soft01
 *
 */
public class ApplicationContext {
	private Map<String, Object> beans=new HashMap<String, Object>();
	public ApplicationContext() {
		
	}
	/**
	 * 根据配置文件初始化全部对象
	 * @param cfg xml配置文件
	 */
	public ApplicationContext(String cfg) {
		init(cfg);
	}
	public void init(String cfg) {
		try {
			InputStream in = getClass().getClassLoader().getResourceAsStream(cfg);
			SAXReader reader = new SAXReader();
			Document doc = reader.read(in);
			Element root = doc.getRootElement();
			List<Element> list = root.elements("bean");
			for(Element e : list) {
				String className = e.attributeValue("class");
				Class cls = Class.forName(className);
				Object obj = cls.newInstance();
				String name = cls.getName();
				//name = "cn.tedu.DemoBean"
				name = name.substring(name.lastIndexOf(".")+1);
				String key = name.substring(0,1).toLowerCase()+name.substring(1);
				//key = "demoBean"
				System.out.println(key);
				beans.put(key, obj);
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}
	
	public Collection<Object> getBeans(){
		//Map 类型上有一个方法values() 用于返回
		//Map 中全部的value对象
		return beans.values();
	}

	public <T> T getBean(String name, Class<T> cls){
		return (T)beans.get(name);
	}
	
	public String toString() {
		return "ApplicationContext [beans=" + beans + "]";
	}
}

12.找注解方法类:

package cn.tedu.base.web;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * 根据用户请求的路径,获取对应的控制器对象和方法
 * 并且执行这个方法
 * @author soft01
 *
 */
public class HandlerMapping {
	//map 中的key是用户请求的url,map中的value是
	//对象和控制方法组成的一个实例
	private Map<String,UrlHandler> urlMap = new HashMap<String,UrlHandler>();
	
	//init方法用于根据控制器对象初始化map集合
	public void init(Collection<Object> controllers) {
		for(Object object : controllers) {
			//object = DemoController
			//利用反射API获取类上注解
			// cls = DemoController
			Class cls = object.getClass();
			//获取类上标注的注解
			RequestMapping rm = (RequestMapping)cls.getAnnotation(RequestMapping.class);
			String path1 = "";
			//获取注解上标注的值
			if(rm!=null) {
				path1 = rm.value();
			}
			System.out.println(path1);
			
			//解析方法上的注解
			//找到全部方法信息
			Method[] methods = cls.getDeclaredMethods();
			for(Method method : methods) {
				//找到方法上声明的注解
				RequestMapping rm2 = method.getAnnotation(RequestMapping.class);
				System.out.println("rm2");
				//如果没有找到注解的方法就跳过这个方法
				if(rm2==null) {
					continue;
				}
				String sub = rm2.value();
				System.out.println(sub);
				String path = path1+sub;
				//添加到map
				urlMap.put(path, new UrlHandler(object,method));
			}
		}
	}
	//根据用户提交的URL路径获取(对象和方法)UrlHandler
	public UrlHandler getUrlHandler(String path) {
		return urlMap.get(path);
	}
	
	public String toString() {
		return "HandlerMapping [urlMap=" + urlMap + "]";
	}
	
}

13.编写Servlet

package cn.tedu.base.web;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.Map;

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


/**
 * Servlet implementation class DispatcherServlet
 */
public class DispatcherServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	private ApplicationContext applicationContext;
	private HandlerMapping handlerMapping;
	/**
	 * Servlet初始化方法,加载控制器类
	 */
	public void init() throws ServletException {
		//读取context.xml
		//解析出类名
		String filename = getServletConfig().getInitParameter("config");
		applicationContext = new ApplicationContext(filename);
		System.out.println(applicationContext);
		handlerMapping = new HandlerMapping();
		//getBeans 方法获取 applicationContext 中
		//创建的全部控制器对象
		handlerMapping.init(applicationContext.getBeans());
	}
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doPost(request,response);
	}


	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("Hello World!");
		
		try {
			//获得用户请求时候的URL路径
			String url = request.getRequestURI();
			//删除url中开头的Context Path
			String contextPath = request.getContextPath();
			if(url.startsWith(contextPath)) {
				url=url.substring(contextPath.length());
			}                                                                                                                  
			
			//查找需要执行的对象和方法
			UrlHandler handler = handlerMapping.getUrlHandler(url);
			Map<String,String[]> paramMap = request.getParameterMap();
			
			//传递Session参数
			SessionMap<String, Object> sessionMap= new SessionMap<String, Object>();
			HttpSession session=request.getSession();
			Enumeration<String> en=session.getAttributeNames();
			while(en.hasMoreElements()){
				String name=en.nextElement();
				sessionMap.put(name, session.getAttribute(name));
			}
			
			//利用UrlHandler执行控制器方法
			ModelMap<String,Object> map = new ModelMap<String,Object>();
			//将全部表单参数paramMap传递给控制器方法
			String target = handler.execute(paramMap,map,sessionMap,applicationContext);
			//将map中需要传递到JSP中的数据,复制到
			//request的attribute中
			for(String key : map.keySet()) {
				Object val = map.get(key);
				request.setAttribute(key, val);
			}
			
			//赋值Session参数
			for(String key: sessionMap.keySet()){
				Object val=sessionMap.get(key);
				session.setAttribute(key, val); 
			}
			
			//利用反射执行执行控制器对象的方法
//			Object obj = handler.getObj();
//			Method method = handler.getMethod();
//			//执行目标方法
//			Object val = method.invoke(obj);
//			String target = val.toString();
			processView(request,response,target);
			
		} catch (Exception e) {
			e.printStackTrace();
			throw new ServletException(e);
		} 
		
//		response.setContentType("text/html");
//		response.getWriter().print("OK"); 
	}

	/**
	 * 处理视图(显示)结果,根据target
	 * 来决定如何处理
	 * 1. 如果以redirect 为开头就进行重定向处理
	 * 		如果是http开始就绝对路径重定向
	 * 		如果不是http开始的就补上 contextPath再重定向
	 * 2. 如果不是以 redirect 就直转发
	 * @param request
	 * @param response
	 * @param target
	 */
	private void processView(HttpServletRequest request,HttpServletResponse response,String target) throws ServletException,IOException{
		if(target==null) {
			throw new ServletException("没有结果");
		}
		if(target.startsWith("redirect:")) {
			//重定向
			String path = target.substring("redirect:".length());
			if(!path.startsWith("http")) {
				path = request.getContextPath()+path;
			}
			response.sendRedirect(path);
		}else {
			//转发
			String path = "/WEB-INF/jsp/"+target+".jsp";
			forward(request,response,path);
		}
	}
	
	private void forward(HttpServletRequest request, HttpServletResponse response, String path) throws ServletException, IOException {
		RequestDispatcher rd = request.getRequestDispatcher(path);
		rd.forward(request, response);
	}
	
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

linsa_pursuer

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

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

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

打赏作者

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

抵扣说明:

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

余额充值