当前位置: 首页 > news >正文

用 FastAPI 写求职者登录注册

用 FastAPI 写求职者登录注册

这是一个仿 BOSS 直聘项目的后端模块,技术栈是 FastAPI + MySQL + Redis + 阿里云 OSS。下面按代码结构记录求职者登录注册这块怎么写。

数据库表 models

文件 app/models/job_seeker.py

用 ORM 建表就是写一个 Python 类:

fromtortoise.modelsimportModelfromtortoiseimportfieldsclassJobSeeker(Model):id=fields.IntField(pk=True)mobile=fields.CharField(max_length=20,unique=True)# 手机号,不能重复password=fields.CharField(max_length=255,null=True)avatar=fields.CharField(max_length=500,null=True)# 存头像图片网址nickname=fields.CharField(max_length=100,null=True)email=fields.CharField(max_length=100,null=True)wechat=fields.CharField(max_length=100,null=True)create_time=fields.DatetimeField(auto_now_add=True)update_time=fields.DatetimeField(auto_now=True)classMeta:table="t_job_seeker"

unique=True 表示手机号不能重复,null=True 表示可以为空,auto_now_add 和 auto_now 让数据库自动记录注册时间和修改时间。

参数校验 schemas

文件 app/schemas/job_seeker.py

前端传来的数据用 Pydantic 定规矩,Field(…) 表示必传:

frompydanticimportBaseModel,FieldclassLoginMobileRequest(BaseModel):mobile:str=Field(...,title="手机号")password:str=Field(...,title="密码")classLoginSmsCodeRequest(BaseModel):mobile:str=Field(...)code:str=Field(...)classCreateUserRequest(BaseModel):mobile:str=Field(...)password:str=Field(...)code:str=Field(...)classUpdateUserRequest(BaseModel):mobile:str=Field(...)new_password:str=Field(...)code:str=Field(...)

漏传的话 FastAPI 会自动报错,不用自己处理。

业务逻辑 services

文件 app/services/job_seeker.py

先准备两个工具:

importredis redis_client=redis.Redis(host="localhost",port=6379,db=6,decode_responses=True)importrandomdefget_random_code(length=6):return''.join(random.choices('1234567890',k=length))

手机号密码登录

asyncdeflogin_by_mobile_password(req):jobseeker=awaitJobSeeker.get_or_none(mobile=req.mobile)ifjobseekerisNone:raiseException("手机号不存在")ifjobseeker.password!=req.password:raiseException("密码错误")access_token,refresh_token=create_tokens(str(jobseeker.id),jobseeker.mobile)return{"jobseeker":jobseeker,"access_token":access_token,"refresh_token":refresh_token}

这里密码是明文对比的,练手可以,正式项目要加密存储。

发送登录验证码

asyncdefsend_sms_code(mobile):jobseeker=awaitJobSeeker.get_or_none(mobile=mobile)ifjobseekerisNone:raiseException("手机号不存在")code=get_random_code(6)redis_client.set(f"boss-api:login:sms:{mobile}",code,ex=120)returncode

验证码存进 Redis,120 秒后自动过期。return code 是为了方便测试,正式项目应该调用短信服务发到手机。

验证码登录

asyncdeflogin_by_mobile_code(req):jobseeker=awaitJobSeeker.get_or_none(mobile=req.mobile)ifjobseekerisNone:raiseException("手机号不存在")redis_code=redis_client.get(f"boss-api:login:sms:{req.mobile}")ifredis_codeisNone:raiseException("验证码已过期")ifredis_code!=req.code:raiseException("验证码错误")access_token,refresh_token=create_tokens(str(jobseeker.id),jobseeker.mobile)redis_client.delete(f"boss-api:login:sms:{req.mobile}")return{"jobseeker":jobseeker,"access_token":access_token,"refresh_token":refresh_token}

先取 Redis 里的验证码,过期或错误就报错,通过后发 token 并删掉验证码。

注册

发注册验证码:

asyncdefsend_sms_code_create(mobile):code=get_random_code(6)redis_client.set(f"boss-api:create:sms:{mobile}",code,ex=120)returncode

正式建号:

asyncdefcreate_job(req):redis_code=redis_client.get(f"boss-api:create:sms:{req.mobile}")ifnotredis_code:raiseException("验证码已过期")ifredis_code!=req.code:raiseException("验证码错误")ifawaitJobSeeker.get_or_none(mobile=req.mobile):raiseException("手机号已存在")new_jobseeker=awaitJobSeeker.create(**dict(req))redis_client.delete(f"boss-api:create:sms:{req.mobile}")access_token,refresh_token=create_tokens(str(new_jobseeker.id),new_jobseeker.mobile)return{"msg":"注册成功","access_token":access_token,"refresh_token":refresh_token}

注册的 Redis key 用 boss-api:create:sms:,登录用 boss-api:login:sms:,两者分开互不干扰。JobSeeker.create(**dict(req)) 把前端参数直接变成一条新记录,注册完直接发 token。

忘记密码

asyncdefforget_password(req):redis_code=redis_client.get(f"boss-api:login:sms:{req.mobile}")ifnotredis_code:raiseException("验证码已过期")ifredis_code!=req.code:raiseException("验证码错误")jobseeker=awaitJobSeeker.get_or_none(mobile=req.mobile)ifjobseekerisNone:raiseException("手机号不存在")jobseeker.password=req.new_passwordawaitjobseeker.save()# 改了一定要 save,不然不会存进数据库redis_client.delete(f"boss-api:login:sms:{req.mobile}")return{"msg":"修改密码成功"}

上传头像

asyncdefupdate_avatar(job_seeker_id,file):content=awaitfile.read()oss=AliyunOSSTool()ok,res=oss.upload_single_file(content,file.filename,oss_path="job_seeker_avatar/")ifok:jobseeker=awaitJobSeeker.get_or_none(id=job_seeker_id)jobseeker.avatar=res["access_url"]awaitjobseeker.save()returnresraiseException("上传失败")

图片传到 OSS,OSS 返回图片网址,只把这个网址存进 avatar 字段。

查看资料

asyncdefget_job_seeker_info_by_id(job_seeker_id):js=awaitJobSeeker.get_or_none(id=job_seeker_id)js.nickname=f"用户{js.mobile}"ifjs.nicknameisNoneelsejs.nickname js.avatar="默认头像网址"ifjs.avatarisNoneelsejs.avatarreturnjs

昵称和头像为空时给默认值。

接口 apis

文件 app/apis/job_seeker_api.py

service 写好后包成接口网址:

@job_seeker_router.post("/login_by_mobile_password")asyncdeflogin_by_mobile_password(req:LoginMobileRequest):res=awaitJobSeekerService.login_by_mobile_password(req)return{"code":1,"message":"登录成功","data":res}

接口层只做三件事:收参数、调 service、包成统一格式返回。返回格式 code 为 1 成功 0 失败,message 是提示,data 是数据。

全部接口(前缀 /job_seeker):

  • /login_by_mobile_password 密码登录
  • /send_sms_code/{mobile} 发登录验证码
  • /login_by_mobile_code 验证码登录
  • /send_sms_code_create/{mobile} 发注册验证码
  • /create_job 注册
  • /forget_password 忘记密码
  • /me 查看资料(需登录)
  • /update_avatar 换头像(需登录)

登录状态 JWT

登录成功后发 token,之后每次请求带上,服务器验证就知道是谁。

defcreate_tokens(user_id,username):payload={"user_id":user_id,"username":username,"type":"access"}payload["exp"]=datetime.utcnow()+timedelta(minutes=120)access_token=jwt.encode(payload,SECRET_KEY,algorithm="HS256")rpayload=payload.copy()rpayload["type"]="refresh"rpayload["exp"]=datetime.utcnow()+timedelta(days=7)refresh_token=jwt.encode(rpayload,SECRET_KEY,algorithm="HS256")returnaccess_token,refresh_token

