普通视图

发现新文章,点击刷新页面。
昨天 — 2025年4月2日首页

我的远程实习(六) | 一个demo讲清Auth.js国外平台登录鉴权👈|nextjs

作者 浪遏
2025年4月2日 23:00

前言

前些时候 ,远程实习要求实现鉴权登录 , 采用 Auth.js 可用于在 Nextjs 项目中实现登录鉴权 ,今天我写一个 demo , 为大家展示一下 auth 鉴权的数据流转 ~

我们首先介绍一下: NextAuth.js 是一个专为 Next.js 应用设计的灵活且易于使用认证库,它极大地简化了添加登录、注册、登出等认证功能的过程。该库以其灵活性和强大的功能而闻名,支持多种认证方式,包括电子邮件与密码、OAuth 2.0 提供商(如 Google、GitHub、Facebook 等),以及自定义提供商。

主要特点

  • 丰富的内置提供者:支持众多 OAuth 和 OpenID Connect 提供商,方便快捷地与第三方服务集成。
  • 会话管理:提供简明的 API 来处理用户会话,轻松获取当前用户的会话信息。
  • 数据库兼容性:支持与多种数据库集成,适用于存储用户数据,并能无缝对接无头 CMS 和自定义后端。
  • 多语言支持:根据用户的语言偏好显示错误消息及其他文本,增强用户体验。
  • 可定制页面:允许开发者创建符合应用设计风格的自定义登录、注册或错误页面。
  • 安全优先:采用一系列安全默认设置,帮助保护应用免受常见的安全威胁。
  • API 路由整合:利用 Next.js 的 API 路由机制来实现身份验证逻辑,让开发者可以自由创建用于登录、登出等操作的自定义端点。
  • 会话管理选项:提供选择,既可以通过 JSON Web Tokens (JWT) 实现无状态会话管理,也可以选择基于数据库的会话管理。
  • 适配器支持:为了满足将用户数据持久化到数据库的需求,NextAuth.js 提供了与不同数据库系统(如 Prisma、TypeORM 等)集成的适配器。

demo 效果展示

Gitee:gitee.com/luli1314520…

开源不易 , 点个小小赞支持下 ~

认证演示项目登录流程分析

项目结构

项目使用Next.js框架构建,采用App Router架构,主要目录结构:

  • /app - 前端页面和API路由
    • /app/auth/signin - 登录页面
    • /app/api/auth/[...nextauth] - NextAuth API路由
  • /auth - NextAuth配置
  • /services - 业务逻辑层
  • /models - 数据访问层
  • /types - 类型定义
  • /lib - 工具函数

认证方案

项目使用NextAuth.js实现第三方OAuth认证,支持以下登录方式:

  • Google OAuth登录
  • GitHub OAuth登录

认证数据流转图

┌─────────────┐      ┌─────────────────┐      ┌────────────────────┐
│             │      │                 │      │                    │
│  用户界面   │─────▶│  NextAuth API   │─────▶│  OAuth提供商       │
│  (客户端)   │◀─────│  (服务端路由)   │◀─────│  (Google/GitHub)   │
│             │      │                 │      │                    │
└─────────────┘      └─────────────────┘      └────────────────────┘
       │                     │                         │
       │                     │                         │
       ▼                     ▼                         │
┌─────────────┐      ┌─────────────────┐               │
│             │      │                 │               │
│  会话状态   │◀────▶│  数据存储       │◀──────────────┘
│  (JWT)      │      │  (用户信息)     │
│             │      │                 │
└─────────────┘      └─────────────────┘

数据流转详解

  1. 用户界面 → NextAuth API → OAuth提供商
    • 用户在前端界面点击登录按钮,触发认证流程
    • NextAuth API构建OAuth授权URL并重定向用户
    • 用户被引导至第三方OAuth提供商(Google/GitHub)进行身份验证
  2. OAuth提供商 → NextAuth API → 数据存储
    • 用户在OAuth提供商完成身份验证
    • 提供商重定向回NextAuth回调URL,附带授权码
    • NextAuth API使用授权码换取用户信息
    • 用户信息被保存到数据存储中
  3. NextAuth API → 会话状态
    • 认证成功后,NextAuth创建JWT令牌
    • JWT包含必要的用户信息和认证元数据
    • 令牌加密并存储在HTTP-only cookie中
  4. 会话状态 用户界面
    • 前端通过 useSession钩子或 getSession函数获取会话状态
    • 会话状态包含用户身份和权限信息
    • 用户界面根据会话状态调整显示内容(如显示用户资料)
  5. 会话状态 数据存储
    • JWT令牌中包含用户标识符(如UUID)
    • 可通过标识符从数据存储中获取完整用户信息
    • 会话更新时可能涉及数据存储的变更(如更新登录时间)

登录流程(前端到后端)

1. 前端登录入口

文件: app/auth/signin/page.tsx

登录页面是服务端组件,显示登录按钮并检查用户是否已登录:

export default async function SignInPage({
  searchParams,
}: {
  searchParams: { callbackUrl: string | undefined };
}) {
  const session = await auth();
  if (session) {
    return redirect(searchParams.callbackUrl || "/");
  }
  
  // 登录页面UI
}

此组件对应数据流转图中的用户界面(客户端),负责展示登录界面并检查会话状态。

2. 客户端登录按钮

文件: app/auth/signin/client.tsx

客户端组件,处理登录按钮点击事件:

export function SignInButton({ 
  provider, 
  callbackUrl,
  children
}: { 
  provider: string; 
  callbackUrl?: string;
  children: React.ReactNode;
}) {
  return (
    <button
      onClick={() => signIn(provider, { callbackUrl: callbackUrl || '/' })}
      className="..."
    >
      {provider === 'google' && <FaGoogle className="..." />}
      {provider === 'github' && <FaGithub className="..." />}
      <span>{children}</span>
    </button>
  );
}

此组件触发用户界面→NextAuth API的数据流,调用 signIn函数发起认证请求。

3. NextAuth API路由

文件: app/api/auth/[...nextauth]/route.ts

处理NextAuth的API请求:

import NextAuth from "next-auth";
import { authOptions } from "@/auth/config";

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST };

此文件对应数据流转图中的NextAuth API(服务端路由),处理所有认证相关的HTTP请求,包括:

  • 认证请求(重定向到OAuth提供商)
  • 回调处理(接收OAuth提供商的回调)
  • 会话查询(前端获取会话状态)

4. NextAuth配置

文件: auth/config.ts

配置NextAuth认证选项,包括:

  • 认证提供商(Google、GitHub)
  • 登录回调
  • JWT处理
  • Session处理

关键代码:

export const authOptions: NextAuthConfig = {
  providers,
  pages: {
    signIn: "/auth/signin",
  },
  callbacks: {
    async signIn({ user, account, profile }) { ... },
    async redirect({ url, baseUrl }) { ... },
    async session({ session, token }) { ... },
    async jwt({ token, user, account }) { ... },
  },
};

这个配置文件定义了NextAuth API如何与OAuth提供商数据存储交互,以及如何处理会话状态

5. NextAuth实例

文件: auth/index.ts

创建NextAuth实例并导出相关函数:

import NextAuth from "next-auth";
import { authOptions } from "./config";

export const { handlers, signIn, signOut, auth } = NextAuth(authOptions);

这些导出的函数促成了数据流转图中的多个流程:

  • signIn:用户界面→NextAuth API
  • auth:NextAuth API→会话状态
  • signOut:用户界面→NextAuth API→会话状态(清除)

6. 用户信息处理

文件: services/user.ts

处理用户信息保存逻辑:

export async function saveUser(user: User) {
  try {
    const existUser = await findUserByEmail(user.email);
    if (!existUser) {
      await insertUser(user);
    } else {
      user.id = existUser.id;
      user.uuid = existUser.uuid;
      user.created_at = existUser.created_at;
    }
    return user;
  } catch (e) {
    throw e;
  }
}

此服务对应数据流转图中的NextAuth API→数据存储流程,负责将OAuth提供商返回的用户信息持久化到数据存储中。

7. 数据存储

文件: models/user.ts

示例项目使用内存数组存储用户信息,实际项目应使用数据库:

// 演示用简化版本,实际项目中应使用数据库
const users: User[] = [];

export async function findUserByEmail(email: string): Promise<User | null> { ... }
export async function findUserByUuid(uuid: string): Promise<User | null> { ... }
export async function insertUser(user: User): Promise<User> { ... }

此模块实现了数据流转图中的**数据存储(用户信息)**组件,提供用户数据的CRUD操作。

登录流程详解

  1. 用户点击登录按钮
    • 前端调用 signIn(provider)函数
    • NextAuth.js将用户重定向到第三方OAuth提供商(Google/GitHub)
    • 数据流:用户界面→NextAuth API→OAuth提供商
  2. 第三方认证
    • 用户在第三方平台完成认证
    • 第三方平台将用户重定向回应用的回调URL
    • 数据流:OAuth提供商→NextAuth API
  3. 处理回调
    • NextAuth.js API接收回调请求
    • 调用 jwt回调处理令牌
    • 保存用户信息到后端存储
    • 数据流:NextAuth API→数据存储NextAuth API→会话状态
  4. 创建会话
    • 调用 session回调构建会话信息
    • 返回包含用户信息的会话对象
    • 数据流:会话状态用户界面
  5. 完成登录
    • 用户被重定向到指定的回调URL或首页
    • 前端可通过 useSession钩子或 auth()函数访问会话信息
    • 数据流:会话状态用户界面会话状态数据存储

JWT与会话状态管理

JWT(JSON Web Token)在认证流程中扮演核心角色,对应数据流转图中的**会话状态(JWT)**节点:

  1. JWT创建
    • 用户成功认证后,NextAuth创建包含用户信息的JWT令牌
    • JWT中存储必要的用户信息(如UUID、头像URL等)
    • 数据流:NextAuth API→会话状态
  2. JWT存储
    • JWT令牌加密后存储在HTTP-only cookie中
    • 浏览器每次请求自动发送cookie,实现无状态认证
    • 数据流:会话状态用户界面
  3. 会话构建
    • 服务端通过 auth()函数解析JWT令牌获取会话信息
    • 客户端通过 useSession()钩子访问会话状态
    • 数据流:会话状态用户界面
  4. 会话与数据存储交互
    • 会话中的用户标识可用于从数据存储获取完整用户信息
    • 可通过会话中的用户ID更新数据存储中的用户信息
    • 数据流:会话状态数据存储

类型扩展

NextAuth类型扩展,支持JWT和Session中的自定义字段:

declare module "next-auth" {
  interface JWT {
    user?: {
      uuid?: string;
      nickname?: string;
      avatar_url?: string;
      created_at?: string;
    };
  }

  interface Session {
    user: {
      uuid?: string;
      email?: string | null;
      name?: string | null;
      nickname?: string | null;
      avatar_url?: string | null;
      created_at?: string | null;
    }
  }
}

用户类型定义,对应数据存储中的用户数据结构:

export interface User {
  id?: number;
  uuid?: string;
  email: string;
  created_at?: string;
  nickname: string;
  avatar_url: string;
  signin_type?: string;
  signin_ip?: string;
  signin_provider?: string;
  signin_openid?: string;
}

安全考虑

  1. 环境变量:OAuth客户端ID和密钥存储在环境变量中
  2. 重定向检查:验证重定向URL的合法性
  3. JWT令牌:使用JWT保存会话状态,加密存储防篡改
  4. 无密码存储:使用OAuth方式不需要存储用户密码
  5. HTTP-only Cookie:JWT存储在HTTP-only cookie中,防止JavaScript访问
  6. CSRF保护:NextAuth内置CSRF令牌验证机制

数据流转优化建议

  1. 数据库集成
    • 将内存存储替换为持久化数据库(MongoDB、PostgreSQL等)
    • 优化数据存储与NextAuth API的交互性能
  2. 令牌刷新机制
    • 实现OAuth访问令牌自动刷新功能
    • 延长用户会话有效期,减少重复登录
  3. 缓存层引入
    • 在数据存储与会话状态之间添加缓存层(如Redis)
    • 减轻数据库负担,提高频繁会话查询性能
  4. 前端状态管理
    • 优化前端会话状态管理,减少不必要的API调用
    • 实现优雅的会话过期处理和自动重新认证

参考

昨天以前首页

场景题:大文件上传 ?| 过总字节一面😱

作者 浪遏
2025年3月31日 22:24

前言

刚刚要吃饭,放下书包 , 适才掏出手机点开支付码 , 微信嘀嘟一声 , 划过一条消息 :过佬发来一张图片 , 我好奇地点开群聊消息 , 点入图片 , 不禁一惊 , 过佬出征字节了😱 , 面经如下 :

现在都流行问场景题呢👈 , 之前在掘金上看到个观点 : 在 AI 大行其道 の 今天 , 八股一问便有答案 , 也是说在一定程度上打破了信息差 ; 未来在传统八股方面会减弱 , 将会聚焦与场景化 , 各位掘友怎被看 ?

哈哈 , 不管怎么样😏 , 如果八股背成了八股 , 自然无用 , 利用八股解决实际问题 ,有何尝不是场景化地具体实现呢 ?

今天 , 开始一起研究下大文件上传吧 ~

我之前写过一篇文章关于大文件上传 , 主要是用于实践 , 很多细节没有挖掘哈~

大文件上传👈 | React + NestJs |分片、断点续传、秒传🚀 , 你是否知道 ???

现在进行深入拷打 ~

就是因为这篇文章 , 我也被拷打过😭 , 面经如下 :

完整的面试经过可以查看我之前写的一篇面经:

2025年2月凉面与热面(1)——杭州AI公司一二面

过总

先看过佬的面经

问题一 : 大文件上传以及应用场景优化

这个是十分大的问题 , 拆解下来就是应用场景性能优化 , 可以先泛泛谈之 ~

应用场景

这边举一些例子:

  • 云存储:用户向百度网盘、阿里云盘等上传大型视频、软件安装包等,利用大文件上传功能,即便网络不稳定也能成功上传。
  • 企业办公:设计师上传大型设计素材文件到公司共享服务器,或程序员上传项目部署包等。
  • 在线视频制作:创作者将本地高清视频素材上传到在线剪辑平台。

在这些例子里面 , 我有个点有深刻影响 🤡👈 —— 程序员上传项目部署包 , 我之前文章提到 (本地构建/手动上传/服务器运行) 的部署方案 , 在这个过程中 , 我就需要在本地打包上传服务器 , 这就是大文件上传 ~

我的远程实习(四)| Ailln叫我docker部署项目,我顺便填了以前的坑

性能优化

  • 网络层面:利用 CDN(内容分发网络),将文件切片缓存到离用户近的节点,加快传输速度。如腾讯云 CDN 可加速文件上传(旨在通过将网站内容缓存到全球各地的节点,实现快速、稳定和高效的访问体验)
  • 前端优化:使用多线程或 Web Workers 实现多切片并发上传。像 JavaScript 借助 Web Workers 可开启多个线程同时上传不同切片。
  • 后端优化:优化服务器存储和文件合并逻辑,采用分布式存储系统,如 Ceph,提升存储和处理能力。

问题二:断点续传,怎么确认上传完了

断点续传原理:在上传过程中记录已上传切片信息,网络中断恢复后,从上次中断处继续上传。例如,上传 100 个切片的文件,传到第 30 个时断网,恢复后从第 31 个开始。
确认上传完成方式

  • 计数确认:前端记录已上传切片数量,后端接收切片时也记录。当两端记录的已上传切片数都等于总分片数,确认上传完成。比如前端记录上传了 100 个切片中的 100 个,后端也接收了 100 个,即上传完毕。
  • 哈希校验:对完整文件计算哈希值(如 MD5、SHA - 1 等),上传过程中服务器对合并后的文件计算哈希值,两者相等则确认上传完成。例如上传一个软件安装包,本地计算其 MD5 值,服务器合并后计算 MD5 值与之比对。

