-- ============================================================ -- nl-pms-api / code-count 共用库初始化脚本(MySQL 5.7+ / 8.x) -- 用法:mysql -u root -p < init.sql -- 合并自 view/init.sql(业务同步表)+ pms_files(文件元数据)。 -- 生产环境服务启动不执行任何建表/迁移(DDL),部署前必须先执行本脚本; -- 仅 dev 环境(config.yaml 里 env: dev)启动时才 AutoMigrate。 -- 默认账号:liqi / qiqi991012(bcrypt 哈希存储,可在应用内注册新账号) -- ============================================================ CREATE DATABASE IF NOT EXISTS code_count DEFAULT CHARSET utf8mb4; USE code_count; -- 应用账号(密码为 bcrypt 哈希) CREATE TABLE IF NOT EXISTS users( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID', username VARCHAR(64) NOT NULL UNIQUE COMMENT '登录名', password_hash VARCHAR(100) NOT NULL COMMENT 'bcrypt 密码哈希', totp_secret VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'TOTP Base32 密钥(仅管理员)', totp_enabled TINYINT NOT NULL DEFAULT 0 COMMENT '是否已启用 TOTP', last_login_ip VARCHAR(64) NOT NULL DEFAULT '' COMMENT '最近登录 IP', last_seen_at VARCHAR(32) NOT NULL DEFAULT '' COMMENT '最近活跃时间 RFC3339', ai_banned TINYINT NOT NULL DEFAULT 0 COMMENT '禁止使用 AI', disabled TINYINT NOT NULL DEFAULT 0 COMMENT '禁用账号', created_at VARCHAR(32) NOT NULL COMMENT '注册时间(RFC3339)' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='应用账号'; -- Todo 同步表(按 user_id 隔离,LWW 以 updated_at 判定) -- history 为生命周期轨迹 JSON:[{"status":"open","at":"..."},...],记录每次进入某状态的时间 -- team_id>0 表示该条已共享给对应团队(团队管理员可见),0 为私密 CREATE TABLE IF NOT EXISTS sync_todos( user_id BIGINT NOT NULL COMMENT '所属用户ID', uuid CHAR(36) NOT NULL COMMENT '客户端生成的全局唯一ID', title TEXT NOT NULL COMMENT '标题', content MEDIUMTEXT NOT NULL COMMENT '正文(Markdown,可含内嵌图片)', project_name VARCHAR(255) NOT NULL DEFAULT '' COMMENT '关联项目名(展示用)', due_at VARCHAR(32) NOT NULL DEFAULT '' COMMENT '截止时间', priority VARCHAR(16) NOT NULL DEFAULT 'medium' COMMENT '优先级:low/medium/high', status VARCHAR(16) NOT NULL DEFAULT 'open' COMMENT '状态:open/doing/done/cancelled', history MEDIUMTEXT NOT NULL COMMENT '生命周期轨迹 JSON', team_id BIGINT NOT NULL DEFAULT 0 COMMENT '共享团队ID,0=私密', created_at VARCHAR(32) NOT NULL DEFAULT '' COMMENT '创建时间', updated_at VARCHAR(32) NOT NULL COMMENT '最后更新时间(LWW)', deleted TINYINT NOT NULL DEFAULT 0 COMMENT '软删除标记:1=已删', PRIMARY KEY(user_id, uuid), KEY idx_sync_todos_updated(user_id, updated_at), KEY idx_sync_todos_team(team_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='待办同步表'; -- 工单同步表 CREATE TABLE IF NOT EXISTS sync_tickets( user_id BIGINT NOT NULL COMMENT '所属用户ID', uuid CHAR(36) NOT NULL COMMENT '客户端生成的全局唯一ID', title TEXT NOT NULL COMMENT '标题', description MEDIUMTEXT NOT NULL COMMENT '描述(Markdown)', type VARCHAR(16) NOT NULL DEFAULT 'task' COMMENT '类型:task/bug/feature 等', project_name VARCHAR(255) NOT NULL DEFAULT '' COMMENT '关联项目名', start_at VARCHAR(32) NOT NULL DEFAULT '' COMMENT '开始时间', due_at VARCHAR(32) NOT NULL DEFAULT '' COMMENT '截止时间', status VARCHAR(16) NOT NULL DEFAULT 'open' COMMENT '状态', priority VARCHAR(16) NOT NULL DEFAULT 'medium' COMMENT '优先级', history MEDIUMTEXT NOT NULL COMMENT '生命周期轨迹 JSON', team_id BIGINT NOT NULL DEFAULT 0 COMMENT '共享团队ID,0=私密', created_at VARCHAR(32) NOT NULL DEFAULT '' COMMENT '创建时间', updated_at VARCHAR(32) NOT NULL COMMENT '最后更新时间(LWW)', deleted TINYINT NOT NULL DEFAULT 0 COMMENT '软删除标记', PRIMARY KEY(user_id, uuid), KEY idx_sync_tickets_updated(user_id, updated_at), KEY idx_sync_tickets_team(team_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工单同步表'; -- 记事本同步表 CREATE TABLE IF NOT EXISTS sync_notes( user_id BIGINT NOT NULL COMMENT '所属用户ID', uuid CHAR(36) NOT NULL COMMENT '客户端生成的全局唯一ID', content MEDIUMTEXT NOT NULL COMMENT '记事本正文', updated_at VARCHAR(32) NOT NULL COMMENT '最后更新时间(LWW)', deleted TINYINT NOT NULL DEFAULT 0 COMMENT '软删除标记', PRIMARY KEY(user_id, uuid), KEY idx_sync_notes_updated(user_id, updated_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='记事本同步表'; -- 按用户存储的同步设置(如加密盐 enc_salt、加密后的 AI API Key api_keys)。 -- api_keys 的值为 AES-256-GCM 密文(密钥由登录密码派生),服务器无法解密。 -- 另有全局资源行:日历节日背景图存为 fest_img:<节日名>,统一挂在管理员账号 -- (id=1)名下 —— 管理员在应用内上传推送,所有账号登录后拉取展示。 CREATE TABLE IF NOT EXISTS sync_settings( user_id BIGINT NOT NULL COMMENT '所属用户ID(全局资源挂在管理员 id=1)', name VARCHAR(64) NOT NULL COMMENT '设置键名', value MEDIUMTEXT NOT NULL COMMENT '设置值(明文或密文)', updated_at VARCHAR(32) NOT NULL COMMENT '最后更新时间', PRIMARY KEY(user_id, name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户同步设置键值表'; -- 旧版本升级:早期脚本建的 content/description 为 TEXT(64KB), -- 待办/工单内容支持 Markdown 内嵌图片后需要 MEDIUMTEXT;重复执行无副作用。 ALTER TABLE sync_todos MODIFY content MEDIUMTEXT NOT NULL COMMENT '正文(Markdown,可含内嵌图片)'; ALTER TABLE sync_tickets MODIFY description MEDIUMTEXT NOT NULL COMMENT '描述(Markdown)'; -- 旧版本升级:补 history 生命周期列(不存在时才添加,重复执行无副作用)。 SET @sql = IF((SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='code_count' AND TABLE_NAME='sync_todos' AND COLUMN_NAME='history')=0, 'ALTER TABLE sync_todos ADD COLUMN history MEDIUMTEXT NOT NULL COMMENT ''生命周期轨迹 JSON'' AFTER status', 'SELECT 1'); PREPARE st FROM @sql; EXECUTE st; DEALLOCATE PREPARE st; SET @sql = IF((SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='code_count' AND TABLE_NAME='sync_tickets' AND COLUMN_NAME='history')=0, 'ALTER TABLE sync_tickets ADD COLUMN history MEDIUMTEXT NOT NULL COMMENT ''生命周期轨迹 JSON'' AFTER priority', 'SELECT 1'); PREPARE st FROM @sql; EXECUTE st; DEALLOCATE PREPARE st; -- ============================================================ -- 团队协作(v2.1):资料 / 团队 / 团队任务 / 日报 / 通知 -- ============================================================ -- 用户公开资料(同服务器用户互相可见:昵称/头衔/技术栈标签/头像缩略图) CREATE TABLE IF NOT EXISTS user_profiles( user_id BIGINT PRIMARY KEY COMMENT '对应用户ID', nickname VARCHAR(64) NOT NULL DEFAULT '' COMMENT '昵称', title VARCHAR(64) NOT NULL DEFAULT '' COMMENT '头衔/职位', email VARCHAR(128) NOT NULL DEFAULT '' COMMENT '公开邮箱', bio VARCHAR(500) NOT NULL DEFAULT '' COMMENT '个人简介', tech_tags VARCHAR(1000) NOT NULL DEFAULT '[]' COMMENT '技术栈标签 JSON 数组', avatar_thumb MEDIUMTEXT NOT NULL COMMENT '头像缩略图(dataURL 或 URL)', updated_at VARCHAR(32) NOT NULL COMMENT '最后更新时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户公开资料'; -- 用户头像历史(与账号关联,多端可回选;每人保留最近若干条) CREATE TABLE IF NOT EXISTS user_avatar_history( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '历史记录ID', user_id BIGINT NOT NULL COMMENT '所属用户ID', mode VARCHAR(16) NOT NULL DEFAULT 'url' COMMENT '存储模式:url | base64', value MEDIUMTEXT NOT NULL COMMENT '头像内容(URL 或 dataURL)', created_at VARCHAR(32) NOT NULL COMMENT '写入时间(RFC3339)', KEY idx_uah_user_id(user_id, id DESC) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户头像历史'; -- 团队(digest_time:日报 AI 摘要自动生成时间 HH:MM) CREATE TABLE IF NOT EXISTS teams( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '团队ID', name VARCHAR(64) NOT NULL COMMENT '团队名称', owner_id BIGINT NOT NULL COMMENT '创建者用户ID', digest_time VARCHAR(8) NOT NULL DEFAULT '21:00' COMMENT '日报 AI 摘要自动生成时间 HH:MM', ai_banned TINYINT NOT NULL DEFAULT 0 COMMENT '禁止团队使用 AI', created_at VARCHAR(32) NOT NULL COMMENT '创建时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='团队'; -- 团队成员(role: owner | admin | member) CREATE TABLE IF NOT EXISTS team_members( team_id BIGINT NOT NULL COMMENT '团队ID', user_id BIGINT NOT NULL COMMENT '成员用户ID', role VARCHAR(16) NOT NULL DEFAULT 'member' COMMENT '角色:owner/admin/member', joined_at VARCHAR(32) NOT NULL COMMENT '加入时间', PRIMARY KEY(team_id, user_id), KEY idx_team_members_user(user_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='团队成员'; -- 团队任务/工单(服务器唯一真相,在线操作;kind: todo | ticket) CREATE TABLE IF NOT EXISTS team_tasks( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '任务ID', team_id BIGINT NOT NULL COMMENT '所属团队ID', kind VARCHAR(16) NOT NULL DEFAULT 'todo' COMMENT '种类:todo/ticket', title TEXT NOT NULL COMMENT '标题', description MEDIUMTEXT NOT NULL COMMENT '描述', priority VARCHAR(16) NOT NULL DEFAULT 'medium' COMMENT '优先级', status VARCHAR(16) NOT NULL DEFAULT 'open' COMMENT '状态', creator_id BIGINT NOT NULL COMMENT '创建者用户ID', assignee_id BIGINT NOT NULL DEFAULT 0 COMMENT '指派人用户ID,0=未指派', start_at VARCHAR(32) NOT NULL DEFAULT '' COMMENT '开始时间', due_at VARCHAR(32) NOT NULL DEFAULT '' COMMENT '截止时间', urged_at VARCHAR(32) NOT NULL DEFAULT '' COMMENT '最近催办时间', history MEDIUMTEXT NOT NULL COMMENT '生命周期轨迹 JSON', updated_at VARCHAR(32) NOT NULL COMMENT '最后更新时间', deleted TINYINT NOT NULL DEFAULT 0 COMMENT '软删除标记', KEY idx_team_tasks_team(team_id, deleted), KEY idx_team_tasks_assignee(assignee_id, deleted) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='团队任务/工单'; -- 团队日报(date 为本地日期 YYYY-MM-DD) CREATE TABLE IF NOT EXISTS team_reports( team_id BIGINT NOT NULL COMMENT '团队ID', user_id BIGINT NOT NULL COMMENT '提交人用户ID', date CHAR(10) NOT NULL COMMENT '日报日期 YYYY-MM-DD', content MEDIUMTEXT NOT NULL COMMENT '日报正文(Markdown)', submitted_at VARCHAR(32) NOT NULL COMMENT '提交/更新时间', PRIMARY KEY(team_id, user_id, date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='团队日报'; -- 团队日报 AI 摘要(管理员客户端生成,全员可见) CREATE TABLE IF NOT EXISTS team_digests( team_id BIGINT NOT NULL COMMENT '团队ID', date CHAR(10) NOT NULL COMMENT '摘要对应日期 YYYY-MM-DD', content MEDIUMTEXT NOT NULL COMMENT 'AI 摘要正文(Markdown)', provider VARCHAR(32) NOT NULL DEFAULT '' COMMENT '生成所用 AI 提供商', generated_at VARCHAR(32) NOT NULL COMMENT '生成时间', PRIMARY KEY(team_id, date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='团队日报 AI 摘要'; -- 团队通知(指派/催办/成员变动等;客户端同步时按 to_user 增量拉取转本地消息) CREATE TABLE IF NOT EXISTS team_notices( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '通知ID', team_id BIGINT NOT NULL COMMENT '团队ID', to_user BIGINT NOT NULL COMMENT '接收用户ID', from_user BIGINT NOT NULL COMMENT '发送用户ID', kind VARCHAR(16) NOT NULL COMMENT '通知类型', ref_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '关联对象ID', content TEXT NOT NULL COMMENT '通知正文', created_at VARCHAR(32) NOT NULL COMMENT '创建时间', KEY idx_team_notices_to(to_user, id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='团队通知'; -- 旧版本升级:个人待办/工单支持按条共享到团队(team_id=0 私密;重复执行无副作用)。 SET @sql = IF((SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='code_count' AND TABLE_NAME='sync_todos' AND COLUMN_NAME='team_id')=0, 'ALTER TABLE sync_todos ADD COLUMN team_id BIGINT NOT NULL DEFAULT 0 COMMENT ''共享团队ID,0=私密'' AFTER history', 'SELECT 1'); PREPARE st FROM @sql; EXECUTE st; DEALLOCATE PREPARE st; SET @sql = IF((SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='code_count' AND TABLE_NAME='sync_tickets' AND COLUMN_NAME='team_id')=0, 'ALTER TABLE sync_tickets ADD COLUMN team_id BIGINT NOT NULL DEFAULT 0 COMMENT ''共享团队ID,0=私密'' AFTER history', 'SELECT 1'); PREPARE st FROM @sql; EXECUTE st; DEALLOCATE PREPARE st; SET @sql = IF((SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA='code_count' AND TABLE_NAME='sync_todos' AND INDEX_NAME='idx_sync_todos_team')=0, 'ALTER TABLE sync_todos ADD KEY idx_sync_todos_team(team_id)', 'SELECT 1'); PREPARE st FROM @sql; EXECUTE st; DEALLOCATE PREPARE st; SET @sql = IF((SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA='code_count' AND TABLE_NAME='sync_tickets' AND INDEX_NAME='idx_sync_tickets_team')=0, 'ALTER TABLE sync_tickets ADD KEY idx_sync_tickets_team(team_id)', 'SELECT 1'); PREPARE st FROM @sql; EXECUTE st; DEALLOCATE PREPARE st; -- 默认账号 liqi(密码 qiqi991012 的 bcrypt 哈希);已存在则跳过,不会覆盖改过的密码 INSERT IGNORE INTO users(username, password_hash, created_at) VALUES ('liqi', '$2a$10$XWBtGPu9xYRyr8diFEfvBeEHRkO6pa3CDduE09OurJztuXVcJZOB2', '2026-08-11T13:20:00Z'); -- ============================================================ -- 文件存储元数据(nl-pms-api) -- ============================================================ -- 已上传文件元数据(文件内容落盘在 storage_dir,本表只存相对路径与摘要) -- 归属按 (user_id, team_id) 记录:素材库据此划定"本人 / 团队管理员 / 超管 id=1"的管理范围。 CREATE TABLE IF NOT EXISTS pms_files( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(191) NOT NULL, -- 存储相对路径,如 2026/08/13/<随机hex>.jpg original VARCHAR(255) NOT NULL DEFAULT '', -- 原始文件名(仅备查) mime VARCHAR(64) NOT NULL DEFAULT '', size BIGINT NOT NULL DEFAULT 0, sha256 CHAR(64) NOT NULL DEFAULT '', -- 内容摘要,同归属秒传去重 user_id BIGINT NOT NULL DEFAULT 0, -- 上传者(users 表 id,0 为未知) team_id BIGINT NOT NULL DEFAULT 0, -- 上传时所在团队(teams 表 id,0 为个人) kind VARCHAR(16) NOT NULL DEFAULT '', -- avatar | content created_at VARCHAR(32) NOT NULL DEFAULT '', UNIQUE KEY idx_pms_files_name(name), KEY idx_pms_files_sha256(sha256), KEY idx_pms_files_user_id(user_id), KEY idx_pms_files_team_id(team_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='已上传文件元数据'; -- 旧版本升级:补 team_id 归属列与 user_id/team_id 索引(不存在才添加,重复执行无副作用)。 SET @sql = IF((SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='code_count' AND TABLE_NAME='pms_files' AND COLUMN_NAME='team_id')=0, 'ALTER TABLE pms_files ADD COLUMN team_id BIGINT NOT NULL DEFAULT 0 AFTER user_id', 'SELECT 1'); PREPARE st FROM @sql; EXECUTE st; DEALLOCATE PREPARE st; SET @sql = IF((SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA='code_count' AND TABLE_NAME='pms_files' AND INDEX_NAME='idx_pms_files_user_id')=0, 'ALTER TABLE pms_files ADD KEY idx_pms_files_user_id(user_id)', 'SELECT 1'); PREPARE st FROM @sql; EXECUTE st; DEALLOCATE PREPARE st; SET @sql = IF((SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA='code_count' AND TABLE_NAME='pms_files' AND INDEX_NAME='idx_pms_files_team_id')=0, 'ALTER TABLE pms_files ADD KEY idx_pms_files_team_id(team_id)', 'SELECT 1'); PREPARE st FROM @sql; EXECUTE st; DEALLOCATE PREPARE st; -- ============================================================ -- 管理员后台 / TOTP / 日活 / AI 用量 / 发版(v2.2) -- ============================================================ SET @sql = IF((SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='code_count' AND TABLE_NAME='users' AND COLUMN_NAME='totp_secret')=0, 'ALTER TABLE users ADD COLUMN totp_secret VARCHAR(64) NOT NULL DEFAULT '''' COMMENT ''TOTP Base32 密钥(仅管理员)'' AFTER password_hash', 'SELECT 1'); PREPARE st FROM @sql; EXECUTE st; DEALLOCATE PREPARE st; SET @sql = IF((SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='code_count' AND TABLE_NAME='users' AND COLUMN_NAME='totp_enabled')=0, 'ALTER TABLE users ADD COLUMN totp_enabled TINYINT NOT NULL DEFAULT 0 COMMENT ''是否已启用 TOTP'' AFTER totp_secret', 'SELECT 1'); PREPARE st FROM @sql; EXECUTE st; DEALLOCATE PREPARE st; SET @sql = IF((SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='code_count' AND TABLE_NAME='users' AND COLUMN_NAME='last_login_ip')=0, 'ALTER TABLE users ADD COLUMN last_login_ip VARCHAR(64) NOT NULL DEFAULT '''' COMMENT ''最近登录 IP'' AFTER totp_enabled', 'SELECT 1'); PREPARE st FROM @sql; EXECUTE st; DEALLOCATE PREPARE st; SET @sql = IF((SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='code_count' AND TABLE_NAME='users' AND COLUMN_NAME='last_seen_at')=0, 'ALTER TABLE users ADD COLUMN last_seen_at VARCHAR(32) NOT NULL DEFAULT '''' COMMENT ''最近活跃时间 RFC3339'' AFTER last_login_ip', 'SELECT 1'); PREPARE st FROM @sql; EXECUTE st; DEALLOCATE PREPARE st; SET @sql = IF((SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='code_count' AND TABLE_NAME='users' AND COLUMN_NAME='ai_banned')=0, 'ALTER TABLE users ADD COLUMN ai_banned TINYINT NOT NULL DEFAULT 0 COMMENT ''禁止使用 AI'' AFTER last_seen_at', 'SELECT 1'); PREPARE st FROM @sql; EXECUTE st; DEALLOCATE PREPARE st; SET @sql = IF((SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='code_count' AND TABLE_NAME='users' AND COLUMN_NAME='disabled')=0, 'ALTER TABLE users ADD COLUMN disabled TINYINT NOT NULL DEFAULT 0 COMMENT ''禁用账号'' AFTER ai_banned', 'SELECT 1'); PREPARE st FROM @sql; EXECUTE st; DEALLOCATE PREPARE st; SET @sql = IF((SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='code_count' AND TABLE_NAME='teams' AND COLUMN_NAME='ai_banned')=0, 'ALTER TABLE teams ADD COLUMN ai_banned TINYINT NOT NULL DEFAULT 0 COMMENT ''禁止团队使用 AI'' AFTER digest_time', 'SELECT 1'); PREPARE st FROM @sql; EXECUTE st; DEALLOCATE PREPARE st; CREATE TABLE IF NOT EXISTS user_daily_active( user_id BIGINT NOT NULL COMMENT '用户ID', active_date CHAR(10) NOT NULL COMMENT '活跃日期 YYYY-MM-DD', last_ip VARCHAR(64) NOT NULL DEFAULT '' COMMENT '当日最近 IP', last_seen_at VARCHAR(32) NOT NULL DEFAULT '' COMMENT '当日最近活跃时间', PRIMARY KEY(user_id, active_date), KEY idx_uda_date(active_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户日活'; CREATE TABLE IF NOT EXISTS ai_usage_daily( user_id BIGINT NOT NULL COMMENT '用户ID', team_id BIGINT NOT NULL DEFAULT 0 COMMENT '团队ID,0=个人', usage_date CHAR(10) NOT NULL COMMENT '用量日期 YYYY-MM-DD', provider VARCHAR(32) NOT NULL DEFAULT '' COMMENT 'AI 提供商', prompt_tokens BIGINT NOT NULL DEFAULT 0 COMMENT '提示 token', completion_tokens BIGINT NOT NULL DEFAULT 0 COMMENT '补全 token', calls INT NOT NULL DEFAULT 0 COMMENT '调用次数', estimated TINYINT NOT NULL DEFAULT 0 COMMENT '是否含估算数据', PRIMARY KEY(user_id, team_id, usage_date, provider), KEY idx_aud_date(usage_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI Token 日汇总'; CREATE TABLE IF NOT EXISTS app_releases( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '发版ID', version VARCHAR(32) NOT NULL COMMENT '版本号 semver', channel VARCHAR(16) NOT NULL DEFAULT 'stable' COMMENT '渠道', filename VARCHAR(255) NOT NULL COMMENT '存储相对路径', sha256 CHAR(64) NOT NULL DEFAULT '' COMMENT '文件 SHA256', size_bytes BIGINT NOT NULL DEFAULT 0 COMMENT '字节大小', changelog MEDIUMTEXT NOT NULL COMMENT '更新说明', created_at VARCHAR(32) NOT NULL COMMENT '上传时间', is_latest TINYINT NOT NULL DEFAULT 0 COMMENT '是否当前最新', UNIQUE KEY idx_app_releases_ver_ch(version, channel), KEY idx_app_releases_latest(channel, is_latest) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户端发版'; CREATE TABLE IF NOT EXISTS admin_stepup( user_id BIGINT PRIMARY KEY COMMENT '管理员用户ID', token_hash CHAR(64) NOT NULL COMMENT 'stepup token SHA256', client_ip VARCHAR(64) NOT NULL DEFAULT '' COMMENT '签发时 IP', expires_at VARCHAR(32) NOT NULL COMMENT '过期时间 RFC3339' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理员敏感操作二次验证';