‘
一、什么是缓存?
复制存储在计算机上其他位置的原始值的数据集合,通常是为了更容易访问
缓存在不同场景下有不同的作用:
-
操作系统的磁盘缓存:减少磁盘机械操作
-
数据库缓存:减少文件系统I/O
-
应用程序缓存:减少数据库查询操作
-
web服务器缓存:减少对服务器的请求
-
客户端浏览器缓存:减少对网站的访问
经典缓存策略:
- 查询数据,先查缓存,如缓存无数据再进行数据库查询
- 更新数据,先更新缓存,再更新数据库
本文主要讲述应用程序缓存框架Redis
二、什么是Redis
一个NoSql数据库,由C语言编写,数据模型是key-value
三、为什么使用Redis作为缓存
- NoSql 数据库结构简单,易扩展;
- Redis 支持多种数据类型,包括String,List,Set,zSet,hash等;
- Redis 支持数据持久化,在内存数据缓存的同时将数据备份在磁盘中,重启时可以重新加载使用,而不会丢失;
- Redis 单个value的最大限制为1GB,容量巨大。
四、实践整合Redis缓存
用到的服务有: spring-cloud-parent spring-cloud-eureka-service spring-cloud-config-service spring-cloud-gateway :https://codechina.csdn.net/m0_38075227/spring-cloud-demo
1、创建项目,加依赖,添加缓存依赖 spring-boot-starter-data-redis
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-parent</artifactId>
<groupId>com.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.redis</groupId>
<artifactId>spring-cloud-redis</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--mysql 驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--oracle 驱动-->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
</dependency>
<!--druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!--druid对spring监控需要的aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--mybatis plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--mybatis plus extension,包含了mybatis plus core-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
</dependency>
<!--代码生成器依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
</dependency>
<!--模板文件依赖-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.1</version>
</dependency>
<!--缓存依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--作为web项目存在-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--eureka 客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--作为web项目存在-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--配置客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
<!--实时刷新配置文件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!--mysql 驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!--spring boot 提供的jdbc支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--druid对spring监控需要的aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
</project>
2、加配置,在config工程的/resource/configs目录下,加入redis-dev.yml配置文件,进行redis配置(由于仅本地测试运行,配置一个redis即可)
spring:
redis:
database: 1 # Redis数据库索引(默认为0)
host: 192.168.1.130 # Redis服务器地址
password: test123 # Redis服务器连接密码(默认为空)
port: 6379 # Redis服务器连接端口
timeout: 1000 # 连接超时时间(毫秒)
pool:
max-active: 200 # 连接池最大连接数(使用负值表示没有限制)
max-idle: 10 # 连接池中的最大空闲连接
min-idle: 0 # 连接池中的最小空闲连接
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制)
3、配置网关
server:
port: 9999
spring:
profiles:
active: dev
application:
name: springcloud-gateway
cloud:
# 配置中心
config:
fail-fast: true
name: ${spring.application.name}
profile: ${spring.profiles.active}
uri: http://localhost:8080
#网关配置中心
gateway:
discovery:
locator: #路由访问方式:http://Gateway_HOST:Gateway_PORT/大写的serviceId/**,其中微服务应用名默认大写访问。
enabled: false #是否与服务发现组件进行结合,通过 serviceId(必须设置成大写) 转发到具体的服务实例。默认为false,设为true便开启通过服务中心的自动根据 serviceId 创建路由的功能。
lower-case-service-id: true #允许通过模块名小写代理
routes:
# - id: springcloud-client
# uri: lb://springcloud-client #网关路由到springcloud-client模块,lb指向内部注册模块
# predicates: #转发谓词,用于设置匹配路由的规则
# - Path=/client/** #通过请求路径匹配
# - Method=GET #通过请求方式匹配
# # - RemoteAddr=127.0.0.1/25 #通过请求id匹配,只有在某个 ip 区间号段的请求才会匹配路由,其中/后的是子网掩码
# # - After=2018-01-20T06:06:06+08:00[Asia/Shanghai] #根据时间进行匹配,在指定时间之后才会匹配路由
# # - Before=2018-01-20T06:06:06+08:00[Asia/Shanghai] #根据时间进行匹配,在指定时间之前才会匹配路由
# # - Between=2018-01-20T06:06:06+08:00[Asia/Shanghai], 2019-01-20T06:06:06+08:00[Asia/Shanghai] #根据时间段进行匹配,处于指定时间段才会匹配路由
# filters:
# - RequestTime=true #此处仅需要配置过滤器工厂前缀及其属性值启动即可(注意“-”后面一定要跟空格,否则报错)
- id: spring-cloud-druid
uri: lb://spring-cloud-druid #网关路由到springcloud-client模块,lb指向内部注册模块
predicates: #转发谓词,用于设置匹配路由的规则
- Path=/spring-cloud-druid/** #通过请求路径匹配
filters:
- RequestTime=true
- StripPrefix=1
- id: spring-cloud-mybatis-plus
uri: lb://spring-cloud-mybatis-plus #网关路由到springcloud-client模块,lb指向内部注册模块
predicates: #转发谓词,用于设置匹配路由的规则
- Path=/spring-cloud-mybatis-plus/** #通过请求路径匹配
filters:
- RequestTime=true
- StripPrefix=1
- id: spring-cloud-redis
uri: lb://spring-cloud-redis #网关路由到springcloud-client模块,lb指向内部注册模块
predicates: #转发谓词,用于设置匹配路由的规则
- Path=/spring-cloud-redis/** #通过请求路径匹配
filters:
- RequestTime=true
- StripPrefix=1
main:
allow-bean-definition-overriding: true #是否允许同Bean覆盖
# 注册中心配置
eureka:
instance:
prefer-ip-address: true #优先使用IP地址注册
client:
service-url:
defaultZone: http://127.0.0.1:8761/eureka/
4、配置本项目的配置文件bootstrap.yml,SpringCloudRedisAppliction等等,其他文件下载资源即可找到
server:
port: 8668 #服务端口
spring:
profiles:
active: dev #当前生效环境
application:
name: spring-cloud-redis #指定应用的唯一标识/服务名
# 配置中心
cloud:
config:
fail-fast: true
name: datasource-mybatis-plus,redis #指定工程于config server中的应用名(此处包括datasource,届时启动初始化环境会包含datasource-{spring.profiles.active}.yml文件)
profile: ${spring.profiles.active} #指定工程于config server中的生效环境
uri: http://localhost:8080 #指定配置中心的注册路径
# 注册中心配置
eureka:
instance:
prefer-ip-address: true #优先使用IP地址注册
client:
service-url:
defaultZone: http://127.0.0.1:8761/eureka/ #eureka的注册地址
package com.redis;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @类名 SpringCloudRedisAppliction
* @描述 TODO
* @版本 1.0
* @创建人 XuKang
* @创建时间 2021/6/23 10:13
**/
@SpringBootApplication
@EnableEurekaClient
public class SpringCloudRedisAppliction {
public static void main(String[] args) {
SpringApplication.run(SpringCloudRedisAppliction.class);
}
}
5、编写工具类
在common-data模块添加RedisHelper.java,用以封装提供更方便快捷的Redis操作(即便我们可以直接使用RedisTemplate)
package com.redis.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @类名 RedisHelper
* @描述 封装提供更方便快捷的Redis操作
* @版本 1.0
* @创建人 XuKang
* @创建时间 2021/6/23 10:26
**/
@Component
public class RedisHelper {
@Autowired
private StringRedisTemplate redisTemplate;
//=============================common============================
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
* @return
*/
public boolean expire(String key,long time){
try {
if(time>0){
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key){
return redisTemplate.getExpire(key,TimeUnit.SECONDS);
}
/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key){
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
public void del(String ... key){
if(key!=null&&key.length>0){
if(key.length==1){
redisTemplate.delete(key[0]);
}else{
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
//============================String=============================
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public String get(String key){
return key==null?null:redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key,String value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key,String value,long time){
try {
if(time>0){
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
}else{
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
* @return
*/
public long incr(String key, long delta){
if(delta<0){
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/
public long decr(String key, long delta){
if(delta<0){
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
//================================Map=================================
/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public Object hget(String key,String item){
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map<Object,Object> hmget(String key){
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public boolean hmset(String key, Map<String,Object> map){
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String,Object> map, long time){
try {
redisTemplate.opsForHash().putAll(key, map);
if(time>0){
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key,String item,String value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key,String item,String value,long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if(time>0){
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item){
redisTemplate.opsForHash().delete(key,item);
}
/**
* 判断hash表中是否有该项的值
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item){
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
* @return
*/
public double hincr(String key, String item,double by){
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
* @return
*/
public double hdecr(String key, String item,double by){
return redisTemplate.opsForHash().increment(key, item,-by);
}
//============================set=============================
/**
* 根据key获取Set中的所有值
* @param key 键
* @return
*/
public Set<String> sGet(String key){
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key,String value){
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, String...values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key,long time,String...values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if(time>0) expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
* @param key 键
* @return
*/
public long sGetSetSize(String key){
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object ...values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
//===============================list=================================
/**
* 获取list缓存的内容
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return
*/
public List<String> lGet(String key, long start, long end){
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
* @param key 键
* @return
*/
public long lGetListSize(String key){
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
* @return
*/
public Object lGetIndex(String key,long index){
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, String value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, String value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<String> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<String> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index,String value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key,long count,String value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
6、修改controller文件
package com.redis.controller;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.api.R;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.redis.config.RedisHelper;
import com.redis.pojo.Client;
import com.redis.service.ClientService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* <p>
* 前端控制器
* </p>
*
* @author Mybatis Plus
* @since 2021-06-21
*/
@RestController
@RequestMapping("/client")
public class ClientController {
@Autowired
private ClientService clientService;
//引入redis操作工具
@Autowired
private RedisHelper redisHelper;
/**
* 新增client
* @param client client对象
* @return
*/
@PostMapping("addClient")
public R addClient(@RequestBody Client client){
boolean bl = this.clientService.save(client);
if(bl){
//填充缓存,key为client_${id}
client = this.clientService.getById(client.getId());
this.redisHelper.set("client_"+client.getId(), JSONUtil.toJsonStr(client));
return R.ok(Boolean.TRUE);
}
return R.failed("插入数据失败");
}
/**
* 根据id获取client
* @param id client的id
* @return
*/
@GetMapping("/{id}")
public R getClientById(@PathVariable Integer id){
//首先从缓存获取
Client client = JSONUtil.toBean(JSONUtil.toJsonStr(this.redisHelper.get("client_"+id)), Client.class);
if(ObjectUtil.isNotNull(client)){
return R.ok(client);
}
return R.ok(this.clientService.getById(id));
}
/**
* 根据id删除client
* @param id client的id
* @return
*/
@DeleteMapping("/{id}")
public R removeClient(@PathVariable Integer id){
boolean bl = this.clientService.removeById(id);
if (bl){
//从缓存移除
this.redisHelper.del("client_"+id);
return R.ok(Boolean.TRUE);
}
return R.failed("删除数据失败");
}
@GetMapping("/page")
public R getClientPage(){
Page<Client> page= new Page(1,2);
return R.ok(this.clientService.page(page));
}
}
7、处理一些启动的异常
修改redis本地密码
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass test123
OK
127.0.0.1:6379> auth test123
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "test123"
127.0.0.1:6379>
创建分页插件 配置PaginationInterceptor,MP提供的分页方法无效
package com.redis.config;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @类名 MybatisPlusConfig
* @描述 创建分页插件 配置PaginationInterceptor,MP提供的分页方法无效
* @版本 1.0
* @创建人 XuKang
* @创建时间 2021/6/23 15:40
**/
@Configuration
public class MybatisPlusConfig {
@Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}
}
8、postman测试
请求添加 查看数据库 查看redis缓存 删除数据 查看数据库 查看redis缓存
五、总结
1、缓存是复制存储在计算机上其他位置的原始值的数据集合,通常是为了更容易访问 2、Redis是一个NoSql数据库,其 支持多种数据类型和支持数据库持久化特性以及其巨大的容量使其称为备受欢迎的服务器缓存及应用程序缓存选择 3、SpringBoot整合redis需要引入 spring-boot-starter-data-redis 依赖
六、资源地址
‘
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/4497.html