目录
说明
AI体检报告解读、病例小结或者类似的业务可以参考,完整DEMO不提供,只提供思路。
前言
在上家公司写过AI体检报告解读和AI精准就医推荐需求,不过那时候是调用分公司AI研发部的
API,估计他们也是调用大模型,后台使用的是JeecgBoot管理数据,例如你有这个权益卡,这个
权益卡包含这个服务你就可以使用这个服务。
这次自己对接千帆大模型使用OSS上传体检报告,apache.pdfbox识别pdf内容,设置号Prompt,
调用千帆大模型对体检报告进行分析。
需求
输入如下信息,提前准备好老人体检报告(PDF格式)点击确认按钮以后,会使用AI对老人的健康
报告进行评估。
下面是健康评估的详情页面,使用AI分析后的结果页,给了很多数据,可以让护理员或销售来查看
健康状况,进一步更好的服务老人或者给老人推荐一些护理服务。
流程说明
表结构说明
建表语句:
CREATE TABLE "health_assessment" (
"id" bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
"elder_name" varchar(255) DEFAULT NULL COMMENT '老人姓名',
"id_card" varchar(255) DEFAULT NULL COMMENT '身份证号',
"birth_date" datetime DEFAULT NULL COMMENT '出生日期',
"age" int DEFAULT NULL COMMENT '年龄',
"gender" int DEFAULT NULL COMMENT '性别(0:男,1:女)',
"health_score" varchar(255) DEFAULT NULL COMMENT '健康评分',
"risk_level" varchar(255) DEFAULT NULL COMMENT '严重危险(健康, 提示, 风险, 危险, 严重危险)',
"suggestion_for_admission" int DEFAULT NULL COMMENT '是否建议入住(0:建议,1:不建议)',
"nursing_level_name" varchar(255) DEFAULT NULL COMMENT '推荐护理等级',
"admission_status" int DEFAULT NULL COMMENT '入住情况(0:已入住,1:未入住)',
"total_check_date" varchar(64) DEFAULT NULL COMMENT '总检日期',
"physical_exam_institution" varchar(255) DEFAULT NULL COMMENT '体检机构',
"physical_report_url" varchar(255) DEFAULT NULL COMMENT '体检报告URL链接',
"assessment_time" datetime DEFAULT NULL COMMENT '评估时间',
"report_summary" text COMMENT '报告总结',
"disease_risk" text COMMENT '疾病风险',
"abnormal_analysis" text COMMENT '异常分析',
"system_score" varchar(255) DEFAULT NULL COMMENT '健康系统分值',
"create_by" varchar(255) DEFAULT NULL COMMENT '创建者',
"create_time" datetime DEFAULT NULL COMMENT '创建时间',
"update_by" varchar(255) DEFAULT NULL COMMENT '更新者',
"update_time" datetime DEFAULT NULL COMMENT '更新时间',
"remark" text COMMENT '备注',
PRIMARY KEY ("id")
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='健康评估表';
整体流程
- 上传PDF文件存储到OSS
- 读取PDF文件内容 存储到Redis key为身份证号 value为pdf识别内容
- 点击确定 根据身份证从Redis获取pdf识别内容
- 结合Prompt+pdf内容 调用大模型 返回JSON结果 接收
百度智能云
注册和实名认证
- 注册地址:https://qianfan.cloud.baidu.com/
需要实名认证,不然大模型调用不了,实名认证自己认证一下。
创建应用
实名认证后,需要创建一个应用,获取到这个应用的API Key和Secret Key。
进入管理平台后,找到应用接入,创建新的应用,只有创建了应用,后面才能让大模型来绑定应用
使用。
输入应用的名称,和应用的描述点击确认即可。
获取到AppId、API Key、Secret Key后续项目中调用API需要用到。
费用说明
记得充钱!!!! 调用大模型需要花费TOKEN TOKEN是需要钱的!!!!
充钱才能使你变的强大!!!
大模型API说明
我们的需求中,prompt是比较多的,低版本都不支持大量token使用,所以采用ERNIE-4.0-8K-
Preview。
官方地址:https://cloud.baidu.com/doc/WENXINWORKSHOP/s/nluv1jxlp
ERNIE 4.0是百度自研的旗舰级超大规模⼤语⾔模型,相较ERNIE 3.5实现了模型能力全面升级,
广泛适用于各领域复杂任务场景;支持自动对接百度搜索插件,保障问答信息时效,支持5K
tokens输入+2K tokens输出。
对话模型,一次请求就是一次对话
提供了java的sdk调用,地址:https://github.com/baidubce/bce-qianfan-sdk/tree/main/java
支持单轮对话、多轮对话、流式
部分关键的参数:
名称 |
类型 |
必填 |
描述 |
messages |
List[dict] |
是 |
对话信息, message中的content总长度和system字段总内容不能超过20000个字符,且不能超过5120 tokens |
model |
string |
是 |
模型名称,用于指定平台支持预置服务的模型,说明:该字段为固定值ERNIE-4.0-8K-Preview(必须开通付费) |
temperature |
float |
否 |
大模型的采样参数, (1)较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定 (2)默认0.8,范围 (0, 1.0],不能为0 |
max_output_tokens |
int |
否 |
指定模型最大输出token数,说明: (1)如果设置此参数,范围[2, 2048] (2)如果不设置此参数,最大输出token数为1024 |
response_format |
string |
否 |
指定响应内容的格式,说明: (1)可选值: · json_object:以json格式返回,可能出现不满足效果情况 · text:以文本格式返回 (2)如果不填写参数response_format值,默认为text |
集成大模型
导入依赖:
<dependency>
<groupId>com.baidubce</groupId>
<artifactId>qianfan</artifactId>
<version>0.1.1</version>
</dependency>
编写一个main测试方法:
public class AIModelTest {
private static final String prompt = "你能帮我分析一份完整的体检报告吗?";
public static void main(String[] args) {
/**
* 第一个参数:认证类型,固定选择 Auth.TYPE_OAUTH
* 第二个参数:accessKeyId,从百度云控制台创建的应用里可以找到
* 第三个参数:accessKeySecret,从百度云控制台创建的应用里可以找到
*/
Qianfan qianfan = new Qianfan(Auth.TYPE_OAUTH, "xEO9h4cswlghfdfdUiYpkNt", "T68lkk7XuyfgfdgfdWtCQcFCVkd2HnZuKH");
ChatResponse response = qianfan.chatCompletion()
.model("ERNIE-4.0-8K-Preview") // 模型名称,要选择自己开通付费的模型
.addMessage("user", prompt) // 聊天内容,可以设置多个,每个消息包含role(角色,user表示用户,assistant表示模型),content(消息内容)
// .temperature(0.7) // 采样参数,取值范围(0,1]
// .maxOutputTokens(2000) // 模型输出最大长度,取值范围[2, 2048]
// .responseFormat("json_object") // 模型输出格式,取值范围:text(文本)、json_object(JSON对象)
.execute();
String result = response.getResult();
System.out.println(result);
}
}
结果输出:
设计Prompt
设计之后的Prompt提示词,这里涉及了大量的专业名词和健康指标,实际开发中需要找产品经理
协助
请以一个专业医生的视角来分析这份体检报告,报告中包含了一些异常数据,我需要您对这些数据进行解读,并给出相应的健康建议。 体检内容如下: 内容略.... 要求: 1. 提取体检报告中的“总检日期”; 2. 通过临床医学、疾病风险评估模型和数据智能分析,给该用户的风险等级和健康指数给出结果。风险等级分为:健康、提示、风险、危险、严重危险。健康指数范围为0至100分; 3. 根据用户身体各项指标数据,详细说明该用户各项风险等级的占比是多少,最多保留两位小数。结论格式:该用户健康占比20.00%,提示占比20.00%,风险占比20%,危险占比20%,严重危险占比20%; 4. 对于体检报告有异常数据,请列出(异常数据的结论、体检项目名称、检查结果、参考值、单位、异常解读、建议)这8字段。解读异常数据,解决这些数据可能代表的健康问题或风险。分析可能的原因,包括但不限于生活习惯、饮食习惯、遗传因素等。基于这些异常数据和可能的原因,请给出具体的健康建议,包括饮食调整、运动建议、生活方式改变以及是否需要进一步检查或治疗等。 结论格式:异常数据的结论:肥胖,体检项目名称:体重指数BMI,检查结果:29.2,参考值>24,单位:-。异常解读:体重超标包括超重与肥胖。体重指数(BMI)=体重(kg)/身⾼(m)的平⽅,BMI≥24为超重,BMI≥28为肥胖;男性腰围≥90cm和⼥性腰围≥85cm为腹型肥胖。体重超标是⼀种由多因素(如遗传、进⻝油脂较多、运动少、疾病等)引起的慢性代谢性疾病,尤其是肥胖,已经被世界卫⽣组织列为导致疾病负担的⼗⼤危险因素之⼀。AI建议:采取综合措施预防和控制体重,积极改变⽣活⽅式,宜低脂、低糖、⾼纤维素膳⻝,多⻝果蔬及菌藻类⻝物,增加有氧运动。若有相关疾病(如⾎脂异常、⾼⾎压、糖尿病等)应积极治疗。 5. 根据这个体检报告的内容,分别是给人体的8大系统打分,每项满分为100分,8大系统分别为:呼吸系统、消化系统、内分泌系统、免疫系统、循环系统、泌尿系统、运动系统、感官系统 6. 给体检报告做一个总结,总结格式:体检报告中尿蛋⽩、癌胚抗原、⾎沉、空腹⾎糖、总胆固醇、⽢油三酯、低密度脂蛋⽩胆固醇、⾎清载脂蛋⽩B、动脉硬化指数、⽩细胞、平均红细胞体积、平均⾎红蛋⽩共12项指标提示异常,尿液常规共1项指标处于临界值,⾎脂、⾎液常规、尿液常规、糖类抗原、⾎清酶类等共43项指标提示正常,综合这些临床指标和数据分析:肾脏、肝胆、⼼脑⾎管存在隐患,其中⼼脑⾎管有“⾼危”⻛险;肾脏部位有“中危”⻛险;肝胆部位有“低危”⻛险。 输出要求: 最后,将以上结果输出为JSON格式,不要包含其他的文字说明,所有的返回结果都是json,详细格式如下: { "totalCheckDate": "YYYY-MM-DD", "healthAssessment": { "riskLevel": "healthy/caution/risk/danger/severeDanger", "healthIndex": XX.XX }, "riskDistribution": { "healthy": XX.XX, "caution": XX.XX, "risk": XX.XX, "danger": XX.XX, "severeDanger": XX.XX }, "abnormalData": [ { "conclusion": "异常数据的结论", "examinationItem": "体检项目名称", "result": "检查结果", "referenceValue": "参考值", "unit": "单位", "interpret":"对于异常的结论进一步详细的说明", "advice":"针对于这一项的异常,给出一些健康的建议" } ], "systemScore": { "breathingSystem": XX, "digestiveSystem": XX, "endocrineSystem": XX, "immuneSystem": XX, "circulatorySystem": XX, "urinarySystem": XX, "motionSystem": XX, "senseSystem": XX }, "summarize": "体检报告的总结" }
上传体检报告
点击上传体检报告后,需要将体检报告上传到阿里云OSS然后返回OSS链接,同时识别PDF文件
内容存储到Redis,点击确认的时候,根据身份证号从Redis获取PDF内容,组合Prompt调用大模
型返回JSON数据接收。
读取PDF内容
使用Apache PDFBox来识别PDF内容。
导入依赖:
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.24</version>
</dependency>
创建一个PDFUtil工具类,接收一个文件输入流,读取PDF文件内容,返回String PDF内容。
public class PDFUtil {
public static String pdfToString(InputStream inputStream) {
PDDocument document = null;
try {
// 加载PDF文档
document = PDDocument.load(inputStream);
// 创建一个PDFTextStripper实例来提取文本
PDFTextStripper pdfStripper = new PDFTextStripper();
// 从PDF文档中提取文本
String text = pdfStripper.getText(document);
return text;
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭PDF文档
if (document != null) {
try {
document.close();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
}
测试:
public class PDFUtilTest {
public static void main(String[] args) throws FileNotFoundException {
FileInputStream fileInputStream = new FileInputStream("E:\\tmp\\体检报告-刘爱国-男-69岁.pdf");
String result = PDFUtil.pdfToString(fileInputStream);
System.out.println(result);
}
}
功能实现
OSSAliyunFileStorageService是上传文件至OSS.
@Autowired
private OSSAliyunFileStorageService fileStorageService;
@Autowired
private RedisTemplate<String,String> redisTemplate;
/**
* 通用上传请求(单个)
*/
@ApiOperation("健康文档上传")
@PostMapping("/upload")
public AjaxResult uploadFile(MultipartFile file, String idCardNo) throws Exception
{
try{
//生成一个名字,保证不重复,唯一
String originalFilename = file.getOriginalFilename();
String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));
String url = fileStorageService.store(fileName, file.getInputStream());
AjaxResult ajax = AjaxResult.success();
ajax.put("url", url);
ajax.put("fileName", url);
ajax.put("newFileName", FileUtils.getName(fileName));
ajax.put("originalFilename", file.getOriginalFilename());
//PDF文件内容读取为字符串
String content = PDFUtil.pdfToString(file.getInputStream());
//临时存储到redis中
redisTemplate.opsForHash().put("healthReport", idCardNo, content);
return ajax;
}catch (Exception e){
return AjaxResult.error(e.getMessage());
}
}
智能评测
抽取大模型工具
application.yml定义配置:
baidu:
accessKey: xEO9h4csw9zWlUiYpkNt
secretKey: T67Xuyx9JoCQcFCVkd2HnZuKH
qianfanModel: ERNIE-4.0-8K-Preview
定义properties类方便读取配置:
@Data
@Configuration
@ConfigurationProperties(prefix = "baidu")
public class BaiduAIProperties {
private String accessKey;
private String secretKey;
private String qianfanModel;
}
模型工具类,传入组装好的Prompt,返回String JSON。
@Component
@Slf4j
public class AIModelInvoker {
@Autowired
private BaiduAIProperties baiduAIProperties;
public String qianfanInvoker(String prompt) {
System.out.println(prompt);
Qianfan qianfan = new Qianfan(Auth.TYPE_OAUTH, baiduAIProperties.getAccessKey(), baiduAIProperties.getSecretKey());
ChatResponse response = qianfan.chatCompletion()
.model(baiduAIProperties.getQianfanModel())
.addMessage("user", prompt)
.temperature(0.7)
.maxOutputTokens(2000)
.responseFormat("json_object")
.execute();
String result = response.getResult();
System.out.println(result);
return result;
}
}
功能实现
根据大模型返回的结果数据,返回的是一个JSON,调用大模型的时候已经指定返回的格式是
JSON,然后提取JSON数据,根据返回的结果判断。
例如前端页面的柱状图、饼图数据这些都是模型返回,然后渲染的。
@Autowired
private AIModelInvoker aIModelInvoker;
/**
* 新增健康评估
*
* @param healthAssessmentDto 健康评估
* @return 结果
*/
@Override
public Long insertHealthAssessment(HealthAssessmentDto healthAssessmentDto) {
try {
//组装prompt,然后调用千帆大模型来分析数据
String prompt = getPrompt(healthAssessmentDto);
String result = aIModelInvoker.qianfanInvoker(prompt);
if (StringUtils.isEmpty(result)) {
throw new BaseException("AI分析失败");
}
//解析数据
HealthReportVo healthReportVo = JSONUtil.toBean(result, HealthReportVo.class);
//保存数据
Long id = insertHealthReport(healthReportVo, healthAssessmentDto);
return id;
} catch (BaseException e) {
throw new BaseException("AI分析失败");
}
}
/**
* 插入健康报告
*
* @param healthReportVo
* @return
*/
private Long insertHealthReport(HealthReportVo healthReportVo, HealthAssessmentDto healthAssessmentDto) {
HealthAssessment healthAssessment = new HealthAssessment();
//总检日期
healthAssessment.setTotalCheckDate(healthReportVo.getTotalCheckDate());
healthAssessment.setElderName(healthAssessmentDto.getElderName());
String idCard = healthAssessmentDto.getIdCard();
healthAssessment.setIdCard(idCard);
healthAssessment.setBirthDate(IDCardUtils.getBirthDateByIdCard(idCard));
healthAssessment.setAge(IDCardUtils.getAgeByIdCard(idCard));
healthAssessment.setGender(IDCardUtils.getGenderFromIdCard(idCard));
double healthScore = healthReportVo.getHealthAssessment().getHealthIndex();
healthAssessment.setHealthScore(String.valueOf(healthScore));
healthAssessment.setRiskLevel(healthReportVo.getHealthAssessment().getRiskLevel());
//是否建议入住
healthAssessment.setSuggestionForAdmission(getSuggestionForAdmission(healthScore));
healthAssessment.setNursingLevelName(getNursingLevelName(healthScore));
healthAssessment.setAdmissionStatus(0);
healthAssessment.setTotalCheckDate(healthReportVo.getTotalCheckDate());
healthAssessment.setPhysicalExamInstitution(healthAssessmentDto.getPhysicalExamInstitution());
healthAssessment.setPhysicalReportUrl(healthAssessmentDto.getPhysicalReportUrl());
healthAssessment.setAssessmentTime(LocalDateTime.now());
healthAssessment.setReportSummary(healthReportVo.getSummarize());
healthAssessment.setDiseaseRisk(JSONUtil.toJsonStr(healthReportVo.getRiskDistribution()));
healthAssessment.setAbnormalAnalysis(JSONUtil.toJsonStr(healthReportVo.getAbnormalData()));
healthAssessment.setSystemScore(JSONUtil.toJsonStr(healthReportVo.getSystemScore()));
save(healthAssessment);
return healthAssessment.getId();
}
/**
* 计算护理等级
*
* @param healthScore
* @return
*/
private String getNursingLevelName(double healthScore) {
//处理边界
if (healthScore < 0 || healthScore > 100) {
throw new IllegalArgumentException("健康评分必须在0到100之间");
}
if(healthScore >= 60 && healthScore < 70){
return "三级护理等级";
}else if (healthScore >= 70 && healthScore < 80){
return "二级护理等级";
}else if (healthScore >= 80 && healthScore < 90){
return "一级护理等级";
}else if (healthScore >= 90){
return "特级护理等级";
}
return "无";
}
/**
* 是否建议入住
*
* @param healthScore
* @return
*/
private Integer getSuggestionForAdmission(double healthScore) {
if (healthScore >= 60) {
return 0;
}
return 1;
}
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 获取prompt
*
* @param healthAssessmentDto
* @return
*/
private String getPrompt(HealthAssessmentDto healthAssessmentDto) {
//获取文件中的内容
String content = (String) redisTemplate.opsForHash().get("healthReport", healthAssessmentDto.getIdCard());
//判断是否为空
if (StringUtils.isEmpty(content)) {
throw new BaseException("文件提取内容失败,请重新上传提交报告");
}
String prompt = "请以一个专业医生的视角来分析这份体检报告,报告中包含了一些异常数据,我需要您对这些数据进行解读,并给出相应的健康建议。\n" +
"体检内容如下:\n" +
content + "\n" +
"\n" +
"要求:\n" +
"1. 提取体检报告中的“总检日期”;\n" +
"2. 通过临床医学、疾病风险评估模型和数据智能分析,给该用户的风险等级和健康指数给出结果。风险等级分为:健康、提示、风险、危险、严重危险。健康指数范围为0至100分;\n" +
"3. 根据用户身体各项指标数据,详细说明该用户各项风险等级的占比是多少,最多保留两位小数。结论格式:该用户健康占比20.00%,提示占比20.00%,风险占比20%,危险占比20%,严重危险占比20%;\n" +
"4. 对于体检报告有异常数据,请列出(异常数据的结论、体检项目名称、检查结果、参考值、单位、异常解读、建议)这8字段。解读异常数据,解决这些数据可能代表的健康问题或风险。分析可能的原因,包括但不限于生活习惯、饮食习惯、遗传因素等。基于这些异常数据和可能的原因,请给出具体的健康建议,包括饮食调整、运动建议、生活方式改变以及是否需要进一步检查或治疗等。\n" +
"结论格式:异常数据的结论:肥胖,体检项目名称:体重指数BMI,检查结果:29.2,参考值>24,单位:-。异常解读:体重超标包括超重与肥胖。体重指数(BMI)=体重(kg)/身⾼(m)的平⽅,BMI≥24为超重,BMI≥28为肥胖;男性腰围≥90cm和⼥性腰围≥85cm为腹型肥胖。体重超标是⼀种由多因素(如遗传、进⻝油脂较多、运动少、疾病等)引起的慢性代谢性疾病,尤其是肥胖,已经被世界卫⽣组织列为导致疾病负担的⼗⼤危险因素之⼀。AI建议:采取综合措施预防和控制体重,积极改变⽣活⽅式,宜低脂、低糖、⾼纤维素膳⻝,多⻝果蔬及菌藻类⻝物,增加有氧运动。若有相关疾病(如⾎脂异常、⾼⾎压、糖尿病等)应积极治疗。\n" +
"5. 根据这个体检报告的内容,分别是给人体的8大系统打分,每项满分为100分,8大系统分别为:呼吸系统、消化系统、内分泌系统、免疫系统、循环系统、泌尿系统、运动系统、感官系统\n" +
"6. 给体检报告做一个总结,总结格式:体检报告中尿蛋⽩、癌胚抗原、⾎沉、空腹⾎糖、总胆固醇、⽢油三酯、低密度脂蛋⽩胆固醇、⾎清载脂蛋⽩B、动脉硬化指数、⽩细胞、平均红细胞体积、平均⾎红蛋⽩共12项指标提示异常,尿液常规共1项指标处于临界值,⾎脂、⾎液常规、尿液常规、糖类抗原、⾎清酶类等共43项指标提示正常,综合这些临床指标和数据分析:肾脏、肝胆、⼼脑⾎管存在隐患,其中⼼脑⾎管有“⾼危”⻛险;肾脏部位有“中危”⻛险;肝胆部位有“低危”⻛险。\n" +
"\n" +
"输出要求:\n" +
"最后,将以上结果输出为JSON格式,不要包含其他的文字说明,所有的返回结果都是json,详细格式如下:\n" +
"\n" +
"{\n" +
" \"totalCheckDate\": \"YYYY-MM-DD\",\n" +
" \"healthAssessment\": {\n" +
" \"riskLevel\": \"healthy/caution/risk/danger/severeDanger\",\n" +
" \"healthIndex\": XX.XX\n" +
" },\n" +
" \"riskDistribution\": {\n" +
" \"healthy\": XX.XX,\n" +
" \"caution\": XX.XX,\n" +
" \"risk\": XX.XX,\n" +
" \"danger\": XX.XX,\n" +
" \"severeDanger\": XX.XX\n" +
" },\n" +
" \"abnormalData\": [\n" +
" {\n" +
" \"conclusion\": \"异常数据的结论\",\n" +
" \"examinationItem\": \"体检项目名称\",\n" +
" \"result\": \"检查结果\",\n" +
" \"referenceValue\": \"参考值\",\n" +
" \"unit\": \"单位\",\n" +
" \"interpret\":\"对于异常的结论进一步详细的说明\",\n" +
" \"advice\":\"针对于这一项的异常,给出一些健康的建议\"\n" +
" }\n" +
" ],\n" +
" \"systemScore\": {\n" +
" \"breathingSystem\": XX,\n" +
" \"digestiveSystem\": XX,\n" +
" \"endocrineSystem\": XX,\n" +
" \"immuneSystem\": XX,\n" +
" \"circulatorySystem\": XX,\n" +
" \"urinarySystem\": XX,\n" +
" \"motionSystem\": XX,\n" +
" \"senseSystem\": XX\n" +
" },\n" +
" \"summarize\": \"体检报告的总结\"\n" +
"}";
return prompt;
}
总结
主要是设置好Prompt然后结合PDF内容调用大模型,然后返回JSON数据解析组装,好像一些大模
型直接使用OSS链接也可以,上传PDF后key为身份证号 value为PDF内容 (其实OSS链接也可
以)点击确认根据身份证号从Redis获取PDF内容,组装Prompt调用大模型,流程不复杂,理解了
其实就很容易,后续类似调用大模型需求也大概这种流程。