整个项目基于spring boot+ spring mvc +mybatis plus 实现基本的crud,通过redis
+nginx+linux去优化,基本框架还包括maven+tomcat实现,使用工具包括idea+vmware+finalshell,idea内使用git做仓库处理.
首先进行项目搭建
Idea部署项目,加上具体pom文件配置spring boot-starter之类的,然后rescours下创建yml文件写上jdbc相关连接。
服务端功能实现包括:
1.登录功能:发送验证码并验证处理。后期基于redis做优化处理
进入后台管理端后:
2.实现员工管理,包括员工的crud以及员工的搜索功能和分页显示功能。
3.实现分类管理,单表的crud
4..实现菜品管理,涉及菜品表和分类表的crud
5.实现套餐管理,涉及套餐表和套餐与菜品关系表(setmealDish)的crud
客服端功能实现包括:
1.登录功能
2.点餐功能
3.购物车功能
4.地址填写
…
先开始服务端的功能实现
具体实现逻辑:
1.1员工登录功能:
//1.将页面提交的密码进行md5加密处理
//2.根据页面提交的用户名username查询数据库
//3.如果没有查询到则返回登录失败结果
//4.密码比对,如果不一致则返回登陆失败结果
//5.查看员工状态,如果为已经=禁用状态,则返回员工已禁用结果
//6.登录成功,将员工id存入Session并返回登录成功结果
2.员工管理:实现crud
首先是
2.1新增操作:
正常来说就直接调用service的save方法就行,且基于mybatis plus,每层是这样的:
@Mapper public interface EmployeeMapper extends BaseMapper<Employee> //service接口层 public interface EmployeeService extends IService<Employee> //实现层 @Service public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService //控制层@Slf4j @RestController @RequestMapping("/employee") public class EmployeeController
正常是直接调用service接口层继承的Iservice中内置的save方法就行,但这里实现加上了一些日志般的记录,设置默认员工密码、当前做了修改的修改人信息和修改时间,而当前修改人即登录者管理人,要获取它的id,id由session获取,当然是前面登录的时候把id存进了session,这里才能get到,因此方法参数上必有
HttpServletRequest request
通过request得到session
2.2分页显示员工处理:
方法返回值泛型取Page,mp实现分页
1.构建分页构造器page a = new page();
2.构建条件构造器:lambdaquery,做一些like操作和order排序操作后
3.调用page方法:
Page page2 = employeeService.page(page1, lambdaQueryWrapper);
然后返回page2就行了。
2.3根据id修改员工操作:
直接调用updateById就行
主要是,修改前要先回显一下数据,看你修改的是哪个地方,
这时候前端传过来路径后面/了一个id参数
这种,你的方法要写出
@GetMapping("/{id}")
public R<Employee> getById(@PathVariable Long id)
Pathvariable是确保参数id是来自上面的那个路径id,
简单根据id获取对象判断一下空不空,就返回对象回去就可以回显了
3.分类管理功能:crud
没关联到多张表数据,分类的实体类属性已经包含了该有的属性,因此这里的crud
都是直接调用接口,基于mp直接调用crud的方法(save、updateBYId…)
4.菜品管理:crud处理、回显、批量停售/起售处理、分页显示、图片文件上传下载处理
4.1在新增菜品中,遇到的问题是首先需要一个图片的上传和下载,通过file实现,具体看代码。其次,难点:涉及两张表的问题,在新增菜品栏,有口味数据,而原来的实体类dish不含有这个属性,因此要实现它就要搞另外一个实体类dishDto,继承dish类并增加口味属性,因此问题在于:你这里保存了菜品的口味数据,口味表那里应该也要有这个口味数据,口味本来就有一个实体类dishflavor,
功能实现:
不能直接调用save方法了,因此到impl那里搞一个savedishFlavor方法,并开启事务注解
逻辑:
1.先保存菜品的基本信息,在该方法中直接this.save(),
2.获取菜品id
3.获取菜品口味,但这里的菜品口味实体类是含有id,我们要重新把得到的id封装到口味表中,通过stream流实体:
//获取菜品id
Long dishId = dishDto.getId();
//菜品口味,要将id重新封装到flavors的list集合中
List<DishFlavor> flavors = dishDto.getFlavors();
flavors.stream().map((item)->{
item.setDishId(dishId);
return item;
}).collect(Collectors.toList());
最后调saveBatch方法,批量保存把口味数据保存到口味表中。
4.2修改操作
回显功能前端完成了
在做修改的时候,首先直接调用this.updateById(),更新dish表
然后通过lambdaQuery,条件是id的eq,然后remove,删除口味信息,再如同新增一样,重新通过stream流得到菜品口味,
难点关键:删除再新增的过程就完成了修改操作了
4.3分页显示的回显操作
难点:回显也不能直接通过getById去得到了,因为口味不在同一个实体类中,要改一下impl里面了,逻辑是
1.获取基本信息this.getById
2.拷贝基本属性到dishdto中
BeanUtils.copyProperties(dish,dishDto);
3.获取口味信息,根据lamdaQuery,条件还是id的eq
然后set进dishdto,再返回就行
4.3分页显示处理
同员工管理的分页显示,泛型取Page,mp通过page实现分页功能
以前的分页查询就是单表的直接构造Page分页构造器,然后构造条件构造器lamdbaQuery,然后调用一下接口.Page(Page对象,lamda对象)就行。
现在要显示出菜品的分类名字,dish表没有,而在dishDto中有这个属性
因此
1.按照正常dish表的分页构造方法(上面讲的调用接口.Page方法)得到含dish表所有属性的page对象
Page page2 = dishService.page(page1, lambdaQueryWrapper);
2.新增一个分页构造器Page<dishDto>对象
3.进行对象基本属性拷贝,dish的给到dishdto
//对象拷贝,把page1的属性值拷贝到dishDto里面来,除了records对应的list集合,就是基本属性,dishdto继承dish,基本属性都有
BeanUtils.copyProperties(page2,dishDtoPage,"records");
3.利用page2.getRecord得到records(一个list集合里面含有分类id、分类名字等等信息)
4.对records进行streams流遍历,然后在里面new一个dishdto对象,通过它获取分类id,然后根据id获取分类对象,并得到分类名字,然后把分类名字set进这个diahdto对象再返回,streams流最终是list集合
5.把该list集合set进Page<dishDto>的那个对象,最后返回就完成了。
难点在于:分类名字需要得到,它在另外一张表,因此需要属性拷贝、streams流等过程得到。
4.4菜品管理中的删除操作
这个就比较普通,主要考虑到的是,删除菜品数据的同时删除一下菜品表中的数据,涉及两张表,都做删除操作,一个直接调removeById,一个做一下lambdaQuery查询之后调remove操作就行。
这里不仅实现删除操作,还涉及批量删除的操作
因此在controller层调impl层做删除的时候,应该搞一个循环去处理。
for(long id:ids)
{
dishService.removeByIdWithFlavor(id);
}
4.5菜品批量停售起售问题
这里传参带了它的状态值
如上面加个for循环拿来批量处理
里面逻辑:根据id得到菜品数据dish,然后判断传来的状态值,重新set进dish,再调dishservice.updateById(dish)实现状态更新,自然完成起售停售问题(基于状态值实现)
4.6菜品查询操作
基于dishdto,需要口味属性list集合在内和分类属性在内
以前查询就lambdaQuery,条件是id的eq和状态为1的判断,最后返回就行。
现在是dto对象返回(R<List<dishDto>>),因此在lambda得到基本dish的对象后,做一下stream流,在里面把口味信息和分类信息set进dto对象,再返回响应
List<DishDto> dishDtoList = list.stream().map((item)->{
DishDto dishDto = new DishDto();
//基本属性也要拷贝
BeanUtils.copyProperties(item,dishDto);
//item就是Dish里面的每一个对象
Long categoryId = item.getCategoryId();
//根据id去category那里获取name
Category category = categoryService.getById(categoryId);
if(category != null)
{
String categoryName = category.getName();
dishDto.setCategoryName(categoryName);
}
Long dishID = item.getId();
LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DishFlavor::getDishId,dishID);
List<DishFlavor> dishFlavorList = dishFlavorService.list(queryWrapper);
dishDto.setFlavors(dishFlavorList);
return dishDto;
}).collect(Collectors.toList());
5.套餐管理:套餐的crud,基本类和数据库表,三层架构之类的已经写好,讲一下逻辑
套餐是包括添加菜品的,因此基本的套餐表和实体类并没有含有菜品的名称、价格之类的东西。所以又要引进dto扩展一下属性。
5.1套餐的添加操作
涉及套餐表和菜品表的操作。
1.先保存套餐基本信息(dto的属性)
2.获取套餐中的菜品信息所在集合
List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes(); //获取到套餐实体属性那个dish对应的list集合
3.stream流把套餐id传进套餐与菜品关系表对象setmealDishes
4.保存套餐和菜品的基本信息
5.2套餐修改操作
5.2.1回显操作,前端传的是setmeal的id
逻辑:
1.先get得到setmeal基本属性
2.拷贝基本属性给setmealDto对象
3.条件构造得到套餐和菜品关系表setmealDish的集合,里面是套餐中存的菜品数据,用list封装的
4.把这个集合set进dto对象,最后返回dto对象即可。
5.2.2修改操作
逻辑:
1.修改基本信息
this.updateById(setmealDto);
2.清除当前的setmealdish集合里面的数据
3.把当前的setmealdish数据新增进去
4.保存
5.3批量停售起售处理
跟菜品管理的一样,一个for循环,根据id得到套餐对象,判断状态值后重新set进setmeal对象,调updateBYId方法就行
5.4批量删除操作
@DeleteMapping
public R<String> delete(Long[] ids)
{
for(long id:ids)
{
setmealService.removeWithDish(id);
}
return R.success("删除成功");
}
需要做的是,不仅删除该套餐id在套餐表的信息,同样要删除该id在套餐与菜品关系表setmealdish中的数据,前者直接removeById,后者用一下lambdaQuery,加一下id的eq判断再调接口remove就行
5.5套餐查询操作
直接lambdaQuery
@GetMapping("/list")
public R<List<Setmeal>> list( Setmeal setmeal)
{
log.info("setmeal{}",setmeal);
LambdaQueryWrapper<Setmeal> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(setmeal.getCategoryId() !=null ,Setmeal::getCategoryId,setmeal.getCategoryId());
lambdaQueryWrapper.eq(setmeal.getStatus() !=null ,Setmeal::getStatus,setmeal.getStatus());
List<Setmeal> list = setmealService.list(lambdaQueryWrapper);
return R.success(list);
}
至此完成服务端的功能实现
开始客服端的点餐模块
1.用户登录功能:
发送验证码处理:
前端输入手机号码后实现发送验证码功能,后端代码逻辑:验证手机号长度和正确性,然后调用验证码随机生成工具类去生成。并存入redis中
redisTemplate.opsForValue().set(phone,code,5, TimeUnit.MINUTES); //5分钟存活时间
登录处理:
获取验证码之后,进行验证码判空和判相等处理,如果验证码符合,进行手机号判断,基于mybatis plus,因此使用
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(User::getPhone,phone);
User user = userService.getOne(lambdaQueryWrapper);
对user进行判断是否存在,为空,则添加save处理,最后删除redis
2.购物车功能
2.1购物车添加功能
逻辑:
1.获取用户id,看是谁下单的购物车,这个id拿来做购物车lambdaQuery的eq操作的
2.判断下单的是菜品还是套餐,这里做lambdaQuery的dishid/setmealId的eq判断
3.调用shoppingCarService.getone得到购物车数据对象
4.判断该对象是否为空,不为空则进行数量加一操作,该操作就是获取number,+1后set回对象,在调用update方法,为空则数量标为1,调save保存数据
2.2购物车查看功能
直接lambdaQuery查询
@GetMapping("/list")
public R<List<ShoppingCart>> list1()
{
LambdaQueryWrapper<ShoppingCart> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId());
lambdaQueryWrapper.orderByAsc(ShoppingCart::getCreateTime);
List<ShoppingCart> list = shoppingCarService.list(lambdaQueryWrapper);
return R.success(list);
}
2.3清空购物车功能
@DeleteMapping("/clean")
public R<String > clean()
{
LambdaQueryWrapper<ShoppingCart> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(ShoppingCart::getUserId,BaseContext.getCurrentId());
shoppingCarService.remove(lambdaQueryWrapper);
return R.success("清除成功");
}
2.4购物车做减菜品操作
跟添加进购物车一样
1.先获取用户id,拿去做eq条件保证是该用户
2.判断是菜品还是套餐
3.getone得到购物车对象
4.判断对象不为空,获取number,-1后set回对象,再调用updateByid方法,实现修改。
3.地址功能
3.1新增地址操作
这个比较简单,调一下save方法就行。调之前,把用户id set进地址类对象就行。
addressBookService.save(addressBook);
3.2根据id查询地址
也比较简单,直接调getById方法就行
AddressBook addressBook = addressBookService.getById(id);
4.下单功能,实现提交处理
逻辑:
1.获取用户id
Long currentId = BaseContext.getCurrentId();
2.根据这个id作为lamdaQuery的eq条件,获取购物车数据,
List<ShoppingCart>shoppingCarts=shoppingCarService.list(queryWrapper;
3.根据这个id获取用户数据
4.根据参数Order类的对象获取地址id,并通过该id获取地址数据
5.判断购物车数据和地址数据是否为空,为空返回提示
6.向order类对象set进去所有属性
7.向订单表插入数据,this.save(order)
8.向订单明细表插入数据
9.清空购物车数据
前面很多功能要实现获取用户的id,这里通过写一个类,然后基于ThreadLocal封装工具类,用户保存和获取当前登录用户的id
public class BaseContext {
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
public static void setCurrentId(Long id){
threadLocal.set(id);
}
public static Long getCurrentId(){
// log.info("id {}",getCurrentId());
return threadLocal.get();
}
}
项目还需要一些配置类
1.mybatisPlusConfig用于配置mp的分页插件
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor()
{
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
2.webMvcConfig类继承webMvcConfigurationSupport
由于项目中resources没有static文件夹,因此后端的静态文件不能被识别,这里就需要搞一个静态资源映射
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("静态资源配置");
//addResourceHandler就是网页的地址,然后要映射到addResourceLocations后面那个路径,classpath其实是resources的路径
registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
}
2.根据id做查询/修改等操作的时候,id的格式在js中有json转对象时不同,简单来说就是id精度有问题,需要消息转换器让id值作为字符串,不被当成json被处理掉
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("扩展消息转换器...");
//创建消息转换器对象
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
//设置对象转换器,底层使用Jackson将Java对象转为json
messageConverter.setObjectMapper(new JacksonObjectMapper());
//将上面的消息转换器对象追加到mvc框架的转换器集合中
converters.add(0,messageConverter);
}