Skip to content

会话管理 ​

本文档详细介绍了校园领域搜索问答系统中的会话管理功能模块的设计与实现。该模块旨在有效管理用户与系统之间的对话会话,确保对话的连贯性和一致性。本文将涵盖接口设计、数据库表结构、控制器实现以及服务层实现。

功能概述 ​

会话管理模块主要负责以下功能:

  • 创建会话:允许用户创建新的对话会话。
  • 列出会话:提供用户所有对话会话的列表,支持分页查询。
  • 设置会话名称:允许用户修改现有会话的名称。
  • 删除会话:支持用户删除不需要的会话,采用软删除方式。

接口设计 ​

1. 创建会话 ​

  • 请求方法:POST

  • 接口路径:/api/v1/chat/create

  • 请求参数:

    参数类型描述是否必填
    nameString会话名称是
    school_idLong学校 ID(可选,默认值为 1)否
    chat_typeInteger会话类型(默认值为 0)否
    typeString会话类型描述否
    app_idLong应用 ID(可选)否
  • 响应格式:

    json
    {
      "data": {
        "id": 12345,
        "name": "会话名称"
      },
      "code": 1,
      "msg": null,
      "ok": true
    }

2. 列出会话 ​

  • 请求方法:GET

  • 接口路径:/api/v1/chat/list

  • 请求参数:

    参数类型描述是否必填
    offsetInteger页码(默认值为 1)否
    limitInteger每页数量(默认值为 10)否
    school_idLong学校 ID(可选)否
    chat_typeInteger会话类型(可选)否
  • 响应格式:

    json
    {
      "data": [
        {
          "id": 12345,
          "name": "会话名称",
          "user_id": "user123",
          "school_id": 1,
          "type": "类型描述",
          "chat_type": 0,
          "create_time": "2024-07-16T12:34:36.651846Z",
          "update_time": "2024-07-16T12:34:36.651846Z"
        }
        // 更多会话
      ],
      "code": 1,
      "msg": null,
      "ok": true
    }

    3. 设置会话名称 ​

  • 请求方法:GET

  • 接口路径:/api/v1/chat/set/name?session_id=xxx?name=xxx

  • 请求参数:

    参数类型描述是否必填
    session_idLong会话 ID是
    nameString新的会话名称是
  • 响应格式:

    json
    {
      "data": null,
      "code": 1,
      "msg": null,
      "ok": true
    }

4. 删除会话 ​

  • 请求方法:GET

  • 接口路径:/api/v1/chat/delete/session_id=?

  • 请求参数:

    参数类型描述是否必填
    session_idLong会话 ID是
  • 响应格式:

    json
    {
      "data": null,
      "code": 1,
      "msg": null,
      "ok": true
    }

后端代码实现 ​

ApiChatHandler ​

ApiChatHandler类负责处理与会话管理相关的 HTTP 请求,包括创建会话、列出会话、设置会话名称以及删除会话。以下是该类的实现代码及说明:

java
package nexus.io.llm.handler;

import java.util.List;
import java.util.stream.Collectors;

import com.jfinal.kit.Kv;
import nexus.io.db.TableResult;
import nexus.io.db.activerecord.Row;
import nexus.io.jfinal.aop.Aop;
import nexus.io.llm.services.LlmChatSessionService;
import nexus.io.llm.services.LlmQuestionRecommendService;
import nexus.io.model.body.RespBodyVo;
import nexus.io.model.page.Page;
import nexus.io.tio.boot.http.TioRequestContext;
import nexus.io.tio.http.common.HttpRequest;
import nexus.io.tio.http.common.HttpResponse;
import nexus.io.tio.http.server.util.CORSUtils;
import nexus.io.tio.utils.hutool.StrUtil;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ApiChatHandler {

  /**
   * 处理推荐问题请求
   *
   * @param request HTTP请求对象
   * @return HTTP响应对象
   */
  public HttpResponse recommend(HttpRequest request) {
    // 获取响应对象
    HttpResponse response = TioRequestContext.getResponse();
    // 启用CORS
    CORSUtils.enableCORS(response);

    // 获取请求参数num,默认值为4
    Integer num = request.getInt("num");
    if (num == null) {
      num = 4;
    }

    // 调用服务层获取推荐问题数据
    TableResult<Page<Row>> tr = Aop.get(LlmQuestionRecommendService.class).page(num);

    RespBodyVo respBodyVo;
    if (tr.isOk()) {
      // 获取数据列表
      List<Row> list = tr.getData().getList();
      // 将Row转换为Kv对象
      List<Kv> kvs = list.stream().map(Row::toKv).collect(Collectors.toList());
      // 构建成功响应
      respBodyVo = RespBodyVo.ok(kvs);
    } else {
      // 构建失败响应
      respBodyVo = RespBodyVo.fail(tr.getMsg());
    }

    // 设置响应的JSON内容
    return response.setJson(respBodyVo);
  }

  /**
   * 创建会话
   *
   * @param request HTTP请求对象
   * @return HTTP响应对象
   */
  public HttpResponse createSession(HttpRequest request) {
    HttpResponse response = TioRequestContext.getResponse();
    CORSUtils.enableCORS(response);

    String userId = TioRequestContext.getUserIdString();
    String name = request.getParam("name");
    String schoolIdString = request.getParam("school_id");
    Integer chatType = request.getInt("chat_type");
    String type = request.getString("type");
    Long appId = request.getLong("app_id");
    if (chatType == null) {
      chatType = 0;
    }

    RespBodyVo respBodyVo = null;

    if (StrUtil.isBlank(name)) {
      response.setStatus(400);
      respBodyVo = RespBodyVo.fail("name cannot be empty");
    }
    if (StrUtil.isBlank(schoolIdString)) {
      schoolIdString = "1";
    }

    Long school_id = Long.parseLong(schoolIdString);
    TableResult<Kv> tr = Aop.get(LlmChatSessionService.class).create(userId, name, school_id, type, chatType, appId);
    if (tr.isOk()) {
      respBodyVo = RespBodyVo.ok(tr.getData());
    } else {
      respBodyVo = RespBodyVo.fail();
    }

    return response.setJson(respBodyVo);
  }

  /**
   * 删除会话
   *
   * @param request HTTP请求对象
   * @return HTTP响应对象
   */
  public HttpResponse deleteSession(HttpRequest request) {
    HttpResponse response = TioRequestContext.getResponse();
    CORSUtils.enableCORS(response);

    Long session_id = request.getLong("session_id");
    log.info("session_id: {}", session_id);
    if (session_id == null) {
      return response.fail(RespBodyVo.fail("session_id can not be empty"));
    }

    String userId = TioRequestContext.getUserIdString();
    int updateResult = Aop.get(LlmChatSessionService.class).softDelete(session_id, userId);
    if (updateResult > 0) {
      response.setJson(RespBodyVo.ok());
    } else {
      response.setJson(RespBodyVo.fail());
    }
    return response;
  }

  /**
   * 列出会话
   *
   * @param request HTTP请求对象
   * @return HTTP响应对象
   */
  public HttpResponse listSession(HttpRequest request) {
    HttpResponse response = TioRequestContext.getResponse();
    CORSUtils.enableCORS(response);

    Integer pageNo = request.getInt("offset");
    Integer pageSize = request.getInt("limit");
    Long schoolId = request.getLong("school_id");
    Integer chatType = request.getInt("chat_type");

    if (pageNo == null) {
      pageNo = 1;
    }
    if (pageSize == null) {
      pageSize = 10;
    }

    if (chatType == null) {
      chatType = 0;
    }
    String userId = TioRequestContext.getUserIdString();

    List<Kv> list = Aop.get(LlmChatSessionService.class).page(pageNo, pageSize, userId, schoolId, chatType);

    return response.setJson(RespBodyVo.ok(list));
  }

  /**
   * 设置会话名称
   *
   * @param request HTTP请求对象
   * @return HTTP响应对象
   */
  public HttpResponse setSessionName(HttpRequest request) {
    HttpResponse response = TioRequestContext.getResponse();
    CORSUtils.enableCORS(response);

    Long sessionId = request.getLong("session_id");
    String name = request.getParam("name");

    if (sessionId == null) {
      return response.fail(RespBodyVo.fail("session_id can not be empty"));
    }

    if (StrUtil.isEmpty(name)) {
      return response.fail(RespBodyVo.fail("name can not be empty"));
    }

    String userId = TioRequestContext.getUserIdString();

    boolean exists = Aop.get(LlmChatSessionService.class).exists(sessionId, userId);
    if (!exists) {
      return response.fail(RespBodyVo.fail("invalid session"));
    }

    int updateResult = Aop.get(LlmChatSessionService.class).updateSessionName(name, sessionId, userId);
    RespBodyVo respBodyVo = null;
    if (updateResult > 0) {
      respBodyVo = RespBodyVo.ok();
    } else {
      respBodyVo = RespBodyVo.fail();
    }
    return response.setJson(respBodyVo);
  }
}

控制器代码说明 ​

  • createSession:

    • 处理/api/v1/chat/create的POST请求。
    • 从请求中获取必要的参数,如name、school_id、chat_type等。
    • 校验参数有效性,确保name不为空。
    • 调用服务层LlmChatSessionService.create方法创建会话。
    • 根据服务层返回结果构建响应。
  • deleteSession:

    • 处理/api/v1/chat/delete的DELETE请求。
    • 获取并校验session_id参数。
    • 调用服务层LlmChatSessionService.softDelete方法软删除会话。
    • 根据删除结果构建响应。
  • listSession:

    • 处理/api/v1/chat/list的GET请求。
    • 获取分页参数offset和limit,设置默认值。
    • 获取可选参数school_id和chat_type。
    • 调用服务层LlmChatSessionService.page方法获取会话列表。
    • 构建并返回会话列表的响应。
  • setSessionName:

    • 处理/api/v1/chat/set/name的PUT请求。
    • 获取并校验session_id和name参数。
    • 检查会话是否存在且属于当前用户。
    • 调用服务层LlmChatSessionService.updateSessionName方法更新会话名称。
    • 根据更新结果构建响应。

服务层实现 ​

LlmChatSessionService类负责与数据库交互,处理会话的创建、查询、更新和删除操作。以下是该类的实现代码及说明:

java
package nexus.io.llm.services;

import java.util.ArrayList;
import java.util.List;

import com.jfinal.kit.Kv;
import nexus.io.db.TableInput;
import nexus.io.db.TableResult;
import nexus.io.db.activerecord.Db;
import nexus.io.db.activerecord.Row;
import nexus.io.llm.consts.AgentTableNames;
import nexus.io.model.page.Page;
import nexus.io.table.services.ApiTable;
import nexus.io.tio.utils.snowflake.SnowflakeIdUtils;

public class LlmChatSessionService {

  /**
   * 创建会话
   *
   * @param userId    用户ID
   * @param name      会话名称
   * @param school_id 学校ID
   * @param type      会话类型描述
   * @param chat_type 会话类型
   * @param appId     应用ID
   * @return 创建结果,包含会话ID和名称
   */
  public TableResult<Kv> create(String userId, String name, Long school_id, String type, Integer chat_type, Long appId) {

    long id = SnowflakeIdUtils.id();
    Row record = Row.by("id", id);
    record.setTableName(AgentTableNames.llm_chat_session);
    record.set("name", name);
    record.set("user_id", userId);
    record.set("school_id", school_id);
    record.set("type", type);
    record.set("chat_type", chat_type);
    if (appId != null) {
      record.set("app_id", appId);
    }
    boolean save = Db.save(record);
    if (save) {
      return TableResult.ok(Kv.by("id", id).set("name", name));
    } else {
      return TableResult.fail();
    }
  }

  /**
   * 检查会话是否存在且属于当前用户
   *
   * @param id     会话ID
   * @param userId 用户ID
   * @return 存在返回true,否则返回false
   */
  public boolean exists(Long id, String userId) {
    String sql = "select count(1) from %s where id=? and user_id=? and deleted=0";
    sql = String.format(sql, AgentTableNames.llm_chat_session);
    return Db.existsBySql(sql, id, userId);
  }

  /**
   * 分页获取会话列表
   *
   * @param pageNo    页码
   * @param pageSize  每页数量
   * @param userId    用户ID
   * @param schoolId  学校ID
   * @param chat_type 会话类型
   * @return 会话列表
   */
  public List<Kv> page(int pageNo, int pageSize, String userId, Long schoolId, Integer chat_type) {

    TableInput ti = TableInput.create();
    ti.set("user_id", userId);

    if (schoolId != null) {
      ti.set("school_id", schoolId);
    }
    ti.set("chat_type", chat_type);
    ti.set("deleted", 0);

    ti.pageNo(pageNo).pageSize(pageSize);
    ti.orderBy("create_time").asc(false);

    TableResult<Page<Row>> result = ApiTable.page(AgentTableNames.llm_chat_session, ti);
    Page<Row> paginate = result.getData();

    List<Row> list = paginate.getList();
    List<Kv> kvs = new ArrayList<>();
    for (Row record : list) {
      kvs.add(record.toKv());
    }
    return kvs;
  }

  /**
   * 更新会话名称
   *
   * @param name     新的会话名称
   * @param id       会话ID
   * @param userId   用户ID
   * @return 更新影响的行数
   */
  public int updateSessionName(String name, Long id, String userId) {
    String sql = String.format("update %s set name=? where id=? and user_id=?", AgentTableNames.llm_chat_session);
    return Db.updateBySql(sql, name, id, userId);
  }

  /**
   * 软删除会话
   *
   * @param id     会话ID
   * @param userId 用户ID
   * @return 更新影响的行数
   */
  public int softDelete(Long id, String userId) {
    String sql = "update %s set deleted=1 where id=? and user_id=?";
    sql = String.format(sql, AgentTableNames.llm_chat_session);
    return Db.updateBySql(sql, id, userId);
  }
}

服务层代码说明 ​

  • create:

    • 生成唯一的会话 ID(使用 Snowflake 算法)。
    • 构建Row对象,设置会话相关字段。
    • 保存会话记录到数据库。
    • 返回创建结果,包括会话 ID 和名称。
  • exists:

    • 检查指定的会话 ID 是否存在且属于当前用户,且未被删除。
    • 返回布尔值,存在则返回true,否则返回false。
  • page:

    • 构建查询条件,设置用户 ID、学校 ID、会话类型及删除标志。
    • 设置分页参数和排序方式。
    • 调用ApiTable.page方法进行分页查询。
    • 将查询结果转换为List<Kv>格式,便于前端使用。
  • updateSessionName:

    • 更新指定会话的名称,确保会话属于当前用户。
    • 执行更新操作,返回影响的行数。
  • softDelete:

    • 对指定会话进行软删除,设置deleted字段为1。
    • 确保会话属于当前用户。
    • 执行更新操作,返回影响的行数。

总结 ​

本文档详细介绍了校园领域搜索问答系统中会话管理功能模块的设计与实现。通过定义清晰的 API 接口、设计合理的数据库表结构以及实现高效的控制器和服务层代码,该模块能够有效地管理用户的对话会话,确保系统的连贯性和一致性。未来,可以进一步优化会话管理功能,例如增加会话恢复功能、会话分类管理以及与其他模块的深度集成,以提升系统的整体用户体验和功能丰富性。

MIT Licensed