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.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<String> PUBLIC_PREFIX = Set.of(
private static final java.util.List<String> 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;

View File

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

View File

@@ -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

View File

@@ -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' }
})
}

View File

@@ -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('上传成功,但无法解析图片地址')
}
}

View File

@@ -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('上传成功,但无法解析图片地址')
}
}

View File

@@ -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('上传成功,但无法解析图片地址')
}
}

View File

@@ -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',