数据持久化与网络请求全攻略:Preferences、关系数据库、HTTP实战

系列文章 :鸿蒙NEXT开发实战系列 -- 第4篇(共5篇) 适合人群 :有ArkUI基础,想掌握数据存储和网络请求的开发者 开发环境 :DevEco Studio 5.0.5+ | HarmonyOS NEXT (API 14) 阅读时长:约25分钟

上一篇:状态管理一文通 | 下一篇:性能优化实战指南


一、引言:数据持久化与网络请求的重要性

在移动应用开发中,数据是应用的血液。一个优秀的应用不仅要能展示数据,更要能高效地存储数据和获取网络数据。想象一下:如果每次打开应用都要重新登录、每次查看历史记录都要等待网络加载、每次操作都要担心数据丢失------这样的应用体验将是灾难性的。

HarmonyOS NEXT提供了完整的数据管理解决方案:

  • Preferences:轻量级键值存储,适合保存用户设置、配置信息

  • RDB关系型数据库:结构化数据存储,适合保存复杂业务数据

  • HTTP网络请求:与服务器交互,获取和提交数据

本文将系统讲解这三种数据管理方式,并通过完整的实战项目,帮助你构建健壮的数据层架构。无论你是开发工具类应用、社交App还是企业管理软件,这些知识都必不可少。


二、Preferences轻量级存储教程

2.1 什么是Preferences

Preferences是HarmonyOS提供的轻量级键值存储方案,类似于Android的SharedPreferences和iOS的UserDefaults。它适用于:

  • 用户设置(主题、语言、通知开关)

  • 应用配置(服务器地址、版本号)

  • 简单状态标记(是否首次启动、是否已登录)

特点

  • 数据以键值对形式存储

  • 支持的数据类型:numberstringbooleanArray

  • 数据存储在内存中,读写速度快

  • 支持持久化到文件系统

2.2 基本用法

2.2.1 获取Preferences实例
复制代码
import { preferences } from '@kit.ArkData';

// 获取Preferences实例
const store = await preferences.getPreferences(context, 'myStore');
2.2.2 写入数据
复制代码
// 写入不同类型的数据
await store.put('userName', '张三');
await store.put('userAge', 25);
await store.put('isVip', true);

// 持久化到文件系统
await store.flush();
2.2.3 读取数据
复制代码
// 读取数据,需要提供默认值
const userName = await store.get('userName', '');
const userAge = await store.get('userAge', 0);
const isVip = await store.get('isVip', false);
2.2.4 删除数据
复制代码
// 删除单个键
await store.delete('userName');

// 清空所有数据
await store.clear();

// 持久化更改
await store.flush();

2.3 实战:存储用户设置

让我们实现一个用户设置页面,保存用户的偏好设置:

复制代码
// UserSettings.ets
import { preferences } from '@kit.ArkData';
import { Context } from '@kit.AbilityKit';

// 用户设置接口
interface UserSettings {
  theme: 'light' | 'dark';
  fontSize: number;
  notificationsEnabled: boolean;
  language: string;
}

// 默认设置
const defaultSettings: UserSettings = {
  theme: 'light',
  fontSize: 16,
  notificationsEnabled: true,
  language: 'zh-CN'
};

// 设置管理器
class SettingsManager {
  private store: preferences.Preferences | null = null;
  private context: Context;

  constructor(context: Context) {
    this.context = context;
  }

  // 初始化
  async init() {
    this.store = await preferences.getPreferences(this.context, 'userSettings');
  }

  // 保存设置
  async saveSettings(settings: UserSettings) {
    if (!this.store) {
      throw new Error('SettingsManager未初始化');
    }

    await this.store.put('settings', JSON.stringify(settings));
    await this.store.flush();
    console.info('设置已保存');
  }

  // 读取设置
  async getSettings(): Promise<UserSettings> {
    if (!this.store) {
      throw new Error('SettingsManager未初始化');
    }

    const settingsStr = await this.store.get('settings', '');
    if (settingsStr === '') {
      return defaultSettings;
    }

    try {
      return JSON.parse(settingsStr as string) as UserSettings;
    } catch (e) {
      console.error('解析设置失败:', e);
      return defaultSettings;
    }
  }

  // 重置设置
  async resetSettings() {
    await this.saveSettings(defaultSettings);
  }
}

// 使用示例
@Component
struct SettingsPage {
  @State settings: UserSettings = defaultSettings;
  private settingsManager: SettingsManager | null = null;

  async aboutToAppear() {
    this.settingsManager = new SettingsManager(getContext(this));
    await this.settingsManager.init();
    this.settings = await this.settingsManager.getSettings();
  }

  build() {
    Column() {
      Text('用户设置')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 20 })

      // 主题选择
      Row() {
        Text('主题模式')
          .width(120)
        Select([
          { value: '浅色模式' },
          { value: '深色模式' }
        ])
          .selected(this.settings.theme === 'dark' ? 1 : 0)
          .onSelect(async (index: number) => {
            this.settings.theme = index === 0 ? 'light' : 'dark';
            await this.settingsManager?.saveSettings(this.settings);
          })
      }
      .width('100%')
      .padding(10)

      // 字体大小
      Row() {
        Text('字体大小')
          .width(120)
        Slider({
          value: this.settings.fontSize,
          min: 12,
          max: 24,
          step: 2
        })
          .onChange(async (value: number) => {
            this.settings.fontSize = value;
            await this.settingsManager?.saveSettings(this.settings);
          })
        Text(`${this.settings.fontSize}px`)
          .margin({ left: 10 })
      }
      .width('100%')
      .padding(10)

      // 通知开关
      Row() {
        Text('通知提醒')
          .width(120)
        Toggle({ type: ToggleType.Switch, isOn: this.settings.notificationsEnabled })
          .onChange(async (isOn: boolean) => {
            this.settings.notificationsEnabled = isOn;
            await this.settingsManager?.saveSettings(this.settings);
          })
      }
      .width('100%')
      .padding(10)

      // 重置按钮
      Button('重置设置')
        .margin({ top: 20 })
        .onClick(async () => {
          await this.settingsManager?.resetSettings();
          this.settings = defaultSettings;
        })
    }
    .padding(20)
    .width('100%')
  }
}

2.4 踩坑记录

踩坑1:忘记调用flush()

