瑞吉外卖第二天

news2024/11/24 19:17:53

问题分析

前面我们已经完成了后台系统的员工登录功能开发,但是目前还存在一个问题,接下来我们来说明一个这个问题, 以及如何处理。

1). 目前现状

用户如果不登录,直接访问系统首页面,照样可以正常访问。
在这里插入图片描述

2). 理想效果
上述这种设计并不合理,我们希望看到的效果应该 是,只有登录成功后才可以访问系统中的页面,如果没有登录, 访问系统中的任何界面都直接跳转到登录页面。
在这里插入图片描述
那么,具体应该怎么实现呢?

可以使用我们之前讲解过的过滤器、拦截器来实现,在过滤器、拦截器中拦截前端发起的请求,判断用户是否已经完成登录,如果没有登录则返回提示信息,跳转到登录页面。
1.2 思路分析
在这里插入图片描述
过滤器具体的处理逻辑如下:

A. 获取本次请求的URI

B. 判断本次请求, 是否需要登录, 才可以访问

C. 如果不需要,则直接放行

D. 判断登录状态,如果已登录,则直接放行

E. 如果未登录, 则返回未登录结果

如果未登录,我们需要给前端返回什么样的结果呢? 这个时候, 我们可以去看看前端是如何处理的 ?
在这里插入图片描述
1.3 代码实现
拦截器与过滤器的区别:

1 拦截器是属于springmvc体系的,只能拦截controller的请求

2 过滤器是属于servlet体系,可以拦截所有的请求。

1). 定义登录校验过滤器

自定义一个过滤器 LoginCheckFilter 并实现 Filter 接口, 在doFilter方法中完成校验的逻辑。 那么接下来, 我们就根据上述分析的步骤, 来完成具体的功能代码实现
所属包: com.mywork.reggie.filter

package com.mywork.reggie.filters;

import com.alibaba.fastjson.JSON;
import com.mywork.reggie.common.R;
import com.sun.org.apache.bcel.internal.generic.RETURN;
import org.springframework.util.AntPathMatcher;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

 @WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter{

     //该类专门用于匹配请求路径的。
     private AntPathMatcher antPathMatcher = new AntPathMatcher();


    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //1.  由于request对象需要使用到子类特有的方法,所以强制类型转换
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        //2. 获取本次请求的url
        String requestURI = request.getRequestURI();  //http://localhost:8080/backend/index.html

        //3. 定义一个数组存储需要放行的url,判断本次请求是否需要登录权限的。
        String[] urls = {"/backend/**","/front/**","/employee/login"};
        boolean flag = checkUrl(urls,requestURI);
        if(flag){
            //直接放行
            filterChain.doFilter(request,response);
            return;
        }

        //4. 如果需要登录权限的,那么从session中取出登录成功标记检查
        HttpSession session = request.getSession();
        if(session.getAttribute("employee")!=null){
            //如果用户已经登录,也可以直接放行
            filterChain.doFilter(request,response);
            return;
        }


        //5. 如果没有登录,返回数据前端,让前端发生跳转
        /*
          目前你使用的是Filter,不是springmvc ,所有如果你需要返回json数据,需要自己转换,

         */
        String json = JSON.toJSONString(R.error("NOTLOGIN"));
        response.getWriter().write(json);

    }

    /*
        检查本次请求路径是否属于直接放行的资源
     */
     private boolean checkUrl(String[] urls, String requestURI) {
         //遍历所有放行的路径,是否与本次请求的路径匹配
         for (String url : urls) {
             if(antPathMatcher.match(url,requestURI)){
                 return true;
             }
         }
         return false;
     }
 }

在这里插入图片描述
2). 开启组件扫描

需要在引导类上, 加上Servlet组件扫描的注解, 来扫描过滤器配置的@WebFilter注解, 扫描上之后, 过滤器在运行时就生效了

package com.mywork.reggie;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@SpringBootApplication
@MapperScan(basePackages = "com.mywork.reggie.dao")
@ServletComponentScan(basePackages = "com.mywork.reggie.filters")
public class ReggieApplication {
    public static void main(String[] args) {
        SpringApplication.run(ReggieApplication.class, args);
    }
}

@ServletComponentScan 的作用:
在SpringBoot项目中, 在引导类/配置类上加了该注解后, 会自动扫描项目中(当前包及其子包下)的@WebServlet , @WebFilter , @WebListener 注解, 自动注册Servlet的相关组件 ;

功能测试

代码编写完毕之后,我们需要将工程重启一下,然后在浏览器地址栏直接输入系统管理后台首页,然后看看是否可以跳转到登录页面即可。我们也可以通过debug的形式来跟踪一下代码执行的过程。

在这里插入图片描述

对于前端的代码, 也可以进行debug调试。

F12打开浏览器的调试工具, 找到我们前面提到的request.js, 在request.js的响应拦截器位置打上断点

在这里插入图片描述
2. 新增员工
2.1 需求分析

后台系统中可以管理员工信息,通过新增员工来添加后台系统用户。点击[添加员工]按钮跳转到新增页面,如下:
在这里插入图片描述
当填写完表单信息, 点击"保存"按钮后, 会提交该表单的数据到服务端, 在服务端中需要接受数据, 然后将数据保存至数据库中。

2.2 数据模型

新增员工,其实就是将我们新增页面录入的员工数据插入到employee表。employee表中的status字段已经设置了默认值1,表示状态正常。
在这里插入图片描述

需要注意,employee表中对username字段加入了唯一约束,因为username是员工的登录账号,必须是唯一的。
在这里插入图片描述
2.3 程序执行流程

在开发代码之前,我们需要结合着前端页面发起的请求, 梳理一下整个程序的执行过程:
在这里插入图片描述

A. 点击"保存"按钮, 页面发送ajax请求,将新增员工页面中输入的数据以json的形式提交到服务端, 请求方式POST, 请求路径 /employee

B. 服务端Controller接收页面提交的数据并调用Service将数据进行保存

C. Service调用Mapper操作数据库,保存数据

2.4 代码实现

在EmployeeController中增加save方法, 用于保存用户员工信息。

  1. EmployeeController 获取登陆者与创建修改时间
package com.mywork.reggie.controller;

import com.mywork.reggie.common.R;
import com.mywork.reggie.entity.Employee;
import com.mywork.reggie.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@RestController
@RequestMapping("/employee")
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;

    

    /**
     * 作用:员工添加
     */
    @PostMapping
    public R save(@RequestBody  Employee employee, HttpSession session){
        //1. 先从session中取出当前登录的用户
        Long empId = (Long) session.getAttribute("employee");
        //2. 补全员工的创建人与修改人
        employee.setCreateUser(empId);
        employee.setUpdateUser(empId);
        //3. 调用service方法进行添加
        employeeService.save(employee);
        return R.success("添加成功");
    }


}

EmployeeService

package com.mywork.reggie.service;

import com.mywork.reggie.common.R;
import com.mywork.reggie.entity.Employee;

public interface EmployeeService {

    //添加员工
    void save(Employee employee);
}

EmployeeServiceImpl

package com.mywork.reggie.service.impl;

import com.mywork.reggie.common.R;
import com.mywork.reggie.dao.EmployeeDao;
import com.mywork.reggie.entity.Employee;
import com.mywork.reggie.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.time.LocalDateTime;

@Service
public class EmployeeServiceImpl implements EmployeeService{

    @Autowired(required = false)
    private EmployeeDao employeeDao;
   
    

    /*
    添加员工
     */
    @Override
    public void save(Employee employee) {
        //1.补全用户的数据
        employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
        //2. 补全创建时间与修改时间、状态
        employee.setStatus(1);//默认是启用的状态
        employee.setCreateTime(LocalDateTime.now());
        employee.setUpdateTime(LocalDateTime.now());
        employeeDao.save(employee);
    }
}

EmployeeMapper
用#号代表映射的是实体类里的属性名

package com.mywork.reggie.dao;

import com.mywork.reggie.entity.Employee;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;

public interface EmployeeDao {


   

    @Insert("insert into employee values(null,#{name},#{username},#{password},#{phone},#{sex},#{idNumber},#{status},#{createTime},#{updateTime},#{createUser},#{updateUser})")
    void save(Employee employee);
}

2.5 功能测试

代码编写完毕之后,我们需要将工程重启, 完毕之后直接访问管理系统首页, 点击 “员工管理” 页面中的 “添加员工” 按钮, 输入员工基本信息, 然后点击 “保存” 进行数据保存, 保存完毕后, 检查数据库中是否录入员工数据。

当我们在测试中,添加用户时, 输入了一个已存在的用户名时,前端界面出现错误提示信息:
在这里插入图片描述
而此时,服务端已经报错了, 报错信息如下:
在这里插入图片描述
出现上述的错误, 主要就是因为在 employee 表结构中,我们针对于username字段,建立了唯一索引,添加重复的username数据时,违背该约束,就会报错。但是此时前端提示的信息并不具体,用户并不知道是因为什么原因造成的该异常,我们需要给用户提示详细的错误信息

2.6 全局异常处理
2.6.1 思路分析

要想解决上述测试中存在的问题,我们需要对程序中可能出现的异常进行捕获,通常有两种处理方式:

A. 在Controller方法中加入 try…catch 进行异常捕获
在这里插入图片描述
如果采用这种方式,虽然可以解决,但是存在弊端,需要我们在保存其他业务数据时,也需要在Controller方法中加上try…catch进行处理,代码冗余,不通用。

B. 使用异常处理器进行全局异常捕获

采用这种方式来实现,我们只需要在项目中定义一个通用的全局异常处理器,就可以解决本项目的所有异常。
2.6.2 全局异常处理器
在这里插入图片描述

在项目中自定义一个全局异常处理器,在异常处理器上加上注解 @ControllerAdvice,可以通过属性annotations指定拦截哪一类的Controller方法。 并在异常处理器的方法上加上注解 @ExceptionHandler 来指定拦截的是那一类型的异常。

  1. 自定义一个异常类,名字已存在的异常类
package com.mywork.reggie.exception;

public class NameExistsException extends  RuntimeException {

    public NameExistsException(String message) {
        super(message);
    }
}

修改EmployeeServiceImpl添加方法,添加之前先检查username是否已经存在
在这里插入图片描述

package com.mywork.reggie.service.impl;

import com.mywork.reggie.common.R;
import com.mywork.reggie.dao.EmployeeDao;
import com.mywork.reggie.entity.Employee;
import com.mywork.reggie.exception.NameExistsException;
import com.mywork.reggie.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.time.LocalDateTime;

@Service
public class EmployeeServiceImpl implements EmployeeService{

    @Autowired(required = false)
    private EmployeeDao employeeDao;
   

    /*
    添加员工
     */
    @Override
    public void save(Employee employee) {
        //查询员工的名字是否存在
        Employee dbEmp = employeeDao.findByName(employee);
        //如果不为空
        if(dbEmp!=null){
            throw  new NameExistsException(employee.getUsername());
        }


        //1.补全用户的数据
        employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
        //2. 补全创建时间与修改时间、状态
        employee.setStatus(1);//默认是启用的状态
        employee.setCreateTime(LocalDateTime.now());
        employee.setUpdateTime(LocalDateTime.now());
        employeeDao.save(employee);
    }
}

自定义 了全局异常处理器

package com.mywork.reggie.exception;

import com.mywork.reggie.common.R;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/*
 @RestControllerAdvice 表明该类是一个全局异常处理器。
 */
@RestControllerAdvice
public class GlobalExceptionHandler {


    /*
   @ExceptionHandler : 用于指定这个方法处理那些类型的异常的。

    */
    @ExceptionHandler(NameExistsException.class)
    public R exceptionHandler(NameExistsException e){
        e.printStackTrace(); //打印异常信息,否则控制台没有任何的内容
        return R.error(e.getMessage()+"已经存在");
    }

    /*
    @ExceptionHandler : 用于指定这个方法处理那些类型的异常的。

     */
    @ExceptionHandler(Exception.class)
    public R exceptionHandler(Exception e){
        e.printStackTrace(); //打印异常信息,否则控制台没有任何的内容
        return R.error("目前访问人数过多,请稍后!");
    }
}

2.6.3 测试

全局异常处理器编写完毕之后,我们需要将项目重启, 完毕之后直接访问管理系统首页, 点击 “员工管理” 页面中的 “添加员工” 按钮。当我们在测试中,添加用户时, 输入了一个已存在的用户名时,前端界面出现如下错误提示信息:
在这里插入图片描述
3. 员工分页查询
3.1 需求分析

系统中的员工很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。而在我们的分页查询页面中, 除了分页条件以外,还有一个查询条件 “员工姓名”。
在这里插入图片描述

  • 请求参数
    • 搜索条件: 员工姓名(模糊查询)
    • 分页条件: 每页展示条数 , 页码
  • 响应数据
    • 总记录数
    • 结果列表

3.2 程序执行流程
3.2.1 页面流程分析

在开发代码之前,需要梳理一下整个程序的执行过程。

A. 点击菜单,打开员工管理页面时,执行查询:
在这里插入图片描述
B. 搜索栏输入员工姓名,回车,执行查询:

在这里插入图片描述
1). 页面发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端

2). 服务端Controller接收页面提交的数据, 并组装条件调用Service查询数据

3). Service调用Dao操作数据库,查询分页数据

4). Controller将查询到的分页数据, 响应给前端页面

5). 页面接收到分页数据, 并通过ElementUI的Table组件展示到页面上

3.2.2 前端代码介绍

1). 访问员工列表页面/member/list.html时, 会触发Vuejs中的钩子方法, 在页面初始化时调用created方法
在这里插入图片描述
从上述的前端代码中我们可以看到, 执行完分页查询, 我们需要给前端返回的信息中需要包含两项 : records 中封装结果列表, total中封装总记录数 。

而在组装请求参数时 , page、pageSize 都是前端分页插件渲染时的参数;
在这里插入图片描述

2). 在getMemberList方法中, 通过axios发起异步请求
在这里插入图片描述
最终发送给服务端的请求为 : GET请求 , 请求链接 /employee/page?page=1&pageSize=10&name=xxx

3.3 代码实现

pageHelper

介绍

PageHelper是国内非常优秀的一款开源的mybatis分页插件,它支持基本主流与常用的数据库, 例如mysql、 oracle、mariaDB、 DB2、 SQLite、Hsqldb等。

本项目在 github 的项目地址: https://github.com/pagehelper/Mybatis-PageHelper

本项目在 gitosc 的项目地址: http://git.oschina.net/free/Mybatis_PageHelper

自己实现分页的弊端:

  • (1)每一种数据库分页语句不同,我们实现分页需要针对每一种数据库定制符合其语法的SQL,如果更换数据库,需要修改分页SQL。limit top rownum
  • (2)除了这些以外,我们还要定义PageBean,封装分页参数,PageHelper提供了PageInfo对象给你们去使用,这里的PageInfo对象就是你们以前定义pageBean.

3.3.1 mybatis分页插件-pageHelper的使用
导入依赖(已完成)

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.0</version>
</dependency>

在application.yml文件配置信息

pagehelper:
   #使用的数据库
  helper-dialect: mysql
  #合理化分页
  #如果查询页数小于实际页数则查询第一页,如果超出则查询最后一页
  reasonable: true

测试(pageHelper的基本使用)

EmployeeDao(添加一个findByName方法)

package com.mywork.reggie.dao;

import com.mywork.reggie.entity.Employee;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface EmployeeDao {



    /*
        分页的时候name的参数是不一定传递的,所以我们需要使用动态sql
        ,一旦使用动态sql,即使只有一个参数也需要使用参数的名字
     */
    List<Employee>  findByPage(@Param("name") String name);

}

EmployeeMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.mywork.reggie.dao.EmployeeDao">

    <select id="findByPage" resultType="employee">
        select * from employee
        <where>
            <if test="name!=null and name!=''">
                name like concat('%',#{name},'%')
            </if>
        </where>
    </select>
  1. 测试类
  package com.mywork.reggie.test;
   
   import com.github.pagehelper.PageHelper;
   import com.github.pagehelper.PageInfo;
   import com.mywork.reggie.dao.EmployeeDao;
   import com.mywork.reggie.entity.Employee;
   import org.junit.jupiter.api.Test;
   import org.springframework.beans.factory.annotation.Autowired;
   import org.springframework.boot.test.context.SpringBootTest;
   
   import java.util.List;
   
   @SpringBootTest
   public class PageTest {
   
       @Autowired
       private EmployeeDao employeeDao;
   
   
       /*
           pageHelper的用法步骤:
               1. 设置当前页与页面大小
               2. 查询页面的数据
               3. 创建PageInfo对象,PageINfo封装了页面的所有数据,相当于以前的PageBean对象
        */
       @Test
       public void test01(){
           //        1. 设置当前页与页面大小
           PageHelper.startPage(1,2);
   
   //        2. 查询页面的数据   limit (curPgae-1)*pageSize , pagesize
           List<Employee> empList = employeeDao.findByPage(null);
   
   
   
   //        3. 创建PageInfo对象,PageINfo封装了页面的所有数据,相当于以前的PageBean对象
           PageInfo<Employee> pageInfo = new PageInfo<>(empList);
   
           System.out.println("当前页:"+pageInfo.getPageNum());
           System.out.println("页面大小:"+pageInfo.getPageSize());
           System.out.println("总记录数:"+pageInfo.getTotal());
           System.out.println("总页数:"+pageInfo.getPages());
           System.out.println("页面的数据:"+pageInfo.getList());
   
       }
   }
   

pagehelp小结
1.设置当前页与页面大小
2.查询页面的数据
3.创建PageInfo对象,把List对象传入即可

3.3.2 分页查询实现

在上面我们已经分析了,页面在进行分页查询时, 具体的请求信息如下:

  请求  	说明                    
  请求方式	GET                   
  请求路径	/employee/page        
  请求参数	page , pageSize , name

那么查询完毕后我们需要给前端返回什么样的结果呢?

在上述我们也分析了, 查询返回的结果数据data中应该封装两项信息, 分别为: records 封装分页列表数据, total 中封装符合条件的总记录数。 那我们则需要自定义一个Page类必须包含records 与total两个属性的

Page实体类的编写

package com.mywork.reggie.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Page<T>  {
    protected List<T> records;
    protected long total;
    protected long pageSize;
    protected long page;

}

EmployeeController控制器

package com.mywork.reggie.controller;

import com.github.pagehelper.PageInfo;
import com.mywork.reggie.common.R;
import com.mywork.reggie.entity.Employee;
import com.mywork.reggie.entity.Page;
import com.mywork.reggie.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@RestController
@RequestMapping("/employee")
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;

    /**
     * 作用:员工列表分页
     */
    @GetMapping("/page")
    public R  page(@RequestParam(defaultValue = "1") Integer page,
                   @RequestParam(defaultValue = "10") Integer pageSize,String name){
      Page<Employee> pageBean =  employeeService.findByPage(page,pageSize,name);
      return R.success(pageBean);
    }



}

EmployeeService接口

package com.mywork.reggie.service;

import com.mywork.reggie.common.R;
import com.mywork.reggie.entity.Employee;
import com.mywork.reggie.entity.Page;

public interface EmployeeService {

 

    /*
    员工分页列表
     */
    Page<Employee> findByPage(Integer page, Integer pageSize, String name);
}

EmployeeServiceImpl接口实现类
推荐安装使用GeneralAllSetter,在类中按alt+enter使用

package com.mywork.reggie.service.impl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.mywork.reggie.common.R;
import com.mywork.reggie.dao.EmployeeDao;
import com.mywork.reggie.entity.Employee;
import com.mywork.reggie.entity.Page;
import com.mywork.reggie.exception.NameExistsException;
import com.mywork.reggie.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.time.LocalDateTime;
import java.util.List;

@Service
public class EmployeeServiceImpl implements EmployeeService{

    @Autowired(required = false)
    private EmployeeDao employeeDao;
   
    /*
    员工分页列表
     */
    @Override
    public Page<Employee> findByPage(Integer page, Integer pageSize, String name) {
        //1.  设置当前页与页面大小
        PageHelper.startPage(page,pageSize);
        //2.  查询当前页的数据
        List<Employee> employeeList = employeeDao.findByPage(name);
        //3.  创建PageInfo对象,把List传入。
        PageInfo<Employee> pageInfo = new PageInfo<>(employeeList);
        //4.  创建Page对象,然后把PageInfo的数据封装到page对象。
        Page<Employee> pageResult = new Page<>(); //alt+enter
        pageResult.setRecords(pageInfo.getList()) ;//页面的数据
        pageResult.setTotal(pageInfo.getTotal()); //总记录数
        pageResult.setPageSize(pageInfo.getPageSize()); //页面大小
        pageResult.setPage(pageInfo.getPageNum()); //当前页
        return pageResult;
    }
}

EmployeeMapper文件

package com.mywork.reggie.dao;

import com.mywork.reggie.entity.Employee;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface EmployeeDao {


   
    /*
        分页的时候name的参数是不一定传递的,所以我们需要使用动态sql
        ,一旦使用动态sql,即使只有一个参数也需要使用参数的名字
     */
    List<Employee>  findByPage(@Param("name") String name);

}

EmployeeMapper.xml映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.mywork.reggie.dao.EmployeeDao">

    <select id="findByPage" resultType="employee">
        select * from employee
        <where>
            <if test="name!=null and name!=''">
                name like concat('%',#{name},'%')
            </if>
        </where>
    </select>

</mapper>

3.4 功能测试

代码编写完毕之后,我们需要将工程重启, 完毕之后直接访问管理系统首页, 默认就会打开员工管理的列表页面, 我们可以查看列表数据是否可以正常展示, 也可以通过分页插件来测试分页功能, 及员工姓名的模糊查询功能。

在进行测试时,可以使用浏览器的监控工具查看页面和服务端的数据交互细节。 并借助于debug的形式, 根据服务端参数接收及逻辑执行情况。
在这里插入图片描述

测试过程中可以发现,对于员工状态字段(status)服务端返回的是状态码(1或者0),但是页面上显示的则是“正常”或者“已禁用”,这是因为页面中在展示数据时进行了处理。
在这里插入图片描述
4. 启用/禁用员工账号
4.1 需求分析

在员工管理列表页面,可以对某个员工账号进行启用或者禁用操作。账号禁用的员工不能登录系统,启用后的员工可以正常登录。如果某个员工账号状态为正常,则按钮显示为 “禁用”,如果员工账号状态为已禁用,则按钮显示为"启用"。

需要注意,只有管理员(admin用户)可以对其他普通用户进行启用、禁用操作,所以普通用户登录系统后启用、禁用按钮不显示。

A. admin 管理员登录
在这里插入图片描述

B. 普通用户登录
在这里插入图片描述
4.2 程序执行流程
4.2.1 页面按钮动态展示

在上述的需求中,我们提到需要实现的效果是 : 只有管理员(admin用户)可以对其他普通用户进行启用、禁用操作,所以普通用户登录系统后启用、禁用按钮不显示 , 页面中是怎么做到只有管理员admin能够看到启用、禁用按钮的?

1). 在列表页面(list.html)加载时, 触发钩子函数created, 在钩子函数中, 会从localStorage中获取到用户登录信息, 然后获取到用户名
在这里插入图片描述
2). 在页面中, 通过Vue指令v-if进行判断,如果登录用户为admin将展示 启用/禁用 按钮, 否则不展示
在这里插入图片描述
4.2.2 执行流程分析

1). 当管理员admin点击 “启用” 或 “禁用” 按钮时, 调用方法statusHandle
在这里插入图片描述
2). statusHandle方法中进行二次确认, 然后发起ajax请求, 传递id、status参数
在这里插入图片描述

最终发起异步请求, 请求服务端, 请求信息如下:

请求 说明
请求方式 PUT
请求路径 /employee
请求参数 {“id”:xxx,“status”:xxx}

{…params} : 三点是ES6中出现的扩展运算符。作用是遍历当前使用的对象能够访问到的所有属性,并将属性放入当前对象中。

4.3 代码实现

在开发代码之前,需要梳理一下整个程序的执行过程:

1). 页面发送ajax请求,将参数(id、status)提交到服务端

2). 服务端Controller接收页面提交的数据并调用Service更新数据

3). Service调用Dao操作数据库

启用、禁用员工账号,本质上就是一个更新操作,也就是对status状态字段进行操作。在Controller中创建update方法,此方法是一个通用的修改员工信息的方法。
代码实现

  1. EmployeeController
package com.mywork.reggie.controller;

import com.github.pagehelper.PageInfo;
import com.mywork.reggie.common.R;
import com.mywork.reggie.entity.Employee;
import com.mywork.reggie.entity.Page;
import com.mywork.reggie.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@RestController
@RequestMapping("/employee")
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;


    /**
     * 作用:修改员工信息
     */
    @PutMapping
    public R  updateEmp(@RequestBody Employee employee,HttpSession session){
        //补全修改人
        //1.先从session中取出当前登录的用户
        Long empId = (Long) session.getAttribute("employee");
        //2. 补全员工的创建人与修改人
        employee.setUpdateUser(empId);
        employeeService.updateEmp(employee);
        return R.success("修改成功");
    }


}

EmployeeService

package com.mywork.reggie.service;

import com.mywork.reggie.common.R;
import com.mywork.reggie.entity.Employee;
import com.mywork.reggie.entity.Page;

public interface EmployeeService {

 
    /*
    修改员工信息
     */
    void updateEmp(Employee employee);
}

EmployeeServiceImp

package com.mywork.reggie.service.impl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.mywork.reggie.common.R;
import com.mywork.reggie.dao.EmployeeDao;
import com.mywork.reggie.entity.Employee;
import com.mywork.reggie.entity.Page;
import com.mywork.reggie.exception.NameExistsException;
import com.mywork.reggie.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.time.LocalDateTime;
import java.util.List;

@Service
public class EmployeeServiceImpl implements EmployeeService{

    @Autowired(required = false)
    private EmployeeDao employeeDao;
   

