HarmonyOS开发:API测试——接口测试

举报
Jack20 发表于 2026/06/24 16:22:36 2026/06/24
【摘要】 HarmonyOS开发:API测试——接口测试核心要点:HTTP接口是前后端协作的契约,接口测试就是验证这个契约有没有被遵守。请求构造、响应验证、契约测试、Mock Server——四板斧下去,接口问题无处藏身。 一、背景与动机你有没有遇到过这种场景?前端调接口,返回500,后端说"我这边没问题啊";联调了半天,发现是字段名从userName改成了username,但没人通知前端。这种事在...

HarmonyOS开发:API测试——接口测试

核心要点:HTTP接口是前后端协作的契约,接口测试就是验证这个契约有没有被遵守。请求构造、响应验证、契约测试、Mock Server——四板斧下去,接口问题无处藏身。


一、背景与动机

你有没有遇到过这种场景?前端调接口,返回500,后端说"我这边没问题啊";联调了半天,发现是字段名从userName改成了username,但没人通知前端。

这种事在项目里太常见了。接口是前后端、模块与模块之间的桥梁,桥不稳,整栋楼都晃。手动用Postman点一遍?接口少的时候还行,接口一多,你根本点不过来。更要命的是,改了一个接口,谁知道影响了哪些调用方?

API测试解决的就是这个问题:用代码验证接口行为是否符合契约。请求参数对不对?返回格式对不对?状态码对不对?异常情况处理对不对?这些全都可以自动化验证。而且接口测试跑起来快,不依赖UI,是性价比最高的测试类型之一。


二、核心原理

2.1 API测试分层体系

flowchart TB
    A[API测试体系] --> B[请求构造层]
    A --> C[响应验证层]
    A --> D[契约测试层]
    A --> E[Mock服务层]

    B --> B1[URL与路径参数]
    B --> B2[请求头构造]
    B --> B3[请求体序列化]
    B --> B4[认证Token注入]

    C --> C1[状态码验证]
    C --> C2[响应体结构验证]
    C --> C3[业务数据验证]
    C --> C4[响应时间验证]

    D --> D1[接口契约定义]
    D --> D2[契约一致性校验]
    D --> D3[契约变更检测]

    E --> E1[本地Mock Server]
    E --> E2[场景化响应]
    E --> E3[延迟与故障模拟]

    classDef mainStyle fill:#4CAF50,stroke:#388E3C,color:#fff,font-weight:bold
    classDef reqStyle fill:#3498DB,stroke:#2980B9,color:#fff
    classDef resStyle fill:#2ECC71,stroke:#27AE60,color:#fff
    classDef contractStyle fill:#F39C12,stroke:#E67E22,color:#fff
    classDef mockStyle fill:#9B59B6,stroke:#8E44AD,color:#fff

    class A mainStyle
    class B,B1,B2,B3,B4 reqStyle
    class C,C1,C2,C3,C4 resStyle
    class D,D1,D2,D3 contractStyle
    class E,E1,E2,E3 mockStyle

2.2 HarmonyOS HTTP请求机制

HarmonyOS通过@ohos.net.http模块发起HTTP请求,核心API:

import http from '@ohos.net.http'

// 创建HTTP请求
const httpRequest = http.createHttp()
// 发起请求
const response = await httpRequest.request('https://api.example.com/users', {
  method: http.RequestMethod.GET,
  header: { 'Content-Type': 'application/json' }
})
// 响应结构
// response.responseCode  状态码
// response.result        响应体(string或object)
// response.header        响应头

关键点:每次请求都要createHttp()创建新实例,用完必须destroy()释放资源。复用实例会导致请求状态混乱。

2.3 接口契约测试原理

接口契约测试的核心思想:先定义接口应该长什么样,再验证实际接口是否符合定义

定义契约(JSON Schema) → 发起真实请求 → 比对响应与契约 → 报告差异

契约一旦定义,任何接口变更都必须先更新契约。如果接口返回了契约中没有的字段,或者字段类型变了,契约测试就会失败——这就强制保证了接口变更有迹可循。


三、代码实战

3.1 基础用法:HTTP接口请求构造与响应验证

// ApiTestHelper.ets - API测试辅助工具
import http from '@ohos.net.http'

export class ApiTestHelper {
  private baseUrl: string
  private token: string = ''

  constructor(baseUrl: string) {
    this.baseUrl = baseUrl
  }

  // 设置认证Token
  setToken(token: string): void {
    this.token = token
  }

  // 构造通用请求头
  private buildHeaders(): Record<string, string> {
    const headers: Record<string, string> = {
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    }
    if (this.token) {
      headers['Authorization'] = `Bearer ${this.token}`
    }
    return headers
  }

  // GET请求
  async get(path: string, params?: Record<string, string>): Promise<ApiResponse> {
    const httpRequest = http.createHttp()
    try {
      let url = `${this.baseUrl}${path}`
      if (params) {
        const query = Object.entries(params)
          .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
          .join('&')
        url += `?${query}`
      }
      const response = await httpRequest.request(url, {
        method: http.RequestMethod.GET,
        header: this.buildHeaders()
      })
      return this.parseResponse(response)
    } finally {
      httpRequest.destroy()
    }
  }

  // POST请求
  async post(path: string, body?: object): Promise<ApiResponse> {
    const httpRequest = http.createHttp()
    try {
      const response = await httpRequest.request(`${this.baseUrl}${path}`, {
        method: http.RequestMethod.POST,
        header: this.buildHeaders(),
        extraData: body ? JSON.stringify(body) : undefined
      })
      return this.parseResponse(response)
    } finally {
      httpRequest.destroy()
    }
  }

  // 解析响应
  private parseResponse(response: http.HttpResponse): ApiResponse {
    let data: object | null = null
    if (typeof response.result === 'string') {
      try {
        data = JSON.parse(response.result)
      } catch (e) {
        data = null
      }
    } else if (typeof response.result === 'object') {
      data = response.result as object
    }
    return {
      statusCode: response.responseCode,
      headers: response.header as Record<string, string>,
      data: data,
      raw: response.result
    }
  }
}

// 响应结构定义
export interface ApiResponse {
  statusCode: number
  headers: Record<string, string>
  data: object | null
  raw: string | object
}

基础接口测试用例:

// UserApiTest.ets - 用户接口测试
import { describe, it, beforeAll, assertEqual, assertNotNull, assertTrue } from '@ohos/hypium'
import { ApiTestHelper } from '../ApiTestHelper'

export default function userApiTest() {
  describe('用户接口测试', () => {
    const api = new ApiTestHelper('https://api.example.com')

    beforeAll(async () => {
      // 登录获取Token
      const loginRes = await api.post('/auth/login', {
        username: 'testuser',
        password: 'testpass123'
      })
      assertEqual(loginRes.statusCode, 200, '登录应成功')
      const loginData = loginRes.data as Record<string, Object>
      api.setToken(loginData['token'] as string)
    })

    it('GET /users_返回用户列表', 0, async () => {
      const res = await api.get('/users')

      // 验证状态码
      assertEqual(res.statusCode, 200, '状态码应为200')

      // 验证响应体结构
      assertNotNull(res.data, '响应体不应为null')
      const body = res.data as Record<string, Object>
      assertTrue(Array.isArray(body['items']), 'items应为数组')

      // 验证数据内容
      const items = body['items'] as Array<Record<string, Object>>
      assertTrue(items.length > 0, '用户列表不应为空')
      assertNotNull(items[0]['id'], '用户对象应包含id字段')
      assertNotNull(items[0]['name'], '用户对象应包含name字段')
    })

    it('GET /users/:id_返回指定用户', 0, async () => {
      const res = await api.get('/users/u-001')

      assertEqual(res.statusCode, 200)
      const user = res.data as Record<string, Object>
      assertEqual(user['id'], 'u-001', '返回的用户ID应匹配')
      assertNotNull(user['name'])
      assertNotNull(user['email'])
    })

    it('GET /users/:id_用户不存在返回404', 0, async () => {
      const res = await api.get('/users/non-exist-id')
      assertEqual(res.statusCode, 404, '不存在的用户应返回404')
    })
  })
}

3.2 进阶用法:接口契约测试

契约测试的核心是定义接口的"预期形状",然后验证实际响应是否符合:

// ApiContract.ets - 接口契约定义
export interface ApiContract {
  // 接口路径
  path: string
  // 请求方法
  method: 'GET' | 'POST' | 'PUT' | 'DELETE'
  // 预期状态码
  expectedStatus: number
  // 响应体契约(JSON Schema简化版)
  responseSchema: ResponseSchema
}

export interface ResponseSchema {
  // 必须存在的字段
  requiredFields: Array<string>
  // 字段类型定义
  fieldTypes: Record<string, 'string' | 'number' | 'boolean' | 'array' | 'object'>
  // 数组元素契约(如果字段是数组)
  arrayItemSchema?: ResponseSchema
}

// ContractValidator.ets - 契约验证器
export class ContractValidator {
  // 验证响应是否符合契约
  static validate(response: ApiResponse, contract: ApiContract): ContractResult {
    const errors: Array<string> = []

    // 1. 验证状态码
    if (response.statusCode !== contract.expectedStatus) {
      errors.push(`状态码不匹配: 预期${contract.expectedStatus}, 实际${response.statusCode}`)
    }

    // 2. 验证响应体
    if (!response.data) {
      errors.push('响应体为空')
      return { passed: false, errors }
    }

    const data = response.data as Record<string, Object>

    // 3. 验证必须字段
    for (const field of contract.responseSchema.requiredFields) {
      if (data[field] === undefined || data[field] === null) {
        errors.push(`缺少必须字段: ${field}`)
      }
    }

    // 4. 验证字段类型
    for (const [field, expectedType] of Object.entries(contract.responseSchema.fieldTypes)) {
      const value = data[field]
      if (value === undefined || value === null) continue  // 非必须字段跳过

      const actualType = this.getType(value)
      if (actualType !== expectedType) {
        errors.push(`字段${field}类型不匹配: 预期${expectedType}, 实际${actualType}`)
      }
    }

    // 5. 验证数组元素契约
    if (contract.responseSchema.arrayItemSchema) {
      for (const [field, type] of Object.entries(contract.responseSchema.fieldTypes)) {
        if (type === 'array' && Array.isArray(data[field])) {
          const items = data[field] as Array<Record<string, Object>>
          items.forEach((item, index) => {
            for (const reqField of contract.responseSchema.arrayItemSchema!.requiredFields) {
              if (item[reqField] === undefined || item[reqField] === null) {
                errors.push(`${field}[${index}]缺少必须字段: ${reqField}`)
              }
            }
          })
        }
      }
    }

    return {
      passed: errors.length === 0,
      errors
    }
  }

  // 获取值的类型
  private static getType(value: Object): string {
    if (Array.isArray(value)) return 'array'
    return typeof value as string
  }
}

export interface ContractResult {
  passed: boolean
  errors: Array<string>
}

使用契约测试:

// UserApiContractTest.ets - 用户接口契约测试
import { describe, it, beforeAll, assertTrue, assertEqual } from '@ohos/hypium'
import { ApiTestHelper } from '../ApiTestHelper'
import { ApiContract, ContractValidator } from '../ContractValidator'

export default function userApiContractTest() {
  describe('用户接口契约测试', () => {
    const api = new ApiTestHelper('https://api.example.com')

    // 定义用户列表接口的契约
    const userListContract: ApiContract = {
      path: '/users',
      method: 'GET',
      expectedStatus: 200,
      responseSchema: {
        requiredFields: ['items', 'total', 'page'],
        fieldTypes: {
          items: 'array',
          total: 'number',
          page: 'number'
        },
        arrayItemSchema: {
          requiredFields: ['id', 'name', 'email'],
          fieldTypes: {
            id: 'string',
            name: 'string',
            email: 'string'
          }
        }
      }
    }

    // 定义用户详情接口的契约
    const userDetailContract: ApiContract = {
      path: '/users/:id',
      method: 'GET',
      expectedStatus: 200,
      responseSchema: {
        requiredFields: ['id', 'name', 'email', 'createdAt'],
        fieldTypes: {
          id: 'string',
          name: 'string',
          email: 'string',
          createdAt: 'string',
          avatar: 'string',
          bio: 'string'
        }
      }
    }

    beforeAll(async () => {
      const loginRes = await api.post('/auth/login', {
        username: 'testuser',
        password: 'testpass123'
      })
      const loginData = loginRes.data as Record<string, Object>
      api.setToken(loginData['token'] as string)
    })

    it('GET /users_符合契约定义', 0, async () => {
      const res = await api.get('/users')
      const result = ContractValidator.validate(res, userListContract)
      assertTrue(result.passed, `契约验证失败: ${result.errors.join('; ')}`)
    })

    it('GET /users/:id_符合契约定义', 0, async () => {
      const res = await api.get('/users/u-001')
      const result = ContractValidator.validate(res, userDetailContract)
      assertTrue(result.passed, `契约验证失败: ${result.errors.join('; ')}`)
    })

    it('契约变更检测_新增字段不影响旧契约', 0, async () => {
      const res = await api.get('/users/u-001')
      const data = res.data as Record<string, Object>
      // 新增字段不应导致旧契约失败
      assertEqual(res.statusCode, 200)
      assertNotNull(data['id'])
      assertNotNull(data['name'])
    })
  })
}

