编码规范
命名
严格遵守java命名规范
- 命名采用驼峰式
- 变量名将拥有一定的含义,要通俗易懂,杜绝使用含糊不清的变量名称
- 其他参照java官方要求进行编码
注释
必须提供注释说明具体的业务逻辑。可以自行创建intellij注释模板。方法包括,方法注释,类注释, 方法需要写明具体的用途以及注意事项。
微服务命名
项目中的业务服务需要根据具体的业务类型携带service,比如user-service, order-service...
编码注意事项
所有微服务将完成服务注册于发现,不同业务之间调用,进项使用REST进行内部链接,不需重复写实现逻辑。服务的拆分本着粗粒度的原则,尽量避免分布式事务。
技术选型
jdk
本次选择java版本为1.8
spring-cloud
本次使用的spring-cloud版本为Camden.SR4
服务编写指南
项目结构初始化将完成基本配置的构建(开发人员了解相关知识即可,后续有时间再深入研究)
- 配置服务器(configserver)
- 服务注册器(discovery)
- 熔断UI(monitor-dashboard)
- 授权服务器 (auth-service)
- 路由 (gateway)
- mysql数据库 (mysql)
- mongodb数据库(mongodb)
服务的创建过程
yun-cloud 项目属于maven项目。通过spring-init 创建一个spring-cloud 的pom 项目, 该项目作为根pom。微服务通过new->module->maven-next->service-name 进行maven子项目的构建,创建完成之后src和配置文件都没有,我们需要修改pom文件,完善pom信息,创建SpringApplication程序入口,创建bootstrap.yml 进行服务器配置文件的加载。步骤如图所示:
- 完善pom信息
- 创建程序入口
- 创建 bootstrap.yml
- 创建 服务.yml 比如 user-service.yml 放置到配置服务器(config)中。
tips: bootstrap.yml 为配置文件引导程序,application为应用配置,我们更倾向于把应用相关的配置到application.yml中,比如mysql 等。
配置服务器的连接
配置服务器有两种连接方式:
- 通过指定配置进行连接
spring:
application:
name: statistics-service
cloud:
config:
uri: http://config:8888
- 通过eureka自动进行配置发现(自动发现的前提是config-server的ServerID = configserver,默认的配置服务器如果不填写application.name 默认为configserver)
spring:
cloud:
config:
discovery:
enabled: true
开发人员可以根据自己需求任选一种方式进行连接。
注册发现的配置
Eureka服务器已经启动,我们需要配置注册到服务器,并开启注册发现。
- 引入依赖 (默认我们在根pom中已经全局引入了starter-eureka)
- 通过注解进行注册和发现 ,注解有两种方式,一种是 @EnableDiscoveryClient ,另一种是@EnableEurekaClient ,两者的区别是,discoveryClient实现了许多的服务发现方法,比如使用eureka, consul, zookeeper ,而eurekaClient则是netflix提供的。两者选择一个即可。
- 修改配置bootstrap.yml ,指定注册服务器
eureka:
client:
serviceUrl:
defaultZone: http://discovery:8761/eureka/
hystrix-dashboard 的配置
目前,我们将不配置hystrix-dashboard。后期我们通过引入依赖和稍许修改配置文件就可以实现该功能。详细配置可以参考官方教程或者是提供的sample样例。
授权服务器的配置和使用
待更新。。。
路由配置
我们使用Zuul 作为代理路由服务。
- Zuul 为唯一对外暴露的端口.
mysql
mysql 我们使用tutumcloud/mysql 提供的镜像文件进行构建.可以cd 到mysql 执行docker build -t lvshangke/mysql .
进行手动镜像的构建,也可以通过compose.yml 指定build build ./mysql
,之后可以通过docker命令编译镜像,也可以通过docker-compose build 进行编译。
mongodb
mongodb 我们使用tutumcloud/mongodb 提供的镜像进行构建,使用版本3.2,具体用法跟mysql类似,可以参考文档进行配置也可以参考demo进行使用。
mybatis
我们在拆分微服务的时候,首先考虑的问题是服务的颗粒度,设计方向是,尽量的粗粒度的设计,避免事务的集群访问,关于涉及到事务的跨主机问题如果无法避免,则使用JMS实现消息队列的处理方案.
- 服务的粒度直接影响到了服务之间调用复杂度
- 我们习惯使用jpa处理表关系,之后就进行快速的开发迭代.但是在spring-cloud中,我们并不会首选使用jpa,考虑到JPA涉及到表关系处理,A业务处理用户基本CURD,B业务中处理用户银行卡的CURD,如果使用JPA,则A,B表具有重复的表关系配置,User,Bank.因为1个用户有多个银行卡.这样的业务逻辑设计就过分的细粒度.然而一旦使用JPA,无法避免的即使相互依赖,导致上述说的重复工作,所以我们建议不是必须使用,则首先考虑使用mybatis.
- 在开发中,我们维护一套JPA,主要用户核心业务和数据库结构初始化工作(也可以不用)
- 开发人员编写的小业务,复杂查询等,建议使用mybatis来完成数据库操作
参考资料: mybatis,mybatis-spring-boot-autoconfigure
mybatis 依赖
我们选择1.2.0版本.
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.2.0</version>
</dependency>
mybatis 配置
mybatis支持注解的配置方式,对于简单的服务可以使用该方式,但是我们更倾向于使用xml配置的方式,因为自由度更高,对于一些复杂查询,可以更加方便使用.两种方式都做一些介绍
tips: mybatis
feign
JMS
待更新。。。
docker
我们使用docker作为服务的测试和生产环境.
docker 开发环境
docker 作为开发环境的要求以及常见问题:
- 所有的微服务创建 src/main/docker/Dockerfile 文件
- 所有的微服务中pom 中需要增加maven-docker-plugin插件,并进行统一的配置
- 项目根目录创建docker-compose.yml 文件,进行服务编写.
tips: 我们通过links 进行容器host关联,不过,由于容器启动顺序问题,导致有些服务器还需要重新启动才可以,通过使用version 2 中的depends_on 语法替代links, 虽然容器在初始化网络中可以按照顺序,不过,微服务中很多需要等待配置服务器启动之后才可以调用配置.容器起来了,应用还没有起来.通过compose.yml 启动以后,建议使用手动重新启动下,必须保证配置服务器在注册服务器上已经注册服务. 官方参考中给予了关于启动顺序的解决方案,depends_on,可以使用wait-for-it,因为我们基于服务自动发现,所以导致无法正确的监听,在上述服务器配置中,我们通过主动声明的方式显式的指定配置服务器,之后就可以通过wait-for-it command: ["./wait-for-it.sh","configserver:8888","-t","0","--strict","--","java","-jar","/app/user-service-1.0.jar"]
启动监控,那么,当配置服务器启动之后将会触发微服务进行启动.在开发的过程中可以降低服务发现的时间,默认的每次心跳为30',我们可以修改eureka.instance.lease-renewal-interval-in-seconds: 1
进行时间压缩,生产环境将还原该配置,使用系统默认的心跳时间
开发常用操作
- 我们通过使用命令进行快速的编译,通过使用 mvn clean install 进行镜像的快速构建
- 镜像构建以后,我们通过docker-compose up -d进行启动
- 常用的compose 一般有,stop.start,rm,up.
- 对于局部改动的镜像,我们可以通过intellij maven工具进行可视化操作.
- 开发中我们经常使用UI工具作为快速查看容器日志,CURD等操作.
docker 生产环境
调试环境
我们在开发微服务时候,将频繁的修改config,假如频繁的重新打包镜像,发布服务,这样下来无形中就耽误了很多时间,
- 本地安装docker 环境
- 项目初始化 ,执行
mvn clean install
docker-compose up -d
,初始化环境包括,配置服务器,服务发现,路由,数据库等. - 创建自己的微服务模块, 运行Application.因为所有的docker服务编排已经提供了基本的服务,并且对外提供了端口,默认开发环境中都映射到localhost,比如数据库等等. 我们在开发测试过程中,可以直接运行自己的微服务,查看服务是否有问题,及时进行修改.
- 编写application.yml 放置在resources中,因为开发阶段需要频繁的修改该文件,一般情况下这个文件在配置服务器中,难免会造成时间的浪费,我们通过手动指定,待程序测试通过后,把该文件重命名为服务.yml,放置到config中.
- 开发测试没有问题之后,把配置文件放置在配置仓库,docker-compose.yml 中进行自己开发服务模块的编写.
- 最后提交到git.
tips:在开发测试阶段,尽量的压缩 镜像构建的时间,避免其他服务的问题导致开发时间的延长,所以,建议大家编写服务前首先更新git,然后运行一次镜像,发布服务, 进行自己服务的开发工作, 对于原有服务的更新,我们建议注释服务编排中的该服务,本地启动待修改的程序进行开发测试,完成之后再进行修改以及代码提交.(对于服务依赖,我们也可以本地启动多个服务进行调试),通过本地运行可以避免构建,依赖造成的时间浪费.
安全机制
我们使用oauth2.0作为授权机制来完成集群的授权处理.具体的配置如下:
- 授权服务器
1.1 引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
1.2 Application 中使用@EnableGlobalMethodSecurity(prePostEnabled = true)
开启全局的方法鉴权,比如使用@PreAuthorize
1.3 编写授权服务器的安全验证配置
@Configuration
@EnableResourceServer
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().anyRequest().authenticated()
.and()
.csrf().disable()
.httpBasic().disable().anonymous().disable();
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/info");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("123456").roles("USER").and()
.withUser("admin").password("123456").roles("USER","ADMIN");
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
安全配置规则默认为所有的连接都需要授权.这里使用默认的方式采用内存存储token,初始化测试用户的时候也是通过配置, 我们在开发中通过注入service,实现自定义鉴权处理,主要涉及到的有 UserDetailsService,UserDetails.还需要初始化一个authenticationManagerBean 注意这个 @Bean
1.4 编写oauth2 配置
@Configuration
@EnableAuthorizationServer
public class OAuthConfiguration extends AuthorizationServerConfigurerAdapter {
private TokenStore tokenStore = new InMemoryTokenStore();
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("mobile")
.authorizedGrantTypes("refresh_token", "password")
.scopes("api")
.and()
.withClient("user-service")
.secret("123456")
.authorizedGrantTypes("client_credentials", "refresh_token")
.scopes("server");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore)
.authenticationManager(authenticationManager);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
}
oauth2的授权,需要有客户端链接完成请求, 这里分配了多个客户端链接,默认我们使用mobile作为移动端api的调用请求(外部), 其他客户端作为 服务之间进行鉴权的链接(内部). 外部的鉴权方式,我们采用password方式,方便api调用,内部采用client_credentials的方式进行鉴权. token我们暂时使用内存token,后续再进行redis的迁移. 这里我们需要忽略的链接可以配置到config(WebSecurity web)中.
1.5 配置文件的修改
server:
context-path: /uaa
默认所有的内部微服务都有context-path 假如外部方位 /uaa/test 通过zuul 代理之后,就到了 service/uaa/test.在进行spring-security开发的过程中,我们一般需要开启日志
logging:
level:
org.springframework.cloud: DEBUG
org.springframework.security: DEBUG
还有在开发过程中,一旦开启了DEBUG,需要控制下eureka心跳时间,要不然日志特别多,不好定位问题.
1.6 配置zuul 代理
因为文档上面说application 加上@EnableOAuth2Sso @EnableZuulProxy 就可以开启token转发,我们就按要求配置下, 实际情况是 好像没什么用.
作为对外的zuul ,我们这里可以按照上面1.3的方式给他单独配置一个安全过滤器,不过作为对外提供的端口,我们这里不做安全控制, 所有的校验机制交给鉴权服务器去处理.
routes:
auth-service:
path: /uaa/**
url: http://localhost:8084
account-service:
path: /user/**
serviceId: user-service
我们定义一个转发规则. zuul这里我们就配置spring-security了,我们开放这个微服务, 允许访问的连接进行代理配置即可
1.7 微服务编写
微服务相当于一个小应用, 这里可以配置微服务自己的安全机制,参考上面的1.3.进行自己服务的连接处理,可以忽略一些连接,也可以验证一些连接.我们先看配置,具体的流程下面再整理
我们通过
@SpringBootApplication
@EnableOAuth2Client
@EnableEurekaClient
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class UserApplication {
public static void main(String [] args){
SpringApplication.run(UserApplication.class, args);
}
我们按照上面的配置方法,开启全局方法验证,开启Oauth2Client. 之后编写自己服务的安全控制.
@Configuration
@EnableResourceServer
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().anyRequest().authenticated()
.and()
.csrf().disable()
.httpBasic().disable();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
这里测试使用全部需要授权才能访问. 假如用户已经获取到token, 访问该资源,该资源又要去鉴权服务器查看是否正确. 默认我们内部的访问为"server"通过客户端鉴权,查看token的合法性. 所以这里需要注入几个bean
@Bean
@ConfigurationProperties(prefix = "security.oauth2.client")
public ClientCredentialsResourceDetails clientCredentialsResourceDetails() {
return new ClientCredentialsResourceDetails();
}
@Bean
public RequestInterceptor oauth2FeignRequestInterceptor(){
return new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(), clientCredentialsResourceDetails());
}
@Bean
public OAuth2RestTemplate clientCredentialsRestTemplate() {
return new OAuth2RestTemplate(clientCredentialsResourceDetails());
}
通过@Configuration进行注入.
1.8 微服务的配置
security:
oauth2:
client:
clientId: user-service
clientSecret: 123456
accessTokenUri: http://localhost:8084/uaa/oauth/token
grant-type: client_credentials
scope: server
resource:
user-info-uri: http://localhost:8084/uaa/me
注意user-info-uri,当用户实际已经获取token,则访问该资源,该资源会去查看是否已经被鉴权服务器认证, 我们在auth-service 中需要创建一个controller
@RequestMapping("/me")
public Principal getCurrentLoggedInUser(Principal user) {
return user;
}
1.9 使用
我们开放了zuul作为对外暴露的端口, 当用户通过zuul访问资源时候,每个资源都属于一个微服务,每个微服务都是一个spring项目,都有自己的一套验证机制,是否需要鉴权呀? 如果需要, 则用户没有权利就收到反馈结果, 用户需要申请一个token
申请token: localhost:8080/uaa/oauth/token
请求类型: POST
参数: username ,password, grant_type, scope(选填)
请求头: Authorization: Basic bW9iaWxlOg== (这里的Basic 后面跟的是mobile:的base64编码方式,代表请求的客户端为mobile,密码为空)
用户一旦申请了token 在遇到一些需要授权的连接的时候就可以携带该token
请求: localhost/user/
类型: GET
请求头: Authorization: Bearer $token
我们可以通过log日志查看整体的流程, 通过token访问zuul, zuul转发到对应的微服务,是否有安全配置, 一层层过滤器下来,用户通过userinfouri访问鉴权服务器,(携带client: user-service,password:,scope:) ,鉴权服务器查看client是否合法,完成鉴权.