Commit Graph
9 Commits
Author SHA1 Message Date
laoyang 2aaabce8e3 节点表单内支持快捷新建参与人成员(批 29) 2026-09-29 22:34:34 +08:00
laoyang cd37f89807 chore: 导航链接的 src 改为读配置,去掉代码里的个人域名
面向公开仓库脱敏:高德 URI API 的 src 原先在服务端与前端各写死一份个人域名。

- src/helpers.php / templates/nodes/form.php:src 统一取 `config.php` 的 `amap.nav_src`。
  前端模板用 json_encode 输出,与后端同源 —— 原先这个值在两处各写一遍,
  注释里也提醒着「改一边就得改另一边」,现在只改配置一处即可。
- config.example.php:新增 `amap.nav_src`,默认占位 `trip.example.com`
  (src 只是高德侧的来源统计标识,填任意英数字串都能正常工作),
  并注明部署时按自己的域名改这一行。
- LICENSE:版权署名由个人昵称改为 The TripLog Authors。

线上配置已同步补上 `nav_src`(保持原有取值),因此**线上行为不变**:
以线上真实代码 + 真实配置跑过 amap_nav_url(),PHP 7.4 与 8.1 产出的 URL 与改动前逐字节一致。

验证:php -l 全过;amap_nav_url 单测 25/25(新增「src 必须来自配置」「源码里不得出现个人域名」
两条守卫,并做了反向验证 —— 把域名写回去时确实会红);渲染检查 386/386;
部署 10 步全绿(双版本 php -l + 静态断言 + 哈希校验 + 数据库迁移 + HTTP 冒烟 + 真实渲染)。
2026-09-28 19:12:59 +08:00
laoyang 142b187911 节点表单:参与人直接勾选 + 起点终点记录详细地址(批 13)
参与人(用户反馈:不想等全部填完、保存之后才回详情页加人):
- 节点表单里新增「参与人(直接勾选)」区块:家庭成员按胶囊复选框列出,
  保存即生效;编辑时预勾选已有人。
- Members::syncForNode() 只做增删,**不动留下来的那些人的情绪 / 签到 /
  角色标签**(重新勾一遍不该把之前记的心情清掉)。
- 安全性:新增走 Members::save(),逐条校验「用户属于该活动所在家庭」,
  夹带别家 user_id 进不来;只有请求带 members_present=1(表单确实渲染了
  参与人区块)才同步 —— 否则没这个字段的请求会把已有成员清空。

起点/终点记录详细地址(用户反馈:高德联想只返回简化地名):
- 联想项选中后不再是「青岛北站」就完事:
  ① 有坐标 → 逆地理编码(AMap.Geocoder.getAddress)取该点的
     formattedAddress;
  ② 没坐标 → 按「区县 + 名称」正查,顺带把坐标补回来。
- 详细地址写进 start/end_location 的 address 字段,输入框下方实时显示
  「详细地址:…」,节点详情页本来就展示这一项。
- 路线地图补点(只有地名没坐标时)改为**优先用详细地址**查询,
  比简化地名定位准。

已验证:本地全链 12 步全绿(渲染检查 350 项、真 AMap 浏览器验证 11 项、
表单交互 75 项);远端 e2e 350 项 0 失败(新增 10 项:勾选落库 / 跨家庭
拒绝 / 取消勾选移出 / 情绪不被清 / 无标记不动参与人);部署静态断言、
双 PHP 渲染、内容断言全过后提交。
2026-09-24 12:36:01 +08:00
laoyang 00cd99d07d 路线地图体验修正 + 节点表单裁剪改按钮触发(批 11)
地图四项调整(用户反馈):
- 位置移到封面之下、时间轴之前 —— 行程一长不用拉到底才看到地图;
- 圆形标记放在该段行程线的中点(起终点都有才取中点,缺一头放
  有的一头),不再是起点位置;anchor 改 center 居中骑在线上;
- 只有地名、没坐标的地点(偏僻地方下拉选不到点位):浏览器端调
  AMap.Geocoder 按地名查坐标,查到补进路线全量重画(drawRoute 幂等),
  查不到保持缺省不硬造位置;data-route 新增 sname/ename 地名;
- 提示文案同步更新。

裁剪交互(用户反馈):
- 选图后不再自动弹出裁剪框;每张能裁的缩略图旁放「✂ 裁剪」按钮
  (左上角,与右上角 × 对称),点了才弹框,只处理这一张;
- 「逐张裁剪」批量入口保留:确定后仍自动弹下一张(cropChain 标志
  区分两种流程);删除 addedCroppable 自动触发逻辑。

已验证:本地全链 11 步全绿(渲染检查 339 项、真实浏览器交互更新为
按钮触发流程 75 项);远端 e2e 340 项 0 失败(新增 9 项断言);
部署静态断言/双 PHP 渲染/内容断言全过后提交。
2026-09-24 11:41:11 +08:00
laoyang cbfdcb9d16 附件:支持视频 / 文档上传,可预览的浏览器直接看,预览不了的给下载
之前节点附件只能传图片和 PDF(accept 过滤 + 20MB 上限),视频传不上来。
本批放开:MP4 / MOV / PDF / Word / Excel / ZIP 都能作为附件上传,
仍走同一份 Uploads::handleFiles(MIME 白名单三层识别不变),存进已配置的 OSS。

大小分级:照片仍走 photo_max_mb(管理员可调),视频与文档放宽到 40MB
(Uploads::MAX_FILE_BYTES,给线上 post_max_size=50M 留余量),先 detect 再按
is_image 选上限。前端 form.php / attachments.php 同步拦截并按类型细分暂存区图标。

预览走灯箱四类型视图:图片(img)/ 视频(<video>,配合 media/show 新增的
HTTP Range 单区间 206/416,进度条可拖、Safari 也能播)/ PDF(iframe 内嵌
阅读器)/ 其它(Word / ZIP 等浏览器预览不了 → 灯箱里给下载按钮)。
dl=1 下载不接 Range;多区间回退 200 全量。

已验证:本地全链 11 步全绿(渲染检查 308 项、表单交互 73 项);
远端 e2e 0 失败(新增 mp4 上传落库 video/mp4、Range 206/416/后缀区间/
多区间回退/dl 不接 Range 专项断言);部署后双 PHP 渲染逐字节一致。
2026-09-23 16:22:01 +08:00
laoyang 86e0481a19 节点表单:24 小时制时间控件、附件可裁剪、保存失败不丢已填内容
1) 时间选择不再出现「上午/下午」
   datetime-local 的显示格式由浏览器 UI 语言决定(中文环境渲染成
   「下午 03:30」),页面与元素的 lang 都压不住(截图实测)。
   拆成「原生 date 控件 + 时下拉(00-23) + 分下拉(00-59)」组合控件
   (templates/_datetime.php),24 小时制由控件本身决定,浏览器改不掉。
   服务端 read_dt_input() 把三段拼回 Y-m-d H:i:s 并校验,兼容旧单字段。
   节点起止时间与成员签到时间一并换掉。

2) 附件可裁剪,不想裁点「确定」就按原图上传
   裁剪弹窗抽成 templates/_crop_modal.php 与照片页共用;表单里选完图
   自动逐张进入裁剪,整图选框=没裁(保留原文件、不重新编码、省 base64
   流量),裁过的走 cropped 通道(隐藏域 JSON,原生表单提交也能带),
   没裁的留在 input.files,已裁卡片带角标,可随时重置重来。

3) 节点保存失败不再清空整页
   服务端:失败分支 flash_old($_POST),重开的表单按 old()/old_array()/
   old_dt() 全字段回填(含勾选删除状态),渲染完即清(layout.php)。
   前端:sessionStorage 草稿兜底服务端拿不到 $_POST 的情况
   (post_max_size 超限、会话过期),恢复条件收窄到「页面有错误提示条」,
   避免陈旧草稿污染新表单。

已验证:本地 php -l 全量 0 错、模板渲染 275 项、表单交互(真实浏览器)
73 项——含真实裁剪一刀、草稿恢复、三段式时间选择;远端 e2e 289 项
0 失败(存储设置测试期间归一为 local,不再写用户真实 OSS 桶,收尾还原);
部署后双 PHP 渲染逐字节一致、59 文件哈希校验一致。
2026-09-23 09:11:42 +08:00
laoyang 44ffee6c92 节点表单:时间框整块可点、起点终点一键导航、可直接传多张附件
三件事都是「界面上明明该有、但用起来不通」的地方:

1) 时间输入框只能点右侧那个小日历图标才弹选择器
   原生行为如此(而且各浏览器还不一致),用户会以为输入框是只读的。
   在全局脚本里给 datetime-local / date 挂 click → showPicker(),
   等于把整块输入区都当成那个图标;选择器已在显示时再调是空操作,
   不支持的浏览器静默跳过。顺带给可点光标。
   写在 layout.php 里,所以签到时间等其它时间框一并生效。

2) 起点/终点的精确位置此前只存在页面上,没有落库
   高德选点回调里拿到的 lng/lat 现在随节点一起存库,节点详情页和
   表单页都生成高德导航链接,点一下就能直接导航过去(手机端会尝试
   唤起高德 App)。链接只给 to 不给 from,让移动端自动用当前位置当起点。
   ⚠️ 高德 URI API 的坐标顺序是「经度,纬度」,写反了不报错、只会安静地
   导到地球另一边,所以单独写了单元测试守住顺序。
   手工改地名时会清掉旧坐标,退化成关键字搜索,避免「名字改了位置没改」。

3) 节点处不能上传图片附件
   节点表单改成 multipart,加拖放区 + 多选 + 本地预览 + 逐个移除,
   编辑态可勾选删除已有附件(逐条校验归属,防止改 id 删到别人家的文件)。
   上传逻辑从 AttachmentController 抽到 src/Uploads.php,两处共用一份:
   MIME 白名单、大小上限、孤儿回滚各抄一份迟早走偏。
   请求体超过 post_max_size 时 PHP 会静默丢掉 $_POST 与 $_FILES,紧随的
   CSRF 校验必然失败、报「请求已过期」——这是最难排查的一类问题。
   现在按 CONTENT_LENGTH 提前识别并给出人话提示,且该守卫必须排在
   csrf_check() 之前(只读长度、不改状态,所以顺序上是安全的)。

已验证:本地 php -l 全量、模板渲染 255 项、amap_nav_url 单测 18 项、
真实浏览器交互 47 项(时间框三处点击都弹、多选/移除后 input.files 真被
改写、超限被前端拦下、导航链接随选点刷新、勾选删除变红、三视口无溢出)、
打包 57 文件、远端 e2e 267 项,全部通过;部署后双 PHP 渲染逐字节一致。
2026-09-23 07:53:20 +08:00
laoyang f9a624ef4b 交通字段简化 + 照片多图/裁剪/双存储后端 + 界面重构
交通字段
- 飞机只记航班号、火车只记车次,不再收座位号 / 线路 / 承运人 / 登机口
  (多人同乘一趟时座位号本来就无意义)
- trip_nodes 的历史列保留但不再写入也不再渲染,编辑一次即被显式清成 NULL
- 节点表单按出行方式切换号码标签(航班号 / 车次 / 班次 / 线路 / 航次)

照片
- 一个节点可传多张照片,前端支持逐张裁剪(Cropper.js,只把裁过的走 base64)
- MIME 由自建的 src/Mime.php 按内容识别:getimagesize → 魔数嗅探 → 扩展名映射,
  刻意不依赖 fileinfo —— 线上同时装着 PHP 7.4(没装该扩展)与 8.1
- 单张体积上限、可传张数在设置页可调

双存储后端
- 本站 uploads/ 或阿里云 OSS,在「存储设置」页配 endpoint / 桶名 / 前缀 / 内网开关;
  OSS 配置不完整时静默回退本地,避免管理员填错就把上传整个弄挂
- OSS 鉴权支持 ECS 实例 RAM 角色换 STS 临时凭证(临时凭证缓存在 /tmp,不落 Web 目录),
  也支持直接填 AccessKey
- OSS V1 签名手写实现,不引 SDK(本项目无 composer / 无 autoload)

媒体代理
- 照片与活动封面一律经 media/show、media/cover 代理输出,先校验家庭归属再吐字节
- 页面里不出现 OSS 域名,也不出现本地真实路径;桶保持私有
- 响应带 nosniff,并关闭 display_errors,免得 PHP 报错混进图片正文把图弄坏

数据库
- 新增迁移机制(src/Migrate.php + tools/migrate.php,仅 CLI 可跑)
- 建 app_settings 表;attachments 增加 storage / object_key / mime_type / size_bytes /
  width / height / is_image 与 idx_storage
- MySQL 8 不支持「加列时带 IF NOT EXISTS」,迁移一律先查 information_schema

界面
- 顶栏只留品牌与「新建活动」,其余入口收进右上角下拉菜单(details/summary,零 JS 可开合)
- 新增「存储设置」页:当前生效存储、数据库结构版本、是否 ECS 运行环境三张状态卡,
  外加测试连接与角色探测
- 样式表整体重写

验证
- 模板渲染检查 234 项、SQL 列名静态检查、模板到 CSS 的类覆盖检查、内联 JS 语法检查全通过
- 已部署到 trip.yangyq.net 并完成线上验证:双 PHP(8.1 / 7.4)14 条路由渲染字节数完全一致、
  26 项内容断言全部通过、照片代理取件与磁盘原件逐字节一致(69/69 字节)
2026-09-22 23:09:37 +08:00
laoyang a79956758b 补齐未接入代码的功能域:参与人 / 评论 / 花销与分摊
数据库里早就建好了 comments、node_members + role_tags、expenses +
expense_splits 三组表,但一直没有代码接入。本次把这三块连上业务,
并新增「节点详情页」作为聚合入口。

新增
- src/Access.php          统一归属校验。此前每个控制器各写各的 family_id
                          条件,容易漏;现在 activity()/node()/target() 一处收敛,
                          requireAdmin() 统一管理员判断。
- src/Comments.php        两级平铺:parent_id 只允许指向顶层,写「回复的回复」
                          时规范化回顶层;删顶层会连同回复一起删。
- src/Members.php         参与人 + 角色标签 + 情绪 + 签到。情绪目录 17 项,
                          全部选用 Emoji 11.0(2018)及以前的码位,避免旧系统
                          字体缺字形显示成空白方块。
- src/Expenses.php        花销与分摊。分摊按「分」为单位 intdiv 平均,
                          余数归最后一人,保证 SUM(share_amount) = total_amount。
- templates/nodes/show.php 节点详情页:基本信息 + 参与人 + 花销 + 讨论 + 附件入口。
- 角色标签管理、评论区、参与人区、花销区各自的模板与控制器。

修复
- 越权:往别人活动的节点上插节点、把别家用户加成参与人,都只校验了
  「节点存在」,没校验「用户与节点同家庭」。现统一走 Access。
- 删活动时节点附件文件删不掉:附件在 /uploads/attachments/ 下,却调了
  只认 /uploads/covers/ 的删除函数,判定不通过 → 文件永久残留。改为
  统一的 deleteUploadFile(),两个目录都认。
- 删有引用记录的用户会 500(activities.created_by / attachments.uploader_id /
  comments.author_id / expenses.payer_user_id 这几处没有级联,也没做前置检查)。

其他
- 活动/节点列表补上附件、评论、参与人、花销四个计数(一次 GROUP BY 查完)。
- 首页布局新增一条迁移易踩的规避:新列上的索引必须排在 ALTER TABLE 之后。
- 新增 .gitignore:config.php(含数据库口令)与 uploads/ 下用户上传的
  头像封面不入库,只保留 uploads/.htaccess 那条禁止执行脚本的规则。
- 新增 config.example.php 作为新环境的配置模板。

本批不改数据库结构,不需要任何数据迁移。

本地与线上验证
- php -l 全绿(PHP 8.4 本地 / 线上 7.4.30 与 8.1.7 双版本)。
- 模板渲染检查 180 项全通过。
- 线上端到端 153 项全通过:登录、活动增删改查、节点流转、
  参与人/角色标签/情绪/签到、评论、花销与分摊、权限、级联清理、
  全站页面无 PHP 报错泄漏、跑完基线逐表核对且既有 id 集合逐字未变。
- 顺带写了 trip_sql_column_check.py:拿真实库表结构静态校验全项目 SQL 的
  列名引用。本次就是靠它之外的 e2e 抓到 travel_modes 并没有 line_name
  (该列属于 trip_nodes),一个列名写错导致节点详情页整页 Fatal。
2026-09-22 22:03:08 +08:00