完善秒杀页面

This commit is contained in:
2025-07-04 22:45:57 +08:00
parent 1b8f396047
commit 5c578146c1
8 changed files with 184 additions and 87 deletions

View File

@@ -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
@@ -133,3 +157,10 @@ 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
## 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

View File

@@ -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('已加载测试商品数据');
}
});
}
});
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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>
<!-- 系统特性介绍 -->