开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

💛任职要求中的熟系Redis,你真的熟悉吗?💛 🌈往期回顾

发表于 2021-08-10

这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战

🌈往期回顾

**感谢阅读,希望能对你有所帮助,博文若有瑕疵请在评论区留言或在主页个人介绍中添加我私聊我,感谢每一位小伙伴不吝赐教。我是XiaoLin,既会写bug也会唱rap的男人**
  • ❤Git真的就是Pull和Push吗?❤
  • ❤RESTful都不会,你怎么拿20k?❤
  • SpringBoot最全笔记,企业最核心的技术你确定不来看看?

一、NoSQL

1.1、NoSQL引言

**NoSQL**( `Not Only SQL` ),意即**不仅仅是SQL**, 泛指非关系型的数据库。Nosql这个技术门类,早期就有人提出,发展至2009年趋势越发高涨。

1.2、为什么是NoSQL

随着互联网网站的兴起,传统的关系数据库在应付动态网站,特别是超大规模和高并发的纯动态网站已经显得力不从心,暴露了很多难以克服的问题。如`商城网站中对商品数据频繁查询`、`对热搜商品的排行统计`、`订单超时问题`、以及微信朋友圈(音频,视频)存储等相关使用传统的关系型数据库实现就显得非常复杂,虽然能实现相应功能但是在性能上却不是那么乐观。nosql这个技术门类的出现,更好的解决了这些问题,它告诉了世界不仅仅是sql。

1.3、NoSQL的四大分类

1.3.1、键值(Key-Value)存储数据库

1.3.1.1、说明

这一类数据库主要会使用到一个哈希表,这个表中有一个特定的键和一个指针指向特定的数据。

1.3.1.2、特点

  1. Key/value模型对于IT系统来说的优势在于简单、易部署。
  2. 如果DBA只对部分值进行查询或更新的时候,Key/value就显得效率低下了。

1.3.1.3、相关产品

  1. Tokyo Cabinet/Tyrant,
  2. Redis
  3. SSDB
  4. Voldemort
  5. Oracle BDB

1.3.2、列存储数据库

1.3.2.1、说明

这部分数据库通常是用来应对分布式存储的海量数据。

1.3.2.2、特点

键仍然存在,但是它们的特点是指向了多个列。这些列是由列家族来安排的。

1.3.2.3、相关产品

  1. Cassandra
  2. HBase
  3. Riak.

1.3.3、文档型数据库

1.3.3.1、说明

文档型数据库的灵感是来自于Lotus Notes办公软件的,而且它同第一种键值存储相类似该类型的数据模型是版本化的文档,半结构化的文档以特定的格式存储,比如JSON。文档型数据库可以看作是键值数据库的升级版,允许之间嵌套键值。而且文档型数据库比键值数据库的查询效率更高。

1.3.3.2、特点

以文档形式存储。

1.3.3.3、相关产品

  1. MongoDB
  2. CouchDB
  3. MongoDb(4.x)
  4. SequoiaDB(国内)

1.3.4、图形(Graph)数据库

1.3.4.1、特点

图形结构的数据库同其他行列以及刚性结构的SQL数据库不同,它是使用灵活的图形模型,并且能够扩展到多个服务器上。

1.3.4.2、相关产品

  1. Neo4J
  2. InfoGrid
  3. Infinite Graph

1.4、NoSQL应用场景

  • 数据模型比较简单
  • 需要灵活性更强的IT系统
  • 对数据库性能要求较高
  • 不需要高度的数据一致性

二、Redis入门

2.1、概述

image-20200623121234046

redis是一个内存型的数据库

2.2、Redis特点

  1. Redis是一个高性能key/value内存型数据库。
  2. Redis支持丰富的数据类型 。
  3. Redis支持持久化 。
  4. Redis单线程,单进程。

2.3、Redis的安装(Linux版)

2.3.1、下载Redis源码包

下载地址直达

image-20200623121540434

2.3.2、上传并解压

1
shell复制代码tar -zxvf redis-4.0.10.tar.gz

image-20200623124522026

2.3.3、安装gcc

1
shell复制代码 yum install -y gcc

2.3.4、进入解压缩目录

1
she复制代码make MALLOC=libc

2.3.5、编译完成后执行如下命令

1
shell复制代码make install PREFIX=/usr/redis

2.3.6、进入/usr/redis目录启动redis服务

1
shell复制代码./redis-server

image-20200623125420505

一旦出现这个页面就代表Redis启动了!

2.3.7、进入bin目录执行客户端连接操作

1
2
shell复制代码./redis-cli –p 6379        (终端不展示中文)
/redis-cli –p 6379 --raw (终端展示中文)

2.4、Redis安装的细节

redis启动服务的 细节

如果直接使用`./redis-server`方式启动使用的是`redis-server`这个shell设置,我们在启动的时候,要先启动服务器 `./redis-server` 然后克隆窗口(**不可以按ctrl+c暂停**),在新窗口中启动客户端 `./redis-cli –p 6379`

如何在启动redis时指定配置文件启动

默认在reidis安装完成之后在安装目录没有任何配置文件,需要在源码目录中复制redis.conf配置文件到安装目录,具体操作:
  1. 进入源码目录,复制文件
1
shell复制代码cp redis.conf /usr/redis
  1. 进入/usr/redis的安装目录查看复制配置文件
1
2
shell复制代码 cd /usr/redis
ls
  1. 进入bin目录加载配置启动
1
shell复制代码./redis-server ../redis.conf

redis中库的概念

库是database 用来存放数据的一个基本单元,一个库可以存放key-value键值对,redis中每一个库都有一个唯一名称(编号),从0开始,默认库的个数是16个库,库的编号0-15,默认使用的是0号库。
我们可以使用命令来切换库,每个库是相互隔离的,每个库都可以有相同或者不同的key和value,互不影响。
1
2
3
4
5
6
shell复制代码# 切换库
select dbid(库编号)
# 清空当前库
flushDB
# 清空所有库
flushAll

开启Redis远程连接

  1. 修改配置文件开启远程连接
1
shell复制代码vim redis.conf
  1. 修改配置,按两下键盘上的g到文章的第一个字母,然后在ntework章节中找到bind属性,默认:bind 127.0.0.1 只允许本机访问,修改为:
1
2
shell复制代码# 允许一切客户端访问
bind 0.0.0.0
  1. 以配置文件的方式重启Redis
1
shell复制代码./redis-server ../redis.conf
  1. 可以在客户端中关闭Redis
1
2
shell复制代码# (切记前面有一个空格)
hutdown

三、Redis数据库相关指令

3.1、数据库操作指令

3.1.1、库的说明

Rsdis默认配置器动Redis服务后,默认会存在16个库,编号从0-15,可以使用`select 库的编号` ,来选择一个Rsdis的库。
1
shell复制代码seelect 库编号

3.1.2、清空库

1
2
3
4
shell复制代码# 清空当前的库 
FLUSHDB
# 清空全部的库
FLUSHALL

3.1.3、redis客户端显示中文

1
shell复制代码./redis-cli  -p 7000 --raw

3.2、操作key的相关指令

3.2.1、DEL指令

3.2.1.1、语法

1
shell复制代码 DEL key [key ...]

3.2.1.2、作用

删除给定的一个或多个key 。不存在的key 会被忽略。

3.2.1.3、返回值

被删除key的数量。

3.2.2、EXISTS指令

3.2.2.1、语法

1
shell复制代码EXISTS key

3.2.2.2、作用

检查给定key是否存在。

3.2.2.3、返回值

若key 存在,返回1 ,否则返回0。

3.2.3、EXPIRE指令

3.2.3.1、语法

1
shell复制代码EXPIRE key seconds

3.2.3.2、作用

为给定key设置生存时间,当key 过期时(生存时间为0 ),它会被自动删除。他的时间复杂度为O(1)。

3.2.3.3、返回值

设置成功返回1。

3.2.4、PEXPIRE指令

3.2.4.1、语法

1
shell复制代码PEXPIRE key milliseconds

3.2.4.2、作用

这个命令和EXPIRE 命令的作用类似,但是它以毫秒为单位设置key 的生存时间,而不像EXPIRE 命令那样,以秒为单位。他的时间复杂度也是 O(1)

3.2.4.3、返回值

设置成功,返回1 key 不存在或设置失败,返回0

3.2.5、KEYS指令

3.2.5.1、语法

1
shell复制代码KEYS pattern
他的pattern是一种通配符,可以进行匹配:
  1. KEYS * :匹配数据库中所有key 。
  2. KEYS h?llo:匹配hello ,hallo 和hxllo 等。
  3. KEYS h*llo:匹配hllo 和heeeeello 等。
  4. KEYS h[ae]llo:匹配hello 和hallo ,但不匹配hillo 。特殊符号用 "\" 隔开。

3.2.5.2、作用

查找所有符合给定模式pattern 的key 。

3.2.5.3、返回值

符合给定模式的key列表。

3.2.6、MOVE指令

3.2.6.1、语法

1
shell复制代码MOVE key db

3.2.6.2、作用

将当前数据库的key 移动到给定的数据库db 当中。

3.2.6.3、返回值

移动成功返回1 ,失败则返回0

3.2.7、TTL指令

3.2.7.1、语法

1
shell复制代码 TTL key

3.2.7.2、作用

以秒为单位,返回给定key 的剩余生存时间(TTL, time to live)。

3.2.7.3、返回值

  1. 当key 不存在时,返回-2 。
  2. 当key 存在但没有设置剩余生存时间时,返回-1 。
  3. 否则,以秒为单位,返回key 的剩余生存时间。
  4. 在Redis 2.8 以前,当key 不存在,或者key 没有设置剩余生存时间时,命令都返回-1 。

3.2.8、PTTL指令

3.2.8.1、语法

1
shell复制代码PTTL key

3.2.8.2、作用

这个命令类似于TTL 命令,但它以毫秒为单位返回key 的剩余生存时间,而不是像TTL 命令那样,以秒为单位。

