SpringCloud前后端整体开发流程-以及技术总结文章实时更新中

SpringCloud前后端整体开发流程

目前文章2025年12月24实时更新中

下面是我以前再开发中使用到的一些东西占时整理成目录,后期会目录中的每个版块会进行不定时的更新,也是为了让自己更加深入的了解这些技术.

之前有些技术只是在项目中使用过,会写一些,但是具体从0->1没有实际应用过,在我看来现在应该也是有许多的开发程序员和我是一样的,只是使用过具体的原理并没有了解过,并且再第二次使用相同的技术的时候又会忘记了,急促的情况下只会面向deepseek/百度/通义/豆包/讯飞编程了.

现在我会将自己接触到的东西每天整理成文档实时更新下去,ai不会取代我们的,但是ai是真的会取代初级开发,只要我们对这门技术掌握的越来越熟练,使用起来就像自己的手脚一样,我感觉我也算是出师了,加油吧!少年们!

一.前期准备

一.knife4j

步骤一:接入swagger

1.引入坐标

xml 复制代码
<--引入坐标-->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
</dependency>

添加配置类

java 复制代码
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfiguration {

   @Bean
   public Docket buildDocket() {
      return new Docket(DocumentationType.SWAGGER_2)
              .apiInfo(buildApiInfo())
              .select()
              // 要扫描的API(Controller)基础包
              .apis(RequestHandlerSelectors.basePackage("com.***"))
              .paths(PathSelectors.any())
              .build();
   }

   private ApiInfo buildApiInfo() {
      Contact contact = new Contact("项目名称","","");
      return new ApiInfoBuilder()
              .title("项目名称-平台管理API文档")
              .description("平台管理服务api")
              .contact(contact)
              .version("1.0.0").build();//版本号
   }
}

swagger常用注解

在Java类中添加Swagger的注解即可生成Swagger接口文档,常用Swagger注解如下:

@Api:修饰整个类,描述Controller的作用 @ApiOperation:描述一个类的一个方法,或者说一个接口 @ApiParam:单个参数的描述信息

@ApiModel:用对象来接收参数

@ApiModelProperty:用对象接收参数时,描述对象的一个字段

@ApiResponse:HTTP响应其中1个描述

@ApiResponses:HTTP响应整体描述

@ApiIgnore:使用该注解忽略这个API

@ApiError :发生错误返回的信息

@ApiImplicitParam:一个请求参数

@ApiImplicitParams:多个请求参数的描述信息

@ApiImplicitParam属性:

属性 取值 作用
paramType 查询参数类型
path 以地址的形式提交数据
query 直接跟参数完成自动映射赋值
body 以流的形式提交 仅支持POST
header 参数在request headers 里边提交
form 以form表单的形式提交 仅支持POST
dataType 参数的数据类型 只作为标志说明,并没有实际验证
Long
String
name 接收参数名
value 接收参数的意义描述
required 参数是否必填
true 必填
false 非必填
defaultValue 默认值
java 复制代码
//使用方式
@Api(value = "xxx名称", tags = "xxx", description = "xxx名称API")
public interface XXXControllerApi {

    /**
     * xxx列表查询
     * @param xxx
     * @return
     */
    @ApiOperation("xxx列表查询")
    public AjaxResult findByName(XXX  xxx);
}

@Data
public class XXX  {
    @ApiModelProperty("XXX名称",required = true)
    private String name;
}

服务访问地址:http://localhost:${port}/swagger-ui.html

步骤二:接入knife4j

xml 复制代码
<--引入坐标-->
<dependency>
     <groupId>com.github.xiaoymin</groupId>
     <artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
java 复制代码
//添加配置类
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
@EnableKnife4j
@Import(BeanValidatorPluginsConfiguration.class)
public class Knife4jConfiguration {

   @Bean
   public Docket buildDocket() {
      return new Docket(DocumentationType.SWAGGER_2)
              .apiInfo(buildApiInfo())
              .select()
              // 要扫描的API(Controller)基础包
              .apis(RequestHandlerSelectors.basePackage("com.***"))
              .paths(PathSelectors.any())
              .build();
   }

