1 Star 0 Fork 0

fallingdust / SpringMVC项目:tlias-web-management

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
Apache-2.0

项目介绍

部署项目

  1. 应用程序路径必须设置为localhost:8080/上下文路径必须为空,因为nginx会将请求映射到localhost:8080/

  2. 运行前端环境nginx:项目下"other/nginx-1.22.0-tlias/nginx-1.22.0-tlias/nginx.exe"文件,浏览器访问:http://localhost:90,进入前端页面。

    nginx需要在一个没有中文的路径下;您应该将nginx压缩包解压到一个没有中文的路径下,而不是解压后再移动到一个没有中文的路径下,以确保nginx能正常启动!

项目特色

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

    项目实现了员工和部门管理系统的服务端功能开发,前端代码已经实现。前后端开发都严格遵循需求文档(见“other”目录)。

  • 项目是整合springboot、mybatis,使用Maven管理依赖和插件。

  • 项目后期将application.properties配置文件替换为了更具优势的application.yml文件,application.properties配置文件备份到了other目录中。

  • 项目采用了阿里云OSS技术,所有文件都存储在云端,云上传程序的配置项采用配置注入的方式。

  • 我们的登录功能并非徒有其表,还包含了登录校验的核心功能。

  • 我们还对项目进行了异常处理和事务管理。

  • 最后基于AOP实现了记录业务耗时的功能。

准备工作

  1. 准备数据库表(dept、emp)

    SQL语已在other目录备好

  2. 创建springboot工程,引入对应的起步依赖(web、mybatis、mysql驱动、lombok)

  3. 配置文件application.properties中引入mybatis的配置信息,准备对应的实体类

  4. 准备对应的Mapper、Service(接口、实现类)、Controllers基础结构

Restful开发规范

REST(REpresentational State Transfer),表述性状态转换,它是一种软件架构风格。

风格 示例
传统的请求参数 http://localhost:8080/user/getById?id=1
http://localhost:8080/user/saveUser
http://localhost:8080/user/updateUser
http://localhost:8080/user/deleteUser?id=1
REST风格 http://localhost:8080/users/1
http://localhost:8080/users
http://localhost:8080/users
http://localhost:8080/users/1

传统的参数传递方式好吗?我们每个人都有着不一样的命名习惯,这大大加大了后期的维护难度。

规范:URL定位资源,HTTP动词描述操作(即请求方式:GET、POST、PUT、DELETE)。

比如:

REST是风格,是约定方式,约定不是规定,可以打破。

上下文路径中描述模块的功能时通常使用复数,也就是加s的格式,表示此类资源,而非单个资源。如:users、emps、books..

文件的上传和存储

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

要实现文件上传功能,前端需要实现“file”属性的表单,并将提交方式设置为"post",还要指定编码方式为“multipart/form-data”。

“multipart/form-data”会将文件内容一并上传,而默认的“application/x-www-form-urlencoded”只能上传编码后的文件名信息

本地存储

本地存储存在缺陷,了解接口

/**
 * 演示文件从前端上传至服务端
 *
 * @param username 用户名
 * @param age      年龄
 * @param image    图片
 */
@PostMapping("/upload")
public Result upload(String username,Integer age,MultipartFile image)throws IOException{
        log.info("接收到文件上传:{},{},{}",username,age,image);
        /*
         * 在表单上传文件或文本到服务端时,通常会先将文件或文本保存在本地临时位置,然后通过HTTP请求发送到服务端。
         * 这个临时位置的文件或文本通常会在一定时间后自动删除,但具体时间取决于服务器的配置和操作系统的设置。
         * 因此,虽然文件或文本在上传之后可能会被立即删除,但这不是一定的,具体取决于服务器的配置和操作系统的设置。
         * */

        // 获取原始文件名
        String originalFilename=image.getOriginalFilename();

        /* public int lastIndexOf(String str)
         * 返回指定子字符串最后一次出现的索引。空字符串""的最后一次出现被认为出现在索引值this.length()处。
         * */
        String newFileName=UUID.randomUUID().toString()+originalFilename.substring(0,originalFilename.lastIndexOf("."));

        // 存储文件至本地
        image.transferTo(new File("D:\\BaiduSyncdisk\\myCode\\myJavaWeb\\tlias-web-management\\image\\"+newFileName));

        return Result.success();
        }

MultipartFile类的常用方法

方法名 说明
String getOriginalFilename(); 获取原始文件名
void transferTo(File dest); 将接收的文件转存到磁盘文件中
long getSjze(); 获取文件的大小,单位:字节
byte[] getBytes(); 获取文件内容的字节数组
InputStream getlnputStream(); 获取接收到的文件内容的输入流

在SpringBoot中,文件上传,默认单个文件允许最大大小为1M。如果需要上传大文件,可以进行如下配置:

#配置单个文件最大上传大小
spring.servlet.multipart.max-file-size=10MB
#配置单个请求最大上传大小(一次请求可以上传多个文件)
spring.servlet.multipart.max-request-size=100MB

云存储

如何使用云存储?

  1. 准备工作

  2. 参照官方SDK编写入门程序

    SDK:Software Development Kit 的缩写,软件开发工具包,包括辅助软件开发的依赖(jar包)、代码示例等,都可以叫做SDK。

  3. 集成使用

阿里云OSS

阿里云是阿里巴巴集团旗下全球领先的云计算公司,也是国内最大的云服务提供商。

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

使用阿里云OOS:

  1. 注册阿里云(实名认证)

    官网:阿里云-计算,为了无法计算的价值 (aliyun.com)

  2. 充值

  3. 开通对象存储服务(OSS)

  4. 创建bucket

    Bucket:存储空间是户用于存储对象(Object,就是文件)的容器,所有的对象都必须隶属于某个存储空间。

  5. 获取AccessKey(秘钥)

    AccessKey.csv

  6. 参照官方SDK编写入门程序

    在官网找到OOS的SDK文档 入门程序:

    package com.itheima.tliaswebmanagement;
    
    import com.aliyun.oss.ClientException;
    import com.aliyun.oss.OSS;
    import com.aliyun.oss.OSSClientBuilder;
    import com.aliyun.oss.OSSException;
    import com.aliyun.oss.common.auth.CredentialsProviderFactory;
    import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
    
    import java.io.ByteArrayInputStream;
    
    public class Demo {
    
        public static void main(String[] args) throws Exception {
            // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
            String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
            // 强烈建议不要把访问凭证保存到工程代码里,否则可能导致访问凭证泄露,威胁您账号下所有资源的安全。本代码示例以从环境变量中获取访问凭证为例。运行本代码示例之前,请先配置环境变量。
            EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
            String accessKeyId = "LTAI5tSTnrNJSy6fH7XvteWi";//这是我添加的
            String secretAccessKey = "CdbXZN8ZWVIRvd2WYP5qxy1r40yzX8";//这是我添加的
            // 填写Bucket名称,例如examplebucket。
            String bucketName = "web-tlias-falling-dust";
            // 填写Object完整路径,例如exampledir/exampleobject.txt。Object完整路径中不能包含Bucket名称。
            String objectName = "C:\\Users\\Lenovo\\OneDrive\\图片\\本机照片\\登峰造极.jpeg";
    
            // 创建OSSClient实例。
            OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);
            ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, secretAccessKey);//这是我修改的
    
            try {
                String content = "Hello OSS";
                ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(content.getBytes()));
            } 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();
                }
            }
        }
    }
  7. 案例集成OSS

    见代码,我们创建了工具类用于传输文件至阿里云OOS,又新建了一个Controller专门用于响应文件类型的参数。

文件配置

注入义阿里云配置

将阿里云的配置信息作为硬编码写在工程类中是不可取的,我们最好将其写在application.properties等springboot支持的配置文件中(springboot仅支持properties和yml配置文件,不支持xml配置文件):

这些都是我们自己定义的配置信息,key的名称没有固定规则,我们建议见名知义。

springboot已经为我们准备好了一个注解:@Value注解通常用于外部配置的属性注入,具体用法为:@Value("${配置文件中的key}"),我们需要为工具类的相应属性添加该注解。

