文章目录
基于Socket的简易聊天工具
简易聊天工具,该聊天工具有服务器端和客户端两个方面,服务端通过套接字与服务器建立连接,服务器会接受客户端的请求,并对客户端做出响应。而客户端则会通过与服务器建立连接,来进行客户端之间P2P的信息交流。
简易聊天工具的功能:
1、登录、注册
2、公聊:多个客户端可以互相通话。每个客户端都可以收到其他客户端的发言。
3、私聊:客户端可以指定一个客户端进行对话,其他客户端收不到信息。
项目设计
1.界面设计
a.注册界面
b.登陆界面
c.聊天界面
2.类的设计
a.UserInfo:用户信息类型
b.DbUtil:工具类,用于存储用户信息到数据库中
c.LoginFrame:登陆窗口
d.RegisterFrame:注册窗口
e.ChatFrame:聊天窗口,客户端
f.ChatServer:服务器
程序编写
1.ChatServer
a. 先定义服务端的端口号:8088
b. 定义start方法,用于服务器端得主要逻辑的封装
c. 在start方法中,调用accept方法,响应客户端
d. 获取客户端得输入流,来接受信息
e. 获取输出流,向客户端发送数据
2.ChatFrame
a. 先实例化Socket对象,指定服务器的ip和port,用于发送请求连接
b. 定义start方法,封装客户端的运行逻辑
c. 获取客户端的输出流,向服务端发送数据
d. 获取客户端的输入流,接受服务端发送的数据
==注意:==如果想让客户端发信息的同时,又收信息,即并发。因此需要多线程技术。
窗体的设计
1.ChatFrame的窗体设计:借用Swing框架
a. 继承JFrame
b.添加 panel,showText,nameText,inputText属性
c.定义init方法,给属性初始化,同时设置窗体的属性信息,如:居中,不可拉伸,可见性等等
d.在start方法中,添加监听器,重写keyReleased方法,监听enter键。接受inputText的信息,调用sendMessage方法进行发送
e.定义sendMessage方法注意:修改了服务端的代码:返回的数据为客户端发送的数据
2.修改ChatServer
a.处理多个客户端
目标:可以运行多个客户端,然后自己能收到自己的信息
公聊的设计
需求:
一个客户端发送的数据,所有的客户端都要收到。
实现:
服务器端可以收到客户端A的数据message,同时服务器端有所有客户端的socket,也就是说服务器端可以有所有客户端的pw,即可以遍历所有的pw 发送数据message。
- 设计一个Map属性,用于存储所有的客户端名称与pw得映射关系
- 遍历pws,发送某个客户端的信息给所有的客户端。
私聊的设计
需求:
一个客户端发送的数据,只有@××××的那个客户端可以收到消息。
实现:
服务器端可以收到客户端A的数据message,然后读取数据message中是否包含@符号,是则在遍历所有的客户名称是否有该用户,有该用户则把消息单独发送给@××××用户
显示在线的姓名
1.将所有在线人命保存到一个线性表中。然后将线性表发送给每一个客户端。使之显示在nameText文本框中。
2.当有离线的,要及时删除此姓名。
代码实现
1、数据库连接的工具类
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/*连接数据库的工具类*/
public class DbUtil {
//使用的是mysql-connector-java-8.0.16.jar
public static final String URL = "jdbc:mysql://localhost:3308/wechat?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC";
public static final String DRIVER_NAME = "com.mysql.cj.jdbc.Driver";
public static final String USER_NAME = "root";
public static final String PASSWORD = "123456";
private static Connection connection;
static {
try {
Class.forName(DRIVER_NAME);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private static Connection getConn() {
if (connection == null) {
try {
connection = DriverManager.getConnection(URL, USER_NAME, PASSWORD);
} catch (SQLException e) {
e.printStackTrace();
}
}
return connection;
}
/**
* 添加用户
* @param user
*/
public static void save(UserInfo user) {
// 调用jdbc,连接数据库,存入信息
Connection conn = getConn();
String sql = "INSERT INTO admin(username,password) VALUES (?, ?)";
try {
PreparedStatement pst = conn.prepareStatement(sql);
pst.setString(1, user.getUsername());
pst.setString(2, user.getPassword());
pst.execute();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 登陆校验使用
*/
public static int query(UserInfo u) {
Connection conn = getConn();
List<Object> params = new ArrayList<Object>();
String sql = "SELECT * FROM admin WHERE 1=1";
if (u.getId() != null) {
sql += " and id=?";
params.add(u.getId());
}
if (u.getUsername() != null) {
sql += " and username=?";
params.add(u.getUsername());
}
if (u.getPassword() != null) {
sql += " and password=?";
params.add(u.getPassword());
}
try {
PreparedStatement pst = conn.prepareStatement(sql);
for (int i = 0; i < params.size(); i++) {
Object obj = params.get(i);
if(obj instanceof Long) {
pst.setLong(i + 1, (Long) obj);
} else if(obj instanceof String) {
pst.setString(i + 1, (String) obj);
} else if(obj instanceof String) {
pst.setString(i + 1, (String) obj);
}
}
ResultSet rs = pst.executeQuery();
if(rs.next()) {
return 1;
}
} catch (SQLException e) {
e.printStackTrace();
}
return 0;
}
}
2、用户实体类
public class UserInfo {
private Long id;
private String username;
private String password;
public UserInfo() {
}
public UserInfo(Long id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "UserInfo [id=" + id + ", username=" + username + ", password=" + password + "]";
}
}
3、服务端
package com.ccu;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
public class ChatServer {
/* 添加ServerSocket属性 */
private ServerSocket server;
/* 定义Map属性,用于存储用户和输出流对象的映射关系 */
private Map<String, PrintWriter> pws;
/* 定义一个线性表集合,存储所有的在线姓名 */
private LinkedList<String> onlinenames;
/* 构造器 */
public ChatServer() {
try {
// 初始化属性
/*
* ServerSocket(int port) port:当前程序的端口号
*/
server = new ServerSocket(8088);
pws = new HashMap<String, PrintWriter>();
onlinenames = new LinkedList<String>();
} catch (Exception e) {
e.printStackTrace();
}
}
// 保持用户名和输出流对象
public void savePW(String username, PrintWriter pw) {
if (username == null || username.length() == 0) {
return;
}
pws.put(username, pw);
}
// 溢出用户名和输出流对象
public void removePW(String username) {
if (username == null || username.length() == 0) {
return;
}
pws.remove(username);
}
/**
* 用于服务器启动时运行的主要逻辑
*/
public void start() {
try {
/*
* accept():是一个阻塞方法,用于响应客户端的请求,如果响应成功,会返回与之请求的客户端的socket对象。
* 此对象用于标记客户端,其提供了一套IO流用于数据的传输
*/
while (true) {
System.out.println("----------等待客户端连接-----------");
Socket socket = server.accept();
System.out.println("------一个客户端已经连接成功-------");
// 将刚连接上的客户端socket封装到一个线程中,是之与其他线程进并发.:注意:多个客户端就可以同时工作。
Runnable r = new GetClientInfoHandler(socket);
Thread t = new Thread(r);
t.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// 实例化一个服务器对象
ChatServer s = new ChatServer();
// 开启服务器
s.start();
}
/*
* 定义内部类,处理单个客户端发送的数据
*/
class GetClientInfoHandler implements Runnable {
private Socket socket;
public GetClientInfoHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
String username = null;
try {
// 处理客户端数据
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is, "utf-8"));
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(new OutputStreamWriter(os, "utf-8"), true);
// 获取客户端第一次发送来的数据,即用户名
username = br.readLine();
// 将username和pw作为映射关系存储
savePW(username, pw);
// 保存在线人数
onlinenames.add(username);
sendOnlinenames("在线人的姓名:" + onlinenames.toString());
String line = "";
while ((line = br.readLine()) != null) {
if (!privateChat(username, line)) {// 私聊模块自己完成
sendPublicMessage(username, line);
}
}
} catch (Exception e) {
// e.printStackTrace();
// 客户端下线,将用户名和输出流这个键值对移除掉
removePW(username);
System.out.println(username + "下线了");
offLine(username);
// 移除在线人名单
onlinenames.remove(username);
sendOnlinenames("在线人的姓名:" + onlinenames.toString());
}
}
}
public void sendPublicMessage(String username, String message) {
// 遍历Map,取出所有的输出流,来发送line这条信息
Set<String> usernames = pws.keySet();
for (String uname : usernames) {
pws.get(uname).println(username + ":" + message);
}
}
/**
* 私聊方法
*
*/
public boolean privateChat(String username, String line) {
if (!line.contains("@")) {
return false;
}
int end = line.indexOf("@");
//获取@对象
String name = line.substring(end + 1);
//判断@对象是否存在
if(!pws.containsKey(name)) {
return false;
}
//获取发言者
//System.out.println("发言者" + username);
//获取发言者说的内容
String content = line.substring(0, line.indexOf("@"));
pws.get(name).println(username + "私密我:" + content);
pws.get(username).println(line);
return true;
}
public void sendOnlinenames(String onlinenames) {
// 遍历Map,取出所有的输出流,来发送line这条信息
Set<String> usernames = pws.keySet();
for (String uname : usernames) {
pws.get(uname).println(onlinenames);
}
}
public void offLine(String username) {
// 遍历Map,取出所有的输出流,来发送line这条信息
Set<String> usernames = pws.keySet();
for (String uname : usernames) {
pws.get(uname).println(username + "离线了");
}
}
}
4、客户端
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
public class ChatFrame extends JFrame {
/* 定义Sockt属性 */
private Socket socket;
/* 添加面板属性,三个文本框属性 */
private JPanel panel;
private JTextArea showText;
private JTextArea nameText;
private JTextArea inputText;
// 登陆用户名
private String username;
/* 构造器 */
public ChatFrame(String username) {
try {
// 初始化属性 本机ip: localhost,127.0.0.1
/*
* Socket(String ip,int port) ip:服务器端的ip port:服务器端的程序的端口号
*/
socket = new Socket("localhost", 8088);
/* 调用init方法 */
init();
this.username = username;
this.setTitle(username);
} catch (Exception e) {
e.printStackTrace();
}
}
/* 封装一个方法init,用于窗体属性的初始化 */
public void init() {
// 设置窗体的大小:宽 800,高550
setSize(800, 550);
panel = new JPanel();
// 设置文本框: 第一个参数为:行数, 第二个参数为列数
showText = new JTextArea(25, 53);
nameText = new JTextArea(25, 15);
inputText = new JTextArea(5, 70);
// 窗体居中
this.setLocationRelativeTo(null);
// 设置窗体不可拉伸
this.setResizable(false);
// 关闭窗体,默认停止程序
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 设置窗体的布局,为边框布局
this.setLayout(new BorderLayout());
// 将面板设置到窗体上,放置在中间
this.add(panel, BorderLayout.CENTER);
// 设置面板的布局
panel.setLayout(new FlowLayout());
showText.setEditable(false);// 设置不可编辑
// 创建滚动条对象
JScrollPane scroll = new JScrollPane(showText);
// 设置垂直滚动条
scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
panel.add(scroll);// 注意,将滚动条对象填入面板中
nameText.setEditable(false);
scroll = new JScrollPane(nameText);
// 设置垂直滚动条
scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
panel.add(scroll);
panel.add(inputText);
// 设置窗体显示
this.setVisible(true);
}
/**
* 用于启动客户端程序,运行主要逻辑
*/
public void start() {
try {
// 发送用户名给服务器,让服务器记录姓名
sendMessage(username);
// 定义一个线程,来处理服务端发送来的数据
Runnable r = new GetServerInfoHandler();
Thread t = new Thread(r);
t.start();
// 添加键盘的监听器
KeyListener l = new KeyAdapter() {
// 重写 键盘释放方法
@Override
public void keyReleased(KeyEvent e) {
// 获取回车键,进行发送数据
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
// 获取输入文本框里的内容
String content = inputText.getText().trim();
// System.out.println("文本框的内容为:" + content);
// 发送信息
sendMessage(content);
// 清空文本框的内容
inputText.setText("");
}
}
};
// 注册监听器
inputText.addKeyListener(l);
inputText.requestFocus();// 获取焦点
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源,关闭流操作
try {
// socket.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
public static void main(String[] args) {
// 实例化一个客户端对象
ChatFrame c = new ChatFrame("jack" + (int) (Math.random() * Integer.MAX_VALUE));
// 启动客户端
c.start();
}
/**
* 定义一个内部类,实现Runnable接口,定义任务体为接受服务端发送来的数据
*/
class GetServerInfoHandler implements Runnable {
public void run() {
try {
/* 获取输入流,接受 服务端发送的数据 */
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is, "utf-8"));
String line = "";
while ((line = br.readLine()) != null) {
// 将收到的信息显示在showText上
if (line.contains("在线人的姓名")) { // "在线人的姓名:[,,,,,,,, ]"
// 获取所有的姓名
int startindex = line.indexOf("[") + 1;
int endIndex = line.indexOf("]");
String info = line.substring(startindex, endIndex);
String[] names = info.split(",");
nameText.setText(null);
for (String name : names) {
nameText.append(name + "\r\n");
}
} else {
showText.append(line + "\r\n");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
*
*/
public void sendMessage(String content) {
PrintWriter pw = null;
try {
OutputStream os = socket.getOutputStream();
pw = new PrintWriter(new OutputStreamWriter(os, "utf-8"), true);
pw.println(content);
} catch (Exception e) {
e.printStackTrace();
}
}
}
5、注册页面
import java.awt.Font;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
public class RegisterFrame extends JFrame {
private static JButton RegisterFrameButton;// 註冊按钮
private static JLabel RegisterFramePanel;// 注册的版面
private static JTextField userText;// 用户名
private static JPasswordField pwdText;// 密码
private static JLabel userLabel;
private static JLabel pwdLabel;
public RegisterFrame() {// 初始化登陆界面
init();
}
public void init() {
Font font = new Font("黑体", Font.PLAIN, 20);// 设置字体
this.setSize(800, 550);
// 给註冊界面添加背景图片
ImageIcon bgim = new ImageIcon(LoginFrame.class.getResource("login.png"));// 背景图案
bgim.setImage(
bgim.getImage().getScaledInstance(bgim.getIconWidth(), bgim.getIconHeight(), Image.SCALE_DEFAULT));
RegisterFramePanel = new JLabel();
RegisterFramePanel.setIcon(bgim);
userLabel = new JLabel("用户名");
userLabel.setBounds(200, 100, 60, 50);
userLabel.setFont(font);
pwdLabel = new JLabel("密码");
pwdLabel.setBounds(200, 200, 60, 50);
pwdLabel.setFont(font);
// 加入文本框
userText = new JTextField("");
userText.setBounds(350, 100, 250, 50);
userText.setFont(font);
pwdText = new JPasswordField("");// 密码输入框
pwdText.setBounds(350, 200, 250, 50);
pwdText.setFont(font);
RegisterFrameButton = new JButton("注册");
RegisterFrameButton.setBounds(300, 300, 200, 50);
RegisterFrameButton.setFont(font);
RegisterFramePanel.add(userText);
RegisterFramePanel.add(pwdText);
RegisterFramePanel.add(userLabel);
RegisterFramePanel.add(pwdLabel);
RegisterFramePanel.add(RegisterFrameButton);
this.add(RegisterFramePanel);
this.setTitle("注册");
this.setVisible(true);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
this.setResizable(false);// 设置窗口不可随意拉伸
}
public void start() {
ActionListener RegisterFrameButton_ls = new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
int result = 0;
while (true) {
// 获取注册界面的输入的用户名和密码
String username = userText.getText();
char[] password = pwdText.getPassword();
String str = String.valueOf(password); // 将char数组转化为string类型
// 验证用户名和密码
UserInfo user = new UserInfo();
user.setUsername(username);
result = DbUtil.query(user);
if (result == 1) {
JOptionPane.showMessageDialog(null, "对不起!该用户名已被注册!请您重新输入!", "提示", JOptionPane.ERROR_MESSAGE);
break;
} else {
user.setPassword(str);
DbUtil.save(user);
// 点击注册按钮,跳转到登录页面
JOptionPane.showMessageDialog(null, "恭喜您注册成功!", "提示", JOptionPane.INFORMATION_MESSAGE);
LoginFrame client = new LoginFrame();
client.start();// 为跳转的界面
dispose();// 销毁当前界面
break;
}
}
}
};
RegisterFrameButton.addActionListener(RegisterFrameButton_ls);
}
public static void main(String[] args) {
RegisterFrame register = new RegisterFrame();
register.start();
}
}
6、登录页面
import java.awt.Font;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
public class LoginFrame extends JFrame {
private static int count = 0;
private static JButton LoginFrameButton;// 登陆按钮
private static JButton RegisterFrameButton;// 退出按钮
private static JLabel LoginFramePanel;// 登录的版面
private static JTextField userText;// 用户名
private static JPasswordField pwdText;// 密码
private static JLabel userLabel;
private static JLabel pwdLabel;
public LoginFrame() {// 初始化登陆界面
init();
}
public void init() {
Font font = new Font("黑体", Font.PLAIN, 20);// 设置字体
this.setSize(800, 550);
// 给登陆界面添加背景图片
ImageIcon bgim = new ImageIcon(LoginFrame.class.getResource("login.png"));// 背景图案
bgim.setImage(
bgim.getImage().getScaledInstance(bgim.getIconWidth(), bgim.getIconHeight(), Image.SCALE_DEFAULT));
LoginFramePanel = new JLabel();
LoginFramePanel.setIcon(bgim);
userLabel = new JLabel("用户名");
userLabel.setBounds(200, 100, 60, 50);
userLabel.setFont(font);
pwdLabel = new JLabel("密码");
pwdLabel.setBounds(200, 200, 60, 50);
pwdLabel.setFont(font);
// 加入文本框
userText = new JTextField("");
userText.setBounds(350, 100, 250, 50);
userText.setFont(font);
pwdText = new JPasswordField("");// 密码输入框
pwdText.setBounds(350, 200, 250, 50);
pwdText.setFont(font);
LoginFrameButton = new JButton("登陆"); // 更改成LoginFrameButton
LoginFrameButton.setBounds(250, 300, 100, 50);
LoginFrameButton.setFont(font);
RegisterFrameButton = new JButton("注册");
RegisterFrameButton.setBounds(450, 300, 100, 50);
RegisterFrameButton.setFont(font);
LoginFramePanel.add(userText);
LoginFramePanel.add(pwdText);
LoginFramePanel.add(userLabel);
LoginFramePanel.add(pwdLabel);
LoginFramePanel.add(LoginFrameButton);
LoginFramePanel.add(RegisterFrameButton);
this.add(LoginFramePanel);
this.setTitle("登录");
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
this.setResizable(false);// 设置窗口不可随意拉伸
}
public void start() {
/**
* 处理点击事件 1.登陆按钮点击事件,判断账号密码是否正确,若正确,弹出监测信息界面 否则,无响应(暂时无响应)
* :后可在登陆界面添加一个logLabel提示用户是否用户密码正确 2.退出按钮,直接退出程序
*/
// 登陆点击事件
ActionListener LoginFrameButton_ls = new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
int result = 0;
while (true) {
// 获取登陆界面的输入的用户名和密码
String username = userText.getText();
char[] password = pwdText.getPassword();
String str = String.valueOf(password); // 将char数组转化为string类型
//验证用户名和密码
UserInfo user = new UserInfo();
user.setUsername(username);
user.setPassword(str);
result = DbUtil.query(user);
if (result != 1) {
JOptionPane.showMessageDialog(null, "对不起!您的输入有误!请重新输入", "提示", JOptionPane.ERROR_MESSAGE);
break;
} else {
// 设置
// 登陆成功,页面跳转
ChatFrame client = new ChatFrame(username);
client.start();// 为跳转的界面
dispose();// 销毁当前界面
break;
}
}
}
};
ActionListener RegisterFrameButton_ls = new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
// 点击注册按钮,跳转到注册页面
RegisterFrame client = new RegisterFrame();
client.start();// 为跳转的界面
dispose();// 销毁当前界面
}
};
LoginFrameButton.addActionListener(LoginFrameButton_ls);
RegisterFrameButton.addActionListener(RegisterFrameButton_ls);
}
public static void main(String[] args) {
LoginFrame login = new LoginFrame();
login.start();
}
}
7、数据库表结构
/*
Navicat Premium Data Transfer
Source Server : 3308
Source Server Type : MySQL
Source Server Version : 50622
Source Host : localhost:3308
Source Schema : wechat
Target Server Type : MySQL
Target Server Version : 50622
File Encoding : 65001
Date: 08/07/2019 18:36:51
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for admin
-- ----------------------------
DROP TABLE IF EXISTS `admin`;
CREATE TABLE `admin` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`password` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of admin
-- ----------------------------
INSERT INTO `admin` VALUES (1, 'mayun', 'my');
INSERT INTO `admin` VALUES (2, 'wangjianlin', 'wjl');
INSERT INTO `admin` VALUES (8, 'dongmingzhu', 'dmz');
INSERT INTO `admin` VALUES (9, 'mahuateng', 'mht');
SET FOREIGN_KEY_CHECKS = 1;