package com.gyf.csams import io.ktor.application.* import io.ktor.http.content.* import io.ktor.util.* import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.transactions.transaction import org.slf4j.Logger import java.io.File import java.time.LocalDateTime import kotlin.properties.Delegates import kotlin.system.exitProcess 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 : AbstractService() { /** * 检查学号是否已注册,true=已注册 */ fun registered(selectId: String): Boolean { return transaction { return@transaction !User.find { Users.studentId eq selectId }.empty() } } /** * 注册 */ fun register(userVo: UserRegVo): UserResDto? { try { return transaction { val originPassword = randomNum(8) User.new { studentId = userVo.studentId name = userVo.name password = originPassword.md5() } return@transaction UserResDto(password = originPassword) } } catch (e: Exception) { log.error("注册失败,发生异常:$e") return null } } /** * 前台登录 * */ fun login(userLoginVo: UserLoginVo, _ip: String): UserVo { return transaction { val matchUser = User.find { Users.studentId eq userLoginVo.studentId }.firstOrNull() when { matchUser == null -> { log.warn("学号:${userLoginVo.studentId}不存在") throw StudentIdError(userLoginVo.studentId) } userLoginVo.password.md5() != matchUser.password -> { log.warn("密码:${userLoginVo.password}错误") throw PasswordError(id = userLoginVo.studentId, password = userLoginVo.password) } else -> { val token = UserToken.new { user = matchUser ip = _ip device = userLoginVo.device token = listOf(matchUser.id, ip, device).joinToString(separator = ('a'..'z').random().toString()) .md5() } token.flush() return@transaction UserVo( studentId = matchUser.studentId, token = Token( id = matchUser.id.value, token = token.token, createTime = token.createTime.format() ), name = matchUser.name, headImg = matchUser.headImg?.filepath, desc = matchUser.desc, associationMemberVo = matchUser.associationMember?.let { AssociationMemberVo(association = toAssociationVo(it.association),isHead = it.isHead) } ) } } } } private fun toAssociationVo(vo:Association):AssociationVo{ return AssociationVo(id=vo.id.value,name = vo.name,desc = vo.desc,logo = vo.logo.filepath, faculty = AssociationFaculty.valueOf(vo.faculty),level = vo.level?.let { AssociationLevel.valueOf(it) }) } private fun tokenRes(token: ManagerToken, matchManager: Manager): ManagerVo { return ManagerVo( account = matchManager.account, token = Token( id = matchManager.id.value, token = token.token, createTime = token.createTime.format() ), desc = matchManager.desc, duty = Duty.valueOf(matchManager.duty), headImg = matchManager.headImg?.filepath, name = matchManager.name ) } private fun tokenRes(token:UserToken,matchUser:User):UserVo{ return UserVo( studentId = matchUser.studentId, token = Token( id = matchUser.id.value, token = token.token, createTime = token.createTime.format() ), name = matchUser.name, headImg = matchUser.headImg?.filepath, desc = matchUser.desc, associationMemberVo = matchUser.associationMember?.let { AssociationMemberVo(association = toAssociationVo(it.association),isHead = it.isHead) } ) } fun login(managerLoginVo: ManagerLoginVo, _ip: String): ManagerVo { return transaction { val matchManager = Manager.find { Managers.account eq managerLoginVo.account }.firstOrNull() when { matchManager == null -> { log.warn("账号:${managerLoginVo.account}不存在") throw AccountError(managerLoginVo.account) } managerLoginVo.password.md5() != matchManager.password -> { log.warn("密码:${managerLoginVo.password}错误") throw PasswordError(id = managerLoginVo.account, password = managerLoginVo.password) } else -> { val token = ManagerToken.new { manager = matchManager ip = _ip device = managerLoginVo.device token = listOf(matchManager.id, ip, device).joinToString(separator = ('a'..'z').random().toString()) .md5() } token.flush() return@transaction tokenRes(token, matchManager) } } } } fun getManagerVo(token: Token): ManagerVo { return transaction { val c = ManagerToken.find { ManagerTokens.managerId eq token.id ManagerTokens.token eq token.token } if (!c.empty()) { val manager = Manager.findById(token.id) return@transaction tokenRes(token = c.first(),matchManager = manager!!) } else { throw IllegalArgumentException("token校验失败") } } } fun getUserVo(token: Token): UserVo { return transaction { val c= UserToken.find { UserTokens.userId eq token.id UserTokens.token eq token.token } if (!c.empty()) { val user = User.findById(token.id) return@transaction tokenRes(token = c.first(),matchUser = user!!) } else { throw IllegalArgumentException("token校验失败") } } } fun validManagerToken(token:Token):Boolean{ return transaction { !ManagerToken.find { ManagerTokens.managerId eq token.id ManagerTokens.token eq token.token }.empty() } } fun validUserToken(token: Token):Boolean{ return transaction { !UserToken.find { UserTokens.userId eq token.id UserTokens.token eq token.token }.empty() } } fun validToken(vo: T): Boolean { return if (vo.clientType == ClientType.Foreground) { validUserToken(vo.token) } else { validManagerToken(vo.token) } } fun logout(vo: OnlyToken): Boolean { return transaction { if (vo.clientType == ClientType.Foreground) UserTokens.deleteWhere { UserTokens.userId eq vo.token.id } > 0 else ManagerTokens.deleteWhere { ManagerTokens.managerId eq vo.token.id } > 0 } } fun test() { log.info("开始测试") transaction { log.info("查询到个${User.count()}用户") } log.info("结束测试") } } /** * 主页服务 */ object MainService : AbstractService() { private var maxSize by Delegates.notNull() override fun init(environment: ApplicationEnvironment) { super.init(environment) this.maxSize = environment.config.property("ktor.deployment.leaveMessage.maxSize").getString().toInt() } /** * 创建留言信息 * * @param leaveMessageVo * @return */ fun createMessage(leaveMessageVo: LeaveMessageVo): Boolean { return if (leaveMessageVo.message.isNotEmpty()) { return transaction { val count = LeaveMessage.count().toInt() log.info("系统留言数:$count,限制数:$maxSize") if (count >= maxSize) { LeaveMessage.all().sortedBy { it.createTime }.subList(0, count - maxSize).forEach { it.delete() } } log.info("保存留言:${leaveMessageVo.message}") LeaveMessage.new { user = User.findById(leaveMessageVo.token.id) ?: throw IllegalArgumentException("非法id") message = leaveMessageVo.message } log.info("留言保存成功") return@transaction true } } else { log.info("留言不能为空") false } } fun getAllLeaveMessage(): List { return transaction { log.info("获取所有留言") return@transaction LeaveMessage.all().toList().map { LeaveMessageDto( message = "${it.user.name}说:${it.message}", user = UserInfoVo(name = it.user.name, headImg = it.user.headImg?.filepath, desc = it.user.desc) ) } } } } /** * 文件管理服务 */ 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() filePath = this::class.java.classLoader.getResource(uploadDir)?.path ?: throw IllegalArgumentException("初始化资源目录失败") log.info("上传路径[${filePath}]") } 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 getPartData(map: Map, 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): List? { val map = data.associateBy { it.name }.toMutableMap() log.info("map=${map}") val userId = getPartData(map, "id").value val token = getPartData(map, "token").value val createTime = getPartData(map, "createTime").value val tokenVo = Token(token = token, id = userId.toInt(), createTime = createTime.toLong()) if (AccountService.validUserToken(tokenVo)) { map.remove("id") map.remove("token") val fileIds = mutableListOf() 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) } log.info("文件成功保存到${file.absolutePath}") val fileId = save(id = userId, "/${uploadDir}/${fullFileName}", file = file) fileIds.add(fileId) } } return fileIds } else { return null } } } /** * 社团服务 */ object AssociationService : AbstractService() { /** * 注册社团 * * @param regVo * @return */ fun register(regVo: AssociationRegVo) { return transaction { //再次申请 val user = User.findById(regVo.token.id) ?: throw UserIdError(regVo.token.id) if(regVo.id!=null){ log.info("再次提交【${regVo.name}】注册资料") val association= Association.findById(regVo.id)?:throw RegIdError(regVo.id) val log = AuditLogging.new { this.user = user } association.apply { name = regVo.name desc = regVo.desc logo = ImageFile.findById(regVo.fileId) ?: throw FileIdError(regVo.fileId) this.log = log } }else { val association:Association?= AuditLogging.find { AuditLeggings.userId eq user.id }.firstOrNull()?.user?.associationMember?.association when { association != null && association.log.result == true -> throw IllegalArgumentException("您是社团团长不能再创建其他社团") association != null && association.log.result == null -> throw IllegalArgumentException("您已经提交过社团注册资料,请耐心等待后台管理员处理") association == null -> { val log = AuditLogging.new { this.user = user } Association.new { name = regVo.name desc = regVo.desc logo = ImageFile.findById(regVo.fileId) ?: throw FileIdError(regVo.fileId) this.log = log faculty = user.faculty().name } } } } Notification.new { title = "注册社团" content = "您成功提交了一份社团注册资料,请耐心等待后台受理" receiverId = regVo.token.id receiverClient = ClientType.Foreground.name } BackgroundService.createBackgroundNotification( title = "审核注册社团", content = "用户${user.name}提交了一份社团资料需要您进行受理", duty = Duty.PamphaBhusal ) this@AssociationService.log.info("未审核社团:${regVo.name}创建成功") } } /** * 前台读取社团注册资料 * */ fun read(vo:OnlyToken):AssociationCheckVo?{ return transaction { val association:Association?= AuditLogging.find { AuditLeggings.userId eq vo.token.id }.firstOrNull()?.user?.associationMember?.association return@transaction association?.let { AssociationCheckVo(id=it.id.value,name=it.name,desc=it.desc,logo = it.logo.filepath, faculty = AssociationFaculty.valueOf(it.faculty),level = it.level?.let { it1 -> AssociationLevel.valueOf( it1 ) },checkStatus = when{ it.log.nextAudit == null && it.log.manager == null->CheckStatus.WaitFirst it.log.nextAudit == null && it.log.manager != null->CheckStatus.AcceptFirst it.log.nextAudit != null && it.log.nextAudit?.manager==null ->CheckStatus.WaitLast it.log.nextAudit != null && it.log.nextAudit?.result==null ->CheckStatus.AcceptLast else->CheckStatus.Finish },applyTime = it.log.applyTime.format(), firstCause = it.log.cause?:"",lastCause = it.log.nextAudit?.cause, fileId = it.logo.id.value) } } } /** * 注册资料受理 * * @param vo */ fun accept(vo: AcceptRegAssociation) { transaction { val association= Association.find { Associations.logId eq vo.regId }.firstOrNull()?:throw RegIdError(vo.regId) when (val manager = Manager.findById(vo.token.id)) { null -> throw ManagerIdError(vo.token.id) else -> { association.apply { if(vo.isFirstAccept) { log.manager = manager log.acceptTime = LocalDateTime.now() }else{ log.nextAudit?.manager=manager log.nextAudit?.acceptTime=LocalDateTime.now() } } log.info("[${association.name}]社团注册资料已受理") Notification.new { title = "注册社团" content = "您提交的[${association.name}]社团注册资料已受理" receiverId = vo.token.id receiverClient = ClientType.Foreground.name } } } } } /** * 社团列表加载 * * @param vo * @return */ fun load(vo: SearchAssociationVo): List { return transaction { log.info("社团搜索条件[name=${vo.name},desc=${vo.desc}]") val nextAudit=AuditLeggings.alias("nextAudit") return@transaction Associations.innerJoin(otherTable = AuditLeggings).innerJoin(nextAudit,{AuditLeggings.nextAudit},{nextAudit[AuditLeggings.id]}) .slice(Associations.columns) .select { nextAudit[AuditLeggings.result] eq true and (Associations.name like "%${vo.name}%") and (Associations.desc like "%${vo.desc}%") }.map { val imageFile=ImageFile.findById(it[Associations.logo])?:throw FileIdError(it[Associations.logo].value) AssociationVo(name = it[Associations.name], id = it[Associations.id].value, logo = imageFile.filepath, desc = it[Associations.desc], faculty = it[Associations.faculty].let { it1 -> AssociationFaculty.valueOf( it1 ) }, level = it[Associations.level]?.let { it1 -> AssociationLevel.valueOf(it1) }) } } } private fun toAuditLoggingVo(it:AuditLogging?):AuditLoggingVo?{ return it?.let { val auditLogging=AuditLoggingVo(id=it.id.value,user = UserInfoVo(name = it.user.name,headImg = it.user.headImg?.filepath,desc = it.user.desc),applyTime = it.applyTime.format(),manager = it.manager?.let { ManagerInfoVo(duty = Duty.valueOf(it.duty),name = it.name,headImg = it.headImg?.filepath,desc = it.desc) }, acceptTime = it.acceptTime?.format(),cause = it.cause,result = it.result,auditTime = it.auditTime?.format(), nextAudit = toAuditLoggingVo(it.nextAudit)) auditLogging } } fun loadAudit(vo: OnlyToken): List { return transaction { return@transaction Association.all().map { val log=toAuditLoggingVo(it.log)?:throw IllegalArgumentException("转换审核记录出错!!!!") DisposeRegInfoVo(name = it.name,desc = it.desc,logo = it.logo.filepath,log=log) }.apply { log.info("找到${this.size}份社团注册资料") } } } /** * 审核记录 * * @param vo */ fun check(vo:CheckRegVo){ transaction { try { val association=Association.find { Associations.logId eq vo.regId }.firstOrNull()?:throw RegIdError(vo.regId) val matchUser=User.findById(vo.token.id)?:throw UserIdError(vo.token.id) association.log.apply { if(nextAudit!=null){ nextAudit?.apply { cause = vo.cause result = vo.result auditTime = LocalDateTime.now() } }else { cause = vo.cause result = vo.result auditTime = LocalDateTime.now() } }.apply { this@AssociationService.log.info("更新审核结果") } when{ association.log.nextAudit==null&&vo.result->{ val log = AuditLogging.new { this.user = matchUser }.apply { this@AssociationService.log.info("构造复审记录") } association.log.nextAudit=log BackgroundService.createBackgroundNotification( title = "审核注册社团", content = "总部长上报了一份社团注册资料需要您进行受理", duty = Duty.Teacher ).apply { this@AssociationService.log.info("通知老师复审社团注册资料") } Notification.new { title = "注册社团" content = "您提交的【${association.name}】社团注册资料初审通过,请耐心等待复审" receiverId = matchUser.id.value receiverClient = ClientType.Foreground.name }.apply { this@AssociationService.log.info("通知前台用户审核结果") } } association.log.nextAudit==null&&!vo.result->{ Notification.new { title = "注册社团" content = "您提交的【${association.name}】社团注册资料初审不通过,可根据初审意见,重新申请\n" + "【初审意见:${vo.cause},审核人:${association.log.manager?.desc?:""}】" receiverId = matchUser.id.value receiverClient = ClientType.Foreground.name }.apply { this@AssociationService.log.info("通知前台用户审核结果") } } association.log.nextAudit!=null&&vo.result->{ AssociationMember.new { this.association=association isHead=true }.apply { matchUser.associationMember=this this@AssociationService.log.info("初始化社团团长") } Notification.new { title = "注册社团" content = "您提交的【${association.name}】社团注册资料复审通过" receiverId = matchUser.id.value receiverClient = ClientType.Foreground.name }.apply { this@AssociationService.log.info("通知前台用户审核结果") } } else->{ Notification.new { title = "注册社团" content = "您提交的【${association.name}】社团注册资料复审不通过,可根据复审意见,重新申请\n" + "【复审意见:${vo.cause},审核人:${association.log.nextAudit?.manager?.desc?:""}】" receiverId = matchUser.id.value receiverClient = ClientType.Foreground.name }.apply { this@AssociationService.log.info("通知前台用户审核结果") } } } this@AssociationService.log.info("【${association.name}】社团注册资料审核完成") } catch (e: Exception) { rollback() throw e } } } } /** * 通知服务 */ object NotificationService : AbstractService() { /** * 拉取最新通知 * * @param vo * @return */ fun pull(vo: NotificationDto): List { return transaction { log.info("通知查询条件[receiverId=${vo.receiverId},receiverClient=${vo.receiverClient.name},pull=false]") val notifications = Notification.find { Notifications.pull eq false and (Notifications.receiverId eq vo.receiverId) and (Notifications.receiverClient eq vo.receiverClient.name) } log.info("获取${notifications.count()}条最新通知") return@transaction notifications.map { it.pull = true NotificationVo( title = it.title, id = it.id.value, content = it.content, createTime = it.createTime.format() ) } } } /** * 未读通知计数 * * @param vo * @return */ fun count(vo: NotificationDto): Long { return transaction { return@transaction Notification.find { Notifications.read eq false and (Notifications.receiverId eq vo.receiverId) and (Notifications.receiverClient eq vo.receiverClient.name) }.count().apply { log.info("未读通知${this}条") } } } /** * * @param vo * @return */ fun list(vo: NotificationDto): List? { return vo.page?.let { transaction { log.info("page:${it}") return@transaction Notification.find { (Notifications.receiverId eq vo.receiverId) and (Notifications.receiverClient eq vo.receiverClient.name) }.limit(n = it.pageSize, offset = (it.currentPage - 1) * it.pageSize).map { NotificationVo( title = it.title, id = it.id.value, content = it.content, createTime = it.createTime.format() ) } } } } } /** * 后台服务 */ object BackgroundService : AbstractService() { override fun init(environment: ApplicationEnvironment) { super.init(environment) initManager() } /** * 前台任务通知管理员处理 * */ fun createBackgroundNotification(content: String, title: String, duty: Duty) { Manager.find { Managers.duty eq duty.name }.apply { if (count() == 0L) { log.warn("找不到适当的${duty.desc}处理此任务") } else { forEach { Notification.new { this.title = title this.content = content receiverId = it.id.value receiverClient = ClientType.Background.name } } } } } private fun createManager(duty: Duty, num: Int = 1): MutableList { val managerList = mutableListOf() repeat(num) { val originPassword = randomNum() Manager.new { account = randomNum() password = originPassword.md5() this.duty = duty.name this.name = duty.desc }.apply { managerList.add(InitManagerDto(account = account, originPassword = originPassword, duty = duty)) } } return managerList } //初始化管理员 private fun initManager() { transaction { val resourcePath = this::class.java.classLoader.getResource("")?.path ?: throw IllegalArgumentException("初始化资源目录失败") val file = File(resourcePath, "管理员账号.txt") try { if (!file.exists()) { Manager.count().let { it -> if (it.toInt() == 0) { val allManager = mutableListOf() allManager.addAll(createManager(Duty.Teacher, 1)) allManager.addAll(createManager(Duty.PamphaBhusal, 1)) allManager.addAll(createManager(Duty.SecretaryOfTheMinister, 1)) allManager.addAll(createManager(Duty.PropagandaDepartment, 1)) allManager.addAll(createManager(Duty.LiaisonMinister, 1)) arrayOf( Duty.SecretaryDepartmentOfficer, Duty.PublicityDepartmentOfficer, Duty.LiaisonOfficer ).forEach { allManager.addAll(createManager(it, 3)) } allManager.forEach { file.appendText("${it.account}------${it.originPassword}------${it.duty.desc}\n") } log.info("共生成${allManager.size}个管理员账号") } else { log.info("不需要生成管理员") } } } } catch (e: Exception) { log.error(e) exitProcess(0) } } } }