当前位置: 首页 > news >正文

阿里云OSS Api工具类不使用sdk

本文工具实现了OSS简单的上传、下载、获取bucket列表功能,一个工具类搞定,不用集成oss sdk

v1签名算法

v1算法(v1算法将在2025年9月停用,旧的key不受影响,新key必须用v4)

v1签名工具类OssV1Signer.java

package org.example;import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.text.SimpleDateFormat;
import java.util.*;public class OssV1Signer {private static final String ALGORITHM = "HmacSHA1";private static final String SIGNING_ALGORITHM = "OSS";public static String getGMTDate() {Calendar cd = Calendar.getInstance();SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);sdf.setTimeZone(TimeZone.getTimeZone("GMT"));return sdf.format(cd.getTime());}public static String buildStringToSign(String httpMethod, String canonicalizedResource, String date) {return String.join("\n",httpMethod,"", // Content-MD5"", // Content-Typedate, // ✅ 修正:使用 datecanonicalizedResource);}public static byte[] hmacSHA1(byte[] key, String data) throws Exception {Mac mac = Mac.getInstance(ALGORITHM);mac.init(new SecretKeySpec(key, ALGORITHM));return mac.doFinal(data.getBytes("UTF-8"));}public static String toBase64(byte[] data) {return Base64.getEncoder().encodeToString(data);}public static String signRequest(String httpMethod, String canonicalizedResource, String accessKeyId, String secretKey, String date) throws Exception {String stringToSign = buildStringToSign(httpMethod, canonicalizedResource, date);byte[] signatureBytes = hmacSHA1(secretKey.getBytes("UTF-8"), stringToSign);String signature = toBase64(signatureBytes);return String.format("%s %s:%s", SIGNING_ALGORITHM, accessKeyId, signature);}
}

v1上传下载获取列表工具类

package org.example;import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.example.OssV1Signer;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;public class OssWebApi {private static final String ossBucket = "a-file";private static final String accessKeyId = "aaa";private static final String secretAccessKey = "bbb";private static final String endpoint = "oss-cn-beijing.aliyuncs.com";private final static String CHARSET_UTF8 = "utf8";private final static String ALGORITHM = "HmacSHA1";// OSS读取对象public static String getOssObj(String key) throws Exception {String signResourcePath = "/" + ossBucket + key;String url = "http://" + ossBucket + "." + endpoint;String date = OssV1Signer.getGMTDate();String canonicalizedResource = signResourcePath;// 使用 V1 签名工具类生成签名String signature = OssV1Signer.signRequest("GET", canonicalizedResource, accessKeyId, secretAccessKey,date);Map<String, String> headers = new HashMap<>();headers.put("Date", date);headers.put("Authorization", signature);return get(url + key, headers);}// OSS上传对象public static String putOssObj(String key, String content) throws Exception {String signResourcePath = "/" + ossBucket + key;String connectUrl = "http://" + ossBucket + "." + endpoint;String date = OssV1Signer.getGMTDate();String canonicalizedResource = signResourcePath;// 使用 V1 签名工具类生成签名String signature = OssV1Signer.signRequest("PUT", canonicalizedResource, accessKeyId, secretAccessKey,date);URL putUrl = new URL(connectUrl + key);HttpURLConnection connection;StringBuffer sbuffer = null;try {connection = (HttpURLConnection) putUrl.openConnection();connection.setDoOutput(true);connection.setRequestMethod("PUT");connection.setRequestProperty("Date", date);connection.setRequestProperty("Authorization", signature);connection.setReadTimeout(10000);connection.setConnectTimeout(10000);connection.connect();OutputStream out = connection.getOutputStream();out.write(new String(content).getBytes());out.flush();out.close();if (connection.getResponseCode() == 200) {InputStreamReader inputStream = new InputStreamReader(connection.getInputStream());BufferedReader reader = new BufferedReader(inputStream);sbuffer = new StringBuffer("");String lines;while ((lines = reader.readLine()) != null) {lines = new String(lines.getBytes(), "utf-8");sbuffer.append(lines);}reader.close();} else {return null;}connection.disconnect();} catch (IOException e) {e.printStackTrace();}return key;}/*** 获取当前 endpoint 下的所有 bucket 列表*/public static List<String> listBuckets() throws Exception {String url = "http://" + endpoint;String date = OssV1Signer.getGMTDate();String canonicalizedResource = "/";// 使用 V1 签名工具类生成签名String signature = OssV1Signer.signRequest("GET", canonicalizedResource, accessKeyId, secretAccessKey,date);Map<String, String> headers = new HashMap<>();headers.put("Date", date);headers.put("Authorization", signature);String responseXml = get(url, headers);return parseBucketNamesFromXml(responseXml);}// 工具方法:发起 GET 请求public static String get(String url, Map<String,String> head) throws IOException {HttpClient client = HttpClients.createDefault();HttpGet httpGet = new HttpGet(url);for (String key : head.keySet()) {httpGet.setHeader(key, head.get(key));}HttpResponse response = client.execute(httpGet);HttpEntity entity = response.getEntity();return EntityUtils.toString(entity, "utf-8");}// 工具方法:解析 XML 返回的 bucket 列表private static List<String> parseBucketNamesFromXml(String xmlResponse) {List<String> bucketNames = new ArrayList<>();try {DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();Document doc = builder.parse(new InputSource(new StringReader(xmlResponse)));NodeList buckets = doc.getElementsByTagName("Name");for (int i = 0; i < buckets.getLength(); i++) {Node node = buckets.item(i);if (node.getNodeType() == Node.ELEMENT_NODE) {Element element = (Element) node;bucketNames.add(element.getTextContent());}}} catch (Exception e) {throw new RuntimeException("Failed to parse XML response", e);}return bucketNames;}}

v1示例调用

package org.example;import java.io.IOException;
import java.util.*;/*** Hello world!*/
public class App {private static final String ossBucket = "a-file";private static final String accessKeyId = "aaa";private static final String secretAccessKey = "bbb"; //可根据自己的oss产品自行更改域名private static final String endpoint = "oss-cn-beijing.aliyuncs.com/";public static void main(String[] args) throws Exception {/* */try {List<String> buckets = OssWebApi.listBuckets();System.out.println("Available Buckets: " + buckets);String putResult = OssWebApi.putOssObj("/test/abc.txt", "this is test content");System.out.println(putResult);String getResult = OssWebApi.getOssObj("/test/abc.txt");System.out.println(getResult);} catch (IOException e) {e.printStackTrace();}}
}

v4签名算法

v4为推荐使用算法

v4签名工具类

package org.example;import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringUtils;import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.*;public class OssV4Signer {private static final String ALGORITHM = "HmacSHA256";private static final String TERMINATOR = "aliyun_v4_request";private static final String SECRET_KEY_PREFIX = "aliyun_v4";private static final String SIGNING_ALGORITHM = "OSS4-HMAC-SHA256";private static final String SERVICE = "oss";public static final String DATE_FORMAT = "yyyyMMdd'T'HHmmss'Z'";public static final String DATE_ONLY_FORMAT = "yyyyMMdd";// 获取当前时间戳(ISO8601)public static String getDateTime() {SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);sdf.setTimeZone(TimeZone.getTimeZone("UTC"));return sdf.format(new Date());}public static String getDateStamp() {SimpleDateFormat sdf = new SimpleDateFormat(DATE_ONLY_FORMAT);sdf.setTimeZone(TimeZone.getTimeZone("UTC"));return sdf.format(new Date());}// HMAC-SHA256 工具public static byte[] hmacSHA256(byte[] key, String data) throws Exception {Mac mac = Mac.getInstance(ALGORITHM);mac.init(new SecretKeySpec(key, ALGORITHM));return mac.doFinal(data.getBytes(StandardCharsets.UTF_8));}// Hex 编码public static String toHex(byte[] data) {return Hex.encodeHexString(data);}// URL 编码(保留斜杠)public static String urlEncode(String value) {try {return URLEncoder.encode(value, StandardCharsets.UTF_8.toString()).replace("+", "%20").replace("*", "%2A").replace("%7E", "~").replace("%2F", "/");} catch (UnsupportedEncodingException e) {throw new RuntimeException(e);}}// 构建 Canonical Request// 构建 Canonical Requestpublic static String buildCanonicalRequest(String method,String path,Map<String, String> query,TreeMap<String, String> headers,Set<String> signedHeaders,String hashedPayload) throws Exception {// ✅ 提取 Host 头用于判断是否是 Virtual Host 模式String host = headers.get("host");String actualPath = path;if (host != null && host.contains(".")) {String[] parts = host.split("\\.");// 判断是否为 4 级域名:bucket.oss-cn-region.aliyuncs.comif (parts.length == 4 && parts[2].equals("aliyuncs")) {String bucket = parts[0];actualPath = "/" + bucket + path;}}StringBuilder canonical = new StringBuilder();canonical.append(method).append("\n");// ✅ 使用修正后的 pathcanonical.append(urlEncode(actualPath)).append("\n");TreeMap<String, String> orderedQuery = new TreeMap<>();if (query != null) {for (Map.Entry<String, String> entry : query.entrySet()) {orderedQuery.put(urlEncode(entry.getKey()), urlEncode(entry.getValue()));}}String queryString = orderedQuery.isEmpty() ? "" : StringUtils.join(orderedQuery, "&");canonical.append(queryString).append("\n");for (Map.Entry<String, String> h : headers.entrySet()) {canonical.append(h.getKey().toLowerCase()).append(":").append(h.getValue().trim()).append("\n");}canonical.append("\n");canonical.append(String.join(";", signedHeaders)).append("\n");canonical.append(hashedPayload);return canonical.toString();}/*** 构建用于签名的 StringToSign*/public static String buildStringToSign(String canonicalRequest, String datetime, String scope) throws Exception {// 1️⃣ 计算 Canonical Request 的 SHA-256 哈希MessageDigest digest = MessageDigest.getInstance("SHA-256");byte[] canonicalHash = digest.digest(canonicalRequest.getBytes(StandardCharsets.UTF_8));// 2️⃣ 转成 Hex 格式String hashHex = toHex(canonicalHash);// 3️⃣ 拼接 StringToSignreturn String.join("\n",SIGNING_ALGORITHM,datetime,scope,hashHex);}// 构建 SigningKeypublic static byte[] buildSigningKey(String secretKey, String dateStamp, String region) throws Exception {byte[] kSecret = (SECRET_KEY_PREFIX + secretKey).getBytes(StandardCharsets.UTF_8);byte[] kDate = hmacSHA256(kSecret, dateStamp);byte[] kRegion = hmacSHA256(kDate, region);byte[] kService = hmacSHA256(kRegion, SERVICE);return hmacSHA256(kService, TERMINATOR);}// 构建 Scopepublic static String buildScope(String dateStamp, String region) {return String.join("/",dateStamp,region,SERVICE,TERMINATOR);}// 构建 Authorization Headerpublic static String buildAuthorizationHeader(String accessKeyId, String signature, String scope, Set<String> signedHeaders) {StringBuilder auth = new StringBuilder(SIGNING_ALGORITHM).append(" Credential=").append(accessKeyId).append("/").append(scope);if (signedHeaders != null && !signedHeaders.isEmpty()) {auth.append(", AdditionalHeaders=").append(String.join(";", signedHeaders));}auth.append(", Signature=").append(signature);return auth.toString();}// 主签名函数// 主签名函数public static String signRequest(String method,String path,Map<String, String> query,Map<String, String> headers,String payloadHash,String accessKeyId,String secretKey,String region,String datetime) throws Exception {String dateStamp = datetime.substring(0, 8);TreeMap<String, String> sortedHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);if (headers != null) {for (Map.Entry<String, String> entry : headers.entrySet()) {sortedHeaders.put(entry.getKey(), entry.getValue());}}Set<String> signedHeaders = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);for (String key : Arrays.asList("host", "x-oss-date", "x-oss-security-token")) {if (sortedHeaders.containsKey(key)) {signedHeaders.add(key.toLowerCase());}}for (String key : sortedHeaders.keySet()) {if (key.toLowerCase().startsWith("x-oss-")) {signedHeaders.add(key.toLowerCase());}}// ✅ 构建 Canonical RequestString canonicalRequest = buildCanonicalRequest(method, path, query, sortedHeaders, signedHeaders, payloadHash);// ✅ 构建 StringToSignString stringToSign = buildStringToSign(canonicalRequest, datetime, buildScope(dateStamp, region));// ✅ 计算 Signing Key 和 Signaturebyte[] signingKey = buildSigningKey(secretKey, dateStamp, region);String signature = toHex(hmacSHA256(signingKey, stringToSign));// ✅ 打印调试信息(用于比对服务端返回)System.out.println("=== Signature Debug Info ===");System.out.println("CanonicalRequest:\n" + canonicalRequest);System.out.println("\nStringToSign:\n" + stringToSign);System.out.println("\nComputed Signature: " + signature);System.out.println("============================");return buildAuthorizationHeader(accessKeyId, signature, buildScope(dateStamp, region), signedHeaders);}}

v4上传下载获取列表工具类

package org.example;import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;public class OssWebv4Api {private static final String ossBucket= "a-file";private static final String accessKeyId= "aaa";private static final String secretAccessKey= "bbb"; //可根据自己的oss产品自行更改域名private static final String endpoint= "oss-cn-beijing.aliyuncs.com";private final static String CHARSET_UTF8 = "utf8";//OSS读取public static String getOssObj(String key) throws Exception {String host = ossBucket + "." + endpoint;String uri = "/" + key;String method = "GET";String region = "cn-beijing";String datetime = OssV4Signer.getDateTime();Map<String, String> headers = new HashMap<>();headers.put("Host", host);headers.put("x-oss-date", datetime);headers.put("x-oss-content-sha256", "UNSIGNED-PAYLOAD");headers.put("Content-Type", "application/octet-stream");String payloadHash = "UNSIGNED-PAYLOAD"; // GET 不需要 bodyString authorization = OssV4Signer.signRequest(method, uri, null, headers, payloadHash,accessKeyId, secretAccessKey, region, datetime);headers.put("Authorization", authorization);String url = "http://" + host + uri;return get(url, headers);}//OSS上传public static String putOssObj(String key, String content) throws Exception {String host = ossBucket + "." + endpoint.replace("/", "");// => "vvvtimes-file.oss-cn-beijing.aliyuncs.com"String uri = "/" + key; // => /test.txt// 示例:/vvvtimes-file/test.txtString method = "PUT";String region = "cn-beijing";String datetime = OssV4Signer.getDateTime();Map<String, String> headers = new HashMap<>();headers.put("Host", host);headers.put("x-oss-date", datetime);headers.put("x-oss-content-sha256", "UNSIGNED-PAYLOAD"); // 改为固定值headers.put("Content-Type", "application/octet-stream");String authorization = OssV4Signer.signRequest(method, uri, null, headers, "UNSIGNED-PAYLOAD", // 同样传 UNSIGNED-PAYLOADaccessKeyId, secretAccessKey, region, datetime);headers.put("Authorization", authorization);URL putUrl = new URL("http://" + host + uri);HttpURLConnection connection = (HttpURLConnection) putUrl.openConnection();connection.setRequestMethod("PUT");connection.setDoOutput(true);for (Map.Entry<String, String> header : headers.entrySet()) {connection.setRequestProperty(header.getKey(), header.getValue());}connection.connect();try (OutputStream out = connection.getOutputStream()) {out.write(content.getBytes(StandardCharsets.UTF_8));}if (connection.getResponseCode() == 200) {return key;} else {InputStream errorStream = connection.getErrorStream();if (errorStream != null) {String errMsg = readStream(errorStream);System.err.println("Error Response: " + errMsg);}return null;}}private static String readStream(InputStream inputStream) throws IOException {BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));StringBuilder sb = new StringBuilder();String line;while ((line = reader.readLine()) != null) {sb.append(line).append("\n");}return sb.toString();}public static String get(String url,Map<String,String> head)throws IOException {HttpClient client = HttpClients.createDefault();HttpGet httpGet = new HttpGet(url);for(String key : head.keySet()){httpGet.setHeader(key,head.get(key));}HttpResponse response = client.execute(httpGet);response.getEntity().getContent();HttpEntity entity = response.getEntity();return EntityUtils.toString(entity, "utf-8");}public static String buildGetSignData(String Date,String CanonicalizedResource){return  "GET" + "\n"+ "\n"+ "\n"+ Date + "\n"+ CanonicalizedResource;}public static String buildPutSignData(String Date,String CanonicalizedResource){return  "PUT" + "\n"+ "\n"+ "\n"+ Date + "\n"+ CanonicalizedResource;}public static String getGMTDate(){Calendar cd = Calendar.getInstance();SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);sdf.setTimeZone(TimeZone.getTimeZone("GMT"));return sdf.format(cd.getTime());}/*** 获取当前 endpoint 下的所有 bucket 列表(使用阿里云 OSS V4 签名)* @return bucket 名称列表* @throws IOException*/public static List<String> listBuckets() throws Exception {String host = endpoint;String uri = "/";String method = "GET";String region = "cn-beijing"; // 根据实际 endpoint 修改String datetime = OssV4Signer.getDateTime();Map<String, String> headers = new HashMap<>();headers.put("Host", host);headers.put("x-oss-date", datetime);headers.put("x-oss-content-sha256", "UNSIGNED-PAYLOAD");String payloadHash = "UNSIGNED-PAYLOAD";// ✅ 使用 OSS V4 签名String authorization = OssV4Signer.signRequest(method,uri,null,                // query 参数为空headers,payloadHash,accessKeyId,secretAccessKey,region,datetime);headers.put("Authorization", authorization);URL url = new URL("http://" + host + uri);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setRequestMethod(method);for (Map.Entry<String, String> header : headers.entrySet()) {conn.setRequestProperty(header.getKey(), header.getValue());}conn.connect();if (conn.getResponseCode() == 200) {String responseXml = readStream(conn.getInputStream());return parseBucketNamesFromXml(responseXml);} else {String errMsg = readStream(conn.getErrorStream());System.err.println("Error Response: " + errMsg);throw new RuntimeException("List buckets failed with code: " + conn.getResponseCode());}}public static String buildListSignData(String Date) {return "GET\n\n\n" + Date + "\n/";}private static List<String> parseBucketNamesFromXml(String xmlResponse) {List<String> bucketNames = new ArrayList<>();try {DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();Document doc = builder.parse(new InputSource(new StringReader(xmlResponse)));NodeList buckets = doc.getElementsByTagName("Name");for (int i = 0; i < buckets.getLength(); i++) {Node node = buckets.item(i);if (node.getNodeType() == Node.ELEMENT_NODE) {Element element = (Element) node;bucketNames.add(element.getTextContent());}}} catch (Exception e) {throw new RuntimeException("Failed to parse XML response", e);}return bucketNames;}private static final String SERVICE = "s3";private static final String TERMINATOR = "aws4_request";public static String getSigningKey(String key, String dateStamp, String regionName, String serviceName) {byte[] kDate = hmacSHA256(key.getBytes(), dateStamp);byte[] kRegion = hmacSHA256(kDate, regionName);byte[] kService = hmacSHA256(kRegion, serviceName);return new String(hmacSHA256(kService, TERMINATOR));}public static byte[] hmacSHA256(byte[] key, String data) {try {Mac mac = Mac.getInstance("HmacSHA256");mac.init(new SecretKeySpec(key, "HmacSHA256"));return mac.doFinal(data.getBytes(CHARSET_UTF8));} catch (Exception e) {throw new RuntimeException(e);}}public static String toHex(byte[] data) {StringBuilder sb = new StringBuilder();for (byte b : data) {sb.append(String.format("%02x", b & 0xff));}return sb.toString();}public static String getDateStamp() {SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");sdf.setTimeZone(TimeZone.getTimeZone("GMT"));return sdf.format(new Date());}}

v4调用示例

package org.example;import java.io.IOException;
import java.util.*;/*** Hello world!*/
public class App {private static final String ossBucket = "a-file";private static final String accessKeyId = "aaa";private static final String secretAccessKey = "bbb"; //可根据自己的oss产品自行更改域名private static final String endpoint = "oss-cn-beijing.aliyuncs.com/";public static void main(String[] args) throws Exception {try {System.out.println("List buckets: " + OssWebv4Api.listBuckets());System.out.println("Upload object: " + OssWebv4Api.putOssObj("test.txt", "Hello OSS V4!"));System.out.println("Read object: " + OssWebv4Api.getOssObj("test.txt"));} catch (IOException e) {e.printStackTrace();} catch (Exception e) {throw new RuntimeException(e);}}
}

相关文章:

  • 通过 Terraform 构建您的第一个 Azure Linux 虚拟机
  • AWS EC2 使用Splunk DB connect 连接 RDS mysql
  • Missashe考研日记—Day44-Day50
  • 怎么判断文件是否支持多线程下载
  • orzdba.gz 下载解压使用教程:MySQL/InnoDB 监控命令参数详解与实战技巧
  • 优先级队列 模版题单
  • YOLOv8源码修改(5)- YOLO知识蒸馏(下)设置蒸馏超参数:以yolov8-pose为例
  • [C++] 洛谷B3959(GESP2024三月四级)做题
  • LLM多平台统一调用系统-LiteLLM概述
  • C++ 中的引用参数(Reference Parameter)‌
  • 【DeepSeek】计算机科学与技术专业的学习顺序
  • Vue3编译器:静态提升原理
  • 【Simulink】IEEE5/IEEE9/IEEE14/IEEE30/IEEE33/IEEE39仿真模型
  • 【Day36】
  • openjdk底层(hotspot)汇编指令的内存分布
  • 关于多类型数据划分清洗的整理
  • ISO 20000体系:服务请求管理、问题管理、事件管理区别与联系
  • BAT32G113 发送互补PWM
  • 第十九章:数据治理之数据指标(一):数据指标工具之【指标口径管理系统】与【指标数据查询系统】
  • (九)PMSM驱动控制学习---无感控制之高阶滑膜观测器
  • 网站建设没有业务怎么办/百度上怎么免费开店
  • 做投资要关注哪些网站/品牌推广外包公司
  • 用自己电脑做外网访问网站/自动点击器安卓
  • wordpress怎么删回复/合肥百度网站排名优化
  • 国际b2b免费网站/关键词推广排名软件
  • 绥化做网站/如何做百度免费推广