问题 :写入数据后没有调用flush(),导致数据没有持久化,应用重启后数据丢失。

原因 :Preferences的数据默认存储在内存中,只有调用flush()才会写入文件系统。

解决方案 :每次修改数据后都调用flush(),或者封装一个自动flush的方法:

复制代码
async putAndFlush(key: string, value: preferences.ValueType) {
  await this.store?.put(key, value);
  await this.store?.flush(); // 立即持久化
}
踩坑2:并发读写问题

问题:多个地方同时读写同一个Preferences文件,导致数据不一致。

解决方案:使用单例模式管理Preferences实例:

复制代码
class PreferencesSingleton {
  private static instances: Map<string, preferences.Preferences> = new Map();

  static async getInstance(context: Context, name: string): Promise<preferences.Preferences> {
    if (!this.instances.has(name)) {
      const store = await preferences.getPreferences(context, name);
      this.instances.set(name, store);
    }
    return this.instances.get(name)!;
  }
}
踩坑3:存储大量数据

问题:将大量数据(如列表、图片)存储在Preferences中,导致性能问题。

原因:Preferences不适合存储大量数据,它的设计目的是轻量级配置存储。

解决方案:大数据应使用RDB或文件存储,Preferences只存储配置信息。


三、关系型数据库(RDB)使用教程

3.1 RDB简介

RDB(Relational Database)是HarmonyOS提供的关系型数据库,基于SQLite实现。它适用于:

  • 结构化数据存储(用户信息、订单记录)

  • 复杂查询(条件筛选、排序、分页)

  • 数据关联(多表查询、事务处理)

特点

  • 支持标准SQL语法

  • 支持事务处理

  • 支持数据加密

  • 支持数据库版本升级

3.2 创建数据库和表

3.2.1 获取RDB Store
复制代码
import { relationalStore } from '@kit.ArkData';

const STORE_CONFIG: relationalStore.RdbStoreConfig = {
  name: 'myApp.db',
  securityLevel: relationalStore.SecurityLevel.S1
};

const store = await relationalStore.getRdbStore(context, STORE_CONFIG);
3.2.2 创建表
复制代码
// 创建用户表
const createTableSql = `
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    age INTEGER,
    email TEXT UNIQUE,
    created_at TEXT DEFAULT CURRENT_TIMESTAMP
  )
`;

await store.execute(createTableSql);

3.3 CRUD操作

3.3.1 插入数据
复制代码
// 插入单条数据
const user: relationalStore.ValuesBucket = {
  name: '张三',
  age: 25,
  email: 'zhangsan@example.com'
};

const rowId = await store.insert('users', user);
console.info(`插入成功,行ID: ${rowId}`);

// 批量插入
const users: relationalStore.ValuesBucket[] = [
  { name: '李四', age: 30, email: 'lisi@example.com' },
  { name: '王五', age: 28, email: 'wangwu@example.com' }
];

for (const user of users) {
  await store.insert('users', user);
}
3.3.2 查询数据
复制代码
// 查询所有用户
const resultSet = await store.query('users', ['id', 'name', 'age', 'email']);

// 遍历结果
while (resultSet.goToNextRow()) {
  const id = resultSet.getLong(resultSet.getColumnIndex('id'));
  const name = resultSet.getString(resultSet.getColumnIndex('name'));
  const age = resultSet.getLong(resultSet.getColumnIndex('age'));
  const email = resultSet.getString(resultSet.getColumnIndex('email'));

  console.info(`用户: ${name}, 年龄: ${age}, 邮箱: ${email}`);
}

resultSet.close();

// 条件查询
const predicates = new relationalStore.RdbPredicates('users');
predicates.equalTo('age', 25);
predicates.orderByAsc('name');

const filteredResultSet = await store.query(predicates, ['id', 'name', 'age']);
3.3.3 更新数据
复制代码
const updatePredicates = new relationalStore.RdbPredicates('users');
updatePredicates.equalTo('name', '张三');

const updateValues: relationalStore.ValuesBucket = {
  age: 26,
  email: 'zhangsan_new@example.com'
};

const changedRows = await store.update(updateValues, updatePredicates);
console.info(`更新了 ${changedRows} 行`);
3.3.4 删除数据
复制代码
const deletePredicates = new relationalStore.RdbPredicates('users');
deletePredicates.equalTo('name', '王五');

const deletedRows = await store.delete(deletePredicates);
console.info(`删除了 ${deletedRows} 行`);

3.4 实战:用户信息管理

让我们实现一个完整的用户信息管理模块:

复制代码
// UserManager.ets
import { relationalStore } from '@kit.ArkData';
import { Context } from '@kit.AbilityKit';

// 用户接口
interface User {
  id?: number;
  name: string;
  age: number;
  email: string;
  phone?: string;
}

// 用户管理器
class UserManager {
  private store: relationalStore.RdbStore | null = null;
  private context: Context;

  constructor(context: Context) {
    this.context = context;
  }

  // 初始化数据库
  async init() {
    const config: relationalStore.RdbStoreConfig = {
      name: 'userManager.db',
      securityLevel: relationalStore.SecurityLevel.S1
    };

    this.store = await relationalStore.getRdbStore(this.context, config);

    // 创建用户表
    const createSql = `
      CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        age INTEGER,
        email TEXT UNIQUE,
        phone TEXT,
        created_at TEXT DEFAULT (datetime('now','localtime')),
        updated_at TEXT DEFAULT (datetime('now','localtime'))
      )
    `;

    await this.store.execute(createSql);
    console.info('数据库初始化完成');
  }

  // 添加用户
  async addUser(user: User): Promise<number> {
    if (!this.store) throw new Error('数据库未初始化');

    const values: relationalStore.ValuesBucket = {
      name: user.name,
      age: user.age,
      email: user.email,
      phone: user.phone || ''
    };

    const rowId = await this.store.insert('users', values);
    console.info(`添加用户成功,ID: ${rowId}`);
    return rowId;
  }

