feat: 添加公告功能
This commit is contained in:
184
database.py
184
database.py
@@ -54,7 +54,7 @@ config = get_config()
|
||||
DB_FILE = config.DB_FILE
|
||||
|
||||
# 数据库版本 (用于迁移管理)
|
||||
DB_VERSION = 5
|
||||
DB_VERSION = 6
|
||||
|
||||
# ==================== 时区处理工具函数 ====================
|
||||
# Bug fix: 统一时区处理,避免混用导致的问题
|
||||
@@ -219,6 +219,30 @@ def init_database():
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
|
||||
)
|
||||
''')
|
||||
|
||||
# 公告表
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS announcements (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
is_active INTEGER DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
''')
|
||||
|
||||
# 公告永久关闭记录表(用户维度)
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS announcement_dismissals (
|
||||
user_id INTEGER NOT NULL,
|
||||
announcement_id INTEGER NOT NULL,
|
||||
dismissed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (user_id, announcement_id),
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (announcement_id) REFERENCES announcements (id) ON DELETE CASCADE
|
||||
)
|
||||
''')
|
||||
# 用户定时任务表
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS user_schedules (
|
||||
@@ -263,6 +287,11 @@ def init_database():
|
||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_bug_feedbacks_user_id ON bug_feedbacks(user_id)')
|
||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_bug_feedbacks_status ON bug_feedbacks(status)')
|
||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_bug_feedbacks_created_at ON bug_feedbacks(created_at)')
|
||||
|
||||
# 公告表索引
|
||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_announcements_active ON announcements(is_active)')
|
||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_announcements_created_at ON announcements(created_at)')
|
||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_announcement_dismissals_user ON announcement_dismissals(user_id)')
|
||||
# 用户定时任务表索引
|
||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_user_schedules_user_id ON user_schedules(user_id)')
|
||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_user_schedules_enabled ON user_schedules(enabled)')
|
||||
@@ -346,6 +375,10 @@ def migrate_database():
|
||||
_migrate_to_v5(conn)
|
||||
current_version = 5
|
||||
|
||||
if current_version < 6:
|
||||
_migrate_to_v6(conn)
|
||||
current_version = 6
|
||||
|
||||
# 更新版本号
|
||||
cursor.execute('UPDATE db_version SET version = ?, updated_at = CURRENT_TIMESTAMP WHERE id = 1',
|
||||
(DB_VERSION,))
|
||||
@@ -514,6 +547,46 @@ def _migrate_to_v5(conn):
|
||||
conn.commit()
|
||||
|
||||
|
||||
def _migrate_to_v6(conn):
|
||||
"""迁移到版本6 - 添加公告功能相关表"""
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='announcements'")
|
||||
if not cursor.fetchone():
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS announcements (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
is_active INTEGER DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
''')
|
||||
print(" ✓ 创建 announcements 表 (公告)")
|
||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_announcements_active ON announcements(is_active)')
|
||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_announcements_created_at ON announcements(created_at)')
|
||||
print(" ✓ 创建 announcements 表索引")
|
||||
|
||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='announcement_dismissals'")
|
||||
if not cursor.fetchone():
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS announcement_dismissals (
|
||||
user_id INTEGER NOT NULL,
|
||||
announcement_id INTEGER NOT NULL,
|
||||
dismissed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (user_id, announcement_id),
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (announcement_id) REFERENCES announcements (id) ON DELETE CASCADE
|
||||
)
|
||||
''')
|
||||
print(" ✓ 创建 announcement_dismissals 表 (公告永久关闭记录)")
|
||||
cursor.execute('CREATE INDEX IF NOT EXISTS idx_announcement_dismissals_user ON announcement_dismissals(user_id)')
|
||||
print(" ✓ 创建 announcement_dismissals 表索引")
|
||||
|
||||
conn.commit()
|
||||
|
||||
|
||||
# ==================== 管理员相关 ====================
|
||||
|
||||
def ensure_default_admin():
|
||||
@@ -1713,6 +1786,115 @@ def get_feedback_stats():
|
||||
return dict(row) if row else {'total': 0, 'pending': 0, 'replied': 0, 'closed': 0}
|
||||
|
||||
|
||||
# ==================== 公告管理 ====================
|
||||
|
||||
def create_announcement(title, content, is_active=True):
|
||||
"""创建公告(默认启用;启用时会自动停用其他公告)"""
|
||||
title = (title or '').strip()
|
||||
content = (content or '').strip()
|
||||
if not title or not content:
|
||||
return None
|
||||
|
||||
with db_pool.get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
cst_time = get_cst_now_str()
|
||||
|
||||
if is_active:
|
||||
cursor.execute('UPDATE announcements SET is_active = 0, updated_at = ? WHERE is_active = 1', (cst_time,))
|
||||
|
||||
cursor.execute('''
|
||||
INSERT INTO announcements (title, content, is_active, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
''', (title, content, 1 if is_active else 0, cst_time, cst_time))
|
||||
conn.commit()
|
||||
return cursor.lastrowid
|
||||
|
||||
|
||||
def get_announcement_by_id(announcement_id):
|
||||
"""根据ID获取公告"""
|
||||
with db_pool.get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('SELECT * FROM announcements WHERE id = ?', (announcement_id,))
|
||||
row = cursor.fetchone()
|
||||
return dict(row) if row else None
|
||||
|
||||
|
||||
def get_announcements(limit=50, offset=0):
|
||||
"""获取公告列表(管理员用)"""
|
||||
with db_pool.get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
SELECT * FROM announcements
|
||||
ORDER BY created_at DESC, id DESC
|
||||
LIMIT ? OFFSET ?
|
||||
''', (limit, offset))
|
||||
return [dict(row) for row in cursor.fetchall()]
|
||||
|
||||
|
||||
def set_announcement_active(announcement_id, is_active):
|
||||
"""启用/停用公告;启用时会自动停用其他公告"""
|
||||
with db_pool.get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
cst_time = get_cst_now_str()
|
||||
|
||||
if is_active:
|
||||
cursor.execute('UPDATE announcements SET is_active = 0, updated_at = ? WHERE is_active = 1', (cst_time,))
|
||||
cursor.execute('''
|
||||
UPDATE announcements
|
||||
SET is_active = 1, updated_at = ?
|
||||
WHERE id = ?
|
||||
''', (cst_time, announcement_id))
|
||||
else:
|
||||
cursor.execute('''
|
||||
UPDATE announcements
|
||||
SET is_active = 0, updated_at = ?
|
||||
WHERE id = ?
|
||||
''', (cst_time, announcement_id))
|
||||
|
||||
conn.commit()
|
||||
return cursor.rowcount > 0
|
||||
|
||||
|
||||
def delete_announcement(announcement_id):
|
||||
"""删除公告(同时清理用户关闭记录)"""
|
||||
with db_pool.get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('DELETE FROM announcement_dismissals WHERE announcement_id = ?', (announcement_id,))
|
||||
cursor.execute('DELETE FROM announcements WHERE id = ?', (announcement_id,))
|
||||
conn.commit()
|
||||
return cursor.rowcount > 0
|
||||
|
||||
|
||||
def get_active_announcement_for_user(user_id):
|
||||
"""获取当前用户应展示的启用公告(已永久关闭的不再返回)"""
|
||||
with db_pool.get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
SELECT a.*
|
||||
FROM announcements a
|
||||
LEFT JOIN announcement_dismissals d
|
||||
ON d.announcement_id = a.id AND d.user_id = ?
|
||||
WHERE a.is_active = 1 AND d.announcement_id IS NULL
|
||||
ORDER BY a.created_at DESC, a.id DESC
|
||||
LIMIT 1
|
||||
''', (user_id,))
|
||||
row = cursor.fetchone()
|
||||
return dict(row) if row else None
|
||||
|
||||
|
||||
def dismiss_announcement_for_user(user_id, announcement_id):
|
||||
"""用户永久关闭某条公告(幂等)"""
|
||||
with db_pool.get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
cst_time = get_cst_now_str()
|
||||
cursor.execute('''
|
||||
INSERT OR IGNORE INTO announcement_dismissals (user_id, announcement_id, dismissed_at)
|
||||
VALUES (?, ?, ?)
|
||||
''', (user_id, announcement_id, cst_time))
|
||||
conn.commit()
|
||||
return cursor.rowcount >= 0
|
||||
|
||||
|
||||
# ==================== 用户定时任务管理 ====================
|
||||
|
||||
def get_user_schedules(user_id):
|
||||
|
||||
Reference in New Issue
Block a user