SpringBoot集成Open WebUI实现AI流式对话
背景与架构概览
在企业 CRM 系统中,我们希望为业务人员提供一个内嵌的 AI 助手,让用户能直接在系统内输入问题、实时获取 AI 回答,而无需跳转到外部 AI 平台。
整体方案选型:
| 组件 | 说明 |
|---|---|
| Open WebUI | 开源 LLM 前端平台,提供 OpenAI 兼容接口,支持多模型管理 |
| openai-java SDK | 官方 Java 客户端,直接对接 OpenAI 兼容 API |
| Spring WebFlux | 响应式编程,支持 SSE(Server-Sent Events)流式推送 |
| Redis | 缓存 Token,避免每次请求都重新登录 Open WebUI |
整体请求链路:
前端输入框 → POST /v1/chat/completions
→ LLMController(SSE 接口)
→ LLMService(获取 Token + 构造请求)
→ OpenAI Java SDK(流式调用 Open WebUI)
→ SSE 逐块推送回前端
依赖与配置
Maven 依赖
<!-- OpenAI Java 官方 SDK -->
<dependency>
<groupId>com.openai</groupId>
<artifactId>openai-java</artifactId>
<version>2.5.0</version>
</dependency>
<!-- Spring WebFlux(SSE 支持) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- Hutool(HTTP 工具 + JSON 解析) -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.x</version>
</dependency>
<!-- Spring Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>application.yml 配置
open-web-ui: base-url: http://your-openwebui-host/api # Open WebUI 的 OpenAI 兼容接口地址
Open WebUI 的 OpenAI 兼容接口通常为 http://host/api/v1,请根据实际部署调整。
Token 生命周期管理
设计思路
Open WebUI 使用 JWT Token 进行鉴权,Token 有有效期(默认约数小时)。若每次调用 AI 接口都重新登录,不仅效率低下,还会对 Open WebUI 服务产生不必要的登录压力。
因此,我们设计了一套 “先查缓存 → 有效直接用 → 过期再刷新” 的 Token 自动管理机制,并通过 Redis 实现跨实例共享。
TokenRepository 接口抽象
定义标准的增删查接口,便于后续替换为其他存储介质(如内存 Map、数据库等)。
public interface TokenRepository {
OpenWebUIToken get(String key);
void save(String key, OpenWebUIToken value);
void delete(String key);
}
设计亮点:通过接口隔离存储实现,TokenManager 不依赖具体存储技术,方便单元测试和替换。
Redis 存储实现
public class RedisTokenRepository implements TokenRepository {
private String prefix = "openwebui:";
private final RedisTemplate<Object, Object> redisTemplate;
public RedisTokenRepository(RedisTemplate<Object, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public RedisTokenRepository(String prefix, RedisTemplate<Object, Object> redisTemplate) {
this.prefix = prefix;
this.redisTemplate = redisTemplate;
}
@Override
public OpenWebUIToken get(String key) {
return BeanUtil.copyProperties(
redisTemplate.opsForValue().get(prefix + key),
OpenWebUIToken.class
);
}
@Override
public void save(String key, OpenWebUIToken value) {
redisTemplate.opsForValue().set(prefix + key, value);
}
@Override
public void delete(String key) {
redisTemplate.delete(prefix + key);
}
}
关键点说明:
- Key 格式为
openwebui:{username},通过前缀做命名空间隔离,避免与其他 Redis Key 冲突。 - 使用
BeanUtil.copyProperties从 Redis 返回的LinkedHashMap反序列化为强类型对象,避免手动类型转换。
OpenWebUIToken 数据模型
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class OpenWebUIToken implements Serializable {
@Schema(description = "认证token")
private String accessToken;
@Schema(description = "token过期时间(毫秒时间戳)")
private Long expireAt;
@Schema(description = "用户名")
private String username;
@Schema(description = "密码")
private String password;
/**
* 判断 Token 是否仍然有效
*/
public boolean isValid() {
return StringUtils.isNotBlank(accessToken)
&& Objects.nonNull(expireAt)
&& TimeUtil.getLocalDateTime(expireAt).isAfter(LocalDateTime.now());
}
}
isValid() 方法封装了有效性判断,Token 有效的条件:
accessToken不为空expireAt不为空- 当前时间在过期时间之前
OpenWebUITokenManager 核心管理器
这是整个 Token 管理的核心组件,负责:
- 优先从缓存获取有效 Token
- Token 失效时,发起 HTTP 请求重新登录 Open WebUI 并刷新缓存
- 使用
@Synchronized防止并发场景下的重复登录(双检锁模式)
@Slf4j
public class OpenWebUITokenManager {
/** Token 默认有效期(秒),登录接口无 expires_at 时使用 */
private static final Long TOKEN_EXPIRE_TIME = 60 * 60L;
private final String signInUrl;
private final TokenRepository tokenRepository;
public OpenWebUITokenManager(String signInUrl, TokenRepository tokenRepository) {
this.signInUrl = signInUrl;
this.tokenRepository = tokenRepository;
}
/**
* 获取有效 Token(优先缓存,缓存失效则重新登录)
*/
public String getValidToken(String username, String password) {
if (!StringUtils.hasText(username) || !StringUtils.hasText(password)) {
log.warn("用户名或密码为空");
return null;
}
// 1. 查询缓存
OpenWebUIToken cachedToken = tokenRepository.get(username);
// 2. 缓存有效直接返回
if (cachedToken != null && cachedToken.isValid()) {
log.debug("使用缓存 Token: {}", username);
return cachedToken.getAccessToken();
}
// 3. 缓存失效,重新登录
log.info("Token 过期或不存在,重新登录: {}", username);
return refreshToken(username, password);
}
public String getValidToken(Credential credential) {
if (Objects.isNull(credential)) return null;
return getValidToken(credential.getUsername(), credential.getPassword());
}
/**
* 刷新 Token(加锁防并发,内部二次检查)
*/
@Synchronized
public String refreshToken(String username, String password) {
// 二次检查:加锁后再次确认缓存是否已被其他线程刷新
OpenWebUIToken cachedToken = tokenRepository.get(username);
if (cachedToken != null && cachedToken.isValid()) {
return cachedToken.getAccessToken();
}
try {
// 调用 Open WebUI 登录接口
HttpResponse response = HttpUtil.createPost(signInUrl)
.header("Content-Type", "application/json")
.body(JSONUtil.toJsonStr(Map.of("email", username, "password", password)))
.execute();
if (!response.isOk() || !StringUtils.hasText(response.body())) {
throw new RuntimeException("登录失败: " + response.getStatus());
}
// 解析响应
JSONObject responseData = JSONUtil.parseObj(response.body());
String token = responseData.getStr("token");
// 计算过期时间:优先使用接口返回值,否则使用默认值
Long expiresTime = TimeUtil.getEpochMilli(LocalDateTime.now().plusSeconds(TOKEN_EXPIRE_TIME));
String expiresAt = responseData.get("expires_at").toString();
if (StringUtils.hasText(expiresAt)) {
expiresTime = Long.parseLong(expiresAt) * 1000; // 秒 → 毫秒
}
// 构建并缓存 Token
OpenWebUIToken webUIToken = OpenWebUIToken.builder()
.accessToken(token)
.username(username)
.password(password)
.expireAt(expiresTime)
.build();
tokenRepository.save(username, webUIToken);
log.info("Token 刷新成功: {}, 过期时间: {}", username, expiresTime);
return token;
} catch (Exception e) {
log.error("登录获取 Token 失败: {}", username, e);
throw new RuntimeException("Open WebUI 登录失败", e);
}
}
}
并发安全分析:
线程 A: getValidToken → 缓存失效 → refreshToken(加锁) 线程 B: getValidToken → 缓存失效 → refreshToken(等待锁) 线程 A: 登录成功,缓存 Token,释放锁 线程 B: 获取到锁 → 二次检查缓存 → 发现 Token 有效 → 直接返回
通过二次检查(Double-Check),避免了多个线程同时发起重复登录请求。
凭证获取
系统用户与 Open WebUI 账号存在映射关系。CredentialProvider 负责根据当前登录用户 ID 查询其对应的 Open WebUI 账号和密码。
@Slf4j
@Component
public class CredentialProvider {
@Resource
private SysUserService sysUserService;
/**
* 根据用户 ID 获取 Open WebUI 登录凭证
*/
public Credential getCredential(String userId) {
SysUser sysUser = sysUserService.getById(userId);
if (Objects.isNull(sysUser)) {
throw new BusinessException(401, "用户不存在");
}
// 用户的 AI 邮箱(即 Open WebUI 账号)
String aiEmail = sysUser.getAiEmail();
// 密码规则:邮箱前缀 + 固定后缀
String password = aiEmail.split("@")[0] + "AI++2025&";
return new Credential(aiEmail, password);
}
}
设计说明:
- 系统在用户表中存储
aiEmail字段,与 Open WebUI 用户一一对应。 - 密码采用固定规则生成,方便批量初始化 Open WebUI 用户,同时保持一定的随机性。
- 这种设计使得系统用户与 AI 平台账号解耦,无需在系统中明文存储 AI 平台密码。
系统提示词管理
系统提示词(System Prompt)决定了 AI 的角色定位和回答风格。为了方便维护多套提示词,我们将其以文本文件的形式存放在 classpath 中,通过枚举统一管理。
提示词枚举
@Getter
@AllArgsConstructor
public enum SystemPromptEnum {
DEFAULT("default", "默认");
private final String code;
private final String message;
/**
* 根据 code 获取枚举,未找到时返回 DEFAULT
*/
public static SystemPromptEnum of(String code) {
for (SystemPromptEnum value : values()) {
if (value.getCode().equals(code)) {
return value;
}
}
return DEFAULT;
}
}
提示词加载器
@Slf4j
public class SystemPromptLoader {
private static final String SYSTEM_PROMPT_PATH = "prompt/";
private static final String SYSTEM_PROMPT_SUFFIX = "-system-prompt.txt";
private SystemPromptLoader() {}
/**
* 从 classpath 加载指定名称的系统提示词文件
* 文件路径:resources/prompt/{promptName}-system-prompt.txt
*/
public static String loadSystemPrompt(String promptName) {
ClassPathResource resource = new ClassPathResource(
SYSTEM_PROMPT_PATH + promptName + SYSTEM_PROMPT_SUFFIX
);
log.info("加载系统提示: {}", resource.getPath());
try (InputStream in = resource.getInputStream()) {
return new String(in.readAllBytes(), Charset.defaultCharset());
} catch (Exception e) {
log.error("加载系统提示失败: {}", promptName, e);
return "";
}
}
public static String loadSystemPrompt(SystemPromptEnum promptEnum) {
return loadSystemPrompt(promptEnum.getCode());
}
}
文件结构示例:
src/main/resources/
└── prompt/
└── default-system-prompt.txt ← 默认场景提示词default-system-prompt.txt 内容示例:
你是一名专业的企业 CRM 智能助手,请根据用户的问题给出准确、简洁的回答。 回答时请使用中文,保持专业、友好的语气。
扩展新场景只需新增枚举值和对应文本文件,无需修改业务代码,符合开闭原则。
Spring Bean 配置
@Configuration
public class OpenWebUIConfig {
@Bean
public OpenWebUITokenManager openWebUITokenManager(
RedisTemplate<Object, Object> redisTemplate) {
return new OpenWebUITokenManager(
OpenWebUIConstant.LOGIN_URL, // Open WebUI 登录接口地址
new RedisTokenRepository(redisTemplate) // Redis Token 存储
);
}
}
OpenWebUIConstant.LOGIN_URL 参考值:
public class OpenWebUIConstant {
public static final String LOGIN_URL = "http://your-openwebui-host/api/v1/auths/signin";
}
请求与响应模型
请求参数ChatRequest
@Data
public class ChatRequest {
@Schema(description = "场景标识(用于加载特定场景提示词),默认 default")
private String code = SystemPromptEnum.DEFAULT.getCode();
@NotEmpty(message = "请输入文字...")
@Schema(description = "用户输入内容")
private String prompt;
@Schema(description = "模型名称,默认 Qwen3-VL-8B-Instruct")
private String model = "Qwen3-VL-8B-Instruct";
}
字段说明:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
code | String | 否 | 场景标识,关联系统提示词,默认 default |
prompt | String | 是 | 用户输入的问题 |
model | String | 否 | 指定 Open WebUI 中部署的模型名称 |
响应数据ChatStreamingVo
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ChatStreamingVo {
@Schema(description = "本次推送的内容片段")
private String content;
@Schema(description = "使用的模型名称")
private String model;
}
每个 SSE 事件携带一个内容片段(content),前端拼接所有片段即可得到完整回答。
LLMService 流式对话核心实现
这是整个功能的核心,主要完成以下步骤:
- 加载当前场景的系统提示词
- 获取当前用户的 Open WebUI 凭证和有效 Token
- 使用 OpenAI Java SDK 构建流式请求
- 通过
Flux+ SSE 将响应片段逐块推送
@Slf4j
@Service
public class LLMService {
@Resource
private OpenWebUITokenManager openWebUITokenManager;
@Value("${open-web-ui.base-url}")
private String baseUrl;
@Resource
private CredentialProvider credentialProvider;
public Flux<ServerSentEvent<ChatStreamingVo>> chatStream(ChatRequest chatRequest) {
// 1. 加载系统提示词
String systemPrompt = SystemPromptLoader.loadSystemPrompt(
SystemPromptEnum.of(chatRequest.getCode())
);
// 2. 获取当前登录用户的 Open WebUI Token
Credential credential = credentialProvider.getCredential(
SecurityUtils.getUser().getId()
);
String validToken = openWebUITokenManager.getValidToken(credential);
// 3. 构建 OpenAI Java 客户端(复用 Open WebUI 兼容接口)
OpenAIClient aiClient = OpenAIOkHttpClient.builder()
.baseUrl(baseUrl)
.apiKey(validToken) // 将 Open WebUI Token 作为 API Key
.build();
// 4. 构建对话参数
ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
.model(chatRequest.getModel())
.addSystemMessage(systemPrompt) // 系统提示词
.addUserMessage(chatRequest.getPrompt()) // 用户输入
.build();
// 5. 流式调用 + Flux 包装 + SSE 封装
return Flux.using(
// 创建流式响应资源
() -> aiClient.chat().completions().createStreaming(params),
// 将 Stream<ChatCompletionChunk> 转换为 Flux<ServerSentEvent>
streamResponse -> Flux.fromStream(streamResponse.stream())
.map(chunk -> {
// 提取当前 chunk 中的文本内容
String content = chunk.choices().stream()
.findFirst()
.flatMap(choice -> choice.delta().content())
.orElse("");
return ServerSentEvent.<ChatStreamingVo>builder()
.data(new ChatStreamingVo(content, chatRequest.getModel()))
.build();
}),
// 流结束/出错/取消 时关闭资源,防止连接泄漏
StreamResponse::close
)
// 切换到弹性线程池,避免阻塞事件循环线程
.subscribeOn(Schedulers.boundedElastic())
// 全局异常处理
.onErrorResume(e -> {
log.error("LLM 流式对话异常", e);
throw new BusinessException("LLM 流式对话异常");
});
}
}
关键技术点详解
Flux.using的资源管理模式
Flux.using 是 Reactor 提供的资源管理操作符,它的三个参数分别对应:
Flux.using(
resourceSupplier, // 创建资源(流式响应对象)
sourceSupplier, // 使用资源生产数据
resourceCleanup // 资源清理(无论成功/失败/取消都会执行)
)
这里使用它来确保 StreamResponse 对象(底层是一个 HTTP 长连接)无论何种情况下都能被正确关闭,防止连接资源泄漏。
subscribeOn(Schedulers.boundedElastic())
OpenAI SDK 的流式调用是阻塞的 I/O 操作,而 Spring WebFlux 的事件循环线程(Netty NIO 线程)不允许被阻塞。通过 subscribeOn 将订阅行为切换到 boundedElastic 线程池(专为阻塞 I/O 设计),避免阻塞主事件循环。
SSE 数据格式
ServerSentEvent 对象在 Spring WebFlux 中会被序列化为标准的 SSE 格式:
data: {"content":"你好","model":"Qwen3-VL-8B-Instruct"}
data: {"content":",有什么可以","model":"Qwen3-VL-8B-Instruct"}
data: {"content":"帮助您的?","model":"Qwen3-VL-8B-Instruct"}
LLMController 接口层
@Tag(name = "LLM 对话")
@RestController
@RequestMapping("/v1/chat")
public class LLMController {
@Resource
private LLMService llmService;
@Operation(summary = "LLM 流式对话")
@PreAuthorize("@knifeSecurity.authenticated()")
@PostMapping(value = "/completions", produces = "text/event-stream")
public Flux<ServerSentEvent<ChatStreamingVo>> chatStream(
@Valid @RequestBody ChatRequest chatRequest) {
return llmService.chatStream(chatRequest);
}
}
要点:
produces = "text/event-stream":声明接口返回 SSE 格式,浏览器EventSourceAPI 及 Fetch 流均可消费。@PreAuthorize:接口鉴权,确保只有登录用户才能访问。- 返回
Flux<ServerSentEvent<...>>:Spring WebFlux 会自动将其转换为持续推送的 SSE 响应。
前端对接思路
前端通过 Fetch API + ReadableStream 消费 SSE,实时渲染流式内容:
async function sendChat(prompt) {
const response = await fetch('/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ prompt, code: 'default', model: 'Qwen3-VL-8B-Instruct' })
});
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let answer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
// 解析 SSE 数据行
const text = decoder.decode(value);
const lines = text.split('\n').filter(line => line.startsWith('data:'));
for (const line of lines) {
const data = line.replace('data:', '').trim();
if (!data || data === '[DONE]') continue;
try {
const parsed = JSON.parse(data);
answer += parsed.content;
// 更新 UI 文本框
document.getElementById('answer').innerText = answer;
} catch (e) {
// 忽略非 JSON 行
}
}
}
}
Vue 3 + Element Plus 示例(输入框 + 流式渲染):
<template>
<div class="ai-chat">
<el-input
v-model="prompt"
type="textarea"
:rows="3"
placeholder="输入你的问题..."
@keydown.ctrl.enter="sendChat"
/>
<el-button type="primary" :loading="loading" @click="sendChat">发送</el-button>
<div class="answer" v-if="answer">
<pre>{{ answer }}</pre>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useUserStore } from '@/store/user'
const prompt = ref('')
const answer = ref('')
const loading = ref(false)
const userStore = useUserStore()
async function sendChat() {
if (!prompt.value.trim()) return
loading.value = true
answer.value = ''
const response = await fetch('/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${userStore.token}`
},
body: JSON.stringify({ prompt: prompt.value })
})
const reader = response.body.getReader()
const decoder = new TextDecoder()
try {
while (true) {
const { done, value } = await reader.read()
if (done) break
const lines = decoder.decode(value).split('\n')
for (const line of lines) {
if (!line.startsWith('data:')) continue
const json = line.slice(5).trim()
if (!json || json === '[DONE]') continue
answer.value += JSON.parse(json).content
}
}
} finally {
loading.value = false
}
}
</script>整体流程图
┌────────────────────────────────────────────────────────────────────┐
│ 前端浏览器 │
│ 用户输入 prompt → Fetch POST /v1/chat/completions │
│ ← 逐块接收 SSE 数据 → 拼接渲染到文本框 │
└────────────────────────────────┬───────────────────────────────────┘
│ HTTP SSE
┌────────────────────────────────▼───────────────────────────────────┐
│ LLMController │
│ @PostMapping(produces = "text/event-stream") │
│ → 调用 LLMService.chatStream(chatRequest) │
└────────────────────────────────┬───────────────────────────────────┘
│
┌────────────────────────────────▼───────────────────────────────────┐
│ LLMService │
│ 1. SystemPromptLoader.loadSystemPrompt(code) 加载提示词 │
│ 2. CredentialProvider.getCredential(userId) 获取 AI 凭证 │
│ 3. TokenManager.getValidToken(credential) 获取有效 Token │
│ 4. OpenAIOkHttpClient.build(baseUrl, token) 构建 SDK 客户端 │
│ 5. Flux.using(createStreaming, mapChunks, close) 流式调用 │
└────┬───────────────────────────────────────────┬───────────────────┘
│ Token 管理 │ AI 调用
┌────▼──────────────────────────┐ ┌─────────────▼──────────────────┐
│ OpenWebUITokenManager │ │ Open WebUI │
│ ┌──────────────────────────┐ │ │ POST /api/v1/auths/signin │
│ │ Redis 缓存查询 │ │ │ POST /api/v1/chat/completions │
│ │ → 有效:直接返回 │ │ │ (OpenAI 兼容接口) │
│ │ → 失效:登录刷新 + 缓存 │ │ │ │
│ └──────────────────────────┘ │ │ → 流式返回 ChatCompletionChunk │
└───────────────────────────────┘ └────────────────────────────────┘
设计总结与经验
亮点设计
| 设计点 | 说明 |
|---|---|
| Token 双检锁 | @Synchronized + 内部二次校验,防止高并发下重复登录 |
| 接口隔离存储 | TokenRepository 接口 + RedisTokenRepository 实现,易替换、易测试 |
| 提示词文件化 | 提示词以 .txt 存放 classpath,通过枚举管理多场景,无需改代码 |
| 资源安全释放 | Flux.using 三段式确保流式连接必然关闭,防止资源泄漏 |
| 线程模型正确 | subscribeOn(Schedulers.boundedElastic()) 避免阻塞 WebFlux 事件线程 |
| 用户凭证映射 | 系统用户与 AI 平台账号分离,凭证由 CredentialProvider 统一管理 |
注意事项
Token 有效期与 Redis TTL 保持一致:建议在 save 时同步设置 Redis Key 的过期时间,避免 Redis 中存放已过期 Token 占用内存。
// 改进建议:设置 Redis TTL long ttlSeconds = (expiresTime - System.currentTimeMillis()) / 1000; redisTemplate.opsForValue().set(prefix + key, value, ttlSeconds, TimeUnit.SECONDS);
Open WebUI 模型名称与实际部署保持一致:ChatRequest.model 的默认值 Qwen3-VL-8B-Instruct 需要与 Open WebUI 中实际加载的模型 ID 完全匹配,否则会返回 404。
系统提示词文件编码:建议统一使用 UTF-8 编码保存 .txt 文件,避免中文乱码。
SSE 连接超时:生产环境中建议配置 Nginx 的 proxy_read_timeout 和 proxy_buffering off,确保 SSE 长连接不被中断。
location /v1/chat/ {
proxy_pass http://backend;
proxy_buffering off;
proxy_read_timeout 120s;
proxy_set_header Cache-Control no-cache;
}AI 账号批量初始化:在用户表中添加 ai_email 字段后,需要在 Open WebUI 中预先创建对应账号,或通过 Open WebUI 管理接口批量创建。
小结
本文完整介绍了在企业 Spring Boot 项目中集成 Open WebUI 的实践方案:
- 通过 Redis 缓存 + 双检锁 实现 Token 的自动管理与并发安全;
- 通过 枚举 + classpath 文本文件 实现多场景系统提示词的灵活管理;
- 通过 OpenAI Java SDK + Spring WebFlux + SSE 实现流式 AI 响应;
- 通过 Fetch ReadableStream 在前端实时渲染流式输出。
这套架构可以方便地扩展到更多 AI 场景:代码审查、文案生成、智能客服等,只需新增场景枚举和对应提示词文件即可快速接入。
以上就是SpringBoot集成Open WebUI实现AI流式对话的详细内容,更多关于SpringBoot AI流式对话的资料请关注脚本之家其它相关文章!
相关文章
springboot使用kafka推送数据到服务端的操作方法带认证
在使用Kafka进行数据推送时,遇到认证问题导致连接失败,本文详细介绍了Kafka的认证配置过程,包括配置文件的引入和参数设置,实际测试表明,需要正确配置sasl.jaas.config以及其他认证参数,探讨了配置文件是否可以同时存在多个配置块的可能性,并提出了实际操作中的注意事项2024-11-11
阿里Sentinel支持Spring Cloud Gateway的实现
这篇文章主要介绍了阿里Sentinel支持Spring Cloud Gateway的实现,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧2019-04-04
详谈HashMap和ConcurrentHashMap的区别(HashMap的底层源码)
下面小编就为大家带来一篇详谈HashMap和ConcurrentHashMap的区别(HashMap的底层源码)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧2017-08-08
java日志LoggerFactory.getLogger的用法及说明
这篇文章主要介绍了java日志LoggerFactory.getLogger的用法及说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教2023-02-02


最新评论