Files
contract-manager/.trae/rules/client_controller_rules.md
songqq e761990ebf feat: 实现SMB文件服务并优化合同文件管理
- 新增SmbFileService服务类,支持SMB/CIFS协议的文件操作
- 修改合同文件管理逻辑,支持SMB路径检查与目录创建
- 优化BankTableCell实现工厂模式并更新相关文档
- 调整Redis配置并添加连接测试
- 修复合同发票视图模型的时间处理问题
- 更新项目版本至0.0.134-SNAPSHOT
2025-11-12 16:32:03 +08:00

8.3 KiB
Raw Blame History

客户端 Controller 类规则

1. 目录结构

客户端控制器位于client/src/main/java/com/ecep/contract/controller/目录下,按业务模块组织:

  • 根目录:包含基础控制器、抽象控制器和主窗口控制器
  • 业务子包:按业务领域组织,如company/project/contract/vendor/
  • tab/子包包含所有Tab皮肤控制器和相关接口、抽象类
  • table/子包:包含表格相关控制器和单元格实现

2. 命名规范

  • 基础控制器BaseController.java
  • 抽象控制器:以Abst开头,如AbstEntityController.javaAbstManagerWindowController.java
  • 窗口控制器:以WindowController结尾,如HomeWindowController.java
  • 管理窗口控制器:以ManagerWindowController结尾,如CompanyManagerWindowController.java
  • Tab皮肤控制器:以TabSkin结尾,如CompanyTabSkinBase.java
  • 管理器皮肤:以ManagerSkin结尾,如CompanyManagerSkin.java
  • 组件控制器:如OkHttpLoginController.java

3. 继承关系

控制器类遵循以下继承层次结构:

  • BaseController:所有控制器的基础类,提供窗口显示、异常处理等通用功能
    • AbstEntityBasedController:基于实体的控制器抽象类
      • AbstManagerWindowController:管理窗口控制器的抽象类,包含搜索、分页等功能
    • AbstEntityController<T extends IdentityEntity, TV extends IdentityViewModel<T>>:实体详情窗口控制器的抽象类
      • 具体业务窗口控制器,如CompanyWindowControllerProjectWindowController

Tab相关控制器继承关系

  • Skin:皮肤接口基础
    • TabSkinTab皮肤接口
      • AbstGenericTabSkin<C extends BaseController>通用Tab皮肤抽象类
        • AbstEntityBasedTabSkin<C extends AbstEntityController<T, V>, T extends IdentityEntity, V extends IdentityViewModel<T>>基于实体的Tab皮肤抽象类
          • 具体业务Tab皮肤控制器CompanyTabSkinBase.javaContractTabSkinFiles.java

4. 注解使用

客户端控制器类应使用以下注解:

  • @Component声明为Spring组件使其可被Spring容器管理
  • @Scope("prototype"):设置为原型作用域,确保每次请求创建新实例
  • @Lazy:延迟加载,提高应用启动性能
  • @FxmlPath("/ui/[业务模块]/[文件名].fxml")指定对应的FXML文件路径
  • @Autowired:自动注入依赖的服务层组件

5. FXML文件规范

  • 文件位置FXML文件通常位于/client/src/main/resources/ui/目录下,按业务模块组织子目录
  • 命名规范:使用小写字母和下划线,如home.fxmlcompany/company.fxml
  • 关联方式:通过@FxmlPath注解指定控制器对应的FXML文件路径
  • 组件IDFXML文件中组件的id应与控制器中对应的字段名保持一致驼峰命名法

6. 控制器方法规范

6.1 初始化方法

  • initialize()控制器初始化方法用于设置UI组件初始状态、绑定事件监听器等
    • 此方法会在FXML加载完成后自动调用

6.2 窗口生命周期方法

  • onShown(WindowEvent windowEvent):窗口显示时触发的方法
    • 用于执行窗口显示后的初始化操作,如数据加载、组件状态更新等
    • 通常需要调用super.onShown(windowEvent)确保父类逻辑执行

6.3 窗口显示方法

  • public static void show():静态方法,用于便捷地显示控制器对应的窗口
    • 通常有多个重载版本支持不同参数如viewModel、owner窗口等
    • 内部调用BaseController.show()方法实现窗口显示逻辑

6.4 Tab相关方法

Tab皮肤控制器包含以下核心方法

  • install()安装Tab皮肤加载FXML并初始化UI组件
  • initializeUIComponents()初始化UI组件设置数据绑定和事件监听
  • getTab()获取对应的Tab对象
  • save()保存Tab中的数据变更
  • dispose():释放资源,清理监听器等

7. 字段规范

  • UI组件字段与FXML文件中的组件id对应使用public访问修饰符
  • 服务依赖字段:使用@Autowired注解注入,通常使用private访问修饰符
  • ViewModel字段:通常作为控制器的核心数据模型,提供getter/setter方法
  • 命名规范:使用驼峰命名法,如nameFieldtabPanesaveBtn

8. 数据绑定与更新

  • ViewModel绑定控制器通常通过ViewModel与实体数据进行绑定
  • 异步数据加载:使用CompletableFuture进行异步数据加载避免阻塞UI线程
  • UI更新在JavaFX应用线程中更新UI组件可使用Platform.runLater()确保线程安全

9. 事件处理

  • 按钮点击事件:通过setOnAction()方法绑定事件处理器
  • 表单字段变更可通过JavaFX的属性绑定机制监听字段变更
  • Tab切换事件:实现onTabShown()方法处理Tab显示事件

10. 异常处理

  • 使用handleException()方法统一处理异常
  • 异常信息通常会显示在状态栏并记录日志
  • 异步操作中的异常应通过exceptionally()handle()方法捕获处理

11. 最佳实践

  • 职责分离控制器应专注于UI交互和数据展示业务逻辑应委托给服务层
  • 避免重复代码:利用抽象类和接口提取通用逻辑
  • 资源管理:及时释放资源,避免内存泄漏
  • 线程安全确保在JavaFX应用线程中更新UI
  • 代码可读性:添加适当的注释,遵循命名规范

12. 示例代码结构

@Lazy
@Scope("prototype")
@Component
@FxmlPath("/ui/[模块名]/[文件名].fxml")
public class [业务]WindowController extends AbstEntityController<[Vo类型], [ViewModel类型]> {
    private static final Logger logger = LoggerFactory.getLogger([业务]WindowController.class);
    
    @Autowired
    private [业务]Service [业务]Service;
    
    // UI组件字段
    public BorderPane root;
    public TabPane tabPane;
    public TextField nameField;
    // ...其他UI组件
    
    public static void show([Vo类型] entity, Window window) {
        show([ViewModel类型].from(entity), window);
    }
    
    public static void show([ViewModel类型] viewModel, Window window) {
        BaseController.show([业务]WindowController.class, viewModel, window);
    }
    
    @Override
    public void initialize() {
        // 初始化UI组件和事件监听
    }
    
    @Override
    public void onShown(WindowEvent windowEvent) {
        super.onShown(windowEvent);
        // 窗口显示后的逻辑
    }
}

13. TableCell 工厂模式规范

13.1 工厂方法命名

  • TableCell类应提供静态工厂方法forTableColumn,用于创建单元格工厂
  • 方法命名必须为forTableColumn,保持一致性

13.2 工厂方法参数

  • 工厂方法应接收服务层参数,如BankService
  • 参数类型应与TableCell构造函数所需的服务类型一致

13.3 工厂方法返回类型

  • 返回类型应为Callback<javafx.scene.control.TableColumn<V, T>, javafx.scene.control.TableCell<V, T>>
  • 其中V为ViewModel类型T为单元格值类型

13.4 工厂方法实现

  • 在工厂方法内部创建并返回TableCell实例
  • 使用泛型参数确保类型安全

13.5 使用方式

  • 在设置表格列的单元格工厂时应调用TableCell的静态工厂方法
  • 避免直接使用new TableCell<>(service)的方式创建实例

13.6 示例代码

/**
 * 银行单元格
 */
@NoArgsConstructor
public class BankTableCell<T> extends AsyncUpdateTableCell<T, Integer, BankVo> {
    /**
     * 创建单元格工厂
     * 
     * @param bankService 银行服务
     * @return 单元格工厂
     */
    public static <V> Callback<javafx.scene.control.TableColumn<V, Integer>, javafx.scene.control.TableCell<V, Integer>> forTableColumn(
            BankService bankService) {
        return param -> new BankTableCell<V>(bankService);
    }

    public BankTableCell(BankService service) {
        setService(service);
    }

    @Override
    protected BankService getServiceBean() {
        return getBean(BankService.class);
    }
}

13.7 使用示例

// 推荐方式:使用工厂方法
column.setCellFactory(BankTableCell.forTableColumn(getBankService()));

// 不推荐方式:直接实例化
// column.setCellFactory(param -> new BankTableCell<>(getBankService()));