diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..52e1306 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# ── 本地配置(含数据库口令与加密密钥),不入库 ────────────────────────────── +# 部署时由服务器上的 config.php 提供;新环境请复制 config.example.php 再改。 +config.php + +# ── 用户上传内容(头像 / 照片 / 附件),不入库 ────────────────────────────── +# 只保留「禁止执行脚本」的保护规则,否则上传的 .php 能被直接访问。 +uploads/* +!uploads/.htaccess + +# ── 临时产物 ──────────────────────────────────────────────────────────────── +*.bak +*.bak.* +*~ +.DS_Store +Thumbs.db diff --git a/README.md b/README.md index 235e150..bf5b472 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,64 @@ # FamilyBook -家书,记录家庭的全部生活,一本用岁月写成的家谱。 \ No newline at end of file +**家书,记录家庭的全部生活,一本用岁月写成的家谱。** + +> 把一个家的「人」「日子」「去过的地方、看过的电影」收进同一本书里。 + +--- + +## 它是什么 + +一个自托管的家庭生活系统。它不是又一个待办清单,而是**这个家的长期档案** —— +十年之后还能翻出孩子第一次观影的票根、那年暑假走过的路线。 + +围绕三个圈展开: + +- **人**:家庭成员、亲友通讯录,以及他们之间的共享关系 +- **时间**:日历、日程、提醒,以及任务的期限 +- **事件**:观影、旅行、其他活动 —— 一切都落在同一条时间轴上 + +## 它在做什么 + +| 阶段 | 主题 | 内容 | +| --- | --- | --- | +| 0 | 地基 | 名册、账号、登录、权限模型、安装向导 | +| 1 | 通信 | 通讯录、共享给某个成员、CardDAV 服务端 | +| 2 | 时间 | 日历本、重复日程、提醒、CalDAV 服务端 | +| 3 | 归一 | 观影与旅行迁入统一事件模型 | +| 4 | 任务 | 任务管理,期限自动映射成日历事件 | +| 5 | 回看 | 统计、年度家书、时间轴 | + +当前进度:**阶段 0 起步**,仓库里只有图标与脚手架。 + +## 技术选型 + +- PHP 7.4+(无框架、无 Composer,上传到站点目录即可运行) +- MySQL 5.7+ / 8.0(`utf8mb4`) +- 前端为原生 HTML / CSS / JavaScript,无构建步骤 + +⚠️ 注意 CalDAV / CardDAV 是**双向协议**,和单纯的 `.ics` 文件导出不是一个量级的东西。 + +## 部署 + +1. 把代码放到站点根目录; +2. 复制 `config.example.php` 为 `config.php`,填写数据库与 `app_key`; +3. 访问站点根,按引导页确认 PHP 版本、扩展、配置、数据库连接四项都通过。 + +`config.php` 不入库(已在 `.gitignore` 中忽略),其中包含数据库口令与加密密钥。 + +⚠️ **`app_key` 一旦开始记录数据就不能再改**(换掉即解不开已有数据), +并且它必须单独备份 —— 密钥丢失等同于数据永久丢失。 + +## 隐私 + +身份证号、手机号属于敏感个人信息,一律加密存储。 +(就家庭自用而言,身份证号其实没有多少必须存的场景,启用前请三思。) + +## 图标 + +见 `assets/icons/`。概念是「**这本书就是家**」:屋顶即一本摊开的书, +屋顶下纸做的墙上,三行横线是这个家写下的日子。 + +## 开源协议 + +[Apache License 2.0](LICENSE) diff --git a/apple-touch-icon.png b/apple-touch-icon.png new file mode 100644 index 0000000..0078552 Binary files /dev/null and b/apple-touch-icon.png differ diff --git a/assets/icons/familybook-icon-maskable.svg b/assets/icons/familybook-icon-maskable.svg new file mode 100644 index 0000000..4bf8abc --- /dev/null +++ b/assets/icons/familybook-icon-maskable.svg @@ -0,0 +1,29 @@ + diff --git a/assets/icons/familybook-icon.svg b/assets/icons/familybook-icon.svg new file mode 100644 index 0000000..c004b25 --- /dev/null +++ b/assets/icons/familybook-icon.svg @@ -0,0 +1,45 @@ + diff --git a/assets/icons/icon-128.png b/assets/icons/icon-128.png new file mode 100644 index 0000000..573b498 Binary files /dev/null and b/assets/icons/icon-128.png differ diff --git a/assets/icons/icon-144.png b/assets/icons/icon-144.png new file mode 100644 index 0000000..9e8d540 Binary files /dev/null and b/assets/icons/icon-144.png differ diff --git a/assets/icons/icon-16.png b/assets/icons/icon-16.png new file mode 100644 index 0000000..6884c36 Binary files /dev/null and b/assets/icons/icon-16.png differ diff --git a/assets/icons/icon-180.png b/assets/icons/icon-180.png new file mode 100644 index 0000000..7f21b08 Binary files /dev/null and b/assets/icons/icon-180.png differ diff --git a/assets/icons/icon-192-maskable.png b/assets/icons/icon-192-maskable.png new file mode 100644 index 0000000..9be3e6e Binary files /dev/null and b/assets/icons/icon-192-maskable.png differ diff --git a/assets/icons/icon-192.png b/assets/icons/icon-192.png new file mode 100644 index 0000000..c235e39 Binary files /dev/null and b/assets/icons/icon-192.png differ diff --git a/assets/icons/icon-256.png b/assets/icons/icon-256.png new file mode 100644 index 0000000..2793bf8 Binary files /dev/null and b/assets/icons/icon-256.png differ diff --git a/assets/icons/icon-32.png b/assets/icons/icon-32.png new file mode 100644 index 0000000..392d984 Binary files /dev/null and b/assets/icons/icon-32.png differ diff --git a/assets/icons/icon-384.png b/assets/icons/icon-384.png new file mode 100644 index 0000000..95e2c48 Binary files /dev/null and b/assets/icons/icon-384.png differ diff --git a/assets/icons/icon-48.png b/assets/icons/icon-48.png new file mode 100644 index 0000000..39921f2 Binary files /dev/null and b/assets/icons/icon-48.png differ diff --git a/assets/icons/icon-512-maskable.png b/assets/icons/icon-512-maskable.png new file mode 100644 index 0000000..1b8c206 Binary files /dev/null and b/assets/icons/icon-512-maskable.png differ diff --git a/assets/icons/icon-512.png b/assets/icons/icon-512.png new file mode 100644 index 0000000..c432c54 Binary files /dev/null and b/assets/icons/icon-512.png differ diff --git a/assets/icons/icon-64.png b/assets/icons/icon-64.png new file mode 100644 index 0000000..f01bfd6 Binary files /dev/null and b/assets/icons/icon-64.png differ diff --git a/assets/icons/icon-96.png b/assets/icons/icon-96.png new file mode 100644 index 0000000..24e9677 Binary files /dev/null and b/assets/icons/icon-96.png differ diff --git a/config.example.php b/config.example.php new file mode 100644 index 0000000..37bc160 --- /dev/null +++ b/config.example.php @@ -0,0 +1,41 @@ + '家书', + // 站点对外根地址,末尾不要带斜杠。示例值,请替换成你自己的 + 'base_url' => 'https://example.com', + + // ---------------- 数据库(MySQL)---------------- + 'db' => [ + 'host' => '127.0.0.1', + 'port' => 3306, + 'name' => 'familybook', + 'user' => 'familybook', + 'pass' => '', + 'charset' => 'utf8mb4', + ], + + // ---------------- 敏感字段加密密钥 ---------------- + // 用于身份证号、手机号的对称加密(openssl AES-256-CBC)。 + // ⚠️ 开始记录数据之后这个值就不能再改:换掉它,已有数据就解不开了。 + // ⚠️ 请单独备份它。密钥丢失 = 数据永久丢失,比数据库丢了还严重。 + // 生成方式:openssl rand -hex 16 + 'app_key' => '请填写32位随机十六进制串', + + // ---------------- 调试 ---------------- + // true 时引导页会打印数据库连接错误的详情。 + // ⚠️ 线上务必保持 false —— 错误详情里可能带数据库账号名。 + 'debug' => false, +]; diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..07d27cf Binary files /dev/null and b/favicon.ico differ diff --git a/index.php b/index.php new file mode 100644 index 0000000..3297935 --- /dev/null +++ b/index.php @@ -0,0 +1,141 @@ + PHP_VERSION_ID >= 70400, + 'val' => PHP_VERSION . '(需 7.4 以上)', +]; + +// 2. 必需扩展 +$needExt = ['pdo_mysql' => '数据库', 'mbstring' => '中文处理', 'openssl' => '敏感字段加密', 'json' => '配置与接口', 'gd' => '图片裁剪']; +foreach ($needExt as $ext => $why) { + $ok = extension_loaded($ext); + $checks['扩展 ' . $ext] = ['ok' => $ok, 'val' => $ok ? '已加载 · ' . $why : '缺失 · ' . $why]; +} + +// 3. 配置文件 +$checks['配置文件 config.php'] = [ + 'ok' => $hasConfig, + 'val' => $hasConfig ? '已就位' : '未找到(请复制 config.example.php 后填写)', +]; + +// 4. 数据库连接(只有配置齐全才去连) +if (is_array($config) && !empty($config['db'])) { + $d = $config['db']; + try { + $dsn = sprintf( + 'mysql:host=%s;port=%d;dbname=%s;charset=%s', + $d['host'], $d['port'], $d['name'], $d['charset'] + ); + $pdo = new PDO($dsn, $d['user'], $d['pass'], [ + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_EMULATE_PREPARES => false, + ]); + $ver = $pdo->query('SELECT VERSION()')->fetchColumn(); + $checks['数据库连接'] = ['ok' => true, 'val' => 'MySQL ' . $ver . ' · 库 ' . $d['name']]; + } catch (Throwable $e) { + $checks['数据库连接'] = [ + 'ok' => false, + 'val' => $debug ? $e->getMessage() : '连接失败(把 config.php 的 debug 设为 true 可看详情)', + ]; + } +} + +$allOk = true; +foreach ($checks as $c) { + $allOk = $allOk && $c['ok']; +} +$siteName = is_array($config) && !empty($config['site_name']) ? $config['site_name'] : '家书'; +?> + +
+ + +FamilyBook —— 一本用岁月写成的家谱
+ + + +