fix(apple-login): 修复JWT解析与Base64URL解码错误
This commit is contained in:
10
pom.xml
10
pom.xml
@@ -55,11 +55,11 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<!-- <dependency>-->
|
||||||
<groupId>com.alibaba.cloud.ai</groupId>
|
<!-- <groupId>com.alibaba.cloud.ai</groupId>-->
|
||||||
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
|
<!-- <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>-->
|
||||||
<version>1.0.0.4</version>
|
<!-- <version>1.0.0.4</version>-->
|
||||||
</dependency>
|
<!-- </dependency>-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.jsonwebtoken</groupId>
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
<artifactId>jjwt-api</artifactId>
|
<artifactId>jjwt-api</artifactId>
|
||||||
|
|||||||
@@ -1,28 +1,42 @@
|
|||||||
//package com.yolo.keyborad.config;
|
package com.yolo.keyborad.config;
|
||||||
//
|
|
||||||
//import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
|
|
||||||
//import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
|
import org.springframework.ai.chat.client.ChatClient;
|
||||||
//import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.ai.chat.model.ChatModel;
|
||||||
//import org.springframework.context.annotation.Bean;
|
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||||
//import org.springframework.context.annotation.Configuration;
|
import org.springframework.ai.openai.api.OpenAiApi;
|
||||||
//
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
///*
|
import org.springframework.context.annotation.Bean;
|
||||||
// * @author: ziin
|
import org.springframework.context.annotation.Configuration;
|
||||||
// * @date: 2025/11/11 20:37
|
|
||||||
// */
|
/*
|
||||||
//@Configuration
|
* @author: ziin
|
||||||
//public class LLMConfig {
|
* @date: 2025/11/11 20:37
|
||||||
////
|
*/
|
||||||
//// @Value("${spring.ai.dashscope.api-key}")
|
@Configuration
|
||||||
//// private String apiKey;
|
public class LLMConfig {
|
||||||
//// @Value("${spring.ai.dashscope.chat.base-url}")
|
|
||||||
//// private String baseUrl;
|
@Value("${spring.ai.openai.api-key}")
|
||||||
////
|
private String apiKey;
|
||||||
//// @Bean
|
@Value("${spring.ai.openai.base-url}")
|
||||||
//// public DashScopeApi dashScopeApi() {
|
private String baseUrl;
|
||||||
//// return DashScopeApi.builder()
|
@Value("${spring.ai.openai.chat.options.model}")
|
||||||
//// .apiKey(apiKey)
|
private String openRouterChatModel;
|
||||||
//// .baseUrl(baseUrl)
|
|
||||||
//// .build();
|
@Bean
|
||||||
//// }
|
public OpenAiApi openAiApi() {
|
||||||
//}
|
return OpenAiApi.builder()
|
||||||
|
.apiKey(apiKey)
|
||||||
|
.baseUrl(baseUrl)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ChatClient chatClient(ChatModel geminiModel) {
|
||||||
|
return ChatClient.builder(geminiModel)
|
||||||
|
.defaultOptions(OpenAiChatOptions.builder()
|
||||||
|
.model(openRouterChatModel)
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
package com.yolo.keyborad.controller;
|
package com.yolo.keyborad.controller;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||||
import com.yolo.keyborad.common.BaseResponse;
|
import com.yolo.keyborad.common.BaseResponse;
|
||||||
|
import com.yolo.keyborad.common.ErrorCode;
|
||||||
import com.yolo.keyborad.common.ResultUtils;
|
import com.yolo.keyborad.common.ResultUtils;
|
||||||
|
|
||||||
|
import com.yolo.keyborad.exception.BusinessException;
|
||||||
|
import com.yolo.keyborad.model.dto.IosPayVerifyReq;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.ai.chat.client.ChatClient;
|
import org.springframework.ai.chat.client.ChatClient;
|
||||||
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
|
import org.springframework.boot.context.properties.bind.DefaultValue;
|
||||||
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
|
|
||||||
import org.springframework.ai.chat.model.ChatModel;
|
|
||||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -31,24 +29,8 @@ import reactor.core.publisher.Flux;
|
|||||||
@Tag(name = "测试控制器", description = "测试控制器")
|
@Tag(name = "测试控制器", description = "测试控制器")
|
||||||
public class DemoController {
|
public class DemoController {
|
||||||
|
|
||||||
private final ChatClient openAiChatClient;
|
@Resource
|
||||||
|
private ChatClient client;
|
||||||
private final ChatModel chatModel;
|
|
||||||
|
|
||||||
public DemoController(@Qualifier("openAiChatModel") ChatModel chatModel) {
|
|
||||||
|
|
||||||
this.chatModel = chatModel;
|
|
||||||
|
|
||||||
// 构造时,可以设置 ChatClient 的参数
|
|
||||||
// {@link org.springframework.ai.chat.client.ChatClient};
|
|
||||||
this.openAiChatClient = ChatClient.builder(chatModel)
|
|
||||||
.defaultOptions(OpenAiChatOptions.builder()
|
|
||||||
.build())
|
|
||||||
// 实现 Chat Memory 的 Advisor
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("/test")
|
@GetMapping("/test")
|
||||||
@Operation(summary = "测试接口", description = "测试接口")
|
@Operation(summary = "测试接口", description = "测试接口")
|
||||||
@@ -60,11 +42,20 @@ public class DemoController {
|
|||||||
|
|
||||||
@GetMapping("/talk")
|
@GetMapping("/talk")
|
||||||
@Operation(summary = "测试聊天接口", description = "测试接口")
|
@Operation(summary = "测试聊天接口", description = "测试接口")
|
||||||
public Flux<String> testTalk(String userInput){
|
@Parameter(name = "userInput",required = true,description = "测试聊天接口",example = "talk to something")
|
||||||
return openAiChatClient
|
public Flux<String> testTalk(@DefaultValue("you are so cute!") String userInput){
|
||||||
|
return client
|
||||||
.prompt("You're a 25-year-old guy—witty and laid-back, always replying in English.\n" +
|
.prompt("You're a 25-year-old guy—witty and laid-back, always replying in English.\n" +
|
||||||
"Read the user's last message, then write three short and funny replies that sound like something a guy would say. Go easy on the emojis.\n" +
|
"Read the user's last message, then write three short and funny replies that sound like something a guy would say. Go easy on the emojis.\n" +
|
||||||
"Keep each under 20 words. Use '/t' to separate them—no numbers or extra labels.").user("you are so cute!")
|
"Keep each under 20 words. Use '/t' to separate them—no numbers or extra labels.")
|
||||||
.stream().content();
|
.user(userInput)
|
||||||
|
.stream()
|
||||||
|
.content();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "IOS内购凭证校验", description = "IOS内购凭证校验")
|
||||||
|
public BaseResponse<String> iosPay(@RequestBody IosPayVerifyReq req) {
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
package com.yolo.keyborad.controller;
|
package com.yolo.keyborad.controller;
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.yolo.keyborad.common.BaseResponse;
|
||||||
|
import com.yolo.keyborad.common.ResultUtils;
|
||||||
|
import com.yolo.keyborad.model.dto.AppleLoginReq;
|
||||||
import com.yolo.keyborad.service.impl.IAppleService;
|
import com.yolo.keyborad.service.impl.IAppleService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -30,11 +36,13 @@ public class UserController {
|
|||||||
/**
|
/**
|
||||||
* 苹果登录
|
* 苹果登录
|
||||||
*
|
*
|
||||||
* @param code JWT
|
* @param appleLoginReq identityToken
|
||||||
*/
|
*/
|
||||||
@PostMapping("/appleLogin")
|
@PostMapping("/appleLogin")
|
||||||
public String appleLogin(String code) throws Exception {
|
@Operation(summary = "苹果登录", description = "苹果登录接口")
|
||||||
String subjectId = appleService.login(code);
|
@Parameter(name = "code",required = true,description = "苹果登录凭证",example = "123456")
|
||||||
return JSON.toJSONString(subjectId);
|
public BaseResponse<String> appleLogin(@RequestBody AppleLoginReq appleLoginReq) throws Exception {
|
||||||
|
String subjectId = appleService.login(appleLoginReq.getIdentityToken());
|
||||||
|
return ResultUtils.success(subjectId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import com.alibaba.fastjson.JSONObject;
|
|||||||
import io.jsonwebtoken.*;
|
import io.jsonwebtoken.*;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.codec.binary.Base64;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
@@ -14,6 +13,7 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.security.KeyFactory;
|
import java.security.KeyFactory;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.security.spec.RSAPublicKeySpec;
|
import java.security.spec.RSAPublicKeySpec;
|
||||||
|
import java.util.Base64;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,8 +27,6 @@ import java.util.Objects;
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class AppleServiceImpl implements IAppleService {
|
public class AppleServiceImpl implements IAppleService {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录
|
* 登录
|
||||||
*
|
*
|
||||||
@@ -36,30 +34,46 @@ public class AppleServiceImpl implements IAppleService {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String login(String identityToken) throws Exception {
|
public String login(String identityToken) throws Exception {
|
||||||
// 从identityToken中获取头部和载荷
|
|
||||||
String firstDate;
|
|
||||||
String claim;
|
|
||||||
firstDate = new String(Base64.decodeBase64(identityToken.split("\\.")[0]), StandardCharsets.UTF_8);
|
|
||||||
claim = new String(Base64.decodeBase64(identityToken.split("\\.")[1]), StandardCharsets.UTF_8);
|
|
||||||
|
|
||||||
// 开发者帐户中获取的 10 个字符的标识符密钥
|
// 1. 清理一下 token,防止前后多了引号/空格
|
||||||
String kid = JSONObject.parseObject(firstDate).get("kid").toString();
|
identityToken = identityToken.trim();
|
||||||
String aud = JSONObject.parseObject(claim).get("aud").toString();
|
if (identityToken.startsWith("\"") && identityToken.endsWith("\"")) {
|
||||||
String sub = JSONObject.parseObject(claim).get("sub").toString();
|
identityToken = identityToken.substring(1, identityToken.length() - 1);
|
||||||
|
|
||||||
// 获取公钥
|
|
||||||
PublicKey publicKey = this.getPublicKey(kid);
|
|
||||||
if (Objects.isNull(publicKey)) {
|
|
||||||
throw new RuntimeException("apple授权登录的数据异常");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证Apple登录的JWT令牌
|
// 2. 拆分三段 & 使用 Base64URL 解码
|
||||||
|
String[] parts = identityToken.split("\\.");
|
||||||
|
if (parts.length != 3) {
|
||||||
|
throw new RuntimeException("非法的 identityToken,JWT 结构不正确");
|
||||||
|
}
|
||||||
|
|
||||||
|
Base64.Decoder urlDecoder = Base64.getUrlDecoder();
|
||||||
|
|
||||||
|
String headerJson = new String(urlDecoder.decode(parts[0]), StandardCharsets.UTF_8);
|
||||||
|
String payloadJson = new String(urlDecoder.decode(parts[1]), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
JSONObject header = JSONObject.parseObject(headerJson);
|
||||||
|
JSONObject payload = JSONObject.parseObject(payloadJson);
|
||||||
|
|
||||||
|
// 开发者帐户中获取的 10 个字符的标识符密钥
|
||||||
|
String kid = header.getString("kid");
|
||||||
|
String aud = payload.getString("aud");
|
||||||
|
String sub = payload.getString("sub");
|
||||||
|
|
||||||
|
// 3. 获取公钥
|
||||||
|
PublicKey publicKey = this.getPublicKey(kid);
|
||||||
|
if (Objects.isNull(publicKey)) {
|
||||||
|
throw new RuntimeException("apple授权登录的公钥获取失败,kid=" + kid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 验证Apple登录的JWT令牌
|
||||||
boolean result = this.verifyAppleLoginCode(publicKey, identityToken, aud, sub);
|
boolean result = this.verifyAppleLoginCode(publicKey, identityToken, aud, sub);
|
||||||
|
|
||||||
// 返回用户标识符
|
// 返回用户标识符
|
||||||
if (result) {
|
if (result) {
|
||||||
return sub;
|
return sub;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,28 +82,45 @@ public class AppleServiceImpl implements IAppleService {
|
|||||||
*
|
*
|
||||||
* @param publicKey 公钥
|
* @param publicKey 公钥
|
||||||
* @param identityToken JWT身份令牌
|
* @param identityToken JWT身份令牌
|
||||||
* @param aud 受众
|
* @param aud 受众(目前你从 token 里拿出来的)
|
||||||
* @param sub 用户标识符
|
* @param sub 用户标识符
|
||||||
* @return 验证结果
|
* @return 验证结果
|
||||||
*/
|
*/
|
||||||
private boolean verifyAppleLoginCode(PublicKey publicKey, String identityToken, String aud, String sub) {
|
private boolean verifyAppleLoginCode(PublicKey publicKey, String identityToken, String aud, String sub) {
|
||||||
boolean result = false;
|
boolean result = false;
|
||||||
String appleIssUrl = "https://appleid.apple.com";
|
String appleIssUrl = "https://appleid.apple.com";
|
||||||
JwtParser jwtParser = Jwts.parserBuilder()
|
|
||||||
.setSigningKey(publicKey)
|
|
||||||
.requireIssuer(aud)
|
|
||||||
.requireSubject(sub)
|
|
||||||
.requireIssuer(appleIssUrl).build();
|
|
||||||
try {
|
try {
|
||||||
|
JwtParser jwtParser = Jwts.parserBuilder()
|
||||||
|
.setSigningKey(publicKey)
|
||||||
|
.requireIssuer(appleIssUrl) // ✅ iss 固定校验 apple
|
||||||
|
.requireSubject(sub) // ✅ 校验 sub
|
||||||
|
// .requireAudience(expectedAud) // 更严谨做法:用你服务端配置的 client_id 校验
|
||||||
|
.build();
|
||||||
|
|
||||||
Jws<Claims> claim = jwtParser.parseClaimsJws(identityToken);
|
Jws<Claims> claim = jwtParser.parseClaimsJws(identityToken);
|
||||||
if (claim != null && claim.getBody().containsKey("auth_time")) {
|
|
||||||
|
Claims body = claim.getBody();
|
||||||
|
|
||||||
|
// 可选:再手动对 aud 做一层检查(现在 aud 是从 payload 里读出来的)
|
||||||
|
String tokenAud = body.getAudience();
|
||||||
|
if (!Objects.equals(tokenAud, aud)) {
|
||||||
|
log.warn("apple identityToken aud 不一致, tokenAud={}, inputAud={}", tokenAud, aud);
|
||||||
|
// 这里看你要不要直接抛异常
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body.containsKey("auth_time")) {
|
||||||
result = true;
|
result = true;
|
||||||
}
|
}
|
||||||
} catch (ExpiredJwtException e) {
|
} catch (ExpiredJwtException e) {
|
||||||
log.error("error identity expire time out", e);
|
log.error("error identity expire time out", e);
|
||||||
throw new RuntimeException("apple登录授权identityToken过期", e);
|
throw new RuntimeException("apple登录授权identityToken过期", e);
|
||||||
|
} catch (SignatureException e) {
|
||||||
|
// ✅ 专门打一下签名错误日志,便于你确认是不是 key 对不上
|
||||||
|
log.error("apple identityToken 签名校验失败", e);
|
||||||
|
throw new RuntimeException("apple登录授权identityToken签名非法", e);
|
||||||
} catch (SecurityException e) {
|
} catch (SecurityException e) {
|
||||||
log.error("error identity illegal", e);
|
log.error("apple identityToken 安全校验失败", e);
|
||||||
throw new RuntimeException("apple登录授权identityToken非法", e);
|
throw new RuntimeException("apple登录授权identityToken非法", e);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@@ -106,23 +137,30 @@ public class AppleServiceImpl implements IAppleService {
|
|||||||
|
|
||||||
String result = HttpUtil.get(appleAuthUrl);
|
String result = HttpUtil.get(appleAuthUrl);
|
||||||
JSONObject content = JSONObject.parseObject(result);
|
JSONObject content = JSONObject.parseObject(result);
|
||||||
String keys = content.getString("keys");
|
|
||||||
JSONArray keysArray = JSONObject.parseArray(keys);
|
// ✅ 直接拿 JSONArray,之前先 getString 再 parse 容易搞错类型
|
||||||
if (keysArray.isEmpty()) {
|
JSONArray keysArray = content.getJSONArray("keys");
|
||||||
|
if (keysArray == null || keysArray.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
for (Object key : keysArray) {
|
|
||||||
JSONObject keyJsonObject = (JSONObject) key;
|
Base64.Decoder urlDecoder = Base64.getUrlDecoder();
|
||||||
|
|
||||||
|
for (int i = 0; i < keysArray.size(); i++) {
|
||||||
|
JSONObject keyJsonObject = keysArray.getJSONObject(i);
|
||||||
if (kid.equals(keyJsonObject.getString("kid"))) {
|
if (kid.equals(keyJsonObject.getString("kid"))) {
|
||||||
String n = keyJsonObject.getString("n");
|
String n = keyJsonObject.getString("n");
|
||||||
String e = keyJsonObject.getString("e");
|
String e = keyJsonObject.getString("e");
|
||||||
BigInteger modulus = new BigInteger(1, Base64.decodeBase64(n));
|
|
||||||
BigInteger publicExponent = new BigInteger(1, Base64.decodeBase64(e));
|
BigInteger modulus = new BigInteger(1, urlDecoder.decode(n)); // ✅ Base64URL
|
||||||
|
BigInteger publicExponent = new BigInteger(1, urlDecoder.decode(e));
|
||||||
|
|
||||||
RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, publicExponent);
|
RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, publicExponent);
|
||||||
KeyFactory kf = KeyFactory.getInstance("RSA");
|
KeyFactory kf = KeyFactory.getInstance("RSA"); // ✅ 和 kty=RSA / alg=RS256 对应
|
||||||
return kf.generatePublic(spec);
|
return kf.generatePublic(spec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
log.warn("未在 Apple JWK 中找到匹配 kid 的公钥,kid={}", kid);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ spring:
|
|||||||
chat:
|
chat:
|
||||||
options:
|
options:
|
||||||
model: google/gemini-2.5-flash-lite
|
model: google/gemini-2.5-flash-lite
|
||||||
|
embedding:
|
||||||
|
options:
|
||||||
|
model: qwen/qwen3-embedding-8b
|
||||||
dashscope:
|
dashscope:
|
||||||
api-key: 11
|
api-key: 11
|
||||||
application:
|
application:
|
||||||
@@ -14,7 +17,7 @@ spring:
|
|||||||
active: dev
|
active: dev
|
||||||
datasource:
|
datasource:
|
||||||
driver-class-name: org.postgresql.Driver
|
driver-class-name: org.postgresql.Driver
|
||||||
url: jdbc:postgresql://localhost:5432/postgres
|
url: jdbc:postgresql://localhost:5432/keyborad_db
|
||||||
username: root
|
username: root
|
||||||
password: 123asd
|
password: 123asd
|
||||||
mvc:
|
mvc:
|
||||||
|
|||||||
Reference in New Issue
Block a user