# 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); } ```