feat(common): 新增基础设施 HAR(网络/鉴权/日志/状态/工具)
This commit is contained in:
8
common/src/main/ets/Index.ets
Normal file
8
common/src/main/ets/Index.ets
Normal file
@@ -0,0 +1,8 @@
|
||||
export { HttpClient } from './network/HttpClient';
|
||||
export { HttpInterceptor } from './network/HttpInterceptor';
|
||||
export { AuthInterceptor } from './network/AuthInterceptor';
|
||||
export { ApiError } from './network/ApiError';
|
||||
export { ApiResult } from './network/ApiResult';
|
||||
export { SessionStore, SessionManager } from './state/SessionStore';
|
||||
export { Logger } from './logging/Logger';
|
||||
export { FormatUtil } from './utils/FormatUtil';
|
||||
24
common/src/main/ets/logging/Logger.ets
Normal file
24
common/src/main/ets/logging/Logger.ets
Normal file
@@ -0,0 +1,24 @@
|
||||
import { hilog } from '@kit.PerformanceAnalysisKit';
|
||||
|
||||
const APP_DOMAIN: number = 0x0000;
|
||||
|
||||
/**
|
||||
* 日志封装:统一 DOMAIN 与等级,后续可在此加开关/上报。
|
||||
*/
|
||||
export class Logger {
|
||||
static debug(tag: string, message: string): void {
|
||||
hilog.debug(APP_DOMAIN, tag, '%{public}s', message);
|
||||
}
|
||||
|
||||
static info(tag: string, message: string): void {
|
||||
hilog.info(APP_DOMAIN, tag, '%{public}s', message);
|
||||
}
|
||||
|
||||
static warn(tag: string, message: string): void {
|
||||
hilog.warn(APP_DOMAIN, tag, '%{public}s', message);
|
||||
}
|
||||
|
||||
static error(tag: string, message: string): void {
|
||||
hilog.error(APP_DOMAIN, tag, '%{public}s', message);
|
||||
}
|
||||
}
|
||||
26
common/src/main/ets/network/ApiError.ets
Normal file
26
common/src/main/ets/network/ApiError.ets
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 统一错误模型:业务错误码 / HTTP 状态码 / 网络异常。
|
||||
* 继承 Error 以符合 ArkTS 的 throw 限制(只能抛 Error 及其子类)。
|
||||
*/
|
||||
export class ApiError extends Error {
|
||||
readonly code: number;
|
||||
readonly isNetworkError: boolean;
|
||||
|
||||
constructor(code: number, message: string, isNetworkError: boolean = false) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
this.isNetworkError = isNetworkError;
|
||||
}
|
||||
|
||||
static network(detail: string): ApiError {
|
||||
return new ApiError(-1, `网络请求失败: ${detail}`, true);
|
||||
}
|
||||
|
||||
static http(statusCode: number): ApiError {
|
||||
return new ApiError(statusCode, `HTTP 状态码异常: ${statusCode}`, false);
|
||||
}
|
||||
|
||||
static business(code: number, message: string): ApiError {
|
||||
return new ApiError(code, message, false);
|
||||
}
|
||||
}
|
||||
18
common/src/main/ets/network/ApiResult.ets
Normal file
18
common/src/main/ets/network/ApiResult.ets
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* 统一响应包装:与后端约定 code / message / data 结构。
|
||||
*/
|
||||
export class ApiResult<T> {
|
||||
code: number;
|
||||
message: string;
|
||||
data: T;
|
||||
|
||||
constructor(code: number, message: string, data: T) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
isSuccess(): boolean {
|
||||
return this.code === 0;
|
||||
}
|
||||
}
|
||||
24
common/src/main/ets/network/AuthInterceptor.ets
Normal file
24
common/src/main/ets/network/AuthInterceptor.ets
Normal file
@@ -0,0 +1,24 @@
|
||||
import { HttpInterceptor } from './HttpInterceptor';
|
||||
import { SessionManager } from '../state/SessionStore';
|
||||
|
||||
/**
|
||||
* 鉴权拦截器:请求前注入 Authorization 头;401 时清空会话(登出)。
|
||||
*
|
||||
* TODO(鉴权): 401 时的 token 刷新 + 原请求重放(防并发刷新)需在
|
||||
* 后端 refresh 接口约定后实现,当前先清空会话交由上层跳登录。
|
||||
*/
|
||||
export class AuthInterceptor implements HttpInterceptor {
|
||||
onRequest(header: Record<string, string>): Record<string, string> {
|
||||
const session = SessionManager.get();
|
||||
if (session.token.length > 0) {
|
||||
header['Authorization'] = `Bearer ${session.token}`;
|
||||
}
|
||||
return header;
|
||||
}
|
||||
|
||||
onResponse(responseCode: number, result: string): void {
|
||||
if (responseCode === 401) {
|
||||
SessionManager.get().clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
65
common/src/main/ets/network/HttpClient.ets
Normal file
65
common/src/main/ets/network/HttpClient.ets
Normal file
@@ -0,0 +1,65 @@
|
||||
import { http } from '@kit.NetworkKit';
|
||||
import { HttpInterceptor } from './HttpInterceptor';
|
||||
import { ApiError } from './ApiError';
|
||||
|
||||
/**
|
||||
* HTTP 客户端封装:统一超时、请求头、拦截器链。
|
||||
* 基于 @kit.NetworkKit 的 http 模块(Promise 风格)。
|
||||
*/
|
||||
export class HttpClient {
|
||||
private readonly client: http.HttpRequest = http.createHttp();
|
||||
private interceptors: HttpInterceptor[] = [];
|
||||
|
||||
addInterceptor(interceptor: HttpInterceptor): HttpClient {
|
||||
this.interceptors.push(interceptor);
|
||||
return this;
|
||||
}
|
||||
|
||||
async get(url: string, header?: Record<string, string>): Promise<string> {
|
||||
return this.request(url, http.RequestMethod.GET, undefined, header);
|
||||
}
|
||||
|
||||
async post(url: string, body: string, header?: Record<string, string>): Promise<string> {
|
||||
return this.request(url, http.RequestMethod.POST, body, header);
|
||||
}
|
||||
|
||||
async put(url: string, body: string, header?: Record<string, string>): Promise<string> {
|
||||
return this.request(url, http.RequestMethod.PUT, body, header);
|
||||
}
|
||||
|
||||
async delete(url: string, header?: Record<string, string>): Promise<string> {
|
||||
return this.request(url, http.RequestMethod.DELETE, undefined, header);
|
||||
}
|
||||
|
||||
private async request(url: string, method: http.RequestMethod, body?: string,
|
||||
header?: Record<string, string>): Promise<string> {
|
||||
let headers: Record<string, string> = {};
|
||||
if (header !== undefined) {
|
||||
headers = header;
|
||||
}
|
||||
for (const interceptor of this.interceptors) {
|
||||
headers = interceptor.onRequest(headers);
|
||||
}
|
||||
|
||||
const options: http.HttpRequestOptions = {
|
||||
method: method,
|
||||
header: headers,
|
||||
expectDataType: http.HttpDataType.STRING,
|
||||
connectTimeout: 10000,
|
||||
readTimeout: 10000
|
||||
};
|
||||
if (body !== undefined) {
|
||||
options.extraData = body;
|
||||
}
|
||||
|
||||
try {
|
||||
const response: http.HttpResponse = await this.client.request(url, options);
|
||||
for (const interceptor of this.interceptors) {
|
||||
interceptor.onResponse(response.responseCode, response.result as string);
|
||||
}
|
||||
return response.result as string;
|
||||
} catch (err) {
|
||||
throw ApiError.network(JSON.stringify(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
10
common/src/main/ets/network/HttpInterceptor.ets
Normal file
10
common/src/main/ets/network/HttpInterceptor.ets
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* 请求/响应拦截器接口。
|
||||
* 实现类可注入请求头(鉴权)、统一处理响应(401/错误码)等。
|
||||
*/
|
||||
export interface HttpInterceptor {
|
||||
/** 请求发出前,返回(可修改的)请求头。 */
|
||||
onRequest(header: Record<string, string>): Record<string, string>;
|
||||
/** 响应返回后,可据此抛错中断(如 401 触发登出)。 */
|
||||
onResponse(responseCode: number, result: string): void;
|
||||
}
|
||||
56
common/src/main/ets/state/SessionStore.ets
Normal file
56
common/src/main/ets/state/SessionStore.ets
Normal file
@@ -0,0 +1,56 @@
|
||||
import { PersistenceV2 } from '@kit.ArkUI';
|
||||
|
||||
/**
|
||||
* 会话状态:登录 token / 用户信息。
|
||||
* 用 PersistenceV2 持久化,@Trace 字段变更自动落盘。
|
||||
*/
|
||||
@ObservedV2
|
||||
export class SessionStore {
|
||||
@Trace token: string = '';
|
||||
@Trace refreshToken: string = '';
|
||||
@Trace userId: string = '';
|
||||
@Trace displayName: string = '';
|
||||
|
||||
get isLoggedIn(): boolean {
|
||||
return this.token.length > 0;
|
||||
}
|
||||
|
||||
setSession(token: string, refreshToken: string, userId: string, displayName: string): void {
|
||||
this.token = token;
|
||||
this.refreshToken = refreshToken;
|
||||
this.userId = userId;
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.token = '';
|
||||
this.refreshToken = '';
|
||||
this.userId = '';
|
||||
this.displayName = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话单例管理:全局唯一连接,跨页面/跨组件共享同一份持久化状态。
|
||||
*/
|
||||
export class SessionManager {
|
||||
private static instance: SessionStore | undefined = undefined;
|
||||
|
||||
static init(): SessionStore {
|
||||
if (SessionManager.instance === undefined) {
|
||||
SessionManager.instance = PersistenceV2.globalConnect({
|
||||
type: SessionStore,
|
||||
key: 'session',
|
||||
defaultCreator: () => new SessionStore()
|
||||
})!;
|
||||
}
|
||||
return SessionManager.instance;
|
||||
}
|
||||
|
||||
static get(): SessionStore {
|
||||
if (SessionManager.instance === undefined) {
|
||||
return SessionManager.init();
|
||||
}
|
||||
return SessionManager.instance;
|
||||
}
|
||||
}
|
||||
15
common/src/main/ets/utils/FormatUtil.ets
Normal file
15
common/src/main/ets/utils/FormatUtil.ets
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 通用工具(占位示例,随业务扩充)。
|
||||
*/
|
||||
export class FormatUtil {
|
||||
static isEmpty(value: string): boolean {
|
||||
return value.length === 0;
|
||||
}
|
||||
|
||||
static formatCount(count: number): string {
|
||||
if (count >= 10000) {
|
||||
return `${(count / 10000).toFixed(1)}w`;
|
||||
}
|
||||
return `${count}`;
|
||||
}
|
||||
}
|
||||
10
common/src/main/module.json5
Normal file
10
common/src/main/module.json5
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"module": {
|
||||
"name": "common",
|
||||
"type": "har",
|
||||
"description": "$string:module_desc",
|
||||
"deviceTypes": [
|
||||
"phone"
|
||||
]
|
||||
}
|
||||
}
|
||||
8
common/src/main/resources/base/element/string.json
Normal file
8
common/src/main/resources/base/element/string.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"string": [
|
||||
{
|
||||
"name": "module_desc",
|
||||
"value": "common module"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user