  // 获取所有用户
  async getAllUsers(): Promise<User[]> {
    if (!this.store) throw new Error('数据库未初始化');

    const resultSet = await this.store.query('users', 
      ['id', 'name', 'age', 'email', 'phone']);

    const users: User[] = [];
    while (resultSet.goToNextRow()) {
      users.push({
        id: resultSet.getLong(resultSet.getColumnIndex('id')),
        name: resultSet.getString(resultSet.getColumnIndex('name')),
        age: resultSet.getLong(resultSet.getColumnIndex('age')),
        email: resultSet.getString(resultSet.getColumnIndex('email')),
        phone: resultSet.getString(resultSet.getColumnIndex('phone'))
      });
    }
    resultSet.close();

    return users;
  }

  // 根据ID查询用户
  async getUserById(id: number): Promise<User | null> {
    if (!this.store) throw new Error('数据库未初始化');

    const predicates = new relationalStore.RdbPredicates('users');
    predicates.equalTo('id', id);

    const resultSet = await this.store.query(predicates, 
      ['id', 'name', 'age', 'email', 'phone']);

    let user: User | null = null;
    if (resultSet.goToNextRow()) {
      user = {
        id: resultSet.getLong(resultSet.getColumnIndex('id')),
        name: resultSet.getString(resultSet.getColumnIndex('name')),
        age: resultSet.getLong(resultSet.getColumnIndex('age')),
        email: resultSet.getString(resultSet.getColumnIndex('email')),
        phone: resultSet.getString(resultSet.getColumnIndex('phone'))
      };
    }
    resultSet.close();

    return user;
  }

  // 更新用户
  async updateUser(id: number, updates: Partial<User>): Promise<boolean> {
    if (!this.store) throw new Error('数据库未初始化');

    const predicates = new relationalStore.RdbPredicates('users');
    predicates.equalTo('id', id);

    const values: relationalStore.ValuesBucket = {
      ...updates,
      updated_at: new Date().toISOString()
    };

    const changedRows = await this.store.update(values, predicates);
    return changedRows > 0;
  }

  // 删除用户
  async deleteUser(id: number): Promise<boolean> {
    if (!this.store) throw new Error('数据库未初始化');

    const predicates = new relationalStore.RdbPredicates('users');
    predicates.equalTo('id', id);

    const deletedRows = await this.store.delete(predicates);
    return deletedRows > 0;
  }

  // 搜索用户
  async searchUsers(keyword: string): Promise<User[]> {
    if (!this.store) throw new Error('数据库未初始化');

    const predicates = new relationalStore.RdbPredicates('users');
    predicates.like('name', `%${keyword}%`);

    const resultSet = await this.store.query(predicates, 
      ['id', 'name', 'age', 'email', 'phone']);

    const users: User[] = [];
    while (resultSet.goToNextRow()) {
      users.push({
        id: resultSet.getLong(resultSet.getColumnIndex('id')),
        name: resultSet.getString(resultSet.getColumnIndex('name')),
        age: resultSet.getLong(resultSet.getColumnIndex('age')),
        email: resultSet.getString(resultSet.getColumnIndex('email')),
        phone: resultSet.getString(resultSet.getColumnIndex('phone'))
      });
    }
    resultSet.close();

    return users;
  }

  // 分页查询
  async getUsersByPage(page: number, pageSize: number): Promise<User[]> {
    if (!this.store) throw new Error('数据库未初始化');

    const offset = (page - 1) * pageSize;
    const sql = `SELECT * FROM users ORDER BY id DESC LIMIT ${pageSize} OFFSET ${offset}`;

    const resultSet = await this.store.querySql(sql);

    const users: User[] = [];
    while (resultSet.goToNextRow()) {
      users.push({
        id: resultSet.getLong(resultSet.getColumnIndex('id')),
        name: resultSet.getString(resultSet.getColumnIndex('name')),
        age: resultSet.getLong(resultSet.getColumnIndex('age')),
        email: resultSet.getString(resultSet.getColumnIndex('email')),
        phone: resultSet.getString(resultSet.getColumnIndex('phone'))
      });
    }
    resultSet.close();

    return users;
  }
}

// 使用示例
@Component
struct UserListPage {
  @State users: User[] = [];
  @State searchKeyword: string = '';
  private userManager: UserManager | null = null;

  async aboutToAppear() {
    this.userManager = new UserManager(getContext(this));
    await this.userManager.init();
    await this.loadUsers();
  }

  async loadUsers() {
    if (this.searchKeyword) {
      this.users = await this.userManager!.searchUsers(this.searchKeyword);
    } else {
      this.users = await this.userManager!.getAllUsers();
    }
  }

  build() {
    Column() {
      // 搜索栏
      Row() {
        TextInput({ placeholder: '搜索用户...', text: this.searchKeyword })
          .onChange((value: string) => {
            this.searchKeyword = value;
          })
          .layoutWeight(1)
        Button('搜索')
          .onClick(() => this.loadUsers())
      }
      .padding(10)

      // 用户列表
      List({ space: 10 }) {
        ForEach(this.users, (user: User) => {
          ListItem() {
            Row() {
              Column() {
                Text(user.name)
                  .fontSize(18)
                  .fontWeight(FontWeight.Bold)
                Text(`年龄: ${user.age} | 邮箱: ${user.email}`)
                  .fontSize(14)
                  .fontColor('#666')
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Start)

              Button('删除')
                .type(ButtonType.Circle)
                .onClick(async () => {
                  await this.userManager?.deleteUser(user.id!);
                  await this.loadUsers();
                })
            }
            .padding(15)
            .backgroundColor('#fff')
            .borderRadius(8)
            .shadow({ radius: 4, color: '#ddd', offsetX: 0, offsetY: 2 })
          }
        })
      }
      .layoutWeight(1)
      .padding(10)

      // 添加用户按钮
      Button('添加测试用户')
        .margin(10)
        .onClick(async () => {
          const testUser: User = {
            name: `用户${Date.now()}`,
            age: Math.floor(Math.random() * 50) + 18,
            email: `user${Date.now()}@example.com`
          };
          await this.userManager?.addUser(testUser);
          await this.loadUsers();
        })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f5f5f5')
  }
}

3.5 踩坑记录

踩坑1:忘记关闭ResultSet

问题:查询后没有关闭ResultSet,导致内存泄漏。

解决方案:使用try-finally确保ResultSet被关闭:

复制代码
async getUsers(): Promise<User[]> {
  const resultSet = await this.store!.query('users', ['id', 'name']);
  try {
    const users: User[] = [];
    while (resultSet.goToNextRow()) {
      // 处理数据
    }
    return users;
  } finally {
    resultSet.close(); // 确保关闭
  }
}
踩坑2:主键冲突

问题:插入数据时遇到主键冲突错误。

原因:设置了AUTOINCREMENT的主键,但手动指定了已存在的ID。

解决方案:让数据库自动生成主键,或者使用冲突解决策略:

复制代码
// 方法1:不指定ID
const values: relationalStore.ValuesBucket = {
  name: '张三',
  age: 25
};

// 方法2:使用INSERT OR REPLACE
const sql = `INSERT OR REPLACE INTO users (id, name, age) VALUES (1, '张三', 25)`;
await this.store!.execute(sql);
踩坑3:数据库升级问题

问题:应用更新后,数据库表结构变化,导致崩溃。

解决方案:实现数据库版本升级:

复制代码
class DatabaseUpgrade {
  static async upgrade(store: relationalStore.RdbStore, oldVersion: number, newVersion: number) {
    if (oldVersion < 2) {
      // 版本1到版本2:添加phone字段
      await store.execute(`ALTER TABLE users ADD COLUMN phone TEXT`);
    }
    if (oldVersion < 3) {
      // 版本2到版本3:添加address字段
      await store.execute(`ALTER TABLE users ADD COLUMN address TEXT`);
    }
  }
}

四、HTTP网络请求封装教程

4.1 网络请求概述

HarmonyOS NEXT提供了@ohos.net.http模块进行网络请求。在实际开发中,我们需要封装一个通用的HTTP工具类,具备以下功能:

  • 支持GET、POST、PUT、DELETE等请求方法

  • 自动处理请求头和响应头

  • 错误处理和重试机制

  • 请求拦截器和响应拦截器

  • 支持文件上传和下载

4.2 使用@ohos.net.http

4.2.1 基本用法
复制代码
import { http } from '@kit.NetworkKit';

// 创建httpRequest对象
const httpRequest = http.createHttp();

// 发送请求
httpRequest.request(
  'https://api.example.com/data',
  {
    method: http.RequestMethod.GET,
    header: {
      'Content-Type': 'application/json'
    },
    extraData: '',
    connectTimeout: 10000,
    readTimeout: 10000
  },
  (err, data) => {
    if (!err) {
      console.info(`状态码: ${data.responseCode}`);
      console.info(`响应数据: ${data.result}`);
    } else {
      console.error(`请求失败: ${err.message}`);
    }
    httpRequest.destroy();
  }
);
4.2.2 Promise方式
复制代码
async function fetchData(): Promise<string> {
  const httpRequest = http.createHttp();

  try {
    const response = await httpRequest.request(
      'https://api.example.com/data',
      {
        method: http.RequestMethod.GET
      }
    );

    if (response.responseCode === 200) {
      return response.result as string;
    } else {
      throw new Error(`请求失败,状态码: ${response.responseCode}`);
    }
  } finally {
    httpRequest.destroy();
  }
}

4.3 封装通用请求工具

让我们封装一个功能完整的HTTP工具类:

复制代码
// HttpClient.ets
import { http } from '@kit.NetworkKit';

// 请求配置
interface RequestConfig {
  baseURL?: string;
  timeout?: number;
  headers?: Record<string, string>;
}

// 响应数据
interface ResponseData<T = any> {
  code: number;
  data: T;
  message: string;
}

// 请求错误
class RequestError extends Error {
  code: number;
  constructor(message: string, code: number) {
    super(message);
    this.code = code;
  }
}

// HTTP客户端类
class HttpClient {
  private config: RequestConfig;
  private interceptors: {
    request: Array<(config: RequestConfig) => RequestConfig>;
    response: Array<(data: any) => any>;
  };

  constructor(config: RequestConfig = {}) {
    this.config = {
      baseURL: config.baseURL || '',
      timeout: config.timeout || 10000,
      headers: config.headers || {}
    };

    this.interceptors = {
      request: [],
      response: []
    };
  }

  // 添加请求拦截器
  addRequestInterceptor(interceptor: (config: RequestConfig) => RequestConfig) {
    this.interceptors.request.push(interceptor);
    return this;
  }

  // 添加响应拦截器
  addResponseInterceptor(interceptor: (data: any) => any) {
    this.interceptors.response.push(interceptor);
    return this;
  }

  // 发送请求
  async request<T = any>(
    url: string,
    method: http.RequestMethod,
    data?: any,
    headers?: Record<string, string>
  ): Promise<ResponseData<T>> {
    let config = { ...this.config };
    
    // 应用请求拦截器
    for (const interceptor of this.interceptors.request) {
      config = interceptor(config);
    }

    const httpRequest = http.createHttp();

    try {
      const fullUrl = config.baseURL + url;
      
      const options: http.HttpRequestOptions = {
        method: method,
        header: {
          ...config.headers,
          ...headers
        },
        connectTimeout: config.timeout,
        readTimeout: config.timeout,
        extraData: data ? JSON.stringify(data) : undefined
      };

      const response = await httpRequest.request(fullUrl, options);

      if (response.responseCode === 200) {
        let result = JSON.parse(response.result as string) as ResponseData<T>;
        
        // 应用响应拦截器
        for (const interceptor of this.interceptors.response) {
          result = interceptor(result);
        }

        return result;
      } else {
        throw new RequestError(
          `请求失败,状态码: ${response.responseCode}`,
          response.responseCode
        );
      }
    } catch (error) {
      if (error instanceof RequestError) {
        throw error;
      }
      throw new RequestError(
        `网络请求异常: ${(error as Error).message}`,
        -1
      );
    } finally {
      httpRequest.destroy();
    }
  }

  // GET请求
  async get<T = any>(url: string, headers?: Record<string, string>): Promise<ResponseData<T>> {
    return this.request<T>(url, http.RequestMethod.GET, undefined, headers);
  }

  // POST请求
  async post<T = any>(url: string, data?: any, headers?: Record<string, string>): Promise<ResponseData<T>> {
    return this.request<T>(url, http.RequestMethod.POST, data, headers);
  }

  // PUT请求
  async put<T = any>(url: string, data?: any, headers?: Record<string, string>): Promise<ResponseData<T>> {
    return this.request<T>(url, http.RequestMethod.PUT, data, headers);
  }

  // DELETE请求
  async delete<T = any>(url: string, headers?: Record<string, string>): Promise<ResponseData<T>> {
    return this.request<T>(url, http.RequestMethod.DELETE, undefined, headers);
  }
}

// 导出单例
export const httpClient = new HttpClient({
  baseURL: 'https://api.example.com',
  timeout: 15000
});

// 添加请求拦截器 - 添加token
httpClient.addRequestInterceptor((config) => {
  const token = globalThis.token || '';
  if (token) {
    config.headers = {
      ...config.headers,
      'Authorization': `Bearer ${token}`
    };
  }
  return config;
});

// 添加响应拦截器 - 处理业务错误
httpClient.addResponseInterceptor((data) => {
  if (data.code !== 0) {
    throw new RequestError(data.message, data.code);
  }
  return data;
});

4.4 实战:获取天气数据

让我们使用封装的HTTP客户端获取天气数据:

复制代码
// WeatherService.ets
import { httpClient } from './HttpClient';

// 天气数据接口
interface WeatherData {
  city: string;
  temperature: number;
  humidity: number;
  weather: string;
  wind: string;
  forecast: Array<{
    date: string;
    high: number;
    low: number;
    weather: string;
  }>;
}

// 天气服务
class WeatherService {
  // 获取当前天气
  static async getCurrentWeather(city: string): Promise<WeatherData> {
    const response = await httpClient.get<WeatherData>(`/weather/current?city=${city}`);
    return response.data;
  }

  // 获取天气预报
  static async getForecast(city: string, days: number = 7): Promise<WeatherData[]> {
    const response = await httpClient.get<WeatherData[]>(
      `/weather/forecast?city=${city}&days=${days}`
    );
    return response.data;
  }
}

// 使用示例
@Component
struct WeatherPage {
  @State weather: WeatherData | null = null;
  @State loading: boolean = false;
  @State error: string = '';

  async aboutToAppear() {
    await this.loadWeather();
  }

  async loadWeather() {
    this.loading = true;
    this.error = '';

    try {
      this.weather = await WeatherService.getCurrentWeather('北京');
    } catch (e) {
      this.error = (e as Error).message;
    } finally {
      this.loading = false;
    }
  }

  build() {
    Column() {
      if (this.loading) {
        LoadingProgress()
          .width(50)
          .height(50)
      } else if (this.error) {
        Column() {
          Text(this.error)
            .fontSize(16)
            .fontColor('#ff4d4f')
          Button('重试')
            .margin({ top: 10 })
            .onClick(() => this.loadWeather())
        }
      } else if (this.weather) {
        // 天气卡片
        Column() {
          Text(this.weather.city)
            .fontSize(24)
            .fontWeight(FontWeight.Bold)
          Text(`${this.weather.temperature}°C`)
            .fontSize(48)
            .fontWeight(FontWeight.Bold)
            .margin({ top: 10 })
          Text(this.weather.weather)
            .fontSize(18)
            .margin({ top: 5 })
          
          Row() {
            Column() {
              Text('湿度')
                .fontSize(14)
                .fontColor('#666')
              Text(`${this.weather.humidity}%`)
                .fontSize(18)
            }
            .layoutWeight(1)

            Column() {
              Text('风力')
                .fontSize(14)
                .fontColor('#666')
              Text(this.weather.wind)
                .fontSize(18)
            }
            .layoutWeight(1)
          }
          .margin({ top: 20 })
          .width('100%')
        }
        .padding(20)
        .backgroundColor('#e3f2fd')
        .borderRadius(12)
        .margin(10)
      }
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

4.5 踩坑记录

踩坑1:网络权限问题

问题:请求失败,提示没有网络权限。

解决方案 :在module.json5中添加网络权限:

复制代码
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      }
    ]
  }
}
踩坑2:HTTPS证书问题

问题:请求HTTPS接口时证书验证失败。

解决方案:在开发阶段可以跳过证书验证(生产环境不推荐):

复制代码
// 开发环境配置
const options: http.HttpRequestOptions = {
  method: http.RequestMethod.GET,
  // 跳过证书验证(仅开发环境)
  certificate: undefined
};
踩坑3:请求超时

问题:网络请求经常超时。

解决方案

  1. 设置合理的超时时间

  2. 实现重试机制

  3. 添加超时重试逻辑:

    async requestWithRetry<T>(
    url: string,
    method: http.RequestMethod,
    data?: any,
    maxRetries: number = 3
    ): Promise<ResponseData<T>> {
    let lastError: Error | null = null;

    for (let i = 0; i < maxRetries; i++) {
    try {
    return await this.request<T>(url, method, data);
    } catch (error) {
    lastError = error as Error;
    console.warn(请求失败,第${i + 1}次重试: ${lastError.message});

    复制代码
       // 等待一段时间后重试
       await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
     }

    }

    throw lastError;
    }


五、数据缓存策略

5.1 缓存的重要性

合理的数据缓存策略可以:

  • 提升用户体验:减少加载时间,实现秒开效果

  • 降低服务器压力:减少重复请求

  • 支持离线使用:在没有网络时仍能查看历史数据

5.2 多级缓存设计

我们设计一个三级缓存架构:

  1. 内存缓存(L1):最快,但容量有限,应用退出后丢失

  2. Preferences缓存(L2):中等速度,适合配置数据

  3. RDB缓存(L3):最慢,但容量大,支持复杂查询

5.3 实战:实现缓存管理器

复制代码
// CacheManager.ets
import { preferences } from '@kit.ArkData';
import { relationalStore } from '@kit.ArkData';
import { Context } from '@kit.AbilityKit';

// 缓存项
interface CacheItem<T> {
  data: T;
  timestamp: number;
  expireTime: number; // 过期时间(毫秒)
}

// 缓存配置
interface CacheConfig {
  memoryMaxSize: number; // 内存缓存最大条数
  defaultExpireTime: number; // 默认过期时间(毫秒)
}

// 缓存管理器
class CacheManager {
  private static instance: CacheManager;
  private memoryCache: Map<string, CacheItem<any>> = new Map();
  private preferencesStore: preferences.Preferences | null = null;
  private rdbStore: relationalStore.RdbStore | null = null;
  private config: CacheConfig;
  private context: Context;

  private constructor(context: Context, config: CacheConfig) {
    this.context = context;
    this.config = config;
  }

