问题抛出
在前台需要一次保存结构复杂的数据组织,界面原型如下:
导致问题复杂的原因有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。