TCP/IP实现本地聊天室(基于Swing)

1程序流程
1.1用户登录
客户端:
    1、发送登录信息:LOGIN|Username
	   处理USERLISTS命令:所有在线用户的用户名   ---(新客户端)
     2、处理新上线用户信息:ADD|username---(已在线客户端)
服务器端:
	 1、得到所有在线用户信息名称,发回给客户端:USERLISTS|user1_user2_user3
     2、将当前登录用户的信息(用户名),发送给已经在线的其他用户:ADD|userName
     3、将当前登录的Socket信息放入Arraylist中  
1.2私聊
客户端:
	  1、选择用户
          2、组织一个交互协议,发送到服务器:MSG|SenderName|RecName|MSGInfo
          3、接收服务器转发回的消息
    服务器端:
	  1、解析MSG消息协议字符串,通过RecName用户名查找,找到目标ServertChat
	  2、向目标对象发送消息:MSG|SenderName|MSGInfo
1.3 群聊
MSG|Sendername|ALL|MSGInfo
1.4 用户退出连接(待全部实现)
客户端:
	 1、向服务器发送下线消息:OFFLINE|username
	 2、清空在线用户列表
     3、处理下线用户信息:DEL|username
             删除用户列表中的username
  服务器端:
     1、处理OFFLINE,向所有的其他在线用户发送用户下线消息:DEL|username
	 2、清除ArrayList中的当前用户信息(ServerChat)
1.5 服务器关闭
  客户端:
	   1、处理CLOSE命令:界面显示服务器关闭
	   2、关闭ClientChat
       3、清空在线用户列表
    服务器端:
	  1、向所有在线用户发送关闭服务器的信息:CLOSE
	  2、遍历Arraylist将其中的每一个ServerChat关闭
	  3、ArrayList要清空
      4、关闭ServerListener
	  5、关闭ServerSocket
代码实现
客户端
//ClientChat.java
package Client;

import java.io.*;
import java.net.*;

public class ClientChat extends Thread {
	BufferedReader br=null;
	PrintWriter pw=null;
	Socket socket=null;
	
	public ClientChat(Socket socket,String sName) {
		super(sName);
		this.socket=socket;
	}
	
	public void run() {
		try {
			br=new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
			pw=new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8")));
			
			//向服务器发送LOGIN信息,格式 :  LOGIN|UserName
			String sLogin="LOGIN|"+this.getName();
			sendMsg(sLogin);
			
			String str="";
			while((str=br.readLine())!=null){  //多次发送数据处理的请求
				String [] commands=str.split("\\|");
				if(commands[0].equals("USERLISTS")){   //格式:LOGIN|UserName
					String sUSers=commands[1];
					String [] onlineusers=sUSers.split("\\_");  //所有在线用户名数组
					ClientMG.getclientMG().addItems(onlineusers);
				}
				else if(commands[0].equals("ADD")){//新用户上线
					String sNewUser=commands[1];
					ClientMG.getclientMG().addItem(sNewUser);
				}
				else if(commands[0].equals("MSG")){//接收服务器转发回的消息,显示在聊天记录中 ,格式:MSG|SenderName|MSGInfo
					String sendName=commands[1];
					String MsgInfo=commands[2];
					//
					ClientMG.getclientMG().setLogTxt("["+sendName+"]:");
					ClientMG.getclientMG().setLogTxt(MsgInfo);
				}
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		finally {
			try {
				if(pw!=null)
					pw.close();
				if(br!=null)
					br.close();
				if(socket!=null)
					socket.close();
				
			} catch (Exception e2) {
				e2.printStackTrace();
			}
			
		}
//		textResult.setText("与服务器断开连接。");
	}
	//发送消息
	public void sendMsg(String str){
		pw.println(str);
		pw.flush();
	}
}

//ClientForm.java
package Client;

import java.awt.BorderLayout;
import java.awt.EventQueue;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.border.TitledBorder;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.awt.event.ActionEvent;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.ScrollPaneConstants;
import javax.swing.JList;

public class ClientForm extends JFrame {

	private JPanel contentPane;
	public JPanel panel;
	public JLabel lblIp;
	public JTextField textIP;
	public JLabel label;
	public JTextField textPort;
	public JButton btnLogin;
	public JButton btnClose;
	public JPanel panel_1;
	public JTextArea textSend;
	public JButton btnSend;

	DefaultListModel<String> itemUsers;
	/**
	 * Launch the application.
	 */
	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					ClientForm frame = new ClientForm();
					frame.setVisible(true);
					ClientMG.getclientMG().setClientForm(frame);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * Create the frame.
	 */
	public ClientForm() {
		setTitle("\u5BA2\u6237\u7AEF");
		setResizable(false);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBounds(100, 100, 514, 608);
		contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		setContentPane(contentPane);
		contentPane.setLayout(null);
		
		panel = new JPanel();
		panel.setBorder(new TitledBorder(null, "\u914D\u7F6E\u4FE1\u606F", TitledBorder.LEADING, TitledBorder.TOP, null, null));
		panel.setBounds(10, 10, 488, 67);
		contentPane.add(panel);
		panel.setLayout(null);
		
		lblIp = new JLabel("IP");
		lblIp.setBounds(10, 31, 54, 15);
		panel.add(lblIp);
		
		textIP = new JTextField();
		textIP.setText("输入主机IP");
		textIP.setBounds(34, 28, 102, 21);
		panel.add(textIP);
		textIP.setColumns(10);
		
		label = new JLabel("\u7AEF\u53E3");
		label.setBounds(146, 31, 54, 15);
		panel.add(label);
		
		textPort = new JTextField();
		textPort.setText("60000");
		textPort.setBounds(178, 28, 45, 21);
		panel.add(textPort);
		textPort.setColumns(10);
		
		btnLogin = new JButton("\u767B\u5F55");
		btnLogin.addActionListener(new BtnLoginActionListener());
		btnLogin.setBounds(345, 27, 68, 23);
		panel.add(btnLogin);
		
		btnClose = new JButton("\u5173\u95ED");
		btnClose.addActionListener(new BtnCloseActionListener());
		btnClose.setBounds(410, 27, 68, 23);
		panel.add(btnClose);
		
		label_2 = new JLabel("\u7528\u6237\u540D");
		label_2.setBounds(229, 31, 54, 15);
		panel.add(label_2);
		
		textUser = new JTextField();
		textUser.setBounds(269, 28, 66, 21);
		panel.add(textUser);
		textUser.setColumns(10);
		
		panel_1 = new JPanel();
		panel_1.setBorder(new TitledBorder(null, "\u64CD\u4F5C", TitledBorder.LEADING, TitledBorder.TOP, null, null));
		panel_1.setBounds(10, 462, 488, 108);
		contentPane.add(panel_1);
		panel_1.setLayout(null);
		
		btnSend = new JButton("\u53D1\u9001");
		btnSend.addActionListener(new BtnSendActionListener());
		btnSend.setBounds(407, 75, 71, 23);
		panel_1.add(btnSend);
		
		btnSendAll = new JButton("\u7FA4\u53D1");
		btnSendAll.addActionListener(new BtnSendAllActionListener());
		btnSendAll.setBounds(336, 75, 65, 23);
		panel_1.add(btnSendAll);
		
		scrollPane_2 = new JScrollPane();
		scrollPane_2.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
		scrollPane_2.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
		scrollPane_2.setBounds(10, 24, 468, 42);
		panel_1.add(scrollPane_2);
		
		textSend = new JTextArea();
		textSend.setLineWrap(true);
		scrollPane_2.setViewportView(textSend);
		textSend.setColumns(10);
		
		scrollPane = new JScrollPane();
		scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
		scrollPane.setBorder(new TitledBorder(null, "\u804A\u5929\u8BB0\u5F55", TitledBorder.LEADING, TitledBorder.TOP, null, null));
		scrollPane.setBounds(10, 87, 286, 378);
		contentPane.add(scrollPane);
		
		textLog = new JTextArea();
		textLog.setLineWrap(true);
		scrollPane.setViewportView(textLog);
		
		scrollPane_1 = new JScrollPane();
		scrollPane_1.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
		scrollPane_1.setBorder(new TitledBorder(null, "\u5728\u7EBF\u7528\u6237", TitledBorder.LEADING, TitledBorder.TOP, null, null));
		scrollPane_1.setBounds(297, 87, 201, 378);
		contentPane.add(scrollPane_1);
		itemUsers=new DefaultListModel<String>();
		listUsers = new JList(itemUsers);
		scrollPane_1.setViewportView(listUsers);
	}
//	ClientChat sthd;
	public JLabel label_2;
	public JTextField textUser;
	public JScrollPane scrollPane;
	public JTextArea textLog;
	public JScrollPane scrollPane_1;
	public JList listUsers;
	public JButton btnSendAll;
	public JScrollPane scrollPane_2;
	private class BtnLoginActionListener implements ActionListener {
		public void actionPerformed(ActionEvent arg0) {
			String IP=textIP.getText().trim();  //IP
			int port=Integer.parseInt(textPort.getText().trim()); //端口号
			if(ClientMG.getclientMG().Connect(IP, port, textUser.getText().trim())){
				ClientMG.getclientMG().setLogTxt("已登录到服务器。");
			}
			else{
				ClientMG.getclientMG().setLogTxt("登录服务器失败!");
			}
		}
	}
	private class BtnSendActionListener implements ActionListener {
		public void actionPerformed(ActionEvent arg0) {
			//发送文本框的消息,格式:MSG|SenderName|RecName|MSGInfo
//			1、选择用户
//	        2、组织一个交互协议,发送到服务器:MSG|SenderName|RecName|MSGInfo
//			3、将发送消息显示在当前窗口的消息记录中
			if(listUsers.getSelectedIndex()>=0){
				String sendName=ClientMG.getclientMG().getClientChat().getName();
				String RecName=listUsers.getSelectedValue().toString();
				String MSGInfo=textSend.getText().trim();
				String MSG="MSG|"+sendName+"|"+RecName+"|"+MSGInfo;
				ClientMG.getclientMG().getClientChat().sendMsg(MSG); 			
				//将发送的消息放入消息记录框中
				ClientMG.getclientMG().setLogTxt("[我]:");
				ClientMG.getclientMG().setLogTxt(MSGInfo);
				//清空发送框
				textSend.setText("");
			}
			
		}
	}
	private class BtnCloseActionListener implements ActionListener {
		public void actionPerformed(ActionEvent arg0) {
			//断开连接
			//从客户端发送CLose命令到服务 器端,服务端关闭Socket对象,使当前客户端的br.readLine()返回空值,从而退出While,关闭Socket
//			sthd.sendMsg("CLOSE");  
		}
	}
	private class BtnSendAllActionListener implements ActionListener {
		public void actionPerformed(ActionEvent arg0) {
			
			String sendName=ClientMG.getclientMG().getClientChat().getName();
			String RecName="ALL";   //表示群发的功能
			String MSGInfo=textSend.getText().trim();
			String MSG="MSG|"+sendName+"|"+RecName+"|"+MSGInfo;
			ClientMG.getclientMG().getClientChat().sendMsg(MSG); 			
			//将发送的消息放入消息记录框中
			ClientMG.getclientMG().setLogTxt("[我]:");
			ClientMG.getclientMG().setLogTxt(MSGInfo);
			//清空发送框
			textSend.setText("");
		}
	}

}
//ClientMG.java
package Client;

import java.net.*;

public class ClientMG {

	private static final ClientMG clientMG=new ClientMG();
	private ClientMG() {}
	public static ClientMG getclientMG(){
		return clientMG;
	}
	
	private ClientForm clientWin;
	public void setClientForm(ClientForm c){
		clientWin=c;
	}
	public void setLogTxt(String str){
		clientWin.textLog.append(str+"\r\n");
	}
	
	ClientChat cChat;  //聊天用的Socket所在的线程
	public ClientChat getClientChat(){
		return cChat;
	}
	
	public boolean Connect(String IP,int port,String uName){
		Socket socket=null;
		try {
			socket=new Socket(IP, port);  //建立与服务器的连接
		
			cChat=new ClientChat(socket,uName);
			cChat.start();
			
			return true;
		}catch (Exception e) {
			e.printStackTrace();
			return false;
		} 
	}
	
	//列表项的操作
	
	//添加所有的在线用户
	public void addItems(String [] susers){
		for(int i=0;i<susers.length;i++){
			addItem(susers[i]);
		}
		
	}
	public void addItem(String sNewUser){
		clientWin.itemUsers.addElement(sNewUser);
	}
}

服务器端
//ServerChat.java
package Server;

import java.io.*;
import java.net.*;

//import SocketGUI.chat.Client.ClientMG;

public class ServerChat extends Thread {

	BufferedReader br = null;
	PrintWriter pw = null;
	Socket socket=null;
	public ServerChat(Socket socket) {
		this.socket = socket;
	}
	public void run() {
		
		try {
			//实例化br与pw
			br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
			pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8")));
		
			String str =""; 
			while((str = br.readLine())!=null){  //循环响应客户的发送信息//接收客户端发过来的信息,阻塞命令
				String [] commands=str.split("\\|");
				if(commands[0].equals("LOGIN")){   //格式:LOGIN|UserName
					String sUserName=commands[1];
					this.setName(sUserName);  //设置当前聊天用户的名称
					//处理LOGIN,步骤:
					//1、得到所有在线用户信息名称,发回给客户端:USERLISTS|user1_user2_user3
					ServerMG.getServerMG().getOnlineNames(this);					
					
					//2、将当前登录用户的信息(用户名),发送给已经在线的其他用户:ADD|userName
					
					ServerMG.getServerMG().sendNewUsertoAll(this);;
					
					//3、将当前登录的Socket信息放入Arraylist中
					ServerMG.getServerMG().addOnlielist(this);
					
				}
				else if(commands[0].equals("MSG")){  //格式:MSG|SenderName|RecName|MSGInfo
					String sendName=commands[1];
					String RecName=commands[2];
					String MSGInfo=commands[3];
					
					//要判断出是私聊还是群发
					if(RecName.equals("ALL")){
						//群发
						String sRtnMsg="MSG|"+sendName+"|"+MSGInfo;    //回复信息,格式:MSG|SenderName|MSGInfo
						ServerMG.getServerMG().sendMsgtoAll(sRtnMsg, this);
						ServerMG.getServerMG().setLogTxt(sendName+"发消息["+MSGInfo+"]给"+RecName);
					}
					else{
						//私聊
						ServerChat sc=ServerMG.getServerMG().getServerChatByName(RecName);  //找到接收目标Socket
						if(sc!=null){
							String sRtnMsg="MSG|"+sendName+"|"+MSGInfo;    //回复信息,格式:MSG|SenderName|MSGInfo
							sc.sendMsg(sRtnMsg);
							ServerMG.getServerMG().setLogTxt(sendName+"发消息["+MSGInfo+"]给"+RecName);
						}
					}
					
					
					
				}
				ServerMG.getServerMG().setLogTxt("客户端:"+str);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		finally {
			try {
				if (pw != null)
					pw.close();
				if (br != null)
					br.close();
				if (socket != null)
					socket.close();
				
			} catch (Exception e2) {
				e2.printStackTrace();
			}
		}
	}
	public void sendMsg(String str){
		pw.println(str);
		pw.flush();
	}
}

//ServerForm.java
package Server;
import java.awt.BorderLayout;
import java.awt.EventQueue;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.border.TitledBorder;



import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.JButton;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.ScrollPaneConstants;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.net.*;
import java.io.*;

public class ServerForm extends JFrame {

	private JPanel contentPane;
	public JPanel panel;
	public JLabel label;
	public JTextField textPort;
	public JButton btnStart;
	public JButton btnEND;
	public JScrollPane scrollPane;
	public JTextArea textLog;

	
	ServerSocket server = null;
	/**
	 * Launch the application.
	 */
	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					ServerForm frame = new ServerForm();
					frame.setVisible(true);
					ServerMG.getServerMG().setServerForm(frame);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * Create the frame.
	 */
	public ServerForm() {
		setTitle("\u591A\u4EBA\u804A\u5929\u670D\u52A1\u5668");
		setResizable(false);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBounds(100, 100, 450, 466);
		contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		setContentPane(contentPane);
		contentPane.setLayout(null);
		
		panel = new JPanel();
		panel.setBorder(new TitledBorder(null, "\u914D\u7F6E\u4FE1\u606F", TitledBorder.LEADING, TitledBorder.TOP, null, null));
		panel.setBounds(10, 10, 424, 66);
		contentPane.add(panel);
		panel.setLayout(null);
		
		label = new JLabel("\u7AEF\u53E3\uFF1A");
		label.setBounds(24, 31, 54, 15);
		panel.add(label);
		
		textPort = new JTextField();
		textPort.setText("60000");
		textPort.setBounds(62, 28, 66, 21);
		panel.add(textPort);
		textPort.setColumns(10);
		
		btnStart = new JButton("\u5F00\u542F\u670D\u52A1");
		btnStart.addActionListener(new BtnStartActionListener());
		btnStart.setBounds(153, 27, 93, 23);
		panel.add(btnStart);
		
		btnEND = new JButton("\u5173\u95ED\u670D\u52A1");
		btnEND.addActionListener(new BtnENDActionListener());
		btnEND.setBounds(256, 27, 93, 23);
		panel.add(btnEND);
		
		scrollPane = new JScrollPane();
		scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
		scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
		scrollPane.setBorder(new TitledBorder(null, "\u6D88\u606F\u8BB0\u5F55", TitledBorder.LEADING, TitledBorder.TOP, null, null));
		scrollPane.setBounds(10, 86, 424, 342);
		contentPane.add(scrollPane);
		
		textLog = new JTextArea();
		textLog.setLineWrap(true);
		scrollPane.setViewportView(textLog);
	}
	private class BtnStartActionListener implements ActionListener {
		public void actionPerformed(ActionEvent arg0) {
			int port = Integer.parseInt(textPort.getText().trim()); // //设定端口号			
			if(ServerMG.getServerMG().CreateServer(port)){
				ServerMG.getServerMG().setLogTxt("服务器开启...");
			}
			else {
				ServerMG.getServerMG().setLogTxt("服务器启动失败。");
			}

		}
	}
	private class BtnENDActionListener implements ActionListener {
		public void actionPerformed(ActionEvent arg0) {
			ServerMG.getServerMG().CloseServer();
		}
	}
}

//ServerListener.java
package Server;

import java.net.*;

public class ServerListener extends Thread {
	ServerSocket server=null;
	public ServerListener(ServerSocket server) {
		this.server=server;
		
	}
	public void run() {
		Socket socket = null;	
		try {
			while(true){ //监听多个客户端的连接
				socket = server.accept(); //监听客户端连接,客户端连接后,实例化Socket对象
				ServerMG.getServerMG().setLogTxt("客户端信息:" + socket);
				new ServerChat(socket).start();		//开启新线程用户与客户的数据交互		
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
}

//ServerMG.java
package Server;
import java.io.IOException;
import java.net.*;
import java.util.ArrayList;
public class ServerMG {
	//单例化类的实现方法
	private static final ServerMG servermg=new ServerMG();
	private ServerMG() {}
	public static ServerMG getServerMG(){
		return servermg;
	}
	
	//界面操作的相关方法
	private ServerForm serverWin;
	public void setServerForm(ServerForm s){
		serverWin=s;
	}
	
	public void setLogTxt(String str){
		serverWin.textLog.append(str+"\r\n");
	}
	
	ServerSocket server;
	public boolean CreateServer(int port){
		
		try {
			server = new ServerSocket(port);  //创建服务Socket,指定端口号
		
			new ServerListener(server).start();
		    return true;

		} catch (Exception e) {

			e.printStackTrace();
			return false;
		}
	}
	public void CloseServer(){
		try {
			//关闭所有在线Sokcet的步骤
			
			
			
			if(server!=null)
				server.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	//在线用户列表信息
	ArrayList<ServerChat> alOnlineList=new ArrayList<ServerChat>();
	public synchronized void addOnlielist(ServerChat sc){
		//限制同名称登录
		
		alOnlineList.add(sc);
	}
	
	public void getOnlineNames(ServerChat sc){
		if(alOnlineList.size()>0){  //在线用户列表非空时
			String stUser="";
			for(int i=0;i<alOnlineList.size();i++){
				ServerChat s=alOnlineList.get(i);
				stUser+=s.getName()+"_";
			}
			sc.sendMsg("USERLISTS|"+stUser);
		}		
	}
	public void sendNewUsertoAll(ServerChat scurr){
		//将当前登录用户的信息(用户名),发送给已经在线的其他用户
		for(int i=0;i<alOnlineList.size();i++){
			ServerChat s=alOnlineList.get(i);
			s.sendMsg("ADD|"+scurr.getName());
		}
	}
	
	//使用用户名查找相对应的Socket所在ServerChat对象
	public ServerChat getServerChatByName(String sname){
		for(int i=0;i<alOnlineList.size();i++){
			ServerChat s=alOnlineList.get(i);
			if(s.getName().equals(sname)){
				return s;
			}
		}
		return null;
	}
	
	
	//群发消息
	public void sendMsgtoAll(String msg,ServerChat sc){
		
		for(int i=0;i<alOnlineList.size();i++){
			ServerChat s=alOnlineList.get(i);
			if(!s.equals(sc)){   //排除发送者本身
				s.sendMsg(msg);
			}
			
		}
	}
	
	
	
	
}

只需要启动ServerForm 和ClientForm 即可运行

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值