SpringBoot Web开发(SpringMVC)
SpringBoot Web开发(SpringMVC)
MVC 核心组件和调用流程
Spring MVC与许多其他Web框架一样,是围绕前端控制器模式设计的,其中中央
ServletDispatcherServlet做整体请求处理调度!
.
除了DispatcherServletSpringMVC还会提供其他特殊的组件协作完成请求处理和响应呈现。
SpringMVC 处理请求流程

SpringMVC 涉及的组件理解
-
DispatcherServlet:SpringMVC提供,我们需要使用web.xml配置使其生效,它是整个流程处理的核心,所有请求都经过它的处理和分发![ CEO ] -
HandlerMapping:SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效,它内部缓存handler(controller方法)和handler访问路径数据,被DispatcherServlet调用,用于查找路径对应的handler![秘书] -
HandlerAdapter:SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效,它可以处理请求参数和处理响应数据数据,每次DispatcherServlet都是通过handlerAdapter间接调用handler,他是handler和DispatcherServlet之间的适配器![经理] -
Handler : handler又称处理器,他是Controller类内部的方法简称,是由我们自己定义,用来接收参数,向后调用业务,最终返回响应结果![打工人] -
ViewResovler : SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效!视图解析器主要作用简化模版视图页面查找的,但是需要注意,前后端分离项目,后端只返回JSON数据,不返回页面,那就不需要视图解析器!所以,视图解析器,相对其他的组件不是必须的![财务]
简单来讲

调用
Controller层的方法,先通过handlerMapping里面缓存的handler访问路径,和handler(controller方法)识别到需要的请求参数, 通过HandlerAdapter,提取对应的参数 k 对应的 v。然后才执行handler,返回数据再经过handlerAdapter将 handler 返回的数据封装到 response 中,如果是静态资源返回一个字符。利用视图解析器规定的格式拼接访问静态资源
Web 场景原理
自动配置原理
- 整合 web 场景依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
- 引入了
autoconfigure功能 @EnableAutoConfiguration注解使用@Import(AutoConfigurationImportSelector.class)批量导入组件- 加载
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中配置的所有组件 - 在
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports中按需加载关于 web 自动配置类
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration====以下是响应式web场景和现在的没关系======
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.ReactiveMultipartAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.WebSessionIdResolverAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration
================以上没关系=================org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
- 绑定了配置文件的一堆配置项
- SpringMVC的所有配置
spring.mvc - Web场景通用配置
spring.web - 文件上传配置
spring.servlet.multipart - 服务器的配置
server: 比如:编码方式
- SpringMVC的所有配置
自动配置效果
包含了 ContentNegotiatingViewResolver 和 BeanNameViewResolver 组件,方便视图解析
默认的静态资源处理机制: 静态资源放在 static 文件夹下即可直接访问
自动注册了 Converter,GenericConverter, Formatter组件,适配常见数据类型转换和格式化需求
支持 HttpMessageConverters,可以方便返回 json 等数据类型
注册 MessageCodesResolver,方便国际化及错误消息处理
支持 静态
index.html自动使用
ConfigurableWebBindingInitializer,实现消息处理、数据绑定、类型转化、数据校验等功能
WebMvcConfigurer 接口!!!
在
External Libraries中搜索spring-boot-autoconfigure然后再进到
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports找到
WebMvcAutoConfiguration。它是SpringMvC自动配置类再找到
WebMvcAutoConfigurationAdapter。
- 它实现了
WebMvcConfigurer接口- 并且有
@EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class})配置前缀- 得出这个接口包含了所有
SpringMVC组件的默认配置.
通过
WebMvCAutoConfigurationAdapter搜索以下方法可以找到响应组件的默认配置

