超市管理数据库设计

Posted by LvKouKou on December 12, 2023




image-20230327104636895-1679885221095



Database System Concepts


超市管理数据库设计


Student ID: 21307381    Student Name: LJW
Date: 2023.12.12


Lectured by: 潘嵘
Database System Concepts Course
Sun Yat-sen University


超市管理数据库设计

一、引言

要求

以Postgresql、SQL Server或My SQL等作为后台数据库,以React、Vue、Angular等或其它开发工具作为前台开发工具,完成一个小型数据库应用系统的设计开发。

(1)针对给定的系统进行需求分析,设计系统结构图系统功能模块图

(2)针对需求分析,画出E-R图表示的概念模型,并将其转换为至少满足3NF的关系模式,设计较为合理的数据库模式;

(3)系统中应能体现对数据库的保护(安全性、完整性等);

(4)系统应该有较为友好的用户界面;

设计目的

设计一个超市库存管理系统,包括商品信息管理进货管理销售管理库存管理等功能。进货管理负责商品的进货录入、修改和查询;销售管理负责商品的销售录入、修改和查询;库存管理负责商品库存的查询和预警。

设计环境

Pycharm:Python的集成开发环境(IDE);

DataGrip:数据库集成开发环境(IDE);

Mysql:数据库

Node.js:基于 Chrome V8 引擎的 JavaScript 运行环境;

Vue.js:Vue.js是一个流行的JavaScript框架,用于构建交互式的Web应用程序;

Visual Studio Code(VS code):用于编写Vue的前端代码;

ElementUI:基于Vue.js的组件库,提供了一套丰富的UI组件和工具,可以帮助我们快速构建漂亮、响应式的Web界面。

二、概要设计

系统需求分析

随着现代商业的快速发展,超市的库存管理变得越来越复杂,传统的手工管理方式已经无法满足现代化企业的需求。因此,需要一个更加智能化、高效化的库存管理系统来帮助企业进行有效的库存管理。超市库存管理系统可以帮助企业实时监控库存情况,避免因为库存不足而影响销售业绩,同时也可以防止过多的库存积压导致资金占用和商品过期等问题。此外,它还可以提供精准的数据支持,帮助企业更好地掌握市场需求和产品销售情况,优化供应链管理,提高企业的运营效率和盈利能力。

因此,超市库存管理系统的设计与实现具有重要的现实意义和应用价值,需要包括商品信息管理进货管理销售管理库存管理、员工管理的功能。商品信息管理包括商品基本信息和库存的多少及库存预警;进货管理负责商品的进货录入、修改和查询;销售管理负责商品的销售录入、修改和查询;客户管理负责客户的录入、修改和查询,员工管理负责员工的录入、修改和查询。

系统应当在库存低于设定值时触发库存预警,并且可以在销售以及进货后自动更改商品的库存信息。同时,系统应当具备可扩展性,以便添加新的功能。系统的用户界面应当清晰明了,易于操作和导航。用户界面设计应当符合用户习惯,提供直观的操作方式和友好的交互体验。

系统结构设计

  1. 前端设计: 前端部分采用Vue.js框架进行开发,Vue.js是一款流行的JavaScript框架,能够帮助我们构建交互式的用户界面。通过Vue.js,我们可以实现页面的动态数据绑定、组件化开发等特性,使得用户界面更加友好、响应更加迅速。
  2. 后端设计: 后端采用Flask框架进行开发,Flask是一个轻量级的Python Web框架,适合快速开发Web应用程序。我们可以利用Flask框架来处理前端请求、实现业务逻辑、访问数据库等功能。通过Flask提供的路由、视图函数等特性,可以构建出高效稳定的后端服务。
  3. 数据库设计: 数据库部分采用MySQL进行存储和管理,MySQL是一个开源的关系型数据库管理系统,具有稳定性高、性能优异等特点。我们可以在MySQL数据库中创建商品表、进货表、销售表等相关表格,用于存储超市库存管理系统所需的各种数据信息。
  4. 运行环境: 超市库存管理系统采用B/S结构设计,该系统在浏览器上运行,用户可以通过浏览器访问系统,进行商品信息管理、进货管理、销售管理和库存管理等操作。前端页面通过Vue.js进行渲染和交互,后端通过Flask处理前端请求并与MySQL数据库交互,从而完成整个超市库存管理系统的功能。
  5. 交互流程: 用户在浏览器上输入系统地址访问系统,在前端页面上进行操作,如添加商品、录入进货信息、录入销售信息等。前端页面将用户的操作请求发送给后端,后端根据请求内容进行相应的处理,并与数据库进行数据交互。最终,前端页面获取后端返回的数据,进行展示和交互,完成用户的操作需求。

