代理设计模式:从底层原理到源代码 详解
代理设计模式(Proxy Pattern)是一种结构型设计模式,它通过创建一个代理对象来控制对目标对象的访问。代理对象充当客户端和目标对象之间的中介,允许在不修改目标对象的情况下添加额外的功能(如权限控制、日志记录、延迟加载等)。以下将从底层原理到源代码层面,逐步为非专业人士详细解释代理设计模式的每个方面。
一、代理设计模式的通俗概念
1. 什么是代理?
想象你在网上购物,想买一件衣服,但你没有时间直接去实体店。于是,你请了一个朋友(代理)帮你去店里挑衣服、付款并带回来。这个朋友就是“代理”,他代替你完成了与商店的交互。你(客户端)只需要告诉代理你的需求(比如颜色、尺码),代理会帮你处理一切细节。
在程序设计中,代理模式也是类似的:
- 客户端:发出请求的一方(比如你)。
- 代理对象:代替客户端与目标对象交互的对象(比如你的朋友)。
- 目标对象:真正完成工作的对象(比如商店里的衣服)。
代理对象可以在客户端和目标对象之间添加额外的逻辑,比如检查你是否有足够的钱(权限控制)、记录你买了什么(日志记录),或者延迟去商店直到你确认要买(延迟加载)。
2. 为什么需要代理?
代理模式解决的问题是:在不直接修改目标对象的情况下,控制访问或增强功能。
常见场景包括:
- 权限控制:只有特定用户可以访问目标对象。
- 延迟加载:只有在需要时才加载目标对象(比如加载大文件)。
- 日志记录:记录目标对象被调用的时间和参数。
- 远程访问:代理隐藏了目标对象在远程服务器上的细节。
3. 代理模式的本质
代理模式的核心是封装和控制。代理对象封装了对目标对象的访问,客户端通过代理间接调用目标对象的方法。代理可以在调用前后添加额外的逻辑,但客户端无需关心这些细节。
二、代理设计模式的底层原理
代理模式的实现基于面向对象编程的以下关键概念:
1. 接口/抽象类:代理对象和目标对象通常实现同一个接口或继承同一个抽象类,确保它们有相同的方法签名,客户端可以无缝切换。
2. 组合/委托:代理对象通常持有一个目标对象的引用,通过委托(调用目标对象的方法)完成实际工作。
3. 拦截和增强:代理对象在调用目标对象的方法前后插入额外的逻辑,控制访问或增强功能。
工作流程(以买衣服为例)
你(客户端)告诉代理:“我要买一件红色的衣服。”
代理检查你的请求(比如确认你有足够的钱)。
代理将请求转发给商店(目标对象),商店提供衣服。
代理可能记录日志(“你买了一件红色衣服”)。
代理将衣服返回给你。
在代码中,这个流程表现为:
- 客户端调用代理对象的方法。
- 代理对象执行前置逻辑(如检查权限)。
- 代理对象调用目标对象的方法。
- 代理对象执行后置逻辑(如记录日志)。
- 代理对象返回结果给客户端。
三、代理模式的类型
代理模式根据用途分为几种常见类型,理解这些类型有助于选择合适的实现方式:
1. 虚拟代理(Virtual Proxy):延迟加载目标对象,适合目标对象创建成本高的情况(如加载大图片)。
2. 保护代理(Protection Proxy):控制对目标对象的访问,通常用于权限管理。
3. 远程代理(Remote Proxy):隐藏目标对象位于远程服务器的细节,客户端感觉像在本地调用。
4. 智能代理(Smart Proxy):在调用目标对象时添加额外功能,如日志、计数等。
本文将以保护代理为例,详细讲解其实现,因为它简单且能清晰展示代理模式的原理。
四、代理模式的详细实现
以下通过一个具体的例子,用 Java 语言从头实现一个保护代理,逐步解释每部分代码的原理。假设我们有一个文件访问系统,只有管理员可以删除文件,普通用户只能读取文件。
静态代理
1. 定义接口(统一代理和目标对象的契约)
我们需要一个接口,定义文件操作的行为。代理和目标对象都实现这个接口,确保客户端可以用一致的方式调用它们。
public interface FileAccess {void readFile(String fileName);void deleteFile(String fileName);
}
解释:
- FileAccess 接口定义了两个方法:readFile(读取文件)和deleteFile(删除文件)。
- 代理和目标对象都实现这个接口,客户端通过接口调用方法,无需关心背后是代理还是目标对象。
- 这就像你告诉代理“我要买衣服”,代理和商店都理解“买衣服”这个指令。
2. 实现目标对象(实际干活的类)
目标对象是真正执行文件操作的类,比如实际访问文件系统。
public class RealFileAccess implements FileAccess {@Overridepublic void readFile(String fileName) { System.out.println("读取文件: " + fileName); }@Overridepublic void deleteFile(String fileName) {System.out.println("删除文件: " + fileName);}}
解释:
- RealFileAccess 是目标对象,实现了 FileAccess 接口。
- readFile 和 deleteFile 方法模拟文件操作,实际中可能涉及文件系统调用。
- 这个类就像商店,负责实际提供衣服(执行核心逻辑)。
3. 实现代理对象(控制访问)
代理对象也实现 FileAccess 接口,但它会检查权限,并在调用目标对象之前添加控制逻辑。
public class FileAccessProxy implements FileAccess { private RealFileAccess realFileAccess; private String userRole;public FileAccessProxy(String userRole) {this.userRole = userRole;this.realFileAccess = new RealFileAccess(); // 持有目标对象引用}@Overridepublic void readFile(String fileName) {System.out.println("Proxy: 记录读请求 " + fileName);realFileAccess.readFile(fileName); // 委托给目标对象}@Overridepublic void deleteFile(String fileName) {if (userRole.equals("admin")) {System.out.println("Proxy: 有权删除文件: " + fileName);realFileAccess.deleteFile(fileName); // 委托给目标对象} else {System.out.println("Proxy: 权限不足,只有管理员才能删除文件.");}}}
解释:
- 构造函数:FileAccessProxy 接受 userRole(用户角色,如 “admin” 或 “user”),并创建目标对象 RealFileAccess。
- 持目标对象引用:代理通过 realFileAccess 字段持有目标对象的引用,用于委托调用。
- readFile 方法:代理直接调用目标对象的 readFile,并添加日志记录(前置逻辑)。
- deleteFile 方法:代理检查用户角色,只有管理员(userRole 为 “admin”)可以删除文件,否则拒绝访问。
- 这就像你的朋友(代理)在去商店前检查你是否有钱(权限),然后才帮你买衣服。
4. 客户端代码(使用代理)
客户端通过代理对象访问文件系统,无需直接接触目标对象。
public class Main {public static void main(String[] args) {// 普通用户FileAccess userAccess = new FileAccessProxy(“user”);userAccess.readFile(“data.txt”);userAccess.deleteFile(“data.txt”);System.out.println("---");// 管理员FileAccess adminAccess = new FileAccessProxy("admin");adminAccess.readFile("data.txt");adminAccess.deleteFile("data.txt");}
}
输出:
Proxy: 记录读请求文件 data.txt
读取文件: data.txt
Proxy: 权限不足,只有管理员才能删除文件.
---
Proxy: 记录读请求的文件:data.txt
读取文件: data.txt
Proxy: 有权删除文件: data.txt
删除文件: data.txt
解释:
- 客户端创建两个代理对象:一个普通用户(user),一个管理员(admin)。
- 普通用户可以读取文件,但删除文件时被拒绝。
- 管理员可以读取和删除文件。
- 客户端只与代理交互(FileAccess 接口),无需知道目标对象或权限检查的细节。
动态代理(Dynamic Proxy)
动态代理是一种在运行时动态生成代理对象的技术,主要用于在不修改原始类代码的情况下,增强或控制目标对象的行为。其核心思想是通过 反射 和 接口 在运行时生成代理类,实现对目标方法的拦截和增强。
下面以租房为例:
现有租房的接口Rent
房东类Host 实现Rent接口
代理类RentHandler实现InvocationHandler接口
1. 动态代理的核心组件
| 组件 | 作用 | 关键类/接口 |
|---|---|---|
| 抽象接口 | 定义代理类和真实类共同的行为 | Rent(租房接口) |
| 真实对象 | 实际执行业务逻辑的类 | Host(房东类) |
| 调用处理器 | 拦截方法调用并增强逻辑 | InvocationHandler |
| 动态代理类 | 运行时生成的代理对象 | Proxy.newProxyInstance() |
2. 动态代理的设计步骤
(1) 定义抽象接口
代理类和真实类必须实现相同的接口,确保方法调用的兼容性。
public interface Rent {void rent();int getPrice();
}
(2) 实现真实对象(被代理类)
public class Host implements Rent {@Overridepublic void rent() {System.out.println("房东出租房子");}@Overridepublic int getPrice() {return 5000;}
}
(3) 实现调用处理器(InvocationHandler)
负责拦截方法调用,并插入增强逻辑(如日志、权限检查、事务管理等)。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class RentHandler implements InvocationHandler {private final Object target; // 被代理的真实对象(如 Host)public RentHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 前置增强(如权限检查)System.out.println("[代理] 正在检查租客信用...");// 调用真实对象的方法Object result = method.invoke(target, args);
// 后置增强(如日志记录)System.out.println("[代理] 租房完成,签订合同");// 可修改返回值(如砍价)if ("getPrice".equals(method.getName())) {return (int) result - 500; // 代理砍价 500 元}return result;}
}
(4) 动态生成代理对象
使用 Proxy.newProxyInstance() 在运行时生成代理类:
import java.lang.reflect.Proxy;public class Client {public static void main(String[] args) {// 1. 创建真实对象Rent host = new Host();// 2. 创建调用处理器,并关联真实对象RentHandler handler = new RentHandler(host);// 3. 动态生成代理对象Rent proxy = (Rent) Proxy.newProxyInstance(Rent.class.getClassLoader(), // 使用接口的类加载器new Class[]{Rent.class}, // 代理类实现的接口handler // 方法调用的处理器);// 4. 通过代理对象调用方法proxy.rent(); // 会触发 RentHandler.invoke()int price = proxy.getPrice(); // 代理修改返回值System.out.println("最终价格:" + price);}
}
输出结果:
[代理] 正在检查租客信用...
房东出租房子
[代理] 租房完成,签订合同
[代理] 正在检查租客信用...
最终价格:4500
3. 动态代理的适用场景
-
AOP(面向切面编程)
日志记录、性能统计、事务管理。 -
RPC(远程方法调用)
动态代理隐藏网络通信细节(如 Dubbo、gRPC)。 -
权限控制
在方法调用前检查用户权限。 -
缓存代理
缓存方法返回值,避免重复计算。
4. 动态代理的优缺点
✅ 优点
-
无侵入性:无需修改原有代码,直接增强功能。
-
灵活扩展:一个
InvocationHandler可代理多个接口。 -
符合开闭原则:新增功能不影响原有逻辑。
❌ 缺点
-
基于接口:只能代理接口,不能代理类(需用 CGLIB 弥补)。
-
性能开销:反射调用比直接调用稍慢(但现代 JVM 已优化)。
动态代理 vs. 静态代理
| 特性 | 动态代理 | 静态代理 |
|---|---|---|
| 代理类生成时机 | 运行时动态生成 | 编译时手动编写 |
| 代码量 | 少(通用性强) | 多(每个代理类需单独实现) |
| 灵活性 | 高(可代理任意接口) | 低(需为每个类编写代理) |
| 性能 | 稍慢(反射调用) | 快(直接调用) |
五、代理模式的详细原理拆解
1. 接口的作用
- 接口(如 FileAccess)确保代理和目标对象有相同的方法签名,客户端可以用统一的方式调用。
- 这实现了开闭原则:可以替换不同的代理或目标对象,而不修改客户端代码。
- 类似于你在网上购物时,无论是通过朋友(代理)还是直接去商店,购买流程(接口)是一致的。
2. 代理的控制逻辑
- 代理通过持有的目标对象引用(realFileAccess)将请求委托给目标对象。
- 代理可以在调用前后添加逻辑:
- 前置逻辑:如权限检查、日志记录。
- 后置逻辑:如清理资源、返回结果处理。
- 在例子中,deleteFile 的权限检查是前置逻辑,日志记录是前置和后置逻辑的结合。
3. 客户端的透明性
- 客户端通过接口(FileAccess)调用方法,无需知道背后是代理还是目标对象。
- 这实现了封装:客户端只关心结果,不关心权限检查或日志记录的实现细节。
4. 延迟加载(虚拟代理的扩展)
虽然本例是保护代理,但可以扩展为虚拟代理。例如,realFileAccess 可以在第一次调用时才创建:
if (realFileAccess == null) {realFileAccess = new RealFileAccess(); // 延迟初始化
}
这就像你的朋友等到你确认要买衣服时才去商店,节省时间和资源。
六、代理模式的优点和缺点
优点
控制访问:代理可以限制对目标对象的访问(如权限检查)。
功能增强:可以在不修改目标对象的情况下添加日志、缓存等功能。
解耦:客户端与目标对象隔离,降低耦合度。
灵活性:可以动态切换代理逻辑(如根据用户角色选择不同代理)。
缺点
复杂性增加:引入代理对象使系统结构更复杂。
性能开销:代理的额外逻辑可能增加调用时间。
维护成本:需要维护代理和目标对象的同步(方法签名一致)。
七、实际应用场景
代理模式在现实开发中非常常见:
1. Spring AOP:Spring 框架使用动态代理实现切面编程(如日志、事务管理)。
2. 数据库连接池:代理控制数据库连接的分配和回收。
3. Web 框架:代理处理 HTTP 请求的认证、路由等。
4. 图片延迟加载:网页中图片只有在滚动到可视区域时才加载(虚拟代理)。
八、总结
代理设计模式通过引入一个代理对象,控制对目标对象的访问,并在不修改目标对象的情况下添加额外功能。其核心是接口统一、委托调用、逻辑增强。
通过保护代理的例子,我们看到:
- 接口定义了代理和目标对象的契约。
- 代理对象通过持有目标对象引用,拦截和增强客户端请求。
- 客户端通过接口透明调用,无需关心代理的内部逻辑。
相关文章:
代理设计模式:从底层原理到源代码 详解
代理设计模式(Proxy Pattern)是一种结构型设计模式,它通过创建一个代理对象来控制对目标对象的访问。代理对象充当客户端和目标对象之间的中介,允许在不修改目标对象的情况下添加额外的功能(如权限控制、日志记录、延迟…...
15.第二阶段x64游戏实战-分析怪物血量(遍历周围)
免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 本次游戏没法给 内容参考于:微尘网络安全 上一个内容:14.第二阶段x64游戏实战-分析人物的名字 如果想实现自动打怪,那肯定…...
HarmonyOS 基础语法概述 UI范式
ArkUI框架 - UI范式 ArkTS的基本组成 装饰器: 用于装饰类、结构、方法以及变量,并赋予其特殊的含义。如上述示例中Entry、Component和State都是装饰器,Component表示自定义组件,Entry表示该自定义组件为入口组件,Stat…...
专题讨论2:树与查找
在讨论前先回顾一下定义: BST树的定义 二叉搜索树是一种特殊的二叉树,对于树中的任意一个节点: 若它存在左子树,那么左子树中所有节点的值都小于该节点的值。 若它存在右子树,那么右子树中所有节点的值都大于该节点…...
django之数据的翻页和搜索功能
数据的翻页和搜素功能 目录 1.实现搜素功能 2.实现翻页功能 一、实现搜素功能 我们到bootstrap官网, 点击组件, 然后找到输入框组, 并点击作为额外元素的按钮。 我们需要使用上面红色框里面的组件, 就是搜素组件, 代码部分就是下面红色框框出来的部分。 把这里的代码复制…...
盈达科技GEO供应商:用AICC智能认知攻防系统重构AI时代的“内容主权”
《盈达科技GEO供应商:用AICC智能认知攻防系统重构AI时代的“内容主权”》 ——从全网认知统一到多模态智能投喂,破解生成式AI的内容暗战 前言 当用户向ChatGPT提问“XX品牌空调质量如何”时,AI的回答可能直接决定企业30%的潜在客户流向。 生…...
unity脚本-FBX自动化模型面数校验
根据目前模型资源平均面数预算进行脚本制作,自动化校验模型面数是否符合规范。 *注:文件格式为.cs。需要放置在unity资源文件夹Assets>Editor下。 测试效果(拖一个fbx文件进unity时自动检测): 以下为完整代码 us…...
C++用于保留浮点数的两位小数,使用宏定义方法(可兼容低版本Visual Studio)
文章目录 一、 描述二、 样例二、 结果输出 一、 描述 这个宏定义(可放入.h头文件里)使用基本的数学运算,几乎兼容所有版本的VS,以下可对正数做四舍五入: #define ROUND_TO_TWO(x) ( (floor((x) * 100 0.5) / 100) …...
day30 学习笔记
文章目录 前言一、凸包特征检测1.穷举法2.QuickHull法 二、图像轮廓特征查找1.外接矩形2.最小外接矩形3.最小外接圆 前言 通过今天的学习,我掌握了OpenCV中有关凸包特征检测,图像轮廓特征查找的相关原理和操作 一、凸包特征检测 通俗的讲,凸…...
[密码学基础]密码学发展简史:从古典艺术到量子安全的演进
密码学发展简史:从古典艺术到量子安全的演进 密码学作为信息安全的基石,其发展贯穿人类文明史,从最初的文字游戏到量子时代的数学博弈,每一次变革都深刻影响着政治、军事、科技乃至日常生活。本文将以技术演进为主线,…...
(51单片机)LCD显示温度(DS18B20教程)(LCD1602教程)(延时函数教程)(单总线教程)
演示视频: LCD显示温度 源代码 如上图将9个文放在Keli5 中即可,然后烧录在单片机中就行了 烧录软件用的是STC-ISP,不知道怎么安装的可以去看江科大的视频: 【51单片机入门教程-2020版 程序全程纯手打 从零开始入门】https://www.…...
服务器运维:服务器流量的二八法则是什么意思?
文章目录 用户行为角度时间分布角度应用场景角度 服务器流量的二八法则,又称 80/20 法则,源自意大利经济学家帕累托提出的帕累托法则,该法则指出在很多情况下,80% 的结果是由 20% 的因素所决定的。在服务器流量领域,二…...
高并发秒杀使用RabbitMQ的优化思路
高并发秒杀使用RabbitMQ的优化思路 一、判断是否重复抢购(防止一人多次秒杀)的逻辑1. 整体逻辑代码2. 原始判断重复抢购的方式:3. 后来优化为什么用 Redis 判断? 二、高并发下优化过的秒杀逻辑1.秒杀核心逻辑(请求入口)…...
B + 树与 B 树的深度剖析
在数据库领域,B 树和 B 树是两种极为关键的数据结构,它们对于数据的存储、查询以及索引的构建等方面都有着深远的影响。深刻理解这两种树的原理、特性以及它们之间的差异,对于数据库的性能优化、数据组织和管理等工作具有不可替代的重要作用…...
【LeetCode】嚼烂热题100【持续更新】
2、字母异位词分组 方法一:排序哈希表 思路:对每个字符串排序,排序后的字符串作为键插入到哈希表中,值为List<String>形式存储单词原型,键为排序后的字符串。 Map<String, List<String>> m new Ha…...
赛灵思 XC7K325T-2FFG900I FPGA Xilinx Kintex‑7
XC7K325T-2FFG900I 是 Xilinx Kintex‑7 系列中一款工业级 (I) 高性能 FPGA,基于 28 nm HKMG HPL 工艺制程,核心电压标称 1.0 V,I/O 电压可在 0.97 V–1.03 V 之间灵活配置,并可在 –40 C 至 100 C 温度范围内稳定运行。该器件提供…...
【速写】多LoRA并行衍生的一些思考
迁移学习上的一个老问题,怎么做多领域的迁移?以前的逻辑认为领域迁移属于是对参数做方向性的调整,如果两个领域方向相左,实际上不管怎么加权相加都是不合理的。 目前一些做法想着去观察LoRA权重矩阵中的稠密块与稀疏块࿰…...
探索智能仓颉!Cangjie Magic:码字之间,意境自生
仓颉输入法,对于许多老牌中文使用者来说,不仅仅是一种输入工具,更是一种情怀,一种文化符号。它以拆字为核心,将汉字结构还原成最原始的构件,再通过特定的编码规则进行输入。然而,随着拼音输入法…...
py默认框架和代码
py默认框架 平常工作日常需要频繁写python脚本,留下一个常用的模板 # template.py import logging import json import time import functools import os from typing import Any, Dict, Optional, Union from pathlib import Path from logging.handlers import …...
通过 Samba 服务实现 Ubuntu 和 Windows 之间互传文件
在 Ubuntu 上进行配置 1. 安装 Samba 服务 打开终端,输入以下命令来安装 Samba: sudo apt update sudo apt install samba2. 创建共享目录 可以使用以下命令创建一个新的共享目录,例如创建名为 shared_folder 的目录: sudo m…...
k8s-1.28.10 安装metrics-server
1.简介 Metrics Server是一个集群范围的资源使用情况的数据聚合器。作为一个应用部署在集群中。Metric server从每个节点上KubeletAPI收集指标,通过Kubernetes聚合器注册在Master APIServer中。为集群提供Node、Pods资源利用率指标。 2.下载yaml文件 wget https:/…...
基于外部中中断机制,实现以下功能: 1.按键1,按下和释放后,点亮LED 2.按键2,按下和释放后,熄灭LED 3.按键3,按下和释放后,使得LED闪烁
题目: 参照外部中断的原理和代码示例,再结合之前已经实现的按键切换LED状态的实验,用外部中断改进其实现。 请自行参考文档《中断》当中,有关按键切换LED状态的内容, 自行连接电路图,基于外部中断机制,实现以下功能&am…...
【我的创作纪念日】 --- 与CSDN走过的第365天
个人主页:夜晚中的人海 不积跬步,无以至千里;不积小流,无以成江海。-《荀子》 文章目录 🎉一、机缘🚀二、收获🎡三、 日常⭐四、成就🏠五、憧憬 🎉一、机缘 光阴似箭&am…...
学习笔记——《Java面向对象程序设计》-继承
参考教材: Java面向对象程序设计(第3版)微课视频版 清华大学出版社 1、定义子类 class 子类名 extends 父类名{...... }如: class Student extends People{...... } (1)如果一个类的声明中没有extends关…...
鸿蒙生态新利器:华为ArkUI-X混合开发框架深度解析
鸿蒙生态新利器:华为ArkUI-X混合开发框架深度解析 作者:王老汉 | 鸿蒙生态开发者 | 2025年4月 📢 前言:开发者们的新机遇 各位鸿蒙开发者朋友们,是否还在为多平台开发重复造轮子而苦恼?今天给大家介绍一位…...
如何收集用户白屏/长时间无响应/接口超时问题
想象一下这样的场景:一位用户在午休时间打开某电商应用,准备购买一件心仪已久的商品。然而,页面加载了数秒后依然是一片空白,或者点击“加入购物车”按钮后没有任何反馈,甚至在结算时接口超时导致订单失败。用户的耐心被迅速消耗殆尽,关闭应用,转而选择了竞争对手的产品…...
信号调制与解调技术基础解析
调制解调技术是通信系统中实现基带信号与高频载波信号相互转换的主要技术,通过调整信号特性使其适应不同信道环境,保障信息传输的效率和可靠性。 调制与解调的基本概念 调制(Modulation) 将低频基带信号(如语音或数…...
[PTA]2025 CCCC-GPLT天梯赛 胖达的山头
来源:L2-055 胖达的山头-Pintia题意:给定 n n n 个事件的起始和终止时刻(以hh:mm:ss给出),求最多并行事件数。关键词:差分(签到,模板题)题解:将所有时刻转换为秒,当某事件开始1,结束则-1。按时…...
基于Spring Cloud 2023.0.x + Micrometer Tracing的分布式链路追踪详细解析
前言 在微服务架构中,复杂的调用链路常让问题排查如大海捞针。Spring Cloud 2023.0.x整合Micrometer Tracing,深度支持OpenTelemetry标准,为开发者提供了轻量、高效的分布式链路追踪能力。本文将深入解析从TraceID透传到可视化分析的全流程实现,结合最新技术栈代码…...
【扫描件批量改名】批量识别扫描件PDF指定区域内容,用识别的内容修改PDF文件名,基于C++和腾讯OCR的实现方案,超详细
批量识别扫描件PDF指定区域内容并重命名文件方案 应用场景 本方案适用于以下场景: 企业档案数字化管理:批量处理扫描的合同、发票等文件,按内容自动分类命名财务票据处理:自动识别票据上的关键信息(如发票号码、日期)用于归档医疗记录管理:从扫描的检查报告中提取患者I…...
