项目初始化
This commit is contained in:
25
modules/ai/ai-tools/bin/deploy.bat
Normal file
25
modules/ai/ai-tools/bin/deploy.bat
Normal file
@@ -0,0 +1,25 @@
|
||||
@echo off
|
||||
rem /**
|
||||
rem * Copyright (c) 2013-Now https://jeesite.com All rights reserved.
|
||||
rem * No deletion without permission, or be held responsible to law.
|
||||
rem *
|
||||
rem * Author: ThinkGem@163.com
|
||||
rem */
|
||||
echo.
|
||||
echo [<5B><>Ϣ] <20><><EFBFBD>̵<F0B9A4B3>Maven<65><6E><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
echo.
|
||||
|
||||
%~d0
|
||||
cd %~dp0
|
||||
|
||||
if defined JAVA_HOME17 (
|
||||
set "JAVA_HOME=%JAVA_HOME17%" & set "PATH=%JAVA_HOME17%\bin;%PATH%"
|
||||
)
|
||||
call mvn -v
|
||||
echo.
|
||||
|
||||
cd ..
|
||||
call mvn clean deploy -Dmaven.test.skip=true -Pdeploy
|
||||
|
||||
cd bin
|
||||
pause
|
||||
21
modules/ai/ai-tools/bin/deploy.sh
Normal file
21
modules/ai/ai-tools/bin/deploy.sh
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
# /**
|
||||
# * Copyright (c) 2013-Now https://jeesite.com All rights reserved.
|
||||
# * No deletion without permission, or be held responsible to law.
|
||||
# *
|
||||
# * Author: ThinkGem@163.com
|
||||
# */
|
||||
echo ""
|
||||
echo "[信息] 部署工程到Maven服务器。"
|
||||
echo ""
|
||||
|
||||
if [ -n "$JAVA_HOME17" ] && [ -d "$JAVA_HOME17" ]; then
|
||||
export JAVA_HOME="$JAVA_HOME17" PATH="$JAVA_HOME17/bin:$PATH"
|
||||
fi
|
||||
mvn -v
|
||||
echo ""
|
||||
|
||||
cd ..
|
||||
mvn clean deploy -Dmaven.test.skip=true -Pdeploy
|
||||
|
||||
cd bin
|
||||
25
modules/ai/ai-tools/bin/package.bat
Normal file
25
modules/ai/ai-tools/bin/package.bat
Normal file
@@ -0,0 +1,25 @@
|
||||
@echo off
|
||||
rem /**
|
||||
rem * Copyright (c) 2013-Now https://jeesite.com All rights reserved.
|
||||
rem * No deletion without permission, or be held responsible to law.
|
||||
rem *
|
||||
rem * Author: ThinkGem@163.com
|
||||
rem */
|
||||
echo.
|
||||
echo [<5B><>Ϣ] <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>װ<EFBFBD><D7B0><EFBFBD>̣<EFBFBD><CCA3><EFBFBD><EFBFBD><EFBFBD>jar<61><72><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD>
|
||||
echo.
|
||||
|
||||
%~d0
|
||||
cd %~dp0
|
||||
|
||||
if defined JAVA_HOME17 (
|
||||
set "JAVA_HOME=%JAVA_HOME17%" & set "PATH=%JAVA_HOME17%\bin;%PATH%"
|
||||
)
|
||||
call mvn -v
|
||||
echo.
|
||||
|
||||
cd ..
|
||||
call mvn clean install -Dmaven.test.skip=true -Ppackage
|
||||
|
||||
cd bin
|
||||
pause
|
||||
21
modules/ai/ai-tools/bin/package.sh
Normal file
21
modules/ai/ai-tools/bin/package.sh
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
# /**
|
||||
# * Copyright (c) 2013-Now https://jeesite.com All rights reserved.
|
||||
# * No deletion without permission, or be held responsible to law.
|
||||
# *
|
||||
# * Author: ThinkGem@163.com
|
||||
# */
|
||||
echo ""
|
||||
echo "[信息] 打包安装工程,生成jar包文件。"
|
||||
echo ""
|
||||
|
||||
if [ -n "$JAVA_HOME17" ] && [ -d "$JAVA_HOME17" ]; then
|
||||
export JAVA_HOME="$JAVA_HOME17" PATH="$JAVA_HOME17/bin:$PATH"
|
||||
fi
|
||||
mvn -v
|
||||
echo ""
|
||||
|
||||
cd ..
|
||||
mvn clean install -Dmaven.test.skip=true -Ppackage
|
||||
|
||||
cd bin
|
||||
73
modules/ai/ai-tools/pom.xml
Normal file
73
modules/ai/ai-tools/pom.xml
Normal file
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.jeesite</groupId>
|
||||
<artifactId>jeesite-module-ai-parent</artifactId>
|
||||
<version>5.15.1.springboot3-SNAPSHOT</version>
|
||||
<relativePath>../ai-parent/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>jeesite-module-ai-tools</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>JeeSite Module AI Tools</name>
|
||||
<url>https://jeesite.com</url>
|
||||
<inceptionYear>2013-Now</inceptionYear>
|
||||
|
||||
<properties>
|
||||
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- 核心模块 --><!--suppress VulnerableLibrariesLocal -->
|
||||
<dependency>
|
||||
<groupId>com.jeesite</groupId>
|
||||
<artifactId>jeesite-module-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 内容管理模块 -->
|
||||
<dependency>
|
||||
<groupId>com.jeesite</groupId>
|
||||
<artifactId>jeesite-module-cms</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- AI Model -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-model</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- AI MCP -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-mcp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-mcp-annotations</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<id>thinkgem</id>
|
||||
<name>WangZhen</name>
|
||||
<email>thinkgem at 163.com</email>
|
||||
<roles><role>Project lead</role></roles>
|
||||
<timezone>+8</timezone>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<organization>
|
||||
<name>JeeSite</name>
|
||||
<url>https://jeesite.com</url>
|
||||
</organization>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2013-Now https://jeesite.com All rights reserved.
|
||||
* No deletion without permission, or be held responsible to law.
|
||||
*/
|
||||
package com.jeesite.modules.ai.tools.annotation;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* AI Bean 自动扫描
|
||||
* @author ThinkGem
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Documented
|
||||
@Component
|
||||
public @interface AiTools {
|
||||
|
||||
/**
|
||||
* Alias for {@link Component#value}.
|
||||
*/
|
||||
@AliasFor(annotation = Component.class)
|
||||
String value() default "";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2013-Now https://jeesite.com All rights reserved.
|
||||
* No deletion without permission, or be held responsible to law.
|
||||
*/
|
||||
package com.jeesite.modules.ai.tools.aspect;
|
||||
|
||||
import com.jeesite.modules.ai.tools.utils.SubjectHolder;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.apache.shiro.util.ThreadContext;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 用户信息注入切面
|
||||
* @author ThinkGem
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
public class SubjectAspect {
|
||||
|
||||
/**
|
||||
* 给 Tool 传递当前用户信息
|
||||
*/
|
||||
@Around("@annotation(org.springframework.ai.tool.annotation.Tool)")
|
||||
public Object handleThreadContext(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
// Object[] args = joinPoint.getArgs();
|
||||
// for (Object arg : args) {
|
||||
// if (arg instanceof ToolContext toolContext) {
|
||||
// Map<String, Object> context = toolContext.getContext();
|
||||
// Subject subject = (Subject) context.get("subject");
|
||||
// if (subject != null) {
|
||||
// ThreadContext.bind(subject);
|
||||
// return joinPoint.proceed();
|
||||
// }
|
||||
// } else if (arg instanceof McpMeta mcpMeta) { }
|
||||
// }
|
||||
Subject subject = SubjectHolder.getSubject();
|
||||
if (subject != null) {
|
||||
ThreadContext.bind(subject);
|
||||
}
|
||||
return joinPoint.proceed();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2013-Now https://jeesite.com All rights reserved.
|
||||
* No deletion without permission, or be held responsible to law.
|
||||
*/
|
||||
package com.jeesite.modules.ai.tools.impl;
|
||||
|
||||
import com.jeesite.common.lang.DateUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* AI MCP 工具调用
|
||||
* @author ThinkGem
|
||||
*/
|
||||
public class TestAiTools {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TestAiTools.class);
|
||||
|
||||
/**
|
||||
* 获取服务器时间
|
||||
*/
|
||||
public String getCurrentDateTime() {
|
||||
String dateTime = "当前日期时间:" + DateUtils.getDateTime();
|
||||
logger.info("当前日期时间 ============== {}", dateTime);
|
||||
return dateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开关房间的灯
|
||||
*/
|
||||
public String roomLightSwitch(String roomName, boolean on) {
|
||||
String message = roomName + " 房间里的灯被 " + (on ? "打开" : "关闭");
|
||||
logger.info("房间灯光开关 ============== {}", message);
|
||||
return String.format("""
|
||||
{
|
||||
"message": "%s",
|
||||
"roomName": "%s",
|
||||
"on": %s
|
||||
}
|
||||
""", message, roomName, on);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2013-Now https://jeesite.com All rights reserved.
|
||||
* No deletion without permission, or be held responsible to law.
|
||||
*/
|
||||
package com.jeesite.modules.ai.tools.impl;
|
||||
|
||||
import com.jeesite.common.lang.StringUtils;
|
||||
import com.jeesite.common.mapper.JsonMapper;
|
||||
import com.jeesite.common.mybatis.mapper.query.QueryType;
|
||||
import com.jeesite.modules.sys.entity.EmpUser;
|
||||
import com.jeesite.modules.sys.entity.User;
|
||||
import com.jeesite.modules.sys.service.EmpUserService;
|
||||
import com.jeesite.modules.sys.utils.UserUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI MCP 工具调用
|
||||
* @author ThinkGem
|
||||
*/
|
||||
public class UserAiTools {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(UserAiTools.class);
|
||||
|
||||
private final EmpUserService empUserService;
|
||||
|
||||
public UserAiTools(EmpUserService empUserService) {
|
||||
this.empUserService = empUserService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前会话的用户信息
|
||||
*/
|
||||
public String getCurrentUser() {
|
||||
User currentUser = UserUtils.getUser();
|
||||
if (StringUtils.isBlank(currentUser.getUserCode())) {
|
||||
logger.info("当前用户信息 ============== 当前用户未登录。");
|
||||
return "当前用户未登录。";
|
||||
}
|
||||
String result = JsonMapper.toJson(currentUser);
|
||||
logger.info("当前用户信息 ============== 查询结果:{}", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询用户信息
|
||||
*/
|
||||
public String findEmpUserInfo(String userName) {
|
||||
EmpUser where = new EmpUser();
|
||||
where.sqlMap().getWhere().and(w -> w
|
||||
.or("a.user_name", QueryType.LIKE, userName)
|
||||
.or("e.emp_name", QueryType.LIKE, userName));
|
||||
// 权限控制,只能查询当前用户能查询的用户信息
|
||||
logger.info("获取用户信息 ============== 当前用户: {}", where.currentUser().getUserCode());
|
||||
empUserService.addDataScopeFilter(where);
|
||||
List<EmpUser> list = empUserService.findList(where);
|
||||
String result = JsonMapper.toJson(list);
|
||||
logger.info("获取用户信息 ============== 查询结果: {}", result);
|
||||
if (list.isEmpty()) {
|
||||
return "未找到符合条件的用户信息。";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2013-Now https://jeesite.com All rights reserved.
|
||||
* No deletion without permission, or be held responsible to law.
|
||||
*/
|
||||
package com.jeesite.modules.ai.tools.local;
|
||||
|
||||
import com.jeesite.modules.ai.tools.annotation.AiTools;
|
||||
import com.jeesite.modules.ai.tools.impl.TestAiTools;
|
||||
import org.springframework.ai.tool.annotation.Tool;
|
||||
import org.springframework.ai.tool.annotation.ToolParam;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
|
||||
/**
|
||||
* AI MCP 工具调用
|
||||
* @author ThinkGem
|
||||
*/
|
||||
@AiTools
|
||||
@ConditionalOnProperty(name = "spring.ai.mcp.server.enabled", havingValue = "false", matchIfMissing = true)
|
||||
public class LocalTestAiTools extends TestAiTools {
|
||||
|
||||
/**
|
||||
* 获取服务器时间
|
||||
*/
|
||||
@Tool(name="当前服务器时间", description = "当前服务器日期时间,格式为 yyyy-MM-dd HH:mm:ss。")
|
||||
public String getCurrentDateTime() {
|
||||
return super.getCurrentDateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 开关房间的灯
|
||||
*/
|
||||
@Tool(
|
||||
name = "房间灯光开关",
|
||||
description = "控制指定房间的灯光开关。需要提供房间名称(如 '客厅'、'卧室')和目标状态(true 表示开灯,false 表示关灯)。"
|
||||
)
|
||||
public String roomLightSwitch(
|
||||
@ToolParam(description = "要控制的房间名称,例如:'客厅'、'卧室'、'餐厅'、'厨房'") String roomName,
|
||||
@ToolParam(description = "灯光目标状态:true 表示打开灯,false 表示关闭灯") boolean on) {
|
||||
return super.roomLightSwitch(roomName, on);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2013-Now https://jeesite.com All rights reserved.
|
||||
* No deletion without permission, or be held responsible to law.
|
||||
*/
|
||||
package com.jeesite.modules.ai.tools.local;
|
||||
|
||||
import com.jeesite.modules.ai.tools.annotation.AiTools;
|
||||
import com.jeesite.modules.ai.tools.impl.UserAiTools;
|
||||
import com.jeesite.modules.sys.service.EmpUserService;
|
||||
import org.springframework.ai.tool.annotation.Tool;
|
||||
import org.springframework.ai.tool.annotation.ToolParam;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
|
||||
/**
|
||||
* AI MCP 工具调用
|
||||
* @author ThinkGem
|
||||
*/
|
||||
@AiTools
|
||||
@ConditionalOnProperty(name = "spring.ai.mcp.server.enabled", havingValue = "false", matchIfMissing = true)
|
||||
public class LocalUserAiTools extends UserAiTools {
|
||||
|
||||
public LocalUserAiTools(EmpUserService empUserService) {
|
||||
super(empUserService);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前会话的用户信息
|
||||
*/
|
||||
@Tool(name="当前用户信息", description = "无条件获取当前用户信息")
|
||||
public String getCurrentUser() {
|
||||
return super.getCurrentUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询用户信息
|
||||
*/
|
||||
@Tool(
|
||||
name="查询用户信息",
|
||||
description = "根据用户名(登录账号)或员工姓名模糊查询用户信息。结果以表格形式展示," +
|
||||
"包含用户名userName、姓名empUser、部门officeName等基本信息。"
|
||||
)
|
||||
public String findEmpUserInfo(@ToolParam(description = "用户的登录名或员工的真实姓名,支持模糊匹配") String userName) {
|
||||
return super.findEmpUserInfo(userName);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2013-Now https://jeesite.com All rights reserved.
|
||||
* No deletion without permission, or be held responsible to law.
|
||||
*/
|
||||
package com.jeesite.modules.ai.tools.mcp;
|
||||
|
||||
import com.jeesite.modules.ai.tools.impl.TestAiTools;
|
||||
import org.springaicommunity.mcp.annotation.McpTool;
|
||||
import org.springaicommunity.mcp.annotation.McpToolParam;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* AI MCP 工具调用
|
||||
* @author ThinkGem
|
||||
*/
|
||||
@Component
|
||||
@ConditionalOnProperty(name = "spring.ai.mcp.server.enabled", havingValue = "true", matchIfMissing = false)
|
||||
public class McpTestAiTools extends TestAiTools {
|
||||
|
||||
/**
|
||||
* 获取服务器时间
|
||||
*/
|
||||
@McpTool(name="当前服务器时间", description = "当前服务器日期时间,格式为 yyyy-MM-dd HH:mm:ss。")
|
||||
public String getCurrentDateTime() {
|
||||
return super.getCurrentDateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 开关房间的灯
|
||||
*/
|
||||
@McpTool(
|
||||
name = "房间灯光开关",
|
||||
description = "控制指定房间的灯光开关。需要提供房间名称(如 '客厅'、'卧室')和目标状态(true 表示开灯,false 表示关灯)。"
|
||||
)
|
||||
public String roomLightSwitch(
|
||||
@McpToolParam(description = "要控制的房间名称,例如:'客厅'、'卧室'、'餐厅'、'厨房'") String roomName,
|
||||
@McpToolParam(description = "灯光目标状态:true 表示打开灯,false 表示关闭灯") boolean on) {
|
||||
return super.roomLightSwitch(roomName, on);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) 2013-Now https://jeesite.com All rights reserved.
|
||||
* No deletion without permission, or be held responsible to law.
|
||||
*/
|
||||
package com.jeesite.modules.ai.tools.mcp;
|
||||
|
||||
import com.jeesite.modules.ai.tools.impl.UserAiTools;
|
||||
import com.jeesite.modules.sys.service.EmpUserService;
|
||||
import org.springaicommunity.mcp.annotation.McpTool;
|
||||
import org.springaicommunity.mcp.annotation.McpToolParam;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* AI MCP 工具调用
|
||||
* @author ThinkGem
|
||||
*/
|
||||
@Component
|
||||
@ConditionalOnProperty(name = "spring.ai.mcp.server.enabled", havingValue = "true", matchIfMissing = false)
|
||||
public class McpUserAiTools extends UserAiTools {
|
||||
|
||||
public McpUserAiTools(EmpUserService empUserService) {
|
||||
super(empUserService);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前会话的用户信息
|
||||
*/
|
||||
@McpTool(name="当前用户信息", description = "无条件获取当前用户信息")
|
||||
public String getCurrentUser() {
|
||||
return super.getCurrentUser();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询用户信息
|
||||
*/
|
||||
@McpTool(
|
||||
name="查询用户信息",
|
||||
description = "根据用户名(登录账号)或员工姓名模糊查询用户信息。结果以表格形式展示," +
|
||||
"包含用户名userName、姓名empUser、部门officeName等基本信息。"
|
||||
)
|
||||
public String findEmpUserInfo(@McpToolParam(description = "用户的登录名或员工的真实姓名,支持模糊匹配") String userName) {
|
||||
return super.findEmpUserInfo(userName);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2013-Now https://jeesite.com All rights reserved.
|
||||
* No deletion without permission, or be held responsible to law.
|
||||
*/
|
||||
package com.jeesite.modules.ai.tools.utils;
|
||||
|
||||
import org.apache.shiro.subject.Subject;
|
||||
|
||||
/**
|
||||
* SubjectHolder
|
||||
* @author ThinkGem
|
||||
*/
|
||||
public class SubjectHolder {
|
||||
|
||||
private static final InheritableThreadLocal<Subject> threadLocal = new InheritableThreadLocal<>();
|
||||
|
||||
public static Subject getSubject() {
|
||||
return threadLocal.get();
|
||||
}
|
||||
|
||||
public static void setSubject(final Subject subject) {
|
||||
threadLocal.set(subject);
|
||||
}
|
||||
|
||||
public static void remove() {
|
||||
threadLocal.remove();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user