1 Star 1 Fork 0

lijiao / gulimall

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
README.md 19.60 KB
一键复制 编辑 原始数据 按行查看 历史
lijiao 提交于 2021-04-25 16:10 . 设计模式模块

介绍

尚硅谷的谷粒商城项目,这个项目集成了市面上流行的微服务架构和解决方案。

vs code配置

1、下载vs code,傻瓜式安装

2、插件安装

Auto Close Tag:自动开闭标签

Auto Rename Tag

Chinese (Simplifi..):简体中文包

ESLint:ES语法检查

HTML CSS Support :

HTML Sinppets

JavaScript(ES6) co...

Live Server

Open in browser

Vetur

vue环境安装

1、安装node,配置环境变量 2、安装npm 3、安装cnpm 4、cnpm i -g webpack 5、cnpm i -g @vue-cli

node-sass4.9.0安装失败

npm i node-sass --sass_binary_site=https://npm.taobao.org/mirrors/node-sass/

注:这么做得原理就是先单独从淘宝镜像吧nod-sass下载下来,然后再进行编译,因为这句命令好像是不成功的,(npm config set registry http://registry.npm.taobao.org/),默认从github下载,导致报错的 如果之前安装失败的。先清理 缓存 清理缓存:npm rebuild node-sass npm uninstall node-sass

Mybatis逻辑删除

官网:https://baomidou.com/guide/logic-delete.html

1、配置全局逻辑删除配置(可以不用配置,直接使用步骤2中的注解来标注逻辑删除的字段和字段值,注解的优先级大于全局配置)

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: showStatus  # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

2、实体类字段上加上@TableLogic注解

// value表示逻辑未删除的数据,delval表示逻辑删除
@TableLogic(value = "1",delval = "0")
private Integer showStatus;

vscode前端代码模板封装

1、文件 —— 首选项 —— 用户片段 —— 新建全局代码片段文件

2、吧你的代码片段文件名写进去,生成一个新文件,里面填写你的代码片段模板

{
	"http-get请求": {
		"prefix": "httpget",
		"body": [
		"this.\\$http({",
		"url: this.\\$http.adornUrl(''),",
		"method: 'get',",
		"params: this.\\$http.adornParams({})",
		"}).then(({ data }) => {",
		"})"],
		"description": "httpGET请求"
	},
	"http-post请求": {
		"prefix": "httppost",
		"body": [
		"this.\\$http({",
		"url: this.\\$http.adornUrl(''),",
		"method: 'post',",
		"data: this.\\$http.adornData(data, false)",
		"}).then(({ data }) => { });" ],
		"description": "httpPOST请求"
	}
}

关闭ESLink语法检查

1、打开webpack.base.conf.js文件

2、注释掉createLintingRule方法中的语句

3、重新启动前端项目

人人代码生成器

1、启动访问地址:http://localhost/#generator.html

OSS文件上传

1、注册阿里云,然后开通OSS(不用钱,按量计费)

2、打开oss使用API手册(选择对应的语言JDK)

https://help.aliyun.com/document_detail/32009.html?spm=a2c4g.11186623.6.934.485514a0fS7JrL

3、开通阿里云的RAM权限控制,然后创建一个子账号,创建的子账号选择的访问方式,选择:编程访问启用 AccessKey ID 和 AccessKey Secret,支持通过 API 或其他开发工具访问

4、给创建的子账号添加oss权限:AliyunOSSFullAccess

5、使用步骤二中的案例添加依赖之后测试(上传文件流)

// Endpoint以杭州为例,其它Region请按实际情况填写。
String endpoint = "http://oss-cn-hangzhou.aliyuncs.com";
// 云账号AccessKey有所有API访问权限,建议遵循阿里云安全最佳实践,创建并使用RAM子账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建。
String accessKeyId = "<yourAccessKeyId>";
String accessKeySecret = "<yourAccessKeySecret>";

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

// 上传文件流。
InputStream inputStream = new FileInputStream("<yourlocalFile>");
ossClient.putObject("<yourBucketName>", "<yourObjectName>", inputStream);

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

spring cloud alibaba oss集成

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

问题:官方找了半天没找到最新的oss集成文档,只有springboot的集成文档,真心为阿里的开源感到心塞,就这么敷衍的么

1、添加依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alicloud-oss</artifactId>
</dependency>

2、添加配置

spring:
  cloud:
    alicloud:
      access-key: LTAI4G8fDyV7qF9MxaEu7rEr
      secret-key: IzAF0didOy8BUsFKaBroeyp4mixRVw
      oss:
        endpoint: oss-cn-shenzhen.aliyuncs.com

注意:必须放在application.yml或者application.properties配置文件内

3、java代码,跟上面的oss文件上传差不多

@Autowired
private OSSClient ossClient;

 @Test
public void testAliossUpload() throws FileNotFoundException {
    InputStream inputStream = new FileInputStream("D:\\DevelopSoft\\WorkSpaceJava\\gulimall\\document\\pics\\7ae0120ec27dc3a7.jpg");
    ossClient.putObject("gulimall-lijiao", "7ae0120ec27dc3a7.jpg", inputStream);
    // 关闭OSSClient。
    ossClient.shutdown();
    System.out.println("上传成功。。。。");
}

4、使用服务端签名直传方式

https://help.aliyun.com/document_detail/31926.html?spm=a2c4g.11186623.6.1739.27cc7d9cbJFMys

springboot单元测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class Test{
    // class必须是public
}

问题:java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test

解决:单元测试的测试类一定要和启动类在同一个根目录下

JSR303数据校验

JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation,Hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。

Bean Validation 中内置的 constraint

img

Hibernate Validator 附加的 constraint

img

日期校验

在上面没有日期校验注解,so自己定义注解校验:https://blog.csdn.net/mfkarj/article/details/105228132

@DateTimeFormat(pattern = "yyyy-MM-dd") Date startTime

@JsonFormat 注解,jackson在序列化时间时是按照国际标准时间GMT进行格式化的,而在国内默认时区使用的是CST时区,两者相差8小时, @JsonFormat(timezone = "GMT+8", pattern ="yyyy-MM-dd HH:mm:ss")

使用步骤

  • 在实体类上添加如上图中注解

    @NotBlank(message = "品牌名不能为空")
    private String name;
  • 开启注解校验@Valid

    @RequestMapping("/save")
    public R save(@Valid @RequestBody BrandEntity brand, BindingResult result) {
        if (result.hasErrors()) {
                Map<String, String> errorMap = new HashMap<>();
                result.getFieldErrors().forEach((item)->{
                    // 获取fieldError的错误提示
                    String message = item.getDefaultMessage();
                    // 获取错误属性的名字
                    String fieldName = item.getField();
                    errorMap.put(fieldName, message);
                });
                return R.error(400, "提交的数据不合法").put("data", errorMap);
            } else {
                brandService.save(brand);
            }
            return R.ok();
    }
  • 通过BindingResult result对象接收校验结果,并重写响应结果

    result.hasErrors():判断校验是否通过,通过为false

    result.getFieldErrors():获取所有的错误校验信息

统一异常处理

使用方式跟JSR303数据校验差不多,只不过使用切面将所有响应返回的异常获取统一处理

  • 在实体类上添加如上图中注解
  • 开启注解校验@Valid,不需要使用BindingResult result对象接收校验结果,直接让异常抛出
@RequestMapping("/save")public R save(@Valid @RequestBody BrandEntity brand){
	 brandService.save(brand);
	 return R.ok();
}
  • @ControllerAdvice+@ExceptionHandler+@ResponseBody或者@RestControllerAdvice+@ExceptionHandler

    @Slf4j
    @RestControllerAdvice(basePackages = "top.dark.product.controller")
    public class GlobalException {
    
        @ExceptionHandler(value = MethodArgumentNotValidException.class)
        public R handleVaildException(MethodArgumentNotValidException e) {
            log.error("数据校验异常:{},异常类型:{}", e.getMessage(), e.getClass());
            BindingResult bindingResult = e.getBindingResult();
            Map<String, String> errorMap = new HashMap<>();
            bindingResult.getFieldErrors().forEach((item)->{
                errorMap.put(item.getField(), item.getDefaultMessage());
            });
            return R.error(BizCodeEnum.VALID_EXCEPTION.getCode(),BizCodeEnum.VALID_EXCEPTION.getMsg()).put("data", errorMap);
        }
    
        @ExceptionHandler(value = Exception.class)
        public R handleException(Exception e) {
            return R.error(BizCodeEnum.UNKNOW_EXCEPTION.getCode(), BizCodeEnum.UNKNOW_EXCEPTION.getMsg());
        }
    }
  • 系统错误码

    /***
    * 错误码和错误信息定义类
    * 1. 错误码定义规则为 5 为数字
    * 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 000:系统未知
    异常
    * 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
    * 错误码列表:
    * 10: 通用
    * 001:参数格式校验
    * 11: 商品
    * 12: 订单
    * 13: 购物车
    * 14: 物流
    */
    public enum BizCodeEnum {
        UNKNOW_EXCEPTION(10000,"系统未知异常"),
        VALID_EXCEPTION(10001,"参数格式校验失败"),
        ;
    
        private Integer code;
        private String msg;
    
        BizCodeEnum(Integer code, String msg) {
            this.code = code;
            this.msg = msg;
        }
    
        public Integer getCode() {
            return code;
        }
    
        public String getMsg() {
            return msg;
        }
    }

校验分组

背景:更新和新增数据时,部分的数据校验是不相同的,因此需要根据不同的需求分组来进行不同的校验

  • 创建分组接口

    public interface AddGroup {
    }
    public interface UpdateGroup {
    }
    public interface UpdateStatusGroup {
    }
  • 为需要校验的实体字段进行不同分组校验标识

    @NotBlank(message = "首字母不能为空",groups = {AddGroup.class})
    @Pattern(regexp = "^[a-zA-Z]$",message = "首字母必须是a-z或A-Z字符",groups = {AddGroup.class, UpdateGroup.class})
    private String firstLetter;
  • 使用@Validated指定分组校验

    @RequestMapping("/save")
    public R save(@Validated(value = {AddGroup.class}) @RequestBody BrandEntity brand) {
        brandService.save(brand);
        return R.ok();
    }

注意:如果指定了分组标识进行分组校验,字段上没有分配分组标识的字段则不会被校验

自定义校验

1、自定义校验注解

@Documented
@Constraint(validatedBy = {ListValueConstraintValidator.class})
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
    String message() default "{top.dark.common.exception.ListValue.message}";

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

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

    int[] vals() default { };
}

