备份代码
This commit is contained in:
parent
d17dbeeda4
commit
95af56f9c1
|
@ -29,6 +29,7 @@ repositories {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("io.ktor:ktor-server-core-jvm")
|
implementation("io.ktor:ktor-server-core-jvm")
|
||||||
implementation("io.ktor:ktor-server-auth-jvm")
|
implementation("io.ktor:ktor-server-auth-jvm")
|
||||||
|
implementation("io.ktor:ktor-server-auth-jwt:$ktor_version")
|
||||||
implementation("io.ktor:ktor-server-content-negotiation-jvm")
|
implementation("io.ktor:ktor-server-content-negotiation-jvm")
|
||||||
implementation("io.ktor:ktor-serialization-gson-jvm")
|
implementation("io.ktor:ktor-serialization-gson-jvm")
|
||||||
implementation("io.ktor:ktor-server-freemarker-jvm")
|
implementation("io.ktor:ktor-server-freemarker-jvm")
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package echo.org.application.route
|
package echo.org.application.route
|
||||||
|
|
||||||
import echo.org.domain.*
|
import echo.org.domain.*
|
||||||
import echo.org.instructure.AliyunOssFileServiceImpl
|
|
||||||
import echo.org.instructure.DocumentRepositoryImpl
|
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import io.ktor.http.content.*
|
import io.ktor.http.content.*
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
|
@ -10,7 +8,6 @@ import io.ktor.server.freemarker.*
|
||||||
import io.ktor.server.request.*
|
import io.ktor.server.request.*
|
||||||
import io.ktor.server.response.*
|
import io.ktor.server.response.*
|
||||||
import io.ktor.server.routing.*
|
import io.ktor.server.routing.*
|
||||||
import org.koin.dsl.module
|
|
||||||
import org.koin.ktor.ext.inject
|
import org.koin.ktor.ext.inject
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
|
@ -20,11 +17,18 @@ import java.time.LocalDateTime
|
||||||
|
|
||||||
fun Route.handleDocuments() {
|
fun Route.handleDocuments() {
|
||||||
val documentRepository by inject<DocumentRepository>()
|
val documentRepository by inject<DocumentRepository>()
|
||||||
|
val folderRepository by inject<FolderRepository>()
|
||||||
val fileOSS by inject<FileOSS>()
|
val fileOSS by inject<FileOSS>()
|
||||||
route("/documents") {
|
route("/documents") {
|
||||||
get("") {
|
get("") {
|
||||||
val res = documentRepository.pageQuery(DocumentQueryParam())
|
call.respond(FreeMarkerContent("home.ftl", null, "e"))
|
||||||
call.respond(FreeMarkerContent("index.ftl", res.results, "e"))
|
}
|
||||||
|
get("/files") {
|
||||||
|
val topFolders = folderRepository.searchTopFolders()
|
||||||
|
val dataModel = mapOf(
|
||||||
|
"topFolders" to topFolders
|
||||||
|
)
|
||||||
|
call.respond(FreeMarkerContent("files.ftl", dataModel, "e"))
|
||||||
}
|
}
|
||||||
post("/add") {
|
post("/add") {
|
||||||
// 添加一个文件
|
// 添加一个文件
|
||||||
|
@ -90,5 +94,9 @@ fun Route.handleDocuments() {
|
||||||
val pageQuery = documentRepository.pageQuery(documentQueryParam)
|
val pageQuery = documentRepository.pageQuery(documentQueryParam)
|
||||||
call.respond(Result.success(pageQuery))
|
call.respond(Result.success(pageQuery))
|
||||||
}
|
}
|
||||||
|
post("/folder/top") {
|
||||||
|
val topFolders = folderRepository.searchTopFolders()
|
||||||
|
call.respond(Result.success(topFolders))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package echo.org.application.route
|
||||||
|
|
||||||
|
import echo.org.plugins.JWT_AUTH
|
||||||
|
import io.ktor.server.auth.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
|
||||||
|
fun Route.handleUsers() {
|
||||||
|
|
||||||
|
authenticate(JWT_AUTH) {
|
||||||
|
post("/login") {}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,15 @@
|
||||||
package echo.org.domain
|
package echo.org.domain
|
||||||
|
|
||||||
import echo.org.instructure.Documents
|
|
||||||
import org.jetbrains.exposed.dao.LongEntity
|
|
||||||
import org.jetbrains.exposed.dao.LongEntityClass
|
|
||||||
import org.jetbrains.exposed.dao.id.EntityID
|
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
open class BaseDoc(
|
||||||
|
var id: Long,
|
||||||
|
var name: String
|
||||||
|
)
|
||||||
|
|
||||||
data class Document(
|
class Document(
|
||||||
val id: Long,
|
id: Long,
|
||||||
|
name: String,
|
||||||
val title: String,
|
val title: String,
|
||||||
val uploadName: String,
|
val uploadName: String,
|
||||||
val fileType: String,
|
val fileType: String,
|
||||||
|
@ -16,8 +17,9 @@ data class Document(
|
||||||
val createdAt: LocalDateTime,
|
val createdAt: LocalDateTime,
|
||||||
val updatedAt: LocalDateTime,
|
val updatedAt: LocalDateTime,
|
||||||
val fileSize: String,
|
val fileSize: String,
|
||||||
val filePath: String
|
val filePath: String,
|
||||||
)
|
|
||||||
|
) : BaseDoc(id, name)
|
||||||
|
|
||||||
data class DocumentVO(
|
data class DocumentVO(
|
||||||
val title: String, val authorId: String,
|
val title: String, val authorId: String,
|
||||||
|
|
|
@ -57,4 +57,6 @@ interface FolderRepository {
|
||||||
|
|
||||||
// Search folders by name or tags
|
// Search folders by name or tags
|
||||||
suspend fun searchFolders(query: String): List<Folder>
|
suspend fun searchFolders(query: String): List<Folder>
|
||||||
|
|
||||||
|
suspend fun searchTopFolders():List<Folder>
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
package echo.org.domain
|
||||||
|
|
||||||
|
data class User(val id: Int?, val username: String?, val password: String?)
|
|
@ -28,12 +28,6 @@ data class Result<T>(
|
||||||
data class PageResult<T>(
|
data class PageResult<T>(
|
||||||
val results: List<T>, // 查询结果列表
|
val results: List<T>, // 查询结果列表
|
||||||
val totalCount: Long // 总记录数
|
val totalCount: Long // 总记录数
|
||||||
) {
|
)
|
||||||
// companion object {
|
|
||||||
// fun <T> of(results: List<T>,totalCount: Long ): PageResult<T> {
|
|
||||||
// return PageResult()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
open class BasePageQueryParm(var pageNum: Int = 1, var pageSize: Int = 10)
|
open class BasePageQueryParm(var pageNum: Int = 1, var pageSize: Int = 10)
|
|
@ -135,4 +135,11 @@ class FolderRepositoryImpl : FolderRepository {
|
||||||
(Folders.name like "%$query%") or (Folders.tags like "%$query%")
|
(Folders.name like "%$query%") or (Folders.tags like "%$query%")
|
||||||
}.map { Folders.toFolder(it) }
|
}.map { Folders.toFolder(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun searchTopFolders(): List<Folder> = dbQuery {
|
||||||
|
Folders.selectAll().where {
|
||||||
|
parentFolderId.isNull()
|
||||||
|
}.map { Folders.toFolder(it) }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,19 +1,19 @@
|
||||||
package echo.org.plugins
|
package echo.org.plugins
|
||||||
|
|
||||||
import echo.org.application.route.handleDocuments
|
|
||||||
import echo.org.domain.DocumentRepository
|
import echo.org.domain.DocumentRepository
|
||||||
import echo.org.domain.FileOSS
|
import echo.org.domain.FileOSS
|
||||||
|
import echo.org.domain.FolderRepository
|
||||||
import echo.org.instructure.AliyunOssFileServiceImpl
|
import echo.org.instructure.AliyunOssFileServiceImpl
|
||||||
import echo.org.instructure.DocumentRepositoryImpl
|
import echo.org.instructure.DocumentRepositoryImpl
|
||||||
|
import echo.org.instructure.FolderRepositoryImpl
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
import io.ktor.server.http.content.*
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
import org.koin.ktor.plugin.Koin
|
import org.koin.ktor.plugin.Koin
|
||||||
|
|
||||||
|
|
||||||
val DocumentModule = module {
|
val DocumentModule = module {
|
||||||
single<DocumentRepository> { DocumentRepositoryImpl() }
|
single<DocumentRepository> { DocumentRepositoryImpl() }
|
||||||
|
single<FolderRepository> { FolderRepositoryImpl() }
|
||||||
single<FileOSS> { AliyunOssFileServiceImpl() }
|
single<FileOSS> { AliyunOssFileServiceImpl() }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,55 @@
|
||||||
package echo.org.plugins
|
package echo.org.plugins
|
||||||
|
|
||||||
|
import com.auth0.jwt.JWT
|
||||||
|
import com.auth0.jwt.algorithms.Algorithm
|
||||||
|
import echo.org.domain.User
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.application.ApplicationCallPipeline.ApplicationPhase.Call
|
||||||
|
import io.ktor.server.auth.*
|
||||||
|
import io.ktor.server.auth.jwt.*
|
||||||
|
import io.ktor.util.*
|
||||||
|
|
||||||
|
|
||||||
|
const val JWT_AUTH = "auth-jwt"
|
||||||
|
const val SECRET_KEY = "echo"
|
||||||
|
const val SAMPLE_AUDIENCE = "ktor-audience"
|
||||||
|
const val SAMPLE_ISSUER = "ktor-issuer"
|
||||||
|
|
||||||
|
// 定义一个键,用于在上下文中存储用户信息
|
||||||
|
val UserKey = AttributeKey<User>("User")
|
||||||
|
|
||||||
fun Application.configureSecurity() {
|
fun Application.configureSecurity() {
|
||||||
|
install(Authentication) {
|
||||||
|
jwt(JWT_AUTH) {
|
||||||
|
realm = "ktor sample app"
|
||||||
|
verifier(
|
||||||
|
JWT
|
||||||
|
.require(Algorithm.HMAC256(SECRET_KEY)) // 使用您的密钥
|
||||||
|
.withAudience(SAMPLE_AUDIENCE)
|
||||||
|
.withIssuer(SAMPLE_ISSUER)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
validate { credential ->
|
||||||
|
if (credential.payload.audience.contains(SAMPLE_AUDIENCE)) {
|
||||||
|
val username = credential.payload.getClaim("username")
|
||||||
|
val password = credential.payload.getClaim("password")
|
||||||
|
JWTPrincipal(credential.payload).apply {
|
||||||
|
|
||||||
|
}
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加拦截器来存储用户信息到请求的 attributes 中
|
||||||
|
intercept(Call) {
|
||||||
|
val principal = call.principal<JWTPrincipal>()
|
||||||
|
if (principal != null) {
|
||||||
|
val id = principal.payload.getClaim("id").asInt()
|
||||||
|
val username = principal.payload.getClaim("username").asString()
|
||||||
|
val email = principal.payload.getClaim("email").asString()
|
||||||
|
call.attributes.put(UserKey, User(id, username, email))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ ktor:
|
||||||
|
|
||||||
storage:
|
storage:
|
||||||
driverClassName: "com.mysql.cj.jdbc.Driver"
|
driverClassName: "com.mysql.cj.jdbc.Driver"
|
||||||
jdbcURL: "jdbc:mysql://47.97.21.20:3306"
|
jdbcURL: "jdbc:mysql://127.0.0.1:3306"
|
||||||
database: "document"
|
database: "document"
|
||||||
username: "documentAdmin"
|
username: "root"
|
||||||
password: "123456"
|
password: "123456"
|
|
@ -1,85 +1,61 @@
|
||||||
/* 基础重置 */
|
body {
|
||||||
body, h1, h2, h3, p, ul, li, form, input, button {
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
box-sizing: border-box;
|
background-color: #f5f5f5;
|
||||||
font-family: 'Roboto', sans-serif;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
header {
|
||||||
line-height: 1.6;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
background-color: #f9f9f9;
|
background-color: #90b7dc;
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2, h3 {
|
|
||||||
color: #2c3e50;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 链接样式 */
|
|
||||||
a {
|
|
||||||
color: #3498db;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 列表样式 */
|
|
||||||
ul {
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
padding-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 表单样式 */
|
|
||||||
form {
|
|
||||||
background: #ffffff;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
max-width: 500px;
|
|
||||||
margin: 20px auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="file"] {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
background-color: #3498db;
|
|
||||||
border: none;
|
|
||||||
color: white;
|
color: white;
|
||||||
padding: 10px 20px;
|
}
|
||||||
text-align: center;
|
|
||||||
text-decoration: none;
|
.logo {
|
||||||
display: inline-block;
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 5px;
|
|
||||||
transition: background-color 0.3s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
nav {
|
||||||
background-color: #2980b9;
|
background-color: #f5f5f5;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 辅助类 */
|
nav ul {
|
||||||
.container {
|
list-style-type: none;
|
||||||
width: 95%;
|
margin: 0;
|
||||||
max-width: 1200px;
|
padding: 0;
|
||||||
margin: 0 auto;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clearfix::after {
|
nav ul li {
|
||||||
content: "";
|
margin-right: 20px;
|
||||||
display: table;
|
}
|
||||||
clear: both;
|
|
||||||
|
nav ul li a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #007aff;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #007aff;
|
||||||
|
color: white;
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
bottom: 0;
|
||||||
}
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
<#macro main title>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>${title!""}</title>
|
||||||
|
<link rel="stylesheet" href="/files/styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<#include "header.ftl">
|
||||||
|
<#include "nav.ftl">
|
||||||
|
<main>
|
||||||
|
<#nested>
|
||||||
|
</main>
|
||||||
|
<#include "footer.ftl">
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</#macro>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<#import "base.ftl" as base>
|
||||||
|
|
||||||
|
<@base.main title="Files">
|
||||||
|
<h2>Your Files</h2>
|
||||||
|
<ul>
|
||||||
|
<#list documents as document>
|
||||||
|
<li>${document.title} (${document.fileSize} MB)</li>
|
||||||
|
</#list>
|
||||||
|
</ul>
|
||||||
|
</@base.main>
|
|
@ -0,0 +1,3 @@
|
||||||
|
<footer>
|
||||||
|
<p>© 2024 CloudStorage. All rights reserved.</p>
|
||||||
|
</footer>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<header>
|
||||||
|
<div class="logo">CloudStorage</div>
|
||||||
|
<div class="user-info">
|
||||||
|
<span>${user!""}</span>
|
||||||
|
<a href="/logout">Logout</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<#import "base.ftl" as base>
|
||||||
|
|
||||||
|
<@base.main title="Home">
|
||||||
|
<h2>Welcome to CloudStorage</h2>
|
||||||
|
<p>Your files, always with you.</p>
|
||||||
|
</@base.main>
|
|
@ -7,7 +7,7 @@
|
||||||
<body class="container">
|
<body class="container">
|
||||||
<h1>文档列表</h1>
|
<h1>文档列表</h1>
|
||||||
<ul>
|
<ul>
|
||||||
<#list Documents as doc>
|
<#list results as doc>
|
||||||
<li>
|
<li>
|
||||||
${doc.id} - ${doc.name}
|
${doc.id} - ${doc.name}
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/documents">Home</a></li>
|
||||||
|
<li><a href="/documents/files">Files</a></li>
|
||||||
|
<li><a href="/settings">Settings</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
Binary file not shown.
Loading…
Reference in New Issue