在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
概要最近使用 antd pro 开发项目时遇到个新的需求, 就是在登录界面通过短信验证码来登录, 不使用之前的用户名密码之类登录方式. 这种方式虽然增加了额外的短信费用, 但是对于安全性确实提高了不少. antd 中并没有自带能够倒计时的按钮, 整体流程通过短信验证码登录的流程很简单:
前端页面代码import React, { useState } from 'react'; import { connect } from 'umi'; import { message } from 'antd'; import ProForm, { ProFormText, ProFormCaptcha } from '@ant-design/pro-form'; import { MobileTwoTone, MailTwoTone } from '@ant-design/icons'; import { sendSmsCode } from '@/services/login'; const Login = (props) => { const [countDown, handleCountDown] = useState(5); const { dispatch } = props; const [form] = ProForm.useForm(); return ( <div style={{ width: 330, margin: 'auto', }} > <ProForm form={form} submitter={{ searchConfig: { submitText: '登录', }, render: (_, dom) => dom.pop(), submitButtonProps: { size: 'large', style: { width: '100%', }, }, onSubmit: async () => { const fieldsValue = await form.validateFields(); console.log(fieldsValue); await dispatch({ type: 'login/login', payload: { username: fieldsValue.mobile, sms_code: fieldsValue.code }, }); }, }} > <ProFormText fieldProps={{ size: 'large', prefix: <MobileTwoTone />, }} name="mobile" placeholder="请输入手机号" rules={[ { required: true, message: '请输入手机号', }, { pattern: new RegExp(/^1[3-9]\d{9}$/, 'g'), message: '手机号格式不正确', }, ]} /> <ProFormCaptcha fieldProps={{ size: 'large', prefix: <MailTwoTone />, }} countDown={countDown} captchaProps={{ size: 'large', }} name="code" rules={[ { required: true, message: '请输入验证码!', }, ]} placeholder="请输入验证码" onGetCaptcha={async (mobile) => { if (!form.getFieldValue('mobile')) { message.error('请先输入手机号'); return; } let m = form.getFieldsError(['mobile']); if (m[0].errors.length > 0) { message.error(m[0].errors[0]); return; } let response = await sendSmsCode(mobile); if (response.code === 10000) message.success('验证码发送成功!'); else message.error(response.message); }} /> </ProForm> </div> ); }; export default connect()(Login); 请求验证码和登录的 service (src/services/login.js)import request from '@/utils/request'; export async function login(params) { return request('/api/v1/login', { method: 'POST', data: params, }); } export async function sendSmsCode(mobile) { return request(`/api/v1/send/smscode/${mobile}`, { method: 'GET', }); } 处理登录的 model (src/models/login.js)import { stringify } from 'querystring'; import { history } from 'umi'; import { login } from '@/services/login'; import { getPageQuery } from '@/utils/utils'; import { message } from 'antd'; import md5 from 'md5'; const Model = { namespace: 'login', status: '', loginType: '', state: { token: '', }, effects: { *login({ payload }, { call, put }) { payload.client = 'admin'; // payload.password = md5(payload.password); const response = yield call(login, payload); if (response.code !== 10000) { message.error(response.message); return; } // set token to local storage if (window.localStorage) { window.localStorage.setItem('jwt-token', response.data.token); } yield put({ type: 'changeLoginStatus', payload: { data: response.data, status: response.status, loginType: response.loginType }, }); // Login successfully const urlParams = new URL(window.location.href); const params = getPageQuery(); let { redirect } = params; console.log(redirect); if (redirect) { const redirectUrlParams = new URL(redirect); if (redirectUrlParams.origin === urlParams.origin) { redirect = redirect.substr(urlParams.origin.length); if (redirect.match(/^\/.*#/)) { redirect = redirect.substr(redirect.indexOf('#') + 1); } } else { window.location.href = '/home'; } } history.replace(redirect || '/home'); }, logout() { const { redirect } = getPageQuery(); // Note: There may be security issues, please note window.localStorage.removeItem('jwt-token'); if (window.location.pathname !== '/user/login' && !redirect) { history.replace({ pathname: '/user/login', search: stringify({ redirect: window.location.href, }), }); } }, }, reducers: { changeLoginStatus(state, { payload }) { return { ...state, token: payload.data.token, status: payload.status, loginType: payload.loginType, }; }, }, }; export default Model; 后端后端主要就 2 个接口, 一个处理短信验证码的发送, 一个处理登录验证 路由的代码片段: apiV1.POST("/login", authMiddleware.LoginHandler) apiV1.GET("/send/smscode/:mobile", controller.SendSmsCode) 短信验证码的处理
以下代码生成 6 位的数字, 随机数不足 6 位前面补 0 r := rand.New(rand.NewSource(time.Now().UnixNano())) code := fmt.Sprintf("%06v", r.Int31n(1000000)) 调用短信接口 这个简单, 根据购买的短信接口的说明调用即可 保存已经验证码, 以备验证用 这里需要注意的是验证码要有个过期时间, 不能一个验证码一直可用. package util import ( "fmt" "math/rand" "sync" "time" ) type loginItem struct { smsCode string smsCodeExpire int64 } type LoginMap struct { m map[string]*loginItem l sync.Mutex } var lm *LoginMap func InitLoginMap(resetTime int64, loginTryMax int) { lm = &LoginMap{ m: make(map[string]*loginItem), } } func GenSmsCode(key string) string { r := rand.New(rand.NewSource(time.Now().UnixNano())) code := fmt.Sprintf("%06v", r.Int31n(1000000)) if _, ok := lm.m[key]; !ok { lm.m[key] = &loginItem{} } v := lm.m[key] v.smsCode = code v.smsCodeExpire = time.Now().Unix() + 600 // 验证码10分钟过期 return code } func CheckSmsCode(key, code string) error { if _, ok := lm.m[key]; !ok { return fmt.Errorf("验证码未发送") } v := lm.m[key] // 验证码是否过期 if time.Now().Unix() > v.smsCodeExpire { return fmt.Errorf("验证码(%s)已经过期", code) } // 验证码是否正确 if code != v.smsCode { return fmt.Errorf("验证码(%s)不正确", code) } return nil } 登录验证登录验证的代码比较简单, 就是先调用上面的 CheckSmsCode 方法验证是否合法. FAQantd 版本问题使用 antd pro 的 ProForm 要使用 antd 的最新版本, 最好 >= v4.8, 否则前端组件会有不兼容的错误. 可以优化的点上面实现的比较粗糙, 还有以下方面可以继续优化: 验证码需要控制频繁发送, 毕竟发送短信需要费用验证码直接在内存中, 系统重启后会丢失, 可以考虑放在 redis 之类的存储中 到此这篇关于基于 antd pro 的短信验证码登录功能(流程分析)的文章就介绍到这了,更多相关antd pro 验证码登录内容请搜索极客世界以前的文章或继续浏览下面的相关文章希望大家以后多多支持极客世界! |
请发表评论