Files
Claude AIandClaude 9f9cbfbc80 feat: Complete MVP Sprint 01 project setup with comprehensive architecture
## 🏗️ Architecture & Design
- Add ADR collection (15 Architecture Decision Records)
- Comprehensive system design documentation
- Implementation roadmap and project planning

## 📋 Requirements & Documentation
- Detailed functional requirements
- API specifications (OpenAPI & AsyncAPI)
- Postman collections and optimization reports

## 🗄️ Database & Configuration
- Database migration scripts (CDC, core schema)
- Resilience4j configuration for resilience patterns
- Alert manager and Prometheus monitoring rules

## 🎯 Project Templates
- Admin frontend template (Arco Pro + Vue 3)
- Big screen data visualization template (ECharts)
- Enterprise backend template (Jeecg Boot + Spring Boot)

## 🧪 Testing & Quality
- K6 performance testing scenarios
- Load testing benchmark scripts
- Quality assurance framework setup

## 📦 Infrastructure & Deployment
- Complete infrastructure configurations
- Deployment strategies and CI/CD pipelines
- Monitoring and observability setup

This commit establishes the complete foundation for the MVP Sprint 01
development cycle with all necessary architectural decisions, templates,
and documentation ready for team collaboration.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2025-10-11 23:43:18 +08:00
..

开发指南与实施方案

概述

基于系统架构设计和开源技术栈,制定光明科学城科技成果转化平台的开发实施方案,包括开发规范、代码结构、实现指南和最佳实践。

1. 开发环境搭建

1.1 本地开发环境

基础环境要求

# Java开发环境
Java: OpenJDK 17 LTS
Maven: 3.9.0+
IDE: IntelliJ IDEA 2023.3+ / Eclipse 2023-12

# 前端开发环境  
Node.js: 18.18.0 LTS
npm: 9.8.0+ / yarn: 1.22.0+
IDE: VS Code / WebStorm

# Python开发环境 (AI服务)
Python: 3.11+
pip: 23.0+
IDE: PyCharm / VS Code

# 容器环境
Docker: 24.0+
Docker Compose: 2.20+
Kubernetes: 1.28+ (可选)

基础设施服务

# docker-compose.yml - 开发环境
version: '3.8'
services:
  # 达梦数据库 (开发环境可用 PostgreSQL 替代)
  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: gmkx_dev
      POSTGRES_USER: gmkx
      POSTGRES_PASSWORD: gmkx123
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./sql/init:/docker-entrypoint-initdb.d

  # Redis 缓存
  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data

  # RabbitMQ 消息队列
  rabbitmq:
    image: rabbitmq:3.12-management-alpine
    environment:
      RABBITMQ_DEFAULT_USER: gmkx
      RABBITMQ_DEFAULT_PASS: gmkx123
    ports:
      - "5672:5672"
      - "15672:15672"
    volumes:
      - rabbitmq_data:/var/lib/rabbitmq

  # OpenSearch 搜索引擎
  opensearch:
    image: opensearchproject/opensearch:2.11.0
    environment:
      - discovery.type=single-node
      - OPENSEARCH_INITIAL_ADMIN_PASSWORD=Admin123!
    ports:
      - "9200:9200"
      - "9600:9600"
    volumes:
      - opensearch_data:/usr/share/opensearch/data

  # Milvus 向量数据库
  milvus:
    image: milvusdb/milvus:v2.3.2
    command: ["milvus", "run", "standalone"]
    environment:
      ETCD_ENDPOINTS: etcd:2379
      MINIO_ADDRESS: minio:9000
    ports:
      - "19530:19530"
    depends_on:
      - etcd
      - minio

  # MinIO 对象存储
  minio:
    image: minio/minio:latest
    command: server /data --console-address ":9001"
    environment:
      MINIO_ROOT_USER: minioadmin
      MINIO_ROOT_PASSWORD: minioadmin
    ports:
      - "9000:9000"
      - "9001:9001"
    volumes:
      - minio_data:/data

  # Nacos 配置中心
  nacos:
    image: nacos/nacos-server:v2.3.0
    environment:
      MODE: standalone
      SPRING_DATASOURCE_PLATFORM: mysql
    ports:
      - "8848:8848"
      - "9848:9848"

volumes:
  postgres_data:
  redis_data:
  rabbitmq_data:
  opensearch_data:
  minio_data:

1.2 开发工具配置

IDE 配置模板

<!-- IntelliJ IDEA - .idea/codeStyles/Project.xml -->
<component name="ProjectCodeStyleConfiguration">
  <code_scheme name="Project" version="173">
    <JavaCodeStyleSettings>
      <option name="IMPORT_LAYOUT_TABLE">
        <value>
          <package name="java" withSubpackages="true" static="false"/>
          <package name="javax" withSubpackages="true" static="false"/>
          <emptyLine/>
          <package name="org.springframework" withSubpackages="true" static="false"/>
          <package name="org" withSubpackages="true" static="false"/>
          <package name="com" withSubpackages="true" static="false"/>
          <emptyLine/>
          <package name="" withSubpackages="true" static="false"/>
          <emptyLine/>
          <package name="" withSubpackages="true" static="true"/>
        </value>
      </option>
    </JavaCodeStyleSettings>
  </code_scheme>
</component>

VS Code 配置

// .vscode/settings.json
{
  "editor.tabSize": 2,
  "editor.insertSpaces": true,
  "editor.detectIndentation": false,
  "editor.formatOnSave": true,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true,
    "source.organizeImports": true
  },
  "typescript.preferences.importModuleSpecifier": "relative",
  "vue.codeActions.enabled": true,
  "vue.complete.casing.tags": "kebab",
  "vue.complete.casing.props": "camel"
}

// .vscode/extensions.json
{
  "recommendations": [
    "vue.volar",
    "vue.vscode-typescript-vue-plugin", 
    "bradlc.vscode-tailwindcss",
    "esbenp.prettier-vscode",
    "dbaeumer.vscode-eslint",
    "ms-python.python",
    "ms-vscode.vscode-typescript-next"
  ]
}

2. 项目结构与规范

2.1 后端项目结构 (Java)

gmkx-platform/
├── gmkx-common/                    # 公共模块
│   ├── gmkx-common-core/          # 核心工具类
│   ├── gmkx-common-security/      # 安全组件
│   ├── gmkx-common-cache/         # 缓存组件
│   ├── gmkx-common-database/      # 数据库组件
│   ├── gmkx-common-mq/            # 消息队列组件
│   └── gmkx-common-web/           # Web组件
├── gmkx-gateway/                   # API网关
├── gmkx-auth/                      # 认证服务
├── gmkx-services/                  # 业务服务
│   ├── gmkx-user-service/         # 用户服务
│   ├── gmkx-achievement-service/  # 成果服务
│   ├── gmkx-demand-service/       # 需求服务
│   ├── gmkx-broker-service/       # 经纪服务
│   ├── gmkx-activity-service/     # 活动服务
│   ├── gmkx-file-service/         # 文件服务
│   └── gmkx-notification-service/ # 通知服务
├── gmkx-ai/                        # AI服务 (Python)
└── gmkx-deployment/                # 部署配置
    ├── docker/
    ├── kubernetes/
    └── helm/

单个服务内部结构

gmkx-user-service/
├── src/main/java/com/gmkx/user/
│   ├── UserServiceApplication.java          # 启动类
│   ├── config/                              # 配置类
│   │   ├── DatabaseConfig.java
│   │   ├── RedisConfig.java
│   │   └── SecurityConfig.java
│   ├── controller/                          # 控制器层
│   │   ├── UserController.java
│   │   ├── OrganizationController.java
│   │   └── ExpertController.java
│   ├── service/                             # 服务层
│   │   ├── UserService.java
│   │   ├── impl/
│   │   │   └── UserServiceImpl.java
│   │   └── external/                        # 外部服务调用
│   │       └── AuthServiceClient.java
│   ├── repository/                          # 数据访问层
│   │   ├── UserRepository.java
│   │   └── mapper/
│   │       └── UserMapper.java
│   ├── domain/                              # 领域层
│   │   ├── entity/                          # 实体类
│   │   │   ├── User.java
│   │   │   └── Organization.java
│   │   ├── aggregate/                       # 聚合根
│   │   │   └── UserAggregate.java
│   │   ├── valueobject/                     # 值对象
│   │   │   ├── UserId.java
│   │   │   └── ContactInfo.java
│   │   └── event/                           # 领域事件
│   │       ├── UserRegisteredEvent.java
│   │       └── UserActivatedEvent.java
│   ├── dto/                                 # 数据传输对象
│   │   ├── request/
│   │   │   ├── CreateUserRequest.java
│   │   │   └── UpdateUserRequest.java
│   │   └── response/
│   │       ├── UserResponse.java
│   │       └── UserDetailResponse.java
│   ├── infrastructure/                      # 基础设施层
│   │   ├── messaging/                       # 消息处理
│   │   │   ├── UserEventPublisher.java
│   │   │   └── UserEventListener.java
│   │   ├── cache/                           # 缓存实现
│   │   │   └── UserCacheService.java
│   │   └── external/                        # 外部集成
│   │       └── EmailServiceImpl.java
│   └── common/                              # 通用组件
│       ├── exception/
│       │   └── UserNotFoundException.java
│       ├── enums/
│       │   └── UserStatus.java
│       └── constants/
│           └── UserConstants.java
├── src/main/resources/
│   ├── application.yml                      # 应用配置
│   ├── application-dev.yml                 # 开发环境配置
│   ├── application-prod.yml                # 生产环境配置
│   ├── bootstrap.yml                       # 启动配置
│   ├── mapper/                             # MyBatis映射文件
│   │   └── UserMapper.xml
│   └── db/migration/                       # 数据库迁移
│       ├── V1.0.0__Create_user_tables.sql
│       └── V1.0.1__Add_user_indexes.sql
├── src/test/java/                          # 测试代码
└── pom.xml                                 # Maven配置

2.2 前端项目结构 (Vue 3)

gmkx-web/
├── public/                                 # 静态资源
│   ├── index.html
│   ├── favicon.ico
│   └── assets/
├── src/
│   ├── main.ts                            # 应用入口
│   ├── App.vue                            # 根组件
│   ├── components/                        # 通用组件
│   │   ├── common/                        # 基础组件
│   │   │   ├── GButton.vue
│   │   │   ├── GModal.vue
│   │   │   ├── GTable.vue
│   │   │   └── GForm.vue
│   │   ├── business/                      # 业务组件
│   │   │   ├── AchievementCard.vue
│   │   │   ├── DemandCard.vue
│   │   │   └── UserProfile.vue
│   │   └── layout/                        # 布局组件
│   │       ├── AppHeader.vue
│   │       ├── AppSidebar.vue
│   │       └── AppFooter.vue
│   ├── views/                             # 页面组件
│   │   ├── Home.vue
│   │   ├── achievement/
│   │   │   ├── AchievementList.vue
│   │   │   ├── AchievementDetail.vue
│   │   │   └── AchievementForm.vue
│   │   ├── demand/
│   │   │   ├── DemandList.vue
│   │   │   ├── DemandDetail.vue
│   │   │   └── DemandForm.vue
│   │   └── user/
│   │       ├── UserCenter.vue
│   │       └── UserSettings.vue
│   ├── router/                            # 路由配置
│   │   ├── index.ts
│   │   └── modules/
│   │       ├── achievement.ts
│   │       ├── demand.ts
│   │       └── user.ts
│   ├── store/                             # 状态管理
│   │   ├── index.ts
│   │   └── modules/
│   │       ├── user.ts
│   │       ├── achievement.ts
│   │       └── app.ts
│   ├── api/                               # API接口
│   │   ├── index.ts
│   │   ├── types.ts
│   │   └── modules/
│   │       ├── user.ts
│   │       ├── achievement.ts
│   │       └── demand.ts
│   ├── utils/                             # 工具函数
│   │   ├── request.ts                     # HTTP请求封装
│   │   ├── auth.ts                        # 认证工具
│   │   ├── storage.ts                     # 存储工具
│   │   ├── validation.ts                  # 表单验证
│   │   └── format.ts                      # 格式化工具
│   ├── composables/                       # 组合式函数
│   │   ├── useAuth.ts
│   │   ├── useTable.ts
│   │   ├── useForm.ts
│   │   └── useWebSocket.ts
│   ├── styles/                            # 样式文件
│   │   ├── index.scss                     # 全局样式
│   │   ├── variables.scss                 # 样式变量
│   │   ├── mixins.scss                    # 样式混入
│   │   └── components/                    # 组件样式
│   ├── assets/                            # 资源文件
│   │   ├── images/
│   │   ├── icons/
│   │   └── fonts/
│   ├── locales/                           # 国际化
│   │   ├── index.ts
│   │   ├── zh-CN.ts
│   │   └── en-US.ts
│   └── types/                             # TypeScript类型
│       ├── global.d.ts
│       ├── api.d.ts
│       └── components.d.ts
├── tests/                                 # 测试文件
│   ├── unit/
│   └── e2e/
├── .env                                   # 环境变量
├── .env.development                       # 开发环境变量
├── .env.production                        # 生产环境变量
├── vite.config.ts                         # Vite配置
├── tsconfig.json                          # TypeScript配置
├── package.json                           # 项目配置
└── README.md                              # 项目说明

2.3 AI服务项目结构 (Python)

gmkx-ai/
├── app/
│   ├── __init__.py
│   ├── main.py                            # FastAPI应用入口
│   ├── core/                              # 核心配置
│   │   ├── __init__.py
│   │   ├── config.py                      # 应用配置
│   │   ├── security.py                    # 安全配置
│   │   └── logging.py                     # 日志配置
│   ├── api/                               # API路由
│   │   ├── __init__.py
│   │   ├── deps.py                        # 依赖注入
│   │   └── v1/
│   │       ├── __init__.py
│   │       ├── match.py                   # 匹配接口
│   │       ├── recommend.py               # 推荐接口
│   │       └── vectorize.py               # 向量化接口
│   ├── services/                          # 业务服务
│   │   ├── __init__.py
│   │   ├── match_service.py               # 匹配服务
│   │   ├── recommend_service.py           # 推荐服务
│   │   ├── feature_service.py             # 特征服务
│   │   └── model_service.py               # 模型服务
│   ├── models/                            # 算法模型
│   │   ├── __init__.py
│   │   ├── matching/                      # 匹配算法
│   │   │   ├── semantic_matcher.py
│   │   │   ├── keyword_matcher.py
│   │   │   └── hybrid_matcher.py
│   │   ├── recommendation/                # 推荐算法
│   │   │   ├── collaborative_filter.py
│   │   │   ├── content_filter.py
│   │   │   └── deep_recommender.py
│   │   └── nlp/                           # NLP模型
│   │       ├── text_encoder.py
│   │       ├── keyword_extractor.py
│   │       └── classifier.py
│   ├── schemas/                           # 数据模式
│   │   ├── __init__.py
│   │   ├── match.py
│   │   ├── recommend.py
│   │   └── common.py
│   ├── db/                                # 数据库
│   │   ├── __init__.py
│   │   ├── session.py                     # 数据库会话
│   │   ├── models.py                      # 数据模型
│   │   └── repositories/                  # 数据仓库
│   │       ├── __init__.py
│   │       ├── achievement_repo.py
│   │       └── demand_repo.py
│   ├── utils/                             # 工具函数
│   │   ├── __init__.py
│   │   ├── vector_utils.py                # 向量工具
│   │   ├── text_utils.py                  # 文本工具
│   │   └── cache_utils.py                 # 缓存工具
│   └── tests/                             # 测试代码
│       ├── __init__.py
│       ├── test_match.py
│       └── test_recommend.py
├── requirements/                          # 依赖管理
│   ├── base.txt                          # 基础依赖
│   ├── dev.txt                           # 开发依赖
│   └── prod.txt                          # 生产依赖
├── scripts/                               # 脚本文件
│   ├── start.sh                          # 启动脚本
│   ├── train_model.py                    # 模型训练
│   └── data_import.py                    # 数据导入
├── configs/                               # 配置文件
│   ├── development.yml
│   ├── production.yml
│   └── model_configs/
├── Dockerfile                             # Docker构建
├── docker-compose.yml                     # 容器编排
└── README.md                              # 项目说明

3. 编码规范与标准

3.1 Java 编码规范

3.1.1 命名规范

// 类名:大驼峰命名法
public class UserService { }
public class AchievementManagementController { }

// 方法名:小驼峰命名法
public void createUser() { }
public List<Achievement> searchAchievements() { }

// 变量名:小驼峰命名法
private String userName;
private Long achievementId;

// 常量名:全大写+下划线
public static final String DEFAULT_CHARSET = "UTF-8";
public static final int MAX_RETRY_TIMES = 3;

// 包名:全小写
package com.gmkx.user.service;
package com.gmkx.achievement.controller;

3.1.2 代码结构规范

/**
 * 用户服务实现类
 * 
 * @author gmkx-team
 * @since 1.0.0
 */
@Service
@Transactional(rollbackFor = Exception.class)
@Slf4j
public class UserServiceImpl implements UserService {
    
    private final UserRepository userRepository;
    private final UserEventPublisher eventPublisher;
    private final RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 构造函数注入(推荐方式)
     */
    public UserServiceImpl(UserRepository userRepository,
                          UserEventPublisher eventPublisher,
                          RedisTemplate<String, Object> redisTemplate) {
        this.userRepository = userRepository;
        this.eventPublisher = eventPublisher;
        this.redisTemplate = redisTemplate;
    }
    
    /**
     * 创建用户
     * 
     * @param request 创建用户请求
     * @return 用户响应
     * @throws UserExistsException 用户已存在异常
     */
    @Override
    public UserResponse createUser(CreateUserRequest request) {
        // 1. 参数校验
        validateCreateRequest(request);
        
        // 2. 业务逻辑处理
        User user = buildUserFromRequest(request);
        user.setStatus(UserStatus.PENDING);
        user.setCreatedTime(LocalDateTime.now());
        
        // 3. 数据持久化
        User savedUser = userRepository.save(user);
        
        // 4. 发布领域事件
        UserRegisteredEvent event = UserRegisteredEvent.builder()
            .userId(savedUser.getId())
            .tenantId(savedUser.getTenantId())
            .userType(savedUser.getUserType())
            .timestamp(LocalDateTime.now())
            .build();
        eventPublisher.publish(event);
        
        // 5. 返回结果
        return UserResponse.fromEntity(savedUser);
    }
    
    /**
     * 私有方法:校验创建请求
     */
    private void validateCreateRequest(CreateUserRequest request) {
        Assert.notNull(request, "创建用户请求不能为空");
        Assert.hasText(request.getUsername(), "用户名不能为空");
        Assert.hasText(request.getEmail(), "邮箱不能为空");
        
        // 检查用户名是否已存在
        if (userRepository.existsByUsername(request.getUsername())) {
            throw new UserExistsException("用户名已存在: " + request.getUsername());
        }
    }
    
    /**
     * 私有方法:构建用户实体
     */
    private User buildUserFromRequest(CreateUserRequest request) {
        return User.builder()
            .username(request.getUsername())
            .email(request.getEmail())
            .phone(request.getPhone())
            .userType(request.getUserType())
            .tenantId(TenantContext.getCurrentTenantId())
            .build();
    }
}

3.1.3 异常处理规范

// 自定义业务异常
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class BusinessException extends RuntimeException {
    private final String errorCode;
    private final Object[] args;
    
    public BusinessException(String errorCode, String message, Object... args) {
        super(message);
        this.errorCode = errorCode;
        this.args = args;
    }
}

// 全局异常处理器
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    
    /**
     * 业务异常处理
     */
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) {
        log.warn("业务异常: {}", e.getMessage());
        
        ErrorResponse response = ErrorResponse.builder()
            .code(e.getErrorCode())
            .message(e.getMessage())
            .timestamp(LocalDateTime.now())
            .path(getCurrentRequestPath())
            .build();
            
        return ResponseEntity.badRequest().body(response);
    }
    
    /**
     * 参数验证异常处理
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException e) {
        List<String> errors = e.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(error -> error.getField() + ": " + error.getDefaultMessage())
            .collect(Collectors.toList());
            
        ErrorResponse response = ErrorResponse.builder()
            .code("VALIDATION_ERROR")
            .message("参数验证失败")
            .details(errors)
            .timestamp(LocalDateTime.now())
            .path(getCurrentRequestPath())
            .build();
            
        return ResponseEntity.badRequest().body(response);
    }
    
    /**
     * 系统异常处理
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleSystemException(Exception e) {
        log.error("系统异常", e);
        
        ErrorResponse response = ErrorResponse.builder()
            .code("SYSTEM_ERROR")
            .message("系统内部错误")
            .timestamp(LocalDateTime.now())
            .path(getCurrentRequestPath())
            .build();
            
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
    }
}

3.2 Vue/TypeScript 编码规范

3.2.1 组件规范

<!-- AchievementCard.vue -->
<template>
  <div class="achievement-card">
    <!-- 成果标题 -->
    <h3 class="achievement-card__title">
      {{ achievement.title }}
    </h3>
    
    <!-- 成果摘要 -->
    <p class="achievement-card__summary">
      {{ achievement.summary }}
    </p>
    
    <!-- 操作按钮 -->
    <div class="achievement-card__actions">
      <GButton 
        type="primary"
        size="small"
        @click="handleViewDetail"
      >
        查看详情
      </GButton>
      
      <GButton 
        type="default"
        size="small"
        @click="handleAddToFavorite"
      >
        收藏
      </GButton>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import type { Achievement } from '@/api/types'
import { GButton } from '@/components/common'

// Props 定义
interface Props {
  achievement: Achievement
  showActions?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  showActions: true
})

// Emits 定义
interface Emits {
  viewDetail: [id: string]
  addToFavorite: [id: string]
}

const emit = defineEmits<Emits>()

// 计算属性
const truncatedSummary = computed(() => {
  const maxLength = 100
  if (props.achievement.summary.length <= maxLength) {
    return props.achievement.summary
  }
  return props.achievement.summary.substring(0, maxLength) + '...'
})

// 事件处理
const handleViewDetail = () => {
  emit('viewDetail', props.achievement.id)
}

const handleAddToFavorite = () => {
  emit('addToFavorite', props.achievement.id)
}
</script>

<style scoped lang="scss">
.achievement-card {
  padding: 16px;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  background: #fff;
  transition: box-shadow 0.3s ease;
  
  &:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  }
  
  &__title {
    margin: 0 0 8px 0;
    font-size: 16px;
    font-weight: 600;
    color: #333;
    line-height: 1.4;
  }
  
  &__summary {
    margin: 0 0 16px 0;
    color: #666;
    line-height: 1.5;
  }
  
  &__actions {
    display: flex;
    gap: 8px;
    justify-content: flex-end;
  }
}
</style>

3.2.2 Composables 规范

// composables/useTable.ts
import { ref, reactive, computed, type Ref } from 'vue'
import type { TableColumn, TablePagination, TableFilter } from '@/types'

export interface UseTableOptions<T = any> {
  columns: TableColumn[]
  fetchData: (params: any) => Promise<{ data: T[]; total: number }>
  defaultFilter?: Record<string, any>
  defaultSort?: { field: string; order: 'asc' | 'desc' }
  pageSize?: number
}

export function useTable<T = any>(options: UseTableOptions<T>) {
  // 响应式状态
  const loading = ref(false)
  const data = ref<T[]>([])
  const total = ref(0)
  const error = ref<string | null>(null)
  
  // 分页状态
  const pagination = reactive<TablePagination>({
    current: 1,
    pageSize: options.pageSize || 20,
    total: 0
  })
  
  // 过滤状态
  const filters = reactive<TableFilter>(options.defaultFilter || {})
  
  // 排序状态
  const sorter = reactive({
    field: options.defaultSort?.field || '',
    order: options.defaultSort?.order || 'desc'
  })
  
  // 计算属性
  const hasData = computed(() => data.value.length > 0)
  const isEmpty = computed(() => !loading.value && data.value.length === 0)
  
  // 获取数据
  const fetchData = async (resetPage = false) => {
    if (resetPage) {
      pagination.current = 1
    }
    
    loading.value = true
    error.value = null
    
    try {
      const params = {
        page: pagination.current,
        size: pagination.pageSize,
        ...filters,
        sort: sorter.field ? `${sorter.field},${sorter.order}` : undefined
      }
      
      const result = await options.fetchData(params)
      
      data.value = result.data
      total.value = result.total
      pagination.total = result.total
      
    } catch (err) {
      error.value = err instanceof Error ? err.message : '数据加载失败'
      console.error('Table data fetch error:', err)
    } finally {
      loading.value = false
    }
  }
  
  // 分页处理
  const handlePageChange = (page: number) => {
    pagination.current = page
    fetchData()
  }
  
  const handlePageSizeChange = (size: number) => {
    pagination.pageSize = size
    pagination.current = 1
    fetchData()
  }
  
  // 过滤处理
  const handleFilter = (newFilters: Record<string, any>) => {
    Object.assign(filters, newFilters)
    fetchData(true)
  }
  
  const resetFilter = () => {
    Object.keys(filters).forEach(key => {
      delete filters[key]
    })
    fetchData(true)
  }
  
  // 排序处理
  const handleSort = (field: string, order: 'asc' | 'desc') => {
    sorter.field = field
    sorter.order = order
    fetchData(true)
  }
  
  // 刷新数据
  const refresh = () => {
    fetchData()
  }
  
  // 重置到初始状态
  const reset = () => {
    pagination.current = 1
    Object.keys(filters).forEach(key => {
      delete filters[key]
    })
    sorter.field = options.defaultSort?.field || ''
    sorter.order = options.defaultSort?.order || 'desc'
    fetchData()
  }
  
  return {
    // 状态
    loading: readonly(loading),
    data: readonly(data),
    total: readonly(total),
    error: readonly(error),
    pagination: readonly(pagination),
    filters: readonly(filters),
    sorter: readonly(sorter),
    
    // 计算属性
    hasData,
    isEmpty,
    
    // 方法
    fetchData,
    handlePageChange,
    handlePageSizeChange,
    handleFilter,
    resetFilter,
    handleSort,
    refresh,
    reset
  }
}

3.2.3 API 接口规范

// api/modules/achievement.ts
import request from '@/utils/request'
import type { 
  Achievement, 
  CreateAchievementRequest,
  UpdateAchievementRequest,
  AchievementListQuery,
  SearchAchievementQuery,
  PageResult,
  ApiResponse 
} from '@/api/types'

/**
 * 成果管理 API
 */
export class AchievementApi {
  
  /**
   * 获取成果列表
   */
  static async getList(query: AchievementListQuery): Promise<PageResult<Achievement>> {
    const response = await request.get<ApiResponse<PageResult<Achievement>>>('/api/v1/achievements', {
      params: query
    })
    return response.data
  }
  
  /**
   * 获取成果详情
   */
  static async getById(id: string): Promise<Achievement> {
    const response = await request.get<ApiResponse<Achievement>>(`/api/v1/achievements/${id}`)
    return response.data
  }
  
  /**
   * 创建成果
   */
  static async create(data: CreateAchievementRequest): Promise<Achievement> {
    const response = await request.post<ApiResponse<Achievement>>('/api/v1/achievements', data)
    return response.data
  }
  
  /**
   * 更新成果
   */
  static async update(id: string, data: UpdateAchievementRequest): Promise<Achievement> {
    const response = await request.put<ApiResponse<Achievement>>(`/api/v1/achievements/${id}`, data)
    return response.data
  }
  
  /**
   * 删除成果
   */
  static async delete(id: string): Promise<void> {
    await request.delete(`/api/v1/achievements/${id}`)
  }
  
  /**
   * 搜索成果
   */
  static async search(query: SearchAchievementQuery): Promise<PageResult<Achievement>> {
    const response = await request.post<ApiResponse<PageResult<Achievement>>>('/api/v1/achievements/search', query)
    return response.data
  }
  
  /**
   * 收藏成果
   */
  static async favorite(id: string): Promise<void> {
    await request.post(`/api/v1/achievements/${id}/favorite`)
  }
  
  /**
   * 取消收藏
   */
  static async unfavorite(id: string): Promise<void> {
    await request.delete(`/api/v1/achievements/${id}/favorite`)
  }
}

// 导出便捷方法
export const {
  getList: getAchievementList,
  getById: getAchievementById,
  create: createAchievement,
  update: updateAchievement,
  delete: deleteAchievement,
  search: searchAchievements,
  favorite: favoriteAchievement,
  unfavorite: unfavoriteAchievement
} = AchievementApi

3.3 Python 编码规范

3.3.1 项目结构规范

# app/services/match_service.py
"""
智能匹配服务

提供需求与成果的智能匹配功能,包括语义匹配、关键词匹配和混合匹配算法。
"""

from typing import List, Dict, Any, Optional
from dataclasses import dataclass
from loguru import logger

from app.models.matching.hybrid_matcher import HybridMatcher
from app.schemas.match import MatchRequest, MatchResult
from app.db.repositories.achievement_repo import AchievementRepository
from app.db.repositories.demand_repo import DemandRepository
from app.utils.cache_utils import cached
from app.core.config import settings


@dataclass
class MatchConfig:
    """匹配配置"""
    max_results: int = 20
    similarity_threshold: float = 0.3
    keyword_weight: float = 0.4
    semantic_weight: float = 0.6
    

class MatchService:
    """智能匹配服务"""
    
    def __init__(
        self,
        achievement_repo: AchievementRepository,
        demand_repo: DemandRepository
    ):
        self.achievement_repo = achievement_repo
        self.demand_repo = demand_repo
        self.matcher = HybridMatcher()
        self.config = MatchConfig()
        
    async def match_demand_to_achievements(
        self, 
        demand_id: str,
        config: Optional[MatchConfig] = None
    ) -> List[MatchResult]:
        """
        为需求匹配相关成果
        
        Args:
            demand_id: 需求ID
            config: 匹配配置
            
        Returns:
            匹配结果列表
            
        Raises:
            ValueError: 需求不存在
            RuntimeError: 匹配过程出错
        """
        # 使用传入的配置或默认配置
        match_config = config or self.config
        
        try:
            # 获取需求信息
            demand = await self.demand_repo.get_by_id(demand_id)
            if not demand:
                raise ValueError(f"需求不存在: {demand_id}")
                
            logger.info(f"开始为需求 {demand_id} 匹配成果")
            
            # 执行匹配
            matches = await self._execute_matching(demand, match_config)
            
            # 过滤和排序
            filtered_matches = self._filter_and_sort_matches(matches, match_config)
            
            logger.info(f"需求 {demand_id} 匹配完成,找到 {len(filtered_matches)} 个匹配项")
            
            return filtered_matches
            
        except Exception as e:
            logger.error(f"匹配过程出错: {e}")
            raise RuntimeError(f"匹配失败: {str(e)}") from e
    
    async def _execute_matching(
        self, 
        demand: Dict[str, Any], 
        config: MatchConfig
    ) -> List[MatchResult]:
        """执行匹配算法"""
        
        # 获取候选成果
        candidates = await self.achievement_repo.get_published_achievements(
            tenant_id=demand.get('tenant_id'),
            limit=config.max_results * 3  # 获取更多候选项
        )
        
        if not candidates:
            return []
        
        # 执行混合匹配
        matches = await self.matcher.match(
            demand=demand,
            candidates=candidates,
            weights={
                'keyword': config.keyword_weight,
                'semantic': config.semantic_weight
            }
        )
        
        return matches
    
    def _filter_and_sort_matches(
        self, 
        matches: List[MatchResult], 
        config: MatchConfig
    ) -> List[MatchResult]:
        """过滤和排序匹配结果"""
        
        # 过滤低分匹配
        filtered = [
            match for match in matches
            if match.score >= config.similarity_threshold
        ]
        
        # 按分数排序
        filtered.sort(key=lambda x: x.score, reverse=True)
        
        # 限制结果数量
        return filtered[:config.max_results]
    
    @cached(ttl=300)  # 缓存5分钟
    async def get_hot_matches(self, limit: int = 10) -> List[Dict[str, Any]]:
        """获取热门匹配"""
        
        hot_demands = await self.demand_repo.get_hot_demands(limit=limit)
        hot_matches = []
        
        for demand in hot_demands:
            matches = await self.match_demand_to_achievements(
                demand_id=demand['id'],
                config=MatchConfig(max_results=3)  # 每个需求只返回3个匹配
            )
            
            if matches:
                hot_matches.append({
                    'demand': demand,
                    'matches': matches
                })
        
        return hot_matches


# 依赖注入工厂
def create_match_service() -> MatchService:
    """创建匹配服务实例"""
    achievement_repo = AchievementRepository()
    demand_repo = DemandRepository()
    return MatchService(achievement_repo, demand_repo)

3.3.2 API 接口规范

# app/api/v1/match.py
"""
匹配相关API接口
"""

from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks
from fastapi.security import HTTPBearer
from loguru import logger

from app.schemas.match import (
    MatchRequest, 
    MatchResponse, 
    BatchMatchRequest,
    MatchHistoryResponse
)
from app.services.match_service import MatchService, create_match_service
from app.core.deps import get_current_user, get_tenant_id
from app.core.response import ApiResponse, success_response, error_response
from app.utils.rate_limit import rate_limit

router = APIRouter(prefix="/match", tags=["智能匹配"])
security = HTTPBearer()


@router.post("/demand/{demand_id}", response_model=ApiResponse[List[MatchResponse]])
@rate_limit(max_calls=10, time_window=60)  # 限制每分钟10次调用
async def match_demand(
    demand_id: str,
    background_tasks: BackgroundTasks,
    current_user: dict = Depends(get_current_user),
    tenant_id: str = Depends(get_tenant_id),
    match_service: MatchService = Depends(create_match_service)
) -> ApiResponse[List[MatchResponse]]:
    """
    为指定需求匹配相关成果
    
    Args:
        demand_id: 需求ID
        background_tasks: 后台任务
        current_user: 当前用户
        tenant_id: 租户ID
        match_service: 匹配服务
        
    Returns:
        匹配结果列表
        
    Raises:
        HTTPException: 需求不存在或匹配失败
    """
    try:
        logger.info(f"用户 {current_user['id']} 请求匹配需求 {demand_id}")
        
        # 执行匹配
        matches = await match_service.match_demand_to_achievements(demand_id)
        
        # 转换为响应格式
        response_data = [
            MatchResponse(
                achievement_id=match.achievement_id,
                score=match.score,
                reasons=match.reasons,
                similarity=match.similarity
            )
            for match in matches
        ]
        
        # 异步记录匹配历史
        background_tasks.add_task(
            _record_match_history,
            demand_id=demand_id,
            user_id=current_user['id'],
            matches=matches
        )
        
        return success_response(
            data=response_data,
            message=f"匹配成功,找到 {len(response_data)} 个相关成果"
        )
        
    except ValueError as e:
        logger.warning(f"匹配请求参数错误: {e}")
        raise HTTPException(status_code=400, detail=str(e))
        
    except Exception as e:
        logger.error(f"匹配请求处理失败: {e}")
        raise HTTPException(status_code=500, detail="匹配服务暂时不可用")


