1. 企业认证接口事务 多文件上传企业认证是今天新增的完整功能它最能体现真实业务的复杂度要同时写 3 张表、传 3 个文件营业执照 法人身份证正反面。1.1 接口层接收表单 三个文件# app/apis/enterprise_api.pyenterprise_api.post(/save,summary保存企业信息)asyncdefsaveEnterpriseInfo(form:EnterpriseCreateRequestDepends(get_enterprise_form),# 表单字段自动解析business_license_file:UploadFileFile(None,description营业执照),legal_id_front_file:UploadFileFile(None,description法人身份证正面),legal_id_back_file:UploadFileFile(None,description法人身份证反面),):awaitEnterpriseService.saveEnterpriseInfo(form,business_license_file,legal_id_front_file,legal_id_back_file)return{code:1,message:保存成功}注意File(None)里的None表示文件可以不传但一旦传了就是UploadFile对象。1.2 依赖层用 Form 把一堆表单字段打包成一个对象企业信息字段特别多如果接口参数一个个写会很丑。我们用get_enterprise_form把它们收拢成一个EnterpriseCreateRequest# app/core/depends.pyasyncdefget_enterprise_form(enterprise_nameForm(...,description企业名称),unified_social_credit_codeForm(...,description统一社会信用代码),legal_representativeForm(...,description法人代表),registered_capitalForm(...,description注册资本),establish_dateForm(None,description成立日期),# None 表示可空register_statusForm(...,description登记状态),industry_idForm(...,description所属行业),company_scaleForm(...,description公司规模),headquarters_addressForm(...,description总部地址),business_scopeForm(...,description经营范围),contact_nameForm(...,description联系人姓名),contact_phoneForm(...,description联系电话),contact_emailForm(...,description联系邮箱),# ... 其他字段):returnEnterpriseCreateRequest(enterprise_nameenterprise_name,unified_social_credit_codeunified_social_credit_code,# ... 一一对应)Form(...)里的...表示必填Form(None)表示选填。1.3 业务层事务保证要么全成要么全不成企业认证要连写 3 张表、传 3 个文件。最怕写到第 2 张表时挂了第 1 张却已经写了——数据就脏了。事务Transaction就是用来解决这个问题的事务里的所有操作要么全部成功提交要么整体回滚。# app/services/enterprise_services.pyimportuuidfromfastapiimportUploadFilefromtortoise.transactionsimportin_transactionfromapp.utils.oss_utilimportAliyunOSSToolclassEnterpriseService:staticmethodasyncdefsaveEnterpriseInfo(form,business_license_file,legal_id_front_file,legal_id_back_file):asyncwithin_transaction()asconn:# 开启事务# 1) 主表企业账号状态待审核enterpriseawaitEnterprise.create(enterprise_nameform.enterprise_name,enterprise_codestr(uuid.uuid4()),# 生成唯一企业码account_statusAccountStatus.PENDING_AUDIT,# 待审核blacklist_statusBlackListStatus.NOT_BANNED,audit_typeAuditType.NEW_ENTERPRISE_AUTH,submit_timenow(),)# 2) 子表企业详细信息awaitEnterpriseInfo.create(enterprise_nameform.enterprise_name,unified_social_credit_codeform.unified_social_credit_code,# ... 其它字段enterprise_identerprise.id)# 3) 上传三个文件到阿里云 OSS拿到可访问的 URLossAliyunOSSTool()licawaitbusiness_license_file.read()ok1,res1oss.upload_single_file(lic,business_license_file.filename,oss_pathenterprise/)ifnotok1:raiseException(上传营业执照图片失败)# 抛异常 → 事务自动回滚frontawaitlegal_id_front_file.read()ok2,res2oss.upload_single_file(front,legal_id_front_file.filename,oss_pathenterprise/)ifnotok2:raiseException(上传法人身份证正面图片失败)backawaitlegal_id_back_file.read()ok3,res3oss.upload_single_file(back,legal_id_back_file.filename,oss_pathenterprise/)ifnotok3:raiseException(上传法人身份证反面图片失败)# 4) 子表资质信息存文件 URLawaitEnterpriseQualification.create(contact_nameform.contact_name,contact_phoneform.contact_phone,enterprise_identerprise.id,business_license_urlres1[access_url],legal_id_front_urlres2[access_url],legal_id_back_urlres3[access_url])# 离开 with 代码块 事务提交成功事务就像一个打包操作。我们用async with in_transaction()包住所有写库动作。只要中间任何一步抛异常比如 OSS 上传失败前面已经写入的数据会自动撤销不会出现半成品。OSS对象存储可以理解成云上的硬盘。文件不直接存服务器而是上传到阿里云拿到一个access_url存进数据库前端用这个 URL 就能展示图片。2. 附件简历OSS 上传 数量限制 默认简历求职者可以上传多个附件简历Word/PDF但只允许最多 3 个且其中只能有 1 个是默认简历。来看看怎么实现这两个约束。2.1 接口层保存 / 更新 / 删除# app/apis/resume_api.pyresume_api.post(/upload,summary上传附件简历)asyncdefupload_resume(user_id:intForm(...),resume_file:UploadFileFile(...,description简历文件Word/PDF),is_default:boolForm(False,description是否设为默认简历),):resultawaitResumeService.upload_resume(user_id,resume_file,is_default)return{code:1,message:上传成功,data:result}resume_api.put(/set-default,summary设置默认简历)asyncdefset_default_resume(resume_id:intForm(...),user_id:intForm(...),):awaitResumeService.set_default_resume(resume_id,user_id)return{code:1,message:设置成功}resume_api.delete(/delete,summary删除附件简历)asyncdefdelete_resume(resume_id:intForm(...),user_id:intForm(...),):awaitResumeService.delete_resume(resume_id,user_id)return{code:1,message:删除成功}2.2 业务层校验数量与默认约束# app/services/resume_services.pyfromtortoise.transactionsimportin_transactionclassResumeService:staticmethodasyncdefupload_resume(user_id:int,resume_file:UploadFile,is_default:bool):# 1) 校验数量最多 3 个countawaitResume.filter(user_iduser_id).count()ifcount3:raiseHTTPException(status_code400,detail附件简历最多只能上传 3 个)# 2) 如果设为默认先把其他简历的默认标记取消ifis_default:awaitResume.filter(user_iduser_id,is_defaultTrue).update(is_defaultFalse)# 3) 上传到 OSSossAliyunOSSTool()contentawaitresume_file.read()ok,resultoss.upload_single_file(content,resume_file.filename,oss_pathresumes/)ifnotok:raiseHTTPException(status_code500,detail简历文件上传失败)# 4) 保存记录resumeawaitResume.create(user_iduser_id,file_urlresult[access_url],file_nameresume_file.filename,is_defaultis_default,)return{resume_id:resume.id,file_url:result[access_url]}staticmethodasyncdefset_default_resume(resume_id:int,user_id:int):asyncwithin_transaction()asconn:# 先把该用户所有简历的默认标记取消awaitResume.filter(user_iduser_id).update(is_defaultFalse)# 再把目标简历设为默认awaitResume.filter(idresume_id,user_iduser_id).update(is_defaultTrue)staticmethodasyncdefdelete_resume(resume_id:int,user_id:int):resumeawaitResume.get_or_none(idresume_id,user_iduser_id)ifnotresume:raiseHTTPException(status_code404,detail简历不存在)# 删除 OSS 文件可选ossAliyunOSSTool()oss.delete_file(resume.file_url)# 删除数据库记录awaitresume.delete()3. 总结今天我们完成了两个核心业务功能功能技术要点业务价值企业认证事务 多文件上传 OSS保证数据一致性避免脏数据附件简历数量校验 默认约束 OSS 联动满足产品约束防止用户误操作事务和 OSS 是后端开发中的基础设施技能。掌握了它们你就能应对绝大多数需要写多张表 存文件的真实业务场景。