編碼規(guī)范
命名
嚴(yán)格遵守java命名規(guī)范
- 命名采用駝峰式
- 變量名將擁有一定的含義,要通俗易懂螟炫,杜絕使用含糊不清的變量名稱
- 其他參照java官方要求進行編碼
注釋
必須提供注釋說明具體的業(yè)務(wù)邏輯知市。可以自行創(chuàng)建intellij注釋模板戈钢。方法包括涝桅,方法注釋,類注釋峻汉, 方法需要寫明具體的用途以及注意事項贴汪。
微服務(wù)命名
項目中的業(yè)務(wù)服務(wù)需要根據(jù)具體的業(yè)務(wù)類型攜帶service脐往,比如user-service, order-service...
編碼注意事項
所有微服務(wù)將完成服務(wù)注冊于發(fā)現(xiàn),不同業(yè)務(wù)之間調(diào)用扳埂,進項使用REST進行內(nèi)部鏈接业簿,不需重復(fù)寫實現(xiàn)邏輯。服務(wù)的拆分本著粗粒度的原則聂喇,盡量避免分布式事務(wù)辖源。
技術(shù)選型
jdk
本次選擇java版本為1.8
spring-cloud
本次使用的spring-cloud版本為Camden.SR4
服務(wù)編寫指南
項目結(jié)構(gòu)初始化將完成基本配置的構(gòu)建(開發(fā)人員了解相關(guān)知識即可蔚携,后續(xù)有時間再深入研究)
- 配置服務(wù)器(configserver)
- 服務(wù)注冊器(discovery)
- 熔斷UI(monitor-dashboard)
- 授權(quán)服務(wù)器 (auth-service)
- 路由 (gateway)
- mysql數(shù)據(jù)庫 (mysql)
- mongodb數(shù)據(jù)庫(mongodb)
服務(wù)的創(chuàng)建過程
yun-cloud 項目屬于maven項目希太。通過spring-init 創(chuàng)建一個spring-cloud 的pom 項目, 該項目作為根pom酝蜒。微服務(wù)通過new->module->maven-next->service-name 進行maven子項目的構(gòu)建誊辉,創(chuàng)建完成之后src和配置文件都沒有,我們需要修改pom文件亡脑,完善pom信息堕澄,創(chuàng)建SpringApplication程序入口,創(chuàng)建bootstrap.yml 進行服務(wù)器配置文件的加載霉咨。步驟如圖所示:
- 完善pom信息
- 創(chuàng)建程序入口
- 創(chuàng)建 bootstrap.yml
- 創(chuàng)建 服務(wù).yml 比如 user-service.yml 放置到配置服務(wù)器(config)中蛙紫。
tips: bootstrap.yml 為配置文件引導(dǎo)程序,application為應(yīng)用配置途戒,我們更傾向于把應(yīng)用相關(guān)的配置到application.yml中坑傅,比如mysql 等。
配置服務(wù)器的連接
配置服務(wù)器有兩種連接方式:
- 通過指定配置進行連接
spring:
application:
name: statistics-service
cloud:
config:
uri: http://config:8888
- 通過eureka自動進行配置發(fā)現(xiàn)(自動發(fā)現(xiàn)的前提是config-server的ServerID = configserver,默認(rèn)的配置服務(wù)器如果不填寫application.name 默認(rèn)為configserver)
spring:
cloud:
config:
discovery:
enabled: true
開發(fā)人員可以根據(jù)自己需求任選一種方式進行連接喷斋。
注冊發(fā)現(xiàn)的配置
Eureka服務(wù)器已經(jīng)啟動唁毒,我們需要配置注冊到服務(wù)器,并開啟注冊發(fā)現(xiàn)星爪。
- 引入依賴 (默認(rèn)我們在根pom中已經(jīng)全局引入了starter-eureka)
- 通過注解進行注冊和發(fā)現(xiàn) 浆西,注解有兩種方式,一種是 @EnableDiscoveryClient 顽腾,另一種是@EnableEurekaClient ,兩者的區(qū)別是近零,discoveryClient實現(xiàn)了許多的服務(wù)發(fā)現(xiàn)方法,比如使用eureka, consul, zookeeper 抄肖,而eurekaClient則是netflix提供的秒赤。兩者選擇一個即可。
- 修改配置bootstrap.yml 憎瘸,指定注冊服務(wù)器
eureka:
client:
serviceUrl:
defaultZone: http://discovery:8761/eureka/
hystrix-dashboard 的配置
目前入篮,我們將不配置hystrix-dashboard。后期我們通過引入依賴和稍許修改配置文件就可以實現(xiàn)該功能幌甘。詳細(xì)配置可以參考官方教程或者是提供的sample樣例潮售。
授權(quán)服務(wù)器的配置和使用
待更新痊项。。酥诽。
路由配置
我們使用Zuul 作為代理路由服務(wù)鞍泉。
- Zuul 為唯一對外暴露的端口.
mysql
mysql 我們使用tutumcloud/mysql 提供的鏡像文件進行構(gòu)建.可以cd 到mysql 執(zhí)行docker build -t lvshangke/mysql .
進行手動鏡像的構(gòu)建,也可以通過compose.yml 指定build build ./mysql
,之后可以通過docker命令編譯鏡像,也可以通過docker-compose build 進行編譯肮帐。
mongodb
mongodb 我們使用tutumcloud/mongodb 提供的鏡像進行構(gòu)建,使用版本3.2咖驮,具體用法跟mysql類似,可以參考文檔進行配置也可以參考demo進行使用训枢。
mybatis
我們在拆分微服務(wù)的時候,首先考慮的問題是服務(wù)的顆粒度,設(shè)計方向是,盡量的粗粒度的設(shè)計,避免事務(wù)的集群訪問,關(guān)于涉及到事務(wù)的跨主機問題如果無法避免,則使用JMS實現(xiàn)消息隊列的處理方案.
- 服務(wù)的粒度直接影響到了服務(wù)之間調(diào)用復(fù)雜度
- 我們習(xí)慣使用jpa處理表關(guān)系,之后就進行快速的開發(fā)迭代.但是在spring-cloud中,我們并不會首選使用jpa,考慮到JPA涉及到表關(guān)系處理,A業(yè)務(wù)處理用戶基本CURD,B業(yè)務(wù)中處理用戶銀行卡的CURD,如果使用JPA,則A,B表具有重復(fù)的表關(guān)系配置,User,Bank.因為1個用戶有多個銀行卡.這樣的業(yè)務(wù)邏輯設(shè)計就過分的細(xì)粒度.然而一旦使用JPA,無法避免的即使相互依賴,導(dǎo)致上述說的重復(fù)工作,所以我們建議不是必須使用,則首先考慮使用mybatis.
- 在開發(fā)中,我們維護一套JPA,主要用戶核心業(yè)務(wù)和數(shù)據(jù)庫結(jié)構(gòu)初始化工作(也可以不用)
- 開發(fā)人員編寫的小業(yè)務(wù),復(fù)雜查詢等,建議使用mybatis來完成數(shù)據(jù)庫操作
參考資料: 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支持注解的配置方式,對于簡單的服務(wù)可以使用該方式,但是我們更傾向于使用xml配置的方式,因為自由度更高,對于一些復(fù)雜查詢,可以更加方便使用.兩種方式都做一些介紹
tips: mybatis
feign
JMS
待更新托修。。恒界。
docker
我們使用docker作為服務(wù)的測試和生產(chǎn)環(huán)境.
docker 開發(fā)環(huán)境
docker 作為開發(fā)環(huán)境的要求以及常見問題:
- 所有的微服務(wù)創(chuàng)建 src/main/docker/Dockerfile 文件
- 所有的微服務(wù)中pom 中需要增加maven-docker-plugin插件,并進行統(tǒng)一的配置
- 項目根目錄創(chuàng)建docker-compose.yml 文件,進行服務(wù)編寫.
tips: 我們通過links 進行容器host關(guān)聯(lián),不過,由于容器啟動順序問題,導(dǎo)致有些服務(wù)器還需要重新啟動才可以,通過使用version 2 中的depends_on 語法替代links, 雖然容器在初始化網(wǎng)絡(luò)中可以按照順序,不過,微服務(wù)中很多需要等待配置服務(wù)器啟動之后才可以調(diào)用配置.容器起來了,應(yīng)用還沒有起來.通過compose.yml 啟動以后,建議使用手動重新啟動下,必須保證配置服務(wù)器在注冊服務(wù)器上已經(jīng)注冊服務(wù). 官方參考中給予了關(guān)于啟動順序的解決方案,depends_on,可以使用wait-for-it,因為我們基于服務(wù)自動發(fā)現(xiàn),所以導(dǎo)致無法正確的監(jiān)聽,在上述服務(wù)器配置中,我們通過主動聲明的方式顯式的指定配置服務(wù)器,之后就可以通過wait-for-it command: ["./wait-for-it.sh","configserver:8888","-t","0","--strict","--","java","-jar","/app/user-service-1.0.jar"]
啟動監(jiān)控,那么,當(dāng)配置服務(wù)器啟動之后將會觸發(fā)微服務(wù)進行啟動.在開發(fā)的過程中可以降低服務(wù)發(fā)現(xiàn)的時間,默認(rèn)的每次心跳為30',我們可以修改eureka.instance.lease-renewal-interval-in-seconds: 1
進行時間壓縮,生產(chǎn)環(huán)境將還原該配置,使用系統(tǒng)默認(rèn)的心跳時間
開發(fā)常用操作
- 我們通過使用命令進行快速的編譯,通過使用 mvn clean install 進行鏡像的快速構(gòu)建
- 鏡像構(gòu)建以后,我們通過docker-compose up -d進行啟動
- 常用的compose 一般有,stop.start,rm,up.
- 對于局部改動的鏡像,我們可以通過intellij maven工具進行可視化操作.
- 開發(fā)中我們經(jīng)常使用UI工具作為快速查看容器日志,CURD等操作.
docker 生產(chǎn)環(huán)境
調(diào)試環(huán)境
我們在開發(fā)微服務(wù)時候,將頻繁的修改config,假如頻繁的重新打包鏡像,發(fā)布服務(wù),這樣下來無形中就耽誤了很多時間,
- 本地安裝docker 環(huán)境
- 項目初始化 ,執(zhí)行
mvn clean install
docker-compose up -d
,初始化環(huán)境包括,配置服務(wù)器,服務(wù)發(fā)現(xiàn),路由,數(shù)據(jù)庫等. - 創(chuàng)建自己的微服務(wù)模塊, 運行Application.因為所有的docker服務(wù)編排已經(jīng)提供了基本的服務(wù),并且對外提供了端口,默認(rèn)開發(fā)環(huán)境中都映射到localhost,比如數(shù)據(jù)庫等等. 我們在開發(fā)測試過程中,可以直接運行自己的微服務(wù),查看服務(wù)是否有問題,及時進行修改.
- 編寫application.yml 放置在resources中,因為開發(fā)階段需要頻繁的修改該文件,一般情況下這個文件在配置服務(wù)器中,難免會造成時間的浪費,我們通過手動指定,待程序測試通過后,把該文件重命名為服務(wù).yml,放置到config中.
- 開發(fā)測試沒有問題之后,把配置文件放置在配置倉庫,docker-compose.yml 中進行自己開發(fā)服務(wù)模塊的編寫.
- 最后提交到git.
tips:在開發(fā)測試階段,盡量的壓縮 鏡像構(gòu)建的時間,避免其他服務(wù)的問題導(dǎo)致開發(fā)時間的延長,所以,建議大家編寫服務(wù)前首先更新git,然后運行一次鏡像,發(fā)布服務(wù), 進行自己服務(wù)的開發(fā)工作, 對于原有服務(wù)的更新,我們建議注釋服務(wù)編排中的該服務(wù),本地啟動待修改的程序進行開發(fā)測試,完成之后再進行修改以及代碼提交.(對于服務(wù)依賴,我們也可以本地啟動多個服務(wù)進行調(diào)試),通過本地運行可以避免構(gòu)建,依賴造成的時間浪費.
安全機制
我們使用oauth2.0作為授權(quán)機制來完成集群的授權(quán)處理.具體的配置如下:
- 授權(quán)服務(wù)器
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)
開啟全局的方法鑒權(quán),比如使用@PreAuthorize
1.3 編寫授權(quán)服務(wù)器的安全驗證配置
@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();
}
}
安全配置規(guī)則默認(rèn)為所有的連接都需要授權(quán).這里使用默認(rèn)的方式采用內(nèi)存存儲token,初始化測試用戶的時候也是通過配置, 我們在開發(fā)中通過注入service,實現(xiàn)自定義鑒權(quán)處理,主要涉及到的有 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的授權(quán),需要有客戶端鏈接完成請求, 這里分配了多個客戶端鏈接,默認(rèn)我們使用mobile作為移動端api的調(diào)用請求(外部), 其他客戶端作為 服務(wù)之間進行鑒權(quán)的鏈接(內(nèi)部). 外部的鑒權(quán)方式,我們采用password方式,方便api調(diào)用,內(nèi)部采用client_credentials的方式進行鑒權(quán). token我們暫時使用內(nèi)存token,后續(xù)再進行redis的遷移. 這里我們需要忽略的鏈接可以配置到config(WebSecurity web)中.
1.5 配置文件的修改
server:
context-path: /uaa
默認(rèn)所有的內(nèi)部微服務(wù)都有context-path 假如外部方位 /uaa/test 通過zuul 代理之后,就到了 service/uaa/test.在進行spring-security開發(fā)的過程中,我們一般需要開啟日志
logging:
level:
org.springframework.cloud: DEBUG
org.springframework.security: DEBUG
還有在開發(fā)過程中,一旦開啟了DEBUG,需要控制下eureka心跳時間,要不然日志特別多,不好定位問題.
1.6 配置zuul 代理
因為文檔上面說application 加上@EnableOAuth2Sso @EnableZuulProxy 就可以開啟token轉(zhuǎn)發(fā),我們就按要求配置下, 實際情況是 好像沒什么用.
作為對外的zuul ,我們這里可以按照上面1.3的方式給他單獨配置一個安全過濾器,不過作為對外提供的端口,我們這里不做安全控制, 所有的校驗機制交給鑒權(quán)服務(wù)器去處理.
routes:
auth-service:
path: /uaa/**
url: http://localhost:8084
account-service:
path: /user/**
serviceId: user-service
我們定義一個轉(zhuǎn)發(fā)規(guī)則. zuul這里我們就配置spring-security了,我們開放這個微服務(wù), 允許訪問的連接進行代理配置即可
1.7 微服務(wù)編寫
微服務(wù)相當(dāng)于一個小應(yīng)用, 這里可以配置微服務(wù)自己的安全機制,參考上面的1.3.進行自己服務(wù)的連接處理,可以忽略一些連接,也可以驗證一些連接.我們先看配置,具體的流程下面再整理
我們通過
@SpringBootApplication
@EnableOAuth2Client
@EnableEurekaClient
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class UserApplication {
public static void main(String [] args){
SpringApplication.run(UserApplication.class, args);
}
我們按照上面的配置方法,開啟全局方法驗證,開啟Oauth2Client. 之后編寫自己服務(wù)的安全控制.
@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();
}
}
這里測試使用全部需要授權(quán)才能訪問. 假如用戶已經(jīng)獲取到token, 訪問該資源,該資源又要去鑒權(quán)服務(wù)器查看是否正確. 默認(rèn)我們內(nèi)部的訪問為"server"通過客戶端鑒權(quán),查看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 微服務(wù)的配置
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,當(dāng)用戶實際已經(jīng)獲取token,則訪問該資源,該資源會去查看是否已經(jīng)被鑒權(quán)服務(wù)器認(rèn)證, 我們在auth-service 中需要創(chuàng)建一個controller
@RequestMapping("/me")
public Principal getCurrentLoggedInUser(Principal user) {
return user;
}
1.9 使用
我們開放了zuul作為對外暴露的端口, 當(dāng)用戶通過zuul訪問資源時候,每個資源都屬于一個微服務(wù),每個微服務(wù)都是一個spring項目,都有自己的一套驗證機制,是否需要鑒權(quán)呀? 如果需要, 則用戶沒有權(quán)利就收到反饋結(jié)果, 用戶需要申請一個token
申請token: localhost:8080/uaa/oauth/token
請求類型: POST
參數(shù): username ,password, grant_type, scope(選填)
請求頭: Authorization: Basic bW9iaWxlOg== (這里的Basic 后面跟的是mobile:的base64編碼方式,代表請求的客戶端為mobile,密碼為空)
用戶一旦申請了token 在遇到一些需要授權(quán)的連接的時候就可以攜帶該token
請求: localhost/user/
類型: GET
請求頭: Authorization: Bearer $token
我們可以通過log日志查看整體的流程, 通過token訪問zuul, zuul轉(zhuǎn)發(fā)到對應(yīng)的微服務(wù),是否有安全配置, 一層層過濾器下來,用戶通過userinfouri訪問鑒權(quán)服務(wù)器,(攜帶client: user-service,password:,scope:) ,鑒權(quán)服務(wù)器查看client是否合法,完成鑒權(quán).