简介:这是一套基于Flutter开发的淘宝客(淘客)商城APP开源源码,面向移动端开发者、Flutter初学者及电商类应用实践者,旨在提供完整的跨平台淘客系统实现方案,涵盖商品展示、佣金结算、订单跟踪等核心淘客业务逻辑。资源包共589个文件,以254个Dart源码文件构成主体架构,辅以165个PNG和11个JPG图像资源、47个SVG矢量图标、14个GIF动效素材,以及13个Android原生AAR依赖库(含AlibcTradeBiz、alibabauth_core等淘宝联盟SDK组件),整体压缩包大小为34.17MB,结构清晰,便于模块化学习与二次开发。已有117人下载学习,适合希望快速掌握Flutter+淘客SDK集成、理解跨端电商APP工程组织方式的开发者。读者可直接运行调试完整APP,深入分析淘客登录授权、商品API对接、安全加固(SGM/UT/SecurityGuard等阿里系AAR)及多端适配实现细节。
1. 这不是又一个“仿淘宝”Demo:它是一套跑在真机上的淘客闭环系统,含佣金结算、商品同步、推广链接生成与多端适配逻辑
你搜“Flutter 淘宝客源码”,90%结果是空壳界面:首页轮播图能点,商品列表能滑,但点进去——404,跳转失败,分享按钮点完没反应,后台根本没连上淘宝联盟API。而这份「苏分宝·店流宝」开源项目,我用真机(Android 14 + iOS 17.5)从注册账号、拉取佣金商品、生成带PID的淘客链接、唤起手淘/京东/拼多多客户端,到最终在微信里成功转发并追踪点击,全流程走通了三次。它不是教学Demo,而是按真实淘客运营场景拆解出的最小可行系统:包含完整的Flutter层状态管理(Cubit为主)、原生侧(Android Java / iOS Objective-C)与淘宝联盟SDK的桥接封装、动态权限申请、深链接回传处理,以及关键的「佣金预估+订单同步」双通道机制。适合两类人:想快速上线轻量淘客工具的个体开发者,或需要在现有App中嵌入淘客模块的Android/iOS团队——尤其当你已卡在「Flutter调用Java组件」或「iOS侧EventChannel回调丢失」这类黑匣子问题上时,它的桥接层代码就是现成的对照答案。
2. 从源码结构看设计意图:为什么选Cubit而非Bloc?为什么Android用Java而非Kotlin?为什么iOS不直接用Swift?
2.1 目录骨架解析:四个核心模块决定能否真正落地
项目根目录下可见清晰分层:
lib/ ├── main.dart # 入口:初始化Cubit、配置全局Theme、注入Dio实例 ├── core/ # 基础设施:网络拦截器(自动加淘客PID)、本地缓存(SharedPreferences封装)、路由守卫(未登录跳登录页) ├── features/ # 功能域:home(首页商品流)、product(商品详情+推广生成)、order(订单同步页)、me(账户与佣金) ├── services/ # 原生桥接:android/(Java实现)、ios/(Objective-C实现)、platform_channel.dart(统一Channel定义) └── utils/ # 工具类:淘客链接生成器(含加密签名)、PID管理器(多渠道PID池轮询)提示:
services/platform_channel.dart是整个项目的中枢神经。它不直接写业务逻辑,只定义MethodChannel名称、方法名、参数键名(如'getCommissionRate'、'generateTaoBaoLink'),所有原生侧实现必须严格匹配——这是跨端通信不翻车的第一道防线。
2.2 状态管理选型:Cubit比Bloc更轻量,且规避了EventChannel回调丢失的玄学问题
项目大量使用Cubit(而非Bloc),原因很实际:
- 无事件总线干扰:淘客场景中,用户点击“复制链接”后需立即触发原生侧生成链接并返回结果。若用
Bloc,需先add(CopyLinkEvent()),再由mapEventToState触发Channel调用——中间多一层事件队列,当用户快速连点时,EventChannel的异步回调可能因Flutter引擎调度延迟而丢失。 - Cubit直触Channel:
CopyLinkCubit中直接调用PlatformChannel.generateTaoBaoLink(),返回Future<void>,并在onSuccess回调中emit(state.copyWith(isLoading: false))。实测在低端机(Redmi Note 9)上,连点5次无一次回调丢失。 - 状态粒度精准:每个页面对应独立Cubit(如
ProductDetailCubit),状态仅含ProductModel、isGeneratingLink、shareUrl三字段,避免Bloc中常见的“状态爆炸”——当你要同时管理商品详情、评论加载、佣金预估、分享弹窗开关时,Cubit的扁平状态树更易调试。
2.3 原生侧技术栈选择:Java与Objective-C是淘客SDK兼容性的现实妥协
- Android侧坚持Java:淘宝联盟官方Android SDK(
taobao-sdk-android-3.8.0.jar)仅提供Java接口,且其内部依赖android.support.v4(非AndroidX)。若强行用Kotlin重写桥接层,需额外做AndroidX迁移+反射适配,而项目中TbkManager.java直接继承Application并初始化SDK,省去所有兼容层。 - iOS侧用Objective-C:阿里系iOS SDK(
TBKSDK.framework)头文件为.h格式,Objective-C可直接#import;若用Swift,需建.h桥接头并处理@objc标记,而淘客链路中TBKSDK的handleOpenURL方法需在AppDelegate.m中强绑定,Objective-C写法更贴近SDK文档示例。 - 关键验证点:
ios/Classes/TBKHandler.m中第47行[[TBKSDK sharedInstance] handleOpenURL:url options:options];必须放在application:openURL:options:的第一行——否则手淘唤起后无法回传淘客参数,这是血泪经验。
3. 原生桥接实战:Android Java层如何安全调用淘宝联盟SDK?iOS Objective-C层如何捕获手淘回传参数?
3.1 Android侧:Java桥接层完整流程与关键参数说明
android/app/src/main/java/com/example/sufenbao/bridge/TbkManager.java是核心:
public class TbkManager { private static final String CHANNEL_NAME = "sufenbao/tbk"; private static MethodChannel channel; private static Context context; public static void registerWith(Registrar registrar) { context = registrar.context(); channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME); channel.setMethodCallHandler(new TbkMethodHandler()); } private static class TbkMethodHandler implements MethodChannel.MethodCallHandler { @Override public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { switch (call.method) { case "initTbkSdk": initTbkSdk(call, result); break; case "generateTaoBaoLink": generateTaoBaoLink(call, result); break; case "getOrderList": getOrderList(call, result); break; default: result.notImplemented(); } } private void initTbkSdk(MethodCall call, MethodChannel.Result result) { String appKey = call.argument("appKey"); String appSecret = call.argument("appSecret"); String pid = call.argument("pid"); // 注意:此处pid为"mm_xxx_xxx_xxx"格式,非数字ID try { // 淘宝联盟SDK初始化必须在Application.onCreate()中完成,此处仅校验 if (TBKManager.getInstance().isInited()) { result.success(true); } else { result.error("INIT_FAILED", "TBK SDK not initialized in Application", null); } } catch (Exception e) { result.error("INIT_ERROR", e.getMessage(), null); } } private void generateTaoBaoLink(MethodCall call, MethodChannel.Result result) { String itemId = call.argument("itemId"); String materialId = call.argument("materialId"); // 淘宝联盟后台获取的物料ID String adzoneId = call.argument("adzoneId"); // 广告位ID String pid = call.argument("pid"); // 关键:TBKSDK生成链接需传入Map<String, String>参数,非JSON字符串 Map<String, String> params = new HashMap<>(); params.put("itemId", itemId); params.put("materialId", materialId); params.put("adzoneId", adzoneId); params.put("pid", pid); params.put("clickUrl", "https://s.click.taobao.com/xxx"); // 可选:自定义跳转页 try { String link = TBKManager.getInstance().genTaoBaoLink(params); result.success(link); // 返回纯文本链接,Flutter侧自行包装成Uri } catch (Exception e) { result.error("LINK_GEN_ERROR", e.getMessage(), null); } } } }参数说明与避坑点:
pid必须为"mm_12345678_12345678_12345678"格式,淘宝联盟后台「推广管理」→「创建PID」生成,不能用数字ID替代;materialId是淘宝联盟「选品库」中商品的唯一标识,不是淘宝商品ID(num_iid),需调用/api/item/detail接口获取;adzoneId是广告位ID,在「推广管理」→「新建广告位」中创建,每个广告位对应独立曝光数据统计;genTaoBaoLink()返回的链接含&union_lens=lensId参数,此为淘宝联盟防作弊标识,不可手动删除或修改,否则佣金失效。
3.2 iOS侧:Objective-C桥接层捕获手淘回传的深度链接参数
ios/Classes/TBKHandler.m中关键逻辑:
// AppDelegate.m 中已注册 URL Scheme:sufenbao:// - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options { // 必须先调用TBKSDK处理,否则无法解析淘客参数 [[TBKSDK sharedInstance] handleOpenURL:url options:options]; // 解析TBKSDK回传的淘客参数 NSDictionary *tbkParams = [TBKSDK sharedInstance].tbkParams; if (tbkParams && tbkParams.count > 0) { // 将参数转为JSON字典,通过EventChannel发送给Flutter NSError *error; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:tbkParams options:0 error:&error]; if (!error) { NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; // 发送至Flutter侧的EventChannel [self.eventSink withString:jsonString]; } } return YES; } // Flutter侧监听EventChannel // platform_channel.dart中定义: // static const EventChannel _eventChannel = EventChannel('sufenbao/tbk_event');关键验证步骤:
- 在Xcode中为Target添加URL Types,Identifier填
sufenbao,URL Schemes填sufenbao; - 在
Info.plist中添加:
<key>LSApplicationQueriesSchemes</key> <array> <string>taobao</string> <string>tmall</string> <string>sn</string> <!-- 苏宁 --> </array>- 手淘唤起测试:在手机浏览器访问
taobao://item?id=678901234567→ 点击「打开手淘」→ 手淘内完成浏览 → 返回App,检查tbkParams是否含tk_status(1=成交,2=付款,3=确认收货)及tk_price(佣金金额)。
4. 避坑指南:淘客链路中最常翻车的5个节点与现场排查法
4.1 现象:Flutter侧调用generateTaoBaoLink()后无响应,控制台无报错
原因:Androidbuild.gradle中未正确配置minifyEnabled false,ProGuard混淆了TBKSDK的类名。淘宝联盟SDK内部大量使用反射,混淆后Class.forName("com.taobao.tbk.xxx")失败。
解决:在android/app/build.gradle的buildTypes.release块中添加:
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'并在proguard-rules.pro中加入:
-keep class com.taobao.tbk.** { *; } -keep class com.taobao.sdk.** { *; } -dontwarn com.taobao.tbk.**4.2 现象:iOS侧手淘唤起后返回App,tbkParams为空字典
原因:AppDelegate.m中handleOpenURL调用位置错误,或未在application:didFinishLaunchingWithOptions:中初始化TBKSDK。
解决:
- 确保
TBKSDK初始化在didFinishLaunchingWithOptions第一行:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [[TBKSDK sharedInstance] setupWithAppKey:@"your_app_key" appSecret:@"your_app_secret"]; // ... 其他初始化 }handleOpenURL必须在application:openURL:options:中最先执行,且返回值必须为YES。
4.3 现象:生成的淘客链接点击后跳转手淘失败,提示“链接无效”
原因:materialId或adzoneId未在淘宝联盟后台启用,或PID未绑定当前App包名(Android)/Bundle ID(iOS)。
解决:
- 登录淘宝联盟后台 → 「推广管理」→ 「PID管理」→ 找到对应PID → 点击「编辑」→ 在「应用绑定」中添加Android包名(如
com.example.sufenbao)和iOS Bundle ID(如com.example.sufenbao); - 「选品库」中搜索商品,确认该商品的
materialId状态为「已启用」,且所属「推广计划」已开启。
4.4 现象:Flutter侧Cubit状态更新后UI不刷新,但print()显示state已变
原因:ProductDetailPage中未使用BlocBuilder或CubitBuilder,而是直接widget.cubit.state访问状态——Cubit状态变更时,StatefulWidget不会自动重建。
解决:必须用CubitBuilder包裹UI:
CubitBuilder<ProductDetailCubit, ProductDetailState>( builder: (context, state) { if (state is ProductDetailLoading) return CircularProgressIndicator(); if (state is ProductDetailLoaded) return ProductDetailView(product: state.product); return Container(); }, )4.5 现象:Android真机上首次安装App后,调用initTbkSdk()报错ClassNotFoundException: com.taobao.tbk.TBKManager
原因:taobao-sdk-android-3.8.0.jar未正确放入android/app/libs/目录,或build.gradle中未配置flatDir仓库。
解决:
- 将JAR包拷贝至
android/app/libs/; - 在
android/app/build.gradle的repositories块中添加:
flatDir { dirs 'libs' }- 在
dependencies中添加:
implementation(name: 'taobao-sdk-android-3.8.0', ext: 'jar')5. 订单同步与佣金预估:如何让淘客系统真正产生商业价值?
5.1 订单同步双通道机制:主动拉取 + 被动通知
淘客系统的核心价值不在“生成链接”,而在“确认成交”。本项目采用双通道保障订单数据不丢:
| 通道类型 | 触发时机 | 数据来源 | 优势 | 缺陷 |
|---|---|---|---|---|
| 主动拉取 | 用户进入「我的订单」页时 | 调用淘宝联盟/api/order/list接口 | 数据全量、可控、可分页 | 需用户手动刷新,实时性差 |
| 被动通知 | 手淘完成付款后唤起App | iOStbkParams中的tk_status=2、AndroidIntent中的extra_tk_status | 实时性强,用户体验好 | 依赖用户返回App,存在漏单 |
Flutter侧同步逻辑(order_cubit.dart):
class OrderCubit extends Cubit<OrderState> { final OrderRepository _repository; OrderCubit(this._repository) : super(OrderInitial()); Future<void> fetchOrders() async { emit(OrderLoading()); try { final List<OrderModel> orders = await _repository.getOrders( startTime: DateTime.now().subtract(Duration(days: 30)), endTime: DateTime.now(), ); emit(OrderLoaded(orders)); } catch (e) { emit(OrderError(e.toString())); } } // 被动接收手淘回传 void onTbkEventReceived(String json) { final Map<String, dynamic> params = jsonDecode(json); if (params['tk_status'] == 2) { // 付款成功 final order = OrderModel( orderId: params['order_id'], price: double.parse(params['tk_price']), status: 'paid', createTime: DateTime.now(), ); // 插入本地数据库,并触发UI更新 _repository.saveOrder(order); emit(OrderUpdated(order)); } } }5.2 佣金预估:在用户点击前就显示“预计赚¥X.XX”
真实淘客场景中,用户决策关键点是「看到佣金才点」。项目在商品列表页即调用淘宝联盟/api/item/detail接口预估佣金:
// lib/features/home/cubit/home_cubit.dart Future<void> loadHomeItems() async { emit(HomeLoading()); try { final List<ItemModel> items = await _repository.getHomeItems(); // 并行预估每件商品佣金(限流:最多3个并发) final List<Future> estimateFutures = items.take(3).map((item) async { final commission = await _commissionService.estimateCommission( itemId: item.itemId, pid: _pidManager.currentPid, ); item.commission = commission; // 注入预估佣金 }).toList(); await Future.wait(estimateFutures); emit(HomeLoaded(items)); } catch (e) { emit(HomeError(e.toString())); } }关键参数说明:
estimateCommission()内部调用/api/item/detail,传参fields=zk_final_price,num_iid,title,pict_url,item_url,commission_rate,commission_type;commission_rate为百分比(如5.0表示5%),需乘以zk_final_price得到预估佣金;- 注意:该接口有QPS限制(100次/分钟),生产环境需加本地缓存(如
shared_preferences存储itemId → commission映射,有效期2小时)。
5.3 PID轮询策略:避免单PID封禁,提升整体转化率
淘宝联盟对单PID有严格风控:同一PID 24小时内点击超5000次,或转化率低于0.5%,将触发限流。项目实现PID池轮询:
// lib/utils/pid_manager.dart class PidManager { final List<String> _pids = [ 'mm_12345678_12345678_12345678', 'mm_87654321_87654321_87654321', 'mm_11223344_11223344_11223344', ]; int _currentIndex = 0; String get currentPid { final pid = _pids[_currentIndex]; _currentIndex = (_currentIndex + 1) % _pids.length; return pid; } // 生产环境建议升级为:按PID历史转化率加权轮询 // 转化率高者概率权重+30%,避免低效PID持续占用流量 }从那以后我每次上线新淘客功能,都强制走一遍「生成链接→手淘唤起→付款→返回App→订单同步」全链路,哪怕只是改了一行setState()。因为淘客系统的脆弱性不在代码,而在淘宝联盟API的瞬时波动、手淘版本的兼容性、甚至用户手机里是否装了某款清理软件——只有真机跑通三次,才能把“理论上可行”变成“明天就能收款”。希望帮到你。
本文还有配套的精品资源,点击获取