RedwoodJS dbAuth 无密码登录(Passwordless)实战:用邮箱验证码替代密码存储
后端前端Web框架开发工具【免费下载链接】redwoodRedwoodGraphQL项目地址https://gitcode.com/gh_mirrors/re/redwood点击查看免费下载本文是一份完整的实战指南讲解如何基于 RedwoodJS 内置的 dbAuth 认证方案将传统的用户名 密码登录改造为仅需邮箱地址的无密码Passwordless登录系统向用户邮箱发送一次性、有时效性的验证码用户凭验证码即可完成登录。文中逐步覆盖 Prisma 数据模型改造、generateLoginToken服务函数、GraphQL SDL 暴露、auth.js认证函数定制、前端登录/验证码表单与页面、路由切换并深入packages/auth-providers/dbAuth源码说明 dbAuth 的登录校验链路帮助你在完全不依赖任何第三方认证服务的前提下把用户数据的采集面收敛到最小。为什么需要无密码登录RedwoodJS 自带的 dbAuth 是一种完全自托管的认证方案用户凭据存放在你自己的数据库里登录、注册、忘记密码页面都可以自己掌控不依赖任何第三方认证服务也没有按用户数计费的隐形成本。它的核心缺点是你自己负责保管用户密码——尽管 Redwood 遵循最佳实践密码经加盐 PBKDF2 哈希后存储见packages/auth-providers/dbAuth/api/src/DbAuthHandler.ts中的hashPassword与legacyHashPassword调用。如果你不希望采集用户的密码无密码登录是一个理想的替代方案用随机生成的、有时效性的 token 取代密码。整个系统唯一需要收集的用户数据只有邮箱地址——即使数据库被攻破攻击者能拿到的也只是一个邮箱而不是可用于撞库的密码。无密码认证的工作原理无密码认证passwordless authentication的核心思想是用户输入邮箱地址系统生成一个随机 token而非密码并连同过期时间一起存入数据库系统通过邮件把 token或携带 token 的链接发送给用户用户点击链接或输入 token系统校验其有效性存在且未过期后直接为其建立登录会话登录成功后 token 即被销毁成为一次性凭证。在本方案中dbAuth 原有的密码哈希 salt字段被复用为token 哈希 salthashedPassword字段存储的是 token 的哈希值salt字段在登录成功后会被清空使 token 只能使用一次。前提说明本方案需要向用户发送邮件因此你还需要先搭建邮件发送能力。Redwood 官方提供了 Sending Emails 指南基于 nodemailer也可以用你熟悉的任意邮件服务。第 1 步修改 Prisma schema首先打开api/db/schema.prisma为User模型增加三个字段。改造后的模型如下高亮部分为新增字段model User { id Int id default(autoincrement()) name String? email String unique loginToken String loginTokenExpiresAt DateTime? salt String? }字段说明loginToken存储 token 的哈希值注意不是明文 token原理与密码存储一致loginTokenExpiresAttoken 的过期时间用于校验 token 是否仍然有效salt生成 token 哈希时使用的盐。与标准 dbAuth 不同的是这里它是可选的String?因为在无密码模式下登录成功后我们会清空 salt 来使 token 一次性失效。完成修改后执行迁移命令让数据库结构生效yarn rw prisma migrate dev第 2 步创建 generateLoginToken 服务函数接下来需要创建一个 API 端服务函数负责生成 token 并写入数据库。如果你还没有 users 服务先用生成器创建yarn rw g service users然后编辑api/src/services/users/users.js在文件顶部引入 CryptoJS并在文件末尾添加generateLoginToken函数// add this import to the top of the file import CryptoJS from crypto-js // add this to the bottom of the file export const generateLoginToken async ({ email }) { try { // look up if the user exists let lookupUser await db.user.findFirst({ where: { email } }) if (!lookupUser) { return { message: Login Request received } } // here were going to generate a random password of 6 numbers let randomNumber (() { let random CryptoJS.lib.WordArray.random(6) let randomString random.toString() let sixDigitNumber randomString.replace(/\D/g, ) if (sixDigitNumber.length 6) { sixDigitNumber sixDigitNumber.padStart(6, 0) } if (sixDigitNumber.length 6) { sixDigitNumber sixDigitNumber.slice(0, 6) } return sixDigitNumber.toString() })() console.log({ randomNumber }) // email the user this number let salt CryptoJS.lib.WordArray.random(30) let loginToken CryptoJS.PBKDF2(randomNumber, salt, { keySize: 256 / 32, }).toString() // now well update the user with the new salt and loginToken let loginTokenExpiresAt new Date() loginTokenExpiresAt.setMinutes(loginTokenExpiresAt.getMinutes() 15) let data { salt, loginToken, loginTokenExpiresAt, } await db.user.update({ where: { id: lookupUser.id }, data, }) return { message: Login Request received } } catch (error) { console.log({ error }) throw new UserInputError(error.message) } }这段代码的关键点随机 6 位数字验证码CryptoJS.lib.WordArray.random(6)生成随机字节串再过滤非数字字符并补齐/截断为 6 位绝不存储明文 tokenCryptoJS.PBKDF2(randomNumber, salt, { keySize: 256 / 32 })使用 PBKDF2 派生密钥数据库中只保存 token 的哈希值——这与 dbAuth 对密码的处理方式完全一致15 分钟有效期loginTokenExpiresAt被设置为当前时间 15 分钟不暴露用户是否存在无论邮箱是否注册都返回{ message: Login Request received }避免被用来枚举有效邮箱邮件发送点代码中的console.log({ randomNumber })是示意位置实际生产环境中应在此处调用邮件服务把验证码发给用户参考 Sending Emails同时可以删除该日志。第 3 步把 generateToken 加入 SDL 并隐藏 loginToken为了让前端能够调用这个函数需要把它暴露到 GraphQL。同时注意loginToken、loginTokenExpiresAt、salt这些字段绝不能出现在 SDL 中否则任何客户端都能通过 GraphQL 查询拿到 token 哈希与盐。编辑api/src/graphql/users.sdl.js或 TypeScript 项目中的users.sdl.tsexport const schema gql type User { id: Int! name: String email: String! } input CreateUserInput { name: String email: String! } input UpdateUserInput { name: String email: String! } type userTokenResponse { message: String! } type Mutation { createUser(input: CreateUserInput!): User! requireAuth updateUser(id: Int!, input: UpdateUserInput!): User! requireAuth deleteUser(id: Int!): User! requireAuth generateToken(email: String!): userTokenResponse! skipAuth } 注意User类型只暴露id、name、email三个安全字段generateTokenmutation 使用skipAuth指令因为用户此刻尚未登录调用方匿名即可请求验证码其余 mutation 保持requireAuth要求登录后才能操作。第 4 步改造 auth 函数核心改动集中在api/src/functions/auth.js——这是 dbAuth 的入口DbAuthHandler实例化处。在无密码模式下我们需要在loginOptions.handler中校验 token 是否过期并在成功后清空 salt 与过期时间让 token 一次性生效在signupOptions.handler中把username即邮箱写入email字段把传入的hashedPassword此时是随机字符串见第 8 步写入loginToken字段通过authFields告诉 dbAuthhashedPassword字段对应数据库中的loginToken。// ... other functions const loginOptions { handler: async (user) { let loginExpiresAt new Date(user?.loginTokenExpiresAt) let now new Date() if (loginExpiresAt now) { throw Login token expired } // if the user logged in with a token we need to break // the token. Well do this by clearing the salt and // expiration // this will make the token a one-time use db.user.update({ where: { id: user.id }, data: { loginTokenExpiresAt: null, salt: null, }, }) return user }, errors: { // here I modified the following, feel free to modify the other messages incorrectPassword: Incorrect token, }, } // we also need to update the signupOptions const signupOptions { handler: ({ username, hashedPassword, userAttributes }) { return db.user.create({ data: { email: username, loginToken: hashedPassword, salt: null, name: userAttributes.name, }, }) }, // ... other stuff } // and last we need to update the authFields const authHandler new DbAuthHandler(event, context, { db: db, authModelAccessor: user, authFields: { id: id, hashedPassword: loginToken, salt: salt, }, // ... other stuff })对源码的印证查看 DbAuthHandler.ts 可以发现login()方法的执行链路是先_verifyUser(username, password)校验用户与口令 → 再调用loginOptions.handler(user)→ 最后_loginResponse(handlerUser)。也就是说handler是在口令校验通过之后、建立会话之前执行的——这正是我们在这里做过期检查 一次性销毁的时机窗口口令校验环节_verifyPassword会读取authFields.hashedPassword指向的字段此处即loginToken与用户提交的验证码哈希比对同时用authFields.salt指定的字段参与哈希计算我们通过authFields的映射hashedPassword: loginToken完整复用了这套 PBKDF2 加盐哈希校验机制无需改动 dbAuth 底层的口令比对逻辑校验通过后handler若抛出异常异常信息会经由logIn()以{ error: ... }的形式返回前端参见 dbAuth.ts 前端客户端 的login实现。此外_verifyPassword在数据库中找不到匹配哈希时会抛出IncorrectPasswordError其消息文案正是通过loginOptions.errors.incorrectPassword定制的——把它改成Incorrect token会让用户看到与无密码流程一致的语义。第 5 步制作邮箱输入表单前端部分开始。先用生成器创建组件yarn rw g component LoginPasswordlessForm这会生成web/src/components/LoginPasswordlessForm/LoginPasswordlessForm.js。将其内容替换为import { Form, Label, TextField, PasswordField, Submit, FieldError, } from redwoodjs/forms import { navigate, routes, Link } from redwoodjs/router import { MetaTags, useMutation } from redwoodjs/web import { Toaster, toast } from redwoodjs/web/toast const GENERATE_LOGIN_TOKEN gql mutation generateLoginToken($email: String!) { generateLoginToken(email: $email) { message } } const LoginPasswordlessForm ({ setWaitingForCode, setEmail }) { const [generateLoginToken] useMutation( GENERATE_LOGIN_TOKEN, { onCompleted: () { toast.success(Check your email for a login link) setWaitingForCode(true) }, } ) const onSubmit async (data) { setEmail(data.email) const response await generateLoginToken({ variables: { email: data.email }, fetchPolicy: no-cache, }) if (response.error) { toast.error(response.error) } } return ( MetaTags titleLogin / main classNamerw-main Toaster toastOptions{{ className: rw-toast, duration: 6000 }} / div classNamerw-scaffold rw-login-container div classNamerw-segment header classNamerw-segment-header h2 classNamerw-heading rw-heading-secondaryLogin/h2 /header div classNamerw-segment-main div classNamerw-form-wrapper Form onSubmit{onSubmit} classNamerw-form-wrapper Label nameemail classNamerw-label errorClassNamerw-label rw-label-error Email /Label TextField nameemail classNamerw-input errorClassNamerw-input rw-input-error validation{{ required: { value: true, message: Email is required, }, }} / FieldError nameemail classNamerw-field-error / div classNamerw-button-group Submit classNamerw-button rw-button-blue Send Token /Submit /div /Form /div /div div classNamerw-login-link spanDonapos;t have an account?/span{ } Link to{routes.signup()} classNamerw-link Sign up! /Link /div /div /div /main / ) } export default LoginPasswordlessForm要点该表单只收集邮箱通过GENERATE_LOGIN_TOKENmutation 调用第 2、3 步实现的generateTokenfetchPolicy: no-cache确保每次都发起真实请求避免 Apollo 缓存导致重复点击时跳过服务端调用调用成功后将通过setWaitingForCode(true)把控制权交给父页面切换到验证码输入阶段表单 UI 复用了 Redwood 脚手架scaffold的 CSS 类名与yarn rw g dbAuth生成的默认登录页风格保持一致。第 6 步制作验证码输入表单再创建一个接收验证码的组件yarn rw g component LoginPasswordlessTokenForm编辑web/src/components/LoginPasswordlessTokenForm/LoginPasswordlessTokenForm.jsimport { useEffect, useRef } from react import { Form, Label, TextField, PasswordField, Submit, FieldError, } from redwoodjs/forms import { navigate, routes, Link } from redwoodjs/router import { MetaTags, useMutation } from redwoodjs/web import { Toaster, toast } from redwoodjs/web/toast import { useAuth } from src/auth const LoginPasswordlessTokenForm ({ setWaitingForCode, email, code }) { const { isAuthenticated, logIn } useAuth() useEffect(() { if (isAuthenticated) { navigate(routes.home()) } if (email code) { console.log(email, email) logIn({ username: email, password: code }) } }, [isAuthenticated, email, code, logIn]) const onSubmit async (data) { // login expects a username and password for dbauth // so we are passing them. const response await logIn({ username: email, password: data.loginToken }) if (response.error) { toast.error(response.error) } } return ( MetaTags titleLogin / main classNamerw-main Toaster toastOptions{{ className: rw-toast, duration: 6000 }} / div classNamerw-scaffold rw-login-container div classNamerw-segment header classNamerw-segment-header h2 classNamerw-heading rw-heading-secondary Login with Token /h2 /header div classNamerw-segment-main div classNamerw-form-wrapper Form onSubmit{onSubmit} classNamerw-form-wrapper Label nameemail classNamerw-label errorClassNamerw-label rw-label-error Email /Label TextField nameemail classNamerw-input errorClassNamerw-input rw-input-error readOnly{true} defaultValue{email} / FieldError nameemail classNamerw-field-error / Label nameloginToken classNamerw-label errorClassNamerw-label rw-label-error Token /Label TextField nameloginToken classNamerw-input errorClassNamerw-input rw-input-error / FieldError nameloginToken classNamerw-field-error / div classNamerw-button-group Submit classNamerw-button rw-button-blueLogin/Submit /div div classNamerw-button-group button classNamerw-button rw-button-blue onClick{() { setWaitingForCode(false) }} Get another Token /button /div /Form /div /div div classNamerw-login-link spanDonapos;t have an account?/span{ } Link to{routes.signup()} classNamerw-link Sign up! /Link /div /div /div /main / ) } export default LoginPasswordlessTokenForm这段代码的关键在于复用 dbAuth 的登录接口logIn({ username: email, password: data.loginToken })——dbAuth 的登录接口约定接收username和password在无密码模式下我们把邮箱当作 username、把验证码当作 password 传入。前端login函数会将二者连同method: login一起 POST 到 dbAuth 端点见 dbAuth.tsemail字段为只读readOnly{true}防止用户在拿到验证码后修改邮箱Get another Token 按钮通过setWaitingForCode(false)回到第一步重新请求验证码。第 7 步创建新的登录页两个表单的状态由父页面统一控制。生成新页面yarn rw g page LoginPasswordless编辑web/src/pages/LoginPasswordlessPage/LoginPasswordlessPage.jsimport { useEffect, useState } from react import { useLocation } from redwoodjs/router import { MetaTags } from redwoodjs/web import LoginPasswordlessForm from src/components/LoginPasswordlessForm/LoginPasswordlessForm import LoginPasswordlessTokenForm from src/components/LoginPasswordlessTokenForm/LoginPasswordlessTokenForm const LoginPasswordlessPage () { let [waitingForCode, setWaitingForCode] useState(false) let [email, setEmail] useState() let [code, setCode] useState() // onload set email from query string let { search } useLocation() useEffect(() { let params new URLSearchParams(search) // decode magic param let magic params.get(magic) let decoded window.atob(params.get(magic)) // if magic param exists, set email and waitingForCode if (magic) { // decoded is email:code let [email, code] decoded.split(:) setEmail(email) setCode(code) setWaitingForCode(true) } }, [search]) return ( MetaTags titleLoginPasswordless descriptionLoginPasswordless page / {!waitingForCode ( LoginPasswordlessForm setWaitingForCode{setWaitingForCode} setEmail{setEmail} / )} {waitingForCode ( LoginPasswordlessTokenForm email{email} setWaitingForCode{setWaitingForCode} code{code} / )} / ) } export default LoginPasswordlessPage这个页面实现了一种可选的魔法链接magic link体验邮件中可以附带一个形如https://your-site.com/login?magicbase64(邮箱:验证码)的链接页面加载时用URLSearchParams解析magic参数通过window.atob解码出email:code自动填入表单并直接触发logIn见第 6 步组件中useEffect对email code的判断没有魔法链接时页面退化为先输入邮箱 → 再输入验证码的两步流程由waitingForCode状态驱动。提示magic 链接中携带的是明文邮箱与验证码请确保站点使用 HTTPS并注意链接在邮件中的时效性与 token 的 15 分钟有效期保持一致。第 8 步改造注册页注册页不再要求设置密码只需收集邮箱。dbAuth 的signUp()需要username与password两个参数因此我们用浏览器随机生成一个无意义的临时密码填充它会被signupOptions.handler作为loginToken存入数据库但用户永远不会知道它也不会用它登录import { useRef } from react import { useEffect } from react import { Form, Label, TextField, PasswordField, FieldError, Submit, } from redwoodjs/forms import { Link, navigate, routes } from redwoodjs/router import { MetaTags } from redwoodjs/web import { toast, Toaster } from redwoodjs/web/toast import { useAuth } from src/auth const SignupPage () { const { isAuthenticated, signUp } useAuth() let randomString (length) { if (typeof length undefined) length 32 const characterSet ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 const array new Uint8Array(length) window.crypto.getRandomValues(array) const returnString Array.from(array) .map((value) characterSet[value % characterSet.length]) .join() return returnString } useEffect(() { if (isAuthenticated) { navigate(routes.home()) } }, [isAuthenticated]) // focus on username box on page load const emailRef useRef(null) useEffect(() { emailRef.current?.focus() }, []) const onSubmit async (data) { const response await signUp({ username: data.email, password: randomString(), // this is a random string and is not important }) if (response.message) { toast(response.message) } else if (response.error) { toast.error(response.error) } else { // user is signed in automatically toast.success(Welcome!) } } return ( MetaTags titleSignup / main classNamerw-main Toaster toastOptions{{ className: rw-toast, duration: 6000 }} / div classNamerw-scaffold rw-login-container div classNamerw-segment header classNamerw-segment-header h2 classNamerw-heading rw-heading-secondarySignup/h2 /header div classNamerw-segment-main div classNamerw-form-wrapper Form onSubmit{onSubmit} classNamerw-form-wrapper Label nameemail classNamerw-label errorClassNamerw-label rw-label-error Email /Label TextField nameemail classNamerw-input errorClassNamerw-input rw-input-error ref{emailRef} validation{{ required: { value: true, message: Email is required, }, }} / FieldError nameemail classNamerw-field-error / div classNamerw-button-group Submit classNamerw-button rw-button-blue Sign Up /Submit /div /Form /div /div div classNamerw-login-link spanAlready have an account?/span{ } Link to{routes.login()} classNamerw-link Log in! /Link /div /div /div /main / ) } export default SignupPage结合源码看注册链路_createUser()会先做三件事——校验username/password非空、按usernameMatch规则检查邮箱是否已存在、调用signupOptions.passwordValidation然后才执行signupOptions.handler({ username, hashedPassword, salt, userAttributes })。在无密码模式下随机临时密码经hashPassword(password)加盐哈希后作为hashedPassword传入 handler再由 handler 写入loginToken字段handler 同时把salt显式置为null因为后续真正的登录 token 会重新生成盐如果 handler 返回了用户对象dbAuth 会调用_loginResponse自动登录该用户见signup()所以注册成功后用户会直接进入站点——之后再走登录流程请求验证码即可。第 9 步更新路由最后把登录路由指向新的无密码登录页const Routes () { // other stuff return ( Router useAuth{useAuth} Route path/login page{LoginPasswordlessPage} namelogin / Route path/signup page{SignupPage} namesignup / {/*other routes*/} /Router ) }注意需要把LoginPasswordlessPage与SignupPage引入到Routes.js顶部路由的namelogin、signup保持不变这样第 5、6 步表单中routes.login()、routes.signup()的引用无需改动注册后新用户的首次登录体验建议注册成功即自动登录handler 返回 user或引导用户回到登录页输入邮箱请求验证码。无密码流程的整体时序把九步串联起来一次完整的无密码登录请求是用户在登录页输入邮箱前端调用generateTokenGraphQL mutationskipAuth服务端generateLoginToken生成 6 位随机码经 PBKDF2 加盐哈希后连同 15 分钟有效期写入User表并把验证码通过邮件发给用户用户输入验证码或点击 magic link前端调用logIn({ username: email, password: code })dbAuth 端点收到method: login请求_verifyUser用authFields.hashedPassword映射到loginToken与authFields.salt完成 PBKDF2 哈希比对见 DbAuthHandler.ts比对通过后执行loginOptions.handler校验loginTokenExpiresAt未过期随即清空salt与过期时间——token 作废不可再次使用dbAuth 签发 HttpOnly 会话 Cookie用户登录成功。安全注意事项一次性 token登录成功后清空salt使 token 哈希无法被再次校验这是防止 token 重放的关键时效性15 分钟有效期由loginTokenExpiresAt保证过期后handler抛出Login token expired用户需重新请求验证码不存明文数据库只保存 PBKDF2 哈希即使泄露也无法直接还原验证码不暴露用户存在性generateLoginToken对未注册邮箱同样返回成功提示配合第 4 步错误文案的定制可有效对抗邮箱枚举最小数据面整个系统中用户只需提供邮箱即便数据库被攻破攻击者拿到的也仅仅是邮箱地址而非可用于撞库的密码哈希日志脱敏如果沿用标准 dbAuth 的日志配置注意console.log({ randomNumber })这类调试输出在生产环境必须移除避免验证码泄漏到日志dbAuth 的 logger 会默认脱敏敏感参数见 logger 文档 的 Redaction 一节。小结通过以上九个步骤你已经在完全自托管的 dbAuth 之上实现了无密码认证数据库只存储 token 哈希与过期时间用户唯一需要提供的是邮箱验证码一次性且有时效。这套方案没有引入任何第三方认证依赖与 dbAuth 既有的 Cookie 会话、GraphQL 鉴权requireAuth/skipAuth机制完全兼容。若后续需要更强的防重放能力还可参考 dbAuth 的 WebAuthn 支持基于UserCredential模型与webAuthn配置实现 Touch ID / Face ID 等生物识别登录。赞分享后端前端Web框架开发工具【免费下载链接】redwoodRedwoodGraphQL项目地址https://gitcode.com/gh_mirrors/re/redwood点击查看免费下载相关推荐Redwood dbAuth 免密登录Passwordless实战指南用邮箱验证码替代密码Redwood dbAuth 免密登录Passwordless实战指南用邮箱验证码替代密码 导读 本指南以 Redwood 内置的 dbAuth 认证方案后端前端Web框架开发工具Redwood 无密码认证实战基于 dbAuth 搭建 Passwordless 登录Redwood 无密码认证实战基于 dbAuth 搭建 Passwordless 登录 在 Redwood 应用中安全性始终是首要考虑。当你不希望引入第三方后端前端Web框架开发工具Redwood 无密码认证实战基于 dbAuth 的邮箱登录令牌方案Redwood 无密码认证实战基于 dbAuth 的邮箱登录令牌方案 本指南面向使用 Redwood 框架的开发者讲解如何将默认的 dbAuth 用户名/密后端前端Web框架开发工具上一篇3个实战技巧Xenos进程注入工具实现动态链接库加载下一篇教育网和海外服务器如何配置Linux软件源LinuxMirrors --edu与--abroad模式使用指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

ESP01 固件烧录全攻略:Flash Download Tool 从入门到精通

ESP01 固件烧录全攻略:Flash Download Tool 从入门到精通

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/24 15:29:45 阅读更多 →
X79平台M.2 NVMe固态升级指南:从接口原理到性能实测

X79平台M.2 NVMe固态升级指南:从接口原理到性能实测

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/24 15:29:45 阅读更多 →
Flet TextStyle 样式体系详解:掌控文本排版、装饰与主题字体的完整指南

Flet TextStyle 样式体系详解:掌控文本排版、装饰与主题字体的完整指南

前端跨平台桌面应用移动开发 【免费下载链接】flet Build realtime web, mobile and desktop apps in Python only. No frontend experience required. 项目地址: https://gitcode.com/gh_mirrors/fl/flet 点击查看 免费下载 TextStyle 是 Flet 中描述"如何格…

2026/9/24 15:29:45 阅读更多 →

最新新闻

【Dify】大语言模型自动问答与代码执行应用

【Dify】大语言模型自动问答与代码执行应用

大语言模型驱动的智能自动化,正逐步改变代码执行与数据交互的方式。依托runLLMCode工作流,常见的数据处理与个性化场景实现了高效连接、灵活拓展。 本文梳理runLLMCode的核心节点与流程设计,解析其在自动问答、代码执行及外部API集成中的实践方案,旨在为自学编程用户提供高…

2026/9/24 16:17:24 阅读更多 →
PaddleSpeech FastSpeech2 GTA Mel 生成模块(gen_gta_mel)完整解析:从 durations.txt 到声码器微调数据

PaddleSpeech FastSpeech2 GTA Mel 生成模块(gen_gta_mel)完整解析:从 durations.txt 到声码器微调数据

PaddleSpeech FastSpeech2 GTA Mel 生成模块(gen_gta_mel)完整解析:从 durations.txt 到声码器微调数据 【免费下载链接】PaddleSpeech Easy-to-use Speech Toolkit including Self-Supervised Learning model, SOTA/Streaming ASR with punc…

2026/9/24 16:17:24 阅读更多 →
【n8n】n8n项目设置中文 npx 启动方式

【n8n】n8n项目设置中文 npx 启动方式

n8n 是一个非常强大的自动化工具,允许用户在无代码的情况下构建复杂的工作流。然而,默认情况下,n8n 的界面是英文的,若需要使用中文界面,则需要进行一定的汉化操作。 本文将详细介绍如何将 n8n 界面汉化,并解决部分翻译缺失的问题。 文章目录 克隆 n8n 仓库并启动 汉化资…

2026/9/24 16:17:24 阅读更多 →
django CMS 5.0.7 升级指南:Django 4.2–6.0 兼容矩阵、迁移步骤与 15 项 Bug 修复源码级解析

