《高级Java程序设计》大作业(双号版)

北京信息科技大学2021~2022 学年第2学期

高级Java程序设计计时大作业

时间:2022年5月21日星期六 18:30~21:00

注意:此试卷为学号尾号为2,4,6,8,0的学生使用的试卷

班级:       学号:         姓名(电子签名):        

大作业注意事项:               

  1. 要求学生在上述指定时间内,按要求独立完成大作业;
  2. 禁止使用任何网络聊天工具;禁止相互交流讨论,禁止传递任何资料与信息;
  3. 在规定的时间内上传大作业文档到课堂派;
  4. 大作业雷同者,大作业成绩按零分处理。

按下列需求,设计与实现一个网络版的银行管理系统的部分模块。

一、数据库设计(15分)

  1. (2分)创建银行管理数据库,银行管理数据库以Bank+你的姓名的汉语拼音缩写命名(例如:张三的数据库名为BankZS)
  2. (8分)在银行管理数据库中创建账户数据表,账户数据表以account+你的学号命名,账户数据表中包含账号(accNo,varchar(10),Primary key),账户名(accName,varchar(30)),余额( balance , decimal(7,2) ),开户日期(dateCreated,date)
  3. (5分)在账户数据表中添加5条记录。

其中一条记录的账号是你的学号,账户名为你的姓名,余额要大于3000

其他4条记录中2条大于5000,2条小于5000

【在此粘贴证明您完成上述任务的截图】

  • 规划项目结构(5分),
  1. 创建工程,工程以你的姓名的全拼音命名(例如,张三的项目名称zhangsan)
  2. 在项目中创建5个包

server.idao(数据访问接口包)

server.dao(数据访问模型包)

server.model(数据模型包)

server.util(工具包)

client.view(视图包)

【在此粘贴证明您完成上述任务的截图】

  • 创建服务器端程序(60分)
  1. (10分)在server.model包中,创建一个封装了账户信息并实现了序列化接口的类Account ,Account类中应包含accountNo,accountName,balance,dateCreated属性,set/get方法,toString()方法。

【在此粘贴源程序(注意:源程序不能是截图)】

public class Account implements Serializable {
    String accNo;
    String accName;
    Double balance;
    Date dateCreated;

    public Account() {
    }

    public Account(String accNo, String accName, Double balance, Date dateCreated) {
        this.accNo = accNo;
        this.accName = accName;
        this.balance = balance;
        this.dateCreated = dateCreated;
    }

    public String getAccNo() {
        return accNo;
    }

    public void setAccNo(String accNo) {
        this.accNo = accNo;
    }

    public String getAccName() {
        return accName;
    }

    public void setAccName(String accName) {
        this.accName = accName;
    }

    public Double getBalance() {
        return balance;
    }

    public void setBalance(Double balance) {
        this.balance = balance;
    }

    public Date getDateCreated() {
        return dateCreated;
    }

    public void setDateCreated(Date dateCreated) {
        this.dateCreated = dateCreated;
    }

    @Override
    public String toString() {
        return "Account{" +
                "accNo='" + accNo + '\'' +
                ", accName='" + accName + '\'' +
                ", balance=" + balance +
                ", dateCreated=" + dateCreated +
                '}';
    }
}

  1. (10分)在server.util包中建立一个SqlHelper类封装对数据库的连接操作和关闭操作。并设计一个测试类TestSqlHelper,测试SqlHelper的使用。

【在此粘贴源程序(注意:源程序不能是截图)】

