最近一直再思考统一网易云音乐与qq音乐的api,做着做着写了好多代码
利用 linux的 tree -if 命令,展示一下这几天写的代码
root@angel:~# tree -if starter/
starter
starter/annotation
starter/annotation/BiAsyncAnnotaion.java
starter/annotation/MusicService.java
starter/configuration
starter/configuration/properties
starter/configuration/properties/YumbMusicServiceProperties.java
starter/configuration/properties/YumboMusicServiceNeteaseProperties.java
starter/configuration/properties/YumboMusicServiceQQProperties.java
starter/configuration/properties/YumboThreadPoolProperties.java
starter/configuration/YumboMusicAutoConfiguration.java
starter/configuration/YumboThreadPoolExecuterAutoConfiguration.java
starter/controller
starter/controller/MusicController.java
starter/controller/MVController.java
starter/controller/UserController.java
starter/entity
starter/entity/abstractMusic
starter/entity/abstractMusic/AbstractMusic.java
starter/entity/abstractMusic/AbstractYumboMusic.java
starter/entity/abstractMusic/AbstractYumboMV.java
starter/entity/netease
starter/entity/netease/NeteaseCloudMusicInfo.java
starter/entity/netease/NeteaseMusic.java
starter/entity/netease/NeteaseMV.java
starter/entity/otherMusic
starter/entity/otherMusic/OtherMusicInfo.java
starter/entity/qq
starter/entity/qq/QQMusicInfo.java
starter/entity/qq/QQMusic.java
starter/entity/qq/QQMV.java
starter/entity/Singer.java
starter/entity/yumboEnum
starter/entity/yumboEnum/MusicBitRateEnum.java
starter/entity/yumboEnum/MusicEnum.java
starter/entity/yumboEnum/MVBitRateEnum.java
starter/entity/yumboEnum/SearchTypeEnum.java
starter/entity/YumboMusic.java
starter/service
starter/service/YumboCommonService.java
starter/service/YumboMusicService.java
starter/service/YumboMVService.java
starter/SpringBootStarterMusicApplication.java
starter/utils
starter/utils/BiAsyncRequestUtils.java
starter/utils/JsonCombineUtils.java
starter/utils/YumboMusicRequestUtils.java
12 directories, 34 files
root@angel:~# ls -lh starter
total 28K
drwxr-xr-x 2 root root 4.0K Jan 15 22:19 annotation
drwxr-xr-x 3 root root 4.0K Jan 15 22:19 configuration
drwxr-xr-x 2 root root 4.0K Jan 15 22:19 controller
drwxr-xr-x 7 root root 4.0K Jan 15 22:19 entity
drwxr-xr-x 2 root root 4.0K Jan 15 22:19 service
-rw-r--r-- 1 root root 361 Jan 15 22:19 SpringBootStarterMusicApplication.java
drwxr-xr-x 2 root root 4.0K Jan 15 22:19 utils
边写边思考问题,因为我想把两个音乐的api数据合成一个数据(会遇到 字段映射,多余字段,新增字段,字段之间的计数等问题)。在处理这些从网易云音乐api服务器和qq音乐服务器返回的json数据时,起初我是直接使用fastjson的get、put、parse、to这些方法来做转换和计算。
当我做了几个接口后,感觉这样一直在做重复且无聊的事情,我在思考更优的解决方法。比如模仿springboot
中yml
的使用,通过配置达到想要的功能,将json的转换规则进行解耦(不和代码直接绑定)。目的是做一个通用的工具,然后配置规则就能得到转换后的 json
经过自己的思考过后,打算尝试自己做一个工具,完成我想要的功能,于是就开始了设计规则
下面的json字符串是模板数据,也就是需要返回的数据字段的定义
- type表示字段的类型
- note则是说明
- weight表示权重
- count表示需要进行计算的表达式
- template则是模板数据(每一个字段对应数据的
json
结构)
{
"total": {
"type": "int",
"note": "所有歌曲的总数",
"weight": 1,
"count": "qq.data.total + netease.result.songCount"
},
"qq": {
"type": "int",
"note": "songs中来自qq音乐的条数",
"weight": 2,
"count": "qq.songs[length]"
},
"netease": {
"type": "int",
"note": "songs中来自网易云音乐的条数",
"weight": 2,
"count": "netease.songs[length]"
},
"return_back": {
"type": "int",
"note": "当前返回的总条数",
"weight": 3,
"count": "this.qq + this.netease"
},
"template": {
"name": "songs",
"type": "array"
},
"songs": [
{
"type": "qq",
"songName": "json.data.list[].songname",
"albumName": "json.data.list[].albumname",
"durationTime": "json.data.list[].interval",
"musicId": "json.data.list[].songmid",
"singer": [
{
"name": "json.data.list[].singer.name",
"id": "json.data.list[].singer.id"
}
],
"mvId": "json.data.list[].vid",
"albumId": "json.data.list[].albummid"
},
{
"type": "netease",
"songName": "json.result.songs[].name",
"albumName": "json.result.songs[].album.name",
"durationTime": "json.result.songs[].duration",
"musicId": "json.result.songs[].id",
"singer": [
{
"name": "json.result.songs[].artists[].name",
"id": "json.result.songs[].artists[].id"
}
],
"mvId": "json.result.songs[].mvid",
"albumId": "json.result.songs[].album.id"
}
]
}
在处理并形成符合模板数据的过程中,我用到了fastjson中的JsonArray对象,在做遍历的时候,我在思考是java8的forEach方法遍历快还是通过get 索引更快,于是搜索了一下fastjson,于是找到了JsonPath
发现json-path
已经做了我想要做的事情,那么我就打算借用json-path
快速的将网易云音乐,qq音乐的api进行统一。只要定义好路径可以快速的提取自己需要的信息。
当然Fastjson
也提供了json-path
语法,搜索资料经过再三考虑决定使用Fastjson
以下内容来自github的文档,可以直接点击下面的文档地址
JSONPath文档地址(fastjson)
https://github.com/alibaba/fastjson/wiki/JSONPath
第一步、pom中引入fastjson
建议使用最新版的,我在引入的时候fastjson的版本是1.2.75
fastjson的maven仓库地址
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
JsonPath语法
语法 | 作用 |
---|---|
$ | 根对象,例如$.name |
[num] | 数组访问,其中num是数字,可以是负数。 例如 $[0].leader.departments[-1].name |
[num0,num1,num2...] | 数组多个元素访问,其中num是数字,可以是负数,返回数组中的多个元素。例如$[0,3,-2,5] |
[start:end] | 数组范围访问,其中start和end是开始小表和结束下标,可以是负数,返回数组中的多个元素。例如$[0:5] |
[start:end :step] | 数组范围访问,其中start 和end 是开始小表和结束下标,可以是负数;step 是步长,返回数组中的多个元素。例如$[0:5:2] |
[?(key)] | 对象属性非空过滤,例如$.departs[?(name)] |
[key > 123] | 数值类型对象属性比较过滤,例如$.departs[id >= 123] ,比较操作符支持=,!=,>,>=,<,<= |
[key = ‘123’] | 字符串类型对象属性比较过滤,例如$.departs[name = '123'] ,比较操作符支持=,!=,>,>=,<,<= |
[key like ‘aa%’] | 字符串类型like过滤,例如$.departs[name like 'sz*'] ,通配符只支持 % 支持 not like |
[key rlike ‘regexpr’] | 字符串类型正则匹配过滤,例如 departs[name like 'aa(.)*'] ,正则语法为jdk的正则语法,支持 not rlike |
[key in (‘v0’, ‘v1’)] | IN过滤, 支持字符串和数值类型。 例如: $.departs[name in ('wenshao','Yako')] $.departs[id not in (101,102)] |
[key between 234 and 456] | BETWEEN过滤, 支持数值类型,支持not between 例如: $.departs[ id between 101 and 201] $.departs[ id not between 101 and 201] |
length() 或者 size() | 数组长度 例如 $.values.size() 支持类型 java.util.Map 和 java.util.Collection 和数组 |
keySet() | 获取Map的 keySet 或者对象的非空属性名称。例如$.val.keySet() 支持类型:Map 和 普通对象 不支持 :Collection和数组(返回null) |
. | 属性访问,例如$.name |
.. | deepScan属性访问,例如$..name |
* | 对象的所有属性,例如$.leader.* |
['key'] | 属性访问。例如$['name'] |
['key0','key1'] | 多个属性访问。例如$['id','name'] |
下面两种写法的语义相同
$.store.book[0].title
和
$['store']['book'][0]['title']
语法示例
JSONPath | 语义 |
---|---|
$ | 根对象 |
$[-1] | 最后元素 |
$[:-2] | 第1个至倒数第2个 |
$[1:] | 第2个之后所有元素 |
$[1,2,3] | 集合中1,2,3个元素 |
第二步、根据语法以及实际中的json
数据编写jsonpath
jsonString是json字符串,jsonpath则是定义的path规则
String jsonpath="$.store.book[0:3]";// 定义规则
final Object read = JSONPath.read(jsonString, jsonpath);// 进行解析
final String s = JSONObject.toJSONString(read);// 将对象转换成字符串
public static class Entity {
private Integer id;
private String name;
private Object value;
public Entity() {}
public Entity(Integer id, Object value) { this.id = id; this.value = value; }
public Entity(Integer id, String name) { this.id = id; this.name = name; }
public Entity(String name) { this.name = name; }
public Integer getId() { return id; }
public Object getValue() { return value; }
public String getName() { return name; }
public void setId(Integer id) { this.id = id; }
public void setName(String name) { this.name = name; }
public void setValue(Object value) { this.value = value; }
}
例1
读取集合多个元素的某个属性
List<Entity> entities = new ArrayList<Entity>();
entities.add(new Entity("wenshao"));
entities.add(new Entity("ljw2083"));
// 返回enties的所有名称
List<String> names = (List<String>)JSONPath.eval(entities, "$.name");
例2
返回集合中多个元素
List<Entity> entities = new ArrayList<Entity>();
entities.add(new Entity("wenshao"));
entities.add(new Entity("ljw2083"));
entities.add(new Entity("Yako"));
// 返回下标为1和2的元素
List<Entity> result = (List<Entity>)JSONPath.eval(entities, "[1,2]");
例3
按范围返回集合的子集
List<Entity> entities = new ArrayList<Entity>();
entities.add(new Entity("wenshao"));
entities.add(new Entity("ljw2083"));
entities.add(new Entity("Yako"));
// 返回下标从0到2的元素
List<Entity> result = (List<Entity>)JSONPath.eval(entities, "[0:2]");
例4
通过条件过滤,返回集合的子集
List<Entity> entities = new ArrayList<Entity>();
entities.add(new Entity(1001, "ljw2083"));
entities.add(new Entity(1002, "wenshao"));
entities.add(new Entity(1003, "yakolee"));
entities.add(new Entity(1004, null));
List<Object> result = (List<Object>) JSONPath.eval(entities, "[id in (1001)]");
例5
根据属性值过滤条件判断是否返回对象,修改对象,数组属性添加元素
Entity entity = new Entity(1001, "ljw2083");
JSONPath.set(entity, "id", 123456); //将id字段修改为123456
JSONPath.set(entity, "value", new int[0]); //将value字段赋值为长度为0的数组
JSONPath.arrayAdd(entity, "value", 1, 2, 3); //将value字段的数组添加元素1,2,3
例6 keySet
使用keySet抽取对象的属性名,null值属性的名字并不包含在keySet结果中,使用时需要注意,详细可参考示例。
Entity e = new Entity();
e.setId(null);
e.setName("hello");
Map<String, Entity> map = Collections.singletonMap("e", e);
Collection<String> result;
// id is null, excluded by keySet
result = (Collection<String>)JSONPath.eval(map, "$.e.keySet()");// result.seze()=1
e.setId(1L);
result = (Collection<String>)JSONPath.eval(map, "$.e.keySet()");// result.seze()=2,因为id不为null,所以keySet()返回的就是id,name两个