mirror of
https://gitcode.com/ageerle/ruoyi-ai.git
synced 2026-04-15 21:03:40 +00:00
feat: 重构模块
This commit is contained in:
@@ -2,13 +2,14 @@
|
||||
<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>
|
||||
<artifactId>ruoyi-ai</artifactId>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>ruoyi-modules</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
</description>
|
||||
|
||||
<properties>
|
||||
<hutool.version>5.8.18</hutool.version>
|
||||
<org-json.version>20220924</org-json.version>
|
||||
<jda.version>5.0.0-beta.9</jda.version>
|
||||
<chatgpt-java.version>1.1.2-beta0</chatgpt-java.version>
|
||||
@@ -35,26 +34,12 @@
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${hutool.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
<version>${hutool.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
<version>${hutool.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>${org-json.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.dv8tion</groupId>
|
||||
<artifactId>JDA</artifactId>
|
||||
@@ -66,6 +51,7 @@
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.unfbx</groupId>
|
||||
<artifactId>chatgpt-java</artifactId>
|
||||
@@ -77,16 +63,19 @@
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>eu.maxschuster</groupId>
|
||||
<artifactId>dataurl</artifactId>
|
||||
<version>${dataurl.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
|
||||
<version>${knife4j.verison}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>eu.bitwalker</groupId>
|
||||
<artifactId>UserAgentUtils</artifactId>
|
||||
@@ -98,37 +87,28 @@
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-system</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-knowledge-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-chat-api</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.26</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package org.ruoyi.chat.config;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.logging.HttpLoggingInterceptor;
|
||||
import org.ruoyi.common.chat.openai.OpenAiStreamClient;
|
||||
import org.ruoyi.common.chat.openai.function.KeyRandomStrategy;
|
||||
import org.ruoyi.common.chat.openai.interceptor.OpenAILogger;
|
||||
import org.ruoyi.common.core.service.ConfigService;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Chat配置类
|
||||
*
|
||||
* @date: 2023/5/16
|
||||
*/
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
public class ChatConfig {
|
||||
|
||||
@Getter
|
||||
private OpenAiStreamClient openAiStreamClient;
|
||||
|
||||
private final ConfigService configService;
|
||||
|
||||
@Bean
|
||||
public OpenAiStreamClient openAiStreamClient() {
|
||||
String apiHost = configService.getConfigValue("chat", "apiHost");
|
||||
String apiKey = configService.getConfigValue("chat", "apiKey");
|
||||
openAiStreamClient = createOpenAiStreamClient(apiHost,apiKey);
|
||||
return openAiStreamClient;
|
||||
}
|
||||
|
||||
public OpenAiStreamClient createOpenAiStreamClient(String apiHost, String apiKey) {
|
||||
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger());
|
||||
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
|
||||
OkHttpClient okHttpClient = new OkHttpClient.Builder()
|
||||
.addInterceptor(httpLoggingInterceptor)
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.writeTimeout(600, TimeUnit.SECONDS)
|
||||
.readTimeout(600, TimeUnit.SECONDS)
|
||||
.build();
|
||||
return OpenAiStreamClient.builder()
|
||||
.apiHost(apiHost)
|
||||
.apiKey(Collections.singletonList(apiKey))
|
||||
.keyStrategy(new KeyRandomStrategy())
|
||||
.okHttpClient(okHttpClient)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.ruoyi.chat.config;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.ruoyi.common.core.utils.OkHttpUtil;
|
||||
|
||||
import org.ruoyi.domain.vo.ChatModelVo;
|
||||
import org.ruoyi.service.IChatModelService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class OkHttpConfig {
|
||||
|
||||
private final IChatModelService chatModelService;
|
||||
private final Map<String, OkHttpUtil> okHttpUtilMap = new HashMap<>();
|
||||
@Getter
|
||||
private String generate;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
initializeOkHttpUtil("suno");
|
||||
initializeOkHttpUtil("luma");
|
||||
initializeOkHttpUtil("ppt");
|
||||
}
|
||||
|
||||
private void initializeOkHttpUtil(String modelName) {
|
||||
ChatModelVo chatModelVo = chatModelService.selectModelByName(modelName);
|
||||
OkHttpUtil okHttpUtil = new OkHttpUtil();
|
||||
okHttpUtil.setApiHost(chatModelVo.getApiHost());
|
||||
okHttpUtil.setApiKey(chatModelVo.getApiKey());
|
||||
generate = String.valueOf(chatModelVo.getModelPrice());
|
||||
okHttpUtilMap.put(modelName, okHttpUtil);
|
||||
}
|
||||
|
||||
public OkHttpUtil getOkHttpUtil(String modelName) {
|
||||
return okHttpUtilMap.get(modelName);
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,9 @@ import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.chat.service.chat.ISseService;
|
||||
import org.ruoyi.common.chat.domain.request.ChatRequest;
|
||||
|
||||
import org.ruoyi.common.chat.request.ChatRequest;
|
||||
import org.ruoyi.common.chat.entity.Tts.TextToSpeech;
|
||||
import org.ruoyi.common.chat.entity.files.UploadFileResponse;
|
||||
|
||||
import org.ruoyi.common.chat.entity.whisper.WhisperResponse;
|
||||
import org.ruoyi.common.core.domain.R;
|
||||
import org.ruoyi.common.core.domain.model.LoginUser;
|
||||
@@ -19,7 +17,6 @@ import org.ruoyi.common.mybatis.core.page.PageQuery;
|
||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||
import org.ruoyi.common.satoken.utils.LoginHelper;
|
||||
import org.ruoyi.domain.bo.ChatMessageBo;
|
||||
|
||||
import org.ruoyi.domain.vo.ChatMessageVo;
|
||||
import org.ruoyi.service.IChatMessageService;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package org.ruoyi.chat.controller;
|
||||
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.ruoyi.common.core.domain.R;
|
||||
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||
import org.ruoyi.common.web.core.BaseController;
|
||||
import org.ruoyi.system.domain.vo.cover.CoverParamVo;
|
||||
import org.ruoyi.system.domain.vo.cover.CoverVo;
|
||||
import org.ruoyi.system.domain.vo.cover.CoverCallbackVo;
|
||||
import org.ruoyi.system.domain.vo.cover.MusicVo;
|
||||
import org.ruoyi.system.service.ICoverService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 绘声美音-翻唱
|
||||
*
|
||||
* @author NSL
|
||||
* @since 2024-12-25
|
||||
*/
|
||||
@Api(tags = "歌曲翻唱")
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/cover")
|
||||
public class CoverController extends BaseController {
|
||||
|
||||
private final ICoverService coverService;
|
||||
|
||||
@ApiOperation(value = "查找歌曲")
|
||||
@GetMapping("/searchMusic")
|
||||
public R<List<MusicVo>> searchMusic(String musicName) {
|
||||
return R.ok(coverService.searchMusic(musicName));
|
||||
}
|
||||
|
||||
@ApiOperation(value = "翻唱歌曲")
|
||||
@PostMapping("/saveCoverTask")
|
||||
public R<Void> saveCoverTask(@RequestBody CoverParamVo coverParamVo) {
|
||||
coverService.saveCoverTask(coverParamVo);
|
||||
return R.ok("翻唱歌曲处理中请等待10分钟-30分钟,翻唱结果请到翻唱记录中查询!");
|
||||
}
|
||||
|
||||
@ApiOperation(value = "查询翻唱记录")
|
||||
@PostMapping("/searchCoverRecord")
|
||||
public R<TableDataInfo<CoverVo>> searchCoverRecord(@RequestBody PageQuery pageQuery) {
|
||||
return R.ok(coverService.searchCoverRecord(pageQuery));
|
||||
}
|
||||
|
||||
@ApiOperation(value = "翻唱回调接口")
|
||||
@PostMapping("/callback")
|
||||
public R<Void> callback(@RequestBody CoverCallbackVo coverCallbackVo) {
|
||||
coverService.callback(coverCallbackVo);
|
||||
return R.ok();
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.Request;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.ruoyi.chat.domain.InsightFace;
|
||||
import org.ruoyi.chat.service.chat.IChatCostService;
|
||||
import org.ruoyi.chat.util.MjOkHttpUtil;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
@@ -4,11 +4,13 @@ import cn.hutool.json.JSONUtil;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import okhttp3.Request;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.ruoyi.chat.config.OkHttpConfig;
|
||||
import org.ruoyi.chat.domain.bo.GenerateLuma;
|
||||
import org.ruoyi.chat.service.chat.IChatCostService;
|
||||
import org.ruoyi.common.core.utils.OkHttpUtil;
|
||||
import org.ruoyi.system.cofing.OkHttpConfig;
|
||||
import org.ruoyi.system.domain.GenerateLuma;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
package org.ruoyi.chat.controller;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.ruoyi.common.core.domain.R;
|
||||
import org.ruoyi.common.web.core.BaseController;
|
||||
import org.ruoyi.system.domain.vo.ppt.*;
|
||||
import org.ruoyi.system.service.IPptService;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
/**
|
||||
* AI_PPT
|
||||
*
|
||||
* @author NSL
|
||||
* @since 2024-12-30
|
||||
*/
|
||||
@Api(tags = "AI-PPT")
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/ppt")
|
||||
public class PptController extends BaseController {
|
||||
|
||||
private final IPptService pptService;
|
||||
|
||||
@ApiOperation(value = "获取API Token")
|
||||
@GetMapping("/getApiToken")
|
||||
public R<String> getApiToken() {
|
||||
return R.ok(pptService.getApiToken());
|
||||
}
|
||||
|
||||
@ApiOperation(value = "同步流式生成 PPT")
|
||||
@PostMapping("/syncStreamGeneratePpt")
|
||||
public R<Void> syncStreamGeneratePpt(String title) {
|
||||
pptService.syncStreamGeneratePpt(title);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@ApiOperation(value = "查询所有PPT列表")
|
||||
@PostMapping("/selectPptList")
|
||||
public R<Void> selectPptList(@RequestBody PptAllQueryDto pptQueryVo) {
|
||||
pptService.selectPptList(pptQueryVo);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@ApiOperation(value = "生成大纲")
|
||||
@PostMapping(value = "/generateOutline", produces = {MediaType.TEXT_EVENT_STREAM_VALUE})
|
||||
public SseEmitter generateOutline(@RequestBody PptGenerateOutlineDto generateOutlineDto) {
|
||||
return pptService.generateOutline(generateOutlineDto);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "生成大纲内容")
|
||||
@PostMapping(value = "/generateContent", produces = {MediaType.TEXT_EVENT_STREAM_VALUE})
|
||||
public SseEmitter generateOutline(@RequestBody PptGenerateContentDto generateContentDto) {
|
||||
return pptService.generateContent(generateContentDto);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "分页查询 PPT 模板")
|
||||
@PostMapping("/getTemplates")
|
||||
public R<JSONObject> getPptTemplates(@RequestBody PptTemplateQueryDto pptQueryVo) {
|
||||
return R.ok(pptService.getPptTemplates(pptQueryVo));
|
||||
}
|
||||
|
||||
@ApiOperation(value = "生成 PPT")
|
||||
@PostMapping("/generatePptx")
|
||||
public R<JSONObject> generatePptx(@RequestBody PptGeneratePptxDto pptQueryVo) {
|
||||
return R.ok(pptService.generatePptx(pptQueryVo));
|
||||
}
|
||||
|
||||
@ApiOperation(value = "生成PPT成功回调接口")
|
||||
@PostMapping("/successCallback")
|
||||
public R<Void> successCallback() {
|
||||
pptService.successCallback();
|
||||
return R.ok();
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import okhttp3.Request;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.ruoyi.chat.domain.dto.*;
|
||||
import org.ruoyi.chat.enums.ActionType;
|
||||
import org.ruoyi.chat.service.chat.IChatCostService;
|
||||
import org.ruoyi.chat.util.MjOkHttpUtil;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
@@ -4,12 +4,14 @@ import cn.hutool.json.JSONUtil;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import okhttp3.Request;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.ruoyi.chat.config.OkHttpConfig;
|
||||
import org.ruoyi.chat.domain.bo.GenerateLyric;
|
||||
import org.ruoyi.chat.domain.bo.GenerateSuno;
|
||||
import org.ruoyi.chat.service.chat.IChatCostService;
|
||||
import org.ruoyi.common.core.utils.OkHttpUtil;
|
||||
import org.ruoyi.system.cofing.OkHttpConfig;
|
||||
import org.ruoyi.system.domain.GenerateLyric;
|
||||
import org.ruoyi.system.domain.GenerateSuno;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
package org.ruoyi.chat.controller;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.ruoyi.common.core.domain.R;
|
||||
import org.ruoyi.common.web.core.BaseController;
|
||||
import org.ruoyi.system.request.RoleListDto;
|
||||
import org.ruoyi.system.request.SimpleGenerateRequest;
|
||||
import org.ruoyi.system.response.SimpleGenerateDataResponse;
|
||||
import org.ruoyi.system.response.rolelist.ChatAppStoreVO;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 应用市场
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2024-03-19
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/system/voice")
|
||||
public class VoiceController extends BaseController {
|
||||
|
||||
private final IChatAppStoreService voiceRoleService;
|
||||
|
||||
/**
|
||||
* 实时语音生成
|
||||
*/
|
||||
@PostMapping("/simpleGenerate")
|
||||
public R<SimpleGenerateDataResponse> simpleGenerate(@RequestBody SimpleGenerateRequest simpleGenerateRequest) {
|
||||
return R.ok(voiceRoleService.simpleGenerate(simpleGenerateRequest));
|
||||
}
|
||||
|
||||
/**
|
||||
* 角色市场
|
||||
*/
|
||||
@GetMapping("/roleList")
|
||||
public R<List<ChatAppStoreVO>> roleList() {
|
||||
return R.ok(voiceRoleService.roleList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 收藏角色
|
||||
*/
|
||||
@PostMapping("/copyRole")
|
||||
public R<String> copyRole(@RequestBody RoleListDto roleListDto) {
|
||||
voiceRoleService.copyRole(roleListDto);
|
||||
return R.ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.ruoyi.chat.domain.bo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 描述:文生视频请求对象
|
||||
*
|
||||
* @author ageerle@163.com
|
||||
* date 2024/6/27
|
||||
*/
|
||||
@Data
|
||||
public class GenerateLuma {
|
||||
|
||||
private String aspect_ratio;
|
||||
|
||||
private boolean expand_prompt;
|
||||
|
||||
private String image_url;
|
||||
|
||||
private String user_prompt;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.ruoyi.chat.domain.bo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 描述:生成歌词
|
||||
*
|
||||
* @author ageerle@163.com
|
||||
* date 2024/6/27
|
||||
*/
|
||||
@Data
|
||||
public class GenerateLyric {
|
||||
|
||||
/**
|
||||
* 歌词提示词
|
||||
*/
|
||||
private String prompt;
|
||||
|
||||
/**
|
||||
* 回调地址
|
||||
*/
|
||||
private String notify_hook;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.ruoyi.chat.domain.bo;
|
||||
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author WangLe
|
||||
*/
|
||||
@Data
|
||||
public class GenerateSuno implements Serializable {
|
||||
|
||||
/**
|
||||
* 歌词 (自定义模式专用)
|
||||
*/
|
||||
private String prompt;
|
||||
|
||||
/**
|
||||
* mv模型,chirp-v3-0、chirp-v3-5。不写默认 chirp-v3-0
|
||||
*/
|
||||
private String mv;
|
||||
|
||||
/**
|
||||
* 标题(自定义模式专用)
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 风格标签(自定义模式专用)
|
||||
*/
|
||||
private String tags;
|
||||
|
||||
/**
|
||||
* 是否生成纯音乐,true 为生成纯音乐
|
||||
*/
|
||||
private boolean make_instrumental;
|
||||
|
||||
/**
|
||||
* 任务id,用于对之前的任务再操作
|
||||
*/
|
||||
private String task_id;
|
||||
|
||||
/**
|
||||
* float,歌曲延长时间,单位秒
|
||||
*/
|
||||
private int continue_at;
|
||||
|
||||
/**
|
||||
* 歌曲id,需要续写哪首歌
|
||||
*/
|
||||
private String continue_clip_id;
|
||||
|
||||
/**
|
||||
* 灵感模式提示词(灵感模式专用)
|
||||
*/
|
||||
private String gpt_description_prompt;
|
||||
|
||||
/**
|
||||
* 回调地址
|
||||
*/
|
||||
private String notify_hook;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.ruoyi.chat.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public enum BillingType {
|
||||
TOKEN("1", "token扣费"), // token扣费
|
||||
TIMES("2", "次数扣费"); // 次数扣费
|
||||
|
||||
private final String code;
|
||||
private final String description;
|
||||
|
||||
BillingType(String code, String description) {
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public static BillingType fromCode(String code) {
|
||||
for (BillingType type : values()) {
|
||||
if (type.getCode().equals(code)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.ruoyi.chat.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public enum UserGradeType {
|
||||
UNPAID("0", "未付费"), // 未付费用户
|
||||
PAID("1", "已付费"); // 已付费用户
|
||||
|
||||
private final String code;
|
||||
private final String description;
|
||||
|
||||
UserGradeType(String code, String description) {
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public static UserGradeType fromCode(String code) {
|
||||
for (UserGradeType type : values()) {
|
||||
if (type.getCode().equals(code)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,10 @@ import okhttp3.ResponseBody;
|
||||
import okhttp3.sse.EventSource;
|
||||
import okhttp3.sse.EventSourceListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.ruoyi.chat.service.chat.IChatCostService;
|
||||
import org.ruoyi.common.chat.domain.request.ChatRequest;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse;
|
||||
import org.ruoyi.common.core.utils.SpringUtils;
|
||||
import org.ruoyi.common.core.utils.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -40,6 +43,9 @@ public class SSEEventSourceListener extends EventSourceListener {
|
||||
private StringBuilder stringBuffer;
|
||||
|
||||
private String modelName;
|
||||
|
||||
private static final IChatCostService chatCostService = SpringUtils.getBean(IChatCostService.class);
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -55,11 +61,15 @@ public class SSEEventSourceListener extends EventSourceListener {
|
||||
@Override
|
||||
public void onEvent(@NotNull EventSource eventSource, String id, String type, String data) {
|
||||
try {
|
||||
|
||||
if ("[DONE]".equals(data)) {
|
||||
//成功响应
|
||||
emitter.complete();
|
||||
|
||||
// 扣除费用 (消耗字符 模型名称)
|
||||
// 扣除费用
|
||||
ChatRequest chatRequest = new ChatRequest();
|
||||
chatRequest.setModel(modelName);
|
||||
chatRequest.setPrompt(stringBuffer.toString());
|
||||
chatCostService.deductToken(chatRequest);
|
||||
return;
|
||||
}
|
||||
// 解析返回内容
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.ruoyi.chat.service.chat;
|
||||
|
||||
import org.ruoyi.domain.bo.ChatMessageBo;
|
||||
import org.ruoyi.common.chat.request.ChatRequest;
|
||||
|
||||
/**
|
||||
* 计费管理Service接口
|
||||
@@ -11,16 +11,16 @@ import org.ruoyi.domain.bo.ChatMessageBo;
|
||||
public interface IChatCostService {
|
||||
|
||||
/**
|
||||
* 根据消耗的tokens扣除余额
|
||||
* 扣除余额并且保存记录
|
||||
*
|
||||
* @param chatMessageBo
|
||||
* @param chatRequest 对话信息
|
||||
* @return 结果
|
||||
*/
|
||||
|
||||
void deductToken(ChatMessageBo chatMessageBo);
|
||||
void deductToken(ChatRequest chatRequest);
|
||||
|
||||
/**
|
||||
* 扣除用户的余额
|
||||
* 直接扣除用户的余额
|
||||
*
|
||||
*/
|
||||
void deductUserBalance(Long userId, Double numberCost);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.ruoyi.chat.service.chat;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.ruoyi.common.chat.domain.request.ChatRequest;
|
||||
import org.ruoyi.common.chat.request.ChatRequest;
|
||||
import org.ruoyi.common.chat.entity.Tts.TextToSpeech;
|
||||
import org.ruoyi.common.chat.entity.files.UploadFileResponse;
|
||||
import org.ruoyi.common.chat.entity.whisper.WhisperResponse;
|
||||
@@ -62,4 +62,13 @@ public interface ISseService {
|
||||
* @return 回复内容
|
||||
*/
|
||||
String wxCpChat(String prompt);
|
||||
|
||||
|
||||
/**
|
||||
* 联网查询
|
||||
*
|
||||
* @param prompt 提示词
|
||||
* @return 查询内容
|
||||
*/
|
||||
String webSearch (String prompt);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
package org.ruoyi.chat.service.chat.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.chat.enums.BillingType;
|
||||
import org.ruoyi.chat.enums.UserGradeType;
|
||||
import org.ruoyi.chat.service.chat.IChatCostService;
|
||||
import org.ruoyi.common.chat.domain.request.ChatRequest;
|
||||
import org.ruoyi.common.chat.utils.TikTokensUtil;
|
||||
import org.ruoyi.common.core.domain.model.LoginUser;
|
||||
import org.ruoyi.common.core.exception.ServiceException;
|
||||
import org.ruoyi.common.core.exception.base.BaseException;
|
||||
import org.ruoyi.common.satoken.utils.LoginHelper;
|
||||
import org.ruoyi.domain.ChatToken;
|
||||
import org.ruoyi.domain.bo.ChatMessageBo;
|
||||
import org.ruoyi.domain.vo.ChatModelVo;
|
||||
import org.ruoyi.service.IChatMessageService;
|
||||
import org.ruoyi.service.IChatModelService;
|
||||
import org.ruoyi.service.IChatTokenService;
|
||||
import org.ruoyi.system.domain.SysUser;
|
||||
import org.ruoyi.system.mapper.SysUserMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
/**
|
||||
* 计费管理Service实现
|
||||
*
|
||||
* @author ageerle
|
||||
* @date 2025-04-08
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ChatCostServiceImpl implements IChatCostService {
|
||||
|
||||
private final SysUserMapper sysUserMapper;
|
||||
|
||||
private final IChatMessageService chatMessageService;
|
||||
|
||||
private final IChatTokenService chatTokenService;
|
||||
|
||||
private final IChatModelService chatModelService;
|
||||
|
||||
/**
|
||||
* 扣除用户余额
|
||||
*/
|
||||
public void deductToken(ChatRequest chatRequest) {
|
||||
|
||||
int tokens = TikTokensUtil.tokens(chatRequest.getModel(), chatRequest.getPrompt());
|
||||
|
||||
String modelName = chatRequest.getModel();
|
||||
|
||||
ChatMessageBo chatMessageBo = new ChatMessageBo();
|
||||
|
||||
// 计算总token数
|
||||
ChatToken chatToken = chatTokenService.queryByUserId(getUserId(), modelName);
|
||||
if (chatToken == null) {
|
||||
chatToken = new ChatToken();
|
||||
chatToken.setToken(0);
|
||||
}
|
||||
int totalTokens = chatToken.getToken() + tokens;
|
||||
// 如果总token数大于等于1000,进行费用扣除
|
||||
if (totalTokens >= 1000) {
|
||||
// 计算费用
|
||||
int token1 = totalTokens / 1000;
|
||||
int token2 = totalTokens % 1000;
|
||||
if (token2 > 0) {
|
||||
// 保存剩余tokens
|
||||
chatToken.setModelName(modelName);
|
||||
chatToken.setUserId(getUserId());
|
||||
chatToken.setToken(token2);
|
||||
chatTokenService.editToken(chatToken);
|
||||
} else {
|
||||
chatTokenService.resetToken(getUserId(), modelName);
|
||||
}
|
||||
ChatModelVo chatModelVo = chatModelService.selectModelByName(modelName);
|
||||
double cost = chatModelVo.getModelPrice();
|
||||
if (BillingType.TIMES.getCode().equals(chatModelVo.getModelType())) {
|
||||
// 按次数扣费
|
||||
deductUserBalance(getUserId(), cost);
|
||||
chatMessageBo.setDeductCost(cost);
|
||||
}else {
|
||||
// 按token扣费
|
||||
Double numberCost = token1 * cost;
|
||||
deductUserBalance(chatMessageBo.getUserId(), numberCost);
|
||||
chatMessageBo.setDeductCost(numberCost);
|
||||
}
|
||||
chatMessageBo.setContent(chatRequest.getPrompt());
|
||||
} else {
|
||||
deductUserBalance(getUserId(), 0.0);
|
||||
chatMessageBo.setDeductCost(0d);
|
||||
chatMessageBo.setRemark("不满1kToken,计入下一次!");
|
||||
chatToken.setToken(totalTokens);
|
||||
chatToken.setModelName(chatMessageBo.getModelName());
|
||||
chatToken.setUserId(chatMessageBo.getUserId());
|
||||
chatTokenService.editToken(chatToken);
|
||||
}
|
||||
// 保存消息记录
|
||||
chatMessageService.insertByBo(chatMessageBo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从用户余额中扣除费用
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param numberCost 要扣除的费用
|
||||
*/
|
||||
@Override
|
||||
public void deductUserBalance(Long userId, Double numberCost) {
|
||||
SysUser sysUser = sysUserMapper.selectById(userId);
|
||||
if (sysUser == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Double userBalance = sysUser.getUserBalance();
|
||||
if (userBalance < numberCost || userBalance == 0) {
|
||||
throw new ServiceException("余额不足, 请充值");
|
||||
}
|
||||
sysUserMapper.update(null,
|
||||
new LambdaUpdateWrapper<SysUser>()
|
||||
.set(SysUser::getUserBalance, Math.max(userBalance - numberCost, 0))
|
||||
.eq(SysUser::getUserId, userId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 扣除任务费用
|
||||
*/
|
||||
@Override
|
||||
public void taskDeduct(String type,String prompt, double cost) {
|
||||
// 判断用户是否付费
|
||||
checkUserGrade();
|
||||
// 扣除费用
|
||||
deductUserBalance(getUserId(), cost);
|
||||
// 保存消息记录
|
||||
ChatMessageBo chatMessageBo = new ChatMessageBo();
|
||||
chatMessageBo.setUserId(getUserId());
|
||||
chatMessageBo.setModelName(type);
|
||||
chatMessageBo.setContent(prompt);
|
||||
chatMessageBo.setDeductCost(cost);
|
||||
chatMessageBo.setTotalTokens(0);
|
||||
chatMessageService.insertByBo(chatMessageBo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断用户是否付费
|
||||
*/
|
||||
@Override
|
||||
public void checkUserGrade() {
|
||||
SysUser sysUser = sysUserMapper.selectById(getUserId());
|
||||
if(UserGradeType.UNPAID.getCode().equals(sysUser.getUserGrade())){
|
||||
throw new BaseException("该模型仅限付费用户使用。请升级套餐,开启高效体验之旅!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户Id
|
||||
*/
|
||||
public Long getUserId() {
|
||||
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||
if (loginUser == null) {
|
||||
throw new BaseException("用户未登录!");
|
||||
}
|
||||
return loginUser.getUserId();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
package org.ruoyi.chat.service.chat.impl;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.protobuf.ServiceException;
|
||||
import com.zhipu.oapi.ClientV4;
|
||||
import com.zhipu.oapi.service.v4.tools.*;
|
||||
import io.github.ollama4j.OllamaAPI;
|
||||
import io.github.ollama4j.models.chat.OllamaChatMessage;
|
||||
import io.github.ollama4j.models.chat.OllamaChatMessageRole;
|
||||
@@ -13,11 +16,13 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.*;
|
||||
import org.ruoyi.chat.config.ChatConfig;
|
||||
import org.ruoyi.chat.listener.SSEEventSourceListener;
|
||||
|
||||
import org.ruoyi.chat.service.chat.IChatCostService;
|
||||
import org.ruoyi.chat.service.chat.ISseService;
|
||||
import org.ruoyi.common.chat.config.ChatConfig;
|
||||
import org.ruoyi.common.chat.domain.request.ChatRequest;
|
||||
import org.ruoyi.chat.util.IpUtil;
|
||||
import org.ruoyi.common.chat.request.ChatRequest;
|
||||
import org.ruoyi.common.chat.entity.Tts.TextToSpeech;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatCompletion;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse;
|
||||
@@ -26,12 +31,17 @@ import org.ruoyi.common.chat.entity.chat.Message;
|
||||
import org.ruoyi.common.chat.entity.files.UploadFileResponse;
|
||||
import org.ruoyi.common.chat.entity.whisper.WhisperResponse;
|
||||
import org.ruoyi.common.chat.openai.OpenAiStreamClient;
|
||||
import org.ruoyi.common.core.service.ConfigService;
|
||||
import org.ruoyi.common.core.utils.StringUtils;
|
||||
import org.ruoyi.common.core.utils.file.FileUtils;
|
||||
import org.ruoyi.common.core.utils.file.MimeTypeUtils;
|
||||
|
||||
import org.ruoyi.common.redis.utils.RedisUtils;
|
||||
|
||||
import org.ruoyi.domain.vo.ChatModelVo;
|
||||
import org.ruoyi.service.EmbeddingService;
|
||||
import org.ruoyi.service.IChatModelService;
|
||||
import org.ruoyi.service.VectorStoreService;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -48,8 +58,12 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@@ -62,33 +76,80 @@ public class SseServiceImpl implements ISseService {
|
||||
|
||||
private final IChatModelService chatModelService;
|
||||
|
||||
private final EmbeddingService embeddingService;
|
||||
|
||||
private final VectorStoreService vectorStore;
|
||||
|
||||
private final ConfigService configService;
|
||||
|
||||
private final IChatCostService chatCostService;
|
||||
|
||||
private static final String requestIdTemplate = "mycompany-%d";
|
||||
|
||||
private static final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public SseEmitter sseChat(ChatRequest chatRequest, HttpServletRequest request) {
|
||||
SseEmitter sseEmitter = new SseEmitter(0L);
|
||||
SSEEventSourceListener openAIEventSourceListener = new SSEEventSourceListener(sseEmitter);
|
||||
// 获取对话消息列表
|
||||
List<Message> messages = chatRequest.getMessages();
|
||||
// 用户对话内容
|
||||
String chatString = null;
|
||||
try {
|
||||
if (StpUtil.isLogin()) {
|
||||
// 通过模型名称查询模型信息
|
||||
ChatModelVo chatModelVo = chatModelService.selectModelByName(chatRequest.getModel());
|
||||
// 构建api请求客户端
|
||||
openAiStreamClient = chatConfig.createOpenAiStreamClient(chatModelVo.getApiHost(), chatModelVo.getApiKey());
|
||||
// 设置默认提示词
|
||||
Message sysMessage = Message.builder().content(chatModelVo.getSystemPrompt()).role(Message.Role.SYSTEM).build();
|
||||
messages.add(0,sysMessage);
|
||||
|
||||
// 模型设置默认提示词
|
||||
// 查询向量库相关信息加入到上下文
|
||||
if(chatRequest.getKid()!=null){
|
||||
List<Message> knMessages = new ArrayList<>();
|
||||
String content = messages.get(messages.size() - 1).getContent().toString();
|
||||
List<String> nearestList;
|
||||
List<Double> queryVector = embeddingService.getQueryVector(content, chatRequest.getKid());
|
||||
nearestList = vectorStore.nearest(queryVector, chatRequest.getKid());
|
||||
for (String prompt : nearestList) {
|
||||
Message userMessage = Message.builder().content(prompt).role(Message.Role.USER).build();
|
||||
knMessages.add(userMessage);
|
||||
}
|
||||
Message userMessage = Message.builder().content(content + (!nearestList.isEmpty() ? "\n\n注意:回答问题时,须严格根据我给你的系统上下文内容原文进行回答,请不要自己发挥,回答时保持原来文本的段落层级" : "")).role(Message.Role.USER).build();
|
||||
knMessages.add(userMessage);
|
||||
messages.addAll(knMessages);
|
||||
}
|
||||
|
||||
// 是否开启联网查询
|
||||
// 获取用户对话信息
|
||||
Object content = messages.get(messages.size() - 1).getContent();
|
||||
if (content instanceof List<?> listContent) {
|
||||
if (CollectionUtil.isNotEmpty(listContent)) {
|
||||
chatString = listContent.get(0).toString();
|
||||
}
|
||||
} else if (content instanceof String) {
|
||||
chatString = (String) content;
|
||||
}
|
||||
|
||||
// 加载联网信息
|
||||
if(chatRequest.getSearch()){
|
||||
Message message = Message.builder().role(Message.Role.ASSISTANT).content("联网信息:"+webSearch(chatString)).build();
|
||||
messages.add(message);
|
||||
}
|
||||
}else {
|
||||
// 未登录用户限制对话次数,默认5次
|
||||
String clientIp = ServletUtil.getClientIP((javax.servlet.http.HttpServletRequest) request,"X-Forwarded-For");
|
||||
// 未登录用户限制对话次数
|
||||
String clientIp = IpUtil.getClientIp(request);
|
||||
|
||||
// 访客每天默认只能对话5次
|
||||
int timeWindowInSeconds = 5;
|
||||
|
||||
String redisKey = "visitor:" + clientIp;
|
||||
String redisKey = "clientIp:" + clientIp;
|
||||
|
||||
int count = 0;
|
||||
|
||||
if (RedisUtils.getCacheObject(redisKey) == null) {
|
||||
// 当前访问次数
|
||||
// 缓存有效时间1天
|
||||
RedisUtils.setCacheObject(redisKey, count, Duration.ofSeconds(86400));
|
||||
}else {
|
||||
count = RedisUtils.getCacheObject(redisKey);
|
||||
@@ -104,13 +165,11 @@ public class SseServiceImpl implements ISseService {
|
||||
.builder()
|
||||
.messages(messages)
|
||||
.model(chatRequest.getModel())
|
||||
.temperature(chatRequest.getTemperature())
|
||||
.topP(chatRequest.getTop_p())
|
||||
.stream(true)
|
||||
.stream(chatRequest.getStream())
|
||||
.build();
|
||||
openAiStreamClient.streamChatCompletion(completion, openAIEventSourceListener);
|
||||
// 保存消息记录 并扣除费用
|
||||
|
||||
chatCostService.deductToken(chatRequest);
|
||||
} catch (Exception e) {
|
||||
String message = e.getMessage();
|
||||
sendErrorEvent(sseEmitter, message);
|
||||
@@ -147,7 +206,6 @@ public class SseServiceImpl implements ISseService {
|
||||
if (body != null) {
|
||||
// 将ResponseBody转换为InputStreamResource
|
||||
InputStreamResource resource = new InputStreamResource(body.byteStream());
|
||||
|
||||
// 创建并返回ResponseEntity
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.parseMediaType("audio/mpeg"))
|
||||
@@ -289,4 +347,58 @@ public class SseServiceImpl implements ISseService {
|
||||
ChatCompletionResponse chatCompletionResponse = openAiStreamClient.chatCompletion(chatCompletion);
|
||||
return chatCompletionResponse.getChoices().get(0).getMessage().getContent().toString();
|
||||
}
|
||||
|
||||
public String webSearch (String prompt) {
|
||||
String zhipuValue = configService.getConfigValue("zhipu", "key");
|
||||
if(StringUtils.isEmpty(zhipuValue)){
|
||||
throw new IllegalStateException("zhipu config value is empty,请在chat_config中配置zhipu key信息");
|
||||
}else {
|
||||
ClientV4 client = new ClientV4.Builder(zhipuValue)
|
||||
.networkConfig(300, 100, 100, 100, TimeUnit.SECONDS)
|
||||
.connectionPool(new okhttp3.ConnectionPool(8, 1, TimeUnit.SECONDS))
|
||||
.build();
|
||||
|
||||
SearchChatMessage jsonNodes = new SearchChatMessage();
|
||||
jsonNodes.setRole(Message.Role.USER.getName());
|
||||
jsonNodes.setContent(prompt);
|
||||
|
||||
String requestId = String.format(requestIdTemplate, System.currentTimeMillis());
|
||||
WebSearchParamsRequest chatCompletionRequest = WebSearchParamsRequest.builder()
|
||||
.model("web-search-pro")
|
||||
.stream(Boolean.TRUE)
|
||||
.messages(Collections.singletonList(jsonNodes))
|
||||
.requestId(requestId)
|
||||
.build();
|
||||
WebSearchApiResponse webSearchApiResponse = client.webSearchProStreamingInvoke(chatCompletionRequest);
|
||||
List<ChoiceDelta> choices = new ArrayList<>();
|
||||
if (webSearchApiResponse.isSuccess()) {
|
||||
AtomicBoolean isFirst = new AtomicBoolean(true);
|
||||
|
||||
AtomicReference<WebSearchPro> lastAccumulator = new AtomicReference<>();
|
||||
|
||||
webSearchApiResponse.getFlowable().map(result -> result)
|
||||
.doOnNext(accumulator -> {
|
||||
{
|
||||
if (isFirst.getAndSet(false)) {
|
||||
log.info("Response: ");
|
||||
}
|
||||
ChoiceDelta delta = accumulator.getChoices().get(0).getDelta();
|
||||
if (delta != null && delta.getToolCalls() != null) {
|
||||
log.info("tool_calls: {}", mapper.writeValueAsString(delta.getToolCalls()));
|
||||
}
|
||||
choices.add(delta);
|
||||
}
|
||||
})
|
||||
.doOnComplete(() -> System.out.println("Stream completed."))
|
||||
.doOnError(throwable -> System.err.println("Error: " + throwable))
|
||||
.blockingSubscribe();
|
||||
|
||||
WebSearchPro chatMessageAccumulator = lastAccumulator.get();
|
||||
webSearchApiResponse.setFlowable(null);
|
||||
webSearchApiResponse.setData(chatMessageAccumulator);
|
||||
}
|
||||
return choices.get(1).getToolCalls().toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import jakarta.annotation.Resource;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.common.chat.config.ChatConfig;
|
||||
import org.ruoyi.common.chat.entity.embeddings.Embedding;
|
||||
import org.ruoyi.common.chat.entity.embeddings.EmbeddingResponse;
|
||||
import org.ruoyi.common.chat.openai.OpenAiStreamClient;
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.ruoyi.chat.util;
|
||||
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author WangLe
|
||||
*/
|
||||
public class IpUtil {
|
||||
|
||||
public static String getClientIp(HttpServletRequest request) {
|
||||
String ip = null;
|
||||
|
||||
// 获取 X-Forwarded-For 中的第一个非 unknown 的 IP
|
||||
String xForwardedFor = request.getHeader("X-Forwarded-For");
|
||||
if (StringUtils.hasLength(xForwardedFor) && !"unknown".equalsIgnoreCase(xForwardedFor)) {
|
||||
String[] ipAddresses = xForwardedFor.split(",");
|
||||
for (String ipAddress : ipAddresses) {
|
||||
if (StringUtils.hasLength(ipAddress) && !"unknown".equalsIgnoreCase(ipAddress.trim())) {
|
||||
ip = ipAddress.trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果 X-Forwarded-For 中没有找到,则依次尝试其他 header
|
||||
if (ip == null) {
|
||||
ip = request.getHeader("X-Real-IP");
|
||||
}
|
||||
if (ip == null || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("Proxy-Client-IP");
|
||||
}
|
||||
if (ip == null || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||
}
|
||||
if (ip == null || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("HTTP_CLIENT_IP");
|
||||
}
|
||||
if (ip == null || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
|
||||
}
|
||||
|
||||
// 如果以上都没有获取到,则使用 RemoteAddr
|
||||
if (ip == null || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getRemoteAddr();
|
||||
}
|
||||
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
@@ -94,22 +94,6 @@
|
||||
<artifactId>ruoyi-common-tenant</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-pay</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 短信 用哪个导入哪个依赖 -->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.aliyun</groupId>-->
|
||||
<!-- <artifactId>dysmsapi20170525</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.tencentcloudapi</groupId>-->
|
||||
<!-- <artifactId>tencentcloud-sdk-java-sms</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
<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>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-modules</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>ruoyi-system</artifactId>
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
|
||||
<properties>
|
||||
<httpclient.version>4.5.14</httpclient.version>
|
||||
<jackson.version>2.15.2</jackson.version>
|
||||
|
||||
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -32,26 +35,12 @@
|
||||
<artifactId>ruoyi-common-doc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-translation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- OSS功能模块 -->
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-oss</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-log</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- excel-->
|
||||
<dependency>
|
||||
@@ -96,30 +85,6 @@
|
||||
<artifactId>ruoyi-common-chat</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-wechat</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||
<artifactId>jackson-dataformat-xml</artifactId>
|
||||
<version>2.15.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpmime</artifactId>
|
||||
<version>${httpclient.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 支付功能模块 -->
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-pay</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-mail</artifactId>
|
||||
@@ -131,6 +96,18 @@
|
||||
<artifactId>ruoyi-system-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||
<artifactId>jackson-dataformat-xml</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpmime</artifactId>
|
||||
<version>${httpclient.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
package org.ruoyi.system.config;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage;
|
||||
import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
|
||||
import cn.binarywang.wx.miniapp.message.WxMaMessageHandler;
|
||||
import cn.binarywang.wx.miniapp.message.WxMaMessageRouter;
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
import me.chanjar.weixin.common.error.WxRuntimeException;
|
||||
import org.ruoyi.system.properties.WxMaProperties;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Admin
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(WxMaProperties.class)
|
||||
public class WxMaConfiguration {
|
||||
private final WxMaProperties properties;
|
||||
|
||||
@Autowired
|
||||
public WxMaConfiguration(WxMaProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WxMaService wxMaService() {
|
||||
List<WxMaProperties.Config> configs = this.properties.getConfigs();
|
||||
if (configs == null) {
|
||||
throw new WxRuntimeException("大哥,拜托先看下项目首页的说明(readme文件),添加下相关配置,注意别配错了!");
|
||||
}
|
||||
WxMaService maService = new WxMaServiceImpl();
|
||||
maService.setMultiConfigs(
|
||||
configs.stream()
|
||||
.map(a -> {
|
||||
WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
|
||||
// WxMaDefaultConfigImpl config = new WxMaRedisConfigImpl(new JedisPool());
|
||||
// 使用上面的配置时,需要同时引入jedis-lock的依赖,否则会报类无法找到的异常
|
||||
config.setAppid(a.getAppid());
|
||||
config.setSecret(a.getSecret());
|
||||
config.setToken(a.getToken());
|
||||
config.setAesKey(a.getAesKey());
|
||||
config.setMsgDataFormat(a.getMsgDataFormat());
|
||||
return config;
|
||||
}).collect(Collectors.toMap(WxMaDefaultConfigImpl::getAppid, a -> a, (o, n) -> o)));
|
||||
return maService;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WxMaMessageRouter wxMaMessageRouter(WxMaService wxMaService) {
|
||||
final WxMaMessageRouter router = new WxMaMessageRouter(wxMaService);
|
||||
router
|
||||
.rule().handler(logHandler).next()
|
||||
.rule().async(false).content("订阅消息").handler(subscribeMsgHandler).end()
|
||||
.rule().async(false).content("文本").handler(textHandler).end()
|
||||
.rule().async(false).content("图片").handler(picHandler).end()
|
||||
.rule().async(false).content("二维码").handler(qrcodeHandler).end();
|
||||
return router;
|
||||
}
|
||||
|
||||
private final WxMaMessageHandler subscribeMsgHandler = (wxMessage, context, service, sessionManager) -> {
|
||||
service.getMsgService().sendSubscribeMsg(WxMaSubscribeMessage.builder()
|
||||
.templateId("此处更换为自己的模板id")
|
||||
.data(Lists.newArrayList(
|
||||
new WxMaSubscribeMessage.MsgData("keyword1", "339208499")))
|
||||
.toUser(wxMessage.getFromUser())
|
||||
.build());
|
||||
return null;
|
||||
};
|
||||
|
||||
private final WxMaMessageHandler logHandler = (wxMessage, context, service, sessionManager) -> {
|
||||
log.info("收到消息:" + wxMessage.toString());
|
||||
service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("收到信息为:" + wxMessage.toJson())
|
||||
.toUser(wxMessage.getFromUser()).build());
|
||||
return null;
|
||||
};
|
||||
|
||||
private final WxMaMessageHandler textHandler = (wxMessage, context, service, sessionManager) -> {
|
||||
service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("回复文本消息")
|
||||
.toUser(wxMessage.getFromUser()).build());
|
||||
return null;
|
||||
};
|
||||
|
||||
private final WxMaMessageHandler picHandler = (wxMessage, context, service, sessionManager) -> {
|
||||
try {
|
||||
WxMediaUploadResult uploadResult = service.getMediaService()
|
||||
.uploadMedia("image", "png",
|
||||
ClassLoader.getSystemResourceAsStream("tmp.png"));
|
||||
service.getMsgService().sendKefuMsg(
|
||||
WxMaKefuMessage
|
||||
.newImageBuilder()
|
||||
.mediaId(uploadResult.getMediaId())
|
||||
.toUser(wxMessage.getFromUser())
|
||||
.build());
|
||||
} catch (WxErrorException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
private final WxMaMessageHandler qrcodeHandler = (wxMessage, context, service, sessionManager) -> {
|
||||
try {
|
||||
final File file = service.getQrcodeService().createQrcode("123", 430);
|
||||
WxMediaUploadResult uploadResult = service.getMediaService().uploadMedia("image", file);
|
||||
service.getMsgService().sendKefuMsg(
|
||||
WxMaKefuMessage
|
||||
.newImageBuilder()
|
||||
.mediaId(uploadResult.getMediaId())
|
||||
.toUser(wxMessage.getFromUser())
|
||||
.build());
|
||||
} catch (WxErrorException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -2,10 +2,8 @@ package org.ruoyi.system.controller.system;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
import org.ruoyi.common.core.constant.Constants;
|
||||
import org.ruoyi.common.core.domain.R;
|
||||
import org.ruoyi.common.core.domain.model.*;
|
||||
@@ -45,13 +43,13 @@ public class AuthController {
|
||||
private final ISysTenantService tenantService;
|
||||
|
||||
|
||||
@PostMapping("/xcxLogin")
|
||||
public R<LoginVo> login(@Validated @RequestBody String xcxCode) throws WxErrorException {
|
||||
|
||||
String openidFromCode = loginService.getOpenidFromCode((String) JSONUtil.parseObj(xcxCode).get("xcxCode"));
|
||||
LoginVo loginVo = loginService.mpLogin(openidFromCode);
|
||||
return R.ok(loginVo);
|
||||
}
|
||||
// @PostMapping("/xcxLogin")
|
||||
// public R<LoginVo> login(@Validated @RequestBody String xcxCode) throws WxErrorException {
|
||||
//
|
||||
// String openidFromCode = loginService.getOpenidFromCode((String) JSONUtil.parseObj(xcxCode).get("xcxCode"));
|
||||
// LoginVo loginVo = loginService.mpLogin(openidFromCode);
|
||||
// return R.ok(loginVo);
|
||||
// }
|
||||
|
||||
/**
|
||||
* 登录方法
|
||||
|
||||
@@ -95,7 +95,6 @@ public class CaptchaController {
|
||||
String suffix = configService.getConfigValue("mail", "suffix");
|
||||
String prompt = configService.getConfigValue("mail", "prompt");
|
||||
if(StringUtils.isNotEmpty(suffix)){
|
||||
// 动态的域名列表
|
||||
String[] invalidDomains = suffix.split(",");
|
||||
for (String domain : invalidDomains) {
|
||||
if (emailRequest.getUsername().endsWith(domain)) {
|
||||
@@ -108,7 +107,7 @@ public class CaptchaController {
|
||||
String mailTitle = configService.getConfigValue("mail", "mailTitle");
|
||||
String replacedModel = model.replace("{code}", code);
|
||||
try {
|
||||
MailUtils.sendHtml(emailRequest.getUsername(), mailTitle, replacedModel);
|
||||
MailUtils.sendHtml(emailRequest.getUsername(), mailTitle, replacedModel);
|
||||
} catch (Exception e) {
|
||||
log.error("邮箱验证码发送异常 => {}", e.getMessage());
|
||||
return R.fail(e.getMessage());
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.ruoyi.system.properties;
|
||||
|
||||
/**
|
||||
* 微信小程序属性配置类
|
||||
*
|
||||
* @author: wangle
|
||||
* @date: 2023/5/18
|
||||
*/
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "wx.miniapp")
|
||||
public class WxMaProperties {
|
||||
|
||||
private List<Config> configs;
|
||||
|
||||
@Data
|
||||
public static class Config {
|
||||
/**
|
||||
* 设置微信小程序的appid
|
||||
*/
|
||||
private String appid;
|
||||
|
||||
/**
|
||||
* 设置微信小程序的Secret
|
||||
*/
|
||||
private String secret;
|
||||
|
||||
/**
|
||||
* 设置微信小程序消息服务器配置的token
|
||||
*/
|
||||
private String token;
|
||||
|
||||
/**
|
||||
* 设置微信小程序消息服务器配置的EncodingAESKey
|
||||
*/
|
||||
private String aesKey;
|
||||
|
||||
/**
|
||||
* 消息格式,XML或者JSON
|
||||
*/
|
||||
private String msgDataFormat;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.ruoyi.system.runner;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.system.service.ISysOssConfigService;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 初始化 system 模块对应业务数据
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class SystemApplicationRunner implements ApplicationRunner {
|
||||
|
||||
private final ISysOssConfigService ossConfigService;
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
ossConfigService.init();
|
||||
log.info("初始化OSS配置成功");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
package org.ruoyi.system.service;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
|
||||
import cn.binarywang.wx.miniapp.util.WxMaConfigHolder;
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.secure.BCrypt;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.ruoyi.common.core.constant.Constants;
|
||||
import org.ruoyi.common.core.constant.GlobalConstants;
|
||||
import org.ruoyi.common.core.constant.TenantConstants;
|
||||
import org.ruoyi.common.core.domain.dto.RoleDTO;
|
||||
import org.ruoyi.common.core.domain.model.LoginUser;
|
||||
import org.ruoyi.common.core.domain.model.VisitorLoginBody;
|
||||
import org.ruoyi.common.core.domain.model.VisitorLoginUser;
|
||||
import org.ruoyi.common.core.enums.*;
|
||||
import org.ruoyi.common.core.exception.user.CaptchaException;
|
||||
import org.ruoyi.common.core.exception.user.CaptchaExpireException;
|
||||
import org.ruoyi.common.core.exception.user.UserException;
|
||||
import org.ruoyi.common.core.service.ConfigService;
|
||||
import org.ruoyi.common.core.utils.*;
|
||||
import org.ruoyi.common.log.event.LogininforEvent;
|
||||
import org.ruoyi.common.redis.utils.RedisUtils;
|
||||
import org.ruoyi.common.satoken.utils.LoginHelper;
|
||||
import org.ruoyi.common.tenant.exception.TenantException;
|
||||
import org.ruoyi.common.tenant.helper.TenantHelper;
|
||||
import org.ruoyi.system.domain.SysUser;
|
||||
import org.ruoyi.system.domain.bo.SysUserBo;
|
||||
import org.ruoyi.system.domain.vo.LoginVo;
|
||||
import org.ruoyi.system.domain.vo.SysTenantVo;
|
||||
import org.ruoyi.system.domain.vo.SysUserVo;
|
||||
import org.ruoyi.system.mapper.SysUserMapper;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 登录校验方法
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SysLoginService {
|
||||
|
||||
private final SysUserMapper userMapper;
|
||||
private final WxMaService wxMaService;
|
||||
private final ISysPermissionService permissionService;
|
||||
private final ISysTenantService tenantService;
|
||||
private final ISysUserService userService;
|
||||
private final ConfigService configService;
|
||||
|
||||
@Value("${user.password.maxRetryCount}")
|
||||
private Integer maxRetryCount;
|
||||
@Value("${user.password.lockTime}")
|
||||
private Integer lockTime;
|
||||
|
||||
/**
|
||||
* 获取微信
|
||||
* @param xcxCode 获取xcxCode
|
||||
*/
|
||||
public String getOpenidFromCode(String xcxCode) {
|
||||
try {
|
||||
WxMaJscode2SessionResult sessionInfo = wxMaService.getUserService().getSessionInfo(xcxCode);
|
||||
return sessionInfo.getOpenid();
|
||||
} catch (WxErrorException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 登录验证
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param password 密码
|
||||
* @param code 验证码
|
||||
* @param uuid 唯一标识
|
||||
* @return 结果
|
||||
*/
|
||||
public String login(String tenantId, String username, String password, String code, String uuid) {
|
||||
SysUserVo user = loadUserByUsername(tenantId, username);
|
||||
checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
|
||||
// 此处可根据登录用户的数据不同 自行创建 loginUser
|
||||
LoginUser loginUser = buildLoginUser(user);
|
||||
// 生成token
|
||||
LoginHelper.loginByDevice(loginUser, DeviceType.PC);
|
||||
|
||||
recordLogininfor(loginUser.getTenantId(), username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
|
||||
recordLoginInfo(user.getUserId());
|
||||
return StpUtil.getTokenValue();
|
||||
}
|
||||
|
||||
public String smsLogin(String tenantId, String phonenumber, String smsCode) {
|
||||
// 校验租户
|
||||
checkTenant(tenantId);
|
||||
// 通过手机号查找用户
|
||||
SysUserVo user = loadUserByPhonenumber(tenantId, phonenumber);
|
||||
|
||||
checkLogin(LoginType.SMS, tenantId, user.getUserName(), () -> !validateSmsCode(tenantId, phonenumber, smsCode));
|
||||
// 此处可根据登录用户的数据不同 自行创建 loginUser
|
||||
LoginUser loginUser = buildLoginUser(user);
|
||||
// 生成token
|
||||
LoginHelper.loginByDevice(loginUser, DeviceType.APP);
|
||||
|
||||
recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
|
||||
recordLoginInfo(user.getUserId());
|
||||
return StpUtil.getTokenValue();
|
||||
}
|
||||
|
||||
public String emailLogin(String tenantId, String email, String emailCode) {
|
||||
// 校验租户
|
||||
checkTenant(tenantId);
|
||||
// 通过手机号查找用户
|
||||
SysUserVo user = loadUserByEmail(tenantId, email);
|
||||
|
||||
checkLogin(LoginType.EMAIL, tenantId, user.getUserName(), () -> !validateEmailCode(tenantId, email, emailCode));
|
||||
// 此处可根据登录用户的数据不同 自行创建 loginUser
|
||||
LoginUser loginUser = buildLoginUser(user);
|
||||
// 生成token
|
||||
LoginHelper.loginByDevice(loginUser, DeviceType.APP);
|
||||
|
||||
recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
|
||||
recordLoginInfo(user.getUserId());
|
||||
return StpUtil.getTokenValue();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 游客登录
|
||||
*
|
||||
* @param loginBody
|
||||
* @return String
|
||||
* @Date 2023/5/18
|
||||
**/
|
||||
public void visitorLogin(VisitorLoginBody loginBody) {
|
||||
String openid = "";
|
||||
// PC端游客登录
|
||||
if (LoginUserType.PC.getCode().equals(loginBody.getType())) {
|
||||
openid = loginBody.getCode();
|
||||
} else {
|
||||
// 小程序匿名登录
|
||||
try {
|
||||
WxMaJscode2SessionResult session = wxMaService.getUserService().getSessionInfo(loginBody.getCode());
|
||||
openid = session.getOpenid();
|
||||
} catch (WxErrorException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
} finally {
|
||||
// 清理ThreadLocal
|
||||
WxMaConfigHolder.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public LoginVo mpLogin(String openid) {
|
||||
// 使用 openid 查询绑定用户 如未绑定用户 则根据业务自行处理 例如 创建默认用户
|
||||
SysUserVo user = userService.selectUserByOpenId(openid);
|
||||
VisitorLoginUser loginUser = new VisitorLoginUser();
|
||||
if (ObjectUtil.isNull(user)) {
|
||||
SysUserBo sysUser = new SysUserBo();
|
||||
// 改为自增
|
||||
String name = "用户" + UUID.randomUUID().toString().replace("-", "");
|
||||
// 设置默认用户名
|
||||
sysUser.setUserName(name);
|
||||
// 设置默认昵称
|
||||
sysUser.setNickName(name);
|
||||
// 设置默认密码
|
||||
sysUser.setPassword(BCrypt.hashpw("123456"));
|
||||
// 设置微信openId
|
||||
sysUser.setOpenId(openid);
|
||||
String configValue = configService.getConfigValue("mail", "amount");
|
||||
// 设置默认余额
|
||||
sysUser.setUserBalance(NumberUtils.toDouble(configValue, 1));
|
||||
// 注册用户,设置默认租户为0
|
||||
SysUser registerUser = userService.registerUser(sysUser, "0");
|
||||
|
||||
// 构建登录用户信息
|
||||
loginUser.setTenantId("0");
|
||||
loginUser.setUserId(registerUser.getUserId());
|
||||
loginUser.setUsername(registerUser.getUserName());
|
||||
loginUser.setUserType(UserType.APP_USER.getUserType());
|
||||
loginUser.setOpenid(openid);
|
||||
loginUser.setNickName(registerUser.getNickName());
|
||||
|
||||
} else {
|
||||
// 此处可根据登录用户的数据不同 自行创建 loginUser
|
||||
loginUser.setTenantId(user.getTenantId());
|
||||
loginUser.setUserId(user.getUserId());
|
||||
loginUser.setUsername(user.getUserName());
|
||||
loginUser.setUserType(user.getUserType());
|
||||
loginUser.setNickName(user.getNickName());
|
||||
loginUser.setAvatar(user.getWxAvatar());
|
||||
loginUser.setOpenid(openid);
|
||||
}
|
||||
// 生成token
|
||||
LoginHelper.loginByDevice(loginUser, DeviceType.XCX);
|
||||
recordLogininfor(loginUser.getTenantId(), loginUser.getUsername(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
|
||||
LoginVo loginVo = new LoginVo();
|
||||
// 生成令牌
|
||||
loginVo.setToken(StpUtil.getTokenValue());
|
||||
loginVo.setUserInfo(loginUser);
|
||||
return loginVo;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
public void logout() {
|
||||
try {
|
||||
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||
if (TenantHelper.isEnable() && LoginHelper.isSuperAdmin()) {
|
||||
// 超级管理员 登出清除动态租户
|
||||
TenantHelper.clearDynamic();
|
||||
}
|
||||
StpUtil.logout();
|
||||
if (loginUser !=null) {
|
||||
recordLogininfor(loginUser.getTenantId(), loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
|
||||
}
|
||||
} catch (NotLoginException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录登录信息
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @param username 用户名
|
||||
* @param status 状态
|
||||
* @param message 消息内容
|
||||
*/
|
||||
private void recordLogininfor(String tenantId, String username, String status, String message) {
|
||||
LogininforEvent logininforEvent = new LogininforEvent();
|
||||
logininforEvent.setTenantId(tenantId);
|
||||
logininforEvent.setUsername(username);
|
||||
logininforEvent.setStatus(status);
|
||||
logininforEvent.setMessage(message);
|
||||
logininforEvent.setRequest(ServletUtils.getRequest());
|
||||
SpringUtils.context().publishEvent(logininforEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验短信验证码
|
||||
*/
|
||||
private boolean validateSmsCode(String tenantId, String phonenumber, String smsCode) {
|
||||
String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + phonenumber);
|
||||
if (StringUtils.isBlank(code)) {
|
||||
recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
|
||||
throw new CaptchaExpireException();
|
||||
}
|
||||
return code.equals(smsCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验邮箱验证码
|
||||
*/
|
||||
private boolean validateEmailCode(String tenantId, String email, String emailCode) {
|
||||
String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + email);
|
||||
if (StringUtils.isBlank(code)) {
|
||||
recordLogininfor(tenantId, email, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
|
||||
throw new CaptchaExpireException();
|
||||
}
|
||||
return code.equals(emailCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验验证码
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param code 验证码
|
||||
* @param uuid 唯一标识
|
||||
*/
|
||||
public void validateCaptcha(String tenantId, String username, String code, String uuid) {
|
||||
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
|
||||
String captcha = RedisUtils.getCacheObject(verifyKey);
|
||||
RedisUtils.deleteObject(verifyKey);
|
||||
if (captcha == null) {
|
||||
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
|
||||
throw new CaptchaExpireException();
|
||||
}
|
||||
if (!code.equalsIgnoreCase(captcha)) {
|
||||
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
|
||||
throw new CaptchaException();
|
||||
}
|
||||
}
|
||||
|
||||
private SysUserVo loadUserByUsername(String tenantId, String username) {
|
||||
|
||||
SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>().select(SysUser::getUserName, SysUser::getStatus).eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId).eq(SysUser::getUserName, username));
|
||||
if (ObjectUtil.isNull(user)) {
|
||||
log.info("登录用户:{} 不存在.", username);
|
||||
throw new UserException("user.not.exists", username);
|
||||
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
|
||||
log.info("登录用户:{} 已被停用.", username);
|
||||
throw new UserException("user.blocked", username);
|
||||
}
|
||||
if (TenantHelper.isEnable()) {
|
||||
return userMapper.selectTenantUserByUserName(username, tenantId);
|
||||
}
|
||||
return userMapper.selectUserByUserName(username);
|
||||
}
|
||||
|
||||
private SysUserVo loadUserByPhonenumber(String tenantId, String phonenumber) {
|
||||
SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>().select(SysUser::getPhonenumber, SysUser::getStatus).eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId).eq(SysUser::getPhonenumber, phonenumber));
|
||||
if (ObjectUtil.isNull(user)) {
|
||||
log.info("登录用户:{} 不存在.", phonenumber);
|
||||
throw new UserException("user.not.exists", phonenumber);
|
||||
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
|
||||
log.info("登录用户:{} 已被停用.", phonenumber);
|
||||
throw new UserException("user.blocked", phonenumber);
|
||||
}
|
||||
if (TenantHelper.isEnable()) {
|
||||
return userMapper.selectTenantUserByPhonenumber(phonenumber, tenantId);
|
||||
}
|
||||
return userMapper.selectUserByPhonenumber(phonenumber);
|
||||
}
|
||||
|
||||
private SysUserVo loadUserByEmail(String tenantId, String email) {
|
||||
SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>().select(SysUser::getPhonenumber, SysUser::getStatus).eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId).eq(SysUser::getEmail, email));
|
||||
if (ObjectUtil.isNull(user)) {
|
||||
log.info("登录用户:{} 不存在.", email);
|
||||
throw new UserException("user.not.exists", email);
|
||||
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
|
||||
log.info("登录用户:{} 已被停用.", email);
|
||||
throw new UserException("user.blocked", email);
|
||||
}
|
||||
if (TenantHelper.isEnable()) {
|
||||
return userMapper.selectTenantUserByEmail(email, tenantId);
|
||||
}
|
||||
return userMapper.selectUserByEmail(email);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建登录用户
|
||||
*/
|
||||
private LoginUser buildLoginUser(SysUserVo user) {
|
||||
LoginUser loginUser = new LoginUser();
|
||||
loginUser.setTenantId(user.getTenantId());
|
||||
loginUser.setUserId(user.getUserId());
|
||||
loginUser.setDeptId(user.getDeptId());
|
||||
loginUser.setUsername(user.getUserName());
|
||||
loginUser.setAvatar(user.getAvatar());
|
||||
loginUser.setUserType(user.getUserType());
|
||||
loginUser.setMenuPermission(permissionService.getMenuPermission(user.getUserId()));
|
||||
loginUser.setRolePermission(permissionService.getRolePermission(user.getUserId()));
|
||||
loginUser.setDeptName(ObjectUtil.isNull(user.getDept()) ? "" : user.getDept().getDeptName());
|
||||
List<RoleDTO> roles = BeanUtil.copyToList(user.getRoles(), RoleDTO.class);
|
||||
loginUser.setRoles(roles);
|
||||
return loginUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录登录信息
|
||||
*
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
public void recordLoginInfo(Long userId) {
|
||||
SysUser sysUser = new SysUser();
|
||||
sysUser.setUserId(userId);
|
||||
sysUser.setLoginIp(ServletUtils.getClientIP());
|
||||
sysUser.setLoginDate(DateUtils.getNowDate());
|
||||
sysUser.setUpdateBy(userId);
|
||||
userMapper.updateById(sysUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录校验
|
||||
*/
|
||||
private void checkLogin(LoginType loginType, String tenantId, String username, Supplier<Boolean> supplier) {
|
||||
String errorKey = GlobalConstants.PWD_ERR_CNT_KEY + username;
|
||||
String loginFail = Constants.LOGIN_FAIL;
|
||||
|
||||
// 获取用户登录错误次数(可自定义限制策略 例如: key + username + ip)
|
||||
Integer errorNumber = RedisUtils.getCacheObject(errorKey);
|
||||
// 锁定时间内登录 则踢出
|
||||
if (ObjectUtil.isNotNull(errorNumber) && errorNumber.equals(maxRetryCount)) {
|
||||
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
|
||||
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
|
||||
}
|
||||
|
||||
if (supplier.get()) {
|
||||
// 是否第一次
|
||||
errorNumber = ObjectUtil.isNull(errorNumber) ? 1 : errorNumber + 1;
|
||||
// 达到规定错误次数 则锁定登录
|
||||
if (errorNumber.equals(maxRetryCount)) {
|
||||
RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
|
||||
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
|
||||
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
|
||||
} else {
|
||||
// 未达到规定错误次数 则递增
|
||||
RedisUtils.setCacheObject(errorKey, errorNumber);
|
||||
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
|
||||
throw new UserException(loginType.getRetryLimitCount(), errorNumber);
|
||||
}
|
||||
}
|
||||
|
||||
// 登录成功 清空错误次数
|
||||
RedisUtils.deleteObject(errorKey);
|
||||
}
|
||||
|
||||
private void checkTenant(String tenantId) {
|
||||
if (!TenantHelper.isEnable()) {
|
||||
return;
|
||||
}
|
||||
if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
|
||||
return;
|
||||
}
|
||||
SysTenantVo tenant = tenantService.queryByTenantId(tenantId);
|
||||
if (ObjectUtil.isNull(tenant)) {
|
||||
log.info("登录租户:{} 不存在.", tenantId);
|
||||
throw new TenantException("tenant.not.exists");
|
||||
} else if (TenantStatus.DISABLE.getCode().equals(tenant.getStatus())) {
|
||||
log.info("登录租户:{} 已被停用.", tenantId);
|
||||
throw new TenantException("tenant.blocked");
|
||||
} else if (ObjectUtil.isNotNull(tenant.getExpireTime()) && new Date().after(tenant.getExpireTime())) {
|
||||
log.info("登录租户:{} 已超过有效期.", tenantId);
|
||||
throw new TenantException("tenant.expired");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package org.ruoyi.system.service;
|
||||
|
||||
import cn.dev33.satoken.secure.BCrypt;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.ruoyi.common.core.constant.Constants;
|
||||
import org.ruoyi.common.core.constant.GlobalConstants;
|
||||
import org.ruoyi.common.core.domain.model.RegisterBody;
|
||||
import org.ruoyi.common.core.exception.base.BaseException;
|
||||
import org.ruoyi.common.core.exception.user.CaptchaException;
|
||||
import org.ruoyi.common.core.exception.user.CaptchaExpireException;
|
||||
import org.ruoyi.common.core.exception.user.UserException;
|
||||
import org.ruoyi.common.core.service.ConfigService;
|
||||
import org.ruoyi.common.core.utils.MessageUtils;
|
||||
import org.ruoyi.common.core.utils.ServletUtils;
|
||||
import org.ruoyi.common.core.utils.SpringUtils;
|
||||
import org.ruoyi.common.core.utils.StringUtils;
|
||||
import org.ruoyi.common.log.event.LogininforEvent;
|
||||
import org.ruoyi.common.redis.utils.RedisUtils;
|
||||
import org.ruoyi.system.domain.SysUser;
|
||||
import org.ruoyi.system.domain.SysUserRole;
|
||||
import org.ruoyi.system.domain.bo.SysUserBo;
|
||||
import org.ruoyi.system.domain.vo.SysUserVo;
|
||||
import org.ruoyi.system.mapper.SysUserRoleMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 注册校验方法
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class SysRegisterService {
|
||||
|
||||
private final ISysUserService userService;
|
||||
|
||||
private final SysUserRoleMapper userRoleMapper;
|
||||
|
||||
// private final ConfigService configService;
|
||||
/**
|
||||
* 注册
|
||||
*/
|
||||
public void register(RegisterBody registerBody) {
|
||||
|
||||
|
||||
String tenantId = Constants.TENANT_ID;
|
||||
if(StringUtils.isNotBlank(registerBody.getTenantId())){
|
||||
tenantId = registerBody.getTenantId();
|
||||
}
|
||||
String username = registerBody.getUsername();
|
||||
String password = registerBody.getPassword();
|
||||
|
||||
// 检查验证码是否正确
|
||||
validateEmail(username,registerBody.getCode());
|
||||
SysUserBo sysUser = new SysUserBo();
|
||||
sysUser.setDomainName(registerBody.getDomainName());
|
||||
sysUser.setUserName(username);
|
||||
sysUser.setNickName(username);
|
||||
sysUser.setPassword(BCrypt.hashpw(password));
|
||||
if (!userService.checkUserNameUnique(sysUser)) {
|
||||
throw new UserException("添加用户失败", username);
|
||||
}
|
||||
// String configValue = configService.getConfigValue("mail", "amount");
|
||||
|
||||
sysUser.setUserBalance(NumberUtils.toDouble("configValue",1));
|
||||
SysUser user = userService.registerUser(sysUser, tenantId);
|
||||
if (user == null) {
|
||||
throw new UserException("用户注册失败!");
|
||||
}
|
||||
// 设置默认角色
|
||||
SysUserRole sysRole = new SysUserRole();
|
||||
sysRole.setUserId(user.getUserId());
|
||||
sysRole.setRoleId(1L);
|
||||
userRoleMapper.insert(sysRole);
|
||||
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置密码
|
||||
*/
|
||||
public void resetPassWord(RegisterBody registerBody) {
|
||||
String username = registerBody.getUsername();
|
||||
String password = registerBody.getPassword();
|
||||
SysUserVo user = userService.selectUserByUserName(username);
|
||||
if(user == null){
|
||||
throw new UserException(String.format("用户【%s】,未注册!",username));
|
||||
}
|
||||
// 检查验证码是否正确
|
||||
validateEmail(username,registerBody.getCode());
|
||||
userService.resetUserPwd(user.getUserId(),BCrypt.hashpw(password));
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验邮箱验证码
|
||||
*
|
||||
* @param username 用户名
|
||||
*/
|
||||
public void validateEmail(String username,String code) {
|
||||
String key = GlobalConstants.CAPTCHA_CODE_KEY + username;
|
||||
String captcha = RedisUtils.getCacheObject(key);
|
||||
if(code.equals(captcha)){
|
||||
RedisUtils.deleteObject(captcha);
|
||||
}else {
|
||||
throw new BaseException("验证码错误,请重试!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 校验验证码
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param code 验证码
|
||||
* @param uuid 唯一标识
|
||||
*/
|
||||
public void validateCaptcha(String tenantId, String username, String code, String uuid) {
|
||||
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
|
||||
String captcha = RedisUtils.getCacheObject(verifyKey);
|
||||
RedisUtils.deleteObject(verifyKey);
|
||||
if (captcha == null) {
|
||||
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.expire"));
|
||||
throw new CaptchaExpireException();
|
||||
}
|
||||
if (!code.equalsIgnoreCase(captcha)) {
|
||||
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.error"));
|
||||
throw new CaptchaException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录登录信息
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @param username 用户名
|
||||
* @param status 状态
|
||||
* @param message 消息内容
|
||||
* @return
|
||||
*/
|
||||
private void recordLogininfor(String tenantId, String username, String status, String message) {
|
||||
LogininforEvent logininforEvent = new LogininforEvent();
|
||||
logininforEvent.setTenantId(tenantId);
|
||||
logininforEvent.setUsername(username);
|
||||
logininforEvent.setStatus(status);
|
||||
logininforEvent.setMessage(message);
|
||||
logininforEvent.setRequest(ServletUtils.getRequest());
|
||||
SpringUtils.context().publishEvent(logininforEvent);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user