引言
开发过程中我们会使用版本控制工具
,如SVN、Git等。但是我们一样会遇到一种情形:在一套试题系统中,有新建题目、编辑题目、删除题目等功能,且题目可以被多个人修改,每人修改一次即是一个版本。现在的需求就是需要记录每一次修改的详细信息
,每次版本之间的差异
,甚至还可以版本回滚
。
例如题目编号为20170919170800000061的题目被A创建,依次被B、C、D各修改了一次,此时需要比较B和A间的差异、C和B间的差异、D和C间的差异,到最后审核阶段如果B 的版本比较符合,则需要把试题版本内容回滚到B版本作为最后的版本。
仔细分析一下题干,我们的需求是1.比较版本的差异,2.版本的回滚。之前有考虑过两种方案:
- 修改时在前端进行比较,只记录版本的差异,后台只需要进行存取即可。
- 把所有版本信息全部存储在数据库,在请求时后台进行比较差异。
第一种方案带来的问题是没法进行版本回滚,只记录下来了差异,回滚时将会是灾难,那么第二种方案才是较合适的选择。
下图是数据库中的版本修改记录
题目的所有信息全部存储在itemJson中,比较版本间的差异即是比较版本间的itemJson,现在的目标就是要提取两个版本中itemJson中的差异。
通过从网上查找资料找到了两种比较合适的方法,值得借鉴一下。
版本差异(比较Json的方法)
计较两个Json(X,Y),其中可能情况:
- X和Y中均有相同字段
- X中存在Y中不存在的字段
- Y中存在X中不存在的字段
需要掌握:
- 各个字段的用处和意义
- 字段在Map、Json、JavaBean、List、JsonString形态之间的转换
通过Map间接比较
引入Maven依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.1</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.9.1</version> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>23.0</version> </dependency>
|
定义静态工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public static <T> T readJsonToObject(String jsonString, TypeReference<T> tr) { ObjectMapper objectMapper = new ObjectMapper(); if (jsonString == null || "".equals(jsonString)) { return null; } else { try { return (T) objectMapper.readValue(jsonString, tr); } catch (Exception e) {
logger.debug("json error:" + e); } } return null; }
|
定义Map比较的工具类
通过google的guava
表达式中的 *Maps.difference(map1,map2)*方法进行比较,单此方法可比较正常的Map和String内容,对于List方式的比较,同时进行了数值和list内容顺序的比较,显然不符合我们的匹配规则,所以我们要对这个方法配合List的containAll方法进一步做封装。代码如下:
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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| public static List<Map<String, String>> compareMap(Map<String, Object> oldVersion, Map<String, Object> newVersion) { MapDifference<String, Object> difference = Maps.difference(oldVersion, newVersion);
Map<String, MapDifference.ValueDifference<Object>> differenceMap = difference.entriesDiffering(); List<Map<String, String>> result = new ArrayList<>(); Iterator diffIterator = differenceMap.entrySet().iterator(); while (diffIterator.hasNext()) { Map.Entry entry = (java.util.Map.Entry) diffIterator.next();
MapDifference.ValueDifference<Object> valueDifference = (MapDifference.ValueDifference<Object>) entry .getValue(); boolean isList = valueDifference.leftValue() instanceof List && valueDifference.rightValue() instanceof List; boolean isMap = valueDifference.leftValue() instanceof Map && valueDifference.rightValue() instanceof Map; if (!isList && !isMap) { Map<String, String> map = new HashMap<>(); String fieldKey = String.valueOf(entry.getKey());
if (oldVersion.get("content") != null && oldVersion.get("name") != null) { map.put("fieldName", judgeOption(oldVersion.get("name").toString())); } else { map.put("fieldName", judgeFiledName(fieldKey)); } map.put("fieldKey", fieldKey); map.put("oldValue", judgeFiledKey(fieldKey, valueDifference.leftValue().toString())); map.put("newValue", judgeFiledKey(fieldKey, valueDifference.rightValue().toString())); result.add(map); }
if (valueDifference.leftValue() instanceof List && valueDifference.rightValue() instanceof List) { JSONArray j = JSONArray.parseArray(JSON.toJSONString(valueDifference.leftValue())); JSONArray p = JSONArray.parseArray(JSON.toJSONString(valueDifference.rightValue())); JSONObject js = new JSONObject(); JSONObject js1 = new JSONObject(); for (int i = 0; i < j.size(); i++) { js.put(i + "", j.get(i)); } for (int i = 0; i < p.size(); i++) { js1.put(i + "", p.get(i)); } Map<String, Object> requestMap = JsonUtils.readJsonToObject(js.toString(), new TypeReference<Map<String, Object>>() { }); Map<String, Object> requestMap1 = JsonUtils.readJsonToObject(js1.toString(), new TypeReference<Map<String, Object>>() { }); List<Map<String, String>> m = compareMap(requestMap, requestMap1); for (int i = 0; i < m.size(); i++) { result.add(compareMap(requestMap, requestMap1).get(i)); } } if (valueDifference.leftValue() instanceof Map && valueDifference.rightValue() instanceof Map) { result.add(compareMap((Map<String, Object>) valueDifference.leftValue(), (Map<String, Object>) valueDifference.rightValue()).get(0)); } }
Map<String, Object> entriesOnlyOnLeft = difference.entriesOnlyOnLeft(); if (entriesOnlyOnLeft != null && !entriesOnlyOnLeft.isEmpty()) {
Iterator it = entriesOnlyOnLeft.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, String> entry = (java.util.Map.Entry) it.next(); Map<String, String> map = new HashMap<>(); String fieldKey = entry.getKey(); map.put("fieldKey", fieldKey); map.put("fieldName", judgeFiledName(fieldKey)); map.put("oldValue", judgeFiledKey(fieldKey, String.valueOf(entry.getValue()))); map.put("newValue", ""); result.add(map); } }
Map<String, Object> onlyOnRightMap = difference.entriesOnlyOnRight(); if (onlyOnRightMap != null && !onlyOnRightMap.isEmpty()) { Iterator it = onlyOnRightMap.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, String> entry = (java.util.Map.Entry) it.next(); Map<String, String> map = new HashMap<>(); String fieldKey = String.valueOf(entry.getKey()); map.put("fieldKey", fieldKey); map.put("fieldName", judgeFiledName(fieldKey)); map.put("oldValue", ""); map.put("newValue", judgeFiledKey(fieldKey, String.valueOf(entry.getValue()))); result.add(map); } } return result; }
|
定义静态调用方法
1 2 3 4 5 6 7
| public static List<Map<String, String>> compareJSON(String jsonOld, String jsonNew) { Map<String, Object> oldVersion = JsonUtils.readJsonToObject(jsonOld, new TypeReference<Map<String, Object>>() { }); Map<String, Object> newVersion = JsonUtils.readJsonToObject(jsonNew, new TypeReference<Map<String, Object>>() { }); return compareMap(oldVersion, newVersion); }
|
返回结果
经过处理后的返回结果
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
| [ { "newValue": "10", "fieldName": "分数", "fieldKey": "score", "oldValue": "8" }, { "newValue": "10", "fieldName": "教材依据", "fieldKey": "teachingMaterialBasis", "oldValue": "2" }, { "newValue": "2", "fieldName": "大纲依据", "fieldKey": "syllabusBasis", "oldValue": "1" }, { "newValue": "1.0", "fieldName": "难度系数", "fieldKey": "difficult", "oldValue": "0.4" }, { "newValue": "掌握", "fieldName": "能力层次", "fieldKey": "abilityLevel", "oldValue": "熟悉" }, { "newValue": "测试关键词", "fieldName": "关键字", "fieldKey": "keyWord", "oldValue": "个" } ]
|
查看所有源码点击:下载
转为JavaBean比较
将itemJson字符串转化为JavaBean,比较JavaBean之前的差异。比较JavaBean间的差异可以用Javers。
引入Javers的Maven依赖
1 2 3 4 5
| <dependency> <groupId>org.javers</groupId> <artifactId>javers-core</artifactId> <version>3.5.0</version> </dependency>
|
将itemJson转为JavaBean
方式一:利用Jackson
工具类JsonUtils
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
| public class JsonUtils { private static final ObjectMapper MAPPER = new ObjectMapper();
public static String objectToJson(Object data) { try { String string = MAPPER.writeValueAsString(data); return string; } catch (JsonProcessingException e) { e.printStackTrace(); } return null; }
public static <T> T jsonToPojo(String jsonData, Class<T> beanType) { try { T t = MAPPER.readValue(jsonData, beanType); return t; } catch (Exception e) { e.printStackTrace(); } return null; }
public static <T>List<T> jsonToList(String jsonData, Class<T> beanType) { JavaType javaType = MAPPER.getTypeFactory().constructParametricType(List.class, beanType); try { List<T> list = MAPPER.readValue(jsonData, javaType); return list; } catch (Exception e) { e.printStackTrace(); } return null; } }
|
Json转Pojo
1 2
| Item item1 = JsonUtils.jsonToPojo(itemJsonOld, Item.class); Item item2 = JsonUtils.jsonToPojo(itemJsonNew, Item.class);
|
方式二:利用fastJson
引入Maven依赖
1 2 3 4 5
| <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.1.26</version> </dependency>
|
Json转Pojo
1 2
| Item item1 = JSONObject.parseObject(json3, Item.class); Item item2 = JSONObject.parseObject(json4, Item.class);
|
利用Javers比较JavaBean
1 2 3 4 5 6 7 8 9 10 11
| Javers j = JaversBuilder.javers().build(); Diff diff = j.compare(item1, item2); if (diff.hasChanges()) { List<Change> changes = diff.getChanges(); for (Change change : changes) { if (change instanceof ValueChange) { ValueChange valChange = (ValueChange) change; System.out.println(valChange.getPropertyName() + " -- " + valChange.getLeft() + "--" + valChange.getRight()); } } }
|
版本回滚
其实版本回滚在上面的比较中已经说了,就是把需要回滚的版本itemJson转化为JavaBean传给前台,同时生成一份最新的版本为当前版本,记录操作人、操作时间等等记录即可。需要了解及使用Gson、fastJson、Jackson的使用,及使用工具将Map、Json、JavaBean、List、JsonString对象之间状态的转换。
总结
- 熟悉业务。
- 掌握Map、Json、JavaBean、List、JsonString对象之间状态的转换。
- 版本需要存储所有信息便于回滚。
- 个人倾向使用Javers比较JavaBean进行比较版本差异。