# swipeflow **Repository Path**: singlr/swipeflow ## Basic Information - **Project Name**: swipeflow - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-07 - **Last Updated**: 2026-09-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README
# 刷Flow (SwipeFlow) **轻量级网页版短视频播放器 · 类抖音交互体验 · 多数据源聚合** ![.NET 8](https://img.shields.io/badge/.NET-8-blue) ![Furion](https://img.shields.io/badge/Furion-4.9+-green) ![SqlSugar](https://img.shields.io/badge/SqlSugar-5.1+-orange) ![Vben Admin](https://img.shields.io/badge/Vben%20Admin-5.0-purple) ![Element Plus](https://img.shields.io/badge/Element%20Plus-2.x-blue) ![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue) ![License](https://img.shields.io/badge/License-MIT-green) ![Status](https://img.shields.io/badge/Status-Design%20Planning-yellow)
--- ## 项目简介 **刷Flow** 是一个基于 Web 的短视频播放器应用,提供类似抖音的竖屏全屏、上下滑动切换的沉浸式浏览体验。支持对接多种数据源——包括通过用户 Token 自动同步今日头条收藏视频、后台手动录入等,可灵活扩展内容来源。 ### 核心特性 - **沉浸式播放** — 竖屏全屏、上下滑动切换、自动播放/暂停 - **移动端适配** — 手势操作、内联播放、触屏优化 - **多数据源** — 支持头条收藏同步(用户 Token)、后台手动录入、批量导入 - **用户互动** — 前端用户登录后可点赞、收藏视频 - **流畅体验** — 视频预加载、IntersectionObserver 智能播放控制 - **后台管理** — 基于 Vben Admin 5 + Element Plus 的可视化内容管理系统 - **高性能后端** — .NET 8 + Furion + SqlSugar,零配置 RESTful API 自动生成 ## 技术架构 ``` ┌──────────────────────────────────────────────────┐ │ 前端播放器 (SPA) │ │ Vue 3 + Vite + CSS Scroll Snap + xgplayer │ └──────────────────────┬───────────────────────────┘ │ RESTful API ┌──────────────────────▼───────────────────────────┐ │ 后端服务层 (.NET 8) │ │ Furion 4.9+ (动态 WebAPI · JWT 鉴权 · │ │ 数据验证 · 事件总线 · 定时任务 · 缓存) │ ├──────────────────────────────────────────────────┤ │ ORM 层 (SqlSugar 5.1+) │ │ CodeFirst · 链式查询 · 分页 · SQLite 适配 │ └──────────────────────┬───────────────────────────┘ │ ┌──────────────────────▼───────────────────────────┐ │ 数据源层 │ │ SQLite (主库) · 头条用户 Token 同步 · OSS/CDN │ └──────────────────────────────────────────────────┘ ┌──────────────────────────────────────────────────┐ │ 管理后台 (SPA) │ │ Vben Admin 5.0 + Element Plus + TypeScript │ │ Monorepo (pnpm + Turborepo) │ │ 动态路由 · 按钮级权限 · 国际化 · 主题切换 │ └──────────────────────────────────────────────────┘ ``` ### 技术栈详情 | 层级 | 技术选型 | 说明 | |------|----------|------| | **后端框架** | .NET 8 + Furion 4.9+ | 动态 WebAPI、依赖注入、数据验证、JWT 鉴权、事件总线、缓存、定时任务 | | **ORM** | SqlSugar 5.1+ | 高性能 ORM,CodeFirst 自动建表、链式查询、分页、SQLite 适配 | | **数据库** | SQLite | 轻量级嵌入式数据库,零配置部署,适合中小规模应用 | | **管理后台** | Vben Admin 5.0 + Element Plus | Monorepo 架构,动态路由、细粒度权限、国际化、多主题 | | **前端播放器** | Vue 3 + Vite + xgplayer | 竖屏滑动播放器,CSS Scroll Snap + IntersectionObserver | | **播放器内核** | xgplayer(西瓜播放器) | 字节跳动开源,中文文档完善,自定义插件丰富 | ## 快速开始 ### 环境要求 | 工具 | 版本要求 | 说明 | |------|----------|------| | .NET SDK | >= 8.0 | 后端运行环境 | | Node.js | >= 18.x(推荐 18.18+ LTS) | 前端 & 管理后台运行环境 | | pnpm | >= 8.x | Vben Admin 5 强制使用 pnpm 管理依赖 | | Git | 最新版 | 代码版本管理 | ### 安装步骤 #### 1. 克隆项目 ```bash git clone https://gitee.com/singlr/swipeflow.git cd swipeflow ``` #### 2. 后端启动(.NET 8 + Furion + SqlSugar) ```bash cd server/SwipeFlow.Web.Entry # 还原 NuGet 包 dotnet restore # 启动项目(首次启动自动创建 SQLite 数据库及表结构) dotnet run ``` 后端默认运行在 `http://localhost:5002`,Swagger 文档地址:`http://localhost:5002/swagger` > Furion 的 SqlSugar 集成支持 CodeFirst 模式,首次启动时会自动根据实体类创建 SQLite 数据库和表结构,无需手动建表。 #### 3. 管理后台启动(Vben Admin 5 + Element Plus) ```bash cd admin # 安装依赖(仅支持 pnpm) pnpm install # 启动 Element Plus 版本 pnpm dev:ele ``` 管理后台默认运行在 `http://localhost:8003` #### 4. 前端播放器启动 ```bash cd client # 安装依赖 pnpm install # 启动开发服务器 pnpm dev ``` 前端播放器默认运行在 `http://localhost:8004` ## 项目结构(规划) ``` swipeflow/ ├── server/ # 后端项目(.NET 8 + Furion + SqlSugar) │ ├── SwipeFlow.Web.Entry/ # Web 入口层(启动项目) │ │ ├── Program.cs # 应用入口(Serve.Run()) │ │ ├── appsettings.json # 应用配置 │ │ └── appsettings.Development.json │ │ │ ├── SwipeFlow.Application/ # 应用层(业务逻辑) │ │ ├── Controllers/ # 动态 WebAPI 控制器 │ │ │ ├── VideoController.cs # 视频管理接口 │ │ │ ├── DataSourceController.cs # 数据源管理接口 │ │ │ ├── AuthController.cs # 认证接口 │ │ │ ├── UserController.cs # 用户管理接口 │ │ │ ├── DeptController.cs # 部门管理接口 │ │ │ ├── RoleController.cs # 角色管理接口 │ │ │ ├── MenuController.cs # 菜单管理接口 │ │ │ └── ConfigController.cs # 配置管理接口 │ │ ├── Services/ # 业务服务 │ │ │ ├── VideoService.cs # 视频业务逻辑 │ │ │ ├── BaseSyncService.cs # 同步服务抽象基类 │ │ │ ├── ToutiaoSyncService.cs # 头条收藏同步服务 │ │ │ ├── DouyinSyncService.cs # 抖音收藏同步服务 │ │ │ ├── InteractionService.cs # 点赞/收藏业务逻辑 │ │ │ ├── DataSourceService.cs # 数据源聚合逻辑 │ │ │ ├── UserService.cs # 用户管理服务 │ │ │ ├── DeptService.cs # 部门管理服务 │ │ │ ├── RoleService.cs # 角色管理服务 │ │ │ ├── MenuService.cs # 菜单管理服务 │ │ │ └── ConfigService.cs # 配置管理服务 │ │ └── Dtos/ # 数据传输对象 │ │ │ ├── SwipeFlow.Core/ # 核心层(实体 & 配置) │ │ ├── Entities/ # 数据库实体(SqlSugar CodeFirst) │ │ │ ├── VideoInfo.cs # 视频信息实体 │ │ │ ├── VideoCategory.cs # 视频分类(频道)实体 │ │ │ ├── ThirdPartyAuth.cs # 第三方登录配置实体 │ │ │ ├── VideoLike.cs # 用户点赞记录 │ │ │ ├── VideoFavorite.cs # 用户收藏记录 │ │ │ ├── DataSource.cs # 数据源配置实体 │ │ │ ├── SysUser.cs # 系统用户实体 │ │ │ ├── SysDept.cs # 部门实体 │ │ │ ├── SysRole.cs # 角色实体 │ │ │ ├── SysMenu.cs # 菜单实体 │ │ │ ├── SysConfig.cs # 系统配置实体 │ │ │ ├── SysUserRole.cs # 用户角色关联 │ │ │ └── SysRoleMenu.cs # 角色菜单关联 │ │ ├── Enums/ # 枚举定义 │ │ └── Options/ # 配置选项类 │ │ │ ├── SwipeFlow.Database/ # 数据库层 │ │ ├── SqlSugarSetup.cs # SqlSugar 服务注册与配置 │ │ ├── DbContext.cs # 数据库上下文 │ │ └── SeedData.cs # 种子数据初始化 │ │ │ └── SwipeFlow.sln # 解决方案文件 │ ├── admin/ # 管理后台(Vben Admin 5 + Element Plus) │ ├── apps/ │ │ └── web-ele/ # Element Plus 子应用 │ │ ├── src/ │ │ │ ├── api/ # 接口请求(对接后端 Furion API) │ │ │ │ ├── video.ts # 视频管理接口 │ │ │ │ ├── datasource.ts # 数据源接口 │ │ │ │ ├── auth.ts # 认证接口 │ │ │ │ ├── user.ts # 用户管理接口 │ │ │ │ ├── dept.ts # 部门管理接口 │ │ │ │ ├── role.ts # 角色管理接口 │ │ │ │ ├── menu.ts # 菜单管理接口 │ │ │ │ └── config.ts # 配置管理接口 │ │ │ ├── views/ # 页面视图 │ │ │ │ ├── video/ # 视频管理页面 │ │ │ │ ├── datasource/ # 数据源管理页面 │ │ │ │ ├── dashboard/ # 仪表盘 │ │ │ │ └── system/ # 系统管理 │ │ │ │ ├── user/ # 用户管理 │ │ │ │ ├── dept/ # 部门管理 │ │ │ │ ├── role/ # 角色管理 │ │ │ │ ├── menu/ # 菜单管理 │ │ │ │ └── config/ # 配置管理 │ │ │ ├── router/ # 路由配置 │ │ │ └── store/ # Pinia 状态管理 │ │ ├── .env.development # 开发环境变量 │ │ └── vite.config.mts # Vite 配置 │ ├── packages/ # Monorepo 共享包 │ │ ├── @core/ # 核心基础包 │ │ └── effects/ # 副作用包(权限、Hooks、布局) │ ├── pnpm-workspace.yaml # pnpm workspace 配置 │ ├── turbo.json # Turborepo 配置 │ └── package.json │ ├── client/ # 前端播放器(Vue 3 + Vite) │ ├── src/ │ │ ├── components/ │ │ │ ├── VideoPlayer/ # 播放器核心组件(基于 xgplayer) │ │ │ ├── VideoFeed/ # 视频流容器(CSS Scroll Snap 滑动切换) │ │ │ ├── ActionBar/ # 侧边操作栏(点赞/收藏/分享) │ │ │ ├── UserInfo/ # 用户信息浮层 │ │ │ └── AuthPanel/ # 用户登录面板 │ │ ├── composables/ │ │ │ ├── useVideoPlayer.ts # 播放逻辑 Hook │ │ │ ├── useSwipe.ts # 滑动手势 Hook │ │ │ └── usePreload.ts # 预加载策略 Hook │ │ ├── api/ # 接口请求封装 │ │ │ ├── video.ts # 视频接口 │ │ │ ├── auth.ts # 用户认证接口 │ │ │ └── interaction.ts # 点赞/收藏接口 │ │ ├── stores/ # Pinia 状态管理 │ │ │ ├── video.ts # 视频列表状态 │ │ │ └── user.ts # 当前用户状态 │ │ ├── App.vue │ │ └── main.ts │ ├── index.html │ ├── vite.config.ts │ └── package.json │ ├── docs/ # 项目文档 ├── docker-compose.yml # Docker 一键部署 ├── LICENSE └── README.md ``` ## 后端配置说明 ### SqlSugar 数据库配置(appsettings.json) ```json { "ConnectionConfigs": [ { "DbType": "Sqlite", "ConnectionString": "DataSource=./swipeflow.db", "EnableUnderLine": false, "EnableDiffLog": false, "IsAutoCloseConnection": true, "InitKeyType": "Attribute" } ] } ``` ### Furion 服务注册(SqlSugarSetup.cs) ```csharp public static void AddSqlSugarSetup(this IServiceCollection services) { var config = App.Configuration.GetSection("ConnectionConfigs") .Get>(); var db = new SqlSugarScope(config, db => { // 开发环境输出 SQL 日志 db.Aop.OnLogExecuting = (sql, pars) => { App.GetService>() .LogInformation("SQL: {Sql}", sql); }; }); services.AddSingleton(db); } ``` ### 实体示例(CodeFirst 自动建表) ```csharp [SugarTable("video_info", TableDescription = "视频信息表")] public class VideoInfo : EntityBase { [SugarColumn(ColumnName = "title", ColumnDescription = "视频标题", Length = 200)] public string Title { get; set; } [SugarColumn(ColumnName = "cover_url", ColumnDescription = "封面地址", Length = 500)] public string CoverUrl { get; set; } [SugarColumn(ColumnName = "video_url", ColumnDescription = "视频播放地址", Length = 500)] public string VideoUrl { get; set; } [SugarColumn(ColumnName = "author", ColumnDescription = "作者", Length = 100)] public string Author { get; set; } [SugarColumn(ColumnName = "source", ColumnDescription = "来源标识", Length = 50)] public string Source { get; set; } [SugarColumn(ColumnName = "duration", ColumnDescription = "时长(秒)")] public int Duration { get; set; } [SugarColumn(ColumnName = "channel_id", ColumnDescription = "所属频道ID")] public int? ChannelId { get; set; } [SugarColumn(ColumnName = "like_count", ColumnDescription = "点赞数")] public int LikeCount { get; set; } = 0; [SugarColumn(ColumnName = "favorite_count", ColumnDescription = "收藏数")] public int FavoriteCount { get; set; } = 0; [SugarColumn(ColumnName = "sort_order", ColumnDescription = "排序")] public int SortOrder { get; set; } [SugarColumn(ColumnName = "status", ColumnDescription = "状态: 0正常 1垃圾箱 2已删除")] public int Status { get; set; } = 0; } ``` ### 用户互动实体示例 ```csharp [SugarTable("video_like", TableDescription = "用户点赞记录表")] public class VideoLike { [SugarColumn(IsPrimaryKey = true, ColumnName = "video_id", ColumnDescription = "视频ID")] public long VideoId { get; set; } [SugarColumn(IsPrimaryKey = true, ColumnName = "user_id", ColumnDescription = "用户ID")] public long UserId { get; set; } [SugarColumn(ColumnName = "create_time", ColumnDescription = "点赞时间")] public DateTime CreateTime { get; set; } = DateTime.Now; } [SugarTable("video_favorite", TableDescription = "用户收藏记录表")] public class VideoFavorite { [SugarColumn(IsPrimaryKey = true, ColumnName = "video_id", ColumnDescription = "视频ID")] public long VideoId { get; set; } [SugarColumn(IsPrimaryKey = true, ColumnName = "user_id", ColumnDescription = "用户ID")] public long UserId { get; set; } [SugarColumn(ColumnName = "create_time", ColumnDescription = "收藏时间")] public DateTime CreateTime { get; set; } = DateTime.Now; } ``` ### 系统管理实体示例 ```csharp [SugarTable("sys_dept", TableDescription = "部门表")] public class SysDept : EntityBase { [SugarColumn(ColumnName = "parent_id", ColumnDescription = "上级部门ID")] public long? ParentId { get; set; } [SugarColumn(ColumnName = "dept_name", ColumnDescription = "部门名称", Length = 100)] public string DeptName { get; set; } [SugarColumn(ColumnName = "order_num", ColumnDescription = "排序")] public int OrderNum { get; set; } [SugarColumn(ColumnName = "status", ColumnDescription = "状态: 0正常 1停用")] public int Status { get; set; } = 0; } [SugarTable("sys_role", TableDescription = "角色表")] public class SysRole : EntityBase { [SugarColumn(ColumnName = "role_name", ColumnDescription = "角色名称", Length = 100)] public string RoleName { get; set; } [SugarColumn(ColumnName = "role_key", ColumnDescription = "角色权限标识", Length = 100)] public string RoleKey { get; set; } [SugarColumn(ColumnName = "order_num", ColumnDescription = "排序")] public int OrderNum { get; set; } [SugarColumn(ColumnName = "status", ColumnDescription = "状态: 0正常 1停用")] public int Status { get; set; } = 0; } [SugarTable("sys_menu", TableDescription = "菜单表")] public class SysMenu : EntityBase { [SugarColumn(ColumnName = "parent_id", ColumnDescription = "上级菜单ID")] public long? ParentId { get; set; } [SugarColumn(ColumnName = "menu_name", ColumnDescription = "菜单名称", Length = 100)] public string MenuName { get; set; } [SugarColumn(ColumnName = "menu_type", ColumnDescription = "菜单类型: M目录 C菜单 F按钮")] public string MenuType { get; set; } [SugarColumn(ColumnName = "path", ColumnDescription = "路由地址", Length = 200)] public string Path { get; set; } [SugarColumn(ColumnName = "component", ColumnDescription = "组件路径", Length = 200)] public string Component { get; set; } [SugarColumn(ColumnName = "perms", ColumnDescription = "权限标识", Length = 200)] public string Perms { get; set; } [SugarColumn(ColumnName = "icon", ColumnDescription = "菜单图标", Length = 100)] public string Icon { get; set; } [SugarColumn(ColumnName = "order_num", ColumnDescription = "排序")] public int OrderNum { get; set; } [SugarColumn(ColumnName = "status", ColumnDescription = "状态: 0正常 1停用")] public int Status { get; set; } = 0; } [SugarTable("sys_config", TableDescription = "系统配置表")] public class SysConfig : EntityBase { [SugarColumn(ColumnName = "config_name", ColumnDescription = "配置名称", Length = 100)] public string ConfigName { get; set; } [SugarColumn(ColumnName = "config_key", ColumnDescription = "配置键", Length = 100)] public string ConfigKey { get; set; } [SugarColumn(ColumnName = "config_value", ColumnDescription = "配置值", Length = 500)] public string ConfigValue { get; set; } [SugarColumn(ColumnName = "config_type", ColumnDescription = "配置类型: 0系统 1自定义")] public int ConfigType { get; set; } = 1; [SugarColumn(ColumnName = "remark", ColumnDescription = "备注", Length = 500)] public string Remark { get; set; } } [SugarTable("sys_user_role", TableDescription = "用户角色关联表")] public class SysUserRole { [SugarColumn(IsPrimaryKey = true, ColumnName = "user_id", ColumnDescription = "用户ID")] public long UserId { get; set; } [SugarColumn(IsPrimaryKey = true, ColumnName = "role_id", ColumnDescription = "角色ID")] public long RoleId { get; set; } } [SugarTable("sys_role_menu", TableDescription = "角色菜单关联表")] public class SysRoleMenu { [SugarColumn(IsPrimaryKey = true, ColumnName = "role_id", ColumnDescription = "角色ID")] public long RoleId { get; set; } [SugarColumn(IsPrimaryKey = true, ColumnName = "menu_id", ColumnDescription = "菜单ID")] public long MenuId { get; set; } } ``` ### 第三方登录配置实体示例 ```csharp [SugarTable("third_party_auth", TableDescription = "第三方登录配置表")] public class ThirdPartyAuth : EntityBase { [SugarColumn(ColumnName = "site_name", ColumnDescription = "站点名称", Length = 100)] public string SiteName { get; set; } [SugarColumn(ColumnName = "username", ColumnDescription = "用户名", Length = 100)] public string Username { get; set; } [SugarColumn(ColumnName = "password", ColumnDescription = "密码", Length = 200)] public string Password { get; set; } [SugarColumn(ColumnName = "site_url", ColumnDescription = "站点网址", Length = 500)] public string SiteUrl { get; set; } [SugarColumn(ColumnName = "api_url", ColumnDescription = "接口地址", Length = 500)] public string ApiUrl { get; set; } [SugarColumn(ColumnName = "cookies", ColumnDescription = "登录Cookie", Length = -1)] public string Cookies { get; set; } [SugarColumn(ColumnName = "token", ColumnDescription = "访问令牌", Length = 500)] public string Token { get; set; } [SugarColumn(ColumnName = "login_time", ColumnDescription = "登录时间")] public DateTime? LoginTime { get; set; } [SugarColumn(ColumnName = "expire_time", ColumnDescription = "失效时间")] public DateTime? ExpireTime { get; set; } [SugarColumn(ColumnName = "is_valid", ColumnDescription = "是否有效: 0无效 1有效")] public int IsValid { get; set; } = 1; [SugarColumn(ColumnName = "remark", ColumnDescription = "备注", Length = 500)] public string Remark { get; set; } } ``` ### 动态 WebAPI 示例(Furion) ```csharp public class VideoService : IDynamicApiController { private readonly ISqlSugarClient _db; public VideoService(ISqlSugarClient db) { _db = db; } /// /// 获取视频列表(分页) /// public async Task> GetList(int page = 1, int size = 10) { var total = 0; var list = await _db.Queryable() .Where(v => v.Status == 1) .OrderBy(v => v.SortOrder) .ToPageListAsync(page, size, total); return new PagedResult { Items = list.Adapt>(), Total = total }; } /// /// 新增视频 /// [HttpPost] public async Task Add([Required] VideoCreateDto dto) { var entity = dto.Adapt(); return await _db.Insertable(entity).ExecuteCommandAsync() > 0; } } ``` > Furion 的 `IDynamicApiController` 接口会自动将 `VideoService` 的方法映射为 RESTful API 端点,无需手动编写 Controller。 ## 数据源接入 > **重要说明**:今日头条、抖音等平台没有面向第三方开发者的开放平台 API。以下方案基于用户身份登录获取 Token,通过调用平台内部接口获取个人收藏数据,仅限个人学习研究使用。 ### 方式一:第三方收藏同步 #### 整体架构 支持接入多个第三方平台的收藏列表,每个平台有独立的同步服务,通过 `SysConfig` 配置开关控制同步任务的开启与关闭: ``` ┌──────────────────────────────────────────────────┐ │ 定时同步调度器 │ │ 遍历所有平台,检查 SysConfig 开关,执行同步任务 │ └──────┬──────────┬──────────┬──────────────────────┘ │ │ │ ┌──────▼──┐ ┌─────▼────┐ ┌──▼─────────┐ │ 头条同步 │ │ 抖音同步 │ │ ... 扩展 │ │ 服务 │ │ 服务 │ │ 更多平台 │ └─────────┘ └──────────┘ └────────────┘ │ │ │ └──────────┴──────────┘ │ ┌────────────▼────────────┐ │ third_party_auth 表 │ │ (各平台认证信息) │ └─────────────────────────┘ ``` #### 同步开关配置 在 `SysConfig` 中按平台名称配置同步开关,可在管理后台的「配置管理」页面中开启或关闭: | 配置键 | 配置值 | 说明 | |--------|--------|------| | `sync_toutiao_enabled` | true/false | 是否开启今日头条收藏同步 | | `sync_douyin_enabled` | true/false | 是否开启抖音收藏同步 | | `sync_toutiao_interval` | 3600 | 头条同步间隔(秒) | | `sync_douyin_interval` | 3600 | 抖音同步间隔(秒) | #### 通用同步接口 ```csharp /// /// 平台同步服务抽象基类 /// public abstract class BaseSyncService { protected readonly ISqlSugarClient _db; protected readonly IHttpClientFactory _httpClientFactory; /// 平台名称,用于读取认证配置和同步开关 public abstract string PlatformName { get; } /// 来源标识,写入视频的 source 字段 public abstract string SourceTag { get; } /// 拉取指定平台收藏列表 protected abstract Task> FetchFavoritesAsync(ThirdPartyAuth auth); /// /// 执行同步(检查开关 -> 读取认证 -> 拉取列表 -> 去重写入) /// public async Task SyncFavorites() { // 1. 检查同步开关 var enabled = await _db.Queryable() .Where(c => c.ConfigKey == $"sync_{SourceTag}_enabled" && c.ConfigValue == "true") .AnyAsync(); if (!enabled) throw new Exception($"平台 {PlatformName} 同步未开启,请在系统配置中启用"); // 2. 读取认证信息 var auth = await _db.Queryable() .Where(a => a.SiteName == PlatformName && a.IsValid == 1) .FirstAsync(); if (auth == null || string.IsNullOrEmpty(auth.Token)) throw new Exception($"未配置有效的 {PlatformName} 登录信息"); // 3. 拉取收藏列表 var allVideos = await FetchFavoritesAsync(auth); // 4. 按视频 URL 去重,仅新增本地不存在的视频 var existingUrls = _db.Queryable() .Where(v => v.Source == SourceTag) .Select(v => v.VideoUrl) .ToList(); var newVideos = allVideos .Where(v => !existingUrls.Contains(v.VideoUrl)) .ToList(); if (newVideos.Any()) { await _db.Insertable(newVideos).ExecuteCommandAsync(); } // 5. 更新认证表中的登录时间 auth.LoginTime = DateTime.Now; await _db.Updateable(auth).UpdateColumns(a => new { a.LoginTime }).ExecuteCommandAsync(); return new SyncResult { TotalFetched = allVideos.Count, NewAdded = newVideos.Count }; } } ``` #### 今日头条同步实现 ```csharp public class ToutiaoSyncService : BaseSyncService { public override string PlatformName => "今日头条"; public override string SourceTag => "toutiao"; public ToutiaoSyncService(ISqlSugarClient db, IHttpClientFactory httpClientFactory) : base(db, httpClientFactory) { } protected override async Task> FetchFavoritesAsync(ThirdPartyAuth auth) { var client = _httpClientFactory.CreateClient("Toutiao"); client.DefaultRequestHeaders.Add("Cookie", $"sessionid={auth.Token}; ttwid={auth.Cookies}"); client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ..."); var allVideos = new List(); long maxBehotTime = 0; bool hasMore = true; while (hasMore) { var url = $"https://www.toutiao.com/api/pc/list/user/feed" + $"?category=pc_profile_favorites" + $"&max_behot_time={maxBehotTime}" + $"&count=20"; var response = await client.GetAsync(url); var json = await response.Content.ReadFromJsonAsync(); if (json?.Data == null || !json.Data.Any()) break; foreach (var item in json.Data) { if (item.VideoUrl == null) continue; allVideos.Add(new VideoInfo { Title = item.Title, CoverUrl = item.CoverUrl, VideoUrl = item.VideoUrl, Author = item.Author, Source = SourceTag, Duration = item.Duration, Status = 0, ChannelId = null }); } hasMore = json.HasMore; maxBehotTime = json.NextMaxBehotTime; } return allVideos; } } ``` #### 抖音收藏同步实现 ```csharp public class DouyinSyncService : BaseSyncService { public override string PlatformName => "抖音"; public override string SourceTag => "douyin"; public DouyinSyncService(ISqlSugarClient db, IHttpClientFactory httpClientFactory) : base(db, httpClientFactory) { } protected override async Task> FetchFavoritesAsync(ThirdPartyAuth auth) { var client = _httpClientFactory.CreateClient("Douyin"); client.DefaultRequestHeaders.Add("Cookie", $"sessionid={auth.Token}; ttwid={auth.Cookies}"); client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ..."); var allVideos = new List(); // 抖音收藏接口需根据实际抓包分析实现 // 接口地址和参数可能随版本更新变化 return allVideos; } } ``` #### 同步调度器(Furion 定时任务) ```csharp public class SyncSchedulerJob : IJob { private readonly ISqlSugarClient _db; private readonly IEnumerable _syncServices; public SyncSchedulerJob(ISqlSugarClient db, IEnumerable syncServices) { _db = db; _syncServices = syncServices; } public async Task ExecuteAsync() { foreach (var service in _syncServices) { try { // 检查该平台同步开关 var enabled = await _db.Queryable() .Where(c => c.ConfigKey == $"sync_{service.SourceTag}_enabled" && c.ConfigValue == "true") .AnyAsync(); if (!enabled) continue; // 检查同步间隔 var interval = await _db.Queryable() .Where(c => c.ConfigKey == $"sync_{service.SourceTag}_interval") .Select(c => c.ConfigValue) .FirstAsync(); // 检查上次同步时间(此处省略间隔判断逻辑) var result = await service.SyncFavorites(); Log.Information("{Platform} 同步完成: 获取 {Total} 条, 新增 {New} 条", service.PlatformName, result.TotalFetched, result.NewAdded); } catch (Exception ex) { Log.Error(ex, "{Platform} 同步异常", service.PlatformName); } } } } ``` #### 同步流程总结 1. 定时任务触发,遍历所有已注册的同步服务 2. 每个服务先检查 `SysConfig` 中对应的同步开关(`sync_{source}_enabled`)是否为 `true` 3. 开关关闭则跳过,开关开启则从 `third_party_auth` 表读取认证信息 4. 调用平台内部接口拉取收藏列表 5. 按 `video_url` 去重,仅新增本地不存在的视频 6. 线上已移除的视频,本地保留不动 7. 更新认证表的登录时间 8. 手动同步时,可在管理后台选择指定平台触发同步 ### 方式二:后台手动录入 通过 Vben Admin 5 管理后台直接添加视频信息,新增时按 `video_url` 检测是否已存在,防止重复录入: | 字段 | 类型 | 说明 | |------|------|------| | `title` | string | 视频标题 | | `coverUrl` | string | 封面图地址 | | `videoUrl` | string | 视频播放地址(MP4/HLS) | | `author` | string | 作者名称 | | `source` | string | 来源标识 | | `duration` | int | 视频时长(秒) | | `sortOrder` | int | 排序权重 | ### 方式三:批量导入 支持通过 JSON 文件批量导入视频数据,导入时按 `video_url` 去重,已存在的视频自动跳过: ```bash dotnet run --project server/SwipeFlow.Web.Entry -- --import ./data/videos.json ``` #### 去重策略 所有视频导入方式(同步、手动录入、批量导入)均基于 `video_url` 字段进行去重: | 导入方式 | 去重依据 | 重复处理 | 覆盖更新 | |----------|----------|----------|----------| | 头条同步 | `video_url` | 跳过,保留本地记录 | 否 | | 后台手动录入 | `video_url` | 提示已存在,不允许提交 | 否 | | 批量导入 | `video_url` | 自动跳过,日志记录被跳过的条目 | 否 | ## 播放器核心能力 | 能力 | 实现方式 | |------|----------| | 竖屏全屏 | `100vh` + `object-fit: cover` | | 滑动切换 | CSS `scroll-snap-type: y mandatory` | | 自动播放/暂停 | `IntersectionObserver`(阈值 50%) | | iOS 内联播放 | `playsinline` + `webkit-playsinline` | | 静音起播 | 首帧 `muted`,用户交互后开声音 | | 预加载 | 当前视频前后各预载 1 个 | | 播放器内核 | xgplayer(西瓜播放器),支持 MP4/HLS | ## 用户互动功能 | 功能 | 说明 | |------|------| | 用户注册/登录 | 前端播放器独立账号,JWT 鉴权 | | 点赞 | 点击爱心图标点赞/取消点赞,实时更新计数 | | 收藏 | 点击收藏图标收藏视频,可在个人中心查看 | | 操作状态同步 | 已点赞/已收藏的视频状态随页面刷新保持 | | 未登录引导 | 未登录用户点击操作按钮时弹出登录面板 | ## 开发路线图 ### Phase 1 — 基础框架搭建 - [ ] 后端 .NET 8 + Furion + SqlSugar + SQLite 项目初始化 - [ ] 管理后台 Vben Admin 5 (Element Plus) 项目初始化 - [ ] 前端播放器 Vue 3 + Vite + xgplayer 项目初始化 - [ ] 视频实体 CodeFirst 自动建表 - [ ] 视频 CRUD 动态 WebAPI ### Phase 2 — 核心功能 - [ ] 竖屏滑动播放器完整实现(Scroll Snap + xgplayer) - [ ] 管理后台视频管理页面(列表/新增/编辑/删除) - [ ] JWT 鉴权 + 管理后台登录对接 - [ ] 视频文件上传(本地/OSS) - [ ] 前端用户注册/登录及点赞收藏功能 ### Phase 3 — 系统管理(RBAC) - [ ] 部门管理:树形结构、增删改查 - [ ] 角色管理:角色 CRUD、权限标识配置 - [ ] 菜单管理:树形菜单、目录/菜单/按钮三级类型 - [ ] 用户管理:用户 CRUD、分配角色、所属部门 - [ ] 角色菜单关联:按角色分配菜单权限 - [ ] 配置管理:系统参数键值对维护 ### Phase 4 — 头条收藏同步 - [ ] 头条 Token 配置管理页面(Vben Admin) - [ ] 头条收藏接口对接与数据解析 - [ ] 同步服务 + 去重逻辑 - [ ] 定时任务自动同步 - [ ] Token 过期检测与提醒 ### Phase 5 — 体验优化 - [ ] 视频预加载与缓存策略 - [ ] 骨架屏与加载动画 - [ ] 点赞、收藏、分享交互 - [ ] 视频转码与自适应码率(HLS) ### Phase 6 — 部署与运维 - [ ] Docker 一键部署(docker-compose) - [ ] Nginx 反向代理配置 - [ ] CI/CD 自动化部署 - [ ] PWA 离线支持 ## Docker 部署 ```yaml # docker-compose.yml version: '3.8' services: server: build: context: ./server dockerfile: Dockerfile ports: - "5000:8080" volumes: - ./data:/app/Data # SQLite 数据库持久化 - ./uploads:/app/uploads # 上传文件持久化 environment: - ASPNETCORE_ENVIRONMENT=Production admin: build: context: ./admin dockerfile: Dockerfile ports: - "5777:80" depends_on: - server client: build: context: ./client dockerfile: Dockerfile ports: - "5173:80" depends_on: - server ``` ## 贡献指南 欢迎提交 Issue 和 Pull Request! 1. Fork 本仓库 2. 创建功能分支 (`git checkout -b feature/AmazingFeature`) 3. 提交更改 (`git commit -m 'feat: add some AmazingFeature'`) 4. 推送到分支 (`git push origin feature/AmazingFeature`) 5. 开启 Pull Request ## 开源协议 本项目基于 MIT License 开源。 - 后端框架 Furion — MIT/Apache-2.0 双重协议 - ORM 框架 SqlSugar — MIT 协议 - 管理后台 Vben Admin — MIT 协议 > **合规提醒**:通过用户 Token 获取的今日头条收藏数据仅限个人学习研究使用,不得用于商业用途。请遵守今日头条的服务条款,尊重内容创作者的版权。Token 信息请妥善保管,避免泄露。 --- **如果这个项目对你有帮助,欢迎在 [Gitee](https://gitee.com/singlr/swipeflow) 上 Star 支持一下!**