二 简介
系统架构图
三 涉及的技术
3.1 项目构建:
3.2 微服务架构:spring cloud:
注册中心:eureka1.x
配置中心:spring config
负载均衡:ribbon
api网关:zuul
服务调用:feign
3.3 依赖管理
3.4 nosql支持:
3.5 队列技术:
3.6 日志处理:
3.7 持久层框架(orm):
3.8 数据源:
3.9 项目调试:
四 项目结构:
4.1 项目结构:
4.2 包结构:
五 Restful 接口设计
5.1 实体类设计
5.2 请求
5.3 响应
5.3.1 正常响应
5.3.2 异常响应
5.4 参数校验
5.4.1 controller的请求参数进行JSR303校验
5.4.2 手动参数校验
5.5 service设计(待完成)
5.6 dao设计(待完成)
6 开发步骤
6.1 新建一个maven项目,作为vdp-cloud父项目中的子项目,添加vdp-cloud-core依赖进pom, 添加spring-boot-starter-web依赖, 否则在启动项目报错时在console无法打印错误信息
6.2 在application.yaml配置服务
6.3 修改bootstrap.yml里面的配置
6.4 修改其他一些服务的ip地址
6.5 如果要用lombok,需要在IDE中安装lombok插件
一 背景
我们希望针对"数据采集,处理,寄存,存储"的应用场景,设计一个数据灵活处理,存储和通信的框架。
我们将需求分解成了不同服务,使用者只要调用相应方法即可,服务和底层实现组件松散组合,可以根据需要进行调整和扩展。
二 简介
采用springboot+springcloud构筑的微服务分布式架构。可以方便的实现服务的注册和发现。使用者只需要通过简单的配置开启不同的服务即可(如何使用后面有介绍),设计的服务:
- 归档archive:
将采集后的元素数据和解析后的数据备份到指定的服务器中,后续可以进行分析和计算. 存储storage(未完成) :
消息队列msgqueue:
定义发生和接收的通用方法, 在配置文件中配置本服务节点需要的用到的队列, 在服务启动时初始化队列,在发生放和接收方指定相应的队列进行发生和接收操作.- 寄存memory:
一个通用的寄存服务,对一些通用的缓存技术进行了封装,如redis和memcache. 封装了通用的存和取的方法. - 单点登陆sso(目前未支持)
可以灵活开启或关闭上述服务,不开启则不进行连接和bean的初始化。各个服务可以通过配置的方式灵活选择不同的组件来实现,服务和组件采用策略设计模式实现,各个服务可以根据需要灵活扩展和调整组件。
服务和支持的组件如下(包括计划支持的服务和组件):
- 归档archive:mongodb,elasticsearch(计划支持)
- 存储storage:mysql
- 消息队列msgqueue:rabbitmq,kafka(计划支持),rocket(计划支持)
- 寄存memory:redis
- 单点登陆sso(计划支持):spring security集成auth2(计划支持)
服务和支持的组件是一对一的关系,如归档archive服务,支持的基础组件为mongodb,elasticsearch,只能选择一种基础组件来实现. 即我们配置mongodb组件后,归档服务底层实现是由mongodb的api实现,且数据会归档到mongodb中.
系统架构图
我们通过在vdp-cloud创建一个module 模块,依赖core包,就可以变成上图Business Node中的一server node,具有服务注册和发现功能,且可以调用归档(archive),消息队列(msgqueue),寄存(Memory)等服务。
三 涉及的技术
3.1 项目构建:
spring boot:通过spring boot快速项目构建
3.2 微服务架构:spring cloud:
注册中心:eureka1.x
eureka2.0被闭源,如果spring放弃eureka,那么可以选用zookeeper或者
console,到时候再支持。
配置中心:spring config
官方推荐git来管理配置,现在只支持本地文件系统,如果要修改成git,修改下配置即可。
如果需要更进一步扩展后期可能会支持携程的apollo。
负载均衡:ribbon
springcould之间服务调用的负载均衡
api网关:zuul
本项目节点外的第三方调用本项目服务节点暴露的api时, 通过zuul进行代理. 如web平台调用XXX项目查询归档的历史数据, 那么不是直接访问里面的XXX服务节点去请求数据,而是只访问zuul,由zuul代理访问XXX服务节点
配置示例:
zuul:
routes:
#### common routes, default support LoadBalancer
vdp-cloud-sc: /sc/**
vdp-cloud-vlr: /vlr/**
vdp-cloud-dqs: /dqs/**
访问: http://localhost:20001/vlr/vlr/device-authentication/a1234567890123456
返回:
{
"msg": "OK",
"data": "a1234567890123456",
"debug": null,
"status": 200,
"ok": true
}
服务调用:feign
3.3 依赖管理
maven
3.4 nosql支持:
redis:寄存服务使用的组件,目前只支持集群模式(cluster)
mongodb:归档服务使用的组件,支持单机模式和复制集模式
3.5 队列技术:
生产者需要创建的队列, 和消费者需要监听的队列, 都通过在application.yaml配置文件中进行配置.
支持队列组件有:
- rabbitmq:消息队列服务使用的组件,支持集单机模式,计划支持集群模式.
需要在生产者和消费者中的application.yaml中相同的队列, 在加载的时候会自动进行初始化:
queueNames:
dataUpdateQ: topic.scdata.sc.vlr
eventCalculateQ: topic.rtinfo.sc.event
格式为:"exchange类型.数据类型.生产者.消费者", "数据类型"作为exchange, "生产者.消费者"作为routeKey, 整个作为queue名称.
消费者通过指定queueNames下的key就能够实现监听指定的队列, 使用方式参考test项目下的MqController.java
3.6 日志处理:
log4j/logslf/logback: 日志收集, 采用lombok框架在类中简化创建log收集器:
@Slf4j
public class VdpExceptionHandler {
//相当于
//Logger log = LoggerFactory.getLogger(VdpExceptionHandler.class);
…
public void test(){
log.error("发生异常!!!", e);
}
}
@Slf4j相当于在类中创建了类成员
Logger log = LoggerFactory.getLogger(VdpExceptionHandler.class);
3.7 持久层框架(orm):
mybaits:通过mybaits-plus简化curd操作。通过mybaits-generate将数据库表生成实体类
3.8 数据源:
druid:实现数据源监控功能
监控用户名和密码在application.yaml中进行配置
monitor:
urlSuffix: /druid/* #访问的url地址后缀, 例如http://localhost:8080/druid/
username: admin
password: 123
3.9 项目调试:
swagger
swagger会管理所有包含@ApiOperation注解的控制器方法,同时,可利用@ApiImplicitParams注解标记接口中的参数,具体用法请参考UserController类中的用法。
通过application.yaml配置来实现swagger的开启和关闭
swagger:
open: true #开启/关闭swagger,开启--true,关闭--false
protocol: http #协议,http或https
base-package: com.vdp.vlr.modules.controller #一定要写对,会在这个路径下扫描controller定义
title: 寄存服务(VLR)开发api
version: 1.0
description: vdp-cloud项目RESTful API
四 项目结构:
4.1 项目结构:
vdp-cloud: 父类, 用于springcloud和springboot的版本管控, maven插件, 仓库统一管控等.
|--vdp-cloud-common: 常用组件和工具类
|--vdp-cloud-core:依赖vdp-cloud-common项目
|--vdp-cloud-base-config-server: 微服务配置中心
|--vdp-cloud-base-discovery-eureka: 微服务注册中心
|--vdp-cloud-base-gateway-zuul: 微服务api网关
|--vdp-cloud-base-monitor-admin: 微服务节点监控项目
|--vdp-cloud-test:测试项目,用于测试框架的各个功能. 依赖vdp-cloud-core项目(也间接依赖了comon项目)
4.2 包结构:
- vdp-common项目:
com.vdp.common
|--swagger: swagger的配置
|--utils: 常用的工具类
|--web: web开发需要的组件,如web异常处理,返回值的pojo等 - vdp-cloud-core项目:
com.vdp.core
|--common: 通用工具类
|--component: 用于实现服务的基本组件,如druid,mongo,rabbitmq,redis等
|--container: 加载和容器的基本配置参数, 参考vdp-cloud-test项目application.yaml中的containerconfig配置
|--data: 容器需要使用到的annotation,entity,const等
|--service: 设计的服务, 有归档服务,消息队列服务等,请参考简介部分的设计的服务
新建一个项目时需要依赖core项目,具体参考test项目的pom.xml配置
五 Restful 接口设计
5.1 实体类设计
添加JSR注解实现参数检验, 通过JSR303进行校验
通过Lombok注解, 编译时自动添加Setter、Getter、toString()、equals()和hashCode()。
package com.vdp.sc.testCurd.testParamValid;
import lombok.Data;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.Size;
/**
* @ClassName Student
* @Description 实体类: 1.参数检验, 通过JSR303进行校验
* 2.通过Lombok注解, 编译时自动添加Setter、Getter、toString()、equals()和hashCode()。
* @Author longxn
* @Date 2018/7/9 20:14
*/
@Data
public class Student {
String id;
@Size(min = 3, max = 5, message = "姓名长度为3到5之间")
String name;
@Min(value=0)
@Max(value=150)
Integer age;
}
5.2 请求
在Spring MVC 中,Restful API的定义对应为Controller层。根据Restful的接口定义规范:
GET(SELECT):从服务器取出资源(一项或多项)。
POST(CREATE):在服务器新建一个资源。
PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
DELETE(DELETE):从服务器删除资源。
我们设计接口如下所示,详见():
//UserController.java
package com.vdp.sc.testCurd;
import com.mystudy.spring.domain.User;
import com.mystudy.spring.service.UserService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/user")
public class UserController
{
@Autowired
private UserService userService;
@ApiOperation(value="获取用户列表", notes="获取用户列表")
@GetMapping(value = "/users")
@ResponseStatus(HttpStatus.OK)
public List<User> getUserList()
{
return userService.getUserList();
}
@ApiOperation(value="添加用户", notes="添加用户")
@PostMapping(value = "/users")
@ResponseStatus(HttpStatus.CREATED)
public Object addUser(@RequestBody User user){
return userService.addUser(user);
}
@ApiOperation(value="获取用户信息", notes="根据id获取用户信息")
@GetMapping(value = "/users/{id}")
@ResponseStatus(HttpStatus.OK)
public Object getUser(@PathVariable("id") String id) throws NotFoundException
{
return userService.getUser(id);
}
@ApiOperation(value="删除用户", notes="根据id删除用户")
@DeleteMapping(value = "/users/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteUser(@PathVariable("id") String id)
{
userService.deleteUser(id);
}
@ApiOperation(value="更新用户", notes="更新用户")
@PatchMapping(value = "/users/{id}")
@ResponseStatus(HttpStatus.CREATED)
public User updateUser(@PathVariable("id") String id, @RequestBody User user)
{
return userService.update(id, user);
}
@ApiOperation(value="测试")
@GetMapping(value = "/test")
@ResponseStatus(HttpStatus.OK)
public String test()
{
return "test ok!";
}
}
在这些接口上,我们使用了如下注解:
- @GetMapping 对应get请求
- @PostMapping 对应post请求
- @DeleteMapping 对应delete
- @PutMapping 对应put和patch请求
注意url和方法名的命名规则, url中不准出现get, update等
5.3 响应
5.3.1 正常响应
直接返回请求的数据
根据请求的类型需要设置相应的ResponseStatus进行相应,对应关系如下:
@GetMapping: @ResponseStatus(HttpStatus.OK) //200
@PostMapping: @ResponseStatus(HttpStatus.CREATED) //201
@DeleteMapping: @ResponseStatus(HttpStatus.NO_CONTENT) //204
@PatchMapping: @ResponseStatus(HttpStatus.CREATED) //201
@PutMapping: @ResponseStatus(HttpStatus.CREATED) //201
5.3.2 异常响应
发生异常会由VdpExceptionHandler.java
进行拦截处理
异常产生和匹配规则如下:
- 认证异常* :
异常类:AuthenticationException.java
HttpStatus: 401
JSON中自定义status: 401
备注: 在身份认证过程中判断token失效或者过期时手动抛出该异常 - 参数校验异常* :
异常类:IllegalArgumentException.java
HttpStatus: 500
JSON中自定义status: 501
备注: 在校验参数失败时抛出该异常,如果是手动判断不满足条件,则需要手动抛出异常 - JSR参数校验异常* :
备注:MethodArgumentNotValidException.java
HttpStatus: 500
JSON中自定义status: 501
备注: 在请求或者springboot中的配置文件开启JSR校验(参考5.4 参数校验)时, 若校验不通过则自动产生该异常 - 业务异常* :
备注:ServiceException.java
HttpStatus: 500
JSON中自定义status: 555
备注: 如果业务逻辑或者数据异常, 则手动抛出该异常 - 其他异常* :
异常类:Exception.java
HttpStatus: 500
JSON中自定义status: 500
备注: 如果上述异常都没匹配上匹配该异常
5.4 参数校验
5.4.1 controller的请求参数进行JSR303校验
//CheckParamController
package com.vdp.sc.testCurd.testParamValid;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
/**
* @ClassName CheckParamController
* @Description 对@RequestParam和@PathVariable的校验需要添加@Validated到类上
* @Author longxn
* @Date 2018/7/9 20:25
*/
@RestController
@Validated
public class CheckParamController {
// http://localhost:8080/checkParamTest?name=1&age=-1
// 提示: msg": "checkParamTest.age: age必须大于0"
@GetMapping("checkParamTest")
public void checkParamTest(@RequestParam @Min(value = 2,message = "age必须大于0") Integer age){
System.out.println(age + ",.,.");
}
//加@Validated对具体实体类检验,需要在实体类加相应的注解,如@Size等
// http://localhost:8080/checkParamTestPost
// 提示: msg": "姓名长度为3到5之间"
// 参数:
// {
// "name":"1",
// "age":1
// }
@PostMapping("checkParamTestPost")
public void checkParamTestPost(@RequestBody @Validated Student student){
System.out.println(student);
}
// http://localhost:8080/checkParamTestPath/aa
// msg": "checkParamTestPath.name: 名字长度在3到5之间"
@GetMapping("checkParamTestPath/{name}")
public void checkParamTestPath(@NotNull @NotBlank @Size(min=3,max=5, message = "名字长度在3到5之间")@PathVariable String name){
System.out.println(name);
}
}
5.4.2 手动参数校验
采用Spring的Assert进行校验
//第一个参数为false则抛出IllegalArgumentException异常
Assert.isTrue(concurrentConsumers > 0, "'concurrentConsumers' value must be at least 1 (one)");
Assert.isTrue(!this.exclusive || concurrentConsumers == 1,"When the consumer is exclusive, the concurrency must be 1");
复杂校验通过Optional
ZOrder order = this.getOrderByOrderNum(orderNum);
Optional.ofNullable(order).filter(o -> {
return null != o && (0 == o.getStatus() || 3 == o.getStatus() || 9 == o.getStatus());
}).orElseThrow(() -> new ParamException("获取数据异常,订单号有误或者订单状态异常!"));
简单的一些条件判断可以使用Objects, 和common3的StringUtils
日期处理
文件上传处理
5.5 service设计(待完成)
5.6 dao设计(待完成)
- mybatis-plus(mybaits-generate)
- spring data,hibernate, ORM,实现JPA规范
6 开发步骤
6.1 新建一个maven项目,作为vdp-cloud
父项目中的子项目,添加vdp-cloud-core
依赖进pom, 添加spring-boot-starter-web依赖, 否则在启动项目报错时在console无法打印错误信息
<parent>
<groupId>com.vdp-cloud</groupId>
<artifactId>vdp-cloud</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>com.vdp-cloud</groupId>
<artifactId>vdp-cloud-core</artifactId>
<version>${vdp.cloud.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
6.2 在application.yaml配置服务
###### 容器功能相关配置 #################
# 第一级key不要用驼峰格式, 会导致一直无法读取到配置!!!!!
# enabled: 根据需要配置成true还是false. 配置成false时,不会创建bean, 也不会去连接对应的mysql,mongodb等服务器
# component: 选择一种在support中已经被支持的组件, 采用的enum类,在enum类中可以查询到支持的component
containerconfig:
msgqueue:
enabled: false
component: rabbitmq
memory:
enabled: false #
component: redis
storage:
enabled: true
component: mysql
archive:
enabled: true
component: mongodb
ThreadNum: 2
QueueCap: 200000
6.3 修改bootstrap.yml里面的配置
- 注册服务,实例名等
6.4 修改其他一些服务的ip地址
如修改mongodb, mysql, redis, rabbitmq等对应的ip地址, 端口和密码