# JSP前端开发规范
# JSP文件规范
1、文件目录:jsp文件放到WEB-INF各插件目录下:
{projectName}/src/main/webapp/WEB-INF/jsp/[ctp|apps]/{module}/abcList.jsp
2、文件命名:遵循camel命名风格,尽量遵循Controller的方法名和jsp名称一致
//good addUser.jsp userList.jsp
//bad add_user.jsp userlist.jsp
3、禁止在JSP中写java代码,尽量把JSP当作纯html使用:
4、jsp页面注释使用:<%-- 被注释的内容 --%>
5、Javascript文件引用:js文件保持和jsp同名文件放在{projectName}/src/main/webapp/[apps_res|common]/{module}/abcList.js
css文件同理
6、禁止在页面使用seeyon作为静态文件上下文,引用V5标准模板后,可以通过${path}
来代替seeyon硬编码:
<!--good code-->
<link rel="stylesheet" href="${path}/apps_res/{module}/css/xxx.css${ctp:resSuffix()}">
<script type="text/javascript" src="${path }/apps_res/{module}/js/xxx.js${ctp:resSuffix()}"></script>
<!--bad code-->
<link rel="stylesheet" href="seeyon/apps_res/{module}/css/xxx.css${ctp:resSuffix()}">
<script type="text/javascript" src="seeyon/apps_res/{module}/js/xxx.js${ctp:resSuffix()}"></script>
# JSTL表达式
JSTL属于后端技术,底层是执行Java代码。
原则上,新jsp页面不允许使用JSTL全部标签,通过html+Javascript均能代替相关能力。
曾经使用过JSTL的JSP页面,也只允许使用JSTL core、JSTL fmt 和JSTL functions的部分tag,不允许使用JSTL sql和JSTL XML。
允许使用的tag列表
1. c:out
2. c:url
3. c:forEach & c:if
4. fmt:message
# CTP EL表达式
ctp EL表达式是V5平台按照jstl规范,自定义的el表达式,定义申明文件在WEB-INF/tld/ctp.tld
文件中,用法都是以${ctp:xxx()}
形式使用
# CTP EL常用表达式
不推荐使用涉及Java对象操作的CTP EL表达式,操作不符合前后端分离规范。
推荐使用静态化执行的CTP EL表达式,对前后端分离影响相对小一些。
表达式 | 示例 | 说明 |
---|---|---|
i18n(_[1~5]) | ${ctp:i18n("abc.bcd")} ${ctp:i18n("abc.bcd1",123)} | 推荐,国际化 |
formatDate | ${ctp:formatDate(date)} | 不推荐,需要对java对象进行转换,yyyy-MM-dd |
formatDateTime | ${ctp:formatDateTime(date)} | 不推荐,需要对java对象进行转换,yyyy-MM-dd HH:mm |
toHTML | ${ctp:toHTML(string)} | 不推荐,需要对java对象进行转换,将字符串转换成HTML,将对\r \n < > & 空格进行转换 |
toHTMLAlt | ${ctp:toHTMLAlt(string)} | 不推荐,需要对java对象进行转换,将字符串转换成HTML,不包括 \n |
toHTMLWithoutSpace | ${ctp:toHTMLWithoutSpace(string)} | 不推荐,需要对java对象进行转换,将字符串转换成HTML,将对\r \n < > & 空格不进行转换 |
showMemberName | ${ctp:showMemberName(memberId)} | 不推荐,需要对java对象进行转换,显示人员姓名 |
hasPlugin | ${ctp:hasPlugin(pluginName)} | 推荐,判断是否包含某个插件 |
resSuffix | ${ctp:resSuffix()} | 推荐,静态资源时间戳后缀 |
csrfSuffix | ${ctp:csrfSuffix()} | 推荐,CSRF请求后缀 |
# jsp全局变量(推荐使用)
变量名 | 示例 | 说明 |
---|---|---|
path | ${path} | 当前应用上下文 |
# 扩展:为什么不推荐使用EL?
不推荐新的JSP中使用原生EL表达式、JSTL表达式,不推荐原因有二:
- EL表达式是渲染页面时执行,极易引发反射型和存储型XSS
- 尽量少的EL表达式代码编写,利于后续JSP->html迁移,降低前后端分离代价
- 更清晰的职责,前端就是前端、后端就是后端,方便做职责分离,也更适合现在的开发模式
如果老代码,已经在JSP中使用EL表达式,建议参考安全篇,对EL表达式中的变量进行XSS防护。
针对不同场景,我们也有相应EL表达式的替换方案。
场景一:而编写js的<script>
代码块嵌入了EL表达式,则是XSS攻击的源头。
<html>
<head>
</head>
<body>
</body>
<script type="text/javascript">
function init(){
// Bad Code : 如下通过原生EL引入的id和title变量极易引发XSS攻击
var id = ${id};
var title = ${title};
// Nice Code:改用Javascript直接获取URL中的变量
var queryParams = getQueryParams();
var id = queryParams.id;
var title = queryParams.title;
}
/**
* 获取url后边的参数实现原理 注:此方法平台已经封装好,业务组调用即可
* @returns
*/
function getQueryParams(){
var search = location.search;
var param = {};
if (search.length > 0) {
var strs = search.split("?")[1].split("&");
for (var i = 0; i < strs.length; i++) {
var con = strs[i].split("=");
param[con[0]] = decodeURIComponent(con[1]);
}
}
return param;
}
</script>
<%-- Nice Code:推荐通过src的形式引入Javascript --%>
<script type="text/javascript" src="apps_res/{module}/js/xxx.js"></script>
</html>
场景二:JSP中的html标签,value值直接用EL表达式获取,也是XSS攻击源头。
<%-- Bad Code以下写法都不再推荐 --%>
<input type="text" value="${demo.title}">
<input type="hidden" value="${demo.name}">
<%-- 如果要对某些文本字段赋值,请使用AJAX的形式获取数据后,再通过Javascript对DOM赋值。 --%>
# V5标准JSP模板
如果编写一个新的JSP页面,我们推荐使用CTP标准的JSP模板,通过如下模板配置,能够使用CTP JSP前端组件的各种特性,并且保持浏览器最大兼容性。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html class="over_hidden h100b">
<head>
<%@include file="/WEB-INF/jsp/common/common_header.jsp"%>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="renderer" content="webkit">
<title>${ctp:i18n("文件标题") }</title>
<link rel="stylesheet" href="${path}/apps_res/{module}/css/xxx.css${ctp:resSuffix()}">
</head>
<body class="over_hidden h100b">
</body>
<%@include file="/WEB-INF/jsp/common/common_footer.jsp"%>
<script type="text/javascript" src="${path }/apps_res/{module}/js/xxx.js${ctp:resSuffix()}"></script>
</html>
# 关于common_header.jsp
common_header.jsp是V5基于JSP提供的标准模板,适合放在JSP页面<head>
标签里,主要标准化内置了:${path}
上下文变量、平台CSS样式。
# 关于common_footer.jsp
common_footer.jsp也是标准模板,适合放在JSP页面</body>
标签后面,主要标准化内置了:Javascript可使用的V5公共变量、SeeyonUI组件库。
# EClipse和IDEA设置默认模板
开发人员可以在自己的IED开发工具中预置默认的JSP模板方便直接调用:
# SeeyonUI前端组件
V5提供了基于JSP的各种平台UI前端组件,前面标准JSP引入规范编写代码后即可使用这些UI组件做功能开发。
详细的组件引用方法参考站内>技术平台>前端技术>JSP前端组件库
。
# js前端压缩组件
平台提供了启动压缩Javascript文件的能力,可以将一批js压缩成一个xx.min.js文件使用。
Javascript压缩清单:/ctp-common/src/main/webapp/common/compressconfig/compressconfig.xml
<!-- 新建协同 -->
<js>
<inputfile><![CDATA[/common/js/orgIndex/jquery.tokeninput.js]]></inputfile>
<inputfile><![CDATA[/apps_res/collaboration/js/newCollaboration.js]]></inputfile>
<inputfile><![CDATA[/apps_res/collaboration/js/project_select.js]]></inputfile>
<inputfile><![CDATA[/common/js/template/templateApi.js]]></inputfile>
<inputfile><![CDATA[/apps_res/collaboration/js/dealNodeCommon.js]]></inputfile>
<inputfile><![CDATA[/apps_res/collaboration/atwho/js/jquery.atwho.js]]></inputfile>
<inputfile><![CDATA[/apps_res/collaboration/atwho/js/jquery.caret.js]]></inputfile>
<outputfile isObscure="false"><![CDATA[/apps_res/collaboration/js/newCollaboration-all-min.js]]></outputfile>
</js>
<!-- 处理协同 -->
<js>
<inputfile><![CDATA[/apps_res/uc/rongcloud/chat.js]]></inputfile>
<inputfile><![CDATA[/apps_res/collaboration/js/comment.js]]></inputfile>
<inputfile><![CDATA[/apps_res/collaboration/js/componentPage.js]]></inputfile>
<inputfile><![CDATA[/apps_res/doc/js/knowledgeBrowseUtils.js]]></inputfile>
<inputfile><![CDATA[/apps_res/collaboration/js/summary.js]]></inputfile>
<inputfile><![CDATA[/common/waterMark/js/waterMark.js]]></inputfile>
<inputfile><![CDATA[/common/isignaturehtml/artDialog/dialog-min.js]]></inputfile>
<inputfile><![CDATA[/common/workflow/allocation/manualAllocation.js]]></inputfile>
<inputfile><![CDATA[/apps_res/collaboration/atwho/js/jquery.atwho.js]]></inputfile>
<inputfile><![CDATA[/apps_res/collaboration/atwho/js/jquery.caret.js]]></inputfile>
<outputfile isObscure="false"><![CDATA[/apps_res/collaboration/js/summary-jsp-min.js]]></outputfile>
</js>
<!-- 正文编辑器 -->
Javascript压缩后端实现逻辑:/ctp-portal/src/main/java/com/seeyon/ctp/portal/util/SeeyonCompressorUtils.java
如何判断压缩成功?从ctp.log日志中能看到如下信息表示压缩成功。
INFO: SeeyonCompressorUtils: - 启动时开始压缩配置的js和css
......
INFO: SeeyonCompressorUtils: - 压缩配置的js和css结束
压缩成功后的效果,在对应文件目录能看到一个xxx.min.js文件
如何判断压缩失败?从ctp.log日志中看到“启动压缩js和css时出错”并且有异常堆栈则说明压缩失败!需要根据错误信息寻找对应代码行检查代码准确性。
# Javascript代码最佳实践
javascript模块化的第一条规则:一个模块不应该为全局名字空间添加多于一条的标记.通俗的讲:除了给全局命名空间定义一个模块的命名空间,其它的你一句代码都不要写
1、防止全局变量被覆盖
2、减少全局变量个数
//js文件名和命名空间名保持一致
//全局变量和函数保存在命名空间中
//Collaboration.js
var Collaboration;
if(!Collaboration) Collaboration = {};//第一级域名
Collaboration.xxx = xxx;//变量
Collaboration.函数名1=function(){ //函数
}
//---------------------------------------------------------------------------------------
//如果js文件名相同,则需要将不同的js放到不同的目录,则需要定义多级
var com;
if(!com) com={};//如果com不存在,则新生成一个
else if(typeof com!="object"){//如果已存在,但不是一个对象,则抛出一个异常
throw new Error("com already exists and is not an object");
}
if(!com.util) com.util={};//如果com.util不存在则新生成一个
else if(typeof com.util!="object"){//如果com存在,但不是一个对象,则抛出一个异常
throw new Error("com.util already exists and is not an object");
}
if(!com.util.Collaboration){//如果com.util.ModuleClass存在,则直接抛出异常
throw new Error("com.util.Collaboration already exists");
}
com.util.Collaboration = {//在com.util.Collaboration不存在的情况下,我们才能正常使用在此命名空间下定义的代码
函数1:function(){ 函数体;},
函数2:function(){ 函数体;}
};
快速跳转
