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

📅 2026/7/29 2:58:28
FastAPI 后端实战:企业审核功能设计(审核记录 + 状态流转)
FastAPI 后端实战企业审核功能设计审核记录 状态流转记录今天在招聘类项目仿 BOSS 直聘后端写的「企业审核」功能。技术栈FastAPI Tortoise ORM MySQL Redis 阿里云 OSS。只讲后端不展开前端。一、需求与约定企业提交认证资料后需要后台进行审核通过 / 驳回并且要能查看完整的审核历史时间线展示同时审核状态要驱动账号能否登录。核心约定审核结果用整数枚举0提交申请 /1通过 /2驳回审核记录采用**追加append-only**方式每次操作都新增一条而不是覆盖这样完整历史不会丢账号是否通过审核以最新的那条审核记录为准而不是单独在别处维护一个状态位二、数据模型新增一张「企业审核表」挂在企业主表t_enterprise下面用enterprise_id关联# app/models/enterprise.pyclassEnterpriseReview(Model):企业审核表idfields.IntField(pkTrue,description主键ID)review_resultfields.IntField(nullTrue,description审核结果(1:通过,2:驳回))review_reasonfields.CharField(max_length512,nullTrue,description拒绝原因)remarkfields.TextField(nullTrue,description备注说明)audit_timefields.DatetimeField(nullTrue,description审核时间)audit_userfields.CharField(max_length100,nullTrue,description审核人)enterprise_idfields.IntField(nullTrue,description企业ID)classMeta:tablet_enterprise_reviewtable_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:intField(...,description审核结果(0:提交申请,1:通过,2:驳回))review_reason:str|NoneField(None,description拒绝原因)remark:str|NoneField(None,description备注说明)audit_user:str|NoneField(None,description审核人)enterprise_id:intField(...,description企业ID)review_reason/remark/audit_user都允许为空audit_user在管理端没登录态时由后端默认填充见下review_reason只有在驳回时才需要。四、审核接口与核心逻辑两个接口一个是审核操作一个是查历史# app/apis/enterprise_api.pyenterprise_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:intQuery(...,title企业ID,description企业ID)):resawaitEnterpriseService.get_review_records(enterprise_id)return{code:1,message:查询成功,data:res}4.1 审核操作追加记录 通过时改主表状态# app/services/enterprise_service.pystaticmethodasyncdefenterprise_review(enterpriseReviewCreateRequest:EnterpriseReviewCreateRequest):enterprise_identerpriseReviewCreateRequest.enterprise_id audit_userenterpriseReviewCreateRequest.audit_useror平台管理员# 每次审核操作都追加一条审核记录审核历史便于前端展示历史审核记录awaitEnterpriseReview.create(enterprise_identerprise_id,review_resultenterpriseReviewCreateRequest.review_result,review_reasonenterpriseReviewCreateRequest.review_reason,remarkenterpriseReviewCreateRequest.remark,audit_useraudit_user,audit_timenow(),)ifenterpriseReviewCreateRequest.review_result1:enterpriseawaitEnterprise.get_or_none(identerprise_id)ifenterprise:enterprise.account_statusAccountStatus.NORMALawaitenterprise.save()# TODO 发送短信(手机号)或者邮件(邮箱)给 用户这里有两个设计点追加而非覆盖不管通过还是驳回都EnterpriseReview.create一条新记录。这样提交 → 驳回 → 重新提交 → 通过的完整流转在表里都查得到前端做时间线直接读这张表就行。通过才改主表状态只有review_result 1才把企业主表的account_status从PENDING_AUDIT改成NORMAL。驳回时不改主表状态——因为后续可能重新提交、再审核状态最终由「最新审核记录」决定见第五节登录校验。4.2 查询历史按 id 正序直接给前端时间线staticmethodasyncdefget_review_records(enterprise_id:int):查询企业的审核记录历史按时间正序供前端历史审核记录时间线展示recordsawaitEnterpriseReview.filter(enterprise_identerprise_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,})returnresultorder_by(id)保证时间正序前端拿到就是天然的「提交 → 审核1 → 审核2…」时间线不需要再排序。五、两个联动点审核功能不是孤立的提交认证和登录都要和它挂钩。5.1 提交认证时写第一条审核记录企业提交认证资料saveEnterpriseInfo时在事务里先写一条review_result0的「提交申请」记录保证审核历史从提交那一刻就完整# 写入第一条审核记录企业提交认证申请awaitEnterpriseReview.create(enterprise_identerprise.id,review_result0,review_reasonNone,remark企业提交认证申请,audit_user企业,audit_timenow(),)5.2 登录门槛以「最新审核记录」为准企业登录时不查主表状态位而是取该企业最新一条审核记录判断# 取最新一条审核记录判断是否已通过审核enterprise_reviewawaitEnterpriseReview.filter(enterprise_identerprise_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 记录表承载业务状态能否登录由最新一条记录派生既保留完整审计轨迹又避免了多处状态位不同步的坑。希望对做审核 / 审批类功能的同学有一点参考价值。