Developer Guide

AI 辅助开发

使用 AI 和 MCP 提升 Tagtag Starter 开发效率

Tagtag Starter 框架全面支持 AI 辅助开发,通过集成 Model Context Protocol (MCP) 和提供 AI 友好的文档,让您能够充分利用 AI 工具来加速开发流程。

什么是 MCP?

Model Context Protocol (MCP) 是一个开放标准,用于将 AI 助手(如 Claude、ChatGPT 等)连接到外部数据源和工具。通过 MCP,AI 可以直接访问您的项目文档、代码库和其他资源,从而提供更准确、更相关的帮助。

Tagtag Starter 的 AI 支持特性

1. AI 友好的文档

Tagtag Starter 官方文档已经集成了 nuxt-llms 模块,自动生成 AI 可读的文档文件:

  • llms.txt: 简洁的结构化文档,适合 AI 快速理解项目
  • llms-full.txt: 完整的详细文档,提供深度的项目信息

访问地址:

这些文档遵循 llms.txt 标准,可以被各种 AI 工具和 MCP 服务器直接读取和解析。

2. MCP 工具集成

Tagtag Starter 文档站点已经配置了 @nuxtjs/mcp-toolkit,提供了标准的 MCP 工具接口,允许 AI 助手直接访问和查询文档内容。

如何在 Trae 中使用 AI 辅助开发

步骤 1: 配置 MCP 服务器

在 Trae 中,您需要配置 MCP 服务器来连接到 Tagtag Starter 的文档资源。

MCP 配置示例

创建或编辑您的 MCP 配置文件(通常在用户配置目录):

{
  "mcpServers": {
    "tagtag-docs": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "http://tagtag.dev/llms.txt"
      ],
      "env": {}
    }
  }
}

步骤 2: 在 Trae 中启用 MCP

  1. 打开 Trae 的设置
  2. 导航到 "AI" 或 "MCP" 配置部分
  3. 添加 Tagtag Starter 文档的 MCP 服务器配置
  4. 保存并重启 Trae

步骤 3: 使用 AI 助手

配置完成后,您就可以在 Trae 中使用 AI 助手了:

// 示例:询问 AI 如何创建新的 CRUD 模块
"请帮我创建一个用户管理模块,包含增删改查功能"

AI 会自动访问 Tagtag Starter 文档,理解框架结构,并为您提供准确的代码示例和指导。

实际应用场景

1. 快速生成 CRUD 代码

// 向 AI 提问
"基于 Tagtag Starter 框架,帮我生成一个产品管理的 CRUD 模块,包括:
- 实体类 Product
- Mapper 接口
- Service 层
- Controller 层
- 前端页面"

AI 会参考 Tagtag Starter 的代码规范和最佳实践,生成符合框架标准的代码。

详细示例:生成产品管理模块

步骤 1:生成实体类

// 向 AI 提问
"生成 Product 实体类,包含以下字段:
- id (Long, 主键)
- name (String, 产品名称,非空)
- code (String, 产品编码,唯一)
- price (BigDecimal, 价格,非空)
- stock (Integer, 库存数量)
- status (Integer, 状态:0-禁用,1-启用)
- description (String, 产品描述)
- createTime (LocalDateTime, 创建时间)
- updateTime (LocalDateTime, 更新时间)
- createBy (String, 创建人)
- updateBy (String, 更新人)
- deleted (Integer, 逻辑删除标记)

参考 Tagtag Starter 的 User 实体类实现"

AI 生成的代码:

package dev.tagtag.module.product.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;

@Data
@TableName("sys_product")
public class Product {
    
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String name;
    
    private String code;
    
    private BigDecimal price;
    
    private Integer stock;
    
    private Integer status;
    
    private String description;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    
    @TableField(fill = FieldFill.INSERT)
    private String createBy;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String updateBy;
    
    @TableLogic
    private Integer deleted;
}

步骤 2:生成 DTO 类

// 向 AI 提问
"生成 ProductDTO 和 ProductQuery 类,用于数据传输和查询
参考 Tagtag Starter 的 UserDTO 和 UserQuery 实现"

AI 生成的代码:

package dev.tagtag.module.product.dto;

import lombok.Data;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;

@Data
public class ProductDTO {
    
    private Long id;
    
    @NotBlank(message = "产品名称不能为空")
    private String name;
    
    @NotBlank(message = "产品编码不能为空")
    private String code;
    
    @NotNull(message = "价格不能为空")
    @DecimalMin(value = "0.01", message = "价格必须大于 0")
    private BigDecimal price;
    
    @NotNull(message = "库存数量不能为空")
    @Min(value = 0, message = "库存数量不能为负数")
    private Integer stock;
    
    @NotNull(message = "状态不能为空")
    private Integer status;
    
    private String description;
}
package dev.tagtag.module.product.dto;

import lombok.Data;

@Data
public class ProductQuery {
    
    private String name;
    
    private String code;
    
    private Integer status;
    
    private Integer pageNum = 1;
    
    private Integer pageSize = 10;
}

步骤 3:生成 Mapper 接口

// 向 AI 提问
"生成 ProductMapper 接口,继承 BaseMapper
参考 Tagtag Starter 的 UserMapper 实现"

AI 生成的代码:

package dev.tagtag.module.product.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import dev.tagtag.module.product.entity.Product;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface ProductMapper extends BaseMapper<Product> {
}

步骤 4:生成 Service 层

// 向 AI 提问
"生成 ProductService 接口和 ProductServiceImpl 实现类
包含以下方法:
- pageProducts(ProductQuery query): 分页查询产品
- getProductById(Long id): 根据 ID 查询产品
- createProduct(ProductDTO productDTO): 创建产品
- updateProduct(ProductDTO productDTO): 更新产品
- deleteProduct(Long id): 删除产品
- batchDeleteProducts(List<Long> ids): 批量删除产品
- updateProductStatus(Long id, Integer status): 更新产品状态

参考 Tagtag Starter 的 UserService 实现"

AI 生成的代码:

package dev.tagtag.module.product.service;

import dev.tagtag.module.product.dto.ProductDTO;
import dev.tagtag.module.product.dto.ProductQuery;
import dev.tagtag.common.core.page.PageResult;

public interface ProductService {
    
    PageResult<ProductDTO> pageProducts(ProductQuery query);
    
    ProductDTO getProductById(Long id);
    
    void createProduct(ProductDTO productDTO);
    
    void updateProduct(ProductDTO productDTO);
    
    void deleteProduct(Long id);
    
    void batchDeleteProducts(java.util.List<Long> ids);
    
    void updateProductStatus(Long id, Integer status);
}
package dev.tagtag.module.product.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import dev.tagtag.module.product.dto.ProductDTO;
import dev.tagtag.module.product.dto.ProductQuery;
import dev.tagtag.module.product.entity.Product;
import dev.tagtag.module.product.mapper.ProductMapper;
import dev.tagtag.module.product.service.ProductService;
import dev.tagtag.common.core.page.PageResult;
import dev.tagtag.common.core.exception.BusinessException;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class ProductServiceImpl implements ProductService {
    
    private final ProductMapper productMapper;
    
    @Override
    public PageResult<ProductDTO> pageProducts(ProductQuery query) {
        LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(query.getName() != null, Product::getName, query.getName())
               .eq(query.getCode() != null, Product::getCode, query.getCode())
               .eq(query.getStatus() != null, Product::getStatus, query.getStatus())
               .orderByDesc(Product::getCreateTime);
        
        Page<Product> page = productMapper.selectPage(
            new Page<>(query.getPageNum(), query.getPageSize()), 
            wrapper
        );
        
        return PageResult.of(page, this::convertToDTO);
    }
    
    @Override
    public ProductDTO getProductById(Long id) {
        Product product = productMapper.selectById(id);
        if (product == null) {
            throw new BusinessException("产品不存在");
        }
        return convertToDTO(product);
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createProduct(ProductDTO productDTO) {
        Product product = convertToEntity(productDTO);
        productMapper.insert(product);
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateProduct(ProductDTO productDTO) {
        Product product = productMapper.selectById(productDTO.getId());
        if (product == null) {
            throw new BusinessException("产品不存在");
        }
        BeanUtils.copyProperties(productDTO, product);
        productMapper.updateById(product);
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteProduct(Long id) {
        productMapper.deleteById(id);
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchDeleteProducts(java.util.List<Long> ids) {
        ids.forEach(this::deleteProduct);
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateProductStatus(Long id, Integer status) {
        Product product = productMapper.selectById(id);
        if (product == null) {
            throw new BusinessException("产品不存在");
        }
        product.setStatus(status);
        productMapper.updateById(product);
    }
    
    private ProductDTO convertToDTO(Product product) {
        ProductDTO dto = new ProductDTO();
        BeanUtils.copyProperties(product, dto);
        return dto;
    }
    
    private Product convertToEntity(ProductDTO dto) {
        Product product = new Product();
        BeanUtils.copyProperties(dto, product);
        return product;
    }
}

步骤 5:生成 Controller 层

// 向 AI 提问
"生成 ProductController,提供 RESTful API
参考 Tagtag Starter 的 UserController 实现"

AI 生成的代码:

package dev.tagtag.module.product.controller;

import dev.tagtag.common.core.page.PageResult;
import dev.tagtag.common.core.result.Result;
import dev.tagtag.module.product.dto.ProductDTO;
import dev.tagtag.module.product.dto.ProductQuery;
import dev.tagtag.module.product.service.ProductService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/products")
@RequiredArgsConstructor
public class ProductController {
    
    private final ProductService productService;
    
    @GetMapping
    public Result<PageResult<ProductDTO>> pageProducts(ProductQuery query) {
        PageResult<ProductDTO> result = productService.pageProducts(query);
        return Result.success(result);
    }
    
    @GetMapping("/{id}")
    public Result<ProductDTO> getProductById(@PathVariable Long id) {
        ProductDTO product = productService.getProductById(id);
        return Result.success(product);
    }
    
    @PostMapping
    public Result<Void> createProduct(@RequestBody ProductDTO productDTO) {
        productService.createProduct(productDTO);
        return Result.success();
    }
    
    @PutMapping
    public Result<Void> updateProduct(@RequestBody ProductDTO productDTO) {
        productService.updateProduct(productDTO);
        return Result.success();
    }
    
    @DeleteMapping("/{id}")
    public Result<Void> deleteProduct(@PathVariable Long id) {
        productService.deleteProduct(id);
        return Result.success();
    }
    
    @DeleteMapping("/batch")
    public Result<Void> batchDeleteProducts(@RequestBody java.util.List<Long> ids) {
        productService.batchDeleteProducts(ids);
        return Result.success();
    }
    
    @PutMapping("/{id}/status")
    public Result<Void> updateProductStatus(@PathVariable Long id, @RequestParam Integer status) {
        productService.updateProductStatus(id, status);
        return Result.success();
    }
}

步骤 6:生成前端页面

// 向 AI 提问
"生成产品管理的前端页面,包括:
- 产品列表页面(index.vue)
- 产品表单组件(FormModal.vue)

参考 Tagtag Starter 的用户管理页面实现
使用 Vue 3 Composition API、TypeScript、VXE Table"

AI 生成的代码:

<template>
  <div class="product-container">
    <Grid
      ref="gridRef"
      :columns="columns"
      :data="products"
      :toolbar-config="toolbarConfig"
      :checkbox-config="{ checkField: 'checked' }"
      @toolbar-click="handleToolbarClick"
    >
      <template #toolbar_buttons>
        <a-button type="primary" @click="handleAdd">
          <template #icon><PlusOutlined /></template>
          新增
        </a-button>
        <a-button 
          :disabled="!hasSelection"
          @click="handleBatchDelete"
        >
          <template #icon><DeleteOutlined /></template>
          批量删除
        </a-button>
      </template>
      
      <template #action="{ row }">
        <a-space>
          <a-button type="link" @click="handleEdit(row)">编辑</a-button>
          <a-button type="link" danger @click="handleDelete(row)">删除</a-button>
          <a-switch
            :checked="row.status === 1"
            @change="(checked) => handleStatusChange(row, checked)"
          />
        </a-space>
      </template>
    </Grid>
    
    <FormModal
      v-model:visible="modalVisible"
      :title="modalTitle"
      :form-data="formData"
      @submit="handleSubmit"
    />
  </div>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue';
import { useTable } from '@vben/plugins/vxe-table';
import { 
  pageProducts, 
  createProduct, 
  updateProduct, 
  deleteProduct,
  batchDeleteProducts,
  updateProductStatus 
} from '@/api/product';
import type { ProductDTO, ProductQuery } from '@/api/product';

const [Grid, gridApi] = useTable();

const columns = [
  { field: 'id', title: 'ID', width: 80 },
  { field: 'name', title: '产品名称', minWidth: 150 },
  { field: 'code', title: '产品编码', width: 120 },
  { field: 'price', title: '价格', width: 100, formatter: ({ cellValue }) => `¥${cellValue}` },
  { field: 'stock', title: '库存', width: 80 },
  { field: 'status', title: '状态', width: 80, formatter: ({ cellValue }) => cellValue === 1 ? '启用' : '禁用' },
  { field: 'createTime', title: '创建时间', width: 160 },
  { title: '操作', slots: { default: 'action' }, width: 200, fixed: 'right' }
];

const products = ref<ProductDTO[]>([]);
const modalVisible = ref(false);
const modalTitle = ref('');
const formData = ref<Partial<ProductDTO>>({});

const toolbarConfig = {
  buttons: [
    { code: 'add', name: '新增' }
  ]
};

const hasSelection = computed(() => {
  return gridApi.value?.getSelectedRows().length > 0;
});

const loadData = async () => {
  const query: ProductQuery = { pageNum: 1, pageSize: 10 };
  const result = await pageProducts(query);
  products.value = result.records;
};

const handleAdd = () => {
  modalTitle.value = '新增产品';
  formData.value = {};
  modalVisible.value = true;
};

const handleEdit = (row: ProductDTO) => {
  modalTitle.value = '编辑产品';
  formData.value = { ...row };
  modalVisible.value = true;
};

const handleDelete = (row: ProductDTO) => {
  Modal.confirm({
    title: '确认删除',
    content: `确定要删除产品"${row.name}"吗?`,
    onOk: async () => {
      await deleteProduct(row.id!);
      message.success('删除成功');
      loadData();
    }
  });
};

const handleBatchDelete = async () => {
  const selectedRows = gridApi.value?.getSelectedRows() || [];
  Modal.confirm({
    title: '确认删除',
    content: `确定要删除选中的 ${selectedRows.length} 个产品吗?`,
    onOk: async () => {
      const ids = selectedRows.map(row => row.id);
      await batchDeleteProducts(ids);
      message.success('批量删除成功');
      loadData();
    }
  });
};

const handleStatusChange = async (row: ProductDTO, checked: boolean) => {
  await updateProductStatus(row.id!, checked ? 1 : 0);
  message.success('状态更新成功');
  loadData();
};

const handleSubmit = async (data: ProductDTO) => {
  if (data.id) {
    await updateProduct(data);
    message.success('更新成功');
  } else {
    await createProduct(data);
    message.success('创建成功');
  }
  modalVisible.value = false;
  loadData();
};

loadData();
</script>
<template>
  <a-modal
    :visible="visible"
    :title="title"
    :confirm-loading="loading"
    @ok="handleSubmit"
    @cancel="handleCancel"
  >
    <a-form
      ref="formRef"
      :model="formState"
      :rules="rules"
      :label-col="{ span: 6 }"
      :wrapper-col="{ span: 16 }"
    >
      <a-form-item label="产品名称" name="name">
        <a-input v-model:value="formState.name" placeholder="请输入产品名称" />
      </a-form-item>
      
      <a-form-item label="产品编码" name="code">
        <a-input v-model:value="formState.code" placeholder="请输入产品编码" />
      </a-form-item>
      
      <a-form-item label="价格" name="price">
        <a-input-number 
          v-model:value="formState.price" 
          :min="0.01"
          :precision="2"
          style="width: 100%"
          placeholder="请输入价格"
        />
      </a-form-item>
      
      <a-form-item label="库存数量" name="stock">
        <a-input-number 
          v-model:value="formState.stock" 
          :min="0"
          style="width: 100%"
          placeholder="请输入库存数量"
        />
      </a-form-item>
      
      <a-form-item label="状态" name="status">
        <a-radio-group v-model:value="formState.status">
          <a-radio :value="1">启用</a-radio>
          <a-radio :value="0">禁用</a-radio>
        </a-radio-group>
      </a-form-item>
      
      <a-form-item label="产品描述" name="description">
        <a-textarea 
          v-model:value="formState.description" 
          :rows="4"
          placeholder="请输入产品描述"
        />
      </a-form-item>
    </a-form>
  </a-modal>
</template>

<script setup lang="ts">
import { ref, watch, reactive } from 'vue';
import type { FormInstance } from 'ant-design-vue';
import type { ProductDTO } from '@/api/product';

interface Props {
  visible: boolean;
  title: string;
  formData: Partial<ProductDTO>;
}

interface Emits {
  (e: 'update:visible', value: boolean): void;
  (e: 'submit', data: ProductDTO): void;
}

const props = defineProps<Props>();
const emit = defineEmits<Emits>();

const formRef = ref<FormInstance>();
const loading = ref(false);
const formState = reactive<Partial<ProductDTO>>({});

const rules = {
  name: [{ required: true, message: '请输入产品名称', trigger: 'blur' }],
  code: [{ required: true, message: '请输入产品编码', trigger: 'blur' }],
  price: [{ required: true, message: '请输入价格', trigger: 'blur' }],
  stock: [{ required: true, message: '请输入库存数量', trigger: 'blur' }],
  status: [{ required: true, message: '请选择状态', trigger: 'change' }]
};

watch(() => props.formData, (data) => {
  Object.assign(formState, data);
}, { immediate: true });

const handleSubmit = async () => {
  try {
    loading.value = true;
    await formRef.value?.validate();
    emit('submit', formState as ProductDTO);
  } catch (error) {
    console.error('表单验证失败:', error);
  } finally {
    loading.value = false;
  }
};

const handleCancel = () => {
  emit('update:visible', false);
  formRef.value?.resetFields();
};
</script>

2. 查询 API 文档

// 向 AI 提问
"Tagtag Starter 中的 RBAC 权限系统是如何实现的?"

AI 会从文档中提取相关信息,为您提供详细的解释和代码示例。

详细示例:查询 RBAC 权限系统

// 向 AI 提问
"请详细解释 Tagtag Starter 中的 RBAC 权限系统,包括:
1. 权限模型设计(用户、角色、权限的关系)
2. 数据库表结构
3. 权限验证的实现方式
4. 前端权限控制
5. 后端权限注解使用

请提供相关代码示例"

AI 提供的详细解答:

1. 权限模型设计

Tagtag Starter 采用基于 RBAC(Role-Based Access Control)的权限模型,包含以下核心概念:

  • 用户(User):系统的使用者
  • 角色(Role):权限的集合,用户通过角色获得权限
  • 权限(Permission):对资源的操作权限
  • 菜单(Menu):前端菜单和按钮的权限控制

2. 数据库表结构

-- 用户表
CREATE TABLE sys_user (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL UNIQUE,
    password VARCHAR(100) NOT NULL,
    real_name VARCHAR(50),
    email VARCHAR(100),
    phone VARCHAR(20),
    status TINYINT DEFAULT 1,
    create_time DATETIME,
    update_time DATETIME,
    create_by VARCHAR(50),
    update_by VARCHAR(50),
    deleted TINYINT DEFAULT 0
);

-- 角色表
CREATE TABLE sys_role (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_name VARCHAR(50) NOT NULL,
    role_code VARCHAR(50) NOT NULL UNIQUE,
    description VARCHAR(200),
    status TINYINT DEFAULT 1,
    create_time DATETIME,
    update_time DATETIME,
    create_by VARCHAR(50),
    update_by VARCHAR(50),
    deleted TINYINT DEFAULT 0
);

-- 权限表
CREATE TABLE sys_permission (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    permission_name VARCHAR(50) NOT NULL,
    permission_code VARCHAR(100) NOT NULL UNIQUE,
    resource_type VARCHAR(20),
    resource_url VARCHAR(200),
    description VARCHAR(200),
    create_time DATETIME,
    update_time DATETIME,
    create_by VARCHAR(50),
    update_by VARCHAR(50),
    deleted TINYINT DEFAULT 0
);

-- 菜单表
CREATE TABLE sys_menu (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    menu_name VARCHAR(50) NOT NULL,
    menu_code VARCHAR(50) NOT NULL UNIQUE,
    parent_id BIGINT DEFAULT 0,
    menu_type VARCHAR(20),
    icon VARCHAR(50),
    path VARCHAR(200),
    component VARCHAR(200),
    sort_order INT DEFAULT 0,
    status TINYINT DEFAULT 1,
    create_time DATETIME,
    update_time DATETIME,
    create_by VARCHAR(50),
    update_by VARCHAR(50),
    deleted TINYINT DEFAULT 0
);

-- 用户角色关联表
CREATE TABLE sys_user_role (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    role_id BIGINT NOT NULL,
    create_time DATETIME,
    UNIQUE KEY uk_user_role (user_id, role_id)
);

-- 角色权限关联表
CREATE TABLE sys_role_permission (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_id BIGINT NOT NULL,
    permission_id BIGINT NOT NULL,
    create_time DATETIME,
    UNIQUE KEY uk_role_permission (role_id, permission_id)
);

-- 角色菜单关联表
CREATE TABLE sys_role_menu (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_id BIGINT NOT NULL,
    menu_id BIGINT NOT NULL,
    create_time DATETIME,
    UNIQUE KEY uk_role_menu (role_id, menu_id)
);

3. 权限验证的实现方式

// 权限验证注解
package dev.tagtag.common.security.annotation;

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequiresPermissions {
    
    String[] value() default {};
    
    Logical logical() default Logical.AND;
    
    enum Logical {
        AND, OR
    }
}

// 权限验证切面
package dev.tagtag.common.security.aspect;

import dev.tagtag.common.security.annotation.RequiresPermissions;
import dev.tagtag.common.security.service.PermissionService;
import dev.tagtag.common.core.exception.ForbiddenException;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

@Aspect
@Component
@RequiredArgsConstructor
public class PermissionAspect {
    
    private final PermissionService permissionService;
    
    @Before("@annotation(dev.tagtag.common.security.annotation.RequiresPermissions)")
    public void checkPermission(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        RequiresPermissions requiresPermissions = signature.getMethod()
            .getAnnotation(RequiresPermissions.class);
        
        String[] permissions = requiresPermissions.value();
        RequiresPermissions.Logical logical = requiresPermissions.logical();
        
        boolean hasPermission = permissionService.hasPermissions(permissions, logical);
        
        if (!hasPermission) {
            throw new ForbiddenException("权限不足");
        }
    }
}

// 权限服务
package dev.tagtag.common.security.service;

import dev.tagtag.common.security.utils.SecurityUtils;
import dev.tagtag.module.iam.mapper.UserMapper;
import dev.tagtag.module.iam.mapper.RoleMapper;
import dev.tagtag.module.iam.mapper.PermissionMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class PermissionService {
    
    private final UserMapper userMapper;
    private final RoleMapper roleMapper;
    private final PermissionMapper permissionMapper;
    
    public boolean hasPermissions(String[] permissions, RequiresPermissions.Logical logical) {
        Long userId = SecurityUtils.getUserId();
        Set<String> userPermissions = getUserPermissions(userId);
        
        if (logical == RequiresPermissions.Logical.AND) {
            return userPermissions.containsAll(List.of(permissions));
        } else {
            return List.of(permissions).stream()
                .anyMatch(userPermissions::contains);
        }
    }
    
    private Set<String> getUserPermissions(Long userId) {
        List<Long> roleIds = userMapper.getRoleIdsByUserId(userId);
        return roleIds.stream()
            .flatMap(roleId -> permissionMapper.getPermissionCodesByRoleId(roleId).stream())
            .collect(Collectors.toSet());
    }
}

4. 前端权限控制

// 权限指令
import type { Directive, DirectiveBinding } from 'vue';

const permission: Directive = {
  mounted(el: HTMLElement, binding: DirectiveBinding) {
    const { value } = binding;
    const permissions = useUserStore().permissions;
    
    if (value && !permissions.includes(value)) {
      el.parentNode?.removeChild(el);
    }
  }
};

export default permission;

// 权限组合式函数
import { computed } from 'vue';
import { useUserStore } from '@/store/user';

export function usePermission() {
  const userStore = useUserStore();
  
  const hasPermission = (permission: string | string[]): boolean => {
    const permissions = userStore.permissions;
    if (Array.isArray(permission)) {
      return permission.some(p => permissions.includes(p));
    }
    return permissions.includes(permission);
  };
  
  const hasAllPermissions = (permissions: string[]): boolean => {
    const userPermissions = userStore.permissions;
    return permissions.every(p => userPermissions.includes(p));
  };
  
  const hasAnyPermission = (permissions: string[]): boolean => {
    const userPermissions = userStore.permissions;
    return permissions.some(p => userPermissions.includes(p));
  };
  
  return {
    hasPermission,
    hasAllPermissions,
    hasAnyPermission
  };
}

// 使用示例
<template>
  <div>
    <!-- 使用指令 -->
    <a-button v-permission="'user:create'">新增用户</a-button>
    
    <!-- 使用组合式函数 -->
    <a-button v-if="hasPermission('user:update')">编辑</a-button>
    
    <!-- 批量权限检查 -->
    <a-button v-if="hasAllPermissions(['user:delete', 'user:export'])">
      批量操作
    </a-button>
  </div>
</template>

<script setup lang="ts">
import { usePermission } from '@/hooks/usePermission';

const { hasPermission, hasAllPermissions } = usePermission();
</script>

5. 后端权限注解使用

// Controller 中使用权限注解
package dev.tagtag.module.iam.controller;

import dev.tagtag.common.security.annotation.RequiresPermissions;
import dev.tagtag.common.core.result.Result;
import dev.tagtag.module.iam.dto.UserDTO;
import dev.tagtag.module.iam.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
    
    private final UserService userService;
    
    @GetMapping
    @RequiresPermissions("user:list")
    public Result<PageResult<UserDTO>> pageUsers(UserQuery query) {
        PageResult<UserDTO> result = userService.pageUsers(query);
        return Result.success(result);
    }
    
    @GetMapping("/{id}")
    @RequiresPermissions("user:query")
    public Result<UserDTO> getUserById(@PathVariable Long id) {
        UserDTO user = userService.getUserById(id);
        return Result.success(user);
    }
    
    @PostMapping
    @RequiresPermissions("user:create")
    public Result<Void> createUser(@RequestBody UserDTO userDTO) {
        userService.createUser(userDTO);
        return Result.success();
    }
    
    @PutMapping
    @RequiresPermissions("user:update")
    public Result<Void> updateUser(@RequestBody UserDTO userDTO) {
        userService.updateUser(userDTO);
        return Result.success();
    }
    
    @DeleteMapping("/{id}")
    @RequiresPermissions("user:delete")
    public Result<Void> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return Result.success();
    }
    
    @PutMapping("/{id}/status")
    @RequiresPermissions(value = {"user:update", "user:status"}, logical = Logical.OR)
    public Result<Void> updateUserStatus(@PathVariable Long id, @RequestParam Integer status) {
        userService.updateUserStatus(id, status);
        return Result.success();
    }
}

3. 代码审查和优化

// 向 AI 提问
"请审查这段代码,看是否符合 Tagtag Starter 的开发规范"

AI 会根据 Tagtag 的代码风格指南,提供改进建议。

4. 问题排查

// 向 AI 提问
"我在使用 Tagtag Starter 的文件存储功能时遇到了问题,错误信息是..."

AI 会结合文档和常见问题,帮助您快速定位和解决问题。

最佳实践

1. 提供清晰的上下文

当向 AI 提问时,提供足够的上下文信息:

// 好的提问
"在 Tagtag Starter 框架中,我需要创建一个新的模块 'order',包含订单管理功能。
请参考现有的 'user' 模块结构,生成相应的代码。"

// 不好的提问
"帮我写个订单模块"

2. 分步骤提问

对于复杂的任务,将其分解为多个小步骤:

// 第一步
"帮我创建订单实体类 Order,包含 id、userId、amount、status 等字段"

// 第二步
"基于 Order 实体,创建对应的 Mapper 接口"

// 第三步
"创建 OrderService 接口和实现类"

3. 验证生成的代码

AI 生成的代码需要人工审查和测试:

  • 检查是否符合项目规范
  • 运行单元测试
  • 进行集成测试
  • 代码审查

4. 持续学习

通过 AI 辅助开发,您可以:

  • 学习框架的最佳实践
  • 理解复杂的架构设计
  • 掌握新的开发技巧
  • 提高代码质量

高级用法

自定义 MCP 工具

如果您需要扩展 MCP 功能,可以在 Tagtag Starter 项目中添加自定义工具:

// server/mcp/tools/custom-tools.ts
export default defineMCPTool({
  name: 'tagtag-code-generator',
  description: 'Generate Tagtag Starter module code',
  inputSchema: {
    type: 'object',
    properties: {
      moduleName: {
        type: 'string',
        description: 'Module name'
      }
    }
  },
  handler: async (input) => {
    // 自定义代码生成逻辑
    return {
      success: true,
      data: 'Generated code...'
    }
  }
})

集成到 CI/CD

将 AI 辅助开发集成到您的 CI/CD 流程中:

# .github/workflows/ai-review.yml
name: AI Code Review
on: [pull_request]
jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: AI Review
        run: |
          npx @modelcontextprotocol/cli review \
            --docs http://tagtag.dev/llms.txt

常见问题

Q: AI 生成的代码可以直接使用吗?

A: 不建议直接使用。AI 生成的代码需要经过人工审查、测试和优化,确保符合项目规范和业务需求。

Q: 如何提高 AI 的回答质量?

A: 提供清晰、具体的上下文信息,参考 Tagtag Starter 的文档结构,使用框架术语,这样 AI 能提供更准确的回答。

Q: MCP 服务器连接失败怎么办?

A: 检查网络连接,确认文档地址可访问,查看 MCP 服务器日志,确保配置正确。

Q: 可以离线使用 AI 辅助开发吗?

A: 可以。您可以下载 llms.txt 文件到本地,配置本地 MCP 服务器,实现离线访问。

总结

Tagtag Starter 框架通过以下方式支持 AI 辅助开发:

  1. AI 友好的文档:提供标准的 llms.txt 和 llms-full.txt 文档
  2. MCP 集成:支持 Model Context Protocol,方便 AI 工具访问
  3. 清晰的代码结构:便于 AI 理解和生成代码
  4. 完善的文档:覆盖所有核心功能和最佳实践

通过合理使用 AI 辅助开发,您可以:

  • 提高开发效率
  • 减少重复工作
  • 学习最佳实践
  • 加速项目交付

开始使用 AI 辅助开发,让 Tagtag Starter 成为您开发路上的得力助手!

相关资源


需要帮助?欢迎在 GitHub Issues 中提问。