diff --git a/dev-protocol-springcloud/SpringCloud项目介绍.md b/dev-protocol-springcloud/SpringCloud项目介绍.md index 8f585e7..486c1de 100644 --- a/dev-protocol-springcloud/SpringCloud项目介绍.md +++ b/dev-protocol-springcloud/SpringCloud项目介绍.md @@ -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) @@ -18,4 +18,17 @@ - 网关动态限流 - (SpringCloud Alibaba Sentinel) - todo - 微服务工程部署与整体可用性验证 - - todo \ No newline at end of file + - todo + + +## 通用工程模块 +- 授权、鉴权中心微服务 + - [dev-protocol-springcloud-project-authority-center](dev-protocol-springcloud-project-authority-center) + +- 用户账户微服务 + +- 商品微服务 + +- 订单微服务 + +- 物流微服务 \ No newline at end of file diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/pic/授权鉴权中心微服务功能逻辑架构.png b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/pic/授权鉴权中心微服务功能逻辑架构.png new file mode 100644 index 0000000..d3f1ab1 Binary files /dev/null and b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/pic/授权鉴权中心微服务功能逻辑架构.png differ diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/pom.xml b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/pom.xml new file mode 100644 index 0000000..f8c7596 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/pom.xml @@ -0,0 +1,98 @@ + + + 4.0.0 + + org.example + dev-protocol + 1.0-SNAPSHOT + ../../pom.xml + + + dev-protocol-springcloud-project-authority-center + 1.0-SNAPSHOT + jar + + + dev-protocol-springcloud-project-authority-center + 授权中心 + + + 8 + 8 + UTF-8 + + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + mysql + mysql-connector-java + 8.0.12 + runtime + + + + org.example + dev-protocol-springcloud-project-mvc-config + 1.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-test + + + + + + + + org.springframework.kafka + spring-kafka + + + + org.freemarker + freemarker + 2.3.30 + + + cn.smallbun.screw + screw-core + 1.0.3 + + + + + + ${artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + + \ No newline at end of file diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/java/org/example/AuthorityCenterApplication.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/java/org/example/AuthorityCenterApplication.java new file mode 100644 index 0000000..99faaed --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/java/org/example/AuthorityCenterApplication.java @@ -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; + +/** + *

授权中心启动入口

+ * */ +@EnableJpaAuditing // 允许 Jpa 自动审计 +@EnableDiscoveryClient +@SpringBootApplication +public class AuthorityCenterApplication { + public static void main(String[] args) { + + SpringApplication.run(AuthorityCenterApplication.class, args); + } +} \ No newline at end of file diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/java/org/example/constant/AuthorityConstant.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/java/org/example/constant/AuthorityConstant.java new file mode 100644 index 0000000..ea0006d --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/java/org/example/constant/AuthorityConstant.java @@ -0,0 +1,13 @@ +package org.example.constant; + +/** + *

授权需要使用的一些常量信息

+ * */ +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; +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/java/org/example/controller/AuthorityController.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/java/org/example/controller/AuthorityController.java new file mode 100644 index 0000000..5a3ca42 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/java/org/example/controller/AuthorityController.java @@ -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; + +/** + *

对外暴露的授权服务接口

+ * */ +@Slf4j +@RestController +@RequestMapping("/authority") +public class AuthorityController { + + private final IJWTService ijwtService; + + public AuthorityController(IJWTService ijwtService) { + this.ijwtService = ijwtService; + } + + /** + *

从授权中心获取 Token (其实就是登录功能), 且返回信息中没有统一响应的包装

+ * */ + @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() + )); + } + + /** + *

注册用户并返回当前注册用户的 Token, 即通过授权中心创建用户

+ * */ + @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 + )); + } +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/java/org/example/dao/UserDao.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/java/org/example/dao/UserDao.java new file mode 100644 index 0000000..ec60e18 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/java/org/example/dao/UserDao.java @@ -0,0 +1,22 @@ +package org.example.dao; + +import org.example.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; + +/** + *

User Dao 接口定义

+ * */ +public interface UserDao extends JpaRepository { + + /** + *

根据用户名查询 User 对象

+ * select * from t_dev_protocol_cloud_user where username = ? + * */ + User findByUsername(String username); + + /** + *

根据用户名和密码查询实体对象

+ * select * from t_dev_protocol_cloud_user where username = ? and password = ? + * */ + User findByUsernameAndPassword(String username, String password); +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/java/org/example/entity/User.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/java/org/example/entity/User.java new file mode 100644 index 0000000..c376f76 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/java/org/example/entity/User.java @@ -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; + +/** + *

用户表实体类定义

+ * */ +@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; +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/java/org/example/service/IJWTService.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/java/org/example/service/IJWTService.java new file mode 100644 index 0000000..e3f41fe --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/java/org/example/service/IJWTService.java @@ -0,0 +1,26 @@ +package org.example.service; + + +import org.example.vo.UsernameAndPassword; + +/** + *

JWT 相关服务接口定义

+ * */ +public interface IJWTService { + + /** + *

生成 JWT Token, 使用默认的超时时间

+ * */ + String generateToken(String username, String password) throws Exception; + + /** + *

生成指定超时时间的 Token, 单位是天

+ * */ + String generateToken(String username, String password, int expire) throws Exception; + + /** + *

注册用户并生成 Token 返回

+ * */ + String registerUserAndGenerateToken(UsernameAndPassword usernameAndPassword) + throws Exception; +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/java/org/example/service/impl/JWTServiceImpl.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/java/org/example/service/impl/JWTServiceImpl.java new file mode 100644 index 0000000..059ce1e --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/java/org/example/service/impl/JWTServiceImpl.java @@ -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; + +/** + *

JWT 相关服务接口实现

+ * */ +@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()); + } + + /** + *

根据本地存储的私钥获取到 PrivateKey 对象

+ * */ + private PrivateKey getPrivateKey() throws Exception { + + PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec( + new BASE64Decoder().decodeBuffer(AuthorityConstant.PRIVATE_KEY)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return keyFactory.generatePrivate(priPKCS8); + } +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/resources/bootstrap.yml b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..ebf7d22 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/resources/bootstrap.yml @@ -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 diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/resources/http/authority.http b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/resources/http/authority.http new file mode 100644 index 0000000..cea788e --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/resources/http/authority.http @@ -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" +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/resources/sql/t_ecommerce_user.sql b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/resources/sql/t_ecommerce_user.sql new file mode 100644 index 0000000..e698bc7 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/main/resources/sql/t_ecommerce_user.sql @@ -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='用户表'; diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/test/java/org/example/AuthorityCenterApplicationTests.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/test/java/org/example/AuthorityCenterApplicationTests.java new file mode 100644 index 0000000..03945be --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/test/java/org/example/AuthorityCenterApplicationTests.java @@ -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; + +/** + *

授权中心测试入口

+ * 验证授权中心环境可用性 + * */ +@SpringBootTest +@RunWith(SpringRunner.class) +public class AuthorityCenterApplicationTests { + + @Test + public void contextLoad() { + + } +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/test/java/org/example/dao/UserDaoTest.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/test/java/org/example/dao/UserDaoTest.java new file mode 100644 index 0000000..06d0d66 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/test/java/org/example/dao/UserDaoTest.java @@ -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; + + +/** + *

User 相关的测试

+ * */ +@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))); + } +} \ No newline at end of file diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/test/java/org/example/service/RSATest.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/test/java/org/example/service/RSATest.java new file mode 100644 index 0000000..84bf968 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/src/test/java/org/example/service/RSATest.java @@ -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; + +/** + *

RSA 非对称加密算法: 生成公钥和私钥

+ * */ +@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加密解密为字符串。 + } +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/电商-授权鉴权服务.md b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/电商-授权鉴权服务.md new file mode 100644 index 0000000..7c3e020 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center/电商-授权鉴权服务.md @@ -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 的授权 + +## 验证服务可用性 + +## 授权、鉴权中心微服务总结 \ No newline at end of file diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-common/pom.xml b/dev-protocol-springcloud/dev-protocol-springcloud-project-common/pom.xml new file mode 100644 index 0000000..a3c6db4 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-common/pom.xml @@ -0,0 +1,55 @@ + + + 4.0.0 + + org.example + dev-protocol + 1.0-SNAPSHOT + ../../pom.xml + + + dev-protocol-springcloud-project-common + 1.0-SNAPSHOT + jar + + dev-protocol-springcloud-project-common + 通用模块 + + + 8 + 8 + UTF-8 + 0.9.1 + + + + + io.jsonwebtoken + jjwt + ${jjwt.version} + + + org.projectlombok + lombok + + + org.apache.commons + commons-lang3 + 3.12.0 + + + cn.hutool + hutool-all + 5.7.22 + + + + com.alibaba.fastjson2 + fastjson2 + 2.0.18 + + + + \ No newline at end of file diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-common/src/main/java/org/example/constant/CommonConstant.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-common/src/main/java/org/example/constant/CommonConstant.java new file mode 100644 index 0000000..d65c0c2 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-common/src/main/java/org/example/constant/CommonConstant.java @@ -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"; + +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-common/src/main/java/org/example/utils/TokenParseUtils.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-common/src/main/java/org/example/utils/TokenParseUtils.java new file mode 100644 index 0000000..289a345 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-common/src/main/java/org/example/utils/TokenParseUtils.java @@ -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 { + + /** + *

从 JWT Token 中解析 LoginUserInfo 对象

+ */ + public static LoginUserInfo parseUserInfoFromToken(String token) throws Exception { + if (null == token) { + return null; + } + + Jws 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 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); + } + + +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-common/src/main/java/org/example/vo/CommonResponse.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-common/src/main/java/org/example/vo/CommonResponse.java new file mode 100644 index 0000000..07977c9 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-common/src/main/java/org/example/vo/CommonResponse.java @@ -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 implements Serializable { + + /** 错误码 */ + private Integer code; + + /** 错误消息 */ + private String message; + + /** 泛型响应数据 */ + private T Data; + + public CommonResponse(Integer code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-common/src/main/java/org/example/vo/JwtToken.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-common/src/main/java/org/example/vo/JwtToken.java new file mode 100644 index 0000000..478719a --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-common/src/main/java/org/example/vo/JwtToken.java @@ -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; + +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-common/src/main/java/org/example/vo/LoginUserInfo.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-common/src/main/java/org/example/vo/LoginUserInfo.java new file mode 100644 index 0000000..22320fb --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-common/src/main/java/org/example/vo/LoginUserInfo.java @@ -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; + +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-common/src/main/java/org/example/vo/UsernameAndPassword.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-common/src/main/java/org/example/vo/UsernameAndPassword.java new file mode 100644 index 0000000..05171dd --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-common/src/main/java/org/example/vo/UsernameAndPassword.java @@ -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; + +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-mvc-config/pom.xml b/dev-protocol-springcloud/dev-protocol-springcloud-project-mvc-config/pom.xml new file mode 100644 index 0000000..225a19c --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-mvc-config/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + org.example + dev-protocol + 1.0-SNAPSHOT + ../../pom.xml + + + dev-protocol-springcloud-project-mvc-config + + + dev-protocol-springcloud-project-mvc-config + 通用配置模块 + + + 8 + 8 + UTF-8 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.example + dev-protocol-springcloud-project-common + 1.0-SNAPSHOT + + + + \ No newline at end of file diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-mvc-config/src/main/java/org/example/Main.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-mvc-config/src/main/java/org/example/Main.java new file mode 100644 index 0000000..407f157 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-mvc-config/src/main/java/org/example/Main.java @@ -0,0 +1,7 @@ +package org.example; + +public class Main { + public static void main(String[] args) { + System.out.println("Hello world!"); + } +} \ No newline at end of file diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-mvc-config/src/main/java/org/example/advice/CommonResponseDataAdvice.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-mvc-config/src/main/java/org/example/advice/CommonResponseDataAdvice.java new file mode 100644 index 0000000..2ccb898 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-mvc-config/src/main/java/org/example/advice/CommonResponseDataAdvice.java @@ -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 实现统一响应 + * ResponseBodyAdvice接口是在Controller执行return之后,在response返回给浏览器或者APP客户端之前,执行的对response的一些处理。可以实现对response数据的一些统一封装或者加密等操作 + */ +//声明该类要处理的包路径 +@RestControllerAdvice("org.example") +public class CommonResponseDataAdvice implements ResponseBodyAdvice { + /** + * 判断是否要执行beforeBodyWrite方法,true为执行,false不执行 + * 通过supports方法,我们可以选择哪些类,或者哪些方法要对response进行处理,其余的则不处理。 + * + * @param returnType + * @param converterType + * @return + */ + @Override + @SuppressWarnings("all") + public boolean supports(MethodParameter returnType, Class> 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> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { + + // 定义最终的返回对象 + CommonResponse commonResponse = new CommonResponse<>(0, ""); + + if (body == null) { + return commonResponse; + } else if (body instanceof CommonResponse) { + commonResponse = (CommonResponse) body; + } else { + commonResponse.setData(body); + } + return commonResponse; + } +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-mvc-config/src/main/java/org/example/advice/GlobalExceptionAdvice.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-mvc-config/src/main/java/org/example/advice/GlobalExceptionAdvice.java new file mode 100644 index 0000000..9b60974 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-mvc-config/src/main/java/org/example/advice/GlobalExceptionAdvice.java @@ -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 handlerCommerceException(Exception ex) { + CommonResponse response = new CommonResponse<>(-1, "business error"); + response.setData(ex.getMessage()); + log.error("commerce service has error: [{}]", ex.getMessage(), ex); + return response; + } +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-mvc-config/src/main/java/org/example/annotation/IgnoreResponseAdvice.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-mvc-config/src/main/java/org/example/annotation/IgnoreResponseAdvice.java new file mode 100644 index 0000000..c114280 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-mvc-config/src/main/java/org/example/annotation/IgnoreResponseAdvice.java @@ -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 { +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/SpringCloud-Sleuth+Zipkin.md b/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/SpringCloud-Sleuth+Zipkin.md new file mode 100644 index 0000000..cda430c --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/SpringCloud-Sleuth+Zipkin.md @@ -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 (服务与服务之间的时间消耗)
+> -Rest[FLAG]-> Service C +--- + +- SpringCloud Sleuth 跟踪实现原理 + - 为了实现请求跟踪:当请求发送到分布式系统的入口端点时,只需要服务跟踪框架为该请求创建一个唯一的跟踪标识,Trace ID + - 为了统计各处理单元的时间延迟,当请求到达各个服务组件时,或是处理逻辑到达某个状态时,也通过一个唯一标识来标记它的开始、具体过程以及结束,Span ID + +> Service A[traceld, spanldA] -> Service B[traceld, spanldA1] -> Service D[traceld, spanldA11]
+> -> 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 + + + org.springframework.cloud + spring-cloud-starter-zipkin + +``` +- 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) diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/pic/Brave中常用的数据结构及其说明.png b/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/pic/Brave中常用的数据结构及其说明.png new file mode 100644 index 0000000..743d2d0 Binary files /dev/null and b/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/pic/Brave中常用的数据结构及其说明.png differ diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/pic/SpringCloudsleuthZipkin逻辑架构.png b/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/pic/SpringCloudsleuthZipkin逻辑架构.png new file mode 100644 index 0000000..778b09d Binary files /dev/null and b/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/pic/SpringCloudsleuthZipkin逻辑架构.png differ diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/pic/ZipkinWebUI.png b/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/pic/ZipkinWebUI.png new file mode 100644 index 0000000..5c9dc6e Binary files /dev/null and b/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/pic/ZipkinWebUI.png differ diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/pic/Zipkin流程图.png b/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/pic/Zipkin流程图.png new file mode 100644 index 0000000..4e71691 Binary files /dev/null and b/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/pic/Zipkin流程图.png differ diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/pic/实现跨服务Trace追踪.png b/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/pic/实现跨服务Trace追踪.png new file mode 100644 index 0000000..e7863f0 Binary files /dev/null and b/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/pic/实现跨服务Trace追踪.png differ diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/pom.xml b/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/pom.xml new file mode 100644 index 0000000..f38f755 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + org.example + dev-protocol + 1.0-SNAPSHOT + ../../pom.xml + + + dev-protocol-springcloud-sleuth-zipkin + + + 8 + 8 + UTF-8 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + + + + + org.springframework.cloud + spring-cloud-starter-zipkin + + + + org.springframework.kafka + spring-kafka + + + + org.projectlombok + lombok + + + + \ No newline at end of file diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/src/main/java/org/example/SleuthApplication.java b/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/src/main/java/org/example/SleuthApplication.java new file mode 100644 index 0000000..cbcf73d --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/src/main/java/org/example/SleuthApplication.java @@ -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); + } +} \ No newline at end of file diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/src/main/java/org/example/controller/SleuthTraceInfoController.java b/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/src/main/java/org/example/controller/SleuthTraceInfoController.java new file mode 100644 index 0000000..f0798f1 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/src/main/java/org/example/controller/SleuthTraceInfoController.java @@ -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; + +/** + *

打印跟踪信息

+ * */ +@Slf4j +@RestController +@RequestMapping("/sleuth") +public class SleuthTraceInfoController { + + private final SleuthTraceInfoService traceInfoService; + + public SleuthTraceInfoController(SleuthTraceInfoService traceInfoService) { + this.traceInfoService = traceInfoService; + } + + /** + *

打印日志跟踪信息

+ * */ + @GetMapping("/trace-info") + public void logCurrentTraceInfo() { + traceInfoService.logCurrentTraceInfo(); + } +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/src/main/java/org/example/sampler/SamplerConfig.java b/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/src/main/java/org/example/sampler/SamplerConfig.java new file mode 100644 index 0000000..aedd4da --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/src/main/java/org/example/sampler/SamplerConfig.java @@ -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; + +/** + *

使用配置的方式设定抽样率

+ * */ +@Configuration +public class SamplerConfig { + /** + *

限速采集

+ * */ + @Bean + public Sampler sampler() { + return RateLimitingSampler.create(100); + } + +// /** +// *

概率采集, 默认的采样策略, 默认值是 0.1

+// * */ +// @Bean +// public Sampler defaultSampler() { +// return ProbabilityBasedSampler.create(0.5f); +// } +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/src/main/java/org/example/service/SleuthTraceInfoService.java b/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/src/main/java/org/example/service/SleuthTraceInfoService.java new file mode 100644 index 0000000..26a3084 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/src/main/java/org/example/service/SleuthTraceInfoService.java @@ -0,0 +1,29 @@ +package org.example.service; + +import brave.Tracer; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + *

使用代码更直观的看到 Sleuth 生成的相关跟踪信息

+ * */ +@Slf4j +@Service +public class SleuthTraceInfoService { + + /** brave.Tracer 跟踪对象 */ + private final Tracer tracer; + + public SleuthTraceInfoService(Tracer tracer) { + this.tracer = tracer; + } + + /** + *

打印当前的跟踪信息到日志中

+ * */ + public void logCurrentTraceInfo() { + + log.info("Sleuth trace id: [{}]", tracer.currentSpan().context().traceId()); + log.info("Sleuth span id: [{}]", tracer.currentSpan().context().spanId()); + } +} \ No newline at end of file diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/src/main/resources/bootstrap.yml b/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..b7fbcb9 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/src/main/resources/bootstrap.yml @@ -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 diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/src/main/resources/http/sleuth.http b/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/src/main/resources/http/sleuth.http new file mode 100644 index 0000000..0b1ac0c --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin/src/main/resources/http/sleuth.http @@ -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 \ No newline at end of file diff --git a/dev-protocol-springcloud/pom.xml b/dev-protocol-springcloud/pom.xml index bccc41f..3d1960d 100644 --- a/dev-protocol-springcloud/pom.xml +++ b/dev-protocol-springcloud/pom.xml @@ -7,11 +7,10 @@ org.example 1.0-SNAPSHOT - 4.0.0 + 4.0.0 dev-protocol-springcloud 1.0-SNAPSHOT - pom diff --git a/pom.xml b/pom.xml index 2cc4c2e..076a458 100644 --- a/pom.xml +++ b/pom.xml @@ -53,6 +53,10 @@ dev-protocol-springcloud/dev-protocol-springcloud-communication dev-protocol-springcloud/dev-protocol-springcloud-admin dev-protocol-springcloud/dev-protocol-springcloud-nacos + dev-protocol-springcloud/dev-protocol-springcloud-sleuth-zipkin + dev-protocol-springcloud/dev-protocol-springcloud-project-authority-center + dev-protocol-springcloud/dev-protocol-springcloud-project-mvc-config + dev-protocol-springcloud/dev-protocol-springcloud-project-common