Stories

Detail Return Return

xzgz.top網站開發記錄 - Stories Detail

xzgz.top 網站記錄

主要技術棧

應用主要是由 koa 構建,其中 nextjs 只負責頁面構建完成服務端渲染。以下是主要應用的技術

後台

  • Koa:網站主題內容
  • koa-router:網站後端路由
  • koa-session:應用的 session 寫入
  • nodemailer:發送郵件
  • mysql2:數據庫操作
  • nodemon:開發模式下熱更新
  • typescript
  • ts-node:完成開發模式下運行 ts 文件
  • tsconfig-paths:修復開發模式下 tsconfig.json 里路徑映射在 ts-node 下不起作用。

前台

  • Nextjs:react 的服務端渲染
  • mobx:前端應用的全局狀態管理
  • sass:前端應用的樣式
  • github api:完成筆記儲存,及markdown文件轉html功能

部署

  • 利用github的webhook進行自動化部署。
  • webhook觸發後執行shell腳本完成自動拉代碼、docker構建、運行應用。

    #!bin/sh
    
    exitError() {
    echo "---starting $1---"
    $1
    if [ $? -eq 0 ]; then
      echo "---$1 successfully---"
    else
      echo "!!!!!!!!!!$1 error!!!!!!!!!!"
      exit 0
    fi
    }
    
    echo "==========starting rebuild=========="
    
    cd /root/xzgz
    
    exitError "git pull"
    
    exitError "docker build -t xzgz_i ."
    docker rm -f xzgz_i
    docker stop xzgz_c
    docker rm -f xzgz_c
    exitError "docker run -d -p 3000:3000 --name xzgz_c xzgz_i"
    
    echo "==========end rebuild=========="

遇到的一些問題

tsconfig.json 里路徑映射問題

開發模式下
vscode 讀取 tsconfig.json 裏的路徑映射沒有問題,引用不會報錯,但是 ts-node 啓動腳本時會出現路徑映射丟失的問題,解決方法是使用 tsconfig-paths:

# 使用以下命令開啓腳本
npx ts-node -r tsconfig-paths/register --project ./tsconfig.json app_server/index.ts

在 nodemon 的配置文件 nodemon.json 裏設置

{
  "watch": ["app_server/", "types/"],
  "ext": "js,json,ts",
  "execMap": {
    "ts": "ts-node -r tsconfig-paths/register --project ./tsconfig.json"
  }
}

生產模式下
直接使用 ts-node 啓動服務器不適合生產模式,使用 tsc 將 ts 文件編譯成 js 文件進行運行。

tsc --project tsconfig.prod.json

生成的文件執行時和上面有同樣的問題,因為 tsc 編譯時不會對路徑映射進行編譯,直接執行會報錯找不到模塊。
按照 tsconfig-paths/register 的解決方法,在運行腳本之前對 Module.\_resolveFilename 進行重寫,達到解析路徑映射的問題。具體代碼:

const Module = require("module");
const path = require("path");

// 具體的路徑映射根據實際情況改動
const replaceUrl = /^@server\//;

function findModel(request, _module) {
  const pathArr = request.replace(replaceUrl, "");

  let i = _module;
  while (i) {
    const filename = path.resolve(i.path, "../");
    if (/app_server$/.test(filename)) {
      break;
    } else {
      i = _module.parent;
    }
  }

  return path.join(i.path, "../", pathArr);
}

function preRunApp() {
  var originalResolveFilename = Module._resolveFilename;

  Module._resolveFilename = function (request, _parent) {
    if (replaceUrl.test(request)) {
      const findModule = findModel(request, _parent);
      return originalResolveFilename.call(this, findModule);
    }
    return originalResolveFilename.call(this, request, _parent);
  };
}

preRunApp();

然後在執行腳本時使用命令

node -r ./preRunApp.js dist/app_server/index.js

使用 nodemailer 發送 163 郵箱時超時

nodemailer 使用 163 郵箱發郵件時遇到超時的問題,主要是因為使用 163 郵箱發送郵件時必須開啓 secure:

transporter = nodemailer.createTransport({
  host: "smtp.163.com", // 第三方郵箱的主機地址
  port: 465, // 25/465, secure為true - 465;false - 25
  secure: true, // 163郵箱必須為true,outlook郵箱必須為false
  auth: {
    user: SENDER, // 發送方郵箱的賬號
    pass: SMTP_CODE, // 郵箱授權密碼
  },
});

生產環境請求時間數據和開發環境請求同樣的數據不一致(docker 容器和系統時區不一致)

應用運行在 docker 容器中,docker 容器的日期和系統的不一致。
在 Dockerfile 裏配置將系統使用時區拷貝進 docker 容器中,具體如下:

RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' > /etc/timezone

生產環境請求接口的頁面會報錯,提示水合失敗,開發環境沒有這個問題

這個問題也是由上面的問題影響的。docker 容器裏 react 的脱水執行生成時間的太平洋時間。而到了客户端注水的時候生成東 8 區時間,導致服務端和客户端渲染不一致,因此報錯。

nextjs 中使用 mobx 服務端和客户端數據無法保持一致

因為 mobx 在服務端初始化時生成的數據沒有被傳到客户端,所以需要客户端也會完成一次初始化。某些頁面需要提前在 store 裏調用方法完成服務端渲染。
那麼需要手動的完成 mobx 的注水過程(類似於服務端渲染的脱水注水)。使用如下方法解決:

// pages/_app.ts
function MyApp({
  Component,
  pageProps,
  store, // 重點
}: AppProps & { store: AppStore }) {
  // 注水
  const _defaultStore = initializeStore(store);
  return (
    <MyAppContext.Provider value={_defaultStore}>
      <WithLayout {...pageProps} store={_defaultStore} />
    </MyAppContext.Provider>
  );
}

MyApp.getInitialProps = async (appContext: AppContext) => {
  const { Component, ctx } = appContext;

  // 服務端完成初始化
  const store = initializeStore();
  ctx.store = store;

  let appProps = {};
  if (Component.getInitialProps) {
    appProps = await Component.getInitialProps(ctx);
  }

  return { pageProps: appProps, store };
};

// store/index.ts
class Store {
  this.user = null
  // ...
  // 補水
  hydrate(initState: Store) {
    Object.assign(this, initState);
  }
}


let store: Store;
function initializeStore(initDate?: Store): Store {
  enableStaticRendering(typeof window === "undefined");

  const _store = store ?? new Store();
  if (initDate) {
    _store.hydrate(initDate);
  }
  // 客户端渲染總是new一個新對象
  if (typeof window === "undefined") return _store;
  if (!store) store = _store;

  return _store;
}

export { initializeStore, store };

剩餘問題

現在session沒有寫入持久層,每次重新構建導致所有用户登錄失效。

歡迎大佬來我的網站留言,討論問題。

Add a new Comments

Some HTML is okay.