fix: 修复图片上传回显、登录认证和API路径问题

- 修复上传图片响应解析,正确处理 Arco Upload 返回的 response 对象
- 修复后端 AuthInterceptor 路径匹配,正确放行 /api/auth/login 等公开接口
- 统一前端 API 路径配置,移除重复 /api 前缀
- 添加 /uploads 静态资源代理配置
- 修复图片 URL 生成,添加 origin 前缀确保回显正常
This commit is contained in:
wangziqi
2026-02-11 09:10:29 +08:00
parent 17e9a5b42b
commit 1df27d3a23
8 changed files with 120 additions and 82 deletions

View File

@@ -9,7 +9,8 @@ import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
import java.util.Set; import java.util.Arrays;
import java.util.List;
@Component @Component
public class AuthInterceptor implements HandlerInterceptor { public class AuthInterceptor implements HandlerInterceptor {
@@ -18,7 +19,7 @@ public class AuthInterceptor implements HandlerInterceptor {
@Value("${app.token-header:X-Token}") @Value("${app.token-header:X-Token}")
private String tokenHeader; private String tokenHeader;
private static final Set<String> PUBLIC_PREFIX = Set.of( private static final java.util.List<String> PUBLIC_PATHS = java.util.Arrays.asList(
"/api/auth/login", "/api/auth/login",
"/api/auth/register", "/api/auth/register",
"/api/public" "/api/public"
@@ -37,17 +38,23 @@ public class AuthInterceptor implements HandlerInterceptor {
} }
String uri = request.getRequestURI(); 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; return true;
} }
String token = request.getHeader(tokenHeader); String token = request.getHeader(tokenHeader);
if (token == null || token.isBlank()) { if (token == null || token.isBlank()) {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(401); response.setStatus(401);
response.getWriter().write("{\"code\":401,\"message\":\"请先登录\",\"data\":null}"); response.getWriter().write("{\"code\":401,\"message\":\"请先登录\",\"data\":null}");
return false; return false;
} }
User user = userRepository.findByToken(token).orElse(null); User user = userRepository.findByToken(token).orElse(null);
if (user == null || !Boolean.TRUE.equals(user.getEnabled())) { if (user == null || !Boolean.TRUE.equals(user.getEnabled())) {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(401); response.setStatus(401);
response.getWriter().write("{\"code\":401,\"message\":\"登录状态无效\",\"data\":null}"); response.getWriter().write("{\"code\":401,\"message\":\"登录状态无效\",\"data\":null}");
return false; return false;

View File

@@ -22,7 +22,7 @@ public class FileUploadController {
@Value("${app.upload-url-prefix:/uploads}") @Value("${app.upload-url-prefix:/uploads}")
private String uploadUrlPrefix; private String uploadUrlPrefix;
@PostMapping("/upload") @PostMapping("/upload/file")
public ApiResponse<String> uploadFile(@RequestParam("file") MultipartFile file) { public ApiResponse<String> uploadFile(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) { if (file.isEmpty()) {
return ApiResponse.fail("请选择要上传的文件", String.class); return ApiResponse.fail("请选择要上传的文件", String.class);

View File

@@ -15,6 +15,11 @@ spring:
format_sql: true format_sql: true
jackson: jackson:
time-zone: Asia/Shanghai time-zone: Asia/Shanghai
servlet:
multipart:
max-file-size: 10MB
max-request-size: 100MB
app: app:
token-header: X-Token token-header: X-Token
upload-path: /Users/apple/code/bs/mying/backend/uploads

View File

@@ -1,69 +1,68 @@
import http from './http' import http from './http'
export const api = { export const api = {
register: (payload) => http.post('/api/auth/register', payload), register: (payload) => http.post('/auth/register', payload),
login: (payload) => http.post('/api/auth/login', payload), login: (payload) => http.post('/auth/login', payload),
me: () => http.get('/api/auth/me'), me: () => http.get('/auth/me'),
updateMe: (payload) => http.put('/api/auth/me', payload), updateMe: (payload) => http.put('/auth/me', payload),
banners: () => http.get('/api/public/banners'), banners: () => http.get('/public/banners'),
products: (keyword = '') => http.get('/api/public/products', { params: { keyword } }), products: (keyword = '') => http.get('/public/products', { params: { keyword } }),
customerCart: () => http.get('/api/customer/cart'), customerCart: () => http.get('/customer/cart'),
customerCartViews: () => http.get('/api/customer/cart/views'), customerCartViews: () => http.get('/customer/cart/views'),
addCart: (payload) => http.post('/api/customer/cart', payload), addCart: (payload) => http.post('/customer/cart', payload),
delCart: (productId) => http.delete(`/api/customer/cart/${productId}`), delCart: (productId) => http.delete(`/customer/cart/${productId}`),
checkout: (payload) => http.post('/api/customer/orders/checkout', payload), checkout: (payload) => http.post('/customer/orders/checkout', payload),
customerBuyNow: (payload) => http.post('/api/customer/orders/buy-now', payload), customerBuyNow: (payload) => http.post('/customer/orders/buy-now', payload),
customerOrders: () => http.get('/api/customer/orders'), customerOrders: () => http.get('/customer/orders'),
refundOrder: (id, payload) => http.put(`/api/customer/orders/${id}/refund`, payload), refundOrder: (id, payload) => http.put(`/customer/orders/${id}/refund`, payload),
updateOrderAddress: (id, payload) => http.put(`/api/customer/orders/${id}/address`, payload), updateOrderAddress: (id, payload) => http.put(`/customer/orders/${id}/address`, payload),
deleteOrder: (id) => http.delete(`/api/customer/orders/${id}`), deleteOrder: (id) => http.delete(`/customer/orders/${id}`),
orderLogistics: (id) => http.get(`/api/customer/orders/${id}/logistics`), orderLogistics: (id) => http.get(`/customer/orders/${id}/logistics`),
customerFavorites: () => http.get('/api/customer/favorites'), customerFavorites: () => http.get('/customer/favorites'),
customerFavoriteViews: () => http.get('/api/customer/favorites/views'), customerFavoriteViews: () => http.get('/customer/favorites/views'),
addFavorite: (payload) => http.post('/api/customer/favorites', payload), addFavorite: (payload) => http.post('/customer/favorites', payload),
deleteFavorite: (productId) => http.delete(`/api/customer/favorites/${productId}`), deleteFavorite: (productId) => http.delete(`/customer/favorites/${productId}`),
addReview: (payload) => http.post('/api/customer/reviews', payload), addReview: (payload) => http.post('/customer/reviews', payload),
orderItems: (orderId) => http.get(`/api/customer/orders/${orderId}/items`), orderItems: (orderId) => http.get(`/customer/orders/${orderId}/items`),
applyMerchant: (payload) => http.post('/api/customer/merchant-applications', payload), applyMerchant: (payload) => http.post('/customer/merchant-applications', payload),
merchantOverview: () => http.get('/api/merchant/overview'), merchantOverview: () => http.get('/merchant/overview'),
merchantProducts: () => http.get('/api/merchant/products'), merchantProducts: () => http.get('/merchant/products'),
saveMerchantProduct: (payload) => http.post('/api/merchant/products', payload), saveMerchantProduct: (payload) => http.post('/merchant/products', payload),
deleteMerchantProduct: (id) => http.delete(`/api/merchant/products/${id}`), deleteMerchantProduct: (id) => http.delete(`/merchant/products/${id}`),
merchantOrders: () => http.get('/api/merchant/orders'), merchantOrders: () => http.get('/merchant/orders'),
shipOrder: (id, payload) => http.put(`/api/merchant/orders/${id}/ship`, payload), shipOrder: (id, payload) => http.put(`/merchant/orders/${id}/ship`, payload),
merchantRefund: (id, payload) => http.put(`/api/merchant/orders/${id}/refund`, payload), merchantRefund: (id, payload) => http.put(`/merchant/orders/${id}/refund`, payload),
merchantReviews: () => http.get('/api/merchant/reviews'), merchantReviews: () => http.get('/merchant/reviews'),
merchantLogistics: () => http.get('/api/merchant/logistics'), merchantLogistics: () => http.get('/merchant/logistics'),
merchantInventory: () => http.get('/api/merchant/inventory'), merchantInventory: () => http.get('/merchant/inventory'),
deleteMerchantInventory: (id) => http.delete(`/api/merchant/inventory/${id}`), deleteMerchantInventory: (id) => http.delete(`/merchant/inventory/${id}`),
adminOverview: () => http.get('/api/admin/overview'), adminOverview: () => http.get('/admin/overview'),
adminUsers: () => http.get('/api/admin/users'), adminUsers: () => http.get('/admin/users'),
adminSaveUser: (payload) => http.post('/api/admin/users', payload), adminSaveUser: (payload) => http.post('/admin/users', payload),
adminDeleteUser: (id) => http.delete(`/api/admin/users/${id}`), adminDeleteUser: (id) => http.delete(`/admin/users/${id}`),
adminOrders: () => http.get('/api/admin/orders'), adminOrders: () => http.get('/admin/orders'),
adminUpdateOrder: (id, payload) => http.put(`/api/admin/orders/${id}`, payload), adminUpdateOrder: (id, payload) => http.put(`/admin/orders/${id}`, payload),
adminOrderRisks: () => http.get('/api/admin/orders/risk'), adminOrderRisks: () => http.get('/admin/orders/risk'),
adminAuditRefund: (id, payload) => http.put(`/api/admin/orders/${id}/refund-audit`, payload), adminAuditRefund: (id, payload) => http.put(`/admin/orders/${id}/refund-audit`, payload),
adminAuditShipment: (id, payload) => http.put(`/api/admin/orders/${id}/ship-audit`, payload), adminAuditShipment: (id, payload) => http.put(`/admin/orders/${id}/ship-audit`, payload),
adminMerchantApplications: () => http.get('/api/admin/merchant-applications'), adminMerchantApplications: () => http.get('/admin/merchant-applications'),
adminAuditMerchantApplication: (id, payload) => http.put(`/api/admin/merchant-applications/${id}`, payload), adminAuditMerchantApplication: (id, payload) => http.put(`/admin/merchant-applications/${id}`, payload),
adminBanners: () => http.get('/api/admin/banners'), adminBanners: () => http.get('/admin/banners'),
adminSaveBanner: (payload) => http.post('/api/admin/banners', payload), adminSaveBanner: (payload) => http.post('/admin/banners', payload),
adminDeleteBanner: (id) => http.delete(`/api/admin/banners/${id}`), adminDeleteBanner: (id) => http.delete(`/admin/banners/${id}`),
adminProducts: () => http.get('/api/admin/products'), adminProducts: () => http.get('/admin/products'),
adminProductViews: () => http.get('/api/admin/products/views'), adminProductViews: () => http.get('/admin/products/views'),
adminSaveProduct: (payload) => http.post('/api/admin/products', payload), adminSaveProduct: (payload) => http.post('/admin/products', payload),
adminApproveProduct: (id, payload) => http.put(`/api/admin/products/${id}/approve`, payload), adminApproveProduct: (id, payload) => http.put(`/admin/products/${id}/approve`, payload),
adminDeleteProduct: (id) => http.delete(`/api/admin/products/${id}`), adminDeleteProduct: (id) => http.delete(`/admin/products/${id}`),
adminReviews: () => http.get('/api/admin/reviews'), adminReviews: () => http.get('/admin/reviews'),
adminLogistics: () => http.get('/api/admin/logistics'), adminLogistics: () => http.get('/admin/logistics'),
adminInventory: () => http.get('/api/admin/inventory'), adminInventory: () => http.get('/admin/inventory'),
// File Upload
uploadImage: (file) => { uploadImage: (file) => {
const formData = new FormData() const formData = new FormData()
formData.append('file', file) formData.append('file', file)
@@ -74,7 +73,7 @@ export const api = {
uploadFile: (file) => { uploadFile: (file) => {
const formData = new FormData() const formData = new FormData()
formData.append('file', file) formData.append('file', file)
return http.post('/api/upload', formData, { return http.post('/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' } headers: { 'Content-Type': 'multipart/form-data' }
}) })
} }

View File

@@ -109,13 +109,22 @@ const beforeUpload = (file) => {
return true return true
} }
const onUploadSuccess = (response) => { const onUploadSuccess = (uploadInfo) => {
if (response && response.data) { const response = uploadInfo.response
bannerForm.imageUrl = response.data console.log('Upload server response:', response)
Message.success('图片上传成功') if (response && response.code === 0 && response.data) {
} else if (typeof response === 'string') { const imageUrl = response.data
bannerForm.imageUrl = response 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('图片上传成功') Message.success('图片上传成功')
} else {
console.warn('Unexpected response format:', response)
Message.warning('上传成功,但无法解析图片地址')
} }
} }

View File

@@ -145,13 +145,22 @@ const beforeUpload = (file) => {
return true return true
} }
const onUploadSuccess = (response) => { const onUploadSuccess = (uploadInfo) => {
if (response && response.data) { const response = uploadInfo.response
productForm.imageUrl = response.data console.log('Upload server response:', response)
Message.success('图片上传成功') if (response && response.code === 0 && response.data) {
} else if (typeof response === 'string') { const imageUrl = response.data
productForm.imageUrl = response 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('图片上传成功') Message.success('图片上传成功')
} else {
console.warn('Unexpected response format:', response)
Message.warning('上传成功,但无法解析图片地址')
} }
} }

View File

@@ -129,13 +129,22 @@ const beforeUpload = (file) => {
return true return true
} }
const onUploadSuccess = (response) => { const onUploadSuccess = (uploadInfo) => {
if (response && response.data) { const response = uploadInfo.response
productForm.imageUrl = response.data console.log('Upload server response:', response)
Message.success('图片上传成功') if (response && response.code === 0 && response.data) {
} else if (typeof response === 'string') { const imageUrl = response.data
productForm.imageUrl = response 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('图片上传成功') Message.success('图片上传成功')
} else {
console.warn('Unexpected response format:', response)
Message.warning('上传成功,但无法解析图片地址')
} }
} }

View File

@@ -4,7 +4,7 @@ import vue from '@vitejs/plugin-vue'
export default defineConfig({ export default defineConfig({
plugins: [vue()], plugins: [vue()],
server: { server: {
port: 5173, port: 5174,
proxy: { proxy: {
'/api': { '/api': {
target: 'http://localhost:8080', target: 'http://localhost:8080',