06 项目目录结构与编码规范:三层架构与 DAO 模式实践
📅 2026/7/9 2:14:05
👁️ 阅读次数
📝 编程学习
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):
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 模式带来了三个核心优势:
- 解耦:UI 层不需要知道数据库的具体实现,只调用 DAO 方法
- 可测试:DAO 是纯静态类,可以单独测试,不依赖 UI 框架
- 一致性:所有数据操作都经过
DatabaseService.getStore()进行,不会出现野生的数据库操作
四、命名规范
4.1 标识符命名约定
| 类型 | 规范 | 示例 |
|---|---|---|
| 类名 | PascalCase | DatabaseService、HandwritingRadar |
| 接口名 | PascalCase +Entity/Service/Result | UserEntity、RadarScores |
| 方法名 | camelCase,动词开头 | getInstance()、loadRecent() |
| 私有方法 | camelCase,同上 | parseRow()、draw() |
| @State 变量 | camelCase | isLoading、recentItems |
| 常量 | UPPER_SNAKE_CASE | DB_VERSION、TAG |
| 文件名 | PascalCase +.ets | AppColors.ets、HandwritingDao.ets |
| 数据库表名 | snake_case | handwriting_records、user_archives |
| 数据库字段 | snake_case | archive_id、created_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申请 |
总结
项目的目录结构和编码规范遵循以下核心原则:
- 三层架构:UI 层 → 业务层 → 基础设施层,层间依赖单向,职责清晰
- DAO 模式:统一的数据访问接口,解耦 UI 和数据库
- 设计令牌:AppColors/AppFonts/AppAnimations 集中管理,禁止魔数
- 命名规范:PascalCase(类/文件)、camelCase(方法/变量)、UPPER_SNAKE(常量)
- 文件组织:import → 接口定义 → 常量 → 组件,固定顺序保证可读性
下一篇预告:第07篇 ArkTS 基础类型与接口定义
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- 项目目录结构指南
- ArkTS 编码规范
- HAP/HSP/HAR 模块类型
- ArkTS 与 TypeScript 差异说明
- 第00篇 项目总览
- 第04篇 依赖管理
- 第07篇 ArkTS基础类型
- DevEco Studio 项目管理
编程学习
技术分享
实战经验