首页 > 技术文章 > 复杂结构数据提交

ywjy 2015-12-03 14:32 原文

问题抛出

在前台需要一次保存结构复杂的数据组织,界面原型如下:

复杂结构数据提交-原型

导致问题复杂的原因有2个:

1. 多实例
如界面原型所示,绿茶属于一个实例,白茶属于另一个实例。多实例下,需要保证实例之间的属性的隔离性,以及可能的实例之间的顺序。

2. 属性结构复杂
实体的属性不再都是简单的类型,例如主要成茶就是一个一维集合类型。多维度下,需要保证复杂类型属性的值结构,比如说值的顺序。

笨拙的方案

基于表单提交的形式,将含有name属性值、非disable的input元素,按照name进行组织。相同name的input元素,以一维数组的形式进行组织。这种形式对数据的组织是浅层的:在后台可以得到的结果仅仅是线性的参数序列,每个参数值或者是简单的字符串,或者是一维字符数组。当想要实现更高层的数据结构时,需要在前台进行低维展开,在后台进行高维收缩。

前台的低维展开,可以借助于为name添加前缀来实现。比如绿茶和白茶,本来都应该是name=“kind”,可以展开成一个name="lc_kind"和name=“bc_kind”;同理,发酵程度degree可 以展开成name="lc_degree"和name="bc_degree"。需要考虑排重。

后台的高维收缩,根据name的前缀进行关联和数据的重新结构化。后台的收缩逻辑需要和前台的展开逻辑相一致,就好像序列化与反序列化、加密和解密。前台和后台的耦合度会比较高。

推荐的方案

JS对象+序列化+异步提交。

1. 组织和封装

以JS对象格式组织数据,最终封装到一个顶层的js对象或者数组里。比如界面原型中的数据结构,可以这样封装:

 1 var lc = {
 2     "kind": "绿茶",
 3     "degree": "0%",
 4     "example": ["龙井", "瓜片", "碧螺春茶", "毛峰茶", "云雾茶"]
 5 };
 6 var bc = {
 7     "kind": "白茶",
 8     "degree": "5%-10%",
 9     "example": ["包种茶", "白毫银针茶", "白牡丹茶", "香片"]   
10 };
11 var teas = [lc, bc]; 

当然,各属性的值是需要通过定位dom元素来获取的,而不是常量。最后的teas就是组织封装之后的结果,在这种情形里,封装的结果是一个数组;还有一种情况,封装的结果是一个普通的对象。数组在后台会被解析成List结构,普通对象在后台会被解析成Map结构。

2. 序列化

var data = JSON.stringify(teas);

注:需要引入依赖的json.js文件。

3. 异步提交

 1 $.ajax({
 2     url : "/basic/demo/cds/report.action",
 3     type : "post",
 4     data : data,
 5     dataType : "json",
 6     contentType : "application/json; charset=GBK", // so important
 7     success : function(response) {
 8         if (response.state == "success") {
 9             alert("保存成功!");
10         } else {
11             alert("保存失败!");
12         }
13     },
14     error : function (response) {
15         alert("保存失败!");
16     }
17 }); 

注:
a. 这里data是序列化的结果;
b. 要求发送数据至服务器时,内容编码类型contentType是 “application/json”,而默认的是“application/x-www-form-urlencoded”,所以需要显示设置;所以在jquery提供的ajax系列方法中,只能使用原生的$.ajax(url,[settings])了。

4. 后台解析

后台会将数据解析成List或者Map结构(数组则解析成List,普通对象则解析成Map,也可以都作为Object对象来对待)。

1 @RequestMapping("/report")
2 public String report(Model model, @RequestBody List<Map<String, Object>> teas) {
3     model.addAttribute("state", "success");
4     model.addAttribute("data", teas);
5     return VIEW_JSON;
6 } 

注:参数必须使用@RequestBody注解,且只需要一个这样的参数, 所有的请求参数都解析到了注解对应的参数中;即使声明了更多的参数,也无用。

问题记录

1. 自动解析成Bean的集合

后台获取到的数据,是List、Map交互嵌套,叶子元素为String的结构体;而不是对应bean的结构体。我们需要另外进行转换,包括将String类型的数据转换为其他类型,将Map转化为Bean。
该问题目前无解。

2. 额外的配置

根据之前的预研,是需要在spring-mvc相关配置中添加一些配置,将 MappingJacksonHttpMessageConverter的bean注入AnnotationMethodHandlerAdapter的 messageConverters属性中:

1 <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
2     <property name="messageConverters">
3         <list>
4             <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
5         </list>
6     </property>
7 </bean> 

但是最新的尝试,没有配置也可行。

3. 新窗口打开

在某种“预览”的需求下,根据当前的临时数据,进行展示。如果临时数据结构复杂,应该使用文中推荐的方案,并返回一个url。js脚本使用url打开新窗口,展示临时数据。ajax异步提交的数据要在新的页面中使用,涉及到缓存。可以缓存到session里,或者数据库。推荐缓存到session里,参考在Spring Controller中将数据缓存到session

推荐阅读