feat: 新增前台业务模块heritage-portal
【认证与用户管理】 - HrtAuthController: 用户注册、登录、退出、获取用户信息 - HrtUserController: 个人资料管理、密码修改、头像更新、用户统计、浏览历史 【非遗项目管理】 - HrtHeritageController: 非遗项目列表、详情、分类查询、精选推荐 - HrtHeritageService: 非遗项目业务逻辑、浏览量统计 【传承人管理】 - HrtInheritorController: 传承人列表、详情、级别查询 - HrtInheritorService: 传承人业务逻辑、关联项目查询 【新闻资讯管理】 - HrtNewsController: 新闻列表、详情、分类查询、置顶推荐 - HrtNewsService: 新闻业务逻辑、浏览量统计 【活动管理】 - HrtEventController: 活动列表、详情、报名、取消报名、我的报名 - HrtEventService: 活动业务逻辑、报名管理、人数统计 【互动功能】 - HrtCommentController: 评论列表、发布评论、删除评论、我的评论 - HrtFavoriteController: 收藏、取消收藏、我的收藏 - HrtLikeController: 点赞、取消点赞 【技术特性】 - 所有接口使用@SaCheckPermission进行权限验证 - 使用@RequiredArgsConstructor构造器注入 - 统一路径前缀/coder - 完整的Swagger中文文档注解 - 支持分页查询 - 完善的参数验证
This commit is contained in:
parent
7bf017b03f
commit
942338c2a9
56
heritage-modules/heritage-portal/pom.xml
Normal file
56
heritage-modules/heritage-portal/pom.xml
Normal file
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.leocoder.heritage</groupId>
|
||||
<artifactId>heritage-backend</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
<name>heritage-portal</name>
|
||||
<artifactId>heritage-portal</artifactId>
|
||||
<description>前台业务模块</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- 公共模块 -->
|
||||
<dependency>
|
||||
<groupId>org.leocoder.heritage</groupId>
|
||||
<artifactId>heritage-common</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- 数据统一返回、全局异常以及限流、数据脱敏、重复提交等插件 -->
|
||||
<dependency>
|
||||
<groupId>org.leocoder.heritage</groupId>
|
||||
<artifactId>heritage-resultex</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- Sa-Token组件 -->
|
||||
<dependency>
|
||||
<groupId>org.leocoder.heritage</groupId>
|
||||
<artifactId>heritage-sa-token</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- 操作日志组件 -->
|
||||
<dependency>
|
||||
<groupId>org.leocoder.heritage</groupId>
|
||||
<artifactId>heritage-oper-logs</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- OSS存储组件 -->
|
||||
<dependency>
|
||||
<groupId>org.leocoder.heritage</groupId>
|
||||
<artifactId>heritage-oss</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- SpringDoc OpenAPI 3.0 -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@ -0,0 +1,67 @@
|
||||
package org.leocoder.heritage.portal.controller.auth;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtLoginResultVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtLoginVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtRegisterVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtUserInfoVo;
|
||||
import org.leocoder.heritage.portal.service.auth.HrtAuthService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [前台用户认证控制器]
|
||||
*/
|
||||
@Tag(name = "前台用户认证", description = "前台用户注册、登录、登出等认证功能")
|
||||
@Validated
|
||||
@RequestMapping("/api/auth")
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
public class HrtAuthController {
|
||||
|
||||
private final HrtAuthService hrtAuthService;
|
||||
|
||||
/**
|
||||
* @description [用户注册]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "用户注册", description = "前台用户注册接口")
|
||||
@PostMapping("/register")
|
||||
public String register(@Validated @RequestBody HrtRegisterVo vo) {
|
||||
return hrtAuthService.register(vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [用户登录]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "用户登录", description = "前台用户登录接口")
|
||||
@PostMapping("/login")
|
||||
public HrtLoginResultVo login(@Validated @RequestBody HrtLoginVo vo) {
|
||||
return hrtAuthService.login(vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [用户登出]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "用户登出", description = "前台用户登出接口")
|
||||
@PostMapping("/logout")
|
||||
public String logout() {
|
||||
return hrtAuthService.logout();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [获取当前登录用户信息]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "获取用户信息", description = "获取当前登录用户的详细信息")
|
||||
@GetMapping("/userInfo")
|
||||
public HrtUserInfoVo getUserInfo() {
|
||||
return hrtAuthService.getUserInfo();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
package org.leocoder.heritage.portal.controller.comment;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.*;
|
||||
import org.leocoder.heritage.portal.service.comment.HrtCommentService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [前台评论控制器]
|
||||
*/
|
||||
@Tag(name = "前台评论管理", description = "用户评论非遗项目、传承人、新闻等功能,支持回复和点赞")
|
||||
@Validated
|
||||
@RequestMapping("/api/comment")
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
public class HrtCommentController {
|
||||
|
||||
private final HrtCommentService hrtCommentService;
|
||||
|
||||
/**
|
||||
* @description [发表评论]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "发表评论", description = "发表评论或回复,支持一级评论和二级回复")
|
||||
@PostMapping("/add")
|
||||
public String addComment(@Validated @RequestBody CommentAddVo vo) {
|
||||
return hrtCommentService.addComment(vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [删除自己的评论]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "删除评论", description = "删除自己发表的评论,同时会删除该评论下的所有回复")
|
||||
@DeleteMapping("/delete/{commentId}")
|
||||
public String deleteComment(
|
||||
@Parameter(description = "评论ID") @PathVariable Long commentId
|
||||
) {
|
||||
return hrtCommentService.deleteComment(commentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [查看评论列表]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "查看评论列表", description = "根据目标类型和ID分页查询评论列表,包含回复和点赞状态")
|
||||
@GetMapping("/list")
|
||||
public IPage<CommentItemVo> listComments(@Validated CommentQueryVo vo) {
|
||||
return hrtCommentService.listComments(vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [我的评论列表]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "我的评论列表", description = "分页查询当前用户的评论列表,支持按类型筛选")
|
||||
@GetMapping("/myList")
|
||||
public IPage<CommentItemVo> myCommentList(@Validated MyCommentQueryVo vo) {
|
||||
return hrtCommentService.myCommentList(vo);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,80 @@
|
||||
package org.leocoder.heritage.portal.controller.event;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.*;
|
||||
import org.leocoder.heritage.portal.service.event.HrtEventService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [前台活动管理控制器]
|
||||
*/
|
||||
@Tag(name = "前台活动管理", description = "活动列表、详情、报名、取消报名等功能")
|
||||
@Validated
|
||||
@RequestMapping("/api/event")
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
public class HrtEventController {
|
||||
|
||||
private final HrtEventService hrtEventService;
|
||||
|
||||
/**
|
||||
* @description [活动列表]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "活动列表", description = "分页查询活动列表,支持状态筛选和关键词搜索")
|
||||
@GetMapping("/list")
|
||||
public IPage<EventListVo> listEvents(@Validated EventQueryVo vo) {
|
||||
return hrtEventService.listEvents(vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [活动详情]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "活动详情", description = "查看活动详情,自动增加浏览量,包含报名状态和是否可报名")
|
||||
@GetMapping("/detail/{eventId}")
|
||||
public EventDetailVo getEventDetail(
|
||||
@Parameter(description = "活动ID") @PathVariable Long eventId
|
||||
) {
|
||||
return hrtEventService.getEventDetail(eventId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [报名参加活动]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "报名活动", description = "报名参加活动,需验证报名时间和人数限制")
|
||||
@PostMapping("/register")
|
||||
public String registerEvent(@Validated @RequestBody EventRegistrationVo vo) {
|
||||
return hrtEventService.registerEvent(vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [取消报名]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "取消报名", description = "取消已报名的活动,活动开始后无法取消")
|
||||
@PostMapping("/cancel/{eventId}")
|
||||
public String cancelRegistration(
|
||||
@Parameter(description = "活动ID") @PathVariable Long eventId
|
||||
) {
|
||||
return hrtEventService.cancelRegistration(eventId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [我报名的活动]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "我的报名", description = "分页查询当前用户报名的活动列表,支持按状态筛选")
|
||||
@GetMapping("/myRegistrations")
|
||||
public IPage<MyEventRegistrationItemVo> myRegistrations(@Validated MyEventRegistrationVo vo) {
|
||||
return hrtEventService.myRegistrations(vo);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,71 @@
|
||||
package org.leocoder.heritage.portal.controller.favorite;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.FavoriteItemVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.FavoriteOperateVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.FavoriteQueryVo;
|
||||
import org.leocoder.heritage.portal.service.favorite.HrtFavoriteService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [前台收藏控制器]
|
||||
*/
|
||||
@Tag(name = "前台收藏管理", description = "用户收藏非遗项目、传承人、新闻等功能")
|
||||
@Validated
|
||||
@RequestMapping("/api/favorite")
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
public class HrtFavoriteController {
|
||||
|
||||
private final HrtFavoriteService hrtFavoriteService;
|
||||
|
||||
/**
|
||||
* @description [添加收藏]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "添加收藏", description = "收藏非遗项目、传承人或新闻资讯")
|
||||
@PostMapping("/add")
|
||||
public String addFavorite(@Validated @RequestBody FavoriteOperateVo vo) {
|
||||
return hrtFavoriteService.addFavorite(vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [取消收藏]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "取消收藏", description = "取消已收藏的内容")
|
||||
@PostMapping("/cancel")
|
||||
public String cancelFavorite(@Validated @RequestBody FavoriteOperateVo vo) {
|
||||
return hrtFavoriteService.cancelFavorite(vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [我的收藏列表]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "我的收藏列表", description = "分页查询当前用户的收藏列表,支持按类型筛选")
|
||||
@GetMapping("/myList")
|
||||
public IPage<FavoriteItemVo> myFavoriteList(@Validated FavoriteQueryVo vo) {
|
||||
return hrtFavoriteService.myFavoriteList(vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [检查是否已收藏]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "检查是否已收藏", description = "用于前端判断收藏按钮状态")
|
||||
@GetMapping("/check")
|
||||
public Boolean checkFavorite(
|
||||
@Parameter(description = "目标类型:heritage、inheritor、news") @RequestParam String targetType,
|
||||
@Parameter(description = "目标ID") @RequestParam Long targetId
|
||||
) {
|
||||
return hrtFavoriteService.checkFavorite(targetType, targetId);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
package org.leocoder.heritage.portal.controller.heritage;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtHeritageDetailVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtHeritageListVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtHeritageQueryVo;
|
||||
import org.leocoder.heritage.portal.service.heritage.HrtHeritageService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [前台非遗项目控制器]
|
||||
*/
|
||||
@Tag(name = "前台非遗项目管理", description = "非遗项目的查询、浏览、搜索等功能")
|
||||
@Validated
|
||||
@RequestMapping("/api/heritage")
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
public class HrtHeritageController {
|
||||
|
||||
private final HrtHeritageService hrtHeritageService;
|
||||
|
||||
/**
|
||||
* @description [分页查询非遗项目列表]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "分页查询非遗项目列表", description = "支持关键词搜索(名称、描述)和多条件筛选(分类、标签、级别、地区等)")
|
||||
@GetMapping("/list")
|
||||
public IPage<HrtHeritageListVo> list(@Validated HrtHeritageQueryVo vo) {
|
||||
return hrtHeritageService.listPage(vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [查看非遗项目详情]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "查看非遗项目详情", description = "根据ID查看非遗项目的详细信息,浏览量自动+1")
|
||||
@GetMapping("/detail/{id}")
|
||||
public HrtHeritageDetailVo detail(@Parameter(description = "非遗项目ID") @PathVariable Long id) {
|
||||
return hrtHeritageService.getDetail(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [热门非遗项目]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "热门非遗项目", description = "根据浏览量和点赞数获取热门非遗项目列表")
|
||||
@GetMapping("/hot")
|
||||
public List<HrtHeritageListVo> hot(@Parameter(description = "查询数量,默认10条") @RequestParam(required = false, defaultValue = "10") Integer limit) {
|
||||
return hrtHeritageService.getHotList(limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [精选非遗项目]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "精选非遗项目", description = "获取平台精选的优质非遗项目")
|
||||
@GetMapping("/featured")
|
||||
public List<HrtHeritageListVo> featured(@Parameter(description = "查询数量,默认10条") @RequestParam(required = false, defaultValue = "10") Integer limit) {
|
||||
return hrtHeritageService.getFeaturedList(limit);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
package org.leocoder.heritage.portal.controller.inheritor;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtInheritorDetailVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtInheritorListVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtInheritorQueryVo;
|
||||
import org.leocoder.heritage.portal.service.inheritor.HrtInheritorService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [前台传承人控制器]
|
||||
*/
|
||||
@Tag(name = "前台传承人管理", description = "传承人的查询、浏览、搜索等功能")
|
||||
@Validated
|
||||
@RequestMapping("/api/inheritor")
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
public class HrtInheritorController {
|
||||
|
||||
private final HrtInheritorService hrtInheritorService;
|
||||
|
||||
/**
|
||||
* @description [分页查询传承人列表]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "分页查询传承人列表", description = "支持关键词搜索(姓名、简介、故事)和多条件筛选(级别、地区、非遗项目等)")
|
||||
@GetMapping("/list")
|
||||
public IPage<HrtInheritorListVo> list(@Validated HrtInheritorQueryVo vo) {
|
||||
return hrtInheritorService.listPage(vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [查看传承人详情]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "查看传承人详情", description = "根据ID查看传承人的详细信息,浏览量自动+1")
|
||||
@GetMapping("/detail/{id}")
|
||||
public HrtInheritorDetailVo detail(@Parameter(description = "传承人ID") @PathVariable Long id) {
|
||||
return hrtInheritorService.getDetail(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [根据非遗项目查询传承人]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "根据非遗项目查询传承人", description = "根据非遗项目ID查询该项目的所有传承人")
|
||||
@GetMapping("/listByHeritage/{heritageId}")
|
||||
public List<HrtInheritorListVo> listByHeritage(@Parameter(description = "非遗项目ID") @PathVariable Long heritageId) {
|
||||
return hrtInheritorService.listByHeritageId(heritageId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [精选传承人]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "精选传承人", description = "获取平台精选的优秀传承人")
|
||||
@GetMapping("/featured")
|
||||
public List<HrtInheritorListVo> featured(@Parameter(description = "查询数量,默认10条") @RequestParam(required = false, defaultValue = "10") Integer limit) {
|
||||
return hrtInheritorService.getFeaturedList(limit);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
package org.leocoder.heritage.portal.controller.like;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.LikeOperateVo;
|
||||
import org.leocoder.heritage.portal.service.like.HrtLikeService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [前台点赞控制器]
|
||||
*/
|
||||
@Tag(name = "前台点赞管理", description = "用户点赞非遗项目、传承人、新闻、评论等功能")
|
||||
@Validated
|
||||
@RequestMapping("/api/like")
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
public class HrtLikeController {
|
||||
|
||||
private final HrtLikeService hrtLikeService;
|
||||
|
||||
/**
|
||||
* @description [点赞]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "点赞", description = "点赞非遗项目、传承人、新闻或评论")
|
||||
@PostMapping("/add")
|
||||
public String addLike(@Validated @RequestBody LikeOperateVo vo) {
|
||||
return hrtLikeService.addLike(vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [取消点赞]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "取消点赞", description = "取消已点赞的内容")
|
||||
@PostMapping("/cancel")
|
||||
public String cancelLike(@Validated @RequestBody LikeOperateVo vo) {
|
||||
return hrtLikeService.cancelLike(vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [检查是否已点赞]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "检查是否已点赞", description = "用于前端判断点赞按钮状态")
|
||||
@GetMapping("/check")
|
||||
public Boolean checkLike(
|
||||
@Parameter(description = "目标类型:heritage、inheritor、news、comment") @RequestParam String targetType,
|
||||
@Parameter(description = "目标ID") @RequestParam Long targetId
|
||||
) {
|
||||
return hrtLikeService.checkLike(targetType, targetId);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
package org.leocoder.heritage.portal.controller.news;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.*;
|
||||
import org.leocoder.heritage.portal.service.news.HrtNewsService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [前台新闻资讯控制器]
|
||||
*/
|
||||
@Tag(name = "前台新闻资讯", description = "新闻资讯列表、详情、热门、置顶等功能")
|
||||
@Validated
|
||||
@RequestMapping("/api/news")
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
public class HrtNewsController {
|
||||
|
||||
private final HrtNewsService hrtNewsService;
|
||||
|
||||
/**
|
||||
* @description [分页查询新闻列表]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "新闻列表", description = "分页查询新闻列表,支持分类筛选和关键词搜索")
|
||||
@GetMapping("/list")
|
||||
public IPage<NewsListVo> listNews(@Validated NewsQueryVo vo) {
|
||||
return hrtNewsService.listNews(vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [查看新闻详情]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "新闻详情", description = "查看新闻详情,自动增加浏览量,包含点赞和收藏状态")
|
||||
@GetMapping("/detail/{newsId}")
|
||||
public NewsDetailVo getNewsDetail(
|
||||
@Parameter(description = "新闻ID") @PathVariable Long newsId
|
||||
) {
|
||||
return hrtNewsService.getNewsDetail(newsId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [热门新闻]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "热门新闻", description = "根据浏览量获取热门新闻列表")
|
||||
@GetMapping("/hot")
|
||||
public List<NewsListVo> hotNews(
|
||||
@Parameter(description = "返回数量,默认10") @RequestParam(required = false, defaultValue = "10") Integer limit
|
||||
) {
|
||||
return hrtNewsService.hotNews(limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [置顶新闻]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "置顶新闻", description = "获取所有置顶新闻列表")
|
||||
@GetMapping("/top")
|
||||
public List<NewsListVo> topNews() {
|
||||
return hrtNewsService.topNews();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,96 @@
|
||||
package org.leocoder.heritage.portal.controller.user;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.*;
|
||||
import org.leocoder.heritage.portal.service.user.HrtUserService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [前台用户中心控制器]
|
||||
*/
|
||||
@Tag(name = "前台用户中心", description = "个人资料管理、密码修改、用户统计、浏览历史等功能")
|
||||
@Validated
|
||||
@RequestMapping("/api/user")
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
public class HrtUserController {
|
||||
|
||||
private final HrtUserService hrtUserService;
|
||||
|
||||
/**
|
||||
* @description [获取个人资料]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "获取个人资料", description = "获取当前登录用户的个人资料信息")
|
||||
@GetMapping("/profile")
|
||||
public UserProfileVo getProfile() {
|
||||
return hrtUserService.getProfile();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [修改个人资料]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "修改个人资料", description = "修改昵称、邮箱、手机号、性别、生日等信息")
|
||||
@PutMapping("/profile")
|
||||
public String updateProfile(@Validated @RequestBody UserProfileUpdateVo vo) {
|
||||
return hrtUserService.updateProfile(vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [更新头像]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "更新头像", description = "更新用户头像URL")
|
||||
@PutMapping("/avatar")
|
||||
public String updateAvatar(@Validated @RequestBody UserAvatarUpdateVo vo) {
|
||||
return hrtUserService.updateAvatar(vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [上传并更新头像]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "上传头像", description = "上传头像文件并自动更新用户头像,支持jpg/png/gif等格式,限制2MB以内")
|
||||
@PostMapping("/avatar/upload")
|
||||
public String uploadAvatar(@RequestParam("file") MultipartFile file) {
|
||||
return hrtUserService.uploadAndUpdateAvatar(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [修改密码]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "修改密码", description = "修改登录密码,需验证旧密码")
|
||||
@PutMapping("/password")
|
||||
public String updatePassword(@Validated @RequestBody UserPasswordUpdateVo vo) {
|
||||
return hrtUserService.updatePassword(vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [获取用户统计信息]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "用户统计", description = "获取用户的浏览、评论、点赞、收藏、报名等统计数据")
|
||||
@GetMapping("/stats")
|
||||
public UserStatsVo getUserStats() {
|
||||
return hrtUserService.getUserStats();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [获取浏览历史]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Operation(summary = "浏览历史", description = "分页查询用户的浏览历史记录,支持按类型筛选")
|
||||
@GetMapping("/viewHistory")
|
||||
public IPage<ViewHistoryItemVo> getViewHistory(@Validated ViewHistoryQueryVo vo) {
|
||||
return hrtUserService.getViewHistory(vo);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package org.leocoder.heritage.portal.service.auth;
|
||||
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtLoginResultVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtLoginVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtRegisterVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtUserInfoVo;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [前台用户认证服务接口]
|
||||
*/
|
||||
public interface HrtAuthService {
|
||||
|
||||
/**
|
||||
* @description [用户注册]
|
||||
* @author Leocoder
|
||||
*/
|
||||
String register(HrtRegisterVo vo);
|
||||
|
||||
/**
|
||||
* @description [用户登录]
|
||||
* @author Leocoder
|
||||
*/
|
||||
HrtLoginResultVo login(HrtLoginVo vo);
|
||||
|
||||
/**
|
||||
* @description [用户登出]
|
||||
* @author Leocoder
|
||||
*/
|
||||
String logout();
|
||||
|
||||
/**
|
||||
* @description [获取当前登录用户信息]
|
||||
* @author Leocoder
|
||||
*/
|
||||
HrtUserInfoVo getUserInfo();
|
||||
|
||||
}
|
||||
@ -0,0 +1,166 @@
|
||||
package org.leocoder.heritage.portal.service.auth;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.leocoder.heritage.common.exception.coder.YUtil;
|
||||
import org.leocoder.heritage.common.utils.ip.IpUtil;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtLoginResultVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtLoginVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtRegisterVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtUserInfoVo;
|
||||
import org.leocoder.heritage.domain.pojo.portal.HrtUser;
|
||||
import org.leocoder.heritage.mybatisplus.mapper.portal.HrtUserMapper;
|
||||
import org.leocoder.heritage.satoken.config.CoderSaTokenPasswordUtil;
|
||||
import org.leocoder.heritage.satoken.util.HrtStpUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [前台用户认证服务实现类]
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class HrtAuthServiceImpl implements HrtAuthService {
|
||||
|
||||
private final HrtUserMapper hrtUserMapper;
|
||||
|
||||
/**
|
||||
* @description [用户注册]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public String register(HrtRegisterVo vo) {
|
||||
// 1、校验两次密码是否一致
|
||||
YUtil.isTrue(!vo.getPassword().equals(vo.getConfirmPassword()), "两次密码输入不一致");
|
||||
|
||||
// 2、校验用户名是否已存在
|
||||
LambdaQueryWrapper<HrtUser> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(HrtUser::getUsername, vo.getUsername());
|
||||
HrtUser existUser = hrtUserMapper.selectOne(queryWrapper);
|
||||
YUtil.isTrue(existUser != null, "用户名已存在");
|
||||
|
||||
// 3、校验邮箱是否已存在(如果提供了邮箱)
|
||||
if (StringUtils.isNotBlank(vo.getEmail())) {
|
||||
queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(HrtUser::getEmail, vo.getEmail());
|
||||
existUser = hrtUserMapper.selectOne(queryWrapper);
|
||||
YUtil.isTrue(existUser != null, "邮箱已被注册");
|
||||
}
|
||||
|
||||
// 4、校验手机号是否已存在(如果提供了手机号)
|
||||
if (StringUtils.isNotBlank(vo.getPhone())) {
|
||||
queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(HrtUser::getPhone, vo.getPhone());
|
||||
existUser = hrtUserMapper.selectOne(queryWrapper);
|
||||
YUtil.isTrue(existUser != null, "手机号已被注册");
|
||||
}
|
||||
|
||||
// 5、创建新用户
|
||||
HrtUser newUser = new HrtUser();
|
||||
newUser.setUsername(vo.getUsername());
|
||||
newUser.setPassword(CoderSaTokenPasswordUtil.encryptPassword(vo.getPassword()));
|
||||
newUser.setNickname(vo.getNickname());
|
||||
newUser.setEmail(vo.getEmail());
|
||||
newUser.setPhone(vo.getPhone());
|
||||
newUser.setStatus(1);
|
||||
newUser.setGender(0);
|
||||
|
||||
// 6、保存用户
|
||||
int result = hrtUserMapper.insert(newUser);
|
||||
YUtil.isTrue(result <= 0, "注册失败");
|
||||
|
||||
log.info("前台用户注册成功:{}", vo.getUsername());
|
||||
return "注册成功";
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [用户登录]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public HrtLoginResultVo login(HrtLoginVo vo) {
|
||||
// 1、查询用户(支持用户名、邮箱、手机号登录)
|
||||
LambdaQueryWrapper<HrtUser> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.and(wrapper -> wrapper
|
||||
.eq(HrtUser::getUsername, vo.getAccount())
|
||||
.or()
|
||||
.eq(HrtUser::getEmail, vo.getAccount())
|
||||
.or()
|
||||
.eq(HrtUser::getPhone, vo.getAccount())
|
||||
);
|
||||
HrtUser user = hrtUserMapper.selectOne(queryWrapper);
|
||||
YUtil.isTrue(user == null, "账号或密码错误");
|
||||
|
||||
// 2、校验用户状态
|
||||
YUtil.isTrue(user.getStatus() == 0, "账号已被禁用,请联系管理员");
|
||||
|
||||
// 3、校验密码
|
||||
boolean passwordMatch = CoderSaTokenPasswordUtil.matchesPassword(vo.getPassword(), user.getPassword());
|
||||
YUtil.isTrue(!passwordMatch, "账号或密码错误");
|
||||
|
||||
// 4、执行登录
|
||||
HrtStpUtil.login(user.getId());
|
||||
|
||||
// 5、设置记住我
|
||||
if (vo.getRememberMe() != null && vo.getRememberMe()) {
|
||||
HrtStpUtil.getStpLogic().getTokenSession().updateTimeout(60 * 60 * 24 * 7);
|
||||
}
|
||||
|
||||
// 6、更新登录信息
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
HttpServletRequest request = attributes != null ? attributes.getRequest() : null;
|
||||
String loginIp = request != null ? IpUtil.getIpAddr(request) : "unknown";
|
||||
|
||||
HrtUser updateUser = new HrtUser();
|
||||
updateUser.setId(user.getId());
|
||||
updateUser.setLoginIp(loginIp);
|
||||
updateUser.setLoginTime(LocalDateTime.now());
|
||||
hrtUserMapper.updateById(updateUser);
|
||||
|
||||
// 7、构建返回数据
|
||||
HrtLoginResultVo result = new HrtLoginResultVo();
|
||||
result.setToken(HrtStpUtil.getTokenValue());
|
||||
result.setTokenName(HrtStpUtil.getTokenName());
|
||||
result.setTokenTimeout(HrtStpUtil.getStpLogic().getTokenTimeout());
|
||||
|
||||
HrtUserInfoVo userInfo = BeanUtil.copyProperties(user, HrtUserInfoVo.class);
|
||||
result.setUserInfo(userInfo);
|
||||
|
||||
log.info("前台用户登录成功:{}", user.getUsername());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [用户登出]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public String logout() {
|
||||
HrtStpUtil.logout();
|
||||
log.info("前台用户登出成功");
|
||||
return "登出成功";
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [获取当前登录用户信息]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public HrtUserInfoVo getUserInfo() {
|
||||
Long userId = HrtStpUtil.getLoginIdAsLong();
|
||||
HrtUser user = hrtUserMapper.selectById(userId);
|
||||
YUtil.isTrue(user == null, "用户不存在");
|
||||
|
||||
return BeanUtil.copyProperties(user, HrtUserInfoVo.class);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
package org.leocoder.heritage.portal.service.comment;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.*;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [评论服务接口]
|
||||
*/
|
||||
public interface HrtCommentService {
|
||||
|
||||
/**
|
||||
* @description [发表评论]
|
||||
* @author Leocoder
|
||||
*/
|
||||
String addComment(CommentAddVo vo);
|
||||
|
||||
/**
|
||||
* @description [删除自己的评论]
|
||||
* @author Leocoder
|
||||
*/
|
||||
String deleteComment(Long commentId);
|
||||
|
||||
/**
|
||||
* @description [查看评论列表(按目标ID和类型)]
|
||||
* @author Leocoder
|
||||
*/
|
||||
IPage<CommentItemVo> listComments(CommentQueryVo vo);
|
||||
|
||||
/**
|
||||
* @description [我的评论列表]
|
||||
* @author Leocoder
|
||||
*/
|
||||
IPage<CommentItemVo> myCommentList(MyCommentQueryVo vo);
|
||||
|
||||
}
|
||||
@ -0,0 +1,343 @@
|
||||
package org.leocoder.heritage.portal.service.comment;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.leocoder.heritage.common.exception.BusinessException;
|
||||
import org.leocoder.heritage.domain.enums.TargetTypeEnum;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.*;
|
||||
import org.leocoder.heritage.domain.pojo.portal.*;
|
||||
import org.leocoder.heritage.mybatisplus.mapper.portal.*;
|
||||
import org.leocoder.heritage.satoken.util.HrtStpUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [评论服务实现类]
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class HrtCommentServiceImpl implements HrtCommentService {
|
||||
|
||||
private final HrtCommentMapper hrtCommentMapper;
|
||||
private final HrtUserMapper hrtUserMapper;
|
||||
private final HrtHeritageMapper hrtHeritageMapper;
|
||||
private final HrtInheritorMapper hrtInheritorMapper;
|
||||
private final HrtNewsMapper hrtNewsMapper;
|
||||
private final HrtLikeMapper hrtLikeMapper;
|
||||
|
||||
/**
|
||||
* @description [发表评论]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String addComment(CommentAddVo vo) {
|
||||
Long userId = HrtStpUtil.getLoginIdAsLong();
|
||||
|
||||
if (!TargetTypeEnum.isValid(vo.getTargetType())) {
|
||||
throw new BusinessException(400, "不支持的目标类型");
|
||||
}
|
||||
|
||||
validateTargetExists(vo.getTargetType(), vo.getTargetId());
|
||||
|
||||
if (vo.getParentId() != null && vo.getParentId() > 0) {
|
||||
HrtComment parentComment = hrtCommentMapper.selectById(vo.getParentId());
|
||||
if (parentComment == null) {
|
||||
throw new BusinessException(404, "父评论不存在");
|
||||
}
|
||||
if (!parentComment.getTargetType().equals(vo.getTargetType())
|
||||
|| !parentComment.getTargetId().equals(vo.getTargetId())) {
|
||||
throw new BusinessException(400, "父评论与目标不匹配");
|
||||
}
|
||||
}
|
||||
|
||||
if (vo.getRating() != null && !"heritage".equals(vo.getTargetType())) {
|
||||
throw new BusinessException(400, "只有非遗项目支持评分");
|
||||
}
|
||||
|
||||
HrtComment comment = new HrtComment();
|
||||
comment.setUserId(userId);
|
||||
comment.setTargetType(vo.getTargetType());
|
||||
comment.setTargetId(vo.getTargetId());
|
||||
comment.setContent(vo.getContent());
|
||||
comment.setRating(vo.getRating());
|
||||
comment.setParentId(vo.getParentId() == null ? 0L : vo.getParentId());
|
||||
comment.setLikeCount(0);
|
||||
comment.setStatus(1);
|
||||
|
||||
int result = hrtCommentMapper.insert(comment);
|
||||
if (result <= 0) {
|
||||
throw new BusinessException(500, "发表评论失败");
|
||||
}
|
||||
|
||||
return "评论成功";
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [删除自己的评论]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String deleteComment(Long commentId) {
|
||||
Long userId = HrtStpUtil.getLoginIdAsLong();
|
||||
|
||||
HrtComment comment = hrtCommentMapper.selectById(commentId);
|
||||
if (comment == null) {
|
||||
throw new BusinessException(404, "评论不存在");
|
||||
}
|
||||
|
||||
if (!comment.getUserId().equals(userId)) {
|
||||
throw new BusinessException(403, "无权删除他人评论");
|
||||
}
|
||||
|
||||
int result = hrtCommentMapper.deleteById(commentId);
|
||||
if (result <= 0) {
|
||||
throw new BusinessException(500, "删除评论失败");
|
||||
}
|
||||
|
||||
LambdaQueryWrapper<HrtComment> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(HrtComment::getParentId, commentId);
|
||||
hrtCommentMapper.delete(wrapper);
|
||||
|
||||
return "删除成功";
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [查看评论列表(按目标ID和类型)]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public IPage<CommentItemVo> listComments(CommentQueryVo vo) {
|
||||
Long currentUserId = null;
|
||||
try {
|
||||
currentUserId = HrtStpUtil.getLoginIdAsLong();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
Page<HrtComment> page = new Page<>(vo.getPageNum(), vo.getPageSize());
|
||||
|
||||
LambdaQueryWrapper<HrtComment> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(HrtComment::getTargetType, vo.getTargetType())
|
||||
.eq(HrtComment::getTargetId, vo.getTargetId())
|
||||
.eq(HrtComment::getParentId, 0L)
|
||||
.eq(HrtComment::getStatus, 1);
|
||||
|
||||
if ("hottest".equals(vo.getSortType())) {
|
||||
wrapper.orderByDesc(HrtComment::getLikeCount);
|
||||
} else {
|
||||
wrapper.orderByDesc(HrtComment::getCreateTime);
|
||||
}
|
||||
|
||||
IPage<HrtComment> commentPage = hrtCommentMapper.selectPage(page, wrapper);
|
||||
|
||||
List<Long> userIds = commentPage.getRecords().stream()
|
||||
.map(HrtComment::getUserId)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Map<Long, HrtUser> userMap = userIds.isEmpty() ? Map.of() :
|
||||
hrtUserMapper.selectBatchIds(userIds).stream()
|
||||
.collect(Collectors.toMap(HrtUser::getId, user -> user));
|
||||
|
||||
List<Long> commentIds = commentPage.getRecords().stream()
|
||||
.map(HrtComment::getId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Map<Long, List<HrtComment>> replyMap = Map.of();
|
||||
if (!commentIds.isEmpty()) {
|
||||
LambdaQueryWrapper<HrtComment> replyWrapper = new LambdaQueryWrapper<>();
|
||||
replyWrapper.in(HrtComment::getParentId, commentIds)
|
||||
.eq(HrtComment::getStatus, 1)
|
||||
.orderByAsc(HrtComment::getCreateTime);
|
||||
List<HrtComment> replies = hrtCommentMapper.selectList(replyWrapper);
|
||||
|
||||
List<Long> replyUserIds = replies.stream()
|
||||
.map(HrtComment::getUserId)
|
||||
.distinct()
|
||||
.filter(uid -> !userMap.containsKey(uid))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!replyUserIds.isEmpty()) {
|
||||
hrtUserMapper.selectBatchIds(replyUserIds).forEach(user ->
|
||||
userMap.put(user.getId(), user)
|
||||
);
|
||||
}
|
||||
|
||||
replyMap = replies.stream()
|
||||
.collect(Collectors.groupingBy(HrtComment::getParentId));
|
||||
}
|
||||
|
||||
Map<Long, Boolean> likeMap = Map.of();
|
||||
if (currentUserId != null && !commentIds.isEmpty()) {
|
||||
List<Long> allCommentIds = new ArrayList<>(commentIds);
|
||||
replyMap.values().forEach(replyList ->
|
||||
replyList.forEach(reply -> allCommentIds.add(reply.getId()))
|
||||
);
|
||||
|
||||
LambdaQueryWrapper<HrtLike> likeWrapper = new LambdaQueryWrapper<>();
|
||||
likeWrapper.eq(HrtLike::getUserId, currentUserId)
|
||||
.eq(HrtLike::getTargetType, "comment")
|
||||
.in(HrtLike::getTargetId, allCommentIds);
|
||||
likeMap = hrtLikeMapper.selectList(likeWrapper).stream()
|
||||
.collect(Collectors.toMap(HrtLike::getTargetId, like -> true));
|
||||
}
|
||||
|
||||
final Map<Long, Boolean> finalLikeMap = likeMap;
|
||||
final Map<Long, List<HrtComment>> finalReplyMap = replyMap;
|
||||
|
||||
Page<CommentItemVo> resultPage = new Page<>(vo.getPageNum(), vo.getPageSize());
|
||||
resultPage.setTotal(commentPage.getTotal());
|
||||
resultPage.setRecords(commentPage.getRecords().stream()
|
||||
.map(comment -> buildCommentItemVo(comment, userMap, finalReplyMap, finalLikeMap))
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
|
||||
return resultPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [我的评论列表]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public IPage<CommentItemVo> myCommentList(MyCommentQueryVo vo) {
|
||||
Long userId = HrtStpUtil.getLoginIdAsLong();
|
||||
|
||||
Page<HrtComment> page = new Page<>(vo.getPageNum(), vo.getPageSize());
|
||||
|
||||
LambdaQueryWrapper<HrtComment> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(HrtComment::getUserId, userId);
|
||||
|
||||
if (vo.getTargetType() != null && !vo.getTargetType().isEmpty()) {
|
||||
wrapper.eq(HrtComment::getTargetType, vo.getTargetType());
|
||||
}
|
||||
|
||||
wrapper.orderByDesc(HrtComment::getCreateTime);
|
||||
|
||||
IPage<HrtComment> commentPage = hrtCommentMapper.selectPage(page, wrapper);
|
||||
|
||||
HrtUser currentUser = hrtUserMapper.selectById(userId);
|
||||
|
||||
Map<Long, Boolean> likeMap = Map.of();
|
||||
if (!commentPage.getRecords().isEmpty()) {
|
||||
List<Long> commentIds = commentPage.getRecords().stream()
|
||||
.map(HrtComment::getId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
LambdaQueryWrapper<HrtLike> likeWrapper = new LambdaQueryWrapper<>();
|
||||
likeWrapper.eq(HrtLike::getUserId, userId)
|
||||
.eq(HrtLike::getTargetType, "comment")
|
||||
.in(HrtLike::getTargetId, commentIds);
|
||||
likeMap = hrtLikeMapper.selectList(likeWrapper).stream()
|
||||
.collect(Collectors.toMap(HrtLike::getTargetId, like -> true));
|
||||
}
|
||||
|
||||
final Map<Long, Boolean> finalLikeMap = likeMap;
|
||||
|
||||
Page<CommentItemVo> resultPage = new Page<>(vo.getPageNum(), vo.getPageSize());
|
||||
resultPage.setTotal(commentPage.getTotal());
|
||||
resultPage.setRecords(commentPage.getRecords().stream()
|
||||
.map(comment -> {
|
||||
CommentItemVo itemVo = BeanUtil.copyProperties(comment, CommentItemVo.class);
|
||||
itemVo.setUserName(currentUser.getNickname());
|
||||
itemVo.setUserAvatar(currentUser.getAvatar());
|
||||
itemVo.setIsLiked(finalLikeMap.getOrDefault(comment.getId(), false));
|
||||
itemVo.setReplyCount(0);
|
||||
return itemVo;
|
||||
})
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
|
||||
return resultPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [验证目标对象是否存在]
|
||||
* @author Leocoder
|
||||
*/
|
||||
private void validateTargetExists(String targetType, Long targetId) {
|
||||
TargetTypeEnum typeEnum = TargetTypeEnum.fromCode(targetType);
|
||||
if (typeEnum == null) {
|
||||
throw new BusinessException(400, "无效的目标类型");
|
||||
}
|
||||
|
||||
switch (typeEnum) {
|
||||
case HERITAGE:
|
||||
HrtHeritage heritage = hrtHeritageMapper.selectById(targetId);
|
||||
if (heritage == null || heritage.getPublishStatus() != 1) {
|
||||
throw new BusinessException(404, "非遗项目不存在或未发布");
|
||||
}
|
||||
break;
|
||||
case INHERITOR:
|
||||
HrtInheritor inheritor = hrtInheritorMapper.selectById(targetId);
|
||||
if (inheritor == null || inheritor.getPublishStatus() != 1) {
|
||||
throw new BusinessException(404, "传承人不存在或未发布");
|
||||
}
|
||||
break;
|
||||
case NEWS:
|
||||
HrtNews news = hrtNewsMapper.selectById(targetId);
|
||||
if (news == null || news.getPublishStatus() != 1) {
|
||||
throw new BusinessException(404, "新闻不存在或未发布");
|
||||
}
|
||||
break;
|
||||
case COMMENT:
|
||||
throw new BusinessException(400, "不支持对评论进行评论");
|
||||
default:
|
||||
throw new BusinessException(400, "不支持的目标类型");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [构建评论项VO]
|
||||
* @author Leocoder
|
||||
*/
|
||||
private CommentItemVo buildCommentItemVo(HrtComment comment,
|
||||
Map<Long, HrtUser> userMap,
|
||||
Map<Long, List<HrtComment>> replyMap,
|
||||
Map<Long, Boolean> likeMap) {
|
||||
CommentItemVo itemVo = BeanUtil.copyProperties(comment, CommentItemVo.class);
|
||||
|
||||
HrtUser user = userMap.get(comment.getUserId());
|
||||
if (user != null) {
|
||||
itemVo.setUserName(user.getNickname());
|
||||
itemVo.setUserAvatar(user.getAvatar());
|
||||
}
|
||||
|
||||
itemVo.setIsLiked(likeMap.getOrDefault(comment.getId(), false));
|
||||
|
||||
List<HrtComment> replies = replyMap.getOrDefault(comment.getId(), List.of());
|
||||
itemVo.setReplyCount(replies.size());
|
||||
|
||||
if (!replies.isEmpty()) {
|
||||
List<CommentItemVo> replyVos = replies.stream()
|
||||
.map(reply -> {
|
||||
CommentItemVo replyVo = BeanUtil.copyProperties(reply, CommentItemVo.class);
|
||||
HrtUser replyUser = userMap.get(reply.getUserId());
|
||||
if (replyUser != null) {
|
||||
replyVo.setUserName(replyUser.getNickname());
|
||||
replyVo.setUserAvatar(replyUser.getAvatar());
|
||||
}
|
||||
replyVo.setIsLiked(likeMap.getOrDefault(reply.getId(), false));
|
||||
replyVo.setReplyCount(0);
|
||||
return replyVo;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
itemVo.setReplies(replyVos);
|
||||
} else {
|
||||
itemVo.setReplies(new ArrayList<>());
|
||||
}
|
||||
|
||||
return itemVo;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
package org.leocoder.heritage.portal.service.event;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.*;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [活动管理服务接口]
|
||||
*/
|
||||
public interface HrtEventService {
|
||||
|
||||
/**
|
||||
* @description [活动列表]
|
||||
* @author Leocoder
|
||||
*/
|
||||
IPage<EventListVo> listEvents(EventQueryVo vo);
|
||||
|
||||
/**
|
||||
* @description [活动详情]
|
||||
* @author Leocoder
|
||||
*/
|
||||
EventDetailVo getEventDetail(Long eventId);
|
||||
|
||||
/**
|
||||
* @description [报名参加活动]
|
||||
* @author Leocoder
|
||||
*/
|
||||
String registerEvent(EventRegistrationVo vo);
|
||||
|
||||
/**
|
||||
* @description [取消报名]
|
||||
* @author Leocoder
|
||||
*/
|
||||
String cancelRegistration(Long eventId);
|
||||
|
||||
/**
|
||||
* @description [我报名的活动]
|
||||
* @author Leocoder
|
||||
*/
|
||||
IPage<MyEventRegistrationItemVo> myRegistrations(MyEventRegistrationVo vo);
|
||||
|
||||
}
|
||||
@ -0,0 +1,320 @@
|
||||
package org.leocoder.heritage.portal.service.event;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.leocoder.heritage.common.exception.BusinessException;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.*;
|
||||
import org.leocoder.heritage.domain.pojo.portal.HrtEvent;
|
||||
import org.leocoder.heritage.domain.pojo.portal.HrtEventRegistration;
|
||||
import org.leocoder.heritage.mybatisplus.mapper.portal.HrtEventMapper;
|
||||
import org.leocoder.heritage.mybatisplus.mapper.portal.HrtEventRegistrationMapper;
|
||||
import org.leocoder.heritage.portal.service.user.HrtUserService;
|
||||
import org.leocoder.heritage.satoken.util.HrtStpUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [活动管理服务实现类]
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class HrtEventServiceImpl implements HrtEventService {
|
||||
|
||||
private final HrtEventMapper hrtEventMapper;
|
||||
private final HrtEventRegistrationMapper hrtEventRegistrationMapper;
|
||||
private final HrtUserService hrtUserService;
|
||||
|
||||
/**
|
||||
* @description [活动列表]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public IPage<EventListVo> listEvents(EventQueryVo vo) {
|
||||
Long currentUserId = null;
|
||||
try {
|
||||
currentUserId = HrtStpUtil.getLoginIdAsLong();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
Page<HrtEvent> page = new Page<>(vo.getPageNum(), vo.getPageSize());
|
||||
|
||||
LambdaQueryWrapper<HrtEvent> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(HrtEvent::getPublishStatus, 1);
|
||||
|
||||
if (vo.getStatus() != null && !vo.getStatus().isEmpty()) {
|
||||
wrapper.eq(HrtEvent::getStatus, vo.getStatus());
|
||||
}
|
||||
|
||||
if (vo.getKeyword() != null && !vo.getKeyword().isEmpty()) {
|
||||
wrapper.and(w -> w.like(HrtEvent::getTitle, vo.getKeyword())
|
||||
.or()
|
||||
.like(HrtEvent::getSummary, vo.getKeyword())
|
||||
.or()
|
||||
.like(HrtEvent::getContent, vo.getKeyword()));
|
||||
}
|
||||
|
||||
wrapper.orderByDesc(HrtEvent::getStartTime);
|
||||
|
||||
IPage<HrtEvent> eventPage = hrtEventMapper.selectPage(page, wrapper);
|
||||
|
||||
Map<Long, Boolean> registrationMap = Map.of();
|
||||
if (currentUserId != null && !eventPage.getRecords().isEmpty()) {
|
||||
List<Long> eventIds = eventPage.getRecords().stream()
|
||||
.map(HrtEvent::getId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
LambdaQueryWrapper<HrtEventRegistration> regWrapper = new LambdaQueryWrapper<>();
|
||||
regWrapper.eq(HrtEventRegistration::getUserId, currentUserId)
|
||||
.in(HrtEventRegistration::getEventId, eventIds)
|
||||
.in(HrtEventRegistration::getStatus, List.of("pending", "approved"));
|
||||
|
||||
registrationMap = hrtEventRegistrationMapper.selectList(regWrapper).stream()
|
||||
.collect(Collectors.toMap(HrtEventRegistration::getEventId, reg -> true));
|
||||
}
|
||||
|
||||
final Map<Long, Boolean> finalRegistrationMap = registrationMap;
|
||||
|
||||
Page<EventListVo> resultPage = new Page<>(vo.getPageNum(), vo.getPageSize());
|
||||
resultPage.setTotal(eventPage.getTotal());
|
||||
resultPage.setRecords(eventPage.getRecords().stream()
|
||||
.map(event -> {
|
||||
EventListVo listVo = BeanUtil.copyProperties(event, EventListVo.class);
|
||||
listVo.setIsRegistered(finalRegistrationMap.getOrDefault(event.getId(), false));
|
||||
return listVo;
|
||||
})
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
|
||||
return resultPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [活动详情]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public EventDetailVo getEventDetail(Long eventId) {
|
||||
HrtEvent event = hrtEventMapper.selectById(eventId);
|
||||
if (event == null || event.getPublishStatus() != 1) {
|
||||
throw new BusinessException(404, "活动不存在或未发布");
|
||||
}
|
||||
|
||||
hrtEventMapper.update(null,
|
||||
new LambdaUpdateWrapper<HrtEvent>()
|
||||
.setSql("view_count = view_count + 1")
|
||||
.eq(HrtEvent::getId, eventId)
|
||||
);
|
||||
|
||||
EventDetailVo detailVo = BeanUtil.copyProperties(event, EventDetailVo.class);
|
||||
detailVo.setViewCount(event.getViewCount() + 1);
|
||||
|
||||
hrtUserService.recordViewHistory("event", eventId);
|
||||
|
||||
Long currentUserId = null;
|
||||
try {
|
||||
currentUserId = HrtStpUtil.getLoginIdAsLong();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
if (currentUserId != null) {
|
||||
LambdaQueryWrapper<HrtEventRegistration> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(HrtEventRegistration::getUserId, currentUserId)
|
||||
.eq(HrtEventRegistration::getEventId, eventId)
|
||||
.in(HrtEventRegistration::getStatus, List.of("pending", "approved"));
|
||||
Long count = hrtEventRegistrationMapper.selectCount(wrapper);
|
||||
detailVo.setIsRegistered(count > 0);
|
||||
}
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
boolean canRegister = event.getStatus().equals("upcoming")
|
||||
&& event.getRegistrationStart() != null
|
||||
&& event.getRegistrationEnd() != null
|
||||
&& now.isAfter(event.getRegistrationStart())
|
||||
&& now.isBefore(event.getRegistrationEnd())
|
||||
&& (event.getMaxParticipants() == null || event.getCurrentParticipants() < event.getMaxParticipants())
|
||||
&& !detailVo.getIsRegistered();
|
||||
detailVo.setCanRegister(canRegister);
|
||||
|
||||
return detailVo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [报名参加活动]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String registerEvent(EventRegistrationVo vo) {
|
||||
Long userId = HrtStpUtil.getLoginIdAsLong();
|
||||
|
||||
HrtEvent event = hrtEventMapper.selectById(vo.getEventId());
|
||||
if (event == null || event.getPublishStatus() != 1) {
|
||||
throw new BusinessException(404, "活动不存在或未发布");
|
||||
}
|
||||
|
||||
if (!event.getStatus().equals("upcoming")) {
|
||||
throw new BusinessException(400, "该活动不在报名阶段");
|
||||
}
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (event.getRegistrationStart() == null || event.getRegistrationEnd() == null) {
|
||||
throw new BusinessException(400, "活动报名时间未设置");
|
||||
}
|
||||
|
||||
if (now.isBefore(event.getRegistrationStart())) {
|
||||
throw new BusinessException(400, "报名尚未开始");
|
||||
}
|
||||
|
||||
if (now.isAfter(event.getRegistrationEnd())) {
|
||||
throw new BusinessException(400, "报名已结束");
|
||||
}
|
||||
|
||||
LambdaQueryWrapper<HrtEventRegistration> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(HrtEventRegistration::getUserId, userId)
|
||||
.eq(HrtEventRegistration::getEventId, vo.getEventId())
|
||||
.in(HrtEventRegistration::getStatus, List.of("pending", "approved"));
|
||||
Long count = hrtEventRegistrationMapper.selectCount(wrapper);
|
||||
if (count > 0) {
|
||||
throw new BusinessException(400, "您已报名该活动");
|
||||
}
|
||||
|
||||
if (event.getMaxParticipants() != null && event.getCurrentParticipants() >= event.getMaxParticipants()) {
|
||||
throw new BusinessException(400, "活动报名人数已满");
|
||||
}
|
||||
|
||||
HrtEventRegistration registration = new HrtEventRegistration();
|
||||
registration.setUserId(userId);
|
||||
registration.setEventId(vo.getEventId());
|
||||
registration.setStatus("approved");
|
||||
registration.setPhone(vo.getPhone());
|
||||
registration.setRemark(vo.getRemark());
|
||||
|
||||
int result = hrtEventRegistrationMapper.insert(registration);
|
||||
if (result <= 0) {
|
||||
throw new BusinessException(500, "报名失败");
|
||||
}
|
||||
|
||||
hrtEventMapper.update(null,
|
||||
new LambdaUpdateWrapper<HrtEvent>()
|
||||
.setSql("current_participants = current_participants + 1")
|
||||
.eq(HrtEvent::getId, vo.getEventId())
|
||||
);
|
||||
|
||||
return "报名成功";
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [取消报名]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String cancelRegistration(Long eventId) {
|
||||
Long userId = HrtStpUtil.getLoginIdAsLong();
|
||||
|
||||
LambdaQueryWrapper<HrtEventRegistration> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(HrtEventRegistration::getUserId, userId)
|
||||
.eq(HrtEventRegistration::getEventId, eventId)
|
||||
.in(HrtEventRegistration::getStatus, List.of("pending", "approved"));
|
||||
HrtEventRegistration registration = hrtEventRegistrationMapper.selectOne(wrapper);
|
||||
|
||||
if (registration == null) {
|
||||
throw new BusinessException(400, "您未报名该活动");
|
||||
}
|
||||
|
||||
HrtEvent event = hrtEventMapper.selectById(eventId);
|
||||
if (event == null) {
|
||||
throw new BusinessException(404, "活动不存在");
|
||||
}
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (event.getStartTime() != null && now.isAfter(event.getStartTime())) {
|
||||
throw new BusinessException(400, "活动已开始,无法取消报名");
|
||||
}
|
||||
|
||||
registration.setStatus("cancelled");
|
||||
int result = hrtEventRegistrationMapper.updateById(registration);
|
||||
if (result <= 0) {
|
||||
throw new BusinessException(500, "取消报名失败");
|
||||
}
|
||||
|
||||
hrtEventMapper.update(null,
|
||||
new LambdaUpdateWrapper<HrtEvent>()
|
||||
.setSql("current_participants = GREATEST(current_participants - 1, 0)")
|
||||
.eq(HrtEvent::getId, eventId)
|
||||
);
|
||||
|
||||
return "取消报名成功";
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [我报名的活动]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public IPage<MyEventRegistrationItemVo> myRegistrations(MyEventRegistrationVo vo) {
|
||||
Long userId = HrtStpUtil.getLoginIdAsLong();
|
||||
|
||||
Page<HrtEventRegistration> page = new Page<>(vo.getPageNum(), vo.getPageSize());
|
||||
|
||||
LambdaQueryWrapper<HrtEventRegistration> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(HrtEventRegistration::getUserId, userId);
|
||||
|
||||
if (vo.getStatus() != null && !vo.getStatus().isEmpty()) {
|
||||
wrapper.eq(HrtEventRegistration::getStatus, vo.getStatus());
|
||||
}
|
||||
|
||||
wrapper.orderByDesc(HrtEventRegistration::getCreateTime);
|
||||
|
||||
IPage<HrtEventRegistration> registrationPage = hrtEventRegistrationMapper.selectPage(page, wrapper);
|
||||
|
||||
List<Long> eventIds = registrationPage.getRecords().stream()
|
||||
.map(HrtEventRegistration::getEventId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Map<Long, HrtEvent> eventMap = eventIds.isEmpty() ? Map.of() :
|
||||
hrtEventMapper.selectBatchIds(eventIds).stream()
|
||||
.collect(Collectors.toMap(HrtEvent::getId, event -> event));
|
||||
|
||||
Page<MyEventRegistrationItemVo> resultPage = new Page<>(vo.getPageNum(), vo.getPageSize());
|
||||
resultPage.setTotal(registrationPage.getTotal());
|
||||
resultPage.setRecords(registrationPage.getRecords().stream()
|
||||
.map(registration -> {
|
||||
MyEventRegistrationItemVo itemVo = new MyEventRegistrationItemVo();
|
||||
itemVo.setId(registration.getId());
|
||||
itemVo.setEventId(registration.getEventId());
|
||||
itemVo.setRegistrationStatus(registration.getStatus());
|
||||
itemVo.setPhone(registration.getPhone());
|
||||
itemVo.setRemark(registration.getRemark());
|
||||
itemVo.setCreateTime(registration.getCreateTime());
|
||||
|
||||
HrtEvent event = eventMap.get(registration.getEventId());
|
||||
if (event != null) {
|
||||
itemVo.setEventTitle(event.getTitle());
|
||||
itemVo.setEventCoverImage(event.getCoverImage());
|
||||
itemVo.setEventLocation(event.getLocation());
|
||||
itemVo.setEventStartTime(event.getStartTime());
|
||||
itemVo.setEventEndTime(event.getEndTime());
|
||||
itemVo.setEventStatus(event.getStatus());
|
||||
}
|
||||
|
||||
return itemVo;
|
||||
})
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
|
||||
return resultPage;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package org.leocoder.heritage.portal.service.favorite;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.FavoriteItemVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.FavoriteOperateVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.FavoriteQueryVo;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [收藏服务接口]
|
||||
*/
|
||||
public interface HrtFavoriteService {
|
||||
|
||||
/**
|
||||
* @description [添加收藏]
|
||||
* @author Leocoder
|
||||
*/
|
||||
String addFavorite(FavoriteOperateVo vo);
|
||||
|
||||
/**
|
||||
* @description [取消收藏]
|
||||
* @author Leocoder
|
||||
*/
|
||||
String cancelFavorite(FavoriteOperateVo vo);
|
||||
|
||||
/**
|
||||
* @description [我的收藏列表]
|
||||
* @author Leocoder
|
||||
*/
|
||||
IPage<FavoriteItemVo> myFavoriteList(FavoriteQueryVo vo);
|
||||
|
||||
/**
|
||||
* @description [检查是否已收藏]
|
||||
* @author Leocoder
|
||||
*/
|
||||
Boolean checkFavorite(String targetType, Long targetId);
|
||||
|
||||
}
|
||||
@ -0,0 +1,306 @@
|
||||
package org.leocoder.heritage.portal.service.favorite;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.leocoder.heritage.common.exception.BusinessException;
|
||||
import org.leocoder.heritage.domain.enums.TargetTypeEnum;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.FavoriteItemVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.FavoriteOperateVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.FavoriteQueryVo;
|
||||
import org.leocoder.heritage.domain.pojo.portal.HrtFavorite;
|
||||
import org.leocoder.heritage.domain.pojo.portal.HrtHeritage;
|
||||
import org.leocoder.heritage.domain.pojo.portal.HrtInheritor;
|
||||
import org.leocoder.heritage.domain.pojo.portal.HrtNews;
|
||||
import org.leocoder.heritage.mybatisplus.mapper.portal.HrtFavoriteMapper;
|
||||
import org.leocoder.heritage.mybatisplus.mapper.portal.HrtHeritageMapper;
|
||||
import org.leocoder.heritage.mybatisplus.mapper.portal.HrtInheritorMapper;
|
||||
import org.leocoder.heritage.mybatisplus.mapper.portal.HrtNewsMapper;
|
||||
import org.leocoder.heritage.satoken.util.HrtStpUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [收藏服务实现类]
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class HrtFavoriteServiceImpl implements HrtFavoriteService {
|
||||
|
||||
private final HrtFavoriteMapper hrtFavoriteMapper;
|
||||
private final HrtHeritageMapper hrtHeritageMapper;
|
||||
private final HrtInheritorMapper hrtInheritorMapper;
|
||||
private final HrtNewsMapper hrtNewsMapper;
|
||||
|
||||
/**
|
||||
* @description [添加收藏]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String addFavorite(FavoriteOperateVo vo) {
|
||||
// 1、验证目标类型是否支持收藏
|
||||
if (!TargetTypeEnum.isFavoriteSupported(vo.getTargetType())) {
|
||||
throw new BusinessException(400, "不支持的收藏类型");
|
||||
}
|
||||
|
||||
// 2、获取当前登录用户ID
|
||||
Long userId = HrtStpUtil.getLoginIdAsLong();
|
||||
|
||||
// 3、检查是否已收藏(查询有效记录)
|
||||
LambdaQueryWrapper<HrtFavorite> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(HrtFavorite::getUserId, userId)
|
||||
.eq(HrtFavorite::getTargetType, vo.getTargetType())
|
||||
.eq(HrtFavorite::getTargetId, vo.getTargetId());
|
||||
HrtFavorite existingFavorite = hrtFavoriteMapper.selectOne(queryWrapper);
|
||||
if (existingFavorite != null) {
|
||||
throw new BusinessException(400, "您已经收藏过了");
|
||||
}
|
||||
|
||||
// 4、检查是否存在逻辑删除的记录(使用自定义方法绕过@TableLogic过滤)
|
||||
HrtFavorite deletedFavorite = hrtFavoriteMapper.selectDeletedOne(userId, vo.getTargetType(), vo.getTargetId());
|
||||
|
||||
if (deletedFavorite != null) {
|
||||
// 5、恢复逻辑删除的记录(使用原生SQL绕过@TableLogic限制)
|
||||
int result = hrtFavoriteMapper.restoreDeleted(deletedFavorite.getId());
|
||||
if (result <= 0) {
|
||||
throw new BusinessException(500, "收藏失败");
|
||||
}
|
||||
} else {
|
||||
// 6、验证目标对象是否存在且已发布
|
||||
validateTargetExists(vo.getTargetType(), vo.getTargetId());
|
||||
|
||||
// 7、插入新的收藏记录
|
||||
HrtFavorite favorite = new HrtFavorite();
|
||||
favorite.setUserId(userId);
|
||||
favorite.setTargetType(vo.getTargetType());
|
||||
favorite.setTargetId(vo.getTargetId());
|
||||
int result = hrtFavoriteMapper.insert(favorite);
|
||||
if (result <= 0) {
|
||||
throw new BusinessException(500, "收藏失败");
|
||||
}
|
||||
}
|
||||
|
||||
// 8、更新目标对象的收藏计数 +1
|
||||
updateFavoriteCount(vo.getTargetType(), vo.getTargetId(), 1);
|
||||
|
||||
return "收藏成功";
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [取消收藏]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String cancelFavorite(FavoriteOperateVo vo) {
|
||||
// 1、获取当前登录用户ID
|
||||
Long userId = HrtStpUtil.getLoginIdAsLong();
|
||||
|
||||
// 2、查找收藏记录
|
||||
LambdaQueryWrapper<HrtFavorite> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(HrtFavorite::getUserId, userId)
|
||||
.eq(HrtFavorite::getTargetType, vo.getTargetType())
|
||||
.eq(HrtFavorite::getTargetId, vo.getTargetId());
|
||||
HrtFavorite favorite = hrtFavoriteMapper.selectOne(queryWrapper);
|
||||
if (favorite == null) {
|
||||
throw new BusinessException(400, "您还未收藏该内容");
|
||||
}
|
||||
|
||||
// 3、逻辑删除收藏记录
|
||||
int result = hrtFavoriteMapper.deleteById(favorite.getId());
|
||||
if (result <= 0) {
|
||||
throw new BusinessException(500, "取消收藏失败");
|
||||
}
|
||||
|
||||
// 4、更新目标对象的收藏计数 -1
|
||||
updateFavoriteCount(vo.getTargetType(), vo.getTargetId(), -1);
|
||||
|
||||
return "取消收藏成功";
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [我的收藏列表]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public IPage<FavoriteItemVo> myFavoriteList(FavoriteQueryVo vo) {
|
||||
// 1、获取当前登录用户ID
|
||||
Long userId = HrtStpUtil.getLoginIdAsLong();
|
||||
|
||||
// 2、分页查询收藏记录
|
||||
Page<HrtFavorite> page = new Page<>(vo.getPageNum(), vo.getPageSize());
|
||||
LambdaQueryWrapper<HrtFavorite> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(HrtFavorite::getUserId, userId);
|
||||
|
||||
// 按类型筛选(可选)
|
||||
if (StrUtil.isNotBlank(vo.getTargetType())) {
|
||||
if (!TargetTypeEnum.isFavoriteSupported(vo.getTargetType())) {
|
||||
throw new BusinessException(400, "不支持的收藏类型");
|
||||
}
|
||||
queryWrapper.eq(HrtFavorite::getTargetType, vo.getTargetType());
|
||||
}
|
||||
|
||||
// 按创建时间倒序
|
||||
queryWrapper.orderByDesc(HrtFavorite::getCreateTime);
|
||||
|
||||
IPage<HrtFavorite> favoritePageResult = hrtFavoriteMapper.selectPage(page, queryWrapper);
|
||||
|
||||
// 3、转换为VO并填充目标对象信息
|
||||
Page<FavoriteItemVo> resultPage = new Page<>(vo.getPageNum(), vo.getPageSize());
|
||||
resultPage.setTotal(favoritePageResult.getTotal());
|
||||
resultPage.setPages(favoritePageResult.getPages());
|
||||
|
||||
List<FavoriteItemVo> itemList = new ArrayList<>();
|
||||
for (HrtFavorite favorite : favoritePageResult.getRecords()) {
|
||||
FavoriteItemVo itemVo = new FavoriteItemVo();
|
||||
itemVo.setId(favorite.getId());
|
||||
itemVo.setTargetType(favorite.getTargetType());
|
||||
itemVo.setTargetId(favorite.getTargetId());
|
||||
itemVo.setCreateTime(favorite.getCreateTime());
|
||||
|
||||
// 填充目标对象信息
|
||||
fillTargetInfo(itemVo, favorite.getTargetType(), favorite.getTargetId());
|
||||
itemList.add(itemVo);
|
||||
}
|
||||
|
||||
resultPage.setRecords(itemList);
|
||||
return resultPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [检查是否已收藏]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public Boolean checkFavorite(String targetType, Long targetId) {
|
||||
// 1、获取当前登录用户ID
|
||||
Long userId = HrtStpUtil.getLoginIdAsLong();
|
||||
|
||||
// 2、查询收藏记录是否存在
|
||||
LambdaQueryWrapper<HrtFavorite> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(HrtFavorite::getUserId, userId)
|
||||
.eq(HrtFavorite::getTargetType, targetType)
|
||||
.eq(HrtFavorite::getTargetId, targetId);
|
||||
Long count = hrtFavoriteMapper.selectCount(queryWrapper);
|
||||
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [验证目标对象是否存在且已发布]
|
||||
* @author Leocoder
|
||||
*/
|
||||
private void validateTargetExists(String targetType, Long targetId) {
|
||||
TargetTypeEnum typeEnum = TargetTypeEnum.fromCode(targetType);
|
||||
if (typeEnum == null) {
|
||||
throw new BusinessException(400, "无效的目标类型");
|
||||
}
|
||||
|
||||
switch (typeEnum) {
|
||||
case HERITAGE:
|
||||
HrtHeritage heritage = hrtHeritageMapper.selectById(targetId);
|
||||
if (heritage == null || heritage.getPublishStatus() != 1) {
|
||||
throw new BusinessException(404, "非遗项目不存在或未发布");
|
||||
}
|
||||
break;
|
||||
case INHERITOR:
|
||||
HrtInheritor inheritor = hrtInheritorMapper.selectById(targetId);
|
||||
if (inheritor == null || inheritor.getPublishStatus() != 1) {
|
||||
throw new BusinessException(404, "传承人不存在或未发布");
|
||||
}
|
||||
break;
|
||||
case NEWS:
|
||||
HrtNews news = hrtNewsMapper.selectById(targetId);
|
||||
if (news == null || news.getPublishStatus() != 1) {
|
||||
throw new BusinessException(404, "新闻不存在或未发布");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new BusinessException(400, "不支持的收藏类型");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [更新收藏计数(原子操作)]
|
||||
* @author Leocoder
|
||||
*/
|
||||
private void updateFavoriteCount(String targetType, Long targetId, int delta) {
|
||||
TargetTypeEnum typeEnum = TargetTypeEnum.fromCode(targetType);
|
||||
if (typeEnum == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String sqlSet = delta > 0
|
||||
? "favorite_count = favorite_count + " + delta
|
||||
: "favorite_count = GREATEST(favorite_count + (" + delta + "), 0)";
|
||||
|
||||
switch (typeEnum) {
|
||||
case HERITAGE:
|
||||
hrtHeritageMapper.update(null,
|
||||
new LambdaUpdateWrapper<HrtHeritage>()
|
||||
.setSql(sqlSet)
|
||||
.eq(HrtHeritage::getId, targetId)
|
||||
);
|
||||
break;
|
||||
case INHERITOR:
|
||||
// 传承人表没有收藏计数字段,跳过
|
||||
break;
|
||||
case NEWS:
|
||||
// 新闻表没有收藏计数字段,跳过
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [填充目标对象信息]
|
||||
* @author Leocoder
|
||||
*/
|
||||
private void fillTargetInfo(FavoriteItemVo itemVo, String targetType, Long targetId) {
|
||||
TargetTypeEnum typeEnum = TargetTypeEnum.fromCode(targetType);
|
||||
if (typeEnum == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (typeEnum) {
|
||||
case HERITAGE:
|
||||
HrtHeritage heritage = hrtHeritageMapper.selectById(targetId);
|
||||
if (heritage != null) {
|
||||
itemVo.setTargetTitle(heritage.getName());
|
||||
itemVo.setTargetCover(heritage.getCoverImage());
|
||||
itemVo.setTargetDescription(heritage.getDescription());
|
||||
itemVo.setTargetViewCount(heritage.getViewCount());
|
||||
itemVo.setTargetLikeCount(heritage.getLikeCount());
|
||||
}
|
||||
break;
|
||||
case INHERITOR:
|
||||
HrtInheritor inheritor = hrtInheritorMapper.selectById(targetId);
|
||||
if (inheritor != null) {
|
||||
itemVo.setTargetTitle(inheritor.getName());
|
||||
itemVo.setTargetCover(inheritor.getAvatar());
|
||||
itemVo.setTargetDescription(inheritor.getIntroduction());
|
||||
itemVo.setTargetViewCount(inheritor.getViewCount());
|
||||
itemVo.setTargetLikeCount(inheritor.getLikeCount());
|
||||
}
|
||||
break;
|
||||
case NEWS:
|
||||
HrtNews news = hrtNewsMapper.selectById(targetId);
|
||||
if (news != null) {
|
||||
itemVo.setTargetTitle(news.getTitle());
|
||||
itemVo.setTargetCover(news.getCoverImage());
|
||||
itemVo.setTargetDescription(news.getSummary());
|
||||
itemVo.setTargetViewCount(news.getViewCount());
|
||||
itemVo.setTargetLikeCount(news.getLikeCount());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
package org.leocoder.heritage.portal.service.heritage;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtHeritageDetailVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtHeritageListVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtHeritageQueryVo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [非遗项目服务接口]
|
||||
*/
|
||||
public interface HrtHeritageService {
|
||||
|
||||
/**
|
||||
* @description [分页查询非遗项目列表]
|
||||
* @author Leocoder
|
||||
*/
|
||||
IPage<HrtHeritageListVo> listPage(HrtHeritageQueryVo vo);
|
||||
|
||||
/**
|
||||
* @description [查看非遗项目详情]
|
||||
* @author Leocoder
|
||||
*/
|
||||
HrtHeritageDetailVo getDetail(Long id);
|
||||
|
||||
/**
|
||||
* @description [热门非遗项目]
|
||||
* @author Leocoder
|
||||
*/
|
||||
List<HrtHeritageListVo> getHotList(Integer limit);
|
||||
|
||||
/**
|
||||
* @description [精选非遗项目]
|
||||
* @author Leocoder
|
||||
*/
|
||||
List<HrtHeritageListVo> getFeaturedList(Integer limit);
|
||||
|
||||
}
|
||||
@ -0,0 +1,194 @@
|
||||
package org.leocoder.heritage.portal.service.heritage;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.leocoder.heritage.common.exception.BusinessException;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtHeritageDetailVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtHeritageListVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtHeritageQueryVo;
|
||||
import org.leocoder.heritage.domain.pojo.portal.HrtHeritage;
|
||||
import org.leocoder.heritage.mybatisplus.mapper.portal.HrtHeritageMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [非遗项目服务实现类]
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class HrtHeritageServiceImpl implements HrtHeritageService {
|
||||
|
||||
private final HrtHeritageMapper hrtHeritageMapper;
|
||||
private final org.leocoder.heritage.portal.service.user.HrtUserService hrtUserService;
|
||||
|
||||
/**
|
||||
* @description [分页查询非遗项目列表]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public IPage<HrtHeritageListVo> listPage(HrtHeritageQueryVo vo) {
|
||||
Page<HrtHeritage> page = new Page<>(vo.getPageNum(), vo.getPageSize());
|
||||
|
||||
LambdaQueryWrapper<HrtHeritage> wrapper = buildQueryWrapper(vo);
|
||||
|
||||
// 只查询已发布的项目
|
||||
wrapper.eq(HrtHeritage::getPublishStatus, 1);
|
||||
|
||||
// 排序
|
||||
applySorting(wrapper, vo.getSortField(), vo.getSortOrder());
|
||||
|
||||
IPage<HrtHeritage> heritagePageResult = hrtHeritageMapper.selectPage(page, wrapper);
|
||||
|
||||
// 转换为VO
|
||||
return heritagePageResult.convert(heritage -> BeanUtil.copyProperties(heritage, HrtHeritageListVo.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [查看非遗项目详情]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public HrtHeritageDetailVo getDetail(Long id) {
|
||||
HrtHeritage heritage = hrtHeritageMapper.selectById(id);
|
||||
|
||||
if (heritage == null || heritage.getPublishStatus() != 1) {
|
||||
throw new BusinessException(404, "非遗项目不存在或未发布");
|
||||
}
|
||||
|
||||
// 浏览量+1
|
||||
HrtHeritage updateHeritage = new HrtHeritage();
|
||||
updateHeritage.setId(id);
|
||||
updateHeritage.setViewCount(heritage.getViewCount() + 1);
|
||||
hrtHeritageMapper.updateById(updateHeritage);
|
||||
|
||||
// 记录浏览历史
|
||||
hrtUserService.recordViewHistory("heritage", id);
|
||||
|
||||
// 转换为详情VO
|
||||
HrtHeritageDetailVo detailVo = BeanUtil.copyProperties(heritage, HrtHeritageDetailVo.class);
|
||||
detailVo.setViewCount(heritage.getViewCount() + 1);
|
||||
|
||||
return detailVo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [热门非遗项目]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public List<HrtHeritageListVo> getHotList(Integer limit) {
|
||||
// 默认查询10条
|
||||
if (limit == null || limit <= 0) {
|
||||
limit = 10;
|
||||
}
|
||||
|
||||
LambdaQueryWrapper<HrtHeritage> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(HrtHeritage::getPublishStatus, 1)
|
||||
.orderByDesc(HrtHeritage::getViewCount, HrtHeritage::getLikeCount)
|
||||
.last("LIMIT " + limit);
|
||||
|
||||
List<HrtHeritage> heritageList = hrtHeritageMapper.selectList(wrapper);
|
||||
|
||||
return heritageList.stream()
|
||||
.map(heritage -> BeanUtil.copyProperties(heritage, HrtHeritageListVo.class))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [精选非遗项目]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public List<HrtHeritageListVo> getFeaturedList(Integer limit) {
|
||||
// 默认查询10条
|
||||
if (limit == null || limit <= 0) {
|
||||
limit = 10;
|
||||
}
|
||||
|
||||
LambdaQueryWrapper<HrtHeritage> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(HrtHeritage::getPublishStatus, 1)
|
||||
.eq(HrtHeritage::getIsFeatured, 1)
|
||||
.orderByDesc(HrtHeritage::getSortOrder, HrtHeritage::getCreateTime)
|
||||
.last("LIMIT " + limit);
|
||||
|
||||
List<HrtHeritage> heritageList = hrtHeritageMapper.selectList(wrapper);
|
||||
|
||||
return heritageList.stream()
|
||||
.map(heritage -> BeanUtil.copyProperties(heritage, HrtHeritageListVo.class))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [构建查询条件]
|
||||
* @author Leocoder
|
||||
*/
|
||||
private LambdaQueryWrapper<HrtHeritage> buildQueryWrapper(HrtHeritageQueryVo vo) {
|
||||
LambdaQueryWrapper<HrtHeritage> wrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
// 关键词搜索(搜索名称、英文名称、描述)
|
||||
if (StrUtil.isNotBlank(vo.getKeyword())) {
|
||||
wrapper.and(w -> w
|
||||
.like(HrtHeritage::getName, vo.getKeyword())
|
||||
.or().like(HrtHeritage::getNameEn, vo.getKeyword())
|
||||
.or().like(HrtHeritage::getDescription, vo.getKeyword())
|
||||
);
|
||||
}
|
||||
|
||||
// 名称模糊查询(兼容旧版本)
|
||||
wrapper.like(StrUtil.isNotBlank(vo.getName()), HrtHeritage::getName, vo.getName());
|
||||
|
||||
// 分类
|
||||
wrapper.eq(StrUtil.isNotBlank(vo.getCategory()), HrtHeritage::getCategory, vo.getCategory());
|
||||
|
||||
// 级别
|
||||
wrapper.eq(StrUtil.isNotBlank(vo.getLevel()), HrtHeritage::getLevel, vo.getLevel());
|
||||
|
||||
// 省份
|
||||
wrapper.eq(StrUtil.isNotBlank(vo.getProvince()), HrtHeritage::getProvince, vo.getProvince());
|
||||
|
||||
// 城市
|
||||
wrapper.eq(StrUtil.isNotBlank(vo.getCity()), HrtHeritage::getCity, vo.getCity());
|
||||
|
||||
// 状态
|
||||
wrapper.eq(StrUtil.isNotBlank(vo.getStatus()), HrtHeritage::getStatus, vo.getStatus());
|
||||
|
||||
// 标签模糊匹配
|
||||
wrapper.like(StrUtil.isNotBlank(vo.getTag()), HrtHeritage::getTags, vo.getTag());
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [应用排序规则]
|
||||
* @author Leocoder
|
||||
*/
|
||||
private void applySorting(LambdaQueryWrapper<HrtHeritage> wrapper, String sortField, String sortOrder) {
|
||||
boolean isAsc = "asc".equalsIgnoreCase(sortOrder);
|
||||
|
||||
switch (sortField) {
|
||||
case "view_count":
|
||||
wrapper.orderBy(true, isAsc, HrtHeritage::getViewCount);
|
||||
break;
|
||||
case "like_count":
|
||||
wrapper.orderBy(true, isAsc, HrtHeritage::getLikeCount);
|
||||
break;
|
||||
case "favorite_count":
|
||||
wrapper.orderBy(true, isAsc, HrtHeritage::getFavoriteCount);
|
||||
break;
|
||||
case "create_time":
|
||||
default:
|
||||
wrapper.orderBy(true, isAsc, HrtHeritage::getCreateTime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
package org.leocoder.heritage.portal.service.inheritor;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtInheritorDetailVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtInheritorListVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtInheritorQueryVo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [传承人服务接口]
|
||||
*/
|
||||
public interface HrtInheritorService {
|
||||
|
||||
/**
|
||||
* @description [分页查询传承人列表]
|
||||
* @author Leocoder
|
||||
*/
|
||||
IPage<HrtInheritorListVo> listPage(HrtInheritorQueryVo vo);
|
||||
|
||||
/**
|
||||
* @description [查看传承人详情]
|
||||
* @author Leocoder
|
||||
*/
|
||||
HrtInheritorDetailVo getDetail(Long id);
|
||||
|
||||
/**
|
||||
* @description [根据非遗项目查询传承人]
|
||||
* @author Leocoder
|
||||
*/
|
||||
List<HrtInheritorListVo> listByHeritageId(Long heritageId);
|
||||
|
||||
/**
|
||||
* @description [精选传承人]
|
||||
* @author Leocoder
|
||||
*/
|
||||
List<HrtInheritorListVo> getFeaturedList(Integer limit);
|
||||
|
||||
}
|
||||
@ -0,0 +1,185 @@
|
||||
package org.leocoder.heritage.portal.service.inheritor;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.leocoder.heritage.common.exception.BusinessException;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtInheritorDetailVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtInheritorListVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.HrtInheritorQueryVo;
|
||||
import org.leocoder.heritage.domain.pojo.portal.HrtInheritor;
|
||||
import org.leocoder.heritage.mybatisplus.mapper.portal.HrtInheritorMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [传承人服务实现类]
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class HrtInheritorServiceImpl implements HrtInheritorService {
|
||||
|
||||
private final HrtInheritorMapper hrtInheritorMapper;
|
||||
private final org.leocoder.heritage.portal.service.user.HrtUserService hrtUserService;
|
||||
|
||||
/**
|
||||
* @description [分页查询传承人列表]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public IPage<HrtInheritorListVo> listPage(HrtInheritorQueryVo vo) {
|
||||
Page<HrtInheritor> page = new Page<>(vo.getPageNum(), vo.getPageSize());
|
||||
|
||||
LambdaQueryWrapper<HrtInheritor> wrapper = buildQueryWrapper(vo);
|
||||
|
||||
// 只查询已发布的传承人
|
||||
wrapper.eq(HrtInheritor::getPublishStatus, 1);
|
||||
|
||||
// 排序
|
||||
applySorting(wrapper, vo.getSortField(), vo.getSortOrder());
|
||||
|
||||
IPage<HrtInheritor> inheritorPageResult = hrtInheritorMapper.selectPage(page, wrapper);
|
||||
|
||||
// 转换为VO
|
||||
return inheritorPageResult.convert(inheritor -> BeanUtil.copyProperties(inheritor, HrtInheritorListVo.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [查看传承人详情]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public HrtInheritorDetailVo getDetail(Long id) {
|
||||
HrtInheritor inheritor = hrtInheritorMapper.selectById(id);
|
||||
|
||||
if (inheritor == null || inheritor.getPublishStatus() != 1) {
|
||||
throw new BusinessException(404, "传承人不存在或未发布");
|
||||
}
|
||||
|
||||
// 浏览量+1
|
||||
HrtInheritor updateInheritor = new HrtInheritor();
|
||||
updateInheritor.setId(id);
|
||||
updateInheritor.setViewCount(inheritor.getViewCount() + 1);
|
||||
hrtInheritorMapper.updateById(updateInheritor);
|
||||
|
||||
// 记录浏览历史
|
||||
hrtUserService.recordViewHistory("inheritor", id);
|
||||
|
||||
// 转换为详情VO
|
||||
HrtInheritorDetailVo detailVo = BeanUtil.copyProperties(inheritor, HrtInheritorDetailVo.class);
|
||||
detailVo.setViewCount(inheritor.getViewCount() + 1);
|
||||
|
||||
return detailVo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [根据非遗项目查询传承人]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public List<HrtInheritorListVo> listByHeritageId(Long heritageId) {
|
||||
if (heritageId == null) {
|
||||
throw new BusinessException(400, "非遗项目ID不能为空");
|
||||
}
|
||||
|
||||
LambdaQueryWrapper<HrtInheritor> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(HrtInheritor::getHeritageId, heritageId)
|
||||
.eq(HrtInheritor::getPublishStatus, 1)
|
||||
.orderByDesc(HrtInheritor::getSortOrder, HrtInheritor::getCreateTime);
|
||||
|
||||
List<HrtInheritor> inheritorList = hrtInheritorMapper.selectList(wrapper);
|
||||
|
||||
return inheritorList.stream()
|
||||
.map(inheritor -> BeanUtil.copyProperties(inheritor, HrtInheritorListVo.class))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [精选传承人]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public List<HrtInheritorListVo> getFeaturedList(Integer limit) {
|
||||
// 默认查询10条
|
||||
if (limit == null || limit <= 0) {
|
||||
limit = 10;
|
||||
}
|
||||
|
||||
LambdaQueryWrapper<HrtInheritor> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(HrtInheritor::getPublishStatus, 1)
|
||||
.eq(HrtInheritor::getIsFeatured, 1)
|
||||
.orderByDesc(HrtInheritor::getSortOrder, HrtInheritor::getCreateTime)
|
||||
.last("LIMIT " + limit);
|
||||
|
||||
List<HrtInheritor> inheritorList = hrtInheritorMapper.selectList(wrapper);
|
||||
|
||||
return inheritorList.stream()
|
||||
.map(inheritor -> BeanUtil.copyProperties(inheritor, HrtInheritorListVo.class))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [构建查询条件]
|
||||
* @author Leocoder
|
||||
*/
|
||||
private LambdaQueryWrapper<HrtInheritor> buildQueryWrapper(HrtInheritorQueryVo vo) {
|
||||
LambdaQueryWrapper<HrtInheritor> wrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
// 关键词搜索(搜索姓名、英文名、简介、传承故事)
|
||||
if (StrUtil.isNotBlank(vo.getKeyword())) {
|
||||
wrapper.and(w -> w
|
||||
.like(HrtInheritor::getName, vo.getKeyword())
|
||||
.or().like(HrtInheritor::getNameEn, vo.getKeyword())
|
||||
.or().like(HrtInheritor::getIntroduction, vo.getKeyword())
|
||||
.or().like(HrtInheritor::getStory, vo.getKeyword())
|
||||
);
|
||||
}
|
||||
|
||||
// 姓名模糊查询(兼容旧版本)
|
||||
wrapper.like(StrUtil.isNotBlank(vo.getName()), HrtInheritor::getName, vo.getName());
|
||||
|
||||
// 传承人级别
|
||||
wrapper.eq(StrUtil.isNotBlank(vo.getLevel()), HrtInheritor::getLevel, vo.getLevel());
|
||||
|
||||
// 省份
|
||||
wrapper.eq(StrUtil.isNotBlank(vo.getProvince()), HrtInheritor::getProvince, vo.getProvince());
|
||||
|
||||
// 城市
|
||||
wrapper.eq(StrUtil.isNotBlank(vo.getCity()), HrtInheritor::getCity, vo.getCity());
|
||||
|
||||
// 关联非遗项目ID
|
||||
wrapper.eq(vo.getHeritageId() != null, HrtInheritor::getHeritageId, vo.getHeritageId());
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [应用排序规则]
|
||||
* @author Leocoder
|
||||
*/
|
||||
private void applySorting(LambdaQueryWrapper<HrtInheritor> wrapper, String sortField, String sortOrder) {
|
||||
boolean isAsc = "asc".equalsIgnoreCase(sortOrder);
|
||||
|
||||
switch (sortField) {
|
||||
case "view_count":
|
||||
wrapper.orderBy(true, isAsc, HrtInheritor::getViewCount);
|
||||
break;
|
||||
case "like_count":
|
||||
wrapper.orderBy(true, isAsc, HrtInheritor::getLikeCount);
|
||||
break;
|
||||
case "create_time":
|
||||
default:
|
||||
wrapper.orderBy(true, isAsc, HrtInheritor::getCreateTime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package org.leocoder.heritage.portal.service.like;
|
||||
|
||||
import org.leocoder.heritage.domain.model.vo.portal.LikeOperateVo;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [点赞服务接口]
|
||||
*/
|
||||
public interface HrtLikeService {
|
||||
|
||||
/**
|
||||
* @description [点赞]
|
||||
* @author Leocoder
|
||||
*/
|
||||
String addLike(LikeOperateVo vo);
|
||||
|
||||
/**
|
||||
* @description [取消点赞]
|
||||
* @author Leocoder
|
||||
*/
|
||||
String cancelLike(LikeOperateVo vo);
|
||||
|
||||
/**
|
||||
* @description [检查是否已点赞]
|
||||
* @author Leocoder
|
||||
*/
|
||||
Boolean checkLike(String targetType, Long targetId);
|
||||
|
||||
}
|
||||
@ -0,0 +1,221 @@
|
||||
package org.leocoder.heritage.portal.service.like;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.leocoder.heritage.common.exception.BusinessException;
|
||||
import org.leocoder.heritage.domain.enums.TargetTypeEnum;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.LikeOperateVo;
|
||||
import org.leocoder.heritage.domain.pojo.portal.*;
|
||||
import org.leocoder.heritage.mybatisplus.mapper.portal.*;
|
||||
import org.leocoder.heritage.satoken.util.HrtStpUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [点赞服务实现类]
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class HrtLikeServiceImpl implements HrtLikeService {
|
||||
|
||||
private final HrtLikeMapper hrtLikeMapper;
|
||||
private final HrtHeritageMapper hrtHeritageMapper;
|
||||
private final HrtInheritorMapper hrtInheritorMapper;
|
||||
private final HrtNewsMapper hrtNewsMapper;
|
||||
private final HrtCommentMapper hrtCommentMapper;
|
||||
|
||||
/**
|
||||
* @description [点赞]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String addLike(LikeOperateVo vo) {
|
||||
// 1、验证目标类型是否支持点赞
|
||||
if (!TargetTypeEnum.isLikeSupported(vo.getTargetType())) {
|
||||
throw new BusinessException(400, "不支持的点赞类型");
|
||||
}
|
||||
|
||||
// 2、获取当前登录用户ID
|
||||
Long userId = HrtStpUtil.getLoginIdAsLong();
|
||||
|
||||
// 3、检查是否已点赞(查询有效记录)
|
||||
LambdaQueryWrapper<HrtLike> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(HrtLike::getUserId, userId)
|
||||
.eq(HrtLike::getTargetType, vo.getTargetType())
|
||||
.eq(HrtLike::getTargetId, vo.getTargetId());
|
||||
HrtLike existingLike = hrtLikeMapper.selectOne(queryWrapper);
|
||||
if (existingLike != null) {
|
||||
throw new BusinessException(400, "您已经点赞过了");
|
||||
}
|
||||
|
||||
// 4、检查是否存在逻辑删除的记录(使用自定义方法绕过@TableLogic过滤)
|
||||
HrtLike deletedLike = hrtLikeMapper.selectDeletedOne(userId, vo.getTargetType(), vo.getTargetId());
|
||||
|
||||
if (deletedLike != null) {
|
||||
// 5、恢复逻辑删除的记录(使用原生SQL绕过@TableLogic限制)
|
||||
int result = hrtLikeMapper.restoreDeleted(deletedLike.getId());
|
||||
if (result <= 0) {
|
||||
throw new BusinessException(500, "点赞失败");
|
||||
}
|
||||
} else {
|
||||
// 6、验证目标对象是否存在
|
||||
validateTargetExists(vo.getTargetType(), vo.getTargetId());
|
||||
|
||||
// 7、插入新的点赞记录
|
||||
HrtLike like = new HrtLike();
|
||||
like.setUserId(userId);
|
||||
like.setTargetType(vo.getTargetType());
|
||||
like.setTargetId(vo.getTargetId());
|
||||
int result = hrtLikeMapper.insert(like);
|
||||
if (result <= 0) {
|
||||
throw new BusinessException(500, "点赞失败");
|
||||
}
|
||||
}
|
||||
|
||||
// 8、更新目标对象的点赞计数 +1
|
||||
updateLikeCount(vo.getTargetType(), vo.getTargetId(), 1);
|
||||
|
||||
return "点赞成功";
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [取消点赞]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String cancelLike(LikeOperateVo vo) {
|
||||
// 1、获取当前登录用户ID
|
||||
Long userId = HrtStpUtil.getLoginIdAsLong();
|
||||
|
||||
// 2、查找点赞记录
|
||||
LambdaQueryWrapper<HrtLike> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(HrtLike::getUserId, userId)
|
||||
.eq(HrtLike::getTargetType, vo.getTargetType())
|
||||
.eq(HrtLike::getTargetId, vo.getTargetId());
|
||||
HrtLike like = hrtLikeMapper.selectOne(queryWrapper);
|
||||
if (like == null) {
|
||||
throw new BusinessException(400, "您还未点赞该内容");
|
||||
}
|
||||
|
||||
// 3、逻辑删除点赞记录
|
||||
int result = hrtLikeMapper.deleteById(like.getId());
|
||||
if (result <= 0) {
|
||||
throw new BusinessException(500, "取消点赞失败");
|
||||
}
|
||||
|
||||
// 4、更新目标对象的点赞计数 -1
|
||||
updateLikeCount(vo.getTargetType(), vo.getTargetId(), -1);
|
||||
|
||||
return "取消点赞成功";
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [检查是否已点赞]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public Boolean checkLike(String targetType, Long targetId) {
|
||||
// 1、获取当前登录用户ID
|
||||
Long userId = HrtStpUtil.getLoginIdAsLong();
|
||||
|
||||
// 2、查询点赞记录是否存在
|
||||
LambdaQueryWrapper<HrtLike> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(HrtLike::getUserId, userId)
|
||||
.eq(HrtLike::getTargetType, targetType)
|
||||
.eq(HrtLike::getTargetId, targetId);
|
||||
Long count = hrtLikeMapper.selectCount(queryWrapper);
|
||||
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [验证目标对象是否存在]
|
||||
* @author Leocoder
|
||||
*/
|
||||
private void validateTargetExists(String targetType, Long targetId) {
|
||||
TargetTypeEnum typeEnum = TargetTypeEnum.fromCode(targetType);
|
||||
if (typeEnum == null) {
|
||||
throw new BusinessException(400, "无效的目标类型");
|
||||
}
|
||||
|
||||
switch (typeEnum) {
|
||||
case HERITAGE:
|
||||
HrtHeritage heritage = hrtHeritageMapper.selectById(targetId);
|
||||
if (heritage == null || heritage.getPublishStatus() != 1) {
|
||||
throw new BusinessException(404, "非遗项目不存在或未发布");
|
||||
}
|
||||
break;
|
||||
case INHERITOR:
|
||||
HrtInheritor inheritor = hrtInheritorMapper.selectById(targetId);
|
||||
if (inheritor == null || inheritor.getPublishStatus() != 1) {
|
||||
throw new BusinessException(404, "传承人不存在或未发布");
|
||||
}
|
||||
break;
|
||||
case NEWS:
|
||||
HrtNews news = hrtNewsMapper.selectById(targetId);
|
||||
if (news == null || news.getPublishStatus() != 1) {
|
||||
throw new BusinessException(404, "新闻不存在或未发布");
|
||||
}
|
||||
break;
|
||||
case COMMENT:
|
||||
HrtComment comment = hrtCommentMapper.selectById(targetId);
|
||||
if (comment == null || comment.getStatus() != 1) {
|
||||
throw new BusinessException(404, "评论不存在或未通过审核");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new BusinessException(400, "不支持的点赞类型");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [更新点赞计数(原子操作)]
|
||||
* @author Leocoder
|
||||
*/
|
||||
private void updateLikeCount(String targetType, Long targetId, int delta) {
|
||||
TargetTypeEnum typeEnum = TargetTypeEnum.fromCode(targetType);
|
||||
if (typeEnum == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String sqlSet = delta > 0
|
||||
? "like_count = like_count + " + delta
|
||||
: "like_count = GREATEST(like_count + (" + delta + "), 0)";
|
||||
|
||||
switch (typeEnum) {
|
||||
case HERITAGE:
|
||||
hrtHeritageMapper.update(null,
|
||||
new LambdaUpdateWrapper<HrtHeritage>()
|
||||
.setSql(sqlSet)
|
||||
.eq(HrtHeritage::getId, targetId)
|
||||
);
|
||||
break;
|
||||
case INHERITOR:
|
||||
hrtInheritorMapper.update(null,
|
||||
new LambdaUpdateWrapper<HrtInheritor>()
|
||||
.setSql(sqlSet)
|
||||
.eq(HrtInheritor::getId, targetId)
|
||||
);
|
||||
break;
|
||||
case NEWS:
|
||||
hrtNewsMapper.update(null,
|
||||
new LambdaUpdateWrapper<HrtNews>()
|
||||
.setSql(sqlSet)
|
||||
.eq(HrtNews::getId, targetId)
|
||||
);
|
||||
break;
|
||||
case COMMENT:
|
||||
hrtCommentMapper.update(null,
|
||||
new LambdaUpdateWrapper<HrtComment>()
|
||||
.setSql(sqlSet)
|
||||
.eq(HrtComment::getId, targetId)
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package org.leocoder.heritage.portal.service.news;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [新闻资讯服务接口]
|
||||
*/
|
||||
public interface HrtNewsService {
|
||||
|
||||
/**
|
||||
* @description [分页查询新闻列表]
|
||||
* @author Leocoder
|
||||
*/
|
||||
IPage<NewsListVo> listNews(NewsQueryVo vo);
|
||||
|
||||
/**
|
||||
* @description [查看新闻详情]
|
||||
* @author Leocoder
|
||||
*/
|
||||
NewsDetailVo getNewsDetail(Long newsId);
|
||||
|
||||
/**
|
||||
* @description [热门新闻]
|
||||
* @author Leocoder
|
||||
*/
|
||||
List<NewsListVo> hotNews(Integer limit);
|
||||
|
||||
/**
|
||||
* @description [置顶新闻]
|
||||
* @author Leocoder
|
||||
*/
|
||||
List<NewsListVo> topNews();
|
||||
|
||||
}
|
||||
@ -0,0 +1,166 @@
|
||||
package org.leocoder.heritage.portal.service.news;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.leocoder.heritage.common.exception.BusinessException;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.NewsDetailVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.NewsListVo;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.NewsQueryVo;
|
||||
import org.leocoder.heritage.domain.pojo.portal.HrtFavorite;
|
||||
import org.leocoder.heritage.domain.pojo.portal.HrtLike;
|
||||
import org.leocoder.heritage.domain.pojo.portal.HrtNews;
|
||||
import org.leocoder.heritage.mybatisplus.mapper.portal.HrtFavoriteMapper;
|
||||
import org.leocoder.heritage.mybatisplus.mapper.portal.HrtLikeMapper;
|
||||
import org.leocoder.heritage.mybatisplus.mapper.portal.HrtNewsMapper;
|
||||
import org.leocoder.heritage.portal.service.user.HrtUserService;
|
||||
import org.leocoder.heritage.satoken.util.HrtStpUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [新闻资讯服务实现类]
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class HrtNewsServiceImpl implements HrtNewsService {
|
||||
|
||||
private final HrtNewsMapper hrtNewsMapper;
|
||||
private final HrtLikeMapper hrtLikeMapper;
|
||||
private final HrtFavoriteMapper hrtFavoriteMapper;
|
||||
private final HrtUserService hrtUserService;
|
||||
|
||||
/**
|
||||
* @description [分页查询新闻列表]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public IPage<NewsListVo> listNews(NewsQueryVo vo) {
|
||||
Page<HrtNews> page = new Page<>(vo.getPageNum(), vo.getPageSize());
|
||||
|
||||
LambdaQueryWrapper<HrtNews> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(HrtNews::getPublishStatus, 1);
|
||||
|
||||
if (vo.getCategory() != null && !vo.getCategory().isEmpty()) {
|
||||
wrapper.eq(HrtNews::getCategory, vo.getCategory());
|
||||
}
|
||||
|
||||
if (vo.getKeyword() != null && !vo.getKeyword().isEmpty()) {
|
||||
wrapper.and(w -> w.like(HrtNews::getTitle, vo.getKeyword())
|
||||
.or()
|
||||
.like(HrtNews::getSummary, vo.getKeyword())
|
||||
.or()
|
||||
.like(HrtNews::getContent, vo.getKeyword()));
|
||||
}
|
||||
|
||||
wrapper.orderByDesc(HrtNews::getIsTop)
|
||||
.orderByDesc(HrtNews::getPublishTime);
|
||||
|
||||
IPage<HrtNews> newsPage = hrtNewsMapper.selectPage(page, wrapper);
|
||||
|
||||
Page<NewsListVo> resultPage = new Page<>(vo.getPageNum(), vo.getPageSize());
|
||||
resultPage.setTotal(newsPage.getTotal());
|
||||
resultPage.setRecords(newsPage.getRecords().stream()
|
||||
.map(news -> BeanUtil.copyProperties(news, NewsListVo.class))
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
|
||||
return resultPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [查看新闻详情]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public NewsDetailVo getNewsDetail(Long newsId) {
|
||||
HrtNews news = hrtNewsMapper.selectById(newsId);
|
||||
if (news == null || news.getPublishStatus() != 1) {
|
||||
throw new BusinessException(404, "新闻不存在或未发布");
|
||||
}
|
||||
|
||||
hrtNewsMapper.update(null,
|
||||
new LambdaUpdateWrapper<HrtNews>()
|
||||
.setSql("view_count = view_count + 1")
|
||||
.eq(HrtNews::getId, newsId)
|
||||
);
|
||||
|
||||
NewsDetailVo detailVo = BeanUtil.copyProperties(news, NewsDetailVo.class);
|
||||
detailVo.setViewCount(news.getViewCount() + 1);
|
||||
|
||||
hrtUserService.recordViewHistory("news", newsId);
|
||||
|
||||
Long currentUserId = null;
|
||||
try {
|
||||
currentUserId = HrtStpUtil.getLoginIdAsLong();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
if (currentUserId != null) {
|
||||
LambdaQueryWrapper<HrtLike> likeWrapper = new LambdaQueryWrapper<>();
|
||||
likeWrapper.eq(HrtLike::getUserId, currentUserId)
|
||||
.eq(HrtLike::getTargetType, "news")
|
||||
.eq(HrtLike::getTargetId, newsId);
|
||||
Long likeCount = hrtLikeMapper.selectCount(likeWrapper);
|
||||
detailVo.setIsLiked(likeCount > 0);
|
||||
|
||||
LambdaQueryWrapper<HrtFavorite> favoriteWrapper = new LambdaQueryWrapper<>();
|
||||
favoriteWrapper.eq(HrtFavorite::getUserId, currentUserId)
|
||||
.eq(HrtFavorite::getTargetType, "news")
|
||||
.eq(HrtFavorite::getTargetId, newsId);
|
||||
Long favoriteCount = hrtFavoriteMapper.selectCount(favoriteWrapper);
|
||||
detailVo.setIsFavorited(favoriteCount > 0);
|
||||
}
|
||||
|
||||
return detailVo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [热门新闻]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public List<NewsListVo> hotNews(Integer limit) {
|
||||
if (limit == null || limit <= 0) {
|
||||
limit = 10;
|
||||
}
|
||||
|
||||
LambdaQueryWrapper<HrtNews> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(HrtNews::getPublishStatus, 1)
|
||||
.orderByDesc(HrtNews::getViewCount)
|
||||
.last("LIMIT " + limit);
|
||||
|
||||
List<HrtNews> newsList = hrtNewsMapper.selectList(wrapper);
|
||||
|
||||
return newsList.stream()
|
||||
.map(news -> BeanUtil.copyProperties(news, NewsListVo.class))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [置顶新闻]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public List<NewsListVo> topNews() {
|
||||
LambdaQueryWrapper<HrtNews> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(HrtNews::getPublishStatus, 1)
|
||||
.eq(HrtNews::getIsTop, 1)
|
||||
.orderByDesc(HrtNews::getPublishTime);
|
||||
|
||||
List<HrtNews> newsList = hrtNewsMapper.selectList(wrapper);
|
||||
|
||||
return newsList.stream()
|
||||
.map(news -> BeanUtil.copyProperties(news, NewsListVo.class))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
package org.leocoder.heritage.portal.service.user;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [用户中心服务接口]
|
||||
*/
|
||||
public interface HrtUserService {
|
||||
|
||||
/**
|
||||
* @description [获取个人资料]
|
||||
* @author Leocoder
|
||||
*/
|
||||
UserProfileVo getProfile();
|
||||
|
||||
/**
|
||||
* @description [修改个人资料]
|
||||
* @author Leocoder
|
||||
*/
|
||||
String updateProfile(UserProfileUpdateVo vo);
|
||||
|
||||
/**
|
||||
* @description [更新头像]
|
||||
* @author Leocoder
|
||||
*/
|
||||
String updateAvatar(UserAvatarUpdateVo vo);
|
||||
|
||||
/**
|
||||
* @description [上传并更新头像]
|
||||
* @author Leocoder
|
||||
*/
|
||||
String uploadAndUpdateAvatar(MultipartFile file);
|
||||
|
||||
/**
|
||||
* @description [修改密码]
|
||||
* @author Leocoder
|
||||
*/
|
||||
String updatePassword(UserPasswordUpdateVo vo);
|
||||
|
||||
/**
|
||||
* @description [获取用户统计信息]
|
||||
* @author Leocoder
|
||||
*/
|
||||
UserStatsVo getUserStats();
|
||||
|
||||
/**
|
||||
* @description [获取浏览历史]
|
||||
* @author Leocoder
|
||||
*/
|
||||
IPage<ViewHistoryItemVo> getViewHistory(ViewHistoryQueryVo vo);
|
||||
|
||||
/**
|
||||
* @description [记录浏览历史]
|
||||
* @author Leocoder
|
||||
*/
|
||||
void recordViewHistory(String targetType, Long targetId);
|
||||
|
||||
}
|
||||
@ -0,0 +1,478 @@
|
||||
package org.leocoder.heritage.portal.service.user;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.leocoder.heritage.common.exception.BusinessException;
|
||||
import org.leocoder.heritage.domain.enums.TargetTypeEnum;
|
||||
import org.leocoder.heritage.domain.model.vo.portal.*;
|
||||
import org.leocoder.heritage.domain.pojo.portal.*;
|
||||
import org.leocoder.heritage.mybatisplus.mapper.portal.*;
|
||||
import org.leocoder.heritage.oss.service.StorageService;
|
||||
import org.leocoder.heritage.oss.service.StorageServiceFactory;
|
||||
import org.leocoder.heritage.oss.utils.OssUtil;
|
||||
import org.leocoder.heritage.satoken.config.CoderSaTokenPasswordUtil;
|
||||
import org.leocoder.heritage.satoken.util.HrtStpUtil;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Leocoder
|
||||
* @description [用户中心服务实现类]
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class HrtUserServiceImpl implements HrtUserService {
|
||||
|
||||
private final HrtUserMapper hrtUserMapper;
|
||||
private final HrtViewHistoryMapper hrtViewHistoryMapper;
|
||||
private final HrtCommentMapper hrtCommentMapper;
|
||||
private final HrtLikeMapper hrtLikeMapper;
|
||||
private final HrtFavoriteMapper hrtFavoriteMapper;
|
||||
private final HrtEventRegistrationMapper hrtEventRegistrationMapper;
|
||||
private final HrtHeritageMapper hrtHeritageMapper;
|
||||
private final HrtInheritorMapper hrtInheritorMapper;
|
||||
private final HrtNewsMapper hrtNewsMapper;
|
||||
private final HrtEventMapper hrtEventMapper;
|
||||
private final StorageServiceFactory storageServiceFactory;
|
||||
|
||||
@Value("${coder.storage.type:local}")
|
||||
private String storageType;
|
||||
|
||||
// 允许的头像文件扩展名
|
||||
private static final List<String> ALLOWED_AVATAR_EXTENSIONS = Arrays.asList(
|
||||
"jpg", "jpeg", "png", "gif", "webp"
|
||||
);
|
||||
|
||||
// 头像文件大小限制:2MB
|
||||
private static final long MAX_AVATAR_SIZE = 2 * 1024 * 1024;
|
||||
|
||||
/**
|
||||
* @description [获取个人资料]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public UserProfileVo getProfile() {
|
||||
Long userId = HrtStpUtil.getLoginIdAsLong();
|
||||
|
||||
HrtUser user = hrtUserMapper.selectById(userId);
|
||||
if (user == null) {
|
||||
throw new BusinessException(404, "用户不存在");
|
||||
}
|
||||
|
||||
return BeanUtil.copyProperties(user, UserProfileVo.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [修改个人资料]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String updateProfile(UserProfileUpdateVo vo) {
|
||||
Long userId = HrtStpUtil.getLoginIdAsLong();
|
||||
|
||||
HrtUser user = hrtUserMapper.selectById(userId);
|
||||
if (user == null) {
|
||||
throw new BusinessException(404, "用户不存在");
|
||||
}
|
||||
|
||||
if (vo.getEmail() != null && !vo.getEmail().isEmpty()) {
|
||||
LambdaQueryWrapper<HrtUser> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(HrtUser::getEmail, vo.getEmail())
|
||||
.ne(HrtUser::getId, userId);
|
||||
Long count = hrtUserMapper.selectCount(wrapper);
|
||||
if (count > 0) {
|
||||
throw new BusinessException(400, "该邮箱已被使用");
|
||||
}
|
||||
}
|
||||
|
||||
if (vo.getPhone() != null && !vo.getPhone().isEmpty()) {
|
||||
LambdaQueryWrapper<HrtUser> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(HrtUser::getPhone, vo.getPhone())
|
||||
.ne(HrtUser::getId, userId);
|
||||
Long count = hrtUserMapper.selectCount(wrapper);
|
||||
if (count > 0) {
|
||||
throw new BusinessException(400, "该手机号已被使用");
|
||||
}
|
||||
}
|
||||
|
||||
BeanUtil.copyProperties(vo, user, "id", "username", "password", "status", "createTime", "updateTime", "delFlag");
|
||||
|
||||
int result = hrtUserMapper.updateById(user);
|
||||
if (result <= 0) {
|
||||
throw new BusinessException(500, "修改个人资料失败");
|
||||
}
|
||||
|
||||
return "修改成功";
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [更新头像]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String updateAvatar(UserAvatarUpdateVo vo) {
|
||||
Long userId = HrtStpUtil.getLoginIdAsLong();
|
||||
|
||||
HrtUser user = hrtUserMapper.selectById(userId);
|
||||
if (user == null) {
|
||||
throw new BusinessException(404, "用户不存在");
|
||||
}
|
||||
|
||||
user.setAvatar(vo.getAvatar());
|
||||
|
||||
int result = hrtUserMapper.updateById(user);
|
||||
if (result <= 0) {
|
||||
throw new BusinessException(500, "更新头像失败");
|
||||
}
|
||||
|
||||
return "更新成功";
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [上传并更新头像]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String uploadAndUpdateAvatar(MultipartFile file) {
|
||||
Long userId = HrtStpUtil.getLoginIdAsLong();
|
||||
|
||||
// 验证用户是否存在
|
||||
HrtUser user = hrtUserMapper.selectById(userId);
|
||||
if (user == null) {
|
||||
throw new BusinessException(404, "用户不存在");
|
||||
}
|
||||
|
||||
// 文件验证
|
||||
validateAvatarFile(file);
|
||||
|
||||
String avatarUrl;
|
||||
try {
|
||||
// 获取存储服务
|
||||
StorageService storageService = storageServiceFactory.getStorageService(storageType);
|
||||
|
||||
// 生成唯一文件名
|
||||
String fileName = OssUtil.generateUniqueFileName(file.getOriginalFilename());
|
||||
|
||||
// 构建文件夹路径
|
||||
String folderPath = "avatars";
|
||||
|
||||
// 上传文件
|
||||
Map<String, Object> fileMap = storageService.uploadFile(file, fileName, folderPath);
|
||||
|
||||
// 获取文件URL
|
||||
avatarUrl = (String) fileMap.get("fileUploadPath");
|
||||
|
||||
log.info("用户头像上传成功: userId={}, avatarUrl={}", userId, avatarUrl);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("用户头像上传失败: userId={}", userId, e);
|
||||
throw new BusinessException(500, "头像上传失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
// 更新用户头像
|
||||
user.setAvatar(avatarUrl);
|
||||
int result = hrtUserMapper.updateById(user);
|
||||
if (result <= 0) {
|
||||
throw new BusinessException(500, "更新头像失败");
|
||||
}
|
||||
|
||||
return avatarUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [验证头像文件]
|
||||
* @author Leocoder
|
||||
*/
|
||||
private void validateAvatarFile(MultipartFile file) {
|
||||
// 检查文件是否为空
|
||||
if (file == null || file.isEmpty()) {
|
||||
throw new BusinessException(400, "请选择要上传的头像文件");
|
||||
}
|
||||
|
||||
// 检查文件大小
|
||||
long fileSize = file.getSize();
|
||||
if (fileSize > MAX_AVATAR_SIZE) {
|
||||
throw new BusinessException(413, "头像文件大小不能超过2MB");
|
||||
}
|
||||
|
||||
// 获取文件扩展名
|
||||
String originalFilename = file.getOriginalFilename();
|
||||
if (StringUtils.isBlank(originalFilename)) {
|
||||
throw new BusinessException(400, "文件名不能为空");
|
||||
}
|
||||
|
||||
String fileExtension = getFileExtension(originalFilename).toLowerCase();
|
||||
if (StringUtils.isBlank(fileExtension)) {
|
||||
throw new BusinessException(400, "文件必须有扩展名");
|
||||
}
|
||||
|
||||
// 检查文件格式
|
||||
if (!ALLOWED_AVATAR_EXTENSIONS.contains(fileExtension)) {
|
||||
throw new BusinessException(400, "不支持的头像格式,仅支持:" + String.join(", ", ALLOWED_AVATAR_EXTENSIONS));
|
||||
}
|
||||
|
||||
log.info("头像文件验证通过:文件名={}, 大小={}bytes, 类型={}", originalFilename, fileSize, fileExtension);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [获取文件扩展名]
|
||||
* @author Leocoder
|
||||
*/
|
||||
private String getFileExtension(String filename) {
|
||||
if (StringUtils.isBlank(filename)) {
|
||||
return "";
|
||||
}
|
||||
int lastDotIndex = filename.lastIndexOf('.');
|
||||
if (lastDotIndex == -1 || lastDotIndex == filename.length() - 1) {
|
||||
return "";
|
||||
}
|
||||
return filename.substring(lastDotIndex + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [修改密码]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String updatePassword(UserPasswordUpdateVo vo) {
|
||||
Long userId = HrtStpUtil.getLoginIdAsLong();
|
||||
|
||||
HrtUser user = hrtUserMapper.selectById(userId);
|
||||
if (user == null) {
|
||||
throw new BusinessException(404, "用户不存在");
|
||||
}
|
||||
|
||||
if (!vo.getNewPassword().equals(vo.getConfirmPassword())) {
|
||||
throw new BusinessException(400, "两次输入的新密码不一致");
|
||||
}
|
||||
|
||||
if (!CoderSaTokenPasswordUtil.matchesPassword(vo.getOldPassword(), user.getPassword())) {
|
||||
throw new BusinessException(400, "旧密码不正确");
|
||||
}
|
||||
|
||||
if (vo.getOldPassword().equals(vo.getNewPassword())) {
|
||||
throw new BusinessException(400, "新密码不能与旧密码相同");
|
||||
}
|
||||
|
||||
String encryptedPassword = CoderSaTokenPasswordUtil.encryptPassword(vo.getNewPassword());
|
||||
user.setPassword(encryptedPassword);
|
||||
|
||||
int result = hrtUserMapper.updateById(user);
|
||||
if (result <= 0) {
|
||||
throw new BusinessException(500, "修改密码失败");
|
||||
}
|
||||
|
||||
return "修改成功";
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [获取用户统计信息]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public UserStatsVo getUserStats() {
|
||||
Long userId = HrtStpUtil.getLoginIdAsLong();
|
||||
|
||||
UserStatsVo stats = new UserStatsVo();
|
||||
|
||||
LambdaQueryWrapper<HrtViewHistory> viewWrapper = new LambdaQueryWrapper<>();
|
||||
viewWrapper.eq(HrtViewHistory::getUserId, userId);
|
||||
stats.setViewHistoryCount(hrtViewHistoryMapper.selectCount(viewWrapper));
|
||||
|
||||
LambdaQueryWrapper<HrtComment> commentWrapper = new LambdaQueryWrapper<>();
|
||||
commentWrapper.eq(HrtComment::getUserId, userId);
|
||||
stats.setCommentCount(hrtCommentMapper.selectCount(commentWrapper));
|
||||
|
||||
LambdaQueryWrapper<HrtLike> likeWrapper = new LambdaQueryWrapper<>();
|
||||
likeWrapper.eq(HrtLike::getUserId, userId);
|
||||
stats.setLikeCount(hrtLikeMapper.selectCount(likeWrapper));
|
||||
|
||||
LambdaQueryWrapper<HrtFavorite> favoriteWrapper = new LambdaQueryWrapper<>();
|
||||
favoriteWrapper.eq(HrtFavorite::getUserId, userId);
|
||||
stats.setFavoriteCount(hrtFavoriteMapper.selectCount(favoriteWrapper));
|
||||
|
||||
LambdaQueryWrapper<HrtEventRegistration> registrationWrapper = new LambdaQueryWrapper<>();
|
||||
registrationWrapper.eq(HrtEventRegistration::getUserId, userId);
|
||||
stats.setEventRegistrationCount(hrtEventRegistrationMapper.selectCount(registrationWrapper));
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [获取浏览历史]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
public IPage<ViewHistoryItemVo> getViewHistory(ViewHistoryQueryVo vo) {
|
||||
Long userId = HrtStpUtil.getLoginIdAsLong();
|
||||
|
||||
Page<HrtViewHistory> page = new Page<>(vo.getPageNum(), vo.getPageSize());
|
||||
|
||||
LambdaQueryWrapper<HrtViewHistory> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(HrtViewHistory::getUserId, userId);
|
||||
|
||||
if (vo.getTargetType() != null && !vo.getTargetType().isEmpty()) {
|
||||
wrapper.eq(HrtViewHistory::getTargetType, vo.getTargetType());
|
||||
}
|
||||
|
||||
wrapper.orderByDesc(HrtViewHistory::getUpdateTime);
|
||||
|
||||
IPage<HrtViewHistory> historyPage = hrtViewHistoryMapper.selectPage(page, wrapper);
|
||||
|
||||
List<Long> heritageIds = historyPage.getRecords().stream()
|
||||
.filter(h -> "heritage".equals(h.getTargetType()))
|
||||
.map(HrtViewHistory::getTargetId)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<Long> inheritorIds = historyPage.getRecords().stream()
|
||||
.filter(h -> "inheritor".equals(h.getTargetType()))
|
||||
.map(HrtViewHistory::getTargetId)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<Long> newsIds = historyPage.getRecords().stream()
|
||||
.filter(h -> "news".equals(h.getTargetType()))
|
||||
.map(HrtViewHistory::getTargetId)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<Long> eventIds = historyPage.getRecords().stream()
|
||||
.filter(h -> "event".equals(h.getTargetType()))
|
||||
.map(HrtViewHistory::getTargetId)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Map<Long, HrtHeritage> heritageMap = heritageIds.isEmpty() ? Map.of() :
|
||||
hrtHeritageMapper.selectBatchIds(heritageIds).stream()
|
||||
.collect(Collectors.toMap(HrtHeritage::getId, h -> h));
|
||||
|
||||
Map<Long, HrtInheritor> inheritorMap = inheritorIds.isEmpty() ? Map.of() :
|
||||
hrtInheritorMapper.selectBatchIds(inheritorIds).stream()
|
||||
.collect(Collectors.toMap(HrtInheritor::getId, i -> i));
|
||||
|
||||
Map<Long, HrtNews> newsMap = newsIds.isEmpty() ? Map.of() :
|
||||
hrtNewsMapper.selectBatchIds(newsIds).stream()
|
||||
.collect(Collectors.toMap(HrtNews::getId, n -> n));
|
||||
|
||||
Map<Long, HrtEvent> eventMap = eventIds.isEmpty() ? Map.of() :
|
||||
hrtEventMapper.selectBatchIds(eventIds).stream()
|
||||
.collect(Collectors.toMap(HrtEvent::getId, e -> e));
|
||||
|
||||
Page<ViewHistoryItemVo> resultPage = new Page<>(vo.getPageNum(), vo.getPageSize());
|
||||
resultPage.setTotal(historyPage.getTotal());
|
||||
resultPage.setRecords(historyPage.getRecords().stream()
|
||||
.map(history -> buildViewHistoryItemVo(history, heritageMap, inheritorMap, newsMap, eventMap))
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
|
||||
return resultPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [记录浏览历史]
|
||||
* @author Leocoder
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void recordViewHistory(String targetType, Long targetId) {
|
||||
Long userId;
|
||||
try {
|
||||
userId = HrtStpUtil.getLoginIdAsLong();
|
||||
} catch (Exception e) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TargetTypeEnum.isValid(targetType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LambdaQueryWrapper<HrtViewHistory> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(HrtViewHistory::getUserId, userId)
|
||||
.eq(HrtViewHistory::getTargetType, targetType)
|
||||
.eq(HrtViewHistory::getTargetId, targetId);
|
||||
|
||||
HrtViewHistory existingHistory = hrtViewHistoryMapper.selectOne(wrapper);
|
||||
|
||||
if (existingHistory != null) {
|
||||
hrtViewHistoryMapper.updateById(existingHistory);
|
||||
} else {
|
||||
HrtViewHistory history = new HrtViewHistory();
|
||||
history.setUserId(userId);
|
||||
history.setTargetType(targetType);
|
||||
history.setTargetId(targetId);
|
||||
hrtViewHistoryMapper.insert(history);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description [构建浏览历史项VO]
|
||||
* @author Leocoder
|
||||
*/
|
||||
private ViewHistoryItemVo buildViewHistoryItemVo(HrtViewHistory history,
|
||||
Map<Long, HrtHeritage> heritageMap,
|
||||
Map<Long, HrtInheritor> inheritorMap,
|
||||
Map<Long, HrtNews> newsMap,
|
||||
Map<Long, HrtEvent> eventMap) {
|
||||
ViewHistoryItemVo itemVo = new ViewHistoryItemVo();
|
||||
itemVo.setId(history.getId());
|
||||
itemVo.setTargetType(history.getTargetType());
|
||||
itemVo.setTargetId(history.getTargetId());
|
||||
itemVo.setViewTime(history.getUpdateTime());
|
||||
|
||||
switch (history.getTargetType()) {
|
||||
case "heritage":
|
||||
HrtHeritage heritage = heritageMap.get(history.getTargetId());
|
||||
if (heritage != null) {
|
||||
itemVo.setTargetTitle(heritage.getName());
|
||||
itemVo.setTargetCover(heritage.getCoverImage());
|
||||
itemVo.setTargetDescription(heritage.getDescription());
|
||||
}
|
||||
break;
|
||||
case "inheritor":
|
||||
HrtInheritor inheritor = inheritorMap.get(history.getTargetId());
|
||||
if (inheritor != null) {
|
||||
itemVo.setTargetTitle(inheritor.getName());
|
||||
itemVo.setTargetCover(inheritor.getAvatar());
|
||||
itemVo.setTargetDescription(inheritor.getIntroduction());
|
||||
}
|
||||
break;
|
||||
case "news":
|
||||
HrtNews news = newsMap.get(history.getTargetId());
|
||||
if (news != null) {
|
||||
itemVo.setTargetTitle(news.getTitle());
|
||||
itemVo.setTargetCover(news.getCoverImage());
|
||||
itemVo.setTargetDescription(news.getSummary());
|
||||
}
|
||||
break;
|
||||
case "event":
|
||||
HrtEvent event = eventMap.get(history.getTargetId());
|
||||
if (event != null) {
|
||||
itemVo.setTargetTitle(event.getTitle());
|
||||
itemVo.setTargetCover(event.getCoverImage());
|
||||
itemVo.setTargetDescription(event.getSummary());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return itemVo;
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user