# 奶茶点餐系统
**Repository Path**: li-huahua/milkteaProject
## Basic Information
- **Project Name**: 奶茶点餐系统
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 25
- **Forks**: 2
- **Created**: 2020-12-17
- **Last Updated**: 2025-10-24
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## 东莞理工学院网络空间安全学院
| **课程名称:** | 企业级开发框架专题 | **学期**:2020秋季 |
| :----------------- | :-------------------------------------------: | :------------------: |
| **实验名称**: |期末大作业:基于微信小程序的新零售移动电商系统设计与实验 | **实验序号**:期末大作业 |
| **组长姓名**:宁龙辉 | **组长学号**:201741413146 | **班级**:18网工1班 |
| **实验地址**:宿舍 | **实验日期**:2020-12-10 | **指导老师**:黎志雄 |
| **教师评语**:XXX | **实验成绩**:XXX | **同组同学**:李华铧 201841413114 黄仁欢 201841413111|
## 期末大作业:基于微信小程序的新零售移动电商系统设计与实验
**基于小程序和Spring Boot的奶茶点单系统**
### 一.项目背景
受新冠疫情影响,消费者闭门不出,线下门店纷纷关闭,实体经济收到重大冲击。除了疫情的挑战,传统
零售与传统电商,一直被效率、场景、管控等问题困扰。
新零售赋能传统零售转型升级。线上商城与线下门店的交易无缝缝合,通过技术改变消费者的习惯,集合
结合高效的物流配送,极大提高消费者的购物体验。基于微信的生态圈用户流量,让平台更快的传播获客,吸引
海量用户资源。
### 二.需求分析
本项目需要分为两个客户端:
1. **用户端**

2. **后台管理端**


### 三.技术栈
#### 客户端
微信小程序
#### 后台前端
React,Ant组件库
#### 后台后端
SpringBoot,Mysql
### 四.具体实现
#### 客户端
客户端采用小程序为客户提供图形界面,方便客户查看商品,下订单,查看订单状态
#### 前端
##### 1.登录:
客户可以使用微信一键登录进入小程序,小程序会保存用户的登录信息


##### 2.首页(展示推荐信息,公告图片):

##### 3.点单页面
点单页面为客户展示商品列表以供选购

##### 4.购物车页面
客户可以在购物车页面查看自己选购了那些商品,并提供下单入口

##### 5.订单页面
订单页面向客户展示了客户的历史订单,以及未完成订单

##### 6.客户个人信息页面
客户可以在这个页面查看自己的个人信息,退出登录

##### 小程序端操作说明
##### 7.下订单

##### 8.在购物车结算

##### 9.支付

##### 10.未取餐订单
支付以后可以在未取餐订单中查看已下订单

#### 后端
后端主要提供的功能:
##### 1.“为你推荐”接口
在首页中随机显示8款奶茶
```java
@GetMapping("recommend")
@ApiOperation(value = " 首页“为你推荐”")
public List recommend() {
随机选择8款奶茶向用户推荐
Integer nums = 8;
Integer start = 1;
Integer end = countMilktea();
//1.创建集合容器对象
List list = new ArrayList();
List ret = new ArrayList();
//2.创建Random对象
Random r = new Random();
//循环将得到的随机数进行判断,如果随机数不存在于集合中,则将随机数放入集合中,如果存在,则将随机数丢弃不做操作,进行下一次循环,直到集合长度等于nums
while (list.size() != nums) {
Integer num = r.nextInt(end - start) + start;
if (!list.contains(num)) {
list.add(num);
}
}
for (Object l : list) {
NumberFormat formatter = NumberFormat.getNumberInstance();
String s = formatter.format(Integer.parseInt(l.toString()));
// System.out.println(s);
ret.add(selectOneMilktea(s));
}
return ret;
}
```
mapper语句:
```xml
```
##### 2.按种类排序返回全部奶茶接口
用于小程序点单页面按照种类显示奶茶。先获取奶茶的种类数,然后根据种类id将全部奶茶按照种类装到各个数组中,再将这些数组装到一个大数组中
```java
@GetMapping("selectAllByType")
@ApiOperation(value = "返回全部的奶茶 会按奶茶种类排序")
public List> selectAllByType(String type) {
List> res= new ArrayList>();
int countType = countType();
for (int i = 0; i < countType; i++) {
List temp = selectByType(Integer.toString(i+1) );
res.add(temp);
}
return res;
}
```
mapper语句:
```xml
```
##### 3.用户登录接口
用于用户使用微信进行登录的接口。先判断从小程序获得的openid是否为空,为空则登录失败返回空值,否则查询数据库该openid是否已存入数据库,未存入则将该openid与用户昵称插入数据库并且后端返回存有openid该用户对象,否则只更新用户昵称并返回存有对应用户信息的用户对象。
```java
@PostMapping("login")
@ApiOperation(value = "登录")
public User login(@RequestBody LoginInfoDTO loginInfoDTO) {
String openid = loginInfoDTO.getOpenid();
String nickname = loginInfoDTO.getNickname();
User res = userService.login(openid);
if (res.getOpenid() != null) {
res.setNickname(nickname);
userService.setNickName(res);
}
return res;
}
//userService.login(String openid)方法
@Override
public User login(String openid) {
if (openid.equals("")) {
System.out.println("ID是空的,无法登陆");
return new User();
}
User successUser = userMapper.login(openid);
if (null == successUser) {
this.logon(openid);
User user = new User();
user.setOpenid(openid);
return user;
}
return successUser;
}
```
主要mapper语句:
```xml
insert into cusaccinfo(openid) values(#{openid})
```
##### 4.下单接口
用于用户下单的接口。后端先将订单时间、总价格、用户openid和用户地址信息插入数据库中的orderinfo表(订单信息表),获取自增的orderid后将从小程序获取的奶茶信息拆分开来,按照奶茶的id组成为单条条目,依照orderid插入数据库中的comselectinfo表(订单详情表)中
```java
@PostMapping("addOneOrderByStr")
@ApiOperation(value = "增加一个订单条目")
public boolean addOneOrderByStr(String openid,String drinkStr,String address,String phoneNum,String name)
{
//转对象集合
JSONArray json = JSONArray.fromObject(drinkStr);
List drinkList = (List) JSONArray.toCollection(json, OrderDrink.class);
//加入时间
Timestamp time = new Timestamp(System.currentTimeMillis());
double totalPrice = 0;
//统计价格
for (OrderDrink d : drinkList) {
totalPrice = totalPrice + d.getDrinkPrice();
}
//先插入orderinfo表
Order order = new Order();
order.setOpenid(openid);
order.setTime(time);
order.setTotal(totalPrice);
order.setStatus(0);
order.setAddress(address);
order.setPhonenum(phoneNum);
order.setName(name);
if (orderMapper.addOneOrderInfo(order) == 0)
return false;
//后插入selectinfo表
SelectInfo selectInfo;
//获取orderinof表最大的orderid
int orderId = orderMapper.findLastOrderId();
//拆分drinklist,组成为单条条目,插入数据库
for (OrderDrink d : drinkList) {
selectInfo = new SelectInfo();
selectInfo.setId(d.getDrinkId());
selectInfo.setDescription(d.getDrinkInfo());
selectInfo.setNumber(d.getDrinkNum());
selectInfo.setPrice(d.getDrinkPrice());
selectInfo.setOrderId(orderId);
totalPrice = totalPrice + d.getDrinkPrice();
if (orderMapper.addOneSelectInfo(selectInfo) == 0)
return false;
}
return true;
}
```
mapper语句:
```xml
insert into comselectinfo value(#{orderId},#{id},#{number},#{description},#{price})
insert into OrderInfo value(null,#{openid},#{time},#{total},#{status},#{address},#{phonenum},#{name})
```
##### 5.查询制作中订单接口
```java
@GetMapping("findMakingMiniOrder")
@ApiOperation(value = "查询用户制作中订单缩略信息")
public List findMakingMiniOrder(String openid) {
List entryList = orderMapper.findMakingMiniOrder(openid);
List orderList = new ArrayList<>();
List drinkIdList = new ArrayList<>();
List imageList = new ArrayList<>();
MiniOrder orderTmp = new MiniOrder();
int drinkId;
int orderId = -1;
double total = 0;
//查询结果为空
if (entryList.size() == 0)
return null;
//根据订单编号合并订单条目,组成一个完整订单
for (MiniOrderEntry e : entryList) {
//当前条目属于一个新订单
if (orderId != e.getOrderId()) {
//不是第一个订单才需要添加到orderList
if (orderId != -1) {
orderTmp.setTotal(total);
orderTmp.setDrinkIdList(drinkIdList);
orderTmp.setImageList(imageList);
orderList.add(orderTmp);
}
//清空上个订单用到的变量
orderTmp = new MiniOrder();
drinkIdList = new ArrayList<>();
imageList = new ArrayList<>();
total = 0;
//不是第一个订单才需要清空drinkList,imageList
if (orderId != -1) {
drinkIdList.clear();
imageList.clear();
}
//
orderId = e.getOrderId();
orderTmp.setOrderId(e.getOrderId());
orderTmp.setOrderTime(e.getTime());
orderTmp.setOpenid(e.getOpenid());
total += e.getPrice();
//加入一种饮品
drinkIdList.add(e.getId());
imageList.add(e.getImage());
}
//当前条目与上条条目属于同一个订单,仅添加饮品
else {
total += e.getPrice();
drinkIdList.add(e.getId());
imageList.add(e.getImage());
}
}
orderTmp.setTotal(total);
orderTmp.setDrinkIdList(drinkIdList);
orderTmp.setImageList(imageList);
orderList.add(orderTmp);
return orderList;
}
```
mapper语句:
```xml
```
##### 6.查询已完成订单接口
```java
@GetMapping("findCompletedMiniOrder")
@ApiOperation(value = "查询用户已完成订单缩略信息")
public List findCompletedMiniOrder(String openid) {
List entryList = orderMapper.findCompletedMiniOrder(openid);
List orderList = new ArrayList<>();
List drinkIdList = new ArrayList<>();
List imageList = new ArrayList<>();
MiniOrder orderTmp = new MiniOrder();
int drinkId;
int orderId = -1;
double total = 0;
//查询结果为空
if (entryList.size() == 0)
return null;
//根据订单编号合并订单条目,组成一个完整订单
for (MiniOrderEntry e : entryList) {
//当前条目属于一个新订单
if (orderId != e.getOrderId()) {
//不是第一个订单才需要添加到orderList
if (orderId != -1) {
orderTmp.setTotal(total);
orderTmp.setDrinkIdList(drinkIdList);
orderTmp.setImageList(imageList);
orderList.add(orderTmp);
}
//清空上个订单用到的变量
orderTmp = new MiniOrder();
drinkIdList = new ArrayList<>();
imageList = new ArrayList<>();
total = 0;
//不是第一个订单才需要清空drinkList,imageList
if (orderId != -1) {
drinkIdList.clear();
imageList.clear();
}
//
orderId = e.getOrderId();
orderTmp.setOrderId(e.getOrderId());
orderTmp.setOrderTime(e.getTime());
orderTmp.setOpenid(e.getOpenid());
total += e.getPrice();
//加入一种饮品
drinkIdList.add(e.getId());
imageList.add(e.getImage());
}
//当前条目与上条条目属于同一个订单,仅添加饮品
else {
total += e.getPrice();
drinkIdList.add(e.getId());
imageList.add(e.getImage());
}
}
orderTmp.setTotal(total);
orderTmp.setDrinkIdList(drinkIdList);
orderTmp.setImageList(imageList);
orderList.add(orderTmp);
return orderList;
}
```
mapper语句:
```xml
```
### 后台
#### 前端
#####登录页面

##### 防水墙

前端主页面主要有几个模块组成:左上角是LOGO,右上角是账户信息,左侧栏是菜单,占据页面大部分内容的是内容展示框

前端的菜单有
##### 1.首页
首页向用户展示一些重要统计数据的图表信息,让经营者能够清楚知道自己的营收情况,订单数目,下单人数,品类销量排行

##### 2.订单管理
给经营者管理订单,或是给制作者查看订单信息,根据信息制作相应奶茶

制作人员可以点击制作完成,这样用户就可以得到取餐号在前台取餐


可以在已完成订单中找到

用户则可以在自己的小程序端看到:

查看历史订单记录,也可以起到查账的作用

##### 3.商品管理
经营者可以在这个页面添加或者删除商品

添加商品操作:



其中商品图标是动态获取的

通过COSBrowser,用户可以放自己的图片进去,然后就可以在添加奶茶的时候使用
##### banner管理
banner指的是小程序首页的展示图片,可以从此页面进行更换
#### 后端
后端主要提供的功能:
##### 1.管理员登录接口
管理员使用账号密码登录,后端判断是否可以登录,如果不能登录则返回错误信息,成功则可以得到相关权限并跳转到后台主界面
控制器:
```java
//AdminLoginController
@RestController
@Api(tags = "管理员登录控制器")
public class AdminLoginController {
@Autowired
private AdminService adminService;
@PostMapping("/admin/login")
@ApiOperation(value = "管理员登录")
public Result> login(@RequestParam String username,
@RequestParam String password_md5,
HttpSession session) {
Admin admin = adminService.checkAdmin(username, password_md5);
if (admin != null) {
// admin.setPassword_md5("");
session.setAttribute("admin",admin);
return ResultUtils.success(admin);
}else
{
return ResultUtils.error(-14,"用户名或密码错误");
}
}
}
```
实体类:
```java
public class Admin extends BaseRowModel {
@ExcelProperty(value = "id",index = 0)
private int id;
@ExcelProperty(value = "账号",index = 1)
private String username;
@ExcelProperty(value = "密码",index = 2)
private String password_md5;
public Admin() {
}
public Admin(int id, String username, String password_md5) {
this.id = id;
this.username = username;
this.password_md5 = password_md5;
}
public Admin(int id, String username) {
this.id = id;
this.username = username;
}
public Admin(String username, String password_md5) {
this.username = username;
this.password_md5 = password_md5;
}
public Admin(String username) {
this.username = username;
//……省略getter and setter
}}
```
相关SQL语句
```MYSQL
select *
from admin
where username = #{username}
and password = #{password_md5}
```
##### 2.获取奶茶列表
管理员可以查看目前商品的列表,需要登录后操作

