1. 项目概述:奶茶店点单拼团商城系统
这个基于SpringBoot+Vue3+Android的奶茶店点单系统,是我去年为本地连锁奶茶品牌开发的一个全栈项目。核心功能包括移动端点单、拼团购、会员管理和后台数据统计,特别针对奶茶行业高频、即时性的消费特点做了优化。
系统采用前后端分离架构,后端使用SpringBoot 2.7提供RESTful API,管理端用Vue3+Element Plus实现,安卓App则通过Android Studio开发。最让我自豪的是拼团功能的设计——用户发起拼单后,系统会自动匹配附近3公里内同款商品的订单,15分钟内凑满3单即可享受7折优惠,实测让门店客单价提升了23%。
2. 技术架构解析
2.1 后端SpringBoot设计要点
选用SpringBoot主要考虑其快速开发特性和丰富的生态支持。我在pom.xml中关键配置了:
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.7</version>
</dependency>
特别要注意的是订单服务的分布式锁实现:
java复制public boolean createOrder(OrderDTO orderDTO) {
String lockKey = "order_lock:" + orderDTO.getUserId();
try {
// 使用Redis分布式锁,避免重复下单
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
if (locked != null && locked) {
// 核心下单逻辑
}
} finally {
redisTemplate.delete(lockKey);
}
}
2.2 Vue3管理端关键技术
前端选用Vue3的组合式API,大幅提升了代码复用率。分享几个实用技巧:
- 使用Pinia替代Vuex管理状态:
javascript复制// stores/order.js
export const useOrderStore = defineStore('order', {
state: () => ({
pendingOrders: []
}),
actions: {
async fetchPendingOrders() {
const res = await api.get('/orders?status=pending')
this.pendingOrders = res.data
}
}
})
- 表格渲染优化:通过v-memo缓存静态内容
vue复制<tr v-for="item in list" :key="item.id" v-memo="[item.id]">
<td>{{ item.name }}</td>
<!-- 其他不常变化的列 -->
</tr>
2.3 安卓App开发实战
在Android Studio中需要特别注意的几个配置:
- 在build.gradle中添加必要的依赖:
groovy复制implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.google.code.gson:gson:2.8.9'
- 网络请求封装示例:
kotlin复制interface ApiService {
@POST("order/create")
suspend fun createOrder(@Body order: Order): Response<BaseResult>
}
object RetrofitClient {
private const val BASE_URL = "https://api.xxx.com/"
val instance: ApiService by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
}
3. 核心功能实现细节
3.1 拼团算法设计
拼团功能的核心在于实时匹配算法。我设计了一个基于Redis GEO和ZSET的解决方案:
- 用户发起拼团时,将订单信息存入Hash:
bash复制HSET group:order:{orderId} productId 123 userId 456 timestamp 1672531200
- 使用GEO存储用户位置:
bash复制GEOADD store:location 116.404 39.915 "user:789"
- ZSET实现倒计时:
bash复制ZADD group:expire 1672531260 {orderId}
后台线程每30秒扫描一次ZSET,对即将超时的订单进行相似商品和距离范围的匹配(使用GEORADIUS命令)。
3.2 订单状态机设计
奶茶订单有复杂的生命周期,我采用状态模式实现:
java复制public interface OrderState {
void pay(Order order);
void cancel(Order order);
void complete(Order order);
}
@Component
@Scope("prototype")
public class UnpaidState implements OrderState {
@Override
public void pay(Order order) {
order.setState(OrderStatusEnum.PAID);
// 扣减库存等操作
}
}
状态转换通过Spring状态机实现,配置如下:
java复制@Configuration
@EnableStateMachineFactory
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<String, String> {
@Override
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
states
.withStates()
.initial("UNPAID")
.states(EnumSet.allOf(OrderStatusEnum.class));
}
}
4. 性能优化实践
4.1 高并发下单解决方案
针对奶茶店高峰期的并发问题,我采用了多级缓存策略:
- 商品信息缓存:使用Caffeine实现本地缓存
java复制@Bean
public Caffeine<Object, Object> caffeineConfig() {
return Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(1000);
}
- 库存扣减方案:采用Redis+Lua脚本保证原子性
lua复制local key = KEYS[1]
local num = tonumber(ARGV[1])
local stock = tonumber(redis.call('GET', key))
if stock >= num then
redis.call('DECRBY', key, num)
return 1
end
return 0
4.2 安卓端启动优化
通过以下手段将冷启动时间从2.3s降低到1.1s:
- 启动页主题优化:
xml复制<style name="SplashTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowBackground">@drawable/splash_background</item>
<item name="android:windowFullscreen">true</item>
</style>
- 延迟初始化非必要组件:
kotlin复制class MyApp : Application() {
override fun onCreate() {
super.onCreate()
initCoreComponents()
Handler().postDelayed({
initNonCriticalComponents()
}, 1000)
}
}
5. 踩坑与解决方案
5.1 微信支付回调问题
遇到最头疼的问题是微信支付证书过期导致回调失败。解决方案:
- 实现证书自动更新机制:
java复制@Scheduled(cron = "0 0 3 * * ?")
public void refreshCert() {
// 每天凌晨3点检查证书
}
- 回调接口增加重试机制:
java复制@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 1000))
public String payCallback(HttpServletRequest request) {
// 回调处理逻辑
}
5.2 Vue3路由缓存失效
管理端使用keep-alive缓存页面时,发现详情页返回列表页会重置查询条件。最终解决方案:
vue复制<router-view v-slot="{ Component }">
<keep-alive :include="cachedViews">
<component :is="Component" :key="$route.fullPath" />
</keep-alive>
</router-view>
配合路由配置:
javascript复制{
path: '/order/list',
component: () => import('@/views/order/list'),
meta: { keepAlive: true }
}
6. 项目部署方案
6.1 后端部署要点
使用Docker Compose部署时,mysql配置需要特别注意字符集:
yaml复制services:
mysql:
image: mysql:5.7
environment:
MYSQL_CHARSET: utf8mb4
MYSQL_COLLATION: utf8mb4_unicode_ci
SpringBoot应用的JVM参数建议配置:
bash复制java -jar -Xms512m -Xmx1024m -XX:MaxMetaspaceSize=256m app.jar
6.2 安卓打包技巧
在Android Studio中生成正式APK时,务必配置:
groovy复制android {
signingConfigs {
release {
storeFile file("myreleasekey.keystore")
storePassword "password"
keyAlias "alias"
keyPassword "password"
}
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
}
建议使用Android App Bundle格式上传Google Play,可以减小用户下载体积约20%。
这个项目从技术选型到最终上线历时4个月,最大的收获是深入理解了高并发场景下的分布式系统设计。特别是在促销活动时,系统成功支撑了单店日订单量破3000的成绩。如果让我重新设计,我会考虑引入Kafka来处理订单事件流,进一步提升系统的扩展性。
