狂神说springboot课堂笔记

狂神视频连接:和原文笔记https://www.cnblogs.com/th11/p/15208081.html

第一个SpringBoot程序

到底多么简单:

  • jdk 1.8
  • maven 3.6.1
  • SpringBoot 最新版
  • IDEA

官方:提供了一个快速生成的网站!IDEA集成了这个网站!(建议IDEA创建)

原理初探

自动配置

pom.xml

  • spring-boot-description:核心依赖在父工程中!
  • 我们在写或者引入一些SpringBoot依赖的时候,不需要指定版本,就因为有这些版本仓库

启动器

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
  • spring-boot-starter-web,他就会帮我们自动导入web环境所有依赖!
  • springboot会将所有的功能场景,都变成一个个的启动器
  • 我们要使用什么功能,就只需要找到对应的启动器就可以了starter

主程序

//SpringBootApplication:标注这个类是一个springboot的应用	:启动类下的所有资源被导入
@SpringBootApplication
public class HelloworldApplication {

    public static void main(String[] args) {
        //将springboot应用启动
        SpringApplication.run(HelloworldApplication.class, args);
    }

}

注解

    @SpringBootConfiguration	//springboot的配置
        @Configuration		//spring配置类
    	@Component 			//说明这也是一个spring的组件
    
    @EnableAutoConfiguration	//自动配置
    	@AutoConfigurationPackage		//自动配置包
    		@Import({AutoConfigurationPackages.Registrar.class})	//自动配置`包注册`
    	@Import({AutoConfigurationImportSelector.class})
    
    //获取所有的配置
    List<String> configurations = getCandidateConfigurations(annotationMetadata,attributes);

获得候选的配置

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    //这里的getSpringFactoriesLoaderFactoryClass()方法
    //返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfiguration
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
    return configurations;
}

META-INF/spring.factories:自动配置的核心文件

在这里插入图片描述

Properties properties = PropertiesLoaderUtils.loadProperties(resource);
所有资源加载到配置中!

**结论:**springboot所有自动配置都是在启动的时候扫描并加载:spring.factories所有的自动配置类都在这里面,但不一定生效,要判断条件是否成立,只要成立,只要导入了对应的 start,就有对应的启动器,有了启动器,我们自动装配就会生效,然后就配置成功!

  1. springboot在启动的时候,从类路径下META-INF/spring.factories获取指定的值;
  2. 将这些自动配置的类导入容器,自动配置就会生效,帮我进行自动配置!
  3. 以前我们需要自动配置的东西,现在springboot帮我们做了!
  4. 整合javaEE,解决方案和自动配置的东西都在spring-boot-autoconfigure-2.6.5jar这个包下
  5. 它会把所有需要导入的组件,以类名的方式返回,这些组件就会被添加到容器;
  6. 容器中也会存在非常多的xxxAutoConfiguration的文件(@Bean),就是这些类给容器中导入了这个场景需要的所有组件;并自动配置,@Configuration,JavaConfig!
  7. 有了自动配置类,免去了我们的手动编写配置文件的工作!

SpringApplication

这个类主要做了以下四件事情:

1、推断应用的类型是普通的项目还是Web项目

2、查找并加载所有可用初始化器 , 设置到initializers属性中

3、找出所有的应用程序监听器,设置到listeners属性中

4、推断并设置main方法的定义类,找到运行的主类

查看构造器:

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.setInitializers(this.getSpringFactoriesInstances();
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

run方法流程分析

在这里插入图片描述

SpringBoot配置

yaml可以直接给实体类赋值!

# k=v   properties 只能保存键值对!
# 对空格的要求十分严格!
# 普通的key-value
# 注入到我们的配置类中!
name: qinjiang

# 对象
student:
  name: qinjiang
  age: 3

# 行内写法
student: {name: qinjiang,age: 3}

# 数组
pets:
  - cat
  - dog
  - pig

pets: [cat,dog,pig]

在这里插入图片描述

在这里插入图片描述

配置文件占位符

这里有微调,否则旺财输出为乱码

person:
  name: qinjiang${random.uuid} # 随机uuid
  age: ${random.int}  # 随机int
  happy: false
  birth: 2023/13/14
  maps: {k1: v1,k2: v2}
  hello: LOVE
  lists:
    - code
    - music
    - girl
  dog:
    name: "${person.hello:hello}_旺财"
    age: 3
server:
  port: 8081
spring:
  profiles:
    active: dev

---
server:
  port: 8082
spring:
  profiles: dev

---
server:
  port: 8083
spring:
  profiles: test
//表示这是一个配置类
@Configuration(
    proxyBeanMethods = false
)
//自动配置属性:ServerProperties
@EnableConfigurationProperties({ServerProperties.class})
//Spring的底层注解:根据不同的条件,来判断当前配置或者类是否生效!
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({CharacterEncodingFilter.class})
@ConditionalOnProperty(
    prefix = "server.servlet.encoding",
    value = {"enabled"},
    matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {........

精髓

1、SpringBoot启动会加载大量的自动配置类

2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;

3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)

4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;

# 配置文件到底能写什么 -----联系---spring.factories;

# 在我们这配置文件中能配置的东西,都存在一个规律
# xxxAutoConfiguration:默认值  xxxProperties 和 配置文件绑定,我们就可以使用自动义的配置了!

# 可以通过 debug: true 来查看,哪些自动配置类生效,哪些没有生效!
debug: true

Positive matches:(自动配置类启用的:正匹配)

Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)

Unconditional classes: (没有条件的类)

SpringBoot Web开发

jar : webapp

自动装配

springboot到底帮我们配置了什么?我们能不能进行修改?能修改哪些东西?能不能扩展?

  • xxxxAutoConfiguraion… 向容器中自动配置组件
  • xxxxproperties:自动配置类,装配配置文件中自定义的一些内容!

要解决的问题:

  • 导入静态资源…
  • 首页
  • jsp,模板引擎Thymeleaf
  • 装配扩展SpringMVC
  • 增删改查
  • 拦截器
  • 国际化!

静态资源

public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
        logger.debug("Default resource handling disabled");
    } else {
        this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
        this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
            registration.addResourceLocations(this.resourceProperties.getStaticLocations());
            if (this.servletContext != null) {
                ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
                registration.addResourceLocations(new Resource[]{resource});
            }

        });
    }
}