sql语句:
```mysql
select *
from milktea
```
实体类:
```java
public class Milktea extends BaseRowModel {
@ExcelProperty(value = "编号",index = 0)
String id;
@ExcelProperty(value = "品名",index = 1)
String name;
@ExcelProperty(value = "单价",index = 2)
String price;
@ExcelProperty(value = "类型编号",index = 3)
String type;
@ExcelProperty(value = "类型",index = 4)
String typeName;
@ExcelProperty(value = "展示图片",index = 5)
String image;
//……省略Getter and Setter
}
```
Controller:
```java
@RestController
@Api(tags = "管理员控制器")
public class AdminController {
//…………
@GetMapping("/admin/order/getTodayInfo")
@ApiOperation(value = "统计今日订单数")
public Integer getTodayOrderNum() {
return orderService.getTodayOrderNum();
}
//…………
}
```
奶茶列表导出Excel:
数据库中的所有奶茶可以导出一份Excel方便管理员管理查看

实体类依旧是同上,不同的是接口实现:
Controller:
```java
@Controller
@Api(tags="Excle导出")
public class getExcleController {
//……
MilkteaService milkteaService;
//……
@GetMapping("/getMilkTeaExcle")
@ApiOperation(value = "下载全部奶茶信息")
public void getAllMilkTea(HttpServletResponse response){
List milkteas= milkteaService.selectAllMilktea();
SimpleDateFormat sdf=new SimpleDateFormat("yyyyMMdd");
String time=sdf.format(new Date());
time=time.replaceAll("-","").replaceAll(":","").replaceAll(" ","");
String fileName="全部奶茶信息";
ExcelUtil.writeExcel(response,milkteas,fileName+time,fileName,new Milktea());
}
//……
}
```
##### 3.新增一个品种的奶茶
商品(奶茶)的增加操作
演示且看后台前端部分的内容
SQL语句:
```mysql
INSERT INTO milktea (id, type, price, TypeName, image, name) VALUES (#{id},#{type},#{price},#{typeName},#{image},#{name})
```
对应实体类与2.一致
Controller:
```java
@RestController
@Api(tags = "管理员控制器")
public class AdminController {
//…………
@PostMapping("/admin/milktea")
@ApiOperation(value = "新增奶茶信息")
public Result> saveMilkteaInfo(@RequestBody Milktea milktea) {
return ResultUtils.success(milkteaService.saveMilktea(milktea));
}
//…………
}
```
##### 4.根据ID修改奶茶信息
MilkteaMapper.xml:
```xml
……
update milktea
type = #{type},
price = #{price},
TypeName = #{typeName},
image = #{image},
name = #{name},
id = #{id}
……
```
Controller:
```java
@RestController
@Api(tags = "管理员控制器")
public class AdminController {
//…………
@PutMapping("/admin/milktea")
@ApiOperation(value = "根据ID修改奶茶")
public Result> updateMilkteaInfo(@RequestBody Milktea milktea) {
return ResultUtils.success(milkteaService.updateMilktea(milktea));
}
//…………
}
```
##### 5.根据ID查询奶茶信息
对应实体类与2.一致
Controller:
```java
@RestController
@Api(tags = "管理员控制器")
public class AdminController {
//…………
@GetMapping("/admin/milktea/{milkteaId}")
@ApiOperation(value = "根据ID查询奶茶")
public Result> getMilkteaById(@PathVariable("milkteaId") String milkteaId) {
return ResultUtils.success(milkteaService.selectOneMilktea(milkteaId));
}
//…………
}
```
##### 6.根据ID删除奶茶信息
略
##### 7.统计近N日每日销售量

给前端返回的是Json以便解析
对应实体类:
```java
public class OrderInfoChart extends BaseRowModel {
@ExcelProperty(value = "时间",index = 0)
String time;
@ExcelProperty(value = "订单数",index = 1)
int orderNum;
//省略Getter and Setter
}
```
Controller:
```java
@RestController
@Api(tags = "管理员控制器")
public class AdminController {
//…………
@GetMapping("/admin/orders/getOrderInfoAnyTime")
@ApiOperation(value = "统计历史每日订单数")
public Result> getOrderInfoAnyTime(int days) {
return ResultUtils.success(orderService.getOrderInfo_anyTime(days));
}
//…………
}
SQL语句:
```mysql
SELECT DATE_FORMAT( Time, '%Y-%m-%d' ) time, count(*) orderNum
FROM orderinfo
where DATE_SUB(CURDATE(), INTERVAL #{days} DAY) <= date(Time)
group by DATE_FORMAT( Time, '%Y-%m-%d' )
```
将每日销售量 导出为Excel:

Controller:
```java
@Controller
@Api(tags="Excle导出")
public class getExcleController {
//……
@Resource
OrderService orderService;
//……
@GetMapping("/getOrderInfoChart")
@ApiOperation(value = "下载订单数目统计")
public void getAllMilkTea(HttpServletResponse response,int days){
List OrderInfo= orderService.getOrderInfo_anyTime(days);
System.out.println(OrderInfo.get(0).getTime());
SimpleDateFormat sdf=new SimpleDateFormat("yyyyMMdd");
String time=sdf.format(new Date());
time=time.replaceAll("-","").replaceAll(":","").replaceAll(" ","");
String fileName="订单数目统计";
ExcelUtil.writeExcel(response,OrderInfo,fileName+time,fileName,new OrderInfoChart());
}
//……
}
```
提供给制作人员的接口:
##### 1.查看未制作订单
制作人员可以在后台查看未制作订单以及订单描述信息,根据后台给出的提示进行制作商品

##### 2.更新订单状态

订单制作完成以后,制作员可以更新订单状态
### 总结
本学期的课程以及本次实验让我们实践了本学期学习的SpringBoot和使用gitee协同开发,并在本次大实验中学习了小程序开发和前端的开发框架的使用。
本次实验困难重重,好在给足了时间让我们去学习,让我们在本次实验中收获颇丰。