Android客户端和Servlet服务端的JSON传输(注释详细到啰嗦的地步,欢迎新手学习)

记录一个Android客户端和Servlet服务端的JSON传输的小例子:

1.PostParam 包装了要发送的JSONObject类,这个例子中不包装也可以,但是会麻烦那么一点点。

2.ResponseParam 包装了服务端返回的JSONObject类,这个例子中勉强算是需要的,因为我们会用到bundle传递序列化对象,而JSONObject无法序列化。当然我们也可以把返回的EntIty转化为String传送。

3.PostAction post的线程,HttpPost操作都放到里面来了。

4.OnHttpActionListener action的监听器,就像按钮的设置事件一样,我们在创建它的时候实现方法,从而对获得的数据操作。

5.MainActivity 主线程


下面贴代码,最后会放上GetParam,GetAction和服务端的代码,这样就全了。但是GetAction就不测试了,毕竟和Post很类似。

PostParam:

package com.example.httputil;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;


/**
 * 
 * @author Administrator
 * JSONObject的包装类,我们可以把数据放入这个类的实例并传输它。
 */

public class PostParam
{
	JSONObject param;

	//懒汉式创建param,第一次addParam的时候才创建param。
	//键值对添加数据
	public void addParam(String key, String value){
		if (null == param)
		{
			param = new JSONObject();
		}
		try
		{
			param.put(key, value);
		} catch (JSONException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}				
	}

	//重载addParam,参数是以JSON文件形式添加数据
	public void addParam(String key, JSONObject object){
		if (null == param)
		{
			param = new JSONObject();
		}
		try
		{
			param.put(key, object);
		} catch (JSONException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}				
	}
	
	//重载addParam,参数是以JSONArray形式添加数据
	public void addParam(String key, JSONArray array){
		if (null == param)
		{
			param = new JSONObject();
		}
		try
		{
			param.put(key, array);

		} catch (JSONException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}				
	}
	
	//重写toString,方便传输数据。
	@Override
	public String toString()
	{
		if (null != param)
		{
			return param.toString();
		}
		// TODO Auto-generated method stub
		return "";
	}
}



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

ResponseParam:

package com.example.httputil;

import java.io.Serializable;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
 * 
 * @author Administrator
 *这个类用来包装服务端返回的jsonobject,使得可以序列化,用bundle传输。
 */
public class ResponseParam implements Serializable
{
	private static final long serialVersionUID = 75656654L;
	
	JSONObject response;
	
