Download模块 (十三)

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情
Download模块 (十三)


DownloadTask类封装了一次下载任务的全部信息<M> 以及 真正下载的实现<C>
DownloadTask接收DownloadInfo作为构造参数,并且内部维护一个指向其的引用,作为一个组成部件。


<1>DownloadTask有状态,使用enum类实现:
NOT_START,
IN_PROGRESS,
PAUSED,
FAILED,
COMPLETED,
REMOVED,
DELETED,
FILE_BROKEN;


因为Proxy端对于Download类的status有自己的一套enum,在根据DownloadInfo构造DownloadTask时,会有一个这样的enum转换,
直接将这个转换实现在enum类中了<虽然两套enum的status是一样的,不过出于一种封装性,就分别在Proxy和Service设定了两套冗余的,用一种,意味着两者被紧密耦合了,所以
用两种是比较合适的>


<2>一个DownloadTask还对外开放了一个监听接口,在自己产生各种变化的时候都会通知注册的实现了接口的listener。
一般在通知时都会将Task的id传过去以区分其他task.


<3>DownloadTask内部有一个Downloader类,承担了真正下载的实现,而此Downloader还是一个abstract基类,因为真正Download的实现方式会有不同<比如多线程下载>,
这里采用了一种比较简单的组成方式,每个DownloadTask都一个自己的Downloader<组成>, 这个Downloader是专属于此DownloadTask的,
而对于比较大型的架构,可能不会这么设计,而是做一个单独的DownloadManager,维护一个Downloader池,每个DownloadTask将自己提交到DownloaderManager的任务序列中,
由DownloadManager来负责scheduleDownloadTask何时运行,这样做的好处就是不必频繁的创建销毁资源。 不过在当前的场景中,因为DownloadTask可以继续/暂停,这样要求能够
反向操作Downloader,如果直接占用着Downloader却只是暂停什么都不敢,会造成资源的浪费,如果暂时的将task挪出工作序列,用户点击继续时再挪入工作序列,设计上会比较复杂。
因此对于这样一个小型模块,就采用了简单的一对一组合方式。


<4>实际下载的IO操作是在一个独立的Thread中进行的,而downloader显然应该可以感知某个DownloadThread的状态和变化,因此这里Downloader还要实现DownloadThread开发的监听接口。


<5>因为Downloader是内部类,因此很多外部信息<DownloadInfo>都可以直接引用,不用自己再维护一套,这也是内部类的一个好处。
因为要控制DownloadTask的开始/暂停/停止...,Downloader自然也要有这些流程控制函数, 因为控制的实现会根据 Downloader的实现
而不同,因此,这些函数也是抽象函数。一个已经实现的逻辑是在DownloadThread的Progress发生变化时<显然,进度变化这个事件,对任何
不同下载方式的Downloader来说,应该都是一样的,起码在我们的需求范围内是一样的>,因为回调是在DownloadThread中
发生的,因此所有的处理逻辑要post到MainThread去处理,在这里,直接用DownloadManager调用了DownloadTask的函数,其实更好的做法
是Downloader也开放一个progressChange的接口,在感知到内部Thread的progressChange以后,继续将这个event广播到外部,如何处理event
的逻辑应该在DownloadTask内部<不过因为项目使用的Eventbus都是同步调用,因此没有采用这个做法>。
有一个潜在的issue是在post到MainHandler的runnable中会尝试获取“当前”的网络状况,但是因为是post的,所以并非是一个同步的操作,
这就有潜在的时机不同步。因为progressChange的时刻和runnable真正run的时刻已经不是一个时刻了。
还有一个设计上的问题是在base class的onProgressChange的输入参数已经有了thread的id<这个可以接受,单线程方式也会有一个线程>
,并且后面处理逻辑也包含了对多线程的处理,这是不合适的,baseClass不应该知道这些细节,这些都应该放在子类中,而不是在baseClass这那个做判断分支处理。不过当时没考虑这么细节,并且在设计DownloadInfo时,也在里面泄露了多线程这个底层细节。

