一个精简的微服务项目总结

一个提供SDK接口的精简微服务项目的总结,使用的是SSM(springboot + springMVC + Mybatis)框架,配合Nacos进行服务发现和注册以及服务配置,项目主要包括两个服务:一个网关服务(server_gateway)和一个接口服务(server_appsdk)。

一、IDEA环境相关

  1. Maven Helper 插件安装
    操作maven 命令,如使用maven 打包等

  2. Lombok 插件安装
    lombok 能够减少大量的模板代码,使用
    @Data 注解时可以不用书写
    getter,setter方法,
    toString方法
    hashCode方法
    equals方法等
    @NoArgsConstructor, @AllArgsConstructor 可以不用写构造函数
    需要添加依赖

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
  1. MyBatisCodeHelperPro 插件安装
    设计完表后可以使用该插件自动生成dao层的表对应实体domain、mapper、以及mapper对应sql的xml文件,
    也可以生成service这个看个人需要,前面生成的dao层非常好用

  2. 如果IDEA底部找不到service面板,解决方法:
    打开.idea目录里面的 workspace.xml文件,找到 RunDashboard 项,用下面内容替换下

<component name="RunDashboard">
    <option name="configurationTypes">
      <set>
        <option value="SpringBootApplicationConfigurationType" />
      </set>
    </option>
    <option name="ruleStates">
      <list>
        <RuleState>
          <option name="name" value="ConfigurationTypeDashboardGroupingRule" />
        </RuleState>
        <RuleState>
          <option name="name" value="StatusDashboardGroupingRule" />
        </RuleState>
      </list>
    </option>
  </component>

二、项目结构

1.多模块项目结构

项目名为 server-starter,是一个 新建项目 -》选择 Maven 创建的一个 maven 项目目录作为整个项目的workspace和父pom
一个多模块项目通过一个父POM 引用一个或多个子模块来定义。在父项目的pom.xml中通过以下配置,将子项目关联。

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.7.RELEASE</version>
        <relativePath/>
    </parent>

    <groupId>com.hp</groupId>
    <artifactId>server-starter</artifactId>
    <version>1.0.0</version>
    <packaging>pom</packaging>  
    
    <modules>  
        <module> server_common </module>  
        <module> server_gateway </module> 
        <module> server_appsdk </module> 
    </modules>  

其中 <packaging>pom</packaging> 这个父项目不是创建一个JAR 或者一个WAR,它仅仅是一个引用其它 Maven 项目的POM。
pom.xml 中 modules 列出了项目的子模块。每个modules 元素对应了一个 server-starter 目录下的子目录。Maven知道去这些
子目录寻找pom.xml 文件,并且在构建的 server-starter 的时候,它会将这些子模块包含到要构建的项目中。
子项目的创建:在父项目目录右击new-》Module-》Spring Initalizer 创建相应的子项目

仅仅在父项目配置子项目是不能够真正实现关联的,子项目中需要配置

<parent>  
    <groupId>com.hp</groupId>  
    <artifactId>server-starter</artifactId>  
    <version>1.0.0</version>  
</parent>  

2.编译打包

现在,通过父pom.xml将多个子项目进行了关联,在server-starter上面右键 Run Maven -> clean/package 或者 clean install。
如果右键没有 Run Maven 选项,检查Maven Helper 插件是否安装好
将多个子项目打包,每个子项目都是一个独立的springboot服务都可以打成 JAR包直接在Linux 上面使用 java 命令运行

编译打包,把resources下面面的资源全部打包,并且打包到指定的目录路径下,在各个子项目的pom.xml中添加

