代码主体学习自:【Java项目-QQ聊天器实现的详细教程】https://www.bilibili.com/video/BV1yP411w7yU?vd_source=8131920da818b43b46a6caf0c2212cf4
增加了登陆注册、传送文件功能
login类(登录界面):
package com.wdzl;
import com.wdzl.util.PathUtil;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.sql.*;
import static java.lang.System.exit;
public class login extends JFrame{
private JButton loginLbl;
private JPasswordField mimaTfd;
private JTextField nameTfd;
private JButton exitLbl;
private JButton registerLbl;
public login(){
init();
}
private void init(){
//设置窗体大小
setSize(650,400);
//显示设置 默认居中显示
setLocationRelativeTo(null);
//设置绝对布局
setLayout(null);
//设置标题
setTitle("QQ聊天模拟器---用户登录");
//设置关闭窗体时退出程序
setDefaultCloseOperation(EXIT_ON_CLOSE);
//设置禁止改变窗体大小
setResizable(false);
//设置窗体图标(左上角)
String qqImgPath = PathUtil.getFilePath("img/qq.png");
ImageIcon qqIcon = new ImageIcon(qqImgPath);
setIconImage(qqIcon.getImage());
//设置背景
getContentPane().setBackground(Color.decode("#f1f9f9"));
//创建“QQ聊天模拟器”标签
JLabel titleLbl = new JLabel("QQ聊天模拟器");
titleLbl.setBounds(170, 30, 400, 100);
Font font1 = new Font("楷体", Font.PLAIN+Font.BOLD, 50);
titleLbl.setFont(font1);
add(titleLbl);
//创建“用户名”标签
JLabel nameLbl = new JLabel("用户名:");
nameLbl.setBounds(200, 140, 80, 50);
Font font = new Font("楷体", Font.PLAIN, 20);
nameLbl.setFont(font);
add(nameLbl);
//创建用户名输入框
nameTfd = new JTextField("");
nameTfd.setBounds(280, 140, 140, 50);
nameTfd.setFont(font);
add(nameTfd);
//创建“密码”标签
JLabel mimaLbl = new JLabel("密码:");
mimaLbl.setBounds(210, 195, 80, 50);
mimaLbl.setFont(font);
add(mimaLbl);
//创建密码输入框
mimaTfd = new JPasswordField("");
mimaTfd.setBounds(280, 195, 140, 50);
mimaLbl.setFont(font);
add(mimaTfd);
//创建“登录”按钮
loginLbl = new JButton("登录");
loginLbl.setBounds(150, 290, 70, 40);
Font font2 = new Font("楷体", Font.PLAIN, 15);
loginLbl.setFont(font2);
add(loginLbl);
//创建“注册”按钮
registerLbl = new JButton("注册");
registerLbl.setBounds(300, 290, 70, 40);
registerLbl.setFont(font2);
add(registerLbl);
//创建“退出”按钮
exitLbl = new JButton("退出");
exitLbl.setBounds(445, 290, 70, 40);
exitLbl.setFont(font2);
add(exitLbl);
//login页面跳转-----MainFrame页面----iflogin方法
loginLbl.addActionListener(new ActionListener() {
public void actionPerformed (ActionEvent e){
if (iflogin(nameTfd.getText(), mimaTfd.getText())) {
JOptionPane.showMessageDialog(null, "登陆成功!", "消息", -1);
new MainFrame().setVisible(true);
setVisible(false);
} else {
JOptionPane.showMessageDialog(null, "登陆失败!", "消息", -1);
}
}
});
//login页面跳转-----register页面
registerLbl.addActionListener(new ActionListener() {
public void actionPerformed (ActionEvent e){
new register().setVisible(true);
setVisible(false);
}
});
//login页面跳转-----终止程序
exitLbl.addActionListener(new ActionListener() {
public void actionPerformed (ActionEvent e){
JOptionPane.showMessageDialog(null, "成功退出程序!", "消息", -1);
exit(0);
}
});
}
//判断是否可以登录---在数据库中对比账号密码
public static boolean iflogin(String name, String password) {
if (name == null && password == null) {//判断输入是否合理
return false;
}
Connection con = null;//代表数据库连接
PreparedStatement pre = null;//用于执行预编译的SQL语句
ResultSet resultSet = null;//存储了从数据库查询返回的结果集
try {
con = DriverManager.getConnection("jdbc:mysql://localhost:3307/demo", "root", "123456");
String sql = "SELECT * FROM login WHERE name=? AND password=?";//编写SQL语句
pre = con.prepareStatement(sql);//将SQL查询语句作为参数传入
pre.setString(1, name);//设置第参数的值,(参数的位置,要设置的值)
pre.setString(2, password);
resultSet = pre.executeQuery();//执行查询,并将返回的结果集赋值
return resultSet.next();//判断结果集中是否有下一条记录
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
register类(连接Mysql进行注册账号):
package com.wdzl;
import com.wdzl.util.PathUtil;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.sql.*;
public class register extends JFrame implements ActionListener {
private JTextField nametfd;
private JPasswordField mimatfd;
private JPasswordField mimatfd2;
private ResultSet rs;
public register(){
init();
}
public void init() {
//设置窗体大小
setSize(400,650);
//显示设置 默认居中显示
setLocationRelativeTo(null);
//设置绝对布局
setLayout(null);
//设置标题
setTitle("QQ聊天模拟器---用户注册");
//设置关闭窗体时退出程序
setDefaultCloseOperation(EXIT_ON_CLOSE);
//设置禁止改变窗体大小
setResizable(false);
//设置窗体图标(左上角)
String qqImgPath = PathUtil.getFilePath("img/qq.png");
ImageIcon qqIcon = new ImageIcon(qqImgPath);
setIconImage(qqIcon.getImage());
//设置背景
getContentPane().setBackground(Color.decode("#f1f9f9"));
//创建“用户注册”标签
JLabel titleLbl = new JLabel("QQ用户注册");
titleLbl.setBounds(60, 50, 300, 150);
Font font2 = new Font("楷体", Font.PLAIN, 50);
titleLbl.setFont(font2);
add(titleLbl);
//创建“用户名”标签
JLabel nameLbl = new JLabel("用户名:");
nameLbl.setBounds(80, 200, 90, 50);
Font font = new Font("楷体", Font.PLAIN, 25);
nameLbl.setFont(font);
add(nameLbl);
//创建用户名输入框
nametfd = new JTextField("");
nametfd.setBounds(180, 200, 130, 50);
nametfd.setFont(font);
add(nametfd);
//创建“密码”标签
JLabel mimaLbl = new JLabel("密码:");
mimaLbl.setBounds(90, 260, 130, 50);
mimaLbl.setFont(font);
add(mimaLbl);
//创建密码输入框
mimatfd = new JPasswordField("");
mimatfd.setBounds(180, 260, 130, 50);
mimaLbl.setFont(font);
add(mimatfd);
//创建“确认密码”标签
JLabel mimaLbl2 = new JLabel("确认密码:");
mimaLbl2.setBounds(65, 320, 130, 50);
mimaLbl2.setFont(font);
add(mimaLbl2);
//创建 确认密码 输入框
mimatfd2 = new JPasswordField("");
mimatfd2.setBounds(180, 320, 130, 50);
mimaLbl.setFont(font);
add(mimatfd2);
//确定 按钮
JButton sureBtn = new JButton("确定");
sureBtn.setBounds(60,500,100,50);
Font font1 = new Font("楷体", Font.PLAIN, 20);
sureBtn.setFont(font1);
add(sureBtn);
//取消 按钮
JButton exitBtn = new JButton("取消");
exitBtn.setBounds(230,500,100,50);
exitBtn.setFont(font1);
add(exitBtn);
//register页面跳转-----login页面
exitBtn.addActionListener(new ActionListener() {
public void actionPerformed (ActionEvent e){
setVisible(false);
new login().setVisible(true);
}
});
//给按钮绑定监听
sureBtn.addActionListener(this);
}
//监听到被点击时,代码执行这里
@Override
public void actionPerformed(ActionEvent actionEvent) {
actionPerformed();
}
//注册
public void actionPerformed() {
String name = nametfd.getText();
String password = mimatfd.getText();
String password2 = mimatfd2.getText();
if (nametfd.getText().isEmpty() || mimatfd.getText().isEmpty() ||
mimatfd2.getText().isEmpty()) {
JOptionPane.showMessageDialog(null, "账号或密码不能为空");
return;
}
Connection con = null;
PreparedStatement pre = null;
rs = null;//存储了从数据库查询返回的结果集
try {
con = DriverManager.getConnection("jdbc:mysql://localhost:3307/demo", "root", "123456");
//检查用户名是否已存在在
String sql = "SELECT * FROM login WHERE name=? ";
pre = con.prepareStatement(sql);
pre.setString(1, name);
rs = pre.executeQuery();//执行查询,并将返回的结果集赋值
if (rs.next()) { //判断用户名是否重复
JOptionPane.showMessageDialog(null, "用户名已经存在");
}
else {
if (password2.equals(password)) { //如果两次输入密码一致
//数据库执行操作
String sql2 = "INSERT INTO login(name, password) VALUES (?, ?)";
pre = con.prepareStatement(sql2);
pre.setString(1, name);
pre.setString(2, password);
pre.executeUpdate();
JOptionPane.showMessageDialog(null, "注册成功");
new login().setVisible(true);
this.setVisible(false);
}
else {
JOptionPane.showMessageDialog(null, "两次密码输入不一致");
}
}
} catch (SQLException e) {
throw new RuntimeException("注册失败!", e);
}finally {
try {
// 关闭连接和释放资源
if (rs != null) rs.close();
if (pre != null) pre.close();
if (con != null) con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
MainFrame类(聊天界面、实时通讯、传送文件):
package com.wdzl;
import com.wdzl.util.PathUtil;//使用来自com.wdzl.util包下的PathUtil类
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.net.*;
public class MainFrame extends JFrame implements ActionListener {
private ImageIcon qqIcon;
private ImageIcon headIcon;
private DatagramSocket socket;
private String hostIp;
private int hostPort;
private JTextField ipTfd;
private JTextField sendTfd;
private JTextField portTfd;
private JPanel msgPanel;
private JTextField filePathTfd;
public MainFrame() {
initSocket();
loadImg();
initFrame();
initHead();
initIpPort();
initSendRecvComponents();
initMsgPanel();
}
//加载图像资源
private void loadImg() {
String qqImgPath = PathUtil.getFilePath("img/qq.png");
String headImgPath = PathUtil.getFilePath("img/rabbit(1).jpg");
qqIcon = new ImageIcon(qqImgPath);
headIcon = new ImageIcon(headImgPath);
}
//初始化窗体
private void initFrame() {
setSize(550, 500);
setLocationRelativeTo(null);
setLayout(null);
setTitle("QQ聊天模拟器 " + hostIp + " : " + hostPort);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setResizable(false);
setIconImage(qqIcon.getImage());
getContentPane().setBackground(new Color(203, 243, 252));
}
//初始化socket
private void initSocket() {
try {
socket = new DatagramSocket();
hostPort = socket.getLocalPort();
hostIp = InetAddress.getLocalHost().getHostAddress();
} catch (Exception e) {
throw new RuntimeException(e);
}
new RecevierThread().start();
}
//初始化头像
private void initHead() {
JLabel headLbl = new JLabel(headIcon);
headLbl.setSize(headIcon.getIconWidth(), headIcon.getIconHeight());
headLbl.setLocation(390, 30);
add(headLbl);
}
//初始化ip和port
private void initIpPort() {
JLabel ipLbl = new JLabel("IP:");
ipLbl.setBounds(380, 200, 140, 25);
add(ipLbl);
ipTfd = new JTextField("");
ipTfd.setBounds(380, 225, 150, 25);
add(ipTfd);
JLabel portLbl = new JLabel("Port:");
portLbl.setBounds(380, 260, 140, 25);
add(portLbl);
portTfd = new JTextField("");
portTfd.setBounds(380, 286, 150, 25);
add(portTfd);
}
private void initSendRecvComponents() {
sendTfd = new JTextField();
sendTfd.setBounds(5, 350, 370, 30);
add(sendTfd);
JButton sendTextBtn = new JButton("发送文字");
sendTextBtn.setBounds(415, 350, 90, 30);
sendTextBtn.addActionListener(this);
add(sendTextBtn);
JButton selectFileBtn = new JButton("选择文件");
selectFileBtn.setBounds(5, 390, 100, 30);
selectFileBtn.addActionListener(this);
add(selectFileBtn);
filePathTfd = new JTextField();
filePathTfd.setEditable(false);
filePathTfd.setBounds(110, 390, 250, 30);
add(filePathTfd);
JButton sendFileBtn = new JButton("发送文件");
sendFileBtn.setBounds(365, 390, 90, 30);
sendFileBtn.addActionListener(this);
add(sendFileBtn);
}
//初始化消息面板
private void initMsgPanel() {
msgPanel = new JPanel();
msgPanel.setBounds(5, 5, 370, 330);
msgPanel.setBackground(new Color(239, 239, 239));
add(msgPanel);
}
//监听到被点击时,代码执行这里
@Override
public void actionPerformed(ActionEvent actionEvent) {
JButton clickedButton = (JButton) actionEvent.getSource();
switch (clickedButton.getText()) {
case "发送文字":
sendMessage();
break;
case "选择文件":
chooseFile();
break;
case "发送文件":
sendSelectedFile();
break;
}
}
//发送消息
private void sendMessage() {
sendMessageAsText(sendTfd.getText());
sendTfd.setText(""); //清空消息栏
}
//获取要发送的内容
private void sendSelectedFile() {
String filePath = filePathTfd.getText();
if (!filePath.isEmpty()) {
sendFile(filePath, hostIp, portTfd.getText());
} else {
JOptionPane.showMessageDialog(this, "请选择一个文件");
}
}
//1.选择文件
private void chooseFile() {
JFileChooser fileChooser = new JFileChooser();
//弹出对话框让用户选择本地文件
if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
File selectedFile = fileChooser.getSelectedFile();
filePathTfd.setText(selectedFile.getAbsolutePath());
}
}
//2.“发送文件”按钮触发
private void sendMessageAsText(String msg) {
try {
InetAddress address = InetAddress.getByName(hostIp);
DatagramPacket packet = new DatagramPacket(msg.getBytes(), msg.length(), address, Integer.parseInt(portTfd.getText()));
socket.send(packet);
msgAddToMsgPanel(msg, FlowLayout.RIGHT);
} catch (Exception e) {
JOptionPane.showMessageDialog(this, "发送失败:" + e.getMessage());
}
}
//发送文件
private void sendFile(String filePath, String ip, String portStr) {
int port = Integer.parseInt(portStr);
try (FileInputStream fis = new FileInputStream(filePath)) {
byte[] buffer = new byte[1024];
int bytesRead;
//通过循环,每次从文件中读取一定数量的字节到一个字节数组
while ((bytesRead = fis.read(buffer)) != -1) {
//将这个字节数组封装
DatagramPacket packet = new DatagramPacket(buffer, bytesRead, InetAddress.getByName(ip), port);
socket.send(packet);
}
JOptionPane.showMessageDialog(this, "文件发送成功!");
} catch (IOException e) {
JOptionPane.showMessageDialog(this, "文件发送失败:" + e.getMessage());
}
}
//发送的消息添加到消息面板
private void msgAddToMsgPanel(String msg, int align) {
JLabel msgLbl = new JLabel(msg, SwingConstants.CENTER);
msgLbl.setForeground(new Color(align == FlowLayout.LEFT ? Color.BLACK.getRGB() : Color.BLUE.getRGB()));
msgLbl.setBackground(new Color(align == FlowLayout.LEFT ? Color.WHITE.getRGB() : new Color(27, 180, 238).getRGB()));
msgLbl.setOpaque(true);
msgLbl.setPreferredSize(new Dimension(370, 25));
JPanel itemPanel = new JPanel(new FlowLayout(align));
itemPanel.setBackground(new Color(239, 239, 239));
itemPanel.add(msgLbl);
msgPanel.add(itemPanel);
msgPanel.revalidate();
msgPanel.repaint();
}
//在后台线程中接收通过UDP协议发送的数据包
class RecevierThread extends Thread {
@Override
public void run() {
byte[] data = new byte[1024];
DatagramPacket packet = new DatagramPacket(data, data.length);
StringBuilder receivedData = new StringBuilder(); // 用于暂存接收到的数据
while (true) {
try {
socket.receive(packet); //阻塞等待直到有数据报到来
String msg = new String(data, 0, packet.getLength());
// 假设文件传输以特殊标识开始和结束
//以"FILESTART"开头但不以"FILEEND"结尾,将其追加到receivedData中
if (msg.startsWith("FILESTART") && !msg.endsWith("FILEEND")) {
receivedData.append(msg.substring("FILESTART".length()));
}
//以"FILEEND"结尾,说明文件传输结束
else if (msg.endsWith("FILEEND")) {
receivedData.append(msg.substring(0, msg.indexOf("FILEEND")));
// 文件数据接收完毕,打印接收到的整个文件内容
System.out.println("接收到的文件内容: " + receivedData);
// 清空StringBuilder以便下一次接收
receivedData.setLength(0);
}
else {
// 文本消息处理
SwingUtilities.invokeLater(() -> {
msgAddToMsgPanel(msg, FlowLayout.LEFT);
ipTfd.setText(packet.getAddress().getHostAddress());
portTfd.setText(Integer.toString(packet.getPort()));
});
}
} catch (IOException e) {
JOptionPane.showMessageDialog(MainFrame.this, "接收消息失败:" + e.getMessage());
}
}
}
}
}
MainTest类:
package com.wdzl;
public class TestMain {
public static void main(String[] args) {
new login().setVisible(true);
}
}
代码可运行
计算机小白,请多指教