1. 项目背景与核心需求
淘乐乐员工购物商城是一个典型的B2E(Business to Employee)电商平台,专为企业内部员工福利采购设计。这类系统与传统电商的最大区别在于用户群体的特定性——所有使用者都是经过HR系统认证的在职员工,这带来了权限管理、福利配额、部门结算等特殊业务需求。
在技术选型上,SSM(Spring+SpringMVC+MyBatis)框架组合是JavaEE领域经过多年验证的成熟方案。Spring 4.3.18版本提供了稳定的IoC容器和声明式事务管理,SpringMVC作为轻量级Web框架能很好地处理RESTful风格的接口,而MyBatis 3.4.6则以其灵活的SQL映射能力著称。这个技术栈的选择体现了项目对稳定性、可维护性和开发效率的平衡。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构设计
2.1 分层架构实现
系统采用经典的三层架构,但根据电商特点进行了细化:
- 表现层:基于SpringMVC实现,包含:
- 商品展示模块(JSP+EL表达式)
- 购物车交互模块(AJAX异步请求)
- 订单管理模块(Bootstrap响应式布局)
- 业务逻辑层:通过Spring注解@Service标识的核心服务:
java复制@Service public class EmployeeShopService { @Autowired private InventoryMapper inventoryMapper; @Transactional public PurchaseResult processOrder(OrderDTO order) { // 库存检查、优惠计算等业务逻辑 } } - 数据访问层:MyBatis的Mapper接口与XML映射文件配合:
xml复制<!-- EmployeeMapper.xml --> <select id="selectByDept" resultType="Employee"> SELECT * FROM emp WHERE dept_id=#{deptId} AND status='ACTIVE' </select>
2.2 数据库设计要点
员工商城数据库设计有几个特殊考虑:
- 员工-部门关联表:需要记录历史部门变更
sql复制CREATE TABLE emp_dept_history ( id BIGINT PRIMARY KEY, emp_id VARCHAR(10) NOT NULL, dept_code VARCHAR(8) NOT NULL, effective_date DATE NOT NULL, FOREIGN KEY (emp_id) REFERENCES employee(work_id) ); - 福利额度表:实现不同职级的差异化配额
- 商品分类表:支持多级分类(办公用品、节日礼品等)
3. 核心功能实现细节
3.1 员工身份验证集成
与企业HR系统的集成是项目难点之一。我们采用JWT+LDAP的双重验证机制:
- LDAP基础认证:
java复制@Service public class LdapAuthService { private final LdapTemplate ldapTemplate; public boolean authenticate(String workId, String password) { return ldapTemplate.authenticate( "ou=employees", "(employeeNumber=" + workId + ")", password ); } } - JWT令牌发放:包含员工基础信息和部门权限
- 会话过滤器:通过Spring拦截器验证令牌有效性
3.2 购物车并发控制
针对秒杀场景下的超卖问题,我们实现了三级库存保护:
- Redis预减库存:
java复制public boolean reduceStock(String itemId, int num) { Long remain = redisTemplate.opsForValue() .decrement("stock:" + itemId, num); return remain != null && remain >= 0; } - 数据库乐观锁:
sql复制UPDATE inventory SET stock = stock - #{quantity} WHERE item_id=#{itemId} AND stock >= #{quantity} - 定时任务补偿:每小时同步Redis与数据库库存
3.3 部门结算模块
为满足财务需求,开发了自动化的部门结算功能:
- 账单生成规则引擎:
xml复制<!-- rule-engine.xml --> <rule name="deptSettlement"> <condition>order.status == 'COMPLETED'</condition> <action> settlementService.createBill( order.getDeptCode(), order.getTotalAmount() ) </action> </rule> - Excel报表导出:使用Apache POI生成带部门LOGO的定制化账单
- 审批工作流:集成Activiti实现多级审批
4. 性能优化实践
4.1 页面静态化方案
商品详情页采用Freemarker模板静态化:
java复制@Scheduled(cron = "0 0 2 * * ?")
public void generateStaticPages() {
List<Item> hotItems = itemService.getHotItems();
hotItems.forEach(item -> {
String html = FreeMarkerTemplateUtils
.processTemplate("itemDetail.ftl",
Map.of("item", item));
Files.write(Paths.get("/static/items/"+item.getId()),
html.getBytes());
});
}
4.2 二级缓存设计
MyBatis二级缓存与Redis的混合使用策略:
- 缓存粒度控制:商品基础信息缓存2小时,价格信息缓存10分钟
- 缓存穿透防护:
java复制public Item getItemWithCache(String itemId) { String cacheKey = "item:" + itemId; Item item = redisTemplate.opsForValue().get(cacheKey); if (item == null) { synchronized(this) { item = itemMapper.selectById(itemId); if (item == null) { redisTemplate.opsForValue() .set(cacheKey, new NullItem(), 5, MINUTES); } else { redisTemplate.opsForValue() .set(cacheKey, item, 2, HOURS); } } } return item instanceof NullItem ? null : item; }
5. 安全防护措施
5.1 订单防篡改机制
采用签名验证防止订单参数被修改:
- 客户端生成签名:
javascript复制function generateSign(params, secret) { let str = Object.keys(params) .sort() .map(k => `${k}=${params[k]}`) .join('&'); return md5(str + secret); } - 服务端验证:
java复制public boolean verifySign(OrderDTO order, String secret) { String localSign = DigestUtils.md5Hex( "amount=" + order.getAmount() + "&itemId=" + order.getItemId() + secret ); return localSign.equals(order.getSign()); }
5.2 敏感操作日志
通过AOP记录关键操作:
java复制@Aspect
@Component
public class AuditLogAspect {
@AfterReturning(
pointcut = "@annotation(com.taolele.audit.RequireAudit)",
returning = "result"
)
public void logAuditEvent(JoinPoint jp, Object result) {
AuditLog log = new AuditLog();
log.setOperation(jp.getSignature().getName());
log.setOperator(SecurityUtils.getCurrentUser());
log.setResult(JSON.toJSONString(result));
auditLogRepository.save(log);
}
}
6. 部署与监控
6.1 多环境配置管理
使用Spring Profile实现环境隔离:
properties复制# application-prod.properties
spring.datasource.url=jdbc:mysql://prod-db:3306/taolele
logging.level.root=WARN
# application-dev.properties
spring.datasource.url=jdbc:mysql://localhost:3306/taolele_dev
logging.level.com.taolele=DEBUG
6.2 健康检查端点
自定义健康检查指标:
java复制@Component
public class InventoryHealthIndicator
implements HealthIndicator {
@Override
public Health health() {
int lowStockItems = inventoryMapper.countLowStockItems();
if (lowStockItems > 100) {
return Health.down()
.withDetail("lowStockCount", lowStockItems)
.build();
}
return Health.up()
.withDetail("totalItems", inventoryMapper.countAll())
.build();
}
}
在项目开发过程中,我们发现SSM框架虽然成熟,但在处理前后端分离项目时,SpringMVC的视图解析机制会带来一些冗余配置。后续可考虑将前端完全剥离,采用Vue+SpringBoot的纯API架构,这需要权衡开发效率与架构清晰度。另外,MyBatis的动态SQL虽然灵活,但在复杂查询场景下,XML文件的维护成本会显著增加,这时可以考虑辅助使用QueryDSL等类型安全的查询框架。
