Java修改文件MD5值-yellowcong

MD5是加密算法的一种,MD5对于每一个文件来说都是唯一的,在百度网盘、QQ的文件快传中,都是算文件的MD5在本地的服务器上有没有,如果有直接就可以上传。但是有一个场景,就是我的文件在百度上是 违规的,我可以通过修改MD5值,然后上传问文件。

修改文件MD5思路

一般MD5修改的办法,就是修改文件的内容,自然MD5就修改掉了,但是文件这么多,我们也不一定改得了,所以最核心的思路是,将一个文件分成多个文件,然后合并成一个文件,在合并的时候,添加一个空文件,就可以解决这个问题

/**
 * 给文件生成新的md5验证码
 * @param pathname 文件的全路径
 * @param newFileName  新文件的全路径
 */
private static void newMD5(String pathname,String newFileName){

    //分割文件
    String [] names = FileUtils.partitionFile(pathname);
    //创建一个空文件
    String newFile = pathname+".txt";
    FileUtils.copyStringToFile(UUID.randomUUID().toString(), newFile);

    //解决 md5 修改
    String [] newNames = new String[names.length+1];
    System.out.println(newNames.length);
    for(int i=0;i<names.length;i++){
        newNames[i] = names[i];
    }

    newNames[names.length] = newFile;
    //文件合并,多添加了一个空文件
    FileUtils.uniteFile(newNames, newFileName);

    //删除所有文件
    FileUtils.deletes(newNames);
}

文件分割

文件分割操作中,需要根据文件的大小来计算分割的份数,同时也要写计算分割的文件名,其中文件的大小可以自己根据文件的大小计算份数

/**
 * 文件分割
 * @param partSize
 * @param pathname
 * @return 
 * @throws Exception
 */
private static String[] partitionFile(int partSize,String pathname){
    DataInputStream in  = null;
    String [] names = null;
    try {
        //DataOutputStream
        //通过DataOutputStream 这个对象完成文件的分割
        //缓存
        File file = new File(pathname);
        //long size = file.length();
        //1024*1024 就是 1MB
        //System.out.println(size/1024/1024);
        //获取分割文件的名称
        names = getPartitionFileNames(pathname, partSize);
        in = new DataInputStream( new FileInputStream(file));
        byte [] buff = new byte[1024];
        DataOutputStream out = null;
        for(String name:names){
            System.out.println(name);
            out = new DataOutputStream(new FileOutputStream(new File(name)));
            long size = partSize;
            while(size >0){
                //当没有读取完的情况
                if(in.read(buff) != -1){
                    out.write(buff, 0, buff.length);
                }
                size = size-buff.length;
            }

            out.close();
        }
    } catch (Exception e) {
        throw new RuntimeException("分割失败");
    }finally{
        try {
            if(in!= null ){
                in.close();
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    return names;
}

计算文件名称

/**
 * 根据给定的每个文件的大小来获取文件的数目
 * @param pathname 文件名称
 * @param partSize 每个部分的大小
 * @return
 */
public static String [] getPartitionFileNames(String pathname,long partSize){
    File file = new File(pathname);
    long size = file.length();
    double num = (size/(double)partSize);
    //向上取整数,获取分割文件的数量
    num = Math.ceil(num);
    String [] names = new String [(int)num] ;
    for(int i=0;i<num;i++){
        names[i] = StringUtil.getFileSimpleName(pathname)+"_"+i+"."+StringUtil.getFileTypeByPath(pathname);
    }
    return names;
}

文件合并

文件拆分开后,我们需要将所有分开的文件合并,同时包含了空文件,然后删除空文件的操作

/**
 * 合并文件
 * @param files 多个子文件
 * @param newFile 新的文件
 * @throws Exception 异常
 */
public static void uniteFile(String[] files, String newFile) {
    DataOutputStream out = null;
    try {
        out = new DataOutputStream(new FileOutputStream(new File(newFile)));
        for(String name:files){
            FileInputStream in = new FileInputStream(new File(name));
            byte [] buff = new byte[1024];
            while((in.read(buff)) != -1){
                out.write(buff,0,buff.length);
            }
            in.close();
        }
    } catch (Exception e) {
        throw new RuntimeException(e.getMessage());
    }finally{
        try {
            if(out != null){
            out.close();
            }
        } catch (Exception e2) {
            // TODO: handle exception
        }

    }
}

完整代码

package com.yellowcong.utils;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.math.BigInteger;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.Map.Entry;

import javax.servlet.http.HttpServletRequest;

import org.hibernate.mapping.Array;

import com.sun.xml.internal.messaging.saaj.util.ByteInputStream;

/**
 * 文件的 分割 合并
 * 文件的 md5
 * 获取文件的 md5
 * @author yellowcong
 * @date 2016年4月17日
 *
 */
public class FileUtils {
    //文件夹信息
    private static List<String> dirNames= null;
    //文件信息
    private static List<String> fileNames = null;

    //文件大小单位 ,我们可以通过添加SizeStr 来添加单位
    private static String [] sizeStr = {"B","KB","MB","GB","PB"}; 
    private static int count =0;
    private static long fileSize = 0;

    private FileUtils(){}

    /**
     * 获取类路径下文件的路径
     * @param fileName
     * @return
     */
    public static String getClassPathFilePath(String fileName){
        String path = FileUtils.class.getClassLoader().getResource(fileName).getPath().toString();
        if(path.contains("%20")){
            path = path.replace("%20", " ");
        }
        return path;
    }

    /**
     * 获取文件对象
     * @param fileName
     * @return
     */
    public static File getClassPathFile(String fileName){
        return new File(FileUtils.getClassPathFilePath(fileName));
    }

    /*在类路径创建我们的 文件夹*/
    /**
     * 创建类路径下面的文件夹
     * @param directory
     */
    public static String mkClassPathDir(String directory){
        String path = FileUtils.class.getClassLoader().getResource("").getPath();
        path += directory;
        File file = new File(path);
        if(!file.exists()){
            file.mkdir();
        }
        return path;
    }

    /**
     * 获取类文件的输入流
     * @param fileName
     * @return
     */
    public static InputStream getClassPathFileInputStream(String fileName){
        return FileUtils.class.getResourceAsStream(fileName);
    }


    /**
     * 将File 文件转化为 字符串
     * @param filePath
     * @return
     */
    public static String copyFileToString(String filePath){
        return FileUtils.getFileContent(filePath);
    }

    /**
     * 获取一个文本文件里面的内容
     * @param filePath
     * @return
     */
    public static String getFileContent(String filePath){
        FileReader in  = null;
        BufferedReader reader = null;
        try {
            in  = new FileReader(new File(filePath));
            reader = new BufferedReader(in);
            String line = null;
            StringBuffer str = new StringBuffer();
            while((line = reader.readLine())!= null){
                str.append(line);
                str.append("\r\n");
            }
            return str.toString();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{
            try {
                if(in != null){
                    in.close();
                }
                if(reader != null ){
                    reader.close();
                }
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        return null;
    }
    /**
     * 获取 web目录下的路径,便于上传
     * @param str
     * @param request
     * @return
     */
    public static String getWebFilePath(String str,HttpServletRequest request){
        //这个直接获取的方法已经过期了
//      return request.getRealPath(str);
        return request.getSession().getServletContext().getRealPath(str);
    }

    /**
     * 通过 路径来创建文件
     * @param path
     */
    public static void createDirectory(String path){
        File file = new File(path);
        if(!file.exists()){
            file.mkdirs();
        }
    }

    /**
     * 通过文件夹  获取文件夹 当前目录 和下面目录所有的文件 
     * @param path
     * @param isNowPath 目录级别  当前目录 数据 
     * @return
     */
    //获取所有文件
    public static List<String> getChildFiles(String path,boolean isNowPath){
        if(!new File(path).exists()){
            throw new RuntimeException(path+",文件夹不存在");
        }
        if(!new File(path).isDirectory()){
            throw new RuntimeException(path+",不是文件夹");
        }
        fileNames = new ArrayList<String>();
        listAllFile(path,isNowPath);
        return fileNames;
    }

    /**
     * 通过文件夹  获取文件夹 当前目录 和下面目录所有的文件 
     * @param path
     * @return
     */
    public static List<String> getChildFiles(String path){
        return getChildFiles(path, false);
    }



    /**
     * 通过文件夹 获取
     * 所有的文件夹
     * @param path 目录名称
     * @param isNowPath 是否是当前目录
     * @return
     */
    public static List<String> getChildDirs(String path,boolean isNowPath){
        if(!new File(path).exists()){
            throw new RuntimeException(path+",文件夹不存在");
        }
        if(!new File(path).isDirectory()){
            throw new RuntimeException(path+",不是文件夹");
        }
        dirNames = new ArrayList<String>();
        listAllDir(path,isNowPath);
        return dirNames;
    }
    /**
     * 通过文件夹 获取 我们的子 
     * 所有的文件夹  
     * @param path 目录名称
     * @return
     */
    public static List<String> getChildDirs(String path){
        return getChildDirs(path, false);
    }
    /**
     * 获取某个文件夹下面的文件
     * 其中文件夹包含了第一个文件夹就是自己 重复调用
     * 
     * @param path 文件夹路径名称
     */
    private static void listAllFile(String path,boolean isNowPath){
        File file = new File(path);
        if(file.isDirectory()){
                for(File f:file.listFiles()){
                    if(isNowPath){
                        if(f.isFile()){
                            fileNames.add(f.getPath());
                        }
                    }else{
                        listAllFile(f.getPath(),isNowPath);

                    }
                }
        }else{
            fileNames.add(path);
        }
    }
    /**
     * 获取某个文件夹下面的文件
     * 其中文件夹包含了第一个文件夹就是自己 重复调用
     * 
     * @param path 文件夹路径名称
     * @param isNowPath 是否是当前目录 ,包含子目录
     */
    private static void listAllDir(String path,boolean isNowPath){
        File file = new File(path);
        if(file.isDirectory()){
            dirNames.add(path);
            for(File f:file.listFiles()){
                if(isNowPath){
                    dirNames.add(f.getPath());
                }else{
                    listAllDir(f.getPath(),isNowPath);
                }

            }
        }
    }

    /**
     * 获取所有的文件,包括 文件夹的信息
     * @param path
     * @return
     */
    public static List<String> listAllFiles(String path){
         List<String> strs = FileUtils.getChildDirs(path);
         strs.addAll(FileUtils.getChildFiles(path));
         FileUtils.getChildFiles(path);
         return strs;
    }
    /**
     * 文件分类获取
     * @param isNowPath 是否 只是当前目录
     * 
     * String  文件类型  txt doc 等等
     * List<String> 存的路径
     * 
     * jpg xxx.xx
     *     xx.xx
     * 
     * 
     * @return
     */
    public static Map<String,List<String>> getFilesTypeMap(String path,boolean isNowPath){
        if(!new File(path).exists()){
            throw new RuntimeException(path+",文件夹不存在");
        }
        List<String> str = FileUtils.getChildFiles(path,isNowPath);
        Map<String,List<String>> map = new HashMap<String, List<String>>();
        for(String val:str){
            String type = StringUtil.getFileTypeByPath(val);
            if(map.get(type) == null){
                List<String> names = new ArrayList<String>();
                names.add(val);
                map.put(type, names);
            }else{
                List<String> names =map.get(type);
                names.add(val);
                map.put(type, names);
            }
        }
        return map;
    }
    /**
     * 获取所有目录级别下面的数据
     * @param path
     * @return
     */
    public static Map<String,List<String>> getFilesTypeMap(String path){
        return getFilesTypeMap(path, false);
    }

    /**
     * 获取目录下文件下面 某个类型的数据
     * @param path
     * @param type 文件类型
     * @param isNowPath 是否是 当前目录 , 不是所有 目录
     * @return
     */
    public static List<String> getFilesByType(String path,boolean isNowPath,String [] types){
        List<String> list = null;
        if(types != null && types.length >0){
            Map<String,List<String>> map = getFilesTypeMap(path,isNowPath);
            list = new ArrayList<String>();
            for(String type:types ){
                if(map.get(type)!= null){
                    list.addAll(map.get(type));
                }
            }
        }else{
            //如果没有说明 就是所有数据都要
             listAllFile(path, false);
             list = fileNames;
        }
        return list;
    }

    /**
     * 获取目录下文件下面 某个类型的数据
     * @param path
     * @param type 文件类型
     * @return
     */
    public static List<String> getFilesByType(String path,String [] types){
        return getFilesByType(path, false, types);
    }

    /**
     * 将InputStream  转化为输出数据
     * @param in 输入流
     * @param file 输出的文件对象
     */
    public static void copyInputStreamToFile(InputStream in ,File file){
        OutputStream out= null;
        try {
            out = new FileOutputStream(file);
            byte[] buff = new byte[1024];
            int len = 0;
            while ((len = in.read(buff)) > 0) {
                out.write(buff, 0, len);
            }
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{
            try {
                if(in != null){
                    in.close();
                }
            } catch (Exception e2) {
                // TODO: handle exception
            }finally{
                try {
                    if(out != null){
                        out.close();
                    }
                } catch (Exception e3) {
                    // TODO: handle exception
                }
            }
        }

    }


    /**
     * 将字符数据转化为我们的文件
     * @param data 字节数组
     * @param file 输出文件
     */
    public static void copyByte2File(byte [] data,File file){
        if ((data == null) || (data.length <= 0))
            return;

        OutputStream out = null;
        InputStream in = null;
        try {
            out = new FileOutputStream(file);
            in = new ByteInputStream(data,data.length );
            byte [] buff = new byte[1024];
            int len =  0;
            while((len = in.read(buff))>0){
                out.write(buff, 0, len);
            }
            out.flush();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{
            try {
                if(in != null){
                    in.close();
                }
            } catch (Exception e2) {
                // TODO: handle exception
            }finally{
                try {
                    if(out != null){
                        out.close();
                    }
                } catch (Exception e3) {
                    // TODO: handle exception
                }
            }
        }
    }

    /**
     * 将InputStream 转化为字节码文件
     * @return 
     */
    public static byte[] copyFileToByteArray(File file){
        InputStream  in = null;
        try {
            in = new FileInputStream(file);
            //获取字节码的数量,将字节码装
            int len = in.available();
            byte [] result = new byte[len];
            in.read(result);
            return result;
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{
            try {
                if(in != null){
                    in.close();
                }
            } catch (Exception e2) {
                // TODO: handle exception
            }
        }
        return null;

    }
    /**
     * 将InputStream 转化为字节码文件
     * @return 
     */
    public static byte[] copyInputStreamToByteArray(InputStream in){
        try {
            //获取字节码的数量,将字节码装
            int len = in.available();
            byte [] result = new byte[len];
            in.read(result);
            return result;
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{
            try {
                if(in != null){
                    in.close();
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        return null;

    }
    /**
     * xx "B","KB","MB","GB","PB"
     * 
     * 计算文件大小
     * @param size
     */
    private static void getFileSize(long size){
        long result  = size/1024;
        if(result >=1 && count <(sizeStr.length-2)){
            fileSize = size/1024;
            count = count +1;
            getFileSize(fileSize);
        }else{
            fileSize = size;
        }
    }

    /**
     * 
     * xx "B","KB","MB","GB","PB"
     * 
     * 计算文件大小
     * 我们用的时候,对于 静态的数据,需要清空 或者重新初始化,解决多次使用,导致数据堆积问题
     * @param fileSize
     * @return
     */
    public static String getFileSizeStr(long filesize){
        if(filesize <0){
            throw new RuntimeException("文件大小有问题");
        }
        //多次使用的时候,这个 就会用问题了,一定要清空
        count = 0;
        fileSize= 0;
        getFileSize(filesize);
        return fileSize+sizeStr[count];
    }

    /**
     * 文件分割
     * String pathname = "E:\\迅雷下载\\疯狂动物城.Zootopia.2016.HDCAM.XVID.AC3.中文字幕[rarbt]\\疯狂动物城.Zootopia.2016.HDCAM.XVID.AC3.中文字幕[rarbt].mp4";
     * 给定文件进行分割,默认分割的大小是500MB
     * @return 切割的文件名称
     * @throws Exception
     */
    public static String[] partitionFile(String pathname){
        return partitionFile(1024*1024*500,pathname) ;
    }

    /**
     * 文件分割
     * @param partSize
     * @param pathname
     * @return 
     * @throws Exception
     */
    private static String[] partitionFile(int partSize,String pathname){
        DataInputStream in  = null;
        String [] names = null;
        try {
            //DataOutputStream
            //通过DataOutputStream 这个对象完成文件的分割
            //缓存
            File file = new File(pathname);
            //long size = file.length();
            //1024*1024 就是 1MB
            //System.out.println(size/1024/1024);
            //获取分割文件的名称
            names = getPartitionFileNames(pathname, partSize);
            in = new DataInputStream( new FileInputStream(file));
            byte [] buff = new byte[1024];
            DataOutputStream out = null;
            for(String name:names){
                System.out.println(name);
                out = new DataOutputStream(new FileOutputStream(new File(name)));
                long size = partSize;
                while(size >0){
                    //当没有读取完的情况
                    if(in.read(buff) != -1){
                        out.write(buff, 0, buff.length);
                    }
                    size = size-buff.length;
                }

                out.close();
            }
        } catch (Exception e) {
            throw new RuntimeException("分割失败");
        }finally{
            try {
                if(in!= null ){
                    in.close();
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        return names;
    }

    /**
     * 新建md5文件
     * @param pathname 需要新建md5的文件名称
     * 
     * 创建的文件在那个目录中,然后文件的名称,添加了一个_md5
     */
    public static void newMD5(String pathname){
        String newFileName = StringUtil.getFileSimpleName(pathname)+"_yellowcong_md5."+StringUtil.getFileType(pathname);
        System.err.println("修改前MD5值\t"+FileUtils.getMd5(pathname));
        FileUtils.newMD5(pathname, newFileName);
        System.err.println("修改后MD5值\t"+FileUtils.getMd5(newFileName));
    }

    /**
     * 获取文件的MD5
     * @param filename 文件的名称,全路径的
     * @return
     */
    public static  String getMd5(String filename) {  
        String value = null;  
        FileInputStream in = null;  
        File file = new File(filename);
        try {  
            in = new FileInputStream(file);
            MappedByteBuffer byteBuffer = in.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length());  
            MessageDigest md5 = MessageDigest.getInstance("MD5");  
            md5.update(byteBuffer);  
            BigInteger bi = new BigInteger(1, md5.digest());  
            value = bi.toString(16);  
        } catch (Exception e) {  
            e.printStackTrace();  
        } finally {  
                if(null != in) {  
                    try {  
                    in.close();  
                } catch (IOException e) {  
                    e.printStackTrace();  
                }  
            }  
        }  
        return value;  
    }  
    /**
     * 给文件生成新的md5验证码
     * @param pathname 文件的全路径
     * @param newFileName  新文件的全路径
     */
    private static void newMD5(String pathname,String newFileName){
        //String pathname = "E:\\迅雷下载\\疯狂动物城.Zootopia.2016.HDCAM.XVID.AC3.中文字幕[rarbt]\\疯狂动物城.Zootopia.2016.HDCAM.XVID.AC3.中文字幕[rarbt].mp4";
        //分割文件
        String [] names = FileUtils.partitionFile(pathname);
        //创建一个空文件
        String newFile = pathname+".txt";
        FileUtils.copyStringToFile(UUID.randomUUID().toString(), newFile);

        //解决 md5 修改
        String [] newNames = new String[names.length+1];
        System.out.println(newNames.length);
        for(int i=0;i<names.length;i++){
            newNames[i] = names[i];
        }

        newNames[names.length] = newFile;
        //文件合并,多添加了一个空文件
        FileUtils.uniteFile(newNames, newFileName);

        //删除所有文件
        FileUtils.deletes(newNames);
    }
    /**
     * 删除多个文件
     * @param fileNames
     */
    public static void deletes(String [] fileNames){
        //删除文件
        for(String path:fileNames){
            new File(path).delete();
        }
    }
    /**
     * 合并文件
     * @param files 多个子文件
     * @param newFile 新的文件
     * @throws Exception 异常
     */
    public static void uniteFile(String[] files, String newFile) {
        DataOutputStream out = null;
        try {
            out = new DataOutputStream(new FileOutputStream(new File(newFile)));
            for(String name:files){
                FileInputStream in = new FileInputStream(new File(name));
                byte [] buff = new byte[1024];
                while((in.read(buff)) != -1){
                    out.write(buff,0,buff.length);
                }
                in.close();
            }
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }finally{
            try {
                if(out != null){
                out.close();
                }
            } catch (Exception e2) {
                // TODO: handle exception
            }

        }
    }

    /**
     * 根据给定的每个文件的大小来获取文件的数目
     * @param pathname 文件名称
     * @param partSize 每个部分的大小
     * @return
     */
    public static String [] getPartitionFileNames(String pathname,long partSize){
        File file = new File(pathname);
        long size = file.length();
        double num = (size/(double)partSize);
        //向上取整数,获取分割文件的数量
        num = Math.ceil(num);
        String [] names = new String [(int)num] ;
        for(int i=0;i<num;i++){
            names[i] = StringUtil.getFileSimpleName(pathname)+"_"+i+"."+StringUtil.getFileTypeByPath(pathname);
        }
        return names;
    }

    /**
     * 将String类型的数据直接转化为文本数据
     * @param str
     * @param filename
     */
    public static void copyStringToFile(String str,String filename){
        FileWriter out = null;
        try {
            //将Str 转化为文件
             out= new FileWriter(new File(filename));
            char []  chars = str.toCharArray();
            out.write(chars,0,chars.length);
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            try {
                if(out != null){
                    out.close();
                }
            } catch (Exception e) {
                // TODO: handle exception
            }
        }
    }

    /**
     * 将InputStream 的数据转化为String类型的数据
     * @param in 输入数据
     * @param encoding  编码方式gb2312,utf-8 
     * @return String
     */
    public static String copyInput2String(InputStream in,String encoding){
        String str = null;
        try {
            //写数据
            String line = null;
            BufferedReader reader = new BufferedReader(new InputStreamReader(in,encoding));

            StringBuffer sb = new StringBuffer();
            while((line = reader.readLine())!= null){
                 sb.append(line);
            }
            str = sb.toString();
        } catch (Exception e) {
            // TODO: handle exception
        }finally{
            try {
                if(in != null){
                    in.close();
                }
            } catch (Exception e2) {
                // TODO: handle exception
            }
        }
        return str;
    }

    /**
     * 将InputStream 的数据转化为String类型的数据,设定默认的编码编码方式为UTF-8
     * @param in 输入数据
     * @return String
     */
    public static String copyInput2String(InputStream in){
        return FileUtils.copyInput2String(in,"UTF-8");
    }
}
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

狂飙的yellowcong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值