# CampusOrderSystem
**Repository Path**: Jason-wulf/CampusOrderSystem
## Basic Information
- **Project Name**: CampusOrderSystem
- **Description**: 校园外卖订餐系统
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 2
- **Created**: 2025-05-27
- **Last Updated**: 2025-05-27
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 基于Web的校园外卖订餐系统设计与实现
- [基于Web的校园外卖订餐系统设计与实现](#基于web的校园外卖订餐系统设计与实现)
- [0. 题目要求](#0-题目要求)
- [0.1 前台功能](#01-前台功能)
- [0.2 后台功能(管理员)](#02-后台功能管理员)
- [0.3 功能需求](#03-功能需求)
- [0.4 功能划分](#04-功能划分)
- [0.5 功能描述](#05-功能描述)
- [前后端数据格式约定](#前后端数据格式约定)
- [前后端数据接口约定](#前后端数据接口约定)
- [验证部分](#验证部分)
- [权限的设计](#权限的设计)
- [过程设计](#过程设计)
- [密码加密和解密](#密码加密和解密)
# 0. 题目要求
现代信息化餐饮业已不同于传统餐饮业, 随着科技与时代飞速发展, 人类的需求也随之上升。 过往, 人们都是到店点餐, 经过一系列人工服务才能等到心爱的美食, 当信息化过后的点餐系统, 有效的缩短了等待的过程, 提高了人工的效率, 减少错误的产生。 此款软件针对高校校园而开发。 该论文详细的阐述了开发过程, 从前期需求分析, 编写代码, 到后期测试, 详细的记录了每个环节, 从菜品的管理, 到订单管理, 面向商家, 同时也面向用餐的学生。 系统订餐页面简单直观, 交互性强, 易于操作和使用。
随着学习和工作节奏的加快, 很多教师和学生由于上课、 工作忙碌和天气等原因, 不能去食堂就餐, 因此对外卖送餐上门服务需求很大。 针对沈航校园, 为沈航某餐厅设计一个基于 Web 的校园外卖订餐系统, 为师生提供简便快捷的外卖订餐服务。 系统功能如下.
## 0.1 前台功能
- 用户功能。 包括用户注册, 登录和个人信息修改;
- 订餐服务。 用户订餐信息(品种, 数量, 送货地点等)提交, 修改和删除;
- 订餐查询。 包括用户个人订餐信息查询和订单状态跟踪;
**(自己添加,预定实现)**
- 餐馆列表。 凡是加盟了本校园订餐系统的餐厅, 都将通过餐馆列表显示出来, 通过设置餐厅名称的超链接, 用户可以通过点击超链接进入相应的餐厅进行菜品的浏览, 菜品的名称也设置为超链接, 一旦用户看中了菜品, 可以通过点击菜品超链接, 进入相应的菜品页面, 对菜品进行预订
- 网站公告。 用户在进入校园订餐系统页面后, 可以通过网站公告业务功能了解本站的最近更新的餐馆新闻、 最近卖的最火的热菜、 菜品的优惠资讯和最新的餐饮资讯。
- 帮助中心。 用户若是不会通过本校园订餐系统进行订餐, 可以通过本系统提供的订餐事项服务进行了解如何操作才能实现订餐服务。
- 网站介绍。 为了让用户全方位的了解本站以及为他们服务的我们, 特别推出了团队简介、 团队精神的超链接, 用户只需点击超链接就能够了解我们。 为了本站发展的更好, 特别是能够吸引优秀人才加盟本站继续完善本站的不足, 推出了加入我们的功能窗口, 只要你有才, 只有你有兴趣, 就可以通过此窗口加入我们。
- 联系我们。 我们希望通过本站的窗口为用户提供满意的服务, 只要是体验过本站服务的用户都能够用过留言本来给本站提出一些新异服务的想法, 我们将新鲜想法注入到今后的服务中心, 吸引更多的顾客来体验新异服务。
## 0.2 后台功能(管理员)
- 订单处理。 审核订单, 按订单类别和送餐时间决定订单优先级别, 订单转入配送状态;
- 配送处理。 根据订单时间和送餐地点, 自动计算送餐批次和发货时间;
- 订单查询。 按用户名, 时间等关键词查询订餐信息;
- 订单维护。 订单的增删改功能;
- 订单统计。 按用户类别, 外卖品种、 时间和地理位置(如宿舍楼、 图书馆)等对订单信息进行统计;
- 用户管理。 包括用户维护、 权限和积分管理等功能。
**(自己添加,预定实现)**
- 订单管理。 订单管理提供订单管理、 订单添加、 提餐单、 来路管理四个功能, 管理员通过订单管理业务功能来处理有关订单的业务。
- 餐馆管理。 餐馆管理提供餐馆管理、 工作审批、 工作流程三个功能, 管理员通过餐馆管理业务功能来处理有关餐馆的业务。
- 人事管理。 人事管理通过员工档案、 员工管理、 发布消息、 模块设置功能来处理有关人事的业务。
- 档案管理。 档案管理通过新闻添加、 新闻编辑、 留言管理、 派送管理功能来处理有关档案的业务。
- 短信平台。 短信平台通过短信设置、 短信发送、 短信模块、 历史记录来处理与有关短信的业务。
- 系统管理。 我们希望通过系统管理中的会员管理、 系统管理功能来处理有关会员和系统相关的业务。
## 0.3 功能需求
网上订餐系统的主要目标就是: 用户只需要登录到本系统就可以浏览到本店的所有菜系商品, 为用户提供分类, 搜索等功能, 当用户看中某一款食物时可以点其进去详情页。 详情页面提供商品数量的修改及商品具体的描述。 用户可以在本页面将商品加入到购物车方便事后的结算。 当用户进入到自己的购物车时可以看到刚才自己加入购物车的所有商品, 可以点击继续购物或者结算, 点击结算就会跳转到订单结算界面、 地址修改界面、 支付界面, 最后完成订单。 用户可以看到本系统的公告信息, 以及热门菜信息, 以及用户评价信息等。 管理员登录到后台可以对前台菜系的种类以及数量进行修改、 商品库存的实时查看、 处理订单的状态、 订单生成报表、 公告管理、 留言回复、 管理员管理、 用户管理、 系统管理等一系列功能应有尽有。
## 0.4 功能划分
本系统实现为用户实现了商品浏览、 公告浏览、 站内留言、 商品评价、 在线支付等功能。 为管理员实现了商品管理、 留言管理、 公告管理、 交易信息管理、 库存管理、 交易报表生成、 权限管理等功能。
## 0.5 功能描述
本系统是采用了Shiro安全框架, 以至于本系统许多功能必须登录才能使用, 才有权限。 用户成功登录到本系统后就可以浏览站内的菜品, 可以根据不同的分类进行浏览, 也可以根据关键字进行搜索菜品, 当用户看中某一款或者某一些菜品时, 就可以点击对应的链接进入到菜品详情页, 本页面可以将指定数量的菜品加入到购物车方便结算。 当用户点击结算时就会跳转到填写收货地址以及选择支付方式的界面, 填写地址后选择支付银行, 就会跳转到第三方易宝支付平台, 在用户输入银行卡号和手机验证码时完成支付跳转回支付成功界面, 对应的库存也会随之减少, 付款成功后就等待管理员进行发货, 之后确认收货, 最后去评价订单就完成了一次购物操作。 除此之外用户还可以浏览站内的公告、 热门菜、 商品评价列表以及站内留言等操作。 成功登陆到后台后, 可以对商品进行增删改查操作。 例如对商品的库存, 属性进行操作。 管理员还可以对公告进行操作、 回复留言、 删除留言、 交易记录统计、 打印交易信息、 权限分配等操作。
对于电商网站而言, 人们对于网站的第一印象显得特别重要, 订餐系统亦是如此。 友好的界面给人的感觉是赏心悦目的, 可以满足勾起用户的食欲, 所以对于系统首页的设计很重要。 首页包含了用户注册、 用户登录、 商品浏览、 分类查看、 公告查看、 商品搜索、 站内留言等。 普通游客只要浏览商品的权限, 不能做其他的操作, 成功登陆本系统后可以做后续的操作, 订单、 修改个人信息等。 本系统的首页如图所示:
随着前后端分离项目的热潮,前端各大框架的,前后端沟通部分也成了问题,之前服务端渲染的页面生成到前端来,现在前后端可能是两个服务器,一些技术的迁移,本框架的权限部分的设计思想,借鉴了前端大牛的想法,也有传统后端的设计方案,抛砖引玉,做个桥梁,实现前后端分离的权限的设计,代码仅供参考,思路仅供参考,相信优秀的你写自己的代码,用自己的思想会更为贴切,方便。
最终即具有统一响应结构、 前后台数据流转机制(HTTP消息与Java对象的互相转化机制)、统一的异常处理机制、参数验证机制、Cors跨域请求机制以及鉴权机制
前端设计:采用Vue的element ui ,对于前端设计者来说,应该很好理解源码。
后端设计:shiro + ssm + redis存储jwt
交互方式:前端存储jwt,在访问后端时携带,这也是唯一交互验证方式。
前期工作:设计符合需求的vue模板,路由,资源,角色,用户其中对应关系也可从数据表中体现出来
在ssm框架人基础上添加`shiro`权限管理
第一步:在`web.xml`上`shiroFilter`的拦截器设置,以及加载`spring-shiro.xml`
```
shiroFilter
org.springframework.web.filter.DelegatingFilterProxy
true
targetFilterLifecycle
true
shiroFilter
/*
```
和SpringMVC框架类似,Shiro框架也需要在web.xml中配置一个过滤器。DelegatingFilterProxy会自动到Spring容器中name为shiroFilter的bean,并且将所有Filter的操作都委托给他管理。这就要求在Spring配置中必须注入这样一个这样的Bean:
```
```
此处bean的id和web.xml中Shiro过滤器的名称必须是相同的,否则Shiro会找不到这个Bean。
第二步:导入`shiro`的`maven`依赖
```
```
第三步,配置`spring-shiro.xml`
```
/static/** = anon
/lib/** = anon
/js/** = anon
/login.jsp = anon
/login.do = anon
/logout = logout
/index.jsp = user
/** = user
```
上面配置文件的核心处就是Shiro的web过滤器的配置,当然因为Shiro的所有涉及安全的操作都要经过DefaultWebSecurityManager安全管理器,所以shiroFilter首先就要将其交给SecurityManager管理。loginUrl是账户退出或者session丢失就跳转的地址;unauthorizedUrl是账户权限验证失败跳转的地址,比如账户权限不够等;然后就是过滤器链filterChainDefinitions的配置,他和我们之前配置的.ini文件非常相似,其中主要就是配置资源的的拦截。Shiro提供了很多默认的拦截器,比如什么验证,授权等,这里举例几个比较常用的默认拦截器:
| 默认拦截器名 | 说明 | | -- | -- | | authc | 基于表单的拦截器,比如若用户没有登录就会跳转到loginUrl的地址,其拦截的请求必须是通过登录验证的,即Subject.isAuthenticated() == true的账户才能访问 | | anon | 匿名拦截器,和authc拦截器刚好作用相反。anon配置的请求允许用户为登录就等访问,一般我们配置登录页面和静态CSS等资源是允许匿名访问 | | logout | 退出拦截器,Shiro提供了一个退出的功能,配置了/logout = logout,Shiro就会生成一个虚拟的映射路径,当用户访问了这个路径,Shiro会自动清空缓存并跳转到loginUrl页面 | | user | 用户拦截器,和authc拦截器很类似,都是账户为登录的进行拦截并跳转到loginUrl地址;不同之处在于authc允许账户必须是通过Subject.siAuthenticated() ==true的;而user不仅允许登录账户访问,通过rememberMe登录的用户也能访问
第四步:写Myrealm类继承AuthorizingRealm,认证和授权,如果用户为登录,将跳转到loginUrl进行登录,登录表单中,包含了两个主要参数:用户名username、密码password(这两个参数名称不是固定的,但是要和FormAuthenticationFilter表单过滤器的参数配置要对应)。
用户输入这两个用户名和密码后提交表单,通过绑定了SecurityManager的SecurityUtils得到Subject实例,然后获取身份验证的UsernamePasswordToken传入用户名和密码。
调用subject.login(token)进行登录,SecurityManager会委托Authenticator把相应的token传给Realm,从Realm中获取身份认证信息。
Realm可以是自己实现的Realm,Realm会根据传入的用户名和密码去数据库进行校验(提供Service层登录接口)。
Shiro从Realm中获取安全数据(如用户、身份、权限等),如果校验失败,就会抛出异常,登录失败;否则就登录成功。
```
@Controller
public class LoginController {
@RequestMapping("/login")
public String login(
@RequestParam(value = "username", required = false) String username,
@RequestParam(value = "password", required = false) String password,
Model model) {
String error = null;
if (username != null && password != null) {
//初始化
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
//登录,即身份校验,由通过Spring注入的UserRealm会自动校验输入的用户名和密码在数据库中是否有对应的值
subject.login(token);
return "redirect:index.do";
}catch (Exception e){
e.printStackTrace();
error = "未知错误,错误信息:" + e.getMessage();
}
} else {
error = "请输入用户名和密码";
}
//登录失败,跳转到login页面,这里不做登录成功的处理,由
model.addAttribute("error", error);
return "login";
}
}
```
如上,当login()映射方法得到用户输入的用户名和密码后调用subject.login(token)进行登录,随后就是通过Realm进行登录校验,如果登录失败就可能抛出一系列异常,比如UnknownAccountException用户账户不存在异常、IncorrectCredentialsException用户名或密码错误异常、LockedAccountException账户锁定异常... 。
可能,你也看到有些示例中在Controller层中没有处理登录成功,而是在ShiroFilterFactoryBean中配置successUrl,很多博文中讲到:如果登录成功Shiro会自动跳转到登录前访问的地址,如果找不到登录前访问的地址,就会跳转到successUrl中配置的地址;
第五步:对应的shiro标签的jsp页面
实际的应用中,其一是要求用户简单地进行注册登录,其二是对其授权,附带的有session管理和加密,所以诞生了shiro这款框架,而前后端分离的趋势,使得shiro更好地应用于前端更有实际意义,而目前像vue类似的前端框架也很热门,同时正好接触到了vue,所以为了适应要求,抽象出来基于前后端完全分离的权限框架。
另外,一般认为权限只能是后端来做,但是前后端分离的情况下呢?这样岂不是很没有意义。况且关于vue的权限控制在业界相对没有主流的方案,百度一下,这方面的资料也不多,基本都很零散。
前端地址:https://github.com/hulichao/zhcc-view-source.git
后端地址:https://github.com/hulichao/zhcc-server.git
基本想法就是,用到Vuex 和 Vue Router 前者用来做状态控制,后者绑定路由,这样权限可以直接对应到组件上,某个用于只能访问某个组件,而不用将每个组件都加上权限控制,重要的是还有单点登录。
所以抛砖引玉,写一个通用框架,(至少是通用想法)具体可以模块化来可插拔就ok 了。
非动态路由的问题是只能在拿到权限之后初始化Vue实例,因此必须把登陆页从SPA中剥离出来做成一个独立的页面,用户登录/退出操作需要在两个url之间跳转,体验略差。
另一种做法是直接用所有路由实例化应用,当用户登录拿到权限后再通过元素操作隐藏越权菜单,这时用户还可以手动输入地址访问越权页面,因此还需要给路由加beforeEach钩子来限制路由访问,路由钩子本身会增加一定的性能压力,而且实例化即注册所有路由也会使前端加载冗余的路由组件。
本系统采用的在初始路由注册首页和登录页,并在拿到token后得到权限,然后在实例化Vue实例。路由代码如下:
```
const router = new Router({
routes: [
{
path: '/login',
name: "login",
component: LoginView,
meta: { requiresAuth: false }
},{
path: '/index',
redirect: '/',
meta: { requiresAuth: true }
}
]
});
generateIndexRouter();
// 验证token,存在才跳转
router.beforeEach((to, from, next) => {
let token = sessionStorage.getItem('token')
if(to.path === '/') {
if(!token) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
return
}
}
if(to.meta.requiresAuth) {
if(token) {
next()
} else {
next({
path: '/login',
query: { redirect: to.fullPath }
})
}
} else {
next()
}
});
router.afterEach((to, from) => {
// 设置面包屑
let breadCrumbItems = []
let homePageCrumb = {
title: '首页',
to: '/'
}
breadCrumbItems.push(homePageCrumb)
if(to.meta.nameFullPath) {
let fullPathSplit = to.meta.nameFullPath.split('/')
fullPathSplit.forEach(( item, index ) => {
let routerBreadCrumb = {
title: item,
to: (index == fullPathSplit.length - 1 ? to.path : '')
}
breadCrumbItems.push(routerBreadCrumb)
});
}
// 更新到state
router.app.$store.dispatch('setBreadcurmbItems', breadCrumbItems)
})
// 生成首页路由
function generateIndexRouter() {
if (!sessionStorage.routers) {
return
}
let indexRouter = {
path: "/",
name: "/",
component: resolve => require(['@/views/home/index'], resolve),
children: [
...generateChildRouters()
]
}
router.addRoutes([indexRouter])
}
// 生成嵌套路由(子路由)
function generateChildRouters() {
let routers = JSON.parse(sessionStorage.routers)
let childRouters = []
for(let router of routers) {
if(router.code != null) {
let routerProps = JSON.parse(router.properties)
let childRouter = {
path: router.url,
name: router.code,
component: resolve => require(['@/views/' + router.code + '/index'], resolve),
meta: { routerId: router.id, requiresAuth: routerProps.meta.requiresAuth, nameFullPath: routerProps.nameFullPath }
}
childRouters.push(childRouter)
}
}
return childRouters
}
export default router;
```
## 前后端数据格式约定
既然是restful风格,必然有通用返回状态的类,遵循网上开源原则,一类继承hashmap这样达到可以返回任意的数据,通用的数据就是code.msg.data这些,如果有分页会另外加分页,还有一种是,data.msg.state(code).token + 分页类型的数据如:
```
"data": {
"list": null,
"pagebar": {
"page": 1,
"total": 2,
"limit": 10
}
},
"msg": "error",
"state": 0,
"is_redirect": true,
"redirect_url": "http://qq.com",
"token": null
```
本项目考虑到后期的扩展性,用到了第一类,其中实现了常用的失败和成功的状态码及其响应,类名设计为Result,位于zhcc-common下面,一般性地是封装到ResponseEntity中返回。
## 前后端数据接口约定
分别对应http协议的get/put/post/delete方法,后端权限是:read/:update/:create/:delete
```
case "get":
permissionString += ":read";
break;
case "put":
permissionString += ":update";
break;
case "post":
permissionString += ":create";
break;
case "delete":
permissionString += ":delete";
```
## 验证部分
用的是com.baidu.unbiz.fluentvalidator.ValidationError 而不是hibernateValidator 减轻服务端编程等的压力。直接在controller里面验证,最后封装到Result的fail方法里面返回。
## 权限的设计
权限的控制主要分为4大类,主要是基于RBAC原理。
路由,资源,角色,用户
路由级别和组件级别可控制
## 过程设计
1.权限设计
2.异常设计
3.字典和其他接口设计
4.前后的通讯设计
## 密码加密和解密
常见的加密方式有很多,这里我们介绍Shiro中提供的一套散列算法加密方式。散列算法,是一种不可逆的算法(自然是要不可逆的,因为可逆的算法破解起来也很容易,所以不可逆的算法更安全),常见的散列算法如MD5,、SHA,但是我们再网上看到很多破解MD5加密的网站,不是说散列算法是不可逆的吗?为什么还存在那么多破解密码的网站?其实散列算法确实是不可逆的,即使是常见的MD5加密也是不可逆的加密方式,而网上的破解网站并不是能够逆向算出这个加密密码,而是通过大数据的方式得出来的,相当于,MD5解密的网站中存在一个很大的数据库,里面存放了用户常见的加密密码,然后当用户再用此密码解密时,再从数据库中比对加密后的MD5密码,如果存在就能得到原密码了。为了避免这种情况,引入了盐salt的概念,如果能通过大数据的方式破解MD5的加密,但如果在加密的密码中再添加一组数据进行混淆,破解起来就相当难了,因为添加的salt只有我们自己知道是什么。
自定义一套散列算法:
1. 实例化一个RandomNumberGenerator对象生成随机数,可以用来设置盐值。
2. 设定散列算法的名称和散列迭代次数。
3. 调用SimpleHash()构造方法,将算法名称、用户输入的密码、盐值、迭代次数传入。
4. 通过SimpleHash()构造方法,Shiro能自动帮我们对密码进行加密,并调用实体类对象的setter方法将密码设置进去。
```
@Component
public class PasswordHelper {
//实例化RandomNumberGenerator对象,用于生成一个随机数
private RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
//散列算法名称
private String algorithName = "MD5";
//散列迭代次数
private int hashInterations = 2;
//加密算法
public void encryptPassword(User user){
if (user.getPassword() != null){
//对user对象设置盐:salt;这个盐值是randomNumberGenerator生成的随机数,所以盐值并不需要我们指定
user.setSalt(randomNumberGenerator.nextBytes().toHex());
//调用SimpleHash指定散列算法参数:1、算法名称;2、用户输入的密码;3、盐值(随机生成的);4、迭代次数
String newPassword = new SimpleHash(
algorithName,
user.getPassword(),
ByteSource.Util.bytes(user.getCredentialsSalt()),
hashInterations).toHex();
user.setPassword(newPassword);
}
}
//getter/setter ....
}
```
https://www.liangzl.com/get-article-detail-18526.html
https://github.com/TyCoding/ssm