抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

项目构建

<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

<spring-boot.version>2.6.13</spring-boot.version>
<spring-cloud.version>2021.0.5</spring-cloud.version>
<spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>
<aliyun-spring-boot.version>1.0.0</aliyun-spring-boot.version>
</properties>


<dependencyManagement>
<dependencies>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>aliyun-spring-boot-dependencies</artifactId>
<version>${aliyun-spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

</dependencies>
</dependencyManagement>

OOOO

  1. PO(persistant object):持久化对象

    po 就是对应数据库中某个表的一条记录,多个记录可以用 po 的集合

  2. DO(domain object):领域对象

    就是从现实世界抽象出来的有形或无形的业务实体

  3. TO(Transfer object):数据传输对象

    不同的应用程序之间传输的对象

  4. DTO(Data Transfer object):数据传输对象

    这个概念来源于 J2EE 的设计模式,原来的目的是为了 EJB 的分布式应用提供粗粒度的 数据实体,以减少分布式调用的次数,从而提高分布式调用的性能和降低网络负载,但在这 里,泛指用于展示层与服务层之间的数据传输对象。

  5. VO(Value object):值对象

    通常用于业务层之间的数据传递,和 PO 一样也是仅仅包含数据而已。但应是抽象出的业务对象 , 可以和表对应 , 也可以不 , 这根据业务的需要 。用 new 关键字创建,由 GC 回收的。

    View object:视图对象; 接受页面传递来的数据,封装对象将业务处理完成的对象,封装成页面要用的数据

  6. BO(Business object):业务对象

    从业务模型的角度看 , 见 UML 元件领域模型中的领域对象。封装业务逻辑的 java 对 象 , 通过调用 DAO 方法 , 结合 PO,VO 进行业务操作。business object: 业务对象 主要作 用是把业务逻辑封装为一个对象。这个对象可以包括一个或多个其它的对象。 比如一个简 历,有教育经历、工作经历、社会关系等等。 我们可以把教育经历对应一个 PO ,工作经 历对应一个 PO ,社会关系对应一个 PO 。 建立一个对应简历的 BO 对象处理简历,每 个 BO 包含这些 PO 。 这样处理业务逻辑时,我们就可以针对 BO 去处理。

Nacos注册中心

  1. 引入依赖

    <!--        nacos-->
    <!-- 根据本地安装的nacos1.4.1,使用 2.2.5.RELEASE 版本-->
    <!-- 排除 netflix-ribbon ,加入 loadbalancer-->
    <!-- spring cloud 从2021.0.5起,默认不在使用 bootstrap,需要导入-->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <version>2.2.5.RELEASE</version>
    <exclusions>
    <exclusion>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </exclusion>
    </exclusions>
    </dependency>
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    <version>2.2.5.RELEASE</version>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>

  2. 配置文件

    #    将服务注册到Nacos
    spring:
    application:
    # 服务名称
    name: GuliMall-GateWay
    cloud:
    nacos:
    # 服务地址:端口
    server-addr: 127.0.0.1:8848
  3. 开启服务注册发现

    @EnableDiscoveryClient

Nacos配置中心

  1. 引入依赖

    <!--        nacos-->
    <!-- 根据本地安装的nacos1.4.1,使用 2.2.5.RELEASE 版本-->
    <!-- 排除 netflix-ribbon ,加入 loadbalancer-->
    <!-- spring cloud 从2021.0.5起,默认不在使用 bootstrap,需要导入-->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <version>2.2.5.RELEASE</version>
    <exclusions>
    <exclusion>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </exclusion>
    </exclusions>
    </dependency>
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    <version>2.2.5.RELEASE</version>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>

  2. 添加配置文件

    bootsrtap.yaml,优先级高于 application.properties

    spring:
    application:
    # 服务名称
    name: GuliMall-Coupon
    cloud:
    nacos:
    # 配置中心地址
    server-addr: 127.0.0.1:8848
    config:
    # 配置中心的文件后缀
    file-extension: yaml
    # 配置文件所属组
    group: dev
    # group: prop
    # 配置命名空间(习惯于一个微服务开一个命名空间,用组名区分开发环境与上线环境等)
    namespace: 6b7a84db-c36c-4197-ba58-b624c56947f2
  3. 根据配置文件在Nacos添加配置文件 GuliMall-Coupon.yaml,group:dev

  4. 开启热部署

    // 刷新配置
    @RefreshScope

Feign

  1. 引入依赖

    <!--   feign   -->

    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <!-- 如果使用nacos作为注册中心的话,需要排除ribbon,并加入loadbalance依赖 -->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <version>2.2.5.RELEASE</version>
    <exclusions>
    <exclusion>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </exclusion>
    </exclusions>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-loadbalancer</artifactId>
    <version>2.2.2.RELEASE</version>
    </dependency>
  2. 编写配置类

    import com.heroxin.gulimall.common.utils.R;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.RequestMapping;

    // 将服务注册到spring中,指定服务提供者的名称
    @FeignClient("GuliMall-Coupon")
    public interface CouponFeignService {
    // 服务提供者的路径
    @RequestMapping("/coupon/coupon/member/list")
    R membercoupons();
    }
  3. 调用服务

    @RequestMapping("/coupons")
    public R test(){
    MemberEntity memberEntity = new MemberEntity();
    memberEntity.setNickname("hero");
    // couponFeignService 就是调用远程w
    R membercoupons = couponFeignService.membercoupons();
    return R.ok().put("member", memberEntity).put("coupons",membercoupons.get("coupons"));
    }
  4. 开启远程调用功能

    // 加在服务消费者的启动类上
    @EnableFeignClients

SpringBoot跨域处理

import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").
allowedOriginPatterns("*"). //允许跨域的域名,可以用*表示允许任何域名使用
// allowedOrigins("*"). //在Springboot2.4对应Spring5.3后在设置allowCredentials(true)的基础上不能直接使用通配符设置allowedOrigins,而是需要指定特定的URL。如果需要设置通配符,需要通过allowedOriginPatterns指定
allowedMethods("GET", "POST", "DELETE", "PUT") . //允许任何方法(post、get等)
allowedHeaders("*"). //允许任何请求头
allowCredentials(true). //带上cookie信息
exposedHeaders(HttpHeaders.SET_COOKIE).maxAge(3600L); //maxAge(3600)表明在3600秒内,不需要再发送预检验请求,可以缓存该结果
}
}

GateWay

  1. 引入依赖

    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
  2. 配置

    #    将服务注册到Nacos,需要nacos地址和服务名称
    spring:
    application:
    # 服务名称
    name: GuliMall-GateWay
    cloud:
    nacos:
    # 注册中心地址
    server-addr: 127.0.0.1:8848
    gateway:
    # 路由规则
    routes:
    # product
    - id: product_rout
    uri: lb://GuliMall-Product
    predicates:
    - Path=/api/product/**
    filters:
    - RewritePath=/api/(?<segment>/?.*), /$\{segment}

    # thirdparty
    - id: thirdparty_rout
    uri: lb://GuliMall-ThirdParty
    predicates:
    - Path=/api/thirdparty/**
    filters:
    - RewritePath=/api/thirdparty/(?<segment>/?.*), /$\{segment}

处理跨域请求

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.cors.reactive.CorsWebFilter;

@Configuration
public class CorsConfig {
/*
* 处理跨域请求
* */
@Bean
public CorsWebFilter corsWebFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

CorsConfiguration corsConfiguration = new CorsConfiguration();

//1、配置跨域
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedOriginPattern("*");

corsConfiguration.setAllowCredentials(true);

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

Mybatis-Plus 逻辑删除

  1. 配置规则(高版本可以省略)

    mybatis-plus:
    global-config:
    db-config:
    logic-delete-value: 1 # 表示已删除
    logic-not-delete-value: 0
  2. 加上逻辑删除注解

    @TableLogic
    private Integer showStatus;

JSR303

在 javax 包下

  1. 添加规则

    /**
    * 品牌名
    * jsr303 数据校验
    */
    @Not(message = "品牌名不能为空")
    private String name;
  2. 开启校验

    /**
    * 保存
    * @valild 开启校验功能
    */
    @RequestMapping("/save")
    public R save(@Valid @RequestBody BrandEntity brand){
    brandService.save(brand);
    return R.ok();
    }

VO

VO(View Object):视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。

前后端进行数据传输时,只需要实体类中的某些字段或者需要表中没有的字段时,可以设置一个VO封装满足自己需求的实体类。

Windows10 端口占用

