♻️ 合并 TGSql 与 TGSqlite,重构代码作为 Plugin 使用

This commit is contained in:
BTMuli
2023-05-31 17:32:00 +08:00
parent 1a9ca12363
commit 51058c6825
8 changed files with 477 additions and 434 deletions

304
src/plugins/Sqlite/index.ts Normal file
View File

@@ -0,0 +1,304 @@
/**
* @file plugins Sqlite index.ts
* @description Sqlite 数据库操作类
* @author BTMuli<bt-muli@outlook.com>
* @since Alpha v0.2.0
*/
// tauri
import Database from "tauri-plugin-sql-api";
// utils
import initDataSql from "./sql/initData";
import initTableSql from "./sql/initTable";
import { importUIAFData } from "./sql/updateData";
import { getUiafStatus } from "../../utils/UIAF";
import { insertAppData, insertGameAccountData } from "./sql/insertData";
class Sqlite {
/**
* @description 数据库地址
* @since Alpha v0.2.0
* @private
*/
private readonly dbPath: string = "sqlite:tauri-genshin.db";
/**
* @description 数据库包含的表
* @since Alpha v0.2.0
* @private
*/
private readonly tables: string[] = [
"Achievements",
"AchievementSeries",
"AppData",
"GameAccount",
"NameCard",
"SprialAbyss",
];
/**
* @description 初始化数据库
* @since Alpha v0.2.0
* @returns {Promise<void>}
*/
public async initDB (): Promise<void> {
const db = await Database.load(this.dbPath);
const sql = [...initTableSql(), ...await initDataSql()];
for (const item of sql) {
await db.execute(item);
}
await db.close();
}
/**
* @description 获取数据库信息
* @since Alpha v0.2.0
* @returns {Promise<TGApp.Sqlite.AppData.Item[]>}
*/
public async getAppData (): Promise<TGApp.Sqlite.AppData.Item[]> {
const db = await Database.load(this.dbPath);
const sql = "SELECT * FROM AppData;";
const res: TGApp.Sqlite.AppData.Item[] = await db.select(sql);
await db.close();
return res;
}
/**
* @description 获取 cookie
* @since Alpha v0.2.0
* @returns {Promise<Record<string, string>>}
*/
public async getCookie (): Promise<Record<string, string>> {
const db = await Database.load(this.dbPath);
const sql = "SELECT value FROM AppData WHERE key='cookie';";
const res: Array<{ value: string }> = await db.select(sql);
await db.close();
return JSON.parse(res[0].value);
}
/**
* @description 插入 Account 数据
* @since Alpha v0.2.0
* @param {TGApp.User.Account.Game[]} accounts
* @returns {Promise<void>}
*/
public async insertAccount (accounts: TGApp.User.Account.Game[]): Promise<void> {
const db = await Database.load(this.dbPath);
for (const a of accounts) {
const sql = insertGameAccountData(a);
await db.execute(sql);
}
await db.close();
}
/**
* @description 获取当前选择的游戏账号
* @since Alpha v0.2.0
* @returns {Promise<TGApp.Sqlite.Account.Game|false>}
*/
public async getCurAccount (): Promise<TGApp.Sqlite.Account.Game | false> {
const db = await Database.load(this.dbPath);
const sql = "SELECT * FROM GameAccount WHERE isChosen=1;";
const res: TGApp.Sqlite.Account.Game[] = await db.select(sql);
await db.close();
return res.length === 0 ? false : res[0];
}
/**
* @description 保存 appData
* @since Alpha v0.2.0
* @param {string} key
* @param {string} value
* @returns {Promise<void>}
*/
public async saveAppData (key: string, value: string): Promise<void> {
const db = await Database.load(this.dbPath);
const sql = insertAppData(key, value);
await db.execute(sql);
await db.close();
}
/**
* @description 已有数据表跟触发器不变的情况下,更新数据库数据
* @since Alpha v0.2.0
* @returns {Promise<void>}
*/
public async update (): Promise<void> {
const db = await Database.load(this.dbPath);
const sqlD = await initDataSql();
for (const item of sqlD) {
await db.execute(item);
}
await db.close();
}
/**
* @description 检测数据库完整性
* @since Alpha v0.2.0
* @returns {Promise<boolean>}
*/
public async check (): Promise<boolean> {
const db = await Database.load(this.dbPath);
let isVerified = false;
// 检测数据表是否都存在
const sqlT = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;";
const res: Array<{ name: string }> = await db.select(sqlT);
await db.close();
if (res.length === this.tables.length) {
if (this.tables.every((item) => res.map((i) => i.name).includes(item))) {
isVerified = true;
}
}
return isVerified;
}
/**
* @description 重置数据库
* @since Alpha v0.2.0
* @returns {Promise<void>}
*/
public async reset (): Promise<void> {
const db = await Database.load(this.dbPath);
this.tables.map(async (item) => {
const sql = `DROP TABLE IF EXISTS ${item};`;
await db.execute(sql);
});
await db.close();
await this.initDB();
}
/**
* @description 获取成就系列列表
* @since Alpha v0.2.0
* @returns {Promise<TGApp.Sqlite.Achievement.SeriesTable[]>}
*/
public async getAchievementSeries (): Promise<TGApp.Sqlite.Achievement.SeriesTable[]> {
const db = await Database.load(this.dbPath);
const sql = "SELECT * FROM AchievementSeries ORDER BY `order`;";
const res: TGApp.Sqlite.Achievement.SeriesTable[] = await db.select(sql);
await db.close();
return res;
}
/**
* @description 获取成就系列对应的名片
* @since Alpha v0.2.0
* @param {number} seriesId 系列 ID
* @returns {Promise<TGApp.Sqlite.NameCard.Item>}
*/
public async getNameCard (seriesId: number): Promise<TGApp.Sqlite.NameCard.Item> {
const db = await Database.load(this.dbPath);
const sql = `SELECT * FROM NameCard WHERE name = (SELECT nameCard FROM AchievementSeries WHERE id = ${seriesId});`;
const res: TGApp.Sqlite.NameCard.Item[] = await db.select(sql);
await db.close();
return res[0];
}
/**
* @description 获取成就列表
* @since Alpha v0.2.0
* @param {number} [seriesId] 系列 ID
* @returns {Promise<TGApp.Sqlite.Achievement.SingleTable[]>}
*/
public async getAchievements (seriesId?: number): Promise<TGApp.Sqlite.Achievement.SingleTable[]> {
const db = await Database.load(this.dbPath);
let sql;
if (seriesId) {
sql = `SELECT * FROM Achievements WHERE series=${seriesId} ORDER BY isCompleted, \`order\`;`;
} else {
sql = "SELECT * FROM Achievements ORDER BY isCompleted, `order`;";
}
const res: TGApp.Sqlite.Achievement.SingleTable[] = await db.select(sql);
await db.close();
return res;
}
/**
* @description 获取成就概况
* @since Alpha v0.2.0
* @returns {Promise<{total:number,fin:number}>}
*/
public async getAchievementsOverview (): Promise<{ total: number, fin: number }> {
const db = await Database.load(this.dbPath);
const sql = "SELECT SUM(totalCount) AS total, SUM(finCount) AS fin FROM AchievementSeries;";
const res: Array<{ total: number, fin: number }> = await db.select(sql);
await db.close();
return res[0];
}
/**
* @description 获取最新成就版本
* @since Alpha v0.2.0
* @returns {Promise<string>}
*/
public async getLatestAchievementVersion (): Promise<string> {
const db = await Database.load(this.dbPath);
const sql = "SELECT version FROM AchievementSeries ORDER BY version DESC LIMIT 1;";
const res: Array<{ version: string }> = await db.select(sql);
await db.close();
return res[0].version;
}
/**
* @description 查询成就
* @since Alpha v0.2.0
* @param {string} keyword 关键词
* @returns {Promise<TGApp.Sqlite.Achievement.SingleTable[]>}
*/
public async searchAchievements (keyword: string): Promise<TGApp.Sqlite.Achievement.SingleTable[]> {
const db = await Database.load(this.dbPath);
let sql;
if (keyword.startsWith("v")) {
const version = keyword.replace("v", "");
sql = `SELECT * FROM Achievements WHERE version LIKE '%${version}%' ORDER BY isCompleted, \`order\`;`;
} else {
sql = `SELECT * FROM Achievements WHERE name LIKE '%${keyword}%' OR description LIKE '%${keyword}%'
ORDER BY isCompleted, \`order\`;`;
}
const res: TGApp.Sqlite.Achievement.SingleTable[] = await db.select(sql);
await db.close();
return res;
}
/**
* @description 合并 UIAF 数据
* @since Alpha v0.2.0
* @param {TGApp.Plugins.UIAF.Achievement[]} achievements UIAF 数据
* @returns {Promise<void>}
*/
public async mergeUIAF (achievements: TGApp.Plugins.UIAF.Achievement[]): Promise<void> {
const db = await Database.load(this.dbPath);
const sql = importUIAFData(achievements);
for (const item of sql) {
await db.execute(item);
}
await db.close();
}
/**
* @description 获取 UIAF 数据
* @since Alpha v0.2.0
* @returns {Promise<TGApp.Plugins.UIAF.Achievement[]>}
*/
public async getUIAF (): Promise<TGApp.Plugins.UIAF.Achievement[]> {
const db = await Database.load(this.dbPath);
const sql = "SELECT * FROM Achievements WHERE isCompleted = 1 OR progress > 0";
const res: TGApp.Sqlite.Achievement.SingleTable[] = await db.select(sql);
await db.close();
const achievements: TGApp.Plugins.UIAF.Achievement[] = [];
for (const item of res) {
const completed = item.isCompleted === 1;
const status = getUiafStatus(completed, item.progress);
achievements.push({
id: item.id,
status,
timestamp: completed && item.completedTime ? new Date(item.completedTime).getTime() / 1000 : 0,
current: item.progress,
});
}
return achievements;
}
}
const TGSqlite = new Sqlite();
export default TGSqlite;