  // 获取单例
  static async getInstance(context: Context, config: CacheConfig): Promise<CacheManager> {
    if (!CacheManager.instance) {
      CacheManager.instance = new CacheManager(context, config);
      await CacheManager.instance.init();
    }
    return CacheManager.instance;
  }

  // 初始化
  private async init() {
    // 初始化Preferences
    this.preferencesStore = await preferences.getPreferences(
      this.context,
      'cacheStore'
    );

    // 初始化RDB
    const rdbConfig: relationalStore.RdbStoreConfig = {
      name: 'cache.db',
      securityLevel: relationalStore.SecurityLevel.S1
    };
    this.rdbStore = await relationalStore.getRdbStore(this.context, rdbConfig);

    // 创建缓存表
    await this.rdbStore.execute(`
      CREATE TABLE IF NOT EXISTS cache (
        key TEXT PRIMARY KEY,
        value TEXT,
        timestamp INTEGER,
        expire_time INTEGER
      )
    `);
  }

  // 设置缓存
  async set<T>(key: string, data: T, expireTime?: number): Promise<void> {
    const item: CacheItem<T> = {
      data,
      timestamp: Date.now(),
      expireTime: expireTime || this.config.defaultExpireTime
    };

    // L1: 内存缓存
    this.memoryCache.set(key, item);
    this.cleanMemoryCache();

    // L2: Preferences缓存(只存小数据)
    const dataStr = JSON.stringify(data);
    if (dataStr.length < 1024) { // 小于1KB
      await this.preferencesStore?.put(key, dataStr);
      await this.preferencesStore?.flush();
    }

    // L3: RDB缓存
    const values: relationalStore.ValuesBucket = {
      key: key,
      value: dataStr,
      timestamp: item.timestamp,
      expire_time: item.expireTime
    };

    const predicates = new relationalStore.RdbPredicates('cache');
    predicates.equalTo('key', key);
    
    const existing = await this.rdbStore?.query(predicates, ['key']);
    if (existing && existing.rowCount > 0) {
      await this.rdbStore?.update(values, predicates);
    } else {
      await this.rdbStore?.insert('cache', values);
    }
    existing?.close();
  }

  // 获取缓存
  async get<T>(key: string): Promise<T | null> {
    // L1: 优先从内存缓存获取
    const memoryItem = this.memoryCache.get(key);
    if (memoryItem && !this.isExpired(memoryItem)) {
      return memoryItem.data as T;
    }

    // L2: 从Preferences获取
    const prefData = await this.preferencesStore?.get(key, '');
    if (prefData) {
      try {
        const data = JSON.parse(prefData as string) as T;
        // 回写到内存缓存
        this.memoryCache.set(key, {
          data,
          timestamp: Date.now(),
          expireTime: this.config.defaultExpireTime
        });
        return data;
      } catch (e) {
        // 解析失败,继续从RDB获取
      }
    }

    // L3: 从RDB获取
    const predicates = new relationalStore.RdbPredicates('cache');
    predicates.equalTo('key', key);
    
    const resultSet = await this.rdbStore?.query(
      predicates,
      ['value', 'timestamp', 'expire_time']
    );

    if (resultSet && resultSet.goToNextRow()) {
      const value = resultSet.getString(resultSet.getColumnIndex('value'));
      const timestamp = resultSet.getLong(resultSet.getColumnIndex('timestamp'));
      const expireTime = resultSet.getLong(resultSet.getColumnIndex('expire_time'));
      resultSet.close();

      const item: CacheItem<T> = {
        data: JSON.parse(value),
        timestamp,
        expireTime
      };

      if (!this.isExpired(item)) {
        // 回写到上级缓存
        this.memoryCache.set(key, item);
        return item.data as T;
      }
    }
    resultSet?.close();

    return null;
  }

  // 删除缓存
  async delete(key: string): Promise<void> {
    // 从所有缓存级别删除
    this.memoryCache.delete(key);
    await this.preferencesStore?.delete(key);
    
    const predicates = new relationalStore.RdbPredicates('cache');
    predicates.equalTo('key', key);
    await this.rdbStore?.delete(predicates);
  }

  // 清空所有缓存
  async clear(): Promise<void> {
    this.memoryCache.clear();
    await this.preferencesStore?.clear();
    await this.rdbStore?.execute('DELETE FROM cache');
  }

  // 检查是否过期
  private isExpired(item: CacheItem<any>): boolean {
    return Date.now() - item.timestamp > item.expireTime;
  }

  // 清理内存缓存
  private cleanMemoryCache() {
    if (this.memoryCache.size > this.config.memoryMaxSize) {
      // 删除最早添加的项
      const firstKey = this.memoryCache.keys().next().value;
      if (firstKey) {
        this.memoryCache.delete(firstKey);
      }
    }
  }
}

// 使用示例
@Component
struct DataPage {
  @State data: any = null;
  @State loading: boolean = false;
  private cacheManager: CacheManager | null = null;

  async aboutToAppear() {
    this.cacheManager = await CacheManager.getInstance(getContext(this), {
      memoryMaxSize: 100,
      defaultExpireTime: 5 * 60 * 1000 // 5分钟
    });

    await this.loadData();
  }

  async loadData() {
    this.loading = true;
    const cacheKey = 'user_list';

    try {
      // 先尝试从缓存获取
      let data = await this.cacheManager!.get(cacheKey);

      if (!data) {
        // 缓存未命中,从网络获取
        data = await this.fetchFromNetwork();
        
        // 存入缓存
        await this.cacheManager!.set(cacheKey, data, 10 * 60 * 1000); // 缓存10分钟
      }

      this.data = data;
    } catch (e) {
      console.error('加载数据失败:', (e as Error).message);
    } finally {
      this.loading = false;
    }
  }

  async fetchFromNetwork() {
    // 模拟网络请求
    return new Promise(resolve => {
      setTimeout(() => {
        resolve({
          users: [
            { id: 1, name: '张三' },
            { id: 2, name: '李四' }
          ]
        });
      }, 1000);
    });
  }

  build() {
    Column() {
      if (this.loading) {
        LoadingProgress()
          .width(50)
          .height(50)
      } else if (this.data) {
        Text(JSON.stringify(this.data, null, 2))
          .fontSize(14)
          .padding(10)
      }

      Button('刷新数据')
        .margin({ top: 20 })
        .onClick(async () => {
          await this.cacheManager?.delete('user_list');
          await this.loadData();
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

六、完整项目实战

6.1 项目需求分析

让我们实现一个新闻阅读App的数据层,包含:

  • 用户登录状态管理(Preferences)

  • 新闻列表缓存(RDB + HTTP)

  • 图片缓存(文件系统)

6.2 数据层架构设计

复制代码
┌─────────────────────────────────────┐
│           业务层 (UI)                │
├─────────────────────────────────────┤
│           数据层 (Data)              │
│  ┌─────────────┐  ┌─────────────┐  │
│  │ 用户数据    │  │ 新闻数据    │  │
│  │ (Preferences)│  │ (RDB+HTTP)  │  │
│  └─────────────┘  └─────────────┘  │
├─────────────────────────────────────┤
│           基础设施                   │
│  ┌─────────────┐  ┌─────────────┐  │
│  │ HttpClient  │  │ CacheManager│  │
│  └─────────────┘  └─────────────┘  │
└─────────────────────────────────────┘

6.3 核心代码实现

复制代码
// NewsApp.ets

// 1. 用户数据管理
class UserDataManager {
  private store: preferences.Preferences | null = null;

  async init(context: Context) {
    this.store = await preferences.getPreferences(context, 'userData');
  }

  // 保存登录信息
  async saveLoginInfo(token: string, userId: string) {
    await this.store?.put('token', token);
    await this.store?.put('userId', userId);
    await this.store?.put('loginTime', Date.now().toString());
    await this.store?.flush();
  }

  // 获取token
  async getToken(): Promise<string> {
    return (await this.store?.get('token', '')) as string;
  }

  // 检查是否已登录
  async isLoggedIn(): Promise<boolean> {
    const token = await this.getToken();
    return token !== '';
  }

  // 退出登录
  async logout() {
    await this.store?.clear();
    await this.store?.flush();
  }
}

// 2. 新闻数据管理
class NewsDataManager {
  private rdbStore: relationalStore.RdbStore | null = null;
  private httpClient: HttpClient;

  constructor(httpClient: HttpClient) {
    this.httpClient = httpClient;
  }

  async init(context: Context) {
    const config: relationalStore.RdbStoreConfig = {
      name: 'news.db',
      securityLevel: relationalStore.SecurityLevel.S1
    };

    this.rdbStore = await relationalStore.getRdbStore(context, config);

    // 创建新闻表
    await this.rdbStore.execute(`
      CREATE TABLE IF NOT EXISTS news (
        id TEXT PRIMARY KEY,
        title TEXT,
        summary TEXT,
        content TEXT,
        image_url TEXT,
        category TEXT,
        publish_time TEXT,
        cached_at INTEGER
      )
    `);
  }

  // 获取新闻列表
  async getNewsList(category: string, page: number): Promise<any[]> {
    // 先从缓存获取
    const cachedNews = await this.getNewsFromCache(category, page);
    if (cachedNews.length > 0) {
      return cachedNews;
    }

    // 缓存未命中,从网络获取
    try {
      const response = await this.httpClient.get<any[]>(
        `/news?category=${category}&page=${page}`
      );
      const newsList = response.data;

      // 存入缓存
      await this.saveNewsToCache(newsList);

      return newsList;
    } catch (error) {
      console.error('获取新闻失败:', (error as Error).message);
      return [];
    }
  }

  // 从缓存获取新闻
  private async getNewsFromCache(category: string, page: number): Promise<any[]> {
    const predicates = new relationalStore.RdbPredicates('news');
    predicates.equalTo('category', category);
    predicates.orderByDesc('publish_time');
    
    const pageSize = 20;
    const offset = (page - 1) * pageSize;
    
    const sql = `SELECT * FROM news WHERE category = '${category}' 
                 ORDER BY publish_time DESC LIMIT ${pageSize} OFFSET ${offset}`;
    
    const resultSet = await this.rdbStore?.querySql(sql);

    const newsList: any[] = [];
    if (resultSet) {
      while (resultSet.goToNextRow()) {
        newsList.push({
          id: resultSet.getString(resultSet.getColumnIndex('id')),
          title: resultSet.getString(resultSet.getColumnIndex('title')),
          summary: resultSet.getString(resultSet.getColumnIndex('summary')),
          imageUrl: resultSet.getString(resultSet.getColumnIndex('image_url')),
          publishTime: resultSet.getString(resultSet.getColumnIndex('publish_time'))
        });
      }
      resultSet.close();
    }

    return newsList;
  }

  // 保存新闻到缓存
  private async saveNewsToCache(newsList: any[]) {
    for (const news of newsList) {
      const values: relationalStore.ValuesBucket = {
        id: news.id,
        title: news.title,
        summary: news.summary,
        content: news.content,
        image_url: news.imageUrl,
        category: news.category,
        publish_time: news.publishTime,
        cached_at: Date.now()
      };

      const predicates = new relationalStore.RdbPredicates('news');
      predicates.equalTo('id', news.id);

      const existing = await this.rdbStore?.query(predicates, ['id']);
      if (existing && existing.rowCount > 0) {
        await this.rdbStore?.update(values, predicates);
      } else {
        await this.rdbStore?.insert('news', values);
      }
      existing?.close();
    }
  }
}

// 3. 应用入口
@Entry
@Component
struct NewsApp {
  @State isLoggedIn: boolean = false;
  private userDataManager: UserDataManager = new UserDataManager();
  private newsDataManager: NewsDataManager = new NewsDataManager(httpClient);

  async aboutToAppear() {
    const context = getContext(this);
    
    await this.userDataManager.init(context);
    await this.newsDataManager.init(context);

    this.isLoggedIn = await this.userDataManager.isLoggedIn();
  }

  build() {
    Column() {
      if (this.isLoggedIn) {
        // 显示新闻列表
        NewsListPage({
          newsDataManager: this.newsDataManager,
          onLogout: async () => {
            await this.userDataManager.logout();
            this.isLoggedIn = false;
          }
        })
      } else {
        // 显示登录页面
        LoginPage({
          onLoginSuccess: async (token: string, userId: string) => {
            await this.userDataManager.saveLoginInfo(token, userId);
            this.isLoggedIn = true;
          }
        })
      }
    }
    .width('100%')
    .height('100%')
  }
}

// 新闻列表页面组件
@Component
struct NewsListPage {
  @Prop newsDataManager: NewsDataManager;
  @State newsList: any[] = [];
  @State loading: boolean = false;
  onLogout: () => Promise<void> = async () => {};

  async aboutToAppear() {
    await this.loadNews();
  }

  async loadNews() {
    this.loading = true;
    try {
      this.newsList = await this.newsDataManager.getNewsList('technology', 1);
    } finally {
      this.loading = false;
    }
  }

  build() {
    Column() {
      // 顶部栏
      Row() {
        Text('科技新闻')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
        Blank()
        Button('退出')
          .onClick(() => this.onLogout())
      }
      .padding(10)
      .width('100%')

      // 新闻列表
      List({ space: 10 }) {
        ForEach(this.newsList, (news: any) => {
          ListItem() {
            Row() {
              Column() {
                Text(news.title)
                  .fontSize(16)
                  .fontWeight(FontWeight.Bold)
                  .maxLines(2)
                  .textOverflow({ overflow: TextOverflow.Ellipsis })
                Text(news.summary)
                  .fontSize(14)
                  .fontColor('#666')
                  .maxLines(2)
                  .margin({ top: 5 })
                Text(news.publishTime)
                  .fontSize(12)
                  .fontColor('#999')
                  .margin({ top: 5 })
              }
              .layoutWeight(1)

              Image(news.imageUrl)
                .width(80)
                .height(60)
                .objectFit(ImageFit.Cover)
                .borderRadius(4)
                .margin({ left: 10 })
            }
            .padding(10)
            .backgroundColor('#fff')
            .borderRadius(8)
          }
        })
      }
      .layoutWeight(1)
      .padding(10)

      // 加载状态
      if (this.loading) {
        LoadingProgress()
          .width(30)
          .height(30)
          .margin({ bottom: 10 })
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f5f5f5')
  }
}

// 登录页面组件
@Component
struct LoginPage {
  @State username: string = '';
  @State password: string = '';
  @State loading: boolean = false;
  @State errorMsg: string = '';
  onLoginSuccess: (token: string, userId: string) => Promise<void> = async () => {};

  async handleLogin() {
    if (!this.username || !this.password) {
      this.errorMsg = '请输入用户名和密码';
      return;
    }

    this.loading = true;
    this.errorMsg = '';

    try {
      // 模拟登录请求
      const response = await httpClient.post('/login', {
        username: this.username,
        password: this.password
      });

      await this.onLoginSuccess(response.data.token, response.data.userId);
    } catch (e) {
      this.errorMsg = (e as Error).message;
    } finally {
      this.loading = false;
    }
  }

  build() {
    Column() {
      Text('新闻阅读App')
        .fontSize(28)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 40 })

      TextInput({ placeholder: '请输入用户名', text: this.username })
        .onChange((value: string) => this.username = value)
        .margin({ bottom: 10 })

      TextInput({ placeholder: '请输入密码', text: this.password })
        .type(InputType.Password)
        .onChange((value: string) => this.password = value)
        .margin({ bottom: 20 })

      if (this.errorMsg) {
        Text(this.errorMsg)
          .fontColor('#ff4d4f')
          .margin({ bottom: 10 })
      }

      Button(this.loading ? '登录中...' : '登录')
        .width('100%')
        .height(45)
        .onClick(() => this.handleLogin())
        .enabled(!this.loading)
    }
    .padding(30)
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

七、总结与下期预告

总结

本文详细讲解了HarmonyOS NEXT的三种数据管理方案:

  1. Preferences:轻量级键值存储,适合配置信息

  2. RDB关系型数据库:结构化数据存储,支持复杂查询

  3. HTTP网络请求:与服务器交互,获取和提交数据

我们还实现了:

  • 通用的HTTP客户端封装

  • 多级缓存管理器

  • 完整的新闻阅读App数据层

关键要点

  • Preferences适合小量配置数据,记得调用flush()

  • RDB适合结构化数据,注意关闭ResultSet和处理并发

  • HTTP请求要处理错误和超时,实现重试机制

  • 合理使用缓存策略提升性能和用户体验

下期预告

第5篇:性能优化实战指南:启动速度、内存、渲染全方位优化

下期我们将深入讲解:

  • 应用启动速度优化策略

  • 内存管理与泄漏排查

  • 列表渲染性能优化(LazyForEach、@Reusable)

  • 网络请求性能优化

  • DevEco Studio Profiler性能分析工具实战

敬请期待!


系列文章导航


鸿蒙开发者,专注HarmonyOS应用开发与架构设计。欢迎关注我的CSDN博客,获取更多鸿蒙开发干货。


标签:鸿蒙NEXT | HarmonyOS NEXT | ArkUI | ArkTS | DevEco Studio | 数据持久化 | Preferences | RDB | HTTP请求 | 网络请求 | 缓存策略 | @ohos.net.http

相关推荐
想成为优秀工程师的爸爸2 小时前
车载以太网之要火系列 - 第35篇:郭大侠学UDS(34/36/37服务)- 环环相扣展神奇,丝滑更新不迷离
网络协议·uds·车载以太网
yantaohk2 小时前
高层住宅只有一根光纤入户,能不能多装几条宽带跑PCDN?
网络
路溪非溪2 小时前
关于wifi和蓝牙的共存问题
网络
IPDEEP全球代理2 小时前
美国原生IP是什么意思?有什么用?
网络·网络协议·tcp/ip
techdashen2 小时前
Cloudflare 开源 h3i:深入 HTTP/3 协议调试的利器
网络协议·http·开源
威联通网络存储2 小时前
威联通全闪 iSCSI 底座:虚拟化 MPIO 与 VAAI 卸载解析
网络
ACP广源盛139246256732 小时前
磐石 100 :IX6012 :ASM1812@ACP#国产 PCIe 2.0 交换芯片,轻量级算力扩展应用分享
大数据·linux·运维·网络·人工智能·嵌入式硬件·电脑
H Journey2 小时前
网络编程:服务器监听+非阻塞设置
服务器·网络·服务器监听+非阻塞设置
Promise微笑3 小时前
开关柜局放国产替代浪潮下:开关柜局放监测技术与实践深度解析
网络·数据库·人工智能