    @Override
    public void updateEmp(Employee employee) {
        employee.setUpdateTime(LocalDateTime.now());
        employeeDao.updateEmp(employee);
    }
}

EmployeeMapper

package com.mywork.reggie.dao;

import com.mywork.reggie.entity.Employee;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

import java.util.List;

public interface EmployeeDao {


   
    /*
    修改员工信息 ,不管你是修改员工的状态还是修改员工的基本信息都可以使用这个方法,
    由于修改不同信息传递的字段不一样,所以这里使用动态sql
     */
    void updateEmp(Employee employee);
}

EmployeeMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.mywork.reggie.dao.EmployeeDao">

   

    <!--set标签的作用: 1.帮你添加set的关键字,2.去除最后一个逗号-->
    <update id="updateEmp">
        update employee
        <set>
            <if test="username!=null and username!=''">
                username=#{username},
            </if>
            <if test="name!=null and name!=''">
                name=#{name},
            </if>
            <if test="password!=null and password!=''">
                password=#{password},
            </if>
            <if test="phone!=null and phone!=''">
                phone=#{phone},
            </if>
            <if test="sex!=null and sex!=''">
                sex=#{sex},
            </if>
            <if test="idNumber!=null and idNumber!=''">
                id_number=#{idNumber},
            </if>
            <if test="status!=null">
                status=#{status},
            </if>

            <if test="updateTime!=null">
                update_time=#{updateTime},
            </if>

            <if test="updateUser!=null">
                update_user=#{updateUser},
            </if>
        </set>
        where id=#{id}

    </update>
</mapper>

4.4 功能测试

代码编写完毕之后,我们需要将工程重启。 然后访问前端页面, 进行 “启用” 或 “禁用” 的测试
在这里插入图片描述

测试过程中没有报错,但是功能并没有实现,查看数据库中的数据也没有变化。但是从控制台输出的日志, 可以看出确实没有更新成功。
在这里插入图片描述
而在我们的数据库表结构中, 并不存在该ID, 数据库中 风清扬 对应的ID为 1420038345634918401
在这里插入图片描述
4.5 代码修复
4.5.1 原因分析

通过观察控制台输出的SQL发现页面传递过来的员工id的值和数据库中的id值不一致,这是怎么回事呢?

在这里插入图片描述

在分页查询时,服务端会将返回的R对象进行json序列化,转换为json格式的数据,而员工的ID是一个Long类型的数据,而且是一个长度为 19 位的长整型数据, 该数据返回给前端是没有问题的。
在这里插入图片描述
那么具体的问题出现在哪儿呢?

问题实际上, 就出现在前端JS中, js在对长度较长的长整型数据进行处理时, 会损失精度, 从而导致提交的id和数据库中的id不一致。 这里,我们也可以做一个简单的测试,代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script>
        alert(1420038345634918401);
    </script>
</head>
<body>
</body>
</html>

4.5.2 解决方案

要想解决这个问题,也很简单,我们只需要让js处理的ID数据类型为字符串类型即可, 这样就不会损失精度了。同样, 大家也可以做一个测试:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script>
        alert("1420038345634918401");
    </script>
</head>
<body>
</body>
</html>

那么在我们的业务中, 我们只需要让分页查询返回的json格式数据库中, long类型的属性, 不直接转换为数字类型, 转换为字符串类型就可以解决这个问题了 , 最终返回的结果为 :
在这里插入图片描述
4.5.3 代码修复

由于在SpringMVC中, 将Controller方法返回值转换为json对象, 是通过jackson来实现的, 涉及到SpringMVC中的一个消息转换器MappingJackson2HttpMessageConverter, 所以我们要解决这个问题, 就需要对该消息转换器的功能进行拓展。

具体实现步骤:

1). 提供对象转换器JacksonObjectMapper,基于Jackson进行Java对象到json数据的转换(资料中已经提供,直接复制到项目中使用)

2). 在WebMvcConfig配置类中扩展Spring mvc的消息转换器,在此消息转换器中使用提供的对象转换器进行Java对象到json数据的转换

1). 引入JacksonObjectMapper

package com.mywork.reggie.config;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;

/**
 * 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
 * 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
 * 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
 */
public class JacksonObjectMapper extends ObjectMapper {
    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
    public JacksonObjectMapper() {
        super();
        //收到未知属性时不报异常
        this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
        //反序列化时,属性不存在的兼容处理
  this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

        SimpleModule simpleModule = new SimpleModule()
                .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))

                .addSerializer(BigInteger.class, ToStringSerializer.instance)
                .addSerializer(Long.class, ToStringSerializer.instance)

                .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
        //注册功能模块 例如,可以添加自定义序列化器和反序列化器
        this.registerModule(simpleModule);
    }
}

该自定义的对象转换器, 主要指定了, 在进行json数据序列化及反序列化时, LocalDateTime、LocalDate、LocalTime的处理方式, 以及BigInteger及Long类型数据,直接转换为字符串。
2). 在WebMvcConfig中重写方法extendMessageConverters

/**
 * 扩展mvc框架的消息转换器
 * @param converters
 */
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    log.info("扩展消息转换器...");
    //创建消息转换器对象
    MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
    //设置对象转换器,底层使用Jackson将Java对象转为json
    messageConverter.setObjectMapper(new JacksonObjectMapper());
    //将上面的消息转换器对象追加到mvc框架的转换器集合中
    converters.add(0,messageConverter);
}

