
01
一、 团队介绍
我们是来自金陵科技学院的五人开发团队,在本次鸿蒙仓颉挑战赛中凭借MeetWise智能会议助手项目获得佳绩。
团队成员:
徐州(队长):项目总体规划与核心业务全流程开发,负责AI服务层(IFlyTekService.cj、LLMService.cj)的对接与封装,以及录音详情页、待办模块和笔记模块的全栈开发
朱欣龙(全栈开发工程师):聚焦录音交互与数据基础设施,主导Recording.ets页面的交互逻辑实现,管理录音状态机,开发登录/注册模块及用户数据管理,设计基于RdbStore(SQLite)的本地存储方案
宋忠呈(前端与动效设计师):负责个人中心模块开发(MinePage.ets、ProfilePage.ets、SettingsPage.ets),基于ArkUI Canvas组件优化录音时的实时声波纹动效,提升用户视觉体验
唐志颖(核心逻辑与优化工程师):参与RecordingLogic.cj的开发,优化AudioCapturer音频流采集逻辑,针对讯飞转写结果进行去重算法优化,提升实时文字上屏速度与准确率,负责录音功能的稳定性测试与错误修复
吴仔琪(产品与设计师):负责UI/UX高保真原型设计,制定应用视觉规范,梳理项目技术架构,总结ArkTS与Cangjie混合开发的最佳实践,制作项目路演PPT,剪辑与后期制作产品演示视频
02
二、项目核心
Why:开发初衷
在数字化办公时代,会议记录与信息整理成为职场痛点。传统录音工具缺乏智能化处理能力,无法满足高效会议管理需求。我们致力于打造一款集实时转写、智能摘要、任务管理于一体的智能会议助手,解决以下核心问题:
会议记录效率低下:传统手写记录方式耗时费力,容易遗漏关键信息
信息检索困难:纯音频文件难以快速定位特定内容
会议成果转化慢:会后整理会议纪要需要大量人工操作
任务跟进缺失:会议中产生的待办事项难以有效跟踪
What:核心功能
实时语音转写:集成讯飞开放平台实时转写能力,准确率达95%以上

AI智能摘要:基于智谱GLM-4大模型生成会议摘要,自动提取关键信息点

