commit f2229be084e1e7af1e0492f432a1c3c43deabacd Author: brian Date: Tue Jul 11 13:17:30 2023 +0800 项目初始化 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ae748dd --- /dev/null +++ b/.gitignore @@ -0,0 +1,144 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ +### Java template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### Maven template +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +# https://github.com/takari/maven-wrapper#usage-without-binary-jar +.mvn/wrapper/maven-wrapper.jar + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + diff --git a/README.md b/README.md new file mode 100644 index 0000000..b813bb0 --- /dev/null +++ b/README.md @@ -0,0 +1,139 @@ +# AnswerBi + +## 项目技术栈 + +### 主流框架 & 特性 + +- Spring Boot 2.7.x(贼新) +- Spring MVC +- MyBatis + MyBatis Plus 数据访问(开启分页) +- Spring Boot 调试工具和项目处理器 +- Spring AOP 切面编程 +- Spring Scheduler 定时任务 +- Spring 事务注解 + +### 数据存储 + +- MySQL 数据库 +- Redis 内存数据库 +- Elasticsearch 搜索引擎 +- 腾讯云 COS 对象存储 + +### 工具类 + +- Easy Excel 表格处理 +- Hutool 工具库 +- Gson 解析库 +- Apache Commons Lang3 工具类 +- Lombok 注解 + +### 业务特性 + +- Spring Session Redis 分布式登录 +- 全局请求响应拦截器(记录日志) +- 全局异常处理器 +- 自定义错误码 +- 封装通用响应类 +- Swagger + Knife4j 接口文档 +- 自定义权限注解 + 全局校验 +- 全局跨域处理 +- 长整数丢失精度解决 +- 多环境配置 + + +## 业务功能 + +### 架构设计 + +- 合理分层 + + +## 快速上手 + +> 所有需要修改的地方都标记了 `todo`,便于大家找到修改的位置~ + +### MySQL 数据库 + +1)修改 `application.yml` 的数据库配置为你自己的: + +```yml +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/my_db + username: root + password: 123456 +``` + +2)执行 `sql/create_table.sql` 中的数据库语句,自动创建库表 + +3)启动项目,访问 `http://localhost:8101/api/doc.html` 即可打开接口文档,不需要写前端就能在线调试接口了~ + +### Redis 分布式登录 + +1)修改 `application.yml` 的 Redis 配置为你自己的: + +```yml +spring: + redis: + database: 1 + host: localhost + port: 6379 + timeout: 5000 + password: 123456 +``` + +2)修改 `application.yml` 中的 session 存储方式: + +```yml +spring: + session: + store-type: redis +``` + +3)移除 `MainApplication` 类开头 `@SpringBootApplication` 注解内的 exclude 参数: + +修改前: + +```java +@SpringBootApplication(exclude = {RedisAutoConfiguration.class}) +``` + +修改后: + + +```java +@SpringBootApplication +``` + +### Elasticsearch 搜索引擎 + +1)修改 `application.yml` 的 Elasticsearch 配置为你自己的: + +```yml +spring: + elasticsearch: + uris: http://localhost:9200 + username: root + password: 123456 +``` + +2)复制 `sql/post_es_mapping.json` 文件中的内容,通过调用 Elasticsearch 的接口或者 Kibana Dev Tools 来创建索引(相当于数据库建表) + +``` +PUT post_v1 +{ + 参数见 sql/post_es_mapping.json 文件 +} +``` + +这步不会操作的话需要补充下 Elasticsearch 的知识,或者自行百度一下~ + +3)开启同步任务,将数据库的帖子同步到 Elasticsearch + +找到 job 目录下的 `FullSyncPostToEs` 和 `IncSyncPostToEs` 文件,取消掉 `@Component` 注解的注释,再次执行程序即可触发同步: + +```java +// todo 取消注释开启任务 +//@Component +``` \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..5d98b49 --- /dev/null +++ b/pom.xml @@ -0,0 +1,144 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.2 + + + top.peng + answerBi-backend + 0.0.1-SNAPSHOT + answerBi-backend + + 1.8 + + + + org.springframework.boot + spring-boot-starter-freemarker + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-aop + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 2.2.2 + + + + com.baomidou + mybatis-plus-boot-starter + 3.5.2 + + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.springframework.session + spring-session-data-redis + + + + org.springframework.boot + spring-boot-starter-data-elasticsearch + + + + com.github.binarywang + wx-java-mp-spring-boot-starter + 4.4.0 + + + + com.github.xiaoymin + knife4j-spring-boot-starter + 3.0.3 + + + + com.qcloud + cos_api + 5.6.89 + + + + org.apache.commons + commons-lang3 + + + + com.google.code.gson + gson + 2.9.1 + + + + com.alibaba + easyexcel + 3.1.1 + + + + cn.hutool + hutool-all + 5.8.8 + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + mysql + mysql-connector-java + runtime + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + 2.7.2 + + + + org.projectlombok + lombok + + + + + + + + diff --git a/sql/create_table.sql b/sql/create_table.sql new file mode 100644 index 0000000..76a1c3c --- /dev/null +++ b/sql/create_table.sql @@ -0,0 +1,40 @@ +# 建表脚本 +# @author +# @from + +-- 创建库 +create database if not exists answer_bi; + +-- 切换库 +use answer_bi; + +-- 用户表 +create table if not exists user +( + id bigint auto_increment comment 'id' primary key, + created_time datetime default CURRENT_TIMESTAMP not null comment '创建时间', + updated_time datetime null on update CURRENT_TIMESTAMP comment '更新时间', + deleted_flag tinyint default 0 not null comment '是否删除', + user_account varchar(256) not null comment '账号', + user_password varchar(512) not null comment '密码', + username varchar(256) null comment '用户昵称', + user_avatar varchar(1024) null comment '用户头像', + user_role varchar(256) default 'user' not null comment '用户角色:user/admin/ban', + index idx_userAccount (user_account) +) comment '用户' collate = utf8mb4_unicode_ci; + +-- 图表信息表 +create table if not exists chart +( + id bigint auto_increment comment 'id' primary key, + created_time datetime default CURRENT_TIMESTAMP not null comment '创建时间', + updated_time datetime null on update CURRENT_TIMESTAMP comment '更新时间', + deleted_flag tinyint default 0 not null comment '是否删除', + user_id bigint null comment '创建用户 id', + goal text null comment '分析目标', + chart_data text null comment '图表数据', + chart_type varchar(128) null comment '图表类型', + gen_chart text null comment '生成的图表数据', + gen_result text null comment '生成的分析结论', + index idx_userId (user_id) +) comment '图表信息表' collate = utf8mb4_unicode_ci; diff --git a/sql/post_es_mapping.json b/sql/post_es_mapping.json new file mode 100644 index 0000000..655272d --- /dev/null +++ b/sql/post_es_mapping.json @@ -0,0 +1,52 @@ +{ + "aliases": { + "post": {} + }, + "mappings": { + "properties": { + "title": { + "type": "text", + "analyzer": "ik_max_word", + "search_analyzer": "ik_smart", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "content": { + "type": "text", + "analyzer": "ik_max_word", + "search_analyzer": "ik_smart", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "tags": { + "type": "keyword" + }, + "thumbNum": { + "type": "long" + }, + "favourNum": { + "type": "long" + }, + "userId": { + "type": "keyword" + }, + "createTime": { + "type": "date" + }, + "updateTime": { + "type": "date" + }, + "isDelete": { + "type": "keyword" + } + } + } +} \ No newline at end of file diff --git a/src/main/java/top/peng/answerbi/MainApplication.java b/src/main/java/top/peng/answerbi/MainApplication.java new file mode 100644 index 0000000..3ff98a6 --- /dev/null +++ b/src/main/java/top/peng/answerbi/MainApplication.java @@ -0,0 +1,27 @@ +package top.peng.answerbi; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * 主类(项目启动入口) + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +// todo 如需开启 Redis,须移除 exclude 中的内容 +@SpringBootApplication(exclude = {RedisAutoConfiguration.class}) +@MapperScan("top.peng.answerbi.mapper") +@EnableScheduling +@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true) +public class MainApplication { + + public static void main(String[] args) { + SpringApplication.run(MainApplication.class, args); + } + +} diff --git a/src/main/java/top/peng/answerbi/annotation/AuthCheck.java b/src/main/java/top/peng/answerbi/annotation/AuthCheck.java new file mode 100644 index 0000000..6cdc097 --- /dev/null +++ b/src/main/java/top/peng/answerbi/annotation/AuthCheck.java @@ -0,0 +1,26 @@ +package top.peng.answerbi.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 权限校验 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface AuthCheck { + + /** + * 必须有某个角色 + * + * @return + */ + String mustRole() default ""; + +} + diff --git a/src/main/java/top/peng/answerbi/aop/AuthInterceptor.java b/src/main/java/top/peng/answerbi/aop/AuthInterceptor.java new file mode 100644 index 0000000..3adac7b --- /dev/null +++ b/src/main/java/top/peng/answerbi/aop/AuthInterceptor.java @@ -0,0 +1,69 @@ +package top.peng.answerbi.aop; + +import top.peng.answerbi.annotation.AuthCheck; +import top.peng.answerbi.common.ErrorCode; +import top.peng.answerbi.exception.BusinessException; +import top.peng.answerbi.model.entity.User; +import top.peng.answerbi.model.enums.UserRoleEnum; +import top.peng.answerbi.service.UserService; +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * 权限校验 AOP + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@Aspect +@Component +public class AuthInterceptor { + + @Resource + private UserService userService; + + /** + * 执行拦截 + * + * @param joinPoint + * @param authCheck + * @return + */ + @Around("@annotation(authCheck)") + public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable { + String mustRole = authCheck.mustRole(); + RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes(); + HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest(); + // 当前登录用户 + User loginUser = userService.getLoginUser(request); + // 必须有该权限才通过 + if (StringUtils.isNotBlank(mustRole)) { + UserRoleEnum mustUserRoleEnum = UserRoleEnum.getEnumByValue(mustRole); + if (mustUserRoleEnum == null) { + throw new BusinessException(ErrorCode.NO_AUTH_ERROR); + } + String userRole = loginUser.getUserRole(); + // 如果被封号,直接拒绝 + if (UserRoleEnum.BAN.equals(mustUserRoleEnum)) { + throw new BusinessException(ErrorCode.NO_AUTH_ERROR); + } + // 必须有管理员权限 + if (UserRoleEnum.ADMIN.equals(mustUserRoleEnum)) { + if (!mustRole.equals(userRole)) { + throw new BusinessException(ErrorCode.NO_AUTH_ERROR); + } + } + } + // 通过权限校验,放行 + return joinPoint.proceed(); + } +} + diff --git a/src/main/java/top/peng/answerbi/aop/LogInterceptor.java b/src/main/java/top/peng/answerbi/aop/LogInterceptor.java new file mode 100644 index 0000000..28cb34c --- /dev/null +++ b/src/main/java/top/peng/answerbi/aop/LogInterceptor.java @@ -0,0 +1,56 @@ +package top.peng.answerbi.aop; + +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; +import org.springframework.util.StopWatch; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * 请求响应日志 AOP + * + * @author yunpeng + * @version 1.0 2023/5/16 + **/ +@Aspect +@Component +@Slf4j +public class LogInterceptor { + + /** + * 执行拦截 + */ + @Around("execution(* top.peng.answerbi.controller.*.*(..))") + public Object doInterceptor(ProceedingJoinPoint point) throws Throwable { + // 计时 + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + // 获取请求路径 + RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes(); + HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest(); + // 生成请求唯一 id + String requestId = UUID.randomUUID().toString(); + String url = httpServletRequest.getRequestURI(); + // 获取请求参数 + Object[] args = point.getArgs(); + String reqParam = "[" + StringUtils.join(args, ", ") + "]"; + // 输出请求日志 + log.info("request start,id: {}, path: {}, ip: {}, params: {}", requestId, url, + httpServletRequest.getRemoteHost(), reqParam); + // 执行原方法 + Object result = point.proceed(); + // 输出响应日志 + stopWatch.stop(); + long totalTimeMillis = stopWatch.getTotalTimeMillis(); + log.info("request end, id: {}, cost: {}ms", requestId, totalTimeMillis); + return result; + } +} + diff --git a/src/main/java/top/peng/answerbi/common/CommonResponse.java b/src/main/java/top/peng/answerbi/common/CommonResponse.java new file mode 100644 index 0000000..9d7a34c --- /dev/null +++ b/src/main/java/top/peng/answerbi/common/CommonResponse.java @@ -0,0 +1,35 @@ +package top.peng.answerbi.common; + +import java.io.Serializable; +import lombok.Data; + +/** + * 通用返回类 + * + * @param + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@Data +public class CommonResponse implements Serializable { + + private int code; + + private T data; + + private String message; + + public CommonResponse(int code, T data, String message) { + this.code = code; + this.data = data; + this.message = message; + } + + public CommonResponse(int code, T data) { + this(code, data, ""); + } + + public CommonResponse(ErrorCode errorCode) { + this(errorCode.getCode(), null, errorCode.getMessage()); + } +} diff --git a/src/main/java/top/peng/answerbi/common/DeleteRequest.java b/src/main/java/top/peng/answerbi/common/DeleteRequest.java new file mode 100644 index 0000000..4498422 --- /dev/null +++ b/src/main/java/top/peng/answerbi/common/DeleteRequest.java @@ -0,0 +1,21 @@ +package top.peng.answerbi.common; + +import java.io.Serializable; +import lombok.Data; + +/** + * 删除请求 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@Data +public class DeleteRequest implements Serializable { + + /** + * id + */ + private Long id; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/top/peng/answerbi/common/ErrorCode.java b/src/main/java/top/peng/answerbi/common/ErrorCode.java new file mode 100644 index 0000000..67c1618 --- /dev/null +++ b/src/main/java/top/peng/answerbi/common/ErrorCode.java @@ -0,0 +1,43 @@ +package top.peng.answerbi.common; + +/** + * 自定义错误码 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +public enum ErrorCode { + + SUCCESS(0, "ok"), + PARAMS_ERROR(40000, "请求参数错误"), + NOT_LOGIN_ERROR(40100, "未登录"), + NO_AUTH_ERROR(40101, "无权限"), + NOT_FOUND_ERROR(40400, "请求数据不存在"), + FORBIDDEN_ERROR(40300, "禁止访问"), + SYSTEM_ERROR(50000, "系统内部异常"), + OPERATION_ERROR(50001, "操作失败"); + + /** + * 状态码 + */ + private final int code; + + /** + * 信息 + */ + private final String message; + + ErrorCode(int code, String message) { + this.code = code; + this.message = message; + } + + public int getCode() { + return code; + } + + public String getMessage() { + return message; + } + +} diff --git a/src/main/java/top/peng/answerbi/common/PageRequest.java b/src/main/java/top/peng/answerbi/common/PageRequest.java new file mode 100644 index 0000000..a48e6d0 --- /dev/null +++ b/src/main/java/top/peng/answerbi/common/PageRequest.java @@ -0,0 +1,34 @@ +package top.peng.answerbi.common; + +import top.peng.answerbi.constant.CommonConstant; +import lombok.Data; + +/** + * 分页请求 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@Data +public class PageRequest { + + /** + * 当前页号 + */ + private long current = 1; + + /** + * 页面大小 + */ + private long pageSize = 10; + + /** + * 排序字段 + */ + private String sortField; + + /** + * 排序顺序(默认升序) + */ + private String sortOrder = CommonConstant.SORT_ORDER_ASC; +} diff --git a/src/main/java/top/peng/answerbi/common/ResultUtils.java b/src/main/java/top/peng/answerbi/common/ResultUtils.java new file mode 100644 index 0000000..4839d77 --- /dev/null +++ b/src/main/java/top/peng/answerbi/common/ResultUtils.java @@ -0,0 +1,52 @@ +package top.peng.answerbi.common; + +/** + * 返回工具类 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +public class ResultUtils { + + /** + * 成功 + * + * @param data + * @param + * @return + */ + public static CommonResponse success(T data) { + return new CommonResponse<>(0, data, "ok"); + } + + /** + * 失败 + * + * @param errorCode + * @return + */ + public static CommonResponse error(ErrorCode errorCode) { + return new CommonResponse<>(errorCode); + } + + /** + * 失败 + * + * @param code + * @param message + * @return + */ + public static CommonResponse error(int code, String message) { + return new CommonResponse(code, null, message); + } + + /** + * 失败 + * + * @param errorCode + * @return + */ + public static CommonResponse error(ErrorCode errorCode, String message) { + return new CommonResponse(errorCode.getCode(), null, message); + } +} diff --git a/src/main/java/top/peng/answerbi/config/CorsConfig.java b/src/main/java/top/peng/answerbi/config/CorsConfig.java new file mode 100644 index 0000000..5eb8077 --- /dev/null +++ b/src/main/java/top/peng/answerbi/config/CorsConfig.java @@ -0,0 +1,28 @@ +package top.peng.answerbi.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * 全局跨域配置 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@Configuration +public class CorsConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + // 覆盖所有请求 + registry.addMapping("/**") + // 允许发送 Cookie + .allowCredentials(true) + // 放行哪些域名(必须用 patterns,否则 * 会和 allowCredentials 冲突) + .allowedOriginPatterns("*") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .exposedHeaders("*"); + } +} diff --git a/src/main/java/top/peng/answerbi/config/CosClientConfig.java b/src/main/java/top/peng/answerbi/config/CosClientConfig.java new file mode 100644 index 0000000..9afa7dd --- /dev/null +++ b/src/main/java/top/peng/answerbi/config/CosClientConfig.java @@ -0,0 +1,53 @@ +package top.peng.answerbi.config; + +import com.qcloud.cos.COSClient; +import com.qcloud.cos.ClientConfig; +import com.qcloud.cos.auth.BasicCOSCredentials; +import com.qcloud.cos.auth.COSCredentials; +import com.qcloud.cos.region.Region; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 腾讯云对象存储客户端 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@Configuration +@ConfigurationProperties(prefix = "cos.client") +@Data +public class CosClientConfig { + + /** + * accessKey + */ + private String accessKey; + + /** + * secretKey + */ + private String secretKey; + + /** + * 区域 + */ + private String region; + + /** + * 桶名 + */ + private String bucket; + + @Bean + public COSClient cosClient() { + // 初始化用户身份信息(secretId, secretKey) + COSCredentials cred = new BasicCOSCredentials(accessKey, secretKey); + // 设置bucket的区域, COS地域的简称请参照 https://www.qcloud.com/document/product/436/6224 + ClientConfig clientConfig = new ClientConfig(new Region(region)); + // 生成cos客户端 + return new COSClient(cred, clientConfig); + } +} \ No newline at end of file diff --git a/src/main/java/top/peng/answerbi/config/JsonConfig.java b/src/main/java/top/peng/answerbi/config/JsonConfig.java new file mode 100644 index 0000000..e8da3b3 --- /dev/null +++ b/src/main/java/top/peng/answerbi/config/JsonConfig.java @@ -0,0 +1,31 @@ +package top.peng.answerbi.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import org.springframework.boot.jackson.JsonComponent; +import org.springframework.context.annotation.Bean; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; + +/** + * Spring MVC Json 配置 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@JsonComponent +public class JsonConfig { + + /** + * 添加 Long 转 json 精度丢失的配置 + */ + @Bean + public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { + ObjectMapper objectMapper = builder.createXmlMapper(false).build(); + SimpleModule module = new SimpleModule(); + module.addSerializer(Long.class, ToStringSerializer.instance); + module.addSerializer(Long.TYPE, ToStringSerializer.instance); + objectMapper.registerModule(module); + return objectMapper; + } +} \ No newline at end of file diff --git a/src/main/java/top/peng/answerbi/config/Knife4jConfig.java b/src/main/java/top/peng/answerbi/config/Knife4jConfig.java new file mode 100644 index 0000000..cae6dfc --- /dev/null +++ b/src/main/java/top/peng/answerbi/config/Knife4jConfig.java @@ -0,0 +1,39 @@ +package top.peng.answerbi.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +/** + * Knife4j 接口文档配置 + * https://doc.xiaominfo.com/knife4j/documentation/get_start.html + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@Configuration +@EnableSwagger2 +@Profile({"dev", "test"}) +public class Knife4jConfig { + + @Bean + public Docket defaultApi2() { + return new Docket(DocumentationType.SWAGGER_2) + .apiInfo(new ApiInfoBuilder() + .title("接口文档") + .description("answerBi-backend") + .version("1.0") + .build()) + .select() + // 指定 Controller 扫描包路径 + .apis(RequestHandlerSelectors.basePackage("top.peng.answerbi.controller")) + .paths(PathSelectors.any()) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/top/peng/answerbi/config/MyBatisPlusConfig.java b/src/main/java/top/peng/answerbi/config/MyBatisPlusConfig.java new file mode 100644 index 0000000..10a0a14 --- /dev/null +++ b/src/main/java/top/peng/answerbi/config/MyBatisPlusConfig.java @@ -0,0 +1,32 @@ +package top.peng.answerbi.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * MyBatis Plus 配置 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@Configuration +@MapperScan("top.peng.springbootinit.mapper") +public class MyBatisPlusConfig { + + /** + * 拦截器配置 + * + * @return + */ + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + // 分页插件 + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + return interceptor; + } +} \ No newline at end of file diff --git a/src/main/java/top/peng/answerbi/config/WxOpenConfig.java b/src/main/java/top/peng/answerbi/config/WxOpenConfig.java new file mode 100644 index 0000000..33d771a --- /dev/null +++ b/src/main/java/top/peng/answerbi/config/WxOpenConfig.java @@ -0,0 +1,51 @@ +package top.peng.answerbi.config; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; +import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * 微信开放平台配置 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@Slf4j +@Configuration +@ConfigurationProperties(prefix = "wx.open") +@Data +public class WxOpenConfig { + + private String appId; + + private String appSecret; + + private WxMpService wxMpService; + + /** + * 单例模式(不用 @Bean 是为了防止和公众号的 service 冲突) + * + * @return + */ + public WxMpService getWxMpService() { + if (wxMpService != null) { + return wxMpService; + } + synchronized (this) { + if (wxMpService != null) { + return wxMpService; + } + WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl(); + config.setAppId(appId); + config.setSecret(appSecret); + WxMpService service = new WxMpServiceImpl(); + service.setWxMpConfigStorage(config); + wxMpService = service; + return wxMpService; + } + } +} \ No newline at end of file diff --git a/src/main/java/top/peng/answerbi/constant/CommonConstant.java b/src/main/java/top/peng/answerbi/constant/CommonConstant.java new file mode 100644 index 0000000..0b92c09 --- /dev/null +++ b/src/main/java/top/peng/answerbi/constant/CommonConstant.java @@ -0,0 +1,21 @@ +package top.peng.answerbi.constant; + +/** + * 通用常量 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +public interface CommonConstant { + + /** + * 升序 + */ + String SORT_ORDER_ASC = "ascend"; + + /** + * 降序 + */ + String SORT_ORDER_DESC = " descend"; + +} diff --git a/src/main/java/top/peng/answerbi/constant/FileConstant.java b/src/main/java/top/peng/answerbi/constant/FileConstant.java new file mode 100644 index 0000000..ff379a8 --- /dev/null +++ b/src/main/java/top/peng/answerbi/constant/FileConstant.java @@ -0,0 +1,16 @@ +package top.peng.answerbi.constant; + +/** + * 文件常量 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +public interface FileConstant { + + /** + * COS 访问地址 + * todo 需替换配置 + */ + String COS_HOST = "https://pengspace.top"; +} diff --git a/src/main/java/top/peng/answerbi/constant/UserConstant.java b/src/main/java/top/peng/answerbi/constant/UserConstant.java new file mode 100644 index 0000000..50870de --- /dev/null +++ b/src/main/java/top/peng/answerbi/constant/UserConstant.java @@ -0,0 +1,34 @@ +package top.peng.answerbi.constant; + +/** + * 用户常量 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +public interface UserConstant { + + /** + * 用户登录态键 + */ + String USER_LOGIN_STATE = "user_login"; + + // region 权限 + + /** + * 默认角色 + */ + String DEFAULT_ROLE = "user"; + + /** + * 管理员角色 + */ + String ADMIN_ROLE = "admin"; + + /** + * 被封号 + */ + String BAN_ROLE = "ban"; + + // endregion +} diff --git a/src/main/java/top/peng/answerbi/controller/ChartController.java b/src/main/java/top/peng/answerbi/controller/ChartController.java new file mode 100644 index 0000000..935bf4a --- /dev/null +++ b/src/main/java/top/peng/answerbi/controller/ChartController.java @@ -0,0 +1,211 @@ +package top.peng.answerbi.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.google.gson.Gson; +import top.peng.answerbi.annotation.AuthCheck; +import top.peng.answerbi.common.CommonResponse; +import top.peng.answerbi.common.DeleteRequest; +import top.peng.answerbi.common.ErrorCode; +import top.peng.answerbi.common.ResultUtils; +import top.peng.answerbi.constant.UserConstant; +import top.peng.answerbi.exception.BusinessException; +import top.peng.answerbi.exception.ThrowUtils; +import top.peng.answerbi.model.dto.chart.ChartAddRequest; +import top.peng.answerbi.model.dto.chart.ChartEditRequest; +import top.peng.answerbi.model.dto.chart.ChartQueryRequest; +import top.peng.answerbi.model.dto.chart.ChartUpdateRequest; +import top.peng.answerbi.model.entity.Chart; +import top.peng.answerbi.model.entity.User; +import top.peng.answerbi.service.ChartService; +import top.peng.answerbi.service.UserService; +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 图表接口 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@RestController +@RequestMapping("/chart") +@Slf4j +public class ChartController { + + @Resource + private ChartService chartService; + + @Resource + private UserService userService; + + private final static Gson GSON = new Gson(); + + // region 增删改查 + + /** + * 创建 + * + * @param chartAddRequest + * @param request + * @return + */ + @PostMapping("/add") + public CommonResponse addChart(@RequestBody ChartAddRequest chartAddRequest, HttpServletRequest request) { + if (chartAddRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + Chart chart = new Chart(); + BeanUtils.copyProperties(chartAddRequest, chart); + User loginUser = userService.getLoginUser(request); + chart.setUserId(loginUser.getId()); + boolean result = chartService.save(chart); + ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); + long newChartId = chart.getId(); + return ResultUtils.success(newChartId); + } + + /** + * 删除 + * + * @param deleteRequest + * @param request + * @return + */ + @PostMapping("/delete") + public CommonResponse deleteChart(@RequestBody DeleteRequest deleteRequest, HttpServletRequest request) { + if (deleteRequest == null || deleteRequest.getId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User user = userService.getLoginUser(request); + long id = deleteRequest.getId(); + // 判断是否存在 + Chart oldChart = chartService.getById(id); + ThrowUtils.throwIf(oldChart == null, ErrorCode.NOT_FOUND_ERROR); + // 仅本人或管理员可删除 + if (!oldChart.getUserId().equals(user.getId()) && !userService.isAdmin(request)) { + throw new BusinessException(ErrorCode.NO_AUTH_ERROR); + } + boolean b = chartService.removeById(id); + return ResultUtils.success(b); + } + + /** + * 更新(仅管理员) + * + * @param chartUpdateRequest + * @return + */ + @PostMapping("/update") + @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) + public CommonResponse updateChart(@RequestBody ChartUpdateRequest chartUpdateRequest) { + if (chartUpdateRequest == null || chartUpdateRequest.getId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + Chart chart = new Chart(); + BeanUtils.copyProperties(chartUpdateRequest, chart); + long id = chartUpdateRequest.getId(); + // 判断是否存在 + Chart oldChart = chartService.getById(id); + ThrowUtils.throwIf(oldChart == null, ErrorCode.NOT_FOUND_ERROR); + boolean result = chartService.updateById(chart); + return ResultUtils.success(result); + } + + /** + * 根据 id 获取 + * + * @param id + * @return + */ + @GetMapping("/get") + public CommonResponse getChartById(long id, HttpServletRequest request) { + if (id <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + Chart chart = chartService.getById(id); + if (chart == null) { + throw new BusinessException(ErrorCode.NOT_FOUND_ERROR); + } + return ResultUtils.success(chart); + } + + /** + * 分页获取列表(封装类) + * + * @param chartQueryRequest + * @param request + * @return + */ + @PostMapping("/list/page") + public CommonResponse> listChartByPage(@RequestBody ChartQueryRequest chartQueryRequest, + HttpServletRequest request) { + long current = chartQueryRequest.getCurrent(); + long size = chartQueryRequest.getPageSize(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); + Page chartPage = chartService.page(new Page<>(current, size), + chartService.getQueryWrapper(chartQueryRequest)); + return ResultUtils.success(chartPage); + } + + /** + * 分页获取当前用户创建的资源列表 + * + * @param chartQueryRequest + * @param request + * @return + */ + @PostMapping("/my/list/page") + public CommonResponse> listMyChartByPage(@RequestBody ChartQueryRequest chartQueryRequest, + HttpServletRequest request) { + if (chartQueryRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User loginUser = userService.getLoginUser(request); + chartQueryRequest.setUserId(loginUser.getId()); + long current = chartQueryRequest.getCurrent(); + long size = chartQueryRequest.getPageSize(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); + Page chartPage = chartService.page(new Page<>(current, size), + chartService.getQueryWrapper(chartQueryRequest)); + return ResultUtils.success(chartPage); + } + + // endregion + + /** + * 编辑(用户) + * + * @param chartEditRequest + * @param request + * @return + */ + @PostMapping("/edit") + public CommonResponse editChart(@RequestBody ChartEditRequest chartEditRequest, HttpServletRequest request) { + if (chartEditRequest == null || chartEditRequest.getId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + Chart chart = new Chart(); + BeanUtils.copyProperties(chartEditRequest, chart); + User loginUser = userService.getLoginUser(request); + long id = chartEditRequest.getId(); + // 判断是否存在 + Chart oldChart = chartService.getById(id); + ThrowUtils.throwIf(oldChart == null, ErrorCode.NOT_FOUND_ERROR); + // 仅本人或管理员可编辑 + if (!oldChart.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser)) { + throw new BusinessException(ErrorCode.NO_AUTH_ERROR); + } + boolean result = chartService.updateById(chart); + return ResultUtils.success(result); + } + +} diff --git a/src/main/java/top/peng/answerbi/controller/FileController.java b/src/main/java/top/peng/answerbi/controller/FileController.java new file mode 100644 index 0000000..7ed5e64 --- /dev/null +++ b/src/main/java/top/peng/answerbi/controller/FileController.java @@ -0,0 +1,108 @@ +package top.peng.answerbi.controller; + +import cn.hutool.core.io.FileUtil; +import top.peng.answerbi.common.CommonResponse; +import top.peng.answerbi.common.ErrorCode; +import top.peng.answerbi.common.ResultUtils; +import top.peng.answerbi.constant.FileConstant; +import top.peng.answerbi.exception.BusinessException; +import top.peng.answerbi.manager.CosManager; +import top.peng.answerbi.model.dto.file.UploadFileRequest; +import top.peng.answerbi.model.entity.User; +import top.peng.answerbi.model.enums.FileUploadBizEnum; +import top.peng.answerbi.service.UserService; +import java.io.File; +import java.util.Arrays; +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.RandomStringUtils; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +/** + * 文件接口 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@RestController +@RequestMapping("/file") +@Slf4j +public class FileController { + + @Resource + private UserService userService; + + @Resource + private CosManager cosManager; + + /** + * 文件上传 + * + * @param multipartFile + * @param uploadFileRequest + * @param request + * @return + */ + @PostMapping("/upload") + public CommonResponse uploadFile(@RequestPart("file") MultipartFile multipartFile, + UploadFileRequest uploadFileRequest, HttpServletRequest request) { + String biz = uploadFileRequest.getBiz(); + FileUploadBizEnum fileUploadBizEnum = FileUploadBizEnum.getEnumByValue(biz); + if (fileUploadBizEnum == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + validFile(multipartFile, fileUploadBizEnum); + User loginUser = userService.getLoginUser(request); + // 文件目录:根据业务、用户来划分 + String uuid = RandomStringUtils.randomAlphanumeric(8); + String filename = uuid + "-" + multipartFile.getOriginalFilename(); + String filepath = String.format("/%s/%s/%s", fileUploadBizEnum.getValue(), loginUser.getId(), filename); + File file = null; + try { + // 上传文件 + file = File.createTempFile(filepath, null); + multipartFile.transferTo(file); + cosManager.putObject(filepath, file); + // 返回可访问地址 + return ResultUtils.success(FileConstant.COS_HOST + filepath); + } catch (Exception e) { + log.error("file upload error, filepath = " + filepath, e); + throw new BusinessException(ErrorCode.SYSTEM_ERROR, "上传失败"); + } finally { + if (file != null) { + // 删除临时文件 + boolean delete = file.delete(); + if (!delete) { + log.error("file delete error, filepath = {}", filepath); + } + } + } + } + + /** + * 校验文件 + * + * @param multipartFile + * @param fileUploadBizEnum 业务类型 + */ + private void validFile(MultipartFile multipartFile, FileUploadBizEnum fileUploadBizEnum) { + // 文件大小 + long fileSize = multipartFile.getSize(); + // 文件后缀 + String fileSuffix = FileUtil.getSuffix(multipartFile.getOriginalFilename()); + final long ONE_M = 1024 * 1024L; + if (FileUploadBizEnum.USER_AVATAR.equals(fileUploadBizEnum)) { + if (fileSize > ONE_M) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "文件大小不能超过 1M"); + } + if (!Arrays.asList("jpeg", "jpg", "svg", "png", "webp").contains(fileSuffix)) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "文件类型错误"); + } + } + } +} diff --git a/src/main/java/top/peng/answerbi/controller/PostController.java b/src/main/java/top/peng/answerbi/controller/PostController.java new file mode 100644 index 0000000..849bacf --- /dev/null +++ b/src/main/java/top/peng/answerbi/controller/PostController.java @@ -0,0 +1,250 @@ +package top.peng.answerbi.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.google.gson.Gson; +import java.util.List; +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import top.peng.answerbi.annotation.AuthCheck; +import top.peng.answerbi.common.CommonResponse; +import top.peng.answerbi.common.DeleteRequest; +import top.peng.answerbi.common.ErrorCode; +import top.peng.answerbi.common.ResultUtils; +import top.peng.answerbi.constant.UserConstant; +import top.peng.answerbi.exception.BusinessException; +import top.peng.answerbi.exception.ThrowUtils; +import top.peng.answerbi.model.dto.post.PostAddRequest; +import top.peng.answerbi.model.dto.post.PostEditRequest; +import top.peng.answerbi.model.dto.post.PostQueryRequest; +import top.peng.answerbi.model.dto.post.PostUpdateRequest; +import top.peng.answerbi.model.entity.Post; +import top.peng.answerbi.model.entity.User; +import top.peng.answerbi.model.vo.PostVO; +import top.peng.answerbi.service.PostService; +import top.peng.answerbi.service.UserService; + +/** + * 帖子接口 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@RestController +@RequestMapping("/post") +@Slf4j +public class PostController { + + @Resource + private PostService postService; + + @Resource + private UserService userService; + + private final static Gson GSON = new Gson(); + + // region 增删改查 + + /** + * 创建 + * + * @param postAddRequest + * @param request + * @return + */ + @PostMapping("/add") + public CommonResponse addPost(@RequestBody PostAddRequest postAddRequest, HttpServletRequest request) { + if (postAddRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + Post post = new Post(); + BeanUtils.copyProperties(postAddRequest, post); + List tags = postAddRequest.getTags(); + if (tags != null) { + post.setTags(GSON.toJson(tags)); + } + postService.validPost(post, true); + User loginUser = userService.getLoginUser(request); + post.setUserId(loginUser.getId()); + post.setFavourNum(0); + post.setThumbNum(0); + boolean result = postService.save(post); + ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); + long newPostId = post.getId(); + return ResultUtils.success(newPostId); + } + + /** + * 删除 + * + * @param deleteRequest + * @param request + * @return + */ + @PostMapping("/delete") + public CommonResponse deletePost(@RequestBody + DeleteRequest deleteRequest, HttpServletRequest request) { + if (deleteRequest == null || deleteRequest.getId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User user = userService.getLoginUser(request); + long id = deleteRequest.getId(); + // 判断是否存在 + Post oldPost = postService.getById(id); + ThrowUtils.throwIf(oldPost == null, ErrorCode.NOT_FOUND_ERROR); + // 仅本人或管理员可删除 + if (!oldPost.getUserId().equals(user.getId()) && !userService.isAdmin(request)) { + throw new BusinessException(ErrorCode.NO_AUTH_ERROR); + } + boolean b = postService.removeById(id); + return ResultUtils.success(b); + } + + /** + * 更新(仅管理员) + * + * @param postUpdateRequest + * @return + */ + @PostMapping("/update") + @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) + public CommonResponse updatePost(@RequestBody PostUpdateRequest postUpdateRequest) { + if (postUpdateRequest == null || postUpdateRequest.getId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + Post post = new Post(); + BeanUtils.copyProperties(postUpdateRequest, post); + List tags = postUpdateRequest.getTags(); + if (tags != null) { + post.setTags(GSON.toJson(tags)); + } + // 参数校验 + postService.validPost(post, false); + long id = postUpdateRequest.getId(); + // 判断是否存在 + Post oldPost = postService.getById(id); + ThrowUtils.throwIf(oldPost == null, ErrorCode.NOT_FOUND_ERROR); + boolean result = postService.updateById(post); + return ResultUtils.success(result); + } + + /** + * 根据 id 获取 + * + * @param id + * @return + */ + @GetMapping("/get/vo") + public CommonResponse getPostVOById(long id, HttpServletRequest request) { + if (id <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + Post post = postService.getById(id); + if (post == null) { + throw new BusinessException(ErrorCode.NOT_FOUND_ERROR); + } + return ResultUtils.success(postService.getPostVO(post, request)); + } + + /** + * 分页获取列表(封装类) + * + * @param postQueryRequest + * @param request + * @return + */ + @PostMapping("/list/page/vo") + public CommonResponse> listPostVOByPage(@RequestBody PostQueryRequest postQueryRequest, + HttpServletRequest request) { + long current = postQueryRequest.getCurrent(); + long size = postQueryRequest.getPageSize(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); + Page postPage = postService.page(new Page<>(current, size), + postService.getQueryWrapper(postQueryRequest)); + return ResultUtils.success(postService.getPostVOPage(postPage, request)); + } + + /** + * 分页获取当前用户创建的资源列表 + * + * @param postQueryRequest + * @param request + * @return + */ + @PostMapping("/my/list/page/vo") + public CommonResponse> listMyPostVOByPage(@RequestBody PostQueryRequest postQueryRequest, + HttpServletRequest request) { + if (postQueryRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User loginUser = userService.getLoginUser(request); + postQueryRequest.setUserId(loginUser.getId()); + long current = postQueryRequest.getCurrent(); + long size = postQueryRequest.getPageSize(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); + Page postPage = postService.page(new Page<>(current, size), + postService.getQueryWrapper(postQueryRequest)); + return ResultUtils.success(postService.getPostVOPage(postPage, request)); + } + + // endregion + + /** + * 分页搜索(从 ES 查询,封装类) + * + * @param postQueryRequest + * @param request + * @return + */ + @PostMapping("/search/page/vo") + public CommonResponse> searchPostVOByPage(@RequestBody PostQueryRequest postQueryRequest, + HttpServletRequest request) { + long size = postQueryRequest.getPageSize(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); + Page postPage = postService.searchFromEs(postQueryRequest); + return ResultUtils.success(postService.getPostVOPage(postPage, request)); + } + + /** + * 编辑(用户) + * + * @param postEditRequest + * @param request + * @return + */ + @PostMapping("/edit") + public CommonResponse editPost(@RequestBody PostEditRequest postEditRequest, HttpServletRequest request) { + if (postEditRequest == null || postEditRequest.getId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + Post post = new Post(); + BeanUtils.copyProperties(postEditRequest, post); + List tags = postEditRequest.getTags(); + if (tags != null) { + post.setTags(GSON.toJson(tags)); + } + // 参数校验 + postService.validPost(post, false); + User loginUser = userService.getLoginUser(request); + long id = postEditRequest.getId(); + // 判断是否存在 + Post oldPost = postService.getById(id); + ThrowUtils.throwIf(oldPost == null, ErrorCode.NOT_FOUND_ERROR); + // 仅本人或管理员可编辑 + if (!oldPost.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser)) { + throw new BusinessException(ErrorCode.NO_AUTH_ERROR); + } + boolean result = postService.updateById(post); + return ResultUtils.success(result); + } + +} diff --git a/src/main/java/top/peng/answerbi/controller/PostFavourController.java b/src/main/java/top/peng/answerbi/controller/PostFavourController.java new file mode 100644 index 0000000..98016d9 --- /dev/null +++ b/src/main/java/top/peng/answerbi/controller/PostFavourController.java @@ -0,0 +1,109 @@ +package top.peng.answerbi.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import top.peng.answerbi.common.CommonResponse; +import top.peng.answerbi.common.ErrorCode; +import top.peng.answerbi.common.ResultUtils; +import top.peng.answerbi.exception.BusinessException; +import top.peng.answerbi.exception.ThrowUtils; +import top.peng.answerbi.model.dto.post.PostQueryRequest; +import top.peng.answerbi.model.dto.postfavour.PostFavourAddRequest; +import top.peng.answerbi.model.dto.postfavour.PostFavourQueryRequest; +import top.peng.answerbi.model.entity.Post; +import top.peng.answerbi.model.entity.User; +import top.peng.answerbi.model.vo.PostVO; +import top.peng.answerbi.service.PostFavourService; +import top.peng.answerbi.service.PostService; +import top.peng.answerbi.service.UserService; +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 帖子收藏接口 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@RestController +@RequestMapping("/post_favour") +@Slf4j +public class PostFavourController { + + @Resource + private PostFavourService postFavourService; + + @Resource + private PostService postService; + + @Resource + private UserService userService; + + /** + * 收藏 / 取消收藏 + * + * @param postFavourAddRequest + * @param request + * @return resultNum 收藏变化数 + */ + @PostMapping("/") + public CommonResponse doPostFavour(@RequestBody PostFavourAddRequest postFavourAddRequest, + HttpServletRequest request) { + if (postFavourAddRequest == null || postFavourAddRequest.getPostId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + // 登录才能操作 + final User loginUser = userService.getLoginUser(request); + long postId = postFavourAddRequest.getPostId(); + int result = postFavourService.doPostFavour(postId, loginUser); + return ResultUtils.success(result); + } + + /** + * 获取我收藏的帖子列表 + * + * @param postQueryRequest + * @param request + */ + @PostMapping("/my/list/page") + public CommonResponse> listMyFavourPostByPage(@RequestBody PostQueryRequest postQueryRequest, + HttpServletRequest request) { + if (postQueryRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User loginUser = userService.getLoginUser(request); + long current = postQueryRequest.getCurrent(); + long size = postQueryRequest.getPageSize(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); + Page postPage = postFavourService.listFavourPostByPage(new Page<>(current, size), + postService.getQueryWrapper(postQueryRequest), loginUser.getId()); + return ResultUtils.success(postService.getPostVOPage(postPage, request)); + } + + /** + * 获取用户收藏的帖子列表 + * + * @param postFavourQueryRequest + * @param request + */ + @PostMapping("/list/page") + public CommonResponse> listFavourPostByPage(@RequestBody PostFavourQueryRequest postFavourQueryRequest, + HttpServletRequest request) { + if (postFavourQueryRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + long current = postFavourQueryRequest.getCurrent(); + long size = postFavourQueryRequest.getPageSize(); + Long userId = postFavourQueryRequest.getUserId(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20 || userId == null, ErrorCode.PARAMS_ERROR); + Page postPage = postFavourService.listFavourPostByPage(new Page<>(current, size), + postService.getQueryWrapper(postFavourQueryRequest.getPostQueryRequest()), userId); + return ResultUtils.success(postService.getPostVOPage(postPage, request)); + } +} diff --git a/src/main/java/top/peng/answerbi/controller/PostThumbController.java b/src/main/java/top/peng/answerbi/controller/PostThumbController.java new file mode 100644 index 0000000..9db4b14 --- /dev/null +++ b/src/main/java/top/peng/answerbi/controller/PostThumbController.java @@ -0,0 +1,56 @@ +package top.peng.answerbi.controller; + +import top.peng.answerbi.common.CommonResponse; +import top.peng.answerbi.common.ErrorCode; +import top.peng.answerbi.common.ResultUtils; +import top.peng.answerbi.exception.BusinessException; +import top.peng.answerbi.model.dto.postthumb.PostThumbAddRequest; +import top.peng.answerbi.model.entity.User; +import top.peng.answerbi.service.PostThumbService; +import top.peng.answerbi.service.UserService; +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 帖子点赞接口 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@RestController +@RequestMapping("/post_thumb") +@Slf4j +public class PostThumbController { + + @Resource + private PostThumbService postThumbService; + + @Resource + private UserService userService; + + /** + * 点赞 / 取消点赞 + * + * @param postThumbAddRequest + * @param request + * @return resultNum 本次点赞变化数 + */ + @PostMapping("/") + public CommonResponse doThumb(@RequestBody PostThumbAddRequest postThumbAddRequest, + HttpServletRequest request) { + if (postThumbAddRequest == null || postThumbAddRequest.getPostId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + // 登录才能点赞 + final User loginUser = userService.getLoginUser(request); + long postId = postThumbAddRequest.getPostId(); + int result = postThumbService.doPostThumb(postId, loginUser); + return ResultUtils.success(result); + } + +} diff --git a/src/main/java/top/peng/answerbi/controller/UserController.java b/src/main/java/top/peng/answerbi/controller/UserController.java new file mode 100644 index 0000000..48647af --- /dev/null +++ b/src/main/java/top/peng/answerbi/controller/UserController.java @@ -0,0 +1,280 @@ +package top.peng.answerbi.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import top.peng.answerbi.annotation.AuthCheck; +import top.peng.answerbi.common.CommonResponse; +import top.peng.answerbi.common.DeleteRequest; +import top.peng.answerbi.common.ErrorCode; +import top.peng.answerbi.common.ResultUtils; +import top.peng.answerbi.constant.UserConstant; +import top.peng.answerbi.exception.BusinessException; +import top.peng.answerbi.exception.ThrowUtils; +import top.peng.answerbi.model.dto.user.UserAddRequest; +import top.peng.answerbi.model.dto.user.UserLoginRequest; +import top.peng.answerbi.model.dto.user.UserQueryRequest; +import top.peng.answerbi.model.dto.user.UserRegisterRequest; +import top.peng.answerbi.model.dto.user.UserUpdateMyRequest; +import top.peng.answerbi.model.dto.user.UserUpdateRequest; +import top.peng.answerbi.model.entity.User; +import top.peng.answerbi.model.vo.LoginUserVO; +import top.peng.answerbi.model.vo.UserVO; +import top.peng.answerbi.service.UserService; +import java.util.List; +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 用户接口 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@RestController +@RequestMapping("/user") +@Slf4j +public class UserController { + + @Resource + private UserService userService; + + + // region 登录相关 + + /** + * 用户注册 + * + * @param userRegisterRequest + * @return + */ + @PostMapping("/register") + public CommonResponse userRegister(@RequestBody UserRegisterRequest userRegisterRequest) { + if (userRegisterRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + String userAccount = userRegisterRequest.getUserAccount(); + String userPassword = userRegisterRequest.getUserPassword(); + String checkPassword = userRegisterRequest.getCheckPassword(); + if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword)) { + return null; + } + long result = userService.userRegister(userAccount, userPassword, checkPassword); + return ResultUtils.success(result); + } + + /** + * 用户登录 + * + * @param userLoginRequest + * @param request + * @return + */ + @PostMapping("/login") + public CommonResponse userLogin(@RequestBody UserLoginRequest userLoginRequest, HttpServletRequest request) { + if (userLoginRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + String userAccount = userLoginRequest.getUserAccount(); + String userPassword = userLoginRequest.getUserPassword(); + if (StringUtils.isAnyBlank(userAccount, userPassword)) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + LoginUserVO loginUserVO = userService.userLogin(userAccount, userPassword, request); + return ResultUtils.success(loginUserVO); + } + + /** + * 用户注销 + * + * @param request + * @return + */ + @PostMapping("/logout") + public CommonResponse userLogout(HttpServletRequest request) { + if (request == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + boolean result = userService.userLogout(request); + return ResultUtils.success(result); + } + + /** + * 获取当前登录用户 + * + * @param request + * @return + */ + @GetMapping("/get/login") + public CommonResponse getLoginUser(HttpServletRequest request) { + User user = userService.getLoginUser(request); + return ResultUtils.success(userService.getLoginUserVO(user)); + } + + // endregion + + // region 增删改查 + + /** + * 创建用户 + * + * @param userAddRequest + * @param request + * @return + */ + @PostMapping("/add") + @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) + public CommonResponse addUser(@RequestBody UserAddRequest userAddRequest, HttpServletRequest request) { + if (userAddRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User user = new User(); + BeanUtils.copyProperties(userAddRequest, user); + boolean result = userService.save(user); + ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); + return ResultUtils.success(user.getId()); + } + + /** + * 删除用户 + * + * @param deleteRequest + * @param request + * @return + */ + @PostMapping("/delete") + @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) + public CommonResponse deleteUser(@RequestBody DeleteRequest deleteRequest, HttpServletRequest request) { + if (deleteRequest == null || deleteRequest.getId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + boolean b = userService.removeById(deleteRequest.getId()); + return ResultUtils.success(b); + } + + /** + * 更新用户 + * + * @param userUpdateRequest + * @param request + * @return + */ + @PostMapping("/update") + @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) + public CommonResponse updateUser(@RequestBody UserUpdateRequest userUpdateRequest, + HttpServletRequest request) { + if (userUpdateRequest == null || userUpdateRequest.getId() == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User user = new User(); + BeanUtils.copyProperties(userUpdateRequest, user); + boolean result = userService.updateById(user); + ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); + return ResultUtils.success(true); + } + + /** + * 根据 id 获取用户(仅管理员) + * + * @param id + * @param request + * @return + */ + @GetMapping("/get") + @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) + public CommonResponse getUserById(long id, HttpServletRequest request) { + if (id <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User user = userService.getById(id); + ThrowUtils.throwIf(user == null, ErrorCode.NOT_FOUND_ERROR); + return ResultUtils.success(user); + } + + /** + * 根据 id 获取包装类 + * + * @param id + * @param request + * @return + */ + @GetMapping("/get/vo") + public CommonResponse getUserVOById(long id, HttpServletRequest request) { + CommonResponse response = getUserById(id, request); + User user = response.getData(); + return ResultUtils.success(userService.getUserVO(user)); + } + + /** + * 分页获取用户列表(仅管理员) + * + * @param userQueryRequest + * @param request + * @return + */ + @PostMapping("/list/page") + @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) + public CommonResponse> listUserByPage(@RequestBody UserQueryRequest userQueryRequest, + HttpServletRequest request) { + long current = userQueryRequest.getCurrent(); + long size = userQueryRequest.getPageSize(); + Page userPage = userService.page(new Page<>(current, size), + userService.getQueryWrapper(userQueryRequest)); + return ResultUtils.success(userPage); + } + + /** + * 分页获取用户封装列表 + * + * @param userQueryRequest + * @param request + * @return + */ + @PostMapping("/list/page/vo") + public CommonResponse> listUserVOByPage(@RequestBody UserQueryRequest userQueryRequest, + HttpServletRequest request) { + if (userQueryRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + long current = userQueryRequest.getCurrent(); + long size = userQueryRequest.getPageSize(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); + Page userPage = userService.page(new Page<>(current, size), + userService.getQueryWrapper(userQueryRequest)); + Page userVOPage = new Page<>(current, size, userPage.getTotal()); + List userVO = userService.getUserVO(userPage.getRecords()); + userVOPage.setRecords(userVO); + return ResultUtils.success(userVOPage); + } + + // endregion + + /** + * 更新个人信息 + * + * @param userUpdateMyRequest + * @param request + * @return + */ + @PostMapping("/update/my") + public CommonResponse updateMyUser(@RequestBody UserUpdateMyRequest userUpdateMyRequest, + HttpServletRequest request) { + if (userUpdateMyRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User loginUser = userService.getLoginUser(request); + User user = new User(); + BeanUtils.copyProperties(userUpdateMyRequest, user); + user.setId(loginUser.getId()); + boolean result = userService.updateById(user); + ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); + return ResultUtils.success(true); + } +} diff --git a/src/main/java/top/peng/answerbi/esdao/PostEsDao.java b/src/main/java/top/peng/answerbi/esdao/PostEsDao.java new file mode 100644 index 0000000..ceff698 --- /dev/null +++ b/src/main/java/top/peng/answerbi/esdao/PostEsDao.java @@ -0,0 +1,16 @@ +package top.peng.answerbi.esdao; + +import top.peng.answerbi.model.dto.post.PostEsDTO; +import java.util.List; +import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; + +/** + * 帖子 ES 操作 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +public interface PostEsDao extends ElasticsearchRepository { + + List findByUserId(Long userId); +} \ No newline at end of file diff --git a/src/main/java/top/peng/answerbi/exception/BusinessException.java b/src/main/java/top/peng/answerbi/exception/BusinessException.java new file mode 100644 index 0000000..456bf1f --- /dev/null +++ b/src/main/java/top/peng/answerbi/exception/BusinessException.java @@ -0,0 +1,36 @@ +package top.peng.answerbi.exception; + +import top.peng.answerbi.common.ErrorCode; + +/** + * 自定义异常类 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +public class BusinessException extends RuntimeException { + + /** + * 错误码 + */ + private final int code; + + public BusinessException(int code, String message) { + super(message); + this.code = code; + } + + public BusinessException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.code = errorCode.getCode(); + } + + public BusinessException(ErrorCode errorCode, String message) { + super(message); + this.code = errorCode.getCode(); + } + + public int getCode() { + return code; + } +} diff --git a/src/main/java/top/peng/answerbi/exception/GlobalExceptionHandler.java b/src/main/java/top/peng/answerbi/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..d777e8a --- /dev/null +++ b/src/main/java/top/peng/answerbi/exception/GlobalExceptionHandler.java @@ -0,0 +1,31 @@ +package top.peng.answerbi.exception; + +import top.peng.answerbi.common.CommonResponse; +import top.peng.answerbi.common.ErrorCode; +import top.peng.answerbi.common.ResultUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * 全局异常处理器 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@RestControllerAdvice +@Slf4j +public class GlobalExceptionHandler { + + @ExceptionHandler(BusinessException.class) + public CommonResponse businessExceptionHandler(BusinessException e) { + log.error("BusinessException", e); + return ResultUtils.error(e.getCode(), e.getMessage()); + } + + @ExceptionHandler(RuntimeException.class) + public CommonResponse runtimeExceptionHandler(RuntimeException e) { + log.error("RuntimeException", e); + return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "系统错误"); + } +} diff --git a/src/main/java/top/peng/answerbi/exception/ThrowUtils.java b/src/main/java/top/peng/answerbi/exception/ThrowUtils.java new file mode 100644 index 0000000..c78343e --- /dev/null +++ b/src/main/java/top/peng/answerbi/exception/ThrowUtils.java @@ -0,0 +1,45 @@ +package top.peng.answerbi.exception; + +import top.peng.answerbi.common.ErrorCode; + +/** + * 抛异常工具类 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +public class ThrowUtils { + + /** + * 条件成立则抛异常 + * + * @param condition + * @param runtimeException + */ + public static void throwIf(boolean condition, RuntimeException runtimeException) { + if (condition) { + throw runtimeException; + } + } + + /** + * 条件成立则抛异常 + * + * @param condition + * @param errorCode + */ + public static void throwIf(boolean condition, ErrorCode errorCode) { + throwIf(condition, new BusinessException(errorCode)); + } + + /** + * 条件成立则抛异常 + * + * @param condition + * @param errorCode + * @param message + */ + public static void throwIf(boolean condition, ErrorCode errorCode, String message) { + throwIf(condition, new BusinessException(errorCode, message)); + } +} diff --git a/src/main/java/top/peng/answerbi/job/cycle/IncSyncPostToEs.java b/src/main/java/top/peng/answerbi/job/cycle/IncSyncPostToEs.java new file mode 100644 index 0000000..9edbb10 --- /dev/null +++ b/src/main/java/top/peng/answerbi/job/cycle/IncSyncPostToEs.java @@ -0,0 +1,57 @@ +package top.peng.answerbi.job.cycle; + +import top.peng.answerbi.esdao.PostEsDao; +import top.peng.answerbi.mapper.PostMapper; +import top.peng.answerbi.model.dto.post.PostEsDTO; +import top.peng.answerbi.model.entity.Post; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; +import javax.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.scheduling.annotation.Scheduled; + +/** + * 增量同步帖子到 es + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +// todo 取消注释开启任务 +//@Component +@Slf4j +public class IncSyncPostToEs { + + @Resource + private PostMapper postMapper; + + @Resource + private PostEsDao postEsDao; + + /** + * 每分钟执行一次 + */ + @Scheduled(fixedRate = 60 * 1000) + public void run() { + // 查询近 5 分钟内的数据 + Date fiveMinutesAgoDate = new Date(new Date().getTime() - 5 * 60 * 1000L); + List postList = postMapper.listPostWithDelete(fiveMinutesAgoDate); + if (CollectionUtils.isEmpty(postList)) { + log.info("no inc post"); + return; + } + List postEsDTOList = postList.stream() + .map(PostEsDTO::objToDto) + .collect(Collectors.toList()); + final int pageSize = 500; + int total = postEsDTOList.size(); + log.info("IncSyncPostToEs 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("IncSyncPostToEs end, total {}", total); + } +} diff --git a/src/main/java/top/peng/answerbi/job/once/FullSyncPostToEs.java b/src/main/java/top/peng/answerbi/job/once/FullSyncPostToEs.java new file mode 100644 index 0000000..8665b1a --- /dev/null +++ b/src/main/java/top/peng/answerbi/job/once/FullSyncPostToEs.java @@ -0,0 +1,48 @@ +package top.peng.answerbi.job.once; + +import top.peng.answerbi.esdao.PostEsDao; +import top.peng.answerbi.model.dto.post.PostEsDTO; +import top.peng.answerbi.model.entity.Post; +import top.peng.answerbi.service.PostService; +import java.util.List; +import java.util.stream.Collectors; +import javax.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.boot.CommandLineRunner; + +/** + * 全量同步帖子到 es + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +// todo 取消注释开启任务 +//@Component +@Slf4j +public class FullSyncPostToEs implements CommandLineRunner { + + @Resource + private PostService postService; + + @Resource + private PostEsDao postEsDao; + + @Override + public void run(String... args) { + List postList = postService.list(); + if (CollectionUtils.isEmpty(postList)) { + return; + } + List 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/top/peng/answerbi/manager/CosManager.java b/src/main/java/top/peng/answerbi/manager/CosManager.java new file mode 100644 index 0000000..f13895b --- /dev/null +++ b/src/main/java/top/peng/answerbi/manager/CosManager.java @@ -0,0 +1,51 @@ +package top.peng.answerbi.manager; + +import com.qcloud.cos.COSClient; +import com.qcloud.cos.model.PutObjectRequest; +import com.qcloud.cos.model.PutObjectResult; +import top.peng.answerbi.config.CosClientConfig; +import java.io.File; +import javax.annotation.Resource; +import org.springframework.stereotype.Component; + +/** + * Cos 对象存储操作 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@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/top/peng/answerbi/mapper/ChartMapper.java b/src/main/java/top/peng/answerbi/mapper/ChartMapper.java new file mode 100644 index 0000000..4bdeb3a --- /dev/null +++ b/src/main/java/top/peng/answerbi/mapper/ChartMapper.java @@ -0,0 +1,18 @@ +package top.peng.answerbi.mapper; + +import top.peng.answerbi.model.entity.Chart; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** +* @author yunpeng.zhang +* @description 针对表【chart(图表信息表)】的数据库操作Mapper +* @createDate 2023-07-10 16:45:42 +* @Entity top.peng.springbootinit.model.entity.Chart +*/ +public interface ChartMapper extends BaseMapper { + +} + + + + diff --git a/src/main/java/top/peng/answerbi/mapper/PostFavourMapper.java b/src/main/java/top/peng/answerbi/mapper/PostFavourMapper.java new file mode 100644 index 0000000..6cceba5 --- /dev/null +++ b/src/main/java/top/peng/answerbi/mapper/PostFavourMapper.java @@ -0,0 +1,35 @@ +package top.peng.answerbi.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 top.peng.answerbi.model.entity.Post; +import top.peng.answerbi.model.entity.PostFavour; +import org.apache.ibatis.annotations.Param; + +/** + * 帖子收藏数据库操作 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +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/top/peng/answerbi/mapper/PostMapper.java b/src/main/java/top/peng/answerbi/mapper/PostMapper.java new file mode 100644 index 0000000..39ebb70 --- /dev/null +++ b/src/main/java/top/peng/answerbi/mapper/PostMapper.java @@ -0,0 +1,25 @@ +package top.peng.answerbi.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import top.peng.answerbi.model.entity.Post; +import java.util.Date; +import java.util.List; + +/** + * 帖子数据库操作 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +public interface PostMapper extends BaseMapper { + + /** + * 查询帖子列表(包括已被删除的数据) + */ + List listPostWithDelete(Date minUpdateTime); + +} + + + + diff --git a/src/main/java/top/peng/answerbi/mapper/PostThumbMapper.java b/src/main/java/top/peng/answerbi/mapper/PostThumbMapper.java new file mode 100644 index 0000000..3b6eff6 --- /dev/null +++ b/src/main/java/top/peng/answerbi/mapper/PostThumbMapper.java @@ -0,0 +1,18 @@ +package top.peng.answerbi.mapper; + +import top.peng.answerbi.model.entity.PostThumb; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 帖子点赞数据库操作 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +public interface PostThumbMapper extends BaseMapper { + +} + + + + diff --git a/src/main/java/top/peng/answerbi/mapper/UserMapper.java b/src/main/java/top/peng/answerbi/mapper/UserMapper.java new file mode 100644 index 0000000..6794fce --- /dev/null +++ b/src/main/java/top/peng/answerbi/mapper/UserMapper.java @@ -0,0 +1,18 @@ +package top.peng.answerbi.mapper; + +import top.peng.answerbi.model.entity.User; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** +* @author yunpeng.zhang +* @description 针对表【user(用户)】的数据库操作Mapper +* @createDate 2023-07-10 16:45:42 +* @Entity top.peng.springbootinit.model.entity.User +*/ +public interface UserMapper extends BaseMapper { + +} + + + + diff --git a/src/main/java/top/peng/answerbi/model/dto/chart/ChartAddRequest.java b/src/main/java/top/peng/answerbi/model/dto/chart/ChartAddRequest.java new file mode 100644 index 0000000..2e54b84 --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/dto/chart/ChartAddRequest.java @@ -0,0 +1,36 @@ +package top.peng.answerbi.model.dto.chart; + +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 java.io.Serializable; +import java.util.Date; +import java.util.List; +import lombok.Data; + +/** + * 创建请求 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@Data +public class ChartAddRequest implements Serializable { + /** + * 分析目标 + */ + 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/top/peng/answerbi/model/dto/chart/ChartEditRequest.java b/src/main/java/top/peng/answerbi/model/dto/chart/ChartEditRequest.java new file mode 100644 index 0000000..67a004f --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/dto/chart/ChartEditRequest.java @@ -0,0 +1,37 @@ +package top.peng.answerbi.model.dto.chart; + +import java.io.Serializable; +import java.util.List; +import lombok.Data; + +/** + * 编辑请求 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@Data +public class ChartEditRequest implements Serializable { + + /** + * 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/top/peng/answerbi/model/dto/chart/ChartQueryRequest.java b/src/main/java/top/peng/answerbi/model/dto/chart/ChartQueryRequest.java new file mode 100644 index 0000000..0a1c7e8 --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/dto/chart/ChartQueryRequest.java @@ -0,0 +1,41 @@ +package top.peng.answerbi.model.dto.chart; + +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import top.peng.answerbi.common.PageRequest; + +/** + * 查询请求 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class ChartQueryRequest extends PageRequest implements Serializable { + + /** + * id + */ + private Long id; + + /** + * 分析目标 + */ + 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/top/peng/answerbi/model/dto/chart/ChartUpdateRequest.java b/src/main/java/top/peng/answerbi/model/dto/chart/ChartUpdateRequest.java new file mode 100644 index 0000000..c376220 --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/dto/chart/ChartUpdateRequest.java @@ -0,0 +1,70 @@ +package top.peng.answerbi.model.dto.chart; + +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 java.io.Serializable; +import java.util.Date; +import java.util.List; +import lombok.Data; + +/** + * 更新请求 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@Data +public class ChartUpdateRequest implements Serializable { + + /** + * id + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 创建时间 + */ + private Date createdTime; + + /** + * 更新时间 + */ + private Date updatedTime; + + /** + * 是否删除 + */ + @TableLogic + private Integer deletedFlag; + + + /** + * 分析目标 + */ + private String goal; + + /** + * 图表数据 + */ + private String chartData; + + /** + * 图表类型 + */ + private String chartType; + + /** + * 生成的图表数据 + */ + private String genChart; + + /** + * 生成的分析结论 + */ + private String genResult; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/top/peng/answerbi/model/dto/file/UploadFileRequest.java b/src/main/java/top/peng/answerbi/model/dto/file/UploadFileRequest.java new file mode 100644 index 0000000..df41512 --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/dto/file/UploadFileRequest.java @@ -0,0 +1,21 @@ +package top.peng.answerbi.model.dto.file; + +import java.io.Serializable; +import lombok.Data; + +/** + * 文件上传请求 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@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/top/peng/answerbi/model/dto/post/PostAddRequest.java b/src/main/java/top/peng/answerbi/model/dto/post/PostAddRequest.java new file mode 100644 index 0000000..353c4e3 --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/dto/post/PostAddRequest.java @@ -0,0 +1,32 @@ +package top.peng.answerbi.model.dto.post; + +import java.io.Serializable; +import java.util.List; +import lombok.Data; + +/** + * 创建请求 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@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/top/peng/answerbi/model/dto/post/PostEditRequest.java b/src/main/java/top/peng/answerbi/model/dto/post/PostEditRequest.java new file mode 100644 index 0000000..271aef0 --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/dto/post/PostEditRequest.java @@ -0,0 +1,37 @@ +package top.peng.answerbi.model.dto.post; + +import java.io.Serializable; +import java.util.List; +import lombok.Data; + +/** + * 编辑请求 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@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/top/peng/answerbi/model/dto/post/PostEsDTO.java b/src/main/java/top/peng/answerbi/model/dto/post/PostEsDTO.java new file mode 100644 index 0000000..cad036d --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/dto/post/PostEsDTO.java @@ -0,0 +1,125 @@ +package top.peng.answerbi.model.dto.post; + +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; +import top.peng.answerbi.model.entity.Post; +import java.io.Serializable; +import java.util.Date; +import java.util.List; +import lombok.Data; +import org.apache.commons.collections4.CollectionUtils; +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; + +/** + * 帖子 ES 包装类 + * + * @author yunpeng + * @version 1.0 2023/5/16 + **/ +// 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; + + private static final Gson GSON = new Gson(); + + /** + * 对象转包装类 + * + * @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(GSON.fromJson(tagsStr, new TypeToken>() { + }.getType())); + } + 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 (CollectionUtils.isNotEmpty(tagList)) { + post.setTags(GSON.toJson(tagList)); + } + return post; + } +} diff --git a/src/main/java/top/peng/answerbi/model/dto/post/PostQueryRequest.java b/src/main/java/top/peng/answerbi/model/dto/post/PostQueryRequest.java new file mode 100644 index 0000000..54d6921 --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/dto/post/PostQueryRequest.java @@ -0,0 +1,65 @@ +package top.peng.answerbi.model.dto.post; + +import top.peng.answerbi.common.PageRequest; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 查询请求 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@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/top/peng/answerbi/model/dto/post/PostUpdateRequest.java b/src/main/java/top/peng/answerbi/model/dto/post/PostUpdateRequest.java new file mode 100644 index 0000000..9ae482e --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/dto/post/PostUpdateRequest.java @@ -0,0 +1,37 @@ +package top.peng.answerbi.model.dto.post; + +import java.io.Serializable; +import java.util.List; +import lombok.Data; + +/** + * 更新请求 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@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/top/peng/answerbi/model/dto/postfavour/PostFavourAddRequest.java b/src/main/java/top/peng/answerbi/model/dto/postfavour/PostFavourAddRequest.java new file mode 100644 index 0000000..2ad0eff --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/dto/postfavour/PostFavourAddRequest.java @@ -0,0 +1,21 @@ +package top.peng.answerbi.model.dto.postfavour; + +import java.io.Serializable; +import lombok.Data; + +/** + * 帖子收藏 / 取消收藏请求 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@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/top/peng/answerbi/model/dto/postfavour/PostFavourQueryRequest.java b/src/main/java/top/peng/answerbi/model/dto/postfavour/PostFavourQueryRequest.java new file mode 100644 index 0000000..74494ac --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/dto/postfavour/PostFavourQueryRequest.java @@ -0,0 +1,30 @@ +package top.peng.answerbi.model.dto.postfavour; + +import top.peng.answerbi.common.PageRequest; +import top.peng.answerbi.model.dto.post.PostQueryRequest; +import java.io.Serializable; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 帖子收藏查询请求 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@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/top/peng/answerbi/model/dto/postthumb/PostThumbAddRequest.java b/src/main/java/top/peng/answerbi/model/dto/postthumb/PostThumbAddRequest.java new file mode 100644 index 0000000..060b3f5 --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/dto/postthumb/PostThumbAddRequest.java @@ -0,0 +1,21 @@ +package top.peng.answerbi.model.dto.postthumb; + +import java.io.Serializable; +import lombok.Data; + +/** + * 帖子点赞请求 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@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/top/peng/answerbi/model/dto/user/UserAddRequest.java b/src/main/java/top/peng/answerbi/model/dto/user/UserAddRequest.java new file mode 100644 index 0000000..2dc4100 --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/dto/user/UserAddRequest.java @@ -0,0 +1,36 @@ +package top.peng.answerbi.model.dto.user; + +import java.io.Serializable; +import lombok.Data; + +/** + * 用户创建请求 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@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/top/peng/answerbi/model/dto/user/UserLoginRequest.java b/src/main/java/top/peng/answerbi/model/dto/user/UserLoginRequest.java new file mode 100644 index 0000000..e4679da --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/dto/user/UserLoginRequest.java @@ -0,0 +1,20 @@ +package top.peng.answerbi.model.dto.user; + +import java.io.Serializable; +import lombok.Data; + +/** + * 用户登录请求 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@Data +public class UserLoginRequest implements Serializable { + + private static final long serialVersionUID = 3191241716373120793L; + + private String userAccount; + + private String userPassword; +} diff --git a/src/main/java/top/peng/answerbi/model/dto/user/UserQueryRequest.java b/src/main/java/top/peng/answerbi/model/dto/user/UserQueryRequest.java new file mode 100644 index 0000000..722a2c8 --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/dto/user/UserQueryRequest.java @@ -0,0 +1,48 @@ +package top.peng.answerbi.model.dto.user; + +import top.peng.answerbi.common.PageRequest; +import java.io.Serializable; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 用户查询请求 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@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/top/peng/answerbi/model/dto/user/UserRegisterRequest.java b/src/main/java/top/peng/answerbi/model/dto/user/UserRegisterRequest.java new file mode 100644 index 0000000..aa59429 --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/dto/user/UserRegisterRequest.java @@ -0,0 +1,22 @@ +package top.peng.answerbi.model.dto.user; + +import java.io.Serializable; +import lombok.Data; + +/** + * 用户注册请求体 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@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/top/peng/answerbi/model/dto/user/UserUpdateMyRequest.java b/src/main/java/top/peng/answerbi/model/dto/user/UserUpdateMyRequest.java new file mode 100644 index 0000000..c72b6f5 --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/dto/user/UserUpdateMyRequest.java @@ -0,0 +1,31 @@ +package top.peng.answerbi.model.dto.user; + +import java.io.Serializable; +import lombok.Data; + +/** + * 用户更新个人信息请求 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@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/top/peng/answerbi/model/dto/user/UserUpdateRequest.java b/src/main/java/top/peng/answerbi/model/dto/user/UserUpdateRequest.java new file mode 100644 index 0000000..8445fa7 --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/dto/user/UserUpdateRequest.java @@ -0,0 +1,40 @@ +package top.peng.answerbi.model.dto.user; + +import java.io.Serializable; +import lombok.Data; + +/** + * 用户更新请求 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@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/top/peng/answerbi/model/entity/Chart.java b/src/main/java/top/peng/answerbi/model/entity/Chart.java new file mode 100644 index 0000000..0d1e82b --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/entity/Chart.java @@ -0,0 +1,73 @@ +package top.peng.answerbi.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; + +/** + * 图表信息表 + * @TableName chart + */ +@TableName(value ="chart") +@Data +public class Chart implements Serializable { + /** + * id + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 创建时间 + */ + private Date createdTime; + + /** + * 更新时间 + */ + private Date updatedTime; + + /** + * 是否删除 + */ + @TableLogic + private Integer deletedFlag; + + /** + * 创建用户 id + */ + private Long userId; + + /** + * 分析目标 + */ + private String goal; + + /** + * 图表数据 + */ + private String chartData; + + /** + * 图表类型 + */ + private String chartType; + + /** + * 生成的图表数据 + */ + private String genChart; + + /** + * 生成的分析结论 + */ + private String genResult; + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/top/peng/answerbi/model/entity/Post.java b/src/main/java/top/peng/answerbi/model/entity/Post.java new file mode 100644 index 0000000..67f65a6 --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/entity/Post.java @@ -0,0 +1,76 @@ +package top.peng.answerbi.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 yunpeng + * @version 1.0 2023/5/16 + */ +@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/top/peng/answerbi/model/entity/PostFavour.java b/src/main/java/top/peng/answerbi/model/entity/PostFavour.java new file mode 100644 index 0000000..378b3dd --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/entity/PostFavour.java @@ -0,0 +1,49 @@ +package top.peng.answerbi.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 yunpeng + * @version 1.0 2023/5/16 + **/ +@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/top/peng/answerbi/model/entity/PostThumb.java b/src/main/java/top/peng/answerbi/model/entity/PostThumb.java new file mode 100644 index 0000000..ef4ad1d --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/entity/PostThumb.java @@ -0,0 +1,49 @@ +package top.peng.answerbi.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 yunpeng + * @version 1.0 2023/5/16 + */ +@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/top/peng/answerbi/model/entity/User.java b/src/main/java/top/peng/answerbi/model/entity/User.java new file mode 100644 index 0000000..81e2786 --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/entity/User.java @@ -0,0 +1,68 @@ +package top.peng.answerbi.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; + +/** + * 用户 + * @TableName user + */ +@TableName(value ="user") +@Data +public class User implements Serializable { + /** + * id + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 创建时间 + */ + private Date createdTime; + + /** + * 更新时间 + */ + private Date updatedTime; + + /** + * 是否删除 + */ + @TableLogic + private Integer deletedFlag; + + /** + * 账号 + */ + private String userAccount; + + /** + * 密码 + */ + private String userPassword; + + /** + * 用户昵称 + */ + private String username; + + /** + * 用户头像 + */ + private String userAvatar; + + /** + * 用户角色:user/admin/ban + */ + private String userRole; + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/top/peng/answerbi/model/enums/FileUploadBizEnum.java b/src/main/java/top/peng/answerbi/model/enums/FileUploadBizEnum.java new file mode 100644 index 0000000..38eba1e --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/enums/FileUploadBizEnum.java @@ -0,0 +1,61 @@ +package top.peng.answerbi.model.enums; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.commons.lang3.ObjectUtils; + +/** + * 文件上传业务类型枚举 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +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/top/peng/answerbi/model/enums/UserRoleEnum.java b/src/main/java/top/peng/answerbi/model/enums/UserRoleEnum.java new file mode 100644 index 0000000..9d0a899 --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/enums/UserRoleEnum.java @@ -0,0 +1,63 @@ +package top.peng.answerbi.model.enums; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.commons.lang3.ObjectUtils; + +/** + * 用户角色枚举 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +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/top/peng/answerbi/model/vo/LoginUserVO.java b/src/main/java/top/peng/answerbi/model/vo/LoginUserVO.java new file mode 100644 index 0000000..c0666fc --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/vo/LoginUserVO.java @@ -0,0 +1,52 @@ +package top.peng.answerbi.model.vo; + +import java.io.Serializable; +import java.util.Date; +import lombok.Data; + +/** + * 已登录用户视图(脱敏) + * + * @author yunpeng + * @version 1.0 2023/5/16 + **/ +@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/top/peng/answerbi/model/vo/PostVO.java b/src/main/java/top/peng/answerbi/model/vo/PostVO.java new file mode 100644 index 0000000..59fa67f --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/vo/PostVO.java @@ -0,0 +1,118 @@ +package top.peng.answerbi.model.vo; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import top.peng.answerbi.model.entity.Post; +import java.io.Serializable; +import java.util.Date; +import java.util.List; +import lombok.Data; +import org.springframework.beans.BeanUtils; + +/** + * 帖子视图 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@Data +public class PostVO implements Serializable { + + private final static Gson GSON = new Gson(); + + /** + * 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(); + if (tagList != null) { + post.setTags(GSON.toJson(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(GSON.fromJson(post.getTags(), new TypeToken>() { + }.getType())); + return postVO; + } +} diff --git a/src/main/java/top/peng/answerbi/model/vo/UserVO.java b/src/main/java/top/peng/answerbi/model/vo/UserVO.java new file mode 100644 index 0000000..b246658 --- /dev/null +++ b/src/main/java/top/peng/answerbi/model/vo/UserVO.java @@ -0,0 +1,47 @@ +package top.peng.answerbi.model.vo; + +import java.io.Serializable; +import java.util.Date; +import lombok.Data; + +/** + * 用户视图(脱敏) + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@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/top/peng/answerbi/service/ChartService.java b/src/main/java/top/peng/answerbi/service/ChartService.java new file mode 100644 index 0000000..5fd845f --- /dev/null +++ b/src/main/java/top/peng/answerbi/service/ChartService.java @@ -0,0 +1,24 @@ +package top.peng.answerbi.service; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import top.peng.answerbi.model.dto.chart.ChartQueryRequest; +import top.peng.answerbi.model.dto.post.PostQueryRequest; +import top.peng.answerbi.model.entity.Chart; +import com.baomidou.mybatisplus.extension.service.IService; +import top.peng.answerbi.model.entity.Post; + +/** +* @author yunpeng.zhang +* @description 针对表【chart(图表信息表)】的数据库操作Service +* @createDate 2023-07-10 16:45:42 +*/ +public interface ChartService extends IService { + + /** + * 获取查询条件 + * + * @param postQueryRequest + * @return + */ + QueryWrapper getQueryWrapper(ChartQueryRequest chartQueryRequest); +} diff --git a/src/main/java/top/peng/answerbi/service/PostFavourService.java b/src/main/java/top/peng/answerbi/service/PostFavourService.java new file mode 100644 index 0000000..5d48fc8 --- /dev/null +++ b/src/main/java/top/peng/answerbi/service/PostFavourService.java @@ -0,0 +1,47 @@ +package top.peng.answerbi.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 top.peng.answerbi.model.entity.Post; +import top.peng.answerbi.model.entity.PostFavour; +import top.peng.answerbi.model.entity.User; + +/** + * 帖子收藏服务 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +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/top/peng/answerbi/service/PostService.java b/src/main/java/top/peng/answerbi/service/PostService.java new file mode 100644 index 0000000..5b0c721 --- /dev/null +++ b/src/main/java/top/peng/answerbi/service/PostService.java @@ -0,0 +1,60 @@ +package top.peng.answerbi.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 top.peng.answerbi.model.dto.post.PostQueryRequest; +import top.peng.answerbi.model.entity.Post; +import top.peng.answerbi.model.vo.PostVO; +import javax.servlet.http.HttpServletRequest; + +/** + * 帖子服务 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +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/top/peng/answerbi/service/PostThumbService.java b/src/main/java/top/peng/answerbi/service/PostThumbService.java new file mode 100644 index 0000000..a042f42 --- /dev/null +++ b/src/main/java/top/peng/answerbi/service/PostThumbService.java @@ -0,0 +1,32 @@ +package top.peng.answerbi.service; + +import top.peng.answerbi.model.entity.PostThumb; +import com.baomidou.mybatisplus.extension.service.IService; +import top.peng.answerbi.model.entity.User; + +/** + * 帖子点赞服务 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +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/top/peng/answerbi/service/UserService.java b/src/main/java/top/peng/answerbi/service/UserService.java new file mode 100644 index 0000000..25cd206 --- /dev/null +++ b/src/main/java/top/peng/answerbi/service/UserService.java @@ -0,0 +1,112 @@ +package top.peng.answerbi.service; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.IService; +import top.peng.answerbi.model.dto.user.UserQueryRequest; +import top.peng.answerbi.model.entity.User; +import top.peng.answerbi.model.vo.LoginUserVO; +import top.peng.answerbi.model.vo.UserVO; +import java.util.List; +import javax.servlet.http.HttpServletRequest; + +/** + * 用户服务 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +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/top/peng/answerbi/service/impl/ChartServiceImpl.java b/src/main/java/top/peng/answerbi/service/impl/ChartServiceImpl.java new file mode 100644 index 0000000..c8f70f5 --- /dev/null +++ b/src/main/java/top/peng/answerbi/service/impl/ChartServiceImpl.java @@ -0,0 +1,56 @@ +package top.peng.answerbi.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import top.peng.answerbi.constant.CommonConstant; +import top.peng.answerbi.model.dto.chart.ChartQueryRequest; +import top.peng.answerbi.model.entity.Chart; +import top.peng.answerbi.service.ChartService; +import top.peng.answerbi.mapper.ChartMapper; +import org.springframework.stereotype.Service; +import top.peng.answerbi.utils.SqlUtils; + +/** +* @author yunpeng.zhang +* @description 针对表【chart(图表信息表)】的数据库操作Service实现 +* @createDate 2023-07-10 16:45:42 +*/ +@Service +public class ChartServiceImpl extends ServiceImpl + implements ChartService{ + + /** + * 获取查询条件 + * + * @param chartQueryRequest@return + */ + @Override + public QueryWrapper getQueryWrapper(ChartQueryRequest chartQueryRequest) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + if (chartQueryRequest == null) { + return queryWrapper; + } + Long id = chartQueryRequest.getId(); + String goal = chartQueryRequest.getGoal(); + String chartType = chartQueryRequest.getChartType(); + Long userId = chartQueryRequest.getUserId(); + String sortField = chartQueryRequest.getSortField(); + String sortOrder = chartQueryRequest.getSortOrder(); + + queryWrapper.eq(id != null && id > 0, "id", id); + queryWrapper.eq(StringUtils.isNotBlank(goal), "goal", goal); + queryWrapper.eq(StringUtils.isNotBlank(chartType), "chart_type", chartType); + queryWrapper.eq(ObjectUtils.isNotEmpty(userId), "user_id", userId); + queryWrapper.eq("deleted_flag", false); + queryWrapper.orderBy(SqlUtils.validSortField(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC), + sortField); + return queryWrapper; + + } +} + + + + diff --git a/src/main/java/top/peng/answerbi/service/impl/PostFavourServiceImpl.java b/src/main/java/top/peng/answerbi/service/impl/PostFavourServiceImpl.java new file mode 100644 index 0000000..6a7b92a --- /dev/null +++ b/src/main/java/top/peng/answerbi/service/impl/PostFavourServiceImpl.java @@ -0,0 +1,116 @@ +package top.peng.answerbi.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 top.peng.answerbi.common.ErrorCode; +import top.peng.answerbi.exception.BusinessException; +import top.peng.answerbi.mapper.PostFavourMapper; +import top.peng.answerbi.model.entity.Post; +import top.peng.answerbi.model.entity.PostFavour; +import top.peng.answerbi.model.entity.User; +import top.peng.answerbi.service.PostFavourService; +import top.peng.answerbi.service.PostService; +import javax.annotation.Resource; +import org.springframework.aop.framework.AopContext; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * 帖子收藏服务实现 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@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/top/peng/answerbi/service/impl/PostServiceImpl.java b/src/main/java/top/peng/answerbi/service/impl/PostServiceImpl.java new file mode 100644 index 0000000..2ecf81e --- /dev/null +++ b/src/main/java/top/peng/answerbi/service/impl/PostServiceImpl.java @@ -0,0 +1,316 @@ +package top.peng.answerbi.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.google.gson.Gson; +import top.peng.answerbi.common.ErrorCode; +import top.peng.answerbi.constant.CommonConstant; +import top.peng.answerbi.exception.BusinessException; +import top.peng.answerbi.exception.ThrowUtils; +import top.peng.answerbi.mapper.PostFavourMapper; +import top.peng.answerbi.mapper.PostMapper; +import top.peng.answerbi.mapper.PostThumbMapper; +import top.peng.answerbi.model.dto.post.PostEsDTO; +import top.peng.answerbi.model.dto.post.PostQueryRequest; +import top.peng.answerbi.model.entity.Post; +import top.peng.answerbi.model.entity.PostFavour; +import top.peng.answerbi.model.entity.PostThumb; +import top.peng.answerbi.model.entity.User; +import top.peng.answerbi.model.vo.PostVO; +import top.peng.answerbi.model.vo.UserVO; +import top.peng.answerbi.service.PostService; +import top.peng.answerbi.service.UserService; +import top.peng.answerbi.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 org.apache.commons.collections4.CollectionUtils; +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 yunpeng + * @version 1.0 2023/5/16 + */ +@Service +@Slf4j +public class PostServiceImpl extends ServiceImpl implements PostService { + + private final static Gson GSON = new Gson(); + + @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.like("title", searchText).or().like("content", searchText); + } + queryWrapper.like(StringUtils.isNotBlank(title), "title", title); + queryWrapper.like(StringUtils.isNotBlank(content), "content", content); + if (CollectionUtils.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.eq("isDelete", false); + 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 (CollectionUtils.isNotEmpty(tagList)) { + for (String tag : tagList) { + boolQueryBuilder.filter(QueryBuilders.termQuery("tags", tag)); + } + } + // 包含任何一个标签即可 + if (CollectionUtils.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 (CollectionUtils.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/top/peng/answerbi/service/impl/PostThumbServiceImpl.java b/src/main/java/top/peng/answerbi/service/impl/PostThumbServiceImpl.java new file mode 100644 index 0000000..36351b5 --- /dev/null +++ b/src/main/java/top/peng/answerbi/service/impl/PostThumbServiceImpl.java @@ -0,0 +1,105 @@ +package top.peng.answerbi.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import top.peng.answerbi.common.ErrorCode; +import top.peng.answerbi.exception.BusinessException; +import top.peng.answerbi.mapper.PostThumbMapper; +import top.peng.answerbi.model.entity.Post; +import top.peng.answerbi.model.entity.PostThumb; +import top.peng.answerbi.model.entity.User; +import top.peng.answerbi.service.PostService; +import top.peng.answerbi.service.PostThumbService; +import javax.annotation.Resource; +import org.springframework.aop.framework.AopContext; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * 帖子点赞服务实现 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@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/top/peng/answerbi/service/impl/UserServiceImpl.java b/src/main/java/top/peng/answerbi/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..756ccd8 --- /dev/null +++ b/src/main/java/top/peng/answerbi/service/impl/UserServiceImpl.java @@ -0,0 +1,238 @@ +package top.peng.answerbi.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import top.peng.answerbi.common.ErrorCode; +import top.peng.answerbi.constant.CommonConstant; +import top.peng.answerbi.exception.BusinessException; +import top.peng.answerbi.mapper.UserMapper; +import top.peng.answerbi.model.dto.user.UserQueryRequest; +import top.peng.answerbi.model.entity.User; +import top.peng.answerbi.model.enums.UserRoleEnum; +import top.peng.answerbi.model.vo.LoginUserVO; +import top.peng.answerbi.model.vo.UserVO; +import top.peng.answerbi.service.UserService; +import top.peng.answerbi.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 org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; +import org.springframework.util.DigestUtils; +import top.peng.answerbi.constant.UserConstant; + +/** + * 用户服务实现 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@Service +@Slf4j +public class UserServiceImpl extends ServiceImpl implements UserService { + + /** + * 盐值,混淆密码 + */ + private static final String SALT = "answer"; + + @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("user_account", 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("user_account", userAccount); + queryWrapper.eq("user_password", 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(UserConstant.USER_LOGIN_STATE, user); + return this.getLoginUserVO(user); + } + + /** + * 获取当前登录用户 + * + * @param request + * @return + */ + @Override + public User getLoginUser(HttpServletRequest request) { + // 先判断是否已登录 + Object userObj = request.getSession().getAttribute(UserConstant.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(UserConstant.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(UserConstant.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(UserConstant.USER_LOGIN_STATE) == null) { + throw new BusinessException(ErrorCode.OPERATION_ERROR, "未登录"); + } + // 移除登录态 + request.getSession().removeAttribute(UserConstant.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 (CollectionUtils.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/top/peng/answerbi/utils/NetUtils.java b/src/main/java/top/peng/answerbi/utils/NetUtils.java new file mode 100644 index 0000000..0798bfc --- /dev/null +++ b/src/main/java/top/peng/answerbi/utils/NetUtils.java @@ -0,0 +1,55 @@ +package top.peng.answerbi.utils; + +import java.net.InetAddress; +import javax.servlet.http.HttpServletRequest; + +/** + * 网络工具类 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +public class NetUtils { + + /** + * 获取客户端 IP 地址 + * + * @param request + * @return + */ + public static String getIpAddress(HttpServletRequest request) { + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + if (ip.equals("127.0.0.1")) { + // 根据网卡取本机配置的 IP + InetAddress inet = null; + try { + inet = InetAddress.getLocalHost(); + } catch (Exception e) { + e.printStackTrace(); + } + if (inet != null) { + ip = inet.getHostAddress(); + } + } + } + // 多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 + if (ip != null && ip.length() > 15) { + if (ip.indexOf(",") > 0) { + ip = ip.substring(0, ip.indexOf(",")); + } + } + if (ip == null) { + return "127.0.0.1"; + } + return ip; + } + +} diff --git a/src/main/java/top/peng/answerbi/utils/SpringContextUtils.java b/src/main/java/top/peng/answerbi/utils/SpringContextUtils.java new file mode 100644 index 0000000..dfc43c0 --- /dev/null +++ b/src/main/java/top/peng/answerbi/utils/SpringContextUtils.java @@ -0,0 +1,57 @@ +package top.peng.answerbi.utils; + +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * Spring 上下文获取工具 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@Component +public class SpringContextUtils implements ApplicationContextAware { + + private static ApplicationContext applicationContext; + + @Override + public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException { + SpringContextUtils.applicationContext = applicationContext; + } + + /** + * 通过名称获取 Bean + * + * @param beanName + * @return + */ + public static Object getBean(String beanName) { + return applicationContext.getBean(beanName); + } + + /** + * 通过 class 获取 Bean + * + * @param beanClass + * @param + * @return + */ + public static T getBean(Class beanClass) { + return applicationContext.getBean(beanClass); + } + + /** + * 通过名称和类型获取 Bean + * + * @param beanName + * @param beanClass + * @param + * @return + */ + public static T getBean(String beanName, Class beanClass) { + return applicationContext.getBean(beanName, beanClass); + } +} \ No newline at end of file diff --git a/src/main/java/top/peng/answerbi/utils/SqlUtils.java b/src/main/java/top/peng/answerbi/utils/SqlUtils.java new file mode 100644 index 0000000..115c681 --- /dev/null +++ b/src/main/java/top/peng/answerbi/utils/SqlUtils.java @@ -0,0 +1,25 @@ +package top.peng.answerbi.utils; + +import org.apache.commons.lang3.StringUtils; + +/** + * SQL 工具 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +public class SqlUtils { + + /** + * 校验排序字段是否合法(防止 SQL 注入) + * + * @param sortField + * @return + */ + public static boolean validSortField(String sortField) { + if (StringUtils.isBlank(sortField)) { + return false; + } + return !StringUtils.containsAny(sortField, "=", "(", ")", " "); + } +} diff --git a/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000..46f9d16 --- /dev/null +++ b/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,34 @@ +{ + "properties": [ + { + "name": "cos.client.accessKey", + "type": "java.lang.String", + "description": "Description for cos.client.accessKey." + }, + { + "name": "cos.client.secretKey", + "type": "java.lang.String", + "description": "Description for cos.client.secretKey." + }, + { + "name": "cos.client.region", + "type": "java.lang.String", + "description": "Description for cos.client.region." + }, + { + "name": "cos.client.bucket", + "type": "java.lang.String", + "description": "Description for cos.client.bucket." + }, + { + "name": "wx.open.appId", + "type": "java.lang.String", + "description": "Description for wx.open.appId." + }, + { + "name": "wx.open.appSecret", + "type": "java.lang.String", + "description": "Description for wx.open.appSecret." + } + ] +} \ No newline at end of file diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..b0dbaa3 --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,24 @@ +server: + port: 8101 +spring: + # 数据库配置 + # todo 需替换配置 + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/answer_bi + username: root + password: 123456 + # Redis 配置 + # todo 需替换配置 + redis: + database: 1 + host: localhost + port: 6379 + timeout: 5000 + password: 123456 + # Elasticsearch 配置 + # todo 需替换配置 + elasticsearch: + uris: http://localhost:9200 + username: root + password: 123456 \ No newline at end of file diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 0000000..7df7e1b --- /dev/null +++ b/src/main/resources/application-prod.yml @@ -0,0 +1,28 @@ +server: + port: 8101 +spring: + # 数据库配置 + # todo 需替换配置 + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/answer_bi + username: root + password: 123456 + # Redis 配置 + # todo 需替换配置 + redis: + database: 1 + host: localhost + port: 6379 + timeout: 5000 + password: 123456 + # Elasticsearch 配置 + # todo 需替换配置 + elasticsearch: + uris: http://localhost:9200 + username: root + password: 123456 +mybatis-plus: + configuration: + # 生产环境关闭日志 + log-impl: '' \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..fc0fe00 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,89 @@ +spring: + application: + name: answerBi-backend + # 默认 dev 环境 + profiles: + active: dev + # 支持 swagger3 + mvc: + pathmatch: + matching-strategy: ant_path_matcher + # session 配置 + session: + # todo 取消注释开启分布式 session(须先配置 Redis) + # store-type: redis + # 30 天过期 + timeout: 2592000 + # 数据库配置 + # todo 需替换配置 + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/my_db + username: root + password: 123456 + # Redis 配置 + # todo 需替换配置,然后取消注释 +# redis: +# database: 1 +# host: localhost +# port: 6379 +# timeout: 5000 +# password: 123456 + # Elasticsearch 配置 + # todo 需替换配置,然后取消注释 +# elasticsearch: +# uris: http://localhost:9200 +# username: root +# password: 123456 + # 文件上传 + servlet: + multipart: + # 大小限制 + max-file-size: 10MB +server: + address: 0.0.0.0 + port: 8101 + servlet: + context-path: /api + # cookie 30 天过期 + session: + cookie: + max-age: 2592000 +mybatis-plus: + configuration: + map-underscore-to-camel-case: true + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + global-config: + db-config: + logic-delete-field: deleted_flag # 全局逻辑删除的实体字段名 + logic-delete-value: 1 # 逻辑已删除值(默认为 1) + logic-not-delete-value: 0 # 逻辑未删除值(默认为 0) +# 微信相关 +wx: + # 微信公众平台 + # todo 需替换配置 + mp: + token: xxx + aesKey: xxx + appId: xxx + secret: xxx + config-storage: + http-client-type: HttpClient + key-prefix: wx + redis: + host: 127.0.0.1 + port: 6379 + type: Memory + # 微信开放平台 + # todo 需替换配置 + open: + appId: xxx + appSecret: xxx +# 对象存储 +# todo 需替换配置 +cos: + client: + accessKey: xxx + secretKey: xxx + region: xxx + bucket: xxx \ No newline at end of file diff --git a/src/main/resources/mapper/ChartMapper.xml b/src/main/resources/mapper/ChartMapper.xml new file mode 100644 index 0000000..10f4321 --- /dev/null +++ b/src/main/resources/mapper/ChartMapper.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + id,created_time,updated_time, + deleted_flag,user_id,goal, + chart_data,chart_type,gen_chart, + gen_result + + diff --git a/src/main/resources/mapper/PostFavourMapper.xml b/src/main/resources/mapper/PostFavourMapper.xml new file mode 100644 index 0000000..0cfe67b --- /dev/null +++ b/src/main/resources/mapper/PostFavourMapper.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + id,postId,userId, + createTime,updateTime + + + + diff --git a/src/main/resources/mapper/PostMapper.xml b/src/main/resources/mapper/PostMapper.xml new file mode 100644 index 0000000..c8fb018 --- /dev/null +++ b/src/main/resources/mapper/PostMapper.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + id,title,content,tags, + thumbNum,favourNum,userId, + createTime,updateTime,isDelete + + + + diff --git a/src/main/resources/mapper/PostThumbMapper.xml b/src/main/resources/mapper/PostThumbMapper.xml new file mode 100644 index 0000000..981ef4f --- /dev/null +++ b/src/main/resources/mapper/PostThumbMapper.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + id,postId, + userId,createTime,updateTime + + diff --git a/src/main/resources/mapper/UserMapper.xml b/src/main/resources/mapper/UserMapper.xml new file mode 100644 index 0000000..e932ae5 --- /dev/null +++ b/src/main/resources/mapper/UserMapper.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + id,created_time,updated_time, + deleted_flag,user_account,user_password, + username,user_avatar,user_role + + diff --git a/src/main/resources/test_excel.xlsx b/src/main/resources/test_excel.xlsx new file mode 100644 index 0000000..9e4c4c6 Binary files /dev/null and b/src/main/resources/test_excel.xlsx differ diff --git a/src/test/java/top/peng/answerbi/MainApplicationTests.java b/src/test/java/top/peng/answerbi/MainApplicationTests.java new file mode 100644 index 0000000..3491440 --- /dev/null +++ b/src/test/java/top/peng/answerbi/MainApplicationTests.java @@ -0,0 +1,25 @@ +package top.peng.answerbi; + +import top.peng.answerbi.config.WxOpenConfig; +import javax.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * 主类测试 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@SpringBootTest +class MainApplicationTests { + + @Resource + private WxOpenConfig wxOpenConfig; + + @Test + void contextLoads() { + System.out.println(wxOpenConfig); + } + +} diff --git a/src/test/java/top/peng/answerbi/esdao/PostEsDaoTest.java b/src/test/java/top/peng/answerbi/esdao/PostEsDaoTest.java new file mode 100644 index 0000000..8457345 --- /dev/null +++ b/src/test/java/top/peng/answerbi/esdao/PostEsDaoTest.java @@ -0,0 +1,83 @@ +package top.peng.answerbi.esdao; + +import top.peng.answerbi.model.dto.post.PostEsDTO; +import top.peng.answerbi.model.dto.post.PostQueryRequest; +import top.peng.answerbi.model.entity.Post; +import top.peng.answerbi.service.PostService; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Optional; +import javax.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; + +/** + * 帖子 ES 操作测试 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@SpringBootTest +public class PostEsDaoTest { + + @Resource + private PostEsDao postEsDao; + + @Resource + private PostService postService; + + @Test + void test() { + PostQueryRequest postQueryRequest = new PostQueryRequest(); + com.baomidou.mybatisplus.extension.plugins.pagination.Page page = + postService.searchFromEs(postQueryRequest); + System.out.println(page); + } + + @Test + void testSelect() { + System.out.println(postEsDao.count()); + Page PostPage = postEsDao.findAll( + PageRequest.of(0, 5, Sort.by("createTime"))); + List postList = PostPage.getContent(); + System.out.println(postList); + } + + @Test + void testAdd() { + PostEsDTO postEsDTO = new PostEsDTO(); + postEsDTO.setId(1L); + postEsDTO.setTitle("test"); + postEsDTO.setContent("test"); + postEsDTO.setTags(Arrays.asList("java", "python")); + postEsDTO.setThumbNum(1); + postEsDTO.setFavourNum(1); + postEsDTO.setUserId(1L); + postEsDTO.setCreateTime(new Date()); + postEsDTO.setUpdateTime(new Date()); + postEsDTO.setIsDelete(0); + postEsDao.save(postEsDTO); + System.out.println(postEsDTO.getId()); + } + + @Test + void testFindById() { + Optional postEsDTO = postEsDao.findById(1L); + System.out.println(postEsDTO); + } + + @Test + void testCount() { + System.out.println(postEsDao.count()); + } + + @Test + void testFindByCategory() { + List postEsDaoTestList = postEsDao.findByUserId(1L); + System.out.println(postEsDaoTestList); + } +} diff --git a/src/test/java/top/peng/answerbi/manager/CosManagerTest.java b/src/test/java/top/peng/answerbi/manager/CosManagerTest.java new file mode 100644 index 0000000..0d0cf09 --- /dev/null +++ b/src/test/java/top/peng/answerbi/manager/CosManagerTest.java @@ -0,0 +1,23 @@ +package top.peng.answerbi.manager; + +import javax.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * Cos 操作测试 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@SpringBootTest +class CosManagerTest { + + @Resource + private CosManager cosManager; + + @Test + void putObject() { + cosManager.putObject("test", "test.json"); + } +} \ No newline at end of file diff --git a/src/test/java/top/peng/answerbi/mapper/PostFavourMapperTest.java b/src/test/java/top/peng/answerbi/mapper/PostFavourMapperTest.java new file mode 100644 index 0000000..efd51db --- /dev/null +++ b/src/test/java/top/peng/answerbi/mapper/PostFavourMapperTest.java @@ -0,0 +1,33 @@ +package top.peng.answerbi.mapper; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import top.peng.answerbi.model.entity.Post; +import javax.annotation.Resource; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * 帖子收藏数据库操作测试 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@SpringBootTest +class PostFavourMapperTest { + + @Resource + private PostFavourMapper postFavourMapper; + + @Test + void listUserFavourPostByPage() { + IPage page = new Page<>(2, 1); + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("id", 1); + queryWrapper.like("content", "a"); + IPage result = postFavourMapper.listFavourPostByPage(page, queryWrapper, 1); + Assertions.assertNotNull(result); + } +} \ No newline at end of file diff --git a/src/test/java/top/peng/answerbi/mapper/PostMapperTest.java b/src/test/java/top/peng/answerbi/mapper/PostMapperTest.java new file mode 100644 index 0000000..adf59ab --- /dev/null +++ b/src/test/java/top/peng/answerbi/mapper/PostMapperTest.java @@ -0,0 +1,28 @@ +package top.peng.answerbi.mapper; + +import top.peng.answerbi.model.entity.Post; +import java.util.Date; +import java.util.List; +import javax.annotation.Resource; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * 帖子数据库操作测试 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@SpringBootTest +class PostMapperTest { + + @Resource + private PostMapper postMapper; + + @Test + void listPostWithDelete() { + List postList = postMapper.listPostWithDelete(new Date()); + Assertions.assertNotNull(postList); + } +} \ No newline at end of file diff --git a/src/test/java/top/peng/answerbi/service/PostFavourServiceTest.java b/src/test/java/top/peng/answerbi/service/PostFavourServiceTest.java new file mode 100644 index 0000000..b9a7277 --- /dev/null +++ b/src/test/java/top/peng/answerbi/service/PostFavourServiceTest.java @@ -0,0 +1,44 @@ +package top.peng.answerbi.service; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import top.peng.answerbi.model.entity.Post; +import javax.annotation.Resource; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import top.peng.answerbi.model.entity.User; + +/** + * 帖子收藏服务测试 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@SpringBootTest +class PostFavourServiceTest { + + @Resource + private PostFavourService postFavourService; + + private static final User loginUser = new User(); + + @BeforeAll + static void setUp() { + loginUser.setId(1L); + } + + @Test + void doPostFavour() { + int i = postFavourService.doPostFavour(1L, loginUser); + Assertions.assertTrue(i >= 0); + } + + @Test + void listFavourPostByPage() { + QueryWrapper postQueryWrapper = new QueryWrapper<>(); + postQueryWrapper.eq("id", 1L); + postFavourService.listFavourPostByPage(Page.of(0, 1), postQueryWrapper, loginUser.getId()); + } +} diff --git a/src/test/java/top/peng/answerbi/service/PostServiceTest.java b/src/test/java/top/peng/answerbi/service/PostServiceTest.java new file mode 100644 index 0000000..5cdc354 --- /dev/null +++ b/src/test/java/top/peng/answerbi/service/PostServiceTest.java @@ -0,0 +1,31 @@ +package top.peng.answerbi.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import top.peng.answerbi.model.dto.post.PostQueryRequest; +import top.peng.answerbi.model.entity.Post; +import javax.annotation.Resource; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * 帖子服务测试 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@SpringBootTest +class PostServiceTest { + + @Resource + private PostService postService; + + @Test + void searchFromEs() { + PostQueryRequest postQueryRequest = new PostQueryRequest(); + postQueryRequest.setUserId(1L); + Page postPage = postService.searchFromEs(postQueryRequest); + Assertions.assertNotNull(postPage); + } + +} \ No newline at end of file diff --git a/src/test/java/top/peng/answerbi/service/PostThumbServiceTest.java b/src/test/java/top/peng/answerbi/service/PostThumbServiceTest.java new file mode 100644 index 0000000..823856b --- /dev/null +++ b/src/test/java/top/peng/answerbi/service/PostThumbServiceTest.java @@ -0,0 +1,34 @@ +package top.peng.answerbi.service; + +import javax.annotation.Resource; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import top.peng.answerbi.model.entity.User; + +/** + * 帖子点赞服务测试 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@SpringBootTest +class PostThumbServiceTest { + + @Resource + private PostThumbService postThumbService; + + private static final User loginUser = new User(); + + @BeforeAll + static void setUp() { + loginUser.setId(1L); + } + + @Test + void doPostThumb() { + int i = postThumbService.doPostThumb(1L, loginUser); + Assertions.assertTrue(i >= 0); + } +} diff --git a/src/test/java/top/peng/answerbi/service/UserServiceTest.java b/src/test/java/top/peng/answerbi/service/UserServiceTest.java new file mode 100644 index 0000000..5453c2b --- /dev/null +++ b/src/test/java/top/peng/answerbi/service/UserServiceTest.java @@ -0,0 +1,35 @@ +package top.peng.answerbi.service; + +import javax.annotation.Resource; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * 用户服务测试 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@SpringBootTest +public class UserServiceTest { + + @Resource + private UserService userService; + + @Test + void userRegister() { + String userAccount = "answer"; + String userPassword = ""; + String checkPassword = "123456"; + try { + long result = userService.userRegister(userAccount, userPassword, checkPassword); + Assertions.assertEquals(-1, result); + userAccount = "yu"; + result = userService.userRegister(userAccount, userPassword, checkPassword); + Assertions.assertEquals(-1, result); + } catch (Exception e) { + + } + } +} diff --git a/src/test/java/top/peng/answerbi/utils/EasyExcelTest.java b/src/test/java/top/peng/answerbi/utils/EasyExcelTest.java new file mode 100644 index 0000000..2efd5f7 --- /dev/null +++ b/src/test/java/top/peng/answerbi/utils/EasyExcelTest.java @@ -0,0 +1,34 @@ +package top.peng.answerbi.utils; + +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.support.ExcelTypeEnum; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.util.ResourceUtils; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.List; +import java.util.Map; + +/** + * EasyExcel 测试 + * + * @author yunpeng + * @version 1.0 2023/5/16 + */ +@SpringBootTest +public class EasyExcelTest { + + @Test + public void doImport() throws FileNotFoundException { + File file = ResourceUtils.getFile("classpath:test_excel.xlsx"); + List> list = EasyExcel.read(file) + .excelType(ExcelTypeEnum.XLSX) + .sheet() + .headRowNumber(0) + .doReadSync(); + System.out.println(list); + } + +} \ No newline at end of file