From cb34b02113f15a997fef058c9c257b5fb4e05a1b Mon Sep 17 00:00:00 2001 From: HanZiyao <49408466+h56983577@users.noreply.github.com> Date: Thu, 9 Jan 2025 11:16:42 +0800 Subject: [PATCH 1/7] Hzy token multi client (#69) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix bugs in email sending and vm power managing. * Token 先从 Header 获取,再从 cookie 获取 --- .../src/main/kotlin/cn/edu/buaa/scs/auth/Token.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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..d0918c5 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] } From 2acb05e99873c7f8e22cd6685985b3730e0028f6 Mon Sep 17 00:00:00 2001 From: HanZiyao <49408466+h56983577@users.noreply.github.com> Date: Thu, 9 Jan 2025 17:02:22 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=E7=99=BB=E5=BD=95=E6=B5=81=E7=A8=8B?= =?UTF-8?q?=E5=93=8D=E5=BA=94=E6=B6=88=E6=81=AF=E6=94=B9=E4=B8=BA=E4=B8=AD?= =?UTF-8?q?=E6=96=87=20(#72)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/kotlin/cn/edu/buaa/scs/auth/Token.kt | 2 +- .../src/main/kotlin/cn/edu/buaa/scs/route/Auth.kt | 12 ++++++------ .../src/main/kotlin/cn/edu/buaa/scs/service/Auth.kt | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) 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 d0918c5..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 @@ -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/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/service/Auth.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Auth.kt index 5656260..1b5685c 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("账号未激活,或信息不完整,请重新激活账户") @@ -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() From 7cb9ed59eae6af42d601dcf80f60dafeb3b91205 Mon Sep 17 00:00:00 2001 From: HanZiyao <49408466+h56983577@users.noreply.github.com> Date: Sat, 11 Jan 2025 21:38:18 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E8=99=9A=E6=8B=9F=E6=9C=BA=E8=B0=83?= =?UTF-8?q?=E5=BA=A6=E9=97=AE=E9=A2=98=E3=80=81=E6=B7=BB=E5=8A=A0=E5=92=8C?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=94=A8=E6=88=B7bug=E3=80=81=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E5=AE=9E=E9=AA=8C=E4=BD=9C=E4=B8=9A=20(#75)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/models/CreateUserRequest.kt | 5 ++++- .../scs/controller/models/PatchUserRequest.kt | 4 +++- .../scs/kube/crd/v1alpha1/VirtualMachine.kt | 2 ++ .../kotlin/cn/edu/buaa/scs/route/Admin.kt | 3 ++- .../cn/edu/buaa/scs/route/Experiment.kt | 6 +++++ .../kotlin/cn/edu/buaa/scs/service/Admin.kt | 4 ++-- .../cn/edu/buaa/scs/service/Experiment.kt | 4 ++++ .../kotlin/cn/edu/buaa/scs/service/User.kt | 6 ++++- .../edu/buaa/scs/vm/vcenter/VCenterClient.kt | 9 +++++--- openapi/cloudapi_v2.yaml | 22 +++++++++++++++++++ 10 files changed, 56 insertions(+), 9 deletions(-) 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/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/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() 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/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/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/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/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/vm/vcenter/VCenterClient.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/vm/vcenter/VCenterClient.kt index 34acb48..bd79d23 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 @@ -4,6 +4,8 @@ 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.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,6 +16,9 @@ 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 object VCenterClient : IVMClient { @@ -52,9 +57,7 @@ object VCenterClient : IVMClient { } 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/openapi/cloudapi_v2.yaml b/openapi/cloudapi_v2.yaml index d7f963a..ec7320f 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: @@ -4731,6 +4747,9 @@ components: type: integer format: int32 description: '所在单位' + email: + type: string + description: '邮箱' role: type: integer format: int32 @@ -4806,6 +4825,9 @@ components: type: string nickname: type: string + departmentId: + type: integer + format: int32 ChangePasswordRequest: title: ChangePasswordRequest x-stoplight: From 289c307da43dd100fad0b5fd79685f018cb6cc34 Mon Sep 17 00:00:00 2001 From: HanZiyao <49408466+h56983577@users.noreply.github.com> Date: Sat, 11 Jan 2025 22:05:37 +0800 Subject: [PATCH 4/7] =?UTF-8?q?=E8=99=9A=E6=8B=9F=E6=9C=BA=E8=B0=83?= =?UTF-8?q?=E5=BA=A6=E9=97=AE=E9=A2=98=E3=80=81=E6=B7=BB=E5=8A=A0=E5=92=8C?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=94=A8=E6=88=B7bug=E3=80=81=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E5=AE=9E=E9=AA=8C=E4=BD=9C=E4=B8=9A=20(#77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 虚拟机调度问题、添加和修改用户bug、删除实验作业 * 虚拟机调度问题、添加和修改用户bug、删除实验作业 --- cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/service/Course.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) } } From d099263f8148ef3664c603a10753b311beafb575 Mon Sep 17 00:00:00 2001 From: HanZiyao <49408466+h56983577@users.noreply.github.com> Date: Fri, 28 Feb 2025 12:48:44 +0800 Subject: [PATCH 5/7] =?UTF-8?q?AI=20=E5=AF=B9=E8=AF=9D=E5=8A=A9=E6=89=8B?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=20(#80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + cloudapi-web/build.gradle.kts | 11 ++- .../models/ChatCompletionRequest.kt | 29 +++++++ .../ChatCompletionRequestMessagesInner.kt | 24 ++++++ .../scs/controller/models/ChatHistoryItem.kt | 30 +++++++ .../models/GetChatRecordsRequest.kt | 30 +++++++ .../buaa/scs/controller/plugins/Monitoring.kt | 2 +- .../buaa/scs/controller/plugins/Routing.kt | 1 + .../cn/edu/buaa/scs/model/ChatHistory.kt | 34 ++++++++ .../main/kotlin/cn/edu/buaa/scs/route/Chat.kt | 32 ++++++++ .../kotlin/cn/edu/buaa/scs/service/Chat.kt | 78 +++++++++++++++++++ .../cn/edu/buaa/scs/utils/ApplicationCall.kt | 3 + gradle.properties | 1 + openapi/cloudapi_v2.yaml | 64 +++++++++++++++ 14 files changed, 339 insertions(+), 2 deletions(-) create mode 100644 cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatCompletionRequest.kt create mode 100644 cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatCompletionRequestMessagesInner.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/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/.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-web/build.gradle.kts b/cloudapi-web/build.gradle.kts index 04a6fd3..05ecb55 100644 --- a/cloudapi-web/build.gradle.kts +++ b/cloudapi-web/build.gradle.kts @@ -20,8 +20,15 @@ kotlin { } repositories { + maven { setUrl("https://maven.aliyun.com/repository/central") } + maven { setUrl("https://maven.aliyun.com/repository/jcenter") } + maven { setUrl("https://maven.aliyun.com/repository/google") } + maven { setUrl("https://maven.aliyun.com/repository/gradle-plugin") } + maven { setUrl("https://maven.aliyun.com/repository/public") } + maven { setUrl("https://jitpack.io") } + gradlePluginPortal() + google() mavenCentral() - maven(url = "https://jitpack.io") } dependencies { @@ -43,6 +50,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") diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatCompletionRequest.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatCompletionRequest.kt new file mode 100644 index 0000000..82933ee --- /dev/null +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatCompletionRequest.kt @@ -0,0 +1,29 @@ +/** +* 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 stream + * @param detail + * @param messages + * @param customUid + */ +data class ChatCompletionRequest( + val chatId: kotlin.String, + val stream: kotlin.Boolean, + val detail: kotlin.Boolean, + val messages: kotlin.collections.List, + val customUid: kotlin.String? = null +) + diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatCompletionRequestMessagesInner.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatCompletionRequestMessagesInner.kt new file mode 100644 index 0000000..7f07884 --- /dev/null +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatCompletionRequestMessagesInner.kt @@ -0,0 +1,24 @@ +/** +* 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 role + * @param content + */ +data class ChatCompletionRequestMessagesInner( + val role: kotlin.String, + val content: kotlin.String +) + 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/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/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/model/ChatHistory.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/model/ChatHistory.kt new file mode 100644 index 0000000..d550be9 --- /dev/null +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/model/ChatHistory.kt @@ -0,0 +1,34 @@ +package cn.edu.buaa.scs.model + +import org.ktorm.database.Database +import org.ktorm.entity.Entity +import org.ktorm.entity.sequenceOf +import org.ktorm.schema.* + +interface ChatHistory : Entity, 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/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/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/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/gradle.properties b/gradle.properties index 6f11cfc..4827c9e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,3 @@ kotlin_version=1.7.10 kotlin.code.style=official +org.gradle.jvmargs=-Xmx4g \ No newline at end of file diff --git a/openapi/cloudapi_v2.yaml b/openapi/cloudapi_v2.yaml index ec7320f..8ef25a5 100644 --- a/openapi/cloudapi_v2.yaml +++ b/openapi/cloudapi_v2.yaml @@ -2725,6 +2725,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: @@ -4913,6 +4948,35 @@ 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 securitySchemes: Authorization: type: apiKey From 04a2b265ec4a78df5684f281f9ad4f1797f14125 Mon Sep 17 00:00:00 2001 From: HanZiyao <49408466+h56983577@users.noreply.github.com> Date: Tue, 4 Mar 2025 01:01:31 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E8=B5=84=E6=BA=90=E6=B1=A0=E5=90=8D?= =?UTF-8?q?=E7=A7=B0=E5=8F=AA=E8=83=BD=E4=B8=BA=E5=B0=8F=E5=86=99=E5=92=8C?= =?UTF-8?q?=E6=95=B0=E5=AD=97=20(#82)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 资源池名称只能为小写和数字 * 改为国外maven源 * 去掉构建过程中的内存限制 * 去掉创建k8s用户资源的步骤 --- cloudapi-web/build.gradle.kts | 9 +-------- .../kotlin/cn/edu/buaa/scs/kube/BusinessKubeClient.kt | 2 +- .../src/main/kotlin/cn/edu/buaa/scs/service/Project.kt | 2 +- gradle.properties | 3 +-- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/cloudapi-web/build.gradle.kts b/cloudapi-web/build.gradle.kts index 05ecb55..cbf338b 100644 --- a/cloudapi-web/build.gradle.kts +++ b/cloudapi-web/build.gradle.kts @@ -20,15 +20,8 @@ kotlin { } repositories { - maven { setUrl("https://maven.aliyun.com/repository/central") } - maven { setUrl("https://maven.aliyun.com/repository/jcenter") } - maven { setUrl("https://maven.aliyun.com/repository/google") } - maven { setUrl("https://maven.aliyun.com/repository/gradle-plugin") } - maven { setUrl("https://maven.aliyun.com/repository/public") } - maven { setUrl("https://jitpack.io") } - gradlePluginPortal() - google() mavenCentral() + maven(url = "https://jitpack.io") } dependencies { 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/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/gradle.properties b/gradle.properties index 4827c9e..c1bbeab 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,2 @@ kotlin_version=1.7.10 -kotlin.code.style=official -org.gradle.jvmargs=-Xmx4g \ No newline at end of file +kotlin.code.style=official \ No newline at end of file From bcbdf3d236d43526c2a893eeb9b85d0c047898b7 Mon Sep 17 00:00:00 2001 From: HanZiyao <49408466+h56983577@users.noreply.github.com> Date: Tue, 4 Mar 2025 01:01:42 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=E8=8E=B7=E5=8F=96=20vmware=20web=20?= =?UTF-8?q?=E8=AE=BF=E9=97=AE=E5=87=AD=E8=AF=81=20(#84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/edu/buaa/scs/model/TicketResponse.kt | 13 ++++++++ .../models/ChatCompletionRequest.kt | 29 ------------------ ...uestMessagesInner.kt => TicketResponse.kt} | 12 ++++---- .../main/kotlin/cn/edu/buaa/scs/route/Vm.kt | 5 ++++ .../main/kotlin/cn/edu/buaa/scs/service/Vm.kt | 4 +++ .../kotlin/cn/edu/buaa/scs/vm/VMModule.kt | 2 +- .../edu/buaa/scs/vm/vcenter/VCenterClient.kt | 5 ++++ openapi/cloudapi_v2.yaml | 30 +++++++++++++++++++ .../cn/edu/buaa/scs/vcenter/Connection.kt | 1 + .../cn/edu/buaa/scs/vcenter/VCenterRoute.kt | 5 ++++ .../cn/edu/buaa/scs/vcenter/VCenterWrapper.kt | 12 ++++++++ 11 files changed, 83 insertions(+), 35 deletions(-) create mode 100644 cloudapi-model/src/main/kotlin/cn/edu/buaa/scs/model/TicketResponse.kt delete mode 100644 cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatCompletionRequest.kt rename cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/{ChatCompletionRequestMessagesInner.kt => TicketResponse.kt} (60%) 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/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatCompletionRequest.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatCompletionRequest.kt deleted file mode 100644 index 82933ee..0000000 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatCompletionRequest.kt +++ /dev/null @@ -1,29 +0,0 @@ -/** -* 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 stream - * @param detail - * @param messages - * @param customUid - */ -data class ChatCompletionRequest( - val chatId: kotlin.String, - val stream: kotlin.Boolean, - val detail: kotlin.Boolean, - val messages: kotlin.collections.List, - val customUid: kotlin.String? = null -) - diff --git a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatCompletionRequestMessagesInner.kt b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/TicketResponse.kt similarity index 60% rename from cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatCompletionRequestMessagesInner.kt rename to cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/TicketResponse.kt index 7f07884..6052493 100644 --- a/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/ChatCompletionRequestMessagesInner.kt +++ b/cloudapi-web/src/main/kotlin/cn/edu/buaa/scs/controller/models/TicketResponse.kt @@ -14,11 +14,13 @@ package cn.edu.buaa.scs.controller.models /** * - * @param role - * @param content + * @param ticket 访问凭证 + * @param host 服务器主机地址 */ -data class ChatCompletionRequestMessagesInner( - val role: kotlin.String, - val content: kotlin.String +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/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/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/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 bd79d23..dc2bffe 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,6 +3,7 @@ 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 @@ -56,6 +57,10 @@ 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 { mysql.virtualMachines.find { (it.name eq name) and (it.applyId eq applyId) }?: throw vmNotFound(name) } diff --git a/openapi/cloudapi_v2.yaml b/openapi/cloudapi_v2.yaml index 8ef25a5..6ca0edb 100644 --- a/openapi/cloudapi_v2.yaml +++ b/openapi/cloudapi_v2.yaml @@ -1022,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 @@ -4977,6 +4998,15 @@ components: 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/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)