【课程设计:Java课程设计局域网聊天外加文件传输功能】

2023-10-28 16:20

本文主要是介绍【课程设计:Java课程设计局域网聊天外加文件传输功能】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Java课程设计局域网聊天外加文件传输功能

  • 前言:
  • 整体思路:
    • 注册功能:
      • 注册页面:
      • 注册功能实现:
    • 登录功能:
    • 聊天功能:
      • 聊天页面:
      • 发送按钮实现监听:
    • 文件传输:
    • 服务器端:
    • 客户端
    • 数据库的方法
  • 作者留言:

前言:

本人大二新手一枚,在此发表自己的一些拙见,单纯发表一下记录自己第一次独立完成这个任务,说实话本人编程水平不强,完成此次课设的过程也是很艰难,走了很多弯路,如下图,大概说一下自己的思路,本人建议完成本项目最好实现聊天后再慢慢加功能,本人当时先做页面也没分客户端和服务端,走了很多弯路,实现聊天功能用的TCP,最好自己有整体思路后开始写。本人代码耦合性很大,有点费解,就是在各个类中跳转太多。下面开始解析我的代码。因为第一次写整个文章写的不是很好,解说的也不是很清楚,还望您见谅,所有源码都在这里面如果感兴趣麻烦您自己去看一下思路。
历经两次大改,多次小改

整体思路:

大概说一下聊天功能思路,在客户端中专门开一个线程接受信息,客户端所有的发送消息都在相应的监听中发送给服务器,在服务器端每当有一个客户端连接时就专门开一个线程处理。在服务端储存客户端的socket,发送消息和文件就找到对应的socket发送即可,我用了map存储。

注册功能:

按规定要求输入后客户端将相关信息发送给服务器后服务器存入数据库。

注册页面:

页面设置空布局,一个标签有一个框,验证码两个框。我一般使用的都是作用英文翻译的单词做对象名
注册页面