总结:

  1. 在springboot,我们可以使用以下方式处理静态资源
    • webjars localhost:8080/webjars/
    • public, static, /**, resources localhost:8080/
  2. 优先级:resources > static(默认) > public

模板引擎

结论:只要西药使用thymeleaf,只需要导入对应的依赖就可以了!我们将html放在我们的templates目录下即可!

public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";

MVC配置原理

package com.kuang.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Locale;

//如果你想diy一些定制化的功能,只要写这个组件,然后将它交给springboot,springboot就会帮我们自动装配!
//扩展 springmvc      dispatchservlet
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    //ViewResolver 实现了视图解析器接口的类,我们就可以把它看做视图解析器
    @Bean
    public ViewResolver myViewResolver(){
        return new MyViewResolver();
    }

    //自定义了一个自己的视图解析器  MyViewResolver
    public static class MyViewResolver implements ViewResolver{
        @Override
        public View resolveViewName(String s, Locale locale) throws Exception {
            return null;
        }
    }

}

在springboot中,有很多的xxx 帮助我们进行扩展配置,只要看见了这个东西,我们就要注意了!

**<font color='red'>狂神视频连接:和原文笔记</font>**[https://www.cnblogs.com/th11/p/15208081.html](https://www.cnblogs.com/th11/p/15208081.html)

## 第一个SpringBoot程序

到底多么简单:

- jdk	1.8
- maven    3.6.1
- SpringBoot   最新版
- IDEA

官方:提供了一个快速生成的网站!IDEA集成了这个网站!(建议IDEA创建)

## 原理初探

<font color='red'>自动配置</font>

> **pom.xml**

- spring-boot-description:核心依赖在父工程中!
- 我们在写或者引入一些SpringBoot依赖的时候,不需要指定版本,就因为有这些版本仓库

> **启动器**

​```xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
  • spring-boot-starter-web,他就会帮我们自动导入web环境所有依赖!
  • springboot会将所有的功能场景,都变成一个个的启动器
  • 我们要使用什么功能,就只需要找到对应的启动器就可以了starter

主程序

//SpringBootApplication:标注这个类是一个springboot的应用	:启动类下的所有资源被导入
@SpringBootApplication
public class HelloworldApplication {

    public static void main(String[] args) {
        //将springboot应用启动
        SpringApplication.run(HelloworldApplication.class, args);
    }

}

注解

    @SpringBootConfiguration	//springboot的配置
        @Configuration		//spring配置类
    	@Component 			//说明这也是一个spring的组件
    
    @EnableAutoConfiguration	//自动配置
    	@AutoConfigurationPackage		//自动配置包
    		@Import({AutoConfigurationPackages.Registrar.class})	//自动配置`包注册`
    	@Import({AutoConfigurationImportSelector.class})
    
    //获取所有的配置
    List<String> configurations = getCandidateConfigurations(annotationMetadata,attributes);

获得候选的配置

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    //这里的getSpringFactoriesLoaderFactoryClass()方法
    //返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfiguration
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
    return configurations;
}

META-INF/spring.factories:自动配置的核心文件

在这里插入图片描述

Properties properties = PropertiesLoaderUtils.loadProperties(resource);
所有资源加载到配置中!

**结论:**springboot所有自动配置都是在启动的时候扫描并加载:spring.factories所有的自动配置类都在这里面,但不一定生效,要判断条件是否成立,只要成立,只要导入了对应的 start,就有对应的启动器,有了启动器,我们自动装配就会生效,然后就配置成功!

  1. springboot在启动的时候,从类路径下META-INF/spring.factories获取指定的值;
  2. 将这些自动配置的类导入容器,自动配置就会生效,帮我进行自动配置!
  3. 以前我们需要自动配置的东西,现在springboot帮我们做了!
  4. 整合javaEE,解决方案和自动配置的东西都在spring-boot-autoconfigure-2.6.5jar这个包下
  5. 它会把所有需要导入的组件,以类名的方式返回,这些组件就会被添加到容器;
  6. 容器中也会存在非常多的xxxAutoConfiguration的文件(@Bean),就是这些类给容器中导入了这个场景需要的所有组件;并自动配置,@Configuration,JavaConfig!
  7. 有了自动配置类,免去了我们的手动编写配置文件的工作!

SpringApplication

这个类主要做了以下四件事情:

1、推断应用的类型是普通的项目还是Web项目

2、查找并加载所有可用初始化器 , 设置到initializers属性中

3、找出所有的应用程序监听器,设置到listeners属性中

4、推断并设置main方法的定义类,找到运行的主类

查看构造器:

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.setInitializers(this.getSpringFactoriesInstances();
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

run方法流程分析

在这里插入图片描述

SpringBoot配置

yaml可以直接给实体类赋值!

# k=v   properties 只能保存键值对!
# 对空格的要求十分严格!
# 普通的key-value
# 注入到我们的配置类中!
name: qinjiang

# 对象
student:
  name: qinjiang
  age: 3

# 行内写法
student: {name: qinjiang,age: 3}

# 数组
pets:
  - cat
  - dog
  - pig

pets: [cat,dog,pig]

在这里插入图片描述

在这里插入图片描述

配置文件占位符

这里有微调,否则旺财输出为乱码

person:
  name: qinjiang${random.uuid} # 随机uuid
  age: ${random.int}  # 随机int
  happy: false
  birth: 2023/13/14
  maps: {k1: v1,k2: v2}
  hello: LOVE
  lists:
    - code
    - music
    - girl
  dog:
    name: "${person.hello:hello}_旺财"
    age: 3
server:
  port: 8081
spring:
  profiles:
    active: dev

---
server:
  port: 8082
spring:
  profiles: dev

---
server:
  port: 8083
spring:
  profiles: test
//表示这是一个配置类
@Configuration(
    proxyBeanMethods = false
)
//自动配置属性:ServerProperties
@EnableConfigurationProperties({ServerProperties.class})
//Spring的底层注解:根据不同的条件,来判断当前配置或者类是否生效!
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({CharacterEncodingFilter.class})
@ConditionalOnProperty(
    prefix = "server.servlet.encoding",
    value = {"enabled"},
    matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {........

精髓

1、SpringBoot启动会加载大量的自动配置类

2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;

3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)

4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;

# 配置文件到底能写什么 -----联系---spring.factories;

# 在我们这配置文件中能配置的东西,都存在一个规律
# xxxAutoConfiguration:默认值  xxxProperties 和 配置文件绑定,我们就可以使用自动义的配置了!

# 可以通过 debug: true 来查看,哪些自动配置类生效,哪些没有生效!
debug: true

Positive matches:(自动配置类启用的:正匹配)

Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)

Unconditional classes: (没有条件的类)

SpringBoot Web开发

jar : webapp

自动装配

springboot到底帮我们配置了什么?我们能不能进行修改?能修改哪些东西?能不能扩展?

  • xxxxAutoConfiguraion… 向容器中自动配置组件
  • xxxxproperties:自动配置类,装配配置文件中自定义的一些内容!

要解决的问题:

  • 导入静态资源…
  • 首页
  • jsp,模板引擎Thymeleaf
  • 装配扩展SpringMVC
  • 增删改查
  • 拦截器
  • 国际化!

静态资源

public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
        logger.debug("Default resource handling disabled");
    } else {
        this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
        this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
            registration.addResourceLocations(this.resourceProperties.getStaticLocations());
            if (this.servletContext != null) {
                ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
                registration.addResourceLocations(new Resource[]{resource});
            }

        });
    }
}

总结:

  1. 在springboot,我们可以使用以下方式处理静态资源
    • webjars localhost:8080/webjars/
    • public, static, /**, resources localhost:8080/
  2. 优先级:resources > static(默认) > public

模板引擎

结论:只要西药使用thymeleaf,只需要导入对应的依赖就可以了!我们将html放在我们的templates目录下即可!

public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";

MVC配置原理

package com.kuang.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Locale;

//如果你想diy一些定制化的功能,只要写这个组件,然后将它交给springboot,springboot就会帮我们自动装配!
//扩展 springmvc      dispatchservlet
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    //ViewResolver 实现了视图解析器接口的类,我们就可以把它看做视图解析器
    @Bean
    public ViewResolver myViewResolver(){
        return new MyViewResolver();
    }

    //自定义了一个自己的视图解析器  MyViewResolver
    public static class MyViewResolver implements ViewResolver{
        @Override
        public View resolveViewName(String s, Locale locale) throws Exception {
            return null;
        }
    }

}

在springboot中,有很多的xxx 帮助我们进行扩展配置,只要看见了这个东西,我们就要注意了!

package com.kuang.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

//如果我们要扩展springmvc,官方建议我们这样去做!
@Configuration
@EnableWebMvc //这玩意就是导入一个类:DelegatingWebMvcConfiguration:从容器中获取所以的webmvcconfig
public class MyMvcConfig implements WebMvcConfigurer {

    //视图跳转
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/kuang").setViewName("test");
    }
}

员工管理系统

参考源码:https://blog.csdn.net/weixin_44517301/article/details/121050452

1、首页配置:

  1. 注意点,所有页面的静态资源都需要使用thymeleaf接管;
  2. url:@{}

2、页面国际化:

  1. 我们需要配置 i18n文件
  2. 我们如果需要在项目中进行按钮自动切换,我们需要自定义一个组件localeResolver
  3. 记得将自己写的组件配置到spring容器 @Bean
  4. #{}

3、登录 + 拦截器

在这里插入图片描述

4、员工列表展示

  1. 提取公共页面
    1. th:fragment="sidebar"
    2. th:replace="~{dashboard::topbar}"
    3. 如果要传递参数,可以直接使用 () 传参,接受判断即可!
  2. 列表循环展示5.添加员工

4.返回首页

6.CRUD 搞定

7.404

上周回顾

  • SpringBoot是什?
  • 微服务
  • HelloWorld~
  • 探究源码~ 自动装配原理~
  • 配置 yaml
  • 多文档环境切换
  • 静态资源映射
  • Thymeleaf th:xxx
  • SpringBoot 如何扩展MVC javaconfig~
  • 如何修改SpringBoot的默认配置~
  • CRUD
  • 国际化
  • 拦截器
  • 定制首页,错误页~

这周:

  • JDBC
  • Mybatis:重点
  • Shiro:安全:重点
  • Spring Security:安全:重点
  • 异步任务~,邮件发送,定时任务()
  • Swagger
  • Dubbo + Zookeeper

Data

mybatis

整合包

mybatis-spring-boot-starter

  1. 导入包
  2. 配置文件
  3. mybatis配置
  4. 编写SQL
  5. service层调用dao层
  6. controller调用service层

SpringSecurity(安全)

在web开发中,安全第一位!过滤器,拦截器 ~

功能性需求:否

做网站:安全用在什么时候考虑? 设计之初!

  • 漏洞,隐私泄露 ~
  • 架构一旦确定 ~

shiro、SpringSecurity:很像~除了类不一样,名字不一样;

认证,授权(VIP1,VIP2,VIP3)

  • 功能权限
  • 访问权限
  • 菜单权限
  • … 拦截器,过滤器:大量的原生代码~ 冗余

MVC—SPEING—SPRINGBOOT——框架思想

静态源码: https://gitee.com/ENNRIAAA/spring-security-material/repository/archive/master.zip

shiro

1.导入依赖

<dependencies>
    <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.4.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.slf4j/jcl-over-slf4j -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>1.7.21</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.21</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/log4j/log4j -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>

2.配置文件

log4j.properties

log4j.rootLogger=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n

# General Apache libraries
log4j.logger.org.apache=WARN

# Spring
log4j.logger.org.springframework=WARN

# Default Shiro logging
log4j.logger.org.apache.shiro=INFO

# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN

shiro.ini

[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz

3.HelloWorld

Quickstart源码

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */
 
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.ini.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.lang.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
 