最佳实践
WebMvcConfigurer 接口:
当你需要对
Spring MVC的一些常见功能进行配置和扩展时,会使用WebMvcConfigurer。比如添加拦截器来实现权限验证、配置视图控制器来简化页面跳转、自定义消息转换器来处理特定格式的数据等。不会影响自动配置
WebMvcConfigurer 接口
当你需要对
Spring MVC的底层请求处理逻辑进行深度定制时,会使用WebMvcRegistrations。例如,你想要自定义RequestMappingHandlerMapping的请求映射规则,让它根据特定的条件来匹配请求;或者自定义ExceptionHandlerExceptionResolver的异常处理逻辑,实现更复杂的异常处理策略。不会影响自动配置
@EnableMVC 注解
当你在配置类上添加
@EnableWebMvc注解时,它会全面接管Spring MVC的配置工作。这意味着Spring Boot默认提供的MVC自动配置会被完全覆盖,你需要手动配置所有与Spring MVC相关的组件,像视图解析器、消息转换器、拦截器、静态资源处理器等。
| 方式 | 用法 | 效果 | |
|---|---|---|---|
| 全自动 | 直接编写控制器逻辑 | 全部使用自动配置默认效果 | |
| 手自一体 | @Configuration + 配置WebMvcConfigurer+ 配置 WebMvcRegistrations | 不要标注 @EnableWebMvc | 保留自动配置效果。手动设置部分功能 ,定义MVC底层组件 |
| 全手动 | @Configuration + 配置WebMvcConfigurer | 标注 @EnableWebMvc | 禁用自动配置效果 全手动设置 |
SpringBoot 访问路径设置
路径注解 @RequestMapping
方法级别
直接在方法上加
@RequestMapping。当多个方法处理同一个路径的不同操作时,可以使用方法级别的@RequestMapping注解进行更精细的映射。
@Controller
public class UserController {//这里路径就是 /index 访问就会执行这个方法@RequestMapping("/index")public String index() {return null;}
}
类级别
在类上加
@RequestMapping,方法上还要再设置一次@RequestMapping设置了@RequestMapping的参数那就自动加上 类级别 的地址当前缀,如果没有参数就直接用 类地址 地址
@Controller
@RequestMapping("/user")
public class UserController {@RequestMapping //这里没有参数就直接用类级别的参数也就是 /userpublic String index() {return null;}@RequestMapping(value = "login") //这里有参数就是自动加上类级别的地址当前缀也就是 /user/loginpublic String login() {return null;}}
特定请求方式限制 —— 枚举方式
注意:违背请求方式,会出现405异常!!!
HTTP协议定义了八种请求方式,在SpringMVC中封装到了下面这个枚举类:public enum RequestMethod {GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE }
@Controller
public class UserController {/*** 精准设置访问地址 /user/login* method = RequestMethod.POST 可以指定单个或者多个请求方式!* 注意:违背请求方式会出现405异常!*/@RequestMapping(value = {"/user/login"} , method = RequestMethod.POST)@ResponseBodypublic String login(){System.out.println("UserController.login");return "login success!!";}
}
特定请求方式限制 —— 注解方式
注意:进阶注解只能添加到 handler 方法上,无法添加到类上!
还有
@RequestMapping的 HTTP 方法特定快捷方式变体:
@GetMapping@PostMapping@PutMapping@DeleteMapping@PatchMapping
@RequestMapping(value="/login",method=RequestMethod.GET)
就等于
@GetMapping(value="/login")
精确路径匹配
SpringMVC中@RequestMapping的/可以省略
.
/就表示绝对路径 ,Tomcat 中表示http://localhost:8080/,设置Tomcat上下文路径为/缺省就可以直接使用/+地址访问对应的Servlet
@RequestMapping(value = "/login", method = RequestMethod.POST) public String login() { return null; }
模糊路径匹配
- 在
@RequestMapping注解指定URL地址时,通过使用通配符,匹配多个类似的地址。*表示任意单层:/user/*这种就是一层可以访问,/user/a/b这就是两层访问不了**表示任意层:/user/**,比如:/user/a,/user/a/b都可以
@Controller
public class ProductController {/*** 路径设置为 /product/* * /* 为单层任意字符串 /product/a /product/aaa 可以访问此handler * /product/a/a 不可以* * 路径设置为 /product/** * /** 为任意层任意字符串 /product/a /product/aaa 可以访问此handler * /product/a/a 也可以访问*/@RequestMapping("/product/*")@ResponseBodypublic String show(){System.out.println("ProductController.show");return "product show!";}
}
SpringBoot 接收参数
param 和 json 参数比较
在 HTTP 请求中,我们可以选择不同的参数类型,如 param 类型和 JSON 类型。下面对这两种参数类型进行区别和对比:
param:key = value & key = valuejson:{ key : value, key : value }
.
- 参数编码:
param 类型的参数会被编码为 ASCII 码。例如,假设name=john doe,则会被编码为name=john%20doe。而 JSON 类型的参数会被编码为 UTF-8。
.- 参数顺序:
param 类型的参数没有顺序限制。但是,JSON 类型的参数是有序的。JSON 采用键值对的形式进行传递,其中键值对是有序排列的。
.- 数据类型:
param 类型的参数仅支持字符串类型、数值类型和布尔类型等简单数据类型。而 JSON 类型的参数则支持更复杂的数据类型,如数组、对象等。- 嵌套性:
param 类型的参数不支持嵌套。但是,JSON 类型的参数支持嵌套,可以传递更为复杂的数据结构。
.- 可读性:
param 类型的参数格式比 JSON 类型的参数更加简单、易读。但是,JSON 格式在传递嵌套数据结构时更加清晰易懂。
.总的来说,param 类型的参数适用于单一的数据传递,而 JSON 类型的参数则更适用于更复杂的数据结构传递。根据具体的业务需求,需要选择合适的参数类型。
.
在实际开发中,
常见的做法是:在GET请求中采用param类型的参数,而在POST请求中采用JSON类型的参数传递。
返回对象就是 json
简单类型接值
客户端请求

Handler 接收参数
- 只要形参参数名和类型与传递的参数相同,即可自动接收!
- 也可以不传递参数
@Controller
@RequestMapping("param")
public class ParamController {/*** 前端请求: http://localhost:8080/param/value?name=xx&age=18** 可以利用形参列表,直接接收前端传递的param参数!* 要求: 参数名 = 形参名* 类型相同* 出现乱码正常,json接收具体解决!!* @return 返回前端数据*/@GetMapping(value="/value")@ResponseBodypublic String setupForm(String name,int age){System.out.println("name = " + name + ", age = " + age);return name + age;}
}
简单类型接值【形参和请求参数不一致】
- 指定绑定的请求参数名:
@RequestParam(value="指定请求参数名")【形参名和请求参数名一致可以省略】- 要求请求参数必须传递:
required = false【前端是否必须传递此此参数,默认是必须,不传报400异常】- 为请求参数提供默认值:
defaultValue = "1"【当非必须传递的时候, 可以设置默认值】)
浏览器请求

handler 接收参数
public class HelloController {//绑定请求参数为 myname 和 myage。myage 不必须传递。默认值为 0@RequestMapping(value = "/springmvc/hello")@ResponseBodypublic String hello(@RequestParam(value = "myname") String name, @RequestParam(value = "myage", required =false, defaultValue = "0") int age) {System.out.println("name:" + name + ",age:" + age);return name + ":" + age;}
}
数组类型接收
param的key和数组参数名一致就会接收
客户端请求

handler 参数
@Controller
@RequestMapping("user")
public class testcontroller {@ResponseBody@RequestMapping//注意这里的 数组 参数名要和 param 的 key 一致public String test1(@RequestParam String[] names) {System.out.println(Arrays.toString(names));return Arrays.toString(names);}
}
集合类型接收
一个名字对应多个值
- 多选框,提交的数据的时候一个key对应多个值,我们可以使用集合进行接收!集合用
@RequestParam声明.
注意:param 的 key 要和集合名字一样
客户端请求

handler 接收参数
@Controller
@RequestMapping("user")
public class testcontroller {@ResponseBody@RequestMapping//注意这里的 List 参数名要和 param 的 key 一致public String test1(@RequestParam List<String> names) {System.out.println(names);return names.toString();}}
实体类型接收
要通过对象接收参数值,只需创建一个实体类,为每个属性配备
get 和 set方法,客户端传递的param key要和实体类的属性名一致。接收参数时,在形参列表中声明实体类对象即可。
.
如果是实体类对象的属性还是个对象那就用address.provice这样来做 param 的 key
.
注意点:实体类必须有 get 和 set
POJO
@Data
public class User {private int age;private String name;private Address address;
}
@Data
public class Address {private String province;private String city;}
客户端

handler
@Controller
@RequestMapping("user")
public class testcontroller {@ResponseBody@RequestMappingpublic String test1(User user) {System.out.println(user);return user.toString();}}
日期参数接收
param 的 key 要和 日期类型的参数名一样。并且用
@DateTimeFormat指定格式
客户端

handler
@Controller
@RequestMapping("user")
public class testcontroller {@ResponseBody@RequestMappingpublic String test1(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDate updateTime) {System.out.println(updateTime);return updateTime.toString();}}
路径参数接收
{} 声明的路径原理就是 *
动态路径参数:
http://localhost:8080/path/key/root这里的key/root假如是动态路径。那它可以当参数传递给handler
- 接收动态路径参数必须要用 @
PathVariable声明。- 如果 handler 形参名和路径的名字一样。那就直接
@PathVariable就行。不一样就手动指定@PathVariable的 name
客户端

handler
@Controller
//这里 key 和 password 可以传递给下面的 handler
@RequestMapping("path/{key}/{password}")
public class testcontroller {@ResponseBody@RequestMappingpublic String //第一个参数因为形参名和路径名不一样所以要设置 name 参数test1(@PathVariable(name= "key") String mykey, @PathVariable String password) {System.out.println(mykey + ":" + password);return mykey + ":" + password;}}
路径匹配默认规则 【路径参数】
Ant 风格路径用法
Ant 风格的路径模式语法具有以下规则:
*:表示任意数量的字符。**?:表示任意一个字符。****:表示任意数量的目录。{}:表示一个命名的模式占位符。[]:表示字符集合,例如 [a-z] 表示小写字母。举例
*.html匹配任意名称,扩展名为.html的文件。
/folder1/*/*.java 匹配在folder1目录下的任意两级目录下的.java文件。
/folder2/**/*.jsp匹配在folder2目录下任意目录深度的.jsp文件。
/{type}/{id}.html匹配任意文件名为{id}.html,在任意命名的{type}目录下的文件。注意:Ant 风格的路径模式语法中的特殊字符需要转义,如:
- 要匹配文件路径中的星号,则需要转义为
\\*- 要匹配文件路径中的问号,则需要转义为
\\?。
新规则和旧规则改变
AntPathMatcher与PathPatternParser
- PathPatternParser 在 jmh 基准测试下,有 6~8 倍吞吐量提升,降低 30%~40%空间分配率
- PathPatternParser 兼容 AntPathMatcher语法,并支持更多类型的路径模式
- PathPatternParser
\**多段匹配的支持仅允许在模式末尾使用
@GetMapping("/a*/b?/{p1:[a-f]+}")public String hello(HttpServletRequest request, @PathVariable("p1") String path) {log.info("路径变量p1: {}", path);//获取请求路径String uri = request.getRequestURI();return uri;}
总结
- 使用默认的路径匹配规则,是由
PathPatternParser提供的 - 如果路径中间需要有
\**,替换成ant风格路径
# 改变路径匹配策略:
# ant_path_matcher 老版策略;
# path_pattern_parser 新版策略;
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
JSON 参数接收
前端发送 JSON 数据时,
Spring MVC框架可通过@RequestBody注解将其转为 Java 对象。此注解表示方法参数值从请求体获取,Springboot中直接添加该注解就行,无需额外操作,且不用指定 value 属性,它会自动映射到相应的参数上。
- 第一步:给前端来的 JSON 存储的 POJO 类
@Data
public class Person {private String name;private int age;private String gender;}
- 第二步:接收 JSON
@RequestMapping("/json")
@Controller
@ResponseBody
public class JsonController {//data -> 请求体 post {name,age,gender}//前端 传了一个 json 报 415//原因: java原生的api 只支持路径参数和 param 参数 不支持 json// json 本身就是前端的格式//解决: 1. 导入 json 处理的依赖 2.handlerAdapter 配置 json 转化器@PostMapping("data")public String data(@RequestBody Person person) {System.out.println("person = " + person);return person.toString();}
}
Cookie 数据接收

可以使用
@CookieValue注解将HTTP Cookie的值绑定到的方法参数。
@Controller
@ResponseBody
@RequestMapping("/test")
public class testcontroller {@RequestMapping("/cooie")//形参名和 cookie key 一样就不用给 @CookieValue 指定 namepublic String addPerson(@CookieValue String cookieName){System.out.println("value = " + cookieName);return cookieName;}//创建 Cookie@GetMapping("save")public String save(HttpServletResponse response) {Cookie cookie = new Cookie("cookieName", "root");response.addCookie(cookie);return "ok";}}
请求头信息接收

- 第一种:根据参数接收*
@RequestHeader("请求头的 key")
@GetMapping("/demo")
public void handle(@RequestHeader("Accept-Encoding") String encoding, @RequestHeader("Keep-Alive") long keepAlive) { ...
}
- 第二种:根据形参名自动匹配*
//获取 token
//这种是根据根据参数名自动匹配
@GetMapping("/demo")
public void handle(@RequestHeader String token) { }
SpringBoot 响应数据
模板引擎
模板引擎就是类似 vue 的东西
SpringBoot 包含以下模板引擎的自动配置
FreeMarkerGroovyThymeleafMustache
整合 Thymeleaf
- 导入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
- 在
template放html文件

- 写
controller
@Controller
public class WelcomeController {/*** 利用模板引擎跳转到指定页面* @return*/@GetMapping("/well")public String hello(@RequestParam("name") String name, Model model) {//模板的逻辑视图名//物理视图 = 前缀 + 逻辑视图名 + 后缀//真是地址 = classpath:/templates/welcome.html//把需要给页面共享的数据放到 model 中model.addAttribute("msg", name);return "welcome";}}
返回 JSON 数据
基本使用
- 创建 pojo
@Data
public class User {private String name;private int age;}
- 响应数据
@RequestMapping("json")
@RestController //@Controller + @ResponseBody
public class JsonController {@GetMapping("data2")public List<User> data1() {User user = new User();user.setName("two dogs!");user.setAge(3);List<User> users = new ArrayList<>();users.add(user);return users;}
}
@ResponseBody 注解
- 方法上使用
@ResponseBody
在前后端分离项目里,
@ResponseBody注解加在方法上,它会把方法返回的对象序列化成JSON或 XML 格式的数据,直接发给客户端。这意味着返回值 不会走视图解析器渲染这一步,而是 直接作为数据响应。
@RequestMapping(value = "/user/detail", method = RequestMethod.POST)
@ResponseBody
public User getUser(@RequestBody User userParam) {System.out.println("userParam = " + userParam);User user = new User();user.setAge(18);user.setName("John");//返回的对象,会使用jackson的序列化工具,转成json返回给前端!return user;
}
- 在类上使用
@ResponseBody
如果类中每个方法上都标记了
@ResponseBody注解,那么这些注解就可以提取到类上。
@ResponseBody //responseBody可以添加到类上,代表默认类中的所有方法都生效!
@Controller
@RequestMapping("param")
public class ParamController {
@RestController 注解
类上的
@RestponseBody注解可以和@Controller注解合并为@RestController注解。所以使用了@RestController注解就相当于给类中的每个方法都加了@ResponseBody注解。
内容协商 [ 返回 JSON 原理 ]
@ResponseBody 默认规则
基于请求头内容协商:(默认开启)
- 客户端向服务端发送请求,携带
HTTP标准的Accept请求头。Accept:application/json、text/xml、text/yaml- 服务端根据客户端请求头期望的数据类型进行动态返回
.
.
基于请求参数内容协商:(需要手动开启)
- 发送请求
GET /projects/spring-boot?format=json
format=...的形式进行内容协商- 匹配到
@GetMapping("/projects/spring-boot")- 根据参数协商,优先返回
json类型数据- 如果发送请求
GET /projects/spring-boot?format=xml,优先返回xml类型数据# 开启基于请求参数的内容协商 spring.mvc.contentnegotiation.favor-parameter=true #自定义参数名,默认为format spring.mvc.contentnegotiation.parameter-name=myparam

@ResponseBody 原理
@ResponseBody由HttpMessageConverter处理
HttpMessageConverter会先进行内容协商默认
MessageConverter有以下
ByteArrayHttpMessageConverter: 支持字节数据读写StringHttpMessageConverter: 支持字符串读写ResourceHttpMessageConverter:支持资源读写ResourceRegionHttpMessageConverter: 支持分区资源写出AllEncompassingFormHttpMessageConverter:支持表单xml/json读写MappingJackson2HttpMessageConverter: 支持请求体响应体Json读写系统提供默认的MessageConverter 功能有限,仅用于json或者普通返回数据。额外增加新的内容协商功能,必须增加新的
HttpMessageConverter
静态资源
静态资源路径映射
- 访问
/**默认就来以下路径寻找静态资源
classpath:/META-INF/resources/classpath:/resources/classpath:/static/classpath:/public/
注意:
- 欢迎页
index.html也是默认在以上路径查找。没有就在templates下找index.html项目启动默认访问 favicon.ico(浏览器窗口栏那个图标)也是默认在以上路径查找
静态资源缓存规则
cachePeriod 【大概配置】
cachePeriod是指资源被缓存的时间长度。在此时间范围内,资源会被浏览器或代理服务器缓存,不需要再次从服务器获取。- 默认值:如果没有设置
cachePeriod,则默认没有缓存周期,每次请求都会向服务器发送请求以获取最新的资源。- 单位:
cachePeriod的单位通常是秒(s)。例如,如果设置为3600,则表示资源会被缓存1小时。- 作用:设置
cachePeriod可以减少服务器的负载和网络延迟,提高资源的加载速度,特别是对于不经常变化的资源(如图片、CSS、JavaScript文件)。.
cacheControl 【精确配置】
cacheControl是HTTP协议中的一个头部字段,用于指定在HTTP请求 / 响应链中,资源应该如何被缓存。- 默认:没有
cacheControl- 详细说明:
cacheControl可以设置多个指令,例如:
no-cache:告诉浏览器或代理服务器在重新验证资源之前不能使用缓存资源。no-store:告诉浏览器或代理服务器不存储这次请求或响应的任何部分。max-age=<seconds>:指定资源被缓存的最大时间,单位为秒。public:表明响应可以被任何缓存所存储。private:表明响应只能被单个用户缓存,不能被共享缓存所存储。- 作用:通过
cacheControl,可以更精细地控制资源的缓存行为,确保用户能够获取到最新的资源,或者提高资源的加载速度。.
userLastModified
定义:
useLastModified是一个标志,表示是否在HTTP响应中包含Last-Modified头部。默认::false
详细定义:
userLastModified为true会带上一个Last-Modified(该资源的最后修改时间)。下次请求改资源会获取If-Modified-Since。这个If-Modified-Since的值就是Last-Modified(该资源的最后修改时间)。要是资源从If-Modified-Since开始就没动过。就直接返回 304 。直接使用缓存中的资源通过使用
Last-Modified头部,可以减少不必要的数据传输,因为如果资源没有变化,服务器不需要重新发送资源的内容,只需发送一个状态码即可。有了精确配置就不用大概配置了
自定义静态资源规则
配置方式
翻阅
WebMvConfigurer接口笔记。一直往下进到WebMvcAutoConfigurationAdapter。找到addResourceHandlers方法。这里就是静态资源规则源码

- 静态资源访问路径默认规则
- 照着
getStaticPathPattern()一直往下点 - 可以知道怎么利用 配置文件 自定义这个规则
- 照着



# 重新设置静态资源访问路径
# 从以上图片可以看出前缀是 spring.mvc 后缀是 staticPathPattern
#会覆盖默认规则
spring.mvc.static-path-patteren=/static/**
- 访问静态资源默认到哪个目录去找的默认规则
- 照着
getStaticPathPattern()一直往下点 - 可以知道怎么利用 配置文件 自定义这个规则
- 照着





# 重新设置态资源默认到哪个目录去找的规则# 从以上图片看出 前缀是 spring.web 然后后缀是静态类中的 Resources 中的 staticLocations 属性。然后配置规则是 classpath#会覆盖默认规则
spring.web.resources.static-locations=classpath:/a/, classpath:/b/
- 缓存相关
##大概设置
#设置缓存时间
spring.web.resources.cache.period=3600##详细设置,覆盖period配置:
## 浏览器第一次请求服务器,服务器告诉浏览器此资源缓存7200秒,7200秒以内的所有此资源访问不用发给服务器请求,7200秒以后发请求给服务器
spring.web.resources.cache.cachecontrol.max-age=7200
## 共享缓存。只要是这个客户端。就谁都可以用这个缓存数据
spring.web.resources.cache.cachecontrol.cache-public=true#使用资源 last-modified 时间,来对比服务器和浏览器的资源是否相同没有变化。相同返回 304
spring.web.resources.cache.use-last-modified=true
总结:
spring.mvc前缀- 设置静态资源访问路径
spring.web前缀- 设置访问静态资源要找哪个路径
- 设置静态资源缓存策略
代码方式
因为
WebMvcConfigurer接口包含了所有SpringMVC组件的默认配置。所以我们可以自己写一个配置了实现WebMvcConfigurer接口。自己制定规则
- 第一种方式
@Configuration
public class MyConfig implements WebMvcConfigurer {@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {//保留以前的配置//没有 @EnableWebMvc 禁用boot 默认配置。就算没这句话默认配置也还在WebMvcConfigurer.super.addResourceHandlers(registry);//自己加一些配置registry.addResourceHandler("/static/**").addResourceLocations("classpath:/a/", "classpath:/b/").setCacheControl(CacheControl.maxAge(1180, TimeUnit.SECONDS))}
}
- 第二种方式
为什么容器中放一个
WebMvcConfigurer就能配置底层行为
**
WebMvcAutoConfiguration是一个自动配置类,它里面有一个EnableWebMvcConfiguration静态类 **
EnableWebMvcConfiguration继承于DelegatingWebMvcConfiguration,这两个都生效
DelegatingWebMvcConfiguration利用 DI 把容器中 所有WebMvcConfigurer注入进来别人调用
DelegatingWebMvcConfiguration的方法配置底层规则,而它调用所有WebMvcConfigurer的配置底层方法。
@Configuration //这是一个配置类, 容器中放一个 webMMvcConfigurer组件, 就能自定义底层
public class MyConfig{@Beanpublic WebMvcConfigurer webMvcConfigurer() {return new WebMvcConfigurer() {@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/").setCacheControl(CacheControl.maxAge(1180, TimeUnit.SECONDS));}};}
}
SpringBoot 异常处理
单独写一个异常类。发生异常就会走此类下的 handler 方法
ControllerAdvice: 走字符串拼接网址那一套。前后端不分离的RestControllerAdvice:直接返回数据。不拼接。前后端分离
只要发生异常就进入这里寻找对应的异常处理,并且要注意在配置类扫描这个全局异常类。注意要让主程序扫描到这个类
//全局异常发生会走此类下的 handler 方法
//@ControllerAdvice //可以返回逻辑视图 转发和重定向
@RestControllerAdvice
public class GlobalExceptionHandler {//发生异常 -> 进入 @ControllerAdvice 注解的类型 -> 根据@ExceptionHandler(指定的异常) 去处理//指定的异常 可以精准查找 或者查找父异常@ExceptionHandler(ArithmeticException.class)public Object ArithmeticException(ArithmeticException e) {String message = e.getMessage();System.out.println("message = " + message);return message;}//如果没有 ArithmeticException 就走 Exception@ExceptionHandler(Exception.class)public Object Exception(Exception e) {String message = e.getMessage();System.out.println("message = " + message);return message;}}
SpringBoot 登录校验
登录校验会话技术方案
概述

会话跟踪方案对比

JWT 令牌技术
概念
- Token
令牌(
Token):在计算机领域,令牌是一种代表某种访问权限或身份认证信息的令牌。它可以是一串随机生成的字符或数字,用于验证用户的身份或授权用户对特定资源的访问。普通的令牌可能以各种形式出现,如访问令牌、身份令牌、刷新令牌等。
.
简单理解 : 每个用户生成的唯一字符串标识,可以进行用户识别和校验

- JWT
jwt工作流程
用户提供其凭据(通常是用户名和密码)进行身份验证。
服务器对这些凭据进行验证,并在验证成功后创建一个
JWT。服务器将
JWT发送给客户端,并客户端在后续的请求中将JWT附加在请求头或参数中。服务器接收到请求后,验证
JWT的签名和有效性,并根据JWT中的声明进行身份验证和授权操作
使用语法
- 导入依赖
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency><!-- JDK9以上需要这个依赖-->
<dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>2.3.0</version>
</dependency>
- 生成 JWT 令牌*
- 签名算法可以去 JWT 官网查找,密钥自己设置
public class JWTTest {@Testpublic void jwtTest1() {//自己的内容Map<String, Object> claims = new HashMap<>();claims.put("id", 1);claims.put("name", "com");String jwt = Jwts.builder().signWith(SignatureAlgorithm.HS256, "mangfu")//设置签名算法 和 密钥.setClaims(claims) //自定义内容(载荷)[自己的内容].setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000)) //设置 jwt 有效期为 1 h (当前时间往后推).compact(); //拿到字符串类型返回值System.out.println(jwt);}
注意事项:

实战:登录成功,下发令牌
登录成功。令牌发到浏览器。后面浏览器每次访问服务端就携带令牌。到服务端进行登录校验
- JWT 令牌工具类
- 下发 JWT 令牌
- 解析 JWT 令牌
public class JwtUtils {//设置密钥private static String signKey = "mangfu"//设置 JWT 令牌生效时间 12 小时private static Long expire = 43200000L;public static String generateJwt(Map<String, Object> claims) {String jwt = JWTs.builder().addClaims(claims).signWith(SignatureAlgoritth.HS256, signKey).setExpiration(new Date(System.currentTimeMillis() + expire)).compact()return jwx; }public static Claims parseJWT(String jwt) {Claims claims = JWTs.parser().setSigningKey(signKey).parseClaimsJws(jwt).getBody();;return claims; }}
Filter 过滤器
概念

基本使用

- 注意是
servlet包下的filter init和destory提供了默认实现可以不重写
详细使用细节
- 使用流程

- 拦截器路径设置

- 过滤器链

实战:登录校验,令牌校验
用户登录成功后,会下发
JWT令牌,然后在后续的每次请求中,都需要在请求头header中携带到服务端, 请求头名称为token,值为登录时下发的JWT令牌
注意

校验流程

@WebServlet(urlPatterns = "/*")
public class DemoFilter implements Filter {@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest req = (HttpServletRequest) servletRequest;HttpServletResponse resp = (HttpServletResponse) servletResponse;//1. 获取请求 urlString url = req.getRequestURI().toString();System.out.println("请求的 url: " + url);//2.判断请求 url 中是否包含 login, 如果包含, 说明是登录操作, 放行if (url.contains("login")) {System.out.println("登录操作, 放行...");filterChain.doFilter(req, resp);return;}//3. 获取请求头中的令牌 tokenString jwt = req.getHeader("token");//4. 判断令牌是否存在, 如果不存在, 返回错误结果 (未登录)/*public static boolean hasLength(@Nullable String str) {return str != null && !str.isEmpty();}*/if (StringUtils.hasLength(jwt)) {System.out.println("请求头 token 为空, 返回未登录的信息");/*Result error = Result.error("NOT_LOGIN");//把错误信息转成 JSON 发给前端String notLogin = JSONObject.toJSONString(error)resp.getWriter().write(notLogin);*/return;}//5. 如果令牌存在, 解析 token, 如果解析失败, 返回错误结果 (未登录)//解析不报错说明成功, 报错说明失败try {JwtUtils.parseJWT(jwt);} catch (Exception e) {e.printStackTrace();System.out.println("解析令牌失败, 返回未登录错误信息");/*Result error = Result.error("NOT_LOGIN");//把错误信息转成 JSON 发给前端String notLogin = JSONObject.toJSONString(error)resp.getWriter().write(notLogin);*/}//6. 放行System.out.println("令牌合法, 放行");filterChain.doFilter(req, resp);}
}
Interceptor 拦截器
概念


拦截器使用

就是自定义一个拦截器类实现
HandlerInterceptor,然后配置类实现WebMvcConfigurer扫描拦截器类。配置拦截路径
- 实现
HandlerInterceptor实现其所有方法

- 配置类实现
WebMvcConfigurer添加拦截器
@EnableWebMvc //json数据处理,必须使用此注解,因为他会加入json处理器
@Configuration
@ComponentScan(basePackages = {"com.atguigu.controller","com.atguigu.exceptionhandler"})
public class SpringMvcConfig implements WebMvcConfigurer {//添加拦截器@Overridepublic void addInterceptors(InterceptorRegistry registry) {//配置方案1 拦截全部请求registry.addInterceptor(new MyInterceptor());//配置方案2 指定地址拦截// * 任意一层字符串 ** 任意多层字符串registry.addInterceptor(new MyInterceptor()).addPathPatterns("/user/data");//配置方案3 排除拦截 // addPathPatterns 需要拦截的路径// excludePathPatterns 需要拦截的路径中有哪些不拦截registry.addInterceptor(new MyInterceptor()).addPathPatterns("/user/**").excludePathPatterns("/user/data1");}
}
多个拦截器情况
- 如果有多个拦截器,执行顺序
- preHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置顺序调用各个 preHandle() 方法。
- postHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 postHandle() 方法。
- afterCompletion() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 afterCompletion() 方法。
实战:登录校验,令牌校验

@Component
public class DemoInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1. 获取请求 urlString url = request.getRequestURI().toString();System.out.println("请求的 url: " + url);//2.判断请求 url 中是否包含 login, 如果包含, 说明是登录操作, 放行if (url.contains("login")) {System.out.println("登录操作, 放行...");return true;}//3. 获取请求头中的令牌 tokenString jwt = request.getHeader("token");//4. 判断令牌是否存在, 如果不存在, 返回错误结果 (未登录)/*public static boolean hasLength(@Nullable String str) {return str != null && !str.isEmpty();}*/if (StringUtils.hasLength(jwt)) {System.out.println("请求头 token 为空, 返回未登录的信息");/*Result error = Result.error("NOT_LOGIN");//把错误信息转成 JSON 发给前端String notLogin = JSONObject.toJSONString(error)resp.getWriter().write(notLogin);*/return false;}//5. 如果令牌存在, 解析 token, 如果解析失败, 返回错误结果 (未登录)//解析不报错说明成功, 报错说明失败try {JwtUtils.parseJWT(jwt);} catch (Exception e) {e.printStackTrace();System.out.println("解析令牌失败, 返回未登录错误信息");/*Result error = Result.error("NOT_LOGIN");//把错误信息转成 JSON 发给前端String notLogin = JSONObject.toJSONString(error)resp.getWriter().write(notLogin);*/}//6. 放行System.out.println("令牌合法, 放行");return true;}
}
拦截器和过滤器的区别

RESTFul 风格设计规范
HTTP 协议请求方式要求
REST 风格主张在项目设计、开发过程中,具体的操作符合HTTP协议定义的请求方式的语义。
操作 请求方式 查询操作 GET 保存操作 POST 删除操作 DELETE 更新操作 PUT
URL 风格要求
在
RESTful风格的 API 设计里,URL 路径一般用名词命名,用来表示资源。资源可以是用户、订单这类实体,也能是搜索、计算等服务。设计 URL 路径时,强调用名词标识资源,而非用动词描述操作。比如,把/editEmp(动词)改成/Emp(名词) 。
.
例如:
GET /users:检索用户列表POST /users:创建新用户PUT /users/123:更新ID为123的用户DELETE /users/123:删除ID为123的用户
| 操作 | 传统风格 | REST 风格 |
|---|---|---|
| 保存 | /CRUD/saveEmp | URL 地址:/CRUD/emp 请求方式:POST |
| 删除 | /CRUD/removeEmp?empId=2 | URL 地址:/CRUD/emp/2 请求方式:DELETE |
| 更新 | /CRUD/updateEmp | URL 地址:/CRUD/emp 请求方式:PUT |
| 查询 | /CRUD/editEmp?empId=2 | URL 地址:/CRUD/emp/2 请求方式:GET |
传递参数设计要求
获取数据
GET,删除数据DELTETE
- 参数是 id 标识。使用路径 方式
/url/id- 参数是 是 范围参数。使用 param 方式
/url?page=1&size=10保存数据
POST,修改数据PUT
- 全部使用请求体传递
JSON方式其他原则
- 请求参数应该限制在 10 个以内,过多的请求参数可能导致接口难以维护和使用。
- 对于敏感信息,最好使用 POST 采用请求体来传递参数。
- 如果地址冲突 (请求方式 和 路径都一样) :那就在后面加个动词区分一下,比如
GET /user和GET /user/search
实战举例
接口设计
| 功能 | 接口和请求方式 | 请求参数 | 返回值 |
|---|---|---|---|
| 分页查询 | GET /user | page=1&size=10 | { 响应数据 } |
| 用户添加 | POST /user | { user 数据 } | {响应数据} |
| 用户详情 | GET /user/1 | 路径参数 | {响应数据} |
| 用户更新 | PUT /user | { user 更新数据} | {响应数据} |
| 用户删除 | DELETE /user/1 | 路径参数 | {响应数据} |
| 条件模糊 | GET /user/search | page=1&size=10&keywork=关键字 | {响应数据} |
- 用户 pojo
package com.atguigu.pojo;/*** projectName: com.atguigu.pojo* 用户实体类*/
@Data
public class User {private Integer id;private String name;private Integer age;
}
- controller
/*** projectName: com.atguigu.controller** description: 用户模块的控制器*/
@RequestMapping("user")
@RestController
public class UserController {/*** 模拟分页查询业务接口*/@GetMappingpublic Object queryPage(@RequestParam(name = "page",required = false,defaultValue = "1")int page,@RequestParam(name = "size",required = false,defaultValue = "10")int size){System.out.println("page = " + page + ", size = " + size);System.out.println("分页查询业务!");return "{'status':'ok'}";}/*** 模拟用户保存业务接口*/@PostMappingpublic Object saveUser(@RequestBody User user){System.out.println("user = " + user);System.out.println("用户保存业务!");return "{'status':'ok'}";}/*** 模拟用户详情业务接口*/@PostMapping("/{id}")public Object detailUser(@PathVariable Integer id){System.out.println("id = " + id);System.out.println("用户详情业务!");return "{'status':'ok'}";}/*** 模拟用户更新业务接口*/@PutMappingpublic Object updateUser(@RequestBody User user){System.out.println("user = " + user);System.out.println("用户更新业务!");return "{'status':'ok'}";}/*** 模拟条件分页查询业务接口*/@GetMapping("search")public Object queryPage(@RequestParam(name = "page",required = false,defaultValue = "1")int page,@RequestParam(name = "size",required = false,defaultValue = "10")int size,@RequestParam(name = "keyword",required= false)String keyword){System.out.println("page = " + page + ", size = " + size + ", keyword = " + keyword);System.out.println("条件分页查询业务!");return "{'status':'ok'}";}
}
相关文章:
SpringBoot Web开发(SpringMVC)
SpringBoot Web开发(SpringMVC) MVC 核心组件和调用流程 Spring MVC与许多其他Web框架一样,是围绕前端控制器模式设计的,其中中央 Servlet DispatcherServlet 做整体请求处理调度! . 除了DispatcherServletSpringMVC还会提供其他…...
汽车蓝牙钥匙定位仿真小程序
此需求来自于粉丝的真实需求,假期没事,牛刀小试。 一、项目背景 如今,智能车钥匙和移动端定位技术已经相当普及。为了探索蓝牙 Beacon 在短距离定位场景下的可行性,我们搭建了一个简易原型:利用 UniApp 在移动端采集蓝牙信标的 RSSI(信号强度),通过三边定位算法估算钥…...
K8S中高级存储之PV和PVC
高级存储 PV和PVC 由于kubernetes支持的存储系统有很多,要求客户全都掌握,显然不现实。为了能够屏蔽底层存储实现的细节,方便用户使用, kubernetes引入PV和PVC两种资源对象。 PV(Persistent Volume) PV是…...
【C语言进阶】- 动态内存管理
动态内存管理 1.1 为什么存在动态内存分配1.2 动态内存函数介绍2.1 malloc函数的使用2.2 free函数的使用2.3 calloc函数的使用2.4 realloc函数的使用3.1 常见的动态内存错误3.2 常见笔试题 1.1 为什么存在动态内存分配 我们已经掌握的内存开辟方式有: int val 20;…...
Python实现基于TD3(Twin Delayed Deep Deterministic Policy Gradient)算法来实时更新路径规划算法
下面是一个使用Python实现基于TD3(Twin Delayed Deep Deterministic Policy Gradient)算法来实时更新路径规划算法的三个参数(sigma0,rho0 和 theta)的示例代码。该算法将依据障碍物环境进行优化。 实现思路 环境定义…...
pytorch实现半监督学习
半监督学习(Semi-Supervised Learning,SSL)结合了有监督学习和无监督学习的特点,通常用于部分数据有标签、部分数据无标签的场景。其主要步骤如下: 1. 数据准备 有标签数据(Labeled Data)&…...
我的毕设之路:(2)系统类型的论文写法
一般先进行毕设的设计与实现,再在现成毕设基础上进行描述形成文档,那么论文也就成形了。 1 需求分析:毕业设计根据开题报告和要求进行需求分析和功能确定,区分贴合主题的主要功能和拓展功能能,删除偏离无关紧要的功能…...
LosslessScaling-学习版[steam价值30元的游戏无损放大/补帧工具]
LosslessScaling 链接:https://pan.xunlei.com/s/VOHc-yZBgwBOoqtdZAv114ZTA1?pwdxiih# 解压后运行"A-绿化-解压后运行我.cmd"...
concurrent.futures.Future对象详解:利用线程池与进程池实现异步操作
concurrent.futures.Future对象详解:利用线程池与进程池实现异步操作 一、前言二、使用线程池三、使用进程池四、注意事项五、结语 一、前言 在现代编程中,异步操作已成为提升程序性能和响应速度的关键手段。Python的concurrent.futures模块为此提供了强…...
StarRocks 安装部署
StarRocks 安装部署 StarRocks端口: 官方《配置检查》有服务端口详细描述: https://docs.starrocks.io/zh/docs/deployment/environment_configurations/ StarRocks架构:https://docs.starrocks.io/zh/docs/introduction/Architecture/ Sta…...
Python Matplotlib库:从入门到精通
Python Matplotlib库:从入门到精通 在数据分析和科学计算领域,可视化是一项至关重要的技能。Matplotlib作为Python中最流行的绘图库之一,为我们提供了强大的绘图功能。本文将带你从Matplotlib的基础开始,逐步掌握其高级用法&…...
线程概念、操作
一、背景知识 1、地址空间进一步理解 在父子进程对同一变量进行修改时发生写时拷贝,这时候拷贝的基本单位是4KB,会将该变量所在的页框全拷贝一份,这是因为修改该变量很有可能会修改其周围的变量(局部性原理)…...
【PySide6拓展】QSoundEffect
文章目录 【PySide6拓展】QSoundEffect 音效播放类**基本概念****什么是 QSoundEffect?****QSoundEffect 的特点****安装 PySide6** **如何使用 QSoundEffect?****1. 播放音效****示例代码:播放音效** **代码解析****QSoundEffect 的高级用法…...
33【脚本解析语言】
脚本语言也叫解析语言 脚本一词,相信很多人都听过,那么什么是脚本语言,我们在开发时有一个调试功能,但是发布版是需要编译执行的,体积比较大,同时这使得我们每次更新都需要重新编译,客户再…...
【Unity】 HTFramework框架(五十九)快速开发编辑器工具(Assembly Viewer + ILSpy)
更新日期:2025年1月23日。 Github源码:[点我获取源码] Gitee源码:[点我获取源码] 索引 开发编辑器工具MouseRayTarget焦点视角Collider线框Assembly Viewer搜索程序集ILSpy反编译程序集搜索GizmosElement类找到Gizmos菜单找到Gizmos窗口分析A…...
如何解决TikTok网络不稳定的问题
TikTok是目前全球最受欢迎的短视频平台之一,凭借其丰富多彩的内容和社交功能吸引了数以亿计的用户。然而,尽管TikTok在世界范围内的使用情况不断增长,但不少用户在使用过程中仍然会遇到网络不稳定的问题。无论是在观看视频时遇到缓冲…...
告别页面刷新!如何使用AJAX和FormData优化Web表单提交
系列文章目录 01-从零开始学 HTML:构建网页的基本框架与技巧 02-HTML常见文本标签解析:从基础到进阶的全面指南 03-HTML从入门到精通:链接与图像标签全解析 04-HTML 列表标签全解析:无序与有序列表的深度应用 05-HTML表格标签全面…...
WireShark4.4.2浏览器网络调试指南:数据统计(八)
概述 Wireshark 是一款功能强大的开源网络协议分析软件,被广泛应用于网络调试和数据分析。随着互联网的发展,以及网络安全问题日益严峻,了解如何使用 Wireshark进行浏览器网络调试显得尤为重要。最新的 Wireshark4.4.2 提供了更加强大的功能…...
Hypium+python鸿蒙原生自动化安装配置
Hypiumpython自动化搭建 文章目录 Python安装pip源配置HDC安装Hypium安装DevEco Testing Hypium插件安装及使用方法插件安装工程创建区域 Python安装 推荐从官网获取3.10版本,其他版本可能出现兼容性问题 Python下载地址 下载64/32bitwindows安装文件&am…...
2025创业思路和方向有哪些?
创业思路和方向是决定创业成功与否的关键因素。以下是一些基于找到的参考内容的创业思路和方向,旨在激发创业灵感: 一、技术创新与融合: 1、智能手机与云电视结合:开发集成智能手机功能的云电视,提供通讯、娱乐一体化体…...
docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...
【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...
dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...
C++.OpenGL (10/64)基础光照(Basic Lighting)
基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...
GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...
让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...
提升移动端网页调试效率:WebDebugX 与常见工具组合实践
在日常移动端开发中,网页调试始终是一个高频但又极具挑战的环节。尤其在面对 iOS 与 Android 的混合技术栈、各种设备差异化行为时,开发者迫切需要一套高效、可靠且跨平台的调试方案。过去,我们或多或少使用过 Chrome DevTools、Remote Debug…...
【WebSocket】SpringBoot项目中使用WebSocket
1. 导入坐标 如果springboot父工程没有加入websocket的起步依赖,添加它的坐标的时候需要带上版本号。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dep…...
动态规划-1035.不相交的线-力扣(LeetCode)
一、题目解析 光看题目要求和例图,感觉这题好麻烦,直线不能相交啊,每个数字只属于一条连线啊等等,但我们结合题目所给的信息和例图的内容,这不就是最长公共子序列吗?,我们把最长公共子序列连线起…...
Netty自定义协议解析
目录 自定义协议设计 实现消息解码器 实现消息编码器 自定义消息对象 配置ChannelPipeline Netty提供了强大的编解码器抽象基类,这些基类能够帮助开发者快速实现自定义协议的解析。 自定义协议设计 在实现自定义协议解析之前,需要明确协议的具体格式。例如,一个简单的…...
