大改三角色逻辑:超级管理员、企业管理员、开票员
This commit is contained in:
@@ -8,7 +8,6 @@ import com.bbit.ticket.route.system.registerAuthRoutes
|
||||
import com.bbit.ticket.utils.plugins.configureCors
|
||||
import com.bbit.ticket.utils.plugins.configureDatabase
|
||||
import com.bbit.ticket.utils.plugins.configureLogging
|
||||
import com.bbit.ticket.utils.plugins.configureApiAccessLog
|
||||
import com.bbit.ticket.utils.plugins.configureRedis
|
||||
import com.bbit.ticket.utils.plugins.configureSecurity
|
||||
import com.bbit.ticket.utils.plugins.configureSerialization
|
||||
@@ -19,10 +18,9 @@ import com.bbit.ticket.route.piaotong.registerPTInvoiceRoutes
|
||||
import com.bbit.ticket.route.openapi.registerOpenBlueInvoiceRoutes
|
||||
import com.bbit.ticket.route.openapi.registerOpenInvoiceTaskRoutes
|
||||
import com.bbit.ticket.route.piaotong.registerOpenInvoiceTaskManageRoutes
|
||||
import com.bbit.ticket.route.system.registerDictRoutes
|
||||
import com.bbit.ticket.route.system.registerEnterpriseManageRoutes
|
||||
import com.bbit.ticket.route.system.registerLogsQueryRoutes
|
||||
import com.bbit.ticket.route.system.registerMenuRoutes
|
||||
import com.bbit.ticket.route.system.registerOrgRoutes
|
||||
import com.bbit.ticket.route.system.registerRoleRoutes
|
||||
import com.bbit.ticket.route.system.registerUserRoutes
|
||||
import com.bbit.ticket.service.openapi.OpenInvoiceTaskWorker
|
||||
@@ -46,7 +44,6 @@ fun Application.module() {
|
||||
configureSerialization()
|
||||
configureStatusPages()
|
||||
configureLogging()
|
||||
configureApiAccessLog()
|
||||
configureCors()
|
||||
configureSecurity()
|
||||
configureDatabase()
|
||||
@@ -64,10 +61,9 @@ fun Application.module() {
|
||||
route("/api") {
|
||||
registerAuthRoutes()
|
||||
registerUserRoutes()
|
||||
registerOrgRoutes()
|
||||
registerEnterpriseManageRoutes()
|
||||
registerRoleRoutes()
|
||||
registerMenuRoutes()
|
||||
registerDictRoutes()
|
||||
registerLogsQueryRoutes()
|
||||
route("/open/v1") {
|
||||
route("/blue-invoices") {
|
||||
|
||||
@@ -6,18 +6,23 @@ import com.bbit.ticket.database.piaotong.PtDigitalAccountTable
|
||||
import com.bbit.ticket.database.piaotong.PtEnterpriseTable
|
||||
import com.bbit.ticket.database.system.SysApiAccessLogTable
|
||||
import com.bbit.ticket.database.system.SysUserTable
|
||||
import com.bbit.ticket.entity.common.PageResult
|
||||
import com.bbit.ticket.entity.request.TaxRegisterInfo
|
||||
import com.bbit.ticket.entity.request.UpdateInvoiceSettingRequest
|
||||
import com.bbit.ticket.entity.response.DigitalAccountInfo
|
||||
import com.bbit.ticket.entity.response.DigitalAccountManageItem
|
||||
import com.bbit.ticket.entity.response.EnterpriseManageListItem
|
||||
import com.bbit.ticket.entity.response.EnterpriseManageResponse
|
||||
import com.bbit.ticket.entity.response.OpenApiStatisticsItem
|
||||
import com.bbit.ticket.entity.response.QueryEnterpriseInfoResponse
|
||||
import org.jetbrains.exposed.v1.core.Op
|
||||
import org.jetbrains.exposed.v1.core.ResultRow
|
||||
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.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
|
||||
@@ -26,6 +31,25 @@ import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
object EnterpriseManageDao {
|
||||
fun list(keyword: String?, page: Int, pageSize: Int): PageResult<EnterpriseManageListItem> {
|
||||
var where: Op<Boolean> = PtEnterpriseTable.deletedAt.isNull()
|
||||
if (!keyword.isNullOrBlank()) {
|
||||
val pattern = "%${keyword.trim()}%"
|
||||
where = where and (
|
||||
(PtEnterpriseTable.taxpayerNum like pattern) or
|
||||
(PtEnterpriseTable.enterpriseName like pattern)
|
||||
)
|
||||
}
|
||||
val total = PtEnterpriseTable.selectAll().where { where }.count()
|
||||
val rows = PtEnterpriseTable.selectAll()
|
||||
.where { where }
|
||||
.orderBy(PtEnterpriseTable.createdAt, SortOrder.DESC)
|
||||
.limit(pageSize)
|
||||
.offset(pageOffset(page, pageSize))
|
||||
.toList()
|
||||
return PageResult(rows.map { it.toEnterpriseManageListItem() }, page, pageSize, total)
|
||||
}
|
||||
|
||||
fun findEnterpriseByTaxpayerNum(taxpayerNum: String): ResultRow? =
|
||||
PtEnterpriseTable.selectAll()
|
||||
.where { (PtEnterpriseTable.taxpayerNum eq taxpayerNum) and PtEnterpriseTable.deletedAt.isNull() }
|
||||
@@ -280,6 +304,26 @@ object EnterpriseManageDao {
|
||||
presetPhone = this[PtEnterpriseTable.presetPhone],
|
||||
)
|
||||
|
||||
private fun ResultRow.toEnterpriseManageListItem() = EnterpriseManageListItem(
|
||||
id = this[PtEnterpriseTable.id].toString(),
|
||||
taxpayerNum = this[PtEnterpriseTable.taxpayerNum],
|
||||
enterpriseName = this[PtEnterpriseTable.enterpriseName],
|
||||
legalPersonName = this[PtEnterpriseTable.legalPersonName],
|
||||
contactsName = this[PtEnterpriseTable.contactsName],
|
||||
contactsEmail = this[PtEnterpriseTable.contactsEmail],
|
||||
contactsPhone = this[PtEnterpriseTable.contactsPhone],
|
||||
regionCode = this[PtEnterpriseTable.regionCode],
|
||||
cityName = this[PtEnterpriseTable.cityName],
|
||||
enterpriseAddress = this[PtEnterpriseTable.enterpriseAddress],
|
||||
reviewStatus = this[PtEnterpriseTable.reviewStatus],
|
||||
reviewOpinion = this[PtEnterpriseTable.reviewOpinion],
|
||||
invoiceKind = this[PtEnterpriseTable.invoiceKind],
|
||||
invoiceLayoutFileType = this[PtEnterpriseTable.invoiceLayoutFileType],
|
||||
serviceStatus = this[PtEnterpriseTable.serviceStatus],
|
||||
createdAt = this[PtEnterpriseTable.createdAt].toString(),
|
||||
updatedAt = this[PtEnterpriseTable.updatedAt]?.toString(),
|
||||
)
|
||||
|
||||
fun ResultRow.toDigitalAccountItem() = DigitalAccountManageItem(
|
||||
id = this[PtDigitalAccountTable.id].toString(),
|
||||
enterpriseId = this[PtDigitalAccountTable.enterpriseId].toString(),
|
||||
@@ -306,4 +350,6 @@ object EnterpriseManageDao {
|
||||
apiKey = this[PtDigitalAccountTable.apiKey],
|
||||
status = this[PtDigitalAccountTable.status],
|
||||
)
|
||||
|
||||
private fun pageOffset(page: Int, pageSize: Int): Long = ((page - 1) * pageSize).toLong()
|
||||
}
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
@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.common.system.CreateDictItemRequest
|
||||
import com.bbit.ticket.entity.common.system.CreateDictTypeRequest
|
||||
import com.bbit.ticket.entity.common.system.DictItem
|
||||
import com.bbit.ticket.entity.common.system.DictTypeItem
|
||||
import com.bbit.ticket.entity.common.system.UpdateDictItemRequest
|
||||
import com.bbit.ticket.entity.common.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],
|
||||
)
|
||||
}
|
||||
@@ -4,7 +4,6 @@ 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.common.system.ApiAccessLogItem
|
||||
import com.bbit.ticket.entity.common.system.OperationLogItem
|
||||
import com.bbit.ticket.entity.common.PageResult
|
||||
import com.bbit.ticket.utils.CurrentUser
|
||||
@@ -65,42 +64,6 @@ object LogDao {
|
||||
)
|
||||
}
|
||||
|
||||
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?,
|
||||
@@ -114,7 +77,7 @@ object LogDao {
|
||||
it[traceId] = call.traceIdOrNull()
|
||||
it[userId] = currentUser?.id
|
||||
it[SysOperationLogTable.username] = currentUser?.username
|
||||
it[orgId] = currentUser?.orgId
|
||||
it[enterpriseId] = currentUser?.enterpriseId
|
||||
it[SysOperationLogTable.operationType] = operationType
|
||||
it[SysOperationLogTable.operationName] = operationName
|
||||
it[httpMethod] = call.request.httpMethod.value
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
@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.common.system.CreateOrgRequest
|
||||
import com.bbit.ticket.entity.common.system.OrgTreeNode
|
||||
import com.bbit.ticket.entity.common.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,
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.bbit.ticket.dao.system
|
||||
|
||||
import com.bbit.ticket.database.system.SysOrgTable
|
||||
import com.bbit.ticket.database.piaotong.PtEnterpriseTable
|
||||
import com.bbit.ticket.database.system.SysRoleTable
|
||||
import com.bbit.ticket.database.system.SysUserRoleTable
|
||||
import com.bbit.ticket.database.system.SysUserTable
|
||||
@@ -38,9 +38,9 @@ object UserDao {
|
||||
username: String?,
|
||||
nickname: String?,
|
||||
status: String?,
|
||||
orgId: Uuid?,
|
||||
enterpriseId: Uuid?,
|
||||
): PageResult<UserListItem> {
|
||||
val where = buildWhere(username, nickname, status, orgId)
|
||||
val where = buildWhere(username, nickname, status, enterpriseId)
|
||||
val total = SysUserTable.selectAll().where { where }.count()
|
||||
val rows = SysUserTable.selectAll()
|
||||
.where { where }
|
||||
@@ -51,7 +51,12 @@ object UserDao {
|
||||
|
||||
val roleMap = findRoleCodesByUserIds(rows.map { it[SysUserTable.id] })
|
||||
return PageResult(
|
||||
items = rows.map { row -> row.toUserListItem(roleMap[row[SysUserTable.id]] ?: emptyList()) },
|
||||
items = rows.map { row ->
|
||||
row.toUserListItem(
|
||||
roleCodes = roleMap[row[SysUserTable.id]] ?: emptyList(),
|
||||
enterprise = row[SysUserTable.enterpriseId]?.let(::findEnterprise),
|
||||
)
|
||||
},
|
||||
page = page,
|
||||
pageSize = pageSize,
|
||||
total = total,
|
||||
@@ -69,7 +74,7 @@ object UserDao {
|
||||
HttpStatusCode.NotFound,
|
||||
)
|
||||
|
||||
fun create(request: CreateUserRequest, passwordHash: String, orgId: Uuid?): String {
|
||||
fun create(request: CreateUserRequest, passwordHash: String, enterpriseId: Uuid?): String {
|
||||
val now = OffsetDateTime.now()
|
||||
val row = SysUserTable.insert {
|
||||
it[SysUserTable.username] = request.username.trim()
|
||||
@@ -79,7 +84,7 @@ object UserDao {
|
||||
it[SysUserTable.phone] = request.phone.trimToNull()
|
||||
it[SysUserTable.email] = request.email.trimToNull()
|
||||
it[SysUserTable.avatar] = request.avatar.trimToNull()
|
||||
it[SysUserTable.orgId] = orgId
|
||||
it[SysUserTable.enterpriseId] = enterpriseId
|
||||
it[SysUserTable.status] = request.status
|
||||
it[SysUserTable.tokenVersion] = 1
|
||||
it[SysUserTable.createdAt] = now
|
||||
@@ -130,22 +135,17 @@ object UserDao {
|
||||
)
|
||||
}
|
||||
val roleIds = roles.map { it.id }
|
||||
val org = user[SysUserTable.orgId]?.let { orgId ->
|
||||
SysOrgTable.selectAll()
|
||||
.where { (SysOrgTable.id eq orgId) and SysOrgTable.deletedAt.isNull() }
|
||||
.singleOrNull()
|
||||
}
|
||||
return user.toUserDetail(roleIds, roles, org)
|
||||
return user.toUserDetail(roleIds, roles, user[SysUserTable.enterpriseId]?.let(::findEnterprise))
|
||||
}
|
||||
|
||||
fun updateProfile(id: Uuid, request: UpdateUserRequest, orgId: Uuid?) {
|
||||
fun updateProfile(id: Uuid, request: UpdateUserRequest, enterpriseId: 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.enterpriseId] = enterpriseId
|
||||
it[SysUserTable.updatedAt] = OffsetDateTime.now()
|
||||
}
|
||||
}
|
||||
@@ -196,9 +196,9 @@ object UserDao {
|
||||
SysRoleTable.deletedAt.isNull()
|
||||
}.singleOrNull()?.get(SysRoleTable.id)
|
||||
|
||||
fun orgExists(orgId: Uuid): Boolean =
|
||||
SysOrgTable.selectAll().where {
|
||||
(SysOrgTable.id eq orgId) and SysOrgTable.deletedAt.isNull()
|
||||
fun enterpriseExists(enterpriseId: Uuid): Boolean =
|
||||
PtEnterpriseTable.selectAll().where {
|
||||
(PtEnterpriseTable.id eq enterpriseId) and PtEnterpriseTable.deletedAt.isNull()
|
||||
}.any()
|
||||
|
||||
fun findEnabledRoleCodes(userId: Uuid): List<String> =
|
||||
@@ -231,7 +231,7 @@ object UserDao {
|
||||
|
||||
private fun activeWhere(): Op<Boolean> = SysUserTable.deletedAt.isNull()
|
||||
|
||||
private fun buildWhere(username: String?, nickname: String?, status: String?, orgId: Uuid?): Op<Boolean> {
|
||||
private fun buildWhere(username: String?, nickname: String?, status: String?, enterpriseId: Uuid?): Op<Boolean> {
|
||||
var where: Op<Boolean> = activeWhere()
|
||||
if (!username.isNullOrBlank()) {
|
||||
where = where and (SysUserTable.username like "%$username%")
|
||||
@@ -242,12 +242,17 @@ object UserDao {
|
||||
if (!status.isNullOrBlank()) {
|
||||
where = where and (SysUserTable.status eq status)
|
||||
}
|
||||
if (orgId != null) {
|
||||
where = where and (SysUserTable.orgId eq orgId)
|
||||
if (enterpriseId != null) {
|
||||
where = where and (SysUserTable.enterpriseId eq enterpriseId)
|
||||
}
|
||||
return where
|
||||
}
|
||||
|
||||
private fun findEnterprise(enterpriseId: Uuid): ResultRow? =
|
||||
PtEnterpriseTable.selectAll()
|
||||
.where { (PtEnterpriseTable.id eq enterpriseId) and PtEnterpriseTable.deletedAt.isNull() }
|
||||
.singleOrNull()
|
||||
|
||||
private fun findRoleCodesByUserIds(userIds: List<Uuid>): Map<Uuid, List<String>> {
|
||||
if (userIds.isEmpty()) return emptyMap()
|
||||
return (SysUserRoleTable innerJoin SysRoleTable).selectAll()
|
||||
@@ -259,12 +264,14 @@ object UserDao {
|
||||
.mapValues { entry -> entry.value.map { row -> row[SysRoleTable.code] }.distinct() }
|
||||
}
|
||||
|
||||
private fun ResultRow.toUserListItem(roleCodes: List<String>) = UserListItem(
|
||||
private fun ResultRow.toUserListItem(roleCodes: List<String>, enterprise: ResultRow?) = UserListItem(
|
||||
id = this[SysUserTable.id].toString(),
|
||||
username = this[SysUserTable.username],
|
||||
nickname = this[SysUserTable.nickname],
|
||||
realName = this[SysUserTable.realName],
|
||||
orgId = this[SysUserTable.orgId]?.toString(),
|
||||
enterpriseId = this[SysUserTable.enterpriseId]?.toString(),
|
||||
enterpriseName = enterprise?.get(PtEnterpriseTable.enterpriseName),
|
||||
taxpayerNum = enterprise?.get(PtEnterpriseTable.taxpayerNum),
|
||||
status = this[SysUserTable.status],
|
||||
statusLabel = statusLabel(this[SysUserTable.status]),
|
||||
roleCodes = roleCodes,
|
||||
@@ -273,7 +280,7 @@ object UserDao {
|
||||
private fun ResultRow.toUserDetail(
|
||||
roleIds: List<String>,
|
||||
roles: List<UserRoleBrief>,
|
||||
org: ResultRow?,
|
||||
enterprise: ResultRow?,
|
||||
) = UserDetailResponse(
|
||||
id = this[SysUserTable.id].toString(),
|
||||
username = this[SysUserTable.username],
|
||||
@@ -282,9 +289,9 @@ object UserDao {
|
||||
phone = this[SysUserTable.phone],
|
||||
email = this[SysUserTable.email],
|
||||
avatar = this[SysUserTable.avatar],
|
||||
orgId = this[SysUserTable.orgId]?.toString(),
|
||||
orgName = org?.get(SysOrgTable.name),
|
||||
orgCode = org?.get(SysOrgTable.code),
|
||||
enterpriseId = this[SysUserTable.enterpriseId]?.toString(),
|
||||
enterpriseName = enterprise?.get(PtEnterpriseTable.enterpriseName),
|
||||
taxpayerNum = enterprise?.get(PtEnterpriseTable.taxpayerNum),
|
||||
status = this[SysUserTable.status],
|
||||
statusLabel = statusLabel(this[SysUserTable.status]),
|
||||
roleIds = roleIds,
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.bbit.ticket.database.system
|
||||
|
||||
|
||||
import org.jetbrains.exposed.v1.core.Table
|
||||
import org.jetbrains.exposed.v1.javatime.timestampWithTimeZone
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
object SysDictItemTable : Table("sys_dict_item") {
|
||||
val id = uuid("id").clientDefault { Uuid.random() }
|
||||
val typeId = uuid("type_id").references(SysDictTypeTable.id)
|
||||
val label = varchar("label", 100)
|
||||
val value = varchar("value", 100)
|
||||
val color = varchar("color", 30).nullable()
|
||||
val sort = integer("sort").default(0)
|
||||
val status = varchar("status", 20).default("ENABLED")
|
||||
val remark = varchar("remark", 255).nullable()
|
||||
val createdAt = timestampWithTimeZone("created_at")
|
||||
val createdBy = uuid("created_by").nullable()
|
||||
val updatedAt = timestampWithTimeZone("updated_at").nullable()
|
||||
val updatedBy = uuid("updated_by").nullable()
|
||||
val deletedAt = timestampWithTimeZone("deleted_at").nullable()
|
||||
val deletedBy = uuid("deleted_by").nullable()
|
||||
val version = integer("version").default(1)
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.bbit.ticket.database.system
|
||||
|
||||
|
||||
import org.jetbrains.exposed.v1.core.Table
|
||||
import org.jetbrains.exposed.v1.javatime.timestampWithTimeZone
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
object SysDictTypeTable : Table("sys_dict_type") {
|
||||
val id = uuid("id").clientDefault { Uuid.random() }
|
||||
val code = varchar("code", 80).uniqueIndex()
|
||||
val name = varchar("name", 100)
|
||||
val status = varchar("status", 20).default("ENABLED")
|
||||
val remark = varchar("remark", 255).nullable()
|
||||
val createdAt = timestampWithTimeZone("created_at")
|
||||
val createdBy = uuid("created_by").nullable()
|
||||
val updatedAt = timestampWithTimeZone("updated_at").nullable()
|
||||
val updatedBy = uuid("updated_by").nullable()
|
||||
val deletedAt = timestampWithTimeZone("deleted_at").nullable()
|
||||
val deletedBy = uuid("deleted_by").nullable()
|
||||
val version = integer("version").default(1)
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
}
|
||||
@@ -11,7 +11,7 @@ object SysOperationLogTable : Table("sys_operation_log") {
|
||||
val traceId = varchar("trace_id", 64).nullable()
|
||||
val userId = uuid("user_id").nullable()
|
||||
val username = varchar("username", 50).nullable()
|
||||
val orgId = uuid("org_id").nullable()
|
||||
val enterpriseId = uuid("enterprise_id").nullable()
|
||||
val operationType = varchar("operation_type", 50)
|
||||
val operationName = varchar("operation_name", 100)
|
||||
val httpMethod = varchar("http_method", 20)
|
||||
@@ -25,4 +25,4 @@ object SysOperationLogTable : Table("sys_operation_log") {
|
||||
val createdAt = timestampWithTimeZone("created_at")
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.bbit.ticket.database.system
|
||||
|
||||
import org.jetbrains.exposed.v1.core.Table
|
||||
import org.jetbrains.exposed.v1.javatime.timestampWithTimeZone
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
@OptIn(ExperimentalUuidApi::class)
|
||||
object SysOrgTable : Table("sys_org") {
|
||||
val id = uuid("id").clientDefault { Uuid.random() }
|
||||
val parentId = uuid("parent_id").nullable()
|
||||
val name = varchar("name", 100)
|
||||
val code = varchar("code", 50).uniqueIndex()
|
||||
val sort = integer("sort").default(0)
|
||||
val status = varchar("status", 20).default("ENABLED")
|
||||
val createdAt = timestampWithTimeZone("created_at")
|
||||
val createdBy = uuid("created_by").nullable()
|
||||
val updatedAt = timestampWithTimeZone("updated_at").nullable()
|
||||
val updatedBy = uuid("updated_by").nullable()
|
||||
val deletedAt = timestampWithTimeZone("deleted_at").nullable()
|
||||
val deletedBy = uuid("deleted_by").nullable()
|
||||
val version = integer("version").default(1)
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
}
|
||||
@@ -13,7 +13,6 @@ object SysUserTable : Table("sys_user") {
|
||||
val nickname = varchar("nickname", 50).nullable()
|
||||
val email = varchar("email", 100).nullable()
|
||||
val avatar = text("avatar").nullable()
|
||||
val orgId = uuid("org_id").nullable()
|
||||
val status = varchar("status", 20).default("ENABLED")
|
||||
val enterpriseId = uuid("enterprise_id").nullable()
|
||||
val digitalAccountId = uuid("digital_account_id").nullable()
|
||||
|
||||
@@ -17,8 +17,8 @@ fun menuTypeLabel(type: String): String = when (type) {
|
||||
|
||||
fun dataScopeLabel(scope: String): String = when (scope) {
|
||||
"ALL" -> "全部数据"
|
||||
"DEPT" -> "本组织及下级"
|
||||
"DEPT_ONLY" -> "本组织"
|
||||
"DEPT" -> "本企业"
|
||||
"DEPT_ONLY" -> "本企业"
|
||||
"SELF" -> "仅本人"
|
||||
else -> scope
|
||||
}
|
||||
|
||||
@@ -8,11 +8,8 @@ enum class ErrorCode(val code: String, val message: String) {
|
||||
USERNAME_OR_PASSWORD_INVALID("AUTH.USERNAME_OR_PASSWORD_INVALID", "用户名或密码错误"),
|
||||
USER_DISABLED("AUTH.USER_DISABLED", "用户已禁用"),
|
||||
USER_NOT_FOUND("SYSTEM.USER_NOT_FOUND", "用户不存在"),
|
||||
ORG_NOT_FOUND("SYSTEM.ORG_NOT_FOUND", "组织不存在"),
|
||||
ROLE_NOT_FOUND("SYSTEM.ROLE_NOT_FOUND", "角色不存在"),
|
||||
MENU_NOT_FOUND("SYSTEM.MENU_NOT_FOUND", "菜单不存在"),
|
||||
DICT_TYPE_NOT_FOUND("SYSTEM.DICT_TYPE_NOT_FOUND", "字典类型不存在"),
|
||||
DICT_ITEM_NOT_FOUND("SYSTEM.DICT_ITEM_NOT_FOUND", "字典项不存在"),
|
||||
TOKEN_VERSION_INVALID("AUTH.TOKEN_VERSION_INVALID", "登录状态已失效,请重新登录"),
|
||||
INTERNAL_SERVER_ERROR("COMMON.INTERNAL_SERVER_ERROR", "服务器内部错误"),
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ data class CurrentUserProfile(
|
||||
val realName: String? = null,
|
||||
val phone: String? = null,
|
||||
val email: String? = null,
|
||||
val orgId: String? = null,
|
||||
val enterpriseId: String? = null,
|
||||
val digitalAccountId: String? = null,
|
||||
val userType: String = "SYSTEM",
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
package com.bbit.ticket.entity.common.system
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class DictTypeItem(
|
||||
val id: String,
|
||||
val code: String,
|
||||
val name: String,
|
||||
val status: String,
|
||||
val statusLabel: String,
|
||||
val remark: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class DictItem(
|
||||
val id: String,
|
||||
val typeId: String,
|
||||
val label: String,
|
||||
val value: String,
|
||||
val color: String? = null,
|
||||
val sort: Int,
|
||||
val status: String,
|
||||
val statusLabel: String,
|
||||
val remark: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class CreateDictTypeRequest(val code: String, val name: String, val status: String = "ENABLED", val remark: String? = null)
|
||||
|
||||
@Serializable
|
||||
data class UpdateDictTypeRequest(val name: String, val status: String = "ENABLED", val remark: String? = null)
|
||||
|
||||
@Serializable
|
||||
data class CreateDictItemRequest(
|
||||
val typeId: String,
|
||||
val label: String,
|
||||
val value: String,
|
||||
val color: String? = null,
|
||||
val sort: Int = 0,
|
||||
val status: String = "ENABLED",
|
||||
val remark: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class UpdateDictItemRequest(
|
||||
val typeId: String,
|
||||
val label: String,
|
||||
val value: String,
|
||||
val color: String? = null,
|
||||
val sort: Int = 0,
|
||||
val status: String = "ENABLED",
|
||||
val remark: String? = null,
|
||||
)
|
||||
@@ -15,18 +15,3 @@ data class OperationLogItem(
|
||||
val costMs: Long,
|
||||
val createdAt: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ApiAccessLogItem(
|
||||
val id: String,
|
||||
val traceId: String? = null,
|
||||
val appKey: String? = null,
|
||||
val appName: String? = null,
|
||||
val httpMethod: String,
|
||||
val requestPath: String,
|
||||
val responseCode: String? = null,
|
||||
val status: String,
|
||||
val errorMessage: String? = null,
|
||||
val costMs: Long,
|
||||
val createdAt: String,
|
||||
)
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package com.bbit.ticket.entity.common.system
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class OrgTreeNode(
|
||||
val id: String,
|
||||
val parentId: String? = null,
|
||||
val name: String,
|
||||
val code: String,
|
||||
val sort: Int,
|
||||
val status: String,
|
||||
val statusLabel: String,
|
||||
val children: List<OrgTreeNode> = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class CreateOrgRequest(
|
||||
val parentId: String? = null,
|
||||
val name: String,
|
||||
val code: String,
|
||||
val sort: Int = 0,
|
||||
val status: String = "ENABLED",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class UpdateOrgRequest(
|
||||
val parentId: String? = null,
|
||||
val name: String,
|
||||
val sort: Int = 0,
|
||||
val status: String = "ENABLED",
|
||||
)
|
||||
@@ -7,7 +7,9 @@ data class UserListItem(
|
||||
val username: String,
|
||||
val nickname: String? = null,
|
||||
val realName: String? = null,
|
||||
val orgId: String? = null,
|
||||
val enterpriseId: String? = null,
|
||||
val enterpriseName: String? = null,
|
||||
val taxpayerNum: String? = null,
|
||||
val status: String,
|
||||
val statusLabel: String,
|
||||
val roleCodes: List<String>,
|
||||
@@ -22,9 +24,9 @@ data class UserDetailResponse(
|
||||
val phone: String? = null,
|
||||
val email: String? = null,
|
||||
val avatar: String? = null,
|
||||
val orgId: String? = null,
|
||||
val orgName: String? = null,
|
||||
val orgCode: String? = null,
|
||||
val enterpriseId: String? = null,
|
||||
val enterpriseName: String? = null,
|
||||
val taxpayerNum: String? = null,
|
||||
val status: String,
|
||||
val statusLabel: String,
|
||||
val roleIds: List<String>,
|
||||
@@ -57,7 +59,7 @@ data class CreateUserRequest(
|
||||
val phone: String? = null,
|
||||
val email: String? = null,
|
||||
val avatar: String? = null,
|
||||
val orgId: String? = null,
|
||||
val enterpriseId: String? = null,
|
||||
val status: String = "ENABLED",
|
||||
)
|
||||
|
||||
@@ -68,7 +70,7 @@ data class UpdateUserRequest(
|
||||
val phone: String? = null,
|
||||
val email: String? = null,
|
||||
val avatar: String? = null,
|
||||
val orgId: String? = null,
|
||||
val enterpriseId: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
||||
@@ -61,3 +61,24 @@ data class OpenApiStatisticsItem(
|
||||
val avgCostMs: Long,
|
||||
val lastCalledAt: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class EnterpriseManageListItem(
|
||||
val id: String,
|
||||
val taxpayerNum: String,
|
||||
val enterpriseName: String,
|
||||
val legalPersonName: String? = null,
|
||||
val contactsName: String? = null,
|
||||
val contactsEmail: String? = null,
|
||||
val contactsPhone: String? = null,
|
||||
val regionCode: String? = null,
|
||||
val cityName: String? = null,
|
||||
val enterpriseAddress: String? = null,
|
||||
val reviewStatus: String? = null,
|
||||
val reviewOpinion: String? = null,
|
||||
val invoiceKind: String? = null,
|
||||
val invoiceLayoutFileType: String? = null,
|
||||
val serviceStatus: String? = null,
|
||||
val createdAt: String,
|
||||
val updatedAt: String? = null,
|
||||
)
|
||||
|
||||
@@ -4,6 +4,8 @@ import com.bbit.ticket.entity.common.BizException
|
||||
import com.bbit.ticket.entity.common.PTException
|
||||
import com.bbit.ticket.entity.common.fail
|
||||
import com.bbit.ticket.entity.common.ok
|
||||
import com.bbit.ticket.service.system.OperationLogService
|
||||
import com.bbit.ticket.utils.CurrentUser
|
||||
import com.bbit.ticket.utils.plugins.myJson
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.HttpHeaders
|
||||
@@ -13,6 +15,7 @@ import io.ktor.server.response.header
|
||||
import io.ktor.server.response.respondBytes
|
||||
import io.ktor.server.response.respondText
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlin.time.TimeSource
|
||||
|
||||
/**
|
||||
* 使用统一票通响应格式执行接口逻辑。
|
||||
@@ -35,6 +38,57 @@ suspend inline fun <reified T> ApplicationCall.respondPt(
|
||||
}
|
||||
}
|
||||
|
||||
suspend inline fun <reified T> ApplicationCall.respondPtWithOperationLog(
|
||||
fallbackMessage: String,
|
||||
currentUser: CurrentUser,
|
||||
operationType: String,
|
||||
operationName: String,
|
||||
crossinline block: suspend () -> T,
|
||||
) {
|
||||
val start = TimeSource.Monotonic.markNow()
|
||||
try {
|
||||
respondJson(ok(block()))
|
||||
OperationLogService.success(
|
||||
this,
|
||||
currentUser,
|
||||
operationType,
|
||||
operationName,
|
||||
start.elapsedNow().inWholeMilliseconds,
|
||||
)
|
||||
} catch (e: PTException) {
|
||||
respondJson(fail(code = e.code, message = e.message, traceId = e.serialNo))
|
||||
OperationLogService.fail(
|
||||
this,
|
||||
currentUser,
|
||||
operationType,
|
||||
operationName,
|
||||
e.message,
|
||||
start.elapsedNow().inWholeMilliseconds,
|
||||
)
|
||||
} catch (e: BizException) {
|
||||
respondJson(fail(code = e.errorCode, message = e.message), e.status)
|
||||
OperationLogService.fail(
|
||||
this,
|
||||
currentUser,
|
||||
operationType,
|
||||
operationName,
|
||||
e.message,
|
||||
start.elapsedNow().inWholeMilliseconds,
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
val message = e.message ?: fallbackMessage
|
||||
respondJson(fail(code = "-1", message = message))
|
||||
OperationLogService.fail(
|
||||
this,
|
||||
currentUser,
|
||||
operationType,
|
||||
operationName,
|
||||
message,
|
||||
start.elapsedNow().inWholeMilliseconds,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用统一票通响应格式执行可空查询,空结果按空对象返回。
|
||||
*
|
||||
|
||||
+6
-4
@@ -31,9 +31,10 @@ fun Route.registerOpenInvoiceTaskManageRoutes() {
|
||||
post("/openapi/tasks/queues/{digitalAccountId}/pause") {
|
||||
val digitalAccountId = call.parameters["digitalAccountId"].orEmpty()
|
||||
val body = call.receiveNullable<Map<String, String>>() ?: emptyMap()
|
||||
call.respondPt("暂停 OpenAPI 队列失败") {
|
||||
val currentUser = call.requireCurrentUser()
|
||||
call.respondPtWithOperationLog("暂停 OpenAPI 队列失败", currentUser, "PAUSE_QUEUE", "暂停 OpenAPI 队列") {
|
||||
OpenInvoiceTaskService.pauseQueue(
|
||||
user = call.requireCurrentUser(),
|
||||
user = currentUser,
|
||||
digitalAccountId = digitalAccountId,
|
||||
reason = body["reason"],
|
||||
)
|
||||
@@ -42,8 +43,9 @@ fun Route.registerOpenInvoiceTaskManageRoutes() {
|
||||
|
||||
post("/openapi/tasks/queues/{digitalAccountId}/resume") {
|
||||
val digitalAccountId = call.parameters["digitalAccountId"].orEmpty()
|
||||
call.respondPt("恢复 OpenAPI 队列失败") {
|
||||
OpenInvoiceTaskService.resumeQueue(call.requireCurrentUser(), digitalAccountId)
|
||||
val currentUser = call.requireCurrentUser()
|
||||
call.respondPtWithOperationLog("恢复 OpenAPI 队列失败", currentUser, "RESUME_QUEUE", "恢复 OpenAPI 队列") {
|
||||
OpenInvoiceTaskService.resumeQueue(currentUser, digitalAccountId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,8 +45,9 @@ fun Route.registerPTAuthRoutes() {
|
||||
}
|
||||
|
||||
post("/enterprise/refresh") {
|
||||
call.respondPt("刷新企业信息失败") {
|
||||
PTConfigService.refreshEnterpriseInfo(call.requireCurrentUser())
|
||||
val currentUser = call.requireCurrentUser()
|
||||
call.respondPtWithOperationLog("刷新企业信息失败", currentUser, "REFRESH", "刷新企业信息") {
|
||||
PTConfigService.refreshEnterpriseInfo(currentUser)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,22 +64,25 @@ fun Route.registerPTAuthRoutes() {
|
||||
}
|
||||
|
||||
post("/digital-accounts/refresh") {
|
||||
call.respondPt("刷新数电账号失败") {
|
||||
PTConfigService.refreshDigitalAccounts(call.requireCurrentUser())
|
||||
val currentUser = call.requireCurrentUser()
|
||||
call.respondPtWithOperationLog("刷新数电账号失败", currentUser, "REFRESH", "刷新数电账号") {
|
||||
PTConfigService.refreshDigitalAccounts(currentUser)
|
||||
}
|
||||
}
|
||||
|
||||
post("/digital-accounts") {
|
||||
call.respondPt("新增数电账号失败") {
|
||||
PTConfigService.createDigitalAccount(call.requireCurrentUser(), call.receive<CreateDigitalAccountRequest>())
|
||||
val currentUser = call.requireCurrentUser()
|
||||
call.respondPtWithOperationLog("新增数电账号失败", currentUser, "CREATE", "新增数电账号") {
|
||||
PTConfigService.createDigitalAccount(currentUser, call.receive<CreateDigitalAccountRequest>())
|
||||
}
|
||||
}
|
||||
|
||||
put("/digital-accounts/{id}/status") {
|
||||
call.respondPt("更新数电账号状态失败") {
|
||||
val currentUser = call.requireCurrentUser()
|
||||
call.respondPtWithOperationLog("更新数电账号状态失败", currentUser, "UPDATE_STATUS", "更新数电账号状态") {
|
||||
val id = call.parameters["id"] ?: throw IllegalArgumentException("缺少数电账号ID")
|
||||
PTConfigService.updateDigitalAccountStatus(
|
||||
call.requireCurrentUser(),
|
||||
currentUser,
|
||||
id,
|
||||
call.receive<UpdateDigitalAccountStatusRequest>(),
|
||||
)
|
||||
@@ -92,8 +96,9 @@ fun Route.registerPTAuthRoutes() {
|
||||
}
|
||||
|
||||
put("/preset") {
|
||||
call.respondPt("保存预设数据失败") {
|
||||
PTConfigService.updateInvoiceSetting(call.requireCurrentUser(), call.receive<UpdateInvoiceSettingRequest>())
|
||||
val currentUser = call.requireCurrentUser()
|
||||
call.respondPtWithOperationLog("保存预设数据失败", currentUser, "UPDATE", "保存开票预设数据") {
|
||||
PTConfigService.updateInvoiceSetting(currentUser, call.receive<UpdateInvoiceSettingRequest>())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,9 +109,10 @@ fun Route.registerPTAuthRoutes() {
|
||||
}
|
||||
|
||||
get("/authentication") {
|
||||
call.respondPt("获取认证二维码失败") {
|
||||
val currentUser = call.requireCurrentUser()
|
||||
call.respondPtWithOperationLog("获取认证二维码失败", currentUser, "AUTH_QRCODE", "获取认证二维码") {
|
||||
val account = PTConfigService.requireDigitalAccountForAction(
|
||||
call.requireCurrentUser(),
|
||||
currentUser,
|
||||
call.request.queryParameters["digitalAccountId"],
|
||||
)
|
||||
PTAuthService.getAuthenticationQrcode(
|
||||
@@ -120,23 +126,26 @@ fun Route.registerPTAuthRoutes() {
|
||||
}
|
||||
|
||||
post("/query-auth-status") {
|
||||
call.respondPt("查询认证二维码扫码状态失败") {
|
||||
val currentUser = call.requireCurrentUser()
|
||||
call.respondPtWithOperationLog("查询认证二维码扫码状态失败", currentUser, "AUTH_STATUS", "查询认证扫码状态") {
|
||||
PTAuthService.queryAuthQrcodeScanStatus(call.receive<QueryRealNameAuthQrStatusRequest>())
|
||||
}
|
||||
}
|
||||
|
||||
post("/send-sms-code") {
|
||||
call.respondPt("发送登录短信验证码失败") {
|
||||
val currentUser = call.requireCurrentUser()
|
||||
call.respondPtWithOperationLog("发送登录短信验证码失败", currentUser, "SEND_SMS_CODE", "发送登录短信验证码") {
|
||||
val req = call.receive<GetLoginSmsCodeRequest>()
|
||||
PTConfigService.requireDigitalAccountForLogin(call.requireCurrentUser(), req.taxpayerNum, req.account)
|
||||
PTConfigService.requireDigitalAccountForLogin(currentUser, req.taxpayerNum, req.account)
|
||||
PTAuthService.sendLoginSmsCode(req)
|
||||
}
|
||||
}
|
||||
|
||||
post("/sms-login") {
|
||||
call.respondPt("短信验证码登录失败") {
|
||||
val currentUser = call.requireCurrentUser()
|
||||
call.respondPtWithOperationLog("短信验证码登录失败", currentUser, "SMS_LOGIN", "短信验证码登录") {
|
||||
val req = call.receive<SmsLoginRequest>()
|
||||
PTConfigService.requireDigitalAccountForLogin(call.requireCurrentUser(), req.taxpayerNum, req.account)
|
||||
PTConfigService.requireDigitalAccountForLogin(currentUser, req.taxpayerNum, req.account)
|
||||
PTAuthService.smsLogin(req)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,14 +21,16 @@ import kotlin.uuid.ExperimentalUuidApi
|
||||
*/
|
||||
fun Route.registerPTInvoiceRoutes() {
|
||||
post("/invoiceRed") {
|
||||
call.respondPt("红字任务创建失败") {
|
||||
PTRedService.invoiceRed(call.requireCurrentUser(), call.receive<RedCreateRequest>())
|
||||
val currentUser = call.requireCurrentUser()
|
||||
call.respondPtWithOperationLog("红字任务创建失败", currentUser, "CREATE_RED_INVOICE", "创建红字任务") {
|
||||
PTRedService.invoiceRed(currentUser, call.receive<RedCreateRequest>())
|
||||
}
|
||||
}
|
||||
|
||||
post("/invoiceBlue") {
|
||||
call.respondPt("蓝票任务创建失败") {
|
||||
PTBlueService.invoiceBlue(call.receive<AskInvoiceRequest>(), call.requireCurrentUser())
|
||||
val currentUser = call.requireCurrentUser()
|
||||
call.respondPtWithOperationLog("蓝票任务创建失败", currentUser, "CREATE_BLUE_INVOICE", "创建蓝票任务") {
|
||||
PTBlueService.invoiceBlue(call.receive<AskInvoiceRequest>(), currentUser)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,8 +102,9 @@ fun Route.registerPTInvoiceRoutes() {
|
||||
get("/queryInvoice") {
|
||||
val invoiceReqSerialNo = call.requiredQueryParameter("invoiceReqSerialNo", "请传入发票请求流水号")
|
||||
?: return@get
|
||||
call.respondPt("刷新发票状态失败") {
|
||||
PTBlueService.queryInvoiceAllInfo(call.requireCurrentUser(), invoiceReqSerialNo)
|
||||
val currentUser = call.requireCurrentUser()
|
||||
call.respondPtWithOperationLog("刷新发票状态失败", currentUser, "REFRESH_INVOICE_STATUS", "刷新发票状态") {
|
||||
PTBlueService.queryInvoiceAllInfo(currentUser, invoiceReqSerialNo)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
@file:OptIn(ExperimentalUuidApi::class)
|
||||
|
||||
package com.bbit.ticket.route.system
|
||||
|
||||
import com.bbit.ticket.entity.common.ok
|
||||
import com.bbit.ticket.utils.parseUuid
|
||||
import com.bbit.ticket.utils.queryInt
|
||||
import com.bbit.ticket.utils.queryString
|
||||
import com.bbit.ticket.entity.common.system.CreateDictItemRequest
|
||||
import com.bbit.ticket.entity.common.system.CreateDictTypeRequest
|
||||
import com.bbit.ticket.entity.common.system.UpdateDictItemRequest
|
||||
import com.bbit.ticket.entity.common.system.UpdateDictTypeRequest
|
||||
import com.bbit.ticket.service.system.OperationLogService
|
||||
import com.bbit.ticket.utils.requirePermission
|
||||
import com.bbit.ticket.service.system.DictService
|
||||
import io.ktor.server.auth.authenticate
|
||||
import io.ktor.server.request.receive
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.Route
|
||||
import io.ktor.server.routing.delete
|
||||
import io.ktor.server.routing.get
|
||||
import io.ktor.server.routing.post
|
||||
import io.ktor.server.routing.put
|
||||
import io.ktor.server.routing.route
|
||||
import kotlin.time.TimeSource
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
|
||||
fun Route.registerDictRoutes() {
|
||||
authenticate("auth-jwt") {
|
||||
route("/system/dict-types") {
|
||||
get {
|
||||
call.requirePermission("system:dict:view")
|
||||
val page = call.queryInt("page", 1)
|
||||
val pageSize = call.queryInt("pageSize", 20)
|
||||
call.respond(ok(DictService.listTypes(page, pageSize, call.queryString("keyword"))))
|
||||
}
|
||||
post {
|
||||
val start = TimeSource.Monotonic.markNow()
|
||||
val currentUser = call.requirePermission("system:dict:create")
|
||||
val request = call.receive<CreateDictTypeRequest>()
|
||||
runCatching {
|
||||
val id = DictService.createType(request)
|
||||
call.respond(ok(mapOf("id" to id)))
|
||||
OperationLogService.success(call, currentUser, "CREATE", "新增字典类型", start.elapsedNow().inWholeMilliseconds)
|
||||
}.onFailure {
|
||||
OperationLogService.fail(call, currentUser, "CREATE", "新增字典类型", it.message, start.elapsedNow().inWholeMilliseconds)
|
||||
throw it
|
||||
}
|
||||
}
|
||||
put("/{id}") {
|
||||
val start = TimeSource.Monotonic.markNow()
|
||||
val currentUser = call.requirePermission("system:dict:update")
|
||||
val id = parseUuid(call.parameters["id"] ?: "", "id")
|
||||
val request = call.receive<UpdateDictTypeRequest>()
|
||||
runCatching {
|
||||
DictService.updateType(id, request)
|
||||
call.respond(ok<Unit>(message = "更新成功"))
|
||||
OperationLogService.success(call, currentUser, "UPDATE", "更新字典类型", start.elapsedNow().inWholeMilliseconds)
|
||||
}.onFailure {
|
||||
OperationLogService.fail(call, currentUser, "UPDATE", "更新字典类型", it.message, start.elapsedNow().inWholeMilliseconds)
|
||||
throw it
|
||||
}
|
||||
}
|
||||
delete("/{id}") {
|
||||
val start = TimeSource.Monotonic.markNow()
|
||||
val currentUser = call.requirePermission("system:dict:delete")
|
||||
val id = parseUuid(call.parameters["id"] ?: "", "id")
|
||||
runCatching {
|
||||
DictService.deleteType(id)
|
||||
call.respond(ok<Unit>(message = "删除成功"))
|
||||
OperationLogService.success(call, currentUser, "DELETE", "删除字典类型", start.elapsedNow().inWholeMilliseconds)
|
||||
}.onFailure {
|
||||
OperationLogService.fail(call, currentUser, "DELETE", "删除字典类型", it.message, start.elapsedNow().inWholeMilliseconds)
|
||||
throw it
|
||||
}
|
||||
}
|
||||
}
|
||||
route("/system/dict-items") {
|
||||
get {
|
||||
call.requirePermission("system:dict:view")
|
||||
val page = call.queryInt("page", 1)
|
||||
val pageSize = call.queryInt("pageSize", 20)
|
||||
val typeId = call.queryString("typeId")?.let { parseUuid(it, "typeId") }
|
||||
call.respond(ok(DictService.listItems(page, pageSize, typeId)))
|
||||
}
|
||||
post {
|
||||
val start = TimeSource.Monotonic.markNow()
|
||||
val currentUser = call.requirePermission("system:dict:create")
|
||||
val request = call.receive<CreateDictItemRequest>()
|
||||
runCatching {
|
||||
val id = DictService.createItem(request)
|
||||
call.respond(ok(mapOf("id" to id)))
|
||||
OperationLogService.success(call, currentUser, "CREATE", "新增字典项", start.elapsedNow().inWholeMilliseconds)
|
||||
}.onFailure {
|
||||
OperationLogService.fail(call, currentUser, "CREATE", "新增字典项", it.message, start.elapsedNow().inWholeMilliseconds)
|
||||
throw it
|
||||
}
|
||||
}
|
||||
put("/{id}") {
|
||||
val start = TimeSource.Monotonic.markNow()
|
||||
val currentUser = call.requirePermission("system:dict:update")
|
||||
val id = parseUuid(call.parameters["id"] ?: "", "id")
|
||||
val request = call.receive<UpdateDictItemRequest>()
|
||||
runCatching {
|
||||
DictService.updateItem(id, request)
|
||||
call.respond(ok<Unit>(message = "更新成功"))
|
||||
OperationLogService.success(call, currentUser, "UPDATE", "更新字典项", start.elapsedNow().inWholeMilliseconds)
|
||||
}.onFailure {
|
||||
OperationLogService.fail(call, currentUser, "UPDATE", "更新字典项", it.message, start.elapsedNow().inWholeMilliseconds)
|
||||
throw it
|
||||
}
|
||||
}
|
||||
delete("/{id}") {
|
||||
val start = TimeSource.Monotonic.markNow()
|
||||
val currentUser = call.requirePermission("system:dict:delete")
|
||||
val id = parseUuid(call.parameters["id"] ?: "", "id")
|
||||
runCatching {
|
||||
DictService.deleteItem(id)
|
||||
call.respond(ok<Unit>(message = "删除成功"))
|
||||
OperationLogService.success(call, currentUser, "DELETE", "删除字典项", start.elapsedNow().inWholeMilliseconds)
|
||||
}.onFailure {
|
||||
OperationLogService.fail(call, currentUser, "DELETE", "删除字典项", it.message, start.elapsedNow().inWholeMilliseconds)
|
||||
throw it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.bbit.ticket.route.system
|
||||
|
||||
import com.bbit.ticket.entity.common.ok
|
||||
import com.bbit.ticket.service.system.EnterpriseManageService
|
||||
import com.bbit.ticket.utils.queryInt
|
||||
import com.bbit.ticket.utils.queryString
|
||||
import com.bbit.ticket.utils.requirePermission
|
||||
import io.ktor.server.auth.authenticate
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.Route
|
||||
import io.ktor.server.routing.get
|
||||
import io.ktor.server.routing.route
|
||||
|
||||
fun Route.registerEnterpriseManageRoutes() {
|
||||
authenticate("auth-jwt") {
|
||||
route("/system/enterprises") {
|
||||
get {
|
||||
call.requirePermission("system:enterprise:view")
|
||||
call.respond(
|
||||
ok(
|
||||
EnterpriseManageService.list(
|
||||
keyword = call.queryString("keyword"),
|
||||
page = call.queryInt("page", 1),
|
||||
pageSize = call.queryInt("pageSize", 20),
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,12 +20,6 @@ fun Route.registerLogsQueryRoutes() {
|
||||
val pageSize = call.queryInt("pageSize", 20)
|
||||
call.respond(ok(LogsQueryService.operationLogs(page, pageSize, call.queryString("keyword"), call.queryString("status"))))
|
||||
}
|
||||
get("/api-access") {
|
||||
call.requirePermission("log:api-access:view")
|
||||
val page = call.queryInt("page", 1)
|
||||
val pageSize = call.queryInt("pageSize", 20)
|
||||
call.respond(ok(LogsQueryService.apiAccessLogs(page, pageSize, call.queryString("keyword"), call.queryString("status"))))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
@file:OptIn(ExperimentalUuidApi::class)
|
||||
|
||||
package com.bbit.ticket.route.system
|
||||
|
||||
import com.bbit.ticket.entity.common.ok
|
||||
import com.bbit.ticket.entity.common.system.CreateOrgRequest
|
||||
import com.bbit.ticket.entity.common.system.UpdateOrgRequest
|
||||
import com.bbit.ticket.utils.parseUuid
|
||||
import com.bbit.ticket.utils.requirePermission
|
||||
import com.bbit.ticket.service.system.OperationLogService
|
||||
import com.bbit.ticket.service.system.OrgService
|
||||
import io.ktor.server.auth.authenticate
|
||||
import io.ktor.server.request.receive
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.Route
|
||||
import io.ktor.server.routing.delete
|
||||
import io.ktor.server.routing.get
|
||||
import io.ktor.server.routing.post
|
||||
import io.ktor.server.routing.put
|
||||
import io.ktor.server.routing.route
|
||||
import kotlin.time.TimeSource
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
|
||||
fun Route.registerOrgRoutes() {
|
||||
authenticate("auth-jwt") {
|
||||
route("/system/orgs") {
|
||||
get {
|
||||
call.requirePermission("system:org:view")
|
||||
call.respond(ok(OrgService.tree()))
|
||||
}
|
||||
post {
|
||||
val start = TimeSource.Monotonic.markNow()
|
||||
val currentUser = call.requirePermission("system:org:create")
|
||||
val request = call.receive<CreateOrgRequest>()
|
||||
runCatching {
|
||||
val id = OrgService.create(request)
|
||||
call.respond(ok(mapOf("id" to id)))
|
||||
OperationLogService.success(call, currentUser, "CREATE", "新增组织", start.elapsedNow().inWholeMilliseconds)
|
||||
}.onFailure {
|
||||
OperationLogService.fail(call, currentUser, "CREATE", "新增组织", it.message, start.elapsedNow().inWholeMilliseconds)
|
||||
throw it
|
||||
}
|
||||
}
|
||||
put("/{id}") {
|
||||
val start = TimeSource.Monotonic.markNow()
|
||||
val currentUser = call.requirePermission("system:org:update")
|
||||
val id = parseUuid(call.parameters["id"] ?: "", "id")
|
||||
val request = call.receive<UpdateOrgRequest>()
|
||||
runCatching {
|
||||
OrgService.update(id, request)
|
||||
call.respond(ok<Unit>(message = "更新成功"))
|
||||
OperationLogService.success(call, currentUser, "UPDATE", "更新组织", start.elapsedNow().inWholeMilliseconds)
|
||||
}.onFailure {
|
||||
OperationLogService.fail(call, currentUser, "UPDATE", "更新组织", it.message, start.elapsedNow().inWholeMilliseconds)
|
||||
throw it
|
||||
}
|
||||
}
|
||||
delete("/{id}") {
|
||||
val start = TimeSource.Monotonic.markNow()
|
||||
val currentUser = call.requirePermission("system:org:delete")
|
||||
val id = parseUuid(call.parameters["id"] ?: "", "id")
|
||||
runCatching {
|
||||
OrgService.delete(id)
|
||||
call.respond(ok<Unit>(message = "删除成功"))
|
||||
OperationLogService.success(call, currentUser, "DELETE", "删除组织", start.elapsedNow().inWholeMilliseconds)
|
||||
}.onFailure {
|
||||
OperationLogService.fail(call, currentUser, "DELETE", "删除组织", it.message, start.elapsedNow().inWholeMilliseconds)
|
||||
throw it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ fun Route.registerUserRoutes() {
|
||||
username = call.queryString("username"),
|
||||
nickname = call.queryString("nickname"),
|
||||
status = call.queryString("status"),
|
||||
orgId = call.queryString("orgId")?.let { parseUuid(it, "orgId") },
|
||||
enterpriseId = call.queryString("enterpriseId")?.let { parseUuid(it, "enterpriseId") },
|
||||
)
|
||||
call.respond(ok(result))
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ import org.jetbrains.exposed.v1.core.notInList
|
||||
import org.jetbrains.exposed.v1.core.or
|
||||
import org.jetbrains.exposed.v1.jdbc.Query
|
||||
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
|
||||
@@ -67,6 +68,9 @@ object OpenInvoiceTaskService {
|
||||
private const val QUEUE_RUNNING = "RUNNING"
|
||||
private const val QUEUE_PAUSED = "PAUSED"
|
||||
private const val AUTH_REQUIRED_CODE = "3999"
|
||||
private const val HISTORY_FAILED_CODE = "9999"
|
||||
private const val EXCEPTION_CODE = "EXCEPTION"
|
||||
private const val STALE_EXCEPTION_LOCK_SECONDS = 300L
|
||||
|
||||
suspend fun createIssueTask(
|
||||
principal: OpenApiPrincipal,
|
||||
@@ -113,8 +117,10 @@ object OpenInvoiceTaskService {
|
||||
|
||||
ensureQueueCanAccept(principal.apiKey)
|
||||
|
||||
val taskId = dbQuery {
|
||||
OpenInvoiceTaskTable.insert {
|
||||
val createdTaskId = Uuid.random()
|
||||
val task = dbQuery {
|
||||
OpenInvoiceTaskTable.insertIgnore {
|
||||
it[id] = createdTaskId
|
||||
it[apiKey] = principal.apiKey
|
||||
it[userId] = principal.userId
|
||||
it[enterpriseId] = principal.enterpriseId
|
||||
@@ -132,13 +138,25 @@ object OpenInvoiceTaskService {
|
||||
it[maxPollCount] = com.bbit.ticket.utils.bootstrap.AppConfig.openApiQueue.maxQueryPollCount
|
||||
it[nextRunAt] = now
|
||||
it[createdAt] = now
|
||||
}[OpenInvoiceTaskTable.id]
|
||||
}
|
||||
OpenInvoiceTaskTable.selectAll()
|
||||
.where {
|
||||
(OpenInvoiceTaskTable.taskType eq TASK_ISSUE_BLUE) and
|
||||
(OpenInvoiceTaskTable.invoiceReqSerialNo eq invoiceReqSerialNo)
|
||||
}
|
||||
.single()
|
||||
}
|
||||
if (task[OpenInvoiceTaskTable.runMode] != runMode) {
|
||||
throw BizException(
|
||||
ErrorCode.BAD_REQUEST.code,
|
||||
"invoiceReqSerialNo 已存在 ${task[OpenInvoiceTaskTable.runMode]} 任务,不能重复创建 $runMode 任务",
|
||||
)
|
||||
}
|
||||
|
||||
return OpenInvoiceTaskSubmitResponse(
|
||||
taskId = taskId.toString(),
|
||||
taskId = task[OpenInvoiceTaskTable.id].toString(),
|
||||
invoiceReqSerialNo = invoiceReqSerialNo,
|
||||
status = STATUS_PENDING,
|
||||
status = task[OpenInvoiceTaskTable.status],
|
||||
taskType = TASK_ISSUE_BLUE,
|
||||
runMode = runMode,
|
||||
)
|
||||
@@ -275,7 +293,7 @@ object OpenInvoiceTaskService {
|
||||
pauseApiKey(task[OpenInvoiceTaskTable.apiKey], e.code, e.message)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
retryOrFail(task, "EXCEPTION", e.message ?: "开票任务执行失败")
|
||||
retryOrFail(task, EXCEPTION_CODE, e.message ?: "开票任务执行失败")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +326,7 @@ object OpenInvoiceTaskService {
|
||||
} catch (e: PTException) {
|
||||
retryOrFail(task, e.code, e.message)
|
||||
} catch (e: Exception) {
|
||||
retryOrFail(task, "EXCEPTION", e.message ?: "查询任务执行失败")
|
||||
retryOrFail(task, EXCEPTION_CODE, e.message ?: "查询任务执行失败")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,32 +343,24 @@ object OpenInvoiceTaskService {
|
||||
it[lockedBy] = null
|
||||
it[lockedAt] = null
|
||||
}
|
||||
val exists = OpenInvoiceTaskTable.selectAll()
|
||||
.where {
|
||||
(OpenInvoiceTaskTable.taskType eq TASK_QUERY_BLUE) and
|
||||
(OpenInvoiceTaskTable.invoiceReqSerialNo eq invoiceReqSerialNo)
|
||||
}
|
||||
.singleOrNull() != null
|
||||
if (!exists) {
|
||||
OpenInvoiceTaskTable.insert {
|
||||
it[apiKey] = task[OpenInvoiceTaskTable.apiKey]
|
||||
it[userId] = task[OpenInvoiceTaskTable.userId]
|
||||
it[enterpriseId] = task[OpenInvoiceTaskTable.enterpriseId]
|
||||
it[digitalAccountId] = task[OpenInvoiceTaskTable.digitalAccountId]
|
||||
it[taxpayerNum] = task[OpenInvoiceTaskTable.taxpayerNum]
|
||||
it[taxAccount] = task[OpenInvoiceTaskTable.taxAccount]
|
||||
it[taskType] = TASK_QUERY_BLUE
|
||||
it[sourceType] = task[OpenInvoiceTaskTable.sourceType]
|
||||
it[runMode] = task[OpenInvoiceTaskTable.runMode]
|
||||
it[OpenInvoiceTaskTable.invoiceReqSerialNo] = invoiceReqSerialNo
|
||||
it[batchNo] = task[OpenInvoiceTaskTable.batchNo]
|
||||
it[status] = STATUS_PENDING
|
||||
it[requestBody] = task[OpenInvoiceTaskTable.taxpayerNum]
|
||||
it[maxAttemptCount] = task[OpenInvoiceTaskTable.maxAttemptCount]
|
||||
it[maxPollCount] = task[OpenInvoiceTaskTable.maxPollCount]
|
||||
it[nextRunAt] = now.plusSeconds(com.bbit.ticket.utils.bootstrap.AppConfig.openApiQueue.queryDelaySeconds)
|
||||
it[createdAt] = now
|
||||
}
|
||||
OpenInvoiceTaskTable.insertIgnore {
|
||||
it[apiKey] = task[OpenInvoiceTaskTable.apiKey]
|
||||
it[userId] = task[OpenInvoiceTaskTable.userId]
|
||||
it[enterpriseId] = task[OpenInvoiceTaskTable.enterpriseId]
|
||||
it[digitalAccountId] = task[OpenInvoiceTaskTable.digitalAccountId]
|
||||
it[taxpayerNum] = task[OpenInvoiceTaskTable.taxpayerNum]
|
||||
it[taxAccount] = task[OpenInvoiceTaskTable.taxAccount]
|
||||
it[taskType] = TASK_QUERY_BLUE
|
||||
it[sourceType] = task[OpenInvoiceTaskTable.sourceType]
|
||||
it[runMode] = task[OpenInvoiceTaskTable.runMode]
|
||||
it[OpenInvoiceTaskTable.invoiceReqSerialNo] = invoiceReqSerialNo
|
||||
it[batchNo] = task[OpenInvoiceTaskTable.batchNo]
|
||||
it[status] = STATUS_PENDING
|
||||
it[requestBody] = task[OpenInvoiceTaskTable.taxpayerNum]
|
||||
it[maxAttemptCount] = task[OpenInvoiceTaskTable.maxAttemptCount]
|
||||
it[maxPollCount] = task[OpenInvoiceTaskTable.maxPollCount]
|
||||
it[nextRunAt] = now.plusSeconds(com.bbit.ticket.utils.bootstrap.AppConfig.openApiQueue.queryDelaySeconds)
|
||||
it[createdAt] = now
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -393,8 +403,8 @@ object OpenInvoiceTaskService {
|
||||
HistoryInvoiceBasicTable.update({
|
||||
HistoryInvoiceBasicTable.invoiceReqSerialNo eq task[OpenInvoiceTaskTable.invoiceReqSerialNo]
|
||||
}) {
|
||||
it[HistoryInvoiceBasicTable.code] = code
|
||||
it[msg] = message ?: if (code == "0000") "开票成功" else "开票失败"
|
||||
it[HistoryInvoiceBasicTable.code] = historyCode(code)
|
||||
it[msg] = historyMessage(message ?: if (code == "0000") "开票成功" else "开票失败")
|
||||
it[updatedAt] = now
|
||||
}
|
||||
}
|
||||
@@ -420,8 +430,8 @@ object OpenInvoiceTaskService {
|
||||
HistoryInvoiceBasicTable.update({
|
||||
HistoryInvoiceBasicTable.invoiceReqSerialNo eq task[OpenInvoiceTaskTable.invoiceReqSerialNo]
|
||||
}) {
|
||||
it[HistoryInvoiceBasicTable.code] = code
|
||||
it[msg] = if (code == "6666") "未开票" else "开票中..."
|
||||
it[HistoryInvoiceBasicTable.code] = historyCode(code)
|
||||
it[msg] = historyMessage(if (code == "6666") "未开票" else "开票中...")
|
||||
it[updatedAt] = now
|
||||
}
|
||||
}
|
||||
@@ -467,8 +477,8 @@ object OpenInvoiceTaskService {
|
||||
HistoryInvoiceBasicTable.update({
|
||||
HistoryInvoiceBasicTable.invoiceReqSerialNo eq task[OpenInvoiceTaskTable.invoiceReqSerialNo]
|
||||
}) {
|
||||
it[HistoryInvoiceBasicTable.code] = code
|
||||
it[msg] = message
|
||||
it[HistoryInvoiceBasicTable.code] = historyCode(code)
|
||||
it[msg] = historyMessage(message)
|
||||
it[updatedAt] = now
|
||||
}
|
||||
}
|
||||
@@ -477,13 +487,19 @@ object OpenInvoiceTaskService {
|
||||
|
||||
private suspend fun claimTasks(taskType: String, workerId: String, limit: Int): List<ResultRow> {
|
||||
val now = OffsetDateTime.now()
|
||||
val staleLockAt = now.minusSeconds(STALE_EXCEPTION_LOCK_SECONDS)
|
||||
val candidateIds = dbQuery {
|
||||
val pausedApiKeys = OpenInvoiceQueueControlTable.selectAll()
|
||||
.where { OpenInvoiceQueueControlTable.status eq QUEUE_PAUSED }
|
||||
.map { it[OpenInvoiceQueueControlTable.apiKey] }
|
||||
var where = (OpenInvoiceTaskTable.taskType eq taskType) and
|
||||
(OpenInvoiceTaskTable.status eq STATUS_PENDING) and
|
||||
(OpenInvoiceTaskTable.nextRunAt lessEq now)
|
||||
var where = (OpenInvoiceTaskTable.taskType eq taskType) and (
|
||||
((OpenInvoiceTaskTable.status eq STATUS_PENDING) and (OpenInvoiceTaskTable.nextRunAt lessEq now)) or
|
||||
(
|
||||
(OpenInvoiceTaskTable.status eq STATUS_PROCESSING) and
|
||||
(OpenInvoiceTaskTable.ptCode eq EXCEPTION_CODE) and
|
||||
(OpenInvoiceTaskTable.lockedAt lessEq staleLockAt)
|
||||
)
|
||||
)
|
||||
if (pausedApiKeys.isNotEmpty()) {
|
||||
where = where and (OpenInvoiceTaskTable.apiKey notInList pausedApiKeys)
|
||||
}
|
||||
@@ -499,7 +515,14 @@ object OpenInvoiceTaskService {
|
||||
return dbQuery {
|
||||
candidateIds.mapNotNull { id ->
|
||||
val updated = OpenInvoiceTaskTable.update({
|
||||
(OpenInvoiceTaskTable.id eq id) and (OpenInvoiceTaskTable.status eq STATUS_PENDING)
|
||||
(OpenInvoiceTaskTable.id eq id) and (
|
||||
(OpenInvoiceTaskTable.status eq STATUS_PENDING) or
|
||||
(
|
||||
(OpenInvoiceTaskTable.status eq STATUS_PROCESSING) and
|
||||
(OpenInvoiceTaskTable.ptCode eq EXCEPTION_CODE) and
|
||||
(OpenInvoiceTaskTable.lockedAt lessEq staleLockAt)
|
||||
)
|
||||
)
|
||||
}) {
|
||||
it[status] = STATUS_PROCESSING
|
||||
it[lockedBy] = workerId
|
||||
@@ -517,24 +540,19 @@ object OpenInvoiceTaskService {
|
||||
}
|
||||
|
||||
private suspend fun createHistoryPlaceholder(task: ResultRow, request: AskInvoiceRequest) = dbQuery {
|
||||
val exists = HistoryInvoiceBasicTable.selectAll()
|
||||
.where { HistoryInvoiceBasicTable.invoiceReqSerialNo eq request.invoiceReqSerialNo }
|
||||
.singleOrNull() != null
|
||||
if (!exists) {
|
||||
val now = OffsetDateTime.now()
|
||||
HistoryInvoiceBasicTable.insert {
|
||||
it[userId] = task[OpenInvoiceTaskTable.userId]
|
||||
it[enterpriseId] = task[OpenInvoiceTaskTable.enterpriseId]
|
||||
it[digitalAccountId] = task[OpenInvoiceTaskTable.digitalAccountId]
|
||||
it[invoiceReqSerialNo] = request.invoiceReqSerialNo
|
||||
it[code] = "7777"
|
||||
it[msg] = "开票中..."
|
||||
it[sellerTaxpayerNum] = request.taxpayerNum
|
||||
it[invoiceKind] = request.invoiceIssueKindCode
|
||||
it[invoiceType] = "1"
|
||||
it[invDeletedFlag] = "0"
|
||||
it[createdAt] = now
|
||||
}
|
||||
val now = OffsetDateTime.now()
|
||||
HistoryInvoiceBasicTable.insertIgnore {
|
||||
it[userId] = task[OpenInvoiceTaskTable.userId]
|
||||
it[enterpriseId] = task[OpenInvoiceTaskTable.enterpriseId]
|
||||
it[digitalAccountId] = task[OpenInvoiceTaskTable.digitalAccountId]
|
||||
it[invoiceReqSerialNo] = request.invoiceReqSerialNo
|
||||
it[code] = "7777"
|
||||
it[msg] = "开票中..."
|
||||
it[sellerTaxpayerNum] = request.taxpayerNum
|
||||
it[invoiceKind] = request.invoiceIssueKindCode
|
||||
it[invoiceType] = "1"
|
||||
it[invDeletedFlag] = "0"
|
||||
it[createdAt] = now
|
||||
}
|
||||
}
|
||||
|
||||
@@ -635,6 +653,12 @@ object OpenInvoiceTaskService {
|
||||
else -> "0000"
|
||||
}
|
||||
|
||||
private fun historyCode(code: String): String =
|
||||
if (code.length <= 8) code else HISTORY_FAILED_CODE
|
||||
|
||||
private fun historyMessage(message: String): String =
|
||||
message.take(200)
|
||||
|
||||
private fun scopedTaskQuery(user: CurrentUser): Query =
|
||||
OpenInvoiceTaskTable.selectAll().where { taskScope(user) }
|
||||
|
||||
|
||||
@@ -53,7 +53,6 @@ object AuthService {
|
||||
val (accessToken, expiresIn) = JwtService.issueAccessToken(
|
||||
userId = userId.toString(),
|
||||
username = user[com.bbit.ticket.database.system.SysUserTable.username],
|
||||
orgId = user[com.bbit.ticket.database.system.SysUserTable.orgId]?.toString(),
|
||||
roles = roleCodes,
|
||||
tokenVersion = user[com.bbit.ticket.database.system.SysUserTable.tokenVersion],
|
||||
)
|
||||
@@ -127,7 +126,6 @@ object AuthService {
|
||||
realName = userRow[SysUserTable.realName],
|
||||
phone = userRow[SysUserTable.phone],
|
||||
email = userRow[SysUserTable.email],
|
||||
orgId = userRow[SysUserTable.orgId]?.toString(),
|
||||
enterpriseId = userRow[SysUserTable.enterpriseId]?.toString(),
|
||||
digitalAccountId = userRow[SysUserTable.digitalAccountId]?.toString(),
|
||||
userType = userRow[SysUserTable.userType],
|
||||
@@ -140,10 +138,6 @@ object AuthService {
|
||||
}
|
||||
|
||||
private suspend fun loadMenusForUser(currentUser: CurrentUser) = dbQuery {
|
||||
if (currentUser.isSuperAdmin) {
|
||||
MenuDao.enabledMenusForSuperAdmin()
|
||||
} else {
|
||||
MenuDao.enabledMenusForRoleIds(UserDao.findEnabledRoleIds(currentUser.id))
|
||||
}
|
||||
MenuDao.enabledMenusForRoleIds(UserDao.findEnabledRoleIds(currentUser.id))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
@file:OptIn(ExperimentalUuidApi::class)
|
||||
|
||||
package com.bbit.ticket.service.system
|
||||
|
||||
import com.bbit.ticket.dao.system.DictDao
|
||||
import com.bbit.ticket.entity.common.system.CreateDictItemRequest
|
||||
import com.bbit.ticket.entity.common.system.CreateDictTypeRequest
|
||||
import com.bbit.ticket.entity.common.system.DictItem
|
||||
import com.bbit.ticket.entity.common.system.DictTypeItem
|
||||
import com.bbit.ticket.entity.common.system.UpdateDictItemRequest
|
||||
import com.bbit.ticket.entity.common.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.utils.plugins.dbQuery
|
||||
import com.bbit.ticket.utils.parseUuid
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
object DictService {
|
||||
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, "字典类型编码和名称不能为空")
|
||||
}
|
||||
if (DictDao.typeCodeExists(request.code.trim())) {
|
||||
throw BizException(ErrorCode.DATA_CONFLICT.code, "字典类型编码已存在")
|
||||
}
|
||||
DictDao.createType(request)
|
||||
}
|
||||
|
||||
suspend fun updateType(id: Uuid, request: UpdateDictTypeRequest) = dbQuery {
|
||||
DictDao.requireType(id)
|
||||
DictDao.updateType(id, request)
|
||||
}
|
||||
|
||||
suspend fun deleteType(id: Uuid) = dbQuery {
|
||||
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 { DictDao.listItems(page, pageSize, typeId) }
|
||||
|
||||
suspend fun createItem(request: CreateDictItemRequest): String = dbQuery {
|
||||
val typeId = parseUuid(request.typeId, "typeId")
|
||||
DictDao.requireType(typeId)
|
||||
DictDao.createItem(request, typeId)
|
||||
}
|
||||
|
||||
suspend fun updateItem(id: Uuid, request: UpdateDictItemRequest) = dbQuery {
|
||||
DictDao.requireItem(id)
|
||||
val typeId = parseUuid(request.typeId, "typeId")
|
||||
DictDao.requireType(typeId)
|
||||
DictDao.updateItem(id, request, typeId)
|
||||
}
|
||||
|
||||
suspend fun deleteItem(id: Uuid) = dbQuery {
|
||||
DictDao.requireItem(id)
|
||||
DictDao.softDeleteItem(id)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.bbit.ticket.service.system
|
||||
|
||||
import com.bbit.ticket.dao.piaotong.EnterpriseManageDao
|
||||
import com.bbit.ticket.entity.common.PageResult
|
||||
import com.bbit.ticket.entity.response.EnterpriseManageListItem
|
||||
import com.bbit.ticket.utils.plugins.dbQuery
|
||||
|
||||
object EnterpriseManageService {
|
||||
suspend fun list(keyword: String?, page: Int, pageSize: Int): PageResult<EnterpriseManageListItem> =
|
||||
dbQuery { EnterpriseManageDao.list(keyword, page, pageSize) }
|
||||
}
|
||||
@@ -10,7 +10,6 @@ object JwtService {
|
||||
fun issueAccessToken(
|
||||
userId: String,
|
||||
username: String,
|
||||
orgId: String?,
|
||||
roles: List<String>,
|
||||
tokenVersion: Int,
|
||||
): Pair<String, Long> {
|
||||
@@ -24,7 +23,6 @@ object JwtService {
|
||||
.withExpiresAt(expiresAt)
|
||||
.withSubject(userId)
|
||||
.withClaim("username", username)
|
||||
.withClaim("orgId", orgId)
|
||||
.withArrayClaim("roles", roles.toTypedArray())
|
||||
.withClaim("tokenVersion", tokenVersion)
|
||||
.withClaim("token_type", "access_token")
|
||||
@@ -32,4 +30,4 @@ object JwtService {
|
||||
|
||||
return token to AppConfig.jwt.accessTokenTtlMinutes * 60
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
package com.bbit.ticket.service.system
|
||||
|
||||
import com.bbit.ticket.dao.system.LogDao
|
||||
import com.bbit.ticket.entity.common.system.ApiAccessLogItem
|
||||
import com.bbit.ticket.entity.common.system.OperationLogItem
|
||||
import com.bbit.ticket.entity.common.PageResult
|
||||
import com.bbit.ticket.utils.plugins.dbQuery
|
||||
@@ -12,7 +11,4 @@ import kotlin.uuid.ExperimentalUuidApi
|
||||
object LogsQueryService {
|
||||
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 { LogDao.apiAccessLogs(page, pageSize, keyword, status) }
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
@file:OptIn(ExperimentalUuidApi::class)
|
||||
|
||||
package com.bbit.ticket.service.system
|
||||
|
||||
import com.bbit.ticket.dao.system.OrgDao
|
||||
import com.bbit.ticket.database.system.SysOrgTable
|
||||
import com.bbit.ticket.entity.common.system.CreateOrgRequest
|
||||
import com.bbit.ticket.entity.common.system.OrgTreeNode
|
||||
import com.bbit.ticket.entity.common.system.UpdateOrgRequest
|
||||
import com.bbit.ticket.entity.common.BizException
|
||||
import com.bbit.ticket.entity.common.ErrorCode
|
||||
import com.bbit.ticket.utils.plugins.dbQuery
|
||||
import com.bbit.ticket.utils.parseUuid
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
object OrgService {
|
||||
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, "组织名称和编码不能为空")
|
||||
}
|
||||
if (OrgDao.codeExists(code)) {
|
||||
throw BizException(ErrorCode.DATA_CONFLICT.code, "组织编码已存在")
|
||||
}
|
||||
val parentId = request.parentId?.let { parseUuid(it, "parentId") }
|
||||
if (parentId != null) OrgDao.requireActive(parentId)
|
||||
OrgDao.create(request, parentId)
|
||||
}
|
||||
|
||||
suspend fun update(id: Uuid, request: UpdateOrgRequest) = dbQuery {
|
||||
OrgDao.requireActive(id)
|
||||
val parentId = request.parentId?.let { parseUuid(it, "parentId") }
|
||||
if (parentId == id) {
|
||||
throw BizException(ErrorCode.BAD_REQUEST.code, "上级组织不能选择自身")
|
||||
}
|
||||
if (parentId != null) OrgDao.requireActive(parentId)
|
||||
OrgDao.update(id, request, parentId)
|
||||
}
|
||||
|
||||
suspend fun delete(id: Uuid) = dbQuery {
|
||||
val org = OrgDao.requireActive(id)
|
||||
if (org[SysOrgTable.code] == "DEFAULT_ORG") {
|
||||
throw BizException(ErrorCode.BAD_REQUEST.code, "默认组织不可删除")
|
||||
}
|
||||
if (OrgDao.hasChildren(id)) {
|
||||
throw BizException(ErrorCode.BAD_REQUEST.code, "当前组织存在子组织,不能删除")
|
||||
}
|
||||
if (OrgDao.hasUsers(id)) {
|
||||
throw BizException(ErrorCode.BAD_REQUEST.code, "当前组织存在用户,不能删除")
|
||||
}
|
||||
OrgDao.softDelete(id)
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,8 @@ object UserService {
|
||||
username: String?,
|
||||
nickname: String?,
|
||||
status: String?,
|
||||
orgId: Uuid?,
|
||||
): PageResult<UserListItem> = dbQuery { UserDao.list(page, pageSize, username, nickname, status, orgId) }
|
||||
enterpriseId: Uuid?,
|
||||
): PageResult<UserListItem> = dbQuery { UserDao.list(page, pageSize, username, nickname, status, enterpriseId) }
|
||||
|
||||
suspend fun create(request: CreateUserRequest): String = dbQuery {
|
||||
val username = request.username.trim()
|
||||
@@ -37,22 +37,22 @@ object UserService {
|
||||
throw BizException(ErrorCode.DATA_CONFLICT.code, "用户名已存在")
|
||||
}
|
||||
|
||||
val orgUuid = request.orgId?.let { parseUuid(it, "orgId") }
|
||||
if (orgUuid != null) {
|
||||
ensureOrgExists(orgUuid)
|
||||
val enterpriseUuid = request.enterpriseId?.let { parseUuid(it, "enterpriseId") }
|
||||
if (enterpriseUuid != null) {
|
||||
ensureEnterpriseExists(enterpriseUuid)
|
||||
}
|
||||
UserDao.create(request, PasswordService.hash(request.password), orgUuid)
|
||||
UserDao.create(request, PasswordService.hash(request.password), enterpriseUuid)
|
||||
}
|
||||
|
||||
suspend fun detail(id: Uuid): UserDetailResponse = dbQuery { UserDao.detail(id) }
|
||||
|
||||
suspend fun update(id: Uuid, request: UpdateUserRequest) = dbQuery {
|
||||
UserDao.requireActive(id)
|
||||
val orgUuid = request.orgId?.let { parseUuid(it, "orgId") }
|
||||
if (orgUuid != null) {
|
||||
ensureOrgExists(orgUuid)
|
||||
val enterpriseUuid = request.enterpriseId?.let { parseUuid(it, "enterpriseId") }
|
||||
if (enterpriseUuid != null) {
|
||||
ensureEnterpriseExists(enterpriseUuid)
|
||||
}
|
||||
UserDao.updateProfile(id, request, orgUuid)
|
||||
UserDao.updateProfile(id, request, enterpriseUuid)
|
||||
}
|
||||
|
||||
suspend fun softDelete(id: Uuid) = dbQuery {
|
||||
@@ -91,9 +91,9 @@ object UserService {
|
||||
UserDao.replaceRoles(id, roleIds)
|
||||
}
|
||||
|
||||
private fun ensureOrgExists(orgId: Uuid) {
|
||||
if (!UserDao.orgExists(orgId)) {
|
||||
throw BizException(ErrorCode.ORG_NOT_FOUND.code, ErrorCode.ORG_NOT_FOUND.message)
|
||||
private fun ensureEnterpriseExists(enterpriseId: Uuid) {
|
||||
if (!UserDao.enterpriseExists(enterpriseId)) {
|
||||
throw BizException(ErrorCode.BAD_REQUEST.code, "企业不存在")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,9 @@ import io.ktor.server.application.ApplicationCall
|
||||
|
||||
suspend fun ApplicationCall.requirePermission(permission: String): CurrentUser {
|
||||
val currentUser = requireCurrentUser()
|
||||
if (currentUser.isSuperAdmin || currentUser.permissions.contains(permission)) {
|
||||
if (currentUser.permissions.contains(permission)) {
|
||||
return currentUser
|
||||
}
|
||||
|
||||
throw BizException(ErrorCode.FORBIDDEN.code, ErrorCode.FORBIDDEN.message, HttpStatusCode.Forbidden)
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ import kotlin.uuid.Uuid
|
||||
data class CurrentUser(
|
||||
val id: Uuid,
|
||||
val username: String,
|
||||
val orgId: Uuid?,
|
||||
val enterpriseId: Uuid?,
|
||||
val digitalAccountId: Uuid?,
|
||||
val userType: String,
|
||||
@@ -99,50 +98,36 @@ suspend fun ApplicationCall.requireCurrentUser(): CurrentUser {
|
||||
.toSet()
|
||||
}
|
||||
|
||||
val permissions = if (roleCodes.contains("SUPER_ADMIN")) {
|
||||
val roleIds = dbQuery {
|
||||
(SysUserRoleTable innerJoin SysRoleTable)
|
||||
.selectAll()
|
||||
.where {
|
||||
(SysUserRoleTable.userId eq userUuid) and
|
||||
SysRoleTable.deletedAt.isNull() and
|
||||
(SysRoleTable.status eq "ENABLED")
|
||||
}
|
||||
.map { it[SysRoleTable.id] }
|
||||
}
|
||||
val permissions = if (roleIds.isEmpty()) {
|
||||
emptySet()
|
||||
} else {
|
||||
dbQuery {
|
||||
SysMenuTable.selectAll()
|
||||
(SysRoleMenuTable innerJoin SysMenuTable)
|
||||
.selectAll()
|
||||
.where {
|
||||
SysMenuTable.deletedAt.isNull() and
|
||||
(SysRoleMenuTable.roleId inList roleIds) and
|
||||
SysMenuTable.deletedAt.isNull() and
|
||||
(SysMenuTable.status eq "ENABLED") and
|
||||
SysMenuTable.permission.isNotNull()
|
||||
}
|
||||
.mapNotNull { it[SysMenuTable.permission] }
|
||||
.toSet()
|
||||
}
|
||||
} else {
|
||||
val roleIds = dbQuery {
|
||||
(SysUserRoleTable innerJoin SysRoleTable)
|
||||
.selectAll()
|
||||
.where {
|
||||
(SysUserRoleTable.userId eq userUuid) and
|
||||
SysRoleTable.deletedAt.isNull() and
|
||||
(SysRoleTable.status eq "ENABLED")
|
||||
}
|
||||
.map { it[SysRoleTable.id] }
|
||||
}
|
||||
if (roleIds.isEmpty()) {
|
||||
emptySet()
|
||||
} else {
|
||||
dbQuery {
|
||||
(SysRoleMenuTable innerJoin SysMenuTable)
|
||||
.selectAll()
|
||||
.where {
|
||||
(SysRoleMenuTable.roleId inList roleIds) and
|
||||
SysMenuTable.deletedAt.isNull() and
|
||||
(SysMenuTable.status eq "ENABLED") and
|
||||
SysMenuTable.permission.isNotNull()
|
||||
}
|
||||
.mapNotNull { it[SysMenuTable.permission] }
|
||||
.toSet()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val currentUser = CurrentUser(
|
||||
id = userRow[SysUserTable.id],
|
||||
username = userRow[SysUserTable.username],
|
||||
orgId = userRow[SysUserTable.orgId],
|
||||
enterpriseId = userRow[SysUserTable.enterpriseId],
|
||||
digitalAccountId = userRow[SysUserTable.digitalAccountId],
|
||||
userType = userRow[SysUserTable.userType],
|
||||
|
||||
@@ -12,11 +12,8 @@ import com.bbit.ticket.database.piaotong.OpenInvoiceTaskTable
|
||||
import com.bbit.ticket.database.piaotong.PtDigitalAccountTable
|
||||
import com.bbit.ticket.database.piaotong.PtEnterpriseTable
|
||||
import com.bbit.ticket.database.system.SysApiAccessLogTable
|
||||
import com.bbit.ticket.database.system.SysDictItemTable
|
||||
import com.bbit.ticket.database.system.SysDictTypeTable
|
||||
import com.bbit.ticket.database.system.SysMenuTable
|
||||
import com.bbit.ticket.database.system.SysOperationLogTable
|
||||
import com.bbit.ticket.database.system.SysOrgTable
|
||||
import com.bbit.ticket.database.system.SysRoleMenuTable
|
||||
import com.bbit.ticket.database.system.SysRoleTable
|
||||
import com.bbit.ticket.database.system.SysUserRoleTable
|
||||
@@ -30,14 +27,11 @@ object DatabaseInitializer {
|
||||
|
||||
suspend fun initialize() {
|
||||
val tables = arrayOf(
|
||||
SysOrgTable,
|
||||
SysUserTable,
|
||||
SysRoleTable,
|
||||
SysMenuTable,
|
||||
SysUserRoleTable,
|
||||
SysRoleMenuTable,
|
||||
SysDictTypeTable,
|
||||
SysDictItemTable,
|
||||
SysOperationLogTable,
|
||||
SysApiAccessLogTable,
|
||||
PtEnterpriseTable,
|
||||
|
||||
@@ -30,8 +30,16 @@ object SeedData {
|
||||
const val ADMIN_USERNAME = "admin"
|
||||
const val ADMIN_INIT_PASSWORD = "Admin@123456"
|
||||
|
||||
private const val DEFAULT_ORG_CODE = "DEFAULT_ORG"
|
||||
private const val DEFAULT_ADMIN_TAXPAYER_NUM = "500102201007206608"
|
||||
private const val ADMIN_NICKNAME = "超级管理员"
|
||||
private const val ENTERPRISE_ADMIN_INIT_PASSWORD = "123456"
|
||||
private const val DEV_ENTERPRISE_TAXPAYER_NUM = "500102201007206608"
|
||||
private const val PROD_ENTERPRISE_TAXPAYER_NUM = "91510106MA68JJHN7C"
|
||||
private const val PROD_ENTERPRISE_NAME = "成都主干智慧云信息技术有限公司"
|
||||
private const val PROD_ENTERPRISE_LEGAL_PERSON_NAME = "范鸿才"
|
||||
private const val PROD_ENTERPRISE_REGION_CODE = "51"
|
||||
private const val PROD_ENTERPRISE_CITY_NAME = "成都市"
|
||||
private const val PROD_ENTERPRISE_ADDRESS = "成都金牛区蜀西路42号5栋3单元5层2号"
|
||||
private const val PROD_ENTERPRISE_PHONE = "18981977117"
|
||||
private const val SUPER_ADMIN_ROLE_CODE = "SUPER_ADMIN"
|
||||
private const val ENTERPRISE_ADMIN_ROLE_CODE = "ENTERPRISE_ADMIN"
|
||||
private const val DIGITAL_OPERATOR_ROLE_CODE = "DIGITAL_OPERATOR"
|
||||
@@ -42,9 +50,7 @@ object SeedData {
|
||||
|
||||
suspend fun seed() {
|
||||
val now = OffsetDateTime.now()
|
||||
val orgId = upsertDefaultOrg(now)
|
||||
val roleId = upsertSuperAdminRole(now)
|
||||
val defaultEnterpriseId = ensureDefaultAdminEnterprise()
|
||||
val enterpriseAdminRoleId = upsertBusinessRole(
|
||||
code = ENTERPRISE_ADMIN_ROLE_CODE,
|
||||
name = "企业管理员",
|
||||
@@ -57,48 +63,24 @@ object SeedData {
|
||||
description = "数电账号对应的平台开票员角色",
|
||||
now = now,
|
||||
)
|
||||
val adminId = upsertAdminUser(orgId, defaultEnterpriseId, now)
|
||||
val adminId = upsertAdminUser(now)
|
||||
upsertUserRole(adminId, roleId)
|
||||
val enterpriseSeed = enterpriseSeed()
|
||||
val enterpriseId = ensureSeedEnterprise(enterpriseSeed)
|
||||
val enterpriseAdminId = upsertEnterpriseAdminUser(enterpriseId, enterpriseSeed, now)
|
||||
upsertUserRole(enterpriseAdminId, enterpriseAdminRoleId)
|
||||
val menuIds = upsertMenus(now)
|
||||
disableObsoleteMenus(now)
|
||||
bindRoleMenus(roleId, menuIds.values.toList())
|
||||
bindRoleMenus(roleId, superAdminMenuIds(menuIds))
|
||||
bindRoleMenus(enterpriseAdminRoleId, enterpriseAdminMenuIds(menuIds))
|
||||
bindRoleMenus(digitalOperatorRoleId, digitalOperatorMenuIds(menuIds))
|
||||
seedDicts(now)
|
||||
logger.info("Seed data initialized, default admin username: {}", ADMIN_USERNAME)
|
||||
logger.info("Seed data initialized, super admin username: {}", ADMIN_USERNAME)
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// Organization & Role
|
||||
// Role
|
||||
// =========================================================
|
||||
|
||||
private suspend fun upsertDefaultOrg(now: OffsetDateTime): Uuid = dbQuery {
|
||||
val existing = SysOrgTable.selectAll()
|
||||
.where { (SysOrgTable.code eq DEFAULT_ORG_CODE) and SysOrgTable.deletedAt.isNull() }
|
||||
.singleOrNull()
|
||||
|
||||
if (existing != null) {
|
||||
val id = existing[SysOrgTable.id]
|
||||
SysOrgTable.update({ SysOrgTable.id eq id }) {
|
||||
it[SysOrgTable.name] = "默认组织"
|
||||
it[SysOrgTable.sort] = 0
|
||||
it[SysOrgTable.status] = "ENABLED"
|
||||
it[SysOrgTable.updatedAt] = now
|
||||
}
|
||||
return@dbQuery id
|
||||
}
|
||||
|
||||
val inserted = SysOrgTable.insert {
|
||||
it[SysOrgTable.parentId] = null
|
||||
it[SysOrgTable.name] = "默认组织"
|
||||
it[SysOrgTable.code] = DEFAULT_ORG_CODE
|
||||
it[SysOrgTable.sort] = 0
|
||||
it[SysOrgTable.status] = "ENABLED"
|
||||
it[SysOrgTable.createdAt] = now
|
||||
}
|
||||
inserted[SysOrgTable.id]
|
||||
}
|
||||
|
||||
private suspend fun upsertSuperAdminRole(now: OffsetDateTime): Uuid = dbQuery {
|
||||
val existing = SysRoleTable.selectAll()
|
||||
.where { (SysRoleTable.code eq SUPER_ADMIN_ROLE_CODE) and SysRoleTable.deletedAt.isNull() }
|
||||
@@ -161,43 +143,38 @@ object SeedData {
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// Admin user
|
||||
// Users & Enterprise
|
||||
// =========================================================
|
||||
|
||||
private suspend fun ensureDefaultAdminEnterprise(): Uuid {
|
||||
private suspend fun ensureSeedEnterprise(seed: EnterpriseSeed): Uuid {
|
||||
val existingId = dbQuery {
|
||||
EnterpriseManageDao.findEnterpriseByTaxpayerNum(DEFAULT_ADMIN_TAXPAYER_NUM)?.get(PtEnterpriseTable.id)
|
||||
EnterpriseManageDao.findEnterpriseByTaxpayerNum(seed.taxpayerNum)?.get(PtEnterpriseTable.id)
|
||||
}
|
||||
val ptEnterprise = PTApi.getEnterpriseInfo(QueryEnterpriseInfoRequest(DEFAULT_ADMIN_TAXPAYER_NUM))
|
||||
if (existingId != null) return existingId
|
||||
|
||||
if (existingId != null) {
|
||||
dbQuery { EnterpriseManageDao.updateEnterpriseFromPt(existingId, ptEnterprise) }
|
||||
return existingId
|
||||
}
|
||||
val ptEnterprise = PTApi.getEnterpriseInfo(QueryEnterpriseInfoRequest(seed.taxpayerNum))
|
||||
|
||||
val enterpriseInfo = TaxRegisterInfo(
|
||||
taxpayerNum = DEFAULT_ADMIN_TAXPAYER_NUM,
|
||||
enterpriseName = ptEnterprise.enterpriseName.ifBlank { DEFAULT_ADMIN_TAXPAYER_NUM },
|
||||
legalPersonName = null,
|
||||
contactsName = null,
|
||||
contactsEmail = null,
|
||||
contactsPhone = null,
|
||||
regionCode = ptEnterprise.regionCode,
|
||||
cityName = ptEnterprise.cityName,
|
||||
enterpriseAddress = ptEnterprise.enterpriseAddress,
|
||||
taxRegistrationCertificate = null,
|
||||
taxpayerNum = seed.taxpayerNum,
|
||||
enterpriseName = ptEnterprise.enterpriseName.ifBlank { seed.enterpriseName },
|
||||
legalPersonName = seed.legalPersonName,
|
||||
contactsName = seed.contactsName,
|
||||
contactsEmail = seed.contactsEmail,
|
||||
contactsPhone = seed.contactsPhone,
|
||||
regionCode = ptEnterprise.regionCode.ifBlank { seed.regionCode },
|
||||
cityName = ptEnterprise.cityName.ifBlank { seed.cityName },
|
||||
enterpriseAddress = ptEnterprise.enterpriseAddress.ifBlank { seed.enterpriseAddress.orEmpty() },
|
||||
taxRegistrationCertificate = seed.taxRegistrationCertificate,
|
||||
)
|
||||
|
||||
return dbQuery {
|
||||
val enterpriseId = EnterpriseManageDao.findEnterpriseByTaxpayerNum(DEFAULT_ADMIN_TAXPAYER_NUM)
|
||||
?.get(PtEnterpriseTable.id)
|
||||
?: EnterpriseManageDao.createEnterprise(enterpriseInfo)
|
||||
val enterpriseId = EnterpriseManageDao.createEnterprise(enterpriseInfo)
|
||||
EnterpriseManageDao.updateEnterpriseFromPt(enterpriseId, ptEnterprise)
|
||||
enterpriseId
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun upsertAdminUser(orgId: Uuid, enterpriseId: Uuid, now: OffsetDateTime): Uuid = dbQuery {
|
||||
private suspend fun upsertAdminUser(now: OffsetDateTime): Uuid = dbQuery {
|
||||
val existing = SysUserTable.selectAll()
|
||||
.where { (SysUserTable.username eq ADMIN_USERNAME) and SysUserTable.deletedAt.isNull() }
|
||||
.singleOrNull()
|
||||
@@ -205,14 +182,13 @@ object SeedData {
|
||||
if (existing != null) {
|
||||
val id = existing[SysUserTable.id]
|
||||
SysUserTable.update({ SysUserTable.id eq id }) {
|
||||
it[SysUserTable.nickname] = "系统管理员"
|
||||
it[SysUserTable.orgId] = orgId
|
||||
it[SysUserTable.enterpriseId] = enterpriseId
|
||||
it[SysUserTable.nickname] = ADMIN_NICKNAME
|
||||
it[SysUserTable.enterpriseId] = null
|
||||
it[SysUserTable.status] = "ENABLED"
|
||||
it[SysUserTable.userType] = "SYSTEM"
|
||||
it[SysUserTable.updatedAt] = now
|
||||
it[SysUserTable.phone] = "13000000000"
|
||||
it[SysUserTable.realName] = "测试"
|
||||
it[SysUserTable.phone] = null
|
||||
it[SysUserTable.realName] = null
|
||||
}
|
||||
return@dbQuery id
|
||||
}
|
||||
@@ -220,19 +196,85 @@ object SeedData {
|
||||
val inserted = SysUserTable.insert {
|
||||
it[SysUserTable.username] = ADMIN_USERNAME
|
||||
it[SysUserTable.passwordHash] = PasswordService.hash(ADMIN_INIT_PASSWORD)
|
||||
it[SysUserTable.nickname] = "系统管理员"
|
||||
it[SysUserTable.orgId] = orgId
|
||||
it[SysUserTable.enterpriseId] = enterpriseId
|
||||
it[SysUserTable.nickname] = ADMIN_NICKNAME
|
||||
it[SysUserTable.enterpriseId] = null
|
||||
it[SysUserTable.status] = "ENABLED"
|
||||
it[SysUserTable.userType] = "SYSTEM"
|
||||
it[SysUserTable.tokenVersion] = 1
|
||||
it[SysUserTable.phone] = "13000000000"
|
||||
it[SysUserTable.realName] = "测试"
|
||||
it[SysUserTable.phone] = null
|
||||
it[SysUserTable.realName] = null
|
||||
it[SysUserTable.createdAt] = now
|
||||
}
|
||||
inserted[SysUserTable.id]
|
||||
}
|
||||
|
||||
private suspend fun upsertEnterpriseAdminUser(
|
||||
enterpriseId: Uuid,
|
||||
seed: EnterpriseSeed,
|
||||
now: OffsetDateTime,
|
||||
): Uuid = dbQuery {
|
||||
val existing = SysUserTable.selectAll()
|
||||
.where { (SysUserTable.username eq seed.taxpayerNum) and SysUserTable.deletedAt.isNull() }
|
||||
.singleOrNull()
|
||||
|
||||
if (existing != null) {
|
||||
val id = existing[SysUserTable.id]
|
||||
SysUserTable.update({ SysUserTable.id eq id }) {
|
||||
it[SysUserTable.nickname] = seed.enterpriseName
|
||||
it[SysUserTable.enterpriseId] = enterpriseId
|
||||
it[SysUserTable.status] = "ENABLED"
|
||||
it[SysUserTable.userType] = "ENTERPRISE_ADMIN"
|
||||
it[SysUserTable.updatedAt] = now
|
||||
it[SysUserTable.phone] = seed.contactsPhone
|
||||
it[SysUserTable.realName] = seed.legalPersonName
|
||||
}
|
||||
return@dbQuery id
|
||||
}
|
||||
|
||||
val inserted = SysUserTable.insert {
|
||||
it[SysUserTable.username] = seed.taxpayerNum
|
||||
it[SysUserTable.passwordHash] = PasswordService.hash(ENTERPRISE_ADMIN_INIT_PASSWORD)
|
||||
it[SysUserTable.nickname] = seed.enterpriseName
|
||||
it[SysUserTable.enterpriseId] = enterpriseId
|
||||
it[SysUserTable.status] = "ENABLED"
|
||||
it[SysUserTable.userType] = "ENTERPRISE_ADMIN"
|
||||
it[SysUserTable.tokenVersion] = 1
|
||||
it[SysUserTable.phone] = seed.contactsPhone
|
||||
it[SysUserTable.realName] = seed.legalPersonName
|
||||
it[SysUserTable.createdAt] = now
|
||||
}
|
||||
inserted[SysUserTable.id]
|
||||
}
|
||||
|
||||
private fun enterpriseSeed(): EnterpriseSeed =
|
||||
if (Global.isDev) {
|
||||
EnterpriseSeed(
|
||||
taxpayerNum = DEV_ENTERPRISE_TAXPAYER_NUM,
|
||||
enterpriseName = DEV_ENTERPRISE_TAXPAYER_NUM,
|
||||
legalPersonName = null,
|
||||
contactsName = null,
|
||||
contactsEmail = null,
|
||||
contactsPhone = null,
|
||||
regionCode = "",
|
||||
cityName = "",
|
||||
enterpriseAddress = null,
|
||||
taxRegistrationCertificate = null,
|
||||
)
|
||||
} else {
|
||||
EnterpriseSeed(
|
||||
taxpayerNum = PROD_ENTERPRISE_TAXPAYER_NUM,
|
||||
enterpriseName = PROD_ENTERPRISE_NAME,
|
||||
legalPersonName = PROD_ENTERPRISE_LEGAL_PERSON_NAME,
|
||||
contactsName = PROD_ENTERPRISE_LEGAL_PERSON_NAME,
|
||||
contactsEmail = null,
|
||||
contactsPhone = PROD_ENTERPRISE_PHONE,
|
||||
regionCode = PROD_ENTERPRISE_REGION_CODE,
|
||||
cityName = PROD_ENTERPRISE_CITY_NAME,
|
||||
enterpriseAddress = PROD_ENTERPRISE_ADDRESS,
|
||||
taxRegistrationCertificate = null,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun upsertUserRole(userId: Uuid, roleId: Uuid) = dbQuery {
|
||||
val exists = SysUserRoleTable.selectAll()
|
||||
.where { (SysUserRoleTable.userId eq userId) and (SysUserRoleTable.roleId eq roleId) }
|
||||
@@ -257,10 +299,7 @@ object SeedData {
|
||||
button("system_user_create", "system_user", "新增用户", "SystemUserCreate", "system:user:create", 1),
|
||||
button("system_user_update", "system_user", "修改用户", "SystemUserUpdate", "system:user:update", 2),
|
||||
button("system_user_delete", "system_user", "删除用户", "SystemUserDelete", "system:user:delete", 3),
|
||||
subMenu("system_org", "system", "组织管理", "SystemOrgs", "/system/orgs", "system/orgs/index", "Building2", "system:org:view", 20),
|
||||
button("system_org_create", "system_org", "新增组织", "SystemOrgCreate", "system:org:create", 1),
|
||||
button("system_org_update", "system_org", "更新组织", "SystemOrgUpdate", "system:org:update", 2),
|
||||
button("system_org_delete", "system_org", "删除组织", "SystemOrgDelete", "system:org:delete", 3),
|
||||
subMenu("system_enterprise", "system", "企业管理", "SystemEnterprises", "/system/enterprises", "system/enterprises/index", "Building2", "system:enterprise:view", 20),
|
||||
subMenu("system_role", "system", "角色管理", "SystemRoles", "/system/roles", "system/roles/index", "Shield", "system:role:view", 30),
|
||||
button("system_role_create", "system_role", "新增角色", "SystemRoleCreate", "system:role:create", 1),
|
||||
button("system_role_update", "system_role", "更新角色", "SystemRoleUpdate", "system:role:update", 2),
|
||||
@@ -270,13 +309,8 @@ object SeedData {
|
||||
button("system_menu_create", "system_menu", "新增菜单", "SystemMenuCreate", "system:menu:create", 1),
|
||||
button("system_menu_update", "system_menu", "更新菜单", "SystemMenuUpdate", "system:menu:update", 2),
|
||||
button("system_menu_delete", "system_menu", "删除菜单", "SystemMenuDelete", "system:menu:delete", 3),
|
||||
subMenu("system_dict", "system", "字典管理", "SystemDict", "/system/dicts", "system/dicts/index", "BookType", "system:dict:view", 50),
|
||||
button("system_dict_create", "system_dict", "新增字典", "SystemDictCreate", "system:dict:create", 1),
|
||||
button("system_dict_update", "system_dict", "更新字典", "SystemDictUpdate", "system:dict:update", 2),
|
||||
button("system_dict_delete", "system_dict", "删除字典", "SystemDictDelete", "system:dict:delete", 3),
|
||||
catalog("logs", "日志管理", "LogsRoot", "Logs", 30),
|
||||
subMenu("logs_operation", "logs", "操作日志", "LogsOperation", "/logs/operation", "logs/operation/index", "ScrollText", "log:operation:view", 10),
|
||||
subMenu("logs_api_access", "logs", "接口日志", "LogsApiAccess", "/logs/api-access", "logs/api-access/index", "Waypoints", "log:api-access:view", 20),
|
||||
subMenu("openapi_statistics", "logs", "OpenAPI", "OpenApiStatistics", "/logs/openapi", "statistics/openapi/index", "Waypoints", "openapi:statistics:view", 30),
|
||||
catalog("basic_info", "基础信息", "BasicInfoRoot", "Building2", 40),
|
||||
subMenu("enterprise_info", "basic_info", "企业信息", "EnterpriseInfo", "/enterprise/info", "piaotong/index", "Building2", "enterprise:info:view", 10),
|
||||
@@ -297,6 +331,29 @@ object SeedData {
|
||||
return idMap
|
||||
}
|
||||
|
||||
private fun superAdminMenuIds(menuIds: Map<String, Uuid>): List<Uuid> =
|
||||
listOf(
|
||||
"dashboard",
|
||||
"system",
|
||||
"system_user",
|
||||
"system_user_create",
|
||||
"system_user_update",
|
||||
"system_user_delete",
|
||||
"system_enterprise",
|
||||
"system_role",
|
||||
"system_role_create",
|
||||
"system_role_update",
|
||||
"system_role_delete",
|
||||
"system_role_assign",
|
||||
"system_menu",
|
||||
"system_menu_create",
|
||||
"system_menu_update",
|
||||
"system_menu_delete",
|
||||
"logs",
|
||||
"logs_operation",
|
||||
"openapi_statistics",
|
||||
).mapNotNull { menuIds[it] }
|
||||
|
||||
private fun enterpriseAdminMenuIds(menuIds: Map<String, Uuid>): List<Uuid> =
|
||||
listOf(
|
||||
"dashboard",
|
||||
@@ -308,6 +365,7 @@ object SeedData {
|
||||
"invoice_service",
|
||||
"piaotong_invoice_issue",
|
||||
"piaotong_invoice_history",
|
||||
"logs",
|
||||
"openapi_statistics",
|
||||
).mapNotNull { menuIds[it] }
|
||||
|
||||
@@ -319,6 +377,7 @@ object SeedData {
|
||||
"invoice_service",
|
||||
"piaotong_invoice_issue",
|
||||
"piaotong_invoice_history",
|
||||
"logs",
|
||||
"openapi_statistics",
|
||||
).mapNotNull { menuIds[it] }
|
||||
|
||||
@@ -395,7 +454,7 @@ object SeedData {
|
||||
|
||||
private suspend fun disableObsoleteMenus(now: OffsetDateTime) = dbQuery {
|
||||
SysMenuTable.update({
|
||||
(SysMenuTable.name inList listOf("StatisticsInfoRoot")) and SysMenuTable.deletedAt.isNull()
|
||||
(SysMenuTable.name inList listOf("StatisticsInfoRoot", "SystemOrgs", "SystemDict", "LogsApiAccess")) and SysMenuTable.deletedAt.isNull()
|
||||
}) {
|
||||
it[SysMenuTable.visible] = false
|
||||
it[SysMenuTable.status] = "DISABLED"
|
||||
@@ -403,95 +462,6 @@ object SeedData {
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// Dicts
|
||||
// =========================================================
|
||||
|
||||
private data class SeedDict(val code: String, val name: String, val items: List<SeedDictItem>)
|
||||
private data class SeedDictItem(val label: String, val value: String, val color: String?, val sort: Int)
|
||||
|
||||
private suspend fun seedDicts(now: OffsetDateTime) {
|
||||
val statusItems = listOf(
|
||||
SeedDictItem("启用", "ENABLED", "green", 1),
|
||||
SeedDictItem("禁用", "DISABLED", "red", 2),
|
||||
)
|
||||
|
||||
val dicts = listOf(
|
||||
SeedDict("user_status", "用户状态", statusItems),
|
||||
SeedDict("org_status", "组织状态", statusItems),
|
||||
SeedDict("role_status", "角色状态", statusItems),
|
||||
SeedDict("menu_type", "菜单类型", listOf(
|
||||
SeedDictItem("目录", "CATALOG", "default", 1),
|
||||
SeedDictItem("菜单", "MENU", "blue", 2),
|
||||
SeedDictItem("按钮", "BUTTON", "orange", 3),
|
||||
)),
|
||||
SeedDict("log_status", "日志状态", statusItems),
|
||||
)
|
||||
|
||||
for (dict in dicts) {
|
||||
val typeId = upsertDictType(dict.code, dict.name, now)
|
||||
for (item in dict.items) {
|
||||
upsertDictItem(typeId, item.label, item.value, item.color, item.sort, now)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun upsertDictType(code: String, name: String, now: OffsetDateTime): Uuid = dbQuery {
|
||||
val existing = SysDictTypeTable.selectAll()
|
||||
.where { (SysDictTypeTable.code eq code) and SysDictTypeTable.deletedAt.isNull() }
|
||||
.singleOrNull()
|
||||
|
||||
if (existing != null) {
|
||||
val id = existing[SysDictTypeTable.id]
|
||||
SysDictTypeTable.update({ SysDictTypeTable.id eq id }) {
|
||||
it[SysDictTypeTable.name] = name
|
||||
it[SysDictTypeTable.status] = "ENABLED"
|
||||
it[SysDictTypeTable.updatedAt] = now
|
||||
}
|
||||
return@dbQuery id
|
||||
}
|
||||
|
||||
val inserted = SysDictTypeTable.insert {
|
||||
it[SysDictTypeTable.code] = code
|
||||
it[SysDictTypeTable.name] = name
|
||||
it[SysDictTypeTable.status] = "ENABLED"
|
||||
it[SysDictTypeTable.createdAt] = now
|
||||
}
|
||||
inserted[SysDictTypeTable.id]
|
||||
}
|
||||
|
||||
private suspend fun upsertDictItem(
|
||||
typeId: Uuid, label: String, value: String, color: String?, sort: Int, now: OffsetDateTime,
|
||||
) = dbQuery {
|
||||
val existing = SysDictItemTable.selectAll()
|
||||
.where {
|
||||
(SysDictItemTable.typeId eq typeId) and
|
||||
(SysDictItemTable.value eq value) and
|
||||
SysDictItemTable.deletedAt.isNull()
|
||||
}
|
||||
.singleOrNull()
|
||||
|
||||
if (existing != null) {
|
||||
SysDictItemTable.update({ SysDictItemTable.id eq existing[SysDictItemTable.id] }) {
|
||||
it[SysDictItemTable.label] = label
|
||||
it[SysDictItemTable.color] = color
|
||||
it[SysDictItemTable.sort] = sort
|
||||
it[SysDictItemTable.status] = "ENABLED"
|
||||
it[SysDictItemTable.updatedAt] = now
|
||||
}
|
||||
return@dbQuery
|
||||
}
|
||||
|
||||
SysDictItemTable.insert {
|
||||
it[SysDictItemTable.typeId] = typeId
|
||||
it[SysDictItemTable.label] = label
|
||||
it[SysDictItemTable.value] = value
|
||||
it[SysDictItemTable.color] = color
|
||||
it[SysDictItemTable.sort] = sort
|
||||
it[SysDictItemTable.status] = "ENABLED"
|
||||
it[SysDictItemTable.createdAt] = now
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
@@ -514,6 +484,19 @@ private data class SeedMenu(
|
||||
val builtIn: Boolean = true,
|
||||
)
|
||||
|
||||
private data class EnterpriseSeed(
|
||||
val taxpayerNum: String,
|
||||
val enterpriseName: String,
|
||||
val legalPersonName: String?,
|
||||
val contactsName: String?,
|
||||
val contactsEmail: String?,
|
||||
val contactsPhone: String?,
|
||||
val regionCode: String,
|
||||
val cityName: String,
|
||||
val enterpriseAddress: String?,
|
||||
val taxRegistrationCertificate: String?,
|
||||
)
|
||||
|
||||
private fun rootMenu(key: String, title: String, name: String, path: String, component: String, icon: String, sort: Int): SeedMenu =
|
||||
SeedMenu(key, null, "MENU", title, name, path, component, icon, null, sort, visible = true, keepAlive = true)
|
||||
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
package com.bbit.ticket.utils.plugins
|
||||
|
||||
import com.bbit.ticket.utils.TraceIdKey
|
||||
import com.bbit.ticket.database.system.SysApiAccessLogTable
|
||||
import io.ktor.server.application.Application
|
||||
import io.ktor.server.application.ApplicationCall
|
||||
import io.ktor.server.application.createApplicationPlugin
|
||||
import io.ktor.server.application.install
|
||||
import io.ktor.server.auth.jwt.JWTPrincipal
|
||||
import io.ktor.server.auth.principal
|
||||
import io.ktor.server.request.httpMethod
|
||||
import io.ktor.server.request.path
|
||||
import io.ktor.util.AttributeKey
|
||||
import org.jetbrains.exposed.v1.jdbc.insert
|
||||
import java.time.OffsetDateTime
|
||||
import kotlin.time.TimeSource
|
||||
|
||||
private val accessLogStartMarkKey = AttributeKey<TimeSource.Monotonic.ValueTimeMark>("api-access-start")
|
||||
private val accessLogWrittenKey = io.ktor.util.AttributeKey<Boolean>("api-access-written")
|
||||
|
||||
fun Application.configureApiAccessLog() {
|
||||
install(
|
||||
createApplicationPlugin("ApiAccessLogPlugin") {
|
||||
onCall { call ->
|
||||
if (!call.request.path().startsWith("/api/")) return@onCall
|
||||
call.attributes.put(accessLogStartMarkKey, TimeSource.Monotonic.markNow())
|
||||
}
|
||||
|
||||
// 统一记录 API 访问日志,业务操作日志由各模块在写操作成功/失败后补充。
|
||||
onCallRespond { call, _ ->
|
||||
if (!call.request.path().startsWith("/api/")) return@onCallRespond
|
||||
if (call.attributes.getOrNull(accessLogWrittenKey) == true) return@onCallRespond
|
||||
writeAccessLog(call, null)
|
||||
call.attributes.put(accessLogWrittenKey, true)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun writeAccessLog(
|
||||
call: ApplicationCall,
|
||||
errorMessage: String?,
|
||||
) = dbQuery {
|
||||
val startedAt = call.attributes.getOrNull(accessLogStartMarkKey)
|
||||
val costMs = startedAt?.elapsedNow()?.inWholeMilliseconds ?: 0L
|
||||
val traceId = call.attributes.getOrNull(TraceIdKey)
|
||||
val requestPath = call.request.path().take(255)
|
||||
val principal = call.principal<JWTPrincipal>()
|
||||
val appKeyFromHeader = call.request.headers["X-App-Key"]?.take(100)
|
||||
val appNameFromHeader = call.request.headers["X-App-Name"]?.take(100)
|
||||
val appNameFromUser = principal?.payload?.getClaim("username")?.asString()?.take(100)
|
||||
val responseCode = call.response.status()?.value?.toString()
|
||||
val statusCode = call.response.status()?.value ?: 200
|
||||
val statusForStore = if (statusCode >= 400) "FAIL" else "SUCCESS"
|
||||
|
||||
SysApiAccessLogTable.insert {
|
||||
it[SysApiAccessLogTable.traceId] = traceId?.take(64)
|
||||
it[appKey] = appKeyFromHeader
|
||||
it[appName] = appNameFromHeader ?: appNameFromUser
|
||||
it[httpMethod] = call.request.httpMethod.value.take(20)
|
||||
it[SysApiAccessLogTable.requestPath] = requestPath
|
||||
it[requestHeaders] = maskedHeaders(call.request.headers.entries())
|
||||
it[requestBody] = null
|
||||
it[SysApiAccessLogTable.responseCode] = responseCode?.take(50)
|
||||
it[responseBody] = null
|
||||
it[ip] = call.request.local.remoteHost.take(64)
|
||||
it[SysApiAccessLogTable.status] = statusForStore
|
||||
it[SysApiAccessLogTable.errorMessage] = errorMessage?.take(500)
|
||||
it[SysApiAccessLogTable.costMs] = costMs
|
||||
it[createdAt] = OffsetDateTime.now()
|
||||
}
|
||||
}
|
||||
|
||||
private fun maskedHeaders(entries: Set<Map.Entry<String, List<String>>>): String {
|
||||
if (entries.isEmpty()) return ""
|
||||
val content = entries.joinToString("&") { (key, values) ->
|
||||
val value = if (key.equals("Authorization", ignoreCase = true)) "***" else values.joinToString(",")
|
||||
"${key.lowercase()}=${value.take(120)}"
|
||||
}
|
||||
return content.take(2000)
|
||||
}
|
||||
Reference in New Issue
Block a user