2020-06-07更新:
新增SysSearch类源码
2020-5-25更新:
GitHub地址: https://github.com/0x886c/System.git
2019-12-26 更新客户端签到成功后的UI显示,更新程序步骤完成后数据库的信息界面
宿舍一起完成了一个课程设计,发出来分享下,完成这个题目后,自己对线程的理解深入了许多,也更加了解了Java的封装性
效果图
数据库中的初始测试数据
Client文件夹
Serve文件夹
打开这四个exe文件后,我们输入测试数据
如果输入的数据和数据库中数据一致
如果输入信息与数据库中学生信息不一致
只有签到成功服务端UI才会显示
文件传输客户端
服务端在D:\FiletransferTest收到文件
学生完成签到,提交作业后的数据库信息
程序源码排布
此类实现客户端的UI显示,输入信息监听,客户端线程的启动
package Client;
import UI.UI1;
import UI.UI2;
import UI.UI4;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
public class LoginClient extends Thread{
static Socket socket = null;//静态socket对象可以在LoginClient整个类中使用
public static void main(String[] args){
new UI1();//客户端登陆界面显示
}
public void run(){//获取来自服务端的输入流
try {
String string = new String();
InputStream inputStream = socket.getInputStream();//获得输入流
int len = 0;
byte[] buf = new byte[1024];
//将字节流转化成字符串,获得服务端发送的信息
if ((len=inputStream.read(buf))!=-1){
string = new String(buf,0,len);
}
//System.out.println(string);
if (string.equals("已成功签到")){//服务端确定学生输入信息与数据库中内容一致
new UI4();//弹出签到成功界面
}else {
new UI2();//弹出信息输入错误界面
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static class LoginListener implements ActionListener{
TextField textFieldId,textFieldName,textFieldClass;
String Stuid,Stuname,Stuclass;
Frame loginframe = null;
//通过外部类的方法完成UI1中的参数传递
//一下定义的方法分别获得监听的Id,Name,Class,Frame
public void SetTextId(TextField textField){
textFieldId = textField;
}
public void SetTextName(TextField textField){
textFieldName = textField;
}
public void SetTextClass(TextField textField){
textFieldClass = textField;
}
public void SetTextFrame(Frame frame){
loginframe = frame ;
}
//监听界面输入并以字节流的形式将数据发送至服务端
@Override
public void actionPerformed(ActionEvent e) {
//获得字符串类型的数据
Stuid = textFieldId.getText();
Stuname = textFieldName.getText();
Stuclass = textFieldClass.getText();
//测试是否获得数据
System.out.println(Stuid+'\n'+Stuname+'\n'+Stuclass);
try {
//这里的host是我局域网中当作服务端的主机的静态IP,根据自己情况输入服务端IP
socket = new Socket("192.168.1.100",7777);
OutputStream outputStream = socket.getOutputStream();
//这里字节流中加入了本机IP以边签到时间的录入
outputStream.write((Stuid+" "+Stuname+" "+Stuclass+" "+socket.getInetAddress().getHostAddress()).getBytes());
} catch (UnknownHostException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
}
//监听时启动线程
LoginClient loginClient = new LoginClient();
loginClient.start();
}
}
}
此类实现服务端UI显示,学生成功登录信息监听,服务端线程的启动
package Serve;
import Datebase.SysInsertSignoutTime;
import Datebase.SysInsertTime;
import Datebase.SysSearch;
import Filetransfer.FiletransferClient;
import UI.ServeUi;
import UI.UI1;
import UI.UI2;
import UI.UI4;
import javax.swing.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class SysServe extends Thread{
static ServerSocket serverSocket;
//服务端会收到多个客户端的请求,所以这里用了socket类型集合
static List list = new ArrayList<>();
ServeUi serveUi = new ServeUi();//跨类传值
public static void main(String[] args) {
SysServe sysServe = new SysServe();
sysServe.run();
}
public void run(){
try {
System.out.println("正在等待用户连接");
//交互程序间端口要一致
serverSocket = new ServerSocket(7777);
while (true){
Socket socket = serverSocket.accept();//等待服务端对象连接
System.out.println("成功连接");
list.add(socket);
//启动线程
new ReadThreadFromClient(socket).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public class ReadThreadFromClient extends Thread{
InputStream inputStream = null;
public ReadThreadFromClient(Socket socket){
try {
//获取服务端的IP地址
String ipstring = socket.getInetAddress().getHostAddress();
inputStream = socket.getInputStream();
byte[] buf = new byte[1024];
int len = 0;
//获得字节流
if ((len=inputStream.read(buf))!=-1){
System.out.println("服务器监听:"+'\t'+new String(buf,0,len));
}
if (new SysSearch().main(buf)){//判断监听的信息是否与数据库中的数据一致
//插入签到时间
new SysInsertTime().main(ipstring,buf);
//将获得的学生签到信息传到服务端UI中的AreaText中
serveUi.AddAreaText(new String(buf,0,len)+"已签到");
//向客户端发送成功签到信息
SendMessClient("已成功签到",socket);
} else {
//向客户端发送输入信息有误
SendMessClient("Login端信息输入有误",socket);
new UI2();//登录信息输入错误提示页面弹出
}
} catch (IOException | SQLException e) {
e.printStackTrace();
}
}
}
public void SendMessClient(String message,Socket socket){//向其他客户发送数据
if(socket!=null&&socket.isConnected()){//确保客户端没有掉线
try {
OutputStream outputStream=socket.getOutputStream();//输入与输出均要以流的方式进行
outputStream.write(message.getBytes());//输出字节流
outputStream.flush();//刷新
}catch (IOException e){
e.printStackTrace();
}
}
}
}
文件传输UI显示,文件路径的监听,文件传输线程的启动
package Filetransfer;
import UI.UI3;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
public class FiletransferClient extends Thread{
static Socket socket = null;
public static void main(String[] args){
new UI3();
}
public static class SendFile implements ActionListener {
TextField textFileName;
Frame frame;
String filelocal = new String();
String string = new String();
DataOutputStream dataOutputStream = null;//从本机获得文件-文件输入流
FileInputStream fileInputStream = null;//将数据传送至客户端-数据输出流
public void SetFileLocal(TextField textField){
textFileName = textField;
}
public void SetFrame(Frame f){
frame = f;
}
@Override
public void actionPerformed(ActionEvent e) {
filelocal = textFileName.getText();
//System.out.println(filelocal);
try
{
socket = new Socket(InetAddress.getLocalHost(),8888);
string = filelocal.replaceAll("\\\\","\\\\\\\\");
System.out.println(string);
File file = new File(string);//本机文件路径**注意转义符号**
if(file.exists()){
//先读取传入数据的文件流再以数据流的形式发送
fileInputStream= new FileInputStream(file);
dataOutputStream = new DataOutputStream(socket.getOutputStream());
dataOutputStream.writeUTF(file.getName());
dataOutputStream.flush();
dataOutputStream.writeLong(file.length());
dataOutputStream.flush();
byte[] bytes = new byte[1024];
int length = 0;
long progress = 0;
while ((length = fileInputStream.read(bytes,0,bytes.length))!=-1){
dataOutputStream.write(bytes,0,length);
dataOutputStream.flush();
progress+=length;
System.out.println("| " + (100*progress/file.length()) + "% |");
}
}
} catch (IOException ex) {
ex.printStackTrace();
} finally {
if (fileInputStream!=null){
try {
fileInputStream.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
if (dataOutputStream!=null){
try {
dataOutputStream.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
FiletransferClient filetransferClient1 = new FiletransferClient();
filetransferClient1.start();
}
}
}
实现文件传输服务端的线程启动
PS:为什么没有UI?
我的想法是exe文件被打开后一直存在于后台中,可以一直接受客户端的文件
package Filetransfer;
import Datebase.SysInsertSignoutTime;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
//文件传输线程服务端
public class FiletransferServe extends Thread {
List list = new ArrayList<>();
ServerSocket serverSocket = null;
public FiletransferServe(){//服务端对象的创建和指定端口的设置
try {
serverSocket = new ServerSocket(8888);
} catch (IOException e){
e.printStackTrace();
}
}
public void run(){
super.run();
try {
System.out.println("用户正在连接");
while (true){
Socket socket=serverSocket.accept();//利用accept方法阻塞线程,线程会等待socket的输入流
System.out.println("成功连接");//服务器显示已上线,即线程成功运行
list.add(socket);//增加线程数量并记录
new ReadFile(socket).run();
}
}catch (IOException e){
e.printStackTrace();
}
}
public static class ReadFile extends Thread{
DataInputStream dataInputStream = null;
FileOutputStream fileOutputStream = null;
String ipstring = null;
public ReadFile(Socket socket){
try {
ipstring = socket.getInetAddress().getHostAddress();
dataInputStream = new DataInputStream(socket.getInputStream());
} catch (IOException e){
e.printStackTrace();
}
}
public void run(){
try {
String fileName = dataInputStream.readUTF();//获得数据输入流
File directory = new File("D:\\FiletransferTest");//文件保存路径
if(!directory.exists()) {
directory.mkdir();
}
//创建文件
File file = new File(directory.getAbsolutePath() + File.separatorChar +fileName);
fileOutputStream = new FileOutputStream(file);
byte[] bytes = new byte[1024];
int length = 0;
//字节流获得文件信息
while((length = dataInputStream.read(bytes, 0, bytes.length)) != -1) {
fileOutputStream.write(bytes, 0, length);//将收到的字节流写入文件
fileOutputStream.flush();
}
System.out.println("文件接收成功 [File Name:" + fileName + "]");
System.out.println("ipstring:"+ipstring);//检测获得的客户端IP地址
new SysInsertSignoutTime().main(ipstring);//根据IP地址更新签退时间
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭流
try {
if(fileOutputStream != null)
fileOutputStream.close();
if(dataInputStream != null)
dataInputStream.close();
} catch (Exception e) {}
}
}
}
public static void main(String[] args) {
try {
FiletransferServe filetransferServe =new FiletransferServe(); // 启动客户端
filetransferServe.run();
} catch (Exception e) {
e.printStackTrace();
}
}
}
完成服务端学生数据库签到时间的更新
package Datebase;
import java.sql.*;
import java.util.Date;
public class SysInsertTime {
public void main(String ipstring,byte[] byets) throws SQLException {
Connection con = null;//声明Connection对象
String url = "jdbc:mysql://localhost:3306/test";//URL指向要访问的数据库名test
String user = "root";//MySQL配置时的用户名
String password = "123456";//MySQL配置时的密码
String driver = "com.mysql.jdbc.Driver";
try {
Class.forName(driver);
con = DriverManager.getConnection(url,user,password);
//判断是否连接至数据库
if (!con.isClosed()){
System.out.println("已成功连接至本机数据库");
}
Statement statement = con.createStatement();//创建statement类对象,用来执行SQL语句
//获取当前时间
Date date=new Date();
Timestamp timeStamp = new Timestamp(date.getTime());
System.out.println(timeStamp);
//捕捉字节数组并转化为字符串组,有助于数据处理
String[] strings = new String(byets).split(" ");
//转换SQL语句
//String sql = "update studenttable set "+string+'='+timeStamp+" where id = "+strings[0]+' ';
//更新登入学生的签到时间
String sql = "update studenttable set signintime ='"+timeStamp+"' where id ="+strings[0]+"";
//执行SQL
statement.executeUpdate(sql);
//更新登入学生的IP
sql = "update studenttable set ip = '"+ipstring+"' where id = "+strings[0]+"";
statement.executeUpdate(sql);
} catch (SQLException | ClassNotFoundException e) {
e.printStackTrace();
} finally { System.out.println("数据库已完成签到时间的更新操作");
}
}
}
记录文件提交时间,将此时间戳作为签退时间
package Datebase;
import java.sql.*;
import java.util.Date;
public class SysInsertTime {
public void main(String ipstring,byte[] byets) throws SQLException {
Connection con = null;//声明Connection对象
String url = "jdbc:mysql://localhost:3306/test";//URL指向要访问的数据库名test
String user = "root";//MySQL配置时的用户名
String password = "123456";//MySQL配置时的密码
String driver = "com.mysql.jdbc.Driver";
try {
Class.forName(driver);
con = DriverManager.getConnection(url,user,password);
//判断是否连接至数据库
if (!con.isClosed()){
System.out.println("已成功连接至本机数据库");
}
Statement statement = con.createStatement();//创建statement类对象,用来执行SQL语句
//获取当前时间
Date date=new Date();
Timestamp timeStamp = new Timestamp(date.getTime());
System.out.println(timeStamp);
//捕捉字节数组并转化为字符串组,有助于数据处理
String[] strings = new String(byets).split(" ");
//转换SQL语句
//String sql = "update studenttable set "+string+'='+timeStamp+" where id = "+strings[0]+' ';
//更新登入学生的签到时间
String sql = "update studenttable set signintime ='"+timeStamp+"' where id ="+strings[0]+"";
//执行SQL
statement.executeUpdate(sql);
//更新登入学生的IP
sql = "update studenttable set ip = '"+ipstring+"' where id = "+strings[0]+"";
statement.executeUpdate(sql);
} catch (SQLException | ClassNotFoundException e) {
e.printStackTrace();
} finally { System.out.println("数据库已完成签到时间的更新操作");
}
}
}
判断用户输入的信息是否与数据库中信息一致
package Datebase;
import java.nio.charset.StandardCharsets;
import java.sql.*;
public class SysSearch {
public boolean main(byte[] bytes) {
//声明Connection对象
Connection con;
//驱动程序名
String driver = "com.mysql.jdbc.Driver";
//URL指向要访问的数据库名login
String url = "jdbc:mysql://localhost:3306/test";
//MySQL配置时的用户名
String user = "root";
//MySQL配置时的密码
String password = "123456";
try
{
//加载驱动程序
Class.forName(driver);
//连接MySQL数据库
con = DriverManager.getConnection(url,user,password);
//判断是否成功连接至数据库
if(!con.isClosed())
System.out.println("已成功连接至本机数据库");
//创建statement类对象,用来执行SQL语句
Statement statement = con.createStatement();
//截取学号为有效信息,作为查询依据
String s = new String(bytes);
//防止有人乱输学号姓名班级
if (s.length()<=13){
return false;
}
//用空格分开学号姓名班级
String[] strings = s.split(" ");
//测试语句
//System.out.println(strings[2]);
String sql;
//sql = "select * from studenttable";
sql = "select * from studenttable where id ='"+strings[0]+"'";
//ResultSet类,用来存放获取的结果集
ResultSet rs = statement.executeQuery(sql);
String Person_id = null;
String Person_name = null;
String Person_class = null;
while (rs.next()) {
//获取id这列数据
Person_id = rs.getString("id").trim();
//获取name这列数据
Person_name = rs.getString("name").trim();
//获取class这列数据
Person_class = rs.getString("class").trim();
}
//判断是否学生姓名与学号一一对应,返回方法的布尔型值
if (Person_name.equals(strings[1])){//若信息正确则在服务端弹出签到成功信息
System.out.println("学号:"+Person_id+'\n'
+"姓名:"+Person_name+'\n'
+"班级:"+Person_class+'\n'+"已签到");
return true ;
} else {
return false;
}
//待解决问题,此处class属性的比较返回值总是false,大概率是因为字符串转化为字节后再转化为字符串导致数据不一致
/*if (Person_class.equals(strings[2])) {
System.out.println("2");
}*/
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e){
e.printStackTrace();
}
finally {
System.out.println("数据库已完成查找操作");
}
return false;
}
}
服务端UI
package UI;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class ServeUi {
public ServeUi(){
dispaly();
}
//在文本域中增加监听内容
public void AddAreaText(String string){
textArea.append(string+'\n');
}
Frame frame = new Frame("服务端");
TextArea textArea = new TextArea("服务端监听"+'\n',5,4,TextArea.SCROLLBARS_VERTICAL_ONLY);
public void dispaly(){
frame.setSize(750,500);
frame.setLayout(null);
frame.setBackground(Color.lightGray);
frame.setVisible(true);
textArea.setSize(375,250);
textArea.setBackground(Color.lightGray);
textArea.setBounds(10,50,700,400);
frame.add(textArea);
textArea.setVisible(true);
frame.addWindowListener(new WinClose());
}
class WinClose extends WindowAdapter{
public void windowClosing(WindowEvent e){
System.exit(0);
}
}
}
客户端签到界面UI
package UI;
import Client.LoginClient;
import java.awt.*;
import java.awt.event.*;
public class UI1{
public UI1(){
dispaly();
}//学生登录界面
Button b = new Button();
Frame f = new Frame();
TextField ID,Name,Class = new TextField();
Label id,name,cl,waring = new Label();
LoginClient.LoginListener loginlistener = new LoginClient.LoginListener();//创建类进行参数监听
public void dispaly(){
f=new Frame("Java签到系统");
f.setSize(700,500);
f.setLayout(null);
f.setBackground(Color.lightGray);
f.setVisible(true);
b=new Button("登录");
Font fo = new Font("宋体",Font.BOLD,13);
b.setFont(fo);
ID=new TextField();
Name=new TextField();
Class=new TextField();
id=new Label("学号:");
waring=new Label("请按照:201XXXXXXXXXX,张三,计科XXXX 形式填写");
name=new Label("姓名:");
cl=new Label("班级:");
b.setBounds((f.getWidth()+300)/2,(f.getHeight()-100)/2,100,50);
b.addActionListener(loginlistener);
waring.setBounds((f.getWidth()-300)/2,(f.getHeight()+300)/2,2000,50);
ID.setBounds((f.getWidth()-200)/2,(f.getHeight()-200)/2,200,30);
id.setBounds((f.getWidth()-300)/2,(f.getHeight()-200-10)/2,200,50);
Name.setBounds((f.getWidth()-200)/2,(f.getHeight()-100)/2,200,30);
name.setBounds((f.getWidth()-300)/2,(f.getHeight()-120)/2,200,50);
Class.setBounds((f.getWidth()-200)/2,(f.getHeight()-0)/2,200,30);
cl.setBounds((f.getWidth()-300)/2,(f.getHeight()-0)/2,200,30);
f.add(ID);
f.add(Name);
f.add(Class);
f.add(id);
f.add(name);
f.add(cl);
f.add(b);
f.add(waring);
f.addWindowListener(new WinClose());
//跨类传值
loginlistener.SetTextId(ID);
loginlistener.SetTextName(Name);
loginlistener.SetTextClass(Class);
loginlistener.SetTextFrame(f);
}
}
class WinClose extends WindowAdapter{
public void windowClosing(WindowEvent e) {
System.exit(0);
}
}
签到失败弹出提示界面
package UI;
import java.awt.*;
import java.awt.event.*;
public class UI2 implements ActionListener { //登录信息不准确问题弹出界面
Button b;
Frame f;
Label waring;
public UI2(){
display();
}
public void display(){
f=new Frame("error");
f.setSize(750,400);
f.setLayout(null);
f.setBackground(Color.lightGray);
f.setVisible(true);
b=new Button("确认并返回");
Font fo = new Font("宋体",Font.BOLD,13);
b.setFont(fo);
waring=new Label("请输入正确的,13位学号,姓名,专业加班级号(不加汉字班)形式输入");
waring.setBounds((f.getWidth()-670)/2,(f.getHeight()-200)/2,2000,50);
Font fon = new Font("宋体",Font.BOLD,20);
waring.setFont(fon);
b.setBounds((f.getWidth()-100)/2,(f.getHeight()+100)/2,100,50);
b.addActionListener(this);
f.add(b);
f.add(waring);
f.addWindowListener(new WinClose());
}
public void actionPerformed(ActionEvent e) {
f.dispose();//界面关闭但是其他窗口保留
}
}
文件提交界面
package UI;
import Filetransfer.FiletransferClient;
import java.awt.*;
public class UI3{ //提交文件退出界面
Button b = new Button("提交并退出");
Frame f;
TextField Way;
Label way,waring;
public UI3(){
display();
}
//外部类实现跨类监听
FiletransferClient.SendFile sendFile = new FiletransferClient.SendFile();
public void display(){
f=new Frame("Java签到系统");
f.setSize(700,500);
f.setLayout(null);
f.setBackground(Color.lightGray);
f.setVisible(true);
b=new Button("提交并退出");
Font fo = new Font("宋体",Font.BOLD,13);
b.setFont(fo);
way=new Label("文件路径:");
Way=new TextField();
way.setBounds((f.getWidth()-500)/2,(f.getHeight()-200-10)/2,200,50);
Way.setBounds((f.getWidth()-380)/2,(f.getHeight()-200)/2,400,30);
waring=new Label("例如: D:\\\\Java\\\\workspace\\\\文件名");
waring.setBounds((f.getWidth()-300)/2,(f.getHeight()+300)/2,2000,50);
b.setBounds((f.getWidth()-100)/2,(f.getHeight()+100)/2,100,50);
f.add(b);
f.add(Way);
f.add(way);
f.add(waring);
f.addWindowListener(new WinClose());
//跨类传递参数
sendFile.SetFileLocal(Way);
sendFile.SetFrame(f);
b.addActionListener(sendFile);
}
}
签到成功界面
package UI;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
//签到成功界面
public class UI4{
Button button = new Button("确定");
Frame frame = new Frame("签到成功");
Label label = new Label("签到成功");
Font fo = new Font("宋体",Font.BOLD,26);
public UI4(){
display();
}
public void display(){
frame.setSize(600,400);
frame.setLayout(null);
frame.setVisible(true);
frame.add(button);
frame.add(label);
frame.setBackground(Color.lightGray);
button.setBounds(300,200,200,150);
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
label.setBounds(100,50,600,200);
label.setBackground(Color.lightGray);
label.setFont(fo);
}
}
将程序分开后打包成jar包
PS:注意服务端程序文件记得加上jdbc驱动的jar包
选择主类
在路径下可以测试一下jar包是否能够使用
如果可以的话,我们利用一个名字叫exe4j小软件将jar包封装成exe文件
在这里可以下载
https://www.ej-technologies.com/download/exe4j/files
具体使用过程就不多说了,网上有很多教程,这里只说一下我自己认为的要点
这里这样写的原因是
我们封装后的exe文件是需要jre环境才能运行的,如果在没有jdk的主机上我们的exe文件时无法运行的,如果我们把jre路径写成这样的话,exe文件和jre在同目录下就可以正常使用,我们就可以将两个文件打包成压缩包发送给其他文件以达到其他主机也能正常运行我们的小桌面应用程序