blob: e94afe5847e7178f65dc05aefeaae7e8e7e84714 [file] [log] [blame]
<!doctype html>
<!--
Minimal Mistakes Jekyll Theme 4.4.1 by Michael Rose
Copyright 2017 Michael Rose - mademistakes.com | @mmistakes
Free for personal and commercial use under the MIT license
https://github.com/mmistakes/minimal-mistakes/blob/master/LICENSE.txt
-->
<html lang="cn" class="no-js">
<head>
<meta charset="utf-8">
<!-- begin SEO -->
<title>客户管理系统微服务化实战-PartI - Apache ServiceComb</title>
<meta name="description" content="客户管理系统微服务化实战-PartI">
<meta name="author" content="Yangyong Zheng">
<meta property="og:locale" content="cn">
<meta property="og:site_name" content="Apache ServiceComb">
<meta property="og:title" content="客户管理系统微服务化实战-PartI">
<link rel="canonical" href="https://github.com/pages/apache/incubator-servicecomb-website/cn/docs/crm-part-I/">
<meta property="og:url" content="https://github.com/pages/apache/incubator-servicecomb-website/cn/docs/crm-part-I/">
<meta property="og:description" content="客户管理系统微服务化实战-PartI">
<meta name="twitter:site" content="@ServiceComb">
<meta name="twitter:title" content="客户管理系统微服务化实战-PartI">
<meta name="twitter:description" content="客户管理系统微服务化实战-PartI">
<meta name="twitter:url" content="">
<meta name="twitter:card" content="summary">
<meta name="twitter:creator" content="@">
<meta property="og:type" content="article">
<meta property="article:published_time" content="2018-08-28T00:00:00+08:00">
<script type="application/ld+json">
{
"@context" : "http://schema.org",
"@type" : "Person",
"name" : "Apache ServiceComb",
"url" : "https://github.com/pages/apache/incubator-servicecomb-website",
"sameAs" : null
}
</script>
<meta name="google-site-verification" content="HvJjNd7vvJ-yjSTHlBiIWEYxp_Hrz-PYEY5Idz9LRcA" />
<!-- end SEO -->
<link href="/feed.xml" type="application/atom+xml" rel="alternate" title="Apache ServiceComb Feed">
<!-- http://t.co/dKP3o1e -->
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script>
document.documentElement.className = document.documentElement.className.replace(/\bno-js\b/g, '') + ' js ';
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/1.7.1/clipboard.min.js"></script>
<script src="/assets/vendor/prism/prism.js"></script>
<script type="text/javascript" async
src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-MML-AM_CHTML">
</script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
<script src="https://www.apachecon.com/event-images/snippet.js"></script>
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js" integrity="sha384-b/U6ypiBEHpOf/4+1nzFpr53nxSS+GLCkfwBdFNTxtclqqenISfwAzpKaMNFNmj4" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/js/bootstrap.min.js" integrity="sha384-h0AbiXch4ZDo7tp9hKZ4TsHbi047NrKGLO3SEJAg45jXxnGIfYzk4Si90RDIqNm1" crossorigin="anonymous"></script>
<!-- For all browsers -->
<link rel="stylesheet" href="/assets/css/main.css">
<link rel="stylesheet" href="/assets/vendor/prism/prism.css">
<!--[if lte IE 9]>
<style>
/* old IE unsupported flexbox fixes */
.greedy-nav .site-title {
padding-right: 3em;
}
.greedy-nav button {
position: absolute;
top: 0;
right: 0;
height: 100%;
}
</style>
<![endif]-->
<meta http-equiv="cleartype" content="on">
<!-- start custom head snippets -->
<!-- insert favicons. use http://realfavicongenerator.net/ -->
<link href="https://fonts.loli.net/css?family=Roboto:400,500,700|Source+Code+Pro" rel="stylesheet">
<script src="/assets/js/custom.js"></script>
<!-- end custom head snippets -->
</head>
<body class="layout--single">
<!--[if lt IE 9]>
<div class="notice--danger align-center" style="margin: 0;">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</div>
<![endif]-->
<div class="masthead" onmouseleave="$('#childrenShow').css('display', 'none')">
<div class="masthead__inner-wrap">
<div class="masthead__menu">
<nav id="site-nav" class="greedy-nav">
<a class="site-title active" href="/cn"><img src="https://www.apache.org/img/servicecomb.png"></a>
<ul class="visible-links">
<li class="masthead__menu-item" onmouseenter="$('#childrenShow').css('display', 'none')">
<a href="/cn/">首页</a>
</li>
<li class="masthead__menu-item" onmouseenter="$('#childrenShow').css('display', 'none')">
<a href="/cn/developers/">项目</a>
</li>
<li class="def-nav-li" onmouseenter="$('#childrenShow').css('display', 'block')">
<a href="/cn/docs/users/">文档</a>
<ul id="childrenShow" class="def-children-show-cn" onmouseleave="$('#childrenShow').css('display', 'none')">
<li><a href="/cn/docs/getting-started/" class="">入门指南</a></li>
<li><a href="/cn/docs/users/" class="">用户手册</a></li>
<li><a href="/cn/slides/" class="">大咖视频</a></li>
<li><a href="/cn/faqs/" class="">常见问题</a></li>
</ul>
</li>
<li class="masthead__menu-item" onmouseenter="$('#childrenShow').css('display', 'none')">
<a href="/cn/year-archive/">博文</a>
</li>
<li class="masthead__menu-item" onmouseenter="$('#childrenShow').css('display', 'none')">
<a href="/cn/release/">下载</a>
</li>
</ul>
<button><div class="navicon"></div></button>
<ul class="hidden-links hidden"></ul>
<div class="nav-lang">
<a href=/docs/crm-part-I/>English</a>
</div>
</nav>
</div>
</div>
</div>
<div id="main" role="main">
<div class="sidebar sticky">
<div class="back-to-home"><a href="/cn/">首页</a> > 客户管理系统微服务化实战-PartI</div>
<div itemscope itemtype="http://schema.org/Person">
<div class="author__content">
<h3 class="author__name" itemprop="name">Yangyong Zheng</h3>
<p class="author__bio" itemprop="description">
Fast Action, do not ask
</p>
</div>
<div class="author__urls-wrapper">
<button class="btn btn--inverse">关注</button>
<ul class="author__urls social-icons">
<li>
<a href="https://zhengyangyong.github.io" itemprop="url">
<i class="fa fa-fw fa-chain" aria-hidden="true"></i> 网站
</a>
</li>
<li>
<a href="mailto:yangyong.zheng@huawei.com">
<meta itemprop="email" content="yangyong.zheng@huawei.com" />
<i class="fa fa-fw fa-envelope-square" aria-hidden="true"></i> 电子邮箱
</a>
</li>
<!--
<li>
<a href="http://link-to-whatever-social-network.com/user/" itemprop="sameAs">
<i class="fa fa-fw" aria-hidden="true"></i> Custom Social Profile Link
</a>
</li>
-->
</ul>
</div>
</div>
</div>
<article class="page" itemscope itemtype="http://schema.org/CreativeWork">
<meta itemprop="headline" content="客户管理系统微服务化实战-PartI">
<meta itemprop="description" content="客户管理系统微服务化实战-PartI">
<meta itemprop="datePublished" content="August 28, 2018">
<meta itemprop="dateModified" content="August 28, 2018">
<div class="page__inner-wrap">
<header>
<h1 class="page__title" itemprop="headline">客户管理系统微服务化实战-PartI
</h1>
<p class="page__meta"><i class="fa fa-clock-o" aria-hidden="true"></i>
5 分钟 阅读
</p>
</header>
<section class="page__content" itemprop="text">
<h2 id="客户管理系统微服务化实战-parti">客户管理系统微服务化实战-PartI</h2>
<h2 id="ddd场景极速开发">——DDD、场景、极速开发</h2>
<p>在今年的LC3大会上,ServiceComb展台所展示的demo视频<code class="language-plaintext highlighter-rouge">“30分钟开发雏形CRM应用”</code>引起了参会者的广泛关注,大家纷纷对其背后的技术表现出浓厚的兴趣。本文将从房地产企业的客户管理管理场景入手,使用领域驱动设计,深入技术细节,详解如何快速开发落地一个微服务化的客户管理系统。</p>
<h1 id="1-牛刀小试">1. 牛刀小试</h1>
<p>打开浏览器,输入地址<code class="language-plaintext highlighter-rouge">http://start.servicecomb.io/</code>打开SERVICECOMB SPRING INITIALIZR,修改Project Metadata中的<code class="language-plaintext highlighter-rouge">Group</code><code class="language-plaintext highlighter-rouge">Artifact</code>和ServiceComb Parameters中的<code class="language-plaintext highlighter-rouge">ServiceCenter Address</code><code class="language-plaintext highlighter-rouge">Governance</code>等,点击Generate Project,解压生成下载的demo.zip。</p>
<p><img src="/assets/images/scaffold/StartServiceComb.io.png" alt="SERVICECOMB SPRING INITIALIZR截图" /></p>
<p>运行它也很简单,使用IDE打开项目,DEBUG -&gt; Application.java,或在命令行:</p>
<p>稍等微服务启动就绪,打开浏览器输入<code class="language-plaintext highlighter-rouge">http://localhost:9080/hello</code>验证一下:</p>
<p><img src="/assets/images/scaffold/OutputHello.png" alt="输出hello截图" /></p>
<p>是不是非常轻松呢?</p>
<h1 id="2-脚手架">2. 脚手架</h1>
<p>在建筑领域,脚手架是施工现场为方便工人操作并解决垂直和水平运输而搭设的各种支架以及平台。</p>
<p><img src="/assets/images/scaffold/Scaffold.jpg" alt="scaffold" /></p>
<p>在软件开发领域,它引申为预提供一些基础框架代码加速开发过程,避免从零开始构建项目。用户只需要依据需求场景选择合适的脚手架,然后填充定制的业务逻辑即可,不必再去处理一些基础功能,例如数据库连接、日志实现、RPC传输等。</p>
<p>微服务框架一般都会提供脚手架功能,例如Spring,提供了SPRING INITIALIZR;ServiceComb基于SPRING INITIALIZR,提供了更具优势的特性:</p>
<ol>
<li>生成的项目除了在POM中自动添加必要的依赖,还会提供Producer和Consumer示例代码(Hello World);</li>
<li>会进一步提供<strong>Edge Server</strong><strong>Authcation Server</strong>等更贴近业务的脚手架项目,让用户能快速构建体系完整的微服务系统。</li>
</ol>
<p>那么什么叫一个完整的微服务系统呢?我们可以拿一个具体的场景做例子,会更有感觉:</p>
<h1 id="3-场景地产crm">3. 场景:地产CRM</h1>
<p>您经营着一家房地产开发商,销售房产,迫切需要一套销售系统,考虑到微服务的优势,您决定使用微服务的方式构建系统;主要的业务流程也非常简单:用户前来购买购买产品(房产),首先需要登记用户信息,并缴纳一定数量的定金,待交易当日,挑选心仪的产品(房产),支付尾款,完成交易。</p>
<h2 id="31--使用ddd指导地产crm系统的设计">3.1 使用DDD指导地产CRM系统的设计</h2>
<p>微服务系统的设计方面,领域驱动设计(Domain-Driven Design,DDD)是一个从业务出发的好选择,它由Eric Evans提出,是一种全新的系统设计和建模方法,这里的模型指的就是领域模型(Domain Model)。领域模型通过聚合(Aggregate)组织在一起,聚合间有明显的业务边界,这些边界将领域划分为一个个限界上下文(Bounded Context)。Martin Fowler对它们都有详细的<a href="https://martinfowler.com/tags/domain%20driven%20design.html">解读</a></p>
<p>理论概念都搞清楚了,那么怎么来找模型和聚合呢?一个非常流行的方法就是<a href="https://en.wikipedia.org/wiki/Event_storming">Event Storming</a>,它是由Alberto Brandolini发明,经历了DDD社区和很多团队的实践,也是一种非常有参与感的团队活动:</p>
<p><img src="/assets/images/scaffold/EventStorming.png" alt="EventStorming" /></p>
<p>上图就是我们对地产CRM这个场景使用Event Storming探索的结果,现在我们能够将限界上下文清晰的梳理出来:</p>
<p><img src="/assets/images/scaffold/BoundedContext.png" alt="BoundedContext" /></p>
<blockquote>
<p>提示:Event Storming是一项非常有创造性的活动,也是一个持续讨论和反复改进的过程,不同的团队关注的核心域(Core Domain)不同,得到的最终结果也会有差异。我们的目的是为了演示完整的微服务系统构建的过程,并不涉及商业核心竞争力方面的探讨,因此没有Core Domain和Sub Domain之类的偏重。</p>
</blockquote>
<h2 id="32-将分析成果转化为方案域设计">3.2 将分析成果转化为方案域设计</h2>
<p>当我们完成所有的限界上下文的识别后,可以直接将它们落地为微服务:</p>
<p><img src="/assets/images/scaffold/MicroserviceDesign1.png" alt="MicroserviceDesign1" /></p>
<ol>
<li>用户服务:提供用户信息管理服务,这里保存这用户的账号和密码,负责登录和认证;</li>
<li>产品(房产)服务:提供产品管理服务,保存着房产的信息诸如价格、是否已售出等信息;</li>
<li>支付服务:提供交易时支付服务,模拟对接银行支付定金,以及购房时支付尾款;</li>
</ol>
<p>由于完成一笔交易是一个复杂的流程,与这三个微服务都有关联,因此我们引入了一个复合服务——交易服务:</p>
<p><img src="/assets/images/scaffold/MicroserviceDesign2.png" alt="MicroserviceDesign2" /></p>
<ol>
<li>交易服务:提供产品交易服务,它通过编排调用将整个交易流程串起来,交易服务中有两个流程:
<ul>
<li>定金支付</li>
</ul>
<p>Step1:通过用户服务验证用户身份;</p>
<p>Step2:通过支付服务请求银行扣款,增加定金账号内的定金;</p>
</li>
</ol>
<ul>
<li>
<p>购房交易</p>
<p>Step1:通过用户服务验证用户身份;</p>
<p>Step2:通过资源服务确定用户希望购买的资源(房产)尚未售出;</p>
<p>Step3:通过资源服务标记目标资源(房产)已售出;</p>
<p>Step4:通过支付服务请求扣减定金账号内的定金,以及银行扣剩下的尾款;</p>
<p>最后两个步骤需要保证事务一致性,其中Step4包含两个扣款操作。</p>
</li>
</ul>
<p>之后,我们引入Edge服务提供统一入口:</p>
<p><img src="/assets/images/scaffold/MicroserviceDesign3.png" alt="MicroserviceDesign3" /></p>
<ol>
<li>Edge服务:很多时候也被称为API网关(API Gateway),负责集中认证、动态路由等等;</li>
</ol>
<blockquote>
<p>提示:Edge服务需要依赖服务注册-发现机制,因此同时导入了ServiceCenter。</p>
</blockquote>
<p>最后还需要提供UI:</p>
<p><img src="/assets/images/scaffold/MicroserviceDesign4.png" alt="MicroserviceDesign4" /></p>
<ol>
<li>前端UI(同样以微服务方式提供):用户交互界面;</li>
</ol>
<p>至此,DDD设计地产CRM的工作就结束了。</p>
<h1 id="4-快速实现客户关系管理系统的用户服务">4. 快速实现客户关系管理系统的用户服务</h1>
<h2 id="41-用户微服务并不简单">4.1 用户微服务并不简单</h2>
<p>用户微服务是所有系统中不可或缺的部分,它承载了认证和授权等核心功能——无论是登录一个网站、还是打开一个APP,当涉及到需要身份识别后才能够执行的操作,都需要用户微服务把关。例如观看视频网站上的视频,匿名用户会插播广告,如果希望屏蔽广告,则需要登录并购买VIP会员,登录即是身份认证的过程,而VIP屏蔽广告即是授权的过程。</p>
<h5 id="认证">认证</h5>
<p>认证不仅仅是一次性验证用户名和密码的过程,还需要能反复使用认证的结果,确保后继所有操作都是合法的,这就涉及到“有状态”,但HTTP是一个无状态协议,如何能够将登录成功后的认证信息与后继的请求关联起来呢?</p>
<p>我们非常熟悉的做法是使用Session或Cookie:</p>
<ul>
<li>Session存储在服务端,因此具备良好的防篡改能力,但弊端是使服务有状态,微服务系统中,同一个微服务会依据系统压力的大小弹性伸缩出多个运行实例负载均衡,跨实例访问会状态丢失。</li>
<li>Cookie存储在客户端,它正好与Session相反,优势是服务不必保持状态,但弊端是客户比较容易的篡改Cookie信息,例如修改过期时间以逃避验证,而且浏览器对Cookie也有较多限制。</li>
</ul>
<p>那么,如何兼顾这两方面的需求呢?Token就是一个比较好的解决方案。</p>
<p>Token中文翻译为令牌,它将登录认证后的信息签名后返回,服务端不保存,客户端请求的时候将认证的完整信息附带上提供给服务端验签,签名可以保证信息不被篡改。了解了了解Token的原理,自然要关注Token的格式,JWT就是这样一个基于JSON的开放标准<a href="https://tools.ietf.org/html/rfc7519">RFC-7519</a></p>
<h6 id="jwt-java-web-token规范">JWT (Java Web Token)规范</h6>
<p>简而言之JWT规范由三部分构成:</p>
<ol>
<li>Header: 声明Token的类型也就是JWT,以及加密算法,例如:</li>
</ol>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"typ"</span><span class="p">:</span><span class="w"> </span><span class="s2">"JWT"</span><span class="p">,</span><span class="w">
</span><span class="nl">"alg"</span><span class="p">:</span><span class="w"> </span><span class="s2">"HS256"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<ol>
<li>Playload:存放有效信息,既包含标准签发者、用户、签发时间、过期时间,唯一标识等信息;也可以存放用户自定义的声明信息,例如权限控制相关的内容,例如:</li>
</ol>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"sub"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1234567890"</span><span class="p">,</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"YangYong Zheng"</span><span class="p">,</span><span class="w">
</span><span class="nl">"iat"</span><span class="p">:</span><span class="w"> </span><span class="mi">1516239022</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<ol>
<li>Signature:签名信息,包含Header和Playload的原始信息(Base64编码过)以及签名过后的信息。</li>
</ol>
<blockquote>
<p>提示:JWT IO提供了<a href="https://jwt.io/">在线编码和解码工具</a></p>
</blockquote>
<h5 id="授权">授权</h5>
<p>授权的本意是指将完成某项工作所必须的权力授给下属人员,在软件系统中往往引申为使人或角色具备访问特定资源或更改行为的许可。例如之前提到的VIP屏蔽广告,即是视频网站允许播放终端在特定的帐号登录后跳过广告播放环节(行为)的许可。</p>
<p>授权系统比较常见的做法有ACL和RBAC:</p>
<ul>
<li>ACL:ACL全称Access Control List,它是以受控资源为核心,每一个受控资源,都有一个权限控制列表记录哪些用户或角色对这项资源执行具体操作(也被称为授权点)的权限设置,例如查询(可见)、修改、删除等等。Windows中的文件系统安全即是一个经典的ACL实现案例:</li>
</ul>
<p><img src="/assets/images/scaffold/ACL.png" alt="ACL" /></p>
<ul>
<li>RBAC:RBAC全称Role Based Access Control,与ACL相比,它以角色为核心,权限落地在角色上,不为特定用户授权。它的优势是大幅简化了用户与权限的管理,在受控对象不多或控制粒度要求不高(例如接口访问控制)的场景下非常适用。</li>
</ul>
<p><img src="/assets/images/scaffold/RBAC.png" alt="RBAC" /></p>
<p>由于微服务系统的权限控制主要是接口访问控制上,并且多采用用户组方式组织用户,因此RBAC是比较流行的做法。</p>
<h2 id="42-实现用户微服务">4.2 实现用户微服务</h2>
<h5 id="第一步创建微服务项目">第一步:创建微服务项目</h5>
<p>使用SERVICECOMB SPRING INITIALIZR创建用户微服务,创建完毕后使用IDEA或Eclipse打开项目,我们删掉HelloImpl和HelloConsumer,之后添加自己的实现。</p>
<h5 id="第二步使用mysql持久化用户信息">第二步:使用MySQL持久化用户信息</h5>
<p>用户微服务需要持久化用户信息,我们使用MySQL数据库,ORM使用Spring Data JPA:</p>
<h6 id="引入依赖">引入依赖</h6>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;dependency&gt;</span>
<span class="nt">&lt;groupId&gt;</span>mysql<span class="nt">&lt;/groupId&gt;</span>
<span class="nt">&lt;artifactId&gt;</span>mysql-connector-java<span class="nt">&lt;/artifactId&gt;</span>
<span class="nt">&lt;/dependency&gt;</span>
<span class="nt">&lt;dependency&gt;</span>
<span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
<span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-data-jpa<span class="nt">&lt;/artifactId&gt;</span>
<span class="nt">&lt;/dependency&gt;</span>
</code></pre></div></div>
<h6 id="定义存储user信息的userentity实体">定义存储User信息的UserEntity实体</h6>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Entity</span>
<span class="nd">@Table</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"T_User"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">UserEntity</span> <span class="o">{</span>
<span class="nd">@Id</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">password</span><span class="o">;</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getName</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">name</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setName</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getPassword</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">password</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setPassword</span><span class="o">(</span><span class="nc">String</span> <span class="n">password</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">password</span> <span class="o">=</span> <span class="n">password</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nf">UserEntity</span><span class="o">()</span> <span class="o">{</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nf">UserEntity</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">,</span> <span class="nc">String</span> <span class="n">password</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">password</span> <span class="o">=</span> <span class="n">password</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>在CodeFist模式下,Spring Data JPA会在数据库中自动创建T_User表与此实体映射。</p>
<h6 id="实现userentity实体的repository">实现UserEntity实体的Repository</h6>
<p>我们继承JPA的PagingAndSortingRepository来实现ORM操作</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Repository</span>
<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">UserRepository</span> <span class="kd">extends</span> <span class="nc">PagingAndSortingRepository</span><span class="o">&lt;</span><span class="nc">UserEntity</span><span class="o">,</span> <span class="nc">Long</span><span class="o">&gt;</span> <span class="o">{</span>
<span class="nc">UserEntity</span> <span class="nf">findByName</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<h6 id="配置数据库连接">配置数据库连接</h6>
<p>在项目的<code class="language-plaintext highlighter-rouge">resources</code>目录下新增<code class="language-plaintext highlighter-rouge">application.properties</code>文件,写入数据库连接信息:</p>
<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">spring.datasource.url</span><span class="p">=</span><span class="s">jdbc:mysql://localhost:3306/user_db?useSSL=false</span>
<span class="py">spring.datasource.username</span><span class="p">=</span><span class="s">root</span>
<span class="py">spring.datasource.password</span><span class="p">=</span><span class="s">pwd</span>
<span class="py">spring.jpa.hibernate.ddl-auto</span><span class="p">=</span><span class="s">update</span>
<span class="py">spring.jpa.properties.hibernate.dialect</span><span class="p">=</span><span class="s">org.hibernate.dialect.MySQL5Dialect</span>
</code></pre></div></div>
<blockquote>
<p>提示:关于Spring Data JPA的更多资料请参见<a href="https://projects.spring.io/spring-data-jpa/">这篇文档</a>,为了能够简化依赖的引入我们实际上使用的是Spring Boot JPA Starter,详细的例子请参见<a href="https://spring.io/guides/gs/accessing-data-jpa/">这篇文档</a></p>
</blockquote>
<h5 id="第三步实现jwt认证">第三步:实现JWT认证</h5>
<h6 id="定义jwt接口">定义JWT接口</h6>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">TokenStore</span> <span class="o">{</span>
<span class="nc">String</span> <span class="nf">generate</span><span class="o">(</span><span class="nc">String</span> <span class="n">userName</span><span class="o">);</span>
<span class="kt">boolean</span> <span class="nf">validate</span><span class="o">(</span><span class="nc">String</span> <span class="n">token</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>generate用于生成Token,validate用于验证Token是否正确。</p>
<h6 id="实现tokenstore">实现TokenStore</h6>
<p>我们使用<a href="https://github.com/jwtk/jjwt">jjwt</a>提供的JWT实现,创建JwtTokenStore类,继承TokenStore接口,并重写方法:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span>
<span class="nd">@Component</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">JwtTokenStore</span> <span class="kd">implements</span> <span class="nc">TokenStore</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">Logger</span> <span class="no">LOGGER</span> <span class="o">=</span> <span class="nc">LoggerFactory</span><span class="o">.</span><span class="na">getLogger</span><span class="o">(</span><span class="nc">JwtTokenStore</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">secretKey</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">secondsToExpire</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">JwtTokenStore</span><span class="o">()</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">secretKey</span> <span class="o">=</span> <span class="s">"someSecretKeyForAuthentication"</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">secondsToExpire</span> <span class="o">=</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">24</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nf">JwtTokenStore</span><span class="o">(</span><span class="nc">String</span> <span class="n">secretKey</span><span class="o">,</span> <span class="kt">int</span> <span class="n">secondsToExpire</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">secretKey</span> <span class="o">=</span> <span class="n">secretKey</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">secondsToExpire</span> <span class="o">=</span> <span class="n">secondsToExpire</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">generate</span><span class="o">(</span><span class="nc">String</span> <span class="n">userName</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">Jwts</span><span class="o">.</span><span class="na">builder</span><span class="o">().</span><span class="na">setSubject</span><span class="o">(</span><span class="n">userName</span><span class="o">)</span>
<span class="o">.</span><span class="na">setExpiration</span><span class="o">(</span><span class="nc">Date</span><span class="o">.</span><span class="na">from</span><span class="o">(</span><span class="nc">ZonedDateTime</span><span class="o">.</span><span class="na">now</span><span class="o">().</span><span class="na">plusSeconds</span><span class="o">(</span><span class="n">secondsToExpire</span><span class="o">).</span><span class="na">toInstant</span><span class="o">()))</span>
<span class="o">.</span><span class="na">signWith</span><span class="o">(</span><span class="no">HS512</span><span class="o">,</span> <span class="n">secretKey</span><span class="o">).</span><span class="na">compact</span><span class="o">();</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">validate</span><span class="o">(</span><span class="nc">String</span> <span class="n">token</span><span class="o">)</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">StringUtils</span><span class="o">.</span><span class="na">isNotEmpty</span><span class="o">(</span><span class="nc">Jwts</span><span class="o">.</span><span class="na">parser</span><span class="o">()</span>
<span class="o">.</span><span class="na">setSigningKey</span><span class="o">(</span><span class="n">secretKey</span><span class="o">).</span><span class="na">parseClaimsJws</span><span class="o">(</span><span class="n">token</span><span class="o">).</span><span class="na">getBody</span><span class="o">().</span><span class="na">getSubject</span><span class="o">());</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">JwtException</span> <span class="o">|</span> <span class="nc">IllegalArgumentException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="no">LOGGER</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"validateToken token : "</span> <span class="o">+</span> <span class="n">token</span> <span class="o">+</span> <span class="s">" failed"</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h5 id="第四步实现用户服务">第四步:实现用户服务</h5>
<h6 id="定义userservice接口">定义UserService接口</h6>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">UserService</span> <span class="o">{</span>
<span class="nc">ResponseEntity</span><span class="o">&lt;</span><span class="nc">Boolean</span><span class="o">&gt;</span> <span class="nf">logon</span><span class="o">(</span><span class="nc">UserDTO</span> <span class="n">user</span><span class="o">);</span>
<span class="nc">ResponseEntity</span><span class="o">&lt;</span><span class="nc">Boolean</span><span class="o">&gt;</span> <span class="nf">login</span><span class="o">(</span><span class="nc">UserDTO</span> <span class="n">user</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>logon用于新用户注册,login用于用户登录验证,UserDTO用于参数传递:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">UserDTO</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">password</span><span class="o">;</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getName</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">name</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getPassword</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">password</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nf">UserDTO</span><span class="o">()</span> <span class="o">{</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nf">UserDTO</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">,</span> <span class="nc">String</span> <span class="n">password</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">password</span> <span class="o">=</span> <span class="n">password</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h6 id="实现并发布userservice">实现并发布UserService</h6>
<p>创建UserServiceImpl,继承<code class="language-plaintext highlighter-rouge">UserService</code>接口:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RestSchema</span><span class="o">(</span><span class="n">schemaId</span> <span class="o">=</span> <span class="s">"user"</span><span class="o">)</span>
<span class="nd">@RequestMapping</span><span class="o">(</span><span class="n">path</span> <span class="o">=</span> <span class="s">"/"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">UserServiceImpl</span> <span class="kd">implements</span> <span class="nc">UserService</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">UserRepository</span> <span class="n">repository</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">TokenStore</span> <span class="n">tokenStore</span><span class="o">;</span>
<span class="nd">@Autowired</span>
<span class="kd">public</span> <span class="nf">UserServiceImpl</span><span class="o">(</span><span class="nc">UserRepository</span> <span class="n">repository</span><span class="o">,</span> <span class="nc">TokenStore</span> <span class="n">tokenStore</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">repository</span> <span class="o">=</span> <span class="n">repository</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">tokenStore</span> <span class="o">=</span> <span class="n">tokenStore</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="nd">@PostMapping</span><span class="o">(</span><span class="n">path</span> <span class="o">=</span> <span class="s">"logon"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">ResponseEntity</span><span class="o">&lt;</span><span class="nc">Boolean</span><span class="o">&gt;</span> <span class="nf">logon</span><span class="o">(</span><span class="nd">@RequestBody</span> <span class="nc">UserDTO</span> <span class="n">user</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">validateUser</span><span class="o">(</span><span class="n">user</span><span class="o">))</span> <span class="o">{</span>
<span class="nc">UserEntity</span> <span class="n">dbUser</span> <span class="o">=</span> <span class="n">repository</span><span class="o">.</span><span class="na">findByName</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>
<span class="k">if</span> <span class="o">(</span><span class="n">dbUser</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">UserEntity</span> <span class="n">entity</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">UserEntity</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">getName</span><span class="o">(),</span> <span class="n">user</span><span class="o">.</span><span class="na">getPassword</span><span class="o">());</span>
<span class="n">repository</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">entity</span><span class="o">);</span>
<span class="k">return</span> <span class="k">new</span> <span class="nc">ResponseEntity</span><span class="o">&lt;&gt;(</span><span class="kc">true</span><span class="o">,</span> <span class="nc">HttpStatus</span><span class="o">.</span><span class="na">OK</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">InvocationException</span><span class="o">(</span><span class="no">BAD_REQUEST</span><span class="o">,</span> <span class="s">"user name had exist"</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">InvocationException</span><span class="o">(</span><span class="no">BAD_REQUEST</span><span class="o">,</span> <span class="s">"incorrect user"</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="nd">@PostMapping</span><span class="o">(</span><span class="n">path</span> <span class="o">=</span> <span class="s">"login"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">ResponseEntity</span><span class="o">&lt;</span><span class="nc">Boolean</span><span class="o">&gt;</span> <span class="nf">login</span><span class="o">(</span><span class="nd">@RequestBody</span> <span class="nc">UserDTO</span> <span class="n">user</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">validateUser</span><span class="o">(</span><span class="n">user</span><span class="o">))</span> <span class="o">{</span>
<span class="nc">UserEntity</span> <span class="n">dbUser</span> <span class="o">=</span> <span class="n">repository</span><span class="o">.</span><span class="na">findByName</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>
<span class="k">if</span> <span class="o">(</span><span class="n">dbUser</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">dbUser</span><span class="o">.</span><span class="na">getPassword</span><span class="o">().</span><span class="na">equals</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">getPassword</span><span class="o">()))</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">token</span> <span class="o">=</span> <span class="n">tokenStore</span><span class="o">.</span><span class="na">generate</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>
<span class="nc">HttpHeaders</span> <span class="n">headers</span> <span class="o">=</span> <span class="n">generateAuthenticationHeaders</span><span class="o">(</span><span class="n">token</span><span class="o">);</span>
<span class="c1">//add authentication header</span>
<span class="k">return</span> <span class="k">new</span> <span class="nc">ResponseEntity</span><span class="o">&lt;&gt;(</span><span class="kc">true</span><span class="o">,</span> <span class="n">headers</span><span class="o">,</span> <span class="nc">HttpStatus</span><span class="o">.</span><span class="na">OK</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">InvocationException</span><span class="o">(</span><span class="no">BAD_REQUEST</span><span class="o">,</span> <span class="s">"wrong password"</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">InvocationException</span><span class="o">(</span><span class="no">BAD_REQUEST</span><span class="o">,</span> <span class="s">"user name not exist"</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">InvocationException</span><span class="o">(</span><span class="no">BAD_REQUEST</span><span class="o">,</span> <span class="s">"incorrect user"</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="nf">validateUser</span><span class="o">(</span><span class="nc">UserDTO</span> <span class="n">user</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">user</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="nc">StringUtils</span><span class="o">.</span><span class="na">isNotEmpty</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">getName</span><span class="o">())</span> <span class="o">&amp;&amp;</span> <span class="nc">StringUtils</span><span class="o">.</span><span class="na">isNotEmpty</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">getPassword</span><span class="o">());</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="nc">HttpHeaders</span> <span class="nf">generateAuthenticationHeaders</span><span class="o">(</span><span class="nc">String</span> <span class="n">token</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">HttpHeaders</span> <span class="n">headers</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HttpHeaders</span><span class="o">();</span>
<span class="n">headers</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="no">AUTHORIZATION</span><span class="o">,</span> <span class="n">token</span><span class="o">);</span>
<span class="k">return</span> <span class="n">headers</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>登录成功后,会从TokenStore生成Token,并将其写入Key为<code class="language-plaintext highlighter-rouge">AUTHORIZATION</code>的Header。</p>
<p>由于我们允许任何用户注册和登录,所以目前还没有授权的需求,经过上面四步,具有基本注册和登录功能的用户微服务就构建好了。</p>
<h2 id="43-验证实现的用户服务">4.3 验证实现的用户服务</h2>
<p>启动用户微服务,我们先注册一个账号:</p>
<p><img src="/assets/images/scaffold/TestLogon.png" alt="TestLogon" /></p>
<p>显示注册成功,现在我们使用这个账号登录:</p>
<p><img src="/assets/images/scaffold/TestLogin.png" alt="TestLogin" /></p>
<p>返回登录成功,Response中已经包含了<code class="language-plaintext highlighter-rouge">AUTHORIZATION</code>Header,后继的所有请求都需要使用这个Token值进行合法认证。</p>
<p>至此,实现客户关系管理系统的用户服务工作就结束了,现在我们会将目光转移到Edge服务,通过Edge服务作为微服务调用的统一入口,在它之上构建统一认证,应对海量级调用的挑战。</p>
<h1 id="5-开发高性能边缘服务">5 开发高性能边缘服务</h1>
<h2 id="51-什么是边缘服务edge-service">5.1 什么是边缘服务(Edge Service)</h2>
<p>边缘服务也是一个微服务,微服务化系统通常使用边缘服务(Edge Service)作为所有其它微服务的统一入口,因此它也常常会被称为API Gateway,使用边缘服务的好处有如下几点:</p>
<ul>
<li>动态路由:动态配置URL地址与微服务之间的对应关系,便于扩展,以及实现版本灰度发布等;</li>
<li>统一认证:在入口处进行访问认证,避免需要在所有的微服务中都承载重复的认证机制;</li>
<li>集中监控:与统一认证类似,在边缘服务对入口调用进行监控,容易统计流量信息。</li>
</ul>
<h2 id="52-边缘服务的作用和原理">5.2 边缘服务的作用和原理</h2>
<p>我们先来看不使用边缘服务,UI直接调用用户服务的场景:</p>
<p><img src="/assets/images/scaffold/DirectInvoke.png" alt="DirectInvoke" /></p>
<p>可以看出这种调用方式,UI缺乏一定的灵活性,体现在:</p>
<ul>
<li>UI的实现绑定了Chassis的编程语言Java,无法使用PHP等其它前端技术开发;</li>
<li>UI访问微服务的路径无法动态配置,如果作为后端的微服务系统发生调整,则UI很可能需要修改;</li>
<li>UI很容易混入复合(编排)调用的逻辑,使得结构变得复杂难以维护。</li>
</ul>
<p>我们再看引入边缘服务后,UI如何通过边缘服务调用用户服务:</p>
<p><img src="/assets/images/scaffold/InvokeViaEdge.png" alt="InvokeViaEdge" /></p>
<p>Edge服务将在<code class="language-plaintext highlighter-rouge">9090</code>端口上接受http rest调用,我们设计了下面的转发规则:</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://{edge-host-name}:9090/{ServiceComb微服务Name}/{服务路径&amp;参数}
</code></pre></div></div>
<p>用户微服务名(<code class="language-plaintext highlighter-rouge">service_description.name</code>)是<code class="language-plaintext highlighter-rouge">user-service</code>,因此login调用URL:<em>cse://user-service/login</em>可以通过:<em>http://{edge-host-name}:9090/user-service/login</em> 访问。</p>
<p>如此一来,微服务名成为了路径的一部分,http协议的<code class="language-plaintext highlighter-rouge">hostname</code><code class="language-plaintext highlighter-rouge">port</code>将固定指向Edge服务保持不变,灵活性大大增加了。</p>
<p>到此我们还可以再做一点点改进,引入一个自定义配置<code class="language-plaintext highlighter-rouge">edge.routing-short-path.{简称}</code>,映射微服务名:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>edge:
routing-short-path:
user: user-service
</code></pre></div></div>
<p>上面的配置代表:<em>http://{edge-host-name}:9090/user/login</em> 等效于:<em>http://{edge-host-name}:9090/user-service/login</em> ,如此一来:</p>
<ol>
<li>URL能够更加简洁;</li>
<li>当微服务名发生变化,只需要调整对应的配置,不需要更改前端UI路径代码。</li>
</ol>
<h2 id="53-实现边缘服务">5.3 实现边缘服务</h2>
<h5 id="第一步引入edge-core依赖">第一步:引入Edge Core依赖</h5>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;dependency&gt;</span>
<span class="nt">&lt;groupId&gt;</span>org.apache.servicecomb<span class="nt">&lt;/groupId&gt;</span>
<span class="nt">&lt;artifactId&gt;</span>edge-core<span class="nt">&lt;/artifactId&gt;</span>
<span class="nt">&lt;/dependency&gt;</span>
</code></pre></div></div>
<h5 id="第二步编写调度器dispatcher">第二步:编写调度器Dispatcher</h5>
<p>Edge服务的核心就是调度器Dispatcher,ServiceComb Edge Core中的Dispatcher基于高性能的Vertx Reactive,轻松应对百万量级API请求的挑战;只需要继承AbstractEdgeDispatcher抽象类,添加对应的逻辑即可:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">EdgeDispatcher</span> <span class="kd">extends</span> <span class="nc">AbstractEdgeDispatcher</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">Logger</span> <span class="no">LOGGER</span> <span class="o">=</span> <span class="nc">LoggerFactory</span><span class="o">.</span><span class="na">getLogger</span><span class="o">(</span><span class="nc">EdgeDispatcher</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="c1">//此Dispatcher的优先级,Order级越小,路由策略优先级越高</span>
<span class="kd">public</span> <span class="kt">int</span> <span class="nf">getOrder</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="mi">10000</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">//初始化Dispatcher的路由策略</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">init</span><span class="o">(</span><span class="nc">Router</span> <span class="n">router</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">///捕获 {ServiceComb微服务Name}/{服务路径&amp;参数} 的URL</span>
<span class="nc">String</span> <span class="n">regex</span> <span class="o">=</span> <span class="s">"/([^\\\\/]+)/(.*)"</span><span class="o">;</span>
<span class="n">router</span><span class="o">.</span><span class="na">routeWithRegex</span><span class="o">(</span><span class="n">regex</span><span class="o">).</span><span class="na">handler</span><span class="o">(</span><span class="nc">CookieHandler</span><span class="o">.</span><span class="na">create</span><span class="o">());</span>
<span class="n">router</span><span class="o">.</span><span class="na">routeWithRegex</span><span class="o">(</span><span class="n">regex</span><span class="o">).</span><span class="na">handler</span><span class="o">(</span><span class="n">createBodyHandler</span><span class="o">());</span>
<span class="n">router</span><span class="o">.</span><span class="na">routeWithRegex</span><span class="o">(</span><span class="n">regex</span><span class="o">).</span><span class="na">failureHandler</span><span class="o">(</span><span class="k">this</span><span class="o">::</span><span class="n">onFailure</span><span class="o">).</span><span class="na">handler</span><span class="o">(</span><span class="k">this</span><span class="o">::</span><span class="n">onRequest</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">//处理请求,请注意</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">onRequest</span><span class="o">(</span><span class="nc">RoutingContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">String</span><span class="o">&gt;</span> <span class="n">pathParams</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="na">pathParams</span><span class="o">();</span>
<span class="c1">//从匹配的param0拿到{ServiceComb微服务Name}</span>
<span class="kd">final</span> <span class="nc">String</span> <span class="n">service</span> <span class="o">=</span> <span class="n">pathParams</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"param0"</span><span class="o">);</span>
<span class="c1">//从匹配的param1拿到{服务路径&amp;参数}</span>
<span class="nc">String</span> <span class="n">path</span> <span class="o">=</span> <span class="s">"/"</span> <span class="o">+</span> <span class="n">pathParams</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"param1"</span><span class="o">);</span>
<span class="c1">//还记得我们之前说的做出一点点改进吗?引入一个自定义配置edge.routing-short-path.{简称},映射微服务名;如果简称没有配置,那么就认为直接是微服务的名</span>
<span class="kd">final</span> <span class="nc">String</span> <span class="n">serviceName</span> <span class="o">=</span> <span class="nc">DynamicPropertyFactory</span><span class="o">.</span><span class="na">getInstance</span><span class="o">()</span>
<span class="o">.</span><span class="na">getStringProperty</span><span class="o">(</span><span class="s">"edge.routing-short-path."</span> <span class="o">+</span> <span class="n">service</span><span class="o">,</span> <span class="n">service</span><span class="o">).</span><span class="na">get</span><span class="o">();</span>
<span class="c1">//创建一个Edge转发</span>
<span class="nc">EdgeInvocation</span> <span class="n">edgeInvocation</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">EdgeInvocation</span><span class="o">();</span>
<span class="c1">//允许接受任意版本的微服务实例作为Provider,未来我们会使用此(设置版本)能力实现灰度发布</span>
<span class="n">edgeInvocation</span><span class="o">.</span><span class="na">setVersionRule</span><span class="o">(</span><span class="nc">DefinitionConst</span><span class="o">.</span><span class="na">VERSION_RULE_ALL</span><span class="o">);</span>
<span class="n">edgeInvocation</span><span class="o">.</span><span class="na">init</span><span class="o">(</span><span class="n">serviceName</span><span class="o">,</span> <span class="n">context</span><span class="o">,</span> <span class="n">path</span><span class="o">,</span> <span class="n">httpServerFilters</span><span class="o">);</span>
<span class="n">edgeInvocation</span><span class="o">.</span><span class="na">edgeInvoke</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h5 id="第三步加载调度器dispatcher">第三步:加载调度器Dispatcher</h5>
<p>ServiceComb Edge使用SPI(Service Provider Interface)的方式加载已经编写好的调度器Dispatcher,在resources目录下创建<code class="language-plaintext highlighter-rouge">META-INF.services/org.apache.servicecomb.transport.rest.vertx.VertxHttpDispatcher</code>配置文件,写入上一步EdgeDispatcher的类全名:</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{EdgeDispatcher的包名}.EdgeDispatcher
</code></pre></div></div>
<h5 id="第四步配置microserviceyaml">第四步:配置microservice.yaml</h5>
<p>边缘服务本身也是一个微服务,同样需要配置microservice.yaml:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">APPLICATION_ID</span><span class="pi">:</span> <span class="s">scaffold</span>
<span class="na">service_description</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">edge-service</span>
<span class="na">version</span><span class="pi">:</span> <span class="s">0.0.1</span>
<span class="na">servicecomb</span><span class="pi">:</span>
<span class="na">service</span><span class="pi">:</span>
<span class="na">registry</span><span class="pi">:</span>
<span class="c1">#配置ServiceCenter使得Edge能够发现其他微服务</span>
<span class="na">address</span><span class="pi">:</span> <span class="s">http://127.0.0.1:30100</span>
<span class="c1">#配置Rest Endpoint</span>
<span class="na">rest</span><span class="pi">:</span>
<span class="na">address</span><span class="pi">:</span> <span class="s">0.0.0.0:9090</span>
<span class="c1">#自定义的简称机制配置(这是我们自行扩展实现的)</span>
<span class="na">edge</span><span class="pi">:</span>
<span class="na">routing-short-path</span><span class="pi">:</span>
<span class="na">user</span><span class="pi">:</span> <span class="s">user-service</span>
</code></pre></div></div>
<blockquote>
<p>提示:</p>
<ol>
<li>除了配置Rest Endpoint,我们也支持配置Highway Endpoint,但Highway Endpoint只支持ServiceComb开发的微服务调用;</li>
<li>microservice.yaml中没有配置Handler,Edge支持所有Consumer端Handler,不支持Producer端Handler,调用链原理如下:</li>
</ol>
<p><img src="/assets/images/scaffold/EdgeOnlySupportConsumerHandler.png" alt="EdgeOnlySupportConsumerHandler" /></p>
</blockquote>
<h2 id="54-验证边缘服务">5.4 验证边缘服务</h2>
<p>启动用户微服务和Edge服务,使用<a href="https://www.getpostman.com/">Postman</a>注册一个用户:</p>
<p><img src="/assets/images/scaffold/LogonViaEdge.png" alt="LogonViaEdge" /></p>
<p>成功,现在我们使用新注册的用户名<code class="language-plaintext highlighter-rouge">ldg</code>登录:</p>
<p><img src="/assets/images/scaffold/LoginViaEdge.png" alt="LoginViaEdge" /></p>
<p>同样成功,并在Response中已经包含了正确的<code class="language-plaintext highlighter-rouge">AUTHORIZATION</code>Header。</p>
<h2 id="55-性能比拼">5.5 性能比拼</h2>
<p>ServiceComb Java Chassis也支持集成Netflix Zuul作为网关服务,我们做了一次性能比较,使用ServiceComb Edge作为网关吞吐能力大幅优于Netflix Zuul,性能测试项目源代码在<a href="https://github.com/zhengyangyong/gateway-perf">这里</a></p>
<h1 id="6-扩展边缘服务支持统一认证">6 扩展边缘服务支持统一认证</h1>
<h2 id="61-设计思路">6.1 设计思路</h2>
<p>正如前面提到的,统一认证的目的是在Edge入口处进行访问认证,避免需要在所有的微服务中都承载重复的认证机制,因此:</p>
<ol>
<li>我们先要将认证功能作为一个独立的Procuder发布出来,使Edge服务能够随时认证Token,我们将其命名为<code class="language-plaintext highlighter-rouge">AuthenticationService</code>,放在用户服务中;</li>
<li>将无需认证的访问请求识别出来,包括:</li>
</ol>
<table>
<thead>
<tr>
<th style="text-align: left">功能</th>
<th style="text-align: left">描述</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">login</td>
<td style="text-align: left">登录验证,通过后为用户生成Token</td>
</tr>
<tr>
<td style="text-align: left">logon</td>
<td style="text-align: left">新用户注册</td>
</tr>
</tbody>
</table>
<p>除此之外其他业务请求都需要做Token认证;</p>
<ol>
<li>Edge服务转发访问请求之前,对需要认证的请求先做统一认证,认证通过之后才转发,我们使用<code class="language-plaintext highlighter-rouge">HttpServerFilter</code>扩展这个能力:</li>
</ol>
<p><img src="/assets/images/scaffold/FilterChain.png" alt="FilterChain" /></p>
<p>统一认证流程时序图为:</p>
<p><img src="/assets/images/scaffold/EdgeAuth.png" alt="EdgeAuth" /></p>
<h2 id="62-实现统一认证">6.2 实现统一认证</h2>
<h5 id="第一步发布认证服务">第一步:发布认证服务</h5>
<h6 id="定义authenticationservice">定义AuthenticationService</h6>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">AuthenticationService</span> <span class="o">{</span>
<span class="nc">String</span> <span class="nf">validate</span><span class="o">(</span><span class="nc">String</span> <span class="n">token</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<h6 id="实现并发布authenticationservice">实现并发布AuthenticationService</h6>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RestSchema</span><span class="o">(</span><span class="n">schemaId</span> <span class="o">=</span> <span class="s">"authentication"</span><span class="o">)</span>
<span class="nd">@RequestMapping</span><span class="o">(</span><span class="n">path</span> <span class="o">=</span> <span class="s">"/"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">AuthenticationServiceImpl</span> <span class="kd">implements</span> <span class="nc">AuthenticationService</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">TokenStore</span> <span class="n">tokenStore</span><span class="o">;</span>
<span class="nd">@Autowired</span>
<span class="kd">public</span> <span class="nf">AuthenticationServiceImpl</span><span class="o">(</span><span class="nc">TokenStore</span> <span class="n">tokenStore</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">tokenStore</span> <span class="o">=</span> <span class="n">tokenStore</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="nd">@GetMapping</span><span class="o">(</span><span class="n">path</span> <span class="o">=</span> <span class="s">"validate"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">validate</span><span class="o">(</span><span class="nc">String</span> <span class="n">token</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">userName</span> <span class="o">=</span> <span class="n">tokenStore</span><span class="o">.</span><span class="na">validate</span><span class="o">(</span><span class="n">token</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">userName</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">InvocationException</span><span class="o">(</span><span class="no">BAD_REQUEST</span><span class="o">,</span> <span class="s">"incorrect token"</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">userName</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h5 id="第二步实现统一认证authenticationfilter">第二步:实现统一认证AuthenticationFilter</h5>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">AuthenticationFilter</span> <span class="kd">implements</span> <span class="nc">HttpServerFilter</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">RestTemplate</span> <span class="n">template</span> <span class="o">=</span> <span class="nc">RestTemplateBuilder</span><span class="o">.</span><span class="na">create</span><span class="o">();</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">USER_SERVICE_NAME</span> <span class="o">=</span> <span class="s">"user-service"</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">EDGE_AUTHENTICATION_NAME</span> <span class="o">=</span> <span class="s">"edge-authentication-name"</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">Set</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="no">NOT_REQUIRED_VERIFICATION_USER_SERVICE_METHODS</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashSet</span><span class="o">&lt;&gt;(</span>
<span class="nc">Arrays</span><span class="o">.</span><span class="na">asList</span><span class="o">(</span><span class="s">"login"</span><span class="o">,</span> <span class="s">"logon"</span><span class="o">,</span> <span class="s">"validate"</span><span class="o">));</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">int</span> <span class="nf">getOrder</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="mi">0</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Response</span> <span class="nf">afterReceiveRequest</span><span class="o">(</span><span class="nc">Invocation</span> <span class="n">invocation</span><span class="o">,</span> <span class="nc">HttpServletRequestEx</span> <span class="n">httpServletRequestEx</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">isInvocationNeedValidate</span><span class="o">(</span><span class="n">invocation</span><span class="o">.</span><span class="na">getMicroserviceName</span><span class="o">(),</span> <span class="n">invocation</span><span class="o">.</span><span class="na">getOperationName</span><span class="o">()))</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">token</span> <span class="o">=</span> <span class="n">httpServletRequestEx</span><span class="o">.</span><span class="na">getHeader</span><span class="o">(</span><span class="no">AUTHORIZATION</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="nc">StringUtils</span><span class="o">.</span><span class="na">isNotEmpty</span><span class="o">(</span><span class="n">token</span><span class="o">))</span> <span class="o">{</span>
<span class="nc">String</span> <span class="n">userName</span> <span class="o">=</span> <span class="n">template</span>
<span class="o">.</span><span class="na">getForObject</span><span class="o">(</span><span class="s">"cse://"</span> <span class="o">+</span> <span class="no">USER_SERVICE_NAME</span> <span class="o">+</span> <span class="s">"/validate?token={token}"</span><span class="o">,</span> <span class="nc">String</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="n">token</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="nc">StringUtils</span><span class="o">.</span><span class="na">isNotEmpty</span><span class="o">(</span><span class="n">userName</span><span class="o">))</span> <span class="o">{</span>
<span class="c1">//Add header</span>
<span class="n">invocation</span><span class="o">.</span><span class="na">getContext</span><span class="o">().</span><span class="na">put</span><span class="o">(</span><span class="no">EDGE_AUTHENTICATION_NAME</span><span class="o">,</span> <span class="n">userName</span><span class="o">);</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">Response</span>
<span class="o">.</span><span class="na">failResp</span><span class="o">(</span><span class="k">new</span> <span class="nc">InvocationException</span><span class="o">(</span><span class="nc">Status</span><span class="o">.</span><span class="na">UNAUTHORIZED</span><span class="o">,</span> <span class="s">"authentication failed, invalid token"</span><span class="o">));</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">Response</span><span class="o">.</span><span class="na">failResp</span><span class="o">(</span>
<span class="k">new</span> <span class="nf">InvocationException</span><span class="o">(</span><span class="nc">Status</span><span class="o">.</span><span class="na">UNAUTHORIZED</span><span class="o">,</span> <span class="s">"authentication failed, missing AUTHORIZATION header"</span><span class="o">));</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="nf">isInvocationNeedValidate</span><span class="o">(</span><span class="nc">String</span> <span class="n">serviceName</span><span class="o">,</span> <span class="nc">String</span> <span class="n">operationPath</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="no">USER_SERVICE_NAME</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">serviceName</span><span class="o">))</span> <span class="o">{</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">String</span> <span class="n">method</span> <span class="o">:</span> <span class="no">NOT_REQUIRED_VERIFICATION_USER_SERVICE_METHODS</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">operationPath</span><span class="o">.</span><span class="na">startsWith</span><span class="o">(</span><span class="n">method</span><span class="o">))</span> <span class="o">{</span>
<span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>别忘了通过SPI机制加载它,在<code class="language-plaintext highlighter-rouge">resources\META-INF\services</code>目录中创建<code class="language-plaintext highlighter-rouge">org.apache.servicecomb.common.rest.filter.HttpServerFilter</code>文件:</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>org.apache.servicecomb.scaffold.edge.filter.AuthenticationFilter
</code></pre></div></div>
<h5 id="第三步在用户微服务中增加修改密码的功能用于验证">第三步:在用户微服务中增加修改密码的功能用于验证</h5>
<p>现有的<code class="language-plaintext highlighter-rouge">login</code><code class="language-plaintext highlighter-rouge">logon</code>都无需认证,因此我们在用户微服务中增加需要认证的修改密码的功能用于验证统一认证。</p>
<h6 id="在userservice中添加修改密码">在UserService中添加修改密码</h6>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">UserService</span> <span class="o">{</span>
<span class="nc">ResponseEntity</span><span class="o">&lt;</span><span class="nc">Boolean</span><span class="o">&gt;</span> <span class="nf">logon</span><span class="o">(</span><span class="nc">UserDTO</span> <span class="n">user</span><span class="o">);</span>
<span class="nc">ResponseEntity</span><span class="o">&lt;</span><span class="nc">Boolean</span><span class="o">&gt;</span> <span class="nf">login</span><span class="o">(</span><span class="nc">UserDTO</span> <span class="n">user</span><span class="o">);</span>
<span class="c1">//需要认证的修改密码功能</span>
<span class="nc">ResponseEntity</span><span class="o">&lt;</span><span class="nc">Boolean</span><span class="o">&gt;</span> <span class="nf">changePassword</span><span class="o">(</span><span class="nc">UserUpdateDTO</span> <span class="n">userUpdate</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<h6 id="在userserviceimpl中实现修改密码">在UserServiceImpl中实现修改密码</h6>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Override</span>
<span class="nd">@PostMapping</span><span class="o">(</span><span class="n">path</span> <span class="o">=</span> <span class="s">"changePassword"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">ResponseEntity</span><span class="o">&lt;</span><span class="nc">Boolean</span><span class="o">&gt;</span> <span class="nf">changePassword</span><span class="o">(</span><span class="nd">@RequestBody</span> <span class="nc">UserUpdateDTO</span> <span class="n">userUpdate</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">validateUserUpdate</span><span class="o">(</span><span class="n">userUpdate</span><span class="o">))</span> <span class="o">{</span>
<span class="nc">UserEntity</span> <span class="n">dbUser</span> <span class="o">=</span> <span class="n">repository</span><span class="o">.</span><span class="na">findByName</span><span class="o">(</span><span class="n">userUpdate</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>
<span class="k">if</span> <span class="o">(</span><span class="n">dbUser</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">dbUser</span><span class="o">.</span><span class="na">getPassword</span><span class="o">().</span><span class="na">equals</span><span class="o">(</span><span class="n">userUpdate</span><span class="o">.</span><span class="na">getOldPassword</span><span class="o">()))</span> <span class="o">{</span>
<span class="n">dbUser</span><span class="o">.</span><span class="na">setPassword</span><span class="o">(</span><span class="n">userUpdate</span><span class="o">.</span><span class="na">getNewPassword</span><span class="o">());</span>
<span class="n">repository</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">dbUser</span><span class="o">);</span>
<span class="k">return</span> <span class="k">new</span> <span class="nc">ResponseEntity</span><span class="o">&lt;&gt;(</span><span class="kc">true</span><span class="o">,</span> <span class="nc">HttpStatus</span><span class="o">.</span><span class="na">OK</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">InvocationException</span><span class="o">(</span><span class="no">BAD_REQUEST</span><span class="o">,</span> <span class="s">"wrong password"</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">InvocationException</span><span class="o">(</span><span class="no">BAD_REQUEST</span><span class="o">,</span> <span class="s">"user name not exist"</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">InvocationException</span><span class="o">(</span><span class="no">BAD_REQUEST</span><span class="o">,</span> <span class="s">"incorrect user"</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="63-验证实现的统一认证">6.3 验证实现的统一认证</h2>
<h5 id="确认authenticationfilter在edge服务中成功加载">确认AuthenticationFilter在Edge服务中成功加载</h5>
<p>在Edge服务的启动日志中能够找到:</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2018-07-13 14:38:48,756 [INFO] 1. org.apache.servicecomb.scaffold.edge.filter.AuthenticationFilter. org.apache.servicecomb.foundation.common.utils.SPIServiceUtils.loadSortedService(SPIServiceUtils.java:79)
</code></pre></div></div>
<h5 id="用户登录">用户登录</h5>
<p>使用zhengyangyong登录:</p>
<p><img src="/assets/images/scaffold/LoginFirst.png" alt="LoginFirst" /></p>
<p>拿到的Token值为:eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ6aGVuZ3lhbmd5b25nIiwiZXhwIjoxNTMwNjA4OTczfQ.90teWUNbypPZvds_SD7Kus_y7wLc4b6VzC_aIVg8sLItKxwQ0g4V9BDU665PlqQY5KM-mnk8y0R6ENL1T8YVFg</p>
<h5 id="不带authorization-header请求changepassword">不带Authorization Header请求changePassword</h5>
<p><img src="/assets/images/scaffold/NoAuthHeader.png" alt="NoAuthHeader" /></p>
<p>返回的失败信息是:authentication failed, missing AUTHORIZATION header</p>
<h5 id="使用错误的token请求changepassword">使用错误的Token请求changePassword</h5>
<p><img src="/assets/images/scaffold/ErrorAuthHeader.png" alt="ErrorAuthHeader" /></p>
<p>返回的失败信息是:authentication failed : InvocationException: code=400;msg=CommonExceptionData [message=incorrect token]</p>
<h5 id="使用正确的token请求changepassword">使用正确的Token请求changePassword</h5>
<p><img src="/assets/images/scaffold/AuthSuccess.png" alt="AuthSuccess" /></p>
<p>修改密码成功。</p>
<p><strong>这里可能有疑问,使用zhengyangyong登录后,是可以通过这个Token修改其他用户例如lidagang的密码的,这是因为我们目前构建的validate仅检查Token的有效性,而不做权限检查,基于RBAC的角色权限管理系统将会在未来构建。</strong></p>
<blockquote>
<p>提示:</p>
<ol>
<li>AuthenticationFilter的完整<a href="https://github.com/zhengyangyong/scaffold/blob/master/edge-service/src/main/java/org/apache/servicecomb/scaffold/edge/filter/AuthenticationFilter.java">代码</a></li>
<li>HttpServerFilter的<a href="https://github.com/apache/incubator-servicecomb-docs/blob/master/java-chassis-reference/zh_CN/general-development/http-filter.md">介绍</a></li>
</ol>
</blockquote>
<h1 id="7小结">7.小结</h1>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>本文详细介绍了如何使用http://start.servicecomb.io脚手架快速构建微服务项目、使用领域驱动设计(Domain-Driven Design,DDD)设计地产CRM系统、使用Edge Service构建统一认证边缘服务等内容。至此,一个地产客户关系管理系统的骨架已经初步搭建起来,剩下的模块,我们将在接下来的文章里详细介绍。
</code></pre></div></div>
</section>
<footer class="page__meta">
<p class="page__taxonomy">
<strong><i class="fa fa-fw fa-tags" aria-hidden="true"></i> 标签: </strong>
<span itemprop="keywords">
<a href="/tags/#crm" class="page__taxonomy-item" rel="tag">CRM</a><span class="sep">, </span>
<a href="/tags/#ddd" class="page__taxonomy-item" rel="tag">DDD</a><span class="sep">, </span>
<a href="/tags/#edge" class="page__taxonomy-item" rel="tag">Edge</a><span class="sep">, </span>
<a href="/tags/#jpa" class="page__taxonomy-item" rel="tag">JPA</a><span class="sep">, </span>
<a href="/tags/#jwt" class="page__taxonomy-item" rel="tag">JWT</a><span class="sep">, </span>
<a href="/tags/#scaffold" class="page__taxonomy-item" rel="tag">Scaffold</a>
</span>
</p>
<p class="page__date"><strong><i class="fa fa-fw fa-calendar" aria-hidden="true"></i> 最新的:</strong> <time datetime="2018-08-28">2018年8月28日</time></p>
</footer>
<section class="page__share">
<h4 class="page__share-title">分享</h4>
<a href="https://twitter.com/intent/tweet?via=ServiceComb&text=客户管理系统微服务化实战-PartI /cn/docs/crm-part-I/" class="btn btn--twitter" title="分享 Twitter"><i class="fa fa-fw fa-twitter" aria-hidden="true"></i><span> Twitter</span></a>
<a href="https://www.facebook.com/sharer/sharer.php?u=/cn/docs/crm-part-I/" class="btn btn--facebook" title="分享 Facebook"><i class="fa fa-fw fa-facebook" aria-hidden="true"></i><span> Facebook</span></a>
<a href="https://plus.google.com/share?url=/cn/docs/crm-part-I/" class="btn btn--google-plus" title="分享 Google Plus"><i class="fa fa-fw fa-google-plus" aria-hidden="true"></i><span> Google+</span></a>
<a href="https://www.linkedin.com/shareArticle?mini=true&url=/cn/docs/crm-part-I/" class="btn btn--linkedin" title="分享 LinkedIn"><i class="fa fa-fw fa-linkedin" aria-hidden="true"></i><span> LinkedIn</span></a>
</section>
<nav class="pagination">
<a href="/docs/release-note-1-0-0/" class="pagination--pager" title="[Milestone] Apache ServiceComb (incubating) Release Version 1.0.0
">向前</a>
<a href="/cn/docs/loadtest-saga-with-kubernetes" class="pagination--pager" title="使用Kubernetes对Saga进行压力测试
">向后</a>
</nav>
</div>
<div class="page__comments">
<section id="static-comments">
<!-- Start static comments -->
<div class="js-comments">
</div>
<!-- End static comments -->
<!-- Start new comment form -->
<h4 class="page__comments-title">留下评论</h4>
<p class="small">您的电子邮箱地址并不会被展示。请填写标记为必须的字段。 <span class="required">*</span></p>
<form id="new_comment" class="page__comments-form js-form form" method="post" action="https://api.staticman.net/v1/entry/apache/incubator-servicecomb-website/master">
<div class="form__spinner">
<i class="fa fa-spinner fa-spin fa-3x fa-fw"></i>
<span class="sr-only">正在加载...</span>
</div>
<fieldset>
<label for="comment-form-message">评论 <small class="required">*</small></label>
<textarea type="text" rows="3" id="comment-form-message" name="fields[message]" tabindex="1"></textarea>
<div class="small help-block"><a href="https://daringfireball.net/projects/markdown/">Markdown语法已支持。</a></div>
</fieldset>
<fieldset>
<label for="comment-form-name">姓名 <small class="required">*</small></label>
<input type="text" id="comment-form-name" name="fields[name]" tabindex="2" />
</fieldset>
<fieldset>
<label for="comment-form-email">电子邮箱 <small class="required">*</small></label>
<input type="email" id="comment-form-email" name="fields[email]" tabindex="3" />
</fieldset>
<fieldset>
<label for="comment-form-url">网站(可选)</label>
<input type="url" id="comment-form-url" name="fields[url]" tabindex="4"/>
</fieldset>
<fieldset class="hidden" style="display: none;">
<input type="hidden" name="options[slug]" value="crm-part-I">
<label for="comment-form-location">Not used. Leave blank if you are a human.</label>
<input type="text" id="comment-form-location" name="fields[hidden]" autocomplete="off"/>
</fieldset>
<!-- Start comment form alert messaging -->
<p class="hidden js-notice">
<strong class="js-notice-text"></strong>
</p>
<!-- End comment form alert messaging -->
<fieldset>
<button type="submit" id="comment-form-submit" tabindex="5" class="btn btn--large">提交评论</button>
</fieldset>
</form>
<!-- End new comment form -->
</section>
</div>
</article>
<div class="page__related">
<h4 class="page__related-title">猜您还喜欢</h4>
<div class="grid__wrapper">
<div class="grid__item">
<article class="archive__item" itemscope itemtype="http://schema.org/CreativeWork">
<h2 class="archive__item-title" itemprop="headline">
<a href="/cn/docs/playing-on-the-open-source-community-with-Apache-ServiceComb-GDUT/" rel="permalink">与Apache ServiceComb一起玩开源-广工站 (PPT Download)
</a>
</h2>
<p class="archive__item-excerpt" itemprop="description">与Apache ServiceComb一起玩开源-广工站 (PPT Download)
</p>
<p class="page__meta"><i class="fa fa-clock-o" aria-hidden="true"></i>
少于 1 分钟 阅读
</p>
</article>
</div>
<div class="grid__item">
<article class="archive__item" itemscope itemtype="http://schema.org/CreativeWork">
<h2 class="archive__item-title" itemprop="headline">
<a href="/cn/docs/servicecomb-service-center-client/" rel="permalink">使用ServiceComb客户端轻松调用ServiceCenter
</a>
</h2>
<p class="archive__item-excerpt" itemprop="description">使用ServiceComb客户端轻松调用ServiceCenter
</p>
<p class="page__meta"><i class="fa fa-clock-o" aria-hidden="true"></i>
2 分钟 阅读
</p>
</article>
</div>
<div class="grid__item">
<article class="archive__item" itemscope itemtype="http://schema.org/CreativeWork">
<h2 class="archive__item-title" itemprop="headline">
<a href="/cn/docs/playing-on-the-open-source-community-with-Apache-ServiceComb-BUPT/" rel="permalink">与Apache ServiceComb一起玩开源-北邮站 (PPT Download)
</a>
</h2>
<p class="archive__item-excerpt" itemprop="description">与Apache ServiceComb一起玩开源-北邮站 (PPT Download)
</p>
<p class="page__meta"><i class="fa fa-clock-o" aria-hidden="true"></i>
少于 1 分钟 阅读
</p>
</article>
</div>
<div class="grid__item">
<article class="archive__item" itemscope itemtype="http://schema.org/CreativeWork">
<h2 class="archive__item-title" itemprop="headline">
<a href="/docs/servicecomb-accept-newcapec-institute-code-donation/" rel="permalink">Apache ServiceComb Accept Code Donation From NewCapec Institute
</a>
</h2>
<p class="archive__item-excerpt" itemprop="description">Apache ServiceComb Accept Code Donation From NewCapec Institute
</p>
<p class="page__meta"><i class="fa fa-clock-o" aria-hidden="true"></i>
少于 1 分钟 阅读
</p>
</article>
</div>
</div>
</div>
</div>
<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
<div align="center" style="margin: 0 0;">
<ins class="adsbygoogle"
style="display:block; border-bottom: initial;"
data-ad-client="ca-pub-7328585512091257"
data-ad-slot="3049671934"
data-ad-format="auto"></ins>
</div>
<div class="page__footer">
<footer>
<!-- start custom footer snippets -->
<!-- end custom footer snippets -->
<div class="container">
<div class="row justify-content-md-center">
<div class="col">
<ul>
<p class="header">活动</p>
<a class="acevent" data-format="square" data-mode="dark" data-event="random"></a>
</ul>
</div>
<div class="col">
<ul>
<p class="header">资源</p>
<li><a href="/cn/docs/getting-started/">入门指南</a></li>
<li><a href="/cn/docs/users/">用户指南</a></li>
<li><a href="/cn/slides/">资料</a></li>
<li><a href="/cn/users/faq/">常见问题</a></li>
</ul>
</div>
<div class="col">
<ul>
<p class="header">ASF</p>
<li><a href="http://www.apache.org">基金会</a></li>
<li><a href="http://www.apache.org/licenses/">许可证</a></li>
<li><a href="http://www.apache.org/events/current-event">活动</a></li>
<li><a href="http://www.apache.org/foundation/sponsorship.html">赞助</a></li>
<li><a href="http://www.apache.org/foundation/thanks.html">鸣谢</a></li>
</ul>
</div>
<div class="col">
<ul>
<p class="header">贡献</p>
<li><a href="http://issues.apache.org/jira/browse/SCB">报告本网页问题</a></li>
<li><a href="https://github.com/apache/servicecomb-website/edit/master/_posts/cn/2018-08-28-crm-part-I.md">在Github上编辑此页</a></li>
<li><a href="/cn/developers/submit-codes/">代码提交指南</a></li>
<li><a href="/cn/security">安全</a></li>
</ul>
</div>
<div class="col">
<ul class="social-icons">
<p class="header">社区</p>
<li>
<a href="mailto:dev-subscribe@servicecomb.incubator.apache.org" rel="nofollow"><span class="mail">邮件列表</span></a>
</li>
<li>
<a href="https://github.com/apache?q=ServiceComb" target="_blank"><span class="github">Github</span></a>
</li>
<li>
<a href="https://twitter.com/ServiceComb" target="_blank"><span class="twitter">Twitter</span></a>
</li>
<li>
<a href="/feed.xml" target="_blank"><span class="rss">Feed</span></a>
</li>
</ul>
</div>
</div>
</div>
<div class="page__footer-bottom">
<div>&copy; 2024 Apache ServiceComb. 技术来自于 <a href="http://jekyllrb.com" rel="nofollow">Jekyll</a> &amp; <a href="https://mademistakes.com/work/minimal-mistakes-jekyll-theme/" rel="nofollow">Minimal Mistakes</a>.</div>
<div>All other marks mentioned may be trademarks or registered trademarks of their respective owners.</div>
</div>
</footer>
</div>
<script src="/assets/js/main.min.js"></script>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-101622733-1', 'auto');
ga('send', 'pageview');
</script>
<script>
(function ($) {
var $comments = $('.js-comments');
$('#new_comment').submit(function () {
var form = this;
$(form).addClass('disabled');
$('#comment-form-submit').html('<i class="fa fa-spinner fa-spin fa-fw"></i> 正在加载...');
$.ajax({
type: $(this).attr('method'),
url: $(this).attr('action'),
data: $(this).serialize(),
contentType: 'application/x-www-form-urlencoded',
success: function (data) {
$('#comment-form-submit').html('已提交');
$('.page__comments-form .js-notice').removeClass('notice--danger');
$('.page__comments-form .js-notice').addClass('notice--success');
showAlert('感谢您的评论!被批准后它会立即在此站点展示。');
},
error: function (err) {
console.log(err);
$('#comment-form-submit').html('提交评论');
$('.page__comments-form .js-notice').removeClass('notice--success');
$('.page__comments-form .js-notice').addClass('notice--danger');
showAlert('很抱歉,您的提交存在错误。请确保所有必填字段都已填写正确,然后再试一次。');
$(form).removeClass('disabled');
}
});
return false;
});
function showAlert(message) {
$('.page__comments-form .js-notice').removeClass('hidden');
$('.page__comments-form .js-notice-text').html(message);
}
})(jQuery);
</script>
</body>
</html>