diff --git a/backend/src/main/java/com/qqchen/deploy/backend/api/UserApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/api/UserApiController.java index 3f7601ee..7dcf89a0 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/api/UserApiController.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/api/UserApiController.java @@ -3,7 +3,6 @@ package com.qqchen.deploy.backend.api; import com.qqchen.deploy.backend.common.controller.BaseController; import com.qqchen.deploy.backend.common.api.Response; import com.qqchen.deploy.backend.common.enums.ResponseCode; -import com.qqchen.deploy.backend.common.utils.MessageUtils; import com.qqchen.deploy.backend.converter.UserConverter; import com.qqchen.deploy.backend.entity.User; import com.qqchen.deploy.backend.dto.query.UserQuery; @@ -39,7 +38,7 @@ public class UserApiController extends BaseController, ID extends Serializab public Response create(@RequestBody R request) { T entity = converter.toEntity(request); T savedEntity = service.create(entity); - return Response.success(converter.toVO(savedEntity)); + return Response.success(converter.toResponse(savedEntity)); } @PutMapping("/{id}") @@ -36,7 +36,7 @@ public abstract class BaseController, ID extends Serializab T entity = service.findById(id); converter.updateEntity(entity, request); T updatedEntity = service.update(entity); - return Response.success(converter.toVO(updatedEntity)); + return Response.success(converter.toResponse(updatedEntity)); } @DeleteMapping("/{id}") @@ -48,18 +48,18 @@ public abstract class BaseController, ID extends Serializab @GetMapping("/{id}") public Response findById(@PathVariable ID id) { T entity = service.findById(id); - return Response.success(converter.toVO(entity)); + return Response.success(converter.toResponse(entity)); } @GetMapping public Response> findAll() { List entities = service.findAll(); - return Response.success(converter.toVOList(entities)); + return Response.success(converter.toResponseList(entities)); } @GetMapping("/page") public Response> page(Q query) { Page page = service.page(query); - return Response.success(page.map(entity -> converter.toVO(entity))); + return Response.success(page.map(entity -> converter.toResponse(entity))); } } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/common/converter/BaseConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/common/converter/BaseConverter.java index 959d4f67..775dd936 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/common/converter/BaseConverter.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/common/converter/BaseConverter.java @@ -12,16 +12,16 @@ public interface BaseConverter, R extends BaseRequest, V> { D toEntity(R request); // Domain -> VO - V toVO(D entity); + V toResponse(D entity); // Domain List -> VO List - List toVOList(List entityList); + List toResponseList(List entityList); // 更新实体 void updateEntity(@MappingTarget D entity, R request); // 转换分页结果 - default Page toVOPage(Page page) { - return page.map(this::toVO); + default Page toResponsePage(Page page) { + return page.map(this::toResponse); } } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/common/domain/AggregateRoot.java b/backend/src/main/java/com/qqchen/deploy/backend/common/domain/AggregateRoot.java index b8e65d17..8c7309a9 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/common/domain/AggregateRoot.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/common/domain/AggregateRoot.java @@ -1,10 +1,72 @@ package com.qqchen.deploy.backend.common.domain; -import java.io.Serializable; +import com.qqchen.deploy.backend.common.event.DomainEvent; +import org.springframework.data.domain.AfterDomainEventPublication; +import org.springframework.data.domain.DomainEvents; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; -/** - * 聚合根基类 - */ public abstract class AggregateRoot extends Entity { - // 聚合根特定的行为可以在这里添加 + + private transient final List domainEvents = new ArrayList<>(); + + /** + * 注册领域事件 + */ + protected void registerDomainEvent(DomainEvent event) { + if (event != null) { + domainEvents.add(event); + } + } + + /** + * 获取并清除所有事件 + */ + @DomainEvents + protected Collection domainEvents() { + List events = new ArrayList<>(this.domainEvents); + this.domainEvents.clear(); + return events; + } + + /** + * Spring Data JPA会在事件发布后调用此方法 + */ + @AfterDomainEventPublication + protected void clearDomainEvents() { + this.domainEvents.clear(); + } + + /** + * 验证聚合根状态 + * 子类可以重写此方法实现具体的验证逻辑 + */ + protected void validateState() { + // 默认实现为空,由子类根据需要重写 + } + + /** + * 在保存前验证状态 + */ + public void validateBeforeSave() { + validateState(); + } + + /** + * 在更新前验证状态 + */ + public void validateBeforeUpdate() { + validateState(); + } + + /** + * 在删除前验证状态 + */ + public void validateBeforeDelete() { + // 子类可以重写此方法添加删除前的验证 + } } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/common/event/DomainEvent.java b/backend/src/main/java/com/qqchen/deploy/backend/common/event/DomainEvent.java new file mode 100644 index 00000000..86cc5318 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/common/event/DomainEvent.java @@ -0,0 +1,16 @@ +package com.qqchen.deploy.backend.common.event; + +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +public abstract class DomainEvent { + private final String eventId; + private final LocalDateTime occurredOn; + + protected DomainEvent() { + this.eventId = java.util.UUID.randomUUID().toString(); + this.occurredOn = LocalDateTime.now(); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/common/service/impl/BaseServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/common/service/impl/BaseServiceImpl.java index 1dcc69aa..9ae49b1f 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/common/service/impl/BaseServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/common/service/impl/BaseServiceImpl.java @@ -5,7 +5,7 @@ import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; -import com.qqchen.deploy.backend.common.annotation.SoftDelete; +import com.qqchen.deploy.backend.common.annotation.LogicDelete; import com.qqchen.deploy.backend.common.domain.Entity; import com.qqchen.deploy.backend.common.enums.QueryType; import com.qqchen.deploy.backend.common.query.BaseQuery; @@ -73,7 +73,7 @@ public abstract class BaseServiceImpl, ID extends Serializa Class[] genericTypes = GenericTypeResolver.resolveTypeArguments(getClass(), BaseServiceImpl.class); if (genericTypes != null && genericTypes.length > 0) { Class entityClass = (Class) genericTypes[0]; - SoftDelete softDelete = entityClass.getAnnotation(SoftDelete.class); + LogicDelete softDelete = entityClass.getAnnotation(LogicDelete.class); if (softDelete != null && softDelete.value()) { Path deletedPath = EntityPathResolver.getPath(entityPath, "deleted"); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/converter/UserConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/converter/UserConverter.java index f83d51f2..7b3d8478 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/converter/UserConverter.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/converter/UserConverter.java @@ -19,7 +19,7 @@ public interface UserConverter extends BaseConverter { + + @NotBlank(message = "部门名称不能为空") + @Column(nullable = false) + private String name; + + @NotBlank(message = "部门编码不能为空") + @Column(nullable = false, unique = true) + private String code; + + private String description; + + @Column(name = "parent_id") + private Long parentId; + + @Column(nullable = false) + private Integer sort = 0; + + @Column(nullable = false) + private Boolean enabled = true; + + @Column(name = "leader_id") + private Long leaderId; + + @Column(name = "leader_name") + private String leaderName; + + @Transient // 不映射到数据库 + private List children = new ArrayList<>(); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/JenkinsBuild.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/JenkinsBuild.java new file mode 100644 index 00000000..05ca2d59 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/JenkinsBuild.java @@ -0,0 +1,41 @@ +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.Table; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +@Data +@EqualsAndHashCode(callSuper = true) +@jakarta.persistence.Entity +@Table(name = "sys_jenkins_build") +@LogicDelete +public class JenkinsBuild extends Entity { + + @Column(name = "jenkins_id", nullable = false) + private Long jenkinsId; + + @Column(name = "job_id", nullable = false) + private Long jobId; + + @Column(name = "build_number", nullable = false) + private Integer buildNumber; + + @Column(name = "build_url", nullable = false) + private String buildUrl; + + @Column(name = "build_status", nullable = false) + private String buildStatus; + + @Column(name = "start_time", nullable = false) + private LocalDateTime startTime; + + private Long duration; + + @Column(name = "trigger_cause", columnDefinition = "TEXT") + private String triggerCause; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/JenkinsConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/JenkinsConfig.java new file mode 100644 index 00000000..7645794d --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/JenkinsConfig.java @@ -0,0 +1,46 @@ +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.Table; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +@Data +@EqualsAndHashCode(callSuper = true) +@jakarta.persistence.Entity +@Table(name = "sys_jenkins_config") +@LogicDelete +public class JenkinsConfig extends Entity { + + @Column(nullable = false) + private String name; + + @Column(nullable = false) + private String url; + + @Column(nullable = false) + private String username; + + @Column(nullable = false) + private String password; + + private Integer sort; + + private String remark; + + @Column(name = "last_all_sync_time") + private LocalDateTime lastAllSyncTime; + + @Column(name = "last_view_sync_time") + private LocalDateTime lastViewSyncTime; + + @Column(name = "last_job_sync_time") + private LocalDateTime lastJobSyncTime; + + @Column(name = "last_build_sync_time") + private LocalDateTime lastBuildSyncTime; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/JenkinsJob.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/JenkinsJob.java new file mode 100644 index 00000000..fb036c95 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/JenkinsJob.java @@ -0,0 +1,49 @@ +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.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + + + +@Data +@EqualsAndHashCode(callSuper = true) +@jakarta.persistence.Entity +@Table(name = "sys_jenkins_job") +@LogicDelete +public class JenkinsJob extends Entity { + + @Column(name = "jenkins_id", nullable = false) + private Long jenkinsId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "view_id") + private JenkinsView view; + + @Column(name = "job_name", nullable = false) + private String jobName; + + @Column(name = "job_url", nullable = false) + private String jobUrl; + + private String description; + + private Boolean buildable; + + @Column(name = "last_build_number") + private Integer lastBuildNumber; + + @Column(name = "last_build_time") + private LocalDateTime lastBuildTime; + + @Column(name = "last_build_status") + private String lastBuildStatus; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/JenkinsSyncHistory.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/JenkinsSyncHistory.java new file mode 100644 index 00000000..56c49b4f --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/JenkinsSyncHistory.java @@ -0,0 +1,53 @@ +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.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Table; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +@Data +@EqualsAndHashCode(callSuper = true) +@jakarta.persistence.Entity +@Table(name = "sys_jenkins_sync_history") +@LogicDelete +public class JenkinsSyncHistory extends Entity { + + @Column(name = "jenkins_id", nullable = false) + private Long jenkinsId; + + @Column(name = "sync_type", nullable = false) + @Enumerated(EnumType.STRING) + private SyncType syncType; + + @Column(nullable = false) + @Enumerated(EnumType.STRING) + private SyncStatus status; + + @Column(name = "start_time", nullable = false) + private LocalDateTime startTime; + + @Column(name = "end_time") + private LocalDateTime endTime; + + @Column(name = "error_message", columnDefinition = "TEXT") + private String errorMessage; + + public enum SyncType { + ALL, // 全量同步 + VIEW, // 同步视图 + JOB, // 同步作业 + BUILD // 同步构建记录 + } + + public enum SyncStatus { + SUCCESS, // 同步成功 + FAILED, // 同步失败 + RUNNING // 同步中 + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/JenkinsView.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/JenkinsView.java new file mode 100644 index 00000000..188c93be --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/JenkinsView.java @@ -0,0 +1,28 @@ +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.Table; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +@jakarta.persistence.Entity +@Table(name = "sys_jenkins_view") +@LogicDelete +public class JenkinsView extends Entity { + + @Column(name = "jenkins_id", nullable = false) + private Long jenkinsId; + + @Column(name = "view_name", nullable = false) + private String viewName; + + @Column(name = "view_url", nullable = false) + private String viewUrl; + + private String description; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/Menu.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/Menu.java new file mode 100644 index 00000000..27112006 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/Menu.java @@ -0,0 +1,43 @@ +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.Table; +import lombok.Data; +import lombok.EqualsAndHashCode; + + + +@Data +@EqualsAndHashCode(callSuper = true) +@jakarta.persistence.Entity +@Table(name = "sys_menu") +@LogicDelete +public class Menu extends Entity { + + @Column(nullable = false) + private String name; + + private String permission; + + private String path; + + private String component; + + @Column(nullable = false) + private Integer type; // 0-目录 1-菜单 2-按钮 + + private String icon; + + @Column(name = "parent_id") + private Long parentId; + + @Column(nullable = false) + private Integer sort = 0; + + private Boolean hidden = false; + + @Column(nullable = false) + private Boolean enabled = true; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/RepositoryBranch.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/RepositoryBranch.java new file mode 100644 index 00000000..4f85ed04 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/RepositoryBranch.java @@ -0,0 +1,56 @@ +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.Table; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +@Data +@EqualsAndHashCode(callSuper = true) +@jakarta.persistence.Entity +@Table(name = "sys_repository_branch") +@LogicDelete +public class RepositoryBranch extends Entity { + + @Column(name = "repository_id", nullable = false) + private Long repositoryId; + + @Column(name = "project_id", nullable = false) + private Long projectId; + + @Column(nullable = false) + private String name; + + @Column(name = "commit_id") + private String commitId; + + @Column(name = "commit_message", columnDefinition = "TEXT") + private String commitMessage; + + @Column(name = "commit_author") + private String commitAuthor; + + @Column(name = "commit_date") + private LocalDateTime commitDate; + + private Boolean protected_ = false; + + @Column(name = "developers_can_push") + private Boolean developersCanPush = true; + + @Column(name = "developers_can_merge") + private Boolean developersCanMerge = true; + + @Column(name = "can_push") + private Boolean canPush = true; + + @Column(name = "default_branch") + private Boolean defaultBranch = false; + + @Column(name = "web_url") + private String webUrl; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/RepositoryConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/RepositoryConfig.java new file mode 100644 index 00000000..9c9570f4 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/RepositoryConfig.java @@ -0,0 +1,44 @@ +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.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Table; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + + +@Data +@EqualsAndHashCode(callSuper = true) +@jakarta.persistence.Entity +@Table(name = "sys_repository_config") +@LogicDelete +public class RepositoryConfig extends Entity { + + @Column(nullable = false) + private String name; + + @Column(nullable = false) + private String url; + + @Column(name = "access_token", nullable = false) + private String accessToken; + + @Column(name = "api_version") + private String apiVersion; + + private Integer sort; + + private String remark; + + @Column(name = "last_sync_time") + private LocalDateTime lastSyncTime; + + @Column(name = "last_sync_status") + @Enumerated(EnumType.STRING) + private RepositorySyncHistory.SyncStatus lastSyncStatus; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/RepositoryGroup.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/RepositoryGroup.java new file mode 100644 index 00000000..03127333 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/RepositoryGroup.java @@ -0,0 +1,47 @@ +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.Table; +import lombok.Data; +import lombok.EqualsAndHashCode; + + +@Data +@EqualsAndHashCode(callSuper = true) +@jakarta.persistence.Entity +@Table(name = "sys_repository_group") +@LogicDelete +public class RepositoryGroup extends Entity { + + @Column(name = "repository_id", nullable = false) + private Long repositoryId; + + @Column(name = "group_id", nullable = false) + private Long groupId; + + @Column(nullable = false) + private String name; + + @Column(nullable = false) + private String path; + + private String description; + + private String visibility; + + @Column(name = "parent_id") + private Long parentId; + + @Column(name = "web_url") + private String webUrl; + + @Column(name = "avatar_url") + private String avatarUrl; + + @Column(nullable = false) + private Boolean enabled = true; + + private Integer sort = 0; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/RepositoryProject.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/RepositoryProject.java new file mode 100644 index 00000000..3f17a469 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/RepositoryProject.java @@ -0,0 +1,57 @@ +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.Table; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +@Data +@EqualsAndHashCode(callSuper = true) +@jakarta.persistence.Entity +@Table(name = "sys_repository_project") +@LogicDelete +public class RepositoryProject extends Entity { + + @Column(name = "repository_id", nullable = false) + private Long repositoryId; + + @Column(name = "project_id", nullable = false) + private Long projectId; + + @Column(nullable = false) + private String name; + + @Column(nullable = false) + private String path; + + private String description; + + private String visibility; + + @Column(name = "group_id") + private Long groupId; + + @Column(name = "default_branch") + private String defaultBranch; + + @Column(name = "web_url") + private String webUrl; + + @Column(name = "ssh_url") + private String sshUrl; + + @Column(name = "http_url") + private String httpUrl; + + @Column(name = "last_activity_at") + private LocalDateTime lastActivityAt; + + @Column(nullable = false) + private Boolean enabled = true; + + private Integer sort = 0; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/RepositorySyncHistory.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/RepositorySyncHistory.java new file mode 100644 index 00000000..5bf366ac --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/RepositorySyncHistory.java @@ -0,0 +1,48 @@ +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.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Table; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +@Data +@EqualsAndHashCode(callSuper = true) +@jakarta.persistence.Entity +@Table(name = "sys_repository_sync_history") +@LogicDelete +public class RepositorySyncHistory extends Entity { + + @Column(name = "repository_id", nullable = false) + private Long repositoryId; + + @Column(name = "sync_type", nullable = false) + @Enumerated(EnumType.STRING) + private SyncType syncType; + + @Column(nullable = false) + @Enumerated(EnumType.STRING) + private SyncStatus status; + + @Column(name = "error_message", columnDefinition = "TEXT") + private String errorMessage; + + @Column(name = "start_time", nullable = false) + private LocalDateTime startTime; + + @Column(name = "end_time") + private LocalDateTime endTime; + + public enum SyncType { + GROUP, PROJECT, BRANCH + } + + public enum SyncStatus { + SUCCESS, FAILED, RUNNING + } +} \ 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 new file mode 100644 index 00000000..9f500cfa --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/Role.java @@ -0,0 +1,31 @@ +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.Table; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import lombok.EqualsAndHashCode; + + +@Data +@EqualsAndHashCode(callSuper = true) +@jakarta.persistence.Entity +@Table(name = "sys_role") +@LogicDelete +public class Role extends Entity { + + @NotBlank(message = "角色名称不能为空") + @Column(nullable = false) + private String name; + + @NotBlank(message = "角色编码不能为空") + @Column(nullable = false, unique = true) + private String code; + + private String description; + + @Column(nullable = false) + private Integer sort = 0; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/RoleMenu.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/RoleMenu.java new file mode 100644 index 00000000..ba6edec6 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/RoleMenu.java @@ -0,0 +1,24 @@ +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.Table; +import lombok.Data; +import lombok.EqualsAndHashCode; + + + +@Data +@EqualsAndHashCode(callSuper = true) +@jakarta.persistence.Entity +@Table(name = "sys_role_menu") +@LogicDelete +public class RoleMenu extends Entity { + + @Column(name = "role_id", nullable = false) + private Long roleId; + + @Column(name = "menu_id", nullable = false) + private Long menuId; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/Tenant.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/Tenant.java new file mode 100644 index 00000000..30b08673 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/Tenant.java @@ -0,0 +1,39 @@ +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.Table; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import lombok.EqualsAndHashCode; + + +@Data +@EqualsAndHashCode(callSuper = true) +@jakarta.persistence.Entity +@Table(name = "sys_tenant") +@LogicDelete +public class Tenant extends Entity { + + @NotBlank(message = "租户名称不能为空") + @Column(nullable = false) + private String name; + + @NotBlank(message = "租户编码不能为空") + @Column(nullable = false, unique = true) + private String code; + + @Column(name = "contact_name") + private String contactName; + + @Column(name = "contact_phone") + private String contactPhone; + + private String email; + + private String address; + + @Column(nullable = false) + private Boolean enabled = true; +} \ 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 151c70d0..64a41c1a 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 @@ -1,31 +1,143 @@ package com.qqchen.deploy.backend.entity; -import com.qqchen.deploy.backend.common.annotation.SoftDelete; -import com.qqchen.deploy.backend.common.domain.Entity; +import com.qqchen.deploy.backend.common.annotation.LogicDelete; +import com.qqchen.deploy.backend.common.domain.AggregateRoot; +import com.qqchen.deploy.backend.event.UserRoleChangedEvent; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; +import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + @Data @EqualsAndHashCode(callSuper = true) @jakarta.persistence.Entity -@Table(name = "t_user") -@SoftDelete -public class User extends Entity { +@Table(name = "sys_user") +@LogicDelete +public class User extends AggregateRoot { @Column(unique = true, nullable = false) private String username; - @Column(unique = true, nullable = false) - private String email; - @Column(nullable = false) private String password; + @Column(length = 50) private String nickname; + private String email; + private String phone; + @Column(nullable = false) private Boolean enabled = true; -} \ No newline at end of file + + @Column(name = "dept_id") + private Long deptId; + + @Column(name = "dept_name") + private String deptName; + + + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + private Set userRoles = new HashSet<>(); + + 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 new file mode 100644 index 00000000..73fb8b6a --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/UserRole.java @@ -0,0 +1,22 @@ +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.Table; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +@jakarta.persistence.Entity +@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; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/event/UserEventListener.java b/backend/src/main/java/com/qqchen/deploy/backend/event/UserEventListener.java new file mode 100644 index 00000000..6ac88f1c --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/event/UserEventListener.java @@ -0,0 +1,22 @@ +package com.qqchen.deploy.backend.event; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Slf4j +@Component +public class UserEventListener { + + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void handleUserRoleChanged(UserRoleChangedEvent event) { + log.info("User role changed - userId: {}, roleId: {}, action: {}", + event.getUserId(), + event.getRoleId(), + event.getAction()); + + // 这里可以添加后续处理逻辑 + // 例如:发送通知、更新缓存等 + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/event/UserRoleChangedEvent.java b/backend/src/main/java/com/qqchen/deploy/backend/event/UserRoleChangedEvent.java new file mode 100644 index 00000000..94bfff02 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/event/UserRoleChangedEvent.java @@ -0,0 +1,17 @@ +package com.qqchen.deploy.backend.event; + +import com.qqchen.deploy.backend.common.event.DomainEvent; +import lombok.Getter; + +@Getter +public class UserRoleChangedEvent extends DomainEvent { + private final Long userId; + private final Long roleId; + private final String action; // "ADD" or "REMOVE" + public UserRoleChangedEvent(Long userId, Long roleId, String action) { + super(); + this.userId = userId; + this.roleId = roleId; + this.action = action; + } +} \ No newline at end of file diff --git a/backend/src/main/resources/messages.properties b/backend/src/main/resources/messages.properties new file mode 100644 index 00000000..251a9a10 --- /dev/null +++ b/backend/src/main/resources/messages.properties @@ -0,0 +1,12 @@ +# \u4E2D\u6587 +response.success=\u64CD\u4F5C\u6210\u529F +response.error=\u7CFB\u7EDF\u9519\u8BEF +response.invalid.param=\u65E0\u6548\u7684\u53C2\u6570 +response.unauthorized=\u672A\u6388\u6743 +response.forbidden=\u7981\u6B62\u8BBF\u95EE +response.not.found=\u8D44\u6E90\u672A\u627E\u5230 +response.conflict=\u8D44\u6E90\u51B2\u7A81 + +user.not.found=\u7528\u6237\u4E0D\u5B58\u5728 +user.username.exists=\u7528\u6237\u540D\u5DF2\u5B58\u5728 +user.email.exists=\u90AE\u7BB1\u5DF2\u5B58\u5728 \ No newline at end of file diff --git a/backend/src/main/resources/messages_cn.properties b/backend/src/main/resources/messages_cn.properties deleted file mode 100644 index 8d148dd7..00000000 --- a/backend/src/main/resources/messages_cn.properties +++ /dev/null @@ -1,12 +0,0 @@ -# 中文 -response.success=操作成功 -response.error=系统错误 -response.invalid.param=无效的参数 -response.unauthorized=未授权 -response.forbidden=禁止访问 -response.not.found=资源未找到 -response.conflict=资源冲突 - -user.not.found=用户不存在 -user.username.exists=用户名已存在 -user.email.exists=邮箱已存在 \ No newline at end of file diff --git a/backend/src/main/resources/messages_zh_CN.properties b/backend/src/main/resources/messages_zh_CN.properties new file mode 100644 index 00000000..251a9a10 --- /dev/null +++ b/backend/src/main/resources/messages_zh_CN.properties @@ -0,0 +1,12 @@ +# \u4E2D\u6587 +response.success=\u64CD\u4F5C\u6210\u529F +response.error=\u7CFB\u7EDF\u9519\u8BEF +response.invalid.param=\u65E0\u6548\u7684\u53C2\u6570 +response.unauthorized=\u672A\u6388\u6743 +response.forbidden=\u7981\u6B62\u8BBF\u95EE +response.not.found=\u8D44\u6E90\u672A\u627E\u5230 +response.conflict=\u8D44\u6E90\u51B2\u7A81 + +user.not.found=\u7528\u6237\u4E0D\u5B58\u5728 +user.username.exists=\u7528\u6237\u540D\u5DF2\u5B58\u5728 +user.email.exists=\u90AE\u7BB1\u5DF2\u5B58\u5728 \ No newline at end of file diff --git a/backend/src/test/java/com/qqchen/deploy/common/BackendApplicationTests.java b/backend/src/test/java/com/qqchen/deploy/common/BackendApplicationTests.java deleted file mode 100644 index 4291ac60..00000000 --- a/backend/src/test/java/com/qqchen/deploy/common/BackendApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.qqchen.deploy.common; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class BackendApplicationTests { - - @Test - void contextLoads() { - } - -}