From 1139824098e776e994b4e603383ec9fdc2a97d5c Mon Sep 17 00:00:00 2001 From: pan <1029559041@qq.com> Date: Mon, 3 May 2021 05:49:46 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=B3=A8=E5=86=8C=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 21 +++++++- src/AccountController.kt | 26 +++++++--- src/AccountService.kt | 91 +++++++++++++++++++++++++++++++++++ src/Dao.kt | 68 ++++++++++++++++++++++++-- src/MySQL.kt | 20 +++++++- src/Service.kt | 46 ------------------ src/Vo.kt | 31 ++++++++++-- test/ApplicationTest.kt | 100 ++++++++++++++++++++++++++++++++++++--- 8 files changed, 335 insertions(+), 68 deletions(-) create mode 100644 src/AccountService.kt delete mode 100644 src/Service.kt diff --git a/build.gradle.kts b/build.gradle.kts index 1395968..a4711a1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,16 +24,35 @@ repositories { dependencies { implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version") + /** + * ktor + */ implementation("io.ktor:ktor-server-netty:$ktor_version") - implementation("ch.qos.logback:logback-classic:$logback_version") implementation("io.ktor:ktor-server-core:$ktor_version") + /** + * https://github.com/google/gson + */ implementation("io.ktor:ktor-gson:$ktor_version") + /** + * https://github.com/qos-ch/logback + */ + implementation("ch.qos.logback:logback-classic:$logback_version") + /** + * https://github.com/Kotlin/kotlinx.serialization + */ implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0") + /** + * https://github.com/JetBrains/Exposed + */ implementation("org.jetbrains.exposed:exposed-core:$exposedVersion") implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion") implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion") + implementation("org.jetbrains.exposed:exposed-java-time:$exposedVersion") implementation("mysql:mysql-connector-java:8.0.19") implementation("com.zaxxer:HikariCP:3.4.2") + + implementation("cn.smallbun.screw:screw-core:1.0.5") + testImplementation("io.ktor:ktor-server-tests:$ktor_version") } diff --git a/src/AccountController.kt b/src/AccountController.kt index 90d559a..1cedbe4 100644 --- a/src/AccountController.kt +++ b/src/AccountController.kt @@ -1,6 +1,7 @@ package com.gyf.csams import io.ktor.application.* +import io.ktor.features.* import io.ktor.request.* import io.ktor.response.* import io.ktor.routing.* @@ -17,10 +18,10 @@ fun Application.AccountController() { get(path = "/checkId") { val studentId = call.request.queryParameters["studentId"] if (studentId?.isNotEmpty() == true) { - if (Service.registered(studentId)) { - call.respond(ApiResponse(code = 200, message = "学号已注册", body = true)) + if (AccountService.registered(studentId)) { + call.respond(ApiResponse(message = "学号已注册", body = true)) } else { - call.respond(ApiResponse(code = 200, message = "学号可注册", body = false)) + call.respond(ApiResponse(message = "学号可注册", body = false)) } } else { call.respond(Simple.error("学号检测失败,请联系管理员")) @@ -32,14 +33,27 @@ fun Application.AccountController() { */ post { val userVo = call.receive() - val userResDto = Service.register(userVo) + val userResDto = AccountService.register(userVo) if (userResDto != null) { - call.respond(ApiResponse(code = 200, message = "注册成功", userResDto)) + call.respond(ApiResponse(message = "注册成功", body = userResDto)) } else { - call.respond(ApiResponse(code = 400, message = "注册失败", body = null)) + call.respond(Simple.error("注册失败")) } } } + + route(path = "/login"){ + post{ + val userLoginVo= call.receive() + val tokenResDto:TokenResDto=AccountService.login(userLoginVo,call.request.origin.remoteHost) + call.respond(ApiResponse(message = if(tokenResDto.token!=null) "登陆成功" else "账号或密码错误!!!",body = tokenResDto)) + } + post(path = "/token"){ + val tokenVo=call.receive() + val isValid=AccountService.validToken(tokenVo) + call.respond(ApiResponse(message = if(isValid) "令牌合法" else "令牌不合法",body = isValid)) + } + } } } } diff --git a/src/AccountService.kt b/src/AccountService.kt new file mode 100644 index 0000000..dc9293f --- /dev/null +++ b/src/AccountService.kt @@ -0,0 +1,91 @@ +package com.gyf.csams + +import org.jetbrains.exposed.sql.transactions.transaction +import org.slf4j.LoggerFactory +import java.time.ZoneOffset + + +class AccountService { + + + companion object { + private val logger = LoggerFactory.getLogger(AccountService::class.java) + /** + * 检查学号是否已注册,true=已注册 + */ + fun registered(selectId: String): Boolean { + return transaction { + return@transaction !User.find { Users.studentId eq selectId }.empty() + } + } + + /** + * 注册 + */ + fun register(userVo: UserVo): UserResDto? { + try { + return transaction { + val _pwd = randomNum(8) + User.new { + studentId=userVo.studentId + name=userVo.name + password=_pwd.md5() + } + return@transaction UserResDto(password=_pwd) + } + } catch (e: Exception) { + logger.error("注册失败,发生异常:$e") + return null + } + } + + /** + * 登录 + * + * @param userLoginVo 登陆表单 + */ + fun login(userLoginVo: UserLoginVo,_ip:String):TokenResDto{ + return transaction { + val user=User.find { Users.studentId eq userLoginVo.studentId }.firstOrNull() + + + + when { + user==null -> { + logger.warn("学号:${userLoginVo.studentId}不存在") + return@transaction TokenResDto(isValid = false) + } + userLoginVo.password.md5() != user.password -> { + logger.warn("密码:${userLoginVo.password}错误") + return@transaction TokenResDto(isValid = false) + } + else -> { + val token=UserToken.new{ + studentId=userLoginVo.studentId + ip=_ip + device=userLoginVo.device + token=listOf(studentId,ip,device).joinToString(separator = ('a' .. 'z').random().toString()).md5() + } + return@transaction TokenResDto(isValid = true,token = Token(token = token.token,createTime = token.createTime.toEpochSecond( + ZoneOffset.of("+8")),studentId = token.studentId)) + } + } + } + } + + /** + * 令牌校验 + * + * @param tokenVo + * @return + */ + fun validToken(tokenVo: TokenVo):Boolean{ + return transaction { + return@transaction !UserToken.find { + UserTokens.studentId eq tokenVo.studentId + UserTokens.token eq tokenVo.token + }.empty() + } + } + } +} \ No newline at end of file diff --git a/src/Dao.kt b/src/Dao.kt index 206b363..f2411a4 100644 --- a/src/Dao.kt +++ b/src/Dao.kt @@ -1,12 +1,70 @@ package com.gyf.csams +import org.jetbrains.exposed.dao.IntEntity +import org.jetbrains.exposed.dao.IntEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.sql.Column -import org.jetbrains.exposed.sql.Table +import org.jetbrains.exposed.sql.`java-time`.datetime +import java.time.LocalDateTime -object User: Table(){ - val studentId:Column = varchar(name="student_id",length = 8) +/** + * 用户 + */ +object Users: IntIdTable(){ + /** + * 学号 + */ + val studentId:Column = varchar(name="student_id",length = 8).uniqueIndex() + /** + * 姓名 + */ val name:Column = varchar(name="name",length = 10) + + /** + * 密码,hash加密 + */ val password:Column = varchar(name="password",length = 32) - override val primaryKey: PrimaryKey - get() = PrimaryKey(studentId) +} + +class User(id:EntityID):IntEntity(id){ + companion object : IntEntityClass(Users) + var studentId by Users.studentId + var name by Users.name + var password by Users.password +} + +/** + * 用户授权令牌 + */ +object UserTokens: IntIdTable(){ + /** + * 授权学号 + */ + val studentId:Column = reference("student_id",Users.studentId) + /** + * 令牌 + */ + val token:Column = varchar(name="token",length = 32) + /** + * 授权ip地址 + */ + val ip:Column = varchar(name="ip",length = 32) + /** + * 令牌创建时间 + */ + val createTime:Column = datetime("create_time").default(LocalDateTime.now()) + /** + * 授权设备 + */ + val device:Column = varchar(name="device",length = 256) +} + +class UserToken(id:EntityID):IntEntity(id){ + companion object:IntEntityClass(UserTokens) + var studentId by UserTokens.studentId + var token by UserTokens.token + var ip by UserTokens.ip + var createTime by UserTokens.createTime + var device by UserTokens.device } \ No newline at end of file diff --git a/src/MySQL.kt b/src/MySQL.kt index 2313a24..c407b08 100644 --- a/src/MySQL.kt +++ b/src/MySQL.kt @@ -6,6 +6,22 @@ import io.ktor.application.* import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.SchemaUtils import org.jetbrains.exposed.sql.transactions.transaction +import javax.sql.DataSource + + +class MySQL private constructor(val dataSource: DataSource? = null) { + companion object { + @Volatile + private var instance: MySQL? = null + + fun getInstance(dataSource: DataSource?) = + instance ?: synchronized(this) { + instance ?: MySQL(dataSource).also { instance = it } + } + + } + +} fun Application.MySQL(testing: Boolean = false){ val config = HikariConfig().apply { @@ -17,12 +33,14 @@ fun Application.MySQL(testing: Boolean = false){ } val dataSource = HikariDataSource(config) Database.connect(dataSource) + MySQL.getInstance(dataSource) initTable() } fun initTable(){ transaction { - SchemaUtils.createMissingTablesAndColumns(User) + SchemaUtils.createMissingTablesAndColumns(Users) + SchemaUtils.createMissingTablesAndColumns(UserTokens) } } \ No newline at end of file diff --git a/src/Service.kt b/src/Service.kt deleted file mode 100644 index 0bb604f..0000000 --- a/src/Service.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.gyf.csams - -import org.jetbrains.exposed.sql.insert -import org.jetbrains.exposed.sql.select -import org.jetbrains.exposed.sql.transactions.transaction -import org.slf4j.LoggerFactory - - -class Service { - - - companion object { - private val logger = LoggerFactory.getLogger(Service::class.java) - /** - * 检查学号是否已注册 - */ - fun registered(selectId: String): Boolean { - return transaction { - return@transaction User.select { User.studentId eq selectId }.count().toInt() > 0 - } - } - - /** - * 注册 - */ - fun register(userVo: UserVo): UserResDto? { - var userResDto: UserResDto? = null - try { - transaction { - val _pwd = randomNum(8) - val result = User.insert { - it[studentId] = userVo.studentId - it[name] = userVo.name - it[password] = _pwd.md5() - }.resultedValues - if (result?.isNotEmpty() == true) { - userResDto = UserResDto(password = _pwd) - } - } - } catch (e: Exception) { - logger.error("注册失败,发生异常:$e") - } - return userResDto - } - } -} \ No newline at end of file diff --git a/src/Vo.kt b/src/Vo.kt index e441f1f..f84baa7 100644 --- a/src/Vo.kt +++ b/src/Vo.kt @@ -3,7 +3,7 @@ package com.gyf.csams import kotlinx.serialization.Serializable -data class ApiResponse(val code:Int,val message:String,val body:T?=null) +data class ApiResponse(val code:Int=200,val message:String,val body:T?=null) class Simple { companion object { @@ -17,8 +17,33 @@ class Simple { } } - +/** + * 用户注册表单 + * + * @property studentId 学号 + * @property name 姓名 + */ @Serializable data class UserVo(val studentId:String,val name:String) -data class UserResDto(val password:String) \ No newline at end of file +/** + * 用户登陆表单 + * + * @property studentId 学号 + * @property password 密码 + * @property device 设备型号 + */ +data class UserLoginVo(val studentId: String,val password: String,val device: String) + +data class UserResDto(val password:String) + +data class Token(val token:String,val createTime:Long,val studentId:String) +/** + * 令牌传输 + * + * @property isValid + * @property token + */ +data class TokenResDto(val isValid:Boolean,val token: Token?=null) + +data class TokenVo(val token:String,val studentId:String) \ No newline at end of file diff --git a/test/ApplicationTest.kt b/test/ApplicationTest.kt index 124f25d..3c8076a 100644 --- a/test/ApplicationTest.kt +++ b/test/ApplicationTest.kt @@ -1,11 +1,20 @@ package com.gyf.csams +import cn.smallbun.screw.core.Configuration +import cn.smallbun.screw.core.engine.EngineConfig +import cn.smallbun.screw.core.engine.EngineFileType +import cn.smallbun.screw.core.engine.EngineTemplateType +import cn.smallbun.screw.core.execute.DocumentationExecute +import cn.smallbun.screw.core.process.ProcessConfig import io.ktor.config.* import io.ktor.http.* import io.ktor.server.testing.* +import org.jetbrains.exposed.sql.transactions.transaction +import java.time.LocalDateTime import kotlin.test.Test import kotlin.test.assertEquals + class ApplicationTest { @Test fun testRoot() { @@ -30,21 +39,100 @@ class ApplicationTest { println("admin".md5()) } - @Test - fun testInsertUser(){ - withTestApplication({ + fun initApp(test: TestApplicationEngine.() -> R):R{ + // withTestApplication(test: TestApplicationEngine.() -> R): R + return withTestApplication({ (environment.config as MapApplicationConfig).apply { // Set here the properties - put("ktor.deployment.mysql.jdbcUrl", "jdbc:mysql://localhost:3306/csams") + put("ktor.deployment.mysql.jdbcUrl", "jdbc:mysql://localhost:3306/csams?serverTimezone=Asia/Shanghai") put("ktor.deployment.mysql.driverClassName", "com.mysql.cj.jdbc.Driver") put("ktor.deployment.mysql.username", "root") put("ktor.deployment.mysql.password", "123456") } MySQL(testing = true) - }) { - val c=Service.registered(selectId = "77889") + }, test) + } + + @Test + fun testInsertUser(){ + initApp{ + val c=AccountService.register(UserVo(studentId = "6666",name = "hahaha")) println(c) } } + + @Test + fun testEntryId() { + initApp { + transaction { +// User.new { +// studentId="20210101" +// name="222" +// password="12345678".md5() +// } +// alter table Users comment '123'; + exec("alter table Users comment '6666'") + } + } + } + + @Test + fun localTime(){ + println(LocalDateTime.now()) + } + + /** + * 文档生成 + */ + @Test + fun documentGeneration() { + initApp { + //生成配置 + val engineConfig = EngineConfig.builder() + //生成文件路径 + .fileOutputDir("f:\\Desktop") + //打开目录 + .openOutputDir(true) + //文件类型 + .fileType(EngineFileType.HTML) + //生成模板实现 + .produceType(EngineTemplateType.freemarker) + //自定义文件名称 + .fileName("777").build(); + + //忽略表 +// val ignoreTableName = ArrayList() +// ignoreTableName.add("test_user") +// ignoreTableName.add("test_group") + //忽略表前缀 +// val ignorePrefix = ArrayList() +// ignorePrefix.add("test_") + //忽略表后缀 +// val ignoreSuffix = ArrayList() +// ignoreSuffix.add("_test") + val processConfig = ProcessConfig.builder() //指定生成逻辑、当存在指定表、指定表前缀、指定表后缀时,将生成指定表,其余表不生成、并跳过忽略表配置 + //根据名称指定表生成 + .designatedTableName(ArrayList()) //根据表前缀生成 + .designatedTablePrefix(ArrayList()) //根据表后缀生成 + .designatedTableSuffix(ArrayList()) //忽略表名 +// .ignoreTableName(ignoreTableName) //忽略表前缀 +// .ignoreTablePrefix(ignorePrefix) //忽略表后缀 +// .ignoreTableSuffix(ignoreSuffix) + .build() + //配置 + val config: Configuration = Configuration.builder() //版本 + .version("1.0.0") //描述 + .description("数据库设计文档生成") //数据源 + .dataSource(MySQL.getInstance(null).dataSource) //生成配置 + .engineConfig(engineConfig) //生成配置 + .produceConfig(processConfig) + .build() + //执行生成 + DocumentationExecute(config).execute() + } + + + } + }