场景与目的:   写简单的脚本,执行任务,数据量不是很多, 希望记录历史(像是使用mongodb 一样),和防止重复执行, 同时达到 事件日志的目的。(希望数据可视化,不引入其它数据库,也不需要创建表(sqllite))

1.  HistoryUtils.py (class版本)
2. HistoryUtil.py 脚本版

1.
HistoryUtils.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @mail    : lshan523@163.com
# @Time    : 2026/1/15 10:46
# @Author  : Sea
# @File    : HistoryUtil.py
# @Purpose :
# @history :
# 小文件场景下,使用json文件存储历史记录,每次追加,后续可以查询,修改,删除
# ****************************
import os
import json
from typing import Dict, Any, List, Optional
import uuid
import tempfile
import shutil


class HistoryUtils:
    def __init__(self, history_file="./history/history.json"):
        #历史记录文件
        self.history_file = history_file

    def save(self, history={},history_file= None):
        """
        保存历史记录 把文件写入 本地json文件,可以一直追加,后续可以查询
        :param history_file: 历史记录文件 eg:./history/history.json
        :param history: 历史记录 eg:{"name": "张三9", "age": 27}
        :return:
        """
        history_file = self.history_file
        if not history:
            print("history is None")
            return
        # history 必须是 dict
        assert isinstance(history, dict), "history 必须是 dict"
        history["_id"]=str(uuid.uuid4()).replace("-", "")
        # 创建文件
        if not os.path.exists(history_file):
            print("history.json 不存在 创建文件 "+str(history_file))
            os.makedirs(os.path.dirname(history_file), exist_ok=True)
        with open(history_file, 'a', encoding='utf-8') as f:
            f.write(json.dumps(history, ensure_ascii=False))
            f.write("\n")
        print("保存历史记录")



    def find_one(self,query: Dict[str, Any]={},history_file: str= None) -> List[Dict[str, Any]]:
        """
        查询历史记录
        :param history_file: 历史记录文件
        :param query: {"name": "张三1"}
        :return:
        """
        print("查询历史记录")
        history_file = self.history_file
        result = self.query_all_history(query,1)
        if result:
            return result[0]
        return None




    def find(self,query: Dict[str, Any] = {}, limit: Optional[int] = None,history_file: str = None) -> List[Dict[str, Any]]:
        """
        查询所有匹配的历史记录
        Args:
            history_file: 历史记录文件路径
            query: 查询条件字典,如 {"name": "张三1"}
        Returns:
            所有匹配的记录列表,按时间倒序排列(最新的在前)
        """
        history_file = self.history_file
        results = []
        try:
            with open(history_file, 'r', encoding='utf-8') as f:
                # 读取所有行并反转(最新的在前)
                lines = f.readlines()
                for line in reversed(lines):
                    line = line.strip()
                    if not line:  # 跳过空行
                        continue
                    try:
                        history = json.loads(line)
                        # 条件匹配
                        if all(history.get(key) == value for key, value in query.items()):
                            results.append(history)
                            print(f"查询到匹配记录: {history}")
                            if limit and len(results) >= limit:
                                break
                    except json.JSONDecodeError:
                        line_preview  =line[:50] + "..." if len(line) > 50 else line
                        print(f"JSON解析失败,跳过行: {line_preview}")
                        continue
        except FileNotFoundError:
            print(f"文件不存在: {history_file}")
            return []
        except Exception as e:
            print(f"查询失败: {e}")
            return []
        print(f"共查询到 {len(results)} 条匹配记录")
        return results




    def update(self,query, update, limit=1,history_file=None):
        """
        更新历史记录  文件: ./history/history.json
        内容:{"name": "张三6", "age": 24}  \n
             {"name": "张三7", "age": 25}  \n
             {"name": "张三8", "age": 26}  \n
        高效更新 - 直接定位并修改
        只更新前limit条匹配的记录
        Args:
            history_file: 历史记录文件路径
            query: 查询条件   {"name": "张三8", "age": 26}
            update: 更新内容   {"name": "张三8", "age": 18}
            limit: 最多更新的条数(默认1)
        Returns:
            实际更新的条数
        """
        history_file = self.history_file
        updated_count = 0
        if not os.path.exists(history_file):
            return 0
        with open(history_file, 'r+', encoding='utf-8') as f:
            while updated_count < limit:
                line_start = f.tell()
                line = f.readline()
                if not line:
                    break
                line = line.rstrip('\n')
                if not line:
                    continue
                try:
                    record = json.loads(line)
                    # 检查是否匹配
                    if all(record.get(k) == v for k, v in query.items()):
                        # 移动到行开始位置
                        f.seek(line_start)
                        # 更新记录
                        record.update(update)
                        # 写入更新后的行
                        updated_line = json.dumps(record, ensure_ascii=False) + '\n'
                        # 读取当前位置到文件末尾
                        f.readline()  # 跳过原始行
                        remaining = f.read()
                        # 重新定位并写入
                        f.seek(line_start)
                        f.write(updated_line)
                        if remaining:
                            f.write(remaining)
                        # 截断文件
                        f.truncate()
                        # 重置文件指针以便继续读取
                        f.seek(line_start + len(updated_line))
                        updated_count += 1
                        print(f"快速更新第{updated_count}条: {record}")
                        if updated_count >= limit:
                            print("~~ 达到更新限制,结束 ~~")
                            break
                except json.JSONDecodeError:
                    continue
        print(f"总共更新了 {updated_count} 条记录")
        return updated_count




    def delete(self, query={}, limit=1, history_file=None):
        """
        删除历史记录
        文件: ./history/history.json
        内容:{"name": "张三6", "age": 24}  \n
             {"name": "张三7", "age": 25}  \n
             {"name": "张三8", "age": 26}  \n
        Args:
            query: 删除条件 eg: {"name": "张三8", "age": 26}
            history_file: 历史记录文件路径
            limit: 删除的条数
        Returns:
            实际删除
        """
        history_file = self.history_file
        if not query and limit == 1:
            return 0
        deleted_count = 0
        temp_file_path = None
        try:
            # 检查文件是否存在
            if not os.path.exists(history_file):
                print(f"文件 {history_file} 不存在")
                return 0
            # 确保目录存在
            os.makedirs(os.path.dirname(history_file), exist_ok=True)
            # 在同目录下创建临时文件,避免跨磁盘问题
            temp_dir = os.path.dirname(os.path.abspath(history_file))
            if not temp_dir:  # 如果是当前目录
                temp_dir = os.path.dirname(os.path.abspath(__file__))
            # 创建临时文件
            temp_fd, temp_file_path = tempfile.mkstemp(dir=temp_dir, suffix='.tmp')
            # 读取原文件,过滤记录
            with open(history_file, 'r', encoding='utf-8') as src_file, \
                    open(temp_fd, 'w', encoding='utf-8') as temp_file:
                for line in src_file:
                    line = line.strip()
                    if not line:
                        temp_file.write('\n')
                        continue
                    try:
                        record = json.loads(line)
                        # 检查是否匹配查询条件
                        match = True
                        for key, value in query.items():
                            if key not in record or record[key] != value:
                                match = False
                                break
                        # 如果匹配且尚未达到删除限制,则跳过(删除)
                        if match and (limit == 0 or deleted_count < limit):
                            deleted_count += 1
                            continue
                        # 否则保留记录
                        temp_file.write(line + '\n')
                    except json.JSONDecodeError:
                        # 如果不是有效的JSON,保留原样
                        temp_file.write(line + '\n')
            # 关闭文件后移动
            # 先备份原文件
            backup_file = history_file + '.bak'
            if os.path.exists(history_file):
                shutil.copy2(history_file, backup_file)
            # 用临时文件替换原文件
            shutil.move(temp_file_path, history_file)
            # 删除备份文件(可选)
            if os.path.exists(backup_file):
                os.remove(backup_file)
            print(f"删除了 {deleted_count} 条记录")
            return deleted_count
        except Exception as e:
            print(f"删除过程中出现错误: {e}")
            # 如果发生错误,恢复备份
            if 'backup_file' in locals() and os.path.exists(backup_file):
                print("尝试恢复备份...")
                try:
                    shutil.move(backup_file, history_file)
                    print("备份已恢复")
                except:
                    print("恢复备份失败")
            # 清理临时文件
            if temp_file_path and os.path.exists(temp_file_path):
                try:
                    os.remove(temp_file_path)
                except:
                    pass
            return 0



def test_save_history():
    utils = HistoryUtils("./history/history1.json")
    for i in range(10):
        utils.save( {"name": "张三"+str(i), "age": i})
        utils.find_one( {"name": "张三"+str(i)})
        utils.update({"name": "张三"+str(i)}, {"age": 198},5)
        # utils.delete({"age": 198}, 5)
if __name__ == '__main__':
    utils = HistoryUtils("./history/history1.json")
    # test_save_history()
    rts = utils.find(query={"name": "张三9"}, limit=5)
    print(rts)

 




2. HistoryUtil.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @mail    : lshan523@163.com
# @Time    : 2026/1/15 10:46
# @Author  : Sea
# @File    : HistoryUtil.py
# @Purpose :
# @history :
# 小文件场景下,使用json文件存储历史记录,每次追加,后续可以查询
# ****************************
import os
import json
from typing import Dict, Any, List, Optional
import uuid
import tempfile
import shutil


# 历史记录文件
HISTORY_FILE="./history/history.json"

def save(history={},history_file=HISTORY_FILE):
    """
    保存历史记录 把文件写入 本地json文件,可以一直追加,后续可以查询
    :param history_file: 历史记录文件 eg:./history/history.json
    :param history: 历史记录 eg:{"name": "张三9", "age": 27}
    :return:
    """
    if not history:
        print("history is None")
        return
    # history 必须是 dict
    assert isinstance(history, dict), "history 必须是 dict"
    history["_id"]=str(uuid.uuid4()).replace("-", "")
    # 创建文件
    if not os.path.exists(history_file):
        print("history.json 不存在 创建文件 "+str(history_file))
        os.makedirs(os.path.dirname(history_file), exist_ok=True)
    with open(history_file, 'a', encoding='utf-8') as f:
        f.write(json.dumps(history, ensure_ascii=False))
        f.write("\n")
    print("保存历史记录")



def find_one(query: Dict[str, Any]={},history_file: str= HISTORY_FILE) -> List[Dict[str, Any]]:
    """
    查询历史记录
    :param history_file: 历史记录文件
    :param query: {"name": "张三1"}
    :return:
    """
    print("查询历史记录")
    result = find(query,1)
    if result:
        return result[0]
    return None


def find(query: Dict[str, Any] = {}, limit: Optional[int] = None,history_file: str = HISTORY_FILE) -> List[Dict[str, Any]]:
    """
    查询所有匹配的历史记录 limit 限制, 默认倒序
    Args:
        history_file: 历史记录文件路径
        query: 查询条件字典,如 {"name": "张三1"}
    Returns:
        所有匹配的记录列表,按时间倒序排列(最新的在前)
    """
    results = []
    try:
        with open(history_file, 'r', encoding='utf-8') as f:
            # 读取所有行并反转(最新的在前)
            lines = f.readlines()
            for line in reversed(lines):
                line = line.strip()
                if not line:  # 跳过空行
                    continue
                try:
                    history = json.loads(line)
                    # 条件匹配
                    if all(history.get(key) == value for key, value in query.items()):
                        results.append(history)
                        print(f"查询到匹配记录: {history}")
                        if limit and len(results) >= limit:
                            break
                except json.JSONDecodeError:
                    line_preview  =line[:50] + "..." if len(line) > 50 else line
                    print(f"JSON解析失败,跳过行: {line_preview}")
                    continue
    except FileNotFoundError:
        print(f"文件不存在: {history_file}")
        return []
    except Exception as e:
        print(f"查询失败: {e}")
        return []
    print(f"共查询到 {len(results)} 条匹配记录")
    return results




def update(query, update, limit=1,history_file=HISTORY_FILE):
    """
    更新历史记录  文件: ./history/history.json
    内容:{"name": "张三6", "age": 24}  \n
         {"name": "张三7", "age": 25}  \n
         {"name": "张三8", "age": 26}  \n
    高效更新 - 直接定位并修改
    只更新前limit条匹配的记录
    Args:
        history_file: 历史记录文件路径
        query: 查询条件   {"name": "张三8", "age": 26}
        update: 更新内容   {"name": "张三8", "age": 18}
        limit: 最多更新的条数(默认1)
    Returns:
        实际更新的条数
    """
    updated_count = 0
    if not os.path.exists(history_file):
        return 0
    with open(history_file, 'r+', encoding='utf-8') as f:
        while updated_count < limit:
            line_start = f.tell()
            line = f.readline()
            if not line:
                break
            line = line.rstrip('\n')
            if not line:
                continue
            try:
                record = json.loads(line)
                # 检查是否匹配
                if all(record.get(k) == v for k, v in query.items()):
                    # 移动到行开始位置
                    f.seek(line_start)
                    # 更新记录
                    record.update(update)
                    # 写入更新后的行
                    updated_line = json.dumps(record, ensure_ascii=False) + '\n'
                    # 读取当前位置到文件末尾
                    f.readline()  # 跳过原始行
                    remaining = f.read()
                    # 重新定位并写入
                    f.seek(line_start)
                    f.write(updated_line)
                    if remaining:
                        f.write(remaining)
                    # 截断文件
                    f.truncate()
                    # 重置文件指针以便继续读取
                    f.seek(line_start + len(updated_line))
                    updated_count += 1
                    print(f"快速更新第{updated_count}条: {record}")
                    if updated_count >= limit:
                        print("~~ 达到更新限制,结束 ~~")
                        break
            except json.JSONDecodeError:
                continue
    print(f"总共更新了 {updated_count} 条记录")
    return updated_count




def delete(query={}, limit=1, history_file="history/history.json"):
    """
    删除历史记录
    文件: ./history/history.json
    内容:{"name": "张三6", "age": 24}  \n
         {"name": "张三7", "age": 25}  \n
         {"name": "张三8", "age": 26}  \n
    Args:
        query: 删除条件 eg: {"name": "张三8", "age": 26}
        history_file: 历史记录文件路径
        limit: 删除的条数
    Returns:
        实际删除
    """
    if not query and limit == 1:
        return 0
    deleted_count = 0
    temp_file_path = None
    try:
        # 检查文件是否存在
        if not os.path.exists(history_file):
            print(f"文件 {history_file} 不存在")
            return 0
        # 确保目录存在
        os.makedirs(os.path.dirname(history_file), exist_ok=True)
        # 在同目录下创建临时文件,避免跨磁盘问题
        temp_dir = os.path.dirname(os.path.abspath(history_file))
        if not temp_dir:  # 如果是当前目录
            temp_dir = os.path.dirname(os.path.abspath(__file__))
        # 创建临时文件
        temp_fd, temp_file_path = tempfile.mkstemp(dir=temp_dir, suffix='.tmp')
        # 读取原文件,过滤记录
        with open(history_file, 'r', encoding='utf-8') as src_file, \
                open(temp_fd, 'w', encoding='utf-8') as temp_file:
            for line in src_file:
                line = line.strip()
                if not line:
                    temp_file.write('\n')
                    continue
                try:
                    record = json.loads(line)
                    # 检查是否匹配查询条件
                    match = True
                    for key, value in query.items():
                        if key not in record or record[key] != value:
                            match = False
                            break
                    # 如果匹配且尚未达到删除限制,则跳过(删除)
                    if match and (limit == 0 or deleted_count < limit):
                        deleted_count += 1
                        continue
                    # 否则保留记录
                    temp_file.write(line + '\n')
                except json.JSONDecodeError:
                    # 如果不是有效的JSON,保留原样
                    temp_file.write(line + '\n')
        # 关闭文件后移动
        # 先备份原文件
        backup_file = history_file + '.bak'
        if os.path.exists(history_file):
            shutil.copy2(history_file, backup_file)
        # 用临时文件替换原文件
        shutil.move(temp_file_path, history_file)
        # 删除备份文件(可选)
        if os.path.exists(backup_file):
            os.remove(backup_file)
        print(f"删除了 {deleted_count} 条记录")
        return deleted_count
    except Exception as e:
        print(f"删除过程中出现错误: {e}")
        # 如果发生错误,恢复备份
        if 'backup_file' in locals() and os.path.exists(backup_file):
            print("尝试恢复备份...")
            try:
                shutil.move(backup_file, history_file)
                print("备份已恢复")
            except:
                print("恢复备份失败")
        # 清理临时文件
        if temp_file_path and os.path.exists(temp_file_path):
            try:
                os.remove(temp_file_path)
            except:
                pass
        return 0



def test_save_history():
    for i in range(10):
        save( {"name": "张三"+str(i), "age": i})
        find_one( {"name": "张三"+str(i)})
        update({"name": "张三"+str(i)}, {"age": 198},5)


if __name__ == '__main__':
    # test_save_history()
    delete({"age": 198}, 5)

 

                       

  

 
posted on 2026-01-16 12:16  lshan  阅读(5)  评论(0)    收藏  举报