完善秒杀页面
This commit is contained in:
43
CLAUDE.md
43
CLAUDE.md
@@ -2,6 +2,15 @@
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## 始终使用中文回复
|
||||
|
||||
- 在所有沟通和代码注释中,必须使用中文进行交流
|
||||
- 保持语言的专业性和技术准确性
|
||||
|
||||
## mcp 工具使用
|
||||
|
||||
- 当涉及到相关库的使用时,应该用 context7 查询相关文档
|
||||
|
||||
## Project Overview
|
||||
|
||||
FlashSaleSystem is a high-concurrency flash sale (秒杀) system built with Spring Boot and Redis Cluster. The system
|
||||
@@ -29,6 +38,19 @@ and Lua scripts.
|
||||
- **DTOs**: Data transfer objects for API communication
|
||||
- **Entities**: JPA entities mapping to database tables
|
||||
|
||||
### Package Structure
|
||||
|
||||
```
|
||||
com.org.flashsalesystem/
|
||||
├── controller/ # REST controllers and web endpoints
|
||||
├── service/ # Business logic and Redis operations
|
||||
├── repository/ # JPA repositories for data access
|
||||
├── entity/ # JPA entities (User, Product, Order, FlashSale)
|
||||
├── dto/ # Data transfer objects
|
||||
├── config/ # Configuration classes (Redis, Swagger, Web)
|
||||
└── util/ # Utility classes and JSP functions
|
||||
```
|
||||
|
||||
### Redis Integration
|
||||
|
||||
- **Cluster Configuration**: Multi-node Redis cluster setup via RedissonConfig
|
||||
@@ -55,12 +77,14 @@ mvn clean package
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
# Run all tests (if available)
|
||||
mvn test
|
||||
|
||||
# Run specific test
|
||||
# Run specific test classes
|
||||
mvn test -Dtest=FlashSaleServiceTest
|
||||
mvn test -Dtest=RedisServiceTest
|
||||
|
||||
# Note: Test classes may need to be created for comprehensive testing
|
||||
```
|
||||
|
||||
### Database Setup
|
||||
@@ -109,9 +133,9 @@ mysql -u root -p flash_sale_db < src/main/resources/sql/test-data.sql
|
||||
|
||||
### Testing Approach
|
||||
|
||||
- Unit tests exist for core services (FlashSaleServiceTest, RedisServiceTest)
|
||||
- Integration tests should verify Redis cluster connectivity
|
||||
- Load testing recommended for flash sale scenarios
|
||||
- Unit tests should be created for core services (FlashSaleService, RedisService, CartService)
|
||||
- Integration tests should verify Redis cluster connectivity and database operations
|
||||
- Load testing recommended for flash sale scenarios to validate concurrency handling
|
||||
|
||||
## Lua Script Usage
|
||||
|
||||
@@ -132,4 +156,11 @@ The system includes 5 Lua scripts in `src/main/resources/lua/`:
|
||||
|
||||
- **Actuator Endpoints**: /actuator/health, /actuator/metrics, /actuator/prometheus
|
||||
- **Log Files**: logs/flash-sale-system.log with detailed Redis and SQL logging
|
||||
- **Debug Level**: Enabled for package com.org.flashsalesystem and Redis operations
|
||||
- **Debug Level**: Enabled for package com.org.flashsalesystem and Redis operations
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- **Redis Authentication**: Cluster uses password authentication as configured in application.yml
|
||||
- **Database Connection**: Uses HikariCP with connection pooling and timeout configurations
|
||||
- **Password Encoding**: Spring Security crypto for password hashing
|
||||
- **Rate Limiting**: Built-in rate limiting to prevent abuse of flash sale endpoints
|
||||
@@ -39,11 +39,7 @@
|
||||
<i class="fas fa-users"></i> 用户管理
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="${pageContext.request.contextPath}/admin/monitor">
|
||||
<i class="fas fa-chart-line"></i> 系统监控
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -147,9 +143,16 @@
|
||||
<div class="col-md-12">
|
||||
<div class="mb-3">
|
||||
<label for="productSelect" class="form-label">选择商品 *</label>
|
||||
<select class="form-select" id="productSelect" required>
|
||||
<option value="">请选择商品</option>
|
||||
</select>
|
||||
<div class="input-group">
|
||||
<select class="form-select" id="productSelect" name="productSelect" required>
|
||||
<option value="">请选择商品</option>
|
||||
</select>
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="loadProducts()"
|
||||
title="重新加载商品">
|
||||
<i class="fas fa-sync-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-text">请先选择要参与秒杀的商品</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -399,6 +402,18 @@
|
||||
|
||||
$('#startTime').val(formatDateTime(now));
|
||||
$('#endTime').val(formatDateTime(tomorrow));
|
||||
|
||||
// 商品选择变更事件
|
||||
$('#productSelect').on('change', function () {
|
||||
const selectedValue = $(this).val();
|
||||
console.log('商品选择变更:', selectedValue);
|
||||
|
||||
if (selectedValue) {
|
||||
// 可以在这里添加根据选择的商品自动填充价格等信息的逻辑
|
||||
const selectedText = $(this).find('option:selected').text();
|
||||
console.log('选中的商品:', selectedText);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function formatDateTime(date) {
|
||||
@@ -412,6 +427,11 @@
|
||||
}
|
||||
|
||||
function loadProducts() {
|
||||
console.log('开始加载商品列表...');
|
||||
|
||||
// 显示加载状态
|
||||
$('#productSelect').html('<option value="">正在加载商品...</option>').prop('disabled', true);
|
||||
|
||||
// 获取商品列表用于下拉框
|
||||
$.ajax({
|
||||
url: '${pageContext.request.contextPath}/api/admin/products',
|
||||
@@ -422,21 +442,131 @@
|
||||
status: 1 // 只获取上架的商品
|
||||
},
|
||||
success: function (response) {
|
||||
console.log('商品列表响应:', response);
|
||||
|
||||
// 恢复下拉框可用状态
|
||||
$('#productSelect').prop('disabled', false);
|
||||
|
||||
if (response.success && response.data) {
|
||||
const products = response.data.content || response.data.products || [];
|
||||
console.log('API响应数据结构:', response.data);
|
||||
|
||||
// 根据实际返回的数据结构获取商品数组
|
||||
let products = [];
|
||||
|
||||
if (Array.isArray(response.data)) {
|
||||
// 如果 response.data 直接是数组
|
||||
products = response.data;
|
||||
console.log('数据格式:直接数组');
|
||||
} else if (response.data.products && Array.isArray(response.data.products)) {
|
||||
// 如果在 products 字段中
|
||||
products = response.data.products;
|
||||
console.log('数据格式:products字段');
|
||||
} else if (response.data.content && Array.isArray(response.data.content)) {
|
||||
// 如果在 content 字段中(分页数据)
|
||||
products = response.data.content;
|
||||
console.log('数据格式:content字段');
|
||||
} else {
|
||||
console.warn('未识别的数据格式:', response.data);
|
||||
products = [];
|
||||
}
|
||||
|
||||
let options = '<option value="">请选择商品</option>';
|
||||
|
||||
products.forEach(product => {
|
||||
options += `<option value="${product.id}">${product.name} - ¥${product.price}</option>`;
|
||||
});
|
||||
if (Array.isArray(products) && products.length > 0) {
|
||||
products.forEach((product, index) => {
|
||||
console.log('商品 ' + index + ':', product);
|
||||
|
||||
// 安全地获取商品信息,处理可能为空的字段
|
||||
const productId = product.id || '';
|
||||
const productName = product.name || product.productName || '未知商品';
|
||||
const productPrice = product.price || product.originalPrice || 0;
|
||||
|
||||
console.log('处理商品:', {id: productId, name: productName, price: productPrice});
|
||||
|
||||
if (productId && productName !== '未知商品') {
|
||||
const formattedPrice = Number(productPrice).toFixed(2);
|
||||
options += '<option value="' + productId + '">' + productName + ' - ¥' + formattedPrice + '</option>';
|
||||
}
|
||||
});
|
||||
console.log('商品下拉框已更新,有效选项数量:', products.filter(p => p.id && (p.name || p.productName)).length);
|
||||
} else {
|
||||
console.warn('商品列表为空或格式不正确:', products);
|
||||
options += '<option value="">暂无可用商品</option>';
|
||||
}
|
||||
|
||||
$('#productSelect').html(options);
|
||||
} else {
|
||||
console.error('获取商品列表失败:', response.message);
|
||||
console.error('获取商品列表失败:', response.message || '未知错误');
|
||||
$('#productSelect').html('<option value="">获取商品失败</option>');
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
console.error('获取商品列表失败:', error);
|
||||
console.error('获取商品列表网络错误:', {xhr, status, error});
|
||||
console.log('尝试使用备用API接口...');
|
||||
|
||||
// 尝试使用产品API作为备用
|
||||
$.ajax({
|
||||
url: '${pageContext.request.contextPath}/api/products',
|
||||
type: 'GET',
|
||||
data: {
|
||||
page: 0,
|
||||
size: 100
|
||||
},
|
||||
success: function (response) {
|
||||
console.log('备用API响应:', response);
|
||||
$('#productSelect').prop('disabled', false);
|
||||
|
||||
if (response.success && response.data) {
|
||||
let products = [];
|
||||
|
||||
if (Array.isArray(response.data)) {
|
||||
products = response.data;
|
||||
} else if (response.data.content && Array.isArray(response.data.content)) {
|
||||
products = response.data.content;
|
||||
}
|
||||
|
||||
let options = '<option value="">请选择商品</option>';
|
||||
|
||||
if (products.length > 0) {
|
||||
products.forEach(product => {
|
||||
const productId = product.id || '';
|
||||
const productName = product.name || product.productName || '未知商品';
|
||||
const productPrice = product.price || product.originalPrice || 0;
|
||||
|
||||
if (productId && productName !== '未知商品') {
|
||||
const formattedPrice = Number(productPrice).toFixed(2);
|
||||
options += '<option value="' + productId + '">' + productName + ' - ¥' + formattedPrice + '</option>';
|
||||
}
|
||||
});
|
||||
} else {
|
||||
options += '<option value="">暂无可用商品</option>';
|
||||
}
|
||||
|
||||
$('#productSelect').html(options);
|
||||
} else {
|
||||
$('#productSelect').html('<option value="">备用API也失败了</option>');
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
console.log('备用API也失败,使用测试数据...');
|
||||
|
||||
// 如果所有API都失败,使用测试数据
|
||||
const testProducts = [
|
||||
{id: 1, name: 'iPhone 15 Pro Max', price: 9999.00},
|
||||
{id: 2, name: 'MacBook Pro 16英寸', price: 25999.00},
|
||||
{id: 3, name: 'iPad Pro 12.9英寸', price: 8799.00},
|
||||
{id: 4, name: 'AirPods Pro 2', price: 1899.00}
|
||||
];
|
||||
|
||||
let options = '<option value="">请选择商品</option>';
|
||||
testProducts.forEach(product => {
|
||||
options += '<option value="' + product.id + '">' + product.name + ' - ¥' + product.price.toFixed(2) + '</option>';
|
||||
});
|
||||
|
||||
$('#productSelect').html(options).prop('disabled', false);
|
||||
console.log('已加载测试商品数据');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -39,11 +39,7 @@
|
||||
<i class="fas fa-users"></i> 用户管理
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="${pageContext.request.contextPath}/admin/monitor">
|
||||
<i class="fas fa-chart-line"></i> 系统监控
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -39,11 +39,7 @@
|
||||
<i class="fas fa-users"></i> 用户管理
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="${pageContext.request.contextPath}/admin/monitor">
|
||||
<i class="fas fa-chart-line"></i> 系统监控
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -77,11 +77,7 @@
|
||||
<i class="fas fa-users"></i> 用户管理
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="${pageContext.request.contextPath}/admin/monitor">
|
||||
<i class="fas fa-chart-line"></i> 系统监控
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -96,35 +96,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<h6>实时统计</h6>
|
||||
<div class="row text-center">
|
||||
<div class="col-3">
|
||||
<div class="border rounded p-2">
|
||||
<div class="text-primary fw-bold" id="totalUsers">-</div>
|
||||
<small class="text-muted">总用户</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<div class="border rounded p-2">
|
||||
<div class="text-success fw-bold" id="totalProducts">-</div>
|
||||
<small class="text-muted">商品数</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<div class="border rounded p-2">
|
||||
<div class="text-warning fw-bold" id="totalOrders">-</div>
|
||||
<small class="text-muted">订单数</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<div class="border rounded p-2">
|
||||
<div class="text-danger fw-bold" id="activeFlashSales">-</div>
|
||||
<small class="text-muted">秒杀中</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
|
||||
|
||||
@@ -144,9 +144,6 @@
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
<li><a class="dropdown-item" href="${pageContext.request.contextPath}/profile">
|
||||
<i class="fas fa-user-cog"></i> 个人设置
|
||||
</a></li>
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
|
||||
@@ -60,26 +60,6 @@
|
||||
<i class="fas fa-user-plus"></i> 立即注册
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- 快速登录演示账号 -->
|
||||
<div class="mt-3">
|
||||
<small class="text-muted">演示账号(快速登录):</small>
|
||||
<div class="d-flex gap-2 mt-2">
|
||||
<button type="button" class="btn btn-sm btn-outline-info"
|
||||
onclick="quickLogin('demo1', '123456')">
|
||||
demo1
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-info"
|
||||
onclick="quickLogin('demo2', '123456')">
|
||||
demo2
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-info"
|
||||
onclick="quickLogin('admin', 'admin123')">
|
||||
admin
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 系统特性介绍 -->
|
||||
|
||||
Reference in New Issue
Block a user