查看exphp
數據傳遞示意圖
框架構建
不上傳.mvn文件
此處去掉
添加backend_common模塊後
parent項目添加以下
backend_common添加以下
backend_common的配置pom文件添加以下東西
這是爲了打包後能到resources下的xml的配置文件(好比Mapper文件)
要安裝mybatis helper以及generateallsetter這兩款插件
前者能夠提示mapper中的方法對應xml文件的方法
後者能夠alt+enter自動提示生成set代碼語句html
lombok用法示例
cleanup自動生成try catch自動關閉流java
父級包必定要按照pom打包
mysql
consumer消費示例
restTemplate(http請求工具)
eurekaClient
linux
eureka的面試點
沒有一個同時達到C A P
AP好比redis,它的一致性就不是很強
CA 數據庫場景和分佈式數據庫場景
CP 好比mysql數據庫
zookeeper好比在某個節點獲取數據的時候,在操做結束以前,你都不可能在其餘節點獲取改數據
eureka保證的是可用性,客戶端從服務端註冊表中拉去信息的時候有30s的延遲nginx
影片相關的表結構
web
在沒有使用Feign的狀況下調用film模塊的信息
`
// 播放廳對應的影片數據, 影片冗餘數據, 緩存裏有一份
private MoocHallFilmInfoT describeFilmInfo(String filmId) throws CommonServiceException{面試
// GET REGISTER ServiceInstance choose = eurekaClient.choose("film-service"); // 組織調用參數 String hostname = choose.getHost(); int port = choose.getPort(); String uri = "/films/"+filmId; String url = "http://"+hostname+":"+port + uri; // 經過restTemplate調用影片服務 JSONObject baseResponseVO = restTemplate.getForObject(url, JSONObject.class); // 解析返回值 JSONObject dataJson = baseResponseVO.getJSONObject("data"); // 組織參數 MoocHallFilmInfoT hallFilmInfo = new MoocHallFilmInfoT();
// "filmId":"1",
// "filmName":"我不是藥神",
// "filmLength":"132",
// "filmCats":"喜劇,劇情",
// "actors":"程勇,曹斌,呂受益,劉思慧",
// "imgAddress":"films/238e2dc36beae55a71cabfc14069fe78236351.jpg",redis
hallFilmInfo.setFilmId(dataJson.getIntValue("filmId")); hallFilmInfo.setFilmName(dataJson.getString("filmName")); hallFilmInfo.setFilmLength(dataJson.getString("filmLength")); hallFilmInfo.setFilmCats(dataJson.getString("filmCats")); hallFilmInfo.setActors(dataJson.getString("actors")); hallFilmInfo.setImgAddress(dataJson.getString("imgAddress")); return hallFilmInfo;
}
`算法
建立多環境
啓動Eureka和三個provider
整合Eureka和ribbon
例子中provide爲三個節點
第一種方式
第二種方式
自定義負載規則
IRule的源碼
自定義IRule使服務掛掉
IPing
Hystrix屬於高可用的功能
不能幫助實現任何業務
官方的架構圖
思惟導圖
建立測試工程
構建CommandTest
`
@Test public void executeTest(){ long beginTime = System.currentTimeMillis(); CommandDemo commandDemo = new CommandDemo("execute"); // 同步執行Command String result = commandDemo.execute(); long endTime = System.currentTimeMillis(); System.out.println("result="+result+" , speeding="+(endTime-beginTime)); }
`
構建CommandDemo
package com.imooc.hystrix.show.command; import com.netflix.hystrix.*; import lombok.Data; /** * @author : jiangzh * @program : com.imooc.hystrix.show.command * @description : **/ @Data public class CommandDemo extends HystrixCommand<String> { private String name; public CommandDemo(String name){ super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("CommandHelloWorld") ); this.name = name; } /** * @Description: * @Param: * @return: java.lang.String * @Author: jiangzh */ // 單次請求調用的業務方法 @Override protected String run() throws Exception { String result = "CommandHelloWorld name : "+ name; System.err.println(result+" , currentThread-"+Thread.currentThread().getName()); return result; } }
Hystrix若是command直接執行run方法的話,則直接進入第6步
構建CommandTest
@Test public void queueTest() throws ExecutionException, InterruptedException { long beginTime = System.currentTimeMillis(); CommandDemo commandDemo = new CommandDemo("queue"); Future<String> queue = commandDemo.queue(); long endTime = System.currentTimeMillis(); System.out.println("future end , speeding="+(endTime-beginTime)); long endTime2 = System.currentTimeMillis(); System.out.println("result="+queue.get()+" , speeding="+(endTime2-beginTime)); }
構建CommandDemo
package com.imooc.hystrix.show.command; import com.netflix.hystrix.*; import lombok.Data; /** * @author : jiangzh * @program : com.imooc.hystrix.show.command * @description : **/ @Data public class CommandDemo extends HystrixCommand<String> { private String name; public CommandDemo(String name){ super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("CommandHelloWorld") ); this.name = name; } /** * @Description: * @Param: * @return: java.lang.String * @Author: jiangzh */ // 單次請求調用的業務方法 @Override protected String run() throws Exception { String result = "CommandHelloWorld name : "+ name; Thread.sleep(800l); System.err.println(result+" , currentThread-"+Thread.currentThread().getName()); return result; } }
//非阻塞式調用 必須有耗時的操做,不然主進程退出後,天然也退出了
@Test public void observeTest(){ long beginTime = System.currentTimeMillis(); CommandDemo commandDemo = new CommandDemo("observe"); Observable<String> observe = commandDemo.observe(); // 阻塞式調用 String result = observe.toBlocking().single(); long endTime = System.currentTimeMillis(); System.out.println("result="+result+" , speeding="+(endTime-beginTime)); // 非阻塞式調用 observe.subscribe(new Subscriber<String>() { @Override public void onCompleted() { System.err.println("observe , onCompleted"); } @Override public void onError(Throwable throwable) { System.err.println("observe , onError - throwable="+throwable); } @Override public void onNext(String result) { long endTime = System.currentTimeMillis(); System.err.println("observe , onNext result="+result+" speend:"+(endTime - beginTime)); } }); }
1.必須實例化兩個CommandDemo
2.若想非阻塞的狀況執行,必須sleep
@Test public void toObserveTest() throws InterruptedException { long beginTime = System.currentTimeMillis(); CommandDemo commandDemo1 = new CommandDemo("toObservable1"); Observable<String> toObservable1 = commandDemo1.toObservable(); // 阻塞式調用 String result = toObservable1.toBlocking().single(); long endTime = System.currentTimeMillis(); System.out.println("result="+result+" , speeding="+(endTime-beginTime)); CommandDemo commandDemo2 = new CommandDemo("toObservable2"); Observable<String> toObservable2 = commandDemo2.toObservable(); // 非阻塞式調用 toObservable2.subscribe(new Subscriber<String>() { @Override public void onCompleted() { System.err.println("toObservable , onCompleted"); } @Override public void onError(Throwable throwable) { System.err.println("toObservable , onError - throwable="+throwable); } @Override public void onNext(String result) { long endTime = System.currentTimeMillis(); System.err.println("toObservable , onNext result="+result+" speend:"+(endTime - beginTime)); } }); Thread.sleep(2000l); }
observe是先執行command的run方法返回res,而後執行加載Subscriber
ToObserver是先加載Subscriber後執行command的run方法,因此在第二個例子中,若是不進行休眠的話,OnNext中是打印不出來結果的
現實用的最多的是Command queue
/** * @author : jiangzh * @program : com.imooc.hystrix.show.command * @description : **/ @Data public class ObserveCommandDemo extends HystrixObservableCommand<String> { private String name; public ObserveCommandDemo(String name){ super(Setter .withGroupKey(HystrixCommandGroupKey.Factory.asKey("ObserveCommandDemo")) .andCommandKey(HystrixCommandKey.Factory.asKey("ObserveCommandKey"))); this.name = name; } @Override protected Observable<String> construct() { System.err.println("current Thread: "+Thread.currentThread().getName()); return Observable.create(new Observable.OnSubscribe<String>() { @Override public void call(Subscriber<? super String> subscriber) { // 業務處理 subscriber.onNext("action 1 , name="+name); subscriber.onNext("action 2 , name="+name); subscriber.onNext("action 3 , name="+name); // 業務處理結束 subscriber.onCompleted(); } }).subscribeOn(Schedulers.io()); } }
/** * @author : jiangzh * @program : com.imooc.hystrix.show.command * @description : **/ public class ObserveCommandTest { @Test public void observeTest() throws InterruptedException { long beginTime = System.currentTimeMillis(); ObserveCommandDemo commandDemo = new ObserveCommandDemo("ObserveCommandTest-observe"); Observable<String> observe = commandDemo.observe(); // 阻塞式調用 // String result = observe.toBlocking().single(); // // long endTime = System.currentTimeMillis(); // System.out.println("result="+result+" , speeding="+(endTime-beginTime)); // 非阻塞式調用 observe.subscribe(new Subscriber<String>() { @Override public void onCompleted() { System.err.println("ObserveCommandTest-observe , onCompleted"); } @Override public void onError(Throwable throwable) { System.err.println("ObserveCommandTest-observe , onError - throwable="+throwable); } @Override public void onNext(String result) { long endTime = System.currentTimeMillis(); System.err.println("ObserveCommandTest-observe , onNext result="+result+" speend:"+(endTime - beginTime)); } }); Thread.sleep(1000l); } }
Command不是主線程執行的,ObservableCommand使用的是主線程
package com.imooc.hystrix.show.command; import com.netflix.hystrix.*; import org.assertj.core.util.Lists; import java.util.Collection; import java.util.Iterator; import java.util.List; /** * @author : jiangzh * @program : com.imooc.hystrix.show.command * @description : 請求合併處理對象 **/ public class CommandCollapser extends HystrixCollapser<List<String>, String , Integer> { private Integer id; public CommandCollapser(Integer id){ super(Setter .withCollapserKey(HystrixCollapserKey.Factory.asKey("CommandCollapser")) .andCollapserPropertiesDefaults( HystrixCollapserProperties.defaultSetter() .withTimerDelayInMilliseconds(1000) ) ); this.id = id; } /** * @Description: 獲取請求參數 * @Param: [] * @return: java.lang.Integer * @Author: jiangzh */ @Override public Integer getRequestArgument() { return id; } /** * @Description: 批量業務處理 * @Param: [collection] * @return: com.netflix.hystrix.HystrixCommand<java.util.List<java.lang.String>> * @Author: jiangzh */ @Override protected HystrixCommand<List<String>> createCommand(Collection<CollapsedRequest<String, Integer>> collection) { return new BatchCommand(collection); } /** * @Description: 批量處理結果與請求業務之間映射關係處理 * @Param: [strings, collection] * @return: void * @Author: jiangzh */ @Override protected void mapResponseToRequests(List<String> strings, Collection<CollapsedRequest<String, Integer>> collection) { int counts = 0; Iterator<HystrixCollapser.CollapsedRequest<String, Integer>> iterator = collection.iterator(); while (iterator.hasNext()) { HystrixCollapser.CollapsedRequest<String, Integer> response = iterator.next(); String result = strings.get(counts++); response.setResponse(result); } } } class BatchCommand extends HystrixCommand<List<String>>{ private Collection<HystrixCollapser.CollapsedRequest<String, Integer>> collection; public BatchCommand(Collection<HystrixCollapser.CollapsedRequest<String, Integer>> collection){ super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("BatchCommand"))); this.collection = collection; } @Override protected List<String> run() throws Exception { System.err.println("currentThread : "+Thread.currentThread().getName()); List<String> result = Lists.newArrayList(); Iterator<HystrixCollapser.CollapsedRequest<String, Integer>> iterator = collection.iterator(); while (iterator.hasNext()) { HystrixCollapser.CollapsedRequest<String, Integer> request = iterator.next(); Integer reqParam = request.getArgument(); // 具體業務邏輯 result.add("Mooc req: "+ reqParam); } return result; } }
public class CollapserUnit { @Test public void collapserTest() throws ExecutionException, InterruptedException { HystrixRequestContext context = HystrixRequestContext.initializeContext(); // 構建請求 -> 主要優化點,多個服務調用的屢次HTTP請求合併 // 缺點:不多有機會對同一個服務進行屢次HTTP調用,同時還要足夠的"近" CommandCollapser c1 = new CommandCollapser(1); CommandCollapser c2 = new CommandCollapser(2); CommandCollapser c3 = new CommandCollapser(3); CommandCollapser c4 = new CommandCollapser(4); // 獲取結果, 足夠的近 -> 10ms Future<String> q1 = c1.queue(); Future<String> q2 = c2.queue(); Future<String> q3 = c3.queue(); Future<String> q4 = c4.queue(); String r1 = q1.get(); String r2 = q2.get(); String r3 = q3.get(); String r4 = q4.get(); // 打印 System.err.println(r1+" , "+r2+" , "+r3+" , "+r4); context.close(); } }
由於足夠近因此合併爲兩次請求
增長請求間隔
設置不超過一秒的合併
命名
信號量隔離就是一個排隊的過程,能夠理解爲限流
此時程序不會另起線程,而是在主線程中執行
HystrixBadRequestException觸發的異常
最重要的一點
2000rps 100臺機器,平均20個rps每臺。加上一部分冗餘的值(0.3+0.8倍)
隊列長度設置成線程池長度的0.5-1倍
HystrixCommand默認線程隔離
HystrixObservableCommand默認信號量隔離,同時以此能夠執行多個命令
檢查有沒有緩存,請求合併,必定開啓Hystrix上線文,請求足夠的近
檢查斷路器是否開啓,開啓的話直接fallback
不然檢查信號量和線程池
執行run和construct 失敗的走fallback
有個特殊的狀況是若是拋出的HystrixBadRequestException,不會觸發fallback而直接拋出異常
若是執行成功,超時也會fallback
若是fallback成功的話會返回,失敗的話拋出異常
1 添加依賴 啓動文件開啓器註解
2 consumer中建立接口api
3 comsumer中建立控制器方法
在服務提供方providerController
consumer providerApi
consume controller
注意事項
在provider接口中@RequestParam必定要加上
url進行測試
經過動態代理的方式生成實現類
//若是接口指定實現類,則接口primary爲false,實現類加註解Primary
Feign只要name改爲服務名,則自動接入ribbon
Feign.Hystrix.enabled = true 整合Hystrix
方式1 實現降級
方式2 實現feign.hystrix.FallbackFactory工廠的方式
能提升QPS
不要採用多繼承
其餘的服務模塊依賴該api模塊
package com.mooc.meetingfilm.apis.film; import com.mooc.meetingfilm.apis.film.vo.DescribeFilmRespVO; import com.mooc.meetingfilm.utils.common.vo.BaseResponseVO; import com.mooc.meetingfilm.utils.exception.CommonServiceException; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; /** * @author : jiangzh * @program : com.mooc.meetingfilm.apis.film * @description : Film提供的公共接口服務 **/ public interface FilmFeignApis { /** * @Description: 對外暴露的接口服務 * @Param: [filmId] * @return: com.mooc.meetingfilm.utils.common.vo.BaseResponseVO * @Author: jiangzh */ @RequestMapping(value = "/films/{filmId}", method = RequestMethod.GET) BaseResponseVO<DescribeFilmRespVO> describeFilmById(@PathVariable("filmId") String filmId) throws CommonServiceException; }
package com.mooc.meetingfilm.apis.film.vo; import lombok.Data; /** * @author : jiangzh * @program : com.mooc.meetingfilm.film.controller.vo * @description : 根據主鍵獲取影片信息對象 **/ @Data public class DescribeFilmRespVO { private String filmId; private String filmName; private String filmLength; private String filmCats; private String actors; private String imgAddress; private String subAddress; }
package com.mooc.meetingfilm.hall.apis; import com.mooc.meetingfilm.apis.film.FilmFeignApis; import org.springframework.cloud.openfeign.FeignClient; /** * @author : jiangzh * @program : com.mooc.meetingfilm.hall.apis * @description : film提供的接口服務 **/ @FeignClient(name = "film-service") public interface FilmFeignApi extends FilmFeignApis { }
package com.mooc.meetingfilm.hall; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.ComponentScan; @ComponentScan(basePackages = {"com.mooc.meetingfilm"}) @MapperScan(basePackages = {"com.mooc.meetingfilm.hall.dao"}) @EnableFeignClients @EnableDiscoveryClient @SpringBootApplication public class BackendHallApplication { public static void main(String[] args) { SpringApplication.run(BackendHallApplication.class, args); } }
@Resource private FilmFeignApi filmFeignApi; BaseResponseVO<DescribeFilmRespVO> baseResponseVO = filmFeignApi.describeFilmById(filmId); DescribeFilmRespVO filmResult = baseResponseVO.getData(); if(filmResult ==null || ToolUtils.strIsNull(filmResult.getFilmId())){ throw new CommonServiceException(404,"抱歉,未能找到對應的電影信息,filmId : "+filmId); }
配置路由
不由止的狀況下能夠經過服務名代替配置的路徑
或者省略其中的橫槓
ZUUL 典型的servlet加filter作的
阻塞式線程佔用資源多,
一個請求進來,一個線程處理,阻塞式 併發量沒有高
ZUUL2 NIO模型
在zuul中加入JWTFilter驗證token
package com.mooc.meetingfilm.apigwzuul.filters; import com.alibaba.fastjson.JSONObject; import com.mooc.meetingfilm.utils.common.vo.BaseResponseVO; import com.mooc.meetingfilm.utils.properties.JwtProperties; import com.mooc.meetingfilm.utils.util.JwtTokenUtil; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import io.jsonwebtoken.JwtException; import lombok.extern.slf4j.Slf4j; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Enumeration; /** * @author : jiangzh * @program : com.mooc.meetingfilm.apigwzuul.filters * @description : **/ @Slf4j public class JWTFilter extends ZuulFilter { /** * @Description: Filter類型 * @Param: [] * @return: java.lang.String * @Author: jiangzh */ @Override public String filterType() { return "pre"; } /** * @Description: filter的執行順序 * @Param: [] * @return: int * @Author: jiangzh */ @Override public int filterOrder() { return 0; } /** * @Description: 是否要攔截 * @Param: [] * @return: boolean * @Author: jiangzh */ @Override public boolean shouldFilter() { return true; } /** * @Description: Filter的具體業務邏輯 * @Param: [] * @return: java.lang.Object * @Author: jiangzh */ @Override public Object run() throws ZuulException { // JWT工具類 JwtTokenUtil jwtTokenUtil = new JwtTokenUtil(); JwtProperties jwtProperties = JwtProperties.getJwtProperties(); // ThreadLocal RequestContext ctx = RequestContext.getCurrentContext(); // 獲取當前請求和返回值 HttpServletRequest request = ctx.getRequest(); HttpServletResponse response = ctx.getResponse(); // 提早設置請求繼續,若是失敗則修改此內容 ctx.setSendZuulResponse(true); ctx.setResponseStatusCode(200); // 判斷是不是登錄,若是是登錄則不驗證JWT if (request.getServletPath().endsWith("/" + jwtProperties.getAuthPath())) { return null; } // 一、驗證Token有效性 -> 用戶是否登陸過 final String requestHeader = request.getHeader(jwtProperties.getHeader()); String authToken = null; // Bearer header.payload.sign if (requestHeader != null && requestHeader.startsWith("Bearer ")) { authToken = requestHeader.substring(7); //驗證token是否過時,包含了驗證jwt是否正確 try { boolean flag = jwtTokenUtil.isTokenExpired(authToken); if (flag) { renderJson(ctx , response, BaseResponseVO.noLogin()); }else{ // 二、解析出JWT中的payload -> userid - randomkey String randomkey = jwtTokenUtil.getMd5KeyFromToken(authToken); String userId = jwtTokenUtil.getUsernameFromToken(authToken); // 三、是否須要驗籤,以及驗籤的算法 // 四、判斷userid是否有效 // TODO } } catch (JwtException e) { //有異常就是token解析失敗 renderJson(ctx ,response, BaseResponseVO.noLogin()); } } else { //header沒有帶Bearer字段 renderJson(ctx ,response, BaseResponseVO.noLogin()); } return null; } /** * 渲染json對象 */ public static void renderJson(RequestContext ctx, HttpServletResponse response, Object jsonObject) { // 設置終止請求 response.setHeader("Content-Type", "application/json;charset=UTF-8"); ctx.setSendZuulResponse(false); ctx.setResponseBody(JSONObject.toJSONString(jsonObject)); } }
package com.mooc.meetingfilm.apigwzuul.config; import com.mooc.meetingfilm.apigwzuul.filters.JWTFilter; import com.mooc.meetingfilm.apigwzuul.filters.MyFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author : jiangzh * @program : com.mooc.meetingfilm.apigwzuul.config * @description : **/ @Configuration public class ZuulConfig { @Bean public MyFilter initMyFilter(){ return new MyFilter(); } @Bean public JWTFilter initJWTFilter(){ return new JWTFilter(); } }
Eureka下配置
SpringSecurityConfig配置類 排除CSRF的影響
package com.mooc.meetingfilm.eureka.conf; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; /** * @author : jiangzh * @program : com.mooc.meetingfilm.eureka.conf * @description : SpringSecurity配置 **/ @EnableWebSecurity public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { /** * @Description: 對eureka註冊的URL不進行CSRF防護 * @Param: [http] * @return: void * @Author: jiangzh */ @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().ignoringAntMatchers("/eureka/**"); super.configure(http); } }
解決安全問題 (10:07)
依賴包:
<dependency>
<groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-security</artifactId>
</dependency>
Eureka Server配置:
spring:
security:
user: name: jiangzh password: jiangzh123 roles: SUPERUSER
Eureka URL修改成:
http://jiangzh:jiangzh123@localhost:8761/eureka/
註解test修改
這幾種註解的執行順序
若是有兩個test的執行方法的話
建立可執行的xml文件
建立生成測試報告的模板ExtentTestNGIReporterListener.java
package com.mooc.meetingfilm.testng.films; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.mooc.meetingfilm.testng.common.RestUtils; import com.mooc.meetingfilm.utils.common.vo.BaseResponseVO; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestTemplate; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.List; /** * @author : jiangzh * @program : com.mooc.meetingfilm.testng.films * @description : **/ @Slf4j public class FilmsTest { @Test public void addFilm(){ String url = "http://localhost:8401/films/film:add"; FilmSavedReqVO filmSavedReqVO = new FilmSavedReqVO(); filmSavedReqVO.setFilmStatus("1"); filmSavedReqVO.setFilmName("SpringCloud Jiangzh講的課程"); filmSavedReqVO.setFilmEnName("SpringCloud"); filmSavedReqVO.setMainImgAddress("/imgs/main.jpg"); filmSavedReqVO.setFilmScore("10.0"); filmSavedReqVO.setFilmScorers("123456"); filmSavedReqVO.setPreSaleNum("50000"); filmSavedReqVO.setBoxOffice("90000"); filmSavedReqVO.setFilmTypeId("1"); filmSavedReqVO.setFilmSourceId("1"); filmSavedReqVO.setFilmCatIds("1"); filmSavedReqVO.setAreaId("1"); filmSavedReqVO.setDateId("1"); filmSavedReqVO.setFilmTime("2025-12-11"); filmSavedReqVO.setDirectorId("1"); filmSavedReqVO.setActIds("1,2"); filmSavedReqVO.setRoleNames("管理員,實習"); filmSavedReqVO.setFilmLength("20"); filmSavedReqVO.setBiography("SpringCloud Jiangzh講的課程"); filmSavedReqVO.setFilmImgs("/imgs/1.jpg,/imgs/2.jpg,/imgs/3.jpg,/imgs/4.jpg,/imgs/5.jpg"); RestTemplate restTemplate = RestUtils.getRestTemplate(); ResponseEntity<BaseResponseVO> baseresponse = restTemplate.postForEntity(url, filmSavedReqVO, BaseResponseVO.class); log.info("addFilm baseresponse : {}", baseresponse); // 驗證返回值的Code是否是200 BaseResponseVO body = baseresponse.getBody(); Integer code = new Integer(200); // 第一道攔截 Assert.assertEquals(code, body.getCode()); } //dataProvider有幾回,下邊測試方法就會執行幾回,而且將數據經過該測試方法當作參數傳入 @Test(dataProvider = "filmsDataProvider") public void films(String filmsName, int expectCounts) { String uri = "http://localhost:8401/films"; RestTemplate restTemplate = RestUtils.getRestTemplate(); String response = restTemplate.getForObject(uri, String.class); log.info("response : {}", response); JSONObject result = JSONObject.parseObject(response); // 數量是否大於1 // 名字與插入的內容是否相同 JSONObject data = result.getJSONObject("data"); JSONArray films = data.getJSONArray("films"); // 成功計數器 int count = 0; List<DescribeFilmsRespVO> describeFilmsRespVOS = films.toJavaList(DescribeFilmsRespVO.class); for(DescribeFilmsRespVO vo : describeFilmsRespVOS){ if(vo.getFilmEnName().equals(filmsName)){ count ++ ; } } log.info("count : {}", count); //判斷count和expectCount Assert.assertEquals(count, expectCounts); } //注入動態數據 @DataProvider(name = "filmsDataProvider") public Object[][] filmsDataProvider(){ Object[][] objects = new Object[][]{ {"SpringCloud", 1},//filmName,expectCount~~~~ {"SpringCloud2", 0} }; return objects; } @Data public static class DescribeFilmsRespVO{ private String filmId; private String filmStatus; private String filmName; private String filmEnName; private String filmScore; private String preSaleNum; private String boxOffice; private String filmTime; private String filmLength; private String mainImg; } @Data public static class FilmSavedReqVO{ private String filmStatus; private String filmName; private String filmEnName; private String mainImgAddress; private String filmScore; private String filmScorers; private String preSaleNum; private String boxOffice; private String filmTypeId; private String filmSourceId; private String filmCatIds; private String areaId; private String dateId; private String filmTime; private String directorId; private String actIds; // actIds與RoleNames是否是能在數量上對應上 private String roleNames; private String filmLength; private String biography; private String filmImgs; } }
必定要作的設置改爲linux的環境
設置鏡像加速
這次的基礎鏡像是centos
Dockerfile詳解
#基礎鏡像 FROM centos:7.1.1503 MAINTAINER jiangzheng "coding-jiangzh@qq.com" #定義環境變量 編碼utf8 ENV LANG zh_CN.utf-8 #定義帳戶 USER root ####################################################### #建立文件夾 RUN mkdir -p /home/jiangzh/env /home/jiangzh/workspace /home/jiangzh/bin #複製java並解壓 ADD ./jdk-8u181-linux-x64.tar.gz /home/jiangzh/env/jdk #複製Eureka.jar COPY ./backend-eureka-server.jar /home/jiangzh/workspace/ #複製啓動執行sh COPY ./entrypoint.sh /home/jiangzh/bin/ ####################################################### #定義jdk的環境變量 ENV JAVA_HOME /home/jiangzh/env/jdk/jdk1.8.0_181 #初始化後進入的目錄 WORKDIR /home/jiangzh #對外暴露的端口 EXPOSE 8761 #把JAVA_HOME的配置文件加到path下才能生效 ENV PATH /home/jiangzh:$JAVA_HOME/bin:$PATH #添加.sh的可執行權限 RUN chmod a+x bin/*.sh #在docker啓動的時候執行的命令,表示相對目錄 ENTRYPOINT ["bin/entrypoint.sh"]
entrypoint.sh
#!/bin/sh #定義環境變量 export SHELL_BASE=/home/jiangzh/sbin #進入工做目錄 cd /home/jiangzh/workspace ## start eureka service nohup java -Dfile.encoding="UTF-8" -jar /home/jiangzh/workspace/backend-eureka-server.jar & #死循環防止docker這個進程退出 for (( ; ; )) do sleep 5 done
docker操做必須是root帳戶或者同等權限 #Docker構建須要尋找到dockerfile docker build -t meetingfilm-backend:1.0 . #查看全部的Docker 鏡像列表 docker images #啓動Docker容器 前置準備: meetingfilm-backend:1.0的鏡像 docker run -itd -p 8761:8761 meetingfilm-backend:1.0 #查看已經運行的Docker容器列表 docker ps -a #中止docker容器 docker stop <CONTAINER ID>
dockerfile就是一個文件,構建鏡像前必須有dockerfile
#拉取msyql鏡像 docker pull mysql:5.7 #查看鏡像是否存在 docker images | grep mysql #運行msyql容器 docker run -itd --name jiangzh_mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7 #進入mysql容器 docker exec -it jiangzh_mysql /bin/bash #登陸mysql mysql -uroot -p123456 #修改mysql遠程訪問權限 GRANT ALL ON *.* TO 'root'@'%'; #刷新權限flush privileges #修改加密方式 ALTER USER 'root'@'localhost' IDENTIFIED BY 'password' PASSWORD EXPIRE NEVER; ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '123456'; #再次刷新權限 flush privileges;
sudo docker pull nginx
sudo docker images |grep nginx
sudo mkdir -p /opt/install/nginx/conf
sudo mkdir -p /opt/install/nginx/conf/vhost
sudo mkdir -p /opt/install/nginx/logs
三個目錄隨便寫,主要做用以下:
在/opt/install/nginx/conf目錄中建立nginx.conf,做爲Nginx默認配置文件,大概內容以下:
user root; worker_processes 1; error_log /var/log/nginx/error.log; pid /run/nginx.pid; include /usr/share/nginx/modules/*.conf; events { worker_connections 1024; } http { include /etc/nginx/mime.types; include /etc/nginx/vhost/*.conf; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; server { listen 80; server_name localhost; location / { root html; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } }
建立一個引入配置文件,來測試include是否可用,目錄寫在/opt/install/nginx/conf/vhost
server { listen 80; autoindex on; server_name jiangzh.jd.com; access_log /var/log/nginx/access.log combined; index index.html index.htm index.jsp index.php; if ( $query_string ~* ".*[;'<>].*" ){ return 404; } location / { proxy_pass https://www.jd.com; if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization'; add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain; charset=utf-8'; add_header 'Content-Length' 0; return 204; } if ($request_method = 'POST') { add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization'; add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'; } if ($request_method = 'GET') { add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization'; add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'; } } }
sudo docker run -itd --name jiangzh-nginx -p 80:80 -v /opt/install/nginx/conf/nginx.conf:/etc/nginx/nginx.conf -v /opt/install/nginx/logs:/var/log/nginx -v /opt/install/nginx/conf/vhost:/etc/nginx/vhost nginx
sudo docker ps -a
將本地建立的幾個目錄在啓動時候掛載在Nginx的Docker容器上,達到外部配置文件引入的目標,命令以下:
將文件夾上傳到linux服務器
cd 到console目錄
docker build -t film-console:1.0 .
sh start.sh
docker -exec -it film-console bash