一、引言
1.1 实验目的
理解web Service和SOA架构,构建web 服务。要求:
1)构建校友录服务: 基于RESTful样式,对校友录进行插入、检索、修改、删除、统计等功能;同时,后端数据库使用Mybatis或者Hibernate进行操作。
2)把【校友录服务】部署在docker等容器中。
3)基于spring cloud在2)的基础上构建微服务,并进行部署
1.2 环境配置
- 操作系统:Windows 10 X64,ubuntu虚拟机18.04.1
- 编程工具:Intellij IDEA 2019.2×64,Navicat for MySQL
- 数据库:Mysql(使用Mybatis框架操作)
- 环境:Java jdk 1.8
二、构建校友录服务
2.1 创建数据库
创建数据库alumni_system,创建用户表user
2.2 项目后端代码
(1)后端代码结构如下
(2)Controller层代码
具体代码如下:UserController.java
@Controller
@RequestMapping("/course")
@ComponentScan(value = "course.service")
public class UserController {
private UserService userService;
@Autowired
public UserController(UserService userService){
this.userService = userService;
}
@RequestMapping("/addUser")
public String toRegisterPage(){
return "setInfo";
}
@RequestMapping("/setInfo")
public String insert(HttpServletRequest request, Model model){
userService.insert(request);
model.addAttribute("msg", "注册成功!");
return "redirect:/course/getAll";
}
@RequestMapping("/find")
public String toGetUserPage(){
return "findById";
}
@RequestMapping("/findById")
public String findById(HttpServletRequest request,Model model){
Integer id = Integer.parseInt(request.getParameter("userId"));
User user = userService.findById(id);
model.addAttribute("userinfo",user);
return "findAllUser";
}
@RequestMapping("/getAll")
public String findAll(Model model){
List<User> user = userService.findAll();
model.addAttribute("userinfo",user);
return "findAllUser";
}
@RequestMapping("/updateUser")
public String toupdatePage(HttpServletRequest request,Model model){
Integer id = Integer.parseInt(request.getParameter("id"));
model.addAttribute("id",id);
return "updateInfo";
}
@RequestMapping("/updateInfo")
public String update(HttpServletRequest request, Model model){
Integer id = Integer.parseInt(request.getParameter("id"));
userService.updateById(request,id);
model.addAttribute("msg", "修改成功!");
return "redirect:/course/getAll";
}
@RequestMapping("/deleteUser")
public String deleteById(HttpServletRequest request){
Integer id = Integer.parseInt(request.getParameter("id"));
userService.deleteById(id);
return "redirect:/course/getAll";
}
}
(3)Entity层代码
具体代码如下:User.java
@Getter
@Setter
@ToString
public class User {
private Integer id;
private String name;
private String sex;
private String birthday;
private Integer graduationyear;
private String post;
private String cellphone;
private String email;
private String wx;
public User(){}
public User(String name, String sex, String birthday, String graduationyear, String post, String cellphone, String email, String wx)
{
this.name = name;
this.sex = sex;
this.birthday = birthday;
this.graduationyear = Integer.parseInt(graduationyear);
this.post = post;
this.cellphone = cellphone;
this.email = email;
this.wx = wx;
}
}
(4)Mapper层代码
具体代码如下:UserMapper.java
@Mapper
@Repository
public interface UserMapper {
int insert(User user);
int updateById(@Param("updated") User updated, @Param("id") Integer id);
User findAllById(@Param("id")Integer id);
int deleteById(@Param("id")Integer id);
List<User> findAll();
}
(5)Service层代码
具体代码如下:UserService.java
public interface UserService {
public int insert(HttpServletRequest request);
public int updateById(HttpServletRequest request,Integer id);
public User findById(Integer id);
public int deleteById(Integer id);
public List<User> findAll();
}
UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
@Override
public int insert(HttpServletRequest request) {
User user = new User(request.getParameter("name"),request.getParameter("sex"),
request.getParameter("birthday"),request.getParameter("graduationYear"),
request.getParameter("post"),request.getParameter("cellphone"),
request.getParameter("email"),request.getParameter("wx"));
return userMapper.insert(user);
}
@Override
public int updateById(HttpServletRequest request,Integer id){
User user = new User(request.getParameter("name"),request.getParameter("sex"),
request.getParameter("birthday"),request.getParameter("graduationYear"),
request.getParameter("post"),request.getParameter("cellphone"),
request.getParameter("email"),request.getParameter("wx"));
return userMapper.updateById(user,id);
}
@Override
public User findById(Integer id){
return userMapper.findAllById(id);
}
@Override
public int deleteById(Integer id){
return userMapper.deleteById(id);
}
@Override
public List<User> findAll(){
return userMapper.findAll();
}
}
(6)UserMapper.xml
后台使用mybatis的MybatisCodeHelper工具自动生成mapper的xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.course.Mapper.UserMapper">
<resultMap id="BaseResultMap" type="com.example.course.Entity.User">
<!--@mbg.generated-->
<id column="id" jdbcType="INTEGER" property="id" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="sex" jdbcType="VARCHAR" property="sex" />
<result column="birthday" jdbcType="VARCHAR" property="birthday" />
<result column="graduationYear" jdbcType="INTEGER" property="graduationyear" />
<result column="post" jdbcType="VARCHAR" property="post" />
<result column="cellphone" jdbcType="VARCHAR" property="cellphone" />
<result column="email" jdbcType="VARCHAR" property="email" />
<result column="wx" jdbcType="VARCHAR" property="wx" />
</resultMap>
<sql id="Base_Column_List">
<!--@mbg.generated-->
id, `name`, sex, birthday, graduationYear, post, cellphone, email, wx
</sql>
<insert id="insert" parameterType="com.example.course.Entity.User">
<!--@mbg.generated-->
insert into user (id, `name`, sex,
birthday, graduationYear, post,
cellphone, email, wx
)
values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR}, #{sex,jdbcType=VARCHAR},
#{birthday,jdbcType=VARCHAR}, #{graduationyear,jdbcType=INTEGER}, #{post,jdbcType=VARCHAR},
#{cellphone,jdbcType=VARCHAR}, #{email,jdbcType=VARCHAR}, #{wx,jdbcType=VARCHAR}
)
</insert>
<!--auto generated by MybatisCodeHelper on 2020-05-31-->
<update id="updateById">
update user
<set>
<if test="updated.id != null">
id = #{updated.id,jdbcType=INTEGER},
</if>
<if test="updated.name != null">
name = #{updated.name,jdbcType=VARCHAR},
</if>
<if test="updated.sex != null">
sex = #{updated.sex,jdbcType=VARCHAR},
</if>
<if test="updated.birthday != null">
birthday = #{updated.birthday,jdbcType=VARCHAR},
</if>
<if test="updated.graduationyear != null">
graduationYear = #{updated.graduationyear,jdbcType=INTEGER},
</if>
<if test="updated.post != null">
post = #{updated.post,jdbcType=VARCHAR},
</if>
<if test="updated.cellphone != null">
cellphone = #{updated.cellphone,jdbcType=VARCHAR},
</if>
<if test="updated.email != null">
email = #{updated.email,jdbcType=VARCHAR},
</if>
<if test="updated.wx != null">
wx = #{updated.wx,jdbcType=VARCHAR},
</if>
</set>
where id=#{id,jdbcType=INTEGER}
</update>
<!--auto generated by MybatisCodeHelper on 2020-05-31-->
<select id="findAllById" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from user
where id=#{id,jdbcType=INTEGER}
</select>
<!--auto generated by MybatisCodeHelper on 2020-05-31-->
<delete id="deleteById">
delete from user
where id=#{id,jdbcType=INTEGER}
</delete>
<!--auto generated by MybatisCodeHelper on 2020-06-01-->
<select id="findAll" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from user
</select>
</mapper>
2.3 项目前端代码
前端使用bootstrap框架,代码结构如下:
2.4 运行结果
(1)查看所有校友信息
(2)检索校友信息
按照校友ID检索校友信息:
跳转到搜索页面,输入校友ID:
返回搜索结果:
(3)插入校友信息
点击导航栏的“新增校友”:
进入新增校友信息页面:
输入要插入的校友信息:
点击“确认注册”返回校友信息列表,发现信息插入成功:
(4)删除校友信息
点击校友信息最后一栏的“删除”按钮:
提示是否删除:
点击“确定”,删除成功后返回校友信息列表,发现删除信息成功:
三、把【校友录服务】部署在docker容器中
本次实验我将构建好的校友录服务部署在了我本地ubuntu虚拟机的docker容器中,并可以在虚拟机上运行此服务,我的部署流程如下:
3.1 在ubuntu虚拟机上安装docker容器
(1)打开ubuntu终端,运行安装docker命令
sudo apt-get install -y docker.io
(2)等待安装完毕,使用下面的命令启动 docker:
sudo systemctl start docker
(3)运行系统引导时启用 docker,命令:
sudo systemctl enable docker
(4)检验是否安装并启动成功docker:
跑一下hello-world镜像看能否启动成功,使用指令docker run hello-world,若能输出“Hello from Docker!”,则说明安装并启动成功,可以运行镜像了
3.2 在docker中安装tomcat和mysql容器
(1)安装tomcat指令:
sudo docker pull tomcat
(2)安装mysql指令(默认安装版本为5.7):
sudo docker pull mysql
(3)安装好后,我们使用sudo docker images指令查看安装好的tomcat和mysql
3.3 在docker中启动mysql并建立数据库和表
(1)使用指令启动mysql数据库服务
sudo docker run -itd --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7 --character-set-server=utf8 --collation-server=utf8_unicode_ci
解释上面命令的含义:
- run -itd:在docker中运行一个带交互式的且能后台运行的容器。
- –name mysql:给容器命名为mysql,注意name前面有两个小横杠,你也可以自己随意命名。
- -p 3306:3306:这里是指将容器的3306端口映射到主机的3306端口,冒号前面指的是主机,后面指的是容器。
- -e MY_ROOT_PASSWORD=123456:这行命令的意思是在创建mysql容器的时候在容器中创建一个root用户,密码是123456,要是不想用123456,把它改成你想要的就可以了。
- –character-set-server=utf8 --collation-server=utf8_unicode_ci:解决docker中mysql数据库中文乱码问题
(2)进入mysql容器并登陆
执行指令
sudo docker exec -it mysql bash
再执行
mysql -u root -p
输入密码(123456)
(3)创建数据库alumni_system
create database alumni_system
(4)利用sql文件建表
a)sql文件可以由Navicat导出并存储,复制到ubuntu虚拟机/home文件夹下
b)首先得到mysql容器的全ID,执行指令:
sudo docker inspect -f '{{.Id}}' mysql
c)将sql文件上传到docker中,执行指令:
sudo docker cp /home/jade/alumni_system.sql
b38fcda698d8f2fdceb980c3bf8b26372b3aad10016f65c432f791188d1fefaa:/tmp/
d)选中数据库alumni_system,在mysql中执行语句:
mysql>use alumni_system
e)将sql插入数据库,执行语句:
mysql>source /tmp/alumni_system.sql
插入成功如下,使用desc user查看表:
(5)查看mysql的ip地址,修改spring项目中application.yml(或application.properties)的数据库ip地址
在ubuntu命令行中输入以下指令查看IP地址:
sudo docker inspect mysql
图上可以看到,这里的ip是172.17.0.2,也就是说我们如果部署web应用,就要在工程文件中指定访问的数据库的ip地址为172.17.0.2,这样web应用部署在tomcat上才会去读取mysql中的数据。我这里的工程文件如下图所示:
将工程文件中连接数据库的配置信息改成我们容器的ip地址即可。
3.4 springBoot项目准备(打成jar包)
(1)在pom.xml中添加docker的插件(添加代码后maven项目会自动导入)
主要用了docker-maven-plugin这个maven插件
<!--jar包命名-->
<name>course</name>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- docker 插件 begin -->
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>1.0.0</version>
<configuration>
<imageName>${docker.image.prefix}/${project.artifactId}</imageName>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include>
</resource>
</resources>
</configuration>
</plugin>
<!-- docker 插件 end -->
</plugins>
<finalName>course</finalName><!--打包后文件名称-->
</build>
(2)点击右侧maven状态栏中的Lifecycle的install进行打包成jar包
生成的jar包在target目录下:
(3)把生成的jar包复制到ubuntu虚拟机上,我这里复制到新建的maven目录下
(4)Dockerfile文件配置
在ubuntu上和复制的jar包同目录下,新建一个Dockerfile文件,文件内容如下:
FROM carsharing/alpine-oraclejdk8-bash
VOLUME /tmp
ADD docker-study.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
以上代码解释:
FROM 指定此docker需要依赖的docker image,由于这是一个spring boot项目,故要运行一个spring boot项目,必须得要有jdk,而上面写的carsharing/alpine-oraclejdk8-bash这个镜像中包含了一套拥有jdk的环境,可以此环境中运行java程序
VOLUME 用来指定docker运行的临时目录。它将在主机 /var/lib/docker 目录下创建了一个临时文件,并链接到容器的/tmp。/tmp目录用来持久化到 Docker 数据文件夹,因为 Spring Boot 使用的内嵌 Tomcat 容器默认使用/tmp作为工作目录
ADD 是将pom.xml中的targetPath下的docker-study.jar复制到容器中,并命名为app.jar
ENTRYPOINT 指定了容器启动时要执行的命令。那么这里就是执行java -jar /app.jar的意思,意思就是去运行这个springBoot项目了
注:如果carsharing/alpine-oraclejdk8-bash报错,执行以下指令:
docker pull carsharing/alpine-oraclejdk8-bash
3.5 建立course.jar的镜像并运行
(1)建立course的imgaes,执行指令:
sudo docker build -t course:latest .
(2)查看建立的course镜像,执行指令:
sudo docker images
(3)启动course镜像,执行指令:
sudo docker run -d -p 8080:8080 course
(4)查看运行的镜像,执行指令:
sudo docker ps
可以看到mysql和course正在运行,到此docker镜像上的spring项目就部署好了,我们可以来验证一下
3.6 ubuntu中docker镜像上的spring项目运行情况
(1)查看所有校友信息
(2)插入校友信息
输入要插入的校友信息:
点击“确认注册”返回校友信息列表,发现信息插入成功:
(3)检索校友信息
跳转到搜索页面,输入校友ID:
返回搜索结果:
(4)删除校友信息
点击校友信息最后一栏的“删除”按钮,提示是否删除:
点击“确定”,删除成功后返回校友信息列表,发现删除信息成功:
四、构建course微服务,并进行部署
在第三步的基础上,配置主机ip端口和虚拟机的ip端口,使得主机可以访问部署在ubuntu虚拟机上的docker中的course服务
4.1 配置虚拟机的ip和本机ip在同一个局域网下,使得主机和虚拟机可以互相ping通
(1)使用ifconfig命令,查看ubuntu虚拟机的ip地址
可以看到虚拟机的IP地址为192.168.192.130
(2)win下cmd输入ipconfig查看vm8的ip是不是和linux的ip相同
VM8的ip如果和虚拟机的IP地址不在同一局域网则需要配置(上面是我已经配置好的)
(3)如果不同,则配置成相同局域网,具体操作如下:
打开“网络和Internet设置”,点击更改适配器选项
右键点击VMnet8,选择属性,选中TCP/IPv4,选择属性
修改为和虚拟机相同局域网ip,点击确定,重启VMnet8
主机再ping虚拟机成功
虚拟机也可以ping通主机
4.2 配置主机和虚拟机的端口,使得主机可以访问虚拟机上的docker中的web服务
(1)先关闭虚拟机的防火墙,执行sudo ufw disable命令
(2)配置端口转发
打开VMware虚拟机的虚拟网络编辑器,通过编辑->虚拟网络编辑器进入
选择VMnet8,点击NAT设置
添加端口转发,使得本机的8080端口可以访问虚拟机的8080端口
(3)配置完毕后,在本机可以访问虚拟机中docker里的微服务course
在主机上访问http://localhost:8080/course/getAll 显示出校友信息
验证其他插入、修改、搜索和删除等功能一切正常,至此在虚拟机上的docker中部署微服务并能使主机访问的任务已全部完成
五、总结
5.1 问题总结
(1)在使用sql文件导入docker的数据库中时,遇到了错误[ERR] 1273 - Unknown collation: ‘utf8mb4_0900_ai_ci’,这个错误的原因是生成转储文件的数据库版本为8.0,要导入sql文件的数据库版本为5.7,因为是高版本导入到低版本,引起1273错误。解决方法是打开sql文件,将文件中的所有utf8mb4_0900_ai_ci替换为utf8_general_ci,utf8mb4替换为utf8。保存后再次运行sql文件,运行成功。
(2)在编译Dockerfile文件时,一开始写的是From frolvlad/alpine-oraclejdk8:slim,但是遇到了错误pull access denied for frolvlad/alpine-oraclejdk8, repository does not exist or may require ',后来修改为FROM carsharing/alpine-oraclejdk8-bash,并执行指令sudo docker pull carsharing/alpine-oraclejdk8-bash得以解决。
(3)使用Docker部署Mysql时中文乱码问题,在数据库中使用select语句查看数据显示中文正确,但在前端显示和插入中文均为乱码。解决方法:启动mysql时在命令后加入–character-set-server=utf8 --collation-server=utf8_unicode_ci,从而解决中文乱码问题。
5.2 收获
通过本次实验,我理解了web Service和SOA架构,并学会了构建web服务和使用docker容器部署服务,在编写【校友录服务】的过程中进一步熟悉了使用IDEA创建maven项目和使用后台的mybatis框架进行数据库的操作,在学习的过程中不断发现问题并解决问题,这个过程是煎熬的但问题得到解决的一瞬也是快乐的,总之对于web服务调用的实验我学到了很多东西。
六、参考链接
[3] 将springBoot项目部署到docker入门实例
[4] 关于如何在docker中利用tomcat和MySQL容器部署java web应用的详细步骤