那个叫“死了么”的 App,让我想做一个温暖点的东西

大家好,我是前端大鱼。
前几个月,“死了么”App火了。几个人很短的时间做了一个极简功能——每天签到,两天不签就发邮件给紧急联系人。就这么简单,冲上了付费榜第一。
评论区最高赞的留言我一直记得:“名字太晦气了,为什么不叫‘活着么’?”
我想了很久。活着么?还是有点丧。后来有个读者留言说:“叫‘我在’吧,两个字,既回答了活着,也说出了陪伴。”
就它了。
「我在」——双层含义:我在(活着)、我在这里(守护你)。
今天这篇文章,是一个完整的项目规划书,从产品构想到技术实现,希望能给想做独立开发的朋友一些启发。
一、「我在」是什么?
1.1 产品定位
“死了么”的核心逻辑很简单:每日签到,两天不签就发邮件通知紧急联系人。它的成功在于直面死亡的黑色幽默。
但「我在」不想做第二个“死了么”。
我的产品定位是:从“怕死”到“惜活”,从“被动通知”到“主动记录”,从“孤独一人”到“有人陪伴”。
简单说:
-
“死了么”是在你可能死了的时候通知别人 -
「我在」是在你确定活着的时候记录自己,同时告诉在乎你的人:我还在
1.2 核心功能
|
|
|
|
|---|---|---|
| 每日签到 |
|
|
| 守护者机制 |
|
|
| 心情日记 |
|
|
| 时光胶囊 |
|
|
| 生命树 |
|
|
| 陪伴地图 |
|
|
1.3 渐进式提醒机制
这是「我在」最核心的守护功能:
-
12小时未签到:App推送提醒用户自己:“今天记得说‘我在’哦” -
24小时未签到:通知守护者:“你守护的人今天还没说‘我在’” -
36小时未签到:守护者需确认是否联系上你 -
48小时未签到:发送邮件给紧急联系人:“您的亲友已48小时未说‘我在’”
二、为什么叫「我在」?
这两个字,我想了很久。
第一层含义:活着。当你在App里点击“我在”,就是在告诉世界:今天我也在好好地活着。
第二层含义:陪伴。当你成为别人的守护者,你的存在本身就是一种承诺——“别怕,我在。”
第三层含义:回响。在这个孤独的时代,有人问你“在吗”,你可以回一句“我在”。简单,却温暖。
比起“活着么”的质问,「我在」更像是一个回答,一个承诺,一个拥抱。
三、为什么选uniCloud + UniApp?
作为一个独立开发者,我的选型原则是:一次编写,多端运行,免运维,低成本。
3.1 uniCloud的核心优势
-
一体化开发:前端直接调用云函数,不用配域名、HTTPS、跨域 -
定时任务内置:通过trigger配置,比node-cron更稳定 -
推送集成:uni-push 2.0直接可用,支持离线推送 -
免费额度够用:阿里云或腾讯云空间,每月有免费调用次数 -
自动扩缩容:不用关心服务器压力
四、技术架构
4.1 整体架构图