<6>MultiDownloader extends Downloader, 顾名思义,它是一个支持多线程下载的Downloader,考虑到并行线程不能太多以及Java本身对
线程使用的意见,使用了ExecutorService这种线程池的方式来进行多线程下载,并且使用了fixedNumber的Executor:
Executors.newFixedThreadPool(fixedNumber),内部还维护了一个DownloadThread列表代表交给Executor运行的任务<Thread也是extends自
Runnable,所以可以当成任务>,一个多线程的Downloader,一开始只会有一个Runnable在运行发动一次http get<或者 http head>,因为不
这么做,是无从知晓要下载的资源是否支持Range下载<能够多线程下载的前提>,以及content-length等信息,所以,必须有一个先行,在得到了这些信息以后再通过回调<listener>启动其他的线程来下载,并且这个thread不需要放在Executor里执行,直接start即可
<MultiDownloader 的start就是先start这个探路者线程>,不过探路者Thread也会被放在Downloader的Task列表中。
MultiDownloader还实现了downloadThread开放的ConnectionListner,以感知探路者线程的变化以及其他后继的DownloadThread。

<7>MultiDownloader的pause是遍历Task转发pasue请求, stop则代表着完全停止取消本次下载,销毁探路者线程获取http resp时的回调
Runnbale,遍历Task停止,并且将Task列表清空,Executor shutdown.

<8>MultiDownloader的resume比较复杂,会分阶段,如果resume的时候,探路者线程取得了http resp,但是没有来的及运行回调,那么就将
回调的runnable取消,并重新post到mainHandler中,

<9>为了统计速度,还会收集整合每个downloadThread在单位时间的下载数据量。

<10>在某个DownloadThread因为某种原因被stop的时候,Downloader会通过listener感知,
如果传入的cause是null,说明该thread是正常的下载完以后stop,并且再检测到所有的downloadThread都已经正常stop了,那么可以认为
Downloader的任务已经完成设定flag并调用专门的收尾函数,否则也会调用收尾函数,不过会附带上cause.注意此回调不是在主线程被调用的。

<11>Downloader的onConnect是在探路者线程收到了http resp的时候被回调的,因为不是在主线程,因此处理逻辑会被封装在Runnable中,post出去,并且此Runnable是否存在也会成为判断探路者是否受到http resp的标志<一个HttpUrlConnection的final输入会封装resp的信息
>。注意因为多线程的原因,Downloader内对此Runnable的引用赋值都要同步。
在这一步,运行此runnable的时候,要检查当前DownloadTask是否是IN_Progress的,如果不是,那说明不能继续下去,因此要直接返回,等到
resume的时候,会将此runnable重新post,而如果进一步发现不是PAUSED,那么说明这次Download应该是被停止撤销了,这时候要将维护的
Runnable的引用设为null,以后不会再被run。
否则就继续下去,从代表resp的httpUrlConnection中得到Etag/Last-Modified/Content-Type等信息,并且还要检测resp的header是否有
Accept-Ranges: bytes, 这样才能支持多线程下载。
下一步是则建立File对象以持久化下载数据, 考虑到断点下载,相应的File文件可能已经存在,这种情况下,已经下载的文件长度可以通过
读取文件的长度得到。有时候DownloadInfo里会已经有下载文件的大小,如果没有,那么用resp里的content-length作为文件总大小。
然后考虑建立子task进行多线程下载,后面还有一步,就是对探路者线程的处理,因为采用的是http get,现在的探路者线程会获得整个文件,
但是其实不需要,只需要重新给探路者线程指定需要下载的数据量就可以,当然,如果只能单线程下载,那就不需要做什么了,
最后把指向此runnbale的引用设为null来防止再次被run。
如果在此回调过程中由于某种原因出现了IOException,那么视为失败。

<12>分配子task进行多线程下载,首先要从DownloadInfo中获取文件的大小,还要考虑到断点下载时已经下载存在磁盘的数据,然后会有一个检
测来判断当前是否使用多线程,如果使用,那么优先使用DownloadInfo中指定的Thread数量,否则使用默认的,线程数量为TN
如果确认要使用多线程,那么就构造一个有TN-1个线程的FixedThreadPool 的Executor<好吧,没有使用Executor少数线程通过调度运行多个
task的作用>,然后结合之前已经下载的数据和从大小为每个thread分配合适的chunk大小,创建Downthread的时候就设置好然后将其加入到
Downloader的Thread list并且用Executor来执行。
最后更新还没有完成工作的Thread的数量为当前使用的线程数量,每一个线程成功结束,该值-1,减为0代表着下载全部结束。

<13> Downloader是被封装在DownloadTask中的,Task的start会转发start到内置组成的Downloader<start的时候才会创建一个
Downloader ,lazy,将Downloader作为内部类的好处就是Downloader可以直接访问DownloadTask的所有成员,如果完全独立出来的话,
getter/setter会有一堆,这是非静态内部类的一个好处>,然后切换自己的状态为IN_PROGRESS<影响M>.

<14>一个downLoadTask被pause可能有两种原因:1 是用户主动UI操作 2是网络发生变化导致,在pause函数中应该体现这个区别,
处于鲁棒性,只有IN_PROGRESS的才可以被pause。DownloadTask内部会维护一个是哪种原因导致pause的flag。

<15>同上resume也会被这两种原因resume,

<16>为了获取当前Task的下载速度,会启动一个Timer来定时的计算刷新当前的下载速度,在某些时候<比如pasue>,不需要测速,可以将Timer
cancel并引用设为null来释放资源。在需要的时候重新new 一个 Timer<Timer cancel以后不可重用>并schedule 一个 TimerTask,
每次计算出最新的speed以后,会更新到Notification并且通知监听速度变化的Listener<V上刷新>,保险起见,在开始会尝试释放一次Timer以避免重复启用。
测速测的是从上一次测速TimerTask被schedule以后到现在的平均速度,每次测速的Timer被取消,都会将已经保存下载数据量的成员变量reset
清0<比如pause一次以后>.
测速Timer只有在DownloadTask状态重新变成IN_PROGRESS以后才会重新被schedule开始测速。

<17>一个Downloader被stop的时候会有一个回调函数调用到downloaderStopped()来由DownloadTask处理遇到的下载问题
,给一个自定义的DownloadStopException的enum Cause作为输入,如果为null,
那么视为一次成功的下载完成,更新结束时间到DownloadInfo,然后设置状态为COMPLETE,停止Downloader,最后做一次完整性检查。
否则根据不同的cause来做不同处理:
case INTERRUPTED: 下载线程被interrupt,一般是由于pause引起的,不需要做什么。
case DOWNLOAD_NEED_RETRY: 需要重试,直接pause,
case NET_IOEXCEPTION:
case NET_WIFI_IS_OFF: 有IO错误或者网络切换到非WIFI,直接pasue
case PRECONDITION_FAILED: 对应http 412, 请求头信息中的某些先决条件是错误的,或者没有满足这种条件的文件存在,这种情况下将Etag
和LastModified设为null<去掉所有前置条件>并且停止Downloader,然后重新开始Task
case RANGE_NOT_SATISFIABLE: 提取的资源不支持Range<可能resp声明支持,但是真正请求的时候收到了错误>,重新下载
case FILE_BROKEN:文件损害,停止下载,并且改变状态到 文件被损害
其他的统一视为下载失败<不可克服的>,标记状态为失败.

<18>文件完整性检查被实现在一个static工具类里,这里要做的只是在完整性检测失败以后以FILE_BROKEN调用downloaderStopped

<19>resume里面隐藏了比较多的细节,如果此次resume是因为网络变化而出发的,那么需要检查当前的网络还将是否允许下载,
而如果Task其实已经完成了,但是在UI上还没有反应出来,那么调用downloaderStopped(null)进行一系列收尾工作以后直接返回。
否则就将当前状态置为IN_PROGRESS, 而如果这时候这个DownloadTask还没有配套的Downloader,那么create一个并开始下载,否则resume Downloader。

<20>贯彻始终的问题是如何保持 M V C的一致性,在琐碎而繁多的成员和复杂的回调以后,很多corner case容易被忽略。
DownloadTask的code很杂乱,一开始设计时很多没有考虑到,后期的不断迭代导致了现在。

还有很多细节被文档所忽略,回头补上。

<21>探路者Thread在onConnect以后回调,因为是在非UI线程,因此需要post一个runnable到UI线程,这个runnable的运行也要考虑到stop resume的影响,

在该Runnable中,在运行到一定完成度会将指向自己的引用设为null来表示onConnect的回调在主线程已经运行完毕,在开发时,当pause的时候对该runnable不进行

