新聞中心
譯者 | 朱先忠

創(chuàng)新互聯(lián)專(zhuān)注于富錦企業(yè)網(wǎng)站建設(shè),成都響應(yīng)式網(wǎng)站建設(shè)公司,成都做商城網(wǎng)站。富錦網(wǎng)站建設(shè)公司,為富錦等地區(qū)提供建站服務(wù)。全流程按需開(kāi)發(fā),專(zhuān)業(yè)設(shè)計(jì),全程項(xiàng)目跟蹤,創(chuàng)新互聯(lián)專(zhuān)業(yè)和態(tài)度為您提供的服務(wù)
審校 | 孫淑娟
在本文中,我們將學(xué)習(xí)如何使用Next.js、Prisma、Postgres和Fastify來(lái)聯(lián)合開(kāi)發(fā)一個(gè)完整的全棧Web應(yīng)用程序。具體地說(shuō),我們將構(gòu)建一個(gè)考勤管理演示應(yīng)用程序,用于管理員工的考勤信息。該應(yīng)用程序的流程比較簡(jiǎn)單:一個(gè)管理用戶登錄頁(yè)面,創(chuàng)建當(dāng)天的考勤表界面,還有每個(gè)員工可以在考勤表上登錄和注銷(xiāo)的界面等。
何謂Next.js?
Next.js是一個(gè)靈活的基于React框架的工具,它能夠?yàn)槟峁﹦?chuàng)建快速Web應(yīng)用程序的組件。它通常被稱(chēng)為全棧式React框架,因?yàn)樗梢允骨岸撕秃蠖藨?yīng)用程序位于同一個(gè)代碼基上;并且,這種實(shí)現(xiàn)使用的是無(wú)服務(wù)器端(Serverless)功能。
何謂Prisma?
Prisma是一個(gè)開(kāi)源的ORM框架,同樣基于Node.js框架和Typescript腳本實(shí)現(xiàn)。Prisma大大簡(jiǎn)化了SQL數(shù)據(jù)庫(kù)的數(shù)據(jù)建模、遷移和數(shù)據(jù)訪問(wèn)過(guò)程。截止撰寫(xiě)本文時(shí),Prisma支持以下數(shù)據(jù)庫(kù)管理系統(tǒng):PostgreSQL、MySQL、MariaDB、SQLite、AWS Aurora、Microsoft SQL Server、Azure SQL和MongoDB。當(dāng)然,有關(guān)Prisma所有受支持的數(shù)據(jù)庫(kù)管理系統(tǒng)的列表信息,您可以參考地址https://www.prisma.io/docs/reference/database-reference/supported-databases。
何謂Postgres?
Postgres也稱(chēng)為PostgreSQL,是一個(gè)免費(fèi)開(kāi)源的關(guān)系數(shù)據(jù)庫(kù)管理系統(tǒng)。它是SQL語(yǔ)言的超集,具有許多優(yōu)秀特性,允許開(kāi)發(fā)人員安全地存儲(chǔ)和擴(kuò)展復(fù)雜的數(shù)據(jù)工作負(fù)載。
示例項(xiàng)目開(kāi)發(fā)先決條件
本文是一個(gè)實(shí)踐演示教程。因此,為了順利調(diào)試通過(guò)這個(gè)項(xiàng)目,最好確保先在您的計(jì)算機(jī)上安裝以下軟件:
- Node.js已經(jīng)成功地安裝在您的計(jì)算機(jī)上
- PostgreSQL數(shù)據(jù)庫(kù)服務(wù)器正運(yùn)行在您的計(jì)算機(jī)上
注意:本教程的代碼可以在??Github網(wǎng)站??上找到;所以,您可以隨意克隆下所有源碼并繼續(xù)學(xué)習(xí)。
項(xiàng)目設(shè)置
讓我們從設(shè)置Next.js應(yīng)用程序開(kāi)始。首先,請(qǐng)運(yùn)行下面的命令。
npx create-next-app@latest
等待安裝完成,然后運(yùn)行下面的命令來(lái)安裝依賴項(xiàng)。
yarn add fastify fastify-nextjs iron-session @prisma/client
yarn add prisma nodemon --dev
等待安裝完成即可。
設(shè)置Next.js和Fastify
默認(rèn)情況下,Next.js不使用Fastify作為其服務(wù)器。為了使用Fastfy作為我們的Next.js應(yīng)用程序的服務(wù)器,需要在你的package.json配置文件中添加以下代碼段:
"scripts": {
"dev": "nodemon server.js",
"build": "next build",
"start": "next start",
"lint": "next lint"
}創(chuàng)建我們的Fastify服務(wù)器
接下來(lái),我們創(chuàng)建一個(gè)名字為server.js的文件。這個(gè)文件是我們應(yīng)用程序的入口點(diǎn)。然后,我們添加命令require('fastfy-nextjs'),以便包括一個(gè)特定的插件,此插件能夠暴露Fastify中的Next.js API來(lái)處理頁(yè)面的渲染任務(wù)。
接下來(lái),打開(kāi)server.js文件,并添加以下代碼段:
const fastify = require('fastify')()
async function noOpParser(req, payload) {
return payload;
}
fastify.register(require('fastify-nextjs')).after(() => {
fastify.addContentTypeParser('text/plain', noOpParser);
fastify.addContentTypeParser('application/json', noOpParser);
fastify.next('/*')
fastify.next('/api/*', { method: 'ALL' });
})
fastify.listen(3000, err => {
if (err) throw err
console.log('Server listening on ')
}) 在上面代碼片斷中,我們使用插件fastify-nextjs來(lái)暴露Fastify中的Next.js API,以便幫助我們完成渲染任務(wù)。然后,我們使用noOpParser函數(shù)分析發(fā)來(lái)的請(qǐng)求。具體地說(shuō),此函數(shù)負(fù)責(zé)在我們的Next.js API路由處理器中可以使用請(qǐng)求體中的內(nèi)容。注意到,這里我們通過(guò)命令[fastify.next](
接下來(lái),我們使用“yarn dev”命令運(yùn)行上面的應(yīng)用程序。于是,程序會(huì)在地址localhost:3000上運(yùn)行起來(lái)。
Prisma設(shè)置
首先,運(yùn)行以下命令以獲得基本的Prisma設(shè)置:
npx prisma init
上面的命令將創(chuàng)建一個(gè)名字為Prisma的目錄,其下還有一個(gè)相應(yīng)的配置文件名是schema.prisma。此文件是您的主Prisma配置文件,其中將包含您的數(shù)據(jù)庫(kù)模式。此外,一個(gè).env文件也將添加到項(xiàng)目的根目錄中。注意,您需要打開(kāi)這個(gè).env文件,并將虛擬連接URL替換為PostgreSQL數(shù)據(jù)庫(kù)的真實(shí)連接URL。
現(xiàn)在,把prisma/schema.prisma文件中的內(nèi)容替換成如下代碼:
datasource db {
url = env("DATABASE_URL")
provider="postgresql"
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
email String @unique
name String
password String
role Role @default(EMPLOYEE)
attendance Attendance[]
AttendanceSheet AttendanceSheet[]
}
model AttendanceSheet {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdBy User? @relation(fields: [userId], references: [id])
userId Int?
}
model Attendance {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
signIn Boolean @default(true)
signOut Boolean
signInTime DateTime @default(now())
signOutTime DateTime
user User? @relation(fields: [userId], references: [id])
userId Int?
}
enum Role {
EMPLOYEE
ADMIN
}在上面的代碼片段中,我們創(chuàng)建了一個(gè)用戶,一個(gè)考勤表AttendanceSheet和Attention模型,并定義了每個(gè)模型之間的關(guān)系。
接下來(lái),需要在數(shù)據(jù)庫(kù)中創(chuàng)建表格。請(qǐng)運(yùn)行以下命令:
npx prisma db push
運(yùn)行上述命令后,您應(yīng)該會(huì)在終端中看到如下屏幕截圖所示的輸出:
創(chuàng)建實(shí)用工具函數(shù)
Prisma設(shè)置完成后,讓我們創(chuàng)建三個(gè)實(shí)用函數(shù),它們將不時(shí)在我們的應(yīng)用程序中使用。
為此,打開(kāi)文件lib/parseBody.js,并添加以下代碼段。此函數(shù)的任務(wù)是將請(qǐng)求正文解析為JSON:
export const parseBody = (body) => {
if (typeof body === "string") return JSON.parse(body)
return body
}然后,打開(kāi)/lib/request.js文件,添加以下代碼段。此函數(shù)負(fù)責(zé)返回iron-session的會(huì)話屬性對(duì)象。
export const sessionCookie = () => {
return ({
cookieName: "auth",
password: process.env.SESSION_PASSWORD,
// 安全提示:在生產(chǎn)環(huán)境(使用HTTPS協(xié)議)中應(yīng)當(dāng)把secure設(shè)置為true,但是不能在開(kāi)發(fā)環(huán)境(HTTP)下使用true
cookieOptions: {
secure: process.env.NODE_ENV === "production",
},
})
}接下來(lái),將SESSION_PASSWORD添加到.env文件:它應(yīng)該是至少32個(gè)字符的字符串。
設(shè)計(jì)應(yīng)用程序的樣式
完成上面的實(shí)用函數(shù)開(kāi)發(fā)后,讓我們?yōu)閼?yīng)用程序添加一些樣式。我們將為這個(gè)應(yīng)用程序定義幾個(gè)CSS模塊。為此,打開(kāi)styles/Home.modules.css文件,并添加以下代碼段:
.container {
padding: 0 2rem;
}
.man {
min-height: 100vh;
padding: 4rem 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;創(chuàng)建邊欄組件
造型完成后,讓我們創(chuàng)建邊欄組件,以便幫助我們導(dǎo)航到應(yīng)用程序控制面板上的不同頁(yè)面。為此,打開(kāi)components/SideBar.js文件,并粘貼下面的代碼段。
import Link from 'next/link'
import { useRouter } from 'next/router'
import styles from '../styles/SideBar.module.css'
const SideBar = () => {
const router = useRouter()
const logout = async () => {
try {
const response = await fetch('/api/logout', {
method: 'GET',
credentials: 'same-origin',
});
if(response.status === 200) router.push('/')
} catch (e) {
alert(e)
}
}
return (
)
}
export default SideBar
開(kāi)發(fā)登錄頁(yè)面
現(xiàn)在打開(kāi)page/index.js文件,刪除其中默認(rèn)的所有代碼并添加以下代碼段。下面的代碼將post請(qǐng)求與通過(guò)表單提供的電子郵件和密碼一起發(fā)送到localhost:3000/api/login路由。一旦憑據(jù)驗(yàn)證為有效,它就會(huì)調(diào)用router.push('/dashboard')方法;此方法負(fù)責(zé)把用戶重定向到localhost:3000/api/dashboard:
import Head from 'next/head'
import { postData } from '../lib/request';
import styles from '../styles/Home.module.css'
import { useState } from 'react';
import { useRouter } from 'next/router'
export default function Home({posts}) {
const [data, setData] = useState({email: null, password: null});
const router = useRouter()
const submit = (e) => {
e.preventDefault()
if(data.email && data.password) {
postData('/api/login', data).then(data => {
console.log(data);
if (data.status === "success") router.push('/dashboard')
});
}
}
return (
Login
)
}
設(shè)置登錄API路由
現(xiàn)在打開(kāi)頁(yè)面page/api/login.js,并添加以下代碼段。我們將使用PrismaClient進(jìn)行數(shù)據(jù)庫(kù)查詢。其中,withIronSessionApiRoute是在RESTful應(yīng)用程序中用來(lái)負(fù)責(zé)處理用戶會(huì)話的iron-session函數(shù)。
該路由處理通過(guò)localhost:3000/api/login登錄后的POST請(qǐng)求,并在用戶經(jīng)過(guò)身份驗(yàn)證后生成身份驗(yàn)證Cookie。
import { PrismaClient } from '@prisma/client'
import { withIronSessionApiRoute } from "iron-session/next";
import { parseBody } from '../../lib/parseBody';
import { sessionCookie } from '../../lib/session';
export default withIronSessionApiRoute(
async function loginRoute(req, res) {
const { email, password } = parseBody(req.body)
const prisma = new PrismaClient()
//按唯一標(biāo)識(shí)符
const user = await prisma.user.findUnique({
where: {
email
},})
if(user.password === password) {
//從數(shù)據(jù)庫(kù)中獲取用戶,然后:
user.password = undefined
req.session.user = user
await req.session.save();
return res.send({ status: 'success', data: user });
};
res.send({ status: 'error', message: "incorrect email or password" });
},
sessionCookie(),
);設(shè)置注銷(xiāo)API路由
打開(kāi)/page/api/logout文件并添加下面的代碼段。此路由負(fù)責(zé)處理對(duì)localhost:3000/api/logout的GET請(qǐng)求,該請(qǐng)求通過(guò)銷(xiāo)毀會(huì)話Cookie注銷(xiāo)用戶。
import { withIronSessionApiRoute } from "iron-session/next";
import { sessionCookie } from "../../lib/session";
export default withIronSessionApiRoute(
function logoutRoute(req, res, session) {
req.session.destroy();
res.send({ status: "success" });
},
sessionCookie()
);創(chuàng)建控制面板頁(yè)面
此頁(yè)面為用戶提供了登錄和注銷(xiāo)考勤表的界面。當(dāng)然,管理員還可以通過(guò)此界面創(chuàng)建考勤表?,F(xiàn)在,打開(kāi)page/dashboard/index.js文件,并添加下面代碼段。
import { withIronSessionSsr } from "iron-session/next";
import Head from 'next/head'
import { useState, useCallback } from "react";
import { PrismaClient } from '@prisma/client'
import SideBar from '../../components/SideBar'
import styles from '../../styles/Home.module.css'
import dashboard from '../../styles/Dashboard.module.css'
import { sessionCookie } from "../../lib/session";
import { postData } from "../../lib/request";
export default function Page(props) {
const [attendanceSheet, setState] = useState(JSON.parse(props.attendanceSheet));
const sign = useCallback((action="") => {
const body = {
attendanceSheetId: attendanceSheet[0]?.id,
action
}
postData("/api/sign-attendance", body).then(data => {
if (data.status === "success") {
setState(prevState => {
const newState = [...prevState]
newState[0].attendance[0] = data.data
return newState
})
}
})
}, [attendanceSheet])
const createAttendance = useCallback(() => {
postData("/api/create-attendance").then(data => {
if (data.status === "success") {
alert("New Attendance Sheet Created")
setState([{...data.data, attendance:[]}])
}
})
}, [])
return (
Attendance Management Dashboard
{
props.isAdmin &&
}
{ attendanceSheet.length > 0 &&
Id Created At Sign In Sign Out
{attendanceSheet[0]?.id}
{attendanceSheet[0]?.createdAt}
{
attendanceSheet[0]?.attendance.length != 0 ?
<>
{attendanceSheet[0]?.attendance[0]?.signInTime}
{
attendanceSheet[0]?.attendance[0]?.signOut ?
attendanceSheet[0]?.attendance[0]?.signOutTime: }
>
:
<>
{""}
>
}
}
)
}我們使用getServerSideProps函數(shù)來(lái)生成頁(yè)面數(shù)據(jù),而withIronSessionSsr是一個(gè)用于處理服務(wù)器端呈現(xiàn)頁(yè)面功能的iron-session函數(shù)。在下面的代碼段中,我們使用數(shù)據(jù)庫(kù)考勤表中的一行查詢考勤表的最后一行。其中,userId等于存儲(chǔ)在用戶會(huì)話中的用戶id。我們還檢查用戶是否是管理員(ADMIN)角色。
export const getServerSideProps = withIronSessionSsr( async ({req}) => {
const user = req.session.user
const prisma = new PrismaClient()
const att
文章名稱(chēng):基于Next.js、Prisma、Postgres和Fastfy構(gòu)建全棧APP
分享鏈接:http://fisionsoft.com.cn/article/dhpohis.html


咨詢
建站咨詢
