这是我在 2018 年初开始并于 2022 年完成的个人开发项目Immersive Communities — 一个面向内容创作者的社交媒体平台的一个完整的故事。
我希望它能为任何正在开始一个大型项目或正在建设一个项目并需要继续前进的动力的人提供指导。
早在 2018 年初,微弱的声音就开始出现。人们开始对网络和社交媒体的总体状况发表意见。
一些人对 Facebook 及其用户隐私政策不满意,其他人则抱怨 YouTube 及其货币化做法,而另一些人则将他们的观点转而反对他们发表意见的平台,例如 Medium.com。
但是所有这些的替代方案会是什么样子?
建立社交媒体平台的想法来自于希望内容创作者能够不仅以个人身份创作内容,而且还与他们的社区互动,让他们为内容创作过程做出贡献,同时能够将他们的努力货币化;都在一个地方。
该平台应该可以免费使用,任何人都应该能够加入。不言而喻,那些将时间投入到工作中,教育和娱乐世界各地人们的创作者也应该能够以他们喜欢做的事情为生。
这是否能解决我们今天在网上面临的所有问题?不,但这是一个替代方案,这才是最重要的。这是一粒种子,随着时间的推移,它会变成美丽的东西。
作为一名技术专家,我还想知道是否所有这些都可以由一个人完成。
一个人真的有可能迎接挑战并提供一个人们喜欢使用的强大的企业级平台吗?如果是的话,提供这样一个系统需要什么?
老实说,我对这些问题没有答案,但我决定接受挑战。
在 2018 年初,我作为软件开发人员只有大约 6 年的工作经验,主要是后端 .NET 和一些前端的 WPF。
我也一直住在东京,我在不到 3 年前搬到那里,加班是常态;至少可以说,出于所有意图和目的而进行额外工作的想法是不合理的。
该平台本身,如果它想提供给尽可能多的人,就需要成为一个网站,并且几乎没有 Web 开发经验,这似乎都无法实现。
事实上,这似乎是不可能的,这正是我决定早点而不是晚点开始的原因。所有这一切都会是浪费时间吗?只有时间会证明一切,但我越早开始,就越早找到答案。
然后,在2018 年 2 月 1 日,我开始计划。
我决定做的第一件事就是告诉自己,认为这并不容易,这是对世纪的轻描淡写。我以前从来没有做过这样的事情,所以我真的不知道自己在做什么。
这意味着在这件事完成之前,没有任何爱好或任何类似生活的东西。在我的全职工作中加班,然后回家做这个项目,这将成为新的常态。
我会在某个时候休假和旅行吗?是的,但我必须在这些假期里大部分时间都在工作。
如果我从以前的经验中学到了什么,那就是有条理是成就或破坏您的项目并让您保持正轨的原因。前期准备和设计越多,以后需要做的试错工作就越少。
思考和计划当然比构建更容易,耗时更少,所以我在计划阶段做的越多,在实际开发阶段发现的就越少,这在时间上需要更多的资源。
资源是我没有的,所以我必须通过规划和设计来弥补。我也知道当我遇到困难时,网络是去的地方,但我决定我不会在这个项目中使用 Stack Overflow 并提出任何新问题。
这个决定背后的原因很简单。看到这里有很多东西要学,如果我只是去请人解决我所有的问题,我不会获得任何经验。
项目进展得越深入,它就会变得越困难,而且我将没有任何经验来独自解决它。
因此,我决定只在网上搜索已经存在的答案,而不是为了解决我的问题而提出新问题。一旦项目完成,我可以再次使用 Stack Overflow,但对于这个特定的开发目标,它是禁止的。
我会利用OOAD方法来设计我想要构建的系统。系统会告诉我每个部分是如何工作的,以及它如何与系统的其他部分交互。此外,我将提取稍后将在代码中实现的业务规则。
然后我开始记笔记,并意识到我必须关注两个要点:
因为我知道一天只有 24 小时,而且大部分时间我都会花在工作或旅行上,所以我必须仔细优化我的时间。
对于项目设计部分,为了最大限度地利用我的时间,我决定看看哪些产品已经运作良好,并将其用作灵感来源。
显然,系统需要快速且可供所有人访问,而且由于我没有时间为多个平台编写代码,因此网络就是答案。
然后我转向设计。 Apple 以其广为接受的设计实践而闻名,这是一个直接的灵感来源。接下来,我转向 Pinterest,并认为这是最简单且效果很好的设计。我的决定背后的想法是那句老话。
内容为王。 ——比尔·盖茨,1996 年。
这让我想到了尽可能多地删除不必要的设计细节并专注于呈现内容。如果您考虑一下,人们会访问您的网站一次,因为它有一个很好的设计,但除非您有好的内容,否则他们不会一直回来。
这具有减少设计前端所需时间的效果。
系统本身必须简单。每个用户都应该能够创建和拥有自己的社区。其他用户应该能够作为成员加入并撰写文章以向该社区贡献内容。此功能将受到维基百科的启发,许多用户可以在其中编辑同一篇文章。
如果有很多人,就某个话题一起参与,那么这告诉我们,我们手头有一个社区,因此应该与其他人分开。
就功能而言,用户不仅需要能够编写常规文章并像 Wikipedia 那样将它们连接起来,还需要编写评论,这需要不同类型的文章。
因此,常规文章将被称为“兴趣”,并且本质上是事实和信息性的,任何人都可以编辑它们。
另一方面,“评论”将基于每个兴趣,只有评论的作者才能编辑它。
简而言之,人们可以合作写一部电影,比如说“黑客帝国”,然后随时编辑该文章。他们可以在他们想要的文章中添加任何事实信息。
然后每个用户都可以写下自己对该电影的评论。自然,原始文章将显示为这部电影撰写的所有评论的列表。
我很清楚,我还必须添加两个更重要的功能。第一个是搜索选项,可以搜索每个社区中的文章。另一个功能是推荐,一旦用户滚动到文章末尾,就会根据他们喜欢的内容为用户提供服务。
每个用户还应该能够在他们的个人资料中发布简短的更新或“活动”,这将充当 Facebook 上的墙。这是我在开始考虑可以用来实际交付项目的技术之前制定的基本大纲。
我关注的第二件事是技术堆栈。由于我已经决定要构建一个基于 Web 的项目,所以我决定写下当时最常用的所有流行和现代技术。然后我会选择那些在技术上与我在职业生涯中已经使用过的技术最接近的技术,以便尽可能少地花时间学习新技术。
此外,在这个阶段最让我思考的想法是做出这样的设计决策,这需要我编写尽可能少的代码,从而节省时间。
经过广泛的研究,我确定了以下主要技术堆栈:
此外,通过选择 SaaS 服务,我将进一步节省时间,因为我不需要自己实现这些功能。我决定的服务如下。
这些是我必须尽快学习的主要技术,甚至开始从事该项目。其他一切都必须在此过程中学习。
在这一点上,我开始学习新技术。对于主要和最重要的,我开始阅读以下书籍。
我决定白天从事全职工作,但晚上是公平的游戏。当我回到家时,我可以学习直到我睡觉。就睡眠本身而言,我会被缩短到 6 个小时,以获得更多的学习时间。
我最初预测我只需要一年的时间来构建这个项目。然后,在学习了将近一年的新技术之后,到了 2018 年 12 月,我刚刚完成了主要的阅读材料,同时写了 0 行代码。
2018 年 12 月,我终于开始开发了。我设置了 Visual Studio Code 并开始构建我的开发环境。
从第一天起我就很清楚,我将无法维护如此大型项目所需的所有服务器。不仅从技术角度来看,而且从预算方面也是如此。因此,我必须找到解决方案。
幸运的是,我找到了DevOps形式的解决方案和后端基础架构的无服务器方法。
甚至在这些方法像今天这样广泛流行之前,我就立即清楚地知道,如果我们可以用代码简洁地描述某些东西,我们也可以将其自动化,从而节省时间和资源。
使用 DevOps,我将统一和自动化前端和后端开发,而使用无服务器,我将消除对服务器维护的需求并降低运营成本。
这种理念显然符合我的想法,我决定建立的第一件事是使用 Terraform 建立 CI/CD 管道。
该设计包括 3 个分支,开发、UAT 和生产。我每天都会工作,工作完成后,我只需将更改提交到 AWS CodeCommit 中的开发分支,这将触发 AWS CodeBuild 来构建我的项目。
我会在存储库中保留一个 Terraform 可执行文件,用于 macOS,用于本地测试,以及一个基于 Linux 的,用于在 CodeBuild 上构建。
构建过程开始后,将在 CodeBuild 中调用 Terraform 可执行文件,它会获取 Terraform 代码文件,从而在 AWS 上构建我的基础设施。
然后,所有这些部分都必须连接并自动化,我使用 AWS CodePipeline 完成了这项工作,每次我提交时都会将代码从 CodeCommit 移动到 CodeBuild。
这将帮助我保持我的代码和我的基础设施同步。每当我准备好继续前进时,我只需将 Development 分支合并到 UAT,或将 UAT 合并到 Production 以同步我的其他环境。
完成后端的 CI/CD 管道后,我将转向在本地设置实际网站,以便开始开发前端。
设置前端的第一步是创建一个基于 Aurelia 的项目。使用 Aurelia 而不是 React(过去和现在仍然是 JavaScript 框架最流行的选择)背后的决定是因为MVVM模式。
MVVM 模式在我使用过的 WPF 桌面应用程序中得到了显着的应用。因此,学习 React 要比简单地建立在我已经知道的基础上花费更多的时间。
另一方面,使用 Aurelia 而不是 Angular 或 Vue 的决定是基于 Aurelia 背后的理念——让框架摆脱你的束缚。也就是说,奥里利亚没有奥里利亚。
在使用 Aurelia 进行开发时,您基本上使用的是 HTML、JavaScript 和 CSS,以及一些附加功能,例如我已经熟悉的属性的数据绑定。
因此,该决定为最终决定。接下来,来自静态类型的 C# 世界,我决定使用TypeScript而不是 JavaScript。
接下来是 WebPack。显然需要将应用程序拆分为有助于延迟加载的块。由于我已经知道该应用程序中将有许多功能,因此必须将其构建为能够按需加载部件的 SPA。
否则,用户体验将是不可接受的。
为了更容易处理 WebPack 配置,我决定将Neutrino.JS添加到组合中,并使用其流畅的界面来设置 WebPack。
众所周知,早在 2019 年,移动 Web 浏览就开始兴起。为了为现代 Web 做好准备,开发方法定义如下。
这得益于两项主要技术——Tailwind CSS和 PWA。在样式方面,为了进一步简化系统,我添加了 Tailwind CSS,结果证明这是我做出的最佳决定之一,因为它默认是移动优先的。
此外,它是基于实用程序的,因此它具有很高的可重用性,这正是我所寻找的。
此外,由于我试图提供类似原生的体验,但没有时间构建原生应用程序,我决定选择下一个最好的东西——可以直接从浏览器安装并离线工作的应用程序。
这是 Progressive Web Apps (PWA) 旨在为用户提供的,但手动设置太容易出错且耗时,因此我决定使用具有 Service Worker 安装、离线和缓存功能的Google WorkBox内置。
核心设置完成后,就该在网上试一试了。很明显,任何人都应该随时可以访问该网站,中断不属于现代系统,因此我决定按以下方式设置系统。
首先,HTML 和 JS 文件将从 AWS S3 存储桶提供。为了使其在全球范围内以低延迟可用,它将由 AWS CloudFront CDN 网络提供前端。
在这种情况下,一个小小的挫折是当我决定使用无服务器框架时,因为设置AWS Lambda函数比使用 Terraform 更容易,因此我引入了一项我必须处理的新技术。
设置完成后,我购买了 AWS Route 53 中的域,用于测试和生产域 - https://immersive.community 。
这个名字背后的想法来自一个类似命名的基于社区的网络——Mighty Networks 。我只是决定使用“沉浸式”这个词,因为当时它在谷歌上开始流行起来。
由于“immersive.networks”和“immersive.communities”已经被占用,我选择了“immersive.community”。
现在我已经启动了前端,是时候开始处理数据库了。尽管我过去习惯于使用基于 SQL 的关系数据库,但对于这个特定项目来说它们显然太慢了,所以我决定使用 NoSQL 数据库。我选择 AWS DynamoDB 是因为它的无服务器产品。
为了访问数据库,我选择了 AWS AppSync,它是一种托管的 GraphQL 实施,也是无服务器的。
此时,是时候开始解决我面临的最大问题之一了,即:
如何允许用户加入多个社区,但将每个社区中的私有或受限数据彼此分开?
解决这个问题的最简单方法是创建多个数据库,但这有明显的局限性,因为在某些时候,我们可能会用完我们可以创建的数据库。
事实证明,每个 AWS 账户对您可以创建的资源数量都有限制,因此这不是一个可行的解决方案。
然后,我将最终通过为 DynamoDB 数据库中的每个条目分配一个类型列来解决这个问题。每个用户都将其类型设置为“用户”,而每个社区将简单地设置为“网络”。
然后我会通过添加一个新行来表明用户已加入社区,该行的键被指定为“user#web_user#web”。由于用户和社区名称是唯一的,因此该密钥也是唯一的,因此用户不能多次加入社区。
如果我想执行只有在用户加入社区时才能执行的操作,我会简单地使用 AppSync 提供的管道函数,它允许您查询 DynamoDB 中的多行。
然后我会查询并检查用户是否是社区的成员,只有当他是时,才允许用户执行操作。
这解决了多租户问题,但要解决的最大问题之一指日可待。
不用说,企业级系统的构建考虑了容错性和高可用性。这将允许用户继续使用系统,即使它的某些组件出现故障。
如果我们想实现这样的系统,我们应该考虑到冗余。
我的研究使我找到了这种情况下的最佳解决方案,即主动-主动高可用性架构。事实证明,AWS 上的大多数服务已经具备高可用性,但 AppSync 本身并非如此。因此,我决定创建自己的实现。
网络没有为这个问题提供解决方案,所以我必须自己构建。我开始思考全局。这意味着,我的访问者将来自不同的地区,如果我将 AppSync 放在美国,那么亚洲的访问者会有更高的延迟。
我通过以下方式解决了延迟和高可用性问题。当时我决定在所有可用区域创建 10 个不同的 AppSync API。目前,API 位于美国、亚洲和欧洲。
此外,每个 API 都需要连接到位于同一区域的相应 DynamoDB 数据库。因此,我进一步创建了 10 个额外的 DynamoDB 表。
幸运的是,DynamoDB 提供了一个全局表功能,可以在连接的 DynamoDB 表之间复制数据并使它们保持同步。
现在,无论用户在哪里写入数据库,在数据同步后,不同区域的用户都可以读取相同的信息。
现在出现的问题如下。
如何将用户路由到最近的 API?不仅如此,如果一个 API 发生故障,我们如何立即将调用路由到下一个可用 API?
该解决方案以 CloudFront 和Lambda@Edge函数的形式出现。这是 CloudFront 的一个惊人功能,可以在调用者所在的区域触发 Lambda@Edge 函数。
应该清楚的是,如果我们知道用户所在的位置,我们可以在 Lambda@Edge 函数中根据调用的来源选择 API。
此外,我们还可以获取执行 Lambda@Edge 函数的区域,从而允许我们在同一区域中选择 AppSync API。实施此解决方案的第一步是通过 CloudFront 代理 AppSync 调用。
因此,现在将直接调用 CloudFront 而不是 AppSync。
然后,我必须从 Lambda@Edge 函数内的 CloudFront 参数中提取 HTTP 调用。获得区域和从 CloudFront 参数中提取的 AppSync 查询后,我将对相应的 AppSync API 进行新的 HTTP 调用。
当数据返回时,我只需通过 Lambda@Edge 函数将其传递回 CloudFront。然后,用户将获得所请求的数据。
但是我们还没有解决 Active-Active 的需求。现在的目标是检测 API 不可用的时间点,然后切换到另一个 API。我通过检查 AppSync 调用的结果解决了这个问题。如果不是 HTTP 200 响应,那么调用显然失败了。
然后,我只需从所有可用区域的列表中选择另一个区域,然后调用该区域中的下一个 AppSync API。如果调用成功,我们返回结果,如果失败,我们尝试下一个区域,直到成功。
如果最后一个区域也失败了,那么我们只需返回失败的结果。
这只是主动-主动高可用性架构的循环实现。有了这个系统,我们实际上已经实现了以下三个功能:
对于每个全球用户,我们显然平均具有低延迟,因为每个用户将被路由到他调用 API 的最近区域。我们还有基于区域的负载均衡,因为用户将被路由到他们所在区域的多个 API。
最后,我们有一个主动-主动高可用性,因为即使它的某些 API 或数据库发生故障,系统仍将保持正常运行,因为用户将被路由到下一个可用的 API。
实际上,仅仅处理 API 的高可用性是不够的。我想为所有资源使用它,包括从 CloudFront 提供的 HTML 和 JavaScript 文件。
这一次,我使用了相同的方法,但创建了 16 个 AWS S3 存储桶。每个存储桶将提供相同的文件,但将位于不同的区域。
在这种情况下,当用户访问我们的网站时,浏览器将对 HTML、JS、JSON 或图像文件进行多次 HTTP 调用。在这种情况下,Lambda@Edge 必须提取当前正在调用的 URL。
获得 URL 后,我必须确定此文件的文件类型并对区域中的相应 S3 存储桶进行新的 HTTP 调用。
不用说,如果调用成功,我们会返回文件,如果调用失败,我们会使用和之前一样的路由系统,这样也提供了一个主动-主动的高可用系统。
现在有了这个系统,我们已经达到了另一个里程碑,并为我们的企业级基础设施奠定了另一块基石。这是迄今为止最难开发的系统,花了 3 个月的时间才完成。
事实证明,我们有更多的问题需要解决,这个系统将再次证明是有用的。
PWA 是一项了不起的网络技术,随着时间的推移将被更多的网站使用,但早在 2019 年,事情才刚刚开始。
由于我决定在一个单独的子域上为每个社区服务,我还希望让用户能够安装他们的品牌 PWA,以及适当的标题和应用程序图标。
事实证明,定义所有这些功能的 PWA Manifest 文件不能基于子域工作。它只能根据提供服务的域定义一组值。
我已经可以使用 CloudFront 和 Lambda@Edge 代理 HTTP 调用这一事实在这里也派上了用场。
现在的目标是将每个调用代理到 manifest.json 文件。然后,根据调用来自哪个子域,为了获取相应的社区数据,这将是应用程序图标、标题等,然后我们将使用这些值动态填充 manifest.json。
然后该文件将提供给浏览器,然后社区将作为新的 PWA 应用程序安装在用户的设备上。
一旦我弄清楚了这些关键步骤,就该开始在前端工作了。根据之前基于子域的要求,我们还必须弄清楚如何根据子域加载不同的社区及其数据。
这还需要加载不同的网站布局,这些布局将在每个社区中使用。
例如,主页需要列出所有可用的社区,而其他子域需要列出每个社区的文章。
不用说,为了解决这个问题,我们不能简单地从头开始构建多个不同的网站。这不会扩展,因此,我们需要尽可能多地重用控件和功能。
这些功能将在这两种社区类型之间共享,然后仅在需要时才加载。为了最大限度地提高代码的可重用性,我将所有控件定义为 4 种不同的类型。
<button> 和 <input> 等最小的自定义 HTML 元素被定义为组件。然后我们可以在控件中重用这些组件,这些组件是这些较小元素的集合,例如,个人资料信息控件将显示用户的个人资料图像、用户名、关注者等。
然后,我们将能够在更高级别的元素中重用这些元素,在我们的例子中是 - 页面。
每个页面基本上代表一条路线,例如,我们可以看到所有活动的 Trending 页面或显示实际文章文本的 Interest 页面。然后,我将从这些较小的控件组成每个页面。
最后,最高级别的元素将根据其类型在社区中定义。然后,每个社区元素将简单地定义它需要的所有较低级别的页面。
Aurelia 路由器在这种情况下派上用场,看看您如何动态加载路由。实施按以下方式处理。
不管子域是什么,当网站开始加载时,我们注册两个主要的分支,它们被实现为 Aurelia 组件。它们代表两种不同的社区类型。然后我定义了两种不同的 Web 类型或布局:
“主”类型仅表示当用户登陆主https://immersive.community页面时将加载的网站布局。在这里,我们将显示具有所有相应控件的所有社区。
另一方面,一旦用户导航到子域,我们就需要加载不同的布局。换句话说,我们将加载文章以及相应的功能和路线,而不是社区,例如,发布和编辑文章的能力。
这将根据我们所在的社区类型启用或禁用某些路由。
我们的 Aurelia 和 WebPack 设置将 JavaScript 拆分为适当的块,因此根本不会加载不需要的路由和功能,从而提高速度并节省带宽。
此时,一旦我们确定了我们所在的子域,我们将加载该特定社区的社区和用户数据,从而成功实施了解决方案。
我的理由是我们应该尽量保持设计尽可能简单。因此,由于用户来到网站是为了内容,我们将专注于显示内容,而不是次要功能。
这些文章需要显示在列表中,但它们不应看起来陈旧,因此我决定每篇文章仅包含以下内容。
我确保文章列表不会过时的主要方法是确保用户可以为他们的文章选择每张封面照片的纵横比。灵感来自 Pinterest 显示其图钉的方式,因此每篇文章也会有不同的纵横比。
这需要我实现砌体布局,不能在 CSS Grid 或 FlexBox 中直接选择。
幸运的是,我尝试了几个有用的开源实现并将其用于布局。我必须添加一些改进,例如加载分页数据和随屏幕大小缩放。
接着…
2019 年 11 月, COVID-19的最初迹象开始出现。世界很快就陷入了混乱,没有人知道发生了什么,但它会改变世界以及我们如何以无人想象的方式相互交流。
不久之后,我们将开始在家工作。这将对我的开发过程产生很大影响,因为我不再需要出差上班了。具有讽刺意味的是,世界崩溃了,而我却得到了我需要的休息!
回到开发世界,撰写沉浸式社区文章背后的想法是基于协作。为此,我将维基百科作为协作工作的基础。
此外, Amino Apps和Fandom.com等社区网站以及博客网站HubPages.com也发挥了作用。
以一个人的身份写博客文章可能是一个好的开始,但我们可以通过让人们一起写文章来超越这一点。
一旦我们在文本中添加超链接并将这些不同人撰写的文章联系起来,我们基本上就创建了一个社区,人们聚集在一起参与他们感兴趣的话题。
我决定定义两种类型的文章,即
兴趣将是简短的文章,大约。 5000 个字符长的文章,如实描述一个人可能有的任何特殊兴趣。然后,每个人都可以写一篇评论,并对这个特殊兴趣进行评分。
然后,主要兴趣页面将包含对针对该特定兴趣编写的所有评论的引用。主要区别在于任何人都可以编辑兴趣,但只有撰写评论的人才能编辑它,从而为每篇文章添加个人风格。
在这里,我们之前使用 CloudFront 代理 AppSync 调用的决定又回来了。事实证明, CloudFront 仅支持最长为 8,192 字节的查询字符串,因此,我们无法保存超过此长度的数据。
就每篇文章而言,每个兴趣都可以被点赞和评论。因此,用户可以聚在一起讨论如何编写和编辑每篇文章。此外,可以将每个兴趣添加到用户的个人资料页面,以便快速访问。
一旦所有这些功能都到位,年底就到了。情况看起来不错,我确信该项目将在明年完成。至少可以说,这个假设并不准确。
这一年或多或少地开局良好。经济仍然保持一定程度,但过了一段时间,开始走下坡路。市场开始对大流行做出反应,同样,价格开始上涨。
2020 年初是我投入大量工作但没有真正有效的产品的一年。还有很多事情要做,但我对结果很有信心,所以我继续向前推进。
在我的日常工作中,工作时间也延长了,我们必须比平时更快地完成最后期限。不用说,我不得不重新安排我的日程安排,而节省更多时间的唯一方法就是每晚只睡 4 个小时。
当时的想法是在下午 6 点或 7 点之前回家,然后直接开始工作。然后我可以工作到凌晨 3 点或 4 点,然后睡觉。然后我必须在早上 7 点左右醒来,然后迅速开始我的日常工作。
这当然是每晚睡眠不足,但我想我会在周末睡 12 个小时来弥补那段时间。我还为工作安排了所有假期和公共假期。
新系统建立起来,我按计划进行。
不言而喻,一个文章写作网站应该有一个易于使用的文本编辑器。在 2020 年初, Markdown成为一种非常流行的文本编写方式。我决定沉浸式社区必须开箱即用地支持它。
这不仅需要我编写 Markdown,还需要将其显示为 HTML。 Markdown-It库将用于将 Markdown 转换为 HTML。但是还有一个额外的要求,所以我们应该显示的不同媒体的完整列表如下。
此外,图像和视频应该显示为滑块,用户可以像在 Instagram 上一样在其中滑动图像。这需要混合使用 Markdown 和其他 HTML 元素。
编辑器将分为几个部分,其中有两种类型的输入,文本字段和媒体字段。编辑器中的每个字段都可以上下移动,这很容易使用Sortable.js实现。
对于输入字段,Markdown 字段非常简单,可以使用 <textarea> 元素创建。编辑器还加载了Inconsolata Google Font ,它为正在输入的文本提供了打字机的外观。
此外,为了实际设置文本样式,实现了一个将 Markdown 添加到文本的栏。使用Mousetrap.js使用键盘快捷键也是如此。现在,我们可以使用 Control+B 等轻松地以 ** Markdown 标签的形式添加粗体文本。
打字时,<textarea> 元素随着文本量的增加而扩展是很自然的,所以我使用Autosize.js库来实现这个功能。
媒体字段将能够显示包含嵌入式网站的图像、视频或 iframe。媒体字段的类型将根据媒体本身的类型进行切换。我使用Swiper.js来实现图像之间的滑动。
视频组件是使用Video.js库实现的。
当需要实际上传媒体时,问题就开始出现了。就图像而言,使用浏览器的文件 API从您的设备加载照片和视频很容易。然后我必须做的是首先将可能是 HEIC 格式的图像转换为 JPEG。
然后我会压缩它们,然后再将它们上传到后端。幸运的是, Heic-Convert和Browser-Image-Compression库很好地满足了这个目的。
当我必须选择正确的图像纵横比并在上传之前对其进行裁剪时,另一个问题出现了。这是使用Cropper.JS实现的,但不幸的是,它在 Safari 浏览器上无法开箱即用。
我花了很多时间设置适当的 CSS 以使图像不会从容器中溢出。最后,用户可以轻松地从他的设备加载图像,放大和缩小,并在上传前裁剪图像。
一切完成后,媒体将上传到 Cloudinary,这是一种用于管理媒体文件的服务。
是时候将所有这些放在一起,并以文章的形式展示给用户了。我很幸运,Aurelia 有一个可以动态加载 HTML 的 <compose> 元素。
因此,根据输入类型,我将加载媒体元素或 Markdown 元素,它们将被转换为 HTML。
然后必须使用 CSS 设置 HTML 样式,尤其是我将根据屏幕大小转换的 HTML 表格。在较大的屏幕上,表格将以常规的水平布局显示,而在较小的屏幕上,它们将以垂直布局显示。
这需要一种事件驱动的方法,它可以告诉我们屏幕尺寸何时以及如何改变。在这种情况下使用的最佳库是RxJs ,它处理“调整大小”事件,我能够相应地格式化表格。
然后我回到文章。我不得不改变将文章保存到数据库的方式,因为在这种情况下,多个人可以同时修改文章。
然后我会将新文章保存为初始文章类型,但每篇文章的实际数据将保存为版本。然后,我将能够跟踪每个用户以及每篇文章的更改时间。
如果用户没有首先加载最新版本,这使我能够防止保存新版本。此外,如果某个更新不合适,它可以被禁用,然后之前的版本将再次可见。每篇文章的草稿将以相同的方式保存。
就实际数据输入而言,我决定将其实现为弹出窗口。弹出窗口本身不会简单地出现在屏幕上,而是会从底部向上滑动。此外,可以在弹出窗口内滑动。
为此,我重用了 Swiper.Js 库,而所有其他动画都是使用Animate.CSS库完成的。
弹出窗口实现起来并不简单,因为它需要随屏幕大小缩放。因此,在较大的屏幕上,它将占用屏幕宽度的 50%,而在较小的屏幕上,它将占用 100% 的宽度。
此外,在某些情况下,比如关注者列表,我实现了包含在弹出窗口中的滚动。这意味着我们正在滚动的列表不会停在顶部,而是会在滚动时消失。
我还添加了更多样式并调暗了背景并禁用了滚动或在弹出窗口之外单击。另一方面,文章编辑系统的预览弹出窗口随屏幕移动。
这是受 Apple 的 Shortcuts 应用程序及其弹出窗口显示方式的启发,这也适用于元素上方的药丸按钮和标题。
我实现的最重要的 UI 功能之一进一步受到 iPhone 的启发,即它的导航栏。我注意到几乎所有的移动应用程序都有一个相当基本的导航栏,带有简单的小图标,这些图标并不适合应用程序的整体设计。
我决定简单地复制 iOS 栏并在整个网站中使用它。不用说,它不应该总是可见的,而是应该在我们向下滚动时消失并在我们向上滚动时出现。
当用户向下滚动时,我们假设他对内容感兴趣并且不会离开当前页面,因此我们可以隐藏栏。
另一方面,如果用户向上滚动,他可能正在寻找离开页面的方法,所以我们不妨再次显示该栏。
栏上有四个按钮,它们允许用户导航到网站的四个主要部分。主页按钮导航到每个社区的主页。 Trending 按钮导航到 Trending 页面,用户可以在其中查看其他用户发布的所有近期活动。
下一个按钮是 Engage 按钮,它导航到社区提供的所有功能和设置的列表。最后,“个人资料”按钮将我们带到我们的个人资料页面。
还需要考虑更大的屏幕,因此在大屏幕上显示时,条实际上会移动到屏幕的右侧。它变得粘稠并且在那一点上不会移动到任何地方。
前端最重要的工作完成后,是时候再次访问后端了。系统的这一部分将被证明是实现起来最复杂的部分,但最终,它非常重要,并且还可以很容易地继续使用其他功能。
在面向对象编程中,存在一个关注点分离的概念,我们保持我们的函数简单,让它们只做一件事,这是他们应该做的。
此外,面向切面编程的思想特别是关于关注点的分离,我们需要将业务逻辑与其他横切关注点分开。
例如,将用户保存到数据库自然应该伴随着日志记录,同时正在处理用户的保存。但是这两个功能的代码应该分开。
我决定全面应用这种推理,并从 UI 中提取尽可能多的特性,这些特性对用户来说并不重要,并将它们移到后端。
在我们的案例中,我们主要关注将数据保存到数据库中,这些数据与社区、文章、评论、点赞等相关。
如果我们想跟踪一篇文章获得了多少赞,我们可以有一个过程来计算每篇文章的所有赞并定期更新它们。
由于我们在这里处理存储在数据库中的大量数据以及可能不断流入数据库的大量数据,因此我们需要采用实时数据处理来处理这种情况。
我为此任务选择了 AWS Kinesis。 Kinesis 能够摄取大量实时数据,我们也可以编写 SQL 查询来近乎实时地查询和批处理这些数据。默认情况下,Kinesis 将批处理数据 60 秒或批处理达到 5 MB,以先到者为准。
因此,在我们的案例中,我们将查询传入的数据、含义、新社区的创建、文章、用户、活动等的添加或删除,并每分钟用新数据更新数据库。现在出现的问题是,我们首先如何将数据输入 Kinesis?
我们选择的数据库 DynamoDB 实际上能够以 Lambda 函数的形式定义在添加、删除或修改数据时调用的触发器。然后,我们将捕获这些数据并将其发送到 Kinesis 进行处理。
碰巧的是,我们早期的一个决定会使这个过程稍微难以实施,因为我们不是在处理 1 个数据库,而是实际上在处理 10 个数据库。
因此,一旦添加数据,Lambda 函数将被调用 10 次而不是一次,但我们需要处理每种情况,因为数据可能来自任何数据库,因为它们位于不同的区域。
我通过过滤掉复制的数据而不是用户添加到数据库的原始数据来解决这个问题。
“aws:rep:updateregion”列为我们提供了这些信息,我们可以确定我们是在处理插入数据的区域中的数据,还是表示复制的数据。
一旦解决了这个问题,我们就可以简单地过滤,添加或删除新数据。此外,我们将根据数据类型过滤数据,这意味着我们正在处理代表社区、文章、评论等的数据。
然后我们收集这些数据,将其标记为“插入”或“删除”并将其传递给 Kinesis。这些来自领域驱动设计方法的想法被称为领域事件,它允许我们确定发生了哪些操作并相应地更新我们的数据库。
然后我们将注意力转向 Kinesis。在这里,我们必须定义系统的三个主要部分
Kinesis Streams 允许我们实时大量摄取数据。 Kinesis Analytics 是一个允许我们实际批量查询这些数据并根据滚动时间窗口聚合它的系统。
聚合数据后,我们会将每个结果进一步推送到 Kinesis Firehose,它可以处理大量数据并将其存储在目标服务中,在我们的例子中,它是 JSON 格式的 S3 存储桶。
一旦数据到达 S3 存储桶,我们就会触发另一个 Lambda 函数并处理这些数据以更新 DynamoDB 数据库。
例如,如果有 5 个人在最后一分钟点赞了某个兴趣,我们会在 JSON 文件中找到此数据。然后,我们将更新此兴趣的点赞数,并增加或减少点赞数。在这种情况下,我们只需将其增加 5 个喜欢。
使用该系统,每个社区的统计数据将在一分钟内保持最新。
此外,当我们需要显示聚合数据时,我们不需要编写和执行复杂的查询,因为准确的结果存储在快速 DynamoDB 数据库中的每条记录中,从而提高了每条记录的查询速度。
这种改进是基于数据局部性的思想
现在是开始实施 3rd 方服务的时候了,它可以处理我需要的功能,但购买订阅比自己构建更容易。我实现的第一个服务是 Cloudinary,它是一种媒体管理服务。
我已经在 Cloudinary 上设置了所有预设,以便为以下响应式屏幕断点急切地转换图像。
这些也将是 Tailwind CSS 中设置的断点,我们的网站将符合手机、小型平板电脑、大型平板电脑和计算机显示器的不同屏幕尺寸。
然后根据当前的屏幕大小,我们将使用 <image> 元素上的scrcset属性适当地从 Cloudinary 调用急切创建的图像。
这将帮助我们节省带宽并缩短在移动设备上加载图像的时间。
就视频功能而言,在实施之后,我决定放弃它,因为 Cloudinary 的视频定价太贵了。因此,即使代码在那里,该功能目前还没有使用,但以后可能会可用。
这将需要我将来在 AWS 上构建一个自定义系统。
我决定使用 Embed.ly 嵌入来自 Twitter、YouTube 等流行网站的内容。
不幸的是,这没有问题,所以我不得不使用多种技术从网站上手动删除 Facebook 和 Twitter 脚本,因为它们会在多次加载后干扰嵌入的内容。
说到搜索,我选择了Algolia,实现了对社区、活动、文章、用户的搜索。前端实现很简单。
我只是创建了一个搜索栏,单击该搜索栏会隐藏应用程序的其余部分,当我们输入时,会显示我们当前正在浏览的特定子域的结果。
一旦我们按下“Enter”,主页上的砌体就会显示符合查询的文章。不用说,我还必须实现分页,它会逐步加载结果,以模仿 Pinterest 的外观和感觉。
当我意识到除非您将整个文本存储在我想避免的 Algolia 中,否则无法实际搜索活动时,问题就出现了。因此,我决定只存储每个活动的相关标签,但问题是如何从每个活动中提取相关标签。
答案以AWS Translate和AWS Comprehend的形式出现。由于要添加到数据库的项目数量会很大,并且我们希望将此数据添加到 Algolia,如果我们要单独添加每条记录,我们可能会超载 API。
相反,我们希望实时分批处理它们,因此我们将再次使用 Kinesis 作为解决方案。
在这种情况下,每次向数据库添加新项目都会触发一个 Lambda 函数,该函数会将数据发送到 Kinesis Data Streams,而 Kinesis Data Streams 又会将数据发送到 Kinesis Firehose(这次不需要 Analytics)并进一步存储它们在 S3 存储桶中。
一旦数据被安全存储,我们将触发一个 Lambda 函数将其发送到 Algolia,但在此之前,我们需要处理这些数据。
特别是,我们需要处理活动,我们将使用markdown-remover库从中删除 Markdown 文本。然后我们将得到明文。一旦我们有了实际的文本,我们就可以继续提取将用于搜索的相关标签。
这可以使用 AWS Comprehend 服务轻松完成,但问题是它仅支持某些语言。因此,如果用户使用不受支持的语言写作,我们将无法提取标签。
在这种情况下,我们只需使用 AWS Translate 并将文本翻译成英文。然后我们继续提取标签,然后将它们翻译回原始语言。
现在,我们只需按预期将标签存储在 Algolia 中。
Pinterest 最重要的功能之一是它的推荐引擎。一旦用户点击了 Pin 图,他会立即看到 Pin 图的全尺寸图片,而在图片下方,我们可以看到基于当前 Pin 图的用户可能喜欢的推荐。
这是提高用户保留率并让他们继续浏览网站的好方法。为了实现这个功能,在我的情况下,它必须向用户展示类似的文章,我选择了 Recombee——这是一个推荐引擎 SaaS。
这次实现比 Algolia 更容易,因为我重用了相同的原则。看到我们需要如何推荐社区、文章和活动,对于用户创建的每个新项目,我会使用 Kinesis 对这些项目进行批处理并将它们发送给 Recombee。
推荐过程基于 Views,这意味着每次用户看到一篇文章时,我们都会将这个特定用户和文章的 View 发送给 Recombee。
我们还可以根据用户与项目的交互方式为 Recombee 中的项目分配其他操作。例如,编写一个新的兴趣将被映射到该兴趣的购物车添加。如果用户喜欢某个兴趣,这将是对评分的补充。
如果用户加入社区,这将被映射到该社区的书签。
基于这些数据,Recombee 将为用户创建推荐。
在前端,我会简单地获取用户当前正在阅读的文章,并获取该特定文章和用户的推荐数据。这将显示在每篇文章的底部,作为分页的砌体列表。
这将为用户提供他可能有兴趣阅读的潜在文章列表。
考虑到该网站从一开始就瞄准全球受众,我也必须实施本地化。对于最初的版本,我决定使用 10 种语言,并选择了一个 SaaS 服务——Locize,它是基于i18next 本地化框架实现的。
我们需要根据数量来本地化单词,意思是单数或复数,并且还必须本地化时间。查看我们如何显示每篇文章的创建时间或上次更新时间。
我选择英语作为默认语言,并使用谷歌翻译将所有单词翻译成德语、日语等其他语言。Aurelia 也支持本地化,这再次非常方便。
完成所有翻译后,我将翻译后的 JSON 文件导入应用程序,并根据社区类型将它们拆分,这样我们就不会加载不会使用的不必要的文本。
然后,Aurelia 允许我们简单地使用模板和绑定来自动翻译文本。但我也使用了值转换器来格式化时间,以显示自撰写文章以来已经过了多长时间,而不是显示实际日期。
此外,我还必须格式化数字,因此我不会显示数字 1000,而是显示 1K。所有这些功能都由Numbro和TimeAgo等库处理。
社区网站需要交流,但不仅仅是在公共场合。它也需要私人通信。这意味着我也需要提供实时私人聊天。此功能是使用 Twilio 可编程聊天服务实现的。
每个用户都可以与每个特定社区中的任何其他用户进行私人聊天。后端实现很容易使用 Twilio 库来实现。当谈到前端时,我决定基于 Instagram 设计聊天样式,因为它的设计简洁明了。
我还选择了一项名为Prerender的服务来预渲染网站,以使其可供搜索引擎爬虫使用。在意识到定价可能是一个问题后,我决定自己构建预渲染系统。
为此,我找到了一个名为Puppeteer的库,它是一个 Headless Chrome API。
该库可用于以编程方式加载网站并返回生成的带有执行 JavaScript 的 HTML,而当时的搜索爬虫并没有这样做。该实现将在 Lambda 函数中加载 Puppeteer,该函数将加载网站、呈现网站并返回 HTML。
我会使用 Lambda@Edge 来检测我的用户何时实际上是爬虫,然后将其传递给预渲染 Lambda。这很简单,只需检测 CloudFront 参数中的“用户代理”属性即可。事实证明,Lambda 无法加载 Puppeteer 库,因为它太大了。
这不是什么大问题,因为我后来找到了chrome-aws-lambda库,它开箱即用地完成了所有这些工作,而且体积要小得多,因为它只使用了我的目的所需要的 Puppeteer 核心。
系统完成后,搜索引擎已经足够强大并开始执行 JavaScript。因此,即使我完成了这个功能,我还是把它关掉了,我只是让搜索引擎自己抓取我的网站。
沉浸式社区的核心功能之一是其收益分享计划,其中用户分享 50% 的会员订阅和广告收入。
如前所述,我们需要让我们的创作者不仅可以创作他们的内容,还可以通过这些内容获利。现在的问题是如何实施这个系统。不用说默认选择是 Stripe,所以我按照以下方式进行。
我决定根据每个社区设计收益分享系统。这样,用户可以创建多个社区并根据每个社区赚取收益。每个社区的收入将来自两个来源。
会员订阅是最容易实现的。我将为会员订阅创建三个价格点,每月 5 美元、10 美元和 15 美元。然后每个社区的成员可以每月支持社区所有者,作为回报,不会显示任何广告。
广告系统基于相同的每月订阅,但每月的费用在 100 美元到 1000 美元之间。想要在特定社区做广告的公司可以简单地选择每月支付金额并设置广告横幅。
假设在一个社区中有多个广告商,广告将在每次页面加载或路径更改时随机选择。与其他广告客户相比,广告客户可以通过增加每月付款金额来提高其广告展示频率的方式。
我们还需要向广告商展示他们的广告效果如何,因此我再次使用 Kinesis 设置来衡量观看次数和点击次数。然后,该系统将照常更新统计数据,然后我使用 Brite Charts 库来显示统计数据。
最重要的部分是实际的收益分享功能。这只是通过Stripe Connect功能实现的。用户只需添加他们的银行账户并连接到 Stripe Express,系统就会拥有发送付款所需的所有信息。
然后,我将有一个预定的 Lambda 系统,该系统将每天获取所有用户并更新交易,并确保每笔交易(会员订阅或广告支付)的 50% 转移到支付所在社区的所有者制成。
最后一个必须实现的服务是Auth0 ,这将有助于用户身份验证。经过一番研究,我决定基于 SMS 消息进行无密码设置。
鉴于我们现在处于移动优先的世界,放弃密码并将身份验证基于每个人已经拥有的东西——他们的手机——才有意义。
事实证明,无密码身份验证的 Auth0 实现是次优的,因为它每次都会重定向到他们的网站,并且会基于 URL 参数,这是我想避免的。
定价也不会针对社交网络之类的东西进行扩展,因此我决定使用 AWS Cognito 构建自己的实施。
Cognito 的触发器可以连接到我用来触发身份验证的 Lambda 函数,这非常方便。 Lambda 函数将用于在注册期间收集用户数据。
此时,用户只需提供手机号码和用户名即可注册。
在登录过程中,Lambda 函数将收集用户的电话号码并使用AWS SNS向用户发送包含验证码的 SMS 消息。
然后,用户只需输入此代码即可通过 Cognito 进行验证,并将被重定向到他的个人资料页面。
当然,一旦用户获得授权并将验证数据传回前端,我们就必须对其进行加密,然后才能存储它。相同的授权数据在存储到后端之前会被加密。
此外,在每次注册和登录期间,我们都会存储用户的 IP。
后来事实证明,用户实际上在提供他们的手机号码时会遇到问题,所以我决定用电子邮件代替 SMS。
当我想使用AWS SES时出现重复消息的问题,因此我切换到 Twilio 的SendGrid以便向用户发送电子邮件。
随着这个系统的完成,一年过去了,我两年前开始的项目还远未完成。没有别的选择,只能继续努力,争取尽快完成。我几乎不知道最大的挑战还没有到来。
这是一切都必须到位的时候,但是作为一个单独的开发者工作这么长时间没有任何反馈会让你质疑项目的发展方向。
现在在同一个地方的任何开发人员都可能会问自己以下问题。
即使我看不到尽头,我如何才能保持动力并能够继续前进?
答案很简单。
无论您目前对项目的感觉如何,您都不应该质疑您的决定。你不能让你目前的情绪状态来决定你将如何行动。
您现在可能不想继续,但以后可能会喜欢,如果您真的退出,您肯定会感觉很糟糕。
因此,如果您退出,您将不再拥有该项目,并且所有的工作都将付诸东流。所以唯一要做的就是继续前进,不管发生什么。
此时唯一要记住的是,每一个交付的功能,每一个键盘按键,都让你更接近目标。
在这个项目中,我实际上换了3次工作,每次都非常投入,但即使我不得不去参加工作面试,我仍然会回到家里,坐在办公桌后面,继续我的项目。
如果您缺乏动力,您必须问自己以下问题。
如果你现在辞职,你会去哪里?离开后,你唯一能走的路就是回到你原来的地方。但你已经知道后面有什么了。你已经知道它是什么样的,但你不喜欢它,这就是你最初踏上这段旅程的原因。所以,你现在知道一个事实,那就是没有地方可以回去了。你唯一能走的路,就是向前。前进的唯一方法就是继续工作。
这就是我在这个项目中的所有动力,正如我已经说过的,要么就是这样,要么回到我已经在的地方,所以我决定继续前进。
现在是时候开始把事情整合在一起了,我决定实施管理系统,用于维护每个社区。每个社区所有者都可以就删除其社区中的内容做出决定。
这意味着如果用户的行为不符合行为规则,我们可以禁用广告、文章和活动并禁止用户。
每个社区的所有者也可以向其他用户授予管理员权限。但我们还需要让主社区的管理员能够管理所有其他社区。
此外,这些管理员将能够完全禁用所有社区的其他用户,甚至禁用整个社区。
为了让管理员更容易完成他们的工作,我引入了标记系统,每个项目都可以报告给管理员。用户现在可以在网站上报告他们认为不合适的任何内容。
每个用户的实际权限验证将在后端决定。我只需创建一个 Lambda 函数,该函数将在每个 AppSync 调用中调用,该调用将验证每个请求。
此外,前端将使用 Aurelia 提供的基于路由的授权。我将简单地定义允许或禁止当前用户继续使用特定路线的规则。
例如,如果您被某个社区禁止,您将无法查看您的个人资料。但是这个系统也可以用来防止有人在没有登录的情况下导航到个人资料页面,相反,他们将被重定向到登录页面。
另一个对用户有用的功能是分析仪表板页面。每个社区所有者都可以看到图表,这些图表准确显示了他的社区中发生了多少互动。
对于这种特殊情况,我将重新使用 Kinesis 聚合的数据,并使用Brite Charts库将其与图表一起显示。
此外,我还将获取 Stripe 数据,并显示该社区拥有的订阅者、广告商和总收入的数量。
唯一需要解决的问题是响应式设计,即如何在小屏幕和大屏幕上显示图表。同样,我使用 RxJs 来检测“resize”事件并根据 Tailwind CSS 中定义的屏幕断点应用样式。
路线图上还有一个额外的安全级别,我决定在我的 CloudFront 分配之前实施 WAF。
我使用了 AWS Marketplace 并订阅了Imperva WAF系统,该系统将代理我的流量并确保仅允许已验证为安全的流量。
该解决方案很容易实施,但是一旦第一个月结束,账单就太多了,所以我断开了系统并决定简单地依赖 CloudFront 默认提供的服务。
在这一点上,我必须开始审视我所做的一切,并开始解决仍然存在的小问题。许多事情仍然需要完善,但最大的需要改变的是 DynamoDB 数据库设置。
事实证明,我的初始设置(不是我现在使用的设置)无法很好地扩展。这就是为什么我决定完全重新设计它并开始使用“#”分隔符来指示记录标识符中的分支。
以前,我制作单独的记录并使用 AppSync Pipelines 来定位每个相关记录,这显然是不可持续的。这也对 Kinesis 和 Algolia 和 Recombee 等第 3 方服务设置产生了影响。
反过来,完全重新设计系统以使其正常工作需要 3 个月的时间。完成此操作后,我可以再次继续使用新功能。
东京的夏天炎热潮湿。尤其是在七月和八月,对你所做的任何事情都保持准确是一个相当大的挑战。
那段时间,东京正在举办奥运会,8月7日,据报道创下了奥运会历史上最热的气温。
不用说,坐火车去上班已经没有意义了,因为天气太累了,让我筋疲力尽,晚上无法工作。我意识到我必须乘出租车去上班,以节省更多时间。
这让我有更多的时间睡觉,并且让我回到家后不会因为太累而无法工作。
PWA 是一项伟大的技术,它为我们提供了一种使用推送通知向用户发送通知的方式。我决定这将是一个同样需要的系统,并继续实施。
通知系统将根据被关注的用户来实现。如果您正在关注用户,那么当他创建新活动或文章时,您需要得到通知。
目前,推送通知的唯一问题是,在撰写本文时,iOS 设备上的 Safari 浏览器仍然不支持它们。我决定使用浏览器的Notification API而不是原生推送通知。
在后端,我将创建一个新的AWS API Gateway实例并将其设置为使用实时数据。
在前端,我会使用WebSocket API与 API Gateway 建立连接。一旦被关注的用户发布了一篇新文章,该数据将被发送到 Kinesis。同样,使用批处理,我们获取所有关注作者的用户,然后使用 API 网关将通知发送到前端。
在前端,WebSocket 连接被触发,然后我们用它来调用浏览器的通知 API 并显示通知。
此外,当涉及到用户可以在每篇文章上写的评论时,我们需要跟踪并向用户展示他当前参与讨论的位置。
我还实现了一个未读指示器,它会显示哪个评论部分有用户尚未阅读的新评论。
当用户在调用 AppSync 调用时加载应用程序而不使用await关键字时,将检查这一点。这将确保执行不会等待调用完成,而是首先加载更重要的数据。
一旦调用返回,我们只需更新 UI 并向用户显示通知。
我还会使用弹出窗口形式的通知来向用户发出操作是否成功完成的信号。
例如,我会创建一条弹出消息,告诉用户文章更新是否失败。
看到后端验证是如何完成的,我们必须通过在前端实现验证来给用户更快的反馈,从而给用户更好的体验。
值得庆幸的是,Aurelia 有一个验证插件,并通过流畅的界面适当地实现。这使得创建业务规则变得非常容易,例如限制用户可以在 <input> 字段中输入文章名称的字符数。
我会使用 Aurelia 属性绑定系统来收集验证消息,然后将它们显示在 UI 上。我还需要将其与本地化系统结合起来,并确保消息以正确的语言显示。
这一年剩下的时间里,我们都在处理更小的细节。它要求我创建诸如加载占位符之类的东西。我特别决定不想将加载占位符显示为单独的屏幕元素。
相反,我想向用户表明正在加载一个元素。这就是为什么我使用正在加载的元素的轮廓并给它们一个透明的加载动画。这是受到以相同方式工作的 Netflix 移动应用程序的启发。
到这个时候,年末来了,我现在在主页上工作。此页面将仅显示我们当前拥有的所有社区。幸运的是,我之前创建的基于组件的系统使得重用我编写的大部分代码变得非常容易,因此任务很快完成。
这一年终于结束了,我对所做的工作感到满意。尽管该项目尚未完成,但我知道成功触手可及。
今年将是最后一年。我不知道我是否真的会实现我想要的一切,但我知道无论发生什么我都必须这样做。
我不想像去年那样在夏天重复这项工作,因为它更有可能比去年更热。
我的预测成真了,事实证明,东京在 2022 年的夏季气温是过去 147 年来最热的!
我从设计登陆页面开始。问题如下。
我希望我的用户在访问我的目标网页时有什么感受?
我不想让用户觉得这对一个网站来说太严肃了,而是一个友好和协作的社区。
我最近注意到,登陆页面有插图而不是真人照片,所以沿着这条路走下去是有意义的。这就是我决定在Adobe Stock上购买的一组插图的原因。
登陆页面必须简单,并且还必须快速描述网站提供的所有内容。这也必须进行本地化,因此我使用本地化功能来翻译所有显示的登录页面标题和字幕。
唯一需要克服的技术问题是如何在文本中引入颜色。幸运的是,我能够在翻译定义中使用样式功能,然后使用 Markdown 动态生成将显示在登录页面上的 HTML。
所需的数据,如“隐私政策”和“使用条款”是在线购买的,并使用谷歌翻译翻译成多种语言。
现在是时候整理所有松散的部分了,所以我用剩下的时间来确保后端的所有 Lamda 函数中都存在日志记录。这将帮助我确保如果发生问题,我会知道发生了什么。
当我完成时,乌克兰战争已经开始。这再次增加了全球经济的不确定性,但我继续工作并专注于最终目标。
由于没有使 PWA 实现保持最新,我必须确保所有功能都正常工作,因此需要进行一些进一步的开发来改进 JavaScript 和图像缓存。
离线功能终于打开了,应用程序现在可以正常运行为离线应用程序。
我还必须移动我在后端所做的更改,并将我在 AppSync 上所做的更改实际传播到其他区域。由于在开发过程中这样做太麻烦,所以我开始开发以来没有对其他区域进行任何更改。
环境也是如此。不断地构建所有三个环境会浪费太多时间,所以我最后花了一些时间来同步所有环境并将代码移动到 UAT 和生产。
最后,我必须实现 https://immersive.community 域,它必须在没有“www”子域的情况下工作并正确重定向到主页。
此时,我们正处于2022 年 4 月 25 日凌晨。我长达 4 年的项目终于结束了。我在网站上创建了第一个帖子,然后就睡觉了。我知道我终于成功了。我不仅完成了我打算做的事情,而且还在夏天到来之前完成了它。
具有讽刺意味的是,我冒险的最后一句话是,这不是结束,而只是开始。现在系统已经上线,需要创建的内容以及品牌知名度所需的促销和广告将是一次全新的冒险。
但是,我实际上从这个练习中学到了什么?
嗯,很多。首先,我可以自信地说我永远不会再这样做了。
不是我对结果不满意,恰恰相反,我很满意,但这是你一生要做一次的事情,试图超越自己是没有意义的,只是为了证明你可以做得更好。
我想知道作为一个单独的开发者是否有可能构建一个企业级系统,并且我已经证明它可以通过我们拥有的技术堆栈来完成。
最重要的是,这是对每个从事其副项目或正在考虑开始一个项目的开发人员的声明。
我会向其他开发人员推荐这种方法吗?绝对地。不是因为它是一种最佳的做事方式,它肯定不是。
遇到困难时不寻求帮助当然不是解决问题的最快方法,但它会帮助你发现自己的极限。
一旦你决定做这样的事情,并且你成功了,你就会知道之后你决定做的所有其他事情都会更容易实现。
我相信我的故事会激励你完成你开始的任何事情,无论你感觉如何,即使你没有看到你现在走的路的尽头,只要记住“回到那里”不是你想去的地方.
如果你觉得这个故事很有启发性,请订阅我的YouTube 频道,因为我将开始学习高级“全栈开发”编程课程,我将详细介绍我用来构建沉浸式社区的所有技术。
我在本文中没有涉及的是我处理每个问题的方式的哲学基础和理由,以及我用来分析和设计每个问题的解决方案的技术。
这是一个比简单地了解技术以及如何使用它们更重要的组成部分。我将在我的 YouTube 视频中深入探讨你如何处理问题以及将你带到解决方案的思考过程。
这将是开发人员向创建真实系统并准备分享他的知识的人学习编程的好方法。 YouTube 上见!
也在这里发布