This commit is contained in:
苏元皓
2024-08-29 22:24:42 +08:00
35 changed files with 1245 additions and 33 deletions

View File

@@ -18,11 +18,6 @@
</properties>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
@@ -31,10 +26,11 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.baomidou</groupId>-->
<!-- <artifactId>mybatis-plus-spring-boot3-starter</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
@@ -108,5 +104,10 @@
<artifactId>tencentcloud-sdk-java-tts</artifactId>
<version>3.1.1076</version>
</dependency>
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java-asr</artifactId>
<version>3.1.1083</version>
</dependency>
</dependencies>
</project>

View File

@@ -17,18 +17,12 @@ public class AssistantController {
private AssistantService assistantService;
@GetMapping("/response")
public Object getResponse(@RequestParam String input) {
Map<String, Object> map = assistantService.getResponse(input);
public Object getResponse(@RequestParam String input, @RequestParam int size, @RequestParam String language) {
String userInput = assistantService.speechRecognition(input, size, language);
Map<String, Object> map = assistantService.getResponse(userInput, language);
if (map == null) {
return Result.error("请求出错");
}
return Result.ok(map);
}
@GetMapping("/tts")
public Object textToSpeech(@RequestParam String input) {
Map<String, Object> map = new HashMap<>();
map.put("content", assistantService.textToSpeech(input));
return Result.ok(map);
}
}

View File

@@ -0,0 +1,14 @@
package com.ivmiku.tutorial.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@TableName("guidance")
@Data
public class Guidance {
@TableId
private String id;
private String tag;
private String content;
}

View File

@@ -0,0 +1,8 @@
package com.ivmiku.tutorial.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ivmiku.tutorial.entity.Guidance;
public interface GuidanceMapper extends BaseMapper<Guidance> {
}

View File

@@ -2,11 +2,15 @@ package com.ivmiku.tutorial.service;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ivmiku.tutorial.entity.Guidance;
import com.ivmiku.tutorial.mapper.GuidanceMapper;
import com.ivmiku.tutorial.utils.SnowflakeUtil;
import com.tencentcloudapi.common.AbstractModel;
import com.tencentcloudapi.asr.v20190614.AsrClient;
import com.tencentcloudapi.asr.v20190614.models.SentenceRecognitionRequest;
import com.tencentcloudapi.asr.v20190614.models.SentenceRecognitionResponse;
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.common.profile.ClientProfile;
@@ -14,21 +18,32 @@ import com.tencentcloudapi.common.profile.HttpProfile;
import com.tencentcloudapi.tts.v20190823.TtsClient;
import com.tencentcloudapi.tts.v20190823.models.TextToVoiceRequest;
import com.tencentcloudapi.tts.v20190823.models.TextToVoiceResponse;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class AssistantService {
@Resource
private GuidanceMapper guidanceMapper;
private final String auth = "bad1f6cad39a4c26aa9fe9e4324e096f:ZDczMDIwOTg3NjlhODdmYWVjYTY0YjM1";
private final String secretId = "AKID09INNYxYEFFJH3g9VhljVF3qbDiFdx50";
private final String secretKey = "KajjcNyNaaUCqQroqpzNoMtTHNj4Lbil";
public Map<String, Object> getResponse(String userInput) {
public String getAiResponse(String userInput, String language) {
Map<String, Object> message1 = new HashMap<>();
message1.put("role", "system");
message1.put("content", "模仿语音助手,对用户的问题给出简短的回答");
String prompt = "模仿语音助手,对用户的问题给出简短的回答";
if ("Chinese".equals(language)) {
prompt += ",用中文回答";
} else if ("English".equals(language)) {
prompt += ",用英文回答";
}
message1.put("content", prompt);
Map<String, Object> message2 = new HashMap<>();
message2.put("role", "user");
message2.put("content", userInput);
@@ -50,10 +65,7 @@ public class AssistantService {
}
JSONArray choices = result.getJSONArray("choices");
JSONObject message = choices.getJSONObject(0);
JSONObject content = message.getJSONObject("message");
Map<String, Object> map = new HashMap<>();
map.put("content", content.get("content"));
return map;
return message.getString("message");
}
public String textToSpeech(String content) {
@@ -74,4 +86,80 @@ public class AssistantService {
throw new RuntimeException(e);
}
}
public String speechRecognition(String content, Integer length, String language) {
try{
Credential cred = new Credential(secretId, secretKey);
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint("asr.tencentcloudapi.com");
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
AsrClient client = new AsrClient(cred, "", clientProfile);
SentenceRecognitionRequest req = new SentenceRecognitionRequest();
if ("Chinese".equals(language)) {
req.setEngSerViceType("16k_zh");
} else if ("English".equals(language)) {
req.setEngSerViceType("16k_en");
}
req.setSourceType(1L);
req.setVoiceFormat("wav");
req.setData(content);
req.setDataLen(Long.valueOf(length));
SentenceRecognitionResponse resp = client.SentenceRecognition(req);
return resp.getResult();
} catch (TencentCloudSDKException e) {
throw new RuntimeException(e);
}
}
public String searchGuidance(String tag) {
QueryWrapper<Guidance> queryWrapper = new QueryWrapper<>();
queryWrapper.like("tag", tag);
List<Guidance> list = guidanceMapper.selectList(queryWrapper);
if (list.isEmpty()) {
return null;
}
return list.getFirst().getContent();
}
public String extractTag(String query) {
Map<String, Object> message1 = new HashMap<>();
message1.put("role", "system");
message1.put("content", "根据给出的句子的意思,从以下标签中返回最匹配的一个:安全带、安检、飞机");
Map<String, Object> message2 = new HashMap<>();
message2.put("role", "user");
message2.put("content", query);
JSONArray array = new JSONArray();
array.add(message1);
array.add(message2);
JSONObject params = new JSONObject();
params.put("model", "general");
params.put("messages", array);
HttpResponse response = HttpRequest.post("https://spark-api-open.xf-yun.com/v1/chat/completions")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + auth)
.body(params.toJSONString())
.execute();
JSONObject result = JSONObject.parseObject(response.body());
response.close();
if (result.getInteger("code") != 0) {
return null;
}
JSONArray choices = result.getJSONArray("choices");
JSONObject message = choices.getJSONObject(0);
return String.valueOf(message.getJSONObject("message"));
}
public Map<String, Object> getResponse(String query, String language) {
Map<String, Object> result = new HashMap<>();
String tag = extractTag(query);
String content;
content = searchGuidance(tag);
if (content == null) {
content = getAiResponse(query, language);
}
Map<String, Object> map = new HashMap<>();
map.put("content", textToSpeech(content));
return map;
}
}

View File

@@ -117,6 +117,14 @@
<artifactId>commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
<build>

View File

@@ -0,0 +1,24 @@
package com.ivmiku.tutorial.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.ivmiku.tutorial.mapper")
public class MybatisPlusConfig {
/**
* 添加分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
//interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); 如果有多数据源可以不配具体类型 否则都建议配上具体的DbType
return interceptor;
}
}

View File

@@ -0,0 +1,27 @@
package com.ivmiku.tutorial.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMqConfig {
@Bean
public Queue queue1() {
return new Queue("queue1",true);
}
@Bean
public FanoutExchange exchange1() {
return new FanoutExchange("exchange1",true, false);
}
@Bean
public Binding binding1() {
return BindingBuilder.bind(queue1()).to(exchange1());
}
}

View File

@@ -0,0 +1,15 @@
package com.ivmiku.tutorial.config;
import cn.dev33.satoken.interceptor.SaInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册 Sa-Token 拦截器,打开注解式鉴权功能
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
}
}

View File

@@ -0,0 +1,34 @@
package com.ivmiku.tutorial.config;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
import org.springframework.web.util.WebAppRootListener;
@Configuration
public class WebSocketConfig implements ServletContextInitializer {
@Bean
public ServerEndpointExporter serverEndpointExporter (){
return new ServerEndpointExporter();
}
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.addListener(WebAppRootListener.class);
servletContext.setInitParameter("org.apache.tomcat.websocket.textBufferSize","102400000");
}
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
// 在此处设置bufferSize
container.setMaxTextMessageBufferSize(50*1024*1024);
container.setMaxBinaryMessageBufferSize(50*1024*1024);
container.setMaxSessionIdleTimeout(15 * 60000L);
return container;
}
}

View File

@@ -0,0 +1,39 @@
package com.ivmiku.tutorial.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import com.alibaba.fastjson2.JSON;
import com.ivmiku.tutorial.entity.HistoryQuery;
import com.ivmiku.tutorial.response.Result;
import com.ivmiku.tutorial.service.MessageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.text.ParseException;
@SaCheckLogin
@RestController
@RequestMapping("/api/message")
public class MessageController {
@Autowired
private MessageService messageService;
@PostMapping("/history")
public Object getChatHistory(@RequestBody HistoryQuery input) throws ParseException {
Result result = Result.ok();
if (input.getStartDate() != null && input.getEndDate() != null) {
result.setData(messageService.getChatHistoryByDate(input.getUser1Id(), input.getUser2Id(), input.getStartDate(), input.getEndDate(), input.getPage(), input.getSize()));
} else {
if (input.getPage() <= 0 || input.getSize() <=0) {
return Result.error("请输入合法分页参数");
} else {
result.setData(messageService.getChatHistory(input.getUser1Id(), input.getUser2Id(), input.getPage(), input.getSize()));
}
}
return JSON.toJSON(result);
}
@GetMapping("/list")
public Object getChatList(@RequestParam String user_id, @RequestParam int page, @RequestParam int size) {
return Result.ok(messageService.getChatList(user_id, page, size));
}
}

View File

@@ -0,0 +1,42 @@
package com.ivmiku.tutorial.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.stp.StpUtil;
import com.ivmiku.tutorial.entity.IgnoreUser;
import com.ivmiku.tutorial.response.Result;
import com.ivmiku.tutorial.service.RelationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@SaCheckLogin
@RestController
@RequestMapping("/api/relation")
public class RelationController {
@Autowired
private RelationService relationService;
@PostMapping("/ignore")
public Object ignoreUser(@RequestBody IgnoreUser input) {
relationService.IgnoreUser(input.getUserId(), input.getToIgnore());
return Result.ok();
}
@GetMapping("subscribe")
public Object subscribe(@RequestParam String subId) {
String userId = (String) StpUtil.getLoginId();
relationService.subscribe(userId, subId);
return Result.ok();
}
@GetMapping("fan")
public Object getFanList(@RequestParam int page, @RequestParam int size) {
String userId = (String) StpUtil.getLoginId();
return Result.ok(relationService.getFanList(userId, page, size));
}
@GetMapping("follow")
public Object getFollowList(@RequestParam int page, @RequestParam int size) {
String userId = (String) StpUtil.getLoginId();
return Result.ok(relationService.getSubList(userId, page, size));
}
}

View File

@@ -2,6 +2,7 @@ package com.ivmiku.tutorial.controller;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
import cn.binarywang.wx.miniapp.util.WxMaConfigHolder;
import cn.dev33.satoken.stp.StpUtil;
import com.alibaba.fastjson.JSONObject;
@@ -12,7 +13,10 @@ import com.ivmiku.tutorial.response.Result;
import com.ivmiku.tutorial.utils.RedisUtil;
import jakarta.annotation.Resource;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
@@ -29,11 +33,16 @@ public class UserController {
private RedisUtil redisUtil;
@GetMapping("/login")
public Object login(@RequestParam(name = "code") String code, @RequestParam(name = "rawdata") String rawData, @RequestParam(name = "signature") String signature
) {
public Object login(@RequestParam(name = "code") String code,
@RequestParam(name = "rawdata") String rawData,
@RequestParam(name = "signature") String signature,
@RequestParam(name = "encryptedData") String encryptedData,
@RequestParam(name = "iv") String iv) {
WxMaJscode2SessionResult session;
WxMaUserInfo userInfo;
try {
session = wxMaService.getUserService().getSessionInfo(code);
userInfo = wxMaService.getUserService().getUserInfo(session.getSessionKey(), encryptedData, iv);
} catch (WxErrorException e) {
return JSON.toJSON(Result.error(e.getMessage()));
}
@@ -49,6 +58,8 @@ public class UserController {
user.setOpenid(session.getOpenid());
user.setNickname(raw.getString("nickname"));
user.setAvatarUrl(raw.getString("avatarUrl"));
user.setGender(raw.getString("gender"));
user.setAdmin(false);
userMapper.insert(user);
}
map.put("token", StpUtil.getTokenValue());
@@ -56,4 +67,14 @@ public class UserController {
WxMaConfigHolder.remove();
return JSON.toJSON(Result.ok(map));
}
@GetMapping("/validate")
public Object validate() {
try {
StpUtil.checkLogin();
} catch (Exception e) {
return Result.error(e.getMessage());
}
return Result.ok();
}
}

View File

@@ -0,0 +1,139 @@
package com.ivmiku.tutorial.controller;
import cn.dev33.satoken.stp.StpUtil;
import com.alibaba.fastjson2.JSON;
import com.ivmiku.tutorial.entity.Message;
import com.ivmiku.tutorial.service.MessageService;
import com.ivmiku.tutorial.service.RelationService;
import com.ivmiku.tutorial.utils.DateUtil;
import com.ivmiku.tutorial.utils.MessageUtil;
import jakarta.websocket.*;
import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Controller;
import java.io.IOException;
import java.text.ParseException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static jakarta.websocket.CloseReason.CloseCodes.CLOSED_ABNORMALLY;
@Controller
@ServerEndpoint(value = "/chat/{satoken}")
public class WebSocketServer implements ApplicationContextAware {
public static Map<String, Session> sessionMap = new ConcurrentHashMap<>();
public static Map<String, Integer> controlMap = new HashMap<>();
private static ApplicationContext applicationContext;
@Autowired
private MessageService messageService;
private RabbitTemplate rabbitTemplate;
private RelationService relationService;
@OnMessage
public void onMessage(String message, Session session) throws IOException {
Message msg = JSON.parseObject(message, Message.class);
msg.setDate(DateUtil.getCurrentTime());
if (MessageUtil.checkMessage(msg.getMessage())) {
session.getBasicRemote().sendText("发送的信息含有敏感词,请进行调整");
if (!controlMap.containsKey(msg.getFromId())){
controlMap.put(msg.getFromId(), 0);
}
if (controlMap.get(msg.getFromId()) == 4){
session.getBasicRemote().sendText("由于多次违反社区规则您已被封禁1小时");
session.close(new CloseReason(CLOSED_ABNORMALLY, "账号被封禁"));
StpUtil.kickout(msg.getFromId());
StpUtil.disable(msg.getFromId(), 3600);
controlMap.put(msg.getFromId(), 0);
}
controlMap.put(msg.getFromId(), controlMap.get(msg.getFromId())+1);
} else
if (relationService.ifIgnored(msg.getToId(), msg.getFromId())) {
session.getBasicRemote().sendText("您已被对方屏蔽");
} else {
messageService.insertMessage(msg);
rabbitTemplate.convertAndSend("exchange1", "", JSON.toJSONString(msg));
}
}
@OnOpen
public void onOpen(Session session, EndpointConfig endpointConfig, @PathParam("satoken") String satoken) throws IOException {
String userId = (String) StpUtil.getLoginIdByToken(satoken);
if (userId == null) {
session.getBasicRemote().sendText("Invalid Token");
session.close();
}
this.messageService = WebSocketServer.applicationContext.getBean(MessageService.class);
this.rabbitTemplate = WebSocketServer.applicationContext.getBean(RabbitTemplate.class);
this.relationService = WebSocketServer.applicationContext.getBean(RelationService.class);
sessionMap.put(userId, session);
List<Message> unreadList = messageService.getUnreadMsg(userId);
for(Message msg : unreadList) {
session.getBasicRemote().sendText(JSON.toJSONString(msg));
}
}
@OnClose
public void onClose(CloseReason closeReason, Session session){
sessionMap.remove(session.getId());
}
@OnError
public void onError(Throwable throwable) throws IOException {
throwable.printStackTrace();
}
public void sendToUser(Message msg) throws IOException, ParseException {
if (sessionMap.containsKey(msg.getToId())){
sessionMap.get(msg.getToId()).getBasicRemote().sendText(JSON.toJSONString(msg));
}
else {
messageService.insertUnreadMsg(msg.getToId(), msg);
}
msg.setChatId(messageService.getChatId(msg.getFromId(), msg.getToId()));
messageService.insertToMysql(msg);
messageService.insertToRedis(msg);
}
public void sendToPublic(Message msg) throws IOException, ParseException {
for (Session session : sessionMap.values()) {
session.getBasicRemote().sendText(JSON.toJSONString(msg));
}
msg.setChatId(messageService.getChatId(msg.getFromId(), msg.getToId()));
messageService.insertToMysql(msg);
messageService.insertToRedis(msg);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
WebSocketServer.applicationContext = applicationContext;
}
@RabbitHandler
@RabbitListener(bindings = @QueueBinding(
value = @Queue(),
exchange = @Exchange(value = "exchange1",type = ExchangeTypes.FANOUT)
))
public void sendMsg(String message) throws IOException {
Message msg = JSON.parseObject(message, Message.class);
if (sessionMap.containsKey(msg.getToId())) {
sessionMap.get(msg.getToId()).getBasicRemote().sendText(JSON.toJSONString(msg));
messageService.deleteMessage(msg);
}
}
}

View File

@@ -0,0 +1,18 @@
package com.ivmiku.tutorial.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* @author Aurora
*/
@Data
@TableName("chatid")
public class ChatId {
@TableId(type = IdType.ASSIGN_ID)
private String id;
private String user1Id;
private String user2Id;
}

View File

@@ -0,0 +1,18 @@
package com.ivmiku.tutorial.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author Aurora
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("friend")
public class Friend {
private String user1Id;
private String user2Id;
}

View File

@@ -0,0 +1,12 @@
package com.ivmiku.tutorial.entity;
import lombok.Data;
/**
* @author Aurora
*/
@Data
public class FriendQuery {
private String user1Id;
private String user2Id;
}

View File

@@ -0,0 +1,16 @@
package com.ivmiku.tutorial.entity;
import lombok.Data;
/**
* @author Aurora
*/
@Data
public class HistoryQuery {
private String user1Id;
private String user2Id;
private int page;
private int size;
private String startDate;
private String endDate;
}

View File

@@ -0,0 +1,21 @@
package com.ivmiku.tutorial.entity;
import com.alibaba.fastjson2.annotation.JSONField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author Aurora
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("blacklist")
public class IgnoreUser {
@JSONField(ordinal = 1)
private String userId;
@JSONField(ordinal = 2)
private String toIgnore;
}

View File

@@ -0,0 +1,25 @@
package com.ivmiku.tutorial.entity;
import com.alibaba.fastjson2.annotation.JSONField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
/**
* @author Aurora
*/
@Data
@TableName("message")
public class Message implements Serializable {
@JSONField(ordinal = 1)
private String chatId;
@JSONField(ordinal = 2)
private String fromId;
@JSONField(ordinal = 3)
private String toId;
@JSONField(ordinal = 4)
private String message;
@JSONField(ordinal = 5)
private String date;
}

View File

@@ -0,0 +1,18 @@
package com.ivmiku.tutorial.entity;
import lombok.Data;
@Data
public class SubUser {
private String id;
private String username;
private String avatarUrl;
public static SubUser setUser(User user) {
SubUser subUser = new SubUser();
subUser.setId(user.getOpenid());
subUser.setUsername(user.getNickname());
subUser.setAvatarUrl(user.getAvatarUrl());
return subUser;
}
}

View File

@@ -0,0 +1,13 @@
package com.ivmiku.tutorial.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* @author Aurora
*/
@TableName("subscribe")
@Data
public class Subscribe {
private Long id;
private Long subId;
}

View File

@@ -11,4 +11,8 @@ public class User {
private String openid;
private String nickname;
private String avatarUrl;
private boolean isAdmin;
private String phoneNum;
private String birthday;
private String gender;
}

View File

@@ -0,0 +1,8 @@
package com.ivmiku.tutorial.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ivmiku.tutorial.entity.IgnoreUser;
public interface BlackListMapper extends BaseMapper<IgnoreUser> {
}

View File

@@ -0,0 +1,8 @@
package com.ivmiku.tutorial.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ivmiku.tutorial.entity.ChatId;
public interface ChatIdMapper extends BaseMapper<ChatId> {
}

View File

@@ -0,0 +1,8 @@
package com.ivmiku.tutorial.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ivmiku.tutorial.entity.Friend;
public interface FriendMapper extends BaseMapper<Friend> {
}

View File

@@ -0,0 +1,8 @@
package com.ivmiku.tutorial.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ivmiku.tutorial.entity.Message;
public interface MessageMapper extends BaseMapper<Message> {
}

View File

@@ -0,0 +1,8 @@
package com.ivmiku.tutorial.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ivmiku.tutorial.entity.Subscribe;
public interface SubscribeMapper extends BaseMapper<Subscribe> {
}

View File

@@ -0,0 +1,248 @@
package com.ivmiku.tutorial.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ivmiku.tutorial.entity.ChatId;
import com.ivmiku.tutorial.entity.Message;
import com.ivmiku.tutorial.mapper.ChatIdMapper;
import com.ivmiku.tutorial.mapper.MessageMapper;
import com.ivmiku.tutorial.utils.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* @author Aurora
*/
@Service
public class MessageService {
@Autowired
private MessageMapper messageMapper;
@Autowired
private ChatIdMapper chatIdMapper;
@Autowired
private RedisUtil redisUtil;
/**
* 获取会话id
* @param user1Id 用户1id
* @param user2Id 用户2id
* @return 查询结果
*/
public String getChatId(String user1Id, String user2Id) {
if (Objects.equals(user1Id, "public") || Objects.equals(user2Id, "public")) {
return "0";
}
String id1, id2;
if (user1Id.compareTo(user2Id) < 0) {
id1 = user1Id;
id2 = user2Id;
} else {
id1 = user2Id;
id2 = user1Id;
}
QueryWrapper<ChatId> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user1_id", id1);
queryWrapper.eq("user2_id", id2);
ChatId chatId = chatIdMapper.selectOne(queryWrapper);
if (chatId == null) {
chatId = new ChatId();
chatId.setUser1Id(id1);
chatId.setUser2Id(id2);
chatIdMapper.insert(chatId);
}
return chatIdMapper.selectOne(queryWrapper).getId();
}
/**
* 聊天记录写redis
* @param msg 要写入的信息
* @throws ParseException
*/
@Async
public void insertToRedis(Message msg) throws ParseException {
if (!(msg.getMessage().length() >1000)) {
if (redisUtil.getZsetSize("history:" + msg.getChatId()) >= 50) {
redisUtil.zsetRightPop("history:" + msg.getChatId());
}
redisUtil.zsetAdd("history:" + msg.getChatId(), msg);
redisUtil.setExpireTime("history:" + msg.getChatId());
}
}
/**
* 聊天记录写入mysql
* @param msg 要写入的消息
*/
@Async
public void insertToMysql(Message msg) {
if (!(msg.getMessage().length() >1000)) {
messageMapper.insert(msg);
}
}
/**
* 未读消息写入redis
* @param userId 用户id
* @param msg 未读消息
*/
@Async
public void insertUnreadMsg(String userId, Message msg) {
redisUtil.listAdd("unread:" + userId, msg);
}
/**
* 获取未读消息列表
* @param userId 用户id
* @return 查询结果
*/
public List<Message> getUnreadMsg(String userId) {
if (redisUtil.ifExist("unread:" + userId)) {
List<Message> result = redisUtil.listGet("unread:" + userId, 0, -1);
redisUtil.listClear("unread:" + userId);
return result;
}
return new ArrayList<>();
}
/**
* 从数据库获取聊天记录
* @param chatId 会话id
* @param current 分页参数
* @param size 分页参数
* @return 返回的查询结果
*/
public List<Message> getChatHistoryFromDB(String chatId, int current, int size) {
QueryWrapper<Message> queryWrapper = new QueryWrapper<>();
Page<Message> page = new Page<>(current, size);
queryWrapper.eq("chat_id", chatId);
queryWrapper.orderByDesc("date");
return messageMapper.selectPage(page, queryWrapper).getRecords();
}
/**
* 从数据库获取聊天记录,查询一定范围内
* @param chatId 会话id
* @param current 分页参数
* @param size 分页参数
* @param startDate 开始日期
* @param endDate 结束日期
* @return 返回的查询结果
*/
public List<Message> getChatHistoryFromDBByDate(String chatId, int current, int size, String startDate, String endDate) {
QueryWrapper<Message> queryWrapper = new QueryWrapper<>();
queryWrapper.between("date", startDate, endDate);
Page<Message> page = new Page<>(current, size);
queryWrapper.eq("chat_id", chatId);
queryWrapper.orderByDesc("date");
return messageMapper.selectPage(page, queryWrapper).getRecords();
}
/**
* redis获取聊天记录
* @param chatId 会话id
* @param s 开始
* @param e 结束
* @return 查询结果
*/
public List<Message> getChatHistoryFromRedis(String chatId, int s, int e) {
return redisUtil.zsetGet("history:" + chatId, s, e);
}
/**
* 查询聊天记录
* @param user1Id 用户1id
* @param user2Id 用户2id
* @param page 分页参数
* @param size 分页参数
* @return 查询结果
* @throws ParseException
*/
public List<Message> getChatHistory(String user1Id, String user2Id, int page, int size) throws ParseException {
int start = page * size - size;
int end = page * size - 1;
String chatId = getChatId(user1Id, user2Id);
loadCache(chatId);
List<Message> result = new ArrayList<>(redisUtil.zsetGet("history:" + chatId, start, end));
if ((end -start + 1) == result.size()) {
return result;
}
int redisSize = result.size();
List<Message> dbList = getChatHistoryFromDB(chatId, ((end - result.size()) / size) + 1, size);
result.addAll(dbList.subList(redisSize, dbList.size()));
redisUtil.refreshExpire("history:" + chatId);
return result;
}
/**
* 在一定时间范围内查询聊天记录
* @param user1Id 用户1id
* @param user2Id 用户2id
* @param startDate 开始日期
* @param endDate 结束日期
* @param page 分页参数
* @param size 分页参数
* @return 查询结果
* @throws ParseException
*/
public List<Message> getChatHistoryByDate(String user1Id, String user2Id, String startDate, String endDate, int page, int size) throws ParseException {
int start = page * size - size;
int end = page * size - 1;
String chatId = getChatId(user1Id, user2Id);
loadCache(chatId);
List<Message> result = new ArrayList<>(redisUtil.zsetGetByDate("history:" + chatId, startDate, endDate, start, size));
redisUtil.refreshExpire("history:" + chatId);
if (result.size() == (end - start + 1)) {
return result;
}
int redisSize = result.size();
List<Message> dbList = getChatHistoryFromDBByDate(chatId, ((end - result.size()) / size) + 1, size, startDate, endDate).subList(result.size(), size);
result.addAll(dbList.subList(redisSize, dbList.size()));
return result;
}
/**
* 获取会话列表
* @param userId 用户id
* @param current 分页参数
* @param size 分页参数
* @return 查询结果
*/
public List<ChatId> getChatList(String userId, int current, int size) {
QueryWrapper<ChatId> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user1_id", userId).or().eq("user2_id", userId);
queryWrapper.orderByDesc("id");
Page<ChatId> page = new Page<>(current, size);
return chatIdMapper.selectPage(page, queryWrapper).getRecords();
}
/**
* 加载聊天记录到redis
* @param chatId 会话id
* @throws ParseException
*/
public void loadCache(String chatId) throws ParseException {
if (!redisUtil.ifExist("history:" + chatId)) {
List<Message> list = getChatHistoryFromDB(chatId, 1, 20);
for (Message message : list) {
insertToRedis(message);
}
}
}
public void insertMessage(Message message) {
String id = message.getToId();
redisUtil.listAdd("unread:" + id, message);
}
public void deleteMessage(Message message) {
redisUtil.deleteFromList(message);
}
}

View File

@@ -0,0 +1,120 @@
package com.ivmiku.tutorial.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ivmiku.tutorial.entity.*;
import com.ivmiku.tutorial.mapper.BlackListMapper;
import com.ivmiku.tutorial.mapper.FriendMapper;
import com.ivmiku.tutorial.mapper.SubscribeMapper;
import com.ivmiku.tutorial.utils.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* @author Aurora
*/
@Service
public class RelationService {
@Autowired
private BlackListMapper blackListMapper;
@Autowired
private FriendMapper friendMapper;
@Autowired
private RedisUtil redisUtil;
@Autowired
private SubscribeMapper subscribeMapper;
@Autowired
private UserService userService;
/**
* 屏蔽用户
* @param userId 用户id
* @param toIgnore 要屏蔽的用户id
*/
public void IgnoreUser(String userId, String toIgnore) {
blackListMapper.insert(new IgnoreUser(userId, toIgnore));
if (redisUtil.ifExist("blacklist:" + userId)) {
redisUtil.listAdd("blacklist:" + userId, toIgnore);
}
}
/**
* 查询用户是否屏蔽了该用户
* @param userId 用户id
* @param ignoreId 要查询的id
* @return 查询结果
*/
public boolean ifIgnored(String userId, String ignoreId) {
loadCache(userId);
List<String> blackList = redisUtil.getStringList("blacklist:" + userId, 0, -1);
redisUtil.refreshExpire("blacklist:" + userId);
return blackList.contains(ignoreId);
}
/**
* 加载缓存
* @param userId 用户id
*/
public void loadCache(String userId) {
if (!redisUtil.ifExist("blacklist:" + userId)) {
QueryWrapper<IgnoreUser> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id", userId);
List<IgnoreUser> list = blackListMapper.selectList(queryWrapper);
List<String> result = new ArrayList<>();
if (list != null) {
for (IgnoreUser object : list) {
result.add(object.getToIgnore());
}
}
for (String toIgnore : result) {
redisUtil.listAdd("blacklist:" + userId, toIgnore);
}
redisUtil.setExpireTime("blacklist:" + userId);
}
}
public void subscribe(String id, String toId) {
Subscribe subscribe = new Subscribe();
subscribe.setId(Long.valueOf(id));
subscribe.setSubId(Long.valueOf(toId));
subscribeMapper.insert(subscribe);
}
public List<SubUser> getFanList(String userId, int page, int size) {
List<SubUser> list = new ArrayList<>();
QueryWrapper<Subscribe> queryWrapper = new QueryWrapper<>();
Page<Subscribe> pager = new Page<>(page, size);
queryWrapper.eq("sub_id", userId);
List<Subscribe> array = subscribeMapper.selectPage(pager, queryWrapper).getRecords();
for (Subscribe subscribe : array) {
String queryId = String.valueOf(subscribe.getId());
User user = userService.selectUserById(queryId);
SubUser subUser = SubUser.setUser(user);
list.add(subUser);
}
return list;
}
public List<SubUser> getSubList(String userId, int page, int size) {
List<SubUser> list = new ArrayList<>();
QueryWrapper<Subscribe> queryWrapper = new QueryWrapper<>();
Page<Subscribe> pager = new Page<>(page, size);
queryWrapper.eq("id", userId);
List<Subscribe> array = subscribeMapper.selectPage(pager, queryWrapper).getRecords();
for (Subscribe subscribe : array) {
String queryId = String.valueOf(subscribe.getSubId());
User user = userService.selectUserById(queryId);
SubUser subUser = SubUser.setUser(user);
list.add(subUser);
}
return list;
}
}

View File

@@ -0,0 +1,16 @@
package com.ivmiku.tutorial.service;
import com.ivmiku.tutorial.entity.User;
import com.ivmiku.tutorial.mapper.UserMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Resource
private UserMapper userMapper;
public User selectUserById(String id) {
return userMapper.selectById(id);
}
}

View File

@@ -0,0 +1,43 @@
package com.ivmiku.tutorial.utils;
import jakarta.annotation.PostConstruct;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
/**
* @author Aurora
*/
public class DateUtil {
/**
* 获取当前时间
* @return 当前时间字符串
*/
public static String getCurrentTime() {
Date date = new Date();
SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return ft.format(date);
}
/**
* 转换为时间戳
* @param time 时间字符串
* @return 时间戳
* @throws ParseException
*/
public static long toTimeSig(String time) throws ParseException {
SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = ft.parse(time);
return date.getTime();
}
/**
* 设置当前时区GMT+8
*/
@PostConstruct
public void setTimeZone() {
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
}
}

View File

@@ -0,0 +1,36 @@
package com.ivmiku.tutorial.utils;
import com.ivmiku.tutorial.service.RelationService;
import org.springframework.stereotype.Component;
/**
* 敏感词检测
* @author Aurora
*/
@Component
public class MessageUtil {
private static final String[] SENSITIVE = {"你妈", "你妈逼的"};
private static RelationService relationService = null;
public MessageUtil(RelationService relationService) {
MessageUtil.relationService = relationService;
}
/**
* 查看发送的信息是否含有敏感词
* @param message 要发送的信息
* @return 检查结果
*/
public static boolean checkMessage(String message) {
if (message != null) {
for(String keyword : SENSITIVE) {
if (message.contains(keyword)){
return true;
}
}
}
return false;
}
}

View File

@@ -1,9 +1,18 @@
package com.ivmiku.tutorial.utils;
import com.alibaba.fastjson2.JSON;
import com.ivmiku.tutorial.entity.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @author Aurora
*/
@@ -12,11 +21,110 @@ public class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void insertKey(String userId, String sessionKey) {
redisTemplate.opsForValue().set("sessionkey:" + userId, sessionKey);
public void listAdd(String key, Object value) {
redisTemplate.opsForList().leftPush(key, value);
}
public List<Message> listGet(String key, int s, int e) {
List<Object> list = redisTemplate.opsForList().range(key, s, e);
List<Message> result = new ArrayList<>();
if (list != null) {
for (Object json : list) {
result.add(JSON.parseObject(JSON.toJSONString(json), Message.class));
}
}
return result;
}
public void listClear(String key) {
redisTemplate.opsForList().trim(key, 1, 0);
}
public Long getListSize(String key) {
return redisTemplate.opsForList().size(key);
}
public Set<String> getKey() {
return redisTemplate.keys("history:*");
}
public Object rightPop(String key) {
return redisTemplate.opsForList().rightPop(key);
}
public void zsetAdd(String key, Message value) throws ParseException {
redisTemplate.opsForZSet().add(key, value, DateUtil.toTimeSig(value.getDate()));
}
public List<Message> zsetGet(String key, int s, int e) {
Set<Object> list = redisTemplate.opsForZSet().reverseRange(key, s, e);
List<Message> result = new ArrayList<>();
if (list != null) {
for (Object json : list) {
result.add(JSON.parseObject(JSON.toJSONString(json), Message.class));
}
}
return result;
}
public Long getzsetSize(String key) {
return redisTemplate.opsForZSet().size(key);
}
public List<Message> zsetGetByDate(String key, String startDate, String endDate, int offset, int count) throws ParseException {
Set<Object> list = redisTemplate.opsForZSet().reverseRangeByScore(key, DateUtil.toTimeSig(startDate), DateUtil.toTimeSig(endDate), offset, count);
List<Message> result = new ArrayList<>();
if (list != null) {
for (Object json : list) {
result.add(JSON.parseObject(JSON.toJSONString(json), Message.class));
}
}
return result;
}
public Message zsetRightPop(String key) {
return JSON.parseObject(JSON.toJSONString(Objects.requireNonNull(redisTemplate.opsForZSet().popMin(key)).getValue()), Message.class);
}
public Long getZsetSize(String key) {
return redisTemplate.opsForZSet().size(key);
}
public void setExpireTime(String key) {
if (redisTemplate.opsForValue().getOperations().getExpire(key) > 0) {
redisTemplate.expire(key, 3, TimeUnit.DAYS);
}
}
public void refreshExpire(String key) {
redisTemplate.persist(key);
redisTemplate.expire(key, 3, TimeUnit.DAYS);
}
public boolean ifExist(String key) {
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}
public List<String> getStringList(String key, int s, int e) {
List<Object> list = redisTemplate.opsForList().range(key, s, e);
List<String> result = new ArrayList<>();
assert list != null;
for (Object object : list) {
result.add((String) object);
}
return result;
}
public void insertKey(String openid, String sessionKey) {
redisTemplate.opsForValue().set("sessionkey:" + openid, sessionKey);
}
public String getKey(String userId) {
return (String) redisTemplate.opsForValue().get("sessionkey:" + userId);
}
public void deleteFromList(Message message) {
String id = message.getToId();
redisTemplate.opsForList().remove("unread:" + id, 0, message);
}
}

View File

@@ -11,8 +11,10 @@ server.port=8072
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.password=12345abcde
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/tutorial?useUnicode=true&characterEncoding=utf8&useSSL=false&ServerTimezone=Asia/Shanghai
dubbo.application.qos-enable=false
dubbo.application.qos-enable=false
spring.rabbitmq.port=5672