django CMS 5.0.7 升级指南:Django 4.2–6.0 兼容矩阵、迁移步骤与 15 项 Bug 修复源码级解析

django CMS 5.0.7 升级指南:Django 4.2–6.0 兼容矩阵、迁移步骤与 15 项 Bug 修复源码级解析 【免费下载链接】django-cms The easy-to-use and developer-friendly enterprise CMS powered by Django 项目地址: https://gitcode.com/gh_mirrors/dj/django-cms …

2026/9/24 16:17:24 阅读更多 →
【Dify】Amiibo卡片信息采集并批量录入Notion应用

【Dify】Amiibo卡片信息采集并批量录入Notion应用

Amiibo卡片在玩家和收藏者中热度持续上升,详细整理每张卡片的信息成为许多场景下的实际需求。手动记录角色编号、属性与市场价格耗时费力,容易出错。 本文聚焦于如何借助Dify与大模型工具,从Amiibo卡片图片中自动识别和抓取关键数据,实现一键写入Notion数据库。介绍整体工…

2026/9/24 16:17:24 阅读更多 →
Smithbox完全入门:一站式搞定9款FromSoftware游戏MOD编辑的终极工具指南

Smithbox完全入门:一站式搞定9款FromSoftware游戏MOD编辑的终极工具指南

