为了账号安全,请及时绑定邮箱和手机立即绑定
3.1 打包为微信小程序

注册微信小程序账号,获取到 AppID,我们后面配置的时候会用到。在 HBuilderX 工具栏,点击发行,选择小程序-微信。输入小程序名称和 AppID,单击发行就可以了。这样我们就会获得一个微信小程序的打包文件,接下来我们来发布微信小程序项目,打开微信小程序开发者工具,导入刚刚生成的微信小程序项目的打包文件,在微信小程序开发者工具中先测试一下,项目运行是否正常,项目测试没有问题后,点击右上角>>按钮,上传代码就可以发布微信小程序了,最后等待微信团队审核通过,别人就可以在线上访问到你的项目了。

新浪微博

咱们打开新浪微博,然后随便找个带皇冠的,按下F12键(Mac用户按command+option+i)然后选中控制台的箭头,再点击皇冠:再点开这个图片可以发现:可以看到那些大 V 图标皇冠图标以及各种微博认证等图标,都是放在了一张雪碧图里(即使再牛的大 V,身份标志也是放在雪碧图中的)。

2. 服务快速失败真实业务场景描述

业务场景描述我们继续以某大厂的用户注册服务为例,只不过所在的业务场景发生了变化。有这样一个真实的业务场景,在用户请求用户注册服务时,该服务要求用户上传自己的注册头像,在单体项目架构下,我们上传多次头像都没有任何问题,服务可以正常的进行。但是,将我们把项目架构进行演进,项目由单体架构演变为了微服务的分布式架构之后,时不时地会出现因为用户注册头像上传失败而导致整个用户注册服务无法继续进行的情况。问题原因分析在解决问题之前,我们首先来分析一下这种问题产生的原因。当项目架构由传统的单体架构演变为基于微服务的分布式架构之后,我们需要处理很多由于微服务架构所带来的已知问题,在将这些已知问题处理好后,我们的项目才能正常运行,而上述业务场景中的问题就包括在这些已知问题中,下面我们来介绍一下问题产生的原因(至于其他已知问题,这里不做介绍)。无论是传统的用户注册服务,还是分布式的用户注册服务,其用户注册处理逻辑都大同小异。在处理用户注册逻辑时,一般会将用户注册头像上传的业务逻辑与用户注册的处理逻辑相分离,即将用户注册头像上传作为一个单独的服务存在,同时用户注册的处理逻辑也作为一个单独的服务接口。当正式调用用户注册服务时,会对用户注册头像上传服务做一个校验,如果用户成功上传了所注册的头像,那么用户注册头像上传服务会返回一个头像地址,并且在最后的用户注册逻辑中,将该地址与当前注册的用户相关联,这就完成了整个用户注册的流程。上述业务场景的问题就出现在用户注册头像上传服务中,当外界因素或者由于我们处理用户注册头像格式时的处理逻辑不严谨时,就会出现用户注册头像上传失败的情况, 其中,由处理逻辑不严谨所引起的错误可以直接通过修改源码逻辑来规避这种错误,但是由外界因素所引起的错误就不好规避了。由外界因素引起的错误,可能是项目所在服务器网络波动的影响,也可能是各微服务所在节点间数据通信丢失或堵塞的影响,也可能是没有对分布式事务做处理的影响。我们不需要知道这种错误具体是由于什么外界因素所引起的,我们只需要关心,在外界因素对服务造成影响时,我们有兜底的解决方案即可。那么接下来就让我们来看一下如何设计这个兜底的方案。

3.2 注册事件

Selector 接口并没有声明 register 方法,而是通过它的抽象实现类提供了一个 abstract protected 方法,也没有对外暴露。声明如下:protected abstract SelectionKey register(AbstractSelectableChannel ch,int ops, Object att);Selector 的 register 机制最终委派给了 AbstractSelectableChannel 类。为此,我们要想将 Channel 注册到 Selector,需要调用 AbstractSelectableChannel 的 register 方法。声明如下:public final SelectionKey register(Selector sel, int ops, Object att) throws ClosedChannelException参数说明:sel 是预先创建的 Selector 对象。ops 表示需要注册的具体事件。支持的事件类型如下:- SelectionKey.OP_ACCEPT 表示监听客户端的连接,用于服务器- SelectionKey.OP_CONNECT 表示非阻塞式客户端连接过程,用于客户端- SelectionKey.OP_READ 表示监听读事件- SelectionKey.OP_WRITE 表示监听写事件att 用于保存上下文对象。

1. 注册 Gem

首先我们需要注册一个https://rubygems.org帐号。注册完成之后,您就拥有了属于自己的 RubyGems账号,别人就可以在你的主页上看到你发布的 Gem。

4. 各平台小程序组件存放目录

uni-app 还有一个非常牛的地方,就是支持在 App 和小程序中使用小程序自定义的组件,支持情况如下:平台支持情况小程序组件存放目录 H5 从 HBuilderX2.4.7 起,支持微信小程序组件 wxcomponentsApp(不含 nvue)支持微信小程序组件 wxcomponents 微信小程序支持微信小程序组件 wxcomponents 支付宝小程序支持支付宝小程序组件 mycomponents 百度小程序支持百度小程序组件 swancomponents 字节跳动小程序支持字节跳动小程序组件 ttcomponentsQQ 小程序支持 QQ 小程序组件 wxcomponents

4.1 注册

Broadcast Receiver 是四大组件中唯一一个支持动态注册的组件,我们可以在代码中通过Context.registerReceiver()方法进行注册:IntentFilter filter = new IntentFilter();intentFilter.addAction(getPackageName()+"com.emercy.CUSTOM_RECEIVER");MyReceiver myReceiver = new MyReceiver();registerReceiver(myReceiver, filter);同时,也支持类似 Activity、Service 的静态注册方式,在 AndroidManifest.xml 中添加以下注册代码:<receiver android:name=".MyBroadcastReceiver"> <intent-filter> <action android:name="com.emercy.CUSTOM_RECEIVER" /> </intent-filter></receiver>

2. 代理配置

go mod 虽然可以直接帮我们下载好我们需要的包,但是因为 Go 语言很多包都是在国外的服务器上,国内下载具有一定的困难。但是不用担心,go mod 提供了代理服务,同时很多国内的云服务器产商都提供了自己的代理服务:阿里云: https://mirrors.aliyun.com/goproxy 微软: https://goproxy.io 七牛云: https://goproxy.cn GoCenter: https://gocenter.io

3.1 微信小程序 API

uni-app 的 API 与微信小程序 API 基本一致。掌握微信小程序 API 对后面的开发很有帮助。微信小程序 API 文档:https://developers.weixin.qq.com/miniprogram/dev/api/

2.2 不要随便注册账号

没有绝对信任的网站不要随便注册,如果不得已的话也不要跟自己的重要账号的密码设置一样。这种网站一方面可能是不法分子故意用来盗取信息的,另一方面不是正规机构的网站安全性比较差,即使网站运营者无意,也可能被人攻破后窃取信息。

4.4 注册数据源组件

多个数据源的情况下, 我们需要通过配置类,将数据源注册为组件放入 Spring 容器中。实例:/** * 数据源配置类 */@Configuration//标注为配置类public class DataSourceConfig { /** * 数据源1 */ @Bean//返回值注册为组件 @ConfigurationProperties("spring.datasource.db1")//使用spring.datasource.db1作为前缀的配置 public DataSource db1() { return DataSourceBuilder.create().build(); } /** * 数据源2 */ @Bean//返回值注册为组件 @ConfigurationProperties("spring.datasource.db2")//使用spring.datasource.db2作为前缀的配置 public DataSource db2() { return DataSourceBuilder.create().build(); }}通过这个配置类, Spring 容器中就有两个数据源组件,这两个组件分别采用 spring.datasource.db1 和 spring.datasource.db2 开头的配置信息。所以通过这两个组件,就能分别操作 MySQL 数据源 1 和 SQL Sever 数据源 2 。

