<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Lucifer's Blog]]></title><description><![CDATA[Keep Moving]]></description><link>https://www.superlucifer.cn</link><image><url>https://www.superlucifer.cn/favicon.ico</url><title>Lucifer&apos;s Blog</title><link>https://www.superlucifer.cn</link></image><generator>Shiro (https://github.com/Innei/Shiro)</generator><lastBuildDate>Wed, 10 Jun 2026 04:22:50 GMT</lastBuildDate><atom:link href="https://www.superlucifer.cn/feed" rel="self" type="application/rss+xml"/><pubDate>Wed, 10 Jun 2026 04:22:50 GMT</pubDate><language><![CDATA[zh-CN]]></language><item><title><![CDATA[Electron + Vue3 数据持久化方案：Pinia 与 electron-store 的结合]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://www.superlucifer.cn/posts/code/electron-store">https://www.superlucifer.cn/posts/code/electron-store</a></blockquote><div><h2 id="">前言</h2><p>在开发 Electron + Vue3 应用时，数据持久化是一个常见需求。很多开发者习惯在 Vue 项目中使用 <code>pinia-plugin-persistedstate</code> 插件来实现状态持久化，但在 Electron 应用中，这个方案存在一些问题。</p><p><strong>为什么不能直接使用 pinia-plugin-persistedstate？</strong></p><p><code>pinia-plugin-persistedstate</code> 的默认实现是基于浏览器的 <code>localStorage</code> 或 <code>sessionStorage</code>。在 Electron 应用中，这种方案存在以下问题：</p><ol start="1"><li><p><strong>进程隔离问题</strong>：Electron 采用多进程架构，渲染进程和主进程是隔离的。<code>localStorage</code> 只能在渲染进程中访问，主进程无法直接读取这些数据。</p></li><li><p><strong>数据安全性问题</strong>：<code>localStorage</code> 以明文形式存储数据，敏感信息（如用户 token）存在安全隐患。虽然可以对数据进行加密，但增加了复杂度。</p></li><li><p><strong>数据存储位置不可控</strong>：<code>localStorage</code> 的存储位置由 Chromium 决定，通常在用户目录下的特定位置。如果应用需要自定义存储路径，或者需要在不同机器间迁移数据，这种方式不够灵活。</p></li><li><p><strong>多用户数据隔离困难</strong>：如果应用需要支持多用户登录，并且每个用户的数据需要隔离存储，使用 <code>localStorage</code> 实现起来比较麻烦。</p></li><li><p><strong>应用更新可能导致数据丢失</strong>：在某些情况下（如应用重装、版本更新），<code>localStorage</code> 中的数据可能会丢失。</p></li></ol><p>为了解决这些问题，我们可以使用 <code>electron-store</code> 配合 <code>pinia</code> 来实现更可靠的数据持久化方案。</p><h2 id="">方案概述</h2><p>本方案的核心思路是：</p><ul><li><strong>Pinia</strong>：负责管理应用的响应式状态</li><li><strong>electron-store</strong>：负责在主进程中进行数据持久化</li><li><strong>IPC 通信</strong>：通过 Electron 的 IPC 机制，让渲染进程能够访问 electron-store</li></ul><p>同时，本方案还支持<strong>多用户数据隔离</strong>，每个用户的数据存储在独立的命名空间中。</p><h2 id="">实现步骤</h2><p><strong>1. 安装依赖</strong></p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">yarn add pinia electron-store
</code></pre>
<p><strong>2. 主进程：初始化 electron-store 并注册 IPC 处理器</strong></p><p>在主进程中，我们需要初始化 <code>electron-store</code> 并注册 IPC 处理器，让渲染进程可以通过 IPC 调用来访问存储。</p><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">// src/main/ipc/storeHandlers.js
import { ipcMain } from &#x27;electron&#x27;
import StoreModule from &#x27;electron-store&#x27;
import crypto from &#x27;crypto&#x27;
const Store = StoreModule.default || StoreModule

let store = null
let encryptionKey = null

// 获取或创建加密密钥
function getEncryptionKey() {
  if (encryptionKey) {
    return encryptionKey
  }

  // 创建一个临时 store 来存储加密密钥
  const keyStore = new Store({
    name: &#x27;app-key-store&#x27;
  })

  let savedKey = keyStore.get(&#x27;encryption_key&#x27;)

  if (savedKey) {
    encryptionKey = savedKey
    return encryptionKey
  }

  // 生成 256 位随机密钥
  const key = crypto.randomBytes(32).toString(&#x27;hex&#x27;)
  keyStore.set(&#x27;encryption_key&#x27;, key)
  encryptionKey = key
  return encryptionKey
}

// 初始化 store
function initStore() {
  if (store) return store

  store = new Store({
    name: &#x27;app-config&#x27;,
    encryptionKey: getEncryptionKey(), // 启用加密
    defaults: {
      currentUserId: &#x27;&#x27;,
      users: {}
    }
  })

  return store
}

// 获取当前用户的数据命名空间
function getCurrentUserData() {
  const s = initStore()
  const currentUserId = s.get(&#x27;currentUserId&#x27;)
  if (!currentUserId) {
    return null
  }
  const users = s.get(&#x27;users&#x27;) || {}
  return users[currentUserId] || {}
}

// 设置当前用户的数据
function setCurrentUserData(userData) {
  const s = initStore()
  const currentUserId = s.get(&#x27;currentUserId&#x27;)
  if (!currentUserId) {
    return false
  }
  const users = s.get(&#x27;users&#x27;) || {}
  users[currentUserId] = userData
  s.set(&#x27;users&#x27;, users)
  return true
}

export function registerStoreHandlers() {
  initStore()

  // 设置当前用户（登录时调用）
  ipcMain.handle(&#x27;store-set-current-user&#x27;, (_, userId) =&gt; {
    if (!userId) return false
    const s = initStore()
    s.set(&#x27;currentUserId&#x27;, userId)
    return true
  })

  // 清除当前用户（退出登录时调用）
  ipcMain.handle(&#x27;store-clear-current-user&#x27;, () =&gt; {
    const s = initStore()
    s.set(&#x27;currentUserId&#x27;, &#x27;&#x27;)
    return true
  })

  // 获取当前用户ID
  ipcMain.handle(&#x27;store-get-current-user-id&#x27;, () =&gt; {
    const s = initStore()
    return s.get(&#x27;currentUserId&#x27;)
  })

  // 获取值
  ipcMain.handle(&#x27;store-get&#x27;, (_, key) =&gt; {
    const userData = getCurrentUserData()
    if (!userData) return undefined
    return userData[key]
  })

  // 设置值
  ipcMain.handle(&#x27;store-set&#x27;, (_, key, value) =&gt; {
    const userData = getCurrentUserData()
    if (!userData) return false
    userData[key] = value
    return setCurrentUserData(userData)
  })

  // 删除值
  ipcMain.handle(&#x27;store-delete&#x27;, (_, key) =&gt; {
    const userData = getCurrentUserData()
    if (!userData) return false
    delete userData[key]
    return setCurrentUserData(userData)
  })

  // 获取当前用户的所有数据
  ipcMain.handle(&#x27;store-get-all&#x27;, () =&gt; {
    return getCurrentUserData() || {}
  })
}
</code></pre>
<p><strong>3. 预加载脚本：暴露 Store API</strong></p><p>在预加载脚本中，我们需要将 store 相关的 API 暴露给渲染进程。</p><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">// src/preload/index.js
import { contextBridge, ipcRenderer } from &#x27;electron&#x27;

const api = {
  // electron-store API - 支持多用户数据隔离
  store: {
    // 设置当前用户（登录时调用）
    setCurrentUser: userId =&gt; ipcRenderer.invoke(&#x27;store-set-current-user&#x27;, userId),
    // 清除当前用户（退出登录时调用）
    clearCurrentUser: () =&gt; ipcRenderer.invoke(&#x27;store-clear-current-user&#x27;),
    // 获取当前用户ID
    getCurrentUserId: () =&gt; ipcRenderer.invoke(&#x27;store-get-current-user-id&#x27;),
    // 数据操作（自动关联到当前用户）
    get: key =&gt; ipcRenderer.invoke(&#x27;store-get&#x27;, key),
    set: (key, value) =&gt; ipcRenderer.invoke(&#x27;store-set&#x27;, key, value),
    delete: key =&gt; ipcRenderer.invoke(&#x27;store-delete&#x27;, key),
    getAll: () =&gt; ipcRenderer.invoke(&#x27;store-get-all&#x27;)
  }
}

if (process.contextIsolated) {
  contextBridge.exposeInMainWorld(&#x27;api&#x27;, api)
}
</code></pre>
<p><strong>4. 渲染进程：封装 useElectronStore 组合式函数</strong></p><p>为了方便在 Vue 组件和 Pinia Store 中使用，我们封装一个组合式函数。</p><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">// src/renderer/src/composables/useElectronStore.js
import { ref, readonly } from &#x27;vue&#x27;

export const ELECTRON_STORE_TOKEN = &#x27;token&#x27;
export const ELECTRON_STORE_USER = &#x27;user&#x27;
export const ELECTRON_STORE_SETTING = &#x27;setting&#x27;

export function useElectronStore() {
  const isReady = ref(false)
  const data = ref({})
  const currentUserId = ref(&#x27;&#x27;)

  // 检查是否在 Electron 环境
  const isElectron = typeof window !== &#x27;undefined&#x27; &amp;&amp; window.api &amp;&amp; window.api.store

  // 设置当前用户
  async function setCurrentUser(userId) {
    if (!isElectron) return false
    try {
      await window.api.store.setCurrentUser(userId)
      currentUserId.value = userId
      await getAll()
      return true
    } catch (error) {
      console.error(&#x27;store.setCurrentUser 失败:&#x27;, error)
      return false
    }
  }

  // 清除当前用户
  async function clearCurrentUser() {
    if (!isElectron) return false
    try {
      await window.api.store.clearCurrentUser()
      currentUserId.value = &#x27;&#x27;
      data.value = {}
      return true
    } catch (error) {
      console.error(&#x27;store.clearCurrentUser 失败:&#x27;, error)
      return false
    }
  }

  // 获取当前用户ID
  async function getCurrentUser() {
    if (!isElectron) return &#x27;&#x27;
    try {
      const userId = await window.api.store.getCurrentUserId()
      currentUserId.value = userId
      return userId
    } catch (error) {
      console.error(&#x27;store.getCurrentUserId 失败:&#x27;, error)
      return &#x27;&#x27;
    }
  }

  // 获取值
  async function get(key, defaultValue = undefined) {
    if (!isElectron) return defaultValue
    try {
      const value = await window.api.store.get(key)
      return value !== undefined ? value : defaultValue
    } catch (error) {
      console.error(&#x27;store.get 失败:&#x27;, error)
      return defaultValue
    }
  }

  // 将值转换为可序列化的普通对象（处理 Vue 的 Proxy 对象）
  function toSerializable(value) {
    if (value === null || value === undefined) return value
    if (typeof value === &#x27;function&#x27; || value instanceof Date || value instanceof RegExp) {
      return value
    }
    if (Array.isArray(value)) {
      return value.map(toSerializable)
    }
    if (typeof value === &#x27;object&#x27;) {
      const plain = {}
      for (const key of Object.keys(value)) {
        plain[key] = toSerializable(value[key])
      }
      return plain
    }
    return value
  }

  // 设置值
  async function set(key, value) {
    if (!isElectron) return false
    try {
      const serializableValue = toSerializable(value)
      await window.api.store.set(key, serializableValue)
      return true
    } catch (error) {
      console.error(&#x27;store.set 失败:&#x27;, error)
      return false
    }
  }

  // 获取当前用户的所有数据
  async function getAll() {
    if (!isElectron) return {}
    try {
      const allData = await window.api.store.getAll()
      data.value = allData
      isReady.value = true
      return allData
    } catch (error) {
      console.error(&#x27;store.getAll 失败:&#x27;, error)
      return {}
    }
  }

  return {
    isReady: readonly(isReady),
    data: readonly(data),
    currentUserId: readonly(currentUserId),
    setCurrentUser,
    clearCurrentUser,
    getCurrentUser,
    get,
    set,
    getAll,
    isElectron
  }
}
</code></pre>
<p><strong>5. 在 Pinia Store 中使用</strong></p><p>现在我们可以在 Pinia Store 中使用 <code>useElectronStore</code> 来实现数据持久化。</p><p><strong>用户信息 Store 示例</strong></p><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">// src/renderer/src/store/modules/user.js
import {
  useElectronStore,
  ELECTRON_STORE_TOKEN,
  ELECTRON_STORE_USER
} from &#x27;../../composables/useElectronStore.js&#x27;
import useSettingStore from &#x27;./setting.js&#x27;

const useUserStore = defineStore(&#x27;user&#x27;, () =&gt; {
  const electronStore = useElectronStore()

  const token = ref(&#x27;&#x27;)
  const userInfo = ref({})
  const id = ref(&#x27;&#x27;)
  const name = ref(&#x27;&#x27;)

  // 登录成功处理
  async function handleLoginSuccess(accessToken) {
    token.value = accessToken

    // 先获取用户信息以获取用户ID
    await getUserInfo()

    // 设置当前用户（按用户ID隔离数据）
    if (id.value) {
      await electronStore.setCurrentUser(id.value)
    }

    // 保存 token 到 electron-store
    await electronStore.set(ELECTRON_STORE_TOKEN, accessToken)

    // 初始化设置
    await useSettingStore().init()
  }

  // 从 electron-store 加载用户数据
  async function loadUserFromStore() {
    try {
      const currentUserId = await electronStore.getCurrentUser()

      if (!currentUserId) return

      const storedToken = await electronStore.get(ELECTRON_STORE_TOKEN)
      if (storedToken) {
        token.value = storedToken
      }

      const storedUser = await electronStore.get(ELECTRON_STORE_USER)
      if (storedUser) {
        userInfo.value = storedUser.userInfo || {}
        id.value = storedUser.id || &#x27;&#x27;
        name.value = storedUser.name || &#x27;&#x27;
      }
    } catch (error) {
      console.error(&#x27;从 store 加载用户数据失败:&#x27;, error)
    }
  }

  // 保存用户数据到 electron-store
  async function saveUserToStore() {
    try {
      await electronStore.set(ELECTRON_STORE_USER, {
        userInfo: { ...userInfo.value },
        id: id.value,
        name: name.value
      })
    } catch (error) {
      console.error(&#x27;保存用户数据到 store 失败:&#x27;, error)
    }
  }

  // 获取用户信息
  function getUserInfo() {
    return new Promise(async (resolve, reject) =&gt; {
      try {
        // 调用后端 API getInfo 获取用户信息
        const infoRes = await getInfo()
        console.log(&#x27;获取用户信息成功&#x27;, infoRes)
        name.value = infoRes.data.userName
        id.value = infoRes.data.id
        userInfo.value = infoRes.data
        // 保存用户信息到 electron-store
        await saveUserToStore()
        resolve()
      } catch (error) {
        reject(error)
      }
    })
  }

  // 退出登录
  async function logout() {
    token.value = &#x27;&#x27;
    id.value = &#x27;&#x27;
    name.value = &#x27;&#x27;
    userInfo.value = {}

    // 清除当前用户（退出数据隔离）
    await electronStore.clearCurrentUser()
  }

  // 初始化时加载数据
  async function init() {
    await loadUserFromStore()
  }

  return {
    token,
    userInfo,
    id,
    name,
    handleLoginSuccess,
    loadUserFromStore,
    saveUserToStore,
    logout,
    init
  }
})

export default useUserStore
</code></pre>
<p><strong>设置 Store 示例</strong></p><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">// src/renderer/src/store/modules/setting.js
import { useElectronStore, ELECTRON_STORE_SETTING } from &#x27;../../composables/useElectronStore.js&#x27;

const useSettingStore = defineStore(&#x27;setting&#x27;, () =&gt; {
  const electronStore = useElectronStore()

  const themeMode = ref(&#x27;dark&#x27;)
  const isDark = ref(false)

  // 切换主题模式
  async function toggleThemeMode(mode) {
    themeMode.value = mode
    isDark.value = mode === &#x27;dark&#x27;
    await saveSettingToStore()
    setThemeClass()
  }

  // 设置主题类名
  function setThemeClass() {
    if (isDark.value) {
      document.documentElement.classList.add(&#x27;dark&#x27;)
    } else {
      document.documentElement.classList.remove(&#x27;dark&#x27;)
    }
  }

  // 从 electron-store 加载设置数据
  async function loadSettingFromStore() {
    try {
      const storedSetting = await electronStore.get(ELECTRON_STORE_SETTING)
      if (storedSetting) {
        themeMode.value = storedSetting.themeMode || &#x27;light&#x27;
        isDark.value = storedSetting.isDark || false
      }
    } catch (error) {
      console.error(&#x27;从 store 加载设置数据失败:&#x27;, error)
    }
  }

  // 保存设置数据到 electron-store
  async function saveSettingToStore() {
    try {
      await electronStore.set(ELECTRON_STORE_SETTING, {
        themeMode: themeMode.value,
        isDark: isDark.value
      })
    } catch (error) {
      console.error(&#x27;保存设置数据到 store 失败:&#x27;, error)
    }
  }

  // 初始化
  async function init() {
    await loadSettingFromStore()
    await toggleThemeMode(themeMode.value)
  }

  return {
    themeMode,
    isDark,
    toggleThemeMode,
    loadSettingFromStore,
    saveSettingToStore,
    init
  }
})

export default useSettingStore
</code></pre>
<p><strong>6. 应用启动时初始化</strong></p><p>在应用启动时，我们需要初始化 Pinia 并加载持久化的数据。</p><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">// src/renderer/src/main.js
import { createApp } from &#x27;vue&#x27;
import { createPinia } from &#x27;pinia&#x27;
import App from &#x27;./App.vue&#x27;
import useUserStore from &#x27;./store/modules/user.js&#x27;

async function bootstrap() {
  const app = createApp(App)
  const pinia = createPinia()
  app.use(pinia)

  // 初始化 user store（从 electron-store 加载用户数据）
  const userStore = useUserStore()
  await userStore.init()

  app.mount(&#x27;#app&#x27;)
}

bootstrap()
</code></pre>
<h2 id="">架构图</h2><pre class=""><code class="">┌─────────────────────────────────────────────────────────────────┐
│                        渲染进程 (Renderer)                        │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                    Pinia Store                           │   │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐     │   │
│  │  │  UserStore  │  │ SettingStore│  │ HistoryStore│     │   │
│  │  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘     │   │
│  │         │                │                │             │   │
│  │         └────────────────┼────────────────┘             │   │
│  │                          ▼                              │   │
│  │              ┌───────────────────────┐                  │   │
│  │              │   useElectronStore    │                  │   │
│  │              │     (Composable)      │                  │   │
│  │              └───────────┬───────────┘                  │   │
│  └──────────────────────────┼──────────────────────────────┘   │
│                             │                                   │
│                             ▼                                   │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │                    window.api.store                       │  │
│  │                  (Preload 暴露的 API)                      │  │
│  └──────────────────────────┬───────────────────────────────┘  │
└─────────────────────────────┼───────────────────────────────────┘
                              │
                              │ IPC 通信
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                        主进程 (Main)                             │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │                   IPC Handlers                            │  │
│  │              (storeHandlers.js)                           │  │
│  └──────────────────────────┬───────────────────────────────┘  │
│                             │                                   │
│                             ▼                                   │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │                   electron-store                          │  │
│  │                  (数据持久化存储)                           │  │
│  │                                                          │  │
│  │   {                                                      │  │
│  │     currentUserId: &quot;user123&quot;,                            │  │
│  │     users: {                                             │  │
│  │       &quot;user123&quot;: {                                       │  │
│  │         token: &quot;...&quot;,                                    │  │
│  │         user: { ... },                                   │  │
│  │         setting: { ... },                                │  │
│  │         history: [ ... ]                                 │  │
│  │       }                                                  │  │
│  │     }                                                    │  │
│  │   }                                                      │  │
│  └──────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘
</code></pre>
<h2 id="">方案优势</h2><ol start="1"><li><p><strong>数据安全性</strong>：使用 <code>electron-store</code> 的加密功能，敏感数据得到保护。</p></li><li><p><strong>多用户数据隔离</strong>：每个用户的数据存储在独立的命名空间中，切换用户时自动加载对应用户的数据。</p></li><li><p><strong>主进程可访问</strong>：数据存储在主进程中，主进程代码可以直接访问，方便实现自动登录等功能。</p></li><li><p><strong>存储位置可控</strong>：可以自定义数据存储路径，便于数据迁移和备份。</p></li><li><p><strong>响应式状态管理</strong>：Pinia 提供了完善的响应式状态管理，与 Vue 3 完美集成。</p></li><li><p><strong>类型安全</strong>：可以配合 TypeScript 使用，获得更好的类型提示。</p></li></ol><h2 id="">总结</h2><p>通过 Pinia + electron-store 的组合，可实现一个安全、可靠、支持多用户数据隔离的持久化方案。这个方案充分利用了 Electron 的多进程架构优势，同时保持了 Vue 3 响应式状态管理的便利性。</p><p>相比直接使用 <code>pinia-plugin-persistedstate</code>，这个方案更适合 Electron 应用的场景，提供了更好的数据安全性和灵活性。</p></div><p style="text-align:right"><a href="https://www.superlucifer.cn/posts/code/electron-store#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://www.superlucifer.cn/posts/code/electron-store</link><guid isPermaLink="true">https://www.superlucifer.cn/posts/code/electron-store</guid><dc:creator><![CDATA[Lucifer]]></dc:creator><pubDate>Mon, 13 Apr 2026 14:40:53 GMT</pubDate></item><item><title><![CDATA[Dijkstra 算法]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://www.superlucifer.cn/posts/algorithm/dijkstra">https://www.superlucifer.cn/posts/algorithm/dijkstra</a></blockquote><div><p>Dijkstra 算法是解决<strong>单源最短路径</strong>问题的经典算法。</p><h2 id="">算法概念</h2><p>Dijkstra 算法用于在一个<strong>加权有向图或无向图</strong>中，找到从一个特定源节点到图中所有其他节点的最短路径。</p><p><strong>主要特点：</strong></p><ul><li><strong>单源最短路径</strong>：计算从一个固定起点到其他所有点的距离。</li><li><strong>贪心策略</strong>：每次选择当前距离起点最近且未访问过的节点进行扩展。</li><li><strong>权重限制</strong>：<strong>不能处理带有负权边的图</strong>（如果存在负权边，请使用 Bellman-Ford 或 SPFA 算法）。</li><li><strong>时间复杂度</strong>：
<ul><li>基础版： $O(V^2)$ ，适合稠密图。</li><li>堆优化版： $O(E \log V)$ ，适合稀疏图（ $E$ 是边数， $V$ 是顶点数）。</li></ul></li></ul><h2 id="">核心思路</h2><p>Dijkstra 算法的核心在于<strong>不断“松弛”（Relaxation）边</strong>。</p><ul><li><strong>初始化</strong>：
<ul><li>起点到自身的距离为 0，到其他所有点的距离设为 $\infty$。</li><li>使用一个集合 <code>visited</code> 记录已确定最短路径的节点。</li></ul></li><li><strong>贪心选择</strong>：
<ul><li>从未访问的节点中，选出当前距离起点最近的一个节点 $u$。</li></ul></li><li><strong>松弛操作</strong>：
<ul><li>遍历节点 $u$ 的所有邻居 $v$。</li><li>如果 <code>起点 -&gt; u -&gt; v</code> 的路径长度比当前记录的 <code>起点 -&gt; v</code> 更短，则更新 <code>dist[v]</code>。</li></ul></li><li><strong>重复</strong>：
<ul><li>重复步骤 2 和 3，直到所有节点都被访问或不再有可优化的路径。</li></ul></li></ul><h2 id="">代码实现</h2><ul><li><p>基础版（适合小规模图）</p><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">/**
 * Dijkstra 基础实现 (邻接矩阵)
 * @param {number[][]} graph - 邻接矩阵
 * @param {number} start - 起点编号
 * @returns {number[]} - 起点到各点的最短距离数组
 */
function dijkstra(graph, start) {
    const n = graph.length
    const dist = Array(n).fill(Infinity)
    const visited = Array(n).fill(false)

    dist[start] = 0

    for (let i = 0; i &lt; n; i++) {
        // 1. 找到未访问节点中距离最小的
        let u = -1
        for (let j = 0; j &lt; n; j++) {
            if (!visited[j] &amp;&amp; (u === -1 || dist[j] &lt; dist[u])) {
                u = j
            }
        }

        if (dist[u] === Infinity) break
        visited[u] = true

        // 2. 松弛邻居节点
        for (let v = 0; j &lt; n; v++) {
            if (!visited[v] &amp;&amp; graph[u][v] !== Infinity) {
                if (dist[u] + graph[u][v] &lt; dist[v]) {
                    dist[v] = dist[u] + graph[u][v]
                }
            }
        }
    }
    return dist
}
</code></pre>
</li><li><p>堆优化版（适合稀疏图）</p><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">class MinHeap {
    constructor() {
        this.heap = []
    }
    push(val) {
        this.heap.push(val)
        this.bubbleUp()
    }
    pop() {
        if (this.size() === 0) return null
        if (this.size() === 1) return this.heap.pop()
        const top = this.heap[0]
        this.heap[0] = this.heap.pop()
        this.bubbleDown()
        return top
    }
    size() {
        return this.heap.length
    }
    bubbleUp() {
        let index = this.heap.length - 1
        while (index &gt; 0) {
            let pIdx = Math.floor((index - 1) / 2)
            if (this.heap[index][0] &gt;= this.heap[pIdx][0]) break
            ;[this.heap[index], this.heap[pIdx]] = [this.heap[pIdx], this.heap[index]]
            index = pIdx
        }
    }
    bubbleDown() {
        let index = 0
        while (true) {
            let L = 2 * index + 1,
                R = 2 * index + 2,
                swap = null
            if (L &lt; this.heap.length &amp;&amp; this.heap[L][0] &lt; this.heap[index][0]) swap = L
            if (
                R &lt; this.heap.length &amp;&amp;
                (swap === null ? this.heap[R][0] &lt; this.heap[index][0] : this.heap[R][0] &lt; this.heap[L][0])
            )
                swap = R
            if (swap === null) break
            ;[this.heap[index], this.heap[swap]] = [this.heap[swap], this.heap[index]]
            index = swap
        }
    }
}

/**
 * 堆优化的 Dijkstra 算法
 * @param {number} n - 节点数量
 * @param {Map} adj - 邻接表
 * @param {number} start - 起点
 * @returns {number[]} - 距离数组
 */
function dijkstraOptimized(n, adj, start) {
    const dist = new Array(n).fill(Infinity)
    dist[start] = 0
    const pq = new MinHeap()
    pq.push([0, start]) // [距离, 节点]

    while (pq.size() &gt; 0) {
        const [d, u] = pq.pop()
        if (d &gt; dist[u]) continue // 关键优化：跳过过时路径

        const neighbors = adj.get(u) || []
        for (const [v, weight] of neighbors) {
            if (dist[u] + weight &lt; dist[v]) {
                dist[v] = dist[u] + weight
                pq.push([dist[v], v])
            }
        }
    }
    return dist
}
</code></pre>
</li></ul><h2 id="leetcode-">LeetCode 题目案例</h2><ul><li><a href="https://leetcode.cn/problems/network-delay-time/">743. 网络延迟时间</a></li></ul><p><strong>题目描述</strong>：
有 $n$ 个网络节点，标记为 $1$ 到 $n$。给你一个列表 <code>times</code>，表示信号经过有向边的传递时间。现在，我们从某个节点 $k$ 发出一个信号。需要多久才能使所有节点都收到信号？如果不能使所有节点收到信号，返回 $-1$。</p><p><strong>解析</strong>：
这道题要求计算从起点 $k$ 到达所有节点的最长“最短路径”。</p><p><strong>JS 实现（堆优化版）</strong>：
在 JavaScript 中，由于没有原生的优先队列，通常需要手动实现一个简易的最小堆来达到 $O(E \log V)$ 的复杂度。</p><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">var networkDelayTime = function (times, n, k) {
    // 1. 构建邻接表
    const adj = Array.from({ length: n + 1 }, () =&gt; [])
    for (const [u, v, w] of times) {
        adj[u].push([v, w])
    }

    const dist = Array(n + 1).fill(Infinity)
    dist[k] = 0

    // 2. 优先队列 (最小堆存储: [distance, node])
    const pq = new MinPriorityQueue({ priority: x =&gt; x[0] })
    pq.enqueue([0, k])

    while (!pq.isEmpty()) {
        const [d, u] = pq.dequeue().element

        if (d &gt; dist[u]) continue // 过时路径，跳过

        for (const [v, w] of adj[u]) {
            if (dist[u] + w &lt; dist[v]) {
                dist[v] = dist[u] + w
                pq.enqueue([dist[v], v])
            }
        }
    }

    // 3. 寻找结果
    let maxDist = 0
    for (let i = 1; i &lt;= n; i++) {
        if (dist[i] === Infinity) return -1
        maxDist = Math.max(maxDist, dist[i])
    }
    return maxDist
}
</code></pre>
<p><em>(注：MinPriorityQueue 是 LeetCode 环境内置的库)</em></p><h2 id="">应用场景</h2><ul><li><strong>地图导航与 GPS</strong>：计算两个地理坐标点之间的最短行驶路线。</li><li><strong>网络路由协议</strong>：OSPF（开放式最短路径优先）协议使用 Dijkstra 算法来更新路由表。</li><li><strong>任务调度</strong>：在有向无环图中寻找关键路径或最小化任务总时长。</li><li><strong>游戏 AI</strong>：非网格类地图中，NPC 寻找通往目标点的最短路径。</li></ul><h2 id="dijkstra-vs-floyd">Dijkstra vs Floyd</h2><ul><li><strong>Dijkstra</strong>：单源最短路径。速度快（优化后 $O(E \log V)$），但不能处理负权。</li><li><strong>Floyd</strong>：全源最短路径。代码极简，支持负权边，但速度慢（ $O(V^3)$ ），仅适用于小规模数据。</li></ul><p>在实际开发中，如果只需要从一个点出发，优先选择 <strong>Dijkstra</strong>。</p></div><p style="text-align:right"><a href="https://www.superlucifer.cn/posts/algorithm/dijkstra#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://www.superlucifer.cn/posts/algorithm/dijkstra</link><guid isPermaLink="true">https://www.superlucifer.cn/posts/algorithm/dijkstra</guid><dc:creator><![CDATA[Lucifer]]></dc:creator><pubDate>Sun, 08 Feb 2026 03:35:22 GMT</pubDate></item><item><title><![CDATA[Floyd-Warshall 算法]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://www.superlucifer.cn/posts/algorithm/floyd">https://www.superlucifer.cn/posts/algorithm/floyd</a></blockquote><div><p>Floyd-Warshall 算法是一种解决任意两点间最短路径问题的经典动态规划算法。</p><h2 id="">算法概念</h2><p>Floyd-Warshall 算法（简称 Floyd 算法）用于寻找给定的加权图中<strong>多源点之间最短路径</strong>。</p><p><strong>主要特点：</strong></p><ul><li><strong>全源最短路径</strong>：一次运行即可求出图中任意两个节点之间的最短距离。</li><li><strong>支持负权边</strong>：能够处理边权为负的情况。</li><li><strong>不支持负权回路</strong>：如果图中存在总权重为负的环路，最短路径可能不存在。</li><li><strong>时间复杂度</strong>： $O(V^3)$ ，适合顶点数 $V$ 在 500 以内的图。</li></ul><h2 id="">核心思路：动态规划</h2><p>Floyd 算法的核心在于通过引入<strong>中间节点</strong>来逐步优化路径。</p><p>假设 $dist(i, j)$ 是从顶点 $i$ 到顶点 $j$ 的当前已知最短距离。我们依次考虑每一个顶点 $k$ 作为中间跳板：
对于每一对顶点 $(i, j)$ ，检查：<strong>“如果先从 $i$ 走到 $k$，再从 $k$ 走到 $j$，是否比当前的直接路径 $i \to j$ 更短？”</strong></p><p>状态转移方程为：</p><p>$$
dist(i, j) = \min(dist(i, j), dist(i, k) + dist(k, j))
$$</p><h2 id="">代码实现</h2><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">/**
 * Floyd-Warshall 算法实现
 * @param {number[][]} graph - 邻接矩阵 (INF 表示无边)
 * @returns {number[][]} - 包含所有点对最短路径的矩阵
 */
function floydWarshall(graph) {
    const n = graph.length
    // 拷贝一份矩阵，避免修改原数据
    const dist = Array.from({ length: n }, (_, i) =&gt; [...graph[i]])

    // 核心：依次尝试每一个点作为中间点 k
    for (let k = 0; k &lt; n; k++) {
        for (let i = 0; i &lt; n; i++) {
            for (let j = 0; j &lt; n; j++) {
                // 如果路径 i -&gt; k 和 k -&gt; j 都存在
                if (dist[i][k] !== Infinity &amp;&amp; dist[k][j] !== Infinity) {
                    // 更新 i -&gt; j 的最短路径
                    if (dist[i][k] + dist[k][j] &lt; dist[i][j]) {
                        dist[i][j] = dist[i][k] + dist[k][j]
                    }
                }
            }
        }
    }

    return dist
}

// --- 测试示例 ---
const INF = Infinity
const graph = [
    [0, 3, INF, 7],
    [8, 0, 2, INF],
    [5, INF, 0, 1],
    [2, INF, INF, 0],
]

const shortestPaths = floydWarshall(graph)

console.log(&#x27;所有点对之间的最短路径矩阵：&#x27;)
console.table(shortestPaths)

// 验证结果示例：
// 从 0 到 2 的路径：0 -&gt; 1 -&gt; 2 (权重 3 + 2 = 5)
// 从 0 到 3 的路径：0 -&gt; 1 -&gt; 2 -&gt; 3 (权重 3 + 2 + 1 = 6)
</code></pre>
<h2 id="leetcode-">LeetCode 题目案例</h2><ol start="1"><li><p>案例一：最短路径的应用
<strong>题目：<a href="https://leetcode.cn/problems/find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance/">1334. 阈值距离内邻居最少的城市</a></strong></p><p><strong>题目描述</strong>：有 $n$ 个城市，按从 $0$ 到 $n-1$ 编号。给你一个数组 <code>edges</code>，其中 <code>edges[i] = [from_i, to_i, weight_i]</code> 表示城市之间的双向加权边。最后给定一个 <code>distanceThreshold</code>。请你找到一个城市，其在阈值距离内的邻居数量最少。如果有多个这样的城市，则返回编号最大的那个。</p><p><strong>解析</strong>：我们需要知道<strong>任意两点</strong>之间的最短距离，然后统计每个城市在阈值范围内的城市数量。</p><p><strong>JS 实现</strong>：</p><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">var findTheCity = function(n, edges, distanceThreshold) {
  // 1. 初始化距离矩阵
  const dist = Array.from({ length: n }, () =&gt; Array(n).fill(Infinity));
  for (let i = 0; i &lt; n; i++) dist[i][i] = 0;
  for (const [u, v, w] of edges) {
      dist[u][v] = dist[v][u] = w;
  }

  // 2. Floyd 核心三层循环
  for (let k = 0; k &lt; n; k++) {
      for (let i = 0; i &lt; n; i++) {
          for (let j = 0; j &lt; n; j++) {
              if (dist[i][k] + dist[k][j] &lt; dist[i][j]) {
                  dist[i][j] = dist[i][k] + dist[k][j];
              }
          }
      }
  }

  // 3. 统计并寻找结果
  let minCount = Infinity;
  let res = -1;
  for (let i = 0; i &lt; n; i++) {
      let count = 0;
      for (let j = 0; j &lt; n; j++) {
          if (i !== j &amp;&amp; dist[i][j] &lt;= distanceThreshold) {
              count++;
          }
      }
      if (count &lt;= minCount) {
          minCount = count;
          res = i; // 因为 i 是递增的，这里自动满足“编号最大”的要求
      }
  }
  return res;
};
</code></pre>
</li><li><p>案例二：传递闭包（连通性）的应用
<strong>题目：<a href="https://leetcode.cn/problems/course-schedule-iv/">1462. 课程表 IV</a></strong></p><p><strong>题目描述</strong>：你总共需要上 $numCourses$ 门课。给你一个数组 <code>prerequisites</code>，其中 <code>prerequisites[i] = [a, b]</code> 表示在修课 <code>b</code> 之前必须先修课 <code>a</code>（直接先修）。再给定 <code>queries</code>，判断 <code>queries[j] = [u, v]</code> 中 $u$ 是否是 $v$ 的先修课（包括间接先修）。</p><p><strong>解析</strong>： 这道题不涉及具体的“权重”，而是考察<strong>连通性</strong>。如果 $a \to k$ 连通且 $k \to b$ 连通，那么 $a \to b$ 就连通。这就是 Floyd 算法在处理“传递闭包”时的变体。</p><p><strong>JS 实现</strong>：</p><pre class="language-javascript lang-javascript"><code class="language-javascript lang-javascript">var checkIfPrerequisite = function(numCourses, prerequisites, queries) {
  // 1. 初始化布尔矩阵，表示是否连通
  const isPre = Array.from({ length: numCourses }, () =&gt; Array(numCourses).fill(false));
  for (const [a, b] of prerequisites) {
      isPre[a][b] = true;
  }

  // 2. Floyd 变体：逻辑判断
  for (let k = 0; k &lt; numCourses; k++) {
      for (let i = 0; i &lt; numCourses; i++) {
          for (let j = 0; j &lt; numCourses; j++) {
              // 如果 i 是 k 的先修课，且 k 是 j 的先修课，则 i 也是 j 的先修课
              if (isPre[i][k] &amp;&amp; isPre[k][j]) {
                  isPre[i][j] = true;
              }
          }
      }
  }

  // 3. 直接查表返回结果
  return queries.map(([u, v]) =&gt; isPre[u][v]);
};
</code></pre>
</li></ol><h2 id="">应用场景</h2><ul><li><strong>中小规模图的全源最短路径</strong>：当 $V \le 500$ 时，Floyd 的代码极其简洁。</li><li><strong>传递闭包问题</strong>：如社交网络中的好友关系推导、课程依赖关系判定（如案例二）。</li><li><strong>图中是否存在负环</strong>：运行完 Floyd 后，检查是否存在 <code>dist[i][i] &lt; 0</code>。</li><li><strong>城市规划与网络路由</strong>：预计算任意两点间的通信延迟或运输成本。</li></ul><h2 id="">总结</h2><p>Floyd 算法虽然时间复杂度高达 $O(n^3)$，但其<strong>三层循环的结构</strong>异常优雅且易于实现。在 LeetCode 中，只要看到 $n$ 的范围在 100~400 之间，且需要判断多点之间的关系时，Floyd 往往是最优选择。</p></div><p style="text-align:right"><a href="https://www.superlucifer.cn/posts/algorithm/floyd#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://www.superlucifer.cn/posts/algorithm/floyd</link><guid isPermaLink="true">https://www.superlucifer.cn/posts/algorithm/floyd</guid><dc:creator><![CDATA[Lucifer]]></dc:creator><pubDate>Sat, 10 Jan 2026 07:02:45 GMT</pubDate></item><item><title><![CDATA[差分]]></title><description><![CDATA[<link rel="preload" as="image" href="https://www.superlucifer.cn/api/v2/objects/file/96ed53tjprrc5jith3.png"/><link rel="preload" as="image" href="https://www.superlucifer.cn/api/v2/objects/file/ksats59voglegfnwrq.png"/><div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://www.superlucifer.cn/posts/algorithm/difference">https://www.superlucifer.cn/posts/algorithm/difference</a></blockquote><div><p>差分是前缀和的逆运算，主要用于高效地对数组的某个区间进行批量增减操作。
差分的核心思想是通过差分数组/矩阵的端点操作，将复杂操作的时间复杂度从O(n)降至O(1)。</p><h2 id="">一维差分</h2><ul><li><p>实现原理</p><p>对于数组arr，构建一个差分数组，其中：</p><ul><li><code>diff[0] = arr[0]</code></li><li><code>diff[i] = arr[i] - arr[i-1]</code>，其中<code>i &gt;= 1</code></li></ul><p>通过差分数组，可以快速对数组某个区间进行增减操作：</p><ul><li>对区间[l, r]的每个元素增加val：<code>diff[l] += val</code>，<code>diff[r+1] -= val</code></li><li>对区间[l, r]的每个元素减少val：<code>diff[l] -= val</code>，<code>diff[r+1] += val</code></li></ul><p>然后通过前缀和运算可以还原出修改后的数组。</p></li><li><p>代码实现</p><pre class="language-js lang-js"><code class="language-js lang-js">// 构建差分数组
function buildDiffArray(arr) {
  const n = arr.length;
  const diff = new Array(n).fill(0);
  
  diff[0] = arr[0];
  for (let i = 1; i &lt; n; i++) {
    diff[i] = arr[i] - arr[i - 1];
  }
  
  return diff;
}

// 对区间 [l, r] 增加 val
function rangeAdd(diff, l, r, val) {
  diff[l] += val;
  if (r + 1 &lt; diff.length) {
    diff[r + 1] -= val;
  }
}

// 从差分数组还原原数组
function restoreArray(diff) {
  const n = diff.length;
  const arr = new Array(n);
  
  arr[0] = diff[0];
  for (let i = 1; i &lt; n; i++) {
    arr[i] = arr[i - 1] + diff[i];
  }
  
  return arr;
}

// 示例
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const diff = buildDiffArray(arr);

console.log(&quot;差分数组:&quot;, diff); // [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

// 对区间 [2, 5] 增加 2
rangeAdd(diff, 2, 5, 2); // [1, 1, 3, 1, 1, 1, -1, 1, 1, 1]
const modifiedArr = restoreArray(diff);

console.log(&quot;修改后的数组:&quot;, modifiedArr); // [1, 2, 5, 6, 7, 8, 7, 8, 9, 10]
</code></pre>
<p><img src="https://www.superlucifer.cn/api/v2/objects/file/96ed53tjprrc5jith3.png" alt="差分图解"/></p></li><li><p>复杂度分析</p><ul><li>时间复杂度
<ul><li>预处理：<code>O(n)</code></li><li>查询：<code>O(1)</code></li></ul></li><li>空间复杂度：<code>O(n)</code></li></ul></li></ul><h2 id="">二维差分</h2><p>M-原始数组, D-差分数组</p><ul><li><p>预处理：<code>D[i][j] = M[i][j] - M[i-1][j] - M[i][j-1] + M[i-1][j-1]</code></p><p><img src="https://www.superlucifer.cn/api/v2/objects/file/ksats59voglegfnwrq.png" alt="二维差分矩阵图解"/></p></li><li><p>修改：对(r1,c1)到(r2,c2)区域增加value</p><ul><li><code>D[r1][c1] += value</code></li><li><code>D[r1][c2+1] -= value</code></li><li><code>D[r2+1][c1] -= value</code></li><li><code>D[r2+1][c2+1] += value</code></li></ul></li><li><p>代码实现</p></li></ul><pre class="language-js lang-js"><code class="language-js lang-js">  // 生成随机矩阵
  function generateRandomMatrix(size, maxValue) {
    const matrix = [];
    for (let i = 0; i &lt; size; i++) {
      const row = [];
      for (let j = 0; j &lt; size; j++) {
          row.push(Math.floor(Math.random() * maxValue) + 1);
      }
      matrix.push(row);
    }
    return matrix;
  }

  // 构建二维差分矩阵
  function buildDiffMatrix(matrix) {
    const n = matrix.length;
    const m = matrix[0].length;
    const diff = Array(n).fill().map(() =&gt; Array(m).fill(0));
    
    // 根据原始矩阵计算差分矩阵
    for (let i = 0; i &lt; n; i++) {
      for (let j = 0; j &lt; m; j++) {
        diff[i][j] = matrix[i][j];
        if (i &gt; 0) diff[i][j] -= matrix[i-1][j];
        if (j &gt; 0) diff[i][j] -= matrix[i][j-1];
        if (i &gt; 0 &amp;&amp; j &gt; 0) diff[i][j] += matrix[i-1][j-1];
      }
    }
    
    return diff;
  }

  // 从差分矩阵还原原始矩阵
  function restoreMatrixFromDiff(diff) {
    const n = diff.length;
    const m = diff[0].length;
    const matrix = Array(n).fill().map(() =&gt; Array(m).fill(0));
    
    for (let i = 0; i &lt; n; i++) {
      for (let j = 0; j &lt; m; j++) {
        matrix[i][j] = diff[i][j];
        if (i &gt; 0) matrix[i][j] += matrix[i-1][j];
        if (j &gt; 0) matrix[i][j] += matrix[i][j-1];
        if (i &gt; 0 &amp;&amp; j &gt; 0) matrix[i][j] -= matrix[i-1][j-1];
      }
    }
    
    return matrix;
  }

  const matrixSize = 5;
  const matrix = generateRandomMatrix(matrixSize, 10);
  console.log(&quot;原始矩阵:&quot;, matrix);

  const diffMatrix = buildDiffMatrix(matrix);
  console.log(&quot;差分矩阵:&quot;, diffMatrix);

  const r1 = 1, c1 = 1, r2 = 3, c2 = 3;
  const value = 5
  console.log(`对区域 (${r1},${c1}) 到 (${r2},${c2}) 增加: ${value}`);

  // 应用差分修改
  diffMatrix[r1][c1] += value;
  if (c2 + 1 &lt; matrixSize) {
      diffMatrix[r1][c2 + 1] -= value;
  }
  if (r2 + 1 &lt; matrixSize) {
      diffMatrix[r2 + 1][c1] -= value;
  }
  if (r2 + 1 &lt; matrixSize &amp;&amp; c2 + 1 &lt; matrixSize) {
      diffMatrix[r2 + 1][c2 + 1] += value;
  }

  const modifiedMatrix = restoreMatrixFromDiff(diffMatrix);
  console.log(&quot;修改后的矩阵:&quot;, modifiedMatrix);
</code></pre></div><p style="text-align:right"><a href="https://www.superlucifer.cn/posts/algorithm/difference#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://www.superlucifer.cn/posts/algorithm/difference</link><guid isPermaLink="true">https://www.superlucifer.cn/posts/algorithm/difference</guid><dc:creator><![CDATA[Lucifer]]></dc:creator><pubDate>Sat, 15 Nov 2025 01:30:03 GMT</pubDate></item><item><title><![CDATA[前缀和]]></title><description><![CDATA[<link rel="preload" as="image" href="https://www.superlucifer.cn/api/v2/objects/file/0zrbhl0znv80nw2a5o.png"/><link rel="preload" as="image" href="https://www.superlucifer.cn/api/v2/objects/file/k08n6pxp5xjfvejdnt.png"/><link rel="preload" as="image" href="https://www.superlucifer.cn/api/v2/objects/file/xo1d7f2hqfb2elzol9.png"/><div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://www.superlucifer.cn/posts/algorithm/prefix-sum">https://www.superlucifer.cn/posts/algorithm/prefix-sum</a></blockquote><div><p>前缀和是一种预处理技术，通过计算数组中每个位置前所有元素的和，可以快速求出任意区间的和。<br/>前缀和的核⼼思想是预处理，可以在暴⼒枚举的过程中，快速给出查询的结果，从⽽优化时间复杂度，是经典的⽤空间替换时间的做法。</p><h2 id="">一维前缀和</h2><ul><li><p>实现原理</p><p>对于数组 arr，构建一个前缀和数组 prefix，其中：</p><ul><li><code>prefix[0] = 0</code></li><li><code>prefix[i] = arr[0] + arr[1] + ... + arr[i-1]</code></li></ul></li><li><p>代码实现</p><pre class="language-js lang-js"><code class="language-js lang-js">// 构建前缀和数组
function buildPrefixSum(arr) {
  const n = arr.length;
  const prefix = new Array(n + 1).fill(0);
  
  for (let i = 0; i &lt; n; i++) {
    prefix[i + 1] = prefix[i] + arr[i];
  }
  
  return prefix;
}

// 查询区间和 [l, r]
function queryRangeSum(prefix, l, r) {
  return prefix[r + 1] - prefix[l];
}

// 示例
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const prefix = buildPrefixSum(arr);

console.log(&quot;前缀和数组：&quot;, prefix); // [0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
console.log(&quot;区间 [2, 5] 的和:&quot;, queryRangeSum(prefix, 2, 5)); // 21 - 3 = 3 + 4 + 5 + 6 = 18
</code></pre>
<p><img src="https://www.superlucifer.cn/api/v2/objects/file/0zrbhl0znv80nw2a5o.png" alt="前缀和图解"/></p></li><li><p>复杂度分析</p><ul><li>时间复杂度
<ul><li>预处理：<code>O(n)</code></li><li>查询：<code>O(1)</code></li></ul></li><li>空间复杂度：<code>O(n)</code></li></ul></li><li><p>算法优势</p><ul><li>暴力法：每次查询时间复杂度为<code>O(n)</code>，m 次查询为<code>O(mn)</code></li><li>前缀和法：预处理时间复杂度为<code>O(n)</code>，每次查询为<code>O(1)</code>，m 次查询为<code>O(m)</code>；需要额外的<code>O(n)</code>空间存储前缀和数组</li></ul></li></ul><h2 id="">二维前缀和</h2><p>M-原始数组，P-前缀和数组</p><ul><li><p>预处理：<code>P[i][j] = M[i-1][j-1] + P[i-1][j] + P[i][j-1] - P[i-1][j-1]</code></p><p><img src="https://www.superlucifer.cn/api/v2/objects/file/k08n6pxp5xjfvejdnt.png" alt="二维前缀和矩阵图解"/></p></li><li><p>查询：查询 (r1,c1) 到 (r2,c2) 区域和<code>sum = P[r2+1][c2+1] - P[r1][c2+1] - P[r2+1][c1] + P[r1][c1]</code></p><p><img src="https://www.superlucifer.cn/api/v2/objects/file/xo1d7f2hqfb2elzol9.png" alt="二维前缀和查询图解"/></p></li><li><p>代码实现</p></li></ul><pre class="language-js lang-js"><code class="language-js lang-js">  // 生成随机矩阵
  function generateRandomMatrix(size, maxValue) {
    const matrix = [];
    for (let i = 0; i &lt; size; i++) {
      const row = [];
      for (let j = 0; j &lt; size; j++) {
          row.push(Math.floor(Math.random() * maxValue) + 1);
      }
      matrix.push(row);
    }
    return matrix;
  }

  // 构建二维前缀和矩阵
  function buildPrefixMatrix(matrix) {
    const n = matrix.length;
    const m = matrix[0].length;
    const prefix = Array(n + 1).fill().map(() =&gt; Array(m + 1).fill(0));
    
    for (let i = 1; i &lt;= n; i++) {
        for (let j = 1; j &lt;= m; j++) {
            prefix[i][j] = matrix[i-1][j-1] + prefix[i-1][j] + prefix[i][j-1] - prefix[i-1][j-1];
        }
    }
    
    return prefix;
  }

  const matrix = generateRandomMatrix(5, 10);
  console.log(&quot;原始矩阵：&quot;, matrix);

  const prefixMatrix = buildPrefixMatrix(matrix);
  console.log(&quot;前缀和矩阵：&quot;, prefixMatrix);

  const r1 = 1, c1 = 1, r2 = 3, c2 = 3;
  const sum = prefixMatrix[r2+1][c2+1] - prefixMatrix[r1][c2+1] - prefixMatrix[r2+1][c1] + prefixMatrix[r1][c1];
  console.log(`区域 (${r1},${c1}) 到 (${r2},${c2}) 的和为：${sum}`);
</code></pre></div><p style="text-align:right"><a href="https://www.superlucifer.cn/posts/algorithm/prefix-sum#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://www.superlucifer.cn/posts/algorithm/prefix-sum</link><guid isPermaLink="true">https://www.superlucifer.cn/posts/algorithm/prefix-sum</guid><dc:creator><![CDATA[Lucifer]]></dc:creator><pubDate>Fri, 14 Nov 2025 15:41:15 GMT</pubDate></item><item><title><![CDATA[使用nginx部署vue项目]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://www.superlucifer.cn/posts/code/vue-deploy-nginx">https://www.superlucifer.cn/posts/code/vue-deploy-nginx</a></blockquote><div><p>通常有两种部署方式，根目录部署和二级目录部署。下面以<a href="http://doc.ruoyi.vip/ruoyi-vue/">若依管理系统</a>为例，分别介绍这两种部署方式。</p><h2 id="">🍎根目录部署</h2><p>这是比较常用的一种方式，部署后一般通过<code>ip+端口</code>访问，例如<code>http://localhost:8080</code>。
这种方式通常不需要额外的配置，只需要打包项目后放到服务器上，并参照如下<code>nginx</code>配置即可。</p><pre class="language-nginx lang-nginx"><code class="language-nginx lang-nginx">server {
  listen 8080;
  server_name localhost;

  location / {
    # 将path/web/替换为实际路径
    root path/web/;
    index index.html;
    try_files $uri $uri/ /index.html;
  }

  # 后端服务
  location /prod-api/ {
    proxy_pass http://localhost:3000/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}
</code></pre>
<h2 id="">🍍二级目录部署</h2><p>这种部署方式相较于根目录部署，访问时多了一层目录，例如<code>http://localhost:8080/admin</code>。主要适用于多项目部署到同一端口下的场景。</p><ul><li><p>代码配置</p><ul><li><p>vue2版本</p><ul><li>在<code>.env.development</code>中添加<code>VUE_APP_PUBLIC_PATH = /</code></li><li>在<code>.env.production</code>中添加<code>VUE_APP_PUBLIC_PATH = /admin/</code>，注意前后都要有<code>/</code></li><li><p>修改<code>vue.config.js</code></p><pre class="language-js lang-js"><code class="language-js lang-js">module.exports = {
  publicPath: process.env.VUE_APP_PUBLIC_PATH,
}
</code></pre>
</li><li><p>修改<code>router/index.js</code></p><pre class="language-js lang-js"><code class="language-js lang-js">export default new Router({
  base: process.env.VUE_APP_PUBLIC_PATH,
  mode: &#x27;history&#x27;, 
  scrollBehavior: () =&gt; ({ y: 0 }),
  routes: constantRoutes
})
</code></pre>
</li></ul></li><li><p>vue3版本</p><ul><li>在<code>.env.development</code>中添加<code>VITE_APP_PUBLIC_PATH = /</code></li><li>在<code>.env.production</code>中添加<code>VITE_APP_PUBLIC_PATH = /admin/</code>，注意前后都要有<code>/</code></li><li><p>修改<code>vite.config.js</code></p><pre class="language-js lang-js"><code class="language-js lang-js">...
export default defineConfig(({ mode, command }) =&gt; {
  const env = loadEnv(mode, process.cwd())
  const { VITE_APP_ENV, VITE_APP_PUBLIC_PATH } = env
  return {
    base: VITE_APP_PUBLIC_PATH,
    ...
  }
})
</code></pre>
</li><li><p>修改<code>router/index.js</code></p><pre class="language-js lang-js"><code class="language-js lang-js">const router = createRouter({
  history: createWebHistory(import.meta.env.VITE_APP_PUBLIC_PATH),
  ...
})
</code></pre>
</li></ul></li></ul></li><li><p><code>nginx</code>配置</p><pre class="language-nginx lang-nginx"><code class="language-nginx lang-nginx">server {
  listen 8080;
  server_name localhost;

  # &#x27;/admin/&#x27;也可以换成其他，如&#x27;/app/&#x27;等，只要与代码中的配置保持一致即可
  location /admin/ {
    # 将path/web/替换为实际路径（注意这里使用的是alias，而不是root）
    alias path/web/;
    index index.html;
    # index.html前添加的前缀，与location的&#x27;/admin/&#x27;保持一致
    try_files $uri $uri/ /admin/index.html;
  }

  # 重定向/admin到/admin/
  location = /admin {
    return 302 /admin/;
  }

  # 后端服务
  location /prod-api/ {
    proxy_pass http://localhost:3000/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}
</code></pre>
</li></ul></div><p style="text-align:right"><a href="https://www.superlucifer.cn/posts/code/vue-deploy-nginx#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://www.superlucifer.cn/posts/code/vue-deploy-nginx</link><guid isPermaLink="true">https://www.superlucifer.cn/posts/code/vue-deploy-nginx</guid><dc:creator><![CDATA[Lucifer]]></dc:creator><pubDate>Tue, 28 Oct 2025 14:41:19 GMT</pubDate></item><item><title><![CDATA[域名注册和使用教程]]></title><description><![CDATA[<link rel="preload" as="image" href="https://www.superlucifer.cn/api/v2/objects/file/v6o6wbdnbxlavtsz1r.png"/><link rel="preload" as="image" href="https://www.superlucifer.cn/api/v2/objects/file/dkrhhohtg1ageiab4v.png"/><link rel="preload" as="image" href="https://www.superlucifer.cn/api/v2/objects/file/4t25ebxavnkovykkee.png"/><link rel="preload" as="image" href="https://www.superlucifer.cn/api/v2/objects/file/z0ta1zf3srq1z2frer.png"/><link rel="preload" as="image" href="https://www.superlucifer.cn/api/v2/objects/file/tka7uf66nmp6v82tff.png"/><link rel="preload" as="image" href="https://www.superlucifer.cn/api/v2/objects/file/c4wjgt112z5cb8ig00.png"/><link rel="preload" as="image" href="https://www.superlucifer.cn/api/v2/objects/file/zidfpb7cwwplhjr6ry.png"/><div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://www.superlucifer.cn/posts/other/domain">https://www.superlucifer.cn/posts/other/domain</a></blockquote><div><h2 id="">📦准备</h2><ul><li>阿里云账号，如果没有，访问<a href="https://www.aliyun.com/">阿里云官网</a>，点击右上角注册；完成账号的实名认证并绑定支付宝账号，用于后续支付。</li><li>身份证照片，注册域名和ICP备案时需要上传证件。</li></ul><h2 id="">🔍注册域名</h2><ul><li>访问<a href="https://wanwang.aliyun.com/">阿里云（万网）</a></li><li>输入域名关键字，查询域名
<ul><li>命名尽量简洁，易记</li><li>避免使用数字和特殊字符</li><li>选择合适的后缀，如<code>.com</code>、<code>.cn</code>、<code>.net</code>等</li></ul></li><li>选择好之后，点击立即注册
<img src="https://www.superlucifer.cn/api/v2/objects/file/v6o6wbdnbxlavtsz1r.png" alt="搜索域名"/></li><li>选择注册年限，创建信息模板（首次操作时），点击购买
<img src="https://www.superlucifer.cn/api/v2/objects/file/dkrhhohtg1ageiab4v.png" alt="购买域名"/></li><li>购买成功后，可以返回域名控制台的域名列表查看</li></ul><h2 id="">⚙️域名解析</h2><p><img src="https://www.superlucifer.cn/api/v2/objects/file/4t25ebxavnkovykkee.png" alt="解析" height="692" width="2347"/></p><h3 id="">常见记录类型</h3><ul><li><strong>A记录</strong>：将域名指向IPv4地址</li><li><strong>CNAME</strong>：将域名指向另外一个域名</li><li><strong>MX记录</strong>：将域名指向邮件服务器地址</li><li><p><strong>TXT记录</strong>：文本验证信息</p><h3 id="">解析示例</h3></li></ul><pre class="language-dns lang-dns"><code class="language-dns lang-dns"># 将www.example.com解析到服务器IP：8.137.xx.xx
记录类型：A
主机记录：www
解析请求来源：默认
记录值：8.137.xx.xx
TTL：10分钟

# example.com解析到服务器IP：8.137.xx.xx
记录类型：A
主机记录：@
解析请求来源：默认
记录值：8.137.xx.xx
TTL：10分钟

# 将example.com解析到另一个域名：example.github.io
记录类型：CNAME
主机记录：@
解析请求来源：默认
记录值：example.github.io
TTL：10分钟
</code></pre>
<h2 id="">🚀通过域名访问网站</h2><p>假设现在有一个云服务器，公网IP为<code>8.137.xx.xx</code>，现在有两个域名<code>domain-a.com</code>和<code>domain-b.com</code>，需要通过域名分别访问该服务器上<code>8080</code>端口和<code>8081</code>端口部署的网站，可以按照以下步骤操作：</p><ul><li>添加<strong>A记录</strong>，将两个域名都解析到服务器IP<code>8.137.xx.xx</code></li><li><p><a href="https://www.superlucifer.cn/posts/other/cloud-server#%E8%BF%9E%E6%8E%A5">连接服务器</a>，在服务器上安装nginx</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash"># 安装nginx
sudo yum install -y nginx

# 启动nginx
systemctl start nginx

# 设置开机自启
systemctl enable nginx

# 重启nginx
systemctl reload nginx

</code></pre>
</li><li><p>申请ssl证书</p><ul><li>访问<a href="https://yundun.console.aliyun.com/?spm=5176.2020520154.console-base_product-drawer-right.dcas.1f88AJHQAJHQB4&amp;p=cas">数字证书管理服务</a></li><li>创建个人测试证书
<img src="https://www.superlucifer.cn/api/v2/objects/file/z0ta1zf3srq1z2frer.png" alt="创建ssl证书"/></li><li>选择证书，点击更多，点击下载
<img src="https://www.superlucifer.cn/api/v2/objects/file/tka7uf66nmp6v82tff.png" alt="下载ssl证书"/></li><li>解压下载的文件，将<code>.pem</code>和<code>.key</code>文件上传到服务器<code>/www/server/cert/</code>目录下</li></ul></li><li><p>添加nginx配置文件，将域名和端口映射到应用</p><ul><li>创建配置文件目录<code>/etc/nginx/conf.d</code></li><li>创建配置文件<code>domain-a.conf</code>和<code>domain-b.conf</code></li><li><p><code>domain-a.conf</code>文件配置</p><pre class="language-nginx lang-nginx"><code class="language-nginx lang-nginx">server {
  # 监听标准HTTP端口80
  listen 80;
  # 监听标准HTTPS端口443
  # listen 443 ssl http2 ;

  # 域名
  server_name domain-a.com;

  # 配置ssl证书
  ssl_certificate /www/server/cert/domain-a.com.pem;  # ssl证书路径，需替换为实际路径
  ssl_certificate_key /www/server/cert/domain-a.com.key;  # ssl私钥路径，需替换为实际路径

  # 访问日志（可选，便于排查问题）
  access_log /var/log/nginx/domain-a.access.log;

  location / {
    # 核心配置：将请求转发到本机8080端口的应用
    proxy_pass http://127.0.0.1:8080;

    # 以下配置确保正确传递原始请求的信息（如IP、协议、Host等）
    proxy_set_header Host $host;  # 保持 Host 头部
    proxy_set_header X-Real-IP $remote_addr;  # 获取真实 IP
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  # 传递代理链 IP
    proxy_set_header X-Forwarded-Proto $scheme;  # 传递协议

    # 一些优化配置（可选）
    proxy_buffering off;
    proxy_set_header Connection &#x27;&#x27;;
    proxy_http_version 1.1;
    chunked_transfer_encoding off;
  }
}
</code></pre>
</li><li><p><code>domain-b.conf</code>文件配置</p><pre class="language-nginx lang-nginx"><code class="language-nginx lang-nginx">server {
  # 监听标准HTTP端口80
  listen 80;
  # 监听标准HTTPS端口443
  # listen 443 ssl http2 ;

  # 域名
  server_name domain-b.com;

  # 访问日志（可选，便于排查问题）
  access_log /var/log/nginx/domain-b.access.log;

  # 配置ssl证书
  ssl_certificate /www/server/cert/domain-b.com.pem;  # ssl证书路径，需替换为实际路径
  ssl_certificate_key /www/server/cert/domain-b.com.key;  # ssl私钥路径，需替换为实际路径

  location / {
    # 核心配置：将请求转发到本机8081端口的应用
    proxy_pass http://127.0.0.1:8081;

    # 以下配置确保正确传递原始请求的信息（如IP、协议、Host等）
    proxy_set_header Host $host;  # 保持 Host 头部
    proxy_set_header X-Real-IP $remote_addr;  # 获取真实 IP
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  # 传递代理链 IP
    proxy_set_header X-Forwarded-Proto $scheme;  # 传递协议

    # 一些优化配置（可选）
    proxy_buffering off;
    proxy_set_header Connection &#x27;&#x27;;
    proxy_http_version 1.1;
    chunked_transfer_encoding off;
  }
}
</code></pre>
</li><li><p>在<code>/etc/nginx/nginx.conf</code>文件中添加以下配置</p><pre class="language-nginx lang-nginx"><code class="language-nginx lang-nginx">http {
  ...
  # 使nginx能够加载`/etc/nginx/conf.d/`目录下的所有配置文件
  include /etc/nginx/conf.d/*.conf;
  ...
}
</code></pre>
</li><li><p>重启nginx，输入域名即可访问网站</p></li></ul></li></ul><h2 id="icp">📋ICP备案</h2><p>参考文档：<a href="https://help.aliyun.com/zh/icp-filing/basic-icp-service/user-guide/icp-filing-application-overview?spm=a2c4g.11186623.0.i0">阿里云ICP备案流程</a>
域名解析到国内大陆的服务器后，需要备案，否则无法访问。备案流程如下：</p><ul><li>访问<a href="https://beian.aliyun.com/">ICP待备案管理系统</a></li><li>新增备案，选择<strong>自主备案</strong>
<img src="https://www.superlucifer.cn/api/v2/objects/file/c4wjgt112z5cb8ig00.png" alt="新增备案"/></li><li>填写基础信息（域名）</li><li>填写主办者信息（个人身份信息等）</li><li>填写互联网信息服务，这一步需要选择云产品实例，建议先提前购买好云服务器（注意：免费试用的ECS实例无法备案）
<img src="https://www.superlucifer.cn/api/v2/objects/file/zidfpb7cwwplhjr6ry.png" alt="互联网信息服务"/></li><li>上传身份证正反面照片，扫码进行人脸核验</li><li>提交审核。审核分为多个阶段，包括阿里云初审（1～2个工作日）、工信部短信核验（约5分钟收到短信，<strong>需在24小时内完成核验</strong>）以及管局最终审核（最长约20个工作日）。整体流程预计需要 3～22个工作日 左右，具体以实际操作时间为准。</li></ul></div><p style="text-align:right"><a href="https://www.superlucifer.cn/posts/other/domain#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://www.superlucifer.cn/posts/other/domain</link><guid isPermaLink="true">https://www.superlucifer.cn/posts/other/domain</guid><dc:creator><![CDATA[Lucifer]]></dc:creator><pubDate>Mon, 15 Sep 2025 13:15:00 GMT</pubDate></item><item><title><![CDATA[云服务器购买与连接教程]]></title><description><![CDATA[<link rel="preload" as="image" href="https://www.superlucifer.cn/api/v2/objects/file/86q9trhgiub7n0laq2.png"/><link rel="preload" as="image" href="https://www.superlucifer.cn/api/v2/objects/file/odswmc8voxw5wr09qk.png"/><link rel="preload" as="image" href="https://www.superlucifer.cn/api/v2/objects/file/ip3bbc5ryy35g8dr7a.png"/><link rel="preload" as="image" href="https://www.superlucifer.cn/api/v2/objects/file/m99d8f6vze1wa4w7jz.png"/><link rel="preload" as="image" href="https://www.superlucifer.cn/api/v2/objects/file/mkgb87v0k5sfdyex4g.png"/><link rel="preload" as="image" href="https://www.superlucifer.cn/api/v2/objects/file/35d09m7jsnx9w68n0b.png"/><div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://www.superlucifer.cn/posts/other/cloud-server">https://www.superlucifer.cn/posts/other/cloud-server</a></blockquote><div><blockquote><p>阿里云弹性计算服务（Elastic Compute Service，简称ECS）是阿里云提供的性能卓越、稳定可靠、弹性扩展的IaaS级别云计算服务。对于开发者、企业或个人来说，购买一台ECS服务器是搭建网站、应用、数据库或学习测试的第一步。</p></blockquote>
<h2 id="">📦准备</h2><ul><li>阿里云账号，如果没有，访问<a href="https://www.aliyun.com/">阿里云官网</a>，点击右上角注册；</li><li>完成账号的实名认证；</li><li>绑定支付宝账号。</li></ul><h2 id="">🛒购买</h2><ul><li>访问<a href="https://www.aliyun.com/product/ecs">云服务器ECS</a>；</li><li>选择合适的实例进行购买。首次使用或对服务器要求不高的，建议选择<strong>经济型e实例</strong>，目前阿里云有“99计划”活动，2核2G、3M固定带宽的服务器，基本可以满足个人使用需求；
<img src="https://www.superlucifer.cn/api/v2/objects/file/86q9trhgiub7n0laq2.png" alt="实例列表"/></li><li>购买时选择地域，建议选择离自己最近的区域，这样访问速度更快。其他配置采用默认设置；
<img src="https://www.superlucifer.cn/api/v2/objects/file/odswmc8voxw5wr09qk.png" alt="实例配置"/></li><li>立即购买，购买成功后，可以在控制台查看实例信息；</li><li>设置密码。
<img src="https://www.superlucifer.cn/api/v2/objects/file/ip3bbc5ryy35g8dr7a.png" alt="设置密码"/></li></ul><h2 id="">🔗连接</h2><ul><li><p>本次购买的云服务器为Linux系统，可使用SSH工具如FinalShell进行连接；
<img src="https://www.superlucifer.cn/api/v2/objects/file/m99d8f6vze1wa4w7jz.png" alt="FinalShell"/></p><ul><li>名称：自定义</li><li>主机：实例的<strong>公网IP地址</strong></li><li>端口：默认为<strong>22</strong></li><li>用户名：<strong>root</strong></li><li>密码：申请时设置的密码</li></ul></li><li><p>连接成功后，就可以开始使用啦</p></li></ul><h2 id="web">🚀部署web应用</h2><p>以<a href="http://doc.ruoyi.vip/ruoyi-vue/">若依管理系统</a>为例，具体步骤如下：</p><ul><li><p>打包项目</p><ul><li>本地git拉取代码</li><li><p>安装依赖</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">npm install
</code></pre>
</li><li><p>编译打包，得到dist文件</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">npm run build:prod
</code></pre>
</li></ul></li><li>连接服务器，将dist文件上传到服务器，例如<code>/var/ruoyi</code>目录下</li><li><p>在服务器上安装nginx</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash"># 安装nginx
sudo yum install -y nginx

# 启动nginx
systemctl start nginx

# 设置开机自启
systemctl enable nginx

# 重启nginx
systemctl reload nginx

</code></pre>
</li><li><p>创建配置文件目录<code>/etc/nginx/conf.d</code>，在<code>conf.d</code>目录下创建配置文件<code>web.conf</code>，配置内容如下：</p><pre class="language-nginx lang-nginx"><code class="language-nginx lang-nginx">server {
  # 监听8080端口
  listen 8080; 
  server_name localhost;
  client_max_body_size 50M;
  # charset koi8-r;

  location / {
    # dist文件所在目录
    root /var/ruoyi/dist;
    index index.html;
    try_files $uri $uri/ /index.html;
  }

  # 基础服务
  location /prod-api {
    # 代理到后端服务，这里直接用若依的服务地址，可替换成自己的服务
    proxy_pass http://vue.ruoyi.vip/prod-api;
    # Host $host:$server_port;
  }
}
</code></pre>
</li><li>启动nginx</li><li>创建并绑定<strong>安全组</strong>
<ul><li>登录<a href="https://ecs.console.aliyun.com/home">云服务器管理控制台</a></li><li>创建安全组，开放8080端口
<img src="https://www.superlucifer.cn/api/v2/objects/file/mkgb87v0k5sfdyex4g.png" alt="创建安全组"/></li><li>将安全组绑定到实例上
<img src="https://www.superlucifer.cn/api/v2/objects/file/35d09m7jsnx9w68n0b.png" alt="绑定安全组"/></li></ul></li><li>浏览器中输入<code>http://公网IP地址:8080</code>，即可访问刚刚部署的web应用</li></ul></div><p style="text-align:right"><a href="https://www.superlucifer.cn/posts/other/cloud-server#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://www.superlucifer.cn/posts/other/cloud-server</link><guid isPermaLink="true">https://www.superlucifer.cn/posts/other/cloud-server</guid><dc:creator><![CDATA[Lucifer]]></dc:creator><pubDate>Mon, 25 Aug 2025 12:05:03 GMT</pubDate></item><item><title><![CDATA[Git Bash的使用]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://www.superlucifer.cn/posts/other/git">https://www.superlucifer.cn/posts/other/git</a></blockquote><div><h2 id="">📌提交代码</h2><p>  在Github上新建一个仓库，得到仓库地址，在需要上传的代码目录里打开Git Bash</p><ul><li><p>初始化仓库</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">  # 初始化仓库
  git init -b main
  git remote add origin &lt;仓库地址&gt;
</code></pre>
</li><li><p>添加文件到暂存区</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">  # 添加所有变更到暂存区
  git add .
  # 添加特定文件
  git add &lt;文件名&gt;
  # 交互式添加   
  git add -p
</code></pre>
</li><li><p>提交变更</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">  git commit -m &quot;类型(范围): 描述信息&quot;
</code></pre>
</li><li><p>推送代码</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">  # 推送
  git push origin main
  # 首次推送设置上游
  git push -u origin main
</code></pre>
</li></ul><h2 id="">🔄更新代码</h2><pre class="language-bash lang-bash"><code class="language-bash lang-bash">  git pull origin main

  git status

  git add .

  git commit -m &quot;备注信息&quot;
  
  git push -u origin main
  </code></pre>
<h2 id="">🌱添加分支</h2><pre class="language-bash lang-bash"><code class="language-bash lang-bash">  # 以添加dev分支为例

  # 查看当前分支，分支前有*表示当前分支
  git branch -a

  # 创建新分支dev
  git branch dev

  # 推送dev分支
  git push origin dev:dev

  # 切换到dev分支
  git checkout dev
  </code></pre>
<h2 id="">🧩合并分支</h2><pre class="language-bash lang-bash"><code class="language-bash lang-bash">  # 以合并dev分支到main为例

  # 切换到main分支
  git checkout main

  # 拉取main分支最新代码
  git pull origin main

  # 合并dev分支
  git merge dev

  # 推送main分支
  git push origin main 

  # 切换回dev分支
  git checkout dev
  </code></pre>
<h2 id="">❌删除分支</h2><pre class="language-bash lang-bash"><code class="language-bash lang-bash">  # 以删除dev分支为例

  # 删除远程dev分支
  git push origin :dev

  # 切换到main分支
  git checkout main

  # 删除本地dev分支
  git branch -d dev

  </code></pre>
<h2 id="">✏️重命名分支</h2><pre class="language-bash lang-bash"><code class="language-bash lang-bash">  # 以dev改为dev-new为例

  # 切换到其他分支
  git checkout main

  # 重命名本地分支
  git branch -m dev dev-new

  # 删除远程的dev分支
  git push origin :dev

  # 推送新分支dev-new到远程
  git push origin dev-new

  # 设置上游关联
  git push --set-upstream origin dev-new
  </code></pre></div><p style="text-align:right"><a href="https://www.superlucifer.cn/posts/other/git#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://www.superlucifer.cn/posts/other/git</link><guid isPermaLink="true">https://www.superlucifer.cn/posts/other/git</guid><dc:creator><![CDATA[Lucifer]]></dc:creator><pubDate>Thu, 21 Aug 2025 12:03:18 GMT</pubDate></item><item><title><![CDATA[Shiroi主题部署]]></title><description><![CDATA[<link rel="preload" as="image" href="https://www.superlucifer.cn/api/v2/objects/file/ofpfcdv0ksqyyt2585.png"/><link rel="preload" as="image" href="https://www.superlucifer.cn/api/v2/objects/file/anqwfl006jhn1iqps9.png"/><div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://www.superlucifer.cn/posts/other/shiroi-deploy">https://www.superlucifer.cn/posts/other/shiroi-deploy</a></blockquote><div><p>Shiroi主题是 <a href="https://github.com/Innei/Shiro">Shiro</a> 的闭源版本，获取方式详见 <a href="https://innei.in/sponsor">赞助</a></p><h2 id="">🔍前提</h2><ul><li>拥有 <a href="https://github.com/innei-dev/Shiroi">Shiroi</a> 仓库访问权限</li><li>拥有一台云服务器，本文使用的是2核2G的阿里云ECS云服务器
<ul><li>如果是<strong>中国内地</strong>的服务器，记得先完成<strong>ICP备案</strong></li><li>免费试用的ECS服务器不满足可备案服务器要求</li></ul></li><li>拥有自己的域名</li><li>已安装并启动 Mix Space 后端
<ul><li><a href="https://mx-space.js.org/docs/core/docker">docker部署（推荐）</a></li><li><a href="https://mx-space.js.org/docs/core/extra#%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86">反向代理</a></li></ul></li><li>已<a href="https://mx-space.js.org/docs/themes/shiro/deploy#%E8%AE%BE%E7%BD%AE%E4%B8%BB%E9%A2%98%E9%85%8D%E7%BD%AE">设置主题配置</a></li></ul><h2 id="">📦准备</h2><ul><li><p>服务器需要先安装 Node.js, npm, pnpm, pm2, sharp</p><pre class=""><code class=""># 以阿里云ECS为例
  
# 安装node
sudo yum install -y nodejs
  
# 安装pnpm
npm install -g pnpm
  
# 安装pm2
npm install -g pm2
  
# pm2开机自启动
pm2 startup # 生成启动脚本
pm2 save # 保存当前进程列表

# 安装sharp
npm i -g sharp
</code></pre>
</li><li><p>fork项目<a href="https://github.com/innei-dev/shiroi-deploy-action">shiroi-deploy-action</a>到自己的github账号下</p></li><li><p>在shiroi-deploy-action仓库添加Responsitory secrets
<img src="https://www.superlucifer.cn/api/v2/objects/file/ofpfcdv0ksqyyt2585.png"/></p><ul><li><strong>HOST</strong>：服务器公网ip</li><li><strong>USER</strong>：服务器登录用户名，一般为root</li><li><strong>PASSWORD</strong>：服务器登录密码</li><li><strong>KEY</strong>：服务器 SSH Key（可选，密码 key 二选一）</li><li><strong>PORT</strong>：服务器 SSH 端口，一般为22</li><li><strong>GH_PAT</strong>：可访问 Shiroi 仓库的 Github Token，token设置路径：github头像下拉目录/Settings/Developer settings/Personal access tokens/Tokens(classic)
<img src="https://www.superlucifer.cn/api/v2/objects/file/anqwfl006jhn1iqps9.png"/></li></ul></li></ul><h2 id="">💡触发工作流</h2><ul><li><p>本地拉取先前fork的shiroi-deploy-action项目代码，在项目目录下打开git bash。</p></li><li><p>执行以下命令：</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">  # 生成新的哈希值，并写入build_bash文件
  echo &quot;$(git rev-parse --short HEAD)-$(date +%s)&quot; &gt; build_hash
  </code></pre>
</li><li><p>提交、推送代码，推送之后在shiroi-deploy-action仓库中的<strong>Actions</strong>可以看到执行中的工作流。</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">  git add build_hash
  # 注意提交信息固定为&quot;trigger-workflow&quot;，这样工作流才能正常运行完
  git commit -m &quot;trigger-workflow&quot;
  git push origin main
  </code></pre>
</li></ul><h2 id="">🚀启动前端</h2><ul><li><p>工作流执行完毕后，会在服务器的root下生成shiro目录，在shiro下添加.env文件，内容如下：</p><pre class="language-ini lang-ini"><code class="language-ini lang-ini">  NEXT_PUBLIC_API_URL=http://localhost:2333
  NEXT_PUBLIC_GATEWAY_URL=http://localhost:2333
   
  # 替换为自己的域名
  NEXT_PUBLIC_API_URL=https://example.com/api/v2
  NEXT_PUBLIC_GATEWAY_URL=https://example.com

  TMDB_API_KEY=
  GH_TOKEN=
  </code></pre>
</li><li><p>在shiro目录下，执行以下命令，看到启动成功就可以了。</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">  # 首次启动时执行，保证服务是启动在2323端口（与nginx配置保持一致）
  export PORT=2323
  node server.js
  </code></pre>
</li></ul><h2 id="">🛠️常见问题</h2><ul><li>访问前端时，页面上提示：“初始数据的获取失败，请检査 API服务器是否正常运行。”
<ul><li>是否已在Mix Space后台完成<a href="https://mx-space.js.org/docs/themes/shiro/deploy#%E8%AE%BE%E7%BD%AE%E4%B8%BB%E9%A2%98%E9%85%8D%E7%BD%AE">主题配置</a></li><li>如果是中国大陆的服务器，需进行ICP备案</li></ul></li><li>工作流未触发或提前终止
<ul><li>检查git提交信息是否为：trigger-workflow</li></ul></li></ul></div><p style="text-align:right"><a href="https://www.superlucifer.cn/posts/other/shiroi-deploy#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://www.superlucifer.cn/posts/other/shiroi-deploy</link><guid isPermaLink="true">https://www.superlucifer.cn/posts/other/shiroi-deploy</guid><dc:creator><![CDATA[Lucifer]]></dc:creator><pubDate>Fri, 25 Jul 2025 09:15:07 GMT</pubDate></item></channel></rss>