苍穹外卖技术点总结

1. Swagger

1.1 介绍

Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务(https://swagger.io/)。 它的主要作用是:

  1. 使得前后端分离开发更加方便,有利于团队协作

  2. 接口的文档在线自动生成,降低后端开发人员编写接口文档的负担

  3. 功能测试

    Spring已经将Swagger纳入自身的标准,建立了Spring-swagger项目,现在叫Springfox。通过在项目中引入Springfox ,即可非常简单快捷的使用Swagger。

knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是swagger-bootstrap-ui,取名kni4j是希望它能像一把匕首一样小巧,轻量,并且功能强悍!

目前,一般都使用knife4j框架。

5.2 使用步骤

  1. 导入 knife4j 的maven坐标

    在pom.xml中添加依赖

    <dependency>
       <groupId>com.github.xiaoymin</groupId>
       <artifactId>knife4j-spring-boot-starter</artifactId>
    </dependency>
    
  2. 在配置类中加入 knife4j 相关配置

    WebMvcConfiguration.java

    /**
         * 通过knife4j生成接口文档
         * @return
    */
        @Bean
        public Docket docket() {
            ApiInfo apiInfo = new ApiInfoBuilder()
                    .title("苍穹外卖项目接口文档")
                    .version("2.0")
                    .description("苍穹外卖项目接口文档")
                    .build();
            Docket docket = new Docket(DocumentationType.SWAGGER_2)
                    .apiInfo(apiInfo)
                    .select()
                    .apis(RequestHandlerSelectors.basePackage("com.sky.controller"))
                    .paths(PathSelectors.any())
                    .build();
            return docket;
        }
    
  3. 设置静态资源映射,否则接口文档页面无法访问

    WebMvcConfiguration.java

    /**
         * 设置静态资源映射
         * @param registry
    */
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
            registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
            registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
    
  4. 访问测试

    接口文档访问路径为 http://ip:port/doc.html —> http://localhost:8080/doc.html

2.ThreadLocal

2.1介绍:

ThreadLocal 并不是一个Thread,而是Thread的局部变量。
ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
在这里插入图片描述

2.2常用方法:

  • public void set(T value) 设置当前线程的线程局部变量的值

  • public T get() 返回当前线程所对应的线程局部变量的值

  • public void remove() 移除当前线程的线程局部变量

  • image-20221111212349365
  • 初始工程中已经封装了 ThreadLocal 操作的工具类:

    在sky-common模块

    package com.sky.context;
    
    public class BaseContext {
    
        public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
    
        public static void setCurrentId(Long id) {
            threadLocal.set(id);
        }
    
        public static Long getCurrentId() {
            return threadLocal.get();
        }
    
        public static void removeCurrentId() {
            threadLocal.remove();
        }
    
    }
    

    在拦截器中解析出当前登录员工id,并放入线程局部变量中:

3.分页插件 PageHelper

3.1实现原理

一次请求就是一个线程,PageHelper.startPage(page,size)中携带分页参数。分页参数会设置在ThreadLocal中。PageHelper会在mybatis执行sql前进行拦截,从ThreadLocal中取出分页参数,修改当前执行的sql语句,添加分页sql,最后执行了添加了分页的sql语句,实现分页查询

3.2使用步骤

3.2.1 Controller层

在sky-server模块中,com.sky.controller.admin.EmployeeController中添加分页查询方法。

	/**
     * 员工分页查询
     * @param employeePageQueryDTO
     * @return
     */
    @GetMapping("/page")
    @ApiOperation("员工分页查询")
    public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO){
        log.info("员工分页查询,参数为:{}", employeePageQueryDTO);
        PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO);//后续定义
        return Result.success(pageResult);
    }
3.2.2 Service层接口

在EmployeeService接口中声明pageQuery方法:

	/**
     * 分页查询
     * @param employeePageQueryDTO
     * @return
     */
    PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO);
3.2.3 Service层实现类

在EmployeeServiceImpl中实现pageQuery方法:

	/**
     * 分页查询
     *
     * @param employeePageQueryDTO
     * @return
     */
    public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
        // select * from employee limit 0,10
        //开始分页查询
        PageHelper.startPage(employeePageQueryDTO.getPage(), employeePageQueryDTO.getPageSize());

        Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);//后续定义

        long total = page.getTotal();
        List<Employee> records = page.getResult();

        return new PageResult(total, records);
    }

**注意:**此处使用 mybatis 的分页插件 PageHelper 来简化分页代码的开发。底层基于 mybatis 的拦截器实现。

故在pom.xml文中添加依赖(初始工程已添加)

<dependency>
   <groupId>com.github.pagehelper</groupId>
   <artifactId>pagehelper-spring-boot-starter</artifactId>
   <version>${pagehelper}</version>
</dependency>
3.2.4 Mapper层

在 EmployeeMapper 中声明 pageQuery 方法:

	/**
     * 分页查询
     * @param employeePageQueryDTO
     * @return
     */
    Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO);

在 src/main/resources/mapper/EmployeeMapper.xml 中编写SQL:

<select id="pageQuery" resultType="com.sky.entity.Employee">
        select * from employee
        <where>
            <if test="name != null and name != ''">
                and name like concat('%',#{name},'%')
            </if>
        </where>
        order by create_time desc
    </select>

4.公共字段自动填充

4.1介绍

在上一章节我们已经完成了后台系统的员工管理功能菜品分类功能的开发,在新增员工或者新增菜品分类时需要设置创建时间、创建人、修改时间、修改人等字段,在编辑员工或者编辑菜品分类时需要设置修改时间、修改人等字段。这些字段属于公共字段,也就是也就是在我们的系统中很多表中都会有这些字段,如下:

序号字段名含义数据类型
1create_time创建时间datetime
2create_user创建人idbigint
3update_time修改时间datetime
4update_user修改人idbigint

而针对于这些字段,我们的赋值方式为:

1). 在新增数据时, 将createTime、updateTime 设置为当前时间, createUser、updateUser设置为当前登录用户ID。

2). 在更新数据时, 将updateTime 设置为当前时间, updateUser设置为当前登录用户ID。

答案是可以的,我们使用AOP切面编程,实现功能增强,来完成公共字段自动填充功能。

4.2 实现思路

在实现公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以统一对这些字段进行处理,避免了重复代码。在上述的问题分析中,我们提到有四个公共字段,需要在新增/更新中进行赋值操作, 具体情况如下:

序号字段名含义数据类型操作类型
1create_time创建时间datetimeinsert
2create_user创建人idbigintinsert
3update_time修改时间datetimeinsert、update
4update_user修改人idbigintinsert、update

实现步骤:

1). 自定义注解 AutoFill,用于标识需要进行公共字段自动填充的方法

2). 自定义切面类 AutoFillAspect,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值

3). 在 Mapper 的方法上加入 AutoFill 注解

若要实现上述步骤,需掌握以下知识(之前课程内容都学过)

**技术点:**枚举、注解、AOP、反射

5.Redis

1. Redis入门

1.1 Redis简介

Redis是一个基于内存的key-value结构数据库。Redis 是互联网技术领域使用最为广泛的存储中间件

**官网:**https://redis.io
**中文网:**https://www.redis.net.cn/

key-value结构存储:

在这里插入图片描述

主要特点:

  • 基于内存存储,读写性能高
  • 适合存储热点数据(热点商品、资讯、新闻)
  • 企业应用广泛

Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库,官方提供的数据是可以达到100000+的QPS(每秒内查询次数)。它存储的value类型比较丰富,也被称为结构化的NoSql数据库。

NoSql(Not Only SQL),不仅仅是SQL,泛指非关系型数据库。NoSql数据库并不是要取代关系型数据库,而是关系型数据库的补充。

关系型数据库(RDBMS):

  • Mysql
  • Oracle
  • DB2
  • SQLServer

非关系型数据库(NoSql):

  • Redis
  • Mongo db
  • MemCached
1.2 Redis下载与安装
1.2.1 Redis下载

Redis安装包分为windows版和Linux版:

  • Windows版下载地址:https://github.com/microsoftarchive/redis/releases
  • Linux版下载地址: https://download.redis.io/releases/
1.2.2 Redis安装

1)在Windows中安装Redis(项目中使用)

Redis的Windows版属于绿色软件,直接解压即可使用,解压后目录结构如下:

image-20221130180657152

2)在Linux中安装Redis(简单了解)

在Linux系统安装Redis步骤:

  1. 将Redis安装包上传到Linux
  2. 解压安装包,命令:tar -zxvf redis-4.0.0.tar.gz -C /usr/local
  3. 安装Redis的依赖环境gcc,命令:yum install gcc-c++
  4. 进入/usr/local/redis-4.0.0,进行编译,命令:make
  5. 进入redis的src目录进行安装,命令:make install

安装后重点文件说明:

  • /usr/local/redis-4.0.0/src/redis-server:Redis服务启动脚本
  • /usr/local/redis-4.0.0/src/redis-cli:Redis客户端脚本
  • /usr/local/redis-4.0.0/redis.conf:Redis配置文件
1.3 Redis服务启动与停止

以window版Redis进行演示:

1.3.1 服务启动命令

redis-server.exe redis.windows.conf

image-20221130181950351

Redis服务默认端口号为 6379 ,通过快捷键Ctrl + C 即可停止Redis服务

当Redis服务启动成功后,可通过客户端进行连接。

1.3.2 客户端连接命令

redis-cli.exe

image-20221130182207020

通过redis-cli.exe命令默认连接的是本地的redis服务,并且使用默认6379端口。也可以通过指定如下参数连接:

  • -h ip地址
  • -p 端口号
  • -a 密码(如果需要)
1.3.3 修改Redis配置文件

设置Redis服务密码,修改redis.windows.conf

requirepass 123456

注意:

  • 修改密码后需要重启Redis服务才能生效
  • Redis配置文件中 # 表示注释

重启Redis后,再次连接Redis时,需加上密码,否则连接失败。

redis-cli.exe -h localhost -p 6379 -a 123456

2. Redis数据类型

2.1 五种常用数据类型介绍

Redis存储的是key-value结构的数据,其中key是字符串类型,value有5种常用的数据类型:

  • 字符串 string
  • 哈希 hash
  • 列表 list
  • 集合 set
  • 有序集合 sorted set / zset
2.2 各种数据类型特点
image-20221130190150749

解释说明:

  • 字符串(string):普通字符串,Redis中最简单的数据类型
  • 哈希(hash):也叫散列,类似于Java中的HashMap结构
  • 列表(list):按照插入顺序排序,可以有重复元素,类似于Java中的LinkedList
  • 集合(set):无序集合,没有重复元素,类似于Java中的HashSet
  • 有序集合(sorted set/zset):集合中每个元素关联一个分数(score),根据分数升序排序,没有重复元素

3. Redis常用命令

3.1 字符串操作命令

Redis 中字符串类型常用命令:

  • SET key value 设置指定key的值
  • GET key 获取指定key的值
  • SETEX key seconds value 设置指定key的值,并将 key 的过期时间设为 seconds 秒
  • SETNX key value 只有在 key 不存在时设置 key 的值

更多命令可以参考Redis中文网:https://www.redis.net.cn

3.2 哈希操作命令

Redis hash 是一个string类型的 field 和 value 的映射表,hash特别适合用于存储对象,常用命令:

  • HSET key field value 将哈希表 key 中的字段 field 的值设为 value
  • HGET key field 获取存储在哈希表中指定字段的值
  • HDEL key field 删除存储在哈希表中的指定字段
  • HKEYS key 获取哈希表中所有字段
  • HVALS key 获取哈希表中所有值
image-20221130193121969
3.3 列表操作命令

Redis 列表是简单的字符串列表,按照插入顺序排序,常用命令:

  • LPUSH key value1 [value2] 将一个或多个值插入到列表头部
  • LRANGE key start stop 获取列表指定范围内的元素
  • RPOP key 移除并获取列表最后一个元素
  • LLEN key 获取列表长度
  • BRPOP key1 [key2 ] timeout 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超 时或发现可弹出元素为止
image-20221130193332666
3.4 集合操作命令

Redis set 是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据,常用命令:

  • SADD key member1 [member2] 向集合添加一个或多个成员
  • SMEMBERS key 返回集合中的所有成员
  • SCARD key 获取集合的成员数
  • SINTER key1 [key2] 返回给定所有集合的交集
  • SUNION key1 [key2] 返回所有给定集合的并集
  • SREM key member1 [member2] 移除集合中一个或多个成员
image-20221130193532735
3.5 有序集合操作命令

Redis有序集合是string类型元素的集合,且不允许有重复成员。每个元素都会关联一个double类型的分数。常用命令:

常用命令:

  • ZADD key score1 member1 [score2 member2] 向有序集合添加一个或多个成员
  • ZRANGE key start stop [WITHSCORES] 通过索引区间返回有序集合中指定区间内的成员
  • ZINCRBY key increment member 有序集合中对指定成员的分数加上增量 increment
  • ZREM key member [member …] 移除有序集合中的一个或多个成员
image-20221130193951036
3.6 通用命令

Redis的通用命令是不分数据类型的,都可以使用的命令:

  • KEYS pattern 查找所有符合给定模式( pattern)的 key
  • EXISTS key 检查给定 key 是否存在
  • TYPE key 返回 key 所储存的值的类型
  • DEL key 该命令用于在 key 存在是删除 key

4.在Java中操作Redis

4.1 Redis的Java客户端

前面我们讲解了Redis的常用命令,这些命令是我们操作Redis的基础,那么我们在java程序中应该如何操作Redis呢?这就需要使用Redis的Java客户端,就如同我们使用JDBC操作MySQL数据库一样。

Redis 的 Java 客户端很多,常用的几种:

  • Jedis
  • Lettuce
  • Spring Data Redis

Spring 对 Redis 客户端进行了整合,提供了 Spring Data Redis,在Spring Boot项目中还提供了对应的Starter,即 spring-boot-starter-data-redis。

我们重点学习Spring Data Redis

4.2 Spring Data Redis使用方式
4.2.1 介绍

Spring Data Redis 是 Spring 的一部分,提供了在 Spring 应用中通过简单的配置就可以访问 Redis 服务,对 Redis 底层开发包进行了高度封装。在 Spring 项目中,可以使用Spring Data Redis来简化 Redis 操作。

网址:https://spring.io/projects/spring-data-redis

image-20210927143741458

Spring Boot提供了对应的Starter,maven坐标:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

Spring Data Redis中提供了一个高度封装的类:RedisTemplate,对相关api进行了归类封装,将同一类型操作封装为operation接口,具体分类如下:

  • ValueOperations:string数据操作
  • SetOperations:set类型数据操作
  • ZSetOperations:zset类型数据操作
  • HashOperations:hash类型的数据操作
  • ListOperations:list类型的数据操作
4.2.2 环境搭建

进入到sky-server模块

1). 导入Spring Data Redis的maven坐标(已完成)

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2). 配置Redis数据源

在application-dev.yml中添加

sky:
  redis:
    host: localhost
    port: 6379
    password: 123456
    database: 10

解释说明:

database:指定使用Redis的哪个数据库,Redis服务启动后默认有16个数据库,编号分别是从0到15。

可以通过修改Redis配置文件来指定数据库的数量。

在application.yml中添加读取application-dev.yml中的相关Redis配置

spring:
  profiles:
    active: dev
  redis:
    host: ${sky.redis.host}
    port: ${sky.redis.port}
    password: ${sky.redis.password}
    database: ${sky.redis.database}

3). 编写配置类,创建RedisTemplate对象

package com.sky.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@Slf4j
public class RedisConfiguration {

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
        log.info("开始创建redis模板对象...");
        RedisTemplate redisTemplate = new RedisTemplate();
        //设置redis的连接工厂对象
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //设置redis key的序列化器
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

解释说明:

当前配置类不是必须的,因为 Spring Boot 框架会自动装配 RedisTemplate 对象,但是默认的key序列化器为

JdkSerializationRedisSerializer,导致我们存到Redis中后的数据和原始数据有差别,故设置为

StringRedisSerializer序列化器。

4). 通过RedisTemplate对象操作Redis

在test下新建测试类

package com.sky.test;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.*;

@SpringBootTest
public class SpringDataRedisTest {
    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    public void testRedisTemplate(){
        System.out.println(redisTemplate);
        //string数据操作
        ValueOperations valueOperations = redisTemplate.opsForValue();
        //hash类型的数据操作
        HashOperations hashOperations = redisTemplate.opsForHash();
        //list类型的数据操作
        ListOperations listOperations = redisTemplate.opsForList();
        //set类型数据操作
        SetOperations setOperations = redisTemplate.opsForSet();
        //zset类型数据操作
        ZSetOperations zSetOperations = redisTemplate.opsForZSet();
    }
}

测试:

image-20221130205351403

说明RedisTemplate对象注入成功,并且通过该RedisTemplate对象获取操作5种数据类型相关对象。

上述环境搭建完毕后,接下来,我们就来具体对常见5种数据类型进行操作。

4.2.3 操作常见类型数据

1). 操作字符串类型数据

	/**
     * 操作字符串类型的数据
     */
    @Test
    public void testString(){
        // set get setex setnx
        redisTemplate.opsForValue().set("name","小明");
        String city = (String) redisTemplate.opsForValue().get("name");
        System.out.println(city);
        redisTemplate.opsForValue().set("code","1234",3, TimeUnit.MINUTES);
        redisTemplate.opsForValue().setIfAbsent("lock","1");
        redisTemplate.opsForValue().setIfAbsent("lock","2");
    }

2). 操作哈希类型数据

	/**
     * 操作哈希类型的数据
     */
    @Test
    public void testHash(){
        //hset hget hdel hkeys hvals
        HashOperations hashOperations = redisTemplate.opsForHash();

        hashOperations.put("100","name","tom");
        hashOperations.put("100","age","20");

        String name = (String) hashOperations.get("100", "name");
        System.out.println(name);

        Set keys = hashOperations.keys("100");
        System.out.println(keys);

        List values = hashOperations.values("100");
        System.out.println(values);

        hashOperations.delete("100","age");
    }

3). 操作列表类型数据

	/**
     * 操作列表类型的数据
     */
    @Test
    public void testList(){
        //lpush lrange rpop llen
        ListOperations listOperations = redisTemplate.opsForList();

        listOperations.leftPushAll("mylist","a","b","c");
        listOperations.leftPush("mylist","d");

        List mylist = listOperations.range("mylist", 0, -1);
        System.out.println(mylist);

        listOperations.rightPop("mylist");

        Long size = listOperations.size("mylist");
        System.out.println(size);
    }

4). 操作集合类型数据

	/**
     * 操作集合类型的数据
     */
    @Test
    public void testSet(){
        //sadd smembers scard sinter sunion srem
        SetOperations setOperations = redisTemplate.opsForSet();

        setOperations.add("set1","a","b","c","d");
        setOperations.add("set2","a","b","x","y");

        Set members = setOperations.members("set1");
        System.out.println(members);

        Long size = setOperations.size("set1");
        System.out.println(size);

        Set intersect = setOperations.intersect("set1", "set2");
        System.out.println(intersect);

        Set union = setOperations.union("set1", "set2");
        System.out.println(union);

        setOperations.remove("set1","a","b");
    }

5). 操作有序集合类型数据

	/**
     * 操作有序集合类型的数据
     */
    @Test
    public void testZset(){
        //zadd zrange zincrby zrem
        ZSetOperations zSetOperations = redisTemplate.opsForZSet();

        zSetOperations.add("zset1","a",10);
        zSetOperations.add("zset1","b",12);
        zSetOperations.add("zset1","c",9);

        Set zset1 = zSetOperations.range("zset1", 0, -1);
        System.out.println(zset1);

        zSetOperations.incrementScore("zset1","c",10);

        zSetOperations.remove("zset1","a","b");
    }

6). 通用命令操作

	/**
     * 通用命令操作
     */
    @Test
    public void testCommon(){
        //keys exists type del
        Set keys = redisTemplate.keys("*");
        System.out.println(keys);

        Boolean name = redisTemplate.hasKey("name");
        Boolean set1 = redisTemplate.hasKey("set1");

        for (Object key : keys) {
            DataType type = redisTemplate.type(key);
            System.out.println(type.name());
        }

        redisTemplate.delete("mylist");
    }

6. HttpClient

6.1 介绍

HttpClient 是Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。

image-20221203185003231

HttpClient作用:

  • 发送HTTP请求
  • 接收响应数据

image-20221203185149985 为什么要在Java程序中发送Http请求?有哪些应用场景呢?

HttpClient应用场景:

当我们在使用扫描支付、查看地图、获取验证码、查看天气等功能时

其实,应用程序本身并未实现这些功能,都是在应用程序里访问提供这些功能的服务,访问这些服务需要发送HTTP请求,并且接收响应数据,可通过HttpClient来实现。

image-20221203185427462

HttpClient的maven坐标:

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

HttpClient的核心API:

  • HttpClient:Http客户端对象类型,使用该类型对象可发起Http请求。
  • HttpClients:可认为是构建器,可创建HttpClient对象。
  • CloseableHttpClient:实现类,实现了HttpClient接口。
  • HttpGet:Get方式请求类型。
  • HttpPost:Post方式请求类型。

HttpClient发送请求步骤:

  • 创建HttpClient对象
  • 创建Http请求对象
  • 调用HttpClient的execute方法发送请求

6.2 入门案例

对HttpClient编程工具包有了一定了解后,那么,我们使用HttpClient在Java程序当中来构造Http的请求,并且把请求发送出去,接下来,就通过入门案例分别发送GET请求POST请求,具体来学习一下它的使用方法。

6.2.1 GET方式请求

正常来说,首先,应该导入HttpClient相关的坐标,但在项目中,就算不导入,也可以使用相关的API。

因为在项目中已经引入了aliyun-sdk-oss坐标:

<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
</dependency>

上述依赖的底层已经包含了HttpClient相关依赖。

image-20221203194852825

故选择导入或者不导入均可。

进入到sky-server模块,编写测试代码,发送GET请求。

实现步骤:

  1. 创建HttpClient对象
  2. 创建请求对象
  3. 发送请求,接受响应结果
  4. 解析结果
  5. 关闭资源
package com.sky.test;

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class HttpClientTest {

    /**
     * 测试通过httpclient发送GET方式的请求
     */
    @Test
    public void testGET() throws Exception{
        //创建httpclient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();

        //创建请求对象
        HttpGet httpGet = new HttpGet("http://localhost:8080/user/shop/status");

        //发送请求,接受响应结果
        CloseableHttpResponse response = httpClient.execute(httpGet);

        //获取服务端返回的状态码
        int statusCode = response.getStatusLine().getStatusCode();
        System.out.println("服务端返回的状态码为:" + statusCode);

        HttpEntity entity = response.getEntity();
        String body = EntityUtils.toString(entity);
        System.out.println("服务端返回的数据为:" + body);

        //关闭资源
        response.close();
        httpClient.close();
    }
}

在访问http://localhost:8080/user/shop/status请求时,需要提前启动项目。

测试结果:

image-20221203195917572
6.2.2 POST方式请求

在HttpClientTest中添加POST方式请求方法,相比GET请求来说,POST请求若携带参数需要封装请求体对象,并将该对象设置在请求对象中。

实现步骤:

  1. 创建HttpClient对象
  2. 创建请求对象
  3. 发送请求,接收响应结果
  4. 解析响应结果
  5. 关闭资源
	/**
     * 测试通过httpclient发送POST方式的请求
     */
    @Test
    public void testPOST() throws Exception{
        // 创建httpclient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();

        //创建请求对象
        HttpPost httpPost = new HttpPost("http://localhost:8080/admin/employee/login");

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("username","admin");
        jsonObject.put("password","123456");

        StringEntity entity = new StringEntity(jsonObject.toString());
        //指定请求编码方式
        entity.setContentEncoding("utf-8");
        //数据格式
        entity.setContentType("application/json");
        httpPost.setEntity(entity);

        //发送请求
        CloseableHttpResponse response = httpClient.execute(httpPost);

        //解析返回结果
        int statusCode = response.getStatusLine().getStatusCode();
        System.out.println("响应码为:" + statusCode);

        HttpEntity entity1 = response.getEntity();
        String body = EntityUtils.toString(entity1);
        System.out.println("响应数据为:" + body);

        //关闭资源
        response.close();
        httpClient.close();
    }

测试结果:

image-20221203201023925

7. 缓存菜品

7.1 问题说明

用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大。

image-20221208180228667

**结果:**系统响应慢、用户体验差

7.2 实现思路

通过Redis来缓存菜品数据,减少数据库查询操作。

image-20221208180818572

缓存逻辑分析:

  • 每个分类下的菜品保存一份缓存数据
  • 数据库中菜品数据有变更时清理缓存数据
image-20221208181007639

8. 缓存套餐

8.1 Spring Cache

8.1.1 介绍

Spring Cache 是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。

Spring Cache 提供了一层抽象,底层可以切换不同的缓存实现,例如:

  • EHCache
  • Caffeine
  • Redis(常用)

起步依赖:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-cache</artifactId>  		            		       	 <version>2.7.3</version> 
</dependency>
8.1.2 常用注解

在SpringCache中提供了很多缓存操作的注解,常见的是以下的几个:

注解说明
@EnableCaching开启缓存注解功能,通常加在启动类上
@Cacheable在方法执行前先查询缓存中是否有数据,如果有数据,则直接返回缓存数据;如果没有缓存数据,调用方法并将方法返回值放到缓存中
@CachePut将方法的返回值放到缓存中
@CacheEvict将一条或多条数据从缓存中删除

在spring boot项目中,使用缓存技术只需在项目中导入相关缓存技术的依赖包,并在启动类上使用@EnableCaching开启缓存支持即可。

例如,使用Redis作为缓存技术,只需要导入Spring data Redis的maven坐标即可。

8.1.3 入门案例

1). 环境准备

**导入基础工程:**底层已使用Redis缓存实现

基础环境的代码,在我们今天的资料中已经准备好了, 大家只需要将这个工程导入进来就可以了。导入进来的工程结构如下:

image-20221210183942040

数据库准备:

创建名为spring_cache_demo数据库,将springcachedemo.sql脚本直接导入数据库中。

image-20221210184346304

引导类上加@EnableCaching:

package com.itheima;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@Slf4j
@SpringBootApplication
@EnableCaching//开启缓存注解功能
public class CacheDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(CacheDemoApplication.class,args);
        log.info("项目启动成功...");
    }
}

2). @CachePut注解

@CachePut 说明:

​ 作用: 将方法返回值,放入缓存

​ value: 缓存的名称, 每个缓存名称下面可以有很多key

​ key: 缓存的key ----------> 支持Spring的表达式语言SPEL语法

在save方法上加注解@CachePut

当前UserController的save方法是用来保存用户信息的,我们希望在该用户信息保存到数据库的同时,也往缓存中缓存一份数据,我们可以在save方法上加上注解 @CachePut,用法如下:

	/**
	* CachePut:将方法返回值放入缓存
	* value:缓存的名称,每个缓存名称下面可以有多个key
	* key:缓存的key
	*/
	@PostMapping
    @CachePut(value = "userCache", key = "#user.id")//key的生成:userCache::1
    public User save(@RequestBody User user){
        userMapper.insert(user);
        return user;
    }

**说明:**key的写法如下

#user.id : #user指的是方法形参的名称, id指的是user的id属性 , 也就是使用user的id属性作为key ;

#result.id : #result代表方法返回值,该表达式 代表以返回对象的id属性作为key ;

#p0.id:#p0指的是方法中的第一个参数,id指的是第一个参数的id属性,也就是使用第一个参数的id属性作为key ;

#a0.id:#a0指的是方法中的第一个参数,id指的是第一个参数的id属性,也就是使用第一个参数的id属性作为key ;

#root.args[0].id:#root.args[0]指的是方法中的第一个参数,id指的是第一个参数的id属性,也就是使用第一个参数

的id属性作为key ;

启动服务,通过swagger接口文档测试,访问UserController的save()方法

因为id是自增,所以不需要设置id属性

image-20221210191702887

查看user表中的数据

image-20221210192325931

查看Redis中的数据

image-20221210192418204

3). @Cacheable注解

@Cacheable 说明:

​ 作用: 在方法执行前,spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,调用方法并将方法返回值放到缓存中

​ value: 缓存的名称,每个缓存名称下面可以有多个key

​ key: 缓存的key ----------> 支持Spring的表达式语言SPEL语法

在getById上加注解@Cacheable

	/**
	* Cacheable:在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,	  *调用方法并将方法返回值放到缓存中
	* value:缓存的名称,每个缓存名称下面可以有多个key
	* key:缓存的key
	*/
	@GetMapping
    @Cacheable(cacheNames = "userCache",key="#id")
    public User getById(Long id){
        User user = userMapper.getById(id);
        return user;
    }

重启服务,通过swagger接口文档测试,访问UserController的getById()方法

第一次访问,会请求我们controller的方法,查询数据库。后面再查询相同的id,就直接从Redis中查询数据,不用再查询数据库了,就说明缓存生效了。

提前在redis中手动删除掉id=1的用户数据

image-20221210193834150

**查看控制台sql语句:**说明从数据库查询的用户数据

image-20221210193948896

**查看Redis中的缓存数据:**说明已成功缓存

image-20221210194112334

再次查询相同id的数据时,直接从redis中直接获取,不再查询数据库。

4). @CacheEvict注解

@CacheEvict 说明:

​ 作用: 清理指定缓存

​ value: 缓存的名称,每个缓存名称下面可以有多个key

​ key: 缓存的key ----------> 支持Spring的表达式语言SPEL语法

在 delete 方法上加注解@CacheEvict

	@DeleteMapping
    @CacheEvict(cacheNames = "userCache",key = "#id")//删除某个key对应的缓存数据
    public void deleteById(Long id){
        userMapper.deleteById(id);
    }

	@DeleteMapping("/delAll")
    @CacheEvict(cacheNames = "userCache",allEntries = true)//删除userCache下所有的缓存数据
    public void deleteAll(){
        userMapper.deleteAll();
    }

重启服务,通过swagger接口文档测试,访问UserController的deleteAll()方法

image-20221210195254874

**查看user表:**数据清空

image-20221210195332101

查询Redis缓存数据

image-20221210195500014

8.2 实现思路

实现步骤:

1). 导入Spring Cache和Redis相关maven坐标

2). 在启动类上加入@EnableCaching注解,开启缓存注解功能

3). 在用户端接口SetmealController的 list 方法上加入@Cacheable注解

4). 在管理端接口SetmealController的 save、delete、update、startOrStop等方法上加入CacheEvict注解

9. 订单支付

9.1 微信支付介绍

前面的课程已经实现了用户下单,那接下来就是订单支付,就是完成付款功能。支付大家应该都不陌生了,在现实生活中经常购买商品并且使用支付功能来付款,在付款的时候可能使用比较多的就是微信支付和支付宝支付了。在苍穹外卖项目中,选择的就是微信支付这种支付方式。

要实现微信支付就需要注册微信支付的一个商户号,这个商户号是必须要有一家企业并且有正规的营业执照。只有具备了这些资质之后,才可以去注册商户号,才能开通支付权限。

个人不具备这种资质,所以我们在学习微信支付时,最重要的是了解微信支付的流程,并且能够阅读微信官方提供的接口文档,能够和第三方支付平台对接起来就可以了。

9.2 微信支付准备工作

9.2.1 如何保证数据安全?

完成微信支付有两个关键的步骤:

第一个就是需要在商户系统当中调用微信后台的一个下单接口,就是生成预支付交易单。

第二个就是支付成功之后微信后台会给推送消息。

这两个接口数据的安全性,要求其实是非常高的。

**解决:**微信提供的方式就是对数据进行加密、解密、签名多种方式。要完成数据加密解密,需要提前准备相应的一些文件,其实就是一些证书。

获取微信支付平台证书、商户私钥文件:

image-20221214234038395

在后绪程序开发过程中,就会使用到这两个文件,需要提前把这两个文件准备好。

9.2.2 如何调用到商户系统?

微信后台会调用到商户系统给推送支付的结果,在这里我们就会遇到一个问题,就是微信后台怎么就能调用到我们这个商户系统呢?因为这个调用过程,其实本质上也是一个HTTP请求。

目前,商户系统它的ip地址就是当前自己电脑的ip地址,只是一个局域网内的ip地址,微信后台无法调用到。

解决:内网穿透。通过cpolar软件可以获得一个临时域名,而这个临时域名是一个公网ip,这样,微信后台就可以请求到商户系统了。

cpolar软件的使用:

1). 下载与安装

下载地址:https://dashboard.cpolar.com/get-started
在这里插入图片描述

2). cpolar指定authtoken

复制authtoken:

在这里插入图片描述

执行命令:
在这里插入图片描述

3). 获取临时域名

执行命令:
在这里插入图片描述

获取域名:

在这里插入图片描述

4). 验证临时域名有效性

访问接口文档

使用localhost:8080访问

image-20221215190440717

使用临时域名访问

image-20221215190525166

证明临时域名生效。

10. Spring Task

10.1 介绍

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

**定位:**定时任务框架

**作用:**定时自动执行某段Java代码

image-20221218183054818 为什么要在Java程序中使用Spring Task?

应用场景:

1). 信用卡每月还款提醒

image-20221218183213088

2). 银行贷款每月还款提醒

image-20221218183410430

3). 火车票售票系统处理未支付订单

image-20221218183614351

4). 入职纪念日为用户发送通知

image-20221218183655186

**强调:**只要是需要定时处理的场景都可以使用Spring Task

10.2 cron表达式

cron表达式其实就是一个字符串,通过cron表达式可以定义任务触发的时间

**构成规则:**分为6或7个域,由空格分隔开,每个域代表一个含义

每个域的含义分别为:秒、分钟、小时、日、月、周、年(可选)

举例:

2022年10月12日上午9点整 对应的cron表达式为:0 0 9 12 10 ? 2022

image-20221218184412491

说明:一般的值不同时设置,其中一个设置,另一个用?表示。

**比如:**描述2月份的最后一天,最后一天具体是几号呢?可能是28号,也有可能是29号,所以就不能写具体数字。

为了描述这些信息,提供一些特殊的字符。这些具体的细节,我们就不用自己去手写,因为这个cron表达式,它其实有在线生成器。

cron表达式在线生成器:https://cron.qqe2.com/

image-20221218184959888

可以直接在这个网站上面,只要根据自己的要求去生成corn表达式即可。所以一般就不用自己去编写这个表达式。

通配符:

* 表示所有值;

? 表示未说明的值,即不关心它为何值;

- 表示一个指定的范围;

, 表示附加一个可能值;

/ 符号前表示开始时间,符号后表示每次递增的值;

cron表达式案例:

*/5 * * * * ? 每隔5秒执行一次

0 */1 * * * ? 每隔1分钟执行一次

0 0 5-15 * * ? 每天5-15点整点触发

0 0/3 * * * ? 每三分钟触发一次

0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发

0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发

0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发

0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时

0 0 10,14,16 * * ? 每天上午10点,下午2点,4点

10.3 入门案例

10.3.1 Spring Task使用步骤

1). 导入maven坐标 spring-context(已存在)

image-20221218193251182

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

3). 自定义定时任务类

10.3.2 代码开发

编写定时任务类:

进入sky-server模块中

package com.sky.task;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * 自定义定时任务类
 */
@Component
@Slf4j
public class MyTask {

    /**
     * 定时任务 每隔5秒触发一次
     */
    @Scheduled(cron = "0/5 * * * * ?")
    public void executeTask(){
        log.info("定时任务开始执行:{}",new Date());
    }
}

开启任务调度:

启动类添加注解 @EnableScheduling

package com.sky;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication
@EnableTransactionManagement //开启注解方式的事务管理
@Slf4j
@EnableCaching
@EnableScheduling
public class SkyApplication {
    public static void main(String[] args) {
        SpringApplication.run(SkyApplication.class, args);
        log.info("server started");
    }
}
10.3.3 功能测试

启动服务,查看日志

image-20221218194511420

每隔5秒执行一次。

11. WebSocket

11.1 介绍

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

HTTP协议和WebSocket协议对比:

  • HTTP是短连接
  • WebSocket是长连接
  • HTTP通信是单向的,基于请求响应模式
  • WebSocket支持双向通信
  • HTTP和WebSocket底层都是TCP连接

image-20221222184340172 image-20221222184352573

**思考:**既然WebSocket支持双向通信,功能看似比HTTP强大,那么我们是不是可以基于WebSocket开发所有的业务功能?

WebSocket缺点:

服务器长期维护长连接需要一定的成本
各个浏览器支持程度不一
WebSocket 是长连接,受网络限制比较大,需要处理好重连

**结论:**WebSocket并不能完全取代HTTP,它只适合在特定的场景下使用

WebSocket应用场景:

1). 视频弹幕

image-20221222184616570v

2). 网页聊天

image-20221222184641675

3). 体育实况更新

image-20221222184714092

4). 股票基金报价实时更新

image-20221222184742094

11.2 入门案例

11.2.1 案例分析

**需求:**实现浏览器与服务器全双工通信。浏览器既可以向服务器发送消息,服务器也可主动向浏览器推送消息。

效果展示:

image-20221222190401414

实现步骤:

1). 直接使用websocket.html页面作为WebSocket客户端

2). 导入WebSocket的maven坐标

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

4). 导入配置类WebSocketConfiguration,注册WebSocket的服务端组件

5). 导入定时任务类WebSocketTask,定时向客户端推送数据

11.2.2 代码开发

1). 定义websocket.html页面(资料中已提供)

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="UTF-8">
    <title>WebSocket Demo</title>
</head>
<body>
    <input id="text" type="text" />
    <button onclick="send()">发送消息</button>
    <button onclick="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>

2). 导入maven坐标

在sky-server模块pom.xml中已定义

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

3). 定义WebSocket服务端组件(资料中已提供)

直接导入到sky-server模块即可

package com.sky.websocket;

import org.springframework.stereotype.Component;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * WebSocket服务
 */
@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的服务端组件(从资料中直接导入即可)

package com.sky.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * WebSocket配置类,用于注册WebSocket的Bean
 */
@Configuration
public class WebSocketConfiguration {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}

5). 定义定时任务类,定时向客户端推送数据(从资料中直接导入即可)

package com.sky.task;

import com.sky.websocket.WebSocketServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Component
public class WebSocketTask {
    @Autowired
    private WebSocketServer webSocketServer;

    /**
     * 通过WebSocket每隔5秒向客户端发送消息
     */
    @Scheduled(cron = "0/5 * * * * ?")
    public void sendMessageToClient() {
        webSocketServer.sendToAllClient("这是来自服务端的消息:" + DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalDateTime.now()));
    }
}
11.2.3 功能测试

启动服务,打开websocket.html页面

浏览器向服务器发送数据:

image-20221222192759049

服务器向浏览器间隔5秒推送数据:

image-20221222192926954

12. Apache ECharts

12.1 介绍

Apache ECharts 是一款基于 Javascript 的数据可视化图表库,提供直观,生动,可交互,可个性化定制的数据可视化图表。
官网地址:https://echarts.apache.org/zh/index.html

image-20230101153041348

常见效果展示:

1). 柱形图

image-20230101153748714

2). 饼形图

image-20230101153230868

3). 折线图

image-20230101153824086

**总结:**不管是哪种形式的图形,最本质的东西实际上是数据,它其实是对数据的一种可视化展示。

12.2 入门案例

Apache Echarts官方提供的快速入门:https://echarts.apache.org/handbook/zh/get-started/

效果展示:

image-20230101155524477

实现步骤:

1). 引入echarts.js 文件(当天资料已提供)

2). 为 ECharts 准备一个设置宽高的 DOM

3). 初始化echarts实例

4). 指定图表的配置项和数据

5). 使用指定的配置项和数据显示图表

代码开发:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>ECharts</title>
    <!-- 引入刚刚下载的 ECharts 文件 -->
    <script src="echarts.js"></script>
  </head>
  <body>
    <!-- 为 ECharts 准备一个定义了宽高的 DOM -->
    <div id="main" style="width: 600px;height:400px;"></div>
    <script type="text/javascript">
      // 基于准备好的dom,初始化echarts实例
      var myChart = echarts.init(document.getElementById('main'));

      // 指定图表的配置项和数据
      var option = {
        title: {
          text: 'ECharts 入门示例'
        },
        tooltip: {},
        legend: {
          data: ['销量']
        },
        xAxis: {
          data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
        },
        yAxis: {},
        series: [
          {
            name: '销量',
            type: 'bar',
            data: [5, 20, 36, 10, 10, 20]
          }
        ]
      };

      // 使用刚指定的配置项和数据显示图表。
      myChart.setOption(option);
    </script>
  </body>
</html>

测试:(当天资料中已提供)

image-20230101160244104

使用浏览器方式打开即可。

**总结:**使用Echarts,重点在于研究当前图表所需的数据格式。通常是需要后端提供符合格式要求的动态数据,然后响应给前端来展示图表。

13. Apache POI

13.1 介绍

Apache POI 是一个处理Miscrosoft Office各种文件格式的开源项目。简单来说就是,我们可以使用 POI 在 Java 程序中对Miscrosoft Office各种文件进行读写操作。
一般情况下,POI 都是用于操作 Excel 文件。

image-20230131110631081

Apache POI 的应用场景:

  • 银行网银系统导出交易明细

    image-20230131110810568
  • 各种业务系统导出Excel报表

    image-20230131110839959
  • 批量导入业务数据

    image-20230131110856903

13.2 入门案例

Apache POI既可以将数据写入Excel文件,也可以读取Excel文件中的数据,接下来分别进行实现。

Apache POI的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>
13.2.1 将数据写入Excel文件

1). 代码开发

package com.sky.test;

import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class POITest {

    /**
     * 基于POI向Excel文件写入数据
     * @throws Exception
     */
    public static void write() throws Exception{
        //在内存中创建一个Excel文件对象
        XSSFWorkbook excel = new XSSFWorkbook();
        //创建Sheet页
        XSSFSheet sheet = excel.createSheet("itcast");

        //在Sheet页中创建行,0表示第1行
        XSSFRow row1 = sheet.createRow(0);
        //创建单元格并在单元格中设置值,单元格编号也是从0开始,1表示第2个单元格
        row1.createCell(1).setCellValue("姓名");
        row1.createCell(2).setCellValue("城市");

        XSSFRow row2 = sheet.createRow(1);
        row2.createCell(1).setCellValue("张三");
        row2.createCell(2).setCellValue("北京");

        XSSFRow row3 = sheet.createRow(2);
        row3.createCell(1).setCellValue("李四");
        row3.createCell(2).setCellValue("上海");

        FileOutputStream out = new FileOutputStream(new File("D:\\itcast.xlsx"));
        //通过输出流将内存中的Excel文件写入到磁盘上
        excel.write(out);

        //关闭资源
        out.flush();
        out.close();
        excel.close();
    }
    public static void main(String[] args) throws Exception {
        write();
    }
}

2). 实现效果

在D盘中生成itcast.xlsx文件,创建名称为itcast的Sheet页,同时将内容成功写入。

image-20230131112905034
13.2.2 读取Excel文件中的数据

1). 代码开发

package com.sky.test;

import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class POITest {
    /**
     * 基于POI读取Excel文件
     * @throws Exception
     */
    public static void read() throws Exception{
        FileInputStream in = new FileInputStream(new File("D:\\itcast.xlsx"));
        //通过输入流读取指定的Excel文件
        XSSFWorkbook excel = new XSSFWorkbook(in);
        //获取Excel文件的第1个Sheet页
        XSSFSheet sheet = excel.getSheetAt(0);

        //获取Sheet页中的最后一行的行号
        int lastRowNum = sheet.getLastRowNum();

        for (int i = 0; i <= lastRowNum; i++) {
            //获取Sheet页中的行
            XSSFRow titleRow = sheet.getRow(i);
            //获取行的第2个单元格
            XSSFCell cell1 = titleRow.getCell(1);
            //获取单元格中的文本内容
            String cellValue1 = cell1.getStringCellValue();
            //获取行的第3个单元格
            XSSFCell cell2 = titleRow.getCell(2);
            //获取单元格中的文本内容
            String cellValue2 = cell2.getStringCellValue();

            System.out.println(cellValue1 + " " +cellValue2);
        }

        //关闭资源
        in.close();
        excel.close();
    }

    public static void main(String[] args) throws Exception {
        read();
    }
}

2). 实现效果

将itcast.xlsx文件中的数据进行读取

image-20230131113255962

eateCell(2).setCellValue(“上海”);

    FileOutputStream out = new FileOutputStream(new File("D:\\itcast.xlsx"));
    //通过输出流将内存中的Excel文件写入到磁盘上
    excel.write(out);

    //关闭资源
    out.flush();
    out.close();
    excel.close();
}
public static void main(String[] args) throws Exception {
    write();
}

}


**2). 实现效果**

在D盘中生成itcast.xlsx文件,创建名称为itcast的Sheet页,同时将内容成功写入。

<img src="D:/SePocket/typora/项目/JAVA/苍穹外卖/assets/image-20230131112905034.png" alt="image-20230131112905034" style="zoom:50%;" /> 



#### 13.2.2 读取Excel文件中的数据

**1). 代码开发**

```java
package com.sky.test;

import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class POITest {
    /**
     * 基于POI读取Excel文件
     * @throws Exception
     */
    public static void read() throws Exception{
        FileInputStream in = new FileInputStream(new File("D:\\itcast.xlsx"));
        //通过输入流读取指定的Excel文件
        XSSFWorkbook excel = new XSSFWorkbook(in);
        //获取Excel文件的第1个Sheet页
        XSSFSheet sheet = excel.getSheetAt(0);

        //获取Sheet页中的最后一行的行号
        int lastRowNum = sheet.getLastRowNum();

        for (int i = 0; i <= lastRowNum; i++) {
            //获取Sheet页中的行
            XSSFRow titleRow = sheet.getRow(i);
            //获取行的第2个单元格
            XSSFCell cell1 = titleRow.getCell(1);
            //获取单元格中的文本内容
            String cellValue1 = cell1.getStringCellValue();
            //获取行的第3个单元格
            XSSFCell cell2 = titleRow.getCell(2);
            //获取单元格中的文本内容
            String cellValue2 = cell2.getStringCellValue();

            System.out.println(cellValue1 + " " +cellValue2);
        }

        //关闭资源
        in.close();
        excel.close();
    }

    public static void main(String[] args) throws Exception {
        read();
    }
}

2). 实现效果

将itcast.xlsx文件中的数据进行读取

image-20230131113255962
  • 24
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值