3. CDN

可以通过 CDN 引入 ECharts 文件:<!-- bootstrap 服务 --><!-- bootstrap 提供的免费CDN服务,亲测非常稳定 --><script src="//cdn.bootcss.com/echarts/4.5.0/echarts.common.js"></script><!-- 七牛云存储服务 --><!-- 国内速度稳定,开放性强 --><script src="//cdn.staticfile.org/echarts/4.5.0/echarts.common.js"></script><!-- jsdeliver 服务 --><!-- 微软的CDN服务,虽然国内访问速度比不上国内CDN,但速度不至于太慢,有国际化需求的可以试试 --><script src="//cdn.jsdelivr.net/npm/echarts@4.5.0/echarts.common.js"></script><!-- cdnjs 服务 --><!-- 一个非常全的CDN服务,存储了大多数主流的js、css、图片库 --><script src="//cdnjs.cloudflare.com/ajax/libs/echarts/4.5.0/echarts.common.js"></script>

2. 服务容错与降级真实业务场景描述

业务场景描述有这样一个真实的业务场景:在某大厂的用户业务模块下,存在一个用户注册服务接口,在正常流量下,请求该用户注册服务接口,不会出现任何问题,业务可以正常开展。但是,在遇到某活动举办时,当再次请求该用户注册服务接口时,该服务接口就会报服务处理异常,我们需要做的就是用服务容错与降级的概念来解决这种异常现象。问题原因分析在解决问题之前,我们首先来分析一下这种问题产生的原因。在遇到某活动举办时,当再次请求该用户注册服务接口时,由于此时的请求流量较正常情况下的多,即此时的请求流量可能是正常情况下请求流量的几倍,甚至更多,我们的服务在处理请求时,一方面出现了服务处理堆积的现象;另一方面,当我们的服务器或数据库不能继续处理更多的请求时,没有给用户一个合理地提示,直接让程序报出了异常。以上两方面就是产生该异常的原因,第一方面我们使用服务容错与降级无法解决,只能使用高并发相关的知识,通过限流来解决,但是第二方面我们可以使用服务容错与降级的概念来解决,接下来就让我们看一下如何解决吧。

3.2 局部注册

指令的局部注册和组件的局部注册类似,在实例的参数 options 中使用 directives 选项来注册局部指令,局部指令只能在当前这个实例中使用:// 注册// 短横线命名{ directives: { 'my-directive': { inserted: function (el) { el.focus() } } }}// 驼峰命名{ directives: { 'MyDirective': { inserted: function (el) { el.focus() } } }}// 使用<div v-my-directive></div>692代码解释:JS 代码第 8-14 行,我们定义了局部指令 v-focus,定义 inserted 钩子函数,在节点被插入时获得焦点。HTML 代码第 4 行,我们在 input 元素上使用指令,当页面打开时 id 为 name 的输入框会自动获取焦点。

4.1 全局注册

在全局配置配置文件中可通过 typeHandlers 属性来注册类型处理器。如下:<typeHandlers> <package name="com.imooc.mybatis.handler"/></typeHandlers>通过 package 项来指定类型处理器所在的包路径,这样 handler 包中的所有类型处理器都会注册到全局。当然如果你的类型处理器分散在其它地方,也可以通过如下方式来注册。<typeHandlers> <typeHandler handler="com.imooc.mybatis.handler.JsonArrayTypeHandler"/></typeHandlers>全局注册的类型处理器会自动被 MyBatis 用来处理所有符合类型的参数。如 JsonArrayTypeHandler 通过 MappedJdbcTypes 注解表明了自己将会处理 JdbcType.VARCHAR 类型,MyBatis 会自动将字符串类型的参数交给 JsonArrayTypeHandler 来进行处理。但是,这样显然有问题,因为 JsonObjectTypeHandler 注册的类型也是 JdbcType.VARCHAR 类型,所以全局注册是不推荐的,除非你需要对所有参数都做类型转换。

3.4 定义信息注册数据处理方法

/** * 用户注册信息校验入库 * @return \think\response\Json * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ public function do_register(){ $captcha = $this->request->param('captcha'); //验证码 $password = $this->request->param('password'); //密码 $re_password = $this->request->param('re_password'); //重复密码 $username = $this->request->param('username'); //用户名 if(empty($captcha) ||empty($password) || empty($re_password) || empty($username)){ throw new HttpException(400,"必要参数不能为空"); }// //校验验证码是否正确// if(!captcha_check($captcha)){// throw new HttpException(400,"验证码不正确");// }; //校验两次密码书否输入一致 if($password != $re_password){ throw new HttpException(400,"两次输入密码不一致"); } //检查用户名是否已经被注册过 if(!empty(LoginModel::where('username',$username)->find())){ throw new HttpException(400,"用户名已经存在"); } try { $login = new LoginModel(); $login->sex = $this->request->param('sex',0);//性别赋值,默认值保密 $login->birthday = strtotime($this->request->param('birthday'));//生日转化为时间戳赋值 $login->create_time = time();//注册时间为当前时间 $login->user_status = 1;//用户状态 0-禁用 1-正常 $login->username = $username; $login->password = md5($password."test");//密码加密 $login->nickname = $this->request->param('nickname');//用户昵称 $login->save();//保存 }catch(\Exception $exception){ throw new HttpException(400,"注册失败"); } return json("请求成功"); }用户注册界面如下:

1.2 注册钩子

Flask 框架处理请求时,提供了 2 个挂载点:before_request 和 after_request,执行请求的流程如下:执行在挂载点 before_request 注册的钩子函数;执行页面处理函数;执行在挂载点 after_request 注册的钩子函数。Flask 使用装饰器的语法注册钩子函数,如下所示:@app.before_requestdef before_request(): print('before request')使用装饰器 @app.before_request 在挂载点 before_request 注册函数 before_request,Flask 在处理每个请求时:首先调用函数 before_request,打印字符串 ‘before request’;然后再执行请求的处理函数。

1. Web 安全简介

Web 安全是个大课题,面对层出不求的黑客漏洞,个人能力很有限制,所以这边介绍一个平台 国家信息安全漏洞共享平台,可以到上面注册个账号,并订阅漏洞信息报送。针对各类漏洞笔者归类了 3 个方面:1. 注入与欺骗;2. 编程代码逻辑缺陷;3. 服务器问题;

1.2 没有验证通信方身份

通信双方无法确认对方的身份是否正确。案例1:本地电脑有木马,路由表被踹改,www.baidu.com 的域名被解析到另一台虚假的服务器,此时你在浏览器访问的百度就是黑客的另一个钓鱼网站,然后给你推送了很多虚假消息,此时客户端很难知情。案例2:路由表没问题,但是目标服务器被人黑了,处理不了请求,此时有其它服务冒充提供服务。案例3:某个资源只有特定用户有查询的权限,黑客通过伪造该客户端一样的请求来查询,服务端只是简单的根据入参判断,并无法知道对应客户端的真实性,于是消息很容易泄漏。

2. 教程和官方文档有什么区别?

官方文档中案例较少,而且好多内容对初学者不太友好,官方文档更多的作用是作为手册使用,而不是作为初学者入门学习的资料。举个例子来说就是,我们刚开始学习英语,肯定是先从一年级课本开始学习,而不是一开始就拿着一本牛津字典去学习。

2.1 异步处理 &amp; 应用解耦

以最常见的在网站注册新用户场景为例,如果经过了基本的业务逻辑之后,要通过短信和邮件的方式验证是否用户本人注册,每个流程的请求响应耗时为 100ms,在同步的方式下总共需要耗时 300ms。​ (同步处理场景)其中发送验证短信以及发送验证邮件两个步骤并没有强制的先后依赖关系,所以同步请求的效率相对较低,使用消息队列可以将验证短信和邮件的模块拆开,经过消息队列中转分发请求,假设消息队列的读写时间为 20ms,总流程的耗时被优化到 220ms。​ (消息队列异步处理场景)上述异步请求的过程本质上也是应用解耦的过程,最基础的应用架构中可以将短信注册模块和邮件注册模块都可以耦合在注册业务逻辑中,但是如果有其他的服务也需要使用短信注册功能,就只能调用注册业务的短信模块接口。此时,程序的鲁棒性相对较差,当注册业务模块的服务器宕机之后,会造成所有服务的短信模块都不可用,所以需要将短信模块解耦出来,同理,邮件模块也需要被拆分为单独的服务。候选人需要注意一点,这种拆分本质上都是为了应用服务的高可用。

观察者模式

观察者模式也称为订阅者模式,实际上我觉得订阅者更容易理解。这种设计模式在生活中很常见。比如订阅期刊杂志、定牛奶等等。我们使用的软件中也很常见。比如说微博,你关注了某位明星,其实你就是他的观察者。每当你关注的明星发了新的动态,你就会接收到通知。观察者模式基于发布订阅的方式。订阅者订阅目标对象,目标对象维护订阅者的集合。一旦目标对象状态变化,需要通知所有订阅者,从而触发订阅者的某个行为。

1.2 显示 “登录/注册” 按钮

<body> <div class='header'> <i class="fa fa-calendar-plus-o"></i> 待做清单 {% if hasLogin %} <span class='login'> <i class="fa fa-sign-out"></i> <a href='/users/logout'>退出</a> </span> {% else %} <span class='login'> <i class="fa fa-sign-in"></i> <a href='/users/login'>登录</a> <i class="fa fa-user-plus"></i> <a href='/users/register'>注册</a> </span> {% endif %} </div>如果用户没有登录,网站首页的显示 “登录/注册” 按钮;如果用户已经登录,网站首页的显示 “退出” 按钮。在第 5 行,变量 hasLogin 标记用户是否登录,根据 hasLogin 是否为真显示不同的界面。

3. 注册自定义指令

Vue 自定义指令和组件一样存在着全局注册和局部注册两种方式。全局注册的自定义指令可以在项目中的所有组件中使用,局部注册的指令只能在当前组件内部使用。接下来我们分步介绍全局指令和局部指令的注册方式。

2.1 服务提供者

首先我们新建服务提供者项目,我们选择 Spring Initializr 来初始化 Spring Boot 项目,这是服务提供者的项目信息。pom.xml初始化完成,在 pom.xml 中加入项目所需的依赖。<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>cn.cdd</groupId> <artifactId>zookeeper-provider</artifactId> <version>0.0.1-SNAPSHOT</version> <name>zookeeper-provider</name> <description>zookeeper-providerDemo project for Spring Boot</description> <properties> <java.version>11</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- curator 客户端 --> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>5.1.0</version> </dependency> <!-- curator 客户端 --> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>5.1.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>依赖导入完成后,我们在 application.properties 配置文件中加入端口的配置。application.propertiesserver.port=8090接下来开始编写服务提供者的接口。ProviderService我们在 Spring Boot 主类的同级新建 service 目录,在 service 目录中新建 ProviderService 类。package cn.cdd.zookeeper.provider.service;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Service;import java.net.InetAddress;import java.net.UnknownHostException;@Servicepublic class ProviderService { @Value("${server.port}") private String port; public String callMethod(){ try { return "调用了服务提供者 " + InetAddress.getLocalHost().getHostAddress() + ":" + port + " 的方法"; } catch (UnknownHostException e) { e.printStackTrace(); } return null; }}接下来编写使用 Curator 连接 Zookeeper 服务的代码。CuratorService在 service 目录下新建 CuratorService 类:@Componentpublic class CuratorService implements ApplicationRunner { @Value("${server.port}") private String port; // CuratorFramework 客户端 private static CuratorFramework client; // 服务地址临时节点的父节点 private static final String PROVIDER_NODE = "/imooc/provider"; // 服务地址临时节点的全路径 private static String PROVIDER_ADDRESS; // 服务 ip private static String PROVIDER_IP; @Override public void run(ApplicationArguments args) throws Exception { // 获取客户端,连接 Zookeeper 服务 buildCuratorClient(); // 获取本机 IP PROVIDER_IP = InetAddress.getLocalHost().getHostAddress(); // 注册本机地址到 Zookeeper registeredAddress(); } /** * 构建 CuratorFramework 客户端,并开启会话 */ private void buildCuratorClient() { // 使用 CuratorFrameworkFactory 构建 CuratorFramework client = CuratorFrameworkFactory.builder() .sessionTimeoutMs(10000) // Zookeeper 地址 .connectString("127.0.0.1:2181") // 重连策略 .retryPolicy(new RetryForever(10000)) .build(); // 开启会话 client.start(); System.out.println(">>> 服务提供者连接 Zookeeper "); } /** * 注册服务地址 */ public String registeredAddress() { String address = null; try { Stat stat = client.checkExists().forPath(PROVIDER_NODE); if (stat == null) { client.create().creatingParentsIfNeeded().forPath(PROVIDER_NODE); } // 获取本机地址 address = PROVIDER_IP + ":" + port; // 创建临时节点 /imooc/provider/192.168.0.106:8090 PROVIDER_ADDRESS = client.create() .withMode(CreateMode.EPHEMERAL) .forPath(PROVIDER_NODE + "/" + address); } catch (Exception e) { e.printStackTrace(); } System.out.println(">>> 本服务已上线"); return ">>> 服务提供者 " + address + " 已上线"; } /** * 注销服务地址 */ public String deregistrationAddress() { String address = null; try { Stat stat = client.checkExists().forPath(PROVIDER_ADDRESS); if (stat != null) { client.delete().forPath(PROVIDER_ADDRESS); } // 获取本机地址 address = PROVIDER_IP + ":" + port; } catch (Exception e) { e.printStackTrace(); } System.out.println(">>> 本服务已下线"); return ">>> 服务提供者 " + address + " 已下线"; }}在 CuratorService 类中,我们提供了创建 Curator 客户端的方法,注册服务地址的方法以及注销服务地址的方法。在该服务启动时,就会自动连接 Zookeeper 服务,并且把自身的地址信息注册到 Zookeeper 的临时节点上。ProviderController这里我们使用 RESTful 的风格编写服务提供者对外的接口,在 service 目录同级创建 controller 目录,在 controller 中创建 ProviderController 。@RestController@RequestMapping("/provider")public class ProviderController { @Value("${server.port}") private String port; @Autowired private CuratorService curatorService; @Autowired private ProviderService providerService; /** * 调用方法 * http://localhost:8090/provider/callMethod * * @return String */ @GetMapping("/callMethod") public String callMethod() { return providerService.callMethod(); } /** * 上线服务 * http://localhost:8090/provider/online * * @return String */ @GetMapping("/online") public String registeredAddress() { return curatorService.registeredAddress(); } /** * 下线服务 * http://localhost:8090/provider/offline * * @return String */ @GetMapping("/offline") public String deregistrationAddress() { return curatorService.deregistrationAddress(); }}controller 编写完毕后,我们就可以对我们的服务提供者进行测试了。

2.2 注册表单

<body> <h3><i class='fa fa-user-plus'></i> 注册</h3> <form action="/users/register" method="POST"> <div class="row"> {{ form.name.label }} {{ form.name() }} <b>{{ form.name.errors[0] }}</b> </div> <div class="row"> {{ form.password.label }} {{ form.password() }} <b>{{ form.password.errors[0] }}</b> </div> <div class="row"> {{ form.submit() }} </div> {{ form.hidden_tag() }} </form></body></html>form 是注册表单,包括 3 个字段:name、password、隐藏字段,根据 form 中字段 email 和 password 的属性,它被渲染为如下的 HTML 文件:<html><head> <meta charset='UTF-8'> <link href="https://lib.baomitu.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"> <link href="/static/style.css" rel="stylesheet"> <title>登录</title></head><body> <div class="header"><i class='fa fa-sign-in'></i> 登录</div> <form action="/users/login" method="POST"> <div class="row"> <label for="name">姓名</label> <input id="name" name="name" required type="text" value=""> <b></b> </div> <div class="row"> <label for="password">密码</label> <input id="password" name="password" required type="password" value=""> <b></b> </div> <div class="row"> <input id="submit" name="submit" type="submit" value="登录"> </div> <input id="csrf_token" name="csrf_token" type="hidden" value="ImRlYTZjZDEwZjU3YjNjNGY0MDVkMDc4ZDhiZTMwNWM1OTk2MjhiMzAi.X2LvVA.0x7iz2PGVHH-r8dWf7KQNMkuSAE"> </form></body></html>这里注意两点:form.email.errors 和 form.password.errors 是一个错误信息列表,errors[0] 表示第一条错误信息;form.hidden_tag() 用于防范 CSRF 攻击,生成 <input id=“csrf_token”/> 标签,请参考相关词条。

2. ZkClient 客户端

ZkClient 是一个开源的客户端,在 Zookeeper 原生 API 接口的基础上进行了包装,更便于开发人员使用。内部实现了Session超时重连,Watcher反复注册等功能。想要使用 ZkClient,我们需要搭建 Java 开发环境,这里我们使用 IntelliJ IDEA 为开发工具,JDK 我们使用了长期维护的版本 JDK-11.0.8 。接下来我们开始搭建 ZkClient 运行的环境。

2. 几种常用的客户端-服务器消息传递方式

http 最常用的协议,用于客户端主动向服务器发送请求,单向传递;ajax HTTP 的扩展版,底层还是 HTTP 协议,只不过客户端是无刷新的;comet 也是基于 HTTP 封装的,使用 HTTP 长连接的方式,原理大致是将 HTTP 的timeout 设置较长,服务器有数据变化时返回数据给客户端,同时断开连接,客户端处理完数据之后重新创建一个 HTTP 长连接,循环上述操作(这只是其中一种实现方式);websocket 这是 HTML5 中的新标准,基于 socket 的方式实现客户端与服务端双向通信,需要浏览器支持 HTML5;Adobe Flash Socket 这个也是使用 socket 的方式,需要浏览器支持 flash 才行,为了兼容老版本的浏览器;ActiveX object 只适用于 IE 浏览器;目前尚没有一种方式能兼容所有的浏览器,只能针对软件的目标客户人群做一定的兼容。sse 服务端单向推送。

2.3 服务消费者

我们在服务提供者的同级新建项目服务消费者,使用 Spring Initializr 来初始化,以下是服务消费者的项目信息:pom.xml初始化完成,我们在 pom.xml 文件中加入需要的依赖。<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>cn.cdd</groupId> <artifactId>zookeeper-consumer</artifactId> <version>0.0.1-SNAPSHOT</version> <name>zookeeper-consumer</name> <description>zookeeper-consumer Demo project for Spring Boot</description> <properties> <java.version>11</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- curator 客户端 --> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>5.1.0</version> </dependency> <!-- curator 客户端 --> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>5.1.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>application.properties依赖导入后,我们在 application.properties 配置端口信息server.port=9090接下来我们编写使用 Curator 客户端连接 Zookeeper 服务的代码。CuratorService在项目主类的同级新建目录 service 目录,在 service 目录下新建 CuratorService 类:@Componentpublic class CuratorService implements ApplicationRunner { private static CuratorFramework client; private static final String PROVIDER_NODE = "/imooc/provider"; private static List<String> PROVIDER_SERVER_LIST; private static int NUMBER_OF_REQUESTS = 0; @Override public void run(ApplicationArguments args) throws Exception { // 构建 CuratorFramework 客户端,并开启会话 buildCuratorClient(); // 获取服务列表 getAListOfServiceAddresses(); // 开启对 PROVIDER_NODE 子节点变化事件的监听 startMonitoring(); } /** * 构建 CuratorFramework 客户端,并开启会话 */ private void buildCuratorClient() { // 使用 CuratorFrameworkFactory 构建 CuratorFramework client = CuratorFrameworkFactory.builder() .sessionTimeoutMs(10000) // Zookeeper 地址 .connectString("127.0.0.1:2181") // 重连策略 .retryPolicy(new RetryForever(10000)) .build(); // 开启会话 client.start(); System.out.println(">>> 服务消费者连接 Zookeeper "); } /** * 获取服务列表 * * @throws Exception Exception */ private void getAListOfServiceAddresses() throws Exception { Stat stat = client.checkExists().forPath(PROVIDER_NODE); if (stat == null) { throw new RuntimeException("服务地址未注册到 Zookeeper"); } else { PROVIDER_SERVER_LIST = client.getChildren().forPath(PROVIDER_NODE); } } /** * 开启对 PROVIDER_NODE 子节点变化事件的监听 */ public void startMonitoring() { // 构建 CuratorCache 实例 CuratorCache cache = CuratorCache.build(client, PROVIDER_NODE); // 使用 Fluent 风格和 lambda 表达式来构建 CuratorCacheListener 的事件监听 CuratorCacheListener listener = CuratorCacheListener.builder() // 开启对 PROVIDER_NODE 节点的子节点变化事件的监听 .forPathChildrenCache(PROVIDER_NODE, client, (curator, event) -> // 重新获取服务列表 PROVIDER_SERVER_LIST = curator.getChildren().forPath(PROVIDER_NODE)) // 初始化 .forInitialized(() -> System.out.println(">>> CuratorCacheListener 初始化")) // 构建 .build(); // 注册 CuratorCacheListener 到 CuratorCache cache.listenable().addListener(listener); // CuratorCache 开启缓存 cache.start(); } /** * 轮询策略,按顺序获取服务地址 * * @return 服务地址 */ public String roundRobin() { if (PROVIDER_SERVER_LIST.isEmpty()){ throw new RuntimeException(">>> 服务提供者地址列表为空"); } int i = NUMBER_OF_REQUESTS % PROVIDER_SERVER_LIST.size(); NUMBER_OF_REQUESTS++; return PROVIDER_SERVER_LIST.get(i); }}在 CuratorService 中,我们提供了创建 Curator 客户端的方法,获取服务地址列表的方法,对父节点的子节点变化事件开启监听的方法,以及对服务的负载均衡策略的方法轮询策略。在服务消费者启动时,连接 Zookeeper 服务,获取已注册的服务地址列表,并对服务地址临时节点的父节点开启监听。监听到子节点的变化事件时,则重新获取服务地址列表。ConsumerController这里我们使用 RESTful 的风格来调用服务消费者的方法,在 service 同级创建目录 controller ,在 controller 中创建 ConsumerController 类:@RestController@RequestMapping("/consumer")public class ConsumerController { @Autowired private CuratorService curatorService; @Autowired private RestTemplate restTemplate; /** * 调用方法 * http://localhost:9090/consumer/callMethod * * @return String */ @GetMapping("/callMethod") public String callMethod() { // 轮询策略获取服务地址 String s = curatorService.roundRobin(); // 使用 RestTemplate 远程调用服务的 /provider/callMethod 方法,String.class 为返回值类型 return restTemplate.getForObject("http://" + s + "/provider/callMethod", String.class); }}我们使用了 RestTemplate 来远程调用 RESTful 风格的接口,所以我们需要把 RestTemplate 注入到 Spring IOC 容器中。RestTemplate我们在服务消费者项目的主类中注入Bean RestTemplate :package cn.cdd.zookeeper.consumer;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.annotation.Bean;import org.springframework.web.client.RestTemplate;@SpringBootApplicationpublic class ZookeeperConsumerApplication { public static void main(String[] args) { SpringApplication.run(ZookeeperConsumerApplication.class, args); } @Bean public RestTemplate restTemplate(){ return new RestTemplate(); }}controller 编写完毕后,我们就可以对我们的服务消费者进行测试了。

3. 注册 GitHub 账号

介绍了这么多,接下来带着大家注册自己的 GitHub 账号。

首页上一页1234567下一页尾页
直播
查看课程详情
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号