feat: 邮箱账单自动拉取

- 新增邮箱配置、发件人规则、待处理账单三张表(SQLite/MySQL 双脚本)及 CRUD 接口
- 按邮件发件人匹配账单类型,未命中忽略;发件人规则可配置解压密码与默认分类/标签
- 支持 IMAP IDLE 实时监听、定时兜底与手动立即检查;拉取范围可配置(仅新邮件/最近 N 天/最近 N 封)
- 附件保存至本地 data/email-bill,解析预览后手动导入,复用账单解析与规则引擎
- 立即检查通过 SSE 实时推送执行进度
- 前端新增「邮箱账单」页(邮箱配置/发件人规则/待处理账单三 Tab),支持子路由与 Tab 偏好记忆,配置弹窗提供常用 IMAP 服务器候选项
- 新用户注册自动初始化默认发件人规则(支付宝/微信/美团/招商银行/交通银行)
- 抽取共用账单预览与结果组件,供上传导入与邮箱账单复用

同时包含:
- 统计新增口径筛选(仅计入/全部/仅不计入),支付方式改为后端动态获取
- 统一实体 createdAt/updatedAt 位置至 id 之后,调整部分日志级别
This commit is contained in:
2026-09-19 17:50:55 +08:00
parent dedf30c74d
commit 9bc858eefc
65 changed files with 5940 additions and 529 deletions
+88 -21
View File
@@ -1,22 +1,22 @@
-- 用户表:存储系统用户的基本信息和登录凭证
CREATE TABLE IF NOT EXISTS t_user (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID,主键自增',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名,唯一标识,用于登录',
password_hash VARCHAR(255) NOT NULL COMMENT '密码哈希值,使用BCrypt加密存储',
nickname VARCHAR(50) COMMENT '用户昵称,显示名称',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
nickname VARCHAR(50) COMMENT '用户昵称,显示名称'
);
-- 分类表:存储收支分类信息,支持用户自定义
CREATE TABLE IF NOT EXISTS t_category (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '分类ID,主键自增',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
user_id BIGINT NOT NULL COMMENT '所属用户ID',
name VARCHAR(50) NOT NULL COMMENT '分类名称',
type VARCHAR(10) NOT NULL COMMENT '分类类型:INCOME-收入,EXPENSE-支出',
icon VARCHAR(50) COMMENT '分类图标,可选',
sort_order INT NOT NULL DEFAULT 0 COMMENT '排序序号,数值越小越靠前',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
INDEX idx_user_id (user_id) COMMENT '用户ID索引',
INDEX idx_user_type (user_id, type) COMMENT '用户ID+类型联合索引'
);
@@ -24,6 +24,8 @@ CREATE TABLE IF NOT EXISTS t_category (
-- 收支记录表:存储每一笔收入或支出的详细信息
CREATE TABLE IF NOT EXISTS t_transaction (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '记录ID,主键自增',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
user_id BIGINT NOT NULL COMMENT '所属用户ID',
category_id BIGINT COMMENT '所属分类ID,可为空表示未分类',
type VARCHAR(10) NOT NULL COMMENT '记录类型:INCOME-收入,EXPENSE-支出',
@@ -35,8 +37,6 @@ CREATE TABLE IF NOT EXISTS t_transaction (
transaction_time DATETIME NOT NULL COMMENT '交易发生时间',
include_in_stats TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否计入统计:1-计入,0-不计入(如借款、转账等)',
raw_data JSON COMMENT '账单导入的原始完整数据(结构化JSON),手动记录为空',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_user_id (user_id) COMMENT '用户ID索引',
INDEX idx_category_id (category_id) COMMENT '分类ID索引',
INDEX idx_transaction_time (transaction_time) COMMENT '交易时间索引',
@@ -47,11 +47,11 @@ CREATE TABLE IF NOT EXISTS t_transaction (
-- 标签表:存储用户自定义标签,用于给交易打标签
CREATE TABLE IF NOT EXISTS t_tag (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '标签ID,主键自增',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
user_id BIGINT NOT NULL COMMENT '所属用户ID',
name VARCHAR(50) NOT NULL COMMENT '标签名称',
color VARCHAR(20) COMMENT '标签颜色,用于前端展示',
sort_order INT NOT NULL DEFAULT 0 COMMENT '排序序号,数值越小越靠前',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
INDEX idx_user_id (user_id) COMMENT '用户ID索引',
UNIQUE INDEX uk_user_name (user_id, name) COMMENT '用户ID+标签名唯一索引'
);
@@ -59,9 +59,9 @@ CREATE TABLE IF NOT EXISTS t_tag (
-- 交易标签关联表:交易与标签的多对多关系
CREATE TABLE IF NOT EXISTS t_transaction_tag (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '关联ID,主键自增',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
transaction_id BIGINT NOT NULL COMMENT '交易记录ID',
tag_id BIGINT NOT NULL COMMENT '标签ID',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
UNIQUE INDEX uk_transaction_tag (transaction_id, tag_id) COMMENT '交易+标签唯一索引',
INDEX idx_tag_id (tag_id) COMMENT '标签ID索引'
);
@@ -69,19 +69,20 @@ CREATE TABLE IF NOT EXISTS t_transaction_tag (
-- AI 配置表:存储每个用户的 AI 智能建议相关配置
CREATE TABLE IF NOT EXISTS t_ai_config (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '配置ID,主键自增',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
user_id BIGINT NOT NULL COMMENT '所属用户ID',
enabled TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否启用AI智能建议:1-启用,0-关闭',
api_key VARCHAR(255) COMMENT 'DeepSeek API Key(明文存储)',
base_url VARCHAR(255) NOT NULL DEFAULT 'https://api.deepseek.com' COMMENT 'API 基础地址',
model VARCHAR(50) NOT NULL DEFAULT 'deepseek-chat' COMMENT '使用的模型名称',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE INDEX uk_user_id (user_id) COMMENT '用户ID唯一索引,每个用户一条配置'
);
-- AI 使用记录表:记录每次调用 DeepSeek 的用量与结果
CREATE TABLE IF NOT EXISTS t_ai_usage (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '记录ID,主键自增',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
user_id BIGINT NOT NULL COMMENT '所属用户ID',
type VARCHAR(20) NOT NULL COMMENT '调用类型:SUGGEST-生成建议,TEST-测试连接',
model VARCHAR(50) COMMENT '模型名称',
@@ -93,13 +94,14 @@ CREATE TABLE IF NOT EXISTS t_ai_usage (
success TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否成功:1-成功,0-失败',
error_message VARCHAR(500) COMMENT '失败原因',
duration_ms BIGINT COMMENT '调用耗时(毫秒)',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
INDEX idx_user_created (user_id, created_at) COMMENT '用户ID+创建时间联合索引'
);
-- 通知配置表:存储每个用户的账单导入提醒设置
CREATE TABLE IF NOT EXISTS t_notification_config (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '配置ID,主键自增',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
user_id BIGINT NOT NULL COMMENT '所属用户ID',
enabled TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否启用定时提醒:1-启用,0-关闭',
frequency VARCHAR(10) NOT NULL DEFAULT 'MONTHLY' COMMENT '提醒周期:DAILY-每天,WEEKLY-每周,MONTHLY-每月',
@@ -107,39 +109,38 @@ CREATE TABLE IF NOT EXISTS t_notification_config (
day_of_week INT NOT NULL DEFAULT 1 COMMENT '每周提醒日(1-7,1为周一),frequency=WEEKLY 时生效',
send_time VARCHAR(5) NOT NULL DEFAULT '09:00' COMMENT '提醒时间(HH:mm)',
last_sent_at DATETIME COMMENT '最近一次发送时间,用于避免重复提醒',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE INDEX uk_notification_config_user_id (user_id) COMMENT '用户ID唯一索引,每个用户一条配置'
);
-- 通知渠道表:存储用户配置的邮箱、Server 酱等推送渠道
CREATE TABLE IF NOT EXISTS t_notification_channel (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '渠道ID,主键自增',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
user_id BIGINT NOT NULL COMMENT '所属用户ID',
type VARCHAR(20) NOT NULL COMMENT '渠道类型:EMAIL-邮箱,SERVERCHAN-Server 酱',
name VARCHAR(50) COMMENT '渠道名称,便于用户识别',
enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用:1-启用,0-关闭',
config TEXT COMMENT '渠道配置(JSON),如 webhook 地址、SMTP 账号等',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_notification_channel_user_id (user_id) COMMENT '用户ID索引'
);
-- 通知发送日志表:记录每次提醒的发送结果,便于排查问题
CREATE TABLE IF NOT EXISTS t_notification_log (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '日志ID,主键自增',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
user_id BIGINT NOT NULL COMMENT '所属用户ID',
channel_id BIGINT COMMENT '渠道ID,测试或渠道已删除时可能为空',
channel_type VARCHAR(20) COMMENT '渠道类型',
success TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否成功:1-成功,0-失败',
message VARCHAR(500) COMMENT '结果描述或失败原因',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
INDEX idx_notification_log_user_created (user_id, created_at) COMMENT '用户ID+创建时间联合索引'
);
-- HTTP 访问日志表:记录每次接口请求的方法、路径、状态与耗时,便于追溯接口调用
CREATE TABLE IF NOT EXISTS t_http_log (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '日志ID,主键自增',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
user_id BIGINT COMMENT '用户ID,未登录或认证失败时为空',
method VARCHAR(10) COMMENT '请求方法:GET/POST/PUT/DELETE 等',
path VARCHAR(255) COMMENT '请求路径',
@@ -148,7 +149,6 @@ CREATE TABLE IF NOT EXISTS t_http_log (
duration_ms BIGINT COMMENT '处理耗时(毫秒)',
ip VARCHAR(64) COMMENT '客户端IP',
user_agent VARCHAR(500) COMMENT 'User-Agent',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
INDEX idx_http_log_created (created_at) COMMENT '创建时间索引',
INDEX idx_http_log_user_created (user_id, created_at) COMMENT '用户ID+创建时间联合索引'
);
@@ -156,13 +156,13 @@ CREATE TABLE IF NOT EXISTS t_http_log (
-- 登录日志表:记录每次登录尝试的结果,便于审计与排查
CREATE TABLE IF NOT EXISTS t_login_log (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '日志ID,主键自增',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
user_id BIGINT COMMENT '用户ID,登录失败或用户不存在时为空',
username VARCHAR(50) COMMENT '登录用户名',
success TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否成功:1-成功,0-失败',
message VARCHAR(500) COMMENT '结果描述或失败原因',
ip VARCHAR(64) COMMENT '客户端IP',
user_agent VARCHAR(500) COMMENT 'User-Agent',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
INDEX idx_login_log_created (created_at) COMMENT '创建时间索引',
INDEX idx_login_log_user_created (user_id, created_at) COMMENT '用户ID+创建时间联合索引'
);
@@ -170,6 +170,8 @@ CREATE TABLE IF NOT EXISTS t_login_log (
-- 自定义分类与标签规则表:按关键词、收支类型、金额区间自动归类与打标签
CREATE TABLE IF NOT EXISTS t_rule (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '规则ID,主键自增',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
user_id BIGINT NOT NULL COMMENT '所属用户ID',
name VARCHAR(50) NOT NULL COMMENT '规则名称',
enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用:1-启用,0-停用',
@@ -179,17 +181,82 @@ CREATE TABLE IF NOT EXISTS t_rule (
min_amount DECIMAL(12, 2) COMMENT '金额下限(含),可为空',
max_amount DECIMAL(12, 2) COMMENT '金额上限(含),可为空',
category_id BIGINT COMMENT '命中的目标分类ID,可为空',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_rule_user_id (user_id) COMMENT '用户ID索引'
);
-- 规则标签关联表:规则与标签的多对多关系
CREATE TABLE IF NOT EXISTS t_rule_tag (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '关联ID,主键自增',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
rule_id BIGINT NOT NULL COMMENT '规则ID',
tag_id BIGINT NOT NULL COMMENT '标签ID',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
UNIQUE INDEX uk_rule_tag (rule_id, tag_id) COMMENT '规则+标签唯一索引',
INDEX idx_rule_tag_tag_id (tag_id) COMMENT '标签ID索引'
);
-- 邮箱账单配置表:存储用户用于自动拉取账单附件的 IMAP 邮箱连接信息
CREATE TABLE IF NOT EXISTS t_email_bill_config (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '配置ID,主键自增',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
user_id BIGINT NOT NULL COMMENT '所属用户ID',
name VARCHAR(50) NOT NULL COMMENT '配置名称,便于用户识别',
host VARCHAR(255) NOT NULL COMMENT 'IMAP 服务器地址',
port INT NOT NULL DEFAULT 993 COMMENT 'IMAP 端口',
use_ssl TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否使用 SSL:1-是,0-否',
username VARCHAR(255) NOT NULL COMMENT '邮箱账号',
password VARCHAR(255) NOT NULL COMMENT '邮箱授权码(明文存储,前端脱敏返回)',
folder VARCHAR(100) NOT NULL DEFAULT 'INBOX' COMMENT '监听的邮件文件夹',
enabled TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否启用监听:1-启用,0-关闭',
status VARCHAR(20) NOT NULL DEFAULT 'DISCONNECTED' COMMENT '连接状态:DISCONNECTED-未连接,CONNECTED-已连接,POLLING-轮询中,ERROR-异常',
last_error VARCHAR(500) COMMENT '最近一次连接或拉取失败原因',
last_connected_at DATETIME COMMENT '最近一次成功连接时间',
last_uid BIGINT COMMENT '已处理的最大邮件 UID,用于增量拉取',
expense_category_id BIGINT COMMENT '默认支出分类ID,可为空',
income_category_id BIGINT COMMENT '默认收入分类ID,可为空',
tag_ids TEXT COMMENT '默认标签ID列表(JSON数组)',
fetch_scope VARCHAR(20) NOT NULL DEFAULT 'INCREMENT' COMMENT '拉取范围:INCREMENT-仅新邮件,DAYS-最近N天,COUNT-最近N封',
fetch_limit INT COMMENT '拉取范围数量:DAYS 为天数,COUNT 为封数',
INDEX idx_email_bill_config_user_id (user_id) COMMENT '用户ID索引'
);
-- 邮箱账单发件人规则表:按邮件发件人匹配账单类型与默认归类
CREATE TABLE IF NOT EXISTS t_email_bill_sender_rule (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '规则ID,主键自增',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
user_id BIGINT NOT NULL COMMENT '所属用户ID',
name VARCHAR(50) NOT NULL COMMENT '规则名称',
enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用:1-启用,0-停用',
sort_order INT NOT NULL DEFAULT 0 COMMENT '优先级,数值越小越优先',
sender_patterns TEXT COMMENT '发件人匹配串列表(JSON数组),包含匹配且忽略大小写',
bill_type VARCHAR(20) NOT NULL COMMENT '命中的账单类型:ALIPAY/WECHAT/MEITUAN/CMB/BOCOM',
archive_password VARCHAR(100) COMMENT '该来源账单压缩包解压密码,可为空',
expense_category_id BIGINT COMMENT '默认支出分类ID,可为空',
income_category_id BIGINT COMMENT '默认收入分类ID,可为空',
tag_ids TEXT COMMENT '默认标签ID列表(JSON数组)',
INDEX idx_email_bill_sender_rule_user_id (user_id) COMMENT '用户ID索引'
);
-- 邮箱账单消息表:记录拉取到的账单附件,解析与入库由用户手动触发
CREATE TABLE IF NOT EXISTS t_email_bill_message (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '消息ID,主键自增',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
config_id BIGINT NOT NULL COMMENT '邮箱配置ID',
user_id BIGINT NOT NULL COMMENT '所属用户ID',
uid BIGINT NOT NULL COMMENT '邮件 IMAP UID',
message_id VARCHAR(255) COMMENT '邮件 Message-ID',
subject VARCHAR(500) COMMENT '邮件主题',
from_address VARCHAR(255) COMMENT '发件人地址',
received_at DATETIME COMMENT '邮件接收时间',
attachment_index INT NOT NULL DEFAULT 0 COMMENT '附件在邮件中的序号(从0开始)',
attachment_name VARCHAR(255) COMMENT '附件文件名',
attachment_path VARCHAR(500) COMMENT '附件本地存储相对路径',
sender_rule_id BIGINT COMMENT '命中的发件人规则ID',
bill_type VARCHAR(20) COMMENT '命中的账单类型,未命中时为空',
status VARCHAR(20) NOT NULL DEFAULT 'PULLED' COMMENT '处理状态:PULLED-待解析,CONFIRMED-已导入,IGNORED-已忽略',
error VARCHAR(500) COMMENT '解析失败原因',
UNIQUE INDEX uk_email_bill_message (config_id, uid, attachment_index) COMMENT '配置+UID+附件序号唯一索引,用于去重',
INDEX idx_email_bill_message_user_status (user_id, status) COMMENT '用户ID+状态联合索引'
);
+122 -34
View File
@@ -9,27 +9,29 @@
CREATE TABLE IF NOT EXISTS t_user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
updated_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
username VARCHAR(50) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
nickname VARCHAR(50),
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
updated_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime'))
nickname VARCHAR(50)
);;
CREATE TABLE IF NOT EXISTS t_category (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
user_id INTEGER NOT NULL,
name VARCHAR(50) NOT NULL,
type VARCHAR(10) NOT NULL,
icon VARCHAR(50),
sort_order INTEGER NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime'))
sort_order INTEGER NOT NULL DEFAULT 0
);;
CREATE INDEX IF NOT EXISTS idx_category_user_id ON t_category (user_id);;
CREATE INDEX IF NOT EXISTS idx_category_user_type ON t_category (user_id, type);;
CREATE TABLE IF NOT EXISTS t_transaction (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
updated_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
user_id INTEGER NOT NULL,
category_id INTEGER,
type VARCHAR(10) NOT NULL,
@@ -40,9 +42,7 @@ CREATE TABLE IF NOT EXISTS t_transaction (
external_no VARCHAR(64),
transaction_time DATETIME NOT NULL,
include_in_stats INTEGER NOT NULL DEFAULT 1,
raw_data TEXT,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
updated_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime'))
raw_data TEXT
);;
CREATE INDEX IF NOT EXISTS idx_transaction_user_id ON t_transaction (user_id);;
CREATE INDEX IF NOT EXISTS idx_transaction_category_id ON t_transaction (category_id);;
@@ -52,38 +52,39 @@ CREATE UNIQUE INDEX IF NOT EXISTS uk_user_external ON t_transaction (user_id, ex
CREATE TABLE IF NOT EXISTS t_tag (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
user_id INTEGER NOT NULL,
name VARCHAR(50) NOT NULL,
color VARCHAR(20),
sort_order INTEGER NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime'))
sort_order INTEGER NOT NULL DEFAULT 0
);;
CREATE INDEX IF NOT EXISTS idx_tag_user_id ON t_tag (user_id);;
CREATE UNIQUE INDEX IF NOT EXISTS uk_tag_user_name ON t_tag (user_id, name);;
CREATE TABLE IF NOT EXISTS t_transaction_tag (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
transaction_id INTEGER NOT NULL,
tag_id INTEGER NOT NULL,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime'))
tag_id INTEGER NOT NULL
);;
CREATE UNIQUE INDEX IF NOT EXISTS uk_transaction_tag ON t_transaction_tag (transaction_id, tag_id);;
CREATE INDEX IF NOT EXISTS idx_transaction_tag_tag_id ON t_transaction_tag (tag_id);;
CREATE TABLE IF NOT EXISTS t_ai_config (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
updated_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
user_id INTEGER NOT NULL,
enabled INTEGER NOT NULL DEFAULT 0,
api_key VARCHAR(255),
base_url VARCHAR(255) NOT NULL DEFAULT 'https://api.deepseek.com',
model VARCHAR(50) NOT NULL DEFAULT 'deepseek-chat',
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
updated_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime'))
model VARCHAR(50) NOT NULL DEFAULT 'deepseek-chat'
);;
CREATE UNIQUE INDEX IF NOT EXISTS uk_ai_config_user_id ON t_ai_config (user_id);;
CREATE TABLE IF NOT EXISTS t_ai_usage (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
user_id INTEGER NOT NULL,
type VARCHAR(20) NOT NULL,
model VARCHAR(50),
@@ -94,8 +95,7 @@ CREATE TABLE IF NOT EXISTS t_ai_usage (
cached_tokens INTEGER NOT NULL DEFAULT 0,
success INTEGER NOT NULL DEFAULT 1,
error_message VARCHAR(500),
duration_ms INTEGER,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime'))
duration_ms INTEGER
);;
CREATE INDEX IF NOT EXISTS idx_ai_usage_user_created ON t_ai_usage (user_id, created_at);;
@@ -126,38 +126,38 @@ END;;
CREATE TABLE IF NOT EXISTS t_notification_config (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
updated_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
user_id INTEGER NOT NULL,
enabled INTEGER NOT NULL DEFAULT 0,
frequency VARCHAR(10) NOT NULL DEFAULT 'MONTHLY',
day_of_month INTEGER NOT NULL DEFAULT 1,
day_of_week INTEGER NOT NULL DEFAULT 1,
send_time VARCHAR(5) NOT NULL DEFAULT '09:00',
last_sent_at DATETIME,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
updated_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime'))
last_sent_at DATETIME
);;
CREATE UNIQUE INDEX IF NOT EXISTS uk_notification_config_user_id ON t_notification_config (user_id);;
CREATE TABLE IF NOT EXISTS t_notification_channel (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
updated_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
user_id INTEGER NOT NULL,
type VARCHAR(20) NOT NULL,
name VARCHAR(50),
enabled INTEGER NOT NULL DEFAULT 1,
config TEXT,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
updated_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime'))
config TEXT
);;
CREATE INDEX IF NOT EXISTS idx_notification_channel_user_id ON t_notification_channel (user_id);;
CREATE TABLE IF NOT EXISTS t_notification_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
user_id INTEGER NOT NULL,
channel_id INTEGER,
channel_type VARCHAR(20),
success INTEGER NOT NULL DEFAULT 1,
message VARCHAR(500),
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime'))
message VARCHAR(500)
);;
CREATE INDEX IF NOT EXISTS idx_notification_log_user_created ON t_notification_log (user_id, created_at);;
@@ -179,6 +179,7 @@ END;;
CREATE TABLE IF NOT EXISTS t_http_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
user_id INTEGER,
method VARCHAR(10),
path VARCHAR(255),
@@ -186,27 +187,28 @@ CREATE TABLE IF NOT EXISTS t_http_log (
status INTEGER,
duration_ms INTEGER,
ip VARCHAR(64),
user_agent VARCHAR(500),
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime'))
user_agent VARCHAR(500)
);;
CREATE INDEX IF NOT EXISTS idx_http_log_created ON t_http_log (created_at);;
CREATE INDEX IF NOT EXISTS idx_http_log_user_created ON t_http_log (user_id, created_at);;
CREATE TABLE IF NOT EXISTS t_login_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
user_id INTEGER,
username VARCHAR(50),
success INTEGER NOT NULL DEFAULT 1,
message VARCHAR(500),
ip VARCHAR(64),
user_agent VARCHAR(500),
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime'))
user_agent VARCHAR(500)
);;
CREATE INDEX IF NOT EXISTS idx_login_log_created ON t_login_log (created_at);;
CREATE INDEX IF NOT EXISTS idx_login_log_user_created ON t_login_log (user_id, created_at);;
CREATE TABLE IF NOT EXISTS t_rule (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
updated_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
user_id INTEGER NOT NULL,
name VARCHAR(50) NOT NULL,
enabled INTEGER NOT NULL DEFAULT 1,
@@ -215,17 +217,15 @@ CREATE TABLE IF NOT EXISTS t_rule (
keyword VARCHAR(100),
min_amount NUMERIC(12, 2),
max_amount NUMERIC(12, 2),
category_id INTEGER,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
updated_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime'))
category_id INTEGER
);;
CREATE INDEX IF NOT EXISTS idx_rule_user_id ON t_rule (user_id);;
CREATE TABLE IF NOT EXISTS t_rule_tag (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
rule_id INTEGER NOT NULL,
tag_id INTEGER NOT NULL,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime'))
tag_id INTEGER NOT NULL
);;
CREATE UNIQUE INDEX IF NOT EXISTS uk_rule_tag ON t_rule_tag (rule_id, tag_id);;
CREATE INDEX IF NOT EXISTS idx_rule_tag_tag_id ON t_rule_tag (tag_id);;
@@ -237,3 +237,91 @@ WHEN NEW.updated_at = OLD.updated_at
BEGIN
UPDATE t_rule SET updated_at = strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime') WHERE id = OLD.id;
END;;
CREATE TABLE IF NOT EXISTS t_email_bill_config (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
updated_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
user_id INTEGER NOT NULL,
name VARCHAR(50) NOT NULL,
host VARCHAR(255) NOT NULL,
port INTEGER NOT NULL DEFAULT 993,
use_ssl INTEGER NOT NULL DEFAULT 1,
username VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL,
folder VARCHAR(100) NOT NULL DEFAULT 'INBOX',
enabled INTEGER NOT NULL DEFAULT 0,
status VARCHAR(20) NOT NULL DEFAULT 'DISCONNECTED',
last_error VARCHAR(500),
last_connected_at DATETIME,
last_uid INTEGER,
expense_category_id INTEGER,
income_category_id INTEGER,
tag_ids TEXT,
fetch_scope VARCHAR(20) NOT NULL DEFAULT 'INCREMENT',
fetch_limit INTEGER
);;
CREATE INDEX IF NOT EXISTS idx_email_bill_config_user_id ON t_email_bill_config (user_id);;
CREATE TABLE IF NOT EXISTS t_email_bill_sender_rule (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
updated_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
user_id INTEGER NOT NULL,
name VARCHAR(50) NOT NULL,
enabled INTEGER NOT NULL DEFAULT 1,
sort_order INTEGER NOT NULL DEFAULT 0,
sender_patterns TEXT,
bill_type VARCHAR(20) NOT NULL,
archive_password VARCHAR(100),
expense_category_id INTEGER,
income_category_id INTEGER,
tag_ids TEXT
);;
CREATE INDEX IF NOT EXISTS idx_email_bill_sender_rule_user_id ON t_email_bill_sender_rule (user_id);;
CREATE TABLE IF NOT EXISTS t_email_bill_message (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
updated_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
config_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
uid INTEGER NOT NULL,
message_id VARCHAR(255),
subject VARCHAR(500),
from_address VARCHAR(255),
received_at DATETIME,
attachment_index INTEGER NOT NULL DEFAULT 0,
attachment_name VARCHAR(255),
attachment_path VARCHAR(500),
sender_rule_id INTEGER,
bill_type VARCHAR(20),
status VARCHAR(20) NOT NULL DEFAULT 'PULLED',
error VARCHAR(500)
);;
CREATE UNIQUE INDEX IF NOT EXISTS uk_email_bill_message ON t_email_bill_message (config_id, uid, attachment_index);;
CREATE INDEX IF NOT EXISTS idx_email_bill_message_user_status ON t_email_bill_message (user_id, status);;
CREATE TRIGGER IF NOT EXISTS trg_email_bill_config_updated_at
AFTER UPDATE ON t_email_bill_config
FOR EACH ROW
WHEN NEW.updated_at = OLD.updated_at
BEGIN
UPDATE t_email_bill_config SET updated_at = strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime') WHERE id = OLD.id;
END;;
CREATE TRIGGER IF NOT EXISTS trg_email_bill_sender_rule_updated_at
AFTER UPDATE ON t_email_bill_sender_rule
FOR EACH ROW
WHEN NEW.updated_at = OLD.updated_at
BEGIN
UPDATE t_email_bill_sender_rule SET updated_at = strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime') WHERE id = OLD.id;
END;;
CREATE TRIGGER IF NOT EXISTS trg_email_bill_message_updated_at
AFTER UPDATE ON t_email_bill_message
FOR EACH ROW
WHEN NEW.updated_at = OLD.updated_at
BEGIN
UPDATE t_email_bill_message SET updated_at = strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime') WHERE id = OLD.id;
END;;