笔记编辑:以瀑布流或列表形式展示用户创建的会议笔记。
待办事项管理: 支持“全部”、“进行中”、“已完成”标签切换。
How:关键技术
项目采用HarmonyOS原生开发架构,融合ArkTS与Cangjie语言优势:
AI服务层封装(IFlyTekService.cj)
// 讯飞实时转写服务封装示例public class IFlyTekService {private var socketClient: WebSocketClient;private var transcriptionBuffer: MutableList<String>;public func initialize(): Result<Void, Error> {// 初始化WebSocket连接this.socketClient = new WebSocketClient();this.transcriptionBuffer = newMutableList<String>();// 配置连接参数this.socketClient.setUrl("wss://api.iflyrec.com/ws/v1");this.socketClient.setOnMessageListener((message:String) -> {// 处理转写结果this.handleTranscriptionResult(message);});return Result.success(());}public func startRealTimeTranscription(audioStream: AudioStream):Result<Void, Error> {// 开始实时转写逻辑try {this.socketClient.connect();// 启动音频流采集audioStream.startCapture((data:AudioData) -> {// 发送音频数据到服务器this.sendAudioData(data);});return Result.success(());} catch (error) {return Result.failure(error);}}private func sendAudioData(data: AudioData): Void {// 将音频数据转换为Base64并发送var base64Data = Base64.encode(data.buffer);this.socketClient.send(base64Data);}private func handleTranscriptionResult(message: String): Void {// 解析转写结果并更新UIvar result = Json.parse(message);var text = result.get("text").asString();if (!text.isEmpty()) {this.transcriptionBuffer.add(text);// 通知UI更新EventManager.emit("transcriptionUpdate",text);}}}
大语言模型服务封装(LLMService.cj)
// 智谱GLM-4摘要服务封装public class LLMService {private var httpClient: HttpClient;private var apiKey: String;public func initialize(apiKey: String): Result<Void, Error> {this.apiKey = apiKey;this.httpClient = new HttpClient();return Result.success(());}public func generateSummary(transcript: String): Result<String, Error> {var request = new HttpRequest();request.setMethod(HttpMethod.POST);request.setUrl("https://open.bigmodel.cn/api/paas/v4/chat/completions");// 构建请求体var requestBody = Json.object().set("model", "glm-4").set("messages", Json.array(Json.object().set("role", "system").set("content", "你是会议纪要助手,请根据会议内容生成简洁准确的摘要"),Json.object().set("role", "user").set("content", "请根据以下会议内容生成摘要:\n\n" + transcript))).set("temperature", 0.7);request.setBody(requestBody.toString());request.setHeader("Authorization", "Bearer " + this.apiKey);request.setHeader("Content-Type", "application/json");try {var response = this.httpClient.execute(request);var result = Json.parse(response.getBody());var summary = result.get("choices").get(0).get("message").get("content").asString();return Result.success(summary);} catch (error) {return Result.failure(error);}}}
数据库访问对象(MeetingRecordDAO.ets)
import { RdbStore, RdbPredicates } from '@kit.ArkData';import { MeetingRecord } from '../model/MeetingRecord';const TABLE_NAME = 'meeting_records';const CREATE_TABLE_SQL = `CREATE TABLE IF NOT EXISTS ${TABLE_NAME} (id INTEGER PRIMARY KEY AUTOINCREMENT,title TEXT NOT NULL,content TEXT,createTime INTEGER,audioPath TEXT,summary TEXT)`;export class MeetingRecordDAO {private rdbStore: RdbStore;constructor(rdbStore: RdbStore) {this.rdbStore = rdbStore;}async createTable(): Promise<void> {await this.rdbStore.executeSql(CREATE_TABLE_SQL, []);}async insert(record: MeetingRecord): Promise<number> {const valuesBucket = {'title': record.title,'content': record.content,'createTime': record.createTime,'audioPath': record.audioPath,'summary': record.summary || ''};return await this.rdbStore.insert(TABLE_NAME, valuesBucket);}async queryAll(): Promise<MeetingRecord[]> {const predicates = new dataRdb.RdbPredicates(TABLE_NAME);const resultSet = await this.rdbStore.query(predicates);return this.mapResultSetToRecords(resultSet);}async queryById(id: number): Promise<MeetingRecord | null> {const predicates = new dataRdb.RdbPredicates(TABLE_NAME).equalTo('id', id);const resultSet = await this.rdbStore.query(predicates);const records = this.mapResultSetToRecords(resultSet);return records.length > 0 ? records[0] : null;}async update(record: MeetingRecord): Promise<number> {const valuesBucket = {'title': record.title,'content': record.content,'createTime': record.createTime,'audioPath': record.audioPath,'summary': record.summary};const predicates = new dataRdb.RdbPredicates(TABLE_NAME).equalTo('id', record.id);return await this.rdbStore.update(valuesBucket, predicates);}async delete(id: number): Promise<number> {const predicates = new dataRdb.RdbPredicates(TABLE_NAME).equalTo('id', id);return await this.rdbStore.delete(predicates);}private mapResultSetToRecords(resultSet: dataRdb.ResultSet): MeetingRecord[] {const records: MeetingRecord[] = [];if (resultSet.rowCount > 0) {while (resultSet.goToNextRow()) {const record: MeetingRecord = {id: resultSet.getLong(resultSet.getColumnIndex('id')),title: resultSet.getString(resultSet.getColumnIndex('title')),content: resultSet.getString(resultSet.getColumnIndex('content')),createTime: resultSet.getLong(resultSet.getColumnIndex('createTime')),audioPath: resultSet.getString(resultSet.getColumnIndex('audioPath')),summary: resultSet.getString(resultSet.getColumnIndex('summary'))};records.push(record);}}return records;}}
音频播放器(AudioPlayer.cj)
// 音频播放器封装public class AudioPlayer {private var player: AVPlayer;private var currentPosition: Int64;private var duration: Int64;public func initialize(): Result<Void, Error> {this.player = new AVPlayer();this.currentPosition = 0;this.duration = 0;// 设置播放状态监听this.player.setOnPlayEventListener((event: PlayEvent) -> {switch (event.getType()) {case PlayEvent.Type.PLAY:EventManager.emit("playStarted", ());break;case PlayEvent.Type.PAUSE:EventManager.emit("playPaused", ());break;case PlayEvent.Type.STOP:EventManager.emit("playStopped", ());break;case PlayEvent.Type.COMPLETION:EventManager.emit("playCompleted", ());break;}});return Result.success(());}public func loadAudio(filePath: String): Result<Void, Error> {try {var source = new AVSource();source.setSourceFromUri(filePath);this.player.setAVSource(source);this.player.prepare();this.duration = this.player.getDuration();return Result.success(());} catch (error) {return Result.failure(error);}}public func play(): Result<Void, Error> {try {this.player.play();return Result.success(());} catch (error) {return Result.failure(error);}}public func pause(): Result<Void, Error> {try {this.player.pause();return Result.success(());} catch (error) {return Result.failure(error);}}public func stop(): Result<Void, Error> {try {this.player.stop();return Result.success(());} catch (error) {return Result.failure(error);}}public func seekTo(position: Int64): Result<Void, Error> {try {this.player.seek(position, SeekMode.EXACTLY);this.currentPosition = position;return Result.success(());} catch (error) {return Result.failure(error);}}public func getCurrentPosition(): Int64 {return this.player.getCurrentTimeMs();}public func getDuration(): Int64 {return this.duration;}}
03
三、项目创新点
1. HarmonyOS NEXT 原生“双引擎”架构
首创 ArkTS(声明式 UI)与 Cangjie(高性能逻辑)混合架构。ArkTS 确保交互丝滑,仓颉处理高并发逻辑,实现了表现层与逻辑层的彻底解耦。
2. 智能会议全链路生产力闭环
打通了“高清录音 → 实时精准转写 → AI 摘要生成 → 结构化笔记/待办持久化”的端到端自动化流程,将传统碎片化的会议整理工作效率提升 10 倍以上。
3. 基于物理信号的非线性波形渲染
基于 PCM 采样数据,通过 RMS 均方根计算与对数分贝(dB)转换算法驱动视觉波形,提供工业级、真实起伏的交互反馈。
04
四、核心难点攻克
1. 软硬件协同下的架构转型
挑战 :初期尝试全仓颉开发录音功能,但受限于生态初期仓颉对底层硬件 API(AudioCapturer)的调用限制。
攻克 :果断切换为“硬件接口层(ArkTS)+ 核心处理层(仓颉)”架构。由 ArkTS 稳定采集流数据,透传给仓颉引擎进行高性能分贝计算、加密及 AI 处理,确保了功能的完整性与运行的高效率。
2. 跨语言高频数据同步与性能优化
挑战 :每秒上万个采样点的分贝数据及转写片段跨语言传输易导致 UI 卡顿。
攻克 :在 RecordingLogic.cj 中设计了节流回调机制,利用仓颉轻量级线程处理计算任务,确保了在复杂后台逻辑运行时,UI 列表滑动依然维持在 60 帧。
3. 实时转写结果的“去重与智能合并”算法
挑战 :网络波动导致的 WebSocket 乱序与重复推送会造成界面文字“跳变”闪烁。
攻克 :设计了基于 seg_id 的状态机管理策略,通过智能判断“中间态”与“最终态”文本进行按需刷新,将 UI 无效刷新率降低了 60%。
4. 极端环境下的“录音不丢失”稳定性保障
挑战 :应对断网、内存溢出或意外崩溃等极端场景,确保用户核心数据资产安全。
攻克 :采用“物理写入与在线转写分离”技术,配合指数退避重连算法。即使网络彻底中断, MeetingRecordDAO.ets 也能确保本地音频文件安全存盘。
5. 应用级全局状态的响应式同步
挑战 :多页面间用户信息、录音状态、业务数据的一致性同步难题。
攻克 :基于鸿蒙原生 StorageLink 构建应用级状态总线,建立单源数据池。实现了个人资料、业务状态在全应用内的实时感知与无感同步。
05
五、 感悟经验
1. 技术选型的重要性:
ArkTS+Cangjie的组合为项目提供了强大的性能基础,合理的技术架构是项目成功的关键。HarmonyOS的分布式能力也为我们的应用扩展提供了想象空间。
2. 团队协作的力量:
清晰的职责分工与高效的沟通机制,让我们能够在短时间内完成复杂功能的开发。每周的代码评审和进度同步确保了项目质量与进度。
3. 用户体验导向:
从用户实际需求出发,不断迭代优化功能,才是产品价值的核心所在。我们在开发过程中多次进行用户测试,根据反馈调整产品方向。
4. 持续学习精神:
面对新技术栈和复杂的AI集成,团队成员始终保持学习热情,通过官方文档、社区交流等方式快速掌握所需技能。
06
六、项目源码地址:
https://openatom.tech/cangjiechallenge/06ecb1ed6d7c99e118db3b63e3339ba5.git
领取仓颉专属红包封面!

发放时间2月12日 20:00
定好闹钟~扫码领取~

发放时间2月13日 12:00
定好闹钟~扫码领取~

发放时间2月13日 20:00
定好闹钟~扫码领取~
往期文章:
鸿蒙仓颉编程语言挑战赛一等奖作品:MeetAI-基于Cangjie的智能会后整理助手
鸿蒙仓颉编程语言挑战赛二等奖作品 :以仓颉之码,筑智慧学园——基于仓颉与OpenHarmony的智慧校园协同管控系统实践
鸿蒙仓颉编程语言挑战赛二等奖作品:TaskGenie 打造基于仓颉语言的智能办公“任务中枢”
