初始化项目

master
gitee 7 days ago
commit 18cd02517f

@ -0,0 +1,17 @@
package com.supervision.livedigitalavatarmanage;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@EnableAspectJAutoProxy(exposeProxy = true,proxyTargetClass = true)
@MapperScan(basePackages = {"com.supervision.**.mapper"})
@SpringBootApplication
public class LiveDigitalAvatarManageApplication {
public static void main(String[] args) {
SpringApplication.run(LiveDigitalAvatarManageApplication.class, args);
}
}

@ -0,0 +1,63 @@
package com.supervision.livedigitalavatarmanage.config;
import com.supervision.livedigitalavatarmanage.constant.ResultStatusEnum;
import com.supervision.livedigitalavatarmanage.dto.R;
import com.supervision.livedigitalavatarmanage.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
/**
*
*
* @author wb
* @date 2022/3/10 13:24
*/
@Slf4j
@Configuration
@RestControllerAdvice(annotations = RestController.class, basePackages = {"com.supervision.ai.service.**.controller"})
public class ExceptionHandlerConfig {
/**
*
*
* @param exception
* @return
*/
@ExceptionHandler(IllegalArgumentException.class)
public R<?> manualValidationExceptionResponse(IllegalArgumentException exception) {
log.error("=========手动校验参数异常=========>>>");
log.error(exception.getMessage(), exception);
log.error("<<<=========手动校验参数异常=========");
return R.fail(ResultStatusEnum.ILLEGAL_ARGUMENT.getCode(), exception.getMessage());
}
@ExceptionHandler(BusinessException.class)
public R<?> businessExceptionResponse(BusinessException exception) {
log.error("=========运行异常=========>>>");
log.error(exception.getMessage(), exception);
log.error("<<<=========运行异常=========");
return R.fail(511, exception.getMessage());
}
@ExceptionHandler(RuntimeException.class)
public R<?> manualValidationExceptionResponse(RuntimeException exception) {
log.error("=========运行异常=========>>>");
log.error(exception.getMessage(), exception);
log.error("<<<=========运行异常=========");
return R.fail(ResultStatusEnum.RUNTIME_EXCEPTION.getCode(), exception.getMessage());
}
@ExceptionHandler(MaxUploadSizeExceededException.class)
public R<?> handleMaxSizeException(MaxUploadSizeExceededException exception) {
log.error("=========文件大小超出限制异常=========>>>");
log.error(exception.getMessage(), exception);
log.error("<<<=========文件大小超出限制异常=========");
return R.fail(ResultStatusEnum.EXCEED_FILE_SIZE.getCode(), exception.getMessage());
}
}

@ -0,0 +1,26 @@
package com.supervision.livedigitalavatarmanage.config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import java.time.LocalDateTime;
/**
* @author Ray
*/
public class MyMetaObjectHandler implements MetaObjectHandler {
public MyMetaObjectHandler() {
}
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime", LocalDateTime.now(), metaObject);
this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
}
}

@ -0,0 +1,44 @@
package com.supervision.livedigitalavatarmanage.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* MybatisPlus
*
* @author qmy
* @version 1.0.0 2020/10/22 9:47
* @since JDK1.8
*/
@Configuration
public class MybatisPlusConfig {
@Bean
public MyMetaObjectHandler myMetaObjectHandler() {
return new MyMetaObjectHandler();
}
/**
*
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(this.paginationInterceptor());
return interceptor;
}
private PaginationInnerInterceptor paginationInterceptor() {
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor();
paginationInterceptor.setOverflow(false);
/**
* ! .
*/
paginationInterceptor.setDbType(DbType.POSTGRE_SQL);
return paginationInterceptor;
}
}

@ -0,0 +1,45 @@
package com.supervision.livedigitalavatarmanage.config;
import cn.hutool.core.util.StrUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class OllamaChatModelAspect {
@Value("${spring.ai.ollama.chat.model}")
private String model;
private String callStringMessage = "String org.springframework.ai.chat.model.ChatModel.call(String)";
/**
* ollamacall/no_thinkthink
* @param joinPoint joinPoint
* @return Object
* @throws Throwable
*/
@Around("execution(* org.springframework.ai.chat.model.ChatModel.call(..))")
public Object aroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable {
String signature = joinPoint.getSignature().toString();
// 获取原始参数
Object[] args = joinPoint.getArgs();
// 如果是String类型的call方法修改其参数
/*if (StrUtil.equals(signature, callStringMessage) && args.length > 0) {
args[0] = args[0] + "\n /no_think";
}*/
// 执行原方法
Object result = joinPoint.proceed(args);
if (StrUtil.equals(model,"qwen3:30b-a3b")|| StrUtil.equals(model,"qwen3:32b")) {
if(StrUtil.equals(signature, callStringMessage)){
result = ((String) result).replaceAll("(?is)<think\\b[^>]*>(.*?)</think>", "").trim();
}
}
return result;
}
}

@ -0,0 +1,28 @@
package com.supervision.livedigitalavatarmanage.config;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.JdkClientHttpRequestFactory;
import org.springframework.web.client.RestClient;
import java.net.http.HttpClient;
import java.time.Duration;
@Configuration
public class OllamaConfig {
@Value("${spring.ai.ollama.chat.options.timeout:180000}")
private int timeout;
@Bean
@Qualifier("restClientBuilder")
public RestClient.Builder restClientBuilder() {
JdkClientHttpRequestFactory requestFactory = new JdkClientHttpRequestFactory(
HttpClient.newHttpClient());
requestFactory.setReadTimeout(Duration.ofMillis(timeout));
return RestClient.builder().requestFactory(requestFactory);
}
}

@ -0,0 +1,84 @@
package com.supervision.livedigitalavatarmanage.config;
import com.supervision.livedigitalavatarmanage.filter.JwtAuthenticationFilter;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import java.util.Base64;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final UserDetailsService userDetailsService;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable) // 禁用CSRF
.cors(Customizer.withDefaults())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/login","/ollama/generate").permitAll()
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authenticationProvider(authenticationProvider())
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
// 禁用 formLogin 和 httpBasic
.formLogin(AbstractHttpConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable);
return http.build();
}
@Bean
public AuthenticationProvider authenticationProvider() {
// 使用DaoAuthenticationProvider并注入自定义的UserDetailsService和PasswordEncoder
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService); // 从数据库读取用户进行认证
authProvider.setPasswordEncoder(passwordEncoder()); // 使用BCrypt密码器验证密码
return authProvider;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
// 从AuthenticationConfiguration中获取AuthenticationManager实例
return config.getAuthenticationManager();
}
@Bean
public PasswordEncoder passwordEncoder() {
// 使用BCryptPasswordEncoder作为密码加密器
return new BCryptPasswordEncoder();
}
public static void main(String[] args) {
String s = Base64.getEncoder().encodeToString(Keys.secretKeyFor(SignatureAlgorithm.HS256).getEncoded());
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String encode = bCryptPasswordEncoder.encode("sst123456#");
System.out.println(encode);
}
}

@ -0,0 +1,55 @@
package com.supervision.livedigitalavatarmanage.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
*
* @author qimaoyu
* @create 2019-07-14 10:22
*/
@NoArgsConstructor
@AllArgsConstructor
public enum ResultStatusEnum {
AUTHENTICATION_FAILED(320, "token失效请重新登录"),
NO_ACCESS_TO_THIS_INTERFACE(320, "无权访问此接口!"),
FAILED_TO_GENERATE_TOKEN(321, "生成token失败"),
ACCOUNT_PASSWORD_INCORRECT(322, "账号或密码错误!"),
ACCOUNT_NOT_CREATE(323, "账号未创建!"),
HAS_BEEN_PULLED_BLACK(324, "已被删除或禁用,无法登录!"),
USERNAME_MAIL_IS_EXIST(341, "登录名称已经被注册!"),
USERNAME_IS_BLANK(342, "登录名称为空!"),
VERIFICATION_CODE_EXPIRED(350,"验证码已过期,请重新获取。"),
VERIFICATION_CODE_FAILURE(351,"验证码输入错误。"),
OPERATE_FAIL(360,"修改毕业生信息失败。"),
DATA_IS_EMPTY(370,"查询到的结果为空"),
SYSTEM_ABNORMAL(500, "系统繁忙,请稍后重试!"),
UPLOAD_EXCEPTION(501, "文件上传异常!"),
EXPORT_EXCEPTION(502, "文件导出异常!"),
INCORRECT_FILE_FORMAT(503, "文件格式不正确!"),
PARAMETER_CANNOT_BE_EMPTY(504, "参数不能为空,操作失败!"),
NO_TEMP_UPLOADFILEPATH(505,"未配置文件上传临时存储路径"),
USER_DOES_NOT_EXIST(507, "用户不存在,操作失败!"),
ILLEGAL_ARGUMENT(508, "参数校验失败!"),
RUNTIME_EXCEPTION(509, "程序运行异常!"),
EXCEED_FILE_SIZE(510, "文件大小超出限制!"),
IMPORT_COMPANY_FORMAT_ERROR(521,"Excel表格格式错误"),
IMPORT_COMPANY_FAIL(522,"部分数据导入失败"),
INSERT_FAIL(600,"新增失败"),
DuplicateKeyException(601,"该条信息已经存在,请勿重复添加"),
UPDATE_FAIL(700,"更新失败"),
DELETE_FAIL(800,"删除失败"),
YEAR_IS_CLOSE(1001,"该年度暂未开启");
@Getter
@Setter
private int code;
@Getter
@Setter
private String message;
}

@ -0,0 +1,52 @@
package com.supervision.livedigitalavatarmanage.controller;
import cn.hutool.core.lang.Assert;
import com.supervision.livedigitalavatarmanage.domain.Users;
import com.supervision.livedigitalavatarmanage.dto.R;
import com.supervision.livedigitalavatarmanage.dto.UserDetail;
import com.supervision.livedigitalavatarmanage.service.AuthService;
import com.supervision.livedigitalavatarmanage.util.UserUtil;
import com.supervision.livedigitalavatarmanage.vo.LoginReqVo;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
/**
*
* @param loginReqVo
* @return
*/
@PostMapping("/login")
public R<String> login(@RequestBody LoginReqVo loginReqVo) {
Assert.notEmpty(loginReqVo.getUsername(), "用户名不能为空");
String token = authService.login(loginReqVo.getUsername(), loginReqVo.getPassword());
return R.ok(token);
}
/**
*
* @return
*/
@GetMapping("/heartbeat")
public R<Void> heartbeat() {
UserDetail userDetail = UserUtil.currentUser();
authService.heartbeat(userDetail.getUserId());
return R.ok();
}
@GetMapping("/me")
public R<Users> getCurrentUserDetails() {
Users currentUser = authService.getCurrentUser(UserUtil.currentUser().getUserId());
return R.ok(currentUser);
}
}

