「13」next-shopping:地址模块+一些中间件

地址模块

首先我们更新一下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'

目前我们把authdbrepo操作都收敛到helpers里面了,其他的比如lib后续可以删除了

代码地址:github.com/liyunfu1998...

相关推荐
做梦敲代码14 分钟前
达梦数据库-读写分离集群部署
数据库·达梦数据库
m0_7482309419 分钟前
Rust赋能前端: 纯血前端将 Table 导出 Excel
前端·rust·excel
qq_5895681027 分钟前
Echarts的高级使用,动画,交互api
前端·javascript·echarts
小蜗牛慢慢爬行1 小时前
如何在 Spring Boot 微服务中设置和管理多个数据库
java·数据库·spring boot·后端·微服务·架构·hibernate
hanbarger1 小时前
nosql,Redis,minio,elasticsearch
数据库·redis·nosql
微服务 spring cloud1 小时前
配置PostgreSQL用于集成测试的步骤
数据库·postgresql·集成测试
先睡1 小时前
MySQL的架构设计和设计模式
数据库·mysql·设计模式
弗罗里达老大爷1 小时前
Redis
数据库·redis·缓存
黑客老陈1 小时前
新手小白如何挖掘cnvd通用漏洞之存储xss漏洞(利用xss钓鱼)
运维·服务器·前端·网络·安全·web3·xss