针对SpringWeb中需要注意的细节

news/2024/10/24 9:21:05/文章来源:https://www.cnblogs.com/dwj-2019/p/18498831

一、SpringBootWeb

1、需求和环境搭建

文件命名规范:
Controller:控制层,存放控制器Controller
mapper:持久层,数据访问层,存放mybatis的Mapper接口
Service:业务层,处理逻辑性问题的业务代码
pojo/domain:业务层、存放业务代码   
步骤:
1. 创建一个新的数据库(tlias)准备数据库表(dept、emp)   创建需求的员工表、关系表等。
2. 创建springboot工程,引入对应的起步依赖(web、mybatis、mysql驱动、lombok)

2、生成pom.xml项目配置文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">   <modelVersion>4.0.0</modelVersion>   <parent>       <groupId>org.springframework.boot</groupId>       <artifactId>spring-boot-starter-parent</artifactId>       <version>2.7.5</version>       <relativePath/>    </parent>   <groupId>com.itheima</groupId>   <artifactId>tlias-web-management</artifactId>   <version>0.0.1-SNAPSHOT</version>   <name>tlias-web-management</name>   <description>Demo project for Spring Boot</description>   <properties>       <java.version>11</java.version>   </properties>   <dependencies>       <dependency>           <groupId>org.springframework.boot</groupId>           <artifactId>spring-boot-starter-web</artifactId>       </dependency>       <dependency>           <groupId>org.mybatis.spring.boot</groupId>           <artifactId>mybatis-spring-boot-starter</artifactId>           <version>2.3.0</version>       </dependency>
​       <dependency>           <groupId>com.mysql</groupId>           <artifactId>mysql-connector-j</artifactId>           <scope>runtime</scope>       </dependency>       <dependency>           <groupId>org.projectlombok</groupId>           <artifactId>lombok</artifactId>           <optional>true</optional>       </dependency>       <dependency>           <groupId>org.springframework.boot</groupId>           <artifactId>spring-boot-starter-test</artifactId>           <scope>test</scope>       </dependency>   </dependencies>
​   <build>       <plugins>           <plugin>               <groupId>org.springframework.boot</groupId>               <artifactId>spring-boot-maven-plugin</artifactId>               <configuration>                   <excludes>                       <exclude>                           <groupId>org.projectlombok</groupId>                           <artifactId>lombok</artifactId>                       </exclude>                   </excludes>               </configuration>           </plugin>       </plugins>   </build>
​
</project>

3、配置myBatis核心文件:

3. 配置文件application.properties中引入mybatis的配置信息,准备对应的实体类#数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/tlias
spring.datasource.username=root
spring.datasource.password=1234
​
#开启mybatis的日志输出
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
​
#开启数据库表字段 到 实体类属性的驼峰映射
mybatis.configuration.map-underscore-to-camel-case=true   
​

4、准备Mapper、Service(接口、实现类)、Controller基础结构

实体类   /*部门类的命名需要与数据库表中的数据一一对应*/
@Data//Lomback插件在实体类生成字节码之前生成对应的get/set的方法toString等方法
@NoArgsConstructor
@AllArgsConstructor
public class Dept {   private Integer id;   private String name;   private LocalDateTime createTime;   private LocalDateTime updateTime;
}
Mapper层
数据访问层  DeptMapper   
package com.itheima.mapper;
import org.apache.ibatis.annotations.Mapper;
​
@Mapper
public interface DeptMapper {
}
​  EmpMapper
package com.itheima.mapper;
import org.apache.ibatis.annotations.Mapper;
​
@Mapper
public interface EmpMapper {
}
Service层
业务层   DeptService//业务层的实现接口
package com.itheima.service;
​
//部门业务规则
public interface DeptService {
} 
​   DeptServiceImpl//业务层接口的实体类       
package com.itheima.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
​
//部门业务实现类
@Slf4j
@Service
public class DeptServiceImpl implements DeptService {
}   
Controller层
控制层
package com.itheima.controller;
import org.springframework.web.bind.annotation.RestController;
​
//部门管理控制器
//Controller
@RestController
public class DeptController {
}   

5、RESTFUL风格

- REST(Representational State Transfer),表述性状态转换,它是一种软件架构风格。
​
请求接口的三要素:请求路径、请求参数、请求响应   **传统URL风格如下:**
http://localhost:8080/user/getById?id=1     GET:查询id为1的用户
http://localhost:8080/user/saveUser         POST:新增用户
http://localhost:8080/user/updateUser       POST:修改用户
http://localhost:8080/user/deleteUser?id=1  GET:删除id为1的用户
​
​
基于REST风格URL如下:
http://localhost:8080/users/1  GET:查询id为1的用户
http://localhost:8080/users    POST:新增用户
http://localhost:8080/users    PUT:修改用户
http://localhost:8080/users/1  DELETE:删除id为1的用户
​
​
通过URL定位要操作的资源,通过HTTP动词(请求方式)来描述具体的操作。     在REST风格的URL中,通过四种请求方式,来操作数据的增删改查。 
- GET : 查询
- POST :新增
- PUT :修改
- DELETE :删除   - REST是风格,是约定方式,约定不是规定,可以打破
- 描述模块的功能通常使用复数,也就是加s的格式来描述,表示此类资源,而非单个资源。如:users、emps、books…

6、Result响应规范

开发规范-统一响应结果**
前后端工程在进行交互时,使用统一响应结果 Result。   引入Result实体类对数据进行统一包装。 形如:
package com.itheima.pojo;
​
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
​
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {   private Integer code;//响应码,1 代表成功; 0 代表失败   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);   }
}

7、开发流程

1. 查看页面原型明确需求  - 根据页面原型和需求,进行表结构设计、编写接口文档
​
2. 阅读接口文档
3. 思路分析
4. 功能接口开发  - 就是开发后台的业务功能,一个业务功能,我们称为一个接口
5. 功能接口测试  - 功能开发完毕后,先通过Postman进行功能接口测试,测试通过后,再和前端进行联调测试
6. 前后端联调测试  - 和前端开发人员开发好的前端工程一起测试

二、SpringBootWeb细节

1、Controller层

在controller中接收请求路径中的路径参数  @PathVariable 
如何限定请求方式是POST?  @PostMapping
在controller中接收json格式的请求参数  @RequestBody  //把前端传递的json数据填充到实体类中  在Spring当中为了简化请求路径的定义,可以把公共的请求路径,直接抽取到类上,在类上加一个注解@RequestMapping,并指定请求路径。  注意事项:一个完整的请求路径,应该是类上@RequestMapping的value属性 + 方法上的 @RequestMapping的value属性 
@RequestParam(defaultValue="默认值") //用于从请求参数中获取值并赋给方法参. 常用属性包括:
value:表示要绑定的请求参数名字。默认值为方法参数名,与请求参数名字一致。
required:表示该参数是否是必需的。默认为true,如果请求中没有传递该参数,则会抛出异常。如果设置为false,即可使该参数变为可选。
defaultValue:表示当请求中没有传递该参数时,使用的默认值。数。/****************************************************************************************/      
@Slf4j //自动添加Logger对象,对象名为log
@RestController //@RestController注解是Spring4之后引入的,它的功能是通过@ResponseBody注解自动应用于所有的请求处理方法。
public class DeptController {   @Autowired //自动装配依赖关系。如果存在多个符合类型的对象,Spring会抛出异常。为了避免这种情况,可以结合使用@Autowired注解和@Qualifier注解,通过指定bean的名称来明确指定要注入哪个bean。      private DeptService deptService;
​   //@RequestMapping(value = "/depts" , method = RequestMethod.GET)   //@RequestMapping是一个通用的注解,它可以用于处理任何类型的HTTP请求(GET、POST、PUT、DELETE等)。同时,它也可以用于类级别的注解,用来定义类中所有处理请求的方法的基本请求路径。      @GetMapping("/depts")//@GetMapping是@RequestMapping的特定变体,它只处理HTTP GET请求。它可以用于方法级别的注解,用来处理特定路径的GET请求。   public Result list(){       log.info("查询所有部门数据");       List<Dept> deptList = deptService.list();       return Result.success(deptList);   }
}

2、Service业务层

//定义接口的目的为了实现解耦,在业务逻辑发生变化或者需求变更时,只需要修改实现类而不需要修改调用方的代码。
public interface DeptService {   /**    * 查询所有的部门数据    * @return   存储Dept对象的集合    */   List<Dept> list();
}
​
@Slf4j
@Service
public class DeptServiceImpl implements DeptService {   @Autowired   private DeptMapper deptMapper;      @Override   public List<Dept> list() {       List<Dept> deptList = deptMapper.list();       return deptList;   }
}

3、Mapper层

@Mapper//@Mapper注解是MyBatis框架中的注解。框架会根据接口的定义自动生成Mapper接口的实现类,并执行接口中定义的SQL语句。
public interface DeptMapper {   //查询所有部门数据   @Select("select id, name, create_time, update_time from dept")   List<Dept> list();
}
​
​

4、PageHelper分页插件

    PageHelper是Mybatis的一款功能强大、方便易用的分页插件,支持任何形式的单标、多表的分页查询。
官网使用地址​​​​​​
在pom.xml引入依赖
<dependency>   <groupId>com.github.pagehelper</groupId>   <artifactId>pagehelper-spring-boot-starter</artifactId>   <version>1.4.2</version>
</dependency>      
@Mapper
public interface EmpMapper {   //获取当前页的结果列表   @Select("select * from emp")   public List<Emp> page();
}
//******************************************
@Override
public PageBean page(Integer page, Integer pageSize) {   // 设置分页参数   PageHelper.startPage(page, pageSize);   // 执行分页查询   List<Emp> empList = empMapper.page();    // 获取分页结果   Page<Emp> p = (Page<Emp>) empList;      //封装PageBean   PageBean pageBean = new PageBean(p.getTotal(), p.getResult());    return pageBean;
}
注意:只有紧跟着PageHelper.startPage()的sql语句才被pagehelper起作用

5、文件上传

   文件上传,是指将本地图片、视频、音频等文件上传到服务器,供其他用户浏览或下载的过程。  想要完成文件上传这个功能需要涉及到两个部分:      1. 前端程序   2. 服务端程序      前端实现代码:
<form action="/upload" method="post" enctype="multipart/form-data">姓名: <input type="text" name="username"><br>   年龄: <input type="text" name="age"><br>   头像: <input type="file" name="image"><br>   <input type="submit" value="提交">
</form>
表单必须有file域,用于选择要上传的文件       <input type="file" name="image"/>
表单提交方式必须为POST
> 通常上传的文件会比较大,所以需要使用 POST 提交方式
>表单的编码类型enctype必须要设置为:multipart/form-data,普通默认的编码格式是不适合传输大型的二进制数据的,所以在文件上传时,表单的编码格式必须设置为multipart/form-data。后端程序实现:    - 首先在服务端定义这么一个controller,用来进行文件上传,然后在controller当中定义一个方法来处理`/upload` 请求
​
- 在定义的方法中接收提交过来的数据 (方法中的形参名和请求参数的名字保持一致)  如果表单项的名字和方法中形参名不一致,该怎么办?   答:使用@RequestParam注解进行参数绑定。     - 用户名:String  name - 年龄: Integer  age - 文件: MultipartFile  image
public Result upload(String username,                    Integer age,                     @RequestParam("image") MultipartFile file)        > Spring中提供了一个API:MultipartFile,使用这个API就可以来接收到上传的文件   

6、本地存储

    文件上传功能前端和后端的基础代码实现,文件上传时在服务端会产生一个临时文件,请求响应完成之后,这个临时文件被自动删除,并没有进行保存。       
1. 在服务器本地磁盘上创建images目录,用来存储上传的文件(例:E盘创建images目录)
2. 使用MultipartFile类提供的API方法,把临时文件转存到本地磁盘目录下
​
> MultipartFile 常见方法: 
> - String  getOriginalFilename();  //获取原始文件名
> - void  transferTo(File dest);     //将接收的文件转存到磁盘文件中
> - long  getSize();     //获取文件的大小,单位:字节
> - byte[]  getBytes();    //获取文件内容的字节数组
> - InputStream  getInputStream();    //获取接收到的文件内容的输入流
​   文件上传是没有问题的。但是由于我们是使用原始文件名作为所上传文件的存储名字,当我们再次上传一个名为1.jpg文件时,发现会把之前已经上传成功的文件覆盖掉。    解决方案:保证每次上传文件时文件名都唯一的(使用UUID获取随机文件名)      
@Slf4j
@RestController
public class UploadController {
​   @PostMapping("/upload")   public Result upload(String username, Integer age, MultipartFile image) throws IOException {       log.info("文件上传:{},{},{}",username,age,image);
​       //获取原始文件名       String originalFilename = image.getOriginalFilename();
​       //构建新的文件名       String extname = originalFilename.substring(originalFilename.lastIndexOf("."));//文件扩展名       String newFileName = UUID.randomUUID().toString()+extname;//随机名+文件扩展名
​       //将文件存储在服务器的磁盘目录       image.transferTo(new File("E:/images/"+newFileName));
​       return Result.success();   }
​
}
​
需要上传大文件,可以在application.properties进行如下配置:   
//配置单个文件最大上传大小
spring.servlet.multipart.max-file-size=10MB
​
//配置单个请求最大上传大小(一次请求可以上传多个文件)
spring.servlet.multipart.max-request-size=100MB   
直接存储在服务器的磁盘目录中,存在以下缺点:
​
- 不安全:磁盘如果损坏,所有的文件就会丢失
- 容量有限:如果存储大量的图片,磁盘空间有限(磁盘不可能无限制扩容)
- 无法直接访问
​
通常有两种解决方案:
​
- 自己搭建存储服务器,如:fastDFS 、MinIO
- 使用现成的云服务,如:阿里云,腾讯云,华为云

7、OSS存储

