Sfoglia il codice sorgente

1.提交账号修改密码
2.提交账号绑定认证手机

olym 7 anni fa
parent
commit
53f82c7759

+ 91 - 0
app/controller/profile_controller.js

@@ -29,9 +29,14 @@ module.exports = app => {
             let baseRule = ctx.service.projectAccount.rule('profileBase');
             baseRule = ctx.helper.validateConvert(baseRule);
 
+            // 获取修改密码的字段规则
+            let passwordRule = ctx.service.projectAccount.rule('modifyPassword');
+            passwordRule = ctx.helper.validateConvert(passwordRule);
+
             const renderData = {
                 accountData,
                 baseRule: JSON.stringify(baseRule),
+                passwordRule: JSON.stringify(passwordRule),
             };
             await this.layout('profile/info.ejs', renderData);
         }
@@ -65,6 +70,92 @@ module.exports = app => {
             ctx.redirect(ctx.request.headers.referer);
         }
 
+        /**
+         * 修改密码操作
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        async modifyPassword(ctx) {
+            const password = ctx.request.body.password;
+            const newPassword = ctx.request.body.new_password;
+
+            try {
+                const sessionUser = ctx.session.sessionUser;
+                let accountId = sessionUser.accountId;
+                accountId = parseInt(accountId);
+                if (isNaN(accountId) || accountId <= 0) {
+                    throw '参数错误';
+                }
+                // 验证数据
+                const passwordRule = ctx.service.projectAccount.rule('modifyPassword');
+                ctx.helper.validate(passwordRule);
+
+                const result = await ctx.service.projectAccount.modifyPassword(accountId, password, newPassword);
+                if (!result) {
+                    throw '修改密码失败';
+                }
+                this.setMessage('修改密码成功', this.messageType.SUCCESS);
+            } catch (error) {
+                this.setMessage(error.toString(), this.messageType.ERROR);
+            }
+
+            ctx.redirect(ctx.request.headers.referer);
+        }
+
+        /**
+         * 设置短信验证码
+         *
+         * @param {object} ctx - egg全局变量
+         * @return {void}
+         */
+        async getCode(ctx) {
+            const response = {
+                err: 0,
+                msg: '',
+            };
+            try {
+                const sessionUser = ctx.session.sessionUser;
+                const mobile = ctx.request.body.mobile;
+                const rule = { mobile: { type: 'mobile', allowEmpty: false } };
+                ctx.helper.validate(rule);
+
+                const result = await ctx.service.projectAccount.setSMSCode(sessionUser.accountId, mobile);
+                if (!result) {
+                    throw '获取验证码失败';
+                }
+            } catch (error) {
+                response.err = 1;
+                response.msg = error.toString();
+            }
+
+            ctx.body = response;
+        }
+
+        /**
+         * 绑定认证手机
+         *
+         * @param {object} ctx - egg全局变量
+         * @return {void}
+         */
+        async bindMobile(ctx) {
+            try {
+                const rule = ctx.service.projectAccount.rule('bindMobile');
+                ctx.helper.validate(rule);
+
+                const sessionUser = ctx.session.sessionUser;
+                const result = await ctx.service.projectAccount.bindMobile(sessionUser.accountId, ctx.request.body);
+
+                if (!result) {
+                    throw '绑定手机失败!';
+                }
+                this.setMessage('绑定成功', this.messageType.SUCCESS);
+            } catch (error) {
+                console.log(error);
+                this.setMessage(error.toString(), this.messageType.ERROR);
+            }
+            ctx.redirect(ctx.request.headers.referer);
+        }
     }
 
     return ProfileController;

+ 3 - 2
app/extend/helper.js

