一、权限控制体系设计与实现
1. 凭证存储与 Auth 工具封装
技术原理 :
采用持久化存储方案 实现用户凭证(Token / 用户信息)的安全存储,基于 HarmonyOS 的AsyncStorage
实现键值对管理,通过模块化封装提升复用性。
代码实现:
typescript
// auth.ts
import { AsyncStorage } from '@ohos.data.secureStore';
import { AuthError } from '../errors/AuthError'; // 自定义错误处理模块
// 定义凭证存储规范
export enum AuthKey {
USER_CREDENTIAL = 'user_credential_v1', // 凭证存储Key(版本化管理)
AUTH_TIMESTAMP = 'auth_timestamp' // 凭证时效标记
}
/**
* @class AuthManager
* @description 权限管理核心类,负责凭证的持久化与生命周期管理
*/
export class AuthManager {
/**
* 初始化用户凭证(含时效验证)
* @returns {Promise<UserCredential | null>} 初始化后的用户凭证对象
*/
async initialize(): Promise<UserCredential | null> {
try {
const rawCredential = await AsyncStorage.getString(AuthKey.USER_CREDENTIAL);
if (!rawCredential) return null;
const credential = JSON.parse(rawCredential) as UserCredential;
const timestamp = await this.getAuthTimestamp();
// 简单时效验证(可扩展为Token过期时间校验)
if (Date.now() - timestamp > 3600 * 1000) { // 1小时有效期
await this.clear();
return null;
}
return credential;
} catch (error) {
console.error('[AuthManager] 初始化失败:', error);
throw new AuthError('INIT_FAILED', '凭证初始化失败');
}
}
/**
* 存储用户凭证
* @param {UserCredential} credential 用户凭证对象(含Token/用户信息)
* @returns {Promise<void>}
*/
async setCredential(credential: UserCredential): Promise<void> {
try {
await Promise.all([
AsyncStorage.setString(AuthKey.USER_CREDENTIAL, JSON.stringify(credential)),
AsyncStorage.setNumber(AuthKey.AUTH_TIMESTAMP, Date.now())
]);
} catch (error) {
console.error('[AuthManager] 存储失败:', error);
throw new AuthError('STORAGE_FAILED', '凭证存储失败');
}
}
/**
* 获取用户凭证
* @returns {Promise<UserCredential | null>}
*/
async getCredential(): Promise<UserCredential | null> {
try {
const rawCredential = await AsyncStorage.getString(AuthKey.USER_CREDENTIAL);
return rawCredential ? (JSON.parse(rawCredential) as UserCredential) : null;
} catch (error) {
console.error('[AuthManager] 获取失败:', error);
throw new AuthError('FETCH_FAILED', '凭证获取失败');
}
}
/**
* 清除凭证
* @returns {Promise<void>}
*/
async clear(): Promise<void> {
await Promise.all([
AsyncStorage.delete(AuthKey.USER_CREDENTIAL),
AsyncStorage.delete(AuthKey.AUTH_TIMESTAMP)
]);
}
private async getAuthTimestamp(): Promise<number> {
return AsyncStorage.getNumber(AuthKey.AUTH_TIMESTAMP) || 0;
}
}
// 导出单例实例
export const auth = new AuthManager();
// 类型定义(user.d.ts)
interface UserCredential {
token: string; // 认证Token
userId: string; // 用户唯一标识
username: string; // 用户名称
expiresIn: number; // 过期时间(秒)
}
2. 首页初始化与登录流程
技术方案:
-
通过
aboutToAppear
生命周期钩子实现页面级凭证初始化 -
登录流程采用模块化交互设计,分离 UI 逻辑与权限逻辑
代码实现:
typescript
// HomePage.ets
import { auth } from '../services/auth';
import { UserCredential } from '../types/user';
import { router } from '@ohos.router';
@Entry
@Component
struct HomePage {
@State credential: UserCredential | null = null;
@State isLoading: boolean = true;
// 页面初始化钩子
aboutToAppear() {
this.initAuthState();
}
// 权限状态初始化
private async initAuthState() {
try {
const credential = await auth.initialize();
this.credential = credential;
} catch (error) {
console.error('[HomePage] 初始化失败:', error);
} finally {
this.isLoading = false;
}
}
// 登录路由跳转
private navigateToLogin() {
router.pushUrl({ url: 'pages/LoginPage' });
}
build() {
Column() {
if (isLoading) {
Text('初始化中...').fontSize(16);
} else if (credential) {
// 已登录界面
UserInfoCard({ user: credential });
Button('退出登录').onClick(() => auth.clear().then(() => router.replaceUrl('pages/LoginPage')));
} else {
// 未登录界面
PublicLanding(); // 公共着陆页组件
Button('立即登录').type(ButtonType.Capsule).onClick(this.navigateToLogin.bind(this));
}
}.width('100%').height('100%');
}
}
// 登录页核心逻辑(LoginPage.ets)
@Entry
@Component
struct LoginPage {
@State formData: LoginForm = { username: '', password: '' };
@State errorMsg: string | null = null;
async handleLogin() {
try {
// 模拟登录请求(实际需调用后端API)
const response = await fetch('https://api.example.com/auth/login', {
method: 'POST',
body: JSON.stringify(formData),
headers: { 'Content-Type': 'application/json' }
});
if (!response.ok) throw new Error('登录失败');
const credential = await response.json() as UserCredential;
// 存储凭证并通知全局
await auth.setCredential(credential);
emitter.emit('auth:loginSuccess'); // 触发登录成功事件
// 处理回跳逻辑
const returnUrl = router.getParams()?.return_url || 'pages/HomePage';
router.replaceUrl(returnUrl);
} catch (error) {
this.errorMsg = '用户名或密码错误';
console.error('[LoginPage] 登录失败:', error);
}
}
}
二、请求凭证拦截与失效处理
1. 全局请求拦截器
技术原理 :
基于HTTP 中间件模式 实现请求凭证自动注入,通过状态码(401 Unauthorized
)识别权限失效场景,采用单例锁机制避免多次重复跳转。
代码实现:
javascript
// http-interceptor.ts
import { auth } from '../services/auth';
import { router } from '@ohos.router';
const UNAUTHORIZED_STATUS = 401;
let isProcessing = false; // 防止并发请求重复处理
/**
* HTTP请求拦截器
* @param {RequestOptions} options 请求配置
* @returns {RequestOptions} 增强后的请求配置
*/
export function authInterceptor(options: RequestOptions): RequestOptions {
const newOptions = { ...options };
// 注入凭证(仅处理非登录请求)
if (!newOptions.url.includes('/auth/login')) {
newOptions.headers = newOptions.headers || {};
newOptions.headers.Authorization = `Bearer ${auth.getCredential()?.token || ''}`;
}
return newOptions;
}
/**
* 响应处理器
* @param {Response} response 原始响应
* @returns {Promise<Response>} 处理后的响应
*/
export async function responseHandler(response: Response): Promise<Response> {
if (response.status === UNAUTHORIZED_STATUS) {
await handleAuthFailure();
throw new AuthError('UNAUTHORIZED', '权限验证失败');
}
return response;
}
// 权限失效处理
async function handleAuthFailure() {
if (isProcessing) return;
isProcessing = true;
try {
await auth.clear(); // 清除失效凭证
const currentUrl = router.getCurrentRoute()?.url || 'pages/HomePage';
router.pushUrl({ url: 'pages/LoginPage', params: { return_url: currentUrl } });
} finally {
isProcessing = false;
}
}
2. 接口调用示例
typescript
// api.service.ts
import { authInterceptor, responseHandler } from './http-interceptor';
interface ApiResponse<T> {
code: number;
data: T;
message: string;
}
export class ApiService {
static async get<T>(url: string): Promise<T> {
const response = await fetch(url, authInterceptor({ method: 'GET' }));
return responseHandler(response).then(res => res.json() as ApiResponse<T>).then(res => res.data);
}
}
// 使用示例(首页数据获取)
class HomeModel {
async loadQuestions() {
return ApiService.get<Question[]>('/api/questions');
}
}
三、首页数据更新与事件通信
1. 基于发布 - 订阅模式的事件通知
技术方案 :
采用 ** 轻量级事件总线(Emitter)** 实现跨组件通信,避免组件间直接依赖,提升可维护性。
代码实现:
typescript
// event-bus.ts
type EventCallback = (...args: any[]) => void;
class EventEmitter {
private events: Record<string, EventCallback[]> = {};
on(eventName: string, callback: EventCallback): void {
if (!this.events[eventName]) this.events[eventName] = [];
this.events[eventName].push(callback);
}
off(eventName: string, callback: EventCallback): void {
if (this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter(cb => cb !== callback);
}
}
emit(eventName: string, ...args: any[]): void {
this.events[eventName]?.forEach(callback => callback(...args));
}
}
export const emitter = new EventEmitter();
// 首页组件绑定事件
@Entry
@Component
struct HomeCategory {
@State questions: Question[] = [];
constructor() {
emitter.on('auth:loginSuccess', this.refreshQuestions.bind(this));
}
private async refreshQuestions() {
const model = new HomeModel();
this.questions = await model.loadQuestions();
}
// 组件渲染逻辑...
}
四、访问控制与路由鉴权
1. 鉴权方法封装
php
// auth.ts(扩展鉴权方法)
export class AuthManager {
// ... 原有方法 ...
/**
* 路由鉴权方法
* @param {string} targetUrl 目标路由地址
* @param {Function} callback 鉴权通过后的执行函数
*/
async authorize(targetUrl: string, callback: () => void): Promise<void> {
const credential = await this.getCredential();
if (credential) {
callback(); // 鉴权通过执行回调
} else {
// 存储回跳地址并跳转登录页
router.pushUrl({ url: 'pages/LoginPage', params: { return_url: targetUrl } });
}
}
}
2. 路由使用示例
typescript
// 需要鉴权的页面
@Entry
@Component
struct ProtectedPage {
build() {
Button('进入受保护页面')
.onClick(() => {
auth.authorize('pages/ProtectedPage', () => {
router.pushUrl('pages/ProtectedPage');
});
});
}
}
// 登录页回跳处理
@Entry
@Component
struct LoginPage {
aboutToAppear() {
const returnUrl = router.getParams()?.return_url as string;
if (returnUrl) {
// 登录成功后替换为回跳地址
this.returnUrl = returnUrl;
}
}
async handleLogin() {
// ... 登录逻辑 ...
router.replaceUrl(this.returnUrl || 'pages/HomePage');
}
}
五、隐私协议与用户协议页面
1. 通用 Web 页面组件
typescript
// WebPage.ets(增强版)
import { webview } from '@ohos.web.webview';
import { router } from '@ohos.router';
import { PageTitle } from '@ohos.app.ability';
@Entry
@Component
struct WebPage {
@State pageTitle: string = '协议内容';
@State targetUrl: string = '';
private controller = new webview.WebviewController();
aboutToAppear() {
const params = router.getParams() as { title: string; url: string };
this.pageTitle = params.title;
this.targetUrl = params.url;
// 设置页面标题
const titleBar = PageTitle.getPageTitle();
titleBar.setTitle(this.pageTitle);
}
build() {
Column() {
Web({ src: this.targetUrl, controller: this.controller })
.layoutWeight(1)
.width('100%')
.height('100%')
.onPageFinished((url) => {
console.log('[WebPage] 页面加载完成:', url);
});
}
.onDestroy(() => this.controller.destroy()) // 资源释放
.requirePermissions(['ohos.permission.INTERNET']); // 权限声明
}
}
2. 协议入口调用
php
// LoginPage.ets(协议入口)
Text('用户协议')
.fontSize(14)
.textColor('#1890FF')
.onClick(() => {
router.pushUrl({
url: 'pages/WebPage',
params: { title: '用户协议', url: 'http://110.41.143.89/user' }
});
});
Text('隐私政策')
.fontSize(14)
.margin({ left: 8 })
.textColor('#1890FF')
.onClick(() => {
router.pushUrl({
url: 'pages/WebPage',
params: { title: '隐私政策', url: 'http://110.41.143.89/privacy' }
});
});
六、工程化增强建议
-
凭证加密存储 :
使用
@ohos.security.hcrypt
对存储的凭证进行 AES 加密,提升数据安全性。 -
Token 刷新机制 :
实现
refresh_token
机制,当401
状态码返回时自动请求新 Token,减少用户登录次数。 -
权限层级管理 :
扩展
UserCredential
包含角色信息(如role: 'admin' | 'user'
),在路由鉴权中增加角色校验逻辑。 -
请求队列管理 :
在凭证失效处理中引入请求队列,确保刷新 Token 期间的请求有序执行。
该方案遵循分层架构设计原则,将权限逻辑、存储逻辑、UI 逻辑分离,具备可测试性与可扩展性,适用于中大型 HarmonyOS 应用的权限管理场景。