优化基础框架
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
@file:OptIn(ExperimentalUuidApi::class)
|
||||
|
||||
package com.bbit.ticket.dao.system
|
||||
|
||||
import com.bbit.ticket.database.system.SysDictItemTable
|
||||
import com.bbit.ticket.database.system.SysDictTypeTable
|
||||
import com.bbit.ticket.entity.system.CreateDictItemRequest
|
||||
import com.bbit.ticket.entity.system.CreateDictTypeRequest
|
||||
import com.bbit.ticket.entity.system.DictItem
|
||||
import com.bbit.ticket.entity.system.DictTypeItem
|
||||
import com.bbit.ticket.entity.system.UpdateDictItemRequest
|
||||
import com.bbit.ticket.entity.system.UpdateDictTypeRequest
|
||||
import com.bbit.ticket.entity.common.BizException
|
||||
import com.bbit.ticket.entity.common.ErrorCode
|
||||
import com.bbit.ticket.entity.common.PageResult
|
||||
import com.bbit.ticket.entity.common.statusLabel
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import org.jetbrains.exposed.v1.core.Op
|
||||
import org.jetbrains.exposed.v1.core.ResultRow
|
||||
import org.jetbrains.exposed.v1.core.and
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.core.isNull
|
||||
import org.jetbrains.exposed.v1.core.like
|
||||
import org.jetbrains.exposed.v1.core.or
|
||||
import org.jetbrains.exposed.v1.jdbc.insert
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
import org.jetbrains.exposed.v1.jdbc.update
|
||||
import java.time.OffsetDateTime
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
object DictDao {
|
||||
fun listTypes(page: Int, pageSize: Int, keyword: String?): PageResult<DictTypeItem> {
|
||||
val where = buildTypeWhere(keyword)
|
||||
val total = SysDictTypeTable.selectAll().where { where }.count()
|
||||
val rows = SysDictTypeTable.selectAll().where { where }
|
||||
.orderBy(SysDictTypeTable.createdAt)
|
||||
.limit(pageSize)
|
||||
.offset(pageOffset(page, pageSize))
|
||||
.toList()
|
||||
return PageResult(
|
||||
items = rows.map { it.toDictTypeItem() },
|
||||
page = page,
|
||||
pageSize = pageSize,
|
||||
total = total,
|
||||
)
|
||||
}
|
||||
|
||||
fun typeCodeExists(code: String): Boolean =
|
||||
SysDictTypeTable.selectAll().where {
|
||||
(SysDictTypeTable.code eq code) and SysDictTypeTable.deletedAt.isNull()
|
||||
}.any()
|
||||
|
||||
fun createType(request: CreateDictTypeRequest): String {
|
||||
val inserted = SysDictTypeTable.insert {
|
||||
it[SysDictTypeTable.code] = request.code.trim()
|
||||
it[SysDictTypeTable.name] = request.name.trim()
|
||||
it[SysDictTypeTable.status] = request.status
|
||||
it[SysDictTypeTable.remark] = request.remark.trimToNull()
|
||||
it[SysDictTypeTable.createdAt] = OffsetDateTime.now()
|
||||
}
|
||||
return inserted[SysDictTypeTable.id].toString()
|
||||
}
|
||||
|
||||
fun updateType(id: Uuid, request: UpdateDictTypeRequest) {
|
||||
SysDictTypeTable.update({ SysDictTypeTable.id eq id }) {
|
||||
it[SysDictTypeTable.name] = request.name.trim()
|
||||
it[SysDictTypeTable.status] = request.status
|
||||
it[SysDictTypeTable.remark] = request.remark.trimToNull()
|
||||
it[SysDictTypeTable.updatedAt] = OffsetDateTime.now()
|
||||
}
|
||||
}
|
||||
|
||||
fun requireType(id: Uuid): ResultRow =
|
||||
SysDictTypeTable.selectAll().where {
|
||||
(SysDictTypeTable.id eq id) and SysDictTypeTable.deletedAt.isNull()
|
||||
}.singleOrNull() ?: throw BizException(
|
||||
ErrorCode.DICT_TYPE_NOT_FOUND.code,
|
||||
ErrorCode.DICT_TYPE_NOT_FOUND.message,
|
||||
HttpStatusCode.NotFound,
|
||||
)
|
||||
|
||||
fun typeHasItems(id: Uuid): Boolean =
|
||||
SysDictItemTable.selectAll().where {
|
||||
(SysDictItemTable.typeId eq id) and SysDictItemTable.deletedAt.isNull()
|
||||
}.any()
|
||||
|
||||
fun softDeleteType(id: Uuid) {
|
||||
SysDictTypeTable.update({ SysDictTypeTable.id eq id }) {
|
||||
it[SysDictTypeTable.deletedAt] = OffsetDateTime.now()
|
||||
}
|
||||
}
|
||||
|
||||
fun listItems(page: Int, pageSize: Int, typeId: Uuid?): PageResult<DictItem> {
|
||||
var where: Op<Boolean> = SysDictItemTable.deletedAt.isNull()
|
||||
if (typeId != null) where = where and (SysDictItemTable.typeId eq typeId)
|
||||
val total = SysDictItemTable.selectAll().where { where }.count()
|
||||
val rows = SysDictItemTable.selectAll().where { where }
|
||||
.orderBy(SysDictItemTable.sort)
|
||||
.limit(pageSize)
|
||||
.offset(pageOffset(page, pageSize))
|
||||
.toList()
|
||||
return PageResult(
|
||||
items = rows.map { it.toDictItem() },
|
||||
page = page,
|
||||
pageSize = pageSize,
|
||||
total = total,
|
||||
)
|
||||
}
|
||||
|
||||
fun createItem(request: CreateDictItemRequest, typeId: Uuid): String {
|
||||
val inserted = SysDictItemTable.insert {
|
||||
it[SysDictItemTable.typeId] = typeId
|
||||
it[SysDictItemTable.label] = request.label.trim()
|
||||
it[SysDictItemTable.value] = request.value.trim()
|
||||
it[SysDictItemTable.color] = request.color.trimToNull()
|
||||
it[SysDictItemTable.sort] = request.sort
|
||||
it[SysDictItemTable.status] = request.status
|
||||
it[SysDictItemTable.remark] = request.remark.trimToNull()
|
||||
it[SysDictItemTable.createdAt] = OffsetDateTime.now()
|
||||
}
|
||||
return inserted[SysDictItemTable.id].toString()
|
||||
}
|
||||
|
||||
fun requireItem(id: Uuid): ResultRow =
|
||||
SysDictItemTable.selectAll().where {
|
||||
(SysDictItemTable.id eq id) and SysDictItemTable.deletedAt.isNull()
|
||||
}.singleOrNull() ?: throw BizException(
|
||||
ErrorCode.DICT_ITEM_NOT_FOUND.code,
|
||||
ErrorCode.DICT_ITEM_NOT_FOUND.message,
|
||||
HttpStatusCode.NotFound,
|
||||
)
|
||||
|
||||
fun updateItem(id: Uuid, request: UpdateDictItemRequest, typeId: Uuid) {
|
||||
SysDictItemTable.update({ SysDictItemTable.id eq id }) {
|
||||
it[SysDictItemTable.typeId] = typeId
|
||||
it[SysDictItemTable.label] = request.label.trim()
|
||||
it[SysDictItemTable.value] = request.value.trim()
|
||||
it[SysDictItemTable.color] = request.color.trimToNull()
|
||||
it[SysDictItemTable.sort] = request.sort
|
||||
it[SysDictItemTable.status] = request.status
|
||||
it[SysDictItemTable.remark] = request.remark.trimToNull()
|
||||
it[SysDictItemTable.updatedAt] = OffsetDateTime.now()
|
||||
}
|
||||
}
|
||||
|
||||
fun softDeleteItem(id: Uuid) {
|
||||
SysDictItemTable.update({ SysDictItemTable.id eq id }) {
|
||||
it[SysDictItemTable.deletedAt] = OffsetDateTime.now()
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildTypeWhere(keyword: String?): Op<Boolean> {
|
||||
var where: Op<Boolean> = SysDictTypeTable.deletedAt.isNull()
|
||||
if (!keyword.isNullOrBlank()) {
|
||||
where = where and ((SysDictTypeTable.code like "%$keyword%") or (SysDictTypeTable.name like "%$keyword%"))
|
||||
}
|
||||
return where
|
||||
}
|
||||
|
||||
private fun ResultRow.toDictTypeItem() = DictTypeItem(
|
||||
id = this[SysDictTypeTable.id].toString(),
|
||||
code = this[SysDictTypeTable.code],
|
||||
name = this[SysDictTypeTable.name],
|
||||
status = this[SysDictTypeTable.status],
|
||||
statusLabel = statusLabel(this[SysDictTypeTable.status]),
|
||||
remark = this[SysDictTypeTable.remark],
|
||||
)
|
||||
|
||||
private fun ResultRow.toDictItem() = DictItem(
|
||||
id = this[SysDictItemTable.id].toString(),
|
||||
typeId = this[SysDictItemTable.typeId].toString(),
|
||||
label = this[SysDictItemTable.label],
|
||||
value = this[SysDictItemTable.value],
|
||||
color = this[SysDictItemTable.color],
|
||||
sort = this[SysDictItemTable.sort],
|
||||
status = this[SysDictItemTable.status],
|
||||
statusLabel = statusLabel(this[SysDictItemTable.status]),
|
||||
remark = this[SysDictItemTable.remark],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
@file:OptIn(ExperimentalUuidApi::class)
|
||||
|
||||
package com.bbit.ticket.dao.system
|
||||
|
||||
import com.bbit.ticket.database.system.SysApiAccessLogTable
|
||||
import com.bbit.ticket.database.system.SysOperationLogTable
|
||||
import com.bbit.ticket.entity.system.ApiAccessLogItem
|
||||
import com.bbit.ticket.entity.system.OperationLogItem
|
||||
import com.bbit.ticket.entity.common.PageResult
|
||||
import com.bbit.ticket.utils.CurrentUser
|
||||
import com.bbit.ticket.utils.formatDateTime
|
||||
import com.bbit.ticket.utils.traceIdOrNull
|
||||
import io.ktor.http.formUrlEncode
|
||||
import io.ktor.server.application.ApplicationCall
|
||||
import io.ktor.server.request.httpMethod
|
||||
import io.ktor.server.request.path
|
||||
import org.jetbrains.exposed.v1.core.Op
|
||||
import org.jetbrains.exposed.v1.core.SortOrder
|
||||
import org.jetbrains.exposed.v1.core.and
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.core.like
|
||||
import org.jetbrains.exposed.v1.core.or
|
||||
import org.jetbrains.exposed.v1.jdbc.insert
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
import java.time.OffsetDateTime
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
|
||||
object LogDao {
|
||||
fun operationLogs(page: Int, pageSize: Int, keyword: String?, status: String?): PageResult<OperationLogItem> {
|
||||
var where: Op<Boolean> = Op.TRUE
|
||||
if (!keyword.isNullOrBlank()) {
|
||||
where = where and ((SysOperationLogTable.username like "%$keyword%") or (SysOperationLogTable.requestPath like "%$keyword%"))
|
||||
}
|
||||
if (!status.isNullOrBlank()) {
|
||||
where = where and (SysOperationLogTable.status eq status)
|
||||
}
|
||||
val total = SysOperationLogTable.selectAll().where { where }.count()
|
||||
val rows = SysOperationLogTable.selectAll().where { where }
|
||||
.orderBy(SysOperationLogTable.createdAt, SortOrder.DESC)
|
||||
.limit(pageSize)
|
||||
.offset(pageOffset(page, pageSize))
|
||||
.toList()
|
||||
return PageResult(
|
||||
items = rows.map { row ->
|
||||
OperationLogItem(
|
||||
id = row[SysOperationLogTable.id].toString(),
|
||||
traceId = row[SysOperationLogTable.traceId],
|
||||
username = row[SysOperationLogTable.username],
|
||||
operationType = row[SysOperationLogTable.operationType],
|
||||
operationName = row[SysOperationLogTable.operationName],
|
||||
httpMethod = row[SysOperationLogTable.httpMethod],
|
||||
requestPath = row[SysOperationLogTable.requestPath],
|
||||
status = row[SysOperationLogTable.status],
|
||||
errorMessage = row[SysOperationLogTable.errorMessage],
|
||||
costMs = row[SysOperationLogTable.costMs],
|
||||
createdAt = formatDateTime(row[SysOperationLogTable.createdAt]) ?: "",
|
||||
)
|
||||
},
|
||||
page = page,
|
||||
pageSize = pageSize,
|
||||
total = total,
|
||||
)
|
||||
}
|
||||
|
||||
fun apiAccessLogs(page: Int, pageSize: Int, keyword: String?, status: String?): PageResult<ApiAccessLogItem> {
|
||||
var where: Op<Boolean> = Op.TRUE
|
||||
if (!keyword.isNullOrBlank()) {
|
||||
where = where and ((SysApiAccessLogTable.appName like "%$keyword%") or (SysApiAccessLogTable.requestPath like "%$keyword%"))
|
||||
}
|
||||
if (!status.isNullOrBlank()) {
|
||||
where = where and (SysApiAccessLogTable.status eq status)
|
||||
}
|
||||
val total = SysApiAccessLogTable.selectAll().where { where }.count()
|
||||
val rows = SysApiAccessLogTable.selectAll().where { where }
|
||||
.orderBy(SysApiAccessLogTable.createdAt, SortOrder.DESC)
|
||||
.limit(pageSize)
|
||||
.offset(pageOffset(page, pageSize))
|
||||
.toList()
|
||||
return PageResult(
|
||||
items = rows.map { row ->
|
||||
ApiAccessLogItem(
|
||||
id = row[SysApiAccessLogTable.id].toString(),
|
||||
traceId = row[SysApiAccessLogTable.traceId],
|
||||
appKey = row[SysApiAccessLogTable.appKey],
|
||||
appName = row[SysApiAccessLogTable.appName],
|
||||
httpMethod = row[SysApiAccessLogTable.httpMethod],
|
||||
requestPath = row[SysApiAccessLogTable.requestPath],
|
||||
responseCode = row[SysApiAccessLogTable.responseCode],
|
||||
status = row[SysApiAccessLogTable.status],
|
||||
errorMessage = row[SysApiAccessLogTable.errorMessage],
|
||||
costMs = row[SysApiAccessLogTable.costMs],
|
||||
createdAt = formatDateTime(row[SysApiAccessLogTable.createdAt]) ?: "",
|
||||
)
|
||||
},
|
||||
page = page,
|
||||
pageSize = pageSize,
|
||||
total = total,
|
||||
)
|
||||
}
|
||||
|
||||
fun saveOperationLog(
|
||||
call: ApplicationCall,
|
||||
currentUser: CurrentUser?,
|
||||
operationType: String,
|
||||
operationName: String,
|
||||
status: String,
|
||||
errorMessage: String?,
|
||||
costMs: Long,
|
||||
) {
|
||||
SysOperationLogTable.insert {
|
||||
it[traceId] = call.traceIdOrNull()
|
||||
it[userId] = currentUser?.id
|
||||
it[SysOperationLogTable.username] = currentUser?.username
|
||||
it[orgId] = currentUser?.orgId
|
||||
it[SysOperationLogTable.operationType] = operationType
|
||||
it[SysOperationLogTable.operationName] = operationName
|
||||
it[httpMethod] = call.request.httpMethod.value
|
||||
it[requestPath] = call.request.path().take(255)
|
||||
it[requestParams] = call.request.queryParameters.formUrlEncode().take(1000)
|
||||
it[ip] = call.request.local.remoteHost.take(64)
|
||||
it[userAgent] = call.request.headers["User-Agent"]?.take(255)
|
||||
it[SysOperationLogTable.status] = status
|
||||
it[SysOperationLogTable.errorMessage] = errorMessage
|
||||
it[SysOperationLogTable.costMs] = costMs
|
||||
it[createdAt] = OffsetDateTime.now()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
@file:OptIn(ExperimentalUuidApi::class)
|
||||
|
||||
package com.bbit.ticket.dao.system
|
||||
|
||||
import com.bbit.ticket.database.system.SysMenuTable
|
||||
import com.bbit.ticket.database.system.SysRoleMenuTable
|
||||
import com.bbit.ticket.entity.system.CreateMenuRequest
|
||||
import com.bbit.ticket.entity.system.MenuFlat
|
||||
import com.bbit.ticket.entity.system.MenuNode
|
||||
import com.bbit.ticket.entity.system.MenuTreeNode
|
||||
import com.bbit.ticket.entity.system.UpdateMenuRequest
|
||||
import com.bbit.ticket.entity.common.BizException
|
||||
import com.bbit.ticket.entity.common.ErrorCode
|
||||
import com.bbit.ticket.entity.common.menuTypeLabel
|
||||
import com.bbit.ticket.entity.common.statusLabel
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import org.jetbrains.exposed.v1.core.ResultRow
|
||||
import org.jetbrains.exposed.v1.core.and
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.core.inList
|
||||
import org.jetbrains.exposed.v1.core.isNull
|
||||
import org.jetbrains.exposed.v1.jdbc.insert
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
import org.jetbrains.exposed.v1.jdbc.update
|
||||
import java.time.OffsetDateTime
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
object MenuDao {
|
||||
fun tree(): List<MenuTreeNode> = buildTree(loadAllFlatMenus())
|
||||
|
||||
fun requireActive(id: Uuid): ResultRow =
|
||||
SysMenuTable.selectAll().where {
|
||||
(SysMenuTable.id eq id) and SysMenuTable.deletedAt.isNull()
|
||||
}.singleOrNull() ?: throw BizException(
|
||||
ErrorCode.MENU_NOT_FOUND.code,
|
||||
ErrorCode.MENU_NOT_FOUND.message,
|
||||
HttpStatusCode.NotFound,
|
||||
)
|
||||
|
||||
fun create(request: CreateMenuRequest, parentId: Uuid?): String {
|
||||
val inserted = SysMenuTable.insert {
|
||||
it[SysMenuTable.parentId] = parentId
|
||||
it[SysMenuTable.type] = request.type
|
||||
it[SysMenuTable.title] = request.title.trim()
|
||||
it[SysMenuTable.name] = request.name.trimToNull()
|
||||
it[SysMenuTable.path] = request.path.trimToNull()
|
||||
it[SysMenuTable.component] = request.component.trimToNull()
|
||||
it[SysMenuTable.icon] = request.icon.trimToNull()
|
||||
it[SysMenuTable.permission] = request.permission.trimToNull()
|
||||
it[SysMenuTable.sort] = request.sort
|
||||
it[SysMenuTable.visible] = request.visible
|
||||
it[SysMenuTable.keepAlive] = request.keepAlive
|
||||
it[SysMenuTable.builtIn] = false
|
||||
it[SysMenuTable.status] = request.status
|
||||
it[SysMenuTable.createdAt] = OffsetDateTime.now()
|
||||
}
|
||||
return inserted[SysMenuTable.id].toString()
|
||||
}
|
||||
|
||||
fun update(id: Uuid, request: UpdateMenuRequest, parentId: Uuid?) {
|
||||
SysMenuTable.update({ SysMenuTable.id eq id }) {
|
||||
it[SysMenuTable.parentId] = parentId
|
||||
it[SysMenuTable.type] = request.type
|
||||
it[SysMenuTable.title] = request.title.trim()
|
||||
it[SysMenuTable.name] = request.name.trimToNull()
|
||||
it[SysMenuTable.path] = request.path.trimToNull()
|
||||
it[SysMenuTable.component] = request.component.trimToNull()
|
||||
it[SysMenuTable.icon] = request.icon.trimToNull()
|
||||
it[SysMenuTable.permission] = request.permission.trimToNull()
|
||||
it[SysMenuTable.sort] = request.sort
|
||||
it[SysMenuTable.visible] = request.visible
|
||||
it[SysMenuTable.keepAlive] = request.keepAlive
|
||||
it[SysMenuTable.status] = request.status
|
||||
it[SysMenuTable.updatedAt] = OffsetDateTime.now()
|
||||
}
|
||||
}
|
||||
|
||||
fun hasChildren(id: Uuid): Boolean =
|
||||
SysMenuTable.selectAll().where {
|
||||
(SysMenuTable.parentId eq id) and SysMenuTable.deletedAt.isNull()
|
||||
}.any()
|
||||
|
||||
fun isReferenced(id: Uuid): Boolean =
|
||||
SysRoleMenuTable.selectAll().where { SysRoleMenuTable.menuId eq id }.any()
|
||||
|
||||
fun softDelete(id: Uuid) {
|
||||
SysMenuTable.update({ SysMenuTable.id eq id }) {
|
||||
it[SysMenuTable.deletedAt] = OffsetDateTime.now()
|
||||
}
|
||||
}
|
||||
|
||||
fun enabledMenusForRoleIds(roleIds: List<Uuid>): List<MenuFlat> {
|
||||
if (roleIds.isEmpty()) return emptyList()
|
||||
val rows = (SysRoleMenuTable innerJoin SysMenuTable)
|
||||
.selectAll()
|
||||
.where {
|
||||
(SysRoleMenuTable.roleId inList roleIds) and
|
||||
SysMenuTable.deletedAt.isNull() and
|
||||
(SysMenuTable.status eq "ENABLED")
|
||||
}
|
||||
.distinct()
|
||||
.toList()
|
||||
return rows.map { it.toMenuFlat() }.sortedWith(compareBy<MenuFlat> { it.sort }.thenBy { it.id.toString() })
|
||||
}
|
||||
|
||||
fun enabledMenusForSuperAdmin(): List<MenuFlat> =
|
||||
SysMenuTable.selectAll()
|
||||
.where { SysMenuTable.deletedAt.isNull() and (SysMenuTable.status eq "ENABLED") }
|
||||
.toList()
|
||||
.map { it.toMenuFlat() }
|
||||
.sortedWith(compareBy<MenuFlat> { it.sort }.thenBy { it.id.toString() })
|
||||
|
||||
fun buildAuthTree(flatMenus: List<MenuFlat>): List<MenuNode> {
|
||||
val parentMap = flatMenus.groupBy { it.parentId }
|
||||
fun build(parentId: Uuid?): List<MenuNode> =
|
||||
(parentMap[parentId] ?: emptyList()).map { menu ->
|
||||
MenuNode(
|
||||
id = menu.id.toString(),
|
||||
parentId = menu.parentId?.toString(),
|
||||
type = menu.type,
|
||||
title = menu.title,
|
||||
name = menu.name,
|
||||
path = menu.path,
|
||||
component = menu.component,
|
||||
icon = menu.icon,
|
||||
permission = menu.permission,
|
||||
sort = menu.sort,
|
||||
visible = menu.visible,
|
||||
keepAlive = menu.keepAlive,
|
||||
children = build(menu.id),
|
||||
)
|
||||
}
|
||||
return build(null)
|
||||
}
|
||||
|
||||
private fun loadAllFlatMenus(): List<MenuFlat> =
|
||||
SysMenuTable.selectAll()
|
||||
.where { SysMenuTable.deletedAt.isNull() }
|
||||
.toList()
|
||||
.map { it.toMenuFlat() }
|
||||
|
||||
private fun buildTree(items: List<MenuFlat>): List<MenuTreeNode> {
|
||||
val grouped = items.groupBy { it.parentId }
|
||||
fun children(parentId: Uuid?): List<MenuTreeNode> =
|
||||
(grouped[parentId] ?: emptyList()).sortedBy { it.sort }.map { menu ->
|
||||
MenuTreeNode(
|
||||
id = menu.id.toString(),
|
||||
parentId = menu.parentId?.toString(),
|
||||
type = menu.type,
|
||||
typeLabel = menuTypeLabel(menu.type),
|
||||
title = menu.title,
|
||||
name = menu.name,
|
||||
path = menu.path,
|
||||
component = menu.component,
|
||||
icon = menu.icon,
|
||||
permission = menu.permission,
|
||||
sort = menu.sort,
|
||||
visible = menu.visible,
|
||||
keepAlive = menu.keepAlive,
|
||||
builtIn = menu.builtIn,
|
||||
status = menu.status,
|
||||
statusLabel = statusLabel(menu.status),
|
||||
children = children(menu.id),
|
||||
)
|
||||
}
|
||||
return children(null)
|
||||
}
|
||||
|
||||
private fun ResultRow.toMenuFlat() = MenuFlat(
|
||||
id = this[SysMenuTable.id],
|
||||
parentId = this[SysMenuTable.parentId],
|
||||
type = this[SysMenuTable.type],
|
||||
title = this[SysMenuTable.title],
|
||||
name = this[SysMenuTable.name],
|
||||
path = this[SysMenuTable.path],
|
||||
component = this[SysMenuTable.component],
|
||||
icon = this[SysMenuTable.icon],
|
||||
permission = this[SysMenuTable.permission],
|
||||
sort = this[SysMenuTable.sort],
|
||||
visible = this[SysMenuTable.visible],
|
||||
keepAlive = this[SysMenuTable.keepAlive],
|
||||
builtIn = this[SysMenuTable.builtIn],
|
||||
status = this[SysMenuTable.status],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
@file:OptIn(ExperimentalUuidApi::class)
|
||||
|
||||
package com.bbit.ticket.dao.system
|
||||
|
||||
import com.bbit.ticket.database.system.SysOrgTable
|
||||
import com.bbit.ticket.database.system.SysUserTable
|
||||
import com.bbit.ticket.entity.system.CreateOrgRequest
|
||||
import com.bbit.ticket.entity.system.OrgTreeNode
|
||||
import com.bbit.ticket.entity.system.UpdateOrgRequest
|
||||
import com.bbit.ticket.entity.common.BizException
|
||||
import com.bbit.ticket.entity.common.ErrorCode
|
||||
import com.bbit.ticket.entity.common.statusLabel
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import org.jetbrains.exposed.v1.core.ResultRow
|
||||
import org.jetbrains.exposed.v1.core.and
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.core.isNull
|
||||
import org.jetbrains.exposed.v1.jdbc.insert
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
import org.jetbrains.exposed.v1.jdbc.update
|
||||
import java.time.OffsetDateTime
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
object OrgDao {
|
||||
fun tree(): List<OrgTreeNode> {
|
||||
val rows = SysOrgTable.selectAll()
|
||||
.where { SysOrgTable.deletedAt.isNull() }
|
||||
.orderBy(SysOrgTable.sort)
|
||||
.toList()
|
||||
val nodes = rows.map(::toFlatNode)
|
||||
return buildTree(nodes)
|
||||
}
|
||||
|
||||
fun codeExists(code: String): Boolean =
|
||||
SysOrgTable.selectAll().where {
|
||||
(SysOrgTable.code eq code) and SysOrgTable.deletedAt.isNull()
|
||||
}.any()
|
||||
|
||||
fun requireActive(id: Uuid): ResultRow =
|
||||
SysOrgTable.selectAll().where {
|
||||
(SysOrgTable.id eq id) and SysOrgTable.deletedAt.isNull()
|
||||
}.singleOrNull() ?: throw BizException(
|
||||
ErrorCode.ORG_NOT_FOUND.code,
|
||||
ErrorCode.ORG_NOT_FOUND.message,
|
||||
HttpStatusCode.NotFound,
|
||||
)
|
||||
|
||||
fun create(request: CreateOrgRequest, parentId: Uuid?): String {
|
||||
val inserted = SysOrgTable.insert {
|
||||
it[SysOrgTable.parentId] = parentId
|
||||
it[SysOrgTable.name] = request.name.trim()
|
||||
it[SysOrgTable.code] = request.code.trim()
|
||||
it[SysOrgTable.sort] = request.sort
|
||||
it[SysOrgTable.status] = request.status
|
||||
it[SysOrgTable.createdAt] = OffsetDateTime.now()
|
||||
}
|
||||
return inserted[SysOrgTable.id].toString()
|
||||
}
|
||||
|
||||
fun update(id: Uuid, request: UpdateOrgRequest, parentId: Uuid?) {
|
||||
SysOrgTable.update({ SysOrgTable.id eq id }) {
|
||||
it[SysOrgTable.parentId] = parentId
|
||||
it[SysOrgTable.name] = request.name.trim()
|
||||
it[SysOrgTable.sort] = request.sort
|
||||
it[SysOrgTable.status] = request.status
|
||||
it[SysOrgTable.updatedAt] = OffsetDateTime.now()
|
||||
}
|
||||
}
|
||||
|
||||
fun hasChildren(id: Uuid): Boolean =
|
||||
SysOrgTable.selectAll().where {
|
||||
(SysOrgTable.parentId eq id) and SysOrgTable.deletedAt.isNull()
|
||||
}.any()
|
||||
|
||||
fun hasUsers(id: Uuid): Boolean =
|
||||
SysUserTable.selectAll().where {
|
||||
(SysUserTable.orgId eq id) and SysUserTable.deletedAt.isNull()
|
||||
}.any()
|
||||
|
||||
fun softDelete(id: Uuid) {
|
||||
SysOrgTable.update({ SysOrgTable.id eq id }) {
|
||||
it[SysOrgTable.deletedAt] = OffsetDateTime.now()
|
||||
}
|
||||
}
|
||||
|
||||
private fun toFlatNode(row: ResultRow) = OrgNodeFlat(
|
||||
id = row[SysOrgTable.id],
|
||||
parentId = row[SysOrgTable.parentId],
|
||||
name = row[SysOrgTable.name],
|
||||
code = row[SysOrgTable.code],
|
||||
sort = row[SysOrgTable.sort],
|
||||
status = row[SysOrgTable.status],
|
||||
)
|
||||
|
||||
private fun buildTree(nodes: List<OrgNodeFlat>): List<OrgTreeNode> {
|
||||
val byParent = nodes.groupBy { it.parentId }
|
||||
fun children(parentId: Uuid?): List<OrgTreeNode> =
|
||||
(byParent[parentId] ?: emptyList()).sortedBy { it.sort }.map { item ->
|
||||
OrgTreeNode(
|
||||
id = item.id.toString(),
|
||||
parentId = item.parentId?.toString(),
|
||||
name = item.name,
|
||||
code = item.code,
|
||||
sort = item.sort,
|
||||
status = item.status,
|
||||
statusLabel = statusLabel(item.status),
|
||||
children = children(item.id),
|
||||
)
|
||||
}
|
||||
return children(null)
|
||||
}
|
||||
|
||||
private data class OrgNodeFlat(
|
||||
val id: Uuid,
|
||||
val parentId: Uuid?,
|
||||
val name: String,
|
||||
val code: String,
|
||||
val sort: Int,
|
||||
val status: String,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package com.bbit.ticket.dao.system
|
||||
|
||||
import com.bbit.ticket.database.system.SysMenuTable
|
||||
import com.bbit.ticket.database.system.SysRoleMenuTable
|
||||
import com.bbit.ticket.database.system.SysRoleTable
|
||||
import com.bbit.ticket.database.system.SysUserRoleTable
|
||||
import com.bbit.ticket.entity.system.CreateRoleRequest
|
||||
import com.bbit.ticket.entity.system.RoleDetail
|
||||
import com.bbit.ticket.entity.system.RoleItem
|
||||
import com.bbit.ticket.entity.system.UpdateRoleRequest
|
||||
import com.bbit.ticket.entity.common.BizException
|
||||
import com.bbit.ticket.entity.common.ErrorCode
|
||||
import com.bbit.ticket.entity.common.PageResult
|
||||
import com.bbit.ticket.entity.common.dataScopeLabel
|
||||
import com.bbit.ticket.entity.common.statusLabel
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import org.jetbrains.exposed.v1.core.Op
|
||||
import org.jetbrains.exposed.v1.core.ResultRow
|
||||
import org.jetbrains.exposed.v1.core.and
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.core.inList
|
||||
import org.jetbrains.exposed.v1.core.isNull
|
||||
import org.jetbrains.exposed.v1.core.like
|
||||
import org.jetbrains.exposed.v1.core.or
|
||||
import org.jetbrains.exposed.v1.jdbc.deleteWhere
|
||||
import org.jetbrains.exposed.v1.jdbc.insert
|
||||
import org.jetbrains.exposed.v1.jdbc.insertIgnore
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
import org.jetbrains.exposed.v1.jdbc.update
|
||||
import java.time.OffsetDateTime
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
object RoleDao {
|
||||
fun list(page: Int, pageSize: Int, keyword: String?, status: String?): PageResult<RoleItem> {
|
||||
val where = buildWhere(keyword, status)
|
||||
val total = SysRoleTable.selectAll().where { where }.count()
|
||||
val rows = SysRoleTable.selectAll().where { where }
|
||||
.orderBy(SysRoleTable.createdAt)
|
||||
.limit(pageSize)
|
||||
.offset(pageOffset(page, pageSize))
|
||||
.toList()
|
||||
return PageResult(
|
||||
items = rows.map { it.toRoleItem() },
|
||||
page = page,
|
||||
pageSize = pageSize,
|
||||
total = total,
|
||||
)
|
||||
}
|
||||
|
||||
fun codeExists(code: String): Boolean =
|
||||
SysRoleTable.selectAll().where {
|
||||
(SysRoleTable.code eq code) and SysRoleTable.deletedAt.isNull()
|
||||
}.any()
|
||||
|
||||
fun create(request: CreateRoleRequest): String {
|
||||
val inserted = SysRoleTable.insert {
|
||||
it[SysRoleTable.name] = request.name.trim()
|
||||
it[SysRoleTable.code] = request.code.trim()
|
||||
it[SysRoleTable.description] = request.description.trimToNull()
|
||||
it[SysRoleTable.status] = request.status
|
||||
it[SysRoleTable.dataScope] = request.dataScope
|
||||
it[SysRoleTable.createdAt] = OffsetDateTime.now()
|
||||
}
|
||||
return inserted[SysRoleTable.id].toString()
|
||||
}
|
||||
|
||||
fun requireActive(id: Uuid): ResultRow =
|
||||
SysRoleTable.selectAll().where {
|
||||
(SysRoleTable.id eq id) and SysRoleTable.deletedAt.isNull()
|
||||
}.singleOrNull() ?: throw BizException(
|
||||
ErrorCode.ROLE_NOT_FOUND.code,
|
||||
ErrorCode.ROLE_NOT_FOUND.message,
|
||||
HttpStatusCode.NotFound,
|
||||
)
|
||||
|
||||
fun detail(id: Uuid): RoleDetail {
|
||||
val role = requireActive(id)
|
||||
val menuIds = SysRoleMenuTable.selectAll().where { SysRoleMenuTable.roleId eq id }
|
||||
.map { it[SysRoleMenuTable.menuId].toString() }
|
||||
return role.toRoleDetail(menuIds)
|
||||
}
|
||||
|
||||
fun update(id: Uuid, request: UpdateRoleRequest) {
|
||||
SysRoleTable.update({ SysRoleTable.id eq id }) {
|
||||
it[SysRoleTable.name] = request.name.trim()
|
||||
it[SysRoleTable.description] = request.description.trimToNull()
|
||||
it[SysRoleTable.status] = request.status
|
||||
it[SysRoleTable.dataScope] = request.dataScope
|
||||
it[SysRoleTable.updatedAt] = OffsetDateTime.now()
|
||||
}
|
||||
}
|
||||
|
||||
fun inUse(id: Uuid): Boolean =
|
||||
SysUserRoleTable.selectAll().where { SysUserRoleTable.roleId eq id }.any()
|
||||
|
||||
fun softDelete(id: Uuid) {
|
||||
SysRoleTable.update({ SysRoleTable.id eq id }) {
|
||||
it[SysRoleTable.deletedAt] = OffsetDateTime.now()
|
||||
}
|
||||
SysRoleMenuTable.deleteWhere { roleId eq id }
|
||||
}
|
||||
|
||||
fun countEnabledMenus(menuIds: List<Uuid>): Long =
|
||||
SysMenuTable.selectAll().where {
|
||||
(SysMenuTable.id inList menuIds) and
|
||||
SysMenuTable.deletedAt.isNull() and
|
||||
(SysMenuTable.status eq "ENABLED")
|
||||
}.count()
|
||||
|
||||
fun replaceMenus(id: Uuid, menuIds: List<Uuid>) {
|
||||
SysRoleMenuTable.deleteWhere { roleId eq id }
|
||||
menuIds.forEach { menuId ->
|
||||
SysRoleMenuTable.insertIgnore {
|
||||
it[roleId] = id
|
||||
it[SysRoleMenuTable.menuId] = menuId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildWhere(keyword: String?, status: String?): Op<Boolean> {
|
||||
var where: Op<Boolean> = SysRoleTable.deletedAt.isNull()
|
||||
if (!keyword.isNullOrBlank()) {
|
||||
where = where and ((SysRoleTable.name like "%$keyword%") or (SysRoleTable.code like "%$keyword%"))
|
||||
}
|
||||
if (!status.isNullOrBlank()) {
|
||||
where = where and (SysRoleTable.status eq status)
|
||||
}
|
||||
return where
|
||||
}
|
||||
|
||||
private fun ResultRow.toRoleItem() = RoleItem(
|
||||
id = this[SysRoleTable.id].toString(),
|
||||
name = this[SysRoleTable.name],
|
||||
code = this[SysRoleTable.code],
|
||||
description = this[SysRoleTable.description],
|
||||
status = this[SysRoleTable.status],
|
||||
statusLabel = statusLabel(this[SysRoleTable.status]),
|
||||
dataScope = this[SysRoleTable.dataScope],
|
||||
dataScopeLabel = dataScopeLabel(this[SysRoleTable.dataScope]),
|
||||
)
|
||||
|
||||
private fun ResultRow.toRoleDetail(menuIds: List<String>) = RoleDetail(
|
||||
id = this[SysRoleTable.id].toString(),
|
||||
name = this[SysRoleTable.name],
|
||||
code = this[SysRoleTable.code],
|
||||
description = this[SysRoleTable.description],
|
||||
status = this[SysRoleTable.status],
|
||||
statusLabel = statusLabel(this[SysRoleTable.status]),
|
||||
dataScope = this[SysRoleTable.dataScope],
|
||||
dataScopeLabel = dataScopeLabel(this[SysRoleTable.dataScope]),
|
||||
menuIds = menuIds,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.bbit.ticket.dao.system
|
||||
|
||||
internal fun pageOffset(page: Int, pageSize: Int): Long = ((page - 1) * pageSize).toLong()
|
||||
|
||||
internal fun String?.trimToNull(): String? = this?.trim()?.takeIf { it.isNotEmpty() }
|
||||
@@ -0,0 +1,237 @@
|
||||
package com.bbit.ticket.dao.system
|
||||
|
||||
import com.bbit.ticket.database.system.SysOrgTable
|
||||
import com.bbit.ticket.database.system.SysRoleTable
|
||||
import com.bbit.ticket.database.system.SysUserRoleTable
|
||||
import com.bbit.ticket.database.system.SysUserTable
|
||||
import com.bbit.ticket.entity.system.CreateUserRequest
|
||||
import com.bbit.ticket.entity.system.UserDetailResponse
|
||||
import com.bbit.ticket.entity.system.UserListItem
|
||||
import com.bbit.ticket.entity.system.UpdateUserRequest
|
||||
import com.bbit.ticket.entity.common.BizException
|
||||
import com.bbit.ticket.entity.common.ErrorCode
|
||||
import com.bbit.ticket.entity.common.PageResult
|
||||
import com.bbit.ticket.entity.common.statusLabel
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import org.jetbrains.exposed.v1.core.Op
|
||||
import org.jetbrains.exposed.v1.core.ResultRow
|
||||
import org.jetbrains.exposed.v1.core.and
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.core.inList
|
||||
import org.jetbrains.exposed.v1.core.isNull
|
||||
import org.jetbrains.exposed.v1.core.like
|
||||
import org.jetbrains.exposed.v1.jdbc.Query
|
||||
import org.jetbrains.exposed.v1.jdbc.deleteWhere
|
||||
import org.jetbrains.exposed.v1.jdbc.insert
|
||||
import org.jetbrains.exposed.v1.jdbc.insertIgnore
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
import org.jetbrains.exposed.v1.jdbc.update
|
||||
import java.time.OffsetDateTime
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
object UserDao {
|
||||
fun list(
|
||||
page: Int,
|
||||
pageSize: Int,
|
||||
username: String?,
|
||||
nickname: String?,
|
||||
status: String?,
|
||||
orgId: Uuid?,
|
||||
): PageResult<UserListItem> {
|
||||
val where = buildWhere(username, nickname, status, orgId)
|
||||
val total = SysUserTable.selectAll().where { where }.count()
|
||||
val rows = SysUserTable.selectAll()
|
||||
.where { where }
|
||||
.orderBy(SysUserTable.createdAt)
|
||||
.limit(pageSize)
|
||||
.offset(pageOffset(page, pageSize))
|
||||
.toList()
|
||||
|
||||
val roleMap = findRoleCodesByUserIds(rows.map { it[SysUserTable.id] })
|
||||
return PageResult(
|
||||
items = rows.map { row -> row.toUserListItem(roleMap[row[SysUserTable.id]] ?: emptyList()) },
|
||||
page = page,
|
||||
pageSize = pageSize,
|
||||
total = total,
|
||||
)
|
||||
}
|
||||
|
||||
fun findByUsername(username: String): ResultRow? =
|
||||
activeUsers().where { SysUserTable.username eq username }.singleOrNull()
|
||||
|
||||
fun requireActive(id: Uuid): ResultRow =
|
||||
activeUsers().where { SysUserTable.id eq id }.singleOrNull()
|
||||
?: throw BizException(
|
||||
ErrorCode.USER_NOT_FOUND.code,
|
||||
ErrorCode.USER_NOT_FOUND.message,
|
||||
HttpStatusCode.NotFound,
|
||||
)
|
||||
|
||||
fun create(request: CreateUserRequest, passwordHash: String, orgId: Uuid?): String {
|
||||
val now = OffsetDateTime.now()
|
||||
val row = SysUserTable.insert {
|
||||
it[SysUserTable.username] = request.username.trim()
|
||||
it[SysUserTable.passwordHash] = passwordHash
|
||||
it[SysUserTable.nickname] = request.nickname.trimToNull()
|
||||
it[SysUserTable.realName] = request.realName.trimToNull()
|
||||
it[SysUserTable.phone] = request.phone.trimToNull()
|
||||
it[SysUserTable.email] = request.email.trimToNull()
|
||||
it[SysUserTable.avatar] = request.avatar.trimToNull()
|
||||
it[SysUserTable.orgId] = orgId
|
||||
it[SysUserTable.status] = request.status
|
||||
it[SysUserTable.tokenVersion] = 1
|
||||
it[SysUserTable.createdAt] = now
|
||||
}
|
||||
return row[SysUserTable.id].toString()
|
||||
}
|
||||
|
||||
fun detail(id: Uuid): UserDetailResponse {
|
||||
val user = requireActive(id)
|
||||
val roleIds = SysUserRoleTable.selectAll()
|
||||
.where { SysUserRoleTable.userId eq id }
|
||||
.map { it[SysUserRoleTable.roleId].toString() }
|
||||
return user.toUserDetail(roleIds)
|
||||
}
|
||||
|
||||
fun updateProfile(id: Uuid, request: UpdateUserRequest, orgId: Uuid?) {
|
||||
SysUserTable.update({ SysUserTable.id eq id }) {
|
||||
it[SysUserTable.nickname] = request.nickname.trimToNull()
|
||||
it[SysUserTable.realName] = request.realName.trimToNull()
|
||||
it[SysUserTable.phone] = request.phone.trimToNull()
|
||||
it[SysUserTable.email] = request.email.trimToNull()
|
||||
it[SysUserTable.avatar] = request.avatar.trimToNull()
|
||||
it[SysUserTable.orgId] = orgId
|
||||
it[SysUserTable.updatedAt] = OffsetDateTime.now()
|
||||
}
|
||||
}
|
||||
|
||||
fun softDelete(id: Uuid) {
|
||||
SysUserTable.update({ SysUserTable.id eq id }) {
|
||||
it[SysUserTable.deletedAt] = OffsetDateTime.now()
|
||||
}
|
||||
SysUserRoleTable.deleteWhere { userId eq id }
|
||||
}
|
||||
|
||||
fun updateStatus(id: Uuid, status: String) {
|
||||
SysUserTable.update({ SysUserTable.id eq id }) {
|
||||
it[SysUserTable.status] = status
|
||||
it[SysUserTable.updatedAt] = OffsetDateTime.now()
|
||||
}
|
||||
}
|
||||
|
||||
fun updatePassword(id: Uuid, passwordHash: String, nextTokenVersion: Int) {
|
||||
SysUserTable.update({ SysUserTable.id eq id }) {
|
||||
it[SysUserTable.passwordHash] = passwordHash
|
||||
it[SysUserTable.tokenVersion] = nextTokenVersion
|
||||
it[SysUserTable.updatedAt] = OffsetDateTime.now()
|
||||
}
|
||||
}
|
||||
|
||||
fun replaceRoles(id: Uuid, roleIds: List<Uuid>) {
|
||||
SysUserRoleTable.deleteWhere { userId eq id }
|
||||
roleIds.forEach { roleId ->
|
||||
SysUserRoleTable.insertIgnore {
|
||||
it[userId] = id
|
||||
it[SysUserRoleTable.roleId] = roleId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun countEnabledRoles(roleIds: List<Uuid>): Long =
|
||||
SysRoleTable.selectAll().where {
|
||||
(SysRoleTable.id inList roleIds) and
|
||||
(SysRoleTable.status eq "ENABLED") and
|
||||
SysRoleTable.deletedAt.isNull()
|
||||
}.count()
|
||||
|
||||
fun orgExists(orgId: Uuid): Boolean =
|
||||
SysOrgTable.selectAll().where {
|
||||
(SysOrgTable.id eq orgId) and SysOrgTable.deletedAt.isNull()
|
||||
}.any()
|
||||
|
||||
fun findEnabledRoleCodes(userId: Uuid): List<String> =
|
||||
(SysUserRoleTable innerJoin SysRoleTable)
|
||||
.selectAll()
|
||||
.where {
|
||||
(SysUserRoleTable.userId eq userId) and
|
||||
SysRoleTable.deletedAt.isNull() and
|
||||
(SysRoleTable.status eq "ENABLED")
|
||||
}
|
||||
.map { it[SysRoleTable.code] }
|
||||
|
||||
fun findEnabledRoleIds(userId: Uuid): List<Uuid> =
|
||||
(SysUserRoleTable innerJoin SysRoleTable)
|
||||
.selectAll()
|
||||
.where {
|
||||
(SysUserRoleTable.userId eq userId) and
|
||||
SysRoleTable.deletedAt.isNull() and
|
||||
(SysRoleTable.status eq "ENABLED")
|
||||
}
|
||||
.map { it[SysRoleTable.id] }
|
||||
|
||||
fun updateLoginInfo(userId: Uuid, loginIp: String?) {
|
||||
SysUserTable.update({ SysUserTable.id eq userId }) {
|
||||
it[SysUserTable.lastLoginAt] = OffsetDateTime.now()
|
||||
it[SysUserTable.lastLoginIp] = loginIp
|
||||
it[SysUserTable.updatedAt] = OffsetDateTime.now()
|
||||
}
|
||||
}
|
||||
|
||||
private fun activeUsers(): Query =
|
||||
SysUserTable.selectAll().where { SysUserTable.deletedAt.isNull() }
|
||||
|
||||
private fun buildWhere(username: String?, nickname: String?, status: String?, orgId: Uuid?): Op<Boolean> {
|
||||
var where: Op<Boolean> = SysUserTable.deletedAt.isNull()
|
||||
if (!username.isNullOrBlank()) {
|
||||
where = where and (SysUserTable.username like "%$username%")
|
||||
}
|
||||
if (!nickname.isNullOrBlank()) {
|
||||
where = where and (SysUserTable.nickname like "%$nickname%")
|
||||
}
|
||||
if (!status.isNullOrBlank()) {
|
||||
where = where and (SysUserTable.status eq status)
|
||||
}
|
||||
if (orgId != null) {
|
||||
where = where and (SysUserTable.orgId eq orgId)
|
||||
}
|
||||
return where
|
||||
}
|
||||
|
||||
private fun findRoleCodesByUserIds(userIds: List<Uuid>): Map<Uuid, List<String>> {
|
||||
if (userIds.isEmpty()) return emptyMap()
|
||||
return (SysUserRoleTable innerJoin SysRoleTable).selectAll()
|
||||
.where {
|
||||
(SysUserRoleTable.userId inList userIds) and
|
||||
SysRoleTable.deletedAt.isNull()
|
||||
}
|
||||
.groupBy { it[SysUserRoleTable.userId] }
|
||||
.mapValues { entry -> entry.value.map { row -> row[SysRoleTable.code] }.distinct() }
|
||||
}
|
||||
|
||||
private fun ResultRow.toUserListItem(roleCodes: List<String>) = UserListItem(
|
||||
id = this[SysUserTable.id].toString(),
|
||||
username = this[SysUserTable.username],
|
||||
nickname = this[SysUserTable.nickname],
|
||||
realName = this[SysUserTable.realName],
|
||||
orgId = this[SysUserTable.orgId]?.toString(),
|
||||
status = this[SysUserTable.status],
|
||||
statusLabel = statusLabel(this[SysUserTable.status]),
|
||||
roleCodes = roleCodes,
|
||||
)
|
||||
|
||||
private fun ResultRow.toUserDetail(roleIds: List<String>) = UserDetailResponse(
|
||||
id = this[SysUserTable.id].toString(),
|
||||
username = this[SysUserTable.username],
|
||||
nickname = this[SysUserTable.nickname],
|
||||
realName = this[SysUserTable.realName],
|
||||
phone = this[SysUserTable.phone],
|
||||
email = this[SysUserTable.email],
|
||||
avatar = this[SysUserTable.avatar],
|
||||
orgId = this[SysUserTable.orgId]?.toString(),
|
||||
status = this[SysUserTable.status],
|
||||
statusLabel = statusLabel(this[SysUserTable.status]),
|
||||
roleIds = roleIds,
|
||||
)
|
||||
}
|
||||
+3
-1
@@ -1,6 +1,9 @@
|
||||
@file:OptIn(ExperimentalUuidApi::class)
|
||||
|
||||
package com.bbit.ticket.entity.system
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
|
||||
@Serializable
|
||||
data class LoginRequest(
|
||||
@@ -48,4 +51,3 @@ data class MenuNode(
|
||||
val keepAlive: Boolean,
|
||||
val children: List<MenuNode> = emptyList(),
|
||||
)
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
package com.bbit.ticket.entity.system
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
package com.bbit.ticket.entity.system
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
package com.bbit.ticket.entity.system
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
package com.bbit.ticket.entity.system
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@@ -2,30 +2,19 @@
|
||||
|
||||
package com.bbit.ticket.service.system
|
||||
|
||||
import com.bbit.ticket.dao.system.MenuDao
|
||||
import com.bbit.ticket.dao.system.UserDao
|
||||
import com.bbit.ticket.database.system.SysUserTable
|
||||
import com.bbit.ticket.entity.common.BizException
|
||||
import com.bbit.ticket.entity.common.ErrorCode
|
||||
import com.bbit.ticket.database.system.SysMenuTable
|
||||
import com.bbit.ticket.database.system.SysRoleMenuTable
|
||||
import com.bbit.ticket.database.system.SysRoleTable
|
||||
import com.bbit.ticket.database.system.SysUserRoleTable
|
||||
import com.bbit.ticket.database.system.SysUserTable
|
||||
import com.bbit.ticket.entity.system.CurrentUserProfile
|
||||
import com.bbit.ticket.entity.system.LoginRequest
|
||||
import com.bbit.ticket.entity.system.LoginResponse
|
||||
import com.bbit.ticket.entity.system.MeResponse
|
||||
import com.bbit.ticket.entity.system.MenuNode
|
||||
import com.bbit.ticket.plugins.dbQuery
|
||||
import com.bbit.ticket.utils.CurrentUser
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import org.jetbrains.exposed.v1.core.and
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.core.inList
|
||||
import org.jetbrains.exposed.v1.core.isNull
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
import org.jetbrains.exposed.v1.jdbc.update
|
||||
import java.time.OffsetDateTime
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
object AuthService {
|
||||
suspend fun login(request: LoginRequest, loginIp: String?): LoginResponse {
|
||||
@@ -34,17 +23,13 @@ object AuthService {
|
||||
throw BizException(ErrorCode.BAD_REQUEST.code, "用户名和密码不能为空", HttpStatusCode.BadRequest)
|
||||
}
|
||||
|
||||
val user = dbQuery {
|
||||
SysUserTable.selectAll()
|
||||
.where { (SysUserTable.username eq username) and SysUserTable.deletedAt.isNull() }
|
||||
.singleOrNull()
|
||||
} ?: throw BizException(
|
||||
val user = dbQuery { UserDao.findByUsername(username) } ?: throw BizException(
|
||||
ErrorCode.USERNAME_OR_PASSWORD_INVALID.code,
|
||||
ErrorCode.USERNAME_OR_PASSWORD_INVALID.message,
|
||||
HttpStatusCode.BadRequest,
|
||||
)
|
||||
|
||||
if (!PasswordService.matches(request.password, user[SysUserTable.passwordHash])) {
|
||||
if (!PasswordService.matches(request.password, user[com.bbit.ticket.database.system.SysUserTable.passwordHash])) {
|
||||
throw BizException(
|
||||
ErrorCode.USERNAME_OR_PASSWORD_INVALID.code,
|
||||
ErrorCode.USERNAME_OR_PASSWORD_INVALID.message,
|
||||
@@ -52,50 +37,31 @@ object AuthService {
|
||||
)
|
||||
}
|
||||
|
||||
if (user[SysUserTable.status] != "ENABLED") {
|
||||
if (user[com.bbit.ticket.database.system.SysUserTable.status] != "ENABLED") {
|
||||
throw BizException(ErrorCode.USER_DISABLED.code, ErrorCode.USER_DISABLED.message, HttpStatusCode.BadRequest)
|
||||
}
|
||||
|
||||
val userId = user[SysUserTable.id]
|
||||
val roleCodes = dbQuery {
|
||||
(SysUserRoleTable innerJoin SysRoleTable)
|
||||
.selectAll()
|
||||
.where {
|
||||
(SysUserRoleTable.userId eq userId) and
|
||||
SysRoleTable.deletedAt.isNull() and
|
||||
(SysRoleTable.status eq "ENABLED")
|
||||
}
|
||||
.map { it[SysRoleTable.code] }
|
||||
}
|
||||
val userId = user[com.bbit.ticket.database.system.SysUserTable.id]
|
||||
val roleCodes = dbQuery { UserDao.findEnabledRoleCodes(userId) }
|
||||
|
||||
val (accessToken, expiresIn) = JwtService.issueAccessToken(
|
||||
userId = userId.toString(),
|
||||
username = user[SysUserTable.username],
|
||||
orgId = user[SysUserTable.orgId]?.toString(),
|
||||
username = user[com.bbit.ticket.database.system.SysUserTable.username],
|
||||
orgId = user[com.bbit.ticket.database.system.SysUserTable.orgId]?.toString(),
|
||||
roles = roleCodes,
|
||||
tokenVersion = user[SysUserTable.tokenVersion],
|
||||
tokenVersion = user[com.bbit.ticket.database.system.SysUserTable.tokenVersion],
|
||||
)
|
||||
|
||||
dbQuery {
|
||||
SysUserTable.update({ SysUserTable.id eq userId }) {
|
||||
it[lastLoginAt] = OffsetDateTime.now()
|
||||
it[lastLoginIp] = loginIp
|
||||
it[updatedAt] = OffsetDateTime.now()
|
||||
}
|
||||
}
|
||||
dbQuery { UserDao.updateLoginInfo(userId, loginIp) }
|
||||
|
||||
return LoginResponse(accessToken = accessToken, expiresIn = expiresIn)
|
||||
}
|
||||
|
||||
suspend fun me(currentUser: CurrentUser): MeResponse {
|
||||
val userRow = dbQuery {
|
||||
SysUserTable.selectAll()
|
||||
.where { (SysUserTable.id eq currentUser.id) and SysUserTable.deletedAt.isNull() }
|
||||
.single()
|
||||
}
|
||||
val userRow = dbQuery { UserDao.requireActive(currentUser.id) }
|
||||
|
||||
val allMenus = loadMenusForUser(currentUser)
|
||||
val menuTree = buildMenuTree(allMenus)
|
||||
val menuTree = MenuDao.buildAuthTree(allMenus)
|
||||
val permissions = allMenus.mapNotNull { it.permission }.toSet()
|
||||
|
||||
return MeResponse(
|
||||
@@ -112,97 +78,11 @@ object AuthService {
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun loadMenusForUser(currentUser: CurrentUser): List<MenuFlat> {
|
||||
val rows = if (currentUser.isSuperAdmin) {
|
||||
dbQuery {
|
||||
SysMenuTable.selectAll()
|
||||
.where { SysMenuTable.deletedAt.isNull() and (SysMenuTable.status eq "ENABLED") }
|
||||
.toList()
|
||||
}
|
||||
private suspend fun loadMenusForUser(currentUser: CurrentUser) = dbQuery {
|
||||
if (currentUser.isSuperAdmin) {
|
||||
MenuDao.enabledMenusForSuperAdmin()
|
||||
} else {
|
||||
val roleIds = dbQuery {
|
||||
(SysUserRoleTable innerJoin SysRoleTable)
|
||||
.selectAll()
|
||||
.where {
|
||||
(SysUserRoleTable.userId eq currentUser.id) and
|
||||
SysRoleTable.deletedAt.isNull() and
|
||||
(SysRoleTable.status eq "ENABLED")
|
||||
}
|
||||
.map { it[SysRoleTable.id] }
|
||||
}
|
||||
|
||||
if (roleIds.isEmpty()) {
|
||||
emptyList()
|
||||
} else {
|
||||
dbQuery {
|
||||
(SysRoleMenuTable innerJoin SysMenuTable)
|
||||
.selectAll()
|
||||
.where {
|
||||
(SysRoleMenuTable.roleId inList roleIds) and
|
||||
SysMenuTable.deletedAt.isNull() and
|
||||
(SysMenuTable.status eq "ENABLED")
|
||||
}
|
||||
.distinct()
|
||||
.toList()
|
||||
}
|
||||
}
|
||||
MenuDao.enabledMenusForRoleIds(UserDao.findEnabledRoleIds(currentUser.id))
|
||||
}
|
||||
|
||||
return rows.map { row ->
|
||||
MenuFlat(
|
||||
id = row[SysMenuTable.id],
|
||||
parentId = row[SysMenuTable.parentId],
|
||||
type = row[SysMenuTable.type],
|
||||
title = row[SysMenuTable.title],
|
||||
name = row[SysMenuTable.name],
|
||||
path = row[SysMenuTable.path],
|
||||
component = row[SysMenuTable.component],
|
||||
icon = row[SysMenuTable.icon],
|
||||
permission = row[SysMenuTable.permission],
|
||||
sort = row[SysMenuTable.sort],
|
||||
visible = row[SysMenuTable.visible],
|
||||
keepAlive = row[SysMenuTable.keepAlive],
|
||||
)
|
||||
}.sortedWith(compareBy<MenuFlat> { it.sort }.thenBy { it.id.toString() })
|
||||
}
|
||||
|
||||
private fun buildMenuTree(flatMenus: List<MenuFlat>): List<MenuNode> {
|
||||
val parentMap = flatMenus.groupBy { it.parentId }
|
||||
|
||||
fun build(parentId: Uuid?): List<MenuNode> =
|
||||
(parentMap[parentId] ?: emptyList()).map { menu ->
|
||||
MenuNode(
|
||||
id = menu.id.toString(),
|
||||
parentId = menu.parentId?.toString(),
|
||||
type = menu.type,
|
||||
title = menu.title,
|
||||
name = menu.name,
|
||||
path = menu.path,
|
||||
component = menu.component,
|
||||
icon = menu.icon,
|
||||
permission = menu.permission,
|
||||
sort = menu.sort,
|
||||
visible = menu.visible,
|
||||
keepAlive = menu.keepAlive,
|
||||
children = build(menu.id),
|
||||
)
|
||||
}
|
||||
|
||||
return build(null)
|
||||
}
|
||||
private data class MenuFlat(
|
||||
val id: Uuid,
|
||||
val parentId: Uuid?,
|
||||
val type: String,
|
||||
val title: String,
|
||||
val name: String?,
|
||||
val path: String?,
|
||||
val component: String?,
|
||||
val icon: String?,
|
||||
val permission: String?,
|
||||
val sort: Int,
|
||||
val visible: Boolean,
|
||||
val keepAlive: Boolean,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
|
||||
package com.bbit.ticket.service.system
|
||||
|
||||
import com.bbit.ticket.database.system.SysDictItemTable
|
||||
import com.bbit.ticket.database.system.SysDictItemTable.color
|
||||
import com.bbit.ticket.database.system.SysDictItemTable.label
|
||||
import com.bbit.ticket.database.system.SysDictTypeTable
|
||||
import com.bbit.ticket.dao.system.DictDao
|
||||
import com.bbit.ticket.entity.system.CreateDictItemRequest
|
||||
import com.bbit.ticket.entity.system.CreateDictTypeRequest
|
||||
import com.bbit.ticket.entity.system.DictItem
|
||||
@@ -15,172 +12,54 @@ import com.bbit.ticket.entity.system.UpdateDictTypeRequest
|
||||
import com.bbit.ticket.entity.common.BizException
|
||||
import com.bbit.ticket.entity.common.ErrorCode
|
||||
import com.bbit.ticket.entity.common.PageResult
|
||||
import com.bbit.ticket.entity.common.statusLabel
|
||||
import com.bbit.ticket.plugins.dbQuery
|
||||
import com.bbit.ticket.utils.parseUuid
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import org.jetbrains.exposed.v1.core.ResultRow
|
||||
import org.jetbrains.exposed.v1.core.and
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.core.isNull
|
||||
import org.jetbrains.exposed.v1.core.like
|
||||
import org.jetbrains.exposed.v1.core.or
|
||||
import org.jetbrains.exposed.v1.jdbc.insert
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
import org.jetbrains.exposed.v1.jdbc.update
|
||||
import java.time.OffsetDateTime
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
object DictService {
|
||||
suspend fun listTypes(page: Int, pageSize: Int, keyword: String?): PageResult<DictTypeItem> = dbQuery {
|
||||
var where = SysDictTypeTable.deletedAt.isNull()
|
||||
if (!keyword.isNullOrBlank()) {
|
||||
where = where and ((SysDictTypeTable.code like "%$keyword%") or (SysDictTypeTable.name like "%$keyword%"))
|
||||
}
|
||||
val total = SysDictTypeTable.selectAll().where { where }.count()
|
||||
val rows = SysDictTypeTable.selectAll().where { where }
|
||||
.orderBy(SysDictTypeTable.createdAt)
|
||||
.limit(pageSize)
|
||||
.offset(((page - 1) * pageSize).toLong())
|
||||
.toList()
|
||||
PageResult(
|
||||
items = rows.map {
|
||||
DictTypeItem(
|
||||
id = it[SysDictTypeTable.id].toString(),
|
||||
code = it[SysDictTypeTable.code],
|
||||
name = it[SysDictTypeTable.name],
|
||||
status = it[SysDictTypeTable.status],
|
||||
statusLabel = statusLabel(it[SysDictTypeTable.status]),
|
||||
remark = it[SysDictTypeTable.remark],
|
||||
)
|
||||
},
|
||||
page = page,
|
||||
pageSize = pageSize,
|
||||
total = total,
|
||||
)
|
||||
}
|
||||
suspend fun listTypes(page: Int, pageSize: Int, keyword: String?): PageResult<DictTypeItem> =
|
||||
dbQuery { DictDao.listTypes(page, pageSize, keyword) }
|
||||
|
||||
suspend fun createType(request: CreateDictTypeRequest): String = dbQuery {
|
||||
if (request.code.trim().isBlank() || request.name.trim().isBlank()) {
|
||||
throw BizException(ErrorCode.BAD_REQUEST.code, "字典类型编码和名称不能为空")
|
||||
}
|
||||
val exists = SysDictTypeTable.selectAll().where {
|
||||
(SysDictTypeTable.code eq request.code.trim()) and SysDictTypeTable.deletedAt.isNull()
|
||||
}.any()
|
||||
if (exists) throw BizException(ErrorCode.DATA_CONFLICT.code, "字典类型编码已存在")
|
||||
val inserted = SysDictTypeTable.insert {
|
||||
it[code] = request.code.trim()
|
||||
it[name] = request.name.trim()
|
||||
it[status] = request.status
|
||||
it[remark] = request.remark?.trim()
|
||||
it[createdAt] = OffsetDateTime.now()
|
||||
if (DictDao.typeCodeExists(request.code.trim())) {
|
||||
throw BizException(ErrorCode.DATA_CONFLICT.code, "字典类型编码已存在")
|
||||
}
|
||||
inserted[SysDictTypeTable.id].toString()
|
||||
DictDao.createType(request)
|
||||
}
|
||||
|
||||
suspend fun updateType(id: Uuid, request: UpdateDictTypeRequest) = dbQuery {
|
||||
requireType(id)
|
||||
SysDictTypeTable.update({ SysDictTypeTable.id eq id }) {
|
||||
it[name] = request.name.trim()
|
||||
it[status] = request.status
|
||||
it[remark] = request.remark?.trim()
|
||||
it[updatedAt] = OffsetDateTime.now()
|
||||
}
|
||||
DictDao.requireType(id)
|
||||
DictDao.updateType(id, request)
|
||||
}
|
||||
|
||||
suspend fun deleteType(id: Uuid) = dbQuery {
|
||||
requireType(id)
|
||||
val hasItems = SysDictItemTable.selectAll().where {
|
||||
(SysDictItemTable.typeId eq id) and SysDictItemTable.deletedAt.isNull()
|
||||
}.any()
|
||||
if (hasItems) throw BizException(ErrorCode.BAD_REQUEST.code, "字典类型下存在字典项,不能删除")
|
||||
SysDictTypeTable.update({ SysDictTypeTable.id eq id }) {
|
||||
it[deletedAt] = OffsetDateTime.now()
|
||||
}
|
||||
DictDao.requireType(id)
|
||||
if (DictDao.typeHasItems(id)) throw BizException(ErrorCode.BAD_REQUEST.code, "字典类型下存在字典项,不能删除")
|
||||
DictDao.softDeleteType(id)
|
||||
}
|
||||
|
||||
suspend fun listItems(page: Int, pageSize: Int, typeId: Uuid?): PageResult<DictItem> = dbQuery {
|
||||
var where = SysDictItemTable.deletedAt.isNull()
|
||||
if (typeId != null) where = where and (SysDictItemTable.typeId eq typeId)
|
||||
val total = SysDictItemTable.selectAll().where { where }.count()
|
||||
val rows = SysDictItemTable.selectAll().where { where }
|
||||
.orderBy(SysDictItemTable.sort)
|
||||
.limit(pageSize)
|
||||
.offset(((page - 1) * pageSize).toLong())
|
||||
.toList()
|
||||
PageResult(
|
||||
items = rows.map {
|
||||
DictItem(
|
||||
id = it[SysDictItemTable.id].toString(),
|
||||
typeId = it[SysDictItemTable.typeId].toString(),
|
||||
label = it[label],
|
||||
value = it[SysDictItemTable.value],
|
||||
color = it[color],
|
||||
sort = it[SysDictItemTable.sort],
|
||||
status = it[SysDictItemTable.status],
|
||||
statusLabel = statusLabel(it[SysDictItemTable.status]),
|
||||
remark = it[SysDictItemTable.remark],
|
||||
)
|
||||
},
|
||||
page = page,
|
||||
pageSize = pageSize,
|
||||
total = total,
|
||||
)
|
||||
}
|
||||
suspend fun listItems(page: Int, pageSize: Int, typeId: Uuid?): PageResult<DictItem> =
|
||||
dbQuery { DictDao.listItems(page, pageSize, typeId) }
|
||||
|
||||
suspend fun createItem(request: CreateDictItemRequest): String = dbQuery {
|
||||
val typeId = parseUuid(request.typeId, "typeId")
|
||||
requireType(typeId)
|
||||
val inserted = SysDictItemTable.insert {
|
||||
it[SysDictItemTable.typeId] = typeId
|
||||
it[label] = request.label.trim()
|
||||
it[value] = request.value.trim()
|
||||
it[color] = request.color?.trim()
|
||||
it[sort] = request.sort
|
||||
it[status] = request.status
|
||||
it[remark] = request.remark?.trim()
|
||||
it[createdAt] = OffsetDateTime.now()
|
||||
}
|
||||
inserted[SysDictItemTable.id].toString()
|
||||
DictDao.requireType(typeId)
|
||||
DictDao.createItem(request, typeId)
|
||||
}
|
||||
|
||||
suspend fun updateItem(id: Uuid, request: UpdateDictItemRequest) = dbQuery {
|
||||
requireItem(id)
|
||||
DictDao.requireItem(id)
|
||||
val typeId = parseUuid(request.typeId, "typeId")
|
||||
requireType(typeId)
|
||||
SysDictItemTable.update({ SysDictItemTable.id eq id }) {
|
||||
it[SysDictItemTable.typeId] = typeId
|
||||
it[label] = request.label.trim()
|
||||
it[value] = request.value.trim()
|
||||
it[color] = request.color?.trim()
|
||||
it[sort] = request.sort
|
||||
it[status] = request.status
|
||||
it[remark] = request.remark?.trim()
|
||||
it[updatedAt] = OffsetDateTime.now()
|
||||
}
|
||||
DictDao.requireType(typeId)
|
||||
DictDao.updateItem(id, request, typeId)
|
||||
}
|
||||
|
||||
suspend fun deleteItem(id: Uuid) = dbQuery {
|
||||
requireItem(id)
|
||||
SysDictItemTable.update({ SysDictItemTable.id eq id }) {
|
||||
it[deletedAt] = OffsetDateTime.now()
|
||||
}
|
||||
DictDao.requireItem(id)
|
||||
DictDao.softDeleteItem(id)
|
||||
}
|
||||
|
||||
private fun requireType(id: Uuid): ResultRow =
|
||||
SysDictTypeTable.selectAll().where { (SysDictTypeTable.id eq id) and SysDictTypeTable.deletedAt.isNull() }.singleOrNull()
|
||||
?: throw BizException(
|
||||
ErrorCode.DICT_TYPE_NOT_FOUND.code,
|
||||
ErrorCode.DICT_TYPE_NOT_FOUND.message,
|
||||
HttpStatusCode.NotFound
|
||||
)
|
||||
|
||||
private fun requireItem(id: Uuid): ResultRow =
|
||||
SysDictItemTable.selectAll().where { (SysDictItemTable.id eq id) and SysDictItemTable.deletedAt.isNull() }.singleOrNull()
|
||||
?: throw BizException(
|
||||
ErrorCode.DICT_ITEM_NOT_FOUND.code,
|
||||
ErrorCode.DICT_ITEM_NOT_FOUND.message,
|
||||
HttpStatusCode.NotFound
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,88 +2,17 @@
|
||||
|
||||
package com.bbit.ticket.service.system
|
||||
|
||||
import com.bbit.ticket.database.system.SysApiAccessLogTable
|
||||
import com.bbit.ticket.database.system.SysOperationLogTable
|
||||
import com.bbit.ticket.dao.system.LogDao
|
||||
import com.bbit.ticket.entity.system.ApiAccessLogItem
|
||||
import com.bbit.ticket.entity.system.OperationLogItem
|
||||
import com.bbit.ticket.entity.common.PageResult
|
||||
import com.bbit.ticket.utils.formatDateTime
|
||||
import com.bbit.ticket.plugins.dbQuery
|
||||
import org.jetbrains.exposed.v1.core.Op
|
||||
import org.jetbrains.exposed.v1.core.SortOrder
|
||||
import org.jetbrains.exposed.v1.core.and
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.core.like
|
||||
import org.jetbrains.exposed.v1.core.or
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
|
||||
object LogsQueryService {
|
||||
suspend fun operationLogs(page: Int, pageSize: Int, keyword: String?, status: String?): PageResult<OperationLogItem> = dbQuery {
|
||||
var where: Op<Boolean> = Op.TRUE
|
||||
if (!keyword.isNullOrBlank()) {
|
||||
where = where and ((SysOperationLogTable.username like "%$keyword%") or (SysOperationLogTable.requestPath like "%$keyword%"))
|
||||
}
|
||||
if (!status.isNullOrBlank()) where = where and (SysOperationLogTable.status eq status)
|
||||
val total = SysOperationLogTable.selectAll().where { where }.count()
|
||||
val rows = SysOperationLogTable.selectAll().where { where }
|
||||
.orderBy(SysOperationLogTable.createdAt, SortOrder.DESC)
|
||||
.limit(pageSize)
|
||||
.offset(((page - 1) * pageSize).toLong())
|
||||
.toList()
|
||||
PageResult(
|
||||
items = rows.map {
|
||||
OperationLogItem(
|
||||
id = it[SysOperationLogTable.id].toString(),
|
||||
traceId = it[SysOperationLogTable.traceId],
|
||||
username = it[SysOperationLogTable.username],
|
||||
operationType = it[SysOperationLogTable.operationType],
|
||||
operationName = it[SysOperationLogTable.operationName],
|
||||
httpMethod = it[SysOperationLogTable.httpMethod],
|
||||
requestPath = it[SysOperationLogTable.requestPath],
|
||||
status = it[SysOperationLogTable.status],
|
||||
errorMessage = it[SysOperationLogTable.errorMessage],
|
||||
costMs = it[SysOperationLogTable.costMs],
|
||||
createdAt = formatDateTime(it[SysOperationLogTable.createdAt]) ?: "",
|
||||
)
|
||||
},
|
||||
page = page,
|
||||
pageSize = pageSize,
|
||||
total = total,
|
||||
)
|
||||
}
|
||||
suspend fun operationLogs(page: Int, pageSize: Int, keyword: String?, status: String?): PageResult<OperationLogItem> =
|
||||
dbQuery { LogDao.operationLogs(page, pageSize, keyword, status) }
|
||||
|
||||
suspend fun apiAccessLogs(page: Int, pageSize: Int, keyword: String?, status: String?): PageResult<ApiAccessLogItem> = dbQuery {
|
||||
var where: Op<Boolean> = Op.TRUE
|
||||
if (!keyword.isNullOrBlank()) {
|
||||
where = where and ((SysApiAccessLogTable.appName like "%$keyword%") or (SysApiAccessLogTable.requestPath like "%$keyword%"))
|
||||
}
|
||||
if (!status.isNullOrBlank()) where = where and (SysApiAccessLogTable.status eq status)
|
||||
val total = SysApiAccessLogTable.selectAll().where { where }.count()
|
||||
val rows = SysApiAccessLogTable.selectAll().where { where }
|
||||
.orderBy(SysApiAccessLogTable.createdAt, SortOrder.DESC)
|
||||
.limit(pageSize)
|
||||
.offset(((page - 1) * pageSize).toLong())
|
||||
.toList()
|
||||
PageResult(
|
||||
items = rows.map {
|
||||
ApiAccessLogItem(
|
||||
id = it[SysApiAccessLogTable.id].toString(),
|
||||
traceId = it[SysApiAccessLogTable.traceId],
|
||||
appKey = it[SysApiAccessLogTable.appKey],
|
||||
appName = it[SysApiAccessLogTable.appName],
|
||||
httpMethod = it[SysApiAccessLogTable.httpMethod],
|
||||
requestPath = it[SysApiAccessLogTable.requestPath],
|
||||
responseCode = it[SysApiAccessLogTable.responseCode],
|
||||
status = it[SysApiAccessLogTable.status],
|
||||
errorMessage = it[SysApiAccessLogTable.errorMessage],
|
||||
costMs = it[SysApiAccessLogTable.costMs],
|
||||
createdAt = formatDateTime(it[SysApiAccessLogTable.createdAt]) ?: "",
|
||||
)
|
||||
},
|
||||
page = page,
|
||||
pageSize = pageSize,
|
||||
total = total,
|
||||
)
|
||||
}
|
||||
}
|
||||
suspend fun apiAccessLogs(page: Int, pageSize: Int, keyword: String?, status: String?): PageResult<ApiAccessLogItem> =
|
||||
dbQuery { LogDao.apiAccessLogs(page, pageSize, keyword, status) }
|
||||
}
|
||||
|
||||
@@ -2,160 +2,49 @@
|
||||
|
||||
package com.bbit.ticket.service.system
|
||||
|
||||
import com.bbit.ticket.database.system.SysMenuTable
|
||||
import com.bbit.ticket.database.system.SysMenuTable.builtIn
|
||||
import com.bbit.ticket.database.system.SysMenuTable.component
|
||||
import com.bbit.ticket.database.system.SysMenuTable.icon
|
||||
import com.bbit.ticket.database.system.SysMenuTable.keepAlive
|
||||
import com.bbit.ticket.database.system.SysMenuTable.path
|
||||
import com.bbit.ticket.database.system.SysMenuTable.permission
|
||||
import com.bbit.ticket.database.system.SysMenuTable.visible
|
||||
import com.bbit.ticket.database.system.SysRoleMenuTable
|
||||
import com.bbit.ticket.dao.system.MenuDao
|
||||
import com.bbit.ticket.entity.system.CreateMenuRequest
|
||||
import com.bbit.ticket.entity.system.MenuFlat
|
||||
import com.bbit.ticket.entity.system.MenuTreeNode
|
||||
import com.bbit.ticket.entity.system.UpdateMenuRequest
|
||||
import com.bbit.ticket.entity.common.BizException
|
||||
import com.bbit.ticket.entity.common.ErrorCode
|
||||
import com.bbit.ticket.entity.common.menuTypeLabel
|
||||
import com.bbit.ticket.entity.common.statusLabel
|
||||
import com.bbit.ticket.plugins.dbQuery
|
||||
import com.bbit.ticket.utils.parseUuid
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import org.jetbrains.exposed.v1.core.ResultRow
|
||||
import org.jetbrains.exposed.v1.core.and
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.core.isNull
|
||||
import org.jetbrains.exposed.v1.jdbc.insert
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
import org.jetbrains.exposed.v1.jdbc.update
|
||||
import java.time.OffsetDateTime
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
object MenuService {
|
||||
suspend fun tree(): List<MenuTreeNode> = dbQuery {
|
||||
val rows = SysMenuTable.selectAll().where { SysMenuTable.deletedAt.isNull() }.toList()
|
||||
val flat = rows.map {
|
||||
MenuFlat(
|
||||
id = it[SysMenuTable.id],
|
||||
parentId = it[SysMenuTable.parentId],
|
||||
type = it[SysMenuTable.type],
|
||||
title = it[SysMenuTable.title],
|
||||
name = it[SysMenuTable.name],
|
||||
path = it[path],
|
||||
component = it[component],
|
||||
icon = it[icon],
|
||||
permission = it[permission],
|
||||
sort = it[SysMenuTable.sort],
|
||||
visible = it[visible],
|
||||
keepAlive = it[keepAlive],
|
||||
builtIn = it[builtIn],
|
||||
status = it[SysMenuTable.status],
|
||||
)
|
||||
}
|
||||
buildTree(flat)
|
||||
}
|
||||
suspend fun tree(): List<MenuTreeNode> = dbQuery { MenuDao.tree() }
|
||||
|
||||
suspend fun create(request: CreateMenuRequest): String = dbQuery {
|
||||
validateMenuType(request.type)
|
||||
val parentId = request.parentId?.let { parseUuid(it, "parentId") }
|
||||
if (parentId != null) requireMenu(parentId)
|
||||
val inserted = SysMenuTable.insert {
|
||||
it[SysMenuTable.parentId] = parentId
|
||||
it[SysMenuTable.type] = request.type
|
||||
it[title] = request.title.trim()
|
||||
it[name] = request.name?.trim()
|
||||
it[path] = request.path?.trim()
|
||||
it[component] = request.component?.trim()
|
||||
it[icon] = request.icon?.trim()
|
||||
it[permission] = request.permission?.trim()
|
||||
it[sort] = request.sort
|
||||
it[visible] = request.visible
|
||||
it[keepAlive] = request.keepAlive
|
||||
it[builtIn] = false
|
||||
it[status] = request.status
|
||||
it[createdAt] = OffsetDateTime.now()
|
||||
}
|
||||
inserted[SysMenuTable.id].toString()
|
||||
if (parentId != null) MenuDao.requireActive(parentId)
|
||||
MenuDao.create(request, parentId)
|
||||
}
|
||||
|
||||
suspend fun update(id: Uuid, request: UpdateMenuRequest) = dbQuery {
|
||||
requireMenu(id)
|
||||
MenuDao.requireActive(id)
|
||||
validateMenuType(request.type)
|
||||
val parentId = request.parentId?.let { parseUuid(it, "parentId") }
|
||||
if (parentId == id) throw BizException(ErrorCode.BAD_REQUEST.code, "上级菜单不能选择自身")
|
||||
if (parentId != null) requireMenu(parentId)
|
||||
SysMenuTable.update({ SysMenuTable.id eq id }) {
|
||||
it[SysMenuTable.parentId] = parentId
|
||||
it[SysMenuTable.type] = request.type
|
||||
it[title] = request.title.trim()
|
||||
it[name] = request.name?.trim()
|
||||
it[path] = request.path?.trim()
|
||||
it[component] = request.component?.trim()
|
||||
it[icon] = request.icon?.trim()
|
||||
it[permission] = request.permission?.trim()
|
||||
it[sort] = request.sort
|
||||
it[visible] = request.visible
|
||||
it[keepAlive] = request.keepAlive
|
||||
it[status] = request.status
|
||||
it[updatedAt] = OffsetDateTime.now()
|
||||
}
|
||||
if (parentId != null) MenuDao.requireActive(parentId)
|
||||
MenuDao.update(id, request, parentId)
|
||||
}
|
||||
|
||||
suspend fun delete(id: Uuid) = dbQuery {
|
||||
requireMenu(id)
|
||||
val hasChildren = SysMenuTable.selectAll().where {
|
||||
(SysMenuTable.parentId eq id) and SysMenuTable.deletedAt.isNull()
|
||||
}.any()
|
||||
if (hasChildren) throw BizException(ErrorCode.BAD_REQUEST.code, "存在子菜单,不能删除")
|
||||
val referenced = SysRoleMenuTable.selectAll().where { SysRoleMenuTable.menuId eq id }.any()
|
||||
if (referenced) throw BizException(ErrorCode.BAD_REQUEST.code, "菜单已被角色引用,不能删除")
|
||||
val row = requireMenu(id)
|
||||
if (row[builtIn]) throw BizException(ErrorCode.BAD_REQUEST.code, "基础框架内置菜单不可删除")
|
||||
SysMenuTable.update({ SysMenuTable.id eq id }) {
|
||||
it[deletedAt] = OffsetDateTime.now()
|
||||
val row = MenuDao.requireActive(id)
|
||||
if (MenuDao.hasChildren(id)) throw BizException(ErrorCode.BAD_REQUEST.code, "存在子菜单,不能删除")
|
||||
if (MenuDao.isReferenced(id)) throw BizException(ErrorCode.BAD_REQUEST.code, "菜单已被角色引用,不能删除")
|
||||
if (row[com.bbit.ticket.database.system.SysMenuTable.builtIn]) {
|
||||
throw BizException(ErrorCode.BAD_REQUEST.code, "基础框架内置菜单不可删除")
|
||||
}
|
||||
MenuDao.softDelete(id)
|
||||
}
|
||||
|
||||
private fun requireMenu(id: Uuid): ResultRow =
|
||||
SysMenuTable.selectAll().where { (SysMenuTable.id eq id) and SysMenuTable.deletedAt.isNull() }.singleOrNull()
|
||||
?: throw BizException(
|
||||
ErrorCode.MENU_NOT_FOUND.code,
|
||||
ErrorCode.MENU_NOT_FOUND.message,
|
||||
HttpStatusCode.NotFound
|
||||
)
|
||||
|
||||
private fun validateMenuType(type: String) {
|
||||
if (type !in setOf("CATALOG", "MENU", "BUTTON")) {
|
||||
throw BizException(ErrorCode.BAD_REQUEST.code, "菜单类型必须是目录、菜单或按钮")
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildTree(items: List<MenuFlat>): List<MenuTreeNode> {
|
||||
val grouped = items.groupBy { it.parentId }
|
||||
fun children(parentId: Uuid?): List<MenuTreeNode> =
|
||||
(grouped[parentId] ?: emptyList()).sortedBy { it.sort }.map { menu ->
|
||||
MenuTreeNode(
|
||||
id = menu.id.toString(),
|
||||
parentId = menu.parentId?.toString(),
|
||||
type = menu.type,
|
||||
typeLabel = menuTypeLabel(menu.type),
|
||||
title = menu.title,
|
||||
name = menu.name,
|
||||
path = menu.path,
|
||||
component = menu.component,
|
||||
icon = menu.icon,
|
||||
permission = menu.permission,
|
||||
sort = menu.sort,
|
||||
visible = menu.visible,
|
||||
keepAlive = menu.keepAlive,
|
||||
builtIn = menu.builtIn,
|
||||
status = menu.status,
|
||||
statusLabel = statusLabel(menu.status),
|
||||
children = children(menu.id),
|
||||
)
|
||||
}
|
||||
return children(null)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,10 @@
|
||||
|
||||
package com.bbit.ticket.service.system
|
||||
|
||||
import com.bbit.ticket.database.system.SysOperationLogTable
|
||||
import com.bbit.ticket.utils.traceIdOrNull
|
||||
import com.bbit.ticket.dao.system.LogDao
|
||||
import com.bbit.ticket.plugins.dbQuery
|
||||
import com.bbit.ticket.utils.CurrentUser
|
||||
import io.ktor.http.formUrlEncode
|
||||
import io.ktor.server.application.ApplicationCall
|
||||
import io.ktor.server.request.httpMethod
|
||||
import io.ktor.server.request.path
|
||||
import org.jetbrains.exposed.v1.jdbc.insert
|
||||
import java.time.OffsetDateTime
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
|
||||
object OperationLogService {
|
||||
@@ -39,22 +33,6 @@ object OperationLogService {
|
||||
errorMessage: String?,
|
||||
costMs: Long,
|
||||
) = dbQuery {
|
||||
SysOperationLogTable.insert {
|
||||
it[traceId] = call.traceIdOrNull()
|
||||
it[userId] = currentUser?.id
|
||||
it[username] = currentUser?.username
|
||||
it[orgId] = currentUser?.orgId
|
||||
it[SysOperationLogTable.operationType] = operationType
|
||||
it[SysOperationLogTable.operationName] = operationName
|
||||
it[httpMethod] = call.request.httpMethod.value
|
||||
it[requestPath] = call.request.path().take(255)
|
||||
it[requestParams] = call.request.queryParameters.formUrlEncode().take(1000)
|
||||
it[ip] = call.request.local.remoteHost.take(64)
|
||||
it[userAgent] = call.request.headers["User-Agent"]?.take(255)
|
||||
it[SysOperationLogTable.status] = status
|
||||
it[SysOperationLogTable.errorMessage] = errorMessage
|
||||
it[SysOperationLogTable.costMs] = costMs
|
||||
it[createdAt] = OffsetDateTime.now()
|
||||
}
|
||||
LogDao.saveOperationLog(call, currentUser, operationType, operationName, status, errorMessage, costMs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,140 +2,54 @@
|
||||
|
||||
package com.bbit.ticket.service.system
|
||||
|
||||
import com.bbit.ticket.database.system.SysOrgTable
|
||||
import com.bbit.ticket.database.system.SysUserTable
|
||||
import com.bbit.ticket.dao.system.OrgDao
|
||||
import com.bbit.ticket.entity.system.CreateOrgRequest
|
||||
import com.bbit.ticket.entity.system.OrgTreeNode
|
||||
import com.bbit.ticket.entity.system.UpdateOrgRequest
|
||||
import com.bbit.ticket.entity.common.BizException
|
||||
import com.bbit.ticket.entity.common.ErrorCode
|
||||
import com.bbit.ticket.entity.common.statusLabel
|
||||
import com.bbit.ticket.plugins.dbQuery
|
||||
import com.bbit.ticket.utils.parseUuid
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import org.jetbrains.exposed.v1.core.ResultRow
|
||||
import org.jetbrains.exposed.v1.core.and
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.core.isNull
|
||||
import org.jetbrains.exposed.v1.jdbc.insert
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
import org.jetbrains.exposed.v1.jdbc.update
|
||||
import java.time.OffsetDateTime
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
object OrgService {
|
||||
suspend fun tree(): List<OrgTreeNode> = dbQuery {
|
||||
val rows = SysOrgTable.selectAll()
|
||||
.where { SysOrgTable.deletedAt.isNull() }
|
||||
.orderBy(SysOrgTable.sort)
|
||||
.toList()
|
||||
val nodes = rows.map(::toNode)
|
||||
buildTree(nodes)
|
||||
}
|
||||
suspend fun tree(): List<OrgTreeNode> = dbQuery { OrgDao.tree() }
|
||||
|
||||
suspend fun create(request: CreateOrgRequest): String = dbQuery {
|
||||
val code = request.code.trim()
|
||||
if (code.isBlank() || request.name.trim().isBlank()) {
|
||||
throw BizException(ErrorCode.BAD_REQUEST.code, "组织名称和编码不能为空")
|
||||
}
|
||||
val exists = SysOrgTable.selectAll().where {
|
||||
(SysOrgTable.code eq code) and SysOrgTable.deletedAt.isNull()
|
||||
}.any()
|
||||
if (exists) {
|
||||
if (OrgDao.codeExists(code)) {
|
||||
throw BizException(ErrorCode.DATA_CONFLICT.code, "组织编码已存在")
|
||||
}
|
||||
val parentId = request.parentId?.let { parseUuid(it, "parentId") }
|
||||
if (parentId != null) requireOrg(parentId)
|
||||
val inserted = SysOrgTable.insert {
|
||||
it[SysOrgTable.parentId] = parentId
|
||||
it[name] = request.name.trim()
|
||||
it[SysOrgTable.code] = code
|
||||
it[sort] = request.sort
|
||||
it[status] = request.status
|
||||
it[createdAt] = OffsetDateTime.now()
|
||||
}
|
||||
inserted[SysOrgTable.id].toString()
|
||||
if (parentId != null) OrgDao.requireActive(parentId)
|
||||
OrgDao.create(request, parentId)
|
||||
}
|
||||
|
||||
suspend fun update(id: Uuid, request: UpdateOrgRequest) = dbQuery {
|
||||
requireOrg(id)
|
||||
OrgDao.requireActive(id)
|
||||
val parentId = request.parentId?.let { parseUuid(it, "parentId") }
|
||||
if (parentId == id) {
|
||||
throw BizException(ErrorCode.BAD_REQUEST.code, "上级组织不能选择自身")
|
||||
}
|
||||
if (parentId != null) requireOrg(parentId)
|
||||
SysOrgTable.update({ SysOrgTable.id eq id }) {
|
||||
it[SysOrgTable.parentId] = parentId
|
||||
it[name] = request.name.trim()
|
||||
it[sort] = request.sort
|
||||
it[status] = request.status
|
||||
it[updatedAt] = OffsetDateTime.now()
|
||||
}
|
||||
if (parentId != null) OrgDao.requireActive(parentId)
|
||||
OrgDao.update(id, request, parentId)
|
||||
}
|
||||
|
||||
suspend fun delete(id: Uuid) = dbQuery {
|
||||
val org = requireOrg(id)
|
||||
if (org[SysOrgTable.code] == "DEFAULT_ORG") {
|
||||
val org = OrgDao.requireActive(id)
|
||||
if (org[com.bbit.ticket.database.system.SysOrgTable.code] == "DEFAULT_ORG") {
|
||||
throw BizException(ErrorCode.BAD_REQUEST.code, "默认组织不可删除")
|
||||
}
|
||||
val hasChildren = SysOrgTable.selectAll()
|
||||
.where { (SysOrgTable.parentId eq id) and SysOrgTable.deletedAt.isNull() }
|
||||
.any()
|
||||
if (hasChildren) {
|
||||
if (OrgDao.hasChildren(id)) {
|
||||
throw BizException(ErrorCode.BAD_REQUEST.code, "当前组织存在子组织,不能删除")
|
||||
}
|
||||
val hasUsers = SysUserTable.selectAll()
|
||||
.where { (SysUserTable.orgId eq id) and SysUserTable.deletedAt.isNull() }
|
||||
.any()
|
||||
if (hasUsers) {
|
||||
if (OrgDao.hasUsers(id)) {
|
||||
throw BizException(ErrorCode.BAD_REQUEST.code, "当前组织存在用户,不能删除")
|
||||
}
|
||||
SysOrgTable.update({ SysOrgTable.id eq id }) {
|
||||
it[deletedAt] = OffsetDateTime.now()
|
||||
}
|
||||
OrgDao.softDelete(id)
|
||||
}
|
||||
|
||||
private fun requireOrg(id: Uuid): ResultRow =
|
||||
SysOrgTable.selectAll().where { (SysOrgTable.id eq id) and SysOrgTable.deletedAt.isNull() }.singleOrNull()
|
||||
?: throw BizException(
|
||||
ErrorCode.ORG_NOT_FOUND.code,
|
||||
ErrorCode.ORG_NOT_FOUND.message,
|
||||
HttpStatusCode.NotFound
|
||||
)
|
||||
|
||||
private fun toNode(row: ResultRow): OrgNodeFlat = OrgNodeFlat(
|
||||
id = row[SysOrgTable.id],
|
||||
parentId = row[SysOrgTable.parentId],
|
||||
name = row[SysOrgTable.name],
|
||||
code = row[SysOrgTable.code],
|
||||
sort = row[SysOrgTable.sort],
|
||||
status = row[SysOrgTable.status],
|
||||
)
|
||||
|
||||
private fun buildTree(nodes: List<OrgNodeFlat>): List<OrgTreeNode> {
|
||||
val byParent = nodes.groupBy { it.parentId }
|
||||
fun children(parentId: Uuid?): List<OrgTreeNode> =
|
||||
(byParent[parentId] ?: emptyList()).sortedBy { it.sort }.map { item ->
|
||||
OrgTreeNode(
|
||||
id = item.id.toString(),
|
||||
parentId = item.parentId?.toString(),
|
||||
name = item.name,
|
||||
code = item.code,
|
||||
sort = item.sort,
|
||||
status = item.status,
|
||||
statusLabel = statusLabel(item.status),
|
||||
children = children(item.id),
|
||||
)
|
||||
}
|
||||
return children(null)
|
||||
}
|
||||
private data class OrgNodeFlat(
|
||||
val id: Uuid,
|
||||
val parentId: Uuid?,
|
||||
val name: String,
|
||||
val code: String,
|
||||
val sort: Int,
|
||||
val status: String,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,164 +2,61 @@
|
||||
|
||||
package com.bbit.ticket.service.system
|
||||
|
||||
import com.bbit.ticket.database.system.SysMenuTable
|
||||
import com.bbit.ticket.database.system.SysRoleMenuTable
|
||||
import com.bbit.ticket.database.system.SysRoleTable
|
||||
import com.bbit.ticket.database.system.SysRoleTable.dataScope
|
||||
import com.bbit.ticket.database.system.SysUserRoleTable
|
||||
import com.bbit.ticket.dao.system.RoleDao
|
||||
import com.bbit.ticket.entity.system.CreateRoleRequest
|
||||
import com.bbit.ticket.entity.system.RoleDetail
|
||||
import com.bbit.ticket.entity.system.RoleItem
|
||||
import com.bbit.ticket.entity.system.UpdateRoleMenusRequest
|
||||
import com.bbit.ticket.entity.system.UpdateRoleRequest
|
||||
import com.bbit.ticket.entity.common.BizException
|
||||
import com.bbit.ticket.entity.common.ErrorCode
|
||||
import com.bbit.ticket.entity.common.PageResult
|
||||
import com.bbit.ticket.entity.common.dataScopeLabel
|
||||
import com.bbit.ticket.entity.common.statusLabel
|
||||
import com.bbit.ticket.entity.system.UpdateRoleMenusRequest
|
||||
import com.bbit.ticket.entity.system.UpdateRoleRequest
|
||||
import com.bbit.ticket.plugins.dbQuery
|
||||
import com.bbit.ticket.utils.parseUuid
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import org.jetbrains.exposed.v1.core.ResultRow
|
||||
import org.jetbrains.exposed.v1.core.and
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.core.inList
|
||||
import org.jetbrains.exposed.v1.core.isNull
|
||||
import org.jetbrains.exposed.v1.core.like
|
||||
import org.jetbrains.exposed.v1.core.or
|
||||
import org.jetbrains.exposed.v1.jdbc.deleteWhere
|
||||
import org.jetbrains.exposed.v1.jdbc.insert
|
||||
import org.jetbrains.exposed.v1.jdbc.insertIgnore
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
import org.jetbrains.exposed.v1.jdbc.update
|
||||
import java.time.OffsetDateTime
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
object RoleService {
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
suspend fun list(page: Int, pageSize: Int, keyword: String?, status: String?): PageResult<RoleItem> = dbQuery {
|
||||
var where = SysRoleTable.deletedAt.isNull()
|
||||
if (!keyword.isNullOrBlank()) {
|
||||
where = where and ((SysRoleTable.name like "%$keyword%") or (SysRoleTable.code like "%$keyword%"))
|
||||
}
|
||||
if (!status.isNullOrBlank()) {
|
||||
where = where and (SysRoleTable.status eq status)
|
||||
}
|
||||
val total = SysRoleTable.selectAll().where { where }.count()
|
||||
val rows = SysRoleTable.selectAll().where { where }
|
||||
.orderBy(SysRoleTable.createdAt)
|
||||
.limit(pageSize)
|
||||
.offset(((page - 1) * pageSize).toLong())
|
||||
.toList()
|
||||
PageResult(
|
||||
items = rows.map {
|
||||
RoleItem(
|
||||
id = it[SysRoleTable.id].toString(),
|
||||
name = it[SysRoleTable.name],
|
||||
code = it[SysRoleTable.code],
|
||||
description = it[SysRoleTable.description],
|
||||
status = it[SysRoleTable.status],
|
||||
statusLabel = statusLabel(it[SysRoleTable.status]),
|
||||
dataScope = it[dataScope],
|
||||
dataScopeLabel = dataScopeLabel(it[dataScope]),
|
||||
)
|
||||
},
|
||||
page = page,
|
||||
pageSize = pageSize,
|
||||
total = total,
|
||||
)
|
||||
}
|
||||
suspend fun list(page: Int, pageSize: Int, keyword: String?, status: String?): PageResult<RoleItem> =
|
||||
dbQuery { RoleDao.list(page, pageSize, keyword, status) }
|
||||
|
||||
suspend fun create(request: CreateRoleRequest): String = dbQuery {
|
||||
if (request.name.trim().isBlank() || request.code.trim().isBlank()) {
|
||||
throw BizException(ErrorCode.BAD_REQUEST.code, "角色名称和编码不能为空")
|
||||
}
|
||||
val exists = SysRoleTable.selectAll().where {
|
||||
(SysRoleTable.code eq request.code.trim()) and SysRoleTable.deletedAt.isNull()
|
||||
}.any()
|
||||
if (exists) throw BizException(ErrorCode.DATA_CONFLICT.code, "角色编码已存在")
|
||||
val inserted = SysRoleTable.insert {
|
||||
it[name] = request.name.trim()
|
||||
it[code] = request.code.trim()
|
||||
it[description] = request.description?.trim()
|
||||
it[status] = request.status
|
||||
it[dataScope] = request.dataScope
|
||||
it[createdAt] = OffsetDateTime.now()
|
||||
if (RoleDao.codeExists(request.code.trim())) {
|
||||
throw BizException(ErrorCode.DATA_CONFLICT.code, "角色编码已存在")
|
||||
}
|
||||
inserted[SysRoleTable.id].toString()
|
||||
RoleDao.create(request)
|
||||
}
|
||||
|
||||
suspend fun detail(id: Uuid): RoleDetail = dbQuery {
|
||||
val role = requireRole(id)
|
||||
val menuIds = SysRoleMenuTable.selectAll().where { SysRoleMenuTable.roleId eq id }
|
||||
.map { it[SysRoleMenuTable.menuId].toString() }
|
||||
RoleDetail(
|
||||
id = role[SysRoleTable.id].toString(),
|
||||
name = role[SysRoleTable.name],
|
||||
code = role[SysRoleTable.code],
|
||||
description = role[SysRoleTable.description],
|
||||
status = role[SysRoleTable.status],
|
||||
statusLabel = statusLabel(role[SysRoleTable.status]),
|
||||
dataScope = role[dataScope],
|
||||
dataScopeLabel = dataScopeLabel(role[dataScope]),
|
||||
menuIds = menuIds,
|
||||
)
|
||||
}
|
||||
suspend fun detail(id: Uuid): RoleDetail = dbQuery { RoleDao.detail(id) }
|
||||
|
||||
suspend fun update(id: Uuid, request: UpdateRoleRequest) = dbQuery {
|
||||
requireRole(id)
|
||||
SysRoleTable.update({ SysRoleTable.id eq id }) {
|
||||
it[name] = request.name.trim()
|
||||
it[description] = request.description?.trim()
|
||||
it[status] = request.status
|
||||
it[dataScope] = request.dataScope
|
||||
it[updatedAt] = OffsetDateTime.now()
|
||||
}
|
||||
RoleDao.requireActive(id)
|
||||
RoleDao.update(id, request)
|
||||
}
|
||||
|
||||
suspend fun delete(id: Uuid) = dbQuery {
|
||||
val role = requireRole(id)
|
||||
if (role[SysRoleTable.code] == "SUPER_ADMIN") {
|
||||
val role = RoleDao.requireActive(id)
|
||||
if (role[com.bbit.ticket.database.system.SysRoleTable.code] == "SUPER_ADMIN") {
|
||||
throw BizException(ErrorCode.BAD_REQUEST.code, "超级管理员角色不可删除")
|
||||
}
|
||||
val inUse = SysUserRoleTable.selectAll().where { SysUserRoleTable.roleId eq id }.any()
|
||||
if (inUse) {
|
||||
if (RoleDao.inUse(id)) {
|
||||
throw BizException(ErrorCode.BAD_REQUEST.code, "角色已被用户使用,不能删除")
|
||||
}
|
||||
SysRoleTable.update({ SysRoleTable.id eq id }) {
|
||||
it[deletedAt] = OffsetDateTime.now()
|
||||
}
|
||||
SysRoleMenuTable.deleteWhere { SysRoleMenuTable.roleId eq id }
|
||||
RoleDao.softDelete(id)
|
||||
}
|
||||
|
||||
suspend fun updateMenus(id: Uuid, request: UpdateRoleMenusRequest) = dbQuery {
|
||||
requireRole(id)
|
||||
RoleDao.requireActive(id)
|
||||
val menuIds = request.menuIds.distinct().map { parseUuid(it, "menuId") }
|
||||
if (menuIds.isNotEmpty()) {
|
||||
val validCount = SysMenuTable.selectAll().where {
|
||||
(SysMenuTable.id inList menuIds) and
|
||||
SysMenuTable.deletedAt.isNull() and
|
||||
(SysMenuTable.status eq "ENABLED")
|
||||
}.count()
|
||||
if (validCount != menuIds.size.toLong()) {
|
||||
if (RoleDao.countEnabledMenus(menuIds) != menuIds.size.toLong()) {
|
||||
throw BizException(ErrorCode.BAD_REQUEST.code, "包含不存在或禁用菜单")
|
||||
}
|
||||
}
|
||||
SysRoleMenuTable.deleteWhere { SysRoleMenuTable.roleId eq id }
|
||||
menuIds.forEach { menuId ->
|
||||
SysRoleMenuTable.insertIgnore {
|
||||
it[roleId] = id
|
||||
it[SysRoleMenuTable.menuId] = menuId
|
||||
}
|
||||
}
|
||||
RoleDao.replaceMenus(id, menuIds)
|
||||
}
|
||||
|
||||
private fun requireRole(id: Uuid): ResultRow =
|
||||
SysRoleTable.selectAll().where { (SysRoleTable.id eq id) and SysRoleTable.deletedAt.isNull() }.singleOrNull()
|
||||
?: throw BizException(
|
||||
ErrorCode.ROLE_NOT_FOUND.code,
|
||||
ErrorCode.ROLE_NOT_FOUND.message,
|
||||
HttpStatusCode.NotFound
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
package com.bbit.ticket.service.system
|
||||
|
||||
import com.bbit.ticket.database.system.SysOrgTable
|
||||
import com.bbit.ticket.database.system.SysRoleTable
|
||||
import com.bbit.ticket.database.system.SysUserRoleTable
|
||||
import com.bbit.ticket.database.system.SysUserTable
|
||||
import com.bbit.ticket.database.system.SysUserTable.avatar
|
||||
import com.bbit.ticket.database.system.SysUserTable.email
|
||||
import com.bbit.ticket.database.system.SysUserTable.nickname
|
||||
import com.bbit.ticket.database.system.SysUserTable.phone
|
||||
import com.bbit.ticket.database.system.SysUserTable.realName
|
||||
import com.bbit.ticket.dao.system.UserDao
|
||||
import com.bbit.ticket.entity.system.CreateUserRequest
|
||||
import com.bbit.ticket.entity.system.UpdateUserPasswordRequest
|
||||
import com.bbit.ticket.entity.system.UpdateUserRequest
|
||||
@@ -19,22 +11,8 @@ import com.bbit.ticket.entity.system.UserListItem
|
||||
import com.bbit.ticket.entity.common.BizException
|
||||
import com.bbit.ticket.entity.common.ErrorCode
|
||||
import com.bbit.ticket.entity.common.PageResult
|
||||
import com.bbit.ticket.entity.common.statusLabel
|
||||
import com.bbit.ticket.plugins.dbQuery
|
||||
import com.bbit.ticket.utils.parseUuid
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import org.jetbrains.exposed.v1.core.Op
|
||||
import org.jetbrains.exposed.v1.core.and
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.core.inList
|
||||
import org.jetbrains.exposed.v1.core.isNull
|
||||
import org.jetbrains.exposed.v1.core.like
|
||||
import org.jetbrains.exposed.v1.jdbc.deleteWhere
|
||||
import org.jetbrains.exposed.v1.jdbc.insert
|
||||
import org.jetbrains.exposed.v1.jdbc.insertIgnore
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
import org.jetbrains.exposed.v1.jdbc.update
|
||||
import java.time.OffsetDateTime
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
@@ -47,57 +25,14 @@ object UserService {
|
||||
nickname: String?,
|
||||
status: String?,
|
||||
orgId: Uuid?,
|
||||
): PageResult<UserListItem> = dbQuery {
|
||||
val where = buildWhere(username, nickname, status, orgId)
|
||||
val total = SysUserTable.selectAll().where { where }.count()
|
||||
val rows = SysUserTable.selectAll()
|
||||
.where { where }
|
||||
.orderBy(SysUserTable.createdAt)
|
||||
.limit(pageSize)
|
||||
.offset(((page - 1) * pageSize).toLong())
|
||||
.toList()
|
||||
|
||||
val userIds = rows.map { it[SysUserTable.id] }
|
||||
val roleMap = if (userIds.isEmpty()) {
|
||||
emptyMap()
|
||||
} else {
|
||||
(SysUserRoleTable innerJoin SysRoleTable).selectAll()
|
||||
.where {
|
||||
(SysUserRoleTable.userId inList userIds) and
|
||||
SysRoleTable.deletedAt.isNull()
|
||||
}
|
||||
.groupBy { it[SysUserRoleTable.userId] }
|
||||
.mapValues { entry -> entry.value.map { row -> row[SysRoleTable.code] }.distinct() }
|
||||
}
|
||||
|
||||
PageResult(
|
||||
items = rows.map { row ->
|
||||
UserListItem(
|
||||
id = row[SysUserTable.id].toString(),
|
||||
username = row[SysUserTable.username],
|
||||
nickname = row[SysUserTable.nickname],
|
||||
realName = row[realName],
|
||||
orgId = row[SysUserTable.orgId]?.toString(),
|
||||
status = row[SysUserTable.status],
|
||||
statusLabel = statusLabel(row[SysUserTable.status]),
|
||||
roleCodes = roleMap[row[SysUserTable.id]] ?: emptyList(),
|
||||
)
|
||||
},
|
||||
page = page,
|
||||
pageSize = pageSize,
|
||||
total = total,
|
||||
)
|
||||
}
|
||||
): PageResult<UserListItem> = dbQuery { UserDao.list(page, pageSize, username, nickname, status, orgId) }
|
||||
|
||||
suspend fun create(request: CreateUserRequest): String = dbQuery {
|
||||
val username = request.username.trim()
|
||||
if (username.isBlank() || request.password.isBlank()) {
|
||||
throw BizException(ErrorCode.BAD_REQUEST.code, "用户名和密码不能为空")
|
||||
}
|
||||
val existed = SysUserTable.selectAll().where {
|
||||
(SysUserTable.username eq username) and SysUserTable.deletedAt.isNull()
|
||||
}.any()
|
||||
if (existed) {
|
||||
if (UserDao.findByUsername(username) != null) {
|
||||
throw BizException(ErrorCode.DATA_CONFLICT.code, "用户名已存在")
|
||||
}
|
||||
|
||||
@@ -105,142 +40,59 @@ object UserService {
|
||||
if (orgUuid != null) {
|
||||
ensureOrgExists(orgUuid)
|
||||
}
|
||||
val now = OffsetDateTime.now()
|
||||
val row = SysUserTable.insert {
|
||||
it[SysUserTable.username] = username
|
||||
it[passwordHash] = PasswordService.hash(request.password)
|
||||
it[nickname] = request.nickname?.trim()
|
||||
it[realName] = request.realName?.trim()
|
||||
it[phone] = request.phone?.trim()
|
||||
it[email] = request.email?.trim()
|
||||
it[avatar] = request.avatar?.trim()
|
||||
it[orgId] = orgUuid
|
||||
it[status] = request.status
|
||||
it[tokenVersion] = 1
|
||||
it[createdAt] = now
|
||||
}
|
||||
row[SysUserTable.id].toString()
|
||||
UserDao.create(request, PasswordService.hash(request.password), orgUuid)
|
||||
}
|
||||
|
||||
suspend fun detail(id: Uuid): UserDetailResponse = dbQuery {
|
||||
val user = requireUser(id)
|
||||
val roleIds = SysUserRoleTable.selectAll().where { SysUserRoleTable.userId eq id }
|
||||
.map { it[SysUserRoleTable.roleId].toString() }
|
||||
UserDetailResponse(
|
||||
id = user[SysUserTable.id].toString(),
|
||||
username = user[SysUserTable.username],
|
||||
nickname = user[nickname],
|
||||
realName = user[realName],
|
||||
phone = user[phone],
|
||||
email = user[email],
|
||||
avatar = user[avatar],
|
||||
orgId = user[SysUserTable.orgId]?.toString(),
|
||||
status = user[SysUserTable.status],
|
||||
statusLabel = statusLabel(user[SysUserTable.status]),
|
||||
roleIds = roleIds,
|
||||
)
|
||||
}
|
||||
suspend fun detail(id: Uuid): UserDetailResponse = dbQuery { UserDao.detail(id) }
|
||||
|
||||
suspend fun update(id: Uuid, request: UpdateUserRequest) = dbQuery {
|
||||
requireUser(id)
|
||||
UserDao.requireActive(id)
|
||||
val orgUuid = request.orgId?.let { parseUuid(it, "orgId") }
|
||||
if (orgUuid != null) {
|
||||
ensureOrgExists(orgUuid)
|
||||
}
|
||||
SysUserTable.update({ SysUserTable.id eq id }) {
|
||||
it[nickname] = request.nickname?.trim()
|
||||
it[realName] = request.realName?.trim()
|
||||
it[phone] = request.phone?.trim()
|
||||
it[email] = request.email?.trim()
|
||||
it[avatar] = request.avatar?.trim()
|
||||
it[orgId] = orgUuid
|
||||
it[updatedAt] = OffsetDateTime.now()
|
||||
}
|
||||
UserDao.updateProfile(id, request, orgUuid)
|
||||
}
|
||||
|
||||
suspend fun softDelete(id: Uuid) = dbQuery {
|
||||
if (id.toString() == "00000000-0000-0000-0000-000000000000") {
|
||||
throw BizException(ErrorCode.BAD_REQUEST.code, "系统保留用户不可删除")
|
||||
}
|
||||
requireUser(id)
|
||||
SysUserTable.update({ SysUserTable.id eq id }) {
|
||||
it[deletedAt] = OffsetDateTime.now()
|
||||
}
|
||||
SysUserRoleTable.deleteWhere { SysUserRoleTable.userId eq id }
|
||||
UserDao.requireActive(id)
|
||||
UserDao.softDelete(id)
|
||||
}
|
||||
|
||||
suspend fun updateStatus(id: Uuid, request: UpdateUserStatusRequest) = dbQuery {
|
||||
requireUser(id)
|
||||
SysUserTable.update({ SysUserTable.id eq id }) {
|
||||
it[status] = request.status
|
||||
it[updatedAt] = OffsetDateTime.now()
|
||||
}
|
||||
UserDao.requireActive(id)
|
||||
UserDao.updateStatus(id, request.status)
|
||||
}
|
||||
|
||||
suspend fun updatePassword(id: Uuid, request: UpdateUserPasswordRequest) = dbQuery {
|
||||
val user = requireUser(id)
|
||||
val user = UserDao.requireActive(id)
|
||||
if (request.password.isBlank()) {
|
||||
throw BizException(ErrorCode.BAD_REQUEST.code, "密码不能为空")
|
||||
}
|
||||
val nextTokenVersion = user[SysUserTable.tokenVersion] + 1
|
||||
SysUserTable.update({ SysUserTable.id eq id }) {
|
||||
it[passwordHash] = PasswordService.hash(request.password)
|
||||
it[tokenVersion] = nextTokenVersion
|
||||
it[updatedAt] = OffsetDateTime.now()
|
||||
}
|
||||
UserDao.updatePassword(
|
||||
id = id,
|
||||
passwordHash = PasswordService.hash(request.password),
|
||||
nextTokenVersion = user[com.bbit.ticket.database.system.SysUserTable.tokenVersion] + 1,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun updateRoles(id: Uuid, request: UpdateUserRolesRequest) = dbQuery {
|
||||
requireUser(id)
|
||||
UserDao.requireActive(id)
|
||||
val roleIds = request.roleIds.distinct().map { parseUuid(it, "roleId") }
|
||||
if (roleIds.isNotEmpty()) {
|
||||
val validCount = SysRoleTable.selectAll().where {
|
||||
(SysRoleTable.id inList roleIds) and
|
||||
(SysRoleTable.status eq "ENABLED") and
|
||||
SysRoleTable.deletedAt.isNull()
|
||||
}.count()
|
||||
if (validCount != roleIds.size.toLong()) {
|
||||
if (UserDao.countEnabledRoles(roleIds) != roleIds.size.toLong()) {
|
||||
throw BizException(ErrorCode.BAD_REQUEST.code, "包含不存在或已禁用角色")
|
||||
}
|
||||
}
|
||||
SysUserRoleTable.deleteWhere { SysUserRoleTable.userId eq id }
|
||||
roleIds.forEach { roleId ->
|
||||
SysUserRoleTable.insertIgnore {
|
||||
it[userId] = id
|
||||
it[SysUserRoleTable.roleId] = roleId
|
||||
}
|
||||
}
|
||||
UserDao.replaceRoles(id, roleIds)
|
||||
}
|
||||
|
||||
private fun buildWhere(username: String?, nickname: String?, status: String?, orgId: Uuid?): Op<Boolean> {
|
||||
var where: Op<Boolean> = SysUserTable.deletedAt.isNull()
|
||||
if (!username.isNullOrBlank()) {
|
||||
where = where and (SysUserTable.username like "%$username%")
|
||||
}
|
||||
if (!nickname.isNullOrBlank()) {
|
||||
where = where and (SysUserTable.nickname like "%$nickname%")
|
||||
}
|
||||
if (!status.isNullOrBlank()) {
|
||||
where = where and (SysUserTable.status eq status)
|
||||
}
|
||||
if (orgId != null) {
|
||||
where = where and (SysUserTable.orgId eq orgId)
|
||||
}
|
||||
return where
|
||||
}
|
||||
|
||||
private fun requireUser(id: Uuid) =
|
||||
SysUserTable.selectAll().where { (SysUserTable.id eq id) and SysUserTable.deletedAt.isNull() }.singleOrNull()
|
||||
?: throw BizException(
|
||||
ErrorCode.USER_NOT_FOUND.code,
|
||||
ErrorCode.USER_NOT_FOUND.message,
|
||||
HttpStatusCode.NotFound
|
||||
)
|
||||
|
||||
private fun ensureOrgExists(orgId: Uuid) {
|
||||
val exists = SysOrgTable.selectAll().where { (SysOrgTable.id eq orgId) and SysOrgTable.deletedAt.isNull() }.any()
|
||||
if (!exists) {
|
||||
throw BizException(ErrorCode.ORG_NOT_FOUND.code, ErrorCode.ORG_NOT_FOUND.message, HttpStatusCode.BadRequest)
|
||||
if (!UserDao.orgExists(orgId)) {
|
||||
throw BizException(ErrorCode.ORG_NOT_FOUND.code, ErrorCode.ORG_NOT_FOUND.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class Test {
|
||||
@Test
|
||||
fun helloWorld() {
|
||||
print("Hello World!")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user