From 1d6bb2b7f5c03dca4122f2d6fd0a235f24ecfdf8 Mon Sep 17 00:00:00 2001 From: ZhangAo Date: Sat, 1 Nov 2025 17:29:25 +0800 Subject: [PATCH] u --- code/app/base-app/package.json | 3 +- code/app/base-app/pnpm-lock.yaml | 219 +++++++++++++---- code/app/base-app/src/app/(tabs)/_layout.tsx | 80 ++----- code/app/base-app/src/app/_layout.tsx | 6 +- .../base-app/src/components/CustomTabBar.tsx | 67 ++++++ code/base-project/base-go-v2/cmd/app/main.go | 24 +- code/base-project/base-go-v2/configs/app.yaml | 3 +- code/base-project/base-go-v2/go.mod | 2 +- .../base-go-v2/internal/api/auth/index.go | 5 + .../internal/api/middleware/cors.go | 24 ++ .../internal/api/middleware/error.go | 29 +++ .../internal/api/middleware/logger.go | 43 ++++ .../internal/api/middleware/recovery.go | 31 +++ .../base-go-v2/internal/api/router.go | 23 ++ .../base-go-v2/internal/logx/field.go | 2 + .../base-go-v2/internal/reponse/response.go | 107 +++++++++ .../base-go-v2/internal/router/context.go | 123 ++++++++++ .../base-go-v2/internal/router/router.go | 222 ++++++++++++++++++ .../base-go-v2/internal/router/types.go | 12 + .../base-go-v2/internal/types/appConfig.go | 3 +- .../logs/error/error-2025-11-01.log | 1 + .../base-go-v2/logs/info/info-2025-11-01.log | 9 + .../base-go-v2/logs/warn/warn-2025-11-01.log | 0 23 files changed, 922 insertions(+), 116 deletions(-) create mode 100644 code/app/base-app/src/components/CustomTabBar.tsx create mode 100644 code/base-project/base-go-v2/internal/api/auth/index.go create mode 100644 code/base-project/base-go-v2/internal/api/middleware/cors.go create mode 100644 code/base-project/base-go-v2/internal/api/middleware/error.go create mode 100644 code/base-project/base-go-v2/internal/api/middleware/logger.go create mode 100644 code/base-project/base-go-v2/internal/api/middleware/recovery.go create mode 100644 code/base-project/base-go-v2/internal/api/router.go create mode 100644 code/base-project/base-go-v2/internal/reponse/response.go create mode 100644 code/base-project/base-go-v2/internal/router/context.go create mode 100644 code/base-project/base-go-v2/internal/router/router.go create mode 100644 code/base-project/base-go-v2/internal/router/types.go create mode 100644 code/base-project/base-go-v2/logs/error/error-2025-11-01.log create mode 100644 code/base-project/base-go-v2/logs/info/info-2025-11-01.log create mode 100644 code/base-project/base-go-v2/logs/warn/warn-2025-11-01.log diff --git a/code/app/base-app/package.json b/code/app/base-app/package.json index fd085342..74d86f3f 100644 --- a/code/app/base-app/package.json +++ b/code/app/base-app/package.json @@ -24,10 +24,11 @@ "expo-splash-screen": "~31.0.10", "expo-status-bar": "~3.0.8", "lucide-react-native": "^0.548.0", + "moti": "^0.30.0", "nativewind": "^4.2.1", "react": "19.1.0", "react-native": "0.81.5", - "react-native-curved-bottom-bar": "^3.5.1", + "react-native-gesture-handler": "~2.28.0", "react-native-reanimated": "~3.17.5", "react-native-safe-area-context": "5.4.0", "react-native-screens": "~4.16.0", diff --git a/code/app/base-app/pnpm-lock.yaml b/code/app/base-app/pnpm-lock.yaml index 7c282d66..5d378711 100644 --- a/code/app/base-app/pnpm-lock.yaml +++ b/code/app/base-app/pnpm-lock.yaml @@ -40,7 +40,7 @@ importers: version: 8.0.8(expo@54.0.20)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) expo-router: specifier: ~6.0.13 - version: 6.0.13(@expo/metro-runtime@6.1.2)(@types/react@19.1.17)(expo-constants@18.0.10)(expo-linking@8.0.8)(expo@54.0.20)(react-dom@19.2.0(react@19.1.0))(react-native-reanimated@3.17.5(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.4.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.2(react-dom@19.2.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 6.0.13(@expo/metro-runtime@6.1.2)(@types/react@19.1.17)(expo-constants@18.0.10)(expo-linking@8.0.8)(expo@54.0.20)(react-dom@19.2.0(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-reanimated@3.17.5(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.4.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.2(react-dom@19.2.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) expo-splash-screen: specifier: ~31.0.10 version: 31.0.10(expo@54.0.20) @@ -50,6 +50,9 @@ importers: lucide-react-native: specifier: ^0.548.0 version: 0.548.0(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + moti: + specifier: ^0.30.0 + version: 0.30.0(react-dom@19.2.0(react@19.1.0))(react-native-reanimated@3.17.5(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react@19.1.0) nativewind: specifier: ^4.2.1 version: 4.2.1(react-native-reanimated@3.17.5(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.4.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(tailwindcss@3.4.17) @@ -59,9 +62,9 @@ importers: react-native: specifier: 0.81.5 version: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0) - react-native-curved-bottom-bar: - specifier: ^3.5.1 - version: 3.5.1(@react-navigation/bottom-tabs@7.5.0(@react-navigation/native@7.1.18(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.4.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + react-native-gesture-handler: + specifier: ~2.28.0 + version: 2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native-reanimated: specifier: ~3.17.5 version: 3.17.5(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) @@ -678,6 +681,16 @@ packages: '@clack/prompts@0.11.0': resolution: {integrity: sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==} + '@egjs/hammerjs@2.0.17': + resolution: {integrity: sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==} + engines: {node: '>=0.8.0'} + + '@emotion/is-prop-valid@0.8.8': + resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==} + + '@emotion/memoize@0.7.4': + resolution: {integrity: sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==} + '@es-joy/jsdoccomment@0.50.2': resolution: {integrity: sha512-YAdE/IJSpwbOTiaURNCKECdAwqrJuFiZhylmesBcIRawtYKnBR2wxPhoIewMg+Yu+QuYvHfJNReWpoxGBKOChA==} engines: {node: '>=18'} @@ -1009,6 +1022,24 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@motionone/animation@10.18.0': + resolution: {integrity: sha512-9z2p5GFGCm0gBsZbi8rVMOAJCtw1WqBTIPw3ozk06gDvZInBPIsQcHgYogEJ4yuHJ+akuW8g1SEIOpTOvYs8hw==} + + '@motionone/dom@10.12.0': + resolution: {integrity: sha512-UdPTtLMAktHiqV0atOczNYyDd/d8Cf5fFsd1tua03PqTwwCe/6lwhLSQ8a7TbnQ5SN0gm44N1slBfj+ORIhrqw==} + + '@motionone/easing@10.18.0': + resolution: {integrity: sha512-VcjByo7XpdLS4o9T8t99JtgxkdMcNWD3yHU/n6CLEz3bkmKDRZyYQ/wmSf6daum8ZXqfUAgFeCZSpJZIMxaCzg==} + + '@motionone/generators@10.18.0': + resolution: {integrity: sha512-+qfkC2DtkDj4tHPu+AFKVfR/C30O1vYdvsGYaR13W/1cczPrrcjdvYCj0VLFuRMN+lP1xvpNZHCRNM4fBzn1jg==} + + '@motionone/types@10.17.1': + resolution: {integrity: sha512-KaC4kgiODDz8hswCrS0btrVrzyU2CSQKO7Ps90ibBVSQmjkrt2teqta6/sOG59v7+dPnKMAg13jyqtMKV2yJ7A==} + + '@motionone/utils@10.18.0': + resolution: {integrity: sha512-3XVF7sgyTSI2KWvTf6uLlBJ5iAgRgmvp3bpuOiQJvInd4nZ19ET8lX5unn30SlmRH7hXbBbH+Gxd0m0klJ3Xtw==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1955,6 +1986,9 @@ packages: '@types/graceful-fs@4.1.9': resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + '@types/hammerjs@2.0.46': + resolution: {integrity: sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==} + '@types/istanbul-lib-coverage@2.0.6': resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} @@ -2522,14 +2556,6 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - d3-path@3.1.0: - resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} - engines: {node: '>=12'} - - d3-shape@3.2.0: - resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} - engines: {node: '>=12'} - debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -3138,6 +3164,15 @@ packages: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} engines: {node: '>=0.4.x'} + framer-motion@6.5.1: + resolution: {integrity: sha512-o1BGqqposwi7cgDrtg0dNONhkmPsUFDaLcKXigzuTFC5x58mE8iyTazxSudFzmT6MEyJKfjjU8ItoMe3W+3fiw==} + peerDependencies: + react: '>=16.8 || ^17.0.0 || ^18.0.0' + react-dom: '>=16.8 || ^17.0.0 || ^18.0.0' + + framesync@6.0.1: + resolution: {integrity: sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA==} + freeport-async@2.0.0: resolution: {integrity: sha512-K7od3Uw45AJg00XUmy15+Hae2hOcgKcmN3/EF6Y7i01O0gaqiRx8sUSpsb9+BRNL8RPBrhzPsVfy8q9ADlJuWQ==} engines: {node: '>=8'} @@ -3252,6 +3287,12 @@ packages: hermes-parser@0.32.0: resolution: {integrity: sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==} + hey-listen@1.0.8: + resolution: {integrity: sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==} + + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + hosted-git-info@7.0.2: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} @@ -3556,48 +3597,56 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-gnu@1.30.2: resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.27.0: resolution: {integrity: sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.27.0: resolution: {integrity: sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.27.0: resolution: {integrity: sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.27.0: resolution: {integrity: sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==} @@ -4008,6 +4057,11 @@ packages: mlly@1.8.0: resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + moti@0.30.0: + resolution: {integrity: sha512-YN78mcefo8kvJaL+TZNyusq6YA2aMFvBPl8WiLPy4eb4wqgOFggJOjP9bUr2YO8PrAt0uusmRG8K4RPL4OhCsA==} + peerDependencies: + react-native-reanimated: '*' + ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -4247,6 +4301,9 @@ packages: pnpm-workspace-yaml@1.3.0: resolution: {integrity: sha512-Krb5q8Totd5mVuLx7we+EFHq/AfxA75nbfTm25Q1pIf606+RlaKUG+PXH8SDihfe5b5k4H09gE+sL47L1t5lbw==} + popmotion@11.0.3: + resolution: {integrity: sha512-Y55FLdj3UxkR7Vl3s7Qr4e9m0onSnP8W7d/xQLsoJM40vs6UKHFdygs6SWryasTZYqugMjm3BepCF4CWXDiHgA==} + postcss-import@15.1.0: resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} engines: {node: '>=14.0.0'} @@ -4374,6 +4431,9 @@ packages: peerDependencies: react: '>=17.0.0' + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} @@ -4396,14 +4456,11 @@ packages: react-native-svg: optional: true - react-native-curved-bottom-bar@3.5.1: - resolution: {integrity: sha512-WG7ZTs+JYXCmGrnXzsYB12DD/UvVdSC4pgem7X5BxXR/bnSgPEHMc0lmGhuRoPS7WgH75w8kncUJFDcwlA75fQ==} - engines: {node: '>= 16.0.0'} + react-native-gesture-handler@2.28.0: + resolution: {integrity: sha512-0msfJ1vRxXKVgTgvL+1ZOoYw3/0z1R+Ked0+udoJhyplC2jbVKIJ8Z1bzWdpQRCV3QcQ87Op0zJVE5DhKK2A0A==} peerDependencies: - '@react-navigation/bottom-tabs': '*' react: '*' react-native: '*' - react-native-svg: '*' react-native-is-edge-to-edge@1.1.7: resolution: {integrity: sha512-EH6i7E8epJGIcu7KpfXYXiV2JFIYITtq+rVS8uEb+92naMRBdxhTuS8Wn2Q7j9sqyO0B+Xbaaf9VdipIAmGW4w==} @@ -4436,12 +4493,6 @@ packages: react: '*' react-native: '*' - react-native-size-scaling@0.5.4: - resolution: {integrity: sha512-GtoE7qcw4ftOigtq6hRTV34nosGw9z6WXNq2l06Uj+yFfvrz6teGjD/E22Jo4raegdTIQmPcMU4/nRRhRLZpAA==} - peerDependencies: - react: '*' - react-native: '*' - react-native-svg@15.12.1: resolution: {integrity: sha512-vCuZJDf8a5aNC2dlMovEv4Z0jjEUET53lm/iILFnFewa15b4atjVxU6Wirm6O9y6dEsdjDZVD7Q3QM4T1wlI8g==} peerDependencies: @@ -4817,6 +4868,9 @@ packages: structured-headers@0.4.1: resolution: {integrity: sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==} + style-value-types@5.0.0: + resolution: {integrity: sha512-08yq36Ikn4kx4YU6RD7jWEv27v4V+PUsOGa4n/as8Et3CuODMJQ00ENeAVXAeydX4Z2j1XHZF1K2sX4mGl18fA==} + styleq@0.1.3: resolution: {integrity: sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA==} @@ -5939,6 +5993,18 @@ snapshots: picocolors: 1.1.1 sisteransi: 1.0.5 + '@egjs/hammerjs@2.0.17': + dependencies: + '@types/hammerjs': 2.0.46 + + '@emotion/is-prop-valid@0.8.8': + dependencies: + '@emotion/memoize': 0.7.4 + optional: true + + '@emotion/memoize@0.7.4': + optional: true + '@es-joy/jsdoccomment@0.50.2': dependencies: '@types/estree': 1.0.8 @@ -6106,7 +6172,7 @@ snapshots: wrap-ansi: 7.0.0 ws: 8.18.3 optionalDependencies: - expo-router: 6.0.13(@expo/metro-runtime@6.1.2)(@types/react@19.1.17)(expo-constants@18.0.10)(expo-linking@8.0.8)(expo@54.0.20)(react-dom@19.2.0(react@19.1.0))(react-native-reanimated@3.17.5(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.4.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.2(react-dom@19.2.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo-router: 6.0.13(@expo/metro-runtime@6.1.2)(@types/react@19.1.17)(expo-constants@18.0.10)(expo-linking@8.0.8)(expo@54.0.20)(react-dom@19.2.0(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-reanimated@3.17.5(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.4.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.2(react-dom@19.2.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0) transitivePeerDependencies: - '@modelcontextprotocol/sdk' @@ -6532,6 +6598,41 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@motionone/animation@10.18.0': + dependencies: + '@motionone/easing': 10.18.0 + '@motionone/types': 10.17.1 + '@motionone/utils': 10.18.0 + tslib: 2.8.1 + + '@motionone/dom@10.12.0': + dependencies: + '@motionone/animation': 10.18.0 + '@motionone/generators': 10.18.0 + '@motionone/types': 10.17.1 + '@motionone/utils': 10.18.0 + hey-listen: 1.0.8 + tslib: 2.8.1 + + '@motionone/easing@10.18.0': + dependencies: + '@motionone/utils': 10.18.0 + tslib: 2.8.1 + + '@motionone/generators@10.18.0': + dependencies: + '@motionone/types': 10.17.1 + '@motionone/utils': 10.18.0 + tslib: 2.8.1 + + '@motionone/types@10.17.1': {} + + '@motionone/utils@10.18.0': + dependencies: + '@motionone/types': 10.17.1 + hey-listen: 1.0.8 + tslib: 2.8.1 + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -7948,6 +8049,8 @@ snapshots: dependencies: '@types/node': 24.9.1 + '@types/hammerjs@2.0.46': {} + '@types/istanbul-lib-coverage@2.0.6': {} '@types/istanbul-lib-report@3.0.3': @@ -8608,12 +8711,6 @@ snapshots: csstype@3.1.3: {} - d3-path@3.1.0: {} - - d3-shape@3.2.0: - dependencies: - d3-path: 3.1.0 - debug@2.6.9: dependencies: ms: 2.0.0 @@ -9094,7 +9191,7 @@ snapshots: react: 19.1.0 react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0) - expo-router@6.0.13(@expo/metro-runtime@6.1.2)(@types/react@19.1.17)(expo-constants@18.0.10)(expo-linking@8.0.8)(expo@54.0.20)(react-dom@19.2.0(react@19.1.0))(react-native-reanimated@3.17.5(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.4.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.2(react-dom@19.2.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + expo-router@6.0.13(@expo/metro-runtime@6.1.2)(@types/react@19.1.17)(expo-constants@18.0.10)(expo-linking@8.0.8)(expo@54.0.20)(react-dom@19.2.0(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-reanimated@3.17.5(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.4.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.2(react-dom@19.2.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: '@expo/metro-runtime': 6.1.2(expo@54.0.20)(react-dom@19.2.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) '@expo/schema-utils': 0.1.7 @@ -9128,6 +9225,7 @@ snapshots: vaul: 1.1.2(@types/react@19.1.17)(react-dom@19.2.0(react@19.1.0))(react@19.1.0) optionalDependencies: react-dom: 19.2.0(react@19.1.0) + react-native-gesture-handler: 2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native-reanimated: 3.17.5(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native-web: 0.21.2(react-dom@19.2.0(react@19.1.0))(react@19.1.0) transitivePeerDependencies: @@ -9291,6 +9389,23 @@ snapshots: format@0.2.2: {} + framer-motion@6.5.1(react-dom@19.2.0(react@19.1.0))(react@19.1.0): + dependencies: + '@motionone/dom': 10.12.0 + framesync: 6.0.1 + hey-listen: 1.0.8 + popmotion: 11.0.3 + react: 19.1.0 + react-dom: 19.2.0(react@19.1.0) + style-value-types: 5.0.0 + tslib: 2.8.1 + optionalDependencies: + '@emotion/is-prop-valid': 0.8.8 + + framesync@6.0.1: + dependencies: + tslib: 2.8.1 + freeport-async@2.0.0: {} fresh@0.5.2: {} @@ -9386,6 +9501,12 @@ snapshots: dependencies: hermes-estree: 0.32.0 + hey-listen@1.0.8: {} + + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + hosted-git-info@7.0.2: dependencies: lru-cache: 10.4.3 @@ -10517,6 +10638,14 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.1 + moti@0.30.0(react-dom@19.2.0(react@19.1.0))(react-native-reanimated@3.17.5(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react@19.1.0): + dependencies: + framer-motion: 6.5.1(react-dom@19.2.0(react@19.1.0))(react@19.1.0) + react-native-reanimated: 3.17.5(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + transitivePeerDependencies: + - react + - react-dom + ms@2.0.0: {} ms@2.1.3: {} @@ -10732,6 +10861,13 @@ snapshots: dependencies: yaml: 2.8.1 + popmotion@11.0.3: + dependencies: + framesync: 6.0.1 + hey-listen: 1.0.8 + style-value-types: 5.0.0 + tslib: 2.8.1 + postcss-import@15.1.0(postcss@8.5.6): dependencies: postcss: 8.5.6 @@ -10896,6 +11032,8 @@ snapshots: dependencies: react: 19.1.0 + react-is@16.13.1: {} + react-is@18.3.1: {} react-is@19.2.0: {} @@ -10918,14 +11056,13 @@ snapshots: transitivePeerDependencies: - supports-color - react-native-curved-bottom-bar@3.5.1(@react-navigation/bottom-tabs@7.5.0(@react-navigation/native@7.1.18(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.4.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: - '@react-navigation/bottom-tabs': 7.5.0(@react-navigation/native@7.1.18(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.4.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - d3-shape: 3.2.0 + '@egjs/hammerjs': 2.0.17 + hoist-non-react-statics: 3.3.2 + invariant: 2.2.4 react: 19.1.0 react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0) - react-native-size-scaling: 0.5.4(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react-native-svg: 15.12.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react-native-is-edge-to-edge@1.1.7(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: @@ -10970,11 +11107,6 @@ snapshots: react-native-is-edge-to-edge: 1.2.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) warn-once: 0.1.1 - react-native-size-scaling@0.5.4(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): - dependencies: - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0) - react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: css-select: 5.2.2 @@ -11403,6 +11535,11 @@ snapshots: structured-headers@0.4.1: {} + style-value-types@5.0.0: + dependencies: + hey-listen: 1.0.8 + tslib: 2.8.1 + styleq@0.1.3: {} sucrase@3.35.0: diff --git a/code/app/base-app/src/app/(tabs)/_layout.tsx b/code/app/base-app/src/app/(tabs)/_layout.tsx index 7167ecb7..5c59f244 100644 --- a/code/app/base-app/src/app/(tabs)/_layout.tsx +++ b/code/app/base-app/src/app/(tabs)/_layout.tsx @@ -1,70 +1,22 @@ -import { Tabs } from 'expo-router' -import { CircleDollarSign, CircleUserRound, Heart, Layers, MessageSquare } from 'lucide-react-native' -import React, { useRef } from 'react' -import { Animated, Pressable, View } from 'react-native' - -const tabItems = [ - { name: 'finance', icon: }, - { name: 'dating', icon: }, - { name: 'app-center', icon: }, - { name: 'chat', icon: }, - { name: 'profile', icon: }, -] - -// 自定义 TabBar -function CustomTabBar({ state, descriptors, navigation }: any) { - const scales = useRef(tabItems.map(() => new Animated.Value(1))).current +import { Tabs, TabSlot } from 'expo-router/ui' // ✅ 从 expo-router/ui 导入 +import { CustomTabBar } from '@/components/CustomTabBar' +export default function TabsLayout() { return ( - - {state.routes.map((route: any, index: number) => { - const item = tabItems.find(i => i.name === route.name) - if (!item) - return null + + {/* 渲染当前激活的页面内容 */} + - const isFocused = state.index === index - const color = isFocused ? '#333333' : '#9ca3af' - - const onPressIn = () => - Animated.spring(scales[index], { toValue: 0.8, useNativeDriver: true }).start() - const onPressOut = () => - Animated.spring(scales[index], { toValue: 1, useNativeDriver: true }).start() - - const onPress = () => { - const event = navigation.emit({ - type: 'tabPress', - target: route.key, - canPreventDefault: true, - }) - if (!isFocused && !event.defaultPrevented) { - navigation.navigate(route.name) - } - } - - return ( - - - {React.cloneElement(item.icon, { color })} - - - ) - })} - - ) -} - -export default function TabLayout() { - return ( - }> - {tabItems.map(item => ( - null }} /> - ))} + {/* 您的自定义 TabBar */} + ) } diff --git a/code/app/base-app/src/app/_layout.tsx b/code/app/base-app/src/app/_layout.tsx index 67127a41..3242a551 100644 --- a/code/app/base-app/src/app/_layout.tsx +++ b/code/app/base-app/src/app/_layout.tsx @@ -8,6 +8,8 @@ import { useEffect } from 'react' import { EmptyState } from '@/components/empty-status' import { GluestackUIProvider } from '@/components/ui/gluestack-ui-provider' import '@/styles/global.css' +import 'react-native-reanimated' +import 'react-native-gesture-handler' SplashScreen.preventAutoHideAsync() @@ -25,7 +27,8 @@ export function ErrorBoundary({ error, retry }: ErrorBoundaryProps) { ) } -export default function Layout() { + +export default function RootLayout() { const [loaded, error] = useFonts({ SpaceMono: require('@/static/fonts/SpaceMono-Regular.ttf'), }) @@ -48,6 +51,7 @@ export default function Layout() { name="(tabs)" options={{ headerShown: false }} /> + diff --git a/code/app/base-app/src/components/CustomTabBar.tsx b/code/app/base-app/src/components/CustomTabBar.tsx new file mode 100644 index 00000000..d6430250 --- /dev/null +++ b/code/app/base-app/src/components/CustomTabBar.tsx @@ -0,0 +1,67 @@ +import type { MotiProps } from 'moti' +import { TabList, TabTrigger } from 'expo-router/ui' +import { icons } from 'lucide-react-native' +import { motifySvg } from 'moti/svg' +import { Text, View } from 'react-native' + +type IconNames = keyof typeof icons + +type IconProp = { + name: IconNames + size?: number +} & MotiProps + +interface TabbarItem { + icon: IconNames + name: string + href: string + label: string +} + +interface CustomTabBarProps { + data: TabbarItem[] +} + +function Icon({ name, size = 24, ...rest }: IconProp) { + const IconComponent = motifySvg(icons[name] as any)() + return +} + +export function CustomTabBar({ data }: CustomTabBarProps) { + return ( + + + {data.map(item => ( + + + + {item.label} + + + ))} + + + ) +} diff --git a/code/base-project/base-go-v2/cmd/app/main.go b/code/base-project/base-go-v2/cmd/app/main.go index 43a3412f..fc9edb80 100644 --- a/code/base-project/base-go-v2/cmd/app/main.go +++ b/code/base-project/base-go-v2/cmd/app/main.go @@ -1,25 +1,37 @@ package main import ( + "base-go-v2/internal/api" "base-go-v2/internal/config" "base-go-v2/internal/database" "base-go-v2/internal/logx" + "fmt" + "github.com/valyala/fasthttp" "log" ) func main() { // 初始化配置 - err := config.InitAppConfig("./configs/app.yaml") - if err != nil { + if err := config.InitAppConfig("./configs/app.yaml"); err != nil { log.Fatalf("加载配置失败: %v", err) - return } + // 初始化数据库 - err = database.InitDb() - if err != nil { + if err := database.InitDb(); err != nil { log.Fatalf("加载数据库配置失败: %v", err) - return } + // 初始化日志 logx.InitLogger() + + // 初始化路由 + r := api.InitAppRouter() + addr := fmt.Sprintf(":%d", config.App.Server.Port) + + log.Printf("✅ 服务器启动成功,端口%s", addr) + + // 启动服务器(简洁写法) + if err := fasthttp.ListenAndServe(addr, r.Handler); err != nil { + log.Fatalf("❌ 服务器启动失败: %v", err) + } } diff --git a/code/base-project/base-go-v2/configs/app.yaml b/code/base-project/base-go-v2/configs/app.yaml index eb10e709..af4a7b03 100644 --- a/code/base-project/base-go-v2/configs/app.yaml +++ b/code/base-project/base-go-v2/configs/app.yaml @@ -1,5 +1,6 @@ server: port: 8085 + baseUrl: '/app' snowflake: datacenterID: 0 @@ -23,5 +24,5 @@ log: daily: true # 是否按天滚动 maxDays: 30 # 保留天数,0表示无限 caller: true # 是否记录调用文件行号 - levelDirs: false # 是否按级别目录 + levelDirs: true # 是否按级别目录 console: true # 是否打印到控制台 diff --git a/code/base-project/base-go-v2/go.mod b/code/base-project/base-go-v2/go.mod index 3758b50a..72048674 100644 --- a/code/base-project/base-go-v2/go.mod +++ b/code/base-project/base-go-v2/go.mod @@ -6,6 +6,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.3.0 github.com/lib/pq v1.10.9 github.com/spf13/viper v1.21.0 + go.uber.org/zap v1.27.0 ) require ( @@ -23,7 +24,6 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.68.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/sys v0.37.0 // indirect golang.org/x/text v0.30.0 // indirect diff --git a/code/base-project/base-go-v2/internal/api/auth/index.go b/code/base-project/base-go-v2/internal/api/auth/index.go new file mode 100644 index 00000000..eaf3abd8 --- /dev/null +++ b/code/base-project/base-go-v2/internal/api/auth/index.go @@ -0,0 +1,5 @@ +package auth + +func InitAuthRouter() { + +} diff --git a/code/base-project/base-go-v2/internal/api/middleware/cors.go b/code/base-project/base-go-v2/internal/api/middleware/cors.go new file mode 100644 index 00000000..063f9bc6 --- /dev/null +++ b/code/base-project/base-go-v2/internal/api/middleware/cors.go @@ -0,0 +1,24 @@ +package middleware + +import "base-go-v2/internal/router" + +func CORSMiddleware() router.HandlerFunc { + return func(c *router.Context) error { + origin := string(c.RequestCtx.Request.Header.Peek("Origin")) + if origin == "" { + origin = "*" + } + + c.RequestCtx.Response.Header.Set("Access-Control-Allow-Origin", origin) + c.RequestCtx.Response.Header.Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + c.RequestCtx.Response.Header.Set("Access-Control-Allow-Headers", "Content-Type, Authorization") + c.RequestCtx.Response.Header.Set("Access-Control-Allow-Credentials", "true") + + if string(c.RequestCtx.Method()) == "OPTIONS" { + c.RequestCtx.SetStatusCode(200) + return nil + } + + return c.Next() + } +} diff --git a/code/base-project/base-go-v2/internal/api/middleware/error.go b/code/base-project/base-go-v2/internal/api/middleware/error.go new file mode 100644 index 00000000..90146122 --- /dev/null +++ b/code/base-project/base-go-v2/internal/api/middleware/error.go @@ -0,0 +1,29 @@ +package middleware + +import ( + "base-go-v2/internal/logx" + "base-go-v2/internal/reponse" + "base-go-v2/internal/router" + "runtime/debug" +) + +func ErrorMiddleware() router.HandlerFunc { + return func(c *router.Context) error { + err := c.Next() + + if err != nil { + stack := string(debug.Stack()) + + logx.Logger.Error("请求错误", + logx.String("path", string(c.RequestCtx.Path())), + logx.String("method", string(c.RequestCtx.Method())), + logx.String("error", err.Error()), + logx.String("stack", stack), + ) + + return response.Error(c).Message(err.Error()).Send() + } + + return nil + } +} diff --git a/code/base-project/base-go-v2/internal/api/middleware/logger.go b/code/base-project/base-go-v2/internal/api/middleware/logger.go new file mode 100644 index 00000000..91013ad3 --- /dev/null +++ b/code/base-project/base-go-v2/internal/api/middleware/logger.go @@ -0,0 +1,43 @@ +package middleware + +import ( + "base-go-v2/internal/logx" + "base-go-v2/internal/router" + "base-go-v2/internal/routinex" + "base-go-v2/internal/utils/uid" + "time" +) + +// TrackTime 记录开始时间到现在时间的耗时 +func TrackTime(pre time.Time) time.Duration { + elapsed := time.Since(pre) + + return elapsed +} + +func LoggerMiddleware() router.HandlerFunc { + return func(c *router.Context) error { + start := time.Now() // 记录开始时间 + + // 生成 trackID 并存入 routinex + trackID := uid.NextID() + routinex.Set(logx.TrackID, trackID) + + // 执行业务逻辑,并捕获错误 + err := c.Next() + + // 请求结束后计算耗时 + elapsed := TrackTime(start) + + ip := c.RequestCtx.RemoteIP().String() + + logx.Logger.Info("接口请求", + logx.String("path", string(c.RequestCtx.Path())), + logx.String("method", string(c.RequestCtx.Method())), + logx.String("ip", ip), + logx.Duration("elapsed", elapsed), + ) + + return err + } +} diff --git a/code/base-project/base-go-v2/internal/api/middleware/recovery.go b/code/base-project/base-go-v2/internal/api/middleware/recovery.go new file mode 100644 index 00000000..aa4ff01c --- /dev/null +++ b/code/base-project/base-go-v2/internal/api/middleware/recovery.go @@ -0,0 +1,31 @@ +package middleware + +import ( + "base-go-v2/internal/logx" + response "base-go-v2/internal/reponse" + "base-go-v2/internal/router" + "runtime/debug" +) + +func RecoveryMiddleware() router.HandlerFunc { + return func(c *router.Context) error { + defer func() { + if r := recover(); r != nil { + stack := string(debug.Stack()) // 获取堆栈 + + logx.Logger.Error("请求 panic", + logx.String("path", string(c.RequestCtx.Path())), + logx.String("method", string(c.RequestCtx.Method())), + logx.String("panic", r.(error).Error()), + logx.String("stack", stack), + ) + + // 返回统一 JSON 响应 + _ = response.Error(c).Message("系统 Panic").Send() + } + }() + + // 调用后续 handler + return c.Next() + } +} diff --git a/code/base-project/base-go-v2/internal/api/router.go b/code/base-project/base-go-v2/internal/api/router.go new file mode 100644 index 00000000..964f33ba --- /dev/null +++ b/code/base-project/base-go-v2/internal/api/router.go @@ -0,0 +1,23 @@ +package api + +import ( + "base-go-v2/internal/api/middleware" + "base-go-v2/internal/config" + response "base-go-v2/internal/reponse" + "base-go-v2/internal/router" +) + +func InitAppRouter() *router.Router { + r := router.NewRouter(config.App.Server.BaseUrl) + + r.Use(middleware.RecoveryMiddleware()) + r.Use(middleware.CORSMiddleware()) + r.Use(middleware.LoggerMiddleware()) + r.Use(middleware.ErrorMiddleware()) + + r.GET("/test", func(c *router.Context) error { + return response.Success(c).Send() + }) + + return r +} diff --git a/code/base-project/base-go-v2/internal/logx/field.go b/code/base-project/base-go-v2/internal/logx/field.go index f4000214..b4b3a69d 100644 --- a/code/base-project/base-go-v2/internal/logx/field.go +++ b/code/base-project/base-go-v2/internal/logx/field.go @@ -6,6 +6,8 @@ import ( "go.uber.org/zap" ) +type Field = zap.Field + func String(key, val string) zap.Field { return zap.String(key, val) } func Stringp(key string, val *string) zap.Field { return zap.Stringp(key, val) } func Strings(key string, val []string) zap.Field { return zap.Strings(key, val) } diff --git a/code/base-project/base-go-v2/internal/reponse/response.go b/code/base-project/base-go-v2/internal/reponse/response.go new file mode 100644 index 00000000..e2f5fbc8 --- /dev/null +++ b/code/base-project/base-go-v2/internal/reponse/response.go @@ -0,0 +1,107 @@ +package response + +import ( + "base-go-v2/internal/logx" + "base-go-v2/internal/router" + "base-go-v2/internal/routinex" + "base-go-v2/internal/utils/strutil" + "net/http" +) + +type Result struct { + Code string `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data,omitempty"` + TrackId string `json:"trackId"` +} + +type Code struct { + Value string + Message string +} + +var ( + CodeSuccess = Code{"0000", "请求成功"} + CodeFail = Code{"0001", "请求失败"} + CodeNoLogin = Code{"1001", "未登录"} + CodeLoginExpired = Code{"1002", "登录已过期"} + CodeInvalidToken = Code{"1003", "Token 无效"} + CodeInvalidAccount = Code{"1005", "账号或密码错误"} +) + +const defaultMessage = "未知错误" + +type Builder struct { + c *router.Context + result Result +} + +func Success(c *router.Context) *Builder { + return &Builder{ + c: c, + result: Result{ + Code: CodeSuccess.Value, + Message: CodeSuccess.Message, + }, + } +} + +func Error(c *router.Context) *Builder { + return &Builder{ + c: c, + result: Result{ + Code: CodeFail.Value, + Message: CodeFail.Message, + }, + } +} + +func (b *Builder) Code(code string) *Builder { + b.result.Code = code + b.result.Message = GetMessage(code) + return b +} + +func (b *Builder) CodeObj(code Code) *Builder { + b.result.Code = code.Value + b.result.Message = code.Message + return b +} + +func (b *Builder) Message(msg string) *Builder { + b.result.Message = msg + return b +} + +func (b *Builder) Data(data interface{}) *Builder { + b.result.Data = data + return b +} + +func (b *Builder) Send() error { + // 默认 Data 为空 + if b.result.Data == nil { + b.result.Data = "" + } + + // 从 routinex 获取 trackId + trackId := routinex.Get(logx.TrackID) + if trackId != nil { + b.result.TrackId = strutil.ToString(trackId) + } + + // 发送响应 + return b.c.JSON(http.StatusOK, b.result) +} + +func GetMessage(value string) string { + for _, c := range []Code{ + CodeSuccess, CodeFail, CodeNoLogin, + CodeLoginExpired, CodeInvalidToken, CodeInvalidAccount, + } { + if c.Value == value { + return c.Message + } + } + return defaultMessage +} diff --git a/code/base-project/base-go-v2/internal/router/context.go b/code/base-project/base-go-v2/internal/router/context.go new file mode 100644 index 00000000..89159d66 --- /dev/null +++ b/code/base-project/base-go-v2/internal/router/context.go @@ -0,0 +1,123 @@ +package router + +import ( + "encoding/json" + "sync" + + "github.com/valyala/fasthttp" +) + +// BodyCache 缓存 fasthttp 请求体 +type BodyCache struct { + once sync.Once + Data []byte + Err error +} + +// Load 读取并缓存请求体 +func (b *BodyCache) Load(ctx *fasthttp.RequestCtx) ([]byte, error) { + b.once.Do(func() { + b.Data = ctx.PostBody() + }) + return b.Data, b.Err +} + +// Context fasthttp 上下文封装 +type Context struct { + RequestCtx *fasthttp.RequestCtx + params map[string]string + index int + handlers []HandlerFunc + + bodyCache BodyCache + keys map[string]interface{} + aborted bool +} + +// Next 执行下一个中间件 +func (c *Context) Next() error { + c.index++ + if c.index < len(c.handlers) { + return c.handlers[c.index](c) + } + return nil +} + +// Abort 中止执行 +func (c *Context) Abort() { + c.aborted = true +} + +// Param 获取路由参数 +func (c *Context) Param(key string) string { + return c.params[key] +} + +// Header 获取请求头 +func (c *Context) Header(key string) string { + return string(c.RequestCtx.Request.Header.Peek(key)) +} + +// Body 获取请求体 +func (c *Context) Body() ([]byte, error) { + return c.bodyCache.Load(c.RequestCtx) +} + +// BindJSON 反序列化 JSON 请求体 +func (c *Context) BindJSON(obj interface{}) error { + body, err := c.Body() + if err != nil { + return err + } + return json.Unmarshal(body, obj) +} + +// PostForm 获取表单参数 +func (c *Context) PostForm(key string) string { + return string(c.RequestCtx.FormValue(key)) +} + +// Set 存储键值 +func (c *Context) Set(key string, value interface{}) { + if c.keys == nil { + c.keys = make(map[string]interface{}) + } + c.keys[key] = value +} + +// Get 获取键值 +func (c *Context) Get(key string) (interface{}, bool) { + if c.keys == nil { + return nil, false + } + val, ok := c.keys[key] + return val, ok +} + +// JSON 返回 JSON 响应,支持返回 error +func (c *Context) JSON(statusCode int, data interface{}) error { + c.RequestCtx.SetStatusCode(statusCode) + c.RequestCtx.SetContentType("application/json; charset=utf-8") + + if err := json.NewEncoder(c.RequestCtx).Encode(data); err != nil { + c.Abort() + return err + } + + c.Abort() + return nil +} + +// Text 返回纯文本,支持返回 error +func (c *Context) Text(statusCode int, msg string) error { + c.RequestCtx.SetStatusCode(statusCode) + c.RequestCtx.SetContentType("text/plain; charset=utf-8") + + if _, err := c.RequestCtx.WriteString(msg); err != nil { + c.Abort() + return err + } + + c.Abort() + return nil +} diff --git a/code/base-project/base-go-v2/internal/router/router.go b/code/base-project/base-go-v2/internal/router/router.go new file mode 100644 index 00000000..edaba7a7 --- /dev/null +++ b/code/base-project/base-go-v2/internal/router/router.go @@ -0,0 +1,222 @@ +package router + +import ( + "strings" + + "github.com/valyala/fasthttp" +) + +func (n *node) matchChild(part string) *node { + for _, child := range n.children { + if child.part == part || child.isParam { + return child + } + } + return nil +} + +func (n *node) matchChildren(part string) []*node { + nodes := make([]*node, 0) + for _, child := range n.children { + if child.part == part || child.isParam { + nodes = append(nodes, child) + } + } + return nodes +} + +func (n *node) insert(pattern string, parts []string, height int, handler HandlerFunc) { + if len(parts) == height { + n.pattern = pattern + n.handler = handler + return + } + part := parts[height] + child := n.matchChild(part) + if child == nil { + child = &node{ + part: part, + isParam: len(part) > 0 && part[0] == ':', + } + n.children = append(n.children, child) + } + child.insert(pattern, parts, height+1, handler) +} + +func (n *node) search(parts []string, height int, params map[string]string) *node { + if len(parts) == height || n.part == "*" { + if n.pattern == "" { + return nil + } + return n + } + part := parts[height] + children := n.matchChildren(part) + + for _, child := range children { + if child.isParam { + params[child.part[1:]] = part + } + res := child.search(parts, height+1, params) + if res != nil { + return res + } + } + return nil +} + +// 中间件条目 +type middlewareEntry struct { + handler HandlerFunc + excludePaths map[string]struct{} +} + +// Router 主结构 +type Router struct { + roots map[string]*node + middleware []middlewareEntry + basePath string +} + +// NewRouter 创建 +func NewRouter(basePath ...string) *Router { + path := "/" + if len(basePath) > 0 && basePath[0] != "" { + path = basePath[0] + } + return &Router{ + roots: make(map[string]*node), + basePath: path, + } +} + +type MiddlewareHandle struct { + router *Router + entryIdx int +} + +func (r *Router) Use(handler HandlerFunc) *MiddlewareHandle { + entry := middlewareEntry{handler: handler} + r.middleware = append(r.middleware, entry) + return &MiddlewareHandle{ + router: r, + entryIdx: len(r.middleware) - 1, + } +} + +func (mh *MiddlewareHandle) ExcludePaths(paths ...string) *Router { + excludeMap := make(map[string]struct{}, len(paths)) + for _, p := range paths { + excludeMap[p] = struct{}{} + } + mh.router.middleware[mh.entryIdx].excludePaths = excludeMap + return mh.router +} + +func (r *Router) Group(prefix string, m ...HandlerFunc) *Router { + newMiddleware := make([]middlewareEntry, len(r.middleware)) + copy(newMiddleware, r.middleware) + for _, handler := range m { + entry := middlewareEntry{handler: handler} + newMiddleware = append(newMiddleware, entry) + } + return &Router{ + roots: r.roots, + middleware: newMiddleware, + basePath: joinPaths(r.basePath, prefix), + } +} + +func (r *Router) GET(path string, handler HandlerFunc) *Router { + return r.handle("GET", path, handler) +} + +func (r *Router) POST(path string, handler HandlerFunc) *Router { + return r.handle("POST", path, handler) +} + +func (r *Router) handle(method, path string, handler HandlerFunc) *Router { + fullPath := joinPaths(r.basePath, path) + parts := parsePattern(fullPath) + if r.roots[method] == nil { + r.roots[method] = &node{} + } + r.roots[method].insert(fullPath, parts, 0, handler) + return r +} + +// Handler 入口函数(fasthttp) +func (r *Router) Handler(ctx *fasthttp.RequestCtx) { + method := string(ctx.Method()) + root := r.roots[method] + if root == nil { + ctx.Error("404 Not Found", fasthttp.StatusNotFound) + return + } + + searchParts := parsePattern(string(ctx.Path())) + params := make(map[string]string) + n := root.search(searchParts, 0, params) + if n == nil || n.handler == nil { + ctx.Error("404 Not Found", fasthttp.StatusNotFound) + return + } + + // 组装中间件链 + handlers := make([]HandlerFunc, 0) + path := string(ctx.Path()) + for _, m := range r.middleware { + if m.excludePaths != nil { + if _, excluded := m.excludePaths[path]; excluded { + continue + } + } + handlers = append(handlers, m.handler) + } + handlers = append(handlers, n.handler) // 最终 handler + + c := &Context{ + RequestCtx: ctx, + params: params, + index: -1, + handlers: handlers, + } + + _ = c.Next() +} + +func parsePattern(pattern string) []string { + vs := strings.Split(strings.Trim(pattern, "/"), "/") + parts := make([]string, 0, len(vs)) + for _, item := range vs { + if item != "" { + parts = append(parts, item) + } + } + return parts +} + +func joinPaths(a, b string) string { + if a == "" { + if b == "" { + return "/" + } + if !strings.HasPrefix(b, "/") { + return "/" + b + } + return b + } + if b == "" { + return a + } + aslash := strings.HasSuffix(a, "/") + bslash := strings.HasPrefix(b, "/") + switch { + case aslash && bslash: + return a + b[1:] + case !aslash && !bslash: + return a + "/" + b + default: + return a + b + } +} diff --git a/code/base-project/base-go-v2/internal/router/types.go b/code/base-project/base-go-v2/internal/router/types.go new file mode 100644 index 00000000..59ada547 --- /dev/null +++ b/code/base-project/base-go-v2/internal/router/types.go @@ -0,0 +1,12 @@ +package router + +type HandlerFunc func(c *Context) error + +// node 路由树节点 +type node struct { + pattern string + part string + children []*node + isParam bool + handler HandlerFunc +} diff --git a/code/base-project/base-go-v2/internal/types/appConfig.go b/code/base-project/base-go-v2/internal/types/appConfig.go index 89e6f171..00ded23a 100644 --- a/code/base-project/base-go-v2/internal/types/appConfig.go +++ b/code/base-project/base-go-v2/internal/types/appConfig.go @@ -12,7 +12,8 @@ type AppConfig struct { } type ServerConfig struct { - Port int + Port int + BaseUrl string } type JWTConfig struct { diff --git a/code/base-project/base-go-v2/logs/error/error-2025-11-01.log b/code/base-project/base-go-v2/logs/error/error-2025-11-01.log new file mode 100644 index 00000000..bf2f4101 --- /dev/null +++ b/code/base-project/base-go-v2/logs/error/error-2025-11-01.log @@ -0,0 +1 @@ +{"level":"ERROR","ts":"2025-11-01 13:46:05.593","caller":"middleware/recovery.go:16","msg":"请求 panic","path":"/app/test","method":"GET","panic":"interface conversion: interface {} is uint64, not string","stack":"goroutine 35 [running]:\nruntime/debug.Stack()\n\tD:/devtool/golang/go1.25.0/src/runtime/debug/stack.go:26 +0x5e\nbase-go-v2/internal/api.InitAppRouter.RecoveryMiddleware.func2.1()\n\tD:/workspace/code/base-project/base-go-v2/internal/api/middleware/recovery.go:14 +0x4b\npanic({0x7ff7c009b840?, 0xc000094240?})\n\tD:/devtool/golang/go1.25.0/src/runtime/panic.go:783 +0x132\nbase-go-v2/internal/reponse.(*Builder).Send(0xc0001b7640)\n\tD:/workspace/code/base-project/base-go-v2/internal/reponse/response.go:89 +0xeb\nbase-go-v2/internal/api.InitAppRouter.func1(0xa0?)\n\tD:/workspace/code/base-project/base-go-v2/internal/api/router.go:19 +0x6b\nbase-go-v2/internal/router.(*Context).Next(0x7ff7bfd30a85?)\n\tD:/workspace/code/base-project/base-go-v2/internal/router/context.go:41 +0x35\nbase-go-v2/internal/api.InitAppRouter.ErrorMiddleware.func5(0xc0000a4080)\n\tD:/workspace/code/base-project/base-go-v2/internal/api/middleware/error.go:12 +0x2a\nbase-go-v2/internal/router.(*Context).Next(0x7ff7c010426d?)\n\tD:/workspace/code/base-project/base-go-v2/internal/router/context.go:41 +0x35\nbase-go-v2/internal/api.InitAppRouter.LoggerMiddleware.func4(0xc0000a4080)\n\tD:/workspace/code/base-project/base-go-v2/internal/api/middleware/logger.go:27 +0x77\nbase-go-v2/internal/router.(*Context).Next(0xc0000a2660?)\n\tD:/workspace/code/base-project/base-go-v2/internal/router/context.go:41 +0x35\nbase-go-v2/internal/api.InitAppRouter.CORSMiddleware.func3(0xc0000a4080)\n\tD:/workspace/code/base-project/base-go-v2/internal/api/middleware/cors.go:22 +0x159\nbase-go-v2/internal/router.(*Context).Next(0xc0001840a0?)\n\tD:/workspace/code/base-project/base-go-v2/internal/router/context.go:41 +0x35\nbase-go-v2/internal/api.InitAppRouter.RecoveryMiddleware.func2(0xc0000900a0?)\n\tD:/workspace/code/base-project/base-go-v2/internal/api/middleware/recovery.go:29 +0x45\nbase-go-v2/internal/router.(*Context).Next(...)\n\tD:/workspace/code/base-project/base-go-v2/internal/router/context.go:41\nbase-go-v2/internal/router.(*Router).Handler(0xc0001a81e0, 0xc0000a2608)\n\tD:/workspace/code/base-project/base-go-v2/internal/router/router.go:185 +0x402\ngithub.com/valyala/fasthttp.(*Server).serveConn(0xc0001d0008, {0x7ff7c01a5358, 0xc000096008})\n\tD:/devtool/golang/gopath/pkg/mod/github.com/valyala/fasthttp@v1.68.0/server.go:2465 +0x124b\ngithub.com/valyala/fasthttp.(*workerPool).workerFunc(0xc0001d2000, 0xc000090040)\n\tD:/devtool/golang/gopath/pkg/mod/github.com/valyala/fasthttp@v1.68.0/workerpool.go:225 +0x92\ngithub.com/valyala/fasthttp.(*workerPool).getCh.func1()\n\tD:/devtool/golang/gopath/pkg/mod/github.com/valyala/fasthttp@v1.68.0/workerpool.go:197 +0x32\ncreated by github.com/valyala/fasthttp.(*workerPool).getCh in goroutine 1\n\tD:/devtool/golang/gopath/pkg/mod/github.com/valyala/fasthttp@v1.68.0/workerpool.go:196 +0x194\n","trackId":"375158538101587968"} diff --git a/code/base-project/base-go-v2/logs/info/info-2025-11-01.log b/code/base-project/base-go-v2/logs/info/info-2025-11-01.log new file mode 100644 index 00000000..7aa85053 --- /dev/null +++ b/code/base-project/base-go-v2/logs/info/info-2025-11-01.log @@ -0,0 +1,9 @@ +{"level":"INFO","ts":"2025-11-01 11:02:11.221","caller":"middleware/logger.go:31","msg":"请求结束","logType":"request","trackID":375117289755770880,"path":"/ping","method":"GET","elapsed":0.0009988,"trackId":"375117289755770880"} +{"level":"INFO","ts":"2025-11-01 11:03:01.633","caller":"middleware/logger.go:31","msg":"请求结束","logType":"request","path":"/ping","method":"GET","elapsed":0.0008635,"trackId":"375117501199024128"} +{"level":"INFO","ts":"2025-11-01 11:06:32.290","caller":"middleware/logger.go:34","msg":"请求结束","logType":"request","path":"/ping","method":"GET","clientIP":"\u0000\u0000\u0001","elapsed":0.0005278,"trackId":"375118384762716160"} +{"level":"INFO","ts":"2025-11-01 11:07:13.255","caller":"middleware/logger.go:34","msg":"请求结束","logType":"request","path":"/ping","method":"GET","clientIP":"127.0.0.1","elapsed":0.0006027,"trackId":"375118556582379520"} +{"level":"INFO","ts":"2025-11-01 11:07:55.599","caller":"middleware/logger.go:41","msg":"请求结束","logType":"request","path":"/ping","method":"GET","ip":"127.0.0.1","elapsed":0.0010004,"trackId":"375118734181793792"} +{"level":"INFO","ts":"2025-11-01 13:19:29.451","caller":"middleware/logger.go:34","msg":"请求结束","path":"/app/app/ping","method":"GET","ip":"127.0.0.1","elapsed":0.0005252,"trackId":"375151843401007104"} +{"level":"INFO","ts":"2025-11-01 13:26:31.817","caller":"middleware/logger.go:34","msg":"请求结束","path":"/app/test","method":"GET","ip":"127.0.0.1","elapsed":0.0019991,"trackId":"375153614924021760"} +{"level":"INFO","ts":"2025-11-01 13:43:51.649","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/test","method":"GET","ip":"127.0.0.1","elapsed":0.0005048,"trackId":"375157976299732992"} +{"level":"INFO","ts":"2025-11-01 13:46:59.502","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/test","method":"GET","ip":"127.0.0.1","elapsed":0.001,"trackId":"375158764212322304"} diff --git a/code/base-project/base-go-v2/logs/warn/warn-2025-11-01.log b/code/base-project/base-go-v2/logs/warn/warn-2025-11-01.log new file mode 100644 index 00000000..e69de29b