云服务指的就是通过互联网对外提供的各种各样的服务。   SDK:Software Development Kit 的缩写,软件开发工具包,包括辅助软件开发的依赖(jar包)、代码示例等,都可以叫做SDK。
​
简单说,sdk中包含了我们使用第三方云服务时所需要的依赖,以及一些示例代码。我们可以参照sdk所提供的示例代码就可以完成入门程序。   Bucket:存储空间是用户用于存储对象(Object,就是文件)的容器,所有的对象都必须隶属于某个存储空间。      下面我们根据之前介绍的使用步骤,完成准备工作:    1.通过控制台找到对象存储OSS服务    2.开通OSS服务之后,就可以进入到阿里云对象存储的控制台    3.点击 "Bucket列表",创建一个Bucket    4.参照官方提供的SDK,改造一下,即可实现文件上传功能
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 AliOssTest {   public static void main(String[] args) throws Exception {       // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。       String endpoint = "oss-cn-shanghai.aliyuncs.com";              // AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。       String accessKeyId = "LTAI5t9MZK8iq5T2Av5GLDxX";       String accessKeySecret = "C0IrHzKZGKqU8S7YQcevcotD3Zd5Tc";              // 填写Bucket名称,例如examplebucket。       String bucketName = "web-framework01";       // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。       String objectName = "1.jpg";       // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。       // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。       String filePath= "C:\\Users\\Administrator\\Pictures\\1.jpg";
​       // 创建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();           }       }   }
}
在以上代码中,需要替换的内容为:
- accessKeyId:  AccessKey
- accessKeySecret: AccessKey对应的秘钥
- bucketName:Bucket名称
- objectName:对象名称,在Bucket中存储的对象的名称
- filePath:文件路径

三、参数配置文件

1、参数配置化

​  AliOSSUtils工具类,将文件上传到OSS对象存储服务当中。而在调用工具类进行文件上传时,需要一些参数:
​
- endpoint       //OSS域名
- accessKeyID    //用户身份ID
- accessKeySecret   //用户密钥
- bucketName      //存储空间的名字
​
AliOSSUtils工具类,将文件上传到OSS对象存储服务当中。而在调用工具类进行文件上传时,需要一些参数:
​
- endpoint       //OSS域名
- accessKeyID    //用户身份ID
- accessKeySecret   //用户密钥
- bucketName      //存储空间的名字
​
将参数配置在配置pom.xml文件中。如下:
#自定义OSS配置信息
aliyun.oss.endpoint=https://oss-cn-hangzhou.aliyuncs.com
aliyun.oss.accessKeyId=LTAI4GCH1vX6DKqJWxd6nEuW
aliyun.oss.accessKeySecret=yBshYweHOpqDuhCArrVHwIiBKpyqSL
aliyun.oss.bucketName=web-tlias在将OSS配置参数交给properties配置文件来管理之后,我们的AliOSSUtils工具类就变为以下形式:
@Component
public class AliOSSUtils {   /*以下4个参数没有指定值(默认值:null)*/   private String endpoint;   private String accessKeyId;   private String accessKeySecret;   private String bucketName;
​   //省略其他代码...
}   application.properties是springboot项目默认的配置文件,所以springboot程序在启动时会默认读取application.properties配置文件,而我们可以使用一个现成的注解:@Value,获取配置文件中的数据。   @Value 注解通常用于外部配置的属性注入,具体用法为: @Value("${配置文件中的key}")
@Component
public class AliOSSUtils {
​   @Value("${aliyun.oss.endpoint}")   private String endpoint;      @Value("${aliyun.oss.accessKeyId}")   private String accessKeyId;      @Value("${aliyun.oss.accessKeySecret}")   private String accessKeySecret;      @Value("${aliyun.oss.bucketName}")   private String bucketName;//省略其他代码...}   

2、yml配置文件

# application.properties
​
server.port=8080
server.address=127.0.0.1
# application.yml 
server:port: 8080address: 127.0.0.1
# application.yaml 
​
server:port: 8080address: 127.0.0.1
yml 格式的配置文件,后缀名有两种:
​
- yml (推荐)
- yaml
​
spring: datasource:   driver-class-name: com.mysql.cj.jdbc.Driver   url: jdbc:mysql://localhost:3306/tlias   username: root   password: 1234       
yml格式的数据有以下特点:
- 容易阅读
- 容易与脚本语言交互
- 以数据为核心,重数据轻格式          
yml配置文件的基本语法:
- 大小写敏感
- 数值前边必须有空格,作为分隔符
- 使用缩进表示层级关系,缩进时,不允许使用Tab键,只能用空格(idea中会自动将Tab转换为空格)
- 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
- #表示注释,从这个字符一直到行尾,都会被解析器忽略
​       
yml文件中常见的数据格式   1. 定义对象或Map集合    2. 定义数组、list或set集合
对象/Map集合
user: name: zhangsan age: 18 password: 123456
数组/List/Set集合
hobby:  - java - game - sport

3、@ConfigurationProperties

Spring中给我们提供了一种简化方式@ConfigurationProperties 可以简化这些配置参数的注入      1. 需要创建一个实现类,且实体类中的属性名和配置文件当中key的名字必须要一致
​  > 比如:配置文件当中叫endpoints,实体类当中的属性也得叫endpoints,另外实体类当中的属性还需要提供 getter / setter方法
​
2. 需要将实体类交给Spring的IOC容器管理,成为IOC容器当中的bean对象
​
3. 在实体类上添加`@ConfigurationProperties`注解,并通过perfect属性来指定配置参数项的前缀   
需要引入一个起始依赖,这项依赖它的作用就是会自动的识别被@Configuration Properties注解标识的bean对象。
<dependency>   <groupId>org.springframework.boot</groupId>   <artifactId>spring-boot-configuration-processor</artifactId>
</dependency>   
实体类:AliOSSProperties
/*OSS相关配置*/
@Data
@Component
@ConfigurationProperties(prefix = "aliyun.oss")
public class AliOSSProperties {   //区域   private String endpoint;   //身份ID   private String accessKeyId ;   //身份密钥   private String accessKeySecret ;   //存储空间   private String bucketName;
}
​
@ConfigurationProperties注解我们已经介绍完了,接下来我们就来区分一下@ConfigurationProperties注解以及我们前面所介绍的另外一个@Value注解:   
相同点:都是用来注入外部配置的属性的。
​
不同点:
​
- @Value注解只能一个一个的进行外部属性的注入。
​
- @ConfigurationProperties可以批量的将外部的属性配置注入到bean对象的属性中。

四、会话技术统一拦截技术

    HTTP协议是无状态协议。所谓无状态,指的是每一次请求都是独立的,下一次请求并不会携带上一次请求的数据。
​   会话指的就是浏览器与服务器之间的一次连接,我们就称为一次会话。这个会话就建立了,直到有任何一方断开连接,此时会话就结束了。在一次会话当中,是可以包含多次请求和响应的。这个会话就建立了,直到有任何一方断开连接,此时会话就结束了。在一次会话当中,是可以包含多次请求和响应的。
​    会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据。 
会话跟踪技术有三种:
1. Cookie(客户端会话跟踪技术)  - 数据存储在客户端浏览器当中
2. Session(服务端会话跟踪技术)  - 数据存储在储在服务端
3. 令牌技术
​       
统一拦截技术现实方案有两种:
1. Servlet规范中的Filter过滤器
2. Spring提供的interceptor拦截器

1、会话跟踪方案 Cookie

    cookie 是客户端会话跟踪技术,它是存储在客户端浏览器中的。在浏览器第一次发起请求来请求服务器的时候,我们在服务器端来设置一个cookie。在 cookie 当中我们就可以来存储用户相关的一些数据信息。    服务器端在给客户端在响应数据的时候,会**自动**的将 cookie 响应给浏览器,浏览器接收到响应回来的 cookie 之后,会**自动**的将 cookie 的值存储在浏览器本地。接下来在后续的每一次请求当中,都会将浏览器本地所存储的 cookie **自动**地携带到服务端。     在服务端我们就可以获取到 cookie 的值。我们可以去判断一下这个 cookie 的值是否存在,如果不存在这个cookie,就说明客户端之前是没有访问登录接口的;如果存在 cookie 的值,就说明客户端之前已经登录完成了。这样我们就可以基于 cookie 在同一次会话的不同请求之间来共享数据。
//************************************************************* 
3 个自动:
- 服务器会 自动 的将 cookie 响应给浏览器。
- 浏览器接收到响应回来的数据之后,会 自动 的将 cookie 存储在浏览器本地。
- 在后续的请求当中,浏览器会 自动 的将 cookie 携带到服务器端。
在 HTTP 协议官方给我们提供了一个响应头和请求头:
- 响应头 Set-Cookie :设置Cookie数据的
- 请求头 Cookie:携带Cookie数据的
//*****************************************************************               //代码测试       
@Slf4j
@RestController
public class CookieController {
​   //设置Cookie   @GetMapping("/c1")   public Result cookie1(HttpServletResponse response){
​       response.addCookie(new Cookie("login_username","itheima")); //设置Cookie/响应Cookie       return Result.success();   }   //获取Cookie   @GetMapping("/c2")   public Result cookie2(HttpServletRequest request){       Cookie[] cookies = request.getCookies();       for (Cookie cookie : cookies) {           if(cookie.getName().equals("login_username")){               System.out.println("login_username: "+cookie.getValue()); //输出name为login_username的cookie           }       }       return Result.success();   }
} 
​
**优缺点**
​
- 优点:HTTP协议中支持的技术(像Set-Cookie 响应头的解析以及 Cookie 请求头数据的携带,都是浏览器自动进行的,是无需我们手动操作的)
- 缺点: - 移动端APP(Android、IOS)中无法使用Cookie - 不安全,用户可以自己禁用Cookie - Cookie不能跨域   
区分跨域的维度:
- 协议
- IP/协议
- 端口
只要上述的三个维度有任何一个维度不同,那就是跨域操作

2、会话跟踪方案 Session

    Session 的底层其实就是基于我们刚才所介绍的 Cookie 来实现的。   基于 Session 来进行会话跟踪,浏览器在第一次请求服务器的时候,我们就可以直接在服务器当中来获取到会话对象Session。如果是第一次请求Session ,会话对象是不存在的,这个时候服务器会自动的创建一个会话对象Session 。而每一个会话对象Session ,它都有一个ID(示意图中Session后面括号中的1,就表示ID),我们称之为 Session 的ID。    服务器端在给浏览器响应数据的时候,它会将 Session 的 ID 通过 Cookie 响应给浏览器。其实在响应头当中增加了一个 Set-Cookie 响应头。这个  Set-Cookie  响应头对应的值是不是cookie? cookie 的名字是固定的 JSESSIONID 代表的服务器端会话对象 Session 的 ID。浏览器会自动识别这个响应头,然后自动将Cookie存储在浏览器本地。    在后续的每一次请求当中,都会将 Cookie 的数据获取出来,并且携带到服务端。接下来服务器拿到JSESSIONID这个 Cookie 的值,也就是 Session 的ID。拿到 ID 之后,就会从众多的 Session 当中来找到当前请求对应的会话对象Session。                   //*********代码测试*******************:
@Slf4j
@RestController
public class SessionController {
​   @GetMapping("/s1")   public Result session1(HttpSession session){       log.info("HttpSession-s1: {}", session.hashCode());
​       session.setAttribute("loginUser", "tom"); //往session中存储数据       return Result.success();   }
​   @GetMapping("/s2")   public Result session2(HttpServletRequest request){       HttpSession session = request.getSession();       log.info("HttpSession-s2: {}", session.hashCode());
​       Object loginUser = session.getAttribute("loginUser"); //从session中获取数据       log.info("loginUser: {}", loginUser);       return Result.success(loginUser);   }
} 
​
**优缺点**
​
- 优点:Session是存储在服务端的,安全
- 缺点: - 服务器集群环境下无法直接使用Session - 移动端APP(Android、IOS)中无法使用Cookie - 用户可以自己禁用Cookie - Cookie不能跨域
​
> PS:Session 底层是基于Cookie实现的会话跟踪,如果Cookie不可用,则该方案,也就失效了。      服务器集群环境为何无法使用Session?   首先第一点,我们现在所开发的项目,一般都不会只部署在一台服务器上,因为一台服务器会存在一个很大的问题,就是单点故障。所谓单点故障,指的就是一旦这台服务器挂了,整个应用都没法访问了。

3、会话跟踪令牌

    令牌就是用户身份的标识,其本质就是一个字符串。令牌的形式有很多,我们使用的是功能强大的 JWT令牌。  原理:    在浏览器发起请求。在请求登录接口的时候,如果登录成功,我就可以生成一个令牌,令牌就是用户的合法身份凭证。接下来我在响应数据的时候,我就可以直接将令牌响应给前端。    在前端程序当中接收到令牌之后,就需要将这个令牌存储起来。这个存储可以存储在 cookie 当中,也可以存储在其他的存储空间(比如:localStorage)当中。    接下来,在后续的每一次请求当中,都需要将令牌携带到服务端。携带到服务端之后,接下来我们就需要来校验令牌的有效性。如果令牌是有效的,就说明用户已经执行了登录操作,如果令牌是无效的,就说明用户之前并未执行登录操作。    如果是在同一次会话的多次请求之间,我们想共享数据,我们就可以将共享的数据存储在令牌当中就可以了。      **优缺点**
​
- 优点: - 支持PC端、移动端 - 解决集群环境下的认证问题 - 减轻服务器的存储压力(无需在服务器端存储)
- 缺点:需要自己实现(包括令牌的生成、令牌的传递、令牌的校验)      针对于这三种方案,现在企业开发当中使用的最多的就是第三种令牌技术进行会话跟踪。而前面的这两种传统的方案,现在企业项目开发当中已经很少使用了。所以在我们的课程当中,我们也将会采用令牌技术来解决案例项目当中的会话跟踪问题。

4、JWT令牌

JWT令牌最典型的应用场景就是登录认证:   
JWT全称:JSON Web Token  
官方网址​​​​​​    定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。    简洁:是指jwt就是一个简单的字符串。可以在请求参数或者是请求头当中直接传递。
​    自包含:指的是jwt令牌,看似是一个随机的字符串,但是我们是可以根据自身的需求在jwt令牌中存储自定义的数据内容。     jwt将原始的json数据格式进行了安全的封装,这样就可以直接基于jwt在通信双方安全的进行信息传输了。                ★★★★ JWT令牌的组成:        
- 第一部分:Header(头), 记录令牌类型、签名算法等。 例如:{"alg":"HS256","type":"JWT"}
​
- 第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。 例如:{"id":"1","username":"Tom"}
​
- 第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload,并加入指定秘钥,通过指定签名算法计算而来。     签名的目的就是为了防jwt令牌被篡改,而正是因为jwt令牌最后一个部分数字签名的存在,所以整个jwt 令牌是非常安全可靠的。     JWT通过Base64对数据信息进行编码(是一种基于64个可打印的字符来表示二进制数据的编码方式。)用的64个字符分别是A到Z、a到z、 0- 9,一个加号,一个斜杠,加起来就是64个字符。任何数据经过base64编码之后,最终就会通过这64个字符来表示。当然还有一个符号,那就是等号。等号它是一个补位的符号。                     Base64是编码方式,而不是加密方式。

5、生成和校验

要想使用JWT令牌,需要先引入JWT的依赖:
<!-- JWT依赖-->
<dependency>   <groupId>io.jsonwebtoken</groupId>   <artifactId>jjwt</artifactId>   <version>0.9.1</version>
</dependency>
在引入完JWT来赖后,就可以调用工具包中提供的API来完成JWT令牌的生成和校验
​
工具类:Jwts      生成JWT代码实现:   
@Test
public void genJwt(){   Map<String,Object> claims = new HashMap<>();   claims.put("id",1);   claims.put("username","Tom");      String jwt = Jwts.builder()       .setClaims(claims) //自定义内容(载荷)                 .signWith(SignatureAlgorithm.HS256, "itheima".getBytes(StandardCharsets.UTF_8)) //签名算法               .setExpiration(new Date(System.currentTimeMillis() + 24*3600*1000)) //有效期          .compact();      System.out.println(jwt);
}
​
运行测试结果:     eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjcyNzI5NzMwfQ.fHi0Ub8npbyt71UqLXDdLyipptLgxBUg_mSuGJtXtBk 该字符串被英文标点分为三部分:
> 第一部分解析出来,看到JSON格式的原始数据,所使用的签名算法为HS256。
>
> 第二个部分是我们自定义的数据,之前我们自定义的数据就是id,还有一个exp代表的是我们所设置的过期时间。
>
> 由于前两个部分是base64编码,所以是可以直接解码出来。但最后一个部分并不是base64编码,是经过签名算法计算出来的,所以最后一个部分是不会解析的。
​
修改生成令牌的时指定的过期时间,修改为1分钟
@Test
public void genJwt(){   Map<String,Object> claims = new HashMap<>();   claims.put(“id”,1);   claims.put(“username”,“Tom”);   String jwt = Jwts.builder()       .setClaims(claims) //自定义内容(载荷)                 .signWith(SignatureAlgorithm.HS256, "itheima".getBytes(StandardCharsets.UTF_8)) //签名算法               .setExpiration(new Date(System.currentTimeMillis() + 60*1000)) //有效期60秒          .compact();      System.out.println(jwt);   //输出结果:eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjczMDA5NzU0fQ.RcVIR65AkGiax-ID6FjW60eLFH3tPTKdoK7UtE4A1ro
}
​
@Test
public void parseJwt(){   Claims claims = Jwts.parser()       .setSigningKey("itheima".getBytes(StandardCharsets.UTF_8))//指定签名密钥
.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjczMDA5NzU0fQ.RcVIR65AkGiax-ID6FjW60eLFH3tPTKdoK7UtE4A1ro")       .getBody();
​   System.out.println(claims);
}
​
★★★ 登录下发令牌   
1. 生成令牌  - 在登录成功之后来生成一个JWT令牌,并且把这个令牌直接返回给前端
2. 校验令牌  - 拦截前端请求,从请求中获取到令牌,对令牌进行解析校验
​
​

五、过滤器和拦截器

统一拦截到所有的请求校验令牌:
1. Filter过滤器 (Filter过滤器是Servlet API的一部分,用于对HTTP请求或响应进行预处理或后处理操作。)
2. Interceptor拦截器   
Filter表示过滤器,是 JavaWeb三大组件(Servlet、Filter、Listener)之一。
- 过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能 - 使用了过滤器之后,要想访问web服务器上的资源,必须先经过滤器,过滤器处理完毕之后,才可以访问对应的资源。
- 过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等。   
Filter快速入门程序掌握过滤器的基本使用操作:
​
- 第1步,定义过滤器 :1.定义一个类,实现 Filter 接口,并重写其所有方法。
- 第2步,配置过滤器:Filter类上加 @WebFilter 注解,配置拦截资源的路径。引导类上加 @ServletComponentScan 开启Servlet组件支持。   
★★定义过滤器:
//定义一个类,实现一个标准的Filter过滤器的接口
@WebFilter(urlPattern="/*")//配置过滤器要拦截的请求路径( /* 表示拦截浏览器的所有请求 )
public class DemoFilter implements Filter {   @Override //初始化方法, 只调用一次   public void init(FilterConfig filterConfig) throws ServletException {       System.out.println("init 初始化方法执行了");   }
​   @Override //拦截到请求之后调用, 调用多次   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {       System.out.println("Demo 拦截到了请求...放行前逻辑");       //放行       chain.doFilter(request,response);   }
​   @Override //销毁方法, 只调用一次   public void destroy() {       System.out.println("destroy 销毁方法执行了");   }
}
​
★★★ //三个方法的含义:
> - init方法:过滤器的初始化方法。在web服务器启动的时候会自动的创建Filter过滤器对象,在创建过滤器对象的时候会自动调用init初始化方法,这个方法只会被调用一次。
>
> - doFilter方法:这个方法是在每一次拦截到请求之后都会被调用,所以这个方法是会被调用多次的,每拦截到一次请求就会调用一次doFilter()方法。
>
> - destroy方法: 是销毁的方法。当我们关闭服务器的时候,它会自动的调用销毁方法destroy,而这个销毁方法也只会被调用一次。★★★ 注意:   在Filter类上面加了@WebFilter注解之后,接下来我们还需要在启动类上面加上一个注解@ServletComponentScan,通过这个@ServletComponentScan注解来开启SpringBoot项目对于Servlet组件的支持。    
@ServletComponentScan
@SpringBootApplication
public class TliasWebManagementApplication {
​   public static void main(String[] args) {       SpringApplication.run(TliasWebManagementApplication.class, args);   }
}
     过滤器拦截到了请求之后,如果希望继续访问后面的web资源,就要执行放行操作,放行就是调用 FilterChain对象当中的doFilter()方法,在调用doFilter()这个方法之前所编写的代码属于放行之前的逻辑。     放行后访问完 web 资源之后还会回到过滤器当中,回到过滤器之后如有需求还可以执行放行之后的逻辑,放行之后的逻辑我们写在doFilter()这行代码之后。

拦截路径

拦截路径urlPatterns值含义
拦截具体路径 /login 只有访问 /login 路径时,才会被拦截
目录拦截 /emps/* 访问/emps下的所有资源,都会被拦截
拦截所有 /* 访问所有资源,都会被拦截
@WebFilter(urlPatterns = "/login")  //拦截/login具体路径

2、过滤器链

    在我们web服务器当中,定义了两个过滤器,这两个过滤器就形成了一个过滤器链。 
执行顺序:   这个链上的过滤器在执行的时候会一个一个的执行,会先执行第一个Filter,放行之后再来执行第二个Filter,如果执行到了最后一个过滤器放行之后,才会访问对应的web资源。      访问完web资源之后,按照我们刚才所介绍的过滤器的执行流程,还会回到过滤器当中来执行过滤器放行后的逻辑,而在执行放行后的逻辑的时候,顺序是反着的。   先要执行过滤器2放行之后的逻辑,再来执行过滤器1放行之后的逻辑,最后在给浏览器响应数据。   以注解方式配置的Filter过滤器,它的执行优先级是按时过滤器类名的自动排序确定的,类名排名越靠前,优先级越高。

3、拦截器Interceptor

- 它是一种动态拦截方法调用的机制,类似于过滤器。
- 拦截器是Spring框架中提供的,用来动态拦截控制器方法的执行。   拦截器的作用:
- 拦截请求,在指定方法调用前后,根据业务需要执行预先设定的代码    在拦截器当中,我们通常也是做一些通用性的操作,校验令牌合法性。   
**自定义拦截器:**实现HandlerInterceptor接口,并重写其所有方法    //自定义拦截器
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {   //目标资源方法执行前执行。 返回true:放行    返回false:不放行   @Override   public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {       System.out.println("preHandle .... ");              return true; //true表示放行   }
​   //目标资源方法执行后执行   @Override   public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {       System.out.println("postHandle ... ");   }
​   //视图渲染完毕后执行,最后执行   @Override   public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {       System.out.println("afterCompletion .... ");   }
}
​
注意:
•   preHandle方法:目标资源方法执行前执行。返回true:放行,返回false:不放行
​
•   postHandle方法:目标资源方法执行后执行
​
•   afterCompletion方法:视图渲染完毕后执行,最后执行   
**注册配置拦截器**:实现WebMvcConfigurer接口,并重写addInterceptors方法   
@Configuration  
public class WebConfig implements WebMvcConfigurer {
​   //自定义的拦截器对象   @Autowired   private LoginCheckInterceptor loginCheckInterceptor;
​      @Override   public void addInterceptors(InterceptorRegistry registry) {      //注册自定义拦截器对象       registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");//设置拦截器拦截的请求路径( /** 表示拦截所有请求)   }
}

4、拦截路径

    在注册配置拦截器的时候,我们要指定拦截器的拦截路径,通过`addPathPatterns("要拦截路径")`方法,就可以指定要拦截哪些资源。    在入门程序中我们配置的是`/**`,表示拦截所有资源,而在配置拦截器时,不仅可以指定要拦截哪些资源,还可以指定不拦截哪些资源,只需要调用`excludePathPatterns("不拦截路径")`方法,指定哪些资源不需要拦截。  /***************************************************/
@Configuration  
public class WebConfig implements WebMvcConfigurer {
​   //拦截器对象   @Autowired   private LoginCheckInterceptor loginCheckInterceptor;
​   @Override   public void addInterceptors(InterceptorRegistry registry) {       //注册自定义拦截器对象       registry.addInterceptor(loginCheckInterceptor)               .addPathPatterns("/**")//设置拦截器拦截的请求路径( /** 表示拦截所有请求)               .excludePathPatterns("/login");//设置不拦截的请求路径   }
}

在拦截器中除了可以设置/**拦截所有资源外,还有一些常见拦截路径设置:

拦截路径含义举例
/* 一级路径 能匹配/depts,/emps,/login,不能匹配 /depts/1
/** 任意级路径 能匹配/depts,/depts/1,/depts/1/2
/depts/* /depts下的一级路径 能匹配/depts/1,不能匹配/depts/1/2,/depts
/depts/** /depts下的任意级路径 能匹配/depts,/depts/1,/depts/1/2,不能匹配/emps/1
    Tomcat并不识别所编写的Controller程序,但是它识别Servlet程序,所以在Spring的Web环境中提供了一个非常核心的Servlet:DispatcherServlet(前端控制器),所有请求都会先进行到DispatcherServlet,再将请求转给Controller。   当我们定义了拦截器后,会在执行Controller的方法之前,请求被拦截器拦截住。执行`preHandle()`方法,这个方法执行完成后需要返回一个布尔类型的值,如果返回true,就表示放行本次操作,才会继续访问controller中的方法;如果返回false,则不会放行(controller中的方法也不会执行)。    在controller当中的方法执行完毕之后,再回过来执行`postHandle()`这个方法以及`afterCompletion()` 方法,然后再返回给DispatcherServlet,最终再来执行过滤器当中放行后的这一部分逻辑的逻辑。执行完毕之后,最终给浏览器响应数据。       
/***********************************************************************************************/
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {   @Override   public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {       System.out.println("preHandle .... ");              return true; //true表示放行   }
​   @Override   public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {       System.out.println("postHandle ... ");   }
​   @Override   public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {       System.out.println("afterCompletion .... ");   }
}
​
​
过滤器和拦截器之间的区别主要是两点:
​
- 接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口。
- 拦截范围不同:过滤器Filter会拦截所有的资源,而Interceptor只会拦截Spring环境中的资源。
​
​

六、异常处理

三层架构处理异常的方案:
​
- Mapper接口在操作数据库的时候出错了,此时异常会往上抛(谁调用Mapper就抛给谁),会抛给service。 
- service 中也存在异常了,会抛给controller。
- 而在controller当中,我们也没有做任何的异常处理,所以最终异常会再往上抛。最终抛给框架之后,框架就会返回一个JSON格式的数据,里面封装的就是错误的信息,但是框架返回的JSON格式的数据并不符合我们的开发规范。
​
​

1、全局异常处理器

- 在类上加上一个注解@RestControllerAdvice,加上这个注解就代表我们定义了一个全局异常处理器。
- 在全局异常处理器当中,需要定义一个方法来捕获异常,在这个方法上需要加上注解@ExceptionHandler。通过@ExceptionHandler注解当中的value属性来指定我们要捕获的是哪一类型的异常。
/***************************************************************************/
@RestControllerAdvice
public class GlobalExceptionHandler {
​   //处理异常   @ExceptionHandler(Exception.class) //指定能够处理的异常类型   public Result ex(Exception e){       e.printStackTrace();//打印堆栈中的异常信息
​       //捕获到异常之后,响应一个标准的Result       return Result.error("对不起,操作失败,请联系管理员");   }
}
​
@RestControllerAdvice = @ControllerAdvice + @ResponseBody 理异常的方法返回值会转换为json后再响应给前端   
主要涉及到两个注解:
​
- @RestControllerAdvice  //表示当前类为全局异常处理器
- @ExceptionHandler  //指定可以捕获哪种类型的异常进行处理

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【加密系统】华企盾DSC服务台提示:请升级服务器,否则可能导致客户端退回到旧服务器的版本

华企盾DSC服务台提示:请升级服务器,否则可能导致客户端退回到旧服务器的版本 产生的原因:控制台版本比服务器高导致控制台出现报错解决方案方法:将控制台回退到原来的使用版本,在控制台负载均衡查看连接该服务器各个控制台版本。控制台版本在“关于”中查看,将控制台版本…

设计测试用例编写技巧_

一、查看用例的模板二、用例的要素讲解 .编写用例的要素? 用例编号,用例标题,前置条件,测试步骤,预期结果,优先级 (必写) 系统名称、模块名称、用例创建时间,实际结果,用例类型,执行时间,执行状态等(非必填项) 三、详解测试用例要素 (一)用例编号 可以称为:用…

视觉人体动作行为识别系统

视觉人体动作行为识别系统基于AI视觉智能分析算法,视觉人体动作行为识别系统利用监控摄像头捕捉到的视频数据进行实时分析。通过对工人的操作行为进行识别,系统能够准确判断工人在生产过程中是否存在违规行为或操作错误等情况。例如,系统可以识别工人是否按照正确的顺序执行…

智慧监狱人员行为识别监测系统

智慧监狱人员行为识别监测系统是基于神经网络AI视觉智能分析算法开发的技术。智慧监狱人员行为识别监测系统利用现场监控摄像头,通过对人体活动骨架的结构化分析,根据人体运动轨迹定义了多种异常行为,从而实现对监舍内的静坐不动、离床、攀高、独处;洗手间场景的入厕超时、…

垃圾分类智能监控系统

垃圾分类智能监控系统基于AI视觉智能分析算法,垃圾分类智能监控系统通过现场摄像头对垃圾投放点进行24小时不间断的监控。系统利用智能分析算法,可以实时识别垃圾乱投、垃圾箱满溢、厨余垃圾误时投放等垃圾分类违规投放行为。垃圾分类智能监控系统一旦系统检测到这些违规行为…

2024.7.2

2024.7.2 T1 题面 总共 \(n\) 个数与 \(m\) 个限制,第 \(i\) 个限制给定 \(k_i\) 个数,表示这些数两两不能分为一组,问最少可以分为几组。 \(1\le k\le n\le 10^5,1\le m\le 4\) 题解 把每个人的参赛情况用一个 \([0,15]\) 中的整数 \(s\) 表示,再按照 \(\operatorname{pop…

[快速阅读八] Matlab中bwlookup的实现及其在计算二值图像的欧拉数、面积及其他morph变形中的应用。

以前看过matlab的bwlookup函数,但是总感觉有点神秘,一直没有去仔细分析,最近在分析计算二值图像的欧拉数时,发现自己写的代码和matlab的总是对不少,于是又去翻了下matlab的源代码,看到了matlab里实现欧拉数的代码非常简单,其核心就是借用了bwlookup函数。以前看过matlab…

Ftrans供应链文件分发平台:如何确保数据安全与合规性?

传统制造企业在日常协作中,会涉及到像采购订单和合同、技术规格和图纸、质量标准和检验报告、库存和补货信息等文件分发需求。到在选择供应链文件分发平台时,需要考量以下因素,从而选择出合适的传输方式: 1.安全性:确保文件在传输过程中的安全性是至关重要的。需要考虑传输…

【Shiro】12.自定义过滤器

通过查看若依源码(ruoyi-framework)下的过滤器文件(src.main.java.com.ruoyi.framework.config.ShiroConfig)可以发现设置了过滤器。过滤器(Filter)是Java Servlet技术中的一个重要部分,主要用于在 Servlet 处理请求之前或响应之后对数据进行某些处理。可以这么理解。如果类…

【深度解读】涉密网向非涉密网跨网传输数据,需要注意什么?

网间数据传输的背景 为什么会存在涉密网向非涉密网跨网传输数据呢?哪些行业会面临这样的传输场景呢? 首先,会存在这样的场景,是因为有核心机密数据需要保护,通常会在政府机构、金融机构、军工企业、科研单位和大型企业中会做这样的网络隔离。这种做法主要是为了保护敏感信…

【泛微E9】在查询列表中增加红色字体的提示

效果如下:实现方法:<link rel="stylesheet" href="/js/jquery-ui-1.13.2/jquery-ui.css"> <link rel="stylesheet" href="/js/jquery-ui-1.13.2/jquery-ui.min.css"> <script src="/js/jquery-ui-1.13.2/jquery…

无需等待Vue Release发布,就能在项目中体验最新版

两个月前尤大在Vue 仓库中引入了 pkg.pr.new,有了这个后Vue仓库中的每个commit或者PR都会自动触发一个新的发布,我们就可以在项目中体验最新版本的Vue啦。前言 两个月前尤大在Vue 仓库中引入了 pkg.pr.new,有了这个后Vue仓库中的每个commit或者PR都会自动触发一个新的发布,…