├── AI ├── task.prompt ├── 小易.role └── README.md ├── image ├── Ali_pay.jpg ├── Nginx.png ├── Swagger.png ├── WeChat.jpg ├── Email_get.png ├── Email_sql.png ├── Email_info.png ├── Excel_info.png ├── Nginx_info.png ├── Swagger_info.png ├── cyberkorla.png ├── EasyAccountsFunction.png └── Feature │ ├── Feature-About.png │ ├── Feature-Action.png │ ├── Feature-Board.png │ ├── Feature-Flow.png │ ├── Feature-Login.png │ ├── Feature-Screen.png │ ├── Feature-Flow-AI.png │ ├── Feature-Flow-Add.png │ ├── Feature-Type-Add.png │ ├── Feature-Account-Add.png │ ├── Feature-Flow-Insert.png │ ├── Feature-Screen-More.png │ ├── Feature-Type-List.png │ ├── Feature-Analysis-List.png │ ├── Feature-Analysis-Type.png │ ├── Feature-Flow-Add-Img.png │ ├── Feature-Flow-Template.png │ ├── Feature-Template-List.png │ ├── Feature-Template-add.png │ ├── Feature-Analysis-Chart.png │ └── Feature-Board-Accounts.png ├── update-docker.sh ├── update-docker-chinese.sh ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── .gitignore ├── LICENSE ├── WebHook ├── webhook.py ├── README.md └── webhook-email.py ├── docker-compose.yml ├── docker-compose-chinese.yml ├── Database └── base_sql │ └── yd_jz_base.sql └── README.md /AI/task.prompt: -------------------------------------------------------------------------------- 1 | # 智能体任务指导 2 | 3 | ## 特殊指导 4 | 5 | 6 | -------------------------------------------------------------------------------- /image/Ali_pay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Ali_pay.jpg -------------------------------------------------------------------------------- /image/Nginx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Nginx.png -------------------------------------------------------------------------------- /image/Swagger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Swagger.png -------------------------------------------------------------------------------- /image/WeChat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/WeChat.jpg -------------------------------------------------------------------------------- /image/Email_get.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Email_get.png -------------------------------------------------------------------------------- /image/Email_sql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Email_sql.png -------------------------------------------------------------------------------- /image/Email_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Email_info.png -------------------------------------------------------------------------------- /image/Excel_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Excel_info.png -------------------------------------------------------------------------------- /image/Nginx_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Nginx_info.png -------------------------------------------------------------------------------- /image/Swagger_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Swagger_info.png -------------------------------------------------------------------------------- /image/cyberkorla.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/cyberkorla.png -------------------------------------------------------------------------------- /image/EasyAccountsFunction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/EasyAccountsFunction.png -------------------------------------------------------------------------------- /image/Feature/Feature-About.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Feature/Feature-About.png -------------------------------------------------------------------------------- /image/Feature/Feature-Action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Feature/Feature-Action.png -------------------------------------------------------------------------------- /image/Feature/Feature-Board.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Feature/Feature-Board.png -------------------------------------------------------------------------------- /image/Feature/Feature-Flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Feature/Feature-Flow.png -------------------------------------------------------------------------------- /image/Feature/Feature-Login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Feature/Feature-Login.png -------------------------------------------------------------------------------- /image/Feature/Feature-Screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Feature/Feature-Screen.png -------------------------------------------------------------------------------- /image/Feature/Feature-Flow-AI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Feature/Feature-Flow-AI.png -------------------------------------------------------------------------------- /image/Feature/Feature-Flow-Add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Feature/Feature-Flow-Add.png -------------------------------------------------------------------------------- /image/Feature/Feature-Type-Add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Feature/Feature-Type-Add.png -------------------------------------------------------------------------------- /image/Feature/Feature-Account-Add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Feature/Feature-Account-Add.png -------------------------------------------------------------------------------- /image/Feature/Feature-Flow-Insert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Feature/Feature-Flow-Insert.png -------------------------------------------------------------------------------- /image/Feature/Feature-Screen-More.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Feature/Feature-Screen-More.png -------------------------------------------------------------------------------- /image/Feature/Feature-Type-List.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Feature/Feature-Type-List.png -------------------------------------------------------------------------------- /image/Feature/Feature-Analysis-List.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Feature/Feature-Analysis-List.png -------------------------------------------------------------------------------- /image/Feature/Feature-Analysis-Type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Feature/Feature-Analysis-Type.png -------------------------------------------------------------------------------- /image/Feature/Feature-Flow-Add-Img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Feature/Feature-Flow-Add-Img.png -------------------------------------------------------------------------------- /image/Feature/Feature-Flow-Template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Feature/Feature-Flow-Template.png -------------------------------------------------------------------------------- /image/Feature/Feature-Template-List.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Feature/Feature-Template-List.png -------------------------------------------------------------------------------- /image/Feature/Feature-Template-add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Feature/Feature-Template-add.png -------------------------------------------------------------------------------- /image/Feature/Feature-Analysis-Chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Feature/Feature-Analysis-Chart.png -------------------------------------------------------------------------------- /image/Feature/Feature-Board-Accounts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QingHeYang/EasyAccounts/HEAD/image/Feature/Feature-Board-Accounts.png -------------------------------------------------------------------------------- /AI/小易.role: -------------------------------------------------------------------------------- 1 | role: 一个家庭小财务官 2 | cos_role: 无 3 | name: 小易 4 | character: 一个活泼灵动,古灵精怪的小女生,喜欢精打细算,小财迷,非常讨厌支出,最爱收钱,喜欢内部转账,左手倒右手 5 | user_role: 家庭主人 6 | user_role_description: 家庭财务账单的记录者,发生者 7 | user_scene: 温馨的家,每个月都有账单出现 8 | location: 9 | -------------------------------------------------------------------------------- /update-docker.sh: -------------------------------------------------------------------------------- 1 | docker pull mysql:8.0.31 && 2 | docker pull 775495797/easyaccounts-nginx:latest && 3 | docker pull 775495797/easyaccounts-server:latest && 4 | docker pull 775495797/easyaccounts-webhook:latest && 5 | docker pull 775495797/easyaccounts-ai:latest 6 | -------------------------------------------------------------------------------- /update-docker-chinese.sh: -------------------------------------------------------------------------------- 1 | docker pull registry.cn-beijing.aliyuncs.com/easy_accounts/mysql:8.0.31 && 2 | docker pull registry.cn-beijing.aliyuncs.com/easy_accounts/easyaccounts-nginx:latest && 3 | docker pull registry.cn-beijing.aliyuncs.com/easy_accounts/easyaccounts-server:latest && 4 | docker pull registry.cn-beijing.aliyuncs.com/easy_accounts/easyaccounts-webhook:latest && 5 | docker pull registry.cn-beijing.aliyuncs.com/easy_accounts/easyaccounts-ai:latest -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: 提出一个新需求或功能改进建议 4 | title: "[Feature Request]: 简要描述需求" 5 | labels: enhancement 6 | --- 7 | 8 | **需求背景** 9 | 请简要描述您提出此需求的原因,例如当前遇到的问题或希望解决的痛点。 10 | 11 | **功能描述** 12 | 请详细描述您希望实现的功能: 13 | 14 | - **功能细节**: 15 | - 具体的功能点或操作流程 16 | - 界面或交互上的需求(如果有) 17 | 18 | **预期效果** 19 | 请描述您期望此功能带来的好处或效果。 20 | 21 | **可选的参考方案** 22 | 如果您有任何参考的实现方案、竞品功能或设计灵感,请在此提供相关信息。 23 | 24 | **其他信息** 25 | 如有其他补充内容或上下文,请在此提供。 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | WebHook/__pycache__ 2 | *.log 3 | WebHook/webhook-tools.py 4 | 5 | *.jar 6 | *.xls 7 | Resource/sql/* 8 | Resource/images/* 9 | Resource/excel/**/*.xls 10 | Resource/excel/**/*.xlsx 11 | 12 | Database/data/* 13 | Database/init/* 14 | Server/auth/* 15 | Server/logs/* 16 | 17 | release_start.sh 18 | develop_start.sh 19 | docker-compose-release.yml 20 | CLAUDE.md 21 | .claude/* 22 | 23 | # AI相关的个人配置文件 24 | AI/database/*.db 25 | AI/logs/* 26 | AI/me.prompt 27 | AI/小易-self.role 28 | docs/video_scripts/* -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: 提交 Bug 或问题报告 4 | title: "[Bug]: 简要描述问题" 5 | labels: bug 6 | --- 7 | 8 | **描述问题** 9 | 请简要描述您遇到的问题。 10 | 11 | **系统信息** 12 | 请提供以下详细信息,以便我们更好地定位和解决问题: 13 | 14 | - **部署系统**: (例如:Ubuntu, CentOS, Windows, 群晖, Unraid, etc.) 15 | - **硬件架构**: (例如:x86, ARM) 16 | - **用户权限**: (例如:root, 非 root) 17 | - **Docker 版本**: (例如:20.10.7) 18 | 19 | **复现步骤** 20 | 1. 描述步骤... 21 | 2. 出现问题... 22 | 23 | **期望的结果** 24 | 简要描述您期望的结果。 25 | 26 | **日志/截图** 27 | 请提供日志文件和截图以帮助分析问题: 28 | 29 | - **服务端日志**: 请在 `Serve/logs` 文件夹中查找并附上相关日志。 30 | - **Webhook 日志**: 请在 `WebHook/hook.log` 文件中查找并附上相关日志。 31 | 32 | 如果有错误日志或截图,请附上,以帮助我们更好地理解问题。 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Mercy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /WebHook/webhook.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, File, Form, UploadFile 2 | from pydantic import BaseModel 3 | import logging 4 | import requests 5 | 6 | app = FastAPI() 7 | 8 | # 配置日志 9 | logging.basicConfig(filename='hook.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') 10 | logger = logging.getLogger(__name__) 11 | 12 | # 定义数据模型 13 | class WebhookData(BaseModel): 14 | file_name: str 15 | file_type: str 16 | 17 | @app.post("/webhook") 18 | async def handle_webhook(file: UploadFile = File(...), file_name: str = Form(...), file_type: str = Form(...)): 19 | logger.info(f"Received request with file_name: {file_name} and file_type: {file_type}") 20 | try: 21 | file_content = await file.read() 22 | if file_type == "sql": 23 | result = await handleMySqlBackUp(file_content, file_name) 24 | elif file_type == "analysis_excel": 25 | result = await handleMonthExcelBackUp(file_content, file_name) 26 | elif file_type == "month_excel": 27 | result = await handleMonthExcelBackUp(file_content, file_name) 28 | elif file_type == "screen_excel": 29 | result = await handleScreenExcelBackUp(file_content, file_name) 30 | 31 | logger.info(result) 32 | 33 | return {"status": "ok", "result": result} 34 | except Exception as e: 35 | logger.error(f"An error occurred: {str(e)}") 36 | return {"status": "error", "message": str(e)} 37 | 38 | # 处理备份数据库的逻辑 39 | async def handleMySqlBackUp(file_content: bytes, file_name: str): 40 | logger.info(f"Handling MySQL backup with file_name: {file_name}") 41 | return f"Handled MySQL backup for file_name: {file_name}" 42 | 43 | # 处理备份月度账单的逻辑 44 | async def handleMonthExcelBackUp(file_content: bytes, file_name: str): 45 | logger.info(f"Handling monthly Excel backup with file_name: {file_name}") 46 | return f"Handled monthly Excel backup for file_name: {file_name}" 47 | 48 | # 处理备份筛选账单的逻辑 49 | async def handleScreenExcelBackUp(file_content: bytes, file_name: str): 50 | logger.info(f"Handling screen Excel backup with file_name: {file_name}") 51 | return f"Handled screen Excel backup for file_name: {file_name}" 52 | 53 | # 工具方法: 将文件保存到本地 54 | async def save_file_locally(file: UploadFile, file_path: str): 55 | try: 56 | async with aiofiles.open(file_path, 'wb') as out_file: 57 | content = await file.read() 58 | await out_file.write(content) 59 | logger.info(f"File {file.filename} saved to {file_path}") 60 | except Exception as e: 61 | logger.error(f"Failed to save file {file.filename} to {file_path}: {str(e)}") 62 | 63 | -------------------------------------------------------------------------------- /AI/README.md: -------------------------------------------------------------------------------- 1 | # AI+ 功能 2 | 3 | ## 简介 4 | EasyAccounts 内置了一个AI智能助手。 5 | 这个小助手叫"小易",是个精打细算的小财迷,会用有趣的方式跟你互动。 6 | 7 | ### 配置文件说明 8 | 9 | #### task.prompt - 任务指导文件 10 | 告诉AI你的特殊情况,让它更懂你。 11 | 12 | 直接在 `## 特殊指导` 下方写你的家庭信息就行: 13 | - 家庭成员名字(孩子叫啥、养了什么宠物) 14 | - 平时的记账习惯 15 | - 经常买什么东西 16 | 17 | #### 小易.role - AI性格设定 18 | 这个文件决定了小易的性格,每个字段的意思: 19 | 20 | ```yaml 21 | role: 一个家庭小财务官 # AI扮演什么角色 22 | cos_role: 无 # 角色形象(一般不用改) 23 | name: 小易 # AI叫什么名字 24 | character: 活泼灵动,古灵精怪... # AI的性格,改这个可以调整说话风格 25 | user_role: 家庭主人 # 你是什么角色 26 | user_role_description: ... # 对你角色的描述 27 | user_scene: 温馨的家 # 场景设定 28 | location: # 地理位置(可以不填) 29 | ``` 30 | 31 | ### 使用方法 32 | 1. 直接改上面两个文件,写入你的信息 33 | 2. **改完后需要重启AI服务才会生效** 34 | 3. 重启命令:`docker restart easy_accounts_ai`(如果你用的是AI功能的话) 35 | 36 | ### 注意事项 37 | - 别写敏感信息进去(银行卡号、密码这些) 38 | - 想恢复默认就把改的内容删了就行 39 | 40 | --- 41 | (上述内容由AI生成,下面是开发者手写的) 42 | ## AI功能 43 | ### 开发背景 44 | 2.5.0版本更新了这个ai相关的功能 45 | 不是我追时髦,其实我本职工作也是干这个差不多的东西 46 | 我拿我在公司的成果的的一个中间版本,改了改,完成的这个AI+的功能 47 | 48 | ### 功能 49 | 50 | 该智能体内置了8个工具,可以直接操作你的账本数据: 51 | 52 | #### 1. accounts - 账户查询 53 | 查询你有多少资金账户,返回账户名称、ID和余额。 54 | 比如你问"我微信还有多少钱",AI会调这个工具。 55 | 56 | #### 2. types - 分类查询 57 | 获取所有的账单分类(标签)信息,包含分类ID、名称、父子关系。 58 | 当你问"我都有哪些支出分类"时会用到。 59 | 60 | #### 3. current_date - 获取时间 61 | 获取当前服务器时间,返回yyyy-MM-dd格式。 62 | 处理"这个月"、"上个季度"这种时间相关问题时会用。 63 | 64 | #### 4. year_statistics - 年度统计 65 | 查询某年每个月的收入支出盈余数据。 66 | 比如你问"2024年哪个月花钱最多",就会调用这个。 67 | 68 | #### 5. flows - 流水查询(最强大的工具) 69 | 根据各种条件查询流水记录,支持: 70 | - 按账户查询(某个微信/支付宝的流水) 71 | - 按时间查询(上个月、某个时间段) 72 | - 按分类查询(所有餐饮支出) 73 | - 按备注关键字查询("社保"、"工资"等) 74 | - 组合查询(这个月微信的餐饮支出) 75 | 76 | 返回具体流水列表和收支汇总。 77 | 78 | #### 6. add_flow - 记账 79 | 添加一条流水记录,可以记录: 80 | - 收入(工资到账、红包等) 81 | - 支出(买东西、交房租等) 82 | - 内部转账(微信转支付宝等) 83 | 84 | 比如你说"记一笔,今天吃饭花了50块",AI就会帮你记上。 85 | 86 | #### 7. update_flow - 修改流水 87 | 更新已有的流水记录。 88 | 当你发现记错了,可以说"把昨天那笔吃饭改成60块"。 89 | 90 | #### 8. make_excel - 生成报表 91 | 根据查询条件生成Excel文件,参数和flows一样,只是输出变成Excel。 92 | 比如"导出这个月的所有支出",就会生成一个Excel文件。 93 | 94 | ### 使用示例 95 | 96 | 你可以这样问小易: 97 | - "我微信还有多少钱?" 98 | - "这个月吃饭花了多少?" 99 | - "上个月收入多少?" 100 | - "什么时候给孩子交的学费?" 101 | - "记一笔,今天加油200" 102 | - "导出今年所有的支出明细" 103 | 104 | 小易会自动调用相应的工具,帮你查询或记录。 105 | 106 | ### 技术细节 107 | 这些工具基于Tool call实现,AI会根据你的问题自动选择合适的工具组合。 108 | 比如你问"这个月微信的餐饮支出",AI会: 109 | 1. 先调用current_date获取当前日期 110 | 2. 再调用accounts获取微信账户ID 111 | 3. 然后调用types获取餐饮分类ID 112 | 4. 最后调用flows查询具体流水 113 | 114 | 整个过程自动完成,你只需要正常对话就行。 115 | 116 | ## 开源 117 | 目前代码开源到2.4.0版本 118 | ai相关的2.5.0版本,可能要等很久之后了 119 | 120 | 原本源码会落后正式版本2-3个小版本的 121 | 我这个人本来就懒散,整理代码然后开源,写文档,太耗时间了 122 | 123 | 不过如果开发者们愿意开发 124 | 我给的建议是,进入到ai的容器内部 125 | 将想修改的文件映射出来,然后修改 126 | 不建议改动,项目我写的比较垃圾,外加有大量ai生成的代码 127 | 很容易变屎山 128 | 哪天我更新了,可能会和你写的代码冲突 129 | 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /WebHook/README.md: -------------------------------------------------------------------------------- 1 | # WebHook功能介绍 2 | WebHook是一种通过自定义HTTP回调来实现的事件通知机制。当事件发生时,WebHook会向预先设定的URL发送HTTP请求,以通知事件的发生。 3 | 4 | ## Compose内容 5 | ```yaml 6 | webhook: 7 | image: registry.cn-beijing.aliyuncs.com/easy_accounts/easyaccounts-webhook:latest #此处使用阿里云镜像 8 | container_name: easy_accounts_webhook 9 | restart: always 10 | ports: 11 | - "10671:8083" 12 | volumes: 13 | - ./WebHook:/app/ 14 | #- ./WebHook/webhook.py:/app/webhook.py #如果要自行处理文件,则取消这行注释,映射该文件,则下方的环境变量无效 15 | - ./WebHook/webhook-email.py:/app/webhook.py #如需修改发送邮件服务,请解开此行注释 16 | environment: 17 | - LOG_FILE=/app/hook.log 18 | - SEND_SQL_BACKUP=True # 是否发送SQL备份文件,默认True 19 | - SEND_EXCEL=True # 是否发送Excel文件,默认True 20 | 21 | # -------------------以下为发送邮件服务的环境变量------------------- 22 | - SMTP_SERVER= # SMTP服务器地址 23 | - SMTP_PORT= # SMTP端口 24 | - SMTP_MAIL= # 发件人邮箱,一般来说是SMTP账号,例如自己的QQ邮箱 25 | - SMTP_PASSWORD= # SMTP密码,一般来说是SMTP账号的授权码,需要自己去自己的邮箱中找到设置 26 | - SMTP_TO_LIST= # 收件人邮箱列表,用逗号分隔 27 | 28 | networks: 29 | - easy_accounts_net 30 | ``` 31 | ## WebHook使用说明 32 | 1. WebHook功能需要在EasyAccounts项目中启动,启动后会监听`10671`端口 33 | 2. WebHook 有两个具体实现的功能类,分别是: 34 | - `webhook.py` 35 | - `webhook-email.py` 36 | 37 | ### webhook.py 38 | 此类的主要功能是提供一个示例,用于展示如何使用WebHook功能,提供了一个保存文件的工具类,并未实现具体发送功能。 39 | 40 | ### webhook-email.py 41 | 默认使用的是该文件进行的发送邮件 42 | 此类是发送邮件的具体实现,使用了`email`模块,需要配置邮件服务器的相关信息。 43 | 在 docker-compose.yml 中配置了邮件服务器的相关信息,如下: 44 | ```yaml 45 | environment: 46 | - LOG_FILE=/app/hook.log 47 | - SEND_SQL_BACKUP=True # 是否发送SQL备份文件,默认True 48 | - SEND_EXCEL=True # 是否发送Excel文件,默认True 49 | 50 | # -------------------以下为发送邮件服务的环境变量------------------- 51 | - SMTP_SERVER= # SMTP服务器地址 52 | - SMTP_PORT= # SMTP端口 53 | - SMTP_MAIL= # 发件人邮箱,一般来说是SMTP账号,例如自己的QQ邮箱 54 | - SMTP_PASSWORD= # SMTP密码,一般来说是SMTP账号的授权码,需要自己去自己的邮箱中找到设置 55 | - SMTP_TO_LIST= # 收件人邮箱列表,用逗号分隔 56 | ``` 57 | 58 | 发送邮件的协议为SMTP,暂不支持别的协议,需要配置SMTP服务器的相关信息,不同的邮件供应商有不同的SMTP服务器地址和端口,例如QQ邮箱的SMTP服务器地址为`smtp.qq.com`,端口为`587`。 59 | 如果想使用不同的供应商,需要自行去自己的邮箱找对应的配置信息。 60 | 例如QQ邮箱: 61 |  62 | Outlook邮箱: 63 |  64 | 65 | 目前测试通过的邮件服务商有: 66 | - QQ邮箱 67 | - outlook邮箱 68 | 69 | ## 使用方法 70 | 1. 在`docker-compose.yml`或者`docker-compose-chinese.yml`中配置WebHook服务 71 | 2. 启动EasyAccounts项目 72 | 73 | ## 自定义开发 74 | 解开`docker-compose.yml`中的注释,将`webhook.py`或者`webhook-email.py`映射到容器中,即可自定义开发。 75 | ```yaml 76 | volumes: 77 | - ./WebHook:/app/ 78 | #- ./WebHook/webhook.py:/app/webhook.py #如果要自行处理文件,则取消这行注释,映射该文件,则下方的环境变量无效 79 | - ./WebHook/webhook-email.py:/app/webhook.py #如需修改发送邮件服务,请解开此行注释 80 | ``` 81 | 开发注意事项: 82 | Docker容器内的环境包只有如下内容: 83 | ``` 84 | fastapi==0.111.0 85 | pydantic==2.7.1 86 | Requests==2.32.2 87 | ``` 88 | 89 | ## 日志查看 90 | 日志文件默认保存在`WebHook/hook.log`中。 91 | 92 | ## 注意事项 93 | 邮箱端口请使用TLS加密端口,不支持SSL加密。 94 | 日志文件默认保存在`/app/hook.log`中,可以通过`LOG_FILE`环境变量进行修改。 -------------------------------------------------------------------------------- /WebHook/webhook-email.py: -------------------------------------------------------------------------------- 1 | import os 2 | import smtplib 3 | import logging 4 | from email.mime.multipart import MIMEMultipart 5 | from email.mime.text import MIMEText 6 | from email.mime.base import MIMEBase 7 | from email import encoders 8 | from fastapi import FastAPI, File, Form, UploadFile 9 | from pydantic import BaseModel 10 | 11 | # 读取环境变量 12 | SMTP_SERVER = os.getenv('SMTP_SERVER', '') 13 | SMTP_PORT = os.getenv('SMTP_PORT', '') 14 | SMTP_FROM = os.getenv('SMTP_MAIL', '') 15 | SMTP_PASSWORD = os.getenv('SMTP_PASSWORD', '') 16 | SMTP_TO_LIST = os.getenv('SMTP_TO_LIST', '') 17 | SEND_SQL_BACKUP = os.getenv('SEND_SQL_BACKUP', 'True') 18 | SEND_EXCEL = os.getenv('SEND_EXCEL', 'True') 19 | 20 | # 配置日志 21 | logging.basicConfig(filename='hook.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') 22 | logger = logging.getLogger(__name__) 23 | 24 | app = FastAPI() 25 | 26 | @app.post("/webhook") 27 | async def handle_webhook(file: UploadFile = File(...), file_name: str = Form(...), file_type: str = Form(...)): 28 | if(file_type == "sql_backup" and SEND_SQL_BACKUP == 'False'): 29 | return {"status": "ok", "result": "SQL 备份邮件发送功能已关闭"} 30 | if(file_type == "month_excel" and SEND_EXCEL == 'False'): 31 | return {"status": "ok", "result": "月度 Excel 发送功能已关闭"} 32 | if(file_type == "screen_excel" and SEND_EXCEL == 'False'): 33 | return {"status": "ok", "result": "筛选账单 Excel 发送功能已关闭"} 34 | if(file_type == "analysis_excel" and SEND_EXCEL == 'False'): 35 | return {"status": "ok", "result": "财务分析 Excel 发送功能已关闭"} 36 | if not await check_smtp_info(): 37 | return {"status": "error", "message": "SMTP 信息不完整,请设置环境变量"} 38 | else: 39 | logger.info(f"收到文件: {file_name} & 文件类型为: {file_type}") 40 | try: 41 | file_content = await file.read() 42 | result = await send_email_with_attachment(file_content,file_name, file_type) 43 | logger.info(result) 44 | return {"status": "ok", "result": result} 45 | except Exception as e: 46 | logger.error(f"发生错误: {str(e)}") 47 | return {"status": "ok", "result": "请检查是否收到邮件"} 48 | 49 | 50 | 51 | async def send_email_with_attachment(file_content: bytes, file_name: str, file_type: str): 52 | logger.info(f"发送邮件名称为: {file_name} 收件人列表为: {SMTP_TO_LIST}") 53 | msg = MIMEMultipart() 54 | 55 | if file_type == "sql" and SEND_SQL_BACKUP=='True': 56 | msg['Subject'] = 'SQL 备份日志,备份文件名:' + file_name 57 | body = MIMEText(f'账单SQL备份({file_name})。记得保存好,以防不时之需', 'plain') 58 | elif file_type == "month_excel" and SEND_EXCEL=='True': 59 | msg['Subject'] = file_name+ ' 月度 Excel 已生成,请查收' 60 | body = MIMEText(f'月度账单 Excel({file_name}) 已生成,请查收', 'plain') 61 | elif file_type == "analysis_excel" and SEND_EXCEL=='True': 62 | msg['Subject'] = file_name+ ' 财务分析 Excel 已生成,请查收' 63 | body = MIMEText(f'财务分析 Excel({file_name}) 已生成,请查收', 'plain') 64 | elif file_type == "screen_excel" and SEND_EXCEL=='True': 65 | msg['Subject'] = file_name+ ' 筛选账单 Excel 已生成,请查收' 66 | body = MIMEText(f'筛选账单 Excel({file_name}) 已生成,请查收', 'plain') 67 | 68 | if body is None: 69 | logger.info("用户选择不发送邮件") 70 | return "用户选择不发送邮件" 71 | else: 72 | recipient_list = [email.strip() for email in SMTP_TO_LIST.split(",")] 73 | # 设置发件人、收件人 74 | msg['From'] = SMTP_FROM 75 | msg['To'] = ", ".join(recipient_list) 76 | msg.attach(body) 77 | # 邮件附件处理 78 | file_name = file_name.replace(' ', '') 79 | part = MIMEBase('application', 'octet-stream') 80 | part.set_payload(file_content) 81 | encoders.encode_base64(part) 82 | part.add_header('Content-Disposition', 'attachment', filename=file_name) 83 | part.add_header('Content-Type', 'application/octet-stream', name=file_name) 84 | msg.attach(part) 85 | 86 | # 连接到 SMTP 服务器并发送邮件 87 | with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server: 88 | server.ehlo() # 与服务器打招呼 89 | server.starttls() # 启用TLS 90 | server.ehlo() # 重新与服务器打招呼 91 | server.login(SMTP_FROM, SMTP_PASSWORD) 92 | server.sendmail(SMTP_FROM, recipient_list, msg.as_string()) 93 | logger.info(f"文件 {file_name} 已发送到 {recipient_list}") 94 | return f"文件 {file_name} 已发送到 {recipient_list},发送结果:OK" 95 | 96 | 97 | async def check_smtp_info(): 98 | logger.info("检查 SMTP 信息,打印环境变量") 99 | logger.info(f"SMTP_SERVER: {SMTP_SERVER}\nSMTP_PORT: {SMTP_PORT}\nSMTP_FROM: {SMTP_FROM}\nSMTP_PASSWORD: {len(SMTP_PASSWORD)}位\nSMTP_TO: {SMTP_TO_LIST}\n SEND_SQL_BACKUP: {SEND_SQL_BACKUP}\nSEND_EXCEL: {SEND_EXCEL}") 100 | if not SMTP_SERVER or not SMTP_PORT or not SMTP_FROM or not SMTP_PASSWORD or not SMTP_TO_LIST: 101 | logger.error("SMTP 信息缺失,请设置环境变量") 102 | return False 103 | return True -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | 5 | #数据库端,请勿分开部署Mysql与服务端,内部有网桥链接,分开部署会导致服务端无法连接数据库 6 | #请勿修改"db"服务的名称,否则会导致服务端无法连接数据库 7 | db: 8 | restart: always 9 | container_name: easy_accounts_db 10 | image: mysql:8.0.31 11 | ports: 12 | - "10668:3306" 13 | environment: 14 | TZ: Asia/Shanghai 15 | MYSQL_ROOT_PASSWORD: easy_accounts #数据库root密码,自行修改,修改完需要修改Server中的数据库密码 16 | MYSQL_DATABASE: yd_jz #数据库名称,请勿修改 17 | volumes: 18 | - ./Database/data:/var/lib/mysql 19 | - ./Database/init:/docker-entrypoint-initdb.d 20 | command: 21 | --default-authentication-plugin=mysql_native_password 22 | --character-set-server=utf8mb4 23 | --collation-server=utf8mb4_general_ci 24 | --explicit_defaults_for_timestamp=true 25 | --lower_case_table_names=1 26 | networks: 27 | - easy_accounts_net 28 | 29 | nginx: 30 | restart: always 31 | container_name: easy_accounts_nginx 32 | image: 775495797/easyaccounts-nginx:latest 33 | ports: 34 | - "10669:80" 35 | volumes: 36 | #- ./Web/dist:/usr/share/nginx/html #如若需要自行修改前端,请将前端放置在Web/dist目录下,并解开此行注释 37 | #- ./Web/nginx/default.conf:/etc/nginx/conf.d/default.conf #如若需要自行修改nginx配置,请将配置文件放置在Web/nginx/default.conf目录下,并解开此行注释 38 | - ./Resource:/usr/share/nginx/html/resources #资源文件目录,此文件夹提供一个下载功能 39 | depends_on: 40 | - server 41 | networks: 42 | - easy_accounts_net 43 | 44 | server: 45 | restart: always 46 | container_name: easy_accounts_server 47 | image: 775495797/easyaccounts-server:latest 48 | environment: 49 | #-------------------以下为服务端数据库环境变量,如果不需要外部部署数据库,请勿修改------------------- 50 | #- MYSQL_HOST= # 解开注释则需要填写数据库地址,否则默认为db 51 | #- MYSQL_PORT= # 解开注释则需要填写数据库端口号,否则默认为3306 52 | #- MYSQL_USERNAME= # 解开注释则需要填写数据库用户名,否则默认为root 53 | 54 | - DB_PASSWORD=easy_accounts # 数据库密码,默认easy_accounts,如果使用外部数据库,请修改此处 55 | - SQL_BACKUP_TIME=00 00 22 * * ? # SQL备份时间 corn表达式,默认每天晚上10点 56 | 57 | #-------------------以下为服务端登录功能环境变量------------------- 58 | - ENABLE_LOGIN=true # 是否启用登录功能,默认true 59 | - EXPIRED_TIME=30 # 登录过期时间,默认30分钟,单位分钟 60 | volumes: 61 | - ./Resource/sql:/Ledger/backup #数据库备份文件目录 62 | - ./Resource/excel/month:/Ledger/excel/month #excel 生成月度账单文件目录 63 | - ./Resource/excel/screen:/Ledger/excel/screen #excel 生成筛选账单文件目录 64 | - ./Resource/excel/month:/Ledger/excel/analysis #excel 生成筛选账单文件目录 65 | - ./Resource/images:/Ledger/images #图片上传文件目录 66 | - ./Server/logs:/Ledger/logs #日志文件目录 67 | - ./Server/auth:/Ledger/auth #登录认证文件目录,需要映射,否则重启后会丢失用户名密码 68 | #重置用户名密码请删除auth文件夹下的secret.key文件 69 | #- ./YD_JZ-SNAPSHOT.jar:/Ledger/app/YD_JZ-SNAPSHOT.jar #服务端jar包,如若需要自行修改,请将jar包放置在Server/app目录下,并修改此行 70 | ports: 71 | - 10670:8081 #左侧映射出去的端口号为服务端口号 默认10670 72 | depends_on: 73 | - db 74 | networks: 75 | - easy_accounts_net 76 | 77 | webhook: 78 | image: 775495797/easyaccounts-webhook:latest 79 | container_name: easy_accounts_webhook 80 | restart: always 81 | ports: 82 | - "10671:8083" 83 | volumes: 84 | - ./WebHook:/app/ 85 | #- ./WebHook/webhook.py:/app/webhook.py #如果要自行处理文件,则映射该文件 86 | - ./WebHook/webhook-email.py:/app/webhook.py #如需发送邮件服务,请解开此行注释,并填写下方的环境变量 87 | environment: 88 | - TZ=Asia/Shanghai 89 | - LOG_FILE=/app/hook.log 90 | - SEND_SQL_BACKUP=True # 是否发送SQL备份文件,默认True 91 | - SEND_EXCEL=True # 是否发送Excel文件,默认True 92 | 93 | # -------------------以下为发送邮件服务的环境变量------------------- 94 | - SMTP_SERVER= # SMTP服务器地址 95 | - SMTP_PORT= # SMTP端口 96 | - SMTP_MAIL= # 发件人邮箱,一般来说是SMTP账号,例如自己的QQ邮箱 97 | - SMTP_PASSWORD= # SMTP密码,一般来说是SMTP账号的授权码,需要自己去自己的邮箱中找到设置 98 | - SMTP_TO_LIST= # 收件人邮箱列表,用逗号分隔 99 | 100 | networks: 101 | - easy_accounts_net 102 | 103 | # AI智能助手服务(可选) 104 | # 如果不想使用AI功能,可以删除或注释掉整个ai服务块 105 | ai: 106 | image: 775495797/easyaccounts-ai:latest 107 | container_name: easy_accounts_ai 108 | restart: always 109 | environment: 110 | # LLM 配置(请替换为您的实际配置) 111 | # 默认使用 OpenAI API 配置,您需要: 112 | # 1. 前往 https://platform.openai.com 申请 API Key 113 | # 2. 将下面的 sk-your-openai-key-here 替换为您的实际密钥 114 | 115 | - LLM_EASY_ACCOUNTS_API_KEY=sk-your-openai-key-here # 替换为您的 OpenAI API Key 116 | - LLM_EASY_ACCOUNTS_URL=https://api.openai.com/v1 117 | - LLM_EASY_ACCOUNTS_MODEL=gpt-3.5-turbo 118 | 119 | # 其他可选的 LLM 服务配置示例: 120 | # 月之暗面 Kimi: 121 | #- LLM_EASY_ACCOUNTS_API_KEY=sk-your-kimi-key 122 | #- LLM_EASY_ACCOUNTS_URL=https://api.moonshot.cn/v1 123 | #- LLM_EASY_ACCOUNTS_MODEL=moonshot-v1-8k 124 | 125 | volumes: 126 | # 数据库持久化 127 | - ./AI/database:/app/koalaq_hub_python/resource/database 128 | # 日志文件映射 129 | - ./AI/logs:/app/logs 130 | # 自定义AI角色和指令(可选) 131 | - ./AI/小易.role:/app/koalaq_hub_python/resource/role/小易.role 132 | - ./AI/task.prompt:/app/koalaq_hub_python/resource/prompts/layers/task/easy_accounts_instructions.prompt 133 | 134 | depends_on: 135 | - server 136 | networks: 137 | - easy_accounts_net 138 | 139 | networks: 140 | easy_accounts_net: 141 | -------------------------------------------------------------------------------- /docker-compose-chinese.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | 5 | # 数据库端,如若需要外部部署数据库,请自行部署,服务端使用数据库版本为8.0.31 6 | # 请勿修改"db"服务的名称,否则会导致服务端无法连接数据库 7 | db: 8 | restart: always 9 | container_name: easy_accounts_db 10 | image: registry.cn-beijing.aliyuncs.com/easy_accounts/mysql:8.0.31 #此处使用阿里云镜像 11 | ports: 12 | - "10668:3306" 13 | environment: 14 | TZ: Asia/Shanghai 15 | MYSQL_ROOT_PASSWORD: easy_accounts #数据库root密码,自行修改,修改完需要修改Server中的数据库密码 16 | MYSQL_DATABASE: yd_jz #数据库名称,请勿修改 17 | volumes: 18 | - ./Database/data:/var/lib/mysql 19 | - ./Database/init:/docker-entrypoint-initdb.d 20 | command: 21 | --default-authentication-plugin=mysql_native_password 22 | --character-set-server=utf8mb4 23 | --collation-server=utf8mb4_general_ci 24 | --explicit_defaults_for_timestamp=true 25 | --lower_case_table_names=1 26 | networks: 27 | - easy_accounts_net 28 | 29 | nginx: 30 | restart: always 31 | container_name: easy_accounts_nginx 32 | image: registry.cn-beijing.aliyuncs.com/easy_accounts/easyaccounts-nginx:latest #此处使用阿里云镜像 33 | ports: 34 | - "10669:80" 35 | volumes: 36 | #- ./Web/dist:/usr/share/nginx/html #如若需要自行修改前端,请将前端放置在Web/dist目录下,并解开此行注释 37 | #- ./Web/nginx/default.conf:/etc/nginx/conf.d/default.conf #如若需要自行修改nginx配置,请将配置文件放置在Web/nginx/default.conf目录下,并解开此行注释 38 | - ./Resource:/usr/share/nginx/html/resources #资源文件目录,此文件夹提供一个下载功能 39 | depends_on: 40 | - server 41 | networks: 42 | - easy_accounts_net 43 | 44 | server: 45 | restart: always 46 | container_name: easy_accounts_server 47 | 48 | image: registry.cn-beijing.aliyuncs.com/easy_accounts/easyaccounts-server:latest # 此处使用阿里云镜像 49 | environment: 50 | #-------------------以下为服务端数据库环境变量,如果不需要外部部署数据库,请勿修改------------------- 51 | # - MYSQL_HOST= # 解开注释则需要填写数据库地址,否则默认为db 52 | # - MYSQL_PORT= # 解开注释则需要填写数据库端口号,否则默认为3306 53 | # - MYSQL_USERNAME= # 解开注释则需要填写数据库用户名,否则默认为root 54 | 55 | - DB_PASSWORD=easy_accounts # 数据库密码,默认easy_accounts,如果使用外部数据库,请修改此处 56 | - SQL_BACKUP_TIME=00 00 22 * * ? # SQL备份时间 corn表达式,默认每天晚上10点 57 | 58 | #-------------------以下为服务端登录功能环境变量------------------- 59 | - ENABLE_LOGIN=true # 是否启用登录功能,默认true 60 | - EXPIRED_TIME=30 # 登录过期时间,默认30分钟,单位分钟 61 | volumes: 62 | - ./Resource/sql:/Ledger/backup # 数据库备份文件目录 63 | - ./Resource/excel/month:/Ledger/excel/month # excel 生成月度账单文件目录 64 | - ./Resource/excel/screen:/Ledger/excel/screen # excel 生成筛选账单文件目录 65 | - ./Resource/excel/month:/Ledger/excel/analysis # excel 生成筛选账单文件目录 66 | - ./Resource/images:/Ledger/images # 图片上传文件目录 67 | - ./Server/logs:/Ledger/logs # 日志文件目录 68 | - ./Server/auth:/Ledger/auth # 登录认证文件目录,需要映射,否则重启后会丢失用户名密码 69 | # 重置用户名密码请删除auth文件夹下的secret.key文件 70 | #- ./YD_JZ-SNAPSHOT.jar:/Ledger/app/YD_JZ-SNAPSHOT.jar # 服务端jar包,如若需要自行修改,请将jar包放置在Server/app目录下,并修改此行 71 | ports: 72 | - 10670:8081 # 左侧映射出去的端口号为服务端口号 默认10670 73 | depends_on: 74 | - db 75 | networks: 76 | - easy_accounts_net 77 | 78 | webhook: 79 | image: registry.cn-beijing.aliyuncs.com/easy_accounts/easyaccounts-webhook:latest # 此处使用阿里云镜像 80 | container_name: easy_accounts_webhook 81 | restart: always 82 | ports: 83 | - "10671:8083" 84 | volumes: 85 | - ./WebHook:/app/ 86 | #- ./WebHook/webhook.py:/app/webhook.py # 如果要自行处理文件,则取消这行注释,映射该文件,则下方的环境变量无效 87 | - ./WebHook/webhook-email.py:/app/webhook.py # 如需修改发送邮件服务,请解开此行注释 88 | environment: 89 | - TZ=Asia/Shanghai 90 | - LOG_FILE=/app/hook.log 91 | - SEND_SQL_BACKUP=True # 是否发送SQL备份文件,默认True 92 | - SEND_EXCEL=True # 是否发送Excel文件,默认True 93 | 94 | # -------------------以下为发送邮件服务的环境变量------------------- 95 | - SMTP_SERVER= # SMTP服务器地址 96 | - SMTP_PORT= # SMTP端口 97 | - SMTP_MAIL= # 发件人邮箱,一般来说是SMTP账号,例如自己的QQ邮箱 98 | - SMTP_PASSWORD= # SMTP密码,一般来说是SMTP账号的授权码,需要自己去自己的邮箱中找到设置 99 | - SMTP_TO_LIST= # 收件人邮箱列表,用逗号分隔 100 | 101 | networks: 102 | - easy_accounts_net 103 | 104 | # AI智能助手服务(可选) 105 | # 如果不想使用AI功能,可以删除或注释掉整个ai服务块 106 | ai: 107 | image: registry.cn-beijing.aliyuncs.com/easy_accounts/easyaccounts-ai:latest # 此处使用阿里云镜像 108 | container_name: easy_accounts_ai 109 | restart: always 110 | environment: 111 | # LLM 配置(请替换为您的实际配置) 112 | # 默认使用 OpenAI API 配置,您需要: 113 | # 1. 前往 https://platform.openai.com 申请 API Key 114 | # 2. 将下面的 sk-your-openai-key-here 替换为您的实际密钥 115 | 116 | - LLM_EASY_ACCOUNTS_API_KEY=sk-your-openai-key-here # 替换为您的 OpenAI API Key 117 | - LLM_EASY_ACCOUNTS_URL=https://api.openai.com/v1 118 | - LLM_EASY_ACCOUNTS_MODEL=gpt-3.5-turbo 119 | 120 | # 国内可用的其他 LLM 服务配置示例: 121 | # 月之暗面 Kimi (推荐国内用户): 122 | #- LLM_EASY_ACCOUNTS_API_KEY=sk-your-kimi-key 123 | #- LLM_EASY_ACCOUNTS_URL=https://api.moonshot.cn/v1 124 | #- LLM_EASY_ACCOUNTS_MODEL=moonshot-v1-8k 125 | 126 | 127 | volumes: 128 | # 数据库持久化 129 | - ./AI/database:/app/koalaq_hub_python/resource/database 130 | # 日志文件映射 131 | - ./AI/logs:/app/logs 132 | # 自定义AI角色和指令(可选) 133 | - ./AI/小易.role:/app/koalaq_hub_python/resource/role/小易.role 134 | - ./AI/task.prompt:/app/koalaq_hub_python/resource/prompts/layers/task/easy_accounts_instructions.prompt 135 | 136 | depends_on: 137 | - server 138 | networks: 139 | - easy_accounts_net 140 | 141 | networks: 142 | easy_accounts_net: 143 | -------------------------------------------------------------------------------- /Database/base_sql/yd_jz_base.sql: -------------------------------------------------------------------------------- 1 | -- MySQL dump 10.13 Distrib 8.0.36, for Linux (x86_64) 2 | -- 3 | -- Host: db Database: yd_jz 4 | -- ------------------------------------------------------ 5 | -- Server version 8.0.31 6 | 7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 9 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 10 | /*!50503 SET NAMES utf8mb4 */; 11 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; 12 | /*!40103 SET TIME_ZONE='+00:00' */; 13 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; 14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 15 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 16 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 17 | 18 | -- 19 | -- Current Database: `yd_jz` 20 | -- 21 | 22 | CREATE DATABASE /*!32312 IF NOT EXISTS*/ `yd_jz` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */ /*!80016 DEFAULT ENCRYPTION='N' */; 23 | 24 | USE `yd_jz`; 25 | 26 | -- 27 | -- Table structure for table `account` 28 | -- 29 | 30 | DROP TABLE IF EXISTS `account`; 31 | /*!40101 SET @saved_cs_client = @@character_set_client */; 32 | /*!50503 SET character_set_client = utf8mb4 */; 33 | CREATE TABLE `account` ( 34 | `id` int NOT NULL AUTO_INCREMENT, 35 | `money` varchar(20) NOT NULL DEFAULT '0', 36 | `exempt_money` varchar(20) NOT NULL DEFAULT '0', 37 | `a_name` varchar(50) NOT NULL, 38 | `card` varchar(30) DEFAULT NULL, 39 | `a_disable` tinyint(1) DEFAULT '0', 40 | `create_time` date DEFAULT NULL, 41 | `note` varchar(100) DEFAULT NULL, 42 | PRIMARY KEY (`id`), 43 | UNIQUE KEY `Account_id_uindex` (`id`) 44 | ) ENGINE=InnoDB AUTO_INCREMENT=47 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='账户表'; 45 | /*!40101 SET character_set_client = @saved_cs_client */; 46 | 47 | -- 48 | -- Dumping data for table `account` 49 | -- 50 | 51 | LOCK TABLES `account` WRITE; 52 | /*!40000 ALTER TABLE `account` DISABLE KEYS */; 53 | /*!40000 ALTER TABLE `account` ENABLE KEYS */; 54 | UNLOCK TABLES; 55 | 56 | -- 57 | -- Table structure for table `action` 58 | -- 59 | 60 | DROP TABLE IF EXISTS `action`; 61 | /*!40101 SET @saved_cs_client = @@character_set_client */; 62 | /*!50503 SET character_set_client = utf8mb4 */; 63 | CREATE TABLE `action` ( 64 | `id` int NOT NULL AUTO_INCREMENT, 65 | `h_name` varchar(50) NOT NULL, 66 | `exempt` tinyint(1) DEFAULT '0', 67 | `handle` int NOT NULL DEFAULT '0', 68 | PRIMARY KEY (`id`), 69 | UNIQUE KEY `action_id_uindex` (`id`) 70 | ) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='操作表'; 71 | /*!40101 SET character_set_client = @saved_cs_client */; 72 | 73 | -- 74 | -- Dumping data for table `action` 75 | -- 76 | 77 | LOCK TABLES `action` WRITE; 78 | /*!40000 ALTER TABLE `action` DISABLE KEYS */; 79 | INSERT INTO `action` VALUES (15,'收入',0,0),(16,'支出',0,1),(17,'内部转账',0,2),(18,'借入',1,0),(19,'还钱',1,1),(20,'借出',1,1),(21,'收钱',1,0); 80 | /*!40000 ALTER TABLE `action` ENABLE KEYS */; 81 | UNLOCK TABLES; 82 | 83 | -- 84 | -- Table structure for table `date` 85 | -- 86 | 87 | DROP TABLE IF EXISTS `date`; 88 | /*!40101 SET @saved_cs_client = @@character_set_client */; 89 | /*!50503 SET character_set_client = utf8mb4 */; 90 | CREATE TABLE `date` ( 91 | `id` int NOT NULL 92 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; 93 | /*!40101 SET character_set_client = @saved_cs_client */; 94 | 95 | -- 96 | -- Dumping data for table `date` 97 | -- 98 | 99 | LOCK TABLES `date` WRITE; 100 | /*!40000 ALTER TABLE `date` DISABLE KEYS */; 101 | /*!40000 ALTER TABLE `date` ENABLE KEYS */; 102 | UNLOCK TABLES; 103 | 104 | -- 105 | -- Table structure for table `excel` 106 | -- 107 | 108 | DROP TABLE IF EXISTS `excel`; 109 | /*!40101 SET @saved_cs_client = @@character_set_client */; 110 | /*!50503 SET character_set_client = utf8mb4 */; 111 | CREATE TABLE `excel` ( 112 | `id` int NOT NULL AUTO_INCREMENT, 113 | `e_date` date DEFAULT NULL, 114 | `e_name` varchar(50) NOT NULL, 115 | `e_path` varchar(255) NOT NULL, 116 | `condition` varchar(255) DEFAULT NULL, 117 | PRIMARY KEY (`id`), 118 | UNIQUE KEY `excel_id_uindex` (`id`) 119 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; 120 | /*!40101 SET character_set_client = @saved_cs_client */; 121 | 122 | -- 123 | -- Dumping data for table `excel` 124 | -- 125 | 126 | LOCK TABLES `excel` WRITE; 127 | /*!40000 ALTER TABLE `excel` DISABLE KEYS */; 128 | /*!40000 ALTER TABLE `excel` ENABLE KEYS */; 129 | UNLOCK TABLES; 130 | 131 | -- 132 | -- Table structure for table `flow` 133 | -- 134 | 135 | DROP TABLE IF EXISTS `flow`; 136 | /*!40101 SET @saved_cs_client = @@character_set_client */; 137 | /*!50503 SET character_set_client = utf8mb4 */; 138 | CREATE TABLE `flow` ( 139 | `id` int NOT NULL AUTO_INCREMENT, 140 | `f_date` date NOT NULL, 141 | `money` varchar(20) NOT NULL DEFAULT '0', 142 | `type_id` int NOT NULL, 143 | `action_id` int NOT NULL, 144 | `exempt` tinyint(1) NOT NULL DEFAULT '0', 145 | `note` varchar(100) DEFAULT NULL, 146 | `f_create_date` date DEFAULT NULL, 147 | `account_id` int DEFAULT NULL, 148 | `account_to_id` int DEFAULT NULL, 149 | `collect` tinyint(1) DEFAULT '0', 150 | `f_disable` tinyint(1) DEFAULT '0', 151 | PRIMARY KEY (`id`), 152 | UNIQUE KEY `flow_id_uindex` (`id`) 153 | ) ENGINE=InnoDB AUTO_INCREMENT=1411 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='流水表'; 154 | /*!40101 SET character_set_client = @saved_cs_client */; 155 | 156 | -- 157 | -- Dumping data for table `flow` 158 | -- 159 | 160 | LOCK TABLES `flow` WRITE; 161 | /*!40000 ALTER TABLE `flow` DISABLE KEYS */; 162 | /*!40000 ALTER TABLE `flow` ENABLE KEYS */; 163 | UNLOCK TABLES; 164 | 165 | -- 166 | -- Table structure for table `type` 167 | -- 168 | 169 | DROP TABLE IF EXISTS `type`; 170 | /*!40101 SET @saved_cs_client = @@character_set_client */; 171 | /*!50503 SET character_set_client = utf8mb4 */; 172 | CREATE TABLE `type` ( 173 | `id` int NOT NULL AUTO_INCREMENT, 174 | `t_name` varchar(50) NOT NULL, 175 | `parent` int DEFAULT '-1', 176 | `t_disable` tinyint(1) DEFAULT '0', 177 | `has_child` tinyint(1) DEFAULT '0', 178 | PRIMARY KEY (`id`), 179 | UNIQUE KEY `type_id_uindex` (`id`) 180 | ) ENGINE=InnoDB AUTO_INCREMENT=93 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='分类表'; 181 | /*!40101 SET character_set_client = @saved_cs_client */; 182 | 183 | -- 184 | -- Dumping data for table `type` 185 | -- 186 | 187 | LOCK TABLES `type` WRITE; 188 | /*!40000 ALTER TABLE `type` DISABLE KEYS */; 189 | /*!40000 ALTER TABLE `type` ENABLE KEYS */; 190 | UNLOCK TABLES; 191 | 192 | -- 193 | -- Table structure for table `user` 194 | -- 195 | 196 | DROP TABLE IF EXISTS `user`; 197 | /*!40101 SET @saved_cs_client = @@character_set_client */; 198 | /*!50503 SET character_set_client = utf8mb4 */; 199 | CREATE TABLE `user` ( 200 | `id` int NOT NULL AUTO_INCREMENT, 201 | `name` varchar(50) NOT NULL, 202 | `password` int NOT NULL, 203 | PRIMARY KEY (`id`), 204 | UNIQUE KEY `user_id_uindex` (`id`) 205 | ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表'; 206 | /*!40101 SET character_set_client = @saved_cs_client */; 207 | 208 | -- 209 | -- Dumping data for table `user` 210 | -- 211 | 212 | LOCK TABLES `user` WRITE; 213 | /*!40000 ALTER TABLE `user` DISABLE KEYS */; 214 | /*!40000 ALTER TABLE `user` ENABLE KEYS */; 215 | UNLOCK TABLES; 216 | /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; 217 | 218 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; 219 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; 220 | /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; 221 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 222 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 223 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 224 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; 225 | 226 | -- Dump completed on 2024-05-17 0:59:03 227 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EasyAccounts 2 | ## 简介 3 | EasyAccounts是一款**开源中文记账软件** 4 | 新版本增加**AI**功能,可以通过对话查询、新增、修改流水,可以生成你想要的excel 5 | 如果恰好你也是一个喜欢记账的人,并且符合下面的项目要求 6 | 如果你对互联网上的记账App不放心,如果你怕**信息泄露**,如果你怕几年的**数据丢失** 7 | 🎉🎉🎉也许这个项目能满足你的需求 8 | ### 面向受众: 9 | - 家庭🏠✅ 10 | - 个人👨💼✅ 11 | 12 | > 项目无法支撑团队记账,所以不推荐团队使用。👥❌ 13 | 14 | ### 项目要求 15 | - 💻有一定的计算机基础 16 | - 🐳会部署Docker 17 | - 📖理解记账的基本概念 18 | - 🥘有可以部署的平台(见下文) 19 | ### 支持平台 20 | * Ubuntu&Debian 21 | * Windows 22 | * Mac OS 23 | * 飞牛私有云 Fnos 24 | 25 | ### 资源消耗 26 | 500-800MB内存 27 | 28 | ### 项目特点 29 | - 记账基础维度: 30 | - 账户 31 | - 收支操作 32 | - 分类 33 | - 明细 34 | 35 | - 数据筛选维度: 36 | - 时间 37 | - 账户 38 | - 分类(2级) 39 | - 备注 40 | 41 | - 易用性维度: 42 | - 自己设置模板,3步一笔账 43 | - 生成Excel账单,自己制作账本 44 | - 定时备份数据库 45 | - 账单数据推送 46 | --- 47 | 48 | ### 项目主页 49 | 具体内容见项目主页:[GitBook](https://mercys-organization-2.gitbook.io/easyaccounts/) 50 | 项目部署说明:[项目部署](https://mercys-organization-2.gitbook.io/easyaccounts/deploy/deploy) 51 | 52 | ## 更新计划 53 | ### 2025 Update plan: 54 | 更新计划 55 | 本来投入到另外一个项目中,后来多方面的原因,进度有些推不动 56 | 今年还会做一些开发,大概有下面的几个内容 57 | - [x] AI功能引入 58 | - [x] 增加鉴权功能,主要是用户名和密码 59 | - [ ] 账单导入,确定要做 60 | 61 | 放弃的任务 62 | - [ ] ~~增加数据库切换,Mysql / sqlite~~ (不太好改) 63 | 64 | 延期(无限)的任务 65 | - [ ] 桌面端开发,预计是Electron 66 | - [ ] 安卓移动端开发 (苹果我没有账号) 67 | 68 | ### 2.5.0 Feature 69 | 更新日期:2025-09-19 70 | 更新视频:[【开源记账软件 EasyAccounts v2.5.0 AI 智能体参上!】 ](https://www.bilibili.com/video/BV1zxnKzHEkF/?share_source=copy_web&vd_source=d0722b43e81cb8a8b7ef09b76193df8d) 71 | - 重要特性 72 | - AI功能引入,使用用户自己的key即可 73 | - 接口反向代理,API_BASE_URL已成历史 74 | - 新增功能 75 | - 流水添加图片,每个流水可以附带三张图片 76 | - fix bugs 77 | - 修复一个账单出现很多小数点的问题 78 | 79 | ### 2.4.0 Feature 80 | 更新日期:2025-02-07 81 | 更新视频:[【开源记账软件 EasyAccounts v2.4.0 新版本说明】 ](https://www.bilibili.com/video/BV1P1NPe4Er8/?share_source=copy_web&vd_source=d0722b43e81cb8a8b7ef09b76193df8d) 82 | - 重要特性 83 | - 增加登录功能,可以通过compose来开启关闭,compose不设置,默认关闭 84 | - 增加统计功能,原财务分析页面作废,统计功能为按分类统计汇总,按单项分类汇总,可按月查看单项分类 85 | - 新增功能 86 | - 增加点击生成Excel报表后反馈,变为同步式等待 87 | - webhook修改逻辑判断 88 | - 重构总览界面,增加当年月度概览,账户详情转移至顶部 89 | - 记一笔按钮转移至明细(原流水)界面底部,可以拖动(电脑端可点击) 90 | - 筛选入口转移至原记一笔位置,主页不在提供筛选选项卡 91 | - 筛选默认开启备注搜索明细 92 | - 分类管理增加不参与统计选项,不参与统计的分类不会出现在统计页面 93 | - fixbugs 94 | - 修复主页请求500错误,跨年后请求错误 95 | - 修复webhook发送邮件,多收件人可能发送失败问题 96 | - 生成历史月份Excel,报表中账户金额可以追溯 97 | - 技术特性 98 | - 增加外部Mysql数据库支持,可以通过compose来开启关闭,compose不设置,默认关闭 99 | - 前端VUE2->VUE3 100 | - 前端组件Vant2->Vant4 101 | 102 | ### 升级教程及历史版本 103 | [Release-Note](https://mercys-organization-2.gitbook.io/easyaccounts/release-notes) 104 | 105 | 106 | ## 功能 107 | 使用功能详见: [【开源记账软件 EasyAccounts 使用教程-哔哩哔哩】 ](https://b23.tv/BV1Ds421w78S) 108 | 知乎等详见: [关于我写的个人记账软件方案 - 四点不在线的小能猫的文章 - 知乎](https://zhuanlan.zhihu.com/p/645208377) 109 | 110 | ### 部分截图 111 |
114 |
115 | 总览 116 | |
117 |
118 |
119 | 明细 120 | |
121 |
122 |
123 | 统计 124 | |
125 |
126 |
127 | 设置 128 | |
129 |
132 |
133 | 总览账户 134 | |
135 |
136 |
137 | 新增明细 138 | |
139 |
140 |
141 | 筛选 142 | |
143 |
144 |
145 | 分类界面 146 | |
147 |
150 |
151 | 分类列表 152 | |
153 |
154 |
155 | 分类单项统计 156 | |
157 |
158 |
159 | 统计图表 160 | |
161 |
162 |
163 | 登录 164 | |
165 |
168 |
169 | 附件流水 170 | |
171 |
172 |
173 | AI对话 174 | |
175 |
181 |
182 | Swagger 文档 183 | |
184 |
185 |
186 | Nginx 文件下载 187 | |
188 |
191 |
192 | Email 推送 193 | |
194 |
195 |
196 | Excel 生成 197 | |
198 |
199 |
204 |
205 | ### 可以定义的操作有
206 | 1. 账户:有金额、名称等选项。
207 | 2. 收支:收入、支出、借入、借出、内部转账等选项,此项目我已经再初始化数据库中添加了常用的几项,足够覆盖生活99%以上的场景,不建议修改。
208 | 3. 分类:有一二级类型,例如用车支出,下属可以选择:加油、保险,具体选项参考自己日常生活,~~需要注意的是,分类与操作没有关联,你可以叫做 “我的收入”,但是你记账的时候可以选择“支出”操作,**分类仅用于快捷记录使用**。~~
209 | (新版本中 分类已经与收支关联)
210 |
211 | ### 记账功能
212 | 选择账户->选择操作->选择记账类型->输入金额->保存。
213 | 一条账目就记录完毕,所在金额会在选择的账户中增加或减少。
214 |
215 | ### 报表功能
216 | 一共可以生成2种Excel文档:
217 | - 月度账单:生成一个月的账单。
218 | - 位置:主页流水选项卡里,如果你有流水记录,点击生成报表,没有记录的话就没有这个按钮。
219 | - 可以生成一个月的流水账单,有一点一定要记住,生成账单的时间点,再excel里面是会有你所有的账户金额的,所以如果你再5月记账,生成4月的账单,那么4月的账单里面是有5月的账户金额的,所以生成账单的时候一定要注意时间点。**请在记录当月的流水之前,生成上个月的账单。**
220 | - 筛选账单:生成筛选的账单。
221 | - 位置:主页点击总览,然后点击筛选按钮,选择筛选条件,然后点击生成报表。
222 | - 生成报表前,记得点筛选验证数据,如果没有筛选结果数据,是不会生成报表的,接口会报错,哈哈哈,这是一个小bug,每一次更新我都忘记修改。
223 | - ~~分析报表:生成分析同环比报表。~~(新版本无分析报表)
224 | - ~~位置:主页点击分析选项卡,然后选择周期,点击生成报表。~~
225 | - ~~生成完的同比环比数据会汇总到一个Excel表格中,可以查看同比环比数据。~~
226 |
227 | ### 筛选功能
228 | 基本所有的操作包括类型,都可以算作筛选的选项,得到结果后可以手动生成xls。
229 |
230 | ### 备份功能
231 | 启动项目的时候可以设置SQL备份日期规则,使用cron规则,详情见docker-compose.yml文件中的环境变量。
232 | 备份的文件会存放在Resource/sql目录下,文件名为日期.sql。
233 | Excel生成后,会自动备份到Resource/excel目录下,对应上面三个账单的文件夹。
234 |
235 | ### WebHook功能(发送邮件)
236 | WebHook是一个发送邮件,和处理SQL备份的功能,可以在docker-compose.yml中配置。
237 | 具体使用方法见:[WebHook使用说明](https://mercys-organization-2.gitbook.io/easyaccounts/deploy/webhook)
238 | 配置好发送邮件功能后,就可以在手机上接收excel以及SQL文件了,效果如下:
239 |
240 |
241 |
242 | ### 其他功能
243 | 1. Swagger接口文档,可以查看接口文档,支持自定义开发前端。
244 |
245 | 2. Nginx提供生成文件的下载服务,可以直接下载生成的文件
246 |
247 |
248 | 上述两个地址详见:[项目部署-项目访问](https://mercys-organization-2.gitbook.io/easyaccounts/deploy/deploy#xiang-mu-fang-wen)
249 |
250 | ## 项目数据
251 |
252 | ### Star History
253 |
254 | [](https://star-history.com/#QingHeYang/EasyAccounts&Date)
255 |
256 | ---
257 |
258 | ## 项目贡献
259 | 请阅读贡献指南:[贡献指南](https://mercys-organization-2.gitbook.io/easyaccounts/develop/contributing)
260 |
261 | ## 开发声明
262 | 2021年9月-2025年2月开发已有3年5余月
263 | 项目需求做了不少变更,未来还会有不少更新
264 | 因为我个人能力有限,可能会某一次更新中出现纰漏,请大家多包容一些
265 | 所以请大家一定保护好自己的sql备份数据,bug可以修复,项目可以重新部署,excel可以重新生成,但是sql备份丢了,那就真没什么办法了
266 |
267 | 自2024年年初,本项目开始大范围使用ai编程,其中包括chatGpt,copilot,cursor,claude,Qwen,文心一言
268 | 所以源码中有些注释比较糙,请多理解(我一个旧时代的android工程师能做到这样已经很不容易了啊喂!!)
269 |
270 | ## 结语
271 | 这个项目会一直维护下去,因为它是我自己经常使用的工具。如果你有任何关于记账的好点子,随时告诉我,我会根据情况考虑添加。
272 |
273 | 如果这个项目对你有所帮助,欢迎点个Star;如果遇到问题,欢迎提issue。
274 | 我承诺,项目将继续免费且公益,好的创意我会不断更新。
275 |
276 | 另外,作为一个超级社恐的开发者,有时我可能会稍微迟疑添加别人微信,请大家多包容🤝。
277 | (我真的很社恐,不敢加别的开发者微信,不敢建群)
278 |
279 | 🎁 **爱心传送门**
280 | 如果你愿意支持EasyAccounts:
281 | 扫描下方二维码即可献爱心
282 |
283 | 所有善款都会带着「捐赠者大名」飞奔到**腾讯公益孤儿救助项目**。
284 |
285 | 捐赠款项我会以**捐赠者备注**的名义,转赠至**腾讯公益的孤儿救助项目**,感谢每一位支持的朋友。
286 | 捐赠者名单会定期更新,感谢大家的支持与厚爱🙏。(今天收到了第一笔陌生朋友的转账,被认可的感觉真好啊)
287 |
290 |
291 | |
292 |
293 |
294 | |
295 |