Web后端开发案例

一、准备工作

  • 需求 & 环境

    • 环境搭建:后端工程与数据库环境的准备
    • Spring boot工程,选择依赖。(web,mybatis,mysql,lombok)
    • 在配置文件中引入配置信息,准备对应的实体类
    • 三层架构的基础代码(controller,service,mapper)
    • 准备mapper,service,controller基础结构、接口
  • 开发规范

    • 案例基于当前最为主流的前后端分离开发模式进行开发。

    • 在正式开发之前会定义一个接口文档,前端和后端开发人员需要遵守这一份开发文档进行开发接口。

    • 接口文档是由后端人员根据原型和需求开发的。

    • 前端后端在交互的时候使用Restful风格的接口

      • REST:表述性状态转换,是一种软件架构风格
          1. 通过URL来定位资源
          2. 通过HTTP动词描述操作
        • REST是风格,是约定方式,不是规定,可以打破。
        • 描述功能模块通常使用复数,也就是加s的格式来描述,表示此类资源,而非单个资源。如:users、emps、books…
    • 统一响应结果:Result

    •   @Data
        @NoArgsConstructor
        @AllArgsConstructor
        public class Result{
            private Integer code; //响应码
            private String msg; //请求信息 描述字符串
            private Object data; //返回的数据
            public static Result success(){
                return new Result(1,"success",null);
            }
            public static Result success(Object data){
                return new Result(1,"success",data);
            }
            public static Result error(String msg){
                return new Result(0,msg,null);
            }
        }
      
    • 开发流程:

      • 查看页面原型,明确需求
      • 定义表结构和接口文档
      • 阅读接口文档
      • 思路分析
      • 接口开发
      • 接口测试
      • 前后端联调

二、部门管理开发

查询:

  • image-20230325132111488
  • 浏览接口文档,明确要求。
  • 前端发送请求之后会请求到controllor这个方法
  • controllor调用service接口中的方法获取数据
  • service调用mapper接口中的方法获取数据

image-20230325153008628

删除:

  • image-20230325153403415
  • 请求路径是/depts/
  • 使用注解@PathVariable来得到请求的路径
  • 请求方式是DELETE,所以使用@DeleteMapping

新增部门:

  • image-20230325154858812
  • 将请求的表单数据封装为一个对象
    • 使用@RequestBody注解
      • @RequestBody Dept dept
    • 在service层中补全时间信息

三、员工管理开发

分页查询:

  • image-20230325175833263

  • 查看api规则发现,返回的json数据中有total和rows两个字段,那我们可以新建一个PageBean类作为分页查询的返回对象

  • 注意:在查询时,因为mapper中的形参不是对象或者单个参数,所以需要手动指定每个参数的字段名

    • 使用@Param注解来指定对应的字段名。

          @Select("select * from emp limit #{start},#{pageSize}")
          List<Emp> pageSelect(@Param("start") Integer start, @Param("pageSize") Integer pageSize);
      

    注意:分页查询时,limit后的两个参数为起始位置和查询单页数量。

  • 分页查询插件:PageHelper

    • 引入依赖
    • 使用:
      • PageHelper.startPage(pageNum,pageSize);
      • List<Emp> list = empMapper.lkist();
      • Page<Emp> page = (Page<Emp>) list;

条件分页查询

  • image-20230325223143900
  • 要使用动态SQL实现,因为SQL语句比较复杂,所以使用XML方式配置
  • image-20230326074724969
  • 注意这里的name可能传入空字符串,需要再加一个判断条件以确保不会是空字符串

批量删除员工

  • image-20230326214805588
  • 接受的参数使用逗号分割,并使用动态sql来执行删除语句
  • image-20230326220411602

新增员工

  • image-20230327073117915

  •   @PostMapping
      public Result save(@RequestBody Emp emp){
          log.info("新增员工:{}",emp);
          empService.save(emp);
          return Result.success();
      }
    
  •   //新增员工方法
      void save(Emp emp); 
    
  •   //实现类:
      @Override
      public void save(Emp emp){
          LocalDateTime time = LocalDateTime.now();
          emp.setCreateTime(time);
          emp.setUpdateTime(time);
          empMapper.insert();
      }
    
  •   //新增员工
      @Insert("insert into emp(username,name,gender,image,job,entrydate,dept_id,create_time,update_time) values(#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime})")
      void insert(Emp emp);
    
  • image-20230327074348830

文件上传

  • 简介:

    • 文件上传是指将本地的图片,视频,音频等文件上传到服务器,提供给用户浏览或下载的过程。
    • 文件上传在项目中的应用非常广泛,我们经常发微博,发微信朋友圈都用到了文件上传功能。
  • 前端程序:

      1. 必须定义form表单,其中一个类型为file
      2. 提交方式必须是post方式
      3. 必须指定编码格式,在form表单的属性中添加enctype=“multipart/form-data”
  • 后端程序:

    1. 可以使用spring boot提供的接受文件api
    @RestController
    public class UploadController{
        @PostMapping("/upload")
        public Result upload(String username, Integer age, MultipartFile image){
            return success();
        }
    }
    
  • 形参名和form表单中的name属性保持一致。

  • 请求响应完毕后临时文件会被删除,所以需要保存上传的文件。

本地存储

  • 在服务器端,接受到上传上来的文件之后,将文件存储在本地服务器的磁盘中。
  • spring中已经自带一个方法:transferTo(new File(“path”))
    • 这里的path就是存储的文件位置,文件名可以使用前端传递的文件名

      • 获取原始文件名:getOriginalFilename()
    • 注意:文件夹目录必须存在

    • image-20230327140742363

    • image-20230327140915292

云存储

  • 阿里云OSS

    • 阿里云对象存储OSS,是一款海量、安全、低成本、高可靠的云存储服务,使用OSS,您可以通过网络随时存储和调用包括文本,图片,音频和视频在内的各种文件。
  • 通用思路:

    • 准备工作 -> 参考官方SDK编写入门程序 -> 集成使用
    • image-20230327141846757
  • 阿里云入门:

    • 官方示例代码:Java简单上传 (aliyun.com)

    •   package com.heimamagdemo;
        
        import com.aliyun.oss.ClientException;
        import com.aliyun.oss.OSS;
        import com.aliyun.oss.OSSClientBuilder;
        import com.aliyun.oss.OSSException;
        import com.aliyun.oss.model.PutObjectRequest;
        import com.aliyun.oss.model.PutObjectResult;
        import java.io.FileInputStream;
        import java.io.InputStream;
        
        public class UploadDemo {
        
            public static void main(String[] args) throws Exception {
                // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
                String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
                // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
                String accessKeyId = "yourAccessKeyId";
                String accessKeySecret = "yourAccessKeySecret";
                // 填写Bucket名称,例如examplebucket。
                String bucketName = "examplebucket";
                // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
                String objectName = "exampledir/exampleobject.txt";
                // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
                // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。
                String filePath= "D:\\localpath\\examplefile.txt";
        
                // 创建OSSClient实例。
                OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        
                try {
                    InputStream inputStream = new FileInputStream(filePath);
                    // 创建PutObjectRequest对象。
                    PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, inputStream);
                    // 设置该属性可以返回response。如果不设置,则返回的response为空。
                    putObjectRequest.setProcess("true");
                    // 创建PutObject请求。
                    PutObjectResult result = ossClient.putObject(putObjectRequest);
                    // 如果上传成功,则返回200。
                    System.out.println(result.getResponse().getStatusCode());
                } catch (OSSException oe) {
                    System.out.println("Caught an OSSException, which means your request made it to OSS, "
                            + "but was rejected with an error response for some reason.");
                    System.out.println("Error Message:" + oe.getErrorMessage());
                    System.out.println("Error Code:" + oe.getErrorCode());
                    System.out.println("Request ID:" + oe.getRequestId());
                    System.out.println("Host ID:" + oe.getHostId());
                } catch (ClientException ce) {
                    System.out.println("Caught an ClientException, which means the client encountered "
                            + "a serious internal problem while trying to communicate with OSS, "
                            + "such as not being able to access the network.");
                    System.out.println("Error Message:" + ce.getMessage());
                } finally {
                    if (ossClient != null) {
                        ossClient.shutdown();
                    }
                }
            }
        } 
      
    • image-20230327201420144

    • 可以定义一个工具类来方便使用。

    •   package com.heimamagdemo.utils;
        
        import com.aliyun.oss.OSS;
        import com.aliyun.oss.OSSClientBuilder;
        import com.aliyun.oss.model.*;
        import org.springframework.stereotype.Component;
        import org.springframework.web.multipart.MultipartFile;
        
        import java.io.*;
        import java.net.URL;
        import java.time.LocalDate;
        import java.time.format.DateTimeFormatter;
        import java.util.ArrayList;
        import java.util.Date;
        import java.util.List;
        
        @Component
        public class AliOSSUtil {
        //    private static final String endpoint = AliOSSConfig.getInstance().getEndpoint();
        //    private static final String accessKeyId = AliOSSConfig.getInstance().getAccessKeyId();
        //    private static final String accessKeySecret = AliOSSConfig.getInstance().getAccessKeySecret();
        //    private static final String bucketName = AliOSSConfig.getInstance().getBucketName();
            private static final String endpoint = "oss-cn-beijing.aliyuncs.com";
            private static final String accessKeyId="LTAI5tGjByv5NXuywUAnrLiy";
            private static final String accessKeySecret="EP1CXYMWmeZO5igK7hMd9rZbYTBj99";
            private static final String bucketName="web-myapp";
        
        
            /**
             * 将文件上传到阿里OSS
             *
             * @param sourceFilePathName 本地文件
             * @param aimFilePathName    在阿里OSS中保存的可以包含路径的文件名
             * @return 返回上传后文件的访问路径
             * @throws FileNotFoundException
             */
            public static String upload(String sourceFilePathName, String aimFilePathName) throws FileNotFoundException {
                FileInputStream is = new FileInputStream(sourceFilePathName);
        
                if (aimFilePathName.startsWith("/")) {
                    aimFilePathName = aimFilePathName.substring(1);
                }
        
                // 如果需要上传时设置存储类型与访问权限,请参考以下示例代码。
                ObjectMetadata metadata = new ObjectMetadata();
                int indexOfLastDot = aimFilePathName.lastIndexOf(".");
                String suffix = aimFilePathName.substring(indexOfLastDot);
                metadata.setContentType(getContentType(suffix));
        
                //避免文件覆盖
                aimFilePathName = aimFilePathName.substring(0, indexOfLastDot) + System.currentTimeMillis() + suffix;
        
                PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, aimFilePathName, is);
                //避免访问时将图片下载下来
                putObjectRequest.setMetadata(metadata);
        
                OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        
                ossClient.putObject(putObjectRequest);
        
                Date expiration = new Date(System.currentTimeMillis() + 3600L * 1000 * 24 * 365 * 100);
                URL url = ossClient.generatePresignedUrl(bucketName, aimFilePathName, expiration);
        
                // 关闭ossClient
                ossClient.shutdown();
        
                return url.toString();
            }
        
            /**
             * 网络实现上传头像到OSS
             *
             * @param multipartFile
             * @return
             */
            public static String upload(MultipartFile multipartFile) throws IOException {
                // 获取上传的文件的输入流
                InputStream inputStream = multipartFile.getInputStream();
                // 获取文件名称
                String fileName = multipartFile.getOriginalFilename();
        
                // 避免文件覆盖
                int i = fileName.lastIndexOf(".");
                String suffix = fileName.substring(i);
                fileName = fileName.substring(0, i) + System.currentTimeMillis() + suffix;
        
                // 把文件按照日期进行分类
                // 获取当前日期
                String datePath = DateTimeFormatter.ISO_DATE.format(LocalDate.now());
                // 拼接fileName
                fileName = datePath + "/" + fileName;
        
                // 如果需要上传时设置存储类型与访问权限
                ObjectMetadata metadata = new ObjectMetadata();
                metadata.setContentType(getContentType(fileName.substring(fileName.lastIndexOf("."))));
        
                // 上传文件到OSS时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg。
                PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, fileName, inputStream);
                putObjectRequest.setMetadata(metadata);
        
                OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        
                ossClient.putObject(putObjectRequest);
        
                //文件访问路径
                Date expiration = new Date(System.currentTimeMillis() + 3600L * 1000 * 24 * 365 * 100);
                URL url = ossClient.generatePresignedUrl(bucketName, fileName, expiration);
        
                // 关闭ossClient
                ossClient.shutdown();
                // 把上传到oss的路径返回
                return url.toString();
            }
        
            /**
             * 返回contentType
             *
             * @param FileNameExtension
             * @return
             */
            private static String getContentType(String FileNameExtension) {
                if (FileNameExtension.equalsIgnoreCase(".bmp")) {
                    return "image/bmp";
                }
                if (FileNameExtension.equalsIgnoreCase(".gif")) {
                    return "image/gif";
                }
                if (FileNameExtension.equalsIgnoreCase(".jpeg") ||
                        FileNameExtension.equalsIgnoreCase(".jpg") ||
                        FileNameExtension.equalsIgnoreCase(".png")
                ) {
                    return "image/jpg";
                }
                return "image/jpg";
            }
        
        
            /**
             * 列举 指定路径下所有的文件的文件名
             * 如果要列出根路径下的所有文件,path= ""
             *
             * @param path
             * @return
             */
            public static List<String> listFileName(String path) {
                List<String> res = new ArrayList<>();
                // 构造ListObjectsRequest请求。
                ListObjectsRequest listObjectsRequest = new ListObjectsRequest(bucketName);
        
                // 设置prefix参数来获取fun目录下的所有文件。
                listObjectsRequest.setPrefix(path);
        
                OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        
                // 列出文件。
                ObjectListing listing = ossClient.listObjects(listObjectsRequest);
                // 遍历所有文件
                for (OSSObjectSummary objectSummary : listing.getObjectSummaries()) {
                    System.out.println(objectSummary.getKey());
                }
                // 关闭OSSClient。
                ossClient.shutdown();
                return res;
            }
        
            /**
             * 列举文件下所有的文件url信息
             */
            public static List<String> listFileUrl(String path) {
                List<String> res = new ArrayList<>();
        
                // 构造ListObjectsRequest请求
                ListObjectsRequest listObjectsRequest = new ListObjectsRequest(bucketName);
        
                // 设置prefix参数来获取fun目录下的所有文件。
                listObjectsRequest.setPrefix(path);
        
                OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        
                // 列出文件。
                ObjectListing listing = ossClient.listObjects(listObjectsRequest);
                // 遍历所有文件。
        
                for (OSSObjectSummary objectSummary : listing.getObjectSummaries()) {
                    //文件访问路径
                    Date expiration = new Date(System.currentTimeMillis() + 3600L * 1000 * 24 * 365 * 100);
                    URL url = ossClient.generatePresignedUrl(bucketName, objectSummary.getKey(), expiration);
                    res.add(url.toString());
                }
                // 关闭OSSClient。
                ossClient.shutdown();
                return res;
            }
        
            /**
             * 判断文件是否存在
             *
             * @param objectName
             * @return
             */
            public static boolean isFileExist(String objectName) {
                OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        
                boolean res = ossClient.doesObjectExist(bucketName, objectName);
                return res;
            }
        
            /**
             * 通过文件名下载文件
             *
             * @param objectName    要下载的文件名
             * @param localFileName 本地要创建的文件名
             */
            public static void downloadFile(String objectName, String localFileName) {
                OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        
                // 下载OSS文件到本地文件。如果指定的本地文件存在会覆盖,不存在则新建。
                ossClient.getObject(new GetObjectRequest(bucketName, objectName), new File(localFileName));
                // 关闭OSSClient。
                ossClient.shutdown();
            }
        
            /**
             * 删除文件或目录
             *
             * @param objectName
             */
            public static void delelteFile(String objectName) {
                OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        
                ossClient.deleteObject(bucketName, objectName);
                ossClient.shutdown();
            }
        
            /**
             * 批量删除文件或目录
             *
             * @param keys
             */
            public static void deleteFiles(List<String> keys) {
                OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        
                // 删除文件。
                DeleteObjectsResult deleteObjectsResult = ossClient.deleteObjects(new DeleteObjectsRequest(bucketName).withKeys(keys));
                java.util.List<String> deletedObjects = deleteObjectsResult.getDeletedObjects();
        
                ossClient.shutdown();
            }
            /**
             * 创建文件夹
             *
             * @param folder
             * @return
             */
            public static String createFolder(String folder) {
                // 文件夹名
                final String keySuffixWithSlash = folder;
                OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        
                // 判断文件夹是否存在,不存在则创建
                if (!ossClient.doesObjectExist(bucketName, keySuffixWithSlash)) {
                    // 创建文件夹
                    ossClient.putObject(bucketName, keySuffixWithSlash, new ByteArrayInputStream(new byte[0]));
                    // 得到文件夹名
                    OSSObject object = ossClient.getObject(bucketName, keySuffixWithSlash);
                    String fileDir = object.getKey();
                    ossClient.shutdown();
                    return fileDir;
                }
        
                return keySuffixWithSlash;
            }
        
        }
      

修改员工信息

  • 点击修改后的数据回显。
  • 修改员工。

image-20230327223728129

配置文件

  • 参数配置化:

    • 可以在springboot的配置文件中自定义配置的字段。

    • 然后通过注解@Value(“${字段名}”)来注入参数。

    •       @Value("${aliyun.oss.endpoint}")
            private static String endpoint;
            @Value("${aliyun.oss.accessKeyId}")
            private static String accessKeyId;
            @Value("${aliyun.oss.accessKeySecret}")
            private static String accessKeySecret;
            @Value("${aliyun.oss.bucketName}")
            private static String bucketName;
      
    • image-20230328221205060

  • yaml配置文件:

    •   server:
            port: 8081
            address: 127.0.0.1
      
    • image-20230328223303825

    • image-20230328223508376

  • @ConfigurationProperties注解:

    • 可以自动将配置参数载入到对象的属性中
      • 属性名必须和参数名一致
      • 必须为实体类中的参数提供get/set方法
      • 这个类必须由AOC管理。
    • image-20230328224515330
    • image-20230328230016495

基础登录功能

  • 登录功能

    • 查询用户信息

    •   SELECT COUNT(id) FROM emp WHERE username = #{} and password = #{}
      
    • image-20230329071956421

    • image-20230329072351928

    • image-20230329072925111

  • 登录校验

    • 在我们收到浏览器请求后,先对用户进行校验。
    • 实现思路:
      • 首先http是无状态协议,每一次请求都是独立的
      • 我们可以在登录成功之后存储一个登录标记。
      • 假设员工已经登录了,我们就执行正常的业务操作,否则返回错误信息。
      • 前端拿到错误信息之后自动跳转到登录界面。
    • 我们可以通过同一拦截技术来完成对浏览器所有的请求进行拦截。
      • 在调用login接口时,如果登录成功则进行标记
      • 如果通过拦截时能够验证标记则放行
      • 如果不能通过验证则返回错误信息。
    • 登录标记:
      • 用户在登录成功之后,每一次请求中,都能获取到该标记。
    • 统一拦截:
      • Servlet中提供的:Filter
      • Spring中提供的:Interceptor

登录校验

  • 会话技术

    • 会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接。会话结束。在一次会话中可以包含多次请求和响应。
    • 会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一会话的多次请求间共享数据。
    • 会话跟踪技术:
      • 客户端会话跟踪技术:Cookie
      • 服务端会话跟踪技术:Session
      • 令牌技术(当前最主流)
    • 会话跟踪方案对比:
      • Cookie:image-20230329214518115
      • Session:image-20230329215213724
      • 令牌技术:image-20230329215344417
  • JWT令牌

    • 全称:JSON Web Token
    • 定义了一种简洁的,自包含的格式,用于在通讯双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。
    • 组成:image-20230329220043672
    • 场景:登录认证。
      • 登录成功后,生成令牌
      • 后续每个请求,都要携带JWT令牌,系统在每次请求处理之前,先校验令牌,再处理。
    • 生成JWT令牌:
      • image-20230329222038164
    • 解析JWT令牌:
      • image-20230329222110646
  • 登录后下发令牌:

    • 引入JWT令牌操作工具类
    • 登录成功后,调用工具生成JWT令牌,并返回。
    • 可以使用JWTUtils库来完成这个操作
    • image-20230330094924546
  • 过滤器Filter

    • 概念:

      • Filter过滤器,是JavaWeb三大组件(Servlet、Filter、Listener)之一
      • 过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。
      • 过滤器一般完成一些通用的操作,如登录校验、统一编码处理、敏感字符处理等。
      • image-20230330095605926
    • 快速入门:

      • image-20230330100437939

      • image-20230330100650772

      • 定义Filter:

        • 定义一个类,实现Filter接口,并重写所有的方法。(javax.servlet包下)
      • 配置Filter:

        • Filter类上加上@WebFilter注解,配置拦截资源的路径。
        • 引导类上加@ServletComponentScan注解,开启Servlet组件支持。
      • image-20230330111517774

      • 放行

        •   filterChain.doFilter(servletRequest, servletResponse);
          
    • 详解

      • 执行流程
        • doFilter:
          • 放行前逻辑
          • 放行
          • 放行后逻辑
          • Filter 流程图
          • 放行后访问对应资源,还会返回过滤器中,过滤器中的代码继续运行。
      • 拦截路径
        • 拦截具体路径
          • /login
        • 目录拦截
          • /emps/*
        • 拦截所有
          • /*
      • 过滤器链
        • 在一个web应用中,可以配置多个过滤器,这多个过滤器就形成了一个过滤器链。
        • image-20230330211002599
        • 顺序:
          • 注解配置的Filter,优先级是按照过滤器类名(字符串)的自然排序。
      • 登录校验:
        • image-20230330214121466
  • 拦截器Interceptor

    • 简介&快速入门

      • 概念:是一种动态拦截方法调用的机制,类似于过滤器。Spring框架中提供的,用来动态拦截控制器方法的执行。

      • 作用:拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码。

      • 快速入门:

        • 定义拦截器,实现HandlerInterceptor接口,并重写所有方法
        • 注册拦截器

        image-20230330224441742

      • 详解

        • 拦截路径

          • addPathPatterns设置需要拦截的路径
          • excludePathPatterns设置不需要拦截的路径
          • /*:一级路径
          • /**:多级路径
          • /depts/*:下一级
          • /depts/**:任意下一级或多级
        • 执行流程

          • image-20230330230854091
        • image-20230330230919736

  • 通过注解校验Token

    • @interface

      package com.heimamagdemo.annotation;
      
      import java.lang.annotation.ElementType;
      import java.lang.annotation.Retention;
      import java.lang.annotation.RetentionPolicy;
      import java.lang.annotation.Target;
      
      @Retention(RetentionPolicy.RUNTIME)
      @Target(ElementType.METHOD)
      public @interface TokenCheck {
      }
      
    • class

      package com.heimamagdemo.aspect;
      
      import com.hanzoy.utils.JWTUtils;
      import com.heimamagdemo.pojo.Result;
      import lombok.extern.slf4j.Slf4j;
      import org.aspectj.lang.ProceedingJoinPoint;
      import org.aspectj.lang.annotation.Around;
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.annotation.Pointcut;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Component;
      import org.springframework.util.StringUtils;
      import org.springframework.web.context.request.RequestContextHolder;
      import org.springframework.web.context.request.ServletRequestAttributes;
      
      import javax.servlet.http.HttpServletRequest;
      
      @Component
      @Aspect
      @Slf4j
      public class TokenCheck {
      
          @Autowired
          private JWTUtils jwtUtils;
      
          @Pointcut("@annotation(com.heimamagdemo.annotation.TokenCheck)")
          public void tokenCheckAspect() {
          }
      
          @Around("tokenCheckAspect()")
          public Object aroundTokenCheck(ProceedingJoinPoint joinPoint)throws Throwable {
              ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
              HttpServletRequest request = requestAttributes.getRequest();
              // 从请求头中获取token
              String token = request.getHeader("Token");
              // 校验token是否有效
              if (!StringUtils.hasLength(token) || !jwtUtils.checkToken(token)) {
                  //请求ip
                  log.warn("IP:{}    TokenCheck: token isinvalid!",request.getRemoteHost());
                  // 返回Result.error()对象
                  return Result.error("NOT_LOGIN");
              }
              // 执行被增强的方法,并获取其返回值
              // 对返回值进行处理
              // ...
              return joinPoint.proceed();
          }
      }
      

异常处理

  • 当遇到错误时,返回的数据为错误信息的封装,并不符合开发规范,所以前端不能解析出这个数据。

  • 程序在开发过程中不可避免的会遇到异常现象。

  • 出现异常,该如何处理?

    • 方案一:在controller中使用try…catch处理

    • 方案二:使用全局异常处理器(简单,优雅,推荐)

      image-20230331202515917

  • 如何定义全局异常处理器

    • 定义一个类,使用@RestControllerAdvice

    • 在方法上加上@ExceptionHandler(Exception.class)参数为捕获的异常类型

      package com.heimamagdemo.exception;
      
      import com.heimamagdemo.pojo.Result;
      import org.springframework.web.bind.annotation.ExceptionHandler;
      import org.springframework.web.bind.annotation.RestControllerAdvice;
      
      /**
       * 全局异常处理器
       * @author pqcmm
       */
      @RestControllerAdvice
      public class GlobalExceptionHandler {
          /**
           * 处理所有异常
           * @param e 异常
           * @return Result
           */
          @ExceptionHandler(Exception.class)
          public Result error(Exception e){
              e.printStackTrace();
              return Result.error("对不起,操作失败!");
          }
      }
      
      

      image-20230331203519940

API接口开发文档

1. 部门管理

1.1 部门列表查询

1.1.1 基本信息

请求路径:/depts

请求方式:GET

接口描述:该接口用于部门列表数据查询

1.1.2 请求参数

1.1.3 响应数据

参数格式:application/json

参数说明:

参数名 类型 是否必须 备注
code number 必须 响应码,1 代表成功,0 代表失败
msg string 非必须 提示信息
data object[ ] 非必须 返回的数据
|- id number 非必须 id
|- name string 非必须 部门名称
|- createTime string 非必须 创建时间
|- updateTime string 非必须 修改时间

响应数据样例:

{
  "code": 1,
  "msg": "success",
  "data": [
    {
      "id": 1,
      "name": "学工部",
      "createTime": "2022-09-01T23:06:29",
      "updateTime": "2022-09-01T23:06:29"
    },
    {
      "id": 2,
      "name": "教研部",
      "createTime": "2022-09-01T23:06:29",
      "updateTime": "2022-09-01T23:06:29"
    }
  ]
}

1.2 删除部门

1.2.1 基本信息

请求路径:/depts/

请求方式:DELETE

接口描述:该接口用于根据ID删除部门数据

1.2.2 请求参数

参数格式:路径参数

参数说明:

参数名 类型 是否必须 备注
id number 必须 部门ID

请求参数样例:

/depts/1

1.2.3 响应数据

参数格式:application/json

参数说明:

参数名 类型 是否必须 备注
code number 必须 响应码,1 代表成功,0 代表失败
msg string 非必须 提示信息
data object 非必须 返回的数据

响应数据样例:

{
    "code":1,
    "msg":"success",
    "data":null
}

1.3 添加部门

1.3.1 基本信息

请求路径:/depts

请求方式:POST

接口描述:该接口用于添加部门数据

1.3.2 请求参数

格式:application/json

参数说明:

参数名 类型 是否必须 备注
name string 必须 部门名称

请求参数样例:

{
	"name": "教研部"
}

1.3.3 响应数据

参数格式:application/json

参数说明:

参数名 类型 是否必须 备注
code number 必须 响应码,1 代表成功,0 代表失败
msg string 非必须 提示信息
data object 非必须 返回的数据

响应数据样例:

{
    "code":1,
    "msg":"success",
    "data":null
}

1.4 根据ID查询

1.4.1 基本信息

请求路径:/depts/

请求方式:GET

接口描述:该接口用于根据ID查询部门数据

1.4.2 请求参数

参数格式:路径参数

参数说明:

参数名 类型 是否必须 备注
id number 必须 部门ID

请求参数样例:

/depts/1

1.4.3 响应数据

参数格式:application/json

参数说明:

参数名 类型 是否必须 备注
code number 必须 响应码,1 代表成功,0 代表失败
msg string 非必须 提示信息
data object 非必须 返回的数据
|- id number 非必须 id
|- name string 非必须 部门名称
|- createTime string 非必须 创建时间
|- updateTime string 非必须 修改时间

响应数据样例:

{
  "code": 1,
  "msg": "success",
  "data": {
    "id": 1,
    "name": "学工部",
    "createTime": "2022-09-01T23:06:29",
    "updateTime": "2022-09-01T23:06:29"
  }
}

1.5 修改部门

1.5.1 基本信息

请求路径:/depts

请求方式:PUT

接口描述:该接口用于修改部门数据

1.5.2 请求参数

格式:application/json

参数说明:

参数名 类型 是否必须 备注
id number 必须 部门ID
name string 必须 部门名称

请求参数样例:

{
	"id": 1,
	"name": "教研部"
}

1.5.3 响应数据

参数格式:application/json

参数说明:

参数名 类型 是否必须 备注
code number 必须 响应码,1 代表成功,0 代表失败
msg string 非必须 提示信息
data object 非必须 返回的数据

响应数据样例:

{
    "code":1,
    "msg":"success",
    "data":null
}

2. 员工管理

2.1 员工列表查询

2.1.1 基本信息

请求路径:/emps

请求方式:GET

接口描述:该接口用于员工列表数据的条件分页查询

2.1.2 请求参数

参数格式:queryString

参数说明:

参数名称 是否必须 示例 备注
name 姓名
gender 1 性别 , 1 男 , 2 女
begin 2010-01-01 范围匹配的开始时间(入职日期)
end 2020-01-01 范围匹配的结束时间(入职日期)
page 1 分页查询的页码,如果未指定,默认为1
pageSize 10 分页查询的每页记录数,如果未指定,默认为10

请求数据样例:

/emps?name=张&gender=1&begin=2007-09-01&end=2022-09-01&page=1&pageSize=10

2.1.3 响应数据

参数格式:application/json

参数说明:

名称 类型 是否必须 默认值 备注 其他信息
code number 必须 响应码, 1 成功 , 0 失败
msg string 非必须 提示信息
data object 必须 返回的数据
|- total number 必须 总记录数
|- rows object [] 必须 数据列表 item 类型: object
|- id number 非必须 id
|- username string 非必须 用户名
|- name string 非必须 姓名
|- password string 非必须 密码
|- entrydate string 非必须 入职日期
|- gender number 非必须 性别 , 1 男 ; 2 女
|- image string 非必须 图像
|- job number 非必须 职位, 说明: 1 班主任,2 讲师, 3 学工主管, 4 教研主管, 5 咨询师
|- deptId number 非必须 部门id
|- createTime string 非必须 创建时间
|- updateTime string 非必须 更新时间

响应数据样例:

{
  "code": 1,
  "msg": "success",
  "data": {
    "total": 2,
    "rows": [
       {
        "id": 1,
        "username": "jinyong",
        "password": "123456",
        "name": "金庸",
        "gender": 1,
        "image": "https://web-framework.oss-cn-hangzhou.aliyuncs.com/2022-09-02-00-27-53B.jpg",
        "job": 2,
        "entrydate": "2015-01-01",
        "deptId": 2,
        "createTime": "2022-09-01T23:06:30",
        "updateTime": "2022-09-02T00:29:04"
      },
      {
        "id": 2,
        "username": "zhangwuji",
        "password": "123456",
        "name": "张无忌",
        "gender": 1,
        "image": "https://web-framework.oss-cn-hangzhou.aliyuncs.com/2022-09-02-00-27-53B.jpg",
        "job": 2,
        "entrydate": "2015-01-01",
        "deptId": 2,
        "createTime": "2022-09-01T23:06:30",
        "updateTime": "2022-09-02T00:29:04"
      }
    ]
  }
}

2.2 删除员工

2.2.1 基本信息

请求路径:/emps/

请求方式:DELETE

接口描述:该接口用于批量删除员工的数据信息

2.2.2 请求参数

参数格式:路径参数

参数说明:

参数名 类型 示例 是否必须 备注
ids 数组 array 1,2,3 必须 员工的id数组

请求参数样例:

/emps/1,2,3

2.2.3 响应数据

参数格式:application/json

参数说明:

参数名 类型 是否必须 备注
code number 必须 响应码,1 代表成功,0 代表失败
msg string 非必须 提示信息
data object 非必须 返回的数据

响应数据样例:

{
    "code":1,
    "msg":"success",
    "data":null
}

2.3 添加员工

2.3.1 基本信息

请求路径:/emps

请求方式:POST

接口描述:该接口用于添加员工的信息

2.3.2 请求参数

参数格式:application/json

参数说明:

名称 类型 是否必须 备注
username string 必须 用户名
name string 必须 姓名
gender number 必须 性别, 说明: 1 男, 2 女
image string 非必须 图像
deptId number 非必须 部门id
entrydate string 非必须 入职日期
job number 非必须 职位, 说明: 1 班主任,2 讲师, 3 学工主管, 4 教研主管, 5 咨询师

请求数据样例:

{
  "image": "https://web-framework.oss-cn-hangzhou.aliyuncs.com/2022-09-03-07-37-38222.jpg",
  "username": "linpingzhi",
  "name": "林平之",
  "gender": 1,
  "job": 1,
  "entrydate": "2022-09-18",
  "deptId": 1
}

2.3.3 响应数据

参数格式:application/json

参数说明:

参数名 类型 是否必须 备注
code number 必须 响应码,1 代表成功,0 代表失败
msg string 非必须 提示信息
data object 非必须 返回的数据

响应数据样例:

{
    "code":1,
    "msg":"success",
    "data":null
}

2.4 根据ID查询

2.4.1 基本信息

请求路径:/emps/

请求方式:GET

接口描述:该接口用于根据主键ID查询员工的信息

2.4.2 请求参数

参数格式:路径参数

参数说明:

参数名 类型 是否必须 备注
id number 必须 部门ID

请求参数样例:

/emps/1

2.4.3 响应数据

参数格式:application/json

参数说明:

名称 类型 是否必须 默认值 备注 其他信息
code number 必须 响应码, 1 成功 , 0 失败
msg string 非必须 提示信息
data object 必须 返回的数据
|- id number 非必须 id
|- username string 非必须 用户名
|- name string 非必须 姓名
|- password string 非必须 密码
|- entrydate string 非必须 入职日期
|- gender number 非必须 性别 , 1 男 ; 2 女
|- image string 非必须 图像
|- job number 非必须 职位, 说明: 1 班主任,2 讲师, 3 学工主管, 4 教研主管, 5 咨询师
|- deptId number 非必须 部门id
|- createTime string 非必须 创建时间
|- updateTime string 非必须 更新时间

响应数据样例:

{
  "code": 1,
  "msg": "success",
  "data": {
    "id": 2,
    "username": "zhangwuji",
    "password": "123456",
    "name": "张无忌",
    "gender": 1,
    "image": "https://web-framework.oss-cn-hangzhou.aliyuncs.com/2022-09-02-00-27-53B.jpg",
    "job": 2,
    "entrydate": "2015-01-01",
    "deptId": 2,
    "createTime": "2022-09-01T23:06:30",
    "updateTime": "2022-09-02T00:29:04"
  }
}

2.5 修改员工

2.5.1 基本信息

请求路径:/emps

请求方式:PUT

接口描述:该接口用于修改员工的数据信息

2.5.2 请求参数

参数格式:application/json

参数说明:

名称 类型 是否必须 备注
id number 必须 id
username string 必须 用户名
name string 必须 姓名
gender number 必须 性别, 说明: 1 男, 2 女
image string 非必须 图像
deptId number 非必须 部门id
entrydate string 非必须 入职日期
job number 非必须 职位, 说明: 1 班主任,2 讲师, 3 学工主管, 4 教研主管, 5 咨询师

请求数据样例:

{
  "id": 1,
  "image": "https://web-framework.oss-cn-hangzhou.aliyuncs.com/2022-09-03-07-37-38222.jpg",
  "username": "linpingzhi",
  "name": "林平之",
  "gender": 1,
  "job": 1,
  "entrydate": "2022-09-18",
  "deptId": 1
}

2.5.3 响应数据

参数格式:application/json

参数说明:

参数名 类型 是否必须 备注
code number 必须 响应码,1 代表成功,0 代表失败
msg string 非必须 提示信息
data object 非必须 返回的数据

响应数据样例:

{
    "code":1,
    "msg":"success",
    "data":null
}

2.6 文件上传

2.6.1 基本信息

请求路径:/upload

请求方式:POST

接口描述:上传图片接口

2.6.2 请求参数

参数格式:multipart/form-data

参数说明:

参数名称 参数类型 是否必须 示例 备注
image file

2.6.3 响应数据

参数格式:application/json

参数说明:

参数名 类型 是否必须 备注
code number 必须 响应码,1 代表成功,0 代表失败
msg string 非必须 提示信息
data object 非必须 返回的数据,上传图片的访问路径

响应数据样例:

{
    "code": 1,
    "msg": "success",
    "data": "https://web-framework.oss-cn-hangzhou.aliyuncs.com/2022-09-02-00-27-0400.jpg"
}

3. 其他接口

3.1 登录

3.1.1 基本信息

请求路径:/login

请求方式:POST

接口描述:该接口用于员工登录Tlias智能学习辅助系统,登录完毕后,系统下发JWT令牌。

3.1.2 请求参数

参数格式:application/json

参数说明:

名称 类型 是否必须 备注
username string 必须 用户名
password string 必须 密码

请求数据样例:

{
	"username": "jinyong",
    "password": "123456"
}

3.1.3 响应数据

参数格式:application/json

参数说明:

名称 类型 是否必须 默认值 备注 其他信息
code number 必须 响应码, 1 成功 ; 0 失败
msg string 非必须 提示信息
data string 必须 返回的数据 , jwt令牌

响应数据样例:

{
  "code": 1,
  "msg": "success",
  "data": "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoi6YeR5bq4IiwiaWQiOjEsInVzZXJuYW1lIjoiamlueW9uZyIsImV4cCI6MTY2MjIwNzA0OH0.KkUc_CXJZJ8Dd063eImx4H9Ojfrr6XMJ-yVzaWCVZCo"
}

3.1.4 备注说明

用户登录成功后,系统会自动下发JWT令牌,然后在后续的每次请求中,都需要在请求头header中携带到服务端,请求头的名称为 token ,值为 登录时下发的JWT令牌。

如果检测到用户未登录,则会返回如下固定错误信息:

{
	"code": 0,
	"msg": "NOT_LOGIN",
	"data": null
}