通过以上系统结构设计,我们将实现一个基于Vue.js和Flask的前后端分离架构,以及MySQL数据库的超市库存管理系统。这样的架构设计将使系统更具灵活性、可维护性和可扩展性,为用户提供良好的使用体验。

功能模块设计

超市库存管理系统是一个用于实现超市商品库存管理的软件系统。该系统主要包括商品信息管理、进货管理、销售管理和负责的员工管理和可乎管理功能模块。下面对每个功能模块进行详细说明:

  1. 商品信息管理:
  • 添加商品:管理员可以添加新商品到系统中,包括商品名称、单价、库存等信息。
  • 修改商品信息:管理员可以针对已存在的商品进行修改,如修改商品名称、价格等。
  • 删除商品:管理员可以删除不再销售的商品信息。
  • 查询商品:用户可以通过商品ID、名称等关键字进行商品查询,获取商品的详细信息。
  1. 进货管理:
  • 进货录入:管理员可以录入商品的进货信息,包括商品ID、进货数量、进货时间等。
  • 修改进货信息:管理员可以对已录入的进货信息进行修改,如修改进货数量、进货时间等。
  • 查询进货信息:用户可以根据进货ID、商品ID等关键字进行进货信息的查询。
  1. 销售管理:
  • 销售录入:收银员可以录入商品的销售信息,包括商品ID、销售数量、销售时间等。
  • 修改销售信息:管理员可以对已录入的销售信息进行修改,如修改销售数量、销售时间等。
  • 查询销售信息:用户可以根据销售ID、商品ID等关键字进行销售信息的查询。
  1. 库存管理:
  • 查询库存:用户可以根据商品ID、名称等关键字进行库存查询,获取商品的实时库存信息。
  • 库存预警:系统可以根据设定的库存预警值,在商品库存低于预警值时进行提醒。
  1. 员工管理:
  • 查询员工:用户可以根据员工ID、名称等关键字进行员工查询,获取负责进货员工信息,包括员工电话、员工部门等,以便及时通知进货。
  • 添加员工:管理员可以添加新员工到系统中。
  • 删除员工:开除员工。

通过该系统,超市管理员能够方便地管理商品信息、进行进货和销售操作,并及时掌握商品的库存情况。同时,系统还提供了库存预警功能,帮助管理员做出及时的补货决策,确保商品供应充足,提升用户体验。

image

三、详细设计

系统数据库设计

1.客户表client

列名含义 列名名称 数据类型 长度/精度 是否为空 约束
客户编号 client_id char 10 no primary key
客户姓名 client_name varchar 20 no  
联系方式 phone_number varchar 11   unique
地址 address varchar 10    

2.员工表staff

列名含义 列名名称 数据类型 长度/精度 是否为空 约束
员工编号 staff_id char 10 no primary key
员工姓名 staff_name varchar 20 no  
工作部门 department varchar 10 no check:”purchase” or “sale”(触发器)
薪资 salary decimal (8,2) no check≥0
联系方式 phone_number varchar 11   unique

3.商品信息表goods

列名含义 列名名称 数据类型 长度/精度 是否为空 约束
商品编号 goods_id char 10 no primary key
商品名称 goods_name varchar 20 no  
商品数量(库存数量) goods_num int     DEFAULT 0, check(≥0)

4.进货信息表purchase

列名含义 列名名称 数据类型 长度/精度 是否为空 约束
进货编号 purchase_id char 10 no primary key
进货员工编号 staff_id char 10 no foreign key
商品编号 goods_id char 10 no foreign key
进货单价 purchase_price decimal (6,2) no check≥0
进货数量 purchase_num int   no check>0
进货金额 purchase_amount decimal (8,2) no 进货单价*进货数量 (触发器)
进货日期 purchase_date date   no  

5.销售信息表sale

列名含义 列名名称 数据类型 长度/精度 是否为空 约束
销售编号 sale_id char 10 no primary key
客户编号 client_id char 10 no foreign key
商品编号 goods_id char 10 no foreign key
销售单价 sale_price decimal (6,2) no check≥0
销售数量 sale_num int   no check>0
销售金额 sale_amount decimal (8,2) no 销售单价*销售数量 (触发器)
销售日期 sale_date date   no  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
-- 1. 删除表,保证是我们设计的最新的表
DROP TABLE IF EXISTS purchase;
DROP TABLE IF EXISTS sale;
DROP TABLE IF EXISTS staff;
DROP TABLE IF EXISTS client;
DROP TABLE IF EXISTS goods;