@ -0,0 +1,33 @@
package com.supervision.livedigitalavatarmanage.controller;
import com.supervision.livedigitalavatarmanage.service.OllamaAgentService;
import com.supervision.livedigitalavatarmanage.vo.ollama.OllamResponse;
import com.supervision.livedigitalavatarmanage.vo.ollama.OllamaDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
@Slf4j
@RestController
@RequestMapping("/ollama")
@RequiredArgsConstructor
public class OllamaAgentController {
private final OllamaAgentService agentService;
/**
*
* @param ollamaDTO agentChatReqDTO
* @return
*/
@PostMapping(value = "/generate", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<OllamResponse> streamChat(@RequestBody OllamaDTO ollamaDTO) {
//UserDetail userDetail = UserUtil.currentUser();
return agentService.streamChat(ollamaDTO);
}
}

@ -0,0 +1,67 @@
package com.supervision.livedigitalavatarmanage.domain;
import com.baomidou.mybatisplus.annotation.*;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Date;
import lombok.Data;
/**
* ollama
* @TableName ollama_dialogue_log
*/
@TableName(value ="ollama_dialogue_log")
@Data
public class OllamaDialogueLog implements Serializable {
/**
*
*/
@TableId
private String id;
/**
* id
*/
private String userId;
/**
*
*/
private String userInput;
/**
*
*/
private String systemOut;
/**
*
*/
private Long timeCost;
/**
* 0 1
*/
private Integer answerType;
/**
*
*/
private String model;
/**
*
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
*
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}

@ -0,0 +1,47 @@
package com.supervision.livedigitalavatarmanage.domain;
import com.baomidou.mybatisplus.annotation.*;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Date;
import lombok.Data;
/**
*
* @TableName permissions
*/
@TableName(value ="permissions")
@Data
public class Permissions implements Serializable {
/**
*
*/
@TableId
private String id;
/**
* 访url
*/
private String url;
/**
*
*/
private String description;
/**
*
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
*
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}

@ -0,0 +1,41 @@
package com.supervision.livedigitalavatarmanage.domain;
import com.baomidou.mybatisplus.annotation.*;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Date;
import lombok.Data;
/**
*
* @TableName role_permissions
*/
@TableName(value ="role_permissions")
@Data
public class RolePermissions implements Serializable {
/**
* id
*/
private String roleId;
/**
* id
*/
private String permissionId;
/**
*
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
*
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}

@ -0,0 +1,52 @@
package com.supervision.livedigitalavatarmanage.domain;
import com.baomidou.mybatisplus.annotation.*;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Date;
import lombok.Data;
/**
*
* @TableName roles
*/
@TableName(value ="roles")
@Data
public class Roles implements Serializable {
/**
*
*/
@TableId
private String id;
/**
* code
*/
private String code;
/**
*
*/
private String name;
/**
* 0 1
*/
private String isSystemRole;
/**
*
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
*
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}

@ -0,0 +1,42 @@
package com.supervision.livedigitalavatarmanage.domain;
import com.baomidou.mybatisplus.annotation.*;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Date;
import lombok.Data;
/**
*
* @TableName user_roles
*/
@TableName(value ="user_roles")
@Data
public class UserRoles implements Serializable {
/**
* id
*/
@TableId
private String userId;
/**
* id
*/
private String roleId;
/**
*
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
*
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}

@ -0,0 +1,72 @@
package com.supervision.livedigitalavatarmanage.domain;
import com.baomidou.mybatisplus.annotation.*;
import java.io.Serializable;
import java.time.LocalDateTime;
import lombok.Data;
/**
*
* @TableName users
*/
@TableName(value ="users")
@Data
public class Users implements Serializable {
/**
*
*/
@TableId
private String id;
/**
*
*/
private String username;
/**
*
*/
private String password;
/**
* 0 1
*/
private String disabled;
/**
*
*/
private LocalDateTime lastActive;
/**
*
*/
private Integer failedLoginAttempts;
/**
*
*/
private LocalDateTime lastLoginDate;
/**
*
*/
private LocalDateTime expirationDate;
/**
*
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
*
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}

@ -0,0 +1,86 @@
package com.supervision.livedigitalavatarmanage.dto;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import com.supervision.livedigitalavatarmanage.domain.OllamaDialogueLog;
import com.supervision.livedigitalavatarmanage.vo.ollama.MessageDTO;
import com.supervision.livedigitalavatarmanage.vo.ollama.OllamaDTO;
import lombok.Data;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
@Data
public class OllamaDialogueLogDTO {
/**
* id
*/
private String id;
/**
* id
*/
private String userId;
/**
* id
*/
private String conversationId;
/**
*
*/
private String userInput;
/**
*
*/
private String systemOut;
/**
*
*/
private Long timeCost;
/**
* 0 1
*/
private Integer answerType = 0;
/**
* id
*/
private String model;
/**
*
*/
private LocalDateTime createTime;
public OllamaDialogueLogDTO() {
}
public OllamaDialogueLogDTO(OllamaDTO ollamaDTO) {
this.createTime = LocalDateTime.now();
this.userInput = JSONUtil.toJsonStr(ollamaDTO.getMessages());
this.model = ollamaDTO.getModel();
}
public OllamaDialogueLog toDialogueLog(){
OllamaDialogueLog dialogueLog = new OllamaDialogueLog();
dialogueLog.setId(this.id);
dialogueLog.setUserId(this.userId);
dialogueLog.setAnswerType(this.answerType);
dialogueLog.setUserInput(this.userInput);
dialogueLog.setSystemOut(this.systemOut);
if (null != this.createTime){
dialogueLog.setTimeCost(Duration.between(this.createTime, LocalDateTime.now()).toMillis());
}
dialogueLog.setModel(this.model);
return dialogueLog;
}
}

@ -0,0 +1,131 @@
package com.supervision.livedigitalavatarmanage.dto;
import lombok.Data;
import java.io.Serializable;
import java.util.*;
/**
*
*
* @author qimaoyu
*/
@Data
public class R<T> implements Serializable {
private static final long serialVersionUID = 1L;
public static final String TOTAL_COUNT = "total";
public static final String RESULT_LIST = "result";
/**
*
*/
public static final int SUCCESS = 200;
/**
*
*/
public static final int FAIL = 500;
private int code;
private String msg;
private T data;
public static <T> R<T> ok() {
return restResult(null, SUCCESS, null);
}
public static <T> R<T> judgeResult(Boolean bo, String successMessage, String failMessage) {
if (bo) {
return restResult(null, SUCCESS, successMessage);
} else {
return restResult(null, FAIL, failMessage);
}
}
public static <T> R<T> okMsg(String msg) {
return restResult(null, SUCCESS, msg);
}
public static <T> R<T> ok(T data) {
return restResult(data, SUCCESS, null);
}
public static <T> R<T> ok(T data, String msg) {
return restResult(data, SUCCESS, msg);
}
public static <T> R<T> fail() {
return restResult(null, FAIL, null);
}
public static <T> R<T> fail(String msg) {
return restResult(null, FAIL, msg);
}
public static <T> R<T> fail(T data) {
return restResult(data, FAIL, null);
}
public static <T> R<T> fail(int code, String msg) {
return restResult(null, code, msg);
}
private static <T> R<T> restResult(T data, int code, String msg) {
R<T> apiResult = new R<>();
apiResult.setCode(code);
apiResult.setData(data);
apiResult.setMsg(msg);
return apiResult;
}
public static Map<String, Object> buildDataMap(List list) {
Map<String, Object> dataMap = new HashMap<>();
if (list == null) {
dataMap.put(TOTAL_COUNT, 0);
dataMap.put(RESULT_LIST, new ArrayList<>());
} else {
dataMap.put(TOTAL_COUNT, list.size());
dataMap.put(RESULT_LIST, list);
}
return dataMap;
}
public static Map<String, Object> buildDataMap(List list, Long total) {
Map<String, Object> dataMap = new HashMap<>();
dataMap.put(TOTAL_COUNT, total);
dataMap.put(RESULT_LIST, list);
return dataMap;
}
public static Map<String, Object> buildDataMap(Set list) {
Map<String, Object> dataMap = new HashMap<>();
if (list == null) {
dataMap.put(TOTAL_COUNT, 0);
dataMap.put(RESULT_LIST, new ArrayList<>());
} else {
dataMap.put(TOTAL_COUNT, list.size());
dataMap.put(RESULT_LIST, list);
}
return dataMap;
}
public static Map<String, Object> buildDataMap(Object object) {
if (object == null) {
return null;
}
List<Object> resultList = new ArrayList<>();
resultList.add(object);
Map<String, Object> dataMap = new HashMap<>();
dataMap.put(TOTAL_COUNT, resultList.size());
dataMap.put(RESULT_LIST, resultList);
return dataMap;
}
}

@ -0,0 +1,61 @@
package com.supervision.livedigitalavatarmanage.dto;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.time.LocalDateTime;
import java.util.Collection;
public class UserDetail extends User {
private String userId;
/**
*
*/
private LocalDateTime lastLoginDate;
/**
*
*/
private LocalDateTime expirationDate;
public UserDetail(String userId , String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
this.userId = userId;
}
public UserDetail(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
public UserDetail(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public LocalDateTime getExpirationDate() {
return expirationDate;
}
public void setExpirationDate(LocalDateTime expirationDate) {
this.expirationDate = expirationDate;
}
public LocalDateTime getLastLoginDate() {
return lastLoginDate;
}
public void setLastLoginDate(LocalDateTime lastLoginDate) {
this.lastLoginDate = lastLoginDate;
}
}

@ -0,0 +1,17 @@
package com.supervision.livedigitalavatarmanage.enums;
import lombok.Getter;
@Getter
public enum UserDisabledEnum {
ENABLED("0", "启用"),
DISABLED("1", "禁用");
private final String code;
private final String description;
UserDisabledEnum(String code, String description) {
this.code = code;
this.description = description;
}
}

@ -0,0 +1,76 @@
/*
* : CustomException
* :
* : <>
* : RedName
* : 2022/8/5
* : <>
* : <>
* : <>
*/
package com.supervision.livedigitalavatarmanage.exception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
/**
* <>
*
*
* @author ljt
* @version [, 2022/8/5]
* @see [/]
* @since [/]
*/
@Slf4j
public class BusinessException extends RuntimeException {
/**
*
*/
private final Integer code;
/**
*
*/
private final String message;
public BusinessException(Throwable cause) {
super(cause);
this.code = HttpStatus.INTERNAL_SERVER_ERROR.value();
this.message = null;
}
public BusinessException(Throwable cause, String message) {
super(cause);
this.code = HttpStatus.INTERNAL_SERVER_ERROR.value();
this.message = message;
}
public BusinessException(String message) {
this.code = HttpStatus.INTERNAL_SERVER_ERROR.value();
this.message = message;
}
public BusinessException(String message, Integer code) {
this.message = message;
this.code = code;
}
public BusinessException(String message, Throwable e) {
super(message, e);
log.error(message, e);
this.code = HttpStatus.INTERNAL_SERVER_ERROR.value();
this.message = message;
}
@Override
public String getMessage() {
return message;
}
public Integer getCode() {
return code;
}
}

@ -0,0 +1,15 @@
package com.supervision.livedigitalavatarmanage.exception;
public class UnauthorizedException extends RuntimeException {
public UnauthorizedException() {
super("用户未登录");
}
public UnauthorizedException(String message) {
super(message);
}
public UnauthorizedException(String message, Throwable cause) {
super(message, cause);
}
}

@ -0,0 +1,62 @@
package com.supervision.livedigitalavatarmanage.filter;
import com.supervision.livedigitalavatarmanage.dto.UserDetail;
import com.supervision.livedigitalavatarmanage.util.JwtUtils;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import java.io.IOException;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtils jwtUtils;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServletResponse response,
@NonNull FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
// 提取JWT Token去掉前缀"Bearer "
String token = authHeader.substring(7);
String username;
try {
// 从JWT中解析用户名
username = jwtUtils.getUsernameFromToken(token);
} catch (Exception e) {
// 如果JWT格式不正确或过期直接放行后续的过滤器会处理认证失败
filterChain.doFilter(request, response);
return;
}
// 如果成功提取到用户名,并且当前没有已认证的用户
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
// 根据用户名从数据库加载用户信息
UserDetail userDetails = (UserDetail) userDetailsService.loadUserByUsername(username);
// 验证登录时间是否一致,如果不一致说明登录已过期
// 验证Token的有效性是否未过期
if (jwtUtils.validateLoginTime(token, userDetails.getLastLoginDate())
&& !jwtUtils.isTokenExpired(token)) {
// 将用户信息封装到Authentication对象中标记为已认证
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// 将Authentication对象放入SecurityContext表示当前请求已通过认证
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
}
filterChain.doFilter(request, response);
}
}

@ -0,0 +1,18 @@
package com.supervision.livedigitalavatarmanage.mapper;
import com.supervision.livedigitalavatarmanage.domain.OllamaDialogueLog;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author Administrator
* @description ollama_dialogue_log(ollama)Mapper
* @createDate 2025-07-28 15:10:10
* @Entity com.supervision.livedigitalavatarmanage.domain.OllamaDialogueLog
*/
public interface OllamaDialogueLogMapper extends BaseMapper<OllamaDialogueLog> {
}

@ -0,0 +1,18 @@
package com.supervision.livedigitalavatarmanage.mapper;
import com.supervision.livedigitalavatarmanage.domain.Permissions;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author Administrator
* @description permissions()Mapper
* @createDate 2025-07-28 14:11:27
* @Entity com.supervision.ivedigitalavatarmanage.domain.Permissions
*/
public interface PermissionsMapper extends BaseMapper<Permissions> {
}

@ -0,0 +1,18 @@
package com.supervision.livedigitalavatarmanage.mapper;
import com.supervision.livedigitalavatarmanage.domain.RolePermissions;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author Administrator
* @description role_permissionsMapper
* @createDate 2025-07-28 14:11:27
* @Entity com.supervision.ivedigitalavatarmanage.domain.RolePermissions
*/
public interface RolePermissionsMapper extends BaseMapper<RolePermissions> {
}

@ -0,0 +1,18 @@
package com.supervision.livedigitalavatarmanage.mapper;
import com.supervision.livedigitalavatarmanage.domain.Roles;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author Administrator
* @description rolesMapper
* @createDate 2025-07-28 14:11:27
* @Entity com.supervision.ivedigitalavatarmanage.domain.Roles
*/
public interface RolesMapper extends BaseMapper<Roles> {
}

@ -0,0 +1,18 @@
package com.supervision.livedigitalavatarmanage.mapper;
import com.supervision.livedigitalavatarmanage.domain.UserRoles;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author Administrator
* @description user_rolesMapper
* @createDate 2025-07-28 14:11:27
* @Entity com.supervision.ivedigitalavatarmanage.domain.UserRoles
*/
public interface UserRolesMapper extends BaseMapper<UserRoles> {
}

@ -0,0 +1,18 @@
package com.supervision.livedigitalavatarmanage.mapper;
import com.supervision.livedigitalavatarmanage.domain.Users;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author Administrator
* @description usersMapper
* @createDate 2025-07-28 14:11:27
* @Entity com.supervision.ivedigitalavatarmanage.domain.Users
*/
public interface UsersMapper extends BaseMapper<Users> {
}

@ -0,0 +1,14 @@
package com.supervision.livedigitalavatarmanage.service;
import com.supervision.livedigitalavatarmanage.domain.Users;
public interface AuthService {
String login(String username, String password);
void heartbeat(String userId);
Users getCurrentUser(String userId);
}

@ -0,0 +1,16 @@
package com.supervision.livedigitalavatarmanage.service;
import com.supervision.livedigitalavatarmanage.vo.ollama.OllamResponse;
import com.supervision.livedigitalavatarmanage.vo.ollama.OllamaDTO;
import reactor.core.publisher.Flux;
public interface OllamaAgentService {
/**
*
* @param agentChatReqDTO agentChatReqDTO
* @return
*/
Flux<OllamResponse> streamChat(OllamaDTO ollamaDTO);
}

@ -0,0 +1,13 @@
package com.supervision.livedigitalavatarmanage.service;
import com.supervision.livedigitalavatarmanage.domain.OllamaDialogueLog;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author Administrator
* @description ollama_dialogue_log(ollama)Service
* @createDate 2025-07-28 15:10:10
*/
public interface OllamaDialogueLogService extends IService<OllamaDialogueLog> {
}

@ -0,0 +1,13 @@
package com.supervision.livedigitalavatarmanage.service;
import com.supervision.livedigitalavatarmanage.domain.Permissions;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author Administrator
* @description permissions()Service
* @createDate 2025-07-28 14:11:27
*/
public interface PermissionsService extends IService<Permissions> {
}

@ -0,0 +1,13 @@
package com.supervision.livedigitalavatarmanage.service;
import com.supervision.livedigitalavatarmanage.domain.RolePermissions;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author Administrator
* @description role_permissionsService
* @createDate 2025-07-28 14:11:27
*/
public interface RolePermissionsService extends IService<RolePermissions> {
}

@ -0,0 +1,13 @@
package com.supervision.livedigitalavatarmanage.service;
import com.supervision.livedigitalavatarmanage.domain.Roles;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author Administrator
* @description rolesService
* @createDate 2025-07-28 14:11:27
*/
public interface RolesService extends IService<Roles> {
}

@ -0,0 +1,59 @@
package com.supervision.livedigitalavatarmanage.service;
import org.springframework.ai.chat.model.ChatResponse;
public class ThinkTagState {
private boolean inThink = false;
private final StringBuilder buffer = new StringBuilder();
private ChatResponse chatResponse;
private boolean filerThink = false;
public ThinkTagState(boolean filerThink) {
this.filerThink = filerThink;
}
public ThinkTagState process(String chunk) {
StringBuilder output = new StringBuilder();
String remaining = chunk;
if (!filerThink) {
buffer.append(remaining);
return this;
}
while (!remaining.isEmpty()) {
if (!inThink) {
int startIdx = remaining.indexOf("<think>");
if (startIdx == -1) {
output.append(remaining);
break;
}
output.append(remaining.substring(0, startIdx));
inThink = true;
remaining = remaining.substring(startIdx + "<think>".length());
} else {
int endIdx = remaining.indexOf("</think>");
if (endIdx == -1) {
break; // 等待后续分块
}
inThink = false;
remaining = remaining.substring(endIdx + "</think>".length());
}
}
buffer.append(output);
return this;
}
public String getFilteredText() {
return buffer.toString();
}
public ChatResponse getChatResponse() {
return chatResponse;
}
public void setChatResponse(ChatResponse chatResponse) {
this.chatResponse = chatResponse;
}
}

@ -0,0 +1,13 @@
package com.supervision.livedigitalavatarmanage.service;
import com.supervision.livedigitalavatarmanage.domain.UserRoles;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author Administrator
* @description user_rolesService
* @createDate 2025-07-28 14:11:27
*/
public interface UserRolesService extends IService<UserRoles> {
}

@ -0,0 +1,22 @@
package com.supervision.livedigitalavatarmanage.service;
import com.supervision.livedigitalavatarmanage.domain.Users;
import com.baomidou.mybatisplus.extension.service.IService;
import java.time.LocalDateTime;
/**
* @author Administrator
* @description usersService
* @createDate 2025-07-28 14:11:27
*/
public interface UsersService extends IService<Users> {
Users getByUsername(String username);
void updateByUserId(String userId);
LocalDateTime updateUserLoginTime(String userId);
}

@ -0,0 +1,77 @@
package com.supervision.livedigitalavatarmanage.service.impl;
import com.supervision.livedigitalavatarmanage.domain.Users;
import com.supervision.livedigitalavatarmanage.dto.UserDetail;
import com.supervision.livedigitalavatarmanage.enums.UserDisabledEnum;
import com.supervision.livedigitalavatarmanage.exception.BusinessException;
import com.supervision.livedigitalavatarmanage.service.AuthService;
import com.supervision.livedigitalavatarmanage.service.UsersService;
import com.supervision.livedigitalavatarmanage.util.JwtUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@Service
@RequiredArgsConstructor
public class AuthServiceImpl implements AuthService {
private final AuthenticationManager authenticationManager;
private final JwtUtils jwtUtils;
private final UsersService userService;
// 掉线时间,单位:秒
private long offlineTime = 90; // 默认90秒
/**
* JWT Token
*/
@Override
public String login(String username, String password) {
Users user = userService.getByUsername(username);
if (null == user) {
throw new RuntimeException("用户名或密码有误!");
}
if (UserDisabledEnum.DISABLED.getCode().equals(user.getDisabled())) {
throw new RuntimeException("用户已被禁用");
}
LocalDateTime lastActive = user.getLastActive();
if (null != lastActive && lastActive.plusSeconds(offlineTime).isAfter(LocalDateTime.now())){
throw new BusinessException("一个账号同时只能登录一个设备,请先下线其他设备");
}
try {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(username, password)
);
// 更新用户登录时间
LocalDateTime localDateTime = userService.updateUserLoginTime(user.getId());
UserDetail userDetails = (UserDetail) authentication.getPrincipal();
userDetails.setLastLoginDate(localDateTime);
return jwtUtils.generateToken(userDetails);
} catch (BadCredentialsException e) {
throw new RuntimeException("用户名或密码错误");
} catch (DisabledException e) {
throw new RuntimeException("用户已被禁用");
}
}
@Override
public void heartbeat(String userId) {
userService.updateByUserId(userId);
}
@Override
public Users getCurrentUser(String userId) {
Users users = userService.getById(userId);
users.setPassword(null);
return users;
}
}

@ -0,0 +1,84 @@
package com.supervision.livedigitalavatarmanage.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.supervision.livedigitalavatarmanage.dto.OllamaDialogueLogDTO;
import com.supervision.livedigitalavatarmanage.dto.UserDetail;
import com.supervision.livedigitalavatarmanage.service.OllamaAgentService;
import com.supervision.livedigitalavatarmanage.service.OllamaDialogueLogService;
import com.supervision.livedigitalavatarmanage.service.ThinkTagState;
import com.supervision.livedigitalavatarmanage.util.UserUtil;
import com.supervision.livedigitalavatarmanage.vo.ollama.OllamResponse;
import com.supervision.livedigitalavatarmanage.vo.ollama.OllamaDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
public class OllamaAgentServiceImpl implements OllamaAgentService {
private final OllamaChatModel ollamaChatModel;
private final OllamaDialogueLogService agentDialogueLogService;
@Override
public Flux<OllamResponse> streamChat(OllamaDTO ollamaDTO) {
UserDetail userDetail = UserUtil.currentUser();
OllamaDialogueLogDTO dialogueLogDTO = new OllamaDialogueLogDTO(ollamaDTO);
dialogueLogDTO.setUserId(userDetail.getUserId());
StringBuilder aiResponseBuilder = new StringBuilder();
Flux<OllamResponse> responseFlux = ollamaChatModel.stream(ollamaDTO.toPrompt())
.scan(new ThinkTagState(ollamaDTO.isFilterThink()), (state, response) -> {
String chunk = response.getResult().getOutput().getText();
state.setChatResponse(response);
return state.process(chunk);
})
.filter(state -> StrUtil.isNotBlank(state.getFilteredText()))
.map(response -> {
OllamResponse responseDTO = new OllamResponse(response.getChatResponse());
aiResponseBuilder.append(response.getChatResponse().getResult().getOutput().getText());
return responseDTO;
}).doOnComplete(() -> {
dialogueLogDTO.setSystemOut(aiResponseBuilder.toString());
agentDialogueLogService.save(dialogueLogDTO.toDialogueLog());
}).doOnError(e -> {
log.error("Error during AI chat stream: {}", e.getMessage());
dialogueLogDTO.setSystemOut(aiResponseBuilder.toString());
dialogueLogDTO.setAnswerType(1);
agentDialogueLogService.save(dialogueLogDTO.toDialogueLog());
});
if (ollamaDTO.isStream()){
return responseFlux;
}
return mergeResponses(responseFlux);
}
/**
*
* @param responseFlux
* @return
*/
private Flux<OllamResponse> mergeResponses(Flux<OllamResponse> responseFlux) {
return responseFlux
.collectList()
.flatMapMany(list -> {
if (list.isEmpty()) {
return Flux.empty();
}
OllamResponse last = CollUtil.getLast(list);
String combinedText = list.stream()
.map(r -> r.getMessage().getContent())
.collect(Collectors.joining());
last.getMessage().setContent(combinedText);
return Flux.just(last);
});
}
}

@ -0,0 +1,22 @@
package com.supervision.livedigitalavatarmanage.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.supervision.livedigitalavatarmanage.domain.OllamaDialogueLog;
import com.supervision.livedigitalavatarmanage.service.OllamaDialogueLogService;
import com.supervision.livedigitalavatarmanage.mapper.OllamaDialogueLogMapper;
import org.springframework.stereotype.Service;
/**
* @author Administrator
* @description ollama_dialogue_log(ollama)Service
* @createDate 2025-07-28 15:10:10
*/
@Service
public class OllamaDialogueLogServiceImpl extends ServiceImpl<OllamaDialogueLogMapper, OllamaDialogueLog>
implements OllamaDialogueLogService{
}

@ -0,0 +1,22 @@
package com.supervision.livedigitalavatarmanage.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.supervision.livedigitalavatarmanage.domain.Permissions;
import com.supervision.livedigitalavatarmanage.service.PermissionsService;
import com.supervision.livedigitalavatarmanage.mapper.PermissionsMapper;
import org.springframework.stereotype.Service;
/**
* @author Administrator
* @description permissions()Service
* @createDate 2025-07-28 14:11:27
*/
@Service
public class PermissionsServiceImpl extends ServiceImpl<PermissionsMapper, Permissions>
implements PermissionsService{
}

@ -0,0 +1,22 @@
package com.supervision.livedigitalavatarmanage.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.supervision.livedigitalavatarmanage.domain.RolePermissions;
import com.supervision.livedigitalavatarmanage.service.RolePermissionsService;
import com.supervision.livedigitalavatarmanage.mapper.RolePermissionsMapper;
import org.springframework.stereotype.Service;
/**
* @author Administrator
* @description role_permissionsService
* @createDate 2025-07-28 14:11:27
*/
@Service
public class RolePermissionsServiceImpl extends ServiceImpl<RolePermissionsMapper, RolePermissions>
implements RolePermissionsService{
}

@ -0,0 +1,22 @@
package com.supervision.livedigitalavatarmanage.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.supervision.livedigitalavatarmanage.domain.Roles;
import com.supervision.livedigitalavatarmanage.service.RolesService;
import com.supervision.livedigitalavatarmanage.mapper.RolesMapper;
import org.springframework.stereotype.Service;
/**
* @author Administrator
* @description rolesService
* @createDate 2025-07-28 14:11:27
*/
@Service
public class RolesServiceImpl extends ServiceImpl<RolesMapper, Roles>
implements RolesService{
}

@ -0,0 +1,46 @@
package com.supervision.livedigitalavatarmanage.service.impl;
import cn.hutool.core.util.StrUtil;
import com.supervision.livedigitalavatarmanage.domain.Users;
import com.supervision.livedigitalavatarmanage.dto.UserDetail;
import com.supervision.livedigitalavatarmanage.enums.UserDisabledEnum;
import com.supervision.livedigitalavatarmanage.service.UsersService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final UsersService usersService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 将查询到的用户信息组装成UserDetails对象
// **扩展点**:如需加载用户角色权限,可在此处查询 sys_user_role 表关联的角色,并将角色加入 authorities 列表
List<GrantedAuthority> authorities = Collections.emptyList();
// 使用Spring Security提供的User对象作为UserDetails返回
Users sysUser = usersService.getByUsername(username);
if (sysUser == null) {
throw new UsernameNotFoundException("用户不存在: " + username);
}
if (StrUtil.equals(UserDisabledEnum.DISABLED.getCode(), sysUser.getDisabled())) {
throw new UsernameNotFoundException("用户已被禁用: " + username);
}
UserDetail userDetail = new UserDetail(sysUser.getId(), sysUser.getUsername(), sysUser.getPassword(), authorities);
userDetail.setExpirationDate(sysUser.getExpirationDate());
userDetail.setLastLoginDate(sysUser.getLastLoginDate());
return userDetail;
}
}

@ -0,0 +1,22 @@
package com.supervision.livedigitalavatarmanage.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.supervision.livedigitalavatarmanage.domain.UserRoles;
import com.supervision.livedigitalavatarmanage.service.UserRolesService;
import com.supervision.livedigitalavatarmanage.mapper.UserRolesMapper;
import org.springframework.stereotype.Service;
/**
* @author Administrator
* @description user_rolesService
* @createDate 2025-07-28 14:11:27
*/
@Service
public class UserRolesServiceImpl extends ServiceImpl<UserRolesMapper, UserRoles>
implements UserRolesService{
}

@ -0,0 +1,43 @@
package com.supervision.livedigitalavatarmanage.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.supervision.livedigitalavatarmanage.domain.Users;
import com.supervision.livedigitalavatarmanage.service.UsersService;
import com.supervision.livedigitalavatarmanage.mapper.UsersMapper;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
/**
* @author Administrator
* @description usersService
* @createDate 2025-07-28 14:11:27
*/
@Service
public class UsersServiceImpl extends ServiceImpl<UsersMapper, Users>
implements UsersService{
@Override
public Users getByUsername(String username) {
return super.lambdaQuery().eq(Users::getUsername, username).one();
}
@Override
public void updateByUserId(String userId) {
this.lambdaUpdate().eq(Users::getId, userId)
.set(Users::getLastActive, LocalDateTime.now()).update();
}
@Override
public LocalDateTime updateUserLoginTime(String userId) {
LocalDateTime now = LocalDateTime.now();
this.lambdaUpdate().eq(Users::getId, userId)
.set(Users::getLastLoginDate, now)
.set(Users::getLastActive, now).update();
return now;
}
}

@ -0,0 +1,87 @@
package com.supervision.livedigitalavatarmanage.util;
import cn.hutool.json.JSONUtil;
import com.supervision.livedigitalavatarmanage.dto.UserDetail;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.time.LocalDateTime;
import java.util.Base64;
import java.util.Date;
import java.util.Map;
@Component
public class JwtUtils {
@Value("${jwt.secret}")
private String Secret;
@Value("${jwt.expiration}")
private long Expiration; // 1天
private SecretKey secretKey;
@PostConstruct
public void initKey() {
this.secretKey = Keys.hmacShaKeyFor(Base64.getDecoder().decode(Secret));
}
// 生成Token
public String generateToken(UserDetails userDetails) {
Map<String, Object> sub = new java.util.HashMap<>(Map.of("username", userDetails.getUsername()));
if (userDetails instanceof UserDetail userDetail) {
sub.put("lastLoginTime", userDetail.getLastLoginDate());
}
return Jwts.builder()
.subject(JSONUtil.toJsonStr(sub))
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + Expiration))
.signWith(secretKey, Jwts.SIG.HS256)
.compact();
}
// 从 Token 中解析用户名
public String getUsernameFromToken(String token) {
Jws<Claims> jws = Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token);
String subject = jws.getPayload().getSubject();
return JSONUtil.parseObj(subject).getStr("username");
}
// 判断是否过期
public boolean isTokenExpired(String token) {
Jws<Claims> jws = Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token);
return jws.getPayload().getExpiration().before(new Date());
}
/**
*
*/
public boolean validateLoginTime(String token, LocalDateTime lastLoginTime) {
Jws<Claims> jws = Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token);
String subject = jws.getPayload().getSubject();
LocalDateTime tokenLoginTime = JSONUtil.parseObj(subject).getLocalDateTime("lastLoginTime", null);
// 相差不超过500毫秒
return tokenLoginTime != null && tokenLoginTime.isAfter(lastLoginTime.minusNanos(500_000_000)) &&
tokenLoginTime.isBefore(lastLoginTime.plusNanos(500_000_000));
}
}