@@ -144,14 +144,15 @@ module.exports = {
      * @param {String} url - 请求地址
      * @param {Object} data - 请求数据
      * @param {String} type - 请求类型(POST) POST | GET
+     * @param {String} dataType - 数据类型 json|text
      * @return {Object} - 请求结果
      */
-    async sendRequest(url, data, type = 'POST') {
+    async sendRequest(url, data, type = 'POST', dataType = 'json') {
         // 发起请求
         const response = await this.ctx.curl(url, {
             method: type,
             data,
-            dataType: 'json',
+            dataType,
         });
         if (response.status !== 200) {
             throw '请求失败';

+ 79 - 0
app/lib/sms.js

@@ -0,0 +1,79 @@
+'use strict';
+
+/**
+ * 短信发送相关接口
+ *
+ * @author CaiAoLin
+ * @date 2018/1/25
+ * @version
+ */
+
+const xmlReader = require('xmlreader');
+class SMS {
+
+    /**
+     * 构造函数
+     *
+     * @param {Object} ctx - egg全局变量
+     * @return {void}
+     */
+    constructor(ctx) {
+        this.ctx = ctx;
+        this.url = 'http://101.132.42.40:7862/sms';
+    }
+
+    /**
+     * 发送信息
+     *
+     * @param {String|Array} mobile - 发送的电话号码
+     * @param {String} content - 发送的内容
+     * @return {Boolean} - 发送结果
+     */
+    async send(mobile, content) {
+        if (mobile instanceof Array) {
+            mobile = mobile.join(',');
+        }
+        let result = false;
+        const config = this.ctx.app.config.sms;
+        const postData = {
+            action: 'send',
+            account: config.account,
+            password: config.password,
+            mobile,
+            content,
+            extno: config.extno,
+        };
+        try {
+            const response = await this.ctx.helper.sendRequest(this.url, postData, 'POST', 'text');
+            const xmlData = await this.xmlParse(response);
+            if (xmlData === undefined || xmlData.returnstatus.text() !== 'Success') {
+                throw '短信发送失败!';
+            }
+            result = true;
+        } catch (error) {
+            result = false;
+        }
+
+        return result;
+    }
+
+    /**
+     * xml解析
+     *
+     * @param {String} xml - xml数据
+     * @return {Object} - 解析结果
+     */
+    xmlParse(xml) {
+        return new Promise(function(resolve, reject) {
+            xmlReader.read(xml, function(errors, xmlData) {
+                if (errors) {
+                    reject('');
+                } else {
+                    resolve(xmlData.returnsms);
+                }
+            });
+        });
+    }
+}
+
+module.exports = SMS;

+ 85 - 0
app/public/js/profile.js

@@ -33,8 +33,93 @@ $(document).ready(function() {
         $("#base-submit").click(function() {
             $("#base-form").valid();
         });
+
+        options.rules = passwordRule;
+        // 修改密码验证
+        $("#password-form").validate(options);
+        $("#modify-password").click(function() {
+            $("#password-form").valid();
+        });
+
+        options.rules = {
+            auth_mobile: {
+                mobile: true,
+                required: true,
+            },
+        };
+        $("#mobile-form").validate(options);
+        // 获取验证码
+        let isPosting = false;
+        $("#get-code").click(function() {
+            if (isPosting) {
+                return false;
+            }
+            if(!$("#mobile-form").valid()) {
+                return false;
+            }
+
+            const mobile = $("input[name='auth_mobile']").val();
+            console.log(mobile);
+            const btn = $(this);
+
+            $.ajax({
+                url: '/profile/code?_csrf=' + csrf,
+                type: 'post',
+                data: { mobile: mobile },
+                dataTye: 'json',
+                error: function() {
+                    isPosting = false;
+                },
+                beforeSend: function() {
+                    isPosting = true;
+                },
+                success: function(response) {
+                    isPosting = false;
+                    if (response.err === 0) {
+                        codeSuccess(btn);
+                    } else {
+                        alert(response.msg);
+                    }
+                }
+            });
+        });
+
+        // 绑定按钮
+        $("#bind-btn").click(function() {
+            const code = $("input[name='code']").val();
+            if (code.length < 6) {
+                alert('请填写正确的验证码');
+                return false;
+            }
+        });
     } catch (error) {
         console.log(error);
     }
 
 });
+
+/**
+ * 获取成功后的操作
+ *
+ * @param {Object} btn - 点击的按钮
+ * @return {void}
+ */
+function codeSuccess(btn) {
+    let counter = 60;
+    btn.addClass('disabled').text('重新获取 ' + counter + 'S');
+    btn.parent().siblings('input').removeAttr('readonly').attr('placeholder', '输入短信中的5位验证码');
+    const bindBtn = $("#bind-btn");
+    bindBtn.removeClass('btn-secondary disabled').addClass('btn-primary');
+
+    const countDown = setInterval(function() {
+        const countString = counter - 1 <= 0 ? '' : ' ' + (counter - 1) + 'S';
+        // 倒数结束后
+        if (countString === '') {
+            clearInterval(countDown);
+            btn.removeClass('disabled');
+        }
+        const text = '重新获取' + countString;
+        btn.text(text);
+        counter -= 1;
+    }, 1000);
+}

+ 3 - 0
app/router.js

@@ -37,4 +37,7 @@ module.exports = app => {
     // 个人账号相关
     app.get('/profile/info', sessionAuth, 'profileController.info');
     app.post('/profile/save', sessionAuth, 'profileController.saveBase');
+    app.post('/profile/password', sessionAuth, 'profileController.modifyPassword');
+    app.post('/profile/code', sessionAuth, 'profileController.getCode');
+    app.post('/profile/bind', sessionAuth, 'profileController.bindMobile');
 };

+ 101 - 0
app/service/project_account.js

@@ -11,6 +11,7 @@
 // 加密类
 const crypto = require('crypto');
 const SSO = require('../lib/sso');
+const SMS = require('../lib/sms');
 module.exports = app => {
 
     class ProjectAccount extends app.BaseService {
@@ -57,6 +58,19 @@ module.exports = app => {
                         telephone: { type: 'string', allowEmpty: true, max: 12 },
                     };
                     break;
+                case 'modifyPassword':
+                    rule = {
+                        password: { type: 'string', required: true, min: 6 },
+                        new_password: { type: 'string', required: true, min: 6 },
+                        confirm_password: { type: 'string', required: true, min: 6 },
+                    };
+                    break;
+                case 'bindMobile':
+                    rule = {
+                        code: { type: 'string', required: true, min: 6 },
+                        auth_mobile: { type: 'mobile', allowEmpty: false },
+                    };
+                    break;
                 default:
                     break;
             }
@@ -241,6 +255,93 @@ module.exports = app => {
 
             return result;
         }
+
+        /**
+         * 修改密码
+         *
+         * @param {Number} accountId - 账号id
+         * @param {String} password - 旧密码
+         * @param {String} newPassword - 新密码
+         * @return {Boolean} - 返回修改结果
+         */
+        async modifyPassword(accountId, password, newPassword) {
+            // 查找账号
+            const accountData = await this.getDataByCondition({ id: accountId });
+            if (accountData.password === undefined) {
+                throw '不存在对应用户';
+            }
+            // 判断是否为sso账号,如果是则不能在此系统修改(后续通过接口修改?)
+            if (accountData.password === 'SSO password') {
+                throw 'SSO用户请到SSO系统修改密码';
+            }
+
+            // 加密密码
+            const encryptPassword = crypto.createHmac('sha1', accountData.account).update(password)
+                .digest().toString('base64');
+            if (encryptPassword !== accountData.password) {
+                throw '密码错误';
+            }
+
+            // 通过密码验证后修改数据
+            const encryptNewPassword = crypto.createHmac('sha1', accountData.account).update(newPassword)
+                .digest().toString('base64');
+            const updateData = { password: encryptNewPassword };
+            const result = await this.save(updateData, accountId);
+
+            return result;
+        }
+
+        /**
+         * 设置短信验证码
+         *
+         * @param {Number} accountId - 账号id
+         * @param {String} mobile - 电话号码
+         * @return {Boolean} - 设置结果
+         */
+        async setSMSCode(accountId, mobile) {
+            const cacheKey = 'smsCode:' + accountId;
+            const randString = this.ctx.helper.generateRandomString(6, 2);
+            // 缓存15分钟(拼接电话,防止篡改)
+            this.cache.set(cacheKey, randString + mobile, 'EX', 900);
+            let result = false;
+
+            // 发送短信
+            try {
+                const sms = new SMS(this.ctx);
+                const content = '【纵横计量支付】验证码:' + randString + ',15分钟有效。';
+                result = await sms.send(mobile, content);
+            } catch (error) {
+                result = false;
+            }
+
+            return result;
+        }
+
+        /**
+         * 绑定认证手机
+         *
+         * @param {Number} accountId - 账号id
+         * @param {Object} data - post过来的数据
+         * @return {Boolean} - 绑定结果
+         */
+        async bindMobile(accountId, data) {
+            const cacheKey = 'smsCode:' + accountId;
+            const cacheCode = await this.cache.get(cacheKey);
+            if (cacheCode === null || data.code === undefined || cacheCode !== (data.code + data.auth_mobile)) {
+                return false;
+            }
+
+            // 查找是否有重复的认证手机
+            const accountData = await this.getDataByCondition({ auth_mobile: data.auth_mobile });
+            if (accountData !== null) {
+                throw '已存在对应的手机';
+            }
+
+            const updateData = { auth_mobile: data.auth_mobile };
+
+            return this.save(updateData, accountId);
+        }
+
     }
 
     return ProjectAccount;

+ 1 - 1
app/view/layout/layout.ejs

@@ -54,7 +54,7 @@
                     <span class="caret"></span>
                 </a>
                 <div class="dropdown-menu">
-                    <a href="#" class="dropdown-item">账号资料</a>
+                    <a href="/profile/info" class="dropdown-item">账号资料</a>
                     <a href="#" class="dropdown-item">账号安全</a>
                     <div class="dropdown-divider"></div>
                     <a href="#" class="dropdown-item">帮助中心</a>

+ 24 - 18
app/view/profile/info.ejs

@@ -21,40 +21,38 @@
                         <button type="submit" class="btn btn-primary" id="base-submit">确认修改</button>
                     </form>
                     <!--账号安全-->
-                    <form action="/profile/password" method="post" style="margin-top: 20px;">
+                    <form action="/profile/password" method="post" style="margin-top: 20px;" id="password-form">
+                        <% if(accountData.password !== 'SSO password') { %>
                         <input-text label="旧密码" password="true" name="password"></input-text>
-                        <input-text label="新密码" password="true" name="new_password"></input-text>
+                        <input-text label="新密码" password="true" name="new_password" id="new_password"></input-text>
                         <input-text label="确认新密码" password="true" name="confirm_password"></input-text>
                         <input type="hidden" name="_csrf" value="<%= ctx.csrf %>">
-                        <button type="submit" class="btn btn-primary">修改密码</button>
+                        <button type="submit" class="btn btn-primary" id="modify-password">修改密码</button>
+                        <% } else { %>
+                        <p>SSO用户请到<a href="#">此处</a>修改密码</p>
+                        <% } %>
                     </form>
                     <!--绑定手机-->
-                    <div>
+                    <form id="mobile-form" method="post" action="/profile/bind">
                         <div class="form-group mt-5">
                             <label>认证手机(用于 找回密码、接收通知)</label>
-                            <input class="form-control" placeholder="输入11位手机号码" value="<%= accountData.auth_mobile %>" <% if(accountData.auth_mobile !== '') { %>disabled="disabled"<% } %>/>
+                            <input class="form-control" placeholder="输入11位手机号码" value="<%= accountData.auth_mobile %>"
+                                   <% if(accountData.auth_mobile !== '') { %>disabled="disabled"<% } %> name="auth_mobile"
+                                    maxlength="11"/>
                         </div>
                         <% if (accountData.auth_mobile === '') { %>
                         <div class="form-group">
                             <div class="input-group mb-3">
-                                <input class="form-control" readonly>
-                                <div class="input-group-append">
-                                    <button class="btn btn-outline-secondary" type="button">获取验证码</button>
-                                </div>
-                            </div>
-                        </div>
-                        <div class="form-group">
-                            <div class="input-group mb-3">
-                                <input class="form-control" placeholder="输入短信中的5位验证码">
+                                <input class="form-control" readonly="readonly" name="code"/>
+                                <input type="hidden" name="_csrf" value="<%= ctx.csrf %>">
                                 <div class="input-group-append">
-                                    <button class="btn btn-outline-secondary disabled" type="button">重新获取 58S</button>
+                                    <button class="btn btn-outline-secondary" type="button" id="get-code">获取验证码</button>
                                 </div>
                             </div>
                         </div>
-                        <button type="submit" class="btn btn-secondary disabled">确认绑定</button>
-                        <button type="submit" class="btn btn-primary">确认绑定</button>
+                        <button type="submit" class="btn btn-secondary disabled" id="bind-btn">确认绑定</button>
                         <% } %>
-                    </div>
+                    </form>
                 </div>
             </div>
         </div>
@@ -66,6 +64,14 @@
     });
     let baseRule = '<%- baseRule %>'
     baseRule = JSON.parse(baseRule);
+
+    let passwordRule = '<%- passwordRule %>';
+    passwordRule = JSON.parse(passwordRule);
+    if (passwordRule.confirm_password !== undefined) {
+        passwordRule.confirm_password.equalTo = '#new_password';
+    }
+
+    const csrf = '<%= ctx.csrf %>';
 </script>
 <script type="text/javascript" src="/public/js/profile.js"></script>
 <script type="text/javascript" src="/public/js/validate.extend.js"></script>

+ 9 - 0
config/config.default.js

@@ -45,5 +45,14 @@ module.exports = appInfo => {
             ignoreJSON: true, // 默认为 false,当设置为 true 时,将会放过所有 content-type 为 `application/json` 的请求
         },
     };
