u
This commit is contained in:
1 parent
6849579652
commit
d60be4a75b
24 files changed
+463
-179
No files matched your search
@@ -12,5 +12,6 @@ export default antfu({
|
|||||||
'no-console': 'off', // 关闭 console 报错
|
'no-console': 'off', // 关闭 console 报错
|
||||||
'node/prefer-global/process': 'off',
|
'node/prefer-global/process': 'off',
|
||||||
'@typescript-eslint/no-require-imports': 'off',
|
'@typescript-eslint/no-require-imports': 'off',
|
||||||
|
'no-useless-catch': 'off',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -5,7 +5,7 @@ import { Stack, useRouter } from 'expo-router'
|
|||||||
import * as SplashScreen from 'expo-splash-screen'
|
import * as SplashScreen from 'expo-splash-screen'
|
||||||
import { StatusBar } from 'expo-status-bar'
|
import { StatusBar } from 'expo-status-bar'
|
||||||
import { RefreshCw } from 'lucide-react-native'
|
import { RefreshCw } from 'lucide-react-native'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect } from 'react'
|
||||||
import EmptyState from '@/components/common/EmptyStatus'
|
import EmptyState from '@/components/common/EmptyStatus'
|
||||||
import { GluestackUIProvider } from '@/components/ui/gluestack-ui-provider'
|
import { GluestackUIProvider } from '@/components/ui/gluestack-ui-provider'
|
||||||
import { useUserStore } from '@/store/user'
|
import { useUserStore } from '@/store/user'
|
||||||
@@ -33,33 +33,23 @@ export function ErrorBoundary({ error, retry }: ErrorBoundaryProps) {
|
|||||||
|
|
||||||
export default function RootLayout() {
|
export default function RootLayout() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const userStore = useUserStore()
|
||||||
const token = useUserStore(state => state.token)
|
|
||||||
|
|
||||||
const [fontsLoaded] = useFonts({
|
const [fontsLoaded] = useFonts({
|
||||||
SpaceMono: require('@/static/fonts/SpaceMono-Regular.ttf'),
|
SpaceMono: require('@/static/fonts/SpaceMono-Regular.ttf'),
|
||||||
})
|
})
|
||||||
|
|
||||||
const [storeLoaded, setStoreLoaded] = useState(false)
|
|
||||||
|
|
||||||
// 等待 store 恢复数据
|
|
||||||
useEffect(() => {
|
|
||||||
if (token !== undefined) {
|
|
||||||
setStoreLoaded(true)
|
|
||||||
}
|
|
||||||
}, [token])
|
|
||||||
|
|
||||||
// splash screen 隐藏 & 登录跳转
|
// splash screen 隐藏 & 登录跳转
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (fontsLoaded && storeLoaded) {
|
if (fontsLoaded) {
|
||||||
SplashScreen.hideAsync()
|
SplashScreen.hideAsync()
|
||||||
|
|
||||||
// 如果没有 token,则跳转登录页
|
// 如果没有 token,则跳转登录页
|
||||||
if (token === null) {
|
if (!userStore.isLoggedIn()) {
|
||||||
router.replace('/auth')
|
router.replace('/auth')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [fontsLoaded, storeLoaded, token])
|
}, [fontsLoaded])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GluestackUIProvider>
|
<GluestackUIProvider>
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import type { StepLayoutItem } from '@/components/custom/StepLayout'
|
import type { StepLayoutItem } from '@/components/custom/StepLayout'
|
||||||
import { Slot } from 'expo-router'
|
import { Slot, Stack, useRouter } from 'expo-router'
|
||||||
|
import { Image, Text, View } from 'react-native'
|
||||||
|
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||||
import StepLayout from '@/components/custom/StepLayout'
|
import StepLayout from '@/components/custom/StepLayout'
|
||||||
|
import CustomTabbar from '@/components/custom/Tabbar'
|
||||||
|
|
||||||
const steps: StepLayoutItem[] = [
|
const steps: StepLayoutItem[] = [
|
||||||
{
|
{
|
||||||
@@ -22,5 +25,6 @@ export default function AuthLayout() {
|
|||||||
<StepLayout steps={steps}>
|
<StepLayout steps={steps}>
|
||||||
<Slot />
|
<Slot />
|
||||||
</StepLayout>
|
</StepLayout>
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -36,17 +36,20 @@ export default function AuthLogin() {
|
|||||||
setForm(prev => ({ ...prev, [field]: value }))
|
setForm(prev => ({ ...prev, [field]: value }))
|
||||||
}
|
}
|
||||||
const defaultLogin = () => {
|
const defaultLogin = () => {
|
||||||
alovaInstance.Post('/auth/login/default', {
|
alovaInstance.Post('/auth/test', {
|
||||||
title: 'foo',
|
title: 'foo',
|
||||||
body: 'bar',
|
body: 'bar',
|
||||||
userId: 1,
|
userId: 1,
|
||||||
}).then((res) => {
|
})
|
||||||
console.log(res)
|
.then((res) => {
|
||||||
|
console.log('成功返回:', res)
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('请求失败或未授权:', err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<View className="flex flex-col items-center px-6 h-full">
|
<View className="flex flex-col items-center px-6 h-full">
|
||||||
<Image
|
<Image
|
||||||
source={require('@/static/images/logo.png')}
|
source={require('@/static/images/logo.png')}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export default function AuthRegister() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView ref={scrollRef} style={{ flex: 1 }}>
|
<ScrollView ref={scrollRef} style={{ flex: 1 }}>
|
||||||
<View className="flex flex-col items-center px-6">
|
<View className="flex flex-col items-center px-6 bg-white ">
|
||||||
<Image
|
<Image
|
||||||
source={require('@/static/images/logo.png')}
|
source={require('@/static/images/logo.png')}
|
||||||
style={{ width: 120, height: 120 }}
|
style={{ width: 120, height: 120 }}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Ionicons } from '@expo/vector-icons'
|
import { Ionicons } from '@expo/vector-icons'
|
||||||
import { usePathname, useRouter } from 'expo-router'
|
import { usePathname, useRouter } from 'expo-router'
|
||||||
import { Image, Pressable, ScrollView, Text, View } from 'react-native'
|
import { Image, Pressable, Text, View } from 'react-native'
|
||||||
import Animated, { SlideInRight } from 'react-native-reanimated'
|
import Animated, { SlideInLeft, SlideInRight } from 'react-native-reanimated'
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||||
|
|
||||||
export interface StepLayoutItem {
|
export interface StepLayoutItem {
|
||||||
@@ -31,11 +31,13 @@ export default function StepLayout({ steps, children, showHearder = true }: Step
|
|||||||
// 是否显示header
|
// 是否显示header
|
||||||
const headerVisible = showHearder !== false && currentStep.showHearder !== false
|
const headerVisible = showHearder !== false && currentStep.showHearder !== false
|
||||||
|
|
||||||
|
const enteringAnimation = canGoBack ? SlideInRight.duration(300) : SlideInLeft.duration(300)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={{ flex: 1 }}>
|
<SafeAreaView style={{ flex: 1 }}>
|
||||||
<Animated.View
|
<Animated.View
|
||||||
key={pathname}
|
key={pathname}
|
||||||
entering={SlideInRight.duration(300)}
|
entering={enteringAnimation}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
>
|
>
|
||||||
{headerVisible && (
|
{headerVisible && (
|
||||||
@@ -44,10 +46,7 @@ export default function StepLayout({ steps, children, showHearder = true }: Step
|
|||||||
onPress={canGoBack ? (currentStep.onBack ?? (() => router.back())) : undefined}
|
onPress={canGoBack ? (currentStep.onBack ?? (() => router.back())) : undefined}
|
||||||
style={{ opacity: canGoBack ? 1 : 0 }}
|
style={{ opacity: canGoBack ? 1 : 0 }}
|
||||||
>
|
>
|
||||||
<Ionicons
|
<Ionicons name="arrow-back" size={22} />
|
||||||
name="arrow-back"
|
|
||||||
size={22}
|
|
||||||
/>
|
|
||||||
</Pressable>
|
</Pressable>
|
||||||
|
|
||||||
<Text className="flex-1 text-center text-lg font-semibold">
|
<Text className="flex-1 text-center text-lg font-semibold">
|
||||||
|
|||||||
Whitespace-only changes.
@@ -1,21 +1,110 @@
|
|||||||
|
import type { Method } from 'alova'
|
||||||
|
import type { AxiosError } from 'axios'
|
||||||
import { axiosRequestAdapter } from '@alova/adapter-axios'
|
import { axiosRequestAdapter } from '@alova/adapter-axios'
|
||||||
import { createAlova } from 'alova'
|
import { createAlova } from 'alova'
|
||||||
import { createServerTokenAuthentication } from 'alova/client'
|
import { router } from 'expo-router'
|
||||||
import adapterFetch from 'alova/fetch'
|
import { userStore } from '@/store/user'
|
||||||
import { useToast } from '@/utils/toast'
|
import { toast } from '@/utils/toast'
|
||||||
|
|
||||||
const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthentication({
|
enum HttpCode {
|
||||||
// ...
|
Success = 200,
|
||||||
})
|
Unauthorized = 401,
|
||||||
|
RefreshToken = 402,
|
||||||
|
Fail = 500,
|
||||||
|
}
|
||||||
|
|
||||||
|
const WhiteList = [
|
||||||
|
'/auth/login/**',
|
||||||
|
'/auth/register/**',
|
||||||
|
'/auth/refresh/token',
|
||||||
|
]
|
||||||
|
|
||||||
|
function isWhiteListed(url: string) {
|
||||||
|
return WhiteList.some((pattern) => {
|
||||||
|
if (pattern.endsWith('/**')) {
|
||||||
|
const base = pattern.slice(0, -3)
|
||||||
|
return url.startsWith(base)
|
||||||
|
}
|
||||||
|
return url === pattern
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const alovaInstance = createAlova({
|
const alovaInstance = createAlova({
|
||||||
// baseURL: 'http://192.168.1.136:8085/app',
|
baseURL: 'http://192.168.1.136:8085/app',
|
||||||
baseURL: 'http://192.168.5.6:8085/app',
|
|
||||||
requestAdapter: axiosRequestAdapter(),
|
requestAdapter: axiosRequestAdapter(),
|
||||||
responded(response) {
|
responded: {
|
||||||
// response自动被推断为AxiosResponse类型
|
onSuccess: async (response, method: Method) => {
|
||||||
|
const resData = response.data as ResponseData<any>
|
||||||
|
|
||||||
return response.data
|
if (resData.code === HttpCode.Success) {
|
||||||
|
return resData.data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access Token 失效 → 重新登录
|
||||||
|
if (resData.code === HttpCode.Unauthorized) {
|
||||||
|
console.log('===到这一步了===')
|
||||||
|
router.replace('/auth/login')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access Token 过期但 Refresh Token 有效 → 刷新 token 并重试
|
||||||
|
if (resData.code === HttpCode.RefreshToken) {
|
||||||
|
try {
|
||||||
|
const refreshToken = userStore.getState().userInfo?.refreshToken
|
||||||
|
if (!refreshToken) {
|
||||||
|
router.replace('/auth/login')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用刷新接口
|
||||||
|
const res = await alovaInstance.Get<ResponseData<string>>('/auth/refresh/token', {
|
||||||
|
headers: {
|
||||||
|
refreshToken,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const newToken = res.data
|
||||||
|
|
||||||
|
// 更新 store
|
||||||
|
userStore.getState().setUserInfo({
|
||||||
|
...userStore.getState().userInfo!,
|
||||||
|
token: newToken,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 重试原请求
|
||||||
|
method.config.headers.token = newToken
|
||||||
|
await method.send()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log('===到这一步了===')
|
||||||
|
router.replace('/auth/login')
|
||||||
|
return Promise.reject(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他错误
|
||||||
|
return Promise.reject(resData.message || '请求失败')
|
||||||
|
},
|
||||||
|
|
||||||
|
onError: (error: AxiosError, method) => {
|
||||||
|
console.log(error.status)
|
||||||
|
return Promise.reject(error)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeRequest(method) {
|
||||||
|
if (isWhiteListed(method.url))
|
||||||
|
return
|
||||||
|
|
||||||
|
if (!userStore.getState().isLoggedIn()) {
|
||||||
|
toast.show({
|
||||||
|
title: '尚未登录',
|
||||||
|
})
|
||||||
|
router.replace('/auth/login')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
method.config.headers.token = userStore.getState().userInfo?.token ?? ''
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -4,26 +4,22 @@ import { create } from 'zustand'
|
|||||||
import { createJSONStorage, persist } from 'zustand/middleware'
|
import { createJSONStorage, persist } from 'zustand/middleware'
|
||||||
|
|
||||||
interface UserState {
|
interface UserState {
|
||||||
userInfo: { userId: string, username: string } | null
|
userInfo: UserInfo | null
|
||||||
token: string | null
|
setUserInfo: (userInfo: UserInfo | null) => void
|
||||||
setUserInfo: (userInfo: UserState['userInfo']) => void
|
|
||||||
setToken: (token: string) => void
|
|
||||||
logout: () => void
|
logout: () => void
|
||||||
isLoggedIn: () => boolean
|
isLoggedIn: () => boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useUserStore = create<UserState>()(
|
const useUserStore = create<UserState>()(
|
||||||
persist(
|
persist(
|
||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
userInfo: null,
|
userInfo: null,
|
||||||
token: null,
|
|
||||||
setUserInfo: userInfo => set({ userInfo }),
|
setUserInfo: userInfo => set({ userInfo }),
|
||||||
setToken: token => set({ token }),
|
|
||||||
logout: () => {
|
logout: () => {
|
||||||
console.log('当前 token:', get().token)
|
console.log('当前 token:', get().userInfo?.token)
|
||||||
set({ userInfo: null, token: null })
|
set({ userInfo: null })
|
||||||
},
|
},
|
||||||
isLoggedIn: () => !!get().token,
|
isLoggedIn: () => !!get().userInfo?.token,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: 'user-storage',
|
name: 'user-storage',
|
||||||
@@ -31,3 +27,6 @@ export const useUserStore = create<UserState>()(
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
const userStore = useUserStore
|
||||||
|
|
||||||
|
export { userStore, useUserStore }
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
export {}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface ResponseData<T> {
|
||||||
|
trackId: string
|
||||||
|
code: number
|
||||||
|
message: string
|
||||||
|
data: T
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserInfo {
|
||||||
|
account: string
|
||||||
|
id: string
|
||||||
|
seqNo: number
|
||||||
|
username: string
|
||||||
|
gender: number
|
||||||
|
token?: string
|
||||||
|
refreshToken?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { IToastProps } from '@gluestack-ui/core/lib/esm/toast/creator/types'
|
import type { IToastProps } from '@gluestack-ui/core/lib/esm/toast/creator/types'
|
||||||
import React, { useRef } from 'react'
|
import React, { createContext, useContext, useRef } from 'react'
|
||||||
import { Toast, ToastDescription, ToastTitle, useToast as useGluestackToast } from '@/components/ui/toast'
|
import { Toast, ToastDescription, ToastTitle, useToast as useGluestackToast } from '@/components/ui/toast'
|
||||||
|
|
||||||
type ToastVariant = 'solid' | 'outline'
|
type ToastVariant = 'solid' | 'outline'
|
||||||
@@ -12,28 +12,22 @@ interface ShowToastOptions extends IToastProps {
|
|||||||
action?: ToastAction
|
action?: ToastAction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 创建全局 toast 实例
|
||||||
|
let globalToast: any = null
|
||||||
|
|
||||||
|
// 创建 Context 用于在组件内部使用
|
||||||
|
const ToastContext = createContext<ReturnType<typeof useGluestackToast> | null>(null)
|
||||||
|
|
||||||
export function ToastProviderWrapper({ children }: { children: React.ReactNode }) {
|
export function ToastProviderWrapper({ children }: { children: React.ReactNode }) {
|
||||||
const toast = useGluestackToast()
|
const toast = useGluestackToast()
|
||||||
const toastRef = useRef(toast)
|
const toastRef = useRef(toast)
|
||||||
toastRef.current = toast
|
toastRef.current = toast
|
||||||
|
|
||||||
return <>{children}</>
|
// 设置全局实例
|
||||||
}
|
React.useEffect(() => {
|
||||||
|
globalToast = {
|
||||||
/**
|
|
||||||
* 自定义 Hook,用法:
|
|
||||||
* const toast = useToast();
|
|
||||||
* toast.show({ ... });
|
|
||||||
* toast.close(id);
|
|
||||||
* toast.closeAll();
|
|
||||||
*/
|
|
||||||
export function useToast() {
|
|
||||||
const toast = useGluestackToast()
|
|
||||||
|
|
||||||
return {
|
|
||||||
show: (options: ShowToastOptions) => {
|
show: (options: ShowToastOptions) => {
|
||||||
const id = Math.random().toString()
|
const id = Math.random().toString()
|
||||||
|
|
||||||
toast.show({
|
toast.show({
|
||||||
id,
|
id,
|
||||||
placement: options.placement ?? 'top',
|
placement: options.placement ?? 'top',
|
||||||
@@ -54,18 +48,92 @@ export function useToast() {
|
|||||||
</Toast>
|
</Toast>
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
return id
|
return id
|
||||||
},
|
},
|
||||||
|
|
||||||
close: (id: string) => {
|
close: (id: string) => {
|
||||||
toast.close(id)
|
toast.close(id)
|
||||||
},
|
},
|
||||||
|
|
||||||
closeAll: () => {
|
closeAll: () => {
|
||||||
toast.closeAll()
|
toast.closeAll()
|
||||||
},
|
},
|
||||||
|
isActive: (id: string) => toast.isActive(id),
|
||||||
|
}
|
||||||
|
}, [toast])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToastContext.Provider value={toast}>
|
||||||
|
{children}
|
||||||
|
</ToastContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义 Hook,用于 React 组件内部
|
||||||
|
*/
|
||||||
|
export function useToast() {
|
||||||
|
const toast = useContext(ToastContext)
|
||||||
|
|
||||||
|
if (!toast) {
|
||||||
|
throw new Error('useToast must be used within a ToastProviderWrapper')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
show: (options: ShowToastOptions) => {
|
||||||
|
const id = Math.random().toString()
|
||||||
|
toast.show({
|
||||||
|
id,
|
||||||
|
placement: options.placement ?? 'top',
|
||||||
|
duration: options.duration !== undefined ? options.duration : 3000,
|
||||||
|
avoidKeyboard: options.avoidKeyboard ?? false,
|
||||||
|
containerStyle: options.containerStyle,
|
||||||
|
onCloseComplete: options.onCloseComplete,
|
||||||
|
render: options.render
|
||||||
|
? () => options.render!({ id })
|
||||||
|
: ({ id }) => (
|
||||||
|
<Toast
|
||||||
|
nativeID={`toast-${id}`}
|
||||||
|
variant={options.variant ?? 'solid'}
|
||||||
|
action={options.action ?? 'muted'}
|
||||||
|
>
|
||||||
|
{options.title && <ToastTitle className="font-blod">{options.title}</ToastTitle>}
|
||||||
|
{options.description && <ToastDescription>{options.description}</ToastDescription>}
|
||||||
|
</Toast>
|
||||||
|
),
|
||||||
|
})
|
||||||
|
return id
|
||||||
|
},
|
||||||
|
close: (id: string) => {
|
||||||
|
toast.close(id)
|
||||||
|
},
|
||||||
|
closeAll: () => {
|
||||||
|
toast.closeAll()
|
||||||
|
},
|
||||||
isActive: (id: string) => toast.isActive(id),
|
isActive: (id: string) => toast.isActive(id),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局 toast 实例,可在组件外部使用
|
||||||
|
*/
|
||||||
|
export const toast = {
|
||||||
|
show: (options: ShowToastOptions) => {
|
||||||
|
if (!globalToast) {
|
||||||
|
console.warn('Toast not initialized. Make sure ToastProviderWrapper is rendered.')
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
return globalToast.show(options)
|
||||||
|
},
|
||||||
|
close: (id: string) => {
|
||||||
|
if (globalToast) {
|
||||||
|
globalToast.close(id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closeAll: () => {
|
||||||
|
if (globalToast) {
|
||||||
|
globalToast.closeAll()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isActive: (id: string) => {
|
||||||
|
return globalToast ? globalToast.isActive(id) : false
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"include": [
|
"include": [
|
||||||
"./**/*.ts",
|
"./**/*.ts",
|
||||||
"./**/*.tsx",
|
"./**/*.tsx",
|
||||||
"./src/types/*.d.ts",
|
"src/**/*.d.ts",
|
||||||
"nativewind-env.d.ts"
|
"nativewind-env.d.ts"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
|
|||||||
@@ -13,18 +13,48 @@ func InitAuthRouter(r *router.Router) {
|
|||||||
group := r.Group("auth")
|
group := r.Group("auth")
|
||||||
group.POST("/login/default", loginDefault)
|
group.POST("/login/default", loginDefault)
|
||||||
group.POST("/register/default", registerDefault)
|
group.POST("/register/default", registerDefault)
|
||||||
|
group.POST("/refresh/token", RefreshAccessToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
//func RefreshToken(c *router.Context) error {
|
func RefreshAccessToken(c *router.Context) error {
|
||||||
// _, err := c.GetBodyWithRequired("accessToken", "refreshToken")
|
// 从 Header 获取 Refresh Token
|
||||||
// if err != nil {
|
refreshToken := c.Header("refreshToken")
|
||||||
// return err
|
if refreshToken == "" {
|
||||||
// }
|
return response.Fail(c).
|
||||||
// //accessToken := bodyData.GetString("accessToken")
|
Code(response.HttpCode.Unauthorized).
|
||||||
// //refreshToken := bodyData.GetString("refreshToken")
|
Message("未提供 Token").
|
||||||
//}
|
Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证 Refresh Token
|
||||||
|
tokenResult := jwtx.VerifyToken(refreshToken)
|
||||||
|
|
||||||
|
if !tokenResult.IsValid || tokenResult.IsExpired {
|
||||||
|
// Refresh Token 无效或过期,需要重新登录
|
||||||
|
return response.Fail(c).
|
||||||
|
Code(response.HttpCode.Unauthorized).
|
||||||
|
Message("登录已过期,请重新登录").
|
||||||
|
Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
userID := tokenResult.Claims.UserID
|
||||||
|
|
||||||
|
// 生成新的 Access Token
|
||||||
|
newAccessToken, err := jwtx.CreateAccessToken(userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回新的 Access Token
|
||||||
|
respData := map[string]string{
|
||||||
|
"accessToken": newAccessToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Success(c).
|
||||||
|
Data(respData).
|
||||||
|
Send()
|
||||||
|
}
|
||||||
|
|
||||||
// 默认登录
|
|
||||||
// 默认登录
|
// 默认登录
|
||||||
func loginDefault(c *router.Context) error {
|
func loginDefault(c *router.Context) error {
|
||||||
// 获取请求体并校验必填字段
|
// 获取请求体并校验必填字段
|
||||||
@@ -43,13 +73,12 @@ func loginDefault(c *router.Context) error {
|
|||||||
)
|
)
|
||||||
user, err := db.FindOne("user_info", userQuery)
|
user, err := db.FindOne("user_info", userQuery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Success(c).Message("查询用户失败,请稍后重试").IsSuccess(false).Send()
|
return response.Success(c).Message("查询用户失败,请稍后重试").Send()
|
||||||
}
|
}
|
||||||
|
|
||||||
if user == nil {
|
if user == nil {
|
||||||
// 账号或密码错误
|
// 账号或密码错误
|
||||||
return response.Success(c).
|
return response.Fail(c).
|
||||||
IsSuccess(false).
|
|
||||||
Message("账号或密码错误").
|
Message("账号或密码错误").
|
||||||
Send()
|
Send()
|
||||||
}
|
}
|
||||||
@@ -99,7 +128,7 @@ func registerDefault(c *router.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(existingUsers) > 0 {
|
if len(existingUsers) > 0 {
|
||||||
return response.Error(c).IsSuccess(false).Message("该账号已被注册").Send()
|
return response.Fail(c).Message("该账号已被注册").Send()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构造新用户数据
|
// 构造新用户数据
|
||||||
@@ -115,7 +144,7 @@ func registerDefault(c *router.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if insertedRows != 1 {
|
if insertedRows != 1 {
|
||||||
return response.Success(c).IsSuccess(false).Message("注册失败").Send()
|
return response.Fail(c).Message("注册失败").Send()
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.Success(c).Message("注册成功").Send()
|
return response.Success(c).Message("注册成功").Send()
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
response "base-go-v2/internal/reponse"
|
||||||
|
"base-go-v2/internal/router"
|
||||||
|
"base-go-v2/internal/utils/jwtx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AuthMiddleware() router.HandlerFunc {
|
||||||
|
return func(c *router.Context) error {
|
||||||
|
token := c.Header("token")
|
||||||
|
if token == "" {
|
||||||
|
c.Abort()
|
||||||
|
return response.Fail(c).
|
||||||
|
Code(response.HttpCode.Unauthorized).
|
||||||
|
Message("未提供 Token").
|
||||||
|
Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenVerify := jwtx.VerifyToken(token)
|
||||||
|
|
||||||
|
// Access Token 有效
|
||||||
|
if tokenVerify.IsValid {
|
||||||
|
c.Set("user_id", tokenVerify.Claims.UserID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access Token 失效 → 返回刷新提示
|
||||||
|
if !tokenVerify.IsValid {
|
||||||
|
c.Abort()
|
||||||
|
return response.Fail(c).
|
||||||
|
Code(response.HttpCode.RefreshToken).
|
||||||
|
Message("Access Token 已过期,请刷新").
|
||||||
|
Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ func ErrorMiddleware() router.HandlerFunc {
|
|||||||
// be 是 nil 指针类型
|
// be 是 nil 指针类型
|
||||||
if errors.As(err, &be) {
|
if errors.As(err, &be) {
|
||||||
// 业务错误,不记录日志
|
// 业务错误,不记录日志
|
||||||
return response.Error(c).Message(err.Error()).Send()
|
return response.Fail(c).Message(err.Error()).Send()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,6 +40,6 @@ func ErrorMiddleware() router.HandlerFunc {
|
|||||||
logx.String("error", err.Error()),
|
logx.String("error", err.Error()),
|
||||||
logx.String("stack", stack),
|
logx.String("stack", stack),
|
||||||
)
|
)
|
||||||
return response.Error(c).Message("服务器内部错误").Send()
|
return response.Fail(c).Message("服务器内部错误").Send()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@ func RecoveryMiddleware() router.HandlerFunc {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// 返回统一 JSON 响应
|
// 返回统一 JSON 响应
|
||||||
_ = response.Error(c).Message("系统 Panic").Send()
|
_ = response.Fail(c).Message("系统 Panic").Send()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ func InitAppRouter() *router.Router {
|
|||||||
r.Use(middleware.CORSMiddleware())
|
r.Use(middleware.CORSMiddleware())
|
||||||
r.Use(middleware.LoggerMiddleware())
|
r.Use(middleware.LoggerMiddleware())
|
||||||
r.Use(middleware.ErrorMiddleware())
|
r.Use(middleware.ErrorMiddleware())
|
||||||
|
r.Use(middleware.AuthMiddleware()).ExcludePaths("/auth/**")
|
||||||
|
|
||||||
auth.InitAuthRouter(r)
|
auth.InitAuthRouter(r)
|
||||||
return r
|
return r
|
||||||
|
|||||||
@@ -8,128 +8,71 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Result 是最终响应结构体
|
|
||||||
type Result struct {
|
type Result struct {
|
||||||
IsSuccess bool `json:"isSuccess"` // 是否成功
|
Code int `json:"code"`
|
||||||
Code string `json:"code"` // 状态码
|
Message string `json:"message"`
|
||||||
Message string `json:"message"` // 提示信息
|
Data interface{} `json:"data,omitempty"`
|
||||||
Data interface{} `json:"data,omitempty"` // 数据
|
TrackId string `json:"trackId,omitempty"`
|
||||||
TrackId string `json:"trackId,omitempty"` // 请求追踪ID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Code 定义状态码
|
var HttpCode = struct {
|
||||||
type Code struct {
|
Success int
|
||||||
Value string
|
Unauthorized int
|
||||||
Message string
|
Fail int
|
||||||
|
RefreshToken int
|
||||||
|
}{
|
||||||
|
Success: 200,
|
||||||
|
Unauthorized: 401,
|
||||||
|
Fail: 500,
|
||||||
|
RefreshToken: 402,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 常用状态码
|
|
||||||
var (
|
|
||||||
CodeSuccess = Code{"0000", "请求成功"}
|
|
||||||
CodeFail = Code{"0001", "请求失败"}
|
|
||||||
CodeNoLogin = Code{"1001", "未登录"}
|
|
||||||
CodeLoginExpired = Code{"1002", "登录已过期"}
|
|
||||||
CodeInvalidToken = Code{"1003", "Token 无效"}
|
|
||||||
CodeInvalidAccount = Code{"1005", "账号或密码错误"}
|
|
||||||
)
|
|
||||||
|
|
||||||
const defaultMessage = "未知错误"
|
|
||||||
|
|
||||||
// Builder 用于构建响应
|
|
||||||
type Builder struct {
|
type Builder struct {
|
||||||
c *router.Context
|
c *router.Context
|
||||||
result Result
|
result Result
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------- 构造函数 ----------------
|
|
||||||
|
|
||||||
// Success 创建成功响应
|
|
||||||
func Success(c *router.Context) *Builder {
|
func Success(c *router.Context) *Builder {
|
||||||
return &Builder{
|
return &Builder{
|
||||||
c: c,
|
c: c,
|
||||||
result: Result{
|
result: Result{
|
||||||
IsSuccess: true,
|
Code: HttpCode.Success,
|
||||||
Code: CodeSuccess.Value,
|
Message: "请求成功",
|
||||||
Message: CodeSuccess.Message,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error 创建失败响应
|
func Fail(c *router.Context) *Builder {
|
||||||
func Error(c *router.Context) *Builder {
|
|
||||||
return &Builder{
|
return &Builder{
|
||||||
c: c,
|
c: c,
|
||||||
result: Result{
|
result: Result{
|
||||||
IsSuccess: false,
|
Code: HttpCode.Fail,
|
||||||
Code: CodeFail.Value,
|
Message: "请求失败",
|
||||||
Message: CodeFail.Message,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------- 链式方法 ----------------
|
func (b *Builder) Code(code int) *Builder {
|
||||||
|
|
||||||
// 设置 IsSuccess 字段
|
|
||||||
func (b *Builder) IsSuccess(IsSuccess bool) *Builder {
|
|
||||||
b.result.IsSuccess = IsSuccess
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置 Code,并根据 Code 自动设置 IsSuccess
|
|
||||||
func (b *Builder) Code(code string) *Builder {
|
|
||||||
b.result.Code = code
|
b.result.Code = code
|
||||||
b.result.Message = GetMessage(code)
|
|
||||||
b.result.IsSuccess = code == CodeSuccess.Value
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置 Code 对象,并自动设置 IsSuccess
|
|
||||||
func (b *Builder) CodeObj(code Code) *Builder {
|
|
||||||
b.result.Code = code.Value
|
|
||||||
b.result.Message = code.Message
|
|
||||||
b.result.IsSuccess = code.Value == CodeSuccess.Value
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// 自定义提示信息
|
|
||||||
func (b *Builder) Message(msg string) *Builder {
|
func (b *Builder) Message(msg string) *Builder {
|
||||||
b.result.Message = msg
|
b.result.Message = msg
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置返回数据
|
|
||||||
func (b *Builder) Data(data interface{}) *Builder {
|
func (b *Builder) Data(data interface{}) *Builder {
|
||||||
b.result.Data = data
|
b.result.Data = data
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送响应
|
|
||||||
func (b *Builder) Send() error {
|
func (b *Builder) Send() error {
|
||||||
// 默认 Data 为空
|
|
||||||
if b.result.Data == nil {
|
if b.result.Data == nil {
|
||||||
b.result.Data = ""
|
b.result.Data = ""
|
||||||
}
|
}
|
||||||
|
if trackId := routinex.Get(logx.TrackID); trackId != nil {
|
||||||
// 从 routinex 获取 trackId
|
|
||||||
trackId := routinex.Get(logx.TrackID)
|
|
||||||
if trackId != nil {
|
|
||||||
b.result.TrackId = strutil.ToString(trackId)
|
b.result.TrackId = strutil.ToString(trackId)
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.c.JSON(http.StatusOK, b.result)
|
return b.c.JSON(http.StatusOK, b.result)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------- 辅助函数 ----------------
|
|
||||||
|
|
||||||
// 根据 Code 获取默认提示信息
|
|
||||||
func GetMessage(value string) string {
|
|
||||||
for _, c := range []Code{
|
|
||||||
CodeSuccess, CodeFail, CodeNoLogin,
|
|
||||||
CodeLoginExpired, CodeInvalidToken, CodeInvalidAccount,
|
|
||||||
} {
|
|
||||||
if c.Value == value {
|
|
||||||
return c.Message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultMessage
|
|
||||||
}
|
|
||||||
@@ -69,6 +69,22 @@ func (n *node) search(parts []string, height int, params map[string]string) *nod
|
|||||||
type middlewareEntry struct {
|
type middlewareEntry struct {
|
||||||
handler HandlerFunc
|
handler HandlerFunc
|
||||||
excludePaths map[string]struct{}
|
excludePaths map[string]struct{}
|
||||||
|
excludePrefix []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断路径是否被排除
|
||||||
|
func (m *middlewareEntry) IsExcluded(path string) bool {
|
||||||
|
if m.excludePaths != nil {
|
||||||
|
if _, ok := m.excludePaths[path]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, prefix := range m.excludePrefix {
|
||||||
|
if strings.HasPrefix(path, prefix) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Router 主结构
|
// Router 主结构
|
||||||
@@ -78,7 +94,7 @@ type Router struct {
|
|||||||
basePath string
|
basePath string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRouter 创建
|
// 创建 Router
|
||||||
func NewRouter(basePath ...string) *Router {
|
func NewRouter(basePath ...string) *Router {
|
||||||
path := "/"
|
path := "/"
|
||||||
if len(basePath) > 0 && basePath[0] != "" {
|
if len(basePath) > 0 && basePath[0] != "" {
|
||||||
@@ -90,11 +106,13 @@ func NewRouter(basePath ...string) *Router {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MiddlewareHandle 用于链式排除路径
|
||||||
type MiddlewareHandle struct {
|
type MiddlewareHandle struct {
|
||||||
router *Router
|
router *Router
|
||||||
entryIdx int
|
entryIdx int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use 注册中间件
|
||||||
func (r *Router) Use(handler HandlerFunc) *MiddlewareHandle {
|
func (r *Router) Use(handler HandlerFunc) *MiddlewareHandle {
|
||||||
entry := middlewareEntry{handler: handler}
|
entry := middlewareEntry{handler: handler}
|
||||||
r.middleware = append(r.middleware, entry)
|
r.middleware = append(r.middleware, entry)
|
||||||
@@ -104,15 +122,27 @@ func (r *Router) Use(handler HandlerFunc) *MiddlewareHandle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExcludePaths 支持 /** 前缀匹配
|
||||||
func (mh *MiddlewareHandle) ExcludePaths(paths ...string) *Router {
|
func (mh *MiddlewareHandle) ExcludePaths(paths ...string) *Router {
|
||||||
excludeMap := make(map[string]struct{}, len(paths))
|
excludeMap := make(map[string]struct{}, 0)
|
||||||
|
excludePrefix := make([]string, 0)
|
||||||
|
|
||||||
for _, p := range paths {
|
for _, p := range paths {
|
||||||
|
if strings.HasSuffix(p, "/**") {
|
||||||
|
base := strings.TrimSuffix(p, "/**")
|
||||||
|
excludePrefix = append(excludePrefix, base)
|
||||||
|
} else {
|
||||||
excludeMap[p] = struct{}{}
|
excludeMap[p] = struct{}{}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mh.router.middleware[mh.entryIdx].excludePaths = excludeMap
|
mh.router.middleware[mh.entryIdx].excludePaths = excludeMap
|
||||||
|
mh.router.middleware[mh.entryIdx].excludePrefix = excludePrefix
|
||||||
|
|
||||||
return mh.router
|
return mh.router
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Group 创建子路由组
|
||||||
func (r *Router) Group(prefix string, m ...HandlerFunc) *Router {
|
func (r *Router) Group(prefix string, m ...HandlerFunc) *Router {
|
||||||
newMiddleware := make([]middlewareEntry, len(r.middleware))
|
newMiddleware := make([]middlewareEntry, len(r.middleware))
|
||||||
copy(newMiddleware, r.middleware)
|
copy(newMiddleware, r.middleware)
|
||||||
@@ -127,6 +157,7 @@ func (r *Router) Group(prefix string, m ...HandlerFunc) *Router {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET/POST
|
||||||
func (r *Router) GET(path string, handler HandlerFunc) *Router {
|
func (r *Router) GET(path string, handler HandlerFunc) *Router {
|
||||||
return r.handle("GET", path, handler)
|
return r.handle("GET", path, handler)
|
||||||
}
|
}
|
||||||
@@ -145,7 +176,7 @@ func (r *Router) handle(method, path string, handler HandlerFunc) *Router {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler 入口函数(fasthttp)
|
// Handler 入口(fasthttp)
|
||||||
func (r *Router) Handler(ctx *fasthttp.RequestCtx) {
|
func (r *Router) Handler(ctx *fasthttp.RequestCtx) {
|
||||||
method := string(ctx.Method())
|
method := string(ctx.Method())
|
||||||
root := r.roots[method]
|
root := r.roots[method]
|
||||||
@@ -166,11 +197,9 @@ func (r *Router) Handler(ctx *fasthttp.RequestCtx) {
|
|||||||
handlers := make([]HandlerFunc, 0)
|
handlers := make([]HandlerFunc, 0)
|
||||||
path := string(ctx.Path())
|
path := string(ctx.Path())
|
||||||
for _, m := range r.middleware {
|
for _, m := range r.middleware {
|
||||||
if m.excludePaths != nil {
|
if m.IsExcluded(path) {
|
||||||
if _, excluded := m.excludePaths[path]; excluded {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
|
||||||
handlers = append(handlers, m.handler)
|
handlers = append(handlers, m.handler)
|
||||||
}
|
}
|
||||||
handlers = append(handlers, n.handler) // 最终 handler
|
handlers = append(handlers, n.handler) // 最终 handler
|
||||||
@@ -185,6 +214,7 @@ func (r *Router) Handler(ctx *fasthttp.RequestCtx) {
|
|||||||
_ = c.Next()
|
_ = c.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 辅助函数
|
||||||
func parsePattern(pattern string) []string {
|
func parsePattern(pattern string) []string {
|
||||||
vs := strings.Split(strings.Trim(pattern, "/"), "/")
|
vs := strings.Split(strings.Trim(pattern, "/"), "/")
|
||||||
parts := make([]string, 0, len(vs))
|
parts := make([]string, 0, len(vs))
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ type CustomClaims struct {
|
|||||||
// TokenResult 用于返回验证结果
|
// TokenResult 用于返回验证结果
|
||||||
type TokenResult struct {
|
type TokenResult struct {
|
||||||
Claims *CustomClaims
|
Claims *CustomClaims
|
||||||
Valid bool
|
IsValid bool
|
||||||
Expired bool
|
IsExpired bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateAccessToken 创建 Access Token,只返回 token 和 error
|
// CreateAccessToken 创建 Access Token,只返回 token 和 error
|
||||||
@@ -61,7 +61,7 @@ func createToken(userID int, expiry time.Duration) (string, error) {
|
|||||||
|
|
||||||
// VerifyToken 验证 token 并返回结构化结果
|
// VerifyToken 验证 token 并返回结构化结果
|
||||||
func VerifyToken(tokenString string) TokenResult {
|
func VerifyToken(tokenString string) TokenResult {
|
||||||
result := TokenResult{Valid: false}
|
result := TokenResult{IsValid: false, IsExpired: false}
|
||||||
|
|
||||||
token, _ := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
|
token, _ := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||||
return []byte(config.App.JWT.Secret), nil
|
return []byte(config.App.JWT.Secret), nil
|
||||||
@@ -73,9 +73,9 @@ func VerifyToken(tokenString string) TokenResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
result.Claims = claims
|
result.Claims = claims
|
||||||
result.Valid = true
|
result.IsValid = true
|
||||||
if claims.ExpiresAt != nil && time.Now().After(claims.ExpiresAt.Time) {
|
if claims.ExpiresAt != nil && time.Now().After(claims.ExpiresAt.Time) {
|
||||||
result.Expired = true
|
result.IsExpired = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -36,25 +36,25 @@ func NotEmpty(data map[string]interface{}, fields ...string) error {
|
|||||||
// collectErrors 递归收集字段错误
|
// collectErrors 递归收集字段错误
|
||||||
func collectErrors(v interface{}, path string, errs map[string]string) {
|
func collectErrors(v interface{}, path string, errs map[string]string) {
|
||||||
if v == nil {
|
if v == nil {
|
||||||
errs[path] = fmt.Sprintf("字段 %s 不能为空", path)
|
errs[path] = "字段不能为空"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch val := v.(type) {
|
switch val := v.(type) {
|
||||||
case string:
|
case string:
|
||||||
if strings.TrimSpace(val) == "" {
|
if strings.TrimSpace(val) == "" {
|
||||||
errs[path] = fmt.Sprintf("字段 %s 不能为空", path)
|
errs[path] = "字段不能为空"
|
||||||
}
|
}
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
if len(val) == 0 {
|
if len(val) == 0 {
|
||||||
errs[path] = fmt.Sprintf("字段 %s 不能为空", path)
|
errs[path] = "字段不能为空"
|
||||||
}
|
}
|
||||||
for i, elem := range val {
|
for i, elem := range val {
|
||||||
collectErrors(elem, fmt.Sprintf("%s[%d]", path, i), errs)
|
collectErrors(elem, fmt.Sprintf("%s[%d]", path, i), errs)
|
||||||
}
|
}
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
if len(val) == 0 {
|
if len(val) == 0 {
|
||||||
errs[path] = fmt.Sprintf("字段 %s 不能为空", path)
|
errs[path] = "字段不能为空"
|
||||||
}
|
}
|
||||||
for k, elem := range val {
|
for k, elem := range val {
|
||||||
collectErrors(elem, fmt.Sprintf("%s.%s", path, k), errs)
|
collectErrors(elem, fmt.Sprintf("%s.%s", path, k), errs)
|
||||||
|
|||||||
Whitespace-only changes.
@@ -0,0 +1,69 @@
|
|||||||
|
{"level":"INFO","ts":"2025-11-06 13:11:55.116","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.1.136","elapsed":0.0005349,"trackId":"376961877109899264"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 13:14:17.628","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.1.136","elapsed":0.0005502,"trackId":"376962474844356608"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 13:15:48.248","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.1.136","elapsed":0.0009768,"trackId":"376962854932185088"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 13:19:48.605","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.1.136","elapsed":0.0005207,"trackId":"376963863062511616"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 13:21:27.160","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.1.136","elapsed":0.0005053,"trackId":"376964276432142336"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 13:22:45.690","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376964605815029760"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 13:24:24.423","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376965019931246592"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 13:24:44.251","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376965103095906304"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 13:26:19.417","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.1.136","elapsed":0.0006208,"trackId":"376965502251040768"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 13:31:17.803","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376966753772634112"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 13:31:49.685","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376966887495434240"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 13:31:51.226","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376966893958856704"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 13:31:52.059","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376966897448517632"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 13:38:42.256","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0.0008604,"trackId":"376968617939435520"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 13:49:29.911","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0.0006377,"trackId":"376971334405586944"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:03:51.212","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0.0009761,"trackId":"376974946959622144"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:06:06.794","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376975515635941376"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:18:02.722","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376978518455615488"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:18:38.073","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0.0008201,"trackId":"376978666724261888"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:19:30.070","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0.0002887,"trackId":"376978884819681280"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:20:01.974","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0.0008341,"trackId":"376979018630561792"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:20:12.293","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376979061915779072"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:22:02.141","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0.0009975,"trackId":"376979522647490560"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:22:19.654","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0.0009764,"trackId":"376979596102336512"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:22:58.685","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376979759810215936"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:22:59.290","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376979762351964160"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:22:59.524","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376979763333431296"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:22:59.723","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376979764168097792"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:22:59.923","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376979765006958592"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:00.125","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0.0001014,"trackId":"376979765854208000"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:08.297","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376979800130060288"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:09.077","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0.0010337,"trackId":"376979803397423104"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:12.741","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376979818765352960"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:13.561","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376979822208876544"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:14.337","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376979825463656448"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:15.086","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376979828605190144"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:15.826","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376979831708975104"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:16.041","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376979832610750464"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:16.207","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376979833307004928"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:16.410","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376979834158448640"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:16.624","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0.0009752,"trackId":"376979835051835392"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:16.824","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376979835894890496"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:17.023","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376979836729556992"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:17.222","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0.0004907,"trackId":"376979837564223488"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:17.443","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0.0002058,"trackId":"376979838491164672"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:17.622","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0.0009347,"trackId":"376979839241945088"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:17.841","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376979840160497664"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:18.041","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0.0002851,"trackId":"376979840999358464"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:18.259","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376979841913716736"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:18.457","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0.000401,"trackId":"376979842739994624"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:18.673","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376979843650158592"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:18.891","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376979844564516864"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:19.110","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0.0006448,"trackId":"376979845478875136"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:19.309","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376979846317735936"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:19.525","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376979847223705600"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:19.740","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0.0007353,"trackId":"376979848121286656"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:19.939","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376979848960147456"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:20.140","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0.000381,"trackId":"376979849803202560"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:20.376","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0.0005992,"trackId":"376979850793058304"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:20.591","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376979851694833664"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:20.773","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0.0005447,"trackId":"376979852454002688"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:23:21.190","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0.0003462,"trackId":"376979854207221760"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:25:18.653","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376980346882752512"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:25:20.423","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376980354306670592"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:25:33.356","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0.000976,"trackId":"376980408547409920"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:25:48.802","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376980473336823808"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:25:49.654","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0.0009762,"trackId":"376980476906176512"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 14:26:01.687","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376980527380430848"}
|
||||||
|
{"level":"INFO","ts":"2025-11-06 16:01:53.899","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/test","method":"POST","ip":"192.168.1.136","elapsed":0.0009752,"trackId":"377004653902036992"}
|
||||||
Whitespace-only changes.
Reference in new issue
Block a user