@ -0,0 +1,34 @@
package com.supervision.livedigitalavatarmanage.util;
import com.supervision.livedigitalavatarmanage.dto.UserDetail;
import com.supervision.livedigitalavatarmanage.exception.UnauthorizedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.socket.WebSocketSession;
import java.security.Principal;
public class UserUtil {
public static UserDetail currentUser() {
SecurityContext context = SecurityContextHolder.getContext();
if (null == context) {
throw new UnauthorizedException("未登录或登录已过期,请重新登录");
}
Authentication authentication = context.getAuthentication();
Object principal = authentication.getPrincipal();
if ("anonymousUser".equals(principal)) {
throw new UnauthorizedException("未登录或登录已过期,请重新登录");
}
return (UserDetail) authentication.getPrincipal();
}
public static UserDetail currentUser(WebSocketSession session){
Principal principal = session.getPrincipal();
if (principal instanceof UserDetail userDetail) {
return userDetail;
} else {
throw new UnauthorizedException("未登录或登录已过期,请重新登录");
}
}
}

@ -0,0 +1,11 @@
package com.supervision.livedigitalavatarmanage.vo;
import lombok.Data;
@Data
public class LoginReqVo {
private String username;
private String password;
}

@ -0,0 +1,11 @@
package com.supervision.livedigitalavatarmanage.vo.ollama;
import lombok.Data;
@Data
public class MessageDTO {
private String role;
private String content;
}

