1176 lines
29 KiB
Markdown
1176 lines
29 KiB
Markdown
# v2.0 开发文档
|
||
|
||
> **版本**: 2.0.0
|
||
> **状态**: ✅ 已完成
|
||
> **最后更新**: 2025-12-15
|
||
> **文档类型**: 技术开发文档
|
||
|
||
---
|
||
|
||
## 文档概述
|
||
|
||
本文档详细记录了书签管理系统 v2.0 的完整技术实现,包括:
|
||
- 从 v1.0(本地单用户)到 v2.0(云端多用户)的架构升级
|
||
- 后端 API(ASP.NET Core + FreeSql)完整实现
|
||
- 前端应用(Vue 3 + TypeScript)技术栈
|
||
- 浏览器扩展自动同步机制
|
||
- 后台管理系统实现细节
|
||
|
||
---
|
||
|
||
## 核心任务完成情况
|
||
|
||
### ✅ 已完成功能
|
||
|
||
**后端 API(100%)**
|
||
- [x] JWT 认证系统(包含 RefreshToken 机制)
|
||
- [x] 用户注册/登录/登出
|
||
- [x] 设备自动注册与管理(设备指纹识别)
|
||
- [x] 书签 CRUD(含搜索、批量操作、可见性控制)
|
||
- [x] 标签管理
|
||
- [x] 后台管理接口(用户管理、设备管理、统计数据)
|
||
|
||
**前端应用(100%)**
|
||
- [x] 用户认证界面(登录/注册)
|
||
- [x] 书签管理界面(列表、编辑、搜索)
|
||
- [x] 设备管理界面
|
||
- [x] 可见性控制界面
|
||
- [x] Pinia 状态管理(auth、bookmark、device、tag)
|
||
|
||
**浏览器扩展(90%)**
|
||
- [x] 书签自动同步(监听浏览器书签创建事件)
|
||
- [x] 文件夹路径转标签
|
||
- [x] URL 重复检测
|
||
- [x] 同步状态提示
|
||
- [ ] 删除/修改同步(预留接口)
|
||
|
||
**后台管理系统(100%)**
|
||
- [x] 仪表盘(系统统计数据)
|
||
- [x] 用户管理(列表、详情、禁用、重置密码)
|
||
- [x] 设备管理(强制下线、禁用设备)
|
||
- [x] 书签管理(查看、删除)
|
||
|
||
---
|
||
|
||
|
||
## 技术架构变化
|
||
|
||
###
|
||
|
||
```mermaid
|
||
graph LR
|
||
v1[v1.0_本地单用户]
|
||
v2[v2.0_云端多用户]
|
||
|
||
v1 -->|架构升级| v2
|
||
|
||
subgraph v1_arch [v1.0架构]
|
||
fe1[Vue前端]
|
||
idb[IndexedDB]
|
||
ext1[扩展监控]
|
||
fe1 --> idb
|
||
ext1 -.-> fe1
|
||
end
|
||
|
||
subgraph v2_arch [v2.0架构]
|
||
fe2[Vue前端]
|
||
api[.NET_API]
|
||
db[(SQL数据库)]
|
||
admin[Vue后台]
|
||
ext2[扩展增强]
|
||
|
||
fe2 --> api
|
||
ext2 --> api
|
||
admin --> api
|
||
api --> db
|
||
end
|
||
```
|
||
|
||
### 技术栈选型
|
||
|
||
基于用户确认的技术选型:
|
||
|
||
| 组件 | 技术选择 | 说明 |
|
||
|
||
|------|---------|------|
|
||
|
||
| 后端框架 | ASP.NET Core 8 (LTS) | 长期支持版本,稳定可靠 |
|
||
|
||
| 数据库 | SQL Server / PostgreSQL | 通过 FreeSql 支持多数据库 |
|
||
|
||
| ORM | FreeSql | 高性能 .NET ORM,支持多数据库、CodeFirst、丰富的查询功能 |
|
||
|
||
| 认证 | JWT Bearer Token | 无状态认证,支持多设备 |
|
||
|
||
| 后台管理 | Vue 3 + Element Plus | 与前端技术栈统一 |
|
||
|
||
| API 文档 | Swagger / OpenAPI | 自动生成接口文档 |
|
||
|
||
### FreeSql 核心优势
|
||
|
||
- 支持多种数据库(SQL Server、PostgreSQL、MySQL、Oracle 等)
|
||
- CodeFirst 自动同步实体结构到数据库
|
||
- 强大的表达式解析引擎,支持复杂查询
|
||
- 内置分页、软删除、乐观锁等功能
|
||
- Repository + UnitOfWork 模式支持
|
||
- 性能优秀,支持批量操作
|
||
|
||
## 文档结构设计
|
||
|
||
新文档 `v2.0-开发文档.md` 将包含以下章节:
|
||
|
||
### 1. 项目概述
|
||
|
||
- v2.0 产品定位和核心变化
|
||
- 从 MVP 到生产级的演进路径
|
||
- 多用户、多设备、云同步的业务价值
|
||
|
||
### 2. 系统架构
|
||
|
||
**完整架构图**:
|
||
|
||
```
|
||
用户层
|
||
├── Bookmark SPA (Vue 3)
|
||
├── Browser Extension (Manifest V3)
|
||
└── Admin Panel (Vue 3)
|
||
↓
|
||
API 网关层
|
||
├── ASP.NET Core 8 Web API
|
||
├── JWT 认证中间件
|
||
├── 设备识别中间件
|
||
└── 可见性过滤中间件
|
||
↓
|
||
业务逻辑层
|
||
├── 用户服务 (UserService)
|
||
├── 设备服务 (DeviceService)
|
||
├── 书签服务 (BookmarkService)
|
||
└── 标签服务 (TagService)
|
||
↓
|
||
数据访问层
|
||
├── FreeSql IFreeSql 实例
|
||
├── Repository 模式
|
||
└── UnitOfWork 工作单元
|
||
↓
|
||
数据库层 (SQL Server / PostgreSQL)
|
||
```
|
||
|
||
### 3. 数据模型设计
|
||
|
||
#### 3.1 核心实体
|
||
|
||
**Users (用户表)**
|
||
|
||
```csharp
|
||
using FreeSql.DataAnnotations;
|
||
|
||
[Table(Name = "users")]
|
||
public class User
|
||
{
|
||
[Column(IsPrimary = true)]
|
||
public Guid Id { get; set; }
|
||
|
||
[Column(IsNullable = false, StringLength = 200)]
|
||
[Index("idx_user_email", IsUnique = true)]
|
||
public string Email { get; set; }
|
||
|
||
[Column(IsNullable = false, StringLength = 100)]
|
||
public string UserName { get; set; }
|
||
|
||
[Column(IsNullable = false, StringLength = 500)]
|
||
public string PasswordHash { get; set; }
|
||
|
||
[Column(StringLength = 500)]
|
||
public string? Avatar { get; set; }
|
||
|
||
[Column(ServerTime = DateTimeKind.Utc)]
|
||
public DateTime CreatedAt { get; set; }
|
||
|
||
[Column(IsNullable = true)]
|
||
public DateTime? LastLoginAt { get; set; }
|
||
|
||
[Column(MapType = typeof(int))]
|
||
public UserStatus Status { get; set; }
|
||
|
||
// 导航属性
|
||
[Navigate(nameof(Device.UserId))]
|
||
public List<Device> Devices { get; set; }
|
||
|
||
[Navigate(nameof(Bookmark.UserId))]
|
||
public List<Bookmark> Bookmarks { get; set; }
|
||
}
|
||
|
||
public enum UserStatus
|
||
{
|
||
Normal = 0,
|
||
Disabled = 1
|
||
}
|
||
```
|
||
|
||
**Devices (设备表)**
|
||
|
||
```csharp
|
||
[Table(Name = "devices")]
|
||
public class Device
|
||
{
|
||
[Column(IsPrimary = true)]
|
||
public Guid Id { get; set; }
|
||
|
||
[Column(IsNullable = false)]
|
||
public Guid UserId { get; set; }
|
||
|
||
[Column(IsNullable = false, StringLength = 200)]
|
||
public string DeviceName { get; set; }
|
||
|
||
[Column(IsNullable = false, StringLength = 200)]
|
||
public string DeviceType { get; set; }
|
||
|
||
[Column(IsNullable = false)]
|
||
[Index("idx_device_user_admin")]
|
||
public bool IsAdmin { get; set; }
|
||
|
||
[Column(ServerTime = DateTimeKind.Utc)]
|
||
public DateTime CreatedAt { get; set; }
|
||
|
||
[Column(ServerTime = DateTimeKind.Utc, IsOnUpdateValue = true)]
|
||
public DateTime LastActiveAt { get; set; }
|
||
|
||
[Column(MapType = typeof(int))]
|
||
public DeviceStatus Status { get; set; }
|
||
|
||
// 导航属性
|
||
[Navigate(nameof(UserId))]
|
||
public User User { get; set; }
|
||
}
|
||
|
||
public enum DeviceStatus
|
||
{
|
||
Normal = 0,
|
||
Disabled = 1
|
||
}
|
||
```
|
||
|
||
**Bookmarks (书签表)**
|
||
|
||
```csharp
|
||
[Table(Name = "bookmarks")]
|
||
[Index("idx_bookmark_user_order", nameof(UserId), nameof(Order))]
|
||
public class Bookmark
|
||
{
|
||
[Column(IsPrimary = true)]
|
||
public Guid Id { get; set; }
|
||
|
||
[Column(IsNullable = false)]
|
||
public Guid UserId { get; set; }
|
||
|
||
[Column(IsNullable = false, StringLength = 500)]
|
||
public string Title { get; set; }
|
||
|
||
[Column(IsNullable = false, StringLength = 2000)]
|
||
public string Url { get; set; }
|
||
|
||
[Column(StringLength = 2000)]
|
||
public string? Description { get; set; }
|
||
|
||
[Column(StringLength = -1)] // TEXT/NTEXT
|
||
public string? Icon { get; set; }
|
||
|
||
[Column(MapType = typeof(string), StringLength = -1)] // JSON 存储
|
||
public string[] Tags { get; set; }
|
||
|
||
[Column(IsNullable = false)]
|
||
public int VisitCount { get; set; }
|
||
|
||
public DateTime? LastVisitTime { get; set; }
|
||
|
||
[Column(IsNullable = false)]
|
||
public long Order { get; set; }
|
||
|
||
[Column(MapType = typeof(int))]
|
||
public VisibilityType Visibility { get; set; }
|
||
|
||
[Column(MapType = typeof(string), StringLength = -1)] // JSON 存储
|
||
public Guid[]? AllowedDevices { get; set; }
|
||
|
||
[Column(ServerTime = DateTimeKind.Utc)]
|
||
public DateTime CreatedAt { get; set; }
|
||
|
||
[Column(ServerTime = DateTimeKind.Utc, IsOnUpdateValue = true)]
|
||
public DateTime UpdatedAt { get; set; }
|
||
|
||
// 导航属性
|
||
[Navigate(nameof(UserId))]
|
||
public User User { get; set; }
|
||
}
|
||
|
||
public enum VisibilityType
|
||
{
|
||
Public = 0,
|
||
Private = 1,
|
||
Specified = 2
|
||
}
|
||
```
|
||
|
||
#### 3.2 数据库索引策略
|
||
|
||
- Users: Email 唯一索引
|
||
- Devices: UserId + IsAdmin 复合索引(用于快速查找管理员设备)
|
||
- Bookmarks: UserId + Order 复合索引(列表查询优化)
|
||
|
||
### 4. FreeSql 配置与使用
|
||
|
||
#### 4.1 IFreeSql 实例配置
|
||
|
||
```csharp
|
||
// Program.cs
|
||
using FreeSql;
|
||
|
||
var builder = WebApplication.CreateBuilder(args);
|
||
|
||
// 配置 FreeSql
|
||
var freeSqlBuilder = new FreeSqlBuilder()
|
||
.UseConnectionString(GetDataType(), builder.Configuration.GetConnectionString("DefaultConnection"))
|
||
.UseAutoSyncStructure(builder.Environment.IsDevelopment()) // 开发环境自动同步结构
|
||
.UseMonitorCommand(cmd => Console.WriteLine(cmd.CommandText)) // 监控 SQL
|
||
.Build();
|
||
|
||
// 注册为单例
|
||
builder.Services.AddSingleton<IFreeSql>(freeSqlBuilder);
|
||
|
||
// 辅助方法:根据配置获取数据库类型
|
||
DataType GetDataType()
|
||
{
|
||
var provider = builder.Configuration["DatabaseProvider"];
|
||
return provider?.ToLower() switch
|
||
{
|
||
"postgresql" => DataType.PostgreSQL,
|
||
"sqlserver" => DataType.SqlServer,
|
||
_ => DataType.SqlServer
|
||
};
|
||
}
|
||
|
||
// appsettings.json
|
||
{
|
||
"ConnectionStrings": {
|
||
"DefaultConnection": "Server=.;Database=BookmarkDb;..."
|
||
},
|
||
"DatabaseProvider": "SqlServer" // 或 "PostgreSQL"
|
||
}
|
||
```
|
||
|
||
#### 4.2 Repository 模式
|
||
|
||
```csharp
|
||
// IRepository.cs
|
||
public interface IRepository<TEntity> where TEntity : class
|
||
{
|
||
ISelect<TEntity> Select { get; }
|
||
Task<TEntity> GetAsync(object id);
|
||
Task<List<TEntity>> GetListAsync();
|
||
Task<TEntity> InsertAsync(TEntity entity);
|
||
Task<int> UpdateAsync(TEntity entity);
|
||
Task<int> DeleteAsync(object id);
|
||
}
|
||
|
||
// 实现
|
||
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
|
||
{
|
||
private readonly IFreeSql _freeSql;
|
||
|
||
public Repository(IFreeSql freeSql)
|
||
{
|
||
_freeSql = freeSql;
|
||
}
|
||
|
||
public ISelect<TEntity> Select => _freeSql.Select<TEntity>();
|
||
|
||
public Task<TEntity> GetAsync(object id)
|
||
=> _freeSql.Select<TEntity>().WhereDynamic(id).FirstAsync();
|
||
|
||
public Task<List<TEntity>> GetListAsync()
|
||
=> _freeSql.Select<TEntity>().ToListAsync();
|
||
|
||
public Task<TEntity> InsertAsync(TEntity entity)
|
||
=> _freeSql.Insert(entity).ExecuteInsertedAsync();
|
||
|
||
public Task<int> UpdateAsync(TEntity entity)
|
||
=> _freeSql.Update<TEntity>().SetSource(entity).ExecuteAffrowsAsync();
|
||
|
||
public Task<int> DeleteAsync(object id)
|
||
=> _freeSql.Delete<TEntity>().WhereDynamic(id).ExecuteAffrowsAsync();
|
||
}
|
||
```
|
||
|
||
#### 4.3 UnitOfWork 工作单元
|
||
|
||
```csharp
|
||
// IUnitOfWork.cs
|
||
public interface IUnitOfWork : IDisposable
|
||
{
|
||
void BeginTransaction();
|
||
void Commit();
|
||
void Rollback();
|
||
IRepository<TEntity> GetRepository<TEntity>() where TEntity : class;
|
||
}
|
||
|
||
// 实现
|
||
public class UnitOfWork : IUnitOfWork
|
||
{
|
||
private readonly IFreeSql _freeSql;
|
||
private DbTransaction? _transaction;
|
||
|
||
public UnitOfWork(IFreeSql freeSql)
|
||
{
|
||
_freeSql = freeSql;
|
||
}
|
||
|
||
public void BeginTransaction()
|
||
{
|
||
_transaction = _freeSql.Ado.BeginTransaction();
|
||
}
|
||
|
||
public void Commit()
|
||
{
|
||
_transaction?.Commit();
|
||
_transaction?.Dispose();
|
||
_transaction = null;
|
||
}
|
||
|
||
public void Rollback()
|
||
{
|
||
_transaction?.Rollback();
|
||
_transaction?.Dispose();
|
||
_transaction = null;
|
||
}
|
||
|
||
public IRepository<TEntity> GetRepository<TEntity>() where TEntity : class
|
||
{
|
||
return new Repository<TEntity>(_freeSql);
|
||
}
|
||
|
||
public void Dispose()
|
||
{
|
||
_transaction?.Dispose();
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5. 后端 API 设计
|
||
|
||
#### 5.1 RESTful API 规范
|
||
|
||
**认证接口 (`/api/auth`)**
|
||
|
||
- `POST /register` - 用户注册
|
||
- `POST /login` - 用户登录(返回 JWT + 自动注册设备)
|
||
- `POST /logout` - 退出登录
|
||
- `POST /refresh` - 刷新 Token
|
||
- `POST /password/reset` - 重置密码(P1)
|
||
|
||
**用户接口 (`/api/user`)**
|
||
|
||
- `GET /profile` - 获取用户信息
|
||
- `PUT /profile` - 更新用户信息
|
||
|
||
**设备接口 (`/api/devices`)**
|
||
|
||
- `GET /` - 获取设备列表
|
||
- `PUT /{id}` - 更新设备信息(重命名)
|
||
- `DELETE /{id}` - 移除设备
|
||
- `PUT /{id}/admin` - 设置/取消管理员设备(P1)
|
||
|
||
**书签接口 (`/api/bookmarks`)**
|
||
|
||
- `GET /` - 获取书签列表(根据设备可见性自动过滤)
|
||
- `POST /` - 新增书签
|
||
- `GET /{id}` - 获取书签详情
|
||
- `PUT /{id}` - 更新书签
|
||
- `DELETE /{id}` - 删除书签
|
||
- `PUT /{id}/visibility` - 设置可见性
|
||
- `POST /batch` - 批量操作(P1)
|
||
- `POST /{id}/visit` - 记录访问
|
||
|
||
**标签接口 (`/api/tags`)**
|
||
|
||
- `GET /` - 获取标签列表及统计
|
||
- `PUT /{name}` - 重命名标签(P1)
|
||
- `DELETE /{name}` - 删除标签(P1)
|
||
|
||
#### 5.2 可见性过滤实现(FreeSql)
|
||
|
||
```csharp
|
||
// BookmarkService.cs
|
||
public async Task<List<Bookmark>> GetUserBookmarksAsync(Guid userId, Guid deviceId, bool isAdminDevice)
|
||
{
|
||
var query = _freeSql.Select<Bookmark>()
|
||
.Where(b => b.UserId == userId)
|
||
.Where(b =>
|
||
b.Visibility == VisibilityType.Public ||
|
||
(b.Visibility == VisibilityType.Private && isAdminDevice) ||
|
||
(b.Visibility == VisibilityType.Specified &&
|
||
_freeSql.Select<Bookmark>()
|
||
.Where(x => x.Id == b.Id)
|
||
.Where($"AllowedDevices LIKE '%{deviceId}%'")
|
||
.Any()
|
||
)
|
||
)
|
||
.OrderByDescending(b => b.Order);
|
||
|
||
return await query.ToListAsync();
|
||
}
|
||
```
|
||
|
||
#### 5.3 示例 API Controller
|
||
|
||
```csharp
|
||
[ApiController]
|
||
[Route("api/[controller]")]
|
||
[Authorize]
|
||
public class BookmarksController : ControllerBase
|
||
{
|
||
private readonly IFreeSql _freeSql;
|
||
private readonly ILogger<BookmarksController> _logger;
|
||
|
||
public BookmarksController(IFreeSql freeSql, ILogger<BookmarksController> logger)
|
||
{
|
||
_freeSql = freeSql;
|
||
_logger = logger;
|
||
}
|
||
|
||
[HttpGet]
|
||
public async Task<ActionResult<List<BookmarkDto>>> GetBookmarks()
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
var deviceId = GetCurrentDeviceId();
|
||
var isAdmin = await IsAdminDeviceAsync(deviceId);
|
||
|
||
var bookmarks = await _freeSql.Select<Bookmark>()
|
||
.Where(b => b.UserId == userId)
|
||
.WhereIf(!isAdmin, b => b.Visibility == VisibilityType.Public)
|
||
.OrderByDescending(b => b.Order)
|
||
.ToListAsync();
|
||
|
||
return Ok(bookmarks);
|
||
}
|
||
|
||
[HttpPost]
|
||
public async Task<ActionResult<BookmarkDto>> CreateBookmark([FromBody] CreateBookmarkDto dto)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
|
||
var bookmark = new Bookmark
|
||
{
|
||
Id = Guid.NewGuid(),
|
||
UserId = userId,
|
||
Title = dto.Title,
|
||
Url = dto.Url,
|
||
Description = dto.Description,
|
||
Tags = dto.Tags ?? Array.Empty<string>(),
|
||
Visibility = dto.Visibility,
|
||
AllowedDevices = dto.AllowedDevices,
|
||
Order = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
|
||
CreatedAt = DateTime.UtcNow
|
||
};
|
||
|
||
await _freeSql.Insert(bookmark).ExecuteAffrowsAsync();
|
||
|
||
return CreatedAtAction(nameof(GetBookmark), new { id = bookmark.Id }, bookmark);
|
||
}
|
||
|
||
// 其他 API 方法...
|
||
}
|
||
```
|
||
|
||
### 6. 前端架构更新
|
||
|
||
#### 6.1 数据层变化
|
||
|
||
**v1.0**: IndexedDB (Dexie) → **v2.0**: RESTful API + Axios
|
||
|
||
**新增服务层**:
|
||
|
||
- `src/services/api/client.js` - Axios 实例配置(拦截器、Token 注入)
|
||
- `src/services/api/authApi.js` - 认证接口封装
|
||
- `src/services/api/userApi.js` - 用户接口
|
||
- `src/services/api/deviceApi.js` - 设备接口
|
||
- `src/services/api/bookmarkApi.js` - 书签接口
|
||
- `src/services/api/tagApi.js` - 标签接口
|
||
- `src/services/auth/tokenService.js` - JWT Token 管理(localStorage)
|
||
- `src/services/auth/deviceService.js` - 设备指纹识别
|
||
|
||
#### 6.2 Pinia Store 更新
|
||
|
||
**新增**:
|
||
|
||
- `stores/auth.js` - 用户认证状态(登录、登出、Token 管理)
|
||
- `stores/device.js` - 设备信息(当前设备、是否管理员)
|
||
|
||
**更新**:
|
||
|
||
- `stores/links.js` - 从 Dexie 调用改为 API 调用
|
||
- `stores/settings.js` - 部分设置迁移到服务器端
|
||
|
||
#### 6.3 新增功能组件
|
||
|
||
**用户认证**:
|
||
|
||
- `components/auth/LoginDialog.vue` - 登录对话框
|
||
- `components/auth/RegisterDialog.vue` - 注册对话框
|
||
- `components/auth/PasswordResetDialog.vue` - 密码重置(P1)
|
||
|
||
**设备管理**:
|
||
|
||
- `components/devices/DeviceList.vue` - 设备列表
|
||
- `components/devices/DeviceCard.vue` - 设备卡片
|
||
- `components/devices/DeviceManageDialog.vue` - 设备管理对话框
|
||
|
||
**可见性控制**:
|
||
|
||
- `components/links/VisibilitySelector.vue` - 可见性选择器
|
||
- `components/links/VisibilityBadge.vue` - 可见性徽章
|
||
|
||
### 7. 浏览器扩展增强
|
||
|
||
#### 7.1 新增核心功能
|
||
|
||
**自动同步** (`background/sync.js`):
|
||
|
||
- 监听 `chrome.bookmarks.onCreated` 事件
|
||
- 提取书签信息(标题、URL、图标、文件夹路径)
|
||
- 文件夹路径转标签(如:「工作/项目A」→ ["工作", "项目A"])
|
||
- 调用云端 API 保存书签
|
||
- 显示同步成功/失败通知
|
||
|
||
**新标签页替换**:
|
||
|
||
```json
|
||
// manifest.json
|
||
{
|
||
"chrome_url_overrides": {
|
||
"newtab": "app/index.html"
|
||
}
|
||
}
|
||
```
|
||
|
||
**全局快捷键搜索** (`content/search-overlay.js`):
|
||
|
||
- Content Script 注入所有页面
|
||
- 监听快捷键(Alt+K 或自定义)
|
||
- 浮层搜索框(遮罩层 + 搜索输入 + 结果列表)
|
||
- 调用 API 实时搜索
|
||
- 键盘导航(上下键、回车打开)
|
||
|
||
**一键收藏** (`popup/quick-save.js`):
|
||
|
||
- Popup 中的快速收藏表单
|
||
- 自动填充当前页标题和 URL
|
||
- 可编辑标签、可见性
|
||
- 可选择是否同时保存到浏览器书签
|
||
- 收藏成功提示
|
||
|
||
#### 7.2 认证集成
|
||
|
||
- 使用 `chrome.storage.local` 存储 JWT Token 和设备 ID
|
||
- API 请求时自动携带 Token
|
||
- Token 过期时自动刷新或提示登录
|
||
- 未登录时提示用户登录
|
||
|
||
### 8. 后台管理系统
|
||
|
||
#### 8.1 技术栈与项目结构
|
||
|
||
**技术栈**: Vue 3 + Vite + Vue Router + Pinia + Element Plus + Axios
|
||
|
||
```
|
||
admin-panel/
|
||
├── src/
|
||
│ ├── views/
|
||
│ │ ├── Dashboard.vue # 仪表盘
|
||
│ │ ├── users/
|
||
│ │ │ ├── UserList.vue # 用户列表
|
||
│ │ │ └── UserDetail.vue # 用户详情
|
||
│ │ ├── devices/
|
||
│ │ │ └── DeviceList.vue # 设备管理
|
||
│ │ └── bookmarks/
|
||
│ │ └── BookmarkList.vue # 书签管理
|
||
│ ├── components/
|
||
│ ├── stores/
|
||
│ │ └── admin.js # 管理员状态
|
||
│ ├── services/
|
||
│ │ └── adminApi.js # 后台 API
|
||
│ └── router/
|
||
│ └── index.js
|
||
├── package.json
|
||
└── vite.config.js
|
||
```
|
||
|
||
#### 8.2 核心功能页面
|
||
|
||
**仪表盘**:
|
||
|
||
- 关键指标卡片(用户总数、活跃用户、书签总数、设备总数)
|
||
- 趋势图表(P2 优先级,使用 ECharts)
|
||
|
||
**用户管理**:
|
||
|
||
- 用户列表(分页、搜索、筛选)
|
||
- 禁用/启用用户操作
|
||
- 查看用户详情(基本信息、设备列表、书签统计)
|
||
- 重置密码(P1)
|
||
|
||
**设备管理**:
|
||
|
||
- 查看用户的所有设备
|
||
- 强制设备下线(P1)
|
||
- 禁用设备(P1)
|
||
|
||
**书签管理**:
|
||
|
||
- 查看用户的书签列表(分页、搜索)
|
||
- 按可见性筛选
|
||
- 删除违规书签(P1)
|
||
|
||
### 9. 数据库迁移与种子数据
|
||
|
||
#### 9.1 CodeFirst 自动同步
|
||
|
||
```csharp
|
||
// Program.cs
|
||
var freeSql = new FreeSqlBuilder()
|
||
.UseConnectionString(dataType, connectionString)
|
||
.UseAutoSyncStructure(true) // 自动同步实体结构到数据库
|
||
.Build();
|
||
|
||
// 首次运行时自动创建表和索引
|
||
```
|
||
|
||
#### 9.2 种子数据
|
||
|
||
```csharp
|
||
// 创建超级管理员账号
|
||
public static async Task SeedDataAsync(IFreeSql freeSql)
|
||
{
|
||
var adminExists = await freeSql.Select<User>()
|
||
.Where(u => u.Email == "admin@bookmarks.local")
|
||
.AnyAsync();
|
||
|
||
if (!adminExists)
|
||
{
|
||
var admin = new User
|
||
{
|
||
Id = Guid.NewGuid(),
|
||
Email = "admin@bookmarks.local",
|
||
UserName = "Administrator",
|
||
PasswordHash = BCrypt.Net.BCrypt.HashPassword("Admin@123"),
|
||
Status = UserStatus.Normal,
|
||
CreatedAt = DateTime.UtcNow
|
||
};
|
||
|
||
await freeSql.Insert(admin).ExecuteAffrowsAsync();
|
||
}
|
||
}
|
||
```
|
||
|
||
### 10. 开发指南
|
||
|
||
#### 10.1 环境搭建
|
||
|
||
**后端环境**:
|
||
|
||
- .NET 8 SDK
|
||
- SQL Server 2019+ 或 PostgreSQL 14+
|
||
- Visual Studio 2022 / Rider / VS Code + C# 扩展
|
||
- Postman / Swagger UI
|
||
|
||
**前端环境**:
|
||
|
||
- Node.js 20+
|
||
- npm 8+
|
||
- Chrome 90+
|
||
|
||
#### 10.2 本地开发流程
|
||
|
||
```bash
|
||
# 1. 克隆项目
|
||
git clone <repository>
|
||
|
||
# 2. 后端启动
|
||
cd backend-api
|
||
dotnet restore
|
||
dotnet run
|
||
# 首次运行会自动创建数据库表
|
||
|
||
# 3. 前端启动
|
||
cd bookmark-spa
|
||
npm install
|
||
npm run dev
|
||
|
||
# 4. 后台管理启动
|
||
cd admin-panel
|
||
npm install
|
||
npm run dev
|
||
|
||
# 5. 浏览器扩展
|
||
cd bookmark-spa
|
||
npm run build
|
||
cp -r dist/* ../browser-extension/app/
|
||
# 在 Chrome 中加载扩展
|
||
```
|
||
|
||
#### 10.3 FreeSql 常用操作示例
|
||
|
||
```csharp
|
||
// 查询
|
||
var users = await _freeSql.Select<User>()
|
||
.Where(u => u.Status == UserStatus.Normal)
|
||
.Page(1, 20)
|
||
.ToListAsync();
|
||
|
||
// 插入
|
||
var user = new User { /* ... */ };
|
||
await _freeSql.Insert(user).ExecuteAffrowsAsync();
|
||
|
||
// 更新
|
||
await _freeSql.Update<User>()
|
||
.Set(u => u.LastLoginAt, DateTime.UtcNow)
|
||
.Where(u => u.Id == userId)
|
||
.ExecuteAffrowsAsync();
|
||
|
||
// 删除
|
||
await _freeSql.Delete<User>()
|
||
.Where(u => u.Id == userId)
|
||
.ExecuteAffrowsAsync();
|
||
|
||
// 事务
|
||
using var uow = _freeSql.CreateUnitOfWork();
|
||
try
|
||
{
|
||
var repo = uow.GetRepository<Bookmark>();
|
||
await repo.InsertAsync(bookmark);
|
||
uow.Commit();
|
||
}
|
||
catch
|
||
{
|
||
uow.Rollback();
|
||
throw;
|
||
}
|
||
```
|
||
|
||
### 11. 部署指南
|
||
|
||
#### 11.1 后端部署
|
||
|
||
**Docker 容器化**:
|
||
|
||
```dockerfile
|
||
FROM mcr.microsoft.com/dotnet/aspnet:8.0
|
||
WORKDIR /app
|
||
COPY publish/ .
|
||
EXPOSE 80
|
||
ENTRYPOINT ["dotnet", "BookmarkApi.dll"]
|
||
```
|
||
|
||
**环境变量**:
|
||
|
||
- `ConnectionStrings__DefaultConnection`
|
||
- `DatabaseProvider` (SqlServer / PostgreSQL)
|
||
- `JwtSettings__SecretKey`
|
||
- `JwtSettings__Issuer`
|
||
- `JwtSettings__Audience`
|
||
- `JwtSettings__ExpiryMinutes`
|
||
|
||
#### 11.2 前端部署
|
||
|
||
- Bookmark SPA: Nginx / Vercel / Netlify
|
||
- Admin Panel: Nginx(需配置访问控制)
|
||
- Browser Extension: Chrome Web Store
|
||
|
||
#### 11.3 数据库部署
|
||
|
||
- SQL Server: Azure SQL / 自托管
|
||
- PostgreSQL: Azure PostgreSQL / AWS RDS / 自托管
|
||
- 定期备份策略
|
||
|
||
### 12. 安全实践
|
||
|
||
#### 12.1 认证与授权
|
||
|
||
- 密码使用 BCrypt 或 Argon2 哈希
|
||
- JWT Token 有效期设置(1 小时)
|
||
- Refresh Token 机制(7 天有效期)
|
||
- 设备绑定(Token 包含设备 ID)
|
||
|
||
#### 12.2 API 安全
|
||
|
||
- HTTPS 强制传输
|
||
- CORS 跨域配置
|
||
- 速率限制(ASP.NET Core Rate Limiting)
|
||
- FreeSql 自动参数化防止 SQL 注入
|
||
- 输入验证和输出编码防止 XSS
|
||
|
||
### 13. 测试策略
|
||
|
||
#### 13.1 后端测试
|
||
|
||
- 单元测试: xUnit + Moq
|
||
- FreeSql 内存数据库测试
|
||
- API 集成测试: WebApplicationFactory
|
||
```csharp
|
||
[Fact]
|
||
public async Task GetBookmarks_ReturnsUserBookmarks()
|
||
{
|
||
// Arrange
|
||
var freeSql = new FreeSqlBuilder()
|
||
.UseConnectionString(DataType.Sqlite, ":memory:")
|
||
.Build();
|
||
|
||
// Act & Assert
|
||
}
|
||
```
|
||
|
||
|
||
#### 13.2 前端测试
|
||
|
||
- 单元测试: Vitest
|
||
- E2E 测试: Playwright(包含登录流程)
|
||
|
||
## v2.0 实际实现情况
|
||
|
||
### ✅ P0 核心功能(已全部完成)
|
||
|
||
**后端 API**
|
||
- ✅ ASP.NET Core 8 项目搭建
|
||
- ✅ FreeSql 配置与实体定义(User、Device、Bookmark、RefreshToken)
|
||
- ✅ JWT 认证中间件(包含 RefreshToken 机制)
|
||
- ✅ 用户注册/登录 API(含设备自动注册)
|
||
- ✅ 书签 CRUD API(含搜索、批量删除、可见性控制)
|
||
- ✅ 设备管理 API(重命名、删除、设置管理员)
|
||
- ✅ 标签管理 API
|
||
- ✅ 后台管理 API(用户管理、设备管理、统计数据)
|
||
|
||
**前端应用**
|
||
- ✅ Vue 3 + TypeScript + Pinia
|
||
- ✅ 登录/注册界面
|
||
- ✅ JWT Token 管理(自动刷新)
|
||
- ✅ 书签列表迁移到 API 调用
|
||
- ✅ 设备管理界面
|
||
- ✅ 可见性控制界面
|
||
- ✅ 全局搜索组件
|
||
|
||
**浏览器扩展**
|
||
- ✅ 用户认证集成
|
||
- ✅ Token 存储与管理
|
||
- ✅ 书签自动同步(监听 chrome.bookmarks.onCreated)
|
||
- ✅ 文件夹路径转标签
|
||
- ✅ URL 重复检测
|
||
- ✅ 同步状态通知
|
||
|
||
**后台管理系统**
|
||
- ✅ Vue 3 + Element Plus
|
||
- ✅ 仪表盘统计(用户数、活跃度、书签数、设备数)
|
||
- ✅ 用户管理(列表、详情、禁用/启用、重置密码)
|
||
- ✅ 设备管理(查看、强制下线、禁用)
|
||
- ✅ 书签管理(查看用户书签、删除)
|
||
- ✅ 权限控制(Admin/SuperAdmin)
|
||
|
||
### 📋 P2-P3 增强功能(v3.0 规划)
|
||
|
||
**扩展增强**
|
||
- 📋 新标签页替换为书签应用
|
||
- 📋 全局快捷键搜索(任意页面触发)
|
||
- 📋 右键菜单添加书签
|
||
- 📋 Popup 快速收藏
|
||
|
||
**高级功能**
|
||
- 📋 统计报表和趋势图
|
||
- 📋 标签管理增强(合并、排序)
|
||
- 📋 邮箱验证
|
||
- 📋 数据导出功能
|
||
|
||
---
|
||
|
||
## 已实现的 API 接口清单
|
||
|
||
### 认证接口 (/api/auth)
|
||
|
||
| 方法 | 路径 | 描述 | 状态 |
|
||
|------|------|------|------|
|
||
| POST | /register | 用户注册 | ✅ |
|
||
| POST | /login | 用户登录(含设备注册) | ✅ |
|
||
| POST | /logout | 退出登录 | ✅ |
|
||
| POST | /refresh | 刷新 Access Token | ✅ |
|
||
|
||
### 用户接口 (/api/user)
|
||
|
||
| 方法 | 路径 | 描述 | 状态 |
|
||
|------|------|------|------|
|
||
| GET | /profile | 获取用户信息 | ✅ |
|
||
| PUT | /profile | 更新用户信息 | ✅ |
|
||
| PUT | /password | 修改密码 | ✅ |
|
||
|
||
### 设备接口 (/api/devices)
|
||
|
||
| 方法 | 路径 | 描述 | 状态 |
|
||
|------|------|------|------|
|
||
| GET | / | 获取设备列表 | ✅ |
|
||
| GET | /{id} | 获取设备详情 | ✅ |
|
||
| PUT | /{id} | 更新设备名称 | ✅ |
|
||
| DELETE | /{id} | 删除设备 | ✅ |
|
||
| PUT | /{id}/admin | 设置/取消管理员设备 | ✅ |
|
||
|
||
### 书签接口 (/api/bookmarks)
|
||
|
||
| 方法 | 路径 | 描述 | 状态 |
|
||
|------|------|------|------|
|
||
| GET | / | 获取书签列表(自动过滤可见性) | ✅ |
|
||
| GET | /search | 搜索书签 | ✅ |
|
||
| GET | /{id} | 获取书签详情 | ✅ |
|
||
| POST | / | 新增书签 | ✅ |
|
||
| PUT | /{id} | 更新书签 | ✅ |
|
||
| DELETE | /{id} | 删除书签 | ✅ |
|
||
| POST | /batch/delete | 批量删除 | ✅ |
|
||
| PUT | /{id}/visibility | 设置可见性 | ✅ |
|
||
| POST | /{id}/visit | 记录访问 | ✅ |
|
||
| PUT | /{id}/order | 更新排序 | ✅ |
|
||
| GET | /check-url | 检查 URL 是否存在 | ✅ |
|
||
| POST | /import | 导入书签 | ✅ |
|
||
| GET | /export | 导出书签 | ✅ |
|
||
|
||
### 标签接口 (/api/tags)
|
||
|
||
| 方法 | 路径 | 描述 | 状态 |
|
||
|------|------|------|------|
|
||
| GET | / | 获取标签列表及统计 | ✅ |
|
||
| PUT | /{name} | 重命名标签 | ✅ |
|
||
| DELETE | /{name} | 删除标签 | ✅ |
|
||
|
||
### 后台管理接口 (/api/admin)
|
||
|
||
| 方法 | 路径 | 描述 | 状态 |
|
||
|------|------|------|------|
|
||
| GET | /statistics | 获取系统统计数据 | ✅ |
|
||
| GET | /users | 获取用户列表(分页、搜索、筛选) | ✅ |
|
||
| GET | /users/{id} | 获取用户详情 | ✅ |
|
||
| PUT | /users/{id}/status | 禁用/启用用户 | ✅ |
|
||
| POST | /users/{id}/reset-password | 重置用户密码 | ✅ |
|
||
| GET | /users/{id}/bookmarks | 获取用户书签列表 | ✅ |
|
||
| DELETE | /users/{userId}/bookmarks/{bookmarkId} | 删除用户书签 | ✅ |
|
||
| POST | /devices/{id}/force-logout | 强制设备下线 | ✅ |
|
||
| PUT | /devices/{id}/status | 禁用/启用设备 | ✅ |
|
||
|
||
---
|
||
|
||
## 部署说明
|
||
|
||
### 环境要求
|
||
|
||
**后端环境**
|
||
- .NET 8 SDK
|
||
- SQL Server 2019+ 或 PostgreSQL 14+
|
||
- 依赖包:
|
||
- FreeSql (ORM)
|
||
- BCrypt.Net-Next (密码加密)
|
||
- System.IdentityModel.Tokens.Jwt (JWT)
|
||
- Swashbuckle.AspNetCore (API 文档)
|
||
|
||
**前端环境**
|
||
- Node.js 20+
|
||
- Vue 3 + TypeScript
|
||
- Pinia
|
||
- Element Plus
|
||
|
||
**浏览器扩展**
|
||
- Chrome 90+ (Manifest V3)
|
||
|
||
### 配置说明
|
||
|
||
**appsettings.json**
|
||
```json
|
||
{
|
||
"ConnectionStrings": {
|
||
"DefaultConnection": "Server=.;Database=BookmarkDb;..."
|
||
},
|
||
"DatabaseProvider": "SqlServer",
|
||
"JwtSettings": {
|
||
"SecretKey": "your-256-bit-secret",
|
||
"Issuer": "BookmarkApi",
|
||
"Audience": "BookmarkClient",
|
||
"ExpiryMinutes": 60,
|
||
"RefreshTokenDays": 7
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 技术亮点
|
||
|
||
1. **JWT + RefreshToken 双令牌机制**
|
||
- Access Token 有效期 1 小时
|
||
- Refresh Token 有效期 7 天
|
||
- 自动刷新,无感登录体验
|
||
|
||
2. **设备指纹识别**
|
||
- Canvas 指纹、屏幕分辨率、时区等多因子识别
|
||
- 自动注册设备,支持设备管理
|
||
- 管理员设备机制,灵活权限控制
|
||
|
||
3. **书签可见性控制**
|
||
- Public:所有设备可见
|
||
- Private:仅管理员设备可见
|
||
- Specified:仅指定设备可见
|
||
- API 层自动过滤,确保数据安全
|
||
|
||
4. **自动同步机制**
|
||
- 监听浏览器书签事件
|
||
- 文件夹路径自动转标签
|
||
- URL 重复检测
|
||
- 同步状态实时反馈
|
||
|
||
5. **后台管理系统**
|
||
- 完整的用户管理功能
|
||
- 系统统计数据实时更新
|
||
- 设备管理(强制下线、禁用)
|
||
- 基于角色的权限控制
|
||
|
||
6. **TypeScript 支持**
|
||
- 前端全面使用 TypeScript
|
||
- 类型安全,减少运行时错误
|
||
- 更好的开发体验和代码维护性
|
||
|
||
---
|
||
|
||
## 后续优化建议
|
||
|
||
1. **性能优化**
|
||
- 实现 Redis 缓存,提高 API 响应速度
|
||
- 添加 CDN 支持前端资源
|
||
- 数据库查询优化和索引优化
|
||
|
||
2. **功能增强**
|
||
- 实现新标签页替换
|
||
- 添加全局快捷键搜索
|
||
- 支持邮箱验证
|
||
- 实现数据备份和恢复
|
||
|
||
3. **监控与日志**
|
||
- 添加结构化日志
|
||
- 实现监控告警
|
||
- 错误追踪和性能分析
|
||
|
||
4. **安全加固**
|
||
- 实现 2FA 双因素认证
|
||
- 添加操作日志审计
|
||
- API 速率限制增强
|
||
- XSS 和 CSRF 防护加强
|
||
|
||
---
|
||
|
||
> **v2.0 开发完成时间**: 2025-12-15
|
||
>
|
||
> **代码位置**: `/src/` 目录
|
||
> - 后端: `/src/backend/`
|
||
> - 前端: `/src/frontend/`
|
||
> - 扩展: `/src/extension/`
|
||
> - 后台管理: `/src/admin/`
|