补struts2.1两天快速入门之轻松搞定struts2核心--拦截器

由于拦截器的重要性,决定将其从(struts2.1两天快速入门第一天下午 抽取出来讲)

 

本讲将结合模拟用户权限验证展开:判断用户是否有权限请求访问某一模块或页面.

 

第九讲、自定义拦截器

 

   9.1 首先定义一个User实体类,如下:

Java代码 复制代码
  1. public class User implements Serializable {   
  2.     private String username;   
  3.     private String password;   
  4.     public String getUsername() {   
  5.         return username;   
  6.     }   
  7.     public void setUsername(String username) {   
  8.         this.username = username;   
  9.     }   
  10.     public String getPassword() {   
  11.         return password;   
  12.     }   
  13.     public void setPassword(String password) {   
  14.         this.password = password;   
  15.     }   
  16. }  
public class User implements Serializable {
	private String username;
	private String password;
	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;
	}
}

  9.2 编写一个用户登陆的Action类,如下:

Java代码 复制代码
  1. public class UserLoginAction {   
  2.     //接收复合类型的参数,与struts1.x相类似,此时表单的元素名称   
  3.     //应该为:user.username   user.password   
  4.     private User user;     
  5.     public User getUser() {   
  6.         return user;   
  7.     }   
  8.     public void setUser(User user) {   
  9.         this.user = user;   
  10.     }   
  11.     public String execute(){   
  12.     //模拟用户登陆,实际应用是从数据库里取的,这里只是为了方便测试和学习    
  13.     if("yulon".equals(user.getUsername())&&"123456".equals(user.getPassword())){   
  14.         //为了不让我们看到真正的session,struts2框架对其作了一层封装,用一个Map对象来存储.   
  15.         //Map map = ActionContext.getContext().getSession();   
  16.         //map.put("user", user);   
  17.         //把两段代码合成一段,就不用导入Map类   
  18.         ActionContext.getContext().getSession().put("user", user);   
  19.         return Action.SUCCESS;   //返回到欢迎页面   
  20.      }   
  21.         ActionContext.getContext().put("msg""用户登陆失败!");   
  22.         //返回到登陆页面,struts定义了一系列字符串常量,方便用户使用,统一的好处   
  23.         return Action.LOGIN;      
  24.     }   
  25. }  
public class UserLoginAction {
	//接收复合类型的参数,与struts1.x相类似,此时表单的元素名称
	//应该为:user.username   user.password
	private User user;  
	public User getUser() {
		return user;
	}
	public void setUser(User user) {
		this.user = user;
	}
	public String execute(){
	//模拟用户登陆,实际应用是从数据库里取的,这里只是为了方便测试和学习	
	if("yulon".equals(user.getUsername())&&"123456".equals(user.getPassword())){
		//为了不让我们看到真正的session,struts2框架对其作了一层封装,用一个Map对象来存储.
		//Map map = ActionContext.getContext().getSession();
		//map.put("user", user);
		//把两段代码合成一段,就不用导入Map类
		ActionContext.getContext().getSession().put("user", user);
		return Action.SUCCESS;   //返回到欢迎页面
	 }
		ActionContext.getContext().put("msg", "用户登陆失败!");
		//返回到登陆页面,struts定义了一系列字符串常量,方便用户使用,统一的好处
		return Action.LOGIN;   
	}
}

    知识提示:注意这里返回的是login,而不是LOGIN.查看ActionContext部分源码及相关文档可知,它是一个与线程相关的类,同一个线程内获取的都是同一个ActionContext实例,原理与ThreadLocal相关,之前有讲过ThreadLocal相关知识,ThreadLocal实现在同一个线程内的数据共享,可以更深入得理解ActionContext底层的实现原理.

 

  9.3  在struts.xml配置文件里新增一个package包,名称叫testinterceptor,命名空间定义为/test2,如下配置:     
 

Java代码 复制代码
  1. <package name="testinterceptor" namespace="/test2" extends="struts-default">   
  2.     <!--定义一个转发到登陆页面的Action-->   
  3.     <action name="loginUI">   
  4.         <result>/WEB-INF/jsp/login.jsp</result>   
  5.     </action>   
  6.     <action name="login" class="cn.gkit.action.UserLoginAction" method="execute">   
  7.       <!--重定向到同一个包内名字叫index的Action-->   
  8.         <result name="success" type="redirectAction">index</result>   
  9.         <result name="login">/WEB-INF/jsp/login.jsp</result>   
  10.     </action>   
  11.     <!--为了方便测试,同时也定义一个登出的Action-->   
  12.     <action name="logout" class="cn.gkit.action.UserLogoutAction" method="execute">   
  13.         <result name="success" type="redirectAction">loginUI</result>   
  14.     </action>   
  15.     <!--用户登陆成功后的欢迎页面-->   
  16.     <action name="index">   
  17.         <result name="success">/WEB-INF/jsp/welcome.jsp</result>   
  18.         <result name="login" type="redirectAction">loginUI</result>   
  19.     </action>   
  20. </package>  
  <package name="testinterceptor" namespace="/test2" extends="struts-default">
      <!--定义一个转发到登陆页面的Action-->
    	<action name="loginUI">
    		<result>/WEB-INF/jsp/login.jsp</result>
    	</action>
    	<action name="login" class="cn.gkit.action.UserLoginAction" method="execute">
    	  <!--重定向到同一个包内名字叫index的Action-->
    		<result name="success" type="redirectAction">index</result>
    		<result name="login">/WEB-INF/jsp/login.jsp</result>
    	</action>
    	<!--为了方便测试,同时也定义一个登出的Action-->
    	<action name="logout" class="cn.gkit.action.UserLogoutAction" method="execute">
    		<result name="success" type="redirectAction">loginUI</result>
    	</action>
    	<!--用户登陆成功后的欢迎页面-->
    	<action name="index">
    		<result name="success">/WEB-INF/jsp/welcome.jsp</result>
    		<result name="login" type="redirectAction">loginUI</result>
    	</action>
  </package>

  9.4 编写UserLogoutAction类

Java代码 复制代码
  1. public class UserLogoutAction {   
  2.     public String execute(){   
  3.         //取出当前登陆的用户   
  4.         User user = (User)ActionContext.getContext().getSession().get("user");   
  5.         if(user!=null){   
  6.             //注销用户信息   
  7.             ActionContext.getContext().getSession().remove("user");   
  8.         }   
  9.         return Action.SUCCESS;   
  10.     }   
  11. }  
public class UserLogoutAction {
	public String execute(){
		//取出当前登陆的用户
		User user = (User)ActionContext.getContext().getSession().get("user");
		if(user!=null){
			//注销用户信息
			ActionContext.getContext().getSession().remove("user");
		}
		return Action.SUCCESS;
	}
}

  9.5 编写/WEB-INF/jsp/login.jsp页面

 

Java代码 复制代码
  1. <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>   
  2. <%@ taglib prefix="s" uri="/struts-tags"%>   
  3. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">   
  4. <html>   
  5.   <head>   
  6.     <title>用户登陆</title>   
  7.   </head>   
  8.   <span>${msg}</span>   
  9.   <body>   
  10.     <s:form action="login" namespace="/test2" method="post">   
  11.       用户名: <s:textfield title="填写用户名" name="user.username" ></s:textfield><br/>   
  12.       密   码 :<s:password title="填写密码" name="user.password"></s:password><br/>   
  13.         <s:submit value="登陆"></s:submit>   
  14.     </s:form>   
  15.   </body>   
  16. </html>  
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>用户登陆</title>
  </head>
  <span>${msg}</span>
  <body>
    <s:form action="login" namespace="/test2" method="post">
      用户名: <s:textfield title="填写用户名" name="user.username" ></s:textfield><br/>
      密   码 :<s:password title="填写密码" name="user.password"></s:password><br/>
        <s:submit value="登陆"></s:submit>
    </s:form>
  </body>