问题三:用户上传到一半,重新刷新页面,要不要重新上传

不需要重新上传的情况:若前端和后端有完善的断点续传机制,且能记录已上传切片信息。比如前端使用 IndexedDB 存储已上传切片索引,后端数据库记录接收情况。刷新页面后,前端从** IndexedDB** 读取信息,和后端核对,接着传未上传切片。
需要重新上传的情况:如果没有有效的状态记录和断点续传机制,刷新页面后前端无法知晓已上传进度,可能会重新上传。例如简单的单线程上传脚本,没做任何状态保存,刷新后只能从头开始。

问题四:文件分片的 id 记录在哪

前端记录

  • 内存变量:在 JavaScript 中,可定义变量存储切片 id。如let uploadedChunkIds = [];,每上传一个切片,将其 id push 进数组。适用于简单页面,页面刷新数据丢失。
  • 本地存储:使用localStorageIndexedDB。如localStorage.setItem('chunkIds', JSON.stringify(uploadedChunkIds)); ,可长期保存,支持断点续传,适用于复杂大文件上传场景。

后端记录

  • 数据库:存入关系型数据库(如 MySQL)或非关系型数据库(如 MongoDB) 。以 MySQL 为例,建表记录文件 id、切片 id、上传状态等信息。方便服务器端管理和查询,支持多用户、大规模文件上传场景。
  • 缓存:如 Redis,可快速记录和查询切片 id 状态。适合高并发场景,能提升查询和处理速度。

以上就是过佬面经中的大文件上传了,接下来再来拷打一下 !!! 分别从不同角度拷打 , 可能答案相似之处甚多

我的

以下是对图中文件上传相关问题的详细深刻解释:

如何并发的执行文件上传

  • 原理:利用浏览器或运行环境的多线程、异步特性,同时发起多个文件切片的上传请求。比如在前端 JavaScript 中,可将每个切片的上传任务封装成 Promise,再借助 Promise.all 等方式并发执行。
  • 实现方式:先把大文件切片,如将一个 500MB 的文件切成 500 个 1MB 的切片。然后为每个切片创建上传任务,像使用 XMLHttpRequestFetch API 来发起请求。例如用 Fetch API 时,代码类似 fetch('/upload', { method: 'POST', body: chunk })chunk 为切片数据),最后通过 Promise.all 并发执行这些请求。
  • 注意事项:需控制并发数量,避免因过多并发请求耗尽网络资源或导致浏览器性能下降。可通过自定义队列等方式,设定最大并发数,如设置为 5,当正在执行的上传任务小于 5 时,才从任务队列中取出新切片进行上传。

一般的并发数是多少

  • 影响因素
    • 网络环境:在高速稳定的企业专线网络下,可适当提高并发数;而在普通家庭宽带或移动网络环境中,过高并发数可能导致网络拥塞,一般并发数不宜过高。
    • 服务器性能:服务器配置高、带宽充足,能承受较多并发请求;若服务器性能有限,过多并发会使其负载过高,影响服务稳定性。
    • 浏览器限制:不同浏览器对并发连接数有不同限制,比如 Chrome 浏览器对同一个域名的并发连接数一般限制在 6 - 8 个左右 。
  • 常见取值范围:通常在 3 - 10 之间。对于小型项目或对实时性要求不高的场景,3 - 5 较为合适;而在一些大型文件存储系统且网络和服务器条件较好时,可能会设置到 8 - 10 。但实际应用中需通过测试来确定最优并发数。

如果分片5片,其中两片传完了,接下来怎么办

  • 正常情况:继续按顺序或并发上传剩余的 3 片。若采用顺序上传,依次发起对剩余切片的上传请求;若采用并发上传且还有并发名额,将剩余切片的上传任务加入并发队列执行。
  • 异常处理:若在上传过程中发现已上传的两片存在问题(如通过 MD5 校验发现文件损坏 ),需重新上传这两片。同时,若上传过程中网络中断,需记录已上传切片的状态,待网络恢复后,根据记录继续上传剩余切片或重新上传有问题的切片。
  • 后端协作:后端需配合记录已成功接收的切片信息,以便前端在各种情况下能准确判断上传进度和下一步操作。比如后端可在数据库中记录每个切片的接收状态 。

文件切片大一点好,还是小一点好? 分片切多少怎么考虑

  • 切片大的优劣
    • 优点:切片数量少,上传任务调度开销小,合并操作相对简单,在网络稳定且带宽充足时,能快速完成上传。例如在企业内部高速网络环境下上传大型安装包。
    • 缺点:单个切片传输时间长,网络不稳定时,易因超时等问题导致整个切片重传,浪费时间和流量。
  • 切片小的优劣
    • 优点:单个切片传输快,网络波动影响小,便于实现断点续传,重传成本低。比如在网络状况复杂的公共 Wi - Fi 环境中上传文件。
    • 缺点:切片数量多,调度和管理开销大,后端合并操作也更复杂。
  • 考虑因素
    • 网络状况:网络稳定且带宽大,可适当增大切片;网络不稳定则宜采用较小切片。
    • 文件类型:对实时性要求高的文件(如视频流 ),小切片可减少卡顿;对完整性要求高的文件(如可执行程序 ),大切片可能更合适。
    • 服务器性能:服务器处理能力强、存储 I/O 性能高,可处理较多切片;若服务器性能有限,大切片可减轻其处理压力。

如何实现秒传?(MD5值比对)

  • 原理:利用文件内容的唯一性,通过计算文件的 MD5 值(一种哈希算法,能为文件生成唯一的固定长度字符串 )来标识文件。用户上传文件前,先计算其 MD5 值并发送给服务器,服务器在存储中查找是否有相同 MD5 值的文件。若有,直接将该文件与用户关联,实现秒传;若无,则开始正常上传流程。
  • 实现步骤
    • 前端计算:在前端使用相关库(如 js - md5 库 )计算文件的 MD5 值。
    • 服务器查找:服务器接收到 MD5 值后,在文件索引数据库中查询是否存在相同 MD5 值的文件记录。
    • 结果处理:若找到,向用户返回已存在文件的信息,完成秒传;若未找到,通知前端开始上传文件切片,并在上传完成后将新文件及其 MD5 值记录到数据库。
  • 安全性和局限性:MD5 算法存在碰撞概率(不同文件可能有相同 MD5 值 ),但概率极低。在实际应用中,可结合其他校验方式(如文件大小、文件头信息等 )提高准确性。

前端如何实现并发上传的 ?

大文件并发上传通常按以下步骤和方式实现:

1. 文件切片

先把大文件分割成若干较小的数据块,比如将一个 100MB 的文件按 1MB 大小切成 100 个切片。在前端,利用 JavaScript 的 Blob.prototype.slice 方法就能实现,示例代码如下:

function splitFile(file, chunkSize = 5 * 1024 * 1024) {
    const chunks = [];
    let start = 0;
    while (start < file.size) {
        const chunk = file.slice(start, start + chunkSize);
        chunks.push(chunk);
        start += chunkSize;
    }
    return chunks;
}

2. 并发控制策略

  • 基于 Promise.all 并发:将每个切片的上传请求封装成 Promise 对象,存进数组,再用 Promise.all 并发执行这些 Promise。举例:
const chunks = splitFile(file);
const promises = chunks.map((chunk, index) => {
    const formData = new FormData();
    formData.append('chunk', chunk);
    formData.append('index', index);
    return fetch('/upload', { method: 'POST', body: formData });
});
await Promise.all(promises);

不过,这种方式可能因同时发起过多请求,耗尽系统资源或造成网络拥塞。

  • 队列控制并发数:设定最大并发数,用队列管理切片上传。比如设置最大并发数为 5,上传队列里存放待上传切片,当前上传数小于 5 且队列有切片时,就取出切片上传。示例代码:
const MAX_CONCURRENT_UPLOADS = 5;
const uploadQueue = [];
let activeUploads = 0;

function enqueueUpload(file) {
    uploadQueue.push(file);
    processQueue();
}

function processQueue() {
    if (activeUploads < MAX_CONCURRENT_UPLOADS && uploadQueue.length > 0) {
        const file = uploadQueue.shift();
        activeUploads++;
        uploadFile(file).finally(() => {
            activeUploads--;
            processQueue();
        });
    }
}

3. 前端上传实现

  • 利用 XMLHttpRequest 或 Fetch API:通过这两个 API 发起切片上传请求。以 XMLHttpRequest 为例:
function uploadChunk(chunk, index) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('POST', '/upload', true);
        xhr.onload = () => {
            if (xhr.status === 200) {
                resolve();
            } else {
                reject(new Error('Chunk upload failed'));
            }
        };
        xhr.onerror = () => {
            reject(new Error('Upload error'));
        };
        const formData = new FormData();
        formData.append('chunk', chunk);
        formData.append('index', index);
        xhr.send(formData);
    });
}

4. 后端处理

后端接收切片请求,可借助像 Express.js(Node.js)、Django(Python)等框架。以 Express.js 为例,示例代码如下:

const express = require('express');
const multer = require('multer');
const app = express();
const upload = multer({ dest: 'uploads/' });

app.post('/upload', upload.single('chunk'), (req, res) => {
    // 处理接收到的切片,可记录切片信息到数据库等
    res.status(200).send('Chunk received successfully');
});

5. 切片合并

所有切片都上传到后端后,要按顺序合并成原始文件。比如在 Node.js 里,使用 fs.appendFileSync 方法,先创建空文件,再把切片数据依次追加进去:

const fs = require('fs');
const path = require('path');

function mergeChunks(chunkPaths, outputPath) {
    const writeStream = fs.createWriteStream(outputPath);
    chunkPaths.forEach((chunkPath) => {
        const readStream = fs.createReadStream(chunkPath);
        readStream.pipe(writeStream, { end: false });
        readStream.on('end', () => {
            fs.unlinkSync(chunkPath); // 合并后删除临时切片文件
        });
    });
    writeStream.on('finish', () => {
        console.log('File merged successfully');
    });
}

此外,部分云存储服务(如阿里云 OSS 等)的 SDK 也提供了并发上传功能,像 Java SDK 用 taskNum 、Python SDK 用 num_threads 参数来控制并发数 ,使用时按对应 SDK 文档配置即可实现大文件并发上传。

除了采用 promise.all 并发上传 , 你还知道什么 ?

除了基于Promise.all并发外,还有以下并发控制策略:

自定义队列控制

  • 原理:维护一个任务队列和一个记录当前正在执行任务数量的变量。设定最大并发数,当有新任务时,先放入队列。若当前执行任务数小于最大并发数,从队列取出任务执行;任务完成后,减少当前执行任务数,并检查队列,若有剩余任务则继续取出执行。
  • 示例代码(JavaScript)
class TaskQueue {
    constructor(maxConcurrent) {
        this.maxConcurrent = maxConcurrent;
        this.currentCount = 0;
        this.queue = [];
    }
    addTask(taskFn) {
        return new Promise((resolve, reject) => {
            this.queue.push({ taskFn, resolve, reject });
            this.processQueue();
        });
    }
    processQueue() {
        if (this.currentCount >= this.maxConcurrent || this.queue.length === 0) {
            return;
        }
        const { taskFn, resolve, reject } = this.queue.shift();
        this.currentCount++;
        taskFn()
          .then(resolve)
          .catch(reject)
          .finally(() => {
                this.currentCount--;
                this.processQueue();
            });
    }
}
// 使用示例
const taskQueue = new TaskQueue(3);
const tasks = Array.from({ length: 10 }, (_, i) => () => new Promise((resolve) => setTimeout(() => {
    console.log(`Task ${i} completed`);
    resolve();
}, 1000 * (i + 1))));
const executeTasks = async () => {
    const promises = tasks.map(task => taskQueue.addTask(task));
    await Promise.all(promises);
    console.log('All tasks completed');
};
executeTasks();

生成器函数结合yield

  • 原理:利用生成器函数可以暂停和恢复执行的特性,配合yield关键字手动控制异步任务的执行顺序和并发情况。在生成器函数内部逐个生成异步任务,每次yield一个任务,等待其完成后再继续执行下一个任务。
  • 示例代码(JavaScript)
function* taskGenerator() {
    yield new Promise((resolve) => setTimeout(() => {
        console.log('Task 1 completed');
        resolve();
    }, 1000));
    yield new Promise((resolve) => setTimeout(() => {
        console.log('Task 2 completed');
        resolve();
    }, 1500));
    yield new Promise((resolve) => setTimeout(() => {
        console.log('Task 3 completed');
        resolve();
    }, 2000));
}
const runner = async function () {
    const gen = taskGenerator();
    let result;
    do {
        result = gen.next();
        if (!result.done) {
            await result.value;
        }
    } while (!result.done);
    console.log('All tasks in generator completed');
};
runner();

消息队列

  • 原理:将异步任务放入消息队列中,由多个消费者(可以是线程、进程等)从队列中获取任务并并行处理。消息队列会按照一定规则(如先进先出)分配任务给消费者,能处理大量异步任务且允许一定延迟。适用于后端系统处理高并发任务场景,像大型电商系统处理订单、物流等任务。
  • 示例(以RabbitMQ为例,Python语言)
import pika

# 连接RabbitMQ服务器
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()

# 声明队列
channel.queue_declare(queue='task_queue')

# 生产者发送任务消息
for i in range(10):
    message = f"Task {i}"
    channel.basic_publish(exchange='', routing_key='task_queue', body=message)
print("Tasks sent to queue")

# 关闭连接
connection.close()

# 消费者接收并处理任务(另一个Python脚本示例)
import pika

connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.queue_declare(queue='task_queue')

def callback(ch, method, properties, body):
    print(f"Received and processed: {body}")

channel.basic_consume(queue='task_queue', on_message_callback=callback, auto_ack=True)
print('Waiting for tasks...')
channel.start_consuming()

限流算法

  • 令牌桶算法:系统按固定速率生成令牌放入桶中,桶有最大容量。请求到来时需从桶中获取令牌,有令牌则可继续处理,无令牌则请求被限流。比如设定桶容量为100个令牌,每秒生成10个令牌,若请求瞬间到来200个,只能处理100个,剩余100个等待新令牌生成。
  • 漏桶算法:请求像水一样注入漏桶,漏桶以固定速率流出水(处理请求),若注入速度过快,桶满后多余请求会被丢弃。可想象一个底部有小孔的桶,水不断注入但从小孔恒定流出,水注入太快就会溢出。
  • 应用场景:在高并发网络请求场景中,防止服务器因请求过多负载过高。如Web服务器对API接口请求进行限流,保护服务器稳定运行。

第三方库

  • p-limit:轻量级Promise并发控制库。可设置最大并发数,简单易用。示例代码:
const limit = require('p-limit')(2); // 设置最大并发数为2
const tasks = [
    () => new Promise((resolve) => setTimeout(() => { console.log('Task 1'); resolve(); }, 1000)),
    () => new Promise((resolve) => setTimeout(() => { console.log('Task 2'); resolve(); }, 1500)),
    () => new Promise((resolve) => setTimeout(() => { console.log('Task 3'); resolve(); }, 2000))
];
const runTasks = async () => {
    const results = await Promise.all(tasks.map(task => limit(task)));
};
runTasks();
  • async - pool:支持多种并发策略的Promise并发控制库,能灵活控制并发任务数量、处理任务队列等 。

