最近一直再思考统一网易云音乐与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这些方法来做转换和计算。

  当我做了几个接口后,感觉这样一直在做重复且无聊的事情,我在思考更优的解决方法。比如模仿springbootyml的使用,通过配置达到想要的功能,将json的转换规则进行解耦(不和代码直接绑定)。目的是做一个通用的工具,然后配置规则就能得到转换后的 json

  经过自己的思考过后,打算尝试自己做一个工具,完成我想要的功能,于是就开始了设计规则

下面的json字符串是模板数据,也就是需要返回的数据字段的定义

  1. type表示字段的类型
  2. note则是说明
  3. weight表示权重
  4. count表示需要进行计算的表达式
  5. 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] 数组范围访问,其中startend是开始小表和结束下标,可以是负数;
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两个