在当今电子商务高度发达的时代,线上购物已成为主流消费方式。然而,传统电商平台普遍存在一个核心痛点:用户无法在购买前获得真实的产品体验感。面对这一行业挑战,我们开发了一款创新的沉浸式商品体验平台,通过Vue与Spring Boot技术栈的深度整合,重新定义了线上商品交互的标准。
系统架构与技术栈
该平台采用前后端分离的微服务架构设计,前端基于Vue.js生态体系构建,后端依托Spring Boot框架提供稳定的API服务。这种架构选择不仅保证了系统的高可维护性,还实现了技术栈的现代化升级。
前端技术栈:
- Vue 3.0 + Vue Router + Vuex状态管理
- Element Plus UI组件库
- Axios HTTP客户端
- ECharts数据可视化
后端技术栈:
- Spring Boot 2.7 + Spring MVC + MyBatis Plus
- MySQL 8.0关系型数据库
- Redis缓存服务器
- Maven项目管理工具
- Hutool工具库
项目配置文件展示了完整的技术集成方案:
# 数据库连接配置
spring.datasource.url=jdbc:mysql://192.168.99.4:3306/vue_productexp?useSSL=false&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true
spring.datasource.username=vue_productexp
spring.datasource.password=vue_productexp
# MyBatis Plus配置
mybatis-plus.global-config.db-config.table-prefix=t_
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# Redis缓存配置
spring.redis.port=6379
spring.redis.host=java.envdown.site
spring.redis.password=1234
# 文件上传配置
spring.servlet.multipart.max-file-size=1000MB
spring.servlet.multipart.max-request-size=1000MB
server.servlet.context-path=/vue_productexp
数据库设计亮点
平台数据库设计包含28个核心表,体现了高度规范化的设计理念。以下重点分析几个关键表的结构设计:
地址管理表(t_address)
CREATE TABLE `t_address` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`name` varchar(1000) DEFAULT NULL COMMENT '姓名',
`phone` varchar(1000) DEFAULT NULL COMMENT '联系电话',
`address` varchar(1000) DEFAULT NULL COMMENT '具体位置',
`keyong` tinyint(1) DEFAULT NULL COMMENT '是否可用',
`moren` tinyint(1) DEFAULT NULL COMMENT '是否默认',
`bz` varchar(1000) DEFAULT NULL COMMENT '备注',
`user_id` int(11) DEFAULT NULL COMMENT '所属用户',
`add_time` datetime DEFAULT NULL COMMENT '插入数据库时间',
PRIMARY KEY (`id`),
KEY `FK5220625006534175546` (`user_id`),
CONSTRAINT `FK5220625006534175546` FOREIGN KEY (`user_id`) REFERENCES `t_user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='地址管理'
设计亮点分析:
- 使用
utf8mb4_unicode_ci字符集,完美支持emoji和生僻字存储 tinyint(1)字段优化布尔值存储,相比varchar节省75%空间- 外键约束确保数据引用完整性,防止孤儿记录产生
- 复合索引设计支持高效的用户地址查询
订单管理表(t_orders)
CREATE TABLE `t_orders` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`pdetail` varchar(1000) DEFAULT NULL COMMENT '购买商品详情',
`total` double DEFAULT NULL COMMENT '订单总金额',
`address` varchar(1000) DEFAULT NULL COMMENT '收货地址',
`keeper` varchar(1000) DEFAULT NULL COMMENT '收货人',
`phone` varchar(1000) DEFAULT NULL COMMENT '联系电话',
`ydanhao` varchar(1000) DEFAULT NULL COMMENT '运单号',
`comment` varchar(1000) DEFAULT NULL COMMENT '评价',
`comment_time` varchar(255) DEFAULT NULL COMMENT '评价时间',
`stime` varchar(1000) DEFAULT NULL COMMENT '下单时间',
`bz` varchar(1000) DEFAULT NULL COMMENT '备注',
`user_id` int(11) DEFAULT NULL COMMENT '购买人',
`orderStatus_id` int(11) DEFAULT NULL COMMENT '订单状态',
`add_time` datetime DEFAULT NULL COMMENT '插入数据库时间',
PRIMARY KEY (`id`),
KEY `FK7581838138783766629` (`user_id`),
KEY `FK2263803048440309603` (`orderStatus_id`),
CONSTRAINT `FK2263803048440309603` FOREIGN KEY (`orderStatus_id`) REFERENCES `t_orderstatus` (`id`),
CONSTRAINT `FK7581838138783766629` FOREIGN KEY (`user_id`) REFERENCES `t_user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单管理'
性能优化策略:
- 双外键索引设计支持高效的订单状态和用户查询
datetime类型精确记录时间戳,支持范围查询优化- 金额字段使用
double类型,确保计算精度 - 冗余字段设计减少联表查询,提升读取性能
角色权限表(t_role & t_user_role)
CREATE TABLE `t_role` (
`role_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`role_name` varchar(50) DEFAULT NULL COMMENT '角色名字',
`role_desc` varchar(100) DEFAULT NULL COMMENT '角色备注',
PRIMARY KEY (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='角色表'
CREATE TABLE `t_user_role` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '关联ID',
`user_id` int(11) DEFAULT NULL COMMENT '用户ID',
`role_id` int(11) DEFAULT NULL COMMENT '角色ID',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户角色表'
权限模型设计:
- 多对多关系表支持灵活的用户角色分配
- 角色表独立设计,便于权限管理的扩展
- 自增主键确保数据插入性能最优

核心功能实现
1. 沉浸式商品体验模块
平台的核心创新在于商品体验功能。通过Vue.js的响应式特性,实现了实时交互的商品预览效果。
前端体验组件实现:
<template>
<div class="product-experience">
<div class="product-viewer">
<canvas ref="productCanvas" @mousemove="handleMouseMove"></canvas>
</div>
<div class="control-panel">
<el-slider v-model="rotationX" :min="0" :max="360" @change="updateProductView" />
<el-slider v-model="rotationY" :min="0" :max="360" @change="updateProductView" />
<el-color-picker v-model="productColor" @change="updateProductColor" />
</div>
</div>
</template>
<script>
export default {
data() {
return {
rotationX: 0,
rotationY: 0,
productColor: '#1890ff',
productModel: null
}
},
methods: {
async loadProductModel(productId) {
const response = await this.$api.get(`/product/model/${productId}`)
this.productModel = response.data
this.renderProduct()
},
updateProductView() {
this.renderProduct()
},
renderProduct() {
const canvas = this.$refs.productCanvas
const ctx = canvas.getContext('2d')
// 3D渲染逻辑实现
this.render3DModel(ctx, this.productModel, {
rotationX: this.rotationX,
rotationY: this.rotationY,
color: this.productColor
})
}
}
}
</script>
后端商品模型接口:
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private ProductService productService;
@GetMapping("/model/{productId}")
@RequireLoginWithToken
public Result<ProductModelDTO> getProductModel(@PathVariable Integer productId) {
try {
ProductModelDTO model = productService.getProductModelById(productId);
return Result.success(model);
} catch (Exception e) {
log.error("获取商品模型失败: {}", e.getMessage());
return Result.error(ResultCode.PRODUCT_MODEL_NOT_FOUND);
}
}
@PostMapping("/experience/record")
@RequireLoginWithToken
public Result recordUserExperience(@RequestBody ExperienceRecord record) {
productService.recordUserExperience(record);
return Result.success("体验记录保存成功");
}
}

2. 智能地址管理系统
地址管理模块采用层级化的设计理念,支持多地址管理和默认地址设置。
地址控制器核心实现:
@Controller
@RequestMapping("/address")
public class AddressController {
@Resource
private AddressService addressService;
@Resource
private UserService userService;
@RequestMapping("/list")
@ResponseBody
@RequireLoginWithToken
public Result<List<Address>> getAddressList(HttpServletRequest request) {
try {
Integer userId = JwtUtil.getUserIdFromToken(request);
List<Address> addresses = addressService.getUserAddresses(userId);
return Result.success(addresses);
} catch (Exception e) {
log.error("获取地址列表失败: {}", e.getMessage());
return Result.error(ResultCode.ADDRESS_QUERY_FAILED);
}
}
@PostMapping("/setDefault")
@ResponseBody
@RequireLoginWithToken
public Result setDefaultAddress(@RequestParam Integer addressId,
HttpServletRequest request) {
try {
Integer userId = JwtUtil.getUserIdFromToken(request);
addressService.setDefaultAddress(userId, addressId);
return Result.success("默认地址设置成功");
} catch (DataIntegrityViolationException e) {
log.error("地址数据完整性错误: {}", e.getMessage());
return Result.error(ResultCode.DATA_INTEGRITY_ERROR);
}
}
}
地址服务层业务逻辑:
@Service
public class AddressService {
@Autowired
private AddressMapper addressMapper;
public List<Address> getUserAddresses(Integer userId) {
MPJLambdaWrapper<Address> wrapper = new MPJLambdaWrapper<Address>()
.eq(Address::getUserId, userId)
.eq(Address::getKeyong, 1)
.orderByDesc(Address::getMoren)
.orderByDesc(Address::getAddTime);
return addressMapper.selectList(wrapper);
}
@Transactional
public void setDefaultAddress(Integer userId, Integer addressId) {
// 取消当前所有默认地址
addressMapper.cancelAllDefaultAddresses(userId);
// 设置新的默认地址
Address address = new Address();
address.setId(addressId);
address.setMoren(1);
addressMapper.updateById(address);
}
}

3. 订单流程管理系统
订单系统采用状态机模式管理订单生命周期,确保业务流程的严谨性。
订单状态转换服务:
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderStatusService statusService;
@Transactional
public void processOrderStatus(Integer orderId, OrderStatus newStatus) {
Order order = orderMapper.selectById(orderId);
OrderStatus currentStatus = statusService.getStatusById(order.getOrderStatusId());
if (!canTransition(currentStatus, newStatus)) {
throw new BusinessException("订单状态转换不合法");
}
// 更新订单状态
order.setOrderStatusId(newStatus.getId());
orderMapper.updateById(order);
// 记录状态变更日志
recordStatusChange(orderId, currentStatus, newStatus);
}
private boolean canTransition(OrderStatus from, OrderStatus to) {
// 定义状态转换规则
Map<OrderStatus, List<OrderStatus>> transitionRules = new HashMap<>();
transitionRules.put(OrderStatus.PENDING,
Arrays.asList(OrderStatus.CONFIRMED, OrderStatus.CANCELLED));
transitionRules.put(OrderStatus.CONFIRMED,
Arrays.asList(OrderStatus.SHIPPED, OrderStatus.CANCELLED));
// ... 更多状态转换规则
return transitionRules.getOrDefault(from, Collections.emptyList())
.contains(to);
}
}
订单查询优化实现:
public Page<OrderDTO> getUserOrders(Integer userId, PageModel pageModel) {
MPJLambdaWrapper<Order> wrapper = new MPJLambdaWrapper<Order>()
.selectAll(Order.class)
.selectAs(OrderStatus::getStatusName, OrderDTO::getStatusName)
.selectAs(User::getUsername, OrderDTO::getUsername)
.leftJoin(OrderStatus.class, OrderStatus::getId, Order::getOrderStatusId)
.leftJoin(User.class, User::getId, Order::getUserId)
.eq(Order::getUserId, userId)
.orderByDesc(Order::getAddTime);
return orderMapper.selectJoinPage(new Page<>(pageModel.getPage(), pageModel.getSize()),
OrderDTO.class, wrapper);
}

4. 权限控制系统
基于RBAC模型的权限控制系统,支持细粒度的访问控制。
权限拦截器实现:
@Component
public class PermissionInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("Authorization");
if (StringUtils.isBlank(token)) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
return false;
}
Integer userId = JwtUtil.getUserIdFromToken(token);
User user = userService.getUserWithRoles(userId);
String requestURI = request.getRequestURI();
String method = request.getMethod();
if (!hasPermission(user, requestURI, method)) {
response.setStatus(HttpStatus.FORBIDDEN.value());
return false;
}
return true;
}
private boolean hasPermission(User user, String uri, String method) {
for (Role role : user.getRoles()) {
for (Permission permission : role.getPermissions()) {
if (permission.matches(uri, method)) {
return true;
}
}
}
return false;
}
}
实体模型设计
系统采用领域驱动设计(DDD)理念,核心实体模型设计体现了业务领域的完整性。
用户聚合根设计:
@Data
public class User {
private Integer id;
private String username;
private String password;
private String email;
private String phone;
private Integer status;
private Timestamp addTime;
// 值对象集合
private List<Address> addresses;
private List<Order> orders;
private Set<Role> roles;
public boolean canPlaceOrder() {
return status == 1 && hasValidAddress();
}
private boolean hasValidAddress() {
return addresses.stream()
.anyMatch(addr -> addr.getKeyong() == 1);
}
}
商品领域服务:
@Service
public class ProductDomainService {
public ProductExperience createProductExperience(Product product, User user) {
ProductExperience experience = new ProductExperience();
experience.setProductId(product.getId());
experience.setUserId(user.getId());
experience.setStartTime(new Timestamp(System.currentTimeMillis()));
experience.setExperienceData(buildInitialExperienceData(product));
// 验证用户体验权限
if (!user.hasExperiencePermission(product.getCategory())) {
throw new BusinessException("用户无体验权限");
}
return experience;
}
}
功能展望与优化
基于当前系统架构,提出以下优化方向:
1. 微服务架构改造
将单体应用拆分为商品服务、订单服务、用户服务等独立微服务,提升系统可扩展性。
服务拆分方案:
// 商品服务独立部署
@SpringBootApplication
@EnableEurekaClient
public class ProductServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ProductServiceApplication.class, args);
}
}
// 使用Feign客户端进行服务间调用
@FeignClient(name = "order-service", path = "/api/orders")
public interface OrderServiceClient {
@PostMapping("/create")
OrderDTO createOrder(@RequestBody OrderCreateRequest request);
}
2. 引入Elasticsearch搜索引擎
提升商品搜索性能和相关性排序能力。
搜索服务集成:
@Service
public class ProductSearchService {
@Autowired
private ElasticsearchRestTemplate elasticsearchTemplate;
public Page<ProductDocument> searchProducts(ProductSearchRequest request) {
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 构建复杂查询条件
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.must(QueryBuilders.matchQuery("name", request.getKeyword()));
boolQuery.filter(QueryBuilders.rangeQuery("price")
.gte(request.getMinPrice()).lte(request.getMaxPrice()));
queryBuilder.with