4fe0b5a963
本次提交完成了项目核心基础架构升级: 1. 新增动态API中间件与权限控制系统,支持匿名/鉴权接口分离 2. 搭建云同步服务体系,包含认证、任务同步、安全策略等核心模块 3. 实现语音控制全链路,从STT/意图解析到命令执行 4. 新增任务类型、附件实体与相关仓储接口 5. 重构前端配置与代理规则,统一后端端口为5057 6. 新增多平台测试项目与CI脚本优化 7. 完善项目文档与代码注释规范 移除了旧版迁移文件与冗余代理配置,调整项目结构适配跨平台部署需求。
689 lines
15 KiB
Markdown
689 lines
15 KiB
Markdown
# 代码规范
|
||
|
||
> 本文档面向开发者,定义 Hua.Todo 项目的编码规范。
|
||
|
||
## 1. 概述
|
||
本文档定义 Hua.Todo 项目的代码规范,包括 C#、JavaScript/TypeScript、Vue.js 和其他相关技术的编码标准。遵循这些规范有助于提高代码质量、可读性和可维护性。
|
||
|
||
## 2. 通用规范
|
||
|
||
### 2.1 命名约定
|
||
- **使用有意义的名称**: 变量、函数、类名应清晰表达其用途
|
||
- **避免缩写**: 除非是广泛认知的缩写(如 ID、URL、API)
|
||
- **一致性**: 在整个项目中保持命名风格一致
|
||
|
||
### 2.2 注释规范 (强制)
|
||
- **公共 API 必须添加 XML 文档注释** (包括 `public` / `protected` 的类、接口、方法、属性)
|
||
- **summary**:一句话说明用途,不允许重复嵌套 `<summary>` 标签
|
||
- **param / returns**:构造函数和方法的每个参数都必须有对应 `param`,包括可选参数、`logger` 等基础设施参数;有返回值时补充 `returns`
|
||
- **异常或副作用**:在 summary 中明确说明(例如会注册系统钩子/会启动后台服务)
|
||
- **XML 注释位置**:`///` 文档注释必须紧贴被说明的语言元素;若元素还有特性(如 `[AttributeUsage]`),顺序必须是 XML 注释、特性、类型/成员声明
|
||
- **cref 使用**:`<see cref="..."/>` 只引用当前项目能解析的类型/成员;跨程序集或未引入命名空间时改用普通文本,避免 CS1574 警告
|
||
- **复杂逻辑添加行内注释**
|
||
- **禁止在日志或注释中输出密钥、Token、用户隐私信息**
|
||
|
||
### 2.3 代码格式化
|
||
- **使用统一的代码格式化工具** (VS / IDE 默认格式化)
|
||
- **保持一致的缩进和空格**
|
||
- **每行代码不超过 120 字符**
|
||
- **文件末尾保留一个空行**
|
||
|
||
## 3. C# 代码规范
|
||
|
||
### 3.1 跨平台逻辑规范
|
||
- **禁止混写 `#if`**:禁止在同一文件内混写多个平台的大段 `#if` 实现。
|
||
- **优先使用 partial/接口**:应优先使用 `partial` 类、接口与平台目录分离。
|
||
- **说明平台差异**:平台分离后的公共入口处必须说明"平台差异在哪里、默认实现是什么、为什么这么做"。
|
||
|
||
### 3.2 异步/后台任务
|
||
- **必须说明启动时机、错误处理策略、是否需要 UI 线程、以及是否可并发/可重入**。
|
||
|
||
#### 类和接口
|
||
```csharp
|
||
// 类名使用 PascalCase
|
||
public class TaskService
|
||
{
|
||
}
|
||
|
||
// 接口名使用 PascalCase,以 I 开头
|
||
public interface ITaskService
|
||
{
|
||
}
|
||
```
|
||
|
||
#### 方法和属性
|
||
```csharp
|
||
// 方法名使用 PascalCase
|
||
public Task<List<Task>> GetTasksAsync()
|
||
{
|
||
}
|
||
|
||
// 属性名使用 PascalCase
|
||
public string Title { get; set; }
|
||
```
|
||
|
||
#### 变量和参数
|
||
```csharp
|
||
// 私有字段使用 _camelCase
|
||
private readonly ITaskRepository _taskRepository;
|
||
|
||
// 局部变量使用 camelCase
|
||
var taskList = await GetTasksAsync();
|
||
|
||
// 方法参数使用 camelCase
|
||
public void CreateTask(string title, TaskPriority priority)
|
||
{
|
||
}
|
||
```
|
||
|
||
#### 常量
|
||
```csharp
|
||
// 常量使用 PascalCase
|
||
public const int MaxTaskTitleLength = 200;
|
||
```
|
||
|
||
### 3.3 代码组织
|
||
|
||
#### 文件结构
|
||
```csharp
|
||
// 1. using 语句(按字母顺序)
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Threading.Tasks;
|
||
|
||
// 2. 命名空间
|
||
namespace Hua.Todo.Api.Services;
|
||
|
||
// 3. XML 文档注释(必须位于特性和声明之前)
|
||
/// <summary>
|
||
/// 任务服务实现。
|
||
/// </summary>
|
||
[SomeAttribute]
|
||
public class TaskService : ITaskService
|
||
{
|
||
// 4. 私有字段
|
||
private readonly ITaskRepository _taskRepository;
|
||
|
||
// 5. 构造函数
|
||
public TaskService(ITaskRepository taskRepository)
|
||
{
|
||
_taskRepository = taskRepository;
|
||
}
|
||
|
||
// 6. 公共方法
|
||
public async Task<List<Task>> GetTasksAsync()
|
||
{
|
||
// 实现
|
||
}
|
||
|
||
// 7. 私有方法
|
||
private bool ValidateTask(Task task)
|
||
{
|
||
// 实现
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 命名空间组织
|
||
- 每个文件只包含一个命名空间
|
||
- 命名空间结构应与目录结构一致
|
||
- 使用 `.` 分隔层级
|
||
|
||
### 3.4 编码规范
|
||
|
||
#### 异步编程
|
||
```csharp
|
||
// 异步方法应以 Async 结尾
|
||
public async Task<Task> GetTaskByIdAsync(int id)
|
||
{
|
||
return await _taskRepository.GetByIdAsync(id);
|
||
}
|
||
|
||
// 使用 await 而非 .Result 或 .Wait
|
||
var task = await GetTaskByIdAsync(id);
|
||
|
||
// 使用 ConfigureAwait(false) 在库代码中
|
||
public async Task<List<Task>> GetTasksAsync()
|
||
{
|
||
return await _taskRepository.GetAllAsync().ConfigureAwait(false);
|
||
}
|
||
```
|
||
|
||
#### 依赖注入
|
||
```csharp
|
||
// 优先使用构造函数注入
|
||
public class TaskService : ITaskService
|
||
{
|
||
private readonly ITaskRepository _taskRepository;
|
||
private readonly ILogger<TaskService> _logger;
|
||
|
||
public TaskService(ITaskRepository taskRepository, ILogger<TaskService> logger)
|
||
{
|
||
_taskRepository = taskRepository;
|
||
_logger = logger;
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 异常处理
|
||
```csharp
|
||
// 使用具体的异常类型
|
||
public async Task<Task> GetTaskByIdAsync(int id)
|
||
{
|
||
var task = await _taskRepository.GetByIdAsync(id);
|
||
|
||
if (task == null)
|
||
{
|
||
throw new NotFoundException($"Task with id {id} not found");
|
||
}
|
||
|
||
return task;
|
||
}
|
||
|
||
// 使用 using 语句管理资源
|
||
using var context = new TodoDbContext();
|
||
```
|
||
|
||
#### LINQ 使用
|
||
```csharp
|
||
// 优先使用方法语法
|
||
var completedTasks = tasks.Where(t => t.IsCompleted).ToList();
|
||
|
||
// 复杂查询使用查询语法
|
||
var query = from task in tasks
|
||
where task.IsCompleted
|
||
orderby task.CreatedAt descending
|
||
select task;
|
||
```
|
||
|
||
### 3.5 文档注释
|
||
```csharp
|
||
/// <summary>
|
||
/// 获取指定 ID 的任务
|
||
/// </summary>
|
||
/// <param name="id">任务 ID</param>
|
||
/// <returns>任务对象</returns>
|
||
/// <exception cref="NotFoundException">当任务不存在时抛出</exception>
|
||
public async Task<Task> GetTaskByIdAsync(int id)
|
||
{
|
||
// 实现
|
||
}
|
||
```
|
||
|
||
## 4. JavaScript/TypeScript 代码规范
|
||
|
||
### 4.1 命名规范
|
||
|
||
#### 变量和函数
|
||
```typescript
|
||
// 变量使用 camelCase
|
||
const taskList = [];
|
||
let currentTask = null;
|
||
|
||
// 函数使用 camelCase
|
||
function getTasks() {
|
||
// 实现
|
||
}
|
||
|
||
// 常量使用 UPPER_SNAKE_CASE
|
||
const MAX_TASK_TITLE_LENGTH = 200;
|
||
```
|
||
|
||
#### 类和接口
|
||
```typescript
|
||
// 类名使用 PascalCase
|
||
class TaskService {
|
||
// 实现
|
||
}
|
||
|
||
// 接口名使用 PascalCase
|
||
interface Task {
|
||
id: number;
|
||
title: string;
|
||
}
|
||
|
||
// 类型别名使用 PascalCase
|
||
type TaskPriority = 'high' | 'medium' | 'low';
|
||
```
|
||
|
||
### 4.2 代码组织
|
||
|
||
#### 文件结构
|
||
```typescript
|
||
// 1. 导入语句
|
||
import { ref, computed } from 'vue';
|
||
import { useTaskStore } from '@/stores/tasks';
|
||
import type { Task } from '@/types/task';
|
||
|
||
// 2. 类型定义
|
||
interface TaskForm {
|
||
title: string;
|
||
priority: TaskPriority;
|
||
}
|
||
|
||
// 3. 常量定义
|
||
const DEFAULT_PRIORITY: TaskPriority = 'medium';
|
||
|
||
// 4. 组合式函数或组件
|
||
export function useTasks() {
|
||
// 实现
|
||
}
|
||
```
|
||
|
||
#### 模块导入
|
||
```typescript
|
||
// 优先使用 ES6 模块语法
|
||
import { ref } from 'vue';
|
||
import axios from 'axios';
|
||
|
||
// 导出使用具名导出
|
||
export function useTasks() {
|
||
// 实现
|
||
}
|
||
|
||
export default useTasks;
|
||
```
|
||
|
||
### 4.3 TypeScript 规范
|
||
|
||
#### 类型定义
|
||
```typescript
|
||
// 为所有函数参数和返回值添加类型
|
||
function getTaskById(id: number): Task | null {
|
||
// 实现
|
||
}
|
||
|
||
// 使用接口定义对象类型
|
||
interface Task {
|
||
id: number;
|
||
title: string;
|
||
priority: TaskPriority;
|
||
isCompleted: boolean;
|
||
createdAt: Date;
|
||
}
|
||
|
||
// 使用类型别名定义联合类型
|
||
type TaskPriority = 'high' | 'medium' | 'low';
|
||
|
||
// 使用泛型提高代码复用性
|
||
interface ApiResponse<T> {
|
||
data: T;
|
||
message: string;
|
||
}
|
||
```
|
||
|
||
#### 类型断言
|
||
```typescript
|
||
// 优先使用类型守卫而非类型断言
|
||
function isTask(obj: unknown): obj is Task {
|
||
return typeof obj === 'object' && obj !== null && 'id' in obj;
|
||
}
|
||
|
||
// 避免使用 as any
|
||
const task = response.data as Task; // 避免
|
||
```
|
||
|
||
### 4.4 异步编程
|
||
```typescript
|
||
// 使用 async/await 而非 Promise 链
|
||
async function getTasks(): Promise<Task[]> {
|
||
const response = await axios.get('/api/task');
|
||
return response.data;
|
||
}
|
||
|
||
// 错误处理
|
||
try {
|
||
const tasks = await getTasks();
|
||
} catch (error) {
|
||
console.error('Failed to fetch tasks:', error);
|
||
}
|
||
```
|
||
|
||
## 5. Vue.js 代码规范
|
||
|
||
### 5.1 组件命名
|
||
```vue
|
||
<!-- 组件名使用 PascalCase -->
|
||
<script setup lang="ts">
|
||
// 组件名应与文件名一致
|
||
</script>
|
||
|
||
<template>
|
||
<!-- 模板中使用 kebab-case -->
|
||
<task-item :task="task" />
|
||
</template>
|
||
```
|
||
|
||
### 5.2 组件结构
|
||
```vue
|
||
<template>
|
||
<!-- 1. 模板 -->
|
||
<div class="task-list">
|
||
<task-item
|
||
v-for="task in tasks"
|
||
:key="task.id"
|
||
:task="task"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
// 2. 导入
|
||
import { ref, computed } from 'vue';
|
||
import { useTaskStore } from '@/stores/tasks';
|
||
import TaskItem from './TaskItem.vue';
|
||
|
||
// 3. Props 定义
|
||
interface Props {
|
||
filter: 'all' | 'active' | 'completed';
|
||
}
|
||
|
||
const props = withDefaults(defineProps<Props>(), {
|
||
filter: 'all'
|
||
});
|
||
|
||
// 4. Emits 定义
|
||
const emit = defineEmits<{
|
||
(e: 'task-created', task: Task): void;
|
||
}>();
|
||
|
||
// 5. 响应式状态
|
||
const taskStore = useTaskStore();
|
||
const tasks = computed(() => taskStore.filteredTasks(props.filter));
|
||
|
||
// 6. 方法
|
||
const handleCreateTask = async (title: string) => {
|
||
const task = await taskStore.createTask(title);
|
||
emit('task-created', task);
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* 6. 样式 */
|
||
.task-list {
|
||
padding: 16px;
|
||
}
|
||
</style>
|
||
```
|
||
|
||
### 5.3 组合式函数规范
|
||
```typescript
|
||
// composables/useTasks.ts
|
||
import { ref, computed } from 'vue';
|
||
import { useTaskStore } from '@/stores/tasks';
|
||
|
||
export function useTasks() {
|
||
const taskStore = useTaskStore();
|
||
const loading = ref(false);
|
||
const error = ref<string | null>(null);
|
||
|
||
const tasks = computed(() => taskStore.tasks);
|
||
const completedTasks = computed(() => taskStore.completedTasks);
|
||
|
||
const fetchTasks = async () => {
|
||
loading.value = true;
|
||
error.value = null;
|
||
|
||
try {
|
||
await taskStore.fetchTasks();
|
||
} catch (err) {
|
||
error.value = 'Failed to fetch tasks';
|
||
console.error(err);
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
};
|
||
|
||
return {
|
||
tasks,
|
||
completedTasks,
|
||
loading,
|
||
error,
|
||
fetchTasks
|
||
};
|
||
}
|
||
```
|
||
|
||
### 5.4 状态管理规范
|
||
```typescript
|
||
// stores/tasks.ts
|
||
import { defineStore } from 'pinia';
|
||
import { ref, computed } from 'vue';
|
||
import type { Task } from '@/types/task';
|
||
|
||
export const useTaskStore = defineStore('tasks', () => {
|
||
// State
|
||
const tasks = ref<Task[]>([]);
|
||
const loading = ref(false);
|
||
const error = ref<string | null>(null);
|
||
|
||
// Getters
|
||
const activeTasks = computed(() =>
|
||
tasks.value.filter(task => !task.isCompleted)
|
||
);
|
||
|
||
const completedTasks = computed(() =>
|
||
tasks.value.filter(task => task.isCompleted)
|
||
);
|
||
|
||
// Actions
|
||
async function fetchTasks() {
|
||
loading.value = true;
|
||
try {
|
||
const response = await fetch('/api/task');
|
||
tasks.value = await response.json();
|
||
} catch (err) {
|
||
error.value = 'Failed to fetch tasks';
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
|
||
return {
|
||
tasks,
|
||
loading,
|
||
error,
|
||
activeTasks,
|
||
completedTasks,
|
||
fetchTasks
|
||
};
|
||
});
|
||
```
|
||
|
||
## 6. API 设计规范
|
||
|
||
### 6.1 RESTful API 设计
|
||
```csharp
|
||
// 本项目的任务端点采用 Dynamic API 路由约定:/api/task(由中间件反射分发)
|
||
[HttpGet("task")]
|
||
public async Task<ActionResult<List<Task>>> GetTasks()
|
||
{
|
||
}
|
||
|
||
// 使用资源 ID
|
||
[HttpGet("task/{id}")]
|
||
public async Task<ActionResult<Task>> GetTask(int id)
|
||
{
|
||
}
|
||
|
||
// 使用 HTTP 方法表示操作
|
||
[HttpPost("task")]
|
||
public async Task<ActionResult<Task>> CreateTask(CreateTaskDto dto)
|
||
{
|
||
}
|
||
|
||
[HttpPut("task")]
|
||
public async Task<ActionResult<Task>> UpdateTask(UpdateTaskDto dto)
|
||
{
|
||
}
|
||
|
||
[HttpDelete("task/{id}")]
|
||
public async Task<ActionResult> DeleteTask(int id)
|
||
{
|
||
}
|
||
```
|
||
|
||
### 6.2 响应格式
|
||
```csharp
|
||
// 统一的响应格式
|
||
public class ApiResponse<T>
|
||
{
|
||
public bool Success { get; set; }
|
||
public T Data { get; set; }
|
||
public string Message { get; set; }
|
||
public List<string> Errors { get; set; }
|
||
}
|
||
|
||
// 成功响应
|
||
return Ok(new ApiResponse<Task>
|
||
{
|
||
Success = true,
|
||
Data = task,
|
||
Message = "Task created successfully"
|
||
});
|
||
|
||
// 错误响应
|
||
return BadRequest(new ApiResponse<object>
|
||
{
|
||
Success = false,
|
||
Message = "Validation failed",
|
||
Errors = new List<string> { "Title is required" }
|
||
});
|
||
```
|
||
|
||
## 7. Git 提交规范
|
||
|
||
### 7.1 提交信息格式
|
||
```
|
||
<type>(<scope>): <subject>
|
||
|
||
<body>
|
||
|
||
<footer>
|
||
```
|
||
|
||
### 7.2 Type 类型
|
||
- `feat`: 新功能
|
||
- `fix`: 修复 bug
|
||
- `docs`: 文档更新
|
||
- `style`: 代码格式调整(不影响代码运行)
|
||
- `refactor`: 重构(既不是新功能也不是修复 bug)
|
||
- `perf`: 性能优化
|
||
- `test`: 测试相关
|
||
- `chore`: 构建过程或辅助工具的变动
|
||
|
||
### 7.3 示例
|
||
```
|
||
feat(api): add task completion endpoint
|
||
|
||
- Add PATCH /api/task/{id}/toggle endpoint
|
||
- Update task service to handle completion logic
|
||
- Add unit tests for completion functionality
|
||
|
||
Closes #123
|
||
```
|
||
|
||
## 8. 测试规范
|
||
|
||
### 8.1 单元测试
|
||
```csharp
|
||
// 测试类命名: ClassName + Tests
|
||
public class TaskServiceTests
|
||
{
|
||
[Fact]
|
||
public async Task GetTasksAsync_ReturnsAllTasks()
|
||
{
|
||
// Arrange
|
||
var mockRepository = new Mock<ITaskRepository>();
|
||
var service = new TaskService(mockRepository.Object);
|
||
|
||
// Act
|
||
var result = await service.GetTasksAsync();
|
||
|
||
// Assert
|
||
Assert.NotNull(result);
|
||
Assert.Equal(3, result.Count);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 8.2 集成测试
|
||
```csharp
|
||
public class ApiIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
|
||
{
|
||
private readonly HttpClient _client;
|
||
|
||
public ApiIntegrationTests(WebApplicationFactory<Program> factory)
|
||
{
|
||
_client = factory.CreateClient();
|
||
}
|
||
|
||
[Fact]
|
||
public async Task GetTasks_ReturnsSuccessAndCorrectContentType()
|
||
{
|
||
// Act
|
||
var response = await _client.GetAsync("/api/task");
|
||
|
||
// Assert
|
||
response.EnsureSuccessStatusCode();
|
||
Assert.Equal("application/json", response.Content.Headers.ContentType?.MediaType);
|
||
}
|
||
}
|
||
```
|
||
|
||
## 9. 代码审查清单
|
||
|
||
### 9.1 代码质量
|
||
- [ ] 代码符合项目规范
|
||
- [ ] 变量和函数命名清晰
|
||
- [ ] 没有重复代码
|
||
- [ ] 复杂逻辑有注释说明
|
||
- [ ] 没有硬编码的魔法数字
|
||
|
||
### 9.2 功能正确性
|
||
- [ ] 功能实现符合需求
|
||
- [ ] 边界条件已处理
|
||
- [ ] 错误处理完善
|
||
- [ ] 有相应的单元测试
|
||
|
||
### 9.3 性能和安全
|
||
- [ ] 没有性能问题
|
||
- [ ] 敏感数据已保护
|
||
- [ ] 输入验证完善
|
||
- [ ] 没有安全漏洞
|
||
|
||
## 10. 工具配置
|
||
|
||
### 10.1 C# 工具
|
||
- **代码格式化**: dotnet format
|
||
- **代码分析**: Roslyn Analyzers
|
||
- **代码风格**: .editorconfig
|
||
- **文档生成**: DocFX
|
||
|
||
### 10.2 JavaScript/TypeScript 工具
|
||
- **代码格式化**: Prettier
|
||
- **代码检查**: ESLint
|
||
- **类型检查**: TypeScript
|
||
- **代码风格**: .prettierrc
|
||
|
||
### 10.3 .editorconfig 示例
|
||
```ini
|
||
root = true
|
||
|
||
[*.cs]
|
||
indent_style = space
|
||
indent_size = 4
|
||
end_of_line = crlf
|
||
charset = utf-8
|
||
trim_trailing_whitespace = true
|
||
insert_final_newline = true
|
||
|
||
[*.{js,ts,vue}]
|
||
indent_style = space
|
||
indent_size = 2
|
||
end_of_line = lf
|
||
charset = utf-8
|
||
trim_trailing_whitespace = true
|
||
insert_final_newline = true
|
||
```
|