• 项目--苍穹外卖


    目录

    前端发送的请求,是如何请求到后端服务的?

    完善登录功能

    前后端 分离开发流程:

    导入接口文档 

    Swagger

    新增员工

    员工分页查询

    日期格式 错误:

    启用禁用员工账号

    编辑员工 

    导入分类模块功能代码

    公共字段自动填充


    1.| constant | 存放相关常量类 |
    | context | 存放上下文类 |
    | enumeration | 项目的枚举类存储 |
    | exception | 存放自定义异常类 |
    | json | 处理json转换的类 |
    | properties | 存放SpringBoot相关的配置属性类 |
    | result | 返回结果类的封装 |
    | utils | 常用工具类 |
    2.| Entity | 实体,通常和数据库中的表对应 |
    | DTO | 数据传输对象,通常用于程序中各层之间传递数据 |
    | VO | 视图对象,为前端展示数据提供的对象 |

    | POJO | 普通Java对象,只有属性和对应的getter和setter |

    3.| config | 存放配置类 |
    | controller | 存放controller类 |
    | interceptor | 存放拦截器类 |
    | mapper | 存放mapper接口 |
    | service | 存放service类 |
    | SkyApplication | 启动类 |

    3.先编译一下,然后正常运行

     前后端联调:

    nginx反向代理,就是将前端发送的动态请求由nginx转发到后端服务

    好处: 

    前端发送的请求,是如何请求到后端服务的?

    在使用Nginx作为反向代理的Java项目中,前端发送的请求会被Nginx接收并处理,然后转发给后端服务。以下是一些基本步骤:

    客户端(前端)发送请求到Nginx服务器。
    Nginx服务器接收请求,并根据配置的规则进行处理。
    Nginx根据配置的规则将请求转发给后端服务。
    后端服务接收到请求并处理,然后返回响应。
    Nginx接收后端服务的响应,并将其返回给客户端。

    在这个过程中,Nginx扮演了反向代理的角色,接收客户端的请求并转发给后端服务,同时也将后端服务的响应返回给客户端。通过Nginx,可以实现对请求的负载均衡(把大量的请求按照我们指定的方式均衡的分配给集中的每台服务器)、缓存、安全过滤等处理,提高系统的性能和安全性。 

    配置:nginx.conf

    server{     

                 listen 80;     

                 server_name localhost;          

                 location /api/ {                     

                                 proxy_pass   http://localhost:8080/admin/;  #反向代理     } }

    nginx 负载均衡的配置方式: nginx.conf

    upstream webservers{     

                    server 192.168.100.128:8080;     

                    server 192.168.100.129:8080;

    }

    server{     listen 80;     

                    server_name localhost;          

                    location /api/ {                   

                                    proxy_pass   http://webservers/admin/;  #负载均衡     } }

    完善登录功能

    问题:员工表中的密码是明文存储,安全性太低。 

    1.将密码加密后存储,提高安全性

    2.使用MD5加密方式对明文密码加密

    1.修改数据库中明文密码,改为MD5加密后的密文

    2.修改Java代码,前端提交的密码进行MD5加密后再跟数据库中密码比对

    在EmployeeServiceImpl中

    //进行md5加密,然后再进行比对

    password = DigestUtils.md5DigestAsHex(password.getBytes());

    if (!password.equals(employee.getPassword())) {    

    //密码错误  

    throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR); }

    前后端 分离开发流程

    导入接口文档 

    json导入YApi

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

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

    Swagger

    使用Swagger你只需要按照它的规范去定义接口及接口相关的信息,就可以做到生成接口文档,以及在线接口调试页面。

    Knife4j 是为Java MVC框架集成Swagger生成Api文档的增强解决方案。

    1.导入maven坐标

    2.在配置类中加入knnife4j相关配置

    3.设置静态资源映射,否则接口文档页面无法访问

               

    com.github.xiaoymin            

    knife4j-spring-boot-starter            

    3.0.2

     WebMvcConfiguration中:

    @Bean

    public Docket docket(){    

        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/");
       }

    常用注解:

    通过注解可以控制生成的接口文档,使接口文档拥有更好的可读性,常用注解如下:

    @Api(tags ="员工相关接口")

    @ApiOperation(value = "员工登录")    方法上

    @ApiOperation("员工退出")

    DTO:专注于做数据传输

    当前端提交的数据和实体类中对应的属性差别比较大时,建议使用DTO来封装数据

    VO

    新增员工

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

    void save(EmployeeDTO employeeDTO); 

    @Override
    public void save(EmployeeDTO employeeDTO) {
    Employee employee = new Employee();
    //employee.setName(employeeDTO.getName());

    //对象属性拷贝
    BeanUtils.copyProperties(employeeDTO,employee);
    //设置账号的状态,默认正常状态1表示正常,0表示锁定
    employee.setStatus(StatusConstant.ENABLE);

    //设置密码,默认123456
    employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.

    DEFAULT_PASSWORD.getBytes()));

    //设置当前记录的创建时间和修改时间
    employee.setCreateTime(LocalDateTime.now());
    employee.setUpdateTime(LocalDateTime.now());

    //设置当前记录创建人id和修改人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)" +"values "+
    "(#{name},#{username},#{password},#{phone},#{sex},#{idNumber},#{createTime},#{updateTime},#{createUser},#{updateUser})")
    void insert(Employee employee);

     

    存在问题

    1.录入的用户名已存在,抛出异常后没有处理

    2.新增员工时,创建人id和修改人id设置为了固定值 

    @ExceptionHandler
    public Result exceptionHandler(SQLIntegrityConstraintViolationException ex) {
    log.error("异常信息:{}", ex.getMessage());
    Duplicate entry 'zhangsan' for key 'employee.idx username
    String message = ex.getMessage(); //获得异常信息
    if (message.contains("Duplicate entry")) { //判断异常信息里面有没有关键字Duplicate entry
    String[] split = message.split(" "); //动态把字符串提取出来,根据空格分割,得到数组对象
    String username = split[2]; //第三个,下标为2
    String msg = username + MessageConstant.ALREADY_EXISTS; //用户名已存在
    return Result.error(msg);
    } else {
    return Result.error(MessageConstant.UNKNOWN_ERROR);
    }
    }

     

    解析出登录员工id后,如何传递给Service的save方法?

    ThreadLocal 并不是一个Thread,而是Thread的局部变量。

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

    ThreadLocal常用方法:

    public void set(T value)     设置当前线程的线程局部变量的值

    public T get()         返回当前线程所对应的线程局部变量的值

    public void remove()        移除当前线程的线程局部变量 

    注意:客户端发送的每次请求,后端的Tomcat服务器都会分配一个单独的线程来处理请求

    员工分页查询

    业务规则:

    根据页码展示员工信息

    每页展示10条数据

    分页查询时可以根据需要,输入员工姓名进行查询

    返回数据data:

    total:总记录数   records:代表当前这一页要展示的数据集合,数据多个,为数组

    根据分页查询接口设计对应的DTO:

    @Data

    public class EmployeePageQueryDTO implements Serializable {    

    //员工姓名    

    private String name;    

    //页码    

    private int page;    

    //每页显示记录数     private int pageSize;

    }

    后面所有的分页查询,统一都封装成PageResult对象:

    /**  * 封装分页查询结果  */

    @Data

    @AllArgsConstructor

    @NoArgsConstructor

    public class PageResult implements Serializable {    

    private long total; //总记录数    

    private List records; //当前页数据集合 }

    员工信息分页查询后端返回的对象类型为:Result

    @GetMapping("/page")
    @ApiOperation("员工分页查询")
    public Result page(EmployeePageQueryDTO employeePageQueryDTO){
    log.info("员工分页查询,参数为:{}",employeePageQueryDTO); //输出
    PageResult pageResult =employeeService.pageQuery(employeePageQueryDTO);
    return Result.success(pageResult);
    }

    PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO); 

    @Override
    public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
    //select * from employee limit 0,10
    //开始分页查询
    PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize());

    Page page =employeeMapper.pageQuery(employeePageQueryDTO);
    long total = page.getTotal(); //总记录数
    List records = page.getResult(); //records
    return new PageResult(total,records);
    }

    Page pageQuery(EmployeePageQueryDTO employeePageQueryDTO); 

      


    "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >


    日期格式 错误:

    解决方式: 方式一:在属性上加入注解,对日期进行格式化

    方式二:在 WebMvcConfiguration 中扩展Spring MVC的消息转换器,统一对日期类型进行格式化处理

    配置类上

    @Override
    protected void extendMessageConverters(List> converters) {
    log.info("扩展消息转换器...");
    //创建一个消息转换器对象
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    //需要为消息转换器设置一个对象转换器,对象转换器可以将java对象序列化为json数据
    converter.setObjectMapper(new JacksonObjectMapper());
    //将自己的消息转换器加入容器中
    converters.add(0,converter); //优先使用
    }

    启用禁用员工账号

    业务规则: 可以对状态为“启用” 的员工账号进行“禁用”操作 可以对状态为“禁用”的员工账号进行“启用”操作 状态为“禁用”的员工账号不能登录系统

    根据接口设计中的请求参数形式对应的在 EmployeeController 中创建启用禁用员工账号的方法:

    @PostMapping("/status/{status}")
    @ApiOperation("启用禁用员工账号")
    public Result startStop(@PathVariable Integer status, Long id) {
    log.info("启用禁用员工账号:{},{}", status, id);
    employeeService.startOrStop(status, id);
    return Result.success();
    }

    在 EmployeeService 接口中声明启用禁用员工账号的业务方法:

    void startOrStop(Integer status, Long id);

     在 EmployeeServiceImpl 中实现启用禁用员工账号的业务方法:

    @Override
    public void startOrStop(Integer status, Long id) {
    // Employee employee = new Employee();
    // employee.setStatus(status);
    // employee.setId(id);
    Employee employee = Employee.builder()
    .status(status)
    .id(id)
    .build();
    //Update employee set status =?where id = ?
    employeeMapper.update(employee);

    }

    在 EmployeeMapper 接口中声明 update 方法:

    void updae(Employee employee);

    在 EmployeeMapper.xml 中编写SQL:


    update employee

    name = #{name},
    username = #{username},
    password = #{password},
    phone = #{phone},
    sex = #{sex},
    id_Number = #{idNumber},
    update_Time = #{updateTime},
    update_User = #{updateUser},
    status = #{status},

    where id = #{id}

    编辑员工 

    编辑员工功能涉及到两个接口: 根据id查询员工信息 编辑员工信息

    在 EmployeeController 中创建 getById 方法:

    @GetMapping("/{id}")
    @ApiOperation("根据id查询员工信息")
    public Result getById(@PathVariable Long id) {
    Employee employee = employeeService.getById(id);
    return Result.success(employee);
    }

    在 EmployeeService 接口中声明 getById 方法: 

    Employee getById(Long id); 

    在 EmployeeServiceImpl 中实现 getById 方法: 

    @Override
    public Employee getById(Long id) {
    Employee employee=employeeMapper.getById(id);
    employee.setPassword("****");
    return employee;
    }

    在 EmployeeMapper 接口中声明 getById 方法: 

    @Select("select *from employee where id = #{id}")
    Employee getById(Long id);

     在 EmployeeController 中创建 update 方法:

    @PutMapping
    @ApiOperation("编辑员工信息")
    public Result update(@RequestBody EmployeeDTO employeeDTO) {
    log.info("编辑员工信息:{}", employeeDTO);
    employeeService.update(employeeDTO);
    return Result.success();
    }

    在 EmployeeService 接口中声明 update 方法: 

    void update(EmployeeDTO employeeDTO);

     在 EmployeeServiceImpl 中实现 update 方法:

    public void update(EmployeeDTO employeeDTO) {//通过DTO来接收前端提交过来的数据
    //对象属性拷贝,给employee赋值
    Employee employee = new Employee();
    BeanUtils.copyProperties(employeeDTO, employee);
    employee.setUpdateTime(LocalDateTime.now());
    employee.setUpdateUser(BaseContext.getCurrentId());
    employeeMapper.update(employee); //调用service方法
    }

    导入分类模块功能代码

    业务规则:

    分类名称必须是唯一

    分类按照类型可以分为菜品分类和套餐分类

    新添加的分类状态默认为“禁用” 

    接口设计: 新增分类 分类分页查询 根据id删除分类 修改分类 启用禁用分类 根据类型查询分类

    公共字段自动填充

    问题:代码冗余,不便于后期维护

    • 自定义注解 AutoFill,用于标识需要进行公共字段自动填充的方法
    • 自定义切面类 AutoFillAspect,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值
    • 在 Mapper 的方法上加入 AutoFill 注解
    • 技术点:枚举、注解、AOP、反射

    1. 

    2. 自定义注解 AutoFill

    3.自定义切面 AutoFillAspect

    在Mapper接口的方法上加入 AutoFill 注解

  • 相关阅读:
    分布式下的 ID 实现
    如果你想跨行转做数据分析师,劝你慎重
    Spring Boot关掉APR
    lammps模拟激光加热案例
    C Primer Plus(6) 中文版 第8章 字符输入/输出和输入验证 8.1 单字符I/O:getchar()和putchar()
    LangChain 5易速鲜花内部问答系统
    axios的delete操作你踩过坑吗?
    UML绘制
    什么是分布式软件系统
    MySQL 学习笔记(一)MySQL 事务的ACID特性
  • 原文地址:https://blog.csdn.net/qq_61338849/article/details/132831283