一、总结
在Webx的Velocity中获取url中参数:$rundata.getRequest().getParameter('userId')
在Webx项目中,防止CSRF攻击(Cross-site request forgery,跨站请求伪造),在form表单提交中要加入$!csrfToken.ajaxUniqueToken
在MyBatis的mapper层,使用标签association实现对象的关联,一个bean配多个association标签。
二、Bug描述:Velocity从URL中获取parameter参数
在项目IDCM中,使用webx容器进行项目的开发。前端的模板引擎采用了velocity,在项目中,当从列表页跳到详情页的时候,通常我们的screen层是采用如下方式进行展现的:
public class EditRules extendsBaseScreen {
@AutowiredprivateAutoAssignSupplierBo autoAssignSupplierBo;
@AutowiredprivateSupplierBo supplierBo;
@AutowiredprivateAddressBo addressBo;
@AutowiredprivateSiteBo siteBo;public void execute(@Param("id")
String id, Context context)throwsException {
QueryAssignRulesrDo query= newQueryAssignRulesrDo();if(StringUtils.isBlank(id)) {throw new ServiceException("id is empty ");
}
query.setRuleId(Long.parseLong(id));
BoResultDTO> result =autoAssignSupplierBo.selectByQuery(query);
List list =result.getData();if(CollectionUtils.isNotEmpty(list)) {//当前规则的详情
if (StringUtils.isNotBlank(list.get(0).getType())) {
list.get(0).setTypeVal(list.get(0).getType());
list.get(0).setType(WorkOrderCst.RelocationType.getNameByStrValue(list.get(0).getType()));
}//如果有数量信息需要展示
if (list.get(0).getRuleContent().contains("数量")) {
String[] numDes= list.get(0).getRuleContent().split(" ");for(String str : numDes) {if (str.contains("数量")) {
String[] sz= str.split(":");if (2 ==sz.length) {
list.get(0).setAssetNum(sz[1]);
}
}
}
}
context.put("ruleInfos", JSON.toJSONString(list.get(0)));//补全控件信息
Map map = fullInfo(list.get(0).getRuleJsonVal());if (list.get(0).getRuleContent().contains("数量")) {
map.put("assetNum", list.get(0).getAssetNum());
}
context.put("ruleDes", JSONUtils.toJSONString(map));
}//初始化类型信息
Map RelocationTypeList =WorkOrderCst.RelocationType.getRelocationTypeList();
context.put("RelocationTypeList", RelocationTypeList);//物流供应商信息
context.put("logisticsSps", supplierBo.queryAllByType(WorkOrderCst.SpType.logistics.name()));//传入设备类型
this.setorderDeviceType(context);
}
}
上图代码是自动分配物流供应商从列表页跳转到详情页的时候,需要显示调用screen层的跟*.vm同名的*.java方法,通过传入参数id,即选择了指定行,后台会将查询到的数据封装到对象中,在vm中可以直接使用,而不用再走ajax请求,提升了系统的反映速度。其中在BaseScreen.java中负责公共日志的输出,当前权限的获取,以及一些公共属性的动态获取。但是,有一些业务场景中,我们在进行跳转的时候,只需要知道跳转过来的当前id,并不需要后端来加载数据。这时候,如果能从vm中直接获取跳转过来的url的parameter参数,那么就可以省去跟*.vm同名的*.java中的execute方法。
//唯一正确的用法:
//以下几种用法都无法获取到参数的值
$!request.parameter.userId
$!request.paarmater.getParameter('userId')
此外,Velocity更多使用细节参考英文官方文档或
三、Bug描述:$!csrfToken.hiddenField
CSRF(跨站请求伪造),它通过伪装来自受信任用户的请求来利用受信任的网站。在IDCM项目中,在*.vm页面会有大量的表单提交,在表单提交的时候,为了防止跨站请求伪造,要在form标签之后紧跟$!csrfToken.ajaxUniqueToken。
$!csrfToken.hiddenField
上述代码中,在VM文件的form表单中添加了token。该表单请求极有可能涉及数据增删改,需要防范CSRF,请确认使用POST请求,增加token参数,并在服务端校验token。
三、Bug描述:MyBatis中mapper层的association标签使用
API接口调用,web层/openapi/rack代码如下,RpcResult做为接口查询返回的对象,包括Data、info、Success,所以该层合理的代码应该包含try catch,正确的逻辑打印正确的信息。一旦接口调用错误,要返回合适的信息。
//根据房间和机柜名称,批量查询机柜信息 @ResourceMapping("batchQueryRackByRoomNameAndRackName")
public RpcResult batchQueryRackByRoomNameAndRackName(@RequestParam(name = "queryParam")
String queryParam) {
RpcResult rpcResult = new RpcResult();try{
@SuppressWarnings("unchecked")
List list = (List) JsonToBeanUtil.JsonToJavaBean(queryParam, RackAndRoom.class);
List rackList = new ArrayList();if(CollectionUtils.isNotEmpty(list)) {for(RackAndRoom rackAndRoom : list) {
checkParameter(rackAndRoom);
Rack rack=rackBo.batchQueryRackByRoomNameAndRackName(rackAndRoom.getRackName(), rackAndRoom.getRoomName());
rackList.add(rack);
}
rpcResult.setData(rackList);
rpcResult.setInfo("查询成功!");
rpcResult.setSuccess(true);
}
}catch(Exception e) {
logger.error(" batchQueryRackByRoomNameAndRackName error:" +e.getMessage(), e);
rpcResult.setSuccess(false);
rpcResult.setInfo("查询失败,具体异常信息为:" +e.getMessage());
}returnrpcResult;
}
上述的代码结构在接口查询中,利用了分类的思想,针对接口查询成功和失败,分别对info、success、data进行赋值。并且如果失败,会有日志记录。异常的捕获在RPC层(addError)、Bo层(事务回滚)、OpenAPI层要区别对待。对于RPC中的错误,因为它是通过浏览器跟用户交互的,所以一般会将错误添加到Error对象中,将错误信息反馈给浏览器端的用户,使得用户可以修改自己的操作,达到预期的效果。
/*** 修改排序*/@ResourceMapping("updateOrderIng")public RpcResultupdateOrderIng(@RequestParams
AssignRulesVo assignRulesVo, @RequestParam(name= "type")
String type, ErrorContext error) {
RpcResult result = new RpcResult();
result.setSuccess(true);try{if (null == assignRulesVo || null == assignRulesVo.getOrdering() ||StringUtils.isBlank(type)) {throw new SerialException("缺少排序信息和规则信息,无法修改");
}
autoAssignSupplierBo.updateOrderIng(assignRulesVo, type);
result.setInfo("修改排序成功");
result.setData(true);
}catch(Exception e) {
result.setSuccess(false);
logger.error("updateOrderIng err : ", e.getMessage(), e);
addError(error, ErrorCode.Sys_Error.getCode(), e.getMessage(), result);
}returnresult;
}
对比可以看到RPC中,通常会传入Error对象,使用addError方法,将错误信息反馈到前端。接着讲openapi中的查询接口,它通过调用bo层、boImpl层、Ext层的rackMapperExt.batchQueryRackByRoomNameAndRackName(map);来查询。
select
FROM
idc_rack temp_rack
INNER JOIN idc_room temp_room ON temp_rack.room_id = temp_room.room_id
INNER JOIN idc_site temp_site ON temp_room.site_id = temp_site.site_id
WHERE
temp_rack.is_deleted = 'n'
AND temp_room.is_deleted = 'n'
AND temp_site.is_deleted = 'n'
AND temp_rack.rack_name = #{rackName}
AND temp_room.room_name = #{roomName}
在代码中,查询到的参数采用了标签,将要查询的room、rack、site等信息捞出来,最后映射到BaseResultMap_Ext中,如下:
通过标签实现了对象映射的时候的一对多的关联,上述resultMap使用extends="BaseResultMap"扩展了对象映射,其中BaseResultMap来自于Mybatis自动生成的mapper中的映射关系。
四、批量查询接口性能优化
ext层:
@Resourcepublic interface RackMapperExt extendsRackMapper {
List batchQueryRackByRoomNameAndRackName(Listlist);
}
mapper层:
select
FROM
idc_rack temp_rack
INNER JOIN idc_room temp_room ON temp_rack.room_id=temp_room.room_id
INNER JOIN idc_site temp_site ON temp_room.site_id=temp_site.site_id
WHERE
temp_rack.is_deleted= 'n'AND temp_room.is_deleted= 'n'AND temp_site.is_deleted= 'n'AND(temp_rack.rack_name= #{item.rackName} AND temp_room.room_name =#{item.roomName})
五、MyBatis中的使用
在xml文件中,处于CDATA部分中的所有内容都会被解析器忽略,避免由于>&等sql字符影响xml文档结构,segmentfault问题:
六、待补充:阿里巴巴缓存使用bacardi、tail等。
附录:
阿里巴巴-基础架构事业群业务,项目路径:
基础架构事业群业务。