畢業(yè)設(shè)計拆包:從環(huán)境配置到庫存預(yù)警與銷售事務(wù)的完整實現(xiàn))
簡介這是一套面向計算機(jī)相關(guān)專業(yè)學(xué)生與Python初學(xué)者的藥店藥品管理系統(tǒng)完整項目源碼可作為畢業(yè)設(shè)計、課程設(shè)計或自學(xué)練手參考。系統(tǒng)圍繞藥品庫存、銷售記錄、采購計劃與庫存預(yù)警等日常業(yè)務(wù)展開幫助理解數(shù)據(jù)庫設(shè)計、前后端交互與用戶界面搭建的整合思路。壓縮包共392個文件約20.63MB以jpeg、svg、jpg、png等圖片資源為主配合36個py源碼文件、35個ts與16個vue前端組件另有js、json、less及md說明文檔整體呈現(xiàn)前后端分離的工程結(jié)構(gòu)。項目涵蓋數(shù)據(jù)模型、視圖與控制器分層涉及SQLite或MySQL數(shù)據(jù)庫操作、Tkinter或Web框架界面實現(xiàn)、CSV與JSON數(shù)據(jù)導(dǎo)入導(dǎo)出及異常處理等知識點。目前已有120人學(xué)習(xí)下載適合需要完整項目案例、模塊劃分參考與排錯思路的讀者對照研讀。1. 藥店藥品管理系統(tǒng)拆包一份能跑起來的 Python 畢業(yè)設(shè)計長什么樣很多同學(xué)做畢業(yè)設(shè)計時最頭疼的不是寫代碼而是找不到一個結(jié)構(gòu)完整、能跑通、還能講清楚原理的參考項目。這份基于 Python 的藥店藥品管理系統(tǒng)恰好就是沖著這個痛點來的。它把藥品信息管理、庫存預(yù)警、銷售記錄、采購計劃這些藥店日常運營的核心環(huán)節(jié)用一套代碼串了起來適合計算機(jī)相關(guān)專業(yè)做畢業(yè)設(shè)計或課程設(shè)計的同學(xué)直接上手拆解。拿到壓縮包后你會看到python_yaodian-master這個目錄里面包含了源碼、靜態(tài)資源、模板文件以及依賴清單。它不是那種只有幾個函數(shù)的玩具 demo而是一個帶數(shù)據(jù)庫、帶界面、帶業(yè)務(wù)邏輯的完整應(yīng)用。接下來我會按實際拆包順序把環(huán)境配置、數(shù)據(jù)庫設(shè)計、核心模塊實現(xiàn)和常見翻車點逐一講清楚讓你不僅能跑起來還能在答辯時說出個所以然。2. 環(huán)境配置與項目結(jié)構(gòu)從 python 安裝到 vscode 調(diào)試2.1 依賴清單與 Python 版本選擇拿到項目后第一件事不是急著pip install而是先確認(rèn) Python 版本。這類畢業(yè)設(shè)計項目通常是在 Python 3.8 到 3.10 之間開發(fā)的太新的版本反而容易因為第三方庫兼容性問題翻車。我一般會先看requirements.txt里面列出的庫基本決定了你該用哪個版本的解釋器。# 先查看項目根目錄下的依賴文件 cat requirements.txt # 常見輸出示例具體以實際文件為準(zhǔn) # Flask2.0.1 # Flask-SQLAlchemy2.5.1 # PyMySQL1.0.2 # WTForms2.3.3這里的關(guān)鍵是如果requirements.txt里出現(xiàn)了Flask-SQLAlchemy和PyMySQL說明這是一個基于 Flask 的 Web 應(yīng)用數(shù)據(jù)庫用的是 MySQL。如果出現(xiàn)的是tkinter相關(guān)字樣那可能是桌面 GUI 版本。參數(shù)上Flask2.0.1這種鎖定版本號的寫法意味著你不能隨意升級到 Flask 3.x否則SQLAlchemy的初始化方式會報錯。我一般會建一個虛擬環(huán)境把版本嚴(yán)格對齊。# 創(chuàng)建虛擬環(huán)境推薦用 venv python -m venv venv # 激活虛擬環(huán)境Windows venv\Scripts\activate # 激活虛擬環(huán)境macOS/Linux source venv/bin/activate # 安裝依賴建議加國內(nèi)鏡像源加速 pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple邏輯說明虛擬環(huán)境的作用是把項目依賴和系統(tǒng)全局環(huán)境隔離避免不同項目之間的庫版本沖突。-i參數(shù)指定鏡像源能顯著提升下載速度尤其是PyMySQL和Pillow這類包。安裝完成后用pip list核對一下版本確保沒有出現(xiàn)ERROR或版本降級警告。2.2 目錄結(jié)構(gòu)與入口文件定位解壓后進(jìn)入python_yaodian-master你會看到類似src/、static/、templates/、data/這樣的目錄。src/里通常放著app.py或run.py這是整個項目的入口。static/下是 CSS、JavaScript 和圖片templates/里是 HTML 模板。data/可能包含初始的 SQL 腳本或 CSV 數(shù)據(jù)。# 查看項目目錄樹Linux/macOS tree -L 2 # 或者用 find 命令 find . -maxdepth 2 -type d我一般會先打開app.py看它怎么初始化 Flask 應(yīng)用、怎么注冊藍(lán)圖、怎么連接數(shù)據(jù)庫。重點看這幾行# app.py 中常見的初始化代碼 app Flask(__name__) app.config[SQLALCHEMY_DATABASE_URI] mysqlpymysql://root:passwordlocalhost/yaodian app.config[SQLALCHEMY_TRACK_MODIFICATIONS] False db SQLAlchemy(app)參數(shù)說明SQLALCHEMY_DATABASE_URI里的root:password要換成你本地 MySQL 的用戶名和密碼localhost是數(shù)據(jù)庫地址yaodian是數(shù)據(jù)庫名。SQLALCHEMY_TRACK_MODIFICATIONS設(shè)為False是為了關(guān)閉不必要的信號追蹤減少內(nèi)存開銷。如果這里不改啟動時會直接報連接錯誤。2.3 vscode python 環(huán)境配置與調(diào)試啟動在 vscode 里打開項目文件夾后按CtrlShiftP輸入Python: Select Interpreter選中剛才創(chuàng)建的虛擬環(huán)境。然后配置.vscode/launch.json方便直接按 F5 調(diào)試。{ version: 0.2.0, configurations: [ { name: Python: Flask, type: python, request: launch, program: ${workspaceFolder}/src/app.py, env: { FLASK_ENV: development, FLASK_DEBUG: 1 }, args: [], jinja: true } ] }邏輯說明program指向入口文件FLASK_DEBUG1開啟調(diào)試模式代碼修改后會自動重載。jinja設(shè)為true可以支持模板語法高亮。配置好后在終端里執(zhí)行python src/app.py如果看到Running on http://127.0.0.1:5000說明環(huán)境通了。這時候別急著高興數(shù)據(jù)庫還沒建頁面大概率會報Table yaodian.drug doesnt exist。3. 數(shù)據(jù)庫設(shè)計與藥品信息管理模塊從建表到增刪改查3.1 數(shù)據(jù)表結(jié)構(gòu)分析與 SQL 建表藥店系統(tǒng)的核心表通常包括藥品表、庫存表、銷售記錄表、用戶表。藥品表里會有藥品名稱、規(guī)格、生產(chǎn)廠家、批準(zhǔn)文號、單價、庫存數(shù)量、預(yù)警閾值等字段。我一般會先根據(jù)models.py里的類定義反推 SQL 建表語句。# models.py 中常見的藥品模型定義 class Drug(db.Model): __tablename__ drug id db.Column(db.Integer, primary_keyTrue) name db.Column(db.String(100), nullableFalse) spec db.Column(db.String(50)) manufacturer db.Column(db.String(100)) price db.Column(db.Numeric(10, 2)) stock db.Column(db.Integer, default0) warning_threshold db.Column(db.Integer, default10)對應(yīng)的 SQL 建表語句CREATE TABLE drug ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, spec VARCHAR(50), manufacturer VARCHAR(100), price DECIMAL(10, 2), stock INT DEFAULT 0, warning_threshold INT DEFAULT 10 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;參數(shù)說明DECIMAL(10, 2)表示總長度 10 位小數(shù) 2 位適合存金額。utf8mb4字符集能支持中文和特殊符號。warning_threshold是庫存預(yù)警閾值低于這個數(shù)就觸發(fā)提醒。建表時如果用了utf8而不是utf8mb4插入某些生僻字會報錯這是血淚經(jīng)驗。3.2 藥品信息增刪改查的接口實現(xiàn)在 Flask 里增刪改查通常通過路由函數(shù)實現(xiàn)。下面是一個添加藥品的典型寫法app.route(/drug/add, methods[GET, POST]) def add_drug(): if request.method POST: name request.form[name] spec request.form[spec] manufacturer request.form[manufacturer] price request.form[price] stock request.form[stock] warning_threshold request.form[warning_threshold] new_drug Drug(namename, specspec, manufacturermanufacturer, priceprice, stockstock, warning_thresholdwarning_threshold) db.session.add(new_drug) db.session.commit() return redirect(url_for(drug_list)) return render_template(drug_add.html)邏輯說明request.form獲取表單提交的數(shù)據(jù)db.session.add()把對象加入會話db.session.commit()提交事務(wù)。如果中間任何一步出錯比如price傳了空字符串DECIMAL字段會報錯。我一般會在前端加required屬性后端再加一層try...except捕獲異常。try: db.session.commit() except Exception as e: db.session.rollback() flash(添加失敗 str(e))參數(shù)說明rollback()是后悔藥事務(wù)失敗時回滾避免臟數(shù)據(jù)留在數(shù)據(jù)庫里。flash()用于給用戶提示需要在模板里配合get_flashed_messages()使用。3.3 庫存預(yù)警邏輯與查詢優(yōu)化庫存預(yù)警的核心邏輯是查詢所有stock warning_threshold的藥品在首頁或單獨頁面展示。常見做法是用 SQLAlchemy 的過濾查詢warning_drugs Drug.query.filter(Drug.stock Drug.warning_threshold).all()如果藥品數(shù)量多這個查詢會變慢。我一般會加索引CREATE INDEX idx_stock_warning ON drug(stock, warning_threshold);邏輯說明聯(lián)合索引能加速stock warning_threshold這種范圍查詢。但要注意如果warning_threshold經(jīng)常變索引維護(hù)成本也會上升。對于畢業(yè)設(shè)計的數(shù)據(jù)量不加索引也能跑但答辯時能說出索引優(yōu)化思路是個加分項。4. 銷售記錄與采購計劃模塊業(yè)務(wù)邏輯怎么串起來4.1 銷售出庫與庫存扣減的事務(wù)處理銷售模塊不是簡單插一條記錄就完事它涉及庫存扣減。如果扣減失敗銷售記錄也不應(yīng)該寫入。這就需要用事務(wù)包起來。app.route(/sale/add, methods[POST]) def add_sale(): drug_id request.form[drug_id] quantity int(request.form[quantity]) drug Drug.query.get(drug_id) if drug.stock quantity: flash(庫存不足) return redirect(url_for(sale_add)) try: drug.stock - quantity sale Sale(drug_iddrug_id, quantityquantity, sale_timedatetime.now()) db.session.add(sale) db.session.commit() except Exception as e: db.session.rollback() flash(銷售失敗 str(e)) return redirect(url_for(sale_list))邏輯說明先查庫存再扣減再寫銷售記錄三步在一個事務(wù)里。如果扣減后寫記錄失敗rollback()會把庫存加回去。參數(shù)上quantity要做整數(shù)校驗防止傳入負(fù)數(shù)或非數(shù)字導(dǎo)致庫存異常增加。4.2 采購計劃生成與數(shù)據(jù)導(dǎo)出采購計劃通常是基于庫存預(yù)警結(jié)果生成建議采購清單。常見做法是查詢預(yù)警藥品計算建議采購量然后導(dǎo)出 CSV。import csv from io import StringIO app.route(/purchase/plan) def purchase_plan(): warning_drugs Drug.query.filter(Drug.stock Drug.warning_threshold).all() output StringIO() writer csv.writer(output) writer.writerow([藥品名稱, 規(guī)格, 當(dāng)前庫存, 預(yù)警閾值, 建議采購量]) for drug in warning_drugs: suggest drug.warning_threshold * 2 - drug.stock writer.writerow([drug.name, drug.spec, drug.stock, drug.warning_threshold, suggest]) output.seek(0) return Response(output, mimetypetext/csv, headers{Content-Disposition: attachment;filenamepurchase_plan.csv})邏輯說明StringIO在內(nèi)存里模擬文件對象csv.writer寫入數(shù)據(jù)seek(0)回到開頭Response返回 CSV 內(nèi)容。參數(shù)上suggest的計算方式是「預(yù)警閾值 × 2 - 當(dāng)前庫存」這是一個經(jīng)驗公式保證采購后庫存能回到安全線以上。如果藥店有季節(jié)性波動這個公式可以改成基于歷史銷量的加權(quán)平均。4.3 銷售統(tǒng)計與可視化數(shù)據(jù)準(zhǔn)備銷售統(tǒng)計一般按日、按月匯總。SQL 層面用GROUP BY和SUMSELECT DATE(sale_time) AS sale_date, SUM(quantity) AS total_quantity FROM sale GROUP BY DATE(sale_time) ORDER BY sale_date DESC;在 Flask 里把結(jié)果轉(zhuǎn)成 JSON 傳給前端圖表庫from sqlalchemy import func daily_sales db.session.query( func.date(Sale.sale_time).label(date), func.sum(Sale.quantity).label(total) ).group_by(func.date(Sale.sale_time)).all() data [{date: str(item.date), total: int(item.total)} for item in daily_sales] return jsonify(data)參數(shù)說明func.date()提取日期部分func.sum()求和。jsonify返回 JSON 格式前端用 ECharts 或 Chart.js 渲染。注意item.total可能是Decimal類型要轉(zhuǎn)成int或float才能被 JSON 序列化。5. 避坑與常見問題排查那些讓我熬夜的報錯5.1 數(shù)據(jù)庫連接報錯Access denied 與 Unknown database現(xiàn)象啟動時拋出pymysql.err.OperationalError: (1045, Access denied for user rootlocalhost)或(1049, Unknown database yaodian)。原因SQLALCHEMY_DATABASE_URI里的用戶名、密碼或數(shù)據(jù)庫名和本地 MySQL 不一致。很多人改了代碼里的密碼但忘了在 MySQL 里創(chuàng)建對應(yīng)的數(shù)據(jù)庫。解決先用mysql -u root -p登錄執(zhí)行CREATE DATABASE yaodian DEFAULT CHARSET utf8mb4;然后核對app.config里的連接字符串。如果密碼里有特殊字符如或#需要 URL 編碼。5.2 模板渲染失敗TemplateNotFound 與靜態(tài)文件 404現(xiàn)象頁面報jinja2.exceptions.TemplateNotFound: drug_list.html或者 CSS/JS 加載不出來。原因Flask 默認(rèn)在templates/和static/目錄下找文件。如果項目結(jié)構(gòu)是src/templates/但app.py在src/下路徑是對的如果app.py在根目錄就會找不到。解決確認(rèn)app Flask(__name__, template_foldersrc/templates, static_foldersrc/static)是否正確。或者把app.py放到和templates/同級的目錄。靜態(tài)文件 404 通常是url_for(static, filename...)里的路徑寫錯了。5.3 表單提交后數(shù)據(jù)沒進(jìn)庫CSRF 與字段類型不匹配現(xiàn)象點擊保存后頁面刷新但數(shù)據(jù)庫里沒有新記錄也沒有報錯。原因可能是 Flask-WTF 的 CSRF 保護(hù)開啟了但表單里沒加{{ form.csrf_token }}請求被靜默拒絕?;蛘遬rice字段傳了空字符串DECIMAL類型轉(zhuǎn)換失敗但被try...except吞掉了。解決檢查表單模板里是否有 CSRF token或者在開發(fā)階段臨時設(shè)置WTF_CSRF_ENABLED False。對于字段類型在db.session.commit()之前打印一下new_drug.price的值確認(rèn)不是None或空字符串。5.4 庫存扣減出現(xiàn)負(fù)數(shù)并發(fā)與校驗缺失現(xiàn)象兩個銷售請求同時提交庫存從 1 扣成了 -1。原因先查后改的邏輯在并發(fā)下不安全兩個請求都查到了庫存 1都執(zhí)行了扣減。解決用數(shù)據(jù)庫層面的原子操作比如UPDATE drug SET stock stock - 1 WHERE id ? AND stock 1然后檢查rowcount是否為 1。或者用 SQLAlchemy 的with_for_update()加行鎖。畢業(yè)設(shè)計里并發(fā)場景少但答辯時能說出這個坑說明你考慮過生產(chǎn)環(huán)境。5.5 中文亂碼數(shù)據(jù)庫字符集與連接編碼不一致現(xiàn)象藥品名稱顯示成???或亂碼。原因MySQL 數(shù)據(jù)庫、表、連接三處的字符集不統(tǒng)一。常見的是數(shù)據(jù)庫用了latin1或者連接字符串沒指定charsetutf8mb4。解決建庫時指定DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci連接字符串寫成mysqlpymysql://root:passwordlocalhost/yaodian?charsetutf8mb4。已經(jīng)建好的表可以用ALTER TABLE drug CONVERT TO CHARACTER SET utf8mb4;修復(fù)。6. 進(jìn)階技巧用 Flask-Migrate 管理數(shù)據(jù)庫版本與自動化測試6.1 數(shù)據(jù)庫遷移告別手動刪表重建剛開始做畢業(yè)設(shè)計時我改一次模型就手動DROP TABLE再重建數(shù)據(jù)全丟。后來用了 Flask-Migrate才體會到什么叫后悔藥。# 安裝 Flask-Migrate pip install Flask-Migrate # 初始化遷移倉庫 flask db init # 生成遷移腳本 flask db migrate -m add warning_threshold column # 應(yīng)用遷移 flask db upgrade邏輯說明flask db init只執(zhí)行一次生成migrations/目錄。migrate對比模型和數(shù)據(jù)庫的差異生成 Python 腳本。upgrade執(zhí)行腳本更新表結(jié)構(gòu)。參數(shù)上-m后面的描述要寫清楚改了什么方便回滾時定位。如果遷移報錯用flask db downgrade回退到上一個版本。6.2 單元測試用 pytest 驗證核心業(yè)務(wù)邏輯答辯時老師常問「你怎么保證庫存扣減是對的」。這時候如果有單元測試直接跑一遍給他看。# test_sale.py import pytest from app import app, db, Drug, Sale pytest.fixture def client(): app.config[TESTING] True app.config[SQLALCHEMY_DATABASE_URI] sqlite:///:memory: with app.test_client() as client: with app.app_context(): db.create_all() yield client db.drop_all() def test_sale_reduces_stock(client): drug Drug(name阿莫西林, stock10, warning_threshold5) db.session.add(drug) db.session.commit() response client.post(/sale/add, data{drug_id: drug.id, quantity: 3}) assert response.status_code 302 updated_drug Drug.query.get(drug.id) assert updated_drug.stock 7 def test_sale_insufficient_stock(client): drug Drug(name布洛芬, stock2, warning_threshold5) db.session.add(drug) db.session.commit() response client.post(/sale/add, data{drug_id: drug.id, quantity: 5}, follow_redirectsTrue) assert 庫存不足 in response.get_data(as_textTrue)邏輯說明fixture里用內(nèi)存 SQLite 替代 MySQL每次測試都是干凈的環(huán)境。test_sale_reduces_stock驗證正??蹨ptest_sale_insufficient_stock驗證庫存不足時的提示。參數(shù)上follow_redirectsTrue讓測試客戶端跟隨重定向才能拿到最終頁面的內(nèi)容。跑pytest -v就能看到每個用例的結(jié)果。6.3 用 Postman 或 curl 快速驗證接口有時候不想開瀏覽器直接用 curl 測接口更快。# 測試添加藥品接口 curl -X POST http://127.0.0.1:5000/drug/add \ -d name阿莫西林spec0.25gmanufacturer華北制藥price12.5stock100warning_threshold20 # 測試銷售接口 curl -X POST http://127.0.0.1:5000/sale/add \ -d drug_id1quantity5參數(shù)說明-X POST指定方法-d后面跟表單數(shù)據(jù)。如果接口返回 302說明重定向成功返回 400 或 500就要去看 Flask 控制臺的報錯堆棧。我一般會先用 curl 把接口跑通再去調(diào)前端頁面這樣能快速定位是后端邏輯問題還是前端渲染問題。從那以后我每次拿到一個畢業(yè)設(shè)計項目都強制走一遍「虛擬環(huán)境 → 數(shù)據(jù)庫建表 → 接口 curl 驗證 → 單元測試」的流程不再一上來就改代碼。這套習(xí)慣幫我省下了大量排查環(huán)境問題的時間。希望這份拆包筆記能幫到你讓你在答辯時不僅能演示功能還能講清楚背后的設(shè)計取舍。本文還有配套的精品資源點擊獲取