FastAPI 后端实战:企业审核功能设计(审核记录 + 状态流转)
2026/7/29 2:58:22 网站建设 项目流程

FastAPI 后端实战:企业审核功能设计(审核记录 + 状态流转)

记录今天在招聘类项目(仿 BOSS 直聘)后端写的「企业审核」功能。技术栈:FastAPI + Tortoise ORM + MySQL + Redis + 阿里云 OSS。只讲后端,不展开前端。

一、需求与约定

企业提交认证资料后,需要后台进行审核(通过 / 驳回),并且要能查看完整的审核历史(时间线展示),同时审核状态要驱动账号能否登录。

核心约定:

  • 审核结果用整数枚举:0提交申请 /1通过 /2驳回
  • 审核记录采用**追加(append-only)**方式,每次操作都新增一条,而不是覆盖,这样完整历史不会丢
  • 账号"是否通过审核"以最新的那条审核记录为准,而不是单独在别处维护一个状态位

二、数据模型

新增一张「企业审核表」,挂在企业主表t_enterprise下面(用enterprise_id关联):

# app/models/enterprise.pyclassEnterpriseReview(Model):"""企业审核表"""id=fields.IntField(pk=True,description="主键ID")review_result=fields.IntField(null=True,description="审核结果(1:通过,2:驳回)")review_reason=fields.CharField(max_length=512,null=True,description="拒绝原因")remark=fields.TextField(null=True,description="备注说明")audit_time=fields.DatetimeField(null=True,description="审核时间")audit_user=fields.CharField(max_length=100,null=True,description="审核人")enterprise_id=fields.IntField(null=True,description="企业ID")classMeta:table="t_enterprise_review"table_description="企业审核表"

对应迁移文件migrations/models/6_20260728193745_update.py里建表:

