06 项目目录结构与编码规范:三层架构与 DAO 模式实践

📅 2026/7/9 2:14:05 👁️ 阅读次数 📝 编程学习
06 项目目录结构与编码规范:三层架构与 DAO 模式实践

06 项目目录结构与编码规范:三层架构与 DAO 模式实践

前言

图:06 项目目录结构与编码规范:三层架构与 DAO 模式实践 运行效果截图(HarmonyOS NEXT)

良好的目录结构和编码规范,是大型项目"可维护、可扩展、可协作"的基石。随着业务逻辑增长,没有规范的项目会变成"意大利面条代码"——任何人改动一处都可能牵一发动全身。

"鹿鹿·笔迹心理分析"项目拥有约13,000 行 ArkTS 代码50 个 .ets 文件,在这样的规模下,合理的架构和规范的命名尤为重要。本文将系统介绍项目的目录组织原则、三层架构设计和各层的编码规范。

官方文档·项目目录结构:developer.huawei.com
ArkTS 编码规范:developer.huawei.com

图:鹿鹿项目三层架构——UI 表现层 → 业务功能层 → 公共基础设施层,职责清晰,层间依赖单向


一、项目完整目录树

1.1 顶层目录结构

harmony-app/ ├── AppScope/ # 应用级资源(全局共用) │ ├── app.json5 # 应用配置(包名、版本等) │ └── resources/base/ │ ├── element/string.json # 应用名字符串 │ └── media/app_icon.svg # 应用图标 │ ├── entry/ # 主 Entry 模块(唯一模块) │ ├── src/main/ │ │ ├── ets/ # ArkTS 源码根目录 │ │ ├── resources/ # 模块资源文件 │ │ └── module.json5 # 模块配置 │ ├── build-profile.json5 # 模块构建配置 │ ├── hvigorfile.ts # hvigor 构建脚本 │ └── oh-package.json5 # 模块依赖 │ ├── oh_modules/ # ohpm 依赖(不提交 git) ├── hvigor/ # hvigor 配置目录 ├── build-profile.json5 # 项目构建配置 ├── oh-package.json5 # 项目根依赖 └── hvigorfile.ts # 项目 hvigor 入口

1.2 源码目录详细结构

entry/src/main/ets/ ├── EntryAbility.ets # 应用入口 Ability │ ├── common/ # 公共基础设施层 │ ├── components/ # 可复用 UI 组件(6 个) │ │ ├── EmotionTrendChart.ets # 情绪折线图 │ │ ├── EmptyState.ets # 空状态组件 │ │ ├── HandwritingRadar.ets # 6 维雷达图 │ │ ├── HmTitleBar.ets # 标题栏 │ │ ├── MatchRing.ets # 匹配度圆环 │ │ └── PosterTemplate.ets # 分享海报模板 │ └── theme/ # 设计令牌系统(3 个文件) │ ├── AppAnimations.ets # 动画常量(时长、曲线) │ ├── AppColors.ets # 颜色令牌(20+ 颜色) │ └── AppFonts.ets # 字体/间距/圆角令牌 │ ├── features/ # 业务功能层 │ ├── ai/ # AI 推理模块(4 个文件) │ │ ├── AiPipelineService.ets # 推理流水线(统一入口) │ │ ├── EmotionClassifier.ets # 情绪分类器 │ │ ├── HandwritingFeatureExtractor.ets # 手写特征提取 │ │ └── OcrService.ets # OCR 文字识别 │ └── data/ # 数据访问层(8 个文件) │ ├── ArchiveDao.ets # 档案 DAO │ ├── DatabaseService.ets # RDB 数据库单例 │ ├── DemoSeeder.ets # 演示数据播种器 │ ├── HandwritingDao.ets # 手写记录 DAO │ ├── Models.ets # 数据实体接口定义 │ ├── RelationDao.ets # 关系 DAO │ ├── ReportDao.ets # 报告 DAO │ └── UserDao.ets # 用户 DAO │ └── pages/ # UI 表现层(16 个页面) ├── AnalyzingPage.ets # 分析中(进度动画) ├── ArchivePage.ets # 档案管理 ├── BindRelationPage.ets # NFC 绑定伴侣 ├── CapturePage.ets # 拍照/相册选图 ├── CoupleHomePage.ets # 双人空间首页 ├── DuetReportPage.ets # 双人合盘报告 ├── EmptyPage.ets # 空状态/占位 ├── HomePage.ets # 应用主页 ├── LoginPage.ets # 华为账号登录 ├── MePage.ets # 个人中心 ├── PhoneLoginPage.ets # 手机号登录 ├── ReportDetailPage.ets # 报告详情 ├── SharePage.ets # 分享页面 ├── TrendPage.ets # 情绪趋势 ├── UnclassifiedArchivePage.ets # 未归类整理 └── WritePage.ets # 手写板

二、三层架构设计

2.1 架构分层原则

项目采用经典的三层架构(Three-Tier Architecture)

② 业务功能层 (features/)

读写 AppStorage

使用设计令牌

调用推理

③ 公共基础设施 (common/theme/)

设计令牌系统
AppColors · AppFonts · AppAnimations

① UI 表现层 (pages/ + common/components/)

16 个 @Entry 页面
HomePage · CapturePage · WritePage
AnalyzingPage · ReportDetailPage · ...

6 个可复用组件
HandwritingRadar · EmotionTrendChart
MatchRing · HmTitleBar · EmptyState

data/ — 数据访问层 (DAO)
DatabaseService · UserDao
ArchiveDao · ReportDao · ...

ai/ — AI 推理层
AiPipelineService
OcrService · FeatureExtractor
EmotionClassifier

2.2 各层职责

层次目录主要职责依赖方向
UI 表现层pages/+common/components/页面布局、用户交互、动画效果依赖 features/ 和 common/theme/
业务功能层features/data/+features/ai/数据 CRUD、AI 推理、业务逻辑依赖 common/theme/(偶尔)
公共基础设施common/theme/设计令牌、动画常量无依赖(最底层)

层间依赖原则:只允许上层依赖下层,禁止下层依赖上层(features/data不应该 import 任何pages/中的内容)。


三、DAO 设计模式

3.1 DAO 模式的统一结构

项目中每个 DAO 类都遵循相同的接口约定:

exportclassXxxDao{// ① 私有 getStore:获取 RDB 实例(防止外部绕过 DatabaseService)privatestaticgetStore():relationalStore.RdbStore{returnDatabaseService.getInstance().getStore();}// ② 创建记录staticasynccreate(item:XxxEntity):Promise<number>{try{conststore=XxxDao.getStore();constvalues:relationalStore.ValuesBucket={/* 字段映射 */};returnawaitstore.insert('xxx_table',values);}catch(e){hilog.error(0x0000,TAG,'create 失败: %{public}s',JSON.stringify(e));throwe;}}// ③ 按 ID 查询staticasyncfindById(id:number):Promise<XxxEntity|null>{/* ... */}// ④ 条件查询列表staticasynclistBy(condition:string):Promise<XxxEntity[]>{/* ... */}// ⑤ 更新staticasyncupdate(item:XxxEntity):Promise<void>{/* ... */}// ⑥ 删除staticasyncdeleteById(id:number):Promise<void>{/* ... */}// ⑦ 私有 parseRow:ResultSet → Entity 转换privatestaticparseRow(rs:relationalStore.ResultSet):XxxEntity{return{id:rs.getLong(rs.getColumnIndex('id')),// ... 字段映射};}}

3.2 DAO 设计的优势

这种统一的 DAO 模式带来了三个核心优势:

  1. 解耦:UI 层不需要知道数据库的具体实现,只调用 DAO 方法
  2. 可测试:DAO 是纯静态类,可以单独测试,不依赖 UI 框架
  3. 一致性:所有数据操作都经过DatabaseService.getStore()进行,不会出现野生的数据库操作

四、命名规范

4.1 标识符命名约定

类型规范示例
类名PascalCaseDatabaseServiceHandwritingRadar
接口名PascalCase +Entity/Service/ResultUserEntityRadarScores
方法名camelCase,动词开头getInstance()loadRecent()
私有方法camelCase,同上parseRow()draw()
@State 变量camelCaseisLoadingrecentItems
常量UPPER_SNAKE_CASEDB_VERSIONTAG
文件名PascalCase +.etsAppColors.etsHandwritingDao.ets
数据库表名snake_casehandwriting_recordsuser_archives
数据库字段snake_casearchive_idcreated_at

4.2 装饰器使用约定

// ✅ 标准写法:@Entry 和 @Component 分别一行@Entry@Componentstruct HomePage{// ✅ 公开 @Prop:PascalCase 类型,= 赋默认值@Proptitle:string=''@PropshowBack:boolean=false// ✅ 内部 @State:一目了然的变量名@StateisLoading:boolean=false@Stateitems:ArchiveEntity[]=[]// ✅ @Watch 与 @State 放在一起@State@Watch('onProgressChange')drawProgress:number=0// ✅ 私有常量:类顶部定义privatereadonlyTAG='HomePage'privatereadonlyPAGE_SIZE=20// ✅ 生命周期方法在前,@Builder 在后,build() 最后aboutToAppear():void{...}aboutToDisappear():void{...}@BuilderRecentCard(item:RecentItem){...}build(){...}}

五、文件组织规范

5.1 页面文件结构规范

每个页面文件 (pages/XxxPage.ets) 按以下顺序组织代码:

// 1. import 语句(按 @kit 包 → 业务模块 → 组件顺序)import{hilog}from'@kit.PerformanceAnalysisKit';import{ArchiveDao}from'../features/data/ArchiveDao';import{HmTitleBar}from'../common/components/HmTitleBar';import{AppColors,AppFonts}from'../common/theme/AppColors';// 2. 本地接口/类型定义interfaceRecentItem{reportId:number;name:string;personalityType:string;}// 3. 常量定义constTAG='ArchivePage';constPAGE_SIZE=20;// 4. 页面组件@Entry@Componentstruct ArchivePage{// 4.1 @Prop 属性(外部传入)// 4.2 @State 属性(内部状态)// 4.3 私有成员变量// 4.4 生命周期方法// 4.5 私有业务方法// 4.6 @Builder 方法// 4.7 build() 方法(放最后)}

5.2 组件文件规范

// common/components/HandwritingRadar.ets// 1. 类型定义(放在组件文件内,一起导出)exportinterfaceRadarPoint{label:string;value:number;}// 2. 组件定义(export struct)@Componentexportstruct HandwritingRadar{// @Prop 外部数据@Propdata:RadarPoint[]=[];@PropradarSize:number=280;// 私有内部状态privatecontext:CanvasRenderingContext2D;@State@Watch('onDrawProgressChange')drawProgress:number=0;// 生命周期aboutToAppear():void{...}// @Watch 回调onDrawProgressChange():void{...}// 私有绘图方法privatedraw():void{...}// build()build(){Canvas(this.context).width(this.radarSize).height(this.radarSize)}}

六、设计令牌系统

6.1 AppColors 颜色体系

// common/theme/AppColors.ets — 颜色令牌(不允许直接写 hex 魔数)exportclassAppColors{// 品牌色系(暖棕色系)staticreadonlyBG:string='#F1ECE2';// 背景色staticreadonlyCARD:string='#F9F5EF';// 卡片背景staticreadonlyPRIMARY:string='#A8907A';// 主色staticreadonlyPRIMARY_DEEP:string='#8B7264';// 深主色staticreadonlyWARM:string='#D9A689';// 暖橙色// 文字色系(三级层次)staticreadonlyTEXT:string='#3D352E';// 主文字staticreadonlyTEXT_2:string='#8B7E72';// 次要文字staticreadonlyTEXT_3:string='#B5A99A';// 第三级文字// 辅助色系(情绪色)staticreadonlyQUIET:string='#7B96C2';// 沉静蓝staticreadonlyROSE:string='#D4849A';// 玫瑰粉staticreadonlyDEEP:string='#6B5D52';// 深棕色// 线条/边框staticreadonlyLINE:string='#E8E0D5';// 浅分割线}

6.2 AppFonts 字体令牌

// common/theme/AppFonts.ets — 字体尺寸令牌exportabstractclassAppFonts{// 字号系统(6 级)staticreadonlyTITLE_L:number=28;// 大标题staticreadonlyTITLE_M:number=20;// 中标题staticreadonlySUBTITLE:number=16;// 副标题staticreadonlyBODY:number=14;// 正文staticreadonlyCAPTION:number=12;// 辅助staticreadonlyTINY:number=10;// 极小// 字重常量staticreadonlyW_REGULAR:number=400;staticreadonlyW_MEDIUM:number=500;staticreadonlyW_BOLD:number=700;}

七、代码质量规范

7.1 ArkTS 特有约束

ArkTS 相对 TypeScript 有一些额外的限制,需要注意:

  • 禁止使用any类型:必须明确声明类型
  • 禁止动态属性访问obj['key']应改为(obj as Record<string, T>)['key']
  • 禁止eval():ArkTS 不支持动态代码执行
  • 接口声明方式:ArkTS 推荐使用interface而非type(用于对象类型)
// ❌ TypeScript 写法(在 ArkTS 中不推荐或不允许)constvalue=obj['dynamicKey'];// 动态属性访问constx:any=someValue;// any 类型// ✅ ArkTS 推荐写法constrecord=objasRecord<string,Object>;constvalue=record['dynamicKey'];// 先类型断言再访问constx:number|string=someValue;// 明确类型

7.2 错误处理规范

// ✅ 推荐的错误处理模式staticasynccreate(item:ArchiveEntity):Promise<number>{try{conststore=ArchiveDao.getStore();returnawaitstore.insert('archives',this.toValuesBucket(item));}catch(e){// 1. 记录日志(包含方法名和原始错误)hilog.error(0x0000,TAG,'ArchiveDao.create 失败: %{public}s',JSON.stringify(e));// 2. 重新抛出(让调用方决定如何处理)thrownewError(`ArchiveDao.create failed:${JSON.stringify(e)}`);}}

7.3 注释规范

/** * 双人匹配度圆环组件 * * 使用场景:DuetReportPage 顶部的匹配度展示区域 * 特点:Canvas 绘制 + @Watch 驱动动画(从 0 到目标值顺时针生长) * * @example * MatchRing({ matchValue: 78, ringSize: 200, label: '整体匹配度' }) */@Componentexportstruct MatchRing{@PropmatchValue:number=78// 匹配度 0-100@PropringSize:number=200// 圆环尺寸(宽高相等)@Proplabel:string='匹配度'// 中心文字标签}

八、注意事项与常见问题

8.1 开发注意事项

在实际开发过程中,需特别注意以下几点:

  • API 兼容性:部分接口仅在特定 HarmonyOS NEXT 版本中可用,需做版本条件判断
  • 权限模型:采用静态声明(module.json5)+ 动态申请(requestPermissionsFromUser)的两阶段授权
  • 生命周期:合理使用aboutToAppear()aboutToDisappear()管理资源初始化与释放
  • 状态同步:跨页面数据通过 AppStorage 共享,组件内状态使用@State/@Prop/@Link装饰器

8.2 常见错误与解决方案

开发过程中的高频问题汇总:

错误现象可能原因解决方案
权限被系统拒绝未在module.json5中静态声明添加requestPermissions权限配置项
页面跳转后白屏路由路径未在main_pages.json注册检查并补充路由配置文件
UI 状态不刷新状态变量未添加响应式装饰器为变量加@State/@Observed装饰器
数据库查询返回空查询条件与存储数据格式不匹配使用hilog.debug打印 SQL 条件排查
相机预览黑屏运行时相机权限未获取先调用requestPermissionsFromUser申请

总结

项目的目录结构和编码规范遵循以下核心原则:

  1. 三层架构:UI 层 → 业务层 → 基础设施层,层间依赖单向,职责清晰
  2. DAO 模式:统一的数据访问接口,解耦 UI 和数据库
  3. 设计令牌:AppColors/AppFonts/AppAnimations 集中管理,禁止魔数
  4. 命名规范:PascalCase(类/文件)、camelCase(方法/变量)、UPPER_SNAKE(常量)
  5. 文件组织:import → 接口定义 → 常量 → 组件,固定顺序保证可读性

下一篇预告:第07篇 ArkTS 基础类型与接口定义

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

  • 项目目录结构指南
  • ArkTS 编码规范
  • HAP/HSP/HAR 模块类型
  • ArkTS 与 TypeScript 差异说明
  • 第00篇 项目总览
  • 第04篇 依赖管理
  • 第07篇 ArkTS基础类型
  • DevEco Studio 项目管理