全面掌握ONVIF协议的客户端与服务器源码实例
本文还有配套的精品资源,点击获取
简介:ONVIF是一个用于网络视频监控设备互操作性的开放标准,定义了设备和服务之间的交互方式。本实例源码包括客户端和服务器端,涵盖了设备发现、音视频流管理、事件通知、安全性和PTZ控制等方面,帮助开发者深入理解ONVIF协议,并应用到视频监控系统开发中。源码文件包含 read.txt
文档和 discovery_cs
代码,分别提供了使用说明和设备发现功能实现。
1. ONVIF协议概述
随着网络技术的飞速发展,安防监控系统逐渐转向网络化、智能化。ONVIF (Open Network Video Interface Forum) 协议应运而生,它为不同厂商的网络视频设备间提供了标准化的接口。本章节将对ONVIF协议的基础知识进行介绍,包括其发展背景、核心特点及应用场景。
1.1 ONVIF的发展背景与意义
ONVIF是由Axis Communications、Bosch Security Systems和Sony共同发起成立的一个开放接口标准化组织。它的目标是简化和促进IP监控产品的互操作性,允许用户在不同品牌和制造商之间进行选择,而不必担心产品间的兼容性问题。ONVIF协议的出现大大降低了用户选择与集成成本,推动了安防监控市场的进一步发展。
1.2 ONVIF的核心特点
ONVIF定义了一系列标准接口,涉及设备发现、配置、实时视频流传输、PTZ控制等多个方面。其核心特点包括:
- 互操作性 :确保不同厂商设备间的兼容与通信。
- 标准化 :提供一套标准化的协议规范,简化开发和集成。
- 开放性 :允许第三方软件和设备集成到现有的监控系统中。
1.3 ONVIF的应用场景
ONVIF协议广泛应用于安防监控、智能家居、远程医疗等地方,为这些领域的集成和扩展提供了极大的便利。无论是在小型办公室还是大型工业环境中,ONVIF标准化的接口都能够让设备与系统更加无缝地协同工作。在下一章节中,我们将深入了解ONVIF的设备管理功能,并探讨如何实现这些功能。
2. 设备管理功能实现
2.1 设备发现与信息获取
2.1.1 使用ONVIF协议进行设备发现
ONVIF设备发现是网络视频系统集成中的关键步骤。通过使用ONVIF协议,可以发现网络中的支持ONVIF协议的设备。设备发现通常涉及发送一个多播或广播的探测消息(Probe),以识别所有活跃的ONVIF设备。这些设备会响应探测消息,从而实现发现过程。
在具体实施时,开发者通常使用现成的库如 onvif
库(适用于Python)来进行设备发现。以下是一段简化的Python代码段,展示了如何使用该库进行设备发现:
from onvif import ONVIFCamera# 创建一个ONVIFCamera对象用于设备发现camera = ONVIFCamera()# 执行设备发现过程devices = camera.discover()for device in devices: print(f\"Found device: {device}\")
此代码段创建了一个 ONVIFCamera
对象,该对象能够发送探测消息,并接收网络上ONVIF设备的响应。每个响应包含了一个设备的详细信息,包括设备的名称、模型、以及设备的唯一标识符。
2.1.2 设备信息的获取与解析
设备被发现之后,接下来需要获取设备的详细信息。这些信息通常包括设备的生产能力、配置能力、当前状态等。通过发送GetDeviceInformation请求,可以获取这些信息。这些信息是理解设备功能和性能的基础。
继续使用Python代码示例,展示了如何获取设备信息:
# 选择一个已发现的设备device = devices[0]# 获取设备信息device_info = device.get_device_information()print(f\"Device Information: {device_info}\")
此段代码选择了一个发现的设备,并调用 get_device_information
方法获取并打印了该设备的信息。获取的设备信息通常被组织在一个结构化数据类型中,例如字典或对象,以便于进一步解析和使用。
2.2 设备配置与管理
2.2.1 配置设备参数的方法
配置ONVIF支持的设备通常涉及设置网络参数、更改系统名称和描述等。开发者可以使用GetSystemDateAndTime、SetSystemDateAndTime、GetSystemFactoryDefault、SetSystemFactoryDefault等ONVIF操作来管理系统信息。
以下代码示例演示了如何获取和设置设备的系统日期和时间:
from datetime import datetime# 获取系统日期和时间system_date_and_time = device.get_system_date_and_time()print(f\"System Date and Time: {system_date_and_time}\")# 设置系统日期和时间new_time = datetime.now().strftime(\"%Y:%m:%d %H:%M:%S\")device.set_system_date_and_time(new_time)
2.2.2 设备固件更新流程
固件更新是设备管理中的重要环节。ONVIF提供了固件更新的操作,通过GetFirmwareInformation、GetFirmwareUpgrade和SetFirmwareUpgrade等命令实现。固件更新过程通常包括查询当前固件信息、下载新的固件到设备中,以及实际升级过程。
2.2.3 设备的注册和注销机制
设备注册机制确保了只有授权的客户端可以管理和访问设备。而注销机制则用于移除那些不再被授权的客户端。通过GetProfiles、SetScopes、CreateScopes、GetScopes、DeleteScopes等操作可以实现设备的注册和注销。具体流程如下:
- 使用
GetProfiles
命令获取设备当前的配置。 - 使用
SetScopes
命令更新客户端的访问权限。 - 通过
CreateScopes
和DeleteScopes
命令管理权限范围。
表格示例:ONVIF设备管理命令及描述
| 命令 | 描述 | |-----------------|------------------------------------------------| | GetProfiles | 获取设备支持的配置文件列表 | | SetScopes | 设置设备的访问权限范围 | | CreateScopes | 创建新的访问权限范围 | | DeleteScopes | 删除现有的访问权限范围 | | GetScopes | 获取当前定义的访问权限范围 |
mermaid流程图:设备注册流程图
graph TD; A[开始] --> B[获取设备配置]; B --> C{设备是否需要注册?}; C -->|是| D[执行SetScopes]; D --> E[创建访问权限范围]; E --> F[更新设备配置]; F --> G[设备注册完成]; C -->|否| G; G --> H[结束];
以上章节内容详细介绍了设备发现与信息获取的步骤和方法,以及设备配置与管理的各个方面。这些内容对于IT行业和相关行业的从业者来说具有相当的深度和实用性,特别是对于那些需要进行网络视频设备集成的开发者和技术人员而言,这一章节将提供非常有价值的指导和帮助。
3. 音视频流传输与控制
在视频监控系统中,音视频流的实时获取与传输是一项核心功能。随着网络技术的发展,如何高效地传输音视频数据成为了开发者关注的焦点。本章节主要探讨音视频流的获取、传输技术以及对流媒体的实时控制和同步处理策略。
3.1 音视频流的获取与传输
3.1.1 获取音视频流的技术途径
音视频流的获取是通过摄像头等设备捕获的。在数字化的视频监控系统中,摄像机捕获的模拟信号需要经过编码器转换为数字信号,才能进行网络传输。目前,常用的编码格式有H.264、H.265等,它们能够在保证视频质量的同时减少数据量。
获取音视频流的途径主要有以下两种方式:
- 直接获取 :通过访问摄像机的接口,如RTSP URL,直接获取流媒体数据。通常需要对音视频编码进行解码才能使用。
- 间接获取 :使用服务器或者流媒体服务器作为中转,摄像机将音视频流发送给服务器,然后客户端从服务器获取音视频流。
3.1.2 RTSP协议在流媒体中的应用
实时流协议(Real Time Streaming Protocol,RTSP)是一种网络控制协议,用于控制流媒体服务器的播放、暂停等操作。RTSP工作在应用层,独立于传输层,因此它并不直接传输数据,而是通过控制信息使音视频流正确传输。
使用RTSP协议实现音视频流传输通常涉及以下几个步骤:
- 建立会话 :客户端发送OPTIONS请求来查询服务器支持的功能。
- 描述媒体 :客户端通过DESCRIBE请求获取媒体传输信息。
- 设置会话 :客户端发送SETUP请求来建立传输通道。
- 播放流媒体 :客户端发送PLAY请求来接收流媒体数据。
- 停止传输 :客户端发送TEARDOWN请求停止流传输。
3.1.3 实现音视频流获取的代码示例(Python)
import rtspdef play_rtsp_stream(rtsp_url): # 实例化RTSP客户端 client = rtsp.Client(rtsp_url) # 建立连接 client.connect() # 播放 client.play() # 保持连接 while client.is_playing(): data = client.read(1024) if data: # 处理音视频数据 process_frame(data) else: break # 停止播放 client.stop() # 关闭连接 client.close()def process_frame(data): # 这里可以添加解码数据、显示画面等代码 pass# 示例使用的RTSP URLrtsp_url = \'rtsp://192.168.1.100/media/stream\'play_rtsp_stream(rtsp_url)
在上述代码中,我们创建了一个RTSP客户端,并通过一系列方法建立了连接、开始播放,并读取数据。实际应用中,需要对读取的数据进行解码和显示。
3.1.4 音视频同步处理策略
音视频同步是音视频流传输中的一大挑战。由于音视频数据在网络传输中可能存在时延差异,因此需要同步机制来确保播放时声音和画面的一致性。
同步处理策略通常包含:
- 时间戳同步 :为音视频数据包附加时间戳,播放端根据时间戳进行同步播放。
- 缓冲处理 :在播放端引入缓冲区,通过调整缓冲时间来缓解时延差异。
- 动态调整播放速率 :如果发现音频和视频之间的差异超过一定阈值,则动态调整播放速率。
3.2 音视频流的实时控制
3.2.1 实时播放控制的实现方法
实时播放控制通常包括启动播放、暂停、停止等操作。开发者可以在客户端程序中为用户提供播放控制的界面,并通过RTSP协议与服务器进行交互。
使用Python进行RTSP播放控制的代码示例如下:
def pause_rtsp_stream(rtsp_client): rtsp_client.pause()def stop_rtsp_stream(rtsp_client): rtsp_client.stop() rtsp_client.close()
3.2.2 音视频同步处理策略的代码实现
音视频同步可以通过在播放端添加缓冲来实现。以下是一个简单的音视频同步处理逻辑的伪代码:
# 假设audio_buffer和video_buffer分别是音频和视频的缓冲区audio_buffer = []video_buffer = []def add_to_audio_buffer(data, timestamp): audio_buffer.append((timestamp, data))def add_to_video_buffer(data, timestamp): video_buffer.append((timestamp, data))def sync_play(): audio_time = 0 video_time = 0 while True: if audio_buffer and audio_buffer[0][0] <= video_time: _, audio_data = audio_buffer.pop(0) play_audio(audio_data) audio_time += 100 # 假设每次播放间隔为100毫秒 if video_buffer and video_buffer[0][0] <= audio_time: _, video_data = video_buffer.pop(0) play_video(video_data) video_time += 33 # 假设30fps视频每帧间隔为33毫秒 # 如果缓冲区为空,则等待新数据 if not audio_buffer and not video_buffer: break
在实际开发中,还需要考虑网络状况、缓冲区大小等多种因素,来动态调整播放策略。
3.2.3 实时控制效果的测试与验证
测试实时控制效果通常需要模拟多种网络条件,以确保在不同的网络状况下,控制命令都能被准确无误地传达和执行。此外,还需要对音视频同步性能进行评估,确保在各种延迟和丢包场景下,音频和视频都能较好地同步。
以上是第三章的内容概述。在接下来的章节中,我们将深入探讨事件通知机制、安全性措施、PTZ控制功能以及客户端与服务器端的代码实现。每一部分都将遵循由浅入深的原则,结合具体的技术细节,为读者提供详实的分析和实操指导。
4. 事件通知机制
4.1 事件订阅与通知模型
ONVIF事件模型概述
ONVIF事件模型是ONVIF协议中非常重要的一个组成部分。它允许客户端订阅特定类型的事件,并在这些事件发生时接收通知。这些事件可能包括图像移动侦测、设备离线或警报触发等。
事件订阅模型通过定义一系列消息类型来工作,其中包括 Subscribe
、 Unsubscribe
、 Notification
、 Renew
和 Event
。在事件订阅过程中,客户端可以指定感兴趣的事件类型以及通知的回调地址。设备将根据这些信息,定时或在事件发生时,通过推送或拉取方式向客户端发送通知。
实现事件订阅的步骤与方法
实现事件订阅的过程可以分为以下步骤:
- 发现服务 : 通过之前的设备发现过程,找到支持事件通知的设备和服务。
- 认证 : 使用设备支持的认证方式完成客户端与设备之间的认证。
- 订阅 : 向设备发送
Subscribe
消息,指定感兴趣的事件类型和通知的回调地址。 - 接收通知 : 设备会在指定事件发生时通过RTSP协议向客户端发送
Notification
消息。 - 续订或取消 : 通过发送
Renew
消息来延长订阅的有效期,或发送Unsubscribe
消息取消订阅。
下面是一个简化的代码示例,展示了如何使用Python进行事件订阅:
import requestsfrom onvif import ONVIFCameracamera = ONVIFCamera(\'http://摄像机IP:端口\', \'用户名\', \'密码\')camera.create_profile(\'MyProfile\') # 通常需要创建一个配置文件# 订阅事件event_service = camera.create_service(\'Event\')subscription = event_service.CreatePullPointSubscription( { \'Duration\': \'PT300S\', # 订阅时长为5分钟 \'Types\': \'myEventTypes\', # 指定感兴趣的事件类型 \'Callback\': \'http://客户端回调地址\' # 设置回调地址 })# 接收事件通知while True: notifications = event_service.GetPullPointNotifications( subscriptionReference=subscription[0][\'SubscriptionReference\'] ) for notification in notifications: # 这里处理事件通知数据 pass# 取消订阅event_service.StopPullPointSubscription(subscription)
这段代码首先创建了一个ONVIF摄像机对象,并创建了一个配置文件。然后使用 Event
服务创建了一个订阅,并通过循环接收事件通知。最后,如果不再需要接收通知,则通过调用 StopPullPointSubscription
方法取消订阅。
4.2 事件处理与响应
事件数据的解析与处理
事件数据通常以XML格式发送。客户端接收到事件通知后,首先需要解析XML数据,然后根据事件内容执行相应的处理逻辑。例如,对于一个图像移动侦测事件,客户端可能需要启动录像或发送警报。
下面是一个处理事件通知的示例:
def handle_event(event_xml): # 使用解析器解析XML event_notification = ET.fromstring(event_xml) # 提取事件相关数据 event_type = event_notification.find(\'NotificationMetadata/EventType\').text camera_id = event_notification.find(\'Source/Token\').text # 根据事件类型进行处理 if event_type == \'MyEventType\': # 图像移动侦测事件处理 handle_motion_detection(camera_id)def handle_motion_detection(camera_id): # 启动录像或发送警报等 print(f\"启动录像: {camera_id}\")# 示例的事件XMLevent_xml_example = \"\"\" 摄像机ID 图像移动侦测 Event 2023-04-01T12:00:00Z \"\"\"handle_event(event_xml_example)
在这个例子中, handle_event
函数接收一个XML格式的事件通知。使用 ElementTree
模块解析XML,提取事件类型和源设备的标识。如果事件类型与图像移动侦测匹配,则调用 handle_motion_detection
函数进行处理。
事件驱动架构的设计与实现
在系统设计中,事件驱动架构是一种常见的设计模式。在这种架构中,事件订阅者不需要轮询或请求新的事件,而是被动地接收事件通知。系统中的组件通过这些事件进行通信和协作。
下面是一个简单的mermaid流程图,展示了事件驱动架构的基本工作流程:
graph LR A[监控系统] -->|订阅| B(事件管理器) B -->|事件A| C[事件处理程序1] B -->|事件B| D[事件处理程序2] B -->|事件C| E[事件处理程序3]
- 监控系统 发起事件订阅请求。
- 事件管理器 负责接收事件并根据事件类型分发给相应的 事件处理程序 。
- 每个 事件处理程序 根据自己的业务逻辑对事件进行处理。
事件驱动架构的关键在于如何设计事件管理器和事件处理程序,以及如何高效地进行事件分发。事件管理器需要能够处理大量的并发事件,并确保事件能够及时准确地送达对应的处理程序。此外,事件处理程序应该设计得轻量级,以减少单个事件处理的延迟。
在实际实现中,还需要考虑如何优化事件的存储和检索,以及如何在高负载情况下保持系统的稳定性和可扩展性。这些设计上的考量直接影响到系统对复杂事件处理的响应能力和可靠性。
通过以上的内容,第四章介绍了ONVIF协议中事件通知机制的工作原理和实现方法,以及如何在系统设计中应用事件驱动架构来提高效率和响应速度。
5. 安全性措施
5.1 认证与授权
5.1.1 ONVIF安全认证机制
ONVIF协议为了确保设备间通信的安全性,提供了一套完整的认证机制。在ONVIF协议栈中,认证主要是通过WS-Security标准实现的,它使用了用户名/密码、X.509证书等方法来保证消息交换的安全性。安全认证流程通常是客户端发送一个认证请求到服务器,服务器验证认证信息,如果信息正确,服务器会返回一个安全令牌,客户端后续的请求都将携带这个令牌进行验证。
为了理解该认证机制,可以查看以下步骤:
- 初始化通信 :客户端与服务器建立连接。
- 发送认证请求 :客户端向服务器发送包含认证信息的请求。
- 认证信息验证 :服务器验证客户端提供的认证信息。
- 返回安全令牌 :认证成功后,服务器提供一个安全令牌。
- 后续请求 :客户端使用安全令牌对后续的请求进行签名。
这里使用用户名和密码的认证过程的代码示例:
import requestsfrom requests.auth import HTTPBasicAuth# 指定ONVIF设备的地址和端口onvif_device = \'http://192.168.1.100:80/onvif/device_service\'username = \'admin\'password = \'password\'# 发送认证请求response = requests.post(onvif_device, auth=HTTPBasicAuth(username, password))# 检查响应if response.status_code == 200: # 认证成功,后续的请求需要带上返回的头信息 token = response.headers[\'Token\'] # 在此带上token进行后续的请求...else: print(\'Authentication failed\')
在实际应用中,需要注意保护好认证信息,避免泄露。同时,还需要定期更新密码以保证安全性。
5.1.2 设备与用户的授权流程
授权流程是指在用户成功认证后,系统根据用户的角色或权限,对其能够访问的资源进行限制。ONVIF协议同样支持基于角色的访问控制,设备能够定义不同的用户角色,并将相应的权限分配给不同的角色。
实现授权流程的关键步骤如下:
- 定义用户角色 :在ONVIF设备上配置用户角色,并定义相应的权限。
- 用户分配 :将用户分配给相应的角色,确保用户拥有该角色的权限。
- 权限检查 :在用户请求访问资源时,系统会检查用户的角色和权限。
- 访问控制 :如果用户具有相应的权限,请求将被允许;否则,将拒绝访问。
以下是一个简化的授权流程示例代码:
# 假设服务器端已经通过某个机制为用户分配好了角色和权限# 用户角色定义user_roles = { \'admin\': [\'read\', \'write\', \'control\'], \'viewer\': [\'read\']}# 权限检查函数def check_permissions(user_role, required_permission): if required_permission in user_roles.get(user_role, []): return True else: return False# 模拟用户请求user_role = \'viewer\'requested_resource = \'video_feed\'# 检查用户是否有权限访问请求的资源if check_permissions(user_role, requested_resource): print(f\"User role \'{user_role}\' is permitted to access \'{requested_resource}\'\")else: print(f\"User role \'{user_role}\' is not permitted to access \'{requested_resource}\'\")
该代码段仅作为一个逻辑示例,实际上ONVIF的授权流程会嵌入到设备服务中,通过更为复杂的安全策略来实现。
5.2 加密传输与数据保护
5.2.1 SSL/TLS在ONVIF中的应用
为了保证数据在传输过程中的安全,ONVIF推荐使用SSL/TLS协议进行加密通信。使用SSL/TLS可以有效防止数据被窃听和篡改。在ONVIF通信中,SSL/TLS可以通过如下方式实现:
- 端口的选择 :ONVIF推荐使用443端口(HTTPS默认端口)进行通信。
- 证书的使用 :服务器需要有有效的SSL证书,并且客户端需要信任该证书。
- 握手过程 :客户端和服务器端之间进行SSL握手,建立加密通道。
- 数据加密传输 :一旦握手成功,后续的数据传输都将被加密。
下面是一个使用Python的 requests
库通过HTTPS(SSL/TLS)发送请求的简单示例:
import requests# ONVIF服务的HTTPS地址onvif_service_url = \'https://192.168.1.100/onvif/device_service\'# 发送HTTPS请求response = requests.get(onvif_service_url)# 检查响应if response.status_code == 200: print(\'SSL/TLS connection successful\')else: print(\'Failed to establish a secure connection\')
在实际使用中,需要确保服务器证书有效,并且客户端程序正确处理证书验证过程。
5.2.2 传输过程中的数据加密与解密
传输过程中的数据加密与解密主要涉及到SSL/TLS握手过程中协商的密钥。一旦加密通道建立,数据就会通过这个通道发送。数据发送方会使用会话密钥对数据进行加密,而数据接收方则会使用同样的会话密钥对数据进行解密。
数据加密通常包含以下步骤:
- 密钥交换 :客户端和服务器通过非对称加密算法交换用于生成会话密钥的密钥材料。
- 会话密钥生成 :客户端和服务器各自生成会话密钥。
- 数据加密 :使用会话密钥对数据进行对称加密。
- 数据传输 :加密后的数据通过网络传输到接收方。
- 数据解密 :接收方使用会话密钥对数据进行对称解密。
以下是一个使用伪代码展示数据加密和解密过程的示例:
# 伪代码展示加密过程def encrypt_data(data, session_key): # 使用对称加密算法(如AES)和会话密钥加密数据 encrypted_data = symmetric_encrypt(data, session_key) return encrypted_data# 伪代码展示解密过程def decrypt_data(encrypted_data, session_key): # 使用对称加密算法(如AES)和会话密钥解密数据 data = symmetric_decrypt(encrypted_data, session_key) return data
在实际应用中,数据加密和解密过程会依赖于具体的加密库和算法,例如Python中的 cryptography
库或 PyCryptodome
库。
通过上述章节的探讨,我们可以看到ONVIF协议在安全性措施方面的完整构架,从认证与授权的机制到加密传输和数据保护的实现,都是为了在提高网络监控系统集成的便利性的同时,确保通信的安全性。这些措施对于保障视频监控系统中的数据完整性、保密性和可用性至关重要。
6. PTZ控制功能
6.1 PTZ控制原理与方法
PTZ代表平移(Pan)、倾斜(Tilt)、变焦(Zoom),这些控制动作是通过控制相机上相应电机来实现的。PTZ控制通常用于监控摄像头,允许操作者远程调整摄像头的位置和视场。
6.1.1 PTZ设备概述
PTZ摄像头比静态摄像头有更大的灵活性,可以覆盖更广阔的区域,允许用户通过软件远程控制摄像头的方向和焦距。PTZ控制通常通过网络发送控制命令,这些命令格式遵循特定的协议标准,如ONVIF协议。
6.1.2 控制命令的发送与执行
发送PTZ控制命令需要遵循ONVIF协议的规范。这些命令通过SOAP消息封装,并通过HTTP协议发送给PTZ设备。设备接收到命令后解析出对应的控制指令,然后驱动电机进行相应的动作。
6.2 PTZ控制实例分析
本节通过代码示例和控制效果的测试来分析PTZ控制功能。
6.2.1 PTZ控制代码示例
以下是一个简单的SOAP消息示例,用于向PTZ设备发送一个向右平移的命令。
POST /onvif/device_service HTTP/1.1Host: 192.168.1.100Content-Type: text/xml; charset=utf-8SOAPAction: \"http://www.onvif.org/ver10/ptz/action/move\" profile_token_value 0.0 0.0 PT3S
在上述示例中, ProfileToken
表示控制哪个摄像机配置文件, Velocity
表示控制的速度, Timeout
表示命令执行的超时时间。
6.2.2 控制效果的测试与验证
为了验证PTZ控制命令的效果,可以使用网络抓包工具(例如Wireshark)来监控SOAP消息的传输。在发送控制命令之后,观察摄像头是否按照预期进行平移或倾斜动作。此外,可以记录操作前后的摄像头位置信息来验证命令的具体效果。
通过不断调整和测试,可以确保PTZ设备的控制精度和响应时间达到系统要求。对于复杂的控制场景,可能需要编写更多的控制逻辑,如自动追踪特定物体或人物等。
本文还有配套的精品资源,点击获取
简介:ONVIF是一个用于网络视频监控设备互操作性的开放标准,定义了设备和服务之间的交互方式。本实例源码包括客户端和服务器端,涵盖了设备发现、音视频流管理、事件通知、安全性和PTZ控制等方面,帮助开发者深入理解ONVIF协议,并应用到视频监控系统开发中。源码文件包含 read.txt
文档和 discovery_cs
代码,分别提供了使用说明和设备发现功能实现。
本文还有配套的精品资源,点击获取