spring cloud简介
spring cloud 为开发人员提供了快速构建分布式系统的一些工具,包括配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等。
spring cloud核心组件
Eureka
Eureka 是 Netflix 开发的,一个基于 REST 服务的,服务注册与发现的组件
服务注册中心 : Eureka的服务端应用,提供服务注册和发现功能
服务提供者 : 提供服务的应用,可以是SpringBoot应用,也可以是其它任意技术实现,只要对外提供的是Rest风格服 务即可
服务消费者 : 消费应用从注册中心获取服务列表,从而得知每个服务方的信息,知道去哪里调用服务方
同时,服务提供方与Eureka之间通过“心跳”
机制进行监控,当某个服务提供方出现问题,Eureka自然会把它从服务列表中剔除。
这就实现了服务的自动注册、发现、状态监控
Feign
Feign是Netflix开发的声明式、模板化的HTTP客户端, Feign可以帮助我们更快捷、优雅地调用HTTP API。
Ribbon
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。
Hystrix
在分布式环境中,许多服务依赖项中的一些必然会失败。Hystrix是一个库,通过添加延迟容忍和容错逻辑,帮助你控制这些分布式服务之间的交互。Hystrix通过隔离服务之间的访问点、停止级联失败和提供回退选项来实现这一点,所有这些都可以提高系统的整体弹性。
Zuul
微服务网关。这个组件是负责网络路由的。
项目搭建
首先创建一个主Maven工程,在其pom文件引入依赖,spring Boot版本为2.0.3.RELEASE,Spring Cloud版本为Finchley.RELEASE。这个pom文件作为父pom文件,起到依赖版本控制的作用,其他module工程继承该pom。
(nalan-cloud):聚合各个模块
<modules>
<module>nalan-discovery</module>
<module>nalan-config</module>
<module>nalan-gateway</module>
<module>nalan-common</module>
<module>nalan-generator</module>
<module>nalan-plugin</module>
<module>nalan-sys-admin</module>
</modules>
通过元素用户可以自定义一个或多个Maven属性,然后在POM的其他地方使用${属性名}的方式引用该属性,这种做法的最大意义在于消除重复和统一管理。
<properties>
<spring-boot.version>2.1.7.RELEASE</spring-boot.version>
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<lombok.version>1.18.8</lombok.version>
</properties>
DepencyManagement应用场景
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<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>
创建服务注册中心
nalan-discovery:服务注册中心
其pom.xml继承了父pom文件,并引入spring-cloud-starter-netflix-eureka-server的依赖
<parent>
<artifactId>nalan-cloud</artifactId>
<groupId>com.nalan.cloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<!--服务发现和注册中心-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
启动一个服务注册中心 只需要一个注解@EnableEurekaServer,这个注解需要在springboot工程的启动application类上加:
package com.nalan.cloud.discovery;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class NalanDiscoveryApplication {
public static void main(String[] args){
SpringApplication.run(NalanDiscoveryApplication.class, args);
}
}
eureka是一个高可用的组件,它没有后端缓存,每一个实例注册之后需要向注册中心发送心跳(因此可以在内存中完成),在默认情况下erureka server也是一个eureka client ,必须要指定一个 server。eureka server的配置文件bootstrap.yml:
通过eureka.client.registerWithEureka:false和fetchRegistry:false来表明自己是一个eureka server.
server:
port: 8761
eureka:
instance:
hostname: nalan-discovery
prefer-ip-address: true
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://nalan:nalan@${eureka.instance.hostname}:${server.port}/eureka/
配置中心注册到服务注册中心
nalan-config:配置中心
需要在 本机定义下 host (C:\Windows\System32\drivers\etc\hosts) 添加配置
127.0.0.1 nalan-discovery
配置注册中心地址与配置目录
server:
port: 8888
spring:
application:
name: nalan-config
profiles:
active: native
# 配置中心
cloud:
config:
server:
native:
search-locations: classpath:/config/
# 注册中心
eureka:
instance:
prefer-ip-address: true
client:
service-url:
defaultZone: http://nalan:nalan@nalan-discovery:8761/eureka/
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: '*'
在config目录下有相关配置信息
nalan-sys-admin-dev.yml:数据源信息
# 数据源
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: introcks1234
url: jdbc:mysql://192.168.4.118:3306/nalan_cloud?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai
用户管理服务注册到服务注册中心
nalan-sys-admin:用户管理服务
继承父pom文件
<!--eureka 客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
启动类EurekaClientApplication.java添加`@EnableFeignClients注解
网关服务注册到服务注册中心
nalan-gateway:网关服务
代码生成器
nalan-generate:代码生成器
作用: 可以根据我们已经设计好的数据库表帮助我们自动生成实体类(pojo)、接口(dao)、映射文件(mapper),这样就可以避免每次使用表的时候手动创建一些类和映射文件,节约了大量的时间。(仅限于简单的CRUD操作)
1,引入代码生成器jar包
<!--代码生成模板引擎-->
<dependency>
<artifactId>velocity</artifactId>
<groupId>org.apache.velocity</groupId>
<version>${velocity.version}</version>
</dependency>
2、配置文件
server:
port: 8900
# 数据源配置
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: introcks1234
url: jdbc:mysql://192.168.4.118:3306/nalan_cloud?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai
resources:
static-locations: classpath:/static/,classpath:/views/
3、 编写主要的生成工具类
package com.nalan.cloud.generator.util;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import com.nalan.cloud.generator.entity.ColumnEntity;
import com.nalan.cloud.generator.entity.GenConfig;
import com.nalan.cloud.generator.entity.TableEntity;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* 代码生成器 工具类
*
*/
@Slf4j
@UtilityClass
public class GenUtils {
private static final String ENTITY_JAVA_VM = "Entity.java.vm";
private static final String MAPPER_JAVA_VM = "Mapper.java.vm";
private static final String SERVICE_JAVA_VM = "Service.java.vm";
private static final String SERVICE_IMPL_JAVA_VM = "ServiceImpl.java.vm";
private static final String CONTROLLER_JAVA_VM = "Controller.java.vm";
private static final String MAPPER_XML_VM = "Mapper.xml.vm";
private static final String MENU_SQL_VM = "menu.sql.vm";
private static final String INDEX_VUE_VM = "index.vue.vm";
private static final String API_JS_VM = "api.js.vm";
private static final String CRUD_JS_VM = "crud.js.vm";
private List<String> getTemplates() {
List<String> templates = new ArrayList<>();
templates.add("template/Entity.java.vm");
templates.add("template/Mapper.java.vm");
templates.add("template/Mapper.xml.vm");
templates.add("template/Service.java.vm");
templates.add("template/ServiceImpl.java.vm");
templates.add("template/Controller.java.vm");
// templates.add("template/menu.sql.vm");
return templates;
}
/**
* 生成代码
*/
public void generatorCode(GenConfig genConfig, Map<String, String> table,
List<Map<String, String>> columns, ZipOutputStream zip) {
//配置信息
Configuration config = getConfig();
boolean hasBigDecimal = false;
//表信息
TableEntity tableEntity = new TableEntity();
tableEntity.setTableName(table.get("tableName"));
if (StrUtil.isNotBlank(genConfig.getComments())) {
tableEntity.setComments(genConfig.getComments());
} else {
tableEntity.setComments(table.get("tableComment"));
}
String tablePrefix;
if (StrUtil.isNotBlank(genConfig.getTablePrefix())) {
tablePrefix = genConfig.getTablePrefix();
} else {
tablePrefix = config.getString("tablePrefix");
}
//表名转换成Java类名
String className = tableToJava(tableEntity.getTableName(), tablePrefix);
tableEntity.setCaseClassName(className);
tableEntity.setLowerClassName(StringUtils.uncapitalize(className));
//列信息
List<ColumnEntity> columnList = new ArrayList<>();
for (Map<String, String> column : columns) {
ColumnEntity columnEntity = new ColumnEntity();
columnEntity.setColumnName(column.get("columnName"));
columnEntity.setDataType(column.get("dataType"));
columnEntity.setComments(column.get("columnComment"));
columnEntity.setExtra(column.get("extra"));
//列名转换成Java属性名
String attrName = columnToJava(columnEntity.getColumnName());
columnEntity.setCaseAttrName(attrName);
columnEntity.setLowerAttrName(StringUtils.uncapitalize(attrName));
//列的数据类型,转换成Java类型
String attrType = config.getString(columnEntity.getDataType(), "unknowType");
columnEntity.setAttrType(attrType);
if (!hasBigDecimal && "BigDecimal".equals(attrType)) {
hasBigDecimal = true;
}
//是否主键
if ("PRI".equalsIgnoreCase(column.get("columnKey")) && tableEntity.getPk() == null) {
tableEntity.setPk(columnEntity);
}
columnList.add(columnEntity);
}
tableEntity.setColumns(columnList);
//没主键,则第一个字段为主键
if (tableEntity.getPk() == null) {
tableEntity.setPk(tableEntity.getColumns().get(0));
}
//设置velocity资源加载器
Properties prop = new Properties();
prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
Velocity.init(prop);
//封装模板数据
Map<String, Object> map = new HashMap<>(16);
map.put("tableName", tableEntity.getTableName());
map.put("pk", tableEntity.getPk());
map.put("className", tableEntity.getCaseClassName());
map.put("classname", tableEntity.getLowerClassName());
// map.put("pathName", tableEntity.getLowerClassName().toLowerCase());
map.put("pathName", com.baomidou.mybatisplus.core.toolkit.StringUtils.camelToHyphen(tableEntity.getCaseClassName()));
map.put("columns", tableEntity.getColumns());
map.put("hasBigDecimal", hasBigDecimal);
map.put("datetime", DateUtil.now());
if (StrUtil.isNotBlank(genConfig.getComments())) {
map.put("comments", genConfig.getComments());
} else {
map.put("comments", tableEntity.getComments());
}
if (StrUtil.isNotBlank(genConfig.getAuthor())) {
map.put("author", genConfig.getAuthor());
} else {
map.put("author", config.getString("author"));
}
if (StrUtil.isNotBlank(genConfig.getModuleName())) {
map.put("moduleName", genConfig.getModuleName());
} else {
map.put("moduleName", config.getString("moduleName"));
}
if (StrUtil.isNotBlank(genConfig.getPackageName())) {
map.put("package", genConfig.getPackageName());
map.put("mainPath", genConfig.getPackageName());
} else {
map.put("package", config.getString("package"));
map.put("mainPath", config.getString("mainPath"));
}
VelocityContext context = new VelocityContext(map);
//获取模板列表
List<String> templates = getTemplates();
for (String template : templates) {
//渲染模板
StringWriter sw = new StringWriter();
Template tpl = Velocity.getTemplate(template, CharsetUtil.UTF_8);
tpl.merge(context, sw);
try {
//添加到zip
zip.putNextEntry(new ZipEntry(Objects.requireNonNull(getFileName(template, tableEntity.getCaseClassName(), map.get("package").toString(), map.get("moduleName").toString()))));
IoUtil.write(zip, CharsetUtil.UTF_8, false, sw.toString());
IoUtil.close(sw);
zip.closeEntry();
} catch (IOException e) {
throw new RuntimeException("渲染模板失败,表名:" + tableEntity.getTableName(), e);
}
}
}
/**
* 列名转换成Java属性名
*/
private String columnToJava(String columnName) {
return WordUtils.capitalizeFully(columnName, new char[]{'_'}).replace("_", "");
}
/**
* 表名转换成Java类名
*/
private String tableToJava(String tableName, String tablePrefix) {
if (StringUtils.isNotBlank(tablePrefix)) {
tableName = tableName.replace(tablePrefix, "");
}
return columnToJava(tableName);
}
/**
* 获取配置信息
*/
private Configuration getConfig() {
try {
return new PropertiesConfiguration("generator.properties");
} catch (ConfigurationException e) {
throw new RuntimeException("获取配置文件失败,", e);
}
}
/**
* 获取文件名
*/
private String getFileName(String template, String className, String packageName, String moduleName) {
String packagePath = moduleName + File.separator + "src" + File.separator + "main" + File.separator + "java" + File.separator;
if (StringUtils.isNotBlank(packageName)) {
packagePath += packageName.replace(".", File.separator) + File.separator;
}
if (template.contains(ENTITY_JAVA_VM)) {
return packagePath + "entity" + File.separator + className + ".java";
}
if (template.contains(MAPPER_JAVA_VM)) {
return packagePath + "mapper" + File.separator + className + "Mapper.java";
}
if (template.contains(SERVICE_JAVA_VM)) {
return packagePath + "service" + File.separator + className + "Service.java";
}
if (template.contains(SERVICE_IMPL_JAVA_VM)) {
return packagePath + "service" + File.separator + "impl" + File.separator + className + "ServiceImpl.java";
}
if (template.contains(CONTROLLER_JAVA_VM)) {
return packagePath + "controller" + File.separator + className + "Controller.java";
}
if (template.contains(MAPPER_XML_VM)) {
return moduleName + File.separator + "src" + File.separator + "main" + File.separator + "resources" + File.separator + "mapper" + File.separator + className + "Mapper.xml";
}
if (template.contains(MENU_SQL_VM)) {
return className.toLowerCase() + "_menu.sql";
}
if (template.contains(INDEX_VUE_VM)) {
return moduleName + "-ui" + File.separator + "src" + File.separator + "views" +
File.separator + className.toLowerCase() + File.separator + "index.vue";
}
if (template.contains(API_JS_VM)) {
return moduleName + "-ui" + File.separator + "src" + File.separator + "api" + File.separator + className.toLowerCase() + ".js";
}
if (template.contains(CRUD_JS_VM)) {
return moduleName + "-ui" + File.separator + "src" + File.separator + "const" +
File.separator + "crud" + File.separator + className.toLowerCase() + ".js";
}
return null;
}
}
给注册中心添加security安全认证
1、在我们的注册中心添加security依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2、在yml配置文件中添加以下内容:
spring:
security:
user:
name: nalan
password: nalan
application:
name: nalan-discovery
cloud:
config:
enabled: false
3、在我们的eureka默认注册地址中写上我们安全验证的账户和密码
如何将服务注册到Eureka上呢?只需在客户端配置文件做如下修改
eureka.client.serviceUrl.defaultZone=http://user:123456@localhost:8000/eureka/
eureka:
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://admin:admin@127.0.0.1:8003/eureka/
4、开启基于HTTP basic的认证方法:
处理客户端无法注册到服务中心的问题 Cannot execute request on any known server 错误。
高版本的丢弃了spring.security.basic.enabled这个属性,默认false那么如何开启呢?方法如下 :
/**
* 注册中心需要security验证
*/
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
@SneakyThrows
protected void configure(HttpSecurity http) {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/actuator/**").permitAll()
.anyRequest()
.authenticated().and().httpBasic();
}
}