当前位置: 首页 > article >正文

SpringBoot+Vue+Mysql苍穹外卖

一.项目介绍

1.项目内容

苍穹外卖是一款为大学学子设计的校园外卖服务软件,旨在提供便捷的食堂外卖送至宿舍的服务。该软件包含系统管理后台和用户端(微信小程序)两部分,支持在线浏览菜品、添加购物车、下单等功能,并由学生兼职提供跑腿送餐服务。

2.技术栈

SpringBoot+Vue+Mybatis+Mysql+Redis+Nginx

3.Nginx

网页-->nginx-->服务器

nginx反向代理优势:

1.提高访问速度(nginx可以做缓存)

2.进行负载均衡(将大量请求均匀分发请求)

3.保证后端服务的安全

        # 反向代理,处理管理端发送的请求
        location /api/ {
			proxy_pass   http://localhost:8080/admin/;
            #proxy_pass   http://webservers/admin/;
        }
		
		# 反向代理,处理用户端发送的请求
        location /user/ {
            proxy_pass   http://webservers/user/;
        }

4.Swagger

  @Bean
    public Docket docket() {
        log.info("准备生成接口文档");
        ApiInfo apiInfo = new ApiInfoBuilder()
                .title("苍穹外卖项目接口文档")
                .version("2.0")
                .description("苍穹外卖项目接口文档")
                .build();
        Docket docket = new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.sky.controller"))
                .paths(PathSelectors.any())
                .build();
        return docket;
    }

常用注解

二.具体实现

一.登录功能

用户注册,输入密码-->对密码进行md5加密进行存储-->用户进行登录,密文解码进行比对

    @PostMapping("/login")
    public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
        log.info("员工登录:{}", employeeLoginDTO);

        Employee employee = employeeService.login(employeeLoginDTO);

        //登录成功后,生成jwt令牌
        Map<String, Object> claims = new HashMap<>();
        claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
        String token = JwtUtil.createJWT(
                jwtProperties.getAdminSecretKey(),
                jwtProperties.getAdminTtl(),
                claims);
        EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()
                .id(employee.getId())
                .userName(employee.getUsername())
                .name(employee.getName())
                .token(token)
                .build();
        return Result.success(employeeLoginVO);
    }

public Employee login(EmployeeLoginDTO employeeLoginDTO) {
        String username = employeeLoginDTO.getUsername();
        String password = employeeLoginDTO.getPassword();
        //1、根据用户名查询数据库中的数据
        Employee employee = employeeMapper.getByUsername(username);
        //2、处理各种异常情况(用户名不存在、密码不对、账号被锁定)
        if (employee == null) {
            //账号不存在
            throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
        }
        //密码比对,先进行md5加密再进行密码比较
        password = DigestUtils.md5DigestAsHex(password.getBytes());
        if (!password.equals(employee.getPassword())) {
            //密码错误
            throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
        }
        if (employee.getStatus() == StatusConstant.DISABLE) {
            //账号被锁定
            throw new AccountLockedException(MessageConstant.ACCOUNT_LOCKED);
        }
        //3、返回实体对象
        return employee;
    }
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断当前拦截到的是Controller的方法还是其他资源
        if (!(handler instanceof HandlerMethod)) {
            //当前拦截到的不是动态方法,直接放行
            return true;
        }

        //1、从请求头中获取令牌
        String token = request.getHeader(jwtProperties.getAdminTokenName());

        //2、校验令牌
        try {
            log.info("jwt校验:{}", token);
            Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
            Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
            log.info("当前员工id:", empId);
            BaseContext.setCurrentId(empId);
            //3、通过,放行
            return true;
        } catch (Exception ex) {
            //4、不通过,响应401状态码
            response.setStatus(401);
            return false;
        }
    }

二.公共字段自动填充

自定义注解AutoFill,用于标识需要公共字段自定义填充的方法
自定义切面类AutoFillAspect,统一拦截加入了AutoFill注解的方法,通过反射为公共字段赋值
在Mapper上加入AutoFill注解

public enum OperationType {
    UPDATE,
    INSERT
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    OperationType value();
}

@Aspect
@Component
@Slf4j
public class AutoFillAspect {
    /**
     * 切入点
     */
    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
    public void AutoFillCut() {}

    /**
     * 前置通知,为公共字段进行赋值
     */
    @Before("AutoFillCut()")
    public void AutoFill(JoinPoint joinPoint) throws Exception {
        log.info("AutoFill start");
        //获取当前数据库操作的类型
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        AutoFill autoFill = methodSignature.getMethod().getAnnotation(AutoFill.class);
        OperationType operationType = autoFill.value();
        //获取当前被拦截方法的操作实体
        Object[] args = joinPoint.getArgs();
        if(args == null || args.length == 0) {
            return;
        }
        Object entity=args[0];
        //准备赋值的数据
        LocalDateTime now= LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();
        //为实体进行赋值
        if (operationType == OperationType.INSERT) {
              Method setCrateTime= entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
              Method setCrateUser=entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
              Method setUpdateTime=entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
              Method setUpdateUser=entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
              setCrateTime.invoke(entity,now);
              setCrateUser.invoke(entity,currentId);
              setUpdateTime.invoke(entity,now);
              setUpdateUser.invoke(entity,currentId);
        } else if (operationType == OperationType.UPDATE) {
              Method setUpdateTime=entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
              Method setUpdateUser=entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
              setUpdateTime.invoke(entity,now);
              setUpdateUser.invoke(entity,currentId);
        }
    }
}

三.员工管理

新增员工
@PostMapping
@ApiOperation("新增员工")
public Result save(@RequestBody EmployeeDTO employeeDTO) {
        log.info("新增员工{}",employeeDTO);
        employeeService.save(employeeDTO);
        return Result.success();
}

void save(EmployeeDTO employeeDTO);
public void save(EmployeeDTO employeeDTO) {
        Employee employee  = new Employee();
        //对象属性拷贝
        BeanUtils.copyProperties(employeeDTO,employee);
        employee.setStatus(StatusConstant.ENABLE);
        employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));
        employee.setCreateTime(LocalDateTime.now());
        employee.setUpdateTime(LocalDateTime.now());
        //设置当前记录人的id
        //TODO 后期改为当前用户的id
        employee.setCreateUser(10L);
        employee.setUpdateUser(10L);
        employeeMapper.insert(employee);
    }

@Insert("INSERT INTO employee(name, username, password, phone, sex, id_number, create_time, update_time, create_user, update_user, status) " +
        "VALUES (#{name}, #{username}, #{password}, #{phone}, #{sex}, #{idNumber}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser}, #{status})")
    void insert(Employee employee);

@ExceptionHandler
    public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){
        String message =  ex.getMessage();
        if (message.contains("Duplicate entry")){
            String[] split = message.split(" ");
            String username = split[2];
            String msg = username + MessageConstant.ALREADY_EXIST;
            return Result.error(msg);
        }else{
            return Result.error(MessageConstant.UNKNOWN_ERROR);
        }
    }

ThreadLocal
它为每个线程提供了一个独立的变量副本,使得每个线程可以独立地访问和修改自己的变量副本
一个请求一个线程
public class BaseContext {
    public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
    public static void setCurrentId(Long id) {
        threadLocal.set(id);
    }
    public static Long getCurrentId() {
        return threadLocal.get();
    }
    public static void removeCurrentId() {
        threadLocal.remove();
    }
}
拦截器
BaseContext.setCurrentId(empId);
//新增员工时设置当前记录人的id
employee.setCreateUser(BaseContext.getCurrentId());
employee.setUpdateUser(BaseContext.getCurrentId());
查询员工
PageHelper 是一个基于 MyBatis 的分页插件,用于简化分页查询的实现。
它通过 MyBatis 的拦截器机制,自动在 SQL 查询中添加分页逻辑.

@GetMapping("/page")
    @ApiOperation("员工分页查询")
    public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO) {
        log.info("查询员工{}",employeePageQueryDTO);
        PageResult pageResult = employeeService.page(employeePageQueryDTO);
        return Result.success(pageResult);
    }

