全栈项目搭建+部署流程

前言

这是一篇如何使用 SpringBoot3 + MySQL 8.0 + Vue3 从搭建一个Demo到阿里云服务器部署的保姆级教程。


项目环境(版本)

  • 后端:SpringBoot 3.0.2(JDK:jdk-17.0.12_linux-x64)
  • 前端:Vue3 + ElementUI
  • 数据库:MySQL 8.0
  • 阿里云服务器:操作系统:Ubuntu 20.04 ×64

写在前面

首先在本地创建一个文件夹,用于项目构建及演示。

在其中创建子文件夹,分别用于构建后端应用和前端应用。

后端

写在前面:为了教程的流畅性,后端的部分会按照 本地项目搭建 -> 数据库设计(云端安装及部署)-> 项目代码编写 -> 云端部署及测试 的流程展开介绍。

本地项目搭建

  1. 打开IntelliJ IDEA,创建一个名为hello的项目。

  2. 依赖部分,选择 Spring Web 和 Lombok,选择后点击Create创建项目。

    说明:如果此时依赖未添加或有其他需要添加的依赖,可以在项目创建后通过pom.xml文件进行补充添加。

  3. 创建项目后基本的目录结构如下:

  4. 可以手动删除/resources目录下的静态页面文件(因为前端我们用Vue去写)。另外需要在HelloApplication启动类的同级目录下分别手动新建/controller、/dao、/entity、/mapper、/service目录。

    说明:

    • /controller目录用于编写控制器(用于接收前端发送的请求,对请求进行处理,并响应数据。)。
    • /dao目录用于编写数据访问操作相关的方法。
    • /entity目录用于编写实体类。
    • /mapper目录用于编写与数据库交互的具体操作方法(含具体的SQL语句)。
    • /service目录用于编写具体的交互操作业务方法。

    此时点击启动类,可以正常启动项目。

  5. 进入pom.xml文件,手动添加以下依赖:

    • mybatis
    • mysql
    • druid
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
    </dependency>
    <!-- mybatis依赖 -->
    <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.5</version>
    </dependency>
    <!-- mysql依赖 -->
    <dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
    </dependency>
    <!-- druid依赖 -->
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.20</version>
    </dependency>
    </dependencies>

    至此,项目所需要的依赖都已添加完毕,如上所示。

  6. 此外还需要在pom.xml文件中的<plugins>标签下添加repackage重打包指令。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
    <!-- 替换成你的启动类,例如 com.example.hello.HelloApplication -->
    <mainClass>com.example.hello.HelloApplication</mainClass>
    </configuration>
    <executions>
    <execution>
    <goals>
    <goal>repackage</goal>
    </goals>
    </execution>
    </executions>
    </plugin>

    重点:必须带上 <executions><goal>repackage</goal></executions>,少了这一行一定打包失败!!!!

此时直接启动项目会失败,是因为我们还没有配置数据库连接的相关信息。

数据库设计(云端安装及部署)

  1. 首先在阿里云服务器上安装部署MySQL,这里我使用Xshell(其他软件也可以)进行连接。

    执行如下命令进行数据库安装:

    1
    2
    sudo apt update
    sudo apt install mysql-server -y
  2. 接着键入如下命令查看运行状态:

    1
    sudo systemctl status mysql

    看到 active (running)字样就是启动成功。

  3. 接下来是安全初始化操作(必做!!!!)

    在终端键入如下命令进行安全初始化操作:

    1
    sudo mysql_secure_installation

    会弹出交互式选项,选择参考如下:

    1. VALIDATE PASSWORD COMPONENT:一般选 n(密码强度校验,自己用可以关掉)
    2. 设置 root 密码
    3. Remove anonymous users? → y
    4. Disallow root login remotely? → 如果后面需要远程连接选 n;只本地用选 y(这里选 n)
    5. Remove test database and access to it? → y
    6. Reload privilege tables now? → y

    完成选项后,接着输入mysql -uroot -p可以进入mysql(需要输入登录密码)。

    键入quit可以退出。

  4. 进入云服务器的/etc/mysql/mysql.conf.d目录,编辑其中的mysqld.cnf文件,其中在[mysqld]配置项下有一条port,把他修改为33366

  5. 在阿里云服务器 管理控制台 -> 实例 -> 网络与安全组 点击 添加入方向规则,开放一个33366端口,用于项目测试使用。

  6. 使用Navicat(也可以使用其他软件)创建一个连接,接入阿里云服务器。

    填写完配置信息后可以点击左下角的“测试连接”按钮,如果操作正确会弹出“连接成功”的字样。

  7. 光标移动到【阿里云服务器】,右键点击“新建数据库”。

  8. 新建一个名为“hello_project”的数据库。

  9. 进入数据库后,光标左键点击“表”,右键点击“新建表”。

  10. 创建一个名为“button”的表,在其中创建一个名为“number”的字段。

    这里“number”字段选择的是“varchar”类型。

    至此,阿里云端的数据库部分就已搭建完毕。

项目代码编写

  1. 进入/resources目录下的application.properties文件,进行数据库连接及其他项目相关的配置。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    server.port=44444
    server.address=0.0.0.0

    spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:33366/hello_project?useSSL=false&serverTimezone=Asia/Shanghai
    # ========= 需填写正确的数据库用户名及密码 =========
    spring.datasource.username=数据库用户名
    spring.datasource.password=数据库连接密码

    # ========= druid-spring-boot-starter druid. spring.datasource =========
    druid.initial-size=3
    druid.max-active=5
    druid.min-idle=2
    druid.max-wait=3000
    druid.remove-abandoned=true
    druid.remove-abandoned-timeout=5
    druid.log-abandoned=true

    mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

    现在就可以正常启动SpringBoot项目了。下面继续编写后端代码。

  2. 进入项目目录,HelloApplication启动类自动生成,不用管。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package com.example.hello;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;

    @SpringBootApplication
    public class HelloApplication {

    public static void main(String[] args) {
    SpringApplication.run(HelloApplication.class, args);
    }

    }
  3. 首先在/entity目录下新建一个Button实体类。

    Button实体类的具体内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    package com.example.hello.entity;

    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;

    import java.util.Objects;

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Button {
    private String number; // 用的是String类型

    public String getNumber() {
    return number;
    }

    public void setNumber(String number) {
    this.number = number;
    }

    @Override
    public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Button button = (Button) o;
    return Objects.equals(number, button.number);
    }

    @Override
    public int hashCode() {
    return Objects.hash(number);
    }

    @Override
    public String toString() {
    return "Button{" +
    "number='" + number + '\'' +
    '}';
    }
    }
  4. 接着在/mapper目录下新建一个ButtonMapper接口:

    ButtonMapper接口的具体内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package com.example.hello.mapper;

    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Select;
    import org.apache.ibatis.annotations.Update;

    @Mapper
    public interface ButtonMapper {
    // 查询当前数字
    @Select("select number from button")
    String query();

    // 数据库原子自增,数据库内部+1,不会丢失计数
    @Update("update button set number = number + 1")
    void addOne();

    }

    注意:这里仅编写演示**【查询】和【增加】**方法。其他的功能可以照此进行模仿。

  5. 在/dao目录下新建一个ButtonDao接口,并在其/impl子目录(需手动创建)下新建一个ButtonDaoImpl实现类,并实现上一层的ButtonDao接口:

    ButtonDao接口代码如下:

    1
    2
    3
    4
    5
    6
    7
    package com.example.hello.dao;

    public interface ButtonDao {
    public String query();

    public void addOne();
    }

    ButtonDaoImpl实现类如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    package com.example.hello.dao.impl;

    import com.example.hello.dao.ButtonDao;
    import com.example.hello.mapper.ButtonMapper;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Repository;

    @Repository
    public class ButtonDaoImpl implements ButtonDao {

    @Autowired
    private ButtonMapper buttonMapper;

    @Override
    public String query() {
    String number = buttonMapper.query();
    return number;
    }

    @Override
    public void addOne() {
    buttonMapper.addOne();
    }
    }
  6. 在/service目录下新建一个ButtonService接口,并在其/impl子目录(需手动创建)下新建一个ButtonServiceImpl实现类,并实现上一层的ButtonService接口:

    ButtonService接口代码如下:

    1
    2
    3
    4
    5
    6
    7
    package com.example.hello.service;

    public interface ButtonService {
    String query();

    void addOne();
    }

    ButtonServiceImpl实现类如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package com.example.hello.service.impl;

    import com.example.hello.mapper.ButtonMapper;
    import com.example.hello.service.ButtonService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;

    @Service
    public class ButtonServiceImpl implements ButtonService {

    @Autowired
    private ButtonMapper buttonMapper;

    @Override
    public String query() {
    return buttonMapper.query();
    }

    @Override
    public void addOne() {
    buttonMapper.addOne();
    }
    }
  7. 在/controller目录下新建一个ButtonController控制类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    package com.example.hello.controller;

    import com.example.hello.service.ButtonService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.CrossOrigin;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RestController;

    import java.util.HashMap;
    import java.util.Map;

    @CrossOrigin(origins = "*")
    @RestController
    public class ButtonController {

    @Autowired
    private ButtonService buttonService;

    @GetMapping("/query")
    public String query() {
    return buttonService.query();
    }

    // 新增自增接口,不需要传参数
    @PostMapping("/add")
    public Map<String, Object> add(){
    buttonService.addOne();
    Map<String,Object> result = new HashMap<>();
    result.put("msg","success");
    return result;
    }
    }

    至此所有的后端代码已经编写完毕,下面开始打包部署。

云端部署及测试

  1. 首先要在阿里云服务器上安装jdk。(需要提前下载Linux版本的jdk,我这里下载的是jdk-17.0.12_linux-x64版本)

    将jdk安装包放置在/usr/server目录下,并在终端键入如下命令进行解压到当前目录:

    1
    tar -zvxf jdk-17.0.12_linux-x64_bin.tar.gz

    解压后会自动在当前目录下生成jdk-17.0.12的子目录。

  2. 接着需要编辑/etc目录下的/profile文件:

    在文件末尾增加JAVA_HOME的配置信息:

    1
    2
    export JAVA_HOME=/usr/server/jdk-17.0.12
    export PATH=${JAVA_HOME}/bin:$PATH

    如图所示:

  3. 配置完之后在可以尝试在终端键入命令:java -version。如果有如下提示,则说明安装成功。


    至此,java环境已经配置完毕。

  4. 下面开始打包本地的SpringBoot项目,并进行云服务器部署。

    进入IDEA,点击Maven,点击package进行打包。

  5. 打包完成之后可以在对应的目录下找打包好的jar包

  6. 在阿里云服务器上创建一个/usr/app目录,用于存放jar包以及后续的前端代码。

    将打包好的jar包放置在此目录下。

  7. 因为已经在云服务器上配置好了java环境,所以我们可以直接键入如下命令执行已经编写好的java程序。

    在终端键入如下命令:

    1
    nohup java -jar hello-0.0.1-SNAPSHOT.jar > app.log 2>&1 &

    说明:

    • 通过java -jar可以直接执行已经上传的java程序。
    • 执行的时候,对应的日志信息会输出到app.log文件中去。

    至此,java程序就已经在后台启动了。

  8. 可以在终端键入cat logName.log命令查看日志信息。

  9. 可以在浏览器或者使用postman等调试工具进行测试:

前端

项目代码编写

  1. 进入前端目录,创建项目。

  2. 进入终端,创建一个名为hello的项目(需要提前安装Node.js,这里不做演示)

  3. 此时会进入一个选项(按**上下箭头(↑↓)**可以进行选择。),这里选择第三个进行手动创建。

    按【回车】进行下一步。

  4. 这里默认已经勾选两个选项。只选择添加第一个Bable,Linter / Formatter点击空格取消选择。

    回车进行下一步。

  5. 这里会让我们选择依赖的文件记录到哪里去,这里选择package.json文件。

    回车进行下一步。

  6. 接下来会询问是否存一个快照,这里不需要,键入N。

    回车进行下一步。

  7. 接下来就会联网根据之前的选项进行项目创建。

    等待片刻,项目就会自动创建完毕。

  8. 使用VSCode打开这个项目。进行前端项目编写。其初始目录结构如下:

  9. 在终端输入npm run serve命令可以启动一个本地的服务器,将项目跑起来。

    直接使用浏览器访问就可以。

  10. 打开项目,其中在/components目录下有一个HelloWorld.vue文件,这里不需要,直接删除。

  11. 在/components目录下新建一个Button.vue的文件。

    在此编写一个简单的“按钮”代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    <template>
    <div class="container">
    <el-button class="big-button" size="large" @click="addCount">
    <span class="gradient-text">素念有成</span>
    </el-button>
    <h1>当前已祈愿:{{ count }}次</h1>
    </div>
    </template>

    <script>
    import axios from 'axios';

    export default {
    name: "Button",
    props: ["number"],
    data() {
    return {
    count: 0,
    };
    },
    created() {
    this.getCount();
    },
    methods: {
    async getCount() {
    try {
    const res = await axios.get('http://服务器ip:44444/query',{ // 需要替换为真实的ip地址
    responseType:'text'
    });
    this.count = res.data;
    } catch (err) {
    console.error('请求失败:', err);
    }
    },
    async addCount() {
    try {
    await axios.post('http://服务器ip:44444/add'); // 需要替换为真实的ip地址
    await this.getCount();
    } catch (err) {
    console.error('更新失败:', err);
    }
    }
    }
    }
    </script>

    <style>
    html {
    width: 100%;
    height: 100%;
    background: linear-gradient(to top right,#757F9A, #D7DDE8);
    }

    .el-button {
    width: 15%;
    border-radius: 30px !important;
    background: linear-gradient(to top right,#757F9A, #D7DDE8);
    }

    .gradient-text {
    background: linear-gradient(to top right,#fffef8, #baccd9);
    font-size: 150%;
    background-clip: text;
    color: transparent;
    }

    .big-button:hover {
    filter: brightness(1.1);
    transition: filter 0.3s ease;
    }
    </style>

    注:这里的服务器ip需要替换为真实的ip地址。

  12. 接着编辑App.vue代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    <template>
    <div id="app">
    <Button number="花猪"></Button>
    </div>
    </template>

    <script>
    import Button from './components/Button.vue'
    import axios from 'axios';

    export default {
    name: 'App',
    created() {
    axios.get("http://服务器ip:44444/query").then(function (response) { // 需要替换为真实的ip地址
    })
    },
    components: {
    Button
    }
    }
    </script>

    <style>
    #app {
    font-family: Avenir, Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-align: center;
    color: #2c3e50;
    margin-top: 60px;
    }
    </style>

    注:这里的服务器ip需要替换为真实的ip地址。

  13. 在终端里键入以下命令安装以下依赖:

    1
    npm install axios element-plus font-awesome
  14. 在main.js文件中引入相应的依赖:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import { createApp } from 'vue'
    import App from './App.vue'
    import ElementPlus from 'element-plus'
    import 'element-plus/dist/index.css'
    import 'font-awesome/css/font-awesome.min.css'
    import axios from 'axios'

    const app = createApp(App)

    app.use(ElementPlus)
    app.mount('#app')
  15. 接下来打包编写好的前端项目:在终端键入以下命令进行打包。

    1
    npm run build
  16. 打包完成后,所有的内容都会整合到/dist目录中去。

云端部署及测试

  1. 下一步就需要把本地的/dist目录上传到云服务器的/usr/app目录中去。

  2. 接下来需要配置nginx,让其可以读取上传的前端项目文件。

    进入/etc/nginx/conf.d目录,创建一个vue.conf文件。

    内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    server {
    listen 80;
    server_name localhost;

    location / {
    root /usr/app/dist;
    index index.html;
    }
    }

    其内容是告诉nginx,编写的html代码在哪里。

  3. 接着在终端键入如下命令,使配置生效:

    1
    nginx -s reload
  4. 最后访问服务器ip,可以正常访问页面以及交互。

    至此全流程已经打通!!!!


后记

花了好多天的时间,前前后后全流程进行了两遍。

(PS:AI还是太好用了,本篇文章的封面就是用AI生成的(Butterfly主题可见)。)

参考资料: