<Java>24 项目(满汉楼)

本文最后更新于:2022年6月19日 晚上

24 项目:满汉楼

  • 登录界面
    • 登录(账号、密码)
    • 退出
  • 菜单界面
    • 餐桌状态(显示编号、状态)
    • 预定餐桌(编号[核对、二次确认]、预定人名字、电话)
    • 菜单(名称、价格、类别、编号)
      • 全部、分类
    • 点餐(桌号[核对]、菜品[核对]、数量)
    • 账单(按照桌号分组。菜品、数量、金额、桌号、日期、状态)
    • 结账(桌号、方式、二次确认)
    • 退出系统
  • 数据库
    • 登录用户:login(id、name、password[md5])
    • 餐桌状态:table(id、order、seat、customer)
    • 顾客:customer(id、name、card_id、call)
    • 菜单:dishes(id、name、price、species、available)
    • 账单:bill(id、date、customer、num、table、clear、dishes)

代码:万民堂

说是满汉楼,其实是万民堂

没有优化过,所以健壮性不强。懒得做优化了,反正之前的项目做过了

1. 数据库

mysql://localhost:3306/wan_min

以下大部分是润色后的 show create table xxx 的返回语句

table(餐桌)

餐桌号 id(主键)、餐桌状态 order、座数 seat、订餐人 customer

CREATE TABLE `table` (
      `id` smallint(5) unsigned NOT NULL AUTO_INCREMENT ,
      `order` enum('已预定','用餐','空','不可用') COLLATE utf8_bin NOT NULL DEFAULT '空',
      `seat` smallint(5) unsigned NOT NULL DEFAULT '2',
      `customer` smallint(5) unsigned DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `customer` (`customer`),
      CONSTRAINT `table_ibfk_1` FOREIGN KEY (`customer`) REFERENCES `customer` (`id`)
    )
dishes(菜单)

餐品号 id(主键)、餐品名 name(唯一)、价格 price、分类 species、是否可用 available

CREATE TABLE `dishes` (
      `id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
      `name` char(8) COLLATE utf8_bin NOT NULL,
      `price` smallint(5) unsigned NOT NULL DEFAULT '0',
      `species` enum('主食','主菜','汤品','凉菜','甜点','饮料','') COLLATE utf8_bin NOT NULL DEFAULT '',
      `available` enum('y','n') COLLATE utf8_bin NOT NULL DEFAULT 'y',
      PRIMARY KEY (`id`),
      UNIQUE KEY `name` (`name`)
)
customer(客人信息)

顾客ID id(主键)、顾客名字 name、顾客证件号 card_id(可为空)、顾客电话 call

CREATE TABLE `customer` (
      `id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
      `name` varchar(12) COLLATE utf8_bin NOT NULL,
      `card_id` char(18) COLLATE utf8_bin DEFAULT NULL,
      `call` char(11) COLLATE utf8_bin NOT NULL,
      PRIMARY KEY (`id`)
)
login(登录验证)

系统登录ID id(主键)、系统登录名 name(唯一)、密码 password(md5 加密)

CREATE TABLE `login` (
      `id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
      `name` varchar(10) COLLATE utf8_bin NOT NULL,
      `password` char(32) COLLATE utf8_bin NOT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `name` (`name`)
)
bill(订单)

订单ID id(主键)、日期 date、顾客 customer(外键)、菜品ID id(外键)、菜品数量 num、桌号 table(外键)、结算状态 clear(枚举,y现金、alipay支付宝、wcpay微信、n未付)

CREATE TABLE `bill` (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
      `customer` smallint(5) unsigned NOT NULL,
      `num` smallint(5) unsigned NOT NULL DEFAULT '1',
      `table` smallint(5) unsigned NOT NULL,
      `clear` enum('y','n','alipay','wc_pay') COLLATE utf8_bin NOT NULL DEFAULT 'n',
      `dishes` smallint(5) unsigned NOT NULL,
      PRIMARY KEY (`id`),
      KEY `customer` (`customer`),
      KEY `table` (`table`),
      KEY `dishes` (`dishes`),
      CONSTRAINT `bill_ibfk_1` FOREIGN KEY (`customer`) REFERENCES `customer` (`id`),
      CONSTRAINT `bill_ibfk_2` FOREIGN KEY (`table`) REFERENCES `table` (`id`),
      CONSTRAINT `bill_ibfk_3` FOREIGN KEY (`dishes`) REFERENCES `dishes` (`id`)
)
detail_bill(view,查询订单信息)
CREATE VIEW `detail_bill` AS 
	select `customer`.`name` AS `name`,
       `customer`.`call` AS `call`,
       `bill`.`table` AS `table`,
       `dishes`.`name` AS `dishes`,
       `bill`.`num` AS `num`,
       `bill`.`date` AS `date`,
       `bill`.`clear` AS `clear` 
	from `bill` , `customer`, `dishes` 
	where (`bill`.`customer` = `customer`.`id`) 
		and (`bill`.`dishes` = `dishes`.`id`)

Java 代码

通用部分

以下各包,客户端和服务端各自持有一份,存放在相同目录下。

com.melody.wmt.common
Message.java

com.melody.wmt.common.Message.java

信息包。客户端、服务端的通讯依靠发送该包进行

package com.melody.wmt.common;

import java.io.Serializable;
import java.time.Instant;

/**
 * @author Melody
 * @version 1.0
 */
public class Message implements Serializable {
    private String sender;		//发送者IP
    private String receiver;	//接收者IP
    private String word;		//信息标识
    private Object object;		//信息数据
    private Instant timeStamp;	//信息时间戳

    public Message(String sender, String receiver, String word, Object object) {
        this.sender = sender;
        this.receiver = receiver;
        this.word = word;
        this.object = object;
        timeStamp = Instant.now();
    }

    public String getSender() {
        return sender;
    }

    public void setSender(String sender) {
        this.sender = sender;
    }

    public String getReceiver() {
        return receiver;
    }

    public void setReceiver(String receiver) {
        this.receiver = receiver;
    }

    public String getWord() {
        return word;
    }

    public void setWord(String word) {
        this.word = word;
    }

    public Object getObject() {
        return object;
    }

    public void setObject(Object object) {
        this.object = object;
    }

    public Instant getTimeStamp() {
        return timeStamp;
    }

    public void setTimeStamp() {
        this.timeStamp = Instant.now();
    }
}
MessageType.java

com.melody.wmt.common.MessageType.java

信息标识包。通过该包来分析信息包的种类。该包没有方法。

package com.melody.wmt.common;

/**
 * @author Melody
 * @version 1.0
 */
public interface MessageType {
    /*以下是服务端发送*/
    String REJECT = "0";					//拒绝请求
    String ACCEPT = "A";					//通过请求
    String LOGIN_USER_NOT_EXIST = "LUNE";	//登录失败:用户不存在
    String LOGIN_WRONG_PW = "LWP";			//登陆失败:密码错误
    String WRONG_REQUEST = "WR";			//请求无效
    String TABLE_NOT_EXIST = "TNE";			//餐桌不存在
    String TABLE_OCCUPIED = "TOP";			//餐桌被占用(未点餐)
    String TABLE_DINNING = "TOD";			//餐桌被占用(已点餐)
    String TABLE_NOT_AVAILABLE = "TNA";		//餐桌不可用
    String TABLE_AVAILABLE = "TIA";			//餐桌为空
    String NOT_FOUND = "NF";				//没有找到目标

    /*以下是客户端发送*/
    String LOGIN = "LGI";					//请求登录
    String SHOW_TABLES = "RST";				//请求返回餐桌列表
    String CHECK_TABLE = "RCT";				//请求查询餐桌状态
    String ORDER_TABLE = "ROT";				//请求预定餐桌
    String SHOW_DISHES = "RSD";				//请求返回菜单列表
    String CHECK_DISHES = "RCD";			//请求查询菜单状态
    String ORDER_DISHES = "ROD";			//请求订餐
    String SHOW_ALL_BILLS = "RAB";			//请求返回账单列表
    String CHECK_BILLS = "RCB";				//请求查询账单状态
    String FINISH_BILLS = "RFB";			//请求完成账单
    String LOGOUT = "LGO";					//请求登出
}
com.melody.wmt.sql
LoginData.java

com.melody.wmt.sql.LoginData.java

登录信息包。该包对应表 login 的记录。

package com.melody.wmt.sql;

import java.io.Serializable;

/**
 * @author Melody
 * @version 1.0
 */
public class LoginData implements Serializable {
    private int id;
    private String name;
    private String password;

    public LoginData(){}

    public LoginData(int id, String name, String password) {
        this.id = id;
        this.name = name;
        this.password = password;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
TableData.java

com.melody.wmt.sql.TableData.java

餐桌信息包。该包对应表 table 的记录。

package com.melody.wmt.sql;

import java.io.Serializable;

/**
 * @author Melody
 * @version 1.0
 */
public class TableData implements Serializable {
    private int id;
    private String order;
    private int customer;
    private int seat;

    public TableData() {
    }

    public TableData(int id, String order, int customer, int seat) {
        this.id = id;
        this.order = order;
        this.customer = customer;
        this.seat = seat;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getOrder() {
        return order;
    }

    public void setOrder(String order) {
        this.order = order;
    }

    public int getCustomer() {
        return customer;
    }

    public void setCustomer(int customer) {
        this.customer = customer;
    }

    public int getSeat() {
        return seat;
    }

    public void setSeat(int seat) {
        this.seat = seat;
    }

    @Override
    public String toString() {
        return id + "\t" + seat + "\t" + order + "\t" + (customer == 0 ? "-" : customer);
    }
}

服务端部分

com.melody.wmt.library

在目录 com.melody.wmt.library 下,配置以下 jar 包:

  • druid-1.2.8.jar:德鲁伊连接池

  • mysql-connector-java-8.0.27.jar:MySQL 数据库

  • commons-dbutils-1.7.jar:Apache-DBUtils

com.melody.wmt.server
Server.java

服务端入口

package com.melody.wmt.server;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * @author Melody
 * @version 1.0
 */
public class Server {
    public static final String serverIP;
	
    /* 多此一举地初始化服务端 IP 地址。其实直接写正确地址也行的 */
    static {
        String tempIP = null;
        try {
            tempIP = InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            System.out.println(e);
            tempIP = "192.168.3.16";
        } finally {
            serverIP = tempIP;
        }
    }
	
    /* 主方法
    持续监听端口,每当有接入时就丢给新的 RunningServer 线程 */
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(9000);
        while (true) {
            Socket accept = serverSocket.accept();
            new Thread(new RunningServer(accept)).start();
        }
    }
}
RunningServer.java

服务端进程主体。由 Server.java 创建。

该进程能实现:用户登录验证、识别客户端请求

package com.melody.wmt.server;

import com.melody.wmt.common.Message;
import com.melody.wmt.common.MessageType;
import com.melody.wmt.sql.LoginData;

import java.io.*;
import java.net.Socket;
import java.time.LocalDateTime;

/**
 * @author Melody
 * @version 1.0
 */
public class RunningServer implements Runnable {
    private Socket socket;
    private ObjectInputStream ois;		//通信的输入流
    private ObjectOutputStream oos;		//通信的输出流
    private String targetIP;			//通信对象的 IP
    private LoginData loginData;		//通信对象声明的登录对象的信息。
    							//这个信息来自数据库,不一定和通信对象声明的信息匹配
    private boolean running = true;		//线程运行中。false 的场合线程停止
	
    /* 完成一些初始化 */
    public RunningServer(Socket socket) {
        this.socket = socket;
        try {
            ois = new ObjectInputStream(socket.getInputStream());
            oos = new ObjectOutputStream(socket.getOutputStream());
        } catch (IOException e) {
            System.out.println(e);
            closeAll();
        }
        targetIP = socket.getLocalAddress().getHostAddress();
    }

    @Override
    public void run() {
        try {
            /* 接收通信对象的登录请求 */
            Message message = ServerUtils.receiveMessage(ois);
            /* 获得通信对象声明的登录对象信息。
            这里返回 null 的场合,登录失败 */
            loginData = ServerUtils.checkLogin(message);
            System.out.println(targetIP + " 请求登录 <" + message.getObject()+">");
            /* 登录失败的场合,发送拒绝信息,并关闭所有流 */
            if (loginData == null) {
                ServerUtils.sendRejection(targetIP, oos);
                closeAll();
                return;
            } else {
            /* 登录成功,发送登录对象信息 */
                ServerUtils.sendMessage(new Message(Server.serverIP, targetIP, MessageType.ACCEPT, loginData), oos);
            }
            System.out.println(targetIP + " 登录成功 " + loginData.getName() + LocalDateTime.now());
            
            /* 直到这里,登录成功,正式开始监听客户端请求 */
            while (running) {
                listeningRequest();
            }
        /* 如果发生任何连接异常,我们中断连接,并关闭所有流 */
        } catch (Exception e) {
            System.out.println(targetIP + " 连接中断 " + loginData.getName() + LocalDateTime.now());
            closeAll();
        }
    }
	
    /* 这个方法是关闭所有流的方法。在关闭连接时会被调用 */
    private void closeAll() {
        try {
            if (ois != null) {
                ois.close();
            }
            if (oos != null) {
                oos.close();
            }
            if (socket != null) {
                socket.close();
            }
        } catch (IOException e) {
            System.out.println(e);
        }
    }
	
    /* 这个方法是监听客户端请求的方法。根据不同请求,做出不同反应 */
    private void listeningRequest() {
        /* 监听请求 */
        Message message = ServerUtils.receiveMessage(ois);
        /* 检查收到的数据包的信息标识 */
        String request = message.getWord();
        /* 创建将要发送的数据包。此时,该包还是空的 */
        Message toSend = new Message(Server.serverIP, targetIP, null, null);
        /* 检查一下接收的数据包的用户信息。有任何问题的话我们就发送请求错误包 */
        /* ……在这里解释一下:客户发送的数据包里,主要包含以下信息:
        1. getWord() 返回数据包标识。客户端的任何请求会对应不同的标识
        2. getObject() 一定包含一条 String 语句。该语句被转义字符 \t 分为几个部分。
        	第一部分一定是用户名、第二部分一定是用户密码。
        	根据请求需要,也会有更多其他部分 */
        try {
            if (!ServerUtils.checkUser((String) message.getObject(), loginData)) {
                throw new RuntimeException();
            }
        } catch (Exception e) {
            System.out.println(e);
            toSend.setWord(MessageType.WRONG_REQUEST);
            ServerUtils.sendMessage(toSend, oos);
            return;
        }
        /* 下面,根据不同信息包标识,我们做出不同反应 */
        /* 接收到返回餐桌列表的请求 */
        if (request.equals(MessageType.SHOW_TABLES)) {
            ServerUtils.showTables(toSend, oos);
        /* 接收到检查餐桌的请求 */
        } else if (request.equals(MessageType.CHECK_TABLE)) {
            ServerUtils.checkTable(message, toSend, oos);
        /* 接收到预定餐桌的请求 */
        } else if (request.equals(MessageType.ORDER_TABLE)) {
            ServerUtils.orderTable(message, toSend, oos);
        /* 接收到返回菜单列表的请求 */
        } else if (request.equals(MessageType.SHOW_DISHES)) {
            ServerUtils.showDishes(toSend, oos);
        /* 接收到检查菜单的请求 */
        } else if (request.equals(MessageType.CHECK_DISHES)) {
            ServerUtils.checkDishes(message, toSend, oos);
        /* 接收到点餐的请求 */
        } else if (request.equals(MessageType.ORDER_DISHES)) {
            ServerUtils.orderDishes(message, toSend, oos);
        /* 接收到返回账单列表的请求 */
        } else if (request.equals(MessageType.SHOW_ALL_BILLS)) {
            ServerUtils.showBills(toSend, oos);
        /* 接收到检查账单的请求 */
        } else if (request.equals(MessageType.CHECK_BILLS)) {
            ServerUtils.checkBills(message, toSend, oos);
        /* 接收到结账的请求 */
        } else if (request.equals(MessageType.FINISH_BILLS)) {
            ServerUtils.finishBills(message, toSend, oos);
        /* 接收到登出的请求 */
        } else if (request.equals(MessageType.LOGOUT)) {
            System.out.println(targetIP + " 登出 " + loginData.getName() + LocalDateTime.now());
            running = false;
            closeAll();
        }
    }
}
ServerUtils.java

服务端进程方法包。由 RunningServer.java 调用

该包实现:监听客户端数据包、发送数据包、处理(各种)客户端请求

package com.melody.wmt.server;

import com.melody.wmt.common.Message;
import com.melody.wmt.common.MessageType;
import com.melody.wmt.sql.*;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.List;

/**
 * @author Melody
 * @version 1.0
 */
public class ServerUtils {
    /* 监听客户端发送的数据包 */
    public static Message receiveMessage(ObjectInputStream ois) {
        try {
            Message message = (Message) ois.readObject();
            return message;
        } catch (Exception e) {
            System.out.println(e);
            return null;
        }
    }
	
    /* 发送登录失败回执。这个方法其实可以优化掉 */
    public static boolean sendRejection(String target, ObjectOutputStream oos) {
        Message message = new Message(Server.serverIP, target, MessageType.REJECT, null);
        try {
            oos.writeObject(message);
            oos.flush();
            return true;
        } catch (IOException e) {
            System.out.println(e);
            return false;
        }
    }
	
    /* 检查用户信息。实际上就是检查用户名 */
    public static boolean checkUser(String user, LoginData loginData) {
        if (user == null) {
            return false;
        }
        String[] toCheck = user.split("\t");
        if (toCheck[0].equals(loginData.getName())) {
            return new LoginDAO().checkLogin(toCheck[0], toCheck[1]) != null;
        } else {
            return false;
        }
    }
	
    /* 检查登录信息。登录成功则返回从数据库得到的登录信息包,否则返回 null */
    public static LoginData checkLogin(Message message) {
        if (message == null) {
            return null;
        }
        String word = message.getWord();
        if (word == null || !word.equals(MessageType.LOGIN)) {
            return null;
        }
        try {
            word = (String) message.getObject();
        } catch (Exception e) {
            System.out.println(e);
            return null;
        }
        String[] data = word.split("\t");
        if (data.length != 2) {
            return null;
        }
        return new LoginDAO().checkLogin(data[0], data[1]);
    }
	
    /* 发送数据包给客户端。很多方法会调用这个方法。这个方法也会刷新数据包时间戳 */
    public static void sendMessage(Message toSend, ObjectOutputStream oos) {
        try {
            Thread.currentThread().sleep(300);	//人为加入 0.3 秒发送延迟
            toSend.setTimeStamp();				//刷新数据包时间戳
            oos.writeObject(toSend);
            oos.flush();
        } catch (Exception e) {
            System.out.println(e);
        }
    }
	
    /* 返回餐桌列表 */
    public static void showTables(Message toSend, ObjectOutputStream oos) {
        /* 调用方法得到餐桌列表 */
        List<TableData> tableData = new TableDAO().queryAll();
        /* 将餐桌列表转化成字符串。发送时只发送这个字符串 */
        StringBuilder sb = new StringBuilder();
        for (TableData data : tableData) {
            sb.append(data);
            sb.append("\n");
        }
        toSend.setObject(sb.toString());
        /* 设置数据包标识为请求通过 */
        toSend.setWord(MessageType.ACCEPT);
        /* 发送数据包 */
        sendMessage(toSend, oos);
    }
	
    /* 检查餐桌
    ……这是因为:用户点餐、预定餐桌等场合,需要验证输入的餐桌信息 */
    public static void checkTable(Message receive, Message toSend, ObjectOutputStream oos) {
        try {
        	/* 检查用户请求的餐桌号 */
            int table = Integer.parseInt(((String) receive.getObject()).split("\t")[2]);
            TableData tableData = new TableDAO().queryTable(table);
            /* 下面是根据返回结果,为数据包添加标识语句,并装入查到的餐桌数据 */
            if (tableData == null) {
                toSend.setWord(MessageType.TABLE_NOT_EXIST);
            } else if (tableData.getOrder().equals("不可用")) {
                toSend.setObject(tableData);
                toSend.setWord(MessageType.TABLE_NOT_AVAILABLE);
            } else if (tableData.getOrder().equals("已预定")) {
                toSend.setObject(tableData);
                toSend.setWord(MessageType.TABLE_OCCUPIED);
            } else if (tableData.getOrder().equals("用餐")) {
                toSend.setObject(tableData);
                toSend.setWord(MessageType.TABLE_DINNING);
            } else if (tableData.getOrder().equals("空")) {
                toSend.setObject(tableData);
                toSend.setWord(MessageType.TABLE_AVAILABLE);
            } else {
                toSend.setWord(MessageType.REJECT);
            }
        /* 发生任何异常的场合,设置拒绝请求的标识语句 */
        } catch (Exception e) {
            toSend.setWord(MessageType.REJECT);
        /* 最终一定发送数据包 */
        } finally {
            sendMessage(toSend, oos);
        }
    }
	
    /* 预定餐桌 */
    public static void orderTable(Message receive, Message toSend, ObjectOutputStream oos) {
        try {
            /* 前面说过。接收数据包含有一条被 \t 分隔为不同部分的语句。
            这里:第三部分代表餐桌号,第四部分代表顾客名,第五部分代表顾客电话*/
            String[] mess = ((String) receive.getObject()).split("\t");
            int table = Integer.parseInt(mess[2]);
            String customerName = mess[3];
            String call = mess[4];
            CustomerDAO customerDAO = new CustomerDAO();
            /* 根据接收的顾客信息(姓名、电话)对数据库进行查询,看看有没有这个人
            这里,customer > 0 的场合代表有这个人 */
            int customer = customerDAO.searchCustomer(customerName, call);
            /* 没有这个信息的场合,创建这个顾客信息 */
            if (customer < 0) {
                customerDAO.createCustomer(customerName, call);
                customer = customerDAO.searchCustomer(customerName, call);
            }
            /* 通常到这里不会出现查无此人的情况。如果出现,说明创建信息失败 */
            if (customer < 0) {
                throw new RuntimeException();
            }
            /* 到这里,我们进行预定餐桌操作。返回 true 表示成功 */
            if (new TableDAO().orderTable(table, customer)) {
                toSend.setWord(MessageType.ACCEPT);
            } else {
                toSend.setWord(MessageType.REJECT);
            }
        /* 出现任何问题,我们设置请求拒绝标识符 */
        } catch (Exception e) {
            toSend.setWord(MessageType.REJECT);
        /* 最终一定发送数据包 */
        } finally {
            sendMessage(toSend, oos);
        }
    }
	
    /* 返回菜单列表 */
    public static void showDishes(Message toSend, ObjectOutputStream oos) {
        try {
            /* 首先获取菜单列表 */
            List<DishesData> dishes = new DishesDAO().getDishes();
            /* 接下来,把菜单列表转化为字符串 */
            StringBuilder staple = new StringBuilder();
            StringBuilder mainDishes = new StringBuilder();
            StringBuilder coldDishes = new StringBuilder();
            StringBuilder beverage = new StringBuilder();
            StringBuilder soup = new StringBuilder();
            StringBuilder sweetmeats = new StringBuilder();
            for (DishesData d : dishes) {
                if (d.getAvailable().equals("n")) {
                    continue;
                }
                String spec = d.getSpecies();
                if (spec.equals("主食")) {
                    staple.append(d);
                    staple.append("\n");
                } else if (spec.equals("甜点")) {
                    sweetmeats.append(d);
                    sweetmeats.append("\n");
                } else if (spec.equals("汤品")) {
                    soup.append(d);
                    soup.append("\n");
                } else if (spec.equals("凉菜")) {
                    coldDishes.append(d);
                    coldDishes.append("\n");
                } else if (spec.equals("饮料")) {
                    beverage.append(d);
                    beverage.append("\n");
                } else if (spec.equals("主菜")) {
                    mainDishes.append(d);
                    mainDishes.append("\n");
                }
            }
            /* 合并字符串,以 ### 间隔不同种类 */
            String allDishes = String.join("###", staple, mainDishes, coldDishes, soup, sweetmeats, beverage);
            /* 装填数据包 */
            toSend.setWord(MessageType.ACCEPT);
            toSend.setObject(allDishes);
        /* 出了问题就设置拒绝请求标识符 */
        } catch (Exception e) {
            toSend.setWord(MessageType.REJECT);
        /* 最终一定发送数据包 */
        } finally {
            sendMessage(toSend, oos);
        }
    }
	
    /* 检查菜单。和检查餐桌是一个道理 */
    public static void checkDishes(Message receive, Message toSend, ObjectOutputStream oos) {
        try {
            /* 前面说过。接收数据包含有一条被 \t 分隔为不同部分的语句。
            这里:第三部分代表查询菜品的 ID */
            String[] mess = ((String) receive.getObject()).split("\t");
            int id = Integer.parseInt(mess[2]);
            Object o = new DishesDAO().checkDishes(id);
            if (o == null) {
                toSend.setWord(MessageType.NOT_FOUND);
            } else if (!(o instanceof DishesData)) {
                throw new RuntimeException();
            } else {
                toSend.setWord(MessageType.ACCEPT);
                toSend.setObject(o.toString());
            }
        /* 出现任何问题就设置请求拒绝标识符 */
        } catch (RuntimeException e) {
            toSend.setWord(MessageType.REJECT);
        /* 最终一定发送数据包 */
        } finally {
            sendMessage(toSend, oos);
        }
    }
	
    /* 点餐 */
    public static void orderDishes(Message receive, Message toSend, ObjectOutputStream oos) {
        try {
            /* 前面说过……反正前面说过这个 String 的事!
            这里:第三部分是菜品 ID,第四部分是数量,
            	第五部分是桌号,第六部分是顾客号 */
            String[] mess = ((String) receive.getObject()).split("\t");
            int dishes = Integer.parseInt(mess[2]);
            /* 检查一下菜品是不是正确 */
            /* 这里有一处疏忽。应该一并检查是否菜品可用(available) */
            Object o = new DishesDAO().checkDishes(dishes);
            if (!(o instanceof DishesData)) {
                throw new RuntimeException();
            }
            int num = Integer.parseInt(mess[3]);
            int table = Integer.parseInt(mess[4]);
            int customer = Integer.parseInt(mess[5]);
            /* 尝试点餐 */
            if (new BillDAO().order(dishes, num, table, customer)) {
                new TableDAO().updateTable(table, 2);
                toSend.setWord(MessageType.ACCEPT);
            } else {
                toSend.setWord(MessageType.REJECT);
            }
        /* 出现任何问题就设置请求拒绝标识符 */
        } catch (RuntimeException e) {
            toSend.setWord(MessageType.REJECT);
        /* 最终一定发送数据包 */
        } finally {
            sendMessage(toSend, oos);
        }
    }
	
    /* 返回账单列表 */
    public static void showBills(Message toSend, ObjectOutputStream oos) {
        try {
            /* 和之前的方法一样。这里我们发送的是拼接起来的字符串 */
            List<DetailBillData> billData = new BillDAO().queryBills();
            StringBuilder finished = new StringBuilder();
            StringBuilder unfinished = new StringBuilder();
            for (DetailBillData d : billData) {
                if (d.getClear().equals("n")) {
                    unfinished.append(d);
                    unfinished.append("\n");
                } else {
                    finished.append(d);
                    finished.append("\n");
                }
            }
            toSend.setWord(MessageType.ACCEPT);
            toSend.setObject(String.join("###", finished, unfinished));
        /* 出现任何问题就设置请求拒绝标识符 */
        } catch (Exception e) {
            toSend.setWord(MessageType.REJECT);
        /* 最终一定发送数据包 */
        } finally {
            sendMessage(toSend, oos);
        }
    }
	
    /* 检查订单 */
    public static void checkBills(Message receive, Message toSend, ObjectOutputStream oos) {
        try {
            /* ……这里:第三部分是一个标识符。T 表示按桌号查找
            		第四部分是序号。按桌号查找的话,就代表桌号
            		第五部分是一个布尔值句子。true 表示查询已付订单,
            			false 是未付订单。为空表示查询所有订单 */
            String[] mess = ((String) receive.getObject()).split("\t");
            List<DetailBillData> billData = null;
            StringBuilder sb = new StringBuilder();
            if (mess[2].equals("t") || mess[2].equals("T")) {
                Boolean clear;
                if (mess.length < 5) {
                    clear = null;
                } else if (mess[4].equals("true")) {
                    clear = true;
                } else if (mess[4].equals("false")) {
                    clear = false;
                } else {
                    clear = null;
                }
                billData = new BillDAO().queryBillsByTable(Integer.parseInt(mess[3]), clear);
            }
            /* 同样的,还是返回字符串 */
            for (DetailBillData data : billData) {
                sb.append(billData);
                sb.append("\n");
            }
            toSend.setObject(sb.toString());
            toSend.setWord(MessageType.ACCEPT);
        /* 出现任何问题就设置请求拒绝标识符 */
        } catch (Exception e) {
            toSend.setWord(MessageType.REJECT);
        /* 最终一定发送数据包 */
        } finally {
            sendMessage(toSend, oos);
        }
    }
	
    /* 结算订单 */
    public static void finishBills(Message receive, Message toSend, ObjectOutputStream oos) {
        try {
            /* 第三位是桌号,第四位是结算方法 */ 
            String[] mess = ((String) receive.getObject()).split("\t");
            int table = Integer.parseInt(mess[2]);
            int way = Integer.parseInt(mess[3]);
            /* 不光更新订单信息,也要把指定餐桌重置为空 */
            boolean finish = new BillDAO().finishBill(table, way) > 0 && new TableDAO().clearTable(table);
            toSend.setWord(finish ? MessageType.ACCEPT : MessageType.REJECT);
        /* 出现任何问题就设置请求拒绝标识符 */
        } catch (Exception e) {
            toSend.setWord(MessageType.REJECT);
        /* 最终一定发送数据包 */
        } finally {
            sendMessage(toSend, oos);
        }
    }
}
com.melody.wmt.sql
WanMinSQL.properties

数据库配置文件

里面的密码填自己的。另外,其实上一篇笔记里故意漏的密码是假的。

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/wan_min
username=root
password=******
initialSize=10
minIdle=5
maxActive=50
maxWait=5000
BasicDAO.java

——详见 [23.9 BasicDAO]

代码和上面说的是一样的……大概?

package com.melody.wmt.sql;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

/**
 * @author Melody
 * @version 1.0
 */
public class BasicDAO<T> {
    private QueryRunner queryRunner = new QueryRunner();

    public int update(String sql, Object... parameters){
        Connection connection = null;
        try {
            connection = JDBCUtil.connect();
            return queryRunner.update(connection, sql, parameters);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            JDBCUtil.close(connection, null, null);
        }
    }

    public List<T> queryMulti(Class<T> tClass, String sql, Object... parameters) {
        Connection connection = null;
        try {
            connection = JDBCUtil.connect();
            return queryRunner.query(connection, sql, new BeanListHandler<>(tClass), parameters);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            JDBCUtil.close(connection, null, null);
        }
    }

    public T querySingle(Class<T> tClass, String sql, Object... parameters) {
        Connection connection = null;
        try {
            connection = JDBCUtil.connect();
            return queryRunner.query(connection, sql, new BeanHandler<>(tClass), parameters);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            JDBCUtil.close(connection, null, null);
        }
    }

    public Object queryScalar(String sql, Object... parameters) {
        Connection connection = null;
        try {
            connection = JDBCUtil.connect();
            return queryRunner.query(connection, sql, new ScalarHandler<>(), parameters);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            JDBCUtil.close(connection, null, null);
        }
    }
}
JDBCUtil.java

——见 [23.7.2 Druid(德鲁伊)连接池 ]

package com.melody.wmt.sql;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.FileReader;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

/**
 * @author Melody
 * @version 1.0
 */
public class JDBCUtil {
    private static DataSource dataSource = null;

    static {
        Properties properties = new Properties();
        try {
            properties.load(new FileReader("src\\com\\melody\\wmt\\sql\\WanMinSQL.properties"));
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            new RuntimeException(e);
        }
    }

    public static Connection connect() throws SQLException {
        return dataSource.getConnection();
    }

    public static void close(Connection c, Statement s, ResultSet r) {
        try {
            if (c != null) {
                c.close();
            }
            if (r != null) {
                r.close();
            }
            if (s != null) {
                s.close();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}
TableDAO.java、TableData.java

对应数据库的 table

package com.melody.wmt.sql;

import java.util.List;

/**
 * @author Melody
 * @version 1.0
 */
public class TableDAO extends BasicDAO{
    /* 返回全部的餐桌对象 */
    public List<TableData> queryAll(){
        String sql = "select * from `table`";
        List<TableData> list = queryMulti(TableData.class, sql);
        return list;
    }
	
    /* 返回 ID 指定的餐桌对象。没找到的场合返回 null */
    public TableData queryTable(int id) {
        String sql = "select * from `table` where `id` = ?";
        TableData tableData = null;
        try {
            tableData = (TableData) querySingle(TableData.class, sql, id);
        } catch (Exception e) {
            return null;
        }
        return tableData;
    }
	
    /* 设置指定 ID 的餐桌对象的 order 和 customer */
    public boolean orderTable(int id, int customer) {
        TableData tableData = queryTable(id);
        if (!tableData.getOrder().equals("空")){
            return false;
        }
        String sql = "update `table` set `order` = '已预定', `customer` = ? where `id` = ?";
        int update = update(sql, customer, id);
        return update > 0;
    }
	
    /* 更新指定 ID 餐桌对象的 order 状态。一般是把 已预定 状态改为 用餐 */
    public boolean updateTable(int id, int n) {
        String sql = "update `table` set `order` = ? where `id` = ?";
        String order = null;
        switch (n){
            case 1:
                order = "已预定";
                break;
            case 2:
                order = "用餐";
                break;
            case 3:
                order = "空";
                break;
            case 4:
                order = "不可用";
                break;
            default:
                return false;
        }
        return update(sql, order, id) > 0;
    }
	
    /* 清理指定 ID 的餐桌。通常是结账后调用这个方法 */
    public boolean clearTable(int table) {
        String sql = "update `table` set `order` = '空', `customer` = null where `id` = ?";
        return update(sql, table) > 0;
    }
}
package com.melody.wmt.sql;

import java.io.Serializable;

/**
 * @author Melody
 * @version 1.0
 */
public class TableData implements Serializable {
    private int id;
    private String order;
    private int customer;
    private int seat;

    public TableData() {
    }

    public TableData(int id, String order, int customer, int seat) {
        this.id = id;
        this.order = order;
        this.customer = customer;
        this.seat = seat;
    }
	
    /* 省略了 getter 和 setter。实际代码里是有的 */

    @Override
    public String toString() {
        return id + "\t" + seat + "\t" + order + "\t" + (customer == 0 ? "-" : customer);
    }
}
LoginDAO.java、LoginData.java

对应数据库的 Login

package com.melody.wmt.sql;

/**
 * @author Melody
 * @version 1.0
 */
public class LoginDAO extends BasicDAO{
    /* 返回指定 name 和 password 的对象。没有匹配项则返回 null */
    public LoginData checkLogin(String name, String password){
        try {
            /* 这里体现出,密码是由 md5 方法加密的 */
            String sql = "select * from login where name = ? and password = md5(?)";
            LoginData loginData = (LoginData) querySingle(LoginData.class, sql, name, password);
            return loginData;
        } catch (Exception e) {
            return null;
        }
    }
}
public class LoginData implements Serializable {
    private int id;
    private String name;
    private String password;

    public LoginData(){}

    public LoginData(int id, String name, String password) {
        this.id = id;
        this.name = name;
        this.password = password;
    }
	
    /* 省略了 getter 和 setter。实际代码里是有的 */

}
DishesDAO.java、DishesData.java

对应数据库的 Dishes

package com.melody.wmt.sql;

import java.util.List;

/**
 * @author Melody
 * @version 1.0
 */
public class DishesDAO extends BasicDAO{
    /* 返回所有菜单 */
    public List<DishesData> getDishes() {
        String sql = "select * from `dishes`";
        return queryMulti(DishesData.class, sql);
    }	
	
    /* 返回 ID 指定的菜单。没有则返回 null */
    public Object checkDishes(int id) {
        String sql = "select * from `dishes` where `id` = ?";
        return querySingle(DishesData.class, sql, id);
    }
}
public class DishesData {
    private int id;
    private String name;
    private int price;
    private String species;
    private String available;

    public DishesData(){}

    public DishesData(int id, String name, int price, String species, String available) {
        this.id = id;
        this.name = name;
        this.price = price;
        this.species = species;
        this.available = available;
    }
	
    /* 省略了 getter 和 setter。实际代码里是有的 */

    @Override
    public String toString() {
        return id + "\t" + name + "\t" + price;
    }
}
CustomerDAO.java、CustomerData.java

对应数据库的 Customer

package com.melody.wmt.sql;

/**
 * @author Melody
 * @version 1.0
 */
public class CustomerDAO extends BasicDAO {
    /* 返回 name 和 call 指定的顾客信息。没有则返回 null */
    public int searchCustomer(String name, String call) {
        String sql = "select * from customer where `name` = ? and `call` = ?";
        Object o = querySingle(CustomerData.class, sql, name, call);
        return o == null ? -1 : ((CustomerData) o).getId();
    }
	
    /* 创建顾客信息,信息包含 name 和 call */
    public boolean createCustomer(String name, String call) {
        String sql = "insert into customer (`name`, `call`) values(?, ?)";
        int update = update(sql, name, call);
        return update > 0;
    }
}
package com.melody.wmt.sql;

/**
 * @author Melody
 * @version 1.0
 */
public class CustomerData {
    private int id;
    private String name;
    private String card_id;
    private String call;

    public CustomerData(){}

    public CustomerData(int id, String name, String card_id, String call) {
        this.id = id;
        this.name = name;
        this.card_id = card_id;
        this.call = call;
    }
	
    /* 省略了 getter 和 setter。实际代码里是有的 */

}
BillDAO.java、BillData.java、DetailBillData.java

对应数据库的 Bill 表,Detail_Bill 视图

package com.melody.wmt.sql;

import java.util.List;

/**
 * @author Melody
 * @version 1.0
 */
public class BillDAO extends BasicDAO {
    /* 订餐。形参是 餐品号、数量、桌号、顾客号。成功的场合返回 true */
    public boolean order(int dishes, int num, int table, int customer) {
        String sql = "insert into `bill` (`dishes`, `num`, `table`, `customer`) values(?, ?, ?, ?)";
        int update = update(sql, dishes, num, table, customer);
        return update > 0;
    }
	
    /* 返回账单列表 */
    public List<DetailBillData> queryBills() {
        String sql = "select * from detail_bill";
        return (List<DetailBillData>) queryMulti(DetailBillData.class, sql);
    }
	
    /* 返回特定桌号的帐单列表
    Boolean 为 true 则返回未付账单;false 返回已结账单;null 返回全部 */
    public List<DetailBillData> queryBillsByTable(int table, Boolean clear) {
        String sql;
        if (clear == null) {
            sql = "select * from detail_bill where `table` = ?";
        } else {
            sql = "select * from detail_bill where `table` = ? and " + (clear ? "" : "not") + " `clear` = 'n'";
        }
        return (List<DetailBillData>) queryMulti(DetailBillData.class, sql, table);
    }
	
    /* 将指定桌号的所有未付账单结算,其结算方式为 way 指定的方式 */
    public int finishBill(int table, int way) {
        String clear;
        if (way == 1) {
            clear = "y";
        } else if (way == 2) {
            clear = "alipay";
        } else if (way == 3) {
            clear = "wc_pay";
        } else {
            throw new RuntimeException();
        }
        String sql = "update bill set `clear` = ? where `table` = ? and `clear` = 'n'";
        return update(sql, clear, table);
    }
}
public class BillData {
    private int id;
    private Date date;
    private int customer;
    private int table;
    private int num;
    private String clear;
    private int dishes;

    public BillData() {
    }

    public BillData(int id, Date date, int customer, int table, int num, String clear, int dishes) {
        this.id = id;
        this.date = date;
        this.customer = customer;
        this.table = table;
        this.num = num;
        this.clear = clear;
        this.dishes = dishes;
    }

    /* 省略了 getter 和 setter。实际代码里是有的 */

    @Override
    public String toString() {
        return id + "\t" + customer + "\t" + table + "\t" + num + "\t" + clear + "\t" + dishes + "\t" + date;
    }
}
public class DetailBillData {
    private String name;
    private String call;
    private int table;
    private String dishes;
    private int num;
    private Date date;
    private String clear;

    public DetailBillData() {
    }

    public DetailBillData(String name, String call, int table, String dishes, int num, Date date, String clear) {
        this.name = name;
        this.call = call;
        this.table = table;
        this.dishes = dishes;
        this.num = num;
        this.date = date;
        this.clear = clear;
    }

    /* 省略了 getter 和 setter。实际代码里是有的 */

    @Override
    public String toString() {
        return name + "\t" + call + "\t" + table + "\t" + dishes + "\t" + num + "\t" + date + "\t" + clear;
    }
}

客户端(管理)部分

com.melody.wmt.client;
Clinet.java

客户端程序主体

该包实现:登录界面、菜单界面、选择菜单

package com.melody.wmt.client;

import com.melody.wmt.sql.LoginData;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

/**
 * @author Melody
 * @version 1.0
 */
public class Client {
    public static String IP;						//自己的 IP
    public static String ServerIP = "192.168.3.16";	//服务端 IP
    private static Scanner scanner = new Scanner(System.in);
    private Socket socket;
    private ObjectInputStream ois;					//输入流
    private ObjectOutputStream oos;					//输出流
    							//以上三个属性会在 connect() 方法中被赋值
    public final MenuUtility menuUtility = new MenuUtility(this);
    							//方法类对象,传入形参 this,形成了互相持有的结构
    public final Listening listening = new Listening(this);
    							//监听类对象,传入形参 this,形成了互相持有的结构
    public LoginData loginData = null;				
    							//用户数据。登陆后服务端会发送一个该类对象过来
    public boolean skipMenu = false;
    							//跳过菜单显示。该属性没有投入使用
    
    /* 多此一举地初始化自己的 IP 地址。其实可以直接写。 */
    static {
        try {
            IP = InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            System.out.println(e);
            IP = "192.168.3.16";
        }
    }

	/* 展示主菜单 */
    public void mainMenu() {
        char read;
        while (true) {
            System.out.println("====================================");
            System.out.println("\t\t1. 登 录 系 统");
            System.out.println("\t\t9. 退 出 系 统");
            System.out.print("请选择:");
            read = scanner.next().charAt(0);
            System.out.println();
            System.out.println("------------------------------------");
            switch (read) {
                case '1':
                    /* 这里会先建立连接 connect(),然后调用登录方法 login() */
                    if (connect() && menuUtility.login()) {
                        try {
                            menu_1();
                        } catch (Exception e) {
                            System.out.println(e);
                            /* 有任何问题会执行 closeAll(),即关闭一切流 */
                            closeAll();
                        }
                    }
                    closeAll();
                    break;
                case '9':
                    if (menuUtility.out()) {
                        return;
                    }
                    break;
                default:
                    System.out.println("输入错误。");
                    break;
            }
        }
    }
	
    /* 建立连接 */
    public boolean connect() {
        try {
            socket = new Socket(ServerIP, 9000);
            oos = new ObjectOutputStream(socket.getOutputStream());
            ois = new ObjectInputStream(socket.getInputStream());
            new Thread(listening).start();
            return true;
        } catch (IOException e) {
            System.out.println(e);
            closeAll();
            return false;
        }
    }
	
    /* 关闭所有流。通常是登出、断线或报错后执行这个方法 */
    public void closeAll() {
        try {
            if (ois != null) {
                ois.close();
            }
            if (oos != null) {
                oos.close();
            }
            if (socket != null) {
                socket.close();
            }
            listening.listening = false;
        } catch (IOException e) {
            System.out.println(e);
        }
    }
	
    /* 显示(登录)菜单。这个类只是在屏幕上输出字符 */
    public void showMenu_1() {
        /* 设置了跳过菜单显示的场合,仅一次菜单不显示 */
        if (skipMenu){
            skipMenu = false;
            return;
        }
        System.out.println("================" + loginData.getName() + "================");
        System.out.println("\t\t1. 餐 桌 状 态");
        System.out.println("\t\t2. 预 定 餐 桌");
        System.out.println("\t\t3. 菜 单");
        System.out.println("\t\t4. 点 餐");
        System.out.println("\t\t5. 账 单");
        System.out.println("\t\t6. 结 账");
        System.out.println("\t\t9. 登 出 系 统");
    }
	
    /* 根据菜单选择执行的功能 */
    public void menu_1() {
        char read;
        while (true) {
            /* 显示菜单。设置跳过的场合仅一次菜单不显示 */
            showMenu_1();
            System.out.print("请选择:");
            read = scanner.next().charAt(0);
            System.out.println("------------------------------------");
            switch (read) {
                case '1':
                    menuUtility.showTables();	//展示餐桌列表
                    break;
                case '2':
                    menuUtility.orderTable();	//预定餐桌
                    break;
                case '3':
                    menuUtility.showList();		//展示菜单列表
                    break;
                case '4':
                    menuUtility.orderDishes();	//订餐
                    break;
                case '5':
                    menuUtility.showBill();		//展示账单列表
                    break;
                case '6':
                    menuUtility.payBill();		//结账
                    break;
                case '9':
                    if (menuUtility.logout()) {	//登出
                        return;
                    }
                    break;
                default:
                    System.out.println("输入错误。");
                    break;
            }
        }
    }

    public Socket getSocket() {
        return socket;
    }

    public ObjectInputStream getOis() {
        return ois;
    }

    public ObjectOutputStream getOos() {
        return oos;
    }
	
    /* 抛出一个异常。这个方法用于抛出异常(听君一席话……) */
    public void throwException(String s) {
        throw new RuntimeException(s);
    }
}
Listening.java

监听服务端发送数据包的线程

package com.melody.wmt.client;

import com.melody.wmt.common.Message;


/**
 * @author Melody
 * @version 1.0
 */
public class Listening implements Runnable{
    private Client client;				//和主程序相互持有,便于调用
    public Message message;				//接收的数据包
    public boolean listening = true;	//监听中。false 会停止线程
    public boolean received = false;	//收到文件。
    						//这是实现某个方法的过渡办法,似乎已经可以弃用

    public Listening(Client client) {
        this.client = client;
    }

    @Override
    public void run() {
        listening = true;
        while (listening){
            try {
                Object o = client.getOis().readObject();
                if (o == null){
                    continue;
                }
                message = (Message) o;
                received = true;
            }catch (Exception e) {
                client.throwException("监听异常" + e);
            }
        }
        System.out.println("监听停止");
    }
}

客户端程序的方法包

该包实现:……大部分功能

package com.melody.wmt.client;

import com.melody.wmt.common.Message;
import com.melody.wmt.common.MessageType;
import com.melody.wmt.sql.LoginData;
import com.melody.wmt.sql.TableData;

import java.io.IOException;
import java.util.Scanner;

/**
 * @author Melody
 * @version 1.0
 */
public class MenuUtility {
    private Scanner scanner = new Scanner(System.in);
    private Client client;				//和主程序互相持有,方便调用
    private String userData = null;		//内容是:"用户名\t密码"。
    						//每个发送的数据包的 Object 开头都是这个

    public MenuUtility(Client client) {
        this.client = client;
    }
	
    /* 发送数据包,并返回接收的数据包 */
    /* 通常会返回接收到的数据包。也可能返回 null 代表失败 */
    public Message sendAndReceive(Message message) {
        /* 发送包不能为 null。基本上不会出现这种情况 */
        if (message == null) {
            System.out.println("错误:传递空包");
            return null;
        }
        /* 刷新数据包时间戳 */
        message.setTimeStamp();
        /* 清空监听进程里已有的数据 */
        client.listening.received = false;
        client.listening.message = null;
        /* 接收数据包对象 */
        Message receive = null;
        try {
            /* 发送数据包 */
            client.getOos().writeObject(message);
            /* 标记此时的时间 */
            long time = System.currentTimeMillis();
            int n = 0;
            /* 在指定时间内持续试图接收服务端数据包 */
            /* 经测试,在 while(true) 中如果只有单一的 if 语句,
            	那么该 if 语句条件(或主体)只会执行一次。
            	这会造成客户端接收不到服务端数据包。
            	因此,加入了无关紧要的小动画,确保 if 语句总会重复执行。*/
            while (true) {
                n++;
                System.out.print("\r");
                /* if 语句中的才是主体
                	接收到数据包或者等待 5 秒以上后,拿取数据包并跳出该循环 */
                if (System.currentTimeMillis() - time >= 5000 | client.listening.received) {
                    receive = client.listening.message;
                    break;
                }
                /* 下面是一个旋转线条的小动画。
                	由于上面的 \r 存在,该动画在程序继续后会被其他字符覆盖 */
                switch (n % 4) {
                    case 1:
                        System.out.print("|");
                        break;
                    case 2:
                        System.out.print("/");
                        break;
                    case 3:
                        System.out.print("-");
                        break;
                    default:
                        System.out.print("\\");
                        break;
                }
            }
            return receive;
        /* 有任何异常则返回 null */
        } catch (IOException e) {
            System.out.println(e);
            return null;
        }
    }

    /* 实现登录验证 */
    public boolean login() {
        System.out.print("请输入用户名:");
        String user = scanner.next();
        System.out.print("请输入密码:");
        String pw = scanner.next();
        System.out.println();
        /* 这句字符串会被重复使用 */
        userData = (user + "\t" + pw);
        /* 发送数据包。这里写得比较挤,后面我都分开写了 */
        Message receive = sendAndReceive(new Message(Client.IP, Client.ServerIP, MessageType.LOGIN, userData));
        /* 根据返回包做出相应提示 */
        if (receive == null) {
            System.out.println("错误:服务器无响应");
            return false;
        }
        String reply = receive.getWord();
        if (reply.equals(MessageType.LOGIN_USER_NOT_EXIST)) {
            System.out.println("错误:用户名不存在");
        } else if (reply.equals(MessageType.LOGIN_WRONG_PW)) {
            System.out.println("错误:密码错误");
        } else if (reply.equals(MessageType.ACCEPT)) {
            try {
                client.loginData = (LoginData) receive.getObject();
            } catch (Exception e) {
                System.out.println("未知错误");
                return false;
            }
            System.out.println("登录成功");
        }
        /* 返回值。true 表示登陆成功 */
        return receive.getWord().equals(MessageType.ACCEPT);
    }
	
    /* 退出确认。这是未登录时(即,登陆界面)的退出方法 */
    public boolean out() {
        System.out.print("确定退出吗?(Y/N)");
        char answer = scanner.next().charAt(0);
        return answer == 'Y' || answer == 'y';
    }
	
    /* 展示餐桌列表 */
    public boolean showTables() {
        /* 发送请求,接收服务端返回的数据包 */
        Message message = new Message(Client.IP, Client.ServerIP, MessageType.SHOW_TABLES, userData);
        Message receive = sendAndReceive(message);
        /* 根据返回包的不同作出反应 */
        if (receive == null) {
            System.out.println("错误:服务器无响应");
            return false;
        }
        String r = receive.getWord();
        if (r.equals(MessageType.REJECT)) {
            System.out.println("错误:服务器拒绝了请求");
            return false;
        } else if (r.equals(MessageType.ACCEPT)) {
            System.out.println("ID\t座数\t状态\t顾客");
            System.out.println(receive.getObject());
            return true;
        } else {
            System.out.println("错误:未知的回执" + r);
            return false;
        }
    }
	
    /* 检查餐桌状态 */
    /* need:1不可用;2不存在;3占用未点餐;4占用已点餐;5未占用;6占用
    	根据需要的餐桌状态 need 的不同,有不同的返回方法*/
    public TableData checkTable(int tableID, int need) {
        /* 发送验证餐桌请求,接收返回包 */
        Message message = new Message(client.IP, client.ServerIP, MessageType.CHECK_TABLE, userData + "\t" + tableID);
        Message receive = sendAndReceive(message);
        /* 根据返回包做出不同反应 */
        if (receive == null) {
            System.out.println("错误:服务器无响应");
            return null;
        }
        /* 如果 need 和返回结果匹配,give 值会为 true */
        boolean give = false;
        if (receive.getWord().equals(MessageType.REJECT)) {
            System.out.println("错误:请求被拒绝");
        } else if (receive.getWord().equals(MessageType.TABLE_NOT_AVAILABLE)) {
            give = need == 1;
            System.out.print(give ? "" : "餐桌不可用\n");
        } else if (receive.getWord().equals(MessageType.TABLE_NOT_EXIST)) {
            give = need == 2;
            System.out.print(give ? "" : "餐桌不存在\n");
        } else if (receive.getWord().equals(MessageType.TABLE_OCCUPIED)) {
            give = need == 3 || need == 6;
            System.out.print(give ? "" : "餐桌被占用(未点餐)\n");
        } else if (receive.getWord().equals(MessageType.TABLE_DINNING)) {
            give = need == 4 || need == 6;
            System.out.print(give ? "" : "餐桌被占用(已点餐)\n");
        } else if (receive.getWord().equals(MessageType.TABLE_AVAILABLE)) {
            give = need == 5;
            System.out.print(give ? "" : "餐桌为空\n");
        } else {
            System.out.println("错误:未知错误");
        }
        try {
            /* 只有匹配的结果才会返回餐桌数据 */
            return give ? (TableData) receive.getObject() : null;
        } catch (Exception e) {
            return null;
        }
    }
	
    /* 预定餐桌 */
    public void orderTable() {
        /* 首先会调用一次展示餐桌的方法 */
        showTables();
        /* 输入餐桌号 */
        System.out.println("------------------------------------");
        System.out.print("请输入预定的餐桌号码(-1取消):");
        int tableID = 0;
        try {
            tableID = Integer.parseInt(scanner.next());
            if (tableID == -1) {
                return;
            } else if (tableID <= 0) {
                throw new RuntimeException();
            }
        } catch (Exception e) {
            System.out.println("输入错误。");
            return;
        }
        /* 检查一下上面输入的餐桌是否正确。这时的正确指的是餐桌为空 */
        TableData tableData = checkTable(tableID, 5);
        if (tableData == null) {
            return;
        }
        System.out.print("请输入预定人名字:");
        String name = scanner.next();
        System.out.println("请输入预定人电话:");
        String call = scanner.next();
        System.out.println("请确认预定信息:");
        System.out.println("桌号:" + tableData.getId() + "\t座数:" + tableData.getSeat() + "\t预定人:" + name + "\t电话:" + call);
        System.out.print("确定吗?(Y/N):");
        char confirm = scanner.next().charAt(0);
        if (confirm != 'Y' && confirm != 'y') {
            return;
        }
        /* 发送请求预定餐桌。前面的顾客对象不存在的话,服务端会自动创建新的对象 */
        Message message = new Message(client.IP, client.ServerIP, MessageType.ORDER_TABLE, userData + "\t" + tableID + "\t" + name + "\t" + call);
        Message receive = sendAndReceive(message);
        if (receive.getWord().equals(MessageType.ACCEPT)) {
            System.out.println("预定成功!");
        } else {
            System.out.println("预定失败");
        }
    }
	
    /* 展示菜单 */
    public void showList() {
        /* 发送展示菜单的请求 */
        Message message = new Message(Client.IP, Client.ServerIP, MessageType.SHOW_DISHES, userData);
        Message receive = sendAndReceive(message);
        if (receive == null) {
            System.out.println("错误:服务器无响应");
            return;
        } else if (!receive.getWord().equals(MessageType.ACCEPT)) {
            System.out.println("错误:请求被拒绝");
            return;
        }
        /* 此时拿到了菜单。该菜单的不同类别是由 ### 分割的。我们再将其分开 */
        String[] dishes;
        try {
            dishes = ((String) receive.getObject()).split("###");
        } catch (Exception e) {
            System.out.println("未知错误");
            return;
        }
        char in;
        /* 这其实又是一级菜单了。直到选择 9 前,该菜单会一直循环 */
        while (true) {
            System.out.println("---------------------------------------");
            System.out.println("\t\t请选择要查看的类别");
            System.out.println("\t\t1. 主  食");
            System.out.println("\t\t2. 主  菜");
            System.out.println("\t\t3. 凉  菜");
            System.out.println("\t\t4. 汤  品");
            System.out.println("\t\t5. 甜  点");
            System.out.println("\t\t6. 饮  料");
            System.out.println("\t\t7. 点  餐");
            System.out.println("\t\t9. 返  回");
            System.out.print("请输入:");
            in = scanner.next().charAt(0);
            System.out.println("---------------------------------------");
            switch (in) {
                case '1':
                    System.out.println("------------主  食------------");
                    System.out.println("ID\t名称  \t价格");
                    System.out.println(dishes[0]);
                    break;
                case '2':
                    System.out.println("------------主  菜------------");
                    System.out.println("ID\t名称  \t价格");
                    System.out.println(dishes[1]);
                    break;
                case '3':
                    System.out.println("------------凉  菜------------");
                    System.out.println("ID\t名称  \t价格");
                    System.out.println(dishes[2]);
                    break;
                case '4':
                    System.out.println("------------汤  品------------");
                    System.out.println("ID\t名称  \t价格");
                    System.out.println(dishes[3]);
                    break;
                case '5':
                    System.out.println("------------甜  点------------");
                    System.out.println("ID\t名称  \t价格");
                    System.out.println(dishes[4]);
                    break;
                case '6':
                    System.out.println("------------饮  料------------");
                    System.out.println("ID\t名称  \t价格");
                    System.out.println(dishes[5]);
                    break;
                case '7':
                    /* 这时调用订餐的方法 */
                    orderDishes();
                    break;
                case '9':
                    return;
                default:
                    System.out.println("输入错误");
                    break;
            }
        }
    }
	
    /* 订餐 */
    public void orderDishes() {
        System.out.println("请输入菜品编号(数字,-1返回):");
        int dish;
        try {
            dish = Integer.parseInt(scanner.next());
        } catch (Exception e) {
            System.out.println("输入错误");
            return;
        }
        if (dish < 0) {
            return;
        }
        /* 检查输入的餐品信息。这里把一部分代码封装成了一个新方法
        	……这是因为测试发现,不这样做会导致内存数据不更新,接收到的返回包总是不变 */
        String dishDetail = orderDishesPart1(dish);
        if (dishDetail == null) {
            return;
        }
        System.out.println("请输入数量(数字,-1返回):");
        int num;
        try {
            num = Integer.parseInt(scanner.next());
        } catch (Exception e) {
            System.out.println("输入错误");
            return;
        }
        if (num < 0) {
            return;
        }
        System.out.println("请输入订餐桌号(数字,-1返回):");
        int table;
        try {
            table = Integer.parseInt(scanner.next());
        } catch (Exception e) {
            System.out.println("输入错误");
            return;
        }
        if (table < 0) {
            return;
        }
        /* 检查一下餐桌信息。这里,正确的状态是'已占用' */
        TableData tableData = checkTable(table, 6);
        if (tableData == null) {
            return;
        }
        /* 展示一下信息 */
        System.out.println("ID\t名称  \t价格 \t数量\t桌号");
        System.out.println(dishDetail + "\t" + num + "\t" + tableData.getId());
        System.out.print("确定吗?(Y/N):");
        char confirm = scanner.next().charAt(0);
        if (confirm != 'Y' && confirm != 'y') {
            return;
        }
        /* 发送订餐请求 */
        Message message = new Message(Client.IP, Client.ServerIP, MessageType.ORDER_DISHES, null);
        message.setObject(userData + "\t" + dish + "\t" + num + "\t" + table + "\t" + tableData.getCustomer());
        Message receive = sendAndReceive(message);
        if (receive == null) {
            System.out.println("错误:服务器无响应");
        } else if (receive.getWord().equals(MessageType.REJECT)) {
            System.out.println("错误:请求被拒绝");
        } else if (receive.getWord().equals(MessageType.ACCEPT)) {
            System.out.println("预定成功");
        }
    }
	
    /* 订餐方法中,小部分代码封装成了这个方法 */
    /* 如此做是因为测试发现,不这样做会导致内存数据不更新,
    	接收到的服务端返回包内容总是和方法中第一次接收的返回包内容一样。
    	测试过很多次,问题就是出在这里。解决方法就是这样。
        这样,这个方法执行完毕后,似乎会刷新内存。可能这就是垃圾处理机制吧 */
    public String orderDishesPart1(int dish) {
        Message message = new Message(Client.IP, Client.ServerIP, MessageType.CHECK_DISHES, userData + "\t" + dish);
        Message receive1 = sendAndReceive(message);
        if (receive1 == null) {
            System.out.println("错误:服务器无响应");
            return null;
        } else if (!receive1.getWord().equals(MessageType.ACCEPT)) {
            System.out.println("错误:请求被拒绝");
            return null;
        }
        System.out.println("ID\t名称  \t价格");
        String dishDetail = receive1.getObject().toString();
        System.out.println(dishDetail);
        return dishDetail;
    }

	/* 展示账单列表 */
    public void showBill() {
        /* 发送请求 */
        Message message = new Message(Client.IP, Client.ServerIP, MessageType.SHOW_ALL_BILLS, userData);
        Message receive = sendAndReceive(message);
        if (receive == null) {
            System.out.println("错误:服务器无响应");
            return;
        } else if (receive.getWord().equals(MessageType.REJECT)) {
            System.out.println("错误:请求被拒绝");
            return;
        }
        /* 同样的,账单列表也是以 ### 分隔的 */
        String[] bills;
        try {
            bills = ((String) receive.getObject()).split("###");
        } catch (Exception e) {
            System.out.println("错误:未知错误");
            return;
        }
        /* 另一个小菜单。选择 9 前会一直循环 */
        while (true) {
            System.out.println("---------------------------------------");
            System.out.println("\t\t请选择要查看的类别");
            System.out.println("\t\t1. 已完成账单");
            System.out.println("\t\t2. 未完成账单");
            System.out.println("\t\t9. 返  回");
            System.out.print("请输入:");
            char in = scanner.next().charAt(0);
            System.out.println("---------------------------------------");
//            System.out.println("姓名 \t电话  \t桌号\t菜名   \t数量\t日期          \t结算状态");
            switch (in) {
                case '1':
                    System.out.println(bills[0].length() == 0 ? "---无---" : bills[0]);
                    break;
                case '2':
                    System.out.println((bills.length < 2 || bills[1].length() == 0) ? "---无---" : bills[1]);
                    break;
                case '9':
                    return;
                default:
                    System.out.println("输入错误");
                    break;
            }
        }
    }
	
    /* 按桌号检查账单 */
    public String checkBillByTable(int table) {
        /* 发送请求
        	特别一提:后面的 "\t" + 't' + "\t" + table + "\t" + "true" 
        	代表 按桌号查询(t),号码是 table,展示未结账账单(true) */
        Message message = new Message(Client.IP, Client.ServerIP, MessageType.CHECK_BILLS, userData + "\t" + 't' + "\t" + table + "\t" + "true");
        Message receive = sendAndReceive(message);
        if (receive == null) {
            System.out.println("错误:服务器无响应");
            return null;
        } else if (receive.getWord().equals(MessageType.ACCEPT)) {
            return receive.getObject().toString();
        } else if (receive.getWord().equals(MessageType.REJECT)) {
            System.out.println("错误:请求被拒绝");
            return null;
        } else {
            System.out.println("错误:未知错误");
            return null;
        }
    }
	
    /* 结账 */
    public void payBill() {
        showTables();
        System.out.print("请输入结账的桌号(-1返回):");
        int table = 0;
        try {
            table = Integer.parseInt(scanner.next());
        } catch (Exception e) {
            System.out.println("输入错误");
        }
        if (table < 0) {
            return;
        }
        /* 检查一下桌号 */
        TableData tableData = checkTable(table, 4);
        if (tableData == null) {
            return;
        }
        /* 检查并展示那个桌号的未结账单 */
        String s = checkBillByTable(table);
        if (s == null) {
            return;
        }
        System.out.println(s);
        System.out.println("请确认以上信息(Y/N):");
        char confirm = scanner.next().charAt(0);
        if (confirm != 'Y' && confirm != 'y'){
            return;
        }
        System.out.println("请输入结算方式(1现金;2支付宝;3微信;-1返回):");
        int way = -1;
        try {
            way = Integer.parseInt(scanner.next());
            if (way < -1 || way > 3){
                throw new RuntimeException();
            }
        } catch (Exception e) {
            System.out.println("输入错误");
        }
        if (way == -1) {
            return;
        }
        /* 发送结账请求 */
        Message message = new Message(Client.IP, Client.ServerIP, MessageType.FINISH_BILLS, userData + "\t" +  table + "\t" + way);
        Message receive = sendAndReceive(message);
        if (receive == null) {
            System.out.println("错误:服务器无响应");
        } else if (receive.getWord().equals(MessageType.REJECT)) {
            System.out.println("错误:请求被拒绝");
        } else if (receive.getWord().equals(MessageType.ACCEPT)) {
            System.out.println("结算成功");
        }
    }
	
    /* 登出 */
    public boolean logout() {
        System.out.print("确定登出吗?(Y/N):");
        char confirm;
        try {
            confirm = scanner.next().charAt(0);
            if (confirm != 'y' && confirm != 'Y'){
                return false;
            }
            Message message = new Message(Client.IP, Client.ServerIP, MessageType.LOGOUT, userData);
            client.getOos().writeObject(message);
            return true;
        } catch (IOException e) {
            System.out.println("传输异常");
            return true;
        }
    }
}

<Java>24 项目(满汉楼)
https://i-melody.github.io/2022/02/26/Java/入门阶段/24 项目:满汉楼/
作者
Melody
发布于
2022年2月26日
许可协议