配置文件

@RefreshScope实现配置自动更新

image-20210724115516250

项目中的使用:每个微服务创建自己的命名空间,使用配置分组区分环境,dev、test、pro、

image-20210724115658170

image-20210724122925501

application.properties对应命名空间下,所属分组、应用名.properties。

图片表示application.properties。对应nacos中coupon命名空间,prod分组,Data Id名称为应用名gulimall-coupon.properties文件。yml同理

配置集

同时加载多个配置集

image-20210724120455633

image-20210724120746937

注释配置文件

image-20210724121131511

使用配置集

image-20210724121556728

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#使用coupon命名空间
spring.cloud.nacos.config.namespace=29176167-82d9-46b0-9a00-687acca63fd4
##分组
#spring.cloud.nacos.config.group=prod

#加载多个配置集
spring.cloud.nacos.config.ext-config[0].data-id=datasource.yml
spring.cloud.nacos.config.ext-config[0].group=dev
spring.cloud.nacos.config.ext-config[0].refresh=true

spring.cloud.nacos.config.ext-config[1].data-id=mybatis.yml
spring.cloud.nacos.config.ext-config[1].group=dev
spring.cloud.nacos.config.ext-config[1].refresh=true

spring.cloud.nacos.config.ext-config[2].data-id=other.yml
spring.cloud.nacos.config.ext-config[2].group=dev
spring.cloud.nacos.config.ext-config[2].refresh=true

此时访问localhost:7000/coupon/coupon/test

image-20210724121718295

值是们项目中配置的值,并没有使用到相对应的配置文件

image-20210724121834712

这是因为

image-20210724121903515

No active profile set, falling back to default profiles: default 我们没有设置使用配置文件的分组,默认使用的是DEFAULT_GROUP分组。当我们的配置集中没有程序中使用的配置则会使用默认分组的配置文件,但是coupon命名空间下没有这个默认分组的配置文件,则使用原程序的配置application.properties

当取消注释设置使用prod分组后

image-20210724122432983

Gatewa网关

image-20210724150407519

引入common模块中引入了跟数据库有关的相关依赖,而网关项目又没配置数据库信息,从而启动报错,主启动程序排除解决报错。

image-20210724150644490

1
2
3
4
5
6
7
8
9
10
//不需要使用数据库
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableDiscoveryClient
public class GulimallGatewayApplication {

public static void main(String[] args) {
SpringApplication.run(GulimallGatewayApplication.class, args);
}

}

前端发送请求到网关存在跨域问题

协议名、域名、端口都要相同,其中一个不同都会产生跨越问题。

网关统一解决跨域问题。

image-20210724161302098

image-20210724161316459

image-20210724161342760

image-20210724161354136

image-20210724161026129

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
public class GuliMallCorsConfiguration {

@Bean
public CorsWebFilter corsWebFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

CorsConfiguration corsConfiguration = new CorsConfiguration();
//1.配置跨域
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.setAllowCredentials(true);

source.registerCorsConfiguration("/**", corsConfiguration);
return new CorsWebFilter(source);
}
}

image-20210724161924283

只希望有一个解决跨域,不希望有多个。将另一个注释,这里是renren-fast解决了。(网关解决跨域,人人就不要解决了,否则重复!没配网关的时候不会出现跨域问题,就是人人后台解决了)

人人快速开发

image-20210724153254658

image-20210724153315999

product/category转换成路由为product-category

根据规则页面则应该创建在views目录下product包下的category.vue文件中。

image-20210724154239421

image-20210724160736662

前端项目发送请求到网关中

image-20210724160107864

网关重写路径RewritePath

https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/#global-filters

uri: lb://renren-fast: lb负载均衡://应用名

image-20210724163635845

返回的数据Object中data属性中的data才是我们想要的。所用我们可以使用ES6的解构赋值语法。

image-20210724163808734

只拿出返回数据的data属性值,data.data中则包含我们所要的数据。

前端Vue

image-20210724173433318

框中内容回显,应该使用请求查询当前数据库,得到数据再回显到页面上。避免当多人在页面上操作时一个人修改了,会导致当前数据滞后。

image-20210724181819622

image-20210724181843068

以上图片ES6新语法。

image-20210725095649351

拖拽修改菜单功能

draggable开启拖拽功能

allow-drop 拖拽时判定目标节点能否被放置`Function(draggingNode, dropNode, type)``

``type` 参数有三种情况:’prev’、’inner’ 和 ‘next’,分别表示放置在目标节点前、插入至目标节点和放置在目标节点后

image-20210725103418179

node-drop 拖拽成功完成时触发的事件。共四个参数,依次为:被拖拽节点对应的 Node、结束拖拽时最后进入的节点、被拖拽节点的放置位置(before、after、inner)、event

image-20210725103827605

批量删除

image-20210725153814273

使用组件自带的方法ref="xxx"

image-20210725153847537

image-20210725153907961

this.$refs.menuTree.getCheckedNodes();调用树形组件的getCheckedNodes方法

image-20210725154003981

OOS云存储

阿里云OSS云存储首先创建一个Bucket

Bucket

image-20210725211311976

公共读代表读不需要账户密码,写操作则需要。

image-20210725211518472

可以点击上传文件进行上传操作,通过url访问。

image-20210725211842302

SDK

https://help.aliyun.com/document_detail/32009.html?spm=a2c4g.11186623.6.923.4c6513c03fK1WG

image-20210725212228699

首先安装SDK

1
2
3
4
5
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.10.2</version>
</dependency>

上传文件

简单上传文件

image-20210725212529299

Endpoint地域节点,在Bucekt页面概览中可以找到。

image-20210725213041410

在测试中将上面的代码进行测试。

image-20210725214419532

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
String endpoint = "yourEndpoint";
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
String accessKeyId = "yourAccessKeyId";
String accessKeySecret = "yourAccessKeySecret";

// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

// 创建PutObjectRequest对象。
// 依次填写Bucket名称(例如examplebucket)、Object完整路径(例如exampledir/exampleobject.txt)和本地文件的完整路径。Object完整路径中不能包含Bucket名称。
// 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
PutObjectRequest putObjectRequest = new PutObjectRequest("examplebucket", "exampledir/exampleobject.txt", new File("D:\\localpath\\examplefile.txt"));

// 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。
// ObjectMetadata metadata = new ObjectMetadata();
// metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
// metadata.setObjectAcl(CannedAccessControlList.Private);
// putObjectRequest.setMetadata(metadata);

// 上传文件。
ossClient.putObject(putObjectRequest);

// 关闭OSSClient。
ossClient.shutdown();

上传成功后,就能在我们给定的路径看到我们上传的文件。

image-20210725214536414

Alibaba Cloud OSS

https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md

https://github.com/alibaba/aliyun-spring-boot/tree/master/aliyun-spring-boot-samples/aliyun-oss-spring-boot-sample

image-20210725220653647

1
2
3
4
5
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>aliyun-oss-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>

将我们之前导入的SDK注释掉。这里要指定一下版本,可以区阿里云的Maven仓库查看依赖https://maven.aliyun.com/mvn/search

image-20210725222913248

配置文件配置OSS

1
2
3
4
5
6
7
#oss配置
alibaba:
cloud:
access-key:
secret-key:
oss:
endpoint:

可行

1
2
3
4
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
</dependency>
1
2
3
4
5
6
7
spring:
cloud:
alicloud:
access-key: xxxxxxxxxxxxxx
secret-key: xxxxxxxxxxxxxxxxxxxxxx
oss:
endpoint: oss-cn-shenzhen.aliyuncs.com

我们可以通过OSSClient对象进行相关操作,省略了创建OSSClient的步骤。

image-20210725223431409

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootTest
class GulimallProductApplicationTests {
@Autowired(required = false)
OSSClient ossClient;

@Test
public void testOSSUpload() {
ossClient.putObject("mall-kylin", "exampledir/ossClient.txt", new File("C:\\Users\\kylin\\Desktop\\OSS.txt"));
//关闭
ossClient.shutdown();
}
}

目前尚未知晓原因。

服务端签名后直传

https://help.aliyun.com/document_detail/31926.html?spm=a2c4g.11186623.6.1749.5a3c43d33sLRZe

image-20210726224157206

选择代码示例Java。

应用服务器核心代码解析

image-20210726224417758

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

String accessId = "<yourAccessKeyId>"; // 请填写您的AccessKeyId。
String accessKey = "<yourAccessKeySecret>"; // 请填写您的AccessKeySecret。
String endpoint = "oss-cn-hangzhou.aliyuncs.com"; // 请填写您的 endpoint。
String bucket = "bucket-name"; // 请填写您的 bucketname 。
String host = "https://" + bucket + "." + endpoint; // host的格式为 bucketname.endpoint
// callbackUrl为 上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
String callbackUrl = "http://88.88.88.88:8888";
String dir = "user-dir-prefix/"; // 用户上传文件时指定的前缀。

// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessId, accessKey);
try {
long expireTime = 30;
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
Date expiration = new Date(expireEndTime);
// PostObject请求最大可支持的文件大小为5 GB,即CONTENT_LENGTH_RANGE为5*1024*1024*1024。
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);

Map<String, String> 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));
// respMap.put("expire", formatISO8601Date(expiration));

JSONObject jasonCallback = new JSONObject();
jasonCallback.put("callbackUrl", callbackUrl);
jasonCallback.put("callbackBody",
"filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}");
jasonCallback.put("callbackBodyType", "application/x-www-form-urlencoded");
String base64CallbackBody = BinaryUtil.toBase64String(jasonCallback.toString().getBytes());
respMap.put("callback", base64CallbackBody);

JSONObject ja1 = JSONObject.fromObject(respMap);
// System.out.println(ja1.toString());
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "GET, POST");
response(request, response, ja1.toString());

} catch (Exception e) {
// Assert.fail(e.getMessage());
System.out.println(e.getMessage());
} finally {
ossClient.shutdown();
}
}

image-20210726225017611

这一段代码是返回Json解决跨域,跨域我们前面解决了。Json则使用的是@RestController也不用了。所以删掉这段代码直接返回respMap。

由于导入了spring-cloud-starter-alicloud-oss所以创建OssClient步骤可以省略,文件前缀按日期格式来划分。回调服务器的URL暂时用不到注释掉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
@RestController
public class OssController {

@Resource
OSSClient ossClient;

@Value("${spring.cloud.alicloud.oss.endpoint}")
private String endpoint;

@Value("${spring.cloud.alicloud.oss.bucket}")
private String bucket;

@Value("${spring.cloud.alicloud.access-key}")
private String accessId;

@GetMapping("/oss/policy")
public Map<String, String> policy() {
String host = "https://" + bucket + "." + endpoint; // host的格式为 bucketname.endpoint
// callbackUrl为 上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
//String callbackUrl = "http://88.88.88.88:8888";
String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
String dir = format + "/"; // 用户上传文件时指定的前缀。
try {
long expireTime = 30;
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
Date expiration = new Date(expireEndTime);
// PostObject请求最大可支持的文件大小为5 GB,即CONTENT_LENGTH_RANGE为5*1024*1024*1024。
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);

Map<String, String> 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));
// respMap.put("expire", formatISO8601Date(expiration));

JSONObject jasonCallback = new JSONObject();
//jasonCallback.put("callbackUrl", callbackUrl);
jasonCallback.put("callbackBody",
"filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}");
jasonCallback.put("callbackBodyType", "application/x-www-form-urlencoded");
String base64CallbackBody = BinaryUtil.toBase64String(jasonCallback.toString().getBytes());
respMap.put("callback", base64CallbackBody);
return respMap;
} catch (Exception e) {
// Assert.fail(e.getMessage());
System.out.println(e.getMessage());
} finally {
ossClient.shutdown();
}
return null;
}
}

image-20210726230653881

同时配置网关

http://localhost:88/api/thirdparty/oss/policy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
spring:
cloud:
gateway:
#按顺序依次向下拦截
routes:
#http://localhost:88/?url=baidu
- id: test_route
uri: https://www.baidu.com
predicates:
- Query=url,baidu
#http://localhost:88/?url=qq
- id: qq_route
uri: https://www.qq.com
predicates:
- Query=url,qq
- id: product_route
uri: lb://gulimall-product
predicates:
- Path=/api/product/**
filters:
- RewritePath=/api/(?<segment>.*),/$\{segment}
- id: third_party_route
uri: lb://gulimall-third-party
predicates:
- Path=/api/thirdparty/**
filters:
- RewritePath=/api/thirdparty/(?<segment>.*),/$\{segment}
- id: admin_route
uri: lb://renren-fast
predicates:
- Path=/api/**
filters:
- RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}

image-20210726231356438

前端Upload组件

拿到外网访问的Bucket域名

image-20210727203950435

修改成action的地址

image-20210727204103136

image-20210727205519052

向阿里云OSS地发送请求的时候,出现了跨域问题。

image-20210727205702259

配置跨域设置。

image-20210727205803900

此时就解决了跨域问题。

表格自定义显示logo图片https://element.eleme.io/#/zh-CN/component/table

image-20210727211330246

image-20210727211508500

表单校验

image-20210728205357119

https://element.eleme.io/#/zh-CN/component/form

Form 组件提供了表单验证的功能,只需要通过 rules 属性传入约定的验证规则,并将 Form-Item 的 prop 属性设置为需校验的字段名即可。校验规则参见 async-validator

JSR303校验

image-20210728223819878

javax.validation.constraints包下注解进行校验。

控制器参数使用@Valid注解标注,迭代注解@Validated,获取校验结果BindingResult

image-20210728224840707

统一异常处理

使用@ControllerAdvice注解这是一个增强的 Controller。使用这个 Controller ,可以实现三个方面的功能:

  1. 全局异常处理
  2. 全局数据绑定
  3. 全局数据预处理

@ExceptionHandler异常处理方法,处理指定的异常类型。

@RestControllerAdvice=@RequestBody+@ControllerAdvice

image-20210728232827128

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 统一异常处理
*
* @author kylin
*/
//@RequestBody
//@ControllerAdvice(basePackages = "com.kylin.gulimall.product.controller")
@Slf4j
@RestControllerAdvice(basePackages = "com.kylin.gulimall.product.controller")
public class MallExceptionControllerAdvice {


/**
* 处理JSR303校验抛出的异常
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handlerValidException(MethodArgumentNotValidException e) {
log.error("数据校验出现问题{},异常类型{}", e.getMessage(), e.getClass());
BindingResult bindingResult = e.getBindingResult();
Map<String, String> map = new HashMap<>(16);
bindingResult.getFieldErrors().forEach(fieldError -> {
map.put(fieldError.getField(), fieldError.getDefaultMessage());
});
return R.error(BizCodeEnum.VALID_EXCEPTION.getCode(), BizCodeEnum.VALID_EXCEPTION.getMsg()).put("data", map);
}

/**
* 处理所有异常
*/
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable) {
return R.error(BizCodeEnum.UNKNOWN_EXCEPTION.getCode(), BizCodeEnum.UNKNOWN_EXCEPTION.getMsg());
}
}

定义错误代码枚举类

image-20210728232921327

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
* 错误码和错误信息定义类
* 1. 错误码定义规则为5为数字
* 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常
* 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
* 错误码列表:
* 10: 通用
* 001:参数格式校验
* 002: 短信验证码频率太高
* 11: 商品
* 12: 订单
* 13: 购物车
* 14: 物流
* 15: 用户
* 21: 库存
*
* @author kylin
*/

public enum BizCodeEnum {
UNKNOWN_EXCEPTION(10000, "系统未知异常"),

VALID_EXCEPTION(10001, "参数格式校验失败"),

SMS_CODE_EXCEPTION(10002, "验证码获取频率太高,稍后再试"),

TO_MANY_REQUEST(10003, "请求流量过大"),

SMS_SEND_CODE_EXCEPTION(10403, "短信发送失败"),

USER_EXIST_EXCEPTION(15001, "用户已经存在"),

PHONE_EXIST_EXCEPTION(15002, "手机号已经存在"),

LOGINACTT_PASSWORD_ERROR(15003, "账号或密码错误"),

SOCIALUSER_LOGIN_ERROR(15004, "社交账号登录失败"),

NOT_STOCK_EXCEPTION(21000, "商品库存不足"),

PRODUCT_UP_EXCEPTION(11000, "商品上架异常");

private int code;

private String msg;

BizCodeEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}

public int getCode() {
return code;
}

public String getMsg() {
return msg;
}
}

分组校验

image-20210729141842300

首先定义两个分组

image-20210729141932693

校验注解中的group属性指定所属分组

image-20210729141956346

参数中使用@Validated注解给value赋值,表明该参数所属分组。

image-20210729142110247

group属性没指定分组,个人感觉所属空分组。每个参数都指定不同分组的话,非常冗余。

自定义校验注解

image-20210729160928985

可以参考别人写好的注解

image-20210729143629347

直接将其复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 自定义校验注解
*
* @author kylin
*/

@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {})
public @interface ListValue {

String message() default "{com.kylin.common.valid.ListValue.message}";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

int[] value() default {};
}

导入依赖

1
2
3
4
5
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>2.0.1</version>
</dependency>

自定义注解的默认message消息会从ValidationMessages.properties中读取message名称属性的值。也就是这里配置的com.kylin.common.valid.ListValue.message

image-20210729144725438

接着编写自定义校验器实现ConstraintValidator接口

image-20210729151043772

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 自定义参数校验器
*
* @author kylin
*/
public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> {

private Set<Integer> set = new HashSet<>();

/**
* 初始化方法
*
* @param constraintAnnotation 注解
*/
@Override
public void initialize(ListValue constraintAnnotation) {
int[] value = constraintAnnotation.value();
for (int i : value) {
set.add(i);
}
}

/**
* 判断是否校验成功
*
* @param value 需要校验的值
* @param context
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}

在自定义校验注解中@Constraint(validatedBy = {})指定校验器。@Constraint(validatedBy = {ListValueConstraintValidator.class})

Elasticsearch

安装

docker安装elasticsearch

docker pull elasticsearch:7.4.2

  1. 创建目录,挂载文件。授权
  2. 配置elasticsearch,允许所有的ip访问
  3. docker允许命令 容器名为elasticsearch 开发9200、9300端口。当前以当节点允许,配置Java内存,-v挂载,-d运行images
1
2
3
4
5
6
7
8
9
10
11
12
13
mkdir -p /mydata/elasticsearch/config
mkdir -p /mydata/elasticsearch/data
chmod -R 777 /mydata/elasticsearch/

echo "http.host: 0.0.0.0">>/mydata/elasticsearch/config/elasticsearch.yml

docker run --name elasticsearch -p 9200:9200 -p 9300:9300 --privileged=true \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms64m -Xmx128m" \
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
-v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.4.2

free -m查看内存命令

docker安装kibana

docker pull kibana:7.4.2

1
docker run --name kibana -e ELASTICSEARCH_HOSTS=http://192.168.56.10:9200 -p 5601:5601 -d kibana:7.4.2

入门操作

新增

image-20210806171354295

PUT和POST都可以

  1. POST新增。如果不指定id,会自动生成id。指定id就会修改这个数据,并新增版本号。
  2. PUT可以新增可以修改。PUT必须指定id。由于PUT需要指定id,我们一般用来做修改操作,不指定id会报错。

查询文档

image-20210806173046325

_seq_no乐观锁?if_seq_no=0&if_primary_term=1

更新文档

image-20210806174603841

删除文档或索引

image-20210806174908113

批量API

image-20210806175055903

进阶操作

image-20210806191136797

检索信息

image-20210806191235375

image-20210806191741143

Query DSL

https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html

IK分词器

安装

vagrant 使用root账号连接开启过程

image-20210806193439014

安装wget

yum install wget

1
2
3
4
5
6
7
wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip

unzip elasticsearch-analysis-ik-7.4.2.zip

rm -rf *.zip

mv ./* ik

可以进入到容器里面

docker exec -it elasticsearch /bin/bash

进入到了cd /bin/

运行elasticsearch-plugin list

image-20210806200318664

安装成功。退出容器,重启docker restart elasticserach

image-20210806201224934

词库

image-20210806201510643

可视化插件

https://blog.csdn.net/ljcgit/article/details/103856634

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 显示文件
ls
结果如下:
LICENSE.txt README.textile config lib modules
NOTICE.txt bin data logs plugins

# 进入配置文件夹
cd config

# 显示文件
ls
结果如下:
elasticsearch.keystore ingest-geoip log4j2.properties roles.yml users_roles
elasticsearch.yml jvm.options role_mapping.yml users

# 修改配置文件
vi elasticsearch.yml

# 加入跨域配置
http.cors.enabled: true
http.cors.allow-origin: "*"
1
2
3
docker pull mobz/elasticsearch-head:5

docker run -d --name es_admin -p 9100:9100 mobz/elasticsearch-head:5

Nginx

image-20210812104116061

image-20210812143954097

image-20210812144004182

image-20210812144030106

image-20210812144044282

image-20210812150058383

include /etc/nginx/conf.d/*.conf;

这里会将这个目录下的配置文件一并包括生效,所以要注意这里配置文件所配置的信息。

负载均衡到网关

首先配置本机的host输入gulimall.com访问虚拟机IP地址80端口

image-20210812161751339

虚拟机的80端口运行了Nginx,docker中的/etc/nginx/conf.d/gulimall.conf中配置了server_name gulimall.com当访问的时候会携带host参数,代理地址为http://gulimall

image-20210812162226922

而Nginx的配置文件nginx.conf中配置了gulimall的负载均衡转发到了我们的网关。

image-20210812162446209

我们的网关则把请求转发到了我们的gulimall-product服务中。

image-20210812162556180

JMeter

image-20210812193120140

nginx测试

http://192.168.56.10/发送测试

image-20210812171704547

image-20210812171651589

使用docker stats实时查看容器运行情况

当我点击运行测试的时候,我们能看见

image-20210812171816759

所以nginx是CPU密集型

image-20210812172126327

这里出现的异常是我们线程组给stop导致的,从而请求失败。

image-20210812172255668

  • 吞吐量/s:8086.8
  • 90%响应时间:7
  • 99%相应时间:18

网关测试

http://localhost:88/发送测试

image-20210812192450252

image-20210812195027308

  • 吞吐量/s:26274.7
  • 90%响应时间:3
  • 99%相应时间:9

简单服务测试

访问http://localhost:10000/hello

image-20210812194718448

image-20210812194739087

  • 吞吐量/s:45000
  • 90%响应时间:2
  • 99%相应时间:4

网关+简单服务测试

image-20210812194923054

访问测试http://localhost:88/hello

image-20210812195247406

  • 吞吐量/s:6409.0
  • 90%响应时间:3
  • 99%相应时间:13

两个一加起来吞吐量大量减少。

nginx+网关+简单服务测试

访问http://gulimall.com/hello

image-20210812195846772

image-20210812200013128

  • 吞吐量/s:2279.2
  • 90%响应时间:34
  • 99%相应时间:52

中间件越多,性能损失越多,大多损失在网络交互了。

首页一级菜单渲染

访问测试http://localhost:10000/

image-20210812200642108

image-20210812200700155

  • 吞吐量/s:1831.5(db、thymeleaf)

  • 90%响应时间:38

  • 99%相应时间:61

tymeleaf开启缓存

image-20210812203212229

  • 吞吐量/s:2632.7
  • 90%响应时间:27
  • 99%相应时间:50

日志打印级别提高为erro

image-20210812203618169

  • 吞吐量/s:3125.5
  • 90%响应时间:23
  • 99%相应时间:42

字段设置为索引

首页三级分类数据获取

访问测试http://localhost:10000/index/catalog.json

image-20210812201419596

image-20210812201433004

  • 吞吐量/s:323.2(db)
  • 90%响应时间:176
  • 99%相应时间:266

首页全量数据获取

访问测试http://localhost:10000/

image-20210812202025084

高级设置中勾选从HTML文件获取所有内容的资源

image-20210812202119001

image-20210812202309018

  • 吞吐量/s:62(静态资源)
  • 90%响应时间:1122
  • 99%相应时间:1510

异常是因为有些文件报404

Nginx动静分离优化

image-20210812204222971

image-20210812205808354

将静态目录复制到虚拟机/mydata/nginx/html/static的目录下

进入docker容器,编写配置/etc/nginx/conf.d/gulimall.conf文件

image-20210812210015603

1
location /static/ {	root /usr/share/nginx/html;}

其中容器内目录/usr/share/nginx/html目录挂载到了/mydata/nginx/html/也就是我们存放静态资源的地方。

访问http://gulimall.com进行测试

image-20210812210555606

缓存

image-20210813090910495

image-20210813091002888

image-20210813090933599

redis

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

image-20210813091749522

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class StringRedisTemplate extends RedisTemplate<String, String> {

/**
* Constructs a new <code>StringRedisTemplate</code> instance. {@link #setConnectionFactory(RedisConnectionFactory)}
* and {@link #afterPropertiesSet()} still need to be called.
*/
public StringRedisTemplate() {
setKeySerializer(RedisSerializer.string());
setValueSerializer(RedisSerializer.string());
setHashKeySerializer(RedisSerializer.string());
setHashValueSerializer(RedisSerializer.string());
}

/**
* Constructs a new <code>StringRedisTemplate</code> instance ready to be used.
*
* @param connectionFactory connection factory for creating new connections
*/
public StringRedisTemplate(RedisConnectionFactory connectionFactory) {
this();
setConnectionFactory(connectionFactory);
afterPropertiesSet();
}

protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
return new DefaultStringRedisConnection(connection);
}
}

