修改日志展示逻辑

This commit is contained in:
2025-06-11 18:19:40 +08:00
parent 5bc58e159a
commit 43d1b02a1c
72 changed files with 29 additions and 4132 deletions

View File

@@ -1,47 +0,0 @@
package com.yupi.springbootinit.service;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yupi.springbootinit.model.entity.Post;
import com.yupi.springbootinit.model.entity.PostFavour;
import com.yupi.springbootinit.model.entity.User;
/**
* 帖子收藏服务
*
* @author <a href="https://github.com/liyupi">程序员鱼皮</a>
* @from <a href="https://yupi.icu">编程导航知识星球</a>
*/
public interface PostFavourService extends IService<PostFavour> {
/**
* 帖子收藏
*
* @param postId
* @param loginUser
* @return
*/
int doPostFavour(long postId, User loginUser);
/**
* 分页获取用户收藏的帖子列表
*
* @param page
* @param queryWrapper
* @param favourUserId
* @return
*/
Page<Post> listFavourPostByPage(IPage<Post> page, Wrapper<Post> queryWrapper,
long favourUserId);
/**
* 帖子收藏(内部服务)
*
* @param userId
* @param postId
* @return
*/
int doPostFavourInner(long userId, long postId);
}

View File

@@ -1,60 +0,0 @@
package com.yupi.springbootinit.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yupi.springbootinit.model.dto.post.PostQueryRequest;
import com.yupi.springbootinit.model.entity.Post;
import com.yupi.springbootinit.model.vo.PostVO;
import javax.servlet.http.HttpServletRequest;
/**
* 帖子服务
*
* @author <a href="https://github.com/liyupi">程序员鱼皮</a>
* @from <a href="https://yupi.icu">编程导航知识星球</a>
*/
public interface PostService extends IService<Post> {
/**
* 校验
*
* @param post
* @param add
*/
void validPost(Post post, boolean add);
/**
* 获取查询条件
*
* @param postQueryRequest
* @return
*/
QueryWrapper<Post> getQueryWrapper(PostQueryRequest postQueryRequest);
/**
* 从 ES 查询
*
* @param postQueryRequest
* @return
*/
Page<Post> searchFromEs(PostQueryRequest postQueryRequest);
/**
* 获取帖子封装
*
* @param post
* @param request
* @return
*/
PostVO getPostVO(Post post, HttpServletRequest request);
/**
* 分页获取帖子封装
*
* @param postPage
* @param request
* @return
*/
Page<PostVO> getPostVOPage(Page<Post> postPage, HttpServletRequest request);
}

View File

@@ -1,32 +0,0 @@
package com.yupi.springbootinit.service;
import com.yupi.springbootinit.model.entity.PostThumb;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yupi.springbootinit.model.entity.User;
/**
* 帖子点赞服务
*
* @author <a href="https://github.com/liyupi">程序员鱼皮</a>
* @from <a href="https://yupi.icu">编程导航知识星球</a>
*/
public interface PostThumbService extends IService<PostThumb> {
/**
* 点赞
*
* @param postId
* @param loginUser
* @return
*/
int doPostThumb(long postId, User loginUser);
/**
* 帖子点赞(内部服务)
*
* @param userId
* @param postId
* @return
*/
int doPostThumbInner(long userId, long postId);
}

View File

@@ -1,121 +0,0 @@
package com.yupi.springbootinit.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yupi.springbootinit.model.dto.user.UserQueryRequest;
import com.yupi.springbootinit.model.entity.User;
import com.yupi.springbootinit.model.vo.LoginUserVO;
import com.yupi.springbootinit.model.vo.UserVO;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
/**
* 用户服务
*
* @author <a href="https://github.com/liyupi">程序员鱼皮</a>
* @from <a href="https://yupi.icu">编程导航知识星球</a>
*/
public interface UserService extends IService<User> {
/**
* 用户注册
*
* @param userAccount 用户账户
* @param userPassword 用户密码
* @param checkPassword 校验密码
* @return 新用户 id
*/
long userRegister(String userAccount, String userPassword, String checkPassword);
/**
* 用户登录
*
* @param userAccount 用户账户
* @param userPassword 用户密码
* @param request
* @return 脱敏后的用户信息
*/
LoginUserVO userLogin(String userAccount, String userPassword, HttpServletRequest request);
/**
* 用户登录(微信开放平台)
*
* @param wxOAuth2UserInfo 从微信获取的用户信息
* @param request
* @return 脱敏后的用户信息
*/
LoginUserVO userLoginByMpOpen(WxOAuth2UserInfo wxOAuth2UserInfo, HttpServletRequest request);
/**
* 获取当前登录用户
*
* @param request
* @return
*/
User getLoginUser(HttpServletRequest request);
/**
* 获取当前登录用户(允许未登录)
*
* @param request
* @return
*/
User getLoginUserPermitNull(HttpServletRequest request);
/**
* 是否为管理员
*
* @param request
* @return
*/
boolean isAdmin(HttpServletRequest request);
/**
* 是否为管理员
*
* @param user
* @return
*/
boolean isAdmin(User user);
/**
* 用户注销
*
* @param request
* @return
*/
boolean userLogout(HttpServletRequest request);
/**
* 获取脱敏的已登录用户信息
*
* @return
*/
LoginUserVO getLoginUserVO(User user);
/**
* 获取脱敏的用户信息
*
* @param user
* @return
*/
UserVO getUserVO(User user);
/**
* 获取脱敏的用户信息
*
* @param userList
* @return
*/
List<UserVO> getUserVO(List<User> userList);
/**
* 获取查询条件
*
* @param userQueryRequest
* @return
*/
QueryWrapper<User> getQueryWrapper(UserQueryRequest userQueryRequest);
}

View File

@@ -13,6 +13,7 @@ import org.springframework.util.StopWatch;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
/*
@@ -27,7 +28,6 @@ public class HostInfoServiceImpl extends ServiceImpl<NewHostsMapper, NewHosts> i
@Override
@Async("taskExecutor")
public CompletableFuture<Void> saveHostInfo(List<NewHosts> newHosts) {
try {
StopWatch stopWatch = new StopWatch();
@@ -35,11 +35,11 @@ public class HostInfoServiceImpl extends ServiceImpl<NewHostsMapper, NewHosts> i
saveBatch(newHosts);
stopWatch.stop();
long totalTimeMillis = stopWatch.getTotalTimeMillis();
log.info(" 存储花费: {}ms", totalTimeMillis);
log.info("存储花费: {}ms", totalTimeMillis);
return CompletableFuture.completedFuture(null);
} catch (Exception e) {
// 将异常包装到Future使调用方能处理
return CompletableFuture.failedFuture(e); // Java9+
return CompletableFuture.failedFuture(e);
}
}
@@ -64,6 +64,7 @@ public class HostInfoServiceImpl extends ServiceImpl<NewHostsMapper, NewHosts> i
List<CompletableFuture<Void>> futures = new ArrayList<>();
// 分片提交(避免单批次过大)
Lists.partition(hosts, 1500).forEach(batch -> {
log.info("当前存储数据量大小 {}", batch.size());
CompletableFuture<Void> future = this.saveHostInfo(batch);
futures.add(future);
});

View File

@@ -1,116 +0,0 @@
package com.yupi.springbootinit.service.impl;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yupi.springbootinit.common.ErrorCode;
import com.yupi.springbootinit.exception.BusinessException;
import com.yupi.springbootinit.mapper.PostFavourMapper;
import com.yupi.springbootinit.model.entity.Post;
import com.yupi.springbootinit.model.entity.PostFavour;
import com.yupi.springbootinit.model.entity.User;
import com.yupi.springbootinit.service.PostFavourService;
import com.yupi.springbootinit.service.PostService;
import javax.annotation.Resource;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 帖子收藏服务实现
*
* @author <a href="https://github.com/liyupi">程序员鱼皮</a>
* @from <a href="https://yupi.icu">编程导航知识星球</a>
*/
@Service
public class PostFavourServiceImpl extends ServiceImpl<PostFavourMapper, PostFavour>
implements PostFavourService {
@Resource
private PostService postService;
/**
* 帖子收藏
*
* @param postId
* @param loginUser
* @return
*/
@Override
public int doPostFavour(long postId, User loginUser) {
// 判断是否存在
Post post = postService.getById(postId);
if (post == null) {
throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);
}
// 是否已帖子收藏
long userId = loginUser.getId();
// 每个用户串行帖子收藏
// 锁必须要包裹住事务方法
PostFavourService postFavourService = (PostFavourService) AopContext.currentProxy();
synchronized (String.valueOf(userId).intern()) {
return postFavourService.doPostFavourInner(userId, postId);
}
}
@Override
public Page<Post> listFavourPostByPage(IPage<Post> page, Wrapper<Post> queryWrapper, long favourUserId) {
if (favourUserId <= 0) {
return new Page<>();
}
return baseMapper.listFavourPostByPage(page, queryWrapper, favourUserId);
}
/**
* 封装了事务的方法
*
* @param userId
* @param postId
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int doPostFavourInner(long userId, long postId) {
PostFavour postFavour = new PostFavour();
postFavour.setUserId(userId);
postFavour.setPostId(postId);
QueryWrapper<PostFavour> postFavourQueryWrapper = new QueryWrapper<>(postFavour);
PostFavour oldPostFavour = this.getOne(postFavourQueryWrapper);
boolean result;
// 已收藏
if (oldPostFavour != null) {
result = this.remove(postFavourQueryWrapper);
if (result) {
// 帖子收藏数 - 1
result = postService.update()
.eq("id", postId)
.gt("favourNum", 0)
.setSql("favourNum = favourNum - 1")
.update();
return result ? -1 : 0;
} else {
throw new BusinessException(ErrorCode.SYSTEM_ERROR);
}
} else {
// 未帖子收藏
result = this.save(postFavour);
if (result) {
// 帖子收藏数 + 1
result = postService.update()
.eq("id", postId)
.setSql("favourNum = favourNum + 1")
.update();
return result ? 1 : 0;
} else {
throw new BusinessException(ErrorCode.SYSTEM_ERROR);
}
}
}
}

View File

@@ -1,312 +0,0 @@
package com.yupi.springbootinit.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yupi.springbootinit.common.ErrorCode;
import com.yupi.springbootinit.constant.CommonConstant;
import com.yupi.springbootinit.exception.BusinessException;
import com.yupi.springbootinit.exception.ThrowUtils;
import com.yupi.springbootinit.mapper.PostFavourMapper;
import com.yupi.springbootinit.mapper.PostMapper;
import com.yupi.springbootinit.mapper.PostThumbMapper;
import com.yupi.springbootinit.model.dto.post.PostEsDTO;
import com.yupi.springbootinit.model.dto.post.PostQueryRequest;
import com.yupi.springbootinit.model.entity.Post;
import com.yupi.springbootinit.model.entity.PostFavour;
import com.yupi.springbootinit.model.entity.PostThumb;
import com.yupi.springbootinit.model.entity.User;
import com.yupi.springbootinit.model.vo.PostVO;
import com.yupi.springbootinit.model.vo.UserVO;
import com.yupi.springbootinit.service.PostService;
import com.yupi.springbootinit.service.UserService;
import com.yupi.springbootinit.utils.SqlUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import cn.hutool.core.collection.CollUtil;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
/**
* 帖子服务实现
*
* @author <a href="https://github.com/liyupi">程序员鱼皮</a>
* @from <a href="https://yupi.icu">编程导航知识星球</a>
*/
@Service
@Slf4j
public class PostServiceImpl extends ServiceImpl<PostMapper, Post> implements PostService {
@Resource
private UserService userService;
@Resource
private PostThumbMapper postThumbMapper;
@Resource
private PostFavourMapper postFavourMapper;
@Resource
private ElasticsearchRestTemplate elasticsearchRestTemplate;
@Override
public void validPost(Post post, boolean add) {
if (post == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
String title = post.getTitle();
String content = post.getContent();
String tags = post.getTags();
// 创建时,参数不能为空
if (add) {
ThrowUtils.throwIf(StringUtils.isAnyBlank(title, content, tags), ErrorCode.PARAMS_ERROR);
}
// 有参数则校验
if (StringUtils.isNotBlank(title) && title.length() > 80) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "标题过长");
}
if (StringUtils.isNotBlank(content) && content.length() > 8192) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "内容过长");
}
}
/**
* 获取查询包装类
*
* @param postQueryRequest
* @return
*/
@Override
public QueryWrapper<Post> getQueryWrapper(PostQueryRequest postQueryRequest) {
QueryWrapper<Post> queryWrapper = new QueryWrapper<>();
if (postQueryRequest == null) {
return queryWrapper;
}
String searchText = postQueryRequest.getSearchText();
String sortField = postQueryRequest.getSortField();
String sortOrder = postQueryRequest.getSortOrder();
Long id = postQueryRequest.getId();
String title = postQueryRequest.getTitle();
String content = postQueryRequest.getContent();
List<String> tagList = postQueryRequest.getTags();
Long userId = postQueryRequest.getUserId();
Long notId = postQueryRequest.getNotId();
// 拼接查询条件
if (StringUtils.isNotBlank(searchText)) {
queryWrapper.and(qw -> qw.like("title", searchText).or().like("content", searchText));
}
queryWrapper.like(StringUtils.isNotBlank(title), "title", title);
queryWrapper.like(StringUtils.isNotBlank(content), "content", content);
if (CollUtil.isNotEmpty(tagList)) {
for (String tag : tagList) {
queryWrapper.like("tags", "\"" + tag + "\"");
}
}
queryWrapper.ne(ObjectUtils.isNotEmpty(notId), "id", notId);
queryWrapper.eq(ObjectUtils.isNotEmpty(id), "id", id);
queryWrapper.eq(ObjectUtils.isNotEmpty(userId), "userId", userId);
queryWrapper.orderBy(SqlUtils.validSortField(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC),
sortField);
return queryWrapper;
}
@Override
public Page<Post> searchFromEs(PostQueryRequest postQueryRequest) {
Long id = postQueryRequest.getId();
Long notId = postQueryRequest.getNotId();
String searchText = postQueryRequest.getSearchText();
String title = postQueryRequest.getTitle();
String content = postQueryRequest.getContent();
List<String> tagList = postQueryRequest.getTags();
List<String> orTagList = postQueryRequest.getOrTags();
Long userId = postQueryRequest.getUserId();
// es 起始页为 0
long current = postQueryRequest.getCurrent() - 1;
long pageSize = postQueryRequest.getPageSize();
String sortField = postQueryRequest.getSortField();
String sortOrder = postQueryRequest.getSortOrder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
// 过滤
boolQueryBuilder.filter(QueryBuilders.termQuery("isDelete", 0));
if (id != null) {
boolQueryBuilder.filter(QueryBuilders.termQuery("id", id));
}
if (notId != null) {
boolQueryBuilder.mustNot(QueryBuilders.termQuery("id", notId));
}
if (userId != null) {
boolQueryBuilder.filter(QueryBuilders.termQuery("userId", userId));
}
// 必须包含所有标签
if (CollUtil.isNotEmpty(tagList)) {
for (String tag : tagList) {
boolQueryBuilder.filter(QueryBuilders.termQuery("tags", tag));
}
}
// 包含任何一个标签即可
if (CollUtil.isNotEmpty(orTagList)) {
BoolQueryBuilder orTagBoolQueryBuilder = QueryBuilders.boolQuery();
for (String tag : orTagList) {
orTagBoolQueryBuilder.should(QueryBuilders.termQuery("tags", tag));
}
orTagBoolQueryBuilder.minimumShouldMatch(1);
boolQueryBuilder.filter(orTagBoolQueryBuilder);
}
// 按关键词检索
if (StringUtils.isNotBlank(searchText)) {
boolQueryBuilder.should(QueryBuilders.matchQuery("title", searchText));
boolQueryBuilder.should(QueryBuilders.matchQuery("description", searchText));
boolQueryBuilder.should(QueryBuilders.matchQuery("content", searchText));
boolQueryBuilder.minimumShouldMatch(1);
}
// 按标题检索
if (StringUtils.isNotBlank(title)) {
boolQueryBuilder.should(QueryBuilders.matchQuery("title", title));
boolQueryBuilder.minimumShouldMatch(1);
}
// 按内容检索
if (StringUtils.isNotBlank(content)) {
boolQueryBuilder.should(QueryBuilders.matchQuery("content", content));
boolQueryBuilder.minimumShouldMatch(1);
}
// 排序
SortBuilder<?> sortBuilder = SortBuilders.scoreSort();
if (StringUtils.isNotBlank(sortField)) {
sortBuilder = SortBuilders.fieldSort(sortField);
sortBuilder.order(CommonConstant.SORT_ORDER_ASC.equals(sortOrder) ? SortOrder.ASC : SortOrder.DESC);
}
// 分页
PageRequest pageRequest = PageRequest.of((int) current, (int) pageSize);
// 构造查询
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder)
.withPageable(pageRequest).withSorts(sortBuilder).build();
SearchHits<PostEsDTO> searchHits = elasticsearchRestTemplate.search(searchQuery, PostEsDTO.class);
Page<Post> page = new Page<>();
page.setTotal(searchHits.getTotalHits());
List<Post> resourceList = new ArrayList<>();
// 查出结果后,从 db 获取最新动态数据(比如点赞数)
if (searchHits.hasSearchHits()) {
List<SearchHit<PostEsDTO>> searchHitList = searchHits.getSearchHits();
List<Long> postIdList = searchHitList.stream().map(searchHit -> searchHit.getContent().getId())
.collect(Collectors.toList());
List<Post> postList = baseMapper.selectBatchIds(postIdList);
if (postList != null) {
Map<Long, List<Post>> idPostMap = postList.stream().collect(Collectors.groupingBy(Post::getId));
postIdList.forEach(postId -> {
if (idPostMap.containsKey(postId)) {
resourceList.add(idPostMap.get(postId).get(0));
} else {
// 从 es 清空 db 已物理删除的数据
String delete = elasticsearchRestTemplate.delete(String.valueOf(postId), PostEsDTO.class);
log.info("delete post {}", delete);
}
});
}
}
page.setRecords(resourceList);
return page;
}
@Override
public PostVO getPostVO(Post post, HttpServletRequest request) {
PostVO postVO = PostVO.objToVo(post);
long postId = post.getId();
// 1. 关联查询用户信息
Long userId = post.getUserId();
User user = null;
if (userId != null && userId > 0) {
user = userService.getById(userId);
}
UserVO userVO = userService.getUserVO(user);
postVO.setUser(userVO);
// 2. 已登录,获取用户点赞、收藏状态
User loginUser = userService.getLoginUserPermitNull(request);
if (loginUser != null) {
// 获取点赞
QueryWrapper<PostThumb> postThumbQueryWrapper = new QueryWrapper<>();
postThumbQueryWrapper.in("postId", postId);
postThumbQueryWrapper.eq("userId", loginUser.getId());
PostThumb postThumb = postThumbMapper.selectOne(postThumbQueryWrapper);
postVO.setHasThumb(postThumb != null);
// 获取收藏
QueryWrapper<PostFavour> postFavourQueryWrapper = new QueryWrapper<>();
postFavourQueryWrapper.in("postId", postId);
postFavourQueryWrapper.eq("userId", loginUser.getId());
PostFavour postFavour = postFavourMapper.selectOne(postFavourQueryWrapper);
postVO.setHasFavour(postFavour != null);
}
return postVO;
}
@Override
public Page<PostVO> getPostVOPage(Page<Post> postPage, HttpServletRequest request) {
List<Post> postList = postPage.getRecords();
Page<PostVO> postVOPage = new Page<>(postPage.getCurrent(), postPage.getSize(), postPage.getTotal());
if (CollUtil.isEmpty(postList)) {
return postVOPage;
}
// 1. 关联查询用户信息
Set<Long> userIdSet = postList.stream().map(Post::getUserId).collect(Collectors.toSet());
Map<Long, List<User>> userIdUserListMap = userService.listByIds(userIdSet).stream()
.collect(Collectors.groupingBy(User::getId));
// 2. 已登录,获取用户点赞、收藏状态
Map<Long, Boolean> postIdHasThumbMap = new HashMap<>();
Map<Long, Boolean> postIdHasFavourMap = new HashMap<>();
User loginUser = userService.getLoginUserPermitNull(request);
if (loginUser != null) {
Set<Long> postIdSet = postList.stream().map(Post::getId).collect(Collectors.toSet());
loginUser = userService.getLoginUser(request);
// 获取点赞
QueryWrapper<PostThumb> postThumbQueryWrapper = new QueryWrapper<>();
postThumbQueryWrapper.in("postId", postIdSet);
postThumbQueryWrapper.eq("userId", loginUser.getId());
List<PostThumb> postPostThumbList = postThumbMapper.selectList(postThumbQueryWrapper);
postPostThumbList.forEach(postPostThumb -> postIdHasThumbMap.put(postPostThumb.getPostId(), true));
// 获取收藏
QueryWrapper<PostFavour> postFavourQueryWrapper = new QueryWrapper<>();
postFavourQueryWrapper.in("postId", postIdSet);
postFavourQueryWrapper.eq("userId", loginUser.getId());
List<PostFavour> postFavourList = postFavourMapper.selectList(postFavourQueryWrapper);
postFavourList.forEach(postFavour -> postIdHasFavourMap.put(postFavour.getPostId(), true));
}
// 填充信息
List<PostVO> postVOList = postList.stream().map(post -> {
PostVO postVO = PostVO.objToVo(post);
Long userId = post.getUserId();
User user = null;
if (userIdUserListMap.containsKey(userId)) {
user = userIdUserListMap.get(userId).get(0);
}
postVO.setUser(userService.getUserVO(user));
postVO.setHasThumb(postIdHasThumbMap.getOrDefault(post.getId(), false));
postVO.setHasFavour(postIdHasFavourMap.getOrDefault(post.getId(), false));
return postVO;
}).collect(Collectors.toList());
postVOPage.setRecords(postVOList);
return postVOPage;
}
}

