(十一)在线教育网站搭建一SpringCloud服务发现实现操作

SpringCloud eureka feign

1. 分布式-SpringCloud-服务实现 Netflix Eureka

2. 介绍

Eureka是Netflix开发的服务发现框架,SpringCloud将它集成在自己的子项目 spring-cloud-netflix中,实现SpringCloud的服务发现功能。Eureka包含两个组件: Eureka Server和Eureka Client。

Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。

Eureka Client是一个java客户端,用于简化与Eureka Server的交互,客户端同时也包含一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会 向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有 接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90 秒)。

Eureka Server之间通过复制的方式完成数据的同步,Eureka还提供了客户端缓存机制,即使所有的Eureka Server都挂掉,客户端依然可以利用缓存中的信息消费其他服务的API。综上,Eureka通过心跳检查、客户端缓存等机制,确保了系统的高可用性、灵活性和可伸缩性。

3. 启动eureka服务器

3.1. Maven依赖

父项目pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<properties>
<spring-cloud.version>Hoxton.SR3</spring-cloud.version>
</properties>

<dependencyManagement>
<dependencies>
<!-- springcloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

子项目pom.xml

1
2
3
4
5
6
7
8
<artifactId>edueureka</artifactId>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>

3.2. 配置eureka-server

1
2
3
4
5
6
7
8
9
10
server.port=8003

spring.application.name=online-eureka

#服务器本身是否注册到服务器
eureka.client.register-with-eureka=false
#从eureka获取注册信息
eureka.client.fetch-registry=false
#通信地址
eureka.client.service-url.defaultZone=http://127.0.0.1:${server.port}/eureka/

3.3. 启动@EnableEurekaServer

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplcation {

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

3.4. 访问http://localhost:8003/

4. eureka客户端 - eduservice,videoservice

4.1. eureka-client Maven依赖

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

4.2. 配置eureka-client

1
2
3
4
5
6
7
8
9
10
11
#服务端口号
server.port=8001

#服务名
spring.application.name=online-edu

#eureka 注册
#eureka 服务器中获取的服务器的ip地址是否为主机名
eureka.instance.prefer-ip-address=true
#指定注册中心地址
eureka.client.service-url.defaultZone=http://127.0.0.1:8003/eureka/

4.3. 启动客户端

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableEurekaClient
public class EduServiceApplication {

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

4.4. 启动eduservice,videoservice

服务器端出现注册信息

Registered instance ONLINE-EDU/192.168.0.101:online-edu:8001 with status UP (replication=true)

Registered instance ONLINE-VIDEO/192.168.0.101:online-video:8002 with status UP (replication=true)

5. 删除课程并删除阿里云中对应的video

5.1. eduservice调用videoservice

5.2. 添加Maven依赖

1
2
3
4
5
<!--服务调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

5.3. 主配置类中配置@EnableFeignClients

1
2
3
4
5
6
7
8
9
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class EduServiceApplication {

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

5.4. 添加接口@FeignClient

videoservice中的videocontroller的访问路径就是/videoservice/video/delete/{videoId}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
@CrossOrigin
@RequestMapping("/videoservice/video")
public class VideoController {

@Autowired
VideoService videoService;

@DeleteMapping("/delete/{videoId}")
public R deleteById(@PathVariable String videoId){
videoService.deleteVideoById(videoId);
return R.ok().message("删除视频成功");
}
}

@FeignClient注解用于指定从哪个服务中调用功能 ,名称与被调用的服务名保持一致。

@RequestMapping注解用于对被调用的微服务进行地址映射。

@PathVariable注解一定要指定参数名称,否则出错

@Component注解防止,在其他位置注入CodClient时idea报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.online.edu.eduservice.client;

import com.online.edu.common.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient("online-video")
@Component
public interface VodClient {

@DeleteMapping("/videoservice/video/delete/{videoId}")
public R deleteAliyunVideo(@PathVariable("videoId") String videoId);
}

5.5. 添加到删除课程方法中 - 单个+多个

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
@AutoWired
VodClient vodClient;

//根据id删除
public boolean deleteById(String id){
//查询课程对应视频源Id
String videoId = baseMapper.selectById(id).getVideoSourceId();

if(!StringUtils.isEmpty(videoId)) {

vodClient.deleteAliyunVideo(videoId);
}

int flag = baseMapper.deleteById(id);

return flag>0;
}

//根据条件删除
@Override
public void deleteWrapper(QueryWrapper<EduVideo> queryWrapper) {
//仅查询video_source_id
queryWrapper.select("video_source_id");
List<EduVideo> videos = baseMapper.selectList(queryWrapper);

//删除所有云视频
for(EduVideo video:videos){
vodClient.deleteAliyunVideo(video.getVideoSourceId());
}
baseMapper.delete(queryWrapper);
}

5.6. 使用swagger-ui测试 -error

feign.RetryableException: Read timed out executing DELETE

大概是响应时间限制,响应默认为3s

1
2
3
#连接时长
feign.httpclient.connection-timeout=2000
feign.httpclient.connection-timer-repeat=3000

修改时间

1
2
3
#连接时长
feign.httpclient.connection-timeout=20000
feign.httpclient.connection-timer-repeat=30000

5.7. 成功返回操作

1
2
3
4
5
6
7
8
9
INFO  com.netflix.discovery.DiscoveryClient - DiscoveryClient_ONLINE-EDU/192.168.0.101:online-edu:8001: registering service...
INFO s.d.s.web.plugins.DocumentationPluginsBootstrapper - Context refreshed
INFO s.d.s.web.plugins.DocumentationPluginsBootstrapper - Found 1 custom documentation plugin(s)

INFO c.n.loadbalancer.DynamicServerListLoadBalancer - DynamicServerListLoadBalancer for client online-video initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=online-video,current list of Servers=[192.168.0.101:8002],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone; Instance count:1; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;]
},Server stats: [[Server:192.168.0.101:8002; Zone:defaultZone; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0]
]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@71b75228

INFO com.netflix.config.ChainedDynamicProperty - Flipping property: online-video.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647

6. 统计日注册人数

6.1. 用户注册表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
drop table if exists ucenter_member;

create table ucenter_member (
id char(19) not null comment '会员id',
openid varchar(128) default null comment '微信openid',
mobile varchar(20) default null comment '手机号',
password varchar(255) default null comment '密码',
nickname varchar(50) default null comment '昵称',
sex tinyint(2) unsigned default null comment '性别 1 女,2 男',
age tinyint(3) unsigned default null comment '年龄',
avatar varchar(255) default null comment '用户头像',
sign varchar(100) default null comment '用户签名',
is_disabled tinyint(1) not null default '0' comment '是否禁用 1(true)已禁用, 0(false)未禁用',
is_deleted tinyint(1) not null default '0' comment '逻辑删除 1(true)已删除, 0(false)未删除',
gmt_create datetime not null comment '创建时间',
gmt_modified datetime not null comment '更新时间',
primary key (id)
) comment'会员表';

6.2. 统计某天注册的人数

1
select count(*) from ucenter_member u where Date(u.gmt_create)='2019-01-19'

6.3. UcenterMemberController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.online.edu.memberservice.controller;

import com.online.edu.common.R;
import com.online.edu.memberservice.service.UcenterMemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/memberservice/ucenter-member")
@CrossOrigin
public class UcenterMemberController {

@Autowired
UcenterMemberService ucenterMemberService;

@GetMapping("/getDailyRegisterNumber/{day}")
public R getDailyRegisterNumber(@PathVariable String day){
int count = ucenterMemberService.getDailyRegisterMember(day);

return R.ok().data("registerNum",count);
}
}

6.4. UcenterMemberMapper.xml

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.online.edu.memberservice.mapper.UcenterMemberMapper">

<select id="getRegisterNum" resultType="java.lang.Integer">
select count(*) from ucenter_member u where Date(u.gmt_create) = #{day}
</select>
</mapper>

6.5. UcenterMemberMapper

1
2
3
4
5
public interface UcenterMemberMapper extends BaseMapper<UcenterMember> {

Integer getRegisterNum(String day);

}

6.6. 配置扫描xml

1
2
3
4
5
#mybatis-plus
mybatis-plus.mapper-locations=classpath:com/online/edu/memberservice/mapper/xml/*.xml

#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

6.7. UcenterMemberServiceImpl

1
2
3
4
5
6
7
8
@Service
public class UcenterMemberServiceImpl extends ServiceImpl<UcenterMemberMapper, UcenterMember> implements UcenterMemberService {

@Override
public Integer getDailyRegisterMember(String day) {
return baseMapper.getRegisterNum(day);
}
}

6.8. 测试swagger-ui

6.9. 控制台

1
2
3
4
5
==>  Preparing: select count(*) from ucenter_member u where Date(u.gmt_create) = ? 
==> Parameters: 2019-01-02(String)
<== Columns: count(*)
<== Row: 2
<== Total: 1

6.10. 将注册人数加入到统计表中

7. 统计结果放入日统计表

7.1. 日统计表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
drop table if exists statistics_daily;

create table statistics_daily (
id char(19) not null comment '主键',
date_calculate varchar(20) not null comment '统计日期',
register_num int(11) not null default '0' comment '注册人数',
login_num int(11) not null default '0' comment '登录人数',
video_view_num int(11) not null default '0' comment '每日播放视频数',
course_num int(11) not null default '0' comment '每日新增课程数',
gmt_create datetime not null comment '创建时间',
gmt_modified datetime not null comment '更新时间',
primary key (id),
key statistics_day (date_calculate)
)comment='网站统计日数据';

7.2. 调用memberservice项目路径,得到日注册量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.online.edu.statisticsservice.client;

import com.online.edu.common.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient("online-member")
@Component
public interface MemberClient {

@GetMapping("/memberservice/ucenter-member/getDailyRegisterNumber/{day}")
public R getDailyRegisterNumber(@PathVariable("day") String day);
}

7.3. StatisticsDailyServiceImpl调用MemberClient

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
package com.online.edu.statisticsservice.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.online.edu.common.R;
import com.online.edu.statisticsservice.client.MemberClient;
import com.online.edu.statisticsservice.entity.StatisticsDaily;
import com.online.edu.statisticsservice.mapper.StatisticsDailyMapper;
import com.online.edu.statisticsservice.service.StatisticsDailyService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class StatisticsDailyServiceImpl extends ServiceImpl<StatisticsDailyMapper, StatisticsDaily> implements StatisticsDailyService {

@Autowired
MemberClient memberClient;

@Override
public boolean getDailyRegister(String day) {

//将统计表已有日期删除
QueryWrapper<StatisticsDaily> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("date_calculate",day);
baseMapper.delete(queryWrapper);

//调用member中的统计
Integer count =(Integer) memberClient.getDailyRegisterNumber(day).getData().get("registerNum");

StatisticsDaily daily = new StatisticsDaily();
daily.setDateCalculate(day);
daily.setRegisterNum(count);

//随机生成数据
daily.setCourseNum((int)(Math.random()*200));
daily.setLoginNum((int)(Math.random()*200));
daily.setVideoViewNum((int)(Math.random()*200));

//注册
int flag= baseMapper.insert(daily);
return flag>0;

}
}

7.4. StatisticsDailyController

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
package com.online.edu.statisticsservice.controller;


import com.online.edu.common.R;
import com.online.edu.statisticsservice.service.StatisticsDailyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
* <p>
* 网站统计日数据 前端控制器
* </p>
*
* @author Wang T
* @since 2020-03-09
*/
@RestController
@RequestMapping("/statisticsservice/statistics-daily")
@CrossOrigin
public class StatisticsDailyController {

@Autowired
StatisticsDailyService dailyService;

@GetMapping("/dailyRegister/{day}")
public R getDailyRegister(@PathVariable String day){

boolean flag=dailyService.getDailyRegister(day);
if(flag) {
return R.ok().message("生成成功");
}else{
return R.error().message("生成失败");
}

}

}

7.5. 生成语句

1
2
3
==>  Preparing: INSERT INTO statistics_daily ( id, date_calculate, register_num, login_num, video_view_num, course_num, gmt_create, gmt_modified ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ? ) 
==> Parameters: 1236923304708902913(String), 2019-01-19(String), 5(Integer), 137(Integer), 56(Integer), 155(Integer), 2019-04-19 15:54:31.425(Timestamp), 2019-04-19 15:54:31.425(Timestamp)
<== Updates: 1

8. 前端通过日期选择统计当天注册数

8.1. router/index.js - 生成路由链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
path: '/statistic',
component: Layout,
redirect: '/statistic/list',
name: '统计分析',
meta: { title:'统计分析', icon: 'chart'},
children:[
{
path: 'generte',
name: '生成数据',
component: () => import('@/views/edu/statistic/generte'),
meta: {title:'生成数据', icon: 'guide'}
},
{
path: 'list',
name: '图表显示',
component: () => import('@/views/edu/statistic/list'),
meta: { title: '图表显示', icon: 'table' }
}
]
}

8.2. api/edu/statistis.js - 请求方法

1
2
3
4
5
6
7
8
9
10
11
12
import request from '@/utils/request'

const base = '/statisticsservice/statistics-daily'

export default{
getDailyRegisterNum(day){
return request({
url:`${base}/dailyRegister/`+day,
method:'get'
})
}
}

8.3. views/edu/statistic/generte.vue - 页面展示

el-date-picker 日期组件

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
<template>
<div class="app-container">
<el-form :inline="true" class="demo-form-inline">
<el-form-item>
<el-date-picker
v-model="date"
type="date"
placeholder="请选择日期"
value-format="yyyy-MM-dd"/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-s-open" @click="getDailyRegisterNum">生成</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import statistic from '@/api/edu/statistic'
export default {
data(){
return{
date:''

}
},
created(){

},
methods:{
getDailyRegisterNum(){
statistic.getDailyRegisterNum(this.date).then(response=>{
this.$message({
type:'success',
message:response.message
})
this.$router.push({
path:'/statistic/list'
})
})

}


}
}
</script>
本文结束  感谢您的阅读