查看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请求工具)
eurekaClientlinux
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