黑马springboot瑞吉外卖项目知识点功能逻辑实现

整个项目基于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重新封装到flavorslist集合中
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();
    //根据idcategory那里获取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();
    //设置对象转换器,底层使用JacksonJava对象转为json
    messageConverter.setObjectMapper(new JacksonObjectMapper());
    //将上面的消息转换器对象追加到mvc框架的转换器集合中
    converters.add(0,messageConverter);
}

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值