View File

@@ -1,105 +0,0 @@
package com.yupi.springbootinit.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yupi.springbootinit.common.ErrorCode;
import com.yupi.springbootinit.exception.BusinessException;
import com.yupi.springbootinit.mapper.PostThumbMapper;
import com.yupi.springbootinit.model.entity.Post;
import com.yupi.springbootinit.model.entity.PostThumb;
import com.yupi.springbootinit.model.entity.User;
import com.yupi.springbootinit.service.PostService;
import com.yupi.springbootinit.service.PostThumbService;
import javax.annotation.Resource;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 帖子点赞服务实现
*
* @author <a href="https://github.com/liyupi">程序员鱼皮</a>
* @from <a href="https://yupi.icu">编程导航知识星球</a>
*/
@Service
public class PostThumbServiceImpl extends ServiceImpl<PostThumbMapper, PostThumb>
implements PostThumbService {
@Resource
private PostService postService;
/**
* 点赞
*
* @param postId
* @param loginUser
* @return
*/
@Override
public int doPostThumb(long postId, User loginUser) {
// 判断实体是否存在,根据类别获取实体
Post post = postService.getById(postId);
if (post == null) {
throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);
}
// 是否已点赞
long userId = loginUser.getId();
// 每个用户串行点赞
// 锁必须要包裹住事务方法
PostThumbService postThumbService = (PostThumbService) AopContext.currentProxy();
synchronized (String.valueOf(userId).intern()) {
return postThumbService.doPostThumbInner(userId, postId);
}
}
/**
* 封装了事务的方法
*
* @param userId
* @param postId
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int doPostThumbInner(long userId, long postId) {
PostThumb postThumb = new PostThumb();
postThumb.setUserId(userId);
postThumb.setPostId(postId);
QueryWrapper<PostThumb> thumbQueryWrapper = new QueryWrapper<>(postThumb);
PostThumb oldPostThumb = this.getOne(thumbQueryWrapper);
boolean result;
// 已点赞
if (oldPostThumb != null) {
result = this.remove(thumbQueryWrapper);
if (result) {
// 点赞数 - 1
result = postService.update()
.eq("id", postId)
.gt("thumbNum", 0)
.setSql("thumbNum = thumbNum - 1")
.update();
return result ? -1 : 0;
} else {
throw new BusinessException(ErrorCode.SYSTEM_ERROR);
}
} else {
// 未点赞
result = this.save(postThumb);
if (result) {
// 点赞数 + 1
result = postService.update()
.eq("id", postId)
.setSql("thumbNum = thumbNum + 1")
.update();
return result ? 1 : 0;
} else {
throw new BusinessException(ErrorCode.SYSTEM_ERROR);
}
}
}
}

View File

@@ -1,272 +0,0 @@
package com.yupi.springbootinit.service.impl;
import static com.yupi.springbootinit.constant.UserConstant.USER_LOGIN_STATE;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yupi.springbootinit.common.ErrorCode;
import com.yupi.springbootinit.constant.CommonConstant;
import com.yupi.springbootinit.exception.BusinessException;
import com.yupi.springbootinit.mapper.UserMapper;
import com.yupi.springbootinit.model.dto.user.UserQueryRequest;
import com.yupi.springbootinit.model.entity.User;
import com.yupi.springbootinit.model.enums.UserRoleEnum;
import com.yupi.springbootinit.model.vo.LoginUserVO;
import com.yupi.springbootinit.model.vo.UserVO;
import com.yupi.springbootinit.service.UserService;
import com.yupi.springbootinit.utils.SqlUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
/**
* 用户服务实现
*
* @author <a href="https://github.com/liyupi">程序员鱼皮</a>
* @from <a href="https://yupi.icu">编程导航知识星球</a>
*/
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
/**
* 盐值,混淆密码
*/
public static final String SALT = "yupi";
@Override
public long userRegister(String userAccount, String userPassword, String checkPassword) {
// 1. 校验
if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
}
if (userAccount.length() < 4) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号过短");
}
if (userPassword.length() < 8 || checkPassword.length() < 8) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户密码过短");
}
// 密码和校验密码相同
if (!userPassword.equals(checkPassword)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "两次输入的密码不一致");
}
synchronized (userAccount.intern()) {
// 账户不能重复
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("userAccount", userAccount);
long count = this.baseMapper.selectCount(queryWrapper);
if (count > 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号重复");
}
// 2. 加密
String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
// 3. 插入数据
User user = new User();
user.setUserAccount(userAccount);
user.setUserPassword(encryptPassword);
boolean saveResult = this.save(user);
if (!saveResult) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "注册失败,数据库错误");
}
return user.getId();
}
}
@Override
public LoginUserVO userLogin(String userAccount, String userPassword, HttpServletRequest request) {
// 1. 校验
if (StringUtils.isAnyBlank(userAccount, userPassword)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
}
if (userAccount.length() < 4) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号错误");
}
if (userPassword.length() < 8) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码错误");
}
// 2. 加密
String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
// 查询用户是否存在
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("userAccount", userAccount);
queryWrapper.eq("userPassword", encryptPassword);
User user = this.baseMapper.selectOne(queryWrapper);
// 用户不存在
if (user == null) {
log.info("user login failed, userAccount cannot match userPassword");
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户不存在或密码错误");
}
// 3. 记录用户的登录态
request.getSession().setAttribute(USER_LOGIN_STATE, user);
return this.getLoginUserVO(user);
}
@Override
public LoginUserVO userLoginByMpOpen(WxOAuth2UserInfo wxOAuth2UserInfo, HttpServletRequest request) {
String unionId = wxOAuth2UserInfo.getUnionId();
String mpOpenId = wxOAuth2UserInfo.getOpenid();
// 单机锁
synchronized (unionId.intern()) {
// 查询用户是否已存在
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("unionId", unionId);
User user = this.getOne(queryWrapper);
// 被封号,禁止登录
if (user != null && UserRoleEnum.BAN.getValue().equals(user.getUserRole())) {
throw new BusinessException(ErrorCode.FORBIDDEN_ERROR, "该用户已被封,禁止登录");
}
// 用户不存在则创建
if (user == null) {
user = new User();
user.setUnionId(unionId);
user.setMpOpenId(mpOpenId);
user.setUserAvatar(wxOAuth2UserInfo.getHeadImgUrl());
user.setUserName(wxOAuth2UserInfo.getNickname());
boolean result = this.save(user);
if (!result) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "登录失败");
}
}
// 记录用户的登录态
request.getSession().setAttribute(USER_LOGIN_STATE, user);
return getLoginUserVO(user);
}
}
/**
* 获取当前登录用户
*
* @param request
* @return
*/
@Override
public User getLoginUser(HttpServletRequest request) {
// 先判断是否已登录
Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
User currentUser = (User) userObj;
if (currentUser == null || currentUser.getId() == null) {
throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
}
// 从数据库查询(追求性能的话可以注释,直接走缓存)
long userId = currentUser.getId();
currentUser = this.getById(userId);
if (currentUser == null) {
throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
}
return currentUser;
}
/**
* 获取当前登录用户(允许未登录)
*
* @param request
* @return
*/
@Override
public User getLoginUserPermitNull(HttpServletRequest request) {
// 先判断是否已登录
Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
User currentUser = (User) userObj;
if (currentUser == null || currentUser.getId() == null) {
return null;
}
// 从数据库查询(追求性能的话可以注释,直接走缓存)
long userId = currentUser.getId();
return this.getById(userId);
}
/**
* 是否为管理员
*
* @param request
* @return
*/
@Override
public boolean isAdmin(HttpServletRequest request) {
// 仅管理员可查询
Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
User user = (User) userObj;
return isAdmin(user);
}
@Override
public boolean isAdmin(User user) {
return user != null && UserRoleEnum.ADMIN.getValue().equals(user.getUserRole());
}
/**
* 用户注销
*
* @param request
*/
@Override
public boolean userLogout(HttpServletRequest request) {
if (request.getSession().getAttribute(USER_LOGIN_STATE) == null) {
throw new BusinessException(ErrorCode.OPERATION_ERROR, "未登录");
}
// 移除登录态
request.getSession().removeAttribute(USER_LOGIN_STATE);
return true;
}
@Override
public LoginUserVO getLoginUserVO(User user) {
if (user == null) {
return null;
}
LoginUserVO loginUserVO = new LoginUserVO();
BeanUtils.copyProperties(user, loginUserVO);
return loginUserVO;
}
@Override
public UserVO getUserVO(User user) {
if (user == null) {
return null;
}
UserVO userVO = new UserVO();
BeanUtils.copyProperties(user, userVO);
return userVO;
}
@Override
public List<UserVO> getUserVO(List<User> userList) {
if (CollUtil.isEmpty(userList)) {
return new ArrayList<>();
}
return userList.stream().map(this::getUserVO).collect(Collectors.toList());
}
@Override
public QueryWrapper<User> getQueryWrapper(UserQueryRequest userQueryRequest) {
if (userQueryRequest == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空");
}
Long id = userQueryRequest.getId();
String unionId = userQueryRequest.getUnionId();
String mpOpenId = userQueryRequest.getMpOpenId();
String userName = userQueryRequest.getUserName();
String userProfile = userQueryRequest.getUserProfile();
String userRole = userQueryRequest.getUserRole();
String sortField = userQueryRequest.getSortField();
String sortOrder = userQueryRequest.getSortOrder();
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq(id != null, "id", id);
queryWrapper.eq(StringUtils.isNotBlank(unionId), "unionId", unionId);
queryWrapper.eq(StringUtils.isNotBlank(mpOpenId), "mpOpenId", mpOpenId);
queryWrapper.eq(StringUtils.isNotBlank(userRole), "userRole", userRole);
queryWrapper.like(StringUtils.isNotBlank(userProfile), "userProfile", userProfile);
queryWrapper.like(StringUtils.isNotBlank(userName), "userName", userName);
queryWrapper.orderBy(SqlUtils.validSortField(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC),
sortField);
return queryWrapper;
}
}