作為 Java 學習者從「寫代碼」邁向「做項目」的關鍵一步,單機版書店管理系統涵蓋了面向對象設計、分層架構、設計模式、Swing GUI、文件持久化等核心技術點。本文將系統地梳理該項目的架構設計、功能模塊、核心代碼實現與開發心得,為初中級開發者提供完整的參考藍圖。
本項目旨在通過一個完整的桌面應用開發過程,達成以下 12 項核心訓練目標:
| 模塊 | 核心功能 | 關鍵業務規則 |
|---|---|---|
| 用戶管理 | 註冊、信息修改、刪除、查詢 | 固定權限控制,admin 擁有所有權限 |
| 圖書管理 | 基本信息增刪改查 | 圖書為進貨/銷售的前置條件 |
| 進貨管理 | 進貨單錄入(主從表)、庫存自動增加 | 進貨圖書必須存在;庫存同步更新 |
| 銷售管理 | 銷售單錄入(主從表)、庫存自動扣減 | 銷售數量不能超過庫存;庫存同步更新 |
| 庫存查詢 | 明細查看、多條件篩選 | 支持按庫存編號、圖書、數量區間查詢 |
cn.hncu.bookStore ├── ui/ # 表現層 - Swing 界面面板 ├── business/ # 邏輯層 │ ├── ebi/ # 業務接口 │ ├── ebo/ # 業務實現 │ └── factory/ # 工廠類 ├── dao/ # 數據層 │ ├── dao/ # 數據接口 │ ├── impl/ # 數據實現 │ └── factory/ # 工廠類 ├── vo/ # 值對象 ├── util/ # 工具類 └── common/ # 公共常量/枚舉
public class BookStore extends JFrame {
public BookStore() {
super("書店管理系統---CHX---聯系QQ——619699629");
initComponents();
this.setContentPane(new LoginPanel(this)); // 初始顯示登錄面板
this.setResizable(false);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
// 居中顯示
Dimension scr = Toolkit.getDefaultToolkit().getScreenSize();
this.setLocation(scr.width / 5, scr.height / 5);
}
// 菜單項切換面板的核心邏輯
protected void jMenuItemUserActionPerformed(ActionEvent evt) {
this.setContentPane(new ListPanel(this));
this.validate();
}
// 其他模塊切換邏輯類似...
}
關鍵點:主框架持有所有菜單項引用,登錄成功後根據用戶類型動態 setEnabled(true/false) 控制菜單可用性;面板切換使用 setContentPane() + validate() 實現無閃爍切換。
邏輯層接口
public interface LoginEbi {
String login(String name, String pwd); // 成功返回 null,失敗返回錯誤信息
}
邏輯層實現
public class LoginEbo implements LoginEbi {
@Override
public String login(String name, String pwd) {
UserEbi ebi = UserEbiFactory.getUserEbi();
UserModel user = ebi.getUserByName(name);
if (user == null) return "對不起,該用戶不存在!";
if (!user.getPwd().equals(pwd)) return "您輸入的密碼不正確!";
return null;
}
}
表現層登錄面板
public class LoginPanel extends JPanel {
private BookStore mainFrame;
private void btnLoginActionPerformed(ActionEvent evt) {
String name = tfdName.getText();
String pwd = new String(tfpPwd.getPassword());
// 硬編碼 admin 測試賬號
if ("admin".equals(name) && "123456".equals(pwd)) {
enableAllMenus();
mainFrame.setContentPane(new LoginWelcomePanel());
mainFrame.validate();
return;
}
// 正常業務流程
String result = LoginEbiFactory.getLoginEbi().login(name, pwd);
if (result != null) {
JOptionPane.showMessageDialog(mainFrame, result);
return;
}
// 根據用戶類型啟用對應菜單
UserModel user = UserEbiFactory.getUserEbi().getUserByName(name);
enableMenusByUserType(user.getType());
mainFrame.setContentPane(new LoginWelcomePanel());
mainFrame.validate();
}
}
值對象設計
public class StockModel implements Serializable {
private String uuid; // 庫存編號
private String bookUuid; // 圖書編號
private int sumNum; // 庫存數量
private String bookName; // 顯示用書名(冗餘字段)
// getter/setter、equals/hashCode、toString...
}
public class StockQueryModel extends StockModel {
private int sumNum2; // 最大庫存數量(區間查詢用)
}
文件 DAO 實現
public class StockDaoImpl implements StockDao {
private final String FILE_NAME = "Stock.txt";
@Override
public boolean create(StockModel stock) {
List<StockModel> lists = FileIoUtil.readFormFile(FILE_NAME);
for (StockModel model : lists) {
if (model.getUuid().equals(stock.getUuid())) return false;
}
lists.add(stock);
FileIoUtil.write2file(lists, FILE_NAME);
return true;
}
@Override
public List<StockModel> getByCondition(StockQueryModel sqm) {
List<StockModel> lists = FileIoUtil.readFormFile(FILE_NAME);
List<StockModel> results = new ArrayList<>();
for (StockModel model : lists) {
if (!StringComparison.stringEquals(model.getUuid(), sqm.getUuid())) continue;
if (!StringComparison.stringEquals(model.getBookUuid(), sqm.getBookUuid())) continue;
if (sqm.getSumNum() > 0 && sqm.getSumNum() > model.getSumNum()) continue;
if (sqm.getSumNum2() > 0 && sqm.getSumNum2() < model.getSumNum()) continue;
results.add(model);
}
return results;
}
// update、getSingle、getAl 類似...
}
邏輯層薄封裝
public class StockEbo implements StockEbi {
private StockDao dao = StockDaoFactory.getStockDao();
@Override public List<StockModel> getAll() { return dao.getAl(); }
@Override public List<StockModel> getByCondition(StockQueryModel sqm) { return dao.getByCondition(sqm); }
}
進貨邏輯核心
public class InMainEbo implements InMainEbi {
@Override
public boolean create(InMainModel inMain, List<InDetailModel> inDetails) {
// 1. 補全主單信息
String inUuid = uuidDao.getNextUuid(UuidModelConstance.IN_MAIN);
inMain.setUuid(inUuid);
inMain.setInDate(System.currentTimeMillis());
inMainDao.create(inMain);
// 2. 處理明細 & 3. 入庫更新庫存
for (InDetailModel detail : inDetails) {
detail.setUuid(uuidDao.getNextUuid(UuidModelConstance.IN_DETAIL));
detail.setInId(inUuid);
double sumMoney = detail.getSumNum() * bookEbi.getSingle(detail.getBookId()).getInPrice();
detail.setSumMoney(sumMoney);
inDetailDao.create(detail);
// 關鍵:調用庫存入庫邏輯
putInStock(detail.getBookId(), detail.getSumNum());
}
return true;
}
private void putInStock(String bookId, int sumNum) {
StockDao dao = StockDaoFactory.getStockDao();
StockQueryModel sqm = new StockQueryModel();
sqm.setBookUuid(bookId);
List<StockModel> lists = dao.getByCondition(sqm);
if (lists.isEmpty()) {
// 新建庫存記錄
StockModel stock = new StockModel();
stock.setUuid(UuidDaoFactory.getUuidDao().getNextUuid(UuidModelConstance.STOCK));
stock.setBookName(BookEbiFactory.getBookEbi().getSingle(bookId).getName());
stock.setBookUuid(bookId);
stock.setSumNum(sumNum);
dao.create(stock);
} else {
// 累加庫存
StockModel stock = lists.get(0);
stock.setSumNum(stock.getSumNum() + sumNum);
dao.update(stock);
}
}
}
銷售邏輯核心(含庫存預檢)
public class OutMainEbo implements OutMainEbi {
@Override
public boolean create(OutMainModel outMain, List<OutDetailModel> outDetails) {
// 1. 庫存預檢:防止超賣
StockDao stockDao = StockDaoFactory.getStockDao();
for (OutDetailModel detail : outDetails) {
StockQueryModel sqm = new StockQueryModel();
sqm.setBookUuid(detail.getBookId());
List<StockModel> lists = stockDao.getByCondition(sqm);
if (lists.isEmpty() || lists.get(0).getSumNum() < detail.getSumNum()) {
JOptionPane.showMessageDialog(null, "庫存不足,銷售失敗!");
return false;
}
}
// 2. 保存銷售主從單
String outUuid = uuidDao.getNextUuid(UuidModelConstance.OUT_MAIN);
outMain.setUuid(outUuid);
outMain.setOutDate(System.currentTimeMillis());
outMainDao.create(outMain);
for (OutDetailModel detail : outDetails) {
detail.setUuid(uuidDao.getNextUuid(UuidModelConstance.OUT_DETAIL));
detail.setOutId(outUuid);
double sumMoney = detail.getSumNum() * bookEbi.getSingle(detail.getBookId()).getOutPrice();
detail.setSumMoney(sumMoney);
outDetailDao.create(detail);
// 3. 扣減庫存
reduceStock(detail.getBookId(), detail.getSumNum());
}
return true;
}
}
初始狀態:僅「注銷」菜單可用,其餘模塊菜單置灰
提示:若用戶文件被刪除,可使用 admin/123456 登錄恢復
所有模塊菜單點亮,擁有完整操作權限
僅「庫存」菜單可用,體現最小權限原則
僅「銷售」菜單可用
「這可以說是自己的第一個項目吧。寫到現在也有10多天了,自己比較享受寫項目的這個過程... 現在對分包啊,取名字啊,值對象封裝啊,工廠方法,面向接口編程啊,表現層,邏輯層,數據層啊,這些以前用都沒用過的,那時候完全不懂得怎麼回事,不過在自己寫了之後,感覺嘛、也就那麼回事。這種項目開發,還是得自己多練練手,寫得多了,自己就熟了。以後就是手到擒來。」
完整源碼已上傳百度網盤:
鏈接: https://pan.baidu.com/s/1c2ob5r6 密碼: u6jv
這個單機版書店管理系統雖然功能不復雜,但完整踐行了「分層、分模塊、分包、面向接口、工廠解耦、VO 封裝、文件持久化、Swing 界面」的工程化開發流程。對於從教學代碼走向項目代碼的開發者,它是一個極佳的練手項目——代碼量適中、架構清晰、覆蓋面廣、易於擴展。建議學習者在跑通源碼基礎上,嘗試:
通過不斷重構優化,真正內化「工程化思維」,這才是項目練習的最大價值。