flowable-devops/backend/docs/05-开发规范.md
dengqichen d42166d2c0 提交
2025-10-13 16:25:13 +08:00

837 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 开发规范文档
**版本**: v1.0
**目的**: 确保代码质量、可维护性、团队协作效率
---
## 一、代码规范
### 1.1 Java 代码规范
#### 命名规范
```java
// ✅ 正确
public class WorkflowService {} // 类名:大驼峰
public interface WorkflowNode {} // 接口名:大驼峰
public enum ExecutionStatus {} // 枚举:大驼峰
private String userId; // 变量:小驼峰
public static final int MAX_RETRY = 3; // 常量:大写下划线
public void executeWorkflow() {} // 方法:小驼峰
// ❌ 错误
public class workflow_service {} // 不要用下划线
private String UserId; // 变量不要大驼峰
public static final int maxRetry = 3; // 常量不要小驼峰
```
#### 包结构
```
com.workflow
├── controller # REST API控制器
│ ├── WorkflowController.java
│ └── NodeTypeController.java
├── service # 业务逻辑
│ ├── WorkflowService.java
│ └── ExecutionService.java
├── repository # 数据访问
│ └── WorkflowRepository.java
├── model # 数据模型
│ ├── entity/ # 实体类(对应数据库表)
│ ├── dto/ # 数据传输对象
│ └── vo/ # 视图对象
├── engine # 核心引擎
│ ├── ExpressionEngine.java
│ └── WorkflowConverter.java
├── nodes # 节点实现
│ ├── WorkflowNode.java
│ ├── HttpRequestNode.java
│ └── SendEmailNode.java
├── registry # 注册中心
│ └── NodeTypeRegistry.java
├── executor # 执行器
│ └── GenericNodeExecutor.java
├── exception # 自定义异常
│ └── WorkflowException.java
└── config # 配置类
└── FlowableConfig.java
```
#### 注释规范
```java
/**
* 工作流服务
*
* 职责:
* 1. 工作流的创建、更新、删除
* 2. 工作流的执行
* 3. 工作流定义的转换JSON → BPMN
*
* @author zhangsan
* @since 1.0.0
*/
@Service
public class WorkflowService {
/**
* 执行工作流
*
* @param workflowId 工作流ID
* @param input 输入参数
* @return 执行结果
* @throws WorkflowNotFoundException 工作流不存在
* @throws WorkflowExecutionException 执行失败
*/
public WorkflowExecutionResult execute(String workflowId, Map<String, Object> input) {
// 1. 验证工作流是否存在
WorkflowDefinition workflow = workflowRepository.findById(workflowId)
.orElseThrow(() -> new WorkflowNotFoundException(workflowId));
// 2. 准备执行上下文
Map<String, Object> variables = prepareVariables(workflow, input);
// 3. 启动Flowable流程
ProcessInstance instance = runtimeService.startProcessInstanceById(
workflow.getFlowableProcessDefinitionId(),
variables
);
// 4. 返回执行结果
return WorkflowExecutionResult.builder()
.executionId(instance.getId())
.status("running")
.build();
}
}
```
**注释要求**
- 所有 `public` 方法必须有 JavaDoc
- 复杂逻辑必须有行内注释(中文)
- TODO/FIXME 注释必须标注负责人和日期
```java
// TODO(zhangsan, 2024-01-15): 优化表达式缓存策略
// FIXME(lisi, 2024-01-16): 修复循环依赖检测bug
```
#### 异常处理
```java
// ✅ 正确:具体的异常类型
public WorkflowDefinition getById(String id) {
return workflowRepository.findById(id)
.orElseThrow(() -> new WorkflowNotFoundException("工作流不存在: " + id));
}
// ✅ 正确:记录日志
public void execute(String workflowId, Map<String, Object> input) {
try {
// 执行逻辑
doExecute(workflowId, input);
} catch (Exception e) {
log.error("工作流执行失败: workflowId={}, error={}", workflowId, e.getMessage(), e);
throw new WorkflowExecutionException("执行失败", e);
}
}
// ❌ 错误:吞掉异常
try {
doSomething();
} catch (Exception e) {
// 什么都不做
}
// ❌ 错误:抛出原始 Exception
public void doSomething() throws Exception {
// ...
}
```
#### 日志规范
```java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Service
public class WorkflowService {
private static final Logger log = LoggerFactory.getLogger(WorkflowService.class);
public void execute(String workflowId, Map<String, Object> input) {
// INFO: 重要的业务操作
log.info("开始执行工作流: workflowId={}, input={}", workflowId, input);
try {
// DEBUG: 调试信息
log.debug("准备执行上下文: variables={}", variables);
// ... 执行逻辑
log.info("工作流执行成功: workflowId={}, executionId={}", workflowId, executionId);
} catch (Exception e) {
// ERROR: 错误信息(必须包含异常栈)
log.error("工作流执行失败: workflowId={}", workflowId, e);
throw e;
}
}
}
```
**日志级别**
- **ERROR**: 错误,需要立即处理
- **WARN**: 警告,可能有问题但不影响运行
- **INFO**: 重要的业务操作(创建、更新、删除、执行)
- **DEBUG**: 调试信息(开发环境)
---
### 1.2 TypeScript/React 代码规范
#### 命名规范
```typescript
// ✅ 正确
interface WorkflowDefinition {} // 接口:大驼峰
type NodeType = 'http' | 'email'; // 类型:大驼峰
enum ExecutionStatus {} // 枚举:大驼峰
const userId = '123'; // 变量:小驼峰
const MAX_RETRY = 3; // 常量:大写下划线
function executeWorkflow() {} // 函数:小驼峰
// 组件:大驼峰
export default function WorkflowEditor() {}
const CustomNode: React.FC<Props> = () => {}
```
#### 文件命名
```
src/
├── components/
│ ├── WorkflowEditor/ # 组件文件夹:大驼峰
│ │ ├── index.tsx # 主文件:小写
│ │ ├── Canvas.tsx # 子组件:大驼峰
│ │ └── WorkflowEditor.css # 样式:大驼峰
│ └── NodeConfigPanel/
│ └── index.tsx
├── api/
│ └── workflow.ts # API文件小写
├── types/
│ └── workflow.ts # 类型文件:小写
└── utils/
└── expressionParser.ts # 工具文件:小驼峰
```
#### TypeScript 类型定义
```typescript
// ✅ 正确:明确的类型
interface WorkflowNode {
id: string;
type: string;
name: string;
position: { x: number; y: number };
config: Record<string, any>; // 如果确实是任意类型
}
// ✅ 正确:使用泛型
interface ApiResponse<T> {
code: number;
message: string;
data: T;
}
// ❌ 错误:滥用 any
function doSomething(data: any): any {
return data;
}
// ✅ 正确:使用 unknown 或具体类型
function doSomething(data: unknown): string {
if (typeof data === 'string') {
return data;
}
return JSON.stringify(data);
}
```
#### React 组件规范
```tsx
import React, { useState, useEffect, useCallback, memo } from 'react';
/**
* 节点配置面板
*
* @param node - 当前选中的节点
* @param onConfigChange - 配置变化回调
*/
interface Props {
node: Node | null;
onConfigChange: (nodeId: string, config: any) => void;
}
export default function NodeConfigPanel({ node, onConfigChange }: Props) {
// 1. hooks 放在最前面
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
// 2. useEffect
useEffect(() => {
if (node) {
form.setFieldsValue(node.data.config);
}
}, [node, form]);
// 3. 事件处理函数(使用 useCallback 优化)
const handleValuesChange = useCallback(
(changedValues: any, allValues: any) => {
if (node) {
onConfigChange(node.id, allValues);
}
},
[node, onConfigChange]
);
// 4. 条件渲染
if (!node) {
return <Empty description="请选择一个节点" />;
}
// 5. 主要渲染
return (
<div className="node-config-panel">
<Form
form={form}
onValuesChange={handleValuesChange}
>
{/* ... */}
</Form>
</div>
);
}
```
**React 最佳实践**
```tsx
// ✅ 使用 memo 优化性能
const CustomNode = memo(({ data }: Props) => {
return <div>{data.name}</div>;
});
// ✅ 使用 useMemo 缓存计算结果
const upstreamNodes = useMemo(() => {
return edges
.filter(edge => edge.target === node.id)
.map(edge => nodes.find(n => n.id === edge.source));
}, [node.id, nodes, edges]);
// ✅ 使用 useCallback 缓存函数
const handleClick = useCallback(() => {
console.log(node.id);
}, [node.id]);
// ❌ 在渲染中创建新对象/函数(每次重新渲染)
return (
<div onClick={() => console.log(node.id)}> {/* 每次都创建新函数 */}
<Component style={{ color: 'red' }} /> {/* 每次都创建新对象 */}
</div>
);
```
---
## 二、Git 工作流
### 2.1 分支策略
```
main/master # 生产环境只能通过PR合并
develop # 开发环境(默认分支)
feature/xxx # 功能分支
hotfix/xxx # 紧急修复分支
```
### 2.2 分支命名规范
```bash
# 功能分支
feature/workflow-editor # 新功能
feature/node-http-request # 新节点类型
# 修复分支
bugfix/expression-parser-null # Bug修复
# 紧急修复
hotfix/security-vulnerability # 安全漏洞
# 重构
refactor/simplify-converter # 重构
```
### 2.3 Commit 规范
**格式**`<type>(<scope>): <subject>`
```bash
# ✅ 正确示例
feat(editor): 添加字段映射选择器
fix(expression): 修复表达式解析空指针异常
docs(readme): 更新部署文档
style(format): 格式化代码
refactor(converter): 简化BPMN转换逻辑
perf(cache): 优化表达式缓存策略
test(service): 添加工作流服务单元测试
chore(deps): 升级Spring Boot到3.2.1
# ❌ 错误示例
update code # 太模糊
修复bug # 用中文
fix: fixed a bug # 重复
```
**Type 类型**
- `feat`: 新功能
- `fix`: Bug修复
- `docs`: 文档更新
- `style`: 代码格式(不影响代码运行)
- `refactor`: 重构
- `perf`: 性能优化
- `test`: 测试
- `chore`: 构建/工具链
### 2.4 PR (Pull Request) 规范
**标题**
```
feat(editor): 实现字段映射选择器
fix(#123): 修复表达式解析bug
```
**描述模板**
```markdown
## 变更内容
简要描述本次PR的变更内容
## 变更类型
- [ ] 新功能
- [x] Bug修复
- [ ] 文档更新
- [ ] 重构
- [ ] 其他
## 相关Issue
Closes #123
## 测试
- [x] 单元测试已通过
- [x] 手动测试已完成
- [ ] 需要补充测试
## 截图如果有UI变化
![before](url)
![after](url)
## Checklist
- [x] 代码已自测
- [x] 已添加/更新测试
- [x] 已更新文档
- [x] 代码符合规范
```
---
## 三、测试规范
### 3.1 单元测试
**覆盖率要求**
- 核心业务逻辑:>80%
- 工具类:>90%
- Controller>60%
**命名规范**
```java
// 测试类ClassName + Test
public class WorkflowServiceTest {}
// 测试方法should_ExpectedBehavior_When_Condition
@Test
public void should_ThrowException_When_WorkflowNotFound() {}
@Test
public void should_ReturnSuccess_When_WorkflowExecuted() {}
```
**示例**
```java
@SpringBootTest
public class ExpressionEngineTest {
@Autowired
private ExpressionEngine expressionEngine;
@Mock
private DelegateExecution execution;
@BeforeEach
public void setUp() {
// 准备测试数据
Map<String, Object> nodes = Map.of(
"node1", Map.of(
"output", Map.of("email", "test@example.com")
)
);
when(execution.getVariable("nodes")).thenReturn(nodes);
}
@Test
public void should_ResolveExpression_When_ValidExpression() {
// Given
String expression = "${nodes.node1.output.email}";
// When
String result = (String) expressionEngine.evaluate(expression, execution);
// Then
assertEquals("test@example.com", result);
}
@Test
public void should_ThrowException_When_InvalidExpression() {
// Given
String expression = "${invalid.path}";
// When & Then
assertThrows(ExpressionEvaluationException.class, () -> {
expressionEngine.evaluate(expression, execution);
});
}
}
```
### 3.2 集成测试
```java
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class WorkflowControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Test
public void should_CreateWorkflow_When_ValidRequest() throws Exception {
// Given
WorkflowDefinition workflow = WorkflowDefinition.builder()
.name("Test Workflow")
.build();
// When & Then
mockMvc.perform(post("/api/workflows")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(workflow)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").exists())
.andExpect(jsonPath("$.name").value("Test Workflow"));
}
}
```
### 3.3 前端测试
```typescript
import { render, screen, fireEvent } from '@testing-library/react';
import FieldMappingSelector from './FieldMappingSelector';
describe('FieldMappingSelector', () => {
it('应该显示上游节点的字段', () => {
const upstreamNodes = [
{
id: 'node1',
data: { type: 'http_request', name: 'HTTP' },
},
];
render(
<FieldMappingSelector
upstreamNodes={upstreamNodes}
nodeTypes={mockNodeTypes}
/>
);
expect(screen.getByText('HTTP')).toBeInTheDocument();
});
it('应该生成正确的表达式', () => {
const onChange = jest.fn();
render(
<FieldMappingSelector
upstreamNodes={upstreamNodes}
onChange={onChange}
/>
);
// 选择字段
fireEvent.change(screen.getByRole('combobox'), {
target: { value: 'nodes.node1.output.email' },
});
// 验证
expect(onChange).toHaveBeenCalledWith('${nodes.node1.output.email}');
});
});
```
---
## 四、代码审查Code Review
### 4.1 审查清单
**功能性**
- [ ] 代码实现了需求
- [ ] 边界情况已处理
- [ ] 错误处理完善
**代码质量**
- [ ] 命名清晰易懂
- [ ] 逻辑简洁
- [ ] 没有重复代码
- [ ] 注释充分
**性能**
- [ ] 没有明显的性能问题
- [ ] 数据库查询优化
- [ ] 缓存使用合理
**安全**
- [ ] 输入验证
- [ ] SQL注入防护
- [ ] XSS防护
**测试**
- [ ] 有单元测试
- [ ] 测试覆盖核心逻辑
- [ ] 测试通过
### 4.2 审查注释规范
```java
// ✅ 建设性建议
// 建议这里可以使用Optional避免空指针
if (user != null) {
return user.getName();
}
// 可以改为:
return Optional.ofNullable(user)
.map(User::getName)
.orElse("Unknown");
// ✅ 指出问题
// 问题这里有SQL注入风险
String sql = "SELECT * FROM users WHERE name = '" + userName + "'";
// 应该使用:
String sql = "SELECT * FROM users WHERE name = ?";
// ❌ 不要这样
// 这代码写得太烂了
```
---
## 五、文档规范
### 5.1 README.md
```markdown
# 工作流平台
## 快速开始
### 环境要求
- JDK 17+
- Node.js 18+
- PostgreSQL 15+
- Redis 7+
### 本地开发
1. 克隆代码
\`\`\`bash
git clone https://github.com/yourorg/workflow-platform.git
cd workflow-platform
\`\`\`
2. 启动后端
\`\`\`bash
cd backend
./mvnw spring-boot:run
\`\`\`
3. 启动前端
\`\`\`bash
cd frontend
npm install
npm run dev
\`\`\`
4. 访问
http://localhost:3000
## 项目结构
...
## 开发文档
- [架构总览](docs/01-架构总览.md)
- [后端技术设计](docs/02-后端技术设计.md)
- [前端技术设计](docs/03-前端技术设计.md)
```
### 5.2 API 文档
使用 Swagger/OpenAPI
```java
@RestController
@RequestMapping("/api/workflows")
@Tag(name = "工作流管理", description = "工作流的创建、更新、删除、执行")
public class WorkflowController {
@PostMapping
@Operation(summary = "创建工作流", description = "创建一个新的工作流定义")
@ApiResponse(responseCode = "200", description = "创建成功")
@ApiResponse(responseCode = "400", description = "参数错误")
public ResponseEntity<WorkflowDefinition> createWorkflow(
@RequestBody
@io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "工作流定义",
required = true
)
WorkflowDefinition workflow
) {
// ...
}
}
```
访问http://localhost:8080/swagger-ui.html
---
## 六、部署规范
### 6.1 环境配置
```yaml
# .env.example提交到代码库
SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/workflow_db
SPRING_DATASOURCE_USERNAME=postgres
SPRING_DATASOURCE_PASSWORD=
REDIS_HOST=localhost
REDIS_PORT=6379
SMTP_HOST=smtp.example.com
SMTP_PASSWORD=
# .env不提交本地使用
SPRING_DATASOURCE_PASSWORD=your_password
SMTP_PASSWORD=your_smtp_password
```
### 6.2 版本号
使用语义化版本:`MAJOR.MINOR.PATCH`
```
1.0.0 - 第一个正式版本
1.1.0 - 新增功能(向后兼容)
1.1.1 - Bug修复
2.0.0 - 重大变更(不向后兼容)
```
### 6.3 发布流程
```bash
# 1. 更新版本号
# pom.xml: <version>1.1.0</version>
# package.json: "version": "1.1.0"
# 2. 更新 CHANGELOG.md
git add CHANGELOG.md
git commit -m "docs: 更新版本到 1.1.0"
# 3. 打标签
git tag -a v1.1.0 -m "Release v1.1.0"
git push origin v1.1.0
# 4. 构建
docker build -t workflow-platform:1.1.0 .
# 5. 部署
kubectl apply -f k8s/deployment.yaml
```
---
## 七、故障排查
### 7.1 日志级别配置
```yaml
# application-dev.yml
logging:
level:
root: INFO
com.workflow: DEBUG
org.flowable: DEBUG
# application-prod.yml
logging:
level:
root: WARN
com.workflow: INFO
org.flowable: WARN
```
### 7.2 常见问题
**问题1表达式解析失败**
```
错误ExpressionEvaluationException: 表达式解析失败
原因:上游节点输出结构与预期不符
解决:检查 outputSchema 定义,查看实际输出
```
**问题2工作流执行卡住**
```
错误:流程实例一直处于 running 状态
原因:某个节点执行超时或死锁
解决:查询 act_ru_execution 表,找到卡住的节点
```
---
**总结**:以上规范是团队协作的基础,请严格遵守。如有疑问,请在团队会议上讨论。