netstat –aon |findstr “80”
TASKKILL /PID 20976 /F

ElasticSearch

引入依赖

<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.12.1</version>
<exclusions>
<exclusion>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
</exclusion>
<exclusion>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>7.12.1</version>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.12.1</version>
</dependency>

Redis

  1. 引入依赖

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
  2. 配置文件

    spring:
    redis:
    host: 192.168.196.101
    port: 6379 # 默认配置
    password: # redis没有设置密码就不要设置了
  3. 代码

    @Override
    public Map<String, List<Catelog2Vo>> getCatalogJson() {
    // 压入缓存,缓存中的数据是 json 字符串
    String catalogJson = redisTemplate.opsForValue().get("catalogJson");
    if (StringUtils.isEmpty(catalogJson)) {
    // 缓存中没有数据,将查到的数据放入缓存
    Map<String, List<Catelog2Vo>> catalogJsonFromdb = getCatalogJsonFromdb();
    // 序列化
    // 将数据转化为json,json跨语言,跨平台兼容
    String string = JSON.toJSONString(catalogJsonFromdb);
    redisTemplate.opsForValue().set("catalogJson", string, 1, TimeUnit.DAYS);
    }

    // 反序列化,将json转化为我们指定的对象
    Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
    });
    return result;
    }

Redis分布式锁

  1. 引入依赖

    <!--        redis分布式锁-->
    <dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.12.0</version>
    </dependency>
  2. RedissonClient

    @Configuration
    public class MyRedissonConfig {
    /**
    * 所有对redisson的使用都是通过调用redissonclient对象实现的
    * @return
    * @throws IOException
    */
    @Bean(destroyMethod = "shutdown")
    public RedissonClient redisson() throws IOException{
    // 创建配置
    Config config = new Config();
    config.useSingleServer().setAddress("redis://192.168.196.101:6379");
    // 根据config配置创建从redissonclient实例
    return Redisson.create(config);
    }
    }
  3. 代码

    看门狗

    public String testRLock() {
    // 获取一把锁,只要锁的名字相同,就是同一把锁
    RLock lock = redissonClient.getLock("my-lock");
    // 加锁,锁的过期时间为 30 s
    // 锁会自动续期 watchdog : 如果业务执行时间过长,运行期间会自动续期到 30s
    // 不用担心业务运行过程中锁过期而被删除
    // 加锁的业务只要运行完成,就不会进行锁续期。所以及时不手动解锁,在 30s 后锁就会被删除
    lock.lock();
    try {
    // 业务代码
    System.out.println("加锁成功,执行业务...");
    } catch (Exception e) {
    e.printStackTrace();
    } finally {
    // 解锁
    lock.unlock();
    }
    return null;
    }
  4. 读写锁

    /**
    * 保证一定能读到最新数据,修改期间,写锁是一个排它锁(互斥锁、独享锁),读锁是一个共享锁
    * 写锁没释放,读锁必须等待
    * 读 + 读 :相当于无锁,并发读,只会在Redis中记录好,所有当前的读锁。他们都会同时加锁成功
    * 写 + 读 :必须等待写锁释放
    * 写 + 写 :阻塞方式
    * 读 + 写 :有读锁。写也需要等待
    * 只要有读或者写的存都必须等待
    *
    */
    @GetMapping(value = "/write")
    @ResponseBody
    public String testWriteLock() {
    String s = "";
    RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
    RLock rLock = readWriteLock.writeLock();
    try {
    //改数据加写锁,读数据加读锁
    rLock.lock();
    s = UUID.randomUUID().toString();
    ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
    ops.set("writeValue",s);
    TimeUnit.SECONDS.sleep(10);
    } catch (InterruptedException e) {
    e.printStackTrace();
    } finally {
    rLock.unlock();
    }

    return s;
    }

    @GetMapping(value = "/read")
    @ResponseBody
    public String testReadLock() {
    String s = "";
    RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
    //加读锁
    RLock rLock = readWriteLock.readLock();
    try {
    rLock.lock();
    ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
    s = ops.get("writeValue");
    try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
    } catch (Exception e) {
    e.printStackTrace();
    } finally {
    rLock.unlock();
    }

    return s;
    }
  5. 信号量

    /**
    * 车库停车
    * 3车位,3是自己在redis中设置的,key为信号量名,值为3
    * 信号量也可以做分布式限流
    */
    @GetMapping(value = "/park")
    @ResponseBody
    public String park() throws InterruptedException {

    RSemaphore park = redisson.getSemaphore("park");
    // park.acquire(); //获取一个信号、获取一个值,占一个车位
    boolean flag = park.tryAcquire();

    if (flag) {
    //执行业务
    } else {
    return "error";
    }

    return "ok=>" + flag;
    }

    @GetMapping(value = "/go")
    @ResponseBody
    public String go() {
    RSemaphore park = redisson.getSemaphore("park");
    park.release(); //释放一个车位
    return "ok";
    }
  6. 闭锁

    /**
    * 放假、锁门
    * 1班没人了
    * 5个班,全部走完,我们才可以锁大门
    * 分布式闭锁
    */

    @GetMapping(value = "/lockDoor")
    @ResponseBody
    public String lockDoor() throws InterruptedException {

    RCountDownLatch door = redisson.getCountDownLatch("door");
    door.trySetCount(5);
    door.await(); //等待闭锁完成

    return "放假了...";
    }

    @GetMapping(value = "/gogogo/{id}")
    @ResponseBody
    public String gogogo(@PathVariable("id") Long id) {
    RCountDownLatch door = redisson.getCountDownLatch("door");
    door.countDown(); //计数-1

    return id + "班的人都走了...";
    }

SpringCache

Spring-Cache的不足之处:

1)、读模式

缓存穿透:查询一个null数据。解决方案:缓存空数据

缓存击穿:大量并发进来同时查询一个正好过期的数据。解决方案:加锁 ? 默认是无加锁的;使用sync = true来解决击穿问题

缓存雪崩:大量的key同时过期。解决:加随机时间。加上过期时间

2)、写模式:(缓存与数据库一致)

1)、读写加锁。

2)、引入Canal,感知到MySQL的更新去更新Redis

3)、读多写多,直接去数据库查询就行

总结:

常规数据(读多写少,即时性,一致性要求不高的数据,完全可以使用Spring-Cache):写模式(只要缓存的数据有过期时间就足够了)

特殊数据:特殊设计

原理:

CacheManager(RedisCacheManager)->Cache(RedisCache)->Cache负责缓存的读写

  1. 引入依赖

    <!--        cache-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
  2. 配置文件

    spring:
    # 配置缓存
    cache:
    # 使用redis作为缓存
    type: redis
    redis:
    # 设置过期时间为一小时,
    time-to-live: 000
  3. 配置类

    import org.springframework.boot.autoconfigure.cache.CacheProperties;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.cache.annotation.EnableCaching;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.cache.RedisCacheConfiguration;
    import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializationContext;
    import org.springframework.data.redis.serializer.StringRedisSerializer;

    @EnableConfigurationProperties(CacheProperties.class)
    @Configuration
    @EnableCaching
    public class MyCacheConfig {

    @Bean
    public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {

    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
    // config = config.entryTtl();
    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;
    }
    }

  4. 开启缓存

    @EnableCaching
    @SpringBootApplication
    public class GuliMallProductApplication {

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

    }
  5. 使用缓存

    // 设置缓存分区、key
    @Cacheable(value = {"category"},key = "#root.method.name")
    @Override
    public List<CategoryEntity> getLevel1Category() {
    List<CategoryEntity> entities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
    return entities;
    }
  6. 缓存失效模式(更新后删除缓存)

    //    缓存失效模式使用
    @CacheEvict(value = "category",allEntries = true) //删除某个分区下的所有数据
    @Transactional
    @Override
    public void updateCascade(CategoryEntity category) {
    this.updateById(category);
    System.out.println(category.getName());
    categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
    }

SpringSession

  1. 引入依赖

    <!--        spring-session-->
    <dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
    </dependency>
  2. 配置文件

    server:
    servlet:
    session:
    # session 过期时间
    timeout: 30m
    spring:
    redis:
    host: 192.168.196.101
    port: 6379
    # spring session
    session:
    # 存储类型
    store-type: redis
  3. 开启注解

    @EnableRedisHttpSession
    public class GuliMallAuthApplication {

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

    }

  4. 对应的对象实现序列化

    public class MemberResponseVo implements Serializable {}

virtualbox install centos

https://zhuanlan.zhihu.com/p/60408219

评论