# 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表达式,不推荐原因有二:

  1. EL表达式是渲染页面时执行,极易引发反射型和存储型XSS
  2. 尽量少的EL表达式代码编写,利于后续JSP->html迁移,降低前后端分离代价
  3. 更清晰的职责,前端就是前端、后端就是后端,方便做职责分离,也更适合现在的开发模式

如果老代码,已经在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样式。

1717999442805.png

common_footer.jsp也是标准模板,适合放在JSP页面</body>标签后面,主要标准化内置了:Javascript可使用的V5公共变量、SeeyonUI组件库。

1718001213498.png

# EClipse和IDEA设置默认模板

开发人员可以在自己的IED开发工具中预置默认的JSP模板方便直接调用:

1714302547876.png

1714302562416.png

# SeeyonUI前端组件

V5提供了基于JSP的各种平台UI前端组件,前面标准JSP引入规范编写代码后即可使用这些UI组件做功能开发。

详细的组件引用方法参考站内>技术平台>前端技术>JSP前端组件库

1718001526456.png

# 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(){ 函数体;}

};
编撰人:het