Retrofit的简单封装

总结一下自己对Retrofit的使用,使用办法延续之前封装Volley的使用方法,不好的地方希望看到各位能给予宝贵的建议,谢谢

首先来看看Retrofit的使用

网络上有很多的关于Retrofit的使用的文章,我学习Retrofit使用也是通过网上查找文章
我所参考的原文地址为: http://www.tuicool.com/articles/AveimyQ
1、添加依赖或者导入Jar包
依赖的添加:
compile 'com.squareup.retrofit2:retrofit:2.1.0'  
你也可以导入最新的依赖版本
至于Jar,之后我会随Demo一起上传
2、创建业务请求接口
public interface MyRetrofitService {
    //最简单的Get请求,参数以map形式传递
    @GET()
    Call<BookSearchResponse> getService (@Url String url,@QueryMap Map<String,String> Para);
}
3、创建一个Retrofit实例
Retrofit retrofit = new Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create())
                .baseUrl(IP)
                .build();
4、调用请求方法,得到Call的实例,并完成异步请求
BlueService service = retrofit.create(BlueService.class);
Call<BookSearchResponse> call = mBlueService.getSearchBooks("小王子", "", 0, 3);
call.enqueue(new Callback<BookSearchResponse>() {
@Override
public void onResponse(Call<BookSearchResponse> call, Response<BookSearchResponse> response) {
	asyncText.setText("异步请求结果: " + response.body().books.get(0).altTitle);
}
@Override
public void onFailure(Call<BookSearchResponse> call, Throwable t) {
}
});
以上4个步骤,就可以完成的Retrofit的基本的网络请求。
但是总感觉使用起来不方便,而且回调里返回BooSearchResponse这个bean对象好像只能应对与简单的Json格式,当公司的接口返回的Json格式复杂的时候,我还是希望将Json的解析与网络的请求返回的数据进行分离,所以重写重写Retrofit的解析器使返回对象为String类型
新建StringConverterFactory类,代码如下:
public class StringConverterFactory extends Converter.Factory {

    public static StringConverterFactory create() {
        return new StringConverterFactory();
    }

    private StringConverterFactory() {

    }

    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
                                                            Retrofit retrofit) {
        return new StringResponseBodyConverter();
    }

    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type,
                                                          Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
        return new StringRequestBodyConverter();
    }
}
之后还要实现Resuest请求体类和Response响应体类,分别为StringRequestBodyConverter和StringResponseBodyConverter代码如下:
StringRequestBodyConverter
public class StringRequestBodyConverter implements Converter<String, RequestBody> {
    private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
    private static final Charset UTF_8 = Charset.forName("UTF-8");

    StringRequestBodyConverter() {
    }

    @Override public RequestBody convert(String value) throws IOException {
        Buffer buffer = new Buffer();
        Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
        writer.write(value);
        writer.close();
        return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
    }
}
StringResponseBodyConverter
public class StringResponseBodyConverter  implements Converter<ResponseBody, String> {
    @Override
    public String convert(ResponseBody value) throws IOException {
        try {
            return value.string();
        } finally {
            value.close();
        }
    }
}
之后在创建业务请求接口的时候就可以这样
public interface MyRetrofitService {
    //最简单的Get请求,参数以map形式传递
    @GET()
    Call<String> getService (@Url String url,@QueryMap Map<String,String> Para);
}
在创建Retrofit的实例的时候就可以这样使用
Retrofit retrofit = new Retrofit.Builder()
                .addConverterFactory(StringConverterFactory.create())
                .baseUrl(IP)
                .build();
而在异步请求的回调方法得到的body()也将是一个String类型,再写一个回调接口,将Json的解析单独封装,并在其他部分进行解析

封装的Retrofit方法包括简单的Get请求,简单的Post请求,使用Post请求的文件上传,图片按照尺寸压缩的上传,使用Get的文件下载,以及图片的网络加载(由于自己的能力有限,封装的图片加载的方法个人感觉不好用,所有又加入了ImageLoader用于图片的网络加载)
创建业务请求的整体接口如下:
public interface MyRetrofitService {
    //最简单的Get请求,参数以map形式传递
    @GET()
    Call<String> getService (@Url String url,@QueryMap Map<String,String> Para);

    @GET()
    Call<String> getService (@Url String url);
    //下载文件(包含下载进度)
    @GET()
    Call<ResponseBody> getFileService(@Url String url);

    @GET()
    Call<ResponseBody> getFileService(@Url String url,@QueryMap Map<String,String> Para);

    //Post请求 以表单的形式
    @FormUrlEncoded
    @POST()
    Call<String> postService(@Url String url,@FieldMap Map<String,String> Para);

    //上传文件(可以携带参数)用@Part
    //如下所示
    @Multipart
    @POST()//多文件上传
    Call<String> putService(@Url String url,@PartMap Map<String, MultipartBody.Part> Filemap);

    @Multipart
    @POST()//多文件上传,携带参数(在使用的时候根据携带的参数而定)
    Call<String> putService(@Url String url,@PartMap Map<String, MultipartBody.Part> Filemap,@Part("name") RequestBody description);

    @Multipart
    @POST()//单文件上传
    Call<String> putService(@Url String url,@Part MultipartBody.Part File);

    @Multipart
    @POST()//单文件上传,携带参数(在使用的时候根据携带的参数而定)
    Call<String> putService(@Url String url,@Part  MultipartBody.Part File,@Part("name") RequestBody description);
}
整体的使用方法都是大同小异的,基本都是上面的几步。

但是现在又有一个问题,Retrofit没有提供上传和下载的进度回调,这个时候就感觉*****(和谐)。在网上又找了好长的时间,终于找到了思路,自己重写RequestBody和ResponseBody在其中加入进度的回调
先定义进度回调接口ProgressListener如下:
public interface ProgressListener {

    void update(long bytesRead, long contentLength, boolean done);
}
之后重写RequestBody和ResponseBody 如下:
public class ProgressResponseBody extends ResponseBody {
    private final ResponseBody responseBody;
    private final ProgressListener progressListener;
    private BufferedSource bufferedSource;

    public ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener) {
        this.responseBody = responseBody;
        this.progressListener = progressListener;
    }

    @Override
    public MediaType contentType() {
        return responseBody.contentType();
    }


    @Override
    public long contentLength() {
        return responseBody.contentLength();
    }

    @Override
    public BufferedSource source() {
        if (bufferedSource == null) {
            bufferedSource = Okio.buffer(source(responseBody.source()));
        }
        return bufferedSource;
    }

    private Source source(Source source) {
        return new ForwardingSource(source) {
            long totalBytesRead = 0L;
            long bytesWritten = responseBody.contentLength();
            @Override
            public long read(Buffer sink, long byteCount) throws IOException {
                long bytesRead = super.read(sink, byteCount);
                totalBytesRead += bytesRead != -1 ? bytesRead : 0;
                progressListener.update(totalBytesRead,bytesWritten , bytesWritten == totalBytesRead);
                return bytesRead;
            }
        };
    }
}
RequestBody:
public class ProgressRequestBody extends RequestBody {

    //实际的请求体
    private RequestBody body;
    //进度回调接口
    private ProgressListener progressListener;
    //包装完成的BufferedSink
    private BufferedSink bufferedSink;

    public ProgressRequestBody(RequestBody body,ProgressListener progressListener){
        this.body = body;
        this.progressListener = progressListener;
    }

    /**
     * 重写调用实际响应体的contentType
     * @return
     */
    @Override
    public MediaType contentType() {
        return body.contentType();
    }

    @Override
    public void writeTo(BufferedSink sink) throws IOException {
        if (bufferedSink == null) {
            //包装
            bufferedSink = Okio.buffer(sink(sink));
        }
        //写入
        body.writeTo(bufferedSink);
        //必须调用flush,否则最后一部分数据可能不会被写入
        bufferedSink.flush();
    }

    /**
     * 写入,回调进度接口
     * @param sink
     * @return
     */
    private Sink sink(Sink sink){
        return new ForwardingSink(sink) {
            //当前写入字节数
            long bytesWritten = 0L;
            //总字节长度,避免多次调用contentLength()方法
            long contentLength = 0L;
            @Override
            public void write(Buffer source, long byteCount) throws IOException {
                super.write(source, byteCount);
                if (contentLength == 0) {
                    //获得contentLength的值,后续不再调用
                    contentLength = body.contentLength();
                }
                //增加当前写入的字节数
                bytesWritten += byteCount;
                //回调
                progressListener.update(bytesWritten, contentLength, bytesWritten == contentLength);
            }
        };
    }

}
基本步骤都完成了,接下来看看封装的类MyRetrofit
public class MyRetrofit {

    private static String POST = "post";
    private static String GET = "get";

    /**
     * 此方法是调用Get和Post的请求的基础
     * @param type
     * @param IP
     * @param url
     * @param para
     * @param handler
     * @param success
     * @param fail
     * @param marker
     * @param netSuccess
     */
    private static void BaseGetAndPost(String type,String IP,String url,Map<String,String> para, final Handler handler,
                               final int success, final int fail, final int marker, final NetSuccess netSuccess){
        Retrofit retrofit = new Retrofit.Builder()
                .addConverterFactory(StringConverterFactory.create())
                .baseUrl(IP)
                .build();
        MyRetrofitService service = retrofit.create(MyRetrofitService.class);
        Call<String> call = null;
        //判断是POST请求还是GET请求
        if(type.equals(POST)){
            call = service.postService(url,para);
        }else if(type.equals(GET)){
            if(para != null && para.size() != 0) {
                call = service.getService(url, para);
            }else{
                call = service.getService(url);
            }
        }else {
            StringUtil.showMessage("参数出错");
            return;
        }
        call.enqueue(new Callback<String>() {
            @Override
            public void onResponse(Call<String> call, Response<String> response) {
                int code = response.code();
                Message message = new Message();
                message.what = success;
                message.arg1 = marker;
                if(code == 200){
                    if(netSuccess != null){
                        message.obj = netSuccess.onSuccess(response.body().toString());
                    }
                }else {
                    StringUtil.showMessage("连接服务器出错");
                }
                handler.sendMessage(message);
            }
            @Override
            public void onFailure(Call<String> call, Throwable throwable) {
                Message message = new Message();
                message.what = fail;
                message.obj = "FAIL";
                handler.sendMessage(message);
            }
        });
    }

    /**
     * 此方法为单一文件和多文件的上传的基础方法
     * 传递过来的为文件路径,且能显示上传进度
     * @param IP
     * @param url
     * @param FilePath
     * @param description
     * @param handler
     * @param success
     * @param fail
     * @param marker
     * @param netSuccess
     */
    private static void putFileBase(String IP,String url,List<String> FilePath,String description,final Handler handler
            , final int success, final int fail, final int marker, final NetSuccess netSuccess,final ProgressListener listener){
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(IP)
                .addConverterFactory(StringConverterFactory.create())
                .build();
        MyRetrofitService service = retrofit.create(MyRetrofitService.class);
        Call<String> call = null;
        if(FilePath == null || FilePath.size() == 0){
            StringUtil.showMessage("请选择文件");
            return;
        }else{
            if(FilePath.size() ==1) {//单文件上传
                File file = new File(FilePath.get(0));
                RequestBody fileBody = RequestBody.create(MediaType.parse("multipart/form-data"), file);
                ProgressRequestBody requestBody = new ProgressRequestBody(fileBody,listener);
                MultipartBody.Part part = MultipartBody.Part.createFormData("file", file.getName(), requestBody);
                if (description == null) {
                    call = service.putService(url, part);
                } else {
                    RequestBody descriptionBody = RequestBody.create(null, description);
                    call = service.putService(url, part, descriptionBody);
                }
            }else{//多文件上传
                Map<String,MultipartBody.Part> fileBody = null;
                for(int i=0;i<FilePath.size();i++){
                    File file = new File(FilePath.get(i));
                    RequestBody body = RequestBody.create(MediaType.parse("multipart/form-data"), file);
                    ProgressRequestBody requestBody = new ProgressRequestBody(body,listener);
                    MultipartBody.Part part = MultipartBody.Part.createFormData("file", file.getName(), requestBody);
                    fileBody.put(""+i,part);
                }
                if (description == null) {
                    call = service.putService(url, fileBody);
                } else {
                    RequestBody descriptionBody = RequestBody.create(null, description);
                    call = service.putService(url, fileBody, descriptionBody);
                }
            }
        }
        call.enqueue(new Callback<String>() {
            @Override
            public void onResponse(Call<String> call, Response<String> response) {
                int code = response.code();
                Message message = new Message();
                message.what = success;
                message.arg1 = marker;
                if(code == 200){
                    if(netSuccess != null){
                        message.obj = netSuccess.onSuccess(response.body().toString());
                    }
                }else{
                    StringUtil.showMessage("连接服务器出错");
                }
                handler.sendMessage(message);
            }
            @Override
            public void onFailure(Call<String> call, Throwable throwable) {
                Message message = new Message();
                message.what = fail;
                message.obj = "FAIL";
                handler.sendMessage(message);
            }
        });
    }

