fix(i18n): 通道与平台业务异常硬编码中文国际化(23处) + kickout异常吞没修复

- channel 支付/关闭 8 处 + platform 文件/会话/Token/Nonce 15 处: IllegalStateException/RuntimeException 改为 BizInfoException(code, messageKey)

- 新增 i18n key (zh-CN+en-US): common/wechat/alipay/douyin/file/session

- 修复 OnlineUserService.kickout 的 catch(Exception) 吞没业务异常, 加 BizException 透传
This commit is contained in:
DaxPay Dev
2026-07-04 23:12:53 +08:00
parent c8855cf11d
commit acaf80dcde
21 changed files with 102 additions and 47 deletions

View File

@@ -6,7 +6,9 @@ import cn.daxpay.open.channel.alipay.client.req.AlipayCloseReq;
import cn.daxpay.open.channel.alipay.client.resp.AlipayCloseResp;
import cn.daxpay.open.payment.common.result.DaxResult;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.platform.core.code.DaxPayErrorCode;
import cn.daxpay.open.platform.core.enums.pay.pay.CloseTypeEnum;
import cn.daxpay.open.platform.core.exception.BizInfoException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -39,7 +41,7 @@ public class AlipayCloseService {
// 调用子应用
DaxResult<AlipayCloseResp> result = alipayChannelClient.close(req);
if (result.getCode() != 0) {
throw new IllegalStateException("支付宝通道关闭失败: " + result.getMsg());
throw new BizInfoException(DaxPayErrorCode.OPERATION_FAIL, "error.channel.alipay.closeFailed", result.getMsg());
}
return useCancel ? CloseTypeEnum.CANCEL : CloseTypeEnum.CLOSE;

View File

@@ -10,8 +10,11 @@ import cn.daxpay.open.payment.common.result.DaxResult;
import cn.daxpay.open.payment.core.trade.bo.PayTradeResultBo;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayParam;
import cn.daxpay.open.platform.core.code.CommonErrorCode;
import cn.daxpay.open.platform.core.code.DaxPayErrorCode;
import cn.daxpay.open.platform.core.enums.pay.channel.PayMethodEnum;
import cn.daxpay.open.platform.core.enums.unipay.PayBodyTypeEnum;
import cn.daxpay.open.platform.core.exception.BizInfoException;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -52,7 +55,7 @@ public class AlipayPayService {
// 调用子应用
DaxResult<AlipayPayResp> result = alipayChannelClient.pay(req);
if (result.getCode() != 0) {
throw new IllegalStateException("支付宝通道支付失败: " + result.getMsg());
throw new BizInfoException(DaxPayErrorCode.TRADE_FAIL, "error.channel.alipay.payFailed", result.getMsg());
}
return toPayResult(result.getData());
@@ -68,8 +71,8 @@ public class AlipayPayService {
case ALIPAY_QR, ALIPAY_ORDER_QR -> AlipayPayMethod.QR;
case ALIPAY_BARCODE -> AlipayPayMethod.BARCODE;
case ALIPAY_JSAPI, ALIPAY_MINI -> AlipayPayMethod.JSAPI;
default -> throw new UnsupportedOperationException(
"暂不支持的支付宝支付方式: " + methodCode);
default -> throw new BizInfoException(CommonErrorCode.UN_SUPPORTED_OPERATE,
"error.channel.alipay.notSupportMethod", methodCode);
};
}

View File

@@ -7,7 +7,9 @@ import cn.daxpay.open.payment.core.strategy.pay.PayStrategyContext;
import cn.daxpay.open.payment.common.result.DaxResult;
import cn.daxpay.open.payment.core.trade.bo.PayTradeResultBo;
import cn.daxpay.open.payment.core.strategy.pay.AbsNormalPayStrategy;
import cn.daxpay.open.platform.core.code.DaxPayErrorCode;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import cn.daxpay.open.platform.core.exception.BizInfoException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -45,7 +47,7 @@ public class DouyinDirectPayStrategy extends AbsNormalPayStrategy {
// 调用子应用
DaxResult<DouyinPayResp> result = douyinChannelClient.pay(req);
if (result.getCode() != 0) {
throw new IllegalStateException("抖音通道支付失败: " + result.getMsg());
throw new BizInfoException(DaxPayErrorCode.TRADE_FAIL, "error.channel.douyin.payFailed", result.getMsg());
}
// 解析响应

View File

@@ -6,7 +6,9 @@ import cn.daxpay.open.channel.wechat.client.req.WechatCloseReq;
import cn.daxpay.open.channel.wechat.client.resp.WechatCloseResp;
import cn.daxpay.open.payment.common.result.DaxResult;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.platform.core.code.DaxPayErrorCode;
import cn.daxpay.open.platform.core.enums.pay.pay.CloseTypeEnum;
import cn.daxpay.open.platform.core.exception.BizInfoException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -39,7 +41,7 @@ public class WechatCloseService {
// 调用子应用
DaxResult<WechatCloseResp> result = wechatChannelClient.close(req);
if (result.getCode() != 0) {
throw new IllegalStateException("微信通道关闭失败: " + result.getMsg());
throw new BizInfoException(DaxPayErrorCode.OPERATION_FAIL, "error.channel.wechat.closeFailed", result.getMsg());
}
// 微信仅支持关闭, 无撤销

View File

@@ -10,8 +10,11 @@ import cn.daxpay.open.payment.common.result.DaxResult;
import cn.daxpay.open.payment.core.trade.bo.PayTradeResultBo;
import cn.daxpay.open.payment.core.trade.entity.PayTrade;
import cn.daxpay.open.payment.unipay.param.trade.pay.NormalPayParam;
import cn.daxpay.open.platform.core.code.CommonErrorCode;
import cn.daxpay.open.platform.core.code.DaxPayErrorCode;
import cn.daxpay.open.platform.core.enums.pay.channel.PayMethodEnum;
import cn.daxpay.open.platform.core.enums.unipay.PayBodyTypeEnum;
import cn.daxpay.open.platform.core.exception.BizInfoException;
import cn.daxpay.open.platform.system.service.config.PlatformUrlConfigService;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
@@ -63,7 +66,7 @@ public class WechatPayService {
// 调用子应用
DaxResult<WechatPayResp> result = wechatChannelClient.pay(req);
if (result.getCode() != 0) {
throw new IllegalStateException("微信通道支付失败: " + result.getMsg());
throw new BizInfoException(DaxPayErrorCode.TRADE_FAIL, "error.channel.wechat.payFailed", result.getMsg());
}
return toPayResult(result.getData());
@@ -77,7 +80,7 @@ public class WechatPayService {
String base = platformUrlConfigService.getUrlConfig().getBackendBaseUrl();
if (StrUtil.isBlank(base)) {
// backendBaseUrl 未配置时抛清晰异常, 避免 null 透传到微信报模糊的必填校验错误
throw new IllegalStateException("平台后端访问地址(backendBaseUrl)未配置, 无法生成微信回调地址");
throw new BizInfoException(DaxPayErrorCode.CONFIG_ERROR, "error.common.backendBaseUrlNotConfigured");
}
return StrUtil.format("{}/unipay/callback/{}/{}/wechat/pay",
base, order.getMchNo(), order.getAppId());
@@ -93,8 +96,8 @@ public class WechatPayService {
case WECHAT_APP -> WechatPayMethod.APP;
case WECHAT_H5 -> WechatPayMethod.H5;
case WECHAT_BARCODE -> WechatPayMethod.MICROPAY;
default -> throw new UnsupportedOperationException(
"暂不支持的微信支付方式: " + methodCode);
default -> throw new BizInfoException(CommonErrorCode.UN_SUPPORTED_OPERATE,
"error.channel.wechat.unsupportedPayMethod", methodCode);
};
}

View File

@@ -8,6 +8,9 @@ import cn.daxpay.open.platform.capability.file.entity.PlatformFileRecord;
import cn.daxpay.open.platform.capability.file.param.FileUploadConfirmParam;
import cn.daxpay.open.platform.capability.file.param.FileUploadPresignParam;
import cn.daxpay.open.platform.capability.file.result.FileUploadPresignResult;
import cn.daxpay.open.platform.core.code.CommonErrorCode;
import cn.daxpay.open.platform.core.code.DaxPayErrorCode;
import cn.daxpay.open.platform.core.exception.BizInfoException;
import cn.daxpay.open.platform.core.exception.DataNotExistException;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
@@ -115,12 +118,12 @@ public class PlatformFileService {
.orElseThrow(() -> new DataNotExistException("error.file.recordNotExist"));
if (!FileUploadStatusEnum.PENDING.getCode().equals(platformFileRecord.getStatus())) {
throw new IllegalStateException("文件状态不正确,无法确认");
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "error.file.statusInvalidForConfirm");
}
String objectKey = buildObjectKey(platformFileRecord.getPath(), platformFileRecord.getFilename());
if (!objectKey.equals(param.getObjectKey())) {
throw new IllegalArgumentException("对象Key不匹配");
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "error.file.objectKeyMismatch");
}
var storageConfig = s3FileStorageService.getStorageConfig()
@@ -131,7 +134,7 @@ public class PlatformFileService {
boolean exists = s3FileStorageService.exists(bucket, param.getObjectKey());
if (!exists) {
throw new IllegalStateException("文件尚未上传到存储");
throw new BizInfoException(DaxPayErrorCode.OPERATION_FAIL, "error.file.notUploadedToStorage");
}
if (param.getFileSize() != null && param.getFileSize() > 0) {
@@ -163,7 +166,7 @@ public class PlatformFileService {
.orElseThrow(() -> new DataNotExistException("error.file.recordNotExist"));
if (!FileUploadStatusEnum.UPLOADED.getCode().equals(platformFileRecord.getStatus())) {
throw new IllegalStateException("文件未上传完成,无法访问");
throw new BizInfoException(DaxPayErrorCode.OPERATION_FAIL, "error.file.notUploadedForAccess");
}
FileStorageConfig storageConfig = s3FileStorageService.getStorageConfig()
@@ -195,7 +198,7 @@ public class PlatformFileService {
.orElseThrow(() -> new DataNotExistException("error.file.recordNotExist"));
if (!FileUploadStatusEnum.UPLOADED.getCode().equals(platformFileRecord.getStatus())) {
throw new IllegalStateException("文件未上传完成,无法下载");
throw new BizInfoException(DaxPayErrorCode.OPERATION_FAIL, "error.file.notUploadedForDownload");
}
FileStorageConfig storageConfig = s3FileStorageService.getStorageConfig()
@@ -227,7 +230,7 @@ public class PlatformFileService {
.orElseThrow(() -> new DataNotExistException("error.file.recordNotExist"));
if (!FileUploadStatusEnum.UPLOADED.getCode().equals(platformFileRecord.getStatus())) {
throw new IllegalStateException("文件未上传完成,无法下载");
throw new BizInfoException(DaxPayErrorCode.OPERATION_FAIL, "error.file.notUploadedForDownload");
}
FileStorageConfig storageConfig = s3FileStorageService.getStorageConfig()

View File

@@ -66,7 +66,7 @@ public class NonceVerificationAspect {
private HttpServletRequest getRequest() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
throw new NonceMissingException("无法获取请求上下文");
throw new NonceMissingException("error.common.requestContextMissing");
}
return attributes.getRequest();
}

View File

@@ -1,5 +1,7 @@
package cn.daxpay.open.platform.capability.wechat.token.service;
import cn.daxpay.open.platform.core.code.CommonErrorCode;
import cn.daxpay.open.platform.core.exception.BizInfoException;
import cn.hutool.core.util.StrUtil;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
@@ -71,7 +73,7 @@ public class WechatTokenService {
if (StrUtil.isNotBlank(token)) {
return token;
}
throw new RuntimeException("获取AccessToken失败无法获取分布式锁");
throw new BizInfoException(CommonErrorCode.SYSTEM_ERROR, "error.channel.wechat.accessTokenLockFailed");
}
try {
@@ -90,11 +92,11 @@ public class WechatTokenService {
newToken = wxMpService.getAccessToken();
} catch (WxErrorException e) {
log.error("调用微信API获取AccessToken失败wxAppId: {}, 错误: {}", wxAppId, e.getMessage());
throw new RuntimeException("刷新AccessToken失败: " + e.getMessage(), e);
throw new BizInfoException(CommonErrorCode.SYSTEM_ERROR, "error.channel.wechat.refreshAccessTokenFailed", e.getMessage());
}
if (StrUtil.isBlank(newToken)) {
throw new RuntimeException("刷新AccessToken失败返回的Token为空");
throw new BizInfoException(CommonErrorCode.SYSTEM_ERROR, "error.channel.wechat.refreshAccessTokenEmpty");
}
// 缓存新Token

View File

@@ -3,7 +3,7 @@
"wechatNotEnabled": "Merchant or service provider WeChat Pay channel not enabled",
"productConfigNotExist": "Service provider product payment configuration not found",
"noSubMchPerm": "You do not have permission to configure this sub merchant",
"closeFailed": "Close order failed",
"closeFailed": "Alipay channel close failed: {0}",
"closeFailedReason": "Close failed, reason: {0}",
"cannotCloseSyncFirst": "Current transaction cannot be closed, please sync order status first",
"refundQueryInterval": "Please wait more than 10 seconds before retrying refund query",
@@ -15,16 +15,14 @@
"alipayJsapiFailed": "Alipay JSAPI payment failed",
"alipayScanFailed": "Alipay scan payment failed",
"alipayBscanFailed": "Alipay barcode payment failed",
"payFailed": "Payment failed",
"payFailed": "Alipay channel payment failed: {0}",
"notSupportCancel": "This order does not support cancel operation",
"notSupportMethod": "Unsupported payment method",
"notSupportMethod": "Unsupported Alipay payment method: {0}",
"wechatJsapiNeedOpenid": "WeChat JSAPI payment requires openId parameter",
"wechatBarcodeNeedAuthCode": "WeChat barcode payment requires auth code parameter",
"wechatH5Failed": "WeChat V3 H5 payment failed",
"wechatAppFailed": "WeChat V3 APP payment failed",
"wechatJsapiFailed": "WeChat V3 JSAPI payment failed",
"wechatScanFailed": "WeChat V3 scan payment failed",
"wechatBarcodeV3Failed": "WeChat V3 barcode payment failed",
"wechatCancelV3Failed": "WeChat V3 cancel payment failed",
"privateKeyRequired": "Application private key is required",
"alipayPublicKeyRequired": "Alipay public key is required",

View File

@@ -3,5 +3,6 @@
"appIdDuplicate": "Douyin AppId already exists under this channel merchant",
"directMchDuplicate": "This Douyin direct merchant already exists under the merchant",
"appTypeInvalid": "Invalid Douyin application type",
"capabilityDuplicate": "Payment capability [{0}] is duplicated under this channel merchant"
"capabilityDuplicate": "Payment capability [{0}] is duplicated under this channel merchant",
"payFailed": "Douyin channel payment failed: {0}"
}

View File

@@ -2,12 +2,10 @@
"notEnabled": "WeChat Pay is not enabled",
"refundV3Failed": "WeChat refund V3 failed: {0}",
"jsapiNeedOpenid": "WeChat JSAPI payment requires openId",
"barcodeNeedAuthCode": "WeChat barcode payment requires authCode",
"h5Failed": "WeChat H5 payment failed: {0}",
"appFailed": "WeChat APP payment failed: {0}",
"jsapiFailed": "WeChat JSAPI payment failed: {0}",
"scanFailed": "WeChat scan payment failed: {0}",
"barcodeV3Failed": "WeChat barcode V3 payment failed: {0}",
"cancelV3Failed": "WeChat cancel V3 failed: {0}",
"apiV3KeyRequired": "API V3 key is required",
"privateKeyRequired": "Merchant private key is required",
@@ -45,5 +43,11 @@
"privateCertRequired": "API certificate is required",
"subMerchantDuplicate": "This sub-merchant already exists under the WeChat application",
"directMchDuplicate": "The WeChat merchant already exists under this merchant",
"capabilityDuplicate": "WeChat payment capability [{0}] is duplicated"
"capabilityDuplicate": "WeChat payment capability [{0}] is duplicated",
"payFailed": "WeChat channel payment failed: {0}",
"closeFailed": "WeChat channel close failed: {0}",
"unsupportedPayMethod": "Unsupported WeChat payment method: {0}",
"accessTokenLockFailed": "Failed to get AccessToken: cannot acquire distributed lock",
"refreshAccessTokenFailed": "Failed to refresh AccessToken: {0}",
"refreshAccessTokenEmpty": "Failed to refresh AccessToken: returned token is empty"
}

View File

@@ -28,5 +28,7 @@
"payCallTypeNotSupported": "Unsupported checkout type: {0}",
"channelAuthTypeNotExist": "Auth type not found: {0}",
"mchAppStatusNotFound": "Merchant app status not found: {0}",
"storeStatusNotFound": "Store status not found: {0}"
"storeStatusNotFound": "Store status not found: {0}",
"backendBaseUrlNotConfigured": "Backend base URL (backendBaseUrl) is not configured, cannot generate callback URL",
"requestContextMissing": "Cannot obtain request context"
}

View File

@@ -1,5 +1,10 @@
{
"storageConfigNotExist": "Storage configuration not found",
"recordNotExist": "File record not found",
"platformRecordNotExist": "Platform file record not found"
"platformRecordNotExist": "Platform file record not found",
"statusInvalidForConfirm": "Invalid file status, cannot confirm",
"objectKeyMismatch": "Object key mismatch",
"notUploadedToStorage": "File has not been uploaded to storage",
"notUploadedForAccess": "File upload not completed, cannot access",
"notUploadedForDownload": "File upload not completed, cannot download"
}

View File

@@ -0,0 +1,6 @@
{
"sessionNotExistOrExpired": "Session does not exist or has expired",
"invalidSessionId": "Invalid session ID",
"cannotKickoutAdmin": "Cannot force super administrator offline",
"kickoutFailed": "Failed to force user offline: {0}"
}

View File

@@ -3,7 +3,7 @@
"wechatNotEnabled": "商户或服务商微信支付通道未启用",
"productConfigNotExist": "服务商产品支付配置不存在",
"noSubMchPerm": "你没有权限配置此子商户号",
"closeFailed": "关闭订单失败",
"closeFailed": "支付宝通道关闭失败: {0}",
"closeFailedReason": "关闭失败, 原因: {0}",
"cannotCloseSyncFirst": "当前交易无法关闭操作, 请对订单同步状态后再进行操作",
"refundQueryInterval": "间隔10秒以上再发起退款查询请求",
@@ -15,16 +15,14 @@
"alipayJsapiFailed": "支付宝JsApi支付失败",
"alipayScanFailed": "支付宝扫码支付失败",
"alipayBscanFailed": "主动扫码支付失败",
"payFailed": "支付失败",
"payFailed": "支付宝通道支付失败: {0}",
"notSupportCancel": "该订单不支持撤销操作",
"notSupportMethod": "不支持的支付方式",
"notSupportMethod": "不支持的支付宝支付方式: {0}",
"wechatJsapiNeedOpenid": "微信JSAPI支付必须传入openId参数",
"wechatBarcodeNeedAuthCode": "微信付款码支付必须传入付款码参数",
"wechatH5Failed": "微信V3手机网站支付失败",
"wechatAppFailed": "微信V3程序支付失败",
"wechatJsapiFailed": "微信V3JsApi支付失败",
"wechatScanFailed": "微信V3扫码支付失败",
"wechatBarcodeV3Failed": "微信付款码支付V3失败",
"wechatCancelV3Failed": "微信撤销支付V3失败",
"privateKeyRequired": "应用私钥不可为空",
"alipayPublicKeyRequired": "支付宝公钥不可为空",

View File

@@ -3,5 +3,6 @@
"appIdDuplicate": "同一通道商户下抖音应用ID已存在",
"directMchDuplicate": "同一商户下该抖音直连商户已存在",
"appTypeInvalid": "抖音应用类型无效",
"capabilityDuplicate": "同一通道商户下支付能力[{0}]重复配置"
"capabilityDuplicate": "同一通道商户下支付能力[{0}]重复配置",
"payFailed": "抖音通道支付失败: {0}"
}

View File

@@ -2,12 +2,10 @@
"notEnabled": "微信支付未启用",
"refundV3Failed": "微信退款V3失败: {0}",
"jsapiNeedOpenid": "微信JSAPI支付需要传入openId",
"barcodeNeedAuthCode": "微信付款码支付需要传入authCode",
"h5Failed": "微信H5支付失败: {0}",
"appFailed": "微信APP支付失败: {0}",
"jsapiFailed": "微信JSAPI支付失败: {0}",
"scanFailed": "微信扫码支付失败: {0}",
"barcodeV3Failed": "微信付款码V3支付失败: {0}",
"cancelV3Failed": "微信撤销V3失败: {0}",
"apiV3KeyRequired": "API V3密钥未配置",
"privateKeyRequired": "商户私钥未配置",
@@ -45,5 +43,11 @@
"privateCertRequired": "API证书未配置",
"subMerchantDuplicate": "该微信应用下已存在此特约商户号",
"directMchDuplicate": "同一商户下该微信商户已存在",
"capabilityDuplicate": "微信支付能力[{0}]重复配置"
"capabilityDuplicate": "微信支付能力[{0}]重复配置",
"payFailed": "微信通道支付失败: {0}",
"closeFailed": "微信通道关闭失败: {0}",
"unsupportedPayMethod": "暂不支持的微信支付方式: {0}",
"accessTokenLockFailed": "获取AccessToken失败无法获取分布式锁",
"refreshAccessTokenFailed": "刷新AccessToken失败: {0}",
"refreshAccessTokenEmpty": "刷新AccessToken失败返回的Token为空"
}

View File

@@ -28,5 +28,7 @@
"payCallTypeNotSupported": "不支持的收银台类型: {0}",
"channelAuthTypeNotExist": "认证类型不存在: {0}",
"mchAppStatusNotFound": "未找到对应的商户应用状态类型: {0}",
"storeStatusNotFound": "未找到对应的门店状态类型: {0}"
"storeStatusNotFound": "未找到对应的门店状态类型: {0}",
"backendBaseUrlNotConfigured": "平台后端访问地址(backendBaseUrl)未配置, 无法生成回调地址",
"requestContextMissing": "无法获取请求上下文"
}

View File

@@ -1,5 +1,10 @@
{
"storageConfigNotExist": "存储配置不存在",
"recordNotExist": "文件记录不存在",
"platformRecordNotExist": "平台文件记录不存在"
"platformRecordNotExist": "平台文件记录不存在",
"statusInvalidForConfirm": "文件状态不正确,无法确认",
"objectKeyMismatch": "对象Key不匹配",
"notUploadedToStorage": "文件尚未上传到存储",
"notUploadedForAccess": "文件未上传完成,无法访问",
"notUploadedForDownload": "文件未上传完成,无法下载"
}

View File

@@ -0,0 +1,6 @@
{
"sessionNotExistOrExpired": "会话不存在或已过期",
"invalidSessionId": "无效的会话ID",
"cannotKickoutAdmin": "不允许强制超级管理员下线",
"kickoutFailed": "强制用户下线失败: {0}"
}

View File

@@ -1,7 +1,11 @@
package cn.daxpay.open.platform.iam.service.session;
import cn.daxpay.open.platform.core.code.CommonCode;
import cn.daxpay.open.platform.core.code.CommonErrorCode;
import cn.daxpay.open.platform.core.code.DaxPayErrorCode;
import cn.daxpay.open.platform.core.entity.UserDetail;
import cn.daxpay.open.platform.core.exception.BizException;
import cn.daxpay.open.platform.core.exception.BizInfoException;
import cn.daxpay.open.platform.core.rest.param.PageParam;
import cn.daxpay.open.platform.core.rest.result.PageResult;
import cn.daxpay.open.platform.iam.param.session.OnlineUserQuery;
@@ -128,12 +132,12 @@ public class OnlineUserService {
// 使用 Sa-Token API 获取 session然后获取 loginId
SaSession session = StpUtil.getSessionBySessionId(sessionId);
if (session == null) {
throw new RuntimeException("会话不存在或已过期");
throw new BizInfoException(CommonErrorCode.AUTHENTICATION_FAIL, "error.iam.session.sessionNotExistOrExpired");
}
Object loginId = session.getLoginId();
if (loginId == null) {
throw new RuntimeException("无效的会话ID");
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "error.iam.session.invalidSessionId");
}
Long userId = Long.parseLong(loginId.toString());
@@ -141,16 +145,18 @@ public class OnlineUserService {
// 不允许踢掉超级管理员
UserDetail userDetail = session.getModel(CommonCode.USER, UserDetail.class);
if (userDetail != null && userDetail.isAdmin()) {
throw new RuntimeException("不允许强制超级管理员下线");
throw new BizInfoException(CommonErrorCode.UN_SUPPORTED_OPERATE, "error.iam.session.cannotKickoutAdmin");
}
StpUtil.kickout(userId);
} catch (NumberFormatException e) {
log.error("解析用户ID失败: sessionId={}", sessionId, e);
throw new RuntimeException("无效的会话ID");
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "error.iam.session.invalidSessionId");
} catch (Exception e) {
// 业务异常直接透传, 不兜底为系统错误
if (e instanceof BizException) throw e;
log.error("强制用户下线失败: sessionId={}", sessionId, e);
throw new RuntimeException("强制用户下线失败: " + e.getMessage());
throw new BizInfoException(CommonErrorCode.SYSTEM_ERROR, "error.iam.session.kickoutFailed", e.getMessage());
}
}