Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public Result<Map<String, String>> handleNullPointerException(HttpServletRequest
* @param e the e
* @return the result
*/
@ResponseStatus(HttpStatus.OK)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(ServiceException.class)
public Result<Map<String, String>> handleServiceException(ServiceException e) {
// 修改为 log.error,传递异常对象以打印堆栈信息
Expand Down
71 changes: 71 additions & 0 deletions base/src/main/java/com/tinyengine/it/common/utils/SM4Utils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.tinyengine.it.common.utils;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Base64;

public class SM4Utils {

static {
Security.addProvider(new BouncyCastleProvider());
}

private static final String ALGORITHM = "SM4";
private static final String TRANSFORMATION_ECB = "SM4/ECB/PKCS5Padding";
private static final int KEY_SIZE = 128;

/**
* 生成 SM4 密钥
*/
public static String generateKeyBase64() throws Exception {
byte[] key = generateKey();
return Base64.getEncoder().encodeToString(key);
}

public static byte[] generateKey() throws Exception {
KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM, "BC");
kg.init(KEY_SIZE, new SecureRandom());
SecretKey secretKey = kg.generateKey();
return secretKey.getEncoded();
}

/**
* ECB 模式加密 - 只加密API密钥值 (Base64 结果)
*/
public static String encryptECB(String apiKey, String base64Key) throws Exception {
byte[] key = Base64.getDecoder().decode(base64Key);
byte[] encrypted = encryptECB(apiKey.getBytes("UTF-8"), key);
return Base64.getEncoder().encodeToString(encrypted);
}

/**
* ECB 模式解密 - 直接返回API密钥
*/
public static String decryptECB(String encryptedBase64, String base64Key) throws Exception {
byte[] key = Base64.getDecoder().decode(base64Key);
byte[] encrypted = Base64.getDecoder().decode(encryptedBase64);
byte[] decrypted = decryptECB(encrypted, key);
return new String(decrypted, "UTF-8");
}

// ECB 模式的底层方法保持不变
private static byte[] encryptECB(byte[] data, byte[] key) throws Exception {
SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION_ECB, "BC");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
return cipher.doFinal(data);
}

private static byte[] decryptECB(byte[] encryptedData, byte[] key) throws Exception {
SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION_ECB, "BC");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
return cipher.doFinal(encryptedData);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@

package com.tinyengine.it.controller;

import com.tinyengine.it.common.base.Result;
import com.tinyengine.it.common.exception.ExceptionEnum;
import com.tinyengine.it.common.log.SystemControllerLog;
import com.tinyengine.it.model.dto.AiToken;
import com.tinyengine.it.model.dto.ChatRequest;

import com.tinyengine.it.service.app.v1.AiChatV1Service;
Expand All @@ -24,7 +27,6 @@
import io.swagger.v3.oas.annotations.tags.Tag;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
Expand All @@ -50,6 +52,7 @@ public class AiChatController {
*/
@Autowired
private AiChatV1Service aiChatV1Service;

/**
* AI api
*
Expand All @@ -66,23 +69,29 @@ public class AiChatController {
})
@SystemControllerLog(description = "AI chat")
@PostMapping("/ai/chat")
public ResponseEntity<?> aiChat(@RequestBody ChatRequest request) {
try {
Object response = aiChatV1Service.chatCompletion(request);
public ResponseEntity<?> aiChat(@RequestBody ChatRequest request,
@RequestHeader(value = "Authorization", required = false) String authorization) throws Exception {

if (request.isStream()) {
return ResponseEntity.ok()
.contentType(MediaType.TEXT_EVENT_STREAM)
.body((StreamingResponseBody) response);
} else {
return ResponseEntity.ok(response);
}
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(e.getMessage());
if (authorization != null && authorization.startsWith("Bearer ")) {
String token = authorization.replace("Bearer ", "");
request.setApiKey(token);
}

Object response = aiChatV1Service.chatCompletion(request);

if (request.isStream()) {
return ResponseEntity.ok()
.contentType(MediaType.TEXT_EVENT_STREAM)
.header("Cache-Control", "no-cache")
.header("X-Accel-Buffering", "no") // 禁用Nginx缓冲
.body((StreamingResponseBody) response);
} else {
return ResponseEntity.ok(response);
}

}


/**
* AI api v1
*
Expand All @@ -100,24 +109,46 @@ public ResponseEntity<?> aiChat(@RequestBody ChatRequest request) {
@SystemControllerLog(description = "AI completions")
@PostMapping("/chat/completions")
public ResponseEntity<?> completions(@RequestBody ChatRequest request,
@RequestHeader("Authorization") String authorization) {
@RequestHeader(value = "Authorization", required = false) String authorization) throws Exception {
if (authorization != null && authorization.startsWith("Bearer ")) {
String token = authorization.replace("Bearer ", "");
request.setApiKey(token);
}
try {
Object response = aiChatV1Service.chatCompletion(request);

if (request.isStream()) {
return ResponseEntity.ok()
.contentType(MediaType.TEXT_EVENT_STREAM)
.body((StreamingResponseBody) response);
} else {
return ResponseEntity.ok(response);
}
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(e.getMessage());
Object response = aiChatV1Service.chatCompletion(request);

if (request.isStream()) {
return ResponseEntity.ok()
.contentType(MediaType.TEXT_EVENT_STREAM)
.header("Cache-Control", "no-cache")
.header("X-Accel-Buffering", "no") // 禁用Nginx缓冲
.body((StreamingResponseBody) response);
} else {
return ResponseEntity.ok(response);
}
}
/**
* get token
*
* @param request the request
* @return ai回答信息 result
*/
@Operation(summary = "获取加密key信息", description = "获取加密key信息",
parameters = {
@Parameter(name = "request", description = "入参对象")
}, responses = {
@ApiResponse(responseCode = "200", description = "返回信息",
content = @Content(mediaType = "application/json", schema = @Schema())),
@ApiResponse(responseCode = "400", description = "请求失败")
})
@SystemControllerLog(description = "get token")
@PostMapping("/encrypt-key")
public Result<AiToken> getToken(@RequestBody ChatRequest request) throws Exception {
String apiKey = request.getApiKey();
if(apiKey == null || apiKey.isEmpty()) {
return Result.failed(ExceptionEnum.CM320);
}
String token = aiChatV1Service.getToken(apiKey);
return Result.success(new AiToken(token));
}
}
33 changes: 33 additions & 0 deletions base/src/main/java/com/tinyengine/it/model/dto/AiToken.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright (c) 2023 - present TinyEngine Authors.
* Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
*
* Use of this source code is governed by an MIT-style license.
*
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/

package com.tinyengine.it.model.dto;

import lombok.Data;

/**
* The type Ai token.
*
* @since 2025-11-27
*/

@Data
public class AiToken {
String token;

public AiToken(String token) {
this.token = token;
}
public AiToken(){

}
}
Loading
Loading