5. 编辑员工信息
5.1 需求分析

在员工管理列表页面点击 “编辑” 按钮,跳转到编辑页面,在编辑页面回显员工信息并进行修改,最后点击 “保存” 按钮完成编辑操作。
在这里插入图片描述

那么从上述的分析中,我们可以看出当前实现的编辑功能,我们需要实现两个方法:

A. 根据ID查询, 用于页面数据回显

B. 保存修改

5.2 程序执行流程

在开发代码之前需要梳理一下操作过程和对应的程序的执行流程:

1). 点击编辑按钮时,页面跳转到add.html,并在url中携带参数[员工id]
在这里插入图片描述
2). 在add.html页面获取url中的参数[员工id]

3). 发送ajax请求,请求服务端,同时提交员工id参数

4). 服务端接收请求,根据员工id查询员工信息,将员工信息以json形式响应给页面
在这里插入图片描述
5.3 代码实现
5.3.1 根据ID查询

经过上述的分析,我们看到,在根据ID查询员工信息时,请求信息如下:
在这里插入图片描述
代码实现:

  1. 在EmployeeController中增加方法, 根据ID查询员工信息。
package com.mywork.reggie.controller;

import com.mywork.reggie.common.R;
import com.mywork.reggie.entity.Employee;
import com.mywork.reggie.entity.Page;
import com.mywork.reggie.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;

@RestController
@RequestMapping("/employee")
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;
     /**
     * 作用:根据id查询员工的信息
     */
    @GetMapping("/{id}")
    public R  findById(@PathVariable  Long id){
        Employee employee =  employeeService.findById(id);
        return R.success(employee);
    }

}

EmployeeService

package com.mywork.reggie.service;

import com.mywork.reggie.common.R;
import com.mywork.reggie.entity.Employee;
import com.mywork.reggie.entity.Page;

public interface EmployeeService {

    

    /**
     * 根据id查找员工
     * @param id
     * @return
     */
    Employee findById(Long id);
}

EmployeeServiceImpl

package com.mywork.reggie.service.impl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.mywork.reggie.common.R;
import com.mywork.reggie.dao.EmployeeDao;
import com.mywork.reggie.entity.Employee;
import com.mywork.reggie.entity.Page;
import com.mywork.reggie.exception.NameExistsException;
import com.mywork.reggie.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.time.LocalDateTime;
import java.util.List;

@Service
public class EmployeeServiceImpl implements EmployeeService{

    @Autowired(required = false)
    private EmployeeDao employeeDao;

    /**
     * 根据id查找员工
     * @param id
     * @return
     */
    @Override
    public Employee findById(Long id) {
        Employee employee = employeeDao.findById(id);
        return employee;
    }
}

EmployeeMapper

package com.mywork.reggie.dao;

import com.mywork.reggie.entity.Employee;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

import java.util.List;

