{"record":{"id":"088f5d451024a3fe","repo":"dromara/Sa-Token","slug":"12304","errorCode":"12304","errorMessage":"ApiKey 值不可为空","messagePattern":"ApiKey 值不可为空","errorType":"validation","errorClass":"ApiKeyException","httpStatus":null,"severity":"error","filePath":"sa-token-plugin/sa-token-apikey/src/main/java/cn/dev33/satoken/apikey/model/ApiKeyModel.java","lineNumber":143,"sourceCode":"\t\treturn this.extraData.get(key);\n\t}\n\n\t/**\n\t * 删除扩展数据\n\t */\n\tpublic Object removeExtra(String key) {\n\t\tif (this.extraData == null) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this.extraData.remove(key);\n\t}\n\n\t/**\n\t * 数据自检，判断是否可以保存入库\n\t */\n\tpublic void checkByCanSaved() {\n\t\tif (SaFoxUtil.isEmpty(this.apiKey)) {\n\t\t\tthrow new ApiKeyException(\"ApiKey 值不可为空\").setApiKey(apiKey).setCode(SaApiKeyErrorCode.CODE_12304);\n\t\t}\n\t\tif (this.loginId == null) {\n\t\t\tthrow new ApiKeyException(\"无效 ApiKey: \" + apiKey).setApiKey(apiKey).setCode(SaApiKeyErrorCode.CODE_12304);\n\t\t}\n\t\tif (this.createTime == 0) {\n\t\t\tthrow new ApiKeyException(\"请指定 createTime 创建时间\").setApiKey(apiKey).setCode(SaApiKeyErrorCode.CODE_12304);\n\t\t}\n\t\tif (this.expiresTime == 0) {\n\t\t\tthrow new ApiKeyException(\"请指定 expiresTime 过期时间\").setApiKey(apiKey).setCode(SaApiKeyErrorCode.CODE_12304);\n\t\t}\n\t\tif (this.isValid == null) {\n\t\t\tthrow new ApiKeyException(\"请指定 isValid 是否生效\").setApiKey(apiKey).setCode(SaApiKeyErrorCode.CODE_12304);\n\t\t}\n\t}\n\n\t/**\n\t * 获取：此 ApiKey 的剩余有效期（秒）, -1=永不过期\n\t * @return /","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/dromara/Sa-Token/blob/ac2c7f6e94a78573cf0bcb932dd8b04e68fad189/sa-token-plugin/sa-token-apikey/src/main/java/cn/dev33/satoken/apikey/model/ApiKeyModel.java#L125-L161","documentation":"ApiKeyException with error code 12304 thrown by ApiKeyModel.checkByCanSaved() — the pre-persistence sanity check — when the apiKey field is empty (SaFoxUtil.isEmpty). checkByCanSaved is run before saving an ApiKeyModel to storage; the same method also enforces loginId, createTime, expiresTime and isValid, each failing with code 12304 but distinct messages.","triggerScenarios":"Building an ApiKeyModel manually and calling save/create without setting apiKey — e.g. constructing new ApiKeyModel(), setting loginId and times, then calling the manager's create/save which invokes checkByCanSaved(). Only this first check yields the 'ApiKey 值不可为空' message; a null apiKey from a client-supplied payload hits it directly.","commonSituations":"Integrating the sa-token-apikey plugin and hand-assembling the model instead of letting the framework generate the key; trimming/normalizing a client-provided key to empty string before save; API payloads where apiKey is optional on input but required at persistence.","solutions":["Generate a key before saving: use the plugin's key generator (e.g. SaFoxUtil / apiKeyManager random generator) or supply a non-empty unique value.","If the key comes from user input, validate it is non-blank at the API boundary and return a 400 instead of letting persistence fail.","For the sibling messages, also set loginId, createTime, expiresTime (non-zero) and isValid before save.","Catch ApiKeyException and read getCode()==12304 to map to a client-facing validation error."],"exampleFix":"// before\nApiKeyModel model = new ApiKeyModel();\nmodel.setLoginId(10001);\nmodel.setCreateTime(System.currentTimeMillis());\nmodel.setExpiresTime(System.currentTimeMillis() + 86400000L);\nmodel.setIsValid(true);\nsaApiKeyTemplate.save(model); // throws 12304: apiKey empty\n\n// after\nmodel.setApiKey(SaFoxUtil.getRandomString(32)); // or framework-generated key\nsaApiKeyTemplate.save(model);","handlingStrategy":"validation","validationCode":"ApiKeyModel m = new ApiKeyModel();\nm.setApiKey(generateKey());           // non-empty\nm.setLoginId(loginId);                // non-null\nm.setCreateTime(System.currentTimeMillis());\nm.setExpiresTime(System.currentTimeMillis() + ttlMs);\nm.setIsValid(true);\nm.checkByCanSaved(); // passes silently now","typeGuard":"boolean isSavable(ApiKeyModel m) { return SaFoxUtil.isNotEmpty(m.getApiKey()) && m.getLoginId() != null && m.getCreateTime() > 0 && m.getExpiresTime() > 0 && m.getIsValid() != null; }","tryCatchPattern":"try { manager.save(model); } catch (ApiKeyException e) { if (e.getCode() == SaApiKeyErrorCode.CODE_12304) { // map to 400 validation error using e.getMessage() } else throw e; }","preventionTips":["Call checkByCanSaved() yourself in a validate step before persisting so failures surface at the API boundary.","Never hand-roll the apiKey value from user input; generate it server-side (random, unique) and return it once."],"tags":["apikey","validation","sa-token-plugin","code-12304"],"backgroundTag":null,"analyzedSha":"ac2c7f6e94a78573cf0bcb932dd8b04e68fad189","analyzedAt":"2026-08-14T14:36:10.271Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}