我总会在这个阶段简短说明下去年的想法。这年基本忙于工作,除了本职工作的技术管理之外,还花了一部分时间在摄影摄像剪辑中,因为工作需要。这年是近几年拍摄日落自然较少的一年。另外还包括室友的离别,工作的调整,居住环境的改变,又养了一只猫叫做十一……
今年独处时总有一个声音在质问我,活着的本身有什么用?既然最终都会离去,为什么要体验如此多的事情?在宏观角度中所做的一文不值。我所做的,之前有人做过,未来也还会有人走向重复的路,当然还包括其他问题。索性我并不讨厌思考,我追问只是因为想明白这些之后依旧做自己。你应该多去了解世界和事物的运作方式,即使每一个抉择都存在人们指指点点,要知道那是他们的事情,这些都不能阻止你做任何你想做的事。
许多人都自以过来人的身份,希望你活成他们内心的模样,但他们不曾想象世界上存在无数种活法,一但那些事是出乎他们的想象,便七嘴八舌蜂拥而至,我能理解,这其中有许多因素导致的。
如果我不曾有想法,那或许我只能听从别人的安排。但我有想法和目标后,是身边的人从未走过的路,我应该坚定不移选择它,尽管一路上都是各种不理解。如果把物质财富当成成功的唯一标准,我大抵是永远不会合格的。就像过年期间饭桌上一聊起天来,基本都是谈论车子房子结婚各种邻里邻居的悲惨故事。而我更想听听人们谈论自己有趣的经历,不要只关注别人的人生。
我认为变化是常态,也许几年后我看这些话,会有新的感受,但是,我知道的,现在此时,我知道做自己不那么轻松,比如留个长发回老家已经无数人可以跟我说道说道,关于结婚同样如此,不会过问你喜欢什么,只要求你选择什么。
我希望我的一生忠于记录,忠于热爱,忠于自己的内心。
我觉得我的一生是需要一点精神财富的,那些喜好能支撑我开心的活着。
“没有谁知道我是谁,没有谁知道我会成为谁,那么我就可以成为任何人。”
]]>规则:系动词后面+not (主系表:是很在)
规则:主语后+don’t/doesn’t(主语后面动作 动词)
规则:there is no 没有
区分 | 表达 |
---|---|
数量=1 | there is |
数量>1 | there are |
主语 | 系动词 |
---|---|
我 | am |
你 | are |
他/她/它 | is |
数量为1 | is |
数量大于1 | are |
- 主语-句子开头的词语 主语后面是动作/动词 主谓宾
主语 | 谓语动词 |
---|---|
我 | 不变 |
你 | 不变 |
他/她/它 | 变 + s |
数量为1 | 变 + s |
数量大于1 | 不变 |
- 前 to 后 ing
nginx -t 检查配置是否正确
nginx -s reload 重新加载配置信息
server { listen 80; server_name www.xxx.com location / { alias /path/;}
server { listen 80; server_name www.xxx.com location / { proxy_pass http://127.0.0.1:8088}
# HTTPSserver { listen 443 ssl; index index.html index.htm index.php; server_name www.xxx.com; ssl_certificate /xxxxxx.pem; ssl_certificate_key /xxxxxx.key; ssl_session_timeout 5m; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; location / { proxy_pass http://127.0.0.1:8088; }}
# ios Swift 通用链接(Universal Links)location / { charset UTF-8; default_type text/html; return 200 '{"applinks":{"apps":[],"details":[{"appID":"xxxxxxxxxx","paths":["*"]}]}}'; }}
#重定向server { listen 80; server_name www.xxx.com; rewrite ^(.*)$ https://$host$1; #将所有HTTP请求通过rewrite指令重定向到HTTPS。 location / { proxy_pass http://127.0.0.1:8088; }}
]]>依赖
<dependency> <groupId>com.belerweb</groupId> <artifactId>pinyin4j</artifactId> <version>2.5.1</version></dependency>
工具类
public class PinYinUtils { /** * 获取汉字对应的拼音 * * @param chinese 汉字串 * @return */ public static String getFullSpell(String chinese) { StringBuffer bf = new StringBuffer(); char[] arr = chinese.toCharArray(); HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat(); defaultFormat.setCaseType(HanyuPinyinCaseType.UPPERCASE); defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE); for (int i = 0; i < arr.length; i++) { if (arr[i] > 128) { try { bf.append(PinyinHelper.toHanyuPinyinStringArray(arr[i], defaultFormat)[0]); } catch (BadHanyuPinyinOutputFormatCombination e) { e.printStackTrace(); } } else { bf.append(arr[i]); } } return bf.toString(); }}
将查询的数据,重新进行处理
/** * 封装根据拼音首字母的数据 * * @param data * @return */ private List<Map<String, Object>> getPinYinData(List<CpPetKind> data) { List<Map<String, Object>> list = new LinkedList<>(); for (int i = 1; i <= 26; i++) { // 大写 String big = String.valueOf((char) (96 + i)).toUpperCase(); // 小写 String little = big.toLowerCase(); List<CpPetKind> initialsList = new LinkedList<>(); for (int j = 0; j < data.size() - 1; j++) { CpPetKind petKind = data.get(j); String initials = PinYinUtils.getFullSpell(petKind.getBreed()).substring(0, 1); if (big.equals(initials) || little.equals(initials)) { initialsList.add(petKind); } } Map<String, Object> tempMap = new HashMap<>(); tempMap.put("initials", big); tempMap.put("kinds", initialsList); list.add(tempMap); } return list; }
]]>参考地址:http://www.fixbbs.com/p/01439272.html
<dependency> <groupId>org.bytedeco</groupId> <artifactId>javacv</artifactId> <version>1.4.3</version></dependency><dependency> <groupId>org.bytedeco.javacpp-presets</groupId> <artifactId>ffmpeg-platform</artifactId> <version>4.0.2-1.4.3</version></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>RELEASE</version></dependency>
@Override public Response upload(MultipartFile file) { if (file == null) { return Response.fail().message("缺少必要参数!"); } //获取文件名加后缀 String fileName = file.getOriginalFilename(); if (fileName == null) { return Response.fail().message("上传异常!"); } //获取文件后缀 String suffix = ""; int index = fileName.lastIndexOf("."); if (index > 0) { suffix = file.getOriginalFilename().substring(index, fileName.length()); } String contentType = file.getContentType(); String resources = ALYConstants.OSS_PUBLIC_PICTURE + "other/"; if (contentType != null) { if (contentType.contains("video")) { resources = ALYConstants.OSS_PUBLIC_PICTURE + "video/"; } else if (contentType.contains("image")) { resources = ALYConstants.OSS_PUBLIC_PICTURE + "image/"; if (StringUtils.isBlank(suffix)) { suffix = ".jpg"; } } } //组合新的文件名 String imgName = DateUtils.dateTimeNow() + "-" + IdUtils.fastSimpleUUID() + suffix; resources = resources + DateUtils.dateTime() + "/"; try { boolean state = this.oss(file.getBytes(), resources + imgName); if (!state) { return Response.fail().message("文件上传失败!"); } } catch (IOException e) { log.error("【oss上传资源异常,异常信息:[{}]】", e.getMessage()); } String url = ALYConstants.OSS_STATIC_MAIN_LINK + resources + imgName; this.insert(file, url, imgName, suffix, contentType); return Response.ok().content(new JSONObject() {{ put("url", url); }}); } @Override public String videoToImage(String url) throws Exception { String targetFilePath = ""; FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(url); grabber.start(); //判断是否是竖屏小视频 String rotate = grabber.getVideoMetadata("rotate"); int lengthInFrames = grabber.getLengthInFrames(); Frame frame; int i = 0; int index = 3;//截取图片第几帧 while (i < lengthInFrames) { frame = grabber.grabImage(); if (i == index) { if (null != rotate && rotate.length() > 1) { targetFilePath = doExecuteFrame(frame, true); //获取缩略图 } else { targetFilePath = doExecuteFrame(frame, false); //获取缩略图 } break; } i++; } grabber.stop(); return targetFilePath; //返回的是视频第N帧 } private void insert(MultipartFile file, String url, String newName, String suffix, String contentType) { String memberId = memberService.infoId(); CpFile cpFile = new CpFile(); cpFile.setId(IdUtils.getSnowFlakeId()); cpFile.setMemberId(memberId); cpFile.setName(file.getOriginalFilename()); cpFile.setNewName(newName); cpFile.setUrl(url); cpFile.setType(contentType); cpFile.setSize(file.getSize()); cpFile.setSuffix(suffix); cpFile.setCreateTime(DateUtils.getNowDate()); this.baseMapper.insert(cpFile); log.info("【文件上传,上传用户:[{}],地址:[{}]】", memberId, url); } private boolean oss(byte[] file, String name) { String oss = ALYConstants.OSS_STATIC_POSITION; String endpoint = ALYConstants.OSS_ENDPOINT; String accessKeyId = ALYConstants.OSS_ACCESS_KEY; String accessKeySecret = ALYConstants.OSS_ACCESS_SECRET; try { //创建OSSClient实例。 OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); //传Byte数组。 ossClient.putObject(oss, name, new ByteArrayInputStream(file)); //关闭OSSClient。 ossClient.shutdown(); return true; } catch (Exception e) { log.error("【文件上传失败,错误信息:[{}]】", e.getMessage()); } return false; } /** * 截取缩略图,存入阿里云OSS(按自己的上传类型自定义转换文件格式) * * @return 图片地址 * @throws Exception */ public String doExecuteFrame(Frame f, boolean direction) throws Exception { if (null == f || null == f.image) { return ""; } Java2DFrameConverter converter = new Java2DFrameConverter(); BufferedImage bi = converter.getBufferedImage(f); if (direction) { Image image = (Image) bi; bi = rotate(image, 90);//图片旋转90度 } ByteArrayOutputStream os = new ByteArrayOutputStream(); ImageIO.write(bi, "png", os); InputStream input = new ByteArrayInputStream(os.toByteArray()); MultipartFile multipartFile = new MockMultipartFile("temp.jpg", "temp.jpg", "temp.jpg", input); Response data = this.upload(multipartFile); Object content = data.getContent(); return JSONObject.parseObject(String.valueOf(content)).getString("url"); } /** * 图片旋转角度 * * @param src 源图片 * @param angel 角度 * @return 目标图片 */ public static BufferedImage rotate(Image src, int angel) { int src_width = src.getWidth(null); int src_height = src.getHeight(null); // calculate the new image size Rectangle rectangle = CalcRotatedSize(new Rectangle(new Dimension( src_width, src_height)), angel); BufferedImage res; res = new BufferedImage(rectangle.width, rectangle.height, BufferedImage.TYPE_INT_RGB); Graphics2D g2 = res.createGraphics(); // transform(这里先平移、再旋转比较方便处理;绘图时会采用这些变化,绘图默认从画布的左上顶点开始绘画,源图片的左上顶点与画布左上顶点对齐,然后开始绘画,修改坐标原点后,绘画对应的画布起始点改变,起到平移的效果;然后旋转图片即可) //平移(原理修改坐标系原点,绘图起点变了,起到了平移的效果,如果作用于旋转,则为旋转中心点) g2.translate((rectangle.width - src_width) / 2, (rectangle.height - src_height) / 2); // 旋转(原理transalte(dx,dy)->rotate(radians)->transalte(-dx,-dy);修改坐标系原点后,旋转90度,然后再还原坐标系原点为(0,0),但是整个坐标系已经旋转了相应的度数 ) g2.rotate(Math.toRadians(angel), src_width / 2, src_height / 2); // 先旋转(以目标区域中心点为旋转中心点,源图片左上顶点对准目标区域中心点,然后旋转) // g2.translate(rect_des.width/2,rect_des.height/ 2); // g2.rotate(Math.toRadians(angel)); // 再平移(原点恢复到源图的左上顶点处(现在的右上顶点处),否则只能画出1/4) // g2.translate(-src_width/2,-src_height/2); g2.drawImage(src, null, null); return res; } /** * 计算转换后目标矩形的宽高 * * @param src 源矩形 * @param angel 角度 * @return 目标矩形 */ private static Rectangle CalcRotatedSize(Rectangle src, int angel) { double cos = Math.abs(Math.cos(Math.toRadians(angel))); double sin = Math.abs(Math.sin(Math.toRadians(angel))); int des_width = (int) (src.width * cos) + (int) (src.height * sin); int des_height = (int) (src.height * cos) + (int) (src.width * sin); return new java.awt.Rectangle(new Dimension(des_width, des_height)); }
]]>error
home:1 Access to XMLHttpRequest at 'http://127.0.0.1:8080/article/list?start=1&limit=5' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
该注解可使用在类或者方法上,针对性的解决部分接口的跨域问题。
@Configurationpublic class WebMvcConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE") .maxAge(3600) .allowCredentials(true); }}
@Configurationpublic class CrossDomainConfig { @Bean public CorsFilter corsFilter() { //1.添加CORS配置信息 CorsConfiguration config = new CorsConfiguration(); //放行哪些原始域 config.addAllowedOrigin("*"); //是否发送Cookie信息 config.setAllowCredentials(true); //放行哪些原始域(请求方式) config.addAllowedMethod("*"); //放行哪些原始域(头部信息) config.addAllowedHeader("*"); //暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息) config.addExposedHeader("content-type"); //2.添加映射路径 UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource(); configSource.registerCorsConfiguration("/**", config); //3.返回新的CorsFilter. return new CorsFilter(configSource); }}
]]>top
--1.1 cpu --1.2 mem --1.3 id = idle 空闲率(越大越好) --1.4 load average 系统负载率(后面三个值分别是1分钟5分钟15分钟的平均负载 如果这三个值相加除三乘百分之百高于60说明系统负担重, 高于80说明要gg了)
uptime
--19:21:23 up 17 min, 2 users, load average: 0.00, 0.00, 0.00
free
--free -m
df
--df -h
vmstat(包含但不限于)
--[root@contOS-1 ~]# vmstat -n 2 3procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 0 0 0 904864 27448 39636 0 0 50 4 22 28 0 1 99 0 0 0 0 0 904848 27448 39632 0 0 0 0 14 13 0 0 100 0 0 0 0 0 904848 27448 39632 0 0 0 0 15 13 0 0 100 0 0
iostat
--iostat -xdk 2 3 --r/s w/s 每s读写
——————–以上源于周阳哥讲解,我总结的笔记
其他
pstree -p 22704|wc -l
ulimit -a
systemctl start|restart|stop elasticsearch
grep '"responsetime":2.' /usr/local/nginx/logs/access.log
进程十六进制值printf "%x\n" 22414jstack -l PID > jstack.log
]]>dump 文件里,值得关注的线程状态有:
死锁,Deadlock(重点关注)
执行中,Runnable
等待资源,Waiting on condition(重点关注)
等待获取监视器,Waiting on monitor entry(重点关注)
暂停,Suspended
对象等待中,Object.wait() 或 TIMED_WAITING
阻塞,Blocked(重点关注)
停止,Parked
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,
Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
服务降级:服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback
服务熔断:类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示
服务限流:秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行
]]>Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。
它的使用方法是定义一个服务接口然后在上面添加注解即可。
github: 点击访问
可以在使编写Java Http客户端变得更容易。
前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。
pom新增
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
server: port: 80eureka: client: register-with-eureka: false #是否将自己注册到注册中心,集群必须设置为true配合ribbon service-url: #defaultZone: http://localhost:7001/eureka defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/ #集群版
@EnableFeignClients
@SpringBootApplication@EnableFeignClientspublic class OrderFeignMain80 { public static void main(String[] args) { SpringApplication.run(OrderFeignMain80.class, args); }}
定义一个feign接口
@Configuration@FeignClient(value = "CLOUD-PAYMENT-SERVICE")public interface PaymentFeignService { @GetMapping(value = "/payment/get/{id}") CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);}
controller
@RestController@Slf4jpublic class OrderFeignController { @Resource private PaymentFeignService paymentFeignService; @GetMapping(value = "/consumer/payment/get/{id}") public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) { return paymentFeignService.getPaymentById(id); }}
访问 http://127.0.0.1/consumer/payment/get/1 即可发现跟前面写的Ribbon+RestTemplate效果一致。
cloud-provider-payment8001服务新增接口,同时cloud-comsumer-fegin-order80服务的PaymentFeignService接口和controller同样添加调用。
@GetMapping("/payment/feign/timeout") public String paymentFeignTimeOut(){ //测试OpenFeign超时 try { TimeUnit.SECONDS.sleep(3); }catch (InterruptedException e){ e.printStackTrace(); } return serverPort; }
访问http://127.0.0.1:8001/payment/feign/timeout -正常
访问http://127.0.0.1/consumer/payment/feign/timeout -超时
connect timed out executing GET http://CLOUD-PAYMENT-SERVICE/payment/feign/timeoutfeign.RetryableException: connect timed out executing GET http://CLOUD-PAYMENT-SERVICE/payment/feign/timeout at feign.FeignException.errorExecuting(FeignException.java:213)
#设置feign客户端超时时间ribbon: #指的是建立连接后从服务器读取到可用资源所用的时间 ReadTimeout: 5000 #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间 ConnectTimeout: 5000
NONE:默认的,不显示任何日志;
BASIC:仅记录请求方法、URL、响应状态码及执行时间;
HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息;
FULL:除了HEADERS中定义的信息之外,还有请求和响的正文及元数据。
@Configurationpublic class FeignConfig { @Bean Logger.Level feignLoggerLevel(){ return Logger.Level.FULL; }}
logging: level: #以什么级别监控哪个接口 xyz.zx21.springcloud.service.PaymentFeignService: debug
访问 http://127.0.0.1/consumer/payment/get/1
控制台输出日志
2020-03-22 19:46:33.369 DEBUG 3496 --- [p-nio-80-exec-2] x.z.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] ---> GET http://CLOUD-PAYMENT-SERVICE/payment/get/1 HTTP/1.12020-03-22 19:46:33.370 DEBUG 3496 --- [p-nio-80-exec-2] x.z.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] ---> END HTTP (0-byte body)2020-03-22 19:46:33.376 DEBUG 3496 --- [p-nio-80-exec-2] x.z.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] <--- HTTP/1.1 200 (6ms)2020-03-22 19:46:33.377 DEBUG 3496 --- [p-nio-80-exec-2] x.z.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] connection: keep-alive2020-03-22 19:46:33.377 DEBUG 3496 --- [p-nio-80-exec-2] x.z.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] content-type: application/json2020-03-22 19:46:33.377 DEBUG 3496 --- [p-nio-80-exec-2] x.z.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] date: Sun, 22 Mar 2020 11:46:33 GMT2020-03-22 19:46:33.377 DEBUG 3496 --- [p-nio-80-exec-2] x.z.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] keep-alive: timeout=602020-03-22 19:46:33.377 DEBUG 3496 --- [p-nio-80-exec-2] x.z.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] transfer-encoding: chunked2020-03-22 19:46:33.377 DEBUG 3496 --- [p-nio-80-exec-2] x.z.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] 2020-03-22 19:46:33.377 DEBUG 3496 --- [p-nio-80-exec-2] x.z.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] {"code":200,"message":"查询成功,serverPort: 8001","data":{"id":1,"serial":"小信"}}2020-03-22 19:46:33.377 DEBUG 3496 --- [p-nio-80-exec-2] x.z.s.service.PaymentFeignService : [PaymentFeignService#getPaymentById] <--- END HTTP (88-byte body)
]]>Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。
简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。
负载均衡+ResTemplate调用
特定算法:
com.netflix.loadbalancer.RoundRobinRule -轮询com.netflix.loadbalancer.RandomRule-随机com.netflix.loadbalance.RetryRule-先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试WeightedResponseTimeRule-对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择BestAvailableRule-会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务AvailabilityFilteringRule-先过滤掉故障实例,再选择并发较小的实例ZoneAvoidanceRule-默认规则,复合判断server所在区域的性能和server的可用性选择服务器
注意:这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。
@Configurationpublic class MySelfRule { @Bean public IRule myRule() { //定义为随机 return new RandomRule(); }}
添加注解@RibbonClient
@SpringBootApplication@EnableEurekaClient@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)public class OrderMain80 { public static void main(String[] args) { SpringApplication.run(OrderMain80.class, args); }}
访问http://127.0.0.1/consumer/payment/getForEntity/1 ,这个时候轮询变成了随机。
负载轮询算法:rest接口第几次请求数%服务器集群总数量=实际调用服务器位置下标,每次服务重启动后rest接口计数从1开始。
RoundRobinRule
public Server choose(ILoadBalancer lb, Object key) { if (lb == null) { log.warn("no load balancer"); return null; } Server server = null; int count = 0; while (server == null && count++ < 10) { List<Server> reachableServers = lb.getReachableServers(); List<Server> allServers = lb.getAllServers(); int upCount = reachableServers.size(); int serverCount = allServers.size(); if ((upCount == 0) || (serverCount == 0)) { log.warn("No up servers available from load balancer: " + lb); return null; } int nextServerIndex = incrementAndGetModulo(serverCount); server = allServers.get(nextServerIndex); if (server == null) { /* Transient. */ Thread.yield(); continue; } if (server.isAlive() && (server.isReadyToServe())) { return (server); } // Next. server = null; } if (count >= 10) { log.warn("No available alive servers after 10 tries from load balancer: " + lb); } return server; }
controller添加如下,方便测试。
@GetMapping(value = "/payment/lb") public String getPaymentLB(){ return serverPort; }
测试自己写的负载均衡是否生效。
public interface LoadBalance { ServiceInstance instance(List<ServiceInstance> serviceInstances);}
@Componentpublic class MyLB implements LoadBalance { private AtomicInteger atomicInteger = new AtomicInteger(0); public final int getAndIncrement() { int current; int next; do { current = this.atomicInteger.get(); next = current >= 2147483647 ? 0 : current + 1; } while (!this.atomicInteger.compareAndSet(current, next)); //CAS System.out.println("*****第几次访问,次数next: " + next); return next; } @Override public ServiceInstance instance(List<ServiceInstance> serviceInstances) { int index = getAndIncrement() % serviceInstances.size(); return serviceInstances.get(index); }}
@Resource private RestTemplate restTemplate; @Resource private LoadBalance loadBalance; @Resource private DiscoveryClient discoveryClient; @GetMapping("/consumer/payment/lb") public String getPaymentLB() { List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE"); if (instances == null || instances.size() <= 0) { return null; } ServiceInstance serviceInstance = loadBalance.instance(instances); URI uri = serviceInstance.getUri(); return restTemplate.getForObject(uri + "/payment/lb", String.class); }
访问http://localhost//consumer/payment/lb ,发现每次访问端口都在变化。
]]>|组件名|语言|CAP|服务健康检查|对外暴露接口|
|:—– |:—–|:—–
|Eureka |Java|AP|可配支持|HTTP|
|Consul |Go|CP|支持|HTTP/DNS|
|ZooKeeper |java|CP|支持|客户端
CAP理论关注粒度是数据,而不是整体系统设计的策略
C:Consistency(强一致性)
A:Availability(可用性)
P:Partition tolerance(分区容错性)
最多只能同时较好的满足两个。
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,因此,根据 CAP原理将NoSQL数据库分成了满足CA原则、满足CP原则和满足AP原则三大类:
CA-单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
CP-满足一致性,分区容忍性的系统,通常性能不是特别高。
AP-满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
]]>下载地址:点击
我用windows测试的,解压有一个exe程序, cmd
启动:consul agent -dev
访问:http://127.0.0.1:8500/ui/dc1/services
新增cloud-provider-payment8006
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId></dependency>
server: port: 8006# 服务别名---consul注册中心名称spring: application: name: consul-provider-payment cloud: consul: host: localhost port: 8500 discovery: service-name: ${spring.application.name}
@RestController@Slf4jpublic class PaymentController { @Value("${server.port}") private String serverPort; @GetMapping(value = "/payment/consul") public String paymentzk() { return "springcloud with consul: " + serverPort + "\t" + UUID.randomUUID().toString(); }}
新增cloud-consumerconsul-order80
跟提供者服务一致
server: port: 80# 服务别名---consul注册中心名称spring: application: name: consul-consumer-order cloud: consul: host: localhost port: 8500 discovery: service-name: ${spring.application.name}
@RestController@Slf4jpublic class OrderConsulController { private static final String INVOKE_URL = "http://consul-provider-payment"; @Resource private RestTemplate restTemplate; @GetMapping(value = "/consumer/payment/consul") public String paymentInfo(){ String result = restTemplate.getForObject(INVOKE_URL + "/payment/consul", String.class); return result; }}
访问http://127.0.0.1/consumer/payment/consul 可以调用成功!
]]>1. 启动ZK服务: zkServer.sh start2. 查看ZK服务状态: zkServer.sh status3. 停止ZK服务: zkServer.sh stop4. 重启ZK服务: zkServer.sh restart5. ZK服务连接: zkCli.sh
引入zookeeper相关的依赖
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>cloud2020</artifactId> <groupId>xyz.zx21.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-provider-payment8004</artifactId> <dependencies> <dependency> <artifactId>cloud-api-commons</artifactId> <groupId>xyz.zx21.springcloud</groupId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <!-- 引入zookeeper客户端 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId> <exclusions> <exclusion> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.10</version> </dependency> </dependencies></project>
server: port: 8004# 服务别名---注册zookeeper到注册中心名称spring: application: name: cloud-provider-payment cloud: zookeeper: connect-string: 192.168.8.208:2181 max-retries: 10 datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: org.gjt.mm.mysql.Driver url: jdbc:mysql://127.0.0.1:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: 123456
@RestController@Slf4jpublic class PaymentController { @Value("${server.port}") private String serverPort; @GetMapping(value = "/payment/zk") public String paymentzk() { return "springcloud with zookeeper: " + serverPort + "\t" + UUID.randomUUID().toString(); }}
/** * @author Administrator * @date 2020/3/19 14:10 * * //@EnableDiscoveryClient该注解用于向使用consul或 zookeeper作为法册中心时注册服务 */@SpringBootApplication@EnableDiscoveryClientpublic class PaymentMain8004 { public static void main(String[] args) { SpringApplication.run(PaymentMain8004.class, args); }}
启动zookeeper,运行微服务。
[zk: localhost:2181(CONNECTED) 25] ls / [services, zookeeper][zk: localhost:2181(CONNECTED) 26] ls /services [cloud-provider-payment][zk: localhost:2181(CONNECTED) 27] ls /services/cloud-provider-payment[c0ca3c06-7384-42a1-bfa2-63ed8e6f3a1e][zk: localhost:2181(CONNECTED) 28] [zk: localhost:2181(CONNECTED) 28] get /services/cloud-provider-payment/c0ca3c06-7384-42a1-bfa2-63ed8e6f3a1e{"name":"cloud-provider-payment","id":"c0ca3c06-7384-42a1-bfa2-63ed8e6f3a1e","address":"DESKTOP-T08AAHT","port":8004,"sslPort":null,"payload":{"@class":"org.springframework.cloud.zookeeper.discovery.ZookeeperInstance","id":"application-1","name":"cloud-provider-payment","metadata":{}},"registrationTimeUTC":1584766408452,"serviceType":"DYNAMIC","uriSpec":{"parts":[{"value":"scheme","variable":true},{"value":"://","variable":false},{"value":"address","variable":true},{"value":":","variable":false},{"value":"port","variable":true}]}}cZxid = 0x36ctime = Sat Mar 21 20:47:00 CST 2020mZxid = 0x36mtime = Sat Mar 21 20:47:00 CST 2020pZxid = 0x36cversion = 0dataVersion = 0aclVersion = 0ephemeralOwner = 0x170fd2061fe0002dataLength = 536numChildren = 0
server: port: 80spring: application: name: cloud-consumer-order cloud: zookeeper: connect-string: 192.168.8.208:2181 max-retries: 10 datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: org.gjt.mm.mysql.Driver url: jdbc:mysql://127.0.0.1:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: 123456
@Configurationpublic class ApplicationContextConfig { /** * //appLicationcontext.xml <bean id=""class=""> * * 使用@LoapBalanced注解赋于RestTemplate负载均衡的能力 */ @Bean @LoadBalanced public RestTemplate getRestTemplate() { return new RestTemplate(); }}
@RestController@Slf4jpublic class OrderZKController { private static final String INVOKE_URL = "http://cloud-provider-payment"; @Resource private RestTemplate restTemplate; @GetMapping(value = "/consumer/payment/zk") public String paymentInfo(){ String result = restTemplate.getForObject(INVOKE_URL + "/payment/zk", String.class); return result; }}
[zk: localhost:2181(CONNECTED) 4] ls /services[cloud-provider-payment, cloud-consumer-order]
]]>新建一个EurekaServer服务,名称:cloud-eureka-server7001
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency>
server: port: 7001eureka: instance: hostname: localhost #eureka服务端的实例名称 client: register-with-eureka: false #false表示不向注册中心注册自己 fetch-registry: false #false表示自己端就是注册中心 #false表示自己端魔是注册中心,我的钢责就是维护服务实,并不需要去检索服务 service-url: #设置与Eureka Server交互的地址查询服务和注般服务部需要依藏这个她址。 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
package xyz.zx21.springcloud;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;/** * @author Administrator * @date 2020/3/20 13:56 */@SpringBootApplication@EnableEurekaServerpublic class EurekaMain7001 { public static void main(String[] args) { SpringApplication.run(EurekaMain7001.class, args); }}
新增依赖
<!-- 引入eureka客户端 --><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency>
@EnableEurekaClient
新增
eureka: client: register-with-eureka: true #是否将自己注册到注册中心,集群必须设置为true配合ribbon fetch-registry: true #是否从服务端抓取已有的注册信息 #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 service-url: defaultZone: http://localhost:7001/eureka
与上面的同理,添加依赖,改yml和主启动类
application.yml
server: port: 80spring: application: name: cloud-order-serviceeureka: client: register-with-eureka: true #是否将自己注册到注册中心,集群必须设置为true配合ribbon fetch-registry: true #是否从服务端抓取已有的注册信息 #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 service-url: defaultZone: http://localhost:7001/eureka
分别启动eureka和80与8001服务,访问http://localhost:7001/
结果如下已经注册进来了
Application AMIs Availability Zones StatusCLOUD-ORDER-SERVICE n/a (1) (1) UP (1) - DESKTOP-T08AAHT:cloud-order-service:80CLOUD-PAYMENT-SERVICE n/a (1) (1) UP (1) - DESKTOP-T08AAHT:cloud-payment-service:8001
互相注册,相互守望
1 先启动eureka注册中心2 启动服务提供者payment支付服务3 支付服务启动后会把自身信息(比如服务地址以别名方式注册进eureka)4 消费者order服务在需要调用接口时,使用服务别名去注册中心获取实际的RPC远程调用地址5 消费者获得调用地址后,底层实际是利用HttpClient技术实现远程调用6 消费者获得服务地址后会统存在本地jvm内存中,默认每间隔30秒更新一次服务调用地址
127.0.0.1 eureka7001.com127.0.0.1 eureka7002.com
EurekaServer7001 的yml
server: port: 7001eureka: instance:# hostname: localhost #eureka服务端的实例名称 hostname: eureka7001.com #eureka服务端的实例名称 client: register-with-eureka: false #false表示不向注册中心注册自己 fetch-registry: false #false表示自己端就是注册中心 #false表示自己端魔是注册中心,我的钢责就是维护服务实,并不需要去检索服务 service-url: #设置与Eureka Server交互的地址查询服务和注册服务部需要依赖这个她址。# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka #单例 defaultZone: http://eureka7002.com:7002/eureka/
EurekaServer7002 的yml
server: port: 7002eureka: instance:# hostname: localhost #eureka服务端的实例名称 hostname: eureka7002.com #eureka服务端的实例名称 client: register-with-eureka: false #false表示不向注册中心注册自己 fetch-registry: false #false表示自己端就是注册中心 #false表示自己端魔是注册中心,我的钢责就是维护服务实,并不需要去检索服务 service-url: #设置与Eureka Server交互的地址查询服务和注册服务部需要依赖这个她址。# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka #单例 defaultZone: http://eureka7001.com:7001/eureka/
访问http://eureka7001.com:7001/ 和 http://eureka7002.com:7002/
能看到7001指向7002,7002指向7001即可。
DS Replicaseureka7001.com
eureka: client: register-with-eureka: true #是否将自己注册到注册中心,集群必须设置为true配合ribbon fetch-registry: true #是否从服务端抓取已有的注册信息 #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 service-url: #defaultZone: http://localhost:7001/eureka defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/ #集群版
1 先要启动EurekaServer,7001/7002服务2 再要启动服务提供者provider,80013 再要启动消费者,804 http://localhost/consumer/payment/get/1
copycloud-provider-payment8001的代码即可,修改端口。
为了区分方便,修改controller,用于显示端口
@RestController@Slf4jpublic class PaymentController { @Resource private PaymentService paymentService; @Value("${server.port}") private String serverPort; @PostMapping(value = "/payment/create") public CommonResult create(@RequestBody Payment payment) { int result = paymentService.create(payment); log.info("插入结果:" + result); if (result > 0) { return new CommonResult(200, "插入成功,serverPort: " + serverPort, result); } else { return new CommonResult(444, "插入失败", null); } } @GetMapping(value = "/payment/get/{id}") public CommonResult getPaymentById(@PathVariable("id") Long id) { Payment paymentById = paymentService.getPaymentById(id); log.info("查询结果:" + paymentById); if (paymentById != null) { return new CommonResult(200, "查询成功,serverPort: "+ serverPort, paymentById); } else { return new CommonResult(444, "没有对应记录,查询id:" + id, null); } }}
@RestController@Slf4jpublic class OrderController {// public static final String PAYMENT_URL = "http://localhost:8001"; public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE"; @Resource private RestTemplate restTemplate; @GetMapping("/consumer/payment/create") public CommonResult<Payment> create(Payment payment) { return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, CommonResult.class); } @GetMapping("/consumer/payment/get/{id}") public CommonResult<Payment> getPayment(@PathVariable("id") Long id) { return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class); }}
@Configurationpublic class ApplicationContextConfig { /** * //appLicationcontext.xml <bean id=""class=""> * * 使用@LoapBalanced注解赋于RestTemplate负载均衡的能力 */ @Bean @LoadBalanced public RestTemplate getRestTemplate() { return new RestTemplate(); }}
instance: instance-id: payment8001
instance: instance-id: payment8001 prefer-ip-address: true #访问路径可以显示IP地址
对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息
@RestController@Slf4jpublic class PaymentController { @Resource private PaymentService paymentService; @Resource private DiscoveryClient discoveryClient; @Value("${server.port}") private String serverPort; @PostMapping(value = "/payment/create") public CommonResult create(@RequestBody Payment payment) { int result = paymentService.create(payment); log.info("插入结果:" + result); if (result > 0) { return new CommonResult(200, "插入成功,serverPort: " + serverPort, result); } else { return new CommonResult(444, "插入失败", null); } } @GetMapping(value = "/payment/get/{id}") public CommonResult getPaymentById(@PathVariable("id") Long id) { Payment paymentById = paymentService.getPaymentById(id); log.info("查询结果:" + paymentById); if (paymentById != null) { return new CommonResult(200, "查询成功,serverPort: "+ serverPort, paymentById); } else { return new CommonResult(444, "没有对应记录,查询id:" + id, null); } } @GetMapping(value = "/payment/discovery") public Object discovery(){ List<String> services = discoveryClient.getServices(); for (String element : services) { log.info("****element: " +element); } List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE"); for (ServiceInstance instance:instances) { log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri()); } return this.discoveryClient; }}
新增@EnableDiscoveryClient注解
访问 http://localhost:8001/payment/discovery
响应
{ "services": [ "cloud-payment-service" ], "order": 0}
log日志
2020-03-20 16:24:03.078 INFO 11660 --- [nio-8001-exec-9] x.z.s.controller.PaymentController : ****element: cloud-payment-service2020-03-20 16:24:03.078 INFO 11660 --- [nio-8001-exec-9] x.z.s.controller.PaymentController : CLOUD-PAYMENT-SERVICE 192.168.8.122 8002 http://192.168.8.122:80022020-03-20 16:24:03.078 INFO 11660 --- [nio-8001-exec-9] x.z.s.controller.PaymentController : CLOUD-PAYMENT-SERVICE 192.168.8.122 8001 http://192.168.8.122:8001
自我保护机制:默认情况下EurekaClient定时向EurekaServer端发送心跳包如果Eureka在server端在一定时间内(默认90秒)没有收到EurekaClient发送心跳包,便会直接从服务注册列表中剔除该服务,但是在短时间(90秒中)内丢失了大量的服务实例心跳,这时候EurekaServer会开启自我保护机制,不会剔除该服务(该现象可能出现在如果网络不通但是EurekaClient为出现右机,此时如果换做别的注册中心如果一定时间内没有收到心跳会将剔除该服务,这样就出现了严重失误,因为客户端还能正常发送心就,只是网络延迟问题,而保护机制是为了解决此问题而产生的。
yml
eureka: instance:# hostname: localhost #eureka服务端的实例名称 hostname: eureka7001.com #eureka服务端的实例名称 client: register-with-eureka: false #false表示不向注册中心注册自己 fetch-registry: false #false表示自己端就是注册中心 #false表示自己端魔是注册中心,我的钢责就是维护服务实,并不需要去检索服务 service-url: #设置与Eureka Server交互的地址查询服务和注册服务部需要依赖这个她址。# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka #单例 #defaultZone: http://eureka7002.com:7002/eureka/ defaultZone: http://eureka7001.com:7001/eureka/ #单机 server: enable-self-preservation: false #禁用自我保护模式,保证不可用服务被及时剔除 eviction-interval-timer-in-ms: 2000 #2秒
yml
eureka: client: register-with-eureka: true #是否将自己注册到注册中心,集群必须设置为true配合ribbon fetch-registry: true #是否从服务端抓取已有的注册信息 #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 service-url: defaultZone: http://localhost:7001/eureka #defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/ #集群版 instance: instance-id: payment8001 prefer-ip-address: true #访问路径可以显示IP地址 lease-renewal-interval-in-seconds: 1 #向服务端发送心跳的时间间隔,单位为秒(默认是30秒) lease-expiration-duration-in-seconds: 2 #收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除
当关闭8001服务时,eureka会很快删除
]]>1、建module2、改pom3、写yml4、主启动5、业务类
server: port: 80
package xyz.zx21.springcloud;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;/** * @author Administrator * @date 2020/3/19 15:53 */@SpringBootApplicationpublic class OrderMain80 { public static void main(String[] args) { SpringApplication.run(OrderMain80.class, args); }}
注入RestTemplate,文档地址:点击访问
package xyz.zx21.springcloud.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.client.RestTemplate;/** * @author Administrator * @date 2020/3/19 16:39 */@Configurationpublic class ApplicationContextConfig { /** * //appLicationcontext.xml <bean id=""class=""> * */ @Bean public RestTemplate getRestTemplate() { return new RestTemplate(); }}
package xyz.zx21.springcloud.controller;import lombok.extern.slf4j.Slf4j;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.client.RestTemplate;import xyz.zx21.springcloud.entities.CommonResult;import xyz.zx21.springcloud.entities.Payment;import javax.annotation.Resource;/** * @author Administrator * @date 2020/3/19 16:37 */@RestController@Slf4jpublic class OrderController { public static final String PAYMENT_URL = "http://localhost:8001"; @Resource private RestTemplate restTemplate; @GetMapping("/consumer/payment/create") public CommonResult<Payment> create(Payment payment) { return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, CommonResult.class); } @GetMapping("/consumer/payment/get/{id}") public CommonResult<Payment> getPayment(@PathVariable("id") Long id) { return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class); }}
启动PaymentMain8001和OrderMain80,测试OrderMain80调用PaymentMain8001服务。
将多余的代码,单独放到一个模块,提供给各个服务,避免冗余,比如实体类
pom.xml
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>cloud2020</artifactId> <groupId>xyz.zx21.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-api-commons</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <!-- 当optional为true则说明该依赖禁止依赖传递 <optional>true</optional>--> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.1.1</version> </dependency> </dependencies></project>
<dependency> <artifactId>cloud-api-commons</artifactId> <groupId>xyz.zx21.springcloud</groupId> <version>1.0-SNAPSHOT</version></dependency>
关于调用/consumer/payment/create接口,插入数据为null的情况,需要在cloud-provider-payment8001服务对应的controller添加@RequestBody注解
@PostMapping(value = "/payment/create")public CommonResult create(@RequestBody Payment payment) {...}
]]>1、New Project2、聚合总父工程名字3、Maven选版本4、工程名字5、字符编码-UTF-86、注解生效激活7、java编译版本选择88、File Type过滤(可选)
新建一个maven工程,工程名:cloud2020,删除src目录,只留下pom。
修改pom.xml
<packaging>pom</packaging>
完整的pom
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>xyz.zx21.springcloud</groupId> <artifactId>cloud2020</artifactId> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <junit.version>4.12</junit.version> <log4j.version>1.2.17</log4j.version> <lombok.version>1.16.18</lombok.version> <mysql.version>5.1.47</mysql.version> <druid.version>1.1.16</druid.version> <mybatis.spring.boot.version>1.3.2</mybatis.spring.boot.version> </properties> <!-- 子模块继承之后,提供作用:锁定版本+子模块不用写groupId和version --> <dependencyManagement> <dependencies> <!-- springboot 2.2.2 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.2.2.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <!-- springcloud cloud Hoxton.SR1 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR1</version> <type>pom</type> <scope>import</scope> </dependency> <!-- springcloud cloud alibaba 2.1.0.RELEASE --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.1.0.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid.version}</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis.spring.boot.version}</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <optional>true</optional> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <fork>true</fork> <addResources>true</addResources> </configuration> </plugin> </plugins> </build></project>
order(客户端消费者80) -> payment(微服务提供者8001)
1、建module2、改pom3、写yml4、主启动5、业务类
pom.xml
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>cloud2020</artifactId> <groupId>xyz.zx21.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-provider-payment8001</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies></project>
server: port: 8001spring: application: name: cloud-payment-service datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: org.gjt.mm.mysql.Driver url: jdbc:mysql://127.0.0.1:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: 123456mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.com.springcloud.entities
db2019
sql如下
CREATE TABLE `payment` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', `serial` varchar(200) DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
实体类Payment
package xyz.zx21.springcloud.entities;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import java.io.Serializable;/** * @author Administrator * @date 2020/3/19 14:20 */@Data@AllArgsConstructor@NoArgsConstructorpublic class Payment implements Serializable { private Long id; private String serial;}
用于给前端返回统一格式的数据
package xyz.zx21.springcloud.entities;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;/** * @author Administrator * @date 2020/3/19 14:24 */@Data@AllArgsConstructor@NoArgsConstructorpublic class CommonResult<T> { private Integer code; private String message; private T data; public CommonResult(Integer code, String message) { this(code, message, null); }}
package xyz.zx21.springcloud.controller;import lombok.extern.slf4j.Slf4j;import org.springframework.web.bind.annotation.*;import xyz.zx21.springcloud.entities.CommonResult;import xyz.zx21.springcloud.entities.Payment;import xyz.zx21.springcloud.service.PaymentService;import javax.annotation.Resource;/** * @author Administrator * @date 2020/3/19 14:28 */@RestController@Slf4jpublic class PaymentController { @Resource private PaymentService paymentService; @PostMapping(value = "/payment/create") public CommonResult create(Payment payment) { int result = paymentService.create(payment); log.info("插入结果:" + result); if (result > 0) { return new CommonResult(200, "插入成功", result); } else { return new CommonResult(444, "插入失败", null); } } @GetMapping(value = "/payment/get/{id}") public CommonResult getPaymentById(@PathVariable("id") Long id) { Payment paymentById = paymentService.getPaymentById(id); log.info("查询结果:" + paymentById); if (paymentById != null) { return new CommonResult(200, "查询成功", paymentById); } else { return new CommonResult(444, "没有对应记录,查询id:" + id, null); } }}
package xyz.zx21.springcloud.service;import xyz.zx21.springcloud.entities.Payment;/** * @author Administrator * @date 2020/3/19 14:32 */public interface PaymentService { int create(Payment payment); Payment getPaymentById(Long id);}
package xyz.zx21.springcloud.service.impl;import org.springframework.stereotype.Service;import xyz.zx21.springcloud.dao.PaymentDao;import xyz.zx21.springcloud.entities.Payment;import xyz.zx21.springcloud.service.PaymentService;import javax.annotation.Resource;/** * @author Administrator * @date 2020/3/19 14:33 */@Servicepublic class PaymentServiceImpl implements PaymentService { @Resource private PaymentDao paymentDao; @Override public int create(Payment payment) { return paymentDao.create(payment); } @Override public Payment getPaymentById(Long id) { return paymentDao.getPaymentById(id); }}
package xyz.zx21.springcloud.dao;import org.apache.ibatis.annotations.Mapper;import org.apache.ibatis.annotations.Param;import xyz.zx21.springcloud.entities.Payment;/** * @author Administrator * @date 2020/3/19 14:34 */@Mapperpublic interface PaymentDao { int create(Payment payment); Payment getPaymentById(@Param("id") Long id);}
在resources下新建mapper文件夹
PaymentMapper.xml
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="xyz.zx21.springcloud.dao.PaymentDao"> <insert id="create" parameterType="xyz.zx21.springcloud.entities.Payment" useGeneratedKeys="true" keyProperty="id"> insert into payment(serial) values(#{serial}); </insert> <resultMap id="BaseResultMap" type="xyz.zx21.springcloud.entities.Payment"> <id column="id" property="id" jdbcType="BIGINT"></id> <id column="serial" property="serial" jdbcType="VARCHAR"></id> </resultMap> <select id="getPaymentById" parameterType="java.lang.Long" resultMap="BaseResultMap"> select * from payment where id = #{id}; </select></mapper>
get
url:http://127.0.0.1:8001/payment/get/1
返回:
{ "code": 200, "message": "查询成功", "data": { "id": 1, "serial": "小信" }}
post
url:http://127.0.0.1:8001/payment/create?serial=测试
返回:
{ "code": 200, "message": "插入成功", "data": 1}
]]><dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>2.1.2</version></dependency>
@Data@ContentRowHeight()@HeadRowHeight()@ColumnWidth()public class Student { @ExcelProperty("学生姓名") private String name; @ExcelProperty("学生年龄") private int age; @ExcelProperty(value = "性别") private String sex; @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒") @ExcelProperty(value = "日期",index = 5) private Date date;}
解释:
@Data:lombok的注解,可以省略@get/@set等等。
@ContentRowHeight()/@HeadRowHeight()/@ColumnWidth():可修改列宽、行高。
@ExcelProperty:定义列名称或者index位置,默认0开始。
@DateTimeFormat:日期转换
@RunWith(SpringRunner.class)@SpringBootTestpublic class ExcelTest { /** * 最简单的导出 */ @Test public void simpleWrite01() throws IOException { // 写法1 String fileName = (new File("").getCanonicalPath()) + "/simpleWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 // 如果这里想使用03 则 传入excelType参数即可 EasyExcel.write(fileName, Student.class).sheet("模板").doWrite(getData()); // 写法2/* fileName = (new File("").getCanonicalPath()) + "/simpleWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去写 ExcelWriter excelWriter = EasyExcel.write(fileName, Student.class).build(); WriteSheet writeSheet = EasyExcel.writerSheet("模板").build(); excelWriter.write(getData(), writeSheet); // 千万别忘记finish 会帮忙关闭流 excelWriter.finish();*/ } /** * 指定列名导出 */ @Test public void simpleWrite02() throws IOException { String fileName = (new File("").getCanonicalPath()) + "/simpleWrite" + System.currentTimeMillis() + ".xlsx"; // 根据用户传入字段 假设我们要忽略 date Set<String> columnFiledNames = new HashSet<String>(); columnFiledNames.add("age"); columnFiledNames.add("sex"); //1、需要忽略的列名// EasyExcel.write(fileName, Student.class).excludeColumnFiledNames(columnFiledNames).sheet("模板").doWrite(getData()); //2、需要保留的列名 EasyExcel.write(fileName, Student.class).includeColumnFiledNames(columnFiledNames).sheet("模板").doWrite(getData()); } private List<Student> getData() { List<Student> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { Student student = new Student(); student.setName("小信" + i); student.setAge(22); student.setSex("男"); student.setDate(new Date()); list.add(student); } return list; }}
@RestControllerpublic class WebExcelTest { /** * web中写入 */ @GetMapping("get") public void getExcel(HttpServletResponse response) { try { response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding("utf-8"); response.setHeader("Content-disposition", "attachment;filename=" + System.currentTimeMillis() + ".xlsx"); // 这里需要设置不关闭流 EasyExcel.write(response.getOutputStream(), Student.class).autoCloseStream(Boolean.FALSE).sheet("模板") .doWrite(getData()); } catch (IOException e) { e.printStackTrace(); } } private List<Student> getData() { List<Student> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { Student student = new Student(); student.setName("小信" + i); student.setAge(22); student.setSex("男"); student.setDate(new Date()); list.add(student); } return list; }
官方文档:点击
]]>