上传文件服务

社团注册服务
增加图片文件表、社团表、后台管理员表、审核记录表
完善服务抽象类
master
pan 4 years ago
parent 662c6930a0
commit 80468467b0
  1. 1
      build.gradle.kts
  2. 1
      resources/application.conf
  3. 2
      resources/logback.xml
  4. 21
      src/Application.kt
  5. 210
      src/Controller.kt
  6. 108
      src/Dao.kt
  7. 2
      src/MySQL.kt
  8. 281
      src/Service.kt
  9. 25
      src/Util.kt
  10. 20
      src/Vo.kt
  11. 27
      test/ApplicationTest.kt
  12. 32
      test/ChannelTest.kt

@ -29,6 +29,7 @@ dependencies {
*/ */
implementation("io.ktor:ktor-server-netty:$ktor_version") implementation("io.ktor:ktor-server-netty:$ktor_version")
implementation("io.ktor:ktor-server-core:$ktor_version") implementation("io.ktor:ktor-server-core:$ktor_version")
/** /**
* https://github.com/google/gson * https://github.com/google/gson
*/ */

@ -11,6 +11,7 @@ ktor {
leaveMessage { leaveMessage {
maxSize = 20 maxSize = 20
} }
filePath = upload
watch = [ classes ] watch = [ classes ]
} }
application { application {

@ -1,7 +1,7 @@
<configuration> <configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder> <encoder>
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> <pattern>%d{YYYY-MM-dd HH:mm:ss} [%thread] %-5level - %C:%L - %msg%n</pattern>
</encoder> </encoder>
</appender> </appender>
<root level="trace"> <root level="trace">

@ -22,15 +22,26 @@ fun Application.module(testing: Boolean = false) {
} }
install(ContentNegotiation) { install(ContentNegotiation) {
gson { gson()
}
} }
install(CallLogging)
} }
fun Application.Controller(testing: Boolean = false){ fun Application.Controller(testing: Boolean = false){
this.AccountController() AccountController()
this.TestController() TestController()
this.MainController() MainController()
AssociationController()
NotificationController()
/**
* 初始化log
*/
listOf(MainService,AccountService,FileService,AssociationService,BackgroundService).forEach {
it.init(environment)
}
} }