</html>

    知识提示:使用struts2标签首先在导入相关uri, 可以在struts2核心包下的META-INF目录下找到struts-tags.tld文件,里面就有uri的定义,具体标签的使用暂不在本章细讲,大家可以先琢磨一下.

  9.6 编写/WEB-INF/jsp/welcome.jsp

Java代码 复制代码
  1. <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>   
  2. <%@ taglib prefix="s" uri="/struts-tags"%>   
  3. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">   
  4. <html>   
  5.   <head>   
  6.     <title>欢迎首页</title>   
  7.   </head>   
  8.   <body>   
  9.     欢迎用户${sessionScope.user.username}的到来!<br />   
  10.  <a href='<s:url action="logout" namespace="/test2" />'>退出</a>   
  11.   </body>   
  12. </html>  
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>欢迎首页</title>
  </head>
  <body>
    欢迎用户${sessionScope.user.username}的到来!<br />
 <a href='<s:url action="logout" namespace="/test2" />'>退出</a>
  </body>
</html>

  

在没有使用拦截器之前,是可以直接访问/struts2.1/test2/index请求的.

 

  9.7实现自己的拦截器cn.gkit.web.interceptor.UserAuthInterceptor(有三种方法)

 

    第一种方法是实现com.opensymphony.xwork2.interceptor.Interceptor接口,实现相应的方法就行.

Java代码 复制代码
  1. public class UserAuthInterceptor implements Interceptor{   
  2.   
  3.     public void destroy() {   
  4.     }   
  5.     public void init() {   
  6.  init()         System.out.println("服务器启动过程中自动加载");   
  7.     }   
  8.   
  9.     public String intercept(ActionInvocation invocation) throws Exception {   
  10.          //ActionContext.getContext().getSession().get("user");   
  11.         Map map = invocation.getInvocationContext().getSession();   
  12.         if(map.get("user")==null){   
  13. System.out.println("用户还没登陆");   
  14.             return Action.LOGIN;  //struts定义了一系统字符串常量,注意这里返回的是login,而不是LOGIN.   
  15.         }else  
  16.         {   
  17.             String result = invocation.invoke();//如果用户已登陆就通过验证,继承执行下一个拦截器   
  18.             System.out.println("返回结果:"+result);   
  19.             return result;    
  20.         }   
  21.     }   
  22. }  
public class UserAuthInterceptor implements Interceptor{

	public void destroy() {
	}
	public void init() {
 init() 		System.out.println("服务器启动过程中自动加载");
	}

	public String intercept(ActionInvocation invocation) throws Exception {
		 //ActionContext.getContext().getSession().get("user");
		Map map = invocation.getInvocationContext().getSession();
		if(map.get("user")==null){
System.out.println("用户还没登陆");
			return Action.LOGIN;  //struts定义了一系统字符串常量,注意这里返回的是login,而不是LOGIN.
		}else
		{
			String result = invocation.invoke();//如果用户已登陆就通过验证,继承执行下一个拦截器
			System.out.println("返回结果:"+result);
			return result; 
		}
	}
}

    知识提示:invocation.getInvocationContext()获取到的是一个与ActionInvocation相关的ActionContext ,但使用ActionContext.getContext()也一样可以取得.

 

   第二种方法是继承AbstractInterceptor抽象,此类的设计思想跟之前讲的类型转换器类似.它本身也是实现了Interceptor接口,由于我们一般都不用到init()方法和destroy()方法,因此通常情况下都是通过继承AbstractInterceptor来实现我们的拦截器.如下:

Java代码 复制代码
  1. public class UserAuthInterceptor extends AbstractInterceptor{   
  2.                @Override  
  3.     public String intercept(ActionInvocation invocation) throws Exception {   
  4.          //ActionContext.getContext().getSession().get("user");   
  5.         Map map = invocation.getInvocationContext().getSession();   
  6.         if(map.get("user")==null){   
  7. System.out.println("用户还没登陆");   
  8.             return Action.LOGIN;     
  9.         }else  
  10.         {   
  11.             String result = invocation.invoke();   
  12.             System.out.println("返回结果:"+result);   
  13.             return result;    
  14.         }   
  15.     }   
  16. }  
public class UserAuthInterceptor extends AbstractInterceptor{
               @Override
	public String intercept(ActionInvocation invocation) throws Exception {
		 //ActionContext.getContext().getSession().get("user");
		Map map = invocation.getInvocationContext().getSession();
		if(map.get("user")==null){
System.out.println("用户还没登陆");
			return Action.LOGIN;  
		}else
		{
			String result = invocation.invoke();
			System.out.println("返回结果:"+result);
			return result; 
		}
	}
}

   知识提示:查看AbstractInterceptor源码,你会感到惊讶,如下:

Java代码 复制代码
  1.  public abstract class AbstractInterceptor implements Interceptor {   
  2.   
  3.     public void init() {   
  4.     }   
  5.   
  6.     public void destroy() {   
  7.     }   
  8.     public abstract String intercept(ActionInvocation invocation) throws Exception;   
  9. }  
 public abstract class AbstractInterceptor implements Interceptor {

    public void init() {
    }

    public void destroy() {
    }
    public abstract String intercept(ActionInvocation invocation) throws Exception;
}

    知识提示:你没有看错,就是几行代码,它只是帮我们实现了两个不常用到的方法,并且还是空实现. 但里面包含的一种设计思想值得我们去学习研究,所以说学习框架的最高境界是学习它的思想,思想就存在代码当中,大家有时间可以多查看一下它的源码.

 

  9.8 将我们编写好的拦截器类加载到struts.xml配置文件中,如下定义:

 

Java代码 复制代码
  1. <interceptors>   
  2.      <!-- 加载自己编写的拦截器 -->   
  3.     <interceptor name="authInterceptor" class="cn.gkit.web.interceptor.UserAuthInterceptor" />   
  4. </interceptors>  
<interceptors>
     <!-- 加载自己编写的拦截器 -->
    <interceptor name="authInterceptor" class="cn.gkit.web.interceptor.UserAuthInterceptor" />
</interceptors>

 

    9.9 将我们编写好的拦截器应用到具体的action类上,将struts.xml 名称为index的<action>改如下:

 

Java代码 复制代码
  1. <action name="index">   
  2.         <result name="success">/WEB-INF/jsp/welcome.jsp</result>   
  3.         <result name="login" type="redirectAction">loginUI</result>   
  4.         <interceptor-ref name="authInterceptor"></interceptor-ref>   
  5. </action>  
<action name="index">
    	<result name="success">/WEB-INF/jsp/welcome.jsp</result>
    	<result name="login" type="redirectAction">loginUI</result>
    	<interceptor-ref name="authInterceptor"></interceptor-ref>
</action>

    此时:若访问/struts2.1/test2/index,请求将先会被自己定义的authInterceptor拦截器拦截,执行intercept方法的代码,判断用户若没有登陆则转到登陆页面,若存在才会放行(即执行下一步,这里的下一步会执行请求对应的Action方法).

    问题:为什么说拦截器是struts2的核心呢?是因为struts2的很多工作都是通过拦截器来实现的,在你定义的<package>的同时,只要你extends 了struts-default包,就同时拥有了struts-default包内定义的全部内容.通过查看struts-default.xml文件可知,它里面定义了很多拦截器,每个拦截器负责完成不同的工作.如里面名字为params的拦截器的作用是会将页面表单的参数会自动赋值到action里的属性。其中在最后定义了一个默认的拦截器栈<default-interceptor-ref name="defaultStack"/>,默认情况下默认拦截器栈会应用到包内定义的所有action身上. 但如果你手工在一个<action>添加一个额外的拦截器后,此时默认的拦截器栈对本<action>不起作用.解决的办法如下:

     第一种解决方案:重新引入defaultStack拦截器栈,将index改如下:

Java代码 复制代码
  1. <action name="index">   
  2.         <result name="success">/WEB-INF/jsp/welcome.jsp</result>   
  3.         <result name="login" type="redirectAction">loginUI</result>   
  4.         <interceptor-ref name="authInterceptor"></interceptor-ref>   
  5.         <!-- 重新引入 defaultStack-->   
  6.         <interceptor-ref name="defaultStack"></interceptor-ref>   
  7. </action>  
<action name="index">
    	<result name="success">/WEB-INF/jsp/welcome.jsp</result>
    	<result name="login" type="redirectAction">loginUI</result>
    	<interceptor-ref name="authInterceptor"></interceptor-ref>
    	<!-- 重新引入 defaultStack-->
    	<interceptor-ref name="defaultStack"></interceptor-ref>
</action>

    第二种解决方案:定义自已的拦截器栈,如下:

Java代码 复制代码
  1. <interceptors>   
  2.     <!-- 加载自己编写的拦截器 -->   
  3.     <interceptor name="authInterceptor" class="cn.gkit.web.interceptor.UserAuthInterceptor" />   
  4.     <!-- 定义拦截器栈 -->   
  5.     <interceptor-stack name="myInterceptorStack">   
  6.         <!-- 引用用户定义的拦截器 -->   
  7.         <interceptor-ref name="authInterceptor"></interceptor-ref>   
  8.         <!-- 同时也要重新把默认的拦截器栈引入进来 -->   
  9.         <interceptor-ref name="defaultStack"></interceptor-ref>   
  10.     </interceptor-stack>   
  11. </interceptors>  
<interceptors>
    <!-- 加载自己编写的拦截器 -->
    <interceptor name="authInterceptor" class="cn.gkit.web.interceptor.UserAuthInterceptor" />
    <!-- 定义拦截器栈 -->
    <interceptor-stack name="myInterceptorStack">
    	<!-- 引用用户定义的拦截器 -->
    	<interceptor-ref name="authInterceptor"></interceptor-ref>
    	<!-- 同时也要重新把默认的拦截器栈引入进来 -->
    	<interceptor-ref name="defaultStack"></interceptor-ref>
    </interceptor-stack>
</interceptors>

    将自己定义好的拦截器栈应用到具体action类中,如将<action>改写成:

   

Java代码 复制代码
  1. <action name="index">   
  2.     <result name="success">/WEB-INF/jsp/welcome.jsp</result>   
  3.     <result name="login" type="redirectAction">loginUI</result>   
  4.     <!-- 引入自定义拦截器栈 -->   
  5.     <interceptor-ref name="myInterceptorStack"></interceptor-ref>   
  6. </action>  
 <action name="index">
    	<result name="success">/WEB-INF/jsp/welcome.jsp</result>
    	<result name="login" type="redirectAction">loginUI</result>
    	<!-- 引入自定义拦截器栈 -->
    	<interceptor-ref name="myInterceptorStack"></interceptor-ref>
 </action>

   第三种解决方案:重新定义默认拦截器栈

  

