From f9d637a6b66c1bb1d54a87104f94d815e5766e4e Mon Sep 17 00:00:00 2001 From: Jensen Qi Date: Mon, 13 Dec 2021 23:53:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=89=88=E6=9C=AC=E6=8E=A7?= =?UTF-8?q?=E5=88=B6=20Service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/service/VersionControlService.kt | 210 ++++++++++++++++++ .../woden/common/service/dao/CommitmentDAO.kt | 59 +++++ .../common/service/dao/WorkingTreeDAO.kt | 64 ++++++ .../woden/common/service/dto/CommitmentDTO.kt | 33 +++ .../common/service/dto/WorkingTreeDTO.kt | 33 +++ .../woden/common/service/po/CommitmentPO.kt | 33 +++ .../woden/common/service/po/WorkingTreePO.kt | 34 +++ 7 files changed, 466 insertions(+) create mode 100644 common/src/main/kotlin/tech/cuda/woden/common/service/VersionControlService.kt create mode 100644 common/src/main/kotlin/tech/cuda/woden/common/service/dao/CommitmentDAO.kt create mode 100644 common/src/main/kotlin/tech/cuda/woden/common/service/dao/WorkingTreeDAO.kt create mode 100644 common/src/main/kotlin/tech/cuda/woden/common/service/dto/CommitmentDTO.kt create mode 100644 common/src/main/kotlin/tech/cuda/woden/common/service/dto/WorkingTreeDTO.kt create mode 100644 common/src/main/kotlin/tech/cuda/woden/common/service/po/CommitmentPO.kt create mode 100644 common/src/main/kotlin/tech/cuda/woden/common/service/po/WorkingTreePO.kt diff --git a/common/src/main/kotlin/tech/cuda/woden/common/service/VersionControlService.kt b/common/src/main/kotlin/tech/cuda/woden/common/service/VersionControlService.kt new file mode 100644 index 00000000..59e84df7 --- /dev/null +++ b/common/src/main/kotlin/tech/cuda/woden/common/service/VersionControlService.kt @@ -0,0 +1,210 @@ +/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package tech.cuda.woden.common.service + +import me.liuwj.ktorm.database.Database +import me.liuwj.ktorm.dsl.* +import me.liuwj.ktorm.global.addEntity +import me.liuwj.ktorm.global.global +import me.liuwj.ktorm.global.select +import me.liuwj.ktorm.global.update +import tech.cuda.woden.common.service.dao.CommitmentDAO +import tech.cuda.woden.common.service.dao.WorkingTreeDAO +import tech.cuda.woden.common.service.dto.CommitmentDTO +import tech.cuda.woden.common.service.dto.WorkingTreeDTO +import tech.cuda.woden.common.service.dto.toCommitmentDTO +import tech.cuda.woden.common.service.dto.toWorkingTreeDTO +import tech.cuda.woden.common.service.exception.NotFoundException +import tech.cuda.woden.common.service.po.CommitmentPO +import tech.cuda.woden.common.service.po.WorkingTreePO +import java.time.LocalDateTime + +/** + * @author Jensen Qi + * @since 0.1.0 + */ +object VersionControlService { + + private fun requireDirectory(node: WorkingTreePO) = require(node.name.split(".").size == 1) { + "Tree Node ${node.name} must be a directory" + } + + + private fun requireNonDirectory(node: WorkingTreePO) = require(node.name.split(".").size > 1) { + "Tree Node ${node.name} can not be a directory" + } + + internal fun selectWorkingTreeById(nodeId: Int) = WorkingTreeDAO.select() + .where { (WorkingTreeDAO.isRemove eq false) and (WorkingTreeDAO.id eq nodeId) } + .map { WorkingTreeDAO.createEntity(it) } + .firstOrNull() + + + internal fun selectCommitmentById(commitId: Int) = CommitmentDAO.select() + .where { (CommitmentDAO.isRemove eq false) and (CommitmentDAO.id eq commitId) } + .map { CommitmentDAO.createEntity(it) } + .firstOrNull() + + /** + * 创建一个 workingTree 的根节点并返回 + */ + fun createWorkingTree(): WorkingTreeDTO { + Database.global.useTransaction { + val now = LocalDateTime.now() + val workingTree = WorkingTreePO { + this.name = "root" + this.parentId = null + this.stage = null + this.commitId = null + this.isRemove = false + this.createTime = now + this.updateTime = now + }.also { WorkingTreeDAO.addEntity(it) } + return workingTree.toWorkingTreeDTO() + } + } + + /** + * 创建节点名称为[name],父节点为[parentId]的 Working Tree 节点 + * 如果[parentId]对应的节点不存在或已被删除,则抛出[NotFoundException] + * 如果[parentId]不为目录节点,则抛出[IllegalArgumentException] + */ + fun createWorkingNode(name: String, parentId: Int): WorkingTreeDTO = Database.global.useTransaction { + val parent = selectWorkingTreeById(parentId) ?: throw NotFoundException() + requireDirectory(parent) + val now = LocalDateTime.now() + val workingTree = WorkingTreePO { + this.name = name + this.parentId = parent.id + this.stage = null + this.commitId = null + this.isRemove = false + this.createTime = now + this.updateTime = now + }.also { WorkingTreeDAO.addEntity(it) } + return workingTree.toWorkingTreeDTO() + } + + /** + * 查找 working Tree 中节点 ID 为 [nodeId] 的发布历史 + * 如果[nodeId]节点不存在或已被删除,则抛出[NotFoundException] + * 如果[nodeId]节点为目录节点,则抛出[IllegalArgumentException] + */ + fun listingCommitHistory(nodeId: Int): List { + val node = selectWorkingTreeById(nodeId) ?: throw NotFoundException() + requireNonDirectory(node) + return CommitmentDAO.select() + .where { (CommitmentDAO.nodeId eq nodeId) and (CommitmentDAO.isRemove eq false) } + .orderBy(CommitmentDAO.createTime.desc()) + .map { CommitmentDAO.createEntity(it).toCommitmentDTO() } + } + + /** + * 列出 WorkingTree 中节点 ID 为 [nodeId] 的所有子节点 + * 如果[nodeId]节点不存在或已被删除,则抛出[NotFoundException] + * 如果[nodeId]节点不为目录节点,则抛出[IllegalArgumentException] + */ + fun listingChildren(nodeId: Int): List { + val node = selectWorkingTreeById(nodeId) ?: throw NotFoundException() + requireDirectory(node) + return WorkingTreeDAO.select() + .where { (WorkingTreeDAO.isRemove eq false) and (WorkingTreeDAO.parentId eq node.id) } + .map { WorkingTreeDAO.createEntity(it).toWorkingTreeDTO() } + } + + + /** + * 删除 workingTree 中的节点 ID 为 [nodeId] 的节点 + * 如果[nodeId]节点不存在或已被删除,则抛出[NotFoundException] + * 如果[nodeId]节点为根节点,则抛出[IllegalArgumentException] + */ + fun remove(nodeId: Int) = Database.global.useTransaction { + val node = selectWorkingTreeById(nodeId) ?: throw NotFoundException() + require(node.parentId != null) { "can not remove root dir /" } + node.isRemove = true + node.updateTime = LocalDateTime.now() + node.flushChanges() + } + + /** + * 获取节点 ID 为 [nodeId] 的当前 commit + * 如果[nodeId]节点不存在或已被删除,则抛出[NotFoundException] + * 如果[nodeId]节点为目录节点,则抛出[IllegalArgumentException] + * 如果[nodeId]不存在已提交的 commit,则抛出[IllegalArgumentException] + */ + fun getCurrentCommit(nodeId: Int): CommitmentDTO { + val node = selectWorkingTreeById(nodeId) ?: throw NotFoundException() + requireNonDirectory(node) + require(node.commitId != null) { "current has no committed" } + val commitment = selectCommitmentById(node.commitId!!) ?: throw NotFoundException() + return commitment.toCommitmentDTO() + } + + /** + * 将 [content] 写入 ID 为 [nodeId] 的节点缓存区 + * 如果[nodeId]节点不存在或已被删除,则抛出[NotFoundException] + * 如果[nodeId]节点为目录节点,则抛出[IllegalArgumentException] + */ + fun writeStage(nodeId: Int, content: String) = Database.global.useTransaction { + val node = selectWorkingTreeById(nodeId) ?: throw NotFoundException() + requireNonDirectory(node) + node.stage = content + node.updateTime = LocalDateTime.now() + node.flushChanges() + } + + /** + * 发布 ID 为 [nodeId] 的节点中的缓存区 + * 如果[nodeId]节点不存在或已被删除,则抛出[NotFoundException] + * 如果[nodeId]为目录节点,则抛出[IllegalArgumentException] + * 如果[nodeId]的缓存区为 null,则抛出[IllegalArgumentException] + */ + fun commit(nodeId: Int, message: String): CommitmentDTO = Database.global.useTransaction { + val node = selectWorkingTreeById(nodeId) ?: throw NotFoundException() + requireNonDirectory(node) + require(node.stage != null) { "Stage of tree node ${node.name} is null" } + val now = LocalDateTime.now() + val commitment = CommitmentPO { + this.nodeId = node.id + this.content = node.stage!! + this.message = message + this.isRemove = false + this.createTime = now + this.updateTime = now + }.also { CommitmentDAO.addEntity(it) } + WorkingTreeDAO.update { + set(it.commitId, commitment.id) + set(it.updateTime, now) + where { it.id eq node.id } + } + return commitment.toCommitmentDTO() + } + + /** + * 将 ID 为 [nodeId] 的节点回滚为 ID 为 [commitID] 的 commitment + * 如果[nodeId]节点或已被删除,则抛出[NotFoundException] + * 如果[commitID]提交不存在或已被删除,则抛出[NotFoundException] + * 如果[nodeId]为目录节点,则抛出[IllegalArgumentException] + * 如果[commitID]不归属与 [nodeId],则抛出[IllegalStateException] + */ + fun rollback(nodeId: Int, commitID: Int) = Database.global.useTransaction { + val node = selectWorkingTreeById(nodeId) ?: throw NotFoundException() + val commitment = selectCommitmentById(commitID) ?: throw NotFoundException() + requireNonDirectory(node) + check(commitment.nodeId == node.id) { "commitment ${commitment.id} do not belong to node ${node.id}" } + node.stage = commitment.content + node.commitId = commitment.id + node.updateTime = LocalDateTime.now() + node.flushChanges() + } + + +} \ No newline at end of file diff --git a/common/src/main/kotlin/tech/cuda/woden/common/service/dao/CommitmentDAO.kt b/common/src/main/kotlin/tech/cuda/woden/common/service/dao/CommitmentDAO.kt new file mode 100644 index 00000000..c6921628 --- /dev/null +++ b/common/src/main/kotlin/tech/cuda/woden/common/service/dao/CommitmentDAO.kt @@ -0,0 +1,59 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package tech.cuda.woden.common.service.dao + +import me.liuwj.ktorm.schema.* +import tech.cuda.woden.annotation.mysql.* +import tech.cuda.woden.common.service.mysql.type.longtext +import tech.cuda.woden.common.service.po.CommitmentPO + +/** + * @author Jensen Qi + * @since 0.1.0 + */ +@STORE_IN_MYSQL +internal object CommitmentDAO : Table("commitment") { + @BIGINT + @UNSIGNED + @AUTO_INCREMENT + @PRIMARY_KEY + @COMMENT("commit ID") + val id = int("id").primaryKey().bindTo { it.id } + + @BIGINT + @UNSIGNED + @COMMENT("归属的 Working Tree 节点 ID") + val nodeId = int("node_id").bindTo { it.nodeId } + + @LONGTEXT + @COMMENT("commit 内容") + val content = longtext("content").bindTo { it.content } + + @VARCHAR(256) + @COMMENT("commit 注释") + val message = varchar("message").bindTo { it.message } + + @BOOL + @COMMENT("逻辑删除") + val isRemove = boolean("is_remove").bindTo { it.isRemove } + + @DATETIME + @COMMENT("创建时间") + val createTime = datetime("create_time").bindTo { it.createTime } + + @DATETIME + @COMMENT("更新时间") + val updateTime = datetime("update_time").bindTo { it.updateTime } + +} \ No newline at end of file diff --git a/common/src/main/kotlin/tech/cuda/woden/common/service/dao/WorkingTreeDAO.kt b/common/src/main/kotlin/tech/cuda/woden/common/service/dao/WorkingTreeDAO.kt new file mode 100644 index 00000000..d22511e7 --- /dev/null +++ b/common/src/main/kotlin/tech/cuda/woden/common/service/dao/WorkingTreeDAO.kt @@ -0,0 +1,64 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package tech.cuda.woden.common.service.dao + +import me.liuwj.ktorm.schema.* +import tech.cuda.woden.annotation.mysql.* +import tech.cuda.woden.common.service.mysql.type.longtext +import tech.cuda.woden.common.service.po.WorkingTreePO + +/** + * @author Jensen Qi + * @since 0.1.0 + */ +@STORE_IN_MYSQL +internal object WorkingTreeDAO : Table("working_tree") { + @BIGINT + @UNSIGNED + @AUTO_INCREMENT + @PRIMARY_KEY + @COMMENT("Working Tree 节点 ID") + val id = int("id").primaryKey().bindTo { it.id } + + + @VARCHAR(256) + @COMMENT("节点名,有后缀的为叶子节点,无后缀的为非叶子节点(即目录节点)") + val name = varchar("name").bindTo { it.name } + + @BIGINT + @UNSIGNED + @COMMENT("父节点ID") + val parentId = int("parent_id").bindTo { it.parentId } + + @LONGTEXT + @COMMENT("当前 stage 区内容") + val stage = longtext("stage").bindTo { it.stage } + + @BIGINT + @UNSIGNED + @COMMENT("当天已发布的 commit ID") + val commitId = int("commit_id").bindTo { it.commitId } + + @BOOL + @COMMENT("逻辑删除") + val isRemove = boolean("is_remove").bindTo { it.isRemove } + + @DATETIME + @COMMENT("创建时间") + val createTime = datetime("create_time").bindTo { it.createTime } + + @DATETIME + @COMMENT("更新时间") + val updateTime = datetime("update_time").bindTo { it.updateTime } +} \ No newline at end of file diff --git a/common/src/main/kotlin/tech/cuda/woden/common/service/dto/CommitmentDTO.kt b/common/src/main/kotlin/tech/cuda/woden/common/service/dto/CommitmentDTO.kt new file mode 100644 index 00000000..7870768d --- /dev/null +++ b/common/src/main/kotlin/tech/cuda/woden/common/service/dto/CommitmentDTO.kt @@ -0,0 +1,33 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package tech.cuda.woden.common.service.dto + +import tech.cuda.woden.annotation.pojo.DTO +import tech.cuda.woden.common.service.po.CommitmentPO +import tech.cuda.woden.common.service.po.ContainerPO +import java.time.LocalDateTime + +/** + * @author Jensen Qi + * @since 0.1.0 + */ +@DTO(CommitmentPO::class) +data class CommitmentDTO( + val id: Int, + val nodeId: Int, + val content: String, + val message: String, + val createTime: LocalDateTime, + val updateTime: LocalDateTime +) \ No newline at end of file diff --git a/common/src/main/kotlin/tech/cuda/woden/common/service/dto/WorkingTreeDTO.kt b/common/src/main/kotlin/tech/cuda/woden/common/service/dto/WorkingTreeDTO.kt new file mode 100644 index 00000000..832a3ba5 --- /dev/null +++ b/common/src/main/kotlin/tech/cuda/woden/common/service/dto/WorkingTreeDTO.kt @@ -0,0 +1,33 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package tech.cuda.woden.common.service.dto + +import tech.cuda.woden.annotation.pojo.DTO +import tech.cuda.woden.common.service.po.WorkingTreePO +import java.time.LocalDateTime + +/** + * @author Jensen Qi + * @since 0.1.0 + */ +@DTO(WorkingTreePO::class) +data class WorkingTreeDTO( + val id: Int, + val name: String, + val parentId: Int?, + val stage: String?, + val commitId: Int?, + val createTime: LocalDateTime, + val updateTime: LocalDateTime +) diff --git a/common/src/main/kotlin/tech/cuda/woden/common/service/po/CommitmentPO.kt b/common/src/main/kotlin/tech/cuda/woden/common/service/po/CommitmentPO.kt new file mode 100644 index 00000000..9f5ecf5e --- /dev/null +++ b/common/src/main/kotlin/tech/cuda/woden/common/service/po/CommitmentPO.kt @@ -0,0 +1,33 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package tech.cuda.woden.common.service.po + +import me.liuwj.ktorm.entity.Entity +import java.time.LocalDateTime + +/** + * @author Jensen Qi + * @since 0.1.0 + */ +interface CommitmentPO : Entity { + companion object : Entity.Factory() + + val id: Int + var nodeId: Int + var content: String + var message: String + var isRemove: Boolean + var createTime: LocalDateTime + var updateTime: LocalDateTime +} \ No newline at end of file diff --git a/common/src/main/kotlin/tech/cuda/woden/common/service/po/WorkingTreePO.kt b/common/src/main/kotlin/tech/cuda/woden/common/service/po/WorkingTreePO.kt new file mode 100644 index 00000000..4cc57dcd --- /dev/null +++ b/common/src/main/kotlin/tech/cuda/woden/common/service/po/WorkingTreePO.kt @@ -0,0 +1,34 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package tech.cuda.woden.common.service.po + +import me.liuwj.ktorm.entity.Entity +import java.time.LocalDateTime + +/** + * @author Jensen Qi + * @since 0.1.0 + */ +interface WorkingTreePO : Entity { + companion object : Entity.Factory() + + val id: Int + var name: String + var parentId: Int? + var stage: String? + var commitId: Int? + var isRemove: Boolean + var createTime: LocalDateTime + var updateTime: LocalDateTime +} \ No newline at end of file