番外

如何在前端实现文件的断点续传,并确保大文件安全可靠上传?

回答重点

为了在前端实现文件的断点续传,并确保大文件能够安全可靠地上传,我们需要以下关键技术和步骤:

  1. 文件分块上传 (Chunked Upload) :将大文件分成多个小块,每个小块可以独立上传,这样即便在上传过程中网络中断,我们也只需要重新上传未完成的小块,而不必重新上传整个文件。
  2. 断点续传标识 (Resume Identifier) :为了实现断点续传,我们需要一个唯一的标识符来标记已经上传的分块。通常可以通过文件的名字、大小和哈希值生成这样一个标识符。
  3. MD5 校验 (MD5 Checksum) :在上传每个分块之后,计算其 MD5 校验值,并在服务器端进行校验,确保分块在传输过程中没有被篡改或损坏。
  4. 并发上传 (Concurrent Uploads) :利用浏览器的并发上传能力,可以同时上传多个分块,提高上传速度。
  5. 进度监控 (Progress Monitoring) :利用 XMLHttpRequest 或 Fetch API 的进度事件,可以实时跟踪上传进度,并在前端界面上显示。
扩展知识
  1. 文件分块实现:我们可以利用 JavaScript 的 File 对象和 Blob.prototype.slice 方法将文件分成多个小块。例如:
const chunkSize = 5 * 1024 * 1024; // 每块5MB
const file = document.getElementById('fileInput').files[0];
const totalChunks = Math.ceil(file.size / chunkSize);

for (let i = 0; i < totalChunks; i++) {
    const start = i * chunkSize;
    const end = Math.min(file.size, start + chunkSize);
    const chunk = file.slice(start, end);
    // 这里可以执行上传操作
}
  1. 断点续传标识生成:通过文件的名字、大小和哈希值生成唯一标识符。例如:
function generateIdentifier(file) {
    return `${file.name}-${file.size}-${file.lastModified}`;
}
  1. MD5 校验:使用 js-md5 库或 Web Cryptography API 实现分块的 MD5 校验:
async function calculateMD5(fileChunk) {
    const arrayBuffer = await fileChunk.arrayBuffer();
    const hashBuffer = await crypto.subtle.digest('MD5', arrayBuffer);
    const hashArray = Array.from(new Uint8Array(hashBuffer));
    const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    return hashHex;
}
  1. 并发上传:通过 Promise.all 同时上传多个分块提高上传速度:
const promises = [];
for (let i = 0; i < totalChunks; i++) {
    const chunk = file.slice(i * chunkSize, (i + 1) * chunkSize);
    const promise = uploadChunk(chunk);
    promises.push(promise);
}
await Promise.all(promises);
  1. 进度监控:使用 XMLHttpRequest 或 Fetch API 的进度事件,可以更新前端界面的上传进度条。例如:
function uploadChunk(chunk) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('POST', '/upload', true);
        xhr.upload.addEventListener('progress', (event) => {
            if (event.lengthComputable) {
                const percentComplete = (event.loaded / event.total) * 100;
                console.log(`Chunk upload: ${percentComplete}% complete`);
            }
        });
        xhr.onload = () => {
            if (xhr.status === 200) {
                resolve(xhr.responseText);
            } else {
                reject(new Error('Chunk upload failed'));
            }
        };
        xhr.send(chunk);
    });
}

怎么用 JS 实现大型文件上传?要考虑哪些问题?

在前端实现大型文件上传,需要考虑以下几个问题:

  • 分片上传:将大文件切割成多个小块进行上传,可以避免一次性上传大文件导致的上传时间过长,网络中断等问题。通常情况下,每个块大小为 1MB 左右。
  • 断点续传:由于网络等因素,上传过程中可能出现中断,此时需要能够从中断的地方恢复上传。
  • 并发上传:多个文件同时上传,需要对上传队列进行管理,保证上传速度和顺序。
  • 上传进度显示:及时显示上传进度,让用户知道上传进度和状态。

可以通过使用第三方库来实现大型文件上传,比如 Plupload、Resumable.js 等。

以下是一个使用 Plupload 实现大型文件上传的示例:

<!-- 引入 Plupload 的 JavaScript 和 CSS 文件 -->
<script type="text/javascript" src="plupload.full.min.js"></script>
<link rel="stylesheet" href="plupload.css">

<!-- 上传控件的容器 -->
<div id="uploader">
    <p>Your browser doesn't have Flash, Silverlight or HTML5 support.</p>
</div>
<!-- 初始化上传控件 -->
<script type="text/javascript">
var uploader = new plupload.Uploader({
    browse_button: 'uploader', // 上传控件的容器
    url: '/upload', // 上传文件的 URL
    multi_selection: false, // 是否允许同时上传多个文件
    filters: {
        max_file_size: '100mb', // 最大上传文件大小
        mime_types: [
            { title: 'Image files', extensions: 'jpg,jpeg,gif,png' },
            { title: 'Zip files', extensions: 'zip,rar' }
        ]
    },
    init: {
        // 添加文件到上传队列之前触发的事件
        BeforeUpload: function (up, file) {
            console.log('BeforeUpload:', file.name);
        },
        // 开始上传文件时触发的事件
        UploadFile: function (up, file) {
            console.log('UploadFile:', file.name);
        },
        // 上传进度改变时触发的事件
        UploadProgress: function (up, file) {
            console.log('UploadProgress:', file.percent);
        },
        // 上传成功时触发的事件
        FileUploaded: function (up, file, info) {
            console.log('FileUploaded:', file.name, info.response);
        },
        // 上传出错时触发的事件
        Error: function (up, err) {
            console.log('Error:', err.message);
        }
    }
});

// 初始化上传控件
uploader.init();
</script>

在后端,需要根据上传控件发送的请求,来实现文件的接收和存储。具体实现方式视具体情况而定,可以使用 SpringMVC、Express.js 等框架来实现。同时,也需要考虑上传文件大小限制、上传速度控制等问题。

我的远程实习(五)| 教你一招走查网站SEO😉| AITDK

作者 浪遏
2025年3月30日 02:08

前言

大家好啊~ , 今天周日 , 过完今天我又需要投入远程和无尽的课程中了 , 在此刻周日伊始之际 , 不知为何十分亢奋 , 难以入眠 , 遂下床码一点文字 。由于近日接触了 SEO 相关的理论和实践知识

  • 路由相关的
  • SSR 、CSR 、...
  • 。。。

一堆扑面而来 , 又恰巧在前段远程工作时间中 , 我尝试为我所做的 langing page 页做 SEO 优化 , 使用了一框十分 nice 的工具 —— AITDK 。今天太晚 , 就不深入探讨高深的 SEO 了 , 一起看 AITDK

我在掘金搜了下 :唯一篇蜻蜓点水般带过

要不今天在这个不眠之夜 , 我们一起勇闯无人之区吧 ~

邂逅

我想每一个美妙的开始便是官网 , 我们上官网看看 :aitdk.com/extension , 官网做的国际化挺好的 ,它赫然写着“全能 SEO 工具”

指名道姓的举出下面的功能 ~

  • 跟踪网站流量、
  • whois查询、
  • 关键词密度检查
  • 以及进行SEO分析。

功能较全 , 我们挑选几个成玩玩

直接免费添加到浏览器 , 出现如下图标

做下面实验 ~ , 拿我们的掘金兄做做实验

  1. 检查网站首页的 title / description / Canonical 是否合理

来到掘金首页 , 这文案合理吧。🤡

  1. 开发时候,调整关键词密度 , 一般是做 langding page 的时候 ,这个我们看一个 AI 产品 Monica , 这个产品首页就是 langding page

我发现他的关键词密度有点低 , 哈哈 ~ , 没调整好吗 ?🤡

  1. SSR 检测

