源码地址:https://gitee.com/programmer-xiao-kai/reggie_tack_out
本项目(瑞吉外卖)是专门为餐饮企业(餐厅、饭店)定制的一款软件产品,包括系统管理后台和移动端应用两部分。其中系统管理后台主要提供给餐饮企业内部员工使用,可以对餐厅的菜品、套餐、订单等进行管理维护。移动端应用主要提供给消费者使用,可以在线浏览菜品、添加购物车、下单等。
创建一个实体类Employee和employee表进行映射
创建Controller,Service,Mapper
导入返回结果类R
此类是一个通用类R,服务端响应的所有结果最终都会包装成此种类型返回给前端页面
@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
/**
* 员工登录
* @param request
* @param employee
* @return
*/
@PostMapping("/login")
public R<Employee> login(HttpServletRequest request,@RequestBody Employee employee){
//1、将页面提交的密码password进行md5加密处理
String password = employee.getPassword();
password = DigestUtils.md5DigestAsHex(password.getBytes());
//2、根据页面提交的用户名username查询数据库
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Employee::getUsername,employee.getUsername());
Employee emp = employeeService.getOne(queryWrapper);
//3、如果没有查询到则返回登录失败结果
if(emp == null){
return R.error("登录失败");
}
//4、密码比对,如果不一致则返回登录失败结果
if(!emp.getPassword().equals(password)){
return R.error("登录失败");
}
//5、查看员工状态,如果为已禁用状态,则返回员工已禁用结果
if(emp.getStatus() == 0){
return R.error("账号已禁用");
}
//6、登录成功,将员工id存入Session并返回登录成功结果
request.getSession().setAttribute("employee",emp.getId());
return R.success(emp);
}
}
用户点击页面中退出按钮,发送请求,请求地址为/employee/logout,请求方式为POST
/**
* 员工退出
* @param request
* @return
*/
@PostMapping("/logout")
public R<String> logout(HttpServletRequest request){
//清理Session中保存的当前登录员工的id
request.getSession().removeAttribute("employee");
return R.success("退出成功");
}
方法:使用过滤器或者拦截器,中判断用户是否已经完成登录,如果没有登录则跳转到登录页面
(1)、页面发送ajax请求,将新增员工页面中输入的数据以json的数据形式提交到服务端
(2)、服务端Controller接收页面提交的数据并调用Service将数据进行保存
(3)、Service调用Mapper操作数据库,保存数据
/**
* 新增员工
* @param employee
* @return
*/
@PostMapping
public R<String> save(HttpServletRequest request,@RequestBody Employee employee){
log.info("新增员工,员工信息:{}",employee.toString());
//设置初始密码123456,需要进行md5加密处理
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
//此部分抽取到common处
//employee.setCreateTime(LocalDateTime.now());
//employee.setUpdateTime(LocalDateTime.now());
//获得当前登录用户的id
//Long empId = (Long) request.getSession().getAttribute("employee");
//employee.setCreateUser(empId);
//employee.setUpdateUser(empId);
employeeService.save(employee);
return R.success("新增员工成功");
}
- 页面发送ajax请求,将分页查询参数(page,pageSize,name) 提交到服务端
- 服务端Controller接收页面提交的数据并调用Service查询数据
- Service调用Mapper操作数据库,查询分页数据
- Controller将查询到的分页数据响应给页面
- 页面接收到分页数据并通过ElementUI的Table组件展示到页面上
/**
* 员工信息分页查询
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){
log.info("page = {},pageSize = {},name = {}" ,page,pageSize,name);
//构造分页构造器
Page pageInfo = new Page(page,pageSize);
//构造条件构造器
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper();
//添加过滤条件
queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
//添加排序条件
queryWrapper.orderByDesc(Employee::getUpdateTime);
//执行查询
employeeService.page(pageInfo,queryWrapper);
return R.success(pageInfo);
}
在员工管理列表页面,可以对某个员工账号进行启用或者禁用操作。账号禁用的员工不能登录系统,启用后的员工可以正常登录。
需要注意,只有管理员(admin用户)才可以对其他普通用户进行启动、禁用等操作,所以普通的用户登录系统后启动、禁用annual不显示。
启用、禁用员工账号、本质上就是一个更新操作,也就是对status状态字段进行操作,在controller进行update的方法,此方法就是一个通用的修改员工的办法。
问题描述:
通过观察控制台输出的SQL页面发现页面传递过来的员工id的值和数据库中的id值不一样,由于分页查询时js对long型数据进行处理时丢失精度,导致提交的id和数据库中的值不一致
解决办法:
我们可以在服务端给页面响应json数据时处理,将long型数据统一转为字符串。
在员工管理列表页面点击编辑按钮,跳转到编辑页面,在编辑页面回显员工信息并进行修改,最后点击保存按钮。完成编辑操作
/**
* 根据id修改员工信息
* @param employee
* @return
*/
@PutMapping
public R<String> update(HttpServletRequest request,@RequestBody Employee employee){
log.info(employee.toString());
long id = Thread.currentThread().getId();
log.info("线程id为:{}",id);
//此部分抽取到common处
//Long empId = (Long)request.getSession().getAttribute("employee");
//employee.setUpdateTime(LocalDateTime.now());
//employee.setUpdateUser(empId);
employeeService.updateById(employee);
return R.success("员工信息修改成功");
}
对于新增员工时需要设置的创建时间,创建人,修改时间,修改人等字段,在编辑员工时需要设置修改时间和修改人等字段。
MybatisPlus 公共字段自动填充,也就是在插入或者更新的时候为指定的字段,提供赋予指定的值,使用它的好处就是可以统一进行处理,避免了重复的代码。
还有一个小问题就是我们在自动填充createUser和updateUser时,我们的UserID没有动态获取到Id的值,故我们需要改造成动态获取当前用户的id
实现步骤:
/**
* 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id
*/
public class BaseContext {
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
/**
* 设置值
* @param id
*/
public static void setCurrentId(Long id){
threadLocal.set(id);
}
/**
* 获取值
* @return
*/
public static Long getCurrentId(){
return threadLocal.get();
}
}
后台系统在可以
页面(backend/page/category/list.html)发送ajax请求,将新增分类窗口输入的数据以json形式提交到服务端服务端Controller接收页面提交的数据并调用Service将数据进行保存
Service调用Mapper操作数据库,保存数据
@Autowired
private CategoryService categoryService;
/**
* 新增分类
* @param category
* @return
*/
@PostMapping
public R<String> save(@RequestBody Category category){
log.info("category:{}",category);
categoryService.save(category);
return R.success("新增分类成功");
}
需求分析
代码开发
/**
* 分页查询
* @param page
* @param pageSize
* @return
*/
@GetMapping("/page")
public R<Page> page(int page,int pageSize){
//分页构造器
Page<Category> pageInfo = new Page<>(page,pageSize);
//条件构造器
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
//添加排序条件,根据sort进行排序
queryWrapper.orderByAsc(Category::getSort);
//分页查询
categoryService.page(pageInfo,queryWrapper);
return R.success(pageInfo);
}
/**
* 根据id删除分类
* @param id
* @return
*/
@DeleteMapping
public R<String> delete(Long id){
log.info("删除分类,id为:{}",id);
//categoryService.removeById(id);
categoryService.remove(id);
return R.success("分类信息删除成功");
}
前面我们已经实现了根据id删除分类的功能,但是并没有检查删除的分类是否关联了菜品或者套餐,所以我们需要进行功能完善。
要完善分类删除功能,需要先准备基础的类和接口:
1、实体类Dish和Setmeal
2、Mapper接口DishMapper和SetmealMapper
3、Service接口DishService和SetmealService
4、Service实现类DishServicelmpl和SetmealServicelmpl
public interface SetmealService extends IService<Setmeal> {
/**
* 新增套餐,同时需要保存套餐和菜品的关联关系
* @param setmealDto
*/
public void saveWithDish(SetmealDto setmealDto);
/**
* 删除套餐,同时需要删除套餐和菜品的关联数据
* @param ids
*/
public void removeWithDish(List<Long> ids);
}
在完成管理列表页面点击修改按钮,弹出修改窗口回显分类信息并进行修改,最后点击确定按钮完成修改操作。
文件上传,也称为upload,是指将本地图片、视频、音频等文件上传到服务器上,可以供其他用户浏览或下载的过程。文件上传在项目中应用非常广泛,我们经常发微博、发微信朋友圈都用到了文件上传功能。
文件上传时,对页面的form表单由如下要求:
<form method="post" action="/common/upload" enctype="multipart/form-data">
<input name="myFile" type="file" />
<input type="submit" value="提交"/>
form>
文件上传是的输入框 必须是post
文件下载,也称为download,是指将文件从服务器传输到本地计算机的过程。
通过浏览器进行文件下载,通常有两种表现形式:
通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程。
文件的上传和下载代码实现:
文件上传,y
新增菜品,其实就是将新增页面录入的菜品信息插入到dish表,如果添加了口味做法,还需要向dish_flavor表插入数据。所以在新增菜品时,涉及到两个表:
在开发业务之前,先将需要用到的类和接口基本结构创建好:
后台系统中可以管理菜品信息,通过新增功能莱添茄一个新的菜品,在添加菜品时需要选择当前菜品所属的菜品分类,并且需要上传菜品图片,在移动端会按照菜品分类来展示对应的菜品信息
在开发代码之前,需要梳理一下新增菜品时前端页面和服务器的交互过程:
DTO,全称为Data Transfer Object,即数据传输对象,一般用于展示层与服务层之间的数据传输。
系统中的菜品数据很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。
开发菜品信息分页查询功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。
在菜品管理列表页面点击修改按钮,跳转到修改菜品页面,在修改页面回显菜品相关信息并进行修改,最后点击确定按钮完成修改操作
功能测试
代码完善
需求分析
后台系统中可以管理套餐信息,通过新增套餐功能来添加一个新的套餐,在添加套餐时需要选择当前套餐所属的套餐分类和包含的菜品,并且需要上传套餐对应的图片,在移动端会按照套餐分类来展示对应的套餐。
数据模型
新增套餐,其实就是将新增页面录入的套餐信息插入到setmeal表,还需要向setmeal_dish表插入套餐和菜品关联数据。所以在新增套餐时,涉及到两个表:
代码开发
短信服务介绍:
这些第三方短信服务会和各个运营商(移动、联通、电信)对接,我们只需要注册称为会员并且按照开发文档进行调用就可以发送信息
常用短信服务:
阿里云短信服务介绍:
验证码、短信通知、推广短信
腾讯云
京东梦网
乐信
代码开发
使用阿里云短信服务发送短信,可以参考官方提供的文档即可
具体开发步骤:
手机验证码登录方式,方便快捷,安全,没有传统的用户名
数据模型
通过手机验证码登录时,涉及到的为user表,即用户表
代码开发-梳理交互过程
菜品展示,购物车,下单
地址簿,指的是移动端消费者用户的地址信息,用户登录成功后可以维护自己的地址信息,但是同一个用户只能有一个默认地址
数据模型
用户的地址信息会存储在address_book表中,即地址簿
代码开发——梳理交互过程
代码开发准备,先将需要用到的类和接口基本结构创建好:
手机验证码登录方式,方便快捷,安全,没有传统的用户名
数据模型
通过手机验证码登录时,涉及到的为user表,即用户表
代码开发-梳理交互过程
菜品展示,购物车,下单
地址簿,指的是移动端消费者用户的地址信息,用户登录成功后可以维护自己的地址信息,但是同一个用户只能有一个默认地址
数据模型
用户的地址信息会存储在address_book表中,即地址簿
代码开发——梳理交互过程
代码开发准备,先将需要用到的类和接口基本结构创建好: