# spring-cloud-ali
**Repository Path**: kyle94/spring-cloud-ali
## Basic Information
- **Project Name**: spring-cloud-ali
- **Description**: 使用SpringCloud-alibaba运行的Demo
- **Primary Language**: Java
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2020-09-20
- **Last Updated**: 2021-07-31
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
[toc]
# spring-cloud-ali
## 介绍
使用SpringCloud-alibaba运行的Demo
## 软件架构
软件架构说明
## 使用教程
### 依赖说明
该项目中的公共模块有common和system-api。
- common: 公共模块,类似于framework和tools的结合模块
- system-api: 系统通用的各个服务模块的API接口,包括API接口所需要的pojo对象。
由于system-api已经依赖了common模块,所以在创建新的模块的时候,**只需要依赖system-api即可**。
### swagger使用
#### scan配置寻找范围
```java
@SpringBootApplication(scanBasePackages = {"com.kyle"})
public class UserCenterApplication {
public static void main(String[] args) {
SpringApplication.run(UserCenterApplication.class, args);
}
}
```
添加scanBasePackages属性
#### 配置
```yaml
# swagger配置
swagger:
version: v1.0.1
name:
cn: 用户中心
params:
package: com.kyle.user.controller
headers: Authorization,用户认证,false;X-Code,采用技术
```
- swagger.version: 表示该文档的版本
- swagger.name.cn: 该文档的描述
- swagger.params.package: 该swagger读取的包
- swagger.params.headers: 需要显示在swagger文档中的接口的请求头信息
- spring.application.name: swagger的文档名称默认使用项目名称
headers: 使用";"隔开,表示一个header,每个header中使用","隔开,表示一个属性 -- header属性名,header中文名,是否必填
### Result和ServiceException
#### Result
对于正常数据的返回,直接返回数据结构为Result,如果返回的data并不需要,则返回Result。
在返回操作时,不需要去构建Result,可以采用ResultUtil工具类来进行构建。
#### ServiceException
如果在逻辑代码中需要抛出业务相关异常,则直接抛出ServiceException,其参数建议在Errors中创建,方便统一管理。
```
public Result testException(TestRequest request){
TestResponse response=new TestResponse();
response.setCode(request.getCode());
if(request.getCode()==-1){
throw new ServiceException(Errors._TEST_EXCEPTION);
}
response.setMessage("success");
return ResultUtil.success(response);
}
```
### 代码自动生成
本系统目前提供**JPA**代码自动生成,后续将继续添加MyBatis框架的代码自动生成模块。
目前自动生成的有:Controller、Service、ServiceImpl、DTO、Repository、CreateRequest、UpdateRequest、Response。
#### 使用
系统提供两种方式进行代码自动生成,一种为采用接口调用的方式生成Zip包,可根据包中路径将代码文件移动到项目中对应的位置;一种为直接运行GenerateCodeProcessor类。
##### 接口生成
**url: /test/generate/code**
**params:**
- author: 作者名称,默认为kyle,非必填
- pkgName:顶级包名,默认为com.kyle.user
- domainClassPkg: 实体类的包名,默认com.kyle.user.pojo.entities
- domainClassName: 实体类名,默认TestData
- moduleName: 模块名称,默认user-center
这里的参数除了author,其他的必须由开发者自己填写,系统会根据domainClassName对应的实体类自动生成controller、Service等组件。
发起请求后下载生成的zip包并解压,将其中生成的java文件移动到自己的项目中即可。
##### 运行GenerateCodeProcessor类
在项目对应的模块中新建一个类:
```java
public class GenerateCodeProcessor {
public static void main(String[] args) throws IOException {
// 默认文件创建
// InputStream inputStream = GenerateCodeProcessor.class.getClassLoader().getResourceAsStream("generator.yml");
InputStream inputStream = GenerateCodeProcessor.class.getClassLoader().getResourceAsStream("generator_data.yml");
Yaml yaml = new Yaml();
Map> map = yaml.load(inputStream);
inputStream.close();
Map generator = map.get("generator");
String author = generator.get("author");
String packageName = generator.get("packageName");
String domainClassName = generator.get("domainClassName");
String domainClassPkg = generator.get("domainClassPkg");
String moduleName = generator.get("moduleName");
// 生成文件
GenerateServiceImpl service = new GenerateServiceImpl();
service.generateCode(author, packageName, domainClassPkg, domainClassName, moduleName);
}
}
```
再在对应模块的resources目录下新建一个yml文件:generator_data.yml
```
generator:
author: kyle
packageName: com.kyle.user
domainClassName: TestData
domainClassPkg: com.kyle.user.pojo.entities
moduleName: user-center
```
运行后会将代码自动生成到项目中对应的位置,不需要自己进行文件移动。
### 权限设置
在本系统中,权限控制主要又3个方面:
- User <-> Role <-> Menu 菜单层次上的权限控制(与前端相关)已实现
- User <-> Role <-> Auth 接口层次上的权限控制(例如有些用户对修改没有权限,只有查的权限)暂未实现
- User <-> Group 数据层面上的权限控制 暂未实现
#### 菜单层次上的权限控制
首先在需要的菜单接口上面写@PreAuthorize()注解:
```
@ApiOperation("获取用户信息列表 -- 前端点击‘用户’这个菜单项时调用")
@GetMapping("/user/list")
@PreAuthorize("hasAnyAuthority('system:user:list')")
public Result> list() {
// ...
}
```
这里会使用hasAnyAuthority这个属性,表示该用户是否包含对应的权限,这里可以接收的参数是一个String...可变类型,即可以传入多个权限参数。
**这里需要注意的是,hasAnyAuthority('system:user:list')中权限使用单引号**。
这里不使用hasAnyRole()这个属性,是因为hasAnyRole会在'system:user:list'前面加上ROLE_前缀。
(具体的配置解析流程在SecurityExpressionRoot这个类中)
针对以上例子中配置的权限,我们需要对应的数据如下:
- User信息:TestUser
- Role信息:TestUser对应了一个角色为普通管理员(只有查询和修改的权限,没有增删权限),TestRole
- Menu信息:TestRole对应的菜单,例如查询菜单(system:user:list)(system:user:search),修改菜单(system:user:update)
这样,我们在编写接口时,在对应的菜单接口上写入权限控制。
```
@GetMapping("/user/list")
@PreAuthorize("hasAnyAuthority('system:user:list')")
public Result> list() {
// ...
}
@GetMapping("/user")
@PreAuthorize("hasAnyAuthority('system:user:search')")
public Result search() {
// ...
}
@PutMapping("/user")
@PreAuthorize("hasAnyAuthority('system:user:update')")
public Result update() {
// ...
}
```
#### 接口层次上的权限控制
// TODO
#### Group 数据层面上的权限控制
// TODO
### 接口防止重复提交
对于某些接口,可能并不允许在短时间内发起多次请求。例如用户注册接口,我们在遇到网络延迟返回时,可能客户端在收到服务端的返回时已经过去好几
分钟,用户可能这不耐烦的情况下多次点击注册按钮。那么可能会导致服务端多次写入同样的用户数据,导致存在该用户的多条信息(当然, 在注册时应该
采用唯一键的方式反正用户信息重复,禁止重复提交并不能根治该问题)。
此时采用禁止重复提交,让用户在短时间内不能因为误操作导致数据出错。
#### 注解
```
@ApiOperation("获取远程配置")
@GetMapping("/config")
@RepeatSubmit
public Result getConfig() {
return ResultUtil.success("code from nacos is " + code + ", msg from nacos is " + msg);
}
```
@RepeatSubmit注解表示该接口会受到是否可重复提交的监控。
- canRepeat:默认为false,是否允许重复提交
#### 参数配置
- kyle.request.intervalTime:默认为5,请求间隔时间,单位为秒(即两次请求在多少秒之内重复请求则判定为重复提交)
- kyle.request.repeatSubmit.key:默认为,请求头中可以设置该请求的唯一标识,如果该唯一标识为空,则使用该请求的url。
### nacos服务发现
#### 依赖
```xml
org.springframework.boot
spring-boot-starter-actuator
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
```
#### 配置
```yaml
server:
port: 8021
# 指定nacos server的地址: localhost:8848
# 指定nacos server的命名空间: ce67f15e-d5c1-4d05-9ec5-e63f93354032(在nacos后台配置的dev命名空间的ID)
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: ce67f15e-d5c1-4d05-9ec5-e63f93354032
# 指定当前应用的名称(即在Nacos服务中显示的名称): user-center
application:
name: user-center
management:
endpoints:
web:
exposure:
exclude: '*'
```
这里的命名空间在多环境开发情况下建议使用,对于在不同的环境下,所需要的配置、服务都不一样,如dev和prod环境。
nacos的服务发现中心需要自行下载,其要求如下:
1. 64bit系统
2. JDK1.8以上
3. Maven 3.2.x以上
Nacos服务下载方式有三种:
1. 源码下载
```shell script
git clone https://github.com/alibaba/nacos.git
cd nacos/
mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U
ls -al distribution/target/
# change the $version to your actual path
cd distribution/target/nacos-server-$version/nacos/bin
# 启动
startup.sh -m standalone
```
2. 压缩包下载
从github中进行下载:https://github.com/alibaba/nacos/releases
```shell script
unzip nacos-server-$version.zip 或者 tar -xvf nacos-server-$version.tar.gz
cd nacos/bin
# 启动
startup.sh -m standalone
```
3. docker下载
```shell script
docker pull nacos/nacos-server
docker run -d -p 8848:8848 nacos/nacos-server
```
> Nacos的控制台地址:http://127.0.0.1:8848/nacos
> 用户名:nacos 密码:nacos
#### Enable注解
```java
@EnableDiscoveryClient
public class UserCenterApplication {
public static void main(String[] args) {
SpringApplication.run(UserCenterApplication.class, args);
}
}
```
### Nacos配置中心
#### 依赖
```xml
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
```
#### 配置
这里需要创建一个bootstrap.yml文件来配置Nacos配置中心客户端。
```yaml
# 设置配置中心服务的地址,注意这里的port不能省略,即使采用域名配置,也需要"xxx.com:80"
# 指定nacos server的命名空间: ce67f15e-d5c1-4d05-9ec5-e63f93354032(在nacos后台配置的dev命名空间的ID)
# 配置中心采用yaml的格式进行配置
# 指定在配置中心的配置文件的前缀,如果没有指定,则默认使用${spring.application.name}
spring:
cloud:
nacos:
config:
server-addr: localhost:8848
namespace: ce67f15e-d5c1-4d05-9ec5-e63f93354032
file-extension: yaml
prefix: userCenter
```
配置中心的配置文件的名称:
**${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}**
这里需要强调的是,对于配置中心的配置,也需要指定**namespace**,否则无法识别到该配置文件。
#### 配置中心的配置内容使用
```java
@RestController
@RequestMapping("/config")
public class ConfigController {
@Value("${config.test.code: 400}")
private Integer code;
@Value("${config.test.msg: 获取失败}")
private String msg;
@GetMapping
public String getConfig() {
return code + ": " + msg;
}
}
```
使用@Value注解即可获取到该配置,建议使用默认值,原因是如果配置中心的该配置文件被删除时,如果没有默认值,则会由于找不到该文件而导致无法运行。
### Sentinel
Sentinel同样为微服务提供流控、熔断降级的功能,与Hystrix的功能类似,但是Hystrix已经进入维护期,不再提供新的功能。
Sentinel采用的是用户线程来记性服务隔离,而Hystrix采用的是线程池来进行服务隔离。所以Sentinel在服务隔离方面更加精细,
而且Sentinel比之Hystrix的线程池之中的线程切换导致,更加高效。
Sentinel同时提供了DashBoard在线界面,提供了在线修改限流等配置。
#### 控制台下载
1. 通过github下载
https://github.com/alibaba/Sentinel/releases
下载jar包,并使用java -jar运行
> 控制台访问地址:http://localhost:8080
> 账号:sentinel
> 密码:sentinel
2. 通过docker下载
```shell script
docker pull bladex/sentinel-dashboard
docker run --name sentinel -d -p 8858:8858 -d bladex/sentinel-dashboard
```
> 控制台访问地址:http://localhost:8858
> 账号:sentinel
> 密码:sentinel
这里以docker为例,即其端口为8858.
#### 依赖
```xml
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
com.alibaba.csp
sentinel-datasource-extension
```
依赖是添加在访问方,而不是被访问方。
#### 配置
```yaml
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8858(localhost:8080)
port: 8719
feign:
sentinel:
enabled: true
```
由于Sentinel为懒加载模式,所以需要先调用接口,再刷新控制台,才能看到Sentinel策略。
#### 熔断返回处理
com.kyle.systemapi.feign.base.DefaultFeignFallback.java
```
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
String causeMessage = cause.getMessage();
log.error(causeMessage);
if (!(cause instanceof FeignException)) {
// 不是feignException
return ResultUtil.failure(Errors._SYSTEM_ERROR.getKey(), causeMessage);
}
// 这里可以根据e的类型来判断是由于什么规则导致的熔断:FlowException/DegradeException....
return ResultUtil.failure(Errors._SENTINEL_FALLBACK_ERROR.getKey(), causeMessage);
}
```