网络安全加固:HarmonyOS开发中防抓包与数据加密
【摘要】 网络安全加固:防抓包与数据加密安全是隐形的护盾,看不见但必须坚固 一、背景与动机:为什么需要网络安全加固?你有没有想过:当你在咖啡厅连着公共WiFi,打开App输入密码登录时,旁边那个人用抓包工具可能已经看到了你的账号密码?这不是危言耸听。网络传输是透明的,不加密就是"裸奔"。 常见的安全风险风险说明危害等级中间人攻击攻击者拦截并篡改通信内容🔴 高数据泄露敏感信息被明文传输🔴 高重放攻...
网络安全加固:防抓包与数据加密
安全是隐形的护盾,看不见但必须坚固
一、背景与动机:为什么需要网络安全加固?
你有没有想过:当你在咖啡厅连着公共WiFi,打开App输入密码登录时,旁边那个人用抓包工具可能已经看到了你的账号密码?
这不是危言耸听。网络传输是透明的,不加密就是"裸奔"。
常见的安全风险
| 风险 | 说明 | 危害等级 |
|---|---|---|
| 中间人攻击 | 攻击者拦截并篡改通信内容 | 🔴 高 |
| 数据泄露 | 敏感信息被明文传输 | 🔴 高 |
| 重放攻击 | 攻击者重复发送已捕获的请求 | 🟡 中 |
| 接口滥用 | 接口被恶意调用、爬虫滥用 | 🟡 中 |
| 数据篡改 | 传输数据被恶意修改 | 🔴 高 |
安全加固的目标
- 防抓包:即使被抓包,也无法解密内容
- 防篡改:数据被篡改能检测出来
- 防重放:重复请求无法成功
- 防滥用:接口有签名验证,无法伪造
二、核心原理:多层安全防护
安全不是"加个HTTPS"就完事,而是多层防护:
flowchart TB
A[客户端请求] --> B[数据加密<br/>AES/RSA]
B --> C[请求签名<br/>HMAC-SHA256]
C --> D[添加时间戳<br/>防重放]
D --> E[HTTPS传输<br/>TLS加密]
E --> F[服务器验证]
F --> G{签名验证}
G -->|失败| H[拒绝请求]
G -->|成功| I{时间戳验证}
I -->|过期| J[拒绝请求]
I -->|有效| K[解密数据]
K --> L[业务处理]
L --> M[响应加密]
M --> N[返回客户端]
N --> O[客户端解密]
O --> P[业务使用]
classDef primary fill:#4A90E2,stroke:#2E5C8A,color:#fff
classDef warning fill:#F5A623,stroke:#C17D10,color:#fff
classDef error fill:#E74C3C,stroke:#C0392B,color:#fff
classDef info fill:#7ED321,stroke:#5BA318,color:#fff
class A,B,C,D,E,N,O,P primary
class F,G,I,K,L,M info
class H,J error
五层防护:
- 数据加密层:敏感数据加密传输
- 签名验证层:防篡改、防伪造
- 时间戳层:防重放攻击
- 传输加密层:HTTPS/TLS
- 服务端验证层:多重校验
三、代码实战:完整安全加固实现
示例1:加密工具类
// network/security/CryptoUtil.ets
import { cryptoFramework } from '@kit.CryptoArchitectureKit';
import { util } from '@kit.ArkTS';
/**
* 加密工具类
* 提供AES、RSA、HMAC等加密算法
*/
export class CryptoUtil {
/**
* AES加密(对称加密)
* 用于加密请求数据
* @param data 明文数据
* @param key 密钥(16字节)
* @returns Base64编码的密文
*/
static async aesEncrypt(data: string, key: string): Promise<string> {
try {
// 创建对称密钥
const symKey = await this.createAesKey(key);
// 创建加密器
const cipher = cryptoFramework.createCipher('AES256|GCM|PKCS7');
await cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, symKey, null);
// 加密数据
const input: cryptoFramework.DataBlob = {
data: new Uint8Array(Buffer.from(data, 'utf-8').buffer)
};
const output = await cipher.doFinal(input);
// 转Base64
const base64 = util.Base64Helper.encodeToStringSync(output.data);
return base64;
} catch (error) {
console.error('[CryptoUtil] AES加密失败:', error);
throw error;
}
}
/**
* AES解密
* @param encryptedData Base64编码的密文
* @param key 密钥
* @returns 明文数据
*/
static async aesDecrypt(encryptedData: string, key: string): Promise<string> {
try {
// 创建对称密钥
const symKey = await this.createAesKey(key);
// 创建解密器
const cipher = cryptoFramework.createCipher('AES256|GCM|PKCS7');
await cipher.init(cryptoFramework.CryptoMode.DECRYPT_MODE, symKey, null);
// Base64解码
const encrypted = util.Base64Helper.decodeSync(encryptedData);
// 解密数据
const input: cryptoFramework.DataBlob = { data: encrypted };
const output = await cipher.doFinal(input);
// 转字符串
return Buffer.from(output.data.buffer).toString('utf-8');
} catch (error) {
console.error('[CryptoUtil] AES解密失败:', error);
throw error;
}
}
/**
* RSA加密(非对称加密)
* 用于加密AES密钥或敏感数据
* @param data 明文数据
* @param publicKey 公钥
* @returns Base64编码的密文
*/
static async rsaEncrypt(data: string, publicKey: string): Promise<string> {
try {
// 创建公钥
const pubKey = await this.createRsaPublicKey(publicKey);
// 创建加密器
const cipher = cryptoFramework.createCipher('RSA1024|PKCS1');
await cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, pubKey, null);
// 加密数据
const input: cryptoFramework.DataBlob = {
data: new Uint8Array(Buffer.from(data, 'utf-8').buffer)
};
const output = await cipher.doFinal(input);
// 转Base64
return util.Base64Helper.encodeToStringSync(output.data);
} catch (error) {
console.error('[CryptoUtil] RSA加密失败:', error);
throw error;
}
}
/**
* HMAC-SHA256签名
* 用于请求签名验证
* @param data 待签名数据
* @param key 签名密钥
* @returns 十六进制签名字符串
*/
static async hmacSha256(data: string, key: string): Promise<string> {
try {
// 创建MAC
const mac = cryptoFramework.createMac('SHA256');
// 创建密钥
const keyBlob: cryptoFramework.DataBlob = {
data: new Uint8Array(Buffer.from(key, 'utf-8').buffer)
};
const symKey = await cryptoFramework.createSymKey(keyBlob);
// 初始化
await mac.init(symKey);
// 计算签名
const input: cryptoFramework.DataBlob = {
data: new Uint8Array(Buffer.from(data, 'utf-8').buffer)
};
await mac.update(input);
const result = await mac.doFinal();
// 转十六进制
return Array.from(result.data)
.map(b => b.toString(16).padStart(2, '0'))
.join('');
} catch (error) {
console.error('[CryptoUtil] HMAC签名失败:', error);
throw error;
}
}
/**
* MD5哈希
* 用于生成数据指纹
*/
static async md5(data: string): Promise<string> {
try {
const md = cryptoFramework.createMd('MD5');
const input: cryptoFramework.DataBlob = {
data: new Uint8Array(Buffer.from(data, 'utf-8').buffer)
};
await md.update(input);
const result = await md.digest();
return Array.from(result.data)
.map(b => b.toString(16).padStart(2, '0'))
.join('');
} catch (error) {
console.error('[CryptoUtil] MD5计算失败:', error);
throw error;
}
}
/**
* 创建AES密钥
* @private
*/
private static async createAesKey(key: string): Promise<cryptoFramework.SymKey> {
// 确保密钥长度为32字节(AES-256)
let keyBytes = Buffer.from(key, 'utf-8');
if (keyBytes.length < 32) {
// 不足32字节,填充
const padded = Buffer.alloc(32);
keyBytes.copy(padded);
keyBytes = padded;
} else if (keyBytes.length > 32) {
// 超过32字节,截取
keyBytes = keyBytes.slice(0, 32);
}
const keyBlob: cryptoFramework.DataBlob = {
data: new Uint8Array(keyBytes.buffer)
};
return await cryptoFramework.createSymKey(keyBlob);
}
/**
* 创建RSA公钥
* @private
*/
private static async createRsaPublicKey(publicKey: string): Promise<cryptoFramework.PubKey> {
const keyBlob: cryptoFramework.DataBlob = {
data: new Uint8Array(Buffer.from(publicKey, 'base64').buffer)
};
return await cryptoFramework.createPubKey(keyBlob);
}
}
示例2:请求签名器
// network/security/RequestSigner.ets
import { CryptoUtil } from './CryptoUtil';
/**
* 请求签名配置
*/
export interface SignConfig {
/** 签名密钥 */
secretKey: string;
/** 应用ID */
appId: string;
/** 时间戳有效期(毫秒) */
timestampTolerance?: number;
/** 是否启用签名 */
enabled?: boolean;
}
/**
* 请求签名器
* 为请求添加签名,防篡改、防重放
*/
export class RequestSigner {
private config: SignConfig;
constructor(config: SignConfig) {
this.config = {
timestampTolerance: 5 * 60 * 1000, // 默认5分钟
enabled: true,
...config
};
}
/**
* 签名请求
* @param method HTTP方法
* @param url 请求URL
* @param params URL参数
* @param body 请求体
* @returns 签名参数
*/
async sign(
method: string,
url: string,
params?: Record<string, any>,
body?: any
): Promise<Record<string, string>> {
if (!this.config.enabled) {
return {};
}
// 生成时间戳
const timestamp = Date.now().toString();
// 生成随机数
const nonce = this.generateNonce();
// 构建签名字符串
const signString = this.buildSignString(method, url, params, body, timestamp, nonce);
// 计算签名
const signature = await CryptoUtil.hmacSha256(signString, this.config.secretKey);
return {
'X-App-Id': this.config.appId,
'X-Timestamp': timestamp,
'X-Nonce': nonce,
'X-Signature': signature
};
}
/**
* 构建签名字符串
* @private
*/
private buildSignString(
method: string,
url: string,
params?: Record<string, any>,
body?: any,
timestamp?: string,
nonce?: string
): string {
// 按规则拼接签名字符串
const parts: string[] = [];
// 1. HTTP方法
parts.push(method.toUpperCase());
// 2. URL路径
const urlObj = new URL(url);
parts.push(urlObj.pathname);
// 3. 排序后的参数
if (params && Object.keys(params).length > 0) {
const sortedParams = Object.keys(params)
.sort()
.map(key => `${key}=${params[key]}`);
parts.push(sortedParams.join('&'));
}
// 4. 请求体(如果有)
if (body) {
const bodyString = typeof body === 'string' ? body : JSON.stringify(body);
parts.push(bodyString);
}
// 5. 时间戳
if (timestamp) {
parts.push(timestamp);
}
// 6. 随机数
if (nonce) {
parts.push(nonce);
}
return parts.join('\n');
}
/**
* 生成随机数
* @private
*/
private generateNonce(): string {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let nonce = '';
for (let i = 0; i < 16; i++) {
nonce += chars.charAt(Math.floor(Math.random() * chars.length));
}
return nonce;
}
/**
* 验证时间戳是否有效
*/
validateTimestamp(timestamp: string): boolean {
const ts = parseInt(timestamp, 10);
const now = Date.now();
const tolerance = this.config.timestampTolerance!;
return Math.abs(now - ts) <= tolerance;
}
}
示例3:数据加密器
// network/security/DataEncryptor.ets
import { CryptoUtil } from './CryptoUtil';
/**
* 加密配置
*/
export interface EncryptConfig {
/** AES密钥 */
aesKey: string;
/** RSA公钥(用于加密AES密钥) */
rsaPublicKey?: string;
/** 是否启用加密 */
enabled?: boolean;
/** 需要加密的字段 */
encryptFields?: string[];
}
/**
* 数据加密器
* 对敏感数据进行加密传输
*/
export class DataEncryptor {
private config: EncryptConfig;
private encryptedKey: string | null = null;
constructor(config: EncryptConfig) {
this.config = {
enabled: true,
encryptFields: [],
...config
};
}
/**
* 初始化加密器
* 生成加密后的AES密钥
*/
async init(): Promise<void> {
if (!this.config.enabled || !this.config.rsaPublicKey) {
return;
}
// 使用RSA加密AES密钥
this.encryptedKey = await CryptoUtil.rsaEncrypt(
this.config.aesKey,
this.config.rsaPublicKey
);
console.info('[DataEncryptor] 初始化完成');
}
/**
* 加密数据
* @param data 原始数据
* @returns 加密后的数据
*/
async encrypt(data: any): Promise<any> {
if (!this.config.enabled) {
return data;
}
// 如果是字符串,直接加密
if (typeof data === 'string') {
return await CryptoUtil.aesEncrypt(data, this.config.aesKey);
}
// 如果是对象,加密指定字段
if (typeof data === 'object' && data !== null) {
const encrypted = { ...data };
// 加密指定字段
for (const field of this.config.encryptFields || []) {
if (encrypted[field] !== undefined) {
encrypted[field] = await CryptoUtil.aesEncrypt(
String(encrypted[field]),
this.config.aesKey
);
encrypted[`${field}_encrypted`] = true; // 标记已加密
}
}
// 添加加密密钥(如果使用RSA)
if (this.encryptedKey) {
encrypted['_encrypted_key'] = this.encryptedKey;
}
return encrypted;
}
return data;
}
/**
* 解密数据
* @param data 加密数据
* @returns 解密后的数据
*/
async decrypt(data: any): Promise<any> {
if (!this.config.enabled) {
return data;
}
// 如果是字符串,直接解密
if (typeof data === 'string') {
return await CryptoUtil.aesDecrypt(data, this.config.aesKey);
}
// 如果是对象,解密指定字段
if (typeof data === 'object' && data !== null) {
const decrypted = { ...data };
// 解密指定字段
for (const field of this.config.encryptFields || []) {
const encryptedFlag = decrypted[`${field}_encrypted`];
if (encryptedFlag && decrypted[field]) {
decrypted[field] = await CryptoUtil.aesDecrypt(
decrypted[field],
this.config.aesKey
);
delete decrypted[`${field}_encrypted`];
}
}
// 移除加密密钥
delete decrypted['_encrypted_key'];
return decrypted;
}
return data;
}
/**
* 加密整个请求体
*/
async encryptBody(body: any): Promise<string> {
const json = JSON.stringify(body);
return await CryptoUtil.aesEncrypt(json, this.config.aesKey);
}
/**
* 解密整个响应体
*/
async decryptBody(encryptedBody: string): Promise<any> {
const json = await CryptoUtil.aesDecrypt(encryptedBody, this.config.aesKey);
return JSON.parse(json);
}
}
示例4:安全拦截器
// network/security/SecurityInterceptor.ets
import { Interceptor, RequestConfig, Response } from '../types';
import { RequestSigner } from './RequestSigner';
import { DataEncryptor } from './DataEncryptor';
/**
* 安全拦截器配置
*/
export interface SecurityConfig {
/** 签名配置 */
sign?: RequestSigner;
/** 加密配置 */
encryptor?: DataEncryptor;
/** 是否启用HTTPS校验 */
validateHttps?: boolean;
/** 不需要签名的URL */
noSignUrls?: string[];
/** 不需要加密的URL */
noEncryptUrls?: string[];
}
/**
* 安全拦截器
* 为请求添加安全防护
*/
export class SecurityInterceptor implements Interceptor {
private config: SecurityConfig;
constructor(config: SecurityConfig) {
this.config = config;
}
/**
* 请求拦截:签名和加密
*/
async beforeRequest(config: RequestConfig): Promise<RequestConfig> {
// 1. HTTPS校验
if (this.config.validateHttps && !config.url.startsWith('https://')) {
console.warn('[SecurityInterceptor] 非HTTPS请求:', config.url);
}
// 2. 请求签名
if (this.config.sign && !this.shouldSkip(config.url, this.config.noSignUrls)) {
const signHeaders = await this.config.sign.sign(
config.method || 'GET',
config.url,
config.params,
config.data
);
config.headers = {
...config.headers,
...signHeaders
};
}
// 3. 数据加密
if (this.config.encryptor && !this.shouldSkip(config.url, this.config.noEncryptUrls)) {
if (config.data) {
config.data = await this.config.encryptor.encrypt(config.data);
}
}
return config;
}
/**
* 响应拦截:解密
*/
async afterResponse<T>(response: Response<T>): Promise<Response<T>> {
// 解密响应数据
if (this.config.encryptor && response.data) {
const decrypted = await this.config.encryptor.decrypt(response.data);
return {
...response,
data: decrypted as T
};
}
return response;
}
/**
* 检查是否跳过
* @private
*/
private shouldSkip(url: string, skipList?: string[]): boolean {
if (!skipList) return false;
return skipList.some(skipUrl => url.includes(skipUrl));
}
}
示例5:证书锁定(Certificate Pinning)
// network/security/CertificatePinner.ets
/**
* 证书锁定配置
*/
export interface PinConfig {
/** 域名 */
hostname: string;
/** 证书公钥SHA256指纹 */
pins: string[];
}
/**
* 证书锁定器
* 防止中间人攻击
*/
export class CertificatePinner {
private pins: Map<string, string[]> = new Map();
constructor(configs: PinConfig[]) {
for (const config of configs) {
this.pins.set(config.hostname, config.pins);
}
}
/**
* 验证证书
* @param hostname 域名
* @param certificate 证书
*/
async validate(hostname: string, certificate: string): Promise<boolean> {
const expectedPins = this.pins.get(hostname);
if (!expectedPins || expectedPins.length === 0) {
// 没有配置锁定,跳过验证
return true;
}
// 计算证书指纹
const actualPin = await this.calculatePin(certificate);
// 验证指纹是否匹配
const isValid = expectedPins.includes(actualPin);
if (!isValid) {
console.error(`[CertificatePinner] 证书验证失败: ${hostname}`);
console.error(`期望指纹: ${expectedPins.join(', ')}`);
console.error(`实际指纹: ${actualPin}`);
}
return isValid;
}
/**
* 计算证书指纹
* @private
*/
private async calculatePin(certificate: string): Promise<string> {
// 计算SHA256指纹
const md = cryptoFramework.createMd('SHA256');
const input: cryptoFramework.DataBlob = {
data: new Uint8Array(Buffer.from(certificate, 'utf-8').buffer)
};
await md.update(input);
const result = await md.digest();
// 转十六进制
return Array.from(result.data)
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
}
示例6:完整安全配置示例
// network/security/config.ets
import { RequestSigner } from './RequestSigner';
import { DataEncryptor } from './DataEncryptor';
import { SecurityInterceptor } from './SecurityInterceptor';
import { CertificatePinner } from './CertificatePinner';
/**
* 初始化安全模块
*/
export function initSecurity(): SecurityInterceptor {
// 1. 创建请求签名器
const signer = new RequestSigner({
secretKey: 'your-secret-key-here', // 从安全存储获取
appId: 'your-app-id',
timestampTolerance: 5 * 60 * 1000,
enabled: true
});
// 2. 创建数据加密器
const encryptor = new DataEncryptor({
aesKey: 'your-aes-key-32-bytes-long!!!', // 32字节
rsaPublicKey: '...', // 服务器公钥
enabled: true,
encryptFields: ['password', 'idCard', 'phone'] // 需要加密的字段
});
// 3. 创建安全拦截器
const securityInterceptor = new SecurityInterceptor({
sign: signer,
encryptor: encryptor,
validateHttps: true,
noSignUrls: ['/auth/login', '/auth/register'], // 登录注册不签名
noEncryptUrls: ['/public/'] // 公开接口不加密
});
// 4. 配置证书锁定(可选)
const pinner = new CertificatePinner([
{
hostname: 'api.example.com',
pins: [
'sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=' // 实际证书指纹
]
}
]);
console.info('[Security] 安全模块初始化完成');
return securityInterceptor;
}
四、踩坑与注意事项
坑1:密钥硬编码
问题:密钥直接写在代码里,反编译后密钥泄露。
解决:密钥存储在安全区域:
// ❌ 错误:硬编码密钥
const secretKey = 'my-secret-key';
// ✅ 正确:从安全存储获取
import { huks } from '@kit.DeviceAuthenticationKit';
// 使用HUKS存储密钥
async function getSecretKey(): Promise<string> {
// 从安全硬件获取密钥
const key = await huks.getKey('app-secret-key');
return key;
}
坑2:时间戳不同步
问题:客户端时间不准,签名验证失败。
解决:使用服务器时间:
// 获取服务器时间
async function getServerTime(): Promise<number> {
const response = await fetch('/api/time');
return response.data.timestamp;
}
// 使用服务器时间签名
const serverTime = await getServerTime();
const timestamp = serverTime.toString();
坑3:证书锁定太严格
问题:证书更新后,App无法连接服务器。
解决:配置多个证书指纹:
const pinner = new CertificatePinner([{
hostname: 'api.example.com',
pins: [
'sha256/OLD_CERTIFICATE_PIN', // 旧证书
'sha256/NEW_CERTIFICATE_PIN' // 新证书
]
}]);
坑4:加密影响性能
问题:大文件加密耗时太长。
解决:分块加密 + 后台线程:
// 分块加密
async function encryptLargeFile(data: Uint8Array): Promise<Uint8Array> {
const chunkSize = 1024 * 1024; // 1MB
const chunks: Uint8Array[] = [];
for (let i = 0; i < data.length; i += chunkSize) {
const chunk = data.slice(i, i + chunkSize);
const encrypted = await encryptChunk(chunk);
chunks.push(encrypted);
}
return concat(chunks);
}
五、HarmonyOS 6 适配要点
1. HUKS 安全存储
// HarmonyOS 6 提供硬件级安全存储
import { huks } from '@kit.DeviceAuthenticationKit';
// 生成密钥并存储在安全硬件
await huks.generateKey('app-key', {
properties: [
{ tag: huks.HuksTag.HUKS_TAG_ALGORITHM, value: huks.HuksAlg.HUKS_ALG_AES },
{ tag: huks.HuksTag.HUKS_TAG_KEY_SIZE, value: huks.HuksKeySize.HUKS_AES_KEY_SIZE_256 }
]
});
// 使用密钥加密(密钥不离开安全硬件)
const encrypted = await huks.encrypt('app-key', plaintext);
2. 网络安全配置
// HarmonyOS 6 支持网络安全配置文件
// resources/rawfile/network_security_config.json
{
"network-security-config": {
"base-config": {
"trust-anchors": {
"certificates": "@rawfile/trusted_certs"
},
"cleartextTrafficPermitted": false
},
"domain-config": [
{
"domains": ["api.example.com"],
"pin-set": {
"pins": [
{
"digest": "SHA-256",
"value": "certificate-pin-here"
}
]
}
}
]
}
}
3. 生物识别增强
// HarmonyOS 6 支持生物识别保护敏感操作
import { userAuth } from '@kit.UserAuthenticationKit';
// 使用指纹/人脸验证
const authResult = await userAuth.authenticate({
challenge: new Uint8Array(32),
authType: [userAuth.UserAuthType.FINGERPRINT, userAuth.UserAuthType.FACE],
authTrustLevel: userAuth.AuthTrustLevel.ATL3
});
if (authResult.result === userAuth.UserAuthResult.SUCCESS) {
// 验证成功,执行敏感操作
}
六、总结
网络安全是应用的"隐形护盾",虽然用户看不见,但必须坚固:
| 防护层 | 技术 | 防护目标 |
|---|---|---|
| 数据加密 | AES/RSA | 防数据泄露 |
| 请求签名 | HMAC-SHA256 | 防篡改、防伪造 |
| 时间戳 | Timestamp + Nonce | 防重放 |
| HTTPS | TLS | 防中间人 |
| 证书锁定 | Pinning | 防伪造证书 |
记住几个原则:
- ✅ 密钥不能硬编码,存储在安全区域
- ✅ 敏感数据必须加密传输
- ✅ 重要请求必须签名验证
- ✅ 时间戳防止重放攻击
- ✅ 生产环境强制HTTPS
下一篇我们深入网络性能监控,看看如何分析和优化请求性能。
💡 最佳实践提示:建议在应用启动时检查安全配置,如HTTPS是否启用、证书是否有效等,发现问题及时告警。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)