黑马微服务保险(一)
项目介绍
2.1、系统架构
四方保险属于一个保险销售平台;有两个终端:
-
后台管理端
-
APP用户端
整体架构如下:
代理服务:保险平台和保险销售平台通过的nginx、gateway网关
路由处理:gateway网关负责路由通用服务和业务服务,同时做数据埋点和网关权限校验
服务治理:gateway网关、通用服务、业务服务、seata等服务注册和配置都放入nacos中统一管理
通用服务:抽离出加密安全、统一鉴权、消息系统、支付结算、配置存储、数据埋点、数字字典等基础微服务
基础支持:在线文档knife4j、时序数据库influxDB、redisson缓存客户端、xxl-job计划任务、spring-cloud-stream
外部服务:支付宝、微信支付、ocr、三方核保、批单、承保平台
2.2、核心业务
管理后端的核心流程:
暂时无法在飞书文档外展示此内容
app端核心流程:
暂时无法在飞书文档外展示此内容
2.3、课程内容
-
项目介绍&环境搭建
-
保险基础数据管理-分析与设计
-
保险产品
-
产品附件—对象存储
-
产品详情-性能优化与数据脱敏
-
投保试算-保障类
-
投保试算-理财类
-
投保处理-投保处理
-
支付处理-一次性支付
-
支付处理-周期性扣款
-
数据埋点
-
数据中心——时序数据库
-
短信服务
4.1、熟悉项目
熟悉项目的第一步是熟悉项目的结构、用到的技术、编码的一些规范等。
4.1.1、项目结构
下面我们介绍一下各个模块的主要职能:
├── sfbx-cloud # 项目模块父工程,统一管理JAR版本和插件内容
│ ├── sfbx-apache-httpclient # 远程调用通信框架(第三方、保司接口调用)
│ ├── sfbx-dict # 数字字典:其他功能用到的常用字段维护
│ ├── sfbx-file # 对象存储:支持OSS、七牛云的文件存储服务
│ ├── sfbx-framework # 基础支持:缓存、消息中间件、分布式事务、线程池、脱敏、调度等支持
│ ├── sfbx-gateway # 网关路由:销售端、运营端、接口端网关路由
│ ├── sfbx-insurance # 保险业务:四方保险主业务服务包括用户端和管理端
│ ├── sfbx-points # 数据埋点:数据采集、存储、分析
│ ├── sfbx-rule # 规则引擎:规则引擎的UI支持服务
│ ├── sfbx-security # 权限系统:基于oauth2.0的权限管理
│ ├── sfbx-sms # 短信服务:支持阿里云短信、简单短信、腾讯云短信的短信微服务
│ ├── sfbx-task # 计划监听:统一调度响应和监听
│ ├── sfbx-trade # 交易服务:支持支付宝、微信支付的多种场景的支付平台
注意:
一般正式使用和发布的系统;如果所有的微服务加起来,数据库中的表大概 200+
例如:商城 300 - 500 张表; 四方保险类的大概 300 左右
4.1.2、开发规范
-
在项目模块中;*-app的是针对app端的应用服务;*-mgt的是针对web后台管理的应用服务;
-
项目中带有 *-interface的模块是feign客户端放置的模块工程;而应用到的 dto 放置在framework-commons模块中,该模块中的实体类以*VO结尾命名;
-
有需要使用到其它微服务提供的接口的话就引入对应的 *-interface 即可;
-
一般项目中,如 insurance-mgt 的 feign 包内的相当于当前服务中某些业务对外提供的接口功能(不是接口类);这些对外业务接口处理器的请求路径命名一般为 *-feign
保险基础数据-设计与开发
学习目标
-
设计开发基础数据中的保障项、系数项、筛选项、保险分类功能
1、基础数据介绍
保险-基础数据。通过基础数据功能的学习我们主要解决下列问题:
-
什么是基础数据,包含哪些,分别有什么作用?
-
基础数据的表结构如何进行设计?
-
分类与保障项、系数项、筛选项有什么关系?
上图是用户端功能截图,从中我们提炼并定义下列4个名词:
-
保障项:通常是指在购买保险时所包含的具体风险或事件,以及在这些风险或事件发生时,保险公司愿意提供的经济赔偿或保障。不同类型的保险产品涵盖不同的保障项
-
系数项:通常是指一系列用于计算保险费率的因子或系数。这些因子可以包括被保险人的年龄、性别、健康状况、驾驶记录、保险类型、地理位置等,它们会影响最终的保险费率计算。
-
筛选项:通常是指一系列因素和选项,需要根据你的具体需求和情况来考虑,来选择自己需要的保险服务。
-
分类项:通常是指保险根据不同的标准和覆盖范围进行的分类,不同的分类涵盖的方面不同,此处我们主要讨论下列几类保险:医疗、重疾、意外、养老金、旅游等分类;附录-保险分类
1)分页搜索
说明 | 请求信息 |
---|---|
接口地址 | http://sf.mgt.itheima.net/api/insurance-mgt/safeguard/page/{pageNum}/{pageSize} |
请求方式 | POST |
请求参数 | SafeguardVO 对应的数据结构如:{pageSize: 10, total: 1, pageNum: 1, safeguardKey: "", safeguardKeyName: "身故保险金"} |
响应结果 | ResponseResult<Page<SafeguardVO>> 返回的结构参考如下: { "code": 200, "msg": "操作成功", "data": { "records": [ { "id": "1753336375152783362", "createTime": "2024-02-02 16:35:42", "updateTime": "2024-05-13 18:44:03", "createBy": "1371500419615895553", "updateBy": "1371500419615895553", "dataState": "0", "creator": null, "safeguardKey": "BZ056", "safeguardKeyName": "身故保险金", "safeguardVal": "[{\"val\": \"1\", \"name\": \"详见保险条款\", \"score\": \"100\"}]", "safeguardType": "0", "sortNo": null, "remake": null, "checkedIds": null } ], "total": 1, "size": 10, "current": 1, "orders": [], "optimizeCountSql": true, "hitCount": false, "countId": null, "maxLimit": null, "searchCount": true, "pages": 1 }, "operatorId": 1371500419615895553, "operatorName": "admin@qq.com", "operatorSex": "0", "_class": "com.baomidou.mybatisplus.extension.plugins.pagination.Page", "tip": "本站点所有接口仅用于教学演示,所有数据均非真实数据,请勿用作其它用途!", "operationTime": "2024-05-13 18:44:03" } |
这段代码是“保障项”业务的服务层实现类,核心功能是:
-
基于MyBatis-Plus框架,实现了“保障项”的分页查询逻辑:通过构建条件(按保障项编号精确查询、名称模糊查询、状态筛选)和按创建时间降序排序,执行数据库分页查询,再将查询结果从数据库实体类(Safeguard)转换为前端所需的视图对象(SafeguardVO)并返回。
-
同时定义了“保障项”相关的其他业务方法(如单条查询、新增、修改、删除等)的接口实现,但这些方法目前为空实现,待后续补充。
2)新建
说明 | 请求信息 |
---|---|
接口地址 | http://sf.mgt.itheima.net/api/insurance-mgt/safeguard |
请求方式 | PUT ---> 全量更新,如果不存在则新增 |
请求参数 | { "safeguardKeyName": "test111", "safeguardKey": "111", "safeguardVal": "[{\"name\":\"111\",\"val\":\"11\",\"score\":\"1\"}]", "safeguardType": "1", "icon": "", "dataState": "0", "resourceType": "F" } |
响应结果 | ResponseResult<SafeguardVO> 数据结构参考如下: { "code": 200, "msg": "操作成功", "data": { "id": "1789999088167677954", "createTime": "2024-05-13 20:40:13", "updateTime": "2024-05-13 20:40:13", "createBy": "1371500419615895553", "updateBy": "1371500419615895553", "dataState": "0", "creator": null, "safeguardKey": "111", "safeguardKeyName": "test111", "safeguardVal": "[{\"name\":\"111\",\"val\":\"11\",\"score\":\"1\"}]", "safeguardType": "1", "sortNo": null, "remake": null, "checkedIds": null }, "operatorId": 1371500419615895553, "operatorName": "admin@qq.com", "operatorSex": "0", "_class": "com.itheima.sfbx.insurance.dto.SafeguardVO", "tip": "本站点所有接口仅用于教学演示,所有数据均非真实数据,请勿用作其它用途!", "operationTime": "2024-05-13 20:40:13" } |
这段代码实现了“新增保障项”的完整功能,涉及控制器层(Controller)和服务层(Service)的协作,具体如下:
-
控制器层(SafeguardController#createSafeguard)定义了一个处理“新增保障项”请求的接口:
-
通过@PutMapping指定接收PUT请求,访问路径为/safeguard(基于类的@RequestMapping);
-
接收前端传入的SafeguardVO对象(包含新增所需的保障项信息,如类型、编号、值等)作为请求体;
-
调用服务层的save方法处理业务逻辑,将结果用统一响应格式ResponseResult包装后返回给前端;
-
通过Swagger注解(@ApiOperation等)生成API文档,明确接口功能和参数说明。
-
-
服务层(SafeguardServiceImpl#save)实现了新增保障项的核心业务逻辑:
-
将前端传入的SafeguardVO(视图对象)转换为Safeguard实体类(与数据库表映射的对象);
-
调用MyBatis-Plus的save方法将实体类数据保存到数据库;
-
若保存失败(返回false),抛出运行时异常;若成功,将保存后的实体类再转换为SafeguardVO返回;
-
异常处理:捕获保存过程中的所有异常,打印详细日志,并抛出项目自定义异常ProjectException(携带“保存失败”的枚举标识),便于统一异常处理和返回明确的错误信息。
-
整体流程:前端发送包含保障项信息的PUT请求 → 控制器接收并转发给服务层 → 服务层完成对象转换、数据库保存、异常处理 → 最终返回新增结果(成功则返回新增的保障项信息,失败则返回统一错误响应)。
3)保存编辑
说明 | 请求信息 |
---|---|
接口地址 | http://sf.mgt.itheima.net/api/insurance-mgt/safeguard |
请求方式 | PATCH ---》部分更新,不存在的话更新失败 |
请求参数 | SafeguardVO 数据结构参考如下: { "id": "1789999088167677954", "createTime": "2024-05-13 20:40:14", "updateTime": "2024-05-13 20:40:14", "createBy": "1371500419615895553", "updateBy": "1371500419615895553", "dataState": "0", "creator": null, "safeguardKey": "111", "safeguardKeyName": "test1112", "safeguardVal": "[{\"val\":\"11\",\"name\":\"111\",\"score\":\"1\"}]", "safeguardType": "1", "sortNo": null, "remake": null, "checkedIds": null } |
响应结果 | ResponseResult<Boolean> 数据结构参考如下: { "code": 200, "msg": "操作成功", "data": true, "operatorId": 1371500419615895553, "operatorName": "admin@qq.com", "operatorSex": "0", "_class": "java.lang.Boolean", "tip": "本站点所有接口仅用于教学演示,所有数据均非真实数据,请勿用作其它用途!", "operationTime": "2024-05-13 20:45:02" } |
这段代码实现了“修改保障项”的完整功能,包含控制器层(Controller)和服务层(Service)的协作,专门用于处理保障项的更新操作,具体如下:
1. 控制器层(SafeguardController#updateSafeguard)
定义了处理“修改保障项”请求的接口,核心作用是接收前端更新请求并转发给服务层:
-
请求方式与路径:通过@PatchMapping指定接收HTTP PATCH请求,访问路径为/safeguard(基于类的@RequestMapping)。PATCH方法通常用于“部分更新”(只需传递需要修改的字段,无需传递完整对象),区别于PUT方法的“完整更新”。
-
参数接收:通过@RequestBody接收前端传入的SafeguardVO对象,包含需要更新的保障项信息(必须包含id以确定更新哪条记录,其他字段如保障项类型、编号、值等为可选更新项)。
-
Swagger文档说明:通过@ApiOperation等注解说明接口功能,并指定SafeguardVO中需要展示的参数(尤其强调id为必需,用于定位要更新的记录)。
-
业务调用与响应:调用服务层的update方法执行更新逻辑,将返回的布尔结果(true表示成功,false表示失败)用统一响应格式ResponseResult包装后返回给前端。
2. 服务层(SafeguardServiceImpl#update)
实现了修改保障项的核心业务逻辑,负责具体的数据库更新操作和异常处理:
-
对象转换:将前端传入的SafeguardVO(视图对象)转换为Safeguard实体类(与数据库表映射的对象),因为数据库操作需要基于实体类执行。
-
数据库更新:调用MyBatis-Plus提供的updateById方法,根据实体类中的id(主键)定位到具体记录,并更新其字段值(仅更新传入的非空字段,符合PATCH“部分更新”的特性)。
-
结果校验:若updateById返回false(表示更新失败,可能因id不存在或数据库异常),抛出运行时异常提示“更新失败”。
-
异常处理:捕获更新过程中的所有异常,通过日志记录详细堆栈信息,并抛出项目自定义的ProjectException(携带SafeguardEnum.UPDATE_FAIL枚举标识),便于全局异常处理器统一返回错误响应。
整体流程
前端发送包含“需要更新的保障项信息(含id)”的PATCH请求 → 控制器接收请求并调用服务层 → 服务层完成对象转换、数据库更新、结果校验和异常处理 → 最终返回更新是否成功的布尔结果(成功则true,失败则通过异常返回错误信息)。
该功能专门用于“部分更新”场景,允许前端仅传递需要修改的字段,减少数据传输量,同时通过id精准定位更新对象,保证操作的准确性。
4)启用/禁用
这个功能与 保存编辑是一模一样的接口;所以并不需要再写,调用 保存编辑 的接口即可。
5)删除
说明 | 请求信息 |
---|---|
接口地址 | http://sf.mgt.itheima.net/api/insurance-mgt/safeguard |
请求方式 | DELETE |
请求参数 | SafeguardVO 只传递了要删除的 Id 数组;数据结构参考:{checkedIds: ["1789999088167677954"]} |
响应结果 | ResponseResult<Boolean> { "code": 200, "msg": "操作成功", "data": true, "operatorId": 1371500419615895553, "operatorName": "admin@qq.com", "operatorSex": "0", "_class": "java.lang.Boolean", "tip": "本站点所有接口仅用于教学演示,所有数据均非真实数据,请勿用作其它用途!", "operationTime": "2024-05-13 21:04:36" } |
这段代码实现了保障项的批量删除功能,通过控制器层(Controller)接收删除请求、服务层(Service)执行数据库批量删除操作,同时包含参数转换、结果校验和异常处理,具体拆解如下:
-
控制器层(SafeguardController#deleteSafeguard)
核心作用是接收前端批量删除请求,转发给服务层,遵循 RESTful 风格设计:
-
请求方式与路径:通过@DeleteMapping指定接收 HTTP DELETE请求,访问路径为/safeguard(继承类级别的@RequestMapping("/safeguard")),DELETE方法语义上对应 “删除资源”,符合 REST 规范。
-
参数接收:通过@RequestBody接收前端传入的SafeguardVO对象,实际核心参数是SafeguardVO中的checkedIds(即 “选中要删除的保障项 ID 数组”)—— 前端通常会将用户勾选的多条保障项 ID 封装成数组传入。
-
Swagger 文档说明:@ApiOperationSupport(includeParameters = {"safeguardVO.checkedIds"})明确指定 API 文档中仅展示checkedIds参数,避免冗余,同时提示前端 “需传递要删除的 ID 数组”。
-
业务调用与响应:从SafeguardVO中提取checkedIds(String 类型数组),调用服务层的delete方法执行删除逻辑,最终将服务层返回的布尔结果(true成功 /false失败)用统一响应格式ResponseResult包装后返回给前端。
-
服务层(SafeguardServiceImpl#delete)
核心作用是处理批量删除的业务逻辑,执行数据库操作并处理异常:
-
参数类型转换:前端传入的checkedIds是String[](字符串数组,如["1","2","3"]),但数据库中保障项的主键(id)通常是Long类型(数值型,如1、2、3),因此需要通过 Stream 流转换: Arrays.asList(checkedIds).stream().map(Long::new).collect(Collectors.toList()) 将String[]先转成 List<String>,再通过map(Long::new)将每个字符串 ID 转成 Long 类型,最终得到List<Long>(符合 MyBatis-Plus 批量删除方法的参数要求)。
-
数据库批量删除:调用 MyBatis-Plus 提供的removeByIds(List<Long> ids)方法,实现 “根据主键数组批量删除记录”—— 该方法内部会自动拼接DELETE FROM 表名 WHERE id IN (?, ?, ?)的 SQL,比循环单条删除更高效。
-
结果校验:若removeByIds返回false(表示删除失败,可能原因:1. 传入的id不存在;2. 数据库执行异常),则主动抛出RuntimeException,提示 “批量删除保障项失败”。
-
异常处理:捕获删除过程中的RuntimeException(含主动抛出的失败异常、数据库异常等),通过log.error打印详细的异常堆栈信息(便于排查问题),再抛出项目自定义的ProjectException(携带SafeguardEnum.DEL_FAIL枚举标识)—— 统一交给项目的 “全局异常处理器” 处理,最终返回给前端标准化的错误响应(如错误码 + 提示信息)。
整体流程
-
前端:用户勾选多条保障项,将选中的 ID 数组(如["101","102"])封装到SafeguardVO的checkedIds中,发送DELETE /safeguard请求;
-
控制器:接收SafeguardVO,提取checkedIds,调用服务层delete方法;
-
服务层:
-
将String[]的 ID 转成List<Long>;
-
调用removeByIds批量删除数据库记录;
-
校验删除结果,失败则抛异常;
-
捕获异常并打日志,抛自定义异常;
-
-
响应:若删除成功,返回ResponseResult<Boolean>(true);若失败,通过自定义异常返回 “删除失败” 的错误信息。
关键细节
-
批量删除效率:使用 MyBatis-Plus 的removeByIds而非循环单条删除,减少数据库连接次数,提升效率;
-
参数类型适配:解决 “前端传字符串 ID、数据库存数值 ID” 的类型不匹配问题,避免 SQL 执行错误;
-
异常规范化:通过自定义异常ProjectException统一错误码,便于前端统一处理错误场景(如弹窗提示 “删除失败,请重试”)。
关于该保险的初步总结:
一个保险包括: 多个保险方案➕多个保障项➕多个系数项➕多个筛选项➕多个附件
同时保险还会分为几个大类:医疗险,重疾险,意外(出行,运动),旅游(境内境外),人寿(终期,定期,双全),年金(养老,教育,财富)。
保险方案指的是:若保险名称是肺癌保险,会有多个保险方案,例如:
方案一报销肺癌检查费用和住院费。
方案二报销肺癌治疗费用和挂号费。
每个方案包含多个保障项:检查费(保障项),医药费(保障项),挂号费(保障项)。
每个保险同时也包含多个系数项:付款方式:年付/月付(系数项),是否自动续保(系数项)。
筛选项:特色保障:终身给,为谁买?保多久?已有健康状况? 这些都是筛选项,一款保险会绑定一些筛选项,这样筛选的时候就可以看到对应的保险。
添加保险时运用到了分布表单:也就是
一步步将这个保险的所有信息全部输入完毕。
新建保险的分布表单实现技术:
Session会话存储(最常用、最简单) 这是最直观的实现方式,利用服务器端的Session来临时存储整个表单的数据对象。 第一步:定义模型(Model) 创建一个Java类(例如 ProductFormData ),它包含所有步骤需要填写的字段(如 name , description , features , price 等)。这个对象将作为你整个多步表单的数据容器。 第二步:控制器(Controller)处理每一步 Step 1 (/create-product/step1): GET请求:返回第一步的视图(比如一个输入产品名称的页面)。 POST请求:接收用户输入的名称。从Session中获取或创建一个新的 ProductFormData 对象,将名称存入该对象。然后将此对象存回Session。最后重定向到第二步的URL。 Step 2 (/create-product/step2): GET请求:从Session中取出 ProductFormData 对象,用它来预填充第二步的视图(如果需要)。 POST请求:接收用户输入的功能描述,更新Session中的 ProductFormData 对象。重定向到下一步或完成页。 ...后续步骤以此类推... 最终步 (/create-product/finish): POST请求:从Session中取出完整的 ProductFormData 对象。 执行最终的业务逻辑验证。 调用Service层方法,将数据持久化到数据库。 清除Session中存储的临时 ProductFormData 对象。 返回一个成功页面或结果页。 优点:实现简单,逻辑清晰。 缺点:数据存储在服务器内存中,对大量用户并发时可能占用较多内存。用户如果开多个浏览器标签同时创建产品可能会互相干扰。
新建保险时附件的上传(简单/分片):
好的,根据您提供的图片信息,这是一个清晰、标准的文件上传流程。它展示了文件(如图片)如何从用户的前端界面最终被保存到云端对象存储,并在数据库中留下记录的全过程。
下面我将为您分步详细讲解这个流程:
流程总览
这个流程涉及四个核心角色,他们各司其职:
-
前端:用户直接交互的界面,负责选择文件并发起上传。
-
文件微服务:后端业务逻辑的处理中心,是连接前端、数据库和对象存储的桥梁。
-
对象存储(阿里云OSS):专业的云端文件存储服务,负责安全、可靠地存放文件本身。
-
数据库(MySQL):存储与文件相关的记录信息(元数据),如文件名称、大小、在OSS上的存储路径等。
整个流程是一个顺序执行的链条,如下图所示: 前端 -> 文件微服务 -> 对象存储 -> 文件微服务 -> 数据库 -> 文件微服务 -> 前端
分步详解
第1步:前端发起上传请求
-
发生什么:用户在网页或App上选择了要上传的图片,点击“上传”按钮。
-
细节:前端会将文件数据(二进制内容)和相关的表单信息(如文件名)通过HTTP请求(通常是POST)发送给后端的文件微服务。
第2步:文件微服务接收并转发
-
发生什么:文件微服务的接口接收到前端发送过来的文件数据。
-
细节:微服务会对请求进行一些初步处理,如身份认证(验证用户是否有权限上传)、校验文件大小和类型等。验证通过后,它再将文件数据转发给专业的对象存储服务(阿里云OSS)。
第3步:对象存储保存文件
-
发生什么:阿里云OSS接收到文件数据,并将其持久化地存储在自己的服务器集群中。
-
细节:OSS会为每个文件生成一个全局唯一的访问地址(URL)。这个地址就像是文件在互联网上的“门牌号”,后续可以通过这个地址来访问或下载该文件。
第4步:对象存储返回存储信息
-
发生什么:文件成功保存后,OSS会向文件微服务发送一个成功响应。
-
细节:这个响应中包含了关键信息,最主要的就是上一步生成的文件存储地址(URL),还可能包含文件哈希值(ETag)用于校验文件完整性。
第5步:文件微服务保存文件记录
-
发生什么:文件微服务在收到OSS的成功响应后,并不会保存文件本身,而是将文件的“元信息”记录到数据库中。
-
细节:它会向MySQL数据库的某张表(例如 file_record)插入一条新记录。这条记录通常包含:文件ID、原始文件名、在OSS中的存储路径/URL、上传用户ID、上传时间、文件大小等信息。这样做的好处是:应用服务器(微服务)轻量化,只管理业务数据,而耗费磁盘空间的大文件则由专业的OSS负责,架构更清晰、成本更低、扩展性更好。
第6步:文件微服务返回图片地址
-
发生什么:文件微服务将最终的文件访问地址返回给前端。
-
细节:这个地址通常就是直接从OSS获取的那个URL,或者是由微服务包装过的一个更友好的地址。
第7步:前端展示文件
-
发生什么:前端接收到上传成功的响应和文件地址后,通知用户上传成功。
-
细节:前端可以立即使用这个返回的图片地址来渲染、展示刚上传的图片,完成整个上传体验。
总结
这个流程是一个非常经典和高效的现代Web应用架构:
-
职责分离:业务逻辑(微服务)、数据存储(MySQL)和文件存储(OSS)各司其职,系统更稳定,易于维护和扩展。
-
性能与成本:利用专业的OSS服务,节省了自建文件服务器的成本和带宽压力,并且能获得更好的性能和可靠性。
-
安全可控:所有操作都通过后端微服务中转,便于进行统一的权限验证、日志记录和安全审计。
简单来说,这个流程就是:前端传数据 -> 服务端转存到OSS -> 服务端记下文件的“户口” -> 告诉前端文件存好了在哪看。
好的,这张图清晰地展示了基于分片上传技术,并整合了对象存储(阿里云OSS) 和数据库(MySQL) 的完整文件上传架构流程。
这个流程的核心思想是:将一个大文件分割成多个小块(分片)逐个上传,最后在服务端合并。这样做的好处非常明显:
-
克服大文件上传问题:避免因网络不稳定或超时导致整个大文件上传失败。
-
实现断点续传:即使网络中断,下次也可以只传失败的分片,而不用重头开始。
-
充分利用带宽:可以并发上传多个分片,提高上传速度。
下面我们按照图中编号的步骤,详细讲解这个流程:
流程分步讲解
整个流程可以分为三大阶段:1. 初始化 -> 2. 分片上传循环 -> 3. 合并收尾。
角色说明:
-
前端:用户浏览器或客户端应用。
-
文件微服务 (file-web):处理文件相关业务逻辑的后端服务,是连接前端、OSS和数据库的核心枢纽。
-
对象存储 (阿里云OSS):负责存储文件内容的云服务,性价比高,无限扩展。
-
数据库 (MySQL):用于存储文件的上传记录、分片信息、最终地址等元数据。
第一阶段:初始化 (步骤 1-6)
这是上传开始前的准备工作。
-
前端选择要上传的文件后,首先向文件微服务发送一个“初始化分片上传”的请求。
-
文件微服务接收到这个请求。
-
文件微服务随即向阿里云OSS发起一个初始化分片上传的请求。OSS为此文件上传会话创建一个唯一的标识。
-
OSS返回响应,其中包含一个重要的凭证:分片上传标识符 (uploadId)。这个uploadId将用于标识本次整个文件的上传会话,后续所有分片的上传都必须带上它。
-
文件微服务将文件信息(如文件名、大小、uploadId、状态等)保存到MySQL数据库中(图中未明确表名,通常是tab_file或类似表)。这一步是为了记录“有一个文件开始上传了”,便于后续查询和管理。
-
文件微服务将OSS返回的uploadId等信息返回给前端。
至此,前端拿到了“通行证”uploadId,可以开始上传分片了。
第二阶段:分片上传与循环 (步骤 7-12)
这是上传的核心环节,前端将文件切块,并循环上传每一个分片。
-
前端根据预设的分片大小(如每片5MB)将文件切割成多个分片。
-
对于每一个分片,前端都向文件微服务发起上传请求。请求中会携带分片参数(如分片序号partNumber、uploadId)和分片文件数据。
-
文件微服务收到请求后,将分片文件内容转发给OSS。
-
OSS成功接收到一个分片后,会返回一个分片上传结果(partETag)。ETag是OSS为该分片生成的唯一标识,用于后续合并时验证分片的有效性和顺序。
-
文件微服务将这个分片的上传记录(包括uploadId, partNumber, partETag等)保存到数据库的分片记录表(tab_file_part) 中。这一步至关重要,是实现断点续传的关键。即使服务重启,也能从数据库里查询到哪些分片已经上传成功。
-
文件微服务将分片上传成功的结果返回给前端。
步骤 8 到 12 会循环执行,直到文件的所有分片都上传完毕。
第三阶段:合并与收尾 (步骤 13-18)
所有分片上传完成后,需要通知OSS将它们合并成一个完整的文件。
-
前端在所有分片上传成功后,向文件微服务发送一个“汇总并合并分片”的请求。
-
文件微服务向OSS发起“合并分片”的请求。这个请求会携带uploadId以及从数据库查询到的所有分片的partNumber和partETag列表。
-
OSS根据收到的列表,按分片序号顺序将所有分片合并成一个完整的文件。合并成功后,OSS会返回一个最终的、完整的文件访问地址(URL)。
-
OSS将合并结果返回给文件微服务。
-
文件微服务执行清理和更新操作:
-
删除分片记录:从数据库的tab_file_part表中删除本次上传的所有分片记录,因为它们已经没用了。
-
保存完整文件地址:在数据库的主文件表(如tab_file)中更新该文件的状态为“上传成功”,并保存OSS返回的最终文件地址。
-
-
文件微服务通知前端文件合并成功,并返回文件的展示地址。前端随即向用户展示上传成功的文件。
总结
这个架构的优点在于:
-
职责分离:业务服务器(微服务)只处理业务逻辑和元数据管理,沉重的文件存储和传输工作由专业的OSS负责,减轻了主服务器的压力。
-
安全可控:所有对OSS的请求都通过微服务中转,避免了前端直接与OSS交互可能带来的安全密钥泄露风险,也便于做权限校验、日志记录等操作。
-
可靠性高:通过数据库记录上传状态,完美支持了断点续传和错误重试。
-
扩展性强:轻松应对大文件上传和高速上传的需求。
希望这个讲解能帮助你完全理解这张流程图!
好的,这份讲义详细介绍了阿里云OSS的分片上传(Multipart Upload) 功能。下面我将为您分步详细讲解其核心概念、适用场景、流程和限制。
一、什么是分片上传?
分片上传是一种将大文件分割成多个较小部分(称为分片或Part)分别上传,最后在服务端将这些分片组合成一个完整文件的技术。
-
类比:想象一下你要搬运一个巨大的衣柜上楼。因为衣柜太大无法直接通过楼梯,你会把它拆解成木板、抽屉、门板等多个部分,分批搬上楼,最后再重新组装起来。分片上传的过程与此非常相似。
二、为什么要使用分片上传?(使用场景)
讲义中明确了三种主要场景,这些都是简单上传(直接PutObject)无法很好解决的问题:
-
大文件加速上传(核心场景)
-
问题:单个超大文件(如5GB以上的视频、数据库备份、镜像文件)使用简单上传,耗时极长,且容易因网络波动或超时而失败,导致前功尽弃。
-
解决方案:分片上传允许并发上传多个分片,充分利用带宽,极大缩短总上传时间。
-
-
网络环境较差
-
问题:在弱网环境下,上传大文件极易中断。
-
解决方案:分片上传支持断点续传。如果某个分片上传失败,只需重新上传这个失败的分片即可,而无需重传整个文件。这节省了大量时间和流量。
-
-
文件大小不确定(流式上传)
-
问题:在某些场景下(如视频监控、直播录制),文件还在生成中,最终大小未知,但需要立即开始上传。
-
解决方案:可以初始化一个分片上传任务,然后一边生成数据,一边上传一个个分片,最后在所有数据生成完毕后再完成合并。
-
三、分片上传的流程(核心步骤)
讲义中描述的流程是使用OSS API的标准流程,整个过程如下图所示:
-
初始化分片上传(InitiateMultipartUpload)
-
作用:告诉OSS:“我要开始一个新的分片上传任务了”。OSS会为这个任务创建一个唯一的标识符:UploadId。
-
后续作用:后续所有针对这个文件的分片上传、合并等操作,都必须带上这个 UploadId,以便OSS知道这些操作属于同一个任务。
-
-
上传分片(UploadPart)
-
作用:将文件切分后的各个部分依次或并发地上传到OSS。
-
关键参数:
-
UploadId:来自第一步的响应。
-
PartNumber(分片序号):必须从1开始顺序编号(1, 2, 3, ...)。这是OSS最后合并分片的唯一依据。
-
PartData:分片的具体数据内容。
-
-
重要特性:
-
并发上传:各个分片之间没有依赖关系,可以同时上传,这是加速的关键。
-
返回ETag:每个分片上传成功后,OSS都会返回一个ETag(该分片的唯一标识)。客户端必须妥善保存每个分片的 PartNumber 和其对应的 ETag,这是最后合并的凭证。
-
-
-
完成/合并分片(CompleteMultipartUpload)
-
作用:当所有分片都成功上传后,调用此接口通知OSS:“所有分片都传完了,请根据我提供的 PartNumber 顺序和 ETag 列表,把它们合并成一个完整的文件”。
-
输入:UploadId 和保存好的 (PartNumber, ETag) 列表。
-
输出:合并成功后,OSS会返回一个完整的Object,可以像普通文件一样访问。
-
-
放弃上传(AbortMultipartUpload)
-
作用:如果在上传过程中想终止任务(例如用户取消了操作),可以调用此接口。
-
结果:OSS会释放所有已经上传的分片占用的空间。这是一个清理操作,可以避免为未完成的任务支付存储费用。
-
四、分片上传的限制(非常重要)
在使用分片上传时,必须遵守以下规则,否则请求会被拒绝:
限制项 | 具体规则 | 说明 |
文件大小 | 不超过 48.8 TB | 几乎满足了所有场景下的需求。 |
分片数量 | 1 到 10,000 个 | 一个上传任务最多只能有1万个分片。 |
分片大小 | 除最后一个分片外,每个分片大小必须 >= 100 KB,且 <= 5 GB。 | 这是最重要的规则。需要根据文件大小提前规划好分片策略。 |
分片策略举例: 假设你有一个 12 GB 的文件。
-
错误策略:分成 2 个 6 GB 的分片。→ 错误!每个分片不能超过 5 GB。
-
错误策略:分成 12000 个 1 MB 的分片。→ 错误!分片数量不能超过 10000 个。
-
正确策略:分成 3 个分片:Part1 = 5 GB, Part2 = 5 GB, Part3 = 2 GB。最后一个分片(2GB)虽然小于5GB,但大于100KB,这是完全符合规则的。
总结
分片上传是处理大文件或不稳定网络上传的必备方案。它通过分治、并发、断点续传的思想,完美解决了简单上传的痛点。理解其流程(初始化->上传分片->合并)和关键概念(UploadId, PartNumber, ETag)以及严格遵守分片大小和数量的限制,是成功实现这一功能的关键。 UploadId是OSS产生返回给客户端,然后客户端每次上传时都要携带它。
PartNumber是客户端自己产生的,每个分片给一个序号按顺序,最后传输结束后全部发给OSS。
ETag是OSS产生返回给客户端的,每成功接收一个分片后就返回给客户端,最后由客户端统一汇总发给OSS。
上传技术代码说明:
好的,我们来详细讲解一下这段 FileUpLoadController.java 代码的功能。
这段代码是“四方保险”系统中文件微服务(file-web) 的核心控制器(Controller)。它对外提供了一组完整的RESTful API,专门用于处理所有类型的文件上传请求,是前端与后端文件存储系统(阿里云OSS)之间的桥梁。
核心功能总览
这个控制器主要实现了两种文件上传模式:
-
简单上传:适用于小文件、图片等。一次性将整个文件上传到服务器。
-
分片上传:适用于大文件。将文件切割成多个小块(分片)逐一上传,最后在服务器端合并。这种方式支持断点续传,克服网络不稳定问题。
分方法详细讲解
-
简单上传:upLoad 方法
-
对应API路径:POST /file/up-load
-
功能:接收一个完整的文件,直接上传到阿里云OSS。
-
流程解析:
-
接收参数:通过 @RequestParam("file") MultipartFile file 接收前端传来的文件流,通过 FileVO fileVO 对象接收额外的元数据(如业务类型、存储目录等)。
-
设置参数:
-
fileVO.setBucketName(...):从统一配置中获取存储桶名称,覆盖前端可能传递的值,保证安全性和一致性。
-
fileVO.setCompanyNo(...):设置公司编号,用于数据隔离(多租户)。
-
-
构建请求对象:将Spring的 MultipartFile 转换为系统内部定义的 UploadMultipartFile 对象,便于后续服务层统一处理。
-
调用服务:调用 fileService.upLoad(...) 方法,将具体的上传逻辑交给服务层处理。服务层会负责与OSS交互、保存文件记录到数据库等。
-
返回结果:将上传成功后的文件信息(如文件ID、访问URL等,封装在 FileVO 中)返回给前端。
-
-
适用场景:用户头像、保险产品小图片、文档等大小在几MB以内的文件。
-
分片上传 - 初始化:initiateMultipartUpload 方法
-
对应API路径:POST /file/initiate-multipart-up-load
-
功能:开始一个分片上传任务前,向OSS申请一个本次上传任务的唯一标识(uploadId)。
-
流程解析:
-
接收一个 FileVO 对象,包含文件的基本信息。
-
同样设置存储桶和公司编号。
-
调用 fileService.initiateMultipartUpload(...),服务层会与OSS通信,获取一个 uploadId。
-
将这个 uploadId 返回给前端。
-
-
重要目的:前端拿到 uploadId 后,在后续上传每一个分片时都必须带上它,这样OSS才知道这些分片属于同一个文件。
-
分片上传 - 上传分片:uploadPart 方法
-
对应API路径:POST /file/up-load-part
-
功能:上传文件的一个分片。
-
流程解析:
-
接收一个文件分片(MultipartFile file)和分片信息(FilePartVO filePartVO)。FilePartVO 中应包含 uploadId 和当前分片的序号(partNumber)等。
-
设置存储桶和公司编号。
-
构建分片文件对象。
-
调用 fileService.uploadPart(...),服务层会将这个分片上传到 OSS。OSS 会为每个成功上传的分片返回一个唯一标识(ETag)。
-
将当前分片的 ETag 信息返回给前端。前端需要缓存所有这些 ETag,在最后合并时会用到。
-
-
循环执行:前端会根据文件大小和分片大小,循环调用此接口,直到所有分片上传完毕。
-
分片上传 - 合并分片:completeMultipartUpload 方法
-
对应 API 路径:POST /file/complete-multipart-up-load
-
功能:在所有分片都上传成功后,通知 OSS 将所有分片按顺序合并成一个完整的文件。
-
流程解析:
-
接收 FileVO,其中应包含 uploadId 和前端收集的所有分片的 ETag 列表。
-
设置存储桶。
-
调用 fileService.completeMultipartUpload(...)。服务层会告诉 OSS:“请根据这个 uploadId,找到所有分片,并按 partNumber 顺序和提供的 ETag 列表将它们合并”。
-
合并成功后,OSS 会返回完整文件的 ETag。同时,服务层也会在数据库中创建该文件的最终记录。
-
将结果返回给前端,表示整个大文件上传成功。
-
代码设计与架构亮点
-
清晰的职责分离:
-
Controller 层只负责:接收参数、校验参数、调用服务、返回结果。它不包含任何具体的业务逻辑。
-
所有与 OSS 的交互、数据库操作等核心逻辑都封装在 IFileService 接口及其实现中,符合“单一职责”原则。
-
-
安全性与一致性:
-
使用 ossAliyunConfigProperties.getBucketName() 覆盖前端传递的 bucketName,防止前端恶意指定存储到其他位置,保证了配置的集中管理和安全。
-
自动设置 CompanyNo,实现了数据层面的多租户隔离。
-
-
良好的 API 设计:
-
使用了 @ApiOperation, @ApiImplicitParam 等 Swagger 注解,自动生成 API 文档,便于前后端协作。
-
返回值统一包装为 ResponseResult<T>,格式标准,便于前端处理。
-
-
支持多种上传模式:
-
同时提供了简单上传和分片上传两套接口,满足了不同场景下的需求,使系统更健壮。
-
总结
这段 FileUpLoadController 代码是一个设计优良的微服务接口层实现。它通过四个核心方法,为整个“四方保险”系统提供了一个统一、安全、高效且可扩展的文件上传入口。无论是小图片还是大文件,都能通过调用它提供的 API 得以妥善处理,完美践行了将“文件功能”抽离为独立微服务的架构思想。