postEsDTOList = postList.stream().map(PostEsDTO::objToDto).collect(Collectors.toList());
+ final int pageSize = 500;
+ int total = postEsDTOList.size();
+ log.info("FullSyncPostToEs start, total {}", total);
+ for (int i = 0; i < total; i += pageSize) {
+ int end = Math.min(i + pageSize, total);
+ log.info("sync from {} to {}", i, end);
+ postEsDao.saveAll(postEsDTOList.subList(i, end));
+ }
+ log.info("FullSyncPostToEs end, total {}", total);
+ }
+}
diff --git a/src/main/java/com/yupi/springbootinit/manager/AiManager.java b/src/main/java/com/yupi/springbootinit/manager/AiManager.java
new file mode 100644
index 0000000..d8dcd14
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/manager/AiManager.java
@@ -0,0 +1,85 @@
+package com.yupi.springbootinit.manager;
+
+import com.yupi.springbootinit.common.ErrorCode;
+import com.yupi.springbootinit.exception.BusinessException;
+import io.github.briqt.spark4j.SparkClient;
+import io.github.briqt.spark4j.constant.SparkApiVersion;
+import io.github.briqt.spark4j.model.SparkMessage;
+import io.github.briqt.spark4j.model.request.SparkRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@Service
+@Slf4j
+public class AiManager {
+
+ @Resource
+ private SparkClient sparkClient;
+
+
+ /**
+ * 向 AI 发送请求
+ *
+ * @param isNeedTemplate 是否使用模板,进行 AI 生成; true 使用 、false 不使用 ,false 的情况是只想用 AI 不只是生成前端代码
+ * @param content 内容
+ * 分析需求:
+ * 分析网站用户的增长情况
+ * 原始数据:
+ * 日期,用户数
+ * 1号,10
+ * 2号,20
+ * 3号,30
+ * @return AI 返回的内容
+ * '【【【【'
+ *
+ * '【【【【'
+ */
+ public String sendMsgToXingHuo(boolean isNeedTemplate, String content) {
+ List messages = new ArrayList<>();
+ if (isNeedTemplate) {
+ // AI 生成问题的预设条件
+ String predefinedInformation = "请严格按照下面的输出格式生成结果,且不得添加任何多余内容(例如无关文字、注释、代码块标记或反引号):\n" +
+ "\n" +
+ "'【【【【'" +
+ "{ 生成 Echarts V5 的 option 配置对象 JSON 代码,要求为合法 JSON 格式且不含任何额外内容(如注释或多余字符) } '【【【【' 结论: {\n" +
+ "提供对数据的详细分析结论,内容应尽可能准确、详细,不允许添加其他无关文字或注释 }\n" +
+ "\n" +
+ "示例: 输入: 分析需求: 分析网站用户增长情况,请使用柱状图展示 原始数据: 日期,用户数 1号,10 2号,20 3号,30\n" +
+ "\n" +
+ "期望输出: '【【【【' { \"title\": { \"text\": \"分析网站用户增长情况\" }, \"xAxis\": { \"type\": \"category\", \"data\": [\"1号\", \"2号\", \"3号\"] }, \"yAxis\": { \"type\": \"value\" }, \"series\": [ { \"name\": \"用户数\", \"type\": \"bar\", \"data\": [10, 20, 30] } ] } '【【【【' 从数据看,网站用户数由1号的10人增长到2号的20人,再到3号的30人,呈现出明显的上升趋势。这表明在这段时间内网站用户吸引力增强,可能与推广活动、内容更新或其他外部因素有关。";
+ messages.add(SparkMessage.systemContent(predefinedInformation + "\n" + "----------------------------------"));
+ }
+ messages.add(SparkMessage.userContent(content));
+ // 构造请求
+ SparkRequest sparkRequest = SparkRequest.builder()
+ // 消息列表
+ .messages(messages)
+ // 模型回答的tokens的最大长度,非必传,取值为[1,4096],默认为2048
+ .maxTokens(2048)
+ // 核采样阈值。用于决定结果随机性,取值越高随机性越强即相同的问题得到的不同答案的可能性越高 非必传,取值为[0,1],默认为0.5
+ .temperature(0.6)
+ // 指定请求版本
+ .apiVersion(SparkApiVersion.V4_0)
+ .build();
+ // 同步调用
+ String responseContent = sparkClient.chatSync(sparkRequest).getContent().trim();
+ if (!isNeedTemplate) {
+ return responseContent;
+ }
+ log.info("星火 AI 返回的结果 {}", responseContent);
+ AtomicInteger atomicInteger = new AtomicInteger(1);
+ while (responseContent.split("'【【【【'").length < 3) {
+ responseContent = sparkClient.chatSync(sparkRequest).getContent().trim();
+ if (atomicInteger.incrementAndGet() >= 4) {
+ throw new BusinessException(ErrorCode.SYSTEM_ERROR, "星火 AI 生成失败");
+ }
+ }
+ return responseContent;
+ }
+}
+
diff --git a/src/main/java/com/yupi/springbootinit/manager/CosManager.java b/src/main/java/com/yupi/springbootinit/manager/CosManager.java
new file mode 100644
index 0000000..a556cc2
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/manager/CosManager.java
@@ -0,0 +1,51 @@
+package com.yupi.springbootinit.manager;
+
+import com.qcloud.cos.COSClient;
+import com.qcloud.cos.model.PutObjectRequest;
+import com.qcloud.cos.model.PutObjectResult;
+import com.yupi.springbootinit.config.CosClientConfig;
+import java.io.File;
+import javax.annotation.Resource;
+import org.springframework.stereotype.Component;
+
+/**
+ * Cos 对象存储操作
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@Component
+public class CosManager {
+
+ @Resource
+ private CosClientConfig cosClientConfig;
+
+ @Resource
+ private COSClient cosClient;
+
+ /**
+ * 上传对象
+ *
+ * @param key 唯一键
+ * @param localFilePath 本地文件路径
+ * @return
+ */
+ public PutObjectResult putObject(String key, String localFilePath) {
+ PutObjectRequest putObjectRequest = new PutObjectRequest(cosClientConfig.getBucket(), key,
+ new File(localFilePath));
+ return cosClient.putObject(putObjectRequest);
+ }
+
+ /**
+ * 上传对象
+ *
+ * @param key 唯一键
+ * @param file 文件
+ * @return
+ */
+ public PutObjectResult putObject(String key, File file) {
+ PutObjectRequest putObjectRequest = new PutObjectRequest(cosClientConfig.getBucket(), key,
+ file);
+ return cosClient.putObject(putObjectRequest);
+ }
+}
diff --git a/src/main/java/com/yupi/springbootinit/manager/RedisLimiterManager.java b/src/main/java/com/yupi/springbootinit/manager/RedisLimiterManager.java
new file mode 100644
index 0000000..9c39070
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/manager/RedisLimiterManager.java
@@ -0,0 +1,37 @@
+package com.yupi.springbootinit.manager;
+
+import com.yupi.springbootinit.common.ErrorCode;
+import com.yupi.springbootinit.exception.BusinessException;
+import org.redisson.api.RRateLimiter;
+import org.redisson.api.RateIntervalUnit;
+import org.redisson.api.RateType;
+import org.redisson.api.RedissonClient;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+
+/**
+ * 专门提供 RedisLimiter 限流基础服务的(提供了通用的能力)
+ */
+@Service
+public class RedisLimiterManager {
+
+ @Resource
+ private RedissonClient redissonClient;
+
+ /**
+ * 限流操作
+ *
+ * @param key 区分不同的限流器,比如不同的用户 id 应该分别统计
+ */
+ public void doRateLimit(String key) {
+ // 创建一个名称为user_limiter的限流器,每秒最多访问 2 次
+ RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
+ rateLimiter.trySetRate(RateType.OVERALL, 2, 1, RateIntervalUnit.SECONDS);
+ // 每当一个操作来了后,请求一个令牌
+ boolean canOp = rateLimiter.tryAcquire(1);
+ if (!canOp) {
+ throw new BusinessException(ErrorCode.TOO_MANY_REQUEST);
+ }
+ }
+}
diff --git a/src/main/java/com/yupi/springbootinit/manager/ThreadPoolExecutorConfig.java b/src/main/java/com/yupi/springbootinit/manager/ThreadPoolExecutorConfig.java
new file mode 100644
index 0000000..662e329
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/manager/ThreadPoolExecutorConfig.java
@@ -0,0 +1,45 @@
+package com.yupi.springbootinit.manager;
+
+import org.apache.poi.ss.formula.functions.T;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+@Configuration
+public class ThreadPoolExecutorConfig {
+
+ @Bean
+ public ThreadPoolExecutor threadPoolExecutor() {
+ ThreadFactory threadFactory = new ThreadFactory() {
+ private int count = 1;
+
+ @Override
+ public Thread newThread(@NotNull Runnable r) {
+ Thread thread = new Thread(r);
+ thread.setName("线程" + count);
+ count++;
+ return thread;
+ }
+ };
+
+// corePoolSize(核心线程数=>正式员工数):正常情况下,我们的系统应该同时工作的线程数(随时就绪的状态)
+// maximumPoolSize(最大线程数=>哪怕任务再多,你也最多招这些人):极限情况下,我们的线程池最多有多少个线程?
+// keepAliveTime(空闲线程存活时间):非核心线程在没有任务的情况下,过多久要删除(理解为开除临时工),从而释放无用的线程资源。
+// TimeUnit unit(空闲线程存活时间的单位):分钟、秒
+// workQueue(工作队列):用于存放给线程执行的任务,存在一个队列的长度(一定要设置,不要说队列长度无限,因为也会占用资源)
+// threadFactory(线程工厂):控制每个线程的生成、线程的属性(比如线程名)
+ ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
+ 2,
+ 4,
+ 100,
+ TimeUnit.SECONDS,
+ new ArrayBlockingQueue<>(4),
+ threadFactory);
+ return threadPoolExecutor;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/yupi/springbootinit/mapper/ChartMapper.java b/src/main/java/com/yupi/springbootinit/mapper/ChartMapper.java
new file mode 100644
index 0000000..89b467e
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/mapper/ChartMapper.java
@@ -0,0 +1,18 @@
+package com.yupi.springbootinit.mapper;
+
+import com.yupi.springbootinit.model.entity.Chart;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+* @author HeSun
+* @description 针对表【chart(图表信息表)】的数据库操作Mapper
+* @createDate 2025-02-20 20:52:40
+* @Entity com.yupi.springbootinit.model.entity.Chart
+*/
+public interface ChartMapper extends BaseMapper {
+
+}
+
+
+
+
diff --git a/src/main/java/com/yupi/springbootinit/mapper/PostFavourMapper.java b/src/main/java/com/yupi/springbootinit/mapper/PostFavourMapper.java
new file mode 100644
index 0000000..c57cf5e
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/mapper/PostFavourMapper.java
@@ -0,0 +1,35 @@
+package com.yupi.springbootinit.mapper;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.yupi.springbootinit.model.entity.Post;
+import com.yupi.springbootinit.model.entity.PostFavour;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 帖子收藏数据库操作
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+public interface PostFavourMapper extends BaseMapper {
+
+ /**
+ * 分页查询收藏帖子列表
+ *
+ * @param page
+ * @param queryWrapper
+ * @param favourUserId
+ * @return
+ */
+ Page listFavourPostByPage(IPage page, @Param(Constants.WRAPPER) Wrapper queryWrapper,
+ long favourUserId);
+
+}
+
+
+
+
diff --git a/src/main/java/com/yupi/springbootinit/mapper/PostMapper.java b/src/main/java/com/yupi/springbootinit/mapper/PostMapper.java
new file mode 100644
index 0000000..2975b84
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/mapper/PostMapper.java
@@ -0,0 +1,25 @@
+package com.yupi.springbootinit.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.yupi.springbootinit.model.entity.Post;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 帖子数据库操作
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+public interface PostMapper extends BaseMapper {
+
+ /**
+ * 查询帖子列表(包括已被删除的数据)
+ */
+ List listPostWithDelete(Date minUpdateTime);
+
+}
+
+
+
+
diff --git a/src/main/java/com/yupi/springbootinit/mapper/PostThumbMapper.java b/src/main/java/com/yupi/springbootinit/mapper/PostThumbMapper.java
new file mode 100644
index 0000000..fe39c82
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/mapper/PostThumbMapper.java
@@ -0,0 +1,18 @@
+package com.yupi.springbootinit.mapper;
+
+import com.yupi.springbootinit.model.entity.PostThumb;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * 帖子点赞数据库操作
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+public interface PostThumbMapper extends BaseMapper {
+
+}
+
+
+
+
diff --git a/src/main/java/com/yupi/springbootinit/mapper/UserMapper.java b/src/main/java/com/yupi/springbootinit/mapper/UserMapper.java
new file mode 100644
index 0000000..247bf3b
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/mapper/UserMapper.java
@@ -0,0 +1,18 @@
+package com.yupi.springbootinit.mapper;
+
+import com.yupi.springbootinit.model.entity.User;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+* @author HeSun
+* @description 针对表【user(用户)】的数据库操作Mapper
+* @createDate 2025-02-20 20:52:40
+* @Entity com.yupi.springbootinit.model.entity.User
+*/
+public interface UserMapper extends BaseMapper {
+
+}
+
+
+
+
diff --git a/src/main/java/com/yupi/springbootinit/model/dto/chart/ChartAddRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/chart/ChartAddRequest.java
new file mode 100644
index 0000000..420bbfb
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/dto/chart/ChartAddRequest.java
@@ -0,0 +1,39 @@
+package com.yupi.springbootinit.model.dto.chart;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 创建请求
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@Data
+public class ChartAddRequest implements Serializable {
+
+ /**
+ * 名称
+ */
+ private String name;
+
+ /**
+ * 分析目标
+ */
+ private String goal;
+
+ /**
+ * 图表数据
+ */
+ private String chartData;
+
+ /**
+ * 图表类型
+ */
+ private String chartType;
+
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/src/main/java/com/yupi/springbootinit/model/dto/chart/ChartEditRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/chart/ChartEditRequest.java
new file mode 100644
index 0000000..2f8568c
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/dto/chart/ChartEditRequest.java
@@ -0,0 +1,47 @@
+package com.yupi.springbootinit.model.dto.chart;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 编辑请求
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@Data
+public class ChartEditRequest implements Serializable {
+
+ /**
+ * 名称
+ */
+ private String name;
+
+ /**
+ * id
+ */
+ private Long id;
+
+ /**
+ * 分析目标
+ */
+ private String goal;
+
+ /**
+ * 图表数据
+ */
+ private String chartData;
+
+ /**
+ * 图表类型
+ */
+ private String chartType;
+
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/src/main/java/com/yupi/springbootinit/model/dto/chart/ChartQueryRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/chart/ChartQueryRequest.java
new file mode 100644
index 0000000..0c36cc6
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/dto/chart/ChartQueryRequest.java
@@ -0,0 +1,42 @@
+package com.yupi.springbootinit.model.dto.chart;
+
+import com.yupi.springbootinit.common.PageRequest;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+
+/**
+ * 查询请求
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class ChartQueryRequest extends PageRequest implements Serializable {
+
+ private Long id;
+
+ /**
+ * 名称
+ */
+ private String name;
+
+ /**
+ * 分析目标
+ */
+ private String goal;
+
+ /**
+ * 图表类型
+ */
+ private String chartType;
+
+ /**
+ * 用户id
+ */
+ private Long userId;
+
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/src/main/java/com/yupi/springbootinit/model/dto/chart/ChartUpdateRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/chart/ChartUpdateRequest.java
new file mode 100644
index 0000000..bac3ac7
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/dto/chart/ChartUpdateRequest.java
@@ -0,0 +1,78 @@
+package com.yupi.springbootinit.model.dto.chart;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 更新请求
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@Data
+public class ChartUpdateRequest implements Serializable {
+ /**
+ * id
+ */
+ @TableId(type = IdType.ASSIGN_ID)
+ private Long id;
+
+ /**
+ * 名称
+ */
+ private String name;
+
+ /**
+ * 分析目标
+ */
+ private String goal;
+
+ /**
+ * 图表数据
+ */
+ private String chartData;
+
+ /**
+ * 图表类型
+ */
+ private String chartType;
+
+ /**
+ * 生成的图表数据
+ */
+ private String genChart;
+
+ /**
+ * 生成的分析结论
+ */
+ private String genResult;
+
+ /**
+ * 创建用户 id
+ */
+ private Long userId;
+
+ /**
+ * 创建时间
+ */
+ private Date createTime;
+
+ /**
+ * 更新时间
+ */
+ private Date updateTime;
+
+ /**
+ * 是否删除
+ */
+ @TableLogic
+ private Integer isDelete;
+
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/src/main/java/com/yupi/springbootinit/model/dto/chart/GenChartByAiRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/chart/GenChartByAiRequest.java
new file mode 100644
index 0000000..b9b8cb2
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/dto/chart/GenChartByAiRequest.java
@@ -0,0 +1,32 @@
+package com.yupi.springbootinit.model.dto.chart;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 文件上传请求
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@Data
+public class GenChartByAiRequest implements Serializable {
+
+ /**
+ * 名称
+ */
+ private String name;
+
+ /**
+ * 分析目标
+ */
+ private String goal;
+
+ /**
+ * 图标类型
+ */
+ private String chartType;
+
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/src/main/java/com/yupi/springbootinit/model/dto/file/UploadFileRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/file/UploadFileRequest.java
new file mode 100644
index 0000000..09654ec
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/dto/file/UploadFileRequest.java
@@ -0,0 +1,21 @@
+package com.yupi.springbootinit.model.dto.file;
+
+import java.io.Serializable;
+import lombok.Data;
+
+/**
+ * 文件上传请求
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@Data
+public class UploadFileRequest implements Serializable {
+
+ /**
+ * 业务
+ */
+ private String biz;
+
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/src/main/java/com/yupi/springbootinit/model/dto/post/PostAddRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/post/PostAddRequest.java
new file mode 100644
index 0000000..45ac49c
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/dto/post/PostAddRequest.java
@@ -0,0 +1,32 @@
+package com.yupi.springbootinit.model.dto.post;
+
+import java.io.Serializable;
+import java.util.List;
+import lombok.Data;
+
+/**
+ * 创建请求
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@Data
+public class PostAddRequest implements Serializable {
+
+ /**
+ * 标题
+ */
+ private String title;
+
+ /**
+ * 内容
+ */
+ private String content;
+
+ /**
+ * 标签列表
+ */
+ private List tags;
+
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/src/main/java/com/yupi/springbootinit/model/dto/post/PostEditRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/post/PostEditRequest.java
new file mode 100644
index 0000000..40c3888
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/dto/post/PostEditRequest.java
@@ -0,0 +1,37 @@
+package com.yupi.springbootinit.model.dto.post;
+
+import java.io.Serializable;
+import java.util.List;
+import lombok.Data;
+
+/**
+ * 编辑请求
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@Data
+public class PostEditRequest implements Serializable {
+
+ /**
+ * id
+ */
+ private Long id;
+
+ /**
+ * 标题
+ */
+ private String title;
+
+ /**
+ * 内容
+ */
+ private String content;
+
+ /**
+ * 标签列表
+ */
+ private List tags;
+
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/src/main/java/com/yupi/springbootinit/model/dto/post/PostEsDTO.java b/src/main/java/com/yupi/springbootinit/model/dto/post/PostEsDTO.java
new file mode 100644
index 0000000..aa7ae46
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/dto/post/PostEsDTO.java
@@ -0,0 +1,123 @@
+package com.yupi.springbootinit.model.dto.post;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.json.JSONUtil;
+import com.yupi.springbootinit.model.entity.Post;
+import lombok.Data;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.elasticsearch.annotations.Field;
+import org.springframework.data.elasticsearch.annotations.FieldType;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 帖子 ES 包装类
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ **/
+// todo 取消注释开启 ES(须先配置 ES)
+//@Document(indexName = "post")
+@Data
+public class PostEsDTO implements Serializable {
+
+ private static final String DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
+
+ /**
+ * id
+ */
+ @Id
+ private Long id;
+
+ /**
+ * 标题
+ */
+ private String title;
+
+ /**
+ * 内容
+ */
+ private String content;
+
+ /**
+ * 标签列表
+ */
+ private List tags;
+
+ /**
+ * 点赞数
+ */
+ private Integer thumbNum;
+
+ /**
+ * 收藏数
+ */
+ private Integer favourNum;
+
+ /**
+ * 创建用户 id
+ */
+ private Long userId;
+
+ /**
+ * 创建时间
+ */
+ @Field(index = false, store = true, type = FieldType.Date, format = {}, pattern = DATE_TIME_PATTERN)
+ private Date createTime;
+
+ /**
+ * 更新时间
+ */
+ @Field(index = false, store = true, type = FieldType.Date, format = {}, pattern = DATE_TIME_PATTERN)
+ private Date updateTime;
+
+ /**
+ * 是否删除
+ */
+ private Integer isDelete;
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 对象转包装类
+ *
+ * @param post
+ * @return
+ */
+ public static PostEsDTO objToDto(Post post) {
+ if (post == null) {
+ return null;
+ }
+ PostEsDTO postEsDTO = new PostEsDTO();
+ BeanUtils.copyProperties(post, postEsDTO);
+ String tagsStr = post.getTags();
+ if (StringUtils.isNotBlank(tagsStr)) {
+ postEsDTO.setTags(JSONUtil.toList(tagsStr, String.class));
+ }
+ return postEsDTO;
+ }
+
+ /**
+ * 包装类转对象
+ *
+ * @param postEsDTO
+ * @return
+ */
+ public static Post dtoToObj(PostEsDTO postEsDTO) {
+ if (postEsDTO == null) {
+ return null;
+ }
+ Post post = new Post();
+ BeanUtils.copyProperties(postEsDTO, post);
+ List tagList = postEsDTO.getTags();
+ if (CollUtil.isNotEmpty(tagList)) {
+ post.setTags(JSONUtil.toJsonStr(tagList));
+ }
+ return post;
+ }
+}
diff --git a/src/main/java/com/yupi/springbootinit/model/dto/post/PostQueryRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/post/PostQueryRequest.java
new file mode 100644
index 0000000..c9f160a
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/dto/post/PostQueryRequest.java
@@ -0,0 +1,65 @@
+package com.yupi.springbootinit.model.dto.post;
+
+import com.yupi.springbootinit.common.PageRequest;
+import java.io.Serializable;
+import java.util.List;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 查询请求
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class PostQueryRequest extends PageRequest implements Serializable {
+
+ /**
+ * id
+ */
+ private Long id;
+
+ /**
+ * id
+ */
+ private Long notId;
+
+ /**
+ * 搜索词
+ */
+ private String searchText;
+
+ /**
+ * 标题
+ */
+ private String title;
+
+ /**
+ * 内容
+ */
+ private String content;
+
+ /**
+ * 标签列表
+ */
+ private List tags;
+
+ /**
+ * 至少有一个标签
+ */
+ private List orTags;
+
+ /**
+ * 创建用户 id
+ */
+ private Long userId;
+
+ /**
+ * 收藏用户 id
+ */
+ private Long favourUserId;
+
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/src/main/java/com/yupi/springbootinit/model/dto/post/PostUpdateRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/post/PostUpdateRequest.java
new file mode 100644
index 0000000..c90bc70
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/dto/post/PostUpdateRequest.java
@@ -0,0 +1,37 @@
+package com.yupi.springbootinit.model.dto.post;
+
+import java.io.Serializable;
+import java.util.List;
+import lombok.Data;
+
+/**
+ * 更新请求
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@Data
+public class PostUpdateRequest implements Serializable {
+
+ /**
+ * id
+ */
+ private Long id;
+
+ /**
+ * 标题
+ */
+ private String title;
+
+ /**
+ * 内容
+ */
+ private String content;
+
+ /**
+ * 标签列表
+ */
+ private List tags;
+
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/src/main/java/com/yupi/springbootinit/model/dto/postfavour/PostFavourAddRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/postfavour/PostFavourAddRequest.java
new file mode 100644
index 0000000..1b8cc27
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/dto/postfavour/PostFavourAddRequest.java
@@ -0,0 +1,21 @@
+package com.yupi.springbootinit.model.dto.postfavour;
+
+import java.io.Serializable;
+import lombok.Data;
+
+/**
+ * 帖子收藏 / 取消收藏请求
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@Data
+public class PostFavourAddRequest implements Serializable {
+
+ /**
+ * 帖子 id
+ */
+ private Long postId;
+
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/src/main/java/com/yupi/springbootinit/model/dto/postfavour/PostFavourQueryRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/postfavour/PostFavourQueryRequest.java
new file mode 100644
index 0000000..e8bf989
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/dto/postfavour/PostFavourQueryRequest.java
@@ -0,0 +1,30 @@
+package com.yupi.springbootinit.model.dto.postfavour;
+
+import com.yupi.springbootinit.common.PageRequest;
+import com.yupi.springbootinit.model.dto.post.PostQueryRequest;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 帖子收藏查询请求
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class PostFavourQueryRequest extends PageRequest implements Serializable {
+
+ /**
+ * 帖子查询请求
+ */
+ private PostQueryRequest postQueryRequest;
+
+ /**
+ * 用户 id
+ */
+ private Long userId;
+
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/src/main/java/com/yupi/springbootinit/model/dto/postthumb/PostThumbAddRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/postthumb/PostThumbAddRequest.java
new file mode 100644
index 0000000..e6209e7
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/dto/postthumb/PostThumbAddRequest.java
@@ -0,0 +1,21 @@
+package com.yupi.springbootinit.model.dto.postthumb;
+
+import java.io.Serializable;
+import lombok.Data;
+
+/**
+ * 帖子点赞请求
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@Data
+public class PostThumbAddRequest implements Serializable {
+
+ /**
+ * 帖子 id
+ */
+ private Long postId;
+
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/src/main/java/com/yupi/springbootinit/model/dto/user/UserAddRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/user/UserAddRequest.java
new file mode 100644
index 0000000..7c56337
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/dto/user/UserAddRequest.java
@@ -0,0 +1,36 @@
+package com.yupi.springbootinit.model.dto.user;
+
+import java.io.Serializable;
+import lombok.Data;
+
+/**
+ * 用户创建请求
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@Data
+public class UserAddRequest implements Serializable {
+
+ /**
+ * 用户昵称
+ */
+ private String userName;
+
+ /**
+ * 账号
+ */
+ private String userAccount;
+
+ /**
+ * 用户头像
+ */
+ private String userAvatar;
+
+ /**
+ * 用户角色: user, admin
+ */
+ private String userRole;
+
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/src/main/java/com/yupi/springbootinit/model/dto/user/UserLoginRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/user/UserLoginRequest.java
new file mode 100644
index 0000000..85c241e
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/dto/user/UserLoginRequest.java
@@ -0,0 +1,20 @@
+package com.yupi.springbootinit.model.dto.user;
+
+import java.io.Serializable;
+import lombok.Data;
+
+/**
+ * 用户登录请求
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@Data
+public class UserLoginRequest implements Serializable {
+
+ private static final long serialVersionUID = 3191241716373120793L;
+
+ private String userAccount;
+
+ private String userPassword;
+}
diff --git a/src/main/java/com/yupi/springbootinit/model/dto/user/UserQueryRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/user/UserQueryRequest.java
new file mode 100644
index 0000000..7b0e3d4
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/dto/user/UserQueryRequest.java
@@ -0,0 +1,48 @@
+package com.yupi.springbootinit.model.dto.user;
+
+import com.yupi.springbootinit.common.PageRequest;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 用户查询请求
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class UserQueryRequest extends PageRequest implements Serializable {
+ /**
+ * id
+ */
+ private Long id;
+
+ /**
+ * 开放平台id
+ */
+ private String unionId;
+
+ /**
+ * 公众号openId
+ */
+ private String mpOpenId;
+
+ /**
+ * 用户昵称
+ */
+ private String userName;
+
+ /**
+ * 简介
+ */
+ private String userProfile;
+
+ /**
+ * 用户角色:user/admin/ban
+ */
+ private String userRole;
+
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/src/main/java/com/yupi/springbootinit/model/dto/user/UserRegisterRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/user/UserRegisterRequest.java
new file mode 100644
index 0000000..ded70ed
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/dto/user/UserRegisterRequest.java
@@ -0,0 +1,22 @@
+package com.yupi.springbootinit.model.dto.user;
+
+import java.io.Serializable;
+import lombok.Data;
+
+/**
+ * 用户注册请求体
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@Data
+public class UserRegisterRequest implements Serializable {
+
+ private static final long serialVersionUID = 3191241716373120793L;
+
+ private String userAccount;
+
+ private String userPassword;
+
+ private String checkPassword;
+}
diff --git a/src/main/java/com/yupi/springbootinit/model/dto/user/UserUpdateMyRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/user/UserUpdateMyRequest.java
new file mode 100644
index 0000000..f07f55a
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/dto/user/UserUpdateMyRequest.java
@@ -0,0 +1,31 @@
+package com.yupi.springbootinit.model.dto.user;
+
+import java.io.Serializable;
+import lombok.Data;
+
+/**
+ * 用户更新个人信息请求
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@Data
+public class UserUpdateMyRequest implements Serializable {
+
+ /**
+ * 用户昵称
+ */
+ private String userName;
+
+ /**
+ * 用户头像
+ */
+ private String userAvatar;
+
+ /**
+ * 简介
+ */
+ private String userProfile;
+
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/src/main/java/com/yupi/springbootinit/model/dto/user/UserUpdateRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/user/UserUpdateRequest.java
new file mode 100644
index 0000000..6536de5
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/dto/user/UserUpdateRequest.java
@@ -0,0 +1,40 @@
+package com.yupi.springbootinit.model.dto.user;
+
+import java.io.Serializable;
+import lombok.Data;
+
+/**
+ * 用户更新请求
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@Data
+public class UserUpdateRequest implements Serializable {
+ /**
+ * id
+ */
+ private Long id;
+
+ /**
+ * 用户昵称
+ */
+ private String userName;
+
+ /**
+ * 用户头像
+ */
+ private String userAvatar;
+
+ /**
+ * 简介
+ */
+ private String userProfile;
+
+ /**
+ * 用户角色:user/admin/ban
+ */
+ private String userRole;
+
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/src/main/java/com/yupi/springbootinit/model/entity/Chart.java b/src/main/java/com/yupi/springbootinit/model/entity/Chart.java
new file mode 100644
index 0000000..02da097
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/entity/Chart.java
@@ -0,0 +1,81 @@
+package com.yupi.springbootinit.model.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+
+import java.util.Date;
+import lombok.Data;
+
+/**
+ * 图表信息表
+ * @TableName chart
+ */
+@TableName(value ="chart")
+@Data
+public class Chart {
+ /**
+ * id
+ */
+ @TableId(type = IdType.ASSIGN_ID)
+ private Long id;
+
+ /**
+ * 名称
+ */
+ private String name;
+
+ /**
+ * 分析目标
+ */
+ private String goal;
+
+ /**
+ * 图表数据
+ */
+ private String chartData;
+
+ /**
+ * 图表类型
+ */
+ private String chartType;
+
+ /**
+ * 生成的图表数据
+ */
+ private String genChart;
+
+ /**
+ * 生成的分析结论
+ */
+ private String genResult;
+
+ /**
+ * 任务状态
+ */
+ private String status;
+
+ /**
+ * 执行信息
+ */
+ private String execMessage;
+
+ /**
+ * 创建用户 id
+ */
+ private Long userId;
+
+ /**
+ * 创建时间
+ */
+ private Date createTime;
+
+ /**
+ * 更新时间
+ */
+ private Date updateTime;
+
+ /**
+ * 是否删除
+ */
+ @TableLogic
+ private Integer isDelete;
+}
\ No newline at end of file
diff --git a/src/main/java/com/yupi/springbootinit/model/entity/Post.java b/src/main/java/com/yupi/springbootinit/model/entity/Post.java
new file mode 100644
index 0000000..3c0e301
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/entity/Post.java
@@ -0,0 +1,76 @@
+package com.yupi.springbootinit.model.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.util.Date;
+import lombok.Data;
+
+/**
+ * 帖子
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@TableName(value = "post")
+@Data
+public class Post implements Serializable {
+
+ /**
+ * id
+ */
+ @TableId(type = IdType.ASSIGN_ID)
+ private Long id;
+
+ /**
+ * 标题
+ */
+ private String title;
+
+ /**
+ * 内容
+ */
+ private String content;
+
+ /**
+ * 标签列表 json
+ */
+ private String tags;
+
+ /**
+ * 点赞数
+ */
+ private Integer thumbNum;
+
+ /**
+ * 收藏数
+ */
+ private Integer favourNum;
+
+ /**
+ * 创建用户 id
+ */
+ private Long userId;
+
+ /**
+ * 创建时间
+ */
+ private Date createTime;
+
+ /**
+ * 更新时间
+ */
+ private Date updateTime;
+
+ /**
+ * 是否删除
+ */
+ @TableLogic
+ private Integer isDelete;
+
+ @TableField(exist = false)
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/src/main/java/com/yupi/springbootinit/model/entity/PostFavour.java b/src/main/java/com/yupi/springbootinit/model/entity/PostFavour.java
new file mode 100644
index 0000000..7626ec5
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/entity/PostFavour.java
@@ -0,0 +1,49 @@
+package com.yupi.springbootinit.model.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.util.Date;
+import lombok.Data;
+
+/**
+ * 帖子收藏
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ **/
+@TableName(value = "post_favour")
+@Data
+public class PostFavour implements Serializable {
+
+ /**
+ * id
+ */
+ @TableId(type = IdType.AUTO)
+ private Long id;
+
+ /**
+ * 帖子 id
+ */
+ private Long postId;
+
+ /**
+ * 创建用户 id
+ */
+ private Long userId;
+
+ /**
+ * 创建时间
+ */
+ private Date createTime;
+
+ /**
+ * 更新时间
+ */
+ private Date updateTime;
+
+ @TableField(exist = false)
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/src/main/java/com/yupi/springbootinit/model/entity/PostThumb.java b/src/main/java/com/yupi/springbootinit/model/entity/PostThumb.java
new file mode 100644
index 0000000..c22f119
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/entity/PostThumb.java
@@ -0,0 +1,49 @@
+package com.yupi.springbootinit.model.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.util.Date;
+import lombok.Data;
+
+/**
+ * 帖子点赞
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@TableName(value = "post_thumb")
+@Data
+public class PostThumb implements Serializable {
+
+ /**
+ * id
+ */
+ @TableId(type = IdType.AUTO)
+ private Long id;
+
+ /**
+ * 帖子 id
+ */
+ private Long postId;
+
+ /**
+ * 创建用户 id
+ */
+ private Long userId;
+
+ /**
+ * 创建时间
+ */
+ private Date createTime;
+
+ /**
+ * 更新时间
+ */
+ private Date updateTime;
+
+ @TableField(exist = false)
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/src/main/java/com/yupi/springbootinit/model/entity/User.java b/src/main/java/com/yupi/springbootinit/model/entity/User.java
new file mode 100644
index 0000000..95ea135
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/entity/User.java
@@ -0,0 +1,61 @@
+package com.yupi.springbootinit.model.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+
+import java.util.Date;
+import lombok.Data;
+
+/**
+ * 用户
+ * @TableName user
+ */
+@TableName(value ="user")
+@Data
+public class User {
+ /**
+ * id
+ */
+ @TableId(type = IdType.ASSIGN_ID)
+ private Long id;
+
+ /**
+ * 账号
+ */
+ private String userAccount;
+
+ /**
+ * 密码
+ */
+ private String userPassword;
+
+ /**
+ * 用户昵称
+ */
+ private String userName;
+
+ /**
+ * 用户头像
+ */
+ private String userAvatar;
+
+ /**
+ * 用户角色:user/admin
+ */
+ private String userRole;
+
+ /**
+ * 创建时间
+ */
+ private Date createTime;
+
+ /**
+ * 更新时间
+ */
+ private Date updateTime;
+
+ /**
+ * 是否删除
+ */
+ @TableLogic
+ private Integer isDelete;
+}
\ No newline at end of file
diff --git a/src/main/java/com/yupi/springbootinit/model/enums/FileUploadBizEnum.java b/src/main/java/com/yupi/springbootinit/model/enums/FileUploadBizEnum.java
new file mode 100644
index 0000000..da246eb
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/enums/FileUploadBizEnum.java
@@ -0,0 +1,61 @@
+package com.yupi.springbootinit.model.enums;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.ObjectUtils;
+
+/**
+ * 文件上传业务类型枚举
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+public enum FileUploadBizEnum {
+
+ USER_AVATAR("用户头像", "user_avatar");
+
+ private final String text;
+
+ private final String value;
+
+ FileUploadBizEnum(String text, String value) {
+ this.text = text;
+ this.value = value;
+ }
+
+ /**
+ * 获取值列表
+ *
+ * @return
+ */
+ public static List getValues() {
+ return Arrays.stream(values()).map(item -> item.value).collect(Collectors.toList());
+ }
+
+ /**
+ * 根据 value 获取枚举
+ *
+ * @param value
+ * @return
+ */
+ public static FileUploadBizEnum getEnumByValue(String value) {
+ if (ObjectUtils.isEmpty(value)) {
+ return null;
+ }
+ for (FileUploadBizEnum anEnum : FileUploadBizEnum.values()) {
+ if (anEnum.value.equals(value)) {
+ return anEnum;
+ }
+ }
+ return null;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public String getText() {
+ return text;
+ }
+}
diff --git a/src/main/java/com/yupi/springbootinit/model/enums/UserRoleEnum.java b/src/main/java/com/yupi/springbootinit/model/enums/UserRoleEnum.java
new file mode 100644
index 0000000..3aeb3cc
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/enums/UserRoleEnum.java
@@ -0,0 +1,63 @@
+package com.yupi.springbootinit.model.enums;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.ObjectUtils;
+
+/**
+ * 用户角色枚举
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+public enum UserRoleEnum {
+
+ USER("用户", "user"),
+ ADMIN("管理员", "admin"),
+ BAN("被封号", "ban");
+
+ private final String text;
+
+ private final String value;
+
+ UserRoleEnum(String text, String value) {
+ this.text = text;
+ this.value = value;
+ }
+
+ /**
+ * 获取值列表
+ *
+ * @return
+ */
+ public static List getValues() {
+ return Arrays.stream(values()).map(item -> item.value).collect(Collectors.toList());
+ }
+
+ /**
+ * 根据 value 获取枚举
+ *
+ * @param value
+ * @return
+ */
+ public static UserRoleEnum getEnumByValue(String value) {
+ if (ObjectUtils.isEmpty(value)) {
+ return null;
+ }
+ for (UserRoleEnum anEnum : UserRoleEnum.values()) {
+ if (anEnum.value.equals(value)) {
+ return anEnum;
+ }
+ }
+ return null;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public String getText() {
+ return text;
+ }
+}
diff --git a/src/main/java/com/yupi/springbootinit/model/vo/BiResponse.java b/src/main/java/com/yupi/springbootinit/model/vo/BiResponse.java
new file mode 100644
index 0000000..4cdd211
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/vo/BiResponse.java
@@ -0,0 +1,17 @@
+package com.yupi.springbootinit.model.vo;
+
+import lombok.Data;
+
+/**
+ * Bi的返回结果
+ */
+
+@Data
+public class BiResponse {
+
+ private String genChart;
+
+ private String genResult;
+
+ private Long chartId;
+}
diff --git a/src/main/java/com/yupi/springbootinit/model/vo/LoginUserVO.java b/src/main/java/com/yupi/springbootinit/model/vo/LoginUserVO.java
new file mode 100644
index 0000000..47ebf8f
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/vo/LoginUserVO.java
@@ -0,0 +1,52 @@
+package com.yupi.springbootinit.model.vo;
+
+import java.io.Serializable;
+import java.util.Date;
+import lombok.Data;
+
+/**
+ * 已登录用户视图(脱敏)
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ **/
+@Data
+public class LoginUserVO implements Serializable {
+
+ /**
+ * 用户 id
+ */
+ private Long id;
+
+ /**
+ * 用户昵称
+ */
+ private String userName;
+
+ /**
+ * 用户头像
+ */
+ private String userAvatar;
+
+ /**
+ * 用户简介
+ */
+ private String userProfile;
+
+ /**
+ * 用户角色:user/admin/ban
+ */
+ private String userRole;
+
+ /**
+ * 创建时间
+ */
+ private Date createTime;
+
+ /**
+ * 更新时间
+ */
+ private Date updateTime;
+
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/src/main/java/com/yupi/springbootinit/model/vo/PostVO.java b/src/main/java/com/yupi/springbootinit/model/vo/PostVO.java
new file mode 100644
index 0000000..92aa7fd
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/vo/PostVO.java
@@ -0,0 +1,112 @@
+package com.yupi.springbootinit.model.vo;
+
+import cn.hutool.json.JSONUtil;
+import com.yupi.springbootinit.model.entity.Post;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+import lombok.Data;
+import org.springframework.beans.BeanUtils;
+
+/**
+ * 帖子视图
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@Data
+public class PostVO implements Serializable {
+
+ /**
+ * id
+ */
+ private Long id;
+
+ /**
+ * 标题
+ */
+ private String title;
+
+ /**
+ * 内容
+ */
+ private String content;
+
+ /**
+ * 点赞数
+ */
+ private Integer thumbNum;
+
+ /**
+ * 收藏数
+ */
+ private Integer favourNum;
+
+ /**
+ * 创建用户 id
+ */
+ private Long userId;
+
+ /**
+ * 创建时间
+ */
+ private Date createTime;
+
+ /**
+ * 更新时间
+ */
+ private Date updateTime;
+
+ /**
+ * 标签列表
+ */
+ private List tagList;
+
+ /**
+ * 创建人信息
+ */
+ private UserVO user;
+
+ /**
+ * 是否已点赞
+ */
+ private Boolean hasThumb;
+
+ /**
+ * 是否已收藏
+ */
+ private Boolean hasFavour;
+
+ /**
+ * 包装类转对象
+ *
+ * @param postVO
+ * @return
+ */
+ public static Post voToObj(PostVO postVO) {
+ if (postVO == null) {
+ return null;
+ }
+ Post post = new Post();
+ BeanUtils.copyProperties(postVO, post);
+ List tagList = postVO.getTagList();
+ post.setTags(JSONUtil.toJsonStr(tagList));
+ return post;
+ }
+
+ /**
+ * 对象转包装类
+ *
+ * @param post
+ * @return
+ */
+ public static PostVO objToVo(Post post) {
+ if (post == null) {
+ return null;
+ }
+ PostVO postVO = new PostVO();
+ BeanUtils.copyProperties(post, postVO);
+ postVO.setTagList(JSONUtil.toList(post.getTags(), String.class));
+ return postVO;
+ }
+}
diff --git a/src/main/java/com/yupi/springbootinit/model/vo/UserVO.java b/src/main/java/com/yupi/springbootinit/model/vo/UserVO.java
new file mode 100644
index 0000000..536b1f4
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/model/vo/UserVO.java
@@ -0,0 +1,47 @@
+package com.yupi.springbootinit.model.vo;
+
+import java.io.Serializable;
+import java.util.Date;
+import lombok.Data;
+
+/**
+ * 用户视图(脱敏)
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@Data
+public class UserVO implements Serializable {
+
+ /**
+ * id
+ */
+ private Long id;
+
+ /**
+ * 用户昵称
+ */
+ private String userName;
+
+ /**
+ * 用户头像
+ */
+ private String userAvatar;
+
+ /**
+ * 用户简介
+ */
+ private String userProfile;
+
+ /**
+ * 用户角色:user/admin/ban
+ */
+ private String userRole;
+
+ /**
+ * 创建时间
+ */
+ private Date createTime;
+
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/src/main/java/com/yupi/springbootinit/service/ChartService.java b/src/main/java/com/yupi/springbootinit/service/ChartService.java
new file mode 100644
index 0000000..4531e1d
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/service/ChartService.java
@@ -0,0 +1,13 @@
+package com.yupi.springbootinit.service;
+
+import com.yupi.springbootinit.model.entity.Chart;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+* @author HeSun
+* @description 针对表【chart(图表信息表)】的数据库操作Service
+* @createDate 2025-02-20 20:52:40
+*/
+public interface ChartService extends IService {
+
+}
diff --git a/src/main/java/com/yupi/springbootinit/service/PostFavourService.java b/src/main/java/com/yupi/springbootinit/service/PostFavourService.java
new file mode 100644
index 0000000..1e29151
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/service/PostFavourService.java
@@ -0,0 +1,47 @@
+package com.yupi.springbootinit.service;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.yupi.springbootinit.model.entity.Post;
+import com.yupi.springbootinit.model.entity.PostFavour;
+import com.yupi.springbootinit.model.entity.User;
+
+/**
+ * 帖子收藏服务
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+public interface PostFavourService extends IService {
+
+ /**
+ * 帖子收藏
+ *
+ * @param postId
+ * @param loginUser
+ * @return
+ */
+ int doPostFavour(long postId, User loginUser);
+
+ /**
+ * 分页获取用户收藏的帖子列表
+ *
+ * @param page
+ * @param queryWrapper
+ * @param favourUserId
+ * @return
+ */
+ Page listFavourPostByPage(IPage page, Wrapper queryWrapper,
+ long favourUserId);
+
+ /**
+ * 帖子收藏(内部服务)
+ *
+ * @param userId
+ * @param postId
+ * @return
+ */
+ int doPostFavourInner(long userId, long postId);
+}
diff --git a/src/main/java/com/yupi/springbootinit/service/PostService.java b/src/main/java/com/yupi/springbootinit/service/PostService.java
new file mode 100644
index 0000000..d938f0b
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/service/PostService.java
@@ -0,0 +1,60 @@
+package com.yupi.springbootinit.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.yupi.springbootinit.model.dto.post.PostQueryRequest;
+import com.yupi.springbootinit.model.entity.Post;
+import com.yupi.springbootinit.model.vo.PostVO;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 帖子服务
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+public interface PostService extends IService {
+
+ /**
+ * 校验
+ *
+ * @param post
+ * @param add
+ */
+ void validPost(Post post, boolean add);
+
+ /**
+ * 获取查询条件
+ *
+ * @param postQueryRequest
+ * @return
+ */
+ QueryWrapper getQueryWrapper(PostQueryRequest postQueryRequest);
+
+ /**
+ * 从 ES 查询
+ *
+ * @param postQueryRequest
+ * @return
+ */
+ Page searchFromEs(PostQueryRequest postQueryRequest);
+
+ /**
+ * 获取帖子封装
+ *
+ * @param post
+ * @param request
+ * @return
+ */
+ PostVO getPostVO(Post post, HttpServletRequest request);
+
+ /**
+ * 分页获取帖子封装
+ *
+ * @param postPage
+ * @param request
+ * @return
+ */
+ Page getPostVOPage(Page postPage, HttpServletRequest request);
+}
diff --git a/src/main/java/com/yupi/springbootinit/service/PostThumbService.java b/src/main/java/com/yupi/springbootinit/service/PostThumbService.java
new file mode 100644
index 0000000..db731f9
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/service/PostThumbService.java
@@ -0,0 +1,32 @@
+package com.yupi.springbootinit.service;
+
+import com.yupi.springbootinit.model.entity.PostThumb;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.yupi.springbootinit.model.entity.User;
+
+/**
+ * 帖子点赞服务
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+public interface PostThumbService extends IService {
+
+ /**
+ * 点赞
+ *
+ * @param postId
+ * @param loginUser
+ * @return
+ */
+ int doPostThumb(long postId, User loginUser);
+
+ /**
+ * 帖子点赞(内部服务)
+ *
+ * @param userId
+ * @param postId
+ * @return
+ */
+ int doPostThumbInner(long userId, long postId);
+}
diff --git a/src/main/java/com/yupi/springbootinit/service/UserService.java b/src/main/java/com/yupi/springbootinit/service/UserService.java
new file mode 100644
index 0000000..5035bf6
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/service/UserService.java
@@ -0,0 +1,112 @@
+package com.yupi.springbootinit.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.yupi.springbootinit.model.dto.user.UserQueryRequest;
+import com.yupi.springbootinit.model.entity.User;
+import com.yupi.springbootinit.model.vo.LoginUserVO;
+import com.yupi.springbootinit.model.vo.UserVO;
+import java.util.List;
+import javax.servlet.http.HttpServletRequest;
+import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
+
+/**
+ * 用户服务
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+public interface UserService extends IService {
+
+ /**
+ * 用户注册
+ *
+ * @param userAccount 用户账户
+ * @param userPassword 用户密码
+ * @param checkPassword 校验密码
+ * @return 新用户 id
+ */
+ long userRegister(String userAccount, String userPassword, String checkPassword);
+
+ /**
+ * 用户登录
+ *
+ * @param userAccount 用户账户
+ * @param userPassword 用户密码
+ * @param request
+ * @return 脱敏后的用户信息
+ */
+ LoginUserVO userLogin(String userAccount, String userPassword, HttpServletRequest request);
+
+ /**
+ * 获取当前登录用户
+ *
+ * @param request
+ * @return
+ */
+ User getLoginUser(HttpServletRequest request);
+
+ /**
+ * 获取当前登录用户(允许未登录)
+ *
+ * @param request
+ * @return
+ */
+ User getLoginUserPermitNull(HttpServletRequest request);
+
+ /**
+ * 是否为管理员
+ *
+ * @param request
+ * @return
+ */
+ boolean isAdmin(HttpServletRequest request);
+
+ /**
+ * 是否为管理员
+ *
+ * @param user
+ * @return
+ */
+ boolean isAdmin(User user);
+
+ /**
+ * 用户注销
+ *
+ * @param request
+ * @return
+ */
+ boolean userLogout(HttpServletRequest request);
+
+ /**
+ * 获取脱敏的已登录用户信息
+ *
+ * @return
+ */
+ LoginUserVO getLoginUserVO(User user);
+
+ /**
+ * 获取脱敏的用户信息
+ *
+ * @param user
+ * @return
+ */
+ UserVO getUserVO(User user);
+
+ /**
+ * 获取脱敏的用户信息
+ *
+ * @param userList
+ * @return
+ */
+ List getUserVO(List userList);
+
+ /**
+ * 获取查询条件
+ *
+ * @param userQueryRequest
+ * @return
+ */
+ QueryWrapper getQueryWrapper(UserQueryRequest userQueryRequest);
+
+}
diff --git a/src/main/java/com/yupi/springbootinit/service/impl/ChartServiceImpl.java b/src/main/java/com/yupi/springbootinit/service/impl/ChartServiceImpl.java
new file mode 100644
index 0000000..2cb39d7
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/service/impl/ChartServiceImpl.java
@@ -0,0 +1,22 @@
+package com.yupi.springbootinit.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.yupi.springbootinit.model.entity.Chart;
+import com.yupi.springbootinit.service.ChartService;
+import com.yupi.springbootinit.mapper.ChartMapper;
+import org.springframework.stereotype.Service;
+
+/**
+* @author HeSun
+* @description 针对表【chart(图表信息表)】的数据库操作Service实现
+* @createDate 2025-02-20 20:52:40
+*/
+@Service
+public class ChartServiceImpl extends ServiceImpl
+ implements ChartService{
+
+}
+
+
+
+
diff --git a/src/main/java/com/yupi/springbootinit/service/impl/PostFavourServiceImpl.java b/src/main/java/com/yupi/springbootinit/service/impl/PostFavourServiceImpl.java
new file mode 100644
index 0000000..08abe11
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/service/impl/PostFavourServiceImpl.java
@@ -0,0 +1,116 @@
+package com.yupi.springbootinit.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.yupi.springbootinit.common.ErrorCode;
+import com.yupi.springbootinit.exception.BusinessException;
+import com.yupi.springbootinit.mapper.PostFavourMapper;
+import com.yupi.springbootinit.model.entity.Post;
+import com.yupi.springbootinit.model.entity.PostFavour;
+import com.yupi.springbootinit.model.entity.User;
+import com.yupi.springbootinit.service.PostFavourService;
+import com.yupi.springbootinit.service.PostService;
+import javax.annotation.Resource;
+import org.springframework.aop.framework.AopContext;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * 帖子收藏服务实现
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@Service
+public class PostFavourServiceImpl extends ServiceImpl
+ implements PostFavourService {
+
+ @Resource
+ private PostService postService;
+
+ /**
+ * 帖子收藏
+ *
+ * @param postId
+ * @param loginUser
+ * @return
+ */
+ @Override
+ public int doPostFavour(long postId, User loginUser) {
+ // 判断是否存在
+ Post post = postService.getById(postId);
+ if (post == null) {
+ throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);
+ }
+ // 是否已帖子收藏
+ long userId = loginUser.getId();
+ // 每个用户串行帖子收藏
+ // 锁必须要包裹住事务方法
+ PostFavourService postFavourService = (PostFavourService) AopContext.currentProxy();
+ synchronized (String.valueOf(userId).intern()) {
+ return postFavourService.doPostFavourInner(userId, postId);
+ }
+ }
+
+ @Override
+ public Page listFavourPostByPage(IPage page, Wrapper queryWrapper, long favourUserId) {
+ if (favourUserId <= 0) {
+ return new Page<>();
+ }
+ return baseMapper.listFavourPostByPage(page, queryWrapper, favourUserId);
+ }
+
+ /**
+ * 封装了事务的方法
+ *
+ * @param userId
+ * @param postId
+ * @return
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public int doPostFavourInner(long userId, long postId) {
+ PostFavour postFavour = new PostFavour();
+ postFavour.setUserId(userId);
+ postFavour.setPostId(postId);
+ QueryWrapper postFavourQueryWrapper = new QueryWrapper<>(postFavour);
+ PostFavour oldPostFavour = this.getOne(postFavourQueryWrapper);
+ boolean result;
+ // 已收藏
+ if (oldPostFavour != null) {
+ result = this.remove(postFavourQueryWrapper);
+ if (result) {
+ // 帖子收藏数 - 1
+ result = postService.update()
+ .eq("id", postId)
+ .gt("favourNum", 0)
+ .setSql("favourNum = favourNum - 1")
+ .update();
+ return result ? -1 : 0;
+ } else {
+ throw new BusinessException(ErrorCode.SYSTEM_ERROR);
+ }
+ } else {
+ // 未帖子收藏
+ result = this.save(postFavour);
+ if (result) {
+ // 帖子收藏数 + 1
+ result = postService.update()
+ .eq("id", postId)
+ .setSql("favourNum = favourNum + 1")
+ .update();
+ return result ? 1 : 0;
+ } else {
+ throw new BusinessException(ErrorCode.SYSTEM_ERROR);
+ }
+ }
+ }
+
+}
+
+
+
+
diff --git a/src/main/java/com/yupi/springbootinit/service/impl/PostServiceImpl.java b/src/main/java/com/yupi/springbootinit/service/impl/PostServiceImpl.java
new file mode 100644
index 0000000..791c8c4
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/service/impl/PostServiceImpl.java
@@ -0,0 +1,312 @@
+package com.yupi.springbootinit.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.yupi.springbootinit.common.ErrorCode;
+import com.yupi.springbootinit.constant.CommonConstant;
+import com.yupi.springbootinit.exception.BusinessException;
+import com.yupi.springbootinit.exception.ThrowUtils;
+import com.yupi.springbootinit.mapper.PostFavourMapper;
+import com.yupi.springbootinit.mapper.PostMapper;
+import com.yupi.springbootinit.mapper.PostThumbMapper;
+import com.yupi.springbootinit.model.dto.post.PostEsDTO;
+import com.yupi.springbootinit.model.dto.post.PostQueryRequest;
+import com.yupi.springbootinit.model.entity.Post;
+import com.yupi.springbootinit.model.entity.PostFavour;
+import com.yupi.springbootinit.model.entity.PostThumb;
+import com.yupi.springbootinit.model.entity.User;
+import com.yupi.springbootinit.model.vo.PostVO;
+import com.yupi.springbootinit.model.vo.UserVO;
+import com.yupi.springbootinit.service.PostService;
+import com.yupi.springbootinit.service.UserService;
+import com.yupi.springbootinit.utils.SqlUtils;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
+import cn.hutool.core.collection.CollUtil;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.elasticsearch.index.query.BoolQueryBuilder;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.search.sort.SortBuilder;
+import org.elasticsearch.search.sort.SortBuilders;
+import org.elasticsearch.search.sort.SortOrder;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
+import org.springframework.data.elasticsearch.core.SearchHit;
+import org.springframework.data.elasticsearch.core.SearchHits;
+import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
+import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
+import org.springframework.stereotype.Service;
+
+/**
+ * 帖子服务实现
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@Service
+@Slf4j
+public class PostServiceImpl extends ServiceImpl implements PostService {
+
+ @Resource
+ private UserService userService;
+
+ @Resource
+ private PostThumbMapper postThumbMapper;
+
+ @Resource
+ private PostFavourMapper postFavourMapper;
+
+ @Resource
+ private ElasticsearchRestTemplate elasticsearchRestTemplate;
+
+ @Override
+ public void validPost(Post post, boolean add) {
+ if (post == null) {
+ throw new BusinessException(ErrorCode.PARAMS_ERROR);
+ }
+ String title = post.getTitle();
+ String content = post.getContent();
+ String tags = post.getTags();
+ // 创建时,参数不能为空
+ if (add) {
+ ThrowUtils.throwIf(StringUtils.isAnyBlank(title, content, tags), ErrorCode.PARAMS_ERROR);
+ }
+ // 有参数则校验
+ if (StringUtils.isNotBlank(title) && title.length() > 80) {
+ throw new BusinessException(ErrorCode.PARAMS_ERROR, "标题过长");
+ }
+ if (StringUtils.isNotBlank(content) && content.length() > 8192) {
+ throw new BusinessException(ErrorCode.PARAMS_ERROR, "内容过长");
+ }
+ }
+
+ /**
+ * 获取查询包装类
+ *
+ * @param postQueryRequest
+ * @return
+ */
+ @Override
+ public QueryWrapper getQueryWrapper(PostQueryRequest postQueryRequest) {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ if (postQueryRequest == null) {
+ return queryWrapper;
+ }
+ String searchText = postQueryRequest.getSearchText();
+ String sortField = postQueryRequest.getSortField();
+ String sortOrder = postQueryRequest.getSortOrder();
+ Long id = postQueryRequest.getId();
+ String title = postQueryRequest.getTitle();
+ String content = postQueryRequest.getContent();
+ List tagList = postQueryRequest.getTags();
+ Long userId = postQueryRequest.getUserId();
+ Long notId = postQueryRequest.getNotId();
+ // 拼接查询条件
+ if (StringUtils.isNotBlank(searchText)) {
+ queryWrapper.and(qw -> qw.like("title", searchText).or().like("content", searchText));
+ }
+ queryWrapper.like(StringUtils.isNotBlank(title), "title", title);
+ queryWrapper.like(StringUtils.isNotBlank(content), "content", content);
+ if (CollUtil.isNotEmpty(tagList)) {
+ for (String tag : tagList) {
+ queryWrapper.like("tags", "\"" + tag + "\"");
+ }
+ }
+ queryWrapper.ne(ObjectUtils.isNotEmpty(notId), "id", notId);
+ queryWrapper.eq(ObjectUtils.isNotEmpty(id), "id", id);
+ queryWrapper.eq(ObjectUtils.isNotEmpty(userId), "userId", userId);
+ queryWrapper.orderBy(SqlUtils.validSortField(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC),
+ sortField);
+ return queryWrapper;
+ }
+
+ @Override
+ public Page searchFromEs(PostQueryRequest postQueryRequest) {
+ Long id = postQueryRequest.getId();
+ Long notId = postQueryRequest.getNotId();
+ String searchText = postQueryRequest.getSearchText();
+ String title = postQueryRequest.getTitle();
+ String content = postQueryRequest.getContent();
+ List tagList = postQueryRequest.getTags();
+ List orTagList = postQueryRequest.getOrTags();
+ Long userId = postQueryRequest.getUserId();
+ // es 起始页为 0
+ long current = postQueryRequest.getCurrent() - 1;
+ long pageSize = postQueryRequest.getPageSize();
+ String sortField = postQueryRequest.getSortField();
+ String sortOrder = postQueryRequest.getSortOrder();
+ BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
+ // 过滤
+ boolQueryBuilder.filter(QueryBuilders.termQuery("isDelete", 0));
+ if (id != null) {
+ boolQueryBuilder.filter(QueryBuilders.termQuery("id", id));
+ }
+ if (notId != null) {
+ boolQueryBuilder.mustNot(QueryBuilders.termQuery("id", notId));
+ }
+ if (userId != null) {
+ boolQueryBuilder.filter(QueryBuilders.termQuery("userId", userId));
+ }
+ // 必须包含所有标签
+ if (CollUtil.isNotEmpty(tagList)) {
+ for (String tag : tagList) {
+ boolQueryBuilder.filter(QueryBuilders.termQuery("tags", tag));
+ }
+ }
+ // 包含任何一个标签即可
+ if (CollUtil.isNotEmpty(orTagList)) {
+ BoolQueryBuilder orTagBoolQueryBuilder = QueryBuilders.boolQuery();
+ for (String tag : orTagList) {
+ orTagBoolQueryBuilder.should(QueryBuilders.termQuery("tags", tag));
+ }
+ orTagBoolQueryBuilder.minimumShouldMatch(1);
+ boolQueryBuilder.filter(orTagBoolQueryBuilder);
+ }
+ // 按关键词检索
+ if (StringUtils.isNotBlank(searchText)) {
+ boolQueryBuilder.should(QueryBuilders.matchQuery("title", searchText));
+ boolQueryBuilder.should(QueryBuilders.matchQuery("description", searchText));
+ boolQueryBuilder.should(QueryBuilders.matchQuery("content", searchText));
+ boolQueryBuilder.minimumShouldMatch(1);
+ }
+ // 按标题检索
+ if (StringUtils.isNotBlank(title)) {
+ boolQueryBuilder.should(QueryBuilders.matchQuery("title", title));
+ boolQueryBuilder.minimumShouldMatch(1);
+ }
+ // 按内容检索
+ if (StringUtils.isNotBlank(content)) {
+ boolQueryBuilder.should(QueryBuilders.matchQuery("content", content));
+ boolQueryBuilder.minimumShouldMatch(1);
+ }
+ // 排序
+ SortBuilder> sortBuilder = SortBuilders.scoreSort();
+ if (StringUtils.isNotBlank(sortField)) {
+ sortBuilder = SortBuilders.fieldSort(sortField);
+ sortBuilder.order(CommonConstant.SORT_ORDER_ASC.equals(sortOrder) ? SortOrder.ASC : SortOrder.DESC);
+ }
+ // 分页
+ PageRequest pageRequest = PageRequest.of((int) current, (int) pageSize);
+ // 构造查询
+ NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder)
+ .withPageable(pageRequest).withSorts(sortBuilder).build();
+ SearchHits searchHits = elasticsearchRestTemplate.search(searchQuery, PostEsDTO.class);
+ Page page = new Page<>();
+ page.setTotal(searchHits.getTotalHits());
+ List resourceList = new ArrayList<>();
+ // 查出结果后,从 db 获取最新动态数据(比如点赞数)
+ if (searchHits.hasSearchHits()) {
+ List> searchHitList = searchHits.getSearchHits();
+ List postIdList = searchHitList.stream().map(searchHit -> searchHit.getContent().getId())
+ .collect(Collectors.toList());
+ List postList = baseMapper.selectBatchIds(postIdList);
+ if (postList != null) {
+ Map> idPostMap = postList.stream().collect(Collectors.groupingBy(Post::getId));
+ postIdList.forEach(postId -> {
+ if (idPostMap.containsKey(postId)) {
+ resourceList.add(idPostMap.get(postId).get(0));
+ } else {
+ // 从 es 清空 db 已物理删除的数据
+ String delete = elasticsearchRestTemplate.delete(String.valueOf(postId), PostEsDTO.class);
+ log.info("delete post {}", delete);
+ }
+ });
+ }
+ }
+ page.setRecords(resourceList);
+ return page;
+ }
+
+ @Override
+ public PostVO getPostVO(Post post, HttpServletRequest request) {
+ PostVO postVO = PostVO.objToVo(post);
+ long postId = post.getId();
+ // 1. 关联查询用户信息
+ Long userId = post.getUserId();
+ User user = null;
+ if (userId != null && userId > 0) {
+ user = userService.getById(userId);
+ }
+ UserVO userVO = userService.getUserVO(user);
+ postVO.setUser(userVO);
+ // 2. 已登录,获取用户点赞、收藏状态
+ User loginUser = userService.getLoginUserPermitNull(request);
+ if (loginUser != null) {
+ // 获取点赞
+ QueryWrapper postThumbQueryWrapper = new QueryWrapper<>();
+ postThumbQueryWrapper.in("postId", postId);
+ postThumbQueryWrapper.eq("userId", loginUser.getId());
+ PostThumb postThumb = postThumbMapper.selectOne(postThumbQueryWrapper);
+ postVO.setHasThumb(postThumb != null);
+ // 获取收藏
+ QueryWrapper postFavourQueryWrapper = new QueryWrapper<>();
+ postFavourQueryWrapper.in("postId", postId);
+ postFavourQueryWrapper.eq("userId", loginUser.getId());
+ PostFavour postFavour = postFavourMapper.selectOne(postFavourQueryWrapper);
+ postVO.setHasFavour(postFavour != null);
+ }
+ return postVO;
+ }
+
+ @Override
+ public Page getPostVOPage(Page postPage, HttpServletRequest request) {
+ List postList = postPage.getRecords();
+ Page postVOPage = new Page<>(postPage.getCurrent(), postPage.getSize(), postPage.getTotal());
+ if (CollUtil.isEmpty(postList)) {
+ return postVOPage;
+ }
+ // 1. 关联查询用户信息
+ Set userIdSet = postList.stream().map(Post::getUserId).collect(Collectors.toSet());
+ Map> userIdUserListMap = userService.listByIds(userIdSet).stream()
+ .collect(Collectors.groupingBy(User::getId));
+ // 2. 已登录,获取用户点赞、收藏状态
+ Map postIdHasThumbMap = new HashMap<>();
+ Map postIdHasFavourMap = new HashMap<>();
+ User loginUser = userService.getLoginUserPermitNull(request);
+ if (loginUser != null) {
+ Set postIdSet = postList.stream().map(Post::getId).collect(Collectors.toSet());
+ loginUser = userService.getLoginUser(request);
+ // 获取点赞
+ QueryWrapper postThumbQueryWrapper = new QueryWrapper<>();
+ postThumbQueryWrapper.in("postId", postIdSet);
+ postThumbQueryWrapper.eq("userId", loginUser.getId());
+ List postPostThumbList = postThumbMapper.selectList(postThumbQueryWrapper);
+ postPostThumbList.forEach(postPostThumb -> postIdHasThumbMap.put(postPostThumb.getPostId(), true));
+ // 获取收藏
+ QueryWrapper postFavourQueryWrapper = new QueryWrapper<>();
+ postFavourQueryWrapper.in("postId", postIdSet);
+ postFavourQueryWrapper.eq("userId", loginUser.getId());
+ List postFavourList = postFavourMapper.selectList(postFavourQueryWrapper);
+ postFavourList.forEach(postFavour -> postIdHasFavourMap.put(postFavour.getPostId(), true));
+ }
+ // 填充信息
+ List postVOList = postList.stream().map(post -> {
+ PostVO postVO = PostVO.objToVo(post);
+ Long userId = post.getUserId();
+ User user = null;
+ if (userIdUserListMap.containsKey(userId)) {
+ user = userIdUserListMap.get(userId).get(0);
+ }
+ postVO.setUser(userService.getUserVO(user));
+ postVO.setHasThumb(postIdHasThumbMap.getOrDefault(post.getId(), false));
+ postVO.setHasFavour(postIdHasFavourMap.getOrDefault(post.getId(), false));
+ return postVO;
+ }).collect(Collectors.toList());
+ postVOPage.setRecords(postVOList);
+ return postVOPage;
+ }
+
+}
+
+
+
+
diff --git a/src/main/java/com/yupi/springbootinit/service/impl/PostThumbServiceImpl.java b/src/main/java/com/yupi/springbootinit/service/impl/PostThumbServiceImpl.java
new file mode 100644
index 0000000..a0b3c06
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/service/impl/PostThumbServiceImpl.java
@@ -0,0 +1,105 @@
+package com.yupi.springbootinit.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.yupi.springbootinit.common.ErrorCode;
+import com.yupi.springbootinit.exception.BusinessException;
+import com.yupi.springbootinit.mapper.PostThumbMapper;
+import com.yupi.springbootinit.model.entity.Post;
+import com.yupi.springbootinit.model.entity.PostThumb;
+import com.yupi.springbootinit.model.entity.User;
+import com.yupi.springbootinit.service.PostService;
+import com.yupi.springbootinit.service.PostThumbService;
+import javax.annotation.Resource;
+import org.springframework.aop.framework.AopContext;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * 帖子点赞服务实现
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@Service
+public class PostThumbServiceImpl extends ServiceImpl
+ implements PostThumbService {
+
+ @Resource
+ private PostService postService;
+
+ /**
+ * 点赞
+ *
+ * @param postId
+ * @param loginUser
+ * @return
+ */
+ @Override
+ public int doPostThumb(long postId, User loginUser) {
+ // 判断实体是否存在,根据类别获取实体
+ Post post = postService.getById(postId);
+ if (post == null) {
+ throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);
+ }
+ // 是否已点赞
+ long userId = loginUser.getId();
+ // 每个用户串行点赞
+ // 锁必须要包裹住事务方法
+ PostThumbService postThumbService = (PostThumbService) AopContext.currentProxy();
+ synchronized (String.valueOf(userId).intern()) {
+ return postThumbService.doPostThumbInner(userId, postId);
+ }
+ }
+
+ /**
+ * 封装了事务的方法
+ *
+ * @param userId
+ * @param postId
+ * @return
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public int doPostThumbInner(long userId, long postId) {
+ PostThumb postThumb = new PostThumb();
+ postThumb.setUserId(userId);
+ postThumb.setPostId(postId);
+ QueryWrapper thumbQueryWrapper = new QueryWrapper<>(postThumb);
+ PostThumb oldPostThumb = this.getOne(thumbQueryWrapper);
+ boolean result;
+ // 已点赞
+ if (oldPostThumb != null) {
+ result = this.remove(thumbQueryWrapper);
+ if (result) {
+ // 点赞数 - 1
+ result = postService.update()
+ .eq("id", postId)
+ .gt("thumbNum", 0)
+ .setSql("thumbNum = thumbNum - 1")
+ .update();
+ return result ? -1 : 0;
+ } else {
+ throw new BusinessException(ErrorCode.SYSTEM_ERROR);
+ }
+ } else {
+ // 未点赞
+ result = this.save(postThumb);
+ if (result) {
+ // 点赞数 + 1
+ result = postService.update()
+ .eq("id", postId)
+ .setSql("thumbNum = thumbNum + 1")
+ .update();
+ return result ? 1 : 0;
+ } else {
+ throw new BusinessException(ErrorCode.SYSTEM_ERROR);
+ }
+ }
+ }
+
+}
+
+
+
+
diff --git a/src/main/java/com/yupi/springbootinit/service/impl/UserServiceImpl.java b/src/main/java/com/yupi/springbootinit/service/impl/UserServiceImpl.java
new file mode 100644
index 0000000..7bdc599
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/service/impl/UserServiceImpl.java
@@ -0,0 +1,240 @@
+package com.yupi.springbootinit.service.impl;
+
+import static com.yupi.springbootinit.constant.UserConstant.USER_LOGIN_STATE;
+
+import cn.hutool.core.collection.CollUtil;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.yupi.springbootinit.common.ErrorCode;
+import com.yupi.springbootinit.constant.CommonConstant;
+import com.yupi.springbootinit.exception.BusinessException;
+import com.yupi.springbootinit.mapper.UserMapper;
+import com.yupi.springbootinit.model.dto.user.UserQueryRequest;
+import com.yupi.springbootinit.model.entity.User;
+import com.yupi.springbootinit.model.enums.UserRoleEnum;
+import com.yupi.springbootinit.model.vo.LoginUserVO;
+import com.yupi.springbootinit.model.vo.UserVO;
+import com.yupi.springbootinit.service.UserService;
+import com.yupi.springbootinit.utils.SqlUtils;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.util.DigestUtils;
+
+/**
+ * 用户服务实现
+ *
+ * @author 程序员鱼皮
+ * @from 编程导航知识星球
+ */
+@Service
+@Slf4j
+public class UserServiceImpl extends ServiceImpl implements UserService {
+
+ /**
+ * 盐值,混淆密码
+ */
+ public static final String SALT = "yupi";
+
+ @Override
+ public long userRegister(String userAccount, String userPassword, String checkPassword) {
+ // 1. 校验
+ if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword)) {
+ throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
+ }
+ if (userAccount.length() < 4) {
+ throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号过短");
+ }
+ if (userPassword.length() < 8 || checkPassword.length() < 8) {
+ throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户密码过短");
+ }
+ // 密码和校验密码相同
+ if (!userPassword.equals(checkPassword)) {
+ throw new BusinessException(ErrorCode.PARAMS_ERROR, "两次输入的密码不一致");
+ }
+ synchronized (userAccount.intern()) {
+ // 账户不能重复
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("userAccount", userAccount);
+ long count = this.baseMapper.selectCount(queryWrapper);
+ if (count > 0) {
+ throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号重复");
+ }
+ // 2. 加密
+ String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
+ // 3. 插入数据
+ User user = new User();
+ user.setUserAccount(userAccount);
+ user.setUserPassword(encryptPassword);
+ boolean saveResult = this.save(user);
+ if (!saveResult) {
+ throw new BusinessException(ErrorCode.SYSTEM_ERROR, "注册失败,数据库错误");
+ }
+ return user.getId();
+ }
+ }
+
+ @Override
+ public LoginUserVO userLogin(String userAccount, String userPassword, HttpServletRequest request) {
+ // 1. 校验
+ if (StringUtils.isAnyBlank(userAccount, userPassword)) {
+ throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
+ }
+ if (userAccount.length() < 4) {
+ throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号错误");
+ }
+ if (userPassword.length() < 8) {
+ throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码错误");
+ }
+ // 2. 加密
+ String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
+ // 查询用户是否存在
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("userAccount", userAccount);
+ queryWrapper.eq("userPassword", encryptPassword);
+ User user = this.baseMapper.selectOne(queryWrapper);
+ // 用户不存在
+ if (user == null) {
+ log.info("user login failed, userAccount cannot match userPassword");
+ throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户不存在或密码错误");
+ }
+ // 3. 记录用户的登录态
+ request.getSession().setAttribute(USER_LOGIN_STATE, user);
+ return this.getLoginUserVO(user);
+ }
+
+ /**
+ * 获取当前登录用户
+ *
+ * @param request
+ * @return
+ */
+ @Override
+ public User getLoginUser(HttpServletRequest request) {
+ // 先判断是否已登录
+ Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
+ User currentUser = (User) userObj;
+ if (currentUser == null || currentUser.getId() == null) {
+ throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
+ }
+ // 从数据库查询(追求性能的话可以注释,直接走缓存)
+ long userId = currentUser.getId();
+ currentUser = this.getById(userId);
+ if (currentUser == null) {
+ throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
+ }
+ return currentUser;
+ }
+
+ /**
+ * 获取当前登录用户(允许未登录)
+ *
+ * @param request
+ * @return
+ */
+ @Override
+ public User getLoginUserPermitNull(HttpServletRequest request) {
+ // 先判断是否已登录
+ Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
+ User currentUser = (User) userObj;
+ if (currentUser == null || currentUser.getId() == null) {
+ return null;
+ }
+ // 从数据库查询(追求性能的话可以注释,直接走缓存)
+ long userId = currentUser.getId();
+ return this.getById(userId);
+ }
+
+ /**
+ * 是否为管理员
+ *
+ * @param request
+ * @return
+ */
+ @Override
+ public boolean isAdmin(HttpServletRequest request) {
+ // 仅管理员可查询
+ Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
+ User user = (User) userObj;
+ return isAdmin(user);
+ }
+
+ @Override
+ public boolean isAdmin(User user) {
+ return user != null && UserRoleEnum.ADMIN.getValue().equals(user.getUserRole());
+ }
+
+ /**
+ * 用户注销
+ *
+ * @param request
+ */
+ @Override
+ public boolean userLogout(HttpServletRequest request) {
+ if (request.getSession().getAttribute(USER_LOGIN_STATE) == null) {
+ throw new BusinessException(ErrorCode.OPERATION_ERROR, "未登录");
+ }
+ // 移除登录态
+ request.getSession().removeAttribute(USER_LOGIN_STATE);
+ return true;
+ }
+
+ @Override
+ public LoginUserVO getLoginUserVO(User user) {
+ if (user == null) {
+ return null;
+ }
+ LoginUserVO loginUserVO = new LoginUserVO();
+ BeanUtils.copyProperties(user, loginUserVO);
+ return loginUserVO;
+ }
+
+ @Override
+ public UserVO getUserVO(User user) {
+ if (user == null) {
+ return null;
+ }
+ UserVO userVO = new UserVO();
+ BeanUtils.copyProperties(user, userVO);
+ return userVO;
+ }
+
+ @Override
+ public List getUserVO(List userList) {
+ if (CollUtil.isEmpty(userList)) {
+ return new ArrayList<>();
+ }
+ return userList.stream().map(this::getUserVO).collect(Collectors.toList());
+ }
+
+ @Override
+ public QueryWrapper getQueryWrapper(UserQueryRequest userQueryRequest) {
+ if (userQueryRequest == null) {
+ throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空");
+ }
+ Long id = userQueryRequest.getId();
+ String unionId = userQueryRequest.getUnionId();
+ String mpOpenId = userQueryRequest.getMpOpenId();
+ String userName = userQueryRequest.getUserName();
+ String userProfile = userQueryRequest.getUserProfile();
+ String userRole = userQueryRequest.getUserRole();
+ String sortField = userQueryRequest.getSortField();
+ String sortOrder = userQueryRequest.getSortOrder();
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq(id != null, "id", id);
+ queryWrapper.eq(StringUtils.isNotBlank(unionId), "unionId", unionId);
+ queryWrapper.eq(StringUtils.isNotBlank(mpOpenId), "mpOpenId", mpOpenId);
+ queryWrapper.eq(StringUtils.isNotBlank(userRole), "userRole", userRole);
+ queryWrapper.like(StringUtils.isNotBlank(userProfile), "userProfile", userProfile);
+ queryWrapper.like(StringUtils.isNotBlank(userName), "userName", userName);
+ queryWrapper.orderBy(SqlUtils.validSortField(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC),
+ sortField);
+ return queryWrapper;
+ }
+}
diff --git a/src/main/java/com/yupi/springbootinit/utils/ExcelUtils.java b/src/main/java/com/yupi/springbootinit/utils/ExcelUtils.java
new file mode 100644
index 0000000..f2c1ec3
--- /dev/null
+++ b/src/main/java/com/yupi/springbootinit/utils/ExcelUtils.java
@@ -0,0 +1,71 @@
+package com.yupi.springbootinit.utils;
+import cn.hutool.core.collection.CollUtil;
+import com.alibaba.excel.EasyExcel;
+import com.alibaba.excel.support.ExcelTypeEnum;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.util.ResourceUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * Excel 相关工具类
+ */
+@Slf4j
+public class ExcelUtils {
+
+ /**
+ * excel 转 csv
+ *
+ * @param multipartFile
+ * @return
+ */
+ public static String excelToCsv(MultipartFile multipartFile) {
+// File file = null;
+// try {
+// file = ResourceUtils.getFile("classpath:网站数据.xlsx");
+// } catch (FileNotFoundException e) {
+// e.printStackTrace();
+// }
+ // 读取数据
+ List