實戰(zhàn) - 業(yè)務(wù)實現(xiàn) 2
這一小節(jié),是 Java 基礎(chǔ)教程的最后一節(jié),很感謝大家能夠堅持看到這里。本小節(jié)我將帶領(lǐng)大家優(yōu)化用戶鑒權(quán)服務(wù),并完成商品模塊的實現(xiàn)。為了檢驗大家的學習成果,分類模塊的實現(xiàn)將交給大家自行來完成。
1. 用戶密碼加密
上一小節(jié)的最后,我們提到用戶鑒權(quán)服務(wù)是需要優(yōu)化的。大家可以看到我們數(shù)據(jù)庫存儲的是明文密碼,這是非常不推薦的,在實際的項目中,明文存儲用戶的密碼是非常不安全的,也是不負責任的行為。我們在設(shè)計 imooc_user
表時,給password
設(shè)置的類型為固定長度類型char(32)
,32 位正好是MD5
算法加密后的長度。
本系統(tǒng)使用 MD5
算法對密碼進行加密,下面在 util
包下新建一個 MD5Util
類并寫入如下內(nèi)容(可直接復制粘貼代碼):
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數(shù)組進行加密操作,返回的是一個加密的byte數(shù)組,二進制的哈西計算,md5加密的第一步
byte[] digest = messageDigest.digest(source.getBytes());
for (byte b : digest) {
int result = b & 0xff;
// 將得到的int類型的值轉(zhuǎn)化為16進制的值
String hexString = Integer.toHexString(result);
if (hexString.length() < 2) {
//系統(tǒng)會自動把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);
}
}
在主方法中,我們編寫了調(diào)用md5()
加密方法的邏輯,運行代碼,屏幕上得到123456
加密后的字符串:
e10adc3949ba59abbe56e057f20f883e
下面我們將imooc_user
表中存儲的明文密碼,更新為上面的結(jié)果,大家可以使用SQL
語句來進行更新:
UPDATE `imooc_user` SET `password` = 'e10adc3949ba59abbe56e057f20f883e' WHERE `id` = 1;
這里我直接通過 MySQL
客戶端進行更新,如下是操作過程的截圖:
數(shù)據(jù)庫存儲的密碼更新后,我們就無法直接通過原本的驗證邏輯來驗證密碼了,需要修改用戶鑒權(quán)邏輯 —— 將用戶輸入的密碼加密后,再與數(shù)據(jù)庫的密碼進行對比。那么這段邏輯要寫在service
層還是dao
層呢?答案肯定是service
層,此時service
層用于處理業(yè)務(wù)的特性得到了體現(xiàn),修改UserService
下的login
方法,將參數(shù)password
加密:
public User login(String username, String password) {
String md5Password = MD5Util.md5(password);
return userDAO.selectByUserNameAndPassword(username, md5Password);
}
再次啟動應(yīng)用程序,驗證改寫的邏輯是否正確:

至此,我們就完成了對用戶鑒權(quán)服務(wù)的優(yōu)化。
2. 控制臺(儀表盤)
用戶登錄成功后,應(yīng)該顯示控制臺面板,我們下面稱之為儀表盤,它主要包含 3 個選項,分別是管理商品、管理分類以及退出登錄。下面我們編寫一個dashboard()
方法,該方法用來打印儀表盤的相關(guān)操作提示,以及根據(jù)用戶的輸入來執(zhí)行相應(yīng)的操作。如下是部分代碼:
/**
* 主流程方法
*/
public static void run() {
// ... 已省略前面的鑒權(quán)代碼
// 登錄成功后,跳轉(zhuǎn)到儀表盤頁面
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 實現(xiàn)商品模塊
break;
case 2:
System.out.println("正在查詢分類列表...");
// TODO 實現(xiàn)分類模塊
break;
default:
System.out.println("不存在您輸入的選項,請重新輸入");
break;
}
}
}
/**
* 輸出儀表盤操作提示
*/
private static void printDashboardTips() {
System.out.println("請輸入對應(yīng)數(shù)字以進行操作:");
System.out.println("(1. 管理商品 | 2. 管理分類 | 0. 退出登錄)");
}
我們把向控制臺輸出的操作提示,封裝成了一個方法printDashboardTips()
,這樣使代碼更簡潔易讀。
在dashboard()
方法內(nèi)部,實例化了一個Scanner
類,初始化的code1
變量接收用戶的輸入,根據(jù)輸入的數(shù)值用來操作儀表盤,關(guān)于code2
變量,我們將在實現(xiàn)商品模塊代碼的時候使用。緊接著有一個while
循環(huán),其條件始終為true
,當用戶輸入的code
登錄 0
的時候,就跳出循環(huán),也就是退出了應(yīng)用程序。
完成上面的代碼編寫后,我們啟動應(yīng)用程序,來驗證一下:

至此,我們已實現(xiàn)展示儀表盤以及退出登錄的代碼編寫。
3. 商品模塊實現(xiàn)
3.1 商品管理主流程
當用戶輸入的code1
變量為數(shù)字 1
的時候,就要顯示商品管理相關(guān)的操作。我們再封裝一個printGoodsListTips()
方法,用于打印商品管理模塊的相關(guān)操作提示。方法的代碼如下:
/**
* 輸出商品列表頁操作提示
*/
private static void printGoodsListTips() {
System.out.println("請輸入對應(yīng)數(shù)字以進行操作:");
System.out.println("(1. 新增商品 | 2. 編輯商品 | 3. 查看商品詳情 | 4. 刪除商品 | 5. 搜索商品 | 6. 按分類查詢商品 | 0. 返回上一級菜單)");
}
向屏幕打印這些提示后,下面還是一個條件始終為true
的while
循環(huán),當用戶輸入的code
登錄 0
的時候,就跳出當前層循環(huán),也就是返回上一級儀表盤的菜單。
已知了商品管理模塊的所有操作,下面我們在switch(code1)
的case 1
條件分支加入如下邏輯代碼(部分偽代碼):
case 1:
while (true) {
System.out.println("正在查詢商品列表...");
// TODO 查詢并顯示商品列表
printGoodsListTips();
code2 = scanner.nextInt();
if (code2 == 0) {
// 返回上一級,即跳出本層循環(huán)
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
結(jié)構(gòu),每一個條件分支,都對應(yīng)到用戶輸入的數(shù)字,如果用戶輸入的數(shù)字找不到對應(yīng)的分支,那么就重復執(zhí)行循環(huán)體中的代碼。
接下來我們就要實現(xiàn)這些操作。
3.2 查詢商品列表
在dao
包下新建一個GoodsDAO
類,并寫入一下內(nèi)容:
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";
// 預(yù)編譯 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()
方法就用于查詢商品列表(由于數(shù)據(jù)量不大,此處我沒有對列表數(shù)據(jù)進行分頁查詢,大家也可以自行加入)。
在service
包下新建GoodsService
,并調(diào)用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();
}
}
這樣,我們就完成了查詢商品列表的服務(wù)層代碼編寫。
3.3 刪除商品
新增商品、刪除商品、查看商品詳情等功能都是簡單的SQL
語句,這里不再具體寫出實現(xiàn),大家可以參考源碼自行實現(xiàn)。但關(guān)于刪除商品,我要特殊說明一下。對于實際的項目,往往不用對數(shù)據(jù)執(zhí)行DELETE
操作,對于數(shù)據(jù)的刪除往往是更新操作,這也是我們設(shè)置了一個公用字段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 = ?";
// 預(yù)編譯 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;
}
大家可以看到,我們的代碼實現(xiàn)沒有使用DELETE
語句,而是使用了UPDATE
語句,更新了指定id
記錄的delete_time
字段為系統(tǒng)當前時間。
dao
層方法編寫完成后,就可以在service
層調(diào)用該方法了:
/**
* 刪除商品
* @param id 商品id
*/
public void removeGoodsById(Integer id) {
goodsDAO.deleteGoodsById(id);
}
3.4 搜索商品
除了刪除商品的實現(xiàn),搜索商品的實現(xiàn)我們也要特殊講解一下。上面我們提到,由于商品的數(shù)據(jù)量不大,在查詢商品列表時,沒有使用LIMIT
關(guān)鍵字進行分頁查詢。正是由于數(shù)據(jù)量不大的原因,對于搜索商品,我們沒有使用LIKE
關(guān)鍵字進行模糊查詢,而是使用Stream API
直接對商品列表進行過濾,希望通過這里的實現(xiàn)來協(xié)助讓大家理解Stream API
,直接在GoodsService
下添加如下方法:
/**
* 根據(jù)商品名稱搜索商品
* @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());
}
該方法先是調(diào)用了getGoodsList()
方法獲取了商品列表,然后使用Stream API
中的filter()
中間操作,對商品進行過濾,filter()
接收一個斷言型接口,由于是一個函數(shù)式接口,我們可通過lambda
表達式來進行表示。最后調(diào)用collect()
終止操作,將流轉(zhuǎn)化為列表。
服務(wù)層的接口完成后,大家就可以在對應(yīng)的case
分支編寫的具體的邏輯了,每個分支的邏輯大體相同,主要是接收用戶的輸入,以及服務(wù)層方法的調(diào)用。大家可參考github
倉庫的源碼來補全自己的代碼。
4. 作業(yè) - 分類模塊實現(xiàn)
上面,我們已經(jīng)實現(xiàn)了較為復雜的商品模塊,對于分類模塊的實現(xiàn)也大同小異,甚至更加簡單,剩下的功能 ——分類的增刪改查就交由同學們自行實現(xiàn)。希望大家能夠按照我們項目的架構(gòu),將合理的代碼寫到合適的位置,對每個功能點都要將細節(jié)考慮周全,這將有助于降低大家后續(xù)對框架學習的上手成本。
5. 小結(jié)
通過實戰(zhàn)階段的學習,我們知道了數(shù)據(jù)表中的密碼字段,是不能夠明文存儲的,通常使用一些加密算法進行加密,也復習了switch case
條件結(jié)構(gòu)的使用,對于商品模糊查詢,我們使用了 Java 8 中的 Stream API
。
中間還講解了項目的分層技術(shù)、MySQL 的增刪改查操作、JDBC API 的封裝與使用以及Scanner
類的使用等知識,實際的項目基本不會使用Scanner
來與用戶進行交互,都是通過優(yōu)美的前端界面與用戶進行交互的,建議大家可以去看看Lin CMS的示例demo
,它是一個能夠達到企業(yè)級應(yīng)用標準的內(nèi)容管理系統(tǒng)開發(fā)框架。
當然,想要上手使用Lin CMS
,大家還有很長的一段路要走,但是請記住,莫要浮空建高樓,Java 的基礎(chǔ)知識在任何時候都是不能忽視的,希望大家反復學習。Java 基礎(chǔ)的學習到此也就結(jié)束了,再次感謝大家能夠堅持看完!