SpringCloud前后端整体开发流程
- 一.前期准备
- 二.Gateway网关阶段
- 三.OpenFegin远程调用阶段
- 四.Hystrix熔断器阶段
- 五.Ribbon负载均衡阶段
- 六.注册中心阶段
- 六.其他技术
- 七.其他通信技术
- 八.数据库
- 九.存储
- 十.git
- 十一.分布式事务
- [十二.OAuth 2.0 授权框架](#十二.OAuth 2.0 授权框架)
- [十三.Spring Security安全框架](#十三.Spring Security安全框架)
- 十四.集群
- 十五.linux命令
- 十六.docker
- 十七.爬虫
- 十八.java基础
- 十九.前端
目前文章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)
- Header(头部):包含令牌类型和使用的算法
- Payload(载荷):包含声明(用户信息、过期时间等)
- 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
- 下载seata
- 修改配置
如果是基于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;
}
}