# 自定义的阿里云OSS配置信息
aliyun.oss.endpoint=https://oss-cn-hangzhou.aliyuncs.com
aliyun.oss.accessKeyId=LTAI5tSTnrNJSy6fH7XvteWi
aliyun.oss.accessKeySecret=CdbXZN8ZWVIRvd2WYP5qxy1r40yzX8
aliyun.oss.bucketName=web-tlias-falling-dust
    @Value("${aliyun.oss.endpoint}")
    private String endpoint;
    @Value("${aliyun.oss.accessKeyId}")
    private String accessKeyId;
    @Value("${aliyun.oss.secretAccessKey}")
    private String secretAccessKey;
    @Value("${aliyun.oss.bucketName}")
    private String bucketName;

yml配置文件

springboot除了支持properties配置文件外,还支持yml或yaml配置文件。

image.png

基本语法

  • 大小写敏感
  • 数值前边必须有空格,作为分隔符
  • 使用缩进表示层级关系,缩进时,不允许使用Tab键,只能用空格(idea中会自动将Tab转换为空格)
  • 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
  • #表示注释,从这个字符一直到行尾,都会被解析器忽略

yml数据格式

对象/Map集合:

# 对象/Map集合:
user:
 name: zhangsan
 age: 18
 password: 123456

数组/List/Set集合:

# 数组/List/Set集合:
hobby:
  - java
  - c
  - game
  - sport

批量注入配置

我们最开始注入阿里云上传程序的配置信息时是通过@Value注解来实现的,另一种方式则是通过@ConfigurationProperties注解实现批量注入。

@Value注解只能一个一个的进行外部属性的注入。而@ConfigurationProperties可以批量的将外部的属性配置注入到bean对象的属性中。

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>

登录校验

HTTP是一种无状态协议,即服务器不保留与客户交易时的任何状态。

也就是说,上一次的请求对这次的请求没有任何影响,服务端也不会对客户端上一次的请求进行任何记录处理。

为了避免用户使用其他请求参数,直接跳过登录,我们必须实现登录校验功能。

我们接收其他请求参数时必须判断判断用户是否已经登录,因此用户登陆成功后生成一个登录标记。

在接收请求参数时,我们会统一拦截并进行登录校验。

会话技术

会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。

会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于==同一浏览器==,以便在同一次会话的多次请求间共享数据。

会话跟踪方案:

  • 客户端会话跟踪技术:Cookie

    Cookie技术是指服务器在客户端浏览网站的时候,将一段随机生成的包含信息的小块数据存储在客户端的计算机中,以便客户端在后续访问该网站时,可以快速通过Cookie技术从客户端的硬盘中读取数据的一种技术。 Cookie虽然被广泛的应用,并能做到一些使用其它技术不可能实现的功能,但也存在一些不够完美的方面,给应用带来不便。

  • 服务端会话跟踪技术:Session

  • 令牌技术

Cookie技术

CooKie:客户端会话技术,将数据保存到客户端,以后每次请求都携带Cookie数据进行访问。

对于Cookie的实现原理是基于HTTP协议的,其中涉及到到HTTP协议中的两个请求头信息:

  • 响应头:set-cookie
  • 请求头: cookie

image-20230726172543677

跨域:跨域区分三个维度:协议、IP/域名、端口

Session技术

image-20230726174226181

Session是服务器端技术,利用这个技术,服务器在运行时可以为每一个用户的浏览器创建一个其独享的HttpSession对象,由于session为用户浏览器独享,所以用户在访问服务器的web资源时,可以把各自的数据放在各自的session中,当用户再去访问服务器中的其它web资源时,其它web资源再从用户各自的session中取出数据为用户服务。

数据存储在服务端,服务端会为每一个客户端浏览器创建一个独享的session;Session也是一个域对象,域的范围是一个会话。

session特点:

  • session用于存储一次会话的多次请求的数据,存在服务器端

  • session可以存储任意类型,任意大小的数据(只要内存放得下)

Session和Cookie的主要区别在于:

  • Cookie是把用户的数据写给用户的浏览器。
  • Session技术把用户的数据写到用户独占的session中。
  • Session对象由服务器创建,开发人员可以调用request对象的getSession方法得到session对象。

令牌技术

令牌技术是一种用于授权和身份验证的安全机制。在计算机领域,令牌通常是一种由服务器发放给客户端的加密字符串或数字。客户端可以使用令牌来证明其身份,并获得对特定资源或服务的访问权限。

令牌技术在许多场景中被广泛应用,包括身份验证、单点登录、API访问控制等。当用户成功登录后,服务器会生成一个令牌,并将其返回给客户端。客户端在后续的请求中携带该令牌,以证明其身份。服务器会验证令牌的有效性,并根据权限配置决定是否授予访问权限。

令牌通常具有一定的有效期限制,并且可以通过刷新机制来延长其有效期。此外,令牌还可以包含一些附加信息,如用户角色、权限等,以便服务器在验证令牌时进行更精细的授权。

总而言之,令牌技术提供了一种安全、可扩展的身份验证和授权机制,可用于保护系统中的敏感资源和服务。

image-20230726175059857

JWT令牌

简介

全称:JSON Web Token(https://jwt.io/)

定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。

组成:

  • 第一部分:Header(头),记录令牌类型、签名算法等。例如:(“alg":"HS256","type":JWT”]
  • 第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。例如:{“id":"1","username":"Tom”}
  • 第三部分:Signature(签名),防止Token被簒改、确保安全性。将header、payload,并加入指定秘钥,通过指定签名算法计算而来。

image-20230726183650706

Header和Payload都是经过Base64编码的。

Base64:是一种基于64个可打印字符(A-Z a-z 0-9 + /)来表示二进制数据的编码方式。

应用场景

登录认证

  1. 登录成功后,生成令牌
  2. 后续每个请求,都要携带JWT令牌,系统在每次请求处理之前,先校验令牌,通过后,再处理
生成JWT令牌

依赖配置:

        <!--JWT令牌依赖-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

代码实现(测试类):

package com.itheima.tliaswebmanagement;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

//@SpringBootTest//单元测试类添加@SpringBootTest注解后,测试方法运行测试前会自动加载springboot环境
class TliasWebManagementApplicationTests {
    @Test
    void contextLoads() {
    }

    /**
     * 生成JWT
     */
    @Test
    public void testGenJwt() {
        Map<String, Object> claims = new HashMap<>();
        claims.put("name", "Tom");

        // Jwts.builder()是Java中的一种构造函数,用于创建一个JWT(JSON Web Token)的构建器对象。该构建器对象可以用于构建和签署JWT。
        String jwt = Jwts.builder()
                // 接着,signWith()方法用于指定JWT的签名算法和密钥。
                .signWith(SignatureAlgorithm.HS256, "itheima")
                // 用于在 JWT(JSON Web Token)构建器中设置声明(claims)。声明是 JWT 中包含的关于特定实体的信息,例如用户 ID、用户名、电子邮件地址等。
                .setClaims(claims)
                // 用于在 JWT(JSON Web Token)构建器中设置过期时间
                .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000))//有效期为1h
                // 使用 compact() 方法将所有设置组合成一个紧凑的字符串,该字符串可以发送到接收者。
                .compact();
        System.out.println(jwt);// eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiVG9tIiwiZXhwIjoxNjkwMzc1NjU4fQ.KNV_0vrYdiVRMflG7vZZaPpzZayxg-oCyPpbLjXHPcU
    }

    /**
     * 解析JWT令牌
     */
    @Test
    public void testParseJwt() {
        Claims claims = Jwts.parser()
                .setSigningKey("itheima")// 匹配生成JWT时的密钥
                // 注意!是parseClaimsJws而不是parseClaimsJwt!
                .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiVG9tIiwiZXhwIjoxNjkwMzc1NjU4fQ.KNV_0vrYdiVRMflG7vZZaPpzZayxg-oCyPpbLjXHPcU")
                // 获取我们自定义的信息
                .getBody();
        // 一旦令牌任意部分被篡改或者令牌过期,都会造成出现异常。
        System.out.println(claims);//{name=Tom, exp=1690375658}
    }
}

image-20230726193436796

image-20230726193359820

解析JWT并获取部分信息:image-20230726195336484

请求拦截:过滤器和拦截器

过滤器(Filter)

概述

概念:Filter过滤器,是JavaWeb三大组件(Servlet、Filter、Listener)之一。

过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。

过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等。

快速入门

  1. 定义Filter:定义一个类,实现Filter接口,并重写其所有方法。
  2. 配置Filter:Filter类上加@WebFilter注解,配置拦截资源的路径。引导类上加@ServletComponentScan开启Servlet组件支持。
package com.itheima.tliaswebmanagement;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@ServletComponentScan//开启了对Servlet组件的支持
@SpringBootApplication
public class TliasWebManagementApplication {

public static void main(String[] args) {
    SpringApplication.run(TliasWebManagementApplication.class, args);
    }
}
package com.itheima.tliaswebmanagement.filter;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;

import java.io.IOException;

/**
 * 过滤器
 */
@WebFilter("/*")// 过滤所有请求
public class DemoFilter implements Filter {
    /**
     * 初始化方法,只被调用一次
     *
     * @param filterConfig
     * @throws ServletException
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init: 初始化方法已执行");
    }

    /**
     * 拦截请求后被调用,会被调用多次
     *
     * @param servletRequest
     * @param servletResponse
     * @param filterChain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("拦截到了请求");
        // 放行
        filterChain.doFilter(servletRequest, servletResponse);
    }

    /**
     * 销毁方法,只被调用一次
     */
    @Override
    public void destroy() {
        System.out.println("destroy: 销毁方法执行了");
    }
}

细节

疑问:

  1. 放行后访问对应资源,资源访问完成后,还会回到Filter中吗?

  2. 如果回到Filter中,是重新执行还是执行放行后的逻辑呢?

    执行放行后的逻辑

  3. 所有的请求,拦截到了之后,都需要校验令牌吗?

    有一个例外,登录请求

  4. 拦截到请求后,什么情况下才可以放行,执行业务操作?

    有令牌,且令牌校验通过(合法);否则都返回未登录错误结果


Filter 可以根据需求,配置不同的拦截资源路径:

拦截路径 urlPatterns值 含义
拦截具体路径 /login 只有访问/login 路径时,才会被拦截
目录拦截 /emps/* 访问/emps下的所有资源,都会被拦截
拦截所有 /* 访问所有资源,都会被拦截
过滤器链

介绍:一个web应用中,可以配置多个过滤器,这多个过滤器就形成了一个过滤器链。

image-20230727094141899

顺序:注解配置的Filter,优先级是按照过滤器类名(字符串)的自然排序。

登录校验-实现

细节尽在代码中

流程:

graph LR
request([请求]) --> getUrl[获取请求路径] --> estimateRequest{判断是否是登录请求} --是--> doFilter([放行])
estimateRequest --否--> getToken[获取请求头Token] --> estimateToken{判断是否有Token} --是--> parseToken{解析Token} --成功--> doFilter
estimateToken --否--> respondError([返回用户未登录结果])
parseToken --失败--> respondError

拦截器(Interceptor)

概述

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

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

快速入门

  1. 定义拦截器,实现Handlerlnterceptor接口,并重写其所有方法。

    package com.itheima.tliaswebmanagement.interceptor;
    
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    import org.springframework.stereotype.Component;
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    @Component
    public class LoginCheckInterceptor implements HandlerInterceptor {
        /**
         * 目标资源方法执行前执行
         *
         * @param request  请求
         * @param response 响应
         * @param handler
         * @return true-放行,false-不放行
         * @throws Exception
         */
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("preHandle...");
            return true;
        }
    
        /**
         * 目标资源方法执行后执行
         *
         * @param request
         * @param response
         * @param handler
         * @param modelAndView
         * @throws Exception
         */
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            System.out.println("postHandle...");
            HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
        }
    
        /**
         * 视图渲染完毕后执行,最后执行
         *
         * @param request
         * @param response
         * @param handler
         * @param ex
         * @throws Exception
         */
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            System.out.println("afterCompletion...");
            HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
        }
    }
    
  2. 注册拦截器

    package com.itheima.tliaswebmanagement.config;
    
    import com.itheima.tliaswebmanagement.interceptor.LoginCheckInterceptor;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    @Configuration//在Spring Boot中,使用@Configuration注解可以标识一个类作为配置类,用于配置其他组件的属性和关系。
    public class WebConfig implements WebMvcConfigurer {
        @Autowired
        private LoginCheckInterceptor loginCheckInterceptor;
        /**
         * 注册拦截器
         *
         * @param registry
         */
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**")//需要两个小星星哦
                    .excludePathPatterns("/login");//不需要拦截login
        }
    }
    

细节

拦截路径 含义 举例
/* 一级路径 能匹配/depts,/emps,/login,不能匹配/depts/1
/** 任意级路径 能匹配/depts,/depts/1,/depts/1/2…
/depts/* /depts下的一级路径 能匹配/depts/1,不能匹配/depts/1/2
/depts/** /depts下的任意级路径 能匹配/depts,/depts/1,/depts/1/2…不能匹配/emps/1

Filter 与Interceptor

接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现Handlerlnterceptor接口。

拦截范围不同:过滤器Filter会拦截所有的资源,而lnterceptor只会拦截Spring环境中的资源。

如果Filter 与Interceptor同时存在:

image-20230727155925210

登录校验-实现

在入门程序的代码基础上添加了类似于过滤器的校验逻辑,详见代码。

异常处理

程序开发过程中不可避免的会遇到异常现象,此时服务器响应给前端的信息并不符合我们的开发规范。

出现异常,该如何处理?

  1. 方案一:在Controller的方法中进行try..catch处理

    ==代码臃肿,不推荐==

  2. 全局异常处理器

    package com.itheima.tliaswebmanagement.exception;
    
    import com.itheima.tliaswebmanagement.pojo.Result;
    import org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.bind.annotation.RestControllerAdvice;
    
    /**
     * 全局异常处理器
     */
    @RestControllerAdvice// @RestControllerAdvice = @ControllerAdvice + @ResponseBody 而@ResponseBody可以实现返回值自动转为json
    public class GlobleExceptionHandler {
        @ExceptionHandler(Exception.class)//指定异常类型
        public Result ex(Exception ex) {
            ex.printStackTrace();
            return Result.error("错误的操作!");// 前段会把msg信息渲染展示
        }
    }
    

事务管理

事务:回顾

事务是一组操作的集合,它是一个不可分割的工作单位,这些操作要么同时成功,要么同时失败。

操作

  • 开启事务(一组操作开始前,开启事务):start transaction/ begin;
  • 提交事务(这组操作全部成功后,提交事务):commit;
  • 回滚事务(中间任何一个操作出现异常,回滚事务):rollback;

发现问题

删除部门时,如果部门被删除而部门下的员工没有被删除,就会破坏数据的完整性和一致性,所以我们需要添加根据部门删除员工的功能,代码已经实现,详见代码。


但是,如果出现一下情况,阁下当如何应对?

image-20230727175921343

即使程序运行抛出了异常,部门正常删除了,但是部门下的员工却没有删除,还是造成了数据的不一致。

Spring事务管理

注解:@Transactional

位置:业务(service)层的方法上、类上、接口上

作用:将当前方法交给spring进行事务管理,方法执行前,开启事务;成功执行完毕,提交事务;出现异常,回滚事务

image-20230727181048983

日志开关

我们还可以开启springboot事务管理的日志开关:

#spring事务管理日志
logging:
  level:
    org.springframework.jdbc.support.JdbcTransactionManager: debug

回滚日志示例:

2023-07-27T19:02:43.059+08:00 DEBUG 17796 --- [io-8080-exec-10] o.s.jdbc.support.JdbcTransactionManager  : Initiating transaction rollback
2023-07-27T19:02:43.059+08:00 DEBUG 17796 --- [io-8080-exec-10] o.s.jdbc.support.JdbcTransactionManager  : Rolling back JDBC transaction on Connection [com.mysql.cj.jdbc.ConnectionImpl@5beb2b03]
2023-07-27T19:02:43.060+08:00 DEBUG 17796 --- [io-8080-exec-10] o.s.jdbc.support.JdbcTransactionManager  : Releasing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5beb2b03] after transaction

@Transactional进阶

rollbackFor属性

若是出现这种情况,阁下又当如何应对呢?

image-20230727184719724

int i = 1 / 0会触发ArithmeticException异常,它是RuntimeException家族的一员,而此处我们抛出的ExceptionRuntimeException的父类。

==默认情况下,只有出现RuntimeException才回滚异常。==

rollbackFor属性用于控制出现何种异常类型,回滚事务。

    @Override
    public Dept selectById(Integer id) {
        return deptMapper.selectById(id);
    }

    /**
     * 根据id删除部门
     *
     * <p>
     * &#064;Transactional是Spring框架中的注解,用于将事务管理应用于方法或类。
     * 它使得开发者可以通过声明性的方式管理事务,而不需要手动处理事务的开启、提交和回滚等细节。
     * <br>
     * rollbackFor 属性用于指定当发生指定类型的异常时,事务应该进行回滚操作。
     * <br>
     * 默认情况下,只有出现`RuntimeException`才回滚异常。
     * </p>
     *
     * @param id id号
     */
    @Transactional(rollbackFor = Exception.class)
    public void delete(Integer id) throws Exception {
        deptMapper.delete(id);
//        int i = 1 / 0;// 模拟出现异常
        if (true) {
            throw new Exception("出错啦~");
        }
        // 为了保证数据的完整性和一致性,必须连带删除部门下的员工
        empMapper.deleteByDeptId(id);
    }

propagation属性&新需求

事务传播行为

事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。

属性值 含义
REQUIRED 【默认值】需要事务,有则加入,无则创建新事务
REQUIRES_NEW 需要新事务,无论有无,总是创建新事务
SUPPORTS 支持事务,有则加入,无则在无事务状态中运行
NOT_SUPPORTED 不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务
MANDATORY 必须有事务,否则抛异常
NEVER 必须没事务,否则抛异常

解散部门时,记录操作日志

需求:解散部门时,无论是成功还是失败,都要记录操作日志。

步骤:

  1. 解散部门:删除部门、删除部门下的员工
  2. 记录日志到数据库表中

image-20230727200559685

AOP

概述

AOP:Aspect Oriented Programming(面向切面编程、面向方面编程),其实就是面向特定方法编程。

场景:案例部分功能运行较慢,定位执行耗时较长的业务方法,此时需要统计每一个业务方法的执行耗时,而我们决不能采用依次修改各个业务方法的低级手段来统计时间。

实现:==动态代理==是面向切面编程最主流的实现。而SpringAOP是Spring框架的高级技术,旨在管理bean对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程。

Spring AOP入门

统计各个业务层方法执行耗时

  1. 导入依赖:在pom.xml中导入AOP的依赖

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
  2. 编写AOP程序:针对于特定方法根据业务需要进行编程

    package com.itheima.aop;
    
    import lombok.extern.slf4j.Slf4j;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.stereotype.Component;
    
    import javax.swing.*;
    
    @Slf4j
    @Aspect// 标示为AOP类
    @Component// 交给IOC容器管理
    public class TimeAspect {
        /**
         * <h3 style="color: #FFA500">
         * &#064;Around("execution(*  com.itheima.service.*.*(..))")
         * </h3>
         * <p style="color: #FFA500">
         * 这段代码是Spring框架中的注解,用于定义一个拦截器(Interceptor),用于拦截指定方法。
         * <br>
         * 具体来说,这个注解的含义是:
         * <li>&#064;Around:表示这个注解用来定义一个环绕通知(Around  advice)。</li>
         * <div>
         * ("execution(* com.itheima.service.*.*(..))"):表示要拦截的方法的表达式,其中:
         * <li>*(第一个):任意返回值</li>
         * <li style="">execution:表示这个表达式使用Java反射机制中的方法名称和参数类型来匹配方法。</li>
         * <li style="">com.itheima.service:表示要拦截的方法所在的包或类名。</li>
         * <li style="">.*:分别表示任意类或接口、任意方法名。</li>
         * <li style="">(..):表示任意参数列表。</li>
         * </div>
         * 因此,这段代码的含义是:拦截所有位于com.itheima.service包下的任意方法。
         * </p>
         *
         * @param joinPoint
         * @return
         * @throws Throwable
         */
        @Around("execution(* com.itheima.service.*.*(..))")// 切口表达式
        public Object reportTime(ProceedingJoinPoint joinPoint) throws Throwable {
            // 记录开始时间
            long begin = System.currentTimeMillis();
    
            // 调用原始方法运行
            Object result = joinPoint.proceed();// 返回值: 原始方法的返回值
    
            // 记录结束时间
            long end = System.currentTimeMillis();
            log.info(joinPoint.getSignature() + "方法执行耗时: {}ms", end - begin);
            return result;
        }
    }
    

