Просмотр исходного кода

feat: NC-45: 结算后台登陆增加邮箱验证码.

kevin 1 год назад
Родитель
Сommit
19c9b337bc

+ 65 - 2
backendApi/config/params.php

@@ -1,6 +1,69 @@
 <?php
 return [
     'adminEmail' => 'admin@example.com',
-    'noCheckTokenActions' => ['v1/oauth/login', 'v1/oauth/no-login-modify-password', 'v1/oauth/refresh-access-token', 'v1/oauth/refresh-refresh-token', 'v1/oauth/refresh-token', 'v1/site/days-diff', 'v1/site/page-data', 'v1/site/captcha', 'v1/site/send-notice', 'v1/shop/order-period-adjust-batch'],
-    'noCheckPermissionActions' => ['oauth/login', 'oauth/no-login-modify-password', 'oauth/refresh-access-token', 'oauth/refresh-refresh-token', 'oauth/refresh-token', 'oauth/info', 'site/base-info', 'site/days-diff', 'site/page-data', 'site/captcha', 'site/send-notice', 'user/full-info', 'user/filter-user', 'user/generate-user-name', 'reconsume/cant-deduct-date', 'user/get-sub-com', 'user/chk-relation', 'user/get-period-num', 'user/company-bank-get', 'user/main-divide', 'user/chk-del-user', 'reconsume/deduct-audit-add', 'finance/perf-apply-get','file/upload-excel', 'user/move-net-type', 'user/move-get', 'user/reg-info-audit-get', 'user/status-audit-get', 'user/status-audit-get-statuses', 'user/close-login-get', 'user/close-dec-get', 'atlas/main-user-info', 'reconsume/change-audit-get', 'reconsume/cant-deduct-month', 'finance/change-balance-type', 'finance/balance-audit-get', 'file/token', 'finance/perf-audit-get', 'finance/invoice-audit-get', 'finance/withdraw-get', 'finance/deal-type-get', 'ad/upload', 'config/reg-type-get', 'config/pact-get', 'user/reg-info-audit-add-opt', 'reconsume/get-flow-deal-type', 'user/status-close-get', 'finance/mult-point', 'shop/order-period-adjust-batch'],
+    'noCheckTokenActions' => [
+        'v1/oauth/login',
+        'v1/oauth/no-login-modify-password',
+        'v1/oauth/refresh-access-token',
+        'v1/oauth/refresh-refresh-token',
+        'v1/oauth/refresh-token',
+        'v1/site/days-diff',
+        'v1/site/page-data',
+        'v1/site/captcha',
+        'v1/site/send-notice',
+        'v1/shop/order-period-adjust-batch',
+        'v1/oauth/send-email-code',
+    ],
+    'noCheckPermissionActions' => [
+        'oauth/login',
+        'oauth/no-login-modify-password',
+        'oauth/refresh-access-token',
+        'oauth/refresh-refresh-token',
+        'oauth/refresh-token',
+        'oauth/info',
+        'site/base-info',
+        'site/days-diff',
+        'site/page-data',
+        'site/captcha',
+        'site/send-notice',
+        'user/full-info',
+        'user/filter-user',
+        'user/generate-user-name',
+        'reconsume/cant-deduct-date',
+        'user/get-sub-com',
+        'user/chk-relation',
+        'user/get-period-num',
+        'user/company-bank-get',
+        'user/main-divide',
+        'user/chk-del-user',
+        'reconsume/deduct-audit-add',
+        'finance/perf-apply-get',
+        'file/upload-excel',
+        'user/move-net-type',
+        'user/move-get',
+        'user/reg-info-audit-get',
+        'user/status-audit-get',
+        'user/status-audit-get-statuses',
+        'user/close-login-get',
+        'user/close-dec-get',
+        'atlas/main-user-info',
+        'reconsume/change-audit-get',
+        'reconsume/cant-deduct-month',
+        'finance/change-balance-type',
+        'finance/balance-audit-get',
+        'file/token',
+        'finance/perf-audit-get',
+        'finance/invoice-audit-get',
+        'finance/withdraw-get',
+        'finance/deal-type-get',
+        'ad/upload',
+        'config/reg-type-get',
+        'config/pact-get',
+        'user/reg-info-audit-add-opt',
+        'reconsume/get-flow-deal-type',
+        'user/status-close-get',
+        'finance/mult-point',
+        'shop/order-period-adjust-batch',
+        'oauth/send-email-code',
+    ],
 ];

+ 1 - 1
backendApi/config/urlManagerRules.php

@@ -232,10 +232,10 @@ return [
             'GET index' => 'index',
             'GET view' => 'view',
             'GET info' => 'info',
-            'GET test' => 'test',
             'GET refresh-token' => 'refresh-token',
             'GET refresh-access-token' => 'refresh-access-token',
             'GET refresh-refresh-token' => 'refresh-refresh-token',
+            'GET send-email-code' => 'send-email-code',
         ],
     ],
     [

+ 43 - 0
backendApi/modules/v1/components/UserAuth.php

@@ -10,9 +10,11 @@ namespace backendApi\modules\v1\components;
 use backendApi\modules\v1\models\Admin;
 use backendApi\modules\v1\models\AdminRole;
 use backendApi\modules\v1\models\AdminToken;
+use backendApi\modules\v1\models\EmailLog;
 use common\components\Request;
 use common\helpers\Cache;
 use common\helpers\Date;
+use common\helpers\Email;
 use common\helpers\Form;
 use common\helpers\Log;
 use common\helpers\Tool;
@@ -402,4 +404,45 @@ class UserAuth extends User
         }
         return $result;
     }
+
+    public static function sendEmailCode($adminName): array
+    {
+        // 管理员
+        $admin = Admin::findOneAsArray('ADMIN_NAME=:ADMIN_NAME', [':ADMIN_NAME' => $adminName]);
+        if (!$admin) {
+            return [
+                'code' => 500,
+                'errDes' => '管理员账号错误',
+            ];
+        }
+        if (!$admin['email'] || !filter_var($admin['email'], FILTER_VALIDATE_EMAIL)) {
+            return [
+                'code' => 500,
+                'errDes' => '管理员未绑定邮箱, 或邮箱无效',
+            ];
+        }
+
+        // 发送验证码
+        $result = Email::sendAdminLoginCode($admin['email']);
+        if (!$result) {
+            return [
+                'code' => 500,
+                'errDes' => '验证码发送失败',
+            ];
+        }
+
+        // 验证码入库
+        $emailLogObj = new EmailLog();
+        $emailLogObj->ADMIN_ID = $admin['ID'];
+        $emailLogObj->EMAIL = $admin['email'];
+        $emailLogObj->CODE = $result['CODE'];
+        $emailLogObj->CREATED_AT = time();
+        $emailLogObj->UPDATED_AT = time();
+        $emailLogObj->save();
+
+        return [
+            'code' => 200,
+            'email' => sprintf('验证码已发送, 请检查邮箱%s', Tool::hideEmail($admin['email'])),
+        ];
+    }
 }

+ 11 - 2
backendApi/modules/v1/controllers/OauthController.php

@@ -7,6 +7,7 @@
  */
 namespace backendApi\modules\v1\controllers;
 
+use backendApi\modules\v1\components\UserAuth;
 use backendApi\modules\v1\models\AdminForm;
 use backendApi\modules\v1\models\LoginForm;
 use backendApi\modules\v1\models\User;
@@ -127,7 +128,15 @@ class OauthController extends BaseController
         }
     }
 
-    public function actionTest(){
-        return static::notice(['test'=>Yii::$app->getUser()->id]);
+    public function actionSendEmailCode()
+    {
+        $adminName = Yii::$app->request->get('adminName');
+        // 发送邮箱验证码
+        $result = UserAuth::sendEmailCode($adminName);
+        if ($result['code'] == 200) {
+            return static::notice(sprintf('验证码已发送到邮箱 %s, 有效期%d分钟. ', $result['email'], 5));
+        } else {
+            return static::notice(sprintf('验证码发送失败. %s', $result['errDes']), 401);
+        }
     }
 }

+ 3 - 0
backendApi/modules/v1/models/Admin.php

@@ -24,6 +24,7 @@ use common\libs\logging\operate\valueType\Config as ValueTypeConfig;
  * @property string $LAST_LOGIN_IP 上次登录IP
  * @property int $LAST_LOGIN_AT 上次登录时间
  * @property string $BIND_IP 绑定IP
+ * @property string EMAIL 邮箱
  * @property string $CREATE_ADMIN 创建管理员
  * @property string $UPDATE_ADMIN 更新管理员
  * @property int $CREATED_AT 创建时间
@@ -81,6 +82,7 @@ class Admin extends ActiveRecord
             'LAST_LOGIN_IP' => '上次登录IP',
             'LAST_LOGIN_AT' => '上次登录时间',
             'BIND_IP' => '绑定IP',
+            'EMAIL' => '邮箱',
             'CREATE_ADMIN' => '创建管理员',
             'UPDATE_ADMIN' => '更新管理员',
             'CREATED_AT' => '创建时间',
@@ -187,6 +189,7 @@ class Admin extends ActiveRecord
                 'type' => ValueTypeConfig::YES_NO_TYPE,
             ],
             'BIND_IP' => '绑定IP',
+            'EMAIL' => '邮箱',
             'CREATE_ADMIN' => [
                 'label' => '创建人',
                 'type' => function($data){

+ 8 - 3
backendApi/modules/v1/models/AdminForm.php

@@ -17,6 +17,7 @@ class AdminForm extends Model
     public $isEnable;
     public $isModifyPassword;
     public $bindIp;
+    public $email;
     public $oldPassword;
     public $password;
     public $surePassword;
@@ -35,12 +36,13 @@ class AdminForm extends Model
     public function rules()
     {
         return [
-            [['id', 'adminName', 'realName', 'oldPassword', 'password', 'surePassword', 'roleId', 'remark', 'isEnable', 'isModifyPassword', 'bindIp'], 'trim'],
+            [['id', 'adminName', 'realName', 'oldPassword', 'password', 'surePassword', 'roleId', 'remark', 'isEnable', 'isModifyPassword', 'bindIp', 'email'], 'trim'],
             [['id', 'adminName', 'realName', 'roleId'], 'required'],
             [['password', 'surePassword'], 'required', 'on'=>['add', 'changePassword', 'noLoginModifyPassword']],
             [['adminName', 'oldPassword'], 'required', 'on'=>['noLoginModifyPassword']],
             [['adminName'], 'unique', 'targetClass'=>Admin::class, 'targetAttribute'=>'ADMIN_NAME', 'on'=>['add']],
             ['surePassword', 'compare', 'compareAttribute'=>'password', 'message' => '两次密码必须一致'],
+            [['email', 'email']]
         ];
     }
 
@@ -52,8 +54,8 @@ class AdminForm extends Model
     {
         $parentScenarios =  parent::scenarios();
         $customScenarios = [
-            'add' => ['adminName', 'realName', 'password', 'surePassword', 'roleId', 'remark', 'isEnable', 'isModifyPassword', 'bindIp'],
-            'edit' => ['id', 'password', 'surePassword', 'roleId', 'realName', 'remark', 'isEnable', 'isModifyPassword', 'bindIp'],
+            'add' => ['adminName', 'realName', 'password', 'surePassword', 'roleId', 'remark', 'isEnable', 'isModifyPassword', 'bindIp', 'email'],
+            'edit' => ['id', 'password', 'surePassword', 'roleId', 'realName', 'remark', 'isEnable', 'isModifyPassword', 'bindIp', 'email'],
             'changePassword' => ['password', 'surePassword'],
             'noLoginModifyPassword' => ['adminName', 'oldPassword', 'password', 'surePassword'],
         ];
@@ -70,6 +72,7 @@ class AdminForm extends Model
             'isEnable' => '是否启用',
             'isModifyPassword' => '是否修改密码',
             'bindIp' => '绑定IP',
+            'email' => '邮箱',
             'oldPassword' => '原密码',
             'password' => '密码',
             'surePassword' => '确认密码',
@@ -96,6 +99,7 @@ class AdminForm extends Model
             $model->IS_ENABLE = $this->isEnable ? $this->isEnable : 0;
             $model->IS_MODIFY_PASSWORD = $this->isModifyPassword ? $this->isModifyPassword : 0;
             $model->BIND_IP = $this->bindIp;
+            $model->EMAIL = $this->email;
             $model->CREATE_ADMIN = \Yii::$app->user->id;
             $model->CREATED_AT = Date::nowTime();
         } elseif($this->scenario == 'edit') {
@@ -107,6 +111,7 @@ class AdminForm extends Model
             $model->IS_ENABLE = $this->isEnable ? $this->isEnable : 0;
             $model->IS_MODIFY_PASSWORD = $this->isModifyPassword ? $this->isModifyPassword : 0;
             $model->BIND_IP = $this->bindIp;
+            $model->EMAIL = $this->email;
             $model->UPDATE_ADMIN = \Yii::$app->user->id;
             $model->UPDATED_AT = Date::nowTime();
         } elseif($this->scenario == 'changePassword') {

+ 54 - 0
backendApi/modules/v1/models/EmailLog.php

@@ -0,0 +1,54 @@
+<?php
+
+namespace backendApi\modules\v1\models;
+
+use Yii;
+
+/**
+ * This is the model class for table "{{%EMAIL_LOG}}".
+ *
+ * @property string ID
+ * @property string ADMIN_ID 管理员ID
+ * @property string EMAIL 邮箱
+ * @property string CODE 验证码
+ * @property int CREATED_AT 创建时间
+ * @property int UPDATED_AT 更新时间
+ */
+class EmailLog extends \common\components\ActiveRecord
+{
+    /**
+     * @inheritdoc
+     */
+    public static function tableName()
+    {
+        return '{{%EMAIL_LOG}}';
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function rules()
+    {
+        return [
+            [['ADMIN_ID', 'EMAIL', 'CREATED_AT'], 'required'],
+            [['CREATED_AT', 'UPDATED_AT'], 'integer'],
+            [['ID', 'ADMIN_ID'], 'string', 'max' => 32],
+            [['ID'], 'unique'],
+        ];
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function attributeLabels()
+    {
+        return [
+            'ID' => 'ID',
+            'ADMIN_ID' => '管理员ID',
+            'EMAIL' => '邮箱',
+            'CODE' => '验证码',
+            'CREATED_AT' => '创建时间',
+            'UPDATED_AT' => '更新时间',
+        ];
+    }
+}

+ 8 - 1
backendApi/modules/v1/models/LoginForm.php

@@ -16,6 +16,7 @@ class LoginForm extends Model {
     public $adminName;
     public $password;
     public $verifyCode;
+    public $code;
     private $_user;
 
     const ERROR_IS_MODIFY_PASSWORD = 'ERROR_IS_MODIFY_PASSWORD';
@@ -37,7 +38,7 @@ class LoginForm extends Model {
     public function rules() {
         return [
             // username and password are both required
-            [['adminName', 'password', 'verifyCode'], 'required'],
+            [['adminName', 'password', 'verifyCode', 'code'], 'required'],
             // rememberMe must be a boolean value
             ['verifyCode', 'captcha', 'captchaAction'=>'/v1/site/captcha'],
             // password is validated by validatePassword()
@@ -114,6 +115,12 @@ class LoginForm extends Model {
             if(!$this->_user){
                 throw new Exception('账号不存在');
             }
+            // 校验邮箱验证码
+            $codeObj = EmailLog::findOneAsArray('ADMIN_ID=:ADMIN_ID AND EMAIL=:EMAIL', [':ADMIN_ID' => $this->_user['ID'], ':EMAIL' => $this->_user['EMAIL']]);
+            if (!$codeObj || !$codeObj['CODE'] || $codeObj['CODE'] != $this->code) {
+                throw new Exception('邮箱验证码不正确,无法登录');
+            }
+
             if(!$this->_user['IS_ENABLE']){
                 $this->_updateFailTimes($transaction,'账号已经被锁定,无法登录');
                 throw new Exception('账号已经被锁定,无法登录');

+ 12 - 0
backendEle/src/utils/network.js

@@ -91,6 +91,18 @@ const network = {
     })
     return promise
   },
+  sendCode(adminName) {
+    axiosObj.post(`oauth/send-email-code?adminName=${adminName}`, {})
+      .then(response => {
+      // 成功
+      resolve(response)
+      console.log('send-email-code', response)
+    }).catch(error => {
+      reject(error)
+    })
+
+    return promise
+  },
   getUserInfo() {
     let promise = new Promise((resolve, reject) => {
       updateToken(function (accessToken) {

+ 4 - 0
backendEle/src/views/admin/edit.vue

@@ -28,6 +28,9 @@
           </el-input>
           <el-tag type="info">一行一个IP;可指定ip段,如192.168.0.1-255</el-tag>
         </el-form-item>
+        <el-form-item label="" v-show="isAddOrEdit">
+          <el-input v-model="form.email"></el-input>
+        </el-form-item>
         <el-form-item label="备注" v-show="isAddOrEdit">
           <el-input v-model="form.remark"></el-input>
         </el-form-item>
@@ -79,6 +82,7 @@ export default {
         isEnable: true,
         isModifyPassword: false,
         bindIp: '',
+        email: '',
         roleId: null,
         password: null,
         surePassword: null

+ 5 - 0
backendEle/src/views/admin/index.vue

@@ -52,6 +52,11 @@
             {{scope.row.BIND_IP}}
           </template>
         </el-table-column>
+        <el-table-column label="" width="250">
+          <template slot-scope="scope">
+            {{scope.row.EMAIL}}
+          </template>
+        </el-table-column>
         <el-table-column label="创建人" width="150">
           <template slot-scope="scope">
             {{scope.row.CREATE_ADMIN_NAME}}

+ 10 - 0
backendEle/src/views/login/index.vue

@@ -16,6 +16,13 @@
               <template slot="prepend"><i class="el-icon-lock"></i></template>
             </el-input>
           </el-form-item>
+
+          <el-form-item label-width="0px">
+            <el-input v-model="loginForm.code">
+              <template slot="append" @click="sendCode">发送验证码</template>
+            </el-input>
+          </el-form-item>
+
           <el-form-item label-width="0px">
             <el-input type="verifyCode" v-model="loginForm.verifyCode" auto-complete="off" @keyup.enter.native="onSubmit">
               <template slot="prepend"><i class="el-icon-camera"></i></template>
@@ -112,6 +119,9 @@ export default {
     changeCaptcha() {
       this.captchaUrl = REQUEST_URL + 'site/captcha?page_id=' + this.pageId + '&v=' + Math.random();
     },
+    sendCode() {
+      return network.sendCode(this.loginForm.adminName)
+    },
   }
 }
 </script>

+ 14 - 0
common/config/main.php

@@ -56,6 +56,20 @@ return [
             // 'useFileTransport' to false and configure a transport
             // for the mailer to send real emails.
             'useFileTransport' => true,
+            'transport' => [
+                'class' => 'Swift_SmtpTransport',
+                'host' => 'mail.authsmtp.com',
+                'port' => 325,
+                'username' => 'ac71555',
+                'password' => 'I1j-MKi-fJ8-nHb',
+                'encryption' => 'TLS',
+
+//                'host' => 'smtp.163.com',
+//                'port' => 465,
+//                'username' => '18511880790@163.com',
+//                'password' => 'YOUSSWQHISDSWPVB',
+//                'encryption' => 'SSL',
+            ],
         ],
         'swooleAsyncTimer' => [
             'class' => 'common\components\SwooleAsyncTimer',

+ 2 - 2
common/config/params.php

@@ -2,8 +2,8 @@
 $nationParams = require_once __DIR__ . '/params-nation.php';
 return [
     'preparePerfLimit' => isset($mainConfig['preparePerfLimit']) ? $mainConfig['preparePerfLimit'] : false,
-    'adminEmail' => 'admin@example.com',
-    'supportEmail' => 'support@example.com',
+    'adminEmail' => 'eshop-noreply@elken.com',
+    'supportEmail' => 'eshop-noreply@elken.com',
     'nation' => $nationParams,
     'backAccessTokenExpiresIn' => 3000 * 60,
     'backRefreshTokenExpiresIn' => 3000 * 60 * 60,

+ 28 - 0
common/helpers/Email.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace common\helpers;
+
+use Yii;
+
+class Email
+{
+    /**
+     * @param string $email
+     */
+    public static function sendAdminLoginCode(string $email)
+    {
+        $subject = 'Elken Login Code';
+        $code = Tool::randomString(6);
+        $message = sprintf('验证码:%s. 有效时长5分钟.', $code);
+
+        $mailer = Yii::$app->mailer;
+        $mailer->useFileTransport = false;
+        return $mailer->compose()
+//        ->setFrom('eshop-noreply@elken.com')
+            ->setFrom(Yii::$app->params['adminEmail'])
+            ->setTo($email)
+            ->setSubject($subject)
+            ->setHtmlBody($message)
+            ->send();
+    }
+}

+ 13 - 0
common/helpers/Tool.php

@@ -254,6 +254,19 @@ class Tool {
         }
     }
 
+    /**
+     * 隐藏邮箱中间部分.
+     * @param $email
+     * @return string
+     */
+    public static function hideEmail($email): string
+    {
+        $emailExp = explode('@', $email);
+        $mask = substr_replace($emailExp[0], str_repeat('*', strlen($emailExp[0]) - 2), 1, strlen($emailExp[0]) ? strlen($emailExp[0]) : strlen($emailExp[0]) - 2);
+
+        return $mask . '@' . $emailExp[1];
+    }
+
     /**
      * 清空目录下的所有文件
      * @param $dir