• HZERO 登录页面模板

    下载地址:hzero-template-parent

    一、概要

    该登录页面模板是认证服务的implicit模式登录系统的一个模板页面,下面讲解一下如何在实际开发中使用以及在模板页面如何获取参数配置并应用到模板页面上。参数配置可以去参考OAuth认证服务

    二、模板开发流程

    客制化开发方式可以参考OAuth认证服务-个性化开发

    如果要开发新的模板页面,请务必不使用 main 作为目录覆盖平台默认模板。

    三、示例

    1.展示页面

    2.模板页面

    模板页面使用 thymeleaf 模板引擎进行页面的渲染。其中可以通过 template 标签获取后端传递的数据,包括配置,语言,国际化等多种数据,以方便在js中进行使用。html使用 thymeleaf 语法,利用类似th:each等语法可有效减少js代码。

    
      <!-- favico 网站的图标 -->
      <link rel="shortcut icon" th:href="@{static/main/img/favicon.png}" type="image/x-icon" />
    
      <!-- title 网站的标题-->
      <title th:text="${systemTitle} ? ${systemTitle} : 'HZERO'"></title>
    
      
      <!-- css -->
      ...
    
      <!-- js -->
      ...
    
      <!--/* use for template data 从后端获取的数据,主要为多语言,在js中可获取相应的值来进行使用 */-->
      <template id="templateData" 
      data-linkToFindPwd="/oauth/public/main/password_find.html"
      data-captchaGetUrl="/oauth/public/captcha"
      th:attr="
        data-userNameMessage=${userNameMessage},
        data-passwordMessage=${passwordMessage},
        data-captchaMessage=${captchaMessage},
        data-captchaKeyMsg=${captchaKeyMsg},
        data-captchaLoadErrgMsg=${captchaLoadErrgMsg},
        data-phoneMsg=${phoneMsg},
        data-captchaLoadingMsg=${captchaLoadingMsg},
        data-isNeedCaptcha=${isNeedCaptcha},
        data-phone=${phone},
        data-publicKey=${publicKey}
        ...
        ">
      </template>
    
      <body>
    
    <!-- 登录页面布局,可使用模板语法 -->
    <div id="login">
        <div class="login-container login-layout">
          <div class="lang-type">
            <a class="lang-icon" th:each="language:${languages}" th:title="${language.description}">
              <img th:src="'/oauth/static/main/img/' + ${language.code} + '.png'" th:alt="${language.description}">
            </a>
          </div>
    ...
    
      </body>
    
    

    3.语言切换

    右上角的语言切换图片地址是根据后端传递的数组进行遍历来展示语言的切换种类数量

    
    <a class="lang-icon" th:each="language:${languages}" th:title="${language.description}">
       <img th:src="'/oauth/static/main/img/' + ${language.code} + '.png'" th:alt="${language.description}">
    </a>
    
    

    同时增加切换语言的逻辑,调取相应接口设置多语言,然后后端返回的字段会切换成相应的语言。多语言文件地址为 resources/messages 文件下。

    get请求是查询登录语言

    
    $(".lang-icon").click(function (e) {
        var newRegex = /\/oauth\/static\/main\/img\/(\S*)\.png/;
        var l = e.target.src.match(newRegex)[1];
        $.ajax({
          url: '/oauth/login/lang',
          type: 'POST',
          dataType: 'json',
          data: {
            lang: l
          },
          success: function success(data) {
            if (data.success) {
              window.location.href = '/oauth/login';
            }
          }
        });
      });
    
    

    4.登陆表单

    (1)登陆页面的表单,示例给了账号密码登陆和手机登陆两种方式。模板页面采用了jquery.validate插件作为表单校验的方法。

    
    // 点击登陆会先进行校验,然后会调取提交表单的方法
    $("#loginFormAccount").submit(function () {
        var flag = $("#loginFormAccount").valid();
    
        if (flag) {
          handleAccountLogin();
        }
    
        return false;
      });
    
      function _handleAccountLogin() {
          // 获取表单数据
        var t = $("#loginFormAccount").serializeArray();
        var d = {};
        $.each(t, function () {
          d[this.name] = this.value;
        });
        var username = d.username,
          password = d.password,
          captcha = d.captcha;
          // 对获取到的账号名清除空白字符
        var newUsername = $.trim(username);
          // 对获取到的密码进行加密
        var newPassword = encryptPwd($.trim(password));
        updateLoginAccount({
          submitting: true
        });
          // 提交表单
        var _createFormAndSubmit = createFormAndSubmit([{
          name: 'username',
          value: newUsername,
          type: 'text'
        }, {
          name: 'password',
          value: newPassword,
          type: 'password'
        }, {
          name: 'captcha',
          value: captcha,
          type: 'text'
        }], {
          action: 'login',
          method: 'post',
          autocomplete: 'off'
        }, function () {
          updateLoginAccount({
            submitting: false
          });
    
          if (destroy) {
            destroy();
          }
        }),
          destroy = _createFormAndSubmit.destroy;
      }
      var handleAccountLogin = Throttle()(_handleAccountLogin);
    
    ...
    
    }
    

    手机登陆的写法也是类似的。

    (2)验证码分为图片验证码和短信验证码

    图片验证码需要后端传输的 isNeedCaptcha 来判断是否显示。关于相关配置可以查看 安全策略OAuth认证服务

    
     /**
       * 更新验证码
       */
    
      function _updateCaptchaGetUrl() {
        // /oauth/public/captcha?t=...
        $(".login-account-captcha-image").attr("src", storeData.initData.captchaGetUrl + '?t=' + new Date().getTime());
      }
    
    

    短信验证码主要是处理调取验证码接口后的逻辑,这里是将倒计时的数字存储到Local Storage。这样就可以刷新页面后依旧会有倒计时.

    
    function _sendCaptcha() {
        $(".login-form-button-captcha").attr("disabled", "disabled");
        $(".login-form-button-captcha").text("..." + storeData.initData.captchaLoadingMsg);
        var t = $("#loginFormPhone").serializeArray();
        var d = {};
        $.each(t, function () {
          d[this.name] = this.value;
        });
        var phone = d.phone;
        $.ajax('public/send-phone-captcha', {
          false: 'false',
          method: 'GET',
          data: {
            phone: phone
          },
          success: function success(data) {
            if (data.success) {
              message.success({
                message: data.msg || data.message
              });
              $(".login-form-button-captcha").removeAttr("disabled");
              $(".login-form-button-captcha").text(storeData.initData.captchaLoadingMsg);
              setLocalStorage('captchaKey', data.captchaKey);
              setLocalStorage('captchaTime', data.interval || 60);
              setLocalStorage('captchaTime-date', new Date().getTime());
              startLoginPhoneCaptchaTime();
            } else {
              message.error({
                message: data.msg || data.message
              }); // 没有办法知道具体的 计时, 所以直接使用 60
    
              switch (data.code) {
                // case 'captcha.send.interval' 主要是后端也会由倒计时
                default:
                  var nextCaptchaTime = data.interval > 0 ? data.interval : 0;
                  if ((getLocalStorage('captchaTime') || 0) == 0) {
                    updateLoginPhone({
                      captchaTime: nextCaptchaTime
                    });
                    $(".login-form-button-captcha").removeAttr("disabled");
                    $(".login-form-button-captcha").text(storeData.initData.captchaLoadingMsg);
                    setLocalStorage('captchaTime', nextCaptchaTime);
                    setLocalStorage('captchaTime-date', new Date().getTime());
                    clearInterval(loginPhoneCaptchaTimer);
                    startLoginPhoneCaptchaTime();
                  }
    
                  break;
              }
            }
          },
          error: function error() {
            message.error({
              message: initData.captchaLoadErrgMsg
            });
            $(".login-form-button-captcha").removeAttr("disabled");
            $(".login-form-button-captcha").text(storeData.initData.captchaLoadingMsg);
          }
        });
      }
    
      var sendCaptcha = Throttle()(_sendCaptcha);
    
    
    

    5.密码加密

    密码使用后端传输的 publicKey 将密码加密后传输

    
    /**
       * 加密的密码
       * @param {String} password - 需要加密的密码
       */
    
    
      function encryptPwd(password) {
        /* 有公钥 使用 rsa 加密, 否则使用 md5 加密 */
        if (storeData.initData.publicKey) {
          // 初始化加密器
          var encrypt = new JSEncrypt(); // 设置公钥
    
          encrypt.setPublicKey(storeData.initData.publicKey); // 加密
    
          return encrypt.encrypt(password);
        } else {
          return encryptMd5(password);
        }
      }