通知接口对接客户端

master
pan 4 years ago
parent 80468467b0
commit f9a31eed5b
  1. 2
      src/Application.kt
  2. 46
      src/Controller.kt
  3. 37
      src/Dao.kt
  4. 2
      src/MySQL.kt
  5. 90
      src/Service.kt
  6. 13
      src/Vo.kt

@ -39,7 +39,7 @@ fun Application.Controller(testing: Boolean = false){
/** /**
* 初始化log * 初始化log
*/ */
listOf(MainService,AccountService,FileService,AssociationService,BackgroundService).forEach { listOf(MainService,AccountService,FileService,AssociationService,BackgroundService,NotificationService).forEach {
it.init(environment) it.init(environment)
} }
} }

@ -5,7 +5,6 @@ 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 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>()
@ -60,7 +59,7 @@ fun Application.AccountController() {
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<Token>()
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))
} }
@ -108,11 +107,6 @@ fun Application.TestController() {
AccountService.test() AccountService.test()
call.respond(ApiResponse(message = "成功连接服务端", body = true)) call.respond(ApiResponse(message = "成功连接服务端", body = true))
} }
get("$ApiPathPrefix/testR"){
sendNotification(SimpleNotification())
call.respond("建立通知成功")
}
} }
} }
@ -124,7 +118,7 @@ fun Application.AssociationController() {
val multipartData = call.receiveMultipart() val multipartData = call.receiveMultipart()
multipartData.readAllParts().apply { multipartData.readAllParts().apply {
environment.log.info("part size=$size") environment.log.info("part size=$size")
if (size == 3) { if (size == 4) {
val result=FileService.storeFile(this) val result=FileService.storeFile(this)
call.respond(ApiResponse(message = if(result.isNullOrEmpty()) "文件上传失败" else call.respond(ApiResponse(message = if(result.isNullOrEmpty()) "文件上传失败" else
"成功上传${result.size}个文件",body = result)) "成功上传${result.size}个文件",body = result))
@ -186,7 +180,7 @@ data class BackgroundReceiver(val managerId:Int,override val id: Int=managerId,
*/ */
data class ForegroundReceiver(val userId:Int, override val id:Int=userId, override val target: ReceiverType=ReceiverType.Foreground):Receiver() data class ForegroundReceiver(val userId:Int, override val id:Int=userId, override val target: ReceiverType=ReceiverType.Foreground):Receiver()
sealed class NotificationVo{ sealed class BaseNotification{
abstract val type:NotificationType abstract val type:NotificationType
abstract val title:String abstract val title:String
abstract val receiver:Receiver abstract val receiver:Receiver
@ -198,7 +192,7 @@ sealed class NotificationVo{
* @property type * @property type
* @property title * @property title
*/ */
sealed class SysNotificationVo<T>:NotificationVo(){ sealed class SysNotificationVo<T>:BaseNotification(){
override val type: NotificationType=NotificationType.System override val type: NotificationType=NotificationType.System
abstract val content:T abstract val content:T
} }
@ -217,17 +211,35 @@ data class SimpleNotification(
override val type: NotificationType=NotificationType.System, override val type: NotificationType=NotificationType.System,
override val title: String="test", override val title: String="test",
override val receiver: Receiver=BackgroundReceiver(managerId = randomNum().toInt()) override val receiver: Receiver=BackgroundReceiver(managerId = randomNum().toInt())
):NotificationVo() ):BaseNotification()
private val channel = Channel<NotificationVo>()
suspend fun sendNotification(vo:NotificationVo){
channel.send(vo)
}
fun Application.NotificationController(){ fun Application.NotificationController(){
routing { routing {
route("${ApiPathPrefix}/notification") {
post("/pull") {
withToken<NotificationDto>(call) {
val notificationList = NotificationService.pull(vo = it)
call.respond(
ApiResponse(
message = if (notificationList.isEmpty()) "没有最新通知" else "获取${notificationList.size}条最新通知",
body = notificationList
)
)
}
}
post("/count"){
withToken<NotificationDto>(call) {
call.respond(ApiResponse(message = "统计未读通知",body = NotificationService.count(it)))
}
}
post("/list"){
withToken<NotificationDto>(call){
call.respond(ApiResponse(message = "获取通知列表",body=NotificationService.list(it)))
}
}
}
} }
} }

@ -166,12 +166,43 @@ object CheckForms:IntIdTable(){
class CheckForm(id:EntityID<Int>):IntEntity(id){ class CheckForm(id:EntityID<Int>):IntEntity(id){
companion object:IntEntityClass<CheckForm>(CheckForms) companion object:IntEntityClass<CheckForm>(CheckForms)
val type by CheckForms.type var type by CheckForms.type
val manager by Manager referrersOn CheckForms.managerId val manager by Manager referrersOn CheckForms.managerId
val cause by CheckForms.cause var cause by CheckForms.cause
val target by CheckForms.target var target by CheckForms.target
} }
@TableComment("通知记录")
object Notifications:IntIdTable(){
@TableComment("通知标题")
val title:Column<String> = varchar(name="title",length = 10)
@TableComment("通知内容")
val content:Column<String> = varchar(name="content",length = 30)
@TableComment("接收者")
val receiverId:Column<Int> = integer(name="receiver_id")
@TableComment("接收客户端")
val receiverClient:Column<String> = varchar(name="receiver_client",length = 10)
@TableComment("阅读状态")
val read:Column<Boolean> = bool("read").default(false)
@TableComment("拉取状态")
val pull:Column<Boolean> = bool("pull").default(false)
@TableComment("通知创建时间")
val createTime:Column<LocalDateTime> = datetime("create_time").defaultExpression(CurrentDateTime())
}
class Notification(id:EntityID<Int>):IntEntity(id){
companion object:IntEntityClass<Notification>(Notifications)
var title by Notifications.title
var content by Notifications.content
var receiverId by Notifications.receiverId
var receiverClient by Notifications.receiverClient
var read by Notifications.read
var pull by Notifications.pull
var createTime by Notifications.createTime
}

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

@ -2,6 +2,7 @@ package com.gyf.csams
import io.ktor.application.* import io.ktor.application.*
import io.ktor.http.content.* import io.ktor.http.content.*
import org.jetbrains.exposed.sql.and
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.Logger import org.slf4j.Logger
@ -80,7 +81,7 @@ object AccountService:AbstractService() {
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() token.flush()
return@transaction Token(userId = matchUser.id.value,token = token.token, return@transaction Token(id = matchUser.id.value,token = token.token,
createTime = token.createTime.toEpochSecond( createTime = token.createTime.toEpochSecond(
ZoneOffset.of("+8"))) ZoneOffset.of("+8")))
} }
@ -89,20 +90,10 @@ object AccountService:AbstractService() {
} }
fun validToken(token: Token):Boolean{ fun validToken(token: Token):Boolean{
return validToken(TokenVo(token = token.token,userId = token.userId))
}
/**
* 令牌校验
*
* @param tokenVo
* @return
*/
fun validToken(tokenVo: TokenVo):Boolean{
return transaction { return transaction {
!UserToken.find { !UserToken.find {
UserTokens.userId eq tokenVo.userId UserTokens.userId eq token.id
UserTokens.token eq tokenVo.token UserTokens.token eq token.token
}.empty() }.empty()
} }
} }
@ -154,7 +145,7 @@ object MainService:AbstractService(){
} }
log.info("保存留言:${leaveMessageVo.message}") log.info("保存留言:${leaveMessageVo.message}")
LeaveMessage.new { LeaveMessage.new {
user= User.findById(leaveMessageVo.token.userId)?:throw IllegalArgumentException("非法id") user= User.findById(leaveMessageVo.token.id)?:throw IllegalArgumentException("非法id")
message = leaveMessageVo.message message = leaveMessageVo.message
} }
log.info("留言保存成功") log.info("留言保存成功")
@ -236,7 +227,8 @@ object FileService:AbstractService(){
log.info("map=${map}") log.info("map=${map}")
val userId=getPartData<PartData.FormItem>(map,"id").value val userId=getPartData<PartData.FormItem>(map,"id").value
val token= getPartData<PartData.FormItem>(map,"token").value val token= getPartData<PartData.FormItem>(map,"token").value
val tokenVo=TokenVo(token=token,userId=userId.toInt()) val createTime= getPartData<PartData.FormItem>(map,"createTime").value
val tokenVo=Token(token=token,id=userId.toInt(),createTime=createTime.toLong())
if(AccountService.validToken(tokenVo)){ if(AccountService.validToken(tokenVo)){
map.remove("id") map.remove("id")
map.remove("token") map.remove("token")
@ -252,6 +244,7 @@ object FileService:AbstractService(){
val file=File(filePath,fullFileName).apply { val file=File(filePath,fullFileName).apply {
writeBytes(fileBytes) 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) fileIds.add(fileId)
} }
@ -276,6 +269,12 @@ object AssociationService: AbstractService() {
desc=vo.desc desc=vo.desc
logo=ImageFile.findById(vo.fileId) ?: throw IllegalArgumentException("文件id不存在!") logo=ImageFile.findById(vo.fileId) ?: throw IllegalArgumentException("文件id不存在!")
} }
Notification.new {
title="注册社团"
content="您成功提交了一份社团注册资料,请耐心等待后台受理"
receiverId=vo.token.id
receiverClient=ReceiverType.Foreground.name
}
} }
log.info("未审核社团创建成功") log.info("未审核社团创建成功")
true true
@ -297,8 +296,69 @@ enum class ManagerType(val desc:String,val level:Int){
LiaisonOfficer("外联部干事",4) LiaisonOfficer("外联部干事",4)
} }
/**
* 通知服务
*/
object NotificationService:AbstractService(){ object NotificationService:AbstractService(){
/**
* 拉取最新通知
*
* @param vo
* @return
*/
fun pull(vo:NotificationDto):List<NotificationVo>{
return transaction {
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.toEpochSecond(
ZoneOffset.of("+8")))
}
}
}
/**
* 未读通知计数
*
* @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}")
}
}
}
/**
* TODO
*
* @param vo
* @return
*/
fun list(vo:NotificationDto): List<NotificationVo>? {
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.toEpochSecond(
ZoneOffset.of("+8"))*1000)
}
}
}
}
} }
/** /**

@ -36,9 +36,7 @@ data class UserLogoutVo(val userId:Int)
data class UserResDto(val password:String) data class UserResDto(val password:String)
data class Token(val token:String, val createTime:Long, val userId:Int) data class Token(val token:String, val createTime:Long, val id:Int)
data class TokenVo(val token:String,val userId:Int)
sealed class BaseVo{ sealed class BaseVo{
abstract val token:Token abstract val token:Token
@ -57,4 +55,11 @@ data class ImageFileDto(val filepath:String,val md5:String,val createTime: Long,
data class RegAssociationDto(val name:String,val desc:String,val logo:ImageFileDto) data class RegAssociationDto(val name:String,val desc:String,val logo:ImageFileDto)
data class InitManagerDto(val account:String,val originPassword:String) data class InitManagerDto(val account:String,val originPassword:String)
data class PageDto(val currentPage:Long,val pageSize:Int=10)
data class NotificationDto(val receiverId:Int, val receiverClient:ReceiverType, override val token: Token,
val page:PageDto?):BaseVo()
data class NotificationVo(val title:String,val content:String,val id:Int,val createTime: Long)
Loading…
Cancel
Save