class TheSignupPage {private static RegisteredListener registerlListener = null;static JTextField VCodeshow;public TheSignupPage() {JFrame jf2 = new JFrame();jf2.setLayout(null);jf2.setBounds(600, 300, 450, 400);JLabel label = new JLabel("用户名:");label.setBounds(100, 50, 100, 25);JTextField username = new JTextField();username.setBounds(170, 50, 150, 25);username.setEditable(true);jf2.add(label);jf2.add(username);JLabel label1 = new JLabel("账    号:");label1.setBounds(100, 100, 100, 25);JTextField account = new JTextField();account.setBounds(170, 100, 150, 25);account.setEditable(true);jf2.add(label1);jf2.add(account);JLabel label2 = new JLabel("密    码:");label2.setBounds(100, 150, 100, 25);JPasswordField passwordField1 = new JPasswordField();passwordField1.setBounds(170, 150, 150, 25);passwordField1.setEditable(true);jf2.add(label2);jf2.add(passwordField1);JLabel label3 = new JLabel("确认密码:");label3.setBounds(100, 200, 100, 25);JPasswordField passwordField2 = new JPasswordField();passwordField2.setBounds(170, 200, 150, 25);passwordField2.setEditable(true);jf2.add(label3);jf2.add(passwordField2);JLabel label4 = new JLabel("验证码:");label4.setBounds(100, 250, 100, 25);JTextField VerificationCode = new JTextField();VerificationCode.setBounds(170, 250, 100, 25);VCodeshow = new JTextField(10);//验证码显示的框VCodeshow.setBounds(270, 250, 50, 25);VerificationCode.setEditable(true);VCodeshow.setEditable(false);Methods vCode = new Methods();VCodeshow.setText(vCode.randomCode());VCodeshow.addMouseListener(new MouseAdapter() {public void mouseClicked(MouseEvent e) {Methods Code = new Methods();VCodeshow.setText(Code.randomCode());}});jf2.add(label4);jf2.add(VerificationCode);jf2.add(VCodeshow);JButton okButton = new JButton("确  定");JButton cancle = new JButton("取  消");okButton.setBounds(100, 300, 70, 25);cancle.setBounds(250, 300, 70, 25);registerlListener = new RegisteredListener(username, account,passwordField1,passwordField2,VerificationCode,VCodeshow,jf2);//监听okButton.addActionListener(registerlListener);//为确定按钮添加监听cancle.addActionListener(event -> jf2.dispose());jf2.add(okButton);jf2.add(cancle);JButton cancleButton = new JButton(new ImageIcon("picture/退出1.png"));cancleButton.setBounds(422, 0, 26, 26);cancleButton.setRolloverIcon(new ImageIcon("picture/退出副本 2.png"));// setRolloverIcon()设置当光标移动到按钮上时显示的图像cancleButton.setBorderPainted(false);// 是否画边框,如果用自定义图片做按钮背景可以设为 false。jf2.add(cancleButton);cancleButton.addActionListener(event-> jf2.dispose());jf2.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);jf2.setUndecorated(true);jf2.setResizable(false);// 禁止改变窗口大小jf2.setVisible(true);}
}

验证码功能函数:

public String randomCode() {String str = "01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";int index;// = -1;// 存储字符串的下标char c;// 存储下标对应的字符StringBuffer code = new StringBuffer();// 创建StringBuffer对象code,存储生成的验证码Random r = new Random();// 创建随机数对象rfor (int i = 0; i < 4; i++) {index = r.nextInt(str.length());// nextInt生成随机数[0,str.length()]c = str.charAt(index);// 找到index位置的字符code.append(c);// 添加字符}String randomString = new String(code);return randomString;}

注册功能实现:

注册监听

public class RegisteredListener implements ActionListener {private JTextField a;private JTextField b;private JPasswordField c;private JPasswordField d;private JTextField e;private JTextField f;private JFrame g;public RegisteredListener(JTextField username, JTextField account, JPasswordField passwordField1,JPasswordField passwordField2, JTextField VerificationCode, JTextField VCodeshow,JFrame jf2) {super();this.a = username;this.b = account;this.c = passwordField1;this.d = passwordField2;this.e = VerificationCode;this.f = VCodeshow;this.g=jf2;}public void actionPerformed(ActionEvent event) {Methods Code = new Methods();if (b.getText().length() >= 5 && b.getText().length() <= 10) {if (new String(c.getPassword()).length() >= 5 || new String(c.getPassword()).length() <= 10) {if (f.getText().equals(e.getText())) {/** ==比较的是两个对象是不是同一个对象 equals比较的是两个对象的内容是不是相同 目前未能实现监督各框不能为空值*/MultiThreadClient.send(new StringBuffer(a.getText()).append("#135@").toString());try {Thread.sleep(500);//调用sleep()原调用线程快于赋值} catch (InterruptedException e1) {// TODO 自动生成的 catch 块e1.printStackTrace();}if (MultiThreadClient.UserNameCheck) {if (new String(c.getPassword()).equals(new String((d.getPassword())))) {/** 用getPassword() 代替, 返回char[] 数组类型.因为是char[]类型, 其equals方法是来自最原始的Object类,* 其相当于"=="(比较两者的地址是否一致,即指向的内存是否相同). 所以永远都不会相等,即当遇到数组类型时,不能用equals方法来比较.* 应该把char[] 类型转化为String类型(因为String类型的equals被String类override过, 表示对比两者的内容是否相等).*/StringBuffer cABuffer = new StringBuffer();cABuffer.append(a.getText());cABuffer.append("%account&");cABuffer.append(b.getText());cABuffer.append("%account&");cABuffer.append(new String(c.getPassword()));MultiThreadClient.send(cABuffer.toString());g.dispose();JOptionPane.showMessageDialog(null, "创建成功", "提示", JOptionPane.INFORMATION_MESSAGE);TheSignupPage.VCodeshow.setText(Code.randomCode());} else {JOptionPane.showMessageDialog(null, "两次密码不相同", "提示", JOptionPane.INFORMATION_MESSAGE);TheSignupPage.VCodeshow.setText(Code.randomCode());}} else {JOptionPane.showMessageDialog(null, "账号已存在", "提示", JOptionPane.INFORMATION_MESSAGE);TheSignupPage.VCodeshow.setText(Code.randomCode());}} else {JOptionPane.showMessageDialog(null, "验证码错误", "警告", JOptionPane.WARNING_MESSAGE);TheSignupPage.VCodeshow.setText(Code.randomCode());}} else {JOptionPane.showMessageDialog(null, "密码长度大于10或者小于5", "提示", JOptionPane.INFORMATION_MESSAGE);TheSignupPage.VCodeshow.setText(Code.randomCode());}} else {JOptionPane.showMessageDialog(null, "账号长度大于10或者小于5", "提示", JOptionPane.INFORMATION_MESSAGE);TheSignupPage.VCodeshow.setText(Code.randomCode());}}
}

登录功能:

登录界面
下拉框为数据库中已注册账号,注册账号后也会自动添加至此。
密码框有密码明示和隐藏两种功能。
登录界面

界面代码这里我做的太复杂了又是做几个panel又是几个函数处理,大概说一下,我这里开始用了一个BorderLayout布局分为五个区域,五个区域又分别放了五个panel,五个函数处理panel后返回panel。这样太过复杂,就一个空布局设置自己添加就行了。

public class QQLogin {public static JFrame jf = new JFrame();public static void initLogin() throws SQLException {jf.setSize(426, 300);jf.setLocation(497, 242);jf.setUndecorated(true);jf.setResizable(true);// 禁止改变窗口大小jf.setLayout(new BorderLayout());JPanel panel_north = CreatePanel.CreateNorthPanel(jf);jf.add(panel_north, BorderLayout.PAGE_START);JPanel panel_center = CreatePanel.CreateCenterpanel(jf);jf.add(panel_center, BorderLayout.CENTER);// 中间JPanel panel_west = CreatePanel.CreateWestPanel();jf.add(panel_west, BorderLayout.LINE_START);JPanel panel_south = CreatePanel.CreateSouthPanel();jf.add(panel_south, BorderLayout.PAGE_END);JPanel panel_east = CreatePanel.CreateEastPanel();jf.add(panel_east, BorderLayout.LINE_END);jf.setVisible(true);}
}

创造界面的函数:

public class CreatePanel {private static LoginListener ll = null;static JComboBox<Object> jcoCenter ;public static JPanel CreateNorthPanel(JFrame jf) {JPanel panel = new JPanel();panel.setLayout(null);panel.setPreferredSize(new Dimension(0, 140));ImageIcon image = new ImageIcon("picture/雷虎.jpg");JLabel background = new JLabel(image);background.setBounds(0, 0, 426, image.getIconHeight());//getIconHeight()获取图标的高度。panel.add(background);JButton out = new JButton(new ImageIcon("picture/退出1.png"));out.setBounds(397, 0, 32, 32);out.setRolloverIcon(new ImageIcon("picture/退出副本 2.png"));//setRolloverIcon()设置当光标移动到按钮上时显示的图像out.setBorderPainted(false);// 是否画边框,如果用自定义图片做按钮背景可以设为 false。panel.add(out);out.addActionListener(event->jf.dispose());return panel;}public static JPanel CreateWestPanel() {JPanel panel = new JPanel();panel.setLayout(null);panel.setPreferredSize(new Dimension(130, 0));//setPreferredSize仅仅是设置最好的大小ImageIcon image = new ImageIcon("picture/空白图2.jpg");//空白图,标签图(120,110)JLabel background = new JLabel(image);background.setBounds(0, 0, 110, 110);panel.add(background);return panel;//上面图片背景}public static JPanel CreateCenterpanel(JFrame jf) throws SQLException {JPanel panel = new JPanel();panel.setLayout(null);jcoCenter = new JComboBox<Object>();panel.add(jcoCenter);jcoCenter.setEditable(true);jcoCenter.setBounds(0, 15, 175, 30);jcoCenter.setFont(new Font("Calibri", 0, 13));//jcoCenter账号框ImageIcon image = new ImageIcon("picture/密码不可见.png");// 密码框中的密码显示按钮JButton jbu = new JButton(image);jbu.setToolTipText("显示密码");jbu.setPreferredSize(new Dimension(26, 26));jbu.setBorderPainted(false);JPasswordField jpaCenter = new JPasswordField();jpaCenter.setLayout(new FlowLayout(FlowLayout.RIGHT, 0, 0));jpaCenter.setBounds(0, 44, 175, 30);jpaCenter.setPreferredSize(new Dimension(185, 25));jpaCenter.add(jbu);panel.add(jpaCenter);// jpaCenter密码框不可见ImageIcon image2 = new ImageIcon("picture/密码可见2.png");//密码框中的密码显示按钮JButton jbu2 = new JButton(image2);jbu2.setToolTipText("隐藏密码");jbu2.setPreferredSize(new Dimension(26,26));jbu2.setBorderPainted(false);JTextField password  = new JTextField();password.setLayout(new FlowLayout(FlowLayout.RIGHT, 0, 0));password.setBounds(0, 44, 175, 30);password.setPreferredSize(new Dimension(185, 25));password.add(jbu2);password.setVisible(false);panel.add(password);//密码显示框jbu.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {// TODO 自动生成的方法存根jpaCenter.setVisible(false);String g=new String(jpaCenter.getPassword());password.setText(g);password.setVisible(true);}});jbu2.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {// TODO 自动生成的方法存根password.setVisible(false);String a=new String(password.getText());jpaCenter.setText(a);jpaCenter.setVisible(true);}});//从明示到隐藏JCheckBox jch1 = new JCheckBox("记住密码");jch1.setFocusPainted(false);jch1.setFont(new Font("宋体", 0, 13));jch1.setBounds(0, 85, 80, 20);panel.add(jch1);JCheckBox jch2 = new JCheckBox("自动登录");jch2.setFocusPainted(false);jch2.setFont(new Font("宋体", 0, 12));jch2.setBounds(100, 85, 80, 20);panel.add(jch2);ll = new LoginListener(jcoCenter, jpaCenter,password,jf);		return panel;}public static JPanel CreateEastPanel() {JPanel panel = new JPanel();panel.setLayout(null);panel.setPreferredSize(new Dimension(100, 0));JLabel regeist = new JLabel("注册账号");regeist.setForeground(new Color(100, 149, 238));regeist.setBounds(0, 13, 70, 30);regeist.setFont(new Font("宋体", 0, 12));JLabel regetpwd = new JLabel("找回密码");regetpwd.setForeground(new Color(100, 149, 238));regetpwd.setBounds(0, 43, 70, 30);regetpwd.setFont(new Font("宋体", 0, 12));panel.add(regetpwd);panel.add(regeist);regeist.addMouseListener(new MouseAdapter() {//注册账号这里设置监听点击就弹出注册页面public void mouseClicked(MouseEvent e) {// TODO 自动生成的方法存根new TheSignupPage();}	});return panel;}public static JPanel CreateSouthPanel() {JPanel panel = new JPanel();panel.setPreferredSize(new Dimension(0, 51));panel.setLayout(null);JButton jble = new JButton(new ImageIcon("picture/爱心.jpg"));jble.setPreferredSize(new Dimension(40, 40));jble.setFocusPainted(false);jble.setRolloverIcon(new ImageIcon("picture/爱心副本.jpg"));jble.setBorderPainted(false);//为falsejble.setContentAreaFilled(false);jble.setBounds(0, 10, 40, 40);jble.setToolTipText("多账号登录");//在控件上显示提示信息ImageIcon image = new ImageIcon("picture/登录.png");JButton jb = new JButton("登   录", image);//175;40jb.setFont(new Font("宋体", 0, 13));jb.setBounds(130, 0, 175, 40);jb.setHorizontalTextPosition(SwingConstants.CENTER);//setHorizontalTextPosition,登录相对于图像的位置jb.setFocusPainted(false);jb.setBorderPainted(false);jb.setContentAreaFilled(false);jb.setRolloverIcon(new ImageIcon("picture/登录 - 副本.png"));JButton jbri = new JButton(new ImageIcon("picture/二维码登录2.png"));jbri.setBounds(380, 10, 40, 40);jbri.setFocusPainted(false);jbri.setBorderPainted(false);jbri.setContentAreaFilled(false);jbri.setRolloverIcon(new ImageIcon("picture/二维码登录.png"));jbri.setToolTipText("二维码登录");panel.add(jble);panel.add(jb);panel.add(jbri);jb.addActionListener(ll);//登录按钮加入监听		//登录界面return panel;}
}

登录页面的监听和服务端交互

public class LoginListener implements ActionListener {private JComboBox<Object> jco;private JPasswordField jpa;private JTextField jt;static String name;public LoginListener(JComboBox<Object> jco, JPasswordField jpa, JTextField jt, JFrame jf) {super();// 调用actionPerformed(ActionEvent e)this.jco = jco;// actionPerformed()中使用的jpa为LoginListener()调入的this.jpa = jpa;this.jt = jt;}public void actionPerformed(ActionEvent e) {name = (String) jco.getSelectedItem();String pwd = new String(jpa.getPassword());String pwd2 = new String(jt.getText());StringBuffer acountcheck = new StringBuffer();acountcheck.append(name);acountcheck.append("!#");acountcheck.append(pwd);acountcheck.append("!#");acountcheck.append(pwd2);MultiThreadClient.send(acountcheck.toString());//发送账号密码给服务器调用客户端的send方法}
}

聊天功能:

建议先实现群聊再实现私聊。此聊天进入后默认群聊模式。

聊天页面:

群聊展示
选择群聊模式消息文件发送给所有人。
私聊页面
私聊则需在下拉框选择对象后发送消息,此下拉框实时更新既用户上线则会自动添加,有用户下线则自动删除下线用户。

创造界面的代码:

public class TheChatPanel {static TheChatListener theChatListener;JComboBox<Object> jcoCenter = new JComboBox<Object>();JPanel panel;JTextArea out;JPanel showJPanel;JButton breakButton;JButton send;JTextArea input;JScrollPane c2;public TheChatPanel() {JFrame jfn = new JFrame("当前聊天框所属账号为:"+LoginListener.name);jfn.setSize(865, 590);jfn.setLocation(600, 100);panel = new JPanel();panel.setLayout(null);Font font = new Font("楷体", Font.BOLD, 18);out = new JTextArea();JScrollPane c1 = new JScrollPane(out);out.setLineWrap(true);// 单词边界自动换行out.setFont(font);out.setEditable(false);c1.setSize(700, 350);c1.setLocation(0, 0);showJPanel = new JPanel();showJPanel.setLayout(null);showJPanel.setSize(150, 550);showJPanel.setLocation(700, 0);JLabel label = new JLabel("请选择聊天模式");label.setBounds(0, 10, 150, 30);label.setFont(new Font("楷体", 0, 18));ButtonGroup ChatState = new ButtonGroup();JRadioButton PrivateChat = new JRadioButton("私聊");JRadioButton GroupChat = new JRadioButton("群聊", true);ChatState.add(PrivateChat);ChatState.add(GroupChat);GroupChat.setBounds(30, 40, 120, 30);GroupChat.setFocusable(false);GroupChat.setFont(new Font("楷体", 0, 18));GroupChat.addActionListener(e -> {jcoCenter.setVisible(false);JOptionPane.showMessageDialog(jfn, "当前模式为群聊模式将会把信息发送给所有用户", "提示", JOptionPane.INFORMATION_MESSAGE);});PrivateChat.setBounds(30, 70, 120, 30);PrivateChat.setFocusable(false);PrivateChat.setFont(new Font("楷体", 0, 18));PrivateChat.addActionListener(e -> {jcoCenter.setVisible(true);});jcoCenter.addItem("请选择聊天对象");jcoCenter.setEditable(false);jcoCenter.setVisible(false);jcoCenter.setBounds(0, 100, 150, 30);jcoCenter.setFont(new Font("楷体", 0, 16));showJPanel.add(label);showJPanel.add(GroupChat);showJPanel.add(PrivateChat);showJPanel.add(jcoCenter);JLabel ai = new JLabel("聊天输入框↓↓↓", JLabel.CENTER);ai.setFont(font);ai.setBounds(0, 350, 700, 30);send = new JButton("发送");send.setBounds(610, 0, 80, 30);send.setFont(font);send.setFocusable(false);ai.add(send);panel.add(ai);input = new JTextArea();input.setLineWrap(true);// 单词边界自动换行input.setFont(font);c2 = new JScrollPane(input);c2.setSize(702, 170);c2.setLocation(0, 380);new Main2();Main2.Filename(input);theChatListener = new TheChatListener(jfn, out, input, jcoCenter, PrivateChat, GroupChat);panel.add(c1);panel.add(c2);panel.add(showJPanel);jfn.add(panel);jfn.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);jfn.setUndecorated(false);jfn.setResizable(false);// 禁止改变窗口大小jfn.setVisible(true);jfn.addWindowListener(new WindowAdapter() {public void windowClosing(WindowEvent e) {MultiThreadClient.send(new StringBuffer(LoginListener.name).append("#13@").toString());}});send.addActionListener(theChatListener);//发送按钮添加监听。}
}

发送按钮实现监听:

包含判断群聊是否有用户在线,私聊是否选择了用户,判断是消息还是文件。

public class TheChatListener implements ActionListener {public JTextArea jte = new JTextArea();// 聊天记录显示框public JTextArea jte2 = new JTextArea();StringBuffer ChatRecord;JComboBox<Object> SelectUser;String dateString;JRadioButton PrivateChat;JRadioButton GroupChat;JFrame thechatFrame;public TheChatListener(JFrame aa, JTextArea jteOUT, JTextArea jtePUT, JComboBox<Object> jco,JRadioButton PrivateChat, JRadioButton GroupChat) {super();this.jte = jteOUT;this.jte2 = jtePUT;this.SelectUser = jco;this.PrivateChat = PrivateChat;this.GroupChat = GroupChat;this.thechatFrame = aa;}public void actionPerformed(ActionEvent e) {String check = jte2.getText();int usernumber = SelectUser.getItemCount();if (check == null || check.trim().length() == 0) {// check.trim().length()==0 删除所有空格后还剩长度,输入空格也不行。JOptionPane.showMessageDialog(thechatFrame, "发送信息为空或者为全空格", "提示", JOptionPane.INFORMATION_MESSAGE);} else if (GroupChat.isSelected()) {// 群聊模式if (usernumber == 1) {// 无人在线JOptionPane.showMessageDialog(thechatFrame, "当前无用户在线无法群聊", "提示", JOptionPane.INFORMATION_MESSAGE);} else if (check.contains("发送文件为:")) {String FileAddress[] = check.split("发送文件为:");String filename[] = new String(FileAddress[1]).split("\\\\");MultiThreadClient.sendFile(FileAddress[1], LoginListener.name, "alluser",filename[filename.length - 1]);setusermessge("服务器提示", "你向全部用户发送了“" + filename[filename.length - 1]+"”");jte2.setText("");} else {// 有人在线StringBuffer Message = new StringBuffer();Message.append(LoginListener.name);Message.append("%&");Message.append("alluser");Message.append("%&");Message.append(check);MultiThreadClient.send(Message.toString());setusermessge("当前用户-" + LoginListener.name, check);jte2.setText("");}} else {// 私聊模式String item = (String) SelectUser.getSelectedItem();if (!item.equals("请选择聊天对象")) {if (check.contains("发送文件为:")) {String FileAddress[] = check.split("发送文件为:");String filename[] = new String(FileAddress[1]).split("\\\\");MultiThreadClient.sendFile(FileAddress[1], LoginListener.name, item, filename[filename.length - 1]);setusermessge("服务器提示" , "你向" + item + "用户发送了“" + filename[filename.length - 1]+"”");jte2.setText("");} else {// 发送为消息StringBuffer Message = new StringBuffer();Message.append(LoginListener.name);Message.append("%&");Message.append(item);Message.append("%&");Message.append(check);MultiThreadClient.send(Message.toString());setusermessge("当前用户-" + LoginListener.name, check);jte2.setText("");}} else if (usernumber == 1) {JOptionPane.showMessageDialog(thechatFrame, "当前无用户在线请稍后尝试", "提示", JOptionPane.INFORMATION_MESSAGE);} else {JOptionPane.showMessageDialog(thechatFrame, "请选择聊天对象后发送", "提示", JOptionPane.INFORMATION_MESSAGE);}}}public void setusermessge(String user, String usermessage) {//设置消息面板的消息LocalDateTime dateTime = LocalDateTime.now();dateString = String.valueOf(dateTime).substring(0, 19);// substring(0,19)截取二十个,原字符串带秒以后的单位太长ChatRecord = new StringBuffer();ChatRecord.append(jte.getText());ChatRecord.append("                            " + dateString + "\n");ChatRecord.append(user + ":" + "\n");ChatRecord.append("    " + "“");ChatRecord.append(usermessage);ChatRecord.append("”" + "\n");jte.setText(new String(ChatRecord));}
}

文件传输:

发送按钮实现监听已经包含文件传输的功能,在这说一下一个小操作就是直接拉文件进入聊天发送框的代码,
这里实现监听和运用了监听适配器。
在这里插入图片描述

public class Main2 {public static void Filename(JTextArea textArea) {// 创建拖拽目标监听器DropTargetListener listener = new DropTargetListenerImpl5(textArea);// 在 textArea 上注册拖拽目标监听器new DropTarget(textArea, DnDConstants.ACTION_COPY_OR_MOVE, listener, true);}private static class DropTargetListenerImpl5 extends DropTargetAdapter {private JTextArea textArea;public DropTargetListenerImpl5(JTextArea textArea) {this.textArea = textArea;}public void drop(DropTargetDropEvent dtde) {// TODO 自动生成的方法存根// 一般情况下只需要关心此方法的回调boolean isAccept = false;try {/** 1. 文件: 判断拖拽目标是否支持文件列表数据(即拖拽的是否是文件或文件夹, 支持同时拖拽多个)*/if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {// 接收拖拽目标数据dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);isAccept = true;// 以文件集合的形式获取数据List<File> files = (List<File>) dtde.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);// 把文件路径输出到文本区域if (files != null && files.size() > 0) {StringBuilder filePaths = new StringBuilder();for (File file : files) {filePaths.append("发送文件为:" + file.getAbsolutePath());}textArea.append(filePaths.toString());}}} catch (Exception e) {e.printStackTrace();}// 如果此次拖拽的数据是被接受的, 则必须设置拖拽完成(否则可能会看到拖拽目标返回原位置, 造成视觉上以为是不支持拖拽的错误效果)if (isAccept) {dtde.dropComplete(true);}}}
}

服务器端:

主要处理来自客户端的一些操作和请求,以及一些准备操作。

public class MultiThreadServer {public static boolean isStart = false;private static Map<String, UserInformation> clientMap = new ConcurrentHashMap<>();Methods methods = new Methods();static long FileIdentification = 256341875;static long SENDMSG = 951636247;public MultiThreadServer() {new Thread(new reaciveUser()).start();}class UserInformation {String name;Socket clientSocket;UserInformation(String name, Socket clientSocket) {this.name = name;this.clientSocket = clientSocket;}}class reaciveUser implements Runnable {public void run() {ServerSocket serverSocket = null;try {// serverSocket = null;serverSocket = new ServerSocket(6666);if (serverSocket != null) {isStart = true;}for (int i = 0; i < 20; i++) {Socket client = null;client = serverSocket.accept();// 每当有用户连接,新建线程处理new Thread(new user(client)).start();}} catch (IOException e1) {e1.printStackTrace();}try {serverSocket.close();isStart = false;} catch (IOException e) {e.printStackTrace();}}}class user implements Runnable {private Socket client;private user(Socket client) {this.client = client;}public void run() {try {while (isStart) { // 循环接受消息DataInputStream dis = new DataInputStream(client.getInputStream());long aaLong = dis.readLong();if (aaLong == 256341875) {// 文件接受String sendString = dis.readUTF();String reciver = dis.readUTF();String FileName = dis.readUTF();long Long = dis.readLong();// 文件长度结束标志File file = new File("d:\\A服务器接受文件");if (!file.exists()) {file.mkdirs();}FileOutputStream aa = new FileOutputStream(file + "\\" + FileName);int length = 0;long filelong = 0;byte[] buf = new byte[1024];while ((length = dis.read(buf)) != -1) {filelong = filelong + length;aa.write(buf, 0, length);if (filelong == Long) {filelong = 0;break;}}aa.close();if (reciver.equals("alluser")) {Socket currentSocket = clientMap.get(sendString).clientSocket;for (UserInformation clientInformation : clientMap.values()) {Socket clientSocket = clientInformation.clientSocket;if (!(clientSocket == currentSocket)) {long FileIdentification = 256341875;FileInputStream sendfile = new FileInputStream(file + "\\" + FileName);DataOutputStream dos = new DataOutputStream(clientSocket.getOutputStream());dos.writeLong(FileIdentification);dos.writeUTF(clientMap.get(sendString).name);dos.writeUTF(FileName);dos.writeLong(Long);int lth = 0;byte[] bu = new byte[1024];while ((lth = sendfile.read(bu)) != -1) {filelong = filelong + length;dos.write(bu, 0, lth);if (filelong == Long) {filelong = 0;break;}}sendfile.close();}}} else {for (UserInformation clientInformation : clientMap.values()) {if (reciver.equals(clientInformation.name)) {Socket socket2 = clientInformation.clientSocket;FileInputStream sendfile = new FileInputStream(file + "\\" + FileName);DataOutputStream dos = new DataOutputStream(socket2.getOutputStream());dos.writeLong(FileIdentification);dos.writeUTF(clientMap.get(sendString).name);dos.writeUTF(FileName);dos.writeLong(Long);int leth = 0;byte[] bu = new byte[1024];while ((leth = sendfile.read(bu)) != -1) {dos.write(bu, 0, leth);}sendfile.close();}}}} else if (aaLong == 951636247) {String mes = dis.readUTF();try {// 面板展示账号查询if (mes.contains("#*@")) {String OnlineUsers = methods.Project();sendMsg(client, OnlineUsers);}} catch (SQLException e1) {e1.printStackTrace();}// 服务器注册用户if (mes.contains("%account&")) {String creataccount[] = new String[10];creataccount = mes.split("%account&");String username = creataccount[0].toString();String account = creataccount[1].toString();String password = creataccount[2].toString();try {(new Methods()).CreateAccount(username, account, password);} catch (ClassNotFoundException e) {e.printStackTrace();} catch (SQLException e) {e.printStackTrace();}}// 登录账号查询if (mes.contains("!#")) {String cc[] = mes.split("!#");if (!clientMap.containsKey(cc[0])) {try {int len = cc.length;Boolean b1 = methods.findUser(cc[0], cc[1]);Boolean b2 = methods.findUser(cc[0], cc[len - 1]);if (b1 || b2) {String username = methods.FindName(cc[0]);sendMsg(client, "#!true");StringBuffer user = new StringBuffer();if (!clientMap.isEmpty()) {// 已在线用户for (UserInformation key : clientMap.values()) {user.append(key.name);user.append("##");}sendMsg(client, user.toString());sendMsgToAll(new StringBuffer(username).append("1!@#&").toString());}UserInformation userInformation = new UserInformation(username, client);clientMap.put(cc[0], userInformation);} else {// 账号登录错误sendMsg(client, "#!false");}} catch (SQLException e) {e.printStackTrace();}} else {sendMsg(client, "#!existing");}} // 账号查询if (mes.contains("#135@")) {String userName[] = new String[2];userName = mes.split("#135@");try {if ((new Methods()).CheckAccoun(userName[0].toString())) {// 账号未被注册sendMsg(client, "++true");} else {// 账号已被注册sendMsg(client, "++false");}} catch (ClassNotFoundException e) {e.printStackTrace();} catch (SQLException e) {System.out.println("查询名称错误");}}// 信息发送if (mes.contains("%&")) {String message[] = new String[10];message = mes.split("%&");int len = message.length;String senduser = clientMap.get(message[0].toString()).name;String reciveuser = message[1].toString();// reciveuser为接收者名字StringBuffer Message = new StringBuffer();Message.append(senduser);Message.append("%&");Message.append(message[len - 1].toString());if (reciveuser.equals("alluser")) {Socket currentSocket = clientMap.get(message[0].toString()).clientSocket;for (UserInformation clientInformation : clientMap.values()) {Socket clientSocket = clientInformation.clientSocket;if (!(clientSocket == currentSocket)) {DataOutputStream dos = new DataOutputStream(clientSocket.getOutputStream());dos.writeLong(SENDMSG);dos.writeUTF(Message.toString());}}} else {for (UserInformation clientInformation : clientMap.values()) {if (reciveuser.equals(clientInformation.name)) {Socket socket2 = clientInformation.clientSocket;sendMsg(socket2, Message.toString());}}}}if (mes.contains("#13@")) {String closeuser[] = new String[2];closeuser = mes.split("#13@");// 下线用户String user = closeuser[0].toString();Socket closeSocket = clientMap.get(user).clientSocket;String userbye = clientMap.get(closeuser[0].toString()).name;sendMsg(closeSocket, "#13@");clientMap.remove(closeuser[0].toString());if (!(clientMap.size() == 0)) {sendMsgToAll(new StringBuffer(userbye).append("#10*").toString());}isStart = false;client.close();}}}} catch (IOException e) {e.printStackTrace();// System.out.println("客户端连接异常中断");}}public void sendMsg(Socket usersSocket, String str) {try {DataOutputStream dos = new DataOutputStream(usersSocket.getOutputStream());dos.writeLong(SENDMSG);dos.writeUTF(str);} catch (IOException e) {e.printStackTrace();}}public void sendMsgToAll(String str) {try {for (UserInformation clientInformation : clientMap.values()) {Socket clientSocket = clientInformation.clientSocket;DataOutputStream dos = new DataOutputStream(clientSocket.getOutputStream());dos.writeLong(SENDMSG);dos.writeUTF(str);}} catch (IOException e) {e.printStackTrace();}}}public static void main(String[] args) {new MultiThreadServer();}
}

客户端

主要接受来自服务器的消息然后进行处理

public class MultiThreadClient {static Socket s;boolean isConn = false;TheChatPanel chat;QQLogin qqLogin;String aa[] = new String[100];static boolean UserNameCheck = false;static long SENDMSG = 951636247;static long FileIdentification = 256341875;public MultiThreadClient() {try {s = new Socket("127.0.0.1", 6666);send("#*@");// 发送消息获取列表isConn = true;new Thread(new reaciveMsg()).start();} catch (UnknownHostException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}static void sendFile(String FileAddress, String sendname, String ReceviceUser, String filename) {DataOutputStream dos;try {File file2 = new File(FileAddress);try (FileInputStream file = new FileInputStream(file2)) {dos = new DataOutputStream(s.getOutputStream());dos.writeLong(FileIdentification);dos.writeUTF(sendname);dos.writeUTF(ReceviceUser);dos.writeUTF(filename);dos.writeLong(file2.length());int length = 0;byte[] buf = new byte[1024];while ((length = file.read(buf)) != -1) {dos.write(buf, 0, length);}}} catch (IOException e) {e.printStackTrace();}}static void send(String str) {DataOutputStream dos;try {dos = new DataOutputStream(s.getOutputStream());dos.writeLong(SENDMSG);dos.writeUTF(str);} catch (IOException e) {e.printStackTrace();}}class reaciveMsg implements Runnable {String OnlineUsers;reaciveMsg() {try {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}QQLogin.initLogin();} catch (SQLException e2) {e2.printStackTrace();}}public void run() {try {while (isConn) { // 循环接受消息DataInputStream dis = new DataInputStream(s.getInputStream());long aaLong = dis.readLong();if (aaLong == 256341875) {// 文件接受String sendString = dis.readUTF();String FileName = dis.readUTF();long filelength = dis.readLong();File file = new File("d:\\A客户端接受文件");if (!file.exists()) {file.mkdirs();}FileOutputStream aa = new FileOutputStream(file + "\\" + FileName);int length = 0;byte[] buf = new byte[1024];long filelong = 0;while ((length = dis.read(buf)) != -1) {filelong = filelong + length;aa.write(buf, 0, length);if (filelong == filelength) {filelength = 0;break;}}aa.close();TheChatPanel.theChatListener.setusermessge("服务器提示", sendString+"向你发来文件“" + FileName + "”存储于: " + file);// 接收文字信息} else if (aaLong == 951636247) {String str = dis.readUTF();if (str.contains("#337@")) {OnlineUsers = str;try {String user[] = new Methods().Project().split("#337@");for (int i = 0; i < user.length; i++) {CreatePanel.jcoCenter.addItem(user[i]);}} catch (SQLException e) {e.printStackTrace();}}if (str.equals("#!existing")) {JOptionPane.showMessageDialog(QQLogin.jf, "该用户已经登录");} else {if (str.equals("#!true")) {QQLogin.jf.dispose();chat = new TheChatPanel();JOptionPane.showMessageDialog(null, "登录成功,系统开启默认群聊模式,如有需要请自行更改");} else if (str.equals("#!false")) {JOptionPane.showMessageDialog(QQLogin.jf, "你输入的账户名和密码不正确请重新输入!");}}if (str.contains("##")) {aa = str.split("##");for (int i = 0; i < aa.length; i++) {chat.jcoCenter.addItem(aa[i].toString());}} else if (str.contains("1!@#&")) {String OnlineUsers[] = new String[50];OnlineUsers = str.split("1!@#&");chat.jcoCenter.addItem(OnlineUsers[0].toString());}if (str.contains("%&")) {// 设置消息String message[] = new String[10];message = str.split("%&");int len = message.length;String userString = message[0].toString();String XinString = message[len - 1].toString();TheChatPanel.theChatListener.setusermessge(userString, XinString);}// 本客户端关闭if (str.contains("#13@")) {isConn = false;s.close();}// 其他用户下线if (str.contains("#10*")) {String message[] = new String[10];message = str.split("#10*");String RemoveUser = message[0].toString();chat.jcoCenter.removeItem(RemoveUser);}if (str.equals("++true")) {UserNameCheck = true;} else if (str.equals("++false")) {UserNameCheck = false;}}}} catch (IOException e) {e.printStackTrace();}}}public static void main(String[] args) throws UnknownHostException, IOException, SQLException {// TODO 自动生成的方法存根new MultiThreadClient();}
}

数据库的方法

结构有点混乱,当时将验证码方法写在了此类中,并且数据方法写的也不简便部分重复,例如数据的连接可以放外部。

class Methods {PreparedStatement prestmt = null;Connection conn = null;ResultSet rs = null;boolean aa;String ur1 = "jdbc:mysql://localhost:3306/jdbc";String user = "root";String password = "200210";public String randomCode() {String str = "01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";int index;// = -1;// 存储字符串的下标char c;// 存储下标对应的字符StringBuffer code = new StringBuffer();// 创建StringBuffer对象code,存储生成的验证码Random r = new Random();// 创建随机数对象rfor (int i = 0; i < 4; i++) {index = r.nextInt(str.length());// nextInt生成随机数[0,str.length()]c = str.charAt(index);// 找到index位置的字符code.append(c);// 添加字符}String randomString = new String(code);return randomString;}public Boolean findUser(String qqnumber, String pwd) throws SQLException {try {Class.forName("com.mysql.cj.jdbc.Driver");conn = DriverManager.getConnection(ur1, user, password);String sql = "select * from tb_qquser where qqnumber = ? and password = ?";prestmt = conn.prepareStatement(sql);prestmt.setString(1, qqnumber);prestmt.setString(2, pwd);rs = prestmt.executeQuery();if (rs.next()) {return true;}} catch (Exception e) {e.printStackTrace();} finally {if (rs != null) {rs.close();}if (prestmt != null) {prestmt.close();}if (conn != null) {conn.close();}}return false;}boolean CreateAccount(String username, String qqnumber, String passwordtext)throws ClassNotFoundException, SQLException {try {Class.forName("com.mysql.cj.jdbc.Driver");conn = DriverManager.getConnection(ur1, user, password);String sql = "insert into tb_qquser(qqnumber,password,username)values(?,?,?);";prestmt = conn.prepareStatement(sql);prestmt.setString(1, qqnumber);prestmt.setString(2, passwordtext);prestmt.setString(3, username);// TODO 自动生成的方法存根aa = prestmt.execute();if (aa) {// next() 获得匹配元素集合中每个元素紧邻的同胞元素。如果提供选择器,则取回匹配该选择器的下一个同胞元素return true;} else {return false;}} catch (Exception e) {e.printStackTrace();} finally {if (rs != null) {rs.close();}if (prestmt != null) {prestmt.close();}if (conn != null) {conn.close();}}return false;}boolean CheckAccoun(String qqname) throws ClassNotFoundException, SQLException {try {Class.forName("com.mysql.cj.jdbc.Driver");conn = DriverManager.getConnection(ur1, user, password);String sql1 = "select qqnumber from tb_qquser where qqnumber=? ;";prestmt = conn.prepareStatement(sql1);prestmt.setString(1, qqname);rs = prestmt.executeQuery();if (rs.next()) {return false;} else {return true;}} catch (Exception e) {e.printStackTrace();} finally {if (rs != null) {rs.close();}if (prestmt != null) {prestmt.close();}if (conn != null) {conn.close();}}// 原因是finally内不建议使用return,因为函数的执行过程是,在try中调用了// return后,才会执行finally中的代码,所以finally中只能放一些资源释放类的代码段,不能带returnreturn true;}String Project() throws SQLException {Connection conn = null;java.sql.Statement stmt = null;ResultSet rs = null;StringBuffer Pstring = new StringBuffer();String aaString = new String();try {Class.forName("com.mysql.cj.jdbc.Driver");conn = DriverManager.getConnection(ur1, user, password);stmt = conn.createStatement();String sql2 = "select qqnumber from tb_qquser;";rs = stmt.executeQuery(sql2);while (rs.next()) {String str = rs.getString("qqnumber");Pstring.append(str);Pstring.append("#337@");//Pstring.append(",");aaString = new String(Pstring);}} catch (Exception e) {e.printStackTrace();// TODO: handle exception} finally {if (rs != null) {rs.close();}if (stmt != null) {stmt.close();}if (conn != null) {conn.close();}}return aaString;}String FindName(String account) throws SQLException {Connection conn = null;java.sql.Statement stmt = null;ResultSet rs = null;String username = new String();try {Class.forName("com.mysql.cj.jdbc.Driver");conn = DriverManager.getConnection(ur1, user, password);String sql2 = "select username from tb_qquser where qqnumber=?";prestmt = conn.prepareStatement(sql2);prestmt.setString(1, account);rs = prestmt.executeQuery();while (rs.next()) {username = rs.getString("username");}} catch (Exception e) {e.printStackTrace();// TODO: handle exception} finally {if (rs != null) {rs.close();}if (stmt != null) {stmt.close();}if (conn != null) {conn.close();}}return username;}
}

作者留言:

首先此项目结构有点混乱,这是因为是一点点堆积起来的刚开始就没想整体都是实现一个功能后再加另外一个功能,想到一个什么东西就添加什么,方法函数在里面被东调用西调用的,虽然分了很多类写,但是导致调用参数不是很方便,因为第一次写这种东西没经验途中结构也改了两次,在后面时间不够,单纯为了实现功能无所不用极其,在有些地方特别紊乱,但是耐心看还是能看懂的。
收获感受:
第一次自己独立完成这种项目,首先自己还是有很多收获的,对知识的运用也有了一定的了解,其次自己找错解决问题的能力得到了提示,自己也学会去寻找错误看懂报错然后自己解决错误 。写一个项目对自己各方面都有提升,在自己掌握基本知识的情况下去写一个项目不但能巩固知识还能学会对知识整体运用。
对项目最后的解说:

  1. io流很重要运用起来。非常有必要学好io流。
    首先在此项目中用io流的是DataOutputStreamDataInputStream,这是因为它的特性:
    数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。
    数据输出流允许应用程序以与机器无关方式将Java基本数据类型写到底层输出流。
    在区分消息还是文件是运用了这个特性,定义两个长整型变量每次发送首先写入其中,一个代表文件一个代表服务器操作和消息。在服务器区分消息还是用户数据时是将特殊字符加入其中,例如加入
    !# 服务器接受到后意味着登录操作查询,客户端发过来就是 “账号!#密码” 调用字符串方法split()分割开然后进入数据库查询再操作。

  2. 其次项目中处理了很多细节,运用了很多弹窗,例如:登录时密码错误会有弹窗显示,注册时账号和密码都有长度要求,超过长度都会有弹窗警告,登录进去默认群聊也会有弹窗显示,私聊切换群聊也会有弹窗提示等等。

  3. 其次就是账号登入怎么联系到名称和socket的,这三者的联系在这里运用结合的知识,
    private static Map<String, UserInformation> clientMap = new ConcurrentHashMap<>();
    创建一个类 UserInformation封装名字socket,运用键值对应,使得账号能找到名字和socket, UserInformation对象使得名字也能找到socket
    4.在本项目中还有很多小细节处理有兴趣可以去发掘,聊天框的关闭就代表用户下线,服务器就会将此用户下线消息发给所有在线用户。

这篇关于【课程设计:Java课程设计局域网聊天外加文件传输功能】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/294444

相关文章

在Ubuntu上部署SpringBoot应用的操作步骤

《在Ubuntu上部署SpringBoot应用的操作步骤》随着云计算和容器化技术的普及,Linux服务器已成为部署Web应用程序的主流平台之一,Java作为一种跨平台的编程语言,具有广泛的应用场景,本... 目录一、部署准备二、安装 Java 环境1. 安装 JDK2. 验证 Java 安装三、安装 mys

Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单

《Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单》:本文主要介绍Springboot的ThreadPoolTaskScheduler线... 目录ThreadPoolTaskScheduler线程池实现15分钟不操作自动取消订单概要1,创建订单后

JAVA中整型数组、字符串数组、整型数和字符串 的创建与转换的方法

《JAVA中整型数组、字符串数组、整型数和字符串的创建与转换的方法》本文介绍了Java中字符串、字符数组和整型数组的创建方法,以及它们之间的转换方法,还详细讲解了字符串中的一些常用方法,如index... 目录一、字符串、字符数组和整型数组的创建1、字符串的创建方法1.1 通过引用字符数组来创建字符串1.2

SpringCloud集成AlloyDB的示例代码

《SpringCloud集成AlloyDB的示例代码》AlloyDB是GoogleCloud提供的一种高度可扩展、强性能的关系型数据库服务,它兼容PostgreSQL,并提供了更快的查询性能... 目录1.AlloyDBjavascript是什么?AlloyDB 的工作原理2.搭建测试环境3.代码工程1.

Java调用Python代码的几种方法小结

《Java调用Python代码的几种方法小结》Python语言有丰富的系统管理、数据处理、统计类软件包,因此从java应用中调用Python代码的需求很常见、实用,本文介绍几种方法从java调用Pyt... 目录引言Java core使用ProcessBuilder使用Java脚本引擎总结引言python

SpringBoot操作spark处理hdfs文件的操作方法

《SpringBoot操作spark处理hdfs文件的操作方法》本文介绍了如何使用SpringBoot操作Spark处理HDFS文件,包括导入依赖、配置Spark信息、编写Controller和Ser... 目录SpringBoot操作spark处理hdfs文件1、导入依赖2、配置spark信息3、cont

springboot整合 xxl-job及使用步骤

《springboot整合xxl-job及使用步骤》XXL-JOB是一个分布式任务调度平台,用于解决分布式系统中的任务调度和管理问题,文章详细介绍了XXL-JOB的架构,包括调度中心、执行器和Web... 目录一、xxl-job是什么二、使用步骤1. 下载并运行管理端代码2. 访问管理页面,确认是否启动成功

Java中的密码加密方式

《Java中的密码加密方式》文章介绍了Java中使用MD5算法对密码进行加密的方法,以及如何通过加盐和多重加密来提高密码的安全性,MD5是一种不可逆的哈希算法,适合用于存储密码,因为其输出的摘要长度固... 目录Java的密码加密方式密码加密一般的应用方式是总结Java的密码加密方式密码加密【这里采用的

Java中ArrayList的8种浅拷贝方式示例代码

《Java中ArrayList的8种浅拷贝方式示例代码》:本文主要介绍Java中ArrayList的8种浅拷贝方式的相关资料,讲解了Java中ArrayList的浅拷贝概念,并详细分享了八种实现浅... 目录引言什么是浅拷贝?ArrayList 浅拷贝的重要性方法一:使用构造函数方法二:使用 addAll(

解决mybatis-plus-boot-starter与mybatis-spring-boot-starter的错误问题

《解决mybatis-plus-boot-starter与mybatis-spring-boot-starter的错误问题》本文主要讲述了在使用MyBatis和MyBatis-Plus时遇到的绑定异常... 目录myBATis-plus-boot-starpythonter与mybatis-spring-b