@router.post("/batch", response_model=ApiResponse[Dict[str, List[MatchResponse]]])
async def batch_match(
    request: BatchMatchRequest,
    current_user: dict = Depends(get_current_user),
    match_service: MatchService = Depends(create_match_service)
) -> ApiResponse[Dict[str, List[MatchResponse]]]:
    """
    批量匹配多个需求
    
    Args:
        request: 批量匹配请求
        current_user: 当前用户
        match_service: 匹配服务
        
    Returns:
        批量匹配结果
    """
    if len(request.demand_ids) > 50:
        raise HTTPException(status_code=400, detail="批量匹配需求数量不能超过50个")
    
    try:
        results = {}
        
        for demand_id in request.demand_ids:
            matches = await match_service.match_demand_to_achievements(demand_id)
            results[demand_id] = [
                MatchResponse(
                    achievement_id=match.achievement_id,
                    score=match.score,
                    reasons=match.reasons,
                    similarity=match.similarity
                )
                for match in matches
            ]
        
        return success_response(
            data=results,
            message=f"批量匹配完成,处理了 {len(request.demand_ids)} 个需求"
        )
        
    except Exception as e:
        logger.error(f"批量匹配失败: {e}")
        raise HTTPException(status_code=500, detail="批量匹配失败")


@router.get("/hot", response_model=ApiResponse[List[Dict[str, Any]]])
async def get_hot_matches(
    limit: int = 10,
    match_service: MatchService = Depends(create_match_service)
) -> ApiResponse[List[Dict[str, Any]]]:
    """
    获取热门匹配
    
    Args:
        limit: 限制数量
        match_service: 匹配服务
        
    Returns:
        热门匹配列表
    """
    try:
        hot_matches = await match_service.get_hot_matches(limit=limit)
        
        return success_response(
            data=hot_matches,
            message="获取热门匹配成功"
        )
        
    except Exception as e:
        logger.error(f"获取热门匹配失败: {e}")
        raise HTTPException(status_code=500, detail="获取热门匹配失败")


async def _record_match_history(
    demand_id: str, 
    user_id: str, 
    matches: List[MatchResult]
) -> None:
    """记录匹配历史(后台任务)"""
    try:
        # 这里实现匹配历史记录逻辑
        logger.info(f"记录匹配历史: 需求={demand_id}, 用户={user_id}, 匹配数={len(matches)}")
        
    except Exception as e:
        logger.error(f"记录匹配历史失败: {e}")

4. 开发流程与规范

4.1 Git 工作流

4.1.1 分支策略

# 主分支
main                    # 生产环境分支,只允许合并,不允许直接提交
develop                 # 开发环境分支,集成分支

# 功能分支
feature/user-management        # 用户管理功能
feature/achievement-search     # 成果搜索功能
feature/ai-matching           # AI匹配功能

# 发布分支
release/v1.0.0         # 发布分支,用于发布准备

# 修复分支
hotfix/fix-login-bug   # 紧急修复分支

# 个人开发分支
dev/zhangsan/user-api  # 个人开发分支

4.1.2 提交规范

# 提交消息格式
<type>(<scope>): <subject>

<body>

<footer>

# 类型(type)
feat:     新功能
fix:      修复bug
docs:     文档变更
style:    代码格式变更(不影响功能)
refactor: 重构(既不是新功能也不是修复bug)
perf:     性能优化
test:     测试相关
chore:    构建过程或辅助工具变更

# 示例
feat(user): 添加用户注册功能

- 实现用户注册接口
- 添加邮箱验证功能
- 集成短信验证服务

Closes #123

fix(achievement): 修复成果搜索分页问题

成果搜索时分页参数计算错误,导致结果重复

Fixes #456

4.1.3 代码审查规范

# Pull Request 模板
## 变更说明
简要描述本次变更的内容和目的

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

## 测试
- [ ] 单元测试已通过
- [ ] 集成测试已通过
- [ ] 手工测试已完成

## 检查清单
- [ ] 代码遵循项目编码规范
- [ ] 已添加必要的注释
- [ ] 已更新相关文档
- [ ] 无安全漏洞
- [ ] 性能影响可接受

## 相关Issue
Closes #issue_number

## 截图(如适用)

4.2 开发工作流

4.2.1 功能开发流程

# 1. 创建功能分支
git checkout develop
git pull origin develop
git checkout -b feature/achievement-search

# 2. 开发功能
# ... 编码实现 ...

# 3. 提交代码
git add .
git commit -m "feat(achievement): 实现成果搜索功能"

# 4. 推送分支
git push origin feature/achievement-search

# 5. 创建 Pull Request
# 在 GitLab/GitHub 上创建 MR/PR

# 6. 代码审查
# 团队成员进行代码审查

# 7. 合并代码
# 审查通过后合并到 develop 分支

# 8. 删除功能分支
git branch -d feature/achievement-search
git push origin --delete feature/achievement-search

4.2.2 发布流程

# 1. 创建发布分支
git checkout develop
git pull origin develop
git checkout -b release/v1.0.0

# 2. 准备发布
# - 更新版本号
# - 更新 CHANGELOG
# - 最后的测试和修复

# 3. 合并到主分支
git checkout main
git merge release/v1.0.0
git tag v1.0.0
git push origin main --tags

# 4. 合并回开发分支
git checkout develop
git merge release/v1.0.0
git push origin develop

# 5. 删除发布分支
git branch -d release/v1.0.0
git push origin --delete release/v1.0.0

5. 质量保证

5.1 代码质量工具

5.1.1 Java 质量工具配置

<!-- pom.xml - 代码质量插件 -->
<build>
  <plugins>
    <!-- SonarQube 代码质量分析 -->
    <plugin>
      <groupId>org.sonarsource.scanner.maven</groupId>
      <artifactId>sonar-maven-plugin</artifactId>
      <version>3.9.1.2184</version>
    </plugin>
    
    <!-- SpotBugs 静态代码分析 -->
    <plugin>
      <groupId>com.github.spotbugs</groupId>
      <artifactId>spotbugs-maven-plugin</artifactId>
      <version>4.7.3.0</version>
      <configuration>
        <effort>Max</effort>
        <threshold>Low</threshold>
        <xmlOutput>true</xmlOutput>
      </configuration>
    </plugin>
    
    <!-- Checkstyle 代码风格检查 -->
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-checkstyle-plugin</artifactId>
      <version>3.3.0</version>
      <configuration>
        <configLocation>checkstyle.xml</configLocation>
        <includeTestSourceDirectory>true</includeTestSourceDirectory>
        <failOnViolation>true</failOnViolation>
      </configuration>
    </plugin>
    
    <!-- JaCoCo 代码覆盖率 -->
    <plugin>
      <groupId>org.jacoco</groupId>
      <artifactId>jacoco-maven-plugin</artifactId>
      <version>0.8.10</version>
      <executions>
        <execution>
          <goals>
            <goal>prepare-agent</goal>
          </goals>
        </execution>
        <execution>
          <id>report</id>
          <phase>test</phase>
          <goals>
            <goal>report</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

5.1.2 前端质量工具配置

// package.json - 代码质量工具
{
  "scripts": {
    "lint": "eslint src --ext .vue,.js,.ts --fix",
    "lint:style": "stylelint src/**/*.{vue,scss,css} --fix",
    "type-check": "vue-tsc --noEmit",
    "test": "vitest",
    "test:coverage": "vitest --coverage",
    "build": "vue-tsc --noEmit && vite build"
  },
  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "^6.0.0",
    "@typescript-eslint/parser": "^6.0.0",
    "@vue/eslint-config-typescript": "^12.0.0",
    "eslint": "^8.45.0",
    "eslint-plugin-vue": "^9.15.0",
    "stylelint": "^15.10.0",
    "stylelint-config-standard-scss": "^10.0.0",
    "vitest": "^0.34.0",
    "@vitest/coverage-v8": "^0.34.0"
  }
}
// .eslintrc.js
module.exports = {
  root: true,
  env: {
    node: true,
    browser: true,
    es2022: true
  },
  extends: [
    'eslint:recommended',
    '@vue/typescript/recommended',
    'plugin:vue/vue3-recommended'
  ],
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module'
  },
  rules: {
    // Vue 规则
    'vue/multi-word-component-names': 'off',
    'vue/component-definition-name-casing': ['error', 'PascalCase'],
    'vue/component-name-in-template-casing': ['error', 'PascalCase'],
    'vue/define-emits-declaration': ['error', 'type-based'],
    'vue/define-props-declaration': ['error', 'type-based'],
    
    // TypeScript 规则
    '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
    '@typescript-eslint/explicit-function-return-type': 'off',
    '@typescript-eslint/no-explicit-any': 'warn',
    
    // 通用规则
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'prefer-const': 'error',
    'no-var': 'error'
  }
}

5.2 自动化检查

5.2.1 pre-commit 钩子

#!/bin/sh
# .git/hooks/pre-commit

echo "运行 pre-commit 检查..."

# Java 项目检查
if [ -f "pom.xml" ]; then
  echo "检查 Java 代码..."
  
  # 代码格式检查
  mvn checkstyle:check
  if [ $? -ne 0 ]; then
    echo "Checkstyle 检查失败,请修复代码风格问题"
    exit 1
  fi
  
  # 静态代码分析
  mvn spotbugs:check
  if [ $? -ne 0 ]; then
    echo "SpotBugs 检查失败,请修复代码问题"
    exit 1
  fi
  
  # 运行测试
  mvn test
  if [ $? -ne 0 ]; then
    echo "测试失败,请修复测试问题"
    exit 1
  fi
fi

# 前端项目检查
if [ -f "package.json" ]; then
  echo "检查前端代码..."
  
  # ESLint 检查
  npm run lint
  if [ $? -ne 0 ]; then
    echo "ESLint 检查失败,请修复代码风格问题"
    exit 1
  fi
  
  # 类型检查
  npm run type-check
  if [ $? -ne 0 ]; then
    echo "TypeScript 类型检查失败"
    exit 1
  fi
  
  # 运行测试
  npm run test
  if [ $? -ne 0 ]; then
    echo "测试失败,请修复测试问题"
    exit 1
  fi
fi

echo "所有检查通过 ✅"
exit 0

5.2.2 CI/CD 流水线

# .gitlab-ci.yml
stages:
  - code-quality
  - test
  - build
  - deploy

variables:
  MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"
  MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version"

cache:
  paths:
    - .m2/repository/
    - node_modules/

# 代码质量检查阶段
code-quality:
  stage: code-quality
  image: openjdk:17-jdk-slim
  script:
    - ./mvnw $MAVEN_CLI_OPTS checkstyle:check
    - ./mvnw $MAVEN_CLI_OPTS spotbugs:check
    - ./mvnw $MAVEN_CLI_OPTS org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
  artifacts:
    reports:
      junit:
        - target/surefire-reports/TEST-*.xml
        - target/failsafe-reports/TEST-*.xml
    paths:
      - target/site/
  only:
    - merge_requests
    - develop
    - main

# 后端测试
backend-test:
  stage: test
  image: openjdk:17-jdk-slim
  services:
    - postgres:15-alpine
    - redis:7-alpine
  variables:
    POSTGRES_DB: gmkx_test
    POSTGRES_USER: gmkx
    POSTGRES_PASSWORD: gmkx123
    SPRING_PROFILES_ACTIVE: test
  script:
    - ./mvnw $MAVEN_CLI_OPTS test
    - ./mvnw $MAVEN_CLI_OPTS jacoco:report
  coverage: '/Total.*?([0-9]{1,3})%/'
  artifacts:
    reports:
      junit: target/surefire-reports/TEST-*.xml
      coverage_report:
        coverage_format: jacoco
        path: target/site/jacoco/jacoco.xml

# 前端测试
frontend-test:
  stage: test
  image: node:18-alpine
  script:
    - cd gmkx-web
    - npm ci
    - npm run lint
    - npm run type-check
    - npm run test:coverage
  coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: gmkx-web/coverage/cobertura-coverage.xml

# 构建阶段
build:
  stage: build
  image: docker:24-dind
  services:
    - docker:24-dind
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  only:
    - develop
    - main

6. 开发最佳实践

6.1 性能优化指南

6.1.1 后端性能优化

// 数据库查询优化
@Repository
public class UserRepositoryImpl implements UserRepository {
    
    // 使用分页查询避免大结果集
    @Override
    public Page<User> findUsers(UserQuery query, Pageable pageable) {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        
        // 只查询必要字段
        wrapper.select("id", "username", "email", "status", "created_time");
        
        // 添加索引字段查询条件
        if (StringUtils.hasText(query.getStatus())) {
            wrapper.eq("status", query.getStatus());
        }
        
        if (StringUtils.hasText(query.getTenantId())) {
            wrapper.eq("tenant_id", query.getTenantId());
        }
        
        return userMapper.selectPage(new Page<>(pageable.getPageNumber(), pageable.getPageSize()), wrapper);
    }
    
    // 批量操作优化
    @Override
    @Transactional
    public void batchUpdateUserStatus(List<String> userIds, UserStatus status) {
        // 使用批量更新而不是循环单个更新
        if (userIds.size() <= 1000) {
            UpdateWrapper<User> wrapper = new UpdateWrapper<>();
            wrapper.in("id", userIds);
            wrapper.set("status", status);
            wrapper.set("updated_time", LocalDateTime.now());
            userMapper.update(null, wrapper);
        } else {
            // 大批量数据分批处理
            List<List<String>> batches = Lists.partition(userIds, 1000);
            for (List<String> batch : batches) {
                UpdateWrapper<User> wrapper = new UpdateWrapper<>();
                wrapper.in("id", batch);
                wrapper.set("status", status);
                wrapper.set("updated_time", LocalDateTime.now());
                userMapper.update(null, wrapper);
            }
        }
    }
}

// 缓存优化
@Service
public class AchievementServiceImpl implements AchievementService {
    
    // 多级缓存
    @Cacheable(value = "achievement", key = "#id", condition = "#id != null")
    @Override
    public Achievement getById(String id) {
        return achievementRepository.findById(id).orElse(null);
    }
    
    // 缓存预热
    @EventListener
    @Async
    public void warmUpCache(ApplicationReadyEvent event) {
        List<String> hotAchievementIds = getHotAchievementIds();
        hotAchievementIds.parallelStream().forEach(this::getById);
    }
    
    // 缓存失效
    @CacheEvict(value = "achievement", key = "#result.id")
    @Override
    public Achievement update(String id, UpdateAchievementRequest request) {
        Achievement achievement = getById(id);
        if (achievement == null) {
            throw new AchievementNotFoundException(id);
        }
        
        BeanUtils.copyProperties(request, achievement);
        achievement.setUpdatedTime(LocalDateTime.now());
        
        return achievementRepository.save(achievement);
    }
}

6.1.2 前端性能优化

// 组件懒加载
const AchievementDetail = defineAsyncComponent(() => import('@/views/achievement/AchievementDetail.vue'))
const DemandList = defineAsyncComponent(() => import('@/views/demand/DemandList.vue'))

// 路由懒加载
const routes = [
  {
    path: '/achievement',
    component: () => import('@/views/achievement/AchievementLayout.vue'),
    children: [
      {
        path: 'list',
        component: () => import('@/views/achievement/AchievementList.vue')
      },
      {
        path: 'detail/:id',
        component: () => import('@/views/achievement/AchievementDetail.vue')
      }
    ]
  }
]

// 虚拟滚动优化大列表
<template>
  <VirtualList
    :items="achievements"
    :item-height="120"
    :container-height="600"
    v-slot="{ item, index }"
  >
    <AchievementCard :achievement="item" :key="item.id" />
  </VirtualList>
</template>

// 图片懒加载和优化
<template>
  <img
    v-lazy="achievement.thumbnail"
    :alt="achievement.title"
    loading="lazy"
    :style="{ aspectRatio: '16/9' }"
  />
</template>

// API 请求优化
import { debounce } from 'lodash-es'

export const useSearchAchievements = () => {
  const query = ref('')
  const results = ref<Achievement[]>([])
  const loading = ref(false)
  
  // 防抖搜索
  const debouncedSearch = debounce(async (searchQuery: string) => {
    if (!searchQuery.trim()) {
      results.value = []
      return
    }
    
    loading.value = true
    try {
      const response = await searchAchievements({ query: searchQuery })
      results.value = response.data
    } finally {
      loading.value = false
    }
  }, 300)
  
  watch(query, debouncedSearch)
  
  return {
    query,
    results: readonly(results),
    loading: readonly(loading)
  }
}

6.2 安全开发指南

6.2.1 输入验证

// 参数验证
@PostMapping("/users")
public ResponseEntity<UserResponse> createUser(@Valid @RequestBody CreateUserRequest request) {
    // Spring Validation 自动验证
    UserResponse response = userService.createUser(request);
    return ResponseEntity.ok(response);
}

// 自定义验证器
@Component
public class UserValidator {
    
    public void validateCreateRequest(CreateUserRequest request) {
        // 用户名格式验证
        if (!isValidUsername(request.getUsername())) {
            throw new ValidationException("用户名格式不正确");
        }
        
        // 邮箱格式验证
        if (!isValidEmail(request.getEmail())) {
            throw new ValidationException("邮箱格式不正确");
        }
        
        // 密码强度验证
        if (!isStrongPassword(request.getPassword())) {
            throw new ValidationException("密码强度不足");
        }
    }
    
    private boolean isValidUsername(String username) {
        return username != null && 
               username.matches("^[a-zA-Z0-9_]{3,20}$");
    }
    
    private boolean isValidEmail(String email) {
        return email != null && 
               email.matches("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");
    }
    
    private boolean isStrongPassword(String password) {
        return password != null &&
               password.length() >= 8 &&
               password.matches("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]");
    }
}

6.2.2 SQL 注入防护

// 使用 MyBatis-Plus 参数化查询
@Repository
public class AchievementRepositoryImpl implements AchievementRepository {
    
    // 正确方式:使用参数化查询
    @Override
    public List<Achievement> searchByKeyword(String keyword, String techField) {
        QueryWrapper<Achievement> wrapper = new QueryWrapper<>();
        
        if (StringUtils.hasText(keyword)) {
            // 使用 like 查询,MyBatis-Plus 自动处理参数化
            wrapper.like("title", keyword)
                   .or()
                   .like("summary", keyword);
        }
        
        if (StringUtils.hasText(techField)) {
            wrapper.eq("tech_field", techField);
        }
        
        return achievementMapper.selectList(wrapper);
    }
    
    // 错误方式:字符串拼接(永远不要这样做)
    // public List<Achievement> searchByKeywordUnsafe(String keyword) {
    //     String sql = "SELECT * FROM t_achievement WHERE title LIKE '%" + keyword + "%'";
    //     return jdbcTemplate.query(sql, achievementRowMapper);
    // }
}

6.2.3 XSS 防护

// 前端 XSS 防护
import DOMPurify from 'dompurify'

// 清理用户输入
export const sanitizeInput = (input: string): string => {
  return DOMPurify.sanitize(input, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
    ALLOWED_ATTR: ['href']
  })
}

// 安全的 HTML 渲染
<template>
  <div v-html="sanitizedContent"></div>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { sanitizeInput } from '@/utils/security'

interface Props {
  content: string
}

const props = defineProps<Props>()

const sanitizedContent = computed(() => {
  return sanitizeInput(props.content)
})
</script>

6.3 监控与日志

6.3.1 结构化日志

// 使用结构化日志
@Slf4j
@RestController
public class AchievementController {
    
    @PostMapping("/achievements")
    public ResponseEntity<AchievementResponse> createAchievement(
            @RequestBody CreateAchievementRequest request,
            HttpServletRequest httpRequest) {
        
        String requestId = UUID.randomUUID().toString();
        String userId = getCurrentUserId();
        String tenantId = getCurrentTenantId();
        
        // 结构化日志
        log.info("创建成果请求开始: requestId={}, userId={}, tenantId={}, title={}", 
                requestId, userId, tenantId, request.getTitle());
        
        try {
            AchievementResponse response = achievementService.createAchievement(request);
            
            log.info("创建成果请求成功: requestId={}, achievementId={}, duration={}ms", 
                    requestId, response.getId(), getDuration(startTime));
            
            return ResponseEntity.ok(response);
            
        } catch (Exception e) {
            log.error("创建成果请求失败: requestId={}, error={}", 
                    requestId, e.getMessage(), e);
            throw e;
        }
    }
}

// 自定义日志注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BusinessLog {
    String operation();
    String description() default "";
}

@Aspect
@Component
@Slf4j
public class BusinessLogAspect {
    
    @Around("@annotation(businessLog)")
    public Object logBusinessOperation(ProceedingJoinPoint joinPoint, BusinessLog businessLog) throws Throwable {
        String operationId = UUID.randomUUID().toString();
        long startTime = System.currentTimeMillis();
        
        try {
            log.info("业务操作开始: operationId={}, operation={}, description={}", 
                    operationId, businessLog.operation(), businessLog.description());
            
            Object result = joinPoint.proceed();
            
            long duration = System.currentTimeMillis() - startTime;
            log.info("业务操作成功: operationId={}, duration={}ms", operationId, duration);
            
            return result;
            
        } catch (Exception e) {
            long duration = System.currentTimeMillis() - startTime;
            log.error("业务操作失败: operationId={}, duration={}ms, error={}", 
                    operationId, duration, e.getMessage(), e);
            throw e;
        }
    }
}

这个开发指南提供了完整的开发环境搭建、项目结构、编码规范、开发流程和最佳实践,为团队提供了详细的实施指导。