know-engine

接入登录,实现用户级对话隔离

KnowEngine 项目采用 sa token + Redis 实现双轨登录体系,分别服务于两类用户群体: 网页端客户:通过手机号+密码登录,用于车辆售前/售后咨询 钉钉员工(客服):不通过网页登录,直接通过钉钉机器人提问,系统自…

TL;DR

KnowEngine 项目采用 sa token + Redis 实现双轨登录体系,分别服务于两类用户群体: 网页端客户:通过手机号+密码登录,用于车辆售前/售后咨询 钉钉员工(客服):不通过网页登录,直接通过钉钉机器人提问,系统自…

KnowEngine 项目采用 sa-token + Redis 实现双轨登录体系,分别服务于两类用户群体: - 网页端客户:通过手机号+密码登录,用于车辆售前/售后咨询 - 钉钉员工(客服):不通过网页登录,直接通过钉钉机器人提问,系统自动识别身份 两套体系共享同一套认证服务接口,但通过不同的登录入口、不同的 loginId 标识进行隔离。

核心架构

┌─────────────────────────────────────────────┐
│              前端/客户端                      │
│  login.html  /  staff-login.html  /  钉钉    │
└──────────────┬──────────────────┬────────────┘
               │                  │
               ▼                  ▼
┌──────────────────────┐  ┌──────────────────────────┐
│   AuthController     │  │  DingTalk 回调拦截器      │
│  /auth/login         │  │  /dingtalk/**            │
│  /auth/staffLogin    │  │  (排除 sa-token 校验)     │
└──────────┬───────────┘  └──────────┬───────────────┘
           │                         │
           ▼                         ▼
┌──────────────────────────────────────────────┐
│             AuthServiceImpl                   │
│  - login(LoginDTO)        → 客户登录          │
│  - staffLogin(StaffLoginDTO) → 员工登录       │
│  - getCurrentUserId()     → 从 sa-token 获取  │
│  - isStaffLogin()         → 判断是否为员工    │
└──────────────────┬───────────────────────────┘
                   │
                   ▼
┌──────────────────────────────────────────────┐
│            sa-token (StpUtil)                 │
│  StpUtil.login(loginId, device)               │
│  StpUtil.getLoginIdAsString()                 │
│  StpUtil.checkLogin()                         │
└──────────────────┬───────────────────────────┘
                   │
                   ▼
┌──────────────────────────────────────────────┐
│                Redis                          │
│  存储 session、token 映射关系                  │
└──────────────────────────────────────────────┘

sa-token接入

增加相关依赖

<!-- Sa-Token 权限认证 -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-spring-boot3-starter</artifactId>
    <version>1.39.0</version>
</dependency>

<!-- Sa-Token 整合 Redis(使用 Jackson 序列化) -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-redis-jackson</artifactId>
    <version>1.39.0</version>
</dependency>

<!-- 连接池(sa-token-redis-jackson 依赖 commons-pool2) -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

redis用于登录状态管理。 在application.yml中增加配置:

## Sa-Token 配置
sa-token:
  # token 名称(同时也是 Cookie 名称、Header 名称)
  token-name: satoken
  # token 有效期(秒),默认 30 天,-1 代表永久有效
  timeout: 2592000
  # token 最低活跃频率(秒),如果 token 超过这个时间没有访问系统就会被冻结,默认-1 代表不限制
  active-timeout: -1
  # 是否允许同一账号并发登录(为 true 时允许一起登录,为 false 时新登录会挤下旧登录)
  is-concurrent: true
  # 在多人登录同一账号时,是否共用一个 token(为 true 时所有登录共用一个 token,为 false 时每次登录新建一个 token)
  is-share: true
  # token 风格(uuid)
  token-style: uuid
  # 是否输出操作日志
  is-log: false
  # 是否从 cookie 中读取 token
  is-read-cookie: true
  # 是否从 header 中读取 token
  is-read-header: true

全局拦截配置:

package cn.hollis.llm.mentor.know.engine.auth.config;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Set;

/**
 * Sa-Token 配置:注册全局拦截器,对除登录、静态资源外的所有接口进行登录校验
 */
@Configuration
public class SaTokenConfig implements WebMvcConfigurer {

    private static final Set<String> STATIC_EXTENSIONS = Set.of(
            "html", "js", "css", "png", "jpg", "jpeg", "gif", "svg",
            "ico", "woff", "woff2", "ttf"
    );

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SaInterceptor(handler -> {
            // 全局登录校验(排除无需登录的路径和静态资源)
            SaRouter.match("/**")
                    .notMatch("/auth/**")
                    .notMatch("/dingtalk/**")
                    .notMatch("/druid/**")
                    .check(r -> {
                        if (isStaticResource()) return;
                        StpUtil.checkLogin();
                    });

            // 文档管理路由需要员工登录(通过 loginId 前缀识别员工身份)
            SaRouter.match("/api/document/**", "/api/segment/**")
                    .check(r -> {
                        StpUtil.checkLogin();
                        String loginId = StpUtil.getLoginIdAsString();
                        if (!loginId.startsWith("staff_")) {
                            throw new NotLoginException("文档管理需要员工登录", NotLoginException.NOT_TOKEN, null);
                        }
                    });
        })).addPathPatterns("/**");
    }

    private boolean isStaticResource() {
        ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attrs == null) return false;
        HttpServletRequest request = attrs.getRequest();
        String uri = request.getRequestURI();
        int dotIndex = uri.lastIndexOf('.');
        if (dotIndex == -1) return false;
        String ext = uri.substring(dotIndex + 1).toLowerCase();
        return STATIC_EXTENSIONS.contains(ext);
    }
}

客户登录流程

登录入口

文件:AuthController.java

@PostMapping
public


}

认证逻辑

文件:AuthServiceImpl.java → login(LoginDTO)

public


    log
             userInfo


}

前端交互

文件:login.html

const


}
const
if

    window
}

说明:sa-token 默认启用 Cookie 模式(is-read-cookie: true),登录成功后自动将 token 写入浏览器 Cookie,后续请求自动携带。

员工登录流程

网页端员工登录

员工可通过网页端手动登录(用于测试或后台管理):

@PostMapping
public


}

认证逻辑与客户登录类似,区别在于:

// 员工登录:使用 staff_ 前缀 + id 作为登录主键,设备标识为 "staff"
StpUtil
// 示例:loginId = "staff_12345", device = "staff"

身份识别机制

获取当前用户信息

@Override
public


}

@Override
public


}

在controller中,不再接受前端传入的userId,而是用以上方法,从session中读取。前端会传入登录后保存的token,通过这个token就能去session中查询已经保存过的用户id了。 没登录的用户进来会无法获取用户id。也无法获取其他人的id,这样做会更加安全。

路由权限控制

全局登录校验

所有接口默认需要登录,除了以下白名单:

.
.
.

员工专属路由

文档管理相关接口要求必须是员工登录:

// 文档管理路由需要员工登录(通过 loginId 前缀识别员工身份)
SaRouter

业务场景中的应用

查询对话列表

做了权限隔离,用户只能查自己的对话列表。

@GetMapping
public


}
版本提示

模型、框架与接口会持续变化。涉及版本号、参数与生产配置时,请在实践前对照对应官方文档。

LLMentor系统化学习大模型应用工程

内容来自个人课程知识库备份,并经过结构化整理。技术版本持续演进,生产使用前请结合官方文档验证。