CREATE TABLE IF NOT EXISTS `t_enterprise_review`(`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT'主键ID',`review_result` INT COMMENT'审核结果(1:通过,2:驳回)',`review_reason` VARCHAR(512)COMMENT'拒绝原因',`remark` LONGTEXT COMMENT'备注说明',`audit_time` DATETIME(6)COMMENT'审核时间',`audit_user` VARCHAR(100)COMMENT'审核人',`enterprise_id` INT COMMENT'企业ID')CHARACTER SET utf8mb4 COMMENT='企业审核表';

注意enterprise_id是可空的IntField而非硬外键——审核记录本质是「日志」,用松散关联比强外键更省心,删企业时也不会被外键约束卡住。

三、校验模型

入参模型只收审核需要的信息,不收企业资料本身(资料在提交环节已经存过了):

# app/schemas/enterprise.pyclassEnterpriseReviewCreateRequest(BaseModel):review_result:int=Field(...,description="审核结果(0:提交申请,1:通过,2:驳回)")review_reason:str|None=Field(None,description="拒绝原因")remark:str|None=Field(None,description="备注说明")audit_user:str|None=Field(None,description="审核人")enterprise_id:int=Field(...,description="企业ID")

review_reason/remark/audit_user都允许为空:audit_user在管理端没登录态时由后端默认填充(见下);review_reason只有在驳回时才需要。

四、审核接口与核心逻辑

两个接口,一个是审核操作,一个是查历史:

# app/apis/enterprise_api.py@enterprise_router.post("/review",summary="企业审核",description="企业审核")asyncdefenterprise_review(enterpriseReviewCreateRequest:EnterpriseReviewCreateRequest):awaitEnterpriseService.enterprise_review(enterpriseReviewCreateRequest)return{"code":1,"message":"审核完成"}@enterprise_router.get("/review_records",summary="查询企业审核记录")asyncdefselect_enterprise_review_records(enterprise_id:int=Query(...,title="企业ID",description="企业ID")):res=awaitEnterpriseService.get_review_records(enterprise_id)return{"code":1,"message":"查询成功","data":res}

4.1 审核操作:追加记录 + 通过时改主表状态

# app/services/enterprise_service.py@staticmethodasyncdefenterprise_review(enterpriseReviewCreateRequest:EnterpriseReviewCreateRequest):enterprise_id=enterpriseReviewCreateRequest.enterprise_id audit_user=enterpriseReviewCreateRequest.audit_useror"平台管理员"# 每次审核操作都追加一条审核记录(审核历史,便于前端展示历史审核记录)awaitEnterpriseReview.create(enterprise_id=enterprise_id,review_result=enterpriseReviewCreateRequest.review_result,review_reason=enterpriseReviewCreateRequest.review_reason,remark=enterpriseReviewCreateRequest.remark,audit_user=audit_user,audit_time=now(),)ifenterpriseReviewCreateRequest.review_result==1:enterprise=awaitEnterprise.get_or_none(id=enterprise_id)ifenterprise:enterprise.account_status=AccountStatus.NORMALawaitenterprise.save()# TODO 发送短信(手机号)或者邮件(邮箱)给 用户

这里有两个设计点:

  1. 追加而非覆盖:不管通过还是驳回,都EnterpriseReview.create一条新记录。这样"提交 → 驳回 → 重新提交 → 通过"的完整流转在表里都查得到,前端做时间线直接读这张表就行。
  2. 通过才改主表状态:只有review_result == 1才把企业主表的account_statusPENDING_AUDIT改成NORMAL。驳回时不改主表状态——因为后续可能重新提交、再审核,状态最终由「最新审核记录」决定(见第五节登录校验)。

4.2 查询历史:按 id 正序,直接给前端时间线

@staticmethodasyncdefget_review_records(enterprise_id:int):"""查询企业的审核记录历史(按时间正序),供前端"历史审核记录"时间线展示"""records=awaitEnterpriseReview.filter(enterprise_id=enterprise_id).order_by("id")result=[]forrinrecords:result.append({"id":r.id,"enterprise_id":r.enterprise_id,"review_result":r.review_result,"review_reason":r.review_reason,"remark":r.remark,"audit_user":r.audit_user,"audit_time":r.audit_time,})returnresult

order_by("id")保证时间正序,前端拿到就是天然的「提交 → 审核1 → 审核2…」时间线,不需要再排序。

五、两个联动点

审核功能不是孤立的,提交认证和登录都要和它挂钩。

5.1 提交认证时写第一条审核记录

企业提交认证资料(saveEnterpriseInfo)时,在事务里先写一条review_result=0的「提交申请」记录,保证审核历史从提交那一刻就完整:

# 写入第一条审核记录:企业提交认证申请awaitEnterpriseReview.create(enterprise_id=enterprise.id,review_result=0,review_reason=None,remark="企业提交认证申请",audit_user="企业",audit_time=now(),)

5.2 登录门槛:以「最新审核记录」为准

企业登录时不查主表状态位,而是取该企业最新一条审核记录判断:

# 取最新一条审核记录,判断是否已通过审核enterprise_review=awaitEnterpriseReview.filter(enterprise_id=enterprise_id).order_by("-id").first()ifenterprise_reviewisNoneorenterprise_review.review_result!=1:# 审核通过raiseException("账号未审核通过")

order_by("-id").first()拿最新记录,而不是维护一个独立的「当前状态」字段。好处是审核被驳回后再重新提交、再通过的流转都能正确反映——状态始终由最后一条记录说了算,不会出现「主表已改但历史对不上」的不一致。

六、小结

今天企业审核这块后端落地了三样东西:

  • 模型 + 迁移t_enterprise_review审核记录表,enterprise_id松散关联。
  • 审核接口 + 服务POST /enterprise/review追加审核记录、通过时把主表账号状态置为正常;GET /enterprise/review_records按 id 正序返回历史供时间线展示。
  • 两处联动:提交认证写「提交申请」首条记录;登录以最新审核记录判定是否放行。

整套设计的关键就一句话:审核历史用 append-only 记录表承载,业务状态(能否登录)由最新一条记录派生,既保留完整审计轨迹,又避免了多处状态位不同步的坑。希望对做审核 / 审批类功能的同学有一点参考价值。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询