   private ApiInfo buildApiInfo() {
      Contact contact = new Contact("项目名称","","");
      return new ApiInfoBuilder()
              .title("项目名称-平台管理API文档")
              .description("平台管理服务api")
              .contact(contact)
              .version("1.0.0").build();//版本号
   }
}



@Configuration
@ComponentScan("com.xxx.config.knife4j")
public class KnifeConfig {
}

访问地址:http://localhost:${port}/doc.html

二.Gateway网关阶段

Gateway(网关) 是微服务架构中的流量入口和统一管理点,所有外部请求首先到达网关,由网关负责路由、过滤、安全控制等,再转发到对应的后端服务。

可以将网关理解为大厦的前台/门卫:

  • 所有访客先到前台登记
  • 前台检查权限、指引方向
  • 决定谁能进、能去哪里
  • 记录谁进出过

特点:

特性 说明
统一入口 所有流量单一入口,简化客户端调用
解耦 客户端无需知道后端服务细节
动态路由 根据规则将请求路由到不同服务
负载均衡 在多个服务实例间分配流量
服务聚合 合并多个服务的响应

网关的使用实例

网关引入坐标

xml 复制代码
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
    </dependency>
</dependencies>

设置引导类

java 复制代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient  //开启注册中心
public class GatewayApplication {

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

application配置

yml 复制代码
server:
  port: 80
spring:
  application:
    name: 服务名称-gateway
  cloud:
    nacos:
      discovery:
        server-addr: localgost:8848 #nacos的地址主要是使用其中的注册中心里面的服务发现服务名称
    gateway: #网关配置
    # 微服务名称配置
      - discovery:
           locator:
           		enabled: true #默认是false,请求路径前可以添加微服务的名称
              lower-case-service-id: true #允许为小写
           			
