REEEEX

KEEP GOING


  • 首页

  • 归档

  • 标签

  • 关于

Wechat Mini | Note-1

发表于 2018-08-15

微信小程序开发 Note-1

@ 2018年8月15日 15:46:28


微信小程序是什么

是一种全新的连接用户和服务的方式,可以在微信内便捷的获取和传播,同时具有出色的使用体验;

手机端APP的另一种全新的展现形式;

不是HTML5应用;

无须下载过多占用手机内存的app,即开即用;

必备技术栈:HTML,JS,CSS;

不是所有app都适用于小程序;

基于腾讯庞大的社交群体,为原生APP导流;

创业公司优先推出小程序,开发成本低;

当作简单的工具使用,需要在APP上频繁的CRUD不适用;


小程序注册流程

邮箱,小程序基本设置,开发者设置;

小程序开发

hello-world

下载微信开发工具后,建立快速开发基本模版即可开启第一个小程序项目;

app.js 只有唯一一个;

pages是页面目录;

wxml-html,js-js,wxss-css;

官方demo

体验小程序

包含了小程序的官方组件以及接口;


小程序工程的构成

基本文件(app.js,app.json,app.wxss)

​ ·app.js 外部全局主要的JS文件,当作一个JS文件的父类

​ ·app.json 全局的配置文件

​ ·app.wxss 全局的页面样式文件

页面文件(Page目录下)

​ `index(index.js,index.wxml,index.wxss,index.json)

​ xxx.js 私有的JS,相当于的子类

​ xxx.json 以json对象形式存在的配置

​ xxx.wxml 页面文件

​ xxx.wxss 私有的页面样式文件


第一个小程序demo

删除部分不需要的文件;

只保留app.js中的部分内容;

app.json 配置项列表-window

1
2
3
4
5
//app.js
App({
onLaunch: function () {
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//app.json
{
"pages": [
"pages/imooc/imooc",
"pages/index/index"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "WeChat",
"navigationBarTextStyle": "black"
},
"tabBar": {
"list": [
{
"pagePath": "pages/index/index",
"text": "首页"
}
]
}
}
在pages,用于描述当前小程序所有页面路径,这是为了让微信客户端知道当前你的小程序页面定义在哪个目;
在pages,设置的第一行,为打开小程序所显示的主页;

在window字段:定义小程序所有页面的顶部背景颜色,文字颜色定义等;

在tabBar中,暂时为导航栏;
在networkTimeout字段,各类网络请求的超时时间;

数据绑定以及CSS文件
1
2
3
4
5
<!--index.wxml-->
<view class="container">
<text class="txt-css">{{mydata}}</text>
</view>
{{xxx}} 数据绑定的用法;
1
2
3
4
5
6
7
//index.js
//获取应用实例
Page({
data: {
mydata: 'Data Bind'
}
})
1
2
3
4
//index.wxss
.txt-css{
margin-top: 150rpx;
}
  • rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//外部css文件导入 以及 内联样式
@import "out.wxss";
.txt-css{
margin-top: 150rpx;
}
// out.wxss
.txt-left{
margin-left: 350rpx;
}

<!--index.wxml-->
<view class="container">
<text class="txt-css txt-left" style="color:{{color}};">{{mydata}}</text>
</view>

//index.js
//获取应用实例
Page({
data: {
mydata: 'Data Bind',
color: "red"
},
onLoad:function(){
}
})

小程序页面加载

默认加载pages中的第一个目录;

其他目录需要通过触发才能加载;


小程序APP的生命周期

onLaunch:初始化事件,只被执行一次;

onShow:随着onLaunch触发,打开到小程序前台触发的事件;

onHide:小程序随着前台到后台的转变,隐藏到后台的触发事件;

onError:抛出异常时触发的事件;

其他:开发人员定义的事件;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// app.js
App({
onLaunch: function(options) {
console.log("触发onLaunch")
},
onShow: function(options) {
console.log("触发onShow")
},
onHide: function() {
// Do something when hide.
},
onError: function(msg) {
console.log(msg)
},
globalData: 'I am global data',
courseName: '小程序'
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 全局的 getApp() 函数可以用来获取到小程序实例
Page({
data: {
mydata: 'imooc Bind',
color: "green"
},
onLoad: function() {
var appInstance = getApp()
console.log(appInstance.courseName)
this.setData({
mydata: appInstance.courseName
});
}
})

手机端的远程调试

进行手机上的远程调试(debug)时,需要在创建工程时,填上AppID;

使用手机扫描小程序开发根据提供的预览和远程调试的功能;


私有页面的生命周期以及导航

注册页面,生命周期回调函数

onLoad(Object query) 页面加载时触发

onShow() 页面显示/切入前台时触发

onReady() 页面初次渲染完成时触发

onHide() 页面隐藏/切入后台时触发

onUnload() 页面卸载时触发

关于onHide和onUnload,联系到两个API wx.redirectTo(OBJECT) 和 wx.navigateTo(OBJECT)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 在页面中写入一个clickMe的点击事件;
Page({
clickMe:function(){
wx.navigateTo({
url: '../index/index',
}),
wx.redirectTo({
url: '../index/index',
})
}
})
// 注意事项1 url的路径问题,明确相对路径和绝对路径;
// 当跳转当另一页面,点击返回时,只触发了onShow事件,没有触发onLaunch和onReady事件;
// redirectTo事件,触发onUnload事件;
// 注意事项2 减少使用navigateTo,防止onHide消耗过多的资源;


小程序的事件触发

事件

通过行为进行的人机交互的方式;

类似html中onClick和onChange事件等;

如bindtap,当用户点击该组件的时候会在该页面对应的Page中找到相应的事件处理函数。

1
2
3
4
<!-- event.wxml-->
<view class="container">
<view id="tapTest" bindtap="clickMe"> Click me! </view>
</view>
1
2
3
4
5
6
7
8
9
// event.js
Page({
data: {
},
clickMe: function(e) {
console.log("Click Me Was Clicked");
console.log(e);
}
})
事件分类

事件分为冒泡事件和非冒泡事件

​ ·冒泡事件:当一个组件上的事件被触发后,该事件会向父节点传递;

​ ·非冒泡事件:当一个组件上的事件被触发后,该事件不会向父节点传递;


小程序的模块化

抽离通用方法作为通用函数;

构建utils-common类;

可以将一些公共的代码抽离成为一个单独的 js 文件,作为一个模块。模块只有通过 module.exports 或者 exports 才能对外暴露接口;

1
2
3
4
5
6
7
8
9
10
// common.js
function sayHello(name) {
console.log('Hello ' + name + '!')
}
function sayGoodbye(name) {
console.log(`Goodbye ${name} !`)
}
// 区别单引号''和反引号``
module.exports.sayHello = sayHello
exports.sayGoodbye = sayGoodbye

在需要使用这些模块的文件中,使用 require(path) 将公共代码引入 ;

1
2
3
4
5
6
7
8
9
var common = require('common.js')
Page({
helloMINA: function() {
common.sayHello('MINA')
},
goodbyeMINA: function() {
common.sayGoodbye('MINA')
}
})

视图层——数据绑定

HTML中,JQuery dom操作 $选择器;

WX中,通过数据绑定 类似于vue/react,通过Mustache表达式;

作用范围:内容,组件属性(双引号内),控制属性(双引号内),关键字(双引号内);

1
2
3
4
5
6
7
8
<!--pages/dataBind/dataBind.wxml-->
<view class='container' id="myid-{{testid}}">
{{msg}}
<checkbox checked="{{true}}"> </checkbox>
<checkbox checked="{{false}}"> </checkbox>
<checkbox checked="{{flag}}"> </checkbox>
<checkbox checked="{{unflag}}"> </checkbox>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
// pages/dataBind/dataBind.js
Page({
data: {
msg:"这是一个msg",
testid:1001,
flag:true,
unflag:false,
a:1,
b:2,
c:3
}
})
1
2
3
4
5
6
7
8
9
10
<!-- 三元运算 -->
<view hidden="{{flag ? true : false}}"> Hidden </view>
<!-- 算数运算 -->
<view> {{a + b}} + {{c}} + d </view>
view中的内容为 3 + 3 + d
<!-- 字符串运算 -->
<view>{{msg + hello + "test"}}</view>
view中内容为 这是一个msghellotest
<view>{{a+b + hello}}</view>
view中内容为 3hello

视图层——列表渲染

wx:key

如果列表中项目的位置会动态改变或者有新的项目添加到列表中,并且希望列表中的项目保持自己的特征和状态

wx:key 的值以两种形式提供

  1. 字符串,代表在 for 循环的 array 中 item 的某个 property,该 property 的值需要是列表中唯一的字符串或数字,且不能动态改变。
  2. 保留关键字 *this 代表在 for 循环中的 item 本身,这种表示需要 item 本身是一个唯一的字符串或者数字,如:

当数据改变触发渲染层重新渲染的时候,会校正带有 key 的组件,框架会确保他们被重新排序,而不是重新创建,以确保使组件保持自身的状态,并且提高列表渲染时的效率

视图层——条件渲染

wx:if

在框架中,使用 wx:if="" 来判断是否需要渲染该代码块,也可以用 wx:elif 和 wx:else 来添加一个 else 块:

1
2
3
<view wx:if="{{length > 5}}"> 1 </view>
<view wx:elif="{{length > 2}}"> 2 </view>
<view wx:else> 3 </view>
block wx:if

因为 wx:if 是一个控制属性,需要将它添加到一个标签上。如果要一次性判断多个组件标签,可以使用一个 <block/> 标签将多个组件包装起来,并在上边使用 wx:if 控制属性。

1
2
3
4
<block wx:if="{{true}}">
<view> view1 </view>
<view> view2 </view>
</block>

注意: <block/> 并不是一个组件,它仅仅是一个包装元素,不会在页面中做任何渲染,只接受控制属性。

wx:if vs hidden

因为 wx:if 之中的模板也可能包含数据绑定,所以当 wx:if 的条件值切换时,框架有一个局部渲染的过程,因为它会确保条件块在切换时销毁或重新渲染。

同时 wx:if 也是惰性的,如果在初始渲染条件为 false,框架什么也不做,在条件第一次变成真的时候才开始局部渲染

相比之下,hidden 就简单的多,组件始终会被渲染,只是简单的控制显示与隐藏

一般来说,wx:if 有更高的切换消耗而 hidden 有更高的初始渲染消耗。因此,如果需要频繁切换的情景下,用 hidden 更好,如果在运行时条件不大可能改变则 wx:if 较好


通用模板的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!-- 定义模板
使用 name 属性,作为模板的名字。然后在<template/>内定义代码片段-->
<template name="mytemp">
<view>姓名:{{name}}</view>
<view>年龄:{{age}}</view>
<view>地址:{{address}}</view>
<view>备注:{{remark}}</view>
<view>{{msg}}</view>
</template>
<!-- 使用模板
is 属性,声明需要的使用的模板,然后将模板所需要的 data 传入
is 属性可以使用 Mustache 语法,来动态决定具体需要渲染哪个模板 -->
<view class='container'>
<template is="mytemp" data="{{...person,msg,name:'rex',age:'18'}}" />
<template is="mytemp" data="{{...person,msg,name:'rex',age:'18'}}" />
</view>

<!--temp.js-->
Page({
data: {
person:{
address:"BEIJING",
remark:"This is a Template"
},
msg:"This is a Msg"
}
})

wxs模块——页面引用/模块调用模块

WXS(WeiXin Script)是小程序的一套脚本语言,结合 WXML,可以构建出页面的结构;

wxs 不依赖于运行时的基础库版本,可以在所有版本的小程序中运行;
wxs 与 javascript 是不同的语言,有自己的语法,并不和 javascript 一致;
wxs 的运行环境和其他 javascript 代码是隔离的,wxs 中不能调用其他 javascript 文件中定义的函数,也不能调用小程序提供的API;
wxs 函数不能作为组件的事件回调;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// module.wxs
var num2 = require("../wxs/num2.wxs");
var name = 'imooc';
var age = 18;
var method = function(obj) {
console.log(num2.name);
console.log(num2.age);
console.log(num2.method("num2"));
return obj;
}
module.exports = {
name: name,
age: age,
method: method
}
// num2.wxs
var name = 'imooc2';
var age = 20;
var method = function(obj) {
return obj;
}
module.exports = {
name: name,
age: age,
method: method
}
1
2
3
4
5
6
7
8
// wxs.wxml
<view class="container">
<!-- 页面级别的引用 -->
<wxs src="../wxs/module.wxs" module="item"></wxs>
<view>{{item.name}}</view>
<view>{{item.age}}</view>
<view>{{item.method("这是一个参数")}}</view>
</view>

模版的外部引用

模版在某个wxml中定义完毕后,可以被其他页面引用;

关键字import;

import 有作用域的概念,即只会 import 目标文件中定义的 template,而不会 import 目标文件 import 的 template,即不支持间接传递引用(A引用B,B引用C,A不能引用C);

WXML 提供两种文件引用方式import和include;

1
2
3
4
5
<view class="container">
<!-- 模版的外部引用 -->
<import src="../temp/temp.wxml"/>
<template is="mytemp" data="{{name:'imooc',age:'18',...person,msg}}"/>
</view>

页面引用外部wxml通用页面

HTML页面常分为Header,Body,Footer;

定义通用的Header和Footer;

关键字include;

include 可以将目标文件除了 <template/> <wxs/> 外的整个代码引入,相当于是拷贝到 include 位置;

1
2
3
4
5
6
7
8
<!-- index.wxml -->
<include src="header.wxml"/>
<view> body </view>
<include src="footer.wxml"/>
<!-- header.wxml -->
<view> header </view>
<!-- footer.wxml -->
<view> footer </view>

@ 2018年8月15日 20:41:52

Spring Cloud | Note-8

发表于 2018-08-10

Spring Cloud微服务 | Note(8)

@ 2018年8月10日 15:22:48

微服务的集中化配置

配置文件是为了迎合软件的个性化需求;

解决每个微服务的统一配置;

管理微服务的外部,中心化的配置中心;

为什么需要集中化配置

微服务数量多,配置多;

手工管理配置繁琐;

配置分类:

​ ·配置来源划分:源代码、文件、数据库连接、远程调用

​ ·配置环境划分:开发环境、测试环境、预发布环境、生产环境

​ ·配置集成阶段划分:编译、打包、运行

​ ·配置加载方式划分:启动加载、动态加载

配置中心的要求:

​ ·面向可配置的编码

​ ·隔离性

​ ·一致性

​ ·集中化配置

Spring Cloud Config

分布式外部化配置;Config Server;Config Client;


如何继集成Spring Cloud Config Server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// build.gradle
dependencies{
compile('org.springframework.cloud:spring-cloud-config-server')
}
// Application
@SpringBootApplication
@EnableDiscoveryClient
@EnableConfigServer
public class ConfigApplication{
public static void main(String[] args){
SpringApplication.run(ConfigApplication.class,args);
}
}
// application.properties
# config server
server.port=8888
spring.application.name=micro-weather-config-server
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
spring.cloud.config.server.git.uri=https:...
spring.cloud.config.server.git.search-paths=...

如何继集成Spring Cloud Config Client
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// build.gradle
dependencies{
compile('org.springframework.cloud:spring-cloud-config-client')
}
// Application
@SpringBootApplication
@EnableDiscoveryClient
public class ConfigClientApplication{
public static void main(String[] args){
SpringApplication.run(ConfigClientApplication.class,args);
}
}
// application.properties
# config server
spring.application.name=micro-weather-config-client
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
spring.cloud.config.profile=dev
spring.cloud.config.uri=http://localhost:8888
实现微服务的集中化配置

配置中心的文件命名规则

1
2
3
4
5
/{application}/{profile}/[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties

启动Eureka Server以及Config Server

作为Config Client可以在远端获取部署在git上的配置文件的内容


附录

@ 2018年8月10日 16:22:38

Spring Cloud | Note-7

发表于 2018-08-09

Spring Cloud微服务 | Note(7)

@ 2018年8月9日 15:14:02

API网关

可以管理所有API,用于统一API入口;所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能 ;具有功能:如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理;


API网关的意义

集成多个API;WEB应用API网关,移动应用API网关;避免内部信息泄露;为微服务添加额外的安全层;支持混合通信协议;降低构建微服务的复杂性;微服务模拟与虚拟化;

弊端:

架构上需要额外考虑更多编排与管理;

路由逻辑配置要进行统一的管理;可能引发单点故障;


API网关的实现方式

NGINX:HTTP服务器和反向代理,可用于API网关,简单配置,高性能稳定,低资源消耗;

Zuul:认证,鉴权,限流,路由,监控,弹性,安全,负载均衡,协助单点压测,静态响应等边缘服务的框架;

Kong:API网关的管理平台;专注提供微服务,基于NGINX;多插件;


如何集成Zuul

Zuul:主要作为路由转发和过滤器;

路由功能是微服务的一部分,比如/api/user转发到到user服务,/api/shop转发到到shop服务;

zuul默认和Ribbon结合实现了负载均衡的功能;

功能:认证,压力测试,金丝雀测试,动态路由,负载削减,安全,静态响应处理,主动交换管理;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// build.gradle
dependencies {
// Eureka Client
compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
// Zuul
compile('org.springframework.cloud:spring-cloud-starter-netflix-zuul')
}
// Application.java
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
public class ZuulApplication {

public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
// application.properties
spring.application.name=micro-weather-eureka-client-zuul
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
zuul.routes.hi.path=/hi/**
zuul.routes.hi.serviceId=micro-weather-eureka-client

以上配置的意思为:

启动micro-weather-eureka-client时,端口号为:8081;

而启动micro-weather-eureka-client-zuul,端口号为:8080;

访问http://localhost:8080/hello,请求的是client的服务;

而访问http://localhost:8081/hello,请求的是zuul的服务;

而访问http://localhost:8080/hi/hello,请求的是client的服务,由zuul进行了服务请求的转发;

实现API网关

将功能微服务对于数据API与城市数据API的依赖,转化为对Zuul网关的依赖;

1
2
3
4
5
6
7
// application.properties(gateway)
spring.application.name=micro-weather-eureka-client-zuul
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
zuul.routes.city.path=/city/**
zuul.routes.city.serviceId=micro-weather-city-eureka
zuul.routes.data.path=/data/**
zuul.routes.data.serviceId=micro-weather-data-eureka
1
2
3
4
5
6
7
8
// 创建新的DataClient替换原本的CityClient以及WeatherDataServer
@FeignClient("micro-weather-eureka-client-zuul")
public interface DataClient {
@GetMapping("/city/cities")
List<City> listCity() throws Exception;
@GetMapping("/data/weather/cityId/{cityId}")
WeatherResponse getDataByCityId(@PathVariable("cityId")String cityId);
}

附录

@ 2018年8月9日 23:08:56

Spring Cloud | Note-6

发表于 2018-08-08

Spring Cloud微服务 | Note(6)

@ 2018年8月8日 16:13:08

微服务的消费

实现服务与服务之间的调用;

微服务的消费模式

HTTP常用于消费者(无关平台);

服务直连模式:直接访问URI ;

​ 优点:简洁明了,平台语言无关性;

​ 缺点:无法保证服务的可用性,生产环境中少用;

客户端发现模式:

​ 服务实例启动后,将自己的位置信息提交到服务注册表中;

​ 客户端从服务注册表进行查询,获取可用的服务实例;

​ 客户端自行使用负载均衡算法从多个服务实例中选择一个;

服务端发现模式:

​ 区别于客户端的负载均衡器,是独立的;


常见微服务的消费者

Apache HttpClient:用于高效、丰富的http协议的客户端;提供最新的建议和版本;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 依赖
dependenies{
compile('org.apache.httpcomponents:httpclient:4.5.3')
}
// 注入
@Configuration
public clas RestConfiguration{
@Autowired
private RestTemplateBuilder builder;
@Bean
public RestTemplate restTemplate(){
return builder.build();
}
}
// 使用
@Service
public class WeatherDataServiceImpl implements WeatherDataService{
@Autowired
private RestTemplate restTemplate;
private WeatherResponse doGetWeatherData(String uri){
ResponseEntity<String> response = restTemplate.getForEntity(uri,String.class);
// ...
}
}

Ribbon:基于客户端实现负载均衡的工具,可支持HTTP和TCP;每个负载均衡器都是组合支持;常与Eureka组合使用;提供服务消费的客户端;多种负载均衡的算法;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 依赖
dependenies{
compile('org.springframework.cloud:spring-cloud-starter-netflix-ribbon')
}
// 注入
@Configuration
@RibbonClient(name="ribbon-client",configuration=RibbonConfiguration.class)
public clas RestConfiguration{
@Autowired
private RestTemplateBuilder builder;
@Bean
public RestTemplate restTemplate(){
return builder.build();
}
}
// 配置
@Configuration
public class RibbonConfiguration{
@Bean
public ZonePreferenceServerListFilter serverListFilter(){
ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter();
filter.setZone("myZone");
return filter;
}
@Bean
public IPing ribbonPing(){
return new PingUrl();
}
}
// application.properties
spring.application.name=micro-weather-eureka-client-ribbon
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/

// 使用
@RestController
public class CityController{
@Autowired
private RestTemplate restTemplate;
@GetMapping("/cities")
public String listCity(){
// 通过服务应用名称查找
String body = restTemplate.getForEntity("http://msa-weather-city-server/cities",Strign.class);
return body;
}
}

Feign:声明式WEB服务端;可拔插注释的支持;集成负载均衡;Feign 采用的是基于接口的注解;Feign 整合了ribbon,具有负载均衡的能力;整合了Hystrix,具有熔断的能力

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// build.gradle
dependencies {
// Eureka Client
compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
// Feign
compile('org.springframework.cloud:spring-cloud-starter-openfeign')
// Test
testCompile('org.springframework.boot:spring-boot-starter-test')
}

// application.properties
spring.application.name= micro-weather-eureka-client-feign
eureka.client.serviceUrl.defaultZone= http://localhost:8761/eureka/
feign.client.config.feignName.connectTimeout= 5000
feign.client.config.feignName.readTimeout= 5000

// CityClient.java
@FeignClient("micro-weather-city-eureka")
public interface CityClient {

@GetMapping("/cities")
String listCity();
}
// CityController.java
@RestController
public class CityController {
@Autowired
private CityClient cityClient;

@GetMapping("/cities")
public String listCity() {
// 通过Feign客户端来查找
String body = cityClient.listCity();
return body;
}
}

// Application.java
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class FeignApplication {

public static void main(String[] args) {
SpringApplication.run(FeignApplication.class, args);
}
}

使用Feign实现服务的消费者

解决三个TODO问题

1.数据采集微服务在数据同步任务中,依赖与城市数据API微服务

2.功能服务查询天气信息,依赖于数据API微服务

3.功能服务提供的城市列表,依赖于城市数据API微服务


实现服务的负载均衡及高可用

对每个微服务启动多个实例,作为服务负载均衡的测试

到此,完成从单体架构到微服务架构的Feign负载均衡的,Eureka高可用的搭建


附录

@ 2018年8月9日 15:09:55

Spring Cloud | Note-5

发表于 2018-08-07

Spring Cloud微服务 | Note(5)

@2018年8月7日 11:20:19

微服务协调者 Spring Cloud

简介

构建一整套完整的分布式系统的框架;解决微服务的问题;解决配置管理;服务注册;服务发现(不同服务之间互相发现,通过服务中心发现与调用);断路器(保护系统,过载过大);智能路由;微代理;服务间调用;一次性令牌;思维导图模版;全局锁;控制总线;领导选举;分布式会话;集群状态;分布式消息

Spring Cloud 与 Spring Boot关系

Spring Boot是构建Spring Cloud架构的基石;

每个子项目都可看做一个Spring Boot项目;

Spring Cloud 版本命名规则(伦敦地铁站命名);

Finchley版本基于Spring Boot2.0.x;

入门配置、子项目介绍

Spring Cloud Config :配置中心,利用git集中管理程序的配置

Spring Cloud Netflix:集成Netflix的开源软件(Eureka、Hystrix、Zuul、Archaius)

Spring Cloud Bus:消息总线,利用分布式消息将服务和服务实例连接在一起,用于在一个集群中传播状态的变化(配置更改的实现,用与Config联合实现热部署)

Spring Cloud for Cloud Foundry:略

Spring Cloud Cluster:基于Zookeeper、Redis、Hazelacast、Consul实现的领导选举和平民状态模式的抽象和实现

Spring Cloud Consul:分布式协调软件,基于Hashicorp Consul实现服务的发现和配置管理

Spring Cloud Security:在Zuul代理中为OAuth2 REST客户端和认证头转发提供负载均衡

Spring Cloud Sleuth:用于程序的分布式跟踪、基于日志(ELK)的跟踪相兼容,可以日志的收集

Spring Cloud Data Flow:针对现代运行时可组合的微服务程序的云本地编排服务

Spring Cloud Stream:轻量级的事件驱动的微服务框架构建连接到外部系统的程序

Spring Cloud Stream App Starters:基于Spring Boot为外部系统提供Spring的集成

Spring Cloud Task App Starters:Spring Boot应用程序,可能是任何进程,包括Spring Batch作业,并可以在数据处理有限的时间终止

Spring Cloud Contract:一个总体项目,包含帮助用户成功实施消费者驱动契约的解决方案


微服务的注册与发现

服务发现的意义

发布的服务,让其他的服务可以使用发现(例:域名DNS系统);

发现服务的方法:

通过URI访问服务 弊端:绑定主机、难记、难以负载均衡;

使用Eureka:提供服务注册与发现;与Spring Cloud无缝集成;高可用性;开源;


集成Eureka Server

环境:Spring Boot 2.0.3 RELEASE,Spring Cloud Starters Netflix Eureka Server Finchley.SR1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// build.gradle
buildscript {
ext {
springBootVersion = '2.0.3.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
ext {
springCloudVersion = 'Finchley.SR1'
}

dependencies {
// Eureka Server
compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-server')
testCompile('org.springframework.boot:spring-boot-starter-test')
}

dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// application.properties
# Eureka
server.port=8761
eureka.instance.hostname=localhost
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/

// Application.java
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}

集成Eureka Client

环境:Spring Boot 2.0.3 RELEASE,Spring Cloud Starters Netflix Eureka Server Finchley.SR1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// build.gradle
buildscript {
ext {
springBootVersion = '2.0.3.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
ext {
springCloudVersion = 'Finchley.SR1'
}

dependencies {
// Eureka Server
compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
testCompile('org.springframework.boot:spring-boot-starter-test')
}

dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
// application.properties
# eureka client
spring.application.name=micro-weather-eureka-client
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/

// Application.java
@SpringBootApplication
@EnableEurekaClient
public class MicroWeatherEurekaClientApplication {
public static void main(String[] args) {
SpringApplication.run(MicroWeatherEurekaClientApplication.class, args);
}
}

模拟服务关闭后,Server出现:

EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.

证明Server开启保护模式:保持一个时间段(15分钟),服务是否进行暂停,可能是由于网络问题不稳定而不是服务的暂停,进行一个防护机制,减少误判。


实现服务的注册与发现

将四个微服务进行改造,集成到Eureka Server & Cilent中

环境:Redis,Spring Boot 2.0.3 RELEASE,Spring Cloud Starters Netflix Eureka Server Finchley.SR1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 对四个微服务的build.gradle 配置文件进行修改
// 添加如下配置,类eureka client
buildscript {
ext {
springBootVersion = '2.0.3.RELEASE'
}
...
}
...
ext {
springCloudVersion = 'Finchley.SR1'
}

dependencies {
...
// Eureka Client
compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
}

dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
// 四个微服务的Application.java文件进行修改
@SpringBootApplication
@EnableDiscoveryClient
public class ...Application {
public static void main(String[] args) {
SpringApplication.run(...Application.class, args);
}
}
// 四个微服务的application.properties
# ... eureka server
server.pory=808x
spring.application.name=micro-weather-...-eureka
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/

附录

URI:

URI:统一资源标志符(Uniform Resource Identifier)
URL:统一资源定位符(Uniform Resource Location)

URI与URL都是定位资源位置的,就是表示这个资源的位置信息。

URI是一种宽泛的含义更广的定义,而URL则是URI的一个子集,就是说URL是URI的一部分。

换言之,每个URL都是URI,但是不是每个URI都是URL的。他们之间最明显的不同就是在java.net.URI你只能看到他的一些属性,他只是表示一个位置,但是你没有办法通过URI获取到这个对象的流,但是URL就不同了。java.net.URL该类提供方法(openConnection()),通过该方法我们可以通过IO流操作。

也就是说:URL可以直接操作的,但是URI不可以。

负载均衡:

当一台服务器的性能达到极限时,可以使用服务器集群来提高网站的整体性能。那么,在服务器集群中,需要有一台服务器充当调度者的角色,用户的所有请求都会首先由它接收,调度者再根据每台服务器的负载情况将请求分配给某一台后端服务器去处理。

在这个过程中,调度者如何合理分配任务,保证所有后端服务器都将性能充分发挥,从而保持服务器集群的整体性能最优,这就是负载均衡问题。

@ 2018年8月7日 16:09:19

Spring Cloud | Note-4

发表于 2018-08-03

Spring Cloud微服务 | Note(4)

@2018年8月3日 16:22:42

Weather System的微服务架构设计与实现

System-架构设计
现有弊端:

​ ·大而全(功能不足内聚);混杂太多功能;难以理解;难以维护;难以扩展

改造需求:

​ ·微服务拆分的足够小,每个微服务的业务单一

​ ·微服务支持水平拓展

​ ·根据需要,实现微服务之间的相互调用

微服务拆分如下:

​ ·数据采集(msa-collection-server)

​ ·主要功能(msa-report-server)

​ ·城市API(msa-city-server)

​ ·数据API(msa-data-server)

数据流向:

1533198561868


数据采集微服务实现

对代码进行删减,只保留该微服务实现对应服务所需的内容

仅保留了

1
2
3
4
// config 	QuartzConfiguration RestConfiguration
// job WeatherDataSyncJob
// service WeatherDataCollectionService & Impl
// vo City
功能微服务实现

对代码进行删减,只保留该微服务实现对应服务所需的内容

1
2
3
4
// controller 	WeatherReportController
// service WeatherReportService
// vo City Forecast Weather WeatherResponse Yesterday
// UI
城市数据API微服务实现
1
2
3
4
// controller 	WeatherReportController
// service WeatherReportService
// vo City Forecast Weather WeatherResponse Yesterday
// resource citylist.xml
数据API微服务实现

对代码进行删减,只保留该微服务实现对应服务所需的内容

1
2
3
// controller 	WeatherController
// service WeatherDataService
// vo Forecast Weather WeatherResponse Yesterday

到此,原本单架构的服务已经拆分成了四个部分的微服务;

下一个笔记将开始使用Spring Cloud将四个微服务集成起来,

形成一个完整的服务项目;


附录

@2018年8月3日 19:11:49

Spring Cloud | Note-2

发表于 2018-08-02

Spring Cloud微服务 | Note(2)

@2018年8月2日 09:40:55

环境:

JDK8+,Graedle4+,Spring Boot 2.0+,Apache HttpClient

数据来源:

中华万年历API(注意乱码处理)

http://wthrcdn.etouch.cn/weather_mini?city=深圳

http://wthrcdn.etouch.cn/weather_mini?citykey=101280601

应用@
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
@Data
public class WeatherResponse implements Serializable{
private Weather data;
private Integer status;
private String desc;
}
@Data
public class Weather implements Serializable{
// 城市
private String city;
// ...属性根据JSON返回数据编写
}
// 天气数据接口
public interface WeatherDataService {
// 根据城市ID查询天气数据
WeatherResponse getDataByCityId(String cityId);
// 根据城市名称查询天气数据
WeatherResponse getDataByCityName(String cityName);
}
// 实现
@Service
public class WeatherDataServiceImpl implements WeatherDataService {
private static final String WEATHER_URL = "http://wthrcdn.etouch.cn/weather_mini?";
@Autowired
private RestTemplate restTemplate;
@Override
public WeatherResponse getDataByCityId(String cityId) {
String url = WEATHER_URL + "citykey=" + cityId;
return this.doGetWeather(url);
}
private WeatherResponse doGetWeather(String url) {
ResponseEntity<String> respString = restTemplate.getForEntity(url, String.class);
// JSON数据转化为对应类
ObjectMapper mapper = new ObjectMapper();
WeatherResponse resp = null;
String strBody = null;
if (respString.getStatusCodeValue() == 200) {
strBody = respString.getBody();
}
try {
resp = mapper.readValue(strBody, WeatherResponse.class);
} catch (IOException e) {
e.printStackTrace();
}
return resp;
}
}
// config(见附录)
@Configuration
public class RestConfiguration {
@Autowired
private RestTemplateBuilder builder;
@Bean
public RestTemplate restTemplate(){
return builder.build();
}
}

使用Redis提升并发访问能力

Q1:由于依赖的第三方API数据,强依赖会导致长延时(请求-请求-返回-解析-返回),来回两次HTTP请求
Q2:免费接口的风险,限制访问次数
Q3:对第三方的并发冲击
解决办法:Redis

及时响应;减少服务调用;

应用@
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private final static Logger logger = LoggerFactory.getLogger(WeatherDataServiceImpl.class);
private static final long TIME_OUT = 1800L;
@Autowired
private StringRedisTemplate stringRedisTemplate;

// 修改WeaterDataServiceImpl中的doGetWeather()
private WeatherResponse doGetWeather(String url) {
String key = url;
String strBody = null;
// JSON数据转化为对应类
ObjectMapper mapper = new ObjectMapper();
WeatherResponse resp = null;
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
// 先查询Redis缓存
if (stringRedisTemplate.hasKey(key)) {
logger.info("REDIS HAS DATA");
strBody = ops.get(key);
} else {
logger.info("REDIS DOESN'T HAS DATA");
// 缓存不存在,调用接口
ResponseEntity<String> respString = restTemplate.getForEntity(url, String.class);
if (respString.getStatusCodeValue() == 200) {
strBody = respString.getBody();
}
// 数据写入缓存
ops.set(url, strBody, TIME_OUT, TimeUnit.SECONDS);
}
// 类型返回
try {
resp = mapper.readValue(strBody, WeatherResponse.class);
} catch (IOException e) {
logger.error("ERROR:" + e.toString());
}
return resp;
}

实现数据的同步

Quartz Scheduler定时获取数据
应用@
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Configuration
public class QuartzConfiguration {
// JobDetail
@Bean
public JobDetail weatherDataSyncJobDetail(){
return JobBuilder.newJob(WeatherDataSyncJob.class)
.withDescription("WeatherDataSyncJob").storeDurably().build();
}
// Trigger
@Bean
public Trigger weatherDataSyncTrigger(){
SimpleScheduleBuilder schedBuilder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(1800).repeatForever();
return TriggerBuilder.newTrigger().forJob(weatherDataSyncJobDetail())
.withDescription("WeatherDataSyncTrigger").withSchedule(schedBuilder).build();
}
}

public class WeatherDataSyncJob extends QuartzJobBean {
private final static Logger logger = LoggerFactory.getLogger(WeatherDataSyncJob.class);
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
logger.info("Weather Data Sync Job");
}
}
获取城市数据

http://mobile.weather.com.cn/js/citylist.xml

减少调用服务;缓存数据;使用XML存储文件;服务调用本地XML文件;

暂用(citilist.xml)

映射xml数据到java中(使用JASB)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// CityList.java
@XmlRootElement(name = "c")
@XmlAccessorType(XmlAccessType.FIELD) // 通过字段访问
public class CityList {
@XmlElement(name = "d")
private List<City> cityList;
}

// City.java
@XmlRootElement(name = "d")
@XmlAccessorType(XmlAccessType.FIELD) // 通过字段访问
public class City {
@XmlAttribute(name = "d1")
private String cityId;
@XmlAttribute(name = "d2")
private String cityName;
@XmlAttribute(name = "d3")
private String cityCode;
@XmlAttribute(name = "d4")
private String province;
// ...GETTER SETTER
}

// Util
public class XmlBuilder {
// 将XML数据转换成JAVA对应的POJO
public static Object xmlStrToObject(Class<?> clazz, String xmlStr) throws Exception {
Object xmlObject = null;
Reader reader = null;
JAXBContext context = JAXBContext.newInstance(clazz);
// XML转为对象的接口
Unmarshaller unmarshaller = context.createUnmarshaller();
reader = new StringReader(xmlStr);
xmlObject = unmarshaller.unmarshal(reader);
if (reader != null) {
reader.close();
}
return xmlObject;
}
}

// Service
@Service
public class CityDataServiceImpl implements CityDataService {
@Override
public List<City> listCity() throws Exception {
// 读取XML文件
Resource resource = new ClassPathResource("citylist.xml");
BufferedReader br = new BufferedReader(new InputStreamReader(resource.getInputStream(), "utf-8"));
StringBuffer buffer = new StringBuffer();
String line = "";
while ((line = br.readLine()) != null) {
buffer.append(line);
}
br.close();
// XML转化为JAVA对象
CityList cityList = (CityList) XmlBuilder.xmlStrToObject(CityList.class,buffer.toString());
return cityList.getCityList();
}
}

// Modify WeatherService
@Override
public void syncDataByCityId(String cityId) {
String url = WEATHER_URL + "citykey=" + cityId;
this.saveWeatherData(url);
}
private void saveWeatherData(String url) {
String key = url;
String strBody = null;
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
// 缓存不存在,调用接口
ResponseEntity<String> respString = restTemplate.getForEntity(url, String.class);
if (respString.getStatusCodeValue() == 200) {
strBody = respString.getBody();
}
// 数据写入缓存
ops.set(url, strBody, TIME_OUT, TimeUnit.SECONDS);
}

到这一步,基本数据就已经完成了获取和缓存

推荐GUI界面 Redis-GUI

在GUI里面,可以直观的看到所缓存的数据,包括前面提到的City,Weather

接下来就是完成基本的UI界面

到此,整个项目的基本内容就已经实现

接下来,进入到Spring Cloud内容的学习


附录

RestTemplate

RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率;

@2018年8月2日 14:11:16

Spring Cloud | Note-3

发表于 2018-08-02

Spring Cloud微服务 | Note(3)

@2018年8月2日 15:13:33

单块架构进化为微服务架构

单块架构

1533195168009

优点:

功能划分清晰;层次关系良好;层层独立;部署简单;相对技术单一;成本低

缺点:

功能太大;升级风险高;维护成本增加;交付周期变长(按系统交付);可伸缩性差;监控困难


微服务架构

随着系统的壮大,单块架构需要转化,转化成每一个服务单元模块,以API交互,平台无关,高内聚低耦合

1533195708067


微服务设计原则(颗粒度设计原则)

Q:什么是微服务

A:服务可以运行在自己进程中(独立部署),轻量级交互机制,例如HTTP交互,服务A和B之间,没有太多的技术关联,可以使用不同的数据存储形式(MySQL或NoSQL)

设计原则

1.拆分足够微(颗粒度):足够小足以即可(亚马逊两个披萨原则,不能太小,不能太大,满足管理成本)

2.轻量级通信:微服务函数之间的调用需要体现跨域、跨主机,同步(REST),异步(消息中间件)

3.领域驱动原则:服务需要体现领域业务模型,减少微服务通用语言的复杂性,使团队理解服务上下文边界(明确了解开发的业务功能)

4.单一职责原则:颗粒度粗(高耦合,不利于维护,修改重合);高内聚,低耦合,对其它服务的依赖要低,单一界限上下文,通过公开的API访问

5.DevOps以及两个披萨:开发团队小而精,且具有全栈能力,降低沟通成本

6.不限于技术栈:与平台无关,根据场景、业务具体选择技术栈


微服务
服务拆分:

​ ·关注服务的颗粒度,颗粒度才用领域驱动设计进行指导,专注某一领域的功能;

服务注册:

​ ·每个微服务,服务之间的通讯实现;服务注册机制(服务注册中心);通过心跳机制获取状态,并且通过状态通知其他服务;

服务发现:

​ ·通过服务名称等找到所需的服务

服务消费:

​ ·调用其他服务的过程;消费者(调用者),提供商(被调用)

统一入口:

​ ·给服务提供方便的服务名称

配置管理:

​ ·定制功能的配置文件不同;

熔断机制:

​ ·系统的防护;阻挡所有的访问,断开某些服务,返回提示信息,保护系统;

自动拓展:

​ ·服务根据负荷状况,进行自动扩展;


微服务拆分:
意义:

​ ·易于实现;利于维护;易于部署,轻量级(Spring Boot内嵌tomcat);易于更新(服务都是隔离的,易于修改、更新、部署)

正向的反馈闭环:

1533197427183

拆分方法:

横向拆分:根据不同的业务功能,拆分成不同的微服务

​ ·数据采集,数据存储,数据查询,数据展示

纵向拆分:把业务中的不同的功能,模块进行拆分

​ ·1533197559770

使用DDD(驱动设计原则):反映领域的业务模型

​ ·天气数据采集限界上下文:数据采集,数据存储

​ ·天气预报限界上下文:数据展示

​ ·天气数据API限界上下文:数据查询

​ ·城市数据API限界上下文:数据查询


附录

微服务架构设计

NGINX-BLOG

微服务架构的优势与不足

使用API Gateway

深入微服务架构的进程间通信

服务发现的可行方案以及实践案例

微服务的事件驱动数据管理

选择微服务部署策略

从单体式架构迁移到微服务架构

@2018年8月2日 16:20:46

Spring Cloud | Note-1

发表于 2018-08-01

Spring Cloud微服务 | Note(1)

2018年7月28日 14:01:02

Spring Boot 简化开发

​ 1.抛弃传统JavaEE项目繁琐的配置,学习过程

​ 2.更快入门、Spring血统、开箱即用、零配置

开启Spring Boot项目

​ 检查开发环境 采用IDEA、JDK8+、Gradle4.9、Spring Boot 2.0.3(https://start.spring.io)

​ 编译项目 > gradle build

知识点

​ 1.@RestController 用于请求Rest的Controller 包含注解@Controller & @ResponseBody

​ 2.测试@RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc

​ 3.MockMvc(测试)

1
2
3
mockMvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().string(equalTo("Hello World")));

​ 4.静态方法status()/content()/equalTo()

1
2
3
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.hamcrest.Matchers.equalTo;

附录

一、目录文件解释

buildscript.gradle (代码块中脚本优先执行)

  • ext 用于定义动态属性(sprintBootVersion = ‘2.0.3 RELEASE’)
  • repositories 使用中央仓库以及spring仓库
  • dependencies 依赖关系
  • classpath 声明执行其余脚本时,ClassLoader可使用这些依赖项(可引用ext中动态属性${sprintBootVersion})
  • 使用插件
  • 指定生成编译文件版本 默认jar
  • 依赖关系(编译阶段、测试阶段)

二、Gradle

  1. 按约定声明构建和建设
  2. 强大的支持多工程的构建
  3. 强大的依赖管理(基于Apache Ivy),提供最大的便利去构建工程
  4. 全力支持已有的 Maven 或者Ivy仓库基础建设
  5. 支持传递性依赖管理,在不需要远程仓库和pom.xml和ivy配置文件的前提下
  6. 基于groovy脚本构建,其build脚本使用groovy语言编写
  7. 具有广泛的领域模型支持构建
  8. 深度 API
  9. 易迁移
  10. 自由和开放源码,Gradle是一个开源项目,基于 ASL

三、MockMvc

Blog System | Note-6

发表于 2018-08-01

Blog System | Note-5

@2018年8月1日 14:43:38 @Knowledge From Imooc


####

搜索功能

使用Elasticsearchs实现全文检索

应用@
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
// 文档类型的EsBlog实体
@Document(indexName = "blog",type = "blog")
public class EsBlog implements Serializable{
@Id
private String id;

// Blog实体的ID
@Field(index = FieldIndex.not_analyzed)
private Long blogId;
private String title;
private String summary;
private String content;
// 不使用全文检索字段
@Field(index = FieldIndex.not_analyzed)
private String username;
...
private String tags;
}

// EsBlogRepository
public interface EsBlogRepository extends ElasticsearchRepository<EsBlog,String> {
// 模糊搜索查询
Page<EsBlog> findDistinctEsBlogByTitleContainingOrSummaryContainingOrContentContainingOrTagsContaining(String title,String summary,String content,String tags,Pageable pageable);
}

// EsBlogService
public interface EsBlogService {
// 最新博客列表
Page<EsBlog> listNewestEsBlogs(String keyword, Pageable pageable);
...
}

// Impl
@Override
public List<TagVO> listTop30Tags() {
List<TagVO> list = new ArrayList<>();
// given
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchAllQuery())
.withSearchType(SearchType.QUERY_THEN_FETCH)
.withIndices("blog").withTypes("blog")
.addAggregation(terms("tags").field("tags").order(Terms.Order.count(false)).size(30))
.build();
// when
Aggregations aggregations = elasticsearchTemplate.query(searchQuery, new ResultsExtractor<Aggregations>() {
@Override
public Aggregations extract(SearchResponse response) {
return response.getAggregations();
}
});

StringTerms modelTerms = (StringTerms)aggregations.asMap().get("tags");
Iterator<Terms.Bucket> modelBucketIt = modelTerms.getBuckets().iterator();
while (modelBucketIt.hasNext()){
Terms.Bucket actiontypeBucket = modelBucketIt.next();
list.add(new TagVO(actiontypeBucket.getKey().toString(),actiontypeBucket.getDocCount()));
}
return list;
}

// BlogController
@Controller
@RequestMapping("/blogs")
public class BlogController {
@Autowired
private EsBlogService esBlogService;

/**
* 博客列表
*/
@GetMapping
public String listBlogs(@RequestParam(value = "order", required = false, defaultValue = "new") String order,
@RequestParam(value = "keyword", required = false, defaultValue = "") String keyword,
@RequestParam(value = "async", required = false) boolean async,
@RequestParam(value = "pageIndex", required = false, defaultValue = "0") int pageIndex,
@RequestParam(value = "pageSize", required = false, defaultValue = "10") int pageSize,
Model model) {
Page<EsBlog> page = null;
List<EsBlog> list = null;
// 系统初始化时,没有博客数据
boolean isEmpty = true;
try {
// 热门查询
if (order.equals("hot")) {
...
} else if (order.equals("new")) {
...
}
isEmpty = false;
} catch (Exception e) {
...
}
// 所在页面数据列表
list = page.getContent();
model.addAttribute("order", order);
...
// 首次访问页面才加载
if (!async && !isEmpty) {
List<EsBlog> newest = esBlogService.listTop5NewestEsBlogs();
model.addAttribute("newest", newest);
...
}
return (async == true ? "/index :: #mainContainerRepleace" : "/index");
}
}

总结

这一次的博客系统学习,收获颇多(慕课网),

了解到企业级开发的流程(需求分析-技术选型-框架搭建-原型设计-等等)学习到基本的架构设计,

BackEnd:

Spring,Spring MVC,Spring Data,Spring Security,Hibernate,SpringBoot基础知识的巩固;

FrontEnd:

Thymeleaf模版的学习;Bootstrap的使用,jQuery,HTML5,JS,CSS;

Elasticsearchs全文检索;

JpaRepository;

MongoDB-File-Server的使用;

Gradle管理;

继续学习,附下官方文档,保持基础知识的学习,不只是对着代码学着敲而已,官方文档是最好的学习资料。


附录

SpringBoot

Thymeleaf

Bootstrap

Elasticsearch

Gradle

@2018年8月1日 15:02:24

1…345

REX CHEN

日常记录

47 日志
20 标签
© 2019 REX CHEN