feat(master): 鉴权中心

鉴权中心的代码实现1
master
土豆兄弟 1 month ago
parent 676a1e8004
commit 5f96e63086

@ -1,4 +1,4 @@
# 项目大致说明
# 项目大致说明 - SpringCloud 组件
- 微服务注册与配置中心 - (Alibaba Nacos)
- [dev-protocol-springcloud-nacos](dev-protocol-springcloud-nacos)
- 微服务应用监控 - (SpringBoot Admin)
@ -8,7 +8,7 @@
- 微服务网关 - (Gateway)
- [dev-protocol-springcloud-gateway](dev-protocol-springcloud-gateway)
- 分布式链路、日志追踪 - (Sleuth + Zipkin)
- todo
- [dev-protocol-springcloud-sleuth-zipkin](dev-protocol-springcloud-sleuth-zipkin)
- 微服务容错 - (SpringCloud Netflix Hystrix)
- todo
- 消息驱动微服务 - (SpringCloud Stream)
@ -19,3 +19,16 @@
- todo
- 微服务工程部署与整体可用性验证
- todo
## 通用工程模块
- 授权、鉴权中心微服务
- [dev-protocol-springcloud-project-authority-center](dev-protocol-springcloud-project-authority-center)
- 用户账户微服务
- 商品微服务
- 订单微服务
- 物流微服务

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.example</groupId>
<artifactId>dev-protocol</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>dev-protocol-springcloud-project-authority-center</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<!-- 模块名及描述信息 -->
<name>dev-protocol-springcloud-project-authority-center</name>
<description>授权中心</description>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- spring cloud alibaba nacos discovery 依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Java Persistence API, ORM 规范 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL 驱动, 注意, 这个需要与 MySQL 版本对应 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.12</version>
<scope>runtime</scope>
</dependency>
<!-- web 的通用依赖 -->
<dependency>
<groupId>org.example</groupId>
<artifactId>dev-protocol-springcloud-project-mvc-config</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- zipkin = spring-cloud-starter-sleuth + spring-cloud-sleuth-zipkin-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-zipkin</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<!-- screw 生成数据库文档 -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.30</version>
</dependency>
<dependency>
<groupId>cn.smallbun.screw</groupId>
<artifactId>screw-core</artifactId>
<version>1.0.3</version>
</dependency>
</dependencies>
<!--
SpringBoot的Maven插件, 能够以Maven的方式为应用提供SpringBoot的支持可以将
SpringBoot应用打包为可执行的jar或war文件, 然后以通常的方式运行SpringBoot应用
-->
<build>
<finalName>${artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,19 @@
package org.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
/**
* <h1></h1>
* */
@EnableJpaAuditing // 允许 Jpa 自动审计
@EnableDiscoveryClient
@SpringBootApplication
public class AuthorityCenterApplication {
public static void main(String[] args) {
SpringApplication.run(AuthorityCenterApplication.class, args);
}
}

@ -0,0 +1,13 @@
package org.example.constant;
/**
* <h1>使</h1>
* */
public final class AuthorityConstant {
/** RSA 私钥, 除了授权中心以外, 不暴露给任何客户端 */
public static final String PRIVATE_KEY = "MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAKzKEUVNF+VVgyar2SAcm9xGDCL4yjMLEXXsy0BNM6rZTh2metCIqA1vbKvFvuruTLcTHYMtO0+urup2ScRhiZtkHMI9Vy/MGbiG0o5T2YrDUYZD4jPPRgvZ2L95uI+nFkEm1WZ7nuO37HvIE3+WDVvJnI84omFsLwN3bLCnStw5AgMBAAECgYACCZePWdAVL+HQlProXIJO1XXFwPN2MtCnzB0cIkk5Kc6zjxLIZf0M2dCGrGONG8BJVEj4Zn6BkXlEqTv+LXVCZfOLNJuXTOEdBTWQj1EFk2wuXIqrcZgFqT56ChSscMQTiEe/O7ydyQ2qD/ZbNDOcJMf6nto883ZDLtVtOTdzxQJBANwaMS8O0/X0/gkdzrY3dKjOJFmXOIybdURAR6Mum/PGkX6j9xAUO1clErKTEY1jkuqohLmnXw+pKTQTW/Gt290CQQDI+HFel+S64xZ9SGyISK+gXl1gK1mpMT2YaQjmzwotNljn7U3g0nChbltNANYsRcE5X8/kVoX7AihO+8RZp1oNAkEAgS66SVVZoJVfeHhPN/GKff0nppGz9grUI+/aW/NiQwz7nimcO4q0XWx78eWRuruDokiwRcrvZ2Cwt0jZgRq63QJBAIVli3LbZcK7K1lbclb/0Dulh1tnSutoONdqmLMDqGCcW2UO+guKA6LTqpyxOnhGkNwxgb+xwtr68qCCszFDSR0CQQDC52dm0bei9PCi0pebOhtQVYdPx+zfZE4p+aRCV7pYjm8HgMMJslKX8sgLEOg91gO/925QRb0uN5H5oDjOHFVh";
/** 默认的 Token 超时时间, 一天 */
public static final Integer DEFAULT_EXPIRE_DAY = 1;
}

@ -0,0 +1,59 @@
package org.example.controller;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.example.annotation.IgnoreResponseAdvice;
import org.example.service.IJWTService;
import org.example.vo.JwtToken;
import org.example.vo.UsernameAndPassword;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <h1></h1>
* */
@Slf4j
@RestController
@RequestMapping("/authority")
public class AuthorityController {
private final IJWTService ijwtService;
public AuthorityController(IJWTService ijwtService) {
this.ijwtService = ijwtService;
}
/**
* <h2> Token (), </h2>
* */
@IgnoreResponseAdvice
@PostMapping("/token")
public JwtToken token(@RequestBody UsernameAndPassword usernameAndPassword)
throws Exception {
log.info("request to get token with param: [{}]",
JSON.toJSONString(usernameAndPassword));
return new JwtToken(ijwtService.generateToken(
usernameAndPassword.getUsername(),
usernameAndPassword.getPassword()
));
}
/**
* <h2> Token, </h2>
* */
@IgnoreResponseAdvice
@PostMapping("/register")
public JwtToken register(@RequestBody UsernameAndPassword usernameAndPassword)
throws Exception {
log.info("register user with param: [{}]", JSON.toJSONString(
usernameAndPassword
));
return new JwtToken(ijwtService.registerUserAndGenerateToken(
usernameAndPassword
));
}
}

@ -0,0 +1,22 @@
package org.example.dao;
import org.example.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* <h1>User Dao </h1>
* */
public interface UserDao extends JpaRepository<User, Long> {
/**
* <h2> User </h2>
* select * from t_dev_protocol_cloud_user where username = ?
* */
User findByUsername(String username);
/**
* <h2></h2>
* select * from t_dev_protocol_cloud_user where username = ? and password = ?
* */
User findByUsernameAndPassword(String username, String password);
}

@ -0,0 +1,58 @@
package org.example.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Date;
/**
* <h1></h1>
* */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "t_dev_protocol_cloud_user")
public class User implements Serializable {
/** 自增主键 */
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
/** 用户名 */
@Column(name = "username", nullable = false)
private String username;
/** MD5 密码 */
@Column(name = "password", nullable = false)
private String password;
/** 额外的信息, json 字符串存储 */
@Column(name = "extra_info", nullable = false)
private String extraInfo;
/** 创建时间 */
@CreatedDate
@Column(name = "create_time", nullable = false)
private Date createTime;
/** 更新时间 */
@LastModifiedDate
@Column(name = "update_time", nullable = false)
private Date updateTime;
}

@ -0,0 +1,26 @@
package org.example.service;
import org.example.vo.UsernameAndPassword;
/**
* <h1>JWT </h1>
* */
public interface IJWTService {
/**
* <h2> JWT Token, 使</h2>
* */
String generateToken(String username, String password) throws Exception;
/**
* <h2> Token, </h2>
* */
String generateToken(String username, String password, int expire) throws Exception;
/**
* <h2> Token </h2>
* */
String registerUserAndGenerateToken(UsernameAndPassword usernameAndPassword)
throws Exception;
}

@ -0,0 +1,123 @@
package org.example.service.impl;
import com.alibaba.fastjson.JSON;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.example.constant.AuthorityConstant;
import org.example.constant.CommonConstant;
import org.example.dao.UserDao;
import org.example.entity.User;
import org.example.service.IJWTService;
import org.example.vo.LoginUserInfo;
import org.example.vo.UsernameAndPassword;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import sun.misc.BASE64Decoder;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.UUID;
/**
* <h1>JWT </h1>
* */
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class JWTServiceImpl implements IJWTService {
private final UserDao ecommerceUserDao;
public JWTServiceImpl(UserDao ecommerceUserDao) {
this.ecommerceUserDao = ecommerceUserDao;
}
@Override
public String generateToken(String username, String password) throws Exception {
return generateToken(username, password, 0);
}
@Override
public String generateToken(String username, String password, int expire)
throws Exception {
// 首先需要验证用户是否能够通过授权校验, 即输入的用户名和密码能否匹配数据表记录
User ecommerceUser = ecommerceUserDao.findByUsernameAndPassword(
username, password
);
if (null == ecommerceUser) {
log.error("can not find user: [{}], [{}]", username, password);
return null;
}
// Token 中塞入对象, 即 JWT 中存储的信息, 后端拿到这些信息就可以知道是哪个用户在操作
LoginUserInfo loginUserInfo = new LoginUserInfo(
ecommerceUser.getId(), ecommerceUser.getUsername()
);
if (expire <= 0) {
expire = AuthorityConstant.DEFAULT_EXPIRE_DAY;
}
// 计算超时时间
ZonedDateTime zdt = LocalDate.now().plus(expire, ChronoUnit.DAYS)
.atStartOfDay(ZoneId.systemDefault());
Date expireDate = Date.from(zdt.toInstant());
return Jwts.builder()
// jwt payload --> KV
.claim(CommonConstant.JWT_USER_INFO_KEY, JSON.toJSONString(loginUserInfo))
// jwt id
.setId(UUID.randomUUID().toString())
// jwt 过期时间
.setExpiration(expireDate)
// jwt 签名 --> 加密
// .signWith(getPrivateKey(), SignatureAlgorithm.RS256)
.compact();
}
@Override
public String registerUserAndGenerateToken(UsernameAndPassword usernameAndPassword)
throws Exception {
// 先去校验用户名是否存在, 如果存在, 不能重复注册
User oldUser = ecommerceUserDao.findByUsername(
usernameAndPassword.getUsername());
if (null != oldUser) {
log.error("username is registered: [{}]", oldUser.getUsername());
return null;
}
User ecommerceUser = new User();
ecommerceUser.setUsername(usernameAndPassword.getUsername());
ecommerceUser.setPassword(usernameAndPassword.getPassword()); // MD5 编码以后
ecommerceUser.setExtraInfo("{}");
// 注册一个新用户, 写一条记录到数据表中
ecommerceUser = ecommerceUserDao.save(ecommerceUser);
log.info("register user success: [{}], [{}]", ecommerceUser.getUsername(),
ecommerceUser.getId());
// 生成 token 并返回
return generateToken(ecommerceUser.getUsername(), ecommerceUser.getPassword());
}
/**
* <h2> PrivateKey </h2>
* */
private PrivateKey getPrivateKey() throws Exception {
PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(
new BASE64Decoder().decodeBuffer(AuthorityConstant.PRIVATE_KEY));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(priPKCS8);
}
}

@ -0,0 +1,64 @@
server:
port: 7000
servlet:
context-path: /dev-protocol-springcloud-project-authority-center
spring:
application:
name: dev-protocol-springcloud-project-authority-center
cloud:
nacos:
discovery:
enabled: true # 如果不想使用 Nacos 进行服务注册和发现, 设置为 false 即可
server-addr: 127.0.0.1:8848 # Nacos 服务器地址
# server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850 # Nacos 服务器地址
namespace: 1ccc74ae-9398-4dbe-b9d7-4f9addf9f40c
metadata:
management:
context-path: ${server.servlet.context-path}/actuator
jpa:
show-sql: true
hibernate:
ddl-auto: none
properties:
hibernate.show_sql: true
hibernate.format_sql: true
hibernate:
dialect: org.hibernate.dialect.MySQLDialect
open-in-view: false
datasource:
# 数据源
url: jdbc:mysql://127.0.0.1:3306/dev_protocol_springcloud_project?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
# 连接池
hikari:
maximum-pool-size: 8
minimum-idle: 4
idle-timeout: 30000
connection-timeout: 30000
max-lifetime: 45000
auto-commit: true
pool-name: devProtocolSpringcloudHikariCP
# kafka:
# bootstrap-servers: 127.0.0.1:9092
# producer:
# retries: 3
# consumer:
# auto-offset-reset: latest
# zipkin:
# sender:
# type: kafka # 默认是 web
# base-url: http://127.0.0.1:9411/
# 暴露端点
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always

@ -0,0 +1,18 @@
### 获取 Token -- 登录功能实现
POST http://127.0.0.1:7000/ecommerce-authority-center/authority/token
Content-Type: application/json
{
"username": "Qinyi02@imooc.com",
"password": "25d55ad283aa400af464c76d713c07ad"
}
### 注册用户并返回 Token -- 注册功能实现
POST http://127.0.0.1:7000/ecommerce-authority-center/authority/register
Content-Type: application/json
{
"username": "ImoocQinyiImooc@imooc.com",
"password": "25d55ad283aa400af464c76d713c07ad"
}

