事情的起因其实很简单。
去年有段时间,我午休和晚上睡前习惯打开淘宝上的一个小程序——一个木鱼应用,可以手动敲木鱼,也有颂钵禅音,听着"笃、笃、笃"的节奏慢慢就睡着了。效果出乎意料地好,成了我每天助眠的必备工具。
但它有两个致命问题:
第一,不支持后台播放。 切到别的 App 或者锁屏,声音就断了。这对一个助眠应用来说基本等于废了一半——你没法定时关闭后安心睡,也不能一边听一边刷别的。
第二,某一天我再打开,发现它下架了。 没有任何替代品可用。
那一刻的想法很简单也很程序员:既然没有,那我自己写一个好了。
就这样,「睡眠木鱼」这个项目开始了。
二、选型与架构
前端技术栈在flutter和uniapp之间选了 uni-app 3.0(基于 Vue 3):
一套代码同时出微信小程序和 Android App,维护成本低
Vue 生态我熟,Composition API + TypeScript,上手快
社区还算活跃,常见问题能找到参考
理论上支持 10+ 平台(微信、支付宝、抖音、H5、鸿蒙等),未来扩展空间大
后端选型多说两句。很多人问我为什么用 .NET 而不是 Java / Go / Node,答案很简单:效率优先。
写了很多年 C#,ASP.NET Core + Kestrel 是我最舒服的技术栈
性能足够,到我这个用户量级绰绰有余
.NET 10 开发生态已经很成熟:依赖注入、中间件管道、健康检查都是开箱即用
一个人维护,不存在团队协作的语言选型问题
选什么语言不重要,关键是你能用最熟的工具快速把东西做出来。
目前产品覆盖:
✅ 微信小程序(直接搜索【睡眠木鱼】可用)
✅ Android App(还未已上架,蓝凑云有下载链接)
🚧 iOS / 鸿蒙(开发中)
三、uniapp 开发的真实踩坑记录
下面这些坑都是我在这个项目中实际遇到的,分享出来希望能帮到同样在用 uniapp 做产品的朋友。
坑 1:音频播放 —— 最大的技术挑战
这是整个项目最核心的功能,也是耗时最长、踩坑最多的模块。核心矛盾是:用户需要的"后台播放 + 定时关闭",在不同平台上的实现完全不一样。
微信小程序端:
InnerAudioContext 切后台就暂停,iOS 和 Android 表现还不一致
要后台播放只能用 BackgroundAudioManager,但这个 API 同时只能管理一个音频实例,跟多音频切换的需求天然冲突
两个 API 之间切换需要专门维护播放状态机,否则会出现"正在播放却显示暂停""切换音频后进度归零"等各种诡异 Bug
App 端(Android):
uniapp 的音频 API 封装了原生播放器,但兼容性一般,部分低端机型解码 .mp3 时会直接报错
锁屏后系统省电策略杀进程 → 音频中断,需要写原生前台服务保活
音频资源最终做了双格式兼容(MP3 + OGG),配合条件编译分别加载
我最终的方案:
封装统一的 AudioManager 类,内部根据平台自动选择音频 API
小程序用 BackgroundAudioManager 保后台,App 用原生前台服务
播放状态用单向数据流管理,所有状态变更通过事件总线通知 UI
音频资源一律走 对象存储,本地不打包任何音频文件
教训: 如果你的项目重度依赖音频,第一天就去真机上测试后台播放 + 锁屏场景,别等开发完了再踩坑。
坑 2:小程序包体积 —— 差点没上线成功
uniapp 编译出来的初始包轻松破 2MB,而微信小程序主包限制就是 2MB。这个问题不解决,连审核都过不了。
我做了什么:
所有音频,图片资源走 对象存储 在线加载,本地零音频图片文件
部分 UI 改 CSS 绘制减少图片依赖
uniapp 分包加载:主包只放首页 + 核心框架,非核心页面全部丢子包
uniapp 自带的三方库能删的删,能按需引入的绝不全量导入
一顿操作之后,主包压缩到了 1.8MB 以内,刚好卡线过审。
坑 3:国际化(i18n)的隐形工作量
一开始只想做中文版,后来考虑海外用户,加了英文切换。用了 vue-i18n,本以为是"把文案翻译一遍"的事,结果发现远不止如此。
真正遇到的麻烦:
文案长度差异:中文一行能说完的,英文可能要两行。UI 布局大量硬编码了宽度,切英文后按钮溢出、文字截断,改了一轮 CSS
动态内容翻译:签到提示、课程分类标签、系统通知……这些不是静态文案,是后端返回的,需要前端做映射翻译
tabbar的文案切换语言没有生效:暂时还没解决。
建议: i18n 最好从项目第一天就做,至少架构上预留好。后期补的代价远大于一开始就规划。
坑 4:条件编译的代码腐化
uniapp 的 #ifdef / #ifndef 很好用,但当横跨小程序和 App 两端时,代码很容易变成这样:
// #ifdef MP-WEIXIN
// 微信登录、支付、分享逻辑……
// #endif
// #ifdef APP-PLUS
// App 端登录、原生插件调用……
// #endif
业务逻辑里散落着大量条件编译块,三个月后回头看,自己都分不清哪个分支对应哪个平台。
我的教训: 后面重构时把所有平台差异抽到了 adapter 层,业务代码只调 adapter 接口。不要让条件编译侵入业务逻辑。
坑 5:uniapp 内置组件的兼容性问题
项目中用了不少 uniapp 内置组件(scroll-view、swiper 等),在小程序端表现良好,但 App 端渲染效果经常不一致。比如 scroll-view 在 App 端的惯性滚动行为和小程序不一样,swiper 的循环模式在部分安卓设备上有闪烁,在IOS上显示有问题等各种兼容问题。
解决方式: 关键交互组件能使用三方成熟组件优先使用。
四、后端架构:一个人搞定的全栈设计
这一节好好聊。前端是敲门砖,后端才是真功夫。
整体技术栈
层 技术 说明
运行时 .NET 10 + ASP.NET Core Web API Kestrel 服务器,高性能够用
ORM SqlSugarCore 国产 ORM,CodeFirst 自动建表
数据库 PostgreSQL 16 / MySQL 8.0 双数据库兼容,一行配置切换
缓存 Redis 7 + 内存自动降级 StackExchange.Redis
认证 JWT + Token 黑名单 120 分钟有效期 + 7 天 RefreshToken
日志 Serilog 三路输出:控制台 + 文件 + 数据库
存储 策略模式 本地 / 阿里云 OSS / 腾讯云 COS 无缝切换
部署 Docker Compose 5 个服务一键编排
五层 DDD 分层
MuyuBackend/
├── Api/ # 表现层:Controllers + 中间件 + 后台任务
├── Application/ # 应用服务层:31 个 Service + DTOs
├── Domain/ # 领域层:29 个实体 + 枚举定义
├── Infrastructure/ # 基础设施层:DbContext + 仓储 + 缓存 + 存储
└── Common/ # 公共工具:JSON 配置 + 扩展方法
32 个 Controller(17 个前台 + 15 个 Admin 后台),31 个业务 Service,28 张数据库表。
说人话就是:功能模块比你想象的要多得多——用户认证、功德系统、签到、任务、成就、反馈、微信客服、自动回复、资源管理、系统配置、日志审计……麻雀虽小,五脏俱全。
分层的好处是改一处不影响全局。比如后来从 PostgreSQL 切换到腾讯云 MySQL,只改 Infrastructure 层的数据库配置,业务代码一行不动。
为什么选 SqlSugar 而不是 Entity Framework Core
更轻量:不需要 DbContext 那套复杂的变更追踪,心里有数
网上说SqlSugar性能更好:其实我也是第一次用,哈哈
Redis 缓存,但不强依赖
缓存架构有一个我特别满意的设计:Redis 不可用时自动降级到 ConcurrentDictionary 内存缓存。
请求 → 查 Redis → 命中:返回
→ 未命中:查数据库 → 写 Redis → 返回
→ 如果 Redis 挂了:查数据库 → 写内存缓存 → 返回
之后加了:
每分钟健康检查,Redis 恢复后自动回切
缓存过期时间加随机偏移,避免缓存雪崩
Castle DynamicProxy 实现 AOP 拦截,Service 层加个 [Cache] 特性就完事,不需要在业务代码里手写缓存逻辑
这个自动降级方案强烈推荐给做独立产品的朋友。 用户量不大的时候,自建 Docker Redis 的稳定性其实没有想象中高,有个兜底方案会安心很多。
审计日志:吃过亏才知道重要
上线初期为了赶进度,用户操作没有记日志。有一次数据异常——用户反馈功德值对不上——我没有任何线索排查。
后来补上了完整的日志体系:
AuditLogMiddleware:记录所有 /api 请求,包括请求体、响应状态、耗时
ExceptionLogMiddleware:全局异常捕获,记录完整堆栈
登录日志:每次登录的时间、平台、IP、设备信息
慢 SQL 日志:SqlSugar AOP 自动捕获 >1s 的查询,异步写入 slow_sql_logs 表
Serilog 三路输出:开发环境看控制台,生产环境落文件(按日滚动保留 30 天),关键数据入库方便管理后台直接查。
给你一个忠告:审计日志从第一天就做。 不是为了应付合规,是为了出问题时你能快速定位。这是为自己写的代码。
后台任务:生产者-消费者解耦
有些操作不需要等——比如用户敲完木鱼后同步功德值、清理过期 Token、发送通知邮件。这些耗时操作如果阻塞主线程,用户体验会明显下降。
实现了一个简单的 BackgroundTaskQueue + QueuedHostedService:
Controller 把耗时任务丢进队列,立刻返回
后台线程依次消费队列,不影响接口响应速度
没有用消息队列(RabbitMQ / Kafka),因为用户量没到那个级别。一个内存队列 + 后台线程就搞定了。
文件存储:三种方案随时切
音频文件和图片的存储,搞了策略模式:
// 配置一行切换
“Storage”: {
“Provider”: “AliyunOss” // 或 “TencentCos” 或 “Local”
}
三种方案实现同一个 IFileStorage 接口,业务代码完全不感知底层用的是哪个云。甚至能同时启用多个,管理后台在页面上选。音频文件走 对象存储 加速,设置了一年缓存时间,因为音频资源很少变动。
部署:docker-compose 一把梭
5 个服务用一个 docker-compose.yml 编排:
services:
postgres # PostgreSQL 16 (带健康检查)
redis # Redis 7 (带健康检查)
backend # .NET 10 Web API (依赖 postgres + redis 健康)
admin-frontend # Vue 3 管理后台 (Nginx)
miniapp-frontend # uni-app H5 (Nginx)
启动:docker-compose up -d,健康检查确保 backend 在 postgres 和 redis 都就绪后才启动,避免启动顺序导致的连接失败。
为什么没选微信云开发
一开始考虑过微信云开发(CloudBase),开箱即用,省掉运维。但最终没选:
原因:都是费用啊
最终选择了自建后端 + Docker 部署。虽然多了运维工作,但长期可控性更好。
监控与告警
没有上 Prometheus + Grafana 全家桶,用户量没到那个级别。但几个关键监控做了:
健康检查端点:/health 接口返回数据库和 Redis 连接状态
慢 SQL 监控:自动记录 + 管理后台可视化查看
API 调用统计:管理后台有专门页面看接口调用趋势
告警通知:系统异常通过管理后台 + 邮件通知
小而美,够用就行。
五、App 上架与备案 —— 比写代码更磨人的事
代码写完了不算完,真正的心累从「上架」开始。
微信小程序备案
小程序上线前必须完成备案,流程大概是:
提交主体信息(企业或个体户营业执照)
填写小程序基本信息 + 服务类目
提交审核,等待 1~3 个工作日
如果类目选错 → 驳回重来 → 再等 1~3 个工作日
Android App 上架
这更是一场修行。国内安卓应用市场大大小小十个左右,每个市场的审核标准、隐私政策要求、软著要求都不一样:
image.png
光是准备材料、填写各家表单、应对驳回就花了我好长时间。建议同步准备软著申请,这个周期最长(1~3 个月),一定要提前启动。
网站/小程序/APP ICP 备案
如果有配套官网,域名也需要 ICP 备案,又是一个独立流程。从提交到审核通过,顺利的话 1~2 周,不顺利反复驳回的话一个月起步。
一句话总结: 做独立产品,开发只占 40% 的时间,剩下的都是合规、上架、运营。心理预期要先建立好。
六、聊聊 AI:水平拉平了,但虎还是虎,高手+AI就是强强联合
这部分本来没有,但既然是跟园子里的老朋友们聊天,就多聊几句。
AI 真的拉平了程序员的水平差距
这是我的真实感受。
三年前,一个熟练后端和一个初级前端的差距是肉眼可见的——前者知道怎么调性能、怎么设计接口、怎么处理边界情况;后者可能连 Promise 链都绕不明白。
现在呢?AI 让初级程序员也能写出看起来"还行"的代码。复杂查询让 AI 写、页面布局让 AI 生成、甚至单元测试都能一键出。表面上,大家的产出差距在缩小。
这对行业来说是好事——门槛低了,更多人能参与到创造中来。但对每个个体来说,这也意味着:只靠"会写代码"已经不够了。
以前你比同事多会一个框架、多懂一个中间件,那就是实打实的竞争力。现在 AI 几秒钟就能给你讲清楚一个框架的用法。纯技能层面的领先优势,正在被快速抹平。
但前提:你得是虎
有一句话我特别认同——
AI 可以如虎添翼,但前提你得是虎。
什么意思?
AI 能把你的效率翻倍、甚至翻三倍。但如果你本来就是个空壳——不懂需求分析、没有架构思维、不会做技术判断——那 AI 只会帮你更快地产出一堆垃圾。
真正的「虎」是什么?
能独立判断一个需求值不值得做。 AI 不会告诉你"这个功能用户根本不需要",但你会。
能看出 AI 生成的代码哪里有问题。 AI 写出来的代码看起来没 Bug,但性能瓶颈、安全漏洞、扩展性问题,它不会主动提醒你。
知道"为什么这么做"而不只是"怎么做"。 AI 能给你方案,但选哪个方案、为什么选这个,是你的判断。
有产品思维。 技术是为产品服务的,AI 能在技术上帮你,但它不知道你的用户是谁、痛点在哪。
所以我的建议很简单:把 AI 当最强的副驾驶,但方向盘永远握在自己手里。 用它来放大你的优势,别指望它替你补齐短板。
我是怎么用 AI 的(说点实际的)
在这个项目中,AI 帮我做了这些事: