Laravel中JSON存储用户活动:多用户大文件场景旧记录丢失求助
问题分析与修复方案
你现在的问题核心出在两点:
- 当前用
Storage::append写入的不是标准JSON数组,只是每行一个独立JSON对象——如果后续有代码尝试将这些行整合成标准JSON数组(比如读取全部内容后重新编码写入),多用户并发时必然会出现覆盖,导致旧记录丢失。 - 即便不做数组转换,当文件体积过大时,部分文件系统的追加操作可能因缓存或并发冲突出现数据丢失。
方案1:改用JSON Lines格式(推荐,无并发覆盖风险)
这种格式允许每行存储一个独立的JSON对象,写入时直接追加,读取时逐行解析,天然支持并发写入,无需复杂锁机制。
修改appendToLog方法:
protected function appendToLog($activity) { $logFile = 'activity.jsonl'; // 用.jsonl后缀明确标识格式,避免混淆标准JSON // 确保每个JSON对象是单行,防止换行符破坏结构 $log = json_encode($activity, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . PHP_EOL; Storage::append($logFile, $log); }
读取日志时的处理方式:
$lines = Storage::lines('activity.jsonl'); $activities = array_map(fn($line) => json_decode($line, true), $lines);
方案2:标准JSON数组+文件锁(适合必须用数组的场景)
如果一定要保持文件为标准JSON数组,必须在读写时加排他锁,阻止并发操作导致的覆盖。
修改appendToLog方法:
protected function appendToLog($activity) { $logFile = storage_path('app/activity.json'); // 用绝对路径确保锁能正确生效 $handle = fopen($logFile, 'c+'); // 打开文件,不存在则自动创建 if (flock($handle, LOCK_EX)) { // 获取排他锁,同一时间仅一个进程可写入 fseek($handle, 0, SEEK_END); // 跳转至文件末尾 $fileSize = ftell($handle); if ($fileSize === 0) { // 文件为空时,直接写入包含第一个元素的数组 fwrite($handle, json_encode([$activity], JSON_PRETTY_PRINT)); } else { // 移除文件末尾的闭合符"]",追加新元素后补回闭合符 fseek($handle, -1, SEEK_END); fwrite($handle, ',' . PHP_EOL . ' ' . json_encode($activity, JSON_PRETTY_PRINT) . ']'); } flock($handle, LOCK_UN); // 释放锁,允许其他进程操作 } fclose($handle); }
注意事项:
- 用
storage_path获取绝对路径,避免Laravel Storage封装带来的路径歧义,保证锁的有效性。 JSON_PRETTY_PRINT用于维持格式可读性,不需要可以直接删除。- 该方式在高并发场景下会有性能损耗,因为锁会让写入操作串行化。
额外建议
不要用JSON文件存储大量日志,当记录量达到一定规模后性能会急剧下降,建议改用专门的日志表(比如activity_log),或者ELK、Graylog这类专业日志系统;如果坚持用文件存储,可以按日期拆分日志(比如activity_20240520.jsonl),避免单个文件体积过大。
内容的提问来源于stack exchange,提问作者ERaufi
相关产品推荐
相关产品推荐

