基础篇
springboot 项目
- 比较与传统的spring项目
- 无需配置web.xml,applicationContext,springmvc.xml 等诸多配置文件
- 依赖引入简化,以maven为例,只需指明 parent,这是为了继承 parent 的 pom 配置,然后引入starter-web 依赖即可
- 自动配置 bean,如数据库 sqlSessionFactory bean 。
- 创建方式
- IDE 使用 spring initializer
- 手动创建 Maven 项目,再设置parent,引入 starter-web 依赖;创建 resources 目录,及 application.yml(properties)
application.yml(properties)
-
properties 示例
server.address= server.servlet.context-path=/start
-
yml 示例
server: address: 8080 servlet: context-path: /start array: - java - kotlin - groovy - yml array configuration
-
Detail
- 读取 Properties 详细见 Demo-Day1 Project
-
读取 application.yml(properties)
- 示例
@ConfigurationProperties(prefix = "server") public class ServerInfo { //nick_name, nick-name 都对应 驼峰式 nickName private Integer address; private String[] array; //getters //setters 必须要有,因为 @ConfigurationProperties 使用 setter 注入 }
引入 Mybatis
- 传统 spring
- mybatis 依赖
- mybatis-spring 整合依赖
- 配置bean:SqlSessionFactoryBean、MapperScannerConfigurer、DataSource
- 数据库驱动依赖
- 数据库连接配置
- spring-boot
- mybatis-spring-boot-starter 依赖(如web starter 会自动注入所需 bean)
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency>
- 数据库驱动依赖
<dependency> <groupId>com.oracle.database.jdbc</groupId> <artifactId>ojdbc11</artifactId> <scope>runtime</scope> </dependency>
- 数据库连接配置
spring: datasource: driver-class-name: oracle.jdbc.OracleDriver name: rlyslata-oracle username: rlyslata password: rlyslata url: jdbc:oracle:thin:@//127.0.0.1:1521/rlyslatapdb mybatis: configuration: # 开启下划线与驼峰命名映射 map-underscore-to-camel-case: true
- 编写 Mapper 接口
/** * @function mybatis 会将 **@mapper** 注解的接口实现 */ @Mapper public interface UserMapper { @Select("select * from user_tab where id = #{id}") public User findbyId(Integer id); }
Bean
-
扫描路径
- Spring
- 标签:<context:component-scan base-package=“com.itheima”/>
- 注解:@ComponentScan(basePackages = “com.itheima”)
- Springboot
//启动类 springboot 默认扫描此启动类所在的包及其子包,其他地方需使用 @ComponentScan @SpringBootApplication public class SpringbootQuickstartApplication { public static void main(String[] args) { SpringApplication.run(SpringbootQuickstartApplication.class, args); } } //@SpringBootApplication 是 @SpringBootConfiguration, @EnableAutoConfiguration,@ComponentScan 的组合
- Spring
-
注册
第三方包中对象注入IOC容器,不能用 @Component,@Repository,@Service,@Controller;
- Bean
//如果要注册的bean对象来自于第三方(不是自定义的),是无法用 @Component 及衍生注解声明bean的 //标识为配置类 @Configuration public class CommonConfig { @Bean public Country country(){ return new Country(); } //@Bean("name") 可以指定名字,默认是方法名,context.getBean("name") 获取 IOC 中的 bean @Bean public Province province(Country country){ System.out.println("如果 @Bean 方法内要用到已存在的Bean, IOC 会自动注入" + country.toString()); return new Province(); } }
- @Import
- 使用@Import(.class,.class,…)
//启动类设置注解,即使不在默认扫描路径,也能注入 @Import(com.xgp.config.CommonConfig.class)
- 实现 InspectSelector 接口
public class CommonImportSelectorImpl implements ImportSelector{ //使用方式:在启动类 @Import(CommonImportSelectorImpl.class) @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { /** * @example: return new String[]{"com.xgp.springbootmybatis.config.CommonConfig"}; * @fact: 改写为从配置文件读入 */ List<String> imports = new ArrayList<>(); InputStream ins = CommonImportSelectorImpl.class.getClassLoader().getResourceAsStream("common.imports"); BufferedReader br = new BufferedReader(new InputStreamReader(ins)); String line = null; try { while((line = br.readLine()) != null) { imports.add(line); } } catch (IOException e) { e.printStackTrace(); } finally{ try { br.close(); } catch (IOException e) { e.printStackTrace(); } } return imports.toArray(new String[0]); } }
- 注册条件
- @ConditionalOnProperty 配置文件中存在对应的属性,才声明该bean
- @ConditionalOnMissingBean 当不存在当前类型的bean时,才声明该bean
- @ConditionalOnClass 当前环境存在指定的这个类时,才声明该bean
- Bean
-
自动配置原理
Spring Boot 有一个实现 ImportSelector 接口的类,将这个类 @Import 引入实现容器对象注入,然后使用条件注入Bean,例如当 starter 的某个类存在时注入,这样只要引入 starter 依赖,那么当spring boot 启动时就实现了自动配置的效果。追踪@EnableAutoConfiguration
注解,可以发现 spring boot 会读取一个 .imports 文件,里面是要注入ImportSelectot.clss 或 普通配置类.class。
实战篇-Backend
Http
-
form-data、x-www-form-urlencode、raw
在 HTTP 协议中,请求体(Request Body)是用来传递请求参数或数据给服务器的一部分。常见的请求体数据格式包括 form-data、x-www-form-urlencoded 和 raw。form-data:这种格式通常用于上传文件或发送包含二进制数据的请求。它将请求参数和对应的值作为一个个独立的部分进行传输,每个部分都有自己的标识和内容类型。这种格式适用于文件上传、多项数据提交等场景。
x-www-form-urlencoded:这是一种常见的请求体格式,也是 HTML 表单默认的提交方式。在这种格式下,请求参数会被编码为键值对的形式,并使用 URL 编码进行传递。参数之间使用 & 符号分隔,键值对之间使用 = 符号分隔。这种格式适用于普通的表单提交,如用户登录、搜索等。
raw:这种格式允许你直接在请求体中发送原始的数据,可以是文本、JSON、XML 等。你可以选择在请求中指定对应的内容类型(Content-Type),例如 application/json 或 text/xml。这种格式适用于需要自定义数据格式或传递特殊类型数据的场景。
总结起来,form-data 适用于上传文件或发送二进制数据,x-www-form-urlencoded 适用于普通表单提交,raw 则允许你自定义数据格式或发送特殊类型的数据。具体选择哪种格式取决于你的具体需求和服务器接口的要求。
-
Spring MVC 请求处理流程
在 Spring MVC 中,视图渲染是在请求处理程序(Controller 层的方法)执行完毕后开始的。具体的流程如下:
-
请求到达 DispatcherServlet: 当一个请求到达 Spring MVC 应用的 DispatcherServlet 时,DispatcherServlet 会根据配置的处理器映射器(HandlerMapping)找到对应的请求处理程序(Controller 层的方法)。
-
处理器适配器调用请求处理程序: 在找到请求处理程序后,DispatcherServlet 会将请求委托给处理器适配器(HandlerAdapter)来调用对应的请求处理程序。处理器适配器会执行请求处理程序的方法,并返回一个 ModelAndView 对象,其中包含了模型数据和视图名称等信息。
-
视图解析器解析视图名称: DispatcherServlet 在得到 ModelAndView 对象后,会使用配置的视图解析器(ViewResolver)来解析视图名称。视图解析器根据视图名称的规则和配置,确定要使用哪个视图来渲染响应。
-
视图渲染: 一旦视图解析器确定了要使用的视图,DispatcherServlet 将视图和模型数据传递给该视图进行渲染。视图可以是 JSP、Thymeleaf 模板、Freemarker 模板等,根据配置的模板引擎来决定。
-
渲染结果返回给客户端: 视图渲染完成后,DispatcherServlet 将渲染结果(HTML、XML 等)作为响应返回给客户端。
-
Git
- git init
- git config --global user.name/email
配置name,email 才能 commit - git add
- git commit
- git commit --amend
追加修改到上一次提交中 - git restore file
从 stage 恢复 work directory 文件- –staged 撤销 file 的 stage(git add 命令)
- git merge
- git checkout -b
- git checkout commit files
不移动branch,或HEAD,修改 stage 区域 , work directory 区域的 files 版本 ,与 git reset --hard 一样会覆盖 work directory 区域的修改,无法找回这些修改,不安全。 - git reset commit/branch/tag
- –soft 只移动 HEAD 和 branch 到指定的版本位置;history 与 stage 内容不一致,stage 与 work directory 一致。
- –mixed(default) 还会修改 stage(index) 区;history 与 stage 内容一致,stage 与 work directory 不一致。
- –hard 还会丢弃(覆盖) work directory 区的修改;history 与 stage 内容一致,stage 与 work directory 一致。
- git reset commit files
- 无 commit 默认为 HEAD,只针对列出的文件,不改变HEAD 与 Branch 的版本位置,也分 --sort, --mixed, --hard 。
- git revert commit/branch/tag
与 revert 字面意思相同,撤销指定的 commit,然后提交一个新的commit,即前进一个版本。 - git revert -m number commit/branch/tag
用于撤销错误的 merge,number 为 git log 查看到的待要保留的 mainline 分支的次序(1或2)-
撤销 merge :如之前合并了 A, B 分支,一段时间后,然后发现 B 分支有问题,要取消合并 B,保留 A;若 git log 发现 merge commit 中顺序为 A,B,那么命令为 git revert -m 1 commit,commit是 B 的最后一个 commit (B-last)。
-
重新合并分支
撤销B后,A 小组继续提交,最新提交是 A-latest,B 小组也在不断提交修改问题,当B修改完成后最新提交时 B-latest,要再进行合并。
- 直接合并 A-latest,B-latest 是不对的。因为这样合并,git 只会将 B-last 之后的 commit 与 A-latest 合并,相当于丢弃了B-last 及之前的 commit, 这是 git revert -m 的一个值得注意的地方。
- 正确做法:假设上次 revert 生成的 commit 为 revert-commit , 此时应 revert 它,现在生成的新的 commit 是 之前合并的 merge commit 再加上A后序的 commit(遮住 revert-commit 再看版本链就清晰了),即包含了B-latest 之前的 commit,此时再与 B-latest 合并就对了。
-
- git stash
将当前 stage、work directory 区清空。并存到存储区。随时可以恢复。 - git worktree
建立另一个 work directory,并于一个分支关联,也能提交到与原 work directory 的同一个本地仓库。免于频繁切换分支。
Dependency
- org.projectlombok:lombok
通过注解免于手写contructor、setter、getter - org.springframework.boot
-
spring-boot-starter-validation
用于校验用户输入参数
-
mybatis-spring-boot-starter
引入 mybatis,作为starter,另一个目的是实现自动配置 mybatis 所需的Bean。mybatis 封装了数据库操作,如获取数据库连接对象、连接池、数据操作。是spring jpa 的替代。
-
spring-boot-starter-test
测试依赖。
-
- com.auth0:java-jwt
- token 生成
//接收业务数据,生成token并返回 public static String genToken(Map<String, Object> claims) { return JWT.create() .withClaim("claims", claims)//承载的数据, 勿要放入保密信息 .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 12)) .sign(Algorithm.HMAC256(KEY)); }
- token 验证
//接收token,验证token,并返回业务数据 public static Map<String, Object> parseToken(String token) { return JWT.require(Algorithm.HMAC256(KEY)) .build() .verify(token) .getClaim("claims") .asMap(); }
- 拦截器(pipes & filters architecture feature)
-
定义拦截器(Controller之前:preHandle、Controller之后视图渲染之前:postHandle,视图渲染之后:afterCompletion)
@Component public class LoginInterceptor implements HandlerInterceptor{ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //令牌验证,必须携带正确的 Authorization 请求头参数,才会放行 String token = request.getHeader("Authorization"); try { // 存在线程局部变量中,供后序访问,ThreadLocal 存储的值是线程安全的 ThreadLocalUtil.set(userInfo); return true; } catch (Exception e) { response.setStatus(401); return false; } } }
-
注册拦截器
@Configuration @AllArgsConstructor public class WebConfig implements WebMvcConfigurer{ private final LoginInterceptor loginInterceptor; @Override public void addInterceptors(@NonNull InterceptorRegistry registry) { // 注册拦截器,并排除login、register if(loginInterceptor != null ) registry.addInterceptor(loginInterceptor).excludePathPatterns("/user/login", "/user/register"); } }
-
ThreadLocal 优化
在 Interceptor 中解析了 token,获得了 token 承载的数据,可以存在 ThreadLocal 变量中,与后序Controller、Service、DAO 共享;ThreadLocal 存储的值是线程安全的。
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { // 防止内存泄漏 ThreadLocalUtil.remove(); }
-
- token 生成
- com.github.pagehelper:pagehelper-spring-boot-starter
- 分页器使用例子
public PageBean<Article> getArticleList(Integer pageNum, Integer pageSize, Integer categoryId, String state) { PageBean<Article> pb = new PageBean<>(); //开启分页 PageMethod.startPage(pageNum, pageSize);//Page<Article>, Page extends ArrayList,但是经测试 Page.getTotal 并不是当前页的数据条数,用 Page.size 方法 //查询 Map<String, Object> userinfo = ThreadLocalUtil.get(); Integer userid = (Integer)userinfo.get("id"); List<Article> articleList = articleMapper.findArticleList(categoryId, state, userid); //set 属性 pb.setItems(articleList); pb.setTotal((long)articleList.size()); return pb; }
- 分页器使用例子
- org.springframework.boot:spring-boot-starter-data-redis
- 引入 redis 存储 token 副本,当用户重新登陆后,旧 token 失效(比较 redis 中的副本 与 请求头 Authorization,不同则拒绝访问)
- Springboot redis 配置
spring: data: redis: # redis default port port: 6379 host: 127.0.0.1
- redis 直接启动
Redis: 启动:redis-server.exe redis.windows.conf
- redis 以服务启动
安装为服务:redis-server --service-install redis.windows.conf 启动服务:redis-server --service-start 停止服务:redis-server --service-stop 卸载服务:redis-server --service-uninstall 默认端口:6379
- io.minio:minio
- minio 提供对象存储服务(OSS),可以用来存文件,文字,音频等
- springboot 配置 minio
minio: endpoint: http://127.0.0.1:9000 accessKey: 7TENM8YXFh1am23pJRPm secretKey: efhXHOeDcrdpi2yAsCQwiasDLxvzzzEI8ueHMV7I bucketName: big-event
- 默认端口是 9000,端口错了会提示端口错误(…correct API port)
- Minio Java api 十分容易上手,全是基于 Builder 设计模式
- Web 管理地址 http://127.0.0.1:9000
- 一个对象 url 链接存活时间是 0~7 天(官方约定),但是可以通过将 bucket 访问模式设为 public,就可以以 http://host:port/bucketname/objectname 来访问
SpringBoot Deploy
- 打包格式 JAR 因为 SpringBoot 内置了 Web Server
- 项目配置(优先级1~4依次提高)
- 项目内部 Application.yml(properties)
- Jar 所在目录下的Application.yml(properties)
- 操作系统 env variable 格式如 server.port
- 命令参数 java -jar project.jar --server.port=8080
- 项目内部 Application.yml 组织形式
- 单文件
- 单环境 默认
- 多环境 (— 是分隔符)
# 多环境信息指定一个生效的环境,这里是dev生效 spring: profiles: activate: dev # 公共配置放在第一个 --- 前,这里写公共配置,与特定 profile 冲突时,特定 profile 优先级更高 --- # 开发 dev spring: config: activate: on-profile: dev # 在这里写 dev 配置 --- # 测试 test pring: config: activate: on-profile: test # 在这里写 test 配置 --- # 生产 prod pring: config: activate: on-profile: prod # 在这里写 prod 配置
- 多文件
-
每个文件一个Profile
方法与单文件多环境一样,只不过把公共配置(主环境)、其它环境分别放到不同的文件
- application.yml
# 多环境信息指定一个生效的环境,这里是dev生效 spring: profiles: activate: dev # 公共配置放在第一个 --- 前,这里写公共配置,与特定 profile 冲突时,特定 profile 优先级更高
- application-dev.yml
# 开发 dev spring: config: activate: on-profile: dev # 在这里写 dev 配置
- application-test.yml
# 测试 test spring: config: activate: on-profile: test # 在这里写 test 配置
- application-prod.yml
# 生产 prod pring: config: activate: on-profile: prod # 在这里写 prod 配置
- application.yml
-
多个文件组成一个 group 成为一个Profile
- application.yml
spring: profiles: group: "dev": devDB,devSelf,devServer "test": testDB,testSelf,testServer # 表示 dev group 生效 activate: dev
- application-devDB.yml 如同单文件单环境,注意文件命名
- application-devSelf.yml 如同单文件单环境,注意文件命名
- application-devServer.yml 如同单文件单环境,注意文件命名
- application.yml
-
- 单文件
Annotations
- org.springframework.web.bind.annotation
- Param Acception
- @RequestBody 只接受 raw 类型的参数, 如果是实体类,可以直接以实体类作形参,否则用 Map<String,String>
- @PathVariable 接收URL模版参数,/path/{param1}/{param2}, 对应 @PathVariable(“param1”),@PathVariable(“param2”)
- @RequestParam 接收URL查询参数,/path?param1=value1¶m2=value2,对应 @RequestParam(“param1”) type v1, @RequestParam(“param1”) type v2
- @RequestHeader 接收请求头参数,@RequestHeader(“param name”)
- @RestController 注解Controller
- Router
- @RequestMapping
- @GetMapping
- @PostMapping
- @PutMapping
- @Configuration 标记为配置类,当一个类被标记为 @Configuration 时,它被视为一个特殊的类,Spring Boot 在启动过程中会扫描并加载这些配置类,并根据配置类中的定义创建相应的 Bean 实例。
- @Bean 注释方法,意味着spring boot 会用此方法生成一个实例,并注入容器
- @Component
- Service 专门标记Service实现类
- Controller 专门标记Controller
- Param Acception
- org.springframework.validation.annotation
- @Validated: 标记类为需要验证的类
- @NotNull:用于验证字段值不为null。
- @NotEmpty:用于验证字符串、集合或数组字段值不为空。
- @NotBlank:用于验证字符串字段值不为空且不包含空格。
- @Size:用于验证字符串、集合或数组字段值的大小是否在指定范围内。
- @Min:用于验证数字字段值的最小值。
- @Max:用于验证数字字段值的最大值。
- @Pattern:用于验证字符串字段值是否匹配指定的正则表达式。
- @Email:用于验证字符串字段值是否符合电子邮件地址的格式。
- @URL:用于验证URL
- @Valid:用于标记嵌套对象需要进行验证(意思是嵌套对象类中使用了 validation 注解)。
- @AssertTrue:用于验证布尔类型字段值为true。
- @AssertFalse:用于验证布尔类型字段值为false
- 分组校验
@Data public class Category { @NotNull(groups = {InnerCategoryUpdateGroup.class}) private Integer id;//主键ID @NotEmpty private String categoryName;//分类名称 @NotEmpty private String categoryAlias;//分类别名 private Integer createUser;//创建人ID @JsonFormat(pattern = "yy-mm-dd HH:mm:ss") private LocalDateTime createTime;//创建时间 @JsonFormat(pattern = "yy-mm-dd HH:mm:ss") private LocalDateTime updateTime;//更新时间 /** * 当实体类作为不同接口参数,而校验规则不同时,需使用分组校验 * 实体类: * 每个内部接口表示一个校验分组,分组可以继承。 * 校验注解不指定 groups 时,默认为 Default 分组 * 接口参数注解(@Valid 没有 value 值,只能用 @Validated): * @Validated(InnerInterface.class) */ public interface InnerCategoryAddGroup extends Default{ } public interface InnerCategoryUpdateGroup extends Default{ } }
- 自定义校验
- 注解定义
@Documented @Constraint(validatedBy = {StateValidation.class}) @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface State { String message() default "{State 只能是 '已发布' 或 '草稿'}"; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { }; }
- 校验类定义
public class StateValidation implements ConstraintValidator<State, String>{ @Override public boolean isValid(String value, ConstraintValidatorContext context) { if(value == null) { return false; } return value.equals("已发布") || value.equals("草稿"); } }
- 注解定义
- 分组校验
- org.apache.ibatis.annotations
如果参数是实体类对象 obj,注意 sql 语句中不能使用#{obj.attribute}- @Select
- @Insert
- @Delete
- @Update
- lombok
- @Data:
Generate Getters and Setters - @AllArgsContructor, @RequestArgsContructor, @NoArgsContructor:
Generate Contructors
- @Data:
- com.fasterxml.jackson.annotation
- @JsonIgnore: 注释 pojo 类的属性,让 spring 在转换为 JSON 时忽略此属性
- @JsonFormat: 注释 pojo 类的属性,让 spring 在转换为 JSON 时,指定格式,如时间表示形式。
Postman
-
本地代理(vpn)配置API端口走直连,否则会导致 postman 请求被错误转发到代理端口
-
vscode 别用 postman 插件,经常卡崩溃 vscode。
-
collection 可以设置 pre-request-script,会应用到 collection 中所有 request
pm.request.addHeader("Authorization:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9eyJjbGFpbXMiOnsiaWQiOjYsInVzZXJuYW1lIjoicmx5c2xhdGEteHh4In0sImV4cCI6MTcwNjk5MjU2OX0.i3V29SUBVcWtvfZIuGL4X55khWCl699of0sjY1VH2A4")