    /**
     * 此方法为单一文件和多文件的上传的基础方法
     * 一般用于图片的上传(可进行图片的压缩)
     * @param IP
     * @param url
     * @param FilePath
     * @param description
     * @param handler
     * @param success
     * @param fail
     * @param marker
     * @param netSuccess
     */
    private static void putFileBase(String IP,String url,List<String> FilePath,int width,int hight,String description
            ,final Handler handler, final int success, final int fail, final int marker, final NetSuccess netSuccess){
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(IP)
                .addConverterFactory(StringConverterFactory.create())
                .build();
        MyRetrofitService service = retrofit.create(MyRetrofitService.class);
        Call<String> call = null;
        if(FilePath == null || FilePath.size() == 0){
            StringUtil.showMessage("请选择文件");
            return;
        }else{
            if(FilePath.size() ==1) {//单文件上传
                File file = new File(FilePath.get(0));
                Bitmap bitmap = CommonUtil.changeBitmap(FilePath.get(0), width, hight);
                String[] arr = FilePath.get(0).split("\\.");
                int len = arr.length;
                byte[] byteArr = CommonUtil.bitMap2byteArr(bitmap, arr[len - 1]);
                RequestBody fileBody = RequestBody.create(MediaType.parse("multipart/form-data"), byteArr);
                MultipartBody.Part part = MultipartBody.Part.createFormData("file", file.getName(), fileBody);
                if (description == null) {
                    call = service.putService(url, part);
                } else {
                    RequestBody descriptionBody = RequestBody.create(null, description);
                    call = service.putService(url, part, descriptionBody);
                }
            }else{//多文件上传
                Map<String,MultipartBody.Part> fileBody = null;
                for(int i=0;i<FilePath.size();i++){
                    File file = new File(FilePath.get(i));
                    Bitmap bitmap = CommonUtil.changeBitmap(FilePath.get(0), width, hight);
                    String[] arr = FilePath.get(i).split("\\.");
                    int len = arr.length;
                    byte[] byteArr = CommonUtil.bitMap2byteArr(bitmap, arr[len - 1]);
                    RequestBody body = RequestBody.create(MediaType.parse("multipart/form-data"),byteArr);
                    MultipartBody.Part part = MultipartBody.Part.createFormData("file", file.getName(), body);
                    fileBody.put(""+i,part);
                }
                if (description == null) {
                    call = service.putService(url, fileBody);
                } else {
                    RequestBody descriptionBody = RequestBody.create(null, description);
                    call = service.putService(url, fileBody, descriptionBody);
                }
            }
        }
        call.enqueue(new Callback<String>() {
            @Override
            public void onResponse(Call<String> call, Response<String> response) {
                int code = response.code();
                Message message = new Message();
                message.what = success;
                message.arg1 = marker;
                if(code == 200){
                    if(netSuccess != null){
                        message.obj = netSuccess.onSuccess(response.body().toString());
                    }
                }else{
                    StringUtil.showMessage("连接服务器出错");
                }
                handler.sendMessage(message);
            }
            @Override
            public void onFailure(Call<String> call, Throwable throwable) {
                Message message = new Message();
                message.what = fail;
                message.obj = "FAIL";
                handler.sendMessage(message);
            }
        });
    }

