Browse Source

feat: 完成引航计划和用户管理功能开发

主要更新内容:
1. 引航计划管理功能
   - 实现引航计划查询(支持多条件查询、分页、排序)
   - 实现引航计划导入(支持制表符分隔格式、数据预览、重复检查)
   - 实现引航计划导出(Excel格式导出)
   - 添加港区作业类型计算(本港/外港)
   - 完善相关数据表和接口

2. 用户管理功能
   - 实现用户查询(支持多条件查询、分页、排序)
   - 实现用户新增(表单验证、默认密码设置)
   - 实现用户编辑
   - 实现用户删除(单个删除、批量删除、级联删除)
   - 实现密码重置功能
   - 完善相关数据表和接口

3. 前端优化
   - 实现标签页管理功能(TabsView组件)
   - 修复标签页关闭按钮和下拉菜单按钮可见性问题
   - 修复Element Plus图标使用问题(正确引入和注册图标组件)
   - 优化表格布局和响应式设计
   - 添加防重复点击机制

4. 最佳实践文档
   - 更新后端开发最佳实践
   - 更新前端开发最佳实践
   - 添加Element Plus图标使用最佳实践
   - 添加标签页管理最佳实践

5. 文档更新
   - 创建引航计划和用户管理功能需求文档
   - 更新数据库结构文档
   - 添加用户管理菜单Url更新说明文档
heyiwen 7 hours ago
parent
commit
70e4be5c32
100 changed files with 26989 additions and 380 deletions
  1. 481 0
      .trae/rules/backend-dev-best-practices.md
  2. 510 0
      .trae/rules/web-dev-best-practices.md
  3. 1944 54
      .trae/skills/web-dev-best-practices/SKILL.md
  4. 104 234
      .vscode/changelists.json
  5. 1 1
      DataMigration/obj/Debug/netcoreapp3.1/DataMigration.AssemblyInfo.cs
  6. 1 1
      DataMigration/obj/Debug/netcoreapp3.1/DataMigration.AssemblyInfoInputs.cache
  7. 5103 0
      FlinkDataSync/flink-sync.log
  8. BIN
      FlinkDataSync/target/flink-data-sync-1.0-SNAPSHOT.jar
  9. 1 1
      FlinkDataSync/target/maven-archiver/pom.properties
  10. BIN
      FlinkDataSync/target/original-flink-data-sync-1.0-SNAPSHOT.jar
  11. 89 0
      JavaBackend/logs/JavaBackend-2026-02-28.0.log
  12. 17558 89
      JavaBackend/logs/JavaBackend.log
  13. 6 0
      JavaBackend/pom.xml
  14. 60 0
      JavaBackend/src/main/java/com/lianda/backend/controller/EmployeeController.java
  15. 151 0
      JavaBackend/src/main/java/com/lianda/backend/controller/UserController.java
  16. 19 0
      JavaBackend/src/main/java/com/lianda/backend/dto/UserCreateDTO.java
  17. 63 0
      JavaBackend/src/main/java/com/lianda/backend/dto/UserDTO.java
  18. 18 0
      JavaBackend/src/main/java/com/lianda/backend/dto/UserQueryDTO.java
  19. 23 0
      JavaBackend/src/main/java/com/lianda/backend/dto/UserSaveDTO.java
  20. 22 0
      JavaBackend/src/main/java/com/lianda/backend/dto/UserUpdateDTO.java
  21. 42 0
      JavaBackend/src/main/java/com/lianda/backend/model/SalEmployee.java
  22. 108 0
      JavaBackend/src/main/java/com/lianda/backend/model/SysRole.java
  23. 33 0
      JavaBackend/src/main/java/com/lianda/backend/model/SysUserRole.java
  24. 45 0
      JavaBackend/src/main/java/com/lianda/backend/model/SysUserRoleId.java
  25. 11 0
      JavaBackend/src/main/java/com/lianda/backend/model/User.java
  26. 17 0
      JavaBackend/src/main/java/com/lianda/backend/repository/SalEmployeeRepository.java
  27. 9 0
      JavaBackend/src/main/java/com/lianda/backend/repository/SysRoleRepository.java
  28. 19 0
      JavaBackend/src/main/java/com/lianda/backend/repository/SysUserRoleRepository.java
  29. 33 0
      JavaBackend/src/main/java/com/lianda/backend/repository/UserRepository.java
  30. 419 0
      JavaBackend/src/main/java/com/lianda/backend/service/UserService.java
  31. 99 0
      JavaBackend/src/main/java/com/lianda/backend/test/UserMenuUrlUpdater.java
  32. BIN
      JavaBackend/target/classes/com/lianda/backend/Application.class
  33. BIN
      JavaBackend/target/classes/com/lianda/backend/annotation/RequirePermission.class
  34. BIN
      JavaBackend/target/classes/com/lianda/backend/aspect/DataSourceAspect.class
  35. BIN
      JavaBackend/target/classes/com/lianda/backend/config/AppConfig.class
  36. BIN
      JavaBackend/target/classes/com/lianda/backend/config/DataSource.class
  37. BIN
      JavaBackend/target/classes/com/lianda/backend/config/DataSourceContextHolder.class
  38. BIN
      JavaBackend/target/classes/com/lianda/backend/config/JpaConfig.class
  39. BIN
      JavaBackend/target/classes/com/lianda/backend/config/MultiDataSourceConfig.class
  40. BIN
      JavaBackend/target/classes/com/lianda/backend/config/RoutingDataSourceConfig$DataSourceType.class
  41. BIN
      JavaBackend/target/classes/com/lianda/backend/config/RoutingDataSourceConfig$RoutingDataSource.class
  42. BIN
      JavaBackend/target/classes/com/lianda/backend/config/RoutingDataSourceConfig.class
  43. BIN
      JavaBackend/target/classes/com/lianda/backend/config/SecurityConfig.class
  44. BIN
      JavaBackend/target/classes/com/lianda/backend/config/WebConfig.class
  45. BIN
      JavaBackend/target/classes/com/lianda/backend/controller/AuthController.class
  46. BIN
      JavaBackend/target/classes/com/lianda/backend/controller/CustomerCompanyBusinessController$1.class
  47. BIN
      JavaBackend/target/classes/com/lianda/backend/controller/CustomerCompanyBusinessController$2.class
  48. BIN
      JavaBackend/target/classes/com/lianda/backend/controller/CustomerCompanyBusinessController.class
  49. BIN
      JavaBackend/target/classes/com/lianda/backend/controller/DebugController.class
  50. BIN
      JavaBackend/target/classes/com/lianda/backend/controller/EmployeeController.class
  51. BIN
      JavaBackend/target/classes/com/lianda/backend/controller/MenuController.class
  52. BIN
      JavaBackend/target/classes/com/lianda/backend/controller/PilotPlanController.class
  53. BIN
      JavaBackend/target/classes/com/lianda/backend/controller/PortController.class
  54. BIN
      JavaBackend/target/classes/com/lianda/backend/controller/ShipCompanyController.class
  55. BIN
      JavaBackend/target/classes/com/lianda/backend/controller/UserController.class
  56. BIN
      JavaBackend/target/classes/com/lianda/backend/dto/BaseQueryDTO.class
  57. BIN
      JavaBackend/target/classes/com/lianda/backend/dto/CustomerCompanyBusinessSearchResult.class
  58. BIN
      JavaBackend/target/classes/com/lianda/backend/dto/LoginRequest.class
  59. BIN
      JavaBackend/target/classes/com/lianda/backend/dto/PageResponse.class
  60. BIN
      JavaBackend/target/classes/com/lianda/backend/dto/PilotPlanDTO.class
  61. BIN
      JavaBackend/target/classes/com/lianda/backend/dto/PilotPlanDetailDTO.class
  62. BIN
      JavaBackend/target/classes/com/lianda/backend/dto/PilotPlanImportDTO.class
  63. BIN
      JavaBackend/target/classes/com/lianda/backend/dto/PilotPlanProjection.class
  64. BIN
      JavaBackend/target/classes/com/lianda/backend/dto/PilotPlanQueryDTO.class
  65. BIN
      JavaBackend/target/classes/com/lianda/backend/dto/PortDTO.class
  66. BIN
      JavaBackend/target/classes/com/lianda/backend/dto/ShippingCompanyDTO.class
  67. BIN
      JavaBackend/target/classes/com/lianda/backend/dto/UserCreateDTO.class
  68. BIN
      JavaBackend/target/classes/com/lianda/backend/dto/UserDTO.class
  69. BIN
      JavaBackend/target/classes/com/lianda/backend/dto/UserQueryDTO.class
  70. BIN
      JavaBackend/target/classes/com/lianda/backend/dto/UserSaveDTO.class
  71. BIN
      JavaBackend/target/classes/com/lianda/backend/dto/UserUpdateDTO.class
  72. BIN
      JavaBackend/target/classes/com/lianda/backend/interceptor/PermissionInterceptor.class
  73. BIN
      JavaBackend/target/classes/com/lianda/backend/model/BusCustomerCompany.class
  74. BIN
      JavaBackend/target/classes/com/lianda/backend/model/BusCustomerCompanyBusiness.class
  75. BIN
      JavaBackend/target/classes/com/lianda/backend/model/BusCustomerCustomerType.class
  76. BIN
      JavaBackend/target/classes/com/lianda/backend/model/BusCustomerCustomerTypeId.class
  77. BIN
      JavaBackend/target/classes/com/lianda/backend/model/BusPilotTypeSetting.class
  78. BIN
      JavaBackend/target/classes/com/lianda/backend/model/BusShip.class
  79. BIN
      JavaBackend/target/classes/com/lianda/backend/model/DispBerthage.class
  80. BIN
      JavaBackend/target/classes/com/lianda/backend/model/DispBerthageDictionary.class
  81. BIN
      JavaBackend/target/classes/com/lianda/backend/model/DispBerthageSetting.class
  82. BIN
      JavaBackend/target/classes/com/lianda/backend/model/DispDispatcher.class
  83. BIN
      JavaBackend/target/classes/com/lianda/backend/model/DispPilot.class
  84. BIN
      JavaBackend/target/classes/com/lianda/backend/model/DispPilotPlan.class
  85. BIN
      JavaBackend/target/classes/com/lianda/backend/model/DispPort.class
  86. BIN
      JavaBackend/target/classes/com/lianda/backend/model/DispPortDictionary.class
  87. BIN
      JavaBackend/target/classes/com/lianda/backend/model/DispWaterway.class
  88. BIN
      JavaBackend/target/classes/com/lianda/backend/model/PilotPlan.class
  89. BIN
      JavaBackend/target/classes/com/lianda/backend/model/SalEmployee.class
  90. BIN
      JavaBackend/target/classes/com/lianda/backend/model/SysDictionaryItem.class
  91. BIN
      JavaBackend/target/classes/com/lianda/backend/model/SysMenu.class
  92. BIN
      JavaBackend/target/classes/com/lianda/backend/model/SysRole.class
  93. BIN
      JavaBackend/target/classes/com/lianda/backend/model/SysUser.class
  94. BIN
      JavaBackend/target/classes/com/lianda/backend/model/SysUserRole.class
  95. BIN
      JavaBackend/target/classes/com/lianda/backend/model/SysUserRoleId.class
  96. BIN
      JavaBackend/target/classes/com/lianda/backend/model/User.class
  97. BIN
      JavaBackend/target/classes/com/lianda/backend/repository/BusCustomerCompanyBusinessRepository.class
  98. BIN
      JavaBackend/target/classes/com/lianda/backend/repository/BusCustomerCompanyBusinessWriteRepository.class
  99. BIN
      JavaBackend/target/classes/com/lianda/backend/repository/BusCustomerCompanyRepository.class
  100. 0 0
      JavaBackend/target/classes/com/lianda/backend/repository/BusCustomerCompanyWriteRepository.class

+ 481 - 0
.trae/rules/backend-dev-best-practices.md

@@ -0,0 +1,481 @@
+# 后端开发最佳实践
+
+## 1. 数据库表位置规则
+
+### 1.1 数据库说明
+本项目使用两个数据库:
+- **TugboatCommon**:系统公共库,存放所有机构共享的数据
+- **LiandaTugboatMIS**:分支机构的业务库,存放各自机构的业务数据
+
+### 1.2 表位置规则
+根据FlinkDataSync项目的配置,以下表位于TugboatCommon库:
+- Sys_User(用户表)
+- Sys_Role(角色表)
+- Sys_Menu(菜单表)
+- Sys_UserRole(用户角色关联表)
+- Sys_Department(部门表)
+- Sys_Title(职务表)
+- Sys_Dictionary(字典表)
+- Sys_DictionaryItem(字典元素表)
+- Sys_Parameter(参数表)
+- Sys_SerialNumber(序号表)
+- Sys_log(日志表)
+- Sys_Attachment(附件表)
+- Sys_Announcement(公告)
+- Sys_Announcement_Sys_Role(公告发布角色)
+- Sys_Announcement_Sys_User(公告发布用户)
+- 以及其他以Sys_开头的系统表
+
+业务表位于LiandaTugboatMIS库:
+- Bus_*(业务相关表)
+- Disp_*(调度相关表)
+- Sal_*(薪酬相关表)
+- Man_*(机务相关表)
+- Tug_*(拖轮相关表)
+- Pro_*(项目相关表)
+- Fin_*(财务相关表)
+- Fabric_*(布品相关表)
+
+### 1.3 读写分离规则
+只有tugboatcommon和liandatugboatmis这两个库同时存在的表才需要读写分离:
+- 写入到tugboatcommon库
+- 从liandatugboatmis库读取数据
+- 其他表都在各自的数据库里读写即可
+
+### 1.4 数据源配置
+```java
+@Service
+@DataSource(value = RoutingDataSourceConfig.DataSourceType.COMMON)
+public class UserService {
+    // 使用TugboatCommon库
+}
+
+@Service
+@DataSource(value = RoutingDataSourceConfig.DataSourceType.MIS)
+public class BusinessService {
+    // 使用LiandaTugboatMIS库
+}
+```
+
+## 2. 公共字段处理规范
+
+### 2.1 公共字段定义
+以下字段为公共字段,所有表如果存在这些字段,都需要在新增和修改操作中处理:
+- **RecordStatus**:记录状态(1=启用,0=禁用)
+- **CreateUserID**:创建人ID
+- **CreateTime**:创建时间
+- **ModifyUserID**:修改人ID
+- **ModifyTime**:修改时间
+
+### 2.2 新增操作公共字段处理
+```java
+@Transactional
+public UserDTO createUser(UserCreateDTO userCreateDTO) {
+    User user = new User();
+    user.setId(UUID.randomUUID().toString());
+    user.setLoginId(userCreateDTO.getLoginId());
+    user.setName(userCreateDTO.getName());
+    
+    // 设置公共字段
+    String currentUserId = getCurrentUserId();
+    user.setRecordStatus(1); // 新增默认RecordStatus为1(启用)
+    user.setCreateUserID(currentUserId); // 创建人为当前登录人
+    user.setCreateTime(new Date()); // 创建时间为当前时间
+    user.setModifyUserID(currentUserId); // 修改人为当前登录人
+    user.setModifyTime(new Date()); // 修改时间为当前时间
+    
+    user = userRepository.save(user);
+    return UserDTO.fromEntity(user);
+}
+
+/**
+ * 获取当前登录用户ID
+ */
+private String getCurrentUserId() {
+    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+    if (authentication != null && authentication.getPrincipal() instanceof UserDetails) {
+        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
+        return userDetails.getUsername();
+    }
+    return "admin"; // 默认返回admin
+}
+```
+
+### 2.3 修改操作公共字段处理
+```java
+@Transactional
+public UserDTO updateUser(String userId, UserUpdateDTO userUpdateDTO) {
+    User user = userRepository.findById(userId).orElseThrow(() -> 
+        new RuntimeException("用户不存在"));
+    
+    user.setLoginId(userUpdateDTO.getLoginId());
+    user.setName(userUpdateDTO.getName());
+    
+    // 更新公共字段
+    String currentUserId = getCurrentUserId();
+    user.setModifyUserID(currentUserId); // 修改人为当前登录人
+    user.setModifyTime(new Date()); // 修改时间为当前时间
+    
+    user = userRepository.save(user);
+    return UserDTO.fromEntity(user);
+}
+```
+
+### 2.4 前端表单默认值
+```javascript
+const addUserForm = reactive({
+  loginId: '',
+  name: '',
+  recordStatus: 1 // 默认设置为1(启用)
+})
+```
+
+## 3. 数据库表名和字段名规则
+
+### 3.1 表名规则
+- 严格按照数据库结构文档(database_structure.md)中的表名
+- 表名使用PascalCase命名,如:Sys_User、Bus_CustomerCompany
+- 实体类的@Table注解必须与数据库表名完全一致
+- 注意:数据库文档中的表名可能使用下划线分隔,如Sys_User
+
+### 3.2 字段名规则
+- 严格按照数据库结构文档中的字段名
+- 字段名使用PascalCase命名,如:UserID、LoginID、Name
+- 实体类的@Column注解必须与数据库字段名完全一致
+- 注意:数据库文档中的字段名可能使用下划线分隔,如UserID
+
+### 3.3 实体类示例
+```java
+@Entity
+@Table(name = "Sys_User")
+public class User {
+    @Id
+    @Column(name = "UserID")
+    private String id;
+    
+    @Column(name = "LoginID")
+    private String loginId;
+    
+    @Column(name = "Name")
+    private String name;
+    
+    @Column(name = "Password")
+    private String password;
+    
+    @Column(name = "RecordStatus")
+    private Integer recordStatus;
+    
+    @Column(name = "CreateUserID")
+    private String createUserID;
+    
+    @Column(name = "CreateTime")
+    private Date createTime;
+    
+    @Column(name = "ModifyUserID")
+    private String modifyUserID;
+    
+    @Column(name = "ModifyTime")
+    private Date modifyTime;
+}
+```
+
+## 4. 字典数据查询规范
+
+### 4.1 字典查询规则
+获取Sys_DictionaryItem数据时,必须同时传递DictionaryCode和Value值:
+```java
+// 错误:只传递Value值
+SysDictionaryItem item = sysDictionaryItemRepository.findByValue(1);
+
+// 正确:同时传递DictionaryCode和Value
+SysDictionaryItem item = sysDictionaryItemRepository.findByDictionaryCodeAndValue("RecordStatus", 1);
+```
+
+### 4.2 字典Repository示例
+```java
+@Repository
+public interface SysDictionaryItemRepository extends JpaRepository<SysDictionaryItem, String> {
+    @Query(value = "SELECT * FROM Sys_DictionaryItem WHERE DictionaryCode = :dictionaryCode AND Value = :value", nativeQuery = true)
+    SysDictionaryItem findByDictionaryCodeAndValue(@Param("dictionaryCode") String dictionaryCode, @Param("value") Integer value);
+    
+    @Query(value = "SELECT * FROM Sys_DictionaryItem WHERE DictionaryCode = :dictionaryCode ORDER BY OrderNo", nativeQuery = true)
+    List<SysDictionaryItem> findByDictionaryCode(@Param("dictionaryCode") String dictionaryCode);
+}
+```
+
+## 5. 重复校验规范
+
+### 5.1 新增操作重复校验
+```java
+@Transactional
+public UserDTO createUser(UserCreateDTO userCreateDTO) {
+    // 重复校验:检查LoginID是否重复
+    if (userRepository.findByLoginID(userCreateDTO.getLoginId()) != null) {
+        throw new RuntimeException("账号已存在,请使用其他账号");
+    }
+    
+    // 重复校验:检查Name是否重复
+    if (userRepository.findByName(userCreateDTO.getName()) != null) {
+        throw new RuntimeException("用户名已存在,请使用其他用户名");
+    }
+    
+    // 创建用户...
+}
+```
+
+### 5.2 修改操作重复校验
+```java
+@Transactional
+public UserDTO updateUser(String userId, UserUpdateDTO userUpdateDTO) {
+    User user = userRepository.findById(userId).orElseThrow(() -> 
+        new RuntimeException("用户不存在"));
+    
+    // 重复校验:检查LoginID是否与其他用户重复
+    User existingUser = userRepository.findByLoginID(userUpdateDTO.getLoginId());
+    if (existingUser != null && !existingUser.getId().equals(userId)) {
+        throw new RuntimeException("账号已存在,请使用其他账号");
+    }
+    
+    // 重复校验:检查Name是否与其他用户重复
+    existingUser = userRepository.findByName(userUpdateDTO.getName());
+    if (existingUser != null && !existingUser.getId().equals(userId)) {
+        throw new RuntimeException("用户名已存在,请使用其他用户名");
+    }
+    
+    // 更新用户...
+}
+```
+
+## 6. 密码处理规范
+
+### 6.1 密码加密
+使用MD5加密密码:
+```java
+private String md5(String password) {
+    try {
+        MessageDigest md = MessageDigest.getInstance("MD5");
+        byte[] digest = md.digest(password.getBytes("UTF-8"));
+        StringBuilder sb = new StringBuilder();
+        for (byte b : digest) {
+            sb.append(String.format("%02x", b));
+        }
+        return sb.toString();
+    } catch (Exception e) {
+        throw new RuntimeException("密码加密失败", e);
+    }
+}
+```
+
+### 6.2 默认密码
+新增用户时,默认密码为"TIMS123456"的MD5值:
+```java
+user.setPassword(md5("TIMS123456"));
+```
+
+## 7. 删除操作规范
+
+### 7.1 删除确认
+前端必须显示删除确认对话框:
+```javascript
+const deleteUser = async (id) => {
+  try {
+    await ElMessageBox.confirm(
+      '确定要删除该用户吗?删除后用户及其角色关联将被永久删除,不可恢复。',
+      '删除确认',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }
+    )
+    
+    await request.delete(`/user/delete/${id}`)
+    ElMessage.success('删除成功')
+    getUsers()
+  } catch (error) {
+    if (error !== 'cancel') {
+      console.error('删除用户失败:', error)
+      ElMessage.error('删除用户失败')
+    }
+  }
+}
+```
+
+### 7.2 级联删除
+删除主表数据时,必须先删除关联表数据:
+```java
+@Transactional
+public void deleteUsers(List<String> userIds) {
+    for (String userId : userIds) {
+        // 先删除关联表数据
+        sysUserRoleRepository.deleteByUserId(userId);
+        // 再删除主表数据
+        userRepository.deleteById(userId);
+    }
+}
+```
+
+## 8. SQL查询规范
+
+### 8.1 使用JdbcTemplate处理动态SQL
+对于需要动态拼接SQL的查询,使用JdbcTemplate:
+```java
+@Autowired
+@Qualifier("writeJdbcTemplate")
+private JdbcTemplate writeJdbcTemplate;
+
+public List<User> queryUsers(UserQuery query) {
+    StringBuilder sql = new StringBuilder("SELECT * FROM Sys_User WHERE 1=1");
+    List<Object> params = new ArrayList<>();
+    
+    if (query.getLoginId() != null && !query.getLoginId().isEmpty()) {
+        sql.append(" AND LoginID LIKE ?");
+        params.add("%" + query.getLoginId() + "%");
+    }
+    
+    if (query.getName() != null && !query.getName().isEmpty()) {
+        sql.append(" AND Name LIKE ?");
+        params.add("%" + query.getName() + "%");
+    }
+    
+    return writeJdbcTemplate.query(sql.toString(), params.toArray(), new UserRowMapper());
+}
+```
+
+### 8.2 使用CONCAT函数处理字符串拼接
+```java
+// 错误:使用+号拼接字符串
+@Query(value = "SELECT * FROM Sys_User WHERE LoginID LIKE '%' + :loginID + '%'", nativeQuery = true)
+
+// 正确:使用CONCAT函数
+@Query(value = "SELECT * FROM Sys_User WHERE LoginID LIKE CONCAT('%', :loginID, '%')", nativeQuery = true)
+User findByLoginIDLike(@Param("loginID") String loginID);
+```
+
+### 8.3 排序处理
+使用Pageable处理排序,不要在SQL中写死ORDER BY:
+```java
+public Page<User> queryUsers(UserQuery query, Pageable pageable) {
+    Sort sort = pageable.getSort();
+    if (query.getSortBy() != null && query.getSortOrder() != null) {
+        sort = Sort.by(
+            "asc".equalsIgnoreCase(query.getSortOrder()) ? Sort.Direction.ASC : Sort.Direction.DESC,
+            query.getSortBy()
+        );
+    }
+    return userRepository.findAll(pageable.withSort(sort));
+}
+```
+
+## 9. 错误处理规范
+
+### 9.1 业务异常
+使用RuntimeException抛出业务异常:
+```java
+if (userRepository.findByLoginID(userCreateDTO.getLoginId()) != null) {
+    throw new RuntimeException("账号已存在,请使用其他账号");
+}
+```
+
+### 9.2 前端错误处理
+```javascript
+try {
+  await request.post('/user/create', data)
+  ElMessage.success('操作成功')
+} catch (error) {
+  console.error('操作失败:', error)
+  ElMessage.error(error.response?.data?.message || '操作失败,请稍后重试')
+}
+```
+
+## 10. 事务管理规范
+
+### 10.1 使用@Transactional注解
+所有涉及多个数据库操作的方法都必须添加@Transactional注解:
+```java
+@Transactional
+public UserDTO createUser(UserCreateDTO userCreateDTO) {
+    // 创建用户
+    User user = userRepository.save(user);
+    
+    // 创建用户角色关联
+    SysUserRole userRole = new SysUserRole();
+    userRole.setUserId(user.getId());
+    userRole.setRoleId(userCreateDTO.getRoleId());
+    sysUserRoleRepository.save(userRole);
+    
+    return UserDTO.fromEntity(user);
+}
+```
+
+### 10.2 只读事务
+查询方法可以使用@Transactional(readOnly = true):
+```java
+@Transactional(readOnly = true)
+public Page<User> queryUsers(UserQuery query, Pageable pageable) {
+    return userRepository.findAll(pageable);
+}
+```
+
+## 11. 分页查询规范
+
+### 11.1 使用Spring Data JPA分页
+```java
+public Page<User> queryUsers(UserQuery query, Pageable pageable) {
+    return userRepository.findAll(pageable);
+}
+```
+
+### 11.2 前端分页参数
+```javascript
+const getUsers = async () => {
+  const response = await request.get('/user/list', {
+    params: {
+      page: currentPage.value,
+      pageSize: pageSize.value,
+      sortBy: sortMap.value.sortBy,
+      sortOrder: sortMap.value.sortOrder
+    }
+  })
+  userList.value = response.data.content
+  total.value = response.data.totalElements
+}
+```
+
+## 12. 日志记录规范
+
+### 12.1 使用SLF4J记录日志
+```java
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Service
+public class UserService {
+    private static final Logger logger = LoggerFactory.getLogger(UserService.class);
+    
+    public UserDTO createUser(UserCreateDTO userCreateDTO) {
+        logger.info("开始创建用户,账号:{}", userCreateDTO.getLoginId());
+        try {
+            // 创建用户逻辑
+            logger.info("用户创建成功,ID:{}", user.getId());
+            return UserDTO.fromEntity(user);
+        } catch (Exception e) {
+            logger.error("用户创建失败", e);
+            throw new RuntimeException("用户创建失败", e);
+        }
+    }
+}
+```
+
+## 13. 注意事项
+
+1. **不要随意修改数据库结构**:数据库结构以database_structure.md文档为准
+2. **不要添加不存在的字段**:如果数据库文档没有描述某个字段,就是没有这个字段
+3. **注意表名和字段名的大小写**:严格按照数据库文档的大小写
+4. **使用正确的数据源**:根据表的位置选择正确的数据源
+5. **处理公共字段**:所有新增和修改操作都必须处理公共字段
+6. **添加重复校验**:新增和修改操作都必须进行重复校验
+7. **使用事务管理**:涉及多个数据库操作的方法必须使用@Transactional
+8. **记录日志**:重要操作必须记录日志
+9. **处理异常**:使用RuntimeException抛出业务异常,前端统一处理错误提示
+10. **使用MD5加密密码**:密码必须使用MD5加密存储

+ 510 - 0
.trae/rules/web-dev-best-practices.md

