diff --git a/code/base-project/base-app/package.json b/code/base-project/base-app/package.json index 97f633d1..a5d493f7 100644 --- a/code/base-project/base-app/package.json +++ b/code/base-project/base-app/package.json @@ -10,12 +10,14 @@ "web": "expo start --web" }, "dependencies": { + "@alova/adapter-axios": "^2.0.16", "@gluestack-ui/core": "^3.0.12", "@gluestack-ui/utils": "^3.0.11", "@gluestack/ui-next-adapter": "^3.0.3", "@legendapp/motion": "^2.4.0", "@react-native-async-storage/async-storage": "2.2.0", "alova": "^3.3.4", + "axios": "^1.13.2", "expo": "~54.0.20", "expo-constants": "~18.0.10", "expo-font": "~14.0.9", diff --git a/code/base-project/base-app/pnpm-lock.yaml b/code/base-project/base-app/pnpm-lock.yaml index 7098b4d7..1b98d64a 100644 --- a/code/base-project/base-app/pnpm-lock.yaml +++ b/code/base-project/base-app/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@alova/adapter-axios': + specifier: ^2.0.16 + version: 2.0.16(alova@3.3.4)(axios@1.13.2) '@gluestack-ui/core': specifier: ^3.0.12 version: 3.0.12(@gluestack-ui/utils@3.0.11(react-dom@19.2.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)(tailwindcss@3.4.18(yaml@2.8.1)))(react-native-safe-area-context@5.6.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-svg@15.12.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) @@ -26,6 +29,9 @@ importers: alova: specifier: ^3.3.4 version: 3.3.4 + axios: + specifier: ^1.13.2 + version: 1.13.2 expo: specifier: ~54.0.20 version: 54.0.21(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) @@ -111,6 +117,12 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@alova/adapter-axios@2.0.16': + resolution: {integrity: sha512-O9iskfllA8Yd5QV7w/c2aBduf1ocpD5goXp3aJbBXovZanKbsgMyvuFycXmTeqPyLdjURo5fYicEmWHwURpkbQ==} + peerDependencies: + alova: ^3.0.20 + axios: '>=0.4.0' + '@alova/shared@1.3.1': resolution: {integrity: sha512-ijSOaFLUFcVzMKSY3avoEE5C03/p9atjMDPBwvNkwnzaCrhv6/m4A121NdadF8YlHCRuifyYfz90IyEdMXTsJg==} @@ -2215,10 +2227,16 @@ packages: async-limiter@1.0.1: resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + axios@0.19.2: resolution: {integrity: sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==} deprecated: Critical security vulnerability fixed in v0.21.1. For more information, see https://github.com/axios/axios/pull/3410 + axios@1.13.2: + resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} + babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2355,6 +2373,10 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -2469,6 +2491,10 @@ packages: resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} engines: {node: '>=0.1.90'} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + commander@12.1.0: resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} engines: {node: '>=18'} @@ -2616,6 +2642,10 @@ packages: resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} engines: {node: '>=8'} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -2677,6 +2707,10 @@ packages: resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} engines: {node: '>=12'} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -2719,6 +2753,22 @@ packages: error-stack-parser@2.1.4: resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -3153,6 +3203,15 @@ packages: flow-enums-runtime@0.0.6: resolution: {integrity: sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==} + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + follow-redirects@1.5.10: resolution: {integrity: sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==} engines: {node: '>=4.0'} @@ -3164,6 +3223,10 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} + form-data@4.0.4: + resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} + engines: {node: '>= 6'} + format@0.2.2: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} engines: {node: '>=0.4.x'} @@ -3199,6 +3262,10 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + get-nonce@1.0.1: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} @@ -3207,6 +3274,10 @@ packages: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + get-tsconfig@4.13.0: resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} @@ -3252,6 +3323,10 @@ packages: globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -3266,6 +3341,14 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -3587,56 +3670,48 @@ 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==} @@ -3737,6 +3812,10 @@ packages: marky@1.3.0: resolution: {integrity: sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} @@ -4371,6 +4450,9 @@ packages: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -5319,6 +5401,12 @@ snapshots: '@alloc/quick-lru@5.2.0': {} + '@alova/adapter-axios@2.0.16(alova@3.3.4)(axios@1.13.2)': + dependencies: + '@alova/shared': 1.3.1 + alova: 3.3.4 + axios: 1.13.2 + '@alova/shared@1.3.1': {} '@antfu/eslint-config@6.2.0(@vue/compiler-sfc@3.5.22)(eslint@9.39.0(jiti@1.21.7))(typescript@5.9.3)': @@ -8285,12 +8373,22 @@ snapshots: async-limiter@1.0.1: {} + asynckit@0.4.0: {} + axios@0.19.2: dependencies: follow-redirects: 1.5.10 transitivePeerDependencies: - supports-color + axios@1.13.2: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.4 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + babel-jest@29.7.0(@babel/core@7.28.5): dependencies: '@babel/core': 7.28.5 @@ -8484,6 +8582,11 @@ snapshots: cac@6.7.14: {} + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + callsites@3.1.0: {} camelcase-css@2.0.1: {} @@ -8597,6 +8700,10 @@ snapshots: colors@1.4.0: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + commander@12.1.0: {} commander@2.20.3: {} @@ -8725,6 +8832,8 @@ snapshots: define-lazy-prop@2.0.0: {} + delayed-stream@1.0.0: {} + depd@2.0.0: {} dequal@2.0.3: {} @@ -8776,6 +8885,12 @@ snapshots: dotenv@16.4.7: {} + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + eastasianwidth@0.2.0: {} ee-first@1.1.1: {} @@ -8805,6 +8920,21 @@ snapshots: dependencies: stackframe: 1.3.4 + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + escalade@3.2.0: {} escape-html@1.0.3: {} @@ -9356,6 +9486,8 @@ snapshots: flow-enums-runtime@0.0.6: {} + follow-redirects@1.15.11: {} + follow-redirects@1.5.10: dependencies: debug: 3.1.0 @@ -9369,6 +9501,14 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + form-data@4.0.4: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + format@0.2.2: {} freeport-async@2.0.0: {} @@ -9392,10 +9532,28 @@ snapshots: get-caller-file@2.0.5: {} + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + get-nonce@1.0.1: {} get-package-type@0.1.0: {} + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + get-tsconfig@4.13.0: dependencies: resolve-pkg-maps: 1.0.0 @@ -9442,6 +9600,8 @@ snapshots: globrex@0.1.2: {} + gopd@1.2.0: {} + graceful-fs@4.2.11: {} graphemer@1.4.0: {} @@ -9450,6 +9610,12 @@ snapshots: has-flag@4.0.0: {} + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + hasown@2.0.2: dependencies: function-bind: 1.1.2 @@ -9891,6 +10057,8 @@ snapshots: marky@1.3.0: {} + math-intrinsics@1.1.0: {} + mdast-util-find-and-replace@3.0.2: dependencies: '@types/mdast': 4.0.4 @@ -10898,6 +11066,8 @@ snapshots: kleur: 3.0.3 sisteransi: 1.0.5 + proxy-from-env@1.1.0: {} + punycode@2.3.1: {} qrcode-terminal@0.11.0: {} diff --git a/code/base-project/base-app/src/app/auth/login.tsx b/code/base-project/base-app/src/app/auth/login.tsx index 3cf025c6..c474d54b 100644 --- a/code/base-project/base-app/src/app/auth/login.tsx +++ b/code/base-project/base-app/src/app/auth/login.tsx @@ -1,6 +1,7 @@ import { Lock, UserRound } from 'lucide-react-native' import { useState } from 'react' import { Image, Text, View } from 'react-native' +import { AForm, AFormItem, AInput } from '@/components/common/ao' import { IconQq, IconWeixin, IconZhiwen } from '@/components/iconfont' import { Button, ButtonText } from '@/components/ui/button' import { @@ -10,13 +11,9 @@ import { CheckboxLabel, } from '@/components/ui/checkbox' import { Divider } from '@/components/ui/divider' -import { - FormControl, - FormControlLabel, - FormControlLabelText, -} from '@/components/ui/form-control' -import { CheckIcon, EyeIcon, EyeOffIcon } from '@/components/ui/icon' -import { Input, InputField, InputIcon, InputSlot } from '@/components/ui/input' + +import { CheckIcon } from '@/components/ui/icon' + import alovaInstance from '@/request' import { useToast } from '@/utils/toast' @@ -30,13 +27,14 @@ interface LoginForm { export default function AuthLogin() { const toast = useToast() - const [showPassword, setShowPassword] = useState(false) const [form, setForm] = useState({ username: '', password: '', rememberMe: true, }) - + const handleInputChange = (field: keyof LoginForm, value: string) => { + setForm(prev => ({ ...prev, [field]: value })) + } const defaultLogin = () => { alovaInstance.Post('/auth/login/default', { title: 'foo', @@ -56,39 +54,21 @@ export default function AuthLogin() { className="my-12" /> - - - 账号 - - - - - - setForm(prev => ({ ...prev, username: text }))} + + + handleInputChange('username', text)} /> - - - - - - 密码 - - - - - - setForm(prev => ({ ...prev, password: text }))} + + + handleInputChange('password', text)} /> - setShowPassword(prev => !prev)}> - - - - + + - scrollRef: React.RefObject + scrollRef?: React.RefObject } interface FormContextType { @@ -57,7 +57,7 @@ const Form = forwardRef(({ children, form, scrollRef }, ref) const scrollToField = (name: string) => { const y = fieldPositions.current.get(name) - if (y !== undefined && scrollRef.current) { + if (y !== undefined && scrollRef?.current) { scrollRef.current.scrollTo({ y, animated: true }) } } diff --git a/code/base-project/base-app/src/components/common/ao/FormItem.tsx b/code/base-project/base-app/src/components/common/ao/FormItem.tsx index 70d0f337..0c865201 100644 --- a/code/base-project/base-app/src/components/common/ao/FormItem.tsx +++ b/code/base-project/base-app/src/components/common/ao/FormItem.tsx @@ -22,7 +22,7 @@ interface RuleType { } interface FormItemProps { - name: keyof T + name?: keyof T label?: string rules?: RuleType[] children: React.ReactNode diff --git a/code/base-project/base-app/src/request/index.ts b/code/base-project/base-app/src/request/index.ts index 94620693..6b979b19 100644 --- a/code/base-project/base-app/src/request/index.ts +++ b/code/base-project/base-app/src/request/index.ts @@ -1,21 +1,22 @@ +import { axiosRequestAdapter } from '@alova/adapter-axios' import { createAlova } from 'alova' import { createServerTokenAuthentication } from 'alova/client' import adapterFetch from 'alova/fetch' +import { useToast } from '@/utils/toast' + const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthentication({ // ... }) const alovaInstance = createAlova({ - baseURL: 'http://192.168.1.136:8085/app', - requestAdapter: adapterFetch(), - // ... - beforeRequest: onAuthRequired((method) => { - // ...原请求前拦截器 - }), - responded: onResponseRefreshToken((response, method) => { - // ...原响应成功拦截器 - return response.json() - }), + // baseURL: 'http://192.168.1.136:8085/app', + baseURL: 'http://192.168.5.6:8085/app', + requestAdapter: axiosRequestAdapter(), + responded(response) { + // response自动被推断为AxiosResponse类型 + + return response.data + }, }) export default alovaInstance diff --git a/code/base-project/base-go-v2/configs/app.yaml b/code/base-project/base-go-v2/configs/app.yaml index af4a7b03..0246608d 100644 --- a/code/base-project/base-go-v2/configs/app.yaml +++ b/code/base-project/base-go-v2/configs/app.yaml @@ -15,7 +15,8 @@ db: jwt: secret: 3Bde3BGEbYqtqyEUzW3ry8jKFcaPH17fRmTmqE7MDr05Lwj95uruRKrrkb44TJ4s - expiry: -1 + accessExpiry: 30m + refreshExpiry: 720h log: dir: "logs" # 日志根目录 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 index 7138fb0a..985a1cd8 100644 --- a/code/base-project/base-go-v2/internal/api/auth/index.go +++ b/code/base-project/base-go-v2/internal/api/auth/index.go @@ -4,9 +4,9 @@ import ( "base-go-v2/internal/db" response "base-go-v2/internal/reponse" "base-go-v2/internal/router" + "base-go-v2/internal/utils/jwtx" "base-go-v2/internal/utils/mapx" "base-go-v2/internal/utils/uid" - "fmt" ) func InitAuthRouter(r *router.Router) { @@ -15,20 +15,71 @@ func InitAuthRouter(r *router.Router) { group.POST("/register/default", registerDefault) } -func RefreshToken(c *router.Context) error { - bodyData, err := c.GetBodyWithRequired("accessToken", "refreshToken") +//func RefreshToken(c *router.Context) error { +// _, err := c.GetBodyWithRequired("accessToken", "refreshToken") +// if err != nil { +// return err +// } +// //accessToken := bodyData.GetString("accessToken") +// //refreshToken := bodyData.GetString("refreshToken") +//} + +// 默认登录 +// 默认登录 +func loginDefault(c *router.Context) error { + // 获取请求体并校验必填字段 + bodyData, err := c.GetBodyWithRequired("account", "password") if err != nil { return err } - accessToken := bodyData.GetString("accessToken") - refreshToken := bodyData.GetString("refreshToken") -} -// 默认登录 -func loginDefault(c *router.Context) error { - fmt.Println("loginDefault 被调用") + account := bodyData.GetString("account") + password := bodyData.GetString("password") - return response.Success(c).Send() + // 查询用户 + userQuery := mapx.New().SetKV( + "account", account, + "password", password, + ) + user, err := db.FindOne("user_info", userQuery) + if err != nil { + return response.Success(c).Message("查询用户失败,请稍后重试").IsSuccess(false).Send() + } + + if user == nil { + // 账号或密码错误 + return response.Success(c). + IsSuccess(false). + Message("账号或密码错误"). + Send() + } + + userId := user.GetInt("id") + + // 创建 Access Token 和 Refresh Token + accessToken, err := jwtx.CreateAccessToken(userId) + if err != nil { + return err + } + refreshToken, err := jwtx.CreateRefreshToken(userId) + if err != nil { + return err + } + + // 返回数据 + respData := mapx.New(). + SetIntToString("userId", userId). + SetKV( + "username", user.GetString("username"), + "seqNo", user.GetInt("seq_no"), + "gender", user.GetInt("gender"), + "accessToken", accessToken, + "refreshToken", refreshToken, + ) + + return response.Success(c). + Data(respData). + Send() } func registerDefault(c *router.Context) error { @@ -48,7 +99,7 @@ func registerDefault(c *router.Context) error { } if len(existingUsers) > 0 { - return response.Error(c).Message("该账号已被注册").Send() + return response.Error(c).IsSuccess(false).Message("该账号已被注册").Send() } // 构造新用户数据 @@ -64,7 +115,7 @@ func registerDefault(c *router.Context) error { return err } if insertedRows != 1 { - return response.Error(c).Message("注册失败").Send() + return response.Success(c).IsSuccess(false).Message("注册失败").Send() } return response.Success(c).Message("注册成功").Send() diff --git a/code/base-project/base-go-v2/internal/config/app.go b/code/base-project/base-go-v2/internal/config/app.go index b9b777a0..7e203752 100644 --- a/code/base-project/base-go-v2/internal/config/app.go +++ b/code/base-project/base-go-v2/internal/config/app.go @@ -18,8 +18,9 @@ func init() { Port: 8080, }, JWT: types.JWTConfig{ - Secret: "default_secret", - Expiry: time.Hour, + Secret: "default_secret", + RefreshExpiry: time.Hour, + AccessExpiry: time.Hour, }, Db: types.DbConfig{ MaxOpenConns: 10, diff --git a/code/base-project/base-go-v2/internal/db/query.go b/code/base-project/base-go-v2/internal/db/query.go index 0701ff6b..154dd90b 100644 --- a/code/base-project/base-go-v2/internal/db/query.go +++ b/code/base-project/base-go-v2/internal/db/query.go @@ -1,17 +1,18 @@ package db import ( + "base-go-v2/internal/utils/mapx" "fmt" "strings" ) // ---------------- 通用查询方法 ---------------- -// GetOne 根据主键查询单条记录,返回 map[string]interface{} -func GetOne(table string, pkColumn string, pkValue interface{}) (map[string]interface{}, error) { +// GetOne 根据主键查询单条记录,返回 mapx.M +func GetOne(table string, pkColumn string, pkValue interface{}) (mapx.M, error) { query := fmt.Sprintf("SELECT * FROM %s WHERE %s=$1", table, pkColumn) - row := make(map[string]interface{}) + row := mapx.New() err := DB.Get(&row, query, pkValue) if err != nil { return nil, err @@ -19,13 +20,13 @@ func GetOne(table string, pkColumn string, pkValue interface{}) (map[string]inte return row, nil } -// GetBatch 根据主键批量查询,返回 []map[string]interface{} -func GetBatch(table string, pkColumn string, pkValues []interface{}) ([]map[string]interface{}, error) { +// GetBatch 根据主键批量查询,返回 []mapx.M +func GetBatch(table string, pkColumn string, pkValues []interface{}) ([]mapx.M, error) { if len(pkValues) == 0 { return nil, nil } - var rows []map[string]interface{} + var rows []mapx.M // 构建 IN 占位符 placeholders := make([]string, len(pkValues)) @@ -42,10 +43,8 @@ func GetBatch(table string, pkColumn string, pkValues []interface{}) ([]map[stri return rows, nil } -// Find 根据条件查询多条记录,可选排序 -// conditions: map[column]value, 将以 AND 拼接 -// orderBy: 可选排序字段,如 "created_at ASC",为空默认 "b_id DESC" -func Find(table string, conditions map[string]interface{}, orderBy ...string) ([]map[string]interface{}, error) { +// Find 根据条件查询多条记录,可选排序,返回 []mapx.M +func Find(table string, conditions map[string]interface{}, orderBy ...string) ([]mapx.M, error) { if len(conditions) == 0 { return nil, fmt.Errorf("查询条件不能为空") } @@ -67,7 +66,7 @@ func Find(table string, conditions map[string]interface{}, orderBy ...string) ([ } query += " ORDER BY " + order - var rows []map[string]interface{} + var rows []mapx.M err := DB.Select(&rows, query, values...) if err != nil { return nil, err @@ -76,8 +75,8 @@ func Find(table string, conditions map[string]interface{}, orderBy ...string) ([ return rows, nil } -// FindAll 查询整个表,可选排序 -func FindAll(table string, orderBy ...string) ([]map[string]interface{}, error) { +// FindAll 查询整个表,可选排序,返回 []mapx.M +func FindAll(table string, orderBy ...string) ([]mapx.M, error) { query := fmt.Sprintf("SELECT * FROM %s", table) order := "b_id DESC" @@ -86,7 +85,7 @@ func FindAll(table string, orderBy ...string) ([]map[string]interface{}, error) } query += " ORDER BY " + order - var rows []map[string]interface{} + var rows []mapx.M err := DB.Select(&rows, query) if err != nil { return nil, err @@ -94,3 +93,35 @@ func FindAll(table string, orderBy ...string) ([]map[string]interface{}, error) return rows, nil } + +// FindOne 根据条件查询单条记录,返回 mapx.M +func FindOne(table string, conditions map[string]interface{}, orderBy ...string) (mapx.M, error) { + if len(conditions) == 0 { + return nil, fmt.Errorf("查询条件不能为空") + } + + var whereParts []string + var values []interface{} + i := 1 + for k, v := range conditions { + whereParts = append(whereParts, fmt.Sprintf("%s=$%d", k, i)) + values = append(values, v) + i++ + } + + query := fmt.Sprintf("SELECT * FROM %s WHERE %s", table, strings.Join(whereParts, " AND ")) + + order := "b_id DESC" + if len(orderBy) > 0 && orderBy[0] != "" { + order = orderBy[0] + } + query += " ORDER BY " + order + " LIMIT 1" // 只取一条记录 + + row := mapx.New() + err := DB.Get(&row, query, values...) + if err != nil { + return nil, err + } + + return row, nil +} diff --git a/code/base-project/base-go-v2/internal/errs/validation.go b/code/base-project/base-go-v2/internal/errs/validation.go index 43bde8b8..ea0590c9 100644 --- a/code/base-project/base-go-v2/internal/errs/validation.go +++ b/code/base-project/base-go-v2/internal/errs/validation.go @@ -1,19 +1,34 @@ package errs -import "fmt" +import ( + "fmt" + "strings" +) +// ValidationError 用于收集多个字段错误,同时实现 error 接口 type ValidationError struct { - field string - message string + Errors map[string]string // key: 字段名, value: 错误信息 } -func (e *ValidationError) Error() string { - return fmt.Sprintf("字段 %s: %s", e.field, e.message) +// Error 实现 error 接口 +func (v *ValidationError) Error() string { + if len(v.Errors) == 0 { + return "" + } + + msgs := make([]string, 0, len(v.Errors)) + for field, msg := range v.Errors { + msgs = append(msgs, fmt.Sprintf("%s: %s", field, msg)) + } + return strings.Join(msgs, "; ") } -func NewValidationError(field, message string) error { +// NewValidationError 创建多字段的 ValidationError +func NewValidationError(errors map[string]string) error { + if len(errors) == 0 { + return nil + } return &ValidationError{ - field: field, - message: message, + Errors: errors, } } diff --git a/code/base-project/base-go-v2/internal/reponse/response.go b/code/base-project/base-go-v2/internal/reponse/response.go index e2f5fbc8..0e0208bf 100644 --- a/code/base-project/base-go-v2/internal/reponse/response.go +++ b/code/base-project/base-go-v2/internal/reponse/response.go @@ -8,18 +8,22 @@ import ( "net/http" ) +// Result 是最终响应结构体 type Result struct { - Code string `json:"code"` - Message string `json:"message"` - Data interface{} `json:"data,omitempty"` - TrackId string `json:"trackId"` + IsSuccess bool `json:"isSuccess"` // 是否成功 + Code string `json:"code"` // 状态码 + Message string `json:"message"` // 提示信息 + Data interface{} `json:"data,omitempty"` // 数据 + TrackId string `json:"trackId,omitempty"` // 请求追踪ID } +// Code 定义状态码 type Code struct { Value string Message string } +// 常用状态码 var ( CodeSuccess = Code{"0000", "请求成功"} CodeFail = Code{"0001", "请求失败"} @@ -31,53 +35,75 @@ var ( const defaultMessage = "未知错误" +// Builder 用于构建响应 type Builder struct { c *router.Context result Result } +// ---------------- 构造函数 ---------------- + +// Success 创建成功响应 func Success(c *router.Context) *Builder { return &Builder{ c: c, result: Result{ - Code: CodeSuccess.Value, - Message: CodeSuccess.Message, + IsSuccess: true, + Code: CodeSuccess.Value, + Message: CodeSuccess.Message, }, } } +// Error 创建失败响应 func Error(c *router.Context) *Builder { return &Builder{ c: c, result: Result{ - Code: CodeFail.Value, - Message: CodeFail.Message, + IsSuccess: false, + Code: CodeFail.Value, + Message: CodeFail.Message, }, } } +// ---------------- 链式方法 ---------------- + +// 设置 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.Message = GetMessage(code) + b.result.IsSuccess = code == CodeSuccess.Value 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 { 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 { @@ -90,10 +116,12 @@ func (b *Builder) Send() error { b.result.TrackId = strutil.ToString(trackId) } - // 发送响应 return b.c.JSON(http.StatusOK, b.result) } +// ---------------- 辅助函数 ---------------- + +// 根据 Code 获取默认提示信息 func GetMessage(value string) string { for _, c := range []Code{ CodeSuccess, CodeFail, CodeNoLogin, diff --git a/code/base-project/base-go-v2/internal/router/context.go b/code/base-project/base-go-v2/internal/router/context.go index 5fbc6a2a..bfbad89a 100644 --- a/code/base-project/base-go-v2/internal/router/context.go +++ b/code/base-project/base-go-v2/internal/router/context.go @@ -97,7 +97,7 @@ func (c *Context) GetBodyWithRequired(keys ...string) (mapx.M, error) { } // 校验必填字段 - if err := validate.ValidateNotEmpty(req, keys...); err != nil { + if err := validate.NotEmpty(req, keys...); err != nil { return nil, err } 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 00ded23a..b517c54d 100644 --- a/code/base-project/base-go-v2/internal/types/appConfig.go +++ b/code/base-project/base-go-v2/internal/types/appConfig.go @@ -17,8 +17,9 @@ type ServerConfig struct { } type JWTConfig struct { - Secret string - Expiry time.Duration + Secret string + AccessExpiry time.Duration + RefreshExpiry time.Duration } type DbConfig struct { diff --git a/code/base-project/base-go-v2/internal/utils/jwt/jwt.go b/code/base-project/base-go-v2/internal/utils/jwt/jwt.go deleted file mode 100644 index 784651e4..00000000 --- a/code/base-project/base-go-v2/internal/utils/jwt/jwt.go +++ /dev/null @@ -1,77 +0,0 @@ -package jwt - -import ( - "base-go-v2/internal/config" - "errors" - "time" - - "github.com/golang-jwt/jwt/v5" -) - -var ( - ErrTokenExpired = errors.New("token expired") - ErrTokenInvalid = errors.New("token invalid") -) - -// CustomClaims 定义自己的 payload 结构 -type CustomClaims struct { - UserID string `json:"user_id"` - jwt.RegisteredClaims -} - -// CreateToken 创建一个 JWT token -// 支持 config.App.JWT.Expiry == -1 永不过期 -func CreateToken(orgID, userID string) (string, error) { - claims := CustomClaims{ - UserID: userID, - RegisteredClaims: jwt.RegisteredClaims{ - IssuedAt: jwt.NewNumericDate(time.Now()), - }, - } - - expiry := config.App.JWT.Expiry - - switch { - case expiry > 0: - expireAt := time.Now().Add(expiry) - claims.ExpiresAt = jwt.NewNumericDate(expireAt) - case expiry == -1: - // 永不过期,ExpiresAt 设置为 nil - claims.ExpiresAt = nil - default: - // 默认过期时间,例如 1 小时 - expireAt := time.Now().Add(time.Hour) - claims.ExpiresAt = jwt.NewNumericDate(expireAt) - } - - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - return token.SignedString([]byte(config.App.JWT.Secret)) -} - -// VerifyToken 验证并解析 JWT token -func VerifyToken(tokenString string) (*CustomClaims, error) { - signKey := []byte(config.App.JWT.Secret) - - token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) { - // 校验签名算法 - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, ErrTokenInvalid - } - return signKey, nil - }) - if err != nil { - return nil, ErrTokenInvalid - } - - claims, ok := token.Claims.(*CustomClaims) - if !ok || !token.Valid { - return nil, ErrTokenInvalid - } - - // 检查过期:ExpiresAt 为 nil 时永不过期 - if claims.ExpiresAt != nil && time.Now().After(claims.ExpiresAt.Time) { - return nil, ErrTokenExpired - } - - return claims, nil -} diff --git a/code/base-project/base-go-v2/internal/utils/jwtx/jwt.go b/code/base-project/base-go-v2/internal/utils/jwtx/jwt.go new file mode 100644 index 00000000..8cac9ef9 --- /dev/null +++ b/code/base-project/base-go-v2/internal/utils/jwtx/jwt.go @@ -0,0 +1,82 @@ +package jwtx + +import ( + "base-go-v2/internal/config" + "time" + + "github.com/golang-jwt/jwt/v5" +) + +// CustomClaims 自定义 payload,可扩展其他信息 +type CustomClaims struct { + UserID int `json:"user_id"` // 改为 int + jwt.RegisteredClaims +} + +// TokenResult 用于返回验证结果 +type TokenResult struct { + Claims *CustomClaims + Valid bool + Expired bool +} + +// CreateAccessToken 创建 Access Token,只返回 token 和 error +func CreateAccessToken(userID int) (string, error) { + return createToken(userID, config.App.JWT.AccessExpiry) +} + +// CreateRefreshToken 创建 Refresh Token,只返回 token 和 error +func CreateRefreshToken(userID int) (string, error) { + return createToken(userID, config.App.JWT.RefreshExpiry) +} + +// 内部方法,根据过期时间生成 token +func createToken(userID int, expiry time.Duration) (string, error) { + claims := &CustomClaims{ + UserID: userID, + RegisteredClaims: jwt.RegisteredClaims{ + IssuedAt: jwt.NewNumericDate(time.Now()), + }, + } + + switch { + case expiry > 0: + expireAt := time.Now().Add(expiry) + claims.ExpiresAt = jwt.NewNumericDate(expireAt) + case expiry == -1: + claims.ExpiresAt = nil // 永不过期 + default: + expireAt := time.Now().Add(time.Hour) + claims.ExpiresAt = jwt.NewNumericDate(expireAt) + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + signed, err := token.SignedString([]byte(config.App.JWT.Secret)) + if err != nil { + return "", err + } + + return signed, nil +} + +// VerifyToken 验证 token 并返回结构化结果 +func VerifyToken(tokenString string) TokenResult { + result := TokenResult{Valid: false} + + token, _ := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) { + return []byte(config.App.JWT.Secret), nil + }) + + claims, ok := token.Claims.(*CustomClaims) + if !ok || !token.Valid { + return result // token 无效 + } + + result.Claims = claims + result.Valid = true + if claims.ExpiresAt != nil && time.Now().After(claims.ExpiresAt.Time) { + result.Expired = true + } + + return result +} diff --git a/code/base-project/base-go-v2/internal/utils/mapx/index.go b/code/base-project/base-go-v2/internal/utils/mapx/index.go index 1dac82b5..350633fb 100644 --- a/code/base-project/base-go-v2/internal/utils/mapx/index.go +++ b/code/base-project/base-go-v2/internal/utils/mapx/index.go @@ -1,5 +1,10 @@ package mapx +import ( + "encoding/json" + "strconv" +) + // M 是 map[string]interface{} 的别名 type M map[string]interface{} @@ -29,6 +34,35 @@ func (m M) SetKV(kvs ...interface{}) M { return m } +// ---------------- 新增方法 ---------------- + +// SetIntToString 将 int 或 int64 直接转为 string 存储,用于前端安全传输 +func (m M) SetIntToString(key string, val interface{}) M { + var s string + switch v := val.(type) { + case int: + s = strconv.Itoa(v) // 直接转为 string + case int64: + s = strconv.FormatInt(v, 10) + default: + panic("SetIntToString: unsupported type") + } + m[key] = s + return m +} + +// GetIntFromString 将前端传来的字符串解析为 int64 +func (m M) GetIntFromString(key string) int64 { + if v, ok := m[key]; ok { + if s, ok := v.(string); ok { + if i, err := strconv.ParseInt(s, 10, 64); err == nil { + return i + } + } + } + return 0 +} + // Get 获取值 func (m M) Get(key string) interface{} { return m[key] @@ -62,44 +96,15 @@ func (m M) GetInt(key string) int { return int(val) case float64: return int(val) + case string: + if i, err := strconv.ParseInt(val, 10, 64); err == nil { + return int(i) + } } } return 0 } -// GetFloat 获取浮点数类型 -func (m M) GetFloat(key string) float64 { - if v, ok := m[key]; ok { - switch val := v.(type) { - case float32: - return float64(val) - case float64: - return val - case int: - return float64(val) - case int8: - return float64(val) - case int16: - return float64(val) - case int32: - return float64(val) - case int64: - return float64(val) - } - } - return 0 -} - -// GetBool 获取布尔类型 -func (m M) GetBool(key string) bool { - if v, ok := m[key]; ok { - if b, ok := v.(bool); ok { - return b - } - } - return false -} - // Merge 合并另一个 M,后者会覆盖前者同名字段 func (m M) Merge(other M) M { for k, v := range other { @@ -107,3 +112,8 @@ func (m M) Merge(other M) M { } return m } + +// MarshalJSON 支持 M 自定义 JSON 序列化 +func (m M) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]interface{}(m)) +} diff --git a/code/base-project/base-go-v2/internal/utils/validate/validate.go b/code/base-project/base-go-v2/internal/utils/validate/validate.go index 015dd282..fdc172a2 100644 --- a/code/base-project/base-go-v2/internal/utils/validate/validate.go +++ b/code/base-project/base-go-v2/internal/utils/validate/validate.go @@ -1,42 +1,65 @@ package validate import ( - "base-go-v2/internal/errs" - "errors" - "reflect" + "fmt" "strings" ) -// ValidateNotEmpty 校验结构体中指定字段不能为空 -func ValidateNotEmpty(s interface{}, fields ...string) error { - v := reflect.ValueOf(s) - if v.Kind() == reflect.Ptr { - v = v.Elem() +// ValidationError 用于收集多个字段错误,同时实现 error 接口 +type ValidationError struct { + Errors map[string]string +} + +// Error 实现 error 接口 +func (v *ValidationError) Error() string { + msgs := make([]string, 0, len(v.Errors)) + for field, err := range v.Errors { + msgs = append(msgs, fmt.Sprintf("%s: %s", field, err)) } + return strings.Join(msgs, "; ") +} + +// NotEmpty 校验 map[string]interface{} 中指定字段不能为空 +func NotEmpty(data map[string]interface{}, fields ...string) error { + errs := make(map[string]string) for _, field := range fields { - f := v.FieldByName(field) - if !f.IsValid() { - return errors.New("field " + field + " does not exist") - } - - switch f.Kind() { - case reflect.String: - if strings.TrimSpace(f.String()) == "" { - return errs.NewValidationError(field, "不能为空") - } - case reflect.Slice, reflect.Array, reflect.Map: - if f.Len() == 0 { - return errs.NewValidationError(field, "不能为空") - } - case reflect.Ptr, reflect.Interface: - if f.IsNil() { - return errs.NewValidationError(field, "不能为空") - } - default: - continue - } + collectErrors(data[field], field, errs) } + if len(errs) > 0 { + return &ValidationError{Errors: errs} + } return nil } + +// collectErrors 递归收集字段错误 +func collectErrors(v interface{}, path string, errs map[string]string) { + if v == nil { + errs[path] = fmt.Sprintf("字段 %s 不能为空", path) + return + } + + switch val := v.(type) { + case string: + if strings.TrimSpace(val) == "" { + errs[path] = fmt.Sprintf("字段 %s 不能为空", path) + } + case []interface{}: + if len(val) == 0 { + errs[path] = fmt.Sprintf("字段 %s 不能为空", path) + } + for i, elem := range val { + collectErrors(elem, fmt.Sprintf("%s[%d]", path, i), errs) + } + case map[string]interface{}: + if len(val) == 0 { + errs[path] = fmt.Sprintf("字段 %s 不能为空", path) + } + for k, elem := range val { + collectErrors(elem, fmt.Sprintf("%s.%s", path, k), errs) + } + default: + // 其他类型暂不校验 + } +} diff --git a/code/base-project/base-go-v2/logs/info/info-2025-11-05.log b/code/base-project/base-go-v2/logs/info/info-2025-11-05.log index dec4bf64..25f06665 100644 --- a/code/base-project/base-go-v2/logs/info/info-2025-11-05.log +++ b/code/base-project/base-go-v2/logs/info/info-2025-11-05.log @@ -1,3 +1,35 @@ {"level":"INFO","ts":"2025-11-05 16:59:43.475","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.1.136","elapsed":0.0009773,"trackId":"376656818492932096"} {"level":"INFO","ts":"2025-11-05 17:00:17.955","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.1.136","elapsed":0.0009184,"trackId":"376656963112534016"} {"level":"INFO","ts":"2025-11-05 17:00:21.027","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.1.136","elapsed":0,"trackId":"376656976001630208"} +{"level":"INFO","ts":"2025-11-05 21:35:40.062","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0005206,"trackId":"376726261852012544"} +{"level":"INFO","ts":"2025-11-05 21:35:47.481","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0008274,"trackId":"376726292969553920"} +{"level":"INFO","ts":"2025-11-05 21:35:48.665","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726297939804160"} +{"level":"INFO","ts":"2025-11-05 21:35:48.907","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726298954825728"} +{"level":"INFO","ts":"2025-11-05 21:35:49.171","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0009767,"trackId":"376726300057927680"} +{"level":"INFO","ts":"2025-11-05 21:37:38.008","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0005298,"trackId":"376726756557586432"} +{"level":"INFO","ts":"2025-11-05 21:38:18.445","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726926162657280"} +{"level":"INFO","ts":"2025-11-05 21:38:20.520","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0004533,"trackId":"376726934865838080"} +{"level":"INFO","ts":"2025-11-05 21:38:21.140","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726937466306560"} +{"level":"INFO","ts":"2025-11-05 21:38:21.288","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0004953,"trackId":"376726938082869248"} +{"level":"INFO","ts":"2025-11-05 21:38:21.609","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726939433435136"} +{"level":"INFO","ts":"2025-11-05 21:38:21.828","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726940351987712"} +{"level":"INFO","ts":"2025-11-05 21:38:22.088","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.000925,"trackId":"376726941438312448"} +{"level":"INFO","ts":"2025-11-05 21:38:22.281","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0002126,"trackId":"376726942252007424"} +{"level":"INFO","ts":"2025-11-05 21:38:22.476","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0002409,"trackId":"376726943069896704"} +{"level":"INFO","ts":"2025-11-05 21:38:22.663","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726943854231552"} +{"level":"INFO","ts":"2025-11-05 21:38:22.883","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726944776978432"} +{"level":"INFO","ts":"2025-11-05 21:38:23.100","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0007084,"trackId":"376726945682948096"} +{"level":"INFO","ts":"2025-11-05 21:38:23.324","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726946626666496"} +{"level":"INFO","ts":"2025-11-05 21:38:23.548","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726947566190592"} +{"level":"INFO","ts":"2025-11-05 21:38:23.796","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0007185,"trackId":"376726948606377984"} +{"level":"INFO","ts":"2025-11-05 21:38:23.992","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0002713,"trackId":"376726949424267264"} +{"level":"INFO","ts":"2025-11-05 21:38:24.230","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0006165,"trackId":"376726950422511616"} +{"level":"INFO","ts":"2025-11-05 21:38:24.432","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726951273955328"} +{"level":"INFO","ts":"2025-11-05 21:38:24.628","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726952096038912"} +{"level":"INFO","ts":"2025-11-05 21:38:25.076","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726953975087104"} +{"level":"INFO","ts":"2025-11-05 21:38:25.276","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0000358,"trackId":"376726954813947904"} +{"level":"INFO","ts":"2025-11-05 21:38:25.459","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726955581505536"} +{"level":"INFO","ts":"2025-11-05 21:38:25.664","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0004416,"trackId":"376726956437143552"} +{"level":"INFO","ts":"2025-11-05 21:38:25.864","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0005154,"trackId":"376726957276004352"} +{"level":"INFO","ts":"2025-11-05 21:38:26.084","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0.0002596,"trackId":"376726958202945536"} +{"level":"INFO","ts":"2025-11-05 21:38:26.319","caller":"middleware/logger.go:34","msg":"接口请求","path":"/app/auth/login/default","method":"POST","ip":"192.168.5.3","elapsed":0,"trackId":"376726959188606976"}