spring boot与fastdfs的整合使用


spring boot和fastdfs是什么就不说了。百度一下你就知道。

这里我们介绍一下spring boot与fastdfs的整合使用。fastdfs怎么安装请查看我的另外一篇博客:点击打开链接。 这里假设你已经安装好了fastdfs


首先下载最新版的 fastdfs-client-java源码编译,因为这个包maven上没有,需要自己编译成jar本地安装到maven。

源码下载地址:https://github.com/happyfish100/fastdfs-client-java ,或者直接下载我已经编译好的最新版本的jar包:http://download.csdn.net/detail/kokjuis/9919803


怎么使用这个jar,这里有两种方式

1、直接在你的spring boot工程下建立lib文件夹(与src平级)。把jar包加入到lib目录,然后在pom.xml中引用:


<!-- 这个包maven上没有,用源码编译后放在本地引用 -->
		<dependency>
			<groupId>org.csource</groupId>
			<artifactId>fastdfs-client-java</artifactId>
			<version>1.27-SNAPSHOT</version>
			<scope>system</scope>
      		        <systemPath>${project.basedir}/lib/fastdfs-client-java-1.27-SNAPSHOT.jar</systemPath>
		</dependency>



这样就可以在工程中引用了。不过不建议使用这种方式,因为用这种方式引用,在项目打包成jar的时候会maven会找不到这个jar。


2、把编译好的jar安装在你本地的maven库中,然后在引用:
使用maven命令安装jar包到本地。-Dfile=E:\fastdfs-client-java-1.27-SNAPSHOT.jar  路径改为你自己的路径。

mvn install:install-file -DgroupId=org.csource -DartifactId=fastdfs-client-java -Dversion=1.27-SNAPSHOT -Dpackaging=jar -Dfile=E:\fastdfs-client-java-1.27-SNAPSHOT.jar

安装完以后就能正常引用了:

<dependency>
			<groupId>org.csource</groupId>
			<artifactId>fastdfs-client-java</artifactId>
			<version>1.27-SNAPSHOT</version>
		</dependency>

然后看看怎么使用吧:

首先添加fastdfs配置文件:fdfs-client.conf   根据你自己的配置修改


# 连接tracker服务器超时时长
connect_timeout = 10
# socket连接超时时长
network_timeout = 30
# 文件内容编码
charset = UTF-8
# tracker服务器端口
http.tracker_http_port = 6666 
http.anti_steal_token = no
http.secret_key = FastDFS1234567890
# tracker服务器IP和端口(可以写多个)
tracker_server = 192.168.9.181:22122
#tracker_server = xxxx:xxx


然后在工程里的配置类里面声明StorageClient1 (fastdfs调用客户端类)。


@Configuration
public class ApplicationConfig extends WebMvcConfigurerAdapter
{

    static final String FASTDFS_CONFIG = "conf/fdfs-client.conf";

    @Bean
    public StorageClient1 initStorageClient()
    {
        StorageClient1 storageClient = null;
        try
        {
            ClientGlobal.init(FASTDFS_CONFIG);
            System.out.println("ClientGlobal.configInfo(): " + ClientGlobal.configInfo());
            TrackerClient trackerClient = new TrackerClient(ClientGlobal.g_tracker_group);
            TrackerServer trackerServer = trackerClient.getConnection();
            if (trackerServer == null)
            {
                throw new IllegalStateException("getConnection return null");
            }
            StorageServer storageServer = trackerClient.getStoreStorage(trackerServer);
            if (storageServer == null)
            {
                throw new IllegalStateException("getStoreStorage return null");
            }
            storageClient = new StorageClient1(trackerServer, storageServer);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return storageClient;
    }
}


如果没有特殊要求,到这里就算配置好了,简单吧,来看看使用的示例吧:


fastdfs调用工具类:

/*
 * 文件名:FastDFSClient.java 版权:Copyright by www.inhand.com 描述: 修改人:kokJuis 修改时间:2017年7月27日 跟踪单号:
 * 修改单号: 修改内容:
 */

package com.inhand.fastdfs.utils;


import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.csource.common.MyException;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.FileInfo;
import org.csource.fastdfs.StorageClient1;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;


@Component
public class FastDFSClient
{
    @Autowired
    private StorageClient1 storageClient;

    /**
     * 上传文件
     * 
     * @param file
     *            文件对象
     * @param fileName
     *            文件名
     * @return
     */
    public String uploadFile(byte[] buff, String fileName)
    {
        return uploadFile(buff, fileName, null);
    }

