實現一個 SEO 友好的響應式多語言官網 (Vite-SSG + Vuetify3) 我的踩坑之旅

来源:https://www.cnblogs.com/voidzxl/p/18066334/vite-ssg-vuetify3-i18n-website
-Advertisement-
Play Games

在 2023 年的年底,我用 Vite-SSG + Vue3 + Vuetify3 把之前使用 SPA 編寫的官網進行了重構,支持多語言,響應式並且對 SEO 和社交媒體分享十分友好 ...


在 2023 年的年底,我終於有時間下定決心把我的 UtilMeta 項目官網 進行翻新,主要的原因是之前的官網是用 Vue2 實現的一個 SPA 應用,對搜索引擎 SEO 很不友好,這對於介紹項目的官網來說是一個硬傷

所以在調研一圈後,我準備用 Vite-SSG + Vue3 + Vuetify3 把官網重新來過,前後花了兩周左右的時間,本文記錄著開發過程中的思考和總結,要點主要有

  • 為什麼 SPA 應用不應該用於搭建項目官網?
  • SSG 項目的結構是怎樣的,如何配置頁面的路由?
  • 如何搭建多語言的靜態站,編寫支持多語言的頁面組件,以及使用 lang / hreflang 為頁面指定不同的語言版本?
  • 如何用 unhead 庫為每個頁面配置不同的 html 頭部元信息,優化搜索引擎收錄?
  • 如何使用 @media CSS 媒體規則處理響應式頁面在不同設備的首屏載入問題?
  • 如何優雅處理 404 問題,避免 soft 404 對搜索收錄的影響?

為什麼不應該用 SPA 開發官網

這裡我們先收窄一下定義,把【官網】定義為一個介紹性質為主的網站,比如產品介紹,定價方案,關於我們等等,而不是一個直接交互的動態產品(比如各種各樣的 2C 內容平臺,社交平臺),對於動態產品而言使用 SPA 其實無妨,如果想優化搜索收錄可以定期把一些固定的 profile 頁面或者文章頁面提交給搜索引擎

所以就是一個原因,SEO。這是老生常談的問題,SPA 只會生成單個 index.html,爬取你網站上的任何 URL 都只會返回同樣的內容,其中還往往不包括即將渲染出的文本,關鍵詞和鏈接等信息,這就導致搜索引擎呈現的結果一塌糊塗,不僅如此,在 Twiiter, Discord 等社交媒體直接抓取鏈接元信息(標題,描述,插圖)並渲染的平臺上,你的每個網頁都只會呈現一樣的信息

對於一個需要在互聯網上獲客的項目,我們都不應該忽視來自搜索引擎的流量,尤其是國際化的項目。即使我們來到了 AIGC 紀元,以 ChatGPT 為代表的大模型訓練語料獲取仍然以爬取網頁數據為主,這時你的項目各頁面如果能夠提供清晰的,包含足夠準確的關鍵詞和信息的,符合 Web 規範的 HTML 結果,你的項目或文檔也有可能會被 AI 收錄並整合到它們的輸出結果中,所以我認為對網頁結構和渲染的優化其實就是可以統稱為 Agent Optimization,即【對來自搜索引擎或大模型的】網路爬取優化,依然十分重要

合適的姿勢是?

SSR(服務端渲染) / SSG(服務端生成) 都是介紹性官網開發的合適姿勢,對於不需要太多渲染邏輯的靜態頁面來說,SSG 就足矣,你只需要把生成出來的 HTML 扔到任何頁面托管網站上都可以直接提供訪問,對 CDN 也足夠友好,如果自己喜歡折騰也可以搞自己的伺服器來部署,我自己就是使用 nginx 來部署 SSG 生成的靜態頁面作為 CDN 的回源

SSG 項目結構

與 SPA 應用相比,SSG 項目最主要的區別是:路由與對應的頁面模板是固定的,並且在構建階段會直接生成每個頁面的 html 文件,而不是像 SPA 一樣只生成一個 index.html

反映到 Vue 項目的文件結構上,SPA 應用往往需要一個 router 文件來定義 vue-router 的路由和對應的組件,而 SSG 應用則可以把每個頁面的路由和對應的 Vue 頁面組件直接定義在一個文件夾中(往往命名為 pages

所以 Vite-SSG 項目的 main.js 一般長這個樣子:

import App from './App.vue'
import { ViteSSG } from 'vite-ssg'
import routes from '~pages';
import vuetify from './plugins/vuetify';

export const createApp = ViteSSG(
  App,
  // vue-router options
  {routes, scrollBehavior: () => ({ top: 0 }) },
  // function to have custom setups
  ({ app, router, routes, isClient, initialState }) => {
    // install plugins etc.
    app.use(vuetify)
  },
)

我們用 vite-ssg 定義的 ViteSSG 來代替 Vue 預設的 createApp,在導入路由時,我們使用了

import routes from '~pages';

這是來自 vite-plugin-pages 插件的支持,你可以直接把一個文件夾下的 Vue 組件轉化為對應的頁面路由,只需要在 vite.config.js 中配置

// Plugins
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
import Pages from 'vite-plugin-pages'

export default defineConfig(
  ({command, mode}) => {
    return {
      plugins: [
        Pages({
          extensions: ['vue', 'md'],
        }),
        ...
      ],
      ...
    }
  }
)

處理多語言頁面路由

如果你的官網需要給來自世界各地的用戶介紹你們的項目,多語言就基本上是一個必選項了,我們以支持中文與英文為例,其他的語言支持方式可以依此類推

之前我對於多語言的處理是根據 IP 屬地返回語言然後前端直接設置語言,並沒有反應到 URL 上,這其實是一種 Bad Practice,對於用戶訪問的時候看到的是什麼語言版本的頁面完全不可控(因為他們可能使用了代理),用戶在分享頁面時他的受眾也是同理,搜索引擎也無法完全抓取所有的語言版本(因為 Google 的爬蟲主要在美國),所以 Google 也在 文檔 中說明很不建議這樣的做法

對於 SSG 的頁面路由,我的多語言實現實踐是:為每個頁面實現一個通用的頁面組件,其中定義一個屬性 lang,組件中展示的所有文字都可以根據這個 lang 屬性選擇對應的語言版本,由於頁面的屬性在 SSG 構建時會直接傳入,所以會生成不同語言版本的 HTML 頁面文件,一個最簡化的頁面組件示例如下

<script setup>
const props = defineProps({
  lang: {
    type: String,
    default() {
      return 'en'
    }
  },
})

const messages = {
  zh: {
    title: '構建數字世界的基礎設施'
  },
  en: {
    title: 'Building the infrastructure of the digital world'
  },
}

const msg = messages[props.lang];
</script>

<template>
  <div>
    {{ msg.title }}
  </div>
</template>

接下來我們就可以搭建我們多語言頁面的文件夾結構了,你可以選擇把不同的語言都作為不同的子路由,比如

/pages
    /en
        index.vue
    /zh
        index.vue
    /ja
        index.vue
    /..

這樣訪問 /en 會進入英文頁面,訪問 /zh 會進入中文頁面

還有一種方式是選擇一種語言作為預設語言,如英語,然後將它的子路由置於與其他語言目錄平行的位置,比如

/pages
    /zh
        index.vue
    /ja
        index.vue
    index.vue     # en

utilmeta.com 採用的是第二種模式,因為我想讓官網的功能變數名稱是可以直接訪問和鏈接的,保持簡潔,所以我對它的路由是這樣規劃的

/pages
    /zh
        index.vue ------ 首頁(中文)
        about.vue ------ 關於我們(中文)
        solutions.vue -- 解決方案(中文)
        py.vue --------- UtilMeta Python 框架介紹(中文)
    index.vue    ------- 首頁(英語)
    about.vue ---------- 關於我們(英語)
    solutions.vue ------ 解決方案(英語)
    py.vue ------------- UtilMeta Python 框架介紹(英語)

按照 JavaScript 的慣例,index 就會被處理為與它的目錄一致的路由,其他的名稱會根據名稱分配路由

其中,每個語言的頁面組件都可以直接引入它對應的通用頁面組件,然後將 lang 屬性傳入通用頁面組件中,比如 /zh/about.vue 是中文的 “關於我們” 頁面組件

<script setup>
import About from "@/views/About.vue";
import AppWrapper from "@/components/AppWrapper.vue";
</script>

<template>
  <AppWrapper lang="zh" route="about">
     <About lang="zh"></About>
  </AppWrapper>
</template>

其中 @/views/About.vue 是 “關於我們” 頁面的通用組件,我們傳入了 lang="zh",而 AppWrapper 是我編寫的一個通用的頁面骨架組件,包含著每個頁面都需要的頂欄,底欄,邊欄等頁面架構

語言切換

對於支持多語言的官網,我們可以需要在其中添加一個讓用戶主動切換語言的按鈕,它的邏輯也非常簡單,只需要將用戶展示一個支持的語言列表,然後每個語言按鈕都能將用戶切換到對應的頁面路由,比如

<template>
	<v-menu open-on-click>
	  <template v-slot:activator="{ props }">
		<v-btn v-bind="props">
		  <v-icon>mdi-translate</v-icon>
		</v-btn>
	  </template>
	  <v-list color="primary">
		<v-list-item
          v-for="(l, i) in languages"
          :to="getLanguageRoute(l.value)"
          :active="lang === l.value"
          :key="i"
        >
          <v-list-item-title>{{ l.text }}</v-list-item-title>
        </v-list-item>
	  </v-list>
	</v-menu>
</template>

<script setup>
  const props = defineProps({
    lang: {
      type: String,
      default(){
        return 'en'
      }
    },
    route: {
      type: String,
      default(){
        return ''
      }
    }
  });

  const languages = [{
    value: 'en',
    text: 'English'
  }, {
    value: 'zh',
    text: '中文'
  }];
	
  function getLanguageRoute(l){
    if(l === 'en'){
      return '/' + props.route;
    }
    if(!props.route){
      return `/${l}`
    }
    return `/${l}/` + props.route
  }
</script>

還是以上面的 About 頁面為例,如果用戶目前處於 https://utilmeta.com/about 路由(英語),而點擊了 中文 語言,就需要被引導到 https://utilmeta.com/zh/about 頁面,從用戶視角看來,頁面的結構完全一致,只不過語言從英語切換到了中文

使用 unhead 為頁面註入元信息

對於靜態頁面而言,<head> 中的頭信息與頁面元信息非常重要,它決定著搜索引擎收錄的索引與關鍵詞,也決定著頁面鏈接在社交媒體分享時渲染的信息,一般來說 Vue 的頁面組件只是編寫 <body> 中的元素,但只需要使用一個名為 unhead 的庫,你就可以為不同的頁面編寫不同的頭信息了,比如以下是我在 UtilMeta 中文首頁的頁面組件中編寫的元信息

<script setup>
import { useHead } from '@unhead/vue'

const title = 'UtilMeta | 全周期後端 API 應用 DevOps 解決方案';
const description = '面向後端 API 應用的全生命周期解決方案,助力每個創造者,我們的產品有 UtilMeta Python 框架,一個面向後端 API 開發的漸進式元框架,API 管理平臺,以及 utype';

useHead({
  title: title,
  htmlAttrs: {
    lang: 'zh'
  },
  link: [
    {
      hreflang: 'en',
      rel: 'alternate',
      href: 'https://utilmeta.com'
    }
  ],
  meta: [
    {
      name: 'description',
      content: description,
    },
    {
      property: 'og:title',
      content: title
    },
    {
      property: 'og:image',
      content: 'https://utilmeta.com/img/zh.index.png'
    },
    {
      property: 'og:description',
      content: description
    }
  ],
})

import Index from '@/views/Index.vue'
import AppWrapper from "@/components/AppWrapper.vue";

</script>

<template>
  <AppWrapper lang="zh">
    <Index lang="zh"></Index>
  </AppWrapper>
</template>

其中重要的屬性有

  • title:頁面的標題,直接影響著用戶在瀏覽器中看到的頁面標題與搜索引擎收錄的網頁中的標題
  • htmlAttrs.lang:可以直接在 html 根元素中編輯語言屬性 lang 的值
  • hreflang:通過插入含有 hreflang 屬性的 <link> 元素,你可以為頁面指定不同的語言版本,這裡我們就指定了首頁的英文版本的鏈接,這樣的屬性能夠更好地為搜索引擎的多語言呈現提供便利
  • meta.description:元信息中的描述,
  • og:* 按照社交媒體渲染鏈接所通用的 Open Graph 協議 規定的屬性,可以決定著你在把鏈接分享到如 Twitter(X), Discord 等社交媒體或聊天軟體中時,它們的標題,描述和插圖

元信息的註入應該是頁面級的,也就是對於不同語言的頁面,你也應該註入該語言版本的元信息

實現靜態頁面的響應式

你當然希望你的官網在寬屏電腦,平板和手機中都能有著不錯的顯示效果(或者至少不要出現元素錯亂重疊),想要做到這些,就需要開發響應式的網頁

我開發 UtilMeta 官網使用的是 Vue 組件庫是 Vuetify,Vuetify 已經提供了一套 Display 系統和 breakpoints 機制,能夠提供一系列響應式的斷點,讓我們在開發時為不同的設備指定不同的顯示效果

比如

<v-row>
	<v-col :cols="display.xs.value ? 12 : 6">
	</v-col>
	<v-col :cols="display.xs.value ? 12 : 6">
	</v-col>
<v-row>

這樣你就可以通過行列調節內容在不同尺寸設備上的顯示了,示意如下

模板語法的問題

一切看起來都不錯吧?你發現本地調試時確實能夠做到響應式,但是當網站上線時卻發現了問題

那就是,網頁在電腦端載入時,也會預設保持移動端的樣式,直到 js 載入完畢後,才會根據屏幕尺寸調整到合適的樣式,這樣在載入或刷新時,用戶會看到網頁的元素在幾秒內發生了跳變,這是很奇怪的體驗,那麼為什麼會造成這樣的問題呢?

我打開了 vite-ssg 生成的 html 後發現,SSG 在生成時會直接把模板中的配置進行固定和渲染,對於類似下麵的響應式代碼

<v-col :cols="display.xs.value ? 12 : 6">
	<h1 :style='{fontSize: display.xs.value ? "32px" : "48px"}'></h1>
</v-col>

其實在構建成 HTML 文件時就會渲染成

<div class="v-col-12">
	<h1 :style="font-size: 32px"></h1>
</div>

渲染程式會直接把 display.xs.value (以及其他的響應式條件)作為 true 來處理,得到的 HTML 文件就會把某一個設備的樣式給固定,所以用戶在載入時就只能等到控制響應式的 js 代碼載入完畢才能夠根據設備尺寸重新渲染,就會造成短暫的元素跳變的問題

救星 - @media CSS 媒體規則

那麼如何正確處理靜態頁面的響應式樣式呢?我探索出的答案是使用 @media 媒體規則,它可以讓你根據屏幕的大小創建不同的樣式規則,這樣你的響應式樣式就 完全由 CSS 控制 了,當頁面渲染出來的時候(依賴的 css 載入完畢)就會完全按照 CSS 規則進行渲染,在不同設備刷新時也都會直接呈現適配對應設備尺寸的渲染結果,不會出現元素跳變的問題

比如我把 About 頁面的標題添加了 about-title 類,然後在對應的 CSS 中編寫

  .about-title{
    font-size: 60px;
    line-height: 72px;
    max-width: 800px;
    margin: 6rem auto 0;
  }

  @media (max-width: 600px){
    .about-title{
      font-size: 36px;
      line-height: 48px;
      margin: 3rem auto 0;
    }
}

這樣,About 頁面的標題在尺寸小於 600px 的設備中就可以按照 @media 塊中定義的樣式展現了

處理 v-row / v-col

Vuetify 提供的網格(v-row 控制行,v-col 控制列)系統可以很大程度提升響應式網頁開發的效率,但是我們往往需要讓行列的顯示在不同的設備上保持響應式,然而 @media 屬性尚不支持為不同的設備尺寸賦予不同的 HTML class,那麼如何處理網格系統在 SSG 應用中的響應式呢?

下麵是我的實踐,僅供參考:對於需要在移動端切換行數的 v-col 組件,我們可以直接把它在移動端對應的行數命名為一個類,比如 xs-12-col

<v-row>
    <v-col :cols="6" class="xs-12-col">
    </v-col>
    <v-col :cols="6" class="xs-12-col">
    </v-col>
</v-row>

然後我們使用 @media 規則,在移動端尺寸的設備中直接為這些類指定網格樣式參數,比如

@media (max-width: 600px) {
	.xs-12-col{
	  flex: 0 0 100%!important;
	  max-width: 100%!important;
	}
	.xs-10-col{
	  flex: 0 0 83.3%!important;
	  max-width: 83.3%!important;
	}
	.xs-2-col{
	  flex: 0 0 16.6%!important;
	  max-width: 16.6%!important;
	}
}

這樣,我們的網格系統也可以支持 SSG 中的響應式樣式,而不會出現載入跳變了

部署靜態網站

優雅處理 404

在 SSG 靜態頁面中,我們的網站支持的路由是預先定義和生成好的,其他的路徑訪問都應該直接返回 404,但為了給用戶更好的體驗,一般常見的做法是單獨製作一個 404 Notfound 頁面,在訪問路徑沒有頁面時展示給用戶,讓他能方便地轉迴首頁或其他頁面,比如 UtilMeta 官網的 404 頁面如下

使用 Vite-SSG 實現這樣的效果並不困難,你只需要在 pages 文件夾中增加兩個組件

  • 404.vue
  • [...all].vue

這兩個組件中的內容都是相同的,都放置著 404 頁面的組件代碼,[...all].vue 會作為所有沒有匹配到路由的頁面請求的返回頁面,而 404.vue 會輸出一個顯式的路由 404.html,方便在 nginx 中直接進行重定向

完成我們的 SSG 頁面開發後,我們可以調用下麵的命令將頁面構建出對應的 HTML 文件

vite-ssg build

對於我的 UtilMeta 官網而言,生成的文件如下

/dist
    /zh
        about.html
        py.html
        solutions.html
    404.html
    zh.html
    about.html
    index.html
    py.html
    solutions.html

接著,你就可以將這些靜態文件上傳到頁面托管服務或者自行搭建的靜態伺服器上即可提供訪問了,我搭建 UtilMeta 官網的靜態伺服器使用的 nginx 配置如下

server{
    listen 80;
    server_name utilmeta.com;
    rewrite ^/(.*)/$ /$1 permanent;
    
    location ~ /(css|js|img|font|assets)/{
        root /srv/utilmeta/dist;
        try_files $uri =404;
    }
    location /{
        root /srv/utilmeta/dist;
        index index.html;
        try_files $uri $uri.html $uri/index.html =404;
    }

    error_page 404 403 500 502 503 504 /404.html;

    location = /404.html {
        root /srv/utilmeta/dist;
    }
}

配置中監聽 80 而非 443 埠是因為我的官網作為靜態站,官網需要的靜態資源已經全部托管給 CDN 了(包括 SSL 證書),這裡的 nginx 配置的是 CDN 的回源伺服器,所以提供 HTTP 訪問就 ok 了

nginx 配置中 rewrite ^/(.*)/$ /$1 permanent 的作用是將目錄的訪問映射到對相應 HTML 文件的訪問,比如將 https://utilmeta.com/zh/ 映射到 https://utilmeta.com/zh,否則 Nginx 會出現 403 Forbidden 的錯誤

因為 vite-ssg 預設的生成策略會把位於目錄路徑的 index.vue 文件生成為與目錄同名的 html 文件,而不是放置於目錄中的 index.html 文件,所以如果不進行 rewrite 去掉路徑結尾的 / 的話,https://utilmeta.com/zh/ 就會直接訪問到 /zh/ 目錄上,這對於 nginx 來說是禁止的行為

值得註意的是,對於 404 頁面的返回,最好需要伴隨著一個真正的 404 響應碼(Status Code),而不是使用 200 OK 的響應(那樣一般稱為軟 404),因為對於搜索引擎而言,只有檢測到 404 響應碼,才會把這個路由視為無效,而不是判斷返回頁面中的文字,尤其當你的站點進行翻新時,老站點的一些路由就會失效了,如果它們一直留在搜索引擎的結果中誤導用戶,也會給訪客造成很大的困擾

在上面的 nginx 配置中,我們把所有 try_files 指令最後都附上了 =404 ,也就是在匹配不到任何文件時生成 404 的響應碼,然後使用 error_page 把包括 404 在內的常見的錯誤或故障響應碼的錯誤頁面指定為 /404.html ,也就是我們之前編寫的 404 頁面,這樣我們就解決了軟 404 的問題,所有無法匹配的路徑都會返回正確的 404 響應碼以及製作好的 404 頁面

總結

總結一下我們學到和完成的東西

  • 用 Vite-SSG 編寫一個 SSG 官網項目,瞭解了 SSG 項目的頁面路由方式
  • 編寫可復用的多語言的 SSG 頁面組件,通過路由切換實現語言切換功能
  • 使用 unhead 為每個頁面註入頭部元信息,使得每個頁面在搜索引擎與社交媒體上都能正確美觀地展示
  • 使用 @media 解決實現 SSG 靜態頁面的響應式中的問題,以及 Vuetify 網格佈局在 SSG 響應式中的實踐
  • 優雅處理靜態頁面的 404 問題,避免軟 404,提高頁面收錄質量和用戶體驗

如果你覺得這篇文章有幫助,可以逛一下這篇文章中我最終構建的項目官網 utilmeta.com ,也可以關註一下我的 X(Twitter) ,我會不定期分享一些技術實踐和項目


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 結論:當一個事務要對錶進行鎖定時,首先會獲取相應的意向鎖。其他事務可以通過檢查意向鎖來判斷是否有其他事務在更細粒度的級別上對錶進行了鎖定。這有助於避免衝突和提高併發性能 在討論此問題之前我們應當明確兩個前提: Innodb存儲引擎支持行鎖和表鎖共存 行鎖與表鎖之間互不衝突 意向鎖是表級別的鎖,意向鎖 ...
  • 原文: Android 獲取設備的CPU型號和設備型號-Stars-One的雜貨小窩 之前整的項目的總結信息,可能不太全,湊合著用吧,代碼在最下麵一節 CPU型號數據 華為: ro.mediatek.platform vivo: ro.vivo.product.platform oppo: ro.b ...
  • ​ 1:使用Protocol Buffers 首先根目錄gradle中添加依賴: classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.3" 然後項目文件中添加plugin,添加依賴包: apply plugin: 'com.google ...
  • Android 二維碼相關(二) 本篇文章繼續講述下如何使用zxing解析二維碼圖片,獲取內容. 1: 創建RGBLuminanceSource對象. 首先獲取二維碼圖片的bitmap對象. Bitmap bitmap = BitmapFactory.decodeResource(getResour ...
  • Android 二維碼相關(一) 本篇文章主要記錄下android下使用zxing來創建二維碼. 1: 導入依賴 api "com.google.zxing:core:3.5.1" 2: 創建二維碼 創建QRCodeWriter對象 QRCodeWriter qrCodeWriter = new Q ...
  • linux 入門(七) 1: 安裝ffmpeg ubuntu下安裝ffmpeg: sudo apt-get install ffmpeg 2: ffmpeg --help ffmpeg version 4.2.7-0ubuntu0.1 Copyright (c) 2000-2022 the FFmp ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 一、介紹 Decorator,即裝飾器,從名字上很容易讓我們聯想到裝飾者模式 簡單來講,裝飾者模式就是一種在不改變原類和使用繼承的情況下,動態地擴展對象功能的設計理論。 ES6中Decorator功能亦如此,其本質也不是什麼高大上的結構, ...
  • 本文討論了前端架構的重要性,並提出了選擇Vue組件庫時需考慮的因素。重點介紹了ViewDesign這一優秀的Vue組件庫,概述了其特點及如何實現高效前端架構。完整版文章鏈接可在本文結尾處獲取。 ...
一周排行
    -Advertisement-
    Play Games
  • 下麵是一個標準的IDistributedCache用例: public class SomeService(IDistributedCache cache) { public async Task<SomeInformation> GetSomeInformationAsync (string na ...
  • 這個庫提供了在啟動期間實例化已註冊的單例,而不是在首次使用它時實例化。 單例通常在首次使用時創建,這可能會導致響應傳入請求的延遲高於平時。在註冊時創建實例有助於防止第一次Request請求的SLA 以往我們要在註冊的時候實例單例可能會這樣寫: //註冊: services.AddSingleton< ...
  • 最近公司的很多項目都要改單點登錄了,不過大部分都還沒敲定,目前立刻要做的就只有一個比較老的項目 先改一個試試手,主要目標就是最短最快實現功能 首先因為要保留原登錄方式,所以頁面上的改動就是在原來登錄頁面下加一個SSO登錄入口 用超鏈接寫的入口,頁面改造後如下圖: 其中超鏈接的 href="Staff ...
  • Like運算符很好用,特別是它所提供的其中*、?這兩種通配符,在Windows文件系統和各類項目中運用非常廣泛。 但Like運算符僅在VB中支持,在C#中,如何實現呢? 以下是關於LikeString的四種實現方式,其中第四種為Regex正則表達式實現,且在.NET Standard 2.0及以上平... ...
  • 一:背景 1. 講故事 前些天有位朋友找到我,說他們的程式記憶體會偶發性暴漲,自己分析了下是非托管記憶體問題,讓我幫忙看下怎麼回事?哈哈,看到這個dump我還是非常有興趣的,居然還有這種游戲幣自助機類型的程式,下次去大玩家看看他們出幣的機器後端是不是C#寫的?由於dump是linux上的程式,剛好win ...
  • 前言 大家好,我是老馬。很高興遇到你。 我們為 java 開發者實現了 java 版本的 nginx https://github.com/houbb/nginx4j 如果你想知道 servlet 如何處理的,可以參考我的另一個項目: 手寫從零實現簡易版 tomcat minicat 手寫 ngin ...
  • 上一次的介紹,主要圍繞如何統一去捕獲異常,以及為每一種異常添加自己的Mapper實現,並且我們知道,當在ExceptionMapper中返回非200的Response,不支持application/json的響應類型,而是寫死的text/plain類型。 Filter為二方包異常手動捕獲 參考:ht ...
  • 大家好,我是R哥。 今天分享一個爽飛了的面試輔導 case: 這個杭州兄弟空窗期 1 個月+,面試了 6 家公司 0 Offer,不知道問題出在哪,難道是杭州的 IT 崩盤了麽? 報名面試輔導後,經過一個多月的輔導打磨,現在成功入職某上市公司,漲薪 30%+,955 工作制,不咋加班,還不捲。 其他 ...
  • 引入依賴 <!--Freemarker wls--> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.30</version> </dependency> ...
  • 你應如何運行程式 互動式命令模式 開始一個互動式會話 一般是在操作系統命令行下輸入python,且不帶任何參數 系統路徑 如果沒有設置系統的PATH環境變數來包括Python的安裝路徑,可能需要機器上Python可執行文件的完整路徑來代替python 運行的位置:代碼位置 不要輸入的內容:提示符和註 ...