View File

@@ -0,0 +1,88 @@
-- @file plugins Sqlite sql createTable.sql
-- @brief sqlite数据库创建表语句
-- @author BTMuli <bt-muli@outlook.com>
-- @since Alpha v0.2.0
-- @brief 创建成就数据表
create table if not exists Achievements
(
id integer primary key,
series integer,
`order` integer,
name text,
description text,
reward integer,
isCompleted boolean default false,
completedTime text,
progress integer default 0,
version text,
updated text
);
-- @brief 创建成就系列数据表
create table if not exists AchievementSeries
(
id integer primary key,
`order` integer,
name text,
version text,
totalCount integer default 0,
finCount integer default 0,
nameCard text,
updated text
);
-- @brief 创建应用数据表
create table if not exists AppData
(
key text primary key,
value text,
updated text
);
-- @brief 创建游戏账号数据表
create table if not exists GameAccount
(
gameBiz text,
gameUid text,
isChosen boolean,
isOfficial boolean,
level integer,
nickname text,
region text,
regionName text,
updated text,
primary key (gameBiz, gameUid)
);
-- @brief 名片数据表
create table if not exists NameCard
(
name text,
desc text,
type text,
source text,
updated text,
primary key (name, type)
);
-- @brief 创建深渊数据表
create table if not exists SpiralAbyss
(
id integer primary key,
startTime text,
endTime text,
totalBattleTimes integer,
totalWinTimes integer,
maxFloor text,
totalStar integer,
isUnlock boolean,
revealRank text,
defeatRank text,
damageRank text,
takeDamageRank text,
normalSkillRank text,
energySkillRank text,
floors text,
updated text
);

View File

@@ -0,0 +1,40 @@
-- @file plugins Sqlite sql createTrigger.sql
-- @brief 创建触发器
-- @author BTMuli <bt-muli@outlook.com>
-- @since Alpha v0.2.0
-- @brief 成就表相关
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;
update AchievementSeries
set version = new.version,
updated = datetime('now', 'localtime')
where id = new.series
and new.version > version;
end;
create trigger if not exists updateAchievement
after update
on Achievements
for each row
begin
update AchievementSeries
set updated = datetime('now', 'localtime'),
finCount = finCount + 1
where id = new.series
and old.isCompleted = 0
and new.isCompleted = 1;
update AchievementSeries
set updated = datetime('now', 'localtime'),
finCount = finCount - 1
where id = new.series
and old.isCompleted = 1
and new.isCompleted = 0;
end;

View File

@@ -0,0 +1,90 @@
/**
* @file plugins Sqlite sql initData.ts
* @description Sqlite 初始化数据 sql 语句
* @author BTMuli <bt-muli@outlook.com>
* @since Alpha v0.2.0
*/
// tauri
import { app } from "@tauri-apps/api";
// utils
import { getBuildTime } from "../../../utils/TGBuild";
import { insertAchievementData, insertAchievementSeriesData, insertNameCardData } from "./insertData";
import {
AppAchievementsData,
AppAchievementSeriesData,
AppNameCardsData,
} from "../../../data";
/**
* @description 初始化应用表数据
* @since Alpha v0.2.0
* @returns {Promise<string[]>} sql
*/
async function initAppData (): Promise<string[]> {
const sqlRes: string[] = [];
const appVersion = await app.getVersion();
const buildTime = getBuildTime();
// 初始化应用版本
sqlRes.push(`
INSERT INTO AppData (key, value, updated)
VALUES ('appVersion', '${appVersion}', datetime('now', 'localtime'))
ON CONFLICT(key) DO UPDATE SET value = '${appVersion}', updated = datetime('now', 'localtime');`,
);
// 初始化应用数据更新时间
sqlRes.push(`
INSERT INTO AppData (key, value, updated)
VALUES ('dataUpdated', '${buildTime}', datetime('now', 'localtime'))
ON CONFLICT(key) DO UPDATE SET value = '${buildTime}', updated = datetime('now', 'localtime');`,
);
// 初始化 cookie
sqlRes.push(`
INSERT INTO AppData (key, value, updated)
VALUES ('cookie', '{}', datetime('now', 'localtime'))
ON CONFLICT(key) DO NOTHING;`,
);
return sqlRes;
}
/**
* @description 初始化成就系列数据
* @since Alpha v0.2.0
* @returns {string[]} sql
*/
function initAchievementSeriesData (): string[] {
return AppAchievementSeriesData.map((item) => insertAchievementSeriesData(item));
}
/**
* @description 初始化成就数据
* @since Alpha v0.2.0
* @returns {string[]} sql
*/
function initAchievementData (): string[] {
return AppAchievementsData.map((item) => insertAchievementData(item));
}
/**
* @description 初始化应用名片数据
* @since Alpha v0.2.0
* @returns {string[]} sql
*/
function initNameCardData (): string[] {
return AppNameCardsData.map((item) => insertNameCardData(item));
}
/**
* @description 初始化数据
* @since Alpha v0.2.0
* @returns {Promise<string[]>} sql
*/
async function initDataSql (): Promise<string[]> {
const sqlRes: string[] = [];
sqlRes.push(...await initAppData());
sqlRes.push(...initAchievementSeriesData());
sqlRes.push(...initAchievementData());
sqlRes.push(...initNameCardData());
return sqlRes;
}
export default initDataSql;

View File

@@ -0,0 +1,24 @@
/**
* @file plugins Sqlite sql initTable.ts
* @description Sqlite 初始化数据 sql 语句
* @author BTMuli <bt-muli@outlook.com>
* @since Alpha v0.2.0
*/
// data
import createTable from "./createTable.sql?raw";
import createTrigger from "./createTrigger.sql?raw";
/**
* @description 初始化数据库表
* @since Alpha v0.2.0
* @returns {string[]} sql
*/
function initTableSql (): string[] {
return [
createTable,
createTrigger,
];
}
export default initTableSql;

View File

@@ -0,0 +1,104 @@
/**
* @file plugins Sqlite sql insertData.ts
* @description Sqlite 插入数据 sql 语句
* @author BTMuli <bt-muli@outlook.com>
* @since Alpha v0.2.0
*/
/**
* @description 插入成就数据
* @since Alpha v0.2.0
* @param {TGApp.App.Achievement.Item} data 成就数据
* @returns {string} sql
*/
export function insertAchievementData (data: TGApp.App.Achievement.Item): string {
return `
INSERT INTO Achievements (id, series, "order", name, description, reward, completedTime, version, updated)
VALUES (${data.id}, ${data.series}, ${data.order}, '${data.name}', '${data.description}', ${data.reward}, '',
'${data.version}', datetime('now', 'localtime'))
ON CONFLICT(id) DO UPDATE
SET series = ${data.series},
"order" = ${data.order},
name = '${data.name}',
description = '${data.description}',
reward = '${data.reward}',
version = '${data.version}',
updated = datetime('now', 'localtime');
`;
}
/**
* @description 插入成就系列数据
* @since Alpha v0.2.0
* @param {TGApp.App.Achievement.Series} data 成就系列数据
* @returns {string} sql
*/
export function insertAchievementSeriesData (data: TGApp.App.Achievement.Series): string {
return `
INSERT INTO AchievementSeries (id, "order", name, version, nameCard, updated)
VALUES (${data.id}, ${data.order}, '${data.name}', '${data.version}','${data.card}', datetime('now', 'localtime'))
ON CONFLICT(id) DO UPDATE
SET name = '${data.name}',
"order" = ${data.order},
version = '${data.version}',
nameCard = '${data.card}',
updated = datetime('now', 'localtime');
`;
}
/**
* @description 插入应用数据
* @since Alpha v0.2.0
* @param {string} key 键
* @param {string} value 值
* @returns {string} sql
*/
export function insertAppData (key: string, value: string): string {
return `
INSERT INTO AppData (key, value, updated)
VALUES ('${key}', '${value}', datetime('now', 'localtime'))
ON CONFLICT(key) DO UPDATE SET value = '${value}', updated = datetime('now', 'localtime');
`;
}
/**
* @description 插入游戏账号数据
* @since Alpha v0.2.0
* @param {TGApp.User.Account.Game} data 游戏账号数据
* @returns {string} sql
*/
export function insertGameAccountData (data: TGApp.User.Account.Game): string {
const isChosen = data.is_chosen ? 1 : 0;
const isOfficial = data.is_official ? 1 : 0;
return `
INSERT INTO GameAccount (gameBiz, gameUid, isChosen, isOfficial, level, nickname, region, regionName, updated)
VALUES ('${data.game_biz}', '${data.game_uid}', ${isChosen}, ${isOfficial}, ${data.level}, '${data.nickname}',
'${data.region}', '${data.region_name}', datetime('now', 'localtime'))
ON CONFLICT(gameBiz, gameUid) DO UPDATE
SET isChosen = ${isChosen},
isOfficial = ${isOfficial},
level = ${data.level},
nickname = '${data.nickname}',
region = '${data.region}',
regionName = '${data.region_name}',
updated = datetime('now', 'localtime');
`;
}
/**
* @description 插入名片数据
* @since Alpha v0.2.0
* @param {TGApp.App.NameCard.Item} data 名片数据
* @returns {string} sql
*/
export function insertNameCardData (data: TGApp.App.NameCard.Item): string {
return `
INSERT INTO NameCard (name, "desc", type, source, updated)
VALUES ('${data.name}', '${data.desc}', '${data.type}', '${data.source}', datetime('now', 'localtime'))
ON CONFLICT(name) DO UPDATE
SET "desc" = '${data.desc}',
type = '${data.type}',
source = '${data.source}',
updated = datetime('now', 'localtime');
`;
}

View File

@@ -0,0 +1,37 @@
/**
* @file plugins Sqlite sql updateData.ts
* @description 更新数据
* @author BTMuli <bt-muli@outlook.com>
* @since Alpha v0.2.0
*/
/**
* @description 导入UIAF数据
* @since Alpha v0.1.5
* @param {TGApp.Plugins.UIAF.Achievement[]} data
* @returns {string[]} sql
*/
export function importUIAFData (data: TGApp.Plugins.UIAF.Achievement[]): string[] {
const sqlRes: string[] = [];
data.map((achievement) => {
let sql;
// 获取完成状态
const isCompleted = achievement.status === 2 || achievement.status === 3;
if (isCompleted) {
const completedTime = new Date(achievement.timestamp * 1000).toISOString().replace("T", " ").slice(0, 19);
sql = `
UPDATE Achievements
SET isCompleted = 1, completedTime = '${completedTime}', progress = ${achievement.current}, updated = datetime('now', 'localtime')
WHERE id = ${achievement.id} AND (isCompleted = 0 OR completedTime != '${completedTime}' OR progress != ${achievement.current});
`;
} else {
sql = `
UPDATE Achievements
SET progress = ${achievement.current}, updated = datetime('now', 'localtime')
WHERE id = ${achievement.id} AND progress != ${achievement.current};
`;
}
return sqlRes.push(sql);
});
return sqlRes;
}