社团接口更新

完善注册接口
增加社团查询,社团注册资料受理、审核接口
更新用户表、管理员表、审核表、通知表、社团表
增加社团成员表
增加Error自定义异常类
服务类更新token校验逻辑,登录获取用户信息,详看类
工具类增加LocalDateTime->Long转换
实体类更新用户管理员实体,增加社团相关实体
master
pan 3 years ago
parent 9fe8fcad7b
commit 1bdf4a1ae2
  1. 2
      resources/application.conf
  2. 1
      src/Application.kt
  3. 145
      src/Controller.kt
  4. 139
      src/Dao.kt
  5. 19
      src/Error.kt
  6. 2
      src/MySQL.kt
  7. 468
      src/Service.kt
  8. 29
      src/Util.kt
  9. 241
      src/Vo.kt
  10. 31
      test/ApplicationTest.kt

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

@ -30,6 +30,7 @@ fun Application.module(testing: Boolean = false) {
fun Application.Controller(testing: Boolean = false){
StaticController()
AccountController()
TestController()
MainController()

@ -5,6 +5,7 @@ import io.ktor.http.content.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.util.*
suspend inline fun <reified T : ClientBaseVo> withToken(call: ApplicationCall, callback: (vo: T) -> Unit) {
val levelVo = call.receive<T>()
@ -15,6 +16,25 @@ suspend inline fun <reified T : ClientBaseVo> withToken(call: ApplicationCall, c
}
}
fun Application.StaticController(){
routing {
val uploadDir=environment.config.property("ktor.deployment.filePath").getString()
static(uploadDir) {
resources(uploadDir)
}
}
}
suspend fun loginRes(call: ApplicationCall, body:OwnInfoVo){
try {
call.respond(ApiResponse(message = "登陆成功", body = body))
} catch (e: IllegalArgumentException) {
call.respond(ApiResponse(message = "${e.message}",body=null))
}catch (e:Exception){
call.respond(ApiResponse(message = "发生未知错误,请联系管理员",body=null))
}
}
fun Application.AccountController() {
routing {
@ -40,7 +60,7 @@ fun Application.AccountController() {
* 注册账号
*/
post {
val userVo = call.receive<UserVo>()
val userVo = call.receive<UserRegVo>()
val userResDto = AccountService.register(userVo)
if (userResDto != null) {
call.respond(ApiResponse(message = "注册成功", body = userResDto))
@ -55,26 +75,36 @@ fun Application.AccountController() {
route(path = ClientType.Foreground.name.toLowerCase()){
post {
val userLoginVo = call.receive<UserLoginVo>()
val token = AccountService.login(userLoginVo, call.request.host())
call.respond(ApiResponse(message = if (token != null) "登陆成功" else "账号或密码错误!!!", body = token))
loginRes(call=call,body = AccountService.login(userLoginVo, call.request.host()))
}
post("/token"){
val tokenVo = call.receive<Token>()
val isValid = AccountService.validUserToken(tokenVo)
call.respond(ApiResponse(message = if (isValid) "令牌合法" else "令牌不合法", body = isValid))
try {
val userinfo = AccountService.getUserVo(tokenVo)
log.info("$userinfo")
call.respond(ApiResponse(message = "令牌合法", body = userinfo))
} catch (e: Exception) {
log.error(e)
call.respond(ApiResponse(message = "令牌不合法", body = null))
}
}
}
route(path = ClientType.Background.name.toLowerCase()){
post{
val managerLoginVo = call.receive<ManagerLoginVo>()
val token = AccountService.login(managerLoginVo, call.request.host())
call.respond(ApiResponse(message = if (token != null) "登陆成功" else "账号或密码错误!!!", body = token))
loginRes(call=call,body = AccountService.login(managerLoginVo, call.request.host()))
}
post("/token"){
val tokenVo = call.receive<Token>()
val isValid = AccountService.validManagerToken(tokenVo)
call.respond(ApiResponse(message = if (isValid) "令牌合法" else "令牌不合法", body = isValid))
try {
val managerInfo = AccountService.getManagerVo(tokenVo)
log.info("$managerInfo")
call.respond(ApiResponse(message = "令牌合法", body = managerInfo))
} catch (e: Exception) {
log.error(e)
call.respond(ApiResponse(message = "令牌不合法", body = null))
}
}
}
}
@ -82,9 +112,9 @@ fun Application.AccountController() {
post(path = "/logout") {
environment.log.info("退出登录")
log.info("退出登录")
val onlyToken = call.receive<OnlyToken>()
environment.log.info("$onlyToken")
log.info("$onlyToken")
val flag = AccountService.logout(onlyToken)
call.respond(ApiResponse(message = if (flag) "退出成功" else "退出失败", body = flag))
}
@ -131,9 +161,12 @@ fun Application.AssociationController() {
routing {
route("$ApiPathPrefix/association") {
post("/uploadLogo") {
log.info("开始上传logo")
val multipartData = call.receiveMultipart()
multipartData.readAllParts().apply {
environment.log.info("part size=$size")
log.info("part size=$size")
if (size == 4) {
val result=FileService.storeFile(this)
call.respond(ApiResponse(message = if(result.isNullOrEmpty()) "文件上传失败" else
@ -143,20 +176,90 @@ fun Application.AssociationController() {
throw IllegalArgumentException("参数异常")
}
}
log.info("----end-----")
}
post("/register"){
withToken<RegAssociationVo>(call = call){
log.info("开始提交注册资料")
withToken<AssociationRegVo>(call = call){
try {
AssociationService.register(regVo=it)
call.respond(ApiResponse(message = "社团注册资料已提交,请等待受理",body = true))
} catch (e: IllegalArgumentException) {
call.respond(ApiResponse(message = "社团资料提交失败,请联系系统管理员",body = false))
} catch (e:Exception){
call.respond(ApiResponse(message = e.message?:"发生未知错误,请联系系统管理员",body = false))
}
}
log.info("----end-----")
}
post("/list"){
log.info("开始查询社团")
withToken<SearchAssociationVo>(call = call){
try {
call.respond(ApiResponse(message = "社团列表检索完成",body=AssociationService.load(vo=it)))
} catch (e: Exception) {
log.error(e)
call.respond(ApiResponse(message = "社团列表检索失败",body=null))
}
}
log.info("----end-----")
}
post("/accept"){
log.info("开始受理")
withToken<AcceptRegAssociation>(call = call){
try {
AssociationService.accept(vo=it)
call.respond(ApiResponse(message = "社团注册资料受理成功",body=true))
}catch (e:IllegalArgumentException){
call.respond(ApiResponse(message = "社团资料受理失败,请联系系统管理员",body = false))
}catch (e:Exception){
call.respond(Simple.error("发生未知错误,请联系管理员"))
}
}
log.info("----end-----")
}
post("/audit"){
log.info("审核列表")
withToken<OnlyToken>(call = call){
try {
call.respond(ApiResponse(message = "社团注册资料获取成功",body=AssociationService.loadAudit(it)))
}catch (e:IllegalArgumentException){
call.respond(Simple.error("社团注册资料获取失败"))
}catch (e:Exception){
call.respond(Simple.error("发生未知错误,请联系管理员"))
}
}
log.info("----end-----")
}
post("/check"){
log.info("审核社团注册资料")
withToken<CheckRegVo>(call=call){
try {
AssociationService.check(it)
call.respond(ApiResponse(message = "审核结果已保存",body = true))
} catch (e: Exception) {
log.error(e)
call.respond(ApiResponse(message = "审核结果保存失败",body = false))
}
}
log.info("----end-----")
}
post("/read"){
log.info("查询用户社团")
withToken<OnlyToken>(call = call){
try {
val flag=AssociationService.register(vo=it)
call.respond(ApiResponse(message = if(flag) "社团注册资料已提交,请等待受理" else "社团注册资料提交失败",body=flag))
} catch (e: UserIdError) {
call.respond(ApiResponse(message = "社团资料提交失败,请联系管理员",body = false))
} catch (e:FileIdError){
call.respond(ApiResponse(message = "社团资料提交失败,请联系管理员",body = false))
}catch (e:HasAssociationError){
call.respond(ApiResponse(message = e.message?:"发生未知错误,请联系管理员",body = false))
call.respond(ApiResponse(message = "用户社团查询成功",body=AssociationService.read(vo=it)))
} catch (e: Exception) {
log.error(e)
call.respond(Simple.error("用户社团查询失败"))
}
}
log.info("----end-----")
}
}
}

@ -10,17 +10,28 @@ import org.jetbrains.exposed.sql.`java-time`.CurrentTimestamp
import org.jetbrains.exposed.sql.`java-time`.datetime
import java.time.LocalDateTime
@TableComment("用户")
object Users: IntIdTable(){
@TableComment("学号")
val studentId:Column<String> = varchar(name="student_id",length = 8).uniqueIndex()
open class Person:IntIdTable(){
@TableComment("姓名")
val name:Column<String> = varchar(name="name",length = 10)
@TableComment("密码")
val password:Column<String> = varchar(name="password",length = 32)
@TableComment("个人简介")
val desc:Column<String> = varchar(name="desc",length = 20).default("")
@TableComment("头像")
val imgId:Column<EntityID<Int>?> = reference("img_id", ImageFiles).nullable()
}
@TableComment("用户")
object Users: Person(){
@TableComment("学号")
val studentId:Column<String> = varchar(name="student_id",length = 8).uniqueIndex()
@TableComment("社团成员id")
val associationMemberId:Column<EntityID<Int>?> = reference("association_member_id", AssociationMembers).nullable()
}
class User(id:EntityID<Int>):IntEntity(id){
@ -28,8 +39,33 @@ class User(id:EntityID<Int>):IntEntity(id){
var studentId by Users.studentId
var name by Users.name
var password by Users.password
var desc by Users.desc
val headImg by ImageFile optionalReferencedOn Users.imgId
var associationMember by AssociationMember optionalReferencedOn Users.associationMemberId
}
@TableComment("后台管理员")
object Managers:Person(){
@TableComment("帐号")
val account:Column<String> = varchar(name="account",length=10)
@TableComment("职务")
val duty:Column<String> = varchar(name="duty",length = 32)
}
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 desc by Managers.desc
var name by Managers.name
val headImg by ImageFile optionalReferencedOn Managers.imgId
}
open class BaseTokens:IntIdTable(){
@TableComment("令牌")
val token:Column<String> = varchar(name="token",length = 32)
@ -118,7 +154,7 @@ class ImageFile(id:EntityID<Int>):IntEntity(id){
var createTime by ImageFiles.createTime
}
@TableComment("社团")
@TableComment("社团信息")
object Associations:IntIdTable(){
@TableComment("社团名称")
val name:Column<String> = varchar(name = "name", length = 10)
@ -129,11 +165,14 @@ object Associations:IntIdTable(){
@TableComment("社团logo")
val logo:Column<EntityID<Int>> = reference("logo", ImageFiles)
@TableComment("社团审核状态")
val status:Column<Boolean> = bool(name="status").default(false)
@TableComment("审核信息")
val logId:Column<EntityID<Int>> = reference("log_id",AuditLeggings)
@TableComment("社团级别")
val level:Column<String?> = varchar(name="level",length = 1).nullable()
@TableComment("创建人")
val user:Column<EntityID<Int>> = reference("user_id",Users)
@TableComment("所属院系")
val faculty:Column<String> = varchar(name="faculty",length = 20)
}
class Association(id:EntityID<Int>):IntEntity(id){
@ -141,57 +180,64 @@ class Association(id:EntityID<Int>):IntEntity(id){
var name by Associations.name
var desc by Associations.desc
var logo by ImageFile referencedOn Associations.logo
var status by Associations.status
var user by User referencedOn Associations.user
var log by AuditLogging referencedOn Associations.logId
val level by Associations.level
var faculty by Associations.faculty
}
@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("社团成员信息")
object AssociationMembers:IntIdTable(){
@TableComment("社团Id")
val associationId:Column<EntityID<Int>> = reference("association_id",Associations)
@TableComment("等级")
val level:Column<Int> = integer("level")
@TableComment("是否团长")
val isHead:Column<Boolean> = bool("is_head")
}
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
class AssociationMember(id:EntityID<Int>):IntEntity(id) {
companion object : IntEntityClass<AssociationMember>(AssociationMembers)
var association by Association referencedOn AssociationMembers.associationId
var isHead by AssociationMembers.isHead
}
@TableComment("审核记录")
object CheckForms:IntIdTable(){
@TableComment("审核类型")
val type:Column<String> = varchar(name="type",length = 10)
object AuditLeggings:IntIdTable(){
@TableComment("申请人")
val userId: Column<EntityID<Int>> = reference("user_id",Users)
@TableComment("审核人")
val managerId:Column<EntityID<Int>> = reference("manager_id",Managers)
@TableComment("申请时间")
val applyTime:Column<LocalDateTime> = datetime("apply_time").defaultExpression(CurrentDateTime())
@TableComment("负责人")
val managerId: Column<EntityID<Int>?> = reference("manager_id",Managers).nullable()
@TableComment("受理时间")
val acceptTime:Column<LocalDateTime?> = datetime("accept_time").nullable()
@TableComment("审核理由")
val cause:Column<String> = varchar("cause",length = 30)
val cause:Column<String?> = varchar("cause",length = 30).nullable()
@TableComment("审核对象")
val target:Column<Int> = integer("target")
@TableComment("审核结果")
val result:Column<Boolean?> = bool("result").nullable()
@TableComment("审核时间")
val createTime:Column<LocalDateTime> = datetime("create_time").defaultExpression(CurrentDateTime())
val auditTime:Column<LocalDateTime?> = datetime("audit_time").nullable()
@TableComment("复审记录")
val nextAudit:Column<EntityID<Int>?> = reference("next_audit",AuditLeggings).nullable()
}
class CheckForm(id:EntityID<Int>):IntEntity(id){
companion object:IntEntityClass<CheckForm>(CheckForms)
var type by CheckForms.type
val manager by Manager referrersOn CheckForms.managerId
var cause by CheckForms.cause
var target by CheckForms.target
class AuditLogging(id:EntityID<Int>):IntEntity(id){
companion object:IntEntityClass<AuditLogging>(AuditLeggings)
var user by User referencedOn AuditLeggings.userId
var applyTime by AuditLeggings.applyTime
var manager by Manager optionalReferencedOn AuditLeggings.managerId
var acceptTime by AuditLeggings.acceptTime
var cause by AuditLeggings.cause
var result by AuditLeggings.result
var auditTime by AuditLeggings.auditTime
var nextAudit by AuditLogging optionalReferencedOn AuditLeggings.nextAudit
}
@TableComment("通知记录")
@ -200,7 +246,7 @@ object Notifications:IntIdTable(){
val title:Column<String> = varchar(name="title",length = 10)
@TableComment("通知内容")
val content:Column<String> = varchar(name="content",length = 30)
val content:Column<String> = varchar(name="content",length = 256)
@TableComment("接收者")
val receiverId:Column<Int> = integer(name="receiver_id")
@ -228,3 +274,4 @@ class Notification(id:EntityID<Int>):IntEntity(id){
var pull by Notifications.pull
var createTime by Notifications.createTime
}

@ -0,0 +1,19 @@
package com.gyf.csams
class UserIdError(id:Int):IllegalArgumentException("用户Id[${id}]不存在")
class StudentIdError(id:String):IllegalArgumentException("学号[${id}]不存在")
class FileIdError(id:Int):IllegalArgumentException("文件Id[${id}]不存在")
class RegIdError(id:Int):IllegalArgumentException("注册资料Id[${id}]不存在")
class ManagerIdError(id:Int):IllegalArgumentException("管理员Id[${id}]不存在")
class AccountError(id:String):IllegalArgumentException("账号[${id}]不存在")
class PasswordError(id:String,password:String):IllegalArgumentException("[id=${id}]密码[${password}]错误")
class AssociationIdError(id:Int):IllegalArgumentException("社团Id[${id}]不存在")

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

@ -2,13 +2,14 @@ package com.gyf.csams
import io.ktor.application.*
import io.ktor.http.content.*
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.deleteWhere
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.ZoneOffset
import java.time.LocalDateTime
import kotlin.properties.Delegates
import kotlin.system.exitProcess
interface BaseService {
fun init(environment: ApplicationEnvironment)
@ -38,7 +39,7 @@ object AccountService:AbstractService() {
/**
* 注册
*/
fun register(userVo: UserVo): UserResDto? {
fun register(userVo: UserRegVo): UserResDto? {
try {
return transaction {
val originPassword = randomNum(8)
@ -59,62 +60,137 @@ object AccountService:AbstractService() {
* 前台登录
*
*/
fun login(userLoginVo: UserLoginVo,_ip:String):Token?{
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}不存在")
return@transaction null
throw StudentIdError(userLoginVo.studentId)
}
userLoginVo.password.md5() != matchUser.password -> {
log.warn("密码:${userLoginVo.password}错误")
return@transaction null
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 =
listOf(matchUser.id, ip, device).joinToString(separator = ('a'..'z').random().toString())
.md5()
}
token.flush()
return@transaction Token(id = matchUser.id.value,token = token.token,
createTime = token.createTime.toEpochSecond(
ZoneOffset.of("+8")))
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) })
}
fun login(managerLoginVo: ManagerLoginVo, _ip:String):Token?{
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 matchUser=Manager.find { Managers.account eq managerLoginVo.account }.firstOrNull()
val matchManager = Manager.find { Managers.account eq managerLoginVo.account }.firstOrNull()
when {
matchUser==null -> {
log.warn("学号:${managerLoginVo.account}不存在")
return@transaction null
matchManager == null -> {
log.warn("号:${managerLoginVo.account}不存在")
throw AccountError(managerLoginVo.account)
}
managerLoginVo.password.md5() != matchUser.password -> {
managerLoginVo.password.md5() != matchManager.password -> {
log.warn("密码:${managerLoginVo.password}错误")
return@transaction null
throw PasswordError(id = managerLoginVo.account, password = managerLoginVo.password)
}
else -> {
val token = ManagerToken.new {
manager=matchUser
manager = matchManager
ip = _ip
device = managerLoginVo.device
token=listOf(matchUser.id,ip,device).joinToString(separator = ('a' .. 'z').random().toString()).md5()
token =
listOf(matchManager.id, ip, device).joinToString(separator = ('a'..'z').random().toString())
.md5()
}
token.flush()
return@transaction Token(id = matchUser.id.value,token = token.token,
createTime = token.createTime.toEpochSecond(
ZoneOffset.of("+8")))
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 {
@ -210,7 +286,7 @@ object MainService:AbstractService(){
return@transaction LeaveMessage.all().toList().map {
LeaveMessageDto(
message = "${it.user.name}说:${it.message}",
user = UserVo(studentId = it.user.studentId, name = it.user.name)
user = UserInfoVo(name = it.user.name, headImg = it.user.headImg?.filepath, desc = it.user.desc)
)
}
}
@ -227,20 +303,9 @@ object FileService:AbstractService(){
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}]初始化失败")
}
}
filePath =
this::class.java.classLoader.getResource(uploadDir)?.path ?: throw IllegalArgumentException("初始化资源目录失败")
log.info("上传路径[${filePath}]")
}
private fun save(id: String, path: String, file: File): Int {
@ -291,7 +356,7 @@ object FileService:AbstractService(){
writeBytes(fileBytes)
}
log.info("文件成功保存到${file.absolutePath}")
val fileId= save(id = userId,"${uploadDir}/${fullFileName}",file=file)
val fileId = save(id = userId, "/${uploadDir}/${fullFileName}", file = file)
fileIds.add(fileId)
}
}
@ -302,68 +367,280 @@ object FileService:AbstractService(){
}
}
/**
* 社团服务
*/
object AssociationService : AbstractService() {
class UserIdError(id: Int): IllegalArgumentException("用户id${id}不存在")
/**
* 注册社团
*
* @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
}
class FileIdError(id:Int): IllegalArgumentException("文件id${id}不存在!")
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
}
class HasAssociationError(s: String) : IllegalArgumentException(s)
}
}
}
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}创建成功")
}
}
/**
* 社团服务
* 前台读取社团注册资料
*
*/
object AssociationService: AbstractService() {
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 register(vo:RegAssociationVo):Boolean{
return try {
fun load(vo: SearchAssociationVo): List<AssociationVo> {
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<DisposeRegInfoVo> {
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 {
val user=User.findById(vo.token.id)?:throw UserIdError(vo.token.id)
Association.find { Associations.user eq vo.token.id }.apply {
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{
count()!=0L && first().status ->throw HasAssociationError("您是社团团长不能再创建其他社团")
count()!=0L->throw HasAssociationError("您已经提交过社团注册资料,请耐心等待后台管理员处理")
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("通知老师复审社团注册资料")
}
Association.new {
name=vo.name
desc=vo.desc
logo=ImageFile.findById(vo.fileId) ?: throw FileIdError(vo.fileId)
this.user=user
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="您成功提交了一份社团注册资料,请耐心等待后台受理"
receiverId=vo.token.id
content = "您提交的【${association.name}】社团注册资料初审不通过,可根据初审意见,重新申请\n" +
"【初审意见:${vo.cause},审核人:${association.log.manager?.desc?:""}"
receiverId = matchUser.id.value
receiverClient = ClientType.Foreground.name
}.apply {
this@AssociationService.log.info("通知前台用户审核结果")
}
BackgroundService.createBackgroundNotification(title = "审核注册社团",content = "用户${user.name}提交了一份社团资料需要您进行受理",
duty = Duty.PamphaBhusal)
return@transaction true
}
log.info("未审核社团创建成功")
true
} catch (e: Exception) {
log.error(e.stackTraceToString())
false
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("通知前台用户审核结果")
}
}
}
enum class Duty(val desc:String, val level:Int){
Teacher("老师",1),
PamphaBhusal("总部长",2),
SecretaryOfTheMinister("秘书部部长",3),
PropagandaDepartment("宣传部部长",3),
LiaisonMinister("外联部部长",3),
SecretaryDepartmentOfficer("秘书部干事",4),
PublicityDepartmentOfficer("宣传部干事",4),
LiaisonOfficer("外联部干事",4)
this@AssociationService.log.info("${association.name}】社团注册资料审核完成")
} catch (e: Exception) {
rollback()
throw e
}
}
}
}
/**
@ -380,14 +657,18 @@ object NotificationService:AbstractService(){
fun pull(vo: NotificationDto): List<NotificationVo> {
return transaction {
log.info("通知查询条件[receiverId=${vo.receiverId},receiverClient=${vo.receiverClient.name},pull=false]")
val notifications=Notification.find { Notifications.pull eq false and
val notifications = Notification.find {
Notifications.pull eq false and
(Notifications.receiverId eq vo.receiverId) and
(Notifications.receiverClient eq vo.receiverClient.name) }
(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.toEpochSecond(
ZoneOffset.of("+8")))
NotificationVo(
title = it.title, id = it.id.value, content = it.content,
createTime = it.createTime.format()
)
}
}
}
@ -400,9 +681,11 @@ object NotificationService:AbstractService(){
*/
fun count(vo: NotificationDto): Long {
return transaction {
return@transaction Notification.find{ Notifications.read eq false and
return@transaction Notification.find {
Notifications.read eq false and
(Notifications.receiverId eq vo.receiverId) and
(Notifications.receiverClient eq vo.receiverClient.name) }.count().apply {
(Notifications.receiverClient eq vo.receiverClient.name)
}.count().apply {
log.info("未读通知${this}")
}
}
@ -419,10 +702,14 @@ object NotificationService:AbstractService(){
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.toEpochSecond(
ZoneOffset.of("+8"))*1000)
(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()
)
}
}
}
@ -437,7 +724,6 @@ object NotificationService:AbstractService(){
object BackgroundService : AbstractService() {
override fun init(environment: ApplicationEnvironment) {
super.init(environment)
initManager()
@ -472,7 +758,7 @@ object BackgroundService:AbstractService(){
account = randomNum()
password = originPassword.md5()
this.duty = duty.name
level=duty.level
this.name = duty.desc
}.apply {
managerList.add(InitManagerDto(account = account, originPassword = originPassword, duty = duty))
}
@ -483,8 +769,10 @@ object BackgroundService:AbstractService(){
//初始化管理员
private fun initManager() {
transaction {
val resourcePath =this::class.java.classLoader.getResource("")?.path ?: throw IllegalArgumentException("初始化资源目录失败")
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) {
@ -510,6 +798,10 @@ object BackgroundService:AbstractService(){
}
}
}
} catch (e: Exception) {
log.error(e)
exitProcess(0)
}
}
}
}

@ -8,6 +8,8 @@ import org.jetbrains.exposed.sql.vendors.currentDialect
import java.io.File
import java.math.BigInteger
import java.security.MessageDigest
import java.time.LocalDateTime
import java.time.ZoneOffset
const val ApiPathPrefix="/api"
/**
@ -33,12 +35,39 @@ fun ByteArray.md5():String{
@Target(AnnotationTarget.CLASS,AnnotationTarget.FIELD)
annotation class TableComment(val comment:String)
/**
* 转换成时间戳
*LocalDateTime->Long
* @return
*/
fun LocalDateTime.format(): Long {
return this.toEpochSecond(
ZoneOffset.of("+8")
) * 1000
}
/**
* 根据文件头识别格式
*
* @property format
* @property head
*/
enum class FileFormat(val format:String,val head:String){
JPEG("jpg","FFD8FF")
}
/**
* 获取文件格式
*
* @return
*/
fun ByteArray.getFormat(): FileFormat = format(this) ?: throw IllegalArgumentException("无法识别数据格式")
/**
* 获取文件格式
*
* @return
*/
fun File.getFormat(): FileFormat {
val bytes=readBytes()
return format(bytes) ?: throw IllegalArgumentException("无非识别文件[${absolutePath}]文件的格式")

@ -16,7 +16,82 @@ class Simple {
}
data class UserVo(val studentId:String,val name:String)
/**
* 一般信息
*
*/
abstract class PersonInfoVo{
abstract val name:String
abstract val headImg: String?
abstract val desc: String
}
enum class Duty(val desc:String, val level:Int){
Teacher("老师",1),
PamphaBhusal("总部长",2),
SecretaryOfTheMinister("秘书部部长",3),
PropagandaDepartment("宣传部部长",3),
LiaisonMinister("外联部部长",3),
SecretaryDepartmentOfficer("秘书部干事",4),
PublicityDepartmentOfficer("宣传部干事",4),
LiaisonOfficer("外联部干事",4)
}
/**
* 个人信息
*
*/
abstract class OwnInfoVo :PersonInfoVo(){
abstract val token:Token
}
data class ManagerInfoVo(val duty: Duty,
override val name: String,
override val headImg: String?,
override val desc: String
):PersonInfoVo()
data class UserInfoVo(override val name: String, override val headImg: String?, override val desc: String):PersonInfoVo()
/**
* 管理员个人信息
*
* @property account 管理员账号
* @property name 姓名
* @property duty 职务
* @property headImg 头像
* @property desc 个人简介
*/
data class ManagerVo(
val account: String,
val duty: Duty,
override val token: Token,
override val name: String,
override val headImg: String?,
override val desc: String
) : OwnInfoVo()
data class UserRegVo(val studentId: String, val name: String)
/**
* 用户个人信息
*
* @property studentId 学号
* @property name 姓名
* @property headImg 头像
* @property desc 个人简介
*/
data class UserVo(
val studentId: String,
val manager:ManagerVo?=null,
override val token: Token,
override val name: String,
override val headImg: String?,
override val desc: String,
val associationMemberVo: AssociationMemberVo?
) : OwnInfoVo()
sealed class BaseLoginVo{
abstract val password: String
@ -45,15 +120,24 @@ data class ManagerLoginVo(val account:String,
data class Token(val token:String, val createTime:Long, val id:Int)
data class LeaveMessageVo(val message: String, override val token:Token,
override val clientType: ClientType=ClientType.Foreground):ClientBaseVo()
data class OnlyToken(override val token: Token, override val clientType: ClientType):ClientBaseVo()
data class LeaveMessageDto(val message: String,val user: UserVo)
data class LeaveMessageDto(val message: String,val user: UserInfoVo)
data class RegAssociationVo(val name:String, val desc:String, val fileId:Int, override val token: Token,
override val clientType: ClientType=ClientType.Foreground
/**
* 社团注册资料表单
*
* @property name
* @property desc
* @property fileId
*/
data class AssociationRegVo(val id:Int?, val name: String, val desc: String, val fileId: Int,
override val clientType: ClientType=ClientType.Foreground,
override val token: Token
):ClientBaseVo()
data class ImageFileDto(val filepath:String,val md5:String,val createTime: Long,val url:String)
@ -69,3 +153,152 @@ data class NotificationDto(val receiverId:Int, val receiverClient:ClientType, ov
):ClientBaseVo()
data class NotificationVo(val title:String,val content:String,val id:Int,val createTime: Long)
enum class ApplyStatus{
//已受理
Accept,
//已审核
Check
}
data class SearchAssociationVo(val name:String, val desc:String,
override val clientType: ClientType=ClientType.Foreground, override val token: Token
):ClientBaseVo()
/**
* 社团级别
*
*/
enum class AssociationLevel {
A,
B,
C,
D
}
/**
* 所属院系
*
*/
enum class AssociationFaculty(val desc: String, val range: IntRange) {
ForeignLanguageDept("外语系", 0..0),
CivilEngineeringDept("土木工程", 1..10),
SEM("经理管理学院", 11..20),
MechanicalEngineeringDept("机械工程", 21..30),
TransportationDept("交通运输", 31..40),
ArchitectureAndArts("建筑与艺术", 41..50),
ElectricalDept("电气", 51..60),
MaterialsDept("材料", 61..70),
MessageDept("信息", 71..80),
MathematicsDept("数理", 81..90),
GraduateStudent("研究生", 91..99)
}
abstract class BaseAssociationVo{
abstract val id: Int
abstract val name: String
abstract val desc: String
abstract val logo:String
abstract val faculty: AssociationFaculty
abstract val level: AssociationLevel?
}
/**
* 社团列表
*
*/
class AssociationVo(
override val id: Int,
override val name: String,
override val desc: String,
override val logo: String,
override val faculty: AssociationFaculty,
override val level: AssociationLevel?
) :BaseAssociationVo()
//审核状态
enum class CheckStatus(val desc: String){
WaitFirst("等待初审"),
AcceptFirst("初审受理"),
WaitLast("等待复审"),
AcceptLast("复审受理"),
Finish("审核完成")
}
data class AssociationMemberVo(
val association:AssociationVo,
val isHead:Boolean
)
//用户社团
data class AssociationCheckVo(
override val id: Int,
override val name: String,
override val desc: String,
override val logo: String,
override val faculty: AssociationFaculty,
override val level: AssociationLevel?,
val checkStatus:CheckStatus,
val applyTime: Long,
val firstCause:String,
val lastCause:String?,
val fileId:Int
):BaseAssociationVo()
val facultyRange=IntRange(4,5)
fun User.faculty():AssociationFaculty{
val num=studentId.substring(facultyRange).toInt()
AssociationFaculty.values().forEach {
if(num in it.range){
return it
}
}
throw IllegalArgumentException("无法根据${facultyRange}对应的num[${num}]找到所属院系")
}
/**
* 通用审核记录
*
* @property id
* @property user
* @property applyTime
* @property manager
* @property acceptTime
* @property cause
* @property result
* @property auditTime
* @property nextAudit
*/
data class AuditLoggingVo(val id:Int, val user:UserInfoVo, val applyTime: Long, val manager:ManagerInfoVo?,
val acceptTime:Long?, val cause:String?, val result:Boolean?,
val auditTime:Long?, val nextAudit:AuditLoggingVo?)
/**
* 社团注册资料受理
*
* @property regId
* @property clientType
*/
data class AcceptRegAssociation(val regId:Int,
val isFirstAccept:Boolean,
override val clientType: ClientType=ClientType.Background,
override val token: Token
):ClientBaseVo()
/**
* 社团注册审核记录
*
*/
data class DisposeRegInfoVo(val name:String,val desc:String,val logo:String,val log:AuditLoggingVo)
/**
* 社团注册资料审核
*
* @property regId
* @property result
* @property cause
* @property clientType
*/
data class CheckRegVo(val regId:Int, val result:Boolean, val cause:String,
override val clientType: ClientType=ClientType.Background, override val token: Token
):ClientBaseVo()

@ -10,6 +10,9 @@ import com.google.gson.Gson
import io.ktor.config.*
import io.ktor.http.*
import io.ktor.server.testing.*
import org.jetbrains.exposed.sql.alias
import org.jetbrains.exposed.sql.innerJoin
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction
import java.io.File
import kotlin.test.Test
@ -50,7 +53,7 @@ class ApplicationTest {
put("ktor.deployment.mysql.username", "root")
put("ktor.deployment.mysql.password", "123456")
put("ktor.deployment.leaveMessage.maxSize", "20")
put("ktor.deployment.filePath", "upload")
put("ktor.deployment.filePath", "assets")
}
MySQL(testing = true)
}, test)
@ -59,7 +62,7 @@ class ApplicationTest {
@Test
fun testInsertUser(){
initApp{
val c=AccountService.register(UserVo(studentId = "6666",name = "hahaha"))
val c=AccountService.register(UserRegVo(studentId = "6666",name = "hahaha"))
println(c)
}
}
@ -98,6 +101,30 @@ class ApplicationTest {
println(ClientType.Foreground.name.toLowerCase())
}
@Test
fun test(){
println("12345678".substring(IntRange(4,5)))
}
@Test
fun testMap(){
println(arrayOf(1,2,3).mapNotNull { if(it==2) it*2 else null })
}
@Test
fun testAudit(){
initApp {
transaction {
val nextAudit=AuditLeggings.alias("nextAudit")
Associations.innerJoin(otherTable = AuditLeggings).innerJoin(nextAudit,{AuditLeggings.nextAudit},{nextAudit[AuditLeggings.id]})
.slice(Associations.columns)
.select {
nextAudit[AuditLeggings.result] eq true
}.toList()
}
}
}
/**
* 文档生成
*/

Loading…
Cancel
Save