详解Next Auth:自定义邮箱密码登录注册、Github、Notion授权 Convex集成

news2024/11/24 20:13:21

最近用NextJS框架做全栈项目做的很顺手,现在想给项目加上登录、注册、鉴权拦截、分角色路由控制等功能,并接入Github、Notion等第三方登录。
可以使用NextJS官方提供的Auth框架实现。

Intro

阅读本篇,你将学会:
1、登录、注册等逻辑,和如何接入第三方(以Github、Notion为例)
2、建立用户、角色等数据模型,存储用户数据
3、公开、私有路由守卫

技术栈

  • NextJs(前端框架) v14.2.3
  • React(前端框架) 18
  • NextAuth(鉴权框架) v5.0.0-beta.18
  • Convex (后端接口 + ORM)

背景知识学习

在开始实现之前,需要知道NextJS中服务端组件和客户端组件的概念。
NextJS中使用”use client“和”use server“标识服务端和客户端组件,客户端运行在浏览器中,服务端运行在服务器端。不标识时,默认为服务端组件。
服务端组件用于异步请求等,负责与服务端交互、请求数据等,客户端组件主要用于和用户交互。React的钩子也有明确的区分,比如useEffect等钩子只能在客户端组件中使用。

实现步骤

代码框架搭建

npx create-next-app@latest  

使用NextAuth(v5版本)

npm install next-auth@beta  

开始之前,需要在环境变量文件.env.local中配置变量

AUTH_SECRET=**********************

Credentials

我们首先实现一个简单的账号密码注册、登录、登出。
参考: Credentials

1.基础配置

在项目根目录下,新建auth.js文件,并写入以下内容:

import NextAuth from "next-auth"
import Credentials from "next-auth/providers/credentials"
// Your own logic for dealing with plaintext password strings; be careful!
import { saltAndHashPassword } from "@/utils/password"
 
export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [
    Credentials({
      authorize: async (credentials) => {
        let user = null
 
       // logic to salt and hash password
        const pwHash = saltAndHashPassword(credentials.password)
 
        // logic to verify if user exists
        user = await getUserFromDb(credentials.email, pwHash)
 
        if (!user) {
          // No user found, so this is their first attempt to login
          // meaning this is also the place you could do registration
          throw new Error("User not found.")
        }
 
        // return user object with the their profile data
        return user
      },
    }),
  ],
})

在根目录下,新建文件middleware.ts

import NextAuth from 'next-auth';

import {
DEFAULT_LOGIN_REDIRECT,
apiAuthPrefix,
authRoutes,
publicRoutes,
} from "@/routes"

import { auth } from './auth';
export default auth((req) => {

const { nextUrl } = req;
// console.log("NEXT URL" + nextUrl.pathname)
const isLoggedIn = !!req.auth;
const isApiAuthRoute = nextUrl.pathname.startsWith(apiAuthPrefix);
const isPublicRoutes = publicRoutes.includes(nextUrl.pathname);
const isAuthRoute = authRoutes.includes(nextUrl.pathname);
if (isApiAuthRoute) {
// DO NOTHING!
return null;

}
if (isAuthRoute) {
if (isLoggedIn) {
return Response.redirect(new URL(DEFAULT_LOGIN_REDIRECT, nextUrl))
} else {
return null;
}
}

if (!isLoggedIn && !isPublicRoutes) {

return Response.redirect(new URL("/auth/login", nextUrl))

}

})
// invoke the middle ware!

export const config = {

matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"],

};

routes.ts

// Public Routes
export const publicRoutes = [
"/",
]
// redirect logged in users to /settings
export const authRoutes = [
"/auth/login",
"/auth/register",
]
export const apiAuthPrefix = "/api/auth"
export const DEFAULT_LOGIN_REDIRECT = "/dashboard"

middleware.ts为保留文件名,其中config变量定义了触发中间件方法的匹配规则。该文件中,定义了auth方法的过滤器。
route.ts中定义公开路径、用于鉴权的路径、鉴权接口前缀及默认重定向地址。
在过滤方法中,返回null说明无需执行权限检查。对于公开路径及鉴权接口,无需登录即可访问。登录后,再访问注册和登录页面,会自动重定向到DEFAULT_LOGIN_REDIRECT定义的/dashboard路由中。
配置NextAuth路由:
api/auth/[...nextauth]/route.ts

import { handlers } from "@/auth"
export const { GET, POST } = handlers

2.注册页面

实现形如下图的注册页面,核心为可提交的表单,包含name、email、password等字段。

image.png

使用zod进行字段的合法性校验。在schemas/index.ts中,定义注册使用的schema:

import * as z from "zod"
export const RegisterSchema = z.object({
    email: z.string().email({
        message: "Email is Required."
    }),
    password: z.string().min(6,{
        message: "Minimum 6 characters required",
    }),
    name: z.string().min(1,{
        message: "Name is Required"
    })
})

注册页面代码(部分):

"use client"
import { useState, useTransition } from "react"
import { cn } from "@/lib/utils"
import * as z from "zod"
import { zodResolver } from "@hookform/resolvers/zod"
import { register } from "@/actions/register"
interface RegisterFormProps extends React.HTMLAttributes<HTMLDivElement> { }
export function RegisterForm({ className, ...props }: RegisterFormProps) {
  const [isPending, startTransition] = useTransition();
  const [error, setError] = useState<string | undefined>("");
  const [success, setSuccess] = useState<string | undefined>("");
  const form = useForm<z.infer<typeof RegisterSchema>>({
    resolver: zodResolver(RegisterSchema),
    defaultValues: {
      name: "",
      email: "",
      password: ""
    }
  });
  async function onSubmit(values: z.infer<typeof RegisterSchema>) {
    setError("")
    setSuccess("")
    startTransition(() => {
      register(values).then((data) => {
        setError(data.error)
        setSuccess(data.success)
      })
    })
  }
  return (
    <div className={cn("grid gap-6", className)} {...props}>  
          <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
          // form field inputs
            <Button type="submit" className="w-full" disabled={isPending}>Create an account</Button>
          </form>
    </div>
  )
}

actions/register.ts 处理注册用户入库:

"use server";

import * as z from "zod"
import { RegisterSchema } from "@/schemas";
import  bcrypt  from "bcryptjs"
import { api } from "@/convex/_generated/api";
import { fetchMutation, fetchQuery } from "convex/nextjs";
import { getUserByEmail } from "@/data/user";

export const register = async (values: z.infer<typeof RegisterSchema>) => {
    const validatedFields = RegisterSchema.safeParse(values);
    if (!validatedFields.success) {
        return {
            error: "Invalid fields!"
        }
    }
    const { email, password, name } = validatedFields.data;
    const hasedPassword = await bcrypt.hash(password, 10)
    const existingUser = await getUserByEmail(email)
    if (existingUser) {
        ``
        return {
            error: "Email already in use!"
        }
    }
    await fetchMutation(api.user.create, {
        name,
        email,
        password: hasedPassword
    })
    // TODO : Send verification token email
    return {
        // error: "Invalid fields!",
        success: "User Created"
    }
}

用户在注册页面填写名称、邮箱、密码后,点击submit按钮,客户端组件调用了服务组件方法,先查询邮箱是否被占用,未被占用,将明文密码使用bcryptjs加密后,存入数据库中。

3.用户登录

image.png
同样使用zod进行登录表单的字段的合法性校验。在schemas/index.ts中,定义登录使用的schema:

import * as z from "zod"
export const LoginSchema = z.object({
    email: z.string().email(),
    password: z.string().min(1)
})

注意:不应限制用户填入密码规则。虽然注册时限定了用户填写的密码至少6位,但系统的密码规则有可能变更。

登录页面代码(部分):

"use client"
import { useState, useTransition } from "react"
import { cn } from "@/lib/utils"
import { useForm } from "react-hook-form"
import { LoginSchema } from "@/schemas"
import * as z from "zod"
import { zodResolver } from "@hookform/resolvers/zod"
import { login } from "@/actions/login"
interface LoginFormProps extends React.HTMLAttributes<HTMLDivElement> { }

export function LoginForm({ className, ...props }: LoginFormProps) {
  const [isPending, startTransition] = useTransition();
  const [error, setError] = useState<string | undefined>("");
  const [success, setSuccess] = useState<string | undefined>("");
  const form = useForm<z.infer<typeof LoginSchema>>({
    resolver: zodResolver(LoginSchema),
    defaultValues: {
      email: "",
      password: ""
    }
  });

  async function onSubmit(values: z.infer<typeof LoginSchema>) {
    setError("")
    setSuccess("")
    startTransition(() => {
      login(values).then((data) => {
        if (data && data.error) {
          setError(data.error)
        }
      })
    })
  }
 return (
    <div className={cn("grid gap-6", className)} {...props}>
          <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
            <Button type="submit" className="w-full" disabled={isPending}>Login</Button>
          </form>
    </div>
  )
}

actions/login.ts 登录操作:

"use server";
import { signIn } from "@/auth";
import * as z from "zod"
import { LoginSchema } from "@/schemas";
import { DEFAULT_LOGIN_REDIRECT } from "@/routes";
import { AuthError } from "next-auth";
export const login = async (values: z.infer<typeof LoginSchema>) => {
    const validatedFields = LoginSchema.safeParse(values);
    if (!validatedFields.success) {
        return {
            error: "Invalid fields!"
        }
    }
    const { email, password } = validatedFields.data;
    console.log("EMAIL!" + email + "PASSWORD!" + password)

    try {
        await signIn("credentials", {
            email, password,
            // redirect: true,
            redirectTo: DEFAULT_LOGIN_REDIRECT
        },)
    } catch (error) {
        if (error instanceof AuthError) {
            switch (error.type) {
                case "CredentialsSignin":
                    return { error: "Invalid Credentials!" }
                default:
                    return { error: "Something went wrong!" }
            }
        }
        throw error;
    }
}

可以看到,该操作从@/auth中导入了SignIn方法,第一个参数指定了Provider,实际的授权在Provider定义的authorize方法中完成:

import bcrypt from "bcryptjs";
import NextAuth, { type DefaultSession } from "next-auth";
import Credentials from "next-auth/providers/credentials";
import { LoginSchema } from "./schemas";

export const { auth, handlers, signIn, signOut } = NextAuth({
    providers: [
        Credentials({
            async authorize(credentials) {
                const validatedFields = LoginSchema.safeParse(credentials);
                if (validatedFields.success) {
                    const { email, password } = validatedFields.data;
                    const user = await getUserByEmailFromDB(email);
                    if (!user || !user.password) return null;

                    const passwordsMatch = await bcrypt.compare(password, user.password);
                    if (passwordsMatch) {
                        return {
                            ...user,
                            id: user._id
                        }
                    }
                }
                return null;
            },

        }),]

}

其中,由于密码在注册时使用了bcryptjs加密,所以比较时也要使用bcryptjs提供的match方法。至此,使用邮箱和密码登录注册的简单逻辑已完成。

Github Provider

1. 新建Github Oauth App

https://github.com/settings/developers下,新建Oauth Apps

image.png

Callback url很重要,一定是你的站点host+port,后面配置为Next Auth默认回跳地址/api/auth/callback/github。(我当前配置为开发用,生产时需要改为线上地址。)

image.png
配置完成后,将Client ID 和Client Secret粘贴到配置文件中。
.env.local

GITHUB_CLIENT_ID=****
GITHUB_CLIENT_SECRET=****

2. 配置Github Provider

auth.ts的Providers数组中,加入:

GitHub({
  clientId: process.env.GITHUB_CLIENT_ID,
  clientSecret: process.env.GITHUB_CLIENT_SECRET,
  allowDangerousEmailAccountLinking: true
}),

allowDangerousEmailAccountLinking是允许使用同一邮箱的Github、Notion账号互联。若不配置该属性,使用同一邮箱注册的Notion和Github(或其他第三方)登录将被拦截。当使用的Providers来自于不可靠的OAuth App厂商,须谨慎使用该属性。

3.页面触发Github登录

social.tsx
@/auth.ts中导入登录方法,并在点击按钮时触发即可。

"use client"
import { FaGithub } from "react-icons/fa"
import { SiNotion } from "react-icons/si";
import { Button } from "@/components/ui/button"
import { signIn } from "next-auth/react"
import { DEFAULT_LOGIN_REDIRECT } from "@/routes"

export const Social = () => {
    const onClick = (provider: "github" | "notion") => {
        signIn(provider, {
            callbackUrl: DEFAULT_LOGIN_REDIRECT
        })
    }

    return (
        <div className="flex items-center w-full gap-x-2">
            <Button size="lg" className="w-full" variant="outline" onClick={() => onClick("github")}>
                <FaGithub className="h-5 w-5"></FaGithub>
            </Button>
            <Button size="lg" className="w-full" variant="outline" onClick={() => onClick("notion")}>
                <SiNotion className="h-5 w-5" />
            </Button>


        </div>
    )
}

首次点击时,会跳转到Github询问是否授权。之后会直接点击后跳转鉴权并登录。

Notion Provider

Notion登录的配置方法与Github非常类似。

1. 配置Public Ingegration

前往https://www.notion.so/my-integrations ,新建一个Integration,并设置为公有

image.png
需将回调地址配置为/api/auth/callback/notion

image.png

生成并复制Secrets:

image.png

将Client ID和Secret复制到配置文件中。

AUTH_NOTION_ID=""
AUTH_NOTION_SECRET=""
AUTH_NOTION_REDIRECT_URI="http://localhost:3001/api/auth/callback/notion"

2. 配置Notion Provider

auth.ts的Providers数组中,加入:

 Notion({
      clientId: process.env.AUTH_NOTION_ID,
      clientSecret: process.env.AUTH_NOTION_SECRET,
      redirectUri: process.env.AUTH_NOTION_REDIRECT_URI,
      allowDangerousEmailAccountLinking: true
    }),

需传入redirectUri(该uri必须配置到上一步的Ingegration中)

使用Notion登录

登录的触发方法与Github相同,不赘述。点击登录时,会多出一步,可以选择授权访问的page范围。

image.png

Convex Adapter

参考文档:

  1. Auth.js | Creating A Database Adapter
  2. https://stack.convex.dev/nextauth-adapter

本应用使用了Convex作为后台API提供,所以需要实现Convex Adapter。按https://stack.convex.dev/nextauth-adapter 步骤操作即可。

auth.ts

import NextAuth from "next-auth"
import { ConvexAdapter } from "@/app/ConvexAdapter";
 
export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [],
  adapter: ConvexAdapter,
})

注意避坑!!!!

这里遇到了一个坑,配置了Convex Adapter后,使用邮箱密码再也无法登陆。经过调试发现是通过Credentials登录时,没有调用createSession方法,session没有创建。在Github上搜索后,发现有人遇到类似问题:https://github.com/nextauthjs/next-auth/issues/3970

在auth.ts中增加如下配置:

 session: {
    // Set to jwt in order to CredentialsProvider works properly
    strategy: 'jwt'
  }

即可解决问题!!(卡了好久,真的有点坑……教训就是遇到问题先上github搜搜issue,我一直试图手动自己往数据库塞入一个session未果……)

其他功能

1.回调 callbacks

在回调中,可以向session加入自定义属性

auth.ts

  callbacks: {
    async signIn({ user }) {
      // console.log("AFTER SIGN IN !" + JSON.stringify(user));
      return true;
    },

    async jwt({ token, user, account, profile, isNewUser, session }) {
      if (user) {
        token.id = user.id
      }
      const notionAccount = await getNotionAccount(token.id!! as Id<"users">)
      if (notionAccount) {
        token.notion_token = notionAccount.access_token;
      
      }
      return token;
    },

    async session({ token, session }) {
      if (token.sub && session.user) {
        session.user.id = token.sub;
      }
      if (token.notion_token && session.user) {
        session.user.notion_token = token.notion_token;
      }

      return session;
    },
  },

执行顺序: signIn => jwt => session

可在session中读到在jwt方法返回的token值,可将需要的属性放到session中,如角色、权限等。此处我将Notion的secret放到session中,以便业务代码中取用。

2.自定义Session类型

在auth.ts中加入如下代码,可解决自定义session中的属性报ts错误问题。

declare module "next-auth" {
  /**
   * Returned by `auth`, `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
   */
  interface Session {
    user: {
      role: string;
      notion_token: string;
    } & DefaultSession["user"];
  }
}

3.事件 events

auth.ts

  events: {
    async linkAccount({ user }) {
      if (user.id) {
        await setEmailVerified(user.id)
      }
    },
  },

一个用户(user)可连接多个account(由github、notion等提供的第三方OAuth账号),可设置当连接用户时,将用户设置为邮箱已验证(通过github、notion等可靠app登录的用户无需二次验证邮箱。)

4.页面

auth.ts

  pages: {
    signIn: "/auth/login",
    error: "/auth/error"
  },

指定登录页、自定义鉴权出错页。

5.登出

action/logout.ts

"use server"
import { signOut } from "@/auth"

export const logout = async () => {
    //  Some server stuff
    await signOut();
}

在客户端组件中使用:

import { logout } from "@/actions/logout";
function onClickSignOut() {
    logout();
}

6.使用Session获取用户信息

在客户端调用useSession(),需要在SessionProvider的包裹下使用。

(protected)/layout.ts

import { Suspense } from "react";

import { SessionProvider } from "next-auth/react";

export default async function DashboardLayout({
  children, // will be a page or nested layout
}: {
  children: React.ReactNode;
}) {
  return (
    <SessionProvider>
        <div>{children}</div>     
    </SessionProvider>
  );
}

import { useSession } from "next-auth/react";
const session = useSession();

总结

NextAuth能帮助你快速集成第三方登录,也支持灵活的自定义账号、密码登录。

借助ORM框架和一些中间件,一个NextJS项目已可以完成所有业务功能,不需要再有独立的后端服务。对于小而美的项目,是一个快速落地的理想选择。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1817397.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

论文阅读ReLU-KAN和Wav-KAN

这是我读KAN系列论文的第三篇&#xff0c;今天把两篇论文放在一起写&#xff0c;分别是&#xff1a; ReLU-KAN&#xff1a; https://arxiv.org/abs/2406.02075 Wav-KAN&#xff1a; https://arxiv.org/abs/2405.12832 之所以放在一起&#xff0c;是因为这两篇论文针对KAN的…

使用Spring Boot 3实现邮箱登录/注册接口开发

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f4a5;&#x1f4a5;个人主页&#xff1a;奋斗的小羊 &#x1f4a5;&#x1f4a5;所属专栏&#xff1a;C语言 &#x1f680;本系列文章为个人学习…

MySQL-连接查询

049-内连接之等值连接 案例&#xff1a;查询每个员工所在的部门名称&#xff0c;要求显示员工名、部门名。 select e.ename, d.dname from emp e inner join dept d on e.deptnod.deptno;注意&#xff1a;inner可以省略 select e.ename, d.dname from emp e join dept d on…

AI播客下载:AI在商业中的应用(The AI in Business Podcast)

"AI在商业中的播客"是为那些需要寻找AI机会、将AI能力与战略对齐并实现投资回报的非技术商业领袖准备的。 每周&#xff0c;Emerj人工智能研究公司的首席执行官Daniel Faggella会采访来自财富500强公司和独角兽初创公司的顶级AI高管&#xff0c;以揭示趋势、用例和最…

【译】SQLAlchemy文档:SQLAlchemy 统一教程

SQLAlchemy Unified Tutorial SQLAlchemy 是 Python SQL工具包和ORM&#xff0c;它为应用程序开发人员提供了 SQL 的全部功能和灵活性。它提供了一整套企业级持久性模式&#xff0c;专为高效和高性能的数据库访问而设计。 SQLAlchemy呈现为两层API&#xff1a;Core和ORM&…

龙迅LT9611UXC 2 PORT MIPIDSI/CSI转HDMI 2.1,支持音频IIS/SPDIF输入,支持标准4K60HZ输出

龙迅LT9611UXC描述&#xff1a; LT9611UXC是一个高性能的MIPI DSI/CSI到HDMI2.0转换器。MIPI DSI/CSI输入具有可配置的单端口或双端口&#xff0c;1高速时钟通道和1~4高速数据通道&#xff0c;最大2Gbps/通道&#xff0c;可支持高达16Gbps的总带宽。LT9611UXC支持突发模式DSI视…

GStreamer安装——iOS

安装iOS开发 支持从iOS6开始的所有版本 先决条件 iOS开发需要下载Xcode和iOSSDK。Xcode 可以在App Store或 这里 iOSSDK&#xff0c;如果它还没有包含在您的Xcode版本中&#xff0c; 可以从下载选项卡下的Xcode首选项菜单下载。 最低要求iOS版本为6.0。的最低要求版本 Xcode…

Android帧绘制流程深度解析 (二)

书接上回&#xff1a;Android帧绘制流程深度解析 &#xff08;一&#xff09; 5、 dispatchVsync&#xff1a; 在请求Vsync以后&#xff0c;choreographer会等待Vsync的到来&#xff0c;在Vsync信号到来后&#xff0c;会触发dispatchVsync函数&#xff0c;从而调用onVsync方法…

栈(Stack)汇总

栈简介 栈&#xff08;Stack&#xff09;是只允许在一端进行插入或者删除操作的线性表。它的操作特性可以概括为——后进先出&#xff08;Last In First Out&#xff0c;LIFO&#xff09;。栈顶&#xff08;Top&#xff09;——线性表允许进行插入删除的一端&#xff1b; 栈底…

SwaggerSpy:一款针对SwaggerHub的自动化OSINT安全工具

关于SwaggerSpy SwaggerSpy是一款针对SwaggerHub的自动化公开资源情报&#xff08;OSINT&#xff09;安全工具&#xff0c;该工具专为网络安全研究人员设计&#xff0c;旨在简化广大红队研究人员从SwaggerHub上收集已归档API信息的过程&#xff0c;而这些OSINT信息可以为安全人…

BigDecimal-解决java中的浮点运算

《阿里巴巴 Java 开发手册》中提到&#xff1a;“为了避免精度丢失&#xff0c;可以使用 BigDecimal 来进行浮点数的运算”。浮点数的运算竟然还会有精度丢失的风险吗&#xff1f;确实会&#xff01; 示例代码&#xff1a; float a 2.0f - 1.9f; float b 1.8f - 1.7f; Syst…

制作带有目录的电子书

有时候想自己制作一些.mobi格式的电子书在kindle上进行阅读&#xff0c;有两种简单做法。 方法一&#xff1a; 工具&#xff1a;markdown编辑器、calibre 在markdown编辑器中编辑想要制作电子书的文本&#xff0c;在想要设置目录的地方加一个#&#xff08;我只制作了一级标题…

线程安全问题【snychornized 、死锁、线程通信】

目录 一、线程安全1.1 线程安全问题?1.2 如何解决线程安全问题方法具体如何实现? 1.3 同步方法1.4 同步代码块1.5 总结1.6 售票例子1.8 补充 二、线程安全的集合三、死锁【了解】四、线程通信4.1 同步方法4.2 同步代码块4.3 wait和sleep本篇的思维导图 最后 一、线程安全 1.…

使用PHP对接企业微信审批接口的问题与解决办法(二)

在现代企业中&#xff0c;审批流程是非常重要的一环&#xff0c;它涉及到企业内部各种业务流程的规范和高效运转。而随着企业微信的流行&#xff0c;许多企业希望将审批流程整合到企业微信中&#xff0c;以实现更便捷的审批操作。本文将介绍如何使用PHP对接企业微信审批接口&am…

大数据学习——安装hive

一. 安装准备 1. 打开虚拟机&#xff0c;启动配置了NameNode节点的虚拟机&#xff08;一般和mysql在同一台虚拟机&#xff09;并连接shell 二. 安装 1. 上传hive安装包 hive安装包 提取码&#xff1a;6666 切换到/opt/install_packages目录下 可以将之前解压的rpm文件删除…

高效数据架构:分表流程实践

前言 ​ 随着业务的不断扩展&#xff0c;数据量激增成为不可避免的现象。当数据量达到某一临界点时&#xff0c;单一的数据表可能无法承载如此庞大的数据量&#xff0c;此时就需要考虑进行分库分表的策略。尽管业界普遍认为数据量达到1000万时就应考虑分表&#xff0c;但实际上…

线程池 (重点)概述7大参数理解

目录 1、线程池思想概述 2、什么是线程池&#xff1f; 3、不使用线程池的问题 4、线程池的工作原理 5、线程池实现的API、参数说明 5.1、谁代表线程池&#xff1f; 5.2、如何得到线程池对象 5.3、ThreadPoolExecutor构造器的参数说明 6、线程池常见面试题 6.1、临时线程什么时候…

Vue + Asp.NET调试时出现的证书问题 (OpenSSL)

Vue Asp.NET调试时出现的证书问题 1. 证书过期问题步骤一:创建新的私钥步骤 2: 创建新的证书签名请求&#xff08;CSR&#xff09;步骤 3: 使用 CSR 和 CA 私钥签署新证书步骤 4: 替换或使用新证书 2. 证书不受信任问题步骤: 3. 安全证书不指定使用者可选名称步骤一: 删除已生…

java实现文件的压缩及解压

一、起因 开发中需要实现文件的压缩及解压功能&#xff0c;以满足某些特定场景的下的需要&#xff0c;在此说下具体实现。 二、实现 1.定义一个工具类ZipUtils,实现文件的压缩及解压&#xff0c;代码如下&#xff1a; import java.io.*; import java.nio.charset.Charset; impo…

网络安全等级保护基本要求 第1部分:安全通用要求

基本要求 第三级 安全物理环境 物理位置选择 a) 机房场地应选择在具有防震、防风和防雨等能力的建筑内&#xff1b; b) 机房场地应避免设在建筑物的顶层或地下室&#xff0c;否则应加强防水和防潮措施 物理访问控制 a) 机房出入口应配置电子门禁系统&#xff0c;控制、鉴…