1. 项目概述:视康眼镜网店销售系统的技术架构与商业价值
这套基于Java+SSM+Flask的视康眼镜网店销售系统,是专为眼镜行业设计的全渠道电子商务解决方案。系统采用前后端分离架构,后端使用Java+SSM(Spring+SpringMVC+MyBatis)处理核心业务逻辑,前端则通过Flask构建灵活的管理界面,MySQL作为数据存储引擎。这种技术组合既保证了企业级应用的稳定性,又兼顾了管理后台的开发效率。
在实际运营中,系统需要支撑眼镜行业特有的业务场景:包括验光数据管理、镜片参数配置、处方适配验证等专业功能。与普通电商不同,眼镜销售涉及大量非标参数(如瞳距、镜框尺寸、镜片折射率等),这就要求系统在商品SKU管理上实现高度灵活的可配置性。我们通过自定义规格参数模板和智能匹配算法,解决了传统电商系统难以处理专业配镜需求的痛点。
2. 技术架构解析:多框架融合的设计考量
2.1 SSM框架的核心作用
Spring作为控制反转容器,管理着整个系统的Bean生命周期。特别针对眼镜销售场景,我们设计了专门的库存服务(InventoryService)和验光服务(OptometryService),前者处理镜片镜框的库存联动(如特定镜框只能搭配某些类型的镜片),后者管理用户的验光数据安全存储与版本控制。
SpringMVC负责RESTful API的设计,其中商品详情接口需要返回复杂的参数组合。例如获取一款镜架时,除了基础信息外,还需要返回兼容的镜片类型、可选的镀膜方案等扩展属性。我们通过@JsonView实现不同场景下的字段动态过滤,避免数据传输冗余。
MyBatis的灵活映射能力在这里大显身手。眼镜产品的多维度查询(如按脸型推荐、按度数筛选)涉及大量动态SQL,我们使用MyBatis的
xml复制<select id="findFramesByCondition" resultMap="frameResultMap">
SELECT * FROM optical_frame
<where>
<if test="faceShape != null">
AND recommended_face_shape LIKE CONCAT('%',#{faceShape},'%')
</if>
<if test="bridgeWidth != null">
AND bridge_width BETWEEN #{bridgeWidth}-2 AND #{bridgeWidth}+2
</if>
<!-- 10+ other conditions -->
</where>
ORDER BY
<choose>
<when test="sortBy == 'price'">base_price</when>
<when test="sortBy == 'popularity'">sales_count DESC</when>
<otherwise>id</otherwise>
</choose>
</select>
2.2 Flask的定位与实现
Flask在这个架构中主要承担两个角色:一是作为管理后台的快速开发框架,二是提供数据可视化服务。我们利用Flask-Admin快速构建了包含以下专业模块的后台系统:
- 验光数据管理面板:可查看用户的历次验光记录变化曲线
- 销售分析仪表盘:使用Pyecharts生成各镜片材质的销售占比热力图
- 智能推荐配置界面:维护脸型-镜框的匹配规则库
一个典型的Flask路由示例,用于生成销售漏斗分析图:
python复制@app.route('/api/analysis/sales_funnel')
def sales_funnel():
stages = ['浏览', '加入购物车', '提交验光数据', '完成支付']
data = db.session.query(
func.count(distinct(VisitLog.user_id)).label('view'),
# 其他阶段统计...
).first()
funnel = Funnel()
funnel.add("转化阶段", stages, [data.view, data.cart, ...])
return funnel.render_embed()
2.3 混合架构的通信设计
Java与Python服务间通过两种方式交互:
-
同步调用:使用HTTP API进行实时性要求高的操作(如库存检查)
- 定义统一的签名验证机制(X-Auth-Sign头)
- 采用Protocol Buffers作为数据序列化格式
-
异步消息:使用RabbitMQ处理耗时操作(如生成验光报告)
- 创建专门的exchange(optical_direct)
- 定义消息重试策略(最多3次,间隔指数增长)
这种设计使得验光师提交的复杂验光数据(包含角膜地形图等大文件)可以通过消息队列异步处理,避免阻塞主交易流程。
3. 核心业务模块实现细节
3.1 网上配镜工作流引擎
眼镜电商的核心差异点在于其特殊的购买流程:
- 用户选择镜框 → 2. 上传验光单或在线验光 → 3. 系统验证参数兼容性 → 4. 确认处方 → 5. 支付
我们实现了状态机驱动的工作流引擎:
java复制public class OpticalWorkflowEngine {
private StateMachine<OpticalState, OpticalEvent> stateMachine;
@Transition(source = "FRAME_SELECTED", target = "PRESCRIPTION_UPLOAD")
public void handleFrameSelection(Long userId, Long frameId) {
OpticalFrame frame = frameService.validateCompatibility(userId, frameId);
// 检查用户脸型与镜框匹配度
FaceShape shape = userService.getFaceShape(userId);
if (!frame.getCompatibleShapes().contains(shape)) {
throw new IncompatibleException("镜框与脸型不匹配");
}
}
}
3.2 动态价格计算策略
眼镜产品的定价需要考虑多种因素:
- 镜框基础价格
- 镜片材质(树脂/PC/玻璃)
- 镜片功能(防蓝光/变色/渐进多焦点)
- 镀膜方案(减反射/防水/耐磨)
采用策略模式实现价格计算:
java复制public interface PricingStrategy {
BigDecimal calculate(OpticalProduct product);
}
@Service
public class CompositePricingStrategy implements PricingStrategy {
@Autowired
private List<PricingStrategy> strategies;
public BigDecimal calculate(OpticalProduct product) {
return strategies.stream()
.map(s -> s.calculate(product))
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
@Service
@Primary
public class LensMaterialStrategy implements PricingStrategy {
public BigDecimal calculate(OpticalProduct product) {
return product.getLens().getMaterial().getPrice();
}
}
3.3 验光数据安全存储
用户的验光数据属于敏感医疗信息,我们采用分级加密方案:
- 基础信息(姓名、联系方式):AES加密存储
- 医疗数据(瞳距、度数):使用HSM硬件加密模块处理
- 访问控制:基于RBAC模型,只有认证验光师可解密完整数据
数据库设计上,将敏感字段单独存放,并实现MyBatis的类型处理器:
java复制public class EncryptedTypeHandler extends BaseTypeHandler<String> {
private EncryptionService encryptor;
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
String parameter, JdbcType jdbcType) {
ps.setString(i, encryptor.encrypt(parameter));
}
//...其他方法
}
4. 性能优化实战经验
4.1 商品搜索的Elasticsearch优化
眼镜商品有超过50个可筛选属性,我们为ES设计了特殊的mapping:
json复制{
"mappings": {
"properties": {
"frame_width": {"type": "integer", "coerce": false},
"compatible_lens_materials": {
"type": "nested",
"properties": {
"material_id": {"type": "keyword"},
"min_refractive_index": {"type": "float"}
}
},
"face_shape_scores": {
"type": "rank_features"
}
}
}
}
查询时使用bool组合查询,并对脸型匹配度添加boost值:
java复制BoolQueryBuilder query = QueryBuilders.boolQuery()
.must(QueryBuilders.rangeQuery("frame_width")
.gte(userProfile.getFaceWidth() - 2)
.lte(userProfile.getFaceWidth() + 2))
.should(QueryBuilders.nestedQuery("compatible_lens_materials",
QueryBuilders.termQuery("compatible_lens_materials.material_id",
userPrefs.getMaterialId()), ScoreMode.Total))
.boost(1.5f);
4.2 购物车并发控制
采用乐观锁解决镜片库存冲突:
sql复制UPDATE lens_inventory
SET stock = stock - 1
WHERE id = ? AND stock >= ?
AND version = ?
在Java层面对失败操作实现自动重试:
java复制@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 100))
public void deductInventory(Long skuId, int quantity) {
int affected = lensMapper.deductStock(skuId, quantity);
if (affected == 0) {
throw new OptimisticLockingFailureException("库存不足");
}
}
4.3 分布式事务处理
跨Java/Flask服务的处方确认操作,使用Saga模式保证一致性:
- Java服务:开始处方确认 → 预留库存
- Flask服务:生成验光报告PDF
- Java服务:最终确认(如失败则取消预留)
关键是在Flask服务实现幂等操作:
python复制@app.route('/api/prescription/generate', methods=['POST'])
def generate_prescription():
tx_id = request.headers['X-Transaction-ID']
if redis.get(f"prescription:{tx_id}:completed"):
return jsonify({"status": "already_generated"})
# 生成逻辑...
redis.setex(f"prescription:{tx_id}:completed", 3600, 1)
5. 运维部署方案
5.1 容器化部署架构
使用Docker Compose编排服务:
yaml复制version: '3.8'
services:
java-app:
image: openjdk:11-jre
deploy:
resources:
limits:
memory: 2g
environment:
- SPRING_PROFILES_ACTIVE=prod
depends_on:
- redis
- mysql
flask-app:
image: python:3.9
command: gunicorn -w 4 -b :5000 app:app
volumes:
- ./reports:/app/reports
environment:
- DB_URL=mysql://user:pass@mysql:3306/optical
mysql:
image: mysql:8.0
volumes:
- mysql_data:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD_FILE=/run/secrets/db_root_password
secrets:
- db_root_password
5.2 监控指标采集
针对眼镜行业的特殊指标监控:
- 验光数据提交成功率
- 镜框-镜片组合的退货率
- 处方生成耗时百分位
使用Micrometer暴露Java指标,Prometheus采集:
java复制@Bean
public MeterRegistryCustomizer<PrometheusMeterRegistry> configureMetrics() {
return registry -> {
registry.config().commonTags("application", "optical-store");
Gauge.builder("prescription.validation.failure",
validationService::getFailureCount)
.description("验光数据验证失败次数")
.register(registry);
};
}
5.3 日志收集方案
通过ELK Stack实现跨服务日志关联,关键是在日志中注入统一的traceId:
Java端通过MDC实现:
java复制@RestControllerAdvice
public class CorrelationIdAdvice implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) {
MDC.put("correlationId", UUID.randomUUID().toString());
return true;
}
}
Flask端通过请求拦截器实现:
python复制@app.before_request
def set_correlation_id():
if 'X-Correlation-ID' in request.headers:
g.correlation_id = request.headers['X-Correlation-ID']
else:
g.correlation_id = str(uuid.uuid4())
logging.LoggerAdapter(logger, {
'correlationId': g.correlation_id
})
这套系统在实际部署中需要特别注意验光图片的存储优化,我们采用了分级存储策略:近期验光图保存在高性能NAS,历史数据迁移到对象存储,并通过智能缓存减少IO压力。对于高峰期(如开学季配镜高峰),通过自动扩展Flask服务实例来处理暴增的验光报告生成请求。