@@ -0,0 +1,510 @@
+# Web前端开发最佳实践
+
+## 1. 页面布局规范
+
+### 1.1 标题和查询条件区域
+
+参考引航计划的布局方式,页面应包含以下区域:
+
+```vue
+<template>
+  <div class="page-container">
+    <!-- 页面标题 -->
+    <h2>页面名称</h2>
+    
+    <!-- 查询条件区域 -->
+    <div class="search-section">
+      <h3>查询条件</h3>
+      <el-form :model="queryForm" class="search-form">
+        <div class="search-row">
+          <el-form-item label="字段名">
+            <el-input v-model="queryForm.field" placeholder="请输入..." clearable></el-input>
+          </el-form-item>
+          <!-- 更多查询条件 -->
+          <div class="search-actions">
+            <el-button type="primary" @click="query">查询</el-button>
+            <el-button @click="reset">重置</el-button>
+          </div>
+        </div>
+      </el-form>
+    </div>
+    
+    <!-- 操作按钮区域 -->
+    <div class="action-section">
+      <el-button type="primary" @click="add">新增</el-button>
+      <el-button type="danger" @click="deleteSelected" :disabled="selectedIds.length === 0">删除</el-button>
+    </div>
+    
+    <!-- 表格区域 -->
+    <div class="table-container">
+      <el-table>...</el-table>
+    </div>
+  </div>
+</template>
+```
+
+### 1.2 样式规范
+
+```css
+.page-container {
+  padding: 15px;
+  height: 100vh;
+  overflow-y: auto;
+}
+
+.page-container h2 {
+  margin: 0 0 15px 0;
+  font-size: 18px;
+  color: #303133;
+}
+
+.search-section {
+  background: #fff;
+  padding: 15px;
+  margin-bottom: 15px;
+  border-radius: 4px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.search-section h3 {
+  margin: 0 0 12px 0;
+  font-size: 15px;
+  color: #606266;
+}
+
+.search-form {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.search-row {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 12px;
+  align-items: center;
+}
+
+.search-row .el-form-item {
+  margin-bottom: 0;
+  min-width: 200px;
+}
+
+.search-actions {
+  display: flex;
+  gap: 8px;
+  margin-left: auto;
+}
+
+.action-section {
+  margin-bottom: 15px;
+  display: flex;
+  gap: 8px;
+}
+
+.table-container {
+  background: #fff;
+  padding: 15px;
+  border-radius: 4px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+}
+```
+
+**关键间距说明:**
+- 页面容器padding:15px
+- 标题h2:font-size: 18px,margin-bottom: 15px
+- 查询条件区域padding:15px,margin-bottom: 15px
+- 标题h3:font-size: 15px,margin-bottom: 12px
+- search-row gap:12px
+- search-actions gap:8px
+- 操作按钮区域margin-bottom:15px
+- 表格容器padding:15px
+- 分页margin-top:15px
+- 对话框按钮gap:8px
+
+**注意:** 所有间距都应保持紧凑,避免过大的空白区域,确保界面紧凑、专业。
+
+## 2. 表格设计规范
+
+### 2.1 表格属性
+
+```vue
+<el-table
+  :data="tableData"
+  stripe
+  border
+  size="default"
+  :header-cell-style="{ background: '#f5f7fa', color: '#303133', fontWeight: 'bold' }"
+  :height="tableHeight"
+  @selection-change="handleSelectionChange"
+  @sort-change="handleSortChange">
+```
+
+### 2.2 列设计规范
+
+- **序号列**:宽度50px,固定居中
+- **选择列**:宽度55px,固定居中
+- **操作列**:宽度120px,固定右侧
+- **状态列**:宽度80px,居中显示,使用el-tag
+- **日期时间列**:宽度160px,居中显示
+- **用户名列**:宽度100px
+- **账号列**:宽度100px
+- **用户角色列**:宽度150px
+- **企业微信账号列**:宽度120px
+- **其他文本列**:使用min-width,支持文本溢出显示
+
+```vue
+<el-table-column type="selection" width="55" fixed align="center"></el-table-column>
+<el-table-column type="index" label="序号" width="50" fixed align="center"></el-table-column>
+<el-table-column prop="name" label="用户名" width="100" sortable show-overflow-tooltip></el-table-column>
+<el-table-column prop="loginId" label="账号" width="100" sortable show-overflow-tooltip></el-table-column>
+<el-table-column prop="roleName" label="用户角色" width="150" sortable show-overflow-tooltip></el-table-column>
+<el-table-column prop="weChatUserId" label="企业微信账号" width="120" sortable show-overflow-tooltip></el-table-column>
+<el-table-column prop="recordStatusName" label="状态" width="80" sortable align="center">
+  <template #default="scope">
+    <el-tag :type="scope.row.recordStatus === 1 ? 'success' : 'info'" size="small">
+      {{ scope.row.recordStatusName }}
+    </el-tag>
+  </template>
+</el-table-column>
+<el-table-column prop="createTime" label="创建时间" width="160" sortable align="center" show-overflow-tooltip>
+  <template #default="scope">
+    {{ formatDateTime(scope.row.createTime) }}
+  </template>
+</el-table-column>
+<el-table-column prop="modifyTime" label="修改时间" width="160" sortable align="center" show-overflow-tooltip>
+  <template #default="scope">
+    {{ formatDateTime(scope.row.modifyTime) }}
+  </template>
+</el-table-column>
+<el-table-column label="操作" width="120" align="center" fixed="right">
+  <template #default="{ row }">
+    <el-button link type="primary" size="small" @click="edit(row)">编辑</el-button>
+    <el-button link type="danger" size="small" @click="delete(row.id)">删除</el-button>
+  </template>
+</el-table-column>
+```
+
+**重要说明:**
+- 使用固定宽度(width)而不是最小宽度(min-width)可以避免标题栏分行
+- 序号列使用50px而不是60px,更加紧凑
+- 操作列使用120px,足够容纳两个按钮
+- 状态列使用80px,足够显示状态标签
+- 用户名和账号列使用100px,适合大多数情况
+- 用户角色列使用150px,可以容纳多个角色名称(用逗号分隔)
+- 企业微信账号列使用120px,适合大多数微信号长度
+- 日期时间列使用160px,可以完整显示日期时间格式
+
+### 2.3 日期时间格式化
+
+```javascript
+const formatDateTime = (dateStr) => {
+  if (!dateStr) return ''
+  const date = new Date(dateStr)
+  const year = date.getFullYear()
+  const month = String(date.getMonth() + 1).padStart(2, '0')
+  const day = String(date.getDate()).padStart(2, '0')
+  const hours = String(date.getHours()).padStart(2, '0')
+  const minutes = String(date.getMinutes()).padStart(2, '0')
+  const seconds = String(date.getSeconds()).padStart(2, '0')
+  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
+}
+```
+
+## 3. 删除操作规范
+
+### 3.1 删除确认对话框
+
+所有删除操作都必须显示确认对话框,防止误操作。
+
+#### 单个删除
+
+```javascript
+const deleteUser = async (id) => {
+  try {
+    await ElMessageBox.confirm(
+      '确定要删除该用户吗?删除后用户及其角色关联将被永久删除,不可恢复。',
+      '删除确认',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }
+    )
+    
+    await request.delete(`/user/delete/${id}`)
+    ElMessage.success('删除成功')
+    getUsers()
+  } catch (error) {
+    if (error !== 'cancel') {
+      console.error('删除用户失败:', error)
+      ElMessage.error('删除用户失败')
+    }
+  }
+}
+```
+
+#### 批量删除
+
+```javascript
+const deleteSelectedUsers = async () => {
+  if (selectedUserIds.value.length === 0) {
+    return
+  }
+  
+  try {
+    await ElMessageBox.confirm(
+      `确定要删除选中的 ${selectedUserIds.value.length} 个用户吗?删除后用户及其角色关联将被永久删除,不可恢复。`,
+      '删除确认',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }
+    )
+    
+    await request.delete('/user/delete', {
+      data: selectedUserIds.value
+    })
+    ElMessage.success('删除成功')
+    selectedUserIds.value = []
+    getUsers()
+  } catch (error) {
+    if (error !== 'cancel') {
+      console.error('删除用户失败:', error)
+      ElMessage.error('删除用户失败')
+    }
+  }
+}
+```
+
+### 3.2 后端删除逻辑
+
+删除主表数据时,必须先删除关联表数据。
+
+```java
+@Transactional
+public void deleteUsers(List<String> userIds) {
+    for (String userId : userIds) {
+        // 先删除关联表数据
+        sysUserRoleRepository.deleteByUserId(userId);
+        // 再删除主表数据
+        userRepository.deleteById(userId);
+    }
+}
+```
+
+## 4. 排序功能
+
+### 4.1 前端排序
+
+```javascript
+const handleSortChange = ({ prop, order }) => {
+  if (order) {
+    queryForm.value.sortBy = prop
+    queryForm.value.sortOrder = order === 'ascending' ? 'asc' : 'desc'
+  } else {
+    queryForm.value.sortBy = null
+    queryForm.value.sortOrder = null
+  }
+  getUsers()
+}
+```
+
+### 4.2 后端排序
+
+```java
+public Page<User> queryUsers(UserQuery query, Pageable pageable) {
+    Sort sort = pageable.getSort();
+    if (query.getSortBy() != null && query.getSortOrder() != null) {
+        sort = Sort.by(
+            "asc".equalsIgnoreCase(query.getSortOrder()) ? Sort.Direction.ASC : Sort.Direction.DESC,
+            query.getSortBy()
+        );
+    }
+    return userRepository.findAll(pageable.withSort(sort));
+}
+```
+
+## 5. API请求规范
+
+### 5.1 使用request.js
+
+所有API请求都应该使用统一的request工具,自动处理认证和错误。
+
+```javascript
+import request from '@/utils/request'
+
+export const getUsers = (params) => {
+  return request.get('/user/list', { params })
+}
+
+export const createUser = (data) => {
+  return request.post('/user/create', data)
+}
+
+export const deleteUser = (data) => {
+  return request.delete('/user/delete', { data })
+}
+```
+
+### 5.2 错误处理
+
+```javascript
+try {
+  await request.post('/api/endpoint', data)
+  ElMessage.success('操作成功')
+} catch (error) {
+  console.error('操作失败:', error)
+  ElMessage.error('操作失败,请稍后重试')
+}
+```
+
+## 6. 表单验证
+
+### 6.1 前端验证
+
+```vue
+<el-form :model="form" :rules="rules" ref="formRef">
+  <el-form-item label="用户名" prop="name">
+    <el-input v-model="form.name" placeholder="请输入用户名"></el-input>
+  </el-form-item>
+</el-form>
+
+<script>
+const rules = {
+  name: [
+    { required: true, message: '请输入用户名', trigger: 'blur' },
+    { min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
+  ]
+}
+</script>
+```
+
+### 6.2 后端验证
+
+```java
+public class UserCreateDTO {
+    @NotBlank(message = "用户名不能为空")
+    @Size(min = 2, max = 50, message = "用户名长度必须在2-50之间")
+    private String name;
+    
+    @NotBlank(message = "账号不能为空")
+    private String loginId;
+    
+    @NotBlank(message = "密码不能为空")
+    @Size(min = 6, message = "密码长度不能少于6位")
+    private String password;
+}
+```
+
+## 7. 代码规范
+
+### 7.1 命名规范
+
+- 组件名:使用PascalCase,如`UserManagement.vue`
+- 方法名:使用camelCase,如`getUsers`、`handleSortChange`
+- 变量名:使用camelCase,如`userList`、`selectedIds`
+- 常量名:使用UPPER_SNAKE_CASE,如`MAX_PAGE_SIZE`
+
+### 7.2 注释规范
+
+```javascript
+/**
+ * 获取用户列表
+ */
+const getUsers = async () => {
+  // 实现代码
+}
+
+/**
+ * 处理排序变化
+ * @param {Object} param - 排序参数
+ * @param {String} param.prop - 排序字段
+ * @param {String} param.order - 排序方向
+ */
+const handleSortChange = ({ prop, order }) => {
+  // 实现代码
+}
+```
+
+## 8. 性能优化
+
+### 8.1 防抖和节流
+
+```javascript
+import { debounce } from 'lodash-es'
+
+const search = debounce(() => {
+  getUsers()
+}, 300)
+```
+
+### 8.2 虚拟滚动
+
+对于大量数据,使用虚拟滚动提高性能。
+
+```vue
+<el-table
+  :data="tableData"
+  height="600"
+  v-loading="loading">
+</el-table>
+```
+
+## 9. 安全规范
+
+### 9.1 XSS防护
+
+使用Vue的插值表达式,避免直接使用v-html。
+
+```vue
+<!-- 正确 -->
+<div>{{ user.name }}</div>
+
+<!-- 错误 -->
+<div v-html="user.name"></div>
+```
+
+### 9.2 CSRF防护
+
+所有POST、PUT、DELETE请求都应该携带CSRF token。
+
+```javascript
+import request from '@/utils/request'
+
+request.interceptors.request.use(config => {
+  const token = localStorage.getItem('csrf_token')
+  if (token) {
+    config.headers['X-CSRF-TOKEN'] = token
+  }
+  return config
+})
+```
+
+## 10. 可访问性
+
+### 10.1 语义化HTML
+
+```vue
+<nav>...</nav>
+<main>
+  <h1>页面标题</h1>
+  <section class="search-section">...</section>
+  <section class="table-section">...</section>
+</main>
+<footer>...</footer>
+```
+
+### 10.2 键盘导航
+
+确保所有交互元素都可以通过键盘访问。
+
+```vue
+<el-button @click="handleClick" @keyup.enter="handleClick">
+  点击
+</el-button>
+```

File diff suppressed because it is too large
+ 1944 - 54
.trae/skills/web-dev-best-practices/SKILL.md


+ 104 - 234
.vscode/changelists.json

@@ -4,292 +4,162 @@
       "name": "Default",
       "files": [
         ".vscode/changelists.json",
-        ".vscode/settings.json",
-        "DataMigration/DataMigration.csproj",
-        "DataMigration/GetForeignKeyInfo.sql",
-        "DataMigration/Program.cs",
-        "DataMigration/appsettings.json",
-        "DataMigration/bin/Debug/net8.0/BouncyCastle.Cryptography.dll",
-        "DataMigration/bin/Debug/net8.0/DataMigration.deps.json",
-        "DataMigration/bin/Debug/net8.0/DataMigration.dll",
-        "DataMigration/bin/Debug/net8.0/DataMigration.exe",
-        "DataMigration/bin/Debug/net8.0/DataMigration.pdb",
-        "DataMigration/bin/Debug/net8.0/DataMigration.runtimeconfig.json",
-        "DataMigration/bin/Debug/net8.0/Google.Protobuf.dll",
-        "DataMigration/bin/Debug/net8.0/K4os.Compression.LZ4.Streams.dll",
-        "DataMigration/bin/Debug/net8.0/K4os.Compression.LZ4.dll",
-        "DataMigration/bin/Debug/net8.0/K4os.Hash.xxHash.dll",
-        "DataMigration/bin/Debug/net8.0/MySql.Data.dll",
-        "DataMigration/bin/Debug/net8.0/Newtonsoft.Json.dll",
-        "DataMigration/bin/Debug/net8.0/System.Configuration.ConfigurationManager.dll",
-        "DataMigration/bin/Debug/net8.0/System.Data.SqlClient.dll",
-        "DataMigration/bin/Debug/net8.0/System.Diagnostics.EventLog.dll",
-        "DataMigration/bin/Debug/net8.0/System.IO.Pipelines.dll",
-        "DataMigration/bin/Debug/net8.0/System.Security.Cryptography.ProtectedData.dll",
-        "DataMigration/bin/Debug/net8.0/System.Security.Permissions.dll",
-        "DataMigration/bin/Debug/net8.0/System.Windows.Extensions.dll",
-        "DataMigration/bin/Debug/net8.0/ZstdSharp.dll",
-        "DataMigration/bin/Debug/net8.0/appsettings.json",
-        "DataMigration/bin/Debug/net8.0/runtimes/unix/lib/net8.0/System.Data.SqlClient.dll",
-        "DataMigration/bin/Debug/net8.0/runtimes/win-arm64/native/sni.dll",
-        "DataMigration/bin/Debug/net8.0/runtimes/win-x64/native/comerr64.dll",
-        "DataMigration/bin/Debug/net8.0/runtimes/win-x64/native/gssapi64.dll",
-        "DataMigration/bin/Debug/net8.0/runtimes/win-x64/native/k5sprt64.dll",
-        "DataMigration/bin/Debug/net8.0/runtimes/win-x64/native/krb5_64.dll",
-        "DataMigration/bin/Debug/net8.0/runtimes/win-x64/native/krbcc64.dll",
-        "DataMigration/bin/Debug/net8.0/runtimes/win-x64/native/sni.dll",
-        "DataMigration/bin/Debug/net8.0/runtimes/win-x86/native/sni.dll",
-        "DataMigration/bin/Debug/net8.0/runtimes/win/lib/net8.0/System.Data.SqlClient.dll",
-        "DataMigration/bin/Debug/net8.0/runtimes/win/lib/net8.0/System.Diagnostics.EventLog.Messages.dll",
-        "DataMigration/bin/Debug/net8.0/runtimes/win/lib/net8.0/System.Diagnostics.EventLog.dll",
-        "DataMigration/bin/Debug/net8.0/runtimes/win/lib/net8.0/System.Windows.Extensions.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/BouncyCastle.Crypto.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/DataMigration.deps.json",
-        "DataMigration/bin/Debug/netcoreapp3.1/DataMigration.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/DataMigration.exe",
-        "DataMigration/bin/Debug/netcoreapp3.1/DataMigration.pdb",
-        "DataMigration/bin/Debug/netcoreapp3.1/DataMigration.runtimeconfig.dev.json",
-        "DataMigration/bin/Debug/netcoreapp3.1/DataMigration.runtimeconfig.json",
-        "DataMigration/bin/Debug/netcoreapp3.1/Google.Protobuf.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/K4os.Compression.LZ4.Streams.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/K4os.Compression.LZ4.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/K4os.Hash.xxHash.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/Microsoft.Win32.SystemEvents.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/MySql.Data.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/Newtonsoft.Json.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/System.Configuration.ConfigurationManager.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/System.Data.SqlClient.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/System.Drawing.Common.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/System.Runtime.CompilerServices.Unsafe.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/System.Security.Cryptography.ProtectedData.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/System.Security.Permissions.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/System.Windows.Extensions.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/Ubiety.Dns.Core.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/ZstdNet.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/appsettings.json",
-        "DataMigration/bin/Debug/netcoreapp3.1/runtimes/unix/lib/netcoreapp2.1/System.Data.SqlClient.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/runtimes/unix/lib/netcoreapp3.0/System.Drawing.Common.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/runtimes/win-arm64/native/sni.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/runtimes/win-x64/native/sni.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/runtimes/win-x86/native/sni.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/runtimes/win/lib/netcoreapp2.1/System.Data.SqlClient.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/runtimes/win/lib/netcoreapp3.0/Microsoft.Win32.SystemEvents.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/runtimes/win/lib/netcoreapp3.0/System.Drawing.Common.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/runtimes/win/lib/netcoreapp3.0/System.Windows.Extensions.dll",
-        "DataMigration/bin/Debug/netcoreapp3.1/runtimes/win/lib/netstandard2.0/System.Security.Cryptography.ProtectedData.dll",
-        "DataMigration/obj/DataMigration.csproj.nuget.dgspec.json",
-        "DataMigration/obj/DataMigration.csproj.nuget.g.props",
-        "DataMigration/obj/DataMigration.csproj.nuget.g.targets",
-        "DataMigration/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs",
-        "DataMigration/obj/Debug/net8.0/DataMigr.FB59EF5F.Up2Date",
-        "DataMigration/obj/Debug/net8.0/DataMigration.AssemblyInfo.cs",
-        "DataMigration/obj/Debug/net8.0/DataMigration.AssemblyInfoInputs.cache",
-        "DataMigration/obj/Debug/net8.0/DataMigration.GeneratedMSBuildEditorConfig.editorconfig",
-        "DataMigration/obj/Debug/net8.0/DataMigration.GlobalUsings.g.cs",
-        "DataMigration/obj/Debug/net8.0/DataMigration.assets.cache",
-        "DataMigration/obj/Debug/net8.0/DataMigration.csproj.AssemblyReference.cache",
-        "DataMigration/obj/Debug/net8.0/DataMigration.csproj.CoreCompileInputs.cache",
-        "DataMigration/obj/Debug/net8.0/DataMigration.csproj.FileListAbsolute.txt",
-        "DataMigration/obj/Debug/net8.0/DataMigration.dll",
-        "DataMigration/obj/Debug/net8.0/DataMigration.genruntimeconfig.cache",
-        "DataMigration/obj/Debug/net8.0/DataMigration.pdb",
-        "DataMigration/obj/Debug/net8.0/apphost.exe",
-        "DataMigration/obj/Debug/net8.0/ref/DataMigration.dll",
-        "DataMigration/obj/Debug/net8.0/refint/DataMigration.dll",
-        "DataMigration/obj/Debug/netcoreapp3.1/.NETCoreApp,Version=v3.1.AssemblyAttributes.cs",
-        "DataMigration/obj/Debug/netcoreapp3.1/DataMigr.FB59EF5F.Up2Date",
         "DataMigration/obj/Debug/netcoreapp3.1/DataMigration.AssemblyInfo.cs",
         "DataMigration/obj/Debug/netcoreapp3.1/DataMigration.AssemblyInfoInputs.cache",
-        "DataMigration/obj/Debug/netcoreapp3.1/DataMigration.GeneratedMSBuildEditorConfig.editorconfig",
-        "DataMigration/obj/Debug/netcoreapp3.1/DataMigration.assets.cache",
-        "DataMigration/obj/Debug/netcoreapp3.1/DataMigration.csproj.AssemblyReference.cache",
-        "DataMigration/obj/Debug/netcoreapp3.1/DataMigration.csproj.CoreCompileInputs.cache",
-        "DataMigration/obj/Debug/netcoreapp3.1/DataMigration.csproj.FileListAbsolute.txt",
-        "DataMigration/obj/Debug/netcoreapp3.1/DataMigration.dll",
-        "DataMigration/obj/Debug/netcoreapp3.1/DataMigration.genruntimeconfig.cache",
-        "DataMigration/obj/Debug/netcoreapp3.1/DataMigration.pdb",
-        "DataMigration/obj/Debug/netcoreapp3.1/apphost.exe",
-        "DataMigration/obj/project.assets.json",
-        "DataMigration/obj/project.nuget.cache",
-        "JavaBackend/src/main/java/com/lianda/backend/config/SecurityConfig.java",
-        ".vscode/launch.json",
-        ".vscode/tasks.json",
-        "JavaBackend/target/classes/com/lianda/backend/config/SecurityConfig.class",
-        "vue-frontend/src/utils/pilotPlanParser.js",
-        "JavaBackend/target/classes/com/lianda/backend/dto/PilotPlanImportDTO.class",
+        "JavaBackend/src/main/java/com/lianda/backend/repository/UserRepository.java",
+        "JavaBackend/src/main/java/com/lianda/backend/model/SysRole.java",
+        "JavaBackend/src/main/java/com/lianda/backend/model/SysUserRole.java",
+        "JavaBackend/src/main/java/com/lianda/backend/repository/SysRoleRepository.java",
+        "JavaBackend/src/main/java/com/lianda/backend/repository/SysUserRoleRepository.java",
+        "JavaBackend/src/main/java/com/lianda/backend/controller/UserController.java",
+        "JavaBackend/src/main/java/com/lianda/backend/dto/UserDTO.java",
+        "JavaBackend/src/main/java/com/lianda/backend/dto/UserQueryDTO.java",
+        "JavaBackend/src/main/java/com/lianda/backend/service/UserService.java",
+        "vue-frontend/src/components/UserManagement.vue",
+        "JavaBackend/src/main/java/com/lianda/backend/dto/UserCreateDTO.java",
+        "JavaBackend/pom.xml",
+        "JavaBackend/target/classes/com/lianda/backend/annotation/RequirePermission.class",
+        "JavaBackend/target/classes/com/lianda/backend/config/AppConfig.class",
+        "JavaBackend/target/classes/com/lianda/backend/config/DataSource.class",
+        "JavaBackend/target/classes/com/lianda/backend/config/RoutingDataSourceConfig$DataSourceType.class",
+        "JavaBackend/target/classes/com/lianda/backend/config/RoutingDataSourceConfig$RoutingDataSource.class",
+        "JavaBackend/target/classes/com/lianda/backend/config/RoutingDataSourceConfig.class",
+        "JavaBackend/target/classes/com/lianda/backend/controller/AuthController.class",
+        "JavaBackend/target/classes/com/lianda/backend/controller/DebugController.class",
+        "JavaBackend/target/classes/com/lianda/backend/dto/BaseQueryDTO.class",
+        "JavaBackend/target/classes/com/lianda/backend/dto/LoginRequest.class",
+        "JavaBackend/target/classes/com/lianda/backend/dto/PilotPlanDetailDTO.class",
+        "JavaBackend/target/classes/com/lianda/backend/model/BusCustomerCustomerType.class",
+        "JavaBackend/target/classes/com/lianda/backend/model/BusCustomerCustomerTypeId.class",
+        "JavaBackend/target/classes/com/lianda/backend/model/BusPilotTypeSetting.class",
+        "JavaBackend/target/classes/com/lianda/backend/model/DispDispatcher.class",
+        "JavaBackend/target/classes/com/lianda/backend/model/DispPilot.class",
         "JavaBackend/target/classes/com/lianda/backend/model/DispPilotPlan.class",
-        "JavaBackend/target/classes/com/lianda/backend/service/PilotPlanService.class",
+        "JavaBackend/target/classes/com/lianda/backend/model/DispPort.class",
+        "JavaBackend/target/classes/com/lianda/backend/model/DispWaterway.class",
+        "JavaBackend/target/classes/com/lianda/backend/model/SysMenu.class",
+        "JavaBackend/target/classes/com/lianda/backend/repository/BusCustomerCustomerTypeRepository.class",
+        "JavaBackend/target/classes/com/lianda/backend/repository/DispPilotPlanWriteRepository.class",
+        "JavaBackend/target/classes/com/lianda/backend/repository/DispPilotWriteRepository.class",
+        "JavaBackend/target/classes/com/lianda/backend/repository/DispPortReadRepository.class",
+        "JavaBackend/target/classes/com/lianda/backend/repository/DispPortRepository.class",
+        "JavaBackend/target/classes/com/lianda/backend/repository/DispWaterwayRepository.class",
+        "JavaBackend/target/classes/com/lianda/backend/repository/SysMenuRepository.class",
+        "JavaBackend/target/classes/com/lianda/backend/util/CurrentUserUtil$1.class",
+        "JavaBackend/target/classes/com/lianda/backend/util/CurrentUserUtil.class",
+        "JavaBackend/target/classes/com/lianda/backend/util/JwtUtil.class",
         "JavaBackend/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst",
         "JavaBackend/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst",
-        "vue-frontend/src/components/PilotPlan.vue",
-        "DEBUG_GUIDE.md",
-        "start-all.ps1",
-        "vue-frontend/src/utils/testPilotPlanParser.js",
-        ".trae/rules/project_rules.md",
-        "com.lianda.auth/target/classes/com/lianda/auth/config/SecurityConfig.class",
-        "com.lianda.auth/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst",
-        "com.lianda.auth/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst",
-        "com.lianda.auth/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst",
-        ".trae/skills/multi-project-debugger/SKILL.md",
+        "\"\\347\\224\\250\\346\\210\\267\\347\\256\\241\\347\\220\\206\\345\\212\\237\\350\\203\\275\\351\\234\\200\\346\\261\\202\\346\\226\\207\\346\\241\\243.md\"",
+        "JavaBackend/target/classes/com/lianda/backend/test/QueryFunctionCodes.class",
+        "JavaBackend/logs/JavaBackend.log",
+        "JavaBackend/logs/JavaBackend-2026-02-28.0.log",
+        "JavaBackend/src/main/java/com/lianda/backend/model/SysUserRoleId.java",
         "FlinkDataSync/flink-sync.log",
-        "FlinkDataSync/target/classes/com/lianda/flink/sync/MySqlCdcSync.class",
-        "FlinkDataSync/target/classes/com/lianda/flink/sync/MySqlSink.class",
         "FlinkDataSync/target/flink-data-sync-1.0-SNAPSHOT.jar",
         "FlinkDataSync/target/maven-archiver/pom.properties",
-        "FlinkDataSync/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst",
-        "FlinkDataSync/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst",
         "FlinkDataSync/target/original-flink-data-sync-1.0-SNAPSHOT.jar",
-        "JavaBackend/src/main/resources/application.properties",
-        "JavaBackend/target/classes/application.properties",
-        "com.lianda.auth/src/main/resources/application.properties",
-        "com.lianda.auth/target/classes/application.properties",
-        "JavaBackend/src/main/java/com/lianda/backend/dto/PilotPlanImportDTO.java",
-        "JavaBackend/src/main/java/com/lianda/backend/model/DispPilotPlan.java",
-        "JavaBackend/src/main/java/com/lianda/backend/service/PilotPlanService.java",
-        "vue-frontend/src/utils/tokenRefreshManager.js",
-        "JavaBackend/target/classes/com/lianda/backend/dto/LoginRequest.class",
-        "JavaBackend/target/classes/com/lianda/backend/dto/LoginResponse.class",
-        "JavaBackend/target/classes/com/lianda/backend/dto/PilotPlanDTO.class",
-        "JavaBackend/target/classes/com/lianda/backend/dto/PilotPlanQueryDTO.class",
-        "JavaBackend/target/classes/com/lianda/backend/model/DispDispatcher.class",
-        "JavaBackend/target/classes/com/lianda/backend/model/SysMenu.class",
-        "JavaBackend/target/classes/com/lianda/backend/model/User.class",
-        "JavaBackend/target/classes/com/lianda/backend/repository/BusCustomerCompanyRepository.class",
-        "JavaBackend/target/classes/com/lianda/backend/repository/BusShipRepository.class",
-        "JavaBackend/target/classes/com/lianda/backend/repository/DispBerthageRepository.class",
-        "JavaBackend/target/classes/com/lianda/backend/repository/DispDispatcherRepository.class",
-        "JavaBackend/target/classes/com/lianda/backend/repository/DispPilotRepository.class",
-        "JavaBackend/target/classes/com/lianda/backend/repository/DispPortRepository.class",
-        "JavaBackend/target/classes/com/lianda/backend/repository/DispWaterwayRepository.class",
-        "JavaBackend/target/classes/com/lianda/backend/repository/SysDictionaryItemRepository.class",
-        "JavaBackend/target/classes/com/lianda/backend/service/AuthService.class",
-        "JavaBackend/target/classes/com/lianda/backend/service/BusPilotTypeSettingCacheService.class",
-        "JavaBackend/target/classes/com/lianda/backend/service/CommonDataService.class",
-        "JavaBackend/target/classes/com/lianda/backend/service/MenuService.class",
-        "JavaBackend/src/main/java/com/lianda/backend/dto/PilotPlanDTO.java",
-        "JavaBackend/src/main/java/com/lianda/backend/repository/DispPilotPlanRepository.java",
-        "JavaBackend/target/classes/com/lianda/backend/repository/DispPilotPlanRepository.class",
-        "JavaBackend/src/main/java/com/lianda/backend/dto/PilotPlanQueryDTO.java",
-        "JavaBackend/src/main/java/com/lianda/backend/repository/BusCustomerCompanyRepository.java",
-        "JavaBackend/src/main/java/com/lianda/backend/repository/BusShipRepository.java",
-        "JavaBackend/src/main/java/com/lianda/backend/repository/DispBerthageRepository.java",
-        "JavaBackend/src/main/java/com/lianda/backend/repository/DispPilotRepository.java",
-        "JavaBackend/src/main/java/com/lianda/backend/repository/DispPortRepository.java",
-        "JavaBackend/src/main/java/com/lianda/backend/repository/DispWaterwayRepository.java",
-        "JavaBackend/src/main/java/com/lianda/backend/repository/SysDictionaryItemRepository.java",
-        "JavaBackend/target/classes/com/lianda/backend/controller/PilotPlanController.class",
-        "JavaBackend/pom.xml",
-        "JavaBackend/src/main/java/com/lianda/backend/dto/LoginRequest.java",
-        "JavaBackend/src/main/java/com/lianda/backend/dto/LoginResponse.java",
-        "JavaBackend/src/main/java/com/lianda/backend/model/SysMenu.java",
-        "JavaBackend/src/main/java/com/lianda/backend/model/User.java",
-        "compile_error.txt",
-        "JavaBackend/src/main/java/com/lianda/backend/config/RoutingDataSourceConfig.java",
-        "JavaBackend/src/main/java/com/lianda/backend/controller/PilotPlanController.java",
-        "JavaBackend/src/main/java/com/lianda/backend/model/DispDispatcher.java",
-        "JavaBackend/src/main/java/com/lianda/backend/repository/DispDispatcherRepository.java",
-        "JavaBackend/src/main/java/com/lianda/backend/service/AuthService.java",
-        "JavaBackend/src/main/java/com/lianda/backend/service/BusPilotTypeSettingCacheService.java",
-        "JavaBackend/src/main/java/com/lianda/backend/service/CommonDataService.java",
-        "JavaBackend/src/main/java/com/lianda/backend/service/MenuService.java",
-        "JavaBackend/src/main/java/com/lianda/backend/controller/ShipCompanyController.java",
-        "JavaBackend/target/classes/com/lianda/backend/controller/PortController$1.class",
-        "JavaBackend/target/classes/com/lianda/backend/controller/PortController.class",
-        "JavaBackend/target/classes/com/lianda/backend/dto/ShippingCompanyDTO.class",
-        "JavaBackend/target/classes/com/lianda/backend/repository/DispPilotPlanReadRepository.class",
-        ".trae/skills/login-debugger/SKILL.md",
-        "JavaBackend/src/main/java/com/lianda/backend/controller/PortController.java",
-        "JavaBackend/src/main/java/com/lianda/backend/dto/ShippingCompanyDTO.java",
-        "JavaBackend/src/main/java/com/lianda/backend/repository/BusCustomerCustomerTypeRepository.java",
-        "com.lianda.auth/src/main/java/com/lianda/auth/config/SecurityConfig.java",
-        "com.lianda.auth/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst",
-        ".trae/rules/query-error-debugging.md",
-        "JavaBackend/src/main/java/com/lianda/backend/model/BusCustomerCustomerType.java",
-        ".trae/skills/web-dev-best-practices/SKILL.md",
-        "JavaBackend/target/classes/com/lianda/backend/model/BusCustomerCustomerType.class",
-        "JavaBackend/src/main/java/com/lianda/backend/repository/DispPilotPlanReadRepository.java",
-        "vue-frontend/src/components/AdaptiveTable.vue",
-        "vue-frontend/src/assets/styles/layout.css",
-        "vue-frontend/src/components/GridLayoutTable.vue",
-        "vue-frontend/src/components/SimpleAdaptiveTable.vue",
-        "vue-frontend/src/utils/adaptiveLayout.js",
-        "vue-frontend/src/main.js",
-        "vue-frontend/src/utils/layout-best-practices.md",
-        "pilotplan_test_data.txt",
         "JavaBackend/target/classes/com/lianda/backend/Application.class",
-        "JavaBackend/target/classes/com/lianda/backend/annotation/RequirePermission.class",
         "JavaBackend/target/classes/com/lianda/backend/aspect/DataSourceAspect.class",
-        "JavaBackend/target/classes/com/lianda/backend/config/AppConfig.class",
-        "JavaBackend/target/classes/com/lianda/backend/config/DataSource.class",
         "JavaBackend/target/classes/com/lianda/backend/config/DataSourceContextHolder.class",
         "JavaBackend/target/classes/com/lianda/backend/config/JpaConfig.class",
         "JavaBackend/target/classes/com/lianda/backend/config/MultiDataSourceConfig.class",
-        "JavaBackend/target/classes/com/lianda/backend/config/RoutingDataSourceConfig$DataSourceType.class",
-        "JavaBackend/target/classes/com/lianda/backend/config/RoutingDataSourceConfig$RoutingDataSource.class",
-        "JavaBackend/target/classes/com/lianda/backend/config/RoutingDataSourceConfig.class",
+        "JavaBackend/target/classes/com/lianda/backend/config/SecurityConfig.class",
         "JavaBackend/target/classes/com/lianda/backend/config/WebConfig.class",
-        "JavaBackend/target/classes/com/lianda/backend/controller/AuthController.class",
         "JavaBackend/target/classes/com/lianda/backend/controller/CustomerCompanyBusinessController$1.class",
         "JavaBackend/target/classes/com/lianda/backend/controller/CustomerCompanyBusinessController$2.class",
         "JavaBackend/target/classes/com/lianda/backend/controller/CustomerCompanyBusinessController.class",
-        "JavaBackend/target/classes/com/lianda/backend/controller/DebugController.class",
         "JavaBackend/target/classes/com/lianda/backend/controller/MenuController.class",
-        "JavaBackend/target/classes/com/lianda/backend/dto/BaseQueryDTO.class",
+        "JavaBackend/target/classes/com/lianda/backend/controller/PilotPlanController.class",
+        "JavaBackend/target/classes/com/lianda/backend/controller/PortController.class",
+        "JavaBackend/target/classes/com/lianda/backend/controller/ShipCompanyController.class",
         "JavaBackend/target/classes/com/lianda/backend/dto/CustomerCompanyBusinessSearchResult.class",
         "JavaBackend/target/classes/com/lianda/backend/dto/PageResponse.class",
-        "JavaBackend/target/classes/com/lianda/backend/dto/PilotPlanDetailDTO.class",
+        "JavaBackend/target/classes/com/lianda/backend/dto/PilotPlanDTO.class",
+        "JavaBackend/target/classes/com/lianda/backend/dto/PilotPlanImportDTO.class",
         "JavaBackend/target/classes/com/lianda/backend/dto/PilotPlanProjection.class",
+        "JavaBackend/target/classes/com/lianda/backend/dto/PilotPlanQueryDTO.class",
         "JavaBackend/target/classes/com/lianda/backend/dto/PortDTO.class",
+        "JavaBackend/target/classes/com/lianda/backend/dto/ShippingCompanyDTO.class",
         "JavaBackend/target/classes/com/lianda/backend/interceptor/PermissionInterceptor.class",
         "JavaBackend/target/classes/com/lianda/backend/model/BusCustomerCompany.class",
         "JavaBackend/target/classes/com/lianda/backend/model/BusCustomerCompanyBusiness.class",
-        "JavaBackend/target/classes/com/lianda/backend/model/BusCustomerCustomerTypeId.class",
-        "JavaBackend/target/classes/com/lianda/backend/model/BusPilotTypeSetting.class",
         "JavaBackend/target/classes/com/lianda/backend/model/BusShip.class",
         "JavaBackend/target/classes/com/lianda/backend/model/DispBerthage.class",
         "JavaBackend/target/classes/com/lianda/backend/model/DispBerthageDictionary.class",
         "JavaBackend/target/classes/com/lianda/backend/model/DispBerthageSetting.class",
-        "JavaBackend/target/classes/com/lianda/backend/model/DispPilot.class",
-        "JavaBackend/target/classes/com/lianda/backend/model/DispPort.class",
         "JavaBackend/target/classes/com/lianda/backend/model/DispPortDictionary.class",
-        "JavaBackend/target/classes/com/lianda/backend/model/DispWaterway.class",
         "JavaBackend/target/classes/com/lianda/backend/model/PilotPlan.class",
         "JavaBackend/target/classes/com/lianda/backend/model/SysDictionaryItem.class",
         "JavaBackend/target/classes/com/lianda/backend/model/SysUser.class",
         "JavaBackend/target/classes/com/lianda/backend/repository/BusCustomerCompanyBusinessRepository.class",
         "JavaBackend/target/classes/com/lianda/backend/repository/BusCustomerCompanyBusinessWriteRepository.class",
+        "JavaBackend/target/classes/com/lianda/backend/repository/BusCustomerCompanyRepository.class",
         "JavaBackend/target/classes/com/lianda/backend/repository/BusCustomerCompanyWriteRepository.class",
-        "JavaBackend/target/classes/com/lianda/backend/repository/BusCustomerCustomerTypeRepository.class",
         "JavaBackend/target/classes/com/lianda/backend/repository/BusCustomerCustomerTypeWriteRepository.class",
         "JavaBackend/target/classes/com/lianda/backend/repository/BusPilotTypeSettingRepository.class",
         "JavaBackend/target/classes/com/lianda/backend/repository/BusShipReadRepository.class",
+        "JavaBackend/target/classes/com/lianda/backend/repository/BusShipRepository.class",
         "JavaBackend/target/classes/com/lianda/backend/repository/BusShipWriteRepository.class",
         "JavaBackend/target/classes/com/lianda/backend/repository/DispBerthageDictionaryRepository.class",
+        "JavaBackend/target/classes/com/lianda/backend/repository/DispBerthageRepository.class",
         "JavaBackend/target/classes/com/lianda/backend/repository/DispBerthageSettingRepository.class",
         "JavaBackend/target/classes/com/lianda/backend/repository/DispBerthageSettingWriteRepository.class",
         "JavaBackend/target/classes/com/lianda/backend/repository/DispBerthageWriteRepository.class",
-        "JavaBackend/target/classes/com/lianda/backend/repository/DispPilotPlanWriteRepository.class",
-        "JavaBackend/target/classes/com/lianda/backend/repository/DispPilotWriteRepository.class",
+        "JavaBackend/target/classes/com/lianda/backend/repository/DispDispatcherRepository.class",
+        "JavaBackend/target/classes/com/lianda/backend/repository/DispPilotPlanReadRepository.class",
+        "JavaBackend/target/classes/com/lianda/backend/repository/DispPilotPlanRepository.class",
+        "JavaBackend/target/classes/com/lianda/backend/repository/DispPilotRepository.class",
         "JavaBackend/target/classes/com/lianda/backend/repository/DispPortDictionaryRepository.class",
-        "JavaBackend/target/classes/com/lianda/backend/repository/DispPortReadRepository.class",
         "JavaBackend/target/classes/com/lianda/backend/repository/DispPortWriteRepository.class",
         "JavaBackend/target/classes/com/lianda/backend/repository/DispWaterwayWriteRepository.class",
         "JavaBackend/target/classes/com/lianda/backend/repository/PilotPlanRepository.class",
-        "JavaBackend/target/classes/com/lianda/backend/repository/SysMenuRepository.class",
-        "JavaBackend/target/classes/com/lianda/backend/repository/UserRepository.class",
-        "JavaBackend/target/classes/com/lianda/backend/service/UserDetailsServiceImpl.class",
+        "JavaBackend/target/classes/com/lianda/backend/repository/SysDictionaryItemRepository.class",
+        "JavaBackend/target/classes/com/lianda/backend/service/BusPilotTypeSettingCacheService.class",
+        "JavaBackend/target/classes/com/lianda/backend/service/CommonDataService.class",
+        "JavaBackend/target/classes/com/lianda/backend/service/MenuService.class",
+        "JavaBackend/target/classes/com/lianda/backend/service/PilotPlanService.class",
         "JavaBackend/target/classes/com/lianda/backend/test/DatabaseMenuUpdater.class",
         "JavaBackend/target/classes/com/lianda/backend/test/MD5Test.class",
         "JavaBackend/target/classes/com/lianda/backend/test/MenuUrlUpdater.class",
-        "JavaBackend/target/classes/com/lianda/backend/util/CurrentUserUtil$1.class",
-        "JavaBackend/target/classes/com/lianda/backend/util/CurrentUserUtil.class",
-        "JavaBackend/target/classes/com/lianda/backend/util/JwtUtil.class",
-        "JavaBackend/target/classes/com/lianda/backend/controller/ShipCompanyController.class",
-        "JavaBackend/src/main/java/com/lianda/backend/test/QueryFunctionCodes.java",
-        "JavaBackend/target/classes/com/lianda/backend/test/QueryFunctionCodes.class",
-        "JavaBackend/src/main/resources/logback-spring.xml",
-        "JavaBackend/target/classes/logback-spring.xml",
-        "JavaBackend/logs/JavaBackend.log"
+        "vue-frontend/vue.config.js",
+        "vue-frontend/src/utils/request.js",
+        "JavaBackend/target/classes/com/lianda/backend/repository/UserRepository.class",
+        ".trae/rules/web-dev-best-practices.md",
+        "JavaBackend/src/main/java/com/lianda/backend/model/User.java",
+        "JavaBackend/target/classes/com/lianda/backend/model/User.class",
+        "JavaBackend/src/main/java/com/lianda/backend/model/SalEmployee.java",
+        "JavaBackend/src/main/java/com/lianda/backend/repository/SalEmployeeRepository.java",
+        ".trae/rules/backend-dev-best-practices.md",
+        "JavaBackend/src/main/java/com/lianda/backend/dto/UserUpdateDTO.java",
+        "JavaBackend/target/classes/com/lianda/backend/controller/UserController.class",
+        "JavaBackend/target/classes/com/lianda/backend/dto/UserCreateDTO.class",
+        "JavaBackend/target/classes/com/lianda/backend/dto/UserQueryDTO.class",
+        "JavaBackend/target/classes/com/lianda/backend/dto/UserUpdateDTO.class",
+        "JavaBackend/target/classes/com/lianda/backend/model/SalEmployee.class",
+        "JavaBackend/target/classes/com/lianda/backend/model/SysUserRole.class",
+        "JavaBackend/target/classes/com/lianda/backend/repository/SalEmployeeRepository.class",
+        "JavaBackend/target/classes/com/lianda/backend/repository/SysUserRoleRepository.class",
+        "JavaBackend/target/classes/com/lianda/backend/service/UserService.class",
+        "JavaBackend/target/classes/com/lianda/backend/dto/UserDTO.class",
+        "JavaBackend/target/classes/com/lianda/backend/model/SysRole.class",
+        "JavaBackend/target/classes/com/lianda/backend/model/SysUserRoleId.class",
+        "JavaBackend/target/classes/com/lianda/backend/repository/SysRoleRepository.class",
+        "JavaBackend/src/main/java/com/lianda/backend/dto/UserSaveDTO.java",
+        "JavaBackend/target/classes/com/lianda/backend/dto/UserSaveDTO.class",
+        "JavaBackend/src/main/java/com/lianda/backend/controller/EmployeeController.java",
+        "JavaBackend/target/classes/com/lianda/backend/controller/EmployeeController.class",
+        "database_structure.md",
+        ".trae/skills/web-dev-best-practices/SKILL.md",
+        "vue-frontend/src/components/PilotPlan.vue",
+        "JavaBackend/src/main/java/com/lianda/backend/test/UserMenuUrlUpdater.java",
+        "JavaBackend/target/classes/com/lianda/backend/test/UserMenuUrlUpdater.class",
+        "update_user_menu_url.sql",
+        "\"\\346\\233\\264\\346\\226\\260\\347\\224\\250\\346\\210\\267\\347\\256\\241\\347\\220\\206\\350\\217\\234\\345\\215\\225Url-\\346\\211\\247\\350\\241\\214\\350\\257\\264\\346\\230\\216.md\"",
+        "\"\\346\\233\\264\\346\\226\\260\\347\\224\\250\\346\\210\\267\\347\\256\\241\\347\\220\\206\\350\\217\\234\\345\\215\\225Url\\350\\257\\264\\346\\230\\216.md\"",
+        "vue-frontend/src/components/HomePage.vue",
+        "vue-frontend/src/components/TabsView.vue",
+        "vue-frontend/src/main.js"
       ]
     }
   ],

+ 1 - 1
DataMigration/obj/Debug/netcoreapp3.1/DataMigration.AssemblyInfo.cs

@@ -13,7 +13,7 @@ using System.Reflection;
 [assembly: System.Reflection.AssemblyCompanyAttribute("DataMigration")]
 [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
 [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
-[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+5e94ed188c8a7f4d7b8f26eb67da238bd9dac8fa")]
+[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+94b6227841d22a2a4abd2fc88a6b5784f7c551a8")]
 [assembly: System.Reflection.AssemblyProductAttribute("DataMigration")]
 [assembly: System.Reflection.AssemblyTitleAttribute("DataMigration")]
 [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

+ 1 - 1
DataMigration/obj/Debug/netcoreapp3.1/DataMigration.AssemblyInfoInputs.cache

@@ -1 +1 @@
-e5ef1c6279a904a184cbff700bd9d1156ea3fe3ffdf799de2630235344b23f9a
+1a0ed5df2b1634044b2893cea8696895152a6175f32a4fb35443e4fd65d671e4

File diff suppressed because it is too large
+ 5103 - 0
FlinkDataSync/flink-sync.log


BIN
FlinkDataSync/target/flink-data-sync-1.0-SNAPSHOT.jar


+ 1 - 1
FlinkDataSync/target/maven-archiver/pom.properties

@@ -1,5 +1,5 @@
 #Generated by Maven
-#Sat Feb 28 09:02:44 CST 2026
+#Mon Mar 02 09:42:48 CST 2026
 version=1.0-SNAPSHOT
 groupId=com.lianda
 artifactId=flink-data-sync

BIN
FlinkDataSync/target/original-flink-data-sync-1.0-SNAPSHOT.jar


File diff suppressed because it is too large
+ 89 - 0
JavaBackend/logs/JavaBackend-2026-02-28.0.log


File diff suppressed because it is too large
+ 17558 - 89
JavaBackend/logs/JavaBackend.log


+ 6 - 0
JavaBackend/pom.xml

@@ -34,6 +34,12 @@
             <artifactId>spring-boot-starter-data-jpa</artifactId>
         </dependency>
 
+        <!-- Spring Boot Validation -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
         <!-- MySQL Connector -->
         <dependency>
             <groupId>mysql</groupId>

+ 60 - 0
JavaBackend/src/main/java/com/lianda/backend/controller/EmployeeController.java

@@ -0,0 +1,60 @@
+package com.lianda.backend.controller;
+
+import com.lianda.backend.config.DataSource;
+import com.lianda.backend.config.RoutingDataSourceConfig;
+import com.lianda.backend.dto.PageResponse;
+import com.lianda.backend.model.SalEmployee;
+import com.lianda.backend.repository.SalEmployeeRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/employee")
+@DataSource(value = RoutingDataSourceConfig.DataSourceType.BRANCH)
+public class EmployeeController {
+
+    @Autowired
+    private SalEmployeeRepository salEmployeeRepository;
+
+    /**
+     * 获取员工列表
+     *
+     * @param page 页码
+     * @param pageSize 每页大小
+     * @return 分页结果
+     */
+    @GetMapping("/list")
+    public ResponseEntity<Map<String, Object>> getEmployeeList(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "10") int pageSize) {
+        try {
+            Pageable pageable = PageRequest.of(page - 1, pageSize);
+            Page<SalEmployee> employeePage = salEmployeeRepository.findAll(pageable);
+            
+            PageResponse<SalEmployee> pageResponse = new PageResponse<>(
+                employeePage.getContent(),
+                employeePage.getTotalElements(),
+                employeePage.getNumber() + 1,
+                employeePage.getSize()
+            );
+            
+            Map<String, Object> response = new HashMap<>();
+            response.put("success", true);
+            response.put("data", pageResponse);
+            return new ResponseEntity<>(response, HttpStatus.OK);
+        } catch (Exception e) {
+            Map<String, Object> response = new HashMap<>();
+            response.put("success", false);
+            response.put("message", "获取员工列表失败:" + e.getMessage());
+            return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
+        }
+    }
+}

+ 151 - 0
JavaBackend/src/main/java/com/lianda/backend/controller/UserController.java

@@ -0,0 +1,151 @@
+package com.lianda.backend.controller;
+
+import com.lianda.backend.annotation.RequirePermission;
+import com.lianda.backend.dto.PageResponse;
+import com.lianda.backend.dto.UserCreateDTO;
+import com.lianda.backend.dto.UserDTO;
+import com.lianda.backend.dto.UserQueryDTO;
+import com.lianda.backend.dto.UserSaveDTO;
+import com.lianda.backend.model.SysDictionaryItem;
+import com.lianda.backend.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/user")
+public class UserController {
+    
+    @Autowired
+    private UserService userService;
+    
+    /**
+     * 查询用户列表
+     *
+     * @param queryDTO 查询条件
+     * @return 分页结果
+     */
+    @GetMapping("/list")
+    @RequirePermission("0101")
+    public ResponseEntity<Map<String, Object>> getUsers(UserQueryDTO queryDTO) {
+        PageResponse<UserDTO> users = userService.queryUsers(queryDTO);
+        Map<String, Object> response = new HashMap<>();
+        response.put("success", true);
+        response.put("data", users);
+        return new ResponseEntity<>(response, HttpStatus.OK);
+    }
+    
+    /**
+     * 获取所有状态列表
+     *
+     * @return 状态列表
+     */
+    @GetMapping("/record-status")
+    @RequirePermission("010102")
+    public ResponseEntity<List<SysDictionaryItem>> getRecordStatus() {
+        List<SysDictionaryItem> recordStatusList = userService.getRecordStatusList();
+        return new ResponseEntity<>(recordStatusList, HttpStatus.OK);
+    }
+    
+    /**
+     * 保存用户(新增或修改)
+     *
+     * @param userSaveDTO 用户保存DTO
+     * @return 保存后的用户DTO
+     */
+    @PostMapping("/save")
+    @RequirePermission("0104")
+    public ResponseEntity<Map<String, Object>> saveUser(@Valid @RequestBody UserSaveDTO userSaveDTO) {
+        try {
+            UserDTO userDTO = userService.saveUser(userSaveDTO);
+            Map<String, Object> response = new HashMap<>();
+            response.put("success", true);
+            response.put("message", userSaveDTO.getId() == null || userSaveDTO.getId().isEmpty() ? "用户创建成功" : "用户更新成功");
+            response.put("data", userDTO);
+            return new ResponseEntity<>(response, HttpStatus.OK);
+        } catch (Exception e) {
+            Map<String, Object> response = new HashMap<>();
+            response.put("success", false);
+            response.put("message", userSaveDTO.getId() == null || userSaveDTO.getId().isEmpty() ? "用户创建失败:" + e.getMessage() : "用户更新失败:" + e.getMessage());
+            return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
+        }
+    }
+    
+    /**
+     * 删除用户
+     *
+     * @param userIds 用户ID列表
+     * @return 操作结果
+     */
+    @DeleteMapping("/delete")
+    @RequirePermission("0102")
+    public ResponseEntity<Map<String, Object>> deleteUsers(@RequestBody List<String> userIds) {
+        if (userIds == null || userIds.isEmpty()) {
+            Map<String, Object> response = new HashMap<>();
+            response.put("success", false);
+            response.put("message", "用户ID不能为空");
+            return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
+        }
+        
+        try {
+            userService.deleteUsers(userIds);
+            Map<String, Object> response = new HashMap<>();
+            response.put("success", true);
+            response.put("message", "删除成功");
+            response.put("deletedCount", userIds.size());
+            return new ResponseEntity<>(response, HttpStatus.OK);
+        } catch (Exception e) {
+            Map<String, Object> response = new HashMap<>();
+            response.put("success", false);
+            response.put("message", "删除失败:" + e.getMessage());
+            return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
+        }
+    }
+    
+    /**
+     * 重置用户密码
+     *
+     * @param userId 用户ID
+     * @param newPassword 新密码
+     * @return 操作结果
+     */
+    @PostMapping("/reset-password")
+    @RequirePermission("0103")
+    public ResponseEntity<Map<String, Object>> resetPassword(@RequestBody Map<String, String> request) {
+        String userId = request.get("userId");
+        String newPassword = request.get("newPassword");
+        
+        if (userId == null || userId.isEmpty()) {
+            Map<String, Object> response = new HashMap<>();
+            response.put("success", false);
+            response.put("message", "用户ID不能为空");
+            return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
+        }
+        
+        if (newPassword == null || newPassword.isEmpty()) {
+            Map<String, Object> response = new HashMap<>();
+            response.put("success", false);
+            response.put("message", "新密码不能为空");
+            return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
+        }
+        
+        try {
+            userService.resetPassword(userId, newPassword);
+            Map<String, Object> response = new HashMap<>();
+            response.put("success", true);
+            response.put("message", "密码重置成功");
+            return new ResponseEntity<>(response, HttpStatus.OK);
+        } catch (Exception e) {
+            Map<String, Object> response = new HashMap<>();
+            response.put("success", false);
+            response.put("message", "密码重置失败:" + e.getMessage());
+            return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
+        }
+    }
+}

+ 19 - 0
JavaBackend/src/main/java/com/lianda/backend/dto/UserCreateDTO.java

@@ -0,0 +1,19 @@
+package com.lianda.backend.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+@Data
+public class UserCreateDTO {
+    
+    @NotBlank(message = "账号不能为空")
+    private String loginId;
+    
+    @NotBlank(message = "用户名不能为空")
+    private String name;
+    
+    @NotNull(message = "状态不能为空")
+    private Integer recordStatus;
+}

+ 63 - 0
JavaBackend/src/main/java/com/lianda/backend/dto/UserDTO.java

@@ -0,0 +1,63 @@
+package com.lianda.backend.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.lianda.backend.model.User;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class UserDTO {
+    
+    private String id;
+    
+    private String employeeId;
+    
+    private String loginId;
+    
+    private String name;
+    
+    private String roleName;
+    
+    private String weChatUserId;
+    
+    private Integer recordStatus;
+    
+    private String recordStatusName;
+    
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createTime;
+    
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date modifyTime;
+    
+    public static UserDTO fromEntity(User user, String roleName, String recordStatusName) {
+        UserDTO dto = new UserDTO();
+        dto.setId(user.getId());
+        dto.setEmployeeId("");
+        dto.setLoginId(user.getLoginId());
+        dto.setName(user.getName());
+        dto.setRoleName(roleName);
+        dto.setWeChatUserId(user.getWechatUserId() != null ? user.getWechatUserId() : "");
+        dto.setRecordStatus(user.getRecordStatus());
+        dto.setRecordStatusName(recordStatusName);
+        dto.setCreateTime(user.getCreateTime());
+        dto.setModifyTime(user.getModifyTime());
+        return dto;
+    }
+    
+    public static UserDTO fromEntity(User user, String roleName, String recordStatusName, String employeeId) {
+        UserDTO dto = new UserDTO();
+        dto.setId(user.getId());
+        dto.setEmployeeId(employeeId != null ? employeeId : "");
+        dto.setLoginId(user.getLoginId());
+        dto.setName(user.getName());
+        dto.setRoleName(roleName);
+        dto.setWeChatUserId(user.getWechatUserId() != null ? user.getWechatUserId() : "");
+        dto.setRecordStatus(user.getRecordStatus());
+        dto.setRecordStatusName(recordStatusName);
+        dto.setCreateTime(user.getCreateTime());
+        dto.setModifyTime(user.getModifyTime());
+        return dto;
+    }
+}

+ 18 - 0
JavaBackend/src/main/java/com/lianda/backend/dto/UserQueryDTO.java

@@ -0,0 +1,18 @@
+package com.lianda.backend.dto;
+
+import com.lianda.backend.dto.BaseQueryDTO;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class UserQueryDTO extends BaseQueryDTO {
+    
+    private String loginId;
+    
+    private String name;
+    
+    private String roleName;
+    
+    private Integer recordStatus;
+}

+ 23 - 0
JavaBackend/src/main/java/com/lianda/backend/dto/UserSaveDTO.java

@@ -0,0 +1,23 @@
+package com.lianda.backend.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+@Data
+public class UserSaveDTO {
+    
+    private String id;
+    
+    private String employeeId;
+    
+    @NotBlank(message = "账号不能为空")
+    private String loginId;
+    
+    @NotBlank(message = "用户名不能为空")
+    private String name;
+    
+    @NotNull(message = "状态不能为空")
+    private Integer recordStatus;
+}

+ 22 - 0
JavaBackend/src/main/java/com/lianda/backend/dto/UserUpdateDTO.java

@@ -0,0 +1,22 @@
+package com.lianda.backend.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+@Data
+public class UserUpdateDTO {
+    
+    @NotBlank(message = "用户ID不能为空")
+    private String id;
+    
+    @NotBlank(message = "账号不能为空")
+    private String loginId;
+    
+    @NotBlank(message = "用户名不能为空")
+    private String name;
+    
+    @NotNull(message = "状态不能为空")
+    private Integer recordStatus;
+}

+ 42 - 0
JavaBackend/src/main/java/com/lianda/backend/model/SalEmployee.java

@@ -0,0 +1,42 @@
+package com.lianda.backend.model;
+
+import javax.persistence.*;
+import java.util.Date;
+
+@Entity
+@Table(name = "Sal_Employee")
+public class SalEmployee {
+    @Id
+    @Column(name = "EmployeeID", length = 36)
+    private String employeeId;
+    
+    @Column(name = "UserID", length = 36)
+    private String userId;
+    
+    @Column(name = "Name", length = 30)
+    private String name;
+
+    public String getEmployeeId() {
+        return employeeId;
+    }
+
+    public void setEmployeeId(String employeeId) {
+        this.employeeId = employeeId;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+}

+ 108 - 0
JavaBackend/src/main/java/com/lianda/backend/model/SysRole.java

@@ -0,0 +1,108 @@
+package com.lianda.backend.model;
+
+import javax.persistence.*;
+import java.util.Date;
+
+@Entity
+@Table(name = "Sys_Role")
+public class SysRole {
+    @Id
+    @Column(name = "RoleID", length = 36)
+    private String roleId;
+    
+    @Column(name = "RoleName", nullable = false, length = 100)
+    private String roleName;
+    
+    @Column(name = "Description", length = 500)
+    private String description;
+    
+    @Column(name = "IsSystemRole", nullable = false)
+    private Boolean isSystemRole;
+    
+    @Column(name = "RecordStatus")
+    private Integer recordStatus;
+    
+    @Column(name = "CreateUserID")
+    private String createUserId;
+    
+    @Column(name = "CreateTime")
+    private Date createTime;
+    
+    @Column(name = "ModifyUserID")
+    private String modifyUserId;
+    
+    @Column(name = "ModifyTime")
+    private Date modifyTime;
+
+    public String getRoleId() {
+        return roleId;
+    }
+
+    public void setRoleId(String roleId) {
+        this.roleId = roleId;
+    }
+
+    public String getRoleName() {
+        return roleName;
+    }
+
+    public void setRoleName(String roleName) {
+        this.roleName = roleName;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public Boolean getIsSystemRole() {
+        return isSystemRole;
+    }
+
+    public void setIsSystemRole(Boolean isSystemRole) {
+        this.isSystemRole = isSystemRole;
+    }
+
+    public Integer getRecordStatus() {
+        return recordStatus;
+    }
+
+    public void setRecordStatus(Integer recordStatus) {
+        this.recordStatus = recordStatus;
+    }
+
+    public String getCreateUserId() {
+        return createUserId;
+    }
+
+    public void setCreateUserId(String createUserId) {
+        this.createUserId = createUserId;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public String getModifyUserId() {
+        return modifyUserId;
+    }
+
+    public void setModifyUserId(String modifyUserId) {
+        this.modifyUserId = modifyUserId;
+    }
+
+    public Date getModifyTime() {
+        return modifyTime;
+    }
+
+    public void setModifyTime(Date modifyTime) {
+        this.modifyTime = modifyTime;
+    }
+}

+ 33 - 0
JavaBackend/src/main/java/com/lianda/backend/model/SysUserRole.java

@@ -0,0 +1,33 @@
+package com.lianda.backend.model;
+
+import javax.persistence.*;
+import java.io.Serializable;
+
+@Entity
+@Table(name = "Sys_UserRole")
+@IdClass(SysUserRoleId.class)
+public class SysUserRole implements Serializable {
+    @Id
+    @Column(name = "UserID", length = 36)
+    private String userId;
+
+    @Id
+    @Column(name = "RoleID", length = 36)
+    private String roleId;
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getRoleId() {
+        return roleId;
+    }
+
+    public void setRoleId(String roleId) {
+        this.roleId = roleId;
+    }
+}

+ 45 - 0
JavaBackend/src/main/java/com/lianda/backend/model/SysUserRoleId.java

@@ -0,0 +1,45 @@
+package com.lianda.backend.model;
+
+import java.io.Serializable;
+
+public class SysUserRoleId implements Serializable {
+    private String userId;
+    private String roleId;
+
+    public SysUserRoleId() {
+    }
+
+    public SysUserRoleId(String userId, String roleId) {
+        this.userId = userId;
+        this.roleId = roleId;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getRoleId() {
+        return roleId;
+    }
+
+    public void setRoleId(String roleId) {
+        this.roleId = roleId;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        SysUserRoleId that = (SysUserRoleId) o;
+        return userId.equals(that.userId) && roleId.equals(that.roleId);
+    }
+
+    @Override
+    public int hashCode() {
+        return java.util.Objects.hash(userId, roleId);
+    }
+}

+ 11 - 0
JavaBackend/src/main/java/com/lianda/backend/model/User.java

@@ -19,6 +19,9 @@ public class User {
     @Column(name = "Name")
     private String name;
     
+    @Column(name = "WechatUserId", length = 50)
+    private String wechatUserId;
+    
     @Column(name = "RecordStatus")
     private Integer recordStatus;
     
@@ -66,6 +69,14 @@ public class User {
         this.name = name;
     }
 
+    public String getWechatUserId() {
+        return wechatUserId;
+    }
+
+    public void setWechatUserId(String wechatUserId) {
+        this.wechatUserId = wechatUserId;
+    }
+
     public Integer getRecordStatus() {
         return recordStatus;
     }

+ 17 - 0
JavaBackend/src/main/java/com/lianda/backend/repository/SalEmployeeRepository.java

@@ -0,0 +1,17 @@
+package com.lianda.backend.repository;
+
+import com.lianda.backend.model.SalEmployee;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface SalEmployeeRepository extends JpaRepository<SalEmployee, String> {
+    
+    /**
+     * 根据用户ID查询员工
+     *
+     * @param userId 用户ID
+     * @return 员工信息
+     */
+    SalEmployee findByUserId(String userId);
+}

+ 9 - 0
JavaBackend/src/main/java/com/lianda/backend/repository/SysRoleRepository.java

@@ -0,0 +1,9 @@
+package com.lianda.backend.repository;
+
+import com.lianda.backend.model.SysRole;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface SysRoleRepository extends JpaRepository<SysRole, String> {
+}

+ 19 - 0
JavaBackend/src/main/java/com/lianda/backend/repository/SysUserRoleRepository.java

@@ -0,0 +1,19 @@
+package com.lianda.backend.repository;
+
+import com.lianda.backend.model.SysUserRole;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface SysUserRoleRepository extends JpaRepository<SysUserRole, String> {
+    
+    @Query(value = "SELECT * FROM Sys_UserRole WHERE UserID = :userId", nativeQuery = true)
+    List<SysUserRole> findByUserId(@Param("userId") String userId);
+    
+    @Query(value = "DELETE FROM Sys_UserRole WHERE UserID = :userId", nativeQuery = true)
+    void deleteByUserId(@Param("userId") String userId);
+}

+ 33 - 0
JavaBackend/src/main/java/com/lianda/backend/repository/UserRepository.java

@@ -1,6 +1,8 @@
 package com.lianda.backend.repository;
 
 import com.lianda.backend.model.User;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.Query;
 import org.springframework.data.repository.query.Param;
@@ -8,6 +10,37 @@ import org.springframework.stereotype.Repository;
 
 @Repository
 public interface UserRepository extends JpaRepository<User, String> {
+    
     @Query(value = "SELECT * FROM Sys_User WHERE LoginID = :loginId", nativeQuery = true)
     User findByLoginId(@Param("loginId") String loginId);
+    
+    @Query(value = "SELECT * FROM Sys_User WHERE LoginID = :loginID", nativeQuery = true)
+    User findByLoginID(@Param("loginID") String loginID);
+    
+    @Query(value = "SELECT * FROM Sys_User WHERE Name = :name", nativeQuery = true)
+    User findByName(@Param("name") String name);
+    
+    @Query(value = "SELECT * FROM Sys_User WHERE Name LIKE CONCAT('%', :name, '%')", nativeQuery = true)
+    Page<User> findByNameLike(@Param("name") String name, Pageable pageable);
+    
+    @Query(value = "SELECT * FROM Sys_User WHERE Name LIKE CONCAT('%', :name, '%') AND RecordStatus = :recordStatus", nativeQuery = true)
+    Page<User> findByNameLikeAndRecordStatus(@Param("name") String name, @Param("recordStatus") Integer recordStatus, Pageable pageable);
+    
+    @Query(value = "SELECT * FROM Sys_User WHERE Name LIKE CONCAT('%', :name, '%') AND LoginID LIKE CONCAT('%', :loginID, '%')", nativeQuery = true)
+    Page<User> findByNameLikeAndLoginIDLike(@Param("name") String name, @Param("loginID") String loginID, Pageable pageable);
+    
+    @Query(value = "SELECT * FROM Sys_User WHERE Name LIKE CONCAT('%', :name, '%') AND LoginID LIKE CONCAT('%', :loginID, '%') AND RecordStatus = :recordStatus", nativeQuery = true)
+    Page<User> findByNameLikeAndLoginIDLikeAndRecordStatus(@Param("name") String name, @Param("loginID") String loginID, @Param("recordStatus") Integer recordStatus, Pageable pageable);
+    
+    // @Query(value = "SELECT * FROM Sys_User WHERE EmployeeID = :employeeID", nativeQuery = true)
+    // User findByEmployeeID(@Param("employeeID") String employeeID);
+    
+    @Query(value = "SELECT * FROM Sys_User WHERE LoginID LIKE CONCAT('%', :loginID, '%')", nativeQuery = true)
+    Page<User> findByLoginIDLike(@Param("loginID") String loginID, Pageable pageable);
+    
+    @Query(value = "SELECT * FROM Sys_User WHERE RecordStatus = :recordStatus", nativeQuery = true)
+    Page<User> findByRecordStatus(@Param("recordStatus") Integer recordStatus, Pageable pageable);
+    
+    @Query(value = "SELECT * FROM Sys_User WHERE LoginID LIKE CONCAT('%', :loginID, '%') AND RecordStatus = :recordStatus", nativeQuery = true)
+    Page<User> findByLoginIDLikeAndRecordStatus(@Param("loginID") String loginID, @Param("recordStatus") Integer recordStatus, Pageable pageable);
 }

+ 419 - 0
JavaBackend/src/main/java/com/lianda/backend/service/UserService.java

@@ -0,0 +1,419 @@
+package com.lianda.backend.service;
+
+import com.lianda.backend.config.DataSource;
+import com.lianda.backend.config.RoutingDataSourceConfig;
+import com.lianda.backend.dto.PageResponse;
+import com.lianda.backend.dto.UserCreateDTO;
+import com.lianda.backend.dto.UserDTO;
+import com.lianda.backend.dto.UserQueryDTO;
+import com.lianda.backend.dto.UserSaveDTO;
+import com.lianda.backend.model.SalEmployee;
+import com.lianda.backend.model.SysDictionaryItem;
+import com.lianda.backend.model.SysRole;
+import com.lianda.backend.model.SysUserRole;
+import com.lianda.backend.model.User;
+import com.lianda.backend.repository.SalEmployeeRepository;
+import com.lianda.backend.repository.SysDictionaryItemRepository;
+import com.lianda.backend.repository.SysRoleRepository;
+import com.lianda.backend.repository.SysUserRoleRepository;
+import com.lianda.backend.repository.UserRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.security.MessageDigest;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Service
+@DataSource(value = RoutingDataSourceConfig.DataSourceType.COMMON)
+public class UserService {
+
+    @Autowired
+    private UserRepository userRepository;
+
+    @Autowired
+    private SysDictionaryItemRepository sysDictionaryItemRepository;
+
+    @Autowired
+    private SysRoleRepository sysRoleRepository;
+
+    @Autowired
+    private SysUserRoleRepository sysUserRoleRepository;
+
+    @Autowired
+    @Qualifier("readJdbcTemplate")
+    private JdbcTemplate readJdbcTemplate;
+
+    @Autowired
+    @Qualifier("writeJdbcTemplate")
+    private JdbcTemplate writeJdbcTemplate;
+
+    @Autowired
+    private SalEmployeeRepository salEmployeeRepository;
+
+    @Autowired
+    private BCryptPasswordEncoder passwordEncoder;
+
+    /**
+     * 查询用户列表
+     *
+     * @param queryDTO 查询条件
+     * @return 分页结果
+     */
+    public PageResponse<UserDTO> queryUsers(UserQueryDTO queryDTO) {
+        Pageable pageable = PageRequest.of(queryDTO.getPage() - 1, queryDTO.getPageSize());
+        
+        String loginId = (queryDTO.getLoginId() == null || queryDTO.getLoginId().isEmpty()) ? null : queryDTO.getLoginId();
+        String name = (queryDTO.getName() == null || queryDTO.getName().isEmpty()) ? null : queryDTO.getName();
+        Integer recordStatus = queryDTO.getRecordStatus();
+        
+        Page<User> userPage;
+        
+        if (name != null && loginId != null && recordStatus != null) {
+            userPage = userRepository.findByNameLikeAndLoginIDLikeAndRecordStatus("%" + name + "%", "%" + loginId + "%", recordStatus, pageable);
+        } else if (name != null && loginId != null) {
+            userPage = userRepository.findByNameLikeAndLoginIDLike("%" + name + "%", "%" + loginId + "%", pageable);
+        } else if (name != null && recordStatus != null) {
+            userPage = userRepository.findByNameLikeAndRecordStatus("%" + name + "%", recordStatus, pageable);
+        } else if (loginId != null && recordStatus != null) {
+            userPage = userRepository.findByLoginIDLikeAndRecordStatus("%" + loginId + "%", recordStatus, pageable);
+        } else if (name != null) {
+            userPage = userRepository.findByNameLike("%" + name + "%", pageable);
+        } else if (loginId != null) {
+            userPage = userRepository.findByLoginIDLike("%" + loginId + "%", pageable);
+        } else if (recordStatus != null) {
+            userPage = userRepository.findByRecordStatus(recordStatus, pageable);
+        } else {
+            userPage = userRepository.findAll(pageable);
+        }
+        
+        List<UserDTO> userDTOList = userPage.getContent().stream()
+                .map(user -> {
+                    String userRecordStatusName = getRecordStatusName(user.getRecordStatus());
+                    String userRoleName = getUserRoleName(user.getId());
+                    SalEmployee employee = salEmployeeRepository.findByUserId(user.getId());
+                    String employeeId = employee != null ? employee.getEmployeeId() : null;
+                    return UserDTO.fromEntity(user, userRoleName, userRecordStatusName, employeeId);
+                })
+                .collect(Collectors.toList());
+        
+        return new PageResponse<>(userDTOList, userPage.getTotalElements(), queryDTO.getPage(), queryDTO.getPageSize());
+    }
+
+    /**
+     * 获取状态名称
+     *
+     * @param recordStatus 状态值
+     * @return 状态名称
+     */
+    private String getRecordStatusName(Integer recordStatus) {
+        if (recordStatus == null) {
+            return "";
+        }
+        
+        List<SysDictionaryItem> items = sysDictionaryItemRepository.findByDictionaryCode("RecordStatus");
+        Optional<SysDictionaryItem> item = items.stream()
+                .filter(dict -> dict.getValue().equals(recordStatus))
+                .findFirst();
+        
+        return item.map(SysDictionaryItem::getName).orElse("");
+    }
+
+    /**
+     * 获取用户角色名称
+     *
+     * @param userId 用户ID
+     * @return 角色名称(多个角色用逗号分隔)
+     */
+    private String getUserRoleName(String userId) {
+        try {
+            String sql = "SELECT r.RoleName FROM Sys_User_Sys_Role ur " +
+                       "INNER JOIN Sys_Role r ON ur.RoleID = r.RoleID " +
+                       "WHERE ur.UserID = ?";
+            
+            List<String> roleNames = readJdbcTemplate.queryForList(sql, String.class, userId);
+            
+            System.out.println("用户ID: " + userId + ", 查询到的角色数量: " + (roleNames != null ? roleNames.size() : 0));
+            if (roleNames != null && !roleNames.isEmpty()) {
+                System.out.println("角色名称: " + roleNames);
+            }
+            
+            if (roleNames == null || roleNames.isEmpty()) {
+                return "";
+            }
+            
+            return String.join(", ", roleNames);
+        } catch (Exception e) {
+            System.err.println("获取用户角色失败: " + e.getMessage());
+            e.printStackTrace();
+            return "";
+        }
+    }
+
+    /**
+     * 删除用户
+     *
+     * @param userIds 用户ID列表
+     */
+    @Transactional
+    public void deleteUsers(List<String> userIds) {
+        for (String userId : userIds) {
+            // 先删除用户角色关联(从LiandaTugboatMIS库)
+            try {
+                String deleteRoleSql = "DELETE FROM Sys_User_Sys_Role WHERE UserID = ?";
+                readJdbcTemplate.update(deleteRoleSql, userId);
+            } catch (Exception e) {
+                System.err.println("删除用户角色关联失败: " + e.getMessage());
+                e.printStackTrace();
+            }
+            
+            // 再删除用户(从TugboatCommon库)
+            userRepository.deleteById(userId);
+        }
+    }
+
+    /**
+     * 重置用户密码
+     *
+     * @param userId 用户ID
+     * @param newPassword 新密码
+     */
+    @Transactional
+    public void resetPassword(String userId, String newPassword) {
+        User user = userRepository.findById(userId).orElse(null);
+        if (user != null) {
+            String md5Password = md5(newPassword);
+            user.setPassword(md5Password);
+            userRepository.save(user);
+        }
+    }
+
+    /**
+     * MD5加密
+     *
+     * @param str 原字符串
+     * @return MD5加密后的字符串
+     */
+    private String md5(String str) {
+        try {
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            md.update(str.getBytes());
+            byte[] digest = md.digest();
+            StringBuilder sb = new StringBuilder();
+            for (byte b : digest) {
+                sb.append(String.format("%02x", b & 0xff));
+            }
+            return sb.toString();
+        } catch (Exception e) {
+            throw new RuntimeException("MD5加密失败", e);
+        }
+    }
+
+    /**
+     * 获取所有状态列表
+     *
+     * @return 状态列表
+     */
+    public List<SysDictionaryItem> getRecordStatusList() {
+        return sysDictionaryItemRepository.findByDictionaryCode("RecordStatus");
+    }
+
+    /**
+     * 新增用户
+     *
+     * @param userCreateDTO 用户创建DTO
+     * @return 创建的用户DTO
+     */
+    @Transactional
+    public UserDTO createUser(UserCreateDTO userCreateDTO) {
+        // 重复校验:检查LoginID是否重复
+        if (userRepository.findByLoginID(userCreateDTO.getLoginId()) != null) {
+            throw new RuntimeException("账号已存在,请使用其他账号");
+        }
+        
+        // 重复校验:检查Name是否重复
+        if (userRepository.findByName(userCreateDTO.getName()) != null) {
+            throw new RuntimeException("用户名已存在,请使用其他用户名");
+        }
+        
+        User user = new User();
+        user.setId(UUID.randomUUID().toString());
+        user.setLoginId(userCreateDTO.getLoginId());
+        user.setName(userCreateDTO.getName());
+        user.setPassword(md5("TIMS123456"));
+        user.setRecordStatus(userCreateDTO.getRecordStatus());
+        
+        // 设置公共字段
+        String currentUserId = getCurrentUserId();
+        user.setCreateUserID(currentUserId);
+        user.setCreateTime(new Date());
+        user.setModifyUserID(currentUserId);
+        user.setModifyTime(new Date());
+        
+        user = userRepository.save(user);
+        
+        String recordStatusName = getRecordStatusName(user.getRecordStatus());
+        return UserDTO.fromEntity(user, "", recordStatusName);
+    }
+
+    /**
+     * 保存用户(新增或修改)
+     *
+     * @param userSaveDTO 用户保存DTO
+     * @return 保存后的用户DTO
+     */
+    @Transactional
+    public UserDTO saveUser(UserSaveDTO userSaveDTO) {
+        // 判断是新增还是修改
+        if (userSaveDTO.getId() == null || userSaveDTO.getId().isEmpty()) {
+            // 新增用户
+            return createUserInternal(userSaveDTO);
+        } else {
+            // 修改用户
+            return updateUserInternal(userSaveDTO);
+        }
+    }
+
+    /**
+     * 内部方法:创建用户
+     *
+     * @param userSaveDTO 用户保存DTO
+     * @return 创建的用户DTO
+     */
+    private UserDTO createUserInternal(UserSaveDTO userSaveDTO) {
+        // 重复校验:检查LoginID是否重复
+        if (userRepository.findByLoginID(userSaveDTO.getLoginId()) != null) {
+            throw new RuntimeException("账号已存在,请使用其他账号");
+        }
+        
+        // 重复校验:检查Name是否重复
+        if (userRepository.findByName(userSaveDTO.getName()) != null) {
+            throw new RuntimeException("用户名已存在,请使用其他用户名");
+        }
+        
+        // 重复校验:检查关联用户是否已被其他用户关联
+        if (userSaveDTO.getEmployeeId() != null && !userSaveDTO.getEmployeeId().isEmpty()) {
+            SalEmployee employee = salEmployeeRepository.findById(userSaveDTO.getEmployeeId()).orElse(null);
+            if (employee != null && employee.getUserId() != null && !employee.getUserId().isEmpty()) {
+                throw new RuntimeException("该员工已被其他用户关联,请选择其他员工");
+            }
+        }
+        
+        User user = new User();
+        user.setId(UUID.randomUUID().toString());
+        user.setLoginId(userSaveDTO.getLoginId());
+        user.setName(userSaveDTO.getName());
+        user.setPassword(md5("TIMS123456"));
+        user.setRecordStatus(userSaveDTO.getRecordStatus());
+        
+        // 设置公共字段
+        String currentUserId = getCurrentUserId();
+        user.setCreateUserID(currentUserId);
+        user.setCreateTime(new Date());
+        user.setModifyUserID(currentUserId);
+        user.setModifyTime(new Date());
+        
+        user = userRepository.save(user);
+        
+        // 如果选择了关联员工,更新员工的UserID
+        if (userSaveDTO.getEmployeeId() != null && !userSaveDTO.getEmployeeId().isEmpty()) {
+            SalEmployee employee = salEmployeeRepository.findById(userSaveDTO.getEmployeeId()).orElse(null);
+            if (employee != null) {
+                employee.setUserId(user.getId());
+                salEmployeeRepository.save(employee);
+            }
+        }
+        
+        String recordStatusName = getRecordStatusName(user.getRecordStatus());
+        return UserDTO.fromEntity(user, "", recordStatusName);
+    }
+
+    /**
+     * 内部方法:更新用户
+     *
+     * @param userSaveDTO 用户保存DTO
+     * @return 更新后的用户DTO
+     */
+    private UserDTO updateUserInternal(UserSaveDTO userSaveDTO) {
+        User user = userRepository.findById(userSaveDTO.getId()).orElseThrow(() -> 
+            new RuntimeException("用户不存在"));
+        
+        // 重复校验:检查LoginID是否与其他用户重复
+        User existingUser = userRepository.findByLoginID(userSaveDTO.getLoginId());
+        if (existingUser != null && !existingUser.getId().equals(userSaveDTO.getId())) {
+            throw new RuntimeException("账号已存在,请使用其他账号");
+        }
+        
+        // 重复校验:检查Name是否与其他用户重复
+        existingUser = userRepository.findByName(userSaveDTO.getName());
+        if (existingUser != null && !existingUser.getId().equals(userSaveDTO.getId())) {
+            throw new RuntimeException("用户名已存在,请使用其他用户名");
+        }
+        
+        // 重复校验:检查关联用户是否已被其他用户关联
+        if (userSaveDTO.getEmployeeId() != null && !userSaveDTO.getEmployeeId().isEmpty()) {
+            SalEmployee employee = salEmployeeRepository.findById(userSaveDTO.getEmployeeId()).orElse(null);
+            if (employee != null && employee.getUserId() != null && !employee.getUserId().isEmpty() && !employee.getUserId().equals(userSaveDTO.getId())) {
+                throw new RuntimeException("该员工已被其他用户关联,请选择其他员工");
+            }
+        }
+        
+        // 如果之前有关联员工,需要清除该员工的UserID
+        SalEmployee oldEmployee = salEmployeeRepository.findByUserId(userSaveDTO.getId());
+        if (oldEmployee != null && (userSaveDTO.getEmployeeId() == null || userSaveDTO.getEmployeeId().isEmpty() || !oldEmployee.getEmployeeId().equals(userSaveDTO.getEmployeeId()))) {
+            oldEmployee.setUserId(null);
+            salEmployeeRepository.save(oldEmployee);
+        }
+        
+        user.setLoginId(userSaveDTO.getLoginId());
+        user.setName(userSaveDTO.getName());
+        user.setRecordStatus(userSaveDTO.getRecordStatus());
+        
+        // 更新公共字段
+        String currentUserId = getCurrentUserId();
+        user.setModifyUserID(currentUserId);
+        user.setModifyTime(new Date());
+        
+        user = userRepository.save(user);
+        
+        // 如果选择了关联员工,更新员工的UserID
+        if (userSaveDTO.getEmployeeId() != null && !userSaveDTO.getEmployeeId().isEmpty()) {
+            SalEmployee employee = salEmployeeRepository.findById(userSaveDTO.getEmployeeId()).orElse(null);
+            if (employee != null) {
+                employee.setUserId(user.getId());
+                salEmployeeRepository.save(employee);
+            }
+        }
+        
+        String roleName = getUserRoleName(user.getId());
+        String recordStatusName = getRecordStatusName(user.getRecordStatus());
+        SalEmployee employee = salEmployeeRepository.findByUserId(user.getId());
+        String employeeId = employee != null ? employee.getEmployeeId() : null;
+        return UserDTO.fromEntity(user, roleName, recordStatusName, employeeId);
+    }
+
+    /**
+     * 获取当前登录用户ID
+     *
+     * @return 当前登录用户ID
+     */
+    private String getCurrentUserId() {
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        if (authentication != null && authentication.getPrincipal() instanceof org.springframework.security.core.userdetails.User) {
+            org.springframework.security.core.userdetails.User userDetails = 
+                (org.springframework.security.core.userdetails.User) authentication.getPrincipal();
+            return userDetails.getUsername();
+        }
+        return "admin";
+    }
+}

+ 99 - 0
JavaBackend/src/main/java/com/lianda/backend/test/UserMenuUrlUpdater.java

@@ -0,0 +1,99 @@
+package com.lianda.backend.test;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class UserMenuUrlUpdater {
+    // 数据库连接信息
+    private static final String URL = "jdbc:mysql://192.168.0.77:3306/LiandaTugboatMIS?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8";
+    private static final String USER = "root";
+    private static final String PASSWORD = "bowin@2023";
+    
+    public static void main(String[] args) {
+        Connection connection = null;
+        PreparedStatement preparedStatement = null;
+        ResultSet resultSet = null;
+        
+        try {
+            // 加载JDBC驱动
+            Class.forName("com.mysql.cj.jdbc.Driver");
+            
+            // 建立数据库连接
+            connection = DriverManager.getConnection(URL, USER, PASSWORD);
+            System.out.println("数据库连接成功!");
+            
+            // 1. 查询用户管理菜单的当前状态
+            String querySql = "SELECT MenuNo, MenuName, ParentMenuNo, Url FROM Sys_Menu WHERE MenuName = '用户管理'";
+            preparedStatement = connection.prepareStatement(querySql);
+            resultSet = preparedStatement.executeQuery();
+            
+            System.out.println("\n当前用户管理菜单的数据:");
+            System.out.println("MenuNo\tMenuName\tParentMenuNo\tUrl");
+            System.out.println("----------------------------------------");
+            
+            if (resultSet.next()) {
+                String menuNo = resultSet.getString("MenuNo");
+                String menuName = resultSet.getString("MenuName");
+                String parentMenuNo = resultSet.getString("ParentMenuNo");
+                String url = resultSet.getString("Url");
+                
+                System.out.println(menuNo + "\t" + menuName + "\t" + parentMenuNo + "\t" + url);
+            } else {
+                System.out.println("未找到用户管理菜单");
+            }
+            
+            // 关闭ResultSet和PreparedStatement
+            if (resultSet != null) resultSet.close();
+            if (preparedStatement != null) preparedStatement.close();
+            
+            // 2. 更新用户管理菜单的Url字段为'user'
+            String updateSql = "UPDATE Sys_Menu SET Url = 'user' WHERE MenuName = '用户管理'";
+            preparedStatement = connection.prepareStatement(updateSql);
+            
+            int updateResult = preparedStatement.executeUpdate();
+            System.out.println("\n更新用户管理菜单的Url:" + (updateResult > 0 ? "成功" : "失败"));
+            
+            // 关闭PreparedStatement
+            if (preparedStatement != null) preparedStatement.close();
+            
+            // 3. 再次查询用户管理菜单,确认修改后的状态
+            String finalQuerySql = "SELECT MenuNo, MenuName, ParentMenuNo, Url FROM Sys_Menu WHERE MenuName = '用户管理'";
+            preparedStatement = connection.prepareStatement(finalQuerySql);
+            resultSet = preparedStatement.executeQuery();
+            
+            System.out.println("\n修改后的用户管理菜单数据:");
+            System.out.println("MenuNo\tMenuName\tParentMenuNo\tUrl");
+            System.out.println("----------------------------------------");
+            
+            if (resultSet.next()) {
+                String menuNo = resultSet.getString("MenuNo");
+                String menuName = resultSet.getString("MenuName");
+                String parentMenuNo = resultSet.getString("ParentMenuNo");
+                String url = resultSet.getString("Url");
+                
+                System.out.println(menuNo + "\t" + menuName + "\t" + parentMenuNo + "\t" + url);
+            }
+            
+        } catch (ClassNotFoundException e) {
+            System.out.println("数据库驱动加载失败:" + e.getMessage());
+            e.printStackTrace();
+        } catch (SQLException e) {
+            System.out.println("数据库操作失败:" + e.getMessage());
+            e.printStackTrace();
+        } finally {
+            // 关闭资源
+            try {
+                if (resultSet != null) resultSet.close();
+                if (preparedStatement != null) preparedStatement.close();
+                if (connection != null) connection.close();
+                System.out.println("\n数据库连接已关闭");
+            } catch (SQLException e) {
+                System.out.println("关闭资源失败:" + e.getMessage());
+                e.printStackTrace();
+            }
+        }
+    }
+}

BIN
JavaBackend/target/classes/com/lianda/backend/Application.class


BIN
JavaBackend/target/classes/com/lianda/backend/annotation/RequirePermission.class


BIN
JavaBackend/target/classes/com/lianda/backend/aspect/DataSourceAspect.class


BIN
JavaBackend/target/classes/com/lianda/backend/config/AppConfig.class


BIN
JavaBackend/target/classes/com/lianda/backend/config/DataSource.class


BIN
JavaBackend/target/classes/com/lianda/backend/config/DataSourceContextHolder.class


BIN
JavaBackend/target/classes/com/lianda/backend/config/JpaConfig.class


BIN
JavaBackend/target/classes/com/lianda/backend/config/MultiDataSourceConfig.class


BIN
JavaBackend/target/classes/com/lianda/backend/config/RoutingDataSourceConfig$DataSourceType.class


BIN
JavaBackend/target/classes/com/lianda/backend/config/RoutingDataSourceConfig$RoutingDataSource.class


BIN
JavaBackend/target/classes/com/lianda/backend/config/RoutingDataSourceConfig.class


BIN
JavaBackend/target/classes/com/lianda/backend/config/SecurityConfig.class


BIN
JavaBackend/target/classes/com/lianda/backend/config/WebConfig.class


BIN
JavaBackend/target/classes/com/lianda/backend/controller/AuthController.class


BIN
JavaBackend/target/classes/com/lianda/backend/controller/CustomerCompanyBusinessController$1.class


BIN
JavaBackend/target/classes/com/lianda/backend/controller/CustomerCompanyBusinessController$2.class


BIN
JavaBackend/target/classes/com/lianda/backend/controller/CustomerCompanyBusinessController.class


BIN
JavaBackend/target/classes/com/lianda/backend/controller/DebugController.class


BIN
JavaBackend/target/classes/com/lianda/backend/controller/EmployeeController.class


BIN
JavaBackend/target/classes/com/lianda/backend/controller/MenuController.class


BIN
JavaBackend/target/classes/com/lianda/backend/controller/PilotPlanController.class


BIN
JavaBackend/target/classes/com/lianda/backend/controller/PortController.class


BIN
JavaBackend/target/classes/com/lianda/backend/controller/ShipCompanyController.class


BIN
JavaBackend/target/classes/com/lianda/backend/controller/UserController.class


BIN
JavaBackend/target/classes/com/lianda/backend/dto/BaseQueryDTO.class


BIN
JavaBackend/target/classes/com/lianda/backend/dto/CustomerCompanyBusinessSearchResult.class


BIN
JavaBackend/target/classes/com/lianda/backend/dto/LoginRequest.class


BIN
JavaBackend/target/classes/com/lianda/backend/dto/PageResponse.class


BIN
JavaBackend/target/classes/com/lianda/backend/dto/PilotPlanDTO.class


BIN
JavaBackend/target/classes/com/lianda/backend/dto/PilotPlanDetailDTO.class


BIN
JavaBackend/target/classes/com/lianda/backend/dto/PilotPlanImportDTO.class


BIN
JavaBackend/target/classes/com/lianda/backend/dto/PilotPlanProjection.class


BIN
JavaBackend/target/classes/com/lianda/backend/dto/PilotPlanQueryDTO.class


BIN
JavaBackend/target/classes/com/lianda/backend/dto/PortDTO.class


BIN
JavaBackend/target/classes/com/lianda/backend/dto/ShippingCompanyDTO.class


BIN
JavaBackend/target/classes/com/lianda/backend/dto/UserCreateDTO.class


BIN
JavaBackend/target/classes/com/lianda/backend/dto/UserDTO.class


BIN
JavaBackend/target/classes/com/lianda/backend/dto/UserQueryDTO.class


BIN
JavaBackend/target/classes/com/lianda/backend/dto/UserSaveDTO.class


BIN
JavaBackend/target/classes/com/lianda/backend/dto/UserUpdateDTO.class


BIN
JavaBackend/target/classes/com/lianda/backend/interceptor/PermissionInterceptor.class


BIN
JavaBackend/target/classes/com/lianda/backend/model/BusCustomerCompany.class


BIN
JavaBackend/target/classes/com/lianda/backend/model/BusCustomerCompanyBusiness.class


BIN
JavaBackend/target/classes/com/lianda/backend/model/BusCustomerCustomerType.class


BIN
JavaBackend/target/classes/com/lianda/backend/model/BusCustomerCustomerTypeId.class


BIN
JavaBackend/target/classes/com/lianda/backend/model/BusPilotTypeSetting.class


BIN
JavaBackend/target/classes/com/lianda/backend/model/BusShip.class


BIN
JavaBackend/target/classes/com/lianda/backend/model/DispBerthage.class


BIN
JavaBackend/target/classes/com/lianda/backend/model/DispBerthageDictionary.class


BIN
JavaBackend/target/classes/com/lianda/backend/model/DispBerthageSetting.class


BIN
JavaBackend/target/classes/com/lianda/backend/model/DispDispatcher.class


BIN
JavaBackend/target/classes/com/lianda/backend/model/DispPilot.class


BIN
JavaBackend/target/classes/com/lianda/backend/model/DispPilotPlan.class


BIN
JavaBackend/target/classes/com/lianda/backend/model/DispPort.class


BIN
JavaBackend/target/classes/com/lianda/backend/model/DispPortDictionary.class


BIN
JavaBackend/target/classes/com/lianda/backend/model/DispWaterway.class


BIN
JavaBackend/target/classes/com/lianda/backend/model/PilotPlan.class


BIN
JavaBackend/target/classes/com/lianda/backend/model/SalEmployee.class


BIN
JavaBackend/target/classes/com/lianda/backend/model/SysDictionaryItem.class


BIN
JavaBackend/target/classes/com/lianda/backend/model/SysMenu.class


BIN
JavaBackend/target/classes/com/lianda/backend/model/SysRole.class


BIN
JavaBackend/target/classes/com/lianda/backend/model/SysUser.class


BIN
JavaBackend/target/classes/com/lianda/backend/model/SysUserRole.class


BIN
JavaBackend/target/classes/com/lianda/backend/model/SysUserRoleId.class


BIN
JavaBackend/target/classes/com/lianda/backend/model/User.class


BIN
JavaBackend/target/classes/com/lianda/backend/repository/BusCustomerCompanyBusinessRepository.class


BIN
JavaBackend/target/classes/com/lianda/backend/repository/BusCustomerCompanyBusinessWriteRepository.class


BIN
JavaBackend/target/classes/com/lianda/backend/repository/BusCustomerCompanyRepository.class


+ 0 - 0
JavaBackend/target/classes/com/lianda/backend/repository/BusCustomerCompanyWriteRepository.class


Some files were not shown because too many files changed in this diff