3.2.8.3、返回值

  1. 当key 不存在时,返回-2 。当key 存在但没有设置剩余生存时间时,返回-1 。
  2. 否则,以毫秒为单位,返回key 的剩余生存时间。
  3. 在Redis 2.8 以前,当key 不存在,或者key 没有设置剩余生存时间时,命令都返回-1 。

3.2.9、RANDOMKEY指令

3.2.9.1、语法

1
shell复制代码RANDOMKEY

3.2.9.2、作用

从当前数据库中随机返回(不删除) 一个key 。

3.2.9.3、返回值

当数据库不为空时,返回一个key 。当数据库为空时,返回nil 。

3.2.10、RENAME指令

3.2.10.1、语法

1
shell复制代码RENAME key newkey

3.2.10.2、作用

将key 改名为newkey 。当key 和newkey 相同,或者key 不存在时,返回一个错误。当newkey 已经存在时,RENAME 命令将覆盖旧值。

3.2.10.3、返回值

改名成功时提示OK ,失败时候返回一个错误。

3.2.11、TYPE指令

3.2.11.1、语法
1
shell复制代码TYPE key

3.2.11.2、作用

1
shell复制代码返回key 所储存的值的类型。

3.2.11.3、返回值

  1. none (key 不存在)
  2. string (字符串)
  3. list (列表)
  4. set (集合)
  5. zset (有序集)
  6. hash (哈希表)

四、Redis中的数据类型

4.1、String类型

4.1.1、内存模型

image-20200623132104399

4.1.2、常用操作命令

命令 说明
set 设置一个key/value
get 根据key获得对应的value
mset 一次设置多个key value
mget 一次获得多个key的value
getset 获得原始key的值,同时设置新值
strlen 获得对应key存储value的长度
append 为对应key的value追加内容
getrange 截取value的内容,-1可以代表末尾(索引0开始)
setex 设置一个key存活的有效期(秒)
psetex 设置一个key存活的有效期(毫秒)
setnx 存在不做任何操作,不存在添加
msetnx 可以同时设置多个key,原子操作,只要有一个存在都不保存
decr 进行数值类型的-1操作
decrby 根据提供的数据进行减法操作
Incr 进行数值类型的+1操作
incrby 根据提供的数据进行加法操作
Incrbyfloat 根据提供的数据加入浮点数

4.2、List类型

Redis中的List类型相当于java中list 集合,他的特点是元素有序且可以重复

4.2.1、内存模型

image-20200623161114380

4.2.2、常用操作指令

命令 说明
lpush 将某个值加入到一个key列表头部(从左添加)
lpushx 同lpush,但是必须要保证这个key存在
rpush 将某个值加入到一个key列表末尾(从右边开始放元素)
rpushx 同rpush,但是必须要保证这个key存在
lpop 返回和移除列表左边的第一个元素,并且返回
rpop 返回和移除列表右边的第一个元素,并且返回
lrange 获取某一个下标区间内的元素(遍历,默认从0开始,最后的可以用-1)
llen 获取列表元素个数
lset 设置某一个指定索引的值(索引必须存在)
lindex 获取某一个指定索引位置的元素
lrem 删除重复元素 lerm lists 3 zhangsan(删除lists集合中3个zhangsan)
ltrim 保留列表中特定区间内的元素
linsert 在某一个元素之前,之后插入新元素

4.3、Set类型

4.3.1、内存模型

image-20200623193634316

4.3.2、常用命令

命令 说明
sadd 为集合添加元素
smembers 显示集合中所有元素,实际上是无序
scard 返回集合中元素的个数
spop 随机返回一个元素 并将元素在集合中删除
smove 从一个集合中向另一个集合移动元素 ,必须是同一种类型
srem 从集合中删除一个元素
sismember 判断一个集合中是否含有这个元素
srandmember 随机返回元素
sdiff 去掉第一个集合中其它集合含有的相同元素
sinter 求交集
sunion 求和集

4.4、ZSet类型

他是一个可排序的set集合 且不可重复 ,又称作sortSet 。

4.4.1、内存模型

image-20200623194903967

4.4.2、常用命令

命令 说明
zadd 添加一个有序集合元素
zcard 返回集合的元素个数
zrange 升序 zrevrange 降序 返回一个范围内的元素
zrangebyscore 按照分数查找一个范围内的元素
zrank 返回排名
zrevrank 倒序排名
zscore 显示某一个元素的分数
zrem 移除某一个元素
zincrby 给某个特定元素加分

4.5、Hash类型

value中放一个map结构,存在key和value,key是无序的

4.5.1、内存模型

image-20200623200348408

4.5.2、常用命令

命令 说明
hset 设置一个key/value对
hget 获得一个key对应的value
hgetall 获得所有的key/value对
hdel 删除某一个key/value对
hexists 判断一个key是否存在
hkeys 获得所有的key
hvals 获得所有的value
hmset 设置多个key/value
hmget 获得多个key的value
hsetnx 设置一个不存在的key的值
hincrby 为value进行加法运算(实现自增)
hincrbyfloat 为value加入浮点值(最多保留17位)

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SpringBoot统一接口返回和全局异常处理,大佬们怎么玩

发表于 2021-08-10

现在大多数公司项目框架,基本都是属于前后端分离模式,这种模式会涉及到一个前后端对接问题,无论是对前端或者是后台服务,维护一套完善且规范的接口是非常有必要的,这样不仅能够提高对接效率,也可以让我的代码看起来更加简洁优雅。

修改前后最大的区别是我们不用在每个接口单独捕获异常,也不用在每个接口都要组装一遍返回参数,可以参考下面这张对比图:

image.png

一、SpringBoot不使用统一返回格式

默认情况下,SpringBoot会有如下三种返回情况。

1.1 使用字符串返回

1
2
3
4
typescript复制代码@GetMapping("/getUserName")
public String getUserName(){
   return "HuaGe";
}

调用接口返回结果:

1
复制代码HuaGe

1.2 使用实体类返回

1
2
3
4
typescript复制代码@GetMapping("/getUserName")
public User getUserName(){
   return new User("HuaGe",18,"男");
}

调用接口返回结果:

1
2
3
4
5
json复制代码{
 "name": "HuaGe",
 "age": "18",
 "性别": "男",
}

1.3 异常情况下返回

1
2
3
4
5
typescript复制代码@GetMapping("/getUserName")
public static String getUserName(){
   HashMap hashMap = Maps.newHashMap();
   return hashMap.get(0).toString();
}

模拟一个空指针异常,在不做任何异常处理的情况下,可以看下SpringBoot的默认返回结果:

1
2
3
4
5
6
json复制代码{
   "timestamp": "2021-08-09T06:56:41.524+00:00",
   "status": 500,
   "error": "Internal Server Error",
   "path": "/sysUser/getUserName"
}

对于上面这几种情况,如果整个项目没有定义统一的返回格式,五个后台开发人员定义五种返回格式,这样不仅代码臃肿,前后端对接效率低,而且还会有一些意向不到的情况发生,比如前端直接显示异常详情等,这给用户的体验是非常差的。

二、基础玩法

项目中最常见到的是封装一个工具类,类中定义需要返回的字段信息,把需要返回前端的接口信息,通过该类进行封装,这样就可以解决返回格式不统一的现象了。

2.1 参数说明

  • code: 状态码,后台可以维护一套统一的状态码;
  • message: 描述信息,接口调用成功/失败的提示信息;
  • data: 返回数据。

2.2 流程说明

  • 新建Result类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
arduino复制代码public class Result<T> {
   
   private int code;
   
   private String message;
   
   private T data;
​
   public Result() {}
   public Result(int code, String message) {
       this.code = code;
       this.message = message;
  }
   
   /**
    * 成功
    */
   public static <T> Result<T> success(T data) {
       Result<T> result = new Result<T>();
       result.setCode(ResultMsgEnum.SUCCESS.getCode());
       result.setMessage(ResultMsgEnum.SUCCESS.getMessage());
       result.setData(data);
       return result;
  }
​
   /**
    * 失败
    */
   public static <T> Result<T> error(int code, String message) {
       return new Result(code, message);
  }
}
  • 定义返回状态码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
arduino复制代码public enum ResultMsgEnum {
   SUCCESS(0, "成功"),
   FAIL(-1, "失败"),
   AUTH_ERROR(502, "授权失败!"),
   SERVER_BUSY(503, "服务器正忙,请稍后再试!"),
   DATABASE_OPERATION_FAILED(504, "数据库操作失败");
   private int code;
   private String message;
​
   ResultMsgEnum(int code, String message) {
       this.code = code;
       this.message = message;
  }
   public int getCode() {
       return this.code;
  }
   
   public String getMessage() {
       return this.message;
  }
}
  • 使用方式

上面两步定义了数据返回格式和状态码,接下来就要看下在接口中如何使用了。

1
2
3
4
kotlin复制代码@GetMapping("/getUserName")
public Result getUserName(){
   return Result.success("huage");
}

调用结果如下,可以看到是我们在Result中定义的参数类型。

1
2
3
4
5
css复制代码{
   "code": 0,
   "message": "成功",
   "data": "huage"
}

这样写虽然能够满足日常需求,而且我相信很多小伙伴也是这么用的,但是如果我们有大量的接口,然后在每一个接口中都使用Result.success来包装返回信息,会新增很多重复代码,显得不够优雅,甚至都不好意思拿出去显摆。
肯定会有一种方式能够再一次提高代码逼格,实现最优解。

三、进阶用法

基本用法学会后,接下来看点究极版本,主要用到如下两个知识点,用法简单,无论是拿出来教学妹,还是指点小姐姐,都是必备技能。

3.1 类介绍

  • ResponseBodyAdvice: 该接口是SpringMVC 4.1提供的,它允许在 执行 @ResponseBody后自定义返回数据,用来封装统一数据格式返回;
  • @RestControllerAdvice: 该注解是对Controller进行增强的,可以全局捕获抛出的异常。

3.2 用法说明

  • 新建ResponseAdvice类;
  • 实现ResponseBodyAdvice接口,实现supports、beforeBodyWrite方法;
  • 该类用于统一封装controller中接口的返回结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
typescript复制代码@RestControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
   @Autowired
   private ObjectMapper objectMapper;
​
   /**
    * 是否开启功能 true:是
    */
   @Override
   public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
       return true;
  }
​
   /**
    * 处理返回结果
    */
   @Override
   public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
       //处理字符串类型数据
       if(o instanceof String){
           try {
               return objectMapper.writeValueAsString(Result.success(o));
          } catch (JsonProcessingException e) {
               e.printStackTrace();
          }
      }
       return Result.success(o);
  }
}
​

我们可以通过getUserName接口测试一下,会发现和直接使用Result返回的结果是一致的。

不过,细心的小伙伴们肯定注意到了,在ResponseAdvice我们全部使用了Result.success(o)来处理结果,对于error类型的结果未做处理。我们来看下,发生异常情况时,返回结果是什么样呢?继续使用上面HashMap空指针异常的代码,测试结果如下:

1
2
3
4
5
6
7
8
9
10
css复制代码{
   "code": 0,
   "message": "成功",
   "data": {
       "timestamp": "2021-08-09T09:33:26.805+00:00",
       "status": 405,
       "error": "Method Not Allowed",
       "path": "/sysUser/getUserName"
  }
}

虽然格式上没有毛病,但是在code、data字段的具体数据上是不友好或不正确的。不处理好这些事情,会严重影响自己在前端妹妹心中的高大形象的,这是决不能容忍的。

3.3 全局异常处理器

以前我们遇到异常时,第一时间想到的应该是try..catch..finnal吧,不过这种方式会导致大量代码重复,维护困难,逻辑臃肿等问题,这不是我们想要的结果。

今天我们要用的全局异常处理方式,用起来是比较简单的。首先新增一个类,增加@RestControllerAdvice注解,该注解的作用花哥上面已经介绍过,就不再唠叨了。

1
2
3
4
kotlin复制代码@RestControllerAdvice
public class CustomerExceptionHandler {
   
}

如果我们有想要拦截的异常类型,就新增一个方法,使用@ExceptionHandler注解修饰,注解参数为目标异常类型。

例如:controller中接口发生Exception异常时,就会进入到Execption方法中进行捕获,将杂乱的异常信息,转换成指定格式后交给ResponseAdvice方法进行统一格式封装并返回给前端小伙伴。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
kotlin复制代码@RestControllerAdvice
@Slf4j
public class CustomerExceptionHandler {
​
   @ExceptionHandler(AuthException.class)
   public String ErrorHandler(AuthorizationException e) {
       log.error("没有通过权限验证!", e);
       return "没有通过权限验证!";
  }
​
   @ExceptionHandler(Exception.class)
   public Result Execption(Exception e) {
       log.error("未知异常!", e);
       return Result.error(ResultMsgEnum.SERVER_BUSY.getCode(),ResultMsgEnum.SERVER_BUSY.getMessage());
  }
}

再次调用接口getUserName查看返回结果,会发现还是有一些问题,因为我们在CustomerExceptionHandler中已经将接口返回结果封装成Result类型,而代码执行到统一结果返回类ResponseAdvice时,又会结果再次封装,就出现了如下问题。

1
2
3
4
5
6
7
8
9
css复制代码{
   "code": 0,
   "message": "成功",
   "data": {
       "code": 503,
       "message": "服务器正忙,请稍后再试!",
       "data": null
  }
}

3.4 统一返回结果处理类最终版

解决上述问题非常简单,只要在beforeBodyWrite中增加一条判断即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
typescript复制代码@RestControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
   @Autowired
   private ObjectMapper objectMapper;
​
   /**
    * 是否开启功能 true:开启
    */
   @Override
   public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
       return true;
  }
​
   /**
    * 处理返回结果
    */
   @Override
   public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
       //处理字符串类型数据
       if(o instanceof String){
           try {
               return objectMapper.writeValueAsString(Result.success(o));
          } catch (JsonProcessingException e) {
               e.printStackTrace();
          }
      }
       //返回类型是否已经封装
       if(o instanceof Result){
           return o;
      }
       return Result.success(o);
  }
}
​

至此,本章的任务就全部讲完,上述代码可以直接引用,不需要其他的配置项,非常推荐引用到自己的项目中。

四、总结

本章讲解的内容不是很多,主要就两个类的配置,即处理统一返回结果的类ResponseAdvice和异常处理类CustomerExceptionHandler。全文围绕RestControllerAdvice、ResponseBodyAdvice的使用,通过一步步的迭代,最终构建一套通用的代码代码返回格式。本文属于实用知识分享,不知道小伙伴们还有其他好用的统一处理方案嘛,如果有更好的方案,可以分享出来大家一块学习。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

快速搭建spring+netty+websocket实现消息

发表于 2021-08-10

srpc-parent

快速基于spring容器管理的rpc服务

支持tcp,websocket协议

一、背景

在开发过程中,为实现服务端,客户端调用方式,简化web调用流程,快速完成第三方接口对接。整理了基于netty集成rpc方案的集成,为了提供消息推送,服务监控提供了websocket协议的实现。

二、 Rpc服务框架组成:

客户端(Client):服务调用方。

客户端存根(Client Stub):存放服务端地址信息,将客户端的请求参数数据信息打包成网络消息,再通过网络传输发送给服务端。

服务端存根(Server Stub):接收客户端发送过来的请求消息并进行解包,然后再调用本地服务进行处理。

服务端(Server):服务的真正提供者。

Network Service:底层传输,可以是TCP或HTTP。

三、环境支持

1
2
3
4
5
复制代码netty 4.1.8
spring 4.0+
kryo 4.0.2
gson 2.8.5
hessian 4.0.62

四、Rpc流程序列图:

image

五、Websocke序列图:

image

六、使用说明:

1、pom依赖:

image

2、项目说明:

1
2
3
4
makefile复制代码srpc:       rpc组件
rpc-api: 接口组件(demo):封装tcp协议接口
rpc-client: 客户端组件(demo)
rpc-server: 服务端组件

3、tcp使用:

1
复制代码(1)定义接口:

image

1
复制代码(2)服务端实现:

image

1
复制代码(3)服务端启动:

image

1
复制代码(4)客户端调用:

image

4、websocket使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
java复制代码(1)服务端启动:

如上

(2)服务端实现:
@Service
public class ServerSimpleListener implements ServerListener {
@Override
public void doAction(String msg, Channel channel) {
log.info(msg+":"+msg);
TextWebSocketFrame tws = new TextWebSocketFrame("收到信息");
channel.writeAndFlush(tws);
TextWebSocketFrame tws2 = new TextWebSocketFrame("大家都收到信息");
NettyWebSocket.send2All(tws2);
}
}

(3)客户端调用:
if(channel == null || !channel.isActive()) {
channel = WebsocketClient.connectToServer("http://127.0.0.1:1232?665887");
}
TextWebSocketFrame frame = new TextWebSocketFrame("你好\r\n");
channel.writeAndFlush(frame);

(4)客户端回调:
@Service
public class SimpleListener implements BaseListener {
private final static Logger LOGGER = LoggerFactory.getLogger(SimpleListener.class);
/**
* 一个简单的Listener方法
* @param event Guava规定此处只能有一个参数
*/
@Override
public void doAction(final String event){
if (LOGGER.isInfoEnabled()){
LOGGER.info("Received event [{}] and will take a action", event);
}
}

(5)支持指定channel,群发,离线发送

(6)逻辑图如下:

image

七、github地址:github.com/yeqi86/srpc…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

MQ不公平分发消息和预取值| RabbitMQ系列(五)

发表于 2021-08-10

这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战


相关文章

RabbitMQ系列汇总:RabbitMQ系列


前言

  • 在最开始的时候我们学习到 RabbitMQ 分发消息采用的轮训分发,但是在某种场景下这种策略并不是很好。
  • 比方说有两个消费者在处理任务,其中有个消费者 A 处理任务的速度非常快,而另外一个消费者 B 处理速度却很慢,这个时候我们还是采用轮训分发的化就会到这处理速度快的这个消费者很大一部分时间 处于空闲状态,而处理慢的那个消费者一直在干活。
  • 这种分配方式在这种情况下其实就不太好,但是 RabbitMQ 并不知道这种情况它依然很公平的进行分发。
  • 这个时候我们可以使用不公平分发来实现,就是能者多劳模式!你能干,哦,那你就多干点活吧。我比较菜,所以我少干点活。

一、不公平分发

  • 生产者
+ 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
java复制代码/**
* 这是一个测试的生产者
*@author DingYongJun
*@date 2021/8/1
*/
public class DyProducerTest_xiaoxiyingda {
/**
* 这里为了方便,我们使用main函数来测试
* 纯属看你个人选择
* @param args
*/
public static void main(String[] args) throws Exception{
//使用工具类来创建通道
Channel channel = RabbitMqUtils.getChannel();

/**
* 生成一个队列
* 1.队列名称
* 2.队列里面的消息是否持久化 默认消息存储在内存中
* 3.该队列是否只供一个消费者进行消费 是否进行共享 true 可以多个消费者消费
* 4.是否自动删除 最后一个消费者端开连接以后 该队列是否自动删除 true 自动删除
* 5.其他参数
*/
//这是持久化的参数。false不进行持久化,true进行持久化
boolean durable = true;
channel.queueDeclare(QueueNameConstant.XIAOXIYINGDA_MODEL,durable,false,false,null);

/**
* 发送一个消息
* 1.发送到那个交换机
* 2.路由的 key 是哪个
* 3.其他的参数信息
* 4.发送消息的消息体
*/
Scanner sc = new Scanner(System.in);
System.out.println("请输入信息");
while (sc.hasNext()) {
String message = sc.nextLine();
//MessageProperties.PERSISTENT_TEXT_PLAIN;这个代表消息持久化到硬盘
channel.basicPublish("",QueueNameConstant.XIAOXIYINGDA_MODEL,MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());
System.out.println("生产者发出消息" + message);
}
}
}
  • 消费者
+ 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
java复制代码/**
* 这是一个测试的消费者
*@author DingYongJun
*@date 2021/8/1
*/
public class DyConsumerTest_xiaoxiyingda01 {

public static void main(String[] args) throws Exception{
//使用工具类来创建通道
Channel channel = RabbitMqUtils.getChannel();

System.out.println("我是消费者A,我在等待接收消息!");
DeliverCallback deliverCallback = (String var1, Delivery var2)->{
String message= new String(var2.getBody());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(message);
//true 代表批量应答 channel 上未应答的消息 false 单条应答
boolean multiple = false;
channel.basicAck(var2.getEnvelope().getDeliveryTag(),multiple);
};
CancelCallback cancelCallback = (String var1)->{
System.out.println("消息消费被中断");
};
//不公平分发
int prefetchCount = 1;
channel.basicQos(prefetchCount);
/**
* 消费者消费消息
* 1.消费哪个队列
* 2.消费成功之后是否要自动应答 true 代表自动应答 false 手动应答
* 3.消费者未成功消费的回调
*/
channel.basicConsume(QueueNameConstant.XIAOXIYINGDA_MODEL,false,deliverCallback,cancelCallback);
}
}
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
java复制代码/**
* 这是一个测试的消费者
*@author DingYongJun
*@date 2021/8/1
*/
public class DyConsumerTest_xiaoxiyingda02 {

public static void main(String[] args) throws Exception{
//使用工具类来创建通道
Channel channel = RabbitMqUtils.getChannel();

System.out.println("我是消费者B,我在等待接收消息!");
DeliverCallback deliverCallback = (String var1, Delivery var2)->{
String message= new String(var2.getBody());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(message);
//true 代表批量应答 channel 上未应答的消息 false 单条应答
boolean multiple = false;
channel.basicAck(var2.getEnvelope().getDeliveryTag(),multiple);
};
CancelCallback cancelCallback = (String var1)->{
System.out.println("消息消费被中断");
};

//不公平分发
int prefetchCount = 1;
channel.basicQos(prefetchCount);

/**
* 消费者消费消息
* 1.消费哪个队列
* 2.消费成功之后是否要自动应答 true 代表自动应答 false 手动应答
* 3.消费者未成功消费的回调
*/
channel.basicConsume(QueueNameConstant.XIAOXIYINGDA_MODEL,false,deliverCallback,cancelCallback);
}
}
+ 执行结果 - ![image-20210803143650779.png](https://gitee.com/songjianzaina/juejin_p15/raw/master/img/6537f8004a179eeefe741c4a3980853cc887c35467bf609f1644df5ed273330a) - ![image-20210803143701013.png](https://gitee.com/songjianzaina/juejin_p15/raw/master/img/ce081a9e79f4c063ee805fb53c603203dafca94ceda57b27c31f5bb3c7013bce) - ![image-20210803143711503.png](https://gitee.com/songjianzaina/juejin_p15/raw/master/img/858aaa289e16797bd7d0aef3f0c7029169e3f0379a756857fcdb56a57c4db177) + 由结果可以得出一定的结论 - 明显A执行了7条,B执行了3条。 - 两个消费者都设置了不公平分发模式。 - 当A消费者效率快(即一秒执行完),B消费者效率慢(三秒执行完)。 - MQ会自动判断谁干的快,然后给干的快的人分配的多。 - 其实本质上就是不休息呀。干完了就继续来领新的任务! - 生产队的驴都不敢这么压榨吧!哈哈~ + 意思就是如果这个任务我还没有处理完或者我还没有应答你,你先别分配给我,我目前只能处理一个 任务。 + 然后 rabbitmq 就会把该任务分配给没有那么忙的那个空闲消费者,当然如果所有的消费者都没有完 成手上任务。 + 队列还在不停的添加新任务,队列有可能就会遇到队列被撑满的情况,这个时候就只能添加 新的 worker 或者改变其他存储任务的策略。 + 设置代码: -
1
2
3
java复制代码//不公平分发
int prefetchCount = 1;
channel.basicQos(prefetchCount);
-
1
2
3
4
java复制代码//源码为证    
public void basicQos(int prefetchCount) throws IOException {
this.basicQos(0, prefetchCount, false);
}
- int 默认值是0 这个不用解释吧。也就是我们不设置,这个值就是零。 - 设置为1时证明启动了不公平分发模式。

二、预取值分发

  • 本身消息的发送就是异步发送的,所以在任何时候,channel 上肯定不止只有一个消息另外来自消费 者的手动确认本质上也是异步的。
  • 因此这里就存在一个未确认的消息缓冲区,因此希望开发人员能限制此缓冲区的大小,以避免缓冲区里面无限制的未确认消息问题。
  • 当然这将使吞吐量变得很低,特别是消费者连接延迟很严重的情况下,特别是在消费者连接等待时间较长的环境中。对于大多数应用来说,稍微高一点的值将是最佳的。
  • 生产者和上面的例子保持一致即可。无需改动
  • 消费者
+ 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
java复制代码/**
* 这是一个测试的消费者
*@author DingYongJun
*@date 2021/8/1
*/
public class DyConsumerTest_xiaoxiyingda01 {

public static void main(String[] args) throws Exception{
//使用工具类来创建通道
Channel channel = RabbitMqUtils.getChannel();

System.out.println("我是消费者A,我在等待接收消息!");
DeliverCallback deliverCallback = (String var1, Delivery var2)->{
String message= new String(var2.getBody());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(message);
//true 代表批量应答 channel 上未应答的消息 false 单条应答
boolean multiple = false;
channel.basicAck(var2.getEnvelope().getDeliveryTag(),multiple);
};
CancelCallback cancelCallback = (String var1)->{
System.out.println("消息消费被中断");
};
//不公平分发
int prefetchCount = 5;
channel.basicQos(prefetchCount);
/**
* 消费者消费消息
* 1.消费哪个队列
* 2.消费成功之后是否要自动应答 true 代表自动应答 false 手动应答
* 3.消费者未成功消费的回调
*/
channel.basicConsume(QueueNameConstant.XIAOXIYINGDA_MODEL,false,deliverCallback,cancelCallback);
}
}
+ 消费者B代码一致,就是休眠时间加长点,prefetchCount的值设置为3,方便看出来区别。
  • 执行结果
+ ![image-20210803151058489.png](https://gitee.com/songjianzaina/juejin_p15/raw/master/img/e7f597a9e5e21b80cdf0497dc765e915c0f91e96d6fb5ba869971bae0985b164)
+ ![image-20210803151107784.png](https://gitee.com/songjianzaina/juejin_p15/raw/master/img/211ce0910cedf844a0e99ad73f4d162a43f9e22c3fc570411730cb0dcb40126b)
+ ![image-20210803151137282.png](https://gitee.com/songjianzaina/juejin_p15/raw/master/img/5d01298f45810b22dad1e9f3e2d6c1943abd1d6806e73adf8b5e04228ff54f77)
  • 由以上的结果可以得出结论
+ prefetchCount,这个值设置为3时,表示当前通道内,最多只有三条进来,再多就在外面排队。
+ 也就是说,当我们发完八条数据时,A有5条,B有3条。再来一条时,谁先消费完一条,新的就被谁去消费。
+ 如果队列中一直有数据进入。那么会AB消费者会被一直补充消息。会保持满prefetchCount值的消息数在通道内。

路漫漫其修远兮,吾必将上下求索~

如果你认为i博主写的不错!写作不易,请点赞、关注、评论给博主一个鼓励吧~hahah

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Class文件结构分析

发表于 2021-08-10

Class文件结构分析

1. Class文件的结构概览图

在这里插入图片描述

2. 每一项数据说明

类型 名称 数量 说明
u4 magic 1 魔数:确定一个文件是否是Class文件
u2 minor_version 1 Class文件的次版本号
u2 major_version 1 Class文件的主版本号:一个JVM实例只能支持特定范围内版本号的Class文件(可以向下兼容)。
u2 constant_pool_count 1 常量表数量
cp_info constant_pool constant_pool_count-1 常量池:以理解为Class文件的资源仓库,后面的其他数据项可以引用常量池内容。
u2 access_flags 1 类的访问标志信息:用于表示这个类或者接口的访问权限及基础属性。
u2 this_class 1 指向当前类的常量索引:用来确定这个类的的全限定名。
u2 super_class 1 指向父类的常量的索引:用来确定这个类的父类的全限定名。
u2 interfaces_count 1 接口的数量
u2 interfaces interfaces_count 指向接口的常量索引:用来描述这个类实现了哪些接口。
u2 fields_count 1 字段表数量
field_info fields fields_count 字段表集合:描述当前类或接口声明的所有字段。
u2 methods_count 1 方法表数量
method_info methods methods_count 方法表集合:只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法。
u2 attributes_count 1 属性表数量
attributes_info attributes attributes_count 属性表集合:用于描述某些场景专有的信息,如字节码的指令信息等等。

3. Class文件16进制解析

3.1 魔数。
  • Class文件开始是4个字节定义为魔数(Magic Number);
  • 唯一作用:确定一个文件是否是Class文件;
  • 魔数可以自由选择,只要没有广泛使用而且不会引起混淆的即可,这样就不会因为扩展名改变而无法识别;其他许多文件类型格式头都存在魔数,如gif、jpeg等
  • Class文件的魔数为”0xCAFEBABE”(咖啡宝贝),比照ClassFileTest.class如下:
3.2 方法表解析
  • public void bar()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
csharp复制代码00 01 访问控制符 public 
00 11 这里11是16进制转10进制为17,对应 Constant pool: #17 = Utf8 bar
00 0b 描述符0b是16进制转10进制为11,对应 Constant pool: #11 = Utf8 ()V
翻译过来:public void bar()

00 01 表示有1个属性表attribute_count
属性表attribute_info(u2,u4,u1*length)
00 0c 表示attribute_name_index,索引,这里0c是16进制转10进制为12,对应 Constant pool: #12 = Utf8 Code
00 00 00 38 表示attribute_length,代码占的大小,这里38是16进制转10进制为56,表示bar()方法占了56个字节

00 02 表示max_stack最大栈深是2
00 01 表示max_locals最大变量数是1
00 00 00 0a 表示code_length代码行数,这里0a是16进制转10进制为10

args_size 方法的参数有多少个(默认是this,如果方法是static那么就是0)

对应字节码
00 02 00 01 00 00 00 0a b2 00 02 b2 00 03 b6
00 04 b1 00 00 00 02 00 0d 00 00 00 0a 00 02 00
00 00 0f 00 09 00 10 00 0e 00 00 00 0c 00 01 00
00 00 0a 00 0f 00 10 00 00

b2 getstatic
00 nop
03 iconst_0
b6 invokevirtual
04 iconst_1

本地行号表
LineNumberTable:
line 15: 0
line 16: 9

本地变量表
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/cecjx/TestM;
Start+Length 表示一个本地变量的作用域(0对应行是15,10对应行是17||即表示在该方法中,该变量的作用范围是15行到17行)
Slot 表示几个槽存储
Name 表示简单名字

签名
Signature
伪泛型。

在这里插入图片描述

  • 静态变量初始化
1
2
3
4
5
6
7
8
9
10
yaml复制代码 static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: iconst_2
1: putstatic #3 // Field m:I
4: return
LineNumberTable:
line 12: 0

在这里插入图片描述

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

docker入门:单机elasticsearch安装记录,保

发表于 2021-08-10

这是我参与 8 月更文挑战的第 10 天,活动详情查看: 8月更文挑战

用过传统方式安装elasticsearch的小伙伴都知道,有非常多的坑需要填。经常抛出莫名的异常,所以本篇楼主将自己安装单机elasticsearch过程记录下来,帮助小伙伴闭坑。

注意:kibana,es,es插件版本要相同

1.拉取镜像

1
复制代码docker pull elasticsearch:7.10.1

2.新建文件夹

同上文所述相同,需要在宿主机上挂载配置文件与数据文件。

1
2
bash复制代码mkdir -p /usr/local/elasticsearch/config
mkdir -p /usr/local/elasticsearch/data

3.修改配置文件

在中间价的安装中很多个性化设置需要自行修改。这里进入上文新建好的文件夹中。新增elasticsearch.yml文件。

1
2
bash复制代码cd /usr/local/elasticsearch/config/
vi elasticsearch.yml

elasticsearch.yml配置如下

1
2
3
4
5
6
7
yaml复制代码network.host: 0.0.0.0   
network.bind_host: 0.0.0.0 #外网可访问

http.cors.enabled: true
http.cors.allow-origin: "*"
xpack.security.enabled: true # 这条配置表示开启xpack认证机制 spring boot连接使用
xpack.security.transport.ssl.enabled: true

xpack.security配置后,elasticsearch需要账号密码使用,建议安排上。如果使用springboot查询,那一定要设置,否者会报错!

4.启动

1
2
3
4
5
6
7
bash复制代码docker run -p 9200:9200 --name elasticsearch \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms1g -Xmx2g" \
-v /usr/local/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /usr/local/elasticsearch/data:/usr/share/elasticsearch/data \
-v /usr/local/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.10.1

参数解释:

  • -p 9200:9200 :指定端口号
  • –name elasticsearch \ :指定容器名称
  • -e “discovery.type=single-node” \ :单机模式
  • -e ES_JAVA_OPTS=”-Xms1g -Xmx2g” \ :指定内存
  • -v /usr/local/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \ :指定config在宿主机位置
  • -v /usr/local/elasticsearch/data:/usr/share/elasticsearch/data \ :指定数据在宿主机位置
  • -v /usr/local/elasticsearch/plugins:/usr/share/elasticsearch/plugins \ :指定插件在宿主机位置
  • -d elasticsearch:7.10.1 :指定镜像

5.初始化密码

此项仅在上文xpack配置的情况下才需要执行,首先进入容器命令行,然后直行初始化命令:

执行

1
2
bash复制代码docker exec -it 容器id /bin/bash
bin/elasticsearch-setup-passwords interactive

然后依次输入密码(需要输入很多很多次,别挣扎了,输入吧!)

image.png

6.验证

访问ip:9200,如果上文开启了xpack.security,需要输入账号密码。

账号/密码:elastic/上文设置的密码

如果出现以下页面,则代表成功。
image.png

7.安装插件

1.安装

下载地址:github.com/medcl/elast… 主要需要选择与es相同版本

放于上文配置plugins路径,然后新建ik文件夹,将解压后的文件全部放于ik文件夹中。

image.png

重启docker容器

1
复制代码docker restart 556b198b7616

2.验证

重启后,请通过查询验证分词器是否生效。

1
2
3
4
5
sql复制代码GET _analyze?pretty
{
"analyzer": "ik_max_word",
"text": "我吃西红柿"
}

如果出现以下结果,则证明安装生效。

image.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

微信公众号录音文件(amr文件转mp3) 音频转码工具 原理

发表于 2021-08-10

音频转码工具,主要用于将微信语音 amr 格式转换为 mp3 格式以便在 html5 的 audio 标签中进行播放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ini复制代码1.调用微信提供的接口获取录音的InputStream字节流
public InputStream getInputStream(String mediaId) {
InputStream is = null;
try {
  String URL_DOWNLOAD_TEMP_MEDIA = "https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID";
String url = URL_DOWNLOAD_TEMP_MEDIA.replace("ACCESS_TOKEN", "自己写代码获取accessToken").replace("MEDIA_ID", mediaId);
URL urlGet = new URL(url);
HttpURLConnection http = (HttpURLConnection) urlGet.openConnection();
http.setRequestMethod("GET"); // 必须是get方式请求
http.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
http.setDoOutput(true);
http.setDoInput(true);
System.setProperty("sun.net.client.defaultConnectTimeout", "30000");// 连接超时30秒
System.setProperty("sun.net.client.defaultReadTimeout", "30000"); // 读取超时30秒
http.connect();
// 获取文件转化为byte流
is = http.getInputStream();
} catch (Exception e) {
e.printStackTrace();
}
return is;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
ini复制代码2.将获取到的字节流保存为amr文件
public String downloadMediaId(HttpServletRequest request, String mediaId) {
String relfilePath = null;
InputStream inputStream = getInputStream(mediaId);
FileOutputStream fileOutputStream = null;
try {
//服务器资源保存路径
String savePath = request.getSession().getServletContext().getRealPath("/") + "upload/" + DateUtil.getYear() + "/wxmedia/audio/";
savePath = savePath + "audio/";
String filename = String.valueOf(System.currentTimeMillis()) + ".amr";
relfilePath = "upload/" + DateUtil.getYear() + "/wxmedia/audio/" + filename;
File file = new File(savePath);
if (!file.exists()) {
file.mkdirs();
}
byte[] data = new byte[1024];
int len = 0;
fileOutputStream = new FileOutputStream(savePath + filename);
while ((len = inputStream.read(data)) != -1) {
// 判断结果是否有错
if (new String(data).indexOf("errmsg") > -1) {
return null;
}
fileOutputStream.write(data, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return relfilePath;
}

3.将保存的amr文件转成mp3文件

1
2
3
4
5
arduino复制代码public void amrToMp3(String sourcePath, String targetPath) {
File source = new File(sourcePath);
File target = new File(targetPath);
AudioUtils.amrToMp3(source, target);
}

4.所需的jar包依赖

1
2
3
4
5
6
xml复制代码<!--amr文件转音频map文件-->
<dependency>
<groupId>com.github.dadiyang</groupId>
<artifactId>jave</artifactId>
<version>1.0.3</version>
</dependency>

音频转码工具

支持 Linux/Windows/Mac 平台

因为是基于 JAVE 项目的修改,而 JAVE 是依赖 ffmpeg 所以可以适用于所有 ffmpeg 所支持的文件格式的转换。具体可以查看 JAVE 官方文档

原理

  1. 初始化时判断当前运行环境,将bin目录中对应的 ffmpeg 可执行文件拷贝到临时目录中
  2. 根据文件类型及配置通过 Runtime.getRuntime().exec(cmd) 执行 ffmpeg 对应的转码命令

JAVE 项目的问题

ffmpeg 是依赖运行环境的,JAVE 项目封装了ffmpeg,它通过上述的原理使 java 可以调用ffmpeg而且支持跨平台。

  1. 项目老旧没再维护。官网最近版本是2009年发布的,其依赖的ffmpeg早已过时,很多情况下用不了。
  2. 转码一直报异常 EncoderException: Stream mapping
  3. 没有发布maven仓库,而且 JAVE 本身也不是一个maven项目
  4. 不支持mac

本项目特点

本项目为解决上述问题而生。

  • 这是一个maven项目,而且已发布到中央仓库。
  • 项目依赖的 ffmpeg 可执行文件经过验证可以使用(单元测试中提供了一个简单的检验方法)
  • 解决了amr转mp3出现的 EncoderException: Stream mapping
  • 支持 Linux/Windows/Mac 平台

扩展

如果程序无法通过拷贝资源文件的方式获取到 ffmpeg 的可执行文件或者内置的 ffmpeg 不支持你所使用的操作系统

你可以通过环境变量或者在 java 中设置 System.setProperty("ffmpeg.home", "ffmpeg可执行文件所在的目录") 的方式指定你的系统中安装的可用的 ffmpeg 文件的目录

如 System.setProperty("ffmpeg.home", "/usr/local/bin/")

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SpringBoot整合Druid配置数据源监控 前言 实战

发表于 2021-08-09

这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战

| 作者:江夏

| CSDN:blog.csdn.net/qq_41153943

| 掘金:juejin.cn/user/651387…

| 知乎:www.zhihu.com/people/1024…

| GitHub:github.com/JiangXia-10…

本文大概1165字,建议阅读9分钟

前言

我们在进行数据访问声明模板和repository之前都需要配置数据源用来连接数据库。数据源就是连接到数据库的一条路径,数据源中并无真正的数据,它仅仅记录的是你连接到哪个数据库,以及如何连接。

常见的数据源有很多,比如dbcp,c3p0,druid。目前很多公司使用的是阿里巴巴开源的Druid数据源,因为该数据源不仅能够进行数据访问并且有成套的数据源以及安全监控。接下来就通过SpringBoot整合Druid数据源,并配置对数据源的监控。

实战

首先需要新建一个springboot项目,如何新建springboot项目可以参考之前的文章:SpringBoot入门:使用IDEA和Eclipse构建第一个SpringBoot项目,这里需要的依赖文件的配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
xml复制代码<!--引入druid数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.8</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--引入log4jlog4j日志-->
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>

关于druid数据源的依赖可以去网址:mvnrepository.com/artifact/co… 。

接下来新建个配置文件格式为yml格式,配置进行数据访问所需要的一些配置信息,下面是整个的yml配置文件的配置信息,具体的参数的信息如注释所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
yaml复制代码spring:
datasource:
    #用户名
username: root
    #密码
password: 123456
    #数据库的url
url: jdbc:mysql://localhost:3306/jdbc?serverTimezone=Asia/Shanghai
#驱动名称
driver-class-name: com.mysql.cj.jdbc.Driver
    #对象的类型,这里是druid数据源
type: com.alibaba.druid.pool.DruidDataSource
    #初始化连接数:默认值 0
initialSize: 5
    #最小空闲连接数,默认值 0,当高峰期过后,连接使用的少了,但是连接池还是会为你留着minIdle的连接,以备高峰期再次来临的时候不需要创建连接当高峰期过后,连接使用的少了,但是连接池还是会为你留着minIdle的连接,以备高峰期再次来临的时候不需要创建连接
minIdle: 5
#最大活跃连接数,这个数字不宜设置过大,太多的并发连接对数据库的压力很大,甚至会导致雪崩,这是一定要注意的。但是如果设置过小,而应用的服务线程数有很高,可能会导致有的服务线程拿不到连接,所以服务的线程数和数据库连接数是需要经过配合调整的最大活跃连接数,这个数字不宜设置过大,太多的并发连接对数据库的压力很大,甚至会导致雪崩,这是一定要注意的。但是如果设置过小,而应用的服务线程数有很高,可能会导致有的服务线程拿不到连接,所以服务的线程数和数据库连接数是需要经过配合调整的
maxActive: 20
#最大等待毫秒数, 单位为 ms, 超过时间会出错误信息最大等待毫秒数, 单位为 ms, 超过时间会出错误信息
maxWait: 60000
    #每过多少秒运行一次空闲连接回收器,这里设置的是30秒
    timeBetweenEvictionRunsMillis: 30000
    #连接池中的连接空闲多少时间后被回收,这里设置的是30分钟   
minEvictableIdleTimeMillis: 1800000
# 验证使用的SQL语句
validationQuery: SELECT 1 FROM DUAL
#指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除.
testWhileIdle: true
# 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnBorrow: false
#归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
#testOnBorrow和testOnReturn在生产环境一般是不开启的,主要是性能考虑。失效连接主要通过testWhileIdle保证,如果获取到了不可用的数据库连接,一般由应用处理异常
testOnReturn: false
    #是否缓存preparedStatement,即PSCache。PSCache对支持游标的数据库性能提升巨大
poolPreparedStatements: true
    #最大启用PSCache的数量
maxPoolPreparedStatementPerConnectionSize: 20
    # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
    filters: stat,wall,log4j
    # 合并多个DruidDataSource的监控数据
useGlobalDataSourceStat: true
#通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

接着新建一个Druid的配置类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
typescript复制代码@Configuration
public class DruidConfig {
@ConfigurationProperties(prefix = "spring.datasource")
    //将druid数据源注入ioc容器
@Bean
public DataSource druid(){
return new DruidDataSource();
}
//配置Druid的监控
//1、配置管理后台的Servlet
@Bean
public ServletRegistrationBean DruidServlet(){
ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
Map<String,String> initParams = new HashMap<>();
        //druid后台登录的用户名
initParams.put("loginUsername","admin");
        //druid后台登录的密码
initParams.put("loginPassword","111111");
        //默认就是允许所有访问
        initParams.put("allow","");
bean.setInitParameters(initParams);
return bean;
    }
    
  //2、配置监控的filter
@Bean
public FilterRegistrationBean druidStatFilter(){
FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new WebStatFilter());
Map<String,String> initParams = new HashMap<>();
        initParams.put("exclusions","*.js,*.css,/druid/*");
        bean.setInitParameters(initParams);
        bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
    }

}

@ConfigurationProperties注解获取yml配置文件的配置,prefix = “spring.datasource”表示前缀是spring.datasource的配置信息。
到了这一步就已经可以启动项目,看看效果了,启动项目后在浏览器的地址栏输入:

1
bash复制代码http://localhost:8080/druid/

会跳转到Druid管理后台的登录页面,输入刚刚设置的用户名和密码登录管理后台,即可看到Druid监控的相关信息:

图片

图片

图片

图片

接着测试数据的增删改查,在数据库中新建一个表,并插入数据,如下所示。

图片

然后新建一个controller类,添加从数据库中查询数据的方法:

1
2
3
4
5
6
7
8
9
10
11
less复制代码@Controller
public class HelloController {
@Autowired
    JdbcTemplate jdbcTemplate;
@ResponseBody
@GetMapping("/select")
public Map<String,Object> map(){
List<Map<String, Object>> list = jdbcTemplate.queryForList("select * FROM demo");
return list.get(0);
}
}

重新启动SpringBoot项目,并登录Druid后台,可以看到在sql监控下的信息是空的:

图片

然后新建一个标签页,输入请求:

1
bash复制代码http://localhost:8080/select

图片

在去监控后台的sql监控就可以发现已经监控到了刚刚请求中执行的sql语句:

图片

图片

结尾

以上就是如何通过SpringBoot整合Druid数据源以及配置数据源监控,当然Druid的使用不仅仅这些,还需要自己的不断的学习才能掌握更多的知识。

相关推荐:

  • Spring注解(二):@ComponentScan自动扫描组件
  • Spring常用注解大全,值得你的收藏!!!
  • Spring注解(七):使用@Value对Bean进行属性赋值
  • SpringBoot开发Restful风格的接口实现CRUD功能
  • 分布式缓存中间件Redis入门

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【Spring Boot 快速入门】三、Spring Boo

发表于 2021-08-09

这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战

收录专栏

Spring Boot 快速入门

Java全栈架构师

前言

  上一文文章介绍了Spring Boot的最简单的构成,并启动服务展现了了第一个Spring Boot的项目吗,没有连接到数据库。本文针对Spring Boot最简项目集成MyBatis,启动运行,连接数据库查询数据。好了,开始新的一文的介绍。

初识MyBatis

  MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

  使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
xml复制代码        <!-- mybatis start-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.11</version>
</dependency>

MyBatis 特点

  MyBatis能够被广泛使用,与他的优点是分不开的。主要是简单易学、配置灵活、简洁的标签、支持动态SQL,以下是汇总的MyBatis优点:

  • 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
  • 配置灵活:MyBatis不会对应用程序或者数据库的现有设计强加任何影响。 SQL写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
  • 解除SQL与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。SQL和代码的分离,提高了可维护性。
  • 提供映射标签:支持对象与数据库的ORM字段关系映射
  • 提供对象关系映射标签:支持对象关系组建维护
  • 提供xml标签:支持编写动态sql。

MyBatis 配置

  本次介绍的MyBatis 配置是application.properties文件,当然还有yml类型的文件。

  • spring.datasource.url:数据库服务器连接地址
  • spring.datasource.username:数据库用户名
  • spring.datasource.password:数据库用户密码
  • spring.datasource.driver-class-name:数据源驱动类名
  • spring.datasource.type:数据源驱动类型
  • mybatis.mapper-locations:本地sql配置文件路径地址
1
2
3
4
5
6
7
ini复制代码spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
spring.datasource.username=test
spring.datasource.password=12345678
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

mybatis.mapper-locations=classpath*:mapper/**/*.xml

项目结构

项目结构如下图,主要包含启动类DemoMyBatisApplication、Controller、Service、ServiceImpl、Mapper、application.properties和pom.xml

图片.png

@MapperScan注解

  @MapperScan 配置一个或多个包路径,自动的扫描这些包路径下的类,自动的为它们生成代理类。一定要加上这个注解,否则启动项目会报异常。

项目启动

  在项目中找到DemoMyBatisApplication启动类,右击选择启动即可。

查询数据

  在浏览器中输入:http://127.0.0.1:8888/user/getAllUser 即可查询到用户数据信息。如下图返回数据信息

图片.png

上源码

  本次项目使用的源码可以直接拿来使用学习,作者都经过测试,可以正常运行。以下源码仅供学习使用。

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
xml复制代码<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>mybatis</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mybatis</name>
<description>Demo project for Spring Boot and MyBatis and Swagger</description>

<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>
<!-- commons start -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
<!-- commons end -->

<!-- mybatis start-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.11</version>
</dependency>
<!-- mybatis end-->

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

application.properties

1
2
3
4
5
6
7
8
9
10
ini复制代码server.port=8888

# mysql
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
spring.datasource.username=test
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

mybatis.mapper-locations=classpath*:mapper/**/*.xml

DemoMyBatisApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
kotlin复制代码package com.example.demo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
* @ClassName DemoMyBatisApplication
* @Description: 启动类
* @Author JavaZhan @公众号:Java全栈架构师
* @Date 2020/6/13
* @Version V1.0
**/
@SpringBootApplication
@MapperScan("com.example.demo.mapper")
public class DemoMyBatisApplication {

public static void main(String[] args) {
SpringApplication.run(DemoMyBatisApplication.class, args);
}

}

UserMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
xml复制代码<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
<resultMap id="BaseResultMap" type="com.example.demo.module.User">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="age" jdbcType="INTEGER" property="age" />
</resultMap>
<sql id="Base_Column_List">
id, name, age
</sql>
<select id="getAllUser" resultType="com.example.demo.module.User">
SELECT * FROM user
</select>
</mapper>

UserController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
kotlin复制代码package com.example.demo.controller;

import com.example.demo.module.User;
import com.example.demo.service.UserService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import java.util.List;

/**
* @ClassName UserController
* @Description: TODO
* @Author JavaZhan @公众号:Java全栈架构师
* @Date 2020/6/13
* @Version V1.0
**/
@RequestMapping("user")
@Controller
public class UserController {

@Resource
private UserService userService;

@RequestMapping("getAllUser")
@ResponseBody
public List<User> getAllUser(){
return userService.getAllUser();
}
}

  具体的实现方式,用户可以根据业务逻辑去开发处理,本文就不罗列具体实现类的相关代码,大家根据需要开发即可。

结语

  本次基于Spring Boot集成MyBatis的项目就完成了,粗枝大叶的建立了一个集成的框架,当然还有更深入的配置设置。本文主要针对新手入门练习使用,希望本文可以帮助到你。感谢阅读。

  作者介绍:【小阿杰】一个爱鼓捣的程序猿,JAVA开发者和爱好者。公众号【Java全栈架构师】维护者,欢迎关注阅读交流。

  好了,感谢您的阅读,希望您喜欢,如对您有帮助,欢迎点赞收藏。如有不足之处,欢迎评论指正。下次见。

推荐阅读:

我的第一个Spring Boot项目启动啦!

周末建立了Spring Boot专栏,欢迎学习交流

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Apache ShenYu(incubating) 24

发表于 2021-08-09

声明:本文中的Apache ShenYu 都指的是 Apache ShenYu (incubating)
本人作者:肖宇 Apache ShenYu(incubating) Founder && PPMC
2.4.0 Release Manager : 张永伦 Apache ShenYu(incubating) PPMC && Apache ShardingSphere PMC

所有的朋友们:

Apache ShenYu网关是原 Dromara/soul 网关捐献给Apache基金会后改名而来,
此次发布的 2.4.0 版本是 Apache ShenYu 网关进入Apache孵化器后的首个版本。这个版本涉及很多新功能的增加,
项目名称,包名以及maven依赖坐标的变更。

Apache ShenYu 是什么?

Apache ShenYu是使用Java reactor编程方式开发的,具有异步,高性能,跨语言等特性的 API 网关。
在流量控制方面,有精美的Admin控制台,能够精准,动态控制流量,满足复杂的业务场景。
在功能方面,它使用插件化的设计思想,支持许多常见的协议:如 http/https, Dubbo、 Spring Cloud、 GRPC、 Motan、 Sofa、 Tars 等。
同时内置十分丰富的功能插件,如 熔断,限流,鉴权,黑白名单,防火墙,监控,参数更改等等插件。其架构图如下:

流量控制

对流量的控制是网关的灵魂,针对流量控制,Apache ShenYu 设计了选择器,规则 2个概念,来控制流量。

选择器和 规则是 Apache ShenYu 网关中最灵魂的东西。掌握好它,你可以对任何流量进行管理。

一个插件有多个选择器,一个选择器对应多种规则。选择器相当于是对流量的一级筛选,规则就是最终的筛选。

对一个插件而言,我们希望根据我们的配置,达到满足条件的流量,插件才会被执行。

选择器和规则就是为了让流量在满足特定的条件下,才去执行我们想要的,这种规则首先要明白。

插件、选择器和规则执行逻辑如下,当流量进入到Apache ShenYu网关之后,会先判断是否有对应的插件,该插件是否开启;然后判断流量是否匹配该插件的选择器。

然后再判断流量是否匹配该选择器的规则。如果请求流量能满足匹配条件才会执行该插件,否则插件不会被执行,处理下一个。

Apache ShenYu网关就是这样通过层层筛选完成流量控制。其流程图如下 :

流量筛选

流量筛选,是选择器和规则的灵魂,对应为选择器与规则里面的匹配条件(conditions),根据不同的流量筛选规则,我们可以处理各种复杂的场景。

流量筛选可以从Header, URI, Query, Cookie 等等Http请求获取数据,

然后可以采用 Match,=,SpEL,Regex,Groovy等匹配方式,匹配出你所预想的数据。

多组匹配添加可以使用And/Or的匹配策略。上述都是采用SPI的设计思想,用户可以自主进行扩展 :更多的请查看 : shenyu.apache.org/zh/projects…

其过程图如下 :

数据同步与缓存

为了提升网关的性能,Apache ShenYu 网关会将所有的流量控制规则缓存在JVM 内存里面。在集群部署/分布式场景中,Apache ShenYu 自主研发了一套 将 Admin 控制台的数据,远程同步到每一个 Apache ShenYu 网关节点 JVM内存 的方案。

每一种方案,采用 SPI 设计思想,以供用户灵活的选择。目前支持的方案有 HttpLongPull, Websocket, Zookeeper, Nacos, Consul, ETCD。 其整体流程如下 :

Admin控制台

为了方便用户快速便捷的控制流量以及网关的所有功能特性,Apache ShenYu 提供了 一个十分精美的Admin控制台,用户可以中英文切换,在这上面,可以随意的控制流量,启停插件,配置不同的参数与策略,这些操作更改通过上述的数据同步原理,同步到网关的 JVM内存。其后台示意图如下:

菜单/数据权限

网关的后台管理是十分重要的,为了针对企业级的用户,跨部门应用代理,Apache ShenYu设计了一整套的权限控制体系,它包含按钮级别的菜单权限,以及行数据级别的数据权限。并且这些权限控制在 Admin控制台 自主自动可配。

协议代理

协议代理是网关最核心的功能,目前 Apache ShenYu 支持 http 转成 http/https, Websocket,Dubbo、 Spring Cloud、 GRPC、 Motan、 Sofa、 Tars 等协议的转换,未来将支持 TCP, MQTT,MQTT 等协议。

Divide插件

Divide插件,是用来专门代理 http/https/websocket 等方式请求 Apache ShenYu 网关的插件。 它具有 负载均衡,流量预热, 节点发现,超时重试,超时控制 等功能。用户想要使用它,请在网关添加如下依赖, 然后在 Admin控制台 –> 插件管理 –>Divide插件将其设置为 开启。 更详细的介绍请看 : shenyu.apache.org/zh/projects…

1
2
3
4
5
6
7
8
9
10
xml复制代码<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-plugin-divide</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-plugin-httpclient</artifactId>
<version>${project.version}</version>
</dependency>

Dubbo插件

Dubbo插件,是Apache ShenYu网关将 http/https 请求转换成 dubbo协议的插件 。 它采用了Dubbo泛化调用的机制,整合了 Dubbo的客户端,具有服务发现,负载均衡 等功能。用户想要使用它,请在网关添加如下依赖, 然后在 Admin控制台 –> 插件管理 –> dubbo插件将其设置为 开启,并且配置上注册中心, 更详细的介绍请看 : shenyu.apache.org/zh/projects…

1
2
3
4
5
6
7
8
9
10
11
12
xml复制代码<!-- apache shenyu alibaba dubbo plugin start-->
<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-plugin-alibaba-dubbo</artifactId>
<version>${project.version}</version>
</dependency>
<!-- apache shenyu apache dubbo plugin start-->
<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-plugin-apache-dubbo</artifactId>
<version>${project.version}</version>
</dependency>

SpringCloud插件

SpringCloud插件,是Apache ShenYu网关代理 SpringCloud微服务业务的插件 。 它整合了 SpringCloud的注册中心,以及负载均衡服务,实现了服务的代理。用户想要使用它,请在网关添加如下依赖, 然后在 Admin控制台 –> 插件管理 –> SpringCloud插件将其设置为 开启。 更详细的介绍请看 : shenyu.apache.org/zh/projects…

1
2
3
4
5
xml复制代码<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-plugin-springcloud</artifactId>
<version>${project.version}</version>
</dependency>

GRPC插件

GRPC插件,是Apache ShenYu网关将 http/https 请求转换成 GRPC协议的插件 。 它整合了 GRPC 客户端,实现了 GRPC服务的代理。用户想要使用它,请在网关添加如下依赖, 然后在 Admin控制台 –> 插件管理 –> GRPC插件将其设置为 开启。 更详细的介绍请看 :shenyu.apache.org/zh/projects…

1
2
3
4
5
xml复制代码<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-plugin-grpc</artifactId>
<version>${project.version}</version>
</dependency>

Tars插件

Tars插件,是Apache ShenYu网关将 http/https 请求转换成 Tars协议的插件 。 Tars是腾讯开源的 RPC框架, 该插件整合了 Tars-JAVA 客户端,实现了 Tars服务的代理。用户想要使用它,请在网关添加如下依赖, 然后在 Admin控制台 –> 插件管理 –> Tars插件将其设置为 开启。 更详细的介绍请看 : shenyu.apache.org/zh/projects…

1
2
3
4
5
xml复制代码<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-plugin-tars</artifactId>
<version>${project.version}</version>
</dependency>

Sofa插件

Sofa插件,是Apache ShenYu网关将 http/https 请求转换成 Sofa-RPC协议的插件 。 它采用了Sofa泛化调用的机制,整合了 Sofa-RPC的客户端,具有服务发现,负载均衡 等功能。用户想要使用它,请在网关添加如下依赖, 然后在 Admin控制台 –> 插件管理 –> sofa插件将其设置为 开启,并且配置上注册中心。 更详细的介绍请看 : shenyu.apache.org/zh/projects…

1
2
3
4
5
xml复制代码<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-plugin-sofa</artifactId>
<version>${project.version}</version>
</dependency>

熔断限流

Hystrix插件

Hystrix插件,是Apache ShenYu网关整合Hystrix框架,提供请求熔断的功能,Hystrix熔断参数可动态化配置。用户想要使用它,请在网关添加如下依赖, 然后在 Admin控制台 –> 插件管理 –> Hystrix插件将其设置为 开启。 更详细的介绍请看 : shenyu.apache.org/zh/projects…

1
2
3
4
5
xml复制代码<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-plugin-hystrix</artifactId>
<version>${project.version}</version>
</dependency>

Sentinel插件

Sentinel插件,是Apache ShenYu网关整合Sentinel框架,提供请求熔断限流的功能,Sentinel熔断限流参数可动态化配置。用户想要使用它,请在网关添加如下依赖, 然后在 Admin控制台 –> 插件管理 –> Sentinel插件将其设置为 开启。 更详细的介绍请看 : shenyu.apache.org/zh/projects…

1
2
3
4
5
xml复制代码<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-plugin-sentinel</artifactId>
<version>${project.version}</version>
</dependency>

Resilience4j插件

Resilience4j插件,是Apache ShenYu网关整合Resilience4j框架,提供请求熔断限流的功能,Resilience4j熔断限流参数可动态化配置。用户想要使用它,请在网关添加如下依赖, 然后在 Admin控制台 –> 插件管理 –> Resilience4j插件将其设置为 开启。 更详细的介绍请看 : shenyu.apache.org/zh/projects…

1
2
3
4
5
xml复制代码<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-plugin-resilience4j</artifactId>
<version>${project.version}</version>
</dependency>

RateLimiter插件

RateLimiter插件,是Apache ShenYu网关使用redis,提供请求集群限流的功能,限流算法策略有:令牌桶算法,并发限流,漏桶算法,滑动窗口算法。用户想要使用它,请在网关添加如下依赖, 然后在 Admin控制台 –> 插件管理 –> RateLimiter插件将其设置为 开启,并且配置上redis。 更详细的介绍请看 : shenyu.apache.org/zh/projects…

1
2
3
4
5
xml复制代码<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-plugin-ratelimiter</artifactId>
<version>${project.version}</version>
</dependency>

安全/权限认证

Waf插件

Waf插件,是Apache ShenYu网关,用来对流量实现防火墙,主要用来拦截非法请求,或者异常请求,并且给与相关的拒绝策略,它提供了黑白名单配置的功能。用户想要使用它,请在网关添加如下依赖, 然后在 Admin控制台 –> 插件管理 –> Waf插件将其设置为 开启。 更详细的介绍请看 : shenyu.apache.org/zh/projects…

1
2
3
4
5
xml复制代码<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-plugin-waf</artifactId>
<version>${project.version}</version>
</dependency>

Sign插件

Sign插件,是Apache ShenYu网关,用来对请求进行签名认证。用户想要使用它,请在网关添加如下依赖, 然后在 Admin控制台 –> 插件管理 –> Sign插件将其设置为 开启。 更详细的介绍请看 : shenyu.apache.org/zh/projects…

1
2
3
4
5
xml复制代码<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-plugin-sign</artifactId>
<version>${project.version}</version>
</dependency>

JWT插件

JWT插件,是Apache ShenYu网关,是针对 http 请求头的 token 属性或者是 authorization 属性携带值进行鉴权判断,兼容 OAuth2.0。用户想要使用它,请在网关添加如下依赖, 然后在 Admin控制台 –> 插件管理 –> jwt插件将其设置为 开启。 更详细的介绍请看 : shenyu.apache.org/zh/projects…

1
2
3
4
5
xml复制代码<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-plugin-jwt</artifactId>
<version>${project.version}</version>
</dependency>

OAuth2插件

OAuth2插件,是Apache ShenYu网关,使用 Webflux OAuth2客户端实现,用于支持 OAuth2 协议。用户想要使用它,请在网关添加如下依赖, 然后在 Admin控制台 –> 插件管理 –> oauth2插件将其设置为 开启。 更详细的介绍请看 : shenyu.apache.org/zh/projects…

1
2
3
4
5
xml复制代码<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-plugin-oauth2</artifactId>
<version>${project.version}</version>
</dependency>

个性化处理

Rewrite插件

Rewrite插件,是Apache ShenYu网关,支持使用正则表达式来重写URI的插件。用户想要使用它,请在网关添加如下依赖, 然后在 Admin控制台 –> 插件管理 –> rewrite插件将其设置为 开启。 更详细的介绍请看 : shenyu.apache.org/zh/projects…

1
2
3
4
5
xml复制代码<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-plugin-rewrite</artifactId>
<version>${project.version}</version>
</dependency>

Redirect插件

Redirect插件,是Apache ShenYu网关,将请求进行重定向的插件,支持网关内部接口与外部地址。用户想要使用它,请在网关添加如下依赖, 然后在 Admin控制台 –> 插件管理 –> redirect插件将其设置为 开启。 更详细的介绍请看 : shenyu.apache.org/zh/projects…

1
2
3
4
5
xml复制代码<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-plugin-redirect</artifactId>
<version>${project.version}</version>
</dependency>

Request插件

Request插件,是Apache ShenYu网关容许用户对请求参数、请求头 以及 Cookie 进行添加、修改、删除等功能。用户想要使用它,请在网关添加如下依赖, 然后在 Admin控制台 –> 插件管理 –> request插件将其设置为 开启。 更详细的介绍请看 : shenyu.apache.org/zh/projects…

1
2
3
4
5
xml复制代码<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-plugin-request</artifactId>
<version>${project.version}</version>
</dependency>

Context-Path插件

Context-Path插件,是Apache ShenYu网关,容许用户对请求路径上的 Context-Path,进行 添加、修改、删除等功能。用户想要使用它,请在网关添加如下依赖, 然后在 Admin控制台 –> 插件管理 –> context_path插件将其设置为 开启。 更详细的介绍请看 : shenyu.apache.org/zh/projects…

1
2
3
4
5
xml复制代码<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-plugin-context-path</artifactId>
<version>${project.version}</version>
</dependency>

Param-Mapping插件

Param-Mapping插件,是Apache ShenYu网关,容许用户对请求体中的 Body,进行 添加、修改、删除字段等功能。用户想要使用它,请在网关添加如下依赖, 然后在 Admin控制台 –> 插件管理 –> param_mapping插件将其设置为 开启。 更详细的介绍请看 : shenyu.apache.org/zh/projects…

1
2
3
4
5
xml复制代码<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-plugin-param-mapping</artifactId>
<version>${project.version}</version>
</dependency>

ModifyResponse插件

ModifyResponse插件,是Apache ShenYu网关,用来对请求响应体中的 响应头,状态码,响应内容,进行 添加、修改、删除等功能。用户想要使用它,请在网关添加如下依赖, 然后在 Admin控制台 –> 插件管理 –> modifyResponse插件将其设置为 开启。 更详细的介绍请看 : shenyu.apache.org/zh/projects…

1
2
3
4
5
xml复制代码<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-plugin-modify-response</artifactId>
<version>${project.version}</version>
</dependency>

可观测性

Monitor插件

Monitor插件,是Apache ShenYu网关,使用 prometheus来完成对请求量,QPS, JVM等相关metrics进行监控的插件。用户想要使用它,请在网关添加如下依赖, 然后在 Admin控制台 –> 插件管理 –> monitor插件将其设置为 开启, 并且配置 prometheus相关参数。 更详细的介绍请看 : shenyu.apache.org/zh/projects…

1
2
3
4
5
xml复制代码<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-plugin-monitor</artifactId>
<version>${project.version}</version>
</dependency>

Logging插件

Monitor插件,是Apache ShenYu网关,容许用户日志中打印本次请求信息,包含 请求路径、请求方法、请求参数 、响应头、响应体等信息。用户想要使用它,请在网关添加如下依赖, 然后在 Admin控制台 –> 插件管理 –> logging插件将其设置为 开启。 更详细的介绍请看 : shenyu.apache.org/zh/projects…

1
2
3
4
5
xml复制代码<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-plugin-logging</artifactId>
<version>${project.version}</version>
</dependency>

下一个版本规划

  • RPC框架灰度发布增强,包含 SpringCloud,GRPC,Dubbo,Sofa-RPC,Tars等。
  • 新增ShenYu-Agent模块,打造网关metrics, tracing, logging 等可观测性体系。
  • 自定义插件动态加载,方便用户快速,不停机扩展与更新。
  • 集成测试 + 单元测试 全面覆盖。

社区

Apache ShenYu 是完全由国人主导的自主性社区开源项目,目前处在高速发展时期,功能开发,文档完善,BUG修复 等大量的事情需要完成。Apache ShenYu 社区遵循 Apache Way的社区理念,打造一个完全开放,治理的社区。每半个月,会进行一次全体社区会议,社区的committers,contributors, users都会参与其中,在会议上大家可以畅所欲言,提出自己的观点和看法,比如对不同的功能,不同的代码进行讨论,最好达成一致性的观点。在Apache ShenYu 社区中,我们推崇邮件列表 > Github Issue > 微信群的沟通优先级的原则。 主要的目的是让每一个问题,没一个观点,都有记录存档,更好的帮助他人,以推进社区的可持续发展。

邮件订阅

  • 发送订阅邮件。

用自己的邮箱向dev-subscribe@shenyu.apache.org发送一封邮件,主题和内容任意。

  • 接收确认邮件并回复。

完成步骤1后,您将收到一封来自dev-help@shenyu.apache.org的确认邮件(如未收到,请确认该邮件是否已被拦截,或已经被自动归入订阅邮件、垃圾邮件、推广邮件等文件夹)。直接回复该邮件,或点击邮件里的链接快捷回复即可,主题和内容任意。

  • 接收欢迎邮件。

完成以上步骤后,您会收到一封主题为WELCOME to dev@shenyu.apache.org的欢迎邮件,至此您已成功订阅Apache ShenYu的邮件列表。

GitHub

  • 网关 : github.com/apache/incu…
  • 前端 : github.com/apache/incu…
  • 官网 : github.com/apache/incu…

Gitee

  • 网关 : gitee.com/Apache-Shen…
  • 前端 : gitee.com/Apache-Shen…
  • 官网 : gitee.com/Apache-Shen…

社区微信群

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…571572573…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%