后端功能增强:全局异常处理、API控制器、JSP视图和单元测试

- 添加 GlobalExceptionHandler 全局异常处理
- 添加 ApiController REST API 控制器
- 更新 WebConfig 跨域配置和 ProductRepository 查询方法
- 新增 monitor/product-detail/profile JSP 视图页面
- 添加 FlashSaleServiceTest 秒杀服务单元测试
- 更新 application.yml 配置
This commit is contained in:
2026-03-05 20:30:48 +08:00
parent 923e877759
commit 989c2741a2
63 changed files with 15508 additions and 1 deletions

View File

@@ -0,0 +1,42 @@
import { request } from '../request'
import type { ApiResponse, CartItem } from '@/types/api'
export const cartApi = {
// 获取购物车
getCart(): Promise<ApiResponse<CartItem[]>> {
return request.get('/api/cart')
},
// 添加到购物车
addToCart(data: {
productId: number;
quantity: number
}): Promise<ApiResponse> {
return request.post('/api/cart/add', data)
},
// 更新数量
updateQuantity(itemId: string, quantity: number): Promise<ApiResponse> {
return request.put(`/api/cart/item/${itemId}`, { quantity })
},
// 删除商品
removeItem(itemId: string): Promise<ApiResponse> {
return request.delete(`/api/cart/item/${itemId}`)
},
// 批量删除
batchRemove(ids: string[]): Promise<ApiResponse> {
return request.post('/api/cart/batch-remove', { ids })
},
// 清空购物车
clearCart(): Promise<ApiResponse> {
return request.delete('/api/cart/clear')
},
// 获取购物车数量
getCount(): Promise<ApiResponse<{ count: number }>> {
return request.get('/api/cart/count')
},
}

View File

@@ -0,0 +1,42 @@
import { request } from '../request'
import type { ApiResponse, FlashSale, PageParams, PageResponse } from '@/types/api'
export const flashsaleApi = {
// 获取秒杀活动列表
getList(params?: PageParams & { status?: string }): Promise<ApiResponse<PageResponse<FlashSale>>> {
return request.get('/api/flashsale/list', params)
},
// 获取正在进行的秒杀活动
getActive(limit?: number): Promise<ApiResponse<FlashSale[]>> {
return request.get('/api/flashsale/active', { limit })
},
// 获取秒杀活动详情
getDetail(id: number): Promise<ApiResponse<FlashSale>> {
return request.get(`/api/flashsale/${id}`)
},
// 参与秒杀
participate(data: {
flashSaleId: number;
quantity: number;
timestamp?: number;
}): Promise<ApiResponse<{ orderId: number }>> {
return request.post('/api/flashsale/participate', data)
},
// 获取用户参与记录
getUserRecords(): Promise<ApiResponse<any[]>> {
return request.get('/api/flashsale/user-records')
},
// 检查用户是否可以参与
checkEligibility(flashSaleId: number): Promise<ApiResponse<{
eligible: boolean;
reason?: string;
remainingQuota?: number;
}>> {
return request.get(`/api/flashsale/${flashSaleId}/check-eligibility`)
},
}

View File

@@ -0,0 +1,57 @@
import { request } from '../request'
import type { ApiResponse, Order, PageParams, PageResponse } from '@/types/api'
export const orderApi = {
// 创建订单
create(data: {
items: Array<{ productId: number; quantity: number }>
addressId?: number
remark?: string
}): Promise<ApiResponse<Order>> {
return request.post('/api/order/create', data)
},
// 获取订单列表
getList(params?: PageParams & {
status?: string
}): Promise<ApiResponse<PageResponse<Order>>> {
return request.get('/api/order/list', params)
},
// 获取订单详情
getDetail(id: number): Promise<ApiResponse<Order>> {
return request.get(`/api/order/${id}`)
},
// 取消订单
cancel(id: number): Promise<ApiResponse> {
return request.post(`/api/order/${id}/cancel`)
},
// 支付订单
pay(id: number, paymentMethod: string): Promise<ApiResponse> {
return request.post(`/api/order/${id}/pay`, { paymentMethod })
},
// 确认收货
confirm(id: number): Promise<ApiResponse> {
return request.post(`/api/order/${id}/confirm`)
},
// 删除订单
delete(id: number): Promise<ApiResponse> {
return request.delete(`/api/order/${id}`)
},
// 获取订单统计
getStatistics(): Promise<ApiResponse<{
total: number
pending: number
paid: number
shipped: number
completed: number
cancelled: number
}>> {
return request.get('/api/order/statistics')
},
}

View File

@@ -0,0 +1,34 @@
import { request } from '../request'
import type { ApiResponse, Product, PageParams, PageResponse } from '@/types/api'
export const productApi = {
// 获取商品列表
getList(params?: PageParams & {
keyword?: string;
category?: string;
minPrice?: number;
maxPrice?: number;
}): Promise<ApiResponse<PageResponse<Product>>> {
return request.get('/api/product/list', params)
},
// 获取热门商品
getHot(limit = 8): Promise<ApiResponse<Product[]>> {
return request.get('/api/product/hot', { limit })
},
// 获取商品详情
getDetail(id: number): Promise<ApiResponse<Product>> {
return request.get(`/api/product/${id}`)
},
// 搜索商品
search(keyword: string): Promise<ApiResponse<Product[]>> {
return request.get('/api/product/search', { keyword })
},
// 获取商品分类
getCategories(): Promise<ApiResponse<string[]>> {
return request.get('/api/product/categories')
},
}

View File

@@ -0,0 +1,34 @@
import { request } from '../request'
import type { ApiResponse, User, LoginParams, RegisterParams } from '@/types/api'
export const userApi = {
// 登录
login(params: LoginParams): Promise<ApiResponse<{ token: string; user: User }>> {
return request.post('/api/auth/login', params)
},
// 注册
register(params: RegisterParams): Promise<ApiResponse<User>> {
return request.post('/api/auth/register', params)
},
// 退出登录
logout(): Promise<ApiResponse> {
return request.post('/api/auth/logout')
},
// 获取用户信息
getInfo(): Promise<ApiResponse<User>> {
return request.get('/api/user/info')
},
// 更新用户信息
updateInfo(data: Partial<User>): Promise<ApiResponse<User>> {
return request.put('/api/user/info', data)
},
// 修改密码
changePassword(data: { oldPassword: string; newPassword: string }): Promise<ApiResponse> {
return request.post('/api/user/change-password', data)
},
}

View File

@@ -0,0 +1,118 @@
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useUserStore } from '@/stores/user'
import router from '@/router'
// 创建axios实例
const service: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: Number(import.meta.env.VITE_TIMEOUT) || 10000,
headers: {
'Content-Type': 'application/json',
},
})
// 请求拦截器
service.interceptors.request.use(
(config: AxiosRequestConfig) => {
const userStore = useUserStore()
// 添加token
if (userStore.token) {
config.headers = config.headers || {}
config.headers['Authorization'] = `Bearer ${userStore.token}`
}
return config
},
(error) => {
console.error('请求错误:', error)
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
(response: AxiosResponse) => {
const res = response.data
// 自定义状态码处理
if (res.code !== 200 && res.code !== 0) {
// 业务错误
if (res.code === 401) {
// 未登录或token失效
ElMessageBox.confirm('登录已过期,请重新登录', '提示', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
const userStore = useUserStore()
userStore.logout()
router.push('/login')
})
} else if (res.code === 403) {
// 无权限
ElMessage.error('无权限访问')
} else {
// 其他业务错误
ElMessage.error(res.message || '请求失败')
}
return Promise.reject(new Error(res.message || '请求失败'))
}
return res
},
(error) => {
console.error('响应错误:', error)
if (error.response) {
switch (error.response.status) {
case 401:
ElMessage.error('未授权,请登录')
break
case 403:
ElMessage.error('拒绝访问')
break
case 404:
ElMessage.error('请求地址不存在')
break
case 429:
ElMessage.error('请求过于频繁,请稍后再试')
break
case 500:
ElMessage.error('服务器内部错误')
break
default:
ElMessage.error(error.response.data?.message || '请求失败')
}
} else if (error.request) {
ElMessage.error('网络错误,请检查网络连接')
} else {
ElMessage.error('请求配置错误')
}
return Promise.reject(error)
}
)
// 通用请求方法
export const request = {
get<T = any>(url: string, params?: any): Promise<T> {
return service.get(url, { params })
},
post<T = any>(url: string, data?: any): Promise<T> {
return service.post(url, data)
},
put<T = any>(url: string, data?: any): Promise<T> {
return service.put(url, data)
},
delete<T = any>(url: string, params?: any): Promise<T> {
return service.delete(url, { params })
},
}
export default service