地址模块
首先我们更新一下utils/alert.js
,新增地址编辑的处理:
js
import Swal from 'sweetalert2'
import withReactContent from 'sweetalert2-react-content'
const MySwal = withReactContent(Swal)
const alert = (icon, msg) =>
MySwal.fire({
position: 'center',
icon,
title: msg,
showConfirmButton: false,
timer: 2000,
})
export const confirmAlert = ({ title, text, icon, confirmButtonText }) =>
Swal.fire({
title,
text,
icon,
showCancelButton: false,
confirmButtonColor: '#3085d6',
confirmButtonText,
})
export const editInfo = (type, title, patchData, token, isError, error) => {
Swal.fire({
title,
input: 'text',
inputAttributes: {
autocapitalize: 'off',
},
confirmButtonText: '确认',
showLoaderOnConfirm: true,
preConfirm: data => {
if (type === 'mobile') {
if (data.length < 11 || data.length >= 12) {
return Swal.showValidationMessage('请完整输入您的手机号码')
}
const mobile = Number(data)
patchData({
url: '/api/user',
body: { mobile },
token,
})
}
if (type === 'name') {
const name = data
if (name.length < 3) {
return Swal.showValidationMessage('姓氏不能少于三个字')
}
patchData({
url: '/api/user',
body: { name },
token,
})
}
if (type === 'address') {
const address = data
patchData({
url: '/api/user',
body: { address },
token,
})
}
if (isError) Swal.showValidationMessage(error?.data?.err)
},
})
}
export default alert
更新app/api/user/route.js
,在成功更新用户信息过后增加返回地址字段
js
import { NextResponse } from 'next/server'
import db from '@/lib/db'
import User from '@/models/User'
import auth from '@/middleware/auth'
import sendError from '@/utils/sendError'
const uploadInfo = auth(async req => {
try {
const { id: userId } = JSON.parse(req.headers.get('userInfo'))
const result = await req.json()
await db.connect()
await User.findByIdAndUpdate({ _id: userId }, { ...result })
const newUser = await User.findOne({ _id: userId })
await db.disconnect()
return NextResponse.json(
{
msg: '已成功更新用户信息',
user: {
avatar: newUser.avatar,
name: newUser.name,
mobile: newUser.mobile,
email: newUser.email,
role: newUser.role,
root: newUser.root,
address: newUser.address,
},
},
{
status: 201,
}
)
} catch (error) {
return sendError(500, error.message)
}
})
const getUsers = auth(async req => {
try {
const role = req.headers.get('userRole')
if (role !== 'admin') return sendError(400, '无权操作')
await db.connect()
const users = await User.find().select('-password')
await db.disconnect()
return NextResponse.json(
{
users,
},
{
status: 200,
}
)
} catch (error) {
return sendError(500, error.message)
}
})
export const PATCH = uploadInfo
export const GET = getUsers
然后修改app/(main)/profile/addresses/page.js
,修改地址页面
js
'use client'
import Image from 'next/image'
import { useDispatch, useSelector } from 'react-redux'
import { updateUser } from '@/store/slices/authSlice'
import { usePatchDataMutation } from '@/store/slices/fetchApiSlice'
import { BackButton, Icons } from '@/components'
import { editInfo } from '@/utils/alert'
import { useEffect } from 'react'
export default function Addresses() {
const dispatch = useDispatch()
const { token, user } = useSelector(state => state.auth)
const [patchData, { data, isSuccess, isError, error }] = usePatchDataMutation()
useEffect(() => {
if (isSuccess) {
dispatch(updateUser(data.user))
}
}, [isSuccess])
const editAddressHandler = () => {
editInfo('address', '请完整输入您的地址', patchData, token, isError, error)
}
console.log('user', user)
return (
<div>
<BackButton>地址</BackButton>
<div>
{user?.address ? (
<div className="px-5 flex-1">
<div className="flex justify-between py-4 border-b border-gray-200">
<p>{user.address}</p>
{user.address ? (
<Icons.Edit className="icon cursor-pointer" onClick={editAddressHandler} />
) : (
<Icons.Plus className="icon cursor-pointer" onClick={editAddressHandler} />
)}
</div>
</div>
) : (
<div className="py-20 flex flex-col items-center gap-y-4">
<div className="relative h-52 w-52">
<Image src="/images/address.svg" layout="fill" alt="address" />
</div>
<p>暂无地址</p>
<button
className="border-2 border-red-600 text-red-600 flex items-center gap-x-3 px-3 py-2 rounded-lg"
onClick={editAddressHandler}
>
<Icons.Location className="icon text-red-600" />
<span>地址输入</span>
</button>
</div>
)}
</div>
</div>
)
}
效果如下,无地址的情况:
我们输入地址,可以看到更新用户信息的时候返回了整个用户信息,然后会更新本地store.user
,就可以达到页面变动效果。
新增api
主要是新增一个获取当前用户的api,新增app/api/auth/user/route.js
js
import { NextResponse } from 'next/server'
import db from '@/lib/db'
import User from '@/models/User'
import sendError from '@/utils/sendError'
import auth from '@/middleware/auth'
const getUserInfo = auth(async req => {
try {
const { id: userId } = JSON.parse(req.headers.get('userInfo'))
await db.connect()
const user = await User.findOne({ _id: userId }).select('-password')
await db.disconnect()
return NextResponse.json(
{
user: {
name: user.name,
email: user.email,
mobile: user.mobile,
avatar: user.avatar,
address: user.address,
role: user.role,
root: user.root,
},
},
{ status: 200 }
)
} catch (error) {
return sendError(500, error.message)
}
})
export const GET = getUserInfo
helpers
本节新建如下代码:做后续使用
新建helpers/api/api-handler.js
js
import { NextRequest, NextResponse } from 'next/server'
import { errorHandler, jwtMiddleware, validateMiddleware, identityMiddleware } from '@/helpers/api'
export function apiHandler(handler, { identity, schema } = {}) {
return async (req, ...args) => {
try {
const json = await req.json()
req.json = () => json
} catch {}
try {
await jwtMiddleware(req)
await identityMiddleware(req, identity)
await validateMiddleware(req, schema)
const responseBody = await handler(req, ...args)
return NextResponse.json(responseBody || {})
} catch (err) {
console.log('global error handler', err)
return errorHandler(err)
}
}
}
新建helpers/api/error-handler.js
js
import { NextResponse } from 'next/server'
import { setJson } from './set-json'
export function errorHandler(error) {
if (typeof err === 'string') {
const is404 = err.toLowerCase().endsWith('not found')
const status = is404 ? 404 : 400
return NextResponse.json(setJson({ message: err, code: status }), { status })
}
if (err.name === 'JsonWebTokenError') {
return NextResponse.json(
setJson({
message: 'Unauthorized',
code: 401,
}),
{ status: 401 }
)
}
console.error(err)
return NextResponse.json(
setJson({
message: err.message,
code: '500',
}),
{ status: 500 }
)
}
新建helpers/api/identity-middleware.js
js
import User from '@/models/User'
import db from '@/lib/db'
export async function identityMiddleware(req, identity) {
if (!identity || identity === 'user') return
const userId = req.headers.get('userId')
db.connect()
const user = await User.findOne({ _id: userId })
db.disconnect()
if (identity === 'admin' && user.role !== 'admin') {
throw new Error('Unauthorized')
}
if (identity === 'root' && !user.root) {
throw new Error('Unauthorized')
}
req.headers.set('userRole', user.role)
req.headers.set('userRoot', user.root)
}
新建helpers/api/jwt-middleware.js
js
import { auth } from '@/helpers'
async function jwtMiddleware(req) {
if (isPublicPath(req)) {
return
}
const id = auth.verifyToken(req)
req.headers.set('userId', id)
}
function isPublicPath(req) {
const publicPaths = ['POST:/api/auth/login', 'POST:/api/auth/logout', 'POST:/api/auth/register']
return publicPaths.includes(`${req.method}:${req.nextUrl.pathname}`)
}
新建helpers/api/set-json.js
js
export const setJson = ({ code, message, data }) => {
return {
code: code || 0,
message: message || 'ok',
data: data || null,
}
}
新建helpers/api/validate-middleware.js
js
export default async function validateMiddleware(req, schema) {
if (!schema) return
const body = await req.json()
const { error, data } = schema.safeParse(body)
if (error) {
throw `Validation error: ${error.details.map(x => x.message).join(', ')}`
}
// update req.json() to return sanitized req body
req.json = () => data
}
在helpers/api/index.js
将他们导出
js
export * from './api-handler'
export * from './error-handler'
export * from './identity-middleware'
export * from './jwt-middleware'
export * from './set-json'
export * from './validate-middleware'
这上面api
下的主要用于接口的中间件
新建helpers/repo/user-repo.js
js
import bcrypt from 'bcrypt'
import db from '@/lib/db'
import User from '@/models/User'
import { createAccessToken } from '@/utils/generateToken'
const getAll = async () => {
await db.connect()
const users = await User.find().select('-password')
await db.disconnect()
return users
}
const update = async (id, params) => {
const user = await User.findById(id)
if (!user) throw '用户不存在'
Object.assign(user, params)
await user.save()
}
const create = async params => {
const { name, email, password } = params
await db.connect()
if (await User.findOne({ email })) {
throw 'email "' + email + '" 账户已存在'
}
const hashPassword = await bcrypt.hash(password, 12)
const newUser = new User({ name, email, password: hashPassword })
await newUser.save()
await db.disconnect()
return newUser
}
const authenticate = async ({ email, password } = {}) => {
const user = await User.findOne({ email })
if (!user) {
throw '找不到此电子邮件的应用程序'
}
const isMatch = await bcrypt.compare(password, user.password)
if (!isMatch) {
throw '电子邮件地址或密码不正确'
}
const token = createAccessToken({ id: user._id })
return {
user: {
name: user.name,
email: user.email,
role: user.role,
root: user.root,
},
token,
}
}
const updateRole = async (id, role) => {
await db.connect()
const user = await User.findById(id)
if (!user) throw '用户不存在'
await User.findByIdAndUpdate({ _id: id }, { role })
await db.disconnect()
}
const _delete = async id => {
await db.connect()
const user = await User.findById(id)
if (!user) throw '用户不存在'
await User.findByIdAndDelete(id)
await db.disconnect()
}
const resetPassword = async (id, password) => {
const hashPassword = await bcrypt.hash(password, 12)
await db.connect()
const user = await User.findById(id)
if (!user) throw '用户不存在'
await User.findByIdAndUpdate({ _id: id }, { password: hashPassword })
await db.disconnect()
}
const getById = async id => {
try {
await db.connect()
const user = await User.findById(id)
await db.disconnect()
return user
} catch {
throw 'User Not Found'
}
}
export const usersRepo = {
create,
authenticate,
getAll,
getById,
update,
delete: _delete,
updateRole,
resetPassword,
}
并导出helpers/repo/index.js
js
export * from './user-repo'
主要是编写之后的数据库操作
新建helpers/auth.js
js
import jwt from 'jsonwebtoken'
export const auth = {
verifyToken,
createAccessToken,
}
function verifyToken(req) {
const token = req.headers.get('authorization')
const decoded = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET)
const id = decoded.id
return id
}
function createAccessToken(payload) {
return jwt.sign(payload, process.env.ACCESS_TOKEN_SECRET, {
expiresIn: '1d',
})
}
新建helpers/db.js
js
import mongoose from 'mongoose'
const connection = {}
async function connect() {
if (connection.isConnected) {
console.log('Using existing connection.')
return
}
if (mongoose.connections.length > 0) {
connection.isConnected = mongoose.connections[0].readyState
if (connection.isConnected === 1) {
console.log('Use previous connection')
return
}
await mongoose.disconnect()
}
try {
const db = await mongoose.connect(process.env.MONGODB_URL)
console.log('New connection')
connection.isConnected = db.connections[0].readyState
} catch (error) {
console.log(error)
process.exit(1)
}
}
async function discount() {
if (connection.isConnected) {
if (process.env.NODE_ENV === 'production') {
await mongoose.disconnect()
connection.isConnected = false
} else {
console.log('not disconnected')
}
}
}
const db = { connect, disconnect }
export default db
新建helpers/index.js
js
export * from './auth'
export * from './db'
export * from './repo'
目前我们把auth
和db
、repo
操作都收敛到helpers
里面了,其他的比如lib
后续可以删除了