Compare commits

...

23 Commits

Author SHA1 Message Date
ivmiku
17a52ed471 fix: 依赖改名 2024-09-14 18:06:46 +08:00
ivmiku
b987c11cd1 fix: 依赖改名 2024-09-14 14:57:39 +08:00
ivmiku
62a521388d feat: 修改网关配置 2024-09-14 13:57:21 +08:00
ivmiku
bda83a1bfe feat: 完善相关功能 2024-09-14 13:54:30 +08:00
ivmiku
1dc7681ac6 Merge remote-tracking branch 'origin/main' 2024-09-14 13:53:32 +08:00
苏元皓
d4c2c87327 Merge branch 'main' of https://gitea.xinxijishubu.asia/iVMiku/guidance-backend 2024-09-13 22:32:44 +08:00
苏元皓
400a473143 test 2024-09-13 22:32:36 +08:00
ivmiku
262103e034 feat: 完善相关功能 2024-09-13 22:13:21 +08:00
ivmiku
4a39ca48ee feat: 用户信息修改接口 2024-09-09 17:45:18 +08:00
苏元皓
1397512e70 Merge branch 'main' of https://gitea.xinxijishubu.asia/iVMiku/guidance-backend 2024-09-05 14:07:06 +08:00
苏元皓
117ab31c00 修复bug 2024-09-05 14:06:57 +08:00
ivmiku
2a36709093 refactor: AI助手接口改为post 2024-09-04 22:25:57 +08:00
苏元皓
ee64bca6fd Merge branch 'main' of https://gitea.xinxijishubu.asia/iVMiku/guidance-backend 2024-09-03 17:52:50 +08:00
苏元皓
6bf5fe407f 修改配置文件 2024-09-03 17:52:38 +08:00
ivmiku
22b3cdf6ae fix: websocket网关转发设置 2024-09-03 17:19:18 +08:00
ivmiku
e4a343242f fix: 路径修改 2024-09-03 16:36:04 +08:00
ivmiku
d5fa59170a fix: 模块重载 2024-09-03 16:34:11 +08:00
苏元皓
5eb573fb9d 新增猜你喜欢 2024-09-02 17:42:24 +08:00
苏元皓
5dd7f7193f Merge branch 'main' of https://gitea.xinxijishubu.asia/iVMiku/guidance-backend 2024-09-01 16:56:35 +08:00
苏元皓
bde024527f 完善社区模块 2024-09-01 16:55:58 +08:00
ivmiku
df74c239ca feat: 删除链路追踪依赖 2024-09-01 11:18:40 +08:00
ivmiku
0ea3ac4534 Merge remote-tracking branch 'origin/main' 2024-08-30 19:48:56 +08:00
ivmiku
3921858291 feat: 优化的票扫描接口 2024-08-30 19:47:59 +08:00
65 changed files with 1188 additions and 252 deletions

View File

@@ -0,0 +1,14 @@
package com.ivmiku.tutorial.response;
import lombok.Data;
/**
* 提醒用户实体
* @author Aurora
*/
@Data
public class AtNotifier {
private String fromId;
private String toId;
private String postId;
}

View File

@@ -41,11 +41,12 @@
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Sa-Token 权限认证在线文档https://sa-token.cc -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.28.0</version>
<artifactId>sa-token-spring-boot3-starter</artifactId>
</dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
@@ -132,7 +133,6 @@
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-jackson</artifactId>
<version>1.38.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
@@ -190,6 +190,14 @@
<artifactId>commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
</dependencies>
<build>

View File

@@ -1,6 +1,7 @@
package com.ivmiku.tutorial.config;
import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@@ -11,7 +12,8 @@ public class SaTokenConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册注解拦截器,并排除不需要注解鉴权的接口地址 (与登录拦截器无关)
registry.addInterceptor(new SaAnnotationInterceptor())
.addPathPatterns("/**");
registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
.addPathPatterns("/**")
.excludePathPatterns("/swagger-ui/**");
}
}

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

@@ -2,22 +2,23 @@ package com.ivmiku.tutorial.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.stp.StpUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.pagehelper.PageInfo;
import com.ivmiku.tutorial.entity.Comment;
import com.ivmiku.tutorial.entity.Pages;
import com.ivmiku.tutorial.response.AtNotifier;
import com.ivmiku.tutorial.response.Result;
import com.ivmiku.tutorial.service.CommentService;
import com.ivmiku.tutorial.service.FileService;
import com.ivmiku.tutorial.util.MyRedisUtil;
import io.swagger.v3.oas.annotations.Operation;
import org.apiguardian.api.API;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.util.List;
/**
* 评论控制器用于处理评论相关的HTTP请求。
@@ -36,48 +37,36 @@ public class CommentController {
@Resource
private FileService fileService;
@Resource
private RabbitTemplate rabbitTemplate;
@Resource
private MyRedisUtil myRedisUtil;
/**
* 创建评论接口。
*
* @param content 评论内容
* @param postId 帖子的ID
* @param mentionedUserId 提及的用户ID可选
* @param imageFiles 图片文件(可选)
* @param videoFile 视频文件(可选)
* @return 返回操作结果
*/
@PostMapping("/create")
@Operation(summary = "创建评论")
public Result createComment(@RequestParam("content") String content,
@RequestParam("postId") Long postId,
@RequestParam(value = "postId", required = false) Long postId,
@RequestParam(value = "tutorialId", required = false) Long tutorialId,
@RequestParam(value = "mentionedUserId", required = false) String mentionedUserId,
@RequestParam(value = "imageFiles", required = false) MultipartFile[] imageFiles,
@RequestParam(value = "videoFile", required = false) MultipartFile videoFile) {
@RequestParam(value = "videoFile", required = false) MultipartFile videoFile,
@RequestParam("type") String type) {
logger.info("创建评论请求开始用户ID{}", StpUtil.getLoginIdAsString());
Comment comment = new Comment();
comment.setUserOpenid(StpUtil.getLoginIdAsString());
comment.setPostId(postId);
comment.setContent(content);
comment.setParentCommentId(0l);
comment.setMentionedUserId(mentionedUserId);
if (!isValidCommentType(type)) {
return Result.error("无效的评论类型");
}
Comment comment = createCommentObject(content, postId, tutorialId, mentionedUserId, type);
try {
// 上传视频
if (videoFile != null && !videoFile.isEmpty()) {
String uploadResult = fileService.uploadMinio(videoFile);
comment.setVideoUrl(uploadResult);
}
// 上传图片
if (imageFiles != null && imageFiles.length > 0) {
StringBuilder imageUrls = new StringBuilder();
for (MultipartFile file : imageFiles) {
String uploadResult = fileService.uploadMinio(file);
imageUrls.append(uploadResult + ";");
}
comment.setImageUrls(imageUrls.toString());
}
processFileUploads(comment, imageFiles, videoFile);
} catch (Exception e) {
logger.error("文件上传失败", e);
return Result.error("文件上传失败");
@@ -85,60 +74,47 @@ public class CommentController {
commentService.createComment(comment);
logger.info("评论创建成功评论ID{}", comment.getCommentId());
if (!mentionedUserId.isEmpty()) {
AtNotifier notifier = new AtNotifier();
notifier.setFromId(comment.getUserOpenid());
notifier.setToId(mentionedUserId);
notifier.setPostId(String.valueOf(comment.getCommentId()));
rabbitTemplate.convertAndSend("exchange2", "", JSON.toJSONString(notifier));
myRedisUtil.listAdd("at:" + notifier.getToId(), JSON.toJSONString(notifier));
}
return Result.ok(comment);
}
/**
* 回复评论接口。
*
* @param parentCommentId 父评论的ID
* @param postId 帖子的ID
* @param content 回复内容
* @param mentionedUserId 提及的用户ID可选
* @param imageFiles 图片文件(可选)
* @param videoFile 视频文件(可选)
* @return 返回操作结果
*/
@PostMapping("/reply")
@Operation(summary = "回复评论")
public Result replyComment(@RequestParam Long parentCommentId,
@RequestParam Long postId,
@RequestParam String content,
@RequestParam(required = false) String mentionedUserId,
@RequestParam("content") String content,
@RequestParam(value = "postId", required = false) Long postId,
@RequestParam(value = "tutorialId", required = false) Long tutorialId,
@RequestParam(value = "mentionedUserId", required = false) String mentionedUserId,
@RequestParam(value = "imageFiles", required = false) MultipartFile[] imageFiles,
@RequestParam(value = "videoFile", required = false) MultipartFile videoFile) {
@RequestParam(value = "videoFile", required = false) MultipartFile videoFile,
@RequestParam("type") String type) {
logger.info("回复评论请求开始用户ID{}", StpUtil.getLoginIdAsString());
// 检查父评论是否存在
Comment parentComment = commentService.getCommentById(parentCommentId);
if (parentComment == null) {
logger.warn("父评论ID{}不存在", parentCommentId);
return Result.error("父评论不存在");
}
Comment reply = new Comment();
reply.setUserOpenid(StpUtil.getLoginIdAsString());
if (!isValidCommentType(type)) {
return Result.error("无效的评论类型");
}
Comment reply = createCommentObject(content, postId, tutorialId, mentionedUserId, type);
reply.setParentCommentId(parentCommentId);
reply.setPostId(postId);
reply.setContent(content);
reply.setMentionedUserId(mentionedUserId);
try {
// 上传视频
if (videoFile != null && !videoFile.isEmpty()) {
String uploadResult = fileService.uploadMinio(videoFile);
reply.setVideoUrl(uploadResult);
}
// 上传图片
if (imageFiles != null && imageFiles.length > 0) {
StringBuilder imageUrls = new StringBuilder();
for (MultipartFile file : imageFiles) {
String uploadResult = fileService.uploadMinio(file);
imageUrls.append(uploadResult + ";");
}
reply.setImageUrls(imageUrls.toString());
}
processFileUploads(reply, imageFiles, videoFile);
} catch (Exception e) {
logger.error("文件上传失败", e);
return Result.error("文件上传失败");
@@ -146,9 +122,51 @@ public class CommentController {
commentService.createComment(reply);
logger.info("评论回复成功评论ID{}", reply.getCommentId());
if (!mentionedUserId.isEmpty()) {
AtNotifier notifier = new AtNotifier();
notifier.setFromId(reply.getUserOpenid());
notifier.setToId(mentionedUserId);
notifier.setPostId(String.valueOf(reply.getCommentId()));
rabbitTemplate.convertAndSend("exchange2", "", JSON.toJSONString(notifier));
myRedisUtil.listAdd("at:" + notifier.getToId(), JSON.toJSONString(notifier));
}
return Result.ok(reply);
}
// 工具方法:创建评论对象
private Comment createCommentObject(String content, Long postId, Long tutorialId, String mentionedUserId, String type) {
Comment comment = new Comment();
comment.setUserOpenid(StpUtil.getLoginIdAsString());
comment.setContent(content);
comment.setMentionedUserId(mentionedUserId);
if ("post".equals(type)) {
comment.setPostId(postId);
} else if ("tutorial".equals(type)) {
comment.setTutorialId(tutorialId);
}
return comment;
}
// 工具方法:处理文件上传
private void processFileUploads(Comment comment, MultipartFile[] imageFiles, MultipartFile videoFile) throws Exception {
if (videoFile != null && !videoFile.isEmpty()) {
comment.setVideoUrl(fileService.uploadMinio(videoFile));
}
if (imageFiles != null && imageFiles.length > 0) {
StringBuilder imageUrls = new StringBuilder();
for (MultipartFile file : imageFiles) {
imageUrls.append(fileService.uploadMinio(file)).append(";");
}
comment.setImageUrls(imageUrls.toString());
}
}
// 工具方法:校验评论类型
private boolean isValidCommentType(String type) {
return "post".equals(type) || "tutorial".equals(type);
}
/**
* 获取评论详情接口。
*
@@ -216,6 +234,21 @@ public class CommentController {
return Result.ok(comments);
}
/**
* 获取教程下的所有评论。ok
*
* @param tutorialId 帖子的唯一标识ID
* @return 返回操作结果和评论列表
*/
@PostMapping("/tutorial/{tutorialId}")
@Operation(summary = "获取教程下的所有评论")
public Result getTutorialComments(@PathVariable("tutorialId") Long tutorialId, @RequestBody Pages pages) {
logger.info("获取教程ID{}的评论列表", tutorialId);
logger.info("获取pageSize{}", pages.getPageSize());
IPage<Comment> comments = commentService.getTutorialComments(tutorialId, pages.getPageNum(), pages.getPageSize());
return Result.ok(comments);
}
/**
* 获取评论下的所有回复。
*
@@ -230,35 +263,35 @@ public class CommentController {
return Result.ok(replies);
}
/**
* @评论接口。
*
* @param comment 包含@信息的评论数据
* @return 返回操作结果
*/
@PostMapping("/mention")
@Operation(summary = "@评论")
public Result mentionUser(@RequestBody Comment comment) {
logger.info("用户ID{}提交@评论请求", StpUtil.getLoginIdAsString());
// 检查父评论是否存在如果有父评论ID
if (comment.getParentCommentId() != null) {
Comment parentComment = commentService.getCommentById(comment.getParentCommentId());
if (parentComment == null) {
logger.warn("父评论ID{}不存在", comment.getParentCommentId());
return Result.error("父评论不存在");
}
}
// 检查帖子ID是否存在
if (comment.getPostId() == null) {
logger.warn("帖子ID不能为空");
return Result.error("帖子ID不能为空");
}
comment.setUserOpenid(StpUtil.getLoginIdAsString());
commentService.createComment(comment);
logger.info("@评论创建成功评论ID{}", comment.getCommentId());
return Result.ok();
}
// /**
// * @评论接口。
// *
// * @param comment 包含@信息的评论数据
// * @return 返回操作结果
// */
// @PostMapping("/mention")
// @Operation(summary = "@评论")
// public Result mentionUser(@RequestBody Comment comment) {
// logger.info("用户ID{}提交@评论请求", StpUtil.getLoginIdAsString());
//
// // 检查父评论是否存在如果有父评论ID
// if (comment.getParentCommentId() != null) {
// Comment parentComment = commentService.getCommentById(comment.getParentCommentId());
// if (parentComment == null) {
// logger.warn("父评论ID{}不存在", comment.getParentCommentId());
// return Result.error("父评论不存在");
// }
// }
//
// // 检查帖子ID是否存在
// if (comment.getPostId() == null) {
// logger.warn("帖子ID不能为空");
// return Result.error("帖子ID不能为空");
// }
//
// comment.setUserOpenid(StpUtil.getLoginIdAsString());
// commentService.createComment(comment);
// logger.info("@评论创建成功评论ID{}", comment.getCommentId());
// return Result.ok();
// }
}

View File

@@ -4,7 +4,10 @@ import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.lang.hash.Hash;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ivmiku.tutorial.entity.Pages;
import com.ivmiku.tutorial.entity.Post;
import com.ivmiku.tutorial.entity.Tutorials;
import com.ivmiku.tutorial.response.Result;
import com.ivmiku.tutorial.service.InteractionService;
import io.swagger.v3.oas.annotations.Operation;
@@ -124,10 +127,10 @@ public class InteractionController {
*/
@GetMapping("/getFavoritePosts")
@Operation(summary = "获取用户收藏的帖子")
public Result getFavoritePosts() {
public Result getFavoritePosts(@RequestBody Pages pages) {
String userOpenid = StpUtil.getLoginIdAsString();
log.info("用户 {} 正在获取收藏的帖子", userOpenid);
IPage<Post> favoritePost = interactionService.getFavoritePosts(userOpenid);
IPage<Post> favoritePost = interactionService.getFavoritePosts(userOpenid, pages.getPageNum(), pages.getPageSize());
HashMap<String, Object> data = new HashMap<>();
if (favoritePost != null) {
log.info("用户 {} 收藏的帖子获取成功", userOpenid);
@@ -149,4 +152,75 @@ public class InteractionController {
HashMap<String, Object> res = interactionService.getLikeCount(userOpenid);
return Result.ok();
}
/**
* 对官方教程点赞
* @param tutorialId 教程ID
* @return 操作结果
*/
@PostMapping("/likeOfficialTutorial")
@Operation(summary = "对官方教程点赞")
public Result likeOfficialTutorial(@RequestParam Long tutorialId) {
String userOpenid = StpUtil.getLoginIdAsString();
log.info("用户 {} 正在对官方教程 {} 点赞", userOpenid, tutorialId);
interactionService.likeOfficialTutorial(userOpenid, tutorialId);
return Result.ok();
}
/**
* 收藏官方教程
* @param tutorialId 教程ID
* @return 操作结果
*/
@PostMapping("/favoriteOfficialTutorial")
@Operation(summary = "收藏官方教程")
public Result favoriteOfficialTutorial(@RequestParam Long tutorialId) {
String userOpenid = StpUtil.getLoginIdAsString();
log.info("用户 {} 正在收藏官方教程 {}", userOpenid, tutorialId);
interactionService.favoriteOfficialTutorial(userOpenid, tutorialId);
return Result.ok();
}
/**
* 取消对官方教程的点赞
* @param tutorialId 教程ID
* @return 操作结果
*/
@DeleteMapping("/unlikeOfficialTutorial")
@Operation(summary = "取消对官方教程的点赞")
public Result unlikeOfficialTutorial(@RequestParam Long tutorialId) {
String userOpenid = StpUtil.getLoginIdAsString();
log.info("用户 {} 正在取消对官方教程 {} 的点赞", userOpenid, tutorialId);
interactionService.unlikeOfficialTutorial(userOpenid, tutorialId);
return Result.ok();
}
/**
* 取消收藏官方教程
* @param tutorialId 教程ID
* @return 操作结果
*/
@DeleteMapping("/unfavoriteOfficialTutorial")
@Operation(summary = "取消收藏官方教程")
public Result unfavoriteOfficialTutorial(@RequestParam Long tutorialId) {
String userOpenid = StpUtil.getLoginIdAsString();
log.info("用户 {} 正在取消收藏官方教程 {}", userOpenid, tutorialId);
interactionService.unfavoriteOfficialTutorial(userOpenid, tutorialId);
return Result.ok();
}
/**
* 获取用户收藏的官方教程
* @return 用户收藏的官方教程列表
*/
@GetMapping("/getFavoriteOfficialTutorials")
@Operation(summary = "获取用户收藏的官方教程")
public Result getFavoriteOfficialTutorials(@RequestBody Pages pages) {
String userOpenid = StpUtil.getLoginIdAsString();
log.info("用户 {} 正在获取收藏的官方教程", userOpenid);
IPage<Tutorials> favoriteTutorials = interactionService.getFavoriteOfficialTutorials(userOpenid, pages.getPageNum(), pages.getPageSize());
HashMap<String, Object> data = new HashMap<>();
data.put("list", favoriteTutorials);
return Result.ok(data);
}
}

View File

@@ -1,6 +1,7 @@
package com.ivmiku.tutorial.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.pagehelper.PageInfo;
@@ -47,11 +48,12 @@ public class PostController {
@Operation(summary = "创建帖子")
public Result createPost(@RequestParam("title") String title,
@RequestParam("content") String content,
@RequestParam("communityId") Long communityId,
@RequestParam("imageFiles") MultipartFile[] imageFiles,
@RequestParam("videoFile") MultipartFile videoFile,
@RequestParam(value = "communityId") Long communityId,
@RequestParam(value = "imageFiles", required = false) MultipartFile[] imageFiles,
@RequestParam(value = "videoFile", required = false) MultipartFile videoFile,
@RequestParam("isPublic") Integer isPublic,
@RequestParam("location") String location) {
@RequestParam(value = "location", required = false) String location) {
System.out.println("Aaa");
String userId = StpUtil.getLoginIdAsString();
logger.info("用户ID{}开始创建帖子", userId);
@@ -221,6 +223,23 @@ public class PostController {
return Result.ok(history);
}
/**
* 删除用户的搜索历史
*/
@DeleteMapping("/deleteSearchHistory")
@Operation(summary = "删除用户的搜索历史")
public Result deleteSearchHistory() {
String userId = StpUtil.getLoginIdAsString(); // 获取当前登录用户的ID
String key = "search_history:" + userId;
// 删除Redis中的搜索历史
redisTemplate.delete(key);
logger.info("用户ID{}的搜索历史已删除", userId);
return Result.ok("搜索历史已删除");
}
/**
* 根据关键字搜索帖子,包括标题和内容
*/
@@ -261,7 +280,7 @@ public class PostController {
post.setTitle(title);
post.setContent(content);
post.setCommunityId(communityId);
post.setIsDraft(0); // 0 表示草稿
post.setIsDraft(1); // 0 表示草稿
post.setLocation(location);
try {
@@ -304,6 +323,24 @@ public class PostController {
return Result.ok(drafts);
}
/**
* 猜你喜欢
*/
@GetMapping("/guessYouLike")
@Operation(summary = "猜你喜欢")
public Result guessYouLike(@RequestParam("latitude") Double latitude,
@RequestParam("longitude") Double longitude) {
String userId = StpUtil.getLoginIdAsString();
String key = "search_history:" + userId;
// 获取用户的搜索历史关键词
List<String> searchHistory = redisTemplate.opsForList().range(key, 0, -1);
// 获取猜你喜欢的帖子列表
List<Post> recommendedPosts = postService.getRecommendedPosts(latitude, longitude, searchHistory);
return Result.ok(recommendedPosts);
}
}

View File

@@ -38,7 +38,7 @@ public class TutorialController {
@RequestParam(value = "imageFile", required = false) MultipartFile imageFile,
@RequestParam(value = "videoFile", required = false) MultipartFile videoFile,
@RequestParam("isOfficial") Integer isOfficial) {
String userId = StpUtil.getLoginIdAsString(); // 获取 userOpenid
String userId = (String) StpUtil.getLoginId(); // 获取 userOpenid
logger.info("用户ID{} 开始创建教程", userId);
Tutorials tutorial = new Tutorials();
@@ -77,7 +77,7 @@ public class TutorialController {
public Result getTutorial(@PathVariable Long id) {
Tutorials tutorial = tutorialsService.getById(id);
if (tutorial != null) {
return Result.ok("教程获取成功");
return Result.ok(tutorial);
}
return Result.error("未找到教程");
}
@@ -110,7 +110,7 @@ public class TutorialController {
@Operation(summary = "获取所有教程")
public Result listTutorials() {
List<Tutorials> tutorials = tutorialsService.list();
return Result.ok("教程列表获取成功");
return Result.ok(tutorials);
}
// 根据教程标签ID获取教程列表
@GetMapping("/listByTag/{tagId}")

View File

@@ -0,0 +1,80 @@
package com.ivmiku.tutorial.controller;
import cn.dev33.satoken.stp.StpUtil;
import com.alibaba.fastjson.JSON;
import com.ivmiku.tutorial.entity.UserDTO;
import com.ivmiku.tutorial.response.Result;
import com.ivmiku.tutorial.service.impl.SearchService;
import jakarta.websocket.*;
import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Controller;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Controller
@ServerEndpoint(value = "/search/{satoken}")
public class WebSocketServer implements ApplicationContextAware {
public static Map<String, Session> sessionMap = new ConcurrentHashMap<>();
private static ApplicationContext applicationContext;
private SearchService searchService;
@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();
}
sessionMap.put(userId, session);
this.searchService = WebSocketServer.applicationContext.getBean(SearchService.class);
}
@OnClose
public void onClose(CloseReason closeReason, Session session){
sessionMap.remove(session.getId());
}
@OnError
public void onError(Throwable throwable) throws IOException {
throwable.printStackTrace();
}
@OnMessage
public void onMessage(String message, Session session) throws IOException {
char first = message.charAt(0);
String userId = null;
for (Map.Entry<String, Session> entry : sessionMap.entrySet()) {
if (entry.getValue().equals(session)) {
userId = entry.getKey();
}
}
switch (first) {
case '@' -> {
String sub = message.substring(1);
List<UserDTO> result = searchService.getUserList(sub, userId);
session.getBasicRemote().sendText(JSON.toJSONString(Result.ok(result)));
}
case '#' -> {
String sub = message.substring(1);
List<String> list = searchService.getTag(sub);
session.getBasicRemote().sendText(JSON.toJSONString(Result.ok(list)));
}
//预留给社区搜索
default -> {
}
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
WebSocketServer.applicationContext = applicationContext;
}
}

View File

@@ -21,6 +21,9 @@ public class Comment implements Serializable {
private Long postId;
@TableField("tutorialId")
private Long tutorialId; // 新增字段
private String content;
private Long parentCommentId;

View File

@@ -22,5 +22,7 @@ public class Favorite implements Serializable {
private Integer isDeleted;
private Long tutorialId; // 新增字段
private static final long serialVersionUID = 1L;
}

View File

@@ -20,6 +20,8 @@ public class Likee implements Serializable {
private Long commentId;
private Long tutorialId; // 新增字段
private Integer isDeleted;
private static final long serialVersionUID = 1L;

View File

@@ -44,4 +44,6 @@ public class Post implements Serializable {
private Date updatedAt;
private static final long serialVersionUID = 1L;
}

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

@@ -4,11 +4,15 @@ import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@TableName("user")
@Data
@TableName("User")
public class User {
@TableId
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,12 @@
package com.ivmiku.tutorial.entity;
import lombok.Data;
@Data
public class UserDTO {
private String nickname;
private String avatarUrl;
private String phoneNum;
private String birthday;
private String gender;
}

View File

@@ -14,13 +14,16 @@ import org.apache.ibatis.annotations.Select;
public interface LikeMapper extends BaseMapper<Likee> {
// 获取帖子获赞数
@Select("SELECT SUM(like_count) FROM posts WHERE user_openid = #{userOpenid}")
Long getPostLikeCount(@Param("userOpenid") String userOpenid);
// 获取评论获赞数
@Select("SELECT SUM(like_count) FROM comments WHERE user_openid = #{userOpenid}")
Long getCommentLikeCount(@Param("userOpenid") String userOpenid);
// 查询帖子被点赞的总数
Long getLikeCountByPostId(@Param("postId") Long postId);
}

View File

@@ -23,6 +23,12 @@ public interface PostMapper extends BaseMapper<Post> {
List<Post> searchPosts(String keyword);
List<Post> getTopFavoritedPosts(int limit);
List<Post> getPostsBasedOnHistoryAndHotness(Long userId, List<String> searchHistory);
@Select("SELECT * FROM post WHERE ST_Distance_Sphere(point(location_lng, location_lat), point(#{longitude}, #{latitude})) < 50000") // 50km 范围
List<Post> getPostsNearby(Double latitude, Double longitude);
}

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,7 @@
package com.ivmiku.tutorial.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ivmiku.tutorial.entity.User;
public interface UserMapper extends BaseMapper<User> {
}

View File

@@ -68,5 +68,5 @@ public interface CommentService extends IService<Comment> {
void createReply(Long parentCommentId, Long postId, String content, String mentionedUserId);
IPage<Comment> getTutorialComments(Long tutorialId, Integer pageNum, Integer pageSize);
}

View File

@@ -2,6 +2,7 @@ package com.ivmiku.tutorial.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.ivmiku.tutorial.entity.Post;
import com.ivmiku.tutorial.entity.Tutorials;
import java.util.HashMap;
import java.util.List;
@@ -17,7 +18,19 @@ public interface InteractionService {
void likeComment(String userOpenid, Long commentId);
void unlikeComment(String userOpenid, Long commentId);
IPage<Post> getFavoritePosts(String userOpenid);
IPage<Post> getFavoritePosts(String userOpenid, int pageNum, int pageSize);
HashMap<String, Object> getLikeCount(String userOpenid);
void likeOfficialTutorial(String userOpenid, Long tutorialId);
void favoriteOfficialTutorial(String userOpenid, Long tutorialId);
void unlikeOfficialTutorial(String userOpenid, Long tutorialId);
void unfavoriteOfficialTutorial(String userOpenid, Long tutorialId);
IPage<Tutorials> getFavoriteOfficialTutorials(String userOpenid, int pageNum, int pageSize);
// 新增方法:根据帖子 ID 获取点赞总数
Long getLikeCountByPostId(Long postId);
}

View File

@@ -31,4 +31,8 @@ public interface PostService extends IService<Post> {
List<Post> getTopFavoritedPosts(int limit);
List<Post> getDraftsByUserId(String userId);
List<Post> getRecommendedPosts(Double latitude, Double longitude, List<String> searchHistory);
}

View File

@@ -15,4 +15,6 @@ public interface TutorialsService extends IService<Tutorials> {
void createTutorial(Tutorials tutorial);
List<Tutorials> getTutorialsByTagId(Long tagId);
List<Tutorials> getTutorialsByIds(List<Long> tutorialIds);
}

View File

@@ -1,6 +1,7 @@
package com.ivmiku.tutorial.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -8,10 +9,13 @@ import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.ivmiku.tutorial.entity.Comment;
import com.ivmiku.tutorial.mapper.CommentMapper;
import com.ivmiku.tutorial.response.AtNotifier;
import com.ivmiku.tutorial.service.CommentService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -30,6 +34,9 @@ public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> impl
@Autowired
protected CommentMapper commentMapper;
@Resource
private RabbitTemplate rabbitTemplate;
/**
* 保存评论。
* 将评论数据保存到数据库。
@@ -186,8 +193,38 @@ public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> impl
reply.setMentionedUserId(mentionedUserId);
}
createComment(reply);
if (mentionedUserId != null) {
AtNotifier notifier = new AtNotifier();
notifier.setFromId(reply.getUserOpenid());
notifier.setToId(mentionedUserId);
notifier.setPostId(String.valueOf(reply.getCommentId()));
rabbitTemplate.convertAndSend("exchange2", "", JSON.toJSONString(notifier));
}
logger.info("评论回复成功评论ID{}", reply.getCommentId());
}
@Override
public IPage<Comment> getTutorialComments(Long tutorialId, Integer pageNum, Integer pageSize) {
logger.info("开始获取帖子ID{}的评论列表,第{}页,每页{}条", tutorialId, pageNum, pageSize);
// 设置分页参数
// PageHelper.startPage(pageNum, pageSize);
IPage<Comment> page = new Page<>(pageNum, pageSize);
// 查询评论
LambdaQueryWrapper<Comment> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Comment::getTutorialId, tutorialId).eq(Comment::getIsDeleted, 0);
IPage<Comment> comments = commentMapper.selectPage(page, wrapper);
if (comments != null) {
logger.info("获取教程ID{}的评论列表成功", tutorialId);
} else {
logger.warn("教程ID{}的评论列表为空", tutorialId);
}
// 使用 PageInfo 包装结果
return comments;
}
}

View File

@@ -3,18 +3,22 @@ package com.ivmiku.tutorial.service.impl;
import cn.hutool.core.lang.hash.Hash;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ivmiku.tutorial.entity.Favorite;
import com.ivmiku.tutorial.entity.Likee;
import com.ivmiku.tutorial.entity.Post;
import com.ivmiku.tutorial.entity.Tutorials;
import com.ivmiku.tutorial.mapper.FavoriteMapper;
import com.ivmiku.tutorial.mapper.LikeMapper;
import com.ivmiku.tutorial.service.InteractionService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ivmiku.tutorial.service.PostService;
import com.ivmiku.tutorial.service.TutorialsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -34,6 +38,9 @@ public class InteractionServiceImpl implements InteractionService {
@Resource
private PostService postService;
@Resource
private TutorialsService tutorialService;
@Override
public void favoritePost(String userOpenid, Long postId) {
log.info("User {} is favoriting post {}", userOpenid, postId);
@@ -107,13 +114,13 @@ public class InteractionServiceImpl implements InteractionService {
}
@Override
public IPage<Post> getFavoritePosts(String userOpenid) {
public IPage<Post> getFavoritePosts(String userOpenid, int pageNum, int pageSize) {
LambdaQueryWrapper<Favorite> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Favorite::getUserOpenid, userOpenid);
List<Favorite> favorites = favoriteMapper.selectList(wrapper);
if (favorites != null && !favorites.isEmpty()) {
List<Long> postIds = favorites.stream().map(Favorite::getPostId).toList();
return postService.getPostList(userOpenid, 1, 10);
return postService.getPostList(userOpenid, pageNum, pageSize);
}
return null;
}
@@ -157,8 +164,97 @@ public class InteractionServiceImpl implements InteractionService {
} else {
res.put("favoriteCount", 0L); // 如果为空设置为0
}
Long myTotalPost = getFavoritePosts(userOpenid).getTotal();
res.put("myTotalPost", myTotalPost);
return res;
}
@Override
public void likeOfficialTutorial(String userOpenid, Long tutorialId) {
log.info("User {} is liking tutorial {}", userOpenid, tutorialId);
Likee likee = new Likee();
likee.setUserOpenid(userOpenid);
likee.setTutorialId(tutorialId);
likee.setIsDeleted(0);
likeMapper.insert(likee);
}
@Override
public void favoriteOfficialTutorial(String userOpenid, Long tutorialId) {
log.info("User {} is favoriting tutorial {}", userOpenid, tutorialId);
Favorite favorite = new Favorite();
favorite.setUserOpenid(userOpenid);
favorite.setTutorialId(tutorialId);
favorite.setIsDeleted(0);
favoriteMapper.insert(favorite);
}
@Override
public void unlikeOfficialTutorial(String userOpenid, Long tutorialId) {
log.info("User {} is unliking tutorial {}", userOpenid, tutorialId);
QueryWrapper<Likee> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_openid", userOpenid)
.eq("tutorial_id", tutorialId)
.eq("is_deleted", 0);
Likee likee = likeMapper.selectOne(queryWrapper);
if (likee != null) {
likee.setIsDeleted(1);
likeMapper.updateById(likee);
}
}
@Override
public void unfavoriteOfficialTutorial(String userOpenid, Long tutorialId) {
log.info("User {} is unfavoriting tutorial {}", userOpenid, tutorialId);
QueryWrapper<Favorite> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_openid", userOpenid)
.eq("tutorial_id", tutorialId)
.eq("is_deleted", 0);
Favorite favorite = favoriteMapper.selectOne(queryWrapper);
if (favorite != null) {
favorite.setIsDeleted(1);
favoriteMapper.updateById(favorite);
}
}
@Override
public IPage<Tutorials> getFavoriteOfficialTutorials(String userOpenid, int pageNum, int pageSize) {
// 创建分页对象
IPage<Favorite> page = new Page<>(pageNum, pageSize);
// 创建查询条件,筛选出用户收藏的且未被删除的教程
LambdaQueryWrapper<Favorite> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Favorite::getUserOpenid, userOpenid)
.isNotNull(Favorite::getTutorialId) // 确保教程ID不为空
.eq(Favorite::getIsDeleted, 0); // 只获取未删除的收藏记录
// 分页查询收藏的教程ID
IPage<Favorite> favoritePage = favoriteMapper.selectPage(page, wrapper);
// 提取出所有收藏的教程ID
List<Long> tutorialIds = favoritePage.getRecords().stream()
.map(Favorite::getTutorialId)
.toList();
if (tutorialIds.isEmpty()) {
return new Page<>(); // 如果没有收藏记录,返回空的分页对象
}
// 通过教程ID列表查询对应的教程实体并将分页结果映射到教程分页
List<Tutorials> tutorials = tutorialService.getTutorialsByIds(tutorialIds);
// 创建一个新的分页对象,用于返回教程数据
IPage<Tutorials> tutorialPage = new Page<>(pageNum, pageSize);
tutorialPage.setRecords(tutorials);
tutorialPage.setTotal(favoritePage.getTotal()); // 设置总记录数
return tutorialPage;
}
@Override
public Long getLikeCountByPostId(Long postId) {
log.info("Fetching like count for post {}", postId);
return likeMapper.getLikeCountByPostId(postId);
}
}

View File

@@ -5,17 +5,24 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ivmiku.tutorial.entity.BrowingHistory;
import com.ivmiku.tutorial.entity.Post;
import com.ivmiku.tutorial.mapper.CommentMapper;
import com.ivmiku.tutorial.mapper.FavoriteMapper;
import com.ivmiku.tutorial.mapper.LikeMapper;
import com.ivmiku.tutorial.mapper.PostMapper;
import com.ivmiku.tutorial.service.BrowingHistoryService;
import com.ivmiku.tutorial.service.InteractionService;
import com.ivmiku.tutorial.service.PostService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import java.sql.Timestamp;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class PostServiceImpl extends ServiceImpl<PostMapper, Post> implements PostService {
@@ -25,6 +32,10 @@ public class PostServiceImpl extends ServiceImpl<PostMapper, Post> implements Po
@Autowired
private PostMapper postMapper;
@Lazy
@Autowired
private InteractionService interactionService;
@Autowired
private BrowingHistoryService browingHistoryService;
@@ -193,9 +204,40 @@ public class PostServiceImpl extends ServiceImpl<PostMapper, Post> implements Po
public List<Post> getDraftsByUserId(String userId) {
return postMapper.selectList(new LambdaQueryWrapper<Post>()
.eq(Post::getUserOpenid, userId)
.eq(Post::getIsDraft, 0)); // 0 表示草稿
.eq(Post::getIsDraft, 1)); // 0 表示草稿
}
@Override
public List<Post> getRecommendedPosts(Double latitude, Double longitude, List<String> searchHistory) {
// 1. 根据用户当前位置计算距离(例如使用 Haversine 公式)
List<Post> nearbyPosts = postMapper.getPostsNearby(latitude, longitude);
// 2. 根据帖子热度排序
List<Post> sortedPosts = sortPostsByHeat(nearbyPosts);
// 3. 根据用户的搜索历史关键词筛选相关帖子
List<Post> matchingPosts = sortedPosts.stream()
.filter(post -> searchHistory.stream().anyMatch(keyword ->
post.getTitle().contains(keyword) || post.getContent().contains(keyword)))
.collect(Collectors.toList());
return matchingPosts;
}
public List<Post> sortPostsByHeat(List<Post> posts) {
// 根据帖子热度进行排序
return posts.stream()
.sorted((post1, post2) -> {
int post1Heat = calculatePostHeat(post1);
int post2Heat = calculatePostHeat(post2);
return Integer.compare(post2Heat, post1Heat); // 降序排列
})
.collect(Collectors.toList());
}
public int calculatePostHeat(Post post) {
Long postId = post.getPostId();
return interactionService.getLikeCountByPostId(postId).intValue();
}
}

View File

@@ -0,0 +1,58 @@
package com.ivmiku.tutorial.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ivmiku.tutorial.entity.Subscribe;
import com.ivmiku.tutorial.entity.Tag;
import com.ivmiku.tutorial.entity.User;
import com.ivmiku.tutorial.entity.UserDTO;
import com.ivmiku.tutorial.mapper.PostTagMapper;
import com.ivmiku.tutorial.mapper.SubscribeMapper;
import com.ivmiku.tutorial.mapper.TagMapper;
import com.ivmiku.tutorial.mapper.UserMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
@Service
public class SearchService {
@Resource
private UserMapper userMapper;
@Resource
private TagMapper tagMapper;
@Resource
private SubscribeMapper subscribeMapper;
public List<UserDTO> getUserList(String text, String userId) {
int len = text.length();
QueryWrapper<Subscribe> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user1_id", userId);
List<Subscribe> list = subscribeMapper.selectList(queryWrapper);
List<UserDTO> result = new ArrayList<>();
for (Subscribe subscribe : list) {
String user2Id = String.valueOf(subscribe.getSubId());
User user = userMapper.selectById(user2Id);
if (user.getNickname().substring(0, len).equals(text)) {
UserDTO userDTO = new UserDTO();
userDTO.setNickname(user.getNickname());
userDTO.setAvatarUrl(user.getAvatarUrl());
result.add(userDTO);
}
}
return result;
}
public List<String> getTag(String text) {
QueryWrapper<Tag> queryWrapper = new QueryWrapper<>();
queryWrapper.likeRight("name", text);
List<Tag> list = tagMapper.selectList(queryWrapper);
List<String> result = new ArrayList<>();
for (Tag tag : list) {
result.add(tag.getName());
}
return result;
}
}

View File

@@ -31,6 +31,16 @@ public class TutorialsServiceImpl extends ServiceImpl<TutorialsMapper, Tutorials
return tutorialsMapper.selectList(queryWrapper);
}
@Override
public List<Tutorials> getTutorialsByIds(List<Long> tutorialIds) {
if (tutorialIds != null && !tutorialIds.isEmpty()) {
QueryWrapper<Tutorials> queryWrapper = new QueryWrapper<>();
queryWrapper.in("tutorial_id", tutorialIds);
return tutorialsMapper.selectList(queryWrapper);
}
return null;
}
@Override
public void createTutorial(Tutorials tutorial) {
this.save(tutorial);

View File

@@ -0,0 +1,94 @@
package com.ivmiku.tutorial.util;
import com.alibaba.fastjson2.JSON;
import com.ivmiku.tutorial.response.AtNotifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @author Aurora
*/
@Component
public class MyRedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void listAdd(String key, Object value) {
redisTemplate.opsForList().leftPush(key, value);
}
public List<AtNotifier> listGet(String key, int s, int e) {
List<Object> list = redisTemplate.opsForList().range(key, s, e);
List<AtNotifier> result = new ArrayList<>();
if (list != null) {
for (Object json : list) {
result.add(JSON.parseObject(JSON.toJSONString(json), AtNotifier.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 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(AtNotifier message) {
String id = message.getToId();
redisTemplate.opsForList().remove("unread:" + id, 0, message);
}
}

View File

@@ -1,9 +1,11 @@
wx.miniapp.configs[0].appid=wx0d4fdb5c7bf3b12b
wx.miniapp.configs[0].secret=989f155fcc3aee616568473faf1b1d3b
spring.data.redis.host=127.0.0.1
spring.data.redis.host=redis
spring.data.redis.port=6379
spring.data.redis.database=0
spring.data.redis.password=Shuodedaoli114514
spring.application.name=community
@@ -11,14 +13,25 @@ server.port=8073
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/tutorial?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.password=Shuodedaoli114514
spring.datasource.url=jdbc:mysql://mysql:3306/tutorial?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cloud.nacos.discovery.server-addr=nacos:8848
spring.cloud.nacos.discovery.enabled=true
management.zipkin.tracing.endpoint=http://127.0.0.1:9411/api/v2/spans
management.tracing.sampling.probability=1.0
dubbo.application.qos-enable=false
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
minio.endpoint=120.26.243.81
minio.port=9000
minio.accessKey=minio_root
minio.secretKey=minio_123456
minio.bucketName=haixia
spring.data.redis.lettuce.pool.max-active=32
spring.data.redis.lettuce.pool.max-idle=16
spring.data.redis.lettuce.pool.min-idle=8
spring.rabbitmq.host=rabbitmq

View File

@@ -8,6 +8,7 @@
<id property="commentId" column="comment_id" jdbcType="BIGINT"/>
<result property="userOpenid" column="user_openid" jdbcType="VARCHAR"/>
<result property="postId" column="post_id" jdbcType="BIGINT"/>
<result property="tutorialId" column="tutorialId" jdbcType="BIGINT"/>
<result property="content" column="content" jdbcType="VARCHAR"/>
<result property="parentCommentId" column="parent_comment_id" jdbcType="BIGINT"/>
<result property="isDeleted" column="is_deleted" jdbcType="TINYINT"/>
@@ -19,7 +20,7 @@
</resultMap>
<sql id="Base_Column_List">
comment_id, user_openid, post_id, content,
comment_id, user_openid, post_id, tutorialId, content,
parent_comment_id, is_deleted, created_at, updated_at,
mentioned_user_id, image_urls, video_url
</sql>

View File

@@ -8,11 +8,12 @@
<id property="id" column="id" jdbcType="BIGINT"/>
<result property="userOpenid" column="user_openid" jdbcType="VARCHAR"/>
<result property="postId" column="post_id" jdbcType="BIGINT"/>
<result property="tutorialId" column="tutorialId" jdbcType="BIGINT"/>
<result property="isDeleted" column="is_deleted" jdbcType="TINYINT"/>
</resultMap>
<sql id="Base_Column_List">
id,user_openid,post_id,
id,user_openid, post_id, tutorialId,
is_deleted
</sql>
</mapper>

View File

@@ -5,15 +5,33 @@
<mapper namespace="com.ivmiku.tutorial.mapper.LikeMapper">
<resultMap id="BaseResultMap" type="com.ivmiku.tutorial.entity.Likee">
<id property="id" column="id" jdbcType="BIGINT"/>
<result property="userOpenid" column="user_openid" jdbcType="VARCHAR"/>
<result property="postId" column="post_id" jdbcType="BIGINT"/>
<result property="commentId" column="comment_id" jdbcType="BIGINT"/>
<result property="isDeleted" column="is_deleted" jdbcType="TINYINT"/>
<id property="id" column="id" jdbcType="BIGINT"/>
<result property="userOpenid" column="user_openid" jdbcType="VARCHAR"/>
<result property="postId" column="post_id" jdbcType="BIGINT"/>
<result property="tutorialId" column="tutorial_id" jdbcType="BIGINT"/>
<result property="commentId" column="comment_id" jdbcType="BIGINT"/>
<result property="isDeleted" column="is_deleted" jdbcType="TINYINT"/>
</resultMap>
<sql id="Base_Column_List">
id,user_openid,post_id,
comment_id,is_deleted
id, user_openid, post_id, tutorial_id, comment_id, is_deleted
</sql>
<!-- 查询用户所有帖子的点赞总数 -->
<select id="getPostLikeCount" resultType="java.lang.Long">
SELECT COUNT(*)
FROM likee
WHERE user_openid = #{userOpenid}
AND post_id IS NOT NULL
AND is_deleted = 0;
</select>
<!-- 查询用户所有评论的点赞总数 -->
<select id="getCommentLikeCount" resultType="java.lang.Long">
SELECT COUNT(*)
FROM likee
WHERE user_openid = #{userOpenid}
AND comment_id IS NOT NULL
AND is_deleted = 0;
</select>
</mapper>

View File

@@ -86,7 +86,6 @@
SELECT title
FROM post
WHERE is_deleted = 0 AND is_public = 1
ORDER BY view_count DESC, like_count DESC
LIMIT 9
</select>
<select id="searchPosts" resultType="com.ivmiku.tutorial.entity.Post">
@@ -108,5 +107,11 @@
ORDER BY COUNT(f.id) DESC
LIMIT #{limit}
</select>
<select id="getPostsBasedOnHistoryAndHotness" resultType="com.ivmiku.tutorial.entity.Post">
SELECT *
FROM post
WHERE content LIKE CONCAT('%', #{searchHistory}, '%')
ORDER BY hotness DESC
</select>
</mapper>

View File

@@ -56,26 +56,6 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!--micrometer-tracing指标追踪 1-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing</artifactId>
</dependency>
<!--micrometer-tracing-bridge-brave适配zipkin的桥接包 2-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<!--micrometer-observation 3-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
</dependency>
<!--zipkin-reporter-brave 5-->
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>

View File

@@ -3,9 +3,7 @@ package com.ivmiku.tutorial;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(exclude = {
org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinAutoConfiguration.class
})
@SpringBootApplication
public class Main8133 {
public static void main(String[] args) {
SpringApplication.run(Main8133.class, args);

View File

@@ -47,7 +47,8 @@ public class SaTokenConfigure {
"/user/register",
"/swagger-resources/**",
"/v3/**",
"/swagger-ui/**")
"/swagger-ui/**",
"/ws/**")
// 鉴权方法:每次访问进入
.setAuth(obj -> {
// 登录校验 -- 拦截所有路由,并排除指定路由

View File

@@ -1,16 +1,32 @@
server.port=8133
spring.application.name=gateway
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cloud.nacos.discovery.server-addr=nacos:8848
spring.data.redis.host=127.0.0.1
spring.data.redis.host=redis
spring.data.redis.port=6379
spring.data.redis.database=0
#spring.data.redis.password=Shuodedaoli114514
spring.data.redis.password=Shuodedaoli114514
spring.cloud.gateway.routes[0].id=user
spring.cloud.gateway.routes[0].uri=lb://user
spring.cloud.gateway.routes[0].predicates[0]=Path=/user/**
spring.cloud.gateway.routes[0].filters[0]=StripPrefix=1
management.zipkin.tracing.endpoint=http://127.0.0.1:9411/api/v2/spans
management.tracing.sampling.probability=1.0
spring.cloud.gateway.routes[1].id=community
spring.cloud.gateway.routes[1].uri=lb://community
spring.cloud.gateway.routes[1].predicates[0]=Path=/community/**
spring.cloud.gateway.routes[1].filters[0]=StripPrefix=1
spring.cloud.gateway.routes[2].id=navigate
spring.cloud.gateway.routes[2].uri=lb://navigate
spring.cloud.gateway.routes[2].predicates[0]=Path=/navigate/**
spring.cloud.gateway.routes[2].filters[0]=StripPrefix=1
spring.cloud.gateway.routes[3].id=websocket
spring.cloud.gateway.routes[3].uri=lb:ws://user
spring.cloud.gateway.routes[3].predicates[0]=Path=/chat/**
spring.cloud.gateway.routes[4].id=search
spring.cloud.gateway.routes[4].uri=lb:ws://community
spring.cloud.gateway.routes[4].predicates[0]=Path=/search/**

View File

@@ -17,5 +17,15 @@ spring.cloud.gateway.routes[1].uri=lb://community
spring.cloud.gateway.routes[1].predicates[0]=Path=/community/**
spring.cloud.gateway.routes[1].filters[0]=StripPrefix=1
#management.zipkin.tracing.endpoint=http://localhost:9411/api/v2/spans
#management.tracing.sampling.probability=1.0
spring.cloud.gateway.routes[2].id=navigate
spring.cloud.gateway.routes[2].uri=lb://navigate
spring.cloud.gateway.routes[2].predicates[0]=Path=/navigate/**
spring.cloud.gateway.routes[2].filters[0]=StripPrefix=1
spring.cloud.gateway.routes[3].id=websocket
spring.cloud.gateway.routes[3].uri=lb:ws://user
spring.cloud.gateway.routes[3].predicates[0]=Path=/chat/**
spring.cloud.gateway.routes[4].id=search
spring.cloud.gateway.routes[4].uri=lb:ws://community
spring.cloud.gateway.routes[4].predicates[0]=Path=/search/**

Binary file not shown.

Binary file not shown.

View File

@@ -38,7 +38,7 @@
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
<version>8.2.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
@@ -109,5 +109,51 @@
<artifactId>tencentcloud-sdk-java-asr</artifactId>
<version>3.1.1083</version>
</dependency>
<!-- https://mvnrepository.com/artifact/ai.djl/api -->
<dependency>
<groupId>ai.djl</groupId>
<artifactId>api</artifactId>
<version>0.29.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/ai.djl.pytorch/pytorch-model-zoo -->
<dependency>
<groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-model-zoo</artifactId>
<version>0.29.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/ai.djl.pytorch/pytorch-engine -->
<dependency>
<groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-engine</artifactId>
<version>0.29.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/ai.djl.pytorch/pytorch-native-auto -->
<dependency>
<groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-auto</artifactId>
<version>1.9.1</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.0.13</version>
<configuration>
<mainClass>com.ivmiku.tutorial.Main8432</mainClass>
<layout>JAR</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,12 +1,15 @@
package com.ivmiku.tutorial.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
import com.ivmiku.tutorial.entity.AssistantQuery;
import com.ivmiku.tutorial.response.Result;
import com.ivmiku.tutorial.service.AssistantService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@@ -16,10 +19,10 @@ public class AssistantController {
@Resource
private AssistantService assistantService;
@GetMapping("/response")
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);
@PostMapping("/response")
public Object getResponse(@RequestBody AssistantQuery query) {
String userInput = assistantService.speechRecognition(query.getContent(), query.getSize(), query.getLanguage());
Map<String, Object> map = assistantService.getResponse(userInput, query.getLanguage());
if (map == null) {
return Result.error("请求出错");
}

View File

@@ -57,10 +57,29 @@ public class NavigateController {
}
@PostMapping("/ticket")
public Object scanTicket(@RequestPart MultipartFile file) throws IOException {
public Object scanTicket(@RequestPart MultipartFile file, @RequestParam String location) throws IOException {
BufferedImage image = ImageIO.read(file.getInputStream());
Map<String, Object> map = navigateService.scanTicket(image);
return Result.ok(map);
Map<String, Object> result = new HashMap<>();
if (map.containsKey("登机口")) {
String address = navigateService.deGeoCode(location);
address += map.get("登机口");
address += "登机口";
result.put("地址", address);
} else if (map.containsKey("检票口")) {
if (map.containsKey("左边车站")) {
String address = (String) map.get("左边车站");
address += map.get("检票口");
address += "检票口";
result.put("地址", address);
} else {
String address = navigateService.deGeoCode(location);
address += map.get("检票口");
address += "检票口";
result.put("地址", address);
}
}
return Result.ok(result);
}
@GetMapping("/geocode")

View File

@@ -0,0 +1,10 @@
package com.ivmiku.tutorial.entity;
import lombok.Data;
@Data
public class AssistantQuery {
private String content;
private Integer size;
private String language;
}

View File

@@ -65,10 +65,11 @@ public class AssistantService {
}
JSONArray choices = result.getJSONArray("choices");
JSONObject message = choices.getJSONObject(0);
return message.getString("message");
JSONObject content = message.getJSONObject("message");
return content.getString("content");
}
public String textToSpeech(String content) {
public String textToSpeech(String content, String language) {
try{
Credential cred = new Credential(secretId, secretKey);
HttpProfile httpProfile = new HttpProfile();
@@ -80,6 +81,9 @@ public class AssistantService {
req.setText(content);
req.setSessionId(SnowflakeUtil.getNext());
req.setVoiceType(101006L);
if (language.equals("English")) {
req.setPrimaryLanguage(2L);
}
TextToVoiceResponse resp = client.TextToVoice(req);
return resp.getAudio();
} catch (TencentCloudSDKException e) {
@@ -159,7 +163,7 @@ public class AssistantService {
content = getAiResponse(query, language);
}
Map<String, Object> map = new HashMap<>();
map.put("content", textToSpeech(content));
map.put("content", textToSpeech(content, language));
return map;
}
}

View File

@@ -53,7 +53,7 @@ public class NavigateService {
}
public Map<String, Object> scanTicket(BufferedImage image) {
String base64Img = ImgUtil.toBase64(image, "jpg");
String base64Img = ImgUtil.toBase64(image, "png");
SmartStructuralOCRV2Response resp;
try {
Credential cred = new Credential(secretId, secretKey);
@@ -64,7 +64,7 @@ public class NavigateService {
OcrClient client = new OcrClient(cred, "ap-shanghai", clientProfile);
SmartStructuralOCRV2Request req = new SmartStructuralOCRV2Request();
req.setImageBase64(base64Img);
String[] itemNames1 = {"登机口", "检票口"};
String[] itemNames1 = {"登机口", "检票口", "左边车站", "右边车站"};
req.setItemNames(itemNames1);
resp = client.SmartStructuralOCRV2(req);
} catch (TencentCloudSDKException e) {
@@ -106,4 +106,23 @@ public class NavigateService {
JSONObject location = array.getJSONObject(0);
return location.getString("location");
}
public String deGeoCode(String fomattedLoc) {
Map<String, Object> params = new HashMap<>();
params.put("key", key);
params.put("location", fomattedLoc);
params.put("poitype", "飞机场|火车站|地铁站");
HttpResponse response = HttpRequest.get("https://restapi.amap.com/v3/geocode/regeo")
.form(params)
.execute();
String result = response.body();
response.close();
JSONObject resultObj = JSONObject.parseObject(result);
if (resultObj.getInteger("status")==0) {
return null;
}
JSONObject reGeoCode = resultObj.getJSONObject("regeocode");
String address = reGeoCode.getString("formatted_address");
return address;
}
}

View File

@@ -0,0 +1,10 @@
package com.ivmiku.tutorial.service;
import org.springframework.stereotype.Service;
@Service
public class RecogintionService {
static {
}
}

View File

@@ -10,10 +10,7 @@ server.port=8432
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=Shuodedaoli114514
spring.datasource.url=jdbc:mysql://mysql:4514/tutorial?useUnicode=true&characterEncoding=utf8&useSSL=false&ServerTimezone=Asia/Shanghai
spring.datasource.url=jdbc:mysql://mysql:3306/tutorial?useUnicode=true&characterEncoding=utf8&useSSL=false&ServerTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.cloud.nacos.discovery.server-addr=nacos:8848
spring.cloud.nacos.discovery.enabled=true
management.zipkin.tracing.endpoint=http://zipkin:9411/api/v2/spans
management.tracing.sampling.probability=1.0

View File

@@ -10,5 +10,5 @@ dubbo.application.qos-enable=false
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=12345abcde
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/tutorial?useUnicode=true&characterEncoding=utf8&useSSL=false&ServerTimezone=Asia/Shanghai
spring.datasource.password=Shuodedaoli114514
spring.datasource.url=jdbc:mysql://154.40.44.199:4514/tutorial?useUnicode=true&characterEncoding=utf8&useSSL=false&ServerTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true

View File

@@ -13,7 +13,7 @@
<module>user-8072</module>
<module>commons</module>
<module>community-8073</module>
<module>navigate-8432</module>
</modules>
<properties>
@@ -178,6 +178,11 @@
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>${satoken-version}</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-jackson</artifactId>
<version>${satoken-version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo-registry-nacos -->
<dependency>
<groupId>org.apache.dubbo</groupId>

View File

@@ -65,45 +65,10 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo-registry-nacos -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!--micrometer-tracing指标追踪 1-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing</artifactId>
</dependency>
<!--micrometer-tracing-bridge-brave适配zipkin的桥接包 2-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<!--micrometer-observation 3-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
</dependency>
<!--zipkin-reporter-brave 5-->
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-tracing-brave-zipkin-starter</artifactId>
<version>3.2.13</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>

View File

@@ -4,9 +4,7 @@ import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(exclude = {
org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinAutoConfiguration.class
})
@SpringBootApplication
@MapperScan("com.ivmiku.tutorial.mapper")
public class Main8072 {
public static void main(String[] args) {

View File

@@ -14,14 +14,29 @@ public class RabbitMqConfig {
return new Queue("queue1",true);
}
@Bean
public Queue queue2() {
return new Queue("queue2",true);
}
@Bean
public FanoutExchange exchange1() {
return new FanoutExchange("exchange1",true, false);
}
@Bean
public FanoutExchange exchange2() {
return new FanoutExchange("exchange2",true, false);
}
@Bean
public Binding binding1() {
return BindingBuilder.bind(queue1()).to(exchange1());
}
@Bean
public Binding binding2() {
return BindingBuilder.bind(queue2()).to(exchange2());
}
}

View File

@@ -12,7 +12,7 @@ import java.text.ParseException;
@SaCheckLogin
@RestController
@RequestMapping("/api/message")
@RequestMapping("/message")
public class MessageController {
@Autowired
private MessageService messageService;

View File

@@ -10,7 +10,7 @@ import org.springframework.web.bind.annotation.*;
@SaCheckLogin
@RestController
@RequestMapping("/api/relation")
@RequestMapping("/relation")
public class RelationController {
@Autowired
private RelationService relationService;

View File

@@ -2,21 +2,19 @@ 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;
import com.alibaba.fastjson2.JSON;
import com.ivmiku.tutorial.entity.User;
import com.ivmiku.tutorial.entity.UserDTO;
import com.ivmiku.tutorial.mapper.UserMapper;
import com.ivmiku.tutorial.response.Result;
import com.ivmiku.tutorial.service.UserService;
import com.ivmiku.tutorial.utils.RedisUtil;
import jakarta.annotation.Resource;
import me.chanjar.weixin.common.error.WxErrorException;
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 org.springframework.web.bind.annotation.*;
import java.util.HashMap;
@@ -32,17 +30,16 @@ public class UserController {
@Resource
private RedisUtil redisUtil;
@Resource
private UserService userService;
@GetMapping("/login")
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) {
@RequestParam(name = "signature") String signature) {
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()));
}
@@ -77,4 +74,11 @@ public class UserController {
}
return Result.ok();
}
@PostMapping("/edit")
public Object editInfo(@RequestBody UserDTO userDTO) {
String loginId = (String) StpUtil.getLoginId();
userService.changeUserInfo(userDTO, loginId);
return Result.ok();
}
}

View File

@@ -2,7 +2,9 @@ package com.ivmiku.tutorial.controller;
import cn.dev33.satoken.stp.StpUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ivmiku.tutorial.entity.Message;
import com.ivmiku.tutorial.response.AtNotifier;
import com.ivmiku.tutorial.service.MessageService;
import com.ivmiku.tutorial.service.RelationService;
import com.ivmiku.tutorial.utils.DateUtil;
@@ -84,7 +86,15 @@ public class WebSocketServer implements ApplicationContextAware {
sessionMap.put(userId, session);
List<Message> unreadList = messageService.getUnreadMsg(userId);
for(Message msg : unreadList) {
session.getBasicRemote().sendText(JSON.toJSONString(msg));
JSONObject object = JSONObject.from(msg);
object.put("type", 1);
session.getBasicRemote().sendText(object.toJSONString());
}
List<AtNotifier> atList = messageService.getAtList(userId);
for (AtNotifier notifier : atList) {
JSONObject object = JSONObject.from(notifier);
object.put("type", 2);
session.getBasicRemote().sendText(object.toJSONString());
}
}
@@ -132,8 +142,25 @@ public class WebSocketServer implements ApplicationContextAware {
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));
JSONObject jsonObject = JSONObject.from(msg);
jsonObject.put("type", 1);
sessionMap.get(msg.getToId()).getBasicRemote().sendText(jsonObject.toJSONString());
messageService.deleteMessage(msg);
}
}
@RabbitHandler
@RabbitListener(bindings = @QueueBinding(
value = @Queue(),
exchange = @Exchange(value = "exchange2",type = ExchangeTypes.FANOUT)
))
public void sendAt(String message) throws IOException {
AtNotifier notifier = JSON.parseObject(message, AtNotifier.class);
if (sessionMap.containsKey(notifier.getToId())) {
JSONObject jsonObject = JSONObject.from(notifier);
jsonObject.put("type", 2);
sessionMap.get(notifier.getToId()).getBasicRemote().sendText(jsonObject.toJSONString());
messageService.deleteNotifier(notifier);
}
}
}

View File

@@ -0,0 +1,12 @@
package com.ivmiku.tutorial.entity;
import lombok.Data;
@Data
public class UserDTO {
private String nickname;
private String avatarUrl;
private String phoneNum;
private String birthday;
private String gender;
}

View File

@@ -6,6 +6,7 @@ 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.response.AtNotifier;
import com.ivmiku.tutorial.utils.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
@@ -245,4 +246,12 @@ public class MessageService {
public void deleteMessage(Message message) {
redisUtil.deleteFromList(message);
}
public void deleteNotifier(AtNotifier notifier) {
redisUtil.deleteFromList(notifier);
}
public List<AtNotifier> getAtList(String userId) {
return redisUtil.listGetN("at:" + userId, 0, -1);
}
}

View File

@@ -1,6 +1,9 @@
package com.ivmiku.tutorial.service;
import cn.hutool.core.util.DesensitizedUtil;
import cn.hutool.crypto.SecureUtil;
import com.ivmiku.tutorial.entity.User;
import com.ivmiku.tutorial.entity.UserDTO;
import com.ivmiku.tutorial.mapper.UserMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@@ -13,4 +16,30 @@ public class UserService {
public User selectUserById(String id) {
return userMapper.selectById(id);
}
public void changeUserInfo(UserDTO userDTO, String openid) {
String nickname = userDTO.getNickname();
String avatarUrl = userDTO.getAvatarUrl();
String gender = userDTO.getGender();
String phoneNum = userDTO.getPhoneNum();
String birthday = userDTO.getBirthday();
User user = userMapper.selectById(openid);
if (nickname != null) {
user.setNickname(nickname);
}
if (avatarUrl != null) {
user.setAvatarUrl(avatarUrl);
}
if (gender != null) {
user.setGender(gender);
}
if (phoneNum != null) {
phoneNum = DesensitizedUtil.mobilePhone(phoneNum);
user.setPhoneNum(phoneNum);
}
if (birthday != null) {
user.setBirthday(birthday);
}
userMapper.updateById(user);
}
}

View File

@@ -2,6 +2,7 @@ package com.ivmiku.tutorial.utils;
import com.alibaba.fastjson2.JSON;
import com.ivmiku.tutorial.entity.Message;
import com.ivmiku.tutorial.response.AtNotifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@@ -127,4 +128,24 @@ public class RedisUtil {
String id = message.getToId();
redisTemplate.opsForList().remove("unread:" + id, 0, message);
}
public void listAddN(String key, Object value) {
redisTemplate.opsForList().leftPush(key, value);
}
public List<AtNotifier> listGetN(String key, int s, int e) {
List<Object> list = redisTemplate.opsForList().range(key, s, e);
List<AtNotifier> result = new ArrayList<>();
if (list != null) {
for (Object json : list) {
result.add(JSON.parseObject(JSON.toJSONString(json), AtNotifier.class));
}
}
return result;
}
public void deleteFromList(AtNotifier message) {
String id = message.getToId();
redisTemplate.opsForList().remove("unread:" + id, 0, message);
}
}

View File

@@ -1,9 +1,10 @@
wx.miniapp.configs[0].appid=wx0d4fdb5c7bf3b12b
wx.miniapp.configs[0].secret=989f155fcc3aee616568473faf1b1d3b
spring.data.redis.host=127.0.0.1
spring.data.redis.host=redis
spring.data.redis.port=6379
spring.data.redis.database=0
spring.data.redis.password=Shuodedaoli114514
spring.application.name=user
@@ -11,11 +12,10 @@ server.port=8072
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/tutorial?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.password=Shuodedaoli114514
spring.datasource.url=jdbc:mysql://mysql:3306/tutorial?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cloud.nacos.discovery.server-addr=nacos:8848
spring.cloud.nacos.discovery.enabled=true
management.zipkin.tracing.endpoint=http://127.0.0.1:9411/api/v2/spans
management.tracing.sampling.probability=1.0
spring.rabbitmq.host=rabbitmq

View File

@@ -1 +1 @@
spring.profiles.active=dev
spring.profiles.active=dep