"果蔬到家"是一款基于Spring Boot和Uni-APP开发的生鲜电商平台,旨在解决传统果蔬购买方式中存在的耗时费力、信息不对称等问题。作为一名有多年开发经验的Java工程师,我在毕业设计期间完成了这个项目的全栈开发,现在将整个开发过程和经验分享给大家。
这个项目最核心的价值在于:
项目采用前后端分离架构,后端使用Spring Boot+MySQL,前端使用Uni-APP实现跨平台(iOS/Android/小程序)。整个开发周期约3个月,最终实现了完整的电商功能闭环。
后端技术栈:
前端技术栈:
选择这些技术主要基于以下考虑:
系统采用经典的三层架构:
code复制┌───────────────────────────────────────┐
│ 客户端层 │
│ (iOS/Android/小程序/H5) │
└───────────────┬───────────────────────┘
│ HTTP/HTTPS
┌───────────────▼───────────────────────┐
│ 网关层 │
│ (Nginx反向代理+负载均衡) │
└───────────────┬───────────────────────┘
│ RESTful API
┌───────────────▼───────────────────────┐
│ 应用层 │
│ (Spring Boot微服务集群) │
└───────────────┬───────────────────────┘
│ JDBC/JPA
┌───────────────▼───────────────────────┐
│ 数据层 │
│ (MySQL主从+Redis缓存) │
└───────────────────────────────────────┘
这种架构的优势在于:
采用JWT+Spring Security实现认证,关键代码如下:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(jwtAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
}
关键点:
商品表设计考虑了电商场景的特殊需求:
sql复制CREATE TABLE `goods` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL COMMENT '商品名称',
`price` decimal(10,2) NOT NULL COMMENT '售价',
`original_price` decimal(10,2) DEFAULT NULL COMMENT '原价',
`stock` int NOT NULL DEFAULT '0' COMMENT '库存',
`sold` int DEFAULT '0' COMMENT '已售数量',
`images` text COMMENT '商品图片(JSON数组)',
`detail` text COMMENT '商品详情',
`status` tinyint DEFAULT '1' COMMENT '状态(1-上架,0-下架)',
`category_id` bigint DEFAULT NULL COMMENT '分类ID',
`merchant_id` bigint NOT NULL COMMENT '商家ID',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_category` (`category_id`),
KEY `idx_merchant` (`merchant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
设计考虑:
购物车采用Redis+数据库双写策略:
java复制@Service
public class CartServiceImpl implements CartService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private CartMapper cartMapper;
private String getCartKey(Long userId) {
return "cart:" + userId;
}
@Override
public void addToCart(Long userId, CartItem item) {
// 写入Redis
String key = getCartKey(userId);
redisTemplate.opsForHash().put(key,
item.getGoodsId().toString(), item);
// 异步写入数据库
CompletableFuture.runAsync(() -> {
CartItem exist = cartMapper.selectByUserAndGoods(
userId, item.getGoodsId());
if(exist != null) {
exist.setQuantity(exist.getQuantity() + item.getQuantity());
cartMapper.updateById(exist);
} else {
cartMapper.insert(item);
}
});
}
}
优化点:
在秒杀等高并发场景下,采用以下方案:
java复制public boolean reduceStock(Long goodsId, int num) {
String key = "goods_stock:" + goodsId;
Long value = redisTemplate.opsForValue().decrement(key, num);
if(value != null && value >= 0) {
return true;
} else {
// 回滚
redisTemplate.opsForValue().increment(key, num);
return false;
}
}
支付成功后,可能因网络问题导致通知丢失。解决方案:
java复制@Scheduled(cron = "0 0/5 * * * ?")
public void checkUnpaidOrders() {
List<Order> orders = orderMapper.selectUnpaid(30); // 30分钟未支付
for(Order order : orders) {
PaymentStatus status = paymentService.query(order.getOrderNo());
if(status == PaymentStatus.SUCCESS) {
orderService.handlePaySuccess(order);
}
}
}
缓存策略:
SQL优化:
前端优化:
接口安全:
数据安全:
风控策略:
推荐的最低生产环境配置:
code复制├── 前端服务器(2台)
│ ├── CPU: 4核
│ ├── 内存: 8GB
│ └── 带宽: 5Mbps
│
├── 后端服务器(2台)
│ ├── CPU: 8核
│ ├── 内存: 16GB
│ └── 带宽: 内网
│
├── 数据库服务器(主从)
│ ├── CPU: 16核
│ ├── 内存: 32GB
│ └── 存储: SSD 500GB
│
└── Redis服务器(哨兵模式)
├── CPU: 4核
└── 内存: 16GB
使用Docker Compose编排服务:
yaml复制version: '3'
services:
app:
image: fruit-app:1.0
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
depends_on:
- redis
- mysql
mysql:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=123456
- MYSQL_DATABASE=fruit
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:6.0
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
mysql_data:
redis_data:
在开发这个项目过程中,我积累了一些有价值的经验:
特别提醒: 在开发电商系统时,要特别注意事务一致性问题。比如下单扣库存的场景,一定要保证"扣减库存"和"创建订单"两个操作的事务性,否则可能出现库存扣减了但订单没创建成功的情况。
这个项目已经稳定运行了半年,日均订单量在1000单左右。后续计划加入推荐算法、会员体系等功能,进一步提升用户体验和商业价值。