/**
 * Simple Quickstart application showing how to use Shiro's API.
 *
 * @since 0.9 RC2
 */
public class Quickstart {
 
    /*通过LoggerFactory 工厂类,创建log4j对象,
    加上transient就不会被序列化,不会被持久化生命周期仅为内存中
    */
    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
 
 
    public static void main(String[] args) {
 
        // The easiest way to create a Shiro SecurityManager with configured
        // realms, users, roles and permissions is to use the simple INI config.
        // We'll do that by using a factory that can ingest a .ini file and
        // return a SecurityManager instance:
 
        // Use the shiro.ini file at the root of the classpath
        // (file: and url: prefixes load from files and urls respectively):
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();
 
        // for this simple example quickstart, make the SecurityManager
        // accessible as a JVM singleton.  Most applications wouldn't do this
        // and instead rely on their container configuration or web.xml for
        // webapps.  That is outside the scope of this simple quickstart, so
        // we'll just do the bare minimum so you can continue to get a feel
        // for things.
        SecurityUtils.setSecurityManager(securityManager);
 
        // Now that a simple Shiro environment is set up, let's see what you can do:
 
        // get the currently executing user:
        //1 获取当前执行的用户 (也不能叫用户,就是个Subject,类似于用户)
        //2 会自动获取与当前线程所匹配的相关基于用户的数据参数
        //	-----获取原因详解-----
        /*
         *通过SebjectUtils类调用getSubject()方法
         *
         *  通过ThreadContext直接获取到当前的Subject 有三种情况
         * 1 如果能获取,则return Subject对象
         * 2 如果没能获取到,调用buildSubject 创建Subject
         *   再进行尝试绑定,如果subject仍未空,则清空
         * 3 如果操作失败则抛出UnavaiLableSecurityManagerException异常
         *   这时候就需要检查配置或者Realm里出问题了
         */
        Subject currentUser = SecurityUtils.getSubject();
 
        // Do some stuff with a Session (no need for a web or EJB container!!!)
        //3 可以在其中插入获取session的事务,获取当前的会话
        //  此session是Shiro的特有的,提供了常规HttpSession的大部分功能
 
        Session session = currentUser.getSession();
        session.setAttribute("someKey", "aValue");
 
        //4 获取<K,V>的Value值,通过session.getAttribute,这就是角色 我们的用户(Subject)
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("Retrieved the correct value! [" + value + "]");
        }
        //5 OK.现在获取成功了,我们有一个角色了
        // let's login the current user so we can check against roles and permissions:
 
