Commit 4066dd1c authored by Savorboard's avatar Savorboard

Deployed db8db5d with MkDocs version: 1.0.4

parents
cap.dotnet-china.com
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="../img/favicon.ico">
<title>About - CAP</title>
<link rel="stylesheet" href="//use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
<link rel="stylesheet" href="//cdn.jsdelivr.net/font-hack/3.003/css/hack.min.css">
<link href='//fonts.googleapis.com/css?family=PT+Sans:400,400italic,700,700italic&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href='//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,700italic,400,300,600,700&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href="../css/bootstrap-custom.min.css" rel="stylesheet">
<link href="../css/base.min.css" rel="stylesheet">
<link href="../css/cinder.min.css" rel="stylesheet">
<link rel="stylesheet" href="../css/highlight.min.css">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<![endif]-->
<script src="//ajax.googleapis.com/ajax/libs/webfont/1.6.28/webfont.js"></script>
<script>
WebFont.load({
google: {
families: ['Open Sans', 'PT Sans']
}
});
</script>
</head>
<body>
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<!-- Collapsed navigation -->
<div class="navbar-header">
<!-- Expander button -->
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- Main title -->
<a class="navbar-brand" href="..">CAP</a>
</div>
<!-- Expanded navigation -->
<div class="navbar-collapse collapse">
<!-- Main navigation -->
<ul class="nav navbar-nav">
<li >
<a href="..">Home</a>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/us/flat/16.png">User Guide <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../user-guide/getting-started/">Getting Started</a>
</li>
<li >
<a href="../user-guide/api-interface/">API Interface</a>
</li>
<li >
<a href="../user-guide/configuration/">Configuration</a>
</li>
<li >
<a href="../user-guide/design-principle.md.md">Design Principle</a>
</li>
<li >
<a href="../user-guide/implementation-mechanisms/">Implementation Mechanisms</a>
</li>
<li >
<a href="../user-guide/distributed-transactions/">Distributed Transactions</a>
</li>
<li >
<a href="../user-guide/faq/">FAQ</a>
</li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/cn/flat/16.png">使用指南 <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../user-guide-cn/getting-started/">快速开始</a>
</li>
<li >
<a href="../user-guide-cn/api-interface/">API 接口</a>
</li>
<li >
<a href="../user-guide-cn/configuration/">配置</a>
</li>
<li >
<a href="../user-guide-cn/design-principle/">设计原理</a>
</li>
<li >
<a href="../user-guide-cn/implementation-mechanisms/">实现</a>
</li>
<li >
<a href="../user-guide-cn/distributed-transactions/">分布式事务</a>
</li>
<li >
<a href="../user-guide-cn/faq/">FAQ</a>
</li>
</ul>
</li>
<li class="active">
<a href="./">About</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<a href="#" data-toggle="modal" data-target="#mkdocs_search_modal">
<i class="fas fa-search"></i> Search
</a>
</li>
<li >
<a rel="next" href="../user-guide-cn/faq/">
<i class="fas fa-arrow-left"></i> Previous
</a>
</li>
<li class="disabled">
<a rel="prev" >
Next <i class="fas fa-arrow-right"></i>
</a>
</li>
<li>
<a href="https://github.com/dotnetcore/CAP/edit/master/docs/about.md"><i class="fab fa-github"></i> Edit on GitHub</a>
</li>
</ul>
</div>
</div>
</div>
<div class="container">
<div class="col-md-3"><div class="bs-sidebar hidden-print affix well" role="complementary">
<ul class="nav bs-sidenav">
<li class="first-level active"><a href="#contact-us">Contact Us</a></li>
</ul>
</div></div>
<div class="col-md-9" role="main">
<h2 id="contact-us">Contact Us</h2>
<ul>
<li>Submit an issue</li>
<li>Email: yangxiaodong1214@126.com</li>
</ul></div>
</div>
<footer class="col-md-12 text-center">
<hr>
<p>
<small>Documentation built with <a href="http://www.mkdocs.org/">MkDocs</a>.</p></small>
</footer>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="../js/bootstrap-3.0.3.min.js"></script>
<script src="../js/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script>
var base_url = '..';
</script>
<!-- <script data-main="../mkdocs/js/search.js" src="../mkdocs/js/require.js"></script> -->
<script src="../js/base.js"></script>
<script src="../search/main.js"></script>
<div class="modal" id="mkdocs_search_modal" tabindex="-1" role="dialog" aria-labelledby="Search Modal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span>
<span class="sr-only">Close</span>
</button>
<h4 class="modal-title" id="exampleModalLabel">Search</h4>
</div>
<div class="modal-body">
<p>
From here you can search these documents. Enter your search terms below.
</p>
<form role="form">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search..." id="mkdocs-search-query">
</div>
</form>
<div id="mkdocs-search-results"></div>
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
</body>
</html>
body {
padding-top: 70px;
}
h1[id]:before, h2[id]:before, h3[id]:before, h4[id]:before, h5[id]:before, h6[id]:before {
content: "";
display: block;
margin-top: -75px;
height: 75px;
}
p > img {
max-width: 100%;
height: auto;
}
ul.nav li.first-level {
font-weight: bold;
}
ul.nav li.third-level {
padding-left: 12px;
}
div.col-md-3 {
padding-left: 0;
}
div.col-md-9 {
padding-bottom: 100px;
}
div.source-links {
float: right;
}
/*
* Side navigation
*
* Scrollspy and affixed enhanced navigation to highlight sections and secondary
* sections of docs content.
*/
/* By default it's not affixed in mobile views, so undo that */
.bs-sidebar.affix {
position: static;
}
.bs-sidebar.well {
padding: 0;
}
/* First level of nav */
.bs-sidenav {
margin-top: 30px;
margin-bottom: 30px;
padding-top: 10px;
padding-bottom: 10px;
border-radius: 5px;
}
/* All levels of nav */
.bs-sidebar .nav > li > a {
display: block;
padding: 5px 20px;
z-index: 1;
}
.bs-sidebar .nav > li > a:hover,
.bs-sidebar .nav > li > a:focus {
text-decoration: none;
border-right: 1px solid;
}
.bs-sidebar .nav > .active > a,
.bs-sidebar .nav > .active:hover > a,
.bs-sidebar .nav > .active:focus > a {
font-weight: bold;
background-color: transparent;
border-right: 1px solid;
}
/* Nav: second level (shown on .active) */
.bs-sidebar .nav .nav {
display: none; /* Hide by default, but at >768px, show it */
margin-bottom: 8px;
}
.bs-sidebar .nav .nav > li > a {
padding-top: 3px;
padding-bottom: 3px;
padding-left: 30px;
font-size: 90%;
}
/* Show and affix the side nav when space allows it */
@media (min-width: 992px) {
.bs-sidebar .nav > .active > ul {
display: block;
}
/* Widen the fixed sidebar */
.bs-sidebar.affix,
.bs-sidebar.affix-bottom {
width: 213px;
}
.bs-sidebar.affix {
position: fixed; /* Undo the static from mobile first approach */
top: 80px;
max-height: calc(100% - 90px);
}
.bs-sidebar.affix-bottom {
position: absolute; /* Undo the static from mobile first approach */
}
.bs-sidebar.affix-bottom .bs-sidenav,
.bs-sidebar.affix .bs-sidenav {
margin-top: 0;
margin-bottom: 0;
}
}
@media (min-width: 1200px) {
/* Widen the fixed sidebar again */
.bs-sidebar.affix-bottom,
.bs-sidebar.affix {
width: 263px;
}
}
/* Added to support >2 level nav in drop down */
.dropdown-submenu {
position: relative;
}
.dropdown-submenu>.dropdown-menu {
top: 0;
left: 100%;
margin-top: 0px;
margin-left: 0px;
-webkit-border-radius: 0 6px 6px 6px;
-moz-border-radius: 0 6px 6px;
border-radius: 0 6px 6px 6px;
}
.dropdown-submenu:hover>.dropdown-menu {
display: block;
}
.dropdown-submenu>a:after {
display: block;
content: " ";
float: right;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
border-width: 5px 0 5px 5px;
border-left-color: #ccc;
margin-top: 5px;
margin-right: -10px;
}
.dropdown-submenu:hover>a:after {
border-left-color: #fff;
}
.dropdown-submenu.pull-left {
float: none;
}
.dropdown-submenu.pull-left>.dropdown-menu {
left: -100%;
margin-left: 00px;
-webkit-border-radius: 6px 0 6px 6px;
-moz-border-radius: 6px 0 6px 6px;
border-radius: 6px 0 6px 6px;
}
/* Start Bootstrap Callouts CSS Source by Chris Pratt (https://codepen.io/chrisdpratt/pen/IAymB) MIT License*/
.bs-callout {
padding: 20px;
margin: 20px 0;
border: 1px solid #eee;
border-left-width: 5px;
border-radius: 3px;
background-color: #FCFDFF;
}
.bs-callout h4 {
font-style: normal;
font-weight: 400;
margin-top: 0;
margin-bottom: 5px;
}
.bs-callout p:last-child {
margin-bottom: 0;
}
.bs-callout code {
border-radius: 3px;
}
.bs-callout+.bs-callout {
margin-top: -5px;
}
.bs-callout-default {
border-left-color: #FA023C; /*modified from upstream default by Christopher Simpkins*/
}
.bs-callout-default h4 {
color: #FA023C; /*modified from upstream default by Christopher Simpkins*/
}
.bs-callout-primary {
border-left-color: #428bca;
}
.bs-callout-primary h4 {
color: #428bca;
}
.bs-callout-success {
border-left-color: #5cb85c;
}
.bs-callout-success h4 {
color: #5cb85c;
}
.bs-callout-danger {
border-left-color: #d9534f;
}
.bs-callout-danger h4 {
color: #d9534f;
}
.bs-callout-warning {
border-left-color: #f0ad4e;
}
.bs-callout-warning h4 {
color: #f0ad4e;
}
.bs-callout-info {
border-left-color: #5bc0de;
}
.bs-callout-info h4 {
color: #5bc0de;
}
/* End Bootstrap Callouts CSS Source by Chris Pratt */
/* Admonitions */
.admonition {
padding: 20px;
margin: 20px 0;
border: 1px solid #eee;
border-left-width: 5px;
border-radius: 3px;
background-color: #FCFDFF;
}
.admonition p:last-child {
margin-bottom: 0;
}
.admonition code {
border-radius: 3px;
}
.admonition+.admonition {
margin-top: -5px;
}
.admonition.note { /* csslint allow: adjoining-classes */
border-left-color: #428bca;
}
.admonition.warning { /* csslint allow: adjoining-classes */
border-left-color: #f0ad4e;
}
.admonition.danger { /* csslint allow: adjoining-classes */
border-left-color: #d9534f;
}
.admonition-title {
font-size: 19px;
font-style: normal;
font-weight: 400;
margin-top: 0;
margin-bottom: 5px;
}
.admonition.note > .admonition-title {
color: #428bca;
}
.admonition.warning > .admonition-title {
color: #f0ad4e;
}
.admonition.danger > .admonition-title {
color: #d9534f;
}
\ No newline at end of file
body{padding-top:70px}h1[id]:before,h2[id]:before,h3[id]:before,h4[id]:before,h5[id]:before,h6[id]:before{content:"";display:block;margin-top:-75px;height:75px}p>img{max-width:100%;height:auto}ul.nav li.first-level{font-weight:bold}ul.nav li.third-level{padding-left:12px}div.col-md-3{padding-left:0}div.col-md-9{padding-bottom:100px}div.source-links{float:right}.bs-sidebar.affix{position:static}.bs-sidebar.well{padding:0}.bs-sidenav{margin-top:30px;margin-bottom:30px;padding-top:10px;padding-bottom:10px;border-radius:5px}.bs-sidebar .nav>li>a{display:block;padding:5px 20px;z-index:1}.bs-sidebar .nav>li>a:hover,.bs-sidebar .nav>li>a:focus{text-decoration:none;border-right:1px solid}.bs-sidebar .nav>.active>a,.bs-sidebar .nav>.active:hover>a,.bs-sidebar .nav>.active:focus>a{font-weight:bold;background-color:transparent;border-right:1px solid}.bs-sidebar .nav .nav{display:none;margin-bottom:8px}.bs-sidebar .nav .nav>li>a{padding-top:3px;padding-bottom:3px;padding-left:30px;font-size:90%}@media(min-width:992px){.bs-sidebar .nav>.active>ul{display:block}.bs-sidebar.affix,.bs-sidebar.affix-bottom{width:213px}.bs-sidebar.affix{position:fixed;top:80px;max-height:calc(100% - 90px)}.bs-sidebar.affix-bottom{position:absolute}.bs-sidebar.affix-bottom .bs-sidenav,.bs-sidebar.affix .bs-sidenav{margin-top:0;margin-bottom:0}}@media(min-width:1200px){.bs-sidebar.affix-bottom,.bs-sidebar.affix{width:263px}}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:0;margin-left:0;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px;border-radius:0 6px 6px 6px}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropdown-submenu>a:after{display:block;content:" ";float:right;width:0;height:0;border-color:transparent;border-style:solid;border-width:5px 0 5px 5px;border-left-color:#ccc;margin-top:5px;margin-right:-10px}.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:00px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.bs-callout{padding:20px;margin:20px 0;border:1px solid #eee;border-left-width:5px;border-radius:3px;background-color:#fcfdff}.bs-callout h4{font-style:normal;font-weight:400;margin-top:0;margin-bottom:5px}.bs-callout p:last-child{margin-bottom:0}.bs-callout code{border-radius:3px}.bs-callout+.bs-callout{margin-top:-5px}.bs-callout-default{border-left-color:#fa023c}.bs-callout-default h4{color:#fa023c}.bs-callout-primary{border-left-color:#428bca}.bs-callout-primary h4{color:#428bca}.bs-callout-success{border-left-color:#5cb85c}.bs-callout-success h4{color:#5cb85c}.bs-callout-danger{border-left-color:#d9534f}.bs-callout-danger h4{color:#d9534f}.bs-callout-warning{border-left-color:#f0ad4e}.bs-callout-warning h4{color:#f0ad4e}.bs-callout-info{border-left-color:#5bc0de}.bs-callout-info h4{color:#5bc0de}.admonition{padding:20px;margin:20px 0;border:1px solid #eee;border-left-width:5px;border-radius:3px;background-color:#fcfdff}.admonition p:last-child{margin-bottom:0}.admonition code{border-radius:3px}.admonition+.admonition{margin-top:-5px}.admonition.note{border-left-color:#428bca}.admonition.warning{border-left-color:#f0ad4e}.admonition.danger{border-left-color:#d9534f}.admonition-title{font-size:19px;font-style:normal;font-weight:400;margin-top:0;margin-bottom:5px}.admonition.note>.admonition-title{color:#428bca}.admonition.warning>.admonition-title{color:#f0ad4e}.admonition.danger>.admonition-title{color:#d9534f}
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
/*
Cinder Theme for MkDocs | Copyright 2015 Christopher Simpkins | MIT License
*/
body {
font-family:"Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 1.7;
background-color: #FFF;
color: #343838;
}
h1, h2, h3, h4, h5, h6 {
font-family:'PT Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
color: #222;
}
h1 small, h2 small, h3 small, h4 small, h5 small, h6 small, .h1 small, .h2 small, .h3 small, .h4 small, .h5 small, .h6 small, h1 .small, h2 .small, h3 .small, h4 .small, h5 .small, h6 .small, .h1 .small, .h2 .small, .h3 .small, .h4 .small, .h5 .small, .h6 .small {
color: #B1B7B9;
}
h1, h2 {
font-weight: 700;
}
h4 {
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-weight: 300;
margin-top: 20px;
font-style: italic;
}
h5 {
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-weight: 300;
font-variant: small-caps;
}
pre, code {
background-color: #FCFDFF;
}
pre>code {
font-size: 13px;
}
pre {
margin-top: 25px;
margin-bottom: 25px;
}
.lead {
font-family:"Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: 400;
line-height: 1.4;
letter-spacing: 0.0312em;
color: #B1B7B9;
}
.navbar-default {
background-color: #343838;
border-bottom: 8px #EBF2F2 solid;
}
.bs-sidenav {
background-image: url("../img/grid11.png");
background-repeat: repeat;
font-size: 12px;
}
.well {
background-color: #FCFDFF;
}
.btn-default {
background-color:#FCFDFF;
}
.table-striped > tbody > tr:nth-child(2n+1) > td, .table-striped > tbody > tr:nth-child(2n+1) > th {
background-color: #FCFDFF;
}
#mkdocs-search-query:focus {
outline: none;
-webkit-box-shadow: none;
box-shadow: none;
}
#mkdocs-search-query {
font-family:"Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 20px;
font-weight: 700;
color: #343838;
height: 45px;
}
footer > hr {
width: 35%;
}
body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:16px;line-height:1.7;background-color:#FFF;color:#343838}h1,h2,h3,h4,h5,h6{font-family:'PT Sans','Helvetica Neue',Helvetica,Arial,sans-serif;color:#222}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{color:#b1b7b9}h1,h2{font-weight:700}h4{font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-weight:300;margin-top:20px;font-style:italic}h5{font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-weight:300;font-variant:small-caps}pre,code{background-color:#fcfdff}pre>code{font-size:13px}pre{margin-top:25px;margin-bottom:25px}.lead{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:400;line-height:1.4;letter-spacing:.0312em;color:#b1b7b9}.navbar-default{background-color:#343838;border-bottom:8px #ebf2f2 solid}.bs-sidenav{background-image:url("../img/grid11.png");background-repeat:repeat;font-size:12px}.well{background-color:#fcfdff}.btn-default{background-color:#fcfdff}.table-striped>tbody>tr:nth-child(2n+1)>td,.table-striped>tbody>tr:nth-child(2n+1)>th{background-color:#fcfdff}#mkdocs-search-query:focus{outline:0;-webkit-box-shadow:none;box-shadow:none}#mkdocs-search-query{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:20px;font-weight:700;color:#343838;height:45px}footer>hr{width:35%}
/*
github.com style (c) Vasily Polovnyov <vast@whiteants.net>
*/
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
color: #333;
background: #FCFDFF;
}
.hljs-comment,
.hljs-quote {
color: #998;
font-style: italic;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-subst {
color: #333;
font-weight: bold;
}
.hljs-number,
.hljs-literal,
.hljs-variable,
.hljs-template-variable,
.hljs-tag .hljs-attr {
color: #008080;
}
.hljs-string,
.hljs-doctag {
color: #d14;
}
.hljs-title,
.hljs-section,
.hljs-selector-id {
color: #900;
font-weight: bold;
}
.hljs-subst {
font-weight: normal;
}
.hljs-type,
.hljs-class .hljs-title {
color: #458;
font-weight: bold;
}
.hljs-tag,
.hljs-name,
.hljs-attribute {
color: #000080;
font-weight: normal;
}
.hljs-regexp,
.hljs-link {
color: #009926;
}
.hljs-symbol,
.hljs-bullet {
color: #990073;
}
.hljs-built_in,
.hljs-builtin-name {
color: #0086b3;
}
.hljs-meta {
color: #999;
font-weight: bold;
}
.hljs-deletion {
background: #fdd;
}
.hljs-addition {
background: #dfd;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}
.hljs{display:block;overflow-x:auto;padding:.5em;color:#333;background:#fcfdff}.hljs-comment,.hljs-quote{color:#998;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-subst{color:#333;font-weight:bold}.hljs-number,.hljs-literal,.hljs-variable,.hljs-template-variable,.hljs-tag .hljs-attr{color:teal}.hljs-string,.hljs-doctag{color:#d14}.hljs-title,.hljs-section,.hljs-selector-id{color:#900;font-weight:bold}.hljs-subst{font-weight:normal}.hljs-type,.hljs-class .hljs-title{color:#458;font-weight:bold}.hljs-tag,.hljs-name,.hljs-attribute{color:navy;font-weight:normal}.hljs-regexp,.hljs-link{color:#009926}.hljs-symbol,.hljs-bullet{color:#990073}.hljs-built_in,.hljs-builtin-name{color:#0086b3}.hljs-meta{color:#999;font-weight:bold}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold}
This source diff could not be displayed because it is too large. You can view the blob instead.
img/grid1.png

251 Bytes

img/grid10.png

495 Bytes

img/grid11.png

253 Bytes

img/grid12.png

260 Bytes

img/grid13.png

266 Bytes

img/grid14.png

240 Bytes

img/grid15.png

442 Bytes

img/grid16.png

442 Bytes

img/grid17.png

442 Bytes

img/grid18.png

457 Bytes

img/grid19.png

427 Bytes

img/grid2.png

271 Bytes

img/grid20.png

493 Bytes

img/grid3.png

266 Bytes

img/grid4.png

244 Bytes

img/grid5.png

442 Bytes

img/grid6.png

460 Bytes

img/grid7.png

442 Bytes

img/grid8.png

457 Bytes

img/grid9.png

456 Bytes

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="./img/favicon.ico">
<title>Home - CAP</title>
<link rel="stylesheet" href="//use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
<link rel="stylesheet" href="//cdn.jsdelivr.net/font-hack/3.003/css/hack.min.css">
<link href='//fonts.googleapis.com/css?family=PT+Sans:400,400italic,700,700italic&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href='//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,700italic,400,300,600,700&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href="./css/bootstrap-custom.min.css" rel="stylesheet">
<link href="./css/base.min.css" rel="stylesheet">
<link href="./css/cinder.min.css" rel="stylesheet">
<link rel="stylesheet" href="./css/highlight.min.css">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<![endif]-->
<script src="//ajax.googleapis.com/ajax/libs/webfont/1.6.28/webfont.js"></script>
<script>
WebFont.load({
google: {
families: ['Open Sans', 'PT Sans']
}
});
</script>
</head>
<body class="homepage" >
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<!-- Collapsed navigation -->
<div class="navbar-header">
<!-- Expander button -->
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- Main title -->
<a class="navbar-brand" href=".">CAP</a>
</div>
<!-- Expanded navigation -->
<div class="navbar-collapse collapse">
<!-- Main navigation -->
<ul class="nav navbar-nav">
<li class="active">
<a href=".">Home</a>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/us/flat/16.png">User Guide <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="user-guide/getting-started/">Getting Started</a>
</li>
<li >
<a href="user-guide/api-interface/">API Interface</a>
</li>
<li >
<a href="user-guide/configuration/">Configuration</a>
</li>
<li >
<a href="user-guide/design-principle.md.md">Design Principle</a>
</li>
<li >
<a href="user-guide/implementation-mechanisms/">Implementation Mechanisms</a>
</li>
<li >
<a href="user-guide/distributed-transactions/">Distributed Transactions</a>
</li>
<li >
<a href="user-guide/faq/">FAQ</a>
</li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/cn/flat/16.png">使用指南 <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="user-guide-cn/getting-started/">快速开始</a>
</li>
<li >
<a href="user-guide-cn/api-interface/">API 接口</a>
</li>
<li >
<a href="user-guide-cn/configuration/">配置</a>
</li>
<li >
<a href="user-guide-cn/design-principle/">设计原理</a>
</li>
<li >
<a href="user-guide-cn/implementation-mechanisms/">实现</a>
</li>
<li >
<a href="user-guide-cn/distributed-transactions/">分布式事务</a>
</li>
<li >
<a href="user-guide-cn/faq/">FAQ</a>
</li>
</ul>
</li>
<li >
<a href="about/">About</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<a href="#" data-toggle="modal" data-target="#mkdocs_search_modal">
<i class="fas fa-search"></i> Search
</a>
</li>
<li class="disabled">
<a rel="next" >
<i class="fas fa-arrow-left"></i> Previous
</a>
</li>
<li >
<a rel="prev" href="user-guide/getting-started/">
Next <i class="fas fa-arrow-right"></i>
</a>
</li>
<li>
<a href="https://github.com/dotnetcore/CAP/edit/master/docs/index.md"><i class="fab fa-github"></i> Edit on GitHub</a>
</li>
</ul>
</div>
</div>
</div>
<div class="container">
<div class="col-md-3"><div class="bs-sidebar hidden-print affix well" role="complementary">
<ul class="nav bs-sidenav">
<li class="first-level active"><a href="#cap">CAP</a></li>
<li class="second-level"><a href="#introduction">Introduction</a></li>
<li class="second-level"><a href="#contributing">Contributing</a></li>
<li class="second-level"><a href="#license">License</a></li>
</ul>
</div></div>
<div class="col-md-9" role="main">
<h1 id="cap">CAP</h1>
<p>CAP is a library based on .Net standard, which is a solution to deal with distributed transactions, also has the function of EventBus, it is lightweight, easy to use, and efficiently.</p>
<h2 id="introduction">Introduction</h2>
<p>In the process of building an SOA or MicroService system, we usually need to use the event to integrate each services. In the process, the simple use of message queue does not guarantee the reliability. CAP is adopted the local message table program integrated with the current database to solve the exception may occur in the process of the distributed system calling each other. It can ensure that the event messages are not lost in any case.</p>
<p>You can also use the CAP as an EventBus. The CAP provides a simpler way to implement event publishing and subscriptions. You do not need to inherit or implement any interface during the process of subscription and sending.</p>
<p>This is a diagram of the CAP working in the ASP.NET Core MicroService architecture:</p>
<p><img alt="cap.png" src="img/architecture.png" /></p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>CAP implements the Outbox Pattern described in the <a href="https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/multi-container-microservice-net-applications/subscribe-events#designing-atomicity-and-resiliency-when-publishing-to-the-event-bus">eShop ebook</a>.</p>
</div>
<h2 id="contributing">Contributing</h2>
<p>One of the easiest ways to contribute is to participate in discussions and discuss issues. You can also contribute by submitting pull requests with code changes.</p>
<p>If you have any question or problems, please report them on the CAP repository:</p>
<p><a href="https://github.com/dotnetcore/cap/issues/new"><button class="btn btn-primary btn-lg" type="submit"><i class="fab fa-github fa-2x"></i> Report Issue</button></a>
<a href="https://github.com/dotnetcore/cap/issues"><button class="btn btn-primary btn-lg" type="submit"> Active Issues <i class="fab fa-github fa-2x"></i></button></a></p>
<h2 id="license">License</h2>
<p>CAP is licensed under the <a href="https://github.com/dotnetcore/CAP/blob/master/LICENSE.txt">MIT license</a>.</p></div>
</div>
<footer class="col-md-12 text-center">
<hr>
<p>
<small>Documentation built with <a href="http://www.mkdocs.org/">MkDocs</a>.</p></small>
</footer>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="./js/bootstrap-3.0.3.min.js"></script>
<script src="./js/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script>
var base_url = '.';
</script>
<!-- <script data-main="./mkdocs/js/search.js" src="./mkdocs/js/require.js"></script> -->
<script src="./js/base.js"></script>
<script src="search/main.js"></script>
<div class="modal" id="mkdocs_search_modal" tabindex="-1" role="dialog" aria-labelledby="Search Modal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span>
<span class="sr-only">Close</span>
</button>
<h4 class="modal-title" id="exampleModalLabel">Search</h4>
</div>
<div class="modal-body">
<p>
From here you can search these documents. Enter your search terms below.
</p>
<form role="form">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search..." id="mkdocs-search-query">
</div>
</form>
<div id="mkdocs-search-results"></div>
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
</body>
</html>
<!--
MkDocs version : 1.0.4
Build Date UTC : 2019-02-02 11:35:20
-->
/* Highlight */
$( document ).ready(function() {
hljs.initHighlightingOnLoad();
$('table').addClass('table table-striped table-hover');
});
$('body').scrollspy({
target: '.bs-sidebar',
});
/* Prevent disabled links from causing a page reload */
$("li.disabled a").click(function() {
event.preventDefault();
});
/*!
* Bootstrap v3.0.3 (http://getbootstrap.com)
* Copyright 2013 Twitter, Inc.
* Licensed under http://www.apache.org/licenses/LICENSE-2.0
*/
if("undefined"==typeof jQuery)throw new Error("Bootstrap requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]}}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d)};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.is("input")?"val":"html",e=c.data();a+="Text",e.resetText||c.data("resetText",c[d]()),c[d](e[a]||this.options[a]),setTimeout(function(){"loadingText"==a?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.closest('[data-toggle="buttons"]'),b=!0;if(a.length){var c=this.$element.find("input");"radio"===c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?b=!1:a.find(".active").removeClass("active")),b&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}b&&this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition.end&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}this.sliding=!0,f&&this.pause();var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});if(!e.hasClass("active")){if(this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid.bs.carousel",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(j),j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid.bs.carousel")},0)}).emulateTransitionEnd(600)}else{if(this.$element.trigger(j),j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid.bs.carousel")}return f&&this.cycle(),this}};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?(this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350),void 0):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(jQuery),+function(a){"use strict";function b(){a(d).remove(),a(e).each(function(b){var d=c(a(this));d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown")),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown"))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){if("ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('<div class="dropdown-backdrop"/>').insertAfter(a(this)).on("click",b),f.trigger(d=a.Event("show.bs.dropdown")),d.isDefaultPrevented())return;f.toggleClass("open").trigger("shown.bs.dropdown"),e.focus()}return!1}},f.prototype.keydown=function(b){if(/(38|40|27)/.test(b.keyCode)){var d=a(this);if(b.preventDefault(),b.stopPropagation(),!d.is(".disabled, :disabled")){var f=c(d),g=f.hasClass("open");if(!g||g&&27==b.keyCode)return 27==b.which&&f.find(e).focus(),d.click();var h=a("[role=menu] li:not(.divider):visible a",f);if(h.length){var i=h.index(h.filter(":focus"));38==b.keyCode&&i>0&&i--,40==b.keyCode&&i<h.length-1&&i++,~i||(i=0),h.eq(i).focus()}}}};var g=a.fn.dropdown;a.fn.dropdown=function(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new f(this)),"string"==typeof b&&d[b].call(c)})},a.fn.dropdown.Constructor=f,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=g,this},a(document).on("click.bs.dropdown.data-api",b).on("click.bs.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.bs.dropdown.data-api",e,f.prototype.toggle).on("keydown.bs.dropdown.data-api",e+", [role=menu]",f.prototype.keydown)}(jQuery),+function(a){"use strict";var b=function(b,c){this.options=c,this.$element=a(b),this.$backdrop=this.isShown=null,this.options.remote&&this.$element.load(this.options.remote)};b.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},b.prototype.toggle=function(a){return this[this.isShown?"hide":"show"](a)},b.prototype.show=function(b){var c=this,d=a.Event("show.bs.modal",{relatedTarget:b});this.$element.trigger(d),this.isShown||d.isDefaultPrevented()||(this.isShown=!0,this.escape(),this.$element.on("click.dismiss.modal",'[data-dismiss="modal"]',a.proxy(this.hide,this)),this.backdrop(function(){var d=a.support.transition&&c.$element.hasClass("fade");c.$element.parent().length||c.$element.appendTo(document.body),c.$element.show(),d&&c.$element[0].offsetWidth,c.$element.addClass("in").attr("aria-hidden",!1),c.enforceFocus();var e=a.Event("shown.bs.modal",{relatedTarget:b});d?c.$element.find(".modal-dialog").one(a.support.transition.end,function(){c.$element.focus().trigger(e)}).emulateTransitionEnd(300):c.$element.focus().trigger(e)}))},b.prototype.hide=function(b){b&&b.preventDefault(),b=a.Event("hide.bs.modal"),this.$element.trigger(b),this.isShown&&!b.isDefaultPrevented()&&(this.isShown=!1,this.escape(),a(document).off("focusin.bs.modal"),this.$element.removeClass("in").attr("aria-hidden",!0).off("click.dismiss.modal"),a.support.transition&&this.$element.hasClass("fade")?this.$element.one(a.support.transition.end,a.proxy(this.hideModal,this)).emulateTransitionEnd(300):this.hideModal())},b.prototype.enforceFocus=function(){a(document).off("focusin.bs.modal").on("focusin.bs.modal",a.proxy(function(a){this.$element[0]===a.target||this.$element.has(a.target).length||this.$element.focus()},this))},b.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.bs.modal",a.proxy(function(a){27==a.which&&this.hide()},this)):this.isShown||this.$element.off("keyup.dismiss.bs.modal")},b.prototype.hideModal=function(){var a=this;this.$element.hide(),this.backdrop(function(){a.removeBackdrop(),a.$element.trigger("hidden.bs.modal")})},b.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},b.prototype.backdrop=function(b){var c=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var d=a.support.transition&&c;if(this.$backdrop=a('<div class="modal-backdrop '+c+'" />').appendTo(document.body),this.$element.on("click.dismiss.modal",a.proxy(function(a){a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus.call(this.$element[0]):this.hide.call(this))},this)),d&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;d?this.$backdrop.one(a.support.transition.end,b).emulateTransitionEnd(150):b()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(a.support.transition.end,b).emulateTransitionEnd(150):b()):b&&b()};var c=a.fn.modal;a.fn.modal=function(c,d){return this.each(function(){var e=a(this),f=e.data("bs.modal"),g=a.extend({},b.DEFAULTS,e.data(),"object"==typeof c&&c);f||e.data("bs.modal",f=new b(this,g)),"string"==typeof c?f[c](d):g.show&&f.show(d)})},a.fn.modal.Constructor=b,a.fn.modal.noConflict=function(){return a.fn.modal=c,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(b){var c=a(this),d=c.attr("href"),e=a(c.attr("data-target")||d&&d.replace(/.*(?=#[^\s]+$)/,"")),f=e.data("modal")?"toggle":a.extend({remote:!/#/.test(d)&&d},e.data(),c.data());b.preventDefault(),e.modal(f,this).one("hide",function(){c.is(":visible")&&c.focus()})}),a(document).on("show.bs.modal",".modal",function(){a(document.body).addClass("modal-open")}).on("hidden.bs.modal",".modal",function(){a(document.body).removeClass("modal-open")})}(jQuery),+function(a){"use strict";var b=function(a,b){this.type=this.options=this.enabled=this.timeout=this.hoverState=this.$element=null,this.init("tooltip",a,b)};b.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},b.prototype.init=function(b,c,d){this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d);for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focus",i="hover"==g?"mouseleave":"blur";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},b.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},b.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type);return clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show),void 0):c.show()},b.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type);return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide),void 0):c.hide()},b.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){if(this.$element.trigger(b),b.isDefaultPrevented())return;var c=this.tip();this.setContent(),this.options.animation&&c.addClass("fade");var d="function"==typeof this.options.placement?this.options.placement.call(this,c[0],this.$element[0]):this.options.placement,e=/\s?auto?\s?/i,f=e.test(d);f&&(d=d.replace(e,"")||"top"),c.detach().css({top:0,left:0,display:"block"}).addClass(d),this.options.container?c.appendTo(this.options.container):c.insertAfter(this.$element);var g=this.getPosition(),h=c[0].offsetWidth,i=c[0].offsetHeight;if(f){var j=this.$element.parent(),k=d,l=document.documentElement.scrollTop||document.body.scrollTop,m="body"==this.options.container?window.innerWidth:j.outerWidth(),n="body"==this.options.container?window.innerHeight:j.outerHeight(),o="body"==this.options.container?0:j.offset().left;d="bottom"==d&&g.top+g.height+i-l>n?"top":"top"==d&&g.top-l-i<0?"bottom":"right"==d&&g.right+h>m?"left":"left"==d&&g.left-h<o?"right":d,c.removeClass(k).addClass(d)}var p=this.getCalculatedOffset(d,g,h,i);this.applyPlacement(p,d),this.$element.trigger("shown.bs."+this.type)}},b.prototype.applyPlacement=function(a,b){var c,d=this.tip(),e=d[0].offsetWidth,f=d[0].offsetHeight,g=parseInt(d.css("margin-top"),10),h=parseInt(d.css("margin-left"),10);isNaN(g)&&(g=0),isNaN(h)&&(h=0),a.top=a.top+g,a.left=a.left+h,d.offset(a).addClass("in");var i=d[0].offsetWidth,j=d[0].offsetHeight;if("top"==b&&j!=f&&(c=!0,a.top=a.top+f-j),/bottom|top/.test(b)){var k=0;a.left<0&&(k=-2*a.left,a.left=0,d.offset(a),i=d[0].offsetWidth,j=d[0].offsetHeight),this.replaceArrow(k-e+i,i,"left")}else this.replaceArrow(j-f,j,"top");c&&d.offset(a)},b.prototype.replaceArrow=function(a,b,c){this.arrow().css(c,a?50*(1-a/b)+"%":"")},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},b.prototype.hide=function(){function b(){"in"!=c.hoverState&&d.detach()}var c=this,d=this.tip(),e=a.Event("hide.bs."+this.type);return this.$element.trigger(e),e.isDefaultPrevented()?void 0:(d.removeClass("in"),a.support.transition&&this.$tip.hasClass("fade")?d.one(a.support.transition.end,b).emulateTransitionEnd(150):b(),this.$element.trigger("hidden.bs."+this.type),this)},b.prototype.fixTitle=function(){var a=this.$element;(a.attr("title")||"string"!=typeof a.attr("data-original-title"))&&a.attr("data-original-title",a.attr("title")||"").attr("title","")},b.prototype.hasContent=function(){return this.getTitle()},b.prototype.getPosition=function(){var b=this.$element[0];return a.extend({},"function"==typeof b.getBoundingClientRect?b.getBoundingClientRect():{width:b.offsetWidth,height:b.offsetHeight},this.$element.offset())},b.prototype.getCalculatedOffset=function(a,b,c,d){return"bottom"==a?{top:b.top+b.height,left:b.left+b.width/2-c/2}:"top"==a?{top:b.top-d,left:b.left+b.width/2-c/2}:"left"==a?{top:b.top+b.height/2-d/2,left:b.left-c}:{top:b.top+b.height/2-d/2,left:b.left+b.width}},b.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},b.prototype.tip=function(){return this.$tip=this.$tip||a(this.options.template)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},b.prototype.validate=function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},b.prototype.enable=function(){this.enabled=!0},b.prototype.disable=function(){this.enabled=!1},b.prototype.toggleEnabled=function(){this.enabled=!this.enabled},b.prototype.toggle=function(b){var c=b?a(b.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type):this;c.tip().hasClass("in")?c.leave(c):c.enter(c)},b.prototype.destroy=function(){this.hide().$element.off("."+this.type).removeData("bs."+this.type)};var c=a.fn.tooltip;a.fn.tooltip=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof c&&c;e||d.data("bs.tooltip",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.tooltip.Constructor=b,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=c,this}}(jQuery),+function(a){"use strict";var b=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");b.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"html":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(c).is("body")?a(window):a(c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#\w/.test(e)&&a(e);return f&&f.length&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parents(".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top()),"function"==typeof h&&(h=f.bottom());var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;this.affixed!==i&&(this.unpin&&this.$element.css("top",""),this.affixed=i,this.unpin="bottom"==i?e.top-d:null,this.$element.removeClass(b.RESET).addClass("affix"+(i?"-"+i:"")),"bottom"==i&&this.$element.offset({top:document.body.offsetHeight-h-this.$element.height()}))}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(jQuery);
\ No newline at end of file
/*! highlight.js v9.13.1 | BSD3 License | git.io/hljslicense */
!function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=M.exec(o))return w(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||w(i))return i}function o(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function c(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function u(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset<r[0].offset?e:r:"start"===r[0].event?e:r:e.length?e:r}function o(e){function r(e){return" "+e.nodeName+'="'+n(e.value).replace('"',"&quot;")+'"'}l+="<"+t(e)+E.map.call(e.attributes,r).join("")+">"}function c(e){l+="</"+t(e)+">"}function u(e){("start"===e.event?o:c)(e.node)}for(var s=0,l="",f=[];e.length||r.length;){var g=i();if(l+=n(a.substring(s,g[0].offset)),s=g[0].offset,g===e){f.reverse().forEach(c);do u(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===s);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),u(g.splice(0,1)[0])}return l+n(a.substr(s))}function s(e){return e.v&&!e.cached_variants&&(e.cached_variants=e.v.map(function(n){return o(e,{v:null},n)})),e.cached_variants||e.eW&&[o(e)]||[e]}function l(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var o={},c=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");o[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?c("keyword",a.k):B(a.k).forEach(function(e){c(e,a.k[e])}),a.k=o}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.endSameAsBegin&&(a.e=a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]),a.c=Array.prototype.concat.apply([],a.c.map(function(e){return s("self"===e?a:e)})),a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var u=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=u.length?t(u.join("|"),!0):{exec:function(){return null}}}}r(e)}function f(e,t,a,i){function o(e){return new RegExp(e.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")}function c(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t].endSameAsBegin&&(n.c[t].eR=o(n.c[t].bR.exec(e)[0])),n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function s(e,n){return!a&&r(n.iR,e)}function p(e,n){var t=R.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function d(e,n,t,r){var a=r?"":j.classPrefix,i='<span class="'+a,o=t?"":I;return i+=e+'">',i+n+o}function h(){var e,t,r,a;if(!E.k)return n(k);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(k);r;)a+=n(k.substring(t,r.index)),e=p(E,r),e?(M+=e[1],a+=d(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(k);return a+n(k.substr(t))}function b(){var e="string"==typeof E.sL;if(e&&!L[E.sL])return n(k);var t=e?f(E.sL,k,!0,B[E.sL]):g(k,E.sL.length?E.sL:void 0);return E.r>0&&(M+=t.r),e&&(B[E.sL]=t.top),d(t.language,t.value,!1,!0)}function v(){y+=null!=E.sL?b():h(),k=""}function m(e){y+=e.cN?d(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function N(e,n){if(k+=e,null==n)return v(),0;var t=c(n,E);if(t)return t.skip?k+=n:(t.eB&&(k+=n),v(),t.rB||t.eB||(k=n)),m(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?k+=n:(a.rE||a.eE||(k+=n),v(),a.eE&&(k=n));do E.cN&&(y+=I),E.skip||E.sL||(M+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&(r.endSameAsBegin&&(r.starts.eR=r.eR),m(r.starts,"")),a.rE?0:n.length}if(s(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"<unnamed>")+'"');return k+=n,n.length||1}var R=w(e);if(!R)throw new Error('Unknown language: "'+e+'"');l(R);var x,E=i||R,B={},y="";for(x=E;x!==R;x=x.parent)x.cN&&(y=d(x.cN,"",!0)+y);var k="",M=0;try{for(var C,A,S=0;;){if(E.t.lastIndex=S,C=E.t.exec(t),!C)break;A=N(t.substring(S,C.index),C[0]),S=C.index+A}for(N(t.substr(S)),x=E;x.parent;x=x.parent)x.cN&&(y+=I);return{r:M,value:y,language:e,top:E}}catch(O){if(O.message&&-1!==O.message.indexOf("Illegal"))return{r:0,value:n(t)};throw O}}function g(e,t){t=t||j.languages||B(L);var r={r:0,value:n(e)},a=r;return t.filter(w).filter(x).forEach(function(n){var t=f(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function p(e){return j.tabReplace||j.useBR?e.replace(C,function(e,n){return j.useBR&&"\n"===e?"<br>":j.tabReplace?n.replace(/\t/g,j.tabReplace):""}):e}function d(e,n,t){var r=n?y[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function h(e){var n,t,r,o,s,l=i(e);a(l)||(j.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(/<br[ \/]*>/g,"\n")):n=e,s=n.textContent,r=l?f(l,s,!0):g(s),t=c(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=u(t,c(o),s)),r.value=p(r.value),e.innerHTML=r.value,e.className=d(e.className,l,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function b(e){j=o(j,e)}function v(){if(!v.called){v.called=!0;var e=document.querySelectorAll("pre code");E.forEach.call(e,h)}}function m(){addEventListener("DOMContentLoaded",v,!1),addEventListener("load",v,!1)}function N(n,t){var r=L[n]=t(e);r.aliases&&r.aliases.forEach(function(e){y[e]=n})}function R(){return B(L)}function w(e){return e=(e||"").toLowerCase(),L[e]||L[y[e]]}function x(e){var n=w(e);return n&&!n.disableAutodetect}var E=[],B=Object.keys,L={},y={},k=/^(no-?highlight|plain|text)$/i,M=/\blang(?:uage)?-([\w-]+)\b/i,C=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,I="</span>",j={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};return e.highlight=f,e.highlightAuto=g,e.fixMarkup=p,e.highlightBlock=h,e.configure=b,e.initHighlighting=v,e.initHighlightingOnLoad=m,e.registerLanguage=N,e.listLanguages=R,e.getLanguage=w,e.autoDetection=x,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("objectivec",function(e){var t={cN:"built_in",b:"\\b(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)\\w+"},_={keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},i=/[a-zA-Z@][a-zA-Z0-9_]*/,n="@interface @class @protocol @implementation";return{aliases:["mm","objc","obj-c"],k:_,l:i,i:"</",c:[t,e.CLCM,e.CBCM,e.CNM,e.QSM,{cN:"string",v:[{b:'@"',e:'"',i:"\\n",c:[e.BE]},{b:"'",e:"[^\\\\]'",i:"[^\\\\][^']"}]},{cN:"meta",b:"#",e:"$",c:[{cN:"meta-string",v:[{b:'"',e:'"'},{b:"<",e:">"}]}]},{cN:"class",b:"("+n.split(" ").join("|")+")\\b",e:"({|$)",eE:!0,k:n,l:i,c:[e.UTM]},{b:"\\."+e.UIR,r:0}]}});hljs.registerLanguage("sql",function(e){var t=e.C("--","$");return{cI:!0,i:/[<>{}*]/,c:[{bK:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment with",e:/;/,eW:!0,l:/[\w\.]+/,k:{keyword:"as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias allocate allow alter always analyze ancillary and any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second section securefile security seed segment select self sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null unknown",built_in:"array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp varchar varying void"},c:[{cN:"string",b:"'",e:"'",c:[e.BE,{b:"''"}]},{cN:"string",b:'"',e:'"',c:[e.BE,{b:'""'}]},{cN:"string",b:"`",e:"`",c:[e.BE]},e.CNM,e.CBCM,t,e.HCM]},e.CBCM,t,e.HCM]}});hljs.registerLanguage("ruby",function(e){var b="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",r={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},c={cN:"doctag",b:"@[A-Za-z]+"},a={b:"#<",e:">"},s=[e.C("#","$",{c:[c]}),e.C("^\\=begin","^\\=end",{c:[c],r:10}),e.C("^__END__","\\n$")],n={cN:"subst",b:"#\\{",e:"}",k:r},t={cN:"string",c:[e.BE,n],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/`/,e:/`/},{b:"%[qQwWx]?\\(",e:"\\)"},{b:"%[qQwWx]?\\[",e:"\\]"},{b:"%[qQwWx]?{",e:"}"},{b:"%[qQwWx]?<",e:">"},{b:"%[qQwWx]?/",e:"/"},{b:"%[qQwWx]?%",e:"%"},{b:"%[qQwWx]?-",e:"-"},{b:"%[qQwWx]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{b:/<<(-?)\w+$/,e:/^\s*\w+$/}]},i={cN:"params",b:"\\(",e:"\\)",endsParent:!0,k:r},d=[t,a,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{b:"<\\s*",c:[{b:"("+e.IR+"::)?"+e.IR}]}].concat(s)},{cN:"function",bK:"def",e:"$|;",c:[e.inherit(e.TM,{b:b}),i].concat(s)},{b:e.IR+"::"},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",r:0},{cN:"symbol",b:":(?!\\s)",c:[t,{b:b}],r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{cN:"params",b:/\|/,e:/\|/,k:r},{b:"("+e.RSR+"|unless)\\s*",k:"unless",c:[a,{cN:"regexp",c:[e.BE,n],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}].concat(s),r:0}].concat(s);n.c=d,i.c=d;var l="[>?]>",o="[\\w#]+\\(\\w+\\):\\d+:\\d+>",u="(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>",w=[{b:/^\s*=>/,starts:{e:"$",c:d}},{cN:"meta",b:"^("+l+"|"+o+"|"+u+")",starts:{e:"$",c:d}}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:r,i:/\/\*/,c:s.concat(w).concat(d)}});hljs.registerLanguage("yaml",function(e){var b="true false yes no null",a="^[ \\-]*",r="[a-zA-Z_][\\w\\-]*",t={cN:"attr",v:[{b:a+r+":"},{b:a+'"'+r+'":'},{b:a+"'"+r+"':"}]},c={cN:"template-variable",v:[{b:"{{",e:"}}"},{b:"%{",e:"}"}]},l={cN:"string",r:0,v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/\S+/}],c:[e.BE,c]};return{cI:!0,aliases:["yml","YAML","yaml"],c:[t,{cN:"meta",b:"^---s*$",r:10},{cN:"string",b:"[\\|>] *$",rE:!0,c:l.c,e:t.v[0].b},{b:"<%[%=-]?",e:"[%-]?%>",sL:"ruby",eB:!0,eE:!0,r:0},{cN:"type",b:"!"+e.UIR},{cN:"type",b:"!!"+e.UIR},{cN:"meta",b:"&"+e.UIR+"$"},{cN:"meta",b:"\\*"+e.UIR+"$"},{cN:"bullet",b:"^ *-",r:0},e.HCM,{bK:b,k:{literal:b}},e.CNM,l]}});hljs.registerLanguage("xml",function(s){var e="[A-Za-z0-9\\._:-]+",t={eW:!0,i:/</,r:0,c:[{cN:"attr",b:e,r:0},{b:/=\s*/,r:0,c:[{cN:"string",endsParent:!0,v:[{b:/"/,e:/"/},{b:/'/,e:/'/},{b:/[^\s"'=<>`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist"],cI:!0,c:[{cN:"meta",b:"<!DOCTYPE",e:">",r:10,c:[{b:"\\[",e:"\\]"}]},s.C("<!--","-->",{r:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"meta",b:/<\?xml/,e:/\?>/,r:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0},{b:'b"',e:'"',skip:!0},{b:"b'",e:"'",skip:!0},s.inherit(s.ASM,{i:null,cN:null,c:null,skip:!0}),s.inherit(s.QSM,{i:null,cN:null,c:null,skip:!0})]},{cN:"tag",b:"<style(?=\\s|>|$)",e:">",k:{name:"style"},c:[t],starts:{e:"</style>",rE:!0,sL:["css","xml"]}},{cN:"tag",b:"<script(?=\\s|>|$)",e:">",k:{name:"script"},c:[t],starts:{e:"</script>",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"tag",b:"</?",e:"/?>",c:[{cN:"name",b:/[^\/><\s]+/,r:0},t]}]}});hljs.registerLanguage("markdown",function(e){return{aliases:["md","mkdown","mkd"],c:[{cN:"section",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"quote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"^```w*s*$",e:"^```s*$"},{b:"`.+?`"},{b:"^( {4}| )",e:"$",r:0}]},{b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].*?[\\)\\]]",rB:!0,c:[{cN:"string",b:"\\[",e:"\\]",eB:!0,rE:!0,r:0},{cN:"link",b:"\\]\\(",e:"\\)",eB:!0,eE:!0},{cN:"symbol",b:"\\]\\[",e:"\\]",eB:!0,eE:!0}],r:10},{b:/^\[[^\n]+\]:/,rB:!0,c:[{cN:"symbol",b:/\[/,e:/\]/,eB:!0,eE:!0},{cN:"link",b:/:\s*/,e:/$/,eB:!0}]}]}});hljs.registerLanguage("perl",function(e){var t="getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qqfileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmgetsub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedirioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when",r={cN:"subst",b:"[$@]\\{",e:"\\}",k:t},s={b:"->{",e:"}"},n={v:[{b:/\$\d/},{b:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{b:/[\$%@][^\s\w{]/,r:0}]},i=[e.BE,r,n],o=[n,e.HCM,e.C("^\\=\\w","\\=cut",{eW:!0}),s,{cN:"string",c:i,v:[{b:"q[qwxr]?\\s*\\(",e:"\\)",r:5},{b:"q[qwxr]?\\s*\\[",e:"\\]",r:5},{b:"q[qwxr]?\\s*\\{",e:"\\}",r:5},{b:"q[qwxr]?\\s*\\|",e:"\\|",r:5},{b:"q[qwxr]?\\s*\\<",e:"\\>",r:5},{b:"qw\\s+q",e:"q",r:5},{b:"'",e:"'",c:[e.BE]},{b:'"',e:'"'},{b:"`",e:"`",c:[e.BE]},{b:"{\\w+}",c:[],r:0},{b:"-?\\w+\\s*\\=\\>",c:[],r:0}]},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\/\\/|"+e.RSR+"|\\b(split|return|print|reverse|grep)\\b)\\s*",k:"split return print reverse grep",r:0,c:[e.HCM,{cN:"regexp",b:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",r:10},{cN:"regexp",b:"(m|qr)?/",e:"/[a-z]*",c:[e.BE],r:0}]},{cN:"function",bK:"sub",e:"(\\s*\\(.*?\\))?[;{]",eE:!0,r:5,c:[e.TM]},{b:"-\\w\\b",r:0},{b:"^__DATA__$",e:"^__END__$",sL:"mojolicious",c:[{b:"^@@.*",e:"$",cN:"comment"}]}];return r.c=o,s.c=o,{aliases:["pl","pm"],l:/[\w\.]+/,k:t,c:o}});hljs.registerLanguage("java",function(e){var a="[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",t=a+"(<"+a+"(\\s*,\\s*"+a+")*>)?",r="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",s="\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",c={cN:"number",b:s,r:0};return{aliases:["jsp"],k:r,i:/<\/|#/,c:[e.C("/\\*\\*","\\*/",{r:0,c:[{b:/\w+@/,r:0},{cN:"doctag",b:"@[A-Za-z]+"}]}),e.CLCM,e.CBCM,e.ASM,e.QSM,{cN:"class",bK:"class interface",e:/[{;=]/,eE:!0,k:"class interface",i:/[:"\[\]]/,c:[{bK:"extends implements"},e.UTM]},{bK:"new throw return else",r:0},{cN:"function",b:"("+t+"\\s+)+"+e.UIR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:r,c:[{b:e.UIR+"\\s*\\(",rB:!0,r:0,c:[e.UTM]},{cN:"params",b:/\(/,e:/\)/,k:r,r:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]},c,{cN:"meta",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},s={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]},a={cN:"string",b:/'/,e:/'/};return{aliases:["sh","zsh"],l:/\b-?[a-z\._]+\b/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"meta",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],r:0},e.HCM,s,a,t]}});hljs.registerLanguage("shell",function(s){return{aliases:["console"],c:[{cN:"meta",b:"^\\s{0,3}[\\w\\d\\[\\]()@-]*[>%$#]",starts:{e:"$",sL:"bash"}}]}});hljs.registerLanguage("swift",function(e){var i={keyword:"#available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation _ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},t={cN:"type",b:"\\b[A-Z][\\wÀ-ʸ']*",r:0},n=e.C("/\\*","\\*/",{c:["self"]}),r={cN:"subst",b:/\\\(/,e:"\\)",k:i,c:[]},a={cN:"string",c:[e.BE,r],v:[{b:/"""/,e:/"""/},{b:/"/,e:/"/}]},o={cN:"number",b:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",r:0};return r.c=[o],{k:i,c:[a,e.CLCM,n,t,o,{cN:"function",bK:"func",e:"{",eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{b:/</,e:/>/},{cN:"params",b:/\(/,e:/\)/,endsParent:!0,k:i,c:["self",o,a,e.CBCM,{b:":"}],i:/["']/}],i:/\[|%/},{cN:"class",bK:"struct protocol class extension enum",k:i,e:"\\{",eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{cN:"meta",b:"(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain)"},{bK:"import",e:/$/,c:[e.CLCM,n]}]}});hljs.registerLanguage("json",function(e){var i={literal:"true false null"},n=[e.QSM,e.CNM],r={e:",",eW:!0,eE:!0,c:n,k:i},t={b:"{",e:"}",c:[{cN:"attr",b:/"/,e:/"/,c:[e.BE],i:"\\n"},e.inherit(r,{b:/:/})],i:"\\S"},c={b:"\\[",e:"\\]",c:[e.inherit(r)],i:"\\S"};return n.splice(n.length,0,t,c),{c:n,k:i,i:"\\S"}});hljs.registerLanguage("nginx",function(e){var r={cN:"variable",v:[{b:/\$\d+/},{b:/\$\{/,e:/}/},{b:"[\\$\\@]"+e.UIR}]},b={eW:!0,l:"[a-z/_]+",k:{literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},r:0,i:"=>",c:[e.HCM,{cN:"string",c:[e.BE,r],v:[{b:/"/,e:/"/},{b:/'/,e:/'/}]},{b:"([a-z]+):/",e:"\\s",eW:!0,eE:!0,c:[r]},{cN:"regexp",c:[e.BE,r],v:[{b:"\\s\\^",e:"\\s|{|;",rE:!0},{b:"~\\*?\\s+",e:"\\s|{|;",rE:!0},{b:"\\*(\\.[a-z\\-]+)+"},{b:"([a-z\\-]+\\.)+\\*"}]},{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{cN:"number",b:"\\b\\d+[kKmMgGdshdwy]*\\b",r:0},r]};return{aliases:["nginxconf"],c:[e.HCM,{b:e.UIR+"\\s+{",rB:!0,e:"{",c:[{cN:"section",b:e.UIR}],r:0},{b:e.UIR+"\\s",e:";|{",rB:!0,c:[{cN:"attribute",b:e.UIR,starts:b}],r:0}],i:"[^\\s\\}]"}});hljs.registerLanguage("ini",function(e){var b={cN:"string",c:[e.BE],v:[{b:"'''",e:"'''",r:10},{b:'"""',e:'"""',r:10},{b:'"',e:'"'},{b:"'",e:"'"}]};return{aliases:["toml"],cI:!0,i:/\S/,c:[e.C(";","$"),e.HCM,{cN:"section",b:/^\s*\[+/,e:/\]+/},{b:/^[a-z0-9\[\]_-]+\s*=\s*/,e:"$",rB:!0,c:[{cN:"attr",b:/[a-z0-9\[\]_-]+/},{b:/=/,eW:!0,r:0,c:[{cN:"literal",b:/\bon|off|true|false|yes|no\b/},{cN:"variable",v:[{b:/\$[\w\d"][\w\d_]*/},{b:/\$\{(.*?)}/}]},b,{cN:"number",b:/([\+\-]+)?[\d]+_[\d_]+/},e.NM]}]}]}});hljs.registerLanguage("http",function(e){var t="HTTP/[0-9\\.]+";return{aliases:["https"],i:"\\S",c:[{b:"^"+t,e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{b:"^[A-Z]+ (.*?) "+t+"$",rB:!0,e:"$",c:[{cN:"string",b:" ",e:" ",eB:!0,eE:!0},{b:t},{cN:"keyword",b:"[A-Z]+"}]},{cN:"attribute",b:"^\\w",e:": ",eE:!0,i:"\\n|\\s|=",starts:{e:"$",r:0}},{b:"\\n\\n",starts:{sL:[],eW:!0}}]}});hljs.registerLanguage("css",function(e){var c="[a-zA-Z-][a-zA-Z0-9_-]*",t={b:/[A-Z\_\.\-]+\s*:/,rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:/\S/,e:":",eE:!0,starts:{eW:!0,eE:!0,c:[{b:/[\w-]+\(/,rB:!0,c:[{cN:"built_in",b:/[\w-]+/},{b:/\(/,e:/\)/,c:[e.ASM,e.QSM]}]},e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"number",b:"#[0-9A-Fa-f]+"},{cN:"meta",b:"!important"}]}}]};return{cI:!0,i:/[=\/|'\$]/,c:[e.CBCM,{cN:"selector-id",b:/#[A-Za-z0-9_-]+/},{cN:"selector-class",b:/\.[A-Za-z0-9_-]+/},{cN:"selector-attr",b:/\[/,e:/\]/,i:"$"},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{b:"@",e:"[{;]",i:/:/,c:[{cN:"keyword",b:/\w+/},{b:/\s/,eW:!0,eE:!0,r:0,c:[e.ASM,e.QSM,e.CSSNM]}]},{cN:"selector-tag",b:c,r:0},{b:"{",e:"}",i:/\S/,c:[e.CBCM,t]}]}});hljs.registerLanguage("javascript",function(e){var r="[A-Za-z$_][0-9A-Za-z$_]*",t={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},a={cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},n={cN:"subst",b:"\\$\\{",e:"\\}",k:t,c:[]},c={cN:"string",b:"`",e:"`",c:[e.BE,n]};n.c=[e.ASM,e.QSM,c,a,e.RM];var s=n.c.concat([e.CBCM,e.CLCM]);return{aliases:["js","jsx"],k:t,c:[{cN:"meta",r:10,b:/^\s*['"]use (strict|asm)['"]/},{cN:"meta",b:/^#!/,e:/$/},e.ASM,e.QSM,c,e.CLCM,e.CBCM,a,{b:/[{,]\s*/,r:0,c:[{b:r+"\\s*:",rB:!0,r:0,c:[{cN:"attr",b:r,r:0}]}]},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+r+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:r},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:s}]}]},{b:/</,e:/(\/\w+|\w+\/)>/,sL:"xml",c:[{b:/<\w+\s*\/>/,skip:!0},{b:/<\w+/,e:/(\/\w+|\w+\/)>/,skip:!0,c:[{b:/<\w+\s*\/>/,skip:!0},"self"]}]}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:r}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:s}],i:/\[|%/},{b:/\$[(.]/},e.METHOD_GUARD,{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor",e:/\{/,eE:!0}],i:/#(?!!)/}});hljs.registerLanguage("makefile",function(e){var i={cN:"variable",v:[{b:"\\$\\("+e.UIR+"\\)",c:[e.BE]},{b:/\$[@%<?\^\+\*]/}]},r={cN:"string",b:/"/,e:/"/,c:[e.BE,i]},a={cN:"variable",b:/\$\([\w-]+\s/,e:/\)/,k:{built_in:"subst patsubst strip findstring filter filter-out sort word wordlist firstword lastword dir notdir suffix basename addsuffix addprefix join wildcard realpath abspath error warning shell origin flavor foreach if or and call eval file value"},c:[i]},n={b:"^"+e.UIR+"\\s*[:+?]?=",i:"\\n",rB:!0,c:[{b:"^"+e.UIR,e:"[:+?]?=",eE:!0}]},t={cN:"meta",b:/^\.PHONY:/,e:/$/,k:{"meta-keyword":".PHONY"},l:/[\.\w]+/},l={cN:"section",b:/^[^\s]+:/,e:/$/,c:[i]};return{aliases:["mk","mak"],k:"define endef undefine ifdef ifndef ifeq ifneq else endif include -include sinclude override export unexport private vpath",l:/[\w-]+/,c:[e.HCM,i,r,a,n,t,l]}});hljs.registerLanguage("cs",function(e){var i={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long nameof object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let on orderby partial remove select set value var where yield",literal:"null false true"},r={cN:"number",v:[{b:"\\b(0b[01']+)"},{b:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{b:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],r:0},t={cN:"string",b:'@"',e:'"',c:[{b:'""'}]},a=e.inherit(t,{i:/\n/}),c={cN:"subst",b:"{",e:"}",k:i},n=e.inherit(c,{i:/\n/}),s={cN:"string",b:/\$"/,e:'"',i:/\n/,c:[{b:"{{"},{b:"}}"},e.BE,n]},b={cN:"string",b:/\$@"/,e:'"',c:[{b:"{{"},{b:"}}"},{b:'""'},c]},l=e.inherit(b,{i:/\n/,c:[{b:"{{"},{b:"}}"},{b:'""'},n]});c.c=[b,s,t,e.ASM,e.QSM,r,e.CBCM],n.c=[l,s,a,e.ASM,e.QSM,r,e.inherit(e.CBCM,{i:/\n/})];var o={v:[b,s,t,e.ASM,e.QSM]},d=e.IR+"(<"+e.IR+"(\\s*,\\s*"+e.IR+")*>)?(\\[\\])?";return{aliases:["csharp"],k:i,i:/::/,c:[e.C("///","$",{rB:!0,c:[{cN:"doctag",v:[{b:"///",r:0},{b:"<!--|-->"},{b:"</?",e:">"}]}]}),e.CLCM,e.CBCM,{cN:"meta",b:"#",e:"$",k:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},o,r,{bK:"class interface",e:/[{;=]/,i:/[^\s:,]/,c:[e.TM,e.CLCM,e.CBCM]},{bK:"namespace",e:/[{;=]/,i:/[^\s:]/,c:[e.inherit(e.TM,{b:"[a-zA-Z](\\.?\\w)*"}),e.CLCM,e.CBCM]},{cN:"meta",b:"^\\s*\\[",eB:!0,e:"\\]",eE:!0,c:[{cN:"meta-string",b:/"/,e:/"/}]},{bK:"new return throw await else",r:0},{cN:"function",b:"("+d+"\\s+)+"+e.IR+"\\s*\\(",rB:!0,e:/\s*[{;=]/,eE:!0,k:i,c:[{b:e.IR+"\\s*\\(",rB:!0,c:[e.TM],r:0},{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:i,r:0,c:[o,r,e.CBCM]},e.CLCM,e.CBCM]}]}});hljs.registerLanguage("coffeescript",function(e){var c={keyword:"in if for while finally new do return else break catch instanceof throw try this switch continue typeof delete debugger super yield import export from as default await then unless until loop of by when and or is isnt not",literal:"true false null undefined yes no on off",built_in:"npm require console print module global window document"},n="[A-Za-z$_][0-9A-Za-z$_]*",r={cN:"subst",b:/#\{/,e:/}/,k:c},i=[e.BNM,e.inherit(e.CNM,{starts:{e:"(\\s*/)?",r:0}}),{cN:"string",v:[{b:/'''/,e:/'''/,c:[e.BE]},{b:/'/,e:/'/,c:[e.BE]},{b:/"""/,e:/"""/,c:[e.BE,r]},{b:/"/,e:/"/,c:[e.BE,r]}]},{cN:"regexp",v:[{b:"///",e:"///",c:[r,e.HCM]},{b:"//[gim]*",r:0},{b:/\/(?![ *])(\\\/|.)*?\/[gim]*(?=\W|$)/}]},{b:"@"+n},{sL:"javascript",eB:!0,eE:!0,v:[{b:"```",e:"```"},{b:"`",e:"`"}]}];r.c=i;var s=e.inherit(e.TM,{b:n}),t="(\\(.*\\))?\\s*\\B[-=]>",o={cN:"params",b:"\\([^\\(]",rB:!0,c:[{b:/\(/,e:/\)/,k:c,c:["self"].concat(i)}]};return{aliases:["coffee","cson","iced"],k:c,i:/\/\*/,c:i.concat([e.C("###","###"),e.HCM,{cN:"function",b:"^\\s*"+n+"\\s*=\\s*"+t,e:"[-=]>",rB:!0,c:[s,o]},{b:/[:\(,=]\s*/,r:0,c:[{cN:"function",b:t,e:"[-=]>",rB:!0,c:[o]}]},{cN:"class",bK:"class",e:"$",i:/[:="\[\]]/,c:[{bK:"extends",eW:!0,i:/[:="\[\]]/,c:[s]},s]},{b:n+":",e:":",rB:!0,rE:!0,r:0}])}});hljs.registerLanguage("go",function(e){var t={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",literal:"true false iota nil",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{aliases:["golang"],k:t,i:"</",c:[e.CLCM,e.CBCM,{cN:"string",v:[e.QSM,{b:"'",e:"[^\\\\]'"},{b:"`",e:"`"}]},{cN:"number",v:[{b:e.CNR+"[dflsi]",r:1},e.CNM]},{b:/:=/},{cN:"function",bK:"func",e:/\s*\{/,eE:!0,c:[e.TM,{cN:"params",b:/\(/,e:/\)/,k:t,i:/["']/}]}]}});hljs.registerLanguage("php",function(e){var c={b:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},i={cN:"meta",b:/<\?(php)?|\?>/},t={cN:"string",c:[e.BE,i],v:[{b:'b"',e:'"'},{b:"b'",e:"'"},e.inherit(e.ASM,{i:null}),e.inherit(e.QSM,{i:null})]},a={v:[e.BNM,e.CNM]};return{aliases:["php","php3","php4","php5","php6","php7"],cI:!0,k:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally",c:[e.HCM,e.C("//","$",{c:[i]}),e.C("/\\*","\\*/",{c:[{cN:"doctag",b:"@[A-Za-z]+"}]}),e.C("__halt_compiler.+?;",!1,{eW:!0,k:"__halt_compiler",l:e.UIR}),{cN:"string",b:/<<<['"]?\w+['"]?$/,e:/^\w+;?$/,c:[e.BE,{cN:"subst",v:[{b:/\$\w+/},{b:/\{\$/,e:/\}/}]}]},i,{cN:"keyword",b:/\$this\b/},c,{b:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{cN:"function",bK:"function",e:/[;{]/,eE:!0,i:"\\$|\\[|%",c:[e.UTM,{cN:"params",b:"\\(",e:"\\)",c:["self",c,e.CBCM,t,a]}]},{cN:"class",bK:"class interface",e:"{",eE:!0,i:/[:\(\$"]/,c:[{bK:"extends implements"},e.UTM]},{bK:"namespace",e:";",i:/[\.']/,c:[e.UTM]},{bK:"use",e:";",c:[e.UTM]},{b:"=>"},t,a]}});hljs.registerLanguage("cpp",function(t){var e={cN:"keyword",b:"\\b[a-z\\d_]*_t\\b"},r={cN:"string",v:[{b:'(u8?|U|L)?"',e:'"',i:"\\n",c:[t.BE]},{b:'(u8?|U|L)?R"\\(',e:'\\)"'},{b:"'\\\\?.",e:"'",i:"."}]},s={cN:"number",v:[{b:"\\b(0b[01']+)"},{b:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{b:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],r:0},i={cN:"meta",b:/#\s*[a-z]+\b/,e:/$/,k:{"meta-keyword":"if else elif endif define undef warning error line pragma ifdef ifndef include"},c:[{b:/\\\n/,r:0},t.inherit(r,{cN:"meta-string"}),{cN:"meta-string",b:/<[^\n>]*>/,e:/$/,i:"\\n"},t.CLCM,t.CBCM]},a=t.IR+"\\s*\\(",c={keyword:"int float while private char catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignof constexpr decltype noexcept static_assert thread_local restrict _Bool complex _Complex _Imaginary atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and or not",built_in:"std string cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr",literal:"true false nullptr NULL"},n=[e,t.CLCM,t.CBCM,s,r];return{aliases:["c","cc","h","c++","h++","hpp"],k:c,i:"</",c:n.concat([i,{b:"\\b(deque|list|queue|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",e:">",k:c,c:["self",e]},{b:t.IR+"::",k:c},{v:[{b:/=/,e:/;/},{b:/\(/,e:/\)/},{bK:"new throw return else",e:/;/}],k:c,c:n.concat([{b:/\(/,e:/\)/,k:c,c:n.concat(["self"]),r:0}]),r:0},{cN:"function",b:"("+t.IR+"[\\*&\\s]+)+"+a,rB:!0,e:/[{;=]/,eE:!0,k:c,i:/[^\w\s\*&]/,c:[{b:a,rB:!0,c:[t.TM],r:0},{cN:"params",b:/\(/,e:/\)/,k:c,r:0,c:[t.CLCM,t.CBCM,r,s,e,{b:/\(/,e:/\)/,k:c,r:0,c:["self",t.CLCM,t.CBCM,r,s,e]}]},t.CLCM,t.CBCM,i]},{cN:"class",bK:"class struct",e:/[{;:]/,c:[{b:/</,e:/>/,c:["self"]},t.TM]}]),exports:{preprocessor:i,strings:r,k:c}}});hljs.registerLanguage("properties",function(r){var t="[ \\t\\f]*",e="[ \\t\\f]+",s="("+t+"[:=]"+t+"|"+e+")",n="([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",a="([^\\\\:= \\t\\f\\n]|\\\\.)+",c={e:s,r:0,starts:{cN:"string",e:/$/,r:0,c:[{b:"\\\\\\n"}]}};return{cI:!0,i:/\S/,c:[r.C("^\\s*[!#]","$"),{b:n+s,rB:!0,c:[{cN:"attr",b:n,endsParent:!0,r:0}],starts:c},{b:a+s,rB:!0,r:0,c:[{cN:"meta",b:a,endsParent:!0,r:0}],starts:c},{cN:"attr",r:0,b:a+t+"$"}]}});hljs.registerLanguage("diff",function(e){return{aliases:["patch"],c:[{cN:"meta",r:10,v:[{b:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{b:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{b:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{cN:"comment",v:[{b:/Index: /,e:/$/},{b:/={3,}/,e:/$/},{b:/^\-{3}/,e:/$/},{b:/^\*{3} /,e:/$/},{b:/^\+{3}/,e:/$/},{b:/\*{5}/,e:/\*{5}$/}]},{cN:"addition",b:"^\\+",e:"$"},{cN:"deletion",b:"^\\-",e:"$"},{cN:"addition",b:"^\\!",e:"$"}]}});hljs.registerLanguage("apache",function(e){var r={cN:"number",b:"[\\$%]\\d+"};return{aliases:["apacheconf"],cI:!0,c:[e.HCM,{cN:"section",b:"</?",e:">"},{cN:"attribute",b:/\w+/,r:0,k:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{e:/$/,r:0,k:{literal:"on off all"},c:[{cN:"meta",b:"\\s\\[",e:"\\]$"},{cN:"variable",b:"[\\$%]\\{",e:"\\}",c:["self",r]},r,e.QSM]}}],i:/\S/}});hljs.registerLanguage("rust",function(e){var t="([ui](8|16|32|64|128|size)|f(32|64))?",r="alignof as be box break const continue crate do else enum extern false fn for if impl in let loop match mod mut offsetof once priv proc pub pure ref return self Self sizeof static struct super trait true type typeof unsafe unsized use virtual while where yield move default",n="drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!";return{aliases:["rs"],k:{keyword:r,literal:"true false Some None Ok Err",built_in:n},l:e.IR+"!?",i:"</",c:[e.CLCM,e.C("/\\*","\\*/",{c:["self"]}),e.inherit(e.QSM,{b:/b?"/,i:null}),{cN:"string",v:[{b:/r(#*)"(.|\n)*?"\1(?!#)/},{b:/b?'\\?(x\w{2}|u\w{4}|U\w{8}|.)'/}]},{cN:"symbol",b:/'[a-zA-Z_][a-zA-Z0-9_]*/},{cN:"number",v:[{b:"\\b0b([01_]+)"+t},{b:"\\b0o([0-7_]+)"+t},{b:"\\b0x([A-Fa-f0-9_]+)"+t},{b:"\\b(\\d[\\d_]*(\\.[0-9_]+)?([eE][+-]?[0-9_]+)?)"+t}],r:0},{cN:"function",bK:"fn",e:"(\\(|<)",eE:!0,c:[e.UTM]},{cN:"meta",b:"#\\!?\\[",e:"\\]",c:[{cN:"meta-string",b:/"/,e:/"/}]},{cN:"class",bK:"type",e:";",c:[e.inherit(e.UTM,{endsParent:!0})],i:"\\S"},{cN:"class",bK:"trait enum struct union",e:"{",c:[e.inherit(e.UTM,{endsParent:!0})],i:"[\\w\\d]"},{b:e.IR+"::",k:{built_in:n}},{b:"->"}]}});hljs.registerLanguage("tex",function(c){var e={cN:"tag",b:/\\/,r:0,c:[{cN:"name",v:[{b:/[a-zA-Zа-яА-я]+[*]?/},{b:/[^a-zA-Zа-яА-я0-9]/}],starts:{eW:!0,r:0,c:[{cN:"string",v:[{b:/\[/,e:/\]/},{b:/\{/,e:/\}/}]},{b:/\s*=\s*/,eW:!0,r:0,c:[{cN:"number",b:/-?\d*\.?\d+(pt|pc|mm|cm|in|dd|cc|ex|em)?/}]}]}}]};return{c:[e,{cN:"formula",c:[e],r:0,v:[{b:/\$\$/,e:/\$\$/},{b:/\$/,e:/\$/}]},c.C("%","$",{r:0})]}});hljs.registerLanguage("r",function(e){var r="([a-zA-Z]|\\.[a-zA-Z.])[a-zA-Z0-9._]*";return{c:[e.HCM,{b:r,l:r,k:{keyword:"function if in break next repeat else for return switch while try tryCatch stop warning require library attach detach source setMethod setGeneric setGroupGeneric setClass ...",literal:"NULL NA TRUE FALSE T F Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10"},r:0},{cN:"number",b:"0[xX][0-9a-fA-F]+[Li]?\\b",r:0},{cN:"number",b:"\\d+(?:[eE][+\\-]?\\d*)?L\\b",r:0},{cN:"number",b:"\\d+\\.(?!\\d)(?:i\\b)?",r:0},{cN:"number",b:"\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d*)?i?\\b",r:0},{cN:"number",b:"\\.\\d+(?:[eE][+\\-]?\\d*)?i?\\b",r:0},{b:"`",e:"`",r:0},{cN:"string",c:[e.BE],v:[{b:'"',e:'"'},{b:"'",e:"'"}]}]}});hljs.registerLanguage("python",function(e){var r={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10 None True False",built_in:"Ellipsis NotImplemented"},b={cN:"meta",b:/^(>>>|\.\.\.) /},c={cN:"subst",b:/\{/,e:/\}/,k:r,i:/#/},a={cN:"string",c:[e.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[e.BE,b],r:10},{b:/(u|b)?r?"""/,e:/"""/,c:[e.BE,b],r:10},{b:/(fr|rf|f)'''/,e:/'''/,c:[e.BE,b,c]},{b:/(fr|rf|f)"""/,e:/"""/,c:[e.BE,b,c]},{b:/(u|r|ur)'/,e:/'/,r:10},{b:/(u|r|ur)"/,e:/"/,r:10},{b:/(b|br)'/,e:/'/},{b:/(b|br)"/,e:/"/},{b:/(fr|rf|f)'/,e:/'/,c:[e.BE,c]},{b:/(fr|rf|f)"/,e:/"/,c:[e.BE,c]},e.ASM,e.QSM]},s={cN:"number",r:0,v:[{b:e.BNR+"[lLjJ]?"},{b:"\\b(0o[0-7]+)[lLjJ]?"},{b:e.CNR+"[lLjJ]?"}]},i={cN:"params",b:/\(/,e:/\)/,c:["self",b,s,a]};return c.c=[a,s,b],{aliases:["py","gyp"],k:r,i:/(<\/|->|\?)|=>/,c:[b,s,a,e.HCM,{v:[{cN:"function",bK:"def"},{cN:"class",bK:"class"}],e:/:/,i:/[${=;\n,]/,c:[e.UTM,i,{b:/->/,eW:!0,k:"None"}]},{cN:"meta",b:/^[\t ]*@/,e:/$/},{b:/\b(print|exec)\(/}]}});
\ No newline at end of file
/**
* lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.1.6
* Copyright (C) 2018 Oliver Nightingale
* @license MIT
*/
;(function(){
/**
* A convenience function for configuring and constructing
* a new lunr Index.
*
* A lunr.Builder instance is created and the pipeline setup
* with a trimmer, stop word filter and stemmer.
*
* This builder object is yielded to the configuration function
* that is passed as a parameter, allowing the list of fields
* and other builder parameters to be customised.
*
* All documents _must_ be added within the passed config function.
*
* @example
* var idx = lunr(function () {
* this.field('title')
* this.field('body')
* this.ref('id')
*
* documents.forEach(function (doc) {
* this.add(doc)
* }, this)
* })
*
* @see {@link lunr.Builder}
* @see {@link lunr.Pipeline}
* @see {@link lunr.trimmer}
* @see {@link lunr.stopWordFilter}
* @see {@link lunr.stemmer}
* @namespace {function} lunr
*/
var lunr = function (config) {
var builder = new lunr.Builder
builder.pipeline.add(
lunr.trimmer,
lunr.stopWordFilter,
lunr.stemmer
)
builder.searchPipeline.add(
lunr.stemmer
)
config.call(builder, builder)
return builder.build()
}
lunr.version = "2.1.6"
/*!
* lunr.utils
* Copyright (C) 2018 Oliver Nightingale
*/
/**
* A namespace containing utils for the rest of the lunr library
*/
lunr.utils = {}
/**
* Print a warning message to the console.
*
* @param {String} message The message to be printed.
* @memberOf Utils
*/
lunr.utils.warn = (function (global) {
/* eslint-disable no-console */
return function (message) {
if (global.console && console.warn) {
console.warn(message)
}
}
/* eslint-enable no-console */
})(this)
/**
* Convert an object to a string.
*
* In the case of `null` and `undefined` the function returns
* the empty string, in all other cases the result of calling
* `toString` on the passed object is returned.
*
* @param {Any} obj The object to convert to a string.
* @return {String} string representation of the passed object.
* @memberOf Utils
*/
lunr.utils.asString = function (obj) {
if (obj === void 0 || obj === null) {
return ""
} else {
return obj.toString()
}
}
lunr.FieldRef = function (docRef, fieldName, stringValue) {
this.docRef = docRef
this.fieldName = fieldName
this._stringValue = stringValue
}
lunr.FieldRef.joiner = "/"
lunr.FieldRef.fromString = function (s) {
var n = s.indexOf(lunr.FieldRef.joiner)
if (n === -1) {
throw "malformed field ref string"
}
var fieldRef = s.slice(0, n),
docRef = s.slice(n + 1)
return new lunr.FieldRef (docRef, fieldRef, s)
}
lunr.FieldRef.prototype.toString = function () {
if (this._stringValue == undefined) {
this._stringValue = this.fieldName + lunr.FieldRef.joiner + this.docRef
}
return this._stringValue
}
/**
* A function to calculate the inverse document frequency for
* a posting. This is shared between the builder and the index
*
* @private
* @param {object} posting - The posting for a given term
* @param {number} documentCount - The total number of documents.
*/
lunr.idf = function (posting, documentCount) {
var documentsWithTerm = 0
for (var fieldName in posting) {
if (fieldName == '_index') continue // Ignore the term index, its not a field
documentsWithTerm += Object.keys(posting[fieldName]).length
}
var x = (documentCount - documentsWithTerm + 0.5) / (documentsWithTerm + 0.5)
return Math.log(1 + Math.abs(x))
}
/**
* A token wraps a string representation of a token
* as it is passed through the text processing pipeline.
*
* @constructor
* @param {string} [str=''] - The string token being wrapped.
* @param {object} [metadata={}] - Metadata associated with this token.
*/
lunr.Token = function (str, metadata) {
this.str = str || ""
this.metadata = metadata || {}
}
/**
* Returns the token string that is being wrapped by this object.
*
* @returns {string}
*/
lunr.Token.prototype.toString = function () {
return this.str
}
/**
* A token update function is used when updating or optionally
* when cloning a token.
*
* @callback lunr.Token~updateFunction
* @param {string} str - The string representation of the token.
* @param {Object} metadata - All metadata associated with this token.
*/
/**
* Applies the given function to the wrapped string token.
*
* @example
* token.update(function (str, metadata) {
* return str.toUpperCase()
* })
*
* @param {lunr.Token~updateFunction} fn - A function to apply to the token string.
* @returns {lunr.Token}
*/
lunr.Token.prototype.update = function (fn) {
this.str = fn(this.str, this.metadata)
return this
}
/**
* Creates a clone of this token. Optionally a function can be
* applied to the cloned token.
*
* @param {lunr.Token~updateFunction} [fn] - An optional function to apply to the cloned token.
* @returns {lunr.Token}
*/
lunr.Token.prototype.clone = function (fn) {
fn = fn || function (s) { return s }
return new lunr.Token (fn(this.str, this.metadata), this.metadata)
}
/*!
* lunr.tokenizer
* Copyright (C) 2018 Oliver Nightingale
*/
/**
* A function for splitting a string into tokens ready to be inserted into
* the search index. Uses `lunr.tokenizer.separator` to split strings, change
* the value of this property to change how strings are split into tokens.
*
* This tokenizer will convert its parameter to a string by calling `toString` and
* then will split this string on the character in `lunr.tokenizer.separator`.
* Arrays will have their elements converted to strings and wrapped in a lunr.Token.
*
* @static
* @param {?(string|object|object[])} obj - The object to convert into tokens
* @returns {lunr.Token[]}
*/
lunr.tokenizer = function (obj) {
if (obj == null || obj == undefined) {
return []
}
if (Array.isArray(obj)) {
return obj.map(function (t) {
return new lunr.Token(lunr.utils.asString(t).toLowerCase())
})
}
var str = obj.toString().trim().toLowerCase(),
len = str.length,
tokens = []
for (var sliceEnd = 0, sliceStart = 0; sliceEnd <= len; sliceEnd++) {
var char = str.charAt(sliceEnd),
sliceLength = sliceEnd - sliceStart
if ((char.match(lunr.tokenizer.separator) || sliceEnd == len)) {
if (sliceLength > 0) {
tokens.push(
new lunr.Token (str.slice(sliceStart, sliceEnd), {
position: [sliceStart, sliceLength],
index: tokens.length
})
)
}
sliceStart = sliceEnd + 1
}
}
return tokens
}
/**
* The separator used to split a string into tokens. Override this property to change the behaviour of
* `lunr.tokenizer` behaviour when tokenizing strings. By default this splits on whitespace and hyphens.
*
* @static
* @see lunr.tokenizer
*/
lunr.tokenizer.separator = /[\s\-]+/
/*!
* lunr.Pipeline
* Copyright (C) 2018 Oliver Nightingale
*/
/**
* lunr.Pipelines maintain an ordered list of functions to be applied to all
* tokens in documents entering the search index and queries being ran against
* the index.
*
* An instance of lunr.Index created with the lunr shortcut will contain a
* pipeline with a stop word filter and an English language stemmer. Extra
* functions can be added before or after either of these functions or these
* default functions can be removed.
*
* When run the pipeline will call each function in turn, passing a token, the
* index of that token in the original list of all tokens and finally a list of
* all the original tokens.
*
* The output of functions in the pipeline will be passed to the next function
* in the pipeline. To exclude a token from entering the index the function
* should return undefined, the rest of the pipeline will not be called with
* this token.
*
* For serialisation of pipelines to work, all functions used in an instance of
* a pipeline should be registered with lunr.Pipeline. Registered functions can
* then be loaded. If trying to load a serialised pipeline that uses functions
* that are not registered an error will be thrown.
*
* If not planning on serialising the pipeline then registering pipeline functions
* is not necessary.
*
* @constructor
*/
lunr.Pipeline = function () {
this._stack = []
}
lunr.Pipeline.registeredFunctions = Object.create(null)
/**
* A pipeline function maps lunr.Token to lunr.Token. A lunr.Token contains the token
* string as well as all known metadata. A pipeline function can mutate the token string
* or mutate (or add) metadata for a given token.
*
* A pipeline function can indicate that the passed token should be discarded by returning
* null. This token will not be passed to any downstream pipeline functions and will not be
* added to the index.
*
* Multiple tokens can be returned by returning an array of tokens. Each token will be passed
* to any downstream pipeline functions and all will returned tokens will be added to the index.
*
* Any number of pipeline functions may be chained together using a lunr.Pipeline.
*
* @interface lunr.PipelineFunction
* @param {lunr.Token} token - A token from the document being processed.
* @param {number} i - The index of this token in the complete list of tokens for this document/field.
* @param {lunr.Token[]} tokens - All tokens for this document/field.
* @returns {(?lunr.Token|lunr.Token[])}
*/
/**
* Register a function with the pipeline.
*
* Functions that are used in the pipeline should be registered if the pipeline
* needs to be serialised, or a serialised pipeline needs to be loaded.
*
* Registering a function does not add it to a pipeline, functions must still be
* added to instances of the pipeline for them to be used when running a pipeline.
*
* @param {lunr.PipelineFunction} fn - The function to check for.
* @param {String} label - The label to register this function with
*/
lunr.Pipeline.registerFunction = function (fn, label) {
if (label in this.registeredFunctions) {
lunr.utils.warn('Overwriting existing registered function: ' + label)
}
fn.label = label
lunr.Pipeline.registeredFunctions[fn.label] = fn
}
/**
* Warns if the function is not registered as a Pipeline function.
*
* @param {lunr.PipelineFunction} fn - The function to check for.
* @private
*/
lunr.Pipeline.warnIfFunctionNotRegistered = function (fn) {
var isRegistered = fn.label && (fn.label in this.registeredFunctions)
if (!isRegistered) {
lunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\n', fn)
}
}
/**
* Loads a previously serialised pipeline.
*
* All functions to be loaded must already be registered with lunr.Pipeline.
* If any function from the serialised data has not been registered then an
* error will be thrown.
*
* @param {Object} serialised - The serialised pipeline to load.
* @returns {lunr.Pipeline}
*/
lunr.Pipeline.load = function (serialised) {
var pipeline = new lunr.Pipeline
serialised.forEach(function (fnName) {
var fn = lunr.Pipeline.registeredFunctions[fnName]
if (fn) {
pipeline.add(fn)
} else {
throw new Error('Cannot load unregistered function: ' + fnName)
}
})
return pipeline
}
/**
* Adds new functions to the end of the pipeline.
*
* Logs a warning if the function has not been registered.
*
* @param {lunr.PipelineFunction[]} functions - Any number of functions to add to the pipeline.
*/
lunr.Pipeline.prototype.add = function () {
var fns = Array.prototype.slice.call(arguments)
fns.forEach(function (fn) {
lunr.Pipeline.warnIfFunctionNotRegistered(fn)
this._stack.push(fn)
}, this)
}
/**
* Adds a single function after a function that already exists in the
* pipeline.
*
* Logs a warning if the function has not been registered.
*
* @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline.
* @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline.
*/
lunr.Pipeline.prototype.after = function (existingFn, newFn) {
lunr.Pipeline.warnIfFunctionNotRegistered(newFn)
var pos = this._stack.indexOf(existingFn)
if (pos == -1) {
throw new Error('Cannot find existingFn')
}
pos = pos + 1
this._stack.splice(pos, 0, newFn)
}
/**
* Adds a single function before a function that already exists in the
* pipeline.
*
* Logs a warning if the function has not been registered.
*
* @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline.
* @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline.
*/
lunr.Pipeline.prototype.before = function (existingFn, newFn) {
lunr.Pipeline.warnIfFunctionNotRegistered(newFn)
var pos = this._stack.indexOf(existingFn)
if (pos == -1) {
throw new Error('Cannot find existingFn')
}
this._stack.splice(pos, 0, newFn)
}
/**
* Removes a function from the pipeline.
*
* @param {lunr.PipelineFunction} fn The function to remove from the pipeline.
*/
lunr.Pipeline.prototype.remove = function (fn) {
var pos = this._stack.indexOf(fn)
if (pos == -1) {
return
}
this._stack.splice(pos, 1)
}
/**
* Runs the current list of functions that make up the pipeline against the
* passed tokens.
*
* @param {Array} tokens The tokens to run through the pipeline.
* @returns {Array}
*/
lunr.Pipeline.prototype.run = function (tokens) {
var stackLength = this._stack.length
for (var i = 0; i < stackLength; i++) {
var fn = this._stack[i]
var memo = []
for (var j = 0; j < tokens.length; j++) {
var result = fn(tokens[j], j, tokens)
if (result === void 0 || result === '') continue
if (result instanceof Array) {
for (var k = 0; k < result.length; k++) {
memo.push(result[k])
}
} else {
memo.push(result)
}
}
tokens = memo
}
return tokens
}
/**
* Convenience method for passing a string through a pipeline and getting
* strings out. This method takes care of wrapping the passed string in a
* token and mapping the resulting tokens back to strings.
*
* @param {string} str - The string to pass through the pipeline.
* @returns {string[]}
*/
lunr.Pipeline.prototype.runString = function (str) {
var token = new lunr.Token (str)
return this.run([token]).map(function (t) {
return t.toString()
})
}
/**
* Resets the pipeline by removing any existing processors.
*
*/
lunr.Pipeline.prototype.reset = function () {
this._stack = []
}
/**
* Returns a representation of the pipeline ready for serialisation.
*
* Logs a warning if the function has not been registered.
*
* @returns {Array}
*/
lunr.Pipeline.prototype.toJSON = function () {
return this._stack.map(function (fn) {
lunr.Pipeline.warnIfFunctionNotRegistered(fn)
return fn.label
})
}
/*!
* lunr.Vector
* Copyright (C) 2018 Oliver Nightingale
*/
/**
* A vector is used to construct the vector space of documents and queries. These
* vectors support operations to determine the similarity between two documents or
* a document and a query.
*
* Normally no parameters are required for initializing a vector, but in the case of
* loading a previously dumped vector the raw elements can be provided to the constructor.
*
* For performance reasons vectors are implemented with a flat array, where an elements
* index is immediately followed by its value. E.g. [index, value, index, value]. This
* allows the underlying array to be as sparse as possible and still offer decent
* performance when being used for vector calculations.
*
* @constructor
* @param {Number[]} [elements] - The flat list of element index and element value pairs.
*/
lunr.Vector = function (elements) {
this._magnitude = 0
this.elements = elements || []
}
/**
* Calculates the position within the vector to insert a given index.
*
* This is used internally by insert and upsert. If there are duplicate indexes then
* the position is returned as if the value for that index were to be updated, but it
* is the callers responsibility to check whether there is a duplicate at that index
*
* @param {Number} insertIdx - The index at which the element should be inserted.
* @returns {Number}
*/
lunr.Vector.prototype.positionForIndex = function (index) {
// For an empty vector the tuple can be inserted at the beginning
if (this.elements.length == 0) {
return 0
}
var start = 0,
end = this.elements.length / 2,
sliceLength = end - start,
pivotPoint = Math.floor(sliceLength / 2),
pivotIndex = this.elements[pivotPoint * 2]
while (sliceLength > 1) {
if (pivotIndex < index) {
start = pivotPoint
}
if (pivotIndex > index) {
end = pivotPoint
}
if (pivotIndex == index) {
break
}
sliceLength = end - start
pivotPoint = start + Math.floor(sliceLength / 2)
pivotIndex = this.elements[pivotPoint * 2]
}
if (pivotIndex == index) {
return pivotPoint * 2
}
if (pivotIndex > index) {
return pivotPoint * 2
}
if (pivotIndex < index) {
return (pivotPoint + 1) * 2
}
}
/**
* Inserts an element at an index within the vector.
*
* Does not allow duplicates, will throw an error if there is already an entry
* for this index.
*
* @param {Number} insertIdx - The index at which the element should be inserted.
* @param {Number} val - The value to be inserted into the vector.
*/
lunr.Vector.prototype.insert = function (insertIdx, val) {
this.upsert(insertIdx, val, function () {
throw "duplicate index"
})
}
/**
* Inserts or updates an existing index within the vector.
*
* @param {Number} insertIdx - The index at which the element should be inserted.
* @param {Number} val - The value to be inserted into the vector.
* @param {function} fn - A function that is called for updates, the existing value and the
* requested value are passed as arguments
*/
lunr.Vector.prototype.upsert = function (insertIdx, val, fn) {
this._magnitude = 0
var position = this.positionForIndex(insertIdx)
if (this.elements[position] == insertIdx) {
this.elements[position + 1] = fn(this.elements[position + 1], val)
} else {
this.elements.splice(position, 0, insertIdx, val)
}
}
/**
* Calculates the magnitude of this vector.
*
* @returns {Number}
*/
lunr.Vector.prototype.magnitude = function () {
if (this._magnitude) return this._magnitude
var sumOfSquares = 0,
elementsLength = this.elements.length
for (var i = 1; i < elementsLength; i += 2) {
var val = this.elements[i]
sumOfSquares += val * val
}
return this._magnitude = Math.sqrt(sumOfSquares)
}
/**
* Calculates the dot product of this vector and another vector.
*
* @param {lunr.Vector} otherVector - The vector to compute the dot product with.
* @returns {Number}
*/
lunr.Vector.prototype.dot = function (otherVector) {
var dotProduct = 0,
a = this.elements, b = otherVector.elements,
aLen = a.length, bLen = b.length,
aVal = 0, bVal = 0,
i = 0, j = 0
while (i < aLen && j < bLen) {
aVal = a[i], bVal = b[j]
if (aVal < bVal) {
i += 2
} else if (aVal > bVal) {
j += 2
} else if (aVal == bVal) {
dotProduct += a[i + 1] * b[j + 1]
i += 2
j += 2
}
}
return dotProduct
}
/**
* Calculates the cosine similarity between this vector and another
* vector.
*
* @param {lunr.Vector} otherVector - The other vector to calculate the
* similarity with.
* @returns {Number}
*/
lunr.Vector.prototype.similarity = function (otherVector) {
return this.dot(otherVector) / (this.magnitude() * otherVector.magnitude())
}
/**
* Converts the vector to an array of the elements within the vector.
*
* @returns {Number[]}
*/
lunr.Vector.prototype.toArray = function () {
var output = new Array (this.elements.length / 2)
for (var i = 1, j = 0; i < this.elements.length; i += 2, j++) {
output[j] = this.elements[i]
}
return output
}
/**
* A JSON serializable representation of the vector.
*
* @returns {Number[]}
*/
lunr.Vector.prototype.toJSON = function () {
return this.elements
}
/* eslint-disable */
/*!
* lunr.stemmer
* Copyright (C) 2018 Oliver Nightingale
* Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt
*/
/**
* lunr.stemmer is an english language stemmer, this is a JavaScript
* implementation of the PorterStemmer taken from http://tartarus.org/~martin
*
* @static
* @implements {lunr.PipelineFunction}
* @param {lunr.Token} token - The string to stem
* @returns {lunr.Token}
* @see {@link lunr.Pipeline}
*/
lunr.stemmer = (function(){
var step2list = {
"ational" : "ate",
"tional" : "tion",
"enci" : "ence",
"anci" : "ance",
"izer" : "ize",
"bli" : "ble",
"alli" : "al",
"entli" : "ent",
"eli" : "e",
"ousli" : "ous",
"ization" : "ize",
"ation" : "ate",
"ator" : "ate",
"alism" : "al",
"iveness" : "ive",
"fulness" : "ful",
"ousness" : "ous",
"aliti" : "al",
"iviti" : "ive",
"biliti" : "ble",
"logi" : "log"
},
step3list = {
"icate" : "ic",
"ative" : "",
"alize" : "al",
"iciti" : "ic",
"ical" : "ic",
"ful" : "",
"ness" : ""
},
c = "[^aeiou]", // consonant
v = "[aeiouy]", // vowel
C = c + "[^aeiouy]*", // consonant sequence
V = v + "[aeiou]*", // vowel sequence
mgr0 = "^(" + C + ")?" + V + C, // [C]VC... is m>0
meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$", // [C]VC[V] is m=1
mgr1 = "^(" + C + ")?" + V + C + V + C, // [C]VCVC... is m>1
s_v = "^(" + C + ")?" + v; // vowel in stem
var re_mgr0 = new RegExp(mgr0);
var re_mgr1 = new RegExp(mgr1);
var re_meq1 = new RegExp(meq1);
var re_s_v = new RegExp(s_v);
var re_1a = /^(.+?)(ss|i)es$/;
var re2_1a = /^(.+?)([^s])s$/;
var re_1b = /^(.+?)eed$/;
var re2_1b = /^(.+?)(ed|ing)$/;
var re_1b_2 = /.$/;
var re2_1b_2 = /(at|bl|iz)$/;
var re3_1b_2 = new RegExp("([^aeiouylsz])\\1$");
var re4_1b_2 = new RegExp("^" + C + v + "[^aeiouwxy]$");
var re_1c = /^(.+?[^aeiou])y$/;
var re_2 = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
var re_3 = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
var re_4 = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
var re2_4 = /^(.+?)(s|t)(ion)$/;
var re_5 = /^(.+?)e$/;
var re_5_1 = /ll$/;
var re3_5 = new RegExp("^" + C + v + "[^aeiouwxy]$");
var porterStemmer = function porterStemmer(w) {
var stem,
suffix,
firstch,
re,
re2,
re3,
re4;
if (w.length < 3) { return w; }
firstch = w.substr(0,1);
if (firstch == "y") {
w = firstch.toUpperCase() + w.substr(1);
}
// Step 1a
re = re_1a
re2 = re2_1a;
if (re.test(w)) { w = w.replace(re,"$1$2"); }
else if (re2.test(w)) { w = w.replace(re2,"$1$2"); }
// Step 1b
re = re_1b;
re2 = re2_1b;
if (re.test(w)) {
var fp = re.exec(w);
re = re_mgr0;
if (re.test(fp[1])) {
re = re_1b_2;
w = w.replace(re,"");
}
} else if (re2.test(w)) {
var fp = re2.exec(w);
stem = fp[1];
re2 = re_s_v;
if (re2.test(stem)) {
w = stem;
re2 = re2_1b_2;
re3 = re3_1b_2;
re4 = re4_1b_2;
if (re2.test(w)) { w = w + "e"; }
else if (re3.test(w)) { re = re_1b_2; w = w.replace(re,""); }
else if (re4.test(w)) { w = w + "e"; }
}
}
// Step 1c - replace suffix y or Y by i if preceded by a non-vowel which is not the first letter of the word (so cry -> cri, by -> by, say -> say)
re = re_1c;
if (re.test(w)) {
var fp = re.exec(w);
stem = fp[1];
w = stem + "i";
}
// Step 2
re = re_2;
if (re.test(w)) {
var fp = re.exec(w);
stem = fp[1];
suffix = fp[2];
re = re_mgr0;
if (re.test(stem)) {
w = stem + step2list[suffix];
}
}
// Step 3
re = re_3;
if (re.test(w)) {
var fp = re.exec(w);
stem = fp[1];
suffix = fp[2];
re = re_mgr0;
if (re.test(stem)) {
w = stem + step3list[suffix];
}
}
// Step 4
re = re_4;
re2 = re2_4;
if (re.test(w)) {
var fp = re.exec(w);
stem = fp[1];
re = re_mgr1;
if (re.test(stem)) {
w = stem;
}
} else if (re2.test(w)) {
var fp = re2.exec(w);
stem = fp[1] + fp[2];
re2 = re_mgr1;
if (re2.test(stem)) {
w = stem;
}
}
// Step 5
re = re_5;
if (re.test(w)) {
var fp = re.exec(w);
stem = fp[1];
re = re_mgr1;
re2 = re_meq1;
re3 = re3_5;
if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) {
w = stem;
}
}
re = re_5_1;
re2 = re_mgr1;
if (re.test(w) && re2.test(w)) {
re = re_1b_2;
w = w.replace(re,"");
}
// and turn initial Y back to y
if (firstch == "y") {
w = firstch.toLowerCase() + w.substr(1);
}
return w;
};
return function (token) {
return token.update(porterStemmer);
}
})();
lunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer')
/*!
* lunr.stopWordFilter
* Copyright (C) 2018 Oliver Nightingale
*/
/**
* lunr.generateStopWordFilter builds a stopWordFilter function from the provided
* list of stop words.
*
* The built in lunr.stopWordFilter is built using this generator and can be used
* to generate custom stopWordFilters for applications or non English languages.
*
* @param {Array} token The token to pass through the filter
* @returns {lunr.PipelineFunction}
* @see lunr.Pipeline
* @see lunr.stopWordFilter
*/
lunr.generateStopWordFilter = function (stopWords) {
var words = stopWords.reduce(function (memo, stopWord) {
memo[stopWord] = stopWord
return memo
}, {})
return function (token) {
if (token && words[token.toString()] !== token.toString()) return token
}
}
/**
* lunr.stopWordFilter is an English language stop word list filter, any words
* contained in the list will not be passed through the filter.
*
* This is intended to be used in the Pipeline. If the token does not pass the
* filter then undefined will be returned.
*
* @implements {lunr.PipelineFunction}
* @params {lunr.Token} token - A token to check for being a stop word.
* @returns {lunr.Token}
* @see {@link lunr.Pipeline}
*/
lunr.stopWordFilter = lunr.generateStopWordFilter([
'a',
'able',
'about',
'across',
'after',
'all',
'almost',
'also',
'am',
'among',
'an',
'and',
'any',
'are',
'as',
'at',
'be',
'because',
'been',
'but',
'by',
'can',
'cannot',
'could',
'dear',
'did',
'do',
'does',
'either',
'else',
'ever',
'every',
'for',
'from',
'get',
'got',
'had',
'has',
'have',
'he',
'her',
'hers',
'him',
'his',
'how',
'however',
'i',
'if',
'in',
'into',
'is',
'it',
'its',
'just',
'least',
'let',
'like',
'likely',
'may',
'me',
'might',
'most',
'must',
'my',
'neither',
'no',
'nor',
'not',
'of',
'off',
'often',
'on',
'only',
'or',
'other',
'our',
'own',
'rather',
'said',
'say',
'says',
'she',
'should',
'since',
'so',
'some',
'than',
'that',
'the',
'their',
'them',
'then',
'there',
'these',
'they',
'this',
'tis',
'to',
'too',
'twas',
'us',
'wants',
'was',
'we',
'were',
'what',
'when',
'where',
'which',
'while',
'who',
'whom',
'why',
'will',
'with',
'would',
'yet',
'you',
'your'
])
lunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter')
/*!
* lunr.trimmer
* Copyright (C) 2018 Oliver Nightingale
*/
/**
* lunr.trimmer is a pipeline function for trimming non word
* characters from the beginning and end of tokens before they
* enter the index.
*
* This implementation may not work correctly for non latin
* characters and should either be removed or adapted for use
* with languages with non-latin characters.
*
* @static
* @implements {lunr.PipelineFunction}
* @param {lunr.Token} token The token to pass through the filter
* @returns {lunr.Token}
* @see lunr.Pipeline
*/
lunr.trimmer = function (token) {
return token.update(function (s) {
return s.replace(/^\W+/, '').replace(/\W+$/, '')
})
}
lunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer')
/*!
* lunr.TokenSet
* Copyright (C) 2018 Oliver Nightingale
*/
/**
* A token set is used to store the unique list of all tokens
* within an index. Token sets are also used to represent an
* incoming query to the index, this query token set and index
* token set are then intersected to find which tokens to look
* up in the inverted index.
*
* A token set can hold multiple tokens, as in the case of the
* index token set, or it can hold a single token as in the
* case of a simple query token set.
*
* Additionally token sets are used to perform wildcard matching.
* Leading, contained and trailing wildcards are supported, and
* from this edit distance matching can also be provided.
*
* Token sets are implemented as a minimal finite state automata,
* where both common prefixes and suffixes are shared between tokens.
* This helps to reduce the space used for storing the token set.
*
* @constructor
*/
lunr.TokenSet = function () {
this.final = false
this.edges = {}
this.id = lunr.TokenSet._nextId
lunr.TokenSet._nextId += 1
}
/**
* Keeps track of the next, auto increment, identifier to assign
* to a new tokenSet.
*
* TokenSets require a unique identifier to be correctly minimised.
*
* @private
*/
lunr.TokenSet._nextId = 1
/**
* Creates a TokenSet instance from the given sorted array of words.
*
* @param {String[]} arr - A sorted array of strings to create the set from.
* @returns {lunr.TokenSet}
* @throws Will throw an error if the input array is not sorted.
*/
lunr.TokenSet.fromArray = function (arr) {
var builder = new lunr.TokenSet.Builder
for (var i = 0, len = arr.length; i < len; i++) {
builder.insert(arr[i])
}
builder.finish()
return builder.root
}
/**
* Creates a token set from a query clause.
*
* @private
* @param {Object} clause - A single clause from lunr.Query.
* @param {string} clause.term - The query clause term.
* @param {number} [clause.editDistance] - The optional edit distance for the term.
* @returns {lunr.TokenSet}
*/
lunr.TokenSet.fromClause = function (clause) {
if ('editDistance' in clause) {
return lunr.TokenSet.fromFuzzyString(clause.term, clause.editDistance)
} else {
return lunr.TokenSet.fromString(clause.term)
}
}
/**
* Creates a token set representing a single string with a specified
* edit distance.
*
* Insertions, deletions, substitutions and transpositions are each
* treated as an edit distance of 1.
*
* Increasing the allowed edit distance will have a dramatic impact
* on the performance of both creating and intersecting these TokenSets.
* It is advised to keep the edit distance less than 3.
*
* @param {string} str - The string to create the token set from.
* @param {number} editDistance - The allowed edit distance to match.
* @returns {lunr.Vector}
*/
lunr.TokenSet.fromFuzzyString = function (str, editDistance) {
var root = new lunr.TokenSet
var stack = [{
node: root,
editsRemaining: editDistance,
str: str
}]
while (stack.length) {
var frame = stack.pop()
// no edit
if (frame.str.length > 0) {
var char = frame.str.charAt(0),
noEditNode
if (char in frame.node.edges) {
noEditNode = frame.node.edges[char]
} else {
noEditNode = new lunr.TokenSet
frame.node.edges[char] = noEditNode
}
if (frame.str.length == 1) {
noEditNode.final = true
} else {
stack.push({
node: noEditNode,
editsRemaining: frame.editsRemaining,
str: frame.str.slice(1)
})
}
}
// deletion
// can only do a deletion if we have enough edits remaining
// and if there are characters left to delete in the string
if (frame.editsRemaining > 0 && frame.str.length > 1) {
var char = frame.str.charAt(1),
deletionNode
if (char in frame.node.edges) {
deletionNode = frame.node.edges[char]
} else {
deletionNode = new lunr.TokenSet
frame.node.edges[char] = deletionNode
}
if (frame.str.length <= 2) {
deletionNode.final = true
} else {
stack.push({
node: deletionNode,
editsRemaining: frame.editsRemaining - 1,
str: frame.str.slice(2)
})
}
}
// deletion
// just removing the last character from the str
if (frame.editsRemaining > 0 && frame.str.length == 1) {
frame.node.final = true
}
// substitution
// can only do a substitution if we have enough edits remaining
// and if there are characters left to substitute
if (frame.editsRemaining > 0 && frame.str.length >= 1) {
if ("*" in frame.node.edges) {
var substitutionNode = frame.node.edges["*"]
} else {
var substitutionNode = new lunr.TokenSet
frame.node.edges["*"] = substitutionNode
}
if (frame.str.length == 1) {
substitutionNode.final = true
} else {
stack.push({
node: substitutionNode,
editsRemaining: frame.editsRemaining - 1,
str: frame.str.slice(1)
})
}
}
// insertion
// can only do insertion if there are edits remaining
if (frame.editsRemaining > 0) {
if ("*" in frame.node.edges) {
var insertionNode = frame.node.edges["*"]
} else {
var insertionNode = new lunr.TokenSet
frame.node.edges["*"] = insertionNode
}
if (frame.str.length == 0) {
insertionNode.final = true
} else {
stack.push({
node: insertionNode,
editsRemaining: frame.editsRemaining - 1,
str: frame.str
})
}
}
// transposition
// can only do a transposition if there are edits remaining
// and there are enough characters to transpose
if (frame.editsRemaining > 0 && frame.str.length > 1) {
var charA = frame.str.charAt(0),
charB = frame.str.charAt(1),
transposeNode
if (charB in frame.node.edges) {
transposeNode = frame.node.edges[charB]
} else {
transposeNode = new lunr.TokenSet
frame.node.edges[charB] = transposeNode
}
if (frame.str.length == 1) {
transposeNode.final = true
} else {
stack.push({
node: transposeNode,
editsRemaining: frame.editsRemaining - 1,
str: charA + frame.str.slice(2)
})
}
}
}
return root
}
/**
* Creates a TokenSet from a string.
*
* The string may contain one or more wildcard characters (*)
* that will allow wildcard matching when intersecting with
* another TokenSet.
*
* @param {string} str - The string to create a TokenSet from.
* @returns {lunr.TokenSet}
*/
lunr.TokenSet.fromString = function (str) {
var node = new lunr.TokenSet,
root = node,
wildcardFound = false
/*
* Iterates through all characters within the passed string
* appending a node for each character.
*
* As soon as a wildcard character is found then a self
* referencing edge is introduced to continually match
* any number of any characters.
*/
for (var i = 0, len = str.length; i < len; i++) {
var char = str[i],
final = (i == len - 1)
if (char == "*") {
wildcardFound = true
node.edges[char] = node
node.final = final
} else {
var next = new lunr.TokenSet
next.final = final
node.edges[char] = next
node = next
// TODO: is this needed anymore?
if (wildcardFound) {
node.edges["*"] = root
}
}
}
return root
}
/**
* Converts this TokenSet into an array of strings
* contained within the TokenSet.
*
* @returns {string[]}
*/
lunr.TokenSet.prototype.toArray = function () {
var words = []
var stack = [{
prefix: "",
node: this
}]
while (stack.length) {
var frame = stack.pop(),
edges = Object.keys(frame.node.edges),
len = edges.length
if (frame.node.final) {
words.push(frame.prefix)
}
for (var i = 0; i < len; i++) {
var edge = edges[i]
stack.push({
prefix: frame.prefix.concat(edge),
node: frame.node.edges[edge]
})
}
}
return words
}
/**
* Generates a string representation of a TokenSet.
*
* This is intended to allow TokenSets to be used as keys
* in objects, largely to aid the construction and minimisation
* of a TokenSet. As such it is not designed to be a human
* friendly representation of the TokenSet.
*
* @returns {string}
*/
lunr.TokenSet.prototype.toString = function () {
// NOTE: Using Object.keys here as this.edges is very likely
// to enter 'hash-mode' with many keys being added
//
// avoiding a for-in loop here as it leads to the function
// being de-optimised (at least in V8). From some simple
// benchmarks the performance is comparable, but allowing
// V8 to optimize may mean easy performance wins in the future.
if (this._str) {
return this._str
}
var str = this.final ? '1' : '0',
labels = Object.keys(this.edges).sort(),
len = labels.length
for (var i = 0; i < len; i++) {
var label = labels[i],
node = this.edges[label]
str = str + label + node.id
}
return str
}
/**
* Returns a new TokenSet that is the intersection of
* this TokenSet and the passed TokenSet.
*
* This intersection will take into account any wildcards
* contained within the TokenSet.
*
* @param {lunr.TokenSet} b - An other TokenSet to intersect with.
* @returns {lunr.TokenSet}
*/
lunr.TokenSet.prototype.intersect = function (b) {
var output = new lunr.TokenSet,
frame = undefined
var stack = [{
qNode: b,
output: output,
node: this
}]
while (stack.length) {
frame = stack.pop()
// NOTE: As with the #toString method, we are using
// Object.keys and a for loop instead of a for-in loop
// as both of these objects enter 'hash' mode, causing
// the function to be de-optimised in V8
var qEdges = Object.keys(frame.qNode.edges),
qLen = qEdges.length,
nEdges = Object.keys(frame.node.edges),
nLen = nEdges.length
for (var q = 0; q < qLen; q++) {
var qEdge = qEdges[q]
for (var n = 0; n < nLen; n++) {
var nEdge = nEdges[n]
if (nEdge == qEdge || qEdge == '*') {
var node = frame.node.edges[nEdge],
qNode = frame.qNode.edges[qEdge],
final = node.final && qNode.final,
next = undefined
if (nEdge in frame.output.edges) {
// an edge already exists for this character
// no need to create a new node, just set the finality
// bit unless this node is already final
next = frame.output.edges[nEdge]
next.final = next.final || final
} else {
// no edge exists yet, must create one
// set the finality bit and insert it
// into the output
next = new lunr.TokenSet
next.final = final
frame.output.edges[nEdge] = next
}
stack.push({
qNode: qNode,
output: next,
node: node
})
}
}
}
}
return output
}
lunr.TokenSet.Builder = function () {
this.previousWord = ""
this.root = new lunr.TokenSet
this.uncheckedNodes = []
this.minimizedNodes = {}
}
lunr.TokenSet.Builder.prototype.insert = function (word) {
var node,
commonPrefix = 0
if (word < this.previousWord) {
throw new Error ("Out of order word insertion")
}
for (var i = 0; i < word.length && i < this.previousWord.length; i++) {
if (word[i] != this.previousWord[i]) break
commonPrefix++
}
this.minimize(commonPrefix)
if (this.uncheckedNodes.length == 0) {
node = this.root
} else {
node = this.uncheckedNodes[this.uncheckedNodes.length - 1].child
}
for (var i = commonPrefix; i < word.length; i++) {
var nextNode = new lunr.TokenSet,
char = word[i]
node.edges[char] = nextNode
this.uncheckedNodes.push({
parent: node,
char: char,
child: nextNode
})
node = nextNode
}
node.final = true
this.previousWord = word
}
lunr.TokenSet.Builder.prototype.finish = function () {
this.minimize(0)
}
lunr.TokenSet.Builder.prototype.minimize = function (downTo) {
for (var i = this.uncheckedNodes.length - 1; i >= downTo; i--) {
var node = this.uncheckedNodes[i],
childKey = node.child.toString()
if (childKey in this.minimizedNodes) {
node.parent.edges[node.char] = this.minimizedNodes[childKey]
} else {
// Cache the key for this node since
// we know it can't change anymore
node.child._str = childKey
this.minimizedNodes[childKey] = node.child
}
this.uncheckedNodes.pop()
}
}
/*!
* lunr.Index
* Copyright (C) 2018 Oliver Nightingale
*/
/**
* An index contains the built index of all documents and provides a query interface
* to the index.
*
* Usually instances of lunr.Index will not be created using this constructor, instead
* lunr.Builder should be used to construct new indexes, or lunr.Index.load should be
* used to load previously built and serialized indexes.
*
* @constructor
* @param {Object} attrs - The attributes of the built search index.
* @param {Object} attrs.invertedIndex - An index of term/field to document reference.
* @param {Object<string, lunr.Vector>} attrs.documentVectors - Document vectors keyed by document reference.
* @param {lunr.TokenSet} attrs.tokenSet - An set of all corpus tokens.
* @param {string[]} attrs.fields - The names of indexed document fields.
* @param {lunr.Pipeline} attrs.pipeline - The pipeline to use for search terms.
*/
lunr.Index = function (attrs) {
this.invertedIndex = attrs.invertedIndex
this.fieldVectors = attrs.fieldVectors
this.tokenSet = attrs.tokenSet
this.fields = attrs.fields
this.pipeline = attrs.pipeline
}
/**
* A result contains details of a document matching a search query.
* @typedef {Object} lunr.Index~Result
* @property {string} ref - The reference of the document this result represents.
* @property {number} score - A number between 0 and 1 representing how similar this document is to the query.
* @property {lunr.MatchData} matchData - Contains metadata about this match including which term(s) caused the match.
*/
/**
* Although lunr provides the ability to create queries using lunr.Query, it also provides a simple
* query language which itself is parsed into an instance of lunr.Query.
*
* For programmatically building queries it is advised to directly use lunr.Query, the query language
* is best used for human entered text rather than program generated text.
*
* At its simplest queries can just be a single term, e.g. `hello`, multiple terms are also supported
* and will be combined with OR, e.g `hello world` will match documents that contain either 'hello'
* or 'world', though those that contain both will rank higher in the results.
*
* Wildcards can be included in terms to match one or more unspecified characters, these wildcards can
* be inserted anywhere within the term, and more than one wildcard can exist in a single term. Adding
* wildcards will increase the number of documents that will be found but can also have a negative
* impact on query performance, especially with wildcards at the beginning of a term.
*
* Terms can be restricted to specific fields, e.g. `title:hello`, only documents with the term
* hello in the title field will match this query. Using a field not present in the index will lead
* to an error being thrown.
*
* Modifiers can also be added to terms, lunr supports edit distance and boost modifiers on terms. A term
* boost will make documents matching that term score higher, e.g. `foo^5`. Edit distance is also supported
* to provide fuzzy matching, e.g. 'hello~2' will match documents with hello with an edit distance of 2.
* Avoid large values for edit distance to improve query performance.
*
* To escape special characters the backslash character '\' can be used, this allows searches to include
* characters that would normally be considered modifiers, e.g. `foo\~2` will search for a term "foo~2" instead
* of attempting to apply a boost of 2 to the search term "foo".
*
* @typedef {string} lunr.Index~QueryString
* @example <caption>Simple single term query</caption>
* hello
* @example <caption>Multiple term query</caption>
* hello world
* @example <caption>term scoped to a field</caption>
* title:hello
* @example <caption>term with a boost of 10</caption>
* hello^10
* @example <caption>term with an edit distance of 2</caption>
* hello~2
*/
/**
* Performs a search against the index using lunr query syntax.
*
* Results will be returned sorted by their score, the most relevant results
* will be returned first.
*
* For more programmatic querying use lunr.Index#query.
*
* @param {lunr.Index~QueryString} queryString - A string containing a lunr query.
* @throws {lunr.QueryParseError} If the passed query string cannot be parsed.
* @returns {lunr.Index~Result[]}
*/
lunr.Index.prototype.search = function (queryString) {
return this.query(function (query) {
var parser = new lunr.QueryParser(queryString, query)
parser.parse()
})
}
/**
* A query builder callback provides a query object to be used to express
* the query to perform on the index.
*
* @callback lunr.Index~queryBuilder
* @param {lunr.Query} query - The query object to build up.
* @this lunr.Query
*/
/**
* Performs a query against the index using the yielded lunr.Query object.
*
* If performing programmatic queries against the index, this method is preferred
* over lunr.Index#search so as to avoid the additional query parsing overhead.
*
* A query object is yielded to the supplied function which should be used to
* express the query to be run against the index.
*
* Note that although this function takes a callback parameter it is _not_ an
* asynchronous operation, the callback is just yielded a query object to be
* customized.
*
* @param {lunr.Index~queryBuilder} fn - A function that is used to build the query.
* @returns {lunr.Index~Result[]}
*/
lunr.Index.prototype.query = function (fn) {
// for each query clause
// * process terms
// * expand terms from token set
// * find matching documents and metadata
// * get document vectors
// * score documents
var query = new lunr.Query(this.fields),
matchingFields = Object.create(null),
queryVectors = Object.create(null),
termFieldCache = Object.create(null)
fn.call(query, query)
for (var i = 0; i < query.clauses.length; i++) {
/*
* Unless the pipeline has been disabled for this term, which is
* the case for terms with wildcards, we need to pass the clause
* term through the search pipeline. A pipeline returns an array
* of processed terms. Pipeline functions may expand the passed
* term, which means we may end up performing multiple index lookups
* for a single query term.
*/
var clause = query.clauses[i],
terms = null
if (clause.usePipeline) {
terms = this.pipeline.runString(clause.term)
} else {
terms = [clause.term]
}
for (var m = 0; m < terms.length; m++) {
var term = terms[m]
/*
* Each term returned from the pipeline needs to use the same query
* clause object, e.g. the same boost and or edit distance. The
* simplest way to do this is to re-use the clause object but mutate
* its term property.
*/
clause.term = term
/*
* From the term in the clause we create a token set which will then
* be used to intersect the indexes token set to get a list of terms
* to lookup in the inverted index
*/
var termTokenSet = lunr.TokenSet.fromClause(clause),
expandedTerms = this.tokenSet.intersect(termTokenSet).toArray()
for (var j = 0; j < expandedTerms.length; j++) {
/*
* For each term get the posting and termIndex, this is required for
* building the query vector.
*/
var expandedTerm = expandedTerms[j],
posting = this.invertedIndex[expandedTerm],
termIndex = posting._index
for (var k = 0; k < clause.fields.length; k++) {
/*
* For each field that this query term is scoped by (by default
* all fields are in scope) we need to get all the document refs
* that have this term in that field.
*
* The posting is the entry in the invertedIndex for the matching
* term from above.
*/
var field = clause.fields[k],
fieldPosting = posting[field],
matchingDocumentRefs = Object.keys(fieldPosting),
termField = expandedTerm + "/" + field
/*
* To support field level boosts a query vector is created per
* field. This vector is populated using the termIndex found for
* the term and a unit value with the appropriate boost applied.
*
* If the query vector for this field does not exist yet it needs
* to be created.
*/
if (queryVectors[field] === undefined) {
queryVectors[field] = new lunr.Vector
}
/*
* Using upsert because there could already be an entry in the vector
* for the term we are working with. In that case we just add the scores
* together.
*/
queryVectors[field].upsert(termIndex, 1 * clause.boost, function (a, b) { return a + b })
/**
* If we've already seen this term, field combo then we've already collected
* the matching documents and metadata, no need to go through all that again
*/
if (termFieldCache[termField]) {
continue
}
for (var l = 0; l < matchingDocumentRefs.length; l++) {
/*
* All metadata for this term/field/document triple
* are then extracted and collected into an instance
* of lunr.MatchData ready to be returned in the query
* results
*/
var matchingDocumentRef = matchingDocumentRefs[l],
matchingFieldRef = new lunr.FieldRef (matchingDocumentRef, field),
metadata = fieldPosting[matchingDocumentRef],
fieldMatch
if ((fieldMatch = matchingFields[matchingFieldRef]) === undefined) {
matchingFields[matchingFieldRef] = new lunr.MatchData (expandedTerm, field, metadata)
} else {
fieldMatch.add(expandedTerm, field, metadata)
}
}
termFieldCache[termField] = true
}
}
}
}
var matchingFieldRefs = Object.keys(matchingFields),
results = [],
matches = Object.create(null)
for (var i = 0; i < matchingFieldRefs.length; i++) {
/*
* Currently we have document fields that match the query, but we
* need to return documents. The matchData and scores are combined
* from multiple fields belonging to the same document.
*
* Scores are calculated by field, using the query vectors created
* above, and combined into a final document score using addition.
*/
var fieldRef = lunr.FieldRef.fromString(matchingFieldRefs[i]),
docRef = fieldRef.docRef,
fieldVector = this.fieldVectors[fieldRef],
score = queryVectors[fieldRef.fieldName].similarity(fieldVector),
docMatch
if ((docMatch = matches[docRef]) !== undefined) {
docMatch.score += score
docMatch.matchData.combine(matchingFields[fieldRef])
} else {
var match = {
ref: docRef,
score: score,
matchData: matchingFields[fieldRef]
}
matches[docRef] = match
results.push(match)
}
}
/*
* Sort the results objects by score, highest first.
*/
return results.sort(function (a, b) {
return b.score - a.score
})
}
/**
* Prepares the index for JSON serialization.
*
* The schema for this JSON blob will be described in a
* separate JSON schema file.
*
* @returns {Object}
*/
lunr.Index.prototype.toJSON = function () {
var invertedIndex = Object.keys(this.invertedIndex)
.sort()
.map(function (term) {
return [term, this.invertedIndex[term]]
}, this)
var fieldVectors = Object.keys(this.fieldVectors)
.map(function (ref) {
return [ref, this.fieldVectors[ref].toJSON()]
}, this)
return {
version: lunr.version,
fields: this.fields,
fieldVectors: fieldVectors,
invertedIndex: invertedIndex,
pipeline: this.pipeline.toJSON()
}
}
/**
* Loads a previously serialized lunr.Index
*
* @param {Object} serializedIndex - A previously serialized lunr.Index
* @returns {lunr.Index}
*/
lunr.Index.load = function (serializedIndex) {
var attrs = {},
fieldVectors = {},
serializedVectors = serializedIndex.fieldVectors,
invertedIndex = {},
serializedInvertedIndex = serializedIndex.invertedIndex,
tokenSetBuilder = new lunr.TokenSet.Builder,
pipeline = lunr.Pipeline.load(serializedIndex.pipeline)
if (serializedIndex.version != lunr.version) {
lunr.utils.warn("Version mismatch when loading serialised index. Current version of lunr '" + lunr.version + "' does not match serialized index '" + serializedIndex.version + "'")
}
for (var i = 0; i < serializedVectors.length; i++) {
var tuple = serializedVectors[i],
ref = tuple[0],
elements = tuple[1]
fieldVectors[ref] = new lunr.Vector(elements)
}
for (var i = 0; i < serializedInvertedIndex.length; i++) {
var tuple = serializedInvertedIndex[i],
term = tuple[0],
posting = tuple[1]
tokenSetBuilder.insert(term)
invertedIndex[term] = posting
}
tokenSetBuilder.finish()
attrs.fields = serializedIndex.fields
attrs.fieldVectors = fieldVectors
attrs.invertedIndex = invertedIndex
attrs.tokenSet = tokenSetBuilder.root
attrs.pipeline = pipeline
return new lunr.Index(attrs)
}
/*!
* lunr.Builder
* Copyright (C) 2018 Oliver Nightingale
*/
/**
* lunr.Builder performs indexing on a set of documents and
* returns instances of lunr.Index ready for querying.
*
* All configuration of the index is done via the builder, the
* fields to index, the document reference, the text processing
* pipeline and document scoring parameters are all set on the
* builder before indexing.
*
* @constructor
* @property {string} _ref - Internal reference to the document reference field.
* @property {string[]} _fields - Internal reference to the document fields to index.
* @property {object} invertedIndex - The inverted index maps terms to document fields.
* @property {object} documentTermFrequencies - Keeps track of document term frequencies.
* @property {object} documentLengths - Keeps track of the length of documents added to the index.
* @property {lunr.tokenizer} tokenizer - Function for splitting strings into tokens for indexing.
* @property {lunr.Pipeline} pipeline - The pipeline performs text processing on tokens before indexing.
* @property {lunr.Pipeline} searchPipeline - A pipeline for processing search terms before querying the index.
* @property {number} documentCount - Keeps track of the total number of documents indexed.
* @property {number} _b - A parameter to control field length normalization, setting this to 0 disabled normalization, 1 fully normalizes field lengths, the default value is 0.75.
* @property {number} _k1 - A parameter to control how quickly an increase in term frequency results in term frequency saturation, the default value is 1.2.
* @property {number} termIndex - A counter incremented for each unique term, used to identify a terms position in the vector space.
* @property {array} metadataWhitelist - A list of metadata keys that have been whitelisted for entry in the index.
*/
lunr.Builder = function () {
this._ref = "id"
this._fields = []
this.invertedIndex = Object.create(null)
this.fieldTermFrequencies = {}
this.fieldLengths = {}
this.tokenizer = lunr.tokenizer
this.pipeline = new lunr.Pipeline
this.searchPipeline = new lunr.Pipeline
this.documentCount = 0
this._b = 0.75
this._k1 = 1.2
this.termIndex = 0
this.metadataWhitelist = []
}
/**
* Sets the document field used as the document reference. Every document must have this field.
* The type of this field in the document should be a string, if it is not a string it will be
* coerced into a string by calling toString.
*
* The default ref is 'id'.
*
* The ref should _not_ be changed during indexing, it should be set before any documents are
* added to the index. Changing it during indexing can lead to inconsistent results.
*
* @param {string} ref - The name of the reference field in the document.
*/
lunr.Builder.prototype.ref = function (ref) {
this._ref = ref
}
/**
* Adds a field to the list of document fields that will be indexed. Every document being
* indexed should have this field. Null values for this field in indexed documents will
* not cause errors but will limit the chance of that document being retrieved by searches.
*
* All fields should be added before adding documents to the index. Adding fields after
* a document has been indexed will have no effect on already indexed documents.
*
* @param {string} field - The name of a field to index in all documents.
*/
lunr.Builder.prototype.field = function (field) {
this._fields.push(field)
}
/**
* A parameter to tune the amount of field length normalisation that is applied when
* calculating relevance scores. A value of 0 will completely disable any normalisation
* and a value of 1 will fully normalise field lengths. The default is 0.75. Values of b
* will be clamped to the range 0 - 1.
*
* @param {number} number - The value to set for this tuning parameter.
*/
lunr.Builder.prototype.b = function (number) {
if (number < 0) {
this._b = 0
} else if (number > 1) {
this._b = 1
} else {
this._b = number
}
}
/**
* A parameter that controls the speed at which a rise in term frequency results in term
* frequency saturation. The default value is 1.2. Setting this to a higher value will give
* slower saturation levels, a lower value will result in quicker saturation.
*
* @param {number} number - The value to set for this tuning parameter.
*/
lunr.Builder.prototype.k1 = function (number) {
this._k1 = number
}
/**
* Adds a document to the index.
*
* Before adding fields to the index the index should have been fully setup, with the document
* ref and all fields to index already having been specified.
*
* The document must have a field name as specified by the ref (by default this is 'id') and
* it should have all fields defined for indexing, though null or undefined values will not
* cause errors.
*
* @param {object} doc - The document to add to the index.
*/
lunr.Builder.prototype.add = function (doc) {
var docRef = doc[this._ref]
this.documentCount += 1
for (var i = 0; i < this._fields.length; i++) {
var fieldName = this._fields[i],
field = doc[fieldName],
tokens = this.tokenizer(field),
terms = this.pipeline.run(tokens),
fieldRef = new lunr.FieldRef (docRef, fieldName),
fieldTerms = Object.create(null)
this.fieldTermFrequencies[fieldRef] = fieldTerms
this.fieldLengths[fieldRef] = 0
// store the length of this field for this document
this.fieldLengths[fieldRef] += terms.length
// calculate term frequencies for this field
for (var j = 0; j < terms.length; j++) {
var term = terms[j]
if (fieldTerms[term] == undefined) {
fieldTerms[term] = 0
}
fieldTerms[term] += 1
// add to inverted index
// create an initial posting if one doesn't exist
if (this.invertedIndex[term] == undefined) {
var posting = Object.create(null)
posting["_index"] = this.termIndex
this.termIndex += 1
for (var k = 0; k < this._fields.length; k++) {
posting[this._fields[k]] = Object.create(null)
}
this.invertedIndex[term] = posting
}
// add an entry for this term/fieldName/docRef to the invertedIndex
if (this.invertedIndex[term][fieldName][docRef] == undefined) {
this.invertedIndex[term][fieldName][docRef] = Object.create(null)
}
// store all whitelisted metadata about this token in the
// inverted index
for (var l = 0; l < this.metadataWhitelist.length; l++) {
var metadataKey = this.metadataWhitelist[l],
metadata = term.metadata[metadataKey]
if (this.invertedIndex[term][fieldName][docRef][metadataKey] == undefined) {
this.invertedIndex[term][fieldName][docRef][metadataKey] = []
}
this.invertedIndex[term][fieldName][docRef][metadataKey].push(metadata)
}
}
}
}
/**
* Calculates the average document length for this index
*
* @private
*/
lunr.Builder.prototype.calculateAverageFieldLengths = function () {
var fieldRefs = Object.keys(this.fieldLengths),
numberOfFields = fieldRefs.length,
accumulator = {},
documentsWithField = {}
for (var i = 0; i < numberOfFields; i++) {
var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),
field = fieldRef.fieldName
documentsWithField[field] || (documentsWithField[field] = 0)
documentsWithField[field] += 1
accumulator[field] || (accumulator[field] = 0)
accumulator[field] += this.fieldLengths[fieldRef]
}
for (var i = 0; i < this._fields.length; i++) {
var field = this._fields[i]
accumulator[field] = accumulator[field] / documentsWithField[field]
}
this.averageFieldLength = accumulator
}
/**
* Builds a vector space model of every document using lunr.Vector
*
* @private
*/
lunr.Builder.prototype.createFieldVectors = function () {
var fieldVectors = {},
fieldRefs = Object.keys(this.fieldTermFrequencies),
fieldRefsLength = fieldRefs.length,
termIdfCache = Object.create(null)
for (var i = 0; i < fieldRefsLength; i++) {
var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),
field = fieldRef.fieldName,
fieldLength = this.fieldLengths[fieldRef],
fieldVector = new lunr.Vector,
termFrequencies = this.fieldTermFrequencies[fieldRef],
terms = Object.keys(termFrequencies),
termsLength = terms.length
for (var j = 0; j < termsLength; j++) {
var term = terms[j],
tf = termFrequencies[term],
termIndex = this.invertedIndex[term]._index,
idf, score, scoreWithPrecision
if (termIdfCache[term] === undefined) {
idf = lunr.idf(this.invertedIndex[term], this.documentCount)
termIdfCache[term] = idf
} else {
idf = termIdfCache[term]
}
score = idf * ((this._k1 + 1) * tf) / (this._k1 * (1 - this._b + this._b * (fieldLength / this.averageFieldLength[field])) + tf)
scoreWithPrecision = Math.round(score * 1000) / 1000
// Converts 1.23456789 to 1.234.
// Reducing the precision so that the vectors take up less
// space when serialised. Doing it now so that they behave
// the same before and after serialisation. Also, this is
// the fastest approach to reducing a number's precision in
// JavaScript.
fieldVector.insert(termIndex, scoreWithPrecision)
}
fieldVectors[fieldRef] = fieldVector
}
this.fieldVectors = fieldVectors
}
/**
* Creates a token set of all tokens in the index using lunr.TokenSet
*
* @private
*/
lunr.Builder.prototype.createTokenSet = function () {
this.tokenSet = lunr.TokenSet.fromArray(
Object.keys(this.invertedIndex).sort()
)
}
/**
* Builds the index, creating an instance of lunr.Index.
*
* This completes the indexing process and should only be called
* once all documents have been added to the index.
*
* @returns {lunr.Index}
*/
lunr.Builder.prototype.build = function () {
this.calculateAverageFieldLengths()
this.createFieldVectors()
this.createTokenSet()
return new lunr.Index({
invertedIndex: this.invertedIndex,
fieldVectors: this.fieldVectors,
tokenSet: this.tokenSet,
fields: this._fields,
pipeline: this.searchPipeline
})
}
/**
* Applies a plugin to the index builder.
*
* A plugin is a function that is called with the index builder as its context.
* Plugins can be used to customise or extend the behaviour of the index
* in some way. A plugin is just a function, that encapsulated the custom
* behaviour that should be applied when building the index.
*
* The plugin function will be called with the index builder as its argument, additional
* arguments can also be passed when calling use. The function will be called
* with the index builder as its context.
*
* @param {Function} plugin The plugin to apply.
*/
lunr.Builder.prototype.use = function (fn) {
var args = Array.prototype.slice.call(arguments, 1)
args.unshift(this)
fn.apply(this, args)
}
/**
* Contains and collects metadata about a matching document.
* A single instance of lunr.MatchData is returned as part of every
* lunr.Index~Result.
*
* @constructor
* @param {string} term - The term this match data is associated with
* @param {string} field - The field in which the term was found
* @param {object} metadata - The metadata recorded about this term in this field
* @property {object} metadata - A cloned collection of metadata associated with this document.
* @see {@link lunr.Index~Result}
*/
lunr.MatchData = function (term, field, metadata) {
var clonedMetadata = Object.create(null),
metadataKeys = Object.keys(metadata)
// Cloning the metadata to prevent the original
// being mutated during match data combination.
// Metadata is kept in an array within the inverted
// index so cloning the data can be done with
// Array#slice
for (var i = 0; i < metadataKeys.length; i++) {
var key = metadataKeys[i]
clonedMetadata[key] = metadata[key].slice()
}
this.metadata = Object.create(null)
this.metadata[term] = Object.create(null)
this.metadata[term][field] = clonedMetadata
}
/**
* An instance of lunr.MatchData will be created for every term that matches a
* document. However only one instance is required in a lunr.Index~Result. This
* method combines metadata from another instance of lunr.MatchData with this
* objects metadata.
*
* @param {lunr.MatchData} otherMatchData - Another instance of match data to merge with this one.
* @see {@link lunr.Index~Result}
*/
lunr.MatchData.prototype.combine = function (otherMatchData) {
var terms = Object.keys(otherMatchData.metadata)
for (var i = 0; i < terms.length; i++) {
var term = terms[i],
fields = Object.keys(otherMatchData.metadata[term])
if (this.metadata[term] == undefined) {
this.metadata[term] = Object.create(null)
}
for (var j = 0; j < fields.length; j++) {
var field = fields[j],
keys = Object.keys(otherMatchData.metadata[term][field])
if (this.metadata[term][field] == undefined) {
this.metadata[term][field] = Object.create(null)
}
for (var k = 0; k < keys.length; k++) {
var key = keys[k]
if (this.metadata[term][field][key] == undefined) {
this.metadata[term][field][key] = otherMatchData.metadata[term][field][key]
} else {
this.metadata[term][field][key] = this.metadata[term][field][key].concat(otherMatchData.metadata[term][field][key])
}
}
}
}
}
/**
* Add metadata for a term/field pair to this instance of match data.
*
* @param {string} term - The term this match data is associated with
* @param {string} field - The field in which the term was found
* @param {object} metadata - The metadata recorded about this term in this field
*/
lunr.MatchData.prototype.add = function (term, field, metadata) {
if (!(term in this.metadata)) {
this.metadata[term] = Object.create(null)
this.metadata[term][field] = metadata
return
}
if (!(field in this.metadata[term])) {
this.metadata[term][field] = metadata
return
}
var metadataKeys = Object.keys(metadata)
for (var i = 0; i < metadataKeys.length; i++) {
var key = metadataKeys[i]
if (key in this.metadata[term][field]) {
this.metadata[term][field][key] = this.metadata[term][field][key].concat(metadata[key])
} else {
this.metadata[term][field][key] = metadata[key]
}
}
}
/**
* A lunr.Query provides a programmatic way of defining queries to be performed
* against a {@link lunr.Index}.
*
* Prefer constructing a lunr.Query using the {@link lunr.Index#query} method
* so the query object is pre-initialized with the right index fields.
*
* @constructor
* @property {lunr.Query~Clause[]} clauses - An array of query clauses.
* @property {string[]} allFields - An array of all available fields in a lunr.Index.
*/
lunr.Query = function (allFields) {
this.clauses = []
this.allFields = allFields
}
/**
* Constants for indicating what kind of automatic wildcard insertion will be used when constructing a query clause.
*
* This allows wildcards to be added to the beginning and end of a term without having to manually do any string
* concatenation.
*
* The wildcard constants can be bitwise combined to select both leading and trailing wildcards.
*
* @constant
* @default
* @property {number} wildcard.NONE - The term will have no wildcards inserted, this is the default behaviour
* @property {number} wildcard.LEADING - Prepend the term with a wildcard, unless a leading wildcard already exists
* @property {number} wildcard.TRAILING - Append a wildcard to the term, unless a trailing wildcard already exists
* @see lunr.Query~Clause
* @see lunr.Query#clause
* @see lunr.Query#term
* @example <caption>query term with trailing wildcard</caption>
* query.term('foo', { wildcard: lunr.Query.wildcard.TRAILING })
* @example <caption>query term with leading and trailing wildcard</caption>
* query.term('foo', {
* wildcard: lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING
* })
*/
lunr.Query.wildcard = new String ("*")
lunr.Query.wildcard.NONE = 0
lunr.Query.wildcard.LEADING = 1
lunr.Query.wildcard.TRAILING = 2
/**
* A single clause in a {@link lunr.Query} contains a term and details on how to
* match that term against a {@link lunr.Index}.
*
* @typedef {Object} lunr.Query~Clause
* @property {string[]} fields - The fields in an index this clause should be matched against.
* @property {number} [boost=1] - Any boost that should be applied when matching this clause.
* @property {number} [editDistance] - Whether the term should have fuzzy matching applied, and how fuzzy the match should be.
* @property {boolean} [usePipeline] - Whether the term should be passed through the search pipeline.
* @property {number} [wildcard=0] - Whether the term should have wildcards appended or prepended.
*/
/**
* Adds a {@link lunr.Query~Clause} to this query.
*
* Unless the clause contains the fields to be matched all fields will be matched. In addition
* a default boost of 1 is applied to the clause.
*
* @param {lunr.Query~Clause} clause - The clause to add to this query.
* @see lunr.Query~Clause
* @returns {lunr.Query}
*/
lunr.Query.prototype.clause = function (clause) {
if (!('fields' in clause)) {
clause.fields = this.allFields
}
if (!('boost' in clause)) {
clause.boost = 1
}
if (!('usePipeline' in clause)) {
clause.usePipeline = true
}
if (!('wildcard' in clause)) {
clause.wildcard = lunr.Query.wildcard.NONE
}
if ((clause.wildcard & lunr.Query.wildcard.LEADING) && (clause.term.charAt(0) != lunr.Query.wildcard)) {
clause.term = "*" + clause.term
}
if ((clause.wildcard & lunr.Query.wildcard.TRAILING) && (clause.term.slice(-1) != lunr.Query.wildcard)) {
clause.term = "" + clause.term + "*"
}
this.clauses.push(clause)
return this
}
/**
* Adds a term to the current query, under the covers this will create a {@link lunr.Query~Clause}
* to the list of clauses that make up this query.
*
* @param {string} term - The term to add to the query.
* @param {Object} [options] - Any additional properties to add to the query clause.
* @returns {lunr.Query}
* @see lunr.Query#clause
* @see lunr.Query~Clause
* @example <caption>adding a single term to a query</caption>
* query.term("foo")
* @example <caption>adding a single term to a query and specifying search fields, term boost and automatic trailing wildcard</caption>
* query.term("foo", {
* fields: ["title"],
* boost: 10,
* wildcard: lunr.Query.wildcard.TRAILING
* })
*/
lunr.Query.prototype.term = function (term, options) {
var clause = options || {}
clause.term = term
this.clause(clause)
return this
}
lunr.QueryParseError = function (message, start, end) {
this.name = "QueryParseError"
this.message = message
this.start = start
this.end = end
}
lunr.QueryParseError.prototype = new Error
lunr.QueryLexer = function (str) {
this.lexemes = []
this.str = str
this.length = str.length
this.pos = 0
this.start = 0
this.escapeCharPositions = []
}
lunr.QueryLexer.prototype.run = function () {
var state = lunr.QueryLexer.lexText
while (state) {
state = state(this)
}
}
lunr.QueryLexer.prototype.sliceString = function () {
var subSlices = [],
sliceStart = this.start,
sliceEnd = this.pos
for (var i = 0; i < this.escapeCharPositions.length; i++) {
sliceEnd = this.escapeCharPositions[i]
subSlices.push(this.str.slice(sliceStart, sliceEnd))
sliceStart = sliceEnd + 1
}
subSlices.push(this.str.slice(sliceStart, this.pos))
this.escapeCharPositions.length = 0
return subSlices.join('')
}
lunr.QueryLexer.prototype.emit = function (type) {
this.lexemes.push({
type: type,
str: this.sliceString(),
start: this.start,
end: this.pos
})
this.start = this.pos
}
lunr.QueryLexer.prototype.escapeCharacter = function () {
this.escapeCharPositions.push(this.pos - 1)
this.pos += 1
}
lunr.QueryLexer.prototype.next = function () {
if (this.pos >= this.length) {
return lunr.QueryLexer.EOS
}
var char = this.str.charAt(this.pos)
this.pos += 1
return char
}
lunr.QueryLexer.prototype.width = function () {
return this.pos - this.start
}
lunr.QueryLexer.prototype.ignore = function () {
if (this.start == this.pos) {
this.pos += 1
}
this.start = this.pos
}
lunr.QueryLexer.prototype.backup = function () {
this.pos -= 1
}
lunr.QueryLexer.prototype.acceptDigitRun = function () {
var char, charCode
do {
char = this.next()
charCode = char.charCodeAt(0)
} while (charCode > 47 && charCode < 58)
if (char != lunr.QueryLexer.EOS) {
this.backup()
}
}
lunr.QueryLexer.prototype.more = function () {
return this.pos < this.length
}
lunr.QueryLexer.EOS = 'EOS'
lunr.QueryLexer.FIELD = 'FIELD'
lunr.QueryLexer.TERM = 'TERM'
lunr.QueryLexer.EDIT_DISTANCE = 'EDIT_DISTANCE'
lunr.QueryLexer.BOOST = 'BOOST'
lunr.QueryLexer.lexField = function (lexer) {
lexer.backup()
lexer.emit(lunr.QueryLexer.FIELD)
lexer.ignore()
return lunr.QueryLexer.lexText
}
lunr.QueryLexer.lexTerm = function (lexer) {
if (lexer.width() > 1) {
lexer.backup()
lexer.emit(lunr.QueryLexer.TERM)
}
lexer.ignore()
if (lexer.more()) {
return lunr.QueryLexer.lexText
}
}
lunr.QueryLexer.lexEditDistance = function (lexer) {
lexer.ignore()
lexer.acceptDigitRun()
lexer.emit(lunr.QueryLexer.EDIT_DISTANCE)
return lunr.QueryLexer.lexText
}
lunr.QueryLexer.lexBoost = function (lexer) {
lexer.ignore()
lexer.acceptDigitRun()
lexer.emit(lunr.QueryLexer.BOOST)
return lunr.QueryLexer.lexText
}
lunr.QueryLexer.lexEOS = function (lexer) {
if (lexer.width() > 0) {
lexer.emit(lunr.QueryLexer.TERM)
}
}
// This matches the separator used when tokenising fields
// within a document. These should match otherwise it is
// not possible to search for some tokens within a document.
//
// It is possible for the user to change the separator on the
// tokenizer so it _might_ clash with any other of the special
// characters already used within the search string, e.g. :.
//
// This means that it is possible to change the separator in
// such a way that makes some words unsearchable using a search
// string.
lunr.QueryLexer.termSeparator = lunr.tokenizer.separator
lunr.QueryLexer.lexText = function (lexer) {
while (true) {
var char = lexer.next()
if (char == lunr.QueryLexer.EOS) {
return lunr.QueryLexer.lexEOS
}
// Escape character is '\'
if (char.charCodeAt(0) == 92) {
lexer.escapeCharacter()
continue
}
if (char == ":") {
return lunr.QueryLexer.lexField
}
if (char == "~") {
lexer.backup()
if (lexer.width() > 0) {
lexer.emit(lunr.QueryLexer.TERM)
}
return lunr.QueryLexer.lexEditDistance
}
if (char == "^") {
lexer.backup()
if (lexer.width() > 0) {
lexer.emit(lunr.QueryLexer.TERM)
}
return lunr.QueryLexer.lexBoost
}
if (char.match(lunr.QueryLexer.termSeparator)) {
return lunr.QueryLexer.lexTerm
}
}
}
lunr.QueryParser = function (str, query) {
this.lexer = new lunr.QueryLexer (str)
this.query = query
this.currentClause = {}
this.lexemeIdx = 0
}
lunr.QueryParser.prototype.parse = function () {
this.lexer.run()
this.lexemes = this.lexer.lexemes
var state = lunr.QueryParser.parseFieldOrTerm
while (state) {
state = state(this)
}
return this.query
}
lunr.QueryParser.prototype.peekLexeme = function () {
return this.lexemes[this.lexemeIdx]
}
lunr.QueryParser.prototype.consumeLexeme = function () {
var lexeme = this.peekLexeme()
this.lexemeIdx += 1
return lexeme
}
lunr.QueryParser.prototype.nextClause = function () {
var completedClause = this.currentClause
this.query.clause(completedClause)
this.currentClause = {}
}
lunr.QueryParser.parseFieldOrTerm = function (parser) {
var lexeme = parser.peekLexeme()
if (lexeme == undefined) {
return
}
switch (lexeme.type) {
case lunr.QueryLexer.FIELD:
return lunr.QueryParser.parseField
case lunr.QueryLexer.TERM:
return lunr.QueryParser.parseTerm
default:
var errorMessage = "expected either a field or a term, found " + lexeme.type
if (lexeme.str.length >= 1) {
errorMessage += " with value '" + lexeme.str + "'"
}
throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
}
}
lunr.QueryParser.parseField = function (parser) {
var lexeme = parser.consumeLexeme()
if (lexeme == undefined) {
return
}
if (parser.query.allFields.indexOf(lexeme.str) == -1) {
var possibleFields = parser.query.allFields.map(function (f) { return "'" + f + "'" }).join(', '),
errorMessage = "unrecognised field '" + lexeme.str + "', possible fields: " + possibleFields
throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
}
parser.currentClause.fields = [lexeme.str]
var nextLexeme = parser.peekLexeme()
if (nextLexeme == undefined) {
var errorMessage = "expecting term, found nothing"
throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
}
switch (nextLexeme.type) {
case lunr.QueryLexer.TERM:
return lunr.QueryParser.parseTerm
default:
var errorMessage = "expecting term, found '" + nextLexeme.type + "'"
throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)
}
}
lunr.QueryParser.parseTerm = function (parser) {
var lexeme = parser.consumeLexeme()
if (lexeme == undefined) {
return
}
parser.currentClause.term = lexeme.str.toLowerCase()
if (lexeme.str.indexOf("*") != -1) {
parser.currentClause.usePipeline = false
}
var nextLexeme = parser.peekLexeme()
if (nextLexeme == undefined) {
parser.nextClause()
return
}
switch (nextLexeme.type) {
case lunr.QueryLexer.TERM:
parser.nextClause()
return lunr.QueryParser.parseTerm
case lunr.QueryLexer.FIELD:
parser.nextClause()
return lunr.QueryParser.parseField
case lunr.QueryLexer.EDIT_DISTANCE:
return lunr.QueryParser.parseEditDistance
case lunr.QueryLexer.BOOST:
return lunr.QueryParser.parseBoost
default:
var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'"
throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)
}
}
lunr.QueryParser.parseEditDistance = function (parser) {
var lexeme = parser.consumeLexeme()
if (lexeme == undefined) {
return
}
var editDistance = parseInt(lexeme.str, 10)
if (isNaN(editDistance)) {
var errorMessage = "edit distance must be numeric"
throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
}
parser.currentClause.editDistance = editDistance
var nextLexeme = parser.peekLexeme()
if (nextLexeme == undefined) {
parser.nextClause()
return
}
switch (nextLexeme.type) {
case lunr.QueryLexer.TERM:
parser.nextClause()
return lunr.QueryParser.parseTerm
case lunr.QueryLexer.FIELD:
parser.nextClause()
return lunr.QueryParser.parseField
case lunr.QueryLexer.EDIT_DISTANCE:
return lunr.QueryParser.parseEditDistance
case lunr.QueryLexer.BOOST:
return lunr.QueryParser.parseBoost
default:
var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'"
throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)
}
}
lunr.QueryParser.parseBoost = function (parser) {
var lexeme = parser.consumeLexeme()
if (lexeme == undefined) {
return
}
var boost = parseInt(lexeme.str, 10)
if (isNaN(boost)) {
var errorMessage = "boost must be numeric"
throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
}
parser.currentClause.boost = boost
var nextLexeme = parser.peekLexeme()
if (nextLexeme == undefined) {
parser.nextClause()
return
}
switch (nextLexeme.type) {
case lunr.QueryLexer.TERM:
parser.nextClause()
return lunr.QueryParser.parseTerm
case lunr.QueryLexer.FIELD:
parser.nextClause()
return lunr.QueryParser.parseField
case lunr.QueryLexer.EDIT_DISTANCE:
return lunr.QueryParser.parseEditDistance
case lunr.QueryLexer.BOOST:
return lunr.QueryParser.parseBoost
default:
var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'"
throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)
}
}
/**
* export the module via AMD, CommonJS or as a browser global
* Export code from https://github.com/umdjs/umd/blob/master/returnExports.js
*/
;(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(factory)
} else if (typeof exports === 'object') {
/**
* Node. Does not work with strict CommonJS, but
* only CommonJS-like enviroments that support module.exports,
* like Node.
*/
module.exports = factory()
} else {
// Browser globals (root is window)
root.lunr = factory()
}
}(this, function () {
/**
* Just return a value to define the module export.
* This example returns an object, but the module
* can return a function as the exported value.
*/
return lunr
}))
})();
function getSearchTermFromLocation() {
var sPageURL = window.location.search.substring(1);
var sURLVariables = sPageURL.split('&');
for (var i = 0; i < sURLVariables.length; i++) {
var sParameterName = sURLVariables[i].split('=');
if (sParameterName[0] == 'q') {
return decodeURIComponent(sParameterName[1].replace(/\+/g, '%20'));
}
}
}
function joinUrl (base, path) {
if (path.substring(0, 1) === "/") {
// path starts with `/`. Thus it is absolute.
return path;
}
if (base.substring(base.length-1) === "/") {
// base ends with `/`
return base + path;
}
return base + "/" + path;
}
function formatResult (location, title, summary) {
return '<article><h3><a href="' + joinUrl(base_url, location) + '">'+ title + '</a></h3><p>' + summary +'</p></article>';
}
function displayResults (results) {
var search_results = document.getElementById("mkdocs-search-results");
while (search_results.firstChild) {
search_results.removeChild(search_results.firstChild);
}
if (results.length > 0){
for (var i=0; i < results.length; i++){
var result = results[i];
var html = formatResult(result.location, result.title, result.summary);
search_results.insertAdjacentHTML('beforeend', html);
}
} else {
search_results.insertAdjacentHTML('beforeend', "<p>No results found</p>");
}
}
function doSearch () {
var query = document.getElementById('mkdocs-search-query').value;
if (query.length > 2) {
if (!window.Worker) {
displayResults(search(query));
} else {
searchWorker.postMessage({query: query});
}
} else {
// Clear results for short queries
displayResults([]);
}
}
function initSearch () {
var search_input = document.getElementById('mkdocs-search-query');
if (search_input) {
search_input.addEventListener("keyup", doSearch);
}
var term = getSearchTermFromLocation();
if (term) {
search_input.value = term;
doSearch();
}
}
function onWorkerMessage (e) {
if (e.data.allowSearch) {
initSearch();
} else if (e.data.results) {
var results = e.data.results;
displayResults(results);
}
}
if (!window.Worker) {
console.log('Web Worker API not supported');
// load index in main thread
$.getScript(joinUrl(base_url, "search/worker.js")).done(function () {
console.log('Loaded worker');
init();
window.postMessage = function (msg) {
onWorkerMessage({data: msg});
};
}).fail(function (jqxhr, settings, exception) {
console.error('Could not load worker.js');
});
} else {
// Wrap search in a web worker
var searchWorker = new Worker(joinUrl(base_url, "search/worker.js"));
searchWorker.postMessage({init: true});
searchWorker.onmessage = onWorkerMessage;
}
This source diff could not be displayed because it is too large. You can view the blob instead.
var base_path = 'function' === typeof importScripts ? '.' : '/search/';
var allowSearch = false;
var index;
var documents = {};
var lang = ['en'];
var data;
function getScript(script, callback) {
console.log('Loading script: ' + script);
$.getScript(base_path + script).done(function () {
callback();
}).fail(function (jqxhr, settings, exception) {
console.log('Error: ' + exception);
});
}
function getScriptsInOrder(scripts, callback) {
if (scripts.length === 0) {
callback();
return;
}
getScript(scripts[0], function() {
getScriptsInOrder(scripts.slice(1), callback);
});
}
function loadScripts(urls, callback) {
if( 'function' === typeof importScripts ) {
importScripts.apply(null, urls);
callback();
} else {
getScriptsInOrder(urls, callback);
}
}
function onJSONLoaded () {
data = JSON.parse(this.responseText);
var scriptsToLoad = ['lunr.js'];
if (data.config && data.config.lang && data.config.lang.length) {
lang = data.config.lang;
}
if (lang.length > 1 || lang[0] !== "en") {
scriptsToLoad.push('lunr.stemmer.support.js');
if (lang.length > 1) {
scriptsToLoad.push('lunr.multi.js');
}
for (var i=0; i < lang.length; i++) {
if (lang[i] != 'en') {
scriptsToLoad.push(['lunr', lang[i], 'js'].join('.'));
}
}
}
loadScripts(scriptsToLoad, onScriptsLoaded);
}
function onScriptsLoaded () {
console.log('All search scripts loaded, building Lunr index...');
if (data.config && data.config.separator && data.config.separator.length) {
lunr.tokenizer.separator = new RegExp(data.config.separator);
}
if (data.index) {
index = lunr.Index.load(data.index);
data.docs.forEach(function (doc) {
documents[doc.location] = doc;
});
console.log('Lunr pre-built index loaded, search ready');
} else {
index = lunr(function () {
if (lang.length === 1 && lang[0] !== "en" && lunr[lang[0]]) {
this.use(lunr[lang[0]]);
} else if (lang.length > 1) {
this.use(lunr.multiLanguage.apply(null, lang)); // spread operator not supported in all browsers: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator#Browser_compatibility
}
this.field('title');
this.field('text');
this.ref('location');
for (var i=0; i < data.docs.length; i++) {
var doc = data.docs[i];
this.add(doc);
documents[doc.location] = doc;
}
});
console.log('Lunr index built, search ready');
}
allowSearch = true;
postMessage({allowSearch: allowSearch});
}
function init () {
var oReq = new XMLHttpRequest();
oReq.addEventListener("load", onJSONLoaded);
var index_path = base_path + '/search_index.json';
if( 'function' === typeof importScripts ){
index_path = 'search_index.json';
}
oReq.open("GET", index_path);
oReq.send();
}
function search (query) {
if (!allowSearch) {
console.error('Assets for search still loading');
return;
}
var resultDocuments = [];
var results = index.search(query);
for (var i=0; i < results.length; i++){
var result = results[i];
doc = documents[result.ref];
doc.summary = doc.text.substring(0, 200);
resultDocuments.push(doc);
}
return resultDocuments;
}
if( 'function' === typeof importScripts ) {
onmessage = function (e) {
if (e.data.init) {
init();
} else if (e.data.query) {
postMessage({ results: search(e.data.query) });
} else {
console.error("Worker - Unrecognized message: " + e);
}
};
}
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>None</loc>
<lastmod>2019-02-02</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>None</loc>
<lastmod>2019-02-02</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>None</loc>
<lastmod>2019-02-02</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>None</loc>
<lastmod>2019-02-02</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc></loc>
<changefreq>daily</changefreq>
</url>
<url>
<loc>None</loc>
<lastmod>2019-02-02</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>None</loc>
<lastmod>2019-02-02</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>None</loc>
<lastmod>2019-02-02</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>None</loc>
<lastmod>2019-02-02</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>None</loc>
<lastmod>2019-02-02</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>None</loc>
<lastmod>2019-02-02</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>None</loc>
<lastmod>2019-02-02</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>None</loc>
<lastmod>2019-02-02</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>None</loc>
<lastmod>2019-02-02</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>None</loc>
<lastmod>2019-02-02</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>None</loc>
<lastmod>2019-02-02</lastmod>
<changefreq>daily</changefreq>
</url>
</urlset>
\ No newline at end of file
File added
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="../../img/favicon.ico">
<title>API 接口 - CAP</title>
<link rel="stylesheet" href="//use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
<link rel="stylesheet" href="//cdn.jsdelivr.net/font-hack/3.003/css/hack.min.css">
<link href='//fonts.googleapis.com/css?family=PT+Sans:400,400italic,700,700italic&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href='//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,700italic,400,300,600,700&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href="../../css/bootstrap-custom.min.css" rel="stylesheet">
<link href="../../css/base.min.css" rel="stylesheet">
<link href="../../css/cinder.min.css" rel="stylesheet">
<link rel="stylesheet" href="../../css/highlight.min.css">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<![endif]-->
<script src="//ajax.googleapis.com/ajax/libs/webfont/1.6.28/webfont.js"></script>
<script>
WebFont.load({
google: {
families: ['Open Sans', 'PT Sans']
}
});
</script>
</head>
<body>
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<!-- Collapsed navigation -->
<div class="navbar-header">
<!-- Expander button -->
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- Main title -->
<a class="navbar-brand" href="../..">CAP</a>
</div>
<!-- Expanded navigation -->
<div class="navbar-collapse collapse">
<!-- Main navigation -->
<ul class="nav navbar-nav">
<li >
<a href="../..">Home</a>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/us/flat/16.png">User Guide <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../../user-guide/getting-started/">Getting Started</a>
</li>
<li >
<a href="../../user-guide/api-interface/">API Interface</a>
</li>
<li >
<a href="../../user-guide/configuration/">Configuration</a>
</li>
<li >
<a href="../../user-guide/design-principle.md.md">Design Principle</a>
</li>
<li >
<a href="../../user-guide/implementation-mechanisms/">Implementation Mechanisms</a>
</li>
<li >
<a href="../../user-guide/distributed-transactions/">Distributed Transactions</a>
</li>
<li >
<a href="../../user-guide/faq/">FAQ</a>
</li>
</ul>
</li>
<li class="dropdown active">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/cn/flat/16.png">使用指南 <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../getting-started/">快速开始</a>
</li>
<li class="active">
<a href="./">API 接口</a>
</li>
<li >
<a href="../configuration/">配置</a>
</li>
<li >
<a href="../design-principle/">设计原理</a>
</li>
<li >
<a href="../implementation-mechanisms/">实现</a>
</li>
<li >
<a href="../distributed-transactions/">分布式事务</a>
</li>
<li >
<a href="../faq/">FAQ</a>
</li>
</ul>
</li>
<li >
<a href="../../about/">About</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<a href="#" data-toggle="modal" data-target="#mkdocs_search_modal">
<i class="fas fa-search"></i> Search
</a>
</li>
<li >
<a rel="next" href="../getting-started/">
<i class="fas fa-arrow-left"></i> Previous
</a>
</li>
<li >
<a rel="prev" href="../configuration/">
Next <i class="fas fa-arrow-right"></i>
</a>
</li>
<li>
<a href="https://github.com/dotnetcore/CAP/edit/master/docs/user-guide-cn/api-interface.md"><i class="fab fa-github"></i> Edit on GitHub</a>
</li>
</ul>
</div>
</div>
</div>
<div class="container">
<div class="col-md-3"><div class="bs-sidebar hidden-print affix well" role="complementary">
<ul class="nav bs-sidenav">
<li class="first-level active"><a href="#_1">发布/发送</a></li>
<li class="second-level"><a href="#_2">消息补偿</a></li>
<li class="second-level"><a href="#_3">事务</a></li>
<li class="first-level "><a href="#_4">订阅/消费</a></li>
<li class="second-level"><a href="#_5">例外情况</a></li>
</ul>
</div></div>
<div class="col-md-9" role="main">
<p>CAP 的 API 接口只有一个,就是 <code>ICapPublisher</code> 接口,你可以从 DI 容器中获取到该接口的实例进行调用。</p>
<h3 id="_1">发布/发送</h3>
<p>你可以使用 <code>ICapPublisher</code> 接口中的 <code>Publish&lt;T&gt;</code> 或者 <code>PublishAsync&lt;T&gt;</code> 方法来发送消息:</p>
<pre><code class="cs">public class PublishController : Controller
{
private readonly ICapPublisher _capBus;
public PublishController(ICapPublisher capPublisher)
{
_capBus = capPublisher;
}
//不使用事务
[Route(&quot;~/without/transaction&quot;)]
public IActionResult WithoutTransaction()
{
_capBus.Publish(&quot;xxx.services.show.time&quot;, DateTime.Now);
return Ok();
}
//Ado.Net 中使用事务,自动提交
[Route(&quot;~/adonet/transaction&quot;)]
public IActionResult AdonetWithTransaction()
{
using (var connection = new MySqlConnection(ConnectionString))
{
using (var transaction = connection.BeginTransaction(_capBus, autoCommit: true))
{
//业务代码
_capBus.Publish(&quot;xxx.services.show.time&quot;, DateTime.Now);
}
}
return Ok();
}
//EntityFramework 中使用事务,自动提交
[Route(&quot;~/ef/transaction&quot;)]
public IActionResult EntityFrameworkWithTransaction([FromServices]AppDbContext dbContext)
{
using (var trans = dbContext.Database.BeginTransaction(_capBus, autoCommit: true))
{
//业务代码
_capBus.Publish(&quot;xxx.services.show.time&quot;, DateTime.Now);
}
return Ok();
}
}
</code></pre>
<p>下面是PublishAsync这个接口的签名:</p>
<p><strong><code>PublishAsync&lt;T&gt;(string name,T object)</code></strong></p>
<p>默认情况下,在调用此方法的时候 CAP 将在内部创建事务,然后将消息写入到 <code>Cap.Published</code> 这个消息表。</p>
<h4 id="_2">消息补偿</h4>
<p>有时候当发送一条消息出去之后,希望有一个回调可以获得消费方法的通知,用来补偿发送方做的业务操作,那么可以使用下面这个重载。</p>
<p><strong><code>PublishAsync&lt;T&gt;(string name,T object, string callBackName)</code></strong></p>
<p>这个重载中 <code>callbackName</code> 是一个回调的订阅方法名称,当消费端处理完成消息之后CAP会把消费者的处理结果返回并且调用指定的订阅方法。</p>
<blockquote>
<p>在一些需要业务补偿的场景中,我们可以利用此特性进行一些还原的补偿操作。例如:电商系统中的付款操作,订单在进行支付调用支付服务的过程中如果发生异常,那么支付服务可以通过返回一个结果来告诉调用方此次业务失败,调用方将支付状态标记为失败。 调用方通过订阅 <code>callbackName</code>(订阅参数为消费方方法的返回值) 即可接收到支付服务消费者方法的返回结果,从而进行补偿的业务处理。</p>
</blockquote>
<p>下面是使用方法:</p>
<pre><code class="C#">
// 发送方
_capBus.Publish(&quot;xxx.services.show.time&quot;,DaateTime.Now,&quot;callback-show-execute-time&quot;);
[CapSubscribe(&quot;callback-show-execute-time&quot;)] //对应发送的 callbackName
public void ShowPublishTimeAndReturnExecuteTime(DateTime time)
{
Console.WriteLine(time); // 这是订阅方返回的时间
}
//--------------------------------------------------------------------------------
//订阅方
[CapSubscribe(&quot;xxx.services.show.time&quot;)]
public DateTime ShowPublishTimeAndReturnExecuteTime(DateTime time)
{
Console.WriteLine(time); // 这是发送的时间
return DateTime.Now; // 这是消费者返回的时间,CAP会取该方法的返回值用来传递到发送方的回调订阅里面
}
</code></pre>
<h4 id="_3">事务</h4>
<p>事务在 CAP 具有重要作用,它是保证消息可靠性的一个基石。 在发送一条消息到消息队列的过程中,如果不使用事务,我们是没有办法保证我们的业务代码在执行成功后消息已经成功的发送到了消息队列,或者是消息成功的发送到了消息队列,但是业务代码确执行失败。</p>
<p>这里的失败原因可能是多种多样的,比如连接异常,网络故障等等。</p>
<p><em>只有业务代码和CAP的Publish代码必须在同一个事务中,才能够保证业务代码和消息代码同时成功或者失败。</em></p>
<p>以下是两种使用事务进行Publish的代码:</p>
<ul>
<li>EntityFramework</li>
</ul>
<pre><code class="cs">using (var trans = dbContext.Database.BeginTransaction(_capBus, autoCommit: false)
{
//业务代码
_capBus.Publish(&quot;xxx.services.show.time&quot;, DateTime.Now);
trans.Commit();
}
</code></pre>
<p>在不使用自动提交的时候,你的业务代码可以位于 Publish 之前或者之后,只需要保证在同一个事务。 </p>
<p>当使用自动提交时候,需要确保 <code>_capBus.Publish</code> 位于代码的最后。</p>
<p>其中,发送的内容会序列化为Json存储到消息表中。</p>
<ul>
<li>Dapper</li>
</ul>
<pre><code class="cs">using (var connection = new MySqlConnection(ConnectionString))
{
using (var transaction = connection.BeginTransaction(_capBus, autoCommit: false))
{
//your business code
connection.Execute(&quot;insert into test(name) values('test')&quot;, transaction: (IDbTransaction)transaction.DbTransaction);
_capBus.Publish(&quot;sample.rabbitmq.mysql&quot;, DateTime.Now);
transaction.Commit();
}
}
</code></pre>
<h3 id="_4">订阅/消费</h3>
<p><strong>注意:框架无法做到100%确保消息只执行一次,所以在一些关键场景消息端在方法实现的过程中自己保证幂等性。</strong></p>
<p>使用 <code>CapSubscribeAttribute</code> 来订阅 CAP 发布出去的消息。</p>
<pre><code>[CapSubscribe(&quot;xxx.services.bar&quot;)]
public void BarMessageProcessor()
{
}
</code></pre>
<p>这里,你也可以使用多个 <code>CapSubscribe[""]</code> 来同时订阅多个不同的消息 :</p>
<pre><code>[CapSubscribe(&quot;xxx.services.bar&quot;)]
[CapSubscribe(&quot;xxx.services.foo&quot;)]
public void BarAndFooMessageProcessor()
{
}
</code></pre>
<p>其中,<code>xxx.services.bar</code> 为订阅的消息名称,内部实现上,这个名称在不同的消息队列具有不同的代表。 在 Kafka 中,这个名称即为 Topic Name。 在RabbitMQ 中,为 RouteKey。</p>
<blockquote>
<p>RabbitMQ 中的 RouteKey 支持绑定键表达式写法,有两种主要的绑定键:</p>
<p>*(星号)可以代替一个单词.</p>
<p># (井号) 可以代替0个或多个单词.</p>
<p>比如在下面这个图中(P为发送者,X为RabbitMQ中的Exchange,C为消费者,Q为队列)</p>
<p><img alt="" src="http://images2017.cnblogs.com/blog/250417/201708/250417-20170807093230268-283915002.png" /></p>
<p>在这个示例中,我们将发送一条关于动物描述的消息,也就是说 Name(routeKey) 字段中的内容包含 3 个单词。第一个单词是描述速度的(celerity),第二个单词是描述颜色的(colour),第三个是描述哪种动物的(species),它们组合起来类似:“<celerity>.<colour>.<species>”。</p>
<p>然后在使用 <code>CapSubscribe</code> 绑定的时候,Q1绑定为 <code>CapSubscribe["*.orange.*"]</code>, Q2 绑定为 <code>CapSubscribe["*.*.rabbit"]</code><code>[CapSubscribe["lazy.#]</code></p>
<p>那么,当发送一个名为 "quick.orange.rabbit" 消息的时候,这两个队列将会同时收到该消息。同样名为 <code>lazy.orange.elephant</code>的消息也会被同时收到。另外,名为 "quick.orange.fox" 的消息将仅会被发送到Q1队列,名为 "lazy.brown.fox" 的消息仅会被发送到Q2。"lazy.pink.rabbit" 仅会被发送到Q2一次,即使它被绑定了2次。"quick.brown.fox" 没有匹配到任何绑定的队列,所以它将会被丢弃。</p>
<p>另外一种情况,如果你违反约定,比如使用 4个单词进行组合,例如 "quick.orange.male.rabbit",那么它将匹配不到任何的队列,消息将会被丢弃。</p>
<p>但是,假如你的消息名为 "lazy.orange.male.rabbit",那么他们将会被发送到Q2,因为 #(井号)可以匹配 0 或者多个单词。</p>
</blockquote>
<p>在 CAP 中,我们把每一个拥有 <code>CapSubscribe[]</code>标记的方法叫做<strong>订阅者</strong>,你可以把订阅者进行分组。</p>
<p><strong>组(Group)</strong>,是订阅者的一个集合,每一组可以有一个或者多个消费者,但是一个订阅者只能属于某一个组。同一个组内的订阅者订阅的消息只能被消费一次。</p>
<p>如果你在订阅的时候没有指定组,CAP会将订阅者设置到一个默认的组,默认的组名称为 <code>cap.queue.{程序集名称}</code></p>
<p>以下是使用组进行订阅的示例:</p>
<pre><code class="cs">[CapSubscribe(&quot;xxx.services.foo&quot;, Group = &quot;moduleA&quot;)]
public void FooMessageProcessor()
{
}
</code></pre>
<h4 id="_5">例外情况</h4>
<p>这里有几种情况可能需要知道:</p>
<p><strong>① 消息发布的时候订阅方还未启动</strong></p>
<p>Kafka:</p>
<p>当 Kafka 中,发布的消息存储于持久化的日志文件中,所以消息不会丢失,当订阅者所在的程序启动的时候会消费掉这些消息。</p>
<p>RabbitMQ:</p>
<p>在 RabbitMQ 中,应用程序<strong>首次启动</strong>会创建具有持久化的 Exchange 和 Queue,CAP 会针对每一个订阅者Group会新建一个消费者队列,<strong>由于首次启动时候订阅者未启动的所以是没有队列的,消息无法进行持久化,这个时候生产者发的消息会丢失</strong></p>
<p>针对RabbitMQ的消息丢失的问题,有两种解决方式:</p>
<p>i. 部署应用程序之前,在RabbitMQ中手动创建具有durable特性的Exchange和Queue,默认情况他们的名字分别是(cap.default.topic, cap.default.group)。</p>
<p>ii. 提前运行一遍所有实例,让Exchange和Queue初始化。</p>
<p>我们建议采用第 ii 种方案,因为很容易做到。</p></div>
</div>
<footer class="col-md-12 text-center">
<hr>
<p>
<small>Documentation built with <a href="http://www.mkdocs.org/">MkDocs</a>.</p></small>
</footer>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="../../js/bootstrap-3.0.3.min.js"></script>
<script src="../../js/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script>
var base_url = '../..';
</script>
<!-- <script data-main="../../mkdocs/js/search.js" src="../../mkdocs/js/require.js"></script> -->
<script src="../../js/base.js"></script>
<script src="../../search/main.js"></script>
<div class="modal" id="mkdocs_search_modal" tabindex="-1" role="dialog" aria-labelledby="Search Modal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span>
<span class="sr-only">Close</span>
</button>
<h4 class="modal-title" id="exampleModalLabel">Search</h4>
</div>
<div class="modal-body">
<p>
From here you can search these documents. Enter your search terms below.
</p>
<form role="form">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search..." id="mkdocs-search-query">
</div>
</form>
<div id="mkdocs-search-results"></div>
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="../../img/favicon.ico">
<title>配置 - CAP</title>
<link rel="stylesheet" href="//use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
<link rel="stylesheet" href="//cdn.jsdelivr.net/font-hack/3.003/css/hack.min.css">
<link href='//fonts.googleapis.com/css?family=PT+Sans:400,400italic,700,700italic&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href='//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,700italic,400,300,600,700&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href="../../css/bootstrap-custom.min.css" rel="stylesheet">
<link href="../../css/base.min.css" rel="stylesheet">
<link href="../../css/cinder.min.css" rel="stylesheet">
<link rel="stylesheet" href="../../css/highlight.min.css">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<![endif]-->
<script src="//ajax.googleapis.com/ajax/libs/webfont/1.6.28/webfont.js"></script>
<script>
WebFont.load({
google: {
families: ['Open Sans', 'PT Sans']
}
});
</script>
</head>
<body>
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<!-- Collapsed navigation -->
<div class="navbar-header">
<!-- Expander button -->
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- Main title -->
<a class="navbar-brand" href="../..">CAP</a>
</div>
<!-- Expanded navigation -->
<div class="navbar-collapse collapse">
<!-- Main navigation -->
<ul class="nav navbar-nav">
<li >
<a href="../..">Home</a>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/us/flat/16.png">User Guide <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../../user-guide/getting-started/">Getting Started</a>
</li>
<li >
<a href="../../user-guide/api-interface/">API Interface</a>
</li>
<li >
<a href="../../user-guide/configuration/">Configuration</a>
</li>
<li >
<a href="../../user-guide/design-principle.md.md">Design Principle</a>
</li>
<li >
<a href="../../user-guide/implementation-mechanisms/">Implementation Mechanisms</a>
</li>
<li >
<a href="../../user-guide/distributed-transactions/">Distributed Transactions</a>
</li>
<li >
<a href="../../user-guide/faq/">FAQ</a>
</li>
</ul>
</li>
<li class="dropdown active">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/cn/flat/16.png">使用指南 <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../getting-started/">快速开始</a>
</li>
<li >
<a href="../api-interface/">API 接口</a>
</li>
<li class="active">
<a href="./">配置</a>
</li>
<li >
<a href="../design-principle/">设计原理</a>
</li>
<li >
<a href="../implementation-mechanisms/">实现</a>
</li>
<li >
<a href="../distributed-transactions/">分布式事务</a>
</li>
<li >
<a href="../faq/">FAQ</a>
</li>
</ul>
</li>
<li >
<a href="../../about/">About</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<a href="#" data-toggle="modal" data-target="#mkdocs_search_modal">
<i class="fas fa-search"></i> Search
</a>
</li>
<li >
<a rel="next" href="../api-interface/">
<i class="fas fa-arrow-left"></i> Previous
</a>
</li>
<li >
<a rel="prev" href="../design-principle/">
Next <i class="fas fa-arrow-right"></i>
</a>
</li>
<li>
<a href="https://github.com/dotnetcore/CAP/edit/master/docs/user-guide-cn/configuration.md"><i class="fab fa-github"></i> Edit on GitHub</a>
</li>
</ul>
</div>
</div>
</div>
<div class="container">
<div class="col-md-3"><div class="bs-sidebar hidden-print affix well" role="complementary">
<ul class="nav bs-sidenav">
<li class="first-level active"><a href="#cap-options">Cap Options</a></li>
<li class="first-level "><a href="#rabbitmq-options">RabbitMQ Options</a></li>
<li class="first-level "><a href="#kafka-options">Kafka Options</a></li>
<li class="first-level "><a href="#entityframework-options">EntityFramework Options</a></li>
<li class="first-level "><a href="#sqlserver-options">SqlServer Options</a></li>
<li class="first-level "><a href="#mysql-options">MySql Options</a></li>
<li class="first-level "><a href="#postgresql-options">PostgreSql Options</a></li>
</ul>
</div></div>
<div class="col-md-9" role="main">
<p>CAP 使用 Microsoft.Extensions.DependencyInjection 进行配置的注入,你也可以依赖于 DI 从json文件中读取配置。</p>
<h3 id="cap-options">Cap Options</h3>
<p>你可以使用如下方式来配置 CAP 中的一些配置项,例如</p>
<pre><code class="cs">services.AddCap(capOptions =&gt; {
capOptions.FailedCallback = //...
});
</code></pre>
<p><code>CapOptions</code> 提供了以下配置项:</p>
<table>
<thead>
<tr>
<th align="left">NAME</th>
<th align="left">DESCRIPTION</th>
<th>TYPE</th>
<th align="left">DEFAULT</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">DefaultGroup</td>
<td align="left">订阅者所属的默认消费者组</td>
<td>string</td>
<td align="left">cap.queue+程序集名称</td>
</tr>
<tr>
<td align="left">SuccessedMessageExpiredAfter</td>
<td align="left">成功的消息被删除的过期时间</td>
<td>int</td>
<td align="left">3600 秒</td>
</tr>
<tr>
<td align="left">FailedCallback</td>
<td align="left">执行失败消息时的回调函数,详情见下文</td>
<td>Action</td>
<td align="left">NULL</td>
</tr>
<tr>
<td align="left">FailedRetryInterval</td>
<td align="left">失败重试间隔时间</td>
<td>int</td>
<td align="left">60 秒</td>
</tr>
<tr>
<td align="left">FailedRetryCount</td>
<td align="left">失败最大重试次数</td>
<td>int</td>
<td align="left">50 次</td>
</tr>
</tbody>
</table>
<p>CapOptions 提供了 <code>FailedCallback</code> 为处理失败的消息时的回调函数。当消息多次发送失败后,CAP会将消息状态标记为<code>Failed</code>,CAP有一个专门的处理者用来处理这种失败的消息,针对失败的消息会重新放入到队列中发送到MQ,在这之前如果<code>FailedCallback</code>具有值,那么将首先调用此回调函数来告诉客户端。</p>
<p>FailedCallback 的类型为 <code>Action&lt;MessageType,string,string&gt;</code>,第一个参数为消息类型(发送的还是接收的),第二个参数为消息的名称(name),第三个参数为消息的内容(content)。</p>
<h3 id="rabbitmq-options">RabbitMQ Options</h3>
<p>CAP 采用的是针对 CapOptions 进行扩展来实现RabbitMQ的配置功能,所以针对 RabbitMQ 的配置用法如下:</p>
<pre><code class="cs">services.AddCap(capOptions =&gt; {
capOptions.UseRabbitMQ(rabbitMQOption=&gt;{
// rabbitmq options.
});
});
</code></pre>
<p><code>RabbitMQOptions</code> 提供了有关RabbitMQ相关的配置:</p>
<table>
<thead>
<tr>
<th align="left">NAME</th>
<th align="left">DESCRIPTION</th>
<th>TYPE</th>
<th align="left">DEFAULT</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">HostName</td>
<td align="left">宿主地址</td>
<td>string</td>
<td align="left">localhost</td>
</tr>
<tr>
<td align="left">UserName</td>
<td align="left">用户名</td>
<td>string</td>
<td align="left">guest</td>
</tr>
<tr>
<td align="left">Password</td>
<td align="left">密码</td>
<td>string</td>
<td align="left">guest</td>
</tr>
<tr>
<td align="left">VirtualHost</td>
<td align="left">虚拟主机</td>
<td>string</td>
<td align="left">/</td>
</tr>
<tr>
<td align="left">Port</td>
<td align="left">端口号</td>
<td>int</td>
<td align="left">-1</td>
</tr>
<tr>
<td align="left">TopicExchangeName</td>
<td align="left">CAP默认Exchange名称</td>
<td>string</td>
<td align="left">cap.default.topic</td>
</tr>
<tr>
<td align="left">RequestedConnectionTimeout</td>
<td align="left">RabbitMQ连接超时时间</td>
<td>int</td>
<td align="left">30,000 毫秒</td>
</tr>
<tr>
<td align="left">SocketReadTimeout</td>
<td align="left">RabbitMQ消息读取超时时间</td>
<td>int</td>
<td align="left">30,000 毫秒</td>
</tr>
<tr>
<td align="left">SocketWriteTimeout</td>
<td align="left">RabbitMQ消息写入超时时间</td>
<td>int</td>
<td align="left">30,000 毫秒</td>
</tr>
<tr>
<td align="left">QueueMessageExpires</td>
<td align="left">队列中消息自动删除时间</td>
<td>int</td>
<td align="left">(10天) 毫秒</td>
</tr>
</tbody>
</table>
<h3 id="kafka-options">Kafka Options</h3>
<p>CAP 采用的是针对 CapOptions 进行扩展来实现 Kafka 的配置功能,所以针对 Kafka 的配置用法如下:</p>
<pre><code class="cs">services.AddCap(capOptions =&gt; {
capOptions.UseKafka(kafkaOption=&gt;{
// kafka options.
// kafkaOptions.MainConfig.Add(&quot;&quot;, &quot;&quot;);
});
});
</code></pre>
<p><code>KafkaOptions</code> 提供了有关 Kafka 相关的配置,由于Kafka的配置比较多,所以此处使用的是提供的 MainConfig 字典来支持进行自定义配置,你可以查看这里来获取对配置项的支持信息。</p>
<p><a href="https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md">https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md</a></p>
<h3 id="entityframework-options">EntityFramework Options</h3>
<p>如果使用的 Entityframework 来作为消息持久化存储的话,那么你可以在配置 CAP EntityFramework 配置项的时候来自定义一些配置。</p>
<pre><code class="cs">services.AddCap(x =&gt;
{
x.UseEntityFramework&lt;AppDbContext&gt;(efOption =&gt;
{
// entityframework options.
});
});
</code></pre>
<p>注意,如果你使用了 <code>UseEntityFramework</code> 的配置项,那么你不需要再次配置下面的章节几个针对不同数据库的配置,CAP 将会自动读取 DbContext 中使用的数据库相关配置信息。</p>
<table>
<thead>
<tr>
<th align="left">NAME</th>
<th align="left">DESCRIPTION</th>
<th>TYPE</th>
<th align="left">DEFAULT</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">Schema</td>
<td align="left">Cap表架构</td>
<td>string</td>
<td align="left">Cap (SQL Server)</td>
</tr>
<tr>
<td align="left">Schema</td>
<td align="left">Cap表架构</td>
<td>string</td>
<td align="left">cap (PostgreSql)</td>
</tr>
<tr>
<td align="left">TableNamePrefix</td>
<td align="left">Cap表前缀</td>
<td>string</td>
<td align="left">cap (MySql)</td>
</tr>
</tbody>
</table>
<h3 id="sqlserver-options">SqlServer Options</h3>
<p>注意,如果你使用的是 EntityFramewrok,你用不到此配置项。</p>
<p>CAP 采用的是针对 CapOptions 进行扩展来实现 SqlServer 的配置功能,所以针对 SqlServer 的配置用法如下:</p>
<pre><code class="cs">services.AddCap(capOptions =&gt; {
capOptions.UseSqlServer(sqlserverOptions =&gt; {
// sqlserverOptions.ConnectionString
});
});
</code></pre>
<table>
<thead>
<tr>
<th align="left">NAME</th>
<th align="left">DESCRIPTION</th>
<th>TYPE</th>
<th align="left">DEFAULT</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">Schema</td>
<td align="left">Cap表架构</td>
<td>string</td>
<td align="left">Cap</td>
</tr>
<tr>
<td align="left">ConnectionString</td>
<td align="left">数据库连接字符串</td>
<td>string</td>
<td align="left">null</td>
</tr>
</tbody>
</table>
<h3 id="mysql-options">MySql Options</h3>
<p>注意,如果你使用的是 EntityFramewrok,你用不到此配置项。</p>
<p>CAP 采用的是针对 CapOptions 进行扩展来实现 MySql 的配置功能,所以针对 MySql 的配置用法如下:</p>
<pre><code class="cs">services.AddCap(capOptions =&gt; {
capOptions.UseMySql(mysqlOptions =&gt; {
// mysqlOptions.ConnectionString
});
});
</code></pre>
<table>
<thead>
<tr>
<th align="left">NAME</th>
<th align="left">DESCRIPTION</th>
<th>TYPE</th>
<th align="left">DEFAULT</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">TableNamePrefix</td>
<td align="left">Cap表名前缀</td>
<td>string</td>
<td align="left">cap</td>
</tr>
<tr>
<td align="left">ConnectionString</td>
<td align="left">数据库连接字符串</td>
<td>string</td>
<td align="left">null</td>
</tr>
</tbody>
</table>
<h3 id="postgresql-options">PostgreSql Options</h3>
<p>注意,如果你使用的是 EntityFramewrok,你用不到此配置项。</p>
<p>CAP 采用的是针对 CapOptions 进行扩展来实现 PostgreSql 的配置功能,所以针对 PostgreSql 的配置用法如下:</p>
<pre><code class="cs">services.AddCap(capOptions =&gt; {
capOptions.UsePostgreSql(postgreOptions =&gt; {
// postgreOptions.ConnectionString
});
});
</code></pre>
<table>
<thead>
<tr>
<th align="left">NAME</th>
<th align="left">DESCRIPTION</th>
<th>TYPE</th>
<th align="left">DEFAULT</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">Schema</td>
<td align="left">Cap表名前缀</td>
<td>string</td>
<td align="left">cap</td>
</tr>
<tr>
<td align="left">ConnectionString</td>
<td align="left">数据库连接字符串</td>
<td>string</td>
<td align="left">null</td>
</tr>
</tbody>
</table></div>
</div>
<footer class="col-md-12 text-center">
<hr>
<p>
<small>Documentation built with <a href="http://www.mkdocs.org/">MkDocs</a>.</p></small>
</footer>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="../../js/bootstrap-3.0.3.min.js"></script>
<script src="../../js/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script>
var base_url = '../..';
</script>
<!-- <script data-main="../../mkdocs/js/search.js" src="../../mkdocs/js/require.js"></script> -->
<script src="../../js/base.js"></script>
<script src="../../search/main.js"></script>
<div class="modal" id="mkdocs_search_modal" tabindex="-1" role="dialog" aria-labelledby="Search Modal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span>
<span class="sr-only">Close</span>
</button>
<h4 class="modal-title" id="exampleModalLabel">Search</h4>
</div>
<div class="modal-body">
<p>
From here you can search these documents. Enter your search terms below.
</p>
<form role="form">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search..." id="mkdocs-search-query">
</div>
</form>
<div id="mkdocs-search-results"></div>
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="../../img/favicon.ico">
<title>设计原理 - CAP</title>
<link rel="stylesheet" href="//use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
<link rel="stylesheet" href="//cdn.jsdelivr.net/font-hack/3.003/css/hack.min.css">
<link href='//fonts.googleapis.com/css?family=PT+Sans:400,400italic,700,700italic&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href='//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,700italic,400,300,600,700&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href="../../css/bootstrap-custom.min.css" rel="stylesheet">
<link href="../../css/base.min.css" rel="stylesheet">
<link href="../../css/cinder.min.css" rel="stylesheet">
<link rel="stylesheet" href="../../css/highlight.min.css">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<![endif]-->
<script src="//ajax.googleapis.com/ajax/libs/webfont/1.6.28/webfont.js"></script>
<script>
WebFont.load({
google: {
families: ['Open Sans', 'PT Sans']
}
});
</script>
</head>
<body>
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<!-- Collapsed navigation -->
<div class="navbar-header">
<!-- Expander button -->
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- Main title -->
<a class="navbar-brand" href="../..">CAP</a>
</div>
<!-- Expanded navigation -->
<div class="navbar-collapse collapse">
<!-- Main navigation -->
<ul class="nav navbar-nav">
<li >
<a href="../..">Home</a>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/us/flat/16.png">User Guide <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../../user-guide/getting-started/">Getting Started</a>
</li>
<li >
<a href="../../user-guide/api-interface/">API Interface</a>
</li>
<li >
<a href="../../user-guide/configuration/">Configuration</a>
</li>
<li >
<a href="../../user-guide/design-principle.md.md">Design Principle</a>
</li>
<li >
<a href="../../user-guide/implementation-mechanisms/">Implementation Mechanisms</a>
</li>
<li >
<a href="../../user-guide/distributed-transactions/">Distributed Transactions</a>
</li>
<li >
<a href="../../user-guide/faq/">FAQ</a>
</li>
</ul>
</li>
<li class="dropdown active">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/cn/flat/16.png">使用指南 <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../getting-started/">快速开始</a>
</li>
<li >
<a href="../api-interface/">API 接口</a>
</li>
<li >
<a href="../configuration/">配置</a>
</li>
<li class="active">
<a href="./">设计原理</a>
</li>
<li >
<a href="../implementation-mechanisms/">实现</a>
</li>
<li >
<a href="../distributed-transactions/">分布式事务</a>
</li>
<li >
<a href="../faq/">FAQ</a>
</li>
</ul>
</li>
<li >
<a href="../../about/">About</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<a href="#" data-toggle="modal" data-target="#mkdocs_search_modal">
<i class="fas fa-search"></i> Search
</a>
</li>
<li >
<a rel="next" href="../configuration/">
<i class="fas fa-arrow-left"></i> Previous
</a>
</li>
<li >
<a rel="prev" href="../implementation-mechanisms/">
Next <i class="fas fa-arrow-right"></i>
</a>
</li>
<li>
<a href="https://github.com/dotnetcore/CAP/edit/master/docs/user-guide-cn/design-principle.md"><i class="fab fa-github"></i> Edit on GitHub</a>
</li>
</ul>
</div>
</div>
</div>
<div class="container">
<div class="col-md-3"><div class="bs-sidebar hidden-print affix well" role="complementary">
<ul class="nav bs-sidenav">
<li class="first-level active"><a href="#_1">动机</a></li>
<li class="first-level "><a href="#_2">持久化</a></li>
<li class="first-level "><a href="#_3">通讯数据流</a></li>
<li class="first-level "><a href="#_4">一致性</a></li>
</ul>
</div></div>
<div class="col-md-9" role="main">
<h3 id="_1">动机</h3>
<p>随着微服务架构的流行,越来越多的人在尝试使用微服务来架构他们的系统,而在这其中我们会遇到例如分布式事务的问题,为了解决这些问题,我没有发现简单并且易于使用的解决方案,所以我决定来打造这样一个库来解决这个问题。</p>
<p>最初 CAP 是为了解决分布式系统中的事务问题,她采用的是 异步确保 这种弱一致性事务机制实现了分布式事务的最终一致性,更多这方面的信息可以查看第6节。</p>
<p>现在 CAP 除了解决分布式事务的问题外,她另外一个重要的功能就是作为 EventBus 来使用,她具有 EventBus 的所有功能,并且提供了更加简化的方式来处理EventBus中的发布/订阅。</p>
<h3 id="_2">持久化</h3>
<p>CAP 依靠本地数据库实现消息的持久化,CAP 使用这种方式来应对一切环境或者网络异常导致消息丢失的情况,消息的可靠性是分布式事务的基石,所以在任何情况下消息都不能丢失。</p>
<p>对于消息的持久化分为两种:</p>
<p><strong>① 消息进入消息队列之前的持久化</strong></p>
<p>在消息进入到消息队列之前,CAP使用本地数据库表对消息进行持久化,这样可以保证当消息队列出现异常或者网络错误时候消息是没有丢失的。</p>
<p>为了保证这种机制的可靠性,CAP使用和业务代码相同的数据库事务来保证业务操作和CAP的消息在持久化的过程中是强一致的。也就是说在进行消息持久化的过程中,任何一方发生异常情况数据库都会进行回滚操作。</p>
<p><strong>② 消息进入到消息队列之后的持久化</strong></p>
<p>消息进入到消息队列之后,CAP会启动消息队列的持久化功能,我们需要说明一下在 RabbitMQ 和 Kafka 中CAP的消息是如何持久化的。</p>
<p>针对于 RabbitMQ 中的消息持久化,CAP 使用的是具有消息持久化功能的消费者队列,但是这里面可能有例外情况,参加 2.2.1 章节。</p>
<p>由于 Kafka 天生设计的就是使用文件进行的消息持久化,在所以在消息进入到Kafka之后,Kafka会保证消息能够正确被持久化而不丢失。</p>
<h3 id="_3">通讯数据流</h3>
<p>CAP 中消息的流转过程大致如下:</p>
<p><strong>2.2版本以前</strong> </p>
<p><img alt="" src="http://images2017.cnblogs.com/blog/250417/201708/250417-20170803174645928-1813351415.png" /></p>
<blockquote>
<p>“ P ” 代表消息发送者(生产者)。 “ C ” 代表消息消费者(订阅者)。</p>
</blockquote>
<p><strong>2.2版本以后</strong></p>
<p>在2.2以后的版本中,我们调整了一些消息的流转流程,我们移除了数据库中的 Queue 表使用内存队列来代替,详情见:<a href="https://github.com/dotnetcore/CAP/issues/96">Improve the implementation mechanism of queue mode</a></p>
<h3 id="_4">一致性</h3>
<p>CAP 采用最终一致性作为的一致性方案,此方案是遵循 CAP 理论,以下是CAP理论的描述。</p>
<p>C(一致性)一致性是指数据的原子性,在经典的数据库中通过事务来保障,事务完成时,无论成功或回滚,数据都会处于一致的状态,在分布式环境下,一致性是指多个节点数据是否一致;</p>
<p>A(可用性)服务一直保持可用的状态,当用户发出一个请求,服务能在一定的时间内返回结果;</p>
<p>P(分区容忍性)在分布式应用中,可能因为一些分布式的原因导致系统无法运转,好的分区容忍性,使应用虽然是一个分布式系统,但是好像一个可以正常运转的整体</p>
<p>根据 <a href="https://en.wikipedia.org/wiki/CAP_theorem">“CAP”分布式理论</a>, 在一个分布式系统中,我们往往为了可用性和分区容错性,忍痛放弃强一致支持,转而追求最终一致性。大部分业务场景下,我们是可以接受短暂的不一致的。</p>
<p>第 6 节将对此做进一步介绍。</p></div>
</div>
<footer class="col-md-12 text-center">
<hr>
<p>
<small>Documentation built with <a href="http://www.mkdocs.org/">MkDocs</a>.</p></small>
</footer>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="../../js/bootstrap-3.0.3.min.js"></script>
<script src="../../js/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script>
var base_url = '../..';
</script>
<!-- <script data-main="../../mkdocs/js/search.js" src="../../mkdocs/js/require.js"></script> -->
<script src="../../js/base.js"></script>
<script src="../../search/main.js"></script>
<div class="modal" id="mkdocs_search_modal" tabindex="-1" role="dialog" aria-labelledby="Search Modal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span>
<span class="sr-only">Close</span>
</button>
<h4 class="modal-title" id="exampleModalLabel">Search</h4>
</div>
<div class="modal-body">
<p>
From here you can search these documents. Enter your search terms below.
</p>
<form role="form">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search..." id="mkdocs-search-query">
</div>
</form>
<div id="mkdocs-search-results"></div>
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="../../img/favicon.ico">
<title>分布式事务 - CAP</title>
<link rel="stylesheet" href="//use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
<link rel="stylesheet" href="//cdn.jsdelivr.net/font-hack/3.003/css/hack.min.css">
<link href='//fonts.googleapis.com/css?family=PT+Sans:400,400italic,700,700italic&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href='//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,700italic,400,300,600,700&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href="../../css/bootstrap-custom.min.css" rel="stylesheet">
<link href="../../css/base.min.css" rel="stylesheet">
<link href="../../css/cinder.min.css" rel="stylesheet">
<link rel="stylesheet" href="../../css/highlight.min.css">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<![endif]-->
<script src="//ajax.googleapis.com/ajax/libs/webfont/1.6.28/webfont.js"></script>
<script>
WebFont.load({
google: {
families: ['Open Sans', 'PT Sans']
}
});
</script>
</head>
<body>
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<!-- Collapsed navigation -->
<div class="navbar-header">
<!-- Expander button -->
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- Main title -->
<a class="navbar-brand" href="../..">CAP</a>
</div>
<!-- Expanded navigation -->
<div class="navbar-collapse collapse">
<!-- Main navigation -->
<ul class="nav navbar-nav">
<li >
<a href="../..">Home</a>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/us/flat/16.png">User Guide <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../../user-guide/getting-started/">Getting Started</a>
</li>
<li >
<a href="../../user-guide/api-interface/">API Interface</a>
</li>
<li >
<a href="../../user-guide/configuration/">Configuration</a>
</li>
<li >
<a href="../../user-guide/design-principle.md.md">Design Principle</a>
</li>
<li >
<a href="../../user-guide/implementation-mechanisms/">Implementation Mechanisms</a>
</li>
<li >
<a href="../../user-guide/distributed-transactions/">Distributed Transactions</a>
</li>
<li >
<a href="../../user-guide/faq/">FAQ</a>
</li>
</ul>
</li>
<li class="dropdown active">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/cn/flat/16.png">使用指南 <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../getting-started/">快速开始</a>
</li>
<li >
<a href="../api-interface/">API 接口</a>
</li>
<li >
<a href="../configuration/">配置</a>
</li>
<li >
<a href="../design-principle/">设计原理</a>
</li>
<li >
<a href="../implementation-mechanisms/">实现</a>
</li>
<li class="active">
<a href="./">分布式事务</a>
</li>
<li >
<a href="../faq/">FAQ</a>
</li>
</ul>
</li>
<li >
<a href="../../about/">About</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<a href="#" data-toggle="modal" data-target="#mkdocs_search_modal">
<i class="fas fa-search"></i> Search
</a>
</li>
<li >
<a rel="next" href="../implementation-mechanisms/">
<i class="fas fa-arrow-left"></i> Previous
</a>
</li>
<li >
<a rel="prev" href="../faq/">
Next <i class="fas fa-arrow-right"></i>
</a>
</li>
<li>
<a href="https://github.com/dotnetcore/CAP/edit/master/docs/user-guide-cn/distributed-transactions.md"><i class="fab fa-github"></i> Edit on GitHub</a>
</li>
</ul>
</div>
</div>
</div>
<div class="container">
<div class="col-md-3"><div class="bs-sidebar hidden-print affix well" role="complementary">
<ul class="nav bs-sidenav">
<li class="first-level active"><a href="#_1">异步确保</a></li>
</ul>
</div></div>
<div class="col-md-9" role="main">
<p>针对于分布式事务的处理,CAP 采用的是“异步确保”这种方案。</p>
<h3 id="_1">异步确保</h3>
<p>异步确保这种方案又叫做本地消息表,这是一种经典的方案,方案最初来源于 eBay,参考资料见段末链接。这种方案目前也是企业中使用最多的方案之一。</p>
<p>相对于 TCC 或者 2PC/3PC 来说,这个方案对于分布式事务来说是最简单的,而且它是去中心化的。在TCC 或者 2PC 的方案中,必须具有事务协调器来处理每个不同服务之间的状态,而此种方案不需要事务协调器。
另外 2PC/TCC 这种方案如果服务依赖过多,会带来管理复杂性增加和稳定性风险增大的问题。试想如果我们强依赖 10 个服务,9 个都执行成功了,最后一个执行失败了,那么是不是前面 9 个都要回滚掉?这个成本还是非常高的。</p>
<p>但是,并不是说 2PC 或者 TCC 这种方案不好,因为每一种方案都有其相对优势的使用场景和优缺点,这里就不做过多介绍了。</p>
<blockquote>
<p>中文:<a href="http://www.cnblogs.com/savorboard/p/base-an-acid-alternative.html">http://www.cnblogs.com/savorboard/p/base-an-acid-alternative.html</a><br />
英文:<a href="http://queue.acm.org/detail.cfm?id=1394128">http://queue.acm.org/detail.cfm?id=1394128</a></p>
</blockquote></div>
</div>
<footer class="col-md-12 text-center">
<hr>
<p>
<small>Documentation built with <a href="http://www.mkdocs.org/">MkDocs</a>.</p></small>
</footer>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="../../js/bootstrap-3.0.3.min.js"></script>
<script src="../../js/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script>
var base_url = '../..';
</script>
<!-- <script data-main="../../mkdocs/js/search.js" src="../../mkdocs/js/require.js"></script> -->
<script src="../../js/base.js"></script>
<script src="../../search/main.js"></script>
<div class="modal" id="mkdocs_search_modal" tabindex="-1" role="dialog" aria-labelledby="Search Modal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span>
<span class="sr-only">Close</span>
</button>
<h4 class="modal-title" id="exampleModalLabel">Search</h4>
</div>
<div class="modal-body">
<p>
From here you can search these documents. Enter your search terms below.
</p>
<form role="form">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search..." id="mkdocs-search-query">
</div>
</form>
<div id="mkdocs-search-results"></div>
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="../../img/favicon.ico">
<title>FAQ - CAP</title>
<link rel="stylesheet" href="//use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
<link rel="stylesheet" href="//cdn.jsdelivr.net/font-hack/3.003/css/hack.min.css">
<link href='//fonts.googleapis.com/css?family=PT+Sans:400,400italic,700,700italic&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href='//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,700italic,400,300,600,700&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href="../../css/bootstrap-custom.min.css" rel="stylesheet">
<link href="../../css/base.min.css" rel="stylesheet">
<link href="../../css/cinder.min.css" rel="stylesheet">
<link rel="stylesheet" href="../../css/highlight.min.css">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<![endif]-->
<script src="//ajax.googleapis.com/ajax/libs/webfont/1.6.28/webfont.js"></script>
<script>
WebFont.load({
google: {
families: ['Open Sans', 'PT Sans']
}
});
</script>
</head>
<body>
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<!-- Collapsed navigation -->
<div class="navbar-header">
<!-- Expander button -->
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- Main title -->
<a class="navbar-brand" href="../..">CAP</a>
</div>
<!-- Expanded navigation -->
<div class="navbar-collapse collapse">
<!-- Main navigation -->
<ul class="nav navbar-nav">
<li >
<a href="../..">Home</a>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/us/flat/16.png">User Guide <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../../user-guide/getting-started/">Getting Started</a>
</li>
<li >
<a href="../../user-guide/api-interface/">API Interface</a>
</li>
<li >
<a href="../../user-guide/configuration/">Configuration</a>
</li>
<li >
<a href="../../user-guide/design-principle.md.md">Design Principle</a>
</li>
<li >
<a href="../../user-guide/implementation-mechanisms/">Implementation Mechanisms</a>
</li>
<li >
<a href="../../user-guide/distributed-transactions/">Distributed Transactions</a>
</li>
<li >
<a href="../../user-guide/faq/">FAQ</a>
</li>
</ul>
</li>
<li class="dropdown active">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/cn/flat/16.png">使用指南 <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../getting-started/">快速开始</a>
</li>
<li >
<a href="../api-interface/">API 接口</a>
</li>
<li >
<a href="../configuration/">配置</a>
</li>
<li >
<a href="../design-principle/">设计原理</a>
</li>
<li >
<a href="../implementation-mechanisms/">实现</a>
</li>
<li >
<a href="../distributed-transactions/">分布式事务</a>
</li>
<li class="active">
<a href="./">FAQ</a>
</li>
</ul>
</li>
<li >
<a href="../../about/">About</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<a href="#" data-toggle="modal" data-target="#mkdocs_search_modal">
<i class="fas fa-search"></i> Search
</a>
</li>
<li >
<a rel="next" href="../distributed-transactions/">
<i class="fas fa-arrow-left"></i> Previous
</a>
</li>
<li >
<a rel="prev" href="../../about/">
Next <i class="fas fa-arrow-right"></i>
</a>
</li>
<li>
<a href="https://github.com/dotnetcore/CAP/edit/master/docs/user-guide-cn/faq.md"><i class="fab fa-github"></i> Edit on GitHub</a>
</li>
</ul>
</div>
</div>
</div>
<div class="container">
<div class="col-md-3"><div class="bs-sidebar hidden-print affix well" role="complementary">
<ul class="nav bs-sidenav">
</ul>
</div></div>
<div class="col-md-9" role="main">
<p><strong>1、有CAP学习QQ群吗?</strong></p>
<p>CAP没有群。
原因是我希望培养大家独立思考和遇到问题时候的解决能力。
使用时候遇到问题先尝试看文档和独立解决,如果解决不了,可以提ISSUE或者发邮件。
QQ群有效沟通太低,浪费时间。</p>
<p><strong>2、CAP要求发送者与接收者必须使用不同的数据库吗?</strong></p>
<p>没有要求,要求不同的实例使用不同的数据库。不同实例的意思为,不同代码的两套程序。</p>
<p>但是如果你真的需要在不同的实例使用相同的数据库,那么可以参考下面3的答案。</p>
<p><strong>3、CAP如何在不同的实例中使用相同的数据库?</strong></p>
<p>如果想在不同的实例(程序)中连接相同的数据库,那么你可以在配置CAP的时候通过指定不同的数据库表名前缀来实现。</p>
<p>你可以通过以下方式来指定数据库表名前缀:</p>
<pre><code class="cs">public void ConfigureServices(IServiceCollection services)
{
services.AddCap(x =&gt;
{
x.UseKafka(&quot;&quot;);
x.UseMySql(opt =&gt;
{
opt.ConnectionString = &quot;connection string&quot;;
opt.TableNamePrefix = &quot;appone&quot;; // 在这里配置不同的实例使用的表名前缀
});
});
}
</code></pre>
<p>注意:相同的实例不需要指定不同的表名称前缀,他们在接收消息的时候会进行负载均衡。</p>
<p><strong>4、CAP可以不使用数据库吗? 我仅仅是想通过她来传递消息,我可以接受消息丢失的情况</strong> </p>
<p>目前是不可以的。 </p>
<p>CAP 的设计目标即为在不同的微服务或者SOA系统中来保持数据一致性的一种解决方案,保证这种数据一致性方案的前提是利用了传统数据库的 ACID 特性,如果离开了数据库,那么CAP仅仅是一个消息队列的客户端封装,这没有任何意义。</p>
<p><strong>5、使用CAP时候,业务出现错误怎么回滚?</strong> </p>
<p>不能回滚,CAP是最终一致性的方案。</p>
<p>你可以想象你的场景为在调用第三方支付,假如你在进行一项第三方支付的操作,调用支付宝的接口成功后,而你自己的代码出现错误了,支付宝会回滚吗? 如果不回滚那么是又应该怎么处理呢? 这里也是同理。</p></div>
</div>
<footer class="col-md-12 text-center">
<hr>
<p>
<small>Documentation built with <a href="http://www.mkdocs.org/">MkDocs</a>.</p></small>
</footer>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="../../js/bootstrap-3.0.3.min.js"></script>
<script src="../../js/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script>
var base_url = '../..';
</script>
<!-- <script data-main="../../mkdocs/js/search.js" src="../../mkdocs/js/require.js"></script> -->
<script src="../../js/base.js"></script>
<script src="../../search/main.js"></script>
<div class="modal" id="mkdocs_search_modal" tabindex="-1" role="dialog" aria-labelledby="Search Modal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span>
<span class="sr-only">Close</span>
</button>
<h4 class="modal-title" id="exampleModalLabel">Search</h4>
</div>
<div class="modal-body">
<p>
From here you can search these documents. Enter your search terms below.
</p>
<form role="form">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search..." id="mkdocs-search-query">
</div>
</form>
<div id="mkdocs-search-results"></div>
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="../../img/favicon.ico">
<title>快速开始 - CAP</title>
<link rel="stylesheet" href="//use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
<link rel="stylesheet" href="//cdn.jsdelivr.net/font-hack/3.003/css/hack.min.css">
<link href='//fonts.googleapis.com/css?family=PT+Sans:400,400italic,700,700italic&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href='//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,700italic,400,300,600,700&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href="../../css/bootstrap-custom.min.css" rel="stylesheet">
<link href="../../css/base.min.css" rel="stylesheet">
<link href="../../css/cinder.min.css" rel="stylesheet">
<link rel="stylesheet" href="../../css/highlight.min.css">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<![endif]-->
<script src="//ajax.googleapis.com/ajax/libs/webfont/1.6.28/webfont.js"></script>
<script>
WebFont.load({
google: {
families: ['Open Sans', 'PT Sans']
}
});
</script>
</head>
<body>
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<!-- Collapsed navigation -->
<div class="navbar-header">
<!-- Expander button -->
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- Main title -->
<a class="navbar-brand" href="../..">CAP</a>
</div>
<!-- Expanded navigation -->
<div class="navbar-collapse collapse">
<!-- Main navigation -->
<ul class="nav navbar-nav">
<li >
<a href="../..">Home</a>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/us/flat/16.png">User Guide <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../../user-guide/getting-started/">Getting Started</a>
</li>
<li >
<a href="../../user-guide/api-interface/">API Interface</a>
</li>
<li >
<a href="../../user-guide/configuration/">Configuration</a>
</li>
<li >
<a href="../../user-guide/design-principle.md.md">Design Principle</a>
</li>
<li >
<a href="../../user-guide/implementation-mechanisms/">Implementation Mechanisms</a>
</li>
<li >
<a href="../../user-guide/distributed-transactions/">Distributed Transactions</a>
</li>
<li >
<a href="../../user-guide/faq/">FAQ</a>
</li>
</ul>
</li>
<li class="dropdown active">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/cn/flat/16.png">使用指南 <b class="caret"></b></a>
<ul class="dropdown-menu">
<li class="active">
<a href="./">快速开始</a>
</li>
<li >
<a href="../api-interface/">API 接口</a>
</li>
<li >
<a href="../configuration/">配置</a>
</li>
<li >
<a href="../design-principle/">设计原理</a>
</li>
<li >
<a href="../implementation-mechanisms/">实现</a>
</li>
<li >
<a href="../distributed-transactions/">分布式事务</a>
</li>
<li >
<a href="../faq/">FAQ</a>
</li>
</ul>
</li>
<li >
<a href="../../about/">About</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<a href="#" data-toggle="modal" data-target="#mkdocs_search_modal">
<i class="fas fa-search"></i> Search
</a>
</li>
<li >
<a rel="next" href="../../user-guide/faq/">
<i class="fas fa-arrow-left"></i> Previous
</a>
</li>
<li >
<a rel="prev" href="../api-interface/">
Next <i class="fas fa-arrow-right"></i>
</a>
</li>
<li>
<a href="https://github.com/dotnetcore/CAP/edit/master/docs/user-guide-cn/getting-started.md"><i class="fab fa-github"></i> Edit on GitHub</a>
</li>
</ul>
</div>
</div>
</div>
<div class="container">
<div class="col-md-3"><div class="bs-sidebar hidden-print affix well" role="complementary">
<ul class="nav bs-sidenav">
<li class="first-level active"><a href="#_1">介绍</a></li>
<li class="first-level "><a href="#_2">应用场景</a></li>
<li class="first-level "><a href="#quick-start">Quick Start</a></li>
</ul>
</div></div>
<div class="col-md-9" role="main">
<h3 id="_1">介绍</h3>
<p>CAP 是一个遵循 .NET Standard 标准库的C#库,用来处理分布式事务以及提供EventBus的功能,她具有轻量级,高性能,易使用等特点。</p>
<p>目前 CAP 使用的是 .NET Standard 1.6 的标准进行开发,目前最新预览版本已经支持 .NET Standard 2.0.</p>
<h3 id="_2">应用场景</h3>
<p>CAP 的应用场景主要有以下两个:</p>
<ul>
<li>
<ol>
<li>分布式事务中的最终一致性(异步确保)的方案。</li>
</ol>
</li>
</ul>
<p>分布式事务是在分布式系统中不可避免的一个硬性需求,而目前的分布式事务的解决方案也无外乎就那么几种,在了解 CAP 的分布式事务方案前,可以阅读以下 <a href="http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency">这篇文章</a></p>
<p>CAP 没有采用两阶段提交(2PC)这种事务机制,而是采用的 本地消息表+MQ 这种经典的实现方式,这种方式又叫做 异步确保。</p>
<ul>
<li>
<ol>
<li>具有高可用性的 EventBus。</li>
</ol>
</li>
</ul>
<p>CAP 实现了 EventBus 中的发布/订阅,它具有 EventBus 的所有功能。也就是说你可以像使用 EventBus 一样来使用 CAP,另外 CAP 的 EventBus 是具有高可用性的,这是什么意思呢?</p>
<p>CAP 借助于本地消息表来对 EventBus 中的消息进行了持久化,这样可以保证 EventBus 发出的消息是可靠的,当消息队列出现宕机或者连接失败的情况时,消息也不会丢失。</p>
<h3 id="quick-start">Quick Start</h3>
<ul>
<li><strong>引用 NuGet 包</strong></li>
</ul>
<p>使用一下命令来引用CAP的NuGet包:</p>
<pre><code>PM&gt; Install-Package DotNetCore.CAP
</code></pre>
<p>根据使用的不同类型的消息队列,来引入不同的扩展包:</p>
<pre><code>PM&gt; Install-Package DotNetCore.CAP.RabbitMQ
PM&gt; Install-Package DotNetCore.CAP.Kafka
</code></pre>
<p>根据使用的不同类型的数据库,来引入不同的扩展包:</p>
<pre><code>PM&gt; Install-Package DotNetCore.CAP.SqlServer
PM&gt; Install-Package DotNetCore.CAP.MySql
PM&gt; Install-Package DotNetCore.CAP.PostgreSql
PM&gt; Install-Package DotNetCore.CAP.MongoDB
</code></pre>
<ul>
<li><strong>启动配置</strong></li>
</ul>
<p>在 ASP.NET Core 程序中,你可以在 <code>Startup.cs</code> 文件 <code>ConfigureServices()</code> 中配置 CAP 使用到的服务:</p>
<pre><code class="cs">public void ConfigureServices(IServiceCollection services)
{
//......
services.AddDbContext&lt;AppDbContext&gt;(); //Options, If you are using EF as the ORM
services.AddSingleton&lt;IMongoClient&gt;(new MongoClient(&quot;&quot;)); //Options, If you are using MongoDB
services.AddCap(x =&gt;
{
// If you are using EF, you need to add the configuration:
x.UseEntityFramework&lt;AppDbContext&gt;(); //Options, Notice: You don't need to config x.UseSqlServer(&quot;&quot;&quot;) again! CAP can autodiscovery.
// If you are using Ado.Net, you need to add the configuration:
x.UseSqlServer(&quot;Your ConnectionStrings&quot;);
x.UseMySql(&quot;Your ConnectionStrings&quot;);
x.UsePostgreSql(&quot;Your ConnectionStrings&quot;);
// If you are using MongoDB, you need to add the configuration:
x.UseMongoDB(&quot;Your ConnectionStrings&quot;); //MongoDB 4.0+ cluster
// If you are using RabbitMQ, you need to add the configuration:
x.UseRabbitMQ(&quot;localhost&quot;);
// If you are using Kafka, you need to add the configuration:
x.UseKafka(&quot;localhost&quot;);
});
}
</code></pre></div>
</div>
<footer class="col-md-12 text-center">
<hr>
<p>
<small>Documentation built with <a href="http://www.mkdocs.org/">MkDocs</a>.</p></small>
</footer>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="../../js/bootstrap-3.0.3.min.js"></script>
<script src="../../js/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script>
var base_url = '../..';
</script>
<!-- <script data-main="../../mkdocs/js/search.js" src="../../mkdocs/js/require.js"></script> -->
<script src="../../js/base.js"></script>
<script src="../../search/main.js"></script>
<div class="modal" id="mkdocs_search_modal" tabindex="-1" role="dialog" aria-labelledby="Search Modal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span>
<span class="sr-only">Close</span>
</button>
<h4 class="modal-title" id="exampleModalLabel">Search</h4>
</div>
<div class="modal-body">
<p>
From here you can search these documents. Enter your search terms below.
</p>
<form role="form">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search..." id="mkdocs-search-query">
</div>
</form>
<div id="mkdocs-search-results"></div>
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="../../img/favicon.ico">
<title>实现 - CAP</title>
<link rel="stylesheet" href="//use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
<link rel="stylesheet" href="//cdn.jsdelivr.net/font-hack/3.003/css/hack.min.css">
<link href='//fonts.googleapis.com/css?family=PT+Sans:400,400italic,700,700italic&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href='//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,700italic,400,300,600,700&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href="../../css/bootstrap-custom.min.css" rel="stylesheet">
<link href="../../css/base.min.css" rel="stylesheet">
<link href="../../css/cinder.min.css" rel="stylesheet">
<link rel="stylesheet" href="../../css/highlight.min.css">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<![endif]-->
<script src="//ajax.googleapis.com/ajax/libs/webfont/1.6.28/webfont.js"></script>
<script>
WebFont.load({
google: {
families: ['Open Sans', 'PT Sans']
}
});
</script>
</head>
<body>
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<!-- Collapsed navigation -->
<div class="navbar-header">
<!-- Expander button -->
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- Main title -->
<a class="navbar-brand" href="../..">CAP</a>
</div>
<!-- Expanded navigation -->
<div class="navbar-collapse collapse">
<!-- Main navigation -->
<ul class="nav navbar-nav">
<li >
<a href="../..">Home</a>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/us/flat/16.png">User Guide <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../../user-guide/getting-started/">Getting Started</a>
</li>
<li >
<a href="../../user-guide/api-interface/">API Interface</a>
</li>
<li >
<a href="../../user-guide/configuration/">Configuration</a>
</li>
<li >
<a href="../../user-guide/design-principle.md.md">Design Principle</a>
</li>
<li >
<a href="../../user-guide/implementation-mechanisms/">Implementation Mechanisms</a>
</li>
<li >
<a href="../../user-guide/distributed-transactions/">Distributed Transactions</a>
</li>
<li >
<a href="../../user-guide/faq/">FAQ</a>
</li>
</ul>
</li>
<li class="dropdown active">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/cn/flat/16.png">使用指南 <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../getting-started/">快速开始</a>
</li>
<li >
<a href="../api-interface/">API 接口</a>
</li>
<li >
<a href="../configuration/">配置</a>
</li>
<li >
<a href="../design-principle/">设计原理</a>
</li>
<li class="active">
<a href="./">实现</a>
</li>
<li >
<a href="../distributed-transactions/">分布式事务</a>
</li>
<li >
<a href="../faq/">FAQ</a>
</li>
</ul>
</li>
<li >
<a href="../../about/">About</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<a href="#" data-toggle="modal" data-target="#mkdocs_search_modal">
<i class="fas fa-search"></i> Search
</a>
</li>
<li >
<a rel="next" href="../design-principle/">
<i class="fas fa-arrow-left"></i> Previous
</a>
</li>
<li >
<a rel="prev" href="../distributed-transactions/">
Next <i class="fas fa-arrow-right"></i>
</a>
</li>
<li>
<a href="https://github.com/dotnetcore/CAP/edit/master/docs/user-guide-cn/implementation-mechanisms.md"><i class="fab fa-github"></i> Edit on GitHub</a>
</li>
</ul>
</div>
</div>
</div>
<div class="container">
<div class="col-md-3"><div class="bs-sidebar hidden-print affix well" role="complementary">
<ul class="nav bs-sidenav">
<li class="first-level active"><a href="#_1">消息表</a></li>
<li class="first-level "><a href="#_2">消息格式</a></li>
<li class="first-level "><a href="#eventbus">EventBus</a></li>
<li class="first-level "><a href="#_3">重试</a></li>
<li class="first-level "><a href="#_4">数据清理</a></li>
</ul>
</div></div>
<div class="col-md-9" role="main">
<p>CAP 封装了在 ASP.NET Core 中的使用依赖注入来获取 Publisher (<code>ICapPublisher</code>)的接口。而启动方式类似于 “中间件” 的形式,通过在 Startup.cs 配置 <code>ConfigureServices</code><code>Configure</code> 进行启动。</p>
<h3 id="_1">消息表</h3>
<p>当系统引入CAP之后并首次启动后,CAP会在客户端生成 2 个表,分别是 Cap.Published, Cap.Received 。注意表名可能在不同的数据库具有不同的大小写区分,如果你在运行项目的时候没有显式的指定数据库生成架构(Schema)或者表名前缀(TableNamePrefix)的话,默认情况下就是以上的名字。</p>
<p><strong>Cap.Published</strong>:这个表主要是用来存储 CAP 发送到MQ(Message Queue)的客户端消息,也就是说你使用 <code>ICapPublisher</code> 接口 Publish 的消息内容。</p>
<p><strong>Cap.Received</strong>:这个表主要是用来存储 CAP 接收到 MQ(Message Queue) 的客户端订阅的消息,也就是使用 <code>CapSubscribe[]</code> 订阅的那些消息。</p>
<blockquote>
<p>2.2 版本以前:
<strong>Cap.Queue</strong>: 这个表主要是CAP内部用来处理发送和接收消息的一个临时表,通常情况下,如果系统不出现问题,这个表将是空的。</p>
</blockquote>
<p><code>Published</code><code>Received</code> 表具有 StatusName 字段,这个字段用来标识当前消息的状态。目前共有 <code>Scheduled</code><code>Successed</code><code>Failed</code> 等几个状态。</p>
<blockquote>
<p>在 2.2 版本以前的所有状态为:<code>Scheduled</code><code>Enqueued</code><code>Processing</code><code>Successed</code><code>Failed</code> </p>
</blockquote>
<p>CAP 在处理消息的过程中会依次从<code>Scheduled</code><code>Successed</code> 来改变这些消息状态的值。如果是状态值为 <code>Successed</code>,代表该消息已经成功的发送到了 MQ 中。如果为 Failed 则代表消息发送失败。</p>
<p>CAP 2.2 以上版本中会针对 <code>Scheduled</code>,<code>Failed</code> 状态的消息 CAP 会于消息持久化过后 4 分钟后开始进行重试,重试的间隔默认为 60 秒,你可以在 <code>CapOptions</code> 中配置的<code>FailedRetryInterval</code> 来调整默认间隔时间。</p>
<blockquote>
<p>2.2 版本以前, CAP 会对状态为 <code>Failed</code> 的消息默认进行 100 次重试。</p>
</blockquote>
<h3 id="_2">消息格式</h3>
<p>CAP 采用 JSON 格式进行消息传输,以下是消息的对象模型:</p>
<table>
<thead>
<tr>
<th align="left">NAME</th>
<th align="left">DESCRIPTION</th>
<th align="left">TYPE</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">Id</td>
<td align="left">消息编号</td>
<td align="left">int</td>
</tr>
<tr>
<td align="left">Version</td>
<td align="left">消息版本</td>
<td align="left">string</td>
</tr>
<tr>
<td align="left">Name</td>
<td align="left">消息名称</td>
<td align="left">string</td>
</tr>
<tr>
<td align="left">Content</td>
<td align="left">内容</td>
<td align="left">string</td>
</tr>
<tr>
<td align="left">Group</td>
<td align="left">所属消费组</td>
<td align="left">string</td>
</tr>
<tr>
<td align="left">Added</td>
<td align="left"> 创建时间</td>
<td align="left">DateTime</td>
</tr>
<tr>
<td align="left">ExpiresAt</td>
<td align="left">过期时间</td>
<td align="left">DateTime</td>
</tr>
<tr>
<td align="left">Retries</td>
<td align="left">重试次数</td>
<td align="left">int</td>
</tr>
<tr>
<td align="left">StatusName</td>
<td align="left">状态</td>
<td align="left">string</td>
</tr>
</tbody>
</table>
<blockquote>
<p>对于 Cap.Received 中的消息,会多一个 <code>Group</code> 字段来标记所属的消费者组。</p>
</blockquote>
<p>对于消息内容 Content 属性里面的内容CAP 使用 Message 对象进行了一次二次包装。一下为Message对象的信息</p>
<table>
<thead>
<tr>
<th align="left">NAME</th>
<th align="left">DESCRIPTION</th>
<th align="left">TYPE</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">Id</td>
<td align="left">CAP生成的消息编号</td>
<td align="left">string</td>
</tr>
<tr>
<td align="left">Timestamp</td>
<td align="left">消息创建时间</td>
<td align="left">string</td>
</tr>
<tr>
<td align="left">Content</td>
<td align="left">内容</td>
<td align="left">string</td>
</tr>
<tr>
<td align="left">CallbackName</td>
<td align="left">回调的订阅者名称</td>
<td align="left">string</td>
</tr>
</tbody>
</table>
<p>其中 Id 字段,CAP 采用的 MongoDB 中的 ObjectId 分布式Id生成算法生成。</p>
<h3 id="eventbus">EventBus</h3>
<p>EventBus 采用 发布-订阅 风格进行组件之间的通讯,它不需要显式在组件中进行注册。</p>
<p><img alt="" src="http://images2017.cnblogs.com/blog/250417/201708/250417-20170804153901240-1774287236.png" /></p>
<p>上图是EventBus的一个Event的流程,关于 EventBus 的更多信息就不在这里介绍了...</p>
<p>在 CAP 中,为什么说 CAP 实现了 EventBus 中的全部特性,因为 EventBus 具有的两个大功能就是发布和订阅, 在 CAP 中 使用了另外一种优雅的方式来实现的,另外一个 CAP 提供的强大功能就是消息的持久化,以及在任何异常情况下消息的可靠性,这是EventBus不具有的功能。</p>
<p><img alt="" src="https://camo.githubusercontent.com/452505edb71d41f2c1bd18907275b76291621e46/687474703a2f2f696d61676573323031352e636e626c6f67732e636f6d2f626c6f672f3235303431372f3230313730372f3235303431372d32303137303730353137353832373132382d313230333239313436392e706e67" /></p>
<p>CAP 里面发送一个消息可以看做是一个 “Event”,一个使用了CAP的ASP.NET Core 应用程序既可以进行发送也可以进行订阅接收。</p>
<h3 id="_3">重试</h3>
<p>重试在整个CAP架构设计中具有重要作用,CAP 中会针对发送失败或者执行失败的消息进行重试。在整个 CAP 的设计过程中有以下几处采用的重试策略。</p>
<p><strong>1、 发送重试</strong></p>
<p>在消息发送过程中,当出现 Broker 宕机或者连接失败的情况亦或者出现异常的情况下,这个时候 CAP 会对发送的重试,第一次重试次数为 3, 以后每分钟重试一次,进行次数 +1,当总次数达到50次后,CAP将不对其进行重试。</p>
<blockquote>
<p>你可以在 <code>CapOptions</code> 中设置<code>FailedRetryCount</code>来调整默认重试的总次数。</p>
</blockquote>
<p>当失败总次数达到默认失败总次数后,就不会进行重试了,你可以在 Dashboard 中查看消息失败的原因,然后进行人工重试处理。</p>
<p><strong>2、 消费重试</strong></p>
<p>当 Consumer 接收到消息时,会执行消费者方法,在执行消费者方法出现异常时,会进行重试。这个重试策略和上面的 <code>发送重试</code> 是相同的。</p>
<h3 id="_4">数据清理</h3>
<p>数据库消息表中具有一个 <code>ExpiresAt</code> 字段表示消息的过期时间,当消息发送成功或者消费成功后,CAP会将消息状态为 <code>Successed</code><code>ExpiresAt</code> 设置为 <strong>1小时</strong> 后过期,会将消息状态为 <code>Failed</code><code>ExpiresAt</code> 设置为 <strong>15天</strong> 后过期。</p>
<p>CAP 默认情况下会每隔一个小时将消息表的数据进行清理删除,避免数据量过多导致性能的降低。清理规则为 ExpiresAt 不为空并且小于当前时间的数据。 也就是说状态为<code>Failed</code>的消息(正常情况他们已经被重试了 50 次),如果你15天没有人工介入处理,同样会被清理掉。</p></div>
</div>
<footer class="col-md-12 text-center">
<hr>
<p>
<small>Documentation built with <a href="http://www.mkdocs.org/">MkDocs</a>.</p></small>
</footer>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="../../js/bootstrap-3.0.3.min.js"></script>
<script src="../../js/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script>
var base_url = '../..';
</script>
<!-- <script data-main="../../mkdocs/js/search.js" src="../../mkdocs/js/require.js"></script> -->
<script src="../../js/base.js"></script>
<script src="../../search/main.js"></script>
<div class="modal" id="mkdocs_search_modal" tabindex="-1" role="dialog" aria-labelledby="Search Modal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span>
<span class="sr-only">Close</span>
</button>
<h4 class="modal-title" id="exampleModalLabel">Search</h4>
</div>
<div class="modal-body">
<p>
From here you can search these documents. Enter your search terms below.
</p>
<form role="form">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search..." id="mkdocs-search-query">
</div>
</form>
<div id="mkdocs-search-results"></div>
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="../../img/favicon.ico">
<title>API Interface - CAP</title>
<link rel="stylesheet" href="//use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
<link rel="stylesheet" href="//cdn.jsdelivr.net/font-hack/3.003/css/hack.min.css">
<link href='//fonts.googleapis.com/css?family=PT+Sans:400,400italic,700,700italic&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href='//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,700italic,400,300,600,700&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href="../../css/bootstrap-custom.min.css" rel="stylesheet">
<link href="../../css/base.min.css" rel="stylesheet">
<link href="../../css/cinder.min.css" rel="stylesheet">
<link rel="stylesheet" href="../../css/highlight.min.css">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<![endif]-->
<script src="//ajax.googleapis.com/ajax/libs/webfont/1.6.28/webfont.js"></script>
<script>
WebFont.load({
google: {
families: ['Open Sans', 'PT Sans']
}
});
</script>
</head>
<body>
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<!-- Collapsed navigation -->
<div class="navbar-header">
<!-- Expander button -->
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- Main title -->
<a class="navbar-brand" href="../..">CAP</a>
</div>
<!-- Expanded navigation -->
<div class="navbar-collapse collapse">
<!-- Main navigation -->
<ul class="nav navbar-nav">
<li >
<a href="../..">Home</a>
</li>
<li class="dropdown active">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/us/flat/16.png">User Guide <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../getting-started/">Getting Started</a>
</li>
<li class="active">
<a href="./">API Interface</a>
</li>
<li >
<a href="../configuration/">Configuration</a>
</li>
<li >
<a href="../design-principle.md.md">Design Principle</a>
</li>
<li >
<a href="../implementation-mechanisms/">Implementation Mechanisms</a>
</li>
<li >
<a href="../distributed-transactions/">Distributed Transactions</a>
</li>
<li >
<a href="../faq/">FAQ</a>
</li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/cn/flat/16.png">使用指南 <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../../user-guide-cn/getting-started/">快速开始</a>
</li>
<li >
<a href="../../user-guide-cn/api-interface/">API 接口</a>
</li>
<li >
<a href="../../user-guide-cn/configuration/">配置</a>
</li>
<li >
<a href="../../user-guide-cn/design-principle/">设计原理</a>
</li>
<li >
<a href="../../user-guide-cn/implementation-mechanisms/">实现</a>
</li>
<li >
<a href="../../user-guide-cn/distributed-transactions/">分布式事务</a>
</li>
<li >
<a href="../../user-guide-cn/faq/">FAQ</a>
</li>
</ul>
</li>
<li >
<a href="../../about/">About</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<a href="#" data-toggle="modal" data-target="#mkdocs_search_modal">
<i class="fas fa-search"></i> Search
</a>
</li>
<li >
<a rel="next" href="../getting-started/">
<i class="fas fa-arrow-left"></i> Previous
</a>
</li>
<li >
<a rel="prev" href="../configuration/">
Next <i class="fas fa-arrow-right"></i>
</a>
</li>
<li>
<a href="https://github.com/dotnetcore/CAP/edit/master/docs/user-guide/api-interface.md"><i class="fab fa-github"></i> Edit on GitHub</a>
</li>
</ul>
</div>
</div>
</div>
<div class="container">
<div class="col-md-3"><div class="bs-sidebar hidden-print affix well" role="complementary">
<ul class="nav bs-sidenav">
<li class="first-level active"><a href="#interfaces">Interfaces</a></li>
<li class="second-level"><a href="#publishsend">Publish/Send</a></li>
<li class="third-level"><a href="#transactions">Transactions</a></li>
<li class="second-level"><a href="#subscribeconsume">Subscribe/Consume</a></li>
<li class="third-level"><a href="#exceptional-case">Exceptional case</a></li>
</ul>
</div></div>
<div class="col-md-9" role="main">
<h2 id="interfaces">Interfaces</h2>
<p>CAP only has one interface,It is <code>ICapPublisher</code>,You can get its instance from the DI container and then call it.</p>
<h3 id="publishsend">Publish/Send</h3>
<p>You can use the <code>Publish&lt;T&gt;</code> or <code>PublishAsync&lt;T&gt;</code> methods defined in the <code>ICapPublisher</code> interface to send the event messages.</p>
<pre><code class="cs">public class PublishController : Controller
{
private readonly ICapPublisher _capBus;
public PublishController(ICapPublisher capPublisher)
{
_capBus = capPublisher;
}
[Route(&quot;~/adonet/transaction&quot;)]
public IActionResult AdonetWithTransaction()
{
using (var connection = new MySqlConnection(ConnectionString))
{
using (var transaction = connection.BeginTransaction(_capBus, autoCommit: true))
{
//your business code
_capBus.Publish(&quot;xxx.services.show.time&quot;, DateTime.Now);
}
}
return Ok();
}
[Route(&quot;~/ef/transaction&quot;)]
public IActionResult EntityFrameworkWithTransaction([FromServices]AppDbContext dbContext)
{
using (var trans = dbContext.Database.BeginTransaction(_capBus, autoCommit: true))
{
//your business code
_capBus.Publish(&quot;xxx.services.show.time&quot;, DateTime.Now);
}
return Ok();
}
}
</code></pre>
<p>The following is the signature of the of the PublishAsync method</p>
<p><strong><code>PublishAsync&lt;T&gt;(string name,T object)</code></strong></p>
<p>By default,when this method(PublishAsync<T>) is called,CAP will create a transaction internally,
and then write messages into the <code>Cap.Published</code> message table.</p>
<p>In some situations,you may need a callback when a message is sent out, you can use the follwing
overload of the <code>PublishAsync&lt;T&gt;</code> method:</p>
<p><strong><code>PublishAsync&lt;T&gt;(string name,T object, string callBackName)</code></strong></p>
<p>In this overload method,<code>callbackName</code> is the callback name of the subscription method,when the consumption-side finished processing messages,CAP will return the processed result and also call the specified subscription method</p>
<h4 id="transactions">Transactions</h4>
<p>Transaction plays a very import role in CAP, It is a main factor to ensure the reliability of messaging. </p>
<p>In the process of sending a message to the message queue without transaction we can not ensure that messages are sent to the message queue successfully after we finish dealing the business logic,or messages are send to the message queque successfully but our bussiness logic is failed.</p>
<p>There is a variety of reasons that causing failure,eg:connection errors,network errors,etc.</p>
<p><em>Only by putting the business logic and logic in the Publish of CAP in the same transaction so that we can enssure both them to be success or fail</em></p>
<p>The following two blocks of code snippet demonstrate how to use transactions in EntityFramework and dapper when publishing messages.</p>
<ul>
<li>EntityFramework</li>
</ul>
<pre><code class="cs"> using (var trans = dbContext.Database.BeginTransaction(_capBus, autoCommit: false)
{
// Your business logic。
_capBus.Publish(&quot;xxx.services.show.time&quot;, DateTime.Now);
trans.Commit();
}
</code></pre>
<p>When you set the <code>autoCommit: false</code>, you can put your business logic before or after the Publish logic,the only thing you need to do is to ensure that they are in the same transaction.</p>
<p>If you set the <code>autoCommit: true</code>, you need publish message <code>_capBus.Publish</code> at the last.</p>
<p>During the course,the message content will be serialized as json and stored in the message table.</p>
<ul>
<li>Dapper</li>
</ul>
<pre><code class="cs">using (var connection = new MySqlConnection(ConnectionString))
{
using (var transaction = connection.BeginTransaction(_capBus, autoCommit: false))
{
//your business code
connection.Execute(&quot;insert into test(name) values('test')&quot;, transaction: (IDbTransaction)transaction.DbTransaction);
_capBus.Publish(&quot;sample.rabbitmq.mysql&quot;, DateTime.Now);
transaction.Commit();
}
}
</code></pre>
<h3 id="subscribeconsume">Subscribe/Consume</h3>
<p><strong>NOTE: The businsess logics in the subscription side should be keep idempotent.</strong></p>
<p>Use <code>CapSubscribe[""]</code> to decorate a method so that it can subscribe messages published by CAP.</p>
<pre><code>[CapSubscribe(&quot;xxx.services.bar&quot;)]
public void BarMessageProcessor()
{
}
</code></pre>
<p>You can also use multiple <code>CapSubscribe[""]</code> to decorate a method so that you can subscribe messages from different sources accordingly.</p>
<pre><code>[CapSubscribe(&quot;xxx.services.bar&quot;)]
[CapSubscribe(&quot;xxx.services.foo&quot;)]
public void BarAndFooMessageProcessor()
{
}
</code></pre>
<p><code>xxx.services.bar</code> is the name of the message to be subscribed.And it has different name in different message queque Clients.for example,in kafka the name is called Topic Name and in RAbbitMQ it is called RouteKey.</p>
<blockquote>
<p>In RabbitMQ you can use regular expression in RouteKey:</p>
<p>*(Asterisk)stands for a single word.</p>
<p># (hash sign) standards for zero or more words.</p>
<p>See the following picture(P for Publisher,X for Exchange,C for consumer and Q for Queue)</p>
<p><img alt="" src="http://images2017.cnblogs.com/blog/250417/201708/250417-20170807093230268-283915002.png" /></p>
<p>In this example, we're going to send messages which all describe animals. The messages will be sent with a routing key that consists of three words (two dots). The first word in the routing key will describe a celerity, second a colour and third a species: "<celerity>.<colour>.<species>".</p>
<p>We created three bindings: Q1 is bound with binding key "<em>.orange.</em>" and Q2 with "<em>.</em>.rabbit" and "lazy.#".</p>
<p>These bindings can be summarised as:</p>
<p>Q1 is interested in all the orange animals.
Q2 wants to hear everything about rabbits, and everything about lazy animals.
A message with a routing key set to "quick.orange.rabbit" will be delivered to both queues. Message "lazy.orange.elephant" also will go to both of them. On the other hand "quick.orange.fox" will only go to the first queue, and "lazy.brown.fox" only to the second. "lazy.pink.rabbit" will be delivered to the second queue only once, even though it matches two bindings. "quick.brown.fox" doesn't match any binding so it will be discarded.</p>
<p>What happens if we break our contract and send a message with one or four words, like "orange" or "quick.orange.male.rabbit"? Well, these messages won't match any bindings and will be lost.</p>
<p>On the other hand "lazy.orange.male.rabbit", even though it has four words, will match the last binding and will be delivered to the second queue.</p>
</blockquote>
<p>In CAP,we called a method decorated by <code>CapSubscribe[]</code> a <strong>subscriber</strong>,you can group different subscribers.</p>
<p><strong>Group</strong> is a collection of subscribers,each group can have one or multiple consumers,but a subscriber can only belongs to a certain group(you can not put a subscriber into multiple groups).Messages subscribed by members in a certain group can only be consumed once.</p>
<p>If you do not specify any group when subscribing,CAP will put the subscriber into a default group named <code>cap.default.group</code></p>
<p>the following is a demo shows how to use group when subscribing.</p>
<pre><code class="cs">[CapSubscribe(&quot;xxx.services.foo&quot;, Group = &quot;moduleA&quot;)]
public void FooMessageProcessor()
{
}
</code></pre>
<h4 id="exceptional-case">Exceptional case</h4>
<p>The following situations you shoud be aware of.</p>
<p><strong>① the subscription side has not started yet when publishing a message</strong></p>
<p>Kafka:</p>
<p>In Kafka,published messages stored in the Persistent log files,so messages will not lost.when the subscription side started,it can still consume the message.</p>
<p>RabbitMQ:</p>
<p>In RabbitMQ,the application will create Persistent Exchange and Queue at the <strong>first start</strong>,CAP will create a new consumer queue for each consumer group,<strong>because the application started but the subscription side hasn's start yet so there has no queue,thus the message can not be persited,and the published messages will lost</strong></p>
<p>There are two ways to solve this <code>message lost</code> issue in RamitMQ:</p>
<p>i.Before the deployment of your application,you can create durable Exchange and Queue in RabbitMQ by hand,the default names them are (cap.default.topic, cap.default.group).</p>
<p>ii.Run all instances in advance to ensure that both Exchange and Queue are initialized.</p>
<p>It is highly recommanded that users adopt the second way,because it is easier to achieve.</p></div>
</div>
<footer class="col-md-12 text-center">
<hr>
<p>
<small>Documentation built with <a href="http://www.mkdocs.org/">MkDocs</a>.</p></small>
</footer>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="../../js/bootstrap-3.0.3.min.js"></script>
<script src="../../js/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script>
var base_url = '../..';
</script>
<!-- <script data-main="../../mkdocs/js/search.js" src="../../mkdocs/js/require.js"></script> -->
<script src="../../js/base.js"></script>
<script src="../../search/main.js"></script>
<div class="modal" id="mkdocs_search_modal" tabindex="-1" role="dialog" aria-labelledby="Search Modal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span>
<span class="sr-only">Close</span>
</button>
<h4 class="modal-title" id="exampleModalLabel">Search</h4>
</div>
<div class="modal-body">
<p>
From here you can search these documents. Enter your search terms below.
</p>
<form role="form">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search..." id="mkdocs-search-query">
</div>
</form>
<div id="mkdocs-search-results"></div>
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="../../img/favicon.ico">
<title>Configuration - CAP</title>
<link rel="stylesheet" href="//use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
<link rel="stylesheet" href="//cdn.jsdelivr.net/font-hack/3.003/css/hack.min.css">
<link href='//fonts.googleapis.com/css?family=PT+Sans:400,400italic,700,700italic&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href='//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,700italic,400,300,600,700&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href="../../css/bootstrap-custom.min.css" rel="stylesheet">
<link href="../../css/base.min.css" rel="stylesheet">
<link href="../../css/cinder.min.css" rel="stylesheet">
<link rel="stylesheet" href="../../css/highlight.min.css">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<![endif]-->
<script src="//ajax.googleapis.com/ajax/libs/webfont/1.6.28/webfont.js"></script>
<script>
WebFont.load({
google: {
families: ['Open Sans', 'PT Sans']
}
});
</script>
</head>
<body>
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<!-- Collapsed navigation -->
<div class="navbar-header">
<!-- Expander button -->
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- Main title -->
<a class="navbar-brand" href="../..">CAP</a>
</div>
<!-- Expanded navigation -->
<div class="navbar-collapse collapse">
<!-- Main navigation -->
<ul class="nav navbar-nav">
<li >
<a href="../..">Home</a>
</li>
<li class="dropdown active">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/us/flat/16.png">User Guide <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../getting-started/">Getting Started</a>
</li>
<li >
<a href="../api-interface/">API Interface</a>
</li>
<li class="active">
<a href="./">Configuration</a>
</li>
<li >
<a href="../design-principle.md.md">Design Principle</a>
</li>
<li >
<a href="../implementation-mechanisms/">Implementation Mechanisms</a>
</li>
<li >
<a href="../distributed-transactions/">Distributed Transactions</a>
</li>
<li >
<a href="../faq/">FAQ</a>
</li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/cn/flat/16.png">使用指南 <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../../user-guide-cn/getting-started/">快速开始</a>
</li>
<li >
<a href="../../user-guide-cn/api-interface/">API 接口</a>
</li>
<li >
<a href="../../user-guide-cn/configuration/">配置</a>
</li>
<li >
<a href="../../user-guide-cn/design-principle/">设计原理</a>
</li>
<li >
<a href="../../user-guide-cn/implementation-mechanisms/">实现</a>
</li>
<li >
<a href="../../user-guide-cn/distributed-transactions/">分布式事务</a>
</li>
<li >
<a href="../../user-guide-cn/faq/">FAQ</a>
</li>
</ul>
</li>
<li >
<a href="../../about/">About</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<a href="#" data-toggle="modal" data-target="#mkdocs_search_modal">
<i class="fas fa-search"></i> Search
</a>
</li>
<li >
<a rel="next" href="../api-interface/">
<i class="fas fa-arrow-left"></i> Previous
</a>
</li>
<li >
<a rel="prev" href="../implementation-mechanisms/">
Next <i class="fas fa-arrow-right"></i>
</a>
</li>
<li>
<a href="https://github.com/dotnetcore/CAP/edit/master/docs/user-guide/configuration.md"><i class="fab fa-github"></i> Edit on GitHub</a>
</li>
</ul>
</div>
</div>
</div>
<div class="container">
<div class="col-md-3"><div class="bs-sidebar hidden-print affix well" role="complementary">
<ul class="nav bs-sidenav">
<li class="first-level active"><a href="#cap-options">Cap Options</a></li>
<li class="first-level "><a href="#rabbitmq-options">RabbitMQ Options</a></li>
<li class="first-level "><a href="#kafka-options">Kafka Options</a></li>
<li class="first-level "><a href="#entityframework-options">EntityFramework Options</a></li>
<li class="first-level "><a href="#sqlserver-options">SqlServer Options</a></li>
<li class="first-level "><a href="#mysql-options">MySql Options</a></li>
<li class="first-level "><a href="#postgresql-options">PostgreSql Options</a></li>
</ul>
</div></div>
<div class="col-md-9" role="main">
<p>CAP uses Microsoft.Extensions.DependencyInjection for configuration injection. </p>
<h3 id="cap-options">Cap Options</h3>
<p>You can use the following methods to configure some configuration items in the CAP, for example:</p>
<pre><code class="cs">services.AddCap(capOptions =&gt; {
capOptions.FailedCallback = //...
});
</code></pre>
<p><code>CapOptions</code> provides the following configuration items::</p>
<table>
<thead>
<tr>
<th align="left">NAME</th>
<th align="left">DESCRIPTION</th>
<th>TYPE</th>
<th align="left">DEFAULT</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">DefaultGroup</td>
<td align="left">Default consumer group to which the subscriber belongs</td>
<td>string</td>
<td align="left">cap.queue+{assembly name}</td>
</tr>
<tr>
<td align="left">SuccessedMessageExpiredAfter</td>
<td align="left">Expiration date after successful message was deleted</td>
<td>int</td>
<td align="left">3600 seconds</td>
</tr>
<tr>
<td align="left">FailedCallback</td>
<td align="left">Callback function when the failed message is executed. See below for details</td>
<td>Action</td>
<td align="left">NULL</td>
</tr>
<tr>
<td align="left">FailedRetryInterval</td>
<td align="left">Failed Retry Interval</td>
<td>int</td>
<td align="left">60 seconds</td>
</tr>
<tr>
<td align="left">FailedRetryCount</td>
<td align="left">Failed RetryCount</td>
<td>int</td>
<td align="left">50th</td>
</tr>
</tbody>
</table>
<p>CapOptions provides a callback function for <code>FailedCallback</code> to handle failed messages. When the message fails to be sent multiple times, the CAP will mark the message state as <code>Failed</code>. The CAP has a special handler to handle this failed message. The failed message will be put back into the queue and sent to MQ. Prior to this, if <code>FailedCallback</code> has a value, this callback function will be called first to tell the client.</p>
<p>The type of FailedCallback is <code>Action&lt;MessageType,string,string&gt;</code>. The first parameter is the message type (send or receive), the second parameter is the name of the message, and the third parameter is the content of the message.</p>
<h3 id="rabbitmq-options">RabbitMQ Options</h3>
<p>The CAP uses the CapOptions extension to implement the RabbitMQ configuration function. Therefore, the configuration of the RabbitMQ is used as follows:</p>
<pre><code class="cs">services.AddCap(capOptions =&gt; {
capOptions.UseRabbitMQ(rabbitMQOption=&gt;{
// rabbitmq options.
});
});
</code></pre>
<p><code>RabbitMQOptions</code> provides related RabbitMQ configuration:</p>
<table>
<thead>
<tr>
<th align="left">NAME</th>
<th align="left">DESCRIPTION</th>
<th>TYPE</th>
<th align="left">DEFAULT</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">HostName</td>
<td align="left">Host Address</td>
<td>string</td>
<td align="left">localhost</td>
</tr>
<tr>
<td align="left">UserName</td>
<td align="left">username</td>
<td>string</td>
<td align="left">guest</td>
</tr>
<tr>
<td align="left">Password</td>
<td align="left">Password</td>
<td>string</td>
<td align="left">guest</td>
</tr>
<tr>
<td align="left">VirtualHost</td>
<td align="left">Virtual Host</td>
<td>string</td>
<td align="left">/</td>
</tr>
<tr>
<td align="left">Port</td>
<td align="left">Port number</td>
<td>int</td>
<td align="left">-1</td>
</tr>
<tr>
<td align="left">TopicExchangeName</td>
<td align="left">CAP Default Exchange Name</td>
<td>string</td>
<td align="left">cap.default.topic</td>
</tr>
<tr>
<td align="left">RequestedConnectionTimeout</td>
<td align="left">RabbitMQ Connection Timeout</td>
<td>int</td>
<td align="left">30,000 milliseconds</td>
</tr>
<tr>
<td align="left">SocketReadTimeout</td>
<td align="left">RabbitMQ message read timeout</td>
<td>int</td>
<td align="left">30,000 milliseconds</td>
</tr>
<tr>
<td align="left">SocketWriteTimeout</td>
<td align="left">RabbitMQ message write timeout</td>
<td>int</td>
<td align="left">30,000 milliseconds</td>
</tr>
<tr>
<td align="left">QueueMessageExpires</td>
<td align="left">Automatic deletion of messages in queue</td>
<td>int</td>
<td align="left">(10 days) ms</td>
</tr>
</tbody>
</table>
<h3 id="kafka-options">Kafka Options</h3>
<p>CAP adopts Kafka's configuration function to expand CapOptions, so the configuration usage for Kafka is as follows:</p>
<pre><code class="cs">services.AddCap(capOptions =&gt; {
capOptions.UseKafka(kafkaOption=&gt;{
// kafka options.
// kafkaOptions.MainConfig.Add(&quot;&quot;, &quot;&quot;);
});
});
</code></pre>
<p><code>KafkaOptions</code> provides Kafka-related configurations. Because Kafka has more configurations, the MainConfig dictionary provided here is used to support custom configurations. You can check here to get support information for configuration items.</p>
<p><a href="https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md">https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md</a></p>
<h3 id="entityframework-options">EntityFramework Options</h3>
<p>If you are using Entityframework as a message persistence store, then you can customize some configuration when configuring the CAP EntityFramework configuration item.</p>
<pre><code class="cs">services.AddCap(x =&gt;
{
x.UseEntityFramework&lt;AppDbContext&gt;(efOption =&gt;
{
// entityframework options.
});
});
</code></pre>
<p>Note that if you use the <code>UseEntityFramework</code> configuration item, then you do not need to reconfigure the following sections for several different database configurations. The CAP will automatically read the database configuration information used in DbContext.</p>
<table>
<thead>
<tr>
<th align="left">NAME</th>
<th align="left">DESCRIPTION</th>
<th>TYPE</th>
<th align="left">DEFAULT</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">Schema</td>
<td align="left">Cap table schema</td>
<td>string</td>
<td align="left">Cap (SQL Server)</td>
</tr>
<tr>
<td align="left">Schema</td>
<td align="left">Cap table schema</td>
<td>string</td>
<td align="left">cap (PostgreSql)</td>
</tr>
<tr>
<td align="left">TableNamePrefix</td>
<td align="left">Cap table name prefix</td>
<td>string</td>
<td align="left">cap (MySql)</td>
</tr>
</tbody>
</table>
<h3 id="sqlserver-options">SqlServer Options</h3>
<p>Note that if you are using EntityFramewrok, you do not use this configuration item.</p>
<p>CAP adopts the configuration function of SqlServer for extending CapOptions. Therefore, the configuration usage of SqlServer is as follows:</p>
<pre><code class="cs">services.AddCap(capOptions =&gt; {
capOptions.UseSqlServer(sqlserverOptions =&gt; {
// sqlserverOptions.ConnectionString
});
});
</code></pre>
<table>
<thead>
<tr>
<th align="left">NAME</th>
<th align="left">DESCRIPTION</th>
<th>TYPE</th>
<th align="left">DEFAULT</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">Schema</td>
<td align="left">Cap Table Schema</td>
<td>string</td>
<td align="left">Cap</td>
</tr>
<tr>
<td align="left">ConnectionString</td>
<td align="left">Database connection string</td>
<td>string</td>
<td align="left">null</td>
</tr>
</tbody>
</table>
<h3 id="mysql-options">MySql Options</h3>
<p>Note that if you are using EntityFramewrok, you do not use this configuration item.</p>
<p>CAP uses the configuration function for MySql that extends for CapOptions, so the configuration usage for MySql is as follows:</p>
<pre><code class="cs">services.AddCap(capOptions =&gt; {
capOptions.UseMySql(mysqlOptions =&gt; {
// mysqlOptions.ConnectionString
});
});
</code></pre>
<table>
<thead>
<tr>
<th align="left">NAME</th>
<th align="left">DESCRIPTION</th>
<th>TYPE</th>
<th align="left">DEFAULT</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">TableNamePrefix</td>
<td align="left">Cap table name prefix</td>
<td>string</td>
<td align="left">cap</td>
</tr>
<tr>
<td align="left">ConnectionString</td>
<td align="left">Database connection string</td>
<td>string</td>
<td align="left">null</td>
</tr>
</tbody>
</table>
<h3 id="postgresql-options">PostgreSql Options</h3>
<p>Note that if you are using EntityFramewrok, you do not use this configuration item.</p>
<p>CAP uses PostgreSql configuration functions for CapOptions extensions, so the configuration usage for PostgreSql is as follows:</p>
<pre><code class="cs">services.AddCap(capOptions =&gt; {
capOptions.UsePostgreSql(postgreOptions =&gt; {
// postgreOptions.ConnectionString
});
});
</code></pre>
<table>
<thead>
<tr>
<th align="left">NAME</th>
<th align="left">DESCRIPTION</th>
<th>TYPE</th>
<th align="left">DEFAULT</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">Schema</td>
<td align="left">Cap table name prefix</td>
<td>string</td>
<td align="left">cap</td>
</tr>
<tr>
<td align="left">ConnectionString</td>
<td align="left">Database connection string</td>
<td>string</td>
<td align="left">null</td>
</tr>
</tbody>
</table></div>
</div>
<footer class="col-md-12 text-center">
<hr>
<p>
<small>Documentation built with <a href="http://www.mkdocs.org/">MkDocs</a>.</p></small>
</footer>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="../../js/bootstrap-3.0.3.min.js"></script>
<script src="../../js/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script>
var base_url = '../..';
</script>
<!-- <script data-main="../../mkdocs/js/search.js" src="../../mkdocs/js/require.js"></script> -->
<script src="../../js/base.js"></script>
<script src="../../search/main.js"></script>
<div class="modal" id="mkdocs_search_modal" tabindex="-1" role="dialog" aria-labelledby="Search Modal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span>
<span class="sr-only">Close</span>
</button>
<h4 class="modal-title" id="exampleModalLabel">Search</h4>
</div>
<div class="modal-body">
<p>
From here you can search these documents. Enter your search terms below.
</p>
<form role="form">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search..." id="mkdocs-search-query">
</div>
</form>
<div id="mkdocs-search-results"></div>
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="../../img/favicon.ico">
<title>Design principle - CAP</title>
<link rel="stylesheet" href="//use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
<link rel="stylesheet" href="//cdn.jsdelivr.net/font-hack/3.003/css/hack.min.css">
<link href='//fonts.googleapis.com/css?family=PT+Sans:400,400italic,700,700italic&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href='//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,700italic,400,300,600,700&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href="../../css/bootstrap-custom.min.css" rel="stylesheet">
<link href="../../css/base.min.css" rel="stylesheet">
<link href="../../css/cinder.min.css" rel="stylesheet">
<link rel="stylesheet" href="../../css/highlight.min.css">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<![endif]-->
<script src="//ajax.googleapis.com/ajax/libs/webfont/1.6.28/webfont.js"></script>
<script>
WebFont.load({
google: {
families: ['Open Sans', 'PT Sans']
}
});
</script>
</head>
<body>
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<!-- Collapsed navigation -->
<div class="navbar-header">
<!-- Expander button -->
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- Main title -->
<a class="navbar-brand" href="../..">CAP</a>
</div>
<!-- Expanded navigation -->
<div class="navbar-collapse collapse">
<!-- Main navigation -->
<ul class="nav navbar-nav">
<li >
<a href="../..">Home</a>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/us/flat/16.png">User Guide <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../getting-started/">Getting Started</a>
</li>
<li >
<a href="../api-interface/">API Interface</a>
</li>
<li >
<a href="../configuration/">Configuration</a>
</li>
<li >
<a href="../design-principle.md.md">Design Principle</a>
</li>
<li >
<a href="../implementation-mechanisms/">Implementation Mechanisms</a>
</li>
<li >
<a href="../distributed-transactions/">Distributed Transactions</a>
</li>
<li >
<a href="../faq/">FAQ</a>
</li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/cn/flat/16.png">使用指南 <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../../user-guide-cn/getting-started/">快速开始</a>
</li>
<li >
<a href="../../user-guide-cn/api-interface/">API 接口</a>
</li>
<li >
<a href="../../user-guide-cn/configuration/">配置</a>
</li>
<li >
<a href="../../user-guide-cn/design-principle/">设计原理</a>
</li>
<li >
<a href="../../user-guide-cn/implementation-mechanisms/">实现</a>
</li>
<li >
<a href="../../user-guide-cn/distributed-transactions/">分布式事务</a>
</li>
<li >
<a href="../../user-guide-cn/faq/">FAQ</a>
</li>
</ul>
</li>
<li >
<a href="../../about/">About</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<a href="#" data-toggle="modal" data-target="#mkdocs_search_modal">
<i class="fas fa-search"></i> Search
</a>
</li>
<li>
<a href="https://github.com/dotnetcore/CAP/edit/master/docs/user-guide/design-principle.md"><i class="fab fa-github"></i> Edit on GitHub</a>
</li>
</ul>
</div>
</div>
</div>
<div class="container">
<div class="col-md-3"><div class="bs-sidebar hidden-print affix well" role="complementary">
<ul class="nav bs-sidenav">
<li class="first-level active"><a href="#motivation">Motivation</a></li>
<li class="first-level "><a href="#persistence">Persistence</a></li>
<li class="first-level "><a href="#communication-data-streams">Communication Data Streams</a></li>
<li class="first-level "><a href="#consistency">Consistency</a></li>
</ul>
</div></div>
<div class="col-md-9" role="main">
<h3 id="motivation">Motivation</h3>
<p>With the popularity of microservices architecture, more and more people are trying to use microservices to architect their systems. In this we encounter problems such as distributed transactions. To solve these problems, I did not find simplicity and Easy to use solution, so I decided to create such a library to solve this problem.</p>
<p>The original CAP was to solve the transaction problems in the distributed system. She used asynchronous to ensure that this weak consistency transaction mechanism achieved the eventual consistency of the distributed transaction. For more information, see section 6.</p>
<p>Now in addition to solving distributed transaction problems, CAP's other important function is to use it as an EventBus. It has all of the features of EventBus and provides a more simplified way to handle publish/subscribe in EventBus.</p>
<h3 id="persistence">Persistence</h3>
<p>The CAP relies on the local database for persistence of messages. The CAP uses this method to deal with situations in which all messages are lost due to environmental or network anomalies. The reliability of messages is the cornerstone of distributed transactions, so messages cannot be lost under any circumstances.</p>
<p>There are two types of persistence for messages:</p>
<p><strong>1 Persistence before the message enters the message queue</strong></p>
<p>Before the message enters the message queue, the CAP uses the local database table to persist the message. This ensures that the message is not lost when the message queue is abnormal or the network error occurs.</p>
<p>In order to ensure the reliability of this mechanism, CAP uses database transactions with the same business code to ensure that business operations and CAP messages are strongly consistent throughout the persistence process. That is to say, in the process of message persistence, the database of any abnormal situation will be rolled back.</p>
<p><strong>2 Persistence after messages enter the message queue</strong></p>
<p>After the message enters the message queue, the CAP starts the persistence function of the message queue. We need to explain how the message of the CAP in RabbitMQ and Kafka is persistent.</p>
<p>For message persistence in RabbitMQ, CAP uses a consumer queue with message persistence, but there may be exceptions to this and take part in 2.2.1.</p>
<p>Since Kafka is inherently designed to persist messages using files, Kafka ensures that messages are correctly persisted without loss after the message enters Kafka.</p>
<h3 id="communication-data-streams">Communication Data Streams</h3>
<p>The flow of messages in the CAP is roughly as follows:</p>
<p>&gt;2.2 version before</p>
<p><img alt="" src="http://images2017.cnblogs.com/blog/250417/201708/250417-20170803174645928-1813351415.png" /></p>
<blockquote>
<p>"P" represents the sender of the message (producer). "C" stands for message consumer (subscriber).</p>
</blockquote>
<p><strong>After version 2.2</strong></p>
<p>In the 2.2 and later versions, we adjusted the flow of some messages. We removed the Queue table in the database and used the memory queue instead. For details, see: <a href="https://github.com/dotnetcore/CAP/issues/96">Improve the implementation mechanism of queue mode</a></p>
<h3 id="consistency">Consistency</h3>
<p>The CAP uses the ultimate consistency as a consistent solution. This solution follows the CAP theory. The following is the description of the CAP theory.</p>
<p>C (consistent) consistency refers to the atomicity of data. It is guaranteed by transactions in a classic database. When a transaction completes, the data will be in a consistent state regardless of success or rollback. In a distributed environment, consistency is Indicates whether the data of multiple nodes is consistent;</p>
<p>A (availability) service is always available, when the user sends a request, the service can return the result within a certain time;</p>
<p>P (Partition Tolerance) In distributed applications, the system may not operate due to some distributed reasons. The good partition tolerance makes the application a distributed system but it seems to be a functioning whole.</p>
<p>According to <a href="https://en.wikipedia.org/wiki/CAP_theorem">"CAP" distributed theory</a>, in a distributed system, we often reluctantly give up strong consensus support for availability and partition fault tolerance, and instead pursue Ultimate consistency. In most business scenarios, we can accept short-term inconsistencies.</p>
<p>Section 6 will introduce this further.</p></div>
</div>
<footer class="col-md-12 text-center">
<hr>
<p>
<small>Documentation built with <a href="http://www.mkdocs.org/">MkDocs</a>.</p></small>
</footer>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="../../js/bootstrap-3.0.3.min.js"></script>
<script src="../../js/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script>
var base_url = '../..';
</script>
<!-- <script data-main="../../mkdocs/js/search.js" src="../../mkdocs/js/require.js"></script> -->
<script src="../../js/base.js"></script>
<script src="../../search/main.js"></script>
<div class="modal" id="mkdocs_search_modal" tabindex="-1" role="dialog" aria-labelledby="Search Modal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span>
<span class="sr-only">Close</span>
</button>
<h4 class="modal-title" id="exampleModalLabel">Search</h4>
</div>
<div class="modal-body">
<p>
From here you can search these documents. Enter your search terms below.
</p>
<form role="form">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search..." id="mkdocs-search-query">
</div>
</form>
<div id="mkdocs-search-results"></div>
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="../../img/favicon.ico">
<title>Distributed Transactions - CAP</title>
<link rel="stylesheet" href="//use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
<link rel="stylesheet" href="//cdn.jsdelivr.net/font-hack/3.003/css/hack.min.css">
<link href='//fonts.googleapis.com/css?family=PT+Sans:400,400italic,700,700italic&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href='//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,700italic,400,300,600,700&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href="../../css/bootstrap-custom.min.css" rel="stylesheet">
<link href="../../css/base.min.css" rel="stylesheet">
<link href="../../css/cinder.min.css" rel="stylesheet">
<link rel="stylesheet" href="../../css/highlight.min.css">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<![endif]-->
<script src="//ajax.googleapis.com/ajax/libs/webfont/1.6.28/webfont.js"></script>
<script>
WebFont.load({
google: {
families: ['Open Sans', 'PT Sans']
}
});
</script>
</head>
<body>
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<!-- Collapsed navigation -->
<div class="navbar-header">
<!-- Expander button -->
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- Main title -->
<a class="navbar-brand" href="../..">CAP</a>
</div>
<!-- Expanded navigation -->
<div class="navbar-collapse collapse">
<!-- Main navigation -->
<ul class="nav navbar-nav">
<li >
<a href="../..">Home</a>
</li>
<li class="dropdown active">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/us/flat/16.png">User Guide <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../getting-started/">Getting Started</a>
</li>
<li >
<a href="../api-interface/">API Interface</a>
</li>
<li >
<a href="../configuration/">Configuration</a>
</li>
<li >
<a href="../design-principle.md.md">Design Principle</a>
</li>
<li >
<a href="../implementation-mechanisms/">Implementation Mechanisms</a>
</li>
<li class="active">
<a href="./">Distributed Transactions</a>
</li>
<li >
<a href="../faq/">FAQ</a>
</li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/cn/flat/16.png">使用指南 <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../../user-guide-cn/getting-started/">快速开始</a>
</li>
<li >
<a href="../../user-guide-cn/api-interface/">API 接口</a>
</li>
<li >
<a href="../../user-guide-cn/configuration/">配置</a>
</li>
<li >
<a href="../../user-guide-cn/design-principle/">设计原理</a>
</li>
<li >
<a href="../../user-guide-cn/implementation-mechanisms/">实现</a>
</li>
<li >
<a href="../../user-guide-cn/distributed-transactions/">分布式事务</a>
</li>
<li >
<a href="../../user-guide-cn/faq/">FAQ</a>
</li>
</ul>
</li>
<li >
<a href="../../about/">About</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<a href="#" data-toggle="modal" data-target="#mkdocs_search_modal">
<i class="fas fa-search"></i> Search
</a>
</li>
<li >
<a rel="next" href="../implementation-mechanisms/">
<i class="fas fa-arrow-left"></i> Previous
</a>
</li>
<li >
<a rel="prev" href="../faq/">
Next <i class="fas fa-arrow-right"></i>
</a>
</li>
<li>
<a href="https://github.com/dotnetcore/CAP/edit/master/docs/user-guide/distributed-transactions.md"><i class="fab fa-github"></i> Edit on GitHub</a>
</li>
</ul>
</div>
</div>
</div>
<div class="container">
<div class="col-md-3"><div class="bs-sidebar hidden-print affix well" role="complementary">
<ul class="nav bs-sidenav">
<li class="first-level active"><a href="#asynchronous-recovery-events">Asynchronous recovery events</a></li>
</ul>
</div></div>
<div class="col-md-9" role="main">
<p>For the processing of distributed transactions, this CAP library matches the "Asynchronous recovery events" scenario.</p>
<h3 id="asynchronous-recovery-events">Asynchronous recovery events</h3>
<p>As known as the name "native message table", this is a classic solution, originally from EBay, and referenced links about it are at the end of this section. This is also one of the most popular solutions in the business development. </p>
<p>Compared to TCC or 2pc/3pc, this solution is the simplest one for distributed transactions, and is decentralized. In TCC or 2PC solutions, the common transaction handlers synchronize the state among different services with a transaction coordinator, but it's not much required in this CAP solution. In addition, the deeper references of other conditions these services have, the more management complexity and stability risk may be increased in 2PC/TCC. Imagine that if we have 9 services committed successfully of all 10 whitch relied heavily, though the last one execute fail, should we roll back transactions of those 9 service? In fact, the cost is still very high. </p>
<p>However, it's not mean that 2PC or TCC are at a disadvantage, each has its own suitability and matched scenarios, here won't introduce more.</p>
<blockquote>
<p>cn: <a href="http://www.cnblogs.com/savorboard/p/base-an-acid-alternative.html">base-an-acid-alternative</a></p>
<p>en: <a href="http://queue.acm.org/detail.cfm?id=1394128">Base: An Acid Alternative</a></p>
</blockquote></div>
</div>
<footer class="col-md-12 text-center">
<hr>
<p>
<small>Documentation built with <a href="http://www.mkdocs.org/">MkDocs</a>.</p></small>
</footer>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="../../js/bootstrap-3.0.3.min.js"></script>
<script src="../../js/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script>
var base_url = '../..';
</script>
<!-- <script data-main="../../mkdocs/js/search.js" src="../../mkdocs/js/require.js"></script> -->
<script src="../../js/base.js"></script>
<script src="../../search/main.js"></script>
<div class="modal" id="mkdocs_search_modal" tabindex="-1" role="dialog" aria-labelledby="Search Modal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span>
<span class="sr-only">Close</span>
</button>
<h4 class="modal-title" id="exampleModalLabel">Search</h4>
</div>
<div class="modal-body">
<p>
From here you can search these documents. Enter your search terms below.
</p>
<form role="form">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search..." id="mkdocs-search-query">
</div>
</form>
<div id="mkdocs-search-results"></div>
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="../../img/favicon.ico">
<title>FAQ - CAP</title>
<link rel="stylesheet" href="//use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
<link rel="stylesheet" href="//cdn.jsdelivr.net/font-hack/3.003/css/hack.min.css">
<link href='//fonts.googleapis.com/css?family=PT+Sans:400,400italic,700,700italic&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href='//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,700italic,400,300,600,700&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href="../../css/bootstrap-custom.min.css" rel="stylesheet">
<link href="../../css/base.min.css" rel="stylesheet">
<link href="../../css/cinder.min.css" rel="stylesheet">
<link rel="stylesheet" href="../../css/highlight.min.css">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<![endif]-->
<script src="//ajax.googleapis.com/ajax/libs/webfont/1.6.28/webfont.js"></script>
<script>
WebFont.load({
google: {
families: ['Open Sans', 'PT Sans']
}
});
</script>
</head>
<body>
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<!-- Collapsed navigation -->
<div class="navbar-header">
<!-- Expander button -->
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- Main title -->
<a class="navbar-brand" href="../..">CAP</a>
</div>
<!-- Expanded navigation -->
<div class="navbar-collapse collapse">
<!-- Main navigation -->
<ul class="nav navbar-nav">
<li >
<a href="../..">Home</a>
</li>
<li class="dropdown active">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/us/flat/16.png">User Guide <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../getting-started/">Getting Started</a>
</li>
<li >
<a href="../api-interface/">API Interface</a>
</li>
<li >
<a href="../configuration/">Configuration</a>
</li>
<li >
<a href="../design-principle.md.md">Design Principle</a>
</li>
<li >
<a href="../implementation-mechanisms/">Implementation Mechanisms</a>
</li>
<li >
<a href="../distributed-transactions/">Distributed Transactions</a>
</li>
<li class="active">
<a href="./">FAQ</a>
</li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/cn/flat/16.png">使用指南 <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../../user-guide-cn/getting-started/">快速开始</a>
</li>
<li >
<a href="../../user-guide-cn/api-interface/">API 接口</a>
</li>
<li >
<a href="../../user-guide-cn/configuration/">配置</a>
</li>
<li >
<a href="../../user-guide-cn/design-principle/">设计原理</a>
</li>
<li >
<a href="../../user-guide-cn/implementation-mechanisms/">实现</a>
</li>
<li >
<a href="../../user-guide-cn/distributed-transactions/">分布式事务</a>
</li>
<li >
<a href="../../user-guide-cn/faq/">FAQ</a>
</li>
</ul>
</li>
<li >
<a href="../../about/">About</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<a href="#" data-toggle="modal" data-target="#mkdocs_search_modal">
<i class="fas fa-search"></i> Search
</a>
</li>
<li >
<a rel="next" href="../distributed-transactions/">
<i class="fas fa-arrow-left"></i> Previous
</a>
</li>
<li >
<a rel="prev" href="../../user-guide-cn/getting-started/">
Next <i class="fas fa-arrow-right"></i>
</a>
</li>
<li>
<a href="https://github.com/dotnetcore/CAP/edit/master/docs/user-guide/faq.md"><i class="fab fa-github"></i> Edit on GitHub</a>
</li>
</ul>
</div>
</div>
</div>
<div class="container">
<div class="col-md-3"><div class="bs-sidebar hidden-print affix well" role="complementary">
<ul class="nav bs-sidenav">
<li class="first-level active"><a href="#faq">FAQ</a></li>
<li class="second-level"><a href="#any-im-groupeg-tencent-qq-group-to-learn-and-chat-about-cap">Any IM group(e.g Tencent QQ group) to learn and chat about CAP?</a></li>
<li class="second-level"><a href="#does-it-require-certain-different-databases-one-each-for-productor-and-resumer-in-cap">Does it require certain different databases, one each for productor and resumer in CAP?</a></li>
<li class="second-level"><a href="#how-to-use-the-same-database-for-different-programs">How to use the same database for different programs?</a></li>
<li class="second-level"><a href="#if-dont-care-about-message-missing-can-message-productor-exist-without-any-database-for-the-reason-of-sending-message-only">If don't care about message missing, can message productor exist without any database, for the reason of sending message only.</a></li>
</ul>
</div></div>
<div class="col-md-9" role="main">
<h2 id="faq">FAQ</h2>
<h3 id="any-im-groupeg-tencent-qq-group-to-learn-and-chat-about-cap">Any IM group(e.g Tencent QQ group) to learn and chat about CAP?</h3>
<p>None for that. Better than wasting much time in IM group, I hope developers could be capable of independent thinking more, and solve problems yourselves with referenced documents, even create issues or send emails when errors are remaining present.</p>
<h3 id="does-it-require-certain-different-databases-one-each-for-productor-and-resumer-in-cap">Does it require certain different databases, one each for productor and resumer in CAP?</h3>
<p>Not requird differences necessary, a given advice is that using a special database for each program.</p>
<p>Otherwise, look at Q&amp;A below.</p>
<h3 id="how-to-use-the-same-database-for-different-programs">How to use the same database for different programs?</h3>
<p>defining a prefix name of table in <code>ConfigureServices</code> method。</p>
<p>codes exsample:</p>
<pre><code class="cs">public void ConfigureServices(IServiceCollection services)
{
services.AddCap(x =&gt;
{
x.UseKafka(&quot;&quot;);
x.UseMySql(opt =&gt;
{
opt.ConnectionString = &quot;connection string&quot;;
opt.TableNamePrefix = &quot;appone&quot;; // different table name prefix here
});
});
}
</code></pre>
<p>NOTE:different prefixed names cause SLB to no effect.</p>
<h3 id="if-dont-care-about-message-missing-can-message-productor-exist-without-any-database-for-the-reason-of-sending-message-only">If don't care about message missing, can message productor exist without any database, for the reason of sending message only.</h3>
<p>Not yet.</p>
<p>The purpose of CAP is that ensure consistency principle right in microservice or SOA architechtrues. The solution is based on ACID features of database, there is no sense about a single client wapper of message queue without database.</p></div>
</div>
<footer class="col-md-12 text-center">
<hr>
<p>
<small>Documentation built with <a href="http://www.mkdocs.org/">MkDocs</a>.</p></small>
</footer>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="../../js/bootstrap-3.0.3.min.js"></script>
<script src="../../js/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script>
var base_url = '../..';
</script>
<!-- <script data-main="../../mkdocs/js/search.js" src="../../mkdocs/js/require.js"></script> -->
<script src="../../js/base.js"></script>
<script src="../../search/main.js"></script>
<div class="modal" id="mkdocs_search_modal" tabindex="-1" role="dialog" aria-labelledby="Search Modal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span>
<span class="sr-only">Close</span>
</button>
<h4 class="modal-title" id="exampleModalLabel">Search</h4>
</div>
<div class="modal-body">
<p>
From here you can search these documents. Enter your search terms below.
</p>
<form role="form">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search..." id="mkdocs-search-query">
</div>
</form>
<div id="mkdocs-search-results"></div>
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="../../img/favicon.ico">
<title>Getting Started - CAP</title>
<link rel="stylesheet" href="//use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
<link rel="stylesheet" href="//cdn.jsdelivr.net/font-hack/3.003/css/hack.min.css">
<link href='//fonts.googleapis.com/css?family=PT+Sans:400,400italic,700,700italic&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href='//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,700italic,400,300,600,700&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href="../../css/bootstrap-custom.min.css" rel="stylesheet">
<link href="../../css/base.min.css" rel="stylesheet">
<link href="../../css/cinder.min.css" rel="stylesheet">
<link rel="stylesheet" href="../../css/highlight.min.css">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<![endif]-->
<script src="//ajax.googleapis.com/ajax/libs/webfont/1.6.28/webfont.js"></script>
<script>
WebFont.load({
google: {
families: ['Open Sans', 'PT Sans']
}
});
</script>
</head>
<body>
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<!-- Collapsed navigation -->
<div class="navbar-header">
<!-- Expander button -->
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- Main title -->
<a class="navbar-brand" href="../..">CAP</a>
</div>
<!-- Expanded navigation -->
<div class="navbar-collapse collapse">
<!-- Main navigation -->
<ul class="nav navbar-nav">
<li >
<a href="../..">Home</a>
</li>
<li class="dropdown active">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/us/flat/16.png">User Guide <b class="caret"></b></a>
<ul class="dropdown-menu">
<li class="active">
<a href="./">Getting Started</a>
</li>
<li >
<a href="../api-interface/">API Interface</a>
</li>
<li >
<a href="../configuration/">Configuration</a>
</li>
<li >
<a href="../design-principle.md.md">Design Principle</a>
</li>
<li >
<a href="../implementation-mechanisms/">Implementation Mechanisms</a>
</li>
<li >
<a href="../distributed-transactions/">Distributed Transactions</a>
</li>
<li >
<a href="../faq/">FAQ</a>
</li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/cn/flat/16.png">使用指南 <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../../user-guide-cn/getting-started/">快速开始</a>
</li>
<li >
<a href="../../user-guide-cn/api-interface/">API 接口</a>
</li>
<li >
<a href="../../user-guide-cn/configuration/">配置</a>
</li>
<li >
<a href="../../user-guide-cn/design-principle/">设计原理</a>
</li>
<li >
<a href="../../user-guide-cn/implementation-mechanisms/">实现</a>
</li>
<li >
<a href="../../user-guide-cn/distributed-transactions/">分布式事务</a>
</li>
<li >
<a href="../../user-guide-cn/faq/">FAQ</a>
</li>
</ul>
</li>
<li >
<a href="../../about/">About</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<a href="#" data-toggle="modal" data-target="#mkdocs_search_modal">
<i class="fas fa-search"></i> Search
</a>
</li>
<li >
<a rel="next" href="../..">
<i class="fas fa-arrow-left"></i> Previous
</a>
</li>
<li >
<a rel="prev" href="../api-interface/">
Next <i class="fas fa-arrow-right"></i>
</a>
</li>
<li>
<a href="https://github.com/dotnetcore/CAP/edit/master/docs/user-guide/getting-started.md"><i class="fab fa-github"></i> Edit on GitHub</a>
</li>
</ul>
</div>
</div>
</div>
<div class="container">
<div class="col-md-3"><div class="bs-sidebar hidden-print affix well" role="complementary">
<ul class="nav bs-sidenav">
<li class="first-level active"><a href="#introduction">Introduction</a></li>
<li class="first-level "><a href="#usage-scenarios">Usage Scenarios</a></li>
<li class="first-level "><a href="#quick-start">Quick Start</a></li>
</ul>
</div></div>
<div class="col-md-9" role="main">
<h3 id="introduction">Introduction</h3>
<p>CAP is a library based on .Net standard, which is a solution to deal with distributed transactions, also has the function of EventBus, it is lightweight, easy to use, and efficiently.</p>
<h3 id="usage-scenarios">Usage Scenarios</h3>
<p>The usage scenarios of CAP mainly include the following two:</p>
<ul>
<li>1.The scheme of eventual consistency in distributed transactions</li>
</ul>
<p>In the process of building an SOA or MicroService system, we usually need to use the event to integrate each services. In the process, the simple use of message queue does not guarantee the reliability. CAP is adopted the local message table program integrated with the current database to solve the exception may occur in the process of the distributed system calling each other. It can ensure that the event messages are not lost in any case.</p>
<p>Distributed transactions are an inevitable requirement in a distributed system, and the current solution for distributed transactions is nothing more than just a few. Before understanding the CAP's distributed transaction scenarios, you can read the following [Articles] (http://www.infoq.com/en/articles/solution-of-distributed-system-transaction-consistency).</p>
<p>The CAP does not use the two-phase commit (2PC) transaction mechanism, but uses the classical message implementation of the local message table + MQ, which is also called asynchronous guarantee.</p>
<ul>
<li>2.Highly usable EventBus</li>
</ul>
<p>You can also use the CAP as an EventBus. The CAP provides a simpler way to implement event publishing and subscriptions. You do not need to inherit or implement any interface during the process of subscription and sending.</p>
<p>CAP implements the publish and subscribe method of EventBus, which has all the features of EventBus. This means that you can use CAPs just like EventBus. In addition, CAP's EventBus is highly available. What does this mean?</p>
<p>The CAP uses the local message table to persist the messages in the EventBus. This ensures that the messages sent by the EventBus are reliable. When the message queue fails or fails, the messages are not lost.</p>
<h3 id="quick-start">Quick Start</h3>
<ul>
<li><strong>Reference NuGet Package</strong></li>
</ul>
<p>Use the following command to reference the CAP NuGet package:</p>
<pre><code>PM&gt; Install-Package DotNetCore.CAP
</code></pre>
<p>According to the different types of message queues used, different extension packages are introduced:</p>
<pre><code>PM&gt; Install-Package DotNetCore.CAP.RabbitMQ
PM&gt; Install-Package DotNetCore.CAP.Kafka
</code></pre>
<p>According to the different types of databases used, different extension packages are introduced:</p>
<pre><code>PM&gt; Install-Package DotNetCore.CAP.SqlServer
PM&gt; Install-Package DotNetCore.CAP.MySql
PM&gt; Install-Package DotNetCore.CAP.PostgreSql
PM&gt; Install-Package DotNetCore.CAP.MongoDB
</code></pre>
<ul>
<li><strong>Startup Configuration</strong></li>
</ul>
<p>In an ASP.NET Core program, you can configure the services used by the CAP in the <code>Startup.cs</code> file <code>ConfigureServices()</code>:</p>
<pre><code class="cs">public void ConfigureServices(IServiceCollection services)
{
//......
services.AddDbContext&lt;AppDbContext&gt;(); //Options, If you are using EF as the ORM
services.AddSingleton&lt;IMongoClient&gt;(new MongoClient(&quot;&quot;)); //Options, If you are using MongoDB
services.AddCap(x =&gt;
{
// If you are using EF, you need to add the configuration:
x.UseEntityFramework&lt;AppDbContext&gt;(); //Options, Notice: You don't need to config x.UseSqlServer(&quot;&quot;&quot;) again! CAP can autodiscovery.
// If you are using Ado.Net, you need to add the configuration:
x.UseSqlServer(&quot;Your ConnectionStrings&quot;);
x.UseMySql(&quot;Your ConnectionStrings&quot;);
x.UsePostgreSql(&quot;Your ConnectionStrings&quot;);
// If you are using MongoDB, you need to add the configuration:
x.UseMongoDB(&quot;Your ConnectionStrings&quot;); //MongoDB 4.0+ cluster
// If you are using RabbitMQ, you need to add the configuration:
x.UseRabbitMQ(&quot;localhost&quot;);
// If you are using Kafka, you need to add the configuration:
x.UseKafka(&quot;localhost&quot;);
});
}
</code></pre></div>
</div>
<footer class="col-md-12 text-center">
<hr>
<p>
<small>Documentation built with <a href="http://www.mkdocs.org/">MkDocs</a>.</p></small>
</footer>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="../../js/bootstrap-3.0.3.min.js"></script>
<script src="../../js/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script>
var base_url = '../..';
</script>
<!-- <script data-main="../../mkdocs/js/search.js" src="../../mkdocs/js/require.js"></script> -->
<script src="../../js/base.js"></script>
<script src="../../search/main.js"></script>
<div class="modal" id="mkdocs_search_modal" tabindex="-1" role="dialog" aria-labelledby="Search Modal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span>
<span class="sr-only">Close</span>
</button>
<h4 class="modal-title" id="exampleModalLabel">Search</h4>
</div>
<div class="modal-body">
<p>
From here you can search these documents. Enter your search terms below.
</p>
<form role="form">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search..." id="mkdocs-search-query">
</div>
</form>
<div id="mkdocs-search-results"></div>
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="../../img/favicon.ico">
<title>Implementation Mechanisms - CAP</title>
<link rel="stylesheet" href="//use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
<link rel="stylesheet" href="//cdn.jsdelivr.net/font-hack/3.003/css/hack.min.css">
<link href='//fonts.googleapis.com/css?family=PT+Sans:400,400italic,700,700italic&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href='//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,700italic,400,300,600,700&subset=latin-ext,latin' rel='stylesheet' type='text/css'>
<link href="../../css/bootstrap-custom.min.css" rel="stylesheet">
<link href="../../css/base.min.css" rel="stylesheet">
<link href="../../css/cinder.min.css" rel="stylesheet">
<link rel="stylesheet" href="../../css/highlight.min.css">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<![endif]-->
<script src="//ajax.googleapis.com/ajax/libs/webfont/1.6.28/webfont.js"></script>
<script>
WebFont.load({
google: {
families: ['Open Sans', 'PT Sans']
}
});
</script>
</head>
<body>
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<!-- Collapsed navigation -->
<div class="navbar-header">
<!-- Expander button -->
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- Main title -->
<a class="navbar-brand" href="../..">CAP</a>
</div>
<!-- Expanded navigation -->
<div class="navbar-collapse collapse">
<!-- Main navigation -->
<ul class="nav navbar-nav">
<li >
<a href="../..">Home</a>
</li>
<li class="dropdown active">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/us/flat/16.png">User Guide <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../getting-started/">Getting Started</a>
</li>
<li >
<a href="../api-interface/">API Interface</a>
</li>
<li >
<a href="../configuration/">Configuration</a>
</li>
<li >
<a href="../design-principle.md.md">Design Principle</a>
</li>
<li class="active">
<a href="./">Implementation Mechanisms</a>
</li>
<li >
<a href="../distributed-transactions/">Distributed Transactions</a>
</li>
<li >
<a href="../faq/">FAQ</a>
</li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><img src="https://www.countryflags.io/cn/flat/16.png">使用指南 <b class="caret"></b></a>
<ul class="dropdown-menu">
<li >
<a href="../../user-guide-cn/getting-started/">快速开始</a>
</li>
<li >
<a href="../../user-guide-cn/api-interface/">API 接口</a>
</li>
<li >
<a href="../../user-guide-cn/configuration/">配置</a>
</li>
<li >
<a href="../../user-guide-cn/design-principle/">设计原理</a>
</li>
<li >
<a href="../../user-guide-cn/implementation-mechanisms/">实现</a>
</li>
<li >
<a href="../../user-guide-cn/distributed-transactions/">分布式事务</a>
</li>
<li >
<a href="../../user-guide-cn/faq/">FAQ</a>
</li>
</ul>
</li>
<li >
<a href="../../about/">About</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<a href="#" data-toggle="modal" data-target="#mkdocs_search_modal">
<i class="fas fa-search"></i> Search
</a>
</li>
<li >
<a rel="next" href="../configuration/">
<i class="fas fa-arrow-left"></i> Previous
</a>
</li>
<li >
<a rel="prev" href="../distributed-transactions/">
Next <i class="fas fa-arrow-right"></i>
</a>
</li>
<li>
<a href="https://github.com/dotnetcore/CAP/edit/master/docs/user-guide/implementation-mechanisms.md"><i class="fab fa-github"></i> Edit on GitHub</a>
</li>
</ul>
</div>
</div>
</div>
<div class="container">
<div class="col-md-3"><div class="bs-sidebar hidden-print affix well" role="complementary">
<ul class="nav bs-sidenav">
<li class="first-level active"><a href="#message-table">Message Table</a></li>
<li class="first-level "><a href="#message-format5">Message format5</a></li>
<li class="first-level "><a href="#eventbus">EventBus</a></li>
<li class="first-level "><a href="#retry">Retry</a></li>
<li class="first-level "><a href="#data-clean-out">Data clean out</a></li>
</ul>
</div></div>
<div class="col-md-9" role="main">
<p>Users can get a ICapPublisher interface from the ASP.NET Core DI container to publish a message .It is initialized by configurations in the <code>ConfigureServices</code> and <code>configure</code> method in the Startup.cs file,just like the way to initialize a <code>MiddleWare</code> in ASP.NET Core.</p>
<h3 id="message-table">Message Table</h3>
<p>After initialized, CAP will create two tables in the client side,they are <code>Cap.Published</code> and <code>Cap.Received</code>. Please noted that different databases may deal letter case differently,if you do not explicitly specify the Schema or the TableName Prefix before project startup,the default names are the ones mentioned above.</p>
<p><strong>Cap.Published</strong>:Used to store messages(Published by the <code>ICapPublisher</code> service) that CAP published to the MQ(Message Queue)Client side</p>
<p><strong>Cap.Received</strong>:Used to Store messages(subscribed by the <code>CapSubscribe[]</code>) subscribed by the MQ(message Queue) client side that CAP received.</p>
<blockquote>
<p>Before version 2.2 :
<strong>Cap.Queue</strong>:A temporary table that CAP used to deal with published and received messages,under most circumstances(there aren't any errors),it is an empty table.</p>
</blockquote>
<p>Both <code>Published</code> and <code>Received</code> tables have a <code>StatusName</code> field,which is used to mark the status of the current message.Until now it has <code>Scheduled</code><code>Successed</code> and <code>Failed</code> statuses.</p>
<blockquote>
<p>Statuses before version 2.2:<code>Scheduled</code><code>Enqueued</code><code>Processing</code><code>Successed</code> and <code>Failed</code>.</p>
</blockquote>
<p>In the process of dealing with messages,CAP will change the status from <code>Scheduled</code> to <code>Successed</code>(or <code>Failed</code> ).if the final status is <code>Successed</code>,it means that the message is sent to MQ successfully,and <code>Failed</code> means the message is failed to sent to MQ.</p>
<p>Version later than 2.2, CAP will retry after 4 minutes if the status is <code>Scheduled</code> or <code>Failed</code>,the retry interval is default to 60 seconds.You can change it by modify <code>FailedRetryInterval</code> in <code>CapOptions</code>.</p>
<blockquote>
<p>Before version 2.2,CAP retry 100 times for <code>Failed</code> messages by default.</p>
</blockquote>
<h3 id="message-format5">Message format5</h3>
<p>CAP use JSON to transfer message,the following is CAP's messaging object model:</p>
<table>
<thead>
<tr>
<th align="left">NAME</th>
<th align="left">DESCRIPTION</th>
<th align="left">TYPE</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">Id</td>
<td align="left">Message Id</td>
<td align="left">int</td>
</tr>
<tr>
<td align="left">Version</td>
<td align="left">Message Version</td>
<td align="left">string</td>
</tr>
<tr>
<td align="left">Name</td>
<td align="left">Name</td>
<td align="left">string</td>
</tr>
<tr>
<td align="left">Content</td>
<td align="left">Content</td>
<td align="left">string</td>
</tr>
<tr>
<td align="left">Group</td>
<td align="left">Group a message belongs to</td>
<td align="left">string</td>
</tr>
<tr>
<td align="left">Added</td>
<td align="left">add time</td>
<td align="left">DateTime</td>
</tr>
<tr>
<td align="left">ExpiresAt</td>
<td align="left">expire time</td>
<td align="left">DateTime</td>
</tr>
<tr>
<td align="left">Retries</td>
<td align="left">retry times</td>
<td align="left">int</td>
</tr>
<tr>
<td align="left">StatusName</td>
<td align="left">Status Name</td>
<td align="left">string</td>
</tr>
</tbody>
</table>
<blockquote>
<p>for <code>Cap.Received</code>,there is an extra <code>Group</code> filed to mark which group the mesage belongs to.</p>
<p>for the <code>Content</code> property CAP will use a Messsage object to wrap all the contents.The following shows details of the Message Object:</p>
</blockquote>
<table>
<thead>
<tr>
<th align="left">NAME</th>
<th align="left">DESCRIPTION</th>
<th align="left">TYPE</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">Id</td>
<td align="left">Generated by CAP</td>
<td align="left">string</td>
</tr>
<tr>
<td align="left">Timestamp</td>
<td align="left">message create time</td>
<td align="left">string</td>
</tr>
<tr>
<td align="left">Content</td>
<td align="left">content</td>
<td align="left">string</td>
</tr>
<tr>
<td align="left">CallbackName</td>
<td align="left">the subscriber which is used to call back</td>
<td align="left">string</td>
</tr>
</tbody>
</table>
<p>CAP use the same algorithms as MongoDB ObjectId's distributed Id generation algorithms.</p>
<h3 id="eventbus">EventBus</h3>
<p>EventBus adopt the publish-subscribe messaging style to communicate with different components,and there is no need to register it in component explicitly.</p>
<p><img alt="" src="http://images2017.cnblogs.com/blog/250417/201708/250417-20170804153901240-1774287236.png" /></p>
<p>the diagram in the above link shows Eventbus's event flowchart,about EventBus,users can refer to other meterials to learn about it.</p>
<p>We say that CAP implement all the features in Eventbus,EventBus has two features:publish and subscribe,In CAP we implement them in an elegant way.Besides,CAP also has two very robust feature,they are message persistence and messaging reliability under any circumstances,But EventBus don't have such features.</p>
<p><img alt="" src="https://camo.githubusercontent.com/452505edb71d41f2c1bd18907275b76291621e46/687474703a2f2f696d61676573323031352e636e626c6f67732e636f6d2f626c6f672f3235303431372f3230313730372f3235303431372d32303137303730353137353832373132382d313230333239313436392e706e67" /></p>
<p>In CAP,send a message can be regarded as an "Event",When CAP is used in an ASP.NET Core applicaiton,the application has the ablity to publish as well as receive messages.</p>
<h3 id="retry">Retry</h3>
<p>Retry plays a very important role in CAP's infrastructure,CAP will retry for Failed messages.CAP has the following retry strategies:</p>
<p><strong>1、 Retry on sending</strong></p>
<p>in the process of sending a message,when the Broker crashed or connection failed or exceptions are thrown,CAP will retry,it will retry 3 times for the first time,if still failed,then it will retry every 1 minute,the retry the retry count +1,when the retry count come to 50,CAP will not retry any more.</p>
<blockquote>
<p>You can modify <code>FailedRetryCount</code> in <code>CapOptions</code> to change the default retry count.</p>
</blockquote>
<p>As metioned above,when the retry count comes to a certain number,CAP will not retry anymore,this time,you can find out the fail reason in the Dashboard and they deal with it manually.</p>
<p><strong>2、 Retry on Consuming</strong></p>
<p>When consumer received messages,specified method in the consumer will be executed,if exceptions are thrown during this course,CAP will retry,the retry strategy is the same as above <code>Retry on sending</code>.</p>
<h3 id="data-clean-out">Data clean out</h3>
<p>table to store messages in database has an <code>ExpiresAt</code> field to mark the expiration time of the message. CAP will set <code>ExpiresAt</code> value as <strong>1 hour</strong> for <code>Successed</code> messages and <strong>15days</strong> for <code>Failed</code> messages.</p>
<p>To avoid performance slow down caused by a large amount of data,CAP will delete expired data every hour by default,the deletion rule is that <code>ExpiresAt</code> field's value isn't null and samller than current time.That is, <code>Failed</code> messages(it has been retried 50 times by default),if you do not deal with it manually,will also be deleted after 15 days as well,you have to pay attention to it.</p></div>
</div>
<footer class="col-md-12 text-center">
<hr>
<p>
<small>Documentation built with <a href="http://www.mkdocs.org/">MkDocs</a>.</p></small>
</footer>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="../../js/bootstrap-3.0.3.min.js"></script>
<script src="../../js/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script>
var base_url = '../..';
</script>
<!-- <script data-main="../../mkdocs/js/search.js" src="../../mkdocs/js/require.js"></script> -->
<script src="../../js/base.js"></script>
<script src="../../search/main.js"></script>
<div class="modal" id="mkdocs_search_modal" tabindex="-1" role="dialog" aria-labelledby="Search Modal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">&times;</span>
<span class="sr-only">Close</span>
</button>
<h4 class="modal-title" id="exampleModalLabel">Search</h4>
</div>
<div class="modal-body">
<p>
From here you can search these documents. Enter your search terms below.
</p>
<form role="form">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search..." id="mkdocs-search-query">
</div>
</form>
<div id="mkdocs-search-results"></div>
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
</body>
</html>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment