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

19 KiB
Raw Permalink Blame History

开发规范文档

版本: v1.0
目的: 确保代码质量、可维护性、团队协作效率


一、代码规范

1.1 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

注释规范

/**
 * 工作流服务
 * 
 * 职责:
 * 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 注释必须标注负责人和日期
// TODO(zhangsan, 2024-01-15): 优化表达式缓存策略
// FIXME(lisi, 2024-01-16): 修复循环依赖检测bug

异常处理

// ✅ 正确:具体的异常类型
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 {
    // ...
}

日志规范

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 代码规范

命名规范

// ✅ 正确
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 类型定义

// ✅ 正确:明确的类型
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 组件规范

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 最佳实践

// ✅ 使用 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 分支命名规范

# 功能分支
feature/workflow-editor          # 新功能
feature/node-http-request        # 新节点类型

# 修复分支
bugfix/expression-parser-null    # Bug修复

# 紧急修复
hotfix/security-vulnerability    # 安全漏洞

# 重构
refactor/simplify-converter      # 重构

2.3 Commit 规范

格式<type>(<scope>): <subject>

# ✅ 正确示例
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

描述模板

## 变更内容
简要描述本次PR的变更内容

## 变更类型
- [ ] 新功能
- [x] Bug修复
- [ ] 文档更新
- [ ] 重构
- [ ] 其他

## 相关Issue
Closes #123

## 测试
- [x] 单元测试已通过
- [x] 手动测试已完成
- [ ] 需要补充测试

## 截图如果有UI变化
![before](url)
![after](url)

## Checklist
- [x] 代码已自测
- [x] 已添加/更新测试
- [x] 已更新文档
- [x] 代码符合规范

三、测试规范

3.1 单元测试

覆盖率要求

  • 核心业务逻辑:>80%
  • 工具类:>90%
  • Controller>60%

命名规范

// 测试类ClassName + Test
public class WorkflowServiceTest {}

// 测试方法should_ExpectedBehavior_When_Condition
@Test
public void should_ThrowException_When_WorkflowNotFound() {}

@Test
public void should_ReturnSuccess_When_WorkflowExecuted() {}

示例

@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 集成测试

@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 前端测试

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 审查注释规范

// ✅ 建设性建议
// 建议这里可以使用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

# 工作流平台

## 快速开始

### 环境要求
- 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

@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 环境配置

# .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 发布流程

# 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 日志级别配置

# 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 表,找到卡住的节点

总结:以上规范是团队协作的基础,请严格遵守。如有疑问,请在团队会议上讨论。