《花100塊做個摸魚小網站! 》第六篇—將小網站部署到雲伺服器上

sum墨發表於2024-09-09

⭐️基礎連結導航⭐️

伺服器 → ☁️ 阿里雲活動地址

看樣例 → 🐟 摸魚小網站地址

學程式碼 → 💻 原始碼庫地址

一、前言

到這一篇我們終於把環境搭好,也做好了幾個熱搜小元件,為了讓我們方便展示成果或者方便自己摸魚,我們需要將這個小網站部署上雲。整體流程並不複雜,但有很多個小細節。如果某個細節處理不當,可能會導致部署失敗,因此這是一個不斷嘗試和調整的過程。基本流程包括:修改配置、打包、上傳、執行和除錯,然後反覆進行,直到成功。

二、前端資源打包

1. 修改配置

找到apiService.js檔案,將baseURL改成自己的伺服器的IP地址,埠建議使用80,因為http協議預設埠是80,https協議預設埠是443,這個應該都知道吧,如下:

// apiService.js
import axios from "axios";

// 建立axios例項並配置基礎URL
const apiClient = axios.create({
 baseURL: "http://ip:80/api",
  headers: {
    "Content-Type": "application/json"
  },
});

export default {
  // 封裝Get介面
  get(fetchUrl) {
    return apiClient.get(fetchUrl);
  }
};

找到vue.config.js檔案,將下面的配置替換一下,主要用於最佳化打包出來的檔案,將一些不必要的檔案去掉,如下:

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  // 去除Vue打包後js目錄下生成的一些.map檔案,用於加速生產環境構建。
  productionSourceMap: false,
  // 去除Vue打包後.css和.js檔名稱中8位hash值,跟快取有關;一般為true就行。
  filenameHashing:false
})

2. 執行打包命令

開啟命令列,輸入打包命令,如下:

npm run build

打包結束後,會有一個dist資料夾,裡面包括html、js、css等檔案,如下:

3. 提供資源訪問介面

summo-sbmy-start這個module的resources目錄下建立一個static檔案,把dist裡面的內容複製進去(ps:不包括dist目錄本身),如下:

在application.properties中新增thymeleaf配置,如下:

spring.thymeleaf.prefix=classpath:/static/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML

最後在在summo-sbmy-web這個module下建立IndexController.java,如下:

package com.summo.sbmy.web.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class IndexController {

    @GetMapping("/")
    public String index(){
        return "index";
    }
}

這些做好之後,啟動應用,訪問http://localhost/,沒有問題的話,應該就可以訪問了。

如果你發現圖示載入不出來,那可能是有個打包配置沒有弄好,將<include>**/*.svg</include>這行配置放到summo-sbmy-start這個module的 <build>標籤下,如下:

<resources>
  <resource>
  <!-- 指定配置檔案所在的resource目錄 -->
  <directory>src/main/resources</directory>
    <includes>
      <include>application.properties</include>
      <include>logback-spring.xml</include>
      <include>**/*.html</include>
      <include>**/*.js</include>
      <include>**/*.css</include>
      <include>**/*.svg</include>
    </includes>
    <filtering>true</filtering>
  </resource>
  <resource>
  <!-- 指定配置檔案所在的resource目錄 -->
  <directory>src/main/resources</directory>
    <includes>
      <include>**/*.woff</include>
      <include>**/*.ttf</include>
      <include>**/*.xdb</include>
      <include>**/*.jks</include>
      <include>**/*.svg</include>
    </includes>
    <filtering>false</filtering>
  </resource>
</resources>

配置和介面都弄好之後,啟動應用重新訪問一下,這時應該就可以看到圖示了。

三、後端應用打包

在打包上傳之前我們先做一些小準備,開啟我們的idea工具,找到外掛管理,一般是File —> Settings —> Plugins,搜尋Alibaba Cloud Toolkit,下載安裝一下,如下:

下載安裝好之後,有一個Alibaba Cloud View,這裡可以直接連線到我們買的阿里雲ECS伺服器,如下:

這個外掛可以上傳檔案,也可以直接開啟終端,這樣就不用下載專門的軟體連線ECS了,很方便。

1. xxl-job應用部署

(1) 應用打包

找到Maven外掛,執行package命令,如下:

打包出來,如下:

(2) 應用上傳

使用剛才下載的阿里雲外掛,可以直接將打包出來的jar包上傳到指定的目錄,我設定的是/home/admin/xxl-job,如下:

上傳後,開啟終端,可以啟動一下,看看有沒有問題,如下:

(3) 執行指令碼

在我們使用SpringBoot框架開發完一個專案後,需要將該專案打成jar包,放到用於生產的伺服器上去執行。一般都是執行 java -jar xxx.jar &命令執行,但是這樣是有問題的。

比如啟動時需要加入引數,如-Dxxx=xxx,這個命令就會很長不易讀且容易忘。所以,最好是使用shell指令碼將配置與命令維護起來。

新建一個shell指令碼,名為start.sh,如下:

#!/bin/bash
#這裡可替換為你自己的執行程式,其他程式碼無需更改
APP_NAME=xxx.jar

#使用說明,用來提示輸入引數
usage() {
    echo "Usage: sh 指令碼名.sh [start|stop|restart|status]"
    exit 1
}

#檢查程式是否在執行
is_exist(){
  pid=`ps -ef|grep $APP_NAME|grep -v grep|awk '{print $2}' `
  #如果不存在返回1,存在返回0
  if [ -z "${pid}" ]; then
   return 1
  else
    return 0
  fi
}

#啟動方法
start(){
  is_exist
  if [ $? -eq "0" ]; then
    echo "${APP_NAME} is already running. pid=${pid} ."
  else
    nohup java -jar /home/admin/$APP_NAME > /dev/null 2>&1 &
    echo "${APP_NAME} start success"
  fi
}

#停止方法
stop(){
  is_exist
  if [ $? -eq "0" ]; then
    kill -9 $pid
  else
    echo "${APP_NAME} is not running"
  fi
}

#輸出執行狀態
status(){
  is_exist
  if [ $? -eq "0" ]; then
    echo "${APP_NAME} is running. Pid is ${pid}"
  else
    echo "${APP_NAME} is NOT running."
  fi
}

#重啟
restart(){
  stop
  start
}

#根據輸入引數,選擇執行對應方法,不輸入則執行使用說明
case "$1" in
  "start")
    start
    ;;
  "stop")
    stop
    ;;
  "status")
    status
    ;;
  "restart")
    restart
    ;;
  *)
    usage
    ;;
esac

使用方式如下:

  • 給start.sh檔案授權:chmod 744 start.sh
  • 啟動服務,在當前目錄下執行:./start.sh start
  • 關閉服務,在當前目錄下執行:./start.sh stop
  • 重啟服務,在當前目錄下執行:./start.sh restart
  • 檢視服務狀態,在當前目錄下執行:./start.sh status

2. summo-sbmy應用部署

打包、上傳、啟動和上面的xxl-job應用一樣的步驟,我就不贅述了。

這裡說一點別的:在打包時如何指定配置檔案?

就是開發的時候我們一般會有兩個配置檔案(或者更多):日常的application-dev.properties;線上的application-publish.properties。這兩個檔案配置是一樣的,但是使用的資源不同,開發和線上的MySQL、Redis等資源是兩套,就像這樣,如下:

這裡可能有同學有疑惑,既然有了日常和線上環境的配置檔案,為啥還要有application.properties這個檔案?
這裡的application.properties我們可以當做是一個配置檔案容器,它可以將其他配置檔案的內容加到這裡來。還有一個原因就是因為SpringBoot專案啟動的時候只認識application.properties檔案,不認識其他的。

(1) 環境配置區分

首先我們在這個module下新增環境配置,如下:

 <!-- 添個環境的變數,變數名為environment -->
<profiles>
  <profile>
    <id>dev</id>
    <activation>
      <activeByDefault>true</activeByDefault>
    </activation>
    <properties>
      <environment>dev</environment>
    </properties>
  </profile>
  <profile>
    <id>publish</id>
    <properties>
       <environment>publish</environment>
    </properties>
  </profile>
</profiles>

<build>
  <finalName>summo-sbmy</finalName>
  <resources>
    <resource>
      <!-- 指定配置檔案所在的resource目錄 -->
      <directory>src/main/resources</directory>
      <includes>
        <include>application.properties</include>
        <include>application-${environment}.properties</include>
      </includes>
      <filtering>true</filtering>
    </resource>
    ... 其他配置
  </resources>
</build>

重新整理一下Maven外掛,這兩個配置就會被掃描到了,如下:

(2) 使用spring.profiles.active指定環境

在application.properties檔案中加入spring.profiles.active=@environment@配置,將那些不變的配置也可以放進來,如下:

#透過配置的方式啟用環境
spring.profiles.active=@environment@

# 專案名稱
spring.application.name=summo-sbmy
# 伺服器埠
server.port=80

# 資源掃描配置
spring.thymeleaf.prefix=classpath:/static/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML

# Mybatis自動對映
mybatis.configuration.auto-mapping-behavior=full
# Mybatis自動對映下劃線到駝峰格式
mybatis.configuration.map-underscore-to-camel-case=true
# MyBatis-Plus Mapper的XML對映檔案路徑
mybatis-plus.mapper-locations=classpath*:/mybatis/mapper/*.xml

(3) 打包命令

  • 日常環境打包指令
mvn clean package -Dmaven.test.skip=true -P=daily
  • 正式環境打包指令
mvn clean package -Dmaven.test.skip=true -P=publish

四、小結一下

上面說的在打包時指定配置檔案,如果大家嫌麻煩的話,也可以只用一份application.properties檔案,省的麻煩。我是開發環境和線上環境的資料庫是兩套,所以使用不同的配置檔案。

包打好之後上傳到雲伺服器這一步不難,啟動方式建議使用我推薦的start.sh指令碼,這個應該也不難。阿里雲ECS的安全組規則好像預設對80、443埠是全量放開的,所以只要你啟動沒有什麼問題的話,在瀏覽器輸入你的機器公網IP就可以訪問達到你的小網站了。

因為部署的細節比較多,可能大家會出現這樣那樣的問題,如果有問題的話,歡迎評論區留言。

番外:百度貼吧熱榜爬蟲

1. 爬蟲方案評估

百度貼吧熱榜是這樣的, 介面是:https://tieba.baidu.com/hottopic/browse/topicList?res_type=1

這又是一個網頁版熱搜榜,網頁版的資料解析需要使用者到jsoup,這個工具的使用在前面已經說過了,我直接放程式碼了。

2. 網頁解析程式碼

TiebaHotSearchJob.java

package com.summo.sbmy.job.tieba;

import java.io.IOException;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.annotation.PostConstruct;

import com.google.common.collect.Lists;
import com.summo.sbmy.common.model.dto.HotSearchDetailDTO;
import com.summo.sbmy.dao.entity.SbmyHotSearchDO;
import com.summo.sbmy.service.SbmyHotSearchService;
import com.summo.sbmy.service.convert.HotSearchConvert;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import static com.summo.sbmy.common.cache.SbmyHotSearchCache.CACHE_MAP;
import static com.summo.sbmy.common.enums.HotSearchEnum.TIEBA;

/**
 * @author summo
 * @version DouyinHotSearchJob.java, 1.0.0
 * @description 貼吧熱搜Java爬蟲程式碼
 * @date 2024年08月09
 */
@Component
@Slf4j
public class TiebaHotSearchJob {

    @Autowired
    private SbmyHotSearchService sbmyHotSearchService;

    @PostConstruct
    public void init() {
        try {
            // 呼叫 hotSearch 方法
            hotSearch(null);
        } catch (IOException e) {
            log.error("啟動時呼叫熱搜爬蟲任務異常", e);
        }
    }

    @XxlJob("tiebaHotSearchJob")
    public ReturnT<String> hotSearch(String param) throws IOException {
        log.info("貼吧熱搜爬蟲任務開始");
        try {
            String url = "https://tieba.baidu.com/hottopic/browse/topicList?res_type=1";
            List<SbmyHotSearchDO> sbmyHotSearchDOList = Lists.newArrayList();
            Document doc = Jsoup.connect(url).get();
            //標題
            Elements titles = doc.select(".topic-top-item-desc");
            //熱搜連結
            Elements urls = doc.select(".topic-text");
            //熱搜指數
            Elements levels = doc.select(".topic-num");
            for (int i = 0; i < levels.size(); i++) {
                SbmyHotSearchDO sbmyHotSearchDO = SbmyHotSearchDO.builder().hotSearchResource(TIEBA.getCode()).build();
                //設定文章標題
                sbmyHotSearchDO.setHotSearchTitle(titles.get(i).text().trim());
                //設定文章連線
                sbmyHotSearchDO.setHotSearchUrl(urls.get(i).attr("href"));
                //設定知乎三方ID
                sbmyHotSearchDO.setHotSearchId(getValueFromUrl(sbmyHotSearchDO.getHotSearchUrl(), "topic_id"));
                //設定熱搜熱度
                sbmyHotSearchDO.setHotSearchHeat(levels.get(i).text().trim().replace("W實時討論", "") + "萬");
                //按順序排名
                sbmyHotSearchDO.setHotSearchOrder(i + 1);
                sbmyHotSearchDOList.add(sbmyHotSearchDO);
            }
            if (CollectionUtils.isEmpty(sbmyHotSearchDOList)) {
                return ReturnT.SUCCESS;
            }
            //資料加到快取中
            CACHE_MAP.put(TIEBA.getCode(), HotSearchDetailDTO.builder()
                //熱搜資料
                .hotSearchDTOList(
                    sbmyHotSearchDOList.stream().map(HotSearchConvert::toDTOWhenQuery).collect(Collectors.toList()))
                //更新時間
                .updateTime(Calendar.getInstance().getTime()).build());
            //資料持久化
            sbmyHotSearchService.saveCache2DB(sbmyHotSearchDOList);
            log.info("貼吧熱搜爬蟲任務結束");
        } catch (IOException e) {
            log.error("獲取貼吧資料異常", e);
        }
        return ReturnT.SUCCESS;
    }

    /**
     * 從連結中獲取引數
     *
     * @param url   連結
     * @param param 想要提取出值的引數
     * @return
     * @throws Exception
     */
    public static String getValueFromUrl(String url, String param) {
        if (StringUtils.isAnyBlank(url, param)) {
            throw new RuntimeException("從連結中獲取引數異常,url或param為空");
        }
        try {
            URI uri = new URI(url);
            String query = uri.getQuery();
            Map<String, String> queryPairs = new HashMap<>();
            String[] pairs = query.split("&");
            for (String pair : pairs) {
                int idx = pair.indexOf("=");
                String key = URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8.name());
                String value = URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8.name());
                queryPairs.put(key, value);
            }
            return queryPairs.get(param);
        } catch (Exception e) {
            log.error("提取引數發生異常", e);
            throw new RuntimeException("從連結中獲取引數異常");
        }
    }
}

相關文章