diff --git a/backend/pom.xml b/backend/pom.xml
index 8ac08512..a8126d11 100644
--- a/backend/pom.xml
+++ b/backend/pom.xml
@@ -199,4 +199,17 @@
+
+
+
+ ${release.repository.id}
+ ${release.repository.url}
+ ${release.repository.name}
+
+
+ ${snapshot.repository.id}
+ ${snapshot.repository.url}
+ ${snapshot.repository.name}
+
+
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/Role.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/Role.java
index 9f500cfa..78406e47 100644
--- a/backend/src/main/java/com/qqchen/deploy/backend/entity/Role.java
+++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/Role.java
@@ -2,12 +2,16 @@ package com.qqchen.deploy.backend.entity;
import com.qqchen.deploy.backend.common.annotation.LogicDelete;
import com.qqchen.deploy.backend.common.domain.Entity;
+import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
+import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
+import java.util.List;
+
@Data
@EqualsAndHashCode(callSuper = true)
@@ -28,4 +32,7 @@ public class Role extends Entity {
@Column(nullable = false)
private Integer sort = 0;
+
+ @OneToMany(mappedBy = "role", cascade = CascadeType.ALL) // 指向 UserRole 的 role 属性
+ private List userRoles;
}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/User.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/User.java
index 64a41c1a..3ae3b4b1 100644
--- a/backend/src/main/java/com/qqchen/deploy/backend/entity/User.java
+++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/User.java
@@ -2,6 +2,7 @@ package com.qqchen.deploy.backend.entity;
import com.qqchen.deploy.backend.common.annotation.LogicDelete;
import com.qqchen.deploy.backend.common.domain.AggregateRoot;
+import com.qqchen.deploy.backend.common.domain.Entity;
import com.qqchen.deploy.backend.event.UserRoleChangedEvent;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
@@ -11,6 +12,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.HashSet;
+import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -20,7 +22,7 @@ import java.util.stream.Collectors;
@jakarta.persistence.Entity
@Table(name = "sys_user")
@LogicDelete
-public class User extends AggregateRoot {
+public class User extends Entity {
@Column(unique = true, nullable = false)
private String username;
@@ -45,99 +47,99 @@ public class User extends AggregateRoot {
private String deptName;
- @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
- private Set userRoles = new HashSet<>();
+ @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) // 指向 UserRole 的 user 属性
+ private List userRoles;
- public void addRole(Role role) {
- UserRole userRole = new UserRole(this, role);
- userRoles.add(userRole);
- registerDomainEvent(new UserRoleChangedEvent(this.getId(), role.getId(), "ADD"));
- validateState();
- }
-
- public void removeRole(Role role) {
- boolean removed = userRoles.removeIf(ur -> ur.getRole().equals(role));
- if (removed) {
- registerDomainEvent(new UserRoleChangedEvent(this.getId(), role.getId(), "REMOVE"));
- validateState();
- }
- }
-
- public void changePassword(String oldPassword, String newPassword) {
- if (!this.password.equals(oldPassword)) {
- throw new IllegalArgumentException("Old password is incorrect");
- }
- this.password = newPassword;
- registerDomainEvent(new PasswordChangedEvent(this.getId()));
- validateState();
- }
-
- public void setEnabled(boolean enabled) {
- if (this.enabled != enabled) {
- this.enabled = enabled;
- registerDomainEvent(new UserStatusChangedEvent(this.getId(), enabled));
- validateState();
- }
- }
-
- public void updateBasicInfo(String nickname, String phone, String email) {
- boolean changed = false;
-
- if (!Objects.equals(this.nickname, nickname)) {
- this.nickname = nickname;
- changed = true;
- }
-
- if (!Objects.equals(this.phone, phone)) {
- this.phone = phone;
- changed = true;
- }
-
- if (!Objects.equals(this.email, email)) {
- this.email = email;
- changed = true;
- }
-
- if (changed) {
- registerDomainEvent(new UserUpdatedEvent(this.getId()));
- validateState();
- }
- }
-
- public boolean hasRole(String roleCode) {
- return userRoles.stream()
- .map(UserRole::getRole)
- .anyMatch(role -> role.getCode().equals(roleCode));
- }
-
- public Set getRoleCodes() {
- return userRoles.stream()
- .map(UserRole::getRole)
- .map(Role::getCode)
- .collect(Collectors.toSet());
- }
-
- @Override
- protected void validateState() {
- if (username == null || username.trim().isEmpty()) {
- throw new IllegalStateException("Username cannot be empty");
- }
- if (email == null || !email.matches("^[A-Za-z0-9+_.-]+@(.+)$")) {
- throw new IllegalStateException("Invalid email format");
- }
- if (password == null || password.length() < 6) {
- throw new IllegalStateException("Password must be at least 6 characters");
- }
- if (phone != null && !phone.matches("^\\d{11}$")) {
- throw new IllegalStateException("Invalid phone number format");
- }
- }
-
- @Override
- public void validateBeforeDelete() {
- if (!userRoles.isEmpty()) {
- throw new IllegalStateException("Cannot delete user with assigned roles");
- }
- }
+// public void addRole(Role role) {
+// UserRole userRole = new UserRole(this, role);
+// userRoles.add(userRole);
+// registerDomainEvent(new UserRoleChangedEvent(this.getId(), role.getId(), "ADD"));
+// validateState();
+// }
+//
+// public void removeRole(Role role) {
+// boolean removed = userRoles.removeIf(ur -> ur.getRole().equals(role));
+// if (removed) {
+// registerDomainEvent(new UserRoleChangedEvent(this.getId(), role.getId(), "REMOVE"));
+// validateState();
+// }
+// }
+//
+// public void changePassword(String oldPassword, String newPassword) {
+// if (!this.password.equals(oldPassword)) {
+// throw new IllegalArgumentException("Old password is incorrect");
+// }
+// this.password = newPassword;
+// registerDomainEvent(new PasswordChangedEvent(this.getId()));
+// validateState();
+// }
+//
+// public void setEnabled(boolean enabled) {
+// if (this.enabled != enabled) {
+// this.enabled = enabled;
+// registerDomainEvent(new UserStatusChangedEvent(this.getId(), enabled));
+// validateState();
+// }
+// }
+//
+// public void updateBasicInfo(String nickname, String phone, String email) {
+// boolean changed = false;
+//
+// if (!Objects.equals(this.nickname, nickname)) {
+// this.nickname = nickname;
+// changed = true;
+// }
+//
+// if (!Objects.equals(this.phone, phone)) {
+// this.phone = phone;
+// changed = true;
+// }
+//
+// if (!Objects.equals(this.email, email)) {
+// this.email = email;
+// changed = true;
+// }
+//
+// if (changed) {
+// registerDomainEvent(new UserUpdatedEvent(this.getId()));
+// validateState();
+// }
+// }
+//
+// public boolean hasRole(String roleCode) {
+// return userRoles.stream()
+// .map(UserRole::getRole)
+// .anyMatch(role -> role.getCode().equals(roleCode));
+// }
+//
+// public Set getRoleCodes() {
+// return userRoles.stream()
+// .map(UserRole::getRole)
+// .map(Role::getCode)
+// .collect(Collectors.toSet());
+// }
+//
+// @Override
+// protected void validateState() {
+// if (username == null || username.trim().isEmpty()) {
+// throw new IllegalStateException("Username cannot be empty");
+// }
+// if (email == null || !email.matches("^[A-Za-z0-9+_.-]+@(.+)$")) {
+// throw new IllegalStateException("Invalid email format");
+// }
+// if (password == null || password.length() < 6) {
+// throw new IllegalStateException("Password must be at least 6 characters");
+// }
+// if (phone != null && !phone.matches("^\\d{11}$")) {
+// throw new IllegalStateException("Invalid phone number format");
+// }
+// }
+//
+// @Override
+// public void validateBeforeDelete() {
+// if (!userRoles.isEmpty()) {
+// throw new IllegalStateException("Cannot delete user with assigned roles");
+// }
+// }
}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/UserRole.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/UserRole.java
index 73fb8b6a..da1cfe54 100644
--- a/backend/src/main/java/com/qqchen/deploy/backend/entity/UserRole.java
+++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/UserRole.java
@@ -3,6 +3,10 @@ package com.qqchen.deploy.backend.entity;
import com.qqchen.deploy.backend.common.annotation.LogicDelete;
import com.qqchen.deploy.backend.common.domain.Entity;
import jakarta.persistence.Column;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -13,10 +17,12 @@ import lombok.EqualsAndHashCode;
@Table(name = "sys_user_role")
@LogicDelete
public class UserRole extends Entity {
-
- @Column(name = "user_id", nullable = false)
- private Long userId;
-
- @Column(name = "role_id", nullable = false)
- private Long roleId;
+
+ @ManyToOne // 多对一关系
+ @JoinColumn(name = "user_id") // 外键列
+ private User user; // 用户
+
+ @ManyToOne // 多对一关系
+ @JoinColumn(name = "role_id") // 外键列
+ private Role role; // 角色
}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/DepartmentRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/DepartmentRepository.java
new file mode 100644
index 00000000..6385bd00
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/DepartmentRepository.java
@@ -0,0 +1,24 @@
+package com.qqchen.deploy.backend.repository;
+
+import com.qqchen.deploy.backend.common.repository.BaseRepository;
+import com.qqchen.deploy.backend.entity.Department;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+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 DepartmentRepository extends BaseRepository {
+
+ List findByParentIdAndDeletedFalseOrderBySort(Long parentId);
+
+ List findByDeletedFalseOrderBySort();
+
+ boolean existsByCodeAndDeletedFalse(String code);
+
+ boolean existsByNameAndDeletedFalse(String name);
+
+ @Query("SELECT COALESCE(MAX(d.sort), 0) FROM Department d WHERE d.parentId = :parentId AND d.deleted = false")
+ Integer findMaxSortByParentId(@Param("parentId") Long parentId);
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsBuildRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsBuildRepository.java
new file mode 100644
index 00000000..f043bfb5
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsBuildRepository.java
@@ -0,0 +1,15 @@
+package com.qqchen.deploy.backend.repository;
+
+
+import com.qqchen.deploy.backend.common.repository.BaseRepository;
+import com.qqchen.deploy.backend.entity.JenkinsBuild;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+@Repository
+public interface JenkinsBuildRepository extends BaseRepository {
+ List findByJobIdAndDeletedFalse(Long jobId);
+ List findByJobIdAndBuildNumberAndDeletedFalse(Long jobId, Integer buildNumber);
+ void deleteByJobIdAndDeletedFalse(Long jobId);
+ List findByJobIdAndDeletedFalseOrderByBuildNumberDesc(Long jobId);
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsConfigRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsConfigRepository.java
new file mode 100644
index 00000000..7708cdba
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsConfigRepository.java
@@ -0,0 +1,13 @@
+package com.qqchen.deploy.backend.repository;
+
+
+import com.qqchen.deploy.backend.common.repository.BaseRepository;
+import com.qqchen.deploy.backend.entity.JenkinsConfig;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+@Repository
+public interface JenkinsConfigRepository extends BaseRepository {
+ List findByDeletedFalseOrderBySort();
+ boolean existsByNameAndDeletedFalse(String name);
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsJobRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsJobRepository.java
new file mode 100644
index 00000000..ae46f061
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsJobRepository.java
@@ -0,0 +1,26 @@
+package com.qqchen.deploy.backend.repository;
+
+import com.qqchen.deploy.backend.common.repository.BaseRepository;
+import com.qqchen.deploy.backend.entity.JenkinsJob;
+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 JenkinsJobRepository extends BaseRepository {
+ List findByJenkinsIdAndDeletedFalse(Long jenkinsId);
+ List findByJenkinsIdAndJobNameAndDeletedFalse(Long jenkinsId, String jobName);
+ void deleteByJenkinsIdAndDeletedFalse(Long jenkinsId);
+
+ @Query("SELECT j FROM JenkinsJob j " +
+ "JOIN FETCH j.view v " +
+ "WHERE j.jenkinsId = :jenkinsId " +
+ "AND v.viewName = :viewName " +
+ "AND j.deleted = false " +
+ "AND v.deleted = false")
+ List findJobsByJenkinsIdAndViewName(
+ @Param("jenkinsId") Long jenkinsId,
+ @Param("viewName") String viewName
+ );
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsSyncHistoryRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsSyncHistoryRepository.java
new file mode 100644
index 00000000..2f40933e
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsSyncHistoryRepository.java
@@ -0,0 +1,12 @@
+package com.qqchen.deploy.backend.repository;
+
+
+import com.qqchen.deploy.backend.common.repository.BaseRepository;
+import com.qqchen.deploy.backend.entity.JenkinsSyncHistory;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+@Repository
+public interface JenkinsSyncHistoryRepository extends BaseRepository {
+ List findTop50ByOrderByStartTimeDesc();
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsViewRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsViewRepository.java
new file mode 100644
index 00000000..e08fd058
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsViewRepository.java
@@ -0,0 +1,15 @@
+package com.qqchen.deploy.backend.repository;
+
+
+import com.qqchen.deploy.backend.common.repository.BaseRepository;
+import com.qqchen.deploy.backend.entity.JenkinsView;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface JenkinsViewRepository extends BaseRepository {
+ List findByJenkinsIdAndDeletedFalse(Long jenkinsId);
+ List findByJenkinsIdAndViewNameAndDeletedFalse(Long jenkinsId, String viewName);
+ void deleteByJenkinsIdAndDeletedFalse(Long jenkinsId);
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/MenuRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/MenuRepository.java
new file mode 100644
index 00000000..3e1a8183
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/MenuRepository.java
@@ -0,0 +1,27 @@
+package com.qqchen.deploy.backend.repository;
+
+import com.qqchen.deploy.backend.entity.Menu;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+@Repository
+public interface MenuRepository extends JpaRepository