惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

TaoSecurity Blog
TaoSecurity Blog
V2EX - 技术
V2EX - 技术
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
S
Secure Thoughts
Forbes - Security
Forbes - Security
Engineering at Meta
Engineering at Meta
Microsoft Azure Blog
Microsoft Azure Blog
Apple Machine Learning Research
Apple Machine Learning Research
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Webroot Blog
Webroot Blog
W
WeLiveSecurity
Blog — PlanetScale
Blog — PlanetScale
G
Google Developers Blog
Last Week in AI
Last Week in AI
月光博客
月光博客
H
Help Net Security
PCI Perspectives
PCI Perspectives
Security Archives - TechRepublic
Security Archives - TechRepublic
Hugging Face - Blog
Hugging Face - Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
有赞技术团队
有赞技术团队
T
Troy Hunt's Blog
Google DeepMind News
Google DeepMind News
Hacker News: Ask HN
Hacker News: Ask HN
Microsoft Security Blog
Microsoft Security Blog
N
News and Events Feed by Topic
Project Zero
Project Zero
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
L
LangChain Blog
P
Privacy & Cybersecurity Law Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
V
V2EX
I
Intezer
H
Hacker News: Front Page
Recent Announcements
Recent Announcements
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
博客园 - Franky
T
Threat Research - Cisco Blogs
Spread Privacy
Spread Privacy
博客园 - 【当耐特】
美团技术团队
Schneier on Security
Schneier on Security
D
Docker
Scott Helme
Scott Helme
L
LINUX DO - 最新话题
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
aimingoo的专栏
aimingoo的专栏
L
Lohrmann on Cybersecurity

Kevin Blog

产品随想 2 产品力与人才密度 产品随想 1 做营销还是做产品 Play PyTorch Stable Diffusion and ONNX, Ollama on Intel Core Ultra 5 225H Ubuntu 25.04 Play with ROCm, PyTorch, Ollama on Ubuntu 24.04 and 780m 写在新的旅程开始前 想念自然与青春 懒猫微服体验——自由协作的神器 没有光纤的日子怎么上网?自制 Home WI-FI! Swift on Server Tour 6 关联 User 和 Post Swift on Server Tour 4 构建 Post Controller Swift on Server Tour 3 构建 Post 的 API 将不懂的日语一拍扫尽,介绍捧读全新的「OCR 工作台」功能 Swift on Server Tour 2 连通你的数据库与服务器 Swift on Server Tour 1 你的第一个 Server App 以及它背后的故事 Swift on Server Tour 0: 为什么这可能是你的好选择 纪念左耳朵耗子 How to learn Japanese by reading Novels and News with the help of Oyomi. Write WebAssembly in Swift and use it in Swift App BenQ WiT ScreenBar Halo 体验报告 记录 2021 年考驾照的体验 捧读的 EPUB 日语轻小说阅读器来了 使用 Go Mobile 开发跨平台 Library 使用 Kotlin Native 开发跨平台 Library 小番茄 - 一个只有陪伴的自习室 保友金豪电脑椅异响维修记 青岛度假指南 2020 「捧读」更新,支持针对任意文本进行语法讲解 一次百元耳机与千元耳机的横评 高刷墨水屏的海信 A5 Pro 体验报告 「捧读」更新 Safari 扩展,轻松阅读日语网页 新 App 「捧读:日语语法学习与分析」的开发幕后思考 明基 WiT ScreenBar Plus 体验 Switch游戏推荐:「健身环大冒险」和「太鼓达人」 妥协是否是一种艺术? 「50 音起源」1.2 更新说明 作客 UX Coffee 分享了自己最近的独立开发者生活 青岛度假指南 2019 我有「快乐自由」 关于小电台这款产品 聊一聊 50 音起源的再设计 极端精英主义的崩塌 Xcode Error: CFBundleSupportedPlatforms or Mach-O LC_VERSION_MIN 自由职业一周年我收获了什么 「50音起源」从起源学习 50 音的 App iOS/Android 青岛疗养(度假)指南 关于独处的一些经验 跨省携猫🐱搬家记 关于 FilmMentor 这款小众产品的想法 选择离开北京,以及为什么可以
Swift on Server Tour 5 创建 Users
Kevin Zhow · 2023-10-21 · via Kevin Blog

在本章中我们将在数据库中创建 User 表,用于存储用户信息,并理解如何将 Post 表和 User 关联起来,以便我们可以知道每篇微博是由哪个用户编写的。

本章代码可以在 Github 中找到。

设计 User 表的数据结构

一个基础的 User 表应该包含以下字段

  • id:主键,用于唯一标识一个用户
  • username:用户名,用于登录
  • password_hash:密码,用于登录,存储的应该是加密后的密码
  • createdAt:创建时间,用于记录用户创建时间

如果我们插入了一个假数据 happyuser,那么我们的 User 表应该是这样的:

id username password_hash createdAt
0 happyuser encrypted text 2023-1-1

那么如何关联 Post 表呢?

数据库的关系型设计

