作者主页:编程千纸鹤
作者简介:Java、前端、Pythone开发多年,做过高程,项目经理,架构师
主要内容:Java项目开发、毕业设计开发、面试技术整理、最新技术分享
项目编号:BS-QD-004
前言:
当前时代,全球的经济已经从工业经济到知识经济的改变,过去专家说知识经济的两个首要属性是信息化和全球化,要完成化和全球化,这时就需要稳定的网络和完备的数据库。但时至今日中国的服务价值意识提高,人力成本开始骤增,对于很多快递企业来说一个10元成本的单子,有5元是在最后一公里被消耗了。研究快递驿站的智能物流货架的应用方案,随着社会和科学技术的演变,仓储管理的方法也不断优化。从全部人工管理的方法,这样不仅效率低,工作量大,并且准确率差。尤其是当今快递行业飞速发展,货流量大,人工管理更显不足。为了提高仓库管理效率,减少仓库管理成本,特开发菜鸟驿站库存管理系统。
一,项目简介
本系统使用 Node.JS 语言开发,该编程语言简单易学,能够与多个框架完美结合,又具备面向对象、与平台无关、多线程等特点,因此比较容易进行开发工作。开发工具使用Visual Studio Code,它强大的整合能力能更好的对项目进行管理,丰富的提示功能可以使开发人员更加高效的进行开发工作。数据库使用MySQL,其简单易用、开源免费、社区庞大而完善的特点,对于初次尝试人员非常友好。系统的架构是B/S,也就是浏览器/服务器模型,客户端完成主要的业务,一些交互或发起请求在前端完成。对于用户非常简单,通过一个浏览器就可以与系统进行访问和交互。为了实现高效的开发和后期维护,系统采用了MVC架构,即数据模型层、视图表现层、路由控制层,客户通过视图交互实现数据功能的使用效果,路由控制层主要处理前端传过来的各种请求,通过路由和参数以及身份认证来处理逻辑,并把最终的结果给到前端,数据模型层主要是对数据的增删改查等操作,当然主要依赖于路由控制层。如上的开发工具和技术都能够满足开发需求,故在技术上是可行的。随着科学技术的不断提高,计算机科学应用也越来越普及,相比手工管理,使用计算机软件对仓储信息进行管理,无论从效率、科学性还是规范性方面都有着巨大的优势,该软件的开发不会侵犯国家、集体和他人的利益,所以其具备社会公认的可行性。系统开发不需要高端设备,大部分技术开源免费,哪怕开发过程中发生重大失误,改正即可,不会损耗材料,系统开发出来,可以无限备份投入使用,非常完全符合经济上的可行性。
业务流程图
系统数据流程图
二,环境介绍
2.2相关技术和开发环境
2.2.1 相关技术
(1)B/S结构(Browser/Server结构)简介
B/S(Brower/Server,浏览器/服务器)模式又称B/S结构,是Web兴起后的一种网络结构模式。Web浏览器是客户端最主要的应用软件。这种模式统一了客户端,将系统功能实现的核心部分集中到服务器上,简化了系统的开发、维护和使用;客户机上只需要安装一个浏览器,服务器上安装SQL Server, Oracle, MySql等数据库;浏览器通过Web Server同数据库进行数据交互。Browser指的是Web浏览器,极少数事务逻辑在前端实现,但主要事务逻辑在服务器端实现。B/S架构的系统无须特别安装,只有Web浏览器即可。
其实就是我们前端现在做的一些事情,大部分的逻辑交给后台来实现,我们前端大部分是做一些数据渲染,请求等比较少的逻辑。通过三层结构模型,大大减轻了客户端的压力,降低了开发和维护的成本,也降低了客户的总成本。
(2)Mysql简介
MySQL是Web世界中使用最广泛的数据库服务器。SQLite的特点是轻量级、可嵌入,但不能承受高并发访问,适合桌面和移动应用。而MySQL是为服务器端设计的数据库,能承受高并发访问,同时占用的内存也远远大于SQLite。
此外,MySQL内部有多种数据库引擎,最常用的引擎是支持数据库事务的InnoDB。存储引擎就是存储数据,建立索引,更新、查询数据等技术的实现方式。存储引擎是基于表的。mysql支持多种存储引擎,而oracle、sqlserver等只有一种存储引擎
即市场占有率最大的关系型数据库,类似于excel表格
DML:select、insert、update、delete
DDL:drop、create等
- MySQL数据库的优点
MySQL的主要优势如下:
1、速度: 系统运行速度非常快。
2、价格:MySQL对多数个人来说是免费使用的。
3、容易使用:相比于其他数据库的设置和管理相比,相对于比较简单,容易学习。
4、可移植性: 跨平台能力,可以适用于不同的系统平台之下,例如:Windows 、Linux、Unix、MacOS等。
5、丰富的接口: 提供了用于C 、C++、Eiffel、Java、Perl、PHP、Python、Rudy和TCL 等语言的API。
6、支持查询语言:MySQL可以利用标准SQL语法和支持ODBC(开放式数据库连接)的应用程序。
7、安全性和连接性; 非常灵活的安全和校验机制,允许主机验证。连接到服务器时,所有的密码
都采用加密形式,从而保证了密码安全。并且由于MySQL时网络化的,因此可以在因特网网上的任何地方访问,提高数据共享效率。
(4)HTML简介
HTML 是用来描述网页的一种语言。
HTML 指的是超文本标记语言 (Hyper Text Markup Language)
HTML 不是一种编程语言,而是一种标记语言 (markup language)
标记语言是一套标记标签 (markup tag)
HTML 使用标记标签来描述网页
HTML 标签
HTML 标记标签通常被称为 HTML 标签 (HTML tag)。
HTML 标签是由尖括号包围的关键词,比如 <html>
HTML 标签通常是成对出现的,比如 <b> 和 </b>
标签对中的第一个标签是开始标签,第二个标签是结束标签
开始和结束标签也被称为开放标签和闭合标签
HTML 文档 = 网页
HTML 文档描述网页
HTML 文档包含 HTML 标签和纯文本
HTML 文档也被称为网页
(5)Node.js简介
Node.js是基于google公司旗下的产品Chrome浏览器,其中使用的V8引擎就来自于此。由于浏览器的特殊性,引擎最显著的特点就是事件驱动、异步的I/O模式,在不断的优化下,非常的高效和轻量
(6)Visual Studio Code简介
相比于Visual Studio的重量级产品不同,Visual Studio Code可谓非常的轻量级,它诞生于2015年4月30日的开发者大会上,并且跨平台的特性,以及针对现代web应用和云平台的源代码编辑器。软件只有几十兆,因为不绑定任何的插件,但是因为拥有强大的插件系统和插件商城,所以开发者可以有选择的使用各种插件,提高效率,对于内置支持的Javascript和TypeScript,并且可以通过扩展来支持其他语言,比如C++、C、JAVA、Python等语言。
(7)Nginx简介
Nginx是支持多线程的方式的,只是我们主流的方式还是多进程的方式,也是nginx的默认方式。
master进程主要用来管理worker进程,包含:接收来自外界的信号,向各worker进程发送信号,监控worker进程的运行状态,当worker进程退出后(异常情况下),会自动重新启动新的worker进程。
worker进程则是处理基本的网络事件。多个worker进程之间是对等的,他们同等竞争来自客户端的请求,各进程互相之间是独立的。一个请求,只可能在一个worker进程中处理,一个worker进程,不可能处理其它进程的请求。
2.2.2 系统的开发环境
系统设计平台:Microsoft Windows 10
数据库设计工具:MySQL
程序设计工具:Visual studio code
三,系统展示
4.2.1 注册界面
注册界面是管理系统最重要的环节,只有注册才能有能力访问管理后台系统,以及进一步的操作,这样也就规避了不同人员在没有权限下,操作管理后台。
没有权限操作数据,会对整个数据造成破坏,产生不可挽回的影响。
具体代码如下:
let { name, password, password2, phone, username } = ctx.request.body;
let adminUserList = await query('select * from admin_user');
let time = dayjs().format('YYYY-MM-DD HH:mm:ss')
if (adminUserList.some(item => item.username === username)) {
ctx.body = {
code: -1,
data: null,
message: '该用户已经注册'
};
} else {
try {
let str = `INSERT INTO admin_user(username, password, name, phone, time) VALUES('${username}', '${password}', '${name}', '${phone}', '${time}')`;
console.log(str);
await query(str);
ctx.response.redirect('/login');
} catch(err) {
console.log(err);
ctx.response.redirect('/register');
}
}
注册操作需要的信息有:用户名(登录名)、用户密码、确认密码、真实姓名、用户手机号。具体操作页面如4-1所示:
4.2.2 登录界面
管理员登录是验证用户身份最主要的手段,后台系统通过token保存用户身份,前端把token放到浏览器storage中存储,每次请求带上token数据。整体的实现也非常简单,通过输入用户名和密码,前端发起http请求,给到后端,后端拿到用户名和密码,进一步判断是否与数据库的数据一致,如果通过验证则跳到首页,否则就会返回给客户端相应的错误。通过认证后,用户就会与之创建连接,就可以完成之后的后续操作了。
具体实现代码如下所示:
let adminUserList = await query(`
SELECT id, username, name, phone
FROM admin_use
WHERE username='${username}' && password='${password}'
`);
if (adminUserList.length > 0) {
let token = jwt.sign({ ...adminUserList[0] }, secret);
ctx.body = {
code: 1,
data: {
token,
username: username
},
message: '登录成功'
}
具体的功能页面如下图4-2所示:
图4-2 登录界面
4.2.3 入库界面
每为确保公司货货物进出入库能得到管制,确保仓库库存数据的准确,必须规范好商品的出入库
流程。最重要的就是入库管理,通过点击顶部的商品入库跳到对应页面
具体代码如下:
操作界面如图4-3:
router.post('/instock', async function (ctx, next) {
let { token } = ctx.request.header;
let { name, phone, goodsName } = ctx.request.body;
try {
let userInfo = jwt.verify(token, secret);
let time = dayjs().format('YYYY-MM-DD HH:mm:ss');
console.log(name, phone, goodsName, userInfo);
// 1. 先用手机号查customer表,是否有此用户
let customerList = await query(`select id, phone from customer`);
let curCustomer = customerList.find(item => item.phone == phone);
let customerId;
if (!curCustomer) {
// 先加一下这个用户
customerId = customerList.length + 1;
await query(`insert into customer(id, name, phone, time) values(${customerId}, '${name}', '${phone}', '${time}')`);
} else {
customerId = curCustomer.id;
}
// 2. 找一个空的货架单元
// 把所有库存的仓库都找出来
let storehouseGoodsList = await query(`select storehouse_id from storehouse_goods where is_out<>1`);
let storehouseList = await query(`select id from storehouse`);
let emptyStoreId = '';
if (storehouseGoodsList.length > 0) {
let emptyStoreList = storehouseList.filter(item => !storehouseGoodsList.some(it => it.storehouse_id === item.id))
if (emptyStoreList.length === 0) throw new Error('没有空余的货架,请出库以腾出空位!');
emptyStoreId = emptyStoreList[0].id;
} else {
emptyStoreId = storehouseList[0].id;
}
// 写入出库
let goodsList = await query(`select id from goods`);
let goodsId = goodsList.length + 1;
await query(`insert into goods(id, name, customer_id, admin_id) values(${goodsId}, '${goodsName}', ${customerId}, ${userInfo.id})`);
await query(`insert into storehouse_goods(goods_id, storehouse_id, is_out, instock_time) values(${goodsId}, ${emptyStoreId}, 0 ,'${time}');`);
ctx.body = {
code: 1,
data: null,
message: '商品入库成功!'
}
} catch(err) {
console.log(err);
ctx.response.redirect('/login');
}
});
图4-3 商品入库
4.2.4 出库界面
仓当客户收到取件码后,通过扫码就可以获得对应的库存id(因为没有具体的物理设备,此操作通过查询步骤,给出库存id),通过检验身份信息,此功能通过点击顶部的商品出库跳到对应的页面完成
相关代码如下:
具体操作页面如图4-4:
router.post('/outstock', async function (ctx, next) {
let { token } = ctx.request.header;
let { val } = ctx.request.body;
try {
let userInfo = jwt.verify(token, secret);
let sgList = await query(`select id, goods_id, is_out from storehouse_goods where id=${val}`);
if (sgList.length === 0) throw new Error('库存id无效');
if (sgList[0].is_out === 1) throw new Error('该商品已经出库');
let time = dayjs().format('YYYY-MM-DD HH:mm:ss');
await query(`UPDATE storehouse_goods SET is_out=1, outstock_time='${time}' where id=${val}`);
ctx.body = {
code: 1,
data: null,
message: '出库成功'
}
} catch(err) {
ctx.body = {
code: -1,
data: null,
message: err.message
}
}
});
图4-4 商品出库界面
4.2.5 查询界面
相关代码如下:
具体操作页面如图4-5所示:
router.get('/queryorder', async function (ctx, next) {
let { token } = ctx.request.header;
let { value } = ctx.request.query;
try {
let userInfo = jwt.verify(token, secret);
let reg_tel = /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/;
let arr = [];
// , G.is_out, G.instock_time, G.outstock_time
// 如果是手机号
if (reg_tel.test(value)) {
let customerList = await query(`SELECT id FROM customer WHERE phone='${value}'`);
if (customerList.length === 0) throw new Error('没有这个手机号的记录');
// 找到这个商品
let goodsList = await query(`SELECT G.id, G.name AS 'good_name', A.name AS 'admin_name', C.name AS 'customer_name' FROM goods G, admin_user A, customer C WHERE G.customer_id=${customerList[0].id} AND C.id=${customerList[0].id} AND A.id=G.admin_id`);
if (goodsList.length > 0) {
for (let i=0; i<goodsList.length; i++) {
let _item = { ...goodsList[i] };
let sgList = await query(`SELECT * FROM storehouse_goods WHERE goods_id=${_item.id}`);
if (sgList.length === 0) throw new Error('没有找到此库存');
let storeList = await query(`SELECT * FROM storehouse WHERE id=${sgList[0].storehouse_id}`);
if (storeList.length === 0) throw new Error('没有找到此仓库');
// 找这个仓库
_item.store_id = storeList[0].id;
_item.store_code = storeList[0].name;
_item.sg_id = sgList[0].id;
_item.is_out = sgList[0].is_out;
_item.instock_time = sgList[0].instock_time;
_item.outstock_time = sgList[0].outstock_time;
arr.push(_item);
}
}
console.log(goodsList);
} else {
// 使用取件码查询
// 1. 查询库存表是否有这个仓库
let storeList = await query(`SELECT id, name FROM storehouse WHERE name='${value}'`);
if (storeList.length === 0) throw new Error(`${value}这个取件码无效`);
// 2. 找到这个库存
let sgList = await query(`SELECT * FROM storehouse_goods WHERE storehouse_id=${storeList[0].id}`);
if (sgList.length === 0) throw new Error(`没有找到这个库存`);
// 3. 找到这个商品
let goodsList = await query(`SELECT G.id, G.name AS 'good_name', A.name AS 'admin_name', C.name AS 'customer_name' FROM goods G, admin_user A, customer C WHERE G.id=${sgList[0].id} AND C.id=G.customer_id AND A.id=G.admin_id`);
if (goodsList.length > 0) {
arr.push(goodsList[0]);
arr[0].store_id = storeList[0].id;
arr[0].store_code = storeList[0].name;
arr[0].sg_id = sgList[0].id;
arr[0].is_out = sgList[0].is_out;
arr[0].instock_time = sgList[0].instock_time;
arr[0].outstock_time = sgList[0].outstock_time;
}
}
ctx.body = {
code: arr.length > 0? 1: -1,
data: arr,
message: arr.length > 0? 'query ok': '没有找到指定的库存'
}
} catch(err) {
ctx.body = {
code: -1,
data: null,
message: err.message
}
}
})
图4-5 库存查询页面
4.2.6 库存异常界面
在常规的快递库存管理过程中,不可避免的会遇到快递长期无人领取,一方面有可能是客户工作繁忙等原因,也有可能是因为某些原因客户没有收到通知短信,导致库存长期被占用。这里有两个问题:第一、因为库存被占用,不能存放其他快递;第二、客户没有接收到快递,降低用户体验,并且有可能造成用户的经济损失。基于以上原因,特设库存异常功能,解决此类问题,相关代码如下:
let arr = [];
let timeWhere = '';
switch (val) {
case '1': // 超时一周
timeWhere = `instock_time < '${ dayjs(dayjs() - 7*24*60*60*1000).format('YYYY-MM-DD HH:mm:ss') }'`;
break;
case '2': // 超时3周
timeWhere = `instock_time < '${ dayjs(dayjs() - 3*7*24*60*60*1000).format('YYYY-MM-DD HH:mm:ss') }'`;
break;
case '3': // 超时一个月
timeWhere = `instock_time < '${ dayjs(dayjs() - 30*24*60*60*1000).format('YYYY-MM-DD HH:mm:ss') }'`;
break;
case '4': // 超时三个月
timeWhere = `instock_time < '${ dayjs(dayjs() - 90*24*60*60*1000).format('YYYY-MM-DD HH:mm:ss') }'`;
break;
case '5': // 超时一年
timeWhere = `instock_time < '${ dayjs(dayjs() - 365*24*60*60*1000).format('YYYY-MM-DD HH:mm:ss') }'`;
break;
}
let sgList = await query(`select * from storehouse_goods where is_out<>1 and ${timeWhere}`);
for (let i=0; i<sgList.length; i++) {
let item = { ...sgList[i], instock_time: dayjs(sgList[i].instock_time).format('YYYY-MM-DD HH:mm:ss') };
let storeList = await query(`select id, name from storehouse where id=${item.storehouse_id}`);
let goodsList = await query(`select G.id, G.name AS 'good_name', A.name AS 'admin_name', C.name AS 'customer_name' from goods G, admin_user A, customer C where G.id=${item.goods_id} AND C.id=G.customer_id AND A.id=G.admin_id`);
if (goodsList.length > 0) {
item.store_id = storeList[0].id;
item.store_code = storeList[0].name;
item.admin_name = goodsList[0].admin_name;
item.customer_name = goodsList[0].customer_name;
item.good_name = goodsList[0].good_name;
}
arr.push(item);
}
具体操作界面如图4-6所示:
图4-6 库存异常界面
4.2.7 客户信息
有些场景我们需要查找客户信息,比如某一快递异常,找客户的手机号,给客户发消息,这时就需要客户信息功能界面,可以模糊查询。
具体代码如下:
queryWhere = `where name like '%${val}%'`;
let arr = await query(`select * from customer ${queryWhere}`);
arr = arr.map(item => ({ ...item, time: dayjs(item.time).format('YYYY-MM-DD HH-mm-ss') }));
具体界面如图4-7所示:
图4-7 客户信息界面
四,项目总结
5.1测试的目的
系统测试(System Testing),系统测试是把已经完成的硬件、软件、外设、网络等所有组成部分结合在一起,对整个系统进行单元测试和总体测试,通过与系统的需求做比较,找到所开发的系统与用户需求之间的差别,进而优化系统的手段。目前无论大企业还是中小企业,都再利用互联网进行信息的管理和分享,所以一个软件系统进行系统的测试和总结,是必要的和必需的,而且通过测试工作的进行,可以反映出最初没有考虑到的细节或流程,也有查漏补缺的功能,进而提高软件的质量。
5.2测试的方法
5.2.1 白盒测试
白盒测试又被称为:结构测试或者叫做逻辑驱动测试,这是一种需要基于代码的测试,白盒是一种形象的比喻,系统程序就像透明盒子一样,是可以看见的,也就是我们可以系统的各个模块是如何运行和调用的。比如:大部分公司首先会进行白盒测试,也就是按功能模块系统的对子模块进行系统的测试,包括运行的异常和文本域或模拟用户行为创建并不符合系统的输入,从而建立全面准确并具有说服力了的测试用例。虽然白盒测试有很多优点,但也有几个无法规避的问题,一个系统程序运行会有很多条类似于tree结构的不同路径,不可能测试所有的情况,也为程序不稳定埋下了伏笔。
5.2.2 黑盒测试
黑盒测试通常也成为数据驱动的测试方法,黑盒顾名思义就是,把系统程序看作看不见的黑盒子,完全不用考虑系统的流程,数据的流动,以及各个字段的类型。在完全不考虑程序本身的前提下,更多从用户使用情况或用户体验出发,测试每个模块是否可以正常稳定运行,按照程序需求文档,逐条验证是否可以输入数据而产生预期的正确结果,白盒测试着眼于内部,而黑盒子着眼于外部,当然这里有一个弊端,就是黑盒测试不能系统功能是否设计合理等细节问题。
5.2.3 灰盒测试
白盒测试和黑盒测试都有各自的优缺点,而灰盒测试介于黑盒测试和白盒测试之间,综合了各自的优点,有又规避了其中的缺点,不仅像白盒测试那样测试系统程序内部的功能逻辑,还会重点关注输入、输出的准确性。比如某个程序段运行时,通过外部的异常表现,转而进入程序内部,结合程序内部逻辑结构综合分析,这样就可以全面的诊断和测试系统程序。
5.2.4 静态测试
静态测试是比较独特,不像其他测试方法,需要运行被测试的系统程序,静态测试通过系统的分析程序结构,语法,接口规范,过程来检查程序的手段。
5.2.5 动态测试
动态测试是运行被测试的系统程序,分析程序运行的结果和预期达到的结果的差异,并总结出具体的程序性能指标,正确率等。
5.3测试用例
5.3.1 系统登录功能测试
系统的测试是甲方验收的重要的依据,通过输入所需信息查看是否和预期一样。是否可以完整成功运行整个系统。如表5-1所示
图5-1 测试用例
名称 | 功能 | 操作 | 期望结果 | 实际结果 | 测试状态 |
用户注册 | 注册管理员 | 输入用户名:wxs; 密码:12345678; 确认密码:12345678 真实名字:王先生 手机号:18201107931 | 注册成功 | 与期望相同 | |
用户登录 | 管理员登录 | 用户名:wxs; 密码:12345678 | 登录成功 | 与期望相同 | |
查询库存 | 库存列表 | 输入用户手机号:,查询它的商品快递 | 查询成功 | 与期望相同 | |
商品入库 | 商品入库 | 收件人姓名:李先生; 收件人手机号:18435250911; 商品名称:小米手环S1 | 入库成功 | 与期望相同 | |
商品出库 | 商品出库 | 输入库存id:6 | 出库成功 | 与期望相同 | |
库存异常 | 查询异常库存 | 选择超时一年未取的选项 | 查询成功 | 与期望相同 | |
客户信息 | 客户信息 | 输入用户姓名 | 查询成功 | 与期望相同 |
5.4测试总结
时间不知不觉程序已经测试完成,虽然功能总体来说比较简单,但是通过自己的双手一步一步的完成此论文,非常的激动。系统总体达到了总目标,编码结果和测试结果完成预期规划,此次测试从系统的各个环节进行了全面测试,基本达到系统独立应用的能力,基本功能已经实现。
通过测试,功能更加齐全和稳定,在测试中不断会有bug产生,通过不断的寻找解决方法,最终完成预期效果。