public interface EmployeeDao {


 

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

5.3.2 修改员工(已完成不需要再写)

经过上述的分析,我们看到,在修改员工信息时,请求信息如下:
在这里插入图片描述

5.4 功能测试

代码编写完毕之后,我们需要将工程重启。 然后访问前端页面, 按照前面分析的操作流程进行测试,查看数据是否正常修改即可。

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

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

相关文章

Java 【异常】

一、认识异常 Exception 在 Java 中&#xff0c;将程序执行过程中发生的不正常行为称为异常 。 异常是异常exception&#xff0c;报错是报错error 1.算数异常 0不能作为除数&#xff0c;所以算数异常 2.空指针异常 arr不指向任何对象&#xff0c;打印不出arr的长度&#xff0c;…

【动态规划刷题 12】等差数列划分 最长湍流子数组

139. 单词拆分 链接: 139. 单词拆分 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。 注意&#xff1a;不要求字典中出现的单词全部都使用&#xff0c;并且字典中的单词可以重复使用。 示例 1&#xff1a; 输入: …

思腾合力GPU服务器

布局智能生产基地&#xff01;经开区思腾合力超前助力“东数西算”&#xff01; 2022-04-20 18:41泰达服务 算力作为数字经济的核心生产力&#xff0c;是国民经济发展的重要基础设施。经开区企业思腾合力在“十四五”开局之年打造出集研发、生产、制造为一体的人工智能产业园…

【C++】常用拷贝和替换算法

0.前言 1.copy #include <iostream> using namespace std;// 常用拷贝算法 copy #include<vector> #include<algorithm>void myPrint(int val) {cout << val << " "; }void test01() {vector<int>v;for (int i 0; i < 10; i…

@Autowired和@Resource

文章目录 简介Autowired注解什么是Autowired注解Autowired注解的使用方式Autowired注解的优势和不足 Qualifier总结&#xff1a; Resource注解什么是Resource注解Resource注解的使用方式Resource注解的优势和不足 Autowired vs ResourceAutowired和Resource的区别为什么推荐使用…

Django+Nginx+uWSGI+Supervisor实战

大家好&#xff0c;真的是许久没有更新文章了&#xff0c;甚是想念&#xff0c;最近这段时间事情很多&#xff0c;家里的事情、工作的事情&#xff0c;真没有太多时间去码文章&#xff0c;其实已经搁置了些许文章&#xff0c;没有整理&#xff0c;趁着这段时间风平浪静&#xf…

MSOS604A是德科技keysight MSOS604A示波器

181/2461/8938Infiniium S系列示波器融合了创新技术&#xff0c;旨在提供卓越的测量。新的10位ADC和低噪声前端技术协同工作&#xff0c;提供高达8 GHz的性能和业界最佳的信号完整性。一个高级框架&#xff0c;配有可快速启动的固态硬盘、可轻松触摸的15英寸电容式显示屏和可快…

洛谷P8814:解密 ← CSP-J 2022 复赛第2题

【题目来源】https://www.luogu.com.cn/problem/P8814https://www.acwing.com/problem/content/4732/【题目描述】 给定一个正整数 k&#xff0c;有 k 次询问&#xff0c;每次给定三个正整数 ni&#xff0c;ei&#xff0c;di&#xff0c;求两个正整数 pi&#xff0c;qi&#xf…

取消合并单元格并快速填充

例如&#xff1a; 步骤如下&#xff1a; 1/ 取消合并单元格 2/ 全选表格 3/ excel导航栏 - 开始 - 查找和选择- 定位条件 快捷键&#xff1a;ctrlG 4/ 选择“空值” - 点击确定 5/ 输入公式 “a2" 注意&#xff0c;自定定位在a3单元格 输入完公式后&#xff0c;按…

file-storage-sdk项目开发中的踩坑记录

文章目录 file-storage-sdk项目开发中的踩坑记录问题1&#xff1a;项目启动报错&#xff1a;Attribute "click" appears more than once in element问题2&#xff1a;前端对话框被遮挡问题3&#xff1a;RequestBody无法接收表单数据问题4&#xff1a;文件上传失败问题…

WebServer 解析HTTP 响应报文

一、基础API部分&#xff0c;介绍stat、mmap、iovec、writev、va_list 1.1 stat​ 作用&#xff1a;获取文件信息 #include <sys/types.h> #include <sys/stat.h> #include <unistd.h>// 获取文件属性&#xff0c;存储在statbuf中 int stat(const char *…

Excel必备!6种快速插入√或x标记的方法揭秘

本教程展示了在Excel中插入勾或叉的六种不同方法。Excel中有两种复选标记——交互式复选框和勾号符号。 勾选框,也称为复选框或复选标记框,是一种特殊控件,允许你通过鼠标单击来选择或取消选择某个选项,即选中或取消选中勾选框。​ 勾号符号,也称为复选符号或复选标记,…

小程序排名优化全攻略

随着小程序的快速发展,小程序之间的竞争也日益激烈。如何在竞争对手众多的环境下脱颖而出,通过小程序排名优化来提高曝光率和流量转化率,已成为许多小程序开发者和运营者关注的重点。本文将全面解析小程序排名优化的方法,让您可以更好地提升小程序的搜索排名。 【名即微】 小程…

开目软件携手纷享销客共建CRM平台,数智推动规模化发展

在制造业升级转型、工业化与信息化深度融合的浪潮下&#xff0c;一批提供制造业数字化赋能的工业软件服务商趁势发力&#xff0c;迎来广阔发展空间。 武汉开目信息技术股份有限公司&#xff08;以下简称“开目软件”&#xff09;是中国高端工业软件领导品牌&#xff0c;凭借自…

Hadoop生态圈中的Hive数据仓库技术

Hadoop生态圈中的Hive数据仓库技术 一、Hive数据仓库的基本概念二、Hive的架构组成三、Hive和数据库的区别四、Hive的安装部署五、Hive的基本使用六、Hive的元数据库的配置问题七、Hive的相关配置项八、Hive的基本使用方式1、Hive的命令行客户端的使用2、使用hiveserver2方法操…

如何预防最新的Mallox变种malloxx勒索病毒感染您的计算机?

导言&#xff1a; 在数字时代&#xff0c; .malloxx 勒索病毒的威胁一直悬在我们头上&#xff0c;如何应对这种威胁&#xff0c;以及在数据被勒索后如何恢复它们&#xff0c;都是备受关注的话题。本文91数据恢复将向您介绍 .malloxx 勒索病毒的独特工作方式&#xff0c;提供与众…

APP启动优化Android篇

背景 为什么重提启动优化&#xff1f;首先&#xff0c;用户进入APP唯一的路径就是启动&#xff0c;这是体验核心链路的第一环。启动分为冷启动、热启动和温启动&#xff0c;本文中「启动」一词如果没有特别说明&#xff0c;均为冷启动。启动时间过长&#xff0c;会造成用户流失…

l8-d10 TCP协议是如何实现可靠传输的

一、TCP主要特点 TCP 是面向连接的运输层协议&#xff0c;在无连接的、不可靠的 IP 网络服务基础之上提供可靠交付的服务。为此&#xff0c;在 IP 的数据报服务基础之上&#xff0c;增加了保证可靠性的一系列措施。 TCP主要特点 1.TCP 是面向连接的运输层协议。 每一条 TCP 连…

【侯捷】C++面向对象 (上)

1.C 编程简介 & 目标 培养代码正规编范class 分为 带pointer 和 不带pointer的 学习C &#xff1a; 语言 标准库 2.C vs C C语言 &#xff1a; &#xff08;type&#xff09;数据 函数 —create—》 数据sC &#xff1a; (class ) 数据 成员 —create—》 对象不带指…