65.9K
CodeProject 正在变化。 阅读更多。
Home

跳过日志文件 - 尝试自动异常处理!

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.99/5 (47投票s)

2016年9月8日

Apache

18分钟阅读

viewsIcon

66593

codeRR 是一个开源的错误处理服务。它包含了您在记录/报告异常时忘记添加的上下文信息。

引言

本文从使用者的角度出发,将引导您了解所有功能,并展示如何在您自己的应用程序中开始使用。

codeRR 是一个用于检测和分析异常的开源服务。它可以在不稳定的网络连接下工作,甚至可以在公司代理后面运行。它为每个报告的异常附加了上下文信息,这使得识别和纠正异常原因变得更加容易。

背景

我从事编程已经超过20年了。在这期间,错误管理一直是个反复出现的问题,特别是当您的应用程序是 Windows/桌面应用程序时。用户的错误报告往往内容稀少,没有足够的细节来重现错误。您要么只能凭经验猜测,要么只能进行“猴子测试”来找出根本原因。

最糟糕的问题是,当您认为已经找到并纠正了原因后,稍后却又收到了关于同一问题的新错误报告。这时候,您实际上是让代码库变得更糟了。

错误报告的事实标准一直是日志记录。但您知道的,我们努力做好日志记录,包含足够的细节。但我们是否总会漏掉那个能告诉我们异常抛出原因的关键字段呢?

或者从用户的角度来看。如果您在别人的应用程序中遇到错误,您有多大可能会去分析这个错误?您会创建一个详细的错误报告并联系应用程序制造商的支持部门吗?我想不会。因此,一旦您收到一份错误报告,可以肯定的是,已经有好几个其他用户遇到了同样的错误。

codeRR 正是解决这些问题的一次尝试。codeRR 不依赖于用户的错误报告或日志文件。当用户遇到一个新的、唯一的异常时,codeRR 会联系您,并且您通常能获得所有需要的信息,从而可以直接开始修复 bug,而无需先进行任何分析。

功能导览

让我们先来了解一下所有功能。本文的最后一章包含一个循序渐进的入门指南。

codeRR 由一个客户端(nuget 包)和一个服务器(IIS Web 应用程序)组成。客户端在您的应用程序中检测异常,收集上下文信息,并将所有内容上传到服务器。

让我们通过一个简单的控制台程序来报告一个异常,看看我们能得到什么样的酷炫功能。

class Program
{
    static void Main(string[] args)
    {
        // Initialization
        var url = new Uri("https:///coderr/");
        Err.Configuration.Credentials(url, "yourAppKey", "yourSharedSecret");

        try
        {
            throw new InvalidOperationException("Hello world");
        }
        catch (Exception ex)
        {
            Err.Report(ex);
        }
    }
}

当该代码运行时,异常将被上传到服务器。当您访问您的 codeRR 网站时,您会看到类似这样的界面:

Our dashboard

仪表盘为您提供了在 codeRR 中配置的所有应用程序的概览。

呈现的信息

描述

名称  
事件 所有被识别为同一错误的已报告异常的聚合。
活动事件 尚未解决或忽略的事件。
报表 一份已上传的错误报告(相当于一个已记录的异常)
等待中的用户 在遇到错误页面时输入了自己电子邮件地址的用户,即他们希望获得状态更新,并可以被联系以进行进一步分析。
反馈 已填写错误描述(错误发生时他们正在做什么)的用户数量。

让我们点击该事件以获取有关该特定错误的更多信息。

事件

事件用于将错误报告分组。与日志库不同,如果您收到同一个异常 5000 次,codeRR 不会生成 5000 个不同的错误。相反,codeRR 会识别出收到的报告与之前的报告是同一个异常。这两个报告会被归为一个事件。ISO 20000 将事件定义为:

引用

对服务的非计划性中断、服务质量的降低或尚未影响到客户服务的事件。

……这很贴合您的用户在抛出异常时的体验。

incident page

事件页面包含有关特定错误的信息。从这里,您可以浏览我们收到的关于该错误的所有报告。在上面的截图中,我再次运行了我的示例应用程序,使得该事件下总共有两个报告。

您可以对事件执行两种操作:

  • 关闭事件 - 将其标记为已解决/已纠正(即不应再发生)
  • 忽略事件 - 不再为此事件存储任何报告或生成通知

当您忽略一个事件时,所有新的报告都将被丢弃。报告计数器将继续增长,以便您可以看到报告仍在被接收。但新的报告将不会被分析或存储。

当您关闭一个事件时,您还可以编写一条消息,该消息将分发给所有等待新版本的用户。这一切都通过 codeRR 完成,并且只发送给那些在异常抛出时注册了状态通知的用户。

以下是 codeRR 迄今为止实现的一些分析功能。

标签

如果您看上面的截图,您会注意到 `console-application` 标签。codeRR 会为收到的报告识别一些 StackOverflow 标签。如果您点击一个标签,您将被引导到对该异常消息的搜索,并仅筛选该标签。

Showcase of a StackOverflow.com search

错误来源

错误来源显示了错误报告来自何处。该信息以热力图的形式呈现。因此,您可以轻松识别错误是否更频繁地发生在某个特定区域。这表明错误可能与本地化或文化问题有关。

A map displaying all error origins

当报告数量增多时,图钉将被替换为热力图。

上下文数据

事件下的上下文数据是聚合和专门化的结合。集合首先被专门化,例如,Web 浏览器的“`User-Agent`”字符串被提取到其自己的集合中。完成后,
信息被聚合然后进行比较。因此,codeRR 可以告诉您,例如,一个事件的 99% 的错误报告是针对文化“`sv-SE`”的,或者在所有报告中,操作系统中可用的内存少于 100MB。

context data example

反馈 / 错误描述

codeRR 为用户提供了一种在捕获异常时留下错误描述的方式,即他们可以描述异常发生时他们正在做什么。他们还可以留下自己的电子邮件地址,以便在事件关闭时收到通知。

这是 WinForms 项目的内置错误表单:

Feedback form

您也可以像这样自己收集信息:

//last param is email address
var feedback = new UserSuppliedInformation("I pressed the 'any' key.", null); 
Err.LeaveFeedback(errorIdFromReport, feedback);

上传后,反馈将在事件下可用:

Screenshot from feedback in the website

您的用户提供的反馈越多,就越容易识别和纠正问题。

错误报告

事件是了解您的应用程序有哪些不同错误的好方法,包括它们发生的频率和共同点等信息。但是,有时深入了解每个特定错误报告的细节,以便理解异常抛出的原因会更好。

因此,您可以点击事件下列表中的任何一个报告进行查看。

Screenshot from a specific error report (exception occurrence)

Notifications

是否曾想在您的应用程序中抛出异常时立即收到通知?

codeRR 支持多种类型的通知。您可以收到手机短信或电子邮件消息。

以下是 codeRR 支持的通知:

user notification options

应用程序版本

codeRR 可以跟踪应用程序版本,以查看同一错误是否已存在于多个版本中,或者是否在几个版本后再次出现。

要激活此功能,您需要进入管理区域,并选择您要提升版本号的程序集。

Selects correct assembly in admin area

您还需要确保在您的项目中指定了版本号:

assemblyversion

因此,一旦报告了异常,该事件将被标记上应用程序版本:

Incident got version tag

如果您现在提升程序集版本:

changing the assembly version

...并报告相同的异常,您将看到该事件将包含两个应用程序版本:

Incident got two version tags.

客户端库

codeRR 服务器负责分析和呈现信息,而客户端库则用于检测异常和收集上下文信息。

客户端库将自身注入到您喜欢的 .NET 库/框架的管道中,以便能够自动收集异常和信息。但是,如果您有 `try`/`catch` 块,您也可以自己报告异常。

我们目前构建的库有:

名称 描述
Coderr.Client 基础库。如果您想自己报告异常,这个库就足够了。
Core.Client.NetStd .NET Standard (v1.6 和 v2.0) 库。
Coderr.Client.AspNet

通用的 ASP.NET 库。捕获所有未处理的异常并报告它们。

收集有关 HTTP 请求、会话数据等信息。允许您轻松地为不同的 HTTP 错误代码创建自定义错误页面。

Coderr.Client.AspNet.Mvc5

ASP.NET MVC5 专用库。

功能与 ASP.NET 库相同,但还会收集特定的 MVC5 信息,如路由数据、ViewBag 等。还允许您通过在错误视图文件夹中创建 razor 视图来自定义错误页面。

Coderr.Client.AspNet.WebApi2

ASP.NET WebApi 2 的库。

跟踪异常、无效的模型状态、失败的授权尝试、丢失的 API。收集所有可用信息,包括 ASP.NET 管道和 WebApi 中的信息。例如模型、请求、路由数据等。

Coderr.Client.AspNetCore.Mvc

ASP.NET Core MVC 的库。

跟踪异常、无效的模型状态、失败的授权尝试、丢失的页面。收集所有可用信息,包括 OWIN 管道和 MVC 中的信息。例如 viewbag、请求、viewdata、viewbag、路由数据等。

Coderr.Client.Wcf

捕获 WCF 管道中未处理的异常。

收集 WCF 特定信息,例如处理失败的入站 WCF 消息。

Coderr.Client.Log4Net 报告您记录的所有异常,包括您编写的错误消息。
Coderr.Client.WinForms

报告所有未处理的异常。

可以截取屏幕截图并收集所有打开窗体的状态。

Coderr.Client.WPF

报告所有未处理的异常。

可以截取屏幕截图并收集所有打开窗体的状态。

也可以通过您自己的库/应用程序直接报告异常。客户端规范 (HTTP/JSON) 可在在线文档中找到。

下面是关于我们一些库功能的更多介绍。

核心库

这个库是所有使用 codeRR 进行异常报告的基础。所有其他客户端库都基于这个库。

使用核心库,您必须像这样手动报告异常:

try
{
    doSomething();
}
catch (Exception ex)
{
    Err.Report(ex);
}

您还可以附加不同种类的上下文信息,我们将在“入门”一章中再回到这个话题。

上下文集合

上下文集合是一组从特定来源获取的属性。上下文集合可以是 HTTP 标头、视图模型、路由数据等。

核心库中有一些内置的上下文集合。有些默认添加到管道中,这意味着每次报告异常时都会包含它们。其他集合则需要您在配置客户端库时手动添加。

如果您愿意,也可以轻松地创建并包含您自己的

应用程序信息

此集合包含有关您的进程的信息。它包含诸如已用内存、线程数和已用 CPU 量等信息。

Assemblies

所有已加载到 `AppDomain` 中的程序集及其版本。

异常信息

来自异常的信息,包括所有属性。如果您曾经使用过 EntityFramework 并遇到过 DbEntityValidationException 吗?

它的异常消息只是说:

引用

System.Data.Entity.Validation.DbEntityValidationException: 一个或多个实体验证失败。有关详细信息,请参见 'EntityValidationErrors' 属性。

当您在日志文件中看到这个时,您知道为什么会得到这个异常吗?

由于 codeRR 包含了所有异常属性,您可以直接获得验证信息。

以下是来自 `DbEntityValidationException` 异常的所有信息的一个子集。如您所见,您既可以得到 EF 实体中所有属性的值,也可以得到所有验证失败的信息。

Entity Framework properties

文件版本

所有程序集的文件版本,因为程序集版本可能相同而文件版本更高(有人遇到过 GAC 地狱吗?)。

操作系统

错误是发生在所有 Windows 版本上还是仅在某些版本上?

系统信息

可用 RAM 内存量是否是瓶颈?

线程信息

线程信息,如当前的 UI 区域性。

哪个线程崩溃了? 提示:如果您自己启动线程,请给它们命名。

ASP.NET

这个库适用于不使用 MVC 的 ASP.NET 项目。所以它非常适合基于 ASP.NET 的库,例如 Nancy

上下文集合

这些集合由 ASP.NET 库生成。

HTTP 标头

失败请求的所有 HTTP 标头。

上传的文件

请求中上传的所有文件的名称、大小和上下文类型。

表单

HTTP 表单中的所有项目(名称和值)。

查询字符串(QueryString)

查询字符串变量(键和值)。

Session

所有会话项(支持复杂对象)。

错误页面

该库包含一个自定义错误页面,当捕获到异常时可以激活(并显示)该页面。

要激活它,请添加以下代码:

var provider = new VirtualPathProviderBasedGenerator("~/Errors/");
Err.Configuration.SetErrorPageGenerator(provider);

该代码表示库应该在指定文件夹中查找错误页面(*.aspx* 或 *.html*)。

该库尝试根据 HTTP 代码查找页面。如果代码是 404,该库会尝试查找以下错误页面:

  1. FileNotFound.aspx
  2. FileNotFound.html
  3. Error.aspx
  4. Error.html

也就是说,它会先尝试查找最具体的文件。如果特定文件不存在,它会尝试加载一个通用的文件。

错误信息

要将信息显示到您的视图中,只需在您的代码后台声明以下属性之一。

属性 类型 描述
ErrorContext HttpErrorReporterContext 更多信息请查阅客户端 API 规范。
异常 异常 捕获到的异常
示例
public partial class NotFound : System.Web.UI.Page
{
	protected void Page_Load(object sender, EventArgs e)
	{

	}

	public Exception Exception { get; set; }

	public HttpErrorReporterContext ErrorContext { get; set; }
}

然后只需在您的 HTML 中显示错误信息即可:

<%@ Page Language="C#" AutoEventWireup="true" 
CodeBehind="NotFound.aspx.cs" Inherits="codeRR.Client.AspNet.Demo.Errors.NotFound" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Failed to find <%= Exception.Message %></title>
</head>
<body>
	<form method="post" action="$URL$">
		<input type="hidden" value="$reportId$" name="reportId" />
        <div>
            Page is not found
        </div>
        <div>
            <%= ErrorContext.HttpStatusCode  %>
        </div>
		<div>
			<p>Could you please let us know how to reproduce it? 
			Any information you	 give us will help us solve it faster.</p>
			<textarea rows="10" cols="40" name="Description"></textarea>
		</div>
    </form>
</body>
</html>

如果您只使用 HTML,您可以使用以下模板字符串:

模板文本 描述
$ExceptionMessage 异常消息
$reportId$ 生成的报告ID
$URL$ codeRR 自身的 URL,用于提交错误描述、电子邮件等。
$AllowReportUploading$ 如果您想允许用户决定是否上传报告。

示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>An error occurred</title>
    <meta name="ROBOTS" content="NOINDEX, NOFOLLOW" />
    <meta name="X-powered-with" content="https://coderrapp.com" />
    <style type="text/css">
        /*CssStyles*/
    </style>
</head>
<body>
    <div style="" class="container">
        <div style="width: 100%; text-align: center">
            <h1>Looks like something went wrong!</h1>
        </div>
        <form method="post" action="$URL$">
            <div class="img">
                <img src="/images/Error.jpg" />
            </div>
            <div class="content">
                <p>
                    Thanks for taking the time and letting us know about the issue.
                </p>
                <p>
                    We’re working to fix it for you as fast as we can, 
                    apologies for the inconvenience.
                </p>
                <input type="hidden" value="$reportId$" name="reportId" />
                <div class="AllowSubmissionStyle">
                    <p>
                        However, If you allow us to collect additional error information 
                        we'll be able to analyze this error much faster.
                    </p>
                    <input type="checkbox" name="Allowed" 
                    value="true" checked="$AllowReportUploading$" />
                    I allow thy to collect the additional information.
                </div>
                <div class="AllowFeedbackStyle">
                    <p>Could you please let us know how to reproduce it? 
                    Any information you give us will help us solve it faster.</p>
                    <textarea rows="10" cols="40" 
                    name="Description"></textarea>
                </div>

                <div class="AskForEmailAddress">
                    <p>Enter your email address if you would like to 
                    receive status updates about this error.</p>
                    <input type="text" name="email" 
                    placeholder="email address" />
                </div>
                <hr />
                <input type="submit" value="Send report" />
                <a href="/">Back to homepage</a>
            </div>
            <div style="clear: both;"></div>
        </form>
    </div>
</body>
</html>

ASP.NET MVC

这个客户端库将为 codeRR 提供 ASP.NET 和 MVC 特定的上下文信息。

除了检测和上传未捕获的异常外,该库还提供以下功能。

错误页面

该库内置了对错误页面的支持。

要使用库中包含的页面,请在 *global.asax* 中添加以下内容(在 `Err.Configuration.Credentials()` 行之后):

Err.Configuration.DisplayErrorPages();

自定义错误页面

如果我们内置的页面不合您的心意,您可以包含您自己的视图。

示例

@model codeRR.Client.AspNet.Mvc5.CoderrViewModel

<h1>Internal Server Error</h1>
<p>
    We've experienced a malfunction in the core crystal cooling. 
    The ship will explode within five seconds.
</p>

