From 9fd80980e7a52e5a7bc1da156b4404b3971d9ae6 Mon Sep 17 00:00:00 2001 From: BBIT-Kai <2911862937@qq.com> Date: Wed, 27 May 2026 09:10:54 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=A7=E6=94=B9=E4=B8=89=E8=A7=92=E8=89=B2?= =?UTF-8?q?=E9=80=BB=E8=BE=91=EF=BC=9A=E8=B6=85=E7=BA=A7=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=91=98=E3=80=81=E4=BC=81=E4=B8=9A=E7=AE=A1=E7=90=86=E5=91=98?= =?UTF-8?q?=E3=80=81=E5=BC=80=E7=A5=A8=E5=91=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/bbit/ticket/Application.kt | 8 +- .../dao/piaotong/EnterpriseManageDao.kt | 46 +++ .../com/bbit/ticket/dao/system/DictDao.kt | 181 ---------- .../com/bbit/ticket/dao/system/LogDao.kt | 39 +-- .../com/bbit/ticket/dao/system/OrgDao.kt | 122 ------- .../com/bbit/ticket/dao/system/UserDao.kt | 59 ++-- .../database/system/SysDictItemTable.kt | 28 -- .../database/system/SysDictTypeTable.kt | 24 -- .../database/system/SysOperationLogTable.kt | 4 +- .../ticket/database/system/SysOrgTable.kt | 25 -- .../ticket/database/system/SysUserTable.kt | 1 - .../ticket/entity/common/DisplayLabels.kt | 4 +- .../bbit/ticket/entity/common/ErrorCode.kt | 3 - .../ticket/entity/common/system/AuthDto.kt | 1 - .../ticket/entity/common/system/DictDto.kt | 53 --- .../ticket/entity/common/system/LogDto.kt | 15 - .../ticket/entity/common/system/OrgDto.kt | 31 -- .../ticket/entity/common/system/UserDto.kt | 14 +- .../response/EnterpriseManageResponse.kt | 21 ++ .../ticket/route/piaotong/PTRouteSupport.kt | 54 +++ .../registerOpenInvoiceTaskManageRoutes.kt | 10 +- .../route/piaotong/registerPTAuthRoutes.kt | 43 ++- .../route/piaotong/registerPTInvoiceRoutes.kt | 15 +- .../ticket/route/system/registerDictRoutes.kt | 128 ------- .../system/registerEnterpriseManageRoutes.kt | 31 ++ .../route/system/registerLogsQueryRoutes.kt | 6 - .../ticket/route/system/registerOrgRoutes.kt | 73 ---- .../ticket/route/system/registerUserRoutes.kt | 2 +- .../service/openapi/OpenInvoiceTaskService.kt | 146 ++++---- .../bbit/ticket/service/system/AuthService.kt | 8 +- .../bbit/ticket/service/system/DictService.kt | 65 ---- .../service/system/EnterpriseManageService.kt | 11 + .../bbit/ticket/service/system/JwtService.kt | 4 +- .../ticket/service/system/LogsQueryService.kt | 4 - .../bbit/ticket/service/system/OrgService.kt | 56 ---- .../bbit/ticket/service/system/UserService.kt | 26 +- .../bbit/ticket/utils/RequirePermission.kt | 3 +- .../bbit/ticket/utils/SecurityPrincipal.kt | 49 +-- .../utils/bootstrap/DatabaseInitializer.kt | 6 - .../bbit/ticket/utils/bootstrap/SeedData.kt | 317 +++++++++--------- .../utils/plugins/ApiAccessLogPlugin.kt | 81 ----- web/src/api/logs.ts | 11 +- web/src/api/system/dict.ts | 61 ---- web/src/api/system/enterprise.ts | 6 + web/src/api/system/org.ts | 18 - web/src/components/AppMenu.vue | 7 +- web/src/components/AppTabs.vue | 16 +- web/src/features/dashboard/index.vue | 105 +++++- web/src/features/logs/api-access/index.vue | 142 -------- .../piaotong/digital-accounts/index.vue | 180 ++++++++-- .../features/piaotong/invoice-issue/index.vue | 104 +++--- web/src/features/system/dicts/index.vue | 237 ------------- web/src/features/system/enterprises/index.vue | 141 ++++++++ web/src/features/system/menus/index.vue | 1 - web/src/features/system/orgs/index.vue | 238 ------------- web/src/features/system/roles/index.vue | 4 +- web/src/features/system/users/index.vue | 64 +++- web/src/layouts/MainLayout.vue | 30 +- web/src/stores/auth.ts | 28 +- web/src/style.css | 46 ++- web/src/types/auth.ts | 1 - web/src/types/logs.ts | 15 - web/src/types/system/dict.ts | 25 -- web/src/types/system/enterprise.ts | 29 ++ web/src/types/system/org.ts | 25 -- web/src/types/system/user.ts | 16 +- web/src/utils/display.ts | 4 +- 67 files changed, 1141 insertions(+), 2230 deletions(-) delete mode 100644 server/src/main/kotlin/com/bbit/ticket/dao/system/DictDao.kt delete mode 100644 server/src/main/kotlin/com/bbit/ticket/dao/system/OrgDao.kt delete mode 100644 server/src/main/kotlin/com/bbit/ticket/database/system/SysDictItemTable.kt delete mode 100644 server/src/main/kotlin/com/bbit/ticket/database/system/SysDictTypeTable.kt delete mode 100644 server/src/main/kotlin/com/bbit/ticket/database/system/SysOrgTable.kt delete mode 100644 server/src/main/kotlin/com/bbit/ticket/entity/common/system/DictDto.kt delete mode 100644 server/src/main/kotlin/com/bbit/ticket/entity/common/system/OrgDto.kt delete mode 100644 server/src/main/kotlin/com/bbit/ticket/route/system/registerDictRoutes.kt create mode 100644 server/src/main/kotlin/com/bbit/ticket/route/system/registerEnterpriseManageRoutes.kt delete mode 100644 server/src/main/kotlin/com/bbit/ticket/route/system/registerOrgRoutes.kt delete mode 100644 server/src/main/kotlin/com/bbit/ticket/service/system/DictService.kt create mode 100644 server/src/main/kotlin/com/bbit/ticket/service/system/EnterpriseManageService.kt delete mode 100644 server/src/main/kotlin/com/bbit/ticket/service/system/OrgService.kt delete mode 100644 server/src/main/kotlin/com/bbit/ticket/utils/plugins/ApiAccessLogPlugin.kt delete mode 100644 web/src/api/system/dict.ts create mode 100644 web/src/api/system/enterprise.ts delete mode 100644 web/src/api/system/org.ts delete mode 100644 web/src/features/logs/api-access/index.vue delete mode 100644 web/src/features/system/dicts/index.vue create mode 100644 web/src/features/system/enterprises/index.vue delete mode 100644 web/src/features/system/orgs/index.vue delete mode 100644 web/src/types/system/dict.ts create mode 100644 web/src/types/system/enterprise.ts delete mode 100644 web/src/types/system/org.ts diff --git a/server/src/main/kotlin/com/bbit/ticket/Application.kt b/server/src/main/kotlin/com/bbit/ticket/Application.kt index b26b944..e475704 100644 --- a/server/src/main/kotlin/com/bbit/ticket/Application.kt +++ b/server/src/main/kotlin/com/bbit/ticket/Application.kt @@ -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") { diff --git a/server/src/main/kotlin/com/bbit/ticket/dao/piaotong/EnterpriseManageDao.kt b/server/src/main/kotlin/com/bbit/ticket/dao/piaotong/EnterpriseManageDao.kt index f9bf13b..f802212 100644 --- a/server/src/main/kotlin/com/bbit/ticket/dao/piaotong/EnterpriseManageDao.kt +++ b/server/src/main/kotlin/com/bbit/ticket/dao/piaotong/EnterpriseManageDao.kt @@ -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 { + var where: Op = 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() } diff --git a/server/src/main/kotlin/com/bbit/ticket/dao/system/DictDao.kt b/server/src/main/kotlin/com/bbit/ticket/dao/system/DictDao.kt deleted file mode 100644 index cb67b33..0000000 --- a/server/src/main/kotlin/com/bbit/ticket/dao/system/DictDao.kt +++ /dev/null @@ -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 { - 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 { - var where: Op = 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 { - var where: Op = 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], - ) -} diff --git a/server/src/main/kotlin/com/bbit/ticket/dao/system/LogDao.kt b/server/src/main/kotlin/com/bbit/ticket/dao/system/LogDao.kt index ee4047e..df19344 100644 --- a/server/src/main/kotlin/com/bbit/ticket/dao/system/LogDao.kt +++ b/server/src/main/kotlin/com/bbit/ticket/dao/system/LogDao.kt @@ -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 { - var where: Op = 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 diff --git a/server/src/main/kotlin/com/bbit/ticket/dao/system/OrgDao.kt b/server/src/main/kotlin/com/bbit/ticket/dao/system/OrgDao.kt deleted file mode 100644 index 3409d13..0000000 --- a/server/src/main/kotlin/com/bbit/ticket/dao/system/OrgDao.kt +++ /dev/null @@ -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 { - 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): List { - val byParent = nodes.groupBy { it.parentId } - fun children(parentId: Uuid?): List = - (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, - ) -} diff --git a/server/src/main/kotlin/com/bbit/ticket/dao/system/UserDao.kt b/server/src/main/kotlin/com/bbit/ticket/dao/system/UserDao.kt index ad7fb31..ebe6d3a 100644 --- a/server/src/main/kotlin/com/bbit/ticket/dao/system/UserDao.kt +++ b/server/src/main/kotlin/com/bbit/ticket/dao/system/UserDao.kt @@ -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 { - 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 = @@ -231,7 +231,7 @@ object UserDao { private fun activeWhere(): Op = SysUserTable.deletedAt.isNull() - private fun buildWhere(username: String?, nickname: String?, status: String?, orgId: Uuid?): Op { + private fun buildWhere(username: String?, nickname: String?, status: String?, enterpriseId: Uuid?): Op { var where: Op = 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): Map> { 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) = UserListItem( + private fun ResultRow.toUserListItem(roleCodes: List, 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, roles: List, - 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, diff --git a/server/src/main/kotlin/com/bbit/ticket/database/system/SysDictItemTable.kt b/server/src/main/kotlin/com/bbit/ticket/database/system/SysDictItemTable.kt deleted file mode 100644 index a4047a3..0000000 --- a/server/src/main/kotlin/com/bbit/ticket/database/system/SysDictItemTable.kt +++ /dev/null @@ -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) -} diff --git a/server/src/main/kotlin/com/bbit/ticket/database/system/SysDictTypeTable.kt b/server/src/main/kotlin/com/bbit/ticket/database/system/SysDictTypeTable.kt deleted file mode 100644 index a71ca5b..0000000 --- a/server/src/main/kotlin/com/bbit/ticket/database/system/SysDictTypeTable.kt +++ /dev/null @@ -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) -} diff --git a/server/src/main/kotlin/com/bbit/ticket/database/system/SysOperationLogTable.kt b/server/src/main/kotlin/com/bbit/ticket/database/system/SysOperationLogTable.kt index ea33043..6839a36 100644 --- a/server/src/main/kotlin/com/bbit/ticket/database/system/SysOperationLogTable.kt +++ b/server/src/main/kotlin/com/bbit/ticket/database/system/SysOperationLogTable.kt @@ -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) -} \ No newline at end of file +} diff --git a/server/src/main/kotlin/com/bbit/ticket/database/system/SysOrgTable.kt b/server/src/main/kotlin/com/bbit/ticket/database/system/SysOrgTable.kt deleted file mode 100644 index 9c79398..0000000 --- a/server/src/main/kotlin/com/bbit/ticket/database/system/SysOrgTable.kt +++ /dev/null @@ -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) -} diff --git a/server/src/main/kotlin/com/bbit/ticket/database/system/SysUserTable.kt b/server/src/main/kotlin/com/bbit/ticket/database/system/SysUserTable.kt index 6c5675b..ea50874 100644 --- a/server/src/main/kotlin/com/bbit/ticket/database/system/SysUserTable.kt +++ b/server/src/main/kotlin/com/bbit/ticket/database/system/SysUserTable.kt @@ -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() diff --git a/server/src/main/kotlin/com/bbit/ticket/entity/common/DisplayLabels.kt b/server/src/main/kotlin/com/bbit/ticket/entity/common/DisplayLabels.kt index 311cbee..d329ce2 100644 --- a/server/src/main/kotlin/com/bbit/ticket/entity/common/DisplayLabels.kt +++ b/server/src/main/kotlin/com/bbit/ticket/entity/common/DisplayLabels.kt @@ -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 } diff --git a/server/src/main/kotlin/com/bbit/ticket/entity/common/ErrorCode.kt b/server/src/main/kotlin/com/bbit/ticket/entity/common/ErrorCode.kt index f6f7c75..e1c0f3e 100644 --- a/server/src/main/kotlin/com/bbit/ticket/entity/common/ErrorCode.kt +++ b/server/src/main/kotlin/com/bbit/ticket/entity/common/ErrorCode.kt @@ -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", "服务器内部错误"), } diff --git a/server/src/main/kotlin/com/bbit/ticket/entity/common/system/AuthDto.kt b/server/src/main/kotlin/com/bbit/ticket/entity/common/system/AuthDto.kt index 30e7966..c29fda6 100644 --- a/server/src/main/kotlin/com/bbit/ticket/entity/common/system/AuthDto.kt +++ b/server/src/main/kotlin/com/bbit/ticket/entity/common/system/AuthDto.kt @@ -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", diff --git a/server/src/main/kotlin/com/bbit/ticket/entity/common/system/DictDto.kt b/server/src/main/kotlin/com/bbit/ticket/entity/common/system/DictDto.kt deleted file mode 100644 index 67bcb66..0000000 --- a/server/src/main/kotlin/com/bbit/ticket/entity/common/system/DictDto.kt +++ /dev/null @@ -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, -) diff --git a/server/src/main/kotlin/com/bbit/ticket/entity/common/system/LogDto.kt b/server/src/main/kotlin/com/bbit/ticket/entity/common/system/LogDto.kt index 4ad0b0d..6b16477 100644 --- a/server/src/main/kotlin/com/bbit/ticket/entity/common/system/LogDto.kt +++ b/server/src/main/kotlin/com/bbit/ticket/entity/common/system/LogDto.kt @@ -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, -) diff --git a/server/src/main/kotlin/com/bbit/ticket/entity/common/system/OrgDto.kt b/server/src/main/kotlin/com/bbit/ticket/entity/common/system/OrgDto.kt deleted file mode 100644 index 41da171..0000000 --- a/server/src/main/kotlin/com/bbit/ticket/entity/common/system/OrgDto.kt +++ /dev/null @@ -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 = 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", -) diff --git a/server/src/main/kotlin/com/bbit/ticket/entity/common/system/UserDto.kt b/server/src/main/kotlin/com/bbit/ticket/entity/common/system/UserDto.kt index 176aa21..3828183 100644 --- a/server/src/main/kotlin/com/bbit/ticket/entity/common/system/UserDto.kt +++ b/server/src/main/kotlin/com/bbit/ticket/entity/common/system/UserDto.kt @@ -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, @@ -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, @@ -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 diff --git a/server/src/main/kotlin/com/bbit/ticket/entity/response/EnterpriseManageResponse.kt b/server/src/main/kotlin/com/bbit/ticket/entity/response/EnterpriseManageResponse.kt index f841a36..510e96d 100644 --- a/server/src/main/kotlin/com/bbit/ticket/entity/response/EnterpriseManageResponse.kt +++ b/server/src/main/kotlin/com/bbit/ticket/entity/response/EnterpriseManageResponse.kt @@ -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, +) diff --git a/server/src/main/kotlin/com/bbit/ticket/route/piaotong/PTRouteSupport.kt b/server/src/main/kotlin/com/bbit/ticket/route/piaotong/PTRouteSupport.kt index 8e2a9b5..88edf9d 100644 --- a/server/src/main/kotlin/com/bbit/ticket/route/piaotong/PTRouteSupport.kt +++ b/server/src/main/kotlin/com/bbit/ticket/route/piaotong/PTRouteSupport.kt @@ -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 ApplicationCall.respondPt( } } +suspend inline fun 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, + ) + } +} + /** * 使用统一票通响应格式执行可空查询,空结果按空对象返回。 * diff --git a/server/src/main/kotlin/com/bbit/ticket/route/piaotong/registerOpenInvoiceTaskManageRoutes.kt b/server/src/main/kotlin/com/bbit/ticket/route/piaotong/registerOpenInvoiceTaskManageRoutes.kt index df8bd24..aec2ad2 100644 --- a/server/src/main/kotlin/com/bbit/ticket/route/piaotong/registerOpenInvoiceTaskManageRoutes.kt +++ b/server/src/main/kotlin/com/bbit/ticket/route/piaotong/registerOpenInvoiceTaskManageRoutes.kt @@ -31,9 +31,10 @@ fun Route.registerOpenInvoiceTaskManageRoutes() { post("/openapi/tasks/queues/{digitalAccountId}/pause") { val digitalAccountId = call.parameters["digitalAccountId"].orEmpty() val body = call.receiveNullable>() ?: 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) } } } diff --git a/server/src/main/kotlin/com/bbit/ticket/route/piaotong/registerPTAuthRoutes.kt b/server/src/main/kotlin/com/bbit/ticket/route/piaotong/registerPTAuthRoutes.kt index fa1aa04..15ef8f6 100644 --- a/server/src/main/kotlin/com/bbit/ticket/route/piaotong/registerPTAuthRoutes.kt +++ b/server/src/main/kotlin/com/bbit/ticket/route/piaotong/registerPTAuthRoutes.kt @@ -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()) + val currentUser = call.requireCurrentUser() + call.respondPtWithOperationLog("新增数电账号失败", currentUser, "CREATE", "新增数电账号") { + PTConfigService.createDigitalAccount(currentUser, call.receive()) } } 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(), ) @@ -92,8 +96,9 @@ fun Route.registerPTAuthRoutes() { } put("/preset") { - call.respondPt("保存预设数据失败") { - PTConfigService.updateInvoiceSetting(call.requireCurrentUser(), call.receive()) + val currentUser = call.requireCurrentUser() + call.respondPtWithOperationLog("保存预设数据失败", currentUser, "UPDATE", "保存开票预设数据") { + PTConfigService.updateInvoiceSetting(currentUser, call.receive()) } } @@ -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()) } } post("/send-sms-code") { - call.respondPt("发送登录短信验证码失败") { + val currentUser = call.requireCurrentUser() + call.respondPtWithOperationLog("发送登录短信验证码失败", currentUser, "SEND_SMS_CODE", "发送登录短信验证码") { val req = call.receive() - 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() - PTConfigService.requireDigitalAccountForLogin(call.requireCurrentUser(), req.taxpayerNum, req.account) + PTConfigService.requireDigitalAccountForLogin(currentUser, req.taxpayerNum, req.account) PTAuthService.smsLogin(req) } } diff --git a/server/src/main/kotlin/com/bbit/ticket/route/piaotong/registerPTInvoiceRoutes.kt b/server/src/main/kotlin/com/bbit/ticket/route/piaotong/registerPTInvoiceRoutes.kt index ee46f0f..103a0fc 100644 --- a/server/src/main/kotlin/com/bbit/ticket/route/piaotong/registerPTInvoiceRoutes.kt +++ b/server/src/main/kotlin/com/bbit/ticket/route/piaotong/registerPTInvoiceRoutes.kt @@ -21,14 +21,16 @@ import kotlin.uuid.ExperimentalUuidApi */ fun Route.registerPTInvoiceRoutes() { post("/invoiceRed") { - call.respondPt("红字任务创建失败") { - PTRedService.invoiceRed(call.requireCurrentUser(), call.receive()) + val currentUser = call.requireCurrentUser() + call.respondPtWithOperationLog("红字任务创建失败", currentUser, "CREATE_RED_INVOICE", "创建红字任务") { + PTRedService.invoiceRed(currentUser, call.receive()) } } post("/invoiceBlue") { - call.respondPt("蓝票任务创建失败") { - PTBlueService.invoiceBlue(call.receive(), call.requireCurrentUser()) + val currentUser = call.requireCurrentUser() + call.respondPtWithOperationLog("蓝票任务创建失败", currentUser, "CREATE_BLUE_INVOICE", "创建蓝票任务") { + PTBlueService.invoiceBlue(call.receive(), 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) } } diff --git a/server/src/main/kotlin/com/bbit/ticket/route/system/registerDictRoutes.kt b/server/src/main/kotlin/com/bbit/ticket/route/system/registerDictRoutes.kt deleted file mode 100644 index a802154..0000000 --- a/server/src/main/kotlin/com/bbit/ticket/route/system/registerDictRoutes.kt +++ /dev/null @@ -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() - 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() - runCatching { - DictService.updateType(id, request) - call.respond(ok(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(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() - 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() - runCatching { - DictService.updateItem(id, request) - call.respond(ok(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(message = "删除成功")) - OperationLogService.success(call, currentUser, "DELETE", "删除字典项", start.elapsedNow().inWholeMilliseconds) - }.onFailure { - OperationLogService.fail(call, currentUser, "DELETE", "删除字典项", it.message, start.elapsedNow().inWholeMilliseconds) - throw it - } - } - } - } -} diff --git a/server/src/main/kotlin/com/bbit/ticket/route/system/registerEnterpriseManageRoutes.kt b/server/src/main/kotlin/com/bbit/ticket/route/system/registerEnterpriseManageRoutes.kt new file mode 100644 index 0000000..2515eae --- /dev/null +++ b/server/src/main/kotlin/com/bbit/ticket/route/system/registerEnterpriseManageRoutes.kt @@ -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), + ) + ) + ) + } + } + } +} diff --git a/server/src/main/kotlin/com/bbit/ticket/route/system/registerLogsQueryRoutes.kt b/server/src/main/kotlin/com/bbit/ticket/route/system/registerLogsQueryRoutes.kt index ddfea56..08d73fd 100644 --- a/server/src/main/kotlin/com/bbit/ticket/route/system/registerLogsQueryRoutes.kt +++ b/server/src/main/kotlin/com/bbit/ticket/route/system/registerLogsQueryRoutes.kt @@ -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")))) - } } } } diff --git a/server/src/main/kotlin/com/bbit/ticket/route/system/registerOrgRoutes.kt b/server/src/main/kotlin/com/bbit/ticket/route/system/registerOrgRoutes.kt deleted file mode 100644 index c7018a4..0000000 --- a/server/src/main/kotlin/com/bbit/ticket/route/system/registerOrgRoutes.kt +++ /dev/null @@ -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() - 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() - runCatching { - OrgService.update(id, request) - call.respond(ok(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(message = "删除成功")) - OperationLogService.success(call, currentUser, "DELETE", "删除组织", start.elapsedNow().inWholeMilliseconds) - }.onFailure { - OperationLogService.fail(call, currentUser, "DELETE", "删除组织", it.message, start.elapsedNow().inWholeMilliseconds) - throw it - } - } - } - } -} diff --git a/server/src/main/kotlin/com/bbit/ticket/route/system/registerUserRoutes.kt b/server/src/main/kotlin/com/bbit/ticket/route/system/registerUserRoutes.kt index 2b6f7c6..71f0056 100644 --- a/server/src/main/kotlin/com/bbit/ticket/route/system/registerUserRoutes.kt +++ b/server/src/main/kotlin/com/bbit/ticket/route/system/registerUserRoutes.kt @@ -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)) } diff --git a/server/src/main/kotlin/com/bbit/ticket/service/openapi/OpenInvoiceTaskService.kt b/server/src/main/kotlin/com/bbit/ticket/service/openapi/OpenInvoiceTaskService.kt index 3850290..c0c8419 100644 --- a/server/src/main/kotlin/com/bbit/ticket/service/openapi/OpenInvoiceTaskService.kt +++ b/server/src/main/kotlin/com/bbit/ticket/service/openapi/OpenInvoiceTaskService.kt @@ -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 { 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) } diff --git a/server/src/main/kotlin/com/bbit/ticket/service/system/AuthService.kt b/server/src/main/kotlin/com/bbit/ticket/service/system/AuthService.kt index 1e1e45b..545b8b6 100644 --- a/server/src/main/kotlin/com/bbit/ticket/service/system/AuthService.kt +++ b/server/src/main/kotlin/com/bbit/ticket/service/system/AuthService.kt @@ -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)) } } diff --git a/server/src/main/kotlin/com/bbit/ticket/service/system/DictService.kt b/server/src/main/kotlin/com/bbit/ticket/service/system/DictService.kt deleted file mode 100644 index 3663e7a..0000000 --- a/server/src/main/kotlin/com/bbit/ticket/service/system/DictService.kt +++ /dev/null @@ -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 = - 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 = - 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) - } -} diff --git a/server/src/main/kotlin/com/bbit/ticket/service/system/EnterpriseManageService.kt b/server/src/main/kotlin/com/bbit/ticket/service/system/EnterpriseManageService.kt new file mode 100644 index 0000000..513ec98 --- /dev/null +++ b/server/src/main/kotlin/com/bbit/ticket/service/system/EnterpriseManageService.kt @@ -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 = + dbQuery { EnterpriseManageDao.list(keyword, page, pageSize) } +} diff --git a/server/src/main/kotlin/com/bbit/ticket/service/system/JwtService.kt b/server/src/main/kotlin/com/bbit/ticket/service/system/JwtService.kt index 7759680..d8e8a09 100644 --- a/server/src/main/kotlin/com/bbit/ticket/service/system/JwtService.kt +++ b/server/src/main/kotlin/com/bbit/ticket/service/system/JwtService.kt @@ -10,7 +10,6 @@ object JwtService { fun issueAccessToken( userId: String, username: String, - orgId: String?, roles: List, tokenVersion: Int, ): Pair { @@ -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 } -} \ No newline at end of file +} diff --git a/server/src/main/kotlin/com/bbit/ticket/service/system/LogsQueryService.kt b/server/src/main/kotlin/com/bbit/ticket/service/system/LogsQueryService.kt index 031356b..a671ce5 100644 --- a/server/src/main/kotlin/com/bbit/ticket/service/system/LogsQueryService.kt +++ b/server/src/main/kotlin/com/bbit/ticket/service/system/LogsQueryService.kt @@ -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 = dbQuery { LogDao.operationLogs(page, pageSize, keyword, status) } - - suspend fun apiAccessLogs(page: Int, pageSize: Int, keyword: String?, status: String?): PageResult = - dbQuery { LogDao.apiAccessLogs(page, pageSize, keyword, status) } } diff --git a/server/src/main/kotlin/com/bbit/ticket/service/system/OrgService.kt b/server/src/main/kotlin/com/bbit/ticket/service/system/OrgService.kt deleted file mode 100644 index 3f338c3..0000000 --- a/server/src/main/kotlin/com/bbit/ticket/service/system/OrgService.kt +++ /dev/null @@ -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 = 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) - } -} diff --git a/server/src/main/kotlin/com/bbit/ticket/service/system/UserService.kt b/server/src/main/kotlin/com/bbit/ticket/service/system/UserService.kt index 13dd867..4628a60 100644 --- a/server/src/main/kotlin/com/bbit/ticket/service/system/UserService.kt +++ b/server/src/main/kotlin/com/bbit/ticket/service/system/UserService.kt @@ -25,8 +25,8 @@ object UserService { username: String?, nickname: String?, status: String?, - orgId: Uuid?, - ): PageResult = dbQuery { UserDao.list(page, pageSize, username, nickname, status, orgId) } + enterpriseId: Uuid?, + ): PageResult = 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, "企业不存在") } } } diff --git a/server/src/main/kotlin/com/bbit/ticket/utils/RequirePermission.kt b/server/src/main/kotlin/com/bbit/ticket/utils/RequirePermission.kt index a7282e2..95c777a 100644 --- a/server/src/main/kotlin/com/bbit/ticket/utils/RequirePermission.kt +++ b/server/src/main/kotlin/com/bbit/ticket/utils/RequirePermission.kt @@ -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) } - diff --git a/server/src/main/kotlin/com/bbit/ticket/utils/SecurityPrincipal.kt b/server/src/main/kotlin/com/bbit/ticket/utils/SecurityPrincipal.kt index 9371fa7..1bcee26 100644 --- a/server/src/main/kotlin/com/bbit/ticket/utils/SecurityPrincipal.kt +++ b/server/src/main/kotlin/com/bbit/ticket/utils/SecurityPrincipal.kt @@ -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], diff --git a/server/src/main/kotlin/com/bbit/ticket/utils/bootstrap/DatabaseInitializer.kt b/server/src/main/kotlin/com/bbit/ticket/utils/bootstrap/DatabaseInitializer.kt index b39d29f..e0e37b6 100644 --- a/server/src/main/kotlin/com/bbit/ticket/utils/bootstrap/DatabaseInitializer.kt +++ b/server/src/main/kotlin/com/bbit/ticket/utils/bootstrap/DatabaseInitializer.kt @@ -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, diff --git a/server/src/main/kotlin/com/bbit/ticket/utils/bootstrap/SeedData.kt b/server/src/main/kotlin/com/bbit/ticket/utils/bootstrap/SeedData.kt index 0d202b7..142e66d 100644 --- a/server/src/main/kotlin/com/bbit/ticket/utils/bootstrap/SeedData.kt +++ b/server/src/main/kotlin/com/bbit/ticket/utils/bootstrap/SeedData.kt @@ -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): List = + 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): List = 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) - 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) diff --git a/server/src/main/kotlin/com/bbit/ticket/utils/plugins/ApiAccessLogPlugin.kt b/server/src/main/kotlin/com/bbit/ticket/utils/plugins/ApiAccessLogPlugin.kt deleted file mode 100644 index f1bbcf8..0000000 --- a/server/src/main/kotlin/com/bbit/ticket/utils/plugins/ApiAccessLogPlugin.kt +++ /dev/null @@ -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("api-access-start") -private val accessLogWrittenKey = io.ktor.util.AttributeKey("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() - 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>>): 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) -} diff --git a/web/src/api/logs.ts b/web/src/api/logs.ts index b9ece48..93b0d07 100644 --- a/web/src/api/logs.ts +++ b/web/src/api/logs.ts @@ -1,5 +1,5 @@ import http from '@/api/http' -import type { ApiAccessLogPage, OperationLogPage } from '@/types/logs' +import type { OperationLogPage } from '@/types/logs' export function listOperationLogsApi(params: { page: number @@ -9,12 +9,3 @@ export function listOperationLogsApi(params: { }) { return http.get('/logs/operation', { params }) } - -export function listApiAccessLogsApi(params: { - page: number - pageSize: number - keyword?: string - status?: string -}) { - return http.get('/logs/api-access', { params }) -} diff --git a/web/src/api/system/dict.ts b/web/src/api/system/dict.ts deleted file mode 100644 index 6d256a0..0000000 --- a/web/src/api/system/dict.ts +++ /dev/null @@ -1,61 +0,0 @@ -import http from '@/api/http' -import type { DictItemPage, DictTypePage } from '@/types/system/dict' - -export function listDictTypesApi(params: { page: number; pageSize: number; keyword?: string }) { - return http.get('/system/dict-types', { params }) -} - -export function createDictTypeApi(payload: { - code: string - name: string - status: string - remark?: string -}) { - return http.post('/system/dict-types', payload) -} - -export function updateDictTypeApi( - id: string, - payload: { name: string; status: string; remark?: string } -) { - return http.put(`/system/dict-types/${id}`, payload) -} - -export function deleteDictTypeApi(id: string) { - return http.delete(`/system/dict-types/${id}`) -} - -export function listDictItemsApi(params: { page: number; pageSize: number; typeId?: string }) { - return http.get('/system/dict-items', { params }) -} - -export function createDictItemApi(payload: { - typeId: string - label: string - value: string - color?: string - sort: number - status: string - remark?: string -}) { - return http.post('/system/dict-items', payload) -} - -export function updateDictItemApi( - id: string, - payload: { - typeId: string - label: string - value: string - color?: string - sort: number - status: string - remark?: string - } -) { - return http.put(`/system/dict-items/${id}`, payload) -} - -export function deleteDictItemApi(id: string) { - return http.delete(`/system/dict-items/${id}`) -} diff --git a/web/src/api/system/enterprise.ts b/web/src/api/system/enterprise.ts new file mode 100644 index 0000000..34f9e54 --- /dev/null +++ b/web/src/api/system/enterprise.ts @@ -0,0 +1,6 @@ +import http from '@/api/http' +import type { EnterpriseManagePage, EnterpriseQuery } from '@/types/system/enterprise' + +export function listEnterprisesApi(params: EnterpriseQuery) { + return http.get('/system/enterprises', { params }) +} diff --git a/web/src/api/system/org.ts b/web/src/api/system/org.ts deleted file mode 100644 index 35e5c4b..0000000 --- a/web/src/api/system/org.ts +++ /dev/null @@ -1,18 +0,0 @@ -import http from '@/api/http' -import type { CreateOrgRequest, OrgTreeNode, UpdateOrgRequest } from '@/types/system/org' - -export function listOrgsApi() { - return http.get('/system/orgs') -} - -export function createOrgApi(payload: CreateOrgRequest) { - return http.post('/system/orgs', payload) -} - -export function updateOrgApi(id: string, payload: UpdateOrgRequest) { - return http.put(`/system/orgs/${id}`, payload) -} - -export function deleteOrgApi(id: string) { - return http.delete(`/system/orgs/${id}`) -} diff --git a/web/src/components/AppMenu.vue b/web/src/components/AppMenu.vue index 825f4ef..0400735 100644 --- a/web/src/components/AppMenu.vue +++ b/web/src/components/AppMenu.vue @@ -108,12 +108,17 @@ function handleSelect(key: string) { } :deep(.n-menu-item-content--selected) { + color: var(--app-primary); font-weight: 600; box-shadow: none; } :deep(.n-menu-item-content--selected::before) { - background: transparent; + background: var(--app-primary-soft); +} + +:deep(.n-menu-item-content:hover::before) { + background: var(--app-primary-subtle); } :deep(.n-menu--collapsed .n-menu-item-content), diff --git a/web/src/components/AppTabs.vue b/web/src/components/AppTabs.vue index 904a178..e7ba29c 100644 --- a/web/src/components/AppTabs.vue +++ b/web/src/components/AppTabs.vue @@ -41,7 +41,7 @@ function onClose(path: string) { .tabs-shell { margin-top: 12px; padding-top: 10px; - border-top: 1px solid #eef1f5; + border-top: 1px solid var(--app-border); } .tabs-track { @@ -64,9 +64,9 @@ function onClose(path: string) { height: 32px; padding: 0 12px; border: 1px solid transparent; - border-radius: 9px; + border-radius: 8px; background: transparent; - color: #6b7280; + color: var(--app-text-muted); cursor: pointer; transition: background 0.16s ease, @@ -75,13 +75,15 @@ function onClose(path: string) { } .tab-chip:hover { - background: #f3f4f6; - color: #111827; + background: var(--app-primary-soft); + color: var(--app-primary); } .tab-chip-active { - background: #111827; - color: #ffffff; + border-color: transparent; + background: var(--app-primary-soft); + color: var(--app-primary); + font-weight: 600; } .tab-title { diff --git a/web/src/features/dashboard/index.vue b/web/src/features/dashboard/index.vue index 635ec96..3e4f2ae 100644 --- a/web/src/features/dashboard/index.vue +++ b/web/src/features/dashboard/index.vue @@ -1,5 +1,5 @@ diff --git a/web/src/features/logs/api-access/index.vue b/web/src/features/logs/api-access/index.vue deleted file mode 100644 index 6d0d7ed..0000000 --- a/web/src/features/logs/api-access/index.vue +++ /dev/null @@ -1,142 +0,0 @@ - - - diff --git a/web/src/features/piaotong/digital-accounts/index.vue b/web/src/features/piaotong/digital-accounts/index.vue index 851b07a..abee72b 100644 --- a/web/src/features/piaotong/digital-accounts/index.vue +++ b/web/src/features/piaotong/digital-accounts/index.vue @@ -74,10 +74,23 @@ - - - {{ smsTip }} - + + + + {{ smsTip }} + + + 发送验证码 + @@ -109,11 +146,15 @@ import { NGrid, NIcon, NInput, + NInputGroup, NModal, NPopconfirm, + NRadioButton, + NRadioGroup, NSelect, NSpace, NTag, + NTooltip, useMessage } from 'naive-ui' import { KeyRound, Plus, RefreshCw, ShieldCheck } from 'lucide-vue-next' @@ -138,15 +179,22 @@ const showCreate = ref(false) const showSms = ref(false) const showQrcode = ref(false) const smsLoading = ref(false) +const smsCodeLoading = ref(false) const qrLoading = ref(false) const smsCode = ref('') const smsTip = ref('') const qrcodeImg = ref('') +const qrcodeType = ref('1') +const qrcodeGenTime = ref('') +const qrcodeTip = ref('') const selected = ref(null) const createFormRef = ref(null) const canManage = computed(() => authStore.user?.userType !== 'DIGITAL_OPERATOR') const canUpdateStatus = computed(() => authStore.hasPermission('digital-account:status')) +const qrcodeTypeLabel = computed(() => + qrcodeType.value === '2' ? '国家网络身份认证 APP' : '电子税务局 APP' +) const createForm = reactive({ account: '', @@ -175,7 +223,13 @@ const identityOptions = [ const columns: DataTableColumns = [ { title: '税局账号', key: 'account', minWidth: 140 }, - { title: '平台登录账号', key: 'platformUsername', minWidth: 180, render: (row) => row.platformUsername || '-' }, + { + title: '平台登录账号', + key: 'platformUsername', + width: 180, + ellipsis: true, + render: (row) => renderEllipsisText(row.platformUsername) + }, { title: '姓名', key: 'name', minWidth: 100 }, { title: '登录身份', key: 'identityType', render: (row) => identityLabel(row.identityType) }, { title: '账号状态', key: 'authStatus', render: (row) => authStatusLabel(row.authStatus) }, @@ -214,8 +268,9 @@ const columns: DataTableColumns = [ { title: 'API Key', key: 'apiKey', - minWidth: 180, - render: (row) => row.apiKey || '-' + width: 180, + ellipsis: true, + render: (row) => renderEllipsisText(row.apiKey) }, { title: '操作', @@ -282,6 +337,18 @@ function authStatusLabel(value?: string | null) { return value ? map[value] || value : '-' } +function renderEllipsisText(value?: string | null) { + if (!value) return '-' + return h( + NTooltip, + { trigger: 'hover' }, + { + trigger: () => h('span', { class: 'cell-ellipsis' }, value), + default: () => value + } + ) +} + async function load() { loading.value = true try { @@ -324,13 +391,21 @@ async function updateAccountStatus(row: DigitalAccountItem) { async function openSms(row: DigitalAccountItem) { selected.value = row smsCode.value = '' - smsLoading.value = true + smsTip.value = '' showSms.value = true +} + +async function sendSmsCode() { + if (!selected.value) return + smsCodeLoading.value = true try { - const res = await sendLoginSmsCodeApi({ taxpayerNum: row.taxpayerNum, account: row.account }) + const res = await sendLoginSmsCodeApi({ + taxpayerNum: selected.value.taxpayerNum, + account: selected.value.account + }) smsTip.value = res.phoneNum ? `验证码已发送至 ${res.phoneNum}` : res.resultMsg } finally { - smsLoading.value = false + smsCodeLoading.value = false } } @@ -353,6 +428,10 @@ async function submitSms() { async function openQrcode(row: DigitalAccountItem) { selected.value = row + qrcodeType.value = '1' + qrcodeImg.value = '' + qrcodeGenTime.value = '' + qrcodeTip.value = '' showQrcode.value = true await loadQrcode() } @@ -361,8 +440,11 @@ async function loadQrcode() { if (!selected.value) return qrLoading.value = true try { - const res = await getAuthQrcodeApi('1', selected.value.id) + const res = await getAuthQrcodeApi(qrcodeType.value, selected.value.id) qrcodeImg.value = res.qrcodeImg ? `data:image/png;base64,${res.qrcodeImg}` : '' + qrcodeType.value = res.qrcodeType || qrcodeType.value + qrcodeGenTime.value = res.genTime || '' + qrcodeTip.value = res.resultMsg || '' } finally { qrLoading.value = false } @@ -388,27 +470,81 @@ onMounted(load) flex-shrink: 0; } +.cell-ellipsis { + display: inline-block; + width: 148px; + max-width: 148px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + vertical-align: bottom; +} + .modal-card { width: min(720px, 92vw); } -.sms-card { - width: min(420px, 92vw); -} - .modal-actions { justify-content: flex-end; + margin-top: 10px; + padding-top: 12px; + border-top: 1px solid #eef1f5; +} + +.auth-panel { + width: 100%; +} + +.auth-account { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 8px 10px; + border: 1px solid #eef1f5; + border-radius: 8px; + background: #fafafa; +} + +.auth-account span { + color: #6b7280; + font-size: 12px; +} + +.auth-account strong { + color: #111827; + font-size: 13px; + font-weight: 600; + word-break: break-all; } .qrcode-wrap { display: flex; flex-direction: column; align-items: center; - gap: 12px; + justify-content: center; + min-height: 236px; + border: 1px solid #eef1f5; + border-radius: 8px; + background: #fff; } .qrcode-wrap img { width: 220px; height: 220px; } + +.qrcode-meta { + display: flex; + flex-direction: column; + gap: 2px; + color: #6b7280; + font-size: 12px; + text-align: center; +} + +.qrcode-meta span { + color: #111827; + font-weight: 600; +} diff --git a/web/src/features/piaotong/invoice-issue/index.vue b/web/src/features/piaotong/invoice-issue/index.vue index b417e1d..8b5839d 100644 --- a/web/src/features/piaotong/invoice-issue/index.vue +++ b/web/src/features/piaotong/invoice-issue/index.vue @@ -1,15 +1,17 @@