Xml代码 复制代码
  1. <interceptors>  
  2.     <!-- 加载自己编写的拦截器 -->  
  3.     <interceptor name="authInterceptor" class="cn.gkit.web.interceptor.UserAuthInterceptor" />  
  4.     <!-- 定义拦截器栈 -->  
  5.     <interceptor-stack name="myInterceptorStack">  
  6.         <!-- 引用用户定义的拦截器 -->  
  7.         <interceptor-ref name="authInterceptor"></interceptor-ref>  
  8.         <!-- 同时也要重新把默认的拦截器栈引入进来 -->  
  9.         <interceptor-ref name="defaultStack"></interceptor-ref>  
  10.     </interceptor-stack>  
  11. </interceptors>  
  12.  <!-- 重新定义默认的拦截器栈,覆盖掉struts-default定义的默认拦截器栈 -->  
  13. <default-interceptor-ref name="myInterceptorStack"></default-interceptor-ref>  

    注意:此时在testinterceptor包内定义的所有<action>默认都会被myInterceptorStack拦截器拦截,index不用重新定义拦截器,改如下:

Xml代码 复制代码
  1. <action name="index">  
  2.     <result name="success">/WEB-INF/jsp/welcome.jsp</result>  
  3.     <result name="login" type="redirectAction">loginUI</result>  
  4. </action>  

 

    具体使用哪一种方法按项目需求而定

 

 9.10 在 9.7节里面还有一种自定义拦截器的方法没讲到:就是方法过滤拦截器(MethodFilterInterceptor)

     

     编写方法过滤拦截器第一步:编写继承于MethodFilterInterceptor抽象类的拦截器

cn.gkit.web.interceptor. MyMethodFilterInterceptor ,如下:
Java代码 复制代码
  1. public class MyMethodFilterInterceptor extends MethodFilterInterceptor {   
  2.   
  3.     @Override  
  4.     protected String doIntercept(ActionInvocation invocation) throws Exception {   
  5.         System.out.println("执行了MyMethodFilterInterceptor拦截器");   
  6.         String resultString = invocation.invoke();   
  7.         System.out.println("返回的结果:"+resultString);   
  8.         return resultString;   
  9.     }   
  10. }  
public class MyMethodFilterInterceptor extends MethodFilterInterceptor {

	@Override
	protected String doIntercept(ActionInvocation invocation) throws Exception {
		System.out.println("执行了MyMethodFilterInterceptor拦截器");
		String resultString = invocation.invoke();
		System.out.println("返回的结果:"+resultString);
		return resultString;
	}
}
 第二步,在上次编写的test包内加载这个拦截器,如下:
Xml代码 复制代码
  1. <interceptors>  
  2.   <interceptor name="mymethodinterceptor" class="cn.gkit.web.interceptor.MyMethodFilterInterceptor"></interceptor>  
  3. </interceptors>  
 第三步,在具体的action里应用该拦截器,如下:
Xml代码 复制代码
  1. <package name="test" namespace="/test" extends="gkit">  
  2.    <interceptors>  
  3.     <!-- 定义方法过滤拦截器 -->  
  4.     <interceptor name="mymethodinterceptor" class="cn.gkit.web.interceptor.MyMethodFilterInterceptor"/>  
  5.    </interceptors>  
  6.     <action name="*User" class="cn.gkit.action.HelloWorldAction" method="{1}" >  
  7.             <param name="message">属性注入</param>  
  8.     <result name="success">/WEB-INF/jsp/helloworld.jsp</result>    
  9.     <interceptor-ref name="mymethodinterceptor">  
  10.     <!--指定要拦截的方法  includeMethods的优先级比excludeMethods的要高-->  
  11.         <param name="includeMethods">add,execute</param>  
  12.     </interceptor-ref>  
  13.     <interceptor-ref name="defaultStack"></interceptor-ref>  
  14.      </action>  
  15. </package>  
   知识提示: MethodFilterInterceptor有两个重要的属性 excludeMethods includeMethods 排除/包含action里的某个业务方法,多个方法用逗号分开.以上配置表示:cn.gkit.action.HelloWorldAction内的add与execute方法将会被mymethodinterceptor拦截器拦截.
拦截器至此已基本上讲完,谢谢大家的阅读!也希望大家能从中学到东西.如果有某处地方看不懂,大家可以与我一起讨论!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值