当前位置:   article > 正文

JAVA实现百度网盘文件上传_java上传文件到百度网盘

java上传文件到百度网盘
JAVA实现百度网盘文件上传
配置信息的获取可以看专栏中的其他博客
一、常量配置
package com.shp.dev.service.baidu;

/**
 * @Description: TODO 百度网盘基本常量信息
 */
public interface Constant {

    String APP_ID="24110959";
    String APP_NAME="云存储";
    String APP_KEY="cVQ2xjHGyzsIzq3kxKXK5dEzclBDGrxG";
    String SECRET_KEY="DaZzd20bxXf9PPNOArPEpICsxc4yMgpf";
    String SING_key="aA4nWN4oMyRpJ6^N%1K7X0jmeFfUhlkq";
    String APP_PATH="/apps/"+APP_NAME+"/";

    //单位mb
    // 普通用户单个分片大小固定为4MB(文件大小如果小于4MB,无需切片,直接上传即可),单文件总大小上限为4G。
    //普通会员用户单个分片大小上限为16MB,单文件总大小上限为10G。
    //超级会员用户单个分片大小上限为32MB,单文件总大小上限为20G。
    Integer UNIT=32;


    //获取授权码,需要扫码登陆
    String GET_CODE_URL="https://openapi.baidu.com/oauth/2.0/authorize?response_type=code&client_id="+APP_KEY+"&redirect_uri=oob&scope=basic,netdisk&display=tv&qrcode=1&force_login=1";

    //获取到的授权码
    String CODE="389853bcabdb033c1bcf3e6b5a6dba61";

    //根据授权码换取token
    String GET_TOKEN_BY_CODE="https://openapi.baidu.com/oauth/2.0/token?grant_type=authorization_code&code="+CODE+"&client_id="+APP_KEY+"&client_secret="+SECRET_KEY+"&redirect_uri=oob";

    //获取到的TOKEN
    String RTOKEN="122.fec5f9d6dd1644c2c57c89cc510f7ec8.YBMpVZwjo9y5kSMFnVmSMJL9dj25T5X02gjLwV8.1J2sEw";
    String ATOKEN="121.03680b5ab4a5fe8dba360b09f161595e.Yga1poFnDVL6-w3_cDt3xtJtMUKbHuoCaO3CmYO.TxDw4Q";


    //操作文件 copy, mover, rename, delete
    String FILE_MANAGER_URL=" https://pan.baidu.com/rest/2.0/xpan/file";

    //预上传
    String GET_READY_FILE_URL="https://pan.baidu.com/rest/2.0/xpan/file";

    //分片上传
    String SLICING_UPLOAD_FILE_URL="https://d.pcs.baidu.com/rest/2.0/pcs/superfile2";

    //下载文件
    String DOWN_LOUE_URL="https://pan.baidu.com/rest/2.0/xpan/multimedia";

    //文件搜索
    String FILE_SEARCH="https://pan.baidu.com/rest/2.0/xpan/file?method=search";


}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
二、文件上传工具
package com.shp.dev.service.baidu;

import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;

import static com.shp.dev.service.baidu.Constant.*;

/**
 * @Description: TODO 上传文件到百度网盘
 */

@Slf4j
public class FileUtils {

    @SneakyThrows
    public static void main(String[] args) {
        //不能有空格
        String filePath = "C:\\Users\\Administrator\\Desktop\\分片\\";
        String fileName = "a.jpg";
        System.out.println(save(filePath, fileName));
    }

    /**
     * @Description: TODO 保存文件
     * @param: filePath 文件路径
     * @param: fileName 文件名称
     * return 文件下载地址
     */
    private static String save(String filePath, String fileName) {
        //本地文件地址
        String absoluteFilePath = filePath + fileName;
        //云端文件地址
        String cloudPath = APP_PATH + fileName;

        //文件分片并获取md5值
        File file = new File(absoluteFilePath);
        File[] separate = separate(absoluteFilePath, UNIT);
        StringBuffer md5s = new StringBuffer();
        if (separate.length == 1) {
            md5s.append(getMD5(separate[0]));
        }
        if (separate.length > 1) {
            for (int i = 0; i < separate.length; i++) {
                md5s.append(getMD5(separate[i]) + "\",\"");
                log.info("正在分片,{}{}", separate[i].toString(), i);
            }
            String s = md5s.toString();
            md5s = new StringBuffer(s.substring(0, md5s.length() - 3));
        }

        //预上传
        String precreate = precreate(cloudPath, file.length(), 0, md5s.toString());
        log.info("预上传{}", precreate);

        //分片上传
        String upload = upload(cloudPath, (String) new JSONObject(precreate).get("uploadid"), separate);
        log.info("分片上传{}", upload);

        //创建文件
        String create = create(fileName, file.length(), 0, md5s.toString());
        log.info("创建文件{}", create);

        //获取下载地址
        String downUrl = getDownUrl(fileName);
        log.info("获取下载地址{}", downUrl);

        return downUrl;
    }


    /**
     * @Description: TODO 获取下载地址
     * @param: fileName 文件名
     */
    private static String getDownUrl(String fileName) {
        String fileSearch = HttpUtil.get(FILE_SEARCH + "&access_token=" + ATOKEN + "&key=" + fileName);
        JSONObject jsonObject = new JSONObject(fileSearch);
        JSONArray list = jsonObject.getJSONArray("list");
        JSONObject listJSONObject = list.getJSONObject(0);
        Long fs_id = listJSONObject.getLong("fs_id");
        String url = DOWN_LOUE_URL + "?method=filemetas&access_token=" + ATOKEN + "&fsids=[" + fs_id + "]&dlink=1";
        String s = HttpUtil.get(url);
        JSONObject sJsonObject = new JSONObject(s);
        JSONArray jsonArray = sJsonObject.getJSONArray("list");
        JSONObject jsonObjectClient = jsonArray.getJSONObject(0);
        String dlink = jsonObjectClient.getStr("dlink");
        return dlink;
    }

    /**
     * @Description: TODO 创建文件
     * @param: fileName 文件名称
     * @param: size 文件大小 字节
     * @param: isDir 0文件 1目录(设置为目录是 size要设置为0)
     * @param: blockList (文件的md5值) 可以把文件分为多个,然后分批上传
     * @return: java.lang.String
     */
    private static String create(String fileName, Long size, Integer isDir, String blockList) {
        String strURL = FILE_MANAGER_URL + "?method=create&access_token=" + ATOKEN;
        String params = "path=" + APP_PATH + fileName + "&size=" + size + "&autoinit=1&block_list=[\"" + blockList + "\"]&isdir=" + isDir;
        return open(strURL, params, "POST");
    }

    /**
     * @Description: TODO 分片上传
     * @param: path 上传到百度网盘的地址
     * @param: uploadid 上传的id
     * @param: filePath 本地文件的地址
     * @return: java.lang.String
     */
    private static String upload(String path, String uploadid, File[] files) {
        try {

            for (int i = 0; i < files.length; i++) {
                String url = SLICING_UPLOAD_FILE_URL + "?method=upload" +
                        "&access_token=" + ATOKEN +
                        "&type=tmpfile&partseq=" + i +
                        "&path=" + path +
                        "&uploadid=" + uploadid;
                String s = sendFile(url, files[i]);
                log.info("正在上传分片文件{}{}", s, i);
            }

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


    /**
     * @Description: TODO 预上传
     * @param: cloudPath 云端路径
     * @param: size 文件大小 字节
     * @param: isDir 0文件 1目录(设置为目录是 size要设置为0)
     * @param: blockList (文件的md5值) 可以把文件分为多个,然后分批上传
     * @return: java.lang.String
     */
    private static String precreate(String cloudPath, Long size, Integer isDir, String blockList) {
        String strURL = GET_READY_FILE_URL + "?method=precreate&access_token=" + ATOKEN;
        String params = "path=" + cloudPath + "&size=" + size + "&autoinit=1&block_list=[\"" + blockList + "\"]&isdir=" + isDir;
        return open(strURL, params, "POST");
    }


    /**
     * @Description: TODO 获取md5值
     * String path 文件地址
     */
    private final static String[] strHex = {"0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};

    private static String getMD5(File path) {
        StringBuilder buffer = new StringBuilder();
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] b = md.digest(org.apache.commons.io.FileUtils.readFileToByteArray(path));
            for (int value : b) {
                int d = value;
                if (d < 0) {
                    d += 256;
                }
                int d1 = d / 16;
                int d2 = d % 16;
                buffer.append(strHex[d1]).append(strHex[d2]);
            }
            return buffer.toString();
        } catch (Exception e) {
            return null;
        }
    }


    /**
     * @Description: TODO
     * @param: strURL 网址,可以是 http://aaa?bbb=1&ccc=2 拼接的
     * @param: params 拼接的body参数也就是form表单的参数  ddd=1&eee=2
     * @param: method 请求方式 get/post/put/delte等
     * @return: java.lang.String
     */
    private static String open(String strURL, String params, String method) {
        try {
            URL url = new URL(strURL);// 创建连接
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setUseCaches(false);
            connection.setInstanceFollowRedirects(true);
            connection.setRequestMethod(method);
            connection.setRequestProperty("Accept", "application/json");// 设置接收数据的格式
            connection.setRequestProperty("Content-Type", "application/json");// 设置发送数据的格式
            connection.connect();
            OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream(), StandardCharsets.UTF_8);// utf-8编码
            out.append(params);
            out.flush();
            out.close(); // 读取响应
            int length = connection.getContentLength();// 获取长度
            InputStream is = connection.getInputStream();
            if (length != -1) {
                byte[] data = new byte[length];
                byte[] temp = new byte[512];
                int readLen = 0;
                int destPos = 0;
                while ((readLen = is.read(temp)) > 0) {
                    System.arraycopy(temp, 0, data, destPos, readLen);
                    destPos += readLen;
                }
                return new String(data, StandardCharsets.UTF_8);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 向指定 URL 发送POST方法的请求
     *
     * @param url   发送请求的 URL
     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @return 所代表远程资源的响应结果
     */
    public static String sendFile(String url, String param, String file) {
        if (url == null || param == null) {
            return url;
        }

        PrintWriter out = null;
        BufferedReader in = null;
        String result = "";
        try {
            URL realUrl = new URL(url);
            // 打开和URL之间的连接
            URLConnection conn = realUrl.openConnection();
            // 设置通用的请求属性
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 发送POST请求必须设置如下两行
            conn.setDoOutput(true);
            conn.setDoInput(true);
            //设置链接超时时间为2秒
            conn.setConnectTimeout(1000);
            //设置读取超时为2秒
            conn.setReadTimeout(1000);
            // 获取URLConnection对象对应的输出流
            out = new PrintWriter(conn.getOutputStream());
            out.write(file);
            // 发送请求参数
            out.print(param);
            // flush输出流的缓冲
            out.flush();
            // 定义BufferedReader输入流来读取URL的响应
            in = new BufferedReader(
                    new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            System.out.println(e.getMessage() + "地址:" + url);
            return null;
        }
        //使用finally块来关闭输出流、输入流
        finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (IOException ex) {
                System.out.println(ex.getMessage());
                return null;
            }
        }
        return result;
    }


    /**
     * @param: filePath
     * @param: unit 单个文件大小
     * @return: 返回文件的目录
     */
    private static File[] separate(Object obj, Integer unit) {

        try {

            InputStream bis = null;//输入流用于读取文件数据
            OutputStream bos = null;//输出流用于输出分片文件至磁盘
            File file = null;
            if (obj instanceof String) {
                file = new File((String) obj);
            }
            if (obj instanceof File) {
                file = (File) obj;
            }

            String filePath = file.getAbsolutePath();
            File newFile = new File(filePath.substring(0, filePath.lastIndexOf("\\") + 1));
            String directoryPath = newFile.getAbsolutePath();
            long splitSize = unit * 1024 * 1024;//单片文件大小,MB
            if (file.length() < splitSize) {
                log.info("文件小于单个分片大小,无需分片{}", file.length());
                return new File[]{file};
            }


            //分片二
            //RandomAccessFile in=null;
            //RandomAccessFile out =null;
            //long length=file.length();//文件大小
            //long count=length%splitSize==0?(length/splitSize):(length/splitSize+1);//文件分片数
            //byte[] bt=new byte[1024];
            //in=new RandomAccessFile(file, "r");
            //for (int i = 1; i <= count; i++) {
            //    out = new RandomAccessFile(new File(filePath+"."+i), "rw");//定义一个可读可写且后缀名为.part的二进制分片文件
            //    long begin = (i-1)*splitSize;
            //    long end = i* splitSize;
            //    int len=0;
            //    in.seek(begin);
            //    while (in.getFilePointer()<end&&-1!=(len=in.read(bt))) {
            //        out.write(bt, 0, len);
            //    }
            //    out.close();
            //}


            //分片一
            bis = new BufferedInputStream(new FileInputStream(file));
            long writeByte = 0;//已读取的字节数
            int len = 0;
            byte[] bt = new byte[1024];
            while (-1 != (len = bis.read(bt))) {
                if (writeByte % splitSize == 0) {
                    if (bos != null) {
                        bos.flush();
                        bos.close();
                    }
                    bos = new BufferedOutputStream(new FileOutputStream(filePath + "." + (writeByte / splitSize + 1) + ".part"));
                }
                writeByte += len;
                bos.write(bt, 0, len);
            }


            log.info("文件分片成功!");

            //排除被分片的文件
            if (newFile.isDirectory()) {
                File[] files = newFile.listFiles();
                File[] resultFiles = new File[files.length - 1];
                int j = 0;
                for (int i = 0; i < files.length; i++) {
                    if (!files[i].equals(file)) {
                        resultFiles[j] = files[i];
                        j++;
                    }
                }
                return resultFiles;
            }

            bos.flush();
            bos.close();
            bis.close();
            return new File[0];
        } catch (Exception e) {
            log.info("文件分片失败!");
            e.printStackTrace();
        }
        return null;
    }

    //splitNum:要分几片,currentDir:分片后存放的位置,subSize:按多大分片
    public static File[] nioSpilt(Object object, int splitNum, String currentDir, double subSize) {
        try {
            File file = null;
            if (object instanceof String) {
                file = new File((String) object);
            }
            if (object instanceof String) {
                file = (File) object;
            }
            FileInputStream fis = new FileInputStream(file);
            FileChannel inputChannel = fis.getChannel();
            FileOutputStream fos;
            FileChannel outputChannel;
            long splitSize = (long) subSize;
            long startPoint = 0;
            long endPoint = splitSize;
            for (int i = 1; i <= splitNum; i++) {
                fos = new FileOutputStream(currentDir + i);
                outputChannel = fos.getChannel();
                inputChannel.transferTo(startPoint, splitSize, outputChannel);
                startPoint += splitSize;
                endPoint += splitSize;
                outputChannel.close();
                fos.close();
            }
            inputChannel.close();
            fis.close();
            File newFile = new File(file.getAbsolutePath().substring(0, file.getAbsolutePath().lastIndexOf("\\") + 1));
            if (newFile.isDirectory()) {
                return newFile.listFiles();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new File[0];
    }

    /**
     * @Description: TODO 发送文件
     * @param: url 发送地址
     * @param: file 发送文件
     * @return: java.lang.String
     */
    private static String sendFile(String url, File file) {
        try {
            MultipartEntityBuilder builder = MultipartEntityBuilder.create();
            builder.setContentType(ContentType.MULTIPART_FORM_DATA);
            builder.addBinaryBody("file", file);
            String body = "";
            //创建httpclient对象
            CloseableHttpClient client = HttpClients.createDefault();
            //创建post方式请求对象
            HttpPost httpPost = new HttpPost(url);
            //设置请求参数
            HttpEntity httpEntity = builder.build();
            httpPost.setEntity(httpEntity);
            //执行请求操作,并拿到结果(同步阻塞)
            CloseableHttpResponse response = client.execute(httpPost);
            //获取结果实体
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                //按指定编码转换结果实体为String类型
                body = EntityUtils.toString(entity, "utf-8");
            }
            EntityUtils.consume(entity);
            //释放链接
            response.close();
            return body;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438
  • 439
  • 440
  • 441
  • 442
  • 443
  • 444
  • 445
  • 446
  • 447
  • 448
  • 449
  • 450
  • 451
  • 452
  • 453
  • 454
  • 455
  • 456
  • 457
  • 458
  • 459
  • 460
  • 461
  • 462
  • 463
  • 464
  • 465
  • 466
  • 467
  • 468
  • 469
  • 470
  • 471
  • 472
  • 473
  • 474
  • 475
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/604111
推荐阅读
相关标签
  

闽ICP备14008679号