干预,因为在下面的DownloadThread里已经实现了,runnable内部也有判断,stop时会将该引用也设为null代表着如果还没有进行onConnect回调,那么就不进行了,而在resume的时候,则要考虑到该runnable被pause的问题,如果runnable没有run完,那么会重新post。

<22>对于HTTP range请求,对端返回200,这说明对端不支持分段下载,那么将此Task的downloader stop掉并将其引用设为null,然后对应的DownloadInfo的threadNum设为-1,并且将原来下载的文件<如果有的话>删除,重新开始下载,直接将这段判断逻辑封装在了resume中,如果resume发现指向Downloader的引用为null,那么就重新开始

一次新的Download,并且将Task的FinsihedContentLength设为0,还是使用此Task对应的DownloadInfo作为Downloader初始化的信息,,其中的判断会因为threadNum为-1

而将startPos设为0<因为文件已经删除,length为0>,而后面的探路者线程再一次取到200时,因为startPos是0,因此会顺利返回到Task的onConnect回调,在分配新线程时,因为200回复周中没有"range"这个header,所以会只使用单线程<这里其实是依赖于server回复的正确,相信server在回200的时候不会带range这个header>,这就实现了对于不支持分段下载的文件的再次重新下载。

<23>一个corner case: 一个文件之前以单线程下载了一部分,然后在这一次恢复下载的时候发现可以多线程下载,那么显然之前下载的部分不必考虑了,也只有这种情况下

才能setMultiThreadStartPos,在其他情况,都认为此


<24>目前虽然在DownloadTaskInfo里保存了threadNum,并且有setter,但是目前其实这个ThreadNum只有两个有效值<1或者3>,这是为了简化逻辑,这样考虑,如果上次是X个线程下载,这次恢复的时候被设置为了Y个线程下载,这种情况下想要实现断点续穿还是很麻烦的,因此就只有两个有效值1或3,如果在可以3的时候,发现之前是1,

那么必然的之前下载的部分一定都是有效的,就在当前没有下载的部分进行分段<chunk = threadNum/unfinsihed, 如果还有余数,那么分出来的 chunk再 +1, 最后一个线程会

以file的end作为结尾>,并且建立一个数组来保存每个线程自己任务的下载量,这样下次可以断点续传了,而如果下次恢复又不幸变为了1个线程,那么只能从最后一次单线程下载的完成点重新开始,之后的所有多线程下载的数据因为不完整就全部废弃了,而如果之前就是1个线程,那么只需要从当前下载File的length开始下载即可。


<25>不管是 真的要range部分 还是 range全部, downloadThread在设置header时都强制使用range header,这是为了照顾探路者线程在初次请求时是请求全部的,也需要强制设上range 0- 来检测是否支持Range.

WeKnora:企业级模块化RAG框架实战指南 RAG(检索增强生成)作为当前知识应用的核心范式,其本质是将非结构化文档转化为可检索、可验证、可审计的语义知识流。实现稳定RAG的关键不在大模型本身,而在于文档解析的结构性保真、向量检索的故障隔离能力与全链路可观测性设计。WeKnora以六边形架构和OpenTelemetry原生支持,将解析、分块、嵌入、重排、生成等环节解耦为可插拔模块,显著提升医疗、金融等强合规场景下的术语一致性与溯源准确率。它不追求‘一键跑通’,而是提供私有化部署就绪的标准化制品与CLI离线预处理能力,真正支撑起对数据主权、响应延迟与十 阅读详情

相关推荐

Turtlebot3 PC端设置:ROS 2 Humble+Ubuntu 22.04系统级校准指南

机器人操作系统(ROS)是构建自主移动机器人感知-决策-执行闭环的核心框架,其本质是一套面向实时性、分布式与硬件抽象的中间件架构。ROS 2基于DDS通信机制,强调节点发现、时间同步与TF坐标系一致性,技术价值在于支撑SLAM建图、导航控制与多传感器融合等高阶功能。典型应用场景包括高校机器人教学、算法在真实硬件上的验证,以及服务机器人原型开发。然而,环境配置失败常导致TF树断裂、激光数据丢失或里程计漂移等现象,根源多在于Ubuntu系统层驱动兼容性、串口权限与固件协议匹配问题。本文聚焦Turtlebot3在