掘金是一个 CSR 和 SSR 混合开发的网站的 , 通过 vue devtools 我们知道掘金是 vue 写的 他也有 SSR 框架 nuxt , 可以观察下面现象

刷新的时候中间是空白的 , 两侧不变 , 这是不是有理由推测中间采用客户端渲染 , 不变的采取服务端渲染(有缓存)呢 ? 🤡 , 使用 AITDK 一侧便知道

在 issue 里面开启 SSR 和 CSR 的检测

检测如下 :

果然如此 !~

总结

有时间 , 我们一起探讨下 SEO ~

vue react路由底层原理 | Hash & histroy 模式

作者 浪遏
2025年3月30日 00:30

一、前端路由原理

1. SPA(单页面应用)

单页面应用(Single Page Application)是一种只加载一次 HTML 页面并在用户与应用交互时动态更新页面内容的 Web 应用。浏览器在首次加载时会获取所需的 HTML、CSS 和 JavaScript 文件,之后所有的操作都通过 JavaScript 控制。这种模式非常适合现代 Web 应用,因为它可以提供流畅的用户体验。

我们结合 react 项目来理解就是 index.html 页面提供挂载点和 js 的引用

2. 路由的必要性

对于复杂的 SPA 应用,路由是不可或缺的。路由允许开发者在不同的页面之间切换,而无需重新加载整个页面。Vue 和 React 都提供了强大的路由功能,无论React 还是 Vue 的 router 都支持两种模式:Hash 模式和 History 模式。

我们先来看看 Hash 和 histroy 的核心差异

二、Hash 模式

1. 定义

Hash 模式是一种通过在 URL 中添加 # 符号来实现前端路由的方式。浏览器在检测到 # 后的路径变化时,不会重新发起请求,而是触发 onhashchange 事件。

2. 网页 URL 组成部分

  • location.protocol:协议(如 http:
  • location.hostname:主机名(如 127.0.0.1
  • location.host:主机(如 127.0.0.1:8001
  • location.port:端口号(如 8001
  • location.pathname:访问页面(如 01-hash.html
  • location.search:搜索内容(如 ?a=100&b=20
  • location.hash:哈希值(如 #/aaa/bbb

3. Hash 的特点

  • 无刷新跳转:Hash 变化不会触发页面刷新,因此不会重新加载资源。
  • 浏览器兼容性:Hash 模式兼容性较好,几乎所有现代浏览器都支持。
  • SEO 限制:Hash 模式下的 URL 不会被搜索引擎爬虫完全解析,因此不利于 SEO。
  • 灵活性:Hash 模式只能修改 # 后面的部分,因此只能跳转到同文档的 URL。

4. Hash 模式的应用场景

  • To B 系统:企业内部系统通常对 SEO 要求不高,Hash 模式简单易用,适合快速开发。
  • 小型项目:对于功能简单的项目,Hash 模式可以快速实现路由功能。

5. 案例与代码示例

以下是一个简单的 Hash 模式实现:

// 监听 hash 变化
window.addEventListener('hashchange', function() {
  console.log('Hash changed to:', location.hash);
  // 根据 hash 值加载对应的内容
  loadContent(location.hash);
});

// 初始加载
window.addEventListener('load', function() {
  console.log('Initial hash:', location.hash);
  loadContent(location.hash);
});

function loadContent(hash) {
  // 根据 hash 值加载内容
  if (hash === '#/home') {
    console.log('Loading home page...');
  } else if (hash === '#/about') {
    console.log('Loading about page...');
  } else {
    console.log('Loading default page...');
  }
}

三、History 模式

1. 定义

History 模式是 HTML5 提供的新特性,允许开发者直接更改前端路由,更新浏览器 URL 地址而不重新发起请求。

2. 与 Hash 模式的区别

通过一个例子来说明 Hash 和 History 模式在浏览器刷新时的区别:

  • 正常页面浏览
    • https://github.com/xxx 刷新页面
    • https://github.com/xxx/yyy 刷新页面
    • https://github.com/xxx/yyy/zzz 刷新页面
  • History 模式
    • https://github.com/xxx 刷新页面
    • https://github.com/xxx/yyy 前端跳转,不刷新页面
    • https://github.com/xxx/yyy/zzz 前端跳转,不刷新页面

3. History 的 API

  • history.pushState(data, title [, url]):向历史记录堆栈顶部添加一条记录。
  • history.replaceState(data, title [, url]):更改当前的历史记录。
  • history.state:存储 pushStatereplaceState 的数据。
  • window.onpopstate:响应 pushStatereplaceState 的调用。

4. History 的特点

  • 无刷新跳转:通过 pushStatereplaceState 实现无刷新跳转。
  • SEO 友好:URL 更加规范,适合搜索引擎爬虫解析。
  • 服务端支持:需要服务端配置,否则刷新页面会导致 404 错误。

5. History 模式的应用场景

  • To C 系统:面向用户的系统通常对 SEO 要求较高,History 模式更适合。
  • 大型项目:功能复杂的项目需要更灵活的路由管理,History 模式可以提供更好的用户体验。

6. 案例与代码示例

以下是一个简单的 History 模式实现:

// 使用 pushState 添加历史记录
function navigateTo(url) {
  history.pushState({}, '', url);
  loadContent(url);
}

// 监听 popstate 事件
window.addEventListener('popstate', function() {
  loadContent(location.pathname);
});

function loadContent(path) {
  // 根据路径加载内容
  if (path === '/home') {
    console.log('Loading home page...');
  } else if (path === '/about') {
    console.log('Loading about page...');
  } else {
    console.log('Loading default page...');
  }
}

// 初始加载
loadContent(location.pathname);

7. 服务端配置示例(Nginx)

为了支持 History 模式,需要在服务端配置中确保所有路由都指向主页面:

server {
  listen 80;
  server_name example.com;

  location / {
    try_files $uri /index.html;
  }
}

四、两者的选择

在实际项目中,选择 Hash 模式还是 History 模式需要根据具体需求和场景来决定:

  • To B 系统:推荐使用 Hash 模式,因为它相对简单,且对 URL 规范不敏感。
  • To C 系统:可以考虑使用 History 模式,但需要服务端支持。
  • 成本与收益:能用简单的解决方案就不要用复杂的,尽量平衡开发成本和用户体验。

五、性能优化与最佳实践

1. 性能优化

  • 减少不必要的渲染:通过路由守卫或懒加载减少不必要的组件渲染。
  • 缓存策略:合理使用缓存策略,减少网络请求。
  • 代码分割:将路由相关的代码进行分割,按需加载。

2. 最佳实践

  • 统一的路由管理:使用路由库(如 Vue Router 或 React Router)统一管理路由。
  • 路由验证:在路由切换时进行权限验证,确保用户只能访问授权的页面。
  • 错误处理:捕获路由切换时的错误,提供友好的错误提示。

六、总结

Hash 和 History 模式各有优缺点,了解它们的区别和适用场景是前端开发中的重要技能。本文对前端路由原理进行了深入探讨,希望能为大家提供一些帮助。通过合理选择和优化,可以为用户提供一个流畅且高效的用户体验。

参考资料

我的远程实习(四)| Ailln叫我docker部署项目,我顺便填了以前的坑

作者 浪遏
2025年3月28日 22:09

前言

大家好啊~ , 好久不见 , 又到周末了😏; 趁着周末时间总结一下这个星期的实习吧 : 这一周很忙 , 因为要上线项目 , 所以比较急😫 , 但是周末了 , 应该好好休息😡 于是 , 我闲着来给大家闭坑了!!!

这周,算法工程师Ailln安排了任务给我 , 叫我部署新鲜出炉的Ai项目 ,提供了多种可选方案

  • 方案 A(服务器构建/服务器运行)  是最简单的方法,但是需要服务器有较好的性能,否则构建镜像会非常慢。
  • 方案 B(本地构建/推送远程仓库/服务器运行)  是最复杂的方法,但是可以解决服务器性能较差的问题,以及多人协作起来更加方便。
  • 方案 C(本地构建/手动上传/服务器运行)  是最通用的方法,也可以解决服务器性能较差的问题,但是需要手动上传镜像。

在服务器构建是最简单的方案 , 而在本地构建则需要你电脑上有docker环境 , 而我的电脑是windows , 安装docker有点靠运气(因为相比linux , 太坑了),还好我之前已经安装好了🤡 , 顶着C盘13G的压力硬是没有把docker环境(WSL、ubuntu、docker Desktop)删除掉 , 这不 , 它表现的机会来了~

没安装的掘友可以参照我之前写的文章

Windows上安装Docker👈 | 跳出 Linux 圈,Docker 在 Windows 撒欢!🚀🚀🚀 - 掘金 (juejin.cn)

他表现如下:

Error response from daemon: Get "registry-1.docker.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)

头顶飘过一万个草泥🐎,你就这样表现吗 ?

含泪搜寻资料无果 ,Ailln说:没有配置代理!注意是代理到docker的内部!!!

下面是docker Desktop的界面 , 不仅仅要在这里面配置阿里云的镜像(或其他)

image.png

你还要把代理部署到docker的内部

image.png

而代理的地址可以在你的魔法工具上获取 , 这里就不展现了 , 毕竟这是魔法 , 可以查查你的魔法工具的代理地址如何获取。

docker 打包成功了 , 从此也是开启了 docker 容器化部署之路 ,太爽啦 ~

下面给出解决上述问题的体系化解决方案 , 希望能够帮到各位 ~

解决网络问题

如果你在 windows 上已经安装好了 docker , 在拉取镜像的时候报一下错误

docker 报错 :Error response from daemon: Get "registry-1.docker.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)

这里给出针对 Windows 系统的完整解决方案,包含具体操作步骤和深度优化建议:


一、Windows 系统专用解决方案

1. 配置国内镜像加速(最有效)

步骤

  1. 获取镜像加速地址
    • 阿里云镜像加速器(需注册获取专属地址
    • 其他公共镜像源:[https://docker.mirrors.ustc.edu.cn](https://docker.mirrors.ustc.edu.cn)(中科大)
  2. 配置镜像源
    • 通过 Docker Desktop 界面配置
  3. 右键任务栏 Docker 图标 → SettingsDocker Engine
  4. 在配置文件中添加 registry-mirrors 项: { "registry-mirrors": [ "https://your-aliyun-mirror.mirror.aliyuncs.com", "https://docker.mirrors.ustc.edu.cn" ] }
  5. 点击 Apply & Restart 保存并重启 Docker
    • 直接修改配置文件(路径:%USERPROFILE%\.docker\daemon.json
  6. 验证配置
docker info | Select-String "Mirror"  # 查看生效的镜像源
2. 修复 DNS 解析问题

操作步骤

  1. 修改 DNS 服务器
    • 打开 控制面板网络和共享中心 → 当前网络连接 → 属性IPv4 属性
    • 手动设置 DNS:
首选 DNS:8.8.8.8(Google)
备用 DNS:114.114.114.114(国内公共DNS)
- 保存后执行命令刷新缓存:
ipconfig /flushdns
  1. 检查域名解析
3. 处理代理冲突

场景:企业网络或使用 VPN 时的代理配置

  • 为 Docker 配置代理
    1. 打开 Docker Desktop → SettingsResourcesProxies
    2. 填写代理信息:
HTTP Proxy: http://proxy.example.com:8080
HTTPS Proxy: http://proxy.example.com:8080
No Proxy: localhost,127.0.0.1,.internal
3. 重启 Docker
  • 系统级代理排除
    • 在系统代理设置中排除 Docker 相关域名:
registry-1.docker.io, auth.docker.io
4. 解决 WSL2 网络问题

症状:Windows 宿主机正常但 WSL2 内无法联网

  • 重置 WSL2 网络
wsl --shutdown        # 关闭所有WSL实例
netsh winsock reset   # 重置网络堆栈(需管理员权限)
netsh int ip reset    # 重置IP配置

重启电脑后再次启动 Docker

  • 启用 WSL2 桥接模式
    创建 %USERPROFILE%\.wslconfig 文件并添加:
[wsl2]
networkingMode=bridged  # 使用宿主机网络模式

5. 系统时间与证书同步
  • 校准时间
    1. 右键任务栏时间 → 调整日期/时间
    2. 开启 自动设置时间自动设置时区
    3. 手动同步:
w32tm /resync  # 强制时间同步(需管理员权限)
  • 更新根证书
    下载 最新根证书包,替换 %ProgramFiles%\Docker\Docker\resources\certs.pem

二、进阶诊断步骤

1. 网络连通性测试
# 测试HTTPS连接(需安装curl)
curl -v https://registry-1.docker.io/v2/  

# 测试端口连通性
Test-NetConnection registry-1.docker.io -Port 443
2. 防火墙/杀毒软件排查
  • 暂时关闭防火墙
Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled False
  • 杀毒软件白名单:将 Docker 相关进程(如 dockerd.exe, com.docker.backend.exe)加入例外

三、备用解决方案

1. 离线安装镜像
  • 在可联网机器导出镜像
docker pull alpine:latest
docker save -o alpine.tar alpine:latest
  • 在目标机器导入
docker load -i alpine.tar
2. 使用 Docker 内置诊断工具
docker diagnose          # 生成诊断报告
docker --debug pull nginx  # 调试模式查看详细错误

四、长效优化建议

  • 企业级方案:部署 Harbor 私有镜像仓库
  • 网络优化:使用 VPN 全局路由或 Socks5 代理穿透
  • 版本升级:确保 Docker Desktop 为最新版本(官网下载

附:Windows 特有错误处理

错误现象 解决方案
WSL 2 installation is incomplete 安装 WSL2 内核更新包
Hyper-V 未启用 管理员权限运行:Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All
端口被其他进程占用 `netstat -ano

通过以上步骤,90% 的 Windows Docker 网络问题可被解决。如仍存在问题,可提供以下信息进一步排查:

docker version        # 版本信息
docker info           # 系统配置
Get-NetAdapter        # 网络适配器状态

CSS篇:flex 布局👈👈👈

作者 浪遏
2025年3月28日 20:18

认识 flex 布局

Flex布局 : 简单通过容器规则 , 尽量不操作子元素

Flex布局的核心概念包括两个主要部分:Flex容器(Flex Container)和Flex项目(Flex Item)。

容器通过设置display: flexdisplay: inline-flex来启用,而其直接子元素会自动成为Flex项目

如下图 : 设置 container 容器 , 其子元素自动成为他的项目

容器默认存在两条轴: 主轴(Main Axis)交叉轴(Cross Axis) ,其中主轴方向为水平方向(默认为row),交叉轴为垂直方向(默认为column)

如下图:容器会有两个方向轴 , 默认主轴为水平方向向 、交叉轴为垂直方向

其中定义左上角为起点

通过容器属性和项目属性 , 我们可以实现很多更灵活、响应式的页面布局方式,适用于各种屏幕尺寸和设备。

容器属性

我们可以通过容器属性来实现 , 容器属性有六个

  1. flex-direction:决定主轴的方向,例如rowrow-reversecolumncolumn-reverse
  2. justify-content:定义主轴上的对齐方式,例如flex-startflex-endcenter等。
  3. align-items:定义交叉轴上的对齐方式,例如flex-startflex-endcenter等。
  4. flex-wrap:控制子元素是否换行,取值包括nowrap(不换行)、wrap(换行)和wrap-reverse(反向换行)。
  5. align-content:当多行排列时,定义多行的对齐方式。
  6. flex-flow:是flex-directionflex-wrap的简写形式,默认值为row nowrap

flex-direction

  • 定义主轴的方向(即子元素排列的方向)。默认值为 row,表示主轴为水平方向。可以通过设置为 column 将主轴改为垂直方向。
  • 当主轴方向为 row 时,子元素从左到右排列;当主轴方向为 column 时,子元素从上到下排列。

justify-content

控制子元素在主轴上的对齐方式。其值包括:

  • flex-start:子元素靠主轴起点对齐。
  • flex-end:子元素靠主轴终点对齐。
  • center:子元素居中对齐。
  • space-between:子元素在主轴上均匀分布,第一个子元素靠主轴起点,最后一个子元素靠主轴终点。
  • space-around:子元素在主轴上均匀分布,每个子元素两侧的距离相等。
  • space-evenly:子元素在主轴上均匀分布,两端与容器边缘的距离相等。

两端对齐

项目两侧间隔相等

align-items

控制子元素在交叉轴上的对齐方式。其值包括:

  • flex-start:子元素靠交叉轴起点对齐。
  • flex-end:子元素靠交叉轴终点对齐。
  • center:子元素居中对齐。
  • baseline:子元素的基线对齐。
  • stretch:子元素拉伸以填充整个容器的高度。

定义子元素在交叉轴的前后居中的对其方式

项目首行文字对齐

如果没有指定交叉轴的容器 , 他将会占满整个容器

这三个比较常用

  • flex-direction 定义主轴方向,影响子元素的排列方向。
  • justify-content 控制主轴上的对齐方式。
  • align-items 控制交叉轴上的对齐方式。

flex-wrap

  • wrap(换行)
  • nowrap(不换行)
  • wrap-reverse(行序反向)

超出轴线自动换行

默认不换行

align-content

  • flex-start
  • flex-end
  • center
  • stretch
  • space-between
  • space-around

项目属性

在Flex布局中, Flex项目(Flex Item) 的属性用于控制单个项目在容器中的行为。

order

作用:控制Flex项目在容器中的排列顺序,根据项目数值定义元素的排序

默认值0

说明

  • Flex项目默认按照它们在HTML中的顺序排列。
  • order属性允许你改变这种顺序。数值越小,项目越靠前;数值越大,项目越靠后。
  • order可以为负值。
  • 示例

align-self

允许项目自身有单独的交叉轴对其方式 , 可以设置自己的 align-item

其中默认值继承容器属性

flex

项目属性 flex 属性其实包括 :

  • flex-grow
  • flex-shrink
  • flex-basis

常用值flex: initial;: 0 1 auto

Flex项目不会放大,但会缩小。

Flex项目的初始大小由内容决定。

flex-grow

作用:定义Flex项目在容器中分配剩余空间的比例。

默认值0

说明

  • 如果所有项目的flex-grow为0,则不会分配剩余空间。
  • 如果某个项目的值为1,它会占据剩余空间的一部分;如果值为2,它会占据其他项目的两倍空间。

示例

flex-shrink

作用:定义Flex项目在空间不足时的缩小比例。

默认值1

说明

  • 如果所有项目的宽度总和超过容器宽度,项目会按比例缩小。
  • flex-shrink: 0;表示项目不会缩小。

示例

flex-basis

作用:定义Flex项目在分配多余空间之前的初始大小。

默认值auto

说明

  • flex-basis可以设置为固定值(如200px)、百分比(如50%)或关键字(如auto, content)。
  • auto: Flex项目的初始大小由其内容决定。

示例

以下是关于 flex:1flex:autoflex:none 的详细对比分析,涵盖其底层逻辑、行为差异及适用场景:


一、flex:1

1. 语法定义

flex:1flex-grow:1; flex-shrink:1; flex-basis:0% 的简写形式。

2. 核心行为
  • 空间分配逻辑
    元素的初始尺寸由 flex-basis:0% 决定,即默认不占用任何空间。剩余空间会根据 flex-grow 的权重比例分配。例如,若两个元素均设置 flex:1,则平分剩余空间。
  • 收缩能力
    当容器空间不足时,元素会按 flex-shrink:1 的比例收缩,但最小尺寸被限制为 0%(可能导致内容截断)。
3. 典型场景
  • 等分布局:多个子元素需均分容器宽度(如导航栏按钮)。
  • 内容无关的弹性区域:元素尺寸完全由容器剩余空间决定,忽略内容本身大小(如侧边栏和主内容区)。

二、flex:auto

1. 语法定义

flex:auto 等价于 flex-grow:1; flex-shrink:1; flex-basis:auto

2. 核心行为
  • 空间分配逻辑
    元素的初始尺寸由 flex-basis:auto 决定(即元素本身内容或显式设置的宽度/高度)。剩余空间按 flex-grow 分配,但需叠加初始尺寸。例如,若元素 A 的 flex-basis200px,元素 B 的 flex-basis100px,剩余空间将按 200px:100px 的比例分配。
  • 收缩能力
    元素可收缩至小于初始尺寸(但不会小于 min-content),适合动态适配内容。
3. 典型场景
  • 内容优先的弹性布局:需根据内容动态调整(如卡片布局中部分卡片需固定宽度,另一部分自适应)。
  • 混合尺寸分配:需兼顾内容尺寸和剩余空间分配(如表单输入框与按钮组合)。

三、flex:none

1. 语法定义

flex:none 对应 flex-grow:0; flex-shrink:0; flex-basis:auto,即元素完全不可伸缩。

2. 核心行为
  • 固定尺寸
    元素尺寸严格由内容或显式设置的 width/height 决定,既不扩展也不收缩。
  • 溢出处理
    若容器空间不足,元素可能溢出,需结合 overflow 属性控制显示。
3. 典型场景
  • 固定尺寸元素:如图标按钮、固定宽高的广告位。
  • 防止内容截断:需确保元素内容完整显示(如导航栏中的文字标签)。

四、对比总结

属性 等效值 空间分配逻辑 收缩能力 适用场景
flex:1 1 1 0% 完全依赖剩余空间,忽略初始尺寸 可收缩至 0% 等分布局、弹性容器填充
flex:auto 1 1 auto 结合初始尺寸和剩余空间按比例分配 可收缩至最小内容 动态内容适配、混合尺寸布局
flex:none 0 0 auto 完全固定尺寸,不参与空间分配 不可收缩 固定尺寸元素、防止内容截断

五、关键差异示例

场景 1:容器有剩余空间
  • flex:1:两个元素均分剩余空间,忽略各自内容宽度。
  • flex:auto:元素按初始尺寸(如 200px vs 100px)分配剩余空间,导致最终宽度比例不同。
  • flex:none:元素保持初始尺寸,剩余空间由其他弹性项目填充。
场景 2:容器空间不足
  • flex:1:元素按比例收缩,可能截断内容。
  • flex:auto:元素收缩至最小内容宽度(如文字不换行)。
  • flex:none:元素不收缩,可能导致容器溢出。

六、选择建议

  1. 优先 flex:1:当需完全控制空间分配且不依赖内容尺寸时(如响应式栅格)。
  2. 优先 flex:auto:当需兼顾内容动态适配和弹性扩展时(如复杂表单布局)。
  3. 优先 flex:none:当需固定元素尺寸或防止内容破坏布局时(如固定导航栏、图标)。

通过理解这些差异,可更精准地利用 Flexbox 实现复杂布局需求。

总结

Flex布局的主要特点包括:

  1. 空间分配:能够自动调整子元素的大小以适应容器的宽度或高度,从而实现灵活的布局。
  2. 方向无关:布局与方向无关,主轴和交叉轴可以自由切换,方便实现复杂的布局需求6
  3. 对齐方式:通过justify-contentalign-items等属性,可以控制子元素在主轴和交叉轴上的对齐方式。
  4. 换行与环绕flex-wrap属性允许子元素在容器内换行,flex-wrap: wrap可实现环绕布局6

Flex布局适用于简单的线性布局,但对于更复杂的布局场景,如多层嵌套或不规则布局,可能需要结合其他布局模式(如Grid)来实现

参考

❌
❌