在关系性数据库中,表关系被总结为三种:

  • 一对一(One-to-One)
  • 一对多(One-to-Many)
  • 多对多(Many-to-Many)

我们的 User 表和 Post 表的关系是一对多,即一个用户可以有多篇微博,而一篇微博只能属于一个用户。

一对多关系的设计

在数据库中,我们可以通过在 Post 表中添加一个 userId 字段来表示这种关系,这个字段用于存储 User 表中的 id,即 Post 表中的每一行都会有一个 userId 字段,用于表示这篇微博是由哪个用户编写的。

修改之前的 Post 表,添加 userId 字段,表示如下:

id userId content createdAt
0 0 这是第一篇博文 2023-1-1
1 0 在 LONCAFE 写代码感觉不错呢! 2023/7/9 14:44

当我们需要查询 happyuser 的所有微博时,只需要在 Post 表中查询 userId = 0 的所有行即可。

创建 User Model

Sources/App/Models 目录下创建 User.swift 文件,添加如下代码:

import Vapor
import Fluent

final class User: Model, Content {
    static let schema = "users"
    
    @ID(key: .id)
    var id: UUID?
    
    @Field(key: "username")
    var username: String
    
    @Field(key: "password_hash")
    var passwordHash: String
    
    @Timestamp(key: "created_at", on: .create)
    var createdAt: Date?
    
    init() {}
    
    init(id: UUID? = nil, username: String, passwordHash: String) {
        self.id = id
        self.username = username
        self.passwordHash = passwordHash
    }
}

extension User {
    struct CreateDTO: Content {
        let username: String
        let password: String
    }
}

接下来,我们需要添加 User 表的 Migration 文件,用于在数据库中创建 User 表。

Sources/App/Migrations 目录下创建 2_CreateUser.swift 文件,添加如下代码:

import Fluent 

struct CreateUser: AsyncMigration { 
    func prepare(on database: Database) async throws { 
        try await database.schema(User.schema)  
            .id()  
            .field("username", .string, .required)
            .field("password_hash", .string, .required)
            .field("created_at", .datetime)
            .create()
    }

    func revert(on database: Database) async throws { 
        try await database.schema(User.schema).delete() 
    }
}

configure.swift 文件中修改 app.migrations.add,用于注册 User Migration:

app.passwords.use(.bcrypt) // Bcrypt 是一种密码加密算法,可以确保多次加密后的密码都是不同的,但是可以通过原始密码验证
app.migrations.add([CreatePost(), CreateUser()])

创建 User Controller

Sources/App/Controllers 目录下创建 UserController.swift 文件,添加如下代码:

import Fluent
import Vapor

struct UserController: RouteCollection {
    func boot(routes: RoutesBuilder) throws {
        routes.group("users") { posts in
            posts.get(use: index)
            posts.post(use: create)
        }
    }

    func create(req: Request) async throws -> User {
        let postData = try req.content.decode(User.CreateDTO.self)
            
        let user = User(username: postData.username, passwordHash: try await req.password.async.hash(postData.password))
        
        try await user.create(on: req.db)
        
        return user
    }

    func index(req: Request) async throws -> [User] {
        let users = try await User.query(on: req.db).all()
        
        return users
    }
}

configure.swift 文件中注册 UserController

try app.register(collection: UserController())

创建 User 的 XCTest 测试用例

Tests/AppTests 目录下创建 UserTests.swift 文件,添加如下代码:

@testable import App
import XCTVapor

final class UserTests: XCTestCase {
    var app: Application!

    override func setUp() async throws {
        app = Application(.testing)
        try configure(app)

        try await app.autoRevert()
        try await app.autoMigrate()
    }

    
    override func tearDown() async throws {
        app.shutdown()
    }
    
    func testCreateUser() async throws {
        let userData = User.CreateDTO(username: "happyuser", password: "123456")
        try app.test(.POST, "users", beforeRequest: { req in
            try req.content.encode(userData)
        }, afterResponse: { res in
            XCTAssertEqual(res.status, .ok)
            
            let user = try res.content.decode(User.self)
            
            XCTAssertEqual(user.username, userData.username)
            XCTAssertTrue(try app.password.verify("123456", created: user.passwordHash))
        })
    }
    
    func testGetUsers() async throws {
        let user = User(username: "hellouser", passwordHash: try await app.password.async.hash("123456"))
        try await user.save(on: app.db)
        try app.test(.GET, "users", afterResponse: { res in
            XCTAssertEqual(res.status, .ok)
            
            let users = try res.content.decode([User].self)
            
            XCTAssertEqual(users.count, 1)
            XCTAssertEqual(users[0].username, user.username)
        })
    }
}

运行测试用例

在终端中运行 swift test 命令,顺利的话,可以看到测试用例全部通过。

总结

在本章中,我们学习了如何在数据库中创建 User 表,以及如何将 Post 表和 User 表关联起来,但我们尚未真正在数据库中关联这两个表,我们将在下一章中完成这个功能。

本章代码可以在 Github 中找到。