<build>
    <finalName>hp-${project.artifactId}</finalName>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
            <includes>
                <include>**/*.*</include>
            </includes>
        </resource>
    </resources>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
        <!--打包到指定目录下-->
        <plugin>
            <artifactId>maven-antrun-plugin</artifactId>
            <executions>
                <execution>
                    <id>copy-jar</id>
                    <phase>package</phase>
                    <configuration>
                        <tasks>
                            <copy todir="../bin">
                                <fileset dir="./target">
                                    <include name="*.jar"/>
                                </fileset>
                            </copy>
                        </tasks>
                    </configuration>
                    <goals>
                        <goal>run</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

3. 服务模块代码结构

大致的结构jave目录下
com.ali.hp包下面创建以下几个包

  • config
    配置包,如 mybatis,redis,swagger 等相关配置类放在该包下

  • pojo
    自定义的各个对象数据包

  • dao
    数据库相关的数据层包,里面有 domain, mapper, mapperxml等目录,这个包可以放在 server_common 公共模块中,这一各个服务都可以使用

  • service
    提供服务包,主要的业务逻辑的处理都在该包下面,一般用接口 + 实现的层次结构,即在包下面的各个具体服务目录下 建一个接口和一个 impl 包,impl包下写接口的实现

  • controller
    提供前端访问的接口包

  • schedule
    提供定时任务的包

  • 以及其他一些需要的包

4. 多环境配置

比如配置三个环境:dev 开发环境,test 测试环境, prod 生成环境
在 resources 目录下创建4个配置文件
application.yml,application-dev.yml,application-test.yml,application-prod.yml
具体使用哪个配置文件是根据 application.yml 里面的 spring.profiles.active 值决定的。
这样在开发的时候 application-dev.yml 不需要提交到svn,其他的配置可以提交到svn
举例:
application.yml

server:
  port: 8001
spring:
  profiles:
    active: test
  application:
    name: mser-service

application-dev.yml

env:
  serv-addr: 192.168.121.223
spring:
  cloud:
    nacos:
      discovery:
        server-addr: ${env.serv-addr}:8848

application-test.yml

env:
  serv-addr: 47.96.16.xxx
spring:
  cloud:
    nacos:
      discovery:
        server-addr: ${env.serv-addr}:8848

5. 常用注解

@Data
自动生成对象属性的get,set等方法
@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor
注解在类上, 为类提供无参,有指定必须参数, 全参构造函数
@PostConstruct
注解在方法上会在服务器加载Servlet的时候运行,并且只会被服务器调用一次,类似于Serclet的inti()方法。被@PostConstruct修饰的方法会在构造函数之后,init()方法之前运行。
@PreConstruct
注解在方法上会在服务器卸载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的destroy()方法。被@PreConstruct修饰的方法会在destroy()方法之后运行,在Servlet被彻底卸载之前
@Autowired
默认按照类型装配的,属于spring的
@Resource
默认按照名字装配的,属于J2EE的
@Controller
@ResponseBody
@RestController
相当于 @Controller 和 @ResponseBody的组合, 用于 controller 层
@RequestMapping("/api/user/")
@Value("${server.port}")
读取yml里面的配置值

三、Mybatis

封装数据库相关的操作,配合MyBatisCodeHelperPro 插件非常好用
如果是多表联合查询,可以单独写个Mapper,根据需要自定义一个对象用来返回sql查询的结果

1.添加依赖

<!-- mybatis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.4</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.1</version>
</dependency>
<!-- mybatis pagehelper -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>4.2.1</version>
</dependency>

2.添加配置项

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://${env.serv-addr}:3306/gpsdk?characterEncoding=UTF-8&useTimezone=true&serverTimezone=GMT%2B8
    username: root
    password: 123456
    hikari:
      minimum-idle: 5 #最小连接数
      idle-timeout: 600000 #超时时间
      maximum-pool-size: 10 #连接池大小
      auto-commit: true #是否自动提交
      pool-name: MyHikariCP #连接池名字
      max-lifetime: 1800000
      connection-timeout: 30000
      connection-test-query: SELECT 1

3.创建配置类

创建一个config包,在包下建一个 MybatisConfig 配置类(在各自的服务模块上创建,不能在公共模块上建,依赖可以放到公共模块上)
在配置类中配置好MyBatisCodeHelperPro 插件自动生成dao层的domain、mapper、以及mapper对应sql的xml文件

@Configuration
@EnableTransactionManagement
@MapperScan("com.hp.common.dao.mapper")
public class MybatisConfig implements TransactionManagementConfigurer {

    @Autowired
    private DataSource dataSource;

    public MybatisConfig() {
    }

    @Override
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory() {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setTypeAliasesPackage("com.hp.common.dao.domain");

        //分页插件
        PageHelper pageHelper = new PageHelper();
        Properties properties = new Properties();
        properties.setProperty("reasonable", "false");
        properties.setProperty("supportMethodsArguments", "true");
        properties.setProperty("returnPageInfo", "check");
        properties.setProperty("params", "count=countSql");
        pageHelper.setProperties(properties);
        bean.setPlugins(new Interceptor[]{pageHelper});

        try {
            ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            // mybatis配置
            org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
            configuration.setLazyLoadingEnabled(true);
            configuration.setUseGeneratedKeys(true);
            configuration.setDefaultExecutorType(ExecutorType.REUSE);
            configuration.setDefaultStatementTimeout(30);
            configuration.setMapUnderscoreToCamelCase(true);
            configuration.setAutoMappingBehavior(AutoMappingBehavior.FULL);
            bean.setConfiguration(configuration);

            bean.setMapperLocations(resolver.getResources("classpath*:mapper/*.xml"));
            return bean.getObject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

四、Redis

1.添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.添加配置

spring:
  datasource:
    redis:
        database: 0
        host: ${env.serv-addr}
        port: 6379
        password: 
        jedis:
          pool:
            max-active: 20
            min-idle: 0
            max-idle: 20
            max-wait: -1ms

3.使用注意

redis模板对象注入 不要使用 @Autowired 会报错,应该使用 @Resource
StringRedisTemplate 可以使用@Autowired注入

    @Resource
    RedisTemplate<String, Integer> redisTemplate;

五、Swagger

生成api接口文档,再也不用单独在写一个接口文档了,直接通过注解的方式生成文档

1.添加依赖

<!-- swagger依赖 -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.8.0</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.8.0</version>
</dependency>

2.创建配置类

在配置类中配置好controller包的全路径名

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Value("${spring.profiles.active:dev}")
    private String env;
    @Value("${server.port}")
    private String serverPort;

    @Bean
    public Docket createAppRestApi() {
        List<Parameter> pars = new ArrayList<>();

        return new Docket(DocumentationType.SWAGGER_2)
                .enable("dev".equals(env) || "test".equals(env))
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.hp.mser.controller"))
                .paths(PathSelectors.any())
                .build()
                .globalOperationParameters(pars)
                .apiInfo(apiInfo());
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("开放平台服务文档")
                .description("提供开放平台服务")
                .termsOfServiceUrl("http://localhost:" + serverPort + "/v2/api-docs")
                .contact(new Contact("x", "", ""))
                .version("1.0.0")
                .build();
    }
}

3.使用

1.数据对象
在数据对象类上添加注解
@ApiModel(value = "xxxDTO", description = "xxx请求/应答")
在对象属性字段上添加注解
@ApiModelProperty(value = "小游戏ID", name = "id")
2.访问接口
在controller里面的类上添加注解
@Api(tags = "用户接口")
在访问接口类的方法上添加注解
@ApiOperation(value = "swagger测试")

六、Nacos

Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您实现动态服务发现、服务配置管理、服务及流量管理。

1. 下载安装

前往 https://github.com/alibaba/nacos/releases 下载

tar -xvf nacos-server-1.x.x.tar.gz

启动:解压后在 nacos/bin目录下 执行如下命令启动

Linux/Unix/Mac 下

sh startup.sh -m standalone

Windows 下

cmd startup.cmd

测试:打开 http://localhost:8848/nacos/#/login ,默认账号密码都是nacos

2. 添加依赖包

    <!-- nacos 发现服务 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- nacos 配置服务 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

3. 增加配置项

spring:
  cloud:
    nacos:
      discovery:
        server-addr: ${env.address}:8848

4. 添加注解

在程序的main方法上添加:
@EnableDiscoveryClient 开启服务注册发现功能

5. 导入配置

server-appsdk.jar 运行启动时优先会读取同级目录下的启动配置文件,然后根据该配置文件找到nacos上配置的程序需要的配置文件

bootstrap.properties

# nacos配置服务地址
spring.cloud.nacos.config.server-addr=localhost:8848
# 配置文件名
spring.cloud.nacos.config.prefix=server-appsdk
# 环境
spring.profiles.active=test
# 后缀
spring.cloud.nacos.config.file-extension=yaml
#最终生成的文件名规则
# ${spring.cloud.nacos.config.prefix}-${spring.profile.active}.${file-extension}

在 nacos 控制台中 配置管理 | 配置列表 | 导入配置 上传 server-appsdk-test.yaml 配置文件,这样jar包程序就可以读取到这个配置了。

6. 服务管理

程序启动后,在 nacos 控制台中 服务管理 | 服务列表 中查看启动的服务是否已经在列表中了,在说明服务已经注册成功了,可以在控制台中管理该服务了

七、Gateway

Spring Cloud Gateway,相比之前我们使用的 Zuul(1.x) 它有哪些优势呢?Zuul(1.x) 基于 Servlet,使用阻塞 API,它不支持任何长连接,如 WebSockets。
Spring Cloud Gateway 使用非阻塞 API,支持 WebSockets,支持限流等新特性。

1.网关的路由配置

spring:
  application:
    name: gateway-client
  cloud:
    gateway:
      routes:
      - id: route_server_appsdk
        # uri以lb://开头(lb代表从注册中心获取服务),后面接的就是你需要转发到的服务名称
        uri: lb://server_appsdk
        predicates:
        - Path=/api/user/**

id:我们自定义的路由 ID,保持唯一
uri:目标服务地址
predicates:路由条件,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)
- Path 通过请求路径匹配
filters:过滤规则

2.过滤器 filter

过滤器可以在路由请求之前对请求进行处理,也可以在请求响应之后对响应进行处理
在请求路由之前可以做比如参数校验,鉴权,日志记录,协议转换,请求参数修改,路径修改等
在请求响应之后可以做比如记录响应消息,修改响应,修改响应头等
过滤器最常见的功能就是鉴权,日志记录,限流和权重路由
Filter分为Gateway Filter和Global Filter

Global Filter是全局的过滤器,配置之后对所有路由都有效

过滤器 说明
LoadBalancerClientFilter 负载均衡过滤器
Netty Routing Filter 默认使用netty的底层
RouteToRequestUrlFilter 新的请求路由
Websocket Routing Filter websocket路由
Gateway Metrics Filter 路由监控,配合spring-boot-starter-actuator

自定义全局过滤器

@Slf4j
@Component
public class MyFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("this is a pre filter");
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            log.info("this is a post filter");
        }));
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

注意:

1.这里的order不能使用@order 的注解

2.在fitler里面的是pre类型,会在路由前执行,在then里面的是post类型会在路由后执行

3.order 的数值越小pre越先执行,post越后执行

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260