SpringBoot+Vue+Mysql苍穹外卖

news2025/2/23 23:51:26

一.项目介绍

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;
    }

常用注解

5.HttpClient

1.概述:用来提供高效的,丰富的http协议的用户端编程工具包

2.核心api

  • HttpClient
  • HttpClients
  • CloseableHttpClient
  • HttpGet
  • HttpPost

3.简单使用

发送请求步骤
1.创建HttpClient对象
2.创建Http请求对象
3.调用HttpClient的execute方法发送请求

发送Get请求
@Test
    public void testGET() throws IOException {
        //创建httpclient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        //创建请求对象
        HttpGet getstatus = new HttpGet("http://localhost:8080/user/shop/status");
        //发送请求
        CloseableHttpResponse response = httpClient.execute(getstatus);
        //获取服务端返回的状态码
        int statusCode = response.getStatusLine().getStatusCode();
        System.out.println(statusCode);
        HttpEntity entity = response.getEntity();
        if (entity != null) {
           String body = EntityUtils.toString(entity);
           System.out.println(body);
        }
        //关闭资源
        response.close();
        httpClient.close();
}

发送POST请求
@Test
public void testPOST() throws IOException {
        //创建httpclient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        //创建请求对象
        HttpPost post = new HttpPost("http://localhost:8080/admin/employee/login");
        //构造请求的数据
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("username", "admin");
        jsonObject.put("password", "123456");
        StringEntity entity = new StringEntity(jsonObject.toString());
        entity.setContentEncoding("UTF-8");
        entity.setContentType("application/json");
        post.setEntity(entity);
        //发送请求
        CloseableHttpResponse response = httpClient.execute(post);
        //获取服务端返回的状态码
        int statuscode = response.getStatusLine().getStatusCode();
        System.out.println(statuscode);
        //具体的响应数据
        HttpEntity entity1 = response.getEntity();
        if (entity1 != null) {
           String body = EntityUtils.toString(entity1);
           System.out.println(body);
        }
        //关闭资源
        response.close();
        httpClient.close();
}

6.微信小程序

概述:
  • 开发者可以快速地开发一个小程序。小程序可以在微信内被便捷地获取和传播,同时具有出色的使用体验。
  • 微信开发平台:微信开放平台
接入流程
  • 注册
  • 小程序信息完善
  • 开发小程序
  • 提交审核和发布
注册并且完善信息

微信开发者工具开发小程序

二.具体实现

一.登录功能

用户注册,输入密码-->对密码进行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);

六.店铺状态设置

@RestController("adminShopController")
@RequestMapping("/admin/shop")
@Slf4j
@Api(tags = "店铺相关接口")
public class ShopController {
    @Autowired
    private RedisTemplate redisTemplate;
    @PutMapping("/{status}")
    @ApiOperation("管理端设置店铺营业状态")
    public Result setStatus(@PathVariable Integer status){
        log.info("设置店铺的营业状态为{}",status == 1 ? "营业中":"打样中");
        redisTemplate.opsForValue().set("shop_status", status);
        return Result.success();
    }
    @GetMapping("/status")
    @ApiOperation("获取店铺的营业状态")
    public Result<Integer> getStatus(){
        Integer status = (Integer) redisTemplate.opsForValue().get("shop_status");
        return Result.success(status);
    }
}


@RestController("userShopController")
@RequestMapping("/user/shop")
@Slf4j
@Api(tags = "用户端店铺相关接口")
public class ShopController {
    @Autowired
    private RedisTemplate redisTemplate;
    @GetMapping("/status")
    @ApiOperation("获取店铺的营业状态")
    public Result<Integer> getStatus(){
        Integer status = (Integer) redisTemplate.opsForValue().get("shop_status");
        return Result.success(status);
    }
}

七.微信小程序登录

@PostMapping("/login")
    @ApiOperation("微信登录")
    public Result<UserLoginVO> login(@RequestBody UserLoginDTO userLoginDTO) {
        //微信登录
        User user = userservice.wxLogin(userLoginDTO);
        //为微信用户生成jwt令牌
        Map<String, Object> claims = new HashMap<>();
        claims.put(JwtClaimsConstant.USER_ID,user.getId());
        String token=JwtUtil.createJWT(jwtProperties.getUserSecretKey(), jwtProperties.getUserTtl(), claims);
        UserLoginVO userLoginVO = UserLoginVO.builder()
                .id(user.getId())
                .openid(user.getOpenid())
                .token(token)
                .build();
        return Result.success(userLoginVO);
}

User wxLogin(UserLoginDTO userLoginDTO);
public User wxLogin(UserLoginDTO userLoginVO) {
        Map<String, String> params = new HashMap<String, String>();
        params.put("appid",weChatProperties.getAppid());
        params.put("secret",weChatProperties.getSecret());
        params.put("js_code",userLoginVO.getCode());
        params.put("grant_type","authorization_code");
        //调用微信接口服务,获得当前用户的openid
        String res = HttpClientUtil.doGet(WX_LOGIN,params);
        JSONObject jsonObject = JSONObject.parseObject(res);
        String openid = jsonObject.getString("openid");
        //判断openid,空失败
        if (openid == null || openid.equals("")) {
            throw new LoginFailedException(MessageConstant.LOGIN_FAILED);
        }
        //判断当前用户是否为新用户
        User user = userMapper.getByUserId(openid);
        //新用户自动注册
        if (user == null){
            user = User.builder()
                    .openid(openid)
                    .createTime(LocalDateTime.now())
                    .build();
            userMapper.insert(user);
        }
        //返回这个用户对象
        return user;
}

void insert(User user);
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
        insert into user(openid,name,phone,sex,id_number,avatar,create_time)
        values (#{openid},#{name},#{phone},#{sex},#{idNumber},#{avatar},#{createTime})
</insert>

八.缓存

缓冲菜品

用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问过大,访问数据库压力很大

通过Redis来缓存菜品数据,减少数据库查询操作

开始-->后端服务-->缓存是否存在-->存在,直接使用,不存在查询数据库

@GetMapping("/list")
@ApiOperation("根据分类id查询菜品")
public Result<List<DishVO>> list(Long categoryId) {
        //查询redis中是否存在菜品数据,规则:dish_分类id
        String key = "dish_" + categoryId;
        //如果存在,直接返回,无需查询数据库
        List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);
        //如果不存在,查询数据库,将查询到的数据放入redis
        if (list != null) {
            return Result.success(list);
        }else{
        Dish dish = new Dish();
        dish.setCategoryId(categoryId);
        dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品
        List<DishVO> list2 = dishService.listWithFlavor(dish);
        redisTemplate.opsForValue().set(key, list2);
        return Result.success(list2);
        }
}

存在问题,增加,修改,删除菜品以后需要删除缓存

//封装清理缓存数据
private void cleanCache(String pattern){
        Set keys = redisTemplate.keys(pattern);
        redisTemplate.delete(keys);
}
缓存套餐
  • Spring Cache

Spring Cache是一个框架,实现了基于注解的缓存功能,只需要简单的加一个注解,就能实现缓存功能

Spring Cache底层提供了一层抽象,底层可以切换不同的缓存实现

  • EHCache
  • Caffeine
  • Redis

常用注解

九.购物车管理

添加购物车
@PostMapping("/add")
    @ApiOperation("添加购物车")
    public Result addShoppingCart(@RequestBody ShoppingCartDTO shoppingCartDTO) {
        shoppingCartService.addShoppingCart(shoppingCartDTO);
        return Result.success();
    }
void addShoppingCart(ShoppingCartDTO shoppingCartDTO);
public void addShoppingCart(ShoppingCartDTO shoppingCartDTO) {
        ShoppingCart shoppingCart = new ShoppingCart();
        BeanUtils.copyProperties(shoppingCartDTO, shoppingCart);
        Long userId = BaseContext.getCurrentId();
        shoppingCart.setUserId(userId);
        //判断当前商品是否已经在购物车
        List<ShoppingCart> list= shoppingMapper.list(shoppingCart);
        //如果存在,将数量加1
        if (list.size()>0){
            ShoppingCart cart = list.get(0);
            cart.setNumber(cart.getNumber()+1);
            shoppingMapper.updateNumber(cart);
        }
        //如果不存在,将商品加入购物车
        if (list.size()==0){
            //判断是否是菜品还是套餐
            Long dishId = shoppingCartDTO.getDishId();
            Long setmealId = shoppingCartDTO.getSetmealId();
            if(dishId != null){
                Dish dish = dishMapper.geibyid(dishId);
                shoppingCart.setName(dish.getName());
                shoppingCart.setImage(dish.getImage());
                shoppingCart.setAmount(dish.getPrice());
                shoppingCart.setNumber(1);
                shoppingCart.setCreateTime(LocalDateTime.now());
            }else{
                Setmeal setmeal = setmealMapper.getsetmealbyid(setmealId);
                shoppingCart.setName(setmeal.getName());
                shoppingCart.setImage(setmeal.getImage());
                shoppingCart.setAmount(setmeal.getPrice());
                shoppingCart.setNumber(1);
                shoppingCart.setCreateTime(LocalDateTime.now());
            }
            shoppingMapper.addShoppingCart(shoppingCart);
        }
    }

List<ShoppingCart> list(ShoppingCart shoppingCart);
<select id="list" resultType="com.sky.entity.ShoppingCart">
        select * from shopping_cart
        <where>
            <if test="userId != null">and user_id=#{userId}</if>
            <if test="setmealId!= null">and setmeal_id=#{setmealId}</if>
            <if test="dishId != null">and dish_id=#{dishId}</if>
            <if test="dishFlavor != null">and dish_flavor=#{dishFlavor}</if>
        </where>
    </select>

@Insert("insert into shopping_cart(name,user_id,dish_id,setmeal_id,dish_flavor,number,amount,image,create_time) " +
            "values (#{name},#{userId},#{dishId},#{setmealId},#{dishFlavor},#{number},#{amount},#{image},#{createTime})")
void addShoppingCart(ShoppingCart shoppingCart);
查看购物车
@GetMapping("/list")
    @ApiOperation("查看购物车")
    public Result<List<ShoppingCart>> listShoppingCart() {
        Long userId = BaseContext.getCurrentId();
        List<ShoppingCart> shoppingCart = shoppingCartService.getByUserId(userId);
        return Result.success(shoppingCart);
}

List<ShoppingCart> getByUserId(Long userId);
public List<ShoppingCart> getByUserId(Long userId) {
        List<ShoppingCart> list= shoppingMapper.getByUserId(userId);
        return list;
}
@Select("select * from shopping_cart where user_id = #{userId}")
List<ShoppingCart> getByUserId(Long userId);
清空购物车
@DeleteMapping("/clean")
    @ApiOperation("清空购物车")
    public Result cleanShoppingCart() {
        Long userId = BaseContext.getCurrentId();
        shoppingCartService.deleteAll(userId);
        return Result.success();
}

void deleteAll(Long userId);
public void deleteAll(Long userId) {
        shoppingMapper.deleteAll(userId);
}
@Delete("delete from shopping_cart where user_id = #{userId}")
void deleteAll(Long userId);
删除购物车的某一个商品
@PostMapping("/sub")
    @ApiOperation("删除购物车中一个商品")
    public Result sub(@RequestBody ShoppingCartDTO shoppingCartDTO){
        log.info("删除购物车中一个商品,商品:{}", shoppingCartDTO);
        shoppingCartService.subShoppingCart(shoppingCartDTO);
        return Result.success();
}

void subShoppingCart(ShoppingCartDTO shoppingCartDTO);
public void subShoppingCart(ShoppingCartDTO shoppingCartDTO) {
    ShoppingCart shoppingCart = new ShoppingCart();
    BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);
    //设置查询条件,查询当前登录用户的购物车数据
    shoppingCart.setUserId(BaseContext.getCurrentId());
    List<ShoppingCart> list = shoppingMapper.list(shoppingCart);
    if(list != null && list.size() > 0){
        shoppingCart = list.get(0);
        Integer number = shoppingCart.getNumber();
        if(number == 1){
            //当前商品在购物车中的份数为1,直接删除当前记录
            shoppingMapper.deleteById(shoppingCart.getId());
        }else {
            //当前商品在购物车中的份数不为1,修改份数即可
            shoppingCart.setNumber(shoppingCart.getNumber() - 1);
            shoppingMapper.updateNumber(shoppingCart);
            }
        }
}

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

十.用户下单

@PostMapping("/submit")
@ApiOperation("用户提交订单")
    public Result<OrderSubmitVO> addOrder(@RequestBody OrdersSubmitDTO ordersSubmitDTO) {
        log.info("提交订单{}",ordersSubmitDTO);
        OrderSubmitVO orderSubmitVO = orderService.submit(ordersSubmitDTO);
        return Result.success(orderSubmitVO);
}

OrderSubmitVO submit(OrdersSubmitDTO ordersSubmitDTO);
@Override
public OrderSubmitVO submit(OrdersSubmitDTO ordersSubmitDTO) {
        //1.处理业务异常
        AddressBook addressBook =addressBookMapper.getById(ordersSubmitDTO.getAddressBookId());
        if(addressBook==null){
            throw new AddressBookBusinessException(MessageConstant.ADDRESS_BOOK_IS_NULL);
        }
        Long userId = BaseContext.getCurrentId();
        ShoppingCart shoppingCart = new ShoppingCart();
        shoppingCart.setUserId(userId);
        List<ShoppingCart> shoppingCarts = shoppingMapper.getByUserId(userId);
        if(shoppingCarts==null){
            throw new ShoppingCartBusinessException(MessageConstant.SHOPPING_CART_IS_NULL);
        }
        //2.向订单表插入1条数据
        Orders orders = new Orders();
        BeanUtils.copyProperties(ordersSubmitDTO, orders);
        orders.setUserId(userId);
        orders.setOrderTime(LocalDateTime.now());
        orders.setPayStatus(Orders.UN_PAID);
        orders.setStatus(Orders.PENDING_PAYMENT);
        orders.setNumber(String.valueOf(System.currentTimeMillis()));
        orders.setPhone(addressBook.getPhone());
        orderMapper.insert(orders);
        //3.向订单明细表插入n条数据
        List<OrderDetail> orderDetails = new ArrayList<OrderDetail>();
        for (ShoppingCart cart : shoppingCarts) {
            OrderDetail orderDetail = new OrderDetail();
            BeanUtils.copyProperties(cart, orderDetail);
            orderDetail.setOrderId(orders.getId());
            orderDetails.add(orderDetail);
        }
        orderDetailMapper.insertBatch(orderDetails);
        //4.清空当前用户的购物出数据
        shoppingMapper.deleteById(userId);
        //5.封装VO返回结果
        OrderSubmitVO orderSubmitVO =OrderSubmitVO.builder()
                .id(orders.getId())
                .orderTime(orders.getOrderTime())
                .orderNumber(orders.getNumber())
                .orderAmount(orders.getAmount())
                .build();
        return orderSubmitVO;
}

十一.微信支付

微信支付时序图

cpolar内网穿透
设置authtoken,只需设置一次
cpolar.exe authtoken N2QwNzRlMjctNjFkNC00MmVmLWE5ZTYtY2M4NTcwNDIzZmIw

运行cpolar
cpolar.exe http 8080

微信支付相关配置
application.yml
wechat:
    appid: ${sky.wechat.appid}
    secret: ${sky.wechat.secret}
    mchid: ${sky.wechat.mchid}
    mch-serial-no: ${sky.wechat.mch-serial-no}
    private-key-file-path: ${sky.wechat.private-key-file-path}
    api-v3-key: ${sky.wechat.api-v3-key}
    we-chat-pay-cert-file-path: ${sky.wechat.we-chat-pay-cert-file-path}
    notify-url: ${sky.wechat.notify-url}
    refund-notify-url: ${sky.wechat.refund-notify-url}

application-dev.yml
  wechat:
    appid: wx831281689c9e6ae9
    secret: ae3115ef66575e05d2d075e287cb6270
    mchid: 1561414331
    mchSerialNo: 4B3B3DC35414AD50B1B755BAF8DE9CC7CF407606
    privateKeyFilePath: D:\pay\apiclient_key.pem
    apiV3Key: CZBK51236435wxpay435434323FFDuv3
    wechatPayCertFilePath: D:\pay\wechatpay_166096F876F45C7D07CE98952A96EC980368ACFC.pem
    notifyUrl: https://6f67b0c.r5.cpolar.top//notify/paySuccess
    refundNotifyUrl: https://6619cf50.r6.cpolar.top/notify/refundSuccess

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2304130.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络运维学习笔记 018 HCIA-Datacom综合实验02

文章目录 综合实验2sw3&#xff1a;sw4&#xff1a;gw&#xff1a;core1&#xff08;sw1&#xff09;&#xff1a;core2&#xff08;sw2&#xff09;&#xff1a;ISP 综合实验2 sw3&#xff1a; vlan 2 stp mode stp int e0/0/1 port link-type trunk port trunk allow-pass v…

QT 引入Quazip和Zlib源码工程到项目中,无需编译成库,跨平台,压缩进度

前言 最近在做项目时遇到一个需求&#xff0c;需要将升级的文件压缩成zip&#xff0c;再进行传输&#xff1b; 通过网络调研&#xff0c;有许多方式可以实现&#xff0c;例如QT私有模块的ZipReader、QZipWriter&#xff1b;或者第三方库zlib或者libzip或者quazip等&#xff1…

车载诊断架构 --- LIN节点路由转发注意事项

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 简单,单纯,喜欢独处,独来独往,不易合同频过着接地气的生活,除了生存温饱问题之外,没有什么过多的欲望,表面看起来很高冷,内心热情,如果你身…

Eclipse2024中文汉化教程(图文版)

对应Eclipse,部分人需要中文汉化,本章教程,介绍如何对Eclipse进行汉化的具体步骤。 一、汉化前的Eclipse 默认安装Eclipse的时候,默认一般都是English的,我当前版本是使用的是2024-06版本的Eclipse。 二、汉化详细步骤 点击上方菜单选项卡,Hep——Install New Software……

医院安全(不良)事件上报系统源码,基于Laravel8开发,依托其优雅的语法与强大的扩展能力

医院安全&#xff08;不良&#xff09;事件上报系统源码 系统定义&#xff1a; 规范医院安全&#xff08;不良&#xff09;事件的主动报告&#xff0c;增强风险防范意识&#xff0c;及时发现医院不良事件和安全隐患&#xff0c;将获取的医院安全信息进行分析反馈&#xff0c;…

【第一节】C++设计模式(创建型模式)-工厂模式

目录 前言 一、面向对象的两类对象创建问题 二、解决问题 三、工厂模式代码示例 四、工厂模式的核心功能 五、工厂模式的应用场景 六、工厂模式的实现与结构 七、工厂模式的优缺点 八、工厂模式的扩展与优化 九、总结 前言 在面向对象系统设计中&#xff0c;开发者常…

爬虫小案例豆瓣电影top250(json格式)

1.json格式&#xff08;仅供学习参考&#xff09; import requests, json, jsonpathclass Start(object):# 类实例化时会执行def __init__(self):self.headers {user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.…

Spring事务原理 二

在上一篇博文《Spring事务原理 一》中&#xff0c;我们熟悉了Spring声明式事务的AOP原理&#xff0c;以及事务执行的大体流程。 本文中&#xff0c;介绍了Spring事务的核心组件、传播行为的源码实现。下一篇中&#xff0c;我们将结合案例&#xff0c;来讲解实战中有关事务的易…

SpringAI系列 - ToolCalling篇(二) - 如何设置应用侧工具参数ToolContext(有坑)

目录 一、引言二、集成ToolContext示例步骤1: 在`@Tool`标注的工具方法中集成`ToolConext`参数步骤2:`ChatClient`运行时动态设置`ToolContext`参数三、填坑一、引言 在使用AI大模型的工具调用机制时,工具参数都是由大模型解析用户输入上下文获取的,由大模型提供参数给本地…

本地部署MindSearch(开源 AI 搜索引擎框架),然后上传到 hugging face的Spaces——L2G6

部署MindSearch到 hugging face Spaces上——L2G6 任务1 在 官方的MindSearch页面 复制Spaces应用到自己的Spaces下&#xff0c;Space 名称中需要包含 MindSearch 关键词&#xff0c;请在必要的步骤以及成功的对话测试结果当中 实现过程如下&#xff1a; 2.1 MindSearch 简…

MyBatis Plus扩展功能

一、代码生成器 二、逻辑删除 三、枚举处理器 像状态字段我们一般会定义一个枚举&#xff0c;做业务判断的时候就可以直接基于枚举做比较。但是我们数据库采用的是int类型&#xff0c;对应的PO也是Integer。因此业务操作时必须手动把枚举与Integer转换&#xff0c;非常麻烦。 …

深度学习之自然语言处理CBOW预测及模型的保存

自然语言处理CBOW预测及模型的保存 目录 自然语言处理CBOW预测及模型的保存1 自然语言处理1.1 概念1.2 词向量1.2.1 one-hot编码1.2.2 词嵌入1.2.3 常见的词嵌入模型 2 CBOW预测模型搭建2.1 数据及模型确定2.1.1 数据2.1.2 CBOW模型2.1.3 词嵌入降维 2.2 数据预处理2.3 模型搭建…

qt项目配置部署

Test项目: 子项目testFileHelper 1.新建一个test项目的子项目:取名testFileHelper 2.编写测试用例 3.pro文件中引入qosbrowser 4.引入测试对象的cpp和头文件 2.在项目中引入资源文件testfile.txt,在其中输入abc 实现thrid目录复用 移动thrid 将thrild目录统一放在章…

java方法学习

java 方法 在Java中&#xff0c;方法是类&#xff08;或对象&#xff09;的行为或功能的实现。&#xff08;一起实现一个功能&#xff09;java的方法类似于其他语言的函数&#xff0c;是一段用来完成特定功能的代码片段。 方法是解决一类问题步骤的有序结合。 方法包含于类或…

基于vue和微信小程序的校园自助打印系统(springboot论文源码调试讲解)

第3章 系统设计 3.1系统功能结构设计 本系统的结构分为管理员和用户、店长。本系统的功能结构图如下图3.1所示&#xff1a; 图3.1系统功能结构图 3.2数据库设计 本系统为小程序类的预约平台&#xff0c;所以对信息的安全和稳定要求非常高。为了解决本问题&#xff0c;采用前端…

[漏洞篇]文件上传漏洞详解

[漏洞篇]文件上传漏洞详解 一、介绍 1. 概念 文件上传漏洞是指用户上传了一个可执行的脚本文件&#xff0c;并通过此脚本文件获得了执行服务器端命令的能力。这种攻击方式是最为直接和有效的&#xff0c;“文件上传” 本身没有问题&#xff0c;有问题的是文件上传后&#xf…

11.Docker 之分布式仓库 Harbor

Docker 之分布式仓库 Harbor Docker 之分布式仓库 Harbor1. Harbor 组成2. 安装 Harbor Docker 之分布式仓库 Harbor Harbor 是一个用于存储和分发 Docker 镜像的企业级 Registry 服务器&#xff0c;由 VMware 开源&#xff0c;其通过添加一些企业必需的功能特性&#xff0c;例…

Python项目源码34:网页内容提取工具1.0(Tkinter+requests+html2text)

------★Python练手项目源码★------- Python项目32&#xff1a;订单销售额管理系统1.0&#xff08;TkinterCSV&#xff09; Python项目31&#xff1a;初学者也能看懂的聊天机器人1.0源码&#xff08;命令行界面Re正则表达式&#xff09; Python项目源码30&#xff1a;待办事…

使用Termux将安卓手机变成随身AI服务器(page assist连接)

通过以下方法在安卓手机上运行 Ollama 及大模型&#xff0c;无需 Root 权限&#xff0c;具体方案如下&#xff1a; 通过 Termux 模拟 Linux 环境运行 核心工具&#xff1a; 安装 &#xff08;安卓终端模拟器&#xff09;()]。借助 proot-distro 工具安装 Linux 发行版&#xf…

flink-cdc同步数据到doris中

1 创建数据库和表 1.1 数据库脚本 这样直接创建数据库是有问题&#xff0c;因为后面发现superset连接使用doris://root:12345610.101.12.82:9030/internal.eayc?charsetutf8mb4 -- 创建数据库eayc create database if not exists ods_eayc; -- 创建数据表2 数据同步 2.1 f…