Forráskód Böngészése

新增登录验证手机号功能 --赖国然

likeku 7 éve
szülő
commit
fedde2c7ea

+ 1 - 0
modules/all_models/user.js

@@ -13,6 +13,7 @@ let collectionName = 'user';
 
 // 表结构
 let schema = {
+    ssoId: Number,
     username: String,
     email: String,
     mobile: String,

+ 3 - 1
modules/common/helper/mongoose_helper.js

@@ -61,9 +61,11 @@ class MongooseHelper {
         let self = this;
         let limit = 0;
         let skip = 0;
+        let sort = {_id:1};
         if (option !== null && Object.keys(option).length > 0) {
             limit = option.pageSize !== undefined ? option.pageSize : limit;
             skip = option.offset !== undefined ? option.offset : skip;
+            sort = option.sort !== undefined ? option.sort : sort;
         }
 
         return new Promise(function (resolve, reject) {
@@ -73,7 +75,7 @@ class MongooseHelper {
                 } else {
                     resolve(data);
                 }
-            }).skip(skip).limit(limit);
+            }).sort(sort).skip(skip).limit(limit);
         });
     }
 

+ 6 - 2
modules/users/controllers/login_controller.js

@@ -54,12 +54,17 @@ class LoginController {
 
             // 正确登录后 存入session
             let userData = responseData[0];
+
+            if (userData.mobile === '') {
+                return response.json({error: 2,ssoId: userData.id});
+            }
             let sessionUser = {
                 ssoId: userData.id,
                 username: userData.username,
                 email: userData.useremail,
                 mobile: userData.mobile,
             };
+
             request.session.sessionUser = sessionUser;
             // 记录用户数据到数据库
             let result = await userModel.markUser(sessionUser, request);
@@ -76,6 +81,7 @@ class LoginController {
             compilationList = preferenceSetting.login_ask === 1 ? await compilationModel.getList() : [];
             // 获取编办信息
             let sessionCompilation = request.session.sessionCompilation;
+
             if (preferenceSetting.login_ask === 0 && !sessionCompilation &&
                 preferenceSetting.select_version !== '') {
 
@@ -83,12 +89,10 @@ class LoginController {
                 request.session.sessionCompilation = compilationData;
             }
 
-
         } catch (error) {
             console.log(error);
             return response.json({error: 1, msg: error});
         }
-        console.log(request.session.lastPage);
         response.json({
             error: 0,
             msg: '',

+ 80 - 0
modules/users/controllers/sms_controller.js

@@ -0,0 +1,80 @@
+/**
+ * 短信相关控制器
+ *
+ * @author EllisRan
+ * @date 2018/4/17
+ * @version
+ */
+import SmsModel from "../models/sms_model";
+
+class SmsController {
+
+    /**
+     * 发送短信验证码
+     *
+     * @param {object} request
+     * @param {object} response
+     * @return {void}
+     */
+    async code(request, response) {
+        let responseData = {
+            err: 0,
+            msg: ''
+        };
+        let mobile = request.body.mobile;
+        let type = request.body.type;
+        try {
+            let smsModel = new SmsModel();
+            let returnStatus = await smsModel.sendSmsFromSSO(mobile, type);
+            if (returnStatus === null) {
+                throw '获取数据失败!';
+            }
+            if(returnStatus != 1){
+                responseData.err = 1;
+                responseData.msg = await smsModel.getStatusMsg(returnStatus);
+            }
+        } catch (error) {
+            console.log(error);
+            responseData.err = 1;
+            responseData.msg = error;
+        }
+
+        response.json(responseData);
+    }
+
+    /**
+     * 手机和验证码验证并更新sso
+     *
+     * @param {object} request
+     * @param {object} response
+     * @return {void}
+     */
+    async setMobile(request, response) {
+        let responseData = {
+            err: 0,
+            msg: ''
+        };
+        let ssoId = request.body.ssoId;
+        let mobile = request.body.mobile;
+        let code = request.body.code;
+        try {
+            let smsModel = new SmsModel();
+            let returnStatus = await smsModel.checkCodeFromSSO(ssoId, mobile, code);
+            if (returnStatus === null) {
+                throw '获取数据失败!';
+            }
+            if(returnStatus != 1){
+                responseData.err = 1;
+                responseData.msg = await smsModel.getStatusMsg(returnStatus);
+            }
+        } catch (error) {
+            console.log(error);
+            responseData.err = 1;
+            responseData.msg = error;
+        }
+
+        response.json(responseData);
+    }
+}
+
+export default SmsController;

+ 1 - 1
modules/users/controllers/user_controller.js

@@ -59,7 +59,7 @@ class UserController extends BaseController {
             let sessionUser = request.session.sessionUser;
             // 切换验证场景
             let userModel = new UserModel();
-            let condition = {email: sessionUser.email};
+            let condition = {ssoId: sessionUser.ssoId};
             userModel.setScene('saveInfo');
             let result = await userModel.updateUser(condition, updateData);
 

+ 1 - 1
modules/users/models/log_model.js

@@ -95,7 +95,7 @@ class LogModel extends BaseModel {
         };
         page = parseInt(page);
         page = page <= 1 ? 1 : page;
-        let option = {pageSize: pageSize, offset: parseInt((page - 1) * pageSize)};
+        let option = {pageSize: pageSize, offset: parseInt((page - 1) * pageSize), sort: {create_time:-1}};
 
         let logList = await this.db.find(condition, null, option);
         logList = logList.length > 0 ? logList : [];

+ 97 - 0
modules/users/models/sms_model.js

@@ -0,0 +1,97 @@
+/**
+ * 短信业务模型
+ *
+ * @author EllisRan
+ * @date 2018/4/17
+ * @version
+ */
+import Request from "request";
+import BaseModel from "../../common/base/base_model";
+
+class SmsModel extends BaseModel {
+
+    /**
+     * 状态信息
+     *
+     * @var {object}
+     */
+    statusMsg = { 1:'发送成功', 2:'该手机号已注册', 3:'该手机号未注册', 0:'参数有误',
+        4:'短信接口方出错', 5:'验证码添加到数据库出错', 6:'手机号更改到数据库出错',
+        7:'验证码有误', 8:'sso账号不存在' };
+
+    /**
+     * 根据手机号和短信类型调用SSO短信接口获取信息
+     *
+     * @param {string} mobile
+     * @param {int} type  短信用途: 1->未注册过sso手机或更换手机号; 2->找回密码或修改密码;
+     * @return {int}  返回状态码
+     */
+    async sendSmsFromSSO(mobile, type) {
+        let postData = {
+            url: 'http://sso.smartcost.com.cn/building/api/smscode',
+            form: {mobile: mobile, type: type},
+            encoding: 'utf8'
+        };
+        return new Promise(function (resolve, reject) {
+            try {
+                // 请求接口
+                Request.post(postData, function (err, postResponse, body) {
+                    if (err) {
+                        throw '请求错误';
+                    }
+                    if (postResponse.statusCode !== 200) {
+                        throw '通行证验证失败!';
+                    }
+                    resolve(body);
+                });
+            } catch (error) {
+                reject([]);
+            }
+        });
+    }
+
+    /**
+     * 根据手机号和短信调用SSO短信接口并更新手机号
+     *
+     * @param {string} ssoid
+     * @param {string} mobile
+     * @param {string} code
+     * @return {int}  返回状态码
+     */
+    async checkCodeFromSSO(ssoid, mobile, code) {
+        let postData = {
+            url: 'http://sso.smartcost.com.cn/building/api/set/mobile',
+            form: {ssoId: ssoid, mobile: mobile, code: code},
+            encoding: 'utf8'
+        };
+        return new Promise(function (resolve, reject) {
+            try {
+                // 请求接口
+                Request.post(postData, function (err, postResponse, body) {
+                    if (err) {
+                        throw '请求错误';
+                    }
+                    if (postResponse.statusCode !== 200) {
+                        throw '通行证验证失败!';
+                    }
+                    resolve(body);
+                });
+            } catch (error) {
+                reject([]);
+            }
+        });
+    }
+
+
+    /**
+     * 根据状态码获取反馈信息
+     *
+     * @param {string} status
+     * @return {string}
+     */
+    async getStatusMsg(status) {
+        return this.statusMsg[status];
+    }
+}
+
+export default SmsModel;

+ 27 - 5
modules/users/models/user_model.js

@@ -84,17 +84,28 @@ class UserModel extends BaseModel {
      * @return {Promise}
      */
     async markUser(userData, request = null) {
-        let userDataFromDb = await this.findDataByName(userData.username);
+        let userDataFromDb2 = await this.findDataBySsoId(userData.ssoId);
+        let userDataFromDb = await this.findDataByName(userData.username);  //后面新增的账号可淘汰这方法,当前使用是为了兼容旧的账号
         let result = false;
 
-        if (userDataFromDb === null) {
+        if (userDataFromDb === null && userDataFromDb2 === null) {
             // 不存在用户则入库
             result = await this.addUser(userData);
             userDataFromDb = result;
         } else {
-            // 存在则新增登录信息
-            let logModel = new LogModel();
-            result = await logModel.addLoginLog(userDataFromDb._id, request);
+            // 存在则新增登录信息并更新账号信息
+            // let condition = {ssoId: sessionUser.ssoId};
+            let condition = {username: userData.username};
+            let UpdateData = {
+                email : userData.email,
+                mobile : userData.mobile,
+                ssoId : userData.ssoId
+            };
+            let updateResult = await this.updateUser(condition,UpdateData);
+            if (updateResult.ok === 1) {
+                let logModel = new LogModel();
+                result = await logModel.addLoginLog(userDataFromDb._id, request);
+            }
         }
         request.session.sessionUser.id = userDataFromDb._id;
         request.session.sessionUser.real_name = userDataFromDb.real_name;
@@ -129,6 +140,16 @@ class UserModel extends BaseModel {
     }
 
     /**
+     * 根据ssoID查找数据
+     *
+     * @param {string} ssoId
+     * @return {object}
+     */
+    findDataBySsoId(ssoId) {
+        return this.db.findOne({ssoId: ssoId});
+    }
+
+    /**
      * 新增用户
      *
      * @param {object} userData
@@ -136,6 +157,7 @@ class UserModel extends BaseModel {
      */
     addUser(userData) {
         let insertData = {
+            ssoId: userData.ssoId,
             username: userData.username,
             email: userData.email,
             mobile: userData.mobile,

+ 21 - 0
modules/users/routes/sms_route.js

@@ -0,0 +1,21 @@
+/**
+ * 短信相关路由
+ *
+ * @author EllisRan
+ * @date 2018/4/17
+ * @version
+ */
+import Express from "express";
+import SmsController from "../controllers/sms_controller";
+
+module.exports = function (app) {
+
+    const router = Express.Router();
+    const smsController = new SmsController();
+
+// action定义区域
+    router.post('/code', smsController.code);
+    router.post('/mobile', smsController.setMobile);
+
+    app.use('/sms',router);
+};

+ 2 - 2
server.js

@@ -55,8 +55,8 @@ app.use(session({
 // 登录状态全局判断
 app.use(function (req, res, next) {
     let url = req.originalUrl;
-    if (/^\/login/.test(url) || /\.map|\.ico$/.test(url)) {
-        // 如果是登录页面则忽略判断数据
+    if (/^\/login/.test(url) || /\.map|\.ico$/.test(url) || /^\/sms/.test(url)) {
+        // 如果是登录页面或短信接口则忽略判断数据
         next();
     } else {
         try {

+ 1 - 1
web/common/html/header.html

@@ -37,7 +37,7 @@
     <div id="testDisplay" style="color:#ff7e0e;">&nbsp;&nbsp;</div>
     <div class="ml-auto navbar-text p-0">
         <div class="dropdown d-inline-block navbar-nav">
-            <button class="btn btn-link btn-sm dropdown-toggle" type="button" data-toggle="dropdown"><%= sessionUser.real_name === '' ? sessionUser.email : sessionUser.real_name %></button>
+            <button class="btn btn-link btn-sm dropdown-toggle" type="button" data-toggle="dropdown"><%= sessionUser.real_name === '' ? sessionUser.mobile : sessionUser.real_name %></button>
             <div class="dropdown-menu dropdown-menu-right">
                 <a class="dropdown-item" href="/user/info" target="_blank">账号资料</a>
                 <a class="dropdown-item" href="user-buy.html" target="_blank">产品购买</a>

+ 2 - 1
web/common/html/page.html

@@ -1,6 +1,7 @@
 <nav aria-label="...">
-    <ul aria-label="pagination" id="pages"></ul>
+    <ul class="pagination" id="pages"></ul>
 </nav>
+<script type="text/javascript" src="/lib/bootstrap/bootstrap-paginator.js"></script>
 <script type="text/javascript">
     let options = {
         bootstrapMajorVersion: 3,

+ 34 - 0
web/users/html/login.html

@@ -66,6 +66,40 @@
             </div>
         </div>
     </div>
+    <!--弹出手机号-->
+    <div class="modal fade" id="phone" data-backdrop="static">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h5 class="modal-title">验证手机号</h5>
+                </div>
+                <div class="modal-body">
+                    <input type="hidden" id="check_ssoId" value="" />
+                    <div class="form-row">
+                        <div class="form-group col-md-8">
+                            <input type="text" class="form-control" id="mobile" placeholder="输入手机号">
+                            <div class="invalid-feedback">
+                            </div>
+                        </div>
+                    </div>
+                    <div class="form-row">
+                        <div class="form-group col-md-8">
+                            <input type="text" class="form-control" readonly id="code" placeholder="输入验证码">
+                            <div class="invalid-feedback">
+                            </div>
+                        </div>
+                        <div class="form-group col-md-4">
+                            <button class="btn btn-primary" id="get-code">获取验证码</button>
+                        </div>
+                    </div>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                    <button class="btn btn-primary" id="check-code">确定</button>
+                </div>
+            </div>
+        </div>
+    </div>
     <!-- JS. -->
     <!-- inject:js -->
     <script type="text/javascript" src="/public/web/scMathUtil.js"></script>

+ 3 - 3
web/users/html/user-info.html

@@ -19,16 +19,16 @@
     <div class="poj-manage container-fluid">
         <div class="row">
             <div class="col-lg-2">
-                <div class="poj-cate mt-3">
+                <div class="mt-3">
                     <ul class="nav nav-pills flex-column">
                         <li class="nav-item">
-                            <a class="nav-link active" href="user-info.html">账号资料</a>
+                            <a class="nav-link active" href="/user/info">账号资料</a>
                         </li>
                         <li class="nav-item">
                             <a class="nav-link" href="/user/safe">账号安全</a>
                         </li>
                         <li class="nav-item">
-                            <a class="nav-link" href="user-buy.html">产品购买</a>
+                            <a class="nav-link" href="/user/buy">产品购买</a>
                         </li>
                         <li class="nav-item">
                             <a class="nav-link" href="/user/preferences">偏好设置</a>

+ 8 - 8
web/users/html/user-safe.html

@@ -5,7 +5,7 @@
     <meta charset="utf-8">
     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
     <meta http-equiv="x-ua-compatible" content="ie=edge">
-    <title>账号资料-Smartcost</title>
+    <title>账号安全-Smartcost</title>
     <link rel="stylesheet" href="/lib/bootstrap/css/bootstrap.min.css">
     <link rel="stylesheet" href="/web/building_saas/css/main.css">
     <link rel="stylesheet" href="/lib/font-awesome/font-awesome.min.css">
@@ -13,8 +13,8 @@
     <script src="/lib/jquery/jquery-3.2.1.min.js"></script>
     <script src="/lib/popper/popper.min.js"></script>
     <script src="/lib/bootstrap/bootstrap.min.js"></script>
-    <script type="text/javascript" src="/lib/bootstrap/bootstrap-paginator.js"></script>
-    <script src="/web/building_saas/js/global.js"></script>
+    <!--<script type="text/javascript" src="/lib/bootstrap/bootstrap-paginator.js"></script>-->
+    <!--<script src="/web/building_saas/js/global.js"></script>-->
 </head>
 
 <body>
@@ -25,7 +25,7 @@
     <div class="poj-manage container-fluid">
         <div class="row">
             <div class="col-lg-2">
-                <div class="poj-cate mt-3">
+                <div class="mt-3">
                     <ul class="nav nav-pills flex-column">
                         <li class="nav-item">
                             <a class="nav-link" href="/user/info">账号资料</a>
@@ -34,7 +34,7 @@
                             <a class="nav-link active" href="/user/safe">账号安全</a>
                         </li>
                         <li class="nav-item">
-                            <a class="nav-link" href="user-buy.html">产品购买</a>
+                            <a class="nav-link" href="/user/buy">产品购买</a>
                         </li>
                         <li class="nav-item">
                             <a class="nav-link" href="/user/preferences">偏好设置</a>
@@ -129,8 +129,8 @@
     </div>
 </div>
 </body>
-<script type="text/javascript">
-    autoFlashHeight();
-</script>
+<!--<script type="text/javascript">-->
+    <!--autoFlashHeight();-->
+<!--</script>-->
 
 </html>

+ 15 - 16
web/users/html/user-set.html

@@ -19,19 +19,20 @@
         <div class="poj-manage container-fluid">
             <div class="row">
                 <div class="col-lg-2">
-                    <div class="poj-cate mt-3">
+                    <!--<div class="poj-cate mt-3">-->
+                    <div class="mt-3">
                         <ul class="nav nav-pills flex-column">
                             <li class="nav-item">
-                                <a class="nav-link" href="user-info.html">账号资料</a>
+                                <a class="nav-link" href="/user/info">账号资料</a>
                             </li>
                             <li class="nav-item">
-                                <a class="nav-link" href="user-safe.html">账号安全</a>
+                                <a class="nav-link" href="/user/safe">账号安全</a>
                             </li>
                             <li class="nav-item">
-                                <a class="nav-link" href="user-buy.html">产品购买</a>
+                                <a class="nav-link" href="/user/buy">产品购买</a>
                             </li>
                             <li class="nav-item">
-                                <a class="nav-link active" href="user-set.html">偏好设置</a>
+                                <a class="nav-link active" href="/user/preferences">偏好设置</a>
                             </li>
                         </ul>
                     </div>
@@ -42,19 +43,17 @@
                         <form method="post" action="/user/save-preferences">
                             <div class="form-group">
                                 <label class="form-control-label">登录时选择版本</label>
-                                <div>
-                                    <label class="custom-control custom-radio">
-                                      <input name="login_ask" type="radio" class="custom-control-input" value="1"
+                                <div class="form-control">
+                                    <div class="form-check form-check-inline">
+                                      <input name="login_ask" type="radio" class="form-check-input" value="1"
                                         <% if(preferenceSetting.login_ask === 1) { %>checked="checked" <% } %>>
-                                      <span class="custom-control-indicator"></span>
-                                      <span class="custom-control-description">每次询问</span>
-                                    </label>
-                                    <label class="custom-control custom-radio">
-                                      <input name="login_ask" type="radio" class="custom-control-input" value="0"
+                                      <label class="form-check-label">每次询问</label>
+                                    </div>
+                                    <div class="form-check form-check-inline">
+                                      <input name="login_ask" type="radio" class="form-check-input" value="0"
                                         <% if(preferenceSetting.login_ask === 0) { %>checked="checked" <% } %>>
-                                      <span class="custom-control-indicator"></span>
-                                      <span class="custom-control-description">指定版本</span>
-                                    </label>
+                                      <span class="form-check-label">指定版本</span>
+                                    </div>
                                 </div>
                             </div>
                             <div class="form-group">

+ 134 - 0
web/users/js/login.js

@@ -35,6 +35,9 @@ $(document).ready(function () {
                         setVersion(response.compilation_list);
                         $('#ver').modal('show');
                     }
+                } else if(response.error === 2) {
+                    $('#check_ssoId').val(response.ssoId);
+                    $('#phone').modal('show');
                 } else {
                     let msg = response.msg !== undefined ? response.msg : '未知错误';
                     showError(msg, $("input"));
@@ -48,15 +51,146 @@ $(document).ready(function () {
 
     $("input").blur(function () {
         cleanError();
+        cleanValidError($(this));
     });
 
     $(".form-control").on('input', function () {
         $('#hint').html('&nbsp;');
     });
 
+    $("#get-code").click(function() {
+        const mobile = $("#mobile").val();
+        if(!validMobile(mobile)){
+            return false;
+        }
+        const btn = $(this);
+        if(!btn.hasClass('disabled')){
+            $.ajax({
+                url: '/sms/code',
+                type: 'post',
+                data: { mobile: mobile, type: 1},
+                error: function() {
+                    showValidError('短信接口出错!',$('#mobile'));
+                },
+                beforeSend: function() {
+                },
+                success: function(response) {
+                    if (response.err === 0) {
+                        codeSuccess(btn);
+                    } else {
+                        showValidError(response.msg,$('#mobile'));
+                    }
+                }
+            });
+        }
+    });
+
+    $('#check-code').click(function () {
+        const mobile = $("#mobile").val();
+        const ssoId = $("#check_ssoId").val();
+        const code = $("#code").val();
+        if(!validMobile(mobile)) {
+            return false;
+        }
+        if(ssoId === undefined || ssoId === '') {
+            showValidError('账号有误!', $('#code'));
+            return false;
+        }
+        if($.trim(code) === '') {
+            showValidError('验证码不能为空!', $('#code'));
+            return false;
+        }
+        $.ajax({
+            url: '/sms/mobile',
+            type: 'post',
+            data: {ssoId: ssoId, mobile: mobile, code: code},
+            error: function() {
+                showValidError('接口出错!',$('#code'));
+            },
+            beforeSend: function() {
+            },
+            success: function(response) {
+                if (response.err === 0) {
+                    $("#login").click();
+                    $('#phone').modal('hide');
+                } else {
+                    showValidError(response.msg,$('#code'));
+                }
+            }
+        })
+
+    });
+
 });
 
 /**
+ * 获取成功后的操作
+ *
+ * @param {Object} btn - 点击的按钮
+ * @return {void}
+ */
+function codeSuccess(btn) {
+    let counter = 60;
+    btn.removeClass('btn-primary').addClass('btn-outline-secondary disabled').text(counter + '秒 重新获取');
+    btn.parents().siblings('div').children('input').removeAttr('readonly');
+
+    const countDown = setInterval(function() {
+        const countString = counter - 1 <= 0 ? '' : ' ' + (counter - 1) + '秒 ';
+        // 倒数结束后
+        if (countString === '') {
+            clearInterval(countDown);
+            btn.removeClass('btn-outline-secondary disabled').addClass('btn-primary').text('获取验证码');
+        }
+        const text = countString + '重新获取';
+        btn.text(text);
+        counter -= 1;
+    }, 1000);
+}
+
+/**
+ * 验证手机号是否正确
+ *
+ * @return {boolean}
+ */
+function validMobile(mobile) {
+    let result = true;
+    if($.trim(mobile) === ''){
+        showValidError('手机号不能为空!',$('#mobile'));
+        return false;
+    }
+    let mobileValid =  /^(((13[0-9]{1})|(15[0-9]{1})|(18[0-9]{1})|(17[0-9]{1})|(14[0-9]{1}))+\d{8})$/;
+    if(!mobileValid.test(mobile)){
+        showValidError('手机号码格式有误!',$('#mobile'));
+        return false;
+    }
+    return result;
+}
+
+/**
+ * 提示验证信息错误
+ *
+ * @param {string} msg
+ * @param {object} element
+ * @return {void}
+ */
+function showValidError(msg, element) {
+    if (element !== null) {
+        element.addClass('is-invalid');
+        element.siblings().text(msg);
+    }
+}
+
+/**
+ * 清除验证信息错误提示
+ *
+ * @return {void}
+ */
+function cleanValidError(element) {
+    element.removeClass('is-invalid');
+    element.siblings().text('');
+}
+
+/**
  * 验证数据
  *
  * @return {boolean}