@ -0,0 +1,63 @@
package com.supervision.livedigitalavatarmanage.vo.ollama;
import lombok.Data;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.metadata.ChatResponseMetadata;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import java.util.Date;
@Data
public class OllamResponse {
private String model;
private Date created_at;
private MessageDTO message;
private String done_reason;
private Boolean done;
private Long total_duration;
private Long load_duration;
private Long prompt_eval_count;
private Long prompt_eval_duration;
private Long eval_count;
private Long eval_duration;
public OllamResponse() {
}
public OllamResponse(ChatResponse chatResponse) {
ChatResponseMetadata metadata = chatResponse.getMetadata();
this.model = metadata.getModel();
Object createdAt = metadata.get("created-at");
this.created_at = createdAt instanceof Date ? (Date) createdAt : null;
Object evalCount = metadata.get("eval-count");
this.eval_count = evalCount instanceof Number ? ((Number) evalCount).longValue() : null;
Object evalDuration = metadata.get("eval-duration");
this.eval_duration = evalDuration instanceof Number ? ((Number) evalDuration).longValue() : null;
Object totalDuration = metadata.get("total-duration");
this.total_duration = totalDuration instanceof Number ? ((Number) totalDuration).longValue() : null;
Object promptEvalCount = metadata.get("prompt-eval-count");
this.prompt_eval_count = promptEvalCount instanceof Number ? ((Number) promptEvalCount).longValue() : null;
Object promptEvalDuration = metadata.get("prompt-eval-duration");
this.prompt_eval_duration = promptEvalDuration instanceof Number ? ((Number) promptEvalDuration).longValue() : null;
this.done = metadata.get("done");
AssistantMessage output = chatResponse.getResult().getOutput();
this.message = new MessageDTO();
this.message.setRole(output.getMessageType().name().toLowerCase());
this.message.setContent(output.getText());
}
}