      globalcors:
        cors-configurations:
          '[/**]': # 匹配所有请求
            allowedOrigins: "*" #跨域处理 允许所有的域
            allowedMethods: # 支持的方法
            - GET
            - POST
            - PUT
            - DELETE
      routes: #路由配置:转发规则
      # 平台管理
      - id: product-service #唯一标识:默认是一个uuid
        uri: lb://product-service   # 从注册中心发现,理解就是转发到对应的uri的对应的服务器上lb://服务名称
        predicates:  #请求放行的资源路径
        - Path=/admin/**  
        filters:
        - StripPrefix= 1   # 移除路径前缀
        # 在原始请求上添加请求参数  参数名称以及值
        - AddRequestParameter=name,myname

在网关服务中创建全局过滤器

java 复制代码
import io.jsonwebtoken.Claims;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

//实现全局过滤器
@Component
@Log4j2
public class AuthorizeFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.获取请求对象和响应对象
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        //2.判断当前的请求是否为登录,如果是,直接放行
        if(request.getURI().getPath().contains("/login/in")){
            //放行
            return chain.filter(exchange);
        }

        //3.获取当前用户的请求头jwt信息
        HttpHeaders headers = request.getHeaders();
        String jwtToken = headers.getFirst("token");

        //4.判断当前令牌是否存在
        if(StringUtils.isEmpty(jwtToken)){
            //如果不存在,向客户端返回错误提示信息
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

        try {
            //5.如果令牌存在,解析jwt令牌,判断该令牌是否合法,如果不合法,则向客户端返回错误信息
            Claims claims = JwtUtilRealize.getClaimsBody(jwtToken);
            int result = JwtUtilRealize.verifyToken(claims);
            if(result == 0 || result == -1){
                //5.1 合法,则向header中重新设置userId
                Integer id = (Integer) claims.get("id");
                log.info("find userid:{} from uri:{}",id,request.getURI());
                //重新设置token到header中
                ServerHttpRequest serverHttpRequest = request.mutate().headers(httpHeaders -> {
                    httpHeaders.add("userId", id + "");
                }).build();
                exchange.mutate().request(serverHttpRequest).build();
            }
        }catch (Exception e){
            e.printStackTrace();
            //想客户端返回错误提示信息
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }


        //6.放行
        return chain.filter(exchange);
    }
    /**
     * 过滤器排序
     * 优先级设置
     * 值越小,优先级越高
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

三.OpenFegin远程调用阶段

引入坐标

xml 复制代码
        <!--feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

添加ribbon配置

java 复制代码
@Configuration
public class RestTemplateConfig {


    @LoadBalanced
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

在启动类 添加 @EnableFeignClients 注解

java 复制代码
@EnableDiscoveryClient // 激活DiscoveryClient
@EnableEurekaClient
@SpringBootApplication
@EnableFeignClients //开启Feign的功能
public class XxxxApp {
    public static void main(String[] args) {
        SpringApplication.run(XxxxApp.class,args);
    }
}

书写feign接口

java 复制代码
@FeignClient(value = "FEIGN-SYSUSER")
public interface SysUserFeignClient {
    @GetMapping("/sysUser/findId/{id}")
    public SysUser findId(@PathVariable("id") int id);

}

调用openFeign

java 复制代码
@RestController
@RequestMapping("/sysUser")
public class SysUserController {
    @Autowired
    private SysUserFeignClient sysUserFeignClient;
    @GetMapping("/SysUser/{id}")
    public Goods findId(@PathVariable("id") int id){
        SysUser sysUser= sysUserFeignClient.findId(id);
        return sysUser;
    }

其他配置

yml 复制代码
# 设置Ribbon的超时时间
ribbon:
  ConnectTimeout: 1000 # 连接超时时间 默认1s
  ReadTimeout: 3000 # 逻辑处理的超时时间 默认1s
yml 复制代码
# 设置当前的日志级别 debug,feign只支持记录debug级别的日志
logging:
  level:
    com.xxx: debug     #调用了feign客户端所在代码的包名

添加配置

java 复制代码
@Configuration
public class FeignLogConfig {

    /*
        NONE,不记录
        BASIC,记录基本的请求行,响应状态码数据
        HEADERS,记录基本的请求行,响应状态码数据,记录响应头信息
        FULL;记录完成的请求 响应数据
     */

    @Bean
    public Logger.Level level(){
        return Logger.Level.FULL;
    }
}

启动fegin日志级别

java 复制代码
@FeignClient(value = "FEIGN-SYSUSER",configuration = FeignLogConfig.class)
public interface SysUserFeignClient {
    @GetMapping("/sysUser/findId/{id}")
    public SysUser findId(@PathVariable("id") int id);

}

四.Hystrix熔断器阶段

引入坐标

xml 复制代码
   <dependency>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
         </dependency>

在启动类上开启Hystrix功能:@EnableCircuitBreaker

java 复制代码
/**
 * 启动类
 */
@SpringBootApplication
@EnableCircuitBreaker // 开启Hystrix功能
public class HystrixApp {
    public static void main(String[] args) {
        SpringApplication.run(Hystrix.class,args);
    }

}

定义降级的方法

java 复制代码
    /**
     * 定义降级方法:
     *  1. 方法的返回值需要和原方法一样
     *  2. 方法的参数需要和原方法一样
     *  3. 就是只有方法名不同
     */
    public SysUser findId_fallback(int id){
        SysUser sysUser = new SysUser();
        sysUser.setTitle("降级了~~~");
        return sysUser;
    }

使用 @HystrixCommand 注解配置降级方法

java 复制代码
    @GetMapping("/findId/{id}")
    @HystrixCommand(fallbackMethod = "findId_fallback")
    public SysUser findId(@PathVariable("id") int id){
        ....
    }

设置降级的属性

java 复制代码
    @GetMapping("/findId/{id}")
     //设置Hystrix的超时时间,默认1s
    @HystrixCommand(fallbackMethod = "findOne_fallback",commandProperties ={@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000")
    })
    public SysUser findId(@PathVariable("id") int id){
        ....
    }

