SpringBoot+Vue+Mysql苍穹外卖
- 手机
- 2025-08-22 13:45:01

一.项目介绍 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);SpringBoot+Vue+Mysql苍穹外卖由讯客互联手机栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“SpringBoot+Vue+Mysql苍穹外卖”