    /**
     * 用于下载文件(应该先检查文件是否存在)
     * @param IP
     * @param url
     * @param savePath
     * @param para
     * @param handler
     * @param success
     * @param fail
     * @param marker
     * @param netSuccess
     * @param listener
     */

    private static void getFileBase(String IP,String url, final String savePath,Map<String,String> para, final Handler handler
            , final int success, final int fail, final int marker, final NetSuccess netSuccess, final ProgressListener listener){
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(IP)
                .addConverterFactory(StringConverterFactory.create())
                .build();
        MyRetrofitService service = retrofit.create(MyRetrofitService.class);
        Call<ResponseBody> call = null;
        if(para ==null || para.size() ==0) {
            call = service.getFileService(url);
        }else{
            call = service.getFileService(url,para);
        }
        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                int code = response.code();
                Message message = new Message();
                message.what = success;
                message.arg1 = marker;
                if(code == 200){
                    if(netSuccess != null){
                        ResponseBody body = response.body();
                        ProgressResponseBody responseBody = new ProgressResponseBody(body,listener);
                        byte[] bytes = new byte[0];
                        try {
                            bytes = responseBody.bytes();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        if(CommonUtil.saveBytesToFile(bytes,savePath)){
                            StringUtil.showMessage("下载完成");
                        }else{
                            StringUtil.showMessage("下载失败");
                        }
                    }
                }else{
                    StringUtil.showMessage("连接服务器出错");
                }
                handler.sendMessage(message);
            }
            @Override
            public void onFailure(Call<ResponseBody> call, Throwable throwable) {
                Message message = new Message();
                message.what = fail;
                message.obj = "FAIL";
                handler.sendMessage(message);
            }
        });
    }


    private static void getImageBase(String IP,String url,Map<String,String> para, final Handler handler
            , final int success, final int fail, final int marker){
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(IP)
                .addConverterFactory(StringConverterFactory.create())
                .build();
        MyRetrofitService service = retrofit.create(MyRetrofitService.class);
        Call<ResponseBody> call = null;
        if(para ==null || para.size() ==0) {
            call = service.getFileService(url);
        }else{
            call = service.getFileService(url,para);
        }
        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                int code = response.code();
                Message message = new Message();
                message.what = success;
                message.arg1 = marker;
                if(code == 200){
                    ResponseBody body = response.body();
                    byte[] bytes = new byte[0];
                    try {
                        bytes = body.bytes();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    message.obj = CommonUtil.Bytes2Bimap(bytes);
                }else{
                    StringUtil.showMessage("连接服务器出错");
                }
                handler.sendMessage(message);
            }
            @Override
            public void onFailure(Call<ResponseBody> call, Throwable throwable) {
                Message message = new Message();
                message.what = fail;
                message.obj = "FAIL";
                handler.sendMessage(message);
            }
        });
    }


    public interface NetSuccess{
        /**
         * 成功获取数据
         * @param result json数据
         * @return 解析之后的结果
         */
        public <T> T onSuccess(T result);
    }


    /**
     * Get请求的使用方法
     * @param handler
     * @param name
     * @param marker
     */

    public static void getConCeShi(Handler handler,String name,int marker){
        Map<String,String> para = new HashMap<>();
        if(!StringUtil.isEmpty(name)) {
            para.put("name", name);
        }
        BaseGetAndPost(GET, Contents.IP, Contents.getCeShi, para, handler, Contents.SUCCESS,
                Contents.FAIL, marker, new NetSuccess() {
                    @Override
                    public <T> T onSuccess(T result) {
                        //返回的结果,字符串类型
                        return result;
                    }
                });
    }

    /**
     * Post请求的是用方法
     * @param handler
     * @param name
     * @param marker
     */
    public static void postConCeShi(Handler handler,String name,int marker){
        Map<String,String> para = new HashMap<>();
        if(!StringUtil.isEmpty(name)) {
            para.put("name", name);
        }
        BaseGetAndPost(POST, Contents.IP, Contents.postCeShi, para, handler, Contents.SUCCESS,
                Contents.FAIL, marker, new NetSuccess() {
                    @Override
                    public <T> T onSuccess(T result) {
                        //返回的结果,字符串类型
                        return result;
                    }
                });
    }


    /**
     * 文件的上传图片不需要压缩的话也可以用此方法
     * @param handler
     * @param filePath
     * @param descrtion
     * @param marker
     */
    public static void putFileCeShi(Handler handler,List<String> filePath,String descrtion,int marker){

        putFileBase(Contents.IP, Contents.putCeShi, filePath, descrtion, handler, Contents.SUCCESS,
                Contents.FAIL, marker, new NetSuccess() {
                    @Override
                    public <T> T onSuccess(T result) {
                        //返回的结果,字符串类型
                        return result;
                    }
                }, new ProgressListener() {
                    @Override
                    public void update(long bytesRead, long contentLength, boolean done) {
                        //上传进度的展示
                    }
                });
    }

    /**
     * 图片的按照尺寸的压缩上传
     * @param handler
     * @param filePath
     * @param width
     * @param hight
     * @param descrtion
     * @param marker
     */
    public static void putImageCeShi(Handler handler,List<String> filePath,int width,int hight,String descrtion,int marker){

        putFileBase(Contents.IP, Contents.putCeShi, filePath,width,hight, descrtion, handler, Contents.SUCCESS,
                Contents.FAIL, marker, new NetSuccess() {
                    @Override
                    public <T> T onSuccess(T result) {
                        return result;
                    }
                });
    }


    public static void getFileCeShi(Handler handler,String savePath,String curfile,String path,int marker){
        Map<String,String> para = new HashMap<>();
        para.put("curfile",curfile);
        para.put("path",path);
        getFileBase(Contents.IP, Contents.setCeShi, savePath, para, handler, Contents.SUCCESS,
                Contents.FAIL, marker, new NetSuccess() {
                    @Override
                    public <T> T onSuccess(T result) {
                        return null;
                    }
                }, new ProgressListener() {
                    @Override
                    public void update(long bytesRead, long contentLength, boolean done) {
                        //此处显示下载进度的对应UI
                    }
                });
    }

    public static void displayNetImage(Handler handler,String curfile,String path,int marker){
        Map<String,String> para = new HashMap<>();
        para.put("curfile",curfile);
        para.put("path",path);
        getImageBase(Contents.IP, Contents.setCeShi, para, handler, Contents.SUCCESS, Contents.FAIL
                , marker);
    }


}
到此已经是封装完成了,使用方法如下:
//GET请求的测试
//                MyRetrofit.getConCeShi(handler,"", BackMakerUtil.CESHIBACK);
                //Post请求的测试
//                MyRetrofit.postConCeShi(handler,"fei",BackMakerUtil.CESHIBACK);
                //文件上传(有说明信息就写,没有就传null,不需要压缩的图片也可以用此方法)
//                MyRetrofit.putFileCeShi(handler, file, "fei", BackMakerUtil.CESHIBACK);
                //需要压缩的图片的上传
//                MyRetrofit.putImageCeShi(handler, file, 300, 150, "fei", BackMakerUtil.CESHIBACK);
                //下载文件
//                MyRetrofit.getFileCeShi(handler,path,"a.jpg","D:/aDown",BackMakerUtil.CESHIBACK);
                //加载图片
                MyRetrofit.displayNetImage(handler,"a.jpg","D://aDown",BackMakerUtil.CESHIBACK);

到此就全部写完了,希望看到各位能提出宝贵的看法
本人只是一个Android菜鸟,由于还不会RxJava,所以只能这样封装了,学会了RxJava还会继续采用RxJava+Retrofit的方法进行封装
























评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值