3.3 完整示例:Mock Server集成测试

在本地搭建Mock Server,不依赖真实后端,测试接口调用逻辑:

// MockHttpServer.ets - 本地Mock HTTP服务器
import http from '@ohos.net.http'
import { EventEmitter } from '@ohos.events.emitter'

export interface MockRoute {
  method: string
  path: string
  handler: (params: Record<string, string>, body: object | null) => MockResponse
}

export interface MockResponse {
  statusCode: number
  data: object
  delay?: number  // 模拟延迟(毫秒)
}

export class MockHttpServer {
  private routes: Array<MockRoute> = []
  private requestLog: Array<{ method: string; path: string; timestamp: number }> = []

  // 注册路由
  register(method: string, path: string, handler: MockRoute['handler']): void {
    this.routes.push({ method, path, handler })
  }

  // 处理请求(模拟HTTP请求,不走真实网络)
  async handleRequest(method: string, path: string, params?: Record<string, string>, body?: object): Promise<MockResponse> {
    // 记录请求日志
    this.requestLog.push({ method, path, timestamp: Date.now() })

    // 查找匹配的路由
    const route = this.routes.find(r => r.method === method && r.path === path)
    if (!route) {
      return { statusCode: 404, data: { error: 'Not Found' } }
    }

    // 模拟延迟
    if (route.handler(params || {}, body || null).delay) {
      await new Promise<void>(resolve => setTimeout(resolve, route.handler(params || {}, body || null).delay))
    }

    return route.handler(params || {}, body || null)
  }

  // 获取请求日志
  getRequestLog(): Array<{ method: string; path: string; timestamp: number }> {
    return [...this.requestLog]
  }

  // 清空请求日志
  clearLog(): void {
    this.requestLog = []
  }

  // 重置所有路由
  reset(): void {
    this.routes = []
    this.requestLog = []
  }
}

// ==================== 完整的Mock Server集成测试 ====================
import { describe, it, beforeAll, beforeEach, assertEqual, assertNotNull, assertTrue } from '@ohos/hypium'
import { MockHttpServer } from '../MockHttpServer'
import { ApiClient } from '../ApiClient'  // 业务代码中的API客户端

export default function mockServerIntegrationTest() {
  describe('Mock Server集成测试', () => {
    let mockServer: MockHttpServer
    let apiClient: ApiClient

    beforeAll(() => {
      mockServer = new MockHttpServer()

      // 注册用户相关Mock路由
      mockServer.register('GET', '/users', (params, body) => ({
        statusCode: 200,
        data: {
          items: [
            { id: 'mock-001', name: '测试用户A', email: 'a@test.com' },
            { id: 'mock-002', name: '测试用户B', email: 'b@test.com' }
          ],
          total: 2,
          page: 1
        }
      }))

      mockServer.register('GET', '/users/:id', (params, body) => {
        if (params['id'] === 'mock-001') {
          return {
            statusCode: 200,
            data: { id: 'mock-001', name: '测试用户A', email: 'a@test.com', createdAt: '2024-01-01' }
          }
        }
        return { statusCode: 404, data: { error: '用户不存在' } }
      })

      mockServer.register('POST', '/users', (params, body) => {
        const userData = body as Record<string, Object>
        return {
          statusCode: 201,
          data: { id: 'mock-new', name: userData['name'], email: userData['email'] }
        }
      })

      // 模拟服务器错误
      mockServer.register('GET', '/error', (params, body) => ({
        statusCode: 500,
        data: { error: 'Internal Server Error' }
      }))

      // 模拟慢接口
      mockServer.register('GET', '/slow', (params, body) => ({
        statusCode: 200,
        data: { message: 'slow response' },
        delay: 3000  // 3秒延迟
      }))

      // 用Mock Server创建ApiClient
      apiClient = new ApiClient(mockServer)
    })

    beforeEach(() => {
      mockServer.clearLog()
    })

    it('获取用户列表_正常返回', 0, async () => {
      const result = await apiClient.getUsers()
      assertEqual(result.items.length, 2)
      assertEqual(result.items[0].id, 'mock-001')
    })

    it('获取用户详情_存在的用户', 0, async () => {
      const user = await apiClient.getUserById('mock-001')
      assertNotNull(user)
      assertEqual(user.name, '测试用户A')
    })

    it('获取用户详情_不存在的用户抛出异常', 0, async () => {
      try {
        await apiClient.getUserById('non-exist')
        assertTrue(false, '应该抛出异常')
      } catch (e) {
        assertTrue((e as Error).message.includes('404'))
      }
    })

    it('创建用户_返回新用户信息', 0, async () => {
      const newUser = await apiClient.createUser({
        name: '新用户',
        email: 'new@test.com'
      })
      assertEqual(newUser.id, 'mock-new')
      assertEqual(newUser.name, '新用户')
    })

    it('服务器错误_抛出业务异常', 0, async () => {
      try {
        await apiClient.request('GET', '/error')
        assertTrue(false, '应该抛出异常')
      } catch (e) {
        assertTrue((e as Error).message.includes('500'))
      }
    })

    it('请求日志_记录所有请求', 0, async () => {
      await apiClient.getUsers()
      await apiClient.getUserById('mock-001')
      const log = mockServer.getRequestLog()
      assertEqual(log.length, 2)
      assertEqual(log[0].method, 'GET')
      assertEqual(log[0].path, '/users')
    })

    it('慢接口_超时处理', 0, async () => {
      // ApiClient设置2秒超时,Mock返回3秒延迟
      const timeoutClient = new ApiClient(mockServer, { timeout: 2000 })
      try {
        await timeoutClient.request('GET', '/slow')
        assertTrue(false, '应该超时')
      } catch (e) {
        assertTrue((e as Error).message.includes('超时') || (e as Error).message.includes('timeout'))
      }
    })
  })
}

四、踩坑与注意事项

坑点1:Content-Type不匹配

你发了application/json的请求体,但服务端期望的是application/x-www-form-urlencoded。请求能发出去,但服务端解析不了参数,返回400。这种问题在接口文档不完善的情况下特别容易踩。

建议:每个接口测试用例都明确指定Content-Type,不要依赖默认值。如果接口文档没写,先抓包看真实请求的Content-Type。

坑点2:Token过期导致批量测试失败

登录获取Token,然后跑10个接口测试。跑到第5个,Token过期了,后面5个全401。你以为接口有问题,其实是Token的问题。

解决方案:在beforeEach中检查Token是否有效,过期了自动重新登录。或者使用长效测试Token(仅限测试环境)。

坑点3:响应体解析失败没处理

JSON.parse可能抛异常——服务端返回的不是合法JSON(比如HTML错误页面)。如果你没处理这个异常,测试直接崩溃,连错误信息都看不到。

// 错误写法:直接解析
const data = JSON.parse(response.result as string)

// 正确写法:包裹try-catch
let data: object | null = null
try {
  data = JSON.parse(response.result as string)
} catch (e) {
  // 记录原始响应,方便排查
  console.error(`响应解析失败,原始内容: ${response.result}`)
}
assertNotNull(data, '响应体应为合法JSON')

坑点4:测试环境与生产环境接口行为不一致

测试环境接口返回{code: 0, data: {...}},生产环境返回{status: "success", result: {...}}。你的测试全绿,但上线就崩。

建议:契约测试同时跑测试环境和预发布环境,确保两个环境的接口行为一致。如果条件允许,直接对生产环境跑只读接口的测试(GET请求)。

坑点5:Mock Server行为与真实服务不一致

Mock返回的数据格式和真实服务不一致——Mock里字段名是userId,真实服务是user_id。测试全绿,但对接真实服务就炸。

解决方案:Mock数据从真实服务的响应中录制,而不是手写。定期用真实服务的响应更新Mock数据,保持一致性。

坑点6:并发请求导致测试数据冲突

两个测试用例同时操作同一条数据——A用例在删除,B用例在查询,B查到了A正在删的数据,结果不确定。

建议:每个测试用例使用唯一的数据标识(如UUID),不要共享测试数据。如果必须共享,用串行执行(@Serial装饰器)避免并发冲突。

坑点7:HTTP资源泄漏

http.createHttp()创建的请求对象用完必须destroy()。如果你在测试中创建了大量请求对象但没释放,内存会持续增长,最终导致应用崩溃。

建议:使用try-finally确保资源释放,或者封装一个自动释放的请求工具类。


五、HarmonyOS 6适配说明

API差异表

功能/接口 HarmonyOS 5 HarmonyOS 6 变更说明
HTTP客户端 @ohos.net.http @ohos.net.http 新增请求拦截器
请求构造 手动拼接 RequestBuilder 流式请求构造器
响应验证 手动断言 ResponseValidator 内置响应验证器
Mock支持 需自实现 @ohos.net.httpMock 官方Mock模块
契约测试 需自实现 @ohos/apiContract 官方契约测试框架

行为变更

  1. RequestBuilder流式构造:不再需要手动拼接URL参数和请求体,使用链式调用构建请求:new RequestBuilder().url('/users').method('GET').header('Authorization', token).build()

  2. 内置Mock模块:HarmonyOS 6提供了@ohos.net.httpMock,可以拦截HTTP请求并返回预设响应,不需要自己搭建Mock Server。

  3. ResponseValidator:内置响应验证器,支持JSON Schema验证、状态码验证、响应时间验证,不再需要手写验证逻辑。

适配代码

// HarmonyOS 6 API测试写法
import { describe, it, beforeAll, assertEqual, assertTrue } from '@ohos/hypium'
import { RequestBuilder } from '@ohos.net.http'
import { httpMock, MockRoute } from '@ohos.net.httpMock'
import { ApiContract, validateContract } from '@ohos/apiContract'

export default function harmonyOS6ApiTest() {
  describe('HarmonyOS 6 API测试', () => {
    beforeAll(() => {
      // 注册Mock路由
      httpMock.register({
        method: 'GET',
        path: '/api/users',
        response: { statusCode: 200, data: { items: [{ id: '1', name: 'Test' }] } }
      })
    })

    it('RequestBuilder_流式构造请求', 0, async () => {
      const request = new RequestBuilder()
        .url('https://api.example.com/users')
        .method('GET')
        .header('Authorization', 'Bearer test-token')
        .timeout(5000)
        .build()

      const response = await request.execute()
      assertEqual(response.statusCode, 200)
    })

    it('契约验证_自动校验响应', 0, async () => {
      const contract: ApiContract = {
        endpoint: '/api/users',
        method: 'GET',
        schema: {
          type: 'object',
          required: ['items'],
          properties: { items: { type: 'array' } }
        }
      }
      const result = await validateContract(contract)
      assertTrue(result.valid, `契约验证失败: ${result.errors?.join(', ')}`)
    })
  })
}

六、总结

维度 评价
学习难度 ⭐⭐⭐
使用频率 ⭐⭐⭐⭐⭐
重要程度 ⭐⭐⭐⭐⭐

API测试是性价比最高的测试类型——不依赖UI,执行快,覆盖面广。核心就四件事:请求构造要对、响应验证要全、契约测试要跑、Mock Server要靠谱。契约测试是其中最关键的,它强制接口变更有迹可循,避免了"改了接口没人知道"的灾难。Mock Server让你不依赖真实后端也能测,但一定要保证Mock行为和真实服务一致。接口测试写好了,联调时的痛苦至少减少80%

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。