weixin_30509393的博客 487

Python及其常用模块库下载及安装

作者:LP帅气的昵称 链接:http://www.jianshu.com/p/52eb3df32222 來源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 一、Python下载:https://www.python.org/downloads/ 二、Python模块下载:http://www.lfd.uci.edu/~gohlke/pyth

猎摘互联网软件测试业界技术文章专用博客 5962

【信息科学与工程学】【通信工程】【云骨干网】第一百三十三篇 智算中心互联之云骨干网01

传统网络方向(如硬件防火墙、交换机)和安全产品方向(如传统防病毒软件)并未“完全”进入衰退期,但已处于深度转型阶段。市场对基础能力的需求仍在,但技术形态、价值重心和竞争逻辑已发生根本变化。

weixin_49199313的博客 1102

DownLoad模块 ()

DownLoad模块 (一) DownLoad abstarct Class 实现了Comparable: 这个类的定位基本就是该次DownLoad所有信息的聚合体,并且作为一个基类的角色存在, 在抽象上就是一个DownLoad。 虽然倾向于一个纯粹的数据结构,不过还是定义了一些行为. 在MVC中的角色是 一个 重M+轻C 并且其外部行为也就是发出一个事件,然后其他的Listene

fyfcauc的专栏 649

Downloader模块管理网络文件下载任务

Downloader模块管理网络文件下载任务

casey的博客 3099

Download模块 ()

Download模块 () FullscreenFragment类,基础类, 如同其名,是一个会占据APP整个UI的fragment,注意这里的Fullscreen和系统没有任何关系, 并不是用了什么trick来隐藏系统的状态栏使得fragment占据整个屏幕,Fullscreen的意义只是对app本身来说的。 在设计APP的整体layout时,用FrameLayout将所有的View

fyfcauc的专栏 511

Android系统Download模块

一、Download模块简介##首先,Android Download模块主要由两个部分构成:DownloadManager以及DownloadProvider。 DownloadManager:在Android L/M源码位置:frameworks/base/core/java/android/app/DownloadManager.java作用:提供接口供三方apk调用DownloadMan...

heng615975867的专栏 3603

WDA基础十三:常用模板管理

常用的模板一般是SMW0和OAOR,根据不同需求来的。 WAD有个不好的地方就是不支持GUI上的OLE和DOI,所以需要做转换,下面是常用的方式: FUNCTION ZCRM_DOWNLOAD_TEMPLATE *"---------------------------------------------------------------------- *"*"Local Inter...

weixin_30781433的博客 199

Scrapy爬虫入门教程十三 Settings(设置)

Scrapy爬虫入门教程一 安装和基本使用  Scrapy爬虫入门教程二 官方提供Demo  Scrapy爬虫入门教程三 命令行工具介绍和示例  Scrapy爬虫入门教程四 Spider(爬虫)  Scrapy爬虫入门教程五 Selectors(选择器)  Scrapy爬虫入门教程六 Items(项目)  Scrapy爬虫入门教程七 Item Loaders(项目加载器)  Scra...

__静禅__ 4566

2026华数杯B题深度思路解析:VLSI布图规划如何从“面积最小”一路做到“极限死区与异形模块优化”

2026华数杯B题并不是一道简单的“矩形排布题”。 它真正考察的是: 如何设计一种能够在不同约束不断加入时仍然可以持续扩展的Floorplanning框架。 这套方案的核心不是某一个复杂公式,而是一种很清晰的分层思想: 用B*树压缩布局搜索空间; 用轮廓线快速恢复无重叠布局; 用Fast-SA在排列、移动和旋转组合空间中寻找高质量方案; 问题一用面积优先的评价解决自由轮廓; 问题二通过自适应可行率反馈解决固定轮廓HPWL; 问题三利用死区可行性的单调性进行二分逼近; 问题四只升级底层几何

513

【Python】python新手保姆级教程------第 10 章 模块与包