public class SqlHelper {
    public static Connection connect() {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            String url = "jdbc:mysql://localhost:3306/BankCMK?useSSL=true&ServerTimezone=UTC";
            Connection conn = DriverManager.getConnection(url, "root", "$Now2022");
            return conn;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static void closeResultSet(ResultSet rs) {
        try {
            rs.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static void closePreparedStatement(PreparedStatement ps){
        try{
            ps.close();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    public static void closeConnection(Connection conn){
        try{
            conn.close();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

【在此粘贴测试SqlHelper结果的截图(证明数据库连接成功)】

  1. (6分)在server.idao包中,定义一个账户的数据访问接口IAccountDao,

(b)学号尾号为2,4,6,8.0的学生IAccountDao接口包含的抽象方法如下:

  1.  public  int findCount(String accountName); 查询账户名中包含某一指定字符串的账户数量
  2. boolean withdraw(String accountNo,double money); 从指定账户中取款,账户余额减少money。如果取款成功返回true
  3. Account findAccount(String accountNo);查询一条指定账户的账户信息。
  4. List<Account> findAccounts(Date dateCreated1,Date dateCreated2)用于从数据库中查询开户日期在dateCreated1和dateCreated2之间的账户信息。
    1. 注:Date为java.sql包中的Date

【在此粘贴源程序(注意:源程序不能是截图)】

public interface IAccountDao {
    public  int findCount(String accountName);
    boolean withdraw(String accountNo,double money);
    Account findAccount(String accountNo);
    List<Account> findAccounts(Date dateCreated1, Date dateCreated2);
}
  1. (14分)在server.dao包中,定义一个实现了接口IAccountDao的数据访问类AccountDao,并编写测试类。按下列要求具体实现接口IAccountDao提供的功能,并用下列给出的测试类测试。

(b)学号尾号为2,4,6,8,0的学生具体实现:

  1. public  int findCount(String accountName);
  2. boolean withdraw(String accountNo,double money);
  3. Account findAccount(String accountNo);
  4. List<Account> findAccounts(Date dateCreated1,Date dateCreated2)用于从数据库中查询开户日期在dateCreated1和dateCreated2之间的账户信息。
    1. 注:Date为java.sql包中的Date

测试程序TestAccountDao.java

package server.dao;
import java.sql.Date;
import java.util.List;
import server.model.Account;
public class TestAccountDao2 {

    public static  void main(String[] ages){
        List<Account> list=null;
        AccountDao accountdao=new AccountDao();
        System.out.println("\n查询记录数findCount(String studName)");  
        System.out.println("账户表中记录数="+accountdao.findCount(""));
        System.out.println();    
        System.out.println("查询账号为你的学号的那条记录.findAccount(accNo)");
        String accNo="你的学号";//注意:在这里把你真实的学号赋值给变量accNo
        Account acc=accountdao.findAccount(accNo);
        System.out.println(acc);
        System.out.println();  
        System.out.println("\n取款300");
        System.out.println("取款成功:"+accountdao.withdraw(accNo, 300));
        System.out.println("\n测试查询多条记录findAccounts(studName");
         list=accountdao.findAccounts("");
         for(Account s:list)
             System.out.println(s);
         System.out.println();             
         System.out.println("\n测试查找账户开户日期在2022年的账户 ");
         Date date1=new Date(2022-1900,0,0);
         Date date2=new Date(2022-1900,11,30);
         list=accountdao.findAccounts(date1, date2);
         for(Account s:list)
             System.out.println(s);
         System.out.println();   
    }  
}

【在此粘贴源程序AccountDao.java(注意:源程序不能是截图)】

public class AccountDao implements IAccountDao {
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;

    @Override
    public int findCount(String accountName) {
        int count = 0;
        try {
            conn = SqlHelper.connect();
            String sql = "Select count(*) from accountCMK where accName like ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1, "%" + accountName + "%");
            rs = ps.executeQuery();
            if (rs.next()) {
                // 因为所查询出来的数据,也只有 count 一行而已。
                count = rs.getInt(1);
            }
        } catch (Exception e) {

        } finally {
            SqlHelper.closeResultSet(rs);
            SqlHelper.closePreparedStatement(ps);
            SqlHelper.closeConnection(conn);
        }
        return count;
    }


    @Override
    public boolean withdraw(String accountNo, double money) {
        try {
            conn = SqlHelper.connect();
            String sql = "update accountCMK set balance=balance-? where accNo=?";
            ps = conn.prepareStatement(sql);
            ps.setDouble(1, money);
            ps.setString(2, accountNo);
            int n = ps.executeUpdate();
            return n > 0;
        } catch (Exception e) {

        } finally {
            SqlHelper.closeResultSet(rs);
            SqlHelper.closePreparedStatement(ps);
            SqlHelper.closeConnection(conn);
        }
        return false;
    }

    @Override
    public Account findAccount(String accountNo) {
        Account account = null;
        try {
            conn = SqlHelper.connect();
            String sql = "Select * from accountCMK where accNo=?";
            ps = conn.prepareStatement(sql);
            ps.setString(1, accountNo);
            rs = ps.executeQuery();
            if (rs.next()) {
                account = new Account();
                account.setAccNo(rs.getString(1));
                account.setAccName(rs.getString(2));
                account.setBalance(rs.getDouble(3));
                account.setDateCreated(rs.getDate(4));
            }
        } catch (Exception e) {
        } finally {
            SqlHelper.closeResultSet(rs);
            SqlHelper.closePreparedStatement(ps);
            SqlHelper.closeConnection(conn);
        }
        return account;
    }

    @Override
    public List<Account> findAccounts(Date dateCreated1, Date dateCreated2) {
        List<Account> list = new ArrayList<>();
        try {
            conn = SqlHelper.connect();
            String sql = "Select * from accountCMK where dateCreated between ? and ?";

            ps = conn.prepareStatement(sql);
            ps.setDate(1, dateCreated1);
            ps.setDate(2, dateCreated2);

            rs = ps.executeQuery();
            Account account = null;
            while (rs.next()) {
                account = new Account();
                account.setAccNo(rs.getString(1));
                account.setAccName(rs.getString(2));
                account.setBalance(rs.getDouble(3));
                account.setDateCreated(rs.getDate(4));
                list.add(account);
            }
        } catch (Exception e) {

        } finally {
            SqlHelper.closeResultSet(rs);
            SqlHelper.closePreparedStatement(ps);
            SqlHelper.closeConnection(conn);
        }
        return list;
    }
}

【在此粘贴测试结果的截图】

  1. (20分)在server.view包中,创建多线程的服务器(Server)端主程序。

在这个程序中,服务器在某个端口上倾听,等待与客户端连接,同时构造一个线程类,准备接管会话。当一个Socket会话产生后,将这个会话交给线程处理,然后主程序继续监听。

(b)学号尾号为2、4、6的学生设计:当服务器收到客户端发来的一个账号后(例如;2020100012),调用AccountDao对象的 findAccount(String AccountNo)方法,在账户表中查询该账户信息,如果该账户存在就把查询到的账户信息返回给客户端;如果没有查询到该账户,就向客户端返回“该账户不存在”,如果客户端发来的是“exit”,则关闭和该客户端连接。

【在此粘贴源程序(注意:源程序不能是截图)】

public class Server extends JFrame {
    private JPanel panUp = new JPanel();
    private JPanel panMid = new JPanel();
    private JLabel lblLocalPort = new JLabel("本机服务器监听端口:");
    protected JButton butStart = new JButton("启动服务器");
    protected JTextField tfLocalPort = new JTextField(25);
    protected JTextArea taMsg = new JTextArea(25, 25);
    JScrollPane scroll = new JScrollPane(taMsg);
    /***/
    public static int localPort = 8000;     // 默认端口 8000
    ServerSocket serverSocket;      // 服务器端 Socket
    AccountDao dao;
    Account account=null;

    public Server() {
        init();
    }

    private void init() {
        panUp.add(lblLocalPort);
        panUp.add(tfLocalPort);
        panUp.add(butStart);
        tfLocalPort.setText(String.valueOf(localPort));
        butStart.addActionListener(new startServerHandler());   // 注册 "启动服务器" 按钮点击事件

        // panMid 区域初始化
        panMid.setBorder(new TitledBorder("监听消息"));
        panMid.add(scroll);
        taMsg.setEditable(false);

        this.setTitle("服务器端");
        this.add(panUp, BorderLayout.NORTH);
        this.add(panMid, BorderLayout.CENTER);
        this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        this.setPreferredSize(new Dimension(600, 300));
        this.pack();
        this.setVisible(true);
    }

    private class startServerHandler implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            try {
                // 当点击按钮时,获取端口设置并启动新进程、监听端口
                localPort = Integer.parseInt(tfLocalPort.getText());
                serverSocket = new ServerSocket(localPort);
                Thread acptThrd = new Thread(new AcceptRunnable());
                acptThrd.start();
                taMsg.append("**** 服务器(端口" + localPort + ")已启动 ****\n");
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    // TODO 接受用户连接请求的线程关联类
    private class AcceptRunnable implements Runnable {
        public void run() {
            // 持续监听端口,当有新用户连接时 再开启新进程
            while (true) {
                try {
                    Socket socket = serverSocket.accept();
                    // 新的用户已连接,创建 Client 对象
                    taMsg.append("新用户连接:" + socket.toString()+"\n");
                    Client client       = new Client(socket);
                    Thread clientThread = new Thread(client);
                    clientThread.start();
                } catch (Exception ex) {
                    System.out.println(ex);
                }
            }
        }

        // TODO 服务器存放用户对象的客户类(主要编程)。每当有新的用户连接时,该类都会被调用
        // TODO 该类继承自 Runnable,内部含有 run()方法
        private class Client implements Runnable {
            private Socket socket;      // 用来保存用户的连接对象
            private BufferedReader in;   // IO 流
            private PrintStream out;

            // Client类的构建方法。当有 新用户 连接时会被调用
            public Client(Socket socket) throws Exception {
                this.socket = socket;
                InputStream is = socket.getInputStream();
                in = new BufferedReader(new InputStreamReader(is));
                OutputStream os = socket.getOutputStream();
                out = new PrintStream(os);
            }

            //客户类线程运行方法
            public void run() {
                try {
                    while (true) {
                        String usermsg = in.readLine();   //读用户发来消息
                        // 如果用户发过来的消息不为空
                        if (usermsg != null && usermsg.length() > 0) {
                            if (usermsg.equals("exit")) {
                                taMsg.append("用户离开~\n");
                                break;
                            }
                            dao=new AccountDao();
                            account = dao.findAccount(usermsg);

                            if (account!=null) {
                                out.println(account);
                            } else {
                                out.println("该账户不存在");
                            }
                        }
                    }

                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }

        }
    }

    public static void main(String[] args) {
        new Server();
    }
}

【在此粘贴服务器端运行结果的截图(注意:客户端完成后,再把服务器端的运行结果的截图在此粘贴)】

(d)学号尾号为8和0的学生设计:当服务器收到客户端发来的一个账户名中包含的字符串时(例如,李),调用AccountDao对象的 findCount(String accountName)方法在账户表中查询所有账户名中包含给定字符串的账户数量,并把查询到的结果返回给客户端,如果客户端发来的账户名是“exit”,则关闭和该客户端连接。

【在此粘贴源程序(注意:源程序不能是截图)】

【在此粘贴服务器端运行结果的截图(注意:客户端程序完成后,再把服务器端的运行结果的截图在此粘贴)】

三、创建客户端程序(20分)

  1. 在client.view包中创建客户(Client)端主程序。

  1. 学号尾号为2、4、6的学生设计:客户端输入要查询的账号,然后利用套接字向服务器发送该账号,接受服务器返回的查询结果,并输出查询结果(如果查询到了满足条件的账号,就把查询到的账号信息显示出来,否则输出“该账号不存在”)。

【在此粘贴源程序(注意:源程序不能是截图)】

public class Client extends JFrame {
    private JPanel mainPan = new JPanel();
    private JLabel lbl_1 = new JLabel("查询账号:");

    protected JTextField tf_1 = new JTextField(10);
    protected JTextArea taMsg = new JTextArea(25, 25);

    protected JButton butStart = new JButton("查询");
    /***/
    BufferedReader in;
    PrintStream out;
    public static int localPort = 8000;
    public static String localIP = "127.0.0.1";
    public Socket socket;
    Account account;
    String msg;

    public Client() {
        init();
    }

    private void init() {
        mainPan.setLayout(new FlowLayout());
        mainPan.add(lbl_1);
        mainPan.add(tf_1);
        mainPan.add(butStart);
        mainPan.add(taMsg);

        butStart.addActionListener(new btnHandlerStart());

        this.setTitle("客户端");
        this.add(mainPan, BorderLayout.CENTER);
        this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        this.setPreferredSize(new Dimension(400, 400));
        this.pack();
        this.setVisible(true);
    }

    private class btnHandlerStart implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            // 当点击"连接服务器"按钮之后,该按钮被禁用(不可重复点击)。同时"断开服务器按钮"被恢复使用
            linkServer();   // 连接服务器
            Thread acceptThread = new Thread(new ReceiveRunnable());
            acceptThread.start();
        }
    }

    // TODO 连接服务器的方法
    public void linkServer() {
        try {
            socket = new Socket(localIP, localPort);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    // TODO 接收服务器消息的线程关联类
    private class ReceiveRunnable implements Runnable {
        public void run() {
            try {
                in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                out = new PrintStream(socket.getOutputStream());
                out.println(tf_1.getText());
                out.flush();
                while (true) {
                    msg = in.readLine();
                    taMsg.setText(msg);
                }
            } catch (Exception e) {
            }
        }
    }

    // TODO 窗口关闭的动作事件监听处理类
    // 当用户点击 "x" 离开窗口时,也会向服务器发送 bye 请求,目的是为了同步更新数据。
    private class WindowHandler extends WindowAdapter {
        @Override
        public void windowClosing(WindowEvent e) {
            out.println("exit");
        }
    }

    public static void main(String[] args) {
        new Client();
    }
}

【在此粘贴客户端运行结果的截图(注意:服务器端程序完成后,再把客户端的运行结果的截图在此粘贴)截图要有两种查询结果,即查询到满足条件记录的结果和不满足条件的结果】

  1. 学号尾号为8,0的学生设计:客户端输入要查询的账户名所包含的字符串,然后利用套接字向服务器发送该字符串,接受服务器返回的查询结果,并输出查询结果)。

【在此粘贴源程序(注意:源程序不能是截图)】

【在此粘贴客户端运行结果的截图(注意:服务器端程序完成后,再把客户端的运行结果的截图在此粘贴)截图要有两种查询结果,查询到的满足条件的记录数要大于0】

posted @ 2022-11-17 20:27  ThinkStu  阅读(40)  评论(0)    收藏  举报