4.2 云函数结构
cloudfunctions/├── user/ # 用户相关├── checkin/ # 签到相关├── capsule/ # 时光胶囊├── timer/ # 定时任务└── common/ # 公共模块
五、核心代码实现
5.1 数据库设计(简版)
users集合:
{"_id": "用户ID","nickname": "昵称","guardian_id": "守护者ID","emergency_email": "紧急联系人邮箱","last_checkin": "最后签到时间","continuous_days": "连续签到天数"}
checkins集合:
{"user_id": "用户ID","mood": "心情","note": "今日小事","create_date": "签到时间"}
5.2 签到云函数:说“我在”
// cloudfunctions/checkin/create.jsexports.main = async (event, context) => {const { mood, note } = event;const { uid } = context.auth;const db = uniCloud.database();const dbCmd = db.command;// 检查今天是否已签到const today = newDate(); today.setHours(0, 0, 0, 0);const exist = await db.collection('checkins').where({user_id: uid,create_date: dbCmd.gte(today) }).get();if (exist.data.length > 0) {return { code: 400, msg: '今天已经说过“我在”了' }; }// 获取用户信息const user = await db.collection('users').doc(uid).get();const userData = user.data[0];// 计算连续天数let continuousDays = 1;if (userData.last_checkin) {const last = newDate(userData.last_checkin);const yesterday = newDate(today); yesterday.setDate(yesterday.getDate() - 1);if (last >= yesterday) { continuousDays = (userData.continuous_days || 0) + 1; } }// 开启事务const transaction = await db.startTransaction();try {// 插入签到记录await transaction.collection('checkins').add({user_id: uid, mood, note,create_date: newDate(),continuous_days: continuousDays });// 更新用户信息await transaction.collection('users').doc(uid).update({last_checkin: newDate(),continuous_days: continuousDays,total_checkins: dbCmd.inc(1) });await transaction.commit();// 通知守护者(如果有)if (userData.guardian_id) { uniCloud.callFunction({name: 'sendPush',data: {userId: userData.guardian_id,title: '❤️ 你守护的人说“我在”了',content: `${userData.nickname}今天打卡了,连续${continuousDays}天` } }); }return { code: 0, msg: '打卡成功', data: { continuous_days: continuousDays } }; } catch (e) {await transaction.rollback();throw e; }};
5.3 定时任务:检查未签到用户
// cloudfunctions/timer/checkReminder.js'use strict';exports.main = async (event, context) => {const db = uniCloud.database();const dbCmd = db.command;const now = newDate();// 查找48小时未签到的用户const cutoff48 = newDate(now - 48 * 3600 * 1000);const users48 = await db.collection('users').where({last_checkin: dbCmd.lt(cutoff48),emergency_email: dbCmd.exists(true) }).get();for (const user of users48.data) {// 发送邮件给紧急联系人await sendEmail({to: user.emergency_email,subject: '【紧急提醒】您的亲友可能失联',html: `${user.nickname}已48小时未打卡,请确认其安全。` }); }// 查找24小时未签到的用户const cutoff24 = newDate(now - 24 * 3600 * 1000);const users24 = await db.collection('users').where({last_checkin: dbCmd.lt(cutoff24),guardian_id: dbCmd.exists(true) }).get();for (const user of users24.data) {// 通知守护者await sendPushToUser(user.guardian_id, '你守护的人还没打卡', `${user.nickname}已24小时未说“我在”` ); }return { code: 0 };};// 发送推送asyncfunctionsendPushToUser(userId, title, content) {const uniPush = uniCloud.getPushManager();await uniPush.sendMessage({ user_id: userId, title, content });}// 发送邮件(使用nodemailer)asyncfunctionsendEmail({ to, subject, html }) {const nodemailer = require('nodemailer');const transporter = nodemailer.createTransport({host: 'smtp.qq.com',port: 465,secure: true,auth: {user: process.env.EMAIL_USER,pass: process.env.EMAIL_PASS } });await transporter.sendMail({from: `"我在" <${process.env.EMAIL_USER}>`, to, subject, html });}
5.4 前端:首页调用云函数
<template><viewclass="container"><viewclass="streak-card"><textclass="streak-num">{{ continuousDays }}</text><textclass="streak-label">连续说“我在” {{ continuousDays }} 天</text></view><viewv-if="!todayChecked"><button @click="handleCheckin">说「我在」</button></view><viewv-else><text>✅ 今天已打卡</text></view></view></template><script>exportdefault { data() {return {continuousDays: 0,todayChecked: false } }, onLoad() {this.checkTodayStatus(); },methods: {async checkTodayStatus() {const res = await uniCloud.callFunction({name: 'checkin-status' });this.continuousDays = res.result.data.continuous_days;this.todayChecked = res.result.data.today_checked; },async handleCheckin() { uni.showLoading({ title: '打卡中...' });const res = await uniCloud.callFunction({name: 'checkin-create',data: { mood: 'happy', note: '今天很好' } });if (res.result.code === 0) {this.todayChecked = true;this.continuousDays = res.result.data.continuous_days; uni.showToast({ title: '打卡成功', icon: 'success' }); } uni.hideLoading(); } }}</script>
六、成本估算
免费额度(阿里云uniCloud)
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1000日活成本:0元
七、写在最后
「我在」这个名字,是我能想到的最温暖的回答。
如果你也想做独立开发,欢迎评论区聊聊:
-
你最想要「我在」有什么功能? -
你会为哪些功能付费? -
这个名字,你喜欢吗?
评论区抽三位送终身会员(如果App真做出来的话😂)
关注公众号” 大前端历险记“,掌握更多前端开发干货姿势!
请在微信客户端打开
夜雨聆风
