1. 项目概述
不二掌柜ERP是一款基于UniApp开发的企业级进销存订货商城系统,采用SaaS多租户架构设计,支持Web端和微信小程序双端运行。这套系统专为B2B和B2C场景打造,集商品管理、在线交易、库存控制和订单跟踪于一体,为企业提供完整的电商解决方案。
作为从业十年的全栈开发者,我亲历了多个类似项目的实施过程。这套源码最吸引人的地方在于其"开箱即用"的特性——不仅提供了基础功能模块,还预留了充足的二次开发空间。系统采用前后端分离架构,前端基于Vue.js+UniApp,后端使用Spring Boot,数据库支持MySQL,技术栈选择非常符合当前企业级应用的主流趋势。
提示:这套系统特别适合中小型商贸企业、批发商和品牌代理商使用,能有效解决传统进销存系统移动化不足、多终端数据不同步的问题。
2. 系统架构设计
2.1 技术栈选型
前端技术组合:
- UniApp跨端框架:一套代码可编译到iOS、Android、H5及各种小程序平台
- Vue.js 2.x:采用MVVM模式开发,组件化程度高
- Vant Weapp/Vant UI:提供丰富的移动端UI组件
- WebSocket:实现实时库存预警和订单状态推送
后端技术组合:
- Spring Boot 2.7:快速构建微服务架构
- MyBatis-Plus:增强型ORM框架,简化CRUD操作
- Redis:缓存热点数据和分布式锁实现
- RabbitMQ:异步处理订单创建、库存扣减等耗时操作
数据库设计:
- MySQL 8.0:主业务数据库,采用InnoDB引擎
- 分库分表策略:按租户ID进行水平分片
- JSON字段应用:灵活存储网店配置等非结构化数据
2.2 多租户实现方案
系统采用"共享数据库,独立Schema"的多租户模式,每个租户拥有独立的数据命名空间。关键实现点包括:
- 租户标识传递:
java复制// 通过拦截器自动解析租户ID
public class TenantInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String tenantId = request.getHeader("X-Tenant-ID");
TenantContext.setCurrentTenant(tenantId);
return true;
}
}
- 动态数据源路由:
java复制public class TenantDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return TenantContext.getCurrentTenant();
}
}
- SQL自动过滤:
java复制// MyBatis-Plus自动添加租户条件
public class TenantSqlInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
List<AbstractMethod> methodList = super.getMethodList(mapperClass);
methodList.add(new TenantSelect());
return methodList;
}
}
注意:实际项目中建议增加租户配额限制和资源隔离策略,防止某个租户占用过多系统资源影响整体性能。
3. 核心功能实现
3.1 商品管理模块
商品系统采用"SPU+SKU"的数据模型,支持多规格商品管理。核心表结构设计如下:
sql复制CREATE TABLE `mall_goods` (
`id` bigint(22) NOT NULL COMMENT '商品ID',
`shop_id` bigint(22) NOT NULL COMMENT '所属网店',
`spu_code` varchar(64) NOT NULL COMMENT '标准产品单元编码',
`goods_name` varchar(100) NOT NULL COMMENT '商品名称',
`category_id` bigint(22) NOT NULL COMMENT '分类ID',
`main_image` varchar(255) NOT NULL COMMENT '主图URL',
`detail_html` text COMMENT '商品详情HTML',
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '上架状态',
`tenant_id` bigint(22) NOT NULL COMMENT '租户ID',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_spu_shop` (`shop_id`,`spu_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `mall_goods_sku` (
`id` bigint(22) NOT NULL COMMENT 'SKU ID',
`goods_id` bigint(22) NOT NULL COMMENT '关联商品ID',
`sku_code` varchar(64) NOT NULL COMMENT 'SKU编码',
`specs_json` json NOT NULL COMMENT '规格属性JSON',
`price` decimal(10,2) NOT NULL COMMENT '销售价',
`cost_price` decimal(10,2) COMMENT '成本价',
`stock` int(11) NOT NULL DEFAULT '0' COMMENT '库存',
`warning_stock` int(11) DEFAULT '10' COMMENT '库存预警值',
PRIMARY KEY (`id`),
KEY `idx_goods_id` (`goods_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
商品搜索优化方案:
- 建立Elasticsearch索引,支持中文分词搜索
- 使用Redis缓存热门商品和分类导航数据
- 实现基于浏览历史的个性化推荐算法
3.2 订单交易流程
订单系统采用状态机模式管理订单生命周期,核心状态转换如下:
code复制[待支付] -- 支付成功 --> [已支付] -- 发货 --> [已发货] -- 确认收货 --> [已完成]
\ | |
\-- 取消 --> [已取消] <-- 退款 -- [退款中]
关键代码实现:
java复制// 订单状态机配置
public class OrderStateMachineBuilder {
public StateMachine<OrderStatus, OrderEvent> build() {
StateMachineBuilder.Builder<OrderStatus, OrderEvent> builder =
StateMachineBuilder.builder();
// 配置状态转换
builder.configureTransitions()
.withExternal()
.source(OrderStatus.PENDING)
.target(OrderStatus.PAID)
.event(OrderEvent.PAY_SUCCESS)
.and()
.withExternal()
.source(OrderStatus.PENDING)
.target(OrderStatus.CANCELLED)
.event(OrderEvent.CANCEL)
// 其他状态转换...
return builder.build();
}
}
// 库存扣减服务(防超卖实现)
@Service
public class InventoryServiceImpl implements InventoryService {
@Autowired
private RedissonClient redissonClient;
@Transactional
public boolean deductStock(Long skuId, Integer quantity) {
String lockKey = "stock_lock:" + skuId;
RLock lock = redissonClient.getLock(lockKey);
try {
// 获取分布式锁(等待3秒,持有10秒)
if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {
GoodsSku sku = skuMapper.selectById(skuId);
if (sku.getStock() >= quantity) {
sku.setStock(sku.getStock() - quantity);
skuMapper.updateById(sku);
return true;
}
}
return false;
} finally {
lock.unlock();
}
}
}
3.3 支付系统集成
系统支持多种支付方式接入,采用策略模式实现支付渠道的灵活扩展:
java复制// 支付策略接口
public interface PaymentStrategy {
PaymentResult pay(PaymentRequest request);
PaymentResult query(String paymentNo);
PaymentResult refund(RefundRequest request);
}
// 微信支付实现
@Service
public class WechatPaymentStrategy implements PaymentStrategy {
@Override
public PaymentResult pay(PaymentRequest request) {
// 调用微信支付API
WxPayUnifiedOrderRequest wxRequest = new WxPayUnifiedOrderRequest();
wxRequest.setBody(request.getSubject());
wxRequest.setOutTradeNo(request.getOrderNo());
wxRequest.setTotalFee(request.getAmount().multiply(new BigDecimal(100)).intValue());
// 其他参数设置...
try {
WxPayUnifiedOrderResult result = wxPayService.unifiedOrder(wxRequest);
return PaymentResult.success(result.getPrepayId());
} catch (WxPayException e) {
return PaymentResult.fail(e.getErrCodeDes());
}
}
}
// 支付上下文
@Service
public class PaymentContext {
private Map<String, PaymentStrategy> strategyMap = new ConcurrentHashMap<>();
public PaymentContext(List<PaymentStrategy> strategies) {
strategies.forEach(strategy -> {
if (strategy instanceof WechatPaymentStrategy) {
strategyMap.put("wechat", strategy);
} else if (strategy instanceof AlipayPaymentStrategy) {
strategyMap.put("alipay", strategy);
}
// 其他支付方式...
});
}
public PaymentResult executePay(String channel, PaymentRequest request) {
PaymentStrategy strategy = strategyMap.get(channel);
if (strategy == null) {
throw new IllegalArgumentException("Unsupported payment channel");
}
return strategy.pay(request);
}
}
4. 移动端实现要点
4.1 UniApp跨端适配
UniApp项目结构组织建议:
code复制/src
/common # 公共工具类
/components # 跨端通用组件
/pages # 页面文件
/static # 静态资源
/store # Vuex状态管理
/uni_modules # 插件市场安装的模块
/wxcomponents # 微信小程序自定义组件
关键配置项(manifest.json):
json复制{
"name": "不二掌柜订货商城",
"appid": "",
"description": "",
"versionName": "1.0.0",
"versionCode": "100",
"transformPx": false,
"mp-weixin": {
"appid": "wx123456789",
"setting": {
"urlCheck": false,
"es6": true,
"postcss": true
},
"usingComponents": true,
"permission": {
"scope.userLocation": {
"desc": "用于配送地址定位"
}
}
}
}
4.2 性能优化实践
- 图片加载优化:
vue复制<template>
<image
:src="imageUrl"
mode="aspectFill"
lazy-load
:webp="true"
@error="handleImageError"
/>
</template>
<script>
export default {
methods: {
handleImageError(e) {
// 加载备用图片
this.imageUrl = '/static/images/default-product.png'
}
}
}
</script>
- 数据预加载策略:
javascript复制// 在onLoad时预加载下一页数据
onLoad() {
this.loadData()
// 预加载下一页
setTimeout(() => {
this.preloadNextPage()
}, 300)
},
methods: {
async preloadNextPage() {
const nextPage = this.currentPage + 1
const res = await api.getGoodsList({
page: nextPage,
size: this.pageSize
})
// 缓存数据到Vuex
this.$store.commit('goods/prependGoods', res.data.list)
}
}
- 分包加载配置:
json复制// pages.json
{
"subPackages": [
{
"root": "pagesA",
"pages": [
{"path": "goods/list", "style": {}},
{"path": "goods/detail", "style": {}}
]
},
{
"root": "pagesB",
"pages": [
{"path": "order/list", "style": {}},
{"path": "order/detail", "style": {}}
]
}
]
}
5. 二次开发指南
5.1 开发环境搭建
基础环境要求:
- JDK 1.8+
- MySQL 5.7+
- Redis 5.0+
- Node.js 14.x
- HBuilderX(最新稳定版)
后端项目初始化:
bash复制# 克隆项目
git clone https://github.com/example/erp-system.git
# 导入IDE(推荐IntelliJ IDEA)
File -> Open -> 选择pom.xml
# 数据库初始化
mysql -u root -p < docs/sql/init.sql
# 修改配置
vim src/main/resources/application-dev.yml
前端项目运行:
bash复制# 安装依赖
npm install
# 开发模式运行
npm run dev:%PLATFORM% # %PLATFORM%可以是h5、mp-weixin等
# 生产构建
npm run build:%PLATFORM%
5.2 常见定制需求实现
案例1:添加新的支付渠道
- 实现PaymentStrategy接口
- 在PaymentContext中注册新策略
- 前端添加支付方式选择UI
- 修改订单创建流程支持新支付方式
案例2:扩展商品属性
sql复制-- 添加商品扩展表
CREATE TABLE `mall_goods_ext` (
`goods_id` bigint(22) NOT NULL,
`spec_template` json DEFAULT NULL COMMENT '规格模板',
`after_service` varchar(255) DEFAULT NULL COMMENT '售后服务',
`logistics_info` varchar(255) DEFAULT NULL COMMENT '物流信息',
PRIMARY KEY (`goods_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
案例3:实现分销功能
- 设计分销关系表
sql复制CREATE TABLE `mall_distribution` (
`id` bigint(22) NOT NULL,
`user_id` bigint(22) NOT NULL COMMENT '分销商ID',
`parent_id` bigint(22) NOT NULL COMMENT '上级ID',
`level` tinyint(1) NOT NULL COMMENT '层级',
`path` varchar(255) NOT NULL COMMENT '关系路径',
`commission_rate` decimal(5,2) DEFAULT '0.00' COMMENT '佣金比例',
PRIMARY KEY (`id`),
KEY `idx_user` (`user_id`),
KEY `idx_path` (`path`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 修改订单结算流程计算分销佣金
- 添加分销中心页面和API接口
5.3 部署方案
生产环境推荐配置:
- 服务器:2核4G以上(按租户数量扩容)
- 数据库:MySQL主从复制+读写分离
- 缓存:Redis哨兵模式
- 消息队列:RabbitMQ集群
- 对象存储:OSS或七牛云
Docker部署示例:
dockerfile复制# 后端服务Dockerfile
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ADD target/erp-service.jar app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
Nginx配置要点:
nginx复制server {
listen 80;
server_name erp.example.com;
location / {
root /www/erp-web;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# 静态资源缓存
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 30d;
access_log off;
}
}
6. 项目经验总结
在实际部署和二次开发过程中,我总结了以下几个关键经验点:
-
性能调优重点:
- 数据库查询优化:为所有高频查询条件添加复合索引
- 缓存策略:采用多级缓存(Redis+本地缓存)减轻数据库压力
- 批量操作:使用MyBatis的批量插入代替循环单条插入
-
典型问题解决方案:
- 微信支付回调丢失:实现主动查询补单机制
- 库存超卖问题:采用Redis分布式锁+乐观锁双重保障
- 大文件上传:使用分片上传+断点续传方案
-
扩展性设计建议:
- 插件化架构:将支付、物流等功能设计为可插拔模块
- 配置中心:将业务规则改为可配置化
- 工作流引擎:复杂业务流程可采用Activiti等引擎驱动
这套源码作为基础框架非常合适,但在实际商业项目中还需要根据具体业务需求进行深度定制。特别是在高并发场景下,需要引入限流、熔断等保护机制。建议初次使用时先在小规模场景验证,再逐步扩大应用范围。