	ResponseParam(JSONObject response){
		this.response=response;	
	}
	
	
	ResponseParam(String value){
		try
		{
			response = new JSONObject(value);
		} catch (JSONException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	

//返回JSONObject中含有这个key的String	
	
	public String getString(String key){
		if (response.has(key))
		{
			try
			{
				return response.getString(key);
			} catch (JSONException e)
			{
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		return "";
	}
	
	//返回JSONObject中含有这个key的JSONOBJECT	
	public JSONObject getJsonObject(String key){
		if (response.has(key))
		{
			try
			{
				return response.getJSONObject(key);
			} catch (JSONException e)
			{
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}	
		return null;		
	}

	//返回JSONObject中含有这个key的JSONArray
	public JSONArray getJsonArray(String key){
		if (response.has(key))
		{
			try
			{
				return response.getJSONArray(key);
			} catch (JSONException e)
			{
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}	
		return null;		
	}
	
}



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


OnHttpActionListener:

package com.example.httputil;



public interface OnHttpActionListener
{
	//成功post的代码编写	
	public void onHttpActionSuccess(int actionId,ResponseParam responseParam);
	//post失败的代码编写	
	public void  onHttpActionFailed(int actionId,int failHttpCode);
	//post报错的代码编写	
	public void onHttpActionException(int actionId,String excepionMsg);
	

}


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


PostAction:

package com.example.httputil;

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

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;

import android.content.Entity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;


/**
 * 
 * @author Administrator
 *用来发送PostParam实例到指定服务器地址,并接受服务器返回的JSONObject.
 *这个JSONObject将放入ResponseParam的实例中,ResponseParam的实例又赋予给了监听器接口的SUCCESS方法,
 *所以我们只要在主线程创建监听器,实现监听器的SUCCESS接口,就可以把返回的JSONObect里的数据拿出来处理了。
 */
public class PostAction implements Runnable
{
	//线程ID
	int Id;
	//要发送的端口地址
	String url;
	//要传送的Post包的键名
	String paramKey;
	//要传送的Post包
	PostParam postParam;
	//用Handler接受返回的包
	Handler handler;
	//监听器,用来监听post是否成功,或者报错
	OnHttpActionListener onHttpActionListener;
	
	//如果post服务端成功连接,则返回这个值
	final static int MSG_POST_SUCCESS = 0x10;
	
	//如果post服务端失败,比如404页面,则返回这个值
	final static int MSG_POST_FAIL = 0x20;
	
	//如果连接报错,则返回这个值
	final static int MSG_POST_EXCEPTION = 0x30;
	
	//服务器返回的JSONObject将打包成ResponseParam,放到bundle的序列化Object里,这个就是Object的Key
	//然后这个bundle随着message发送到handler里再根据key提取。
	final static String MSG_KEY_SERIALIZABLE = "serializableParmKey";
	
	//如果连接服务器报错,则会把出错的信息放到Bundle发到handler,这个就是提取的key
	final static String MSG_KEY_EXCEPTION = "exceptionKey";
	
	//和服务器接口事先规定好的登录信息提取key,客户端根据这个key发出登录帐号密码包,服务器根据这个key再提取。
	public final static String LoginParam="LoginParam";
	
	/**
	 * 
	 * @param actionId 用来区分各个action的id
	 * @param url post服务端地址
	 * @param paramKey post服务端的包key,一般是这个类里设置好的常量,比如这个类里的LoginParam
	 */
	public PostAction(int actionId, String url,String paramKey)
	{
		
		this.Id = actionId;
		this.url = url;
		this.paramKey=paramKey;
		
		//handler接收message并赋值给监听器,至此,接受端的代码就结束了,我们成功地把服务端的返回值交给了监听器。
		//new action的时候需要setOnHttpActionListener(),创建监听器的的时候又需要实现它的三个方法,根据这三个方法我们可以做相应的处理。
		handler = new Handler(){
			@Override
			public void handleMessage(Message msg)
			{
				
				//判断是否本线程发出的信息,监听器是否设置了
				if (msg.what == Id && onHttpActionListener != null)
				{
					
					//接收的msg里的arg1里我们统一放了post状态常量,根据msg的arg1值,去对应着监听器的接口方法
					switch (msg.arg1)
					{
					//如果是成功连接服务器端,则把返回的包放入监听器。
					case MSG_POST_SUCCESS:
						onHttpActionListener.onHttpActionSuccess(Id,
								(ResponseParam)msg.getData().getSerializable(MSG_KEY_SERIALIZABLE));
						break;
						
					//如果是连接服务器端失败,则把失败返回的http码放入监听器。
					case MSG_POST_FAIL:
						onHttpActionListener.onHttpActionFailed(Id, msg.arg2);
						break;
						
					//如果是连接服务器端报错,则把报错的信息String放入监听器。
					case MSG_POST_EXCEPTION:
						onHttpActionListener.onHttpActionException(Id, 
								msg.getData().getString(MSG_KEY_EXCEPTION));
						break;

				
					}
				}
				
			}
				
				
		};
				
	}
	
	
	//传入监听器
	public void setOnHttpActionListener(OnHttpActionListener onHttpActionListener)
	{
		this.onHttpActionListener = onHttpActionListener;
	}
	
	
	//设置要传输的post包
	public void setPostParam(PostParam postParam)
	{
		this.postParam = postParam;
	}
	
	

	//线程执行的代码
	@Override
	public void run()
	{
		// TODO Auto-generated method stub
	
		try
		{
			
			//设置post连接,连接地址url已经在构造方法那传过来了
			HttpPost post =new HttpPost(url);
			
			//键值对List,下面用工具可以把它转化为entity
			List<NameValuePair> param =new ArrayList<NameValuePair>();
			
			//往键值对里添加数据,paramKey是构造方法那传进来的,发送包的key,值就是postParam里的JSONObject转化为String的值
			param.add(new BasicNameValuePair(paramKey,postParam.toString()));
			
			//用工具把List转化为Entity之后,再放入post里
			post.setEntity(new UrlEncodedFormEntity(param,HTTP.UTF_8));
			
			//执行post,并得到返回的response
			HttpResponse response = new DefaultHttpClient().execute(post);
			
			//得到response里的HttpCode,这个值如果是404则是找不到端口,200则是成功连接,很好理解的
			int httpCode = response.getStatusLine().getStatusCode();
			
			
			//SC_OK=200,判断是否成功连接到服务端
			if ( httpCode== HttpStatus.SC_OK)
			{
				//成功连接,当然有一个服务器返回的JSONOject包了,我们要把这个包传给handler,通过message就可以很好的完成任务
				Message message =new Message();
				//发送这个Message的线程ID
				message.what = Id;
				//arg1放入成功连接状态的常量,告诉handler成功连接了
				message.arg1 = MSG_POST_SUCCESS;
				
				//JSONOject需要用Bundle来放
				Bundle bundle = new Bundle();
				
				
				//由于Bundle只能放序列化的实例,所以JSONOject是放不进的,但是我们已经做好了一个可以序列化的类来包装JSONOject
				//所以把服务端返回的包放进ResponseParam里就可以用Bundler传了。
				//这里还用到了工具类,去把返回的entity转化为String类型。而ResponseParam的其中一个构造方法可以用String做参数。完美结合。
				ResponseParam responseParam = new ResponseParam(EntityUtils.toString(response.getEntity())); 
				//从BUNDLE里拿出序列化包,还得用设置好的key常量,handler中可以用这个key拿出包。
				bundle.putSerializable(MSG_KEY_SERIALIZABLE, responseParam);
				
				//把bundle放入message里
				message.setData(bundle);
				//发送message
				handler.sendMessage(message);
				
			}else {
				//如果连接服务端口失败,直接在arg2里放入失败的 httpcode就行
				Message message =new Message();
				message.what = Id;
				message.arg1 = MSG_POST_FAIL;
				message.arg2 = httpCode;
				handler.sendMessage(message);
			}
			
		} catch (Exception e)
		{
			
			// TODO Auto-generated catch block
			e.printStackTrace();
			//连接服务器接口报错,则返回报错信息即可
			Message message =new Message();
			message.what = Id;
			message.arg1 = MSG_POST_EXCEPTION;
			
			Bundle bundle = new Bundle();
			bundle.putString(MSG_KEY_EXCEPTION, e.getMessage());
			message.setData(bundle);
			handler.sendMessage(message);
		
		} 
	}
	

	
	
	
	
	
}


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


MainActivity(只贴主要方法):

public class MainActivity extends FragmentActivity
{
	//服务器发过来的包的key,服务器端事先声明好登录页面数据包的key
	final static String MSG_KEY_LOGIN = "MSG_KEY_LOGIN";

	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.fragment_main);

		//加载帐号,密码框和登录按钮
		final EditText userText = (EditText)findViewById(R.id.username);
		final EditText passText = (EditText)findViewById(R.id.password);
		Button loginbtn = (Button)findViewById(R.id.loginbtn);
		Button exitbtn = (Button)findViewById(R.id.exitbtn);
	
		
		//登录按钮的点击事件:发送帐号密码到服务端
		loginbtn.setOnClickListener(new OnClickListener()
		{
			
			@Override
			public void onClick(View v)
			{
				// TODO Auto-generated method stub

				String username=userText.getText().toString();
				String password=passText.getText().toString();
				
				//判断帐号密码是否为空
				
				if (username != null && password != null && username.length() > 0 && password.length() > 0)
				{
					doLogin(username, password);
					
					
				}else {
					Toast.makeText(MainActivity.this, "请输入帐号和密码", Toast.LENGTH_LONG).show();
				}
				
				
				
			}
		});
		
	
		}
	
	
	void doLogin(String username,String password){
		
		//创建action,10.0.2.2是虚拟机中的电脑主机本地IP,不要使用localhost或者127.0.0.1
		PostAction action = new PostAction(0,"http://10.0.2.2:8080/TestServer/LoginDo", PostAction.LoginParam);
		
		PostParam loginParma = new PostParam();
		
		loginParma.addParam("username",username);
		loginParma.addParam("password",password);
		
		action.setPostParam(loginParma);
		
		//设置acion的监听器,这个时候我们可以好好处理获得的数据了
		action.setOnHttpActionListener(new OnHttpActionListener()
		{
			
			@Override
			public void onHttpActionSuccess(int actionId, ResponseParam responseParam)
			{
				//返回的包里的的key已经在上面声明好了,得到值并用toast显示。
				Toast.makeText(MainActivity.this,responseParam.getString(MSG_KEY_LOGIN), Toast.LENGTH_LONG).show();
				
			}
			
			@Override
			public void onHttpActionFailed(int actionId, int failHttpCode)
			{
				// TODO Auto-generated method stub
				Toast.makeText(MainActivity.this,failHttpCode, Toast.LENGTH_LONG).show();
				
			}
			
			@Override
			public void onHttpActionException(int actionId, String excepionMsg)
			{
				// TODO Auto-generated method stub
				Toast.makeText(MainActivity.this,excepionMsg, Toast.LENGTH_LONG).show();
				
			}
		});
		
		//不要忘了创建新线程来运行action
		new Thread(action).start();
		
	}


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


服务器的servlet代码,里面要用的JSON的jar包需要自己上网下载,搜下就有,不多说了:

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

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

import net.sf.json.JSONObject;
import net.sf.json.JSONString;
import net.sf.json.util.JSONUtils;






public class LoginDo extends HttpServlet {
	//和客户端声明好的key,服务端用这个key发送数据,客户端会用这个key获得返回的数据。
	final static String MSG_KEY_LOGIN="MSG_KEY_LOGIN";
	
	//如果登录成功则返回这个数据,也就是1
	final static int MSG_KEY_LOGIN_SUCCESS = 1;
	
	//如果帐号密码不正确,就返回它
	final static int MSG_KEY_LOGIN_FAIL = 0;
	
	//如果验证出了异常,则返回它
	final static int MSG_KEY_LOGIN_EXCEPTION = -1;
	
	//和客户端声明好的Key,客户端用这个key发数据过来,而服务端就可以用这个key获得数据
	final static String LoginParam = "LoginParam";
	
	
	/**
	 * Constructor of the object.
	 */
	public LoginDo() {
		super();
	}

	/**
	 * Destruction of the servlet. <br>
	 */
	public void destroy() {
		super.destroy(); // Just puts "destroy" string in log
		// Put your code here
	}

	/**
	 * The doGet method of the servlet. <br>
	 *
	 * This method is called when a form has its tag value method equals to get.
	 * 
	 * @param request the request send by the client to the server
	 * @param response the response send by the server to the client
	 * @throws ServletException if an error occurred
	 * @throws IOException if an error occurred
	 */
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
			
			//设置编码
			request.setCharacterEncoding("UTF-8");
			response.setCharacterEncoding("UTF-8");
			response.setHeader("Content-Type", "text/html;charset=UTF-8");
			
			response.setContentType("text/html");
			PrintWriter out = response.getWriter();

		
			//获取接收到的值
			String loginParam = request.getParameter(LoginParam);
			
			//把这个得到的String转化为JSONObject,这个转化的方式其实并不是很好。
			JSONObject loginObject =JSONObject.fromObject(loginParam);
			
			//
			String username = loginObject.getString("username");
			String password = loginObject.getString("password");
				

		try {
			//加载jdbc驱动
			Class.forName("com.mysql.jdbc.Driver");

			//连接数据库,这里我用的是mysql
			Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/TestServer","root","root");

			//创建陈述对象
			Statement st=conn.createStatement();

			//编写sql语句
			String sql="Select * from User Where username='"+username+"' and password='"+password+"'";

			//使用陈述对象执行sql语句,并把返回值赋给rs
			ResultSet rs=st.executeQuery(sql);

			//如果返回值不为空,则用out传输过去MSG_KEY_LOGIN_SUCCESS
			if(rs.next()!=false){
				JSONObject object =new JSONObject();
				object.put(MSG_KEY_LOGIN, MSG_KEY_LOGIN_SUCCESS);
				out.print(object);
				
				//服务端还是得print一下
				System.out.print("登录成功");
				
	
			}else{
				//帐号密码不正确,返回MSG_KEY_LOGIN_FAIL
				JSONObject object =new JSONObject();
				object.put(MSG_KEY_LOGIN, MSG_KEY_LOGIN_FAIL);
				out.print(object);
				System.out.print("登录失败");
			}

			//关闭连接
			rs.close();
			st.close();
			conn.close();
					
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			//查询报错返回MSG_KEY_LOGIN_EXCEPTION
			JSONObject object =new JSONObject();
			object.put(MSG_KEY_LOGIN,MSG_KEY_LOGIN_EXCEPTION);
			out.print(object);
		}

		out.flush();
		out.close();
	}

	/**
	 * The doPost method of the servlet. <br>
	 *
	 * This method is called when a form has its tag value method equals to post.
	 * 
	 * @param request the request send by the client to the server
	 * @param response the response send by the server to the client
	 * @throws ServletException if an error occurred
	 * @throws IOException if an error occurred
	 */
	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
			doGet(request,response);
	}

	/**
	 * Initialization of the servlet. <br>
	 *
	 * @throws ServletException if an error occurs
	 */
	public void init() throws ServletException {
		// Put your code here
	}

}


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

GetParam:

package com.example.httputil;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;


/**
 * 
 * @author Administrator
 * 参考postparam吧
 */
public class GetParam
{
	//用来存放键值对的map
	Map<String, String> param;
	

	//把键值对放进HashMap里
	public void addParam(String key,String value){
		if (param == null)
		{
			param =new HashMap<String, String>();
		}
		param.put(key, value);						
	}
	
	//重写toString,把map里的键值对按照?key="value"&key2="value2"的方式输出。
	@Override
	public String toString()
	{
		if (param != null)
		{
			StringBuffer sb=new StringBuffer();
			sb.append('?');
			//keySet()可以把map的key放入Set<String>里,从而得到iterator,方便操作,实用的办法。
			Iterator<String> iterator =param.keySet().iterator();
			while (iterator.hasNext())
			{
				String key=iterator.next();
				sb.append(key);
				sb.append('=');
				sb.append(param.get(key));
				//判断是否最后一个参数,如果不是就是加一个“&”
				if (iterator.hasNext())
				{
					sb.append('&');
				}
				
			}
			
			return sb.toString();
			
		}
		return "";
	}

}


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

GetAction:

package com.example.httputil;

import java.io.IOException;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import android.R.integer;
import android.content.Entity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

/**
 * 
 * @author Administrator
 *和PostAction基本一样,参考它就可以了。
 */
public class GetAction implements Runnable
{
	int actionId;
	String url;
	GetParam param;
	OnHttpActionListener onHttpActionListener;
	Handler handler;
	
	final static int MSG_GET_SUCCESS = 0x10;
	final static int MSG_GET_FAIL = 0x20;
	final static int MSG_GET_EXCEPTION = 0x30;
	final static String MSG_KEY_SERIALIZABLE = "serializableParmKey";
	final static String MSG_KEY_EXCEPTION = "exceptionKey";
	
	
	public String getUrl()
	{
		return url;
	}



	public void setParam(GetParam param)
	{
		this.param = param;
	}

	
	public int getActionId()
	{
		return actionId;
	}


	
	public 	void setOnHttpActionListener(OnHttpActionListener onHttpActionListener){
		this.onHttpActionListener =onHttpActionListener;		
	}
	


	public GetAction(int Id,String url)
	{
		
		// TODO Auto-generated constructor stub
		actionId = Id;
		this.url=url;
		handler = new Handler(){
			@Override
			public void handleMessage(Message msg)
			{
				if (msg.what == actionId && onHttpActionListener != null)
				{
					switch (msg.arg1)
					{
					case MSG_GET_SUCCESS:
						onHttpActionListener.onHttpActionSuccess(actionId,
								(ResponseParam)msg.getData().getSerializable(MSG_KEY_SERIALIZABLE));
						break;
					case MSG_GET_FAIL:
						onHttpActionListener.onHttpActionFailed(actionId,msg.arg2);
						
						break;
					case MSG_GET_EXCEPTION:
						onHttpActionListener.onHttpActionException(actionId, 
								msg.getData().getString(MSG_KEY_EXCEPTION));
						break;

					
					}
				}
				
			}
			
			
		};
	}
	

	
	

	@Override
	public void run()
	{
		// TODO Auto-generated method stub
		HttpGet get = new HttpGet(url+param.toString());
		try
		{
			HttpResponse response =new DefaultHttpClient().execute(get);
			int httpCode = response.getStatusLine().getStatusCode();
			if (httpCode == HttpStatus.SC_OK)
			{
				String ret =EntityUtils.toString(response.getEntity());

				ResponseParam responseParam =new ResponseParam(ret);
				
				Message message = new Message();
				message.what = actionId;
				message.arg1 = MSG_GET_SUCCESS;
				
				Bundle bundle = new Bundle();
				bundle.putSerializable(MSG_KEY_SERIALIZABLE, responseParam);
				message.setData(bundle);
				handler.sendMessage(message);
			}else {
				Message message =new Message();
				message.what = actionId;
				message.arg1 = MSG_GET_FAIL;
				message.arg2 = httpCode;
				handler.sendMessage(message);
			}
		}catch (Exception e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
			Message message = new Message();
			message.what = actionId;
			message.arg1 = MSG_GET_EXCEPTION;
			
			Bundle bundle =new Bundle();
			bundle.putString(MSG_KEY_EXCEPTION, e.getMessage());
			message.setData(bundle);
			handler.sendMessage(message);
		}
		
		
		
	}

}


                                       ---------完------------

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值