StringRedisTemplate是专门操作string类型数据。

image-20210813093156229

使用redis将数据存入到缓存中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public Map<String, List<Catelog2Vo>> getCatelogJson() {
//1.加入缓存逻辑,缓存中存的数据就是json字符串
String catalogJson = redisTemplate.opsForValue().get("catalogJson");
if (StringUtils.isEmpty(catalogJson)) {
//2.缓存中没有,查询数据库
Map<String, List<Catelog2Vo>> catalogJsonFromDB = getCatalogJsonFromDB();
//3.查询到的数据再放入缓存,将对象转为json放在缓存中
String s = JSON.toJSONString(catalogJsonFromDB);
redisTemplate.opsForValue().set("catalogJson", s);
return catalogJsonFromDB;
}
//Json字符串-->对象
Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
});

return result;
}

此时压测访问首页三级分类数据获取http://localhost:10000/index/catalog.json(日志关闭,thymeleaf缓存开启,索引创建)

image-20210813094112302

  • 吞吐量/s:1351
  • 90%响应时间:44
  • 99%相应时间:99

出现堆外内存溢出:OutOfDirectMemoryError(lettuce5.2.1暂时没出现,5.1.8出现问题)

  • springboot2.0以后默认使用lettuce作为操作redis的客户端。它使用netty进行网络通信。
  • lettuce的bug导致netty堆外内存溢出

