基於Vapor3框架的Swift項目實戰《NewsBox》-(二:用戶註冊模塊)

基於Vapor3框架的Swift項目實戰《NewsBox》-(二:用戶註冊模塊)


創建新項目

首先我們創建一個新的空工程
在終端輸入
vapor new NewsBox
然後cd到創建好的目錄下
cd NewsBox
生成xcode項目並使用xcode打開
vapor xcode -y
第一次使用這個命令可能會等待swift包管理器自動下載相關依賴
完成後xcode就會自動打開該項目
下面是創建好的項目結構和相關依賴庫版本

項目結構

現在主流的註冊方式爲手機號+驗證碼或郵箱+驗證碼,由於手機驗證碼要收費,對於本demo來說就採用郵箱加驗證碼的方式

郵箱+驗證碼實現註冊

使用郵箱服務有幾種方式
1. 在自己的服務器上面搭建郵箱服務器
2. 在其他地方註冊一個郵箱並開啓SMTP服務,在自己的服務器上面通過SMTP協議來發送郵件
當然對於本demo來說搭建郵箱服務器不是重點,所以就採用第二種方式
需要一個支持SMTP的郵箱賬戶,具體開啓SMTP服務的方法依據不同的郵箱服務商不同,自行百度
在項目的Package.swift中添加SMTP相關依賴庫
本demo使用MySQL,因此也一併講相關庫加進去
我的Package文件內容如下:

    name: "PUBGNewsBox",
    dependencies: [
        // �� A server-side Swift web framework.
        .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"),

        // ���� Swift ORM (queries, models, relations, etc) built on MySQL.
        .package(url: "https://github.com/vapor/fluent-mysql.git", from: "3.0.0-rc"),

        /// mail Server
        .package(url: "https://github.com/IBM-Swift/Swift-SMTP.git", from: "4.0.1")
    ],
    targets: [
        .target(name: "App", dependencies: ["SwiftSMTP", "FluentMySQL", "Vapor"]),
        .target(name: "Run", dependencies: ["App"]),
        .testTarget(name: "AppTests", dependencies: ["App"])
    ]
)

重新在終端裏面用vapor xcode -y打開一次

  • 接下來就是難點了
    怎麼去讓服務器響應我們的請求
    服務器怎麼返回結果

    參考新建項目后里面的ToDo開頭的文件

  • OK看完了之後回到這裏,我們直接上代碼

    新建一個Swift文件名字:Email 內容如下

import Foundation
import Vapor
import SwiftSMTP

struct PUBGBoxEmail : Content{
    var email       :   String
    var verifycode  :   String?
}


fileprivate var emailDic = Dictionary<String,String>()
/// Configure mail Server
fileprivate let smtp = SMTP(hostname: "郵箱SMTP服務地址",     // SMTP server address
                            email: "這裏填你的郵箱地址",     // username to login
                            password: "密碼")           // password to login


fileprivate let PUBGNewsBoxMailAddress = Mail.User(name: "發件人名稱", email: "發件人郵箱")
/// check verify code
extension PUBGBoxEmail{
    func checkVerifyCode() ->Bool{
        guard let verifyCode = emailDic[self.email] else {
            return false
        }
        guard let customVerifyCode = self.verifycode else{
            return false
        }
        if verifyCode == customVerifyCode {
            emailDic[self.email] = nil
            return true
        }
        return false
    }
}

extension PUBGBoxEmail{
    static func sendEmailCode(_ email : String ) -> Bool{

        var verifyCode = emailDic[email]

        if verifyCode == nil{
            verifyCode = RandomString.sharedInstance.getRandomStringOfLength(length: 4)
        }
        ///catch result and return
        let userMail = Mail.User(email: email)

        let mail = Mail(
            from: PUBGNewsBoxMailAddress,
            to: [userMail],
            subject: "歡迎註冊PUBG新聞盒子",
            text: "您本次註冊的驗證碼爲:\(verifyCode!)"
        )
        var result = true
        smtp.send(mail) { (error) in
            if let error = error {
                print(error)
                result = false
            }else{
                emailDic[email] = verifyCode!
            }
        }

        return result
    }

}



/// 隨機字符串生成
class RandomString {
    let str = "1234567890"

    /**
     生成隨機字符串,

     - parameter length: 生成的字符串的長度

     - returns: 隨機生成的字符串
     */
    func getRandomStringOfLength(length: Int) -> String {
        var ranStr = ""
        for _ in 0..<length {
            let index = Int(arc4random_uniform(UInt32(str.count)))
            ranStr.append(str[str.index(str.startIndex, offsetBy: index)])
        }
        return ranStr
    }
    private init() { }
    static let sharedInstance = RandomString()
}

下面來講解

首先我們創建一個Dictonary來保存郵箱和對應的驗證碼
當收到一個發送驗證碼請求時,隨機產生4位數,然後發送郵件到收到的郵箱裏面去
如果發送成功,就緩存郵箱和對應的驗證碼,否則就返回錯誤給用戶(如郵箱地址錯誤等)—-(這裏有一點優化的地方,後面說)

這裏只實現了郵箱相關的方法,並沒有涉及到數據獲取與返回
接下來我們去接受用戶的請求
爲了便於管理,我們創建一個Controller

創建一個名爲UserController的Swift文件
下面是該文件的內容

import Foundation
import Vapor
import FluentMySQL

fileprivate let paramError = "請求參數錯誤"
fileprivate let secretKeyError = "App鑑權錯誤"

/// 1.檢查客戶端密鑰是否正確
/// ```
/// try SercretKey.JudgeSercretKey(sercreKey: requestBody.sercretkey,req: req).map(){result in
/// if !result {
///    return ResponseModel<String>(status: -3, message: secretKeyError, data: nil)
/// }
/// ```
/// 2.檢查用戶登陸狀態
///```
///let loginStatus = AccessToken.checkAccessToken(accessToken: requestBody.accessToken)
///if .ok != loginStatus {
///    return req.eventLoop.newSucceededFuture(result: ResponseModel<String>(status: -1, message: loginStatus.rawValue, data: nil))
///}
///```
final class UserController{

    /// 用戶註冊
    func register(req : Request)throws -> Future<ResponseModel<UserRegistInfo> >{
        return try req.content.decode(RequestModel<UserRegistInfo>.self).flatMap(){ requestBody in

            return try SercretKey.JudgeSercretKey(sercreKey: requestBody.sercretkey,req: req).flatMap(){result in
                if !result {
                    return req.eventLoop.newSucceededFuture(result: ResponseModel<UserRegistInfo>(status: -3, message: secretKeyError, data: nil))
                }

                if requestBody.data == nil {
                    return req.eventLoop.newSucceededFuture(result: ResponseModel<UserRegistInfo>(status: -2, message: paramError, data: nil))
                }

                return try requestBody.data!.check(req: req).flatMap(){checkresult in
                    if checkresult == .ok {
                        requestBody.data!.password = try encryptPassword(password: requestBody.data!.password!)
                        return requestBody.data!.save(on: req).map(){ user in
                            return ResponseModel<UserRegistInfo>(status: 0, message: "註冊成功", data: nil)
                        }
                    }else{
                        return req.eventLoop.newSucceededFuture(result: ResponseModel<UserRegistInfo>(status: -1, message: checkresult.rawValue, data: nil))
                    }
                }

            }
        }

    }
}

上面說到的客戶端密鑰和用戶登陸狀態驗證先不管
主要看register函數
函數接受一個Request參數,返回一個Future<ResponseModel<UserRegistInfo> >
關於Future/Promise這裏不講,請不瞭解的自行百度
在傳進來的req裏面,我們解碼出一個RequestModel<UserRegistInfo>
這裏的ResponseModel和RequestModel內容如下

import Vapor

struct ResponseModel<T> : Content where T : Content{
    var status : Int
    var message : String

    var data : T?

}
final class RequestModel<T> : Content where T : Codable{

    var sercretkey : String
    var timestamp : Int
    var accessToken : String?
    var data : T?

    init(sercretkey : String, timestamp : Int) {
        self.sercretkey = sercretkey
        self.timestamp = timestamp
    }
}

簡單來說就是使用了範型,在Vapor框架裏,想要把請求參數轉換成Model或者把Model轉換成返回參數(JSON)的話,只需要Model繼承Content就行了,這裏使用了Swift4的一個重要特性,SwiftNIO裏的Codable。

最後我們需要給我們的請求指定一個路徑

在routes中

let userRouter = router.grouped("user")
    let userController = UserController()

    userRouter.post("register", use: userController.register)

然後請求參數JSON如下:

{
    "sercretkey":"123",
    "timestamp":456132,
    "data":
    {
    "username":"tzcccy2",
    "password":"0054321",
    "email":"****@qq.com",
    "verifycode":"3432"
    }

}

除去”sercretkey”:”123”,
“timestamp”:456132,
先不看
具體請查看github上的源碼
本文demo地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章