Python 中的模块分为三类,分别是:标准库模块、自定义模块、第三方模块模块命名注意点::::info:::创建如下文件: 模块中包含:创建订单、关闭订单,相关的功能函。 模块中包含:微信支付、支付宝支付,等相关功能。 导入模块 1️⃣import 模块名 如下写法,会导入模块,和模块的全部内容,又叫**全部导入,**是最简单的导入方式 。 :::color4 注意:::: 3️⃣from 模块名 import 具体内容1, 具体内容2, … 4️⃣from 模块名 import 具体内容 as 别名

朱朱博客,分享技术 363

Convolutional neural networks(CNN) (十三) Convolutional Neural Network Exercise

{作为CNN学习入门的一部分,笔者在这里逐步给出UFLDL的各章节Exercise的个人代码实现,供大家参考指正} 基于 Convolutional neural networks(CNN) (十二) Convolutional Neural Network Theory 理论分析,对 Exercise: Convolutional Neural Network进行了MatLab实现,同

ColdCode's Hello World~ 1183

搭建数字图像处理开发环境(Python+pycharm+opencv)

搭建数字图像处理开发环境(Python+pycharm+opencv)安装python安装pycharm安装包pycharm安装过程

3007

【信息科学与工程学】计算机科学与自动化 第二百三十三篇 凤凰架构02

① Firecracker 为每个 microVM 提供极简设备模型:virtio-net, virtio-blk, serial console。:① BB84:Alice 随机选择基矢(+ 或 ×)发送单光子,Bob 随机测量基矢,比对后保留一致位的子集作为原始密钥。:密度 1 EB/mm³;:① Parquet 文件:行组(Row Group)→ 列块(Column Chunk)→ 页(Page)。

weixin_49199313的博客 103

技术文档Introduction的五层认知契约设计方法

Introduction不是开场白,而是技术内容交付的第一道认知关卡。它本质是读者与作者之间建立的隐性契约,涉及场景唤醒、价值具象化、能力映射、结构透明化和风险预披露五大原理。从神经科学的具身认知到工程实践中的信息密度控制,其技术价值在于降低用户决策成本、提升操作掌控感与任务完成率。广泛应用于硬件教程、开源文档、AI教学、嵌入式开发等需强实操引导的场景。本文聚焦‘认知锚点’与‘预期管理’两大核心热词,提供可测量、可验证、可迭代的Introduction构建范式。

weixin_33681778的博客 319

技术实战:从零构建本地化AI语音合成系统

在AI技术快速发展的今天,语音合成已成为内容创作、无障碍服务和智能交互的核心技术。ChatTTS-ui项目为我们提供了一个完整的本地化语音合成解决方案,通过简洁的Web界面和强大的API接口,让开发者能够快速构建专属的AI配音助手。本文将深入探索该项目的技术架构、实现原理和扩展应用,为技术爱好者和开发者提供全面的技术指导。 ## 探索之旅:ChatTTS-ui的技术全景 ChatTTS-ui作

gitblog_00190的博客 781

【信息科学与工程学】计算机科学与自动化 第二百三十三篇 凤凰架构01

编号类型领域系统软件/硬件或系统集成方案及系统架构模块模块函数名称/算法名称算法的数学分析及计算机体系架构及数据结构与算法详细设计及参数列表及数值范围设计关联知识1共识算法分布式基石状态机复制架构:Proposer/Acceptor/Learner 多节点角色模型,基于操作转移(Operation Transfer)分布式共识PaxosPaxos 协议(Prepare/Promise/Accept/Accepted 两阶段)数学基础:多数派(Quorum)投票,基于鸽巢原理保证 W+R>N 时读写集合必有

weixin_49199313的博客 185

PX4装机教程(八)常用硬件

PX4 1.13.0 PIXHAWK4超声波传感器为淘宝上类似下图这种 将超声波连接到飞控的IIC口 将参数,然后重启飞控 在Mavlink控制台输入 输出如下打印信息说明连接正常 在地面站上添加以下测距传感器的显示 可以在地面站上看到超声波测得的距离 选用的是北醒的tfmini plus 将tfmini plus接到飞控的串口上,我这里接在uart4上 将SENS_TFMINI_CFG设置为自己连接的串口 将改成传感器的波特率(默认是115200) 修改完参数重启飞控。 然后在QGC上添加测

超维空间科技的博客 1万+
上一篇: HTTP 412
下一篇: Download模块 (十四)
fyfcauc
博客等级 码龄12年 153粉丝 426原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值