整个项目采用了Gradle工具进行构建,首先是构建一个纯净的Gradle项目,一个空的项目,为整个SpringCloud项目提供一个大的整合范围,同时本项目采用lombok插件实现日志打印,所以IDEA需要安装Lombok的插件,eclipse同理,个人推荐使用IDEA
首先最外层的gradle
项目的build.gradle
文件撰写,引入基础的jar
包,和基础环境的配置,整个大项目的构建层次如上图
buildscript {
// 定义变量
ext {
springBootVersion = '2.0.0.RELEASE'
ALI_REPOSITORY_URL = 'http://maven.aliyun.com/nexus/content/groups/public'
}
// 仓库地址
repositories {
// 先从阿里云maven仓库中去下载,没有再去maven中央仓库
maven {
url ALI_REPOSITORY_URL
}
mavenCentral()
}
// 构建时所需要的插件或者是其他的依赖
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
// 所有模块的总仓库地址
allprojects {
// 在build.gradle文件中直接声明的依赖项、仓库地址等信息是项目自身需要的资源。
repositories {
maven {
url ALI_REPOSITORY_URL
}
}
}
subprojects {
apply plugin: 'java'
apply plugin: 'war';
apply plugin: 'idea'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'org.springframework.boot'
sourceCompatibility = 1.8
targetCompatibility = 1.8
// 指定编码格式
[compileJava,compileTestJava,javadoc]*.options*.encoding = 'UTF-8'
version = "1.0.0-SNAPSHOT" // 开发版本: -SNAPSHOT 正式版本:.RELEASE
description = "spring boot in action"
dependencies {
// SpringBoot测试类需要的jar包
testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-test'
//lombok的jar包
compileOnly 'org.projectlombok:lombok:1.18.10'
annotationProcessor 'org.projectlombok:lombok:1.18.10'
testCompileOnly 'org.projectlombok:lombok:1.18.10'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.10'
//swagger驱动
compile 'io.springfox:springfox-swagger-ui:2.9.2'
compile 'io.springfox:springfox-swagger2:2.9.2'
compile 'io.github.swagger2markup:swagger2markup:1.3.1'
compile 'joda-time:joda-time:2.9.4'
}
}
Common
模块common
主要目的 是将所有项目均需要使用的jar
包和工具类的整合,例如DateUtil
,统一返回类等,因为common
不会单独成为一个项目,所以不需要配置yml
。
Common
模块的build.gradle
文件书写
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
//SpringBootWeb模块
compile group: 'org.springframework.boot', name: 'spring-boot-starter-web'
//springboot的aop切面驱动
compile group: 'org.springframework.boot', name: 'spring-boot-starter-aop'
compile 'org.hibernate:hibernate-validator:5.3.6.Final'
compile 'com.fasterxml.jackson.core:jackson-databind:2.9.7'
}
jar.enabled=true
Eureka
模块Eureka
模块的build.gradle
文件书写
dependencies {
//首先引入common模块
compile project(":oa-common")
testCompile group: 'junit', name: 'junit', version: '4.12'
//Eurake注册中心依赖
compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-netflix-eureka-server', version: '2.0.0.RELEASE'
// https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies
runtime group: 'org.springframework.cloud', name: 'spring-cloud-dependencies', version: 'Finchley.SR4'
}
此时编写启动类和对应的yml
文件,因为个人习惯将不同的属性分别写在application.yml
和bootstrap.yml
两个文件中
bootstrap.yml
# 应用名称
spring:
application:
name: eureka-server
server:
port: 9001 #服务端口
application.yml
文件eureka:
instance:
# 使用 ip 代替实例名
prefer-ip-address: true
# 实例的主机名
hostname: ${spring.cloud.client.ip-address}
# 实例的 ID 规则
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
# hostname: localhost
client:
#是否将eureka自身作为应用注册到eureka注册中心
register-with-eureka: false
#为true时,可以启动,但报异常:Cannot execute request on any known server
fetch-registry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/
#基本信息
info:
description: Eureka注册中心
version: v1.0
author: Eureka
Eureka
项目的启动类package com.js;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* @Description Eurake注册中心,启动时看佛头就行了,不用看那些小字,眼睛挺痛的
* @Author Eureka
*/
@Slf4j
@EnableEurekaServer
@SpringBootApplication
public class OaEurakeStart {
public static void main(String[] args) {
try{
SpringApplication.run(OaEurakeStart.class,args);
log.info(print());
log.info("项目启动成功");
}catch (Exception e){
log.info("项目启动失败");
}
}
private static synchronized String print() {
StringBuilder sb = new StringBuilder();
sb.append("\n");
sb.append(" _ooOoo_\n");
sb.append(" o8888888o\n");
sb.append(" 88\" . \"88\n");
sb.append(" (| -_- |)\n");
sb.append(" O\\ = /O\n");
sb.append(" ____/`---'\\____\n");
sb.append(" .' \\\\| |// `.\n");
sb.append(" / \\\\||| : |||// \\ \n");
sb.append(" / _||||| -:- |||||- \\ \n");
sb.append(" | | \\\\\\ - /// | |\n");
sb.append(" | \\_| ''\\---/'' | |\n");
sb.append(" \\ .-\\__ `-` ___/-. /\n");
sb.append(" ___`. .' /--.--\\ `. . __\n");
sb.append(" .\"\" '< `.___\\_<|>_/___.' >'\"\".\n");
sb.append(" | | : `- \\`.;`\\ _ /`;.`/ - ` : | |\n");
sb.append(" \\ \\ `-. \\_ __\\ /__ _/ .-` / /\n");
sb.append("======`-.____`-.___\\_____/___.-`____.-'======\n");
sb.append(" `=---='\n");
sb.append("...................................................\n");
return sb.toString();
}
}
因为正常来说,SpringCloud
本身存在生产者和消费者,为了演示方便我就只写一个生产者进行测试,消费者通过PostMan
的请求模拟.
该模块的build.gradle
文件书写(图片中的api
模块是为SpringCloud
的feignClient
准备的)
dependencies {
compile project(":oa-common")
testCompile group: 'junit', name: 'junit', version: '4.12'
//Eurake注册中心依赖
compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-netflix-eureka-server', version: '2.0.0.RELEASE'
// https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies
runtime group: 'org.springframework.cloud', name: 'spring-cloud-dependencies', version: 'Finchley.SR4'
// https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign
compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-openfeign', version: '2.0.0.RELEASE'
}
description = "oa运营模块服务"
bootstrap.yml
# 应用名称
spring:
application:
name: oa-operation-service
server:
port: 8080 #服务端口
application.yml
文件server:
tomcat:
# 该线程池可以容纳的最大线程数。默认值:200;
maxThreads: 300
# 最大连接线程数,即:并发处理的最大请求数,默认值为 75
maxConnections: 3000
# 允许的最大连接数,应大于等于 maxProcessors ,默认值为 100
acceptCount: 1000
# 编码方式
uri-encoding: UTF-8
spring:
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
application:
base-package: com.js
#基本信息
info:
description: oa-operation-service
version: v1.0
author: operation
eureka:
instance:
# 使用 ip 代替实例名
prefer-ip-address: true
# 实例的主机名
hostname: ${spring.cloud.client.ip-address}
# 实例的 ID 规则
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
client:
serviceUrl:
defaultZone: http://localhost:9001/eureka/
# encoder-name:
# register-with-eureka: false
fetch-registry: false
application:
name: oa-operation-service
oa-operation-service
的启动类package com.js;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @Description 启动类
*/
@EnableEurekaClient
@SpringBootApplication
@Slf4j
public class OaServiceStart {
public static void main(String[] args) {
try{
SpringApplication.run(OaServiceStart.class,args);
log.info(print());
log.info("项目启动成功");
}catch (Exception e){
log.info("项目启动失败");
}
}
private static synchronized String print() {
StringBuilder sb = new StringBuilder();
sb.append("\n");
sb.append(" _ooOoo_\n");
sb.append(" o8888888o\n");
sb.append(" 88\" . \"88\n");
sb.append(" (| -_- |)\n");
sb.append(" O\\ = /O\n");
sb.append(" ____/`---'\\____\n");
sb.append(" .' \\\\| |// `.\n");
sb.append(" / \\\\||| : |||// \\ \n");
sb.append(" / _||||| -:- |||||- \\ \n");
sb.append(" | | \\\\\\ - /// | |\n");
sb.append(" | \\_| ''\\---/'' | |\n");
sb.append(" \\ .-\\__ `-` ___/-. /\n");
sb.append(" ___`. .' /--.--\\ `. . __\n");
sb.append(" .\"\" '< `.___\\_<|>_/___.' >'\"\".\n");
sb.append(" | | : `- \\`.;`\\ _ /`;.`/ - ` : | |\n");
sb.append(" \\ \\ `-. \\_ __\\ /__ _/ .-` / /\n");
sb.append("======`-.____`-.___\\_____/___.-`____.-'======\n");
sb.append(" `=---='\n");
sb.append("...................................................\n");
return sb.toString();
}
}
因为想要模拟生产者,我们写一个简单的Controller
package com.js.controller;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Description Swagger测试controller
*/
@RestController
@Api("测试Controller")
public class HelloController {
private String url = "HelloController";
@GetMapping("test")
@ApiOperation(value = "测试Controller", notes = "测试Controller")
public String testSwagger() {
System.out.println("测试分支");
return url;
}
}
此时我们访问http://localhost:9001
就可以看到项目是否注册到注册中心
oa-gateway
模块因为正常来说,SpringCloud
本身存在生产者和消费者,为了演示方便我就只写一个生产者进行测试,消费者通过PostMan
的请求模拟.
该模块的build.gradle
文件书写(图片中的api
模块是为SpringCloud
的feignClient
准备的),在SpringBoot2.x
版本存在两种路由转发方法,一种是zuul
组件,另一种是gateway
,本次采用zuul
,当采用Spring web
启动时无法通过gateway
进行路由转发
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
compile 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client:2.0.0.RELEASE'
compile 'org.springframework.cloud:spring-cloud-starter-netflix-zuul:2.0.0.RELEASE'
}
bootstrap.yml
# 应用名称
spring:
application:
name: oa-gateway
server:
port: 10000 #服务端口
application.yml
文件eureka:
instance:
# 使用 ip 代替实例名
prefer-ip-address: true
# 实例的主机名
hostname: ${spring.cloud.client.ip-address}
# 实例的 ID 规则
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
client:
serviceUrl:
# 注册中心地址
defaultZone: http://${eureka.instance.hostname}:9001/eureka/
logging:
level:
# log 级别
org.springframework.cloud.gateway: debug
#自定义路由映射
zuul:
routes:
oa-operation-service: /operation/**
oa-user-service: /user/**
#统一入口为上面的配置,其他入口忽略
ignored-patterns: /*-service/**
host:
connect-timeout-millis: 3000
socket-timeout-millis: 3000
#忽略整个服务,对外提供接口
# ignored-services: oa-service
ribbon:
ReadTimeout: 3000
ConnectTimeout: 3000
hystrix:
command:
default:
execution:
isolation:
thread:
timeout-in-milliseconds: 3000
oa-gateway
的启动类package com.js;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
/**
* @Description 网关启动类
* @EnableZuulProxy 网关注解
*/
@Slf4j
@EnableDiscoveryClient
@SpringBootApplication
@EnableZuulProxy
public class OaGateWayStart {
public static void main(String[] args) {
try{
SpringApplication.run(OaGateWayStart.class,args);
log.info(print());
log.info("项目启动成功");
}catch (Exception e){
log.info("项目启动失败");
}
}
private static synchronized String print() {
StringBuilder sb = new StringBuilder();
sb.append("\n");
sb.append(" _ooOoo_\n");
sb.append(" o8888888o\n");
sb.append(" 88\" . \"88\n");
sb.append(" (| -_- |)\n");
sb.append(" O\\ = /O\n");
sb.append(" ____/`---'\\____\n");
sb.append(" .' \\\\| |// `.\n");
sb.append(" / \\\\||| : |||// \\ \n");
sb.append(" / _||||| -:- |||||- \\ \n");
sb.append(" | | \\\\\\ - /// | |\n");
sb.append(" | \\_| ''\\---/'' | |\n");
sb.append(" \\ .-\\__ `-` ___/-. /\n");
sb.append(" ___`. .' /--.--\\ `. . __\n");
sb.append(" .\"\" '< `.___\\_<|>_/___.' >'\"\".\n");
sb.append(" | | : `- \\`.;`\\ _ /`;.`/ - ` : | |\n");
sb.append(" \\ \\ `-. \\_ __\\ /__ _/ .-` / /\n");
sb.append("======`-.____`-.___\\_____/___.-`____.-'======\n");
sb.append(" `=---='\n");
sb.append("...................................................\n");
return sb.toString();
}
}
此时,我们访问Eureka
时http://localhost:9001
会看到网关服务和生产服务都已经成功注册
我们对生产者项目直接进行访问http://localhost:8080/test
,数据正常返回,因为我所返回的参数进行自定义类的包装可能显示有区别
而我们配置网关的目的是不直接访问这个项目此时我们测试访问网关路径http://localhost:10000/operation/test
会发现展示相同的效果,也就是说网关拦截到我们配置的带有/operation/**
的路径之后,转发到了oa-operation-service
对应的服务,这是在有一台服务的情况下,在微服务架构下,任何一台服务不会只有单独一个项目在运行,此时网关会随机发放到其中一台服务上,也就是轮询机制。
存在两种形式调用,一种是http
请求,而另一种就是feignClient
的形式调用,主要说明feignClient
方法
当oa-operation-service
,需要释放api
时,我们首先将oa-operation-api
模块引入oa-operation-service
我们需要在oa-operation-service
的build.gradle
下引入
compile project(":oa-operation-api")
package com.js.api.system;
import com.js.form.BasePageForm;
import com.js.form.SysLogForm;
import com.js.response.BaseResponse;
import com.js.vo.PageResVo;
import com.js.vo.system.SysLogVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
@RequestMapping("/system/log")
@Api("系统日志Controller")
public interface LogControllerApi {
@PostMapping("getLog")
@ApiOperation(value = "分页获取系统日志", notes = "分页获取系统日志")
BaseResponse<PageResVo<SysLogVo>> getLogMess(@RequestBody BasePageForm basePageForm);
@GetMapping("addLog")
@ApiOperation(value = "添加系统日志", notes = "添加系统日志")
void addLog(@RequestBody SysLogForm sysLogForm);
}
当调用者和生产者不再同一个项目下时,jar
包的传递就比较依赖私服的存在,如果是像我的项目一样,oa-user-service
想要调用oa-operation-service
接口时,可以在oa-user-service
直接引入oa-operation-api
此时我们需要创建一个feignClient
的配置
package com.js.config;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration;
/**
* @Description: 扫描feignClient并加载
* @Date: 2018/10/27
*/
@Configuration
@EnableFeignClients(basePackages = {"com.js.feignclient"})
public class FeignClientConfiguration {
}
接下来我们需要些一个代理服务
package com.js.feignclient;
import com.js.api.system.LogControllerApi;
import org.springframework.cloud.openfeign.FeignClient;
/**
* @Description:value值为想要引入项目的服务名此时你会发现注解Autowired注入LogProxyClient
* 到某个方法里时可以通过.的形式调用
*/
@FeignClient(value = "oa-operation-service")
public interface LogProxyClient extends LogControllerApi {
}