spring cloud简介

spring cloud简介

spring cloud 为开发人员提供了快速构建分布式系统的一些工具,包括配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等。

spring cloud核心组件
Eureka

EurekaNetflix 开发的,一个基于 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();
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值