@ -0,0 +1,42 @@
package com.supervision.livedigitalavatarmanage.vo.ollama;
import cn.hutool.core.lang.Assert;
import lombok.Data;
import org.springframework.ai.chat.messages.*;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.ollama.api.OllamaOptions;
import java.util.List;
import java.util.stream.Collectors;
@Data
public class OllamaDTO {
private String model;
private boolean stream;
private boolean filterThink;
private List<MessageDTO> messages;
public Prompt toPrompt() {
List<Message> list = messages.stream().map(messageDTO -> {
String role = messageDTO.getRole();
if ("user".equals(role)) {
return (Message)new UserMessage(messageDTO.getContent());
} else if ("assistant".equals(role)) {
return new AssistantMessage(messageDTO.getContent());
} else if ("system".equals(role)) {
return new SystemMessage(messageDTO.getContent());
} else {
throw new IllegalArgumentException("Unknown role: " + role);
}
}).toList();
OllamaOptions.Builder optionsBuilder = OllamaOptions.builder();
optionsBuilder.model(model);
optionsBuilder.model(model);
return new Prompt(list,optionsBuilder.build());
}
}

@ -0,0 +1,38 @@
server:
port: 9909
servlet:
context-path: /live-digital-avatar-manage
spring:
application:
name: live-digital-avatar-manage
datasource:
druid:
url: jdbc:postgresql://192.168.10.137:54321/live-digital-avatar-manage
username: postgres
password: 123456
driver-class-name: org.postgresql.Driver
servlet:
multipart:
max-file-size: 10MB
max-request-size: 100MB
ai:
ollama:
baseUrl: http://192.168.10.70:11434
chat:
model: qwen3:30b-a3b
#model: qwen3:32b
options:
max_tokens: 51200
top_p: 0.9
top_k: 40
temperature: 0.7
timeout: 180000
embedding:
model: dengcao/Qwen3-Embedding-0.6B:F16
mybatis-plus:
mapper-locations: classpath*:mapper/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
jwt:
secret: "DlHaPUePiN6MyvpMpsMq/t6swzMHqtrRFd2YnofKz4k=" # JWT?? ?????????? Base64.getEncoder().encodeToString(Keys.secretKeyFor(SignatureAlgorithm.HS256).getEncoded());
expiration: 2592000000 # ???

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.supervision.livedigitalavatarmanage.mapper.OllamaDialogueLogMapper">
<resultMap id="BaseResultMap" type="com.supervision.livedigitalavatarmanage.domain.OllamaDialogueLog">
<id property="id" column="id" jdbcType="VARCHAR"/>
<result property="userId" column="user_id" jdbcType="VARCHAR"/>
<result property="userInput" column="user_input" jdbcType="VARCHAR"/>
<result property="systemOut" column="system_out" jdbcType="VARCHAR"/>
<result property="timeCost" column="time_cost" jdbcType="BIGINT"/>
<result property="answerType" column="answer_type" jdbcType="INTEGER"/>
<result property="model" column="model" jdbcType="VARCHAR"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
</resultMap>
<sql id="Base_Column_List">
id,user_id,user_input,
system_out,time_cost,answer_type,
model,create_time,update_time
</sql>
</mapper>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.supervision.livedigitalavatarmanage.mapper.PermissionsMapper">
<resultMap id="BaseResultMap" type="com.supervision.livedigitalavatarmanage.domain.Permissions">
<id property="id" column="id" jdbcType="VARCHAR"/>
<result property="url" column="url" jdbcType="VARCHAR"/>
<result property="description" column="description" jdbcType="VARCHAR"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
</resultMap>
<sql id="Base_Column_List">
id,url,description,
create_time,update_time
</sql>
</mapper>

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.supervision.livedigitalavatarmanage.mapper.RolePermissionsMapper">
<resultMap id="BaseResultMap" type="com.supervision.livedigitalavatarmanage.domain.RolePermissions">
<result property="roleId" column="role_id" jdbcType="VARCHAR"/>
<result property="permissionId" column="permission_id " jdbcType="VARCHAR"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
</resultMap>
<sql id="Base_Column_List">
role_id,permission_id ,create_time,
update_time
</sql>
</mapper>

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.supervision.livedigitalavatarmanage.mapper.RolesMapper">
<resultMap id="BaseResultMap" type="com.supervision.livedigitalavatarmanage.domain.Roles">
<id property="id" column="id" jdbcType="VARCHAR"/>
<result property="code" column="code" jdbcType="VARCHAR"/>
<result property="name" column="name" jdbcType="VARCHAR"/>
<result property="isSystemRole" column="is_system_role" jdbcType="VARCHAR"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
</resultMap>
<sql id="Base_Column_List">
id,code,name,
is_system_role,create_time,update_time
</sql>
</mapper>

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.supervision.livedigitalavatarmanage.mapper.UserRolesMapper">
<resultMap id="BaseResultMap" type="com.supervision.livedigitalavatarmanage.domain.UserRoles">
<id property="userId" column="user_id " jdbcType="VARCHAR"/>
<result property="roleId" column="role_id " jdbcType="VARCHAR"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
</resultMap>
<sql id="Base_Column_List">
user_id ,role_id ,create_time,
update_time
</sql>
</mapper>

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.supervision.livedigitalavatarmanage.mapper.UsersMapper">
<resultMap id="BaseResultMap" type="com.supervision.livedigitalavatarmanage.domain.Users">
<id property="id" column="id" jdbcType="VARCHAR"/>
<result property="username" column="username" jdbcType="VARCHAR"/>
<result property="password" column="password" jdbcType="VARCHAR"/>
<result property="disabled" column="disabled" jdbcType="VARCHAR"/>
<result property="lastActive" column="last_active" jdbcType="TIMESTAMP"/>
<result property="failedLoginAttempts" column="failed_login_attempts" jdbcType="INTEGER"/>
<result property="lastLoginDate" column="TIMESTAMP" jdbcType="TIMESTAMP"/>
<result property="expirationDate" column="expiration_date" jdbcType="TIMESTAMP"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
</resultMap>
<sql id="Base_Column_List">
id,username,password,
disabled,last_active,failed_login_attempts,
online_status,expiration_date,create_time,
update_time
</sql>
</mapper>

@ -0,0 +1,13 @@
package com.supervision.livedigitalavatarmanage;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class LiveDigitalAvatarManageApplicationTests {
@Test
void contextLoads() {
}
}
Loading…
Cancel
Save