修复apiresult的问题;增加用户详情查看的逻辑

This commit is contained in:
BBIT-Kai
2026-05-21 14:55:08 +08:00
parent 40f1c27e71
commit 3506efe894
7 changed files with 503 additions and 33 deletions
@@ -12,6 +12,7 @@ import com.bbit.ticket.entity.common.BizException
import com.bbit.ticket.entity.common.ErrorCode
import com.bbit.ticket.entity.common.PageResult
import com.bbit.ticket.entity.common.statusLabel
import com.bbit.ticket.entity.common.system.UserRoleBrief
import com.bbit.ticket.utils.ApiKeyUtil
import io.ktor.http.HttpStatusCode
import org.jetbrains.exposed.v1.core.Op
@@ -90,10 +91,26 @@ object UserDao {
fun detail(id: Uuid): UserDetailResponse {
val user = requireActive(id)
val roleIds = SysUserRoleTable.selectAll()
.where { SysUserRoleTable.userId eq id }
.map { it[SysUserRoleTable.roleId].toString() }
return user.toUserDetail(roleIds)
val roles = (SysUserRoleTable innerJoin SysRoleTable)
.selectAll()
.where {
(SysUserRoleTable.userId eq id) and
SysRoleTable.deletedAt.isNull()
}
.map {
UserRoleBrief(
id = it[SysRoleTable.id].toString(),
name = it[SysRoleTable.name],
code = it[SysRoleTable.code],
)
}
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)
}
fun updateProfile(id: Uuid, request: UpdateUserRequest, orgId: Uuid?) {
@@ -226,7 +243,11 @@ object UserDao {
apiKey = this[SysUserTable.apiKey],
)
private fun ResultRow.toUserDetail(roleIds: List<String>) = UserDetailResponse(
private fun ResultRow.toUserDetail(
roleIds: List<String>,
roles: List<UserRoleBrief>,
org: ResultRow?,
) = UserDetailResponse(
id = this[SysUserTable.id].toString(),
username = this[SysUserTable.username],
nickname = this[SysUserTable.nickname],
@@ -235,13 +256,38 @@ object UserDao {
email = this[SysUserTable.email],
avatar = this[SysUserTable.avatar],
orgId = this[SysUserTable.orgId]?.toString(),
orgName = org?.get(SysOrgTable.name),
orgCode = org?.get(SysOrgTable.code),
status = this[SysUserTable.status],
statusLabel = statusLabel(this[SysUserTable.status]),
roleIds = roleIds,
roles = roles,
apiKey = this[SysUserTable.apiKey],
tokenVersion = this[SysUserTable.tokenVersion],
lastLoginAt = this[SysUserTable.lastLoginAt]?.toString(),
lastLoginIp = this[SysUserTable.lastLoginIp],
createdAt = this[SysUserTable.createdAt].toString(),
createdBy = this[SysUserTable.createdBy]?.toString(),
updatedAt = this[SysUserTable.updatedAt]?.toString(),
updatedBy = this[SysUserTable.updatedBy]?.toString(),
deletedAt = this[SysUserTable.deletedAt]?.toString(),
deletedBy = this[SysUserTable.deletedBy]?.toString(),
version = this[SysUserTable.version],
taxpayerNum = this[SysUserTable.taxpayerNum],
account = this[SysUserTable.taxAccount],
taxPassword = this[SysUserTable.taxPassword],
taxIdentityType = this[SysUserTable.taxIdentityType],
taxContactName = this[SysUserTable.taxContactName],
taxContactPhone = this[SysUserTable.taxContactPhone],
taxContactEmail = this[SysUserTable.taxContactEmail],
taxLegalPersonName = this[SysUserTable.taxLegalPersonName],
taxEnterpriseName = this[SysUserTable.taxEnterpriseName],
taxRegionCode = this[SysUserTable.taxRegionCode],
taxCityName = this[SysUserTable.taxCityName],
taxEnterpriseAddress = this[SysUserTable.taxEnterpriseAddress],
taxRegistrationCertificate = this[SysUserTable.taxRegistrationCertificate],
bankName = this[SysUserTable.bankName],
bankAccount = this[SysUserTable.bankAccount],
presetAddress = this[SysUserTable.presetAddress],
presetPhone = this[SysUserTable.presetPhone],
)
}
@@ -24,14 +24,46 @@ data class UserDetailResponse(
val email: String? = null,
val avatar: String? = null,
val orgId: String? = null,
val orgName: String? = null,
val orgCode: String? = null,
val status: String,
val statusLabel: String,
val roleIds: List<String>,
val roles: List<UserRoleBrief> = emptyList(),
val apiKey: String? = null,
val tokenVersion: Int,
val lastLoginAt: String? = null,
val lastLoginIp: String? = null,
val createdAt: String? = null,
val createdBy: String? = null,
val updatedAt: String? = null,
val updatedBy: String? = null,
val deletedAt: String? = null,
val deletedBy: String? = null,
val version: Int,
val taxpayerNum: String? = null,
val account: String? = null,
val taxPassword: String? = null,
val taxIdentityType: String? = null,
val taxContactName: String? = null,
val taxContactPhone: String? = null,
val taxContactEmail: String? = null,
val taxLegalPersonName: String? = null,
val taxEnterpriseName: String? = null,
val taxRegionCode: String? = null,
val taxCityName: String? = null,
val taxEnterpriseAddress: String? = null,
val taxRegistrationCertificate: String? = null,
val bankName: String? = null,
val bankAccount: String? = null,
val presetAddress: String? = null,
val presetPhone: String? = null,
)
@Serializable
data class UserRoleBrief(
val id: String,
val name: String,
val code: String,
)
@Serializable
@@ -12,9 +12,10 @@ import com.bbit.ticket.service.openapi.OpenBlueInvoiceService
import com.bbit.ticket.service.system.ApiAccessLogService
import com.bbit.ticket.utils.requireOpenApiPrincipal
import com.bbit.ticket.utils.plugins.myJson
import io.ktor.http.ContentType
import io.ktor.server.application.ApplicationCall
import io.ktor.server.request.receive
import io.ktor.server.response.respond
import io.ktor.server.response.respondText
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import io.ktor.server.routing.post
@@ -92,20 +93,23 @@ private suspend inline fun <reified T> ApplicationCall.respondOpenApi(
try {
val response = ok(block())
val responseBody = myJson.encodeToString(response)
respond(response)
respondText(responseBody, ContentType.Application.Json)
saveOpenApiLog(appKey, appName, requestBody, response.code, responseBody, "SUCCESS", null, start)
} catch (e: PTException) {
val response = fail(code = e.code, message = e.message, traceId = e.serialNo)
respond(response)
saveOpenApiLog(appKey, appName, requestBody, e.code, myJson.encodeToString(response), "FAILED", e.message, start)
val responseBody = myJson.encodeToString(response)
respondText(responseBody, ContentType.Application.Json)
saveOpenApiLog(appKey, appName, requestBody, e.code, responseBody, "FAILED", e.message, start)
} catch (e: BizException) {
val response = fail(code = e.errorCode, message = e.message)
respond(e.status, response)
saveOpenApiLog(appKey, appName, requestBody, e.errorCode, myJson.encodeToString(response), "FAILED", e.message, start)
val responseBody = myJson.encodeToString(response)
respondText(responseBody, ContentType.Application.Json, e.status)
saveOpenApiLog(appKey, appName, requestBody, e.errorCode, responseBody, "FAILED", e.message, start)
} catch (e: Exception) {
val response = fail(code = "-1", message = e.message ?: "开放接口调用失败")
respond(response)
saveOpenApiLog(appKey, appName, requestBody, "-1", myJson.encodeToString(response), "FAILED", e.message, start)
val responseBody = myJson.encodeToString(response)
respondText(responseBody, ContentType.Application.Json)
saveOpenApiLog(appKey, appName, requestBody, "-1", responseBody, "FAILED", e.message, start)
}
}
@@ -4,12 +4,15 @@ 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.utils.plugins.myJson
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.ApplicationCall
import io.ktor.server.response.header
import io.ktor.server.response.respond
import io.ktor.server.response.respondBytes
import io.ktor.server.response.respondText
import kotlinx.serialization.encodeToString
/**
* 使用统一票通响应格式执行接口逻辑。
@@ -22,13 +25,39 @@ suspend inline fun <reified T> ApplicationCall.respondPt(
crossinline block: suspend () -> T,
) {
try {
respond(ok(block()))
respondJson(ok(block()))
} catch (e: PTException) {
respond(fail(code = e.code, message = e.message, traceId = e.serialNo))
respondJson(fail(code = e.code, message = e.message, traceId = e.serialNo))
} catch (e: BizException) {
respond(e.status, fail(code = e.errorCode, message = e.message))
respondJson(fail(code = e.errorCode, message = e.message), e.status)
} catch (e: Exception) {
respond(fail(code = "-1", message = e.message ?: fallbackMessage))
respondJson(fail(code = "-1", message = e.message ?: fallbackMessage))
}
}
/**
* 使用统一票通响应格式执行可空查询,空结果按空对象返回。
*
* @param fallbackMessage 未知异常时返回给前端的兜底提示。
* @param block 当前接口要执行的查询逻辑。
*/
suspend inline fun <reified T : Any> ApplicationCall.respondPtOrEmptyObject(
fallbackMessage: String,
crossinline block: suspend () -> T?,
) {
try {
val data = block()
if (data == null) {
respondJson(ok(emptyMap<String, String>()))
} else {
respondJson(ok(data))
}
} catch (e: PTException) {
respondJson(fail(code = e.code, message = e.message, traceId = e.serialNo))
} catch (e: BizException) {
respondJson(fail(code = e.errorCode, message = e.message), e.status)
} catch (e: Exception) {
respondJson(fail(code = "-1", message = e.message ?: fallbackMessage))
}
}
@@ -41,7 +70,7 @@ suspend inline fun <reified T> ApplicationCall.respondPt(
suspend fun ApplicationCall.requiredQueryParameter(name: String, message: String): String? {
val value = request.queryParameters[name]?.trim()?.takeIf { it.isNotEmpty() }
if (value == null) {
respond(fail(code = "-1", message = message))
respondJson(fail(code = "-1", message = message))
}
return value
}
@@ -62,10 +91,23 @@ suspend fun ApplicationCall.respondPtPdf(
response.header(HttpHeaders.ContentDisposition, "inline; filename=\"$filename\"")
respondBytes(block(), ContentType.Application.Pdf)
} catch (e: PTException) {
respond(fail(code = e.code, message = e.message, traceId = e.serialNo))
respondJson(fail(code = e.code, message = e.message, traceId = e.serialNo))
} catch (e: BizException) {
respond(e.status, fail(code = e.errorCode, message = e.message))
respondJson(fail(code = e.errorCode, message = e.message), e.status)
} catch (e: Exception) {
respond(fail(code = "-1", message = e.message ?: fallbackMessage))
respondJson(fail(code = "-1", message = e.message ?: fallbackMessage))
}
}
/**
* 显式序列化 JSON 响应,避免 Ktor 在泛型 helper 中丢失 ApiResult<T> 类型信息。
*
* @param data 要返回的响应对象。
* @param status HTTP 状态码。
*/
suspend inline fun <reified T> ApplicationCall.respondJson(
data: T,
status: HttpStatusCode = HttpStatusCode.OK,
) {
respondText(myJson.encodeToString(data), ContentType.Application.Json, status)
}
@@ -57,8 +57,8 @@ fun Route.registerPTAuthRoutes() {
}
get("/enterprise") {
call.respondPt("查询企业信息失败") {
PTConfigService.getEnterpriseInfo(call.requireCurrentUser().id) ?: emptyMap<String, String>()
call.respondPtOrEmptyObject("查询企业信息失败") {
PTConfigService.getEnterpriseInfo(call.requireCurrentUser().id)
}
}
@@ -72,12 +72,12 @@ fun Route.registerPTAuthRoutes() {
}
get("/digital-account") {
call.respondPt("查询数电账号失败") {
call.respondPtOrEmptyObject("查询数电账号失败") {
val currentUser = call.requireCurrentUser()
if (currentUser.taxPayerNum == null) {
throw BizException("-1", "请先完善用户信息", HttpStatusCode.OK)
}
PTConfigService.getDigitalAccount(currentUser.id) ?: emptyMap<String, String>()
PTConfigService.getDigitalAccount(currentUser.id)
}
}
@@ -91,8 +91,8 @@ fun Route.registerPTAuthRoutes() {
}
get("/preset") {
call.respondPt("查询预设数据失败") {
PTConfigService.getPresetData(call.requireCurrentUser().id) ?: emptyMap<String, String>()
call.respondPtOrEmptyObject("查询预设数据失败") {
PTConfigService.getPresetData(call.requireCurrentUser().id)
}
}