消费方需要降级

配置yml

yml 复制代码
# 开启feign对hystrix的支持
feign:
  hystrix:
    enabled: true

书写降级方法

java 复制代码
/**
 * Feign 客户端的降级处理类
 * 1. 定义类 实现 Feign 客户端接口
 * 2. 使用@Component注解将该类的Bean加入SpringIOC容器
 */
@Component
public class SysUserFeignClientFallback implements SysUserFeignClient {
    @Override
    public SysUser findId(int id) {
        SysUser sysUser = new SysUser();
        sysUser.setTitle("又被降级了~~~");
        return sysUser;
    }
}

在 @FeignClient 注解中使用 fallback 属性设置降级处理类。

java 复制代码
@FeignClient(value = "HYSTRIX-PROVIDER",fallback = SysUserFeignClientFallback.class)
public interface SysUserFeignClient {


    @GetMapping("/sysUser/findId/{id}")
    public SysUser findId(@PathVariable("id") int id);

}

添加熔断机制

java 复制代码
    @GetMapping("/findId/{id}")
    @HystrixCommand(fallbackMethod = "findOne_fallback",commandProperties = {
            //设置Hystrix的超时时间,默认1s
         @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000"),
    //监控时间 默认5000 毫秒
   @HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds",value = "5000"),
    //失败次数。默认20次
    @HystrixProperty(name="circuitBreaker.requestVolumeThreshold",value = "20"),
     //失败率 默认50%
   @HystrixProperty(name="circuitBreaker.errorThresholdPercentage",value = "50")

    })
    public SysUser findId(@PathVariable("id") int id){
    ....
   }

五.Ribbon负载均衡阶段

修改配置文件yml

yml 复制代码
#配置的方式设置Ribbon的负载均衡策略
EUREKA-PROVIDER: #设置的服务提供方应用名称
  ribbon:
    NFloadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #策略类

六.注册中心阶段

1.引入坐标

xml 复制代码
        <!--nacos-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>0.2.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-client</artifactId>
            <version>1.1.0</version>
        </dependency>

书写配置类

yml 复制代码
server:
  port: 8000

spring:
  cloud:
    nacos:
      discovery:
        server-addr:  127.0.0.1:8848 # 配置nacos 服务端地址
  application:
    name: nacos-provider # 服务名称

代码实例:

java 复制代码
 @Autowired
    private DiscoveryClient discoveryClient;

    @GetMapping("/goods/{id}")
    public SysUser findId(@PathVariable("id") int id){
        //演示discoveryClient 使用
        List<ServiceInstance> instances = discoveryClient.getInstances("nacos-provider");//获取注册中心的地址
        ServiceInstance instance = instances.get(0);
        String url = "http://"+instance.getHost()+":"+instance.getPort()+"/sysUser/findOne/"+id;
        // 3. 调用方法
        SysUser sysUser= restTemplate.getForObject(url, SysUser.class);

        return sysUser;
    }

六.其他技术

2.redis

3.rocketMq

发送方

java 复制代码
//测试顺序消息
public class Producer {
    public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("group1");
        producer.setNamesrvAddr("192.168.184.128:9876");
        producer.start();

        //创建要执行的业务队列
        List<Order> orderList = new ArrayList<Order>();
        ...
        //设置消息进入到指定的消息队列中
        for(final Order order : orderList){
            Message msg = new Message("orderTopic",order.toString().getBytes());
            //发送时要指定对应的消息队列选择器
            SendResult result = producer.send(msg, new MessageQueueSelector() {
                //设置当前消息发送时使用哪一个消息队列
                public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
                    //根据发送的信息不同,选择不同的消息队列
                    //根据id来选择一个消息队列的对象,并返回->id得到int值
                    int mqIndex = order.getId().hashCode() % list.size();
                    return list.get(mqIndex);
                }
            }, null);

            System.out.println(result);
        }

        producer.shutdown();
    }
}

接收方

java 复制代码
     DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
        consumer.setNamesrvAddr("192.168.184.128:9876");
        consumer.subscribe("orderTopic","*");

        //使用单线程的模式从消息队列中取数据,一个线程绑定一个消息队列
        consumer.registerMessageListener(new MessageListenerOrderly() {
            //使用MessageListenerOrderly接口后,对消息队列的处理由一个消息队列多个线程服务,转化为一个消息队列一个线程服务
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext consumeOrderlyContext) {
                for(MessageExt msg : list){
                    System.out.println(Thread.currentThread().getName()+"  消息:"+new String(msg.getBody()));
                }
                return ConsumeOrderlyStatus.SUCCESS;
            }
        });

        consumer.start();
        System.out.println("接收消息服务已开启运行");
    }

4.kafka

5.XXL-JOB

6.JWT

首先JWT(JSON Web Token)是一种开放标准,用于在各方之间安全地传输信息,通常用于身份验证和授权。(信息来源deepseek)

  1. Header(头部):包含令牌类型和使用的算法
  2. Payload(载荷):包含声明(用户信息、过期时间等)
  3. Signature(签名):验证令牌的完整性和真实性

工作原理:

  • 用户登录后,服务器生成JWT并返回给客户端
  • 客户端后续请求在Authorization头部携带JWT
  • 服务器验证JWT的有效性,无需查询数据库

主要用途:

  • 1.身份验证
    • 用户登录后,服务器颁发JWT
    • 客户端存储JWT(通常localStorage或cookie)
    • 每次请求API时携带JWT
    • 服务器验证JWT决定是否允许访问
  • 2.信息交换
    • 在各方之间安全传输信息
    • 签名确保信息未被篡改

优点:

  • 无状态:服务器不需要存储会话信息
  • 跨域友好:适用于微服务和跨域应用
  • 灵活性:可包含自定义声明
  • 安全性:通过签名防止篡改

安全注意事项:

要存储敏感信息:JWT内容可被解码(非加密)

使用HTTPS:防止令牌被拦截

设置合理过期时间:减少被盗用风险

安全存储客户端:防止XSS攻击

使用场景:

text 复制代码
用户登录 → 服务器验证 → 生成JWT → 返回给客户端
客户端请求API → 携带JWT → 服务器验证 → 返回数据

1.组成结构

JWT令牌结构:

Header、Payload、Signature三部分组成:

每部分中间使用点(.)分隔,比如:aaaa.bbbb.cccc

java中生成token实例:

java 复制代码
import io.jsonwebtoken.*;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.*;

/**
 * 获取token中的claims信息
 * //生成JWT
 * JwtUtilRealize .getToken(3000L)
 * //通过JWT字符串获取JWT的内容
 * Jws<Claims> jws = JwtUtilRealize .getJws("eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAADWLQQqEMAxF75K1hdS2xnqbaANWEAqpMMMwdzcu_Kv_eLwfHL3CAmGbJUtEN3pKLm45OeZc3EpepLDEmRAGqNxh8TTZQkw4gF6r1frVLufjVQ13qScb8VWMuDX78mlvSfiU1VxAxP8NzBeQ6YMAAAA.7sU7xkwLPoyvrLSbLOOcmiHfl0z-D3ffT1v6Yf1upofbLiJBsqkGWpzG-G-H8BxzUG1cPm_kEtgeXVTcwgWrcA");
 * //获取自定义数据
 * Claims claims = jws.getBody();
 * //获取自定义数据中的数据id信息
 * Object id = claims.get("id");
 * 
*/
public class JwtUtilRealize {
    // Token时间设置--后期也可以将token的时间设置到application的配置文件中
    private static final int TOKEN_TIME_OUT = 3_600;
    // 加密KEY
    private static final String TOKEN_ENCIPHER_KEY= "MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY";
    // 设置最小刷新间隔(S)
    private static final int REFRESH_TIME = 300;

  //生成JWT字符串
    public static String getToken(Long id){
        Map<String, Object> claimMaps = new HashMap<>();
        claimMaps.put("id",id);
        long currentTime = System.currentTimeMillis();
        return Jwts.builder()
                .setId(UUID.randomUUID().toString())
                .setIssuedAt(new Date(currentTime))  //签发的时间
                .setSubject("system")  //说明
                .setIssuer("heima") //签发者信息
                .setAudience("app")  //接收用户
                .compressWith(CompressionCodecs.GZIP)  //数据压缩方式
                .signWith(SignatureAlgorithm.HS512, generalKey()) //加密方式,这里占时使用的是对称的加密方式,最小秘钥长度为64位
                .setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000))  //过期时间戳
                .addClaims(claimMaps) //cla信息
                .compact();
    }

     /**
     * 获取token中的claims信息
     *
     * @param token
     * @return
     */
    private static Jws<Claims> getJws(String token) {
            return Jwts.parser()
                    .setSigningKey(generalKey())
                    .parseClaimsJws(token);
    }

    /**
     * 获取payload body信息
     *
     * @param token
     * @return
     */
    public static Claims getClaimsBody(String token) {
        try {
            return getJws(token).getBody();
        }catch (ExpiredJwtException e){
            return null;
        }
    }

    /**
     * 获取hearder body信息
     *
     * @param token
     * @return
     */
    public static JwsHeader getHeaderBody(String token) {
        return getJws(token).getHeader();
    }

  /**
     * 是否过期
     *
     * @param claims
     * @return -1:有效,0:有效,1:过期,2:过期
     */
    public static int verifyToken(Claims claims) {
        if(claims==null){
            return 1;
        }
        try {
            claims.getExpiration()
                    .before(new Date());
            // 需要自动刷新TOKEN
            if((claims.getExpiration().getTime()-System.currentTimeMillis())>REFRESH_TIME*1000){
                return -1;
            }else {
                return 0;
            }
        } catch (ExpiredJwtException ex) {
            return 1;
        }catch (Exception e){
            return 2;
        }
    }



    /**
     * 由字符串生成加密key
     *
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getEncoder().encode(TOKEN_ENCIPHER_KEY.getBytes());
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }  


}

1.XSS攻击(后期补充ing)

七.其他通信技术

1.netty

2.websocket

3.mqtt

八.数据库

1.mysql数据库

2.oracle数据库

3.达梦数据库

4.MongoDB

九.存储

十.git

十一.分布式事务

0.seata

seata使用实例

1.下载seata
  1. 下载seata
  2. 修改配置
    如果是基于file的方式启动注册和配置的情况下
    我们需要将
    mode修改为file
    flushDiskMode = async

conf/file.conf

主要是修改127.0.0.1:8091将该值设置为seata server向外提供服务ip及端口(或域名+端口)

conf 复制代码
service {
  #vgroup->rgroup
  vgroup_mapping.my_test_tx_group = "default"
  #only support single node
  default.grouplist = "127.0.0.1:8091"
  #degrade current not support
  enableDegrade = false
  #disable
  disable = false
  #unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
  max.commit.retry.timeout = "-1"
  max.rollback.retry.timeout = "-1"
}

启动server

到bin目录下执行脚本启动seata server端,注:linux下执行seata-server.sh启动

项目中集成seata引入坐标

xml 复制代码
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        <version>2.1.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>io.seata</groupId>
        <artifactId>seata-all</artifactId>
        <version>0.9.0</version>
        <exclusions>
            <exclusion>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.21</version>
    </dependency>

在seata微服务板块下面创建配置类

java 复制代码
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.plugin.Interceptor;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;

@Configuration
@EnableConfigurationProperties({MybatisPlusProperties.class})
public class DataSourcesProxyConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDataSource() {
        return new DruidDataSource();
    }
    //创建代理数据源
    @Primary//@Primary标识必须配置在代码数据源上,否则本地事务失效
    @Bean
    public DataSourceProxy dataSourceProxy(DataSource druidDataSource) {
        return new DataSourceProxy(druidDataSource);
    }
    private MybatisPlusProperties properties;
    public DataSourcesProxyConfig(MybatisPlusProperties properties) {
        this.properties = properties;
    }
    //替换SqlSessionFactory的DataSource
    @Bean
    public MybatisSqlSessionFactoryBean sqlSessionFactory(DataSourceProxy dataSourceProxy, PaginationInterceptor paginationInterceptor) throws Exception {
        // 这里必须用 MybatisSqlSessionFactoryBean 代替了 SqlSessionFactoryBean,否则 MyBatisPlus 不会生效
        MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
        mybatisSqlSessionFactoryBean.setDataSource(dataSourceProxy);
        mybatisSqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
        mybatisSqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath*:/mapper/*.xml"));
        MybatisConfiguration configuration = this.properties.getConfiguration();
        if(configuration == null){
            configuration = new MybatisConfiguration();
        }
        mybatisSqlSessionFactoryBean.setConfiguration(configuration);
        //设置分页
        Interceptor[] plugins = {paginationInterceptor};
        mybatisSqlSessionFactoryBean.setPlugins(plugins);
        return mybatisSqlSessionFactoryBean;
    }
}

在其他需要微服务的板块下面添加对应的微服务板块的坐标

xml 复制代码
<dependency>
    <groupId>com.xxx</groupId>
    <artifactId>xxx-seata</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

在其他服务里面添加对应的微服务配置类

java 复制代码
@Configuration
@ComponentScan("com.xxx.seata.config")
public class SeataConfig {

}

最后seata中的配置文件file.conf和配置文件register.conf放到每个需要参与分布式事务项目的resources中。

并且需要修改对应的配置文件中的

service.vgroup_mapping.xxx改成vgroup_mapping.#{spring.application.name}_tx_group = "default"

特别注意:#{spring.application.name}是一个变量,指的是该项目的名称

然后再需要的提供微服务的板块yml中添加

yml 复制代码
spring:
  cloud:
    alibaba:
      seata:
        tx-service-group: ${spring.application.name}_tx_group

最后就可以使用@GlobalTransactional注解来进行分布式事务的开发了

1.数据库

1.数据库技术

1.jdbc
2.mybatis
3.mybatis-plus
1.xml书写方式
2.注解书写方式
3.Lambda书写方式
4.数据库调优
5.数据库触发器
6.数据库缓存
7.数据库事务
8.数据库查询优化
9.数据库索引优化以及使用

2.rocketmq

3.redis

4.

十二.OAuth 2.0 授权框架

十三.Spring Security‌安全框架

十四.集群

1.redis

2.数据库

3.mongoDB

4.RocketMq

5.Kafka

十五.linux命令

十六.docker

十七.爬虫

十八.java基础

十九.前端

vue

jquery

css

AJAX

nginx

书写aop模板参考信息来源若依

java 复制代码
/**
 * 自定义操作日志记录注解
 * 
 * @author ruoyi
 *
 */
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log
{
    /**
     * 模块 
     */
    public String title() default "";

    /**
     * 功能
     */
    public BusinessType businessType() default BusinessType.OTHER;

    /**
     * 操作人类别
     */
    public OperatorType operatorType() default OperatorType.MANAGE;

    /**
     * 是否保存请求的参数
     */
    public boolean isSaveRequestData() default true;
}
java 复制代码
/**
 * 操作日志记录处理
 *
 * @author ruoyi
 */
@Aspect
@Component
public class LogAspect
{
    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);

    // 配置织入点
    @Pointcut("@annotation(com.docer.common.annotation.Log)")
    public void logPointCut()
    {
    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "logPointCut()", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Object jsonResult)
    {
        handleLog(joinPoint, null, jsonResult);
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e 异常
     */
    @AfterThrowing(value = "logPointCut()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e)
    {
        handleLog(joinPoint, e, null);
    }

    protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult)
    {
        try
        {
            // 获得注解
            Log controllerLog = getAnnotationLog(joinPoint);
            if (controllerLog == null)
            {
                return;
            }

            // 获取当前的用户
            LoginUser loginUser = SpringUtils.getBean(TokenService.class).getLoginUser(ServletUtils.getRequest());

            // *========数据库日志=========*//
            SysOperLog operLog = new SysOperLog();
            operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
            // 请求的地址
            String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
            operLog.setOperIp(ip);
            // 返回参数
            operLog.setJsonResult(JSON.toJSONString(jsonResult));

            operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
            if (loginUser != null)
            {
                operLog.setOperName(loginUser.getUsername());
            }

            if (e != null)
            {
                operLog.setStatus(BusinessStatus.FAIL.ordinal());
                operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
            }
            // 设置方法名称
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            operLog.setMethod(className + "." + methodName + "()");
            // 设置请求方式
            operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
            // 处理设置注解上的参数
            getControllerMethodDescription(joinPoint, controllerLog, operLog);
            // 保存数据库
            AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
        }
        catch (Exception exp)
        {
            // 记录本地异常日志
            log.error("==前置通知异常==");
            log.error("异常信息:{}", exp.getMessage());
            exp.printStackTrace();
        }
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param log 日志
     * @param operLog 操作日志
     * @throws Exception
     */
    public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog) throws Exception
    {
        // 设置action动作
        operLog.setBusinessType(log.businessType().ordinal());
        // 设置标题
        operLog.setTitle(log.title());
        // 设置操作人类别
        operLog.setOperatorType(log.operatorType().ordinal());
        // 是否需要保存request,参数和值
        if (log.isSaveRequestData())
        {
            // 获取参数的信息,传入到数据库中。
            setRequestValue(joinPoint, operLog);
        }
    }

    /**
     * 获取请求的参数,放到log中
     *
     * @param operLog 操作日志
     * @throws Exception 异常
     */
    private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception
    {
        String requestMethod = operLog.getRequestMethod();
        if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))
        {
            String params = argsArrayToString(joinPoint.getArgs());
            operLog.setOperParam(StringUtils.substring(params, 0, 2000));
        }
        else
        {
            Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
            operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
        }
    }

    /**
     * 是否存在注解,如果存在就获取
     */
    private Log getAnnotationLog(JoinPoint joinPoint)
    {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        if (method != null)
        {
            return method.getAnnotation(Log.class);
        }
        return null;
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray)
    {
        String params = "";
        if (paramsArray != null && paramsArray.length > 0)
        {
            for (int i = 0; i < paramsArray.length; i++)
            {
                if (!isFilterObject(paramsArray[i])) {
                    Object jsonObj = JSON.toJSON(paramsArray[i]);
                    params += jsonObj.toString() + " ";
                }
            }
        }
        return params.trim();
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象,则返回true;否则返回false。
     */
    public boolean isFilterObject(final Object o) {
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse;
    }
}
相关推荐
代码or搬砖2 小时前
String字符串
android·java·开发语言
九皇叔叔2 小时前
MySQL 数据库 Read View 详解
数据库·mysql·mvcc·read view
oden3 小时前
0成本搭建!20分钟用 Workers AI + Vectorize 搞定 RAG(附全套源码)
后端
Elastic 中国社区官方博客3 小时前
Elasticsearch:圣诞晚餐 BBQ - 图像识别
大数据·数据库·elasticsearch·搜索引擎·ai·全文检索
cui_win4 小时前
Prometheus实战教程 - Redis 监控
数据库·redis·prometheus
不会画画的画师4 小时前
Go开发指南:io/ioutil包应用和迁移指南
开发语言·后端·golang
AM越.4 小时前
Java设计模式详解--装饰器设计模式(含uml图)
java·设计模式·uml
5980354154 小时前
【java工具类】小数、整数转中文大写
android·java·开发语言
JIngJaneIL4 小时前
基于java + vue个人博客系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot