<!DOCTYPE html><html lang="zh" data-theme="dark"><head><meta charset="utf-8"><meta name="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1"><title>React + NestJS 购物平台练习【3】数据设计与实现 · Cytrogen 的个人博客</title><meta name="description" content="本文是 React + NestJS 全栈购物平台实践的第三篇,专注于后端数据库的设计与实现。文章从零开始,详细讲解了如何为购物平台定义核心实体(如用户、商品、订单),并使用 TypeORM 装饰器实现数据模型及其复杂的关联关系。接着,教程演示了如何配置和使用 TypeORM 的数据库迁移(Migration)功能,通过自定义脚本来自动化生成、应用和回滚数据库结构变更。最后,文章还探讨了数据库性能优化的关键——索引(Indexing),并为关键查询字段添加了索引。本教程为使用 NestJS 和 TypeORM 进行数据建模与管理提供了完整的实战指南。"><link rel="icon" href="../favicon.png"><link rel="canonical" href="https://cytrogen.icu/posts/5a4b.html"><link rel="webmention" href="https://webmention.io/cytrogen.icu/webmention"><link rel="me" href="https://m.otter.homes/@Cytrogen"><link rel="me" href="https://github.com/cytrogen"><meta name="fediverse:creator" content="@Cytrogen@m.otter.homes"><link rel="preload" href="../fonts/opensans-regular-latin.woff2" as="font" type="font/woff2" crossorigin="anonymous"><style>@font-face {
font-family: 'Open Sans';
src: url('../fonts/opensans-regular-latin.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
size-adjust: 107%;
ascent-override: 97%;
descent-override: 25%;
line-gap-override: 0%;
}
</style><script>(function() {
try {
// 优先级:用户选择 > 系统偏好 > 默认浅色
const saved = localStorage.getItem('theme');
const theme = saved ||
(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', theme);
document.documentElement.style.colorScheme = theme;
} catch (error) {
// 失败时使用默认主题,不阻塞渲染
document.documentElement.setAttribute('data-theme', 'light');
}
})();
</script><link rel="stylesheet" href="../css/ares.css"><script data-netlify-skip-bundle="true">(function() {
document.addEventListener('DOMContentLoaded', function() {
const theme = document.documentElement.getAttribute('data-theme');
const pageWrapper = document.getElementById('page-wrapper');
if (pageWrapper && theme) {
pageWrapper.setAttribute('data-theme', theme);
}
});
})();
</script><!-- hexo injector head_end start -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/hexo-math@4.0.0/dist/style.css">
<!-- hexo injector head_end end --><meta name="generator" content="Hexo 8.1.1"><link rel="alternate" href="atom.xml" title="Cytrogen 的个人博客" type="application/atom+xml">
</head><body><div id="page-wrapper"><a class="skip-link" href="#main-content">跳到主要内容</a><div class="wrap"><header><a class="logo-link" href="../index.html"><img src="../favicon.png" alt="logo"></a><div class="h-card visually-hidden"><img class="u-photo" src="https://cytrogen.icu/favicon.png" alt="Cytrogen"><a class="p-name u-url u-uid" href="https://cytrogen.icu">Cytrogen</a><p class="p-note">Cytrogen 的个人博客,Cytrogen's Blog</p><a class="u-url" rel="me noopener" target="_blank" href="https://m.otter.homes/@Cytrogen">Mastodon</a><a class="u-url" rel="me noopener" target="_blank" href="https://github.com/cytrogen">GitHub</a></div><nav class="site-nav"><div class="nav-main"><div class="nav-primary"><ul class="nav-list hidden-mobile"><li class="nav-item"><a class="nav-link" href="../index.html">首页</a></li></ul><div class="nav-tools"><div class="language-menu"><button class="language-toggle" type="button"><svg class="icon icon-globe" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 1.855A7.97 7.97 0 0 0 5.145 4H7.5V1.077zM4.09 4a9.267 9.267 0 0 1 .64-1.539 6.7 6.7 0 0 1 .597-.933A7.025 7.025 0 0 0 2.255 4H4.09zm-.582 3.5c.03-.877.138-1.718.312-2.5H1.674a6.958 6.958 0 0 0-.656 2.5h2.49zM4.847 5a12.5 12.5 0 0 0-.338 2.5H7.5V5H4.847zM8.5 5v2.5h2.99a12.495 12.495 0 0 0-.337-2.5H8.5zM4.51 8.5a12.5 12.5 0 0 0 .337 2.5H7.5V8.5H4.51zm3.99 0V11h2.653c.187-.765.306-1.608.338-2.5H8.5zM5.145 12c.138.386.295.744.468 1.068.552 1.035 1.218 1.65 1.887 1.855V12H5.145zm.182 2.472a6.696 6.696 0 0 1-.597-.933A9.268 9.268 0 0 1 4.09 12H2.255a7.024 7.024 0 0 0 3.072 2.472zM3.82 11a13.652 13.652 0 0 1-.312-2.5h-2.49c.062.89.291 1.733.656 2.5H3.82zm6.853 3.472A7.024 7.024 0 0 0 13.745 12H11.91a9.27 9.27 0 0 1-.64 1.539 6.688 6.688 0 0 1-.597.933zM8.5 12v2.923c.67-.204 1.335-.82 1.887-1.855A7.97 7.97 0 0 0 10.855 12H8.5zm3.68-1h2.146c.365-.767.594-1.61.656-2.5h-2.49a13.65 13.65 0 0 1-.312 2.5zm2.802-3.5a6.959 6.959 0 0 0-.656-2.5H12.18c.174.782.282 1.623.312 2.5h2.49zM11.27 2.461c.247.464.462.98.64 1.539h1.835a7.024 7.024 0 0 0-3.072-2.472c.218.284.418.598.597.933zM10.855 4a7.966 7.966 0 0 0-.468-1.068C9.835 1.897 9.17 1.282 8.5 1.077V4h2.355z"></path></svg><span>中文</span></button><div class="language-dropdown"></div></div></div><div class="nav-controls"><div class="more-menu hidden-mobile"><button class="more-toggle" type="button"><span>更多</span><svg class="icon icon-chevron-down" width="12" height="12" viewBox="0 0 12 12" fill="currentColor" aria-hidden="true" focusable="false"><path d="M6 8.825c-.2 0-.4-.1-.5-.2l-3.3-3.3c-.3-.3-.3-.8 0-1.1s.8-.3 1.1 0l2.7 2.7 2.7-2.7c.3-.3.8-.3 1.1 0s.3.8 0 1.1l-3.3 3.3c-.1.1-.3.2-.5.2z"></path></svg></button><div class="more-dropdown"><ul class="dropdown-list"><li class="dropdown-item"><a class="nav-link" href="../archives/index.html">归档</a></li><li class="dropdown-item"><a class="nav-link" href="../categories/index.html">分类</a></li><li class="dropdown-item"><a class="nav-link" href="../tags/index.html">标签</a></li><li class="dropdown-item"><a class="nav-link" href="../about/index.html">关于</a></li><li class="dropdown-item"><a class="nav-link" href="../sitemap/index.html">领地地图</a></li></ul></div></div><div class="theme-switcher"><button class="theme-toggle" type="button" role="switch" aria-pressed="false" aria-label="切换主题"><div class="theme-icon moon-icon"><svg class="icon icon-moon" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"></path></svg></div><div class="theme-icon sun-icon"><svg class="icon icon-sun" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"></path></svg></div></button></div><details class="mobile-menu-details hidden-desktop"><summary class="hamburger-menu" aria-label="nav.menu"><svg class="icon icon-bars" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5z"></path></svg><span class="menu-text">nav.menu</span></summary><div class="mobile-menu-dropdown"><ul class="mobile-nav-list"><li class="mobile-nav-item"><a class="mobile-nav-link" href="../index.html">首页</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../archives/index.html">归档</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../categories/index.html">分类</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../tags/index.html">标签</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../about/index.html">关于</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../sitemap/index.html">领地地图</a></li></ul></div></details></div></div></div></nav></header><main class="container" id="main-content" tabindex="-1"><div class="post"><article class="post-block h-entry"><div class="post-meta p-author h-card visually-hidden"><img class="author-avatar u-photo" src="../favicon.png" alt="Cytrogen"><span class="p-name">Cytrogen</span><a class="u-url" href="https://cytrogen.icu">https://cytrogen.icu</a></div><a class="post-permalink u-url u-uid visually-hidden" href="https://cytrogen.icu/posts/5a4b.html">永久链接</a><div class="p-summary visually-hidden"><p>搭建完前端与后端的基础结构后,我们就可以开始着手于数据库设计了。</p></div><div class="visually-hidden"><a class="p-category" href="../categories/%E7%BC%96%E7%A8%8B%E7%AC%94%E8%AE%B0/">编程笔记</a><a class="p-category" href="../tags/%E5%85%A8%E6%A0%88%E5%AE%9E%E8%B7%B5/">全栈实践</a><a class="p-category" href="../tags/Node-js/">Node.js</a><a class="p-category" href="../tags/React-js/">React.js</a><a class="p-category" href="../tags/TypeScript/">TypeScript</a><a class="p-category" href="../tags/NestJS/">NestJS</a></div><h1 class="post-title p-name">React + NestJS 购物平台练习【3】数据设计与实现</h1><div class="post-info"><time class="post-date dt-published" datetime="2024-11-07T05:00:00.000Z">11/7/2024</time><time class="dt-updated visually-hidden" datetime="2026-02-09T17:16:54.997Z"></time></div><div class="post-content e-content"><html><head></head><body><p>搭建完前端与后端的基础结构后,我们就可以开始着手于数据库设计了。</p>
<span id="more"></span>
<h1 id="1-设计基础实体"><a class="markdownIt-Anchor" href="#1-设计基础实体"></a> 1. 设计基础实体</h1>
<p>在设计我们的购物平台的过程中,实体及其关系是核心的数据模型。我们从业务逻辑的角度来看,每个实体的意义和作用,这样可以更好地理解其在系统中的角色。</p>
<p>这里,我们以「用户」、「商品」、「订单」等为主线,讲解如何建立这些实体以及它们之间的关系,并举例说明其在业务场景中的应用。</p>
<h2 id="11-用户"><a class="markdownIt-Anchor" href="#11-用户"></a> 1.1. 用户</h2>
<p>用户实体是系统的核心,因为大部分操作都需要绑定到用户。每个用户都有其唯一的 ID、用户名、邮箱和密码,这些信息用于身份验证和授权。</p>
<blockquote>
<p>示例,一个用户 <code>John Doe</code> 创建了账号,并通过邮箱 <code>johndoe@example.com</code> 登录系统。数据库会在 <code>Users</code> 表中新增一条记录,保存 John 的邮箱、加密密码和创建时间。</p>
</blockquote>
<h4 id="111-代码实现分析"><a class="markdownIt-Anchor" href="#111-代码实现分析"></a> 1.1.1. 代码实现分析</h4>
<p>让我们深入分析 <code>Users</code> 实体的代码实现:</p>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> {</span><br><span class="line"> <span class="title class_">Entity</span>,</span><br><span class="line"> <span class="title class_">PrimaryGeneratedColumn</span>,</span><br><span class="line"> <span class="title class_">Column</span>,</span><br><span class="line"> <span class="title class_">CreateDateColumn</span>,</span><br><span class="line"> <span class="title class_">UpdateDateColumn</span>,</span><br><span class="line"> <span class="title class_">OneToMany</span>,</span><br><span class="line"> <span class="title class_">OneToOne</span></span><br><span class="line">} <span class="keyword">from</span> <span class="string">'typeorm'</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Entity</span>()</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">Users</span> {</span><br><span class="line"> <span class="meta">@PrimaryGeneratedColumn</span>(<span class="string">'uuid'</span>)</span><br><span class="line"> <span class="attr">id</span>: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Column</span>({ <span class="attr">unique</span>: <span class="literal">true</span> })</span><br><span class="line"> <span class="attr">username</span>: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Column</span>({ <span class="attr">unique</span>: <span class="literal">true</span> })</span><br><span class="line"> <span class="attr">email</span>: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Column</span>()</span><br><span class="line"> <span class="attr">password</span>: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@CreateDateColumn</span>()</span><br><span class="line"> <span class="attr">created_at</span>: <span class="title class_">Date</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@UpdateDateColumn</span>()</span><br><span class="line"> <span class="attr">updated_at</span>: <span class="title class_">Date</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<p>这部分使用了 TypeORM 的装饰器来定义表结构:</p>
<ul>
<li><code>@Entity()</code> 装饰器将这个类标记为一个数据库实体</li>
<li><code>@PrimaryGeneratedColumn('uuid')</code> 表示自动生成 <code>UUID</code> 作为主键</li>
<li><code>@Column({ unique: true })</code> 为用户名和邮箱添加了唯一性约束,防止重复注册</li>
<li><code>@CreateDateColumn()</code> 会自动记录实体的创建时间</li>
<li><code>@UpdateDateColumn()</code> 会自动记录实体的更新时间</li>
</ul>
<p>用户表的字段及其业务含义如下:</p>
<ul>
<li><code>id</code>:主键,用于唯一标识每个用户,类型为 <code>UUID</code>,确保安全性和唯一性</li>
<li><code>username</code>:用户名,系统中用于显示的名称,通常在用户之间是唯一的</li>
<li><code>email</code>:用户的邮箱,通常作为主要联系手段,同时也是登录的标识之一</li>
<li><code>password</code>:用户密码,以加密方式存储,用于用户验证</li>
<li><code>created_at</code> 和 <code>updated_at</code>:创建时间和更新时间,记录用户注册和资料更新的时间</li>
</ul>
<h4 id="112-关联关系分析"><a class="markdownIt-Anchor" href="#112-关联关系分析"></a> 1.1.2. 关联关系分析</h4>
<p><code>Users</code> 实体与其他实体之间建立了多个关联关系:</p>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { <span class="title class_">Orders</span> } <span class="keyword">from</span> <span class="string">'./orders.entity'</span>;</span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">Carts</span> } <span class="keyword">from</span> <span class="string">'./carts.entity'</span>;</span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">Addresses</span> } <span class="keyword">from</span> <span class="string">'./addresses.entity'</span>;</span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">Payments</span> } <span class="keyword">from</span> <span class="string">'./payments.entity'</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Entity</span>()</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">Users</span> {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line"> <span class="meta">@OneToMany</span>(<span class="function">() =></span> <span class="title class_">Orders</span>, <span class="function"><span class="params">order</span> =></span> order.<span class="property">user</span>)</span><br><span class="line"> <span class="attr">orders</span>: <span class="title class_">Orders</span>[];</span><br><span class="line"></span><br><span class="line"> <span class="meta">@OneToOne</span>(<span class="function">() =></span> <span class="title class_">Carts</span>, <span class="function"><span class="params">cart</span> =></span> cart.<span class="property">user</span>)</span><br><span class="line"> <span class="attr">cart</span>: <span class="title class_">Carts</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@OneToMany</span>(<span class="function">() =></span> <span class="title class_">Addresses</span>, <span class="function"><span class="params">address</span> =></span> address.<span class="property">user</span>)</span><br><span class="line"> <span class="attr">addresses</span>: <span class="title class_">Addresses</span>[];</span><br><span class="line"></span><br><span class="line"> <span class="meta">@OneToMany</span>(<span class="function">() =></span> <span class="title class_">Payments</span>, <span class="function"><span class="params">payment</span> =></span> payment.<span class="property">user</span>)</span><br><span class="line"> <span class="attr">payments</span>: <span class="title class_">Payments</span>[];</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<p>这些关联展示了用户实体在系统中的核心地位:</p>
<ol>
<li>用户 - 订单关系(<code>@OneToMany</code>)
<ul>
<li>一个用户可以有多个订单</li>
<li>这种一对多的关系允许系统追踪用户的所有购买历史</li>
</ul>
</li>
<li>用户 - 购物车关系(<code>@OneToOne</code>)
<ul>
<li>每个用户只能有一个购物车</li>
<li>一对一的关系确保购物车数据的独立性和安全性</li>
</ul>
</li>
<li>用户 - 地址关系(<code>@OneToMany</code>)
<ul>
<li>用户可以保存多个收货地址</li>
<li>方便用户在下单时快速选择收货地址</li>
</ul>
</li>
<li>用户 - 支付关系(<code>@OneToMany</code>)
<ul>
<li>记录用户的所有支付记录</li>
<li>用于追踪交易历史和财务统计</li>
</ul>
</li>
</ol>
<h4 id="113-用户角色"><a class="markdownIt-Anchor" href="#113-用户角色"></a> 1.1.3. 用户角色</h4>
<p>在开发应用程序时,管理用户的权限和访问控制是至关重要的。这是因为不同类型的用户可能需要访问系统的不同功能。</p>
<p>例如,一个购物平台可能有以下几种角色:</p>
<ul>
<li>管理员:可以管理用户、查看所有订单、编辑商品等</li>
<li>普通用户:只能查看商品、下订单、查看个人资料等</li>
<li>游客:仅限浏览,不进行任何交互</li>
</ul>
<p>在这种场景下,我们需要为用户管理系统添加一个角色字段,以便区分不同的用户类型。</p>
<p>角色字段通常有助于完成以下任务:</p>
<ul>
<li>不同权限管理:每种角色都有不同的权限。管理员可能有权访问系统的所有数据,而普通用户只能查看他们自己的数据。通过角色字段,我们可以在数据库中存储每个用户的角色信息</li>
<li>角色控制的用户界面:角色字段可以帮助系统为不同角色的用户显示不同的页面或功能。例如,管理员可以访问管理控制台,而普通用户只能看到他们的订单历史</li>
<li>安全性增强:通过角色字段,系统可以在后端进行权限验证,确保不同角色的用户只能执行允许他们执行的操作,避免了恶意操作或权限滥用</li>
</ul>
<p>为了明确区分不同角色的值,通常我们会使用枚举(<code>enum</code>)来为角色提供固定的值。</p>
<blockquote>
<p><code>enum</code> 是一种特殊的类,用来表示一组固定的常量。</p>
</blockquote>
<p>通过将 <code>role</code> 字段设置为枚举类型,我们可以确保角色值仅限于我们定义的固定值,这些值会自动被 TypeORM 映射到数据库字段中。</p>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 添加角色的枚举类型</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">enum</span> <span class="title class_">UserRole</span> {</span><br><span class="line"> <span class="variable constant_">ADMIN</span> = <span class="string">'admin'</span>,</span><br><span class="line"> <span class="variable constant_">USER</span> = <span class="string">'user'</span>,</span><br><span class="line"> <span class="variable constant_">GUEST</span> = <span class="string">'guest'</span>,</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@Entity</span>()</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">Users</span> {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 在 Users 实体中添加 role 字段</span></span><br><span class="line"> <span class="meta">@Column</span>({</span><br><span class="line"> <span class="attr">type</span>: <span class="string">'enum'</span>,</span><br><span class="line"> <span class="attr">enum</span>: <span class="title class_">UserRole</span>,</span><br><span class="line"> <span class="attr">default</span>: <span class="title class_">UserRole</span>.<span class="property">USER</span>, <span class="comment">// 默认值为普通用户</span></span><br><span class="line"> })</span><br><span class="line"> <span class="attr">role</span>: <span class="title class_">UserRole</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<h2 id="12-商品"><a class="markdownIt-Anchor" href="#12-商品"></a> 1.2. 商品</h2>
<p>商品实体是平台中与交易直接相关的核心实体,它承载了商品的基本信息、库存管理、定价等重要功能。一个设计良好的商品实体不仅要满足基本的展示需求,还要支持库存管理、订单处理等复杂业务场景。</p>
<p>商品表的作用是提供系统中可供购买的商品清单,记录商品价格和库存。在库存管理方面,当库存减少到零时,商品需要标记为「缺货」或「下架」。</p>
<blockquote>
<p>示例,商家上架了一款新商品「智能手表」,库存数量为 100,售价为 200 元,商品类别为「电子产品」。这款商品的信息会存储在 <code>Products</code> 表中,供用户选择和购买。</p>
</blockquote>
<h4 id="121-代码实现分析"><a class="markdownIt-Anchor" href="#121-代码实现分析"></a> 1.2.1. 代码实现分析</h4>
<p>让我们详细分析 <code>Products</code> 实体的代码实现:</p>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> {</span><br><span class="line"> <span class="title class_">Entity</span>,</span><br><span class="line"> <span class="title class_">PrimaryGeneratedColumn</span>,</span><br><span class="line"> <span class="title class_">Column</span>,</span><br><span class="line"> <span class="title class_">CreateDateColumn</span>,</span><br><span class="line"> <span class="title class_">UpdateDateColumn</span>,</span><br><span class="line"> <span class="title class_">ManyToOne</span>,</span><br><span class="line"> <span class="title class_">OneToMany</span></span><br><span class="line">} <span class="keyword">from</span> <span class="string">'typeorm'</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Entity</span>()</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">Products</span> {</span><br><span class="line"> <span class="meta">@PrimaryGeneratedColumn</span>(<span class="string">'uuid'</span>)</span><br><span class="line"> <span class="attr">id</span>: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Column</span>()</span><br><span class="line"> <span class="attr">name</span>: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Column</span>(<span class="string">'text'</span>)</span><br><span class="line"> <span class="attr">description</span>: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Column</span>(<span class="string">'decimal'</span>)</span><br><span class="line"> <span class="attr">price</span>: <span class="built_in">number</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Column</span>()</span><br><span class="line"> <span class="attr">stock</span>: <span class="built_in">number</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@CreateDateColumn</span>()</span><br><span class="line"> <span class="attr">created_at</span>: <span class="title class_">Date</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@UpdateDateColumn</span>()</span><br><span class="line"> <span class="attr">updated_at</span>: <span class="title class_">Date</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<p>代码实现的特点:</p>
<ul>
<li>使用 <code>text</code> 类型存储描述,支持长文本内容</li>
<li>使用 <code>decimal</code> 类型存储价格,避免浮点数计算误差</li>
<li><code>stock</code> 字段直接使用普通的数值类型,便于进行库存相关的计算</li>
</ul>
<p>商品表的字段及其业务含义如下:</p>
<ul>
<li><code>id</code>:主键,用于唯一标识每个商品,类型为 <code>UUID</code></li>
<li><code>name</code>:商品名称,帮助用户识别商品</li>
<li><code>description</code>:商品描述,用于展示商品的详细信息</li>
<li><code>price</code>:商品价格,定义用户在购买该商品时的成本</li>
<li><code>stock</code>:商品库存数量,代表当前商品的剩余数量</li>
<li><code>created_at</code> 和 <code>updated_at</code>:创建时间和更新时间,记录商品上架和更新的时间</li>
</ul>
<h4 id="122-关联关系分析"><a class="markdownIt-Anchor" href="#122-关联关系分析"></a> 1.2.2. 关联关系分析</h4>
<p><code>Products</code> 实体与其他实体建立了多个关联关系:</p>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { <span class="title class_">Categories</span> } <span class="keyword">from</span> <span class="string">'./categories.entity'</span>;</span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">OrderItems</span> } <span class="keyword">from</span> <span class="string">'./order-items.entity'</span>;</span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">CartItems</span> } <span class="keyword">from</span> <span class="string">'./cart-items.entity'</span>;</span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">InventoryLogs</span> } <span class="keyword">from</span> <span class="string">'./inventory-logs.entity'</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Entity</span>()</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">Products</span> {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line"> <span class="meta">@ManyToOne</span>(<span class="function">() =></span> <span class="title class_">Categories</span>, <span class="function"><span class="params">category</span> =></span> category.<span class="property">products</span>)</span><br><span class="line"> <span class="attr">category</span>: <span class="title class_">Categories</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@OneToMany</span>(<span class="function">() =></span> <span class="title class_">OrderItems</span>, <span class="function"><span class="params">orderItem</span> =></span> orderItem.<span class="property">product</span>)</span><br><span class="line"> <span class="attr">orderItems</span>: <span class="title class_">OrderItems</span>[];</span><br><span class="line"></span><br><span class="line"> <span class="meta">@OneToMany</span>(<span class="function">() =></span> <span class="title class_">CartItems</span>, <span class="function"><span class="params">cartItem</span> =></span> cartItem.<span class="property">product</span>)</span><br><span class="line"> <span class="attr">cartItems</span>: <span class="title class_">CartItems</span>[];</span><br><span class="line"></span><br><span class="line"> <span class="meta">@OneToMany</span>(<span class="function">() =></span> <span class="title class_">InventoryLogs</span>, <span class="function"><span class="params">inventoryLog</span> =></span> inventoryLog.<span class="property">product</span>)</span><br><span class="line"> <span class="attr">inventoryLogs</span>: <span class="title class_">InventoryLogs</span>[];</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<ol>
<li>商品 - 类别关系(<code>@ManyToOne</code>)
<ul>
<li>一个商品属于一个类别</li>
<li>支持商品分类管理和分类检索</li>
<li>多对一的关系使得类别可以包含多个商品</li>
</ul>
</li>
<li>商品 - 订单项关系(<code>@OneToMany</code>)
<ul>
<li>一个商品可以出现在多个订单中</li>
<li>通过 <code>OrderItems</code> 中间表存储具体的购买数量和价格</li>
<li>支持订单历史查询和销售统计</li>
</ul>
</li>
<li>商品 - 购物车项关系(<code>@OneToMany</code>)
<ul>
<li>一个商品可以被加入多个用户的购物车</li>
<li>通过 <code>CartItems</code> 中间表记录购物车中的商品数量</li>
</ul>
</li>
<li>商品 - 库存日志关系(<code>@OneToMany</code>)
<ul>
<li>记录商品库存变动历史</li>
<li>支持库存追踪和审计</li>
</ul>
</li>
</ol>
<h2 id="13-类别"><a class="markdownIt-Anchor" href="#13-类别"></a> 1.3. 类别</h2>
<p>类别实体是商品分类的基础,它帮助我们组织和管理商品,提供更好的浏览和检索体验。一个良好的类别系统能够帮助用户更快地找到所需商品,同时也便于商家进行商品管理。</p>
<blockquote>
<p>当平台增加新类别「智能家居」时,它会被添加到 <code>Categories</code> 表中。用户在浏览智能家居产品时,只需点击此类别,即可看到所有相关商品。</p>
</blockquote>
<h4 id="131-代码实现分析"><a class="markdownIt-Anchor" href="#131-代码实现分析"></a> 1.3.1. 代码实现分析</h4>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { <span class="title class_">Entity</span>, <span class="title class_">PrimaryGeneratedColumn</span>, <span class="title class_">Column</span>, <span class="title class_">CreateDateColumn</span>, <span class="title class_">UpdateDateColumn</span>, <span class="title class_">OneToMany</span> } <span class="keyword">from</span> <span class="string">'typeorm'</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Entity</span>()</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">Categories</span> {</span><br><span class="line"> <span class="meta">@PrimaryGeneratedColumn</span>(<span class="string">'uuid'</span>)</span><br><span class="line"> <span class="attr">id</span>: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Column</span>()</span><br><span class="line"> <span class="attr">name</span>: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Column</span>(<span class="string">'text'</span>)</span><br><span class="line"> <span class="attr">description</span>: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@CreateDateColumn</span>()</span><br><span class="line"> <span class="attr">created_at</span>: <span class="title class_">Date</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@UpdateDateColumn</span>()</span><br><span class="line"> <span class="attr">updated_at</span>: <span class="title class_">Date</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<p>类别表的字段及其业务含义如下:</p>
<ul>
<li><code>id</code>:主键,唯一标识类别,类型为 <code>UUID</code></li>
<li><code>name</code>:类别名称,例如「电子产品」或「家具」</li>
<li><code>description</code>:类别描述,进一步解释类别内容</li>
<li><code>created_at</code> 和 <code>updated_at</code>:创建和更新时间,记录类别的管理信息</li>
</ul>
<h4 id="132-关联关系分析"><a class="markdownIt-Anchor" href="#132-关联关系分析"></a> 1.3.2. 关联关系分析</h4>
<p><code>Categories</code> 实体主要与 <code>Products</code> 实体建立了一对多的关联关系:</p>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { <span class="title class_">Products</span> } <span class="keyword">from</span> <span class="string">'./products.entity'</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Entity</span>()</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">Categories</span> {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line"> <span class="meta">@OneToMany</span>(<span class="function">() =></span> <span class="title class_">Products</span>, <span class="function"><span class="params">product</span> =></span> product.<span class="property">category</span>)</span><br><span class="line"> <span class="attr">products</span>: <span class="title class_">Products</span>[];</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<ol>
<li>类别 - 商品关系(<code>@OneToMany</code>)
<ul>
<li>一个类别可以包含多个商品</li>
<li>体现了分类组织的层次结构</li>
<li>支持按类别查询和统计商品</li>
</ul>
</li>
</ol>
<h2 id="14-订单"><a class="markdownIt-Anchor" href="#14-订单"></a> 1.4. 订单</h2>
<p>订单是电商系统中最核心的业务实体之一,它记录了交易的全过程,连接了用户、商品、支付等多个业务环节。一个完善的订单系统需要处理订单状态流转、支付流程、商品库存等复杂的业务场景。</p>
<blockquote>
<p>当用户下单购买一部智能手表,总价为 200 元,订单状态为「待支付」。在用户支付成功后,订单状态更新为「已支付」。</p>
</blockquote>
<h4 id="141-代码实现分析"><a class="markdownIt-Anchor" href="#141-代码实现分析"></a> 1.4.1. 代码实现分析</h4>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> {</span><br><span class="line"> <span class="title class_">Entity</span>,</span><br><span class="line"> <span class="title class_">PrimaryGeneratedColumn</span>,</span><br><span class="line"> <span class="title class_">Column</span>,</span><br><span class="line"> <span class="title class_">CreateDateColumn</span>,</span><br><span class="line"> <span class="title class_">UpdateDateColumn</span>,</span><br><span class="line"> <span class="title class_">ManyToOne</span>,</span><br><span class="line"> <span class="title class_">OneToMany</span></span><br><span class="line">} <span class="keyword">from</span> <span class="string">'typeorm'</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Entity</span>()</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">Orders</span> {</span><br><span class="line"> <span class="meta">@PrimaryGeneratedColumn</span>(<span class="string">'uuid'</span>)</span><br><span class="line"> <span class="attr">id</span>: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Column</span>(<span class="string">'decimal'</span>)</span><br><span class="line"> <span class="attr">total_price</span>: <span class="built_in">number</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Column</span>()</span><br><span class="line"> <span class="attr">status</span>: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@CreateDateColumn</span>()</span><br><span class="line"> <span class="attr">created_at</span>: <span class="title class_">Date</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@UpdateDateColumn</span>()</span><br><span class="line"> <span class="attr">updated_at</span>: <span class="title class_">Date</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<ul>
<li><code>id</code>:订单的唯一标识符,使用 <code>UUID</code> 类型</li>
<li><code>total_price</code>:订单总价,使用 <code>decimal</code> 类型确保精确计算</li>
<li><code>status</code>:订单状态,用于追踪订单的处理流程</li>
<li><code>created_at</code> 和 <code>updated_at</code>:记录订单的创建和更新时间</li>
</ul>
<h4 id="142-关联关系分析"><a class="markdownIt-Anchor" href="#142-关联关系分析"></a> 1.4.2. 关联关系分析</h4>
<p><code>Orders</code> 实体与其他实体建立了多个关联关系:</p>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { <span class="title class_">Users</span> } <span class="keyword">from</span> <span class="string">'./users.entity'</span>;</span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">OrderItems</span> } <span class="keyword">from</span> <span class="string">'./order-items.entity'</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Entity</span>()</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">Orders</span> {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line"> <span class="meta">@ManyToOne</span>(<span class="function">() =></span> <span class="title class_">Users</span>, <span class="function"><span class="params">user</span> =></span> user.<span class="property">orders</span>)</span><br><span class="line"> <span class="attr">user</span>: <span class="title class_">Users</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@OneToMany</span>(<span class="function">() =></span> <span class="title class_">OrderItems</span>, <span class="function"><span class="params">orderItem</span> =></span> orderItem.<span class="property">order</span>)</span><br><span class="line"> <span class="attr">orderItems</span>: <span class="title class_">OrderItems</span>[];</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<p>这些关联关系支持了完整的订单业务流程:</p>
<ol>
<li>订单 - 用户关系(<code>@ManyToOne</code>)
<ul>
<li>每个订单必须属于一个用户</li>
<li>支持用户订单历史查询</li>
<li>便于进行用户消费分析</li>
</ul>
</li>
<li>订单 - 订单项关系(<code>@OneToMany</code>)
<ul>
<li>一个订单可以包含多个商品</li>
<li>通过 <code>OrderItems</code> 存储具体的商品信息和数量</li>
<li>支持订单明细查询和统计</li>
</ul>
</li>
</ol>
<h2 id="15-订单项"><a class="markdownIt-Anchor" href="#15-订单项"></a> 1.5. 订单项</h2>
<p>订单项实体记录了订单中每个商品的具体购买信息,包括数量、单价和总价等。它不仅连接了订单和商品,还保存了购买时的价格快照,这对于订单历史记录和财务核算都很重要。</p>
<h4 id="151-代码实现分析"><a class="markdownIt-Anchor" href="#151-代码实现分析"></a> 1.5.1. 代码实现分析</h4>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { <span class="title class_">Entity</span>, <span class="title class_">PrimaryGeneratedColumn</span>, <span class="title class_">Column</span>, <span class="title class_">ManyToOne</span> } <span class="keyword">from</span> <span class="string">'typeorm'</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Entity</span>()</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">OrderItems</span> {</span><br><span class="line"> <span class="meta">@PrimaryGeneratedColumn</span>(<span class="string">'uuid'</span>)</span><br><span class="line"> <span class="attr">id</span>: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Column</span>()</span><br><span class="line"> <span class="attr">quantity</span>: <span class="built_in">number</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Column</span>(<span class="string">'decimal'</span>)</span><br><span class="line"> <span class="attr">price</span>: <span class="built_in">number</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Column</span>(<span class="string">'decimal'</span>)</span><br><span class="line"> <span class="attr">total_price</span>: <span class="built_in">number</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<ul>
<li><code>id</code>:订单项的唯一标识符,使用 <code>UUID</code> 类型</li>
<li><code>quantity</code>:购买数量</li>
<li><code>price</code>:商品单价的快照,使用 <code>decimal</code> 类型</li>
<li><code>total_price</code>:该项商品的总价,使用 <code>decimal</code> 类型</li>
</ul>
<h4 id="152-关联关系分析"><a class="markdownIt-Anchor" href="#152-关联关系分析"></a> 1.5.2. 关联关系分析</h4>
<p><code>OrderItems</code> 实体与其他实体建立了多个多对一的关联关系:</p>
<ol>
<li>
<p>订单项 - 订单关系(<code>@ManyToOne</code>)</p>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ManyToOne</span>(<span class="function">() =></span> <span class="title class_">Orders</span>, <span class="function"><span class="params">order</span> =></span> order.<span class="property">orderItems</span>)</span><br><span class="line"><span class="attr">order</span>: <span class="title class_">Orders</span>;</span><br></pre></td></tr></tbody></table></figure>
<ul>
<li>多个订单项属于同一个订单</li>
<li>通过这个关系可以获取订单的完整商品清单</li>
<li>支持订单总价计算和商品统计</li>
</ul>
</li>
<li>
<p>订单项 - 商品关系(<code>@ManyToOne</code>)</p>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ManyToOne</span>(<span class="function">() =></span> <span class="title class_">Products</span>, <span class="function"><span class="params">product</span> =></span> product.<span class="property">orderItems</span>)</span><br><span class="line"><span class="attr">product</span>: <span class="title class_">Products</span>;</span><br></pre></td></tr></tbody></table></figure>
<ul>
<li>记录该订单项对应的具体商品</li>
<li>支持商品销售统计和分析</li>
<li>可以追踪商品的购买历史</li>
</ul>
</li>
</ol>
<h2 id="16-购物车"><a class="markdownIt-Anchor" href="#16-购物车"></a> 1.6. 购物车</h2>
<p>每个用户都有一个购物车,用于暂存待购买的商品信息。</p>
<p>购物车实体记录了用户在线上选择和加入的商品,是下单前的临时存储。它与订单实体有着类似的结构,但服务于不同的业务场景。</p>
<h4 id="161-代码实现分析"><a class="markdownIt-Anchor" href="#161-代码实现分析"></a> 1.6.1. 代码实现分析</h4>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { <span class="title class_">Entity</span>, <span class="title class_">PrimaryGeneratedColumn</span>, <span class="title class_">CreateDateColumn</span>, <span class="title class_">UpdateDateColumn</span>, <span class="title class_">OneToOne</span>, <span class="title class_">OneToMany</span> } <span class="keyword">from</span> <span class="string">'typeorm'</span>;</span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">Users</span> } <span class="keyword">from</span> <span class="string">'./users.entity'</span>;</span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">CartItems</span> } <span class="keyword">from</span> <span class="string">'./cart-items.entity'</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Entity</span>()</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">Carts</span> {</span><br><span class="line"> <span class="meta">@PrimaryGeneratedColumn</span>(<span class="string">'uuid'</span>)</span><br><span class="line"> <span class="attr">id</span>: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@CreateDateColumn</span>()</span><br><span class="line"> <span class="attr">created_at</span>: <span class="title class_">Date</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@UpdateDateColumn</span>()</span><br><span class="line"> <span class="attr">updated_at</span>: <span class="title class_">Date</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<p>代码实现的特点:</p>
<ul>
<li>使用一对一关系与用户实体关联,确保每个用户只有一个购物车</li>
<li>包含基本的时间戳字段,跟踪购物车的创建和更新</li>
</ul>
<p>购物车表的字段及其业务含义如下:</p>
<ul>
<li><code>id</code>:购物车的唯一标识符,使用 <code>UUID</code> 类型</li>
<li><code>created_at</code> 和 <code>updated_at</code>:记录购物车的创建和更新时间</li>
</ul>
<h4 id="162-关联关系分析"><a class="markdownIt-Anchor" href="#162-关联关系分析"></a> 1.6.2. 关联关系分析</h4>
<p><code>Carts</code> 实体与其他实体建立了以下关联关系:</p>
<ol>
<li>
<p>购物车 - 用户关系(<code>@OneToOne</code>)</p>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@OneToOne</span>(<span class="function">() =></span> <span class="title class_">Users</span>, <span class="function"><span class="params">user</span> =></span> user.<span class="property">cart</span>)</span><br><span class="line"><span class="attr">user</span>: <span class="title class_">Users</span>;</span><br></pre></td></tr></tbody></table></figure>
</li>
<li>
<p>购物车 - 购物车项关系(<code>@OneToMany</code>)</p>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@OneToMany</span>(<span class="function">() =></span> <span class="title class_">CartItems</span>, <span class="function"><span class="params">cartItem</span> =></span> cartItem.<span class="property">cart</span>)</span><br><span class="line"><span class="attr">cartItems</span>: <span class="title class_">CartItems</span>[];</span><br></pre></td></tr></tbody></table></figure>
<ul>
<li>一个购物车可以包含多个购物车项</li>
<li>通过这个关系可以获取购物车中的商品列表</li>
</ul>
</li>
</ol>
<h2 id="17-购物车项"><a class="markdownIt-Anchor" href="#17-购物车项"></a> 1.7. 购物车项</h2>
<p>购物车项实体记录了用户在购物车中选择的商品及其数量,它与订单项实体有着类似的结构。</p>
<blockquote>
<p>用户在购物车中添加了两部智能手表,购物车项记录该商品 ID、数量为 2,以及关联的购物车 ID。</p>
</blockquote>
<h4 id="171-代码实现分析"><a class="markdownIt-Anchor" href="#171-代码实现分析"></a> 1.7.1. 代码实现分析</h4>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { <span class="title class_">Entity</span>, <span class="title class_">PrimaryGeneratedColumn</span>, <span class="title class_">Column</span>, <span class="title class_">ManyToOne</span> } <span class="keyword">from</span> <span class="string">'typeorm'</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Entity</span>()</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">CartItems</span> {</span><br><span class="line"> <span class="meta">@PrimaryGeneratedColumn</span>(<span class="string">'uuid'</span>)</span><br><span class="line"> <span class="attr">id</span>: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Column</span>()</span><br><span class="line"> <span class="attr">quantity</span>: <span class="built_in">number</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<p><code>CartItems</code> 实体的特点:</p>
<ul>
<li>使用多对一关系连接购物车和商品实体</li>
<li>记录了商品的购买数量,用于计算购物车总价</li>
</ul>
<h4 id="172-关联关系分析"><a class="markdownIt-Anchor" href="#172-关联关系分析"></a> 1.7.2. 关联关系分析</h4>
<ol>
<li>
<p>购物车项 - 购物车关系(<code>@ManyToOne</code>)</p>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ManyToOne</span>(<span class="function">() =></span> <span class="title class_">Carts</span>, <span class="function"><span class="params">cart</span> =></span> cart.<span class="property">cartItems</span>)</span><br><span class="line"><span class="attr">cart</span>: <span class="title class_">Carts</span>;</span><br></pre></td></tr></tbody></table></figure>
</li>
<li>
<p>购物车项 - 商品关系(<code>@ManyToOne</code>)</p>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ManyToOne</span>(<span class="function">() =></span> <span class="title class_">Products</span>, <span class="function"><span class="params">product</span> =></span> product.<span class="property">cartItems</span>)</span><br><span class="line"><span class="attr">product</span>: <span class="title class_">Products</span>;</span><br></pre></td></tr></tbody></table></figure>
</li>
</ol>
<h2 id="18-支付"><a class="markdownIt-Anchor" href="#18-支付"></a> 1.8. 支付</h2>
<p>支付实体记录了用户在完成下单后的支付信息,包括支付金额、支付状态等。它与订单实体有着直接的关联关系。</p>
<h4 id="181-代码实现分析"><a class="markdownIt-Anchor" href="#181-代码实现分析"></a> 1.8.1. 代码实现分析</h4>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { <span class="title class_">Entity</span>, <span class="title class_">PrimaryGeneratedColumn</span>, <span class="title class_">Column</span>, <span class="title class_">CreateDateColumn</span>, <span class="title class_">ManyToOne</span> } <span class="keyword">from</span> <span class="string">'typeorm'</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Entity</span>()</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">Payments</span> {</span><br><span class="line"> <span class="meta">@PrimaryGeneratedColumn</span>(<span class="string">'uuid'</span>)</span><br><span class="line"> <span class="attr">id</span>: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Column</span>(<span class="string">'decimal'</span>)</span><br><span class="line"> <span class="attr">amount</span>: <span class="built_in">number</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Column</span>()</span><br><span class="line"> <span class="attr">status</span>: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@CreateDateColumn</span>()</span><br><span class="line"> <span class="attr">created_at</span>: <span class="title class_">Date</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<p><code>Payments</code> 实体的特点:</p>
<ul>
<li>使用多对一关系连接订单和用户实体</li>
<li>记录了支付的金额和状态</li>
<li>包含支付的创建时间戳</li>
</ul>
<h4 id="182-关联关系分析"><a class="markdownIt-Anchor" href="#182-关联关系分析"></a> 1.8.2. 关联关系分析</h4>
<ol>
<li>
<p>支付 - 订单关系(<code>@ManyToOne</code>)</p>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ManyToOne</span>(<span class="function">() =></span> <span class="title class_">Orders</span>, <span class="function"><span class="params">order</span> =></span> order.<span class="property">id</span>)</span><br><span class="line"><span class="attr">order</span>: <span class="title class_">Orders</span>;</span><br></pre></td></tr></tbody></table></figure>
<ul>
<li>一笔订单可以有多个支付记录</li>
<li>通过这个关系可以获取支付所属的订单信息</li>
</ul>
</li>
<li>
<p>支付 - 用户关系(<code>@ManyToOne</code>)</p>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ManyToOne</span>(<span class="function">() =></span> <span class="title class_">Users</span>, <span class="function"><span class="params">user</span> =></span> user.<span class="property">payments</span>)</span><br><span class="line"><span class="attr">user</span>: <span class="title class_">Users</span>;</span><br></pre></td></tr></tbody></table></figure>
<ul>
<li>一个用户可以有多笔支付记录</li>
<li>通过这个关系可以获取支付所属的用户信息</li>
</ul>
</li>
</ol>
<h2 id="19-地址"><a class="markdownIt-Anchor" href="#19-地址"></a> 1.9. 地址</h2>
<p>地址实体记录了用户的收货地址信息,包括街道、城市、州 / 省、邮编和国家等详细信息。这些信息在用户下单时需要用到,也可以用于生成发货标签等。</p>
<h4 id="191-代码实现分析"><a class="markdownIt-Anchor" href="#191-代码实现分析"></a> 1.9.1. 代码实现分析</h4>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { <span class="title class_">Entity</span>, <span class="title class_">PrimaryGeneratedColumn</span>, <span class="title class_">Column</span>, <span class="title class_">CreateDateColumn</span>, <span class="title class_">UpdateDateColumn</span>, <span class="title class_">ManyToOne</span> } <span class="keyword">from</span> <span class="string">'typeorm'</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Entity</span>()</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">Addresses</span> {</span><br><span class="line"> <span class="meta">@PrimaryGeneratedColumn</span>(<span class="string">'uuid'</span>)</span><br><span class="line"> <span class="attr">id</span>: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Column</span>()</span><br><span class="line"> <span class="attr">street</span>: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Column</span>()</span><br><span class="line"> <span class="attr">city</span>: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Column</span>()</span><br><span class="line"> <span class="attr">state</span>: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Column</span>()</span><br><span class="line"> <span class="attr">postal_code</span>: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Column</span>()</span><br><span class="line"> <span class="attr">country</span>: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@CreateDateColumn</span>()</span><br><span class="line"> <span class="attr">created_at</span>: <span class="title class_">Date</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@UpdateDateColumn</span>()</span><br><span class="line"> <span class="attr">updated_at</span>: <span class="title class_">Date</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<h4 id="192-关联关系分析"><a class="markdownIt-Anchor" href="#192-关联关系分析"></a> 1.9.2. 关联关系分析</h4>
<ol>
<li>
<p>地址 - 用户关系(<code>@ManyToOne</code>)</p>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ManyToOne</span>(<span class="function">() =></span> <span class="title class_">Users</span>, <span class="function"><span class="params">user</span> =></span> user.<span class="property">addresses</span>)</span><br><span class="line"><span class="attr">user</span>: <span class="title class_">Users</span>; </span><br></pre></td></tr></tbody></table></figure>
<ul>
<li>一个用户可以有多个地址信息</li>
<li>通过这个关系可以获取地址所属的用户信息</li>
</ul>
</li>
</ol>
<h2 id="110-库存日志"><a class="markdownIt-Anchor" href="#110-库存日志"></a> 1.10. 库存日志</h2>
<p>库存日志实体记录了商品库存的变动历史,包括每次库存增减的数量和时间。这些记录对于库存管理、库存盘点和异常排查都非常重要。</p>
<h4 id="1101-代码实现分析"><a class="markdownIt-Anchor" href="#1101-代码实现分析"></a> 1.10.1. 代码实现分析</h4>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { <span class="title class_">Entity</span>, <span class="title class_">PrimaryGeneratedColumn</span>, <span class="title class_">Column</span>, <span class="title class_">CreateDateColumn</span>, <span class="title class_">ManyToOne</span> } <span class="keyword">from</span> <span class="string">'typeorm'</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Entity</span>()</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">InventoryLogs</span> {</span><br><span class="line"> <span class="meta">@PrimaryGeneratedColumn</span>(<span class="string">'uuid'</span>)</span><br><span class="line"> <span class="attr">id</span>: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Column</span>()</span><br><span class="line"> <span class="attr">change</span>: <span class="built_in">number</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@CreateDateColumn</span>()</span><br><span class="line"> <span class="attr">created_at</span>: <span class="title class_">Date</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<p><code>InventoryLogs</code> 实体的特点:</p>
<ul>
<li>使用多对一关系与商品实体关联</li>
<li>记录了库存变动数量(<code>change</code>),正数表示入库,负数表示出库</li>
<li>包含时间戳,记录每次库存变动的具体时间点</li>
</ul>
<h4 id="1102-关联关系分析"><a class="markdownIt-Anchor" href="#1102-关联关系分析"></a> 1.10.2. 关联关系分析</h4>
<ol>
<li>
<p>库存日志 - 商品关系(<code>@ManyToOne</code>)</p>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ManyToOne</span>(<span class="function">() =></span> <span class="title class_">Products</span>, <span class="function"><span class="params">product</span> =></span> product.<span class="property">inventoryLogs</span>)</span><br><span class="line"><span class="attr">product</span>: <span class="title class_">Products</span>;</span><br></pre></td></tr></tbody></table></figure>
<ul>
<li>一个商品可以有多条库存变动记录</li>
<li>通过这个关系可以追踪特定商品的库存变动历史</li>
</ul>
</li>
</ol>
<h1 id="2-创建数据库迁移文件"><a class="markdownIt-Anchor" href="#2-创建数据库迁移文件"></a> 2. 创建数据库迁移文件</h1>
<p>在开发应用程序时,数据库模式的更新是一项常见而重要的工作,尤其是在应用不断演进的过程中。</p>
<p>假设我们在开发一个应用,并需要更新数据库的表结构,比如添加新字段、修改字段类型等。如何在不丢失现有数据的情况下进行这些修改呢?</p>
<p>TypeORM 可以帮助我们轻松处理数据库操作。它内置了强大的迁移功能,使我们能够定义数据库结构变更并自动应用到数据库中。</p>
<h2 id="21-添加自定义命令"><a class="markdownIt-Anchor" href="#21-添加自定义命令"></a> 2.1. 添加自定义命令</h2>
<p>首先,我们在 <code>package.json</code> 中添加一些脚本来管理 TypeORM 的迁移操作。这些脚本将自动构建项目并运行相应的迁移命令,方便我们快速执行迁移操作:</p>
<figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"scripts"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> <span class="attr">"typeorm"</span><span class="punctuation">:</span> <span class="string">"typeorm-ts-node-commonjs -d src/config/data-source.ts"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"migration:initialize"</span><span class="punctuation">:</span> <span class="string">"yarn build && yarn typeorm migration:generate src/migrations/InitialMigration"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"migration:generate"</span><span class="punctuation">:</span> <span class="string">"yarn build && yarn typeorm migration:generate"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"migration:run"</span><span class="punctuation">:</span> <span class="string">"yarn build && yarn typeorm migration:run"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"migration:revert"</span><span class="punctuation">:</span> <span class="string">"yarn build && yarn typeorm migration:revert"</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></tbody></table></figure>
<p>让我们一行一行地分析这些脚本的作用:</p>
<ul>
<li><code>typeorm</code>:指定 TypeORM 的配置文件路径(<code>src/config/data-source.ts</code>),它是所有 TypeORM 操作的基础</li>
<li><code>migration:initialize</code>:生成 <code>InitialMigration</code> 迁移文件</li>
<li><code>migration:generate</code>:生成新的迁移文件
<ul>
<li>用法:<code>yarn migration:generate src/migrations/xxx</code></li>
</ul>
</li>
<li><code>migration:run</code>:执行所有还未运行的迁移,应用到数据库中</li>
<li><code>migration:revert</code>:撤销上一次迁移,通常用于回滚数据库结构到上一个状态</li>
</ul>
<h2 id="22-配置-typeorm-数据源"><a class="markdownIt-Anchor" href="#22-配置-typeorm-数据源"></a> 2.2. 配置 TypeORM 数据源</h2>
<p>接下来,我们需要设置 TypeORM 的数据库配置。</p>
<p>在项目的 <code>src/config/data-source.ts</code> 文件中,我们使用 <code>DataSourceOptions</code> 来定义数据库连接的详细信息。</p>
<p>这里我们还使用了 <code>@nestjs/config</code> 模块来动态读取环境变量,这样在不同环境中可以使用不同的数据库配置。</p>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> { <span class="title class_">ConfigService</span> } <span class="keyword">from</span> <span class="string">'@nestjs/config'</span>;</span><br><span class="line"><span class="keyword">import</span> { <span class="title class_">DataSource</span>, <span class="title class_">DataSourceOptions</span> } <span class="keyword">from</span> <span class="string">'typeorm'</span>;</span><br><span class="line"><span class="keyword">import</span> { config } <span class="keyword">from</span> <span class="string">'dotenv'</span>;</span><br><span class="line"></span><br><span class="line"><span class="title function_">config</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> configService = <span class="keyword">new</span> <span class="title class_">ConfigService</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">dataSourceOptions</span>: <span class="title class_">DataSourceOptions</span> = {</span><br><span class="line"> <span class="attr">type</span>: configService.<span class="property">get</span><<span class="built_in">any</span>>(<span class="string">'DB_TYPE'</span>, <span class="string">'mysql'</span>),</span><br><span class="line"> <span class="attr">host</span>: configService.<span class="property">get</span><<span class="built_in">string</span>>(<span class="string">'DB_HOST'</span>),</span><br><span class="line"> <span class="attr">port</span>: configService.<span class="property">get</span><<span class="built_in">number</span>>(<span class="string">'DB_PORT'</span>),</span><br><span class="line"> <span class="attr">username</span>: configService.<span class="property">get</span><<span class="built_in">string</span>>(<span class="string">'DB_USER'</span>),</span><br><span class="line"> <span class="attr">password</span>: configService.<span class="property">get</span><<span class="built_in">string</span>>(<span class="string">'DB_PASSWORD'</span>),</span><br><span class="line"> <span class="attr">database</span>: configService.<span class="property">get</span><<span class="built_in">string</span>>(<span class="string">'DB_NAME'</span>),</span><br><span class="line"> <span class="attr">synchronize</span>: configService.<span class="property">get</span><<span class="built_in">string</span>>(<span class="string">'APP_ENV'</span>) === <span class="string">'development'</span>,</span><br><span class="line"> <span class="attr">entities</span>: [<span class="string">'dist/entities/*.entity{.ts,.js}'</span>],</span><br><span class="line"> <span class="attr">migrations</span>: [<span class="string">'dist/migrations/*{.ts,.js}'</span>],</span><br><span class="line"> <span class="attr">subscribers</span>: []</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">new</span> <span class="title class_">DataSource</span>(dataSourceOptions);</span><br></pre></td></tr></tbody></table></figure>
<ul>
<li><code>type</code>:指定数据库类型,这里我们通过环境变量 <code>DB_TYPE</code> 来设置,默认为 MySQL</li>
<li><code>host</code>、<code>port</code>、<code>username</code>、<code>password</code>、<code>database</code>:这些参数是数据库连接的必要信息,都可以通过环境变量配置</li>
<li><code>synchronize</code>:如果 <code>APP_ENV</code> 为 <code>development</code>,则会启用同步模式,让 TypeORM 自动更新数据库表结构
<ul>
<li>注意:生产环境下建议禁用此项</li>
</ul>
</li>
<li><code>entities</code> 和 <code>migrations</code>:指定实体和迁移文件的路径。TypeORM 会使用这些路径找到相关文件</li>
</ul>
<h2 id="23-生成数据库迁移文件"><a class="markdownIt-Anchor" href="#23-生成数据库迁移文件"></a> 2.3. 生成数据库迁移文件</h2>
<p>当配置完成后,我们可以通过以下命令来生成用于初始化数据库的迁移文件:</p>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn migration:initialize</span><br></pre></td></tr></tbody></table></figure>
<p>这条命令会自动构建项目,并使用 TypeORM 生成一个迁移文件(位于 <code>src/migrations</code>,且默认命名为 <code>数字数字数字-InitialMigration.ts</code>)。</p>
<p>这个文件会包含创建、修改或删除表结构的 SQL 语句。例如,如果你添加了一个新的字段,生成的迁移文件就会包含一个 <code>ALTER TABLE</code> 语句。</p>
<h2 id="24-执行迁移"><a class="markdownIt-Anchor" href="#24-执行迁移"></a> 2.4. 执行迁移</h2>
<p>生成迁移文件后,使用以下命令将其应用到数据库:</p>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn migration:run</span><br></pre></td></tr></tbody></table></figure>
<p>此命令会检查并执行所有尚未在数据库中应用的迁移。这样可以确保你的数据库结构与最新的迁移文件一致。</p>
<h2 id="25-回滚迁移"><a class="markdownIt-Anchor" href="#25-回滚迁移"></a> 2.5. 回滚迁移</h2>
<p>在开发过程中,偶尔可能需要回滚上次迁移,例如在测试或调试时。使用以下命令可以撤销上一次迁移:</p>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn migration:revert</span><br></pre></td></tr></tbody></table></figure>
<p>TypeORM 会自动识别上次执行的迁移,并撤销相应的更改,恢复到之前的数据库结构。</p>
<h1 id="3-设置数据库索引"><a class="markdownIt-Anchor" href="#3-设置数据库索引"></a> 3. 设置数据库索引</h1>
<p>在开发应用程序时,尤其是当数据库中的数据量越来越大时,查询性能变得至关重要。</p>
<p>数据库索引是一种数据结构,它帮助数据库管理系统(DBMS)高效地检索数据。索引就像书籍中的目录,它可以快速定位到某个数据的位置,而不是扫描整个表。当我们对数据库表进行查询时,索引可以显著提高查询性能,尤其是在处理大量数据时。</p>
<p>在 MySQL 中,索引通常应用于那些需要频繁查询的字段。常见的索引类型有:</p>
<ul>
<li>主键索引:自动为表的主键字段创建</li>
<li>唯一索引:确保每个值唯一</li>
<li>普通索引:加速数据检索,但不强制唯一性</li>
<li>全文索引:用于加速文本内容的检索</li>
</ul>
<p>没有索引的情况下,数据库在执行查询时必须扫描整个表,逐行比较数据。这种方式在小型表中可能没什么问题,但在数据量较大时,会导致性能急剧下降。</p>
<blockquote>
<p>假设我们的购物平台,数据库中存储了数百万条订单记录。当用户搜索特定订单时,如果没有适当的索引,系统可能会扫描整个订单表来找到匹配的记录。随着数据量增加,查询速度变得非常慢,甚至可能导致系统响应延迟。</p>
<p>使用索引后,数据库不再需要扫描整个表,而是通过索引快速定位到目标数据,从而显著提升查询速度。</p>
</blockquote>
<p>在 NestJS 中,我们可以通过 TypeORM 为实体字段添加索引。TypeORM 提供了 <code>@Index()</code> 装饰器,允许我们在数据库表的字段上添加索引。</p>
<h2 id="31-用户"><a class="markdownIt-Anchor" href="#31-用户"></a> 3.1. 用户</h2>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Index</span>()</span><br><span class="line"><span class="meta">@CreateDateColumn</span>()</span><br><span class="line"><span class="attr">created_at</span>: <span class="title class_">Date</span>;</span><br></pre></td></tr></tbody></table></figure>
<p>在 <code>Users</code> 实体中,我们使用了 <code>@Index()</code> 装饰器来为 <code>created_at</code> 字段添加索引。</p>
<p>在大多数应用程序中,<code>created_at</code> 字段通常用于按照时间排序或进行时间范围查询。</p>
<blockquote>
<p>例如,用户可能需要查看某一时间段内的所有注册用户,或者获取最近创建的用户列表。</p>
</blockquote>
<p>假设在应用程序中,用户经常执行以下查询:</p>
<ul>
<li>查询某个时间段内注册的用户。</li>
<li>按照注册时间排序显示用户列表。</li>
</ul>
<p>如果没有为 <code>created_at</code> 字段添加索引,数据库会需要扫描整个用户表来执行这些查询,这样会导致性能问题。添加索引后,数据库可以通过索引快速查找和排序数据,显著提高查询速度,尤其是在数据量较大的情况下。</p>
<h4 id="311-关系字段为何不需要索引"><a class="markdownIt-Anchor" href="#311-关系字段为何不需要索引"></a> 3.1.1. 关系字段为何不需要索引?</h4>
<p>这时候就有人问了:「难道像 <code>orders</code>、<code>cart</code> 这些字段,用户就不会查询了吗?如果经常用到,为什么不为它们加个索引呢?」答案是:确实不需要。</p>
<p>在 TypeORM 中,关系字段(比如 <code>orders</code> 和 <code>cart</code>)通常是外键字段,数据库会自动为这些字段创建索引。例如,在 <code>Users</code> 实体中,<code>orders</code> 字段是通过 <code>@OneToMany</code> 与 <code>Orders</code> 实体关联的。虽然我们没有显式地为 <code>orders</code> 字段添加索引,但在 <code>Orders</code> 表中,<code>user</code> 字段作为外键会自动被索引。</p>
<p>这意味着,当我们查询某个用户的所有订单时,数据库会直接利用 <code>Orders</code> 表中自动创建的 <code>user</code> 索引,来加速查询。而我们不需要额外为 <code>orders</code> 字段添加索引,TypeORM 和数据库已经为我们处理好了这部分的优化。</p>
<h2 id="32-商品"><a class="markdownIt-Anchor" href="#32-商品"></a> 3.2. 商品</h2>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Index</span>()</span><br><span class="line"><span class="meta">@Column</span>(<span class="string">'decimal'</span>)</span><br><span class="line"><span class="attr">price</span>: <span class="built_in">number</span>;</span><br></pre></td></tr></tbody></table></figure>
<p><code>price</code> 字段通常会用于以下几种查询:</p>
<ul>
<li>按照价格范围查询产品,例如查找价格低于某个值的所有产品</li>
<li>按照价格排序,例如将产品列表按价格从低到高或从高到低排序</li>
</ul>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Index</span>()</span><br><span class="line"><span class="meta">@Column</span>()</span><br><span class="line"><span class="attr">stock</span>: <span class="built_in">number</span>;</span><br></pre></td></tr></tbody></table></figure>
<p><code>stock</code> 字段通常用于以下查询场景:</p>
<ul>
<li>查找库存数量为零或低于某个值的产品(例如「缺货」产品或「库存预警」产品)</li>
<li>按照库存数量排序,帮助商家快速查看库存最少的产品</li>
</ul>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Index</span>()</span><br><span class="line"><span class="meta">@CreateDateColumn</span>()</span><br><span class="line"><span class="attr">created_at</span>: <span class="title class_">Date</span>;</span><br></pre></td></tr></tbody></table></figure>
<p>所有的 <code>created_at</code> 字段都需要添加索引。</p>
<h2 id="33-支付"><a class="markdownIt-Anchor" href="#33-支付"></a> 3.3. 支付</h2>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Index</span>()</span><br><span class="line"><span class="meta">@CreateDateColumn</span>()</span><br><span class="line"><span class="attr">created_at</span>: <span class="title class_">Date</span>;</span><br></pre></td></tr></tbody></table></figure>
<h2 id="34-订单"><a class="markdownIt-Anchor" href="#34-订单"></a> 3.4. 订单</h2>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Index</span>()</span><br><span class="line"><span class="meta">@Column</span>()</span><br><span class="line"><span class="attr">status</span>: <span class="built_in">string</span>;</span><br></pre></td></tr></tbody></table></figure>
<p><code>status</code> 字段通常用于查询订单的状态,例如:</p>
<ul>
<li>查询某一状态的订单(如「待处理」、「已发货」、「已完成」等)</li>
<li>按照订单状态统计订单数量</li>
</ul>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Index</span>()</span><br><span class="line"><span class="meta">@CreateDateColumn</span>()</span><br><span class="line"><span class="attr">created_at</span>: <span class="title class_">Date</span>;</span><br></pre></td></tr></tbody></table></figure>
<h2 id="35-订单项"><a class="markdownIt-Anchor" href="#35-订单项"></a> 3.5. 订单项</h2>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Index</span>()</span><br><span class="line"><span class="meta">@Column</span>()</span><br><span class="line"><span class="attr">quantity</span>: <span class="built_in">number</span>;</span><br></pre></td></tr></tbody></table></figure>
<p><code>quantity</code> 字段通常在以下情况中用于查询:</p>
<ul>
<li>查询具有特定数量的订单项(例如查询某种商品的批量购买情况)</li>
<li>按照数量对订单项进行筛选或排序</li>
</ul>
<h2 id="36-类别"><a class="markdownIt-Anchor" href="#36-类别"></a> 3.6. 类别</h2>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Index</span>()</span><br><span class="line"><span class="meta">@Column</span>()</span><br><span class="line"><span class="attr">name</span>: <span class="built_in">string</span>;</span><br></pre></td></tr></tbody></table></figure>
<p><code>name</code> 字段是分类的唯一标识,用于以下情况:</p>
<ul>
<li>按类别名称搜索产品类别(例如,用户希望快速找到特定名称的类别)</li>
<li>在名称字段上进行排序或进行自动完成的匹配查询</li>
</ul>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Index</span>()</span><br><span class="line"><span class="meta">@CreateDateColumn</span>()</span><br><span class="line"><span class="attr">created_at</span>: <span class="title class_">Date</span>;</span><br></pre></td></tr></tbody></table></figure>
<h2 id="37-购物车"><a class="markdownIt-Anchor" href="#37-购物车"></a> 3.7. 购物车</h2>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Index</span>()</span><br><span class="line"><span class="meta">@CreateDateColumn</span>()</span><br><span class="line"><span class="attr">created_at</span>: <span class="title class_">Date</span>;</span><br></pre></td></tr></tbody></table></figure>
<h2 id="38-地址"><a class="markdownIt-Anchor" href="#38-地址"></a> 3.8. 地址</h2>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Index</span>()</span><br><span class="line"><span class="meta">@Column</span>()</span><br><span class="line"><span class="attr">postal_code</span>: <span class="built_in">string</span>;</span><br></pre></td></tr></tbody></table></figure>
<p>在地址系统中,<code>postal_code</code>(邮政编码)字段可能用于以下场景:</p>
<ul>
<li>按邮政编码筛选地址。例如,在电商系统中,按邮政编码筛选用户地址,以确定是否支持配送</li>
<li>邮政编码的批量统计或区域分析,例如确定特定邮政编码区域的用户密度</li>
</ul>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Index</span>()</span><br><span class="line"><span class="meta">@CreateDateColumn</span>()</span><br><span class="line"><span class="attr">created_at</span>: <span class="title class_">Date</span>;</span><br></pre></td></tr></tbody></table></figure>
<h2 id="39-库存日志"><a class="markdownIt-Anchor" href="#39-库存日志"></a> 3.9. 库存日志</h2>
<figure class="highlight ts"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Index</span>()</span><br><span class="line"><span class="meta">@CreateDateColumn</span>()</span><br><span class="line"><span class="attr">created_at</span>: <span class="title class_">Date</span>;</span><br></pre></td></tr></tbody></table></figure>
<p>接下来,我们可以使用以下命令生成数据库迁移文件:</p>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn migration:generate src/migrations/CreateIndex</span><br></pre></td></tr></tbody></table></figure>
<p>然后使用以下命令应用迁移并更新数据库:</p>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn migration:run</span><br></pre></td></tr></tbody></table></figure>
</body></html></div></article></div></main><footer><div class="paginator"><a class="prev" href="8853.html">上一篇</a><a class="next" href="8e94.html">下一篇</a></div><!-- Webmention 显示区域--><div class="webmention-section webmention-empty" data-page-url="posts/5a4b.html" data-full-url="https://cytrogen.icu/posts/5a4b.html" data-mode="static">
<h3 class="webmention-title">Webmentions (<span class="webmention-count">0</span>)</h3>
<div class="webmention-list"></div>
<span>暂无 Webmentions</span>
</div><div class="copyright"><p class="footer-links"><a href="../friends/index.html">友链</a><span class="footer-separator"> ·</span><a href="../links/index.html">邻邦</a><span class="footer-separator"> ·</span><a href="../contact/index.html">联络</a><span class="footer-separator"> ·</span><a href="../colophon/index.html">营造记</a><span class="footer-separator"> ·</span><a href="../atom.xml">RSS订阅</a></p><p>© 2025 - 2026 <a href="https://cytrogen.icu">Cytrogen</a>, powered by <a href="https://hexo.io/" target="_blank">Hexo</a> and <a href="https://github.com/cytrogen/hexo-theme-ares" target="_blank">hexo-theme-ares</a>.</p><p><a href="https://blogscn.fun" target="_blank" rel="noopener">BLOGS·CN</a></p></div></footer></div></div><a class="back-to-top" href="#top" aria-label="返回顶部"><svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path d="M3.293 9.707a1 1 0 010-1.414L9.586 2a2 2 0 012.828 0l6.293 6.293a1 1 0 01-1.414 1.414L11 3.414V17a1 1 0 11-2 0V3.414L2.707 9.707a1 1 0 01-1.414 0z"></path></svg></a><script>document.addEventListener('DOMContentLoaded', function() {
const codeBlocks = document.querySelectorAll('figure.highlight');
codeBlocks.forEach(block => {
let caption = block.querySelector('figcaption');
if (!caption) {
caption = document.createElement('figcaption');
block.insertBefore(caption, block.firstChild);
}
const info = document.createElement('div');
info.className = 'info';
const filename = caption.querySelector('span');
if (filename) {
filename.className = 'filename';
info.appendChild(filename);
}
const lang = block.className.split(' ')[1];
if (lang) {
const langSpan = document.createElement('span');
langSpan.className = 'lang-name';
langSpan.textContent = lang;
info.appendChild(langSpan);
}
const sourceLink = caption.querySelector('a');
if (sourceLink) {
sourceLink.className = 'source-link';
info.appendChild(sourceLink);
}
const actions = document.createElement('div');
actions.className = 'actions';
const codeHeight = block.scrollHeight;
const threshold = 300;
if (codeHeight > threshold) {
block.classList.add('folded');
const toggleBtn = document.createElement('button');
toggleBtn.textContent = '展开';
toggleBtn.addEventListener('click', () => {
block.classList.toggle('folded');
toggleBtn.textContent = block.classList.contains('folded') ? '展开' : '折叠';
});
actions.appendChild(toggleBtn);
}
const copyBtn = document.createElement('button');
copyBtn.textContent = '复制';
copyBtn.addEventListener('click', async () => {
const codeLines = block.querySelectorAll('.code .line');
const code = Array.from(codeLines)
.map(line => line.textContent)
.join('\n')
.replace(/\n\n/g, '\n');
try {
await navigator.clipboard.writeText(code);
copyBtn.textContent = '已复制';
copyBtn.classList.add('copied');
setTimeout(() => {
copyBtn.textContent = '复制';
copyBtn.classList.remove('copied');
}, 3000);
} catch (err) {
console.error('复制失败:', err);
copyBtn.textContent = '复制失败';
setTimeout(() => {
copyBtn.textContent = '复制';
}, 3000);
}
});
actions.appendChild(copyBtn);
caption.innerHTML = '';
caption.appendChild(info);
caption.appendChild(actions);
const markedLines = block.getAttribute('data-marked-lines');
if (markedLines) {
const lines = markedLines.split(',');
lines.forEach(range => {
if (range.includes('-')) {
const [start, end] = range.split('-').map(Number);
for (let i = start; i <= end; i++) {
const line = block.querySelector(`.line-${i}`);
if (line) line.classList.add('marked');
}
} else {
const line = block.querySelector(`.line-${range}`);
if (line) line.classList.add('marked');
}
});
}
});
});</script><script async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" id="MathJax-script"></script><script>(function() {
document.addEventListener('DOMContentLoaded', function() {
const themeToggle = document.querySelector('.theme-toggle');
if (!themeToggle) return;
const getCurrentTheme = () => {
return document.documentElement.getAttribute('data-theme') || 'light';
};
const updateUI = (theme) => {
const isDark = theme === 'dark';
themeToggle.setAttribute('aria-pressed', isDark.toString());
};
const setTheme = (theme) => {
document.documentElement.setAttribute('data-theme', theme);
document.documentElement.style.colorScheme = theme;
const pageWrapper = document.getElementById('page-wrapper');
if (pageWrapper) {
pageWrapper.setAttribute('data-theme', theme);
}
// Find and remove the temporary anti-flicker style tag if it exists.
// This ensures the main stylesheet takes full control after the initial load.
const antiFlickerStyle = document.getElementById('anti-flicker-style');
if (antiFlickerStyle) {
antiFlickerStyle.remove();
}
localStorage.setItem('theme', theme);
updateUI(theme);
};
const toggleTheme = () => {
const current = getCurrentTheme();
const newTheme = current === 'light' ? 'dark' : 'light';
setTheme(newTheme);
};
updateUI(getCurrentTheme());
themeToggle.addEventListener('click', toggleTheme);
if (window.matchMedia) {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', function(e) {
if (!localStorage.getItem('theme')) {
const theme = e.matches ? 'dark' : 'light';
setTheme(theme);
}
});
}
});
})();
</script><script src="../js/details-toggle.js" defer></script><script>(function() {
document.addEventListener('DOMContentLoaded', function() {
const backToTopBtn = document.querySelector('.back-to-top');
if (!backToTopBtn) return;
const toggleButtonVisibility = () => {
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const shouldShow = scrollTop > 200;
if (shouldShow) {
backToTopBtn.classList.add('is-visible');
} else {
backToTopBtn.classList.remove('is-visible');
}
};
let ticking = false;
const handleScroll = () => {
if (!ticking) {
requestAnimationFrame(() => {
toggleButtonVisibility();
ticking = false;
});
ticking = true;
}
};
const scrollToTop = (event) => {
event.preventDefault();
window.scrollTo({
top: 0,
behavior: 'smooth'
});
};
window.addEventListener('scroll', handleScroll);
backToTopBtn.addEventListener('click', scrollToTop);
toggleButtonVisibility();
});
})();</script></body></html>