+
+    // 发送短信相关
+    config.sms = {
+        account: '710030',
+        password: 'w7pRhJ',
+        extno: '10690587',
+        authKey: 'fb5ef483e44b9556512a9febef376051',
+    };
+
     return config;
 };

+ 15 - 0
package-lock.json

@@ -9344,6 +9344,21 @@
       "integrity": "sha1-t28dWRwWomNOvbcDx729D9aBkGU=",
       "dev": true
     },
+    "xmlreader": {
+      "version": "0.2.3",
+      "resolved": "http://registry.npm.taobao.org/xmlreader/download/xmlreader-0.2.3.tgz",
+      "integrity": "sha1-hldutdV5Wabe5+0os8vRw1Wdy5A=",
+      "requires": {
+        "sax": "0.5.8"
+      },
+      "dependencies": {
+        "sax": {
+          "version": "0.5.8",
+          "resolved": "http://registry.npm.taobao.org/sax/download/sax-0.5.8.tgz",
+          "integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE="
+        }
+      }
+    },
     "xregexp": {
       "version": "2.0.0",
       "resolved": "http://registry.npm.taobao.org/xregexp/download/xregexp-2.0.0.tgz",

+ 2 - 1
package.json

@@ -14,7 +14,8 @@
     "egg-view-ejs": "^1.1.0",
     "gt3-sdk": "^2.0.0",
     "moment": "^2.18.1",
-    "ueditor": "^1.2.3"
+    "ueditor": "^1.2.3",
+    "xmlreader": "^0.2.3"
   },
   "devDependencies": {
     "autod": "^2.9.0",