    /**
     * 上传文件
     * 
     * @param file
     *            文件对象
     * @param fileName
     *            文件名
     * @param metaList
     *            文件元数据
     * @return
     */
    public String uploadFile(byte[] buff, String fileName, Map<String, String> metaList)
    {
        try
        {
            NameValuePair[] nameValuePairs = null;
            if (metaList != null)
            {
                nameValuePairs = new NameValuePair[metaList.size()];
                int index = 0;
                for (Iterator<Map.Entry<String, String>> iterator = metaList.entrySet().iterator(); iterator.hasNext();)
                {
                    Map.Entry<String, String> entry = iterator.next();
                    String name = entry.getKey();
                    String value = entry.getValue();
                    nameValuePairs[index++ ] = new NameValuePair(name, value);
                }
            }
            return storageClient.upload_file1(buff, FileUtil.getExtensionName(fileName),
                nameValuePairs);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取文件元数据
     * 
     * @param fileId
     *            文件ID
     * @return
     */
    public Map<String, String> getFileMetadata(String fileId)
    {

        try
        {
            NameValuePair[] metaList = storageClient.get_metadata1(fileId);
            if (metaList != null)
            {
                HashMap<String, String> map = new HashMap<String, String>();
                for (NameValuePair metaItem : metaList)
                {
                    map.put(metaItem.getName(), metaItem.getValue());
                }
                return map;
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 删除文件
     * 
     * @param fileId
     *            文件ID
     * @return 删除失败返回-1,否则返回0
     */
    public int deleteFile(String fileId)
    {
        try
        {
            return storageClient.delete_file1(fileId);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return -1;
    }

    /**
     * 下载文件
     * 
     * @param fileId
     *            文件ID(上传文件成功后返回的ID)
     * @return
     */
    public byte[] downloadFile(String fileId)
    {
        try
        {
            byte[] content = storageClient.download_file1(fileId);
            return content;
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        catch (MyException e)
        {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Description:获取文件信息
     * 
     * @param fileId
     * @return
     * @see
     */
    public FileInfo getFileInfo(String fileId)
    {

        try
        {
            FileInfo fileInfo = storageClient.get_file_info1(fileId);
            return fileInfo;
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        catch (MyException e)
        {
            e.printStackTrace();
        }
        return null;
    }

}

FileUtil:

/*
 * 文件名:FileUtils.java 版权:Copyright by www.huawei.com 描述: 修改人:kokJuis 修改时间:2017年7月27日 跟踪单号: 修改单号:
 * 修改内容:
 */

package com.poly.fastdfs.utils;

public class FileUtil
{

    /**
     * Description: 获取文件后缀名
     * 
     * @param fileName
     * @return
     * @see
     */
    public static String getExtensionName(String fileName)
    {
        String prefix = fileName.substring(fileName.lastIndexOf(".") + 1);
        return prefix;
    }

    /**
     * 根据path获取文件名
     * 
     * @author kokJuis
     * @version 1.0
     * @date 2016-12-12
     * @param filename
     * @return
     */
    public static String getOriginalFilename(String filename)
    {
        if (filename == null) return "";
        int pos = filename.lastIndexOf("/");
        if (pos == -1) pos = filename.lastIndexOf("\\");
        if (pos != -1)
            return filename.substring(pos + 1);
        else
            return filename;
    }

}




简单的上传下载:


package com.inhand.fastdfs.controller;


import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.inhand.fastdfs.base.Code;
import com.inhand.fastdfs.utils.FastDFSClient;
import com.inhand.fastdfs.utils.FileUtil;


@RestController
@RequestMapping("/file/*")
public class FileController
{

    @Autowired
    private FastDFSClient fastDFSClient;

    /**
     * 上传文件
     * 
     * @author kokJuis
     * @version 1.0
     * @date 2016-12-12
     * @return
     */
    @RequestMapping(value = "uploadFile", method = RequestMethod.POST)
    public Map<String, Object> uploadFile(@RequestParam MultipartFile filedata)
    {

        Map<String, Object> m = new HashMap<String, Object>();

        if (filedata != null && !filedata.isEmpty())
        {
            try
            {

                String path = fastDFSClient.uploadFile(filedata.getBytes(), filedata.getOriginalFilename());

                m.put("code", Code.SUCCESS);
                m.put("url", path);
                m.put("msg", "上传成功");

            }
            catch (Exception e)
            {
                e.printStackTrace();
                m.put("code", Code.FAIL);
                m.put("msg", "上传失败");
            }
        }
        else
        {
            m.put("code", Code.PARAMETER_LOST);
            m.put("msg", "参数丢失");
        }
        return m;

    }

    /**
     * 下载文件
     * 
     * @author kokJuis
     * @version 1.0
     * @date 2016-12-12
     * @param imagePath
     * @param local
     * @return
     */
    @RequestMapping(value = "getFileByPath", method = RequestMethod.GET)
    public void getFileByPath(HttpServletResponse response, String path)
    {

        try
        {
            // 判断文件是否存在
            if (fastDFSClient.getFileInfo(path) != null)
            {
                byte[] buffer = fastDFSClient.downloadFile(path);
                // 清空response
                response.reset();
                // 设置response的Header
                response.addHeader("Content-Disposition",
                    "attachment;filename=" + FileUtil.getOriginalFilename(path));
                response.addHeader("Content-Length", "" + buffer.length);
                // 通过文件流的形式写到客户端
                OutputStream toClient = new BufferedOutputStream(response.getOutputStream());
                response.setContentType("application/octet-stream");
                toClient.write(buffer);
                // 写完以后关闭文件流
                toClient.flush();
                toClient.close();
            }

        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }

}


可以添加连接池。可以提高上传下载效率:(这里是改成spring boot后的做法,用sping的可以添加配置文件即可使用)

/*
 * 文件名:ConnectionPool.java 版权:Copyright by www.huawei.com 描述: 修改人:kokJuis 修改时间:2017年8月16日 跟踪单号:
 * 修改单号: 修改内容:
 */

package com.poly.fastdfs.pool;


import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

import org.csource.fastdfs.ClientGlobal;
import org.csource.fastdfs.StorageClient1;
import org.csource.fastdfs.StorageServer;
import org.csource.fastdfs.TrackerClient;
import org.csource.fastdfs.TrackerServer;
import org.springframework.stereotype.Component;


@Component
public class ConnectionPool
{

    private static final String FASTDFS_CONFIG = "conf/fdfs-client.conf";

    /** 空闲的连接池 */
    private LinkedBlockingQueue<StorageClient1> idleConnectionPool = null;

    /** 空闲的忙碌连接池 */
    // private ConcurrentHashMap<StorageClient1, Object> busyConnectionPool = null;

    /** 连接池默认最小连接数 */
    private long minPoolSize = 10;

    /** 连接池默认最大连接数 */
    private long maxPoolSize = 30;

    /** 默认等待时间(单位:秒) */
    private long waitTimes = 200;

    /** fastdfs客户端创建连接默认1次 */
    private static final int COUNT = 1;

    private Object obj = new Object();

    TrackerServer trackerServer = null;

    /**
     * 默认构造方法
     */
    public ConnectionPool()
    {
        /** 初始化连接池 */
        poolInit();

        /** 注册心跳 */
        // HeartBeat beat = new HeartBeat(this);
        // beat.beat();
    }

    public ConnectionPool(long minPoolSize, long maxPoolSize, long waitTimes)
    {
        System.out.println("[线程池构造方法(ConnectionPool)][默认参数:minPoolSize=" + minPoolSize
                           + ",maxPoolSize=" + maxPoolSize + ",waitTimes=" + waitTimes + "]");
        this.minPoolSize = minPoolSize;
        this.maxPoolSize = maxPoolSize;
        this.waitTimes = waitTimes;
        /** 初始化连接池 */
        poolInit();
        /** 注册心跳 */
        HeartBeat beat = new HeartBeat(this);
        beat.beat();
    }

    /**
     * @Description: 连接池初始化 (在加载当前ConnectionPool时执行) 1).加载配置文件 2).空闲连接池初始化;
     *               3).创建最小连接数的连接,并放入到空闲连接池;
     */
    private void poolInit()
    {
        try
        {
            /** 加载配置文件 */
            initClientGlobal();
            /** 初始化空闲连接池 */
            idleConnectionPool = new LinkedBlockingQueue<StorageClient1>();
            /** 初始化忙碌连接池 */
            // busyConnectionPool = new ConcurrentHashMap<StorageClient1, Object>();

            TrackerClient trackerClient = new TrackerClient();
            trackerServer = trackerClient.getConnection();
            int flag = 0;
            while (trackerServer == null && flag < 5)
            {
                System.out.println("[创建TrackerServer(createTrackerServer)][第" + flag + "次重建]");
                flag++ ;
                initClientGlobal();
                trackerServer = trackerClient.getConnection();
            }
            // 测试 Tracker活跃情况
            // ProtoCommon.activeTest(trackerServer.getSocket());

            /** 往线程池中添加默认大小的线程 */
            createTrackerServer();
        }
        catch (Exception e)
        {
            e.printStackTrace();
            System.out.println("[FASTDFS初始化(init)--异常]");
        }
    }

    /**
     * @Description: 创建TrackerServer,并放入空闲连接池
     */
    public void createTrackerServer()
    {

        System.out.println("[创建TrackerServer(createTrackerServer)]");
        TrackerServer trackerServer = null;

        try
        {

            for (int i = 0; i < minPoolSize; i++ )
            {
                // 把client1添加到连接池
                StorageServer storageServer = null;
                StorageClient1 client1 = new StorageClient1(trackerServer, storageServer);
                idleConnectionPool.add(client1);
            }

        }
        catch (Exception e)
        {
            e.printStackTrace();
            System.out.println("[创建TrackerServer(createTrackerServer)][异常:{}]");
        }

    }

    /**
     * @Description: 获取空闲连接 1).在空闲池(idleConnectionPool)中弹出一个连接; 2).把该连接放入忙碌池(busyConnectionPool)中;
     *               3).返回 connection 4).如果没有idle connection, 等待 wait_time秒, and check again
     * @throws AppException
     */
    public StorageClient1 checkout()
    {

        StorageClient1 client1 = idleConnectionPool.poll();

        if (client1 == null)
        {
            if (idleConnectionPool.size() < maxPoolSize)
            {
                createTrackerServer();
                try
                {
                    client1 = idleConnectionPool.poll(waitTimes, TimeUnit.SECONDS);
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                    System.out.println("[获取空闲连接(checkout)-error][error:获取连接超时:{}]");
                }
            }
        }

        // 添加到忙碌连接池
        // busyConnectionPool.put(client1, obj);
        System.out.println("[获取空闲连接(checkout)][获取空闲连接成功]");
        return client1;
    }

    /**
     * @Description: 释放繁忙连接 1.如果空闲池的连接小于最小连接值,就把当前连接放入idleConnectionPool;
     *               2.如果空闲池的连接等于或大于最小连接值,就把当前释放连接丢弃;
     * @param client1
     *            需释放的连接对象
     */

    public void checkin(StorageClient1 client1)
    {

        System.out.println("[释放当前连接(checkin)]");

        client1 = null;
        if (idleConnectionPool.size() < minPoolSize)
        {
            createTrackerServer();
        }

    }

    private void initClientGlobal()
        throws Exception
    {
        ClientGlobal.init(FASTDFS_CONFIG);
    }

    public LinkedBlockingQueue<StorageClient1> getIdleConnectionPool()
    {
        return idleConnectionPool;
    }

    public long getMinPoolSize()
    {
        return minPoolSize;
    }

    public void setMinPoolSize(long minPoolSize)
    {
        if (minPoolSize != 0)
        {
            this.minPoolSize = minPoolSize;
        }
    }

    public long getMaxPoolSize()
    {
        return maxPoolSize;
    }

    public void setMaxPoolSize(long maxPoolSize)
    {
        if (maxPoolSize != 0)
        {
            this.maxPoolSize = maxPoolSize;
        }
    }

    public long getWaitTimes()
    {
        return waitTimes;
    }

    public void setWaitTimes(int waitTimes)
    {
        if (waitTimes != 0)
        {
            this.waitTimes = waitTimes;
        }
    }
}

使用连接池的工具类:

/*
 * 文件名:FastDFSClient.java 版权:Copyright by www.inhand.com 描述: 修改人:kokJuis 修改时间:2017年7月27日 跟踪单号:
 * 修改单号: 修改内容:
 */

package com.poly.fastdfs.utils;


import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.csource.common.MyException;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.FileInfo;
import org.csource.fastdfs.StorageClient1;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import com.poly.fastdfs.pool.ConnectionPool;


@Component
public class FastDFSClient
{

    /** 连接池 */
    @Autowired
    private ConnectionPool connectionPool;

    /**
     * 上传文件
     * 
     * @param file
     *            文件对象
     * @param fileName
     *            文件名
     * @return
     */
    public String uploadFile(byte[] buff, String fileName)
    {
        return uploadFile(buff, fileName, null, null);
    }

    public String uploadFile(byte[] buff, String fileName, String groupName)
    {
        return uploadFile(buff, fileName, null, null);
    }

    /**
     * 上传文件
     * 
     * @param file
     *            文件对象
     * @param fileName
     *            文件名
     * @param metaList
     *            文件元数据
     * @return
     */
    public String uploadFile(byte[] buff, String fileName, Map<String, String> metaList,
                             String groupName)
    {

        try
        {
            NameValuePair[] nameValuePairs = null;
            if (metaList != null)
            {
                nameValuePairs = new NameValuePair[metaList.size()];
                int index = 0;
                for (Iterator<Map.Entry<String, String>> iterator = metaList.entrySet().iterator(); iterator.hasNext();)
                {
                    Map.Entry<String, String> entry = iterator.next();
                    String name = entry.getKey();
                    String value = entry.getValue();
                    nameValuePairs[index++ ] = new NameValuePair(name, value);
                }
            }

            /** 获取可用的tracker,并创建存储server */
            StorageClient1 storageClient = connectionPool.checkout();

            String path = null;
            if (!StringUtils.isEmpty(groupName))
            {
                // 上传到指定分组
                path = storageClient.upload_file1(groupName, buff,
                    FileUtil.getExtensionName(fileName), nameValuePairs);
            }
            else
            {
                path = storageClient.upload_file1(buff, FileUtil.getExtensionName(fileName),
                    nameValuePairs);
            }

            /** 上传完毕及时释放连接 */
            connectionPool.checkin(storageClient);

            return path;

        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取文件元数据
     * 
     * @param fileId
     *            文件ID
     * @return
     */
    public Map<String, String> getFileMetadata(String fileId)
    {

        try
        {

            /** 获取可用的tracker,并创建存储server */
            StorageClient1 storageClient = connectionPool.checkout();

            NameValuePair[] metaList = storageClient.get_metadata1(fileId);
            /** 上传完毕及时释放连接 */
            connectionPool.checkin(storageClient);

            if (metaList != null)
            {
                HashMap<String, String> map = new HashMap<String, String>();
                for (NameValuePair metaItem : metaList)
                {
                    map.put(metaItem.getName(), metaItem.getValue());
                }
                return map;
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 删除文件
     * 
     * @param fileId
     *            文件ID
     * @return 删除失败返回-1,否则返回0
     */
    public int deleteFile(String fileId)
    {
        try
        {

            /** 获取可用的tracker,并创建存储server */
            StorageClient1 storageClient = connectionPool.checkout();

            int i = storageClient.delete_file1(fileId);
            /** 上传完毕及时释放连接 */
            connectionPool.checkin(storageClient);

            return i;
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return -1;
    }

    /**
     * 下载文件
     * 
     * @param fileId
     *            文件ID(上传文件成功后返回的ID)
     * @return
     */
    public byte[] downloadFile(String fileId)
    {
        try
        {

            /** 获取可用的tracker,并创建存储server */
            StorageClient1 storageClient = connectionPool.checkout();
            
            byte[] content = storageClient.download_file1(fileId);
            /** 上传完毕及时释放连接 */
            connectionPool.checkin(storageClient);

            return content;
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        catch (MyException e)
        {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Description:获取文件信息
     * 
     * @param fileId
     * @return
     * @see
     */
    public FileInfo getFileInfo(String fileId)
    {

        try
        {
            /** 获取可用的tracker,并创建存储server */
            StorageClient1 storageClient = connectionPool.checkout();
            FileInfo fileInfo = storageClient.get_file_info1(fileId);
            /** 上传完毕及时释放连接 */
            connectionPool.checkin(storageClient);

            return fileInfo;
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        catch (MyException e)
        {
            e.printStackTrace();
        }
        return null;
    }

}




如果你上传报:
The field file exceeds its maximum permitted size of 1048576 bytes.
或者报 
Required request part 'filedata' is not present


上面的报错是因为spring boot 内置的MultipartResolver有点问题,可以这样解决:


显性注册MultipartResolver:


// 显示声明CommonsMultipartResolver为mutipartResolver
    @Bean(name = "multipartResolver")
    public MultipartResolver multipartResolver()
    {
        CommonsMultipartResolver resolver = new CommonsMultipartResolver();
        // resolver.setDefaultEncoding("UTF-8");
        // resolver.setResolveLazily(true);// resolveLazily属性启用是为了推迟文件解析,以在在UploadAction中捕获文件大小异常
        // resolver.setMaxInMemorySize(40960);
        resolver.setMaxUploadSize(10 * 1024 * 1024);// 上传文件大小 5M 5*1024*1024
        return resolver;
    }

并且在spring boot的启动类添加注解:

@EnableAutoConfiguration(exclude = {MultipartAutoConfiguration.class})

这个注解的意思是排除内置的MultipartResolver。如果没有这个注解,显性注册的MultipartResolver会无效。这样处理以后就能正常上传了


全文完。。。



  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值