苍穹外卖(持续更新)对应技术栈补充 ---- 学习笔记 附代码

1.后端环境搭建 - 前后端联调

image-20240426160107448

nginx 反向代理的配置方式

image-20240426160250985

2. 完善登录功能

2.1 ----- 密码明文处理

  • 将密码加密存储,提高安全性 ==》 使用MD5加密方式对明文密码加密(不可逆的)

  • spring 给了工具类:DigestUtils

3. 导入接口文档

3.1 ----- 前后端分离开发流程

image-20240426164457647

3.2 ----- 操作步骤

使用 yapi 来管理 : 是设计阶段使用的工具,管理和维护接口

Swagger :在开发阶段使用的框架,帮助后端开发人员做后端的接口测试

  • 导入管理端接口

  • 导入用户端接口

3.3 ----- Swagger

引出 :后端测试接口较多,用postman 比较麻烦

介绍

image-20240426170238752

 /**
     * 通过knife4j生成接口文档
     * @return
     */  
@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;
    }
​
  /**
     * 设置静态资源映射
     * @param registry
     */
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        log.info("开始设置静态资源映射");
        registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

这样只要项目启动了就可以在 /doc.html 中进行调试

3.3.1 -------- 常用注解

image-20240427122721155

4. 新增员工功能

4.1 -------- 有关问题

4.1.1 ------ 数据库下划线与实体类驼峰命名不能一一对应
  • 解决方法:

    • mybatis:
        #mapper配置文件
        mapper-locations: classpath:mapper/*.xml
        type-aliases-package: com.sky.entity
        configuration:
          #开启驼峰命名
          map-underscore-to-camel-case: true
4.1.2 ------ 报错 500 说找不到 id_number
  • 原因:在编写sql 语句时 写的是id_number ,而对应的数据库实体类是用的驼峰命名 idNumber

    • 正确写法

    • @Insert("insert into sky_take_out.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})")
  • 后端返回给前端的数据都会封装成一个Result 对象来返回

4.2 -------- 完善代码

4.2.1 ------- 异常处理
  • 有关异常的处理:值得注意的是记得在处理异常的方法前面加上 @ExceptionHandler 注解

4.2.2 ------- 获取当前登录者的id
  • 可以通过解析 JWT 令牌来获取。 获取出来过后用 Thread 的一个局部变量 (ThreadLocal)来操作

    • ThreadLocal 为每个线程提供单独一份存储空间,具有隔离效果,只有在线程内才能获取到对应的值,线程外不能访问、

  • 客户端发起的每一次请求都是一个单独的线程,这样可以把id 存在存储在线程的存储空间中


5. 员工分页查询

  • 使用get 请求所以只能用 query 来传递参数

    • 前端发送请求最常用的是get请求还有post请求 get请求只能传query参数,query参数都是拼在请求地址上的 post可以传body和query两种形式的参数

    1. 在controller 中添加员工分页查询,调用 service 中pagequery 方法,返回一个pageResult 对象

    2. 在service 实现类中创建 pagequery 方法

    3. 用mybatis 的 page helper 插件来简化代码的书写

5.1 ------ 代码完善日期格式化

image-20240428162623045

  • 方式一:

    •     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
          private LocalDateTime createTime;
  • 方式二

    • /**
          * 扩展spring MVC 框架的消息转换器
          * @param converters
          */
         protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
             log.info("扩展消息转换器。。。。");
             //创建一个消息转换器对象
             MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
             //需要为消息转换器设置一个对象转换器,对象转换器可以将 Java 对象序列化为 json 数据
             converter.setObjectMapper(new JacksonObjectMapper());
             //将自己的消息转换器加入容器中并且设置使用
             converters.add(0,converter);
         }

6. 启用禁用员工账号

  • 被禁用的员工账号不能在登录

@Builder   //构建者模式,可以选择构造时需要的参数
​
//使用构建者模式
     Employee employee = Employee.builder()
             .status(status)
             .id(id)
             .build();
​

注意这里的update sql语句 写成动态的比较好,其它的比较简单

<update id="update" parameterType="Employee">
     update sky_take_out.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="updateUser != null"> update_user = #{updateUser}, </if>
         <if test="updateTime != null"> update_time = #{updateTime}, </if>
         <if test="status != null"> status = #{status}, </if>
     </set>
     where id = #{id}
 </update>

7. 分类管理功能

教学是直接导入的,准备自己手敲,巩固一下基础‘

7.1 ------ 手敲修改分类接口

  • 30min 报错信息

      1. 报错说 Request method 'POST' not supported 知识点 @PutMapping 和@PostMapping 的区别

      2. You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'sort = 4

      3. 报错原因:忘记在动态修改sql 语句末尾中添加逗号了。。。。。

7.2 ------ 手敲根据类型查询分类

  • 一个类型可能有多个分类,所以用返回类型是list集合

    • 最后注意下sql 语句

    •     select * from sky_take_out.category
                   where status = 1
                   <if test="type != null">
                       and type = #{type}
                   </if>
                   order by sort asc,create_time desc