PageResult page(EmployeePageQueryDTO employeePageQueryDTO);
@Override
public PageResult page(EmployeePageQueryDTO employeePageQueryDTO) {
        //开始分页查询
        PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize());
        Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);
        long total = page.getTotal();
        List<Employee> records = page.getResult();
        return new PageResult(total,records);
    }

Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO);
<select id="pageQuery" resultType="com.sky.entity.Employee">
        select * from employee
        <where>
           <if test="name != null and name != ''">
                and name like concat('%',#{name},'%')
           </if>
        </where>
        order by create_time desc
</select>
编辑员工
@PutMapping
    @ApiOperation("修改员工信息")
    public Result update(@RequestBody EmployeeDTO employeedao) {
        employeeService.update(employeedao);
        return Result.success();
    }

void update(EmployeeDTO employee);
@Override
public void update(EmployeeDTO employeedao) {
        Employee employee = new Employee();
        BeanUtils.copyProperties(employeedao,employee);
        employee.setUpdateTime(LocalDateTime.now());
        employee.setUpdateUser(BaseContext.getCurrentId());
        employeeMapper.update(employee);
}

<update id="update" parameterType="Employee">
        update employee
        <set>
            <if test="name!=null">name = #{name},</if>
            <if test="username!=null">username = #{username},</if>
            <if test="password!=null">password = #{password},</if>
            <if test="phone!=null">phone = #{phone},</if>
            <if test="sex!=null">sex = #{sex},</if>
            <if test="idNumber!=null">id_number = #{idNumber},</if>
            <if test="updateTime!=null">update_time = #{updateTime},</if>
            <if test="updateUser!=null">update_user = #{updateUser},</if>
            <if test="status!=null">status = #{status}, </if>
        </set>
        where id = #{id}
    </update>

四.菜品管理

新增菜品
@PostMapping
@ApiOperation("新增菜品")
public Result save(@RequestBody DishDTO dishdao) {
        log.info("新增菜品{}",dishdao);
        dishService.saveWithFlavor(dishdao);
        return Result.success();
}

public void saveWithFlavor(DishDTO dishdao);
@PostMapping
@Override
@Transactional
public void saveWithFlavor(DishDTO dishdao) {
        //向菜品表插入1条数据
        Dish dish = new Dish();
        BeanUtils.copyProperties(dishdao, dish);
        dishMapper.insert(dish);
        //向口味表插入n条数据
        //获取insert语句的主键值
        long  dish_id = dish.getId();
        List<DishFlavor> flavors=dishdao.getFlavors();
        if (flavors!=null&&flavors.size()>0){
            flavors.forEach(dishFlavor -> dishFlavor.setDishId(dish_id));
            dishFloarMapper.insertBatch(flavors);
        }
}

@AutoFill(OperationType.INSERT)
void insert(Dish dish);
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
        insert into dish(name,category_id,price,image,description,status,create_time,update_time,create_user,update_user)
        values
        (#{name},#{categoryId},#{price},#{image},#{description},#{status},#{createTime},#{updateTime},#{createUser},#{updateUser})
</insert>

void insertBatch(List<DishFlavor> flavors);
<insert id="insertBatch">
    insert into dish_flavor(dish_id, name, value)
    values
    <foreach collection="flavors" item="df" separator=",">
        (#{df.dishId}, #{df.name}, JSON_ARRAY(#{df.value}))
    </foreach>
</insert>
查询菜品
@GetMapping("/page")
@ApiOperation("菜品分页查询")
public Result<PageResult> GetDish(DishPageQueryDTO dto){
        log.info("菜品查询");
        PageResult list =dishService.getDish(dto);
        return Result.success(list);
}

PageResult getDish(DishPageQueryDTO dto);
public PageResult getDish(DishPageQueryDTO dto) {
        PageHelper.startPage(dto.getPage(),dto.getPageSize());
        Page<DishVO> page = dishMapper.pageQuery(dto);
        return new PageResult(page.getTotal(),page.getResult());
}

Page<DishVO> pageQuery(DishPageQueryDTO dto);
<select id="pageQuery" resultType="com.sky.vo.DishVO">
        select d.* ,c.name as categoryName from dish d left outer join category c on d.category_id = c.id
        <where>
            <if test="name !=null">
                and d.name like concat('%', #{name},'%')
            </if>
            <if test="categoryId !=null">
                and d.category_id = #{category_id}
            </if>
            <if test="status!=null">
                and d.status = #{status}
            </if>
        </where>
        order by d.create_time desc
</select>
删除菜品
@DeleteMapping()
@ApiOperation("删除菜品")
    public Result delete(@RequestParam List<Long> ids) {
        log.info("删除菜品{}",ids);
        dishService.delete(ids);
        return Result.success();
}

@Transactional
@Override
public void delete(List<Long> ids) {
        //起售中的菜品不能删除
        for (Long id : ids) {
            Dish dish = dishMapper.geibyid(id);
            if (dish.getStatus() == StatusConstant.ENABLE){
                 throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
            }
        }
        //被套餐关联的菜品不能删除
        List<Long> SetmealIds = setmealDishMapper.getSetmealDishIdsBydishlId(ids);
        if (SetmealIds!=null&&SetmealIds.size()>0){
            throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
        }
        /*可以删除一个菜品,也可以删除多个菜品
        for (Long id : ids) {
            dishMapper.delete(id);
            //删除菜品后,关联的口味也需要删除
            dishFloarMapper.delete(id);
        }*/
        //优化,根据菜品id集合批量删除
        dishMapper.deletes(ids);
        dishFloarMapper.deletes(ids);
}

void deletes(List<Long> ids);
<delete id="deletes">
        delete from dish where id in
        <foreach collection="ids" open="(" close=")" separator="," item="id">
            #{id}
        </foreach>
    </delete>
修改菜品
@PutMapping
    @ApiOperation("修改菜品")
    public Result update(@RequestBody DishDTO dishdao) {
        dishService.update(dishdao);
        return Result.success();
}

void update(DishDTO dishdao);
public void update(DishDTO dishdao) {
        //修改菜品表
        Dish dish = new Dish();
        BeanUtils.copyProperties(dishdao, dish);
        dishMapper.updatedish(dish);
        //修改口味表,先删除所有口味,在插入传过来的口味
        dishFloarMapper.delete(dishdao  .getId());
        List<DishFlavor> flavors=dishdao.getFlavors();
        if (flavors!=null&&flavors.size()>0){
            flavors.forEach(dishFlavor -> {dishFlavor.setDishId(dish.getId());});
            flavors.forEach(dishFlavor -> dishFlavor.setValue(dishFlavor.getValue().toString()));
            dishFloarMapper.insertBatch(flavors);
        }
}

五.套餐管理

新增套餐
@PostMapping
    @ApiOperation("新增套餐")
    public Result save(@RequestBody SetmealDTO setmealDTO) {
        setmealService.saveWithDish(setmealDTO);
        return Result.success();
}

void saveWithDish(SetmealDTO setmealDTO);
@Transactional
    public void saveWithDish(SetmealDTO setmealDTO) {
        Setmeal setmeal = new Setmeal();
        BeanUtils.copyProperties(setmealDTO, setmeal);
        //向套餐表插入数据
        setmealMapper.insert(setmeal);
        //获取生成的套餐id
        Long setmealId = setmeal.getId();
        List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();
        setmealDishes.forEach(setmealDish -> {
            setmealDish.setSetmealId(setmealId);
        });
        //保存套餐和菜品的关联关系
        setmealDishMapper.insertBatch(setmealDishes);
}

<insert id="insert" parameterType="Setmeal" useGeneratedKeys="true" keyProperty="id">
    insert into setmeal
    (category_id, name, price, status, description, image, create_time, update_time, create_user, update_user)
    values (#{categoryId}, #{name}, #{price}, #{status}, #{description}, #{image}, #{createTime}, #{updateTime},
    #{createUser}, #{updateUser})
</insert>
<insert id="insertBatch" parameterType="list">
    insert into setmeal_dish
    (setmeal_id,dish_id,name,price,copies)
    values
    <foreach collection="setmealDishes" item="sd" separator=",">
        (#{sd.setmealId},#{sd.dishId},#{sd.name},#{sd.price},#{sd.copies})
    </foreach>
</insert>
查询套餐
@GetMapping("/page")
@ApiOperation("分页查询")
public Result<PageResult> page(SetmealPageQueryDTO setmealPageQueryDTO) {
    PageResult pageResult = setmealService.pageQuery(setmealPageQueryDTO);
    return Result.success(pageResult);
}

PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);
public PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO) {
    int pageNum = setmealPageQueryDTO.getPage();
    int pageSize = setmealPageQueryDTO.getPageSize();
    PageHelper.startPage(pageNum, pageSize);
    Page<SetmealVO> page = setmealMapper.pageQuery(setmealPageQueryDTO);
    return new PageResult(page.getTotal(), page.getResult());
}

Page<SetmealVO> pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);
<select id="pageQuery" resultType="com.sky.vo.SetmealVO">
    select
    	s.*,c.name categoryName
    from
    	setmeal s
    left join
    	category c
    on
    	s.category_id = c.id
    <where>
        <if test="name != null">
            and s.name like concat('%',#{name},'%')
        </if>
        <if test="status != null">
            and s.status = #{status}
        </if>
        <if test="categoryId != null">
            and s.category_id = #{categoryId}
        </if>
    </where>
    order by s.create_time desc
</select>
修改套餐
@PutMapping
    @ApiOperation("修改套餐")
    public Result updateSetmeal(@RequestBody SetmealDTO setmealdto){
        log.info("修改套餐{}",setmealdto);
        setmealService.updateSetmeal(setmealdto);
        return Result.success();
}

void updateSetmeal(SetmealDTO setmealdto);
public void updateSetmeal(SetmealDTO setmealdto) {
        Setmeal setmeal = new Setmeal();
        BeanUtils.copyProperties(setmealdto, setmeal);
        //修改套餐信息
        setmealMapper.updateSetmeal(setmeal);
        //修改对应的套餐菜品
        Long setmealId = setmeal.getId();
        //删除套餐和菜品的关联关系,操作setmeal_dish表,执行delete
        setmealDishMapper.deleteBySetmealId(setmealId);
        List<SetmealDish> setmealDishes = setmealdto.getSetmealDishes();
        setmealDishes.forEach(setmealDish -> {
        setmealDish.setSetmealId(setmealId);
        });
        //3、重新插入套餐和菜品的关联关系,操作setmeal_dish表,执行insert
        setmealDishMapper.insertBatch(setmealDishes);
    }

<update id="updateSetmeal">
        update setmeal
        <set>
        <if test="categoryId!=null">category_id = #{categoryId},</if>
        <if test="name!=null">name = #{name},</if>
        <if test="price!=null">price = #{price},</if>
        <if test="description!=null">description = #{description}, </if>
        <if test="image!=null">image = #{image},</if>
        <if test="status!=null">status = #{status},</if>
        </set>
        where id = #{id}
</update>
删除套餐
@DeleteMapping
    @ApiOperation("批量删除套餐")
    public Result delete(@RequestParam List<Long> ids){
        setmealService.deleteBatch(ids);
        return Result.success();
}

void deleteBatch(List<Long> ids);
public void deleteBatch(List<Long> ids) {
        //起售中的套餐无法删除
        for (Long id : ids) {
            Setmeal setmeal = setmealMapper.getsetmealbyid(id);
            if(setmeal.getStatus()== StatusConstant.ENABLE){
                throw new DeletionNotAllowedException(MessageConstant.SETMEAL_ON_SALE);
            }
        }
        for (Long id : ids) {
            //删除套餐表中的数据
            setmealMapper.deleteById(id);
            //删除套餐菜品关系表中的数据
            setmealDishMapper.deleteBySetmealId(id);
        }
}

@Delete("delete from setmeal where id = #{id}")
void deleteById(Long id);

http://www.kler.cn/a/556306.html

相关文章:

  • 设备树及gpio子系统及ioctl控制及字符设备驱动及内核模块编程事项仨LED灯说点就点说灭就灭
  • VMWare安装Debian操作系统
  • CSS 解决 Flex 布局中 justify-content: space-between; 导致最后一排项目分散对齐的问题
  • 数据结构——二叉树经典习题讲解
  • MATLAB学习之旅:从入门到基础实践
  • 【.NET全栈】.NET包含的所有技术
  • Github开源AI LLM大语言模型WebUI框架推荐
  • 【Gin】| 框架源码解析 :路由详解
  • QT SQL框架及QSqlDatabase类
  • MATLAB | 设置滑动窗口计算栅格数据的CV变异系数
  • 装win10系统提示“windows无法安装到这个磁盘,选中的磁盘采用GPT分区形式”解决方法
  • 计算机视觉算法实战——图像风格迁移(主页有源码)
  • solidity之Foundry安装配置(一)
  • 利用Postman和Apipost进行WebSocket调试和文档设计
  • 国产开源PDF解析工具MinerU
  • 网络运维学习笔记 013网工初级(HCIA-Datacom与CCNA-EI)DHCP动态主机配置协议(此处只讲华为)
  • 【C语言】结构体内存对齐问题
  • 前端ES面试题及参考答案
  • Node.js 的 http 模块
  • Vue 3 和 Vite 从零开始搭建项目的详细步骤