一、技术栈选择
- 开发工具:VsCode。
- 后端框架:Spring boot。
- 前端框架:Vue.js+ElementUI。
- 服务网络:Spring Cloud Gateway(Zuul)。
- 服务注册与发现:Spring Cloud Eureka。
- 配置中心:Apollo。
- 数据库:MySQL5.7。
- 基础设施:CentOS7.6+Docker+Kubernetes+jenkins。
二、环境配置
(一)开发终端
安装java jdk。此次选择java1.8,教程已经很多,只需在安装后设置JAVA_HOME、CLASSPATH、path三个系统环境变量即可。
如jdk安装目录为C:\Program Files\Java\jdk1.8.0_231
,则
- 新建
JAVA_HOME=C:\Program Files\Java\jdk1.8.0_231
- 新建
CLASSPATH=.;%Java_Home%\bin;%Java_Home%\lib\dt.jar;%Java_Home%\lib\tools.jar
- path中增加
%JAVA_HOME%\bin
。
安装Maven。
在windows下下载.zip压缩包,Linux下下载.tar.gz压缩包,解压缩后,如安装目录为c:\apache-maven-3.6.3
,此后设置几个环境变量path
(添加c:\apache-maven-3.6.3\bin
)、MAVEN_HOME
(设为c:\apache-maven-3.6.3
)。在C:\apache-maven-3.6.3\conf\settings.xml
中找到<mirrors></mirrors>
,添加以下内容:
<mirrors>
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
找到<repository></repository>
<repository>
<id>spring</id>
<url>https://maven.aliyun.com/repository/spring</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
具体引用阿里maven仓库参考官方文档。
安装VsCode。一步步安装即可。
安装java插件,Java Extension Pack,Microsoft公司。同时会自动安装相关联插件。
安装Spring Boot Extension Pack,Pivotal公司。同时会自动安装相关联插件。
安装Lombok Annotations Support for VS Code。
配置Maven。java.configuration.maven.userSettings
,配置为C:\apache-maven-3.6.3\conf\settings.xml
。
(二)Eureka
搭建要给Eureka服务器,需要从源码开始建立,根据需要设置,一般小型化项目使用单节点默认配置即可。
建立一个Spring boot服务工程,pom.xml需要引入:
<!-- 引入cloud -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- eureka server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
代码只需在application类上使用@EnableEurekaServer的注解即可。配置:
# 开放端口
server.port=8761
# 服务名称
spring.application.name='wpeureka'
# 主机名
eureka.instance.hostname=localhost
# 支持使用ip地址注册
eureka.instance.prefer-ip-address=true
# 以下两个配置说明自己是一个服务端
eureka.client.registerWithEureka=false
eureka.client.fetchRegistry=false
#服务地址
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
将其打包为docker,Dockerfile:
FROM openjdk:8-jre
LABEL maintainer="wsxrafael@126.com"
LABEL description="Spring Cloud Eureka for Docker"
ADD eureka-0.0.1-SNAPSHOT.jar /usr/local/lib/eureka-0.0.1-SNAPSHOT.jar
EXPOSE 8761
ENTRYPOINT ["java", "-jar", "/usr/local/lib/eureka-0.0.1-SNAPSHOT.jar"]
(三)Gateway(Zuul)
与Eureka类似,也是要从代码开始编译搭建。
pom.xml需要引入:
<!-- 引入cloud -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 与eureka配合 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
这里需要注意的是,SpringCloud和Springboot需要版本对应,如2.2.5与Hoxton.SR3是对应的。
配置上,将Gateway向Eureka注册,这样,就可以通过网关访问Eureka的服务。
# 开放端口
server.port=9999
# 服务名称
spring.application.name=wpgateway
# gateway开启服务注册和发现的功能,自动根据服务发现为每一个服务创建了一个router,这个router将以服务名开头的请求路径转发到对应的服务。
spring.cloud.gateway.discovery.locator.enabled=true
# 将请求路径上的服务名配置为小写
spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true
# eureka使用ip地址,不使用hostname
eureka.instance.prefer-ip-address=true
# 向eureka服务端注册
eureka.client.service-url.defaultZone=http://172.17.0.1:28061/eureka/
这里将eureka的地址设为172.17.0.1(桥接网络的主机默认IP),即在docker中运行时默认连接的是主机的eureka服务。
Dockerfile:
FROM openjdk:8-jre
LABEL maintainer="wsxrafael@126.com"
LABEL description="Spring Cloud Gateway for Docker"
ADD gateway-0.0.1-SNAPSHOT.jar /usr/local/lib/gateway-0.0.1-SNAPSHOT.jar
EXPOSE 9999
ENTRYPOINT ["java", "-jar", "/usr/local/lib/gateway-0.0.1-SNAPSHOT.jar"]
(四)MySQL
选用容器方式,从仓库中获取官方镜像。
从docker hub上pull一个MySQL的官方镜像
docker pull MySQL:5.7.29
运行镜像。
docker run \
--name MySQL5.7 \//命名
-p 23306:3306 \//将数据库端口3306绑定到主机的23306上
-v /data/MySQL/data:/var/lib/MySQL \//将数据库数据目录绑定到主机目录/data/MySQL/data上
--restart=always \//自动重启
-e MySQL_ROOT_PASSWORD=123456 \//设置MySQL的root密码为123456
-d MySQL:5.7.29 \
--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci//设置使用utf8字符集。数据库的环境设置,可以通过绑定目录的方式实现,-v /data/MySQL/custom:/etc/MySQL/conf.d,在主机目录/data/MySQL/custom下建立配置文件my.cnf,即可配置数据库环境。
(五)Jenkins
选用容器方式,从仓库中获取官方镜像。
从docker hub上pull一个官方镜像
docker pull jenkins/jenkins
运行镜像。
docker run \
--name jenkins \//命名
-p 8042:8080 \//绑定端口,这里是8042
-v /data/jenkins/data:/var/jenkins_home \//绑定数据目录,这里是到主机目录/data/MySQL/data上
--restart=always \//自动重启
-u root -d jenkins/jenkins
配置
初次访问,http://x.x.x.x:8042,需要激活,激活码在/var/jenkins_home/secrets/initialAdminPassword
,绑定卷后在主机的/data/jenkins/data/secrets/initialAdminPassword
中。
之后选择并安装插件。
之后创建管理员账户。
配置插件更新的国内镜像地址,在Manage Jenkins->Plugin Manager->Advanced
中将升级站点由https://updates.jenkins.io/update-center.json
换为https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json
。
同時修改/data/jenkins/data/updates/default.json
文件,把updates.jenkins-ci.org/download
为mirrors.tuna.tsinghua.edu.cn/jenkins
,把www.google.com
修改成www.baidu.com
。
流水线插件
依赖插件:pipeline。
pipeline的所有配置都在脚本文件中,可以在代码中建立一个Jenkinsfile的脚本文件,主要配置参见下图。
支持从版本管理库中拉取代码,然后执行其中的Jenkinsfile脚本。
一个简单的构建脚本如下:
node {
// 拉取代码
stage('pull code'){
checkout scm
}
// 编译
stage('backend build'){
dir('src') {
sh 'mvn -DskipTest clean install'
}
}
// 单元测试
stage('unit test'){
dir('src') {
sh 'mvn test'
}
}
// docker打包
stage('build docker image'){
dir('wphomepage/backend/docker'){
sh './build.sh'
}
}
}
(六)Apollo
官网帮助中4.5讲的比较明白。
几个概念
- Apollo依托于MySQL数据库,数据存储都在MySQL中。
- Apollo服务端的组成:Config Service、Admin Service、Meta Server、apollo Portal,简单的说就是Config Service负责向客户端提供配置项的读取、推送服务,Admin Service负责面向Portal对配置项进行修改、发布,portal面向用户提供用户界面,Config Service和Admin Service向Eureka注册实现多实例部署发现,在Eureka之上。架了一层Meta Server用于封装Eureka的服务发现接口。
- namespase:若干配置项的集合,apollo默认会建立一个名为application的namespace,绝大部门场景下用这个配置就够了。
引用:官网链接
创建数据库
数据库主要有两种:portal库和config库。Portal数据库只需部署一套,Config数据库需要每一个环境配置一套(DEV/UAT/FAT/PRO)。示例中只装一个DEV环境配置库和一个Portal数据库。数据库sql脚本地址目录下的apolloportaldb.sql和apolloconfigdb.sql。
- 登录数据库
mysql -h127.0.0.1 -P3306 -uroot -p123456
。 - 导入portal数据库,执行脚本:
source ./apolloportaldb.sql
。注意:导入脚本将清空已有数据库。 - 导入config数据库,修改apolloconfigdb.sql并命名为apolloconfigdbdev.sql将脚本里的数据库名ApolloConfigDB改为ApolloConfigDBDev,执行脚本:
source ./apolloportaldbdev.sql
。注意:导入脚本将清空已有数据库。 - 数据库的配置。参考官方帮助的2.1.3,需要更改的配置项都位于两个数据库(ApolloPortalDB、ApolloConfigDB)的ServerConfig表。
需要注意的是config数据库的ServerConfig表中的eureka.service.url配置项,这是配置 Eureka服务Url地址的,一般默认配置localhost即可,因为config service自带一个eureka服务。
引用
创建docker
借用前人成果,引用。
创建docker-compose.yaml,示例:
version: '2.2'
services:
apollo:
image: idoop/docker-apollo:latest
container_name: apollo
network_mode: "bridge"
restart: always
ports:
- 28070:8070
environment:
PORTAL_DB: jdbc:mysql://172.17.0.1:23306/ApolloPortalDB?characterEncoding=utf8
PORTAL_DB_USER: root
PORTAL_DB_PWD: 123456
PORTAL_PORT: 28082
DEV_DB: jdbc:mysql://172.17.0.1:23306/ApolloConfigDBDev?characterEncoding=utf8
DEV_DB_USER: root
DEV_DB_PWD: 123456
docker-compse up -d
启动。
访问http://localhost:28070
即可访问
默认超级用户apllo:admin
三、后端开发架构
(一)初始化Spring boot代码架构
有两种方法,一是通过https://start.spring.io/,在线生成模板,直接访问该网址,勾选选项即可。二是在VsCode中通过Spring boot插件生成,按F1
或Shift+Ctrl+P
调出命令行,选择Spring Initializr: Generate a Maven Project
向导,选择项与在线模板相同。初始化后的代码结构如下:
在VsCode中调试,将会生成.vscode目录,保存调试工程文件。
另外,在src\main\java\com\wp\demo目录下,根据需要再建子目录,推荐建立controller(web访问路由)、dao(数据库访问)、model(Java bean)、service(服务逻辑)目录。
代码结构如下:
(二)编译运行。
在工程目录下。
执行mvn install
将在target目录下生成jar包。
执行java -jar target\xxx.jar
即可运行。
执行mvn -clean
即可清除编译结果。
(三)VsCode中调试
VsCode中创建调试配置。
将生成.vscode目录,内含调试配置。
(四)程序配置
使用application.properties或application.yml文件配置程序行为,常用配置有:
配置端口:server.port = 9090
程序配置在Apollo中集中配置的方法参见:官网说明。
(五)Lombok
使用Lombok插件来简化POJO代码,需要在IDE中安装插件,其作用是使用注释自动生成代码。其原理是:针对“JSR 269 Pluggable Annotation Processing API”规范,Lombok实现了该规范,在javac(java6之后)运行时得到调用。常用的几个注解:
- @Data:@ToString @EqualsAndHashCode @Getter @Setter @RequiredArgsConstructor的集合。
- @AllArgsConstructor:生成全参构造函数。
- @NoArgsConstructor:生成无参构造函数。
- @RequiredArgsConstructor:生成只包含final和@NonNull注解的成员变量的构造函数。
- @ToString:覆盖默认的toString()方法。
- @Getter/@Setter:生成所有成员变量的getter/setter方法。
(六)单元测试
对测试类使用@SpringBootTest注解标明这是一个测试类。对类中测试方法使用@Test标明这是一个测试方法。
针对Controller采用MockMvc模块进行测试。对类使用@RunWith(SpringRunner.class)、@AutoConfigureMockMvc标明。在方面中使用MockMvc、MockMvcRequestBuilders、MockMvcResultMatchers、MockMvcResultHandlers进行测试。
(七)向Eureka、Gateway注册
pom.xml文件添加依赖:
<!-- 引入cloud -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 与eureka配合 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
application.properties文件配置:
# 服务名称
spring.application.name=wphomepage
# eureka使用ip地址,不使用hostname
eureka.instance.prefer-ip-address=true
# 向eureka服务端注册
eureka.client.service-url.defaultZone=http://172.17.0.1:28061/eureka/
在Controller类上加注解:@EnableEurekaClient。
这样就可以通过通过网关访问注册的服务:http://主机d地址:28099/服务名/访问路径。
(八)数据库操作
准备
连接池使用阿里巴巴的Druid,操作数据库有两种方式,一种使用JdbcTemplate直接操作,一种使用MyBatisjinx ORM操作。
【pom.xml文件引入依赖】
<!--JDBC-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<scope>compile</scope>
</dependency>
<!--MySQL-->
<dependency>
<groupId>MySQL</groupId>
<artifactId>MySQL-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--MyBatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
<scope>compile</scope>
</dependency>
<!--阿里Druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>
【属性配置】
在程序配置文件application.properties(或application.yml)中。
#Alibaba Druid
spring.datasource.druid.url = jdbc:MySQL://103.45.109.29:23306/demo?autoreconnect = true&useUnicode=true&characterEncoding=utf8
spring.datasource.druid.username = root
spring.datasource.druid.password = 123456
spring.datasource.druid.driver-class-name = com.MySQL.jdbc.Driver
spring.datasource.druid.max-active = 20
MyBatis
【数据库】
表名:demotest。
两个字段:int型RECID为主键,varchar(255)型name。
【POJO类代码片段】
Demo.java
package com.wp.demo.model;
import lombok.Data;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Demo{
private int id;
private String name;
}
【Mapper类代码片段】
DemoMapper.java
package com.wp.demo.dao;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.ResultMap;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.type.JdbcType;
import com.wp.demo.model.*;
@Mapper
public interface DemoMapper {
@Select("select * from demotest")
@Results(id = "demotestTable", value = {
@Result(column = "RECID", property = "id", jdbcType = JdbcType.INTEGER, id = true) })
Demo[] getDemos();
@Select("select * from demotest where RECID=#{id}")
@ResultMap(value = "demotestTable")
Demo getDemo(int id);
}
对于数据库字段名和POJO类成员变量名称不同的情况,可以通过@Results注解进行映射,@Results有两个属性,id和value,id相当于给这个映射起名,value相当于是一个映射关系集合。这个集合由多个@Result组成,属性column指明数据库字段名,property指明POJO类成员变量名,jdbcType指明数据库中字段类型,id为true表明是主键。后续使用时使用@ResultMap注解即可使用。
【Controller类代码片段】
DemoServiceController.java
package com.wp.demo.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import com.wp.demo.service.DemoService;
import com.wp.demo.model.Demo;
@RestController
public class DemoServiceController {
@Autowired
DemoService demoService;
@RequestMapping(value = "/demos")
public ResponseEntity<Object> getDemo() {
return new ResponseEntity<>(demoService.getDemos(), HttpStatus.OK);
}
@RequestMapping(value = "/demos/{id}", method = RequestMethod.GET)
public ResponseEntity<Object> getDemo(@PathVariable int id){
return new ResponseEntity<>(demoService.getDemo(id), HttpStatus.OK);
}
(九)swagger支持
pom.xml修改
添加:
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
新建config类:SwaggerConfig.java
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
}
访问swagger2的地址,如果服务端口开的是8888,则访问http://localhost:8888/v2/api-docs
,可以看到生成的api文档。文档可读性差,所以加入SwaggerUI的支持。
SwaggerUI的支持,修改pom.xml
添加:
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
再访问http://localhost:8888/swagger-ui.html
,可读性好了很多。
配置拦截器
如果访问不到这个地址,需要配置拦截器。
swagger-ui.html位置在classpath:/META-INF/resources/中,需要路由到这里。
package com.zt.server.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
}
}
这里注意,如果使用extends WebMvcConfigurationSupport
,则会取消对所有默认配置的支持,则需要代码中加上对于静态资源的引用,建议采用implements WebMvcConfigurer
的方式。
引用:
- https://www.ibm.com/developerworks/cn/java/j-using-swagger-in-a-spring-boot-project/index.html
- https://my.oschina.net/pwh19920920/blog/2413927
四、问题
(一)RunWith cannot be resolved to a type错误
这个错误是由于引入了不同的junittest扩展而引起,在pom.xml中找到
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
这是VS Code通过Springboot插件自动添加的测试组件,在其中找到exclusion一节,将其删除。
区别在于import的包不一样。其自动引入的诸如:
import org.junit.jupiter.api.Test;
实际只需引入:
import org.junit.Test;
在原配置下,找不到RunWith的包,所以报错。