在 Nextjs App Router 中使用 Authjs 进行用户身份验证
golang学习网今天将给大家带来《在 Nextjs App Router 中使用 Authjs 进行用户身份验证》,感兴趣的朋友请继续看下去吧!以下内容将会涉及到等等知识点,如果你是正在学习文章或者已经是大佬级别了,都非常欢迎也希望大家都能给我建议评论哈~希望能帮助到大家!
目录
初始设置
- 安装
-
配置
- nextauthconfig 设置
- 路由处理程序设置
- 中间件
- 在服务器端组件中获取会话
- 在客户端组件中获取会话
- 文件夹结构
实施身份验证:凭据和 google oauth
- 设置 prisma
- 凭证
-
添加 google oauth 提供商
- 设置 google oauth 应用程序
- 设置重定向 uri
- 设置环境变量
- 设置提供商
- 创建登录和注册页面
- 文件夹结构
初始设置
安装
npm install next-auth@beta
// env.local auth_secret=generatetd_random_value
配置
nextauthconfig 设置
// src/auth.ts
import nextauth from "next-auth"
export const config = {
providers: [],
}
export const { handlers, signin, signout, auth } = nextauth(config)
它应该放在src文件夹内
providers 在 auth.js 中表示是可用于登录用户的服务。用户可以通过四种方式登录。
- 使用内置的 oauth 提供程序(例如 github、google 等...)
- 使用自定义 oauth 提供程序
- 使用电子邮件
- 使用凭证
https://authjs.dev/reference/nextjs#providers
路由处理程序设置
// src/app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth" // referring to the auth.ts we just created
export const { get, post } = handlers
此文件用于使用 next.js app router 设置路由处理程序。
中间件
// src/middleware.ts
import { auth } from "@/auth"
export default auth((req) => {
// add your logic here
}
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"], // it's default setting
}
在src文件夹内写入。
如果写在 src 文件夹之外,中间件将无法工作。
中间件是一个允许您在请求完成之前运行代码的函数。它对于保护路由和处理整个应用程序的身份验证特别有用。
matcher 是 一个配置选项,用于指定哪些路由中间件应应用于。它有助于仅在必要的路由上运行中间件来优化性能。
示例匹配器: ['/dashboard/:path*'] 仅将中间件应用于仪表板路由。
https://authjs.dev/getting-started/session-management/protecting?framework=express#nextjs-middleware
在服务器端组件中获取会话
// src/app/page.tsx
import { auth } from "@/auth"
import { redirect } from "next/navigation"
export default async function page() {
const session = await auth()
if (!session) {
redirect('/login')
}
return (
<div>
<h1>hello world!</h1>
<img src={session.user.image} alt="user avatar" />
</div>
)
}
在客户端组件中获取会话
// src/app/page.tsx
"use client"
import { usesession } from "next-auth/react"
import { userouter } from "next/navigation"
export default async function page() {
const { data: session } = usesession()
const router = userouter()
if (!session.user) {
router.push('/login')
}
return (
<div>
<h1>hello world!</h1>
<img src={session.user.image} alt="user avatar" />
</div>
)
}
// src/app/layout.tsx
import type { metadata } from "next";
import "./globals.css";
import { sessionprovider } from "next-auth/react"
export const metadata: metadata = {
title: "create next app",
description: "generated by create next app",
};
export default function rootlayout({
children,
}: readonly<{
children: react.reactnode;
}>) {
return (
<html lang="en">
<body>
<sessionprovider>
{children}
</sessionprovider>
</body>
</html>
);
}
文件夹结构
/src
/app
/api
/auth
[...nextauth]
/route.ts // route handler
layout.tsx
page.tsx
auth.ts // provider, callback, logic etc
middleware.ts // a function before request
实施身份验证:凭据和 google oauth
设置棱镜
// prisma/schema.prisma
model user {
id string @id @default(cuid())
name string?
email string? @unique
emailverified datetime?
image string?
password string?
accounts account[]
sessions session[]
}
model account {
// ... (standard auth.js account model)
}
model session {
// ... (standard auth.js session model)
}
// ... (other necessary models)
// src/lib/prisma.ts
import { prismaclient } from "@prisma/client"
const globalforprisma = globalthis as unknown as { prisma: prismaclient }
export const prisma = globalforprisma.prisma || new prismaclient()
if (process.env.node_env !== "production") globalforprisma.prisma = prisma
证书
凭证,在身份验证的上下文中,指的是使用用户提供的信息验证用户身份的方法,通常是用户名(或电子邮件)和密码。
我们可以在 src/auth.ts 中添加凭据。
// src/auth.ts
import nextauth from "next-auth";
import type { nextauthconfig } from "next-auth";
import credentials from "next-auth/providers/credentials"
import { prismaadapter } from "@auth/prisma-adapter"
import { prisma } from "@/lib/prisma"
import bcrypt from 'bcryptjs';
export const config = {
adapter: prismaadapter(prisma),
providers: [
credentials({
credentials: {
email: { label: "email", type: "text" },
password: { label: "password", type: "password" }
},
authorize: async (credentials): promise<any> => {
if (!credentials?.email || !credentials?.password) {
return null;
}
try {
const user = await prisma.user.findunique({
where: {
email: credentials.email as string
}
})
if (!user || !user.hashedpassword) {
return null
}
const ispasswordvalid = await bcrypt.compare(
credentials.password as string,
user.hashedpassword
)
if (!ispasswordvalid) {
return null
}
return {
id: user.id as string,
email: user.email as string,
name: user.name as string,
}
} catch (error) {
console.error('error during authentication:', error)
return null
}
}
})
],
secret: process.env.auth_secret,
pages: {
signin: '/login',
},
session: {
strategy: "jwt",
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id
token.email = user.email
token.name = user.name
}
return token
},
async session({ session, token }) {
if (session.user) {
session.user.id = token.id as string
session.user.email = token.email as string
session.user.name = token.name as string
}
return session
},
},
} satisfies nextauthconfig;
export const { handlers, auth, signin, signout } = nextauth(config);
适配器:
- 将身份验证系统连接到数据库或数据存储解决方案的模块。
秘密:
- 这是一个随机字符串,用于哈希令牌、签名/加密 cookie 以及生成加密密钥。
- 这对于安全至关重要,应该保密。
- 在本例中,它是使用环境变量 auth_secret 设置的。
页面:
- 此对象允许您自定义身份验证页面的 url。
- 在您的示例中,signin: '/login' 表示登录页面将位于 '/login' 路由,而不是默认的 '/api/auth/signin'。
会话:
- 这配置了会话的处理方式。
- 策略:“jwt”表示 json web token 将用于会话管理而不是数据库会话。
回调:
- 这些是在身份验证流程中的各个点调用的函数,允许您自定义流程。
jwt 回调:
- 它在创建或更新 jwt 时运行。
- 在您的代码中,它将用户信息(id、电子邮件、姓名)添加到令牌中。
会话回调:
- 每当检查会话时都会运行。
- 您的代码正在将用户信息从令牌添加到会话对象。
添加 google oauth 提供商
设置 google oauth 应用程序
从 gcp console 创建新的 oauth 客户端 id > api 和服务 > 凭据

创建后,保存您的客户端 id 和客户端密钥以供以后使用。
设置重定向 uri
当我们在本地工作时,设置http://localhost:3000/api/auth/callback/google
生产环境中,只需将 http://localhost:3000 替换为 https://-----即可。

设置环境变量
// .env.local
google_client_id={client_id}
google_client_secret={client_secret}
设置提供商
// src/auth.ts
import googleprovider from "next-auth/providers/google" // add this import.
export const { handlers, auth } = nextauth({
adapter: prismaadapter(prisma),
providers: [
credentialsprovider({
// ... (previous credentials configuration)
}),
googleprovider({
clientid: process.env.google_client_id,
clientsecret: process.env.google_client_secret,
}),
],
// ... other configurations
})
https://authjs.dev/getting-started/authentication/oauth
创建登录和注册页面
//// ui pages
// src/app/login/loginpage.tsx
import link from 'next/link'
import { loginform } from '@/components/auth/loginform'
import { separator } from '@/components/auth/separator'
import { authlayout } from '@/components/auth/authlayout'
import { googleauthbutton } from '@/components/auth/googleauthbutton'
export default function loginpage() {
return (
<authlayout title="welcome back!">
<loginform />
<separator />
<googleauthbutton text="sign in with google" />
<div classname="mt-6 text-center">
<p classname="text-sm text-gray-400">
do not have an account?{' '}
<link href="/signup" classname="pl-1.5 font-medium text-[#3ba55c] hover:text-[#2d7d46]">
sign up
</link>
</p>
</div>
</authlayout>
)
}
// src/app/signup/signuppage.tsx
import link from 'next/link'
import { signupform } from '@/components/auth/signupform'
import { separator } from '@/components/auth/separator'
import { authlayout } from '@/components/auth/authlayout'
import { googleauthbutton } from '@/components/auth/googleauthbutton'
export default function signuppage() {
return (
<authlayout title="welcome!">
<signupform />
<separator />
<googleauthbutton text="sign up with google" />
<div classname="mt-6 text-center">
<p classname="text-sm text-gray-400">
already have an account?{' '}
<link href="/login" classname="pl-1.5 font-medium text-[#3ba55c] hover:text-[#2d7d46]">
sign in
</link>
</p>
</div>
</authlayout>
)
}
//// components
// src/components/auth/authlayout.tsx
import react from 'react'
interface authlayoutprops {
children: react.reactnode
title: string
}
export const authlayout: react.fc<authlayoutprops> = ({ children, title }) => {
return (
<div classname="min-h-screen bg-[#36393f] flex flex-col justify-center py-12 sm:px-6 lg:px-8">
<div classname="sm:mx-auto sm:w-full sm:max-w-md">
<h2 classname="mt-6 text-center text-3xl font-extrabold text-white">
{title}
</h2>
</div>
<div classname="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div classname="bg-[#2f3136] py-8 px-4 shadow sm:rounded-lg sm:px-10">
{children}
</div>
</div>
</div>
)
}
// src/components/auth/googleauthbutton.tsx
import { signin } from "@/auth"
import { button } from "@/components/ui/button"
interface googleauthbuttonprops {
text: string
}
export const googleauthbutton: react.fc<googleauthbuttonprops> = ({ text }) => {
return (
<form
action={async () => {
"use server"
await signin("google", { redirectto: '/' })
}}
>
<button
classname="my-1 w-full bg-white text-gray-700 hover:bg-slate-100"
>
<svg classname="h-5 w-5 mr-2" viewbox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="m22.56 12.25c0-.78-.07-1.53-.2-2.25h12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285f4"/>
<path d="m12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53h2.18v2.84c3.99 20.53 7.7 23 12 23z" fill="#34a853"/>
<path d="m5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09v7.07h2.18c1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#fbbc05"/>
<path d="m12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15c17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#ea4335"/>
<path d="m1 1h22v22h1z" fill="none"/>
</svg>
{text}
</button>
</form>
)
}
// src/components/auth/loginform.tsx
'use client'
import { usetransition } from "react"
import { useform } from "react-hook-form"
import {
form,
formcontrol,
formfield,
formitem,
formlabel,
formmessage,
} from "@/components/ui/form"
import { input } from "@/components/ui/input"
import { button } from "@/components/ui/button"
import { loginresolver, loginschema } from "@/schema/login"
import { usestate } from "react"
import { userouter } from "next/navigation"
import { formerror } from "@/components/auth/formerror"
import { formsuccess } from "@/components/auth/formsuccess"
import { login } from "@/app/actions/auth/login"
import { loader2 } from "lucide-react"
export const loginform = () => {
const [error, seterror] = usestate<string | undefined>('')
const [success, setsuccess] = usestate<string | undefined>('')
const [ispending, starttransition] = usetransition()
const router = userouter();
const form = useform<loginschema>({
defaultvalues: { email: '', password: ''},
resolver: loginresolver,
})
const onsubmit = (formdata: loginschema) => {
starttransition(() => {
seterror('')
setsuccess('')
login(formdata)
.then((data) => {
if (data.success) {
setsuccess(data.success)
router.push('/setup')
} else if (data.error) {
seterror(data.error)
}
})
.catch((data) => {
seterror(data.error)
})
})
}
return (
<form {...form}>
<form onsubmit={form.handlesubmit(onsubmit)}>
<div classname="space-y-3">
<formfield
control={form.control}
name="email"
render={({ field }) => (
<formitem>
<formlabel classname="text-white">email address</formlabel>
<formcontrol>
<input
placeholder="enter your email address"
{...field}
disabled={ispending}
classname="bg-[#40444b] text-white border-gray-600 focus:border-2 focus:border-[#2d7d46]"
/>
</formcontrol>
<formmessage />
</formitem>
)}
/>
<formfield
control={form.control}
name="password"
render={({ field }) => (
<formitem>
<formlabel classname="text-white">password</formlabel>
<formcontrol>
<input
type="password"
placeholder="enter your password"
{...field}
disabled={ispending}
classname="bg-[#40444b] text-white border-gray-600 focus:border-2 focus:border-[#2d7d46]"
/>
</formcontrol>
<formmessage />
</formitem>
)}
/>
<formerror message={error} />
<formsuccess message={success} />
</div>
<button
type="submit"
disabled={ispending}
classname="mt-8 w-full bg-[#3ba55c] hover:bg-[#2d7d46] text-white"
>
{ispending ? (
<>
<loader2 classname="mr-2 h-4 w-4 animate-spin" />
loading...
</>
) : (
'login'
)}
</button>
</form>
</form>
)
}
// src/components/auth/signupform.tsx
'use client'
import { usetransition } from "react"
import { useform } from "react-hook-form"
import {
form,
formcontrol,
formfield,
formitem,
formlabel,
formmessage,
} from "@/components/ui/form"
import { input } from "@/components/ui/input"
import { button } from "@/components/ui/button"
import { signupresolver, signupschema } from "@/schema/signup"
import { usestate } from "react"
import { userouter } from "next/navigation"
import { formerror } from "@/components/auth/formerror"
import { formsuccess } from "@/components/auth/formsuccess"
import { signup } from "@/app/actions/auth/signup"
import { loader2 } from "lucide-react"
export const signupform = () => {
const [error, seterror] = usestate<string | undefined>('')
const [success, setsuccess] = usestate<string | undefined>('')
const [ispending, starttransition] = usetransition()
const router = userouter();
const form = useform<signupschema>({
defaultvalues: { name: '', email: '', password: ''},
resolver: signupresolver,
})
const onsubmit = async (formdata: signupschema) => {
starttransition(() => {
seterror('')
setsuccess('')
signup(formdata)
.then((data) => {
if (data.success) {
setsuccess(data.success)
router.push('/login')
} else if (data.error) {
seterror(data.error)
}
})
.catch((data) => {
seterror(data.error)
})
})
}
return (
<form {...form}>
<form onsubmit={form.handlesubmit(onsubmit)}>
<div classname="space-y-3">
<formfield
control={form.control}
name="name"
render={({ field }) => (
<formitem>
<formlabel classname="text-white">username</formlabel>
<formcontrol>
<input
placeholder="enter your name"
{...field}
disabled={ispending}
classname="bg-[#40444b] text-white border-gray-600 focus:border-2 focus:border-[#2d7d46]"
/>
</formcontrol>
<formmessage />
</formitem>
)}
/>
<formfield
control={form.control}
name="email"
render={({ field }) => (
<formitem>
<formlabel classname="text-white">email address</formlabel>
<formcontrol>
<input
placeholder="enter your email address"
{...field}
disabled={ispending}
classname="bg-[#40444b] text-white border-gray-600 focus:border-2 focus:border-[#2d7d46]"
/>
</formcontrol>
<formmessage />
</formitem>
)}
/>
<formfield
control={form.control}
name="password"
render={({ field }) => (
<formitem>
<formlabel classname="text-white">password</formlabel>
<formcontrol>
<input
type="password"
placeholder="enter your password"
{...field}
disabled={ispending}
classname="bg-[#40444b] text-white border-gray-600 focus:border-2 focus:border-[#2d7d46]"
/>
</formcontrol>
<formmessage />
</formitem>
)}
/>
<formerror message={error} />
<formsuccess message={success} />
</div>
<button
type="submit"
disabled={ispending}
classname="mt-8 w-full bg-[#3ba55c] hover:bg-[#2d7d46] text-white"
>
{ispending ? (
<>
<loader2 classname="mr-2 h-4 w-4 animate-spin" />
loading...
</>
) : (
'sign up'
)}
</button>
</form>
</form>
)
}
// src/components/auth/formsuccess.tsx
import { checkcircledicon } from "@radix-ui/react-icons";
interface formsuccessprops {
message?: string;
}
export const formsuccess = ({ message }: formsuccessprops) => {
if (!message) return null;
return (
<div classname="bg-emerald-500/15 p-3 rounded-md flex items-center gap-x-2 text-sm text-emerald-500">
<checkcircledicon classname="h-4 w-4" />
<p>{message}</p>
</div>
);
};
// src/components/auth/formerror.tsx
import { exclamationtriangleicon } from "@radix-ui/react-icons";
interface formerrorprops {
message?: string;
}
export const formerror = ({ message }: formerrorprops) => {
if (!message) return null;
return (
<div classname="bg-destructive/15 p-3 rounded-md flex items-center gap-x-2 text-sm text-destructive">
<exclamationtriangleicon classname="h-4 w-4" />
<p>{message}</p>
</div>
);
};
// src/components/auth/separator.tsx
export const separator = () => {
return (
<div classname="my-4 relative">
<div classname="absolute inset-0 flex items-center">
<div classname="w-full border-t border-gray-600" />
</div>
<div classname="relative flex justify-center text-sm">
<span classname="px-2 bg-[#2f3136] text-gray-400">or continue with</span>
</div>
</div>
)
}
//// actions
// src/app/actions/auth/login.ts
'use server'
import { loginschema, loginschema } from '@/schema/login'
import { signin } from '@/auth'
export const login = async (formdata: loginschema) => {
const email = formdata['email'] as string
const password = formdata['password'] as string
const validatedfields = loginschema.safeparse({
email: formdata.email as string,
password: formdata.password as string,
})
if (!validatedfields.success) {
return {
errors: validatedfields.error.flatten().fielderrors,
message: 'login failed. please check your input.'
}
}
try {
const result = await signin('credentials', {
redirect: false,
callbackurl: '/setup',
email,
password
})
if (result?.error) {
return { error : 'invalid email or password'}
} else {
return { success : 'login successfully'}
}
} catch {
return { error : 'login failed'}
}
}
// src/app/actions/auth/signup.ts
'use server'
import bcrypt from 'bcryptjs'
import { signupschema, signupschema } from "@/schema/signup"
import { prisma } from '@/lib/prisma';
export const signup = async (formdata: signupschema) => {
const validatedfields = signupschema.safeparse({
name: formdata.name as string,
email: formdata.email as string,
password: formdata.password as string,
})
if (!validatedfields.success) {
return {
errors: validatedfields.error.flatten().fielderrors,
message: 'sign up failed. please check your input.'
}
}
try {
const hashedpassword = await bcrypt.hash(validatedfields.data.password, 10);
const existinguser = await prisma.user.findunique({
where: { email: validatedfields.data.email }
})
if (existinguser) {
return { error: 'user already exists!' }
}
await prisma.user.create({
data: {
name: validatedfields.data.name,
email: validatedfields.data.email,
hashedpassword: hashedpassword,
},
});
return { success: 'user created successfully!' }
} catch (error) {
return { error : `sign up failed`}
}
}
//// validations
// src/schema/login.ts
import * as z from 'zod';
import { zodresolver } from '@hookform/resolvers/zod';
export const loginschema = z.object({
email: z.string().email('this is not valid email address'),
password: z
.string()
.min(8, { message: 'password must contain at least 8 characters' }),
});
export type loginschema = z.infer<typeof loginschema>;
export const loginresolver = zodresolver(loginschema);
// src/schema/signup.ts
import * as z from 'zod';
import { zodresolver } from '@hookform/resolvers/zod';
export const signupschema = z.object({
name: z.string().min(1, {
message: 'name is required'
}),
email: z.string().email('this is not valid email address'),
password: z
.string()
.min(8, { message: 'password must contain at least 8 characters' }),
});
export type signupschema = z.infer<typeof signupschema>;
export const signupresolver = zodresolver(signupschema);
// src/middleware.ts
import { nextresponse } from 'next/server'
import { auth } from "@/auth"
export default auth((req) => {
const { nexturl, auth: session } = req
const isloggedin = !!session
const isloginpage = nexturl.pathname === "/login"
const issignuppage = nexturl.pathname === "/signup"
const issetuppage = nexturl.pathname === "/setup"
// if trying to access /setup while not logged in
if (!isloggedin && issetuppage) {
const loginurl = new url("/login", nexturl.origin)
return nextresponse.redirect(loginurl)
}
// if trying to access /login or /signup while already logged in
if (isloggedin && (isloginpage || issignuppage)) {
const dashboardurl = new url("/", nexturl.origin)
return nextresponse.redirect(dashboardurl)
}
// for all other cases, allow the request to pass through
return nextresponse.next()
})
export const config = {
matcher: ["/login","/signup", "/setup", "/"],
};
文件夹结构
/src
/app
/actions
/login.ts // Login Action
/signup.ts // Signup Action
/api
/auth
[...nextauth]
/route.ts
/login
page.tsx // Login Page
/signup
page.tsx // Sign Up Page
layout.tsx
page.tsx
/components
/auth
AuthLayout.tsx
GoogleAuthButton.tsx
LoginForm.tsx
SignupForm.tsx
FormSuccess.tsx
FormError.tsx
Separator.tsx
/schema
login.ts
signup.ts
auth.ts // in src folder
middleware.ts // in src folder
今天关于《在 Nextjs App Router 中使用 Authjs 进行用户身份验证》的内容就介绍到这里了,是不是学起来一目了然!想要了解更多关于的内容请关注golang学习网公众号!
如何避免 Tree 组件点击节点多次触发接口请求?
- 上一篇
- 如何避免 Tree 组件点击节点多次触发接口请求?
- 下一篇
- 电脑桌面便签:如何让你的工作更高效?
-
- 文章 · 前端 | 9分钟前 |
- CSS调整字母间距方法详解
- 400浏览 收藏
-
- 文章 · 前端 | 10分钟前 | JavaScript 本地服务器 手机HTML脚本 文件协议 HTML运行工具
- 手机运行HTML脚本方法详解
- 371浏览 收藏
-
- 文章 · 前端 | 12分钟前 |
- NextAuth多租户认证与Cookie优化技巧
- 266浏览 收藏
-
- 文章 · 前端 | 18分钟前 |
- JavaScript常用工具函数推荐
- 461浏览 收藏
-
- 文章 · 前端 | 22分钟前 |
- HTML5运行方法全解析教程
- 235浏览 收藏
-
- 文章 · 前端 | 28分钟前 |
- 构造函数与类用法区别详解
- 364浏览 收藏
-
- 文章 · 前端 | 31分钟前 |
- CSS上下边距布局技巧与应用
- 270浏览 收藏
-
- 文章 · 前端 | 32分钟前 |
- CSSsticky定位与overflow实用技巧
- 372浏览 收藏
-
- 文章 · 前端 | 42分钟前 |
- Flexbox和column-count多列布局技巧
- 422浏览 收藏
-
- 文章 · 前端 | 45分钟前 |
- CSS锥形渐变旋转动画怎么实现
- 102浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3197次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3410次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3440次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4548次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3818次使用
-
- JavaScript函数定义及示例详解
- 2025-05-11 502浏览
-
- 优化用户界面体验的秘密武器:CSS开发项目经验大揭秘
- 2023-11-03 501浏览
-
- 使用微信小程序实现图片轮播特效
- 2023-11-21 501浏览
-
- 解析sessionStorage的存储能力与限制
- 2024-01-11 501浏览
-
- 探索冒泡活动对于团队合作的推动力
- 2024-01-13 501浏览

