苍穹外卖知识点总结(如有不足,请大佬指正)

苍穹外卖知识点总结

0.0使用第三方服务的流程

1.导入相关的依赖

2.把一些必要的信息写入到配置文件中,便于管理

3.创建相应的配置类,读取相应的属性文件的信息

4.创建相应的工具类,里买那些具体的实现方法

5.创建相应的MVC配置类,将工具类的对象声明到 Spring 的容器中去

0.错误

当我们在boot项目中 引入 webSocket 组件之后,然后我们在进行单元测试的时候,就会报错

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'serverEndpointExporter' defined in class path resource [com/sky/config/WebSocketConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: javax.websocket.server.ServerContainer not available

这个并不是我们的代码出现了问题,而是boot 单元测试本身的问题

解决方法

1.去掉注解 @SpringBootTest

2.@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

给注解的 webEnvironment 赋予值

1.登陆验证 jwt 令牌

说明:

首先 管理端 在拦截器进行拦截 只有登录界面不被拦截,访问其他 controller 都会被拦截,在其他界面都会先验证 前端 所携带过来的 jwt 令牌,只有 Jwt 令牌正确才会被放行,如果jwt令牌出现错误,就会出现异常,程序停止运行,所以 验证用户登录,只需要解析这个 jwt 令牌,看其是否正确即可

管理端在登陆时候生成 jwt 令牌,然后将这个令牌响应给前端,前端将这个令牌存储起来,以后每次访问都携带这个令牌,放在请求头的 token 里面,后端 在拦截器拦截的时候,先获取token 里面的内容,然后根据 密钥,和 相应的算法 来解析 jwt 令牌

jwt 就是将原始的 json 数据进行了封装,使得它能够在通信双方之间进行安全的传输。

 

头和有效载荷 通过 base64来进行编码

第三部分 数字签名,使用 指定密钥和 和签名算法生成

头部里面存放的是:指定的密钥 和 选用的签名算法

有效载荷;存放的是自定义存储的信息

创建jwt 令牌的步骤

引入依赖

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>${jjwt}</version>
</dependency>

使用 依赖包里的 Jwtsbuild() 方法创建令牌,指定密钥,指定签名算法

 public void test_1(){
        // 1.引入jjwt 依赖
        // 指定一个 密钥  随便写
        String key="muqiu";
        // 指定一个 签名算法
        SignatureAlgorithm hs256 = SignatureAlgorithm.HS256;
        // 指定令牌 存活的时间  时间一到 令牌自动生效  设置存活时间 为 2h  1000*60*60*2
        // 获取当前 时间 毫秒值 System.currentTimeMillis()
        long l = System.currentTimeMillis() + (1000 * 60 * 60 * 2);
        // 将该时间毫秒值 转换成  Date 格式对象
        Date date=new Date(l);
        // 创建要存放的内容
        Map map=new HashMap<>();
        map.put("id",123456);
        map.put("name","张三");
        // 通过 这个 包里面的 Jwts.bulid()方法创建 jwt令牌
        String compact =
                Jwts.builder().
        //   signWith() 指定签名算法和密钥
                signWith(hs256,key).
        //  指定所放入 jwt 令牌的内容
                setClaims(map).
        //  指定 存活时间
                setExpiration(date).
        // 将这个令牌对象转换成 字符串
                compact();
        // 打印字符串
        System.out.println(compact);
     }
通过指定的密钥和jwt 令牌 来解析该内容

token:就是前面获取的 jwt 令牌

key:就是设置令牌时候的密钥

public void test_2(){
    //解析 jwt 令牌
    // 首先先指定解析的 密钥
    String key="muqiu";
    // 然后获取前端传进来的 jwt 令牌
    String token ="eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoi5byg5LiJIiwiaWQiOjEyMzQ1Nn0.VN8YQkEg2Fl8tlj8YlpUGHCBpcr17-pthcyuHa-2cjM";
    // 之后来解析 jwt 令牌  获取令牌对象
    JwtParser parser = Jwts.parser();
    // 解析令牌  setSigningKey() 参数为你指定的密钥
    //  parseClaimsJws() 参数为 前端传进来的 token 令牌
    Jws<Claims> jws = parser.setSigningKey(key).parseClaimsJws(token);
    // 获取存入载荷中的信息
    Claims body = jws.getBody();
    // 获取相应的信息
    Integer id = (Integer) body.get("id");
    String name = (String) body.get("name");
    // 打印信息
    System.out.println(id);
    System.out.println(name);
    // 打印整个 载荷
    System.out.println(body);
​
}

如果 传输的令牌是错误的,就会报错,程序不会继续运行。

2.ThreadLocal(存储信息)

说明:

ThreadLocal 并不是一个Thread 而是Thread的局部变量

它能够为每个线程提供一份单独的存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问

使用方法:创建一个ThreadLocal的工具类
public class ThreadLocalUtil {
    // ThreadLocal 的工具类
    // ThreadLocal是线程的一个局部变量,能够为每一个线程提供一个单独的存储空间
    // 只有线程内才会获得存储空间中的值,线程外则不会获得
    public static ThreadLocal<Long> local=new ThreadLocal<>();
​
    public static void set(Long value){
        local.set(value);
    }
    public static Long get(){
        return local.get();
    }
    public static void remove(){
        local.remove();
    }
}

3.Swagger 生成接口文档,进行接口调试

使用步骤:
1.导入maven坐标
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>${knife4j}</version>
</dependency>
2.在配置类中加入 相关配置 (把这个类 声明到 ioc容器中去) 固定写法

2.1 创建一个返回 Docket的对象

固定写法,若要在其他的地方使用 swagger ,只需要修改扫描包的地方

@Bean
public Docket docket(){
    // 配置类生成  swagger 对象
    //先创建一个 ApiInfoBuilder()对象
    ApiInfoBuilder apiInfoBuilder = new ApiInfoBuilder();
    // 为这个对象赋予一些初始信息
    apiInfoBuilder.title("123456");  //设置Swagger文档的标题
    apiInfoBuilder.description("123456");  //设置Swagger文档的描述信息
    apiInfoBuilder.version("2.0");   // 设置 swagger 文档的版本
    // 将这个对象构建成一个 ApiInfo 对象
    ApiInfo build = apiInfoBuilder.build();
​
    // 创建 DocKet 对象,并为其赋予必要的属性
    // 必须传递的参数是 DocumentationType.SWAGGER_2 固定写法  DocumentationType包里面定义的常量
    Docket docket=new Docket(DocumentationType.SWAGGER_2);
    // Docket 对象赋值
    docket.groupName("1234");  //生成的该类的接口的名称
    docket.apiInfo(build);   //放入上述构建的 ApiInfo 对象
    ApiSelectorBuilder select = docket.select(); // 把其转换为一个 ApiSelectorBuilder 对象
    // 指定要扫描的包 和 其 子包
    select.apis(RequestHandlerSelectors.basePackage("com.sky.controller.admin"));
    select.paths(PathSelectors.any()); // 应该是设置该接口的访问路径(后续学习补充)
    // 将该对象 重新构建成 docket 对象
    Docket docket1 = select.build();
    return docket1;
}
@Bean
public Docket docket1() {
    log.info("准备生成接口文档");
    ApiInfo apiInfo = new ApiInfoBuilder()  //构建生成的文档的一系列的信息 使用的是链式编程
            .title("项目接口文档")      //标题
            .version("2.0")                 // 版本
            .description("项目接口文档")  //描述信息
            .build();
    Docket docket1 = new Docket(DocumentationType.SWAGGER_2)
            .groupName("管理端接口")
            .apiInfo(apiInfo)    //把上面创建的  ApiInfo 的对象放入到apiInfo 里面
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.sky.controller.admin"))  //指定生成接口所要扫描的包,以及其子包
            .paths(PathSelectors.any())
            .build();
    return docket1;
}
3.配置访问资源路径

配置访问者资源路径,必须是在配置类里面(有注解 (@Configuration 修饰),并且这个配置类 必须继承

WebMvcConfigurationSupport

然后重写该类里面的 addResourceHandlers 方法

然后在该方法里面配置 资源路径

protected void addResourceHandlers(ResourceHandlerRegistry registry) {
    log.info("准备映射静态资源");
    // 当我们生成接口文档中之后 这些文件都会放到后述文件的下面,所以将我们请求的路径(addResourceHandler) 映射到后面的路径 ("classpath:/META-INF/resources/")
    registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
    registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}

就是当我们请求前面的 路径时候,会自动去访问 后面的路径

当我们在浏览器 访问 http://localhost:8080/doc.html 就会直接访问到我们的接口文档

4.boot 的配置文件

1.boot配置文件的写法

三种写法 :1.aoolication*.yml

2.application*.yaml

3.application.*properties

其中用的最多的就是 application*.yaml

我们先创建一个主配置文件 application.yaml

然后再创建附属的配置文件 application-min.yaml -后面的名称自己写

application-dev.yaml

在主配置文件中配置别的配置文件(即让配置文件生效) 开启其他的配置文件

spring:

profiles:

active: min

只有配置了之后,其余的配置文件的内容才可以生效。

2.boot配置文件中的主要内容

这里只是配置 具体导入相关的maven依赖

1.配置 端口号
server:
  port: 8080  #设置请求的端口号
2.配置 spring

开启其余的附属配置文件

开启bean的循环依赖

配置数据库连接池信息

配置可以传输文件的容量

配置Redis 数据库

使用 ${} 从附属的配置文件里面 获取相应的具体值 前提是 附属的配置文件 必须被开启

spring:
  profiles:
    active: dev  # 开启另外一个  配置文件,只有在这里面开启了其它配置文件,别的文件才会生效
  main:
    allow-circular-references: true  #开启之后,允许循环依赖  
  datasource:  #配置连接池数据库信息
    druid:
      driver-class-name: ${sky.datasource.driver-class-name}
      url: jdbc:mysql://${sky.datasource.host}:${sky.datasource.port}/${sky.datasource.database}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
      username: ${sky.datasource.username}
      password: ${sky.datasource.password}
  servlet:
    multipart:
#      设置传输时候的一个文件的最大容量
      max-file-size: 10MB
#      设置传输时候,一次传输最大的容量 一次传输可以传递多个文件
      max-request-size: 30MB
  redis:
    port: ${re.redis.port}   #redis 的端口号
    password: ${re.redis.password}  #redis 的密码
    host: ${re.redis.host}  #redis (localhost)
    database: ${re.redis.database}  #redis 所使用的数据库(redis有十六个库,0-15)     
3.配置 mybatis
mybatis:
  #mapper配置文件
  mapper-locations: classpath:mapper/*.xml    #扫描mapper 映射文件
  type-aliases-package: com.sky.entity
  configuration:
    #开启驼峰命名
    map-underscore-to-camel-case: true

开启驼峰命名:开启之后 就可以进行自动映射,如果实体类中的名称 和 数据库中的列名称 是对应的

那么在 xml 文件中写sql 语句的时候,就不用再给 列名起别名了 会自动转换 列名 ——> 驼峰式命名。

4.配置 logging日志

不同的包下面的 log 日志的等级

logging:
  level:
    com:
      sky:
        mapper: debug
        service: info
        controller: info

5.文件上传,使用阿里云OSS(第三方服务)

说明

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

说明:主要是为了上传 图片,或者文件,用于 注册时上传的头像,服务器使用的某些图片等

当我们在上传一个文件时候,会先访问我们后端的接口,我们就可以接受从前端传进来的文件,

之后我们 在 那个接口里面 调用 阿里云 oss 的接口,将文件上传到 阿里云 oss 的

使用第三方服务的通用思路

1.准备工作:完成一些账号的注册等内容,且登陆到后台做一些基本的配置

2.参照官方的 SDK文档编写基础的入门程序

3.集成使用

文件上传的基本步骤

文件上传,是指将本地图片,视频,音频等文件上传到服务器,供其他用户浏览器下载的过程

文件上传应用广泛,微博,朋友圈等,都是用到了文件上传功能

前端上传文件必须要有个表单项 里面有个file 类型,必须使用post传输,必须设置编码格式为

enctype="multypart/form-data" 别的格式不利于文件的上传

在spring中接受上传的文件 必须使用一个类 MulTipartFile 并且参数名还要和前端上传的参数名一致

如果不一致,就要用 RequestParam()注解来指定参数名称

当我们进行文件上传时候,在后端接收到前端传过来的文件时候,会生成临时文件(存着我们上传的文件),缓存起来,一旦该次程序运行完毕,临时文件就会被自动删除。

所以,我们要保存前端上传的文件,就需要在接收到文件之后,把它存储起来。

本地存储:接收到上传上来的文件之后,将文件存储到本地服务器磁盘中

当我们接收到文件之后 可以使用 MultipartFile对象的 getoriginalFilename 获取文件的原始名称

然后再使用 TransferTo 传输到指定的磁盘路径下面 但是存在问题: 如果下一次传递的名称和这次相同,就会覆盖这次的图片

可以使用UUID(通用唯一识别码)

UUID.randomUUID() 就可以获取到uuid,然后使用同 toString可以将其转换为字符串

UUID长度32位,中间有四个横杠 加上横杠36位

8-4-4-4-12

存储到本地磁盘的步骤

1.先获取到文件的原名称

2.通过last Index Of 找到最后一个小数的位置

3.通过substring 将最后一个小数点和之后的内容造成一个新的字符串

4.通过UUID生成一个新的字符串

5.将获取的后缀名 追加到UUID的后面

6.将文件传输到本地磁盘,并且文件名称为 UUID+后缀名

*默认情况下上传文件大小最大为1mb

如果要上传的文件大于1mb需要我们自己设定上传文件的最大容量

可以在配置文件中配置这些信息,设置要请求的文件大小

记住关键字 Multipare

阿里云OSS 阿里云对象存储OSS,通过使用OSS可以通过网络随时存储和调用文本,图片,音频,视频等。。

public class Demo {
​
    public static void main(String[] args) throws Exception {
        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。这里我用的是自己的北京1
        String endpoint = "https://oss-cn-beijing.aliyuncs.com";
CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        // AccessKey  id
        String accessKeyId="你的accessKeyId";
        // AccessKey  Secret
        String accessKeySecret="你的accessKeySecret";
        // 填写Bucket名称,例如examplebucket。
        String bucketName = "你的bucketName";
        // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
        String objectName = "1.webp";
        // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
        // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
        String filePath= "D:\\image\\1.webp";
​
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId,accessKeySecret);
​
        try {
            // 创建PutObjectRequest对象。
            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, new File(filePath));
            // 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。
            // ObjectMetadata metadata = new ObjectMetadata();
            // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
            // metadata.setObjectAcl(CannedAccessControlList.Private);
            // putObjectRequest.setMetadata(metadata);
            
            // 上传文件。
            PutObjectResult result = ossClient.putObject(putObjectRequest);           
        } 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();
            }
        }
    }

官方文档SDK中获取的文件 ,只需要修改属性为自己的就可以了

进行 阿里云 oss 文件上传所需的基本信息

1.要创建一个 Bucket,用于存储对象上传的容器,所有的对象都必须隶属于某个存储空间

2.获取 AccessKey 密钥 密钥id,和密钥secret

苍穹外卖中 进行的文件上传步骤

1.导入maven 依赖
!--            阿里云OSS依赖-->
<!--            这个依赖的底层 使用的是 HttpClient
                所以当我们导入这个依赖的时候  HttpClient 也被导入进来了-->
            <dependency>
                <groupId>com.aliyun.oss</groupId>
                <artifactId>aliyun-sdk-oss</artifactId>
                <version>3.10.2</version>
            </dependency>
2.在配置文件中添加相应的数据,便于修改

这是设置在附属的配置文件里面

 sky:
      alioss:
        access-key-id: 你的AccessID
        access-key-secret: 你的AccessSecret
        bucket-name: 你的bucket
        endpoint: 你的endpoint

这是主配置文件里的内容 读取附属配置文件的内容(首先需附属配置文件被打开)

sky:
    alioss:
        endpoint: ${sky.alioss.endpoint}
        bucket-name: ${sky.alioss.bucket-name}
        access-key-secret: ${sky.alioss.access-key-secret}
        access-key-id: ${sky.alioss.access-key-id}

这是开启附属的配置文件

spring:
  profiles:
    active: dev 
3.创建一个读取配置属性文件的配置属性类,并且把这个类声明到 ico 容器中去
@Component
@ConfigurationProperties(prefix = "sky.alioss")
@Data
// 阿里云的配置属性类,读取了 配置文件中关于 阿里云的信息,并赋给了他的四个属性
/**
 * 三个注解  @Component 表示把这个类声明到 ioc 容器里面
 *          @Data lombok 的注解 它提供了类的 get,set,toString,equals,hashCode方法
 *          @ConfigurationProperties 批量配置读取,boot里面的注解,
 *          使用这个注解可以从配置文件 application*.yaml 中获取到相应的信息
 *          前提是 这个类的属性名称要与配置文件中最后一个key相同
 *          然后使用 (prefix = "sky.alioss") 来去添加通用的前缀
 *          这个注解是优于 @Value 的 因为Value 必须写全类名,而且不可以从配置文件
 *          里面获取到集合信息 但是这个可以。
 */
public class AliOssProperties {
​
    private String endpoint;
    private String accessKeyId;
    private String accessKeySecret;
    private String bucketName;
​
}
4.创建阿里云 OSS 的工具类 最后将文件上传后的地址返回来
@Slf4j
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UtilAliOss {
    // 这是 阿里云 上传文件的工具类
    // 首先,我们可以先创建变量,来接受配置类传递过来的信息
    private  String AccesskeyId;
    private  String AccesskeySecret;
    private  String bucketName;
    private  String edipot;
    public String upload(byte[] bytes,String fileName){
        //先创建一个  OssClientBuilder()对象
        OSSClientBuilder builder = new OSSClientBuilder();
        // 构建出一个 OSS 实例对象
        OSS oss = builder.build(edipot, AccesskeyId, AccesskeySecret);
        try {
            // 创建PutObject请求。
            //创建 PutObject请求 参数是桶名称,文件名称 以及字节输入流对象
            // 将传进来的 文件字节数组,转换为字节输入流对象
            oss.putObject(bucketName, fileName, new ByteArrayInputStream(bytes));
            //在这里就已经将文件上传到了阿里云 OSS 了
        } 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 (oss != null) {
                oss.shutdown();
            }
        }
        //文件访问路径规则 https://bucketName.edipot/fileName
        // 通过StringBuilder 将这个字符串返回到前端
        StringBuilder stringBuilder = new StringBuilder("https://");
        stringBuilder
                .append(bucketName)
                .append(".")
                .append(edipot)
                .append("/")
                .append(fileName);
​
        log.info("文件上传到:{}", stringBuilder.toString());
​
        return stringBuilder.toString();
    }
}
5.创建阿里云 OSS 的配置类,将工具类对象注入到spring容器中去
@Configuration
@Slf4j
public class UtilAliOSSConfig {
​
​
    @Bean
    @ConditionalOnMissingBean   //保证整个容器里面只有一个 工具类对象
    public UtilAliOss utilAliOss(AliOssProperties aliOssProperties){
        // 参数是 读取了配置信息的 阿里云 OSS 的属性配置类
        // 以及被加入到了 Spring 容器里面去
        //实例化一个工具列对象
        UtilAliOss utilAliOss=new UtilAliOss();
        //为其属性赋值
        utilAliOss.setEdipot(aliOssProperties.getEndpoint());
        utilAliOss.setAccesskeyId(aliOssProperties.getAccessKeyId());
        utilAliOss.setBucketName(aliOssProperties.getBucketName());
        utilAliOss.setAccesskeySecret(aliOssProperties.getAccessKeySecret());
        // 返回、
        return utilAliOss;
    }
}
6.在Controller 使用该工具类进行文件上传
  @Autowired
    private UtilAliOss utilAliOss;
    @PostMapping("upload")
    @ApiOperation("文件上传")
    public Result<String> file(MultipartFile file){
        //1.注入阿里云工具类对象
        //2.打印对象
        System.out.println(aliOssUtil);
        //3.获取前端传入的文件名称
        String filename = file.getOriginalFilename();
        //4.从名字中获取 最后一个 . 的位置
        int i = filename.lastIndexOf(".");
        //5.将最后一个点及其后面的字符串截取下来 结果substring就是我们所得到的后缀
        String substring = filename.substring(i);
        //6.使用UUID生成 随机生成UUID对象 并转换成字符串
        String s = UUID.randomUUID().toString();
        //7.将 UUID生成的字符串和 照片的后缀拼起来,组成一个新的字符串
        String ss=s+substring;
        String upload = null;
        try {
            //8.将传进来的文件转换成一个字节数组
            byte[] bytes = file.getBytes();
            //9.调用 阿里云工具类的 upload方法  将文件转换成的字节数组和文件名称一其传进去
//            upload = aliOssUtil.upload(bytes, ss);
            upload=utilAliOss.upload(bytes,ss);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //10.将返回的地址方庄到Result 对象中,一起返回前端
        return Result.success(upload);
    }
}

6.在java 程序中 使用Redis

使用步骤

1.引入 RedisTemplate 的依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.在 application.yaml 文件中配置 redis 数据库的信息

是在 spring 的下面配置的,与配置 数据库连接池同级

spring:
  redis:
    port: ${re.redis.port}   #redis 的端口号
    password: ${re.redis.password}  #redis 的密码
    host: ${re.redis.host}  #redis (localhost)
    database: ${re.redis.database}  #redis 所使用的数据库(redis有十六个库,0-15)

具体的信息在辅助的配置类 application-dev.yaml 要读取辅助配置类的信息,首先要现在这个配置类里面 开启辅助配置类

spring:
  profiles:
    active: dev  # 开启另外一个  配置文件,只有在这里面开启了其它配置文件,别的文件才会生效

开启辅助类的配置文件 在该文件中详细的声明了 redis 数据源的信息

  #配置 redis 数据源
re:
  redis:
    host: localhost
    port: 6379
    password: 123456
    database: 1
3.创建一个 redis 的配置类,并在里面 把RedisTemplate声明到 ioc的容器中去
/**
 * redis 的配置类 用来创建一个 Redis 的 Bean
 * 并把这个Bean 声明到 ioc容器中去
 */
@Configuration  //配置类的注解
@Slf4j   // 开启日志注解
public class RedisConfig {
    
    // 在 java 程序中 操作 reids 主要就是使用 RedisTemplate 对象
    // 所以在这里,我们只需要创建 一个生成 RedisTemplate 对象的bean即可
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
        // 先实例化一个 RedisTemPlate 对象
        RedisTemplate redisTemplate=new RedisTemplate();
        // 设置 Redis 的 key 的序列化器,如果不设置,使用默认的序列化器,那么我们存储到Redis 
        // 的数据 key value 都会变成乱码 ,指定了之后 key 不会变成乱码
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        // 为 redis 对象添加 RedisConnectionFactory 对象
        // 这个 工厂对象存在 是因为 我们导入maven 坐标之后,
        // 他会自动帮我们把这个 Redis链接工厂对象声明到 ioc容器中去,便于我们使用
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    }
}
4.使用 RedisTemplate 的方法获取操作不同数据类型的 Redis 对象
public class Redis {
    @Autowired
    private RedisTemplate redisTemplate;
    @Test
    public void test_01(){
        System.out.println(redisTemplate);
        
        // redis 封装了五类接口 供我们来操作不用类的 redis 数据
        //1.操作字符串类型的数据
        ValueOperations string = redisTemplate.opsForValue();
        //2.操作列表类型的数据
        ListOperations list = redisTemplate.opsForList();
        //3.操作hash 类型的数据
        HashOperations hash = redisTemplate.opsForHash();
        //4.操作集合类型的数据
        SetOperations set = redisTemplate.opsForSet();
        //5.操作有序集合的数据
        ZSetOperations zSet = redisTemplate.opsForZSet();
​
    }
5.使用上述对象可以存储不同数据类型的对象到Redis 中去

我们向Redis 中存储的数据,不依赖于这个数据本身的类型,而是依赖于操作这个数据的那个

RedisTemplate对象 是什么类型的。

7.HttpClient(客户端编程工具包,可以在java程序中构建 HTTP请求)

说明

通过使用 httpClient 我们在我们的java程序中 向微信端发送请求,并获取其返回的结果

其主要用于 :客户在微信那段登陆小程序的时候,回想我们后端发送一个 code 码,通过这个code码,我们可以向微信官方发送请求,获取微信的唯一标识符 opinid,之后,我们可以把返回的opinid存到我们的数据库中,这样就相当于完成了注册,以后该成员再次登录,首先发送一个code码,我们获取其返回的openid 然后去数据库获取相应的用户信息,如果存在,则就返回给前端结果(附上jwt令牌),如果不存在,就先把数据保存到数据库中,相当于注册成功

使用步骤

1.导入Maven依赖

<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version> </dependency>

如果我们导入了 阿里云 oss依赖,那么我们就不需要再去导入 Httpclient依赖了,阿里云oss技术底层用的就是该依赖,会在导入 阿里云oss依赖的同时,通过依赖传递,把HttpClient依赖也导入进来。

2.创建连接
2.1 创建get 连接
public void get () throws Exception{
    //测试通过 Httpclient 发起get请求,并获取返回结果
    // 先创建 httpclient 对象
    CloseableHttpClient client = HttpClients.createDefault();
    // 创建get请求对象,并附上请求地址
    HttpGet get=new HttpGet();
    // 可以通过setURI 的方式 为这个get请求赋予地址
    get.setURI(URI.create("http://localhost:8080/user/shop/status"));
    // 调用 httpclient 对象的 execute 方法发送请求,并获取响应结果
    CloseableHttpResponse response = client.execute(get);
    // 通过响应对象,获取响应信息的状态码
    int code = response.getStatusLine().getStatusCode();
    // 打印状态码
    System.out.println(code);
    // 通过响应对象获取响应信息的响应体
    HttpEntity entity = response.getEntity();
    // 把响应体对象转换成 我们可以看懂的字符串
    String s = EntityUtils.toString(entity);
    // 打印响应体对象
    System.out.println(s);
    // 关闭资源
    response.close();
    client.close();
​
}
2.2 创建 post 连接
public void POST() throws IOException {
    // 通过 httpClient对象来构建 Post 请求
    // 获取HttpClient对象
    CloseableHttpClient httpClient = HttpClients.createDefault();
    // 获取 连接方式对象
    HttpPost post=new HttpPost();
    // 通过该方法 可以将一个字符串转换成 URI对象
    URI uri = URI.create("http://localhost:8080/admin/employee/login");
    // 然后将这个 uri 对象 通过 setURI 方法设置成 POST对象的请求地址
    post.setURI(uri);
    // 因为 post 请求,参数是放在响应体里面的 JSON 参数
    // 所以 通过该方法,设置响应体对象
    // 响应体对象 需要参数,所以我们来构建这个参数
    // 由于其参数类型 是 HttpEntity 是一个顶级接口,所以我们可以使用它的实现类 StringEntity
    // 而实例化 StringEntity 对象,则需要参数,参数为 JSON格式的数据,也就是我们响应体里面的内容
    JSONObject jsonObject=new JSONObject();
    jsonObject.put("username","admin");
    jsonObject.put("password",123456);
    // 把JSON 数据转换成 字符串,作为 StringEntity的参数传递进去
    StringEntity stringEntity=new StringEntity(jsonObject.toString());
    // 通过 StringEntity 对象设置编码格式
    stringEntity.setContentEncoding("utf-8");
    // 设置返回的数据格式
    stringEntity.setContentType("application/json");
    post.setEntity(stringEntity);
    // 把请求对象 作为 HttpClient 的 execute() 方法的参数来获取响应对象
    CloseableHttpResponse response = httpClient.execute(post);
    // 获取响应状态码 并打印
    System.out.println(response.getStatusLine().getStatusCode());
    // 获取 响应体对象
    HttpEntity entity = response.getEntity();
    // 把响应体对象转换成 我们所需要格式的对象
    String s = EntityUtils.toString(entity);
    // 打印返回的响应对象
    System.out.println(s);
    // 关闭资源
    response.close();
    httpClient.close();
}
2.3 通过 httpClient 获取 微信号的 唯一标识符 openid
GET https://api.weixin.qq.com/sns/jscode2session 

请求参数

属性类型必填说明
appidstring小程序 appId
secretstring小程序 appSecret
js_codestring登录时获取的 code,可通过wx.login获取
grant_typestring授权类型,此处只需填写 authorization_code

返回参数

属性类型说明
session_keystring会话密钥
unionidstring用户在开放平台的唯一标识符,若当前小程序已绑定到微信开放平台账号下会返回,详见 UnionID 机制说明
errmsgstring错误信息
openidstring用户唯一标识

普通写法 自己手动拼接

   String appid= weChatProperties.getAppid();
    String secret=weChatProperties.getSecret();
    String str="?appid="+appid+"&secret="+secret+"&js_code="+code+"&grant_typr="+grant_type;
    //创建 HttpClient 对象
    CloseableHttpClient httpClient = HttpClients.createDefault();
    // 创建请求对象 参数为请求路径
    HttpGet httpGet=new HttpGet(url+str);
    //调用execute 方法 并发挥结果
    CloseableHttpResponse response = null;
    HttpEntity entity=null;
    String s=null;
    try {
        response = httpClient.execute(httpGet);
        // 把结果解析成字符串
        entity = response.getEntity();
        s = EntityUtils.toString(entity);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    //解析这个json字符串 获取 openid
    JSONObject jsonObject = JSON.parseObject(s);
    String openid = (String) jsonObject.get("openid");
    return openid;
}

规范写法 把这些请求方式,封装成一个工具类类

public class UtilHttpClient {
    //HttpClient 的工具类
    public static String HttpGet(String url, Map<String,String> map){
        // 获取连接对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        // 获取 get 连接对象
        HttpGet get=new HttpGet();
        //因为是 get请求 所以要进行字符串拼接
        //然后再将拼接好的 字符串转换为  URI 对象,赋给get对象
        String s="";
        if(map!=null &&map.size()>0){
            //那么就是这个 map 集合中有值,要先添加 ?再添加参数
            // 再次给 url拼接  ? 号
            url=url+"?";
            String mapString="";
            //然后进行遍历把 map 的键值对拼接成一个字符串
            Set<String> keySet = map.keySet();
            for(String ss:keySet){
                // s是值,get(s)是键
                mapString=mapString+ss+"="+map.get(ss)+"&";
            }
            // 然后通过字符串拼接,把最后一个 & 符号去掉
            s = mapString.substring(0, mapString.length() - 1);
            System.out.println(s);
        }
​
        // 将这两个字符串拼接成一个 完整的字符串
        String str=url+s;
        System.out.println(str);
        // 将这个完整的地址加参数 转换为 uri对象
        URI uri = URI.create(str);
        // 把对象赋给 请求对象
        get.setURI(uri);
        // 把 get对象当作参数 参数传递给 httpClient对象的execute方法,并获得返回对象
        CloseableHttpResponse response=null;
        String string="";
        try {
           response = httpClient.execute(get);
           // 获取状态码
            int statusCode = response.getStatusLine().getStatusCode();
            if(statusCode == 200){
                //获取响应体对象
                HttpEntity entity = response.getEntity();
                // 把响应体对象解析成字符串返回前端
                string = EntityUtils.toString(entity);
                System.out.println(string);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            //关闭资源
            try {
                response.close();
                httpClient.close();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    return string;
    }
    public static String HttpPost(String url, Map<String,String> map){
        // 获取连接对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        // 获取 POST 连接对象
        HttpPost post=new HttpPost();
        //post请求 传递进来的 url就是访问路径
        post.setURI(URI.create(url));
        // 然后给 post 对象赋予请求体的值
        // 先创建 JSON 对象,获取传过来的 map集合的值
        JSONObject jsonObject=new JSONObject();
        if(map!=null &&map.size()>0){
            Set<String> keySet = map.keySet();
            for(String ss:keySet){
             // 把集合中的属性值 都放入到 json 对象里面
                jsonObject.put(ss,map.get(ss));
            }
        }
        StringEntity stringEntity=null;
        CloseableHttpResponse response=null;
        String string="";
        try {
            // 创建 HttPEntity的实现类对象 并且把 json字符串传递进去
           stringEntity=new StringEntity(jsonObject.toString());
            //设置传输的数据格式
            stringEntity.setContentType("application/json");
            //设置传输的编码格式
            stringEntity.setContentEncoding("utf-8");
            // 将该对象赋给 post 对象
            post.setEntity(stringEntity);
            // 把post 对象赋给 httpclient对象,获取返回对象
            response = httpClient.execute(post);
           // 获取响应结果状态码
            int statusCode = response.getStatusLine().getStatusCode();
            if(statusCode == 200){
                //获取响应体对象
                HttpEntity entity = response.getEntity();
                // 把响应体对象解析成字符串返回前端
                string = EntityUtils.toString(entity);
                System.out.println(string);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            //关闭资源
            try {
                response.close();
                httpClient.close();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return string;
    }
}

然后 调用这些工具类,传参即可

使用 工具类的 get 请求

public void test_1(){
    Map<String,String> map=new HashMap<>();
    map.put("appId","自己的小程序id");
    map.put("secret","自己的小程序密钥");
    map.put("grant_type","authorization_code");
    map.put("js_code","从前端获取的code码");
    String url="https://api.weixin.qq.com/sns/jscode2session";
    String s = UtilHttpClient.HttpGet(url, map);
    System.out.println(s);
}

使用工具类的 POST请求

public void test_2(){
    String url="http://localhost:8080/admin/employee/login";
    Map<String,String> map=new HashMap<>();
    map.put("username","admin");
    map.put("password","123456");
    String s = UtilHttpClient.HttpPost(url, map);
    System.out.println(s);
}

8.SpringTask 定时任务

说明:

Spring Task是spring框架提供的一个 任务调度工具,可以按照约定的时间自动执行某个代码逻辑 类似于闹钟

应用场景

1.信用卡每月还款提醒

2.火车票售票系统未支付订单

3.淘宝未支付订单 等等等

4.入职纪念日发送通知等

重要组成部分,cron表达式

可以通过 cron 表达式在线生成器 去生成 cron表达式

Spring Task 使用步骤

1.导入maven坐标 spring-context
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.7.3</version>
</dependency>

在这个依赖里面 会通过以来传递 来引入相应的context 的依赖

mybatis-spring-boot-starter以来传递所导入的包

2.启动类添加注解 @EnableScheduling 开启任务调度

3.自定义定时任务类

注意:这个类也要声明到 Spring容器里面去

所以类上面要添加注解 @Component

定时任务类 就是在方法上面加注解 @Schedule

@Scheduled(cron = "0 0 1 * * ?")

后面是切点表达式

@Component
@Slf4j
public class OrderTask {
    @Autowired
    private OrderMapper orderMapper;
    /**
     * 解决下单之后 未支付 订单一直处于待支付状态
     *
     * 通过定时任务 每分钟检查一次 是否存在超时任务,
     * 如果存在,就修改订单状态为 已取消
     */
    @Scheduled(cron="0 0/1 * * * ?")
    public void task_1(){
        log.info("定时任务开始执行了:{}",LocalDateTime.now()  );
        // 直接查询数据库中 状态为1 而且下单时间 小于当前系统时间 - 15min的
        Integer status=1;
        // 当前系统时间 减去十五分钟
        LocalDateTime localDateTime = LocalDateTime.now().plusSeconds(-15);
        List<Orders> ordersList=orderMapper.selectByStatusAndTime(status,localDateTime);
        // 查询出来的集合  都是 状态为待支付 且 超时的
        // 可以使用批量修改 批量修改也是每次都要访问数据库 。所以在这里可以通过遍历来修改
        if(ordersList != null &&ordersList.size()>0){
            for(Orders o:ordersList){
                // 献给订单赋予取消原因 和取消时间
                o.setCancelTime(LocalDateTime.now());
                o.setCancelReason("用户没有在15分钟之内付款");
                o.setStatus(6);
                // 修改订单信息
                orderMapper.update(o);
            }
​
        }
​
    }
​
    /**
     * 解决订单一直处于 派送中的问题
     *
     * 通过定时任务 每天凌晨一点检查 检查订单的状态
     *  如果订单状态 还是派送中,就 修改成已完成
     */
    @Scheduled(cron = "0 0 1 * * ?")
    public void task_2(){
        // 直接查询数据库中此时 派送状态为 派送中的订单
        Integer status = 4;
        // 当前时间 减去一小时  查询的是昨天的全部订单
        LocalDateTime localDateTime = LocalDateTime.now().plusHours(-1);
        List<Orders> list=orderMapper.selectByStatusAndTime(status,localDateTime);
        if(list != null && list.size()>0){
            for(Orders o:list){
                // 修改订单状态为 已完成
                o.setStatus(5);
                o.setDeliveryTime(LocalDateTime.now());
                orderMapper.update(o);
            }
        }
    }
}

9.Apache POI

说明

我们可以通过POI在java 程序中 对各种 Miscrosoft进行读写操作

一般情况下,都是用来操作 excel 文件的

使用步骤

1.导入 maven 坐标
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>3.16</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>3.16</version>
</dependency>
2.直接使用
2.1 写入文件例子
public static void writer() throws Exception {
    //在内存中创建一个  excel 文件
    XSSFWorkbook workbook = new XSSFWorkbook();
    // 在这个文件里面创建一个 页面
    XSSFSheet sheet = workbook.createSheet("第一页");
    // 在这个页 创建行对象 行的坐标是从 0 开始的  列的也是
    XSSFRow row = sheet.createRow(1);
    // 通过这个 行 获取具体的单元格
    XSSFCell cell = row.createCell(1);
    // 给 第一页 第二行 第二列的单元格赋值
    cell.setCellValue("姓名");
    // 也可以使用 链式编程
    row.createCell(2).setCellValue("城市");
​
    // 获取第三行的 对象
    XSSFRow row1 = sheet.createRow(3);
    row1.createCell(1).setCellValue("张三");
    row1.createCell(2).setCellValue("上海");
    XSSFRow row2 = sheet.createRow(4);
    row2.createCell(1).setCellValue("李四");
    row2.createCell(2).setCellValue("北京");
    XSSFRow row3 = sheet.createRow(5);
    row3.createCell(1).setCellValue("王五");
    row3.createCell(2).setCellValue("洛阳");
    XSSFRow row4 = sheet.createRow(6);
    row4.createCell(1).setCellValue("赵六");
    row4.createCell(2).setCellValue("杭州");
​
    // 通过输出流 把内存里面的文件写到磁盘中去
    FileOutputStream stream = new FileOutputStream(new File("D://qw.xlsx"));
    workbook.write(stream);
​
    // 关闭资源
    stream.close();
    workbook.close();
}
2.2 读取文件例子
public static void read() throws Exception{
    //先获取 要读取文件的 文件输入流
    FileInputStream file=new FileInputStream(new File("D://qw.xlsx"));
    // 将 这个文件放入到 一个新创建的 XSSFWorkbook() 对象里面
    XSSFWorkbook workbook = new XSSFWorkbook(file);
    // 通过这个 对象来获取 excel中的页
    XSSFSheet sheet = workbook.getSheet("第一页");
    // 通过这个对象 来获取 这一页 最后一行有文字的行号
    int lastRowNum = sheet.getLastRowNum();
    // 通过 for 循环 获取excel 表里面的内容
    for(int i =1;i<lastRowNum;i++){
        // 获取某一行
        XSSFRow row = sheet.getRow(i);
        // 获取单元格对象 并获取其数据
        String value1 = row.getCell(1).getStringCellValue();
        String value2 = row.getCell(2).getStringCellValue();
        // 输出其内容
        System.out.println(value1+"-->"+value2);
    }
    // 关闭流
    workbook.close();
    file.close();
}
2.3实际运用

实际中 由于有的 excel 模板比较复杂,所以我们直接在外面创建好一个 excel 模板,在资源文件路径下面创造一个包 template专门来保存这些模板,之后我们获取这个路径下面的模板来对他进行数据填充就可以了

public void export(HttpServletResponse response) {
    // 导出近三十天的报表数据
    // 先获取 当天的日期 以及三十天之前的日期
    LocalDate end=LocalDate.now();  //end  是当天的数据
    LocalDate begin = end.plusDays(-30);  // begin 三十天之前的时间
    // 精确时间
    LocalDateTime beginTime = LocalDateTime.of(begin, LocalTime.MIN);
    LocalDateTime endTime = LocalDateTime.of(end, LocalTime.MAX);
    // 通过这两个时间 获取这三十天的 营业额 订单完成率 新增用户数 有效订单 平均客单价(营业额/有效订单)
    // 营业额 orders 表  status = 5 必须是已完工的订单
    Double allMoney=orderMapper.allSumAmount(beginTime,endTime);
    // 获取这三十天的总订单
    Integer allOrders=orderMapper.allOrders(beginTime,endTime,null);
    // 获取这三十天的有效订单数量
    Integer effectOrders=orderMapper.allOrders(beginTime,endTime,5);
    // 订单完成率 有效订单书/总订单数
    Double z=0.0;
    if(allOrders == 0.0){
        z=0.0;
    }else {
        Double ef= Double.valueOf(effectOrders);
        z=ef/allOrders;
    }
    //新增用户数量  user表
    Integer newUser=userMapper.allUser(beginTime,endTime);
    // 平均客单价(营业额/有效订单)
    Double averagePrice=0.0;
    if(effectOrders == 0.0){
        averagePrice = 0.0;
    }else {
        averagePrice=allMoney/effectOrders;
    }
    XSSFWorkbook workbook=null;
    // 通过反射获取类加载器下面的文件 也就是 模板文件
    // 获取 类的class 对象  1.类名.class  2.对象.getClass
    InputStream file = ReportServiceImpl.class.getClassLoader().getResourceAsStream("template/运营数据报表模板.xlsx");
    try {
        // 先创建出一个 POI 对象 来出入上面的数据
        workbook = new XSSFWorkbook(file);
        // 获取 页
        XSSFSheet sheet1 = workbook.getSheet("Sheet1");
        // 获取行对象 对其操作  先获取第二行 输入日期
        XSSFRow row = sheet1.createRow(1);
        // 第二行 就一列 获取到 这一列
        XSSFCell cell = row.createCell(2);
        // 给这一列 赋值
        cell.setCellValue("时间:"+begin+"至"+end);
        // 然后获取 第四行 对象 为其 3.5.7 单元格赋值
        XSSFRow row1 = sheet1.createRow(3);
        row1.createCell(2).setCellValue(allMoney);
        row1.createCell(4).setCellValue(z);
        row1.createCell(6).setCellValue(newUser);
        // 获取 第五行 对象 3 5 单元格赋值
        XSSFRow row2 = sheet1.createRow(4);
        row2.createCell(2).setCellValue(effectOrders);
        row2.createCell(4).setCellValue(averagePrice);
        // 求这三十天的每一天的 营业额 订单完成率 新增用户数 有效订单 平均客单价
        // 这里通过 for 循环来求
        for(int i = 1;i<=30;i++){
            // 首先通过数据库 获取当天的数据  begin 三十天之前的时间 从这天开始
            // 营业额
            Double statistics = orderMapper.statistics(begin, 5);
            if(statistics == null){
                statistics=0.0;
            }
            // 当天总订单数量
            Integer allOrd = orderMapper.countByOrder(begin);
            // 当天有效订单数量
            Integer effectOrd = orderMapper.countByEffect(begin, 5);
            // 当天订单完成率
            Double zz=0.0;
            if(allOrd == 0){
                zz=0.0;
            }else {
                Double ef= Double.valueOf(effectOrd);
                zz=ef/allOrd;
            }
            // 当天新增用户数
            Integer user = userMapper.newUser(begin);
            if(user == -1){
                user = 0;
            }
            // 当天平均客单价
            Double averagePri=0.0;
            if(effectOrd == 0){
                averagePri = 0.0;
            }else {
                averagePri=statistics/effectOrd;
            }
            // 从第八行开始  添加这些数据 日期 营业额 有效订单  订单完成率 平均客单价 新增用户数
            //首先获取第八行的对象
            XSSFRow cells = sheet1.createRow(i + 6);
            // 获取 123456 单元格 并输入相应的内容
            cells.createCell(1).setCellValue(String.valueOf(begin));
            cells.createCell(2).setCellValue(statistics);
            cells.createCell(3).setCellValue(effectOrd);
            cells.createCell(4).setCellValue(zz);
            cells.createCell(5).setCellValue(averagePri);
            cells.createCell(6).setCellValue(user);
            //让时间 加一天
            begin=begin.plusDays(1);
        }
        ServletOutputStream stream = response.getOutputStream();
        workbook.write(stream);
    } catch (Exception e) {
        e.printStackTrace();
    }finally {
        // 关闭资源
        try {
            workbook.close();
            file.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
​
    }

10.Spring Cache

说明:

实现类基于注解的缓存功能,只需要简单的加一个注解,就可以实现缓存功能

使用步骤

1.导入相关依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
     <version>2.7.3</version>
</dependency>
2.在配置类上添加 @EnableCaching 开启Spring Cache注解的支持
@SpringBootApplication   //启动类的注解  默认会加载其他的配置类,并且会扫描当前类所在的包及其子包下的文件,将带有ioc注解的组件加入到ioc容器里面
@EnableTransactionManagement //开启注解方式的事务管理
@MapperScan("com/sky/mapper")  //扫描mapper文件
@Slf4j
@EnableCaching   //开启 springCache的注解的使用
@EnableScheduling    // 开启任务调度的使用  也就是 spring_task
public class SkyApplication {
    public static void main(String[] args) {
        SpringApplication.run(SkyApplication.class, args);
        log.info("server started");
    }
}

主要注解 及其说明

CachePut 是在方法执行之后在执行

Cacheable 是在方法执行前执行

CacheEvice 方法执行之后删除

案例

@RestController("userSetmealController")
@RequestMapping("user/setmeal")
@Api("用户套餐接口")
@Slf4j
public class SetMealController {
    @Autowired
    private SetmealService setmealService;
    @GetMapping("list")
    @ApiOperation("用户根据分类 id 查询套餐的接口")
    // 需要把查询之后的结果加载到 缓存里面
    @Cacheable(cacheNames = "setmealCache",key = "#categoryId")
    public Result<List<Setmeal>> list(Integer categoryId){
        log.info("前端传进来的数据是:{}",categoryId);
        // 因为一个套餐种类可能有多个套餐 所以这里有个返回的是套餐的集合
        List<Setmeal> list=setmealService.list(categoryId);
        return Result.success(list);
    }
​
    @GetMapping("dish/{id}")
    @ApiOperation("用户根据套餐id查询菜品")
    // 可以将这个也加入到 缓存里面,修改删除菜品的时候,也把这个缓存清理掉
    @Cacheable(cacheNames = "setmealDishCache",key = "#id")
    public Result< List<DishItemVO>> dish(@PathVariable Long id){
        // 根据 套餐id 查询相应的菜品
​
        log.info("前端传进来的数据是:{}",id);
        // 因为一个套餐种类可能有多个套餐 所以这里有个返回的是套餐的集合
        List<DishItemVO> dish = setmealService.dish(id);
        return Result.success(dish);
    }
}

11.WebSocket

介绍

WebSocket 是基于Tcp 的一种新的网络协议,实现了浏览器和服务器的全双工通信,浏览器和服务器只需要完成一次握手,两者就可以创建持久性的连接,并进行双向数据传输

WebSocket 和 http 的连接区别

http:只有客户端发送请求,服务端才会响应,请求响应模式,服务端不会主动给客户端发送信息,并且当服务端响应完客户端之后,连接就会结束

WebSocket: 当第一次建立连接之后,无论客户端有没有发起请求,服务端都可以去响应,且服务端响应客户端之后,连接并不会结束


应用场景

视频弹幕

网页聊天

qq聊天

微信聊天

以及一些不需要刷新界面就可以出现的内容,基本上都是使用的webSocket技术

使用步骤

1.首先需要一个界面作为我们后端程序的客户端(如苍穹外卖的微信小程序)

 发送消息 关闭连接

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="UTF-8">
    <title>WebSocket Demo</title>
</head>
<body>
    <input id="text" type="text" />
    <button οnclick="send()">发送消息</button>
    <button οnclick="closeWebSocket()">关闭连接</button>
    <div id="message">
    </div>
</body>
<script type="text/javascript">
    var websocket = null;
    var clientId = Math.random().toString(36).substr(2);
​
    //判断当前浏览器是否支持WebSocket
    if('WebSocket' in window){
        //连接WebSocket节点
        websocket = new WebSocket("ws://localhost:8080/ws/"+clientId);
    }
    else{
        alert('Not support websocket')
    }
​
    //连接发生错误的回调方法
    websocket.onerror = function(){
        setMessageInnerHTML("error");
    };
​
    //连接成功建立的回调方法
    websocket.onopen = function(){
        setMessageInnerHTML("连接成功");
    }
​
    //接收到消息的回调方法
    websocket.onmessage = function(event){
        setMessageInnerHTML(event.data);
    }
​
    //连接关闭的回调方法
    websocket.onclose = function(){
        setMessageInnerHTML("close");
    }
​
    //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
    window.onbeforeunload = function(){
        websocket.close();
    }
​
    //将消息显示在网页上
    function setMessageInnerHTML(innerHTML){
        document.getElementById('message').innerHTML += innerHTML + '<br/>';
    }
​
    //发送消息
    function send(){
        var message = document.getElementById('text').value;
        websocket.send(message);
    }
    
    //关闭连接
    function closeWebSocket() {
        websocket.close();
    }
</script>
</html>

</script> </html>

2.导入 WebSocket的 maven 坐标
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

3.导入 服务端的组件,用于和客户端通信

var clientId = Math.random().toString(36).substr(2);

websocket = new WebSocket("ws://localhost:8080/ws/"+clientId);

客户端创建连接时候,传递过来一个经过随机数生成的路径参数

服务端 @ServerEndpoint("/ws/{sid}")来接受,因为是本地所以,路径前面没有加 localhost:8080,直接使用的后面的路径

@Component
@ServerEndpoint("/ws/{sid}")
public class WebSocketServer {
​
    //存放会话对象
    private static Map<String, Session> sessionMap = new HashMap();
​
    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("sid") String sid) {
        System.out.println("客户端:" + sid + "建立连接");
        sessionMap.put(sid, session);
    }
​
    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, @PathParam("sid") String sid) {
        System.out.println("收到来自客户端:" + sid + "的信息:" + message);
    }
​
    /**
     * 连接关闭调用的方法
     *
     * @param sid
     */
    @OnClose
    public void onClose(@PathParam("sid") String sid) {
        System.out.println("连接断开:" + sid);
        sessionMap.remove(sid);
    }
​
    /**
     * 群发
     *
     * @param message
     */
    public void sendToAllClient(String message) {
        Collection<Session> sessions = sessionMap.values();
        for (Session session : sessions) {
            try {
                //服务器向客户端发送消息
                session.getBasicRemote().sendText(message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
​
}
4.通过配置类,将 WebSocket 的组件声明到 Spring 容器中去

这个配置类是固定的写法

@Configuration
public class WebSocketConfiguration {
    // ServerEndpointExporter 加入到 ioc容器中去
    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
​
}
​
public void sendToAllClient(String message) {
    Collection<Session> sessions = sessionMap.values();
    for (Session session : sessions) {
        try {
            //服务器向客户端发送消息
            session.getBasicRemote().sendText(message);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
 public void onOpen(Session session, @PathParam("sid") String sid) {
        System.out.println("客户端:" + sid + "建立连接");
        sessionMap.put(sid, session);
    }
这段代码的意思就是 向所有建立长连接的客户端发送消息
因为 session中存储的是所有建立会话的对象
而这些对象对象以及随机生成的 sid 被存储到了map集合里面
sessionMap.values(); 得到了其中的所有会话对象
然后通过遍历,向所有的会话对象都发送了这个消息
5.催单提醒和来单提醒的思路

1.首先,管理端界面要和 服务端保持长连接,也是就建立了WebSocket连接

2.然后用户在微信小程序(用户端)提交订单和催单都是请求了我们的一个方法

3.当他们请求了方法之后,我们可以在方法里面,去调用 WebSocket 中sendToAllClient向所有与其建立长连接的 客户端发送消息

4.客户端收到消息之后,就会解析数据,发出相应的提示

视屏弹幕就是如此,会把每个人输入的信息先传递到后端,然后后端把这个消息传递到每一个建立连接的客户端上面

6.苍穹外卖的实际运用

催单时候的

public void reminder(Long id) {
    //用户催单 传进来的 是这个 订单号的id
    // 我要通过 这个 id 获取订单的编号
    Orders orders = orderMapper.selectByOId(id);
    // 只有处于 待 接单状态 也就是 status == 2 的订单才可以被催单
    if(orders == null){
        throw new OrderBusinessException("查不到这个订单");
    }
    // 只有 状态为 已派送的订单 才可以被完成
    if(orders.getStatus() != 2){
        throw new OrderBusinessException("只有状态为 待接单的订单才可以进行催单");
    }
    // 创建一个 map 集合  封装 type(1,订单 2,催单 ) orderId  content
    Map map=new HashMap();
    map.put("type",2);
    map.put("orderId",id);
    map.put("content","订单号"+orders.getNumber());
    // 把 map 集合转化成一个 json 字符串
    String s = JSONObject.toJSONString(map);
    // 调用 webServer方法 将消息发送到 服务端
    webSocketServer.sendToAllClient(s);
}

Map map=new HashMap(); map.put("type",2); map.put("orderId",id); map.put("content","订单号"+orders.getNumber()); // 把 map 集合转化成一个 json 字符串 String s = JSONObject.toJSONString(map); // 调用 webServer方法 将消息发送到 服务端 webSocketServer.sendToAllClient(s);

分别在两个 浏览器界面 模仿客户端界面

当小程序订单和催单之后

会将这样的 JSON 字符串 发送到客户端,客户端只需要解析这个字符串,做出相应的提示即可

12.使用公共字段自动填充

1.创建一个枚举类,分别再我们创建数据,和修改数据据的时候对公共的时间字段进行赋值
public enum OperationType {
    //进行更新操作用
   UPDATE,
    // 进行插入操作用 
   INSERT
}
2.自定义一个注解,通过识别该注解的内容,来获取改进行的操作
@Retention(RetentionPolicy.RUNTIME) //起作用时期 (运行时期有效)
@Target(ElementType.METHOD)  //可以放到哪里(只能放到方法上面)
public @interface AutoFill {
    //给注解附上属性值
    // 创建了一个枚举类型的属性,
    // 属性名叫 value 之所以叫value是因为只有value这个属性时候,value可以省略不屑
    OperationType value();
}
3.引入 AOP 的依赖
<!--        AOP 的依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
        </dependency>
    </dependencies>
4.创建切面,切点加增强方法
@Aspect  //声明这个类使用体格切面
@Component  //把这个类 加入到 ioc容器中去
@Slf4j
public class AutoFillAspectj {
    //定义切点,切点名称就是方法的名称
    //切点表达式 方法限定符 返回值类型 包.类.方法(..)
    // 要对 com.muqiu.mapper包下的,加了 AutoFill 起作用
    // execution(* com.muqiu.mapper.*.*(..))这是对所有方法起作用
    // 然后execution(* com.muqiu.mapper.*.*(..)) &&
    // @annotation(com.muqiu.annotation.AutoFill)
    // 对方法上加了这个注解的方法起作用
    @Pointcut("execution(* com.muqiu.mapper.*.*(..)) && @annotation(com.muqiu.annotation.AutoFill)")
    public void pointcut(){
    }
​
    //定义增强方法  在方法执行前执行
    @Before("pointcut()")
    public void enhance(JoinPoint joinPoint){
        log.info("增强方法开始执行l");
        // 传递参数 joinPoint 可以获取信息
        //获取注解的信息 仅仅通过signature是不行的 要通过他的实现类MethodSignature
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取到方法上面的注解对象
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
        // 使用 对象.属性名 获得注解的值
        OperationType value = autoFill.value();
        //获取执行这个增强的全部的参数
        Object[] args = joinPoint.getArgs();
        // 先进行判断
        if(args == null && args.length==0){
            return; // 没有参数,不需要增强,终止运行
        }
        // 假设我们的对象每次都是第一个参数 获取到了我们的对象
        Object arg = args[0];
        // 创建时间节点 使用 一个更新时间,一个插入时间
        LocalDateTime updateTime=LocalDateTime.now();
        LocalDateTime insertTime=LocalDateTime.now();
        // 对属性值进行相应的判断 ,进行不同的增强处理
        if(value.equals(OperationType.INSERT)){
            try {
                //通过反射,进行赋值
                // 先获取类的 字节码对象  类名.class 对象名.getClass()
                Class<?> aClass = arg.getClass();
                // 通过这个对象获取 时间操作的 set方法,一个参数是方法名臣,一个参数是该set方法的数据类型的字节码文件
                Method setCreateTime = aClass.getDeclaredMethod("setCreateTime", LocalDateTime.class);
                Method setUpdateTime = aClass.getDeclaredMethod("setUpdateTime", LocalDateTime.class);
                //通过 invoke方法 赋值,第一个是我们获取到的参数对象,第二个是属性值
                setUpdateTime.invoke(arg,updateTime);
                setCreateTime.invoke(arg,insertTime);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }else{
            try {
                //通过反射,进行赋值
                // 先获取类的 字节码对象  类名.class 对象名.getClass()
                Class<?> aClass = arg.getClass();
                // 通过这个对象获取 时间操作的 set方法
                Method setUpdateTime = aClass.getDeclaredMethod("setUpdateTime", LocalDateTime.class);
                //通过 invoke方法 赋值,第一个是我们获取到的参数对象,第二个是属性值
                setUpdateTime.invoke(arg,updateTime);
            } catch (Exception e) {
                e.printStackTrace();
        }
​
    }
    }
}
5.在启动类上开启 aspectj 的注解的使用

@SpringBootApplication @Slf4j @EnableAspectJAutoProxy // 开启 AOP 的注解 public class StartApplication { public static void main(String[] args) {

    SpringApplication.run(StartApplication.class,args);
    log.info("程序启动");
}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值