场景

  • 记录操作日志
  • 事务管理
  • 权限控制

优势

  • 代码无侵入
  • 减少重复代码
  • 方便维护
  • 提高开发效率

核心概念

  • 连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)
  • 通知:Advice,指那些重复的逻辑,也就是共性功能(最终体现为一个方法)
  • 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
  • 切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
  • 目标对象:Target,通知所应用的对象

通知类型

  • @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
  • @Before:前置通知,此注解标注的通知方法在目标方法前被执行
  • @After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
  • @AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
  • @AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行

注意:

  • @Around环绕通知需要自己调用 ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行
  • @Around环绕通知方法的返回值,必须指定为object,来接收原始方法的返回值。

通知顺序

当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行。

执行顺序

不同切面类中,默认按照切面类的类名字母排序:

  • 目标方法前的通知方法:字母排名靠前的先执行
  • 目标方法后的通知方法:字母排名靠前的后执际

==用@Order(数字)加在切面类上来控制顺序==:

  • 目标方法前的通知方法:数字小的先执行
  • 目标方法后的通知方法:数字小的后执行

切入点表达式

切入点表达式:描述切入点方法的一种表达式

作用:主要用来决定项目中的哪些方法需要加入通知

常见形式:

  1. execution(……):根据方法的签名来匹配
  2. @annotation(……):根据注解匹配

切入点表达式-execution

execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:

execution(访问修饰符 返回值 包名.类名.方法名(方法参数) throws 异常?)

其中可以省略的部分:

  • 访问修饰符:可省略(比如:public、protected)
  • 包名.类名:可省略
  • throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)

可以使用通配符描述切入点:

  • *

    单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分

  • ..

    多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数

==两个切入点表达式可以用||连接==。

书写建议

  • 所有业务方法名在命名时尽量规范,方便切入点表达式快速匹配。

    如:查询类方法都是find 开头,更新类方法都是update开头。

  • 描述切入点方法通常基于接口描述,而不是直接描述实现类,增强拓展性。

  • 在满足业务需要的前提下,尽量缩小切入点的匹配范围。如:包名匹配尽量不使用.,使用*匹配单个包。

切入点表达式-@annotation

@annotation 切入点表达式,用于匹配标识有特定注解的方法。

@annotation(自定义注解全类名)

@Before("@annotation(com.itheima.anno.Log)")
public void before(){
	log.info("before .…..");
}

这里com.itheima.anno.Log是我们自定义的注解,添加了这个注解的方法就会被匹配为切入点。

package anno;

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 Log {

}

连接点

在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。

  • 对于@Around通知,获取连接点信息只能使用 ProceedingJoinPoint
  • 对于其他四种通知,获取连接点信息只能使用JoinPoint,它是ProceedingJoinPoint的父类型

应用

将案例中增、删、改相关接口的操作日志记录到数据库表中。操作日志日志信息包含:操作人、操作时间、执行方法的全类名、执行方法名、方法运行时参数、返回值、方法执行时长。

详见代码。

Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

简介

SpringMVC项目 展开 收起
JavaScript 等 4 种语言
Apache-2.0
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
1
https://gitee.com/a2176281647/tlias-web-management-springmvc.git
git@gitee.com:a2176281647/tlias-web-management-springmvc.git
a2176281647
tlias-web-management-springmvc
SpringMVC项目:tlias-web-management
master

搜索帮助