7.3 ------ 手敲分页查询分类接口

  • 3o min 查询是查询出来了,但是下面没有分页栏

    • image-20240504140630496

    • 报错信息 :无

7.4 ------ 手敲新增分类

  • 完美 无报错

7.5 ------ 手敲禁用,启用分类

  • 无报错

7.6 ------ 手敲根据id 删除分类

  • 注意:在删除分类是要判断该分类下是否关联了相关的菜品

    • //查询当前分类是否关联了菜品,如果关联了就抛出业务异常
             Integer count = dishMapper.countByCategoryId(id);
             if (count > 0) {
                 //当前分类下有菜品,不能删除
                 throw new DeletionNotAllowedException(MessageConstant.CATEGORY_BE_RELATED_BY_DISH);
             }
             //查询当前分类是否关联了套餐,如果关联了就抛出业务异常
             count = setmealMapper.countByCategoryId(id);
             if (count > 0) {
                 throw  new DeletionNotAllowedException(MessageConstant.CATEGORY_BE_RELATED_BY_SETMEAL);
             }

8. 公共字段自动填充

  • 当前问题:有些代码重复使得代码冗余,后期维护不便

  • image-20240505154643825

所涉及的技术点:枚举、注解、AOP、 反射


9. 新增菜品功能

image-20240505165605096

9.1 -------- 文件上传功能

9.1.1 --------- 上传图片
  1. 不能放在本地存储缺点太多了,用阿里 oss对象存储,试用了三个月的。开了桶,

  2. 调用工具类AliOssUtil 里面的upload 方法获取文件的路径,返回给前端让其回显在页面

  3. 其中配置 alioss 也大有学问 ,用了多环境配置

    1. springboot使用yml文件配置多环境方式(dev、test、prod)java脚本之家 (jb51.net)

  4. try {
                //原始文件名
                String originalFilename = file.getOriginalFilename();
                //截取文件的扩展名
                String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
                //构造新文件名称
                String objectName = UUID.randomUUID().toString() + extension;
                //文件的请求路径
                String filePath = aliOssUtil.upload(file.getBytes(), objectName);
                return Result.success(filePath);
            } catch (IOException e) {
                log.info("文件上传失败,{}",e);
            }

9.2 --------- 添加菜品和对应口味

添加菜品时候,需要添加对应的口味 怎么在一个service 方法中完成呢

在方法的上面添加 @Transactional 注解 表示要么都成功 要么都失败

  • 注意在添加完菜品数据时候需要返回对应的菜品id,这样在添加口味的时候才能获取到菜品的id

    • <insert id="insert" useGeneratedKeys="true" keyProperty="id">

9.3 ---------- 菜品分页查询

  • 有一个关键点 在接口中的返回数据中 是categoryName 是另一个表中的 所以就用 DishVO 封装数据,

    • sql 语句 : 用了左外连接来查询

      • <select id="pageQuery" resultType="com.sky.vo.DishVO">
                select d.*,c.name as categoryName from sky_take_out.dish d left outer join sky_take_out.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 = #{categoryId}
                    </if>
                    <if test="status !=null">
                        and d.status = #{status}
                    </if>
                </where>
               order by d.create_time desc
        ​
            </select>

9.4 --------- 删除菜品

image-20240508182705994

  • 设计一个批量删除接口

9.5 --------- 修改菜品

在 修改菜品的时候要注意 :由于菜品表关联了口味表,所以要对数据库分开进行操作。而对于口味表要先删除dishid 关联的旧的口味数据再重新插入新的口味

//先删除
        dishFlavorMapper.deleteById(dishDTO.getId());
        //重新插入
        List<DishFlavor> flavors = dishDTO.getFlavors();
        if (flavors != null && !flavors.isEmpty()){
            flavors.forEach(dishFlavor -> {
                dishFlavor.setDishId(dishDTO.getId());
            });
            //向口味表插入n条诗句
            dishFlavorMapper.insert(flavors);
        }

9.6 ---------- 菜品的启用禁用

  • 对于启用禁用很简单就是要注意在写参数类型的时候记得加上路径注解 @PathVariable

  • 代码

    •   /**
           * 批量删除
           * @param ids
           */
          @Transactional
          public void deleteBatch(List<Long> ids) {
              //查看是菜品的启用状态,如果是启用状态,则不能删除
              for (Long id : ids) {
                  Dish dish = dishMapper.getById(id);
                  if (dish.getStatus() == StatusConstant.ENABLE) {
                      throw new DeletionNotAllowedException("该菜品已启用,不能删除");
                  }
              }
              //判断菜品是否关联了套餐,如果关联了就不能删除  用动态查询
              List<Long> setmealId = setmealDishMapper.getDishIdsBySetmealId(ids);
              if (setmealId != null && setmealId.size() > 0) {
                  throw new DeletionNotAllowedException("该菜品已关联套餐,不能删除");
              }
              //删除菜品表中的菜品数据
              for (Long id : ids) {
                  dishMapper.deleteById(id);
                  //删除菜品关联的口味数据
                  dishFlavorMapper.deleteById(id);
              }
      ​
          }