image-20210813101516117

缓存穿透

image-20210813104402637

缓存雪崩

image-20210813104423528

缓存击穿

image-20210813104510337

分布式锁

image-20210813111419751

image-20210813114326039

结果放入缓存这步操作应该放在锁内,否则会因为网络通信延迟导致又查询了一次数据库。

image-20210813143732232

image-20210813143747795

image-20210813145120148

image-20210813145511448

image-20210813145538707

image-20210813145602560

Redisson

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

https://github.com/redisson/redisson/wiki/Table-of-Content

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 所有对Redisson的使用都是通过RedissonClient对象
*
* @return
*/
@Bean(destroyMethod = "shutdown")
public RedissonClient redisson() {
//1.创建配置
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.56.10:6379");
//2.根据Config创建出RedissonClient示例
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}

缓存数据一致性

image-20210813194300518

image-20210813194310763

image-20210813194328400

image-20210813194344403

SpringCache

image-20210813195753380

image-20210814102652593

引入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

image-20210814101005106

1
2
3
4
5
6
7
8
9
10
@Cacheable(value = {"category"}, key = "#root.method.name", sync = true)
@Override
public List<CategoryEntity> getLevel1Categories() {
System.out.println("get Level 1 Categories........");
long l = System.currentTimeMillis();
List<CategoryEntity> categoryEntities = this.baseMapper.selectList(
new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
System.out.println("消耗时间:" + (System.currentTimeMillis() - l));
return categoryEntities;
}

image-20210814110712397

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@EnableConfigurationProperties(CacheProperties.class)
@Configuration
@EnableCaching
public class MyCacheConfig {

/**
* 配置文件的配置没有用上
* 1. 原来和配置文件绑定的配置类为:@ConfigurationProperties(prefix = "spring.cache")
* public class CacheProperties
* <p>
* 2. 要让他生效,要加上 @EnableConfigurationProperties(CacheProperties.class)
*/
@Bean
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {

RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();

//序列化
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

CacheProperties.Redis redisProperties = cacheProperties.getRedis();
//将配置文件中所有的配置都生效
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}

}

相关配置文件

1
2
3
4
5
6
7
8
9
10
spring.cache.type=redis
#毫秒
spring.cache.redis.time-to-live=3600000
#如果指定了前缀就用我们指定的前缀,如果没有就默认使用缓存的名字作为前缀
spring.cache.redis.key-prefix=CACHE_
#是否使用key前缀
spring.cache.redis.use-key-prefix=true
#是否缓存空值,防止缓存穿透
spring.cache.redis.cache-null-values=true

异步

image-20210817144353440

线程池

JUC之线程池 | Kylin (codekylin.cn)

CompletableFuture

CompletableFuture类似前端的promise

image-20210818092304976

image-20210818094039521

image-20210818094728948

image-20210818095834087

image-20210818111217185

image-20210818152255093

image-20210818153559996

image-20210818153618868