From f735f47affaeaae496364d5fc59d1283d2993fc3 Mon Sep 17 00:00:00 2001 From: HanZiyao Date: Mon, 4 Dec 2023 18:21:07 +0800 Subject: [PATCH 1/4] Fix bugs in email sending and vm power managing. --- .../src/main/kotlin/cn/edu/buaa/scs/utils/Email.kt | 9 +++++++-- .../cn/edu/buaa/scs/kube/crd/v1alpha1/VirtualMachine.kt | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/cloudapi-common/src/main/kotlin/cn/edu/buaa/scs/utils/Email.kt b/cloudapi-common/src/main/kotlin/cn/edu/buaa/scs/utils/Email.kt index 0eefb56..f7ccb2c 100644 --- a/cloudapi-common/src/main/kotlin/cn/edu/buaa/scs/utils/Email.kt +++ b/cloudapi-common/src/main/kotlin/cn/edu/buaa/scs/utils/Email.kt @@ -3,6 +3,8 @@ package cn.edu.buaa.scs.utils import cn.edu.buaa.scs.config.Constant import cn.edu.buaa.scs.config.globalConfig import java.util.* +import javax.activation.CommandMap +import javax.activation.MailcapCommandMap import javax.mail.Message import javax.mail.PasswordAuthentication import javax.mail.Session @@ -12,6 +14,11 @@ import javax.mail.internet.MimeMessage object Email { fun sendEmail(to: String, subject: String, content: String): Result = runCatching { + val mc: MailcapCommandMap = CommandMap.getDefaultCommandMap() as MailcapCommandMap + mc.addMailcap("text/html;; x-java-content-handler=com.sun.mail.handlers.text_html") + CommandMap.setDefaultCommandMap(mc) + Thread.currentThread().contextClassLoader = javaClass.classLoader + val config = globalConfig.email val properties = Properties().apply { @@ -34,8 +41,6 @@ object Email { setRecipients(Message.RecipientType.TO, InternetAddress.parse(to)) setSubject(subject) setContent(content, "text/html; charset=utf-8") - - } Transport.send(message) } diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/kube/crd/v1alpha1/VirtualMachine.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/kube/crd/v1alpha1/VirtualMachine.kt index 5c9fd83..f3af48e 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/kube/crd/v1alpha1/VirtualMachine.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/kube/crd/v1alpha1/VirtualMachine.kt @@ -216,13 +216,13 @@ class VirtualMachineReconciler(val client: KubernetesClient) : Reconciler Date: Sat, 18 Oct 2025 20:26:28 +0800 Subject: [PATCH 2/4] 11 --- .github/workflows/build-cloudapi-web.yaml | 2 +- .github/workflows/build-vcenter.yaml | 2 +- .gitignore | 2 + cloudapi-common/build.gradle.kts | 11 ++ cloudapi-model/build.gradle.kts | 11 ++ .../cn/edu/buaa/scs/model/TicketResponse.kt | 13 ++ cloudapi-web/build.gradle.kts | 13 ++ .../main/kotlin/cn/edu/buaa/scs/auth/Token.kt | 10 +- .../scs/controller/models/ChatHistoryItem.kt | 30 +++++ .../controller/models/CreateUserRequest.kt | 5 +- .../models/GetChatRecordsRequest.kt | 30 +++++ .../scs/controller/models/PatchUserRequest.kt | 4 +- .../scs/controller/models/TicketResponse.kt | 26 ++++ .../buaa/scs/controller/plugins/Monitoring.kt | 2 +- .../buaa/scs/controller/plugins/Routing.kt | 1 + .../edu/buaa/scs/kube/BusinessKubeClient.kt | 2 +- .../scs/kube/crd/v1alpha1/VirtualMachine.kt | 2 + .../cn/edu/buaa/scs/model/ChatHistory.kt | 34 +++++ .../kotlin/cn/edu/buaa/scs/route/Admin.kt | 3 +- .../main/kotlin/cn/edu/buaa/scs/route/Auth.kt | 12 +- .../main/kotlin/cn/edu/buaa/scs/route/Chat.kt | 32 +++++ .../cn/edu/buaa/scs/route/Experiment.kt | 6 + .../main/kotlin/cn/edu/buaa/scs/route/Vm.kt | 5 + .../kotlin/cn/edu/buaa/scs/service/Admin.kt | 4 +- .../kotlin/cn/edu/buaa/scs/service/Auth.kt | 10 +- .../kotlin/cn/edu/buaa/scs/service/Chat.kt | 78 ++++++++++++ .../kotlin/cn/edu/buaa/scs/service/Course.kt | 2 +- .../cn/edu/buaa/scs/service/Experiment.kt | 4 + .../kotlin/cn/edu/buaa/scs/service/Project.kt | 2 +- .../kotlin/cn/edu/buaa/scs/service/User.kt | 6 +- .../main/kotlin/cn/edu/buaa/scs/service/Vm.kt | 4 + .../cn/edu/buaa/scs/utils/ApplicationCall.kt | 3 + .../kotlin/cn/edu/buaa/scs/vm/VMModule.kt | 2 +- .../edu/buaa/scs/vm/vcenter/VCenterClient.kt | 25 +++- gradle.properties | 2 +- openapi/cloudapi_v2.yaml | 116 ++++++++++++++++++ vcenter/build.gradle.kts | 11 ++ .../cn/edu/buaa/scs/vcenter/Connection.kt | 1 + .../cn/edu/buaa/scs/vcenter/VCenterRoute.kt | 5 + .../cn/edu/buaa/scs/vcenter/VCenterWrapper.kt | 12 ++ 40 files changed, 512 insertions(+), 33 deletions(-) create mode 100644 cloudapi-model/src/main/kotlin/cn/edu/buaa/scs/model/TicketResponse.kt create mode 100644 cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatHistoryItem.kt create mode 100644 cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/GetChatRecordsRequest.kt create mode 100644 cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/TicketResponse.kt create mode 100644 cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/model/ChatHistory.kt create mode 100644 cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/route/Chat.kt create mode 100644 cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Chat.kt diff --git a/.github/workflows/build-cloudapi-web.yaml b/.github/workflows/build-cloudapi-web.yaml index 6985028..3b16422 100644 --- a/.github/workflows/build-cloudapi-web.yaml +++ b/.github/workflows/build-cloudapi-web.yaml @@ -20,7 +20,7 @@ jobs: - name: Build and Push Docker Image env: - image: ${{ secrets.ALIYUN_DOCKER_NAMESPACE }}/cloudapi-web + image: ${{ secrets.ALIYUN_DOCKER_REGISTRY }}/${{ secrets.ALIYUN_DOCKER_NAMESPACE }}/cloudapi-web tags: ${{ github.sha }} registry: ${{ secrets.ALIYUN_DOCKER_REGISTRY }} username: ${{ secrets.ALIYUN_DOCKER_USERNAME }} diff --git a/.github/workflows/build-vcenter.yaml b/.github/workflows/build-vcenter.yaml index 3901381..dfe14a2 100644 --- a/.github/workflows/build-vcenter.yaml +++ b/.github/workflows/build-vcenter.yaml @@ -20,7 +20,7 @@ jobs: - name: Build and Push Docker Image env: - image: ${{ secrets.ALIYUN_DOCKER_NAMESPACE }}/vcenter + image: ${{ secrets.ALIYUN_DOCKER_REGISTRY }}/${{ secrets.ALIYUN_DOCKER_NAMESPACE }}/vcenter tags: ${{ github.sha }} registry: ${{ secrets.ALIYUN_DOCKER_REGISTRY }} username: ${{ secrets.ALIYUN_DOCKER_USERNAME }} diff --git a/.gitignore b/.gitignore index bdf87c8..b7bcbc9 100644 --- a/.gitignore +++ b/.gitignore @@ -19,9 +19,11 @@ bin/ ### IntelliJ IDEA ### .idea +.kotlin *.iws *.iml *.ipr +*.log out/ !**/src/main/**/out/ !**/src/test/**/out/ diff --git a/cloudapi-common/build.gradle.kts b/cloudapi-common/build.gradle.kts index 40be325..cc0e175 100644 --- a/cloudapi-common/build.gradle.kts +++ b/cloudapi-common/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + plugins { id("java") @@ -63,8 +65,17 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter:5.8.1") testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") + implementation(kotlin("stdlib-jdk8")) } tasks.getByName("test") { useJUnitPlatform() } +val compileKotlin: KotlinCompile by tasks +compileKotlin.kotlinOptions { + jvmTarget = "1.8" +} +val compileTestKotlin: KotlinCompile by tasks +compileTestKotlin.kotlinOptions { + jvmTarget = "1.8" +} \ No newline at end of file diff --git a/cloudapi-model/build.gradle.kts b/cloudapi-model/build.gradle.kts index 98e7420..2b70739 100644 --- a/cloudapi-model/build.gradle.kts +++ b/cloudapi-model/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + plugins { id("java") kotlin("jvm") @@ -20,8 +22,17 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") + implementation(kotlin("stdlib-jdk8")) } tasks.getByName("test") { useJUnitPlatform() } +val compileKotlin: KotlinCompile by tasks +compileKotlin.kotlinOptions { + jvmTarget = "1.8" +} +val compileTestKotlin: KotlinCompile by tasks +compileTestKotlin.kotlinOptions { + jvmTarget = "1.8" +} \ No newline at end of file diff --git a/cloudapi-model/src/main/kotlin/cn/edu/buaa/scs/model/TicketResponse.kt b/cloudapi-model/src/main/kotlin/cn/edu/buaa/scs/model/TicketResponse.kt new file mode 100644 index 0000000..415abd8 --- /dev/null +++ b/cloudapi-model/src/main/kotlin/cn/edu/buaa/scs/model/TicketResponse.kt @@ -0,0 +1,13 @@ +package cn.edu.buaa.scs.model + +/** + * + * @param ticket 访问凭证 + * @param host 服务器主机地址 + */ +data class TicketResponse( + /* 访问凭证 */ + val ticket: kotlin.String? = null, + /* 服务器主机地址 */ + val host: kotlin.String? = null +) diff --git a/cloudapi-web/build.gradle.kts b/cloudapi-web/build.gradle.kts index 04a6fd3..65368bb 100644 --- a/cloudapi-web/build.gradle.kts +++ b/cloudapi-web/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + val kotlin_version: String by project plugins { @@ -43,6 +45,8 @@ dependencies { implementation("io.ktor:ktor-server-status-pages:$ktor_version") testImplementation("io.ktor:ktor-server-tests-jvm:$ktor_version") testImplementation("io.ktor:ktor-server-test-host-jvm:$ktor_version") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-slf4j:1.7.3") + implementation("ch.qos.logback:logback-classic:1.4.11") // Redis implementation("io.lettuce:lettuce-core:6.1.5.RELEASE") @@ -79,6 +83,7 @@ dependencies { // test testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlin_version") + implementation(kotlin("stdlib-jdk8")) } tasks { @@ -97,3 +102,11 @@ tasks { useJUnitPlatform() } } +val compileKotlin: KotlinCompile by tasks +compileKotlin.kotlinOptions { + jvmTarget = "1.8" +} +val compileTestKotlin: KotlinCompile by tasks +compileTestKotlin.kotlinOptions { + jvmTarget = "1.8" +} \ No newline at end of file diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/auth/Token.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/auth/Token.kt index bcb5e83..48b8f20 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/auth/Token.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/auth/Token.kt @@ -68,16 +68,16 @@ fun fetchToken(call: ApplicationCall) { else -> { // TODO 后续兼容JWT校验 - // 1. try to get token from cookies - possibleTokenKey.firstNotNullOfOrNull { call.request.cookies[it] } ?: - - // 2. try to get token from headers + // 1. try to get token from headers possibleTokenKey.firstNotNullOfOrNull { call.request.headers[it]?.let { auth -> if (auth.startsWith("Bearer")) auth.split(" ")[1] else auth } } ?: + // 2. try to get token from cookies + possibleTokenKey.firstNotNullOfOrNull { call.request.cookies[it] } ?: + // 3. try to get token from query parameters possibleTokenKey.firstNotNullOfOrNull { call.request.queryParameters[it] } @@ -110,7 +110,7 @@ fun fetchToken(call: ApplicationCall) { // redis uuid token authRedis.checkToken(token) ?: // error - throw throw AuthorizationException("incorrect token") + throw throw AuthorizationException("登录已过期,请重新登录") val user = User.id(userId) setUser(user) diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatHistoryItem.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatHistoryItem.kt new file mode 100644 index 0000000..fc94336 --- /dev/null +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatHistoryItem.kt @@ -0,0 +1,30 @@ +/** +* cloudapi_v2 +* buaa scs cloud api v2 +* +* The version of the OpenAPI document: 2.0 +* Contact: loheagn@icloud.com +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit the class manually. +*/ +package cn.edu.buaa.scs.controller.models + + +/** + * + * @param id + * @param chatId + * @param updateTime + * @param title + * @param top + */ +data class ChatHistoryItem( + val id: kotlin.Int? = null, + val chatId: kotlin.String? = null, + val updateTime: kotlin.Long? = null, + val title: kotlin.String? = null, + val top: kotlin.Boolean? = null +) + diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/CreateUserRequest.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/CreateUserRequest.kt index 04d6a3a..d5c4ae8 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/CreateUserRequest.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/CreateUserRequest.kt @@ -18,6 +18,7 @@ package cn.edu.buaa.scs.controller.models * @param departmentId 所在单位 * @param role 1 for student, 2 for teacher, 4 for admin * @param name 姓名 + * @param email 邮箱 */ data class CreateUserRequest( /* 学工号 */ @@ -27,6 +28,8 @@ data class CreateUserRequest( /* 1 for student, 2 for teacher, 4 for admin */ val role: kotlin.Int, /* 姓名 */ - val name: kotlin.String? = null + val name: kotlin.String? = null, + /* 邮箱 */ + val email: kotlin.String? = null ) diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/GetChatRecordsRequest.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/GetChatRecordsRequest.kt new file mode 100644 index 0000000..8ab9aa2 --- /dev/null +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/GetChatRecordsRequest.kt @@ -0,0 +1,30 @@ +/** +* cloudapi_v2 +* buaa scs cloud api v2 +* +* The version of the OpenAPI document: 2.0 +* Contact: loheagn@icloud.com +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit the class manually. +*/ +package cn.edu.buaa.scs.controller.models + + +/** + * + * @param chatId + * @param appId + * @param offset + * @param pageSize + * @param loadCustomFeedbacks + */ +data class GetChatRecordsRequest( + val chatId: kotlin.String, + val appId: kotlin.String? = null, + val offset: kotlin.Int? = null, + val pageSize: kotlin.Int? = null, + val loadCustomFeedbacks: kotlin.Boolean? = null +) + diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/PatchUserRequest.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/PatchUserRequest.kt index fc55485..9081152 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/PatchUserRequest.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/PatchUserRequest.kt @@ -17,10 +17,12 @@ package cn.edu.buaa.scs.controller.models * @param name * @param email * @param nickname + * @param departmentId */ data class PatchUserRequest( val name: kotlin.String? = null, val email: kotlin.String? = null, - val nickname: kotlin.String? = null + val nickname: kotlin.String? = null, + val departmentId: kotlin.Int? = null ) diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/TicketResponse.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/TicketResponse.kt new file mode 100644 index 0000000..6052493 --- /dev/null +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/TicketResponse.kt @@ -0,0 +1,26 @@ +/** +* cloudapi_v2 +* buaa scs cloud api v2 +* +* The version of the OpenAPI document: 2.0 +* Contact: loheagn@icloud.com +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit the class manually. +*/ +package cn.edu.buaa.scs.controller.models + + +/** + * + * @param ticket 访问凭证 + * @param host 服务器主机地址 + */ +data class TicketResponse( + /* 访问凭证 */ + val ticket: kotlin.String? = null, + /* 服务器主机地址 */ + val host: kotlin.String? = null +) + diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/plugins/Monitoring.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/plugins/Monitoring.kt index 242c4d4..bd61f73 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/plugins/Monitoring.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/plugins/Monitoring.kt @@ -41,7 +41,7 @@ fun Application.configureMonitoring() { pathDescription = "", headers = call.request.headers.toMap(), version = version, - realIp = call.request.headers["X-Custom-Remote-Addr"] ?: call.request.origin.host, + realIp = call.request.headers["X-Custom-Remote-Addr"] ?: call.request.origin.localHost, userAgent = userAgent, ) val logRecordResp = LogRecordResp( diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/plugins/Routing.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/plugins/Routing.kt index 2afe8c2..5cd8445 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/plugins/Routing.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/plugins/Routing.kt @@ -11,6 +11,7 @@ fun Application.configureRouting() { route("/api/v2") { authRoute() userRoute() + chatRoute() courseRoute() experimentRoute() fileRoute() diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/kube/BusinessKubeClient.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/kube/BusinessKubeClient.kt index 4726cf9..9982db0 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/kube/BusinessKubeClient.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/kube/BusinessKubeClient.kt @@ -91,7 +91,7 @@ spec: apiVersion: cloudapi.scs.buaa.edu.cn/v1alpha1 kind: ResourcePool metadata: - name: personal-$userId + name: personal-${userId.lowercase()} labels: owner: $userId spec: diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/kube/crd/v1alpha1/VirtualMachine.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/kube/crd/v1alpha1/VirtualMachine.kt index 31600f9..295b6df 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/kube/crd/v1alpha1/VirtualMachine.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/kube/crd/v1alpha1/VirtualMachine.kt @@ -168,6 +168,8 @@ class VirtualMachineReconciler(val client: KubernetesClient) : Reconciler, IEntity { + var id: Int + var userId: String + var chatId: String + var updateTime: Long + var title: String + var top: Boolean + + companion object : Entity.Factory() + + override fun entityId(): IntOrString { + return IntOrString(this.id) + } +} + +object ChatHistories : Table("chat_history") { + val id = int("id").primaryKey().bindTo { it.id } + val userId = varchar("userId").bindTo { it.userId } + val chatId = varchar("chatId").bindTo { it.chatId } + val updateTime = long("updateTime").bindTo { it.updateTime } + val title = varchar("title").bindTo { it.title } + val top = boolean("top").bindTo { it.top } +} + +@Suppress("unused") +val Database.chatHistory + get() = this.sequenceOf(ChatHistories) diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/route/Admin.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/route/Admin.kt index 9025a37..45b2b89 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/route/Admin.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/route/Admin.kt @@ -16,8 +16,9 @@ fun Route.adminRoute() { val req = call.receive() val role = UserRole.fromLevel(req.role) val name = req.name + val email = req.email val departmentId = req.departmentId - call.respond(convertUserModel(call.admin.addUser(req.id, name, role, departmentId))) + call.respond(convertUserModel(call.admin.addUser(req.id, name, role, email, departmentId))) } delete { diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/route/Auth.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/route/Auth.kt index b3034c9..b2d5abe 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/route/Auth.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/route/Auth.kt @@ -39,7 +39,7 @@ fun Route.authRoute() { route("buaaSSOLogin") { post { - val ssoToken = call.request.queryParameters["ssoToken"] ?: throw BadRequestException("ssoToken is required") + val ssoToken = call.request.queryParameters["ssoToken"] ?: throw BadRequestException("请传入统一认证 token") call.respond( call.auth.buaaSSOLogin(ssoToken) ) @@ -55,8 +55,8 @@ fun Route.authRoute() { route("/tokenInfo") { post { val req = call.receiveParameters() - val token = req["token"] ?: throw BadRequestException("token is required") - val service = req["service"] ?: throw BadRequestException("service is required") + val token = req["token"] ?: throw BadRequestException("请传入统一认证 token") + val service = req["service"] ?: throw BadRequestException("请传入统一认证服务名") call.respond( call.auth.getTokenInfo(token, service) ) @@ -65,9 +65,9 @@ fun Route.authRoute() { route("/checkPermission") { get { - val entityType = call.parameters["entityType"] ?: throw BadRequestException("entityType is required") - val entityId = call.parameters["entityId"] ?: throw BadRequestException("entityId is required") - val action = call.parameters["action"] ?: throw BadRequestException("action is required") + val entityType = call.parameters["entityType"] ?: throw BadRequestException("请传入对象类型") + val entityId = call.parameters["entityId"] ?: throw BadRequestException("请传入对象Id") + val action = call.parameters["action"] ?: throw BadRequestException("请传入动作") call.respond(call.auth.checkPermission(entityType, entityId, action)) } } diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/route/Chat.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/route/Chat.kt new file mode 100644 index 0000000..d48406e --- /dev/null +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/route/Chat.kt @@ -0,0 +1,32 @@ +package cn.edu.buaa.scs.route + +import cn.edu.buaa.scs.controller.models.ChatHistoryItem +import cn.edu.buaa.scs.controller.models.GetChatRecordsRequest +import cn.edu.buaa.scs.service.chat +import cn.edu.buaa.scs.utils.userId +import io.ktor.server.application.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* + +fun Route.chatRoute() { + route("/chat") { + route("/history") { + get { + call.respond(call.chat.getChatHistoryByUserId(call.userId())) + } + + post { + val chatHistoryItem = call.receive() + call.chat.createChatHistory(call.userId(), chatHistoryItem) + call.respond("OK") + } + } + + post("/records") { + val requestBody = call.receive() + val records = call.chat.getChatRecords(requestBody) + call.respond(records) + } + } +} diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/route/Experiment.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/route/Experiment.kt index 95eb0e9..08483fa 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/route/Experiment.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/route/Experiment.kt @@ -55,6 +55,12 @@ fun Route.experimentRoute() { call.respond(call.convertExperimentResponse(experiment)) } + delete { + val experimentId = call.getExpIdFromPath() + call.experiment.deleteById(experimentId) + call.respond("OK") + } + route("/assignments") { get { call.respond( diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/route/Vm.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/route/Vm.kt index c9441e9..8bd4c9e 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/route/Vm.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/route/Vm.kt @@ -43,6 +43,11 @@ fun Route.vmRoute() { } } + post("/ticket") { + val vmId = call.getVmIdFromPath() + val ticketResponse = call.vm.getWebTicket(vmId) + call.respond(ticketResponse) + } } route("/template") { diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Admin.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Admin.kt index b25e924..a87d6ee 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Admin.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Admin.kt @@ -17,12 +17,12 @@ val ApplicationCall.admin: AdminService class AdminService(val call: ApplicationCall) : IService { companion object : IService.Caller() - fun addUser(id: String, name: String?, role: UserRole, departmentId: Int): User { + fun addUser(id: String, name: String?, role: UserRole, email: String?, departmentId: Int): User { if (!call.user().isAdmin()) { throw AuthorizationException("only admin can add user") } - return User.createNewUnActiveUser(id, name, role, departmentId) + return User.createNewUnActiveUser(id, name, role, email, departmentId) } fun deleteUsers(userIds: List) { diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Auth.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Auth.kt index 5656260..6e73863 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Auth.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Auth.kt @@ -73,11 +73,11 @@ class AuthService(val call: ApplicationCall) : IService { ): LoginUserResponse { // check useId if (!mysql.users.exists { it.id.eq(userId) }) { - throw BadRequestException("") + throw BadRequestException("用户名或密码错误") } // check password val user = mysql.users.find { it.id.eq(userId) and it.password.eq(passwordHash) } - ?: throw BadRequestException("") + ?: throw BadRequestException("用户名或密码错误") // check active if (!user.isAccepted) { throw BadRequestException("账号未激活,或信息不完整,请重新激活账户") @@ -136,7 +136,7 @@ class AuthService(val call: ApplicationCall) : IService { private suspend fun afterLogin(token: String, user: User): LoginUserResponse { if (user.paasToken.isBlank()) { - call.project.createUser(user.id) +// call.project.createUser(user.id) } // insert token in redis (just for compatibility with older platforms) @@ -194,7 +194,7 @@ class AuthService(val call: ApplicationCall) : IService { fun sendActiveEmail(id: String, name: String, email: String) { val user = User.id(id) if (user.isAccepted) { - throw cn.edu.buaa.scs.error.BadRequestException("the user is already active") + throw cn.edu.buaa.scs.error.BadRequestException("用户已经激活") } val token = "${user.id}${user.password}${System.currentTimeMillis()}".md5() + UlidCreator.getUlid().toString() @@ -291,7 +291,7 @@ class AuthService(val call: ApplicationCall) : IService { user.acceptTime = System.currentTimeMillis().toString() user.flushChanges() - call.project.createUser(user) +// call.project.createUser(user) return afterLogin(generateRSAToken(user.id), user) } diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Chat.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Chat.kt new file mode 100644 index 0000000..8b62c60 --- /dev/null +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Chat.kt @@ -0,0 +1,78 @@ +package cn.edu.buaa.scs.service + +import cn.edu.buaa.scs.controller.models.ChatHistoryItem +import cn.edu.buaa.scs.controller.models.GetChatRecordsRequest +import cn.edu.buaa.scs.model.* +import cn.edu.buaa.scs.storage.mysql +import cn.edu.buaa.scs.utils.HttpClientWrapper +import io.ktor.client.* +import io.ktor.client.engine.cio.* +import io.ktor.client.plugins.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.request.* +import io.ktor.http.* +import io.ktor.serialization.jackson.* +import io.ktor.server.application.* +import org.ktorm.dsl.* +import org.ktorm.entity.* + +val ApplicationCall.chat + get() = ChatService.getSvc(this) { ChatService(this) } + +class ChatService(val call: ApplicationCall) : IService { + companion object : IService.Caller() + + internal val client by lazy { + HttpClientWrapper( + HttpClient(CIO) { + install(ContentNegotiation) { // 确保 JSON 序列化功能可用 + jackson() + } + defaultRequest { + header(HttpHeaders.Authorization, "Bearer fastgpt-sbHq9MGmKbijSJZe3l43BcFWUxNlkcISo1SSBqAHKsqXv8bGQoSIGh28Vw4Dryeu") + } + }, + basePath = "http://10.251.254.178:3000/api" + ) + } + + // 获取指定用户的聊天记录 + fun getChatHistoryByUserId(userId: String): List { + return mysql.chatHistory.filter { it.userId eq userId } + .map { convertToChatHistoryItem(it) }.toList() + } + + // 创建新的聊天记录 + fun createChatHistory(userId: String, chatHistoryItem: ChatHistoryItem): Int { + return mysql.insertAndGenerateKey(ChatHistories) { + set(ChatHistories.userId, userId) + set(ChatHistories.chatId, chatHistoryItem.chatId) + set(ChatHistories.updateTime, chatHistoryItem.updateTime) + set(ChatHistories.title, chatHistoryItem.title) + set(ChatHistories.top, chatHistoryItem.top) + } as Int + } + + // 获取聊天记录 + suspend fun getChatRecords(request: GetChatRecordsRequest): String { + val body = GetChatRecordsRequest( + chatId = request.chatId, + appId = "679e3b3b5fbd929e37095abc", + offset = 0, + pageSize = 30, + loadCustomFeedbacks = false, + ) + val response = client.post("/core/chat/getPaginationRecords", body) + return response.getOrThrow() + } + + private fun convertToChatHistoryItem(chatHistory: ChatHistory): ChatHistoryItem { + return ChatHistoryItem( + id = chatHistory.id, + chatId = chatHistory.chatId, + updateTime = chatHistory.updateTime, + title = chatHistory.title, + top = chatHistory.top + ) + } +} diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Course.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Course.kt index bbe7d4d..39442fc 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Course.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Course.kt @@ -187,7 +187,7 @@ class CourseService(val call: ApplicationCall) : IService { // make sure students exist studentIdList.forEachAsync { studentId -> if (!mysql.users.exists { it.id.inList(studentId.lowerUpperNormal()) }) { - User.createNewUnActiveUser(studentId, null, UserRole.STUDENT, 0) + User.createNewUnActiveUser(studentId, null, UserRole.STUDENT, "", 0) } } diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Experiment.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Experiment.kt index 2b97ea6..172d079 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Experiment.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Experiment.kt @@ -69,6 +69,10 @@ class ExperimentService(val call: ApplicationCall) : IService, FileService.FileD return experiment } + fun deleteById(expId: Int) { + mysql.delete(Experiments) { it.id eq expId } + } + private fun patchExperimentPeerInfo( experiment: Experiment, peerDescription: String?, diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Project.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Project.kt index 4461565..79bf2e7 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Project.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Project.kt @@ -68,7 +68,7 @@ class ProjectService(val call: ApplicationCall) : IService, FileService.FileDeco } suspend fun createUser(userID: String) { - createUser(User.id(userID)) +// createUser(User.id(userID)) } suspend fun createUser(user: User) { diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/User.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/User.kt index 61c94f7..6463b65 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/User.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/User.kt @@ -24,11 +24,12 @@ fun User.Companion.getUerListByIdList(idList: List): List { else mysql.users.filter { it.id.inList(idList) }.toList() } -fun User.Companion.createNewUnActiveUser(id: String, name: String?, role: UserRole, departmentId: Int): User { +fun User.Companion.createNewUnActiveUser(id: String, name: String?, role: UserRole, email: String?, departmentId: Int): User { val user = User { this.id = id this.name = name ?: "未激活用户" this.role = role + this.email = email ?: "" this.departmentId = departmentId } mysql.users.add(user) @@ -102,6 +103,9 @@ class UserService(val call: ApplicationCall) : IService { if (req.nickname != null) { user.nickName = req.nickname } + if (req.departmentId != null) { + user.departmentId = req.departmentId + } user.flushChanges() } diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Vm.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Vm.kt index 3934853..dd3f4c0 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Vm.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Vm.kt @@ -54,6 +54,10 @@ class VmService(val call: ApplicationCall) : IService { return sfClient.getHosts().getOrThrow() } + suspend fun getWebTicket(uuid: String): TicketResponse { + return vmClient.getWebTicket(uuid).getOrThrow() + } + fun getPersonalVms(): List { val vmApplyList = mysql.vmApplyList.filter { ((it.studentId eq call.userId()) or (it.teacherId eq call.userId())) and (it.experimentId eq 0) } diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/utils/ApplicationCall.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/utils/ApplicationCall.kt index 57403aa..bc15f38 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/utils/ApplicationCall.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/utils/ApplicationCall.kt @@ -3,10 +3,13 @@ package cn.edu.buaa.scs.utils import cn.edu.buaa.scs.model.User +import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.plugins.* import io.ktor.server.plugins.callid.* import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.utils.io.* fun ApplicationCall.headerString(key: String): String = this.request.header(key) ?: throw BadRequestException("missing header: $key") diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/vm/VMModule.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/vm/VMModule.kt index 3d8d94a..8069dbb 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/vm/VMModule.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/vm/VMModule.kt @@ -5,7 +5,7 @@ import cn.edu.buaa.scs.vm.sangfor.SangforClient import cn.edu.buaa.scs.vm.vcenter.VCenterClient import io.ktor.server.application.* -lateinit var vmClient: IVMClient +lateinit var vmClient: VCenterClient lateinit var sfClient: SangforClient @Suppress("unused") diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/vm/vcenter/VCenterClient.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/vm/vcenter/VCenterClient.kt index 34acb48..b1cff89 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/vm/vcenter/VCenterClient.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/vm/vcenter/VCenterClient.kt @@ -3,7 +3,10 @@ package cn.edu.buaa.scs.vm.vcenter import cn.edu.buaa.scs.config.globalConfig import cn.edu.buaa.scs.error.NotFoundException import cn.edu.buaa.scs.model.Host +import cn.edu.buaa.scs.model.TicketResponse import cn.edu.buaa.scs.model.VirtualMachine +import cn.edu.buaa.scs.model.virtualMachines +import cn.edu.buaa.scs.storage.mysql import cn.edu.buaa.scs.utils.HttpClientWrapper import cn.edu.buaa.scs.utils.schedule.waitForDone import cn.edu.buaa.scs.vm.* @@ -14,7 +17,12 @@ import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.request.* import io.ktor.http.* import io.ktor.serialization.jackson.* +import org.ktorm.dsl.and +import org.ktorm.dsl.eq +import org.ktorm.entity.find import org.ktorm.jackson.KtormModule +import java.security.cert.X509Certificate +import javax.net.ssl.X509TrustManager object VCenterClient : IVMClient { @@ -32,6 +40,15 @@ object VCenterClient : IVMClient { install(HttpTimeout) { requestTimeoutMillis = 10000L } + engine { + https { + trustManager = object : X509TrustManager { + override fun getAcceptedIssuers(): Array = arrayOf() + override fun checkClientTrusted(chain: Array, authType: String) {} + override fun checkServerTrusted(chain: Array, authType: String) {} + } + } + } }, basePath = globalConfig.vcenter.serviceUrl ) @@ -51,10 +68,12 @@ object VCenterClient : IVMClient { client.get("/vm/$uuid").getOrThrow() } + suspend fun getWebTicket(uuid: String): Result = runCatching { + client.post("/vm/$uuid/ticket").getOrThrow() + } + override suspend fun getVMByName(name: String, applyId: String): Result = runCatching { - getAllVMs().getOrElse { listOf() }.find { vm -> - vm.name == name && vm.applyId == applyId - } ?: throw vmNotFound(name) + mysql.virtualMachines.find { (it.name eq name) and (it.applyId eq applyId) }?: throw vmNotFound(name) } override suspend fun powerOnSync(uuid: String): Result = runCatching { diff --git a/gradle.properties b/gradle.properties index 6f11cfc..c1bbeab 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ kotlin_version=1.7.10 -kotlin.code.style=official +kotlin.code.style=official \ No newline at end of file diff --git a/openapi/cloudapi_v2.yaml b/openapi/cloudapi_v2.yaml index d7f963a..6ca0edb 100644 --- a/openapi/cloudapi_v2.yaml +++ b/openapi/cloudapi_v2.yaml @@ -90,6 +90,22 @@ paths: $ref: '#/components/schemas/PutExperimentRequest' security: - Authorization: [] + delete: + summary: 删除一项实验(作业) + tags: + - 作业 + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ExperimentResponse' + operationId: delete-experiment-experimentId + description: 删除一项实验(作业) + security: + - Authorization: [ ] + parameters: [ ] '/experiment/{experimentId}/assignment': parameters: - schema: @@ -1006,6 +1022,27 @@ paths: in: query name: sync description: 表示该请求是否同步返回,默认为false,即默认异步。client需要在后续查询具体的执行情况 + '/vm/{vmId}/ticket': + parameters: + - schema: + type: string + name: vmId + in: path + required: true + description: vm uuid + post: + summary: 创建 Web 访问凭证 + tags: + - 虚拟机 + description: 创建虚拟机的 Web 访问凭证,返回 ticket 和 host。 + operationId: post-vm-vmId-ticket + responses: + '200': + description: 成功创建访问凭证 + content: + application/json: + schema: + $ref: '#/components/schemas/TicketResponse' /vms: get: summary: get Virtual Machine list @@ -2709,6 +2746,41 @@ paths: description: 教师获取自己的全部助教 security: - Authorization: [] + /chat/history: + get: + summary: 按用户 id 获取历史聊天 + responses: + '200': + description: 聊天历史获取成功 + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ChatHistoryItem' + post: + summary: 新增一条聊天记录 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ChatHistoryItem' + responses: + '200': + description: 成功保存历史记录 + /chat/records: + post: + summary: 获取某个聊天的聊天记录 + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/GetChatRecordsRequest" + responses: + "200": + description: 成功获取到聊天记录 components: schemas: AssignmentResponse: @@ -4731,6 +4803,9 @@ components: type: integer format: int32 description: '所在单位' + email: + type: string + description: '邮箱' role: type: integer format: int32 @@ -4806,6 +4881,9 @@ components: type: string nickname: type: string + departmentId: + type: integer + format: int32 ChangePasswordRequest: title: ChangePasswordRequest x-stoplight: @@ -4891,6 +4969,44 @@ components: type: string required: - studentId + ChatHistoryItem: + type: object + properties: + id: + type: integer + chatId: + type: string + updateTime: + type: integer + format: int64 + title: + type: string + top: + type: boolean + GetChatRecordsRequest: + type: object + required: + - chatId + properties: + appId: + type: string + chatId: + type: string + offset: + type: integer + pageSize: + type: integer + loadCustomFeedbacks: + type: boolean + TicketResponse: + type: object + properties: + ticket: + type: string + description: 访问凭证 + host: + type: string + description: 服务器主机地址 securitySchemes: Authorization: type: apiKey diff --git a/vcenter/build.gradle.kts b/vcenter/build.gradle.kts index b4a53e6..13a1f31 100644 --- a/vcenter/build.gradle.kts +++ b/vcenter/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + plugins { id("java") application @@ -37,6 +39,7 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") + implementation(kotlin("stdlib-jdk8")) } tasks { @@ -55,3 +58,11 @@ tasks { useJUnitPlatform() } } +val compileKotlin: KotlinCompile by tasks +compileKotlin.kotlinOptions { + jvmTarget = "1.8" +} +val compileTestKotlin: KotlinCompile by tasks +compileTestKotlin.kotlinOptions { + jvmTarget = "1.8" +} \ No newline at end of file diff --git a/vcenter/src/main/kotlin/cn/edu/buaa/scs/vcenter/Connection.kt b/vcenter/src/main/kotlin/cn/edu/buaa/scs/vcenter/Connection.kt index 02ac38f..fce0932 100644 --- a/vcenter/src/main/kotlin/cn/edu/buaa/scs/vcenter/Connection.kt +++ b/vcenter/src/main/kotlin/cn/edu/buaa/scs/vcenter/Connection.kt @@ -4,6 +4,7 @@ import com.vmware.photon.controller.model.adapters.vsphere.util.connection.Conne import com.vmware.photon.controller.model.adapters.vsphere.util.connection.GetMoRef import com.vmware.vim25.ManagedObjectReference import com.vmware.vim25.RetrieveOptions +import com.vmware.vim25.VirtualMachineTicket fun Connection.getMoRef(): GetMoRef { return GetMoRef(this) diff --git a/vcenter/src/main/kotlin/cn/edu/buaa/scs/vcenter/VCenterRoute.kt b/vcenter/src/main/kotlin/cn/edu/buaa/scs/vcenter/VCenterRoute.kt index fb6f4b2..f85f2db 100644 --- a/vcenter/src/main/kotlin/cn/edu/buaa/scs/vcenter/VCenterRoute.kt +++ b/vcenter/src/main/kotlin/cn/edu/buaa/scs/vcenter/VCenterRoute.kt @@ -59,6 +59,11 @@ fun Application.vcenterRouting() { VCenterWrapper.convertVMToTemplate(call.getVmUuid()).getOrThrow() call.respond("OK") } + + post("/ticket") { + val ticketResponse = VCenterWrapper.getWebTicket(call.getVmUuid()) + call.respond(ticketResponse) + } } route("/health") { diff --git a/vcenter/src/main/kotlin/cn/edu/buaa/scs/vcenter/VCenterWrapper.kt b/vcenter/src/main/kotlin/cn/edu/buaa/scs/vcenter/VCenterWrapper.kt index cdeef06..37cac5a 100644 --- a/vcenter/src/main/kotlin/cn/edu/buaa/scs/vcenter/VCenterWrapper.kt +++ b/vcenter/src/main/kotlin/cn/edu/buaa/scs/vcenter/VCenterWrapper.kt @@ -4,6 +4,7 @@ import cn.edu.buaa.scs.config.globalConfig import cn.edu.buaa.scs.model.VirtualMachine import cn.edu.buaa.scs.model.VirtualMachineExtraInfo import cn.edu.buaa.scs.model.applyExtraInfo +import cn.edu.buaa.scs.model.TicketResponse import cn.edu.buaa.scs.utils.jsonMapper import cn.edu.buaa.scs.utils.logger import cn.edu.buaa.scs.vm.ConfigVmOptions @@ -360,6 +361,17 @@ object VCenterWrapper { }.getOrThrow() } + suspend fun getWebTicket(uuid: String): TicketResponse { + return baseSyncTask { connection -> + val vmRef = connection.getVmRefByUuid(uuid) + val vmTicket = connection.vimPort.acquireTicket(vmRef, "webmks") + TicketResponse( + ticket = vmTicket.ticket, + host = vmTicket.host + ) + }.getOrThrow() + } + suspend fun convertVMToTemplate(uuid: String): Result { return baseSyncTask { connection -> val vmRef = connection.getVmRefByUuid(uuid) From 5b90a4108b140f20fec5264c388cc0db62678c10 Mon Sep 17 00:00:00 2001 From: HanZiyao Date: Sat, 18 Oct 2025 21:10:14 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=E8=99=9A=E6=8B=9F=E6=9C=BA?= =?UTF-8?q?=E8=B0=83=E5=BA=A6=E6=B5=81=E7=A8=8B=E6=89=93=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/edu/buaa/scs/kube/crd/v1alpha1/VirtualMachine.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/kube/crd/v1alpha1/VirtualMachine.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/kube/crd/v1alpha1/VirtualMachine.kt index 295b6df..ef33ca4 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/kube/crd/v1alpha1/VirtualMachine.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/kube/crd/v1alpha1/VirtualMachine.kt @@ -162,13 +162,16 @@ class VirtualMachineReconciler(val client: KubernetesClient) : Reconciler().rescheduleAfter(10000L) } val vmModel = runBlocking { if (createVmProcessMutex.tryLock(vm)) { try { + logger("vm-reconcile")().info { "Start creating VirtualMachine: ${vm.spec.name}" } vmClient.createVM(vm.spec.toCreateVmOptions()).getOrThrow() } catch (e: Throwable) { + logger("vm-reconcile")().error { "Creat VirtualMachine ${vm.spec.name} failed: ${e.localizedMessage}" } null } finally { createVmProcessMutex.unlock(vm) @@ -219,12 +222,14 @@ class VirtualMachineReconciler(val client: KubernetesClient) : Reconciler Date: Sat, 18 Oct 2025 22:32:04 +0800 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20=E8=99=9A=E6=8B=9F=E6=9C=BA?= =?UTF-8?q?=E8=B0=83=E5=BA=A6=E6=B5=81=E7=A8=8B=E6=89=93=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/edu/buaa/scs/kube/crd/v1alpha1/VirtualMachine.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/kube/crd/v1alpha1/VirtualMachine.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/kube/crd/v1alpha1/VirtualMachine.kt index ef33ca4..34850bc 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/kube/crd/v1alpha1/VirtualMachine.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/kube/crd/v1alpha1/VirtualMachine.kt @@ -171,7 +171,7 @@ class VirtualMachineReconciler(val client: KubernetesClient) : Reconciler