在本文中,将探讨如何为每个注册用户创建一个个人SQLite数据库,以及如何实现用户删除数据、下载数据库、销毁账户等增强功能。这些功能对于确保数据的可用性和所有权至关重要。
此外,还添加了一个额外的增强功能,用于验证UUID是否符合预期。新的代码要求UUID值1)包含正好4个连字符,2)长度正好为36个字符(包括4个连字符)。注意到一些用户保存了像"test1"之类的UUID值。这个新增强功能有助于保护文件系统。
如果要用一个短语来解释软件开发的一致主题,可能会使用:
"在理论上,实践和理论是相同的。但在实践中,它们是不同的。"
可能会稍微改变一下:
"在开发环境(localhost)中,实践和理论是相同的。但在生产环境(公共URL)中,它们是不同的。"
这是因为在开发环境中尝试的一些功能很容易实现,但在生产环境中却有所不同。"在服务器上有效!"现在,让看看实现的最简单的增强功能,并看看它的作用。
为了允许用户从任何地方访问她的数据,添加了一个简单的方法,从UUID生成QRCode。用户只需点击UUID字段旁边的条形码图标按钮,就会生成一个QRCode。
是的,可以采取以下步骤来写入UUID数据库:
UUID:"快速登录"。由于没有人会猜测UUID,使用UUID登录用户。
是的,如果有人黑客攻击网站,他们将看到所有UUID文件夹,并能够访问任何SQLite数据库。是的,这并不安全。是的,这是一个原型,将向展示如何解决这个问题。
用户只需点击页面上的链接即可下载他们的SQLite数据库副本。它将下载到Web浏览器的下载目录。就这么简单。但是,背后的WebAPI(C#)和JavaScript并不那么简单,让看看。
[HttpPost]
public ActionResult DownloadSqliteDb([FromQuery] String uuid)
{
var userDir = Path.Combine(webRootPath, uuid);
var journalDb = Path.Combine(userDir, templateDbFile);
Console.WriteLine(journalDb);
return new PhysicalFileResult(journalDb, "application/x-sqlite3");
}
用户必须传递UUID以识别她尝试下载的数据库。然后只需创建用户文件空间路径到sqlite db(记住从第一篇文章中,模板SQLite数据库被命名为sqlstone_journal.db)。
然后发现了必须使用PhysicalFileResult()来正确返回文件的字节。如果尝试返回FileResult(),会发生一件非常奇怪的事情。那件奇怪的事情发生在身上,几乎要咬断手臂来修复这个问题。然后最终发现了一些文档,并在博客上写了出来。
当文件返回时,决定想用一个包含简单时间戳的名称来保存它,这样下载的浏览器就不会只是将其命名为file(1).db,file(2).db等。此外,这意味着如果有多个UUID日记,可以下载它们而不会覆盖它们。
function downloadSqliteDb() {
console.log("1");
if (currentUuid != null) {
fetch(`${baseUrl}User/DownloadSqliteDb?uuid=${currentUuid}`, {
method: 'POST',
})
.then(resp => {
console.log(resp);
return resp.blob();
})
.then(blob => {
downloadFile(blob, `journal-${GetFileTimeFormat(new Date())}.db`);
});
} else {
uuidRegisterAlert("You need to register your UUID to be able to download your sqlite data. Please register your UUID & try again.", true);
}
}
如所见,还调用了另一个JS函数GetFileTimeFormat(),用它来用时间戳命名文件。会让在源代码中查看那段代码。
有趣的代码是downloadFile() JavaScript。它很奇怪,但它有效。
function downloadFile(blob, name = "journal.db") {
const href = URL.createObjectURL(blob);
const a = Object.assign(document.createElement("a"), {
href,
style: "display:none",
download: name,
});
document.body.appendChild(a);
a.click();
URL.revokeObjectURL(href);
a.remove();
}
疯狂的事情。
这里还有另一个功能要讨论。想让用户能够销毁她的账户。这样她就可以拥有自己的数据。
无权评判?如果想删除日记数据,很乐意让这么做。
[HttpPost]
public ActionResult DestroyUserAccount([FromQuery] String uuid) {
// Every valid UUID will have 4 hyphens & be 36 chars long
int hyphenCounter = uuid.AsSpan().Count('-');
if (uuid.Length != 36 || hyphenCounter != 4) {
return new JsonResult(new { result = false, message = "Your uuid doesn't look like a valid value.\nCannot delete account." });
}
var userDir = Path.Combine(webRootPath, uuid);
try {
Directory.Delete(userDir, true);
} catch (Exception ex) {
return new JsonResult(new { result = false, message = $"Error! Delete Failed. \nCannot delete account. {ex.Message}" });
}
return new JsonResult(new { result = true, message = "The user account and all associated data has been destroyed." });
}
发布UUID,在做了一些检查以确保它看起来像一个有效的ID之后,WebAPI将调用Directory.Delete(),带有true参数(表示应该销毁所有文件夹和文件)。
发现需要这样做,否则一些聪明人会过来,使他的UUID成为css或其他文件夹名称,然后删除Web文件夹。哈哈!那不是很有趣吗?不!
所以检查UUID是否有4个连字符,并且正好是36个字符。如果两个测试都失败了,就拒绝用户的发布。拒绝和一切!😁
function destroyAccount() {
fetch(`${baseUrl}User/DestroyUserAccount?uuid=${currentUuid}`, {
method: 'POST',
})
.then(response => response.json())
.then(data => {
if (data.result == false) {
alert(`Could not destroy the account. \nError -> ${data.message}`);
return;
}
uuidRegisterAlert("The UUID, User Account & all associated data was permanently deleted.");
localStorage.removeItem("currentUuid");
currentUuid = null;
document.querySelector("#uuid").value = "";
window.location.reload();
});
document.querySelector("#modalCloseBtn").click();
}
这一切都在本地主机上运行得很好。删除每次都成功。
但是当把代码移到产品服务器(https://newlibre.com/journal)时,开始得到一个500错误。
完全困惑,直到在API调用中放入一个try/catch。一旦这样做了,发现删除Sqlite DB时有一个错误,因为服务认为Sqlite DB有一个连接打开。由于它认为文件正在使用中,它不允许删除Sqlite db并删除目录。
兔子洞:EF Core,Sqlite DB连接等。
现在要跳进兔子洞,试图发现一种方法来强制应用程序关闭连接,但是那里有很多技术正在进行,可能很难发现谁在保持连接打开。
唉,这就是软件开发人员的生活。
点击垃圾桶图标(下图中突出显示),一个警告对话框会弹出。
如果想销毁账户,点击[Destroy Account]按钮。