在求职平台项目中,用户认证与文件上传是核心基础功能。今天我们将实现了机号密码登录、短信验证码登录、JWT Token 生成与解析,以及头像上传到阿里云 OSS 的功能
二、核心模块与技术选型
- 认证方式:密码登录 + 短信验证码登录
- Token 管理:JWT(access_token + refresh_token)
- 验证码存储:Redis(设置过期时间,防止重复获取)
- 文件存储:阿里云 OSS(存储用户头像)
- 框架分层:接口层(路由)+ 服务层(业务逻辑)+ 模型层(数据库)
三、数据库模型设计
首先定义求职者Jobseeker模型,存储用户核心信息:
python
运行
# app/models/jobseeker.py from tortoise import models, fields class Jobseeker(models.Model): id = fields.IntField(pk=True) phone = fields.CharField(max_length=12, description="手机号", unique=True) password = fields.CharField(max_length=12, description="密码") nickname = fields.CharField(max_length=12, description="昵称", default="用户1") avatar = fields.CharField(max_length=255, description="头像", default="无") wxchat = fields.CharField(max_length=255, description="微信号", null=True) email = fields.CharField(max_length=255, description="邮箱", null=True) create_time = fields.DatetimeField(auto_now_add=True) update_time = fields.DatetimeField(auto_now=True) class Meta: table = "jobseeker"四、接口层实现(路由)
我们使用APIRouter模块化管理求职者接口,将路由前缀设为/jobseeker。
1. 路由初始化
python
运行
# app/api/jobseeker.py from fastapi import APIRouter, Depends, UploadFile, Request from app.schemas.jobseeker import JobseekerLoginPsd, JobseekerLoginSms from app.services.jobseeker_service import JobSeekerService from app.core.depends import getinfo_form_token jobseeker_router = APIRouter(prefix="/jobseeker", tags=["求职者模块"])2. 登录相关接口
(1)手机号 + 密码登录
python
运行
@jobseeker_router.post(path="/loginbypsd/", summary="手机号,密码登录") async def loginbypsd(logininfo: JobseekerLoginPsd): res = await JobSeekerService.loginbypsd(logininfo) return {"code": 200, "msg": "登录成功", "data": res}(2)发送短信验证码
python
运行
@jobseeker_router.get(path='/getsms/', summary="给指定手机号发送验证码") async def getsms(phone: str): res = await JobSeekerService.getsms(phone) return {"code": 200, "msg": "发送成功"}(3)手机号 + 验证码登录
python
运行
@jobseeker_router.post(path='/loginbysms/', summary="手机号+验证码登录") async def loginbysms(logininfo: JobseekerLoginSms): res = await JobSeekerService.loginbysms(logininfo) return {"code": 200, "msg": "登录成功", "data": res}3. 登录后操作接口
(1)获取个人信息
通过Depends(getinfo_form_token)从 Token 中解析出用户 ID:
python
运行
@jobseeker_router.get(path='/getmyinfo/', summary='获取个人信息') async def getmyinfo(seekerid=Depends(getinfo_form_token)): res = await JobSeekerService.getmyinfo(seekerid) return {"code": 200, "msg": "获取成功", "data": res}(2)忘记密码(重置密码)
python
运行
@jobseeker_router.put(path='/getnewpsd/', summary='忘记密码') async def getnewpsd(request: Request, seekerid=Depends(getinfo_form_token)): newpsd = await request.json() res = await JobSeekerService.getnewpsd(newpsd.get("password"), seekerid) return {"code": 200, "msg": "修改成功"}(3)头像上传
接收UploadFile类型文件,上传到阿里云 OSS:
python
运行
@jobseeker_router.post(path='/uploadavatar/', summary='上传头像') async def uploadavatar(file: UploadFile, seekerid=Depends(getinfo_form_token)): res = await JobSeekerService.uploadavatar(file, seekerid) return {"code": 200, "msg": "上传成功"}五、服务层实现(业务逻辑)
1. Redis 连接初始化
python
运行
import redis r = redis.Redis(host="127.0.0.1", port=6379, db=10, decode_responses=True, protocol=2)2. 核心服务类JobSeekerService
(1)手机号 + 密码登录
python
运行
from app.models import Jobseeker from app.utils.jwt_util import create_tokens class JobSeekerService: @staticmethod async def loginbypsd(logininfo: JobseekerLoginPsd): seekerobj = await Jobseeker.get_or_none(phone=logininfo.phone) if seekerobj is None: raise Exception("手机号不存在") if seekerobj.password != logininfo.password: raise Exception("密码错误") # 生成 JWT Token access_token, refresh_token = create_tokens(seekerobj.id, seekerobj.phone) return { "access_token": access_token, "refresh_token": refresh_token, "jobseeker": seekerobj }(2)发送短信验证码
python
运行
import re from app.utils.sms_util import get_sms_code, send_sms_code from app.core.logging import logger @staticmethod async def getsms(phone: str): if not phone: raise Exception("手机号不能为空") if not re.match(r"^1[3-9]\d{9}$", phone): raise Exception("手机号格式不对") smskey = f"sms_code:{phone}" # 防止重复获取验证码 code_redis = r.get(smskey) if code_redis: raise Exception("请勿重复获取验证码") # 生成 6 位验证码 code = str(get_sms_code(6)) # 调用第三方短信平台发送(模拟) send_sms_code(phone, code) logger.info(f"手机号为: {phone},验证码为: {code}") # 存入 Redis,2 分钟过期 r.set(smskey, code, ex=60*2) return True(3)手机号 + 验证码登录
python
运行
@staticmethod async def loginbysms(logininfo: JobseekerLoginSms): seekerobj = await Jobseeker.get_or_none(phone=logininfo.phone) if seekerobj is None: raise Exception("用户不存在") smskey = f"sms_code:{logininfo.phone}" code_redis = r.get(smskey) if code_redis is None: raise Exception("验证码已过期,请重新获取") if code_redis != logininfo.code: raise Exception("验证码错误") # 生成 Token access_token, refresh_token = create_tokens(seekerobj.id, seekerobj.phone) # 登录成功后删除验证码 r.delete(smskey) return { "access_token": access_token, "refresh_token": refresh_token, "jobseeker": seekerobj }(4)获取用户信息
python
运行
@staticmethod async def getmyinfo(seekerid: str): seekerobj = await Jobseeker.get_or_none(id=seekerid) if not seekerobj: raise Exception("用户不存在") return seekerobj(5)重置密码
python
运行
@staticmethod async def getnewpsd(password: str, seekerid: str) -> bool: seekerobj = await Jobseeker.get_or_none(id=seekerid) if not seekerobj: raise Exception("用户不存在") seekerobj.password = password await seekerobj.save() return True(6)头像上传到阿里云 OSS
python
运行
from fastapi import UploadFile from app.utils.alioss_util import AliyunOSSTool @staticmethod async def uploadavatar(file: UploadFile, seekerid: str): if not file: raise Exception("文件不能为空") # 读取文件内容 filedata = await file.read() filename = file.filename # 上传到阿里云 OSS alioss = AliyunOSSTool() issuccess, result = alioss.upload_single_file( filedata, filename, oss_path='p4boss/seeker_avatar/' ) if issuccess: seekerobj = await Jobseeker.get_or_none(id=seekerid) if not seekerobj: raise Exception("用户不存在") # 更新数据库头像地址 seekerobj.avatar = result['access_url'] await seekerobj.save() return True else: raise Exception("上传失败")六、关键技术点解析
1. JWT Token 生成与解析
- 生成:登录成功后,调用
create_tokens()生成access_token(短有效期)和refresh_token(长有效期)。 - 解析:通过
Depends(getinfo_form_token)中间件,从请求头的Authorization中提取 Token,解析出用户 ID,实现接口鉴权。
2. Redis 缓存验证码
- 防重发:获取验证码前先查询 Redis,若存在则拒绝重复请求。
- 过期控制:验证码存入 Redis 时设置
ex=120(2 分钟过期),确保安全性。 - 登录后销毁:验证码登录成功后立即删除 Redis 中的验证码,防止复用。
3. 文件上传到阿里云 OSS
UploadFile:FastAPI 提供的文件上传类型,可直接读取文件内容和文件名。- OSS 工具类:封装
AliyunOSSTool,实现文件上传、获取访问 URL 等功能。 - 数据库更新:上传成功后,将 OSS 返回的
access_url更新到用户avatar字段