在医药零售行业数字化转型的浪潮中,传统药店面临着销售渠道单一、运营成本高企、用户购药便利性不足等核心挑战。医药电商平台应运而生,通过B2C模式为消费者提供安全便捷的药品选购体验,同时帮助药店实现业务数字化升级。本系统采用成熟的SSM框架技术栈,构建了一个功能完备的在线药品销售解决方案。
系统架构与技术栈设计
该平台采用经典的三层架构模式,前端展示层使用JSP动态页面技术,结合jQuery和Bootstrap框架实现响应式用户界面。业务逻辑层基于Spring框架实现依赖注入和事务管理,Web层采用SpringMVC处理请求分发,数据持久层使用MyBatis完成数据库操作。
技术栈配置通过Maven进行依赖管理,关键配置如下:
<dependencies>
<!-- Spring核心依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<!-- MyBatis整合Spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
</dependencies>
Spring配置文件中定义了数据源和事务管理器:
@Configuration
@EnableTransactionManagement
@ComponentScan("com.pharmacy.service")
public class SpringConfig {
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/pharmacy_db?useUnicode=true");
dataSource.setUsername("root");
dataSource.setPassword("password");
return dataSource;
}
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}
数据库设计亮点分析
商品表设计优化
商品表(item)的设计体现了医药电商业务的特殊性。该表采用垂直分表的思想,将商品基本属性与动态业务数据分离:
CREATE TABLE `item` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` varchar(255) DEFAULT NULL COMMENT '商品名称',
`price` varchar(255) DEFAULT NULL COMMENT '商品价格',
`scNum` int(11) DEFAULT NULL COMMENT '收藏数',
`gmNum` int(11) DEFAULT NULL COMMENT '购买数',
`url1` varchar(255) DEFAULT NULL COMMENT '图片URL1',
`url2` varchar(255) DEFAULT NULL COMMENT '图片URL2',
`url3` varchar(255) DEFAULT NULL COMMENT '图片URL3',
`url4` varchar(255) DEFAULT NULL COMMENT '图片URL4',
`url5` varchar(255) DEFAULT NULL COMMENT '图片URL5',
`ms` text DEFAULT NULL COMMENT '商品描述',
`pam1` varchar(255) DEFAULT NULL COMMENT '参数1',
`pam2` varchar(255) DEFAULT NULL COMMENT '参数2',
`pam3` varchar(255) DEFAULT NULL COMMENT '参数3',
`val3` varchar(255) DEFAULT NULL COMMENT '值3',
`val2` varchar(255) DEFAULT NULL COMMENT '值2',
`val1` varchar(255) DEFAULT NULL COMMENT '值1',
`type` int(11) DEFAULT NULL COMMENT '商品类型',
`zk` int(10) DEFAULT NULL COMMENT '折扣',
`category_id_one` int(11) DEFAULT NULL COMMENT '一级分类ID',
`category_id_two` int(11) DEFAULT NULL COMMENT '二级分类ID',
`isDelete` int(2) DEFAULT NULL COMMENT '0否 1是',
`num` int(2) DEFAULT 0 COMMENT '库存数量',
PRIMARY KEY (`id`),
KEY `idx_category` (`category_id_one`,`category_id_two`),
KEY `idx_price` (`price`),
KEY `idx_zk` (`zk`)
) ENGINE=InnoDB AUTO_INCREMENT=86 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='商品表'
设计亮点包括:
- 多图片存储设计:url1-url5字段支持商品多角度展示,满足药品需要展示说明书、包装等详细信息的需求
- 弹性参数结构:pam1-pam3和val1-val3组成键值对,可灵活存储药品规格、剂型、生产厂家等差异化属性
- 双重分类索引:category_id_one和category_id_two建立复合索引,支持高效的多级分类查询
- 软删除机制:isDelete字段实现逻辑删除,保留历史数据的同时避免物理删除的风险
购物车表业务逻辑设计
购物车表(car)的设计重点考虑了并发操作和价格一致性问题:
CREATE TABLE `car` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`item_id` int(11) DEFAULT NULL COMMENT '商品ID',
`user_id` int(11) DEFAULT NULL COMMENT '用户ID',
`num` int(11) DEFAULT NULL COMMENT '数量',
`price` decimal(10,2) DEFAULT NULL COMMENT '单价',
`total` varchar(255) DEFAULT NULL COMMENT '总价',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_item` (`user_id`,`item_id`),
KEY `idx_user` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='购物车表'
关键设计策略:
- 价格快照机制:price字段在加入购物车时存储商品当时价格,避免后续价格变动影响已选商品
- 唯一性约束:uk_user_item索引防止同一用户重复添加同一商品,优化购物车操作体验
- 十进制精度:price使用decimal(10,2)类型,确保金额计算的精确性

核心功能实现深度解析
用户权限控制与会话管理
系统采用基于拦截器的权限控制机制,确保不同角色用户访问权限的严格分离:
@Component
public class AuthInterceptor implements HandlerInterceptor {
private static final String[] IGNORE_URI = {"/login", "/register", "/index"};
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
String uri = request.getRequestURI();
// 放行静态资源和登录相关请求
for (String ignore : IGNORE_URI) {
if (uri.contains(ignore)) {
return true;
}
}
HttpSession session = request.getSession();
User user = (User) session.getAttribute("user");
if (user == null) {
response.sendRedirect(request.getContextPath() + "/login");
return false;
}
// 管理员权限验证
if (uri.contains("/admin") && !user.getRole().equals("admin")) {
response.sendRedirect(request.getContextPath() + "/error/403");
return false;
}
return true;
}
}
SpringMVC配置中注册拦截器:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private AuthInterceptor authInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/css/**", "/js/**", "/images/**");
}
}
商品搜索与分类浏览功能
商品检索功能支持关键词搜索和分类筛选,MyBatis动态SQL实现灵活查询:
@Mapper
public interface ItemMapper {
List<Item> searchItems(@Param("keyword") String keyword,
@Param("categoryOne") Integer categoryOne,
@Param("categoryTwo") Integer categoryTwo,
@Param("orderBy") String orderBy);
}
对应的Mapper XML配置:
<mapper namespace="com.pharmacy.mapper.ItemMapper">
<select id="searchItems" resultType="Item">
SELECT * FROM item
WHERE isDelete = 0
<if test="keyword != null and keyword != ''">
AND (name LIKE CONCAT('%', #{keyword}, '%') OR ms LIKE CONCAT('%', #{keyword}, '%'))
</if>
<if test="categoryOne != null">
AND category_id_one = #{categoryOne}
</if>
<if test="categoryTwo != null">
AND category_id_two = #{categoryTwo}
</if>
<if test="orderBy != null">
ORDER BY
<choose>
<when test="orderBy == 'price_asc'">price ASC</when>
<when test="orderBy == 'price_desc'">price DESC</when>
<when test="orderBy == 'sales'">gmNum DESC</when>
<when test="orderBy == 'popular'">scNum DESC</when>
<otherwise>id DESC</otherwise>
</choose>
</if>
</select>
</mapper>

购物车业务逻辑实现
购物车服务层处理商品添加、数量修改和价格计算等核心业务:
@Service
@Transactional
public class CartService {
@Autowired
private CartMapper cartMapper;
@Autowired
private ItemMapper itemMapper;
public void addToCart(Integer userId, Integer itemId, Integer quantity) {
// 检查库存
Item item = itemMapper.selectById(itemId);
if (item.getNum() < quantity) {
throw new BusinessException("库存不足");
}
// 检查购物车是否已有该商品
Cart existCart = cartMapper.selectByUserAndItem(userId, itemId);
if (existCart != null) {
// 更新数量
existCart.setNum(existCart.getNum() + quantity);
existCart.setTotal(existCart.getPrice().multiply(new BigDecimal(existCart.getNum())));
cartMapper.update(existCart);
} else {
// 新增购物车项
Cart cart = new Cart();
cart.setUserId(userId);
cart.setItemId(itemId);
cart.setNum(quantity);
cart.setPrice(item.getPrice());
cart.setTotal(item.getPrice().multiply(new BigDecimal(quantity)));
cartMapper.insert(cart);
}
}
public BigDecimal calculateTotal(Integer userId) {
List<Cart> cartItems = cartMapper.selectByUserId(userId);
return cartItems.stream()
.map(cart -> new BigDecimal(cart.getTotal()))
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
订单处理与库存管理
订单生成过程涉及复杂的业务逻辑和事务控制:
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private ItemMapper itemMapper;
@Autowired
private CartMapper cartMapper;
@Transactional(rollbackFor = Exception.class)
public Order createOrder(Integer userId, List<Integer> cartIds, String address) {
// 验证购物车商品
List<Cart> cartItems = cartMapper.selectByIds(cartIds);
if (cartItems.isEmpty()) {
throw new BusinessException("购物车为空");
}
// 检查库存并锁定
for (Cart cart : cartItems) {
int result = itemMapper.updateStock(cart.getItemId(), cart.getNum());
if (result == 0) {
throw new BusinessException("商品库存不足: " + cart.getItem().getName());
}
}
// 生成订单
Order order = new Order();
order.setUserId(userId);
order.setOrderNo(generateOrderNo());
order.setStatus(OrderStatus.PENDING_PAYMENT);
order.setAddress(address);
order.setTotalAmount(calculateOrderTotal(cartItems));
orderMapper.insert(order);
// 生成订单明细
for (Cart cart : cartItems) {
OrderDetail detail = new OrderDetail();
detail.setOrderId(order.getId());
detail.setItemId(cart.getItemId());
detail.setQuantity(cart.getNum());
detail.setPrice(cart.getPrice());
orderMapper.insertDetail(detail);
}
// 清空购物车
cartMapper.deleteByIds(cartIds);
return order;
}
private String generateOrderNo() {
return "PO" + System.currentTimeMillis() +
String.format("%04d", new Random().nextInt(9999));
}
}

实体模型设计
基于MyBatis的实体类设计采用注解方式配置映射关系:
@Data
@TableName("item")
public class Item implements Serializable {
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
@TableField("price")
private BigDecimal price;
private Integer scNum;
private Integer gmNum;
private String url1;
private String url2;
private String url3;
private String url4;
private String url5;
private String ms;
private Integer type;
private Integer zk;
@TableField("category_id_one")
private Integer categoryIdOne;
@TableField("category_id_two")
private Integer categoryIdTwo;
@TableField("isDelete")
private Integer isDelete;
private Integer num;
@TableField(exist = false)
private List<Comment> comments;
@TableField(exist = false)
private Category categoryOne;
@TableField(exist = false)
private Category categoryTwo;
}
评论功能实体关系设计:
@Data
@TableName("comment")
public class Comment {
@TableId(type = IdType.AUTO)
private Integer id;
private Integer userId;
private Integer itemId;
private String content;
private Date addTime;
@TableField(exist = false)
private User user;
@TableField(exist = false)
private Item item;
}

功能展望与系统优化方向
性能优化方案
- Redis缓存集成:对热门商品、分类信息等高频读取数据实施缓存策略
@Service
public class ItemServiceWithCache {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String ITEM_CACHE_KEY = "item:";
private static final long CACHE_EXPIRE = 3600; // 1小时
public Item getItemById(Integer id) {
String cacheKey = ITEM_CACHE_KEY + id;
Item item = (Item) redisTemplate.opsForValue().get(cacheKey);
if (item == null) {
item = itemMapper.selectById(id);
if (item != null) {
redisTemplate.opsForValue().set(cacheKey, item, CACHE_EXPIRE, TimeUnit.SECONDS);
}
}
return item;
}
}
- Elasticsearch搜索优化:实现更高效的全文检索和智能推荐
@Configuration
public class ElasticsearchConfig {
@Bean
public RestHighLevelClient elasticsearchClient() {
return new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http")));
}
}
架构演进方向
- 微服务化改造:将单体应用拆分为用户服务、商品服务、订单服务等独立微服务
# Docker Compose服务编排示例
version: '3.8'
services:
user-service:
image: pharmacy/user-service:latest
ports:
- "8081:8080"
environment:
- SPRING_PROFILES_ACTIVE=docker
product-service:
image: pharmacy/product-service:latest
ports:
- "8082:8080"
order-service:
image: pharmacy/order-service:latest
ports:
- "8083:8080"
- 消息队列应用:使用RabbitMQ处理订单创建、库存更新等异步操作
@Component
public class OrderMessageProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendOrderCreateMessage(Order order) {
rabbitTemplate.convertAndSend("order.exchange",
"order.create",
order);
}
}
业务功能扩展
- 药品追溯系统:集成区块链技术实现药品流通全链条追溯
- 智能用药提醒:基于用户购药记录提供个性化的用药提醒服务
- 在线问诊集成:对接第三方医疗平台提供专业的用药咨询服务
该医药电商平台通过严谨的架构设计和细致的功能实现,为传统药店数字化转型提供了可靠的技术支撑。系统在保证业务完整性的同时,为后续的功能扩展和性能优化预留了充足的空间,具备良好的可维护性和可扩展性。