亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

實戰 - 業務實現 2

這一小節,是 Java 基礎教程的最后一節,很感謝大家能夠堅持看到這里。本小節我將帶領大家優化用戶鑒權服務,并完成商品模塊的實現。為了檢驗大家的學習成果,分類模塊的實現將交給大家自行來完成。

1. 用戶密碼加密

上一小節的最后,我們提到用戶鑒權服務是需要優化的。大家可以看到我們數據庫存儲的是明文密碼,這是非常不推薦的,在實際的項目中,明文存儲用戶的密碼是非常不安全的,也是不負責任的行為。我們在設計 imooc_user表時,給password設置的類型為固定長度類型char(32),32 位正好是MD5算法加密后的長度。

本系統使用 MD5 算法對密碼進行加密,下面在 util包下新建一個 MD5Util類并寫入如下內容(可直接復制粘貼代碼):

package com.colorful.util;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MD5Util {

    public static String md5(String source) {
        StringBuilder stringBuilder = new StringBuilder();
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            // 將一個byte數組進行加密操作,返回的是一個加密的byte數組,二進制的哈西計算,md5加密的第一步
            byte[] digest = messageDigest.digest(source.getBytes());
            for (byte b : digest) {
                int result = b & 0xff;
                // 將得到的int類型的值轉化為16進制的值
                String hexString = Integer.toHexString(result);
                if (hexString.length() < 2) {
                    //系統會自動把0省略,所以添加0
                    stringBuilder.append("0");
                }
                stringBuilder.append(hexString);
            }
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return stringBuilder.toString();
    }

    public static void main(String[] args) {
        String password = "123456";
        String s = MD5Util.md5(password);
        System.out.println(s);
    }

}

在主方法中,我們編寫了調用md5()加密方法的邏輯,運行代碼,屏幕上得到123456加密后的字符串:

e10adc3949ba59abbe56e057f20f883e

下面我們將imooc_user表中存儲的明文密碼,更新為上面的結果,大家可以使用SQL語句來進行更新:

 UPDATE `imooc_user` SET `password` = 'e10adc3949ba59abbe56e057f20f883e' WHERE `id` = 1;

這里我直接通過 MySQL 客戶端進行更新,如下是操作過程的截圖:
圖片描述
數據庫存儲的密碼更新后,我們就無法直接通過原本的驗證邏輯來驗證密碼了,需要修改用戶鑒權邏輯 —— 將用戶輸入的密碼加密后,再與數據庫的密碼進行對比。那么這段邏輯要寫在service層還是dao層呢?答案肯定是service層,此時service層用于處理業務的特性得到了體現,修改UserService下的login方法,將參數password加密:

public User login(String username, String password) {
    String md5Password = MD5Util.md5(password);
    return userDAO.selectByUserNameAndPassword(username, md5Password);
}

再次啟動應用程序,驗證改寫的邏輯是否正確:

至此,我們就完成了對用戶鑒權服務的優化。

2. 控制臺(儀表盤)

用戶登錄成功后,應該顯示控制臺面板,我們下面稱之為儀表盤,它主要包含 3 個選項,分別是管理商品、管理分類以及退出登錄。下面我們編寫一個dashboard()方法,該方法用來打印儀表盤的相關操作提示,以及根據用戶的輸入來執行相應的操作。如下是部分代碼:

/**
 * 主流程方法
 */
public static void run() {
    // ... 已省略前面的鑒權代碼
    // 登錄成功后,跳轉到儀表盤頁面
    dashboard();
}

/**
 * 儀表盤操作
 */
private static void dashboard() {
    Scanner scanner = new Scanner(System.in);
    int code1 = 0, code2 = 0;
    while (true) {
        printDashboardTips();
        code1 = scanner.nextInt();
        if (code1 == 0) {
            System.out.println("您已退出登錄");
            break;
        }
        switch (code1) {
            case 1:
                System.out.println("正在查詢商品列表...");
                // TODO 實現商品模塊
                break;
            case 2:
                System.out.println("正在查詢分類列表...");
                // TODO 實現分類模塊
                break;
            default:
                System.out.println("不存在您輸入的選項,請重新輸入");
                break;
        }
    }
}

/**
 * 輸出儀表盤操作提示
 */
private static void printDashboardTips() {
    System.out.println("請輸入對應數字以進行操作:");
    System.out.println("(1. 管理商品 | 2. 管理分類 | 0. 退出登錄)");
}

我們把向控制臺輸出的操作提示,封裝成了一個方法printDashboardTips(),這樣使代碼更簡潔易讀。

dashboard()方法內部,實例化了一個Scanner類,初始化的code1變量接收用戶的輸入,根據輸入的數值用來操作儀表盤,關于code2變量,我們將在實現商品模塊代碼的時候使用。緊接著有一個while循環,其條件始終為true,當用戶輸入的code登錄 0 的時候,就跳出循環,也就是退出了應用程序。

完成上面的代碼編寫后,我們啟動應用程序,來驗證一下:

至此,我們已實現展示儀表盤以及退出登錄的代碼編寫。

3. 商品模塊實現

3.1 商品管理主流程

當用戶輸入的code1變量為數字 1 的時候,就要顯示商品管理相關的操作。我們再封裝一個printGoodsListTips()方法,用于打印商品管理模塊的相關操作提示。方法的代碼如下:

/**
 * 輸出商品列表頁操作提示
 */
private static void printGoodsListTips() {
    System.out.println("請輸入對應數字以進行操作:");
    System.out.println("(1. 新增商品 | 2. 編輯商品 | 3. 查看商品詳情 | 4. 刪除商品 | 5. 搜索商品 | 6. 按分類查詢商品 | 0. 返回上一級菜單)");
}

向屏幕打印這些提示后,下面還是一個條件始終為truewhile循環,當用戶輸入的code登錄 0 的時候,就跳出當前層循環,也就是返回上一級儀表盤的菜單。

已知了商品管理模塊的所有操作,下面我們在switch(code1)case 1條件分支加入如下邏輯代碼(部分偽代碼):

case 1:
    while (true) {
        System.out.println("正在查詢商品列表...");
        // TODO 查詢并顯示商品列表
        printGoodsListTips();
        code2 = scanner.nextInt();
        if (code2 == 0) {
            // 返回上一級,即跳出本層循環
            System.out.println("返回上一級");
            break;
        }
        switch (code2) {
            case 1:
                System.out.println("新增商品");
                break;
            case 2:
                System.out.println("編輯商品");
                break;
            case 3:
                System.out.println("商品詳情");
                break;
            case 4:
                System.out.println("刪除商品");
                break;
            case 5:
                System.out.println("搜索商品");
                break;
            case 6:
                System.out.println("按分類查詢");
                break;
            default:
                System.out.println("不存在您輸入的選項,請重新輸入");
        }
    }
    break;

上面我們提到,code2變量用于接收用戶對于管理商品操作的輸入,此處又是一個switch case結構,每一個條件分支,都對應到用戶輸入的數字,如果用戶輸入的數字找不到對應的分支,那么就重復執行循環體中的代碼。

接下來我們就要實現這些操作。

3.2 查詢商品列表

dao包下新建一個GoodsDAO類,并寫入一下內容:

package com.colorful.dao;

import com.colorful.model.Goods;
import com.colorful.util.JDBCUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

public class GoodsDAO {

    private Connection connection = null;
    private PreparedStatement preparedStatement = null;
    private ResultSet resultSet = null;
    boolean executeResult;
    
    public List<Goods> selectGoodsList() {
        List<Goods> goodsList = new ArrayList<>();
        try {
            // 獲得鏈接
            connection = JDBCUtil.getConnection();
            // 編寫 SQL 語句
            String sql = "SELECT `id`, `name`, `price` FROM `imooc_goods` where `delete_time` is null";
            // 預編譯 SQL
            preparedStatement = connection.prepareStatement(sql);
            resultSet = preparedStatement.executeQuery();
            while (resultSet.next()) {
                Goods goods = new Goods();
                goods.setId(resultSet.getInt("id"));
                goods.setName(resultSet.getString("name"));
                goods.setPrice(resultSet.getDouble("price"));
                goodsList.add(goods);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 釋放資源
            JDBCUtil.release(resultSet, preparedStatement, connection);
        }
        return goodsList;
    }

}

selectGoodsList()方法就用于查詢商品列表(由于數據量不大,此處我沒有對列表數據進行分頁查詢,大家也可以自行加入)。

service包下新建GoodsService,并調用dao層下封裝好的方法:

package com.colorful.service;

import com.colorful.dao.GoodsDAO;
import com.colorful.model.Goods;

import java.util.List;

public class GoodsService {

    private final GoodsDAO goodsDAO = new GoodsDAO();

    /**
     * 獲取商品列表
     * @return 商品列表
     */
    public List<Goods> getGoodsList() {
        return goodsDAO.selectGoodsList();
    }

}

這樣,我們就完成了查詢商品列表的服務層代碼編寫。

3.3 刪除商品

新增商品、刪除商品、查看商品詳情等功能都是簡單的SQL語句,這里不再具體寫出實現,大家可以參考源碼自行實現。但關于刪除商品,我要特殊說明一下。對于實際的項目,往往不用對數據執行DELETE操作,對于數據的刪除往往是更新操作,這也是我們設置了一個公用字段delete_time的意義,當這個delete_time字段不為null的時候,才會被查詢出來。在GoodsDAO類下,新增如下方法:

public boolean deleteGoodsById(Integer id) {
    try {
        // 獲得鏈接
        connection = JDBCUtil.getConnection();
        // 編寫 SQL 語句
        String sql = "UPDATE `imooc_goods` set `delete_time` = ? WHERE id = ?";
        // 預編譯 SQL
        preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setTimestamp(1, new Timestamp(System.currentTimeMillis()));
        preparedStatement.setInt(2, id);
        executeResult = preparedStatement.execute();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // 釋放資源
        JDBCUtil.release(preparedStatement, connection);
    }
    return executeResult;
}

大家可以看到,我們的代碼實現沒有使用DELETE語句,而是使用了UPDATE語句,更新了指定id記錄的delete_time字段為系統當前時間。

dao層方法編寫完成后,就可以在service層調用該方法了:

/**
 * 刪除商品
 * @param id 商品id
 */
public void removeGoodsById(Integer id) {
    goodsDAO.deleteGoodsById(id);
}

3.4 搜索商品

除了刪除商品的實現,搜索商品的實現我們也要特殊講解一下。上面我們提到,由于商品的數據量不大,在查詢商品列表時,沒有使用LIMIT關鍵字進行分頁查詢。正是由于數據量不大的原因,對于搜索商品,我們沒有使用LIKE關鍵字進行模糊查詢,而是使用Stream API直接對商品列表進行過濾,希望通過這里的實現來協助讓大家理解Stream API,直接在GoodsService下添加如下方法:

/**
 * 根據商品名稱搜索商品
 * @param name 商品名稱
 * @return 商品列表
 */
public List<Goods> searchGoodsByName(String name) {
    List<Goods> goodsList = this.getGoodsList();
    return goodsList.stream().filter(
            goods -> goods.getName().contains(name)
    ).collect(Collectors.toList());
}

該方法先是調用了getGoodsList()方法獲取了商品列表,然后使用Stream API中的filter()中間操作,對商品進行過濾,filter()接收一個斷言型接口,由于是一個函數式接口,我們可通過lambda表達式來進行表示。最后調用collect()終止操作,將流轉化為列表。

服務層的接口完成后,大家就可以在對應的case分支編寫的具體的邏輯了,每個分支的邏輯大體相同,主要是接收用戶的輸入,以及服務層方法的調用。大家可參考github倉庫的源碼來補全自己的代碼。

4. 作業 - 分類模塊實現

上面,我們已經實現了較為復雜的商品模塊,對于分類模塊的實現也大同小異,甚至更加簡單,剩下的功能 ——分類的增刪改查就交由同學們自行實現。希望大家能夠按照我們項目的架構,將合理的代碼寫到合適的位置,對每個功能點都要將細節考慮周全,這將有助于降低大家后續對框架學習的上手成本。

5. 小結

通過實戰階段的學習,我們知道了數據表中的密碼字段,是不能夠明文存儲的,通常使用一些加密算法進行加密,也復習了switch case條件結構的使用,對于商品模糊查詢,我們使用了 Java 8 中的 Stream API。

中間還講解了項目的分層技術、MySQL 的增刪改查操作、JDBC API 的封裝與使用以及Scanner類的使用等知識,實際的項目基本不會使用Scanner來與用戶進行交互,都是通過優美的前端界面與用戶進行交互的,建議大家可以去看看Lin CMS的示例demo,它是一個能夠達到企業級應用標準的內容管理系統開發框架。

當然,想要上手使用Lin CMS,大家還有很長的一段路要走,但是請記住,莫要浮空建高樓,Java 的基礎知識在任何時候都是不能忽視的,希望大家反復學習。Java 基礎的學習到此也就結束了,再次感謝大家能夠堅持看完!