Smithbox完全入门:一站式搞定9款FromSoftware游戏MOD编辑的终极工具指南 【免费下载链接】Smithbox Smithbox is a modding tool for Elden Ring, Armored Core VI, Sekiro, Dark Souls 3, Dark Souls 2, Dark Souls, Bloodborne and Demons Souls. 项目地址: htt…

2026/9/24 16:16:23 阅读更多 →

日新闻

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为…

2026/9/24 0:00:19 阅读更多 →
单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

简介:一份基于单细胞RNA测序数据的细胞类型注释算法研究Python毕业设计源码,针对计算机相关专业正在做毕设或需要项目实战的学习者,可用于课程设计与期末大作业。项目代码完整、经导师指导评审通过,可直接运行,覆盖数据…

2026/9/24 0:00:19 阅读更多 →
C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

第一次在项目里被反射卡住,是在一个老旧的WinForms模块里:几十个类依赖PropertyChanged通知,运行时反射读属性、发通知,每次启动慢半拍不说,一上.NET Native/AOT裁剪模式几乎全面崩盘。后来我把这段逻辑全部改成C#源生…

2026/9/24 0:00:19 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/24 14:34:13 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/24 9:10:42 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/24 14:33:56 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/24 12:50:34 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/24 14:33:48 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/24 12:49:17 阅读更多 →