From 1df27d3a23b6a6188957f571340f8f7ecdb5b94d Mon Sep 17 00:00:00 2001 From: wangziqi Date: Wed, 11 Feb 2026 09:10:29 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=9B=9E=E6=98=BE=E3=80=81=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E8=AE=A4=E8=AF=81=E5=92=8CAPI=E8=B7=AF=E5=BE=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复上传图片响应解析,正确处理 Arco Upload 返回的 response 对象 - 修复后端 AuthInterceptor 路径匹配,正确放行 /api/auth/login 等公开接口 - 统一前端 API 路径配置,移除重复 /api 前缀 - 添加 /uploads 静态资源代理配置 - 修复图片 URL 生成,添加 origin 前缀确保回显正常 --- .../maternalmall/config/AuthInterceptor.java | 13 +- .../controller/FileUploadController.java | 2 +- backend/src/main/resources/application.yml | 5 + frontend/src/api/index.js | 117 +++++++++--------- frontend/src/views/admin/AdminBanners.vue | 21 +++- frontend/src/views/admin/AdminProducts.vue | 21 +++- .../src/views/merchant/MerchantProducts.vue | 21 +++- frontend/vite.config.js | 2 +- 8 files changed, 120 insertions(+), 82 deletions(-) diff --git a/backend/src/main/java/com/maternalmall/config/AuthInterceptor.java b/backend/src/main/java/com/maternalmall/config/AuthInterceptor.java index 5986a89..8acd72f 100644 --- a/backend/src/main/java/com/maternalmall/config/AuthInterceptor.java +++ b/backend/src/main/java/com/maternalmall/config/AuthInterceptor.java @@ -9,7 +9,8 @@ import org.springframework.http.HttpMethod; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; -import java.util.Set; +import java.util.Arrays; +import java.util.List; @Component public class AuthInterceptor implements HandlerInterceptor { @@ -18,7 +19,7 @@ public class AuthInterceptor implements HandlerInterceptor { @Value("${app.token-header:X-Token}") private String tokenHeader; - private static final Set PUBLIC_PREFIX = Set.of( + private static final java.util.List PUBLIC_PATHS = java.util.Arrays.asList( "/api/auth/login", "/api/auth/register", "/api/public" @@ -37,17 +38,23 @@ public class AuthInterceptor implements HandlerInterceptor { } String uri = request.getRequestURI(); - if (uri.equals("/") || uri.startsWith("/error") || PUBLIC_PREFIX.stream().anyMatch(uri::startsWith)) { + String contextPath = request.getContextPath(); + String path = contextPath.isEmpty() ? uri : uri.substring(contextPath.length()); + + if (uri.equals("/") || uri.startsWith("/error") || + PUBLIC_PATHS.stream().anyMatch(p -> path.equals(p) || path.startsWith(p + "/") || path.startsWith(p + "?"))) { return true; } String token = request.getHeader(tokenHeader); if (token == null || token.isBlank()) { + response.setContentType("application/json;charset=UTF-8"); response.setStatus(401); response.getWriter().write("{\"code\":401,\"message\":\"请先登录\",\"data\":null}"); return false; } User user = userRepository.findByToken(token).orElse(null); if (user == null || !Boolean.TRUE.equals(user.getEnabled())) { + response.setContentType("application/json;charset=UTF-8"); response.setStatus(401); response.getWriter().write("{\"code\":401,\"message\":\"登录状态无效\",\"data\":null}"); return false; diff --git a/backend/src/main/java/com/maternalmall/controller/FileUploadController.java b/backend/src/main/java/com/maternalmall/controller/FileUploadController.java index 09bf48e..5d0cbb9 100644 --- a/backend/src/main/java/com/maternalmall/controller/FileUploadController.java +++ b/backend/src/main/java/com/maternalmall/controller/FileUploadController.java @@ -22,7 +22,7 @@ public class FileUploadController { @Value("${app.upload-url-prefix:/uploads}") private String uploadUrlPrefix; - @PostMapping("/upload") + @PostMapping("/upload/file") public ApiResponse uploadFile(@RequestParam("file") MultipartFile file) { if (file.isEmpty()) { return ApiResponse.fail("请选择要上传的文件", String.class); diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index cfbfb44..024d629 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -15,6 +15,11 @@ spring: format_sql: true jackson: time-zone: Asia/Shanghai + servlet: + multipart: + max-file-size: 10MB + max-request-size: 100MB app: token-header: X-Token + upload-path: /Users/apple/code/bs/mying/backend/uploads diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index cd732c8..1524616 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -1,69 +1,68 @@ import http from './http' export const api = { - register: (payload) => http.post('/api/auth/register', payload), - login: (payload) => http.post('/api/auth/login', payload), - me: () => http.get('/api/auth/me'), - updateMe: (payload) => http.put('/api/auth/me', payload), + register: (payload) => http.post('/auth/register', payload), + login: (payload) => http.post('/auth/login', payload), + me: () => http.get('/auth/me'), + updateMe: (payload) => http.put('/auth/me', payload), - banners: () => http.get('/api/public/banners'), - products: (keyword = '') => http.get('/api/public/products', { params: { keyword } }), + banners: () => http.get('/public/banners'), + products: (keyword = '') => http.get('/public/products', { params: { keyword } }), - customerCart: () => http.get('/api/customer/cart'), - customerCartViews: () => http.get('/api/customer/cart/views'), - addCart: (payload) => http.post('/api/customer/cart', payload), - delCart: (productId) => http.delete(`/api/customer/cart/${productId}`), - checkout: (payload) => http.post('/api/customer/orders/checkout', payload), - customerBuyNow: (payload) => http.post('/api/customer/orders/buy-now', payload), - customerOrders: () => http.get('/api/customer/orders'), - refundOrder: (id, payload) => http.put(`/api/customer/orders/${id}/refund`, payload), - updateOrderAddress: (id, payload) => http.put(`/api/customer/orders/${id}/address`, payload), - deleteOrder: (id) => http.delete(`/api/customer/orders/${id}`), - orderLogistics: (id) => http.get(`/api/customer/orders/${id}/logistics`), - customerFavorites: () => http.get('/api/customer/favorites'), - customerFavoriteViews: () => http.get('/api/customer/favorites/views'), - addFavorite: (payload) => http.post('/api/customer/favorites', payload), - deleteFavorite: (productId) => http.delete(`/api/customer/favorites/${productId}`), - addReview: (payload) => http.post('/api/customer/reviews', payload), - orderItems: (orderId) => http.get(`/api/customer/orders/${orderId}/items`), - applyMerchant: (payload) => http.post('/api/customer/merchant-applications', payload), + customerCart: () => http.get('/customer/cart'), + customerCartViews: () => http.get('/customer/cart/views'), + addCart: (payload) => http.post('/customer/cart', payload), + delCart: (productId) => http.delete(`/customer/cart/${productId}`), + checkout: (payload) => http.post('/customer/orders/checkout', payload), + customerBuyNow: (payload) => http.post('/customer/orders/buy-now', payload), + customerOrders: () => http.get('/customer/orders'), + refundOrder: (id, payload) => http.put(`/customer/orders/${id}/refund`, payload), + updateOrderAddress: (id, payload) => http.put(`/customer/orders/${id}/address`, payload), + deleteOrder: (id) => http.delete(`/customer/orders/${id}`), + orderLogistics: (id) => http.get(`/customer/orders/${id}/logistics`), + customerFavorites: () => http.get('/customer/favorites'), + customerFavoriteViews: () => http.get('/customer/favorites/views'), + addFavorite: (payload) => http.post('/customer/favorites', payload), + deleteFavorite: (productId) => http.delete(`/customer/favorites/${productId}`), + addReview: (payload) => http.post('/customer/reviews', payload), + orderItems: (orderId) => http.get(`/customer/orders/${orderId}/items`), + applyMerchant: (payload) => http.post('/customer/merchant-applications', payload), - merchantOverview: () => http.get('/api/merchant/overview'), - merchantProducts: () => http.get('/api/merchant/products'), - saveMerchantProduct: (payload) => http.post('/api/merchant/products', payload), - deleteMerchantProduct: (id) => http.delete(`/api/merchant/products/${id}`), - merchantOrders: () => http.get('/api/merchant/orders'), - shipOrder: (id, payload) => http.put(`/api/merchant/orders/${id}/ship`, payload), - merchantRefund: (id, payload) => http.put(`/api/merchant/orders/${id}/refund`, payload), - merchantReviews: () => http.get('/api/merchant/reviews'), - merchantLogistics: () => http.get('/api/merchant/logistics'), - merchantInventory: () => http.get('/api/merchant/inventory'), - deleteMerchantInventory: (id) => http.delete(`/api/merchant/inventory/${id}`), + merchantOverview: () => http.get('/merchant/overview'), + merchantProducts: () => http.get('/merchant/products'), + saveMerchantProduct: (payload) => http.post('/merchant/products', payload), + deleteMerchantProduct: (id) => http.delete(`/merchant/products/${id}`), + merchantOrders: () => http.get('/merchant/orders'), + shipOrder: (id, payload) => http.put(`/merchant/orders/${id}/ship`, payload), + merchantRefund: (id, payload) => http.put(`/merchant/orders/${id}/refund`, payload), + merchantReviews: () => http.get('/merchant/reviews'), + merchantLogistics: () => http.get('/merchant/logistics'), + merchantInventory: () => http.get('/merchant/inventory'), + deleteMerchantInventory: (id) => http.delete(`/merchant/inventory/${id}`), - adminOverview: () => http.get('/api/admin/overview'), - adminUsers: () => http.get('/api/admin/users'), - adminSaveUser: (payload) => http.post('/api/admin/users', payload), - adminDeleteUser: (id) => http.delete(`/api/admin/users/${id}`), - adminOrders: () => http.get('/api/admin/orders'), - adminUpdateOrder: (id, payload) => http.put(`/api/admin/orders/${id}`, payload), - adminOrderRisks: () => http.get('/api/admin/orders/risk'), - adminAuditRefund: (id, payload) => http.put(`/api/admin/orders/${id}/refund-audit`, payload), - adminAuditShipment: (id, payload) => http.put(`/api/admin/orders/${id}/ship-audit`, payload), - adminMerchantApplications: () => http.get('/api/admin/merchant-applications'), - adminAuditMerchantApplication: (id, payload) => http.put(`/api/admin/merchant-applications/${id}`, payload), - adminBanners: () => http.get('/api/admin/banners'), - adminSaveBanner: (payload) => http.post('/api/admin/banners', payload), - adminDeleteBanner: (id) => http.delete(`/api/admin/banners/${id}`), - adminProducts: () => http.get('/api/admin/products'), - adminProductViews: () => http.get('/api/admin/products/views'), - adminSaveProduct: (payload) => http.post('/api/admin/products', payload), - adminApproveProduct: (id, payload) => http.put(`/api/admin/products/${id}/approve`, payload), - adminDeleteProduct: (id) => http.delete(`/api/admin/products/${id}`), - adminReviews: () => http.get('/api/admin/reviews'), - adminLogistics: () => http.get('/api/admin/logistics'), - adminInventory: () => http.get('/api/admin/inventory'), + adminOverview: () => http.get('/admin/overview'), + adminUsers: () => http.get('/admin/users'), + adminSaveUser: (payload) => http.post('/admin/users', payload), + adminDeleteUser: (id) => http.delete(`/admin/users/${id}`), + adminOrders: () => http.get('/admin/orders'), + adminUpdateOrder: (id, payload) => http.put(`/admin/orders/${id}`, payload), + adminOrderRisks: () => http.get('/admin/orders/risk'), + adminAuditRefund: (id, payload) => http.put(`/admin/orders/${id}/refund-audit`, payload), + adminAuditShipment: (id, payload) => http.put(`/admin/orders/${id}/ship-audit`, payload), + adminMerchantApplications: () => http.get('/admin/merchant-applications'), + adminAuditMerchantApplication: (id, payload) => http.put(`/admin/merchant-applications/${id}`, payload), + adminBanners: () => http.get('/admin/banners'), + adminSaveBanner: (payload) => http.post('/admin/banners', payload), + adminDeleteBanner: (id) => http.delete(`/admin/banners/${id}`), + adminProducts: () => http.get('/admin/products'), + adminProductViews: () => http.get('/admin/products/views'), + adminSaveProduct: (payload) => http.post('/admin/products', payload), + adminApproveProduct: (id, payload) => http.put(`/admin/products/${id}/approve`, payload), + adminDeleteProduct: (id) => http.delete(`/admin/products/${id}`), + adminReviews: () => http.get('/admin/reviews'), + adminLogistics: () => http.get('/admin/logistics'), + adminInventory: () => http.get('/admin/inventory'), - // File Upload uploadImage: (file) => { const formData = new FormData() formData.append('file', file) @@ -74,7 +73,7 @@ export const api = { uploadFile: (file) => { const formData = new FormData() formData.append('file', file) - return http.post('/api/upload', formData, { + return http.post('/upload', formData, { headers: { 'Content-Type': 'multipart/form-data' } }) } diff --git a/frontend/src/views/admin/AdminBanners.vue b/frontend/src/views/admin/AdminBanners.vue index 0b0c182..834a400 100644 --- a/frontend/src/views/admin/AdminBanners.vue +++ b/frontend/src/views/admin/AdminBanners.vue @@ -109,13 +109,22 @@ const beforeUpload = (file) => { return true } -const onUploadSuccess = (response) => { - if (response && response.data) { - bannerForm.imageUrl = response.data - Message.success('图片上传成功') - } else if (typeof response === 'string') { - bannerForm.imageUrl = response +const onUploadSuccess = (uploadInfo) => { + const response = uploadInfo.response + console.log('Upload server response:', response) + if (response && response.code === 0 && response.data) { + const imageUrl = response.data + console.log('Image URL from response:', imageUrl) + if (imageUrl.startsWith('/')) { + bannerForm.imageUrl = window.location.origin + imageUrl + } else { + bannerForm.imageUrl = imageUrl + } + console.log('Final imageUrl:', bannerForm.imageUrl) Message.success('图片上传成功') + } else { + console.warn('Unexpected response format:', response) + Message.warning('上传成功,但无法解析图片地址') } } diff --git a/frontend/src/views/admin/AdminProducts.vue b/frontend/src/views/admin/AdminProducts.vue index b54f67d..5053b11 100644 --- a/frontend/src/views/admin/AdminProducts.vue +++ b/frontend/src/views/admin/AdminProducts.vue @@ -145,13 +145,22 @@ const beforeUpload = (file) => { return true } -const onUploadSuccess = (response) => { - if (response && response.data) { - productForm.imageUrl = response.data - Message.success('图片上传成功') - } else if (typeof response === 'string') { - productForm.imageUrl = response +const onUploadSuccess = (uploadInfo) => { + const response = uploadInfo.response + console.log('Upload server response:', response) + if (response && response.code === 0 && response.data) { + const imageUrl = response.data + console.log('Image URL from response:', imageUrl) + if (imageUrl.startsWith('/')) { + productForm.imageUrl = window.location.origin + imageUrl + } else { + productForm.imageUrl = imageUrl + } + console.log('Final imageUrl:', productForm.imageUrl) Message.success('图片上传成功') + } else { + console.warn('Unexpected response format:', response) + Message.warning('上传成功,但无法解析图片地址') } } diff --git a/frontend/src/views/merchant/MerchantProducts.vue b/frontend/src/views/merchant/MerchantProducts.vue index fb1a05b..073f533 100644 --- a/frontend/src/views/merchant/MerchantProducts.vue +++ b/frontend/src/views/merchant/MerchantProducts.vue @@ -129,13 +129,22 @@ const beforeUpload = (file) => { return true } -const onUploadSuccess = (response) => { - if (response && response.data) { - productForm.imageUrl = response.data - Message.success('图片上传成功') - } else if (typeof response === 'string') { - productForm.imageUrl = response +const onUploadSuccess = (uploadInfo) => { + const response = uploadInfo.response + console.log('Upload server response:', response) + if (response && response.code === 0 && response.data) { + const imageUrl = response.data + console.log('Image URL from response:', imageUrl) + if (imageUrl.startsWith('/')) { + productForm.imageUrl = window.location.origin + imageUrl + } else { + productForm.imageUrl = imageUrl + } + console.log('Final imageUrl:', productForm.imageUrl) Message.success('图片上传成功') + } else { + console.warn('Unexpected response format:', response) + Message.warning('上传成功,但无法解析图片地址') } } diff --git a/frontend/vite.config.js b/frontend/vite.config.js index d048596..ddc6f5e 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -4,7 +4,7 @@ import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], server: { - port: 5173, + port: 5174, proxy: { '/api': { target: 'http://localhost:8080',