短信验证码实现逻辑
最终要变成这样三步:
- 前端加载注册页时: 请求
GET /users/image_codes/<uuid>/拿到图片验证码
→ 你这一步已经实现 - 用户输入图形验证码 & 点击「获取短信验证码」按钮:
前端发请求到GET /sms_codes/<mobile>/?image_code=xxx&image_code_id=uuid
后端做:- 校验图形验证码(从 Redis 取
img_<uuid>) - 限制 60 秒内同一手机号只能发一次
- 生成 4 位短信验证码,写入 Redis
sms_<mobile> - 调用 Celery 异步任务发短信
- 校验图形验证码(从 Redis 取
- 用户填完表单点..击「注册」:
后端RegisterView.post- 不再校验图片验证码
- 改为去 Redis 拿
sms_<mobile>,对比前端传来的sms_code - 正确就创建用户
云通讯短信
注册容联云:https://www.yuntongxun.com/
进入免费测试指南:https://doc.yuntongxun.com/p/5a531a353b8496dd00dcdfe2
按照文档:绑定测试账号->寻找 python SDK

Python SDK 文档:https://doc.yuntongxun.com/p/5f029ae7a80948a1006e776e
安装SDK
pip install ronglian_sms_sdk
见最下面的调用示例(见文档即可):
from ronglian_sms_sdk import SmsSDK
accId = '容联云通讯分配的主账号ID'
accToken = '容联云通讯分配的主账号TOKEN'
appId = '容联云通讯分配的应用ID'
def send_message():
sdk = SmsSDK(accId, accToken, appId)
# 模板ID,测试时填“1”
tid = '容联云通讯创建的模板ID'
mobile = '手机号1,手机号2'
# 短信中的变量
datas = ('变量1', '变量2')
resp = sdk.sendMessage(tid, mobile, datas)
print(resp)
# 调用
if __name__ == "__main__":
send_message()
| 参数名 | 类型 | 说明 |
|---|---|---|
| accId | String | 开发者主账号(ACCOUNT SID),登录云通讯网站后可在控制台首页看到 |
| accToken | String | 主账号令牌(AUTH TOKEN),登录云通讯网站后可在控制台首页看到 |
| appId | String | 应用 ID(APPID),请使用管理后台已创建应用的 APPID |
在控制台自行查看上述参数:https://console.yuntongxun.com/member/main

免费开发测试需注意
1.免费开发测试需要使用到"控制台首页",开发者主账户相关信息,如主账号、应用ID等。
2.免费开发测试使用的模板ID为1,具体内容:【云通讯】您的验证码是{1},请于{2}分钟内正确输入。其中{1}和{2}为短信模板参数。
3.测试成功后,即可申请短信模板并 正式使用 。
执行测试



云通信后端逻辑
第一步:在 settings.py 放短信的配置(避免硬编码)
libs/ 专门放“工具类代码”,不要不要硬编码云通讯账号信息 → 而是放到 settings.py(未来上线时你可以把这些放到环境变量,提高安全性。)
meiduo_mall/settings.py 里新增
# 容联云通讯短信配置
RONG_LIAN = {
"accId": "你的accId",
"accToken": "你的accToken",
"appId": "你的appId",
}
第二步:封装短信发送类(libs/sms/ronglian.py)
封装一个短信发送类 libs/sms/ronglian.py(sms 是新建的 python 软件包)
# libs/sms/ronglian.py
import json
from ronglian_sms_sdk import SmsSDK
from django.conf import settings
import logging
logger = logging.getLogger("meiduo")
class RongLianSMS:
"""容联云通讯短信发送封装"""
def __init__(self):
cfg = settings.RONG_LIAN
self.sdk = SmsSDK(cfg["accId"], cfg["accToken"], cfg["appId"])
def send(self, mobile, template_id, datas):
"""
datas: ('验证码', '有效期')
"""
try:
resp_str = self.sdk.sendMessage(template_id, mobile, datas)
# SDK 返回的是字符串,需要手动 loads
resp = json.loads(resp_str)
logger.info(f"短信发送 → 手机号={mobile}, 返回={resp}")
# 判断发送是否成功
if resp.get("statusCode") == "000000":
return True
else:
logger.error(f"短信发送失败 → {resp}")
return False
except Exception as e:
logger.error("短信 SDK 调用异常", exc_info=True)
return False
第三步:Celery 异步发短信任务
首先安装 celery:
pip install celery
根目录创建celery_tasks软件包 以及celery_tasks下的 sms python 软件包
目录结构:
meiduo_mall/
├── __init__.py
├── celeryconfig.py -> 配置文件
├── main.py -> 应用入口(创建 app 对象)
└── sms
├── __init__.py
└── tasks.py -> 任务函数所在(业务逻辑)
celery_tasks/main.py
# celery_tasks/main.py
import os
from celery import Celery
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "meiduo_mall.settings")
# 创建 Celery 应用
app = Celery("meiduo")
# 直接加载独立 Celery 配置
app.config_from_object("celery_tasks.celeryconfig")
# 明确告诉 Celery 去扫描 celery_tasks/sms/tasks.py
app.autodiscover_tasks([
"celery_tasks.sms",
])
celery_tasks/celeryconfig.py
## 在生产环境,celeryconfig 通常不是 python 文件,而是环境变量控制:
# broker_url = os.getenv("CELERY_BROKER_URL")
# result_backend = os.getenv("CELERY_RESULT_BACKEND")
# timezone = "Asia/Shanghai"
# 环境变量控制
# export CELERY_BROKER_URL=redis://10.0.0.1:6379/5
# export CELERY_RESULT_BACKEND=redis://10.0.0.1:6379/6
# =========== Celery 配置 ===========
# 使用 Redis 作为 Celery broker(任务调度队列)
broker_url = "redis://127.0.0.1:6379/5"
# Celery worker 执行结果存储(可不需要,但建议加上)
result_backend = "redis://127.0.0.1:6379/6"
# 时区与 Django 保持一致
timezone = "Asia/Shanghai"
task_serializer = "json"
result_serializer = "json"
accept_content = ["json"]
celery_tasks/sms/tasks.py
# celery_tasks/sms/tasks.py
from celery_tasks.main import app
from libs.sms.ronglian import RongLianSMS
import logging
# 使用 Celery 的 logger(而不是 print)
# 好处:日志等级可控、可写入日志文件、不会和多进程输出乱序
logger = logging.getLogger("celery")
@app.task(
bind=True, # bind=True:使第一个参数是 task 对象本身(self),好用于 retry()
default_retry_delay=5 # 任务失败后默认等待 5 秒再重试
)
def send_sms_code(self, mobile, sms_code):
"""
异步发送短信验证码任务(Celery Worker 执行)
:param mobile: 手机号
:param sms_code: 验证码内容
"""
# 记录任务开始日志(方便排查)
logger.info(f"开始向 {mobile} 发送短信验证码 {sms_code}")
try:
# 调用容联云短信 SDK 发送短信
# template_id = "1" 表示你在容联云平台配置的模板编号
# datas = (短信验证码, 有效期)
ok = RongLianSMS().send(
mobile,
"1",
(sms_code, "5")
)
# 打印容联云返回结果 True / False
logger.info(f"容联云返回: ok={ok}")
# 如果 SDK 返回失败,主动抛异常触发 retry()
if not ok:
raise Exception("短信发送失败")
except Exception as e:
# 记录错误日志(包括容联云 API 错误、网络异常等)
logger.error(f"短信发送失败: {e}")
# Celery 自动重试(最多重试 10 次)
# retry() 会再次把任务丢回队列,不会阻塞当前 worker
raise self.retry(exc=e, max_retries=10)
在 celery_tasks/init.py 中显式导入 task 模块
from .main import app
import celery_tasks.sms.tasks
启动 Celery Worker
进入项目根目录,启动 worker:
celery -A celery_tasks.main worker -l info
你应该看到如下日志:

celery 异步执行原理讲解:
1、Django 用户触发发送验证码接口
例如 users/sms_codes/<mobile>/
后端代码:
send_sms_code.delay(mobile, sms_code)
把任务推到 Redis(broker)里排队,不在主进程里执行,几毫秒就返回给前端(不会卡主)
2、Redis Broker(队列)存储任务
Celery 会把任务序列化成 JSON:
{
"id": "uuid",
"task": "celery_tasks.sms.tasks.send_sms_code",
"args": ["手机号", "验证码"],
"kwargs": {}
}
Celery Worker 后台执行真正的发送逻辑
Worker 启动命令:celery -A celery_tasks.main worker -l info
- Worker 会:
- 连接 Redis
- 监听是否有新任务
- 收到任务后执行 send_sms_code()
这时候才会调用:RongLianSMS().send(mobile, template_id, datas),真正运行云通讯 SDK 发送短信。

第四步:在 users/views.py 写“短信验证码接口”
把它放在 ImageCodeView 下面即可
# ============================================================
# 短信验证码
# ============================================================
from django.views import View
from django import http
from django_redis import get_redis_connection
from random import randint
import logging
logger = logging.getLogger("django")
# 过期时间(你可以放 settings 也可以写在本文件顶部)
SMS_CODE_EXPIRE_TIME = 300 # 短信验证码 300 秒
SMS_FLAG_EXPIRE_TIME = 60 # 发送频率标记 60 秒
class SmsCodeView(View):
"""
发送短信验证码
URL: /sms_codes/<mobile>/?image_code=xxx&image_code_id=uuid
"""
def get(self, request, mobile):
# 1. 接收参数
image_code = request.GET.get("image_code")
image_code_id = request.GET.get("image_code_id")
if not all([mobile, image_code, image_code_id]):
return http.JsonResponse({"code": "4003", "errmsg": "缺少参数"})
# 2. 取 Redis 图片验证码
redis_conn = get_redis_connection("verify_codes")
redis_key = f"img_{image_code_id}"
real_image_code = redis_conn.get(redis_key)
if real_image_code is None:
return http.JsonResponse({"code": "4001", "errmsg": "验证码已过期"})
# 用完删除
redis_conn.delete(redis_key)
# 校验(忽略大小写)
if real_image_code.decode().lower() != image_code.lower():
return http.JsonResponse({"code": "4001", "errmsg": "验证码错误"})
# 3. 发送频率控制:60秒
send_flag = redis_conn.get(f"send_flag_{mobile}")
if send_flag:
return http.JsonResponse({"code": "4002", "errmsg": "请勿频繁发送短信"})
# 4. 生成短信验证码
sms_code = "%04d" % randint(0, 9999)
logger.info(f"{mobile} 的短信验证码是:{sms_code}")
# 5. Redis 管道一次性写入
pl = redis_conn.pipeline()
pl.setex(f"sms_{mobile}", SMS_CODE_EXPIRE_TIME, sms_code)
pl.setex(f"send_flag_{mobile}", SMS_FLAG_EXPIRE_TIME, 1)
pl.execute()
# 6. Celery 异步发送短信
from celery_tasks.sms.tasks import send_sms_code
send_sms_code.delay(mobile, sms_code)
# 7. 返回成功
return http.JsonResponse({"code": "0", "errmsg": "短信发送成功"})
第五步:注册时不再校验图片验证码,而是校验短信验证码
在 RegisterView.post 里删掉你之前的图片校验:

第六步:完善 url
apps/users/urls.py
from django.urls import path
from .views import SmsCodeView # 导入
urlpatterns = [
...
path('sms_codes/<mobile>/', SmsCodeView.as_view()), # 添加这行
]
第七步:可选临时检测方案->手机验证码是否输入正确判断
注册视图加入短信验证码校验
apps/users/views.py 修改 RegisterView.post():
from django_redis import get_redis_connection
class RegisterView(View):
def post(self, request):
...
# 获取用户填写的短信验证码
sms_code_client = request.POST.get("sms_code")
# 判断是否填写
if not sms_code_client:
return http.HttpResponseBadRequest("请填写短信验证码")
# 从 redis 读取
redis_conn = get_redis_connection("verify_codes")
sms_code_server = redis_conn.get(f"sms_{mobile}")
if sms_code_server is None:
return http.HttpResponseBadRequest("短信验证码已过期")
# 对比
if sms_code_client != sms_code_server.decode():
return http.HttpResponseBadRequest("短信验证码错误")
# 校验成功 → 删除 redis 验证码(防止重复使用)
redis_conn.delete(f"sms_{mobile}")
云通讯前端
static/js/register.js
// 点击按钮发送短信验证码
send_sms_code: function () {
if (this.sending_flag == true) return;
this.sending_flag = true;
// 1. 输入基本校验
this.check_mobile();
this.check_image_code();
if (this.error_mobile || this.error_image_code) {
this.sending_flag = false;
return;
}
// 2. 构造 URL
var url = this.host + '/sms_codes/' + this.mobile +
'/?image_code=' + this.image_code +
'&image_code_id=' + this.image_code_id;
console.log("短信请求URL:", url);
// 3. 发请求
axios.get(url, { responseType: 'json' })
.then(response => {
let code = response.data.code;
switch (code) {
case '0': // ======= 成功 =======
console.log("短信发送成功:", response.data);
// 刷新图形验证码(更安全)
this.generate_image_code();
// 开始倒计时
var num = 60;
this.sms_code_tip = num + '秒';
var timer = setInterval(() => {
num -= 1;
if (num <= 0) {
clearInterval(timer);
this.sms_code_tip = '获取短信验证码';
this.sending_flag = false;
} else {
this.sms_code_tip = num + '秒';
}
}, 1000);
break;
case '4001': // 图形验证码错误
case '4003': // 参数缺失
this.error_image_code_message = response.data.errmsg;
this.error_image_code = true;
this.generate_image_code();
this.sending_flag = false;
break;
case '4002': // 发送频率限制
this.error_sms_code_message = "发送过于频繁,请稍后再试";
this.error_sms_code = true;
this.sending_flag = false;
break;
default: // 其它后端异常
this.error_sms_code_message = response.data.errmsg || "短信发送失败";
this.error_sms_code = true;
this.sending_flag = false;
}
})
.catch(error => {
console.log("短信请求异常:", error);
this.error_sms_code_message = "网络异常,请稍后重试";
this.error_sms_code = true;
this.sending_flag = false;
// 强制刷新验证码防止漏洞
this.generate_image_code();
});
},
测试:



登录页面完善
基于上一章结尾登录页面继续完善登录功能
现在的问题:后端 LoginView 基本没问题,主要是前端模板和 login.js 没有对上号,导致 Vue 校验其实没生效。
一、后端 LoginView 建议小改动
登录页不是只有一种入口(主动访问 vs 被要求登录),next 可以让“被拦截的用户”回到原本的页面,不写 next → 用户体验很糟糕
加上 next 跳转逻辑,其他逻辑可以保留你现在的写法。
# apps/users/views.py 里替换 LoginView
class LoginView(View):
"""登录视图"""
def get(self, request):
return render(request, "users/login.html")
def post(self, request):
# 1. 获取参数
username = request.POST.get("username")
password = request.POST.get("password")
remembered = request.POST.get("remembered") # 是否记住登录
# 2. 必填检查
if not all([username, password]):
return http.HttpResponseBadRequest("缺少必要参数")
# 3. 用户名格式检查(先按课程来,只支持用户名登录)
if not re.match(r"^[a-zA-Z0-9_-]{5,20}$", username):
return http.HttpResponseBadRequest("用户名格式不正确")
# 4. 使用 Django 内置认证
user = authenticate(username=username, password=password)
if user is None:
# 认证失败,返回登录页并带错误信息
return render(request, "users/login.html", {"account_errmsg": "用户名或密码错误"})
# 5. 登录成功:写 session
login(request, user)
# 6. 设置 session 过期时间
if remembered == "on":
request.session.set_expiry(30 * 24 * 3600) # 30 天
else:
request.session.set_expiry(0) # 关闭浏览器失效
# 7. 处理 next 参数(从登录拦截回来时会带上)
next_url = request.GET.get("next")
if next_url:
resp = redirect(next_url)
else:
resp = redirect(reverse("contents:index"))
# 8. 设置 cookie,用于首页展示用户名
resp.set_cookie("username", user.username, max_age=14 * 24 * 3600)
return resp
原来只写死跳首页,现在加上 next 会更完整:
代码中:
next_url = request.GET.get("next")
if next_url:
resp = redirect(next_url)
else:
resp = redirect(reverse("contents:index"))
如果 URL 里带着 next 参数,比如:/login/?next=/user/,用户登录成功后应该回去 /user/,所以:resp = redirect(next_url)
如果用户是主动点击“登录”按钮进来的,而不是被拦截跳来的,这种情况 URL 就不会带 next
用户访问 /orders/ (需要登录)
↓
未登录 → 重定向到 /login/?next=/orders/
↓
用户输入用户名密码
↓
登录成功 → 发现 next 参数
↓
跳转回 /orders/
如果没有 next:
登录成功 → 回首页
二、login.html:让表单和 Vue 绑在一起
你现在的模板 没有 v-model / @submit,static/js/login.js 里的 Vue 根本没参与登录。
改成下面这样(可以整体替换 templates/users/login.html):
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>美多商城-登录</title>
<!-- 静态文件引用 -->
<link rel="stylesheet" href="{{ static('css/reset.css') }}">
<link rel="stylesheet" href="{{ static('css/main.css') }}">
<script src="{{ static('js/host.js') }}"></script>
<script src="{{ static('js/vue-2.5.16.js') }}"></script>
<script src="{{ static('js/axios-0.18.0.min.js') }}"></script>
</head>
<body>
<div id="app" v-cloak>
<div class="login_top clearfix">
<a href="/" class="login_logo">
<img src="{{ static('images/logo02.png') }}">
</a>
</div>
<div class="login_form_bg">
<div class="login_form_wrap clearfix">
<div class="login_banner fl"></div>
<div class="slogan fl">商品美 · 种类多 · 欢迎光临</div>
<div class="login_form fr">
<div class="login_title clearfix">
<a class="cur">账户登录</a>
</div>
<div class="form_con">
<div class="form_input cur">
<!-- 登录表单,注意:加了 @submit="on_submit($event)" -->
<form method="post"
action="/users/login/"
id="login-form"
@submit="on_submit($event)">
{{ csrf_field(request) | safe }}
<!-- 用户名 -->
<input type="text"
name="username"
class="name_input"
placeholder="请输入用户名"
v-model="username"
@blur="check_username">
<!-- 前端校验错误 -->
<div v-show="error_username"
class="user_error">
[[ error_username_message ]]
</div>
<!-- 密码 -->
<input type="password"
name="password"
class="pass_input"
placeholder="请输入密码"
v-model="password"
@blur="check_password">
<!-- 后端返回的账号错误(用户名或密码错误) -->
{% if account_errmsg %}
<div class="pwd_error" v-show="!error_password">
{{ account_errmsg }}
</div>
{% endif %}
<!-- 前端密码错误提示 -->
<div v-show="error_password"
class="pwd_error">
[[ error_password_message ]]
</div>
<div class="more_input clearfix">
<!-- 记住登录,勾选后提交值为 on -->
<input type="checkbox"
name="remembered"
v-model="remembered">
<label>记住登录</label>
</div>
<input type="submit" value="登 录" class="input_submit">
</form>
</div>
</div>
<div class="third_party">
<a href="javascript:;" class="qq_login" @click="qq_login">QQ</a>
<a href="javascript:;" class="weixin_login">微信</a>
<a href="/users/register/" class="register_btn">立即注册</a>
</div>
</div>
</div>
</div>
<div class="footer no-mp">
<div class="foot_link">
<a href="#">关于我们</a>
<span>|</span>
<a href="#">联系我们</a>
<span>|</span>
<a href="#">招聘人才</a>
<span>|</span>
<a href="#">友情链接</a>
</div>
<p>CopyRight © 2016 北京美多商业股份有限公司 All Rights Reserved</p>
<p>电话:010-****888 京ICP备*******8号</p>
</div>
</div>
<script src="{{ static('js/common.js') }}"></script>
<script src="{{ static('js/login.js') }}"></script>
</body>
</html>
关键点:
<div id="app" v-cloak>包住整个页面,Vue 接管这块 DOM。<form ... @submit="on_submit($event)">让表单提交走 Vue 的on_submit。- 用户名 / 密码 input 上加了
v-model和@blur,让 input 的值 ↔ Vue 的数据 自动双向同步以及input 失焦(blur)的时候 → 自动执行方法v-model="username"/v-model="password"@blur="check_username"/@blur="check_password"
- 错误提示用
v-show+[[ ... ]](和你 login.js 中的delimiters: ['[[', ']]']对应)。 remembered用v-model勾选,后端拿到的仍然是on/空,和你视图里的判断匹配。
三、static/js/login.js:修变量名、修方法名
现在的 login.js 有几个小坑:
- data 里是
error_password,方法里用的是this.error_pwd,对不上; - 方法名是
check_pwd,模板里用的是check_password; on_submit用window.event.returnValue = false,不太优雅,容易出锅。
把 static/js/login.js 改成下面这样
// static/js/login.js
var vm = new Vue({
el: '#app',
// 修改Vue变量的读取语法,避免和django模板语法冲突
delimiters: ['[[', ']]'],
data: {
host: typeof host !== 'undefined' ? host : '',
error_username: false,
error_password: false,
error_username_message: '请输入5-20个字符的用户名',
error_password_message: '请输入8-20位的密码',
username: '',
password: '',
remembered: true
},
methods: {
// 检查账号
check_username: function () {
var re = /^[a-zA-Z0-9_-]{5,20}$/;
if (re.test(this.username)) {
this.error_username = false;
} else {
this.error_username_message = '用户名必须是5-20位字母、数字、下划线或 -';
this.error_username = true;
}
},
// 检查密码
check_password: function () {
var re = /^[0-9A-Za-z]{8,20}$/;
if (re.test(this.password)) {
this.error_password = false;
} else {
this.error_password_message = '密码必须是8-20位字母或数字';
this.error_password = true;
}
},
// 表单提交
on_submit: function (event) {
// 执行前端校验
this.check_username();
this.check_password();
// 只要有一个错误,就阻止表单提交
if (this.error_username || this.error_password) {
event.preventDefault();
}
// 否则让表单正常提交,后端再做最终校验
},
// qq登录(暂时你后端没写可以先留着)
qq_login: function () {
var next = get_query_string('next') || '/';
var url = this.host + '/qq/login/?next=' + next;
axios.get(url, {
responseType: 'json'
})
.then(response => {
location.href = response.data.login_url;
})
.catch(error => {
console.log(error.response);
});
}
}
});
关键修复点 :
- 统一变量名:
error_password(data)- 模板:
v-show="error_password" - JS:
this.error_password = true/false
- 方法名统一:
- 方法:
check_password - 模板:
@blur="check_password"
- 方法:
on_submit正常接收event,通过event.preventDefault()阻止提交,而不是window.event。
其他完善以及自查:
/users/login/ 已在 apps/users/urls.py 注册:path('login/', LoginView.as_view(), name='login'),
可以调试并查看变量是否获取

