第三章:注册功能实现——表单验证、密码强度检测、邮箱/手机号唯一性检查
好,咱们接着聊注册功能。说实话,注册是用户进入系统的第一道门,也是我踩坑最多的地方。你想想看,一个注册功能要是做得不严谨,后面所有权限管理都是白搭。我个人习惯把注册拆成三个核心环节:表单验证、密码强度检测、唯一性检查。这三个环节缺一不可,顺序也不能乱。
3.1 表单验证:别让脏数据进门
表单验证,说白了就是检查用户填的东西合不合规矩。我在项目中遇到过最离谱的事——用户把手机号填成了「12345678901」,少了一位,结果系统还让他注册成功了。后来查日志才发现,前端验证没做,后端也没兜底。
嗯,这里要注意:前端验证是用户体验,后端验证才是安全底线。前端可以帮你过滤掉90%的明显错误,但后端必须做100%的校验。我建议你至少检查这几项:
- 必填项:用户名、密码、邮箱/手机号,一个都不能少
- 格式校验:邮箱要有@和域名,手机号要符合位数规则
- 长度限制:用户名别超过50字,密码别少于8位
- 特殊字符过滤:防止XSS和SQL注入
核心原则:永远不要信任用户输入。哪怕用户是你亲妈,也要做验证。
来看一段后端验证的代码示例,我用的是Flask + WTForms:
from wtforms import Form, StringField, PasswordField, validators
class RegistrationForm(Form):
username = StringField('用户名', [
validators.DataRequired(message='用户名不能为空'),
validators.Length(min=2, max=20, message='用户名长度2-20位')
])
email = StringField('邮箱', [
validators.DataRequired(message='邮箱不能为空'),
validators.Email(message='请输入有效的邮箱地址')
])
phone = StringField('手机号', [
validators.DataRequired(message='手机号不能为空'),
validators.Regexp(r'^1[3-9]\d{9}$', message='手机号格式不正确')
])
password = PasswordField('密码', [
validators.DataRequired(message='密码不能为空'),
validators.Length(min=8, max=128, message='密码长度8-128位')
])
这段代码里,我用了正则表达式来校验手机号。为什么是^1[3-9]\d{9}$?因为目前国内手机号第二位从3到9都有,11位数字。你想想看,要是用^1\d{10}$,那「10000000000」这种假号也能过,对吧?
3.2 密码强度检测:别让用户用「123456」
密码强度检测,这个我太有发言权了。我曾经维护过一个老系统,用户密码全是「123456」或者「password」。后来被脱库了,老板让我背锅。从那以后,我对密码强度检测就特别较真。
我个人习惯把密码强度分成三个等级:
| 等级 | 要求 | 示例 |
|---|---|---|
| 弱 | 纯数字或纯字母,长度不足8位 | 123456, abcdef |
| 中 | 数字+字母混合,长度8-12位 | abc12345 |
| 强 | 数字+大小写字母+特殊字符,长度12位以上 | Abc@2024#Secure |
我建议注册时至少要求「中」等级,最好强制「强」等级。为什么?因为弱密码在暴力破解面前,撑不过1秒。你想想看,现在GPU跑字典的速度有多快?
来看一个密码强度检测的Python实现:
import re
def check_password_strength(password):
score = 0
if len(password) >= 8:
score += 1
if len(password) >= 12:
score += 1
if re.search(r'[a-z]', password):
score += 1
if re.search(r'[A-Z]', password):
score += 1
if re.search(r'\d', password):
score += 1
if re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
score += 1
if score <= 2:
return '弱'
elif score <= 4:
return '中'
else:
return '强'
小技巧:前端也可以做实时强度检测,用颜色条显示。绿色代表强,黄色代表中,红色代表弱。用户看到红色,自己就会改密码。
这里有个坑——不要存储明文密码。我曾经见过一个项目,密码直接存数据库里,连MD5都没做。后来被脱库,用户信息全裸奔。正确的做法是用bcrypt或Argon2做哈希,加盐处理。
3.3 邮箱/手机号唯一性检查:别让用户重复注册
唯一性检查,说白了就是确保同一个邮箱或手机号只能注册一个账号。这个逻辑看起来简单,但实现起来有讲究。
我建议分两步走:
- 注册前检查:用户填完邮箱/手机号,点击「获取验证码」时,先查一下数据库里有没有这个记录。如果有,直接提示「该邮箱/手机号已被注册」。
- 注册时检查:提交表单时,再次检查唯一性。为什么?因为用户可能在输入验证码的这段时间里,另一个用户已经注册了同一个邮箱。
注意:唯一性检查一定要加数据库唯一索引。光靠代码检查是不够的,高并发下会出现「竞态条件」。我曾经就因为这个,让两个用户注册了同一个手机号,后来对账对了一整天。
来看数据库设计:
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
phone VARCHAR(20) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
注意看,email和phone都加了UNIQUE约束。这是最后一道防线,哪怕代码有bug,数据库也能兜住。
后端检查逻辑:
def check_uniqueness(email, phone):
existing_user = User.query.filter(
(User.email == email) | (User.phone == phone)
).first()
if existing_user:
if existing_user.email == email:
return {'error': '该邮箱已被注册'}
if existing_user.phone == phone:
return {'error': '该手机号已被注册'}
return None
这里我用了OR条件,一次查询搞定。为什么不用两次查询?因为数据库连接是宝贵的资源,能省则省。你想想看,如果每个注册请求都查两次数据库,并发一上来,数据库就扛不住了。
3.4 整合:完整的注册流程
好了,三个环节都讲完了。咱们把它们串起来,看看完整的注册流程是什么样的:
- 用户填写注册表单
- 前端做基本格式验证(比如邮箱格式、密码长度)
- 用户点击「获取验证码」,后端检查邮箱/手机号唯一性
- 用户输入验证码,提交表单
- 后端做完整表单验证(包括密码强度检测)
- 后端再次检查唯一性(防止并发问题)
- 验证通过,密码哈希存储,创建用户记录
- 返回注册成功消息
记住:注册功能是用户信任你的第一步。做得好了,用户觉得你专业;做得差了,用户转身就走。我见过太多项目,注册体验一塌糊涂,结果用户流失率高达60%。
最后说一句,验证码也是注册功能的重要一环。虽然这节课没展开讲,但你要知道,验证码能有效防止机器注册。我个人习惯用短信验证码+图形验证码双重保险。嗯,这个咱们后面章节再细聊。
好,注册功能就讲到这里。下一章咱们聊聊登录功能——Session管理、JWT令牌、记住我功能。到时候见。