项目背景介绍
这个技术我是直接在项目中运用并且学习的,所以我写笔记最优先的角度就是从项目背景出发
继上一次API网关完成了这个实现用户调用一次接口之后让接口次数增多的操作之后,又迎来了新的问题。
就是我们在调用接口的时候需要对用户进行校验,对调用的接口是否存在进行验证。
从这个需求出发,我们第一反应想到的解决办法是什么,应该是在api-gateway项目中也来引入一下这个数据库配置,包括三层架构重新写一轮,包括实体类也需要引入一下
这样做的问题其实很明显,就是代码很冗余,如果以后微服务的模块开发多了,那每个模块都需要引入这个数据库巴拉巴拉一堆配置,这就很麻烦,也不易于维护
所以从实际需求出发,我们就需要用到这个微服务中的远程调用的操作(我感觉对于微服务来说这其实应该是一个很基础的操作,但是我写这个项目的时候,微服务还没学到远程调用)
首先我们可以先来简单想一下这个我们如果想在其它项目中使用其它项目的方法,我们可以怎么做
- 我们可以用笨办法,直接cv,然后搭环境配依赖
- HTTP 请求(提供一个接口,供其他项目调用)(这个方式我没有用过,看鱼皮的笔记先进行一个记录)
- 第三个就是RPC
- 第四个其实我们也肯定知道,就是打个jar包,这也有点类似于自定义start的方式
根据项目背景我们就可以想到用RPC和Dubbo框架来实现远程调用的方式了。
简单的概念介绍:
什么是RPC?
作用:像调用本地方法一样调用远程方法。
和直接 HTTP 调用的区别:
- 对开发者更透明,减少了很多的沟通成本。
- RPC 向远程服务器发送请求时,未必要使用 HTTP 协议,比如还可以用 TCP / IP,性能更高。(内部服务更适用)
什么理解这个像调用本地方法一样调用远程方法,就是比如我们在我们的项目中的三层架构,controller直接调用mapper那样方便。
远程调用的流程:
为什么可以远程调用呢,
首先我们明白调用,肯定是有一方提供方法,一方调用你提供的方法
这里就会有三个角色,提供者调用方和注册中心
提供者根据地址先将方法注册到注册中心,
注册中心进行一个存储,
然后调用方也根据地址去注册中心里面去取你注册到里面的方法
这里的注册中心我用的是后面会记录的nacos。
什么是Dubbo框架:
这个是官方的基于springboot的案例3 - 基于 Spring Boot Starter 开发微服务应用 | Apache Dubbo
这里也有点坑,官方的这个案例里面用的是nacos,如果你没有下载和开启nacos啊,直接报错
我搞这个搞了半天才发现要下载nacos
Dubbo 是一个高性能的Java RPC(远程过程调用)框架,最初由阿里巴巴开发并开源。它主要用于构建分布式服务,可以让服务提供者和消费者之间进行高效、安全的通信。
我们简单理解为就是封装了一些方法让我们可以非常简单的使用这个RPC进行远程调用。
nacos注册中心:
nacos注册中心得单击非持久化搭建:
- 单机:指的是 Nacos 运行在单个实例上,通常用于开发和测试环境。
- 非持久化:表示注册的信息(如服务实例、元数据等)不会被保存在数据库中。Nacos 运行时的数据是在内存中,重启或崩溃后,所有的注册信息将会丢失。
优点:
- 简单易用,启动速度快,适合开发测试用。
- 无需额外配置数据库。
缺点:
- 数据不持久化,产品环境不适用,无法保证服务注册信息的持久性和可靠性。
官网上有详细步骤:
1:详细上来说就是下载解压压缩包
2:注册服务并用startup.cmd -m standalone命令开启,注意这里开发需要到nacos的bin目录下打开cmd输入startup.cmd -m standalone开启
3:第三步我们就可以照着这上面的网址进行访问了
进入之后,如果我们有注册方法的话,这里就会有提示。
4:根据Dubbo框架的官方文档来使用nacos注册中心:
总体说起来就是、
引入对应依赖(包括dubbo的依赖和nacos的依赖)
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>3.0.9</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>2.1.0</version>
</dependency>
在配置文件中配置
# 以下配置指定了应用的名称、使用的协议(Dubbo)、注册中心的类型(Nacos)和地址
dubbo:
application:
# 设置应用的名称
name: dubbo-springboot-demo-provider
# 指定使用 Dubbo 协议,且端口设置为 -1,表示随机分配可用端口
protocol:
name: dubbo
port: -1
registry:
# 配置注册中心为 Nacos,使用的地址是 nacos://localhost:8848
id: nacos-registry
address: nacos://localhost:8848
这些东西直接复制官网就行。
注意点:
这些配置和依赖在provider引入了,在consumer也需要引入,并且需要保持一致
上面是在本地部署,本地部署相对简单安装一个压缩包,输入命令即可。
nacos注册中心的持久化到 MySQL 模式:
- 持久化:表示 Nacos 运行时的服务注册信息会保存在 MySQL 数据库中。这意味着即使 Nacos 实例重启或发生故障,注册的信息仍然可以恢复,确保服务的可用性和数据的一致性。
优点:
- 数据持久化,增强了系统的可靠性,适合生产环境。
- 支持高可用部署,可以通过多个 Nacos 实例搭建集群。
缺点:
- 需要配置和维护 MySQL 数据库,增加了部署成本和复杂性。
- 启动和初次配置可能会稍显复杂。
我使用得是docker容器得搭建方式
1:需要配置mysql的脚本
-- --------------------------------------------------------
-- 主机: 192.168.150.101
-- 服务器版本: 8.0.27 - MySQL Community Server - GPL
-- 服务器操作系统: Linux
-- HeidiSQL 版本: 12.2.0.6576
-- --------------------------------------------------------
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8 */;
/*!50503 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
-- 导出 nacos 的数据库结构
DROP DATABASE IF EXISTS `nacos`;
CREATE DATABASE IF NOT EXISTS `nacos` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
USE `nacos`;
-- 导出 表 nacos.config_info 结构
DROP TABLE IF EXISTS `config_info`;
CREATE TABLE IF NOT EXISTS `config_info` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id',
`group_id` varchar(128) COLLATE utf8_bin DEFAULT NULL,
`content` longtext COLLATE utf8_bin NOT NULL COMMENT 'content',
`md5` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COLLATE utf8_bin COMMENT 'source user',
`src_ip` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT 'source ip',
`app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL,
`tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT '租户字段',
`c_desc` varchar(256) COLLATE utf8_bin DEFAULT NULL,
`c_use` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`effect` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`type` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`c_schema` text COLLATE utf8_bin,
`encrypted_data_key` text COLLATE utf8_bin NOT NULL COMMENT '秘钥',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='config_info';
-- 正在导出表 nacos.config_info 的数据:~0 rows (大约)
DELETE FROM `config_info`;
-- 导出 表 nacos.config_info_aggr 结构
DROP TABLE IF EXISTS `config_info_aggr`;
CREATE TABLE IF NOT EXISTS `config_info_aggr` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id',
`group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id',
`datum_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'datum_id',
`content` longtext COLLATE utf8_bin NOT NULL COMMENT '内容',
`gmt_modified` datetime NOT NULL COMMENT '修改时间',
`app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL,
`tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT '租户字段',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='增加租户字段';
-- 正在导出表 nacos.config_info_aggr 的数据:~0 rows (大约)
DELETE FROM `config_info_aggr`;
-- 导出 表 nacos.config_info_beta 结构
DROP TABLE IF EXISTS `config_info_beta`;
CREATE TABLE IF NOT EXISTS `config_info_beta` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id',
`group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id',
`app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL COMMENT 'app_name',
`content` longtext COLLATE utf8_bin NOT NULL COMMENT 'content',
`beta_ips` varchar(1024) COLLATE utf8_bin DEFAULT NULL COMMENT 'betaIps',
`md5` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COLLATE utf8_bin COMMENT 'source user',
`src_ip` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT 'source ip',
`tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT '租户字段',
`encrypted_data_key` text COLLATE utf8_bin NOT NULL COMMENT '秘钥',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='config_info_beta';
-- 正在导出表 nacos.config_info_beta 的数据:~0 rows (大约)
DELETE FROM `config_info_beta`;
-- 导出 表 nacos.config_info_tag 结构
DROP TABLE IF EXISTS `config_info_tag`;
CREATE TABLE IF NOT EXISTS `config_info_tag` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id',
`group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id',
`tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT 'tenant_id',
`tag_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'tag_id',
`app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL COMMENT 'app_name',
`content` longtext COLLATE utf8_bin NOT NULL COMMENT 'content',
`md5` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COLLATE utf8_bin COMMENT 'source user',
`src_ip` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT 'source ip',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='config_info_tag';
-- 正在导出表 nacos.config_info_tag 的数据:~0 rows (大约)
DELETE FROM `config_info_tag`;
-- 导出 表 nacos.config_tags_relation 结构
DROP TABLE IF EXISTS `config_tags_relation`;
CREATE TABLE IF NOT EXISTS `config_tags_relation` (
`id` bigint NOT NULL COMMENT 'id',
`tag_name` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'tag_name',
`tag_type` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT 'tag_type',
`data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id',
`group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id',
`tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT 'tenant_id',
`nid` bigint NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`nid`),
UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='config_tag_relation';
-- 正在导出表 nacos.config_tags_relation 的数据:~0 rows (大约)
DELETE FROM `config_tags_relation`;
-- 导出 表 nacos.group_capacity 结构
DROP TABLE IF EXISTS `group_capacity`;
CREATE TABLE IF NOT EXISTS `group_capacity` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`group_id` varchar(128) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',
`quota` int unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
`usage` int unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` int unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
`max_aggr_count` int unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值',
`max_aggr_size` int unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
`max_history_count` int unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';
-- 正在导出表 nacos.group_capacity 的数据:~0 rows (大约)
DELETE FROM `group_capacity`;
-- 导出 表 nacos.his_config_info 结构
DROP TABLE IF EXISTS `his_config_info`;
CREATE TABLE IF NOT EXISTS `his_config_info` (
`id` bigint unsigned NOT NULL,
`nid` bigint unsigned NOT NULL AUTO_INCREMENT,
`data_id` varchar(255) COLLATE utf8_bin NOT NULL,
`group_id` varchar(128) COLLATE utf8_bin NOT NULL,
`app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL COMMENT 'app_name',
`content` longtext COLLATE utf8_bin NOT NULL,
`md5` varchar(32) COLLATE utf8_bin DEFAULT NULL,
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`src_user` text COLLATE utf8_bin,
`src_ip` varchar(50) COLLATE utf8_bin DEFAULT NULL,
`op_type` char(10) COLLATE utf8_bin DEFAULT NULL,
`tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT '租户字段',
`encrypted_data_key` text COLLATE utf8_bin NOT NULL COMMENT '秘钥',
PRIMARY KEY (`nid`),
KEY `idx_gmt_create` (`gmt_create`),
KEY `idx_gmt_modified` (`gmt_modified`),
KEY `idx_did` (`data_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='多租户改造';
-- 正在导出表 nacos.his_config_info 的数据:~0 rows (大约)
DELETE FROM `his_config_info`;
-- 导出 表 nacos.permissions 结构
DROP TABLE IF EXISTS `permissions`;
CREATE TABLE IF NOT EXISTS `permissions` (
`role` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
`resource` varchar(255) COLLATE utf8mb4_general_ci NOT NULL,
`action` varchar(8) COLLATE utf8mb4_general_ci NOT NULL,
UNIQUE KEY `uk_role_permission` (`role`,`resource`,`action`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- 正在导出表 nacos.permissions 的数据:~0 rows (大约)
DELETE FROM `permissions`;
-- 导出 表 nacos.roles 结构
DROP TABLE IF EXISTS `roles`;
CREATE TABLE IF NOT EXISTS `roles` (
`username` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
`role` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
UNIQUE KEY `idx_user_role` (`username`,`role`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- 正在导出表 nacos.roles 的数据:~1 rows (大约)
DELETE FROM `roles`;
INSERT INTO `roles` (`username`, `role`) VALUES
('nacos', 'ROLE_ADMIN');
-- 导出 表 nacos.tenant_capacity 结构
DROP TABLE IF EXISTS `tenant_capacity`;
CREATE TABLE IF NOT EXISTS `tenant_capacity` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`tenant_id` varchar(128) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT 'Tenant ID',
`quota` int unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
`usage` int unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` int unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
`max_aggr_count` int unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',
`max_aggr_size` int unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
`max_history_count` int unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='租户容量信息表';
-- 正在导出表 nacos.tenant_capacity 的数据:~0 rows (大约)
DELETE FROM `tenant_capacity`;
-- 导出 表 nacos.tenant_info 结构
DROP TABLE IF EXISTS `tenant_info`;
CREATE TABLE IF NOT EXISTS `tenant_info` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
`kp` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'kp',
`tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT 'tenant_id',
`tenant_name` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT 'tenant_name',
`tenant_desc` varchar(256) COLLATE utf8_bin DEFAULT NULL COMMENT 'tenant_desc',
`create_source` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'create_source',
`gmt_create` bigint NOT NULL COMMENT '创建时间',
`gmt_modified` bigint NOT NULL COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='tenant_info';
-- 正在导出表 nacos.tenant_info 的数据:~0 rows (大约)
DELETE FROM `tenant_info`;
-- 导出 表 nacos.users 结构
DROP TABLE IF EXISTS `users`;
CREATE TABLE IF NOT EXISTS `users` (
`username` varchar(50) COLLATE utf8mb4_general_ci NOT NULL,
`password` varchar(500) COLLATE utf8mb4_general_ci NOT NULL,
`enabled` tinyint(1) NOT NULL,
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- 正在导出表 nacos.users 的数据:~1 rows (大约)
DELETE FROM `users`;
INSERT INTO `users` (`username`, `password`, `enabled`) VALUES
('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', 1);
/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */;
/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */;
这个直接在服务器配置好的mysql中执行即可。
2:创建nacos和mysql的配置文件:
可以新建一个文件夹nacos(这步是可选的,我是为了以后我方便找)
配置文件:
PREFER_HOST_MODE=hostname
MODE=standalone
SPRING_DATASOURCE_PLATFORM=mysql
MYSQL_SERVICE_HOST=自己的服务器ip地址
MYSQL_SERVICE_DB_NAME=nacos
MYSQL_SERVICE_PORT=3307//自己mysql的端口
MYSQL_SERVICE_USER=root
MYSQL_SERVICE_PASSWORD=123
MYSQL_SERVICE_DB_PARAM=characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
springdatasourceplatform就是指定了数据库的类型,说明nacos可以配置很多不同的数据库
接着往下就是自己的基本信息。
3:拉取镜像:
我这里拉取的是nacos/nacos-server:v2.1.0-slim
docker pull nacos/nacos-server:v2.1.0-slim
执行命令即可
4:在对应文件目录中创建容器并运行,并访问登录页
这个对应文件目录就是你nacos存放的目录
直接在客户端内cd 到当前目录
并且执行:
docker run -d \
--name nacos \
--env-file ./nacos/custom.env \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
--restart=always \
nacos/nacos-server:v2.1.0-slim
注意第三行的 ./nacos/custom.env \
如果你上面没有新建文件夹就修改一下这个路径。
启动完成后
我们也可以docker ps看一下
下一步需要注意
就是我用的是腾讯云的服务器
如果你装的是虚拟机那可以不用
如果是服务器,还要到外层去开一下对应的端口
需要开的端口有三个:
8848:这是 Nacos 的默认 HTTP 端口,用于提供 Nacos 的 Web 界面和服务 API
9848:这个端口是 Nacos 的 若干功能服务的卸载装载
9849:这个端口通常用于 Nacos 的 gRPC 相关服务
要不然服务器无法访问登录页
开放了端口之后就可以访问登录页了
账号和密码初始化都是nacos:
这样就配置好了
nacos的配置管理的功能:
我们可以把微服务共享的配置抽取到Nacos中统一管理,这样就不需要每个微服务都重复配置了。分为两步:
配置共享:
添加配置共享:
我们先看有那些配置是一个共享抽象的:
spring:
application:
name: user-service
profiles:
active: dev
datasource:
url: jdbc:mysql://ip地址:3307/hm-user?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123
cloud:
nacos:
server-addr: IP地址:8848 # nacos地址
mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
global-config:
db-config:
update-strategy: not_null
id-type: auto
logging:
level:
com.hmall: debug
pattern:
dateformat: HH:mm:ss:SSS
file:
path: "logs/${spring.application.name}"
knife4j:
enable: true
openapi:
title: 黑马商城用户接口文档
description: "黑马商城用户接口文档"
email: zhanghuyi@itcast.cn
concat: 虎哥
url: https://www.itcast.cn
version: v1.0.0
group:
default:
group-name: default
api-rule: package
api-rule-resources:
- com.hmall.userservice.controller
三个部分:
数据库jdbc,日志swagger和openfeign配置
spring:
datasource:
url: jdbc:mysql://${hm.db.host:IP地址}:${hm.db.port:3307}/${hm.db.database}?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: ${hm.db.pw}
mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
global-config:
db-config:
update-strategy: not_null
id-type: auto
只要上面不确定的都可以用占位符,后面再进行读取
日志配置
logging:
level:
com.hmall: debug
pattern:
dateformat: HH:mm:ss:SSS
file:
path: "logs/${spring.application.name}"
knife4j配置
knife4j:
enable: true
openapi:
title: ${hm.swagger.title:黑马商城购物车接口文档}
description: ${hm.swagger.desc:黑马商城购物车接口文档}
email: zhanghuyi@itcast.cn
concat: 虎哥
url: https://www.itcast.cn
version: v1.0.0
group:
default:
group-name: default
api-rule: package
api-rule-resources:
- ${hm.swagger.package}
拉取共享配置:
在拉取之前,我们需要先来明白一下项目启动的流程:
首先之前我们的springboot项目,先加载配置文件,再初始化bean
不过现在是springcloud的项目了,项目整体的启动流程变了
需要先读取bootstrap.yaml文件里面的配置
再去拉取nacos注册中心中的配置,然后才是springboot的项目启动一样
我们由上面的项目启动过程可知,
我们需要一个bootstrap配置文件
再此之前,我们先引入两个依赖:
<!--nacos配置管理-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--读取bootstrap文件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
在resources目录下新建一个配置文件
spring:
application:
name: cart-service # 服务名称
profiles:
active: dev
cloud:
nacos:
server-addr: IP地址 # nacos地址
config:
file-extension: yaml # 文件后缀名
shared-configs: # 共享配置
- dataId: shared-jdbc.yaml # 共享mybatis配置
- dataId: shared-log.yaml # 共享日志配置
- dataId: shared-swagger.yaml # 共享日志配置
这里面对应的配置名一定要对上就行
有了这个bootstrap配置文件之后,我们就可以简化application.yamll里面的属性了
server:
port: 8082
feign:
okhttp:
enabled: true # 开启OKHttp连接池支持
hm:
swagger:
title: 购物车服务接口文档
package: com.hmall.cart.controller
db:
database: hm-cart
讲完了一些基础概念,下面上一个Demo演示流程
先用Demo会更好入门会更好一点,我刚刚自己重新看了一遍流程,我自己在项目中将需要远程调用的方法抽象成了一个公共模块,所以,感觉理解起来会有点难。
实战:
Demo案例:
在实战之前我们需要引入上面的依赖
并且在springboot的启动类上加上
@EnableDubbo
@SpringBootApplication
@MapperScan("com.yupi.project.mapper")
@EnableDubbo
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
provider和consumer的启动类都要加
首先我们从远程调用的过程入手。
我们首先需要一个provider来提供方法
这里的提供者是master这个项目
我们在这个项目中新建了一个软件包叫provider,里面很简单就是一个接口来规定调用的方法
一个实现这个接口的实现类来重写里面的方法。
import java.util.concurrent.CompletableFuture;
public interface DemoService {
String sayHello(String name);
String sayHello2(String name);
default CompletableFuture<String> sayHelloAsync(String name) {
return CompletableFuture.completedFuture(sayHello(name));
}
}
package com.yupi.project.provider;
import org.apache.dubbo.config.annotation.DubboService;
import org.apache.dubbo.rpc.RpcContext;
@DubboService
public class DemoServiceImpl implements DemoService {
@Override
public String sayHello(String name) {
System.out.println("Hello " + name + ", request from consumer: " + RpcContext.getContext().getRemoteAddress());
return "Hello " + name;
}
@Override
public String sayHello2(String name) {
return "ljh";
}
}
很简单两个方法:输出hello和一个字符串ljh
根据流程下一步得到注册中心了:
看一下注册中心里面的信息:
下一步就是consumer了
consumer就是这里的gateway项目:
首先我同样创建了一个名字一样的软件包,里面也有一个DemoService来接收这个提供者提供的方法。
package com.yupi.project.provider;
import java.util.concurrent.CompletableFuture;
public interface DemoService {
String sayHello(String name);
String sayHello2(String name);
default CompletableFuture<String> sayHelloAsync(String name) {
return CompletableFuture.completedFuture(sayHello(name));
}
}
没有看错和上面的provider就是一模一样的。
调用该接口中的方法实在启动类中
这里放在启动类没有什么特殊含义,单纯就是懒得再开一个软件包而已
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class})
@EnableDubbo
@Service
public class ApiSpringcloudgatewayApplication {
@DubboReference
private DemoService demoService;
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(ApiSpringcloudgatewayApplication.class, args);
ApiSpringcloudgatewayApplication application = context.getBean(ApiSpringcloudgatewayApplication.class);
String result = application.doSayHello("world");
String result2 = application.doSayHello2("world");
System.out.println("result: " + result);
System.out.println("result: " + result2);
}
public String doSayHello(String name) {
return demoService.sayHello(name);
}
public String doSayHello2(String name) {
return demoService.sayHello2(name);
}
@Bean
public GlobalFilter customFilter() {
return new CustomGlobalFilter();
}
}
我们从上到下分析,首先加上@EnableDubbo这个需要记得
@DubboReference
private DemoService demoService;
我觉得着有点像spring的依赖注入,但可能这个是Dubbo框架提供的赋值的方式。
ConfigurableApplicationContext context = SpringApplication.run(ApiSpringcloudgatewayApplication.class, args);
ApiSpringcloudgatewayApplication application = context.getBean(ApiSpringcloudgatewayApplication.class);
这两行就是获取spring的bean工厂,之前在研究spring的源码的时候,就有了解过,bean工厂
然后获取了bean工厂之后再从工厂里面获取对应的bean对象。
接着就是调用provider提供的方法了。
输出结果:
接下来就是项目实战了,这个项目背景就是API开发平台的项目。
项目实战:
首先我在上面介绍的时候有提到,我对这个公共模块进行了抽取。
为什么会想到这个呢
我们仔细看上面的Demo案例,
我们的DemoService是不是在provider和consumer都写了一次。
我们基于这个操作,我们是不是就可以想到我们可以将这个模块抽取出来
再者,抽取公共模块也可以更好的就是规范我们的业务流程。
所以在项目实战之前
我们先抽取一下公共模块
抽取公共模块并在provider中提供接口:
抽象公共模块的步骤
1:想清楚什么方法需要抽象,或者说什么方法是公共,还有其它模块是需要使用的
2:想清楚实体类规整到公共模块还是原来的单体架构模块
3:打包引入依赖,引入依赖之后,需要照着公共的接口创建实现改接口的实体类
根据上面的步骤(其实这个步骤并不是什么规范步骤,就是我自己在抽取的时候遇到了问题,后面复盘总结起来的步骤)
先说一下,一般微服务抽取公共项目的名称都叫什么comon,所以我这里也取名叫api-common
想一下,我们需要抽取什么?
第一个就是我们在项目中需要其它项目引用的方法:
那得结合一下我们的业务
我们在网关的过滤器中,我们需要
1:对用户进行API签名的认证,这个的具体步骤是根据用户在请求头中的accessKey从数据库中查出用户的secretKey,然后结合用户的id+secretKey计算sign,和请求头中发过来的sign进行比对校验。具体更详细步骤在API网关认证哪一章有具体讲
2:查询接口是否存在,这个就是根据接口id到数据库中进行查询
3:调用次数+1,这个就是到UserInterfaceInfo中讲totalNum+1,讲LeftNum-1即可
分析了业务之后,发现我们的需要的方法:
/**
* 内部接口信息服务
*
*/
public interface InnerInterfaceInfoService {
/**
* 从数据库中查询模拟接口是否存在(请求路径、请求方法、请求参数)
*/
InterfaceInfo getInterfaceInfo(String path, String method);
}
/**
* 内部用户接口信息服务
*
*
*/
public interface InnerUserInterfaceInfoService {
/**
* 调用接口统计
* @param interfaceInfoId
* @param userId
* @return
*/
boolean invokeCount(long interfaceInfoId, long userId);
}
import com.yupi.model.entity.User;
/**
* 内部用户服务
*
*/
public interface InnerUserService {
/**
* 数据库中查是否已分配给用户秘钥(accessKey)
* @param accessKey
* @return
*/
User getInvokeUser(String accessKey);
}
第二个就是实体类
第二个就是上面这三个接口中要用到的实体类
想清楚实体类规整到公共模块还是原来的单体架构模块
这个点就是我在抽取的时候没搞清楚的地方了,就是我有的实体类在我master项目中,
有的在common公共模块中
我后面就索性全部改到common公共模块中
并且将master项目中的mapper和mapper,xml都进行了修改
打包引入依赖,引入依赖之后,需要照着公共的接口创建实现改接口的实体类
这里的打包引入依赖就和自定义starter一样。
照着公共的接口创建实现改接口的实体类:
这里当然也可以调用原来的service的方法
不过为了规范一点可以新建一个inner软件包
@DubboService//@DubboService注解是加在你想要暴露出去的方法上的
public class InnerInterfaceInfoServiceImpl implements InnerInterfaceInfoService {
@Resource
private InterfaceInfoMapper interfaceInfoMapper;
@Override
public InterfaceInfo getInterfaceInfo(String url, String method) {
if (StringUtils.isAnyBlank(url, method)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
LambdaQueryWrapper<InterfaceInfo> lambdaQueryWrapper = new LambdaQueryWrapper<>();
LambdaQueryWrapper<InterfaceInfo> eq = lambdaQueryWrapper.eq(InterfaceInfo::getUrl, url)
.eq(InterfaceInfo::getMethod, method);
InterfaceInfo interfaceInfo = interfaceInfoMapper.selectOne(eq);
return interfaceInfo;
}
}
/**
* 内部用户接口信息服务实现类
*
*/
@DubboService
public class InnerUserInterfaceInfoServiceImpl implements InnerUserInterfaceInfoService {
@Resource
private UserInterfaceInfoService userInterfaceInfoService;
@Override
public boolean invokeCount(long interfaceInfoId, long userId) {
return userInterfaceInfoService.invokecount(interfaceInfoId,userId);
}
}
/**
* 内部用户服务实现类
*
*/
@DubboService
public class InnerUserServiceImpl implements InnerUserService {
@Resource
private UserMapper userMapper;
@Override
public User getInvokeUser(String accessKey) {
if (StringUtils.isAnyBlank(accessKey)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("accessKey", accessKey);
return userMapper.selectOne(queryWrapper);
}
}
注意要在这些类上加上 @DubboService注解,注册到nacos注册中心中
等抽取完公共模块之后,剩下的步骤就很简单了
毕竟RPC的宣称就是像调用本地方法一样调用远程方法
consumer调用远程接口:
@Slf4j
public class CustomGlobalFilter implements GlobalFilter, Ordered {
@DubboReference
private InnerUserService innerUserService;
@DubboReference
private InnerInterfaceInfoService innerInterfaceInfoService;
@DubboReference
private InnerUserInterfaceInfoService innerUserInterfaceInfoService;
private static ArrayList<String> IP_WHITE_LIST = new ArrayList<>();
private static final String INTERFACE_HOST = "http://localhost:8123";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1:请求日志
//2:访问控制 -黑白名单
//3:用户鉴权
final HttpHeaders headers = request.getHeaders();
final String accessKey = headers.getFirst("accessKey");
String random = headers.getFirst("random");
String timestamp = headers.getFirst("timestamp");
String sign = headers.getFirst("sign");
String body = headers.getFirst("body");
final User invokeUser = innerUserService.getInvokeUser(accessKey);
if(invokeUser==null){
//user==null,说明这个accessKey根本没有被分配给用户
throw new RuntimeException("accessKey不存在");
}
if(Long.parseLong(random) > 10000){
handleNoAuth(response);
}
//通过时间戳判断是否过期
long currentTimeMillis = System.currentTimeMillis()/1000;
long differenceInSeconds = (long) (currentTimeMillis/1000 - Long.parseLong(timestamp));
if(differenceInSeconds > 300){
handleNoAuth(response);
}
final String secretKey = invokeUser.getSecretKey();
String flag = SingUtils.getSign(body, secretKey);
if(!flag.equals(sign)){
handleNoAuth(response);
}
final Long userId = invokeUser.getId();
//4:从数据库中查询模拟接口是否存在
System.out.println(path);
System.out.println(method);
InterfaceInfo interfaceInfo = innerInterfaceInfoService.getInterfaceInfo(path,method);
if(interfaceInfo==null){
handleNoAuth(response);
}
final Long interfaceInfoId = interfaceInfo.getId();
//5:请求转化,调用模拟接口
final Mono<Void> filter = chain.filter(exchange);//在这个方法中调用次数+1
return handleResponse(exchange,chain,42L,userId);
}
}
源代码很多
流行需要调用远程方法的部分
在调用之前,我们需要先注入一下,这三个接口
就和之前的DemoService一样