@ -1,26 +1,26 @@
package com.gyf.csams package com.gyf.csams
import io.ktor.application.* import io.ktor.application.*
import io.ktor.http.content.*
import io.ktor.request.* import io.ktor.request.*
import io.ktor.response.* import io.ktor.response.*
import io.ktor.routing.* import io.ktor.routing.*
import org.slf4j.LoggerFactory import kotlinx.coroutines.channels.Channel
suspend inline fun <reified T:BaseVo> withToken(call:ApplicationCall, callback:(vo:T)->Unit){ suspend inline fun <reified T : BaseVo> withToken(call: ApplicationCall, callback: (vo: T) -> Unit) {
val levelVo=call.receive<T>() val levelVo = call.receive<T>()
if(AccountService.validToken(levelVo.token)){ if (AccountService.validToken(levelVo.token)) {
callback(levelVo) callback(levelVo)
}else{ } else {
call.respond(ApiResponse(message = "token校验失败",body = null)) call.respond(Simple.error("token校验失败"))
} }
} }
private val logger = LoggerFactory.getLogger(Application::class.java)
fun Application.AccountController() { fun Application.AccountController() {
routing { routing {
route(path = "$ApiPathPrefix/account"){ route(path = "$ApiPathPrefix/account") {
route(path="/register") { route(path = "/register") {
/** /**
* 检测学号是否已注册 * 检测学号是否已注册
*/ */
@ -41,37 +41,37 @@ fun Application.AccountController() {
* 注册账号 * 注册账号
*/ */
post { post {
val userVo = call.receive<UserVo>() val userVo = call.receive<UserVo>()
val userResDto = AccountService.register(userVo) val userResDto = AccountService.register(userVo)
if (userResDto != null) { if (userResDto != null) {
call.respond(ApiResponse(message = "注册成功", body = userResDto)) call.respond(ApiResponse(message = "注册成功", body = userResDto))
} else { } else {
call.respond(Simple.error("注册失败")) call.respond(Simple.error("注册失败"))
} }
} }
} }
route(path = "/login"){ route(path = "/login") {
post{ post {
val userLoginVo= call.receive<UserLoginVo>() val userLoginVo = call.receive<UserLoginVo>()
logger.info("执行登陆") environment.log.info("执行登陆")
val token=AccountService.login(userLoginVo,call.request.host()) val token = AccountService.login(userLoginVo, call.request.host())
logger.info("登录请求处理完毕") environment.log.info("登录请求处理完毕")
call.respond(ApiResponse(message = if(token!=null) "登陆成功" else "账号或密码错误!!!",body = token)) call.respond(ApiResponse(message = if (token != null) "登陆成功" else "账号或密码错误!!!", body = token))
} }
post(path = "/token"){ post(path = "/token") {
val tokenVo=call.receive<TokenVo>() val tokenVo = call.receive<TokenVo>()
val isValid=AccountService.validToken(tokenVo) val isValid = AccountService.validToken(tokenVo)
call.respond(ApiResponse(message = if(isValid) "令牌合法" else "令牌不合法",body = isValid)) call.respond(ApiResponse(message = if (isValid) "令牌合法" else "令牌不合法", body = isValid))
} }
} }
post(path = "/logout"){ post(path = "/logout") {
logger.info("退出登录") environment.log.info("退出登录")
val userLogoutVo=call.receive<UserLogoutVo>() val userLogoutVo = call.receive<UserLogoutVo>()
logger.info("$userLogoutVo") environment.log.info("$userLogoutVo")
val flag=AccountService.logout(userLogoutVo.studentId) val flag = AccountService.logout(userLogoutVo.userId)
call.respond(ApiResponse(message = if(flag) "退出成功" else "退出失败",body = flag)) call.respond(ApiResponse(message = if (flag) "退出成功" else "退出失败", body = flag))
} }
} }
} }
@ -81,33 +81,153 @@ fun Application.AccountController() {
* 主界面接口 * 主界面接口
* *
*/ */
fun Application.MainController(){ fun Application.MainController() {
MainService.register(maxSize = environment.config.property("ktor.deployment.leaveMessage.maxSize").getString().toInt())
routing { routing {
//发送留言 //发送留言
route("$ApiPathPrefix/main") { route("$ApiPathPrefix/main") {
post("/leaveMessage") { post("/leaveMessage") {
withToken<LeaveMessageVo>(call){ withToken<LeaveMessageVo>(call) {
val flag=MainService.createMessage(leaveMessageVo = it) val flag = MainService.createMessage(leaveMessageVo = it)
call.respond(ApiResponse(message = if(flag) "留言创建成功" else "留言创建失败",body=flag)) call.respond(ApiResponse(message = if (flag) "留言创建成功" else "留言创建失败", body = flag))
} }
} }
post("/getMessage"){ post("/getMessage") {
withToken<OnlyToken>(call){ withToken<OnlyToken>(call) {
val s=MainService.getAllLeaveMessage() val s = MainService.getAllLeaveMessage()
call.respond(ApiResponse(message = "留言获取成功",body = s)) call.respond(ApiResponse(message = "留言获取成功", body = s))
} }
} }
} }
} }
} }
fun Application.TestController(){ fun Application.TestController() {
routing { routing {
get("$ApiPathPrefix/test"){ get("$ApiPathPrefix/test") {
AccountService.test() AccountService.test()
call.respond(ApiResponse(message = "成功连接服务端",body=true)) call.respond(ApiResponse(message = "成功连接服务端", body = true))
}
get("$ApiPathPrefix/testR"){
sendNotification(SimpleNotification())
call.respond("建立通知成功")
}
}
}
fun Application.AssociationController() {
routing {
route("$ApiPathPrefix/association") {
post("/uploadLogo") {
val multipartData = call.receiveMultipart()
multipartData.readAllParts().apply {
environment.log.info("part size=$size")
if (size == 3) {
val result=FileService.storeFile(this)
call.respond(ApiResponse(message = if(result.isNullOrEmpty()) "文件上传失败" else
"成功上传${result.size}个文件",body = result))
} else {
call.respond(Simple.error("参数异常"))
throw IllegalArgumentException("参数异常")
}
}
}
post("/register"){
withToken<RegAssociationVo>(call = call){
val flag=AssociationService.register(vo=it)
call.respond(ApiResponse(message = if(flag) "社团注册资料已保存,请等待受理" else "社团注册资料保存失败",body=flag))
}
}
} }
} }
}
/**
* TODO
*
*/
enum class NotificationType{
System
}
/**
* 通知接收客户端
*
*/
enum class ReceiverType{
//前台
Foreground,
//后台
Background
}
/**
* 通知接收者
*
*/
sealed class Receiver{
protected abstract val id:Int
abstract val target:ReceiverType
}
/**
* 后台客户端接收
*
* @property managerId 管理员id
*/
data class BackgroundReceiver(val managerId:Int,override val id: Int=managerId, override val target: ReceiverType=ReceiverType.Background):Receiver()
/**
* 前台客户端接收
*
* @property userId 用户id
*/
data class ForegroundReceiver(val userId:Int, override val id:Int=userId, override val target: ReceiverType=ReceiverType.Foreground):Receiver()
sealed class NotificationVo{
abstract val type:NotificationType
abstract val title:String
abstract val receiver:Receiver
}
/**
* 系统通知
*
* @property type
* @property title
*/
sealed class SysNotificationVo<T>:NotificationVo(){
override val type: NotificationType=NotificationType.System
abstract val content:T
}
/**
* 注册社团通知
*
* @property title
*/
data class RegisterNotification(override val title: String="注册社团通知", override val content: RegAssociationDto,
override val receiver: ForegroundReceiver
):SysNotificationVo<RegAssociationDto>()
data class SimpleNotification(
override val type: NotificationType=NotificationType.System,
override val title: String="test",
override val receiver: Receiver=BackgroundReceiver(managerId = randomNum().toInt())
):NotificationVo()
private val channel = Channel<NotificationVo>()
suspend fun sendNotification(vo:NotificationVo){
channel.send(vo)
}
fun Application.NotificationController(){
routing {
}
} }

@ -33,8 +33,8 @@ class User(id:EntityID<Int>):IntEntity(id){
@TableComment("用户授权令牌") @TableComment("用户授权令牌")
object UserTokens: IntIdTable(){ object UserTokens: IntIdTable(){
@TableComment("授权学号") @TableComment("授权用户")
val studentId:Column<String> = reference("student_id",Users.studentId) val userId:Column<EntityID<Int>> = reference("user_id",Users)
@TableComment("令牌") @TableComment("令牌")
val token:Column<String> = varchar(name="token",length = 32) val token:Column<String> = varchar(name="token",length = 32)
@ -51,7 +51,7 @@ object UserTokens: IntIdTable(){
class UserToken(id:EntityID<Int>):IntEntity(id){ class UserToken(id:EntityID<Int>):IntEntity(id){
companion object:IntEntityClass<UserToken>(UserTokens) companion object:IntEntityClass<UserToken>(UserTokens)
var studentId by UserTokens.studentId var user by User referencedOn UserTokens.userId
var token by UserTokens.token var token by UserTokens.token
var ip by UserTokens.ip var ip by UserTokens.ip
var createTime by UserTokens.createTime var createTime by UserTokens.createTime
@ -61,7 +61,7 @@ class UserToken(id:EntityID<Int>):IntEntity(id){
@TableComment("留言") @TableComment("留言")
object LeaveMessages:IntIdTable(){ object LeaveMessages:IntIdTable(){
@TableComment("留言用户") @TableComment("留言用户")
val studentId:Column<String> = reference("student_id",Users.studentId) val userId:Column<EntityID<Int>> = reference("user_id",Users)
@TableComment("留言内容") @TableComment("留言内容")
val message:Column<String> = varchar(name = "message",length = 20) val message:Column<String> = varchar(name = "message",length = 20)
@ -72,8 +72,106 @@ object LeaveMessages:IntIdTable(){
class LeaveMessage(id:EntityID<Int>):IntEntity(id){ class LeaveMessage(id:EntityID<Int>):IntEntity(id){
companion object:IntEntityClass<LeaveMessage>(LeaveMessages) companion object:IntEntityClass<LeaveMessage>(LeaveMessages)
var studentId by LeaveMessages.studentId var user by User referencedOn LeaveMessages.userId
var message by LeaveMessages.message var message by LeaveMessages.message
var createTime by LeaveMessages.createTime var createTime by LeaveMessages.createTime
} }
@TableComment("图片文件信息")
object ImageFiles:IntIdTable(){
@TableComment("上传用户")
val userId:Column<String> = varchar(name = "user_id",length = 10)
@TableComment("文件相对路径")
val filepath:Column<String> = varchar(name="filepath",length = 50)
@TableComment("文件hash")
val md5:Column<String> = varchar(name="md5",length = 32)
@TableComment("文件创建时间")
val createTime:Column<LocalDateTime> = datetime("create_time").defaultExpression(CurrentDateTime())
}
class ImageFile(id:EntityID<Int>):IntEntity(id){
companion object:IntEntityClass<ImageFile>(ImageFiles)
var userId by ImageFiles.userId
var filepath by ImageFiles.filepath
var md5 by ImageFiles.md5
var createTime by ImageFiles.createTime
}
@TableComment("社团")
object Associations:IntIdTable(){
@TableComment("社团名称")
val name:Column<String> =varchar(name = "name",length = 10)
@TableComment("社团介绍")
val desc:Column<String> =varchar(name = "desc",length = 30)
@TableComment("社团logo")
val logo:Column<EntityID<Int>> = reference("logo",ImageFiles)
@TableComment("社团审核状态")
val status:Column<Boolean> = bool(name="status").default(false)
}
class Association(id:EntityID<Int>):IntEntity(id){
companion object:IntEntityClass<Association>(Associations)
var name by Associations.name
var desc by Associations.desc
var logo by ImageFile referencedOn Associations.logo
var status by Associations.status
}
@TableComment("后台管理员")
object Managers:IntIdTable(){
@TableComment("帐号")
val account:Column<String> = varchar(name="account",length=10)
@TableComment("密码")
val password:Column<String> = varchar(name="password",length = 32)
@TableComment("职务")
val duty:Column<String> = varchar(name="duty",length = 32)
@TableComment("等级")
val level:Column<Int> = integer("level")
}
class Manager(id:EntityID<Int>):IntEntity(id){
companion object:IntEntityClass<Manager>(Managers)
var account by Managers.account
var password by Managers.password
var duty by Managers.duty
var level by Managers.level
}
@TableComment("审核记录")
object CheckForms:IntIdTable(){
@TableComment("审核类型")
val type:Column<String> = varchar(name="type",length = 10)
@TableComment("审核人")
val managerId:Column<EntityID<Int>> = reference("manager_id",Managers)
@TableComment("审核理由")
val cause:Column<String> = varchar("cause",length = 30)
@TableComment("审核对象")
val target:Column<Int> = integer("target")
@TableComment("审核时间")
val createTime:Column<LocalDateTime> = datetime("create_time").defaultExpression(CurrentDateTime())
}
class CheckForm(id:EntityID<Int>):IntEntity(id){
companion object:IntEntityClass<CheckForm>(CheckForms)
val type by CheckForms.type
val manager by Manager referrersOn CheckForms.managerId
val cause by CheckForms.cause
val target by CheckForms.target
}

@ -40,7 +40,7 @@ fun Application.MySQL(testing: Boolean = false){
fun initTable(){ fun initTable(){
transaction { transaction {
val tableList= arrayOf(Users,UserTokens,LeaveMessages) val tableList= arrayOf(Users,UserTokens,LeaveMessages,ImageFiles,Associations,Managers,CheckForms)
SchemaUtils.createMissingTablesAndColumns(*tableList) SchemaUtils.createMissingTablesAndColumns(*tableList)
updateComment(*tableList) updateComment(*tableList)

@ -1,13 +1,30 @@
package com.gyf.csams package com.gyf.csams
import io.ktor.application.*
import io.ktor.http.content.*
import org.jetbrains.exposed.sql.deleteWhere import org.jetbrains.exposed.sql.deleteWhere
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
import org.slf4j.LoggerFactory import org.slf4j.Logger
import java.io.File
import java.time.ZoneOffset import java.time.ZoneOffset
import kotlin.properties.Delegates
interface BaseService{
fun init(environment:ApplicationEnvironment)
}
abstract class AbstractService:BaseService{
protected lateinit var log:Logger
override fun init(environment: ApplicationEnvironment) {
this.log=environment.log
}
}
object AccountService { /**
private val logger = LoggerFactory.getLogger(AccountService::class.java) * 账号服务
*/
object AccountService:AbstractService() {
/** /**
* 检查学号是否已注册,true=已注册 * 检查学号是否已注册,true=已注册
*/ */
@ -23,16 +40,16 @@ object AccountService {
fun register(userVo: UserVo): UserResDto? { fun register(userVo: UserVo): UserResDto? {
try { try {
return transaction { return transaction {
val _pwd = randomNum(8) val originPassword = randomNum(8)
User.new { User.new {
studentId=userVo.studentId studentId=userVo.studentId
name=userVo.name name=userVo.name
password=_pwd.md5() password=originPassword.md5()
} }
return@transaction UserResDto(password=_pwd) return@transaction UserResDto(password=originPassword)
} }
} catch (e: Exception) { } catch (e: Exception) {
logger.error("注册失败,发生异常:$e") log.error("注册失败,发生异常:$e")
return null return null
} }
} }
@ -44,35 +61,35 @@ object AccountService {
*/ */
fun login(userLoginVo: UserLoginVo,_ip:String):Token?{ fun login(userLoginVo: UserLoginVo,_ip:String):Token?{
return transaction { return transaction {
val user=User.find { Users.studentId eq userLoginVo.studentId }.firstOrNull() val matchUser=User.find { Users.studentId eq userLoginVo.studentId }.firstOrNull()
when { when {
user==null -> { matchUser==null -> {
logger.warn("学号:${userLoginVo.studentId}不存在") log.warn("学号:${userLoginVo.studentId}不存在")
return@transaction null return@transaction null
} }
userLoginVo.password.md5() != user.password -> { userLoginVo.password.md5() != matchUser.password -> {
logger.warn("密码:${userLoginVo.password}错误") log.warn("密码:${userLoginVo.password}错误")
return@transaction null return@transaction null
} }
else -> { else -> {
val token=UserToken.new{ val token=UserToken.new{
studentId=userLoginVo.studentId user=matchUser
ip=_ip ip=_ip
device=userLoginVo.device device=userLoginVo.device
token=listOf(studentId,ip,device).joinToString(separator = ('a' .. 'z').random().toString()).md5() token=listOf(matchUser.id,ip,device).joinToString(separator = ('a' .. 'z').random().toString()).md5()
} }
token.flush() token.flush()
return@transaction Token(studentId = userLoginVo.studentId,token = token.token, return@transaction Token(userId = matchUser.id.value,token = token.token,
createTime = token.createTime.toEpochSecond( createTime = token.createTime.toEpochSecond(
ZoneOffset.of("+8")),name=user.name) ZoneOffset.of("+8")))
} }
} }
} }
} }
fun validToken(token: Token):Boolean{ fun validToken(token: Token):Boolean{
return validToken(TokenVo(token = token.token,studentId = token.studentId)) return validToken(TokenVo(token = token.token,userId = token.userId))
} }
/** /**
@ -84,37 +101,40 @@ object AccountService {
fun validToken(tokenVo: TokenVo):Boolean{ fun validToken(tokenVo: TokenVo):Boolean{
return transaction { return transaction {
!UserToken.find { !UserToken.find {
UserTokens.studentId eq tokenVo.studentId UserTokens.userId eq tokenVo.userId
UserTokens.token eq tokenVo.token UserTokens.token eq tokenVo.token
}.empty() }.empty()
} }
} }
fun logout(studentId:String):Boolean{ fun logout(userId:Int):Boolean{
return transaction { return transaction {
UserTokens.deleteWhere { UserTokens.studentId eq studentId }>0 UserTokens.deleteWhere { UserTokens.userId eq userId }>0
} }
} }
fun test(){ fun test(){
logger.info("开始测试") log.info("开始测试")
transaction { transaction {
logger.info("查询到个${User.count()}用户") log.info("查询到个${User.count()}用户")
} }
logger.info("结束测试") log.info("结束测试")
} }
} }
object MainService{ /**
private val logger = LoggerFactory.getLogger(MainService::class.java) * 主页服务
*/
var maxSize:Int=20 object MainService:AbstractService(){
private var maxSize by Delegates.notNull<Int>()
fun register(maxSize:Int){ override fun init(environment:ApplicationEnvironment){
this.maxSize=maxSize super.init(environment)
this.maxSize=environment.config.property("ktor.deployment.leaveMessage.maxSize").getString().toInt()
} }
/** /**
* 创建留言信息 * 创建留言信息
* *
@ -125,32 +145,219 @@ object MainService{
return if(leaveMessageVo.message.isNotEmpty()){ return if(leaveMessageVo.message.isNotEmpty()){
return transaction { return transaction {
val count=LeaveMessage.count().toInt() val count=LeaveMessage.count().toInt()
logger.info("系统留言数:$count,限制数:$maxSize") log.info("系统留言数:$count,限制数:$maxSize")
if(count>= maxSize){ if(count>= maxSize){
LeaveMessage.all().sortedBy { it.createTime }.subList(0, count-maxSize).forEach { LeaveMessage.all().sortedBy { it.createTime }.subList(0, count-maxSize).forEach {
it.delete() it.delete()
} }
} }
logger.info("保存留言:${leaveMessageVo.message}") log.info("保存留言:${leaveMessageVo.message}")
LeaveMessage.new { LeaveMessage.new {
studentId = leaveMessageVo.token.studentId user= User.findById(leaveMessageVo.token.userId)?:throw IllegalArgumentException("非法id")
message = leaveMessageVo.message message = leaveMessageVo.message
} }
logger.info("留言保存成功") log.info("留言保存成功")
return@transaction true return@transaction true
} }
}else{ }else{
logger.info("留言不能为空") log.info("留言不能为空")
false false
} }
} }
fun getAllLeaveMessage():List<LeaveMessageFormatVo>{ fun getAllLeaveMessage():List<LeaveMessageDto>{
return transaction { return transaction {
logger.info("获取所有留言") log.info("获取所有留言")
return@transaction LeaveMessage.all().toList().map { return@transaction LeaveMessage.all().toList().map {
LeaveMessageFormatVo(message = "${User.find { Users.studentId eq it.studentId }.first().name}说:${it.message}",studentId = it.studentId) LeaveMessageDto(
message = "${it.user.name}说:${it.message}",
user = UserVo(studentId = it.user.studentId, name = it.user.name)
)
}
}
}
}
/**
* 文件管理服务
*/
object FileService:AbstractService(){
private lateinit var uploadDir:String
private lateinit var filePath:String
override fun init(environment:ApplicationEnvironment){
super.init(environment)
this.uploadDir= environment.config.property("ktor.deployment.filePath").getString()
val resourcePath =this::class.java.classLoader.getResource("")?.path ?: throw IllegalArgumentException("初始化资源目录失败")
File(resourcePath, uploadDir).apply {
when{
!exists()&&mkdir()-> {
log.info("图片上传路径[${absolutePath}]初始化成功")
this@FileService.filePath=absolutePath
}
exists()->{
log.info("图片上传路径[${absolutePath}]已存在")
this@FileService.filePath=absolutePath
}
else->throw IllegalArgumentException("图片上传路径[${absolutePath}]初始化失败")
}
}
}
private fun save(id: String, path:String, file: File):Int{
return transaction {
return@transaction ImageFile.new {
userId = id
filepath = path
md5 = file.readBytes().md5()
}
}.id.value
}
private inline fun <reified T:PartData> getPartData(map:Map<String?, PartData>, key:String):T{
if(map.containsKey(key)){
val obj= map[key]
if(obj is T){
return obj
}else{
throw IllegalArgumentException("类型错误")
}
}
throw IllegalArgumentException("找不到key:${key}")
}
fun storeFile(data:List<PartData>):List<Int>?{
val map=data.associateBy {
it.name
}.toMutableMap()
log.info("map=${map}")
val userId=getPartData<PartData.FormItem>(map,"id").value
val token= getPartData<PartData.FormItem>(map,"token").value
val tokenVo=TokenVo(token=token,userId=userId.toInt())
if(AccountService.validToken(tokenVo)){
map.remove("id")
map.remove("token")
val fileIds= mutableListOf<Int>()
map.forEach{
val value=it.value
if(value is PartData.FileItem){
val fileBytes = value.streamProvider().readBytes()
val fileName=value.originalFileName?:throw IllegalArgumentException("参数异常")
val format = fileBytes.getFormat()
val fullFileName="${fileName}.${format.format}"
log.info("fullFileName=$fullFileName")
val file=File(filePath,fullFileName).apply {
writeBytes(fileBytes)
}
val fileId= save(id = userId,"${uploadDir}/${fullFileName}",file=file)
fileIds.add(fileId)
}
}
return fileIds
}else{
return null
}
}
}
/**
* 社团服务
*/
object AssociationService: AbstractService() {
fun register(vo:RegAssociationVo):Boolean{
return try {
transaction {
Association.new {
name=vo.name
desc=vo.desc
logo=ImageFile.findById(vo.fileId) ?: throw IllegalArgumentException("文件id不存在!")
}
}
log.info("未审核社团创建成功")
true
} catch (e: Exception) {
log.error(e.stackTraceToString())
false
}
}
}
enum class ManagerType(val desc:String,val level:Int){
Teacher("老师",1),
PamphaBhusal("总部长",2),
SecretaryOfTheMinister("秘书部部长",3),
PropagandaDepartment("宣传部部长",3),
LiaisonMinister("外联部部长",3),
SecretaryDepartmentOfficer("秘书部干事",4),
PublicityDepartmentOfficer("宣传部干事",4),
LiaisonOfficer("外联部干事",4)
}
object NotificationService:AbstractService(){
}
/**
* 后台服务
*/
object BackgroundService:AbstractService(){
override fun init(environment: ApplicationEnvironment) {
super.init(environment)
initManager()
}
private fun createManager(type:ManagerType, num:Int=1): MutableList<InitManagerDto> {
val managerList= mutableListOf<InitManagerDto>()
repeat(num){
val originPassword=randomNum()
Manager.new {
account= randomNum()
password = originPassword.md5()
duty=type.name
level=type.level
}.apply {
managerList.add(InitManagerDto(account=account,originPassword=originPassword))
}
}
return managerList
}
//初始化管理员
private fun initManager(){
transaction {
val resourcePath =this::class.java.classLoader.getResource("")?.path ?: throw IllegalArgumentException("初始化资源目录失败")
val file=File(resourcePath,"管理员账号.txt")
if(!file.exists()) {
Manager.count().let { it ->
if (it.toInt() == 0) {
val allManager = mutableListOf<InitManagerDto>()
allManager.addAll(createManager(ManagerType.Teacher, 1))
allManager.addAll(createManager(ManagerType.PamphaBhusal, 1))
allManager.addAll(createManager(ManagerType.SecretaryOfTheMinister, 1))
allManager.addAll(createManager(ManagerType.PropagandaDepartment, 1))
allManager.addAll(createManager(ManagerType.LiaisonMinister, 1))
arrayOf(
ManagerType.SecretaryDepartmentOfficer,
ManagerType.PublicityDepartmentOfficer,
ManagerType.LiaisonOfficer
).forEach {
allManager.addAll(createManager(it, 3))
}
allManager.forEach {
file.appendText("${it.account}------${it.originPassword}\n")
}
log.info("共生成${allManager.size}个管理员账号")
} else {
log.info("不需要生成管理员")
}
}
} }
} }
} }

@ -5,6 +5,7 @@ import org.jetbrains.exposed.sql.Column
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.vendors.MysqlDialect import org.jetbrains.exposed.sql.vendors.MysqlDialect
import org.jetbrains.exposed.sql.vendors.currentDialect import org.jetbrains.exposed.sql.vendors.currentDialect
import java.io.File
import java.math.BigInteger import java.math.BigInteger
import java.security.MessageDigest import java.security.MessageDigest
@ -24,9 +25,33 @@ fun String.md5(): String {
return BigInteger(1, md.digest(toByteArray())).toString(16).padStart(32, '0') return BigInteger(1, md.digest(toByteArray())).toString(16).padStart(32, '0')
} }
fun ByteArray.md5():String{
val md = MessageDigest.getInstance("MD5")
return BigInteger(1, md.digest(this)).toString(16).padStart(32, '0')
}
@Target(AnnotationTarget.CLASS,AnnotationTarget.FIELD) @Target(AnnotationTarget.CLASS,AnnotationTarget.FIELD)
annotation class TableComment(val comment:String) annotation class TableComment(val comment:String)
enum class FileFormat(val format:String,val head:String){
JPEG("jpg","FFD8FF")
}
fun ByteArray.getFormat(): FileFormat = format(this) ?: throw IllegalArgumentException("无法识别数据格式")
fun File.getFormat(): FileFormat {
val bytes=readBytes()
return format(bytes) ?: throw IllegalArgumentException("无非识别文件[${absolutePath}]文件的格式")
}
private fun format(bytes:ByteArray):FileFormat?{
FileFormat.values().forEach { it ->
if(it.head==bytes.copyOf(it.head.length / 2).joinToString(separator = "") { String.format("%02X",it) }){
return it
}
}
return null
}
/** /**
* 表字段添加注释 * 表字段添加注释

@ -1,7 +1,5 @@
package com.gyf.csams package com.gyf.csams
import kotlinx.serialization.Serializable
data class ApiResponse<T>(val code:Int=200,val message:String,val body:T?=null) data class ApiResponse<T>(val code:Int=200,val message:String,val body:T?=null)
@ -23,7 +21,6 @@ class Simple {
* @property studentId 学号 * @property studentId 学号
* @property name 姓名 * @property name 姓名
*/ */
@Serializable
data class UserVo(val studentId:String,val name:String) data class UserVo(val studentId:String,val name:String)
/** /**
@ -35,14 +32,13 @@ data class UserVo(val studentId:String,val name:String)
*/ */
data class UserLoginVo(val studentId: String,val password: String,val device: String) data class UserLoginVo(val studentId: String,val password: String,val device: String)
@Serializable data class UserLogoutVo(val userId:Int)
data class UserLogoutVo(val studentId:String)
data class UserResDto(val password:String) data class UserResDto(val password:String)
data class Token(val token:String,val createTime:Long,val studentId:String,val name:String) data class Token(val token:String, val createTime:Long, val userId:Int)
data class TokenVo(val token:String,val studentId:String) data class TokenVo(val token:String,val userId:Int)
sealed class BaseVo{ sealed class BaseVo{
abstract val token:Token abstract val token:Token
@ -53,4 +49,12 @@ data class LeaveMessageVo(val message: String, override val token:Token):BaseVo(
data class OnlyToken(override val token: Token):BaseVo() data class OnlyToken(override val token: Token):BaseVo()
//data class PageVo(val pageSize:Int=10,val page:, override val token: Token):BaseVo() //data class PageVo(val pageSize:Int=10,val page:, override val token: Token):BaseVo()
data class LeaveMessageFormatVo(val message: String,val studentId: String) data class LeaveMessageDto(val message: String,val user: UserVo)
data class RegAssociationVo(val name:String, val desc:String,val fileId:Int, override val token: Token):BaseVo()
data class ImageFileDto(val filepath:String,val md5:String,val createTime: Long,val url:String)
data class RegAssociationDto(val name:String,val desc:String,val logo:ImageFileDto)
data class InitManagerDto(val account:String,val originPassword:String)

@ -6,10 +6,12 @@ import cn.smallbun.screw.core.engine.EngineFileType
import cn.smallbun.screw.core.engine.EngineTemplateType import cn.smallbun.screw.core.engine.EngineTemplateType
import cn.smallbun.screw.core.execute.DocumentationExecute import cn.smallbun.screw.core.execute.DocumentationExecute
import cn.smallbun.screw.core.process.ProcessConfig import cn.smallbun.screw.core.process.ProcessConfig
import com.google.gson.Gson
import io.ktor.config.* import io.ktor.config.*
import io.ktor.http.* import io.ktor.http.*
import io.ktor.server.testing.* import io.ktor.server.testing.*
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
import java.io.File
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -47,6 +49,8 @@ class ApplicationTest {
put("ktor.deployment.mysql.driverClassName", "com.mysql.cj.jdbc.Driver") put("ktor.deployment.mysql.driverClassName", "com.mysql.cj.jdbc.Driver")
put("ktor.deployment.mysql.username", "root") put("ktor.deployment.mysql.username", "root")
put("ktor.deployment.mysql.password", "123456") put("ktor.deployment.mysql.password", "123456")
put("ktor.deployment.leaveMessage.maxSize", "20")
put("ktor.deployment.filePath", "upload")
} }
MySQL(testing = true) MySQL(testing = true)
}, test) }, test)
@ -60,19 +64,13 @@ class ApplicationTest {
} }
} }
@Test @Test
fun testUpdateComment(){ fun testUpdateComment(){
initApp { initApp {
// updateComment(Users,UserTokens) // updateComment(Users,UserTokens)
transaction { transaction {
UserToken.new { val s= MainService.getAllLeaveMessage()
studentId = "6666" Gson().toJson(s)
token = "22"
ip = "sdf"
device = "hahha"
}
} }
} }
} }
@ -87,15 +85,12 @@ class ApplicationTest {
} }
} }
@Test
fun localTime(){
initApp {
transaction {
}
} @Test
fun checkFileHead(){
val format=File("E:\\JetBrains\\IdeaProjects\\CsamsServer\\build\\classes\\kotlin\\main\\upload\\1621964313158").getFormat()
println(format.format)
} }
/** /**
@ -117,7 +112,7 @@ class ApplicationTest {
//生成模板实现 //生成模板实现
.produceType(EngineTemplateType.freemarker) .produceType(EngineTemplateType.freemarker)
//自定义文件名称 //自定义文件名称
.fileName(fileName).build(); .fileName(fileName).build()
println("数据库文档输出路径${engineConfig.fileOutputDir}") println("数据库文档输出路径${engineConfig.fileOutputDir}")
//忽略表 //忽略表
// val ignoreTableName = ArrayList<String>() // val ignoreTableName = ArrayList<String>()

@ -0,0 +1,32 @@
/*
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from channels.md by Knit tool. Do not edit.
package kotlinx.coroutines.guide.exampleChannel04
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.produce
fun main() = runBlocking {
val numbers = produceNumbers() // produces integers from 1 and on
val squares = square(numbers) // squares integers
repeat(5) {
delay(1000)
println(squares.receive()) // print first five
}
println("Done!") // we are done
coroutineContext.cancelChildren() // cancel children coroutines
}
@OptIn(ExperimentalCoroutinesApi::class)
fun CoroutineScope.produceNumbers() = produce<Int> {
var x = 1
while (true) send(x++) // infinite stream of integers starting from 1
}
@OptIn(ExperimentalCoroutinesApi::class)
fun CoroutineScope.square(numbers: ReceiveChannel<Int>): ReceiveChannel<Int> = produce {
for (x in numbers) send(x * x)
}
Loading…
Cancel
Save