技术补充

一 、---- JWT 令牌的原理以及运用

引出

传统的会话技术(Session ,Cookie)有许多问题 eg:在服务器集群的环境下 Session 不能共享 。 移动端APP 无法使用 Cookie 。不安全等等

image-20240425190736135

JWT令牌概述

传统的用户会话状态管理一般是服务端通过类似键(Key)、值(Value)的数据结构将用户会话Session信息存储到缓存或数据库中,然后把该数据的Key返回给客户端,为了保证唯一性和安全性,一般Key值会是类似UUID的一个值,UUID本身没有意义,必须和服务端的会话数据关联。这意味着管理用户Session的服务是有状态的,对于有状态的服务管理,随着用户量的增大就会面临状态一致性,扩展性、高可用等各种分布式问题。

客户端(cookie:a49bc98a946f47)------------------>服务端{"a49bc98a946f47":"userinfo"}

WT令牌如:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

形式上也是类似UUID的形式,但JWT令牌是经过编码生成的里面可以包含各种字段信息,可包括对这些字段信息的防篡改签名以及用户签名的算法,类似HTTP协议一样,这些都已经成为了RFC行业标准。 使用JWT令牌有如下优点:

  • 集中化认证:认证(Authentication)逻辑可以委托给独立认证服务,包括公司内部自己研发的集中式认证服务或者可以颁发JWT令牌的LDAP产品或一个外部的第三方商业的认证服务提供者,类似国外的Okta、Auth0,国内的Authing这些认证服务提供商。

  • 密钥更安全:颁发JWT令牌的外部认证服务和使用JWT令牌的应用服务是完全分开的,认证服务也不需要和应用服务共享密钥,不需要在应用服务器上存储密钥,大大降低了密钥被泄露的风险。

  • 认证高可用:应用服务每次在进行认证逻辑的过程中不需要实时调用认证服务,应用服务可以做到完全无状态。

简单总结下:JWT令牌可以应用服务完全无状态,可以让颁发令牌的认证服务和验证令牌的应用服务完全分开。在应用服务中只需要检查JWT令牌本身就可以做验证逻辑,应用服务器可以将登录认证这些逻辑完全委托给独立的认证服务,聚焦应用本身的业务逻辑职责,更加简单和安全,大部分的认证逻辑都由独立的认证服务完成,这些认证逻辑可以在不同的应用服务中复用。

1.1 ----- JWT

  • JSON Web Token( JWT ) 是一个开发的行业标准(RFC 7519),定义了一种简介的、自包含的协议格式,用于在通信双方传递 json 对象,传递的信息是经过数字签名可以被验证和信任。

image-20240426173948168

1.2 ----- 生成 JWT 令牌

  • 导入依赖

1.3 ----- 校验 JWT 令牌

二 、 ---- DDOS

1 -------什么是DDOS攻击?(网络攻击详解) - 知乎 (zhihu.com)

三 、什么是 DO DTO

1 ------ 一篇文章讲清楚VO,BO,PO,DO,DTO的区别 - 知乎 (zhihu.com)

四、文件上传

  • 简介

    image-20240505181125081

image-20240505181650490

4.1 ----------- 上传文件大小设置

image-20240505184203635

4.2 ---------- 存储在本地硬盘的缺点

  1. 无法直接访问

  2. 磁盘容易满

  3. 磁盘坏了数据就没有了

仓库地址

苍穹外卖: 记录第一个项目。苍穹外卖 (gitee.com)

苍穹外卖数据库的E-R图描述了该数据库实体之间的关系,E-R图包括实体(Entity)、关系(Relationship)和属性(Attribute)三个要素。苍穹外卖数据库的E-R图主要包含以下几个实体: 1. 用户(User):记录用户的基本信息,包括用户ID、用户名密码、电话等。 2. 商家(Business):记录商家的基本信息,包括商家ID、商家名、地址、联系电话等。 3. 订单(Order):记录订单的基本信息,包括订单ID、下单时间、送餐地址等。 4. 菜品(Dish):记录菜品的基本信息,包括菜品ID、菜品名、价格、图片等。 5. 购物车(ShoppingCart):记录购物车的菜品信息,包括购物车ID、用户ID、菜品ID等。 这些实体之间的关系如下: 1. 用户(User)和商家(Business)之间是一对多关系,即一个商家可以有多个用户点餐。 2. 商家(Business)和菜品(Dish)之间是一对多关系,即一个商家可以有多种菜品。 3. 订单(Order)和用户(User)之间是一对多关系,即一个用户可以有多个订单。 4. 订单(Order)和商家(Business)之间是一对多关系,即一个商家可以接收多个订单。 5. 订单(Order)和购物车(ShoppingCart)之间是一对多关系,即一个订单可以对应一个或多个购物车。 基于以上实体和关系,苍穹外卖数据库的E-R图可以用如下方式表示: ``` +-------------+ +-------------+ | User | | Business | +-------------+ +-------------+ | | | | | | | | +--+ +----------------+ | ShoppingCart |--+ +----------------+ | | | | +--+ | Dish | +--------+ ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值