<h3>Reason</h3>
<p>
    @Model.Exception.Message
</p>

视图应以 .NET 中 `HttpStatusCode` `enum` 中定义的 HTTP 代码命名,并放置在 *Views/Errors* 文件夹中。

如果没有其他视图匹配,则显示 *Error.cshtml* 视图。

ErrorController

如果仅通过视图控制错误处理还不够,您可以创建自己的 `ErrorController`。像创建其他控制器一样,在 *Controllers* 文件夹中创建它。

操作方法的命名应与视图类似,例如 `public ActionResult InternalServer()`。

由 codeRR 提供的信息表示为 `CoderrViewModel`。将其作为参数传递给您的操作方法。

示例
public class ErrorController : Controller
{
    public ActionResult Index(CoderrViewModel model)
    {
        return View("Error", model);
    }

    public ActionResult NotFound(CoderrViewModel model)
    {
        return View(model);
    }

    public ActionResult InternalServerError(CoderrViewModel model)
    {
        return View(model);
    }
}

自定义格式

如果 HTTP 客户端请求 XML 或 JSON,将返回一个错误对象。这对于 ASP.NET `WebApi` 非常有用。

json

{
  "error": { 
	"msg": "The error message", 
	"reportId": "Unique error id"
  }, 
  hint: "Use the report id when contacting us if you need further assistance." 
}

xml

<Error ReportId="Unique error id" 
 hint="Use the report id when contacting us if you need further assistance">
	Error message
</Error>

上下文集合

以下集合由 ASP.NET MVC 库提供。

控制器 (Controller)

收集控制器名称。

RouteData

收集有关 MVC 所采用路由的信息。

TempData

如果设置了 `TempData`,则会收集它。

示例
TempData["DemoKey"] = new {
		Amount = 20000,
		Expires = DateTime.UtcNow.AddMinutes(5)
};
结果

ViewData / ViewBag

如果指定了 `Viewbag` 和/或 `ViewData`,则会收集它们。

示例
ViewBag.Title = "Hello";
ViewBag.Model = new
{
	state = "Running",
	Collected = true
};
结果

log4net 客户端

log4net 库将 codeRR 注入到 log4net 的日志记录管道中。每次您记录某些内容并包含异常时,它都会报告给 codeRR。这无疑是在遗留应用程序(使用 log4net)中使用 codeRR 强大功能的最简单方法。

用法

如果您有这样的代码:

try
{
	methodThatWillThrowAnException();
}
catch (Exception ex)
{
	_logger.Warn("Failed doing some crazy stuff.", ex);
}

... 异常将被 codeRR 捕获。

另外,您还会在 codeRR 中看到有关日志条目的信息:

WinForms

WinForms 客户端库可以帮助您显示错误页面并收集有关打开窗体的信息。

错误页面

当检测到异常时会显示一个错误页面。默认情况下它看起来是这样的:

您可以使用以下属性对其进行配置:

Err.Configuration.UserInteraction.AskUserForDetails = true;
Err.Configuration.UserInteraction.AskUserForPermission = true;
Err.Configuration.UserInteraction.AskForEmailAddress = true;

示例

all flags set

only ask for permission

only details

only email

上下文信息

WinForms 有两个内置的上下文集合。

OpenForms

codeRR 使用反射从所有打开的窗体中收集信息。这些信息包括所有控件及其配置(位置、内容、可见性等)。

假设您打开了这个窗体:

... 这将为您提供以下信息:

屏幕截图

可以通过以下配置行之一激活屏幕截图功能:

//only of the active form
Err.Configuration.TakeScreenshotOfActiveFormOnly();

// of all forms            
Err.Configuration.TakeScreenshots();

上下文集合将显示为:

codeRR 管道

这是一个小图表,展示了 codeRR 为确保您的异常被检测、捕获、包装上下文信息并最终上传到服务进行分析所采取的步骤。

它还会识别重要的方法调用,如果您想浏览代码并了解事物如何协同工作。
提示:在 Visual Studio 中右键单击代码行,然后使用“查找用法”/“查找引用”来查看方法调用的来源。

(CodeProject 对图片有宽度限制,在此处查看完整尺寸图片。)

入门

一份帮助您报告第一个错误的指南。

服务器安装

您可以下载服务器的源代码,编译并安装它,或者使用预编译版本。无论哪种方式,安装都是 xcopy 部署。

  1. 下载或编译。
  2. 将二进制文件复制到一个新的 Web 服务器文件夹,通常是 *c:\inetpub\wwwroot\coderr*。
  3. 转到 IIS 并使用上下文菜单选项,“添加应用程序...”或“添加网站...”。
  4. 将其命名为“`Coderr`”并指向您创建的文件夹。
  5. 在您的 SQL 服务器中创建一个新的空数据库,并修改 *web.config* 中的连接字符串。
  6. (使用您的 Web 浏览器)浏览到该网站。
  7. 按照设置向导操作
  8. 向导完成后,在 *web.config* 中将 `appKey` `Configured` 更改为 `true`。

服务器现已安装完毕。

创建一个新应用程序

一旦您使用上一步创建的帐户登录到 codeRR 服务器,您应该会看到应用程序向导:

Appwizard

输入一个应用程序名称并进入下一步。

安装客户端

我们的客户端库用于检测异常和收集上下文信息。目的是为您提供足够的信息,以真正理解异常发生的原因。我们不想做假设或编写权宜之计,对吧?

select nuget package

为您的应用程序选择正确的 nuget 包,然后点击“**配置**”。

配置您的应用程序

为了能够报告异常,您需要告诉您的应用程序 codeRR 应该检测异常并收集上下文信息。完成后,codeRR 还需要知道将错误报告上传到哪里。

为了能够做到这一点,codeRR 需要能够识别您的应用程序,以便错误能够正确地存储在您的帐户中。

应用程序向导的下一步将帮助您完成此操作。下面是一个示例。

configure nuget

该说明包含了正确的 `appKey/SharedSecret`,您需要做的就是将信息复制/粘贴到您的应用程序中。

应用程序向导仅在第一个应用程序时启动,但是,您可以为尚未报告任何错误的应用程序自行启动它。

start appwizard manually

手动报告异常

报告异常最简单的方法是这样:

try
{
    somelogic();
}
catch(SomeException ex)
{
	Err.Report(ex);
}

异常应在报告后不久出现在您的服务器安装中。

附加上下文信息

通常,仅凭一个异常不足以理解异常被抛出的原因。codeRR 总是会为您收集大量参数。然而,您可能拥有能让您直接理解异常为何被抛出的信息。

该信息可以在报告时附加:

try
{
    //some stuff that generates an exception
}
catch (Exception ex)
{
    Err.Report(ex, yourContextData);
}

使用匿名对象

如果您需要附加多个值,可以使用匿名对象:

try
{
    //some stuff that generates an exception
}
catch (Exception ex)
{
    Err.Report(ex, new { UserId = userId, UserState = state });
}
结果

自定义集合

我们还有一个对象扩展方法,可以将任何对象转换为上下文集合(我们网站上“**上下文数据**”菜单中的一个分组)。

下面我们正在使用 `.ToContextCollection()` 扩展方法。

try
{
    <span spellcheck="true">//some stuff that generates an exception</span>
}
catch (Exception ex)
{
    var modelCollection = viewModel.ToContextCollection("ViewModel");
    var loggedInUser = User.ToContextCollection("User");
    Err.Report(ex, new[]{modelCollection, loggedInUser});
}
结果

因此,您可以轻松地按您喜欢的方式附加和分组您的信息。

使用标签对异常进行分类

在分析异常时,我们会自动识别常见的 StackOverflow.com 标签(以帮助您通过搜索 StackOverflow.com 找到答案)。您也可以通过向任何上下文集合添加一个名为“`ErrTags`”的特殊属性来添加您自己的标签:

try
{
    //some stuff that generates an exception
}
catch (Exception ex)
{
    Err.Report(ex, new { ErrTags = "important,backend" });
}

当该报告到达 codeRR 服务器时,您将直接在事件页面上看到这些标签:

tags

这是对错误进行分类的好方法。

摘要

codeRR 正在持续开发中。每周都有新功能推出。如果您想了解最新动态,请在 TwitterFacebook 上关注我们。

服务器是使用消息传递、命令/查询、仓储模式、typescript 和其他好东西构建的。在 Github 上查看。

感谢您的阅读,我们非常期待在上述任何渠道听到您的想法。

历史

  • 2016年9月8日:初始版本
© . All rights reserved.