-- 2. 创建客户表
CREATE TABLE client (
    client_id CHAR(10) PRIMARY KEY NOT NULL,
    client_name VARCHAR(20) NOT NULL,
    phone_number VARCHAR(11) UNIQUE,
    address VARCHAR(10)
);

-- 3. 创建员工表
CREATE TABLE staff (
    staff_id CHAR(10) PRIMARY KEY NOT NULL,
    staff_name VARCHAR(20) NOT NULL,
    department VARCHAR(10) NOT NULL,
    salary DECIMAL(8,2) NOT NULL ,
    phone_number VARCHAR(11) UNIQUE,
    CHECK (salary>=0)
);

-- 4. 创建商品表
CREATE TABLE goods (
    goods_id CHAR(10) PRIMARY KEY NOT NULL,
    goods_name VARCHAR(20) NOT NULL,
    goods_num INT DEFAULT 0,
    CHECK (goods_num >= 0)
);

-- 5. 创建进货表
CREATE TABLE purchase (
    purchase_id CHAR(10) PRIMARY KEY NOT NULL,
    goods_id CHAR(10) NOT NULL ,
    staff_id CHAR(10) NOT NULL ,
    purchase_price DECIMAL(6,2) NOT NULL ,
    purchase_num INT NOT NULL ,
    purchase_amount DECIMAL(8,2) NOT NULL ,
    purchase_date DATE NOT NULL ,
    CONSTRAINT fk_goods1 FOREIGN KEY (goods_id) REFERENCES goods(goods_id),
    CONSTRAINT fk_staff FOREIGN KEY (staff_id) REFERENCES staff(staff_id),
    CHECK (purchase_price >= 0),
    CHECK (purchase_num >= 0),
    CHECK (purchase_amount = purchase_price * purchase_num)
);

-- 6. 创建销售表
CREATE TABLE sale (
    sale_id CHAR(10) PRIMARY KEY NOT NULL,
    client_id CHAR(10),
    goods_id CHAR(10),
    sale_price DECIMAL(6,2),
    sale_num INT,
    sale_amount DECIMAL(8,2),
    sale_date DATE,

    CONSTRAINT fk_client FOREIGN KEY (client_id) REFERENCES client(client_id),
    CONSTRAINT fk_goods2 FOREIGN KEY (goods_id) REFERENCES goods(goods_id),
    CHECK (sale_price >= 0),
    CHECK (sale_num >= 0),
    CHECK (sale_amount = sale_price * sale_num)
);

ER图如下:

image2

image3

根据第三范式(3NF)的定义,一个关系模式 R 满足 3NF,当且仅当 R 中的每一个非主属性都不传递依赖于 R 的任何一个候选码。在我们设计的超市库存管理数据库中:

  1. 客户表 client:该表中只有客户编号(client_id)是主键,其他字段都依赖于客户编号,因此满足 1NF 和 2NF,同时也满足 3NF。
  2. 员工表 staff:该表中员工编号(staff_id)是主键,工作部门(department)和薪资(salary)都只依赖于员工编号,因此满足 1NF 和 2NF,同时也满足 3NF。
  3. 商品信息表 goods:该表中商品编号(goods_id)是主键,商品名称(goods_name)和商品数量(goods_num)都只依赖于商品编号,因此满足 1NF 和 2NF,同时也满足 3NF。
  4. 进货信息表 purchase:该表中进货编号(purchase_id)是主键,进货员工编号(staff_id)、商品编号(goods_id)、进货单价(purchase_price)、进货数量(purchase_num)和进货日期(purchase_date)都只依赖于进货编号,同时进货金额(purchase_amount)通过触发器计算,因此满足 1NF 和 2NF,同时也满足 3NF。
  5. 销售信息表 sale:该表中销售流水号(sale_id)是主键,客户编号(client_id)、商品编号(goods_id)、销售单价(sale_price)、销售数量(sale_num)和销售日期(sale_date)都只依赖于销售流水号,同时销售金额(sale_amount)通过触发器计算,因此满足 1NF 和 2NF,同时也满足 3NF。

综上所述,数据库设计满足第三范式。

为了保证数据库的安全性和完整性,还添加了以下几个触发器:

  1. 插入或更新员工表时,工作部门(department)必须是 sale or purchase;
  2. 插入或更新进货表时自动计算进货的总金额=进货单价*进货数量,即使输入错误或者不输入也可以自动补全;
  3. 插入或更新销售表时自动计算进货的总金额=销售单价*销售数量,即使输入错误或者不输入也可以自动补全;
  4. 插入或更新销售表和进货表时,自动更新商品信息表的商品数量(goods_num)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
-- 触发器
-- 1. 工作部门必须是 sale or purchase
-- 限制插入
DROP TRIGGER IF EXISTS enforce_department_insert_constraint;
DELIMITER //
CREATE TRIGGER enforce_department_insert_constraint
BEFORE INSERT ON staff
FOR EACH ROW
BEGIN
IF NEW.department != 'purchase' AND NEW.department != 'sale' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Insert Department must be "purchase" or "sale"';
END IF;
END //
DELIMITER ;

-- 限制更新
DROP TRIGGER IF EXISTS enforce_department_update_constraint;
DELIMITER //
CREATE TRIGGER enforce_department_update_constraint
BEFORE UPDATE ON staff
FOR EACH ROW
BEGIN
    IF NEW.department != 'purchase' AND NEW.department != 'sale' THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Update Department must be "purchase" or "sale"';
    END IF;
END //
DELIMITER ;

-- 2. 插入或更新时自动给计算进货的总金额=进货单价*进货数量
DROP TRIGGER IF EXISTS calculate_insert_purchase_amount;
DELIMITER //
CREATE TRIGGER calculate_insert_purchase_amount
BEFORE INSERT ON purchase
FOR EACH ROW
BEGIN
    SET NEW.purchase_amount = NEW.purchase_price * NEW.purchase_num;
END //

DROP TRIGGER IF EXISTS calculate_update_purchase_amount;
CREATE TRIGGER calculate_update_purchase_amount
BEFORE UPDATE ON purchase
FOR EACH ROW
BEGIN
    SET NEW.purchase_amount = NEW.purchase_price * NEW.purchase_num;
END //
DELIMITER ;

-- 3. 插入或更新时自动给计算销售的总金额=销售单价*销售数量
DROP TRIGGER IF EXISTS calculate_insert_sale_amount;
DELIMITER //
CREATE TRIGGER calculate_insert_sale_amount
BEFORE INSERT ON sale
FOR EACH ROW
BEGIN
    SET NEW.sale_amount = NEW.sale_price * NEW.sale_num;
END //

DROP TRIGGER IF EXISTS calculate_update_sale_amount;
CREATE TRIGGER calculate_update_sale_amount
BEFORE UPDATE ON sale
FOR EACH ROW
BEGIN
    SET NEW.sale_amount = NEW.sale_price * NEW.sale_num;
END //
DELIMITER ;

-- 4.插入进货表时自动更新商品信息
DELIMITER //
DROP TRIGGER IF EXISTS update_goods_num_purchase_insert;
CREATE TRIGGER update_goods_num_purchase_insert
AFTER INSERT ON purchase
FOR EACH ROW
BEGIN
    UPDATE goods
    SET goods_num = goods_num + NEW.purchase_num
    WHERE goods_id = NEW.goods_id;
END //

-- 插入销售表时自动更新商品信息
DROP TRIGGER IF EXISTS update_goods_num_sale_insert;
CREATE TRIGGER update_goods_num_sale_insert
AFTER INSERT ON sale
FOR EACH ROW
BEGIN
    UPDATE goods
    SET goods_num = goods_num - NEW.sale_num
    WHERE goods_id = NEW.goods_id;
END //

-- 更新进货表时自动更新商品信息
DROP TRIGGER IF EXISTS update_goods_num_purchase_update;
CREATE TRIGGER update_goods_num_purchase_update
AFTER UPDATE ON purchase
FOR EACH ROW
BEGIN
    UPDATE goods
    SET goods_num = goods_num + (NEW.purchase_num - OLD.purchase_num)
    WHERE goods_id = NEW.goods_id;
END //

-- 更新销售表时自动更新商品信息
DROP TRIGGER IF EXISTS update_goods_num_sale_update;
CREATE TRIGGER update_goods_num_sale_update
AFTER UPDATE ON sale
FOR EACH ROW
BEGIN
    UPDATE goods
    SET goods_num = goods_num - (NEW.sale_num - OLD.sale_num)
    WHERE goods_id = NEW.goods_id;
END //

DELIMITER ;

系统主要功能模块设计

image4

前端(Vue)—(网页设计)

1.首先需要构造一个登陆页面(login.vue),包含了账号密码登录的表单,通过用户名和密码进行登录验证。用户输入账号和密码后,点击登录按钮即可进行登录操作。登录成功后会跳转到主页。

image5

2.然后设计一个主页面(Main.vue),其他页面按照此样式进行相应更改,并从此路由出去。

  • 使用了 Element UI 组件库中的一些组件,如 el-container、el-header、el-menu
  • 模板的结构是一个嵌套的布局,使用了 el-container 来创建一个容器,包含了头部(el-header)和内容区域(el-main)。头部中使用了 el-menu 组件来创建一个水平导航栏,其中包含了几个 el-menu-item 组件。这些菜单项通过 router-link 进行路由导航。
  • 内容区域又分为两个部分:el-aside 和 el-main。el-aside 是一个垂直导航栏,使用了 el-menu 组件和 el-menu-item 组件,同样通过 router-link 进行路由导航。el-main 则是用来显示子路由的组件。

image6

3.设置前端路由,定义不同路径对应的视图组件,从而实现页面间的切换和导航功能

(1)定义以下视图组件(分别对应不同页面):

  • Main:主页面视图组件
  • Login:登录页面视图组件
  • Purchase:采购页面视图组件
  • Client:客户页面视图组件
  • Commodity:商品页面视图组件
  • Sale:销售页面视图组件
  • Staff:员工页面视图组件

(2)定义了路由规则(routes):

  • ’/’ 路径对应登录页面组件 Login
  • ‘/main’ 路径对应主页面组件 Main,并且定义了子路由(children):

  • 子路由包括 ‘/purchase’、’/staff’、’/client’、’/commodity’、’/sale’,分别对应采购、员工、客户、商品、销售等页面组件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import Vue from 'vue'
import VueRouter from 'vue-router'
import Main from '../views/Main.vue'
import Purchase from '../views/purchase.vue'
import Client from '../views/Client.vue'
import Commodity from '../views/Commodity.vue'
import Login from '../views/login.vue'
import Sale from '../views/sale.vue'
import Staff from '../views/staff.vue'
import Home from '../views/Home.vue'

Vue.use(VueRouter)
//添加组件(跳转页面)
const routes = [
  {
    path: '/', 
    component: Login,
  },

  {
    path: '/main', 
    component: Main,
    children:[
      //子路由
      { path: '/purchase', component: Purchase},
      { path: '/staff', component: Staff},
      { path: '/client', component: Client},
      { path: '/commodity', component: Commodity},
      { path: '/sale', component: Sale}
    ]
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

4.各个页面的具体设计

  • purchase.vue(进货信息表)
  • Client.vue(客户表)
  • Commodity.vue(商品信息表)
  • sale.vue(销售信息表)
  • staff.vue(员工表)

下面以Commodity.vue为例(具体代码见最后代码附录部分)

  1. 标题为“商品信息表”的 <h1> 标签。
  2. 如果存在商品数量低于10个的情况,则会显示一个警告框,用 <el-alert> 实现。
  3. 搜索框:用户可以输入关键字来搜索表格中的数据,用 <el-input> 实现。
  4. 表格区域:用 <el-table> 实现,包含四列,分别为商品编号、商品名称、商品数量和操作。其中,商品数量一列根据具体数据来判断是否需要标红,使用了 <template><span>
  5. 修改、添加记录方框:在操作列中,用户可以点击“编辑”或“删除”按钮进行对应的操作。如果是“编辑”,则会弹出一个对话框,用 <el-dialog> 实现。对话框中包含三个输入框:商品编号、商品名称和商品数量。如果是“添加”,则同样会弹出对话框,但是不需要传入已有的数据。
  6. 分页器:表格下方包含一个分页器,用 <el-pagination> 实现。
  7. 弹出窗:用于编辑或添加记录时使用,包含商品编号、商品名称和商品数量三个输入框,用 <el-form> 实现。同时还包含一个“重置”按钮和一个“确定”按钮,分别用于重置输入框和提交表单。
  8. data 中定义了 table 数组,用于存储从后端获取到的商品信息数据。
  9. created 方法会在组件创建时自动执行,调用 init 方法来从后端获取商品信息数据并初始化表格。
  10. add 方法会在用户点击“添加一条记录”按钮时执行,用于弹出一个对话框。
  11. handEdit 方法会在用户点击“编辑”按钮时执行,用于弹出一个对话框并将选中行的数据传入对话框。
  12. handDelete 方法会在用户点击“删除”按钮时执行,用于发送请求删除选中行的数据。
  13. handleClose 方法会在对话框关闭时执行,用于重新初始化表格数据。
  14. reset 方法会在用户点击“重置”按钮时执行,用于重置表单输入框。
  15. save 方法会在用户点击“确定”按钮时执行,用于提交表单数据。
  16. tables 计算属性用于实现搜索功能,根据搜索框中的关键字过滤表格数据。
  17. hasInsufficientQuantity 计算属性用于判断是否存在商品数量低于10个的情况,以便显示警告框

image7

后端(Python)

1.连接MySQL数据库并执行相关操作。db.py文件封装了连接数据库、查询数据、执行单条和多条 SQL 语句以及关闭连接的功能,并提供了一些常用的方法来操作数据库

具体解释如下:

  • DB_CONFIG 是一个字典,包含了连接数据库所需的配置信息,包括主机名、端口号、用户名、密码、数据库名和字符集。
  • SQLManager 类是数据库操作类的定义,包含了一系列方法来执行数据库操作。
  • __init__ 方法是类的初始化方法,在创建类的实例时会自动调用。它初始化了连接对象 conn 和游标对象 cursor,并调用 connect 方法建立与数据库的连接。
  • connect 方法用于连接数据库,它使用 pymysql 模块的 connect 函数来建立连接,并指定数据库的相关配置参数。然后获取游标对象 cursor,用于执行 SQL 语句。
  • get_list 方法用于查询多条数据,接收一个 SQL 语句和参数,并使用游标对象的 execute 方法执行 SQL 语句。然后使用 fetchall 方法获取所有查询结果,并返回。
  • get_one 方法用于查询单条数据,与 get_list 方法类似,但是使用 fetchone 方法获取单条查询结果。
  • modify 方法用于执行单条 SQL 语句,接收一个 SQL 语句和参数。它使用游标对象的 execute 方法执行 SQL 语句,并使用 commit 方法提交事务。最后返回执行结果,如果受影响的行数大于0,则返回 True,否则返回 False
  • multi_modify 方法用于执行多条 SQL 语句,与 modify 方法类似,但是使用 executemany 方法执行多条 SQL 语句,并返回执行结果。
  • close 方法用于关闭数据库连接和游标对象,分别调用 cursor 对象的 close 方法和 conn 对象的 close 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 数据库操作类
import pymysql
DB_CONFIG = {
	"host": "127.0.0.1",
	"port": 3306,
	"user": "root",
	"passwd": "123456",
	"db": "test1",
	"charset": "utf8"
}

class SQLManager(object):

	# 初始化实例方法
	def __init__(self):
		self.conn = None	# 连接对象,建立与MySQL数据库的连接
		self.cursor = None 	# 游标对象,主要负责执行SQL语句
		self.connect()

	# 连接数据库
	def connect(self):
		# 建立连接
		self.conn = pymysql.connect(
			host=DB_CONFIG["host"],
			port=DB_CONFIG["port"],
			user=DB_CONFIG["user"],
			passwd=DB_CONFIG["passwd"],
			db=DB_CONFIG["db"],
			charset=DB_CONFIG["charset"]
		)
		# 获取游标对象;pymysql.cursors.DictCursor指定返回的结果类型为字典,默认是元组类型
		# 在连接没有关闭之前,游标对象可以反复使用
		self.cursor = self.conn.cursor(cursor=pymysql.cursors.DictCursor)

	# 查询多条数据
	def get_list(self, sql, args=None):
		self.cursor.execute(sql, args)	# 执行SQL语句,返回受影响的行数
		return self.cursor.fetchall()	# 返回所有查询结果

	# 查询单条数据
	def get_one(self, sql, args=None):
		self.cursor.execute(sql, args)
		return self.cursor.fetchone()	# 返回单条查询结果

	# 执行单条SQL语句
	def modify(self, sql, args=None):
		row = self.cursor.execute(sql, args)
		self.conn.commit()	# 对数据库的增、删、改操作需要提交事务,否则操作不生效
		return row > 0	# 若影响的行数>0,说明执行成功,返回True;否则返回False

	# 执行多条SQL语句
	def multi_modify(self, sql, args=None):
		rows = self.cursor.executemany(sql, args)
		self.conn.commit()
		return rows > 0

	# 关闭数据库cursor和连接
	def close(self):
		self.cursor.close()
		self.conn.close()

2.与前端建立连接。app.py使用 Flask 框架搭建了一个简单的后端接口,提供了查询、添加、修改和删除客户信息的功能。它通过自定义的 JsonFlask 类处理返回 JSON 格式的响应,使用 SQLManager 类进行数据库操作,并使用 Flask-CORS 扩展解决跨域请求的问题。

下面以对client表的操作为例:

  • app = JsonFlask(__name__): 创建一个应用实例 app,使用自定义的 JsonFlask 类,该类继承自 Flask 的 Flask 类,用于处理返回 JSON 格式的响应。
  • CORS(app, supports_credentials=True): 调用 CORS 函数并传入应用实例 app,用于解决跨域请求,允许跨域请求携带凭证。
  • db = SQLManager(): 创建一个数据库连接对象 db,使用自定义的 SQLManager 类。
  • @app.route("/all_client", methods=["GET"]): 使用装饰器 @app.route 绑定路由 /all_client 和请求方法 GET,定义了一个名为 all_client 的视图函数。
  • def all_client(): 定义了视图函数 all_client,用于处理 /all_client 路由的 GET 请求。它调用数据库连接对象 dbget_list 方法执行 SQL 查询语句,然后通过 JsonResponse.success 方法构造成功的响应数据并返回。
  • 类似地,@app.route 装饰器定义了 /add_client/update_client/delete_client 等路由对应的视图函数,并在这些函数中执行相应的数据库操作,最后构造响应数据并返回。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 创建视图应用,使用改造后的JsonFlask对象
app = JsonFlask(__name__)

# 解决跨域
CORS(app, supports_credentials=True)

# 数据库连接对象
db = SQLManager()

# 编写视图函数,绑定路由
# client
@app.route("/all_client", methods=["GET"])  # 查询(全部)
def all_client():
    result = db.get_list(sql='select * from client')
    return JsonResponse.success(msg='查询成功', data=result)

@app.route("/add_client", methods=["POST"])  # 添加(单个)
def add_client():
    data = json.loads(request.data)  # json字符串转为dict
    isOk = db.modify(sql='insert into client(client_id,client_name,phone_number,address) values(%s,%s,%s,%s)',
                     args=[data['client_id'], data['client_name'], data['phone_number'], data['address']])
    # python三元表达式
    return JsonResponse.success(msg='添加成功') if isOk else JsonResponse.fail(msg='添加失败')

@app.route("/update_client", methods=["PUT"])  # 修改(单个)
def update_client():
    # request.data获取请求体数据
    # 前端在发送请求时,由于指定了Content-Typeapplication/json;故request.data获取到的就是json数据
    data = json.loads(request.data)  # json字符串转为dict
    if 'client_id' not in data:  # 改为form里对应的xx_id
        return JsonResponse.fail(msg='需要传入client_id')
    isOk = db.modify(sql='update client set client_name=%s,phone_number=%s,address=%s where client_id=%s',  # 改为
                     args=[data['client_name'], data['phone_number'], data['address'], data['client_id']])
    return JsonResponse.success(msg='修改成功') if isOk else JsonResponse.fail(msg='修改失败')

@app.route("/delete_client", methods=["DELETE"])  # 删除(单个)
def delete_client():
    # request.args获取请求链接中 ? 后面的所有参数;以字典的方式存储
    if 'client_id' not in request.args:
        return JsonResponse.fail(msg='需要传入client_id')
    isOk = db.modify(sql='delete from client where client_id=%s', args=[request.args['client_id']])
    return JsonResponse.success(msg='删除成功') if isOk else JsonResponse.fail(msg='删除失败')

各模块的主要算法对应的原代码

见code文件夹

四、调试与运行结果及存在的主要问题

调试、运行和存在的问题和解决方案

postgresql难以连接

python连接postgresql的库psycopg2无法返回字典,即使使用:

1
self.cursor = self.conn.cursor(cursor_factory=extras.DictCursor)

返回的仍然是列表,不便于使用。查询资料后发现没有解决办法,所以改用mysql,详情见下面网站:

[DictCursor doesn’t seem to work under psycopg2]


前端的交互无响应

解决:使用console.log(“打印文本”)和浏览器中按F12进入开发者模式调试。

举例:修改弹出警告样式,一开始是默认使用库中的类,通过开发者模式发现问题,并重写该类(使用! important

image8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.el-alert {
    width: 80% ! important; 
    padding: 8px 16px;
    margin: 0;
    box-sizing: border-box;
    border-radius: 4px;
    position: relative;
    background-color: #FFF;
    overflow: hidden;
    opacity: 1;
    display: flex;
    align-items: center;
    transition: opacity .2s;
}

不知道是否连接上正确的数据库

解决:使用以下代码尝试看是否能访问正确的数据

1
2
3
4
5
from db import *
db = SQLManager()
# result = db.get_list(sql="select * from public.user;")
result = db.get_list(sql="select * from sale")
print(result)

通过后端的接收信息来判断前端的操作是否被正确执行:

image9

五、 完成结果视频展示

为了方便展示,销售流水号设置为saX,商品编号设置为gX,员工编号设置为sX,进货编号设置为pX,客户编号设置为cX。

登陆->依次展示各个表格->以进货过程为例,观察表格的插入、修改、删除以及对其他表格的影响。

员工信息表

  • 展示查找排序

  • 展示修改重置

  • 展示删除

进入商品信息表

  • 插入一个新表项(数量设置为0) -> 进入进货信息表,创造表项(商品编号为前面创造的商品),注意外键约束(staff_id和goods_id必须是员工表和商品表中存在的-> 回到商品信息表,发现刚创建的商品数量发生变化。(并且进货金额即使填错了,也有纠正机制将其记录正确)
  • 进入销售信息表,创造表项,注意外键约束 -> 回到商品信息表,发现商品预警。
  • 进入进货信息表增加,回到商品信息表,发现商品预警消失

演示视频(点击即可播放,视频加载时间可能需要较久,请耐心等待):

六、课程设计小结

总结

本次数据库实验我们成功开发一个超市库存管理系统,包括商品信息管理、进货管理、销售管理和库存管理等功能。为了实现这个目标,我们选择了以下技术和工具:PyCharm作为Python的集成开发环境(IDE),DataGrip作为数据库集成开发环境(IDE),MySQL作为后台数据库,Node.js和Vue.js作为前台开发工具,以及Visual Studio Code(VS Code)用于编写前端代码。

在实验中,我们按照要求逐步完成了超市库存管理系统的设计和开发。首先,我们针对给定的系统进行需求分析,设计了系统结构图和系统功能模块图。通过这些图表,我们能够清晰地了解系统的整体架构和各个功能模块之间的关系,为后续的设计和开发提供了指导。

接下来,我们进行了数据库概念模型的设计,使用E-R图表示系统的数据模型,并将其转换为满足3NF的关系模式。通过这一步骤,我们能够对系统的数据结构有一个清晰的把握,并且能够更好地进行数据库的设计和管理。同时,我们也考虑了系统的安全性和完整性,确保了对数据库的保护。

在开发过程中,我们使用了Vue.js作为前端框架,结合ElementUI组件库,实现了较为友好的用户界面。我们使用python与前端进行交互,并且连接MySQL数据库执行相关操作。

总的来说,本次数据库实验通过使用Python、MySQL、Node.js和Vue.js等工具和技术,成功设计和开发了一个超市库存管理系统。在实验过程中,我们深入理解了数据库的设计原理和开发方法,并且熟悉了常用的开发工具和框架。通过实践,我们提升了对数据库应用系统的理解和开发能力,为以后的项目实践打下了坚实的基础。

改进的地方

在这个数据库实验的设计和开发过程中,存在一些可以改进的地方:

  1. 前端界面交互体验优化:虽然使用了Vue.js和ElementUI构建了用户界面,但可以进一步优化用户交互体验,例如增加动画效果。
  2. 安全性和完整性进一步加强:在数据库层面,可以考虑加强数据的安全性,加密敏感信息,例如员工和客户的个人信息。
  3. 系统性能优化:随着数据量的增加,系统性能可能成为一个问题。可以考虑对数据库的索引、查询语句以及前端页面加载等方面进行优化,以提高系统的响应速度和并发处理能力
  4. 跨平台兼容性:考虑到不同操作系统和浏览器的兼容性,可以进一步测试和优化系统在不同平台上的表现。
  5. 代码质量和可维护性:在开发过程中,还可以提高代码的可复用性和可维护性,例如后端处理操作时的代码,以便于后续的维护和扩展。
  6. 本实验均在本地开发环境完成,并没有部署到服务器上。

七、源代码