🤔 feat(SQLite): 尝试另一种实现

This commit is contained in:
BTMuli
2023-04-25 14:37:07 +08:00
parent 6b3b9748cd
commit cc42d1fcb3
3 changed files with 558 additions and 0 deletions

23
src/types/BBS.d.ts vendored Normal file
View File

@@ -0,0 +1,23 @@
/**
* @file types BBS.d.ts
* @description 米游社BBS相关类型定义
* @auther BTMuli<bt-muli@outlook.com>
* @since Alpha v0.1.4
*/
declare namespace BTMuli.SQLite {
/**
* @description BBS帖子数据
* @interface BBSPost
* @since Alpha v0.1.4
* @property {number} id 帖子ID
* @property {created} created 帖子创建时间
* @property {modified} modified 帖子修改时间
* @return BBSPost
*/
export interface BBSPost {
id: number
created: string
modified: string
}
}

350
src/utils/TGSql.ts Normal file
View File

@@ -0,0 +1,350 @@
/**
* @file utils TGSql.ts
* @description 数据库sql语句
* @auther BTMuli<bt-muli@outlook.com>
* @since Alpha v0.1.4
*/
import { app } from "@tauri-apps/api";
import { getBuildTime } from "./TGBuild";
import { TGAppData } from "../data";
const TGSql = {
initTable: {
all: initSQLiteTable(),
app: initAppTable(),
achievement: initAchievementTable(),
achievementSeries: initAchievementSeriesTable(),
bbsPost: initBbsPostTable(),
},
initData: {
all: initSQLiteData(),
app: initAppData(),
achievement: initAchievementData(),
achievementSeries: initAchievementSeriesData(),
},
insert: {
achievement: insertAchievementData,
achievementSeries: insertAchievementSeriesData,
bbsPost: insertBBSPostData,
},
update: {
achievement: updateAchievementData,
bbsPost: updateBBSPostData,
},
};
/**
* @description 初始化应用数据表
* @since Alpha v0.1.4
* @returns {string[]} sql
*/
function initAppTable (): string[] {
const sqlRes = [];
// 创建应用数据表
sqlRes.push(`
CREATE TABLE IF NOT EXISTS AppData
(
key TEXT PRIMARY KEY,
value TEXT DEFAULT NULL,
updated TEXT DEFAULT NULL
);
`);
// 创建触发器
sqlRes.push(`
CREATE TRIGGER IF NOT EXISTS updateAppData
AFTER UPDATE OF value ON AppData
FOR EACH ROW
BEGIN
UPDATE BBSPost SET isRead = 1, updated = datetime('now', 'localtime')
WHERE created < NEW.value OR modified < NEW.value AND NEW.key = 'bbsPost';
END;
`);
return sqlRes;
}
/**
* @description 初始化成就系列数据表
* @since Alpha v0.1.4
* @returns {string[]} sql
*/
function initAchievementSeriesTable (): string[] {
const sqlRes = [];
// 创建成就系列数据表
sqlRes.push(`
CREATE TABLE IF NOT EXISTS AchievementSeries
(
id INTEGER PRIMARY KEY,
"order" INTEGER,
name TEXT DEFAULT NULL,
version TEXT DEFAULT NULL,
totalCount INTEGER DEFAULT 0,
finCount INTEGER DEFAULT 0,
icon TEXT NOT NULL,
nameCard TEXT DEFAULT NULL,
updated TEXT DEFAULT NULL
);
`);
return sqlRes;
}
/**
* @description 初始化成就数据表
* @since Alpha v0.1.4
* @returns {string[]} sql
*/
function initAchievementTable (): string[] {
const sqlRes = [];
// 创建成就数据表
sqlRes.push(`
CREATE TABLE IF NOT EXISTS Achievements
(
id INTEGER PRIMARY KEY,
series INTEGER,
"order" INTEGER,
name TEXT DEFAULT NULL,
description TEXT DEFAULT NULL,
reward INTEGER DEFAULT 0,
isCompleted BOOLEAN DEFAULT 0,
completedTime TEXT DEFAULT NULL,
progress INTEGER DEFAULT 0,
version TEXT DEFAULT NULL,
updated TEXT DEFAULT NULL
);
`);
// 创建触发器
sqlRes.push(`
CREATE TRIGGER IF NOT EXISTS updateAchievement
AFTER UPDATE ON Achievements
FOR EACH ROW
BEGIN
UPDATE AchievementSeries SET finCount = finCount + 1, updated = datetime('now', 'localtime')
WHERE id = NEW.series AND NEW.isCompleted = 1 AND OLD.isCompleted = 0;
UPDATE AchievementSeries SET finCount = finCount - 1, updated = datetime('now', 'localtime')
WHERE id = NEW.series AND NEW.isCompleted = 0 AND OLD.isCompleted = 1;
END;
`);
sqlRes.push(`
CREATE TRIGGER IF NOT EXISTS insertAchievement
AFTER INSERT ON Achievements
FOR EACH ROW
BEGIN
UPDATE AchievementSeries SET totalCount = totalCount + 1, updated = datetime('now', 'localtime')
WHERE id = NEW.series;
END;
`);
return sqlRes;
}
/**
* @description 初始化米游社帖子数据表
* @since Alpha v0.1.4
* @returns {string[]} sql
*/
function initBbsPostTable (): string[] {
const sqlRes = [];
// 创建米游社帖子数据表
sqlRes.push(`
CREATE TABLE IF NOT EXISTS BBSPost
(
id INTEGER PRIMARY KEY,
created TEXT DEFAULT NULL,
modified TEXT DEFAULT NULL,
isRead BOOLEAN DEFAULT 0,
updated TEXT DEFAULT NULL,
);
`);
return sqlRes;
}
/**
* @description 初始化数据库表
* @since Alpha v0.1.4
* @returns {string[]} sql
*/
function initSQLiteTable (): string[] {
const sqlRes = [];
sqlRes.push(...initAppTable());
sqlRes.push(...initAchievementSeriesTable());
sqlRes.push(...initAchievementTable());
sqlRes.push(...initBbsPostTable());
return sqlRes;
}
/**
* @description 初始化应用数据
* @since Alpha v0.1.4
* @returns {Promise<string[]>} sql
*/
async function initAppData (): Promise<string[]> {
const sqlRes = [];
const appVersion = await app.getVersion();
const buildTime = getBuildTime();
const dataUpdated = buildTime.startsWith("dev") ? buildTime.slice(4) : buildTime;
// 初始化应用版本
sqlRes.push(`
INSERT INTO AppData (key, value, updated)
VALUES ('appVersion', '${appVersion}', datetime('now', 'localtime'));
`);
// 初始化应用数据更新时间
sqlRes.push(`
INSERT INTO AppData (key, value, updated)
VALUES ('dataUpdated', '${dataUpdated}', datetime('now', 'localtime'));
`);
// 初始化米游社帖子数据更新时间
sqlRes.push(`
INSERT INTO AppData (key, value, updated)
VALUES ('bbsPostLine', datetime('now', 'localtime'), datetime('now', 'localtime'));
`);
return sqlRes;
}
/**
* @description 初始化成就系列数据
* @since Alpha v0.1.4
* @returns {string[]} sql
*/
function initAchievementSeriesData (): string[] {
const sqlRes: string[] = [];
const oriData = TGAppData.achievementSeries;
Object.values(oriData).map((series) => {
const sql = insertAchievementSeriesData(series);
return sqlRes.push(sql);
});
return sqlRes;
}
/**
* @description 初始化成就数据
* @since Alpha v0.1.4
* @returns {string[]} sql
*/
function initAchievementData (): string[] {
const sqlRes: string[] = [];
const oriData = TGAppData.achievements;
Object.values(oriData).map((achievement) => {
const sql = insertAchievementData(achievement);
return sqlRes.push(sql);
});
return sqlRes;
}
/**
* @description 初始化数据库数据
* @since Alpha v0.1.4
* @returns {Promise<string[]>} sql
*/
async function initSQLiteData (): Promise<string[]> {
const sqlRes = [];
sqlRes.push(...initAchievementSeriesData());
sqlRes.push(...initAchievementData());
sqlRes.push(...await initAppData());
return sqlRes;
}
/**
* @description 插入数据-成就系列
* @since Alpha v0.1.4
* @param {BTMuli.Genshin.AchievementSeries} data
* @returns {string} sql
*/
function insertAchievementSeriesData (data: BTMuli.Genshin.AchievementSeries): string {
let sql;
if (data.card) {
sql = `
INSERT INTO AchievementSeries (id, "order", name, version, icon, nameCard, updated)
VALUES (${data.id}, ${data.order}, '${data.name}', '${data.version}', '${data.icon}', '${data.card}', datetime('now', 'localtime'));
`;
} else {
sql = `
INSERT INTO AchievementSeries (id, "order", name, version, icon, updated)
VALUES (${data.id}, ${data.order}, '${data.name}', '${data.version}', '${data.icon}', datetime('now', 'localtime'));
`;
}
return sql;
}
/**
* @description 插入数据-成就
* @since Alpha v0.1.4
* @param {BTMuli.Genshin.Achievement} data
* @returns {string} sql
*/
function insertAchievementData (data: BTMuli.Genshin.Achievement): string {
const sql = `
INSERT INTO Achievements (id, series, "order", name, description, reward, version, updated)
VALUES (${data.id}, ${data.series}, ${data.order}, '${data.name}', '${data.description}', ${data.reward}, '${data.version}', datetime('now', 'localtime'));
`;
return sql;
}
/**
* @description 插入数据-米游社帖子
* @since Alpha v0.1.4
* @param {BTMuli.SQLite.BBSPost} data
* @param {string} bbsPostLine 米游社帖子数据更新时间
* @returns {string} sql
*/
function insertBBSPostData (data: BTMuli.SQLite.BBSPost, bbsPostLine: string): string {
// 将 bbsPostLine 与 data.created 跟 data.modified 比较,取最新的时间
const isRead = data.created <= bbsPostLine && data.modified <= bbsPostLine;
const sql = `
INSERT INTO BBSPost (id, created, modified, isRead, updated)
VALUES (${data.id}, '${data.created}', '${data.modified}', ${isRead ? 1 : 0}, datetime('now', 'localtime'))
ON CONFLICT(id) DO UPDATE
SET modified = '${data.modified}' WHERE id = ${data.id} AND modified != '${data.modified}';
`;
return sql;
}
/**
* @description 更新数据-成就
* @since Alpha v0.1.4
* @param {BTMuli.Genshin.Achievement} data
* @returns {string} sql
*/
function updateAchievementData (data: BTMuli.Genshin.Achievement): string {
let sql;
const isCompleted = data.completed ? 1 : 0;
if (data.completed && data.completed_time) {
sql = `
UPDATE Achievements
SET completed = ${isCompleted}, completedTime = '${data.completed_time}', progress = ${data.progress}, updated = datetime('now', 'localtime')
WHERE id = ${data.id};
`;
} else {
sql = `
UPDATE Achievements
SET completed = ${isCompleted}, progress = ${data.progress}, updated = datetime('now', 'localtime')
WHERE id = ${data.id};
`;
}
return sql;
}
/**
* @description 更新数据-米游社帖子
* @since Alpha v0.1.4
* @param {BTMuli.SQLite.BBSPost} data
* @param {boolean} isRead 是否已读
* @returns {string} sql
*/
function updateBBSPostData (data: BTMuli.SQLite.BBSPost, isRead: boolean = false): string {
let sql;
if (isRead) {
sql = `
UPDATE BBSPost
SET isRead = 1, updated = datetime('now', 'localtime')
WHERE id = ${data.id};
`;
} else {
sql = `
UPDATE BBSPost
SET modified = '${data.modified}', isRead = 0, updated = datetime('now', 'localtime')
WHERE id = ${data.id} AND OLD.modified != '${data.modified}';
`;
}
return sql;
}
export default TGSql;

185
src/utils/TGSqlite.ts Normal file
View File

@@ -0,0 +1,185 @@
/**
* @description 数据库操作类
* @class TGSqlite
* @author BTMuli<bt-muli@outlook.com>
* @since Alpha v0.1.4
*/
import Database from "tauri-plugin-sql-api";
import TGSql from "./TGSql";
class TGSqlite {
/**
* @description 数据库地址
* @private
* @type {string}
* @memberof TGSqlite
* @since Alpha v0.1.4
*/
private readonly dbPath: string = "sqlite:tauri-genshin.db";
/**
* @description 数据库实例
* @private
* @type {Database}
* @memberof TGSqlite
* @since Alpha v0.1.4
*/
private readonly db: Database;
/**
* @description 数据库包含的表
* @private
* @type {string[]}
* @memberof TGSqlite
* @since Alpha v0.1.4
*/
private readonly tables: string[] = [
"AppData",
"Achievements",
"AchievementSeries",
"BBSPost",
];
/**
* @description 构造函数
* @constructor
* @memberof TGSqlite
* @since Alpha v0.1.4
*/
constructor () {
this.db = new Database(this.dbPath);
}
/**
* @description 初始化数据库
* @memberof TGSqlite
* @since Alpha v0.1.4
* @returns {Promise<void>}
* @memberof TGSqlite
*/
public async init (): Promise<void> {
const sqlT = TGSql.initTable.all;
for (const item of sqlT) {
await this.db.execute(item);
}
const sqlD = await TGSql.initData.all;
for (const item of sqlD) {
await this.db.execute(item);
}
}
/**
* @description 检测数据库完整性
* @memberof TGSqlite
* @since Alpha v0.1.4
* @returns {Promise<boolean>}
*/
public async check (): Promise<boolean> {
// 检测数据表是否都存在
const sqlT = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;";
const res: Array<{ name: string }> = await this.db.select(sqlT);
if (res.length !== this.tables.length) return false;
for (const item of res) {
if (!this.tables.includes(item.name)) return false;
}
return true;
}
/**
* @description 重置数据库
* @memberof TGSqlite
* @since Alpha v0.1.4
* @returns {Promise<void>}
*/
public async reset (): Promise<void> {
const sql = "DROP TABLE IF EXISTS AppData, Achievement, AchievementSeries, BbsPost;";
await this.db.execute(sql);
await this.init();
}
/**
* @description 获取数据库版本及构建时间
* @memberof TGSqlite
* @since Alpha v0.1.4
* @returns {Promise<{ version: string, buildTime: string }>}
*/
public async getMetadata (): Promise<{ version: string, buildTime: string }> {
const sql = "SELECT * FROM AppData WHERE key='appVersion' OR key='dataUpdated';";
const res: Array<{ key: string, value: string }> = await this.db.select(sql);
const version = res.find((item) => item.key === "appVersion")?.value ?? "0.0.0";
const buildTime = res.find((item) => item.key === "dataUpdated")?.value ?? "1970-01-01 00:00:00";
return { version, buildTime };
}
/**
* @description 清除 BBSPost 数据
* @memberof TGSqlite
* @since Alpha v0.1.4
* @returns {Promise<void>}
*/
public async clearBbsPost (): Promise<void> {
const sql = [];
sql.push("DROP TABLE IF EXISTS BBSPost;");
sql.push(...TGSql.initTable.bbsPost);
sql.push("UPDATE AppData SET value=datetime('now', 'localtime') WHERE key='bbsPostLine';");
for (const item of sql) {
await this.db.execute(item);
}
}
/**
* @description 获取成就系列列表
* @memberof TGSqlite
* @param {number} [seriesId] 系列 ID
* @since Alpha v0.1.4
* @returns {Promise<BTMuli.SQLite.AchievementSeries[]>}
*/
public async getAchievementSeries (seriesId?: number): Promise<BTMuli.SQLite.AchievementSeries[]> {
let sql;
if (seriesId) {
sql = `SELECT * FROM AchievementSeries WHERE id=${seriesId};`;
} else {
sql = "SELECT * FROM AchievementSeries;";
}
const res: BTMuli.SQLite.AchievementSeries[] = await this.db.select(sql);
return res;
}
/**
* @description 获取成就列表
* @memberof TGSqlite
* @param {number} [seriesId] 系列 ID
* @since Alpha v0.1.4
* @returns {Promise<BTMuli.SQLite.Achievements[]>}
*/
public async getAchievements (seriesId?: number): Promise<BTMuli.SQLite.Achievements[]> {
let sql;
if (seriesId) {
sql = `SELECT * FROM Achievement WHERE seriesId=${seriesId};`;
} else {
sql = "SELECT * FROM Achievement;";
}
const res: BTMuli.SQLite.Achievements[] = await this.db.select(sql);
return res;
}
/**
* @description 查询成就
* @memberof TGSqlite
* @param {string} keyword 关键词
* @since Alpha v0.1.4
* @returns {Promise<BTMuli.SQLite.Achievements[]>}
*/
public async searchAchievements (keyword: string): Promise<BTMuli.SQLite.Achievements[]> {
let sql;
if (keyword.startsWith("v")) {
const version = keyword.replace("v", "");
sql = `SELECT * FROM Achievement WHERE version='${version}';`;
} else {
sql = `SELECT * FROM Achievement WHERE name LIKE '%${keyword}%' OR description LIKE '%${keyword}%';`;
}
const res: BTMuli.SQLite.Achievements[] = await this.db.select(sql);
return res;
}
}
export default TGSqlite;