        /*6 接下来,就可以添加用Subject Session 做角色和权限检查之类的功能
          但是,现在只能对Shiro已知用户进行操作,虽然Subject是我们当前的用户,
          但是,一个很严重的问题,这个用户对于我们来说是一个匿名用户
          什么是匿名用户,我们知道有这么一个角色,但是我们不知道他是谁,他有什么权限
          直到他至少登录一次进行操作,我们才能得到用户具体对象权限
          isAuthenticated 收集Subject和凭证/令牌 并进行一个认证机制
          这里只是进行判断是否为第一次登陆或者没有设置RememberMe=true,即RememberMe=false
          如果为true则跳过login ,这里isAuthenticated原值为false
          在处理的时候会由login进行角色验证,并赋予authenticated true值*/
        if (!currentUser.isAuthenticated()) {
 
            /*实际上就是一个普通的字符串没有特殊意义,只是为了将令牌变成char[]数组来存储
             以及其他信息也赋予进去变成一个token*/
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            /*设置,RememberMe缓存,后面直接可以通过subject.getPrincipal来获取用户信息,
            但是,仅仅针对于非匿名用户且非敏感网页(如会员管理,后台和个人信息等),由我们来设置*/
            token.setRememberMe(true);
            //7 第一个分叉,尝试去登录
            try {
                currentUser.login(token);
                //会发生多种异常,从小往大排序,这期间,我们可以在login里加一些其他信息
 
                //为Subject异常,不对应等问题
            } catch (UnknownAccountException uae) {
                log.info("There is no user with username of " + token.getPrincipal());
                //令牌不对应等问题
            } catch (IncorrectCredentialsException ice) {
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
                //账号被锁定问题,登陆次数过多等
            } catch (LockedAccountException lae) {
                log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                        "Please contact your administrator to unlock it.");
            }
            // ... catch more exceptions here (maybe custom ones specific to your application?
            //其他异常或者大的异常,也可设置自己想处理的异常机制
            catch (AuthenticationException ae) {
                //unexpected condition?  error?
            }
        }//输出Subject信息
        //say who they are:
        //print their identifying principal (in this case, a username):
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
 
        //如果角色拥有权限,则输出权限信息
        //test a role:
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
            //很有意思的翻译:愿施瓦茨和你在一起
        } else {
            log.info("Hello, mere mortal.");
            //很有意思的翻译:你好,凡人(意思,你是个凡人,普通人)
        }
        /*isPermitted查看是否有权限,是否有其他特定的相关权限
           即判断已登陆用户是否具有某权限
           查看普通实例用户,这里不查看强大的用户(功能强大的实例级权限管理)
           翻译: 权限名:光剑使用权
           */
        //test a typed permission (not instance-level)
        if (currentUser.isPermitted("lightsaber:wield")) {
            log.info("You may use a lightsaber ring.  Use it wisely.");
            //很有意思的翻译:你可以使用光剑。明智地使用它
        } else {
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
            //很有意思的翻译:抱歉,光剑只供施瓦兹大师使用
        }
        //权限再判断,判断是否有更细粒度的权限
        //a (very powerful) Instance Level permission:
        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                    "Here are the keys - have fun!");
        } else {
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }
 
        //all done - log out!
        /*获取currentUser.login(),登出
        //查看源码分析,
        // 登出的底层实际上是清除session
        //最后再将
        this.session = null;
        this.principals = null;
        //将authenticated初始化即赋值false
        this.authenticated = false*/
 
        currentUser.logout();
        System.exit(0);
    }
}

Spring Secutiry~都有!

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Simple Quickstart application showing how to use Shiro's API.
 *
 * @since 0.9 RC2
 */
public class Quickstart {

    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);

    public static void main(String[] args) {

        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);

        // Now that a simple Shiro environment is set up, let's see what you can do:

        // get the currently executing user:
        //获取当前的用户对象 Subject
        Subject currentUser = SecurityUtils.getSubject();

        //通过当前用户拿到 session
        Session session = currentUser.getSession();
        session.setAttribute("someKey", "aValue");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("Subject>>>session[" + value + "]");
        }

        //判断当前用户是否被认证
        //Token:没有获取,直接设置令牌
        if (!currentUser.isAuthenticated()) {
            UsernamePasswordToken token = new UsernamePasswordToken("haojiahuo123", "vespa");
            token.setRememberMe(true);//设置记住我
            try {
                currentUser.login(token);//执行登录操作
            } catch (UnknownAccountException uae) {
                log.info("There is no user with username of " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) {
                log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                        "Please contact your administrator to unlock it.");
            }
            // ... catch more exceptions here (maybe custom ones specific to your application?
            catch (AuthenticationException ae) {
                //unexpected condition?  error?
            }
        }

        //say who they are:
        //print their identifying principal (in this case, a username):
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

        //test a role:
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }
        //粗粒度
        //test a typed permission (not instance-level)
        //
        if (currentUser.isPermitted("lightsaber:wield")) {
            log.info("You may use a lightsaber ring.  Use it wisely.");
        } else {
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
        }
        //细粒度
        //a (very powerful) Instance Level permission:
        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                    "Here are the keys - have fun!");
        } else {
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }
        //注销
        //all done - log out!
        currentUser.logout();
        //结束
        System.exit(0);
    }
}

具体的看这个博主:https://www.cnblogs.com/feng-zhi/p/14761954.html

Swagger

学习目标:

  • 了解swagger的作用和概念
  • 了解前后端分离
  • 在springboot中集成swagger

Swagger简介

前后端分离

Vue + SpringBoot

后端时代:前端只用管理静态页面;html =>后端。模板引擎 JSP =>后端是主力

前后端分离式时代

  • 后端:后端控制层,服务层,数据访问层【后端团队】
  • 前端:前端控制层,视图层【前端团队】
    • 伪造后端数据,json。已经存在了,不需要后端,前端工程依旧能跑起来
  • 前端如何交互? ===> API
  • 前后端相对独立,松耦合;
  • 前后端甚至可以部署在不同的服务器上;

产生了一个问题:

  • 前后端集成联调,前端人员和后端人员无法做到“即时协商,尽早解决”,最终导致问题集中爆发!

解决方案:

  • 首先指定schema[计划的提纲],实时更新最新的API,降低集成风险 ;
  • 早些年:指定word计划文档;
    • 前端测试后端接口:postman
    • 后端提供接口,后端提供接口需要实时更新的消息的改动!

Swagger

  • 号称世界上最流行的Api框架;
  • RestFul Api 文档在线自动生成工具 => Api文档与API定义同步更新
  • 直接运行,可以在线测试API接口;
  • 支持多种语言:(Java,Php…)

官网:https://swagger.io/

在项目使用swagger需要水平springbox;

  • swagger2
  • ui

SpringBoot集成 Swagger

1、新建一个SpringBoot == web项目

2、导入相关依赖

<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>3.0.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>3.0.0</version>
</dependency>
<!--启动包-->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>

application.properties

# 更改规则
spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER

3.编写一个hello工程

4.配置Swagger ==> Config

@Configuration
@EnableSwagger2   //开启Swagger2
public class SwaggerConfig {
}

5.测试运行!**3.0之后的访问地址:**http://localhost:8080/swagger-ui/index.html
在这里插入图片描述

swagger配置扫描接口

@Bean
public Docket docket(){
    return new Docket(DocumentationType.SWAGGER_2)
            .apiInfo(apiInfo())
            .select()
            //RequestHandlerSelectors,配置要扫描接口的方式
            //basePackage:指定要扫描的包
            //any():扫描全部
            //none():不扫描
            //withClassAnnotation:扫描类上的注解,参数是一个注解的反射对象
            //withMethodAnnotation:扫描方法上的注解
            .apis(RequestHandlerSelectors.basePackage("com.kuang.controller"))
            //paths() 过滤什么路径
            .paths(PathSelectors.ant("/kuang/**"))
            .build();
}

配置是否启动swagger

@Bean
public Docket docket(){
    return new Docket(DocumentationType.SWAGGER_2)
            .apiInfo(apiInfo())
            .enable(false)//enable是否启动swagger,如果为false,则swagger不能在浏览器
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.kuang.controller"))
            //.paths(PathSelectors.ant("/kuang/**"))
            .build();
}

swagger在生产环境中使用,在发布的时候不使用?

  • 判断是不是生产环境 falg = false
  • 注入enable(flag)
//配置了swagger的docket的bean实例
@Bean
public Docket docket(Environment environment){
    //设置要显示的swagger环境
    Profiles profiles = Profiles.of("dev");
    //通过 environment.acceptsProfiles 判断是否处于自己设定的环境当中
    boolean flag = environment.acceptsProfiles(profiles);
    System.out.println(flag);

    return new Docket(DocumentationType.SWAGGER_2)
            .apiInfo(apiInfo())
            .enable(flag)//enable是否启动swagger,如果为false,则swagger不能在浏览器
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.kuang.controller"))
            //.paths(PathSelectors.ant("/kuang/**"))
            .build();
}

配置API文档的分组

.groupName("浪潮")

如何配置多个分组;多个Docket实例即可

@Bean
public Docket docket1(){
    return new Docket(DocumentationType.SWAGGER_2).groupName("A");
}

@Bean
public Docket docket2(){
    return new Docket(DocumentationType.SWAGGER_2).groupName("B");
}

@Bean
public Docket docket3(){
    return new Docket(DocumentationType.SWAGGER_2).groupName("C");
}

实体类配置: 得加get和set方法

@ApiModel("用户实体类")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    @ApiModelProperty("用户名")
    private String username;
    @ApiModelProperty("密码")
    private String password;

在这里插入图片描述

controller

package com.kuang.controller;

import com.kuang.pojo.User;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.Mapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping(value = "/hello")
    public String hello(){
        return "hello";
    }

    //只要我们的接口中,返回值中存在实体类,他就会被扫描到swagger中
    @GetMapping(value = "/user")
    public User user(){
        return new User();
    }

    //Operation接口,不是放在类上的,是方法
    @ApiOperation("hello控制类")
    @GetMapping(value = "hello2")
    public String hello2(String username){
        return "hello" + username;
    }

    @ApiOperation("post测试类")
    @PostMapping(value = "postt")
    public User postt(User user){
        int i = 5/0;
        return user;
    }
}

总结:

  1. 我们可以通过swagger给一些比较难理解的属性或者接口,增加注释信息
  2. 接口文档实时更新
  3. 可以在线测试

seagger是一个优秀的工具,几乎所有的大公司都有在使用它

【注意点】在正式发布的时候,关闭swagger!!!处于安全考虑。而节省运行的内存!

任务

异步任务~

邮件发送~

<!--javax.mail-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>
@Autowired
JavaMailSenderImpl javaMailSender;

@Test
void contextLoads() {
    //一个简单的邮箱
    SimpleMailMessage MailMessage = new SimpleMailMessage();

    MailMessage.setSubject("刘志伟爸爸");
    MailMessage.setText("你站在这别动,我去买点橘子,我就吃两个,剩下的都给你。");

    MailMessage.setFrom("2465665899@qq.com");//发件人,更改发件人,要改properties配置文件
    MailMessage.setTo("2465665899@qq.com");//收件人

    javaMailSender.send(MailMessage);
}

@Test
void contextLoads2() throws MessagingException {
    //一个复杂的邮箱
    MimeMessage mimeMessage = javaMailSender.createMimeMessage();
    //组装~
    MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);

    //正文
    helper.setSubject("父爱无声~~~");
    helper.setText("<p style='color:red'>你室友弄的酒真喝了?</p>",true);

    //附件
    helper.addAttachment("1.jpg",new File("C:\\Users\\Administrator\\Desktop\\1.png"));
    helper.addAttachment("2.jpg",new File("C:\\Users\\Administrator\\Desktop\\2.png"));

    helper.setFrom("2465665899@qq.com");
    helper.setTo("3540428468@qq.com");

    javaMailSender.send(mimeMessage);
}

定时任务表达式

TaskScheduler		//任务调度者
TaskExecutor		//任务执行者

@EnableScheduling//开启定时功能的注解
@Scheduled//什么时候执行~

cron表达式
@Service
public class ScheduledService {

    //在一个特定的时间执行这个方法~  Timer

    //cron 表达式~
    // 秒  分  时  日  月  星期~
    /*
            30 15 10 * * ?      每天10点15分30 执行一次
            30 0/5 10,18 * * ?      每天10点和18点,每隔五分钟执行一次
            0 15 10 ? * 1-6      每个月的周一到周六
     */
    @Scheduled(cron = "0/2 * * * * ?")
    public void hello(){
        System.out.println("BUG可以离我远点~~~");
    }
}

springboot整合redisTemplate

这里一级在Redis课程,讲过一次,具体笔记可以去那里看!
Redis笔记:https://blog.csdn.net/qq_54726480/article/details/123089747

分布式 Dubbo + Zookeeper + springboot

RPC

RPC 两个核心模块:通讯和序列化。

序列化:数据传输需要转换

Dubbo ~ 18年重启! Dubbo 3.x RPC Error Exception

专业的事,交给专业的人来做~ 不靠谱! 阿里巴巴!

zookeeper:注册中心!

dubbo-admin:是一个监控管理后台查看我们注册了哪些服务,哪些服务被消费了

Dubbo:jar包~

步骤:

前提:zookeeper服务已开启!

1、提供者提供服务

  1. 导入依赖
  2. 配置注册中心的地址,以及服务发现名,和要扫描的包~
  3. 在想要被注册的服务上面~增加一个注解@Service

2、消费者如何消费

  1. 导入依赖
  2. 配置注册中心的地址,配置自己的服务名
  3. 从远程注入服务~@Reference

具体可以代码可以看:https://blog.csdn.net/qq_41819988/article/details/109606328

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
整理自尚硅谷视频教程springboot高级篇,并增加部分springboot2.x的内容 一、Spring Boot与缓存 一、JSR107 Java Caching定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, Entry 和 Expiry。 • CachingProvider定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可 以在运行 期访问多个CachingProvider。 • CacheManager定义了创建、配置、获取、管理和控制多个唯一命名 的Cache,这些Cache 存在于CacheManager的上下文中。一个CacheManager仅被一个 CachingProvider所拥有。 • Cache是一个类似Map的数据结构并临时存储以Key为索引的值。一个 Cache仅被一个 CacheManager所拥有。 • Entry是一个存储在Cache中的key-value对。 • Expiry 每一 个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期 的状态。一旦过期,条 目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。 二、Spring缓存抽象 Spring从3.1开始定义了org.springframework.cache.Cache 和 org.springframework.cache.CacheManager接口来统一不同的缓存技术; 并支持使用JCache(JSR- 107)注解简化我们开发; • Cache接口为缓存的组件规范定义,包含缓存的各种操作集合; • Cache接 口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache , ConcurrentMapCache 等; • 每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否 已经被调用 过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法 并缓存结果后返回给用户。下 次调用直接从缓存中获取。 • 使用Spring缓存抽象时我们需要关注以下两点; 1、确定方法需要被缓存 以及他们的缓存策略 2、从缓存中读取之前缓存存储的数据 Cache 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、 ConcurrentMapCache等 CacheManager 缓存管理器,管理各种缓存(Cache)组件 @Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 @CacheEvict 清空缓存 @CachePut 保证方法被调用,又希望结果被缓存。 @EnableCaching 开启基于注解的缓存 keyGenerator 缓存数据时key生成策略 serialize 缓存数据时value序列化策略 @CacheConfig 抽取缓存的公共配置 三、几个重要概念&缓存注解 1、常用注解 2、常用参数 名字 位置 描述 示例 methodName root object 当前被调用的方法名 #root.methodName method root object 当前被调用的方法 #root.method.name target root object 当前被调用的目标对象 #root.target targetClass root object 当前被调用的目标对象类 #root.targetClass args root object 当前被调用的方法的参数列表 #root.args[0] 3、常用参数SPEL明 名字 位置 描述 示例 caches root object 当前方法调用使用的缓存列表(如 @Cacheable(value= {"cache1","cache2"}) ), 则有两 个cache #root.caches[0].name argument name evaluation context 方法参数的名字. 可以直接 #参数 名 ,也可以使用 #p0或#a0 的形 式,0代表参数的索引; #iban 、 #a0 、 #p0 result evaluation context 方法执行后的返回值(仅当方法执 行之后的判断有效,如‘unless’ , ’cache put’的表达式 ’cache evict’的表达式 beforeInvocation=false ) #result 四、代码中使用缓存 1、搭建基本环境 1、导入数据库文件 创建出department和employee表 2、创建javaBean封装数据 3、整合MyBatis操作数据库 1.配置数据源信息 2.使用注解版的MyBatis; 1)、@MapperScan指定需要扫描的mapper接口所在的包
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HY丶浪潮

感谢支持,让自己变更好

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值