开发 Emoji 日记应用:功能实现与服务集成
1. 页面加载函数更新
首先,我们需要更新页面加载时执行的函数。将页面加载时执行的函数(就在
signupClicked
之前)更新为如下代码:
$(function() {
window.emojiPicker = new EmojiPicker({
emojiable_selector: '[data-emojiable=true]',
assetsPath: '/img/', popupButtonClasses: 'fa fa-smile-o'
});
window.emojiPicker.discover();
var loggedInUser = document.getElementById('logged-in');
loggedInUser.innerHTML = "Logged in as: " + getCookie("username");
});
保存文件并刷新浏览器。使用有效的凭证登录,然后点击“注销”。确认注销意图后,页面会刷新,并且 cookies 会被清除。此时,所有用户管理按钮都已连接就绪。
2. 在网页上添加日记条目
当使用布局中的第一张卡片时,选择一个表情符号后,点击巨大的“+”按钮,应该能够将其发送到后端。在 JavaScript 函数列表的底部添加以下函数:
function addEmoji() {
var date = new Date();
var dateString = date.toISOString();
var emoji = $("#add-new-emoji-field").val();
if (emoji == "") {
alert("You must enter an emoji!");
return;
}
var xhr = new XMLHttpRequest();
xhr.open("POST", "/entries");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader("Authorization", "Basic " + btoa(getCookie("username") + ":" + getCookie("password")));
xhr.onreadystatechange = (() => {
if (xhr.readyState == XMLHttpRequest.DONE) {
if (xhr.status == 201) {
location.reload();
} else {
alert(`There was an error adding this emoji: ${xhr.statusText}`);
}
}
});
var body = JSON.stringify({ "emoji": emoji, "date": dateString });
xhr.send(body);
}
保存文件并刷新网页浏览器。确保以
socrates
身份登录,尝试添加一个新的表情符号条目。但由于 JavaScript 和 Swift 对日期格式的处理方式不同,可能会遇到问题。
3. 处理日期格式问题
JavaScript 使用 ISO8601 日期格式,而 Kitura API 目前还不能很好地处理这种格式。我们需要使用 Kitura 的自定义解码功能来解决这个问题。
1. 停止服务器,打开
Sources/Application/Routes/EntryRoutes.swift
文件,在导入语句下方添加以下代码:
enum DateError: String, Error {
case invalidDate
}
let journalEntryDecoder: () -> BodyDecoder = {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
let container = try decoder.singleValueContainer()
let dateNum = try? container.decode(Double.self)
if let dateNum = dateNum, let timeInterval = TimeInterval(exactly: dateNum) {
let dateValue = Date(timeIntervalSinceReferenceDate: timeInterval)
return dateValue
}
guard let dateStr = try? container.decode(String.self) else {
throw DateError.invalidDate
}
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
if let date = formatter.date(from: dateStr) {
return date
}
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssXXXXX"
if let date = formatter.date(from: dateStr) {
return date
}
throw DateError.invalidDate
})
return decoder
}
-
将导入
Kitura的语句更新为:
import KituraContracts
-
在
initializeEntryRoutes函数的底部添加以下代码:
app.router.decoders[.json] = journalEntryDecoder
构建并运行服务器,再次打开网页客户端,尝试添加新的表情符号,应该可以正常显示。
4. 删除网页上的日记条目
每个卡片右上角都有一个大的“X”按钮,现在让它真正起作用。确保服务器正在运行,打开
home.stencil
文件,在函数列表的底部添加以下代码:
function deleteEntry(entryID) {
var xhr = new XMLHttpRequest();
xhr.open("DELETE", "/entries/" + entryID);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader("Authorization", "Basic " + btoa(getCookie("username") + ":" + getCookie("password")));
xhr.onreadystatechange = (() => {
if (xhr.readyState == XMLHttpRequest.DONE) {
location.reload();
}
});
xhr.send();
}
在 HTML 元素中调用这个函数:
<input id={{ entry.id }} class="delete-button" type="submit" value="✕" onClick="deleteEntry(this.id)" onEntry="hideEmojiPicker()">
保存文件并刷新网页。尝试删除日记条目,你会发现只能删除使用相同登录信息创建的条目。
5. 仅查看自己的日记条目
由于浏览器会在每个请求中包含所有 cookies,我们可以利用这一点来过滤出不需要的条目。打开
WebClientRoutes.swift
文件,在
showClient
路由处理程序中,创建
sortedEntries
的代码行下方添加以下代码:
if let username = request.cookies["username"]?.value, !username.isEmpty {
sortedEntries = sortedEntries.filter { $0.user == username }
}
由于
sortedEntries
之前被声明为常量,需要将其更新为变量:
var sortedEntries = ...
构建并运行服务器,回到网页浏览器。以一个用户身份登录,应该只能看到该用户的条目。以该用户身份添加一个条目,注销并以另一个用户身份登录,也能正常显示对应条目。
6. 使用第三方服务扩展应用
接下来,我们将使用 Fortune Cookie API 为每个日记条目添加一条随机的幸运信息。
6.1 Fortune Cookie API 介绍
Fortune Cookie API 是一个简单的托管 API,可通过
http://yerkee.com/api
访问,支持多种类别,如
all
、
computers
、
cookie
等。可以使用以下
curl
命令进行测试:
curl -X GET "http://yerkee.com/api/fortune/all" -H "accept: application/json"
返回的 JSON 响应包含一个
fortune
字段,例如:
{"fortune":"Indecision is the true basis for flexibility."}
6.2 创建 Fortune 类型
为了使用 Swift 的
Codable
功能解析 JSON 响应,我们需要创建一个对应的 Swift 数据类型。可以使用一些在线工具,如
JSON Cafe
来生成。
1. 打开浏览器访问
http://www.jsoncafe.com
,确保选择
Model Generator
选项卡。
2. 将上述示例 JSON 响应粘贴到 JSON 编辑器窗口中。
3. 设置
Root Class
为
Fortune
,
Code Template
为
Swift Codable
。
4. 点击
Generate
按钮,将生成的
Fortune.swift
文件添加到项目的
Sources/Application/Models/
目录中。
6.3 使用 SwiftyRequest 进行 REST 请求
为了从
EmojiJournalServer
调用
Fortune Cookie API
,我们将使用
SwiftyRequest
库。
1. 在
Package.swift
文件的
dependencies
数组中添加以下依赖:
.package(url: "https://github.com/IBM-Swift/SwiftyRequest.git", .upToNextMajor(from: "1.0.0")),
-
在
Application目标的依赖列表中添加"SwiftyRequest":
.target(name: "Application", dependencies: [ "Kitura", "CloudEnvironment","SwiftMetrics","Health","KituraOpenAPI","SwiftKueryPostgreSQL","SwiftKueryORM","CredentialsHTTP", "KituraStencil", "SwiftyRequest"]),
-
打开
Sources/Application/Models/Fortune.swift文件,添加以下导入语句:
import SwiftyRequest
import LoggerAPI
-
创建
FortuneClient类:
class FortuneClient {
private static var baseURL: String {
return "http://yerkee.com"
}
private static var fortuneURL: String {
return "\(baseURL)/api/fortune/all"
}
public static func getFortune(completion: @escaping (String?) -> Void) {
let errorFortune = "No fortune is good fortune"
let request = RestRequest(method: .get, url: fortuneURL)
request.responseObject() {
(response: RestResponse<Fortune>) in
switch response.result {
case .success(let result):
let fortune = result.fortune
return completion(fortune)
case .failure(let error):
Log.error("FortuneClient request failed with \(error)")
return completion(errorFortune)
}
}
}
}
6.4 容错和熔断机制
为了确保应用在远程服务出现问题时仍能正常运行,我们将使用
SwiftyRequest
的熔断机制。
1. 在
Fortune.swift
文件的顶部添加以下导入语句:
import CircuitBreaker
-
在
getFortune函数中,在定义request之前配置熔断参数:
let errorFallback = {
(error: BreakerError, msg: String) -> Void in
Log.error("FortuneClient fallback with \(error)")
return completion(errorFortune)
}
let circuitParameters = CircuitParameters(timeout: 2000, maxFailures: 2, rollingWindow: 5000, fallback: errorFallback)
-
在定义
request之后,添加以下代码:
request.circuitParameters = circuitParameters
完整的
getFortune
函数如下:
public static func getFortune(completion: @escaping (String?) -> Void) {
let errorFortune = "No fortune is good fortune"
let errorFallback = {
(error: BreakerError, msg: String) -> Void in
Log.error("FortuneClient fallback with \(error)")
return completion(errorFortune)
}
let circuitParameters = CircuitParameters(timeout: 2000, maxFailures: 2, rollingWindow: 5000, fallback: errorFallback)
let request = RestRequest(method: .get, url: fortuneURL)
request.circuitParameters = circuitParameters
request.responseObject() {
(response: RestResponse<Fortune>) in
switch response.result {
case .success(let result):
let fortune = result.fortune
return completion(fortune)
case .failure(let error):
Log.error("FortuneClient request failed with \(error)")
return completion(errorFortune)
}
}
}
通过以上步骤,我们不仅实现了 Emoji 日记应用的基本功能,还集成了第三方服务,为应用添加了更多的功能和容错能力。
开发 Emoji 日记应用:功能实现与服务集成
7. 功能总结与流程梳理
为了更好地理解整个开发过程,我们来梳理一下各个功能的实现流程。以下是一个功能实现的流程图:
graph LR
A[页面加载函数更新] --> B[添加日记条目]
B --> C[处理日期格式问题]
C --> D[删除日记条目]
D --> E[仅查看自己的日记条目]
E --> F[使用第三方服务扩展应用]
F --> F1[创建 Fortune 类型]
F --> F2[使用 SwiftyRequest 进行 REST 请求]
F --> F3[容错和熔断机制]
下面是各个功能的简要总结表格:
| 功能 | 实现方式 | 关键代码 |
| ---- | ---- | ---- |
| 页面加载函数更新 | 更新页面加载时执行的函数 |
javascript $(function() { window.emojiPicker = new EmojiPicker({ emojiable_selector: '[data-emojiable=true]', assetsPath: '/img/', popupButtonClasses: 'fa fa-smile-o' }); window.emojiPicker.discover(); var loggedInUser = document.getElementById('logged-in'); loggedInUser.innerHTML = "Logged in as: " + getCookie("username"); });
|
| 添加日记条目 | 在 JavaScript 中编写函数发送 POST 请求 |
javascript function addEmoji() { var date = new Date(); var dateString = date.toISOString(); var emoji = $("#add-new-emoji-field").val(); if (emoji == "") { alert("You must enter an emoji!"); return; } var xhr = new XMLHttpRequest(); xhr.open("POST", "/entries"); xhr.setRequestHeader("Content-Type", "application/json"); xhr.setRequestHeader("Authorization", "Basic " + btoa(getCookie("username") + ":" + getCookie("password"))); xhr.onreadystatechange = (() => { if (xhr.readyState == XMLHttpRequest.DONE) { if (xhr.status == 201) { location.reload(); } else { alert(`There was an error adding this emoji: ${xhr.statusText}`); } } }); var body = JSON.stringify({ "emoji": emoji, "date": dateString }); xhr.send(body); }
|
| 处理日期格式问题 | 使用 Kitura 的自定义解码功能 |
swift enum DateError: String, Error { case invalidDate } let journalEntryDecoder: () -> BodyDecoder = { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in let container = try decoder.singleValueContainer() let dateNum = try? container.decode(Double.self) if let dateNum = dateNum, let timeInterval = TimeInterval(exactly: dateNum) { let dateValue = Date(timeIntervalSinceReferenceDate: timeInterval) return dateValue } guard let dateStr = try? container.decode(String.self) else { throw DateError.invalidDate } let formatter = DateFormatter() formatter.calendar = Calendar(identifier: .iso8601) formatter.locale = Locale(identifier: "en_US_POSIX") formatter.timeZone = TimeZone(secondsFromGMT: 0) formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX" if let date = formatter.date(from: dateStr) { return date } formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssXXXXX" if let date = formatter.date(from: dateStr) { return date } throw DateError.invalidDate }) return decoder }
|
| 删除日记条目 | 在 JavaScript 中编写函数发送 DELETE 请求 |
javascript function deleteEntry(entryID) { var xhr = new XMLHttpRequest(); xhr.open("DELETE", "/entries/" + entryID); xhr.setRequestHeader("Content-Type", "application/json"); xhr.setRequestHeader("Authorization", "Basic " + btoa(getCookie("username") + ":" + getCookie("password"))); xhr.onreadystatechange = (() => { if (xhr.readyState == XMLHttpRequest.DONE) { location.reload(); } }); xhr.send(); }
|
| 仅查看自己的日记条目 | 在 Swift 中根据 cookie 过滤条目 |
swift if let username = request.cookies["username"]?.value, !username.isEmpty { sortedEntries = sortedEntries.filter { $0.user == username } }
|
| 使用第三方服务扩展应用 | 创建 Fortune 类型、使用 SwiftyRequest 进行请求、添加容错和熔断机制 | 创建 Fortune 类型:使用
JSON Cafe
工具;使用 SwiftyRequest 进行请求:添加依赖、编写
FortuneClient
类;容错和熔断机制:添加
CircuitBreaker
导入、配置熔断参数 |
8. 实际应用中的注意事项
在实际开发和应用过程中,还有一些需要注意的地方:
-
安全问题
:
- 目前使用的是基于 cookie 的用户会话管理,建议未来使用基于令牌的身份验证,避免将用户名和密码以明文形式存储在 cookie 中。
- 在处理用户输入时,要进行严格的验证和过滤,防止 SQL 注入、XSS 攻击等安全问题。
-
性能优化
:
- 对于频繁调用的 API 请求,考虑使用缓存机制,减少不必要的网络请求。
- 对数据库查询进行优化,避免全表扫描等低效操作。
-
兼容性问题
:
- 不同浏览器对 JavaScript 和 CSS 的支持可能存在差异,需要进行充分的测试,确保应用在各种主流浏览器上都能正常显示和使用。
- 对于 Swift 代码,要注意不同版本的 Swift 编译器和运行时环境的兼容性。
9. 总结
通过以上一系列的操作,我们成功实现了 Emoji 日记应用的多个重要功能,包括页面加载函数更新、日记条目的添加与删除、日期格式处理、用户条目过滤以及使用第三方服务扩展应用等。同时,我们还引入了容错和熔断机制,提高了应用的稳定性和可靠性。
在开发过程中,我们使用了多种技术和工具,如 JavaScript、Swift、jQuery、Kitura、SwiftyRequest 等,充分展示了如何将不同的技术栈结合起来,构建一个功能丰富、性能稳定的 Web 应用。
希望这些内容能为你在开发类似应用时提供一些参考和帮助,你可以根据实际需求对代码和功能进行进一步的扩展和优化。
超级会员免费看
1197

被折叠的 条评论
为什么被折叠?