access_token 有效期 2 小时,每次请求都带;refresh_token 有效期 7 天,用来换新 token。

app/core/depends.py 里从请求头取 token 并解析出 user_id:

defget_job_seeker_info(authorization:str=Header(None)):ifnotauthorization:raiseException("请登录")token=authorization.replace("Bearer ","")payload=verify_token(token)ifpayloadisNone:raiseException("验证失败")returnpayload["user_id"]

需要登录的接口加 Depends(get_job_seeker_info),FastAPI 自动先验证并把 user_id 传进来:

@job_seeker_router.get("/me")asyncdefget_me(job_seeker_id=Depends(get_job_seeker_info)):js=awaitJobSeekerService.get_job_seeker_info_by_id(job_seeker_id)return{"code":1,"message":"获取成功","data":js}

统一异常处理

service 里 raise 的异常由全局处理器接住,转成统一返回:

defglobal_exception_handler(request,exc):returnJSONResponse(status_code=500,content={"code":0,"message":str(exc)})

在 main.py 注册:

app.add_exception_handler(Exception,global_exception_handler)

小结

把这块写完,后端常用的建表、参数校验、分层、JWT 登录、Redis 存验证码、OSS 存图片、统一报错都接触到了。建议跟着敲一遍,把遇到的报错自己解决掉。正式上线前还要注意密码加密、验证码真发短信、发码接口加限流。

http://www.jsqmd.com/news/1245390/

相关文章:

  • 从剧本到成片:AI 短剧生产平台的工程化架构与落地实践
  • GPT-5.6为什么更适合先做分析?直接写代码反而容易返工
  • 2026内江阳台门窗推荐榜:5家密封与五金实力对比 - 家居装修资讯
  • OpenClaw安装与使用:新手常见问题解决方案
  • 智能驾驶车载系统数据安全需求
  • AI辅助写作工具在毕业生求职中的应用与测评
  • AI作弊倒逼教育评估变革:澳大利亚高校重开口试与线下考试
  • AIGC检测与降AI率工具实测指南
  • 如何使用/不使用OTG功能将OPPO手机中的文件传输到U盘
  • 电信行业项目管理全链路数字化怎么选?一家年项目额超十亿电信企业500+项目一体化管控的选型分析
  • 微软平台助力亚太地区人工智能现代化转型
  • C语言指针深入 I 学习总结
  • 零代码微信AI助理OpenClaw实战指南
  • Grok模型集成实战:从API接入到生产部署的完整指南
  • 2026智家AI口碑评价好不好,智家AI深度测评
  • 日志服务与文件共享
  • Django DRF+PostgreSQL 电商 API:构建高并发商品与订单核心接口
  • 中文文本分类实战:机器学习与深度学习方案对比
  • AI写作工具如何提升学术论文创作效率
  • 使用Cloudflare部署CloudMail,零成本拥有专属企业域名邮箱,无需服务器
  • 亲身到店探访唐山亨得利名表服务中心|网点地址与客服热线(2026年7月更新) - 亨得利官方
  • 大模型成本效率深度解析:从架构优化到工程实践
  • Claude Code 安装使用文档
  • VirtualBox主机与虚拟机复制粘贴失效问题解决方案
  • 话轮转换沉默阈值优化:从基础原理到AI对话系统实践
  • CDN行业现状解析与选择指南
  • Boss招聘平台--求职者登录
  • TM4C129x以太网MAC的PPS输出与DMA总线模式寄存器级配置详解
  • 标题优化实战指南:提升搜索可见性与点击率的三步法
  • 嵌入式ADC接口设计与PCB布局:从理论到实践,提升TMS570/RM4x采样精度