目录
分布式基础篇总结
1、分布式基础概念
微服务、注册中心、配置中心、远程调用、Feign、网关
(1)微服务
- 微服务最大的特点就是独立、自治。
- 在本项目中每一个不同功能的项目都创建了它自己的服务,不同功能的项目可以实现并行开发、互不影响。
(2)Nacos注册中心
- 注册中心可以说是微服务架构中的”通讯录“,它记录了服务和服务地址的映射关系。
- 在分布式架构中,服务会注册到这里,当服务需要调用其它服务时,就这里找到服务的地址,进行调用。
(3)Nacos配置中心
- 顾名思义将配置中心化,说白了就是将配置从应用中抽取出来,统一管理,优雅的解决了配置的动态变更、权限管理、持久化、运维成本等问题。
Nacos使用步骤:
-
导入相关依赖
<!--服务的注册/发现--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--配置中心来做配置管理--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency>
-
添加配置文件
注册中心配置:
application.yml
spring: cloud: nacos: discovery: server-addr: localhost:8848 application: name: productName
配置中心配置:
bootstrap.properties
# 改名字,对应nacos里的配置文件名 spring.application.name=gulimall-coupon # nacos 地址 spring.cloud.nacos.config.server-addr=localhost:8848 # 可以选择对应的命名空间,写上对应环境的命名空间ID spring.cloud.nacos.config.namespace=79d5ebaa-bd56-446e-97e1-ba06cd7e51fa # 更改配置分组 spring.cloud.nacos.config.group=dev # 拉取配置文件 spring.cloud.nacos.config.extension-configs[0].data-id=datasource.yml spring.cloud.nacos.config.extension-configs[0].group=dev spring.cloud.nacos.config.extension-configs[0].refresh=true
-
启动类添加相关注解开启功能
@EnableDiscoveryClient
(4)远程调用-feign
- 在服务开发期间存在远程调用的场景,在本项目中服务的远程调用是由Feign来实现的。
- 而使用Feign进行远程调用的前提则是服务被注册到注册中心。
Feign的使用步骤:
-
导入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
-
启动类添加注解:指定要扫描的包,也就是要进行远程调用的包
@EnableFeignClients(basePackages="包名")
@EnableFeignClients("com.ljn.gulimall.product.feign")
-
编写远程调用类,并指定远程调用的服务名和路径
// 要调用的服务 @FeignClient("gulimall-coupon") public interface CouponFeignService { //找到gulimall-coupon服务,给/coupon/spubounds/save发送请求。 @PostMapping("/coupon/spubounds/save") R saveSpuBounds(@RequestBody SpuBoundTo spuBoundTo); }
-
在需要远程调用的方法中调用该方法
@Autowired CouponFeignService couponFeignService; R r = couponFeignService.saveSpuBounds(spuBoundTo);
(5)网关-gateway
- API 网关是一个反向路由,屏蔽内部细节,为调用者提供统一入口,接收所有调用者请求,通过路由机制转发到服务实例。
- 本项目中使用Gateway作为网关,所有的请求都发送给网关,由网关代理给其他服务。
- 我们可以在网关处做很多统一的处理,如:统一的跨域解决。
Gateway使用步骤:
-
创建
Gateway
服务 -
导入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
-
将服务注册到注册中心
spring.application.name=gulimall-gateway spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
-
配置路由,请求转发
spring: cloud: gateway: routes: - id: product_route uri: lb://gulimall-product # 要路由的服务 lb 代表负载均衡 predicates: # 什么情况下路由给它 - Path=/api/product/** filters: # 过滤 # 把/api/* 去掉,剩下的留下来 - RewritePath=/api/(?<segment>.*),/$\{segment}
-
其他统一处理(如:统一跨域处理)
@Configuration // gateway public class GulimallCorsConfiguration { @Bean // 添加过滤器 public CorsWebFilter corsWebFilter() { // 基于url跨域,选择reactive包下的 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); // 跨域配置信息 CorsConfiguration corsConfiguration = new CorsConfiguration(); // 允许跨域的头 corsConfiguration.addAllowedHeader("*"); // 允许跨域的请求方式 corsConfiguration.addAllowedMethod("*"); // 允许跨域的请求来源 corsConfiguration.addAllowedOrigin("*"); // 是否允许携带cookie跨域 corsConfiguration.setAllowCredentials(true); // 任意url都要进行跨域配置 source.registerCorsConfiguration("/**", corsConfiguration); return new CorsWebFilter(source); } }
2、基础开发
SpringBoot2.0、SpringCloud、Mybatis-Plus、Vue组件化、阿里云对象存储
在对后台管理系统的开发中:
- 使用SpringBoot 对项目进行搭建
- 简单使用了SpringCloud 的Nacos、Feign 、Gateway 实现项目的注册与发现、配置管理、请求转发等。
- 前端代码则是基于Vue组件化开发
- 对于图片的存储使用了第三方服务——阿里云对象存储(OSS)。
(1)SpringCloud
-
使用是
SpringCloud Alibaba
引入依赖管理:
<dependencyManagement>
<dependencies>
<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>
</dependencies>
</dependencyManagement>
(2)阿里云对象存储(OSS)
是阿里云提供的海量、安全、低成本、高可靠的云存储服务。
图片上传方式:
-
本项目选
择服务端签名后上传
的方式实现图片的存储功能。 - 即保障了安全性,又避免给服务器造成过多的压力。
使用步骤:
- 开启RAM访问控制,创建拥有指定权限的子账户,使用子账户进行图片上传操作。
- 导入依赖
<!-- 对象存储oss-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
</dependency>
-
application.ym
l中配置
key
,
secret
和
endpoint
相关信息spring: cloud: alicloud: secret-key: XXXXXXXXXXXXXXXXXX access-key: XXXXXXXXXXXXXXXXXX oss: endpoint: oss-cn-hangzhou.aliyuncs.com bucket: XXXXXXXXXXX
-
按照OSS 的java SDK 编写controller
package com.ljn.gulimall.thirdparty.Controller; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClient; import com.aliyun.oss.common.utils.BinaryUtil; import com.aliyun.oss.model.MatchMode; import com.aliyun.oss.model.PolicyConditions; import com.ljn.common.utils.R; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.text.SimpleDateFormat; import java.util.Date; import java.util.LinkedHashMap; import java.util.Map; @RestController public class OssController { @Autowired OSS ossClient; @Value("${spring.cloud.alicloud.oss.endpoint}") String endpoint; @Value("${spring.cloud.alicloud.oss.bucket}") String bucket; @Value("${spring.cloud.alicloud.access-key}") String accessId; @Value("${spring.cloud.alicloud.secret-key}") String accessKey; @RequestMapping("/oss/policy") public R policy() { // host的格式为 bucketname.endpoint String host = "https://" + bucket + "." + endpoint; String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); // 用户上传文件时指定的前缀。 String dir = format; Map<String, String> respMap = null; try { long expireTime = 30; long expireEndTime = System.currentTimeMillis() + expireTime * 1000; Date expiration = new Date(expireEndTime); PolicyConditions policyConds = new PolicyConditions(); policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000); policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir); String postPolicy = ossClient.generatePostPolicy(expiration, policyConds); byte[] binaryData = postPolicy.getBytes("utf-8"); String encodedPolicy = BinaryUtil.toBase64String(binaryData); String postSignature = ossClient.calculatePostSignature(postPolicy); respMap = new LinkedHashMap<String, String>(); respMap.put("accessid", accessId); respMap.put("policy", encodedPolicy); respMap.put("signature", postSignature); respMap.put("dir", dir); respMap.put("host", host); respMap.put("expire", String.valueOf(expireEndTime / 1000)); } catch (Exception e) { // Assert.fail(e.getMessage()); System.out.println(e.getMessage()); } finally { ossClient.shutdown(); } return R.ok().put("data",respMap); } }
3、环境
• Linux、Docker、MySQL、Redis、逆向工程
4、开发规范
• 数据校验JSR303、全局异常处理、全局统一返回、全局跨域处理
• 枚举状态、业务状态码、VO与TO与PO划分、逻辑删除
• Lombok:@Data、@Slf4j
对于数据的新增与修改使用JSR303进行数据校验,配置全局的异常处理、统一返回和跨域处理,将项目中固定的状态使用枚举进行编写。项目中还使用了Mybatis-Plus的逻辑删除功能。
(1)JSR303数据校验
-
新版本springboot需要添加
validation
启动器<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> <version>2.3.7.RELEASE</version> </dependency>
-
给Bean添加校验注解参考
:javax.validation.constraints
包下,并定义自己的message提示,如:@NotBlank(message = "品牌名不能为空") private String name; @NotEmpty @URL(message = "logo地址必须合法") private String logo; @Pattern(regexp = "/^[a-zA-Z]$/", message = "检索首字母必须是一个字母") private String firstLetter; @Min(value = 0,message = "排序必须大于等于0") private Integer sort;
-
controller
的方法中添加
@Valid
注解开启校验,如:@RequestMapping("/save") public R save(@Valid @RequestBody BrandEntity brand){ brandService.save(brand); return R.ok(); }
-
分组校验(多场景的复杂校验)
-
@Validated
注解指定分组 -
标注上
groups
,指定什么情况下才需要进行校验 - 默认情况下,在分组校验情况下,没有指定指定分组的校验注解,将不会生效,它只会在不分组的情况下生效。
如:指定在更新和添加的时候,都需要进行校验。新增时不需要带id,修改时必须带id
@NotNull(message = "修改必须定制品牌id", groups = {UpdateGroup.class})
@Null(message = "新增不能指定id", groups = {AddGroup.class})
@TableId
private Long brandId;
@RequestMapping("/save")
public R save(@Validated(AddGroup.class) @RequestBody BrandEntity brand) {
brandService.save(brand);
return R.ok();
}
-
自定义校验注解
- 编写自定义检验注解
-
common
创建文件
ValidationMessages.properties
配置文件 -
编写自定义校验器
ConstraintValidator
- 关联校验器和检验注解
(2) Mybatis-Plus逻辑删除功能
-
配置全局的逻辑删除规则(可省略)
mybatis-plus: global-config: db-config: id-type: auto logic-delete-value: 1 # 逻辑已删除值(默认为 1) logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
-
给Bean加上逻辑删除注解@TableLogic
/**配置特定的逻辑删除状态 * 是否显示[0-不显示,1显示] */ @TableLogic(value = "1",delval = "0") private Integer showStatus;
(3) 统一的异常处理
-
编写异常处理类,使用
@ControllerAdvice。
-
可以使用SpringMvc所提供的
@ControllerAdvice
,通过
basePackages
能够说明处理哪些路径下的异常。 -
使用
@ExceptionHandler
标注方法可以处理的异常。 -
如:抽取一个异常处理类:
com.ljn.gulimall.product.exception.GuliMallExceptionControllerAdvice
,对数据校验做统一的异常处理:@Slf4j @RestControllerAdvice(basePackages = "com.ljn.gulimall.product.controller") public class GuliMallExceptionControllerAdvice { // 数据校验异常 @ExceptionHandler(value = Exception.class) public R handleValidException(MethodArgumentNotValidException exception) { Map<String, String> map = new HashMap<>(); // 1. 获取数据校验的错误结果 BindingResult bindingResult = exception.getBindingResult(); // 2. 遍历获取结果 bindingResult.getFieldErrors().forEach(fieldError -> { String message = fieldError.getDefaultMessage(); String field = fieldError.getField(); map.put(field, message); }); log.error("数据校验出现问题{},异常类型{}", exception.getMessage(), exception.getClass()); return R.error(400, "数据校验出现问题").put("data", map); } // 默认异常 @ExceptionHandler(value = Throwable.class) public R handleException(Throwable throwable) { log.error("未知异常{},异常类型{}", throwable.getMessage(), throwable.getClass()); return R.error(400, "数据校验出现问题"); } }