novawei / snowball

SpringCloud Quick Start Template

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

SNOWBALL

SpringCloud Quick Start Template

功能

  • DOCKER开发和部署
  • 数据库服务
  • NACOS服务
  • 接口设计
  • 国际化处理
  • 分布式事务Seata
  • Sentinel
  • 登录认证和权限控制
  • [TODO]Jenkins
  • [TODO]Log管理, ELK
  • [TODO]链路追踪,Zipkin

运行

注意:MySQL Server的配置文件server/database/conf.d/mysql.cnf要求必须为只读, 运行之前,需要将该文件设置为只读,否则配置会被忽略。

mvn clean install
# add --build to rebuild images
docker-compose up --build

SNOWBALL说明

新增模块建议在snowball/pom.xml中添加dependencyManagement,以减少导入时version的书写

    <dependencyManagement>
        <dependencies>
            ...
            <!--snowball artifacts version management-->
            <dependency>
                <groupId>com.nova</groupId>
                <artifactId>core</artifactId>
                <version>${project.version}</version>
            </dependency>
            <dependency>
                <groupId>com.nova</groupId>
                <artifactId>web</artifactId>
                <version>${project.version}</version>
            </dependency>
            <dependency>
                <groupId>com.nova</groupId>
                <artifactId>seata</artifactId>
                <version>${project.version}</version>
            </dependency>
            <dependency>
                <groupId>com.nova</groupId>
                <artifactId>gateway</artifactId>
                <version>${project.version}</version>
            </dependency>
            <dependency>
                <groupId>com.nova</groupId>
                <artifactId>uac-api</artifactId>
                <version>${project.version}</version>
            </dependency>
            <dependency>
                <groupId>com.nova</groupId>
                <artifactId>auth-api</artifactId>
                <version>${project.version}</version>
            </dependency>
            <dependency>
                <groupId>com.nova</groupId>
                <artifactId>order-api</artifactId>
                <version>${project.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

DOCKER开发和部署

参考官方文档,安装docker环境

Docker官网

Gateway服务Dockerfile示例,新建服务只需要拷贝startup.sh、wait-for.sh、Dockerfile,并改Dockerfile中的JAR_NAME

FROM openjdk:8u212-jre-alpine

ENV JAR_NAME=gateway TZ=Asia/Shanghai JAVA_OPTS="-Xms200m -Xmx200m"

ADD ./startup.sh /usr/local/bin/startup.sh
ADD ./wait-for.sh /usr/local/bin/wait-for.sh
RUN chmod +x /usr/local/bin/startup.sh /usr/local/bin/wait-for.sh

ADD ./target/${JAR_NAME}.jar /app/

ENTRYPOINT ["/usr/bin/env"]
CMD ["/usr/local/bin/startup.sh"]

EXPOSE 6001

Dockerfile中shell脚本说明:

  • startup.sh:用于启动java服务或者原来镜像的ENTRYPOINT服务
  • wait-for.sh:用于等待某个服务和端口启动后再进行启动,参考Gateway服务的command:
  gateway:
    hostname: gateway
    image: snowball/gateway:${SNOWBALL_VERSION}
    build:
      context: ./gateway
    depends_on:
      - nacos-server
    command: /usr/local/bin/wait-for.sh -t 0 nacos-server:8848 -- /usr/local/bin/startup.sh
    restart: always
    ports:
      - 8080:8080

数据库服务

位于server/database

目录说明:

  • tmp: 临时目录,目前docker-compose.yml中配置的数据和日志映射到tmp目录下,生产环境请注意切换合适的存储位置
  • conf.d: MySQL配置目录,具体配置请查看mysql.cnf
  • initdb.d: 数据库初始化SQL脚本目录,目前有nacos、seata、order、uac三个服务的SQL

NACOS服务(注册中心、配置中心)

位于server/nacos

目录说明:

  • tmp: 临时目录,目前docker-compose.yml中配置的日志映射到tmp目录下,生产环境请注意切换合适的存储位置
  • resources: 存放各个Group的配置文件,注意:此处只是保存配置,并未配置到Nacos中,如需导入,可以将resources/*打包成zip,然后启动Nacos服务导入进去

接口设计

接口设计

  • 成功的接口响应,HTTP Response Body总是返回用户需要的信息,不再包装code、message等其他字段
  • 接口JSON字段格式默认采用SNAKE_CASE,如需切换,可在Nacos配置文件中设置
spring:
  jackson:
    property-naming-strategy: SNAKE_CASE
    default-property-inclusion: NON_NULL
    deserialization:
      FAIL_ON_UNKNOWN_PROPERTIES: false

例如,获取订单信息的接口,http://localhost:8080/order/orders/1

{
  "id": 1,
  "user_id": 1,
  "name": "订单1",
  "status": 0,
  "create_time": "2020-02-02T00:00:00.000+00:00"
}

复杂的返回数据,建议也要采用合理的组织结构,不要将不同的复杂对象的字段放到一个层级,例如,用户详细信息接口

{
    "id": 1,
    "username": "snowball",
    "name": "雪球",
    "department": {
      "id": 1,
      "name": "研发部"
    }
}
  • 失败的接口响应(HTTP STATUS 4xx/5xx),在SpringCloud的异常信息基础上,补充apiCodeapiMessage字段
{
    "timestamp": "2022-05-12T07:20:22.174+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "exception": "feign.FeignException$InternalServerError",
    "message": "[500] during [GET] to [http://uac-service/uac/users/hello/exception] [UserClient#getUserThrowException()]: [{\"timestamp\":\"2022-05-12T07:20:22.058+00:00\",\"status\":500,\"error\":\"Internal Server Error\",\"exception\":\"java.lang.RuntimeException\",\"message\":\"user not exists\",\"path\":\"/uac/users/hello/exception\",\"apiCode\":1,\"apiMessage\":\"fail\"}]",
    "path": "/order/orders/hello/global/transaction",
    "api_code": 1,
    "api_message": "失败"
}

接口访问限制、版本控制、国际化信息处理

外部(Gateway)访问和微服务内部(FeignClient)访问的区分

微服务整体,通过Gateway将接口API提供给用户访问,微服务内部之间通过FeignClient进行调用,例如UAC-用户和订单服务,订单服务需要访问用户信息。有些接口可能涉及敏感或者重要的数据,并不能暴露出来进行访问。

内外网访问限制实现,设计X-Api-Origin HTTP Header:

  1. 当接口从Gateway进行访问时,会通过Filter,设置X-Api-Origin=Gateway
  2. 当接口从FeignClient进行访问时,会设置X-Api-Origin=Feign

接口版本区分

接口版本控制实现,设计X-Api-Version HTTP Header:
APP或者WEB调用接口时,需要设置X-Api-Version=v1来控制接口版本

接口错误信息,国际化处理

common/web中CustomErrorAttributes类会统一拦截错误,补充apiCodeapiMessage字段,其中apiMessage字段会根据当前请求的HTTP Header Accept-Language自动匹配Locale,返回对应的错误提示。

i18n文件存放在common/web的resources/i18n目录下,包括ApiCodemessages两个bundle

Annotation注解,方便进行访问限制和版本控制

设计Annotation相关类:

  • PublicXXXMapping标记为公共接口(允许从Gateway和FeignClient访问),ProtectedXXXMapping标记为保护接口(只允许从FeignClient访问)
  • XXXV1XXXMapping=接口版本v1,XXXV2XXXMapping=接口版本v2

国际化处理

common/web中I18nUtils类,封装了MessageSource的方法,导入到Java文件进行调用即可。

  • resources/i18n/ApiCode:专门存放ApiCode对应的国际化解析信息
  • resources/i18n/messages:存放其他消息对应的国际化解析信息

分布式事务Seata

Seata - Seata官方文档

分布式事务涉及的database中都需要添加seata要求的undo_log表,参考server/database/initdb.d/snowball_uac.sql和server/database/initdb.d/snowball_order.sql

-- ----------------------------
-- SEATA Global Transaction
-- Table structure for undo_log
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
-- ----------------------------
CREATE TABLE IF NOT EXISTS `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

使用说明:

  1. 在对应service pom中导入依赖
<dependency>
    <groupId>com.nova</groupId>
    <artifactId>seata</artifactId>
</dependency>
  1. 在需要分布式事务的方法上添加@GlobalTransactional注解
@GlobalTransactional(rollbackFor = Exception.class)
@Override
public void testGlobalTransaction(Order order) {
  // do something
}

Sentinel

Sentinel:分布式系统的流量防卫兵

Gateway网关集成了Sentinel流量控制,DataSource采用nacos。

spring:
  cloud:
    sentinel:
      log:
        dir: ./logs/csp
      datasource:
        flow:
          nacos:
            server-addr: nacos-server:8848
            dataId: ${spring.application.name}-sentinel-rules
            groupId: SENTINEL_GROUP
            data-type: json
            rule-type: flow

Sentinel控制配置文件/server/nacos/resources/DEFAULT_GROUP/gateway-sentinel-rules

[
  {
    "resource": "uac-service",
    "count": 100,
    "grade": 1,
    "limitApp": "default",
    "strategy": 0,
    "controlBehavior": 0
  },
  {
    "resource": "order-service",
    "count": 100,
    "grade": 1,
    "limitApp": "default",
    "strategy": 0,
    "controlBehavior": 0
  }
]

登录认证和权限控制

集成spring-security,位于common/security模块,可通过WebSecurityConfiguration修改配置, 默认允许访问/error/auth/**接口,请求头包含X-Api-Origin: Fegin的接口不进行认证拦截。

禁用Session,采用JWT Token的方式进行认证。登录成功后,后续接口需要设置请求头Authorization: (JWT TOKEN), 前端可以通过localStorage存储。

后端采用Redis缓存用户登录状态。

public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin().disable()
                .csrf().disable()
                .httpBasic().disable()
                .cors();

        http.authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                .antMatchers("/error").permitAll()
                .antMatchers("/auth/**").permitAll()
                .requestMatchers(new RequestHeaderRequestMatcher(XApiOrigin.HEADER_NAME, XApiOrigin.TYPE_FEIGN)).permitAll()
                .anyRequest().authenticated();

        http.addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

        http.exceptionHandling()
                .authenticationEntryPoint(new SecurityAuthenticationEntryPoint())
                .accessDeniedHandler(new SecurityAccessDeniedHandler());

        // 关闭session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
    // ...
}

权限控制,需要在各个模块独立进行控制,例如uac模块的用户相关接口

@RestController
@RequestMapping("/uac/users")
public class UserController {
 
    @PublicV1GetMapping("/{id}")
    public UserVo getVoById(@PathVariable("id") String id) {
        User user = userService.getById(id);
        return BeanUtils.convert(user, UserVo.class);
    }

    @ApiLog
    @PreAuthorize("hasRole('ADMIN')")
    @PublicV1PostMapping
    public UserVo saveForAdmin(@Valid @RequestBody User user) {
        if (BeanUtils.isNotNull(userService.getByUsername(user.getUsername()))) {
            throw new ApiException(ApiCode.USR_DUPLICATED, user.getUsername());
        }
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        user.setPassword(encoder.encode(user.getPassword()));
        userService.save(user);
        return BeanUtils.convert(user, UserVo.class);
    }

}

如果接口没有权限,会返回如下所示的错误信息

{
    "timestamp": "2022-07-19T04:08:08.771+00:00",
    "status": 403,
    "error": "Forbidden",
    "exception": "org.springframework.security.access.AccessDeniedException",
    "message": "不允许访问",
    "api_code": 403,
    "api_message": "没有权限"
}

参考

About

SpringCloud Quick Start Template

License:Apache License 2.0


Languages

Language:Java 69.6%Language:Shell 28.8%Language:Dockerfile 1.6%