Liferay 6.1的创建新用户页面如图:
这个页面的代码在/html/portlet/login/create_account.jsp中:
- ...
- <portlet:actionURL var="createAccoutURL">
- <portlet:param name="saveLastPath" value="0" />
- <portlet:param name="struts_action" value="/login/create_account" />
- </portlet:actionURL>
- <aui:form action="<%= createAccoutURL %>" method="post" name="fm">
- <aui:input name="<%= Constants.CMD %>" type="hidden" value="<%= Constants.ADD %>" />
- <aui:input name="redirect" type="hidden" value="<%= redirect %>" />
- <aui:input name="openId" type="hidden" value="<%= openId %>" />
- ..
- <%
- UserPasswordException upe = (UserPasswordException)errorException;
- %>
- <c:if test="<%= upe.getType() == UserPasswordException.PASSWORD_CONTAINS_TRIVIAL_WORDS %>">
- <liferay-ui:message key="that-password-uses-common-words-please-enter-in-a-password-that-is-harder-to-guess-i-e-contains-a-mix-of-numbers-and-letters" />
- </c:if>
- <c:if test="<%= upe.getType() == UserPasswordException.PASSWORD_INVALID %>">
- <liferay-ui:message key="that-password-is-invalid-please-enter-in-a-different-password" />
- </c:if>
- <c:if test="<%= upe.getType() == UserPasswordException.PASSWORD_LENGTH %>">
- <%= LanguageUtil.format(pageContext, "that-password-is-too-short-or-too-long-please-make-sure-your-password-is-between-x-and-512-characters", String.valueOf(passwordPolicy.getMinLength()), false) %>
- </c:if>
- <c:if test="<%= upe.getType() == UserPasswordException.PASSWORD_TOO_TRIVIAL %>">
- <liferay-ui:message key="that-password-is-too-trivial" />
- </c:if>
- <c:if test="<%= upe.getType() == UserPasswordException.PASSWORDS_DO_NOT_MATCH %>">
- <liferay-ui:message key="the-passwords-you-entered-do-not-match-each-other-please-re-enter-your-password" />
- </c:if>
- </liferay-ui:error>
- <liferay-ui:error exception="<%= UserScreenNameException.class %>" message="please-enter-a-valid-screen-name" />
- <liferay-ui:error exception="<%= WebsiteURLException.class %>" message="please-enter-a-valid-url" />
- <c:if test='<%= SessionMessages.contains(request, "missingOpenIdUserInformation") %>'>
- <div class="portlet-msg-info">
- <liferay-ui:message key="you-have-successfully-authenticated-please-provide-the-following-required-information-to-access-the-portal" />
- </div>
- </c:if>
- <aui:model-context model="<%= Contact.class %>" />
- <aui:fieldset>
- <aui:column>
- <aui:input model="<%= User.class %>" name="firstName" />
- <aui:input model="<%= User.class %>" name="middleName" />
- <aui:input model="<%= User.class %>" name="lastName">
- <c:if test="<%= PrefsPropsUtil.getBoolean(company.getCompanyId(), PropsKeys.USERS_LAST_NAME_REQUIRED, PropsValues.USERS_LAST_NAME_REQUIRED) %>">
- <aui:validator name="required" />
- </c:if>
- </aui:input>
- <c:if test="<%= !PrefsPropsUtil.getBoolean(company.getCompanyId(), PropsKeys.USERS_SCREEN_NAME_ALWAYS_AUTOGENERATE) %>">
- <aui:input model="<%= User.class %>" name="screenName" />
- </c:if>
- <aui:input model="<%= User.class %>" name="emailAddress">
- <c:if test="<%= PrefsPropsUtil.getBoolean(company.getCompanyId(), PropsKeys.USERS_EMAIL_ADDRESS_REQUIRED, PropsValues.USERS_EMAIL_ADDRESS_REQUIRED) %>">
- <aui:validator name="required" />
- </c:if>
- </aui:input>
- </aui:column>
- <aui:column>
- <c:if test="<%= PropsValues.LOGIN_CREATE_ACCOUNT_ALLOW_CUSTOM_PASSWORD %>">
- <aui:input label="password" name="password1" size="30" type="password" value="" />
- <aui:input label="enter-again" name="password2" size="30" type="password" value="">
- <aui:validator name="equalTo">
- '#<portlet:namespace />password1'
- </aui:validator>
- </aui:input>
- </c:if>
- <c:choose>
- <c:when test="<%= PrefsPropsUtil.getBoolean(company.getCompanyId(), PropsKeys.FIELD_ENABLE_COM_LIFERAY_PORTAL_MODEL_CONTACT_BIRTHDAY) %>">
- <aui:input name="birthday" value="<%= birthday %>" />
- </c:when>
- <c:otherwise>
- <aui:input name="birthdayMonth" type="hidden" value="<%= Calendar.JANUARY %>" />
- <aui:input name="birthdayDay" type="hidden" value="1" />
- <aui:input name="birthdayYear" type="hidden" value="1970" />
- </c:otherwise>
- </c:choose>
- <c:if test="<%= PrefsPropsUtil.getBoolean(company.getCompanyId(), PropsKeys.FIELD_ENABLE_COM_LIFERAY_PORTAL_MODEL_CONTACT_MALE) %>">
- <aui:select label="gender" name="male">
- <aui:option label="male" value="1" />
- <aui:option label="female" selected="<%= !male %>" value="0" />
- </aui:select>
- </c:if>
- <c:if test="<%= PropsValues.CAPTCHA_CHECK_PORTAL_CREATE_ACCOUNT %>">
- <portlet:actionURL windowState="<%= LiferayWindowState.EXCLUSIVE.toString() %>" var="captchaURL">
- <portlet:param name="struts_action" value="/login/captcha" />
- </portlet:actionURL>
- <liferay-ui:captcha url="<%= captchaURL %>" />
- </c:if>
- </aui:column>
- </aui:fieldset>
- <aui:button-row>
- <aui:button type="submit" />
- </aui:button-row>
- </aui:form>
- <liferay-util:include page="/html/portlet/login/navigation.jsp" />
- <c:if test="<%= windowState.equals(WindowState.MAXIMIZED) %>">
- <aui:script>
- Liferay.Util.focusFormField(document.<portlet:namespace />fm.<portlet:namespace />firstName);
- </aui:script>
- </c:if>
这里我们讲一个比较有趣的地方-随机文本验证:
随机文本验证:
这个对应的页面元素为:
- <c:if test="<%= PropsValues.CAPTCHA_CHECK_PORTAL_CREATE_ACCOUNT %>">
- <portlet:actionURL windowState="<%= LiferayWindowState.EXCLUSIVE.toString() %>" var="captchaURL">
- <portlet:param name="struts_action" value="/login/captcha" />
- </portlet:actionURL>
- <liferay-ui:captcha url="<%= captchaURL %>" />
- </c:if>
它的外观部分展示在liferay-ui.tld中:
- <tag>
- <name>captcha</name>
- <tag-class>com.liferay.taglib.ui.CaptchaTag</tag-class>
- <body-content>JSP</body-content>
- <attribute>
- <name>url</name>
- <required>true</required>
- <rtexprvalue>true</rtexprvalue>
- </attribute>
- </tag>
看出,它的<body-content>是一段jsp代码段:
它只需要一个参数,是从上面的<portlet:param name="struts_action" value="/login/captcha" /> 传过来的。
而对应的是tag的类是 CaptchaTag,查看这个类
- ...
- private static final String _PAGE = "/html/taglib/ui/captcha/page.jsp";
- ..
- }
可以知道captcha标记包含的jsp代码位于/html/taglib/ui/captcha/page.jsp中:
- <%@ include file="/html/taglib/ui/captcha/init.jsp" %>
- <liferay-util:include page="<%= CaptchaUtil.getTaglibPath() %>" />
最终找到的页面是/html/taglib/ui/captcha/simplecaptcha.jsp:
- ...
- <%@ include file="/html/taglib/ui/captcha/init.jsp" %>
- <%
- String url = (String)request.getAttribute("liferay-ui:captcha:url");
- boolean captchaEnabled = false;
- try {
- if (portletRequest != null) {
- captchaEnabled = CaptchaUtil.isEnabled(portletRequest);
- }
- else {
- captchaEnabled = CaptchaUtil.isEnabled(request);
- }
- }
- catch (CaptchaMaxChallengesException cmce) {
- captchaEnabled = true;
- }
- %>
- <c:if test="<%= captchaEnabled %>">
- <div class="taglib-captcha">
- <img alt="<liferay-ui:message key="text-to-identify" />" class="captcha" src="<%= url %>" />
- <aui:input label="text-verification" name="captchaText" size="10" type="text" value="">
- <aui:validator name="required" />
- </aui:input>
- </div>
- </c:if>
对比页面源码,果然这两段是一致的:
再深入下去,还能找到更多有趣的发现,比如这个url,是在liferay-ui;captcha:url中指定的:
- String url = (String)request.getAttribute("liferay-ui:captcha:url");
这个url可以回溯到create_account.jsp中的定义:
- <portlet:actionURL windowState="<%= LiferayWindowState.EXCLUSIVE.toString() %>" var="captchaURL">
- <portlet:param name="struts_action" value="/login/captcha" />
- </portlet:actionURL>
- <liferay-ui:captcha url="<%= captchaURL %>" />
所以这个url不是页面url,而是局部url,这个url指向了随即生成的数字图片地址。
它会提交给struts框架,我们在struts-config.xml中找到与/login/captcha url映射匹配的处理类:
- <action path="/login/captcha" type="com.liferay.portal.captcha.CaptchaPortletAction" />
我们找到这个类研究下,发现:
- public class CaptchaPortletAction extends PortletAction {
- @Override
- public void processAction(
- ActionMapping mapping, ActionForm form, PortletConfig portletConfig,
- ActionRequest actionRequest, ActionResponse actionResponse)
- throws Exception {
- try {
- CaptchaUtil.serveImage(actionRequest, actionResponse);
- setForward(actionRequest, ActionConstants.COMMON_NULL);
- }
- catch (Exception e) {
- _log.error(e);
- }
- }
- ..
- }
它最终会让CaptchaUtil来负责绘制图形:
- public static void serveImage(
- PortletRequest portletRequest, PortletResponse portletResponse)
- throws IOException {
- getCaptcha().serveImage(portletRequest, portletResponse);
- }
而CaptchaUtil最终又会让Captcha类(最终是SimpleCaptchaImpl类)来绘制图形:
- public void serveImage(
- PortletRequest portletRequest, PortletResponse portletResponse)
- throws IOException {
- PortletSession portletSession = portletRequest.getPortletSession();
- nl.captcha.Captcha simpleCaptcha = getSimpleCaptcha();
- portletSession.setAttribute(
- WebKeys.CAPTCHA_TEXT, simpleCaptcha.getAnswer());
- HttpServletResponse response = PortalUtil.getHttpServletResponse(
- portletResponse);
- CaptchaServletUtil.writeImage(
- response.getOutputStream(), simpleCaptcha.getImage());
- }
而绘制图形的实际代码在CaptchaServletUtil中,它位于simplecaptcha.jar包中:
- public static void writeImage(HttpServletResponse httpservletresponse, BufferedImage bufferedimage)
- {
- httpservletresponse.setHeader("Cache-Control", "private,no-cache,no-store");
- httpservletresponse.setContentType("image/png");
- try
- {
- writeImage(((OutputStream) (httpservletresponse.getOutputStream())), bufferedimage);
- }
- catch(IOException ioexception)
- {
- ioexception.printStackTrace();
- }
- }
可以看出,这个图片格式为 image/png,并且这个图片永远是不会被浏览器缓存的。
最终实际承担画图任务的是ImageIO类:
- ImageIO.write(bufferedimage, "png", outputstream);