2、自定义校验器

public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {

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

    // 初始化方法
    @Override
    public void initialize(ListValue constraintAnnotation) {
        int[] vals = constraintAnnotation.vals();
        if (vals != null && vals.length > 0) {
            Arrays.stream(vals).forEach((item)->{
                set.add(item);
            });
        }
    }

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

3、关联自定义校验器和自定义校验注解

@Constraint(validatedBy = {ListValueConstraintValidator.class})

4、message默认信息都是从ValidationMessages.properties文件中获取的,我们也能自定义默认message值。创建一个新的ValidationMessages.properties文件

top.dark.common.exception.ListValue.message=必须提交指定的值

父子组件传递数据

子组件给父组件传递数据,事件机制:子组件给父组件发送一个事件,携带上数据

1、子组件创建一个触发事件,事件里面通过this.$emit()函数将数据传递给父组件

handleNodeClick(data,node,component){
    console.log("子组件数据:",data,node,component);
    this.$emit('tree-attr-group', data,node,component);
}

this.$emit("事件名",...不定参数数据)

2、父组件在引入子组件标签中使用步骤1中的定义的事件获取传递过来的数据

<Category @tree-attr-group="treeAttrGroup"></Category>

import Category from "../common/category";
components: { Category,AddOrUpdate },
    
// 树型子组件数据传递给父组件
treeAttrGroup(data,node,component){
    console.log("父组件获取到子组件数据:",data,node,component);
    console.log("节点id:",data.catId);
},

json小技巧

标注该字段不是数据库表实体字段
@TableField(exist = false)

标注该字段为空时不需要将其转换为json属性传输出去
@JsonInclude(value = JsonInclude.Include.NON_EMPTY)

忽略指定属性
@JSONField(serialize = false)

接口文档地址

https://easydoc.xyz/s/78237135/ZUqEdvA4/hKJTcbfd

冗余字段数据一致性

数据库设计的时候有些中间表(关联表)会设计一些冗余字段,在更新这些主表中的字段时,如果没有同步更新中间表中的冗余字段,则会导致数据的不一致性,因此在进行更新主表中的信息数据时需要将中间表中的冗余字段也同步进行更新

Mybatis Plus分页插件

官方:https://baomidou.com/guide/page.html

@Configuration
@EnableTransactionManagement // 开启事务使用
@MapperScan("top.dark.product.dao")
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }

    /**
     * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题
     */
    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return configuration -> configuration.setUseDeprecatedExecutor(false);
    }
}

可以在需要使用事务控制的代码方法上添加@Transactional注解来启动事务

MyBatis-Plus 分页实体Page类说明

public class Page<T> implements IPage<T> {
    private static final long serialVersionUID = 8545996863226528798L;
    protected List<T> records;
    protected long total;
    protected long size;
    protected long current;
    protected List<OrderItem> orders;
    protected boolean optimizeCountSql;
    protected boolean isSearchCount;
    protected boolean hitCount;
    protected String countId;
    protected Long maxLimit;

records 查询数据 total 数据总条数 size 每页显示条数,默认 10 current 当前页,默认1 orders 排序字段信息 optimizeCountSql 自动优化 COUNT SQL,默认true isSearchCount 是否进行 count 查询,默认true hitCount 是否命中count缓存,默认false

VUE前端pubsub-js插件

vue ReferenceError: PubSub is not defined 解决方案

npm install --save pubsub-js

全局使用

import PubSub from 'pubsub-js'
Vue.prototype.PubSub = PubSub   //组件发布订阅消息

这个插件总共就两个方法:订阅和发布,其原理等同于:绑定事件和触发事件

发布:第一个参数为自定义事件名(与订阅方法保持一致),第二个参数为你需要传递给订阅者的参数

 this.PubSub.publish("catPath",v);

订阅:第一个参数没啥用,第二个参数是发布方法传递过来的参数

this.catPathSub = PubSub.subscribe("catPath", (msg, val) => {
    this.spu.catalogId = val[val.length - 1];
});

Mybatis Plus主键自增

对于主键自增,mybatis-plus里最直接的有两种方法,一种是INPUT ,一种是AUTO.

如果设置的是在数据库自增,就需要在pojo类中标注。@TableId(type=IdType.AUTO)

还可以直接在配置文件中进行全局配置,但是如果不想要主键自增的时候就需要在主键上添加上面的注解,标注不用主键自增

class feign.RetryableException

ribbon调用超时,在配置文件中配置超时时间

http请求报错超时,feign的调用分两层,ribbon的调用和hystrix的调用,高版本的hystrix默认是关闭的,所以设置ribbon即可

#请求处理的超时时间
ribbon.ReadTimeout: 120000
#请求连接的超时时间
ribbon.ConnectTimeout: 30000
 
#feign.hystrix.enabled: true
#hystrix 熔断机制
#hystrix:
#  shareSecurityContext: true
#  command:
#    default:
#      circuitBreaker:
#        sleepWindowInMilliseconds: 100000
#        forceClosed: true
#      execution:
#        isolation:
#          thread:
#            timeoutInMilliseconds: 600000

Docker安装ES

下载:

docker pull elasticsearch:7.4.2 存储和检索数据
docker pull kibana:7.4.2 可视化检索数据

创建文件与目录:

mkdir -p /mydata/elasticsearch/config
mkdir -p /mydata/elasticsearch/data
mkdir -p /mydata/elasticsearch/plugins
echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml
chmod -R 777 /mydata/elasticsearch/ 保证权限

启动:

docker run --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms64m -Xmx512m" -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

问题:docker: invalid reference format: repository name must be lowercase.

启动的过程中有格式不对

kibana

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

http://192.168.136.138:9200 一定改为自己虚拟机的地址

ES基本概念

测试数据:https://github.com/elastic/elasticsearch/blob/master/docs/src/test/resources/accounts.json

官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html

索引(index)——对应数据库概念

类型(type)——对应表概念

文档(document)——对应记录概念

springboot中默认是给elasticsearch定义过版本,可能会给es的客户端依赖造成版本不一致性

解决方案:在springboot中的依赖里面找到版本设定的值,然后在自定义的pom文件中自定义版本进行覆盖

gulimall高级版

redis内存泄漏问题

背景:压力不间断测试,redis报堆外内存溢出

2021-03-09 22:43:56.228  WARN 16220 --- [ioEventLoop-4-1] io.lettuce.core.protocol.CommandHandler  : null Unexpected exception during request: io.netty.util.internal.OutOfDirectMemoryError: failed to allocate 125829120 byte(s) of direct memory (used: 138412032, max: 259522560)
Java
1
https://gitee.com/lijiao163/gulimall.git
git@gitee.com:lijiao163/gulimall.git
lijiao163
gulimall
gulimall
master

搜索帮助