用户名 / 手机号登录
第 1 部分:构建校验脚本
apps/users/utils.py
import re
from django.contrib.auth.backends import ModelBackend
from apps.users.models import User
"""
抽取:根据用户名或手机号获取 User 对象
"""
def get_user_by_username(username):
try:
# 手机号
if re.match(r'^1[3-9]\d{9}$', username):
user = User.objects.get(mobile=username)
else:
# 用户名
user = User.objects.get(username=username)
except User.DoesNotExist:
return None
return user
"""
自定义认证后端:支持 用户名/手机号 登录
继承 ModelBackend,否则 Django admin 无法使用多字段认证
"""
class UsernameMobileModelBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
"""
username: 用户输入的用户名或手机号
password: 密码
"""
# 1. 获取用户对象(可能是用户名,也可能是手机号)
user = get_user_by_username(username)
# 2. 校验密码
if user and user.check_password(password):
return user
return None
第 2 部分:settings.py 启用自定义认证后端
meiduo_mall/settings.py
AUTHENTICATION_BACKENDS = [
'apps.users.utils.UsernameMobileModelBackend',
]
第 3 部分:优化 LoginView
LoginView.post() 中
修改前
if not re.match(r"^[a-zA-Z0-9_-]{5,20}$", username):
return http.HttpResponseBadRequest("用户名格式不正确")
修改后(支持手机号)
# 用户名 或 手机号 格式校验
if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', username) and \
not re.match(r'^1[3-9]\d{9}$', username):
return http.HttpResponseBadRequest("请输入正确的用户名或手机号")
其他全部保持不变,因为认证后端已经替你处理手机号登录。
第 4 部分:前端校验统一升级
static/js/login.js
修改 check_username():
check_username: function () {
var re1 = /^[a-zA-Z0-9_-]{5,20}$/; // 用户名
var re2 = /^1[3-9]\d{9}$/; // 手机号
if (re1.test(this.username) || re2.test(this.username)) {
this.error_username = false;
} else {
this.error_username = true;
this.error_username_message = "请输入正确的用户名或手机号";
}
}
templates/users/login.html
现在的 placeholder 是:
<input type="text"
name="username"
class="name_input"
placeholder="请输入用户名"
v-model="username"
@blur="check_username">
修改为:
<input type="text"
name="username"
class="name_input"
placeholder="请输入用户名或手机号"
v-model="username"
@blur="check_username">
测试登录:


发布者:LJH,转发请注明出处:https://www.ljh.cool/44251.html