@ -0,0 +1,11 @@
-- 创建 t_ecommerce_user 数据表
CREATE TABLE IF NOT EXISTS `dev_protocol_springcloud_project`.`t_dev_protocol_cloud_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`username` varchar(64) NOT NULL DEFAULT '' COMMENT '用户名',
`password` varchar(256) NOT NULL DEFAULT '' COMMENT 'MD5 加密之后的密码',
`extra_info` varchar(1024) NOT NULL DEFAULT '' COMMENT '额外的信息',
`create_time` datetime NOT NULL DEFAULT '0000-01-01 00:00:00' COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT '0000-01-01 00:00:00' COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

@ -0,0 +1,21 @@
package org.example;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* <h1></h1>
*
* */
@SpringBootTest
@RunWith(SpringRunner.class)
public class AuthorityCenterApplicationTests {
@Test
public void contextLoad() {
}
}

@ -0,0 +1,35 @@
package org.example.dao;
import cn.hutool.crypto.digest.MD5;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.example.entity.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* <h1>User </h1>
* */
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserDaoTest {
@Autowired
private UserDao ecommerceUserDao;
@Test
public void createUserRecord() {
User ecommerceUser = new User();
ecommerceUser.setUsername("q@bbbbbbyyyyyy.com");
ecommerceUser.setPassword(MD5.create().digestHex("12345678"));
ecommerceUser.setExtraInfo("{}");
log.info("save user: [{}]",
JSON.toJSON(ecommerceUserDao.save(ecommerceUser)));
}
}

@ -0,0 +1,50 @@
package org.example.service;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.RSA;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
/**
* <h1>RSA : </h1>
* */
@Slf4j
public class RSATest {
// https://doc.hutool.cn/pages/jwt/#jwt%E8%A7%A3%E6%9E%90
@Test
public void generateKeyBytes() throws Exception {
// KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
// keyPairGenerator.initialize(2048);
// 生成公钥和私钥对
// KeyPair keyPair = keyPairGenerator.generateKeyPair();
// // 获取公钥和私钥对象
// RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
// RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
//
// log.info("private key: [{}]", Base64.encode(privateKey.getEncoded()));
// log.info("public key: [{}]", Base64.encode(publicKey.getEncoded()));
RSA rsa = new RSA();
//获得私钥
rsa.getPrivateKey();
log.info("private key: [{}]", rsa.getPrivateKeyBase64());
// MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAKzKEUVNF+VVgyar2SAcm9xGDCL4yjMLEXXsy0BNM6rZTh2metCIqA1vbKvFvuruTLcTHYMtO0+urup2ScRhiZtkHMI9Vy/MGbiG0o5T2YrDUYZD4jPPRgvZ2L95uI+nFkEm1WZ7nuO37HvIE3+WDVvJnI84omFsLwN3bLCnStw5AgMBAAECgYACCZePWdAVL+HQlProXIJO1XXFwPN2MtCnzB0cIkk5Kc6zjxLIZf0M2dCGrGONG8BJVEj4Zn6BkXlEqTv+LXVCZfOLNJuXTOEdBTWQj1EFk2wuXIqrcZgFqT56ChSscMQTiEe/O7ydyQ2qD/ZbNDOcJMf6nto883ZDLtVtOTdzxQJBANwaMS8O0/X0/gkdzrY3dKjOJFmXOIybdURAR6Mum/PGkX6j9xAUO1clErKTEY1jkuqohLmnXw+pKTQTW/Gt290CQQDI+HFel+S64xZ9SGyISK+gXl1gK1mpMT2YaQjmzwotNljn7U3g0nChbltNANYsRcE5X8/kVoX7AihO+8RZp1oNAkEAgS66SVVZoJVfeHhPN/GKff0nppGz9grUI+/aW/NiQwz7nimcO4q0XWx78eWRuruDokiwRcrvZ2Cwt0jZgRq63QJBAIVli3LbZcK7K1lbclb/0Dulh1tnSutoONdqmLMDqGCcW2UO+guKA6LTqpyxOnhGkNwxgb+xwtr68qCCszFDSR0CQQDC52dm0bei9PCi0pebOhtQVYdPx+zfZE4p+aRCV7pYjm8HgMMJslKX8sgLEOg91gO/925QRb0uN5H5oDjOHFVh
//获得公钥
rsa.getPublicKey();
log.info("public key: [{}]", rsa.getPublicKeyBase64());
// MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsyhFFTRflVYMmq9kgHJvcRgwi+MozCxF17MtATTOq2U4dpnrQiKgNb2yrxb7q7ky3Ex2DLTtPrq7qdknEYYmbZBzCPVcvzBm4htKOU9mKw1GGQ+Izz0YL2di/ebiPpxZBJtVme57jt+x7yBN/lg1byZyPOKJhbC8Dd2ywp0rcOQIDAQAB
// 自助生成密钥对
KeyPair pair = SecureUtil.generateKeyPair("RSA");
pair.getPrivate();
pair.getPublic();
// 自助生成的密钥对是byte[]形式我们可以使用Base64.encode方法转为Base64便于存储为文本。 当然如果使用RSA对象也可以使用encryptStr和decryptStr加密解密为字符串。
}
}

@ -0,0 +1,34 @@
## 授权、鉴权中心微服务功能设计
- 什么是 JWT
- JSON Web Token (WT)是一个开放标准,它定义了一种紧凑的、自包含的方式,用于作为 JSON对象在各方之间安全地传输信息
- 哪些场景下可以考虑使用 JWT?
- 用户授权 信息交换
---
- JWT 的结构(组成部分)
- JWT由三个部分组成:Header、Payload、Signature,且用圆点连接 xxxxx.yyyyy.ZZZZZ
- Header:由两部分(Token 类型、加密算法名称)组成,并使用 Base64 编码 {'alg':"HS256",'type':"JWT"}
- Payload:KV 形式的数据,即你想传递的数据(授权的话就是 Token 信息 )
- Signature:为了得到签名部分,你必须有编码过的 Header、编码过的 payload、一个秘钥, 签名算法是 Header 中指定的那个,然对它们签名即可
- HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)
---
- 授权、鉴权中心微服务功能逻辑架构
- ![授权鉴权中心微服务功能逻辑架构.png](pic/授权鉴权中心微服务功能逻辑架构.png)
- 鉴权功能不走HTTP可以更快来进行完成开发, 因为微服务很多模块都要依赖鉴权服务
---
## 搭建授权、鉴权中心微服务
- [FIX] Access to DialectResolutionInfo cannot be null when 'hibernate.dialect' not set
- 加入配置:
- spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
- [FIX] Caused by: com.mysql.cj.exceptions.InvalidConnectionAttributeException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more than one time
- 在配置文件中 spring.datasource.url 加 serverTimezone=GMT%2B8
## 数据表及 ORM 过程
-
## 生成 RSA256 公钥和私钥对
- [RSATest.java]
- 一般是放在服务器上, 用的时候拉取下来使用
## 基于 JWT + RSA256 的授权
## 验证服务可用性
## 授权、鉴权中心微服务总结

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.example</groupId>
<artifactId>dev-protocol</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>dev-protocol-springcloud-project-common</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<!-- 模块名及描述信息 -->
<name>dev-protocol-springcloud-project-common</name>
<description>通用模块</description>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jjwt.version>0.9.1</jjwt.version>
</properties>
<dependencies>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.22</version>
</dependency>
<!-- 引入fastjson2-->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.18</version>
</dependency>
</dependencies>
</project>

@ -0,0 +1,19 @@
package org.example.constant;
/**
* @author q
* @createTime 2024-08-19 18:18:06
* @Description
*/
public final class CommonConstant {
/** RSA 公钥 */
public static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsyhFFTRflVYMmq9kgHJvcRgwi+MozCxF17MtATTOq2U4dpnrQiKgNb2yrxb7q7ky3Ex2DLTtPrq7qdknEYYmbZBzCPVcvzBm4htKOU9mKw1GGQ+Izz0YL2di/ebiPpxZBJtVme57jt+x7yBN/lg1byZyPOKJhbC8Dd2ywp0rcOQIDAQAB";
/** JWT 中存储用户信息的 key */
public static final String JWT_USER_INFO_KEY = "dev-protocol-user";
/** 授权中心的 service-id */
public static final String AUTHORITY_CENTER_SERVICE_ID = "dev-protocol-springcloud-project-authority-center";
}

@ -0,0 +1,64 @@
package org.example.utils;
import com.alibaba.fastjson2.JSON;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import org.example.constant.CommonConstant;
import org.example.vo.LoginUserInfo;
import sun.misc.BASE64Decoder;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Calendar;
/**
* @author q
* @createTime 2024-08-19 18:17:42
* @Description JWT Token
*/
public class TokenParseUtils {
/**
* <h2> JWT Token LoginUserInfo </h2>
*/
public static LoginUserInfo parseUserInfoFromToken(String token) throws Exception {
if (null == token) {
return null;
}
Jws<Claims> claimsJws = parseToken(token, getPublicKey());
Claims body = claimsJws.getBody();
// 如果 Token 已经过期了, 返回 null
if (body.getExpiration().before(Calendar.getInstance().getTime())) {
return null;
}
// 返回 Token 中保存的用户信息
return JSON.parseObject(
body.get(CommonConstant.JWT_USER_INFO_KEY).toString(),
LoginUserInfo.class
);
}
/**
* JWT Token
*/
private static Jws<Claims> parseToken(String token, PublicKey publicKey) {
return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
}
/**
* PublicKey
*/
private static PublicKey getPublicKey() throws Exception {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(
new BASE64Decoder().decodeBuffer(CommonConstant.PUBLIC_KEY)
);
return KeyFactory.getInstance("RSA").generatePublic(keySpec);
}
}

@ -0,0 +1,39 @@
package org.example.vo;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.io.Serializable;
/**
* @author q
* @createTime 2024-08-19 18:17:02
* @Description
* {
* "code": 0,
* "message": "",
* "data": {}
* }
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class CommonResponse<T> implements Serializable {
/** 错误码 */
private Integer code;
/** 错误消息 */
private String message;
/** 泛型响应数据 */
private T Data;
public CommonResponse(Integer code, String message) {
this.code = code;
this.message = message;
}
}

@ -0,0 +1,22 @@
package org.example.vo;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* @author q
* @createTime 2024-08-19 18:17:16
* @Description Token
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class JwtToken {
/** JWT */
private String token;
}

@ -0,0 +1,25 @@
package org.example.vo;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* @author q
* @createTime 2024-08-19 18:17:24
* @Description
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class LoginUserInfo {
/** 用户 id */
private Long id;
/** 用户名 */
private String username;
}

@ -0,0 +1,21 @@
package org.example.vo;
import lombok.Getter;
import lombok.Setter;
/**
* @author q
* @createTime 2024-08-19 18:17:33
* @Description
*/
@Getter
@Setter
public class UsernameAndPassword {
/** 用户名 */
private String username;
/** 密码 */
private String password;
}

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.example</groupId>
<artifactId>dev-protocol</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>dev-protocol-springcloud-project-mvc-config</artifactId>
<!-- 模块名及描述信息 -->
<name>dev-protocol-springcloud-project-mvc-config</name>
<description>通用配置模块</description>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 引入 Web 功能 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入项目通用依赖 -->
<dependency>
<groupId>org.example</groupId>
<artifactId>dev-protocol-springcloud-project-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

@ -0,0 +1,7 @@
package org.example;
public class Main {
public static void main(String[] args) {
System.out.println("Hello world!");
}
}

@ -0,0 +1,72 @@
package org.example.advice;
import org.example.annotation.IgnoreResponseAdvice;
import org.example.vo.CommonResponse;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/**
* @author q
* @createTime 2024-08-19 18:11:45
* @Description
* ResponseBodyAdviceControllerreturnresponseAPPresponseresponse
*/
//声明该类要处理的包路径
@RestControllerAdvice("org.example")
public class CommonResponseDataAdvice implements ResponseBodyAdvice<Object> {
/**
* beforeBodyWritetruefalse
* supportsresponse
*
* @param returnType
* @param converterType
* @return
*/
@Override
@SuppressWarnings("all")
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// 判断类上是否使用了@IgnoreResponseAdvice注解
if (returnType.getDeclaringClass()
.isAnnotationPresent(IgnoreResponseAdvice.class)) {
return false;
}
if (returnType.getMethod().isAnnotationPresent(IgnoreResponseAdvice.class)) {
return false;
}
return true;
}
/**
* response
*
* @param body
* @param returnType
* @param selectedContentType
* @param selectedConverterType
* @param request
* @param response
* @return
*/
@Override
@SuppressWarnings("all")
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 定义最终的返回对象
CommonResponse<Object> commonResponse = new CommonResponse<>(0, "");
if (body == null) {
return commonResponse;
} else if (body instanceof CommonResponse) {
commonResponse = (CommonResponse<Object>) body;
} else {
commonResponse.setData(body);
}
return commonResponse;
}
}

@ -0,0 +1,28 @@
package org.example.advice;
import lombok.extern.slf4j.Slf4j;
import org.example.vo.CommonResponse;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* @author q
* @createTime 2024-08-19 18:13:14
* @Description
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionAdvice {
@ExceptionHandler(value = Exception.class)
@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
public CommonResponse<String> handlerCommerceException(Exception ex) {
CommonResponse<String> response = new CommonResponse<>(-1, "business error");
response.setData(ex.getMessage());
log.error("commerce service has error: [{}]", ex.getMessage(), ex);
return response;
}
}

@ -0,0 +1,16 @@
package org.example.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author q
* @createTime 2024-08-19 18:21:14
* @Description
*/
@Target({ElementType.TYPE, ElementType.METHOD}) // 可以放在class或方法上
@Retention(RetentionPolicy.RUNTIME) // 定义该注解保留到运行时
public @interface IgnoreResponseAdvice {
}

@ -0,0 +1,202 @@
## SpringCloud Sleuth + Zipkin 概览
### 认识 SpringCloud sleuth
- SpringCloud Sleuth 实现的功能是:它会自动为当前应用构建起各通信通道的跟踪机制
- 通过诸如 RabbitMQ、Kafka(或者其他任何 SpringCloud Stream 绑定器实现的消息中间件)传递的请求
- 通过 Zuul、Gateway 代理传递的请求
- 通过 RestTemplate 发起的请求
> Service A -Rest[FLAG]-> ServiceB -Kafka[FLAG]-> Service D (服务与服务之间的时间消耗)</br>
> -Rest[FLAG]-> Service C
---
- SpringCloud Sleuth 跟踪实现原理
- 为了实现请求跟踪:当请求发送到分布式系统的入口端点时,只需要服务跟踪框架为该请求创建一个唯一的跟踪标识,Trace ID
- 为了统计各处理单元的时间延迟,当请求到达各个服务组件时,或是处理逻辑到达某个状态时,也通过一个唯一标识来标记它的开始、具体过程以及结束,Span ID
> Service A[traceld, spanldA] -> Service B[traceld, spanldA1] -> Service D[traceld, spanldA11]</br>
> -> Service C[traceld,spanldA2]
---
- Zipkin 的基础概念
- Zipkin 解决微服务架构中的延迟问题,包括数据的收集、存储、查找和展现
- Zipkin 有四大核心组件构成:
- Collector:收集器组件
- Storage:存储组件
- API: RESTFUAPI提供外部访问接口
- U:Web Ul,提供可视化査询页面
- ![Zipkin流程图.png](pic/Zipkin流程图.png)
---
- Zipkin Web UI 预览
- 链路追踪与服务依赖 Zipkin 展示预览
- ![ZipkinWebUI.png](pic/ZipkinWebUI.png)
## 集成 SpringCloud Sleuth 实现微服务通信跟踪
- 集成 SpringCloud Sleuth 的步骤
- 第一原则:保证你的微服务存在跨进程通信,否则,意义不大(完全可以通过更简单的方式实现)
- 在 pom.xml中添加依赖配置
- 注意日志中的输出变化
- 代码中调用方法的打印是 10 进制的, Sleuth 打印是 16进制的打印
- PS: 命令行退出 mysql \q
- echo 'ibase=10,obase=16;xxxxxxxxxxx' | bc
---
## 搭建 Zipkin Server 实现对跟踪信息的收集
- 搭建 Zipkin Server 的步骤
- Tips:SpringCloud Finchley 版本(包含)之后,官方不建议自己搭建 Zipkin-Server, 提供了已经打包好的jar 文件(SpringBoot工程),直接下载启动即可
- 下载地址:curl -sSL https://zipkin.io/quickstart.sh | bash -s
- 选择自己需要的版本,我的是 zipkin-server-2.21.7-exec.jar
- 选择 exec.jar 结尾的 jar
---
- 启动命令: nohup java -jar zipkin-server-2.21.7-exec.jar --server.port=8888 &
---
- 配置 Zipkin Server 实现对跟踪信息的收集
- 为什么需要对 Zipkin Server 做自定义配置 ?
- 默认情况下Zipkin Server 将跟踪信息存储在内存中(JVM ),重启会丢失
- Zipkin Server 默认使用 HTTP 方式上报跟踪数据,性能较差
- Zipkin Server 配置 MySQL 跟踪数据持久化
- MySQL 中添加数据表:
- https://github.com/openzipkin/zipkin/blob/master/zipkin-storage/mysal-v1/src/main/resources/mysql.sql
- Zipkin Server 启动指定 MySQL 路径
---
- 启动命令
- java -jar zipkin-server-2.21.7-exec.jar --STORAGE_TYPE=mysql --MYSQL_HOST=127.0.0.1 --MYSQL_TCP_PORT=3306 --MYSOL_USER=root --MYSQL_PASS=root --MYSQL_DB=by_zipkin
## SpringCloud Sleuth 整合 Zipkin 实现分布式链路跟踪、收集
- SpringCloud Sleuth 整合 Zipkin 的步骤
- 简单的两个步骤(Zipkin Server 使用 MySQL 实现跟踪数据持久化)
- pom 文件中添加依赖
```xml
<!-- zipkin = spring-cloud-starter-sleuth + spring-cloud-sleuth-zipkin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
```
- bootstrap 中增加 Zipkin 的配置
```yaml
kafka:
bootstrap-servers: 127.0.0.1:9092
producer:
retries: 3
consumer:
auto-offset-reset: latest
sleuth:
sampler:
# ProbabilityBasedSampler 抽样策略
probability: 1.0 # 采样比例, 1.0 表示 100%, 默认是 0.1
# RateLimitingSampler 抽样策略, 设置了限速采集, spring.sleuth.sampler.probability 属性值无效
rate: 100 # 每秒间隔接受的 trace 量
zipkin:
sender:
type: kafka # 默认是 web
base-url: http://localhost:9411/
```
---
- 下载、安装 Kafka 配置跟踪数据传输
- 下载Kafka :https://kafka.apache.org/quickstart
- 一般采用
- 解压、启动 ZK和 Kafka Server 即可(使用默认配置)
- 启动内置 zk
```shell
Kafka_2.13-2.7.0 bin/zookeeper-server-start.sh config/zookeeper.properties
```
- 启动 kafka
```shell
kafka_2.13-2.7.0 bin/kafka-server-start.sh config/server.properties
```
- 启动 zipkin
```shell
zipkin java -DKAFKA_BOOTSTRAP_SERVERS=127.0.0.1:9092 -jar zipkin-server-2.21.7-exec.jar --STORAGE_TYPE=mysql --MYSQL_HOST=127.0.0.1 --MYSQL_TCP_PORT=3306 --MYSQL_USER=root --MYSQL_PASS=root --MYSQL_DB=imooc_zipkin
```
---
## SpringCloud Sleuth 设置采样率、抽样收集策略
- SpringCloud Sleuth 采样收集
- 收集跟踪信息是一把双刃剑,需要做好权衡
- 收集的跟踪信息越多,越能反映出系统的实际运行情况
- 高并发场景下,大量的请求调用会产生海量的跟踪日志信息,性能开销太大
- Sleuth --(Kafka[策略True])--> Zipkin
- 可以自由选择 Zipkin brave 自带的两个抽样策略
- ProbabilityBasedSampler 采样率策略
- 默认使用的策略, 以请求百分比的方式配置和收集跟踪信息: 它的默认值为 0.1,代表收集 10% 的请求跟踪信息
- spring.sleuth.samplerprobability=0.5
- RateLimitingSampler 抽样策略
- 限速采集,也就是说它可以用来限制每秒追踪请求的最大数量,优先级更高
- spring.sleuth.sampler.rate=10
- 开发环境下,配置尽可能高的采样率, 方便解决问题和性能瓶颈等的尽早发现
- 线上环境采样率不宜过高
---
```yaml
sleuth:
sampler:
# ProbabilityBasedSampler 抽样策略
probability: 1.0 # 采样比例, 1.0 表示 100%, 默认是 0.1
# RateLimitingSampler 抽样策略, 设置了限速采集, spring.sleuth.sampler.probability 属性值无效
rate: 100 # 每秒间隔接受的 trace 量
```
- 使用代码的方式 [SamplerConfig.java]
- PS: 代码和配置文件: 只使用一种方式即可
- PS: 采样策略也使用一个即可, 通常只使用限速采集
---
## SpringCloud Sleuth + Zipkin 分布式日志追踪总结
- SpringCloud sleuth + Zipkin 逻辑架构
- 跟踪、收集所涉及到的三个组件(模块) Sleuth、Zipkin、Brave
- 三个组件之间的关系
- Brave 是一个 tracer 库,提供的是 tracer 接囗
- Sleuth 采用了 Brave 作为 tracer库
- Sleuth可以不使用 Zipkin
- ![SpringCloudsleuthZipkin逻辑架构.png](pic/SpringCloudsleuthZipkin逻辑架构.png)
---
- Brave 解读
- Brave 的两个最基本、也是最核心的概念
- trace: 以看作是一个逻辑执行过程中的整个链条
- span:是 trace 跟踪的基本单位
```java
void order(){
GoodsService();
AccountService();
}
void Accountservice(){
DeductBalance();
}
```
```text
Span: Order _
/ \ |
Child Child |
/ \ |
Span: Goods Span: Account Trace
| |
Child |
| |
Span: Deduct -
```
---
- Brave 中常用的数据结构及其说明
- Tracing:工具类,用于生成 Tracer 类实例
- Tracer :也是工具类,用于生成 Span
- Span:实际记录每个功能块执行信息的类
- TraceContext :记录 trace 的执行过程中的元数据信息类
- Propagation :用于在分布式环境或者跨进程条件下的trace 跟踪时实现 TraceContext 传递的工具类
- ![Brave中常用的数据结构及其说明.png](pic/Brave中常用的数据结构及其说明.png)
---
- SpringCloud Sleuth 实现跨服务 Trace 追踪
- SpringCloud Sleuth 和 Brave 提供了很多不同的分布式框架的支持,例如 qRPC、Kafka、HTTP 等
- Brave 实现跨服务(或者跨线程)Trace追踪的核心是通过 TraceContext 中核心信息的传递来实现的
- 样例:Brave 针对 HTTP 服务进行Context 传递的标准流程
- ![实现跨服务Trace追踪.png](pic/实现跨服务Trace追踪.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.example</groupId>
<artifactId>dev-protocol</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>dev-protocol-springcloud-sleuth-zipkin</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- sleuth -->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-sleuth</artifactId>-->
<!-- </dependency>-->
<!-- zipkin = spring-cloud-starter-sleuth + spring-cloud-sleuth-zipkin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<!-- kafka -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>

@ -0,0 +1,11 @@
package org.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SleuthApplication {
public static void main(String[] args) {
SpringApplication.run(SleuthApplication.class, args);
}
}

@ -0,0 +1,30 @@
package org.example.controller;
import lombok.extern.slf4j.Slf4j;
import org.example.service.SleuthTraceInfoService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <h1></h1>
* */
@Slf4j
@RestController
@RequestMapping("/sleuth")
public class SleuthTraceInfoController {
private final SleuthTraceInfoService traceInfoService;
public SleuthTraceInfoController(SleuthTraceInfoService traceInfoService) {
this.traceInfoService = traceInfoService;
}
/**
* <h2></h2>
* */
@GetMapping("/trace-info")
public void logCurrentTraceInfo() {
traceInfoService.logCurrentTraceInfo();
}
}

@ -0,0 +1,28 @@
package org.example.sampler;
import brave.sampler.RateLimitingSampler;
import brave.sampler.Sampler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* <h2>使</h2>
* */
@Configuration
public class SamplerConfig {
/**
* <h2></h2>
* */
@Bean
public Sampler sampler() {
return RateLimitingSampler.create(100);
}
// /**
// * <h2>概率采集, 默认的采样策略, 默认值是 0.1</h2>
// * */
// @Bean
// public Sampler defaultSampler() {
// return ProbabilityBasedSampler.create(0.5f);
// }
}

@ -0,0 +1,29 @@
package org.example.service;
import brave.Tracer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* <h1>使 Sleuth </h1>
* */
@Slf4j
@Service
public class SleuthTraceInfoService {
/** brave.Tracer 跟踪对象 */
private final Tracer tracer;
public SleuthTraceInfoService(Tracer tracer) {
this.tracer = tracer;
}
/**
* <h2></h2>
* */
public void logCurrentTraceInfo() {
log.info("Sleuth trace id: [{}]", tracer.currentSpan().context().traceId());
log.info("Sleuth span id: [{}]", tracer.currentSpan().context().spanId());
}
}

@ -0,0 +1,76 @@
server:
port: 9999
servlet:
context-path: /dev-protocol-springcloud-sleuth-zipkin
spring:
application:
name: dev-protocol-springcloud-sleuth-zipkin # 应用名称也是构成 Nacos 配置管理 dataId 字段的一部分 (当 config.prefix 为空时)
cloud:
nacos:
# 服务注册发现
discovery:
enabled: true # 如果不想使用 Nacos 进行服务注册和发现, 设置为 false 即可
server-addr: 127.0.0.1:8848
# server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850 # Nacos 服务器地址, 集群模式
# todo 根据你自己的业务来进行区分找到
namespace: 1ccc74ae-9398-4dbe-b9d7-4f9addf9f40c
# group: e-commerce
# metadata:
# management:
# context-path: ${server.servlet.context-path}/actuator
# # 配置管理
# config:
# prefix: imooc-e-commerce
# file-extension: yaml # 配置内容的数据格式, 默认为 properties
# enabled: true # 如果不想使用 Nacos 进行配置管理, 设置为 false 即可
# group: DEFAULT_GROUP # 组, 默认为 DEFAULT_GROUP
# namespace: 1bc13fd5-843b-4ac0-aa55-695c25bc0ac6
# server-addr: 127.0.0.1:8848
kafka:
bootstrap-servers: 127.0.0.1:9092
producer:
retries: 3
consumer:
auto-offset-reset: latest
sleuth:
sampler:
# ProbabilityBasedSampler 抽样策略
probability: 1.0 # 采样比例, 1.0 表示 100%, 默认是 0.1
# RateLimitingSampler 抽样策略, 设置了限速采集, spring.sleuth.sampler.probability 属性值无效
rate: 100 # 每秒间隔接受的 trace 量
zipkin:
sender:
type: kafka # 默认是 web
base-url: http://localhost:9411/
main:
allow-bean-definition-overriding: true
#
## Feign 的相关配置
#feign:
# # feign 开启 gzip 压缩
# compression:
# request:
# enabled: true
# mime-types: text/xml,application/xml,application/json
# min-request-size: 1024
# response:
# enabled: true
# # 禁用默认的 http, 启用 okhttp
# httpclient:
# enabled: false
# okhttp:
# enabled: true
# # OpenFeign 集成 Hystrix
# hystrix:
# enabled: true
# 暴露端点
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always

@ -0,0 +1,5 @@
### 查看 Sleuth 跟踪信息
GET http://127.0.0.1:9001/imooc/dev-protocol-spring-cloud-nacos/sleuth/trace-info
Accept: application/json
e-commerce-user: eyJhbGciOiJSUzI1NiJ9.eyJlLWNvbW1lcmNlLXVzZXIiOiJ7XCJpZFwiOjE3LFwidXNlcm5hbWVcIjpcIkltb29jUWlueWlAaW1vb2MuY29tXCJ9IiwianRpIjoiMGIxNzQyYWItMWU3OC00OTZjLWIyNTAtMjNkZGQ1ZGEyZTU1IiwiZXhwIjoxNjI0MjA0ODAwfQ.QKGHzohSHdYDHzUVHpe9gNPUgzfkPwrSbB-WiMWYjLlt2tr9BufzZM8bSt-whb_bd0hKoC6rkYYO0WUVR67uSML-2yaTL1xMIn8GH9Flyig3rpO4vefL3Hp2TXIpwHHa7WlKsLzcUpNk9lxWs2B5k0ICdYCH_jD5Tx6N7CzfSUG9u4fOnVeM9UFE2nX_DURupUh_DKCc2oOoMeyCSR7Ma8-Ab4WQU3r-U0YivR8G1A0kmKOIoTeRhM3LcPuxUPh3rCbrjzMg--fexRGw0O38Qsby6pz-ku2IlTyFXY6_jNOG1BZR34-jBOnaIciP1TExw9bFumeuC2GcowTHJVH1Nw
token: imooc

@ -7,11 +7,10 @@
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<modelVersion>4.0.0</modelVersion>
<artifactId>dev-protocol-springcloud</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>

@ -53,6 +53,10 @@
<module>dev-protocol-springcloud/dev-protocol-springcloud-communication</module>
<module>dev-protocol-springcloud/dev-protocol-springcloud-admin</module>
<module>dev-protocol-springcloud/dev-protocol-springcloud-nacos</module>
<module>dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin</module>
<module>dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center</module>
<module>dev-protocol-springcloud/dev-protocol-springcloud-project-mvc-config</module>
<module>dev-protocol-springcloud/dev-protocol-springcloud-project-common</module>
</modules>
<properties>

Loading…
Cancel
Save