项目初始化 Initial commit

This commit is contained in:
brian 2023-07-11 13:17:30 +08:00
commit f2229be084
103 changed files with 5940 additions and 0 deletions

144
.gitignore vendored Normal file
View File

@ -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

139
README.md Normal file
View File

@ -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
```

144
pom.xml Normal file
View File

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>top.peng</groupId>
<artifactId>answerBi-backend</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>answerBi-backend</name>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<!-- elasticsearch-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.binarywang/wx-java-mp-spring-boot-starter -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-mp-spring-boot-starter</artifactId>
<version>4.4.0</version>
</dependency>
<!-- https://doc.xiaominfo.com/knife4j/documentation/get_start.html-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<!-- https://cloud.tencent.com/document/product/436/10199-->
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>5.6.89</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.1</version>
</dependency>
<!-- https://github.com/alibaba/easyexcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.1</version>
</dependency>
<!-- https://hutool.cn/docs/index.html#/-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.8</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.2</version>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

40
sql/create_table.sql Normal file
View File

@ -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;

52
sql/post_es_mapping.json Normal file
View File

@ -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"
}
}
}
}

View File

@ -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);
}
}

View File

@ -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 "";
}

View File

@ -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();
}
}

View File

@ -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 startid: {}, 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;
}
}

View File

@ -0,0 +1,35 @@
package top.peng.answerbi.common;
import java.io.Serializable;
import lombok.Data;
/**
* 通用返回类
*
* @param <T>
* @author yunpeng
* @version 1.0 2023/5/16
*/
@Data
public class CommonResponse<T> 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());
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -0,0 +1,52 @@
package top.peng.answerbi.common;
/**
* 返回工具类
*
* @author yunpeng
* @version 1.0 2023/5/16
*/
public class ResultUtils {
/**
* 成功
*
* @param data
* @param <T>
* @return
*/
public static <T> CommonResponse<T> 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);
}
}

View File

@ -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("*");
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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";
}

View File

@ -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";
}

View File

@ -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
}

View File

@ -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<Long> 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<Boolean> 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<Boolean> 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<Chart> 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<Page<Chart>> listChartByPage(@RequestBody ChartQueryRequest chartQueryRequest,
HttpServletRequest request) {
long current = chartQueryRequest.getCurrent();
long size = chartQueryRequest.getPageSize();
// 限制爬虫
ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);
Page<Chart> 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<Page<Chart>> 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<Chart> chartPage = chartService.page(new Page<>(current, size),
chartService.getQueryWrapper(chartQueryRequest));
return ResultUtils.success(chartPage);
}
// endregion
/**
* 编辑用户
*
* @param chartEditRequest
* @param request
* @return
*/
@PostMapping("/edit")
public CommonResponse<Boolean> 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);
}
}

View File

@ -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<String> 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, "文件类型错误");
}
}
}
}

View File

@ -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 <a href="https://github.com/liyupi">程序员鱼皮</a>
* @from <a href="https://yupi.icu">编程导航知识星球</a>
*/
@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<Long> addPost(@RequestBody PostAddRequest postAddRequest, HttpServletRequest request) {
if (postAddRequest == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
Post post = new Post();
BeanUtils.copyProperties(postAddRequest, post);
List<String> 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<Boolean> 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<Boolean> 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<String> 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<PostVO> 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<Page<PostVO>> listPostVOByPage(@RequestBody PostQueryRequest postQueryRequest,
HttpServletRequest request) {
long current = postQueryRequest.getCurrent();
long size = postQueryRequest.getPageSize();
// 限制爬虫
ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);
Page<Post> 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<Page<PostVO>> 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<Post> 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<Page<PostVO>> searchPostVOByPage(@RequestBody PostQueryRequest postQueryRequest,
HttpServletRequest request) {
long size = postQueryRequest.getPageSize();
// 限制爬虫
ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);
Page<Post> postPage = postService.searchFromEs(postQueryRequest);
return ResultUtils.success(postService.getPostVOPage(postPage, request));
}
/**
* 编辑用户
*
* @param postEditRequest
* @param request
* @return
*/
@PostMapping("/edit")
public CommonResponse<Boolean> 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<String> 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);
}
}

View File

@ -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<Integer> 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<Page<PostVO>> 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<Post> 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<Page<PostVO>> 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<Post> postPage = postFavourService.listFavourPostByPage(new Page<>(current, size),
postService.getQueryWrapper(postFavourQueryRequest.getPostQueryRequest()), userId);
return ResultUtils.success(postService.getPostVOPage(postPage, request));
}
}

View File

@ -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<Integer> 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);
}
}

View File

@ -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<Long> 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<LoginUserVO> 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<Boolean> 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<LoginUserVO> 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<Long> 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<Boolean> 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<Boolean> 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<User> 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<UserVO> getUserVOById(long id, HttpServletRequest request) {
CommonResponse<User> 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<Page<User>> listUserByPage(@RequestBody UserQueryRequest userQueryRequest,
HttpServletRequest request) {
long current = userQueryRequest.getCurrent();
long size = userQueryRequest.getPageSize();
Page<User> 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<Page<UserVO>> 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<User> userPage = userService.page(new Page<>(current, size),
userService.getQueryWrapper(userQueryRequest));
Page<UserVO> userVOPage = new Page<>(current, size, userPage.getTotal());
List<UserVO> userVO = userService.getUserVO(userPage.getRecords());
userVOPage.setRecords(userVO);
return ResultUtils.success(userVOPage);
}
// endregion
/**
* 更新个人信息
*
* @param userUpdateMyRequest
* @param request
* @return
*/
@PostMapping("/update/my")
public CommonResponse<Boolean> 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);
}
}

View File

@ -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<PostEsDTO, Long> {
List<PostEsDTO> findByUserId(Long userId);
}

View File

@ -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;
}
}

View File

@ -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, "系统错误");
}
}

View File

@ -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));
}
}

View File

@ -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<Post> postList = postMapper.listPostWithDelete(fiveMinutesAgoDate);
if (CollectionUtils.isEmpty(postList)) {
log.info("no inc post");
return;
}
List<PostEsDTO> 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);
}
}

View File

@ -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<Post> postList = postService.list();
if (CollectionUtils.isEmpty(postList)) {
return;
}
List<PostEsDTO> 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);
}
}

View File

@ -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);
}
}

View File

@ -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<Chart> {
}

View File

@ -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<PostFavour> {
/**
* 分页查询收藏帖子列表
*
* @param page
* @param queryWrapper
* @param favourUserId
* @return
*/
Page<Post> listFavourPostByPage(IPage<Post> page, @Param(Constants.WRAPPER) Wrapper<Post> queryWrapper,
long favourUserId);
}

View File

@ -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<Post> {
/**
* 查询帖子列表包括已被删除的数据
*/
List<Post> listPostWithDelete(Date minUpdateTime);
}

View File

@ -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<PostThumb> {
}

View File

@ -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<User> {
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<String> tags;
private static final long serialVersionUID = 1L;
}

View File

@ -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<String> tags;
private static final long serialVersionUID = 1L;
}

View File

@ -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<String> 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<List<String>>() {
}.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<String> tagList = postEsDTO.getTags();
if (CollectionUtils.isNotEmpty(tagList)) {
post.setTags(GSON.toJson(tagList));
}
return post;
}
}

View File

@ -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<String> tags;
/**
* 至少有一个标签
*/
private List<String> orTags;
/**
* 创建用户 id
*/
private Long userId;
/**
* 收藏用户 id
*/
private Long favourUserId;
private static final long serialVersionUID = 1L;
}

View File

@ -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<String> tags;
private static final long serialVersionUID = 1L;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<String> 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;
}
}

View File

@ -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<String> 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;
}
}

View File

@ -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;
}

View File

@ -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<String> 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<String> 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<List<String>>() {
}.getType()));
return postVO;
}
}

View File

@ -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;
}

View File

@ -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<Chart> {
/**
* 获取查询条件
*
* @param postQueryRequest
* @return
*/
QueryWrapper<Chart> getQueryWrapper(ChartQueryRequest chartQueryRequest);
}

View File

@ -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<PostFavour> {
/**
* 帖子收藏
*
* @param postId
* @param loginUser
* @return
*/
int doPostFavour(long postId, User loginUser);
/**
* 分页获取用户收藏的帖子列表
*
* @param page
* @param queryWrapper
* @param favourUserId
* @return
*/
Page<Post> listFavourPostByPage(IPage<Post> page, Wrapper<Post> queryWrapper,
long favourUserId);
/**
* 帖子收藏内部服务
*
* @param userId
* @param postId
* @return
*/
int doPostFavourInner(long userId, long postId);
}

View File

@ -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<Post> {
/**
* 校验
*
* @param post
* @param add
*/
void validPost(Post post, boolean add);
/**
* 获取查询条件
*
* @param postQueryRequest
* @return
*/
QueryWrapper<Post> getQueryWrapper(PostQueryRequest postQueryRequest);
/**
* ES 查询
*
* @param postQueryRequest
* @return
*/
Page<Post> searchFromEs(PostQueryRequest postQueryRequest);
/**
* 获取帖子封装
*
* @param post
* @param request
* @return
*/
PostVO getPostVO(Post post, HttpServletRequest request);
/**
* 分页获取帖子封装
*
* @param postPage
* @param request
* @return
*/
Page<PostVO> getPostVOPage(Page<Post> postPage, HttpServletRequest request);
}

View File

@ -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<PostThumb> {
/**
* 点赞
*
* @param postId
* @param loginUser
* @return
*/
int doPostThumb(long postId, User loginUser);
/**
* 帖子点赞内部服务
*
* @param userId
* @param postId
* @return
*/
int doPostThumbInner(long userId, long postId);
}

View File

@ -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<User> {
/**
* 用户注册
*
* @param userAccount 用户账户
* @param userPassword 用户密码
* @param checkPassword 校验密码
* @return 新用户 id
*/
long userRegister(String userAccount, String userPassword, String checkPassword);
/**
* 用户登录
*
* @param userAccount 用户账户
* @param userPassword 用户密码
* @param request
* @return 脱敏后的用户信息
*/
LoginUserVO userLogin(String userAccount, String userPassword, HttpServletRequest request);
/**
* 获取当前登录用户
*
* @param request
* @return
*/
User getLoginUser(HttpServletRequest request);
/**
* 获取当前登录用户允许未登录
*
* @param request
* @return
*/
User getLoginUserPermitNull(HttpServletRequest request);
/**
* 是否为管理员
*
* @param request
* @return
*/
boolean isAdmin(HttpServletRequest request);
/**
* 是否为管理员
*
* @param user
* @return
*/
boolean isAdmin(User user);
/**
* 用户注销
*
* @param request
* @return
*/
boolean userLogout(HttpServletRequest request);
/**
* 获取脱敏的已登录用户信息
*
* @return
*/
LoginUserVO getLoginUserVO(User user);
/**
* 获取脱敏的用户信息
*
* @param user
* @return
*/
UserVO getUserVO(User user);
/**
* 获取脱敏的用户信息
*
* @param userList
* @return
*/
List<UserVO> getUserVO(List<User> userList);
/**
* 获取查询条件
*
* @param userQueryRequest
* @return
*/
QueryWrapper<User> getQueryWrapper(UserQueryRequest userQueryRequest);
}

View File

@ -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<ChartMapper, Chart>
implements ChartService{
/**
* 获取查询条件
*
* @param chartQueryRequest@return
*/
@Override
public QueryWrapper<Chart> getQueryWrapper(ChartQueryRequest chartQueryRequest) {
QueryWrapper<Chart> 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;
}
}

View File

@ -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<PostFavourMapper, PostFavour>
implements PostFavourService {
@Resource
private PostService postService;
/**
* 帖子收藏
*
* @param postId
* @param loginUser
* @return
*/
@Override
public int doPostFavour(long postId, User loginUser) {
// 判断是否存在
Post post = postService.getById(postId);
if (post == null) {
throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);
}
// 是否已帖子收藏
long userId = loginUser.getId();
// 每个用户串行帖子收藏
// 锁必须要包裹住事务方法
PostFavourService postFavourService = (PostFavourService) AopContext.currentProxy();
synchronized (String.valueOf(userId).intern()) {
return postFavourService.doPostFavourInner(userId, postId);
}
}
@Override
public Page<Post> listFavourPostByPage(IPage<Post> page, Wrapper<Post> queryWrapper, long favourUserId) {
if (favourUserId <= 0) {
return new Page<>();
}
return baseMapper.listFavourPostByPage(page, queryWrapper, favourUserId);
}
/**
* 封装了事务的方法
*
* @param userId
* @param postId
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int doPostFavourInner(long userId, long postId) {
PostFavour postFavour = new PostFavour();
postFavour.setUserId(userId);
postFavour.setPostId(postId);
QueryWrapper<PostFavour> postFavourQueryWrapper = new QueryWrapper<>(postFavour);
PostFavour oldPostFavour = this.getOne(postFavourQueryWrapper);
boolean result;
// 已收藏
if (oldPostFavour != null) {
result = this.remove(postFavourQueryWrapper);
if (result) {
// 帖子收藏数 - 1
result = postService.update()
.eq("id", postId)
.gt("favourNum", 0)
.setSql("favourNum = favourNum - 1")
.update();
return result ? -1 : 0;
} else {
throw new BusinessException(ErrorCode.SYSTEM_ERROR);
}
} else {
// 未帖子收藏
result = this.save(postFavour);
if (result) {
// 帖子收藏数 + 1
result = postService.update()
.eq("id", postId)
.setSql("favourNum = favourNum + 1")
.update();
return result ? 1 : 0;
} else {
throw new BusinessException(ErrorCode.SYSTEM_ERROR);
}
}
}
}

View File

@ -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<PostMapper, Post> 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<Post> getQueryWrapper(PostQueryRequest postQueryRequest) {
QueryWrapper<Post> queryWrapper = new QueryWrapper<>();
if (postQueryRequest == null) {
return queryWrapper;
}
String searchText = postQueryRequest.getSearchText();
String sortField = postQueryRequest.getSortField();
String sortOrder = postQueryRequest.getSortOrder();
Long id = postQueryRequest.getId();
String title = postQueryRequest.getTitle();
String content = postQueryRequest.getContent();
List<String> tagList = postQueryRequest.getTags();
Long userId = postQueryRequest.getUserId();
Long notId = postQueryRequest.getNotId();
// 拼接查询条件
if (StringUtils.isNotBlank(searchText)) {
queryWrapper.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<Post> searchFromEs(PostQueryRequest postQueryRequest) {
Long id = postQueryRequest.getId();
Long notId = postQueryRequest.getNotId();
String searchText = postQueryRequest.getSearchText();
String title = postQueryRequest.getTitle();
String content = postQueryRequest.getContent();
List<String> tagList = postQueryRequest.getTags();
List<String> orTagList = postQueryRequest.getOrTags();
Long userId = postQueryRequest.getUserId();
// es 起始页为 0
long current = postQueryRequest.getCurrent() - 1;
long pageSize = postQueryRequest.getPageSize();
String sortField = postQueryRequest.getSortField();
String sortOrder = postQueryRequest.getSortOrder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
// 过滤
boolQueryBuilder.filter(QueryBuilders.termQuery("isDelete", 0));
if (id != null) {
boolQueryBuilder.filter(QueryBuilders.termQuery("id", id));
}
if (notId != null) {
boolQueryBuilder.mustNot(QueryBuilders.termQuery("id", notId));
}
if (userId != null) {
boolQueryBuilder.filter(QueryBuilders.termQuery("userId", userId));
}
// 必须包含所有标签
if (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<PostEsDTO> searchHits = elasticsearchRestTemplate.search(searchQuery, PostEsDTO.class);
Page<Post> page = new Page<>();
page.setTotal(searchHits.getTotalHits());
List<Post> resourceList = new ArrayList<>();
// 查出结果后 db 获取最新动态数据比如点赞数
if (searchHits.hasSearchHits()) {
List<SearchHit<PostEsDTO>> searchHitList = searchHits.getSearchHits();
List<Long> postIdList = searchHitList.stream().map(searchHit -> searchHit.getContent().getId())
.collect(Collectors.toList());
List<Post> postList = baseMapper.selectBatchIds(postIdList);
if (postList != null) {
Map<Long, List<Post>> idPostMap = postList.stream().collect(Collectors.groupingBy(Post::getId));
postIdList.forEach(postId -> {
if (idPostMap.containsKey(postId)) {
resourceList.add(idPostMap.get(postId).get(0));
} else {
// es 清空 db 已物理删除的数据
String delete = elasticsearchRestTemplate.delete(String.valueOf(postId), PostEsDTO.class);
log.info("delete post {}", delete);
}
});
}
}
page.setRecords(resourceList);
return page;
}
@Override
public PostVO getPostVO(Post post, HttpServletRequest request) {
PostVO postVO = PostVO.objToVo(post);
long postId = post.getId();
// 1. 关联查询用户信息
Long userId = post.getUserId();
User user = null;
if (userId != null && userId > 0) {
user = userService.getById(userId);
}
UserVO userVO = userService.getUserVO(user);
postVO.setUser(userVO);
// 2. 已登录获取用户点赞收藏状态
User loginUser = userService.getLoginUserPermitNull(request);
if (loginUser != null) {
// 获取点赞
QueryWrapper<PostThumb> postThumbQueryWrapper = new QueryWrapper<>();
postThumbQueryWrapper.in("postId", postId);
postThumbQueryWrapper.eq("userId", loginUser.getId());
PostThumb postThumb = postThumbMapper.selectOne(postThumbQueryWrapper);
postVO.setHasThumb(postThumb != null);
// 获取收藏
QueryWrapper<PostFavour> postFavourQueryWrapper = new QueryWrapper<>();
postFavourQueryWrapper.in("postId", postId);
postFavourQueryWrapper.eq("userId", loginUser.getId());
PostFavour postFavour = postFavourMapper.selectOne(postFavourQueryWrapper);
postVO.setHasFavour(postFavour != null);
}
return postVO;
}
@Override
public Page<PostVO> getPostVOPage(Page<Post> postPage, HttpServletRequest request) {
List<Post> postList = postPage.getRecords();
Page<PostVO> postVOPage = new Page<>(postPage.getCurrent(), postPage.getSize(), postPage.getTotal());
if (CollectionUtils.isEmpty(postList)) {
return postVOPage;
}
// 1. 关联查询用户信息
Set<Long> userIdSet = postList.stream().map(Post::getUserId).collect(Collectors.toSet());
Map<Long, List<User>> userIdUserListMap = userService.listByIds(userIdSet).stream()
.collect(Collectors.groupingBy(User::getId));
// 2. 已登录获取用户点赞收藏状态
Map<Long, Boolean> postIdHasThumbMap = new HashMap<>();
Map<Long, Boolean> postIdHasFavourMap = new HashMap<>();
User loginUser = userService.getLoginUserPermitNull(request);
if (loginUser != null) {
Set<Long> postIdSet = postList.stream().map(Post::getId).collect(Collectors.toSet());
loginUser = userService.getLoginUser(request);
// 获取点赞
QueryWrapper<PostThumb> postThumbQueryWrapper = new QueryWrapper<>();
postThumbQueryWrapper.in("postId", postIdSet);
postThumbQueryWrapper.eq("userId", loginUser.getId());
List<PostThumb> postPostThumbList = postThumbMapper.selectList(postThumbQueryWrapper);
postPostThumbList.forEach(postPostThumb -> postIdHasThumbMap.put(postPostThumb.getPostId(), true));
// 获取收藏
QueryWrapper<PostFavour> postFavourQueryWrapper = new QueryWrapper<>();
postFavourQueryWrapper.in("postId", postIdSet);
postFavourQueryWrapper.eq("userId", loginUser.getId());
List<PostFavour> postFavourList = postFavourMapper.selectList(postFavourQueryWrapper);
postFavourList.forEach(postFavour -> postIdHasFavourMap.put(postFavour.getPostId(), true));
}
// 填充信息
List<PostVO> postVOList = postList.stream().map(post -> {
PostVO postVO = PostVO.objToVo(post);
Long userId = post.getUserId();
User user = null;
if (userIdUserListMap.containsKey(userId)) {
user = userIdUserListMap.get(userId).get(0);
}
postVO.setUser(userService.getUserVO(user));
postVO.setHasThumb(postIdHasThumbMap.getOrDefault(post.getId(), false));
postVO.setHasFavour(postIdHasFavourMap.getOrDefault(post.getId(), false));
return postVO;
}).collect(Collectors.toList());
postVOPage.setRecords(postVOList);
return postVOPage;
}
}

View File

@ -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<PostThumbMapper, PostThumb>
implements PostThumbService {
@Resource
private PostService postService;
/**
* 点赞
*
* @param postId
* @param loginUser
* @return
*/
@Override
public int doPostThumb(long postId, User loginUser) {
// 判断实体是否存在根据类别获取实体
Post post = postService.getById(postId);
if (post == null) {
throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);
}
// 是否已点赞
long userId = loginUser.getId();
// 每个用户串行点赞
// 锁必须要包裹住事务方法
PostThumbService postThumbService = (PostThumbService) AopContext.currentProxy();
synchronized (String.valueOf(userId).intern()) {
return postThumbService.doPostThumbInner(userId, postId);
}
}
/**
* 封装了事务的方法
*
* @param userId
* @param postId
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int doPostThumbInner(long userId, long postId) {
PostThumb postThumb = new PostThumb();
postThumb.setUserId(userId);
postThumb.setPostId(postId);
QueryWrapper<PostThumb> thumbQueryWrapper = new QueryWrapper<>(postThumb);
PostThumb oldPostThumb = this.getOne(thumbQueryWrapper);
boolean result;
// 已点赞
if (oldPostThumb != null) {
result = this.remove(thumbQueryWrapper);
if (result) {
// 点赞数 - 1
result = postService.update()
.eq("id", postId)
.gt("thumbNum", 0)
.setSql("thumbNum = thumbNum - 1")
.update();
return result ? -1 : 0;
} else {
throw new BusinessException(ErrorCode.SYSTEM_ERROR);
}
} else {
// 未点赞
result = this.save(postThumb);
if (result) {
// 点赞数 + 1
result = postService.update()
.eq("id", postId)
.setSql("thumbNum = thumbNum + 1")
.update();
return result ? 1 : 0;
} else {
throw new BusinessException(ErrorCode.SYSTEM_ERROR);
}
}
}
}

View File

@ -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<UserMapper, User> 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<User> 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<User> 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<UserVO> getUserVO(List<User> userList) {
if (CollectionUtils.isEmpty(userList)) {
return new ArrayList<>();
}
return userList.stream().map(this::getUserVO).collect(Collectors.toList());
}
@Override
public QueryWrapper<User> getQueryWrapper(UserQueryRequest userQueryRequest) {
if (userQueryRequest == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空");
}
Long id = userQueryRequest.getId();
String unionId = userQueryRequest.getUnionId();
String mpOpenId = userQueryRequest.getMpOpenId();
String userName = userQueryRequest.getUserName();
String userProfile = userQueryRequest.getUserProfile();
String userRole = userQueryRequest.getUserRole();
String sortField = userQueryRequest.getSortField();
String sortOrder = userQueryRequest.getSortOrder();
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq(id != null, "id", id);
queryWrapper.eq(StringUtils.isNotBlank(unionId), "unionId", unionId);
queryWrapper.eq(StringUtils.isNotBlank(mpOpenId), "mpOpenId", mpOpenId);
queryWrapper.eq(StringUtils.isNotBlank(userRole), "userRole", userRole);
queryWrapper.like(StringUtils.isNotBlank(userProfile), "userProfile", userProfile);
queryWrapper.like(StringUtils.isNotBlank(userName), "userName", userName);
queryWrapper.orderBy(SqlUtils.validSortField(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC),
sortField);
return queryWrapper;
}
}

View File

@ -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;
}
}

View File

@ -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 <T>
* @return
*/
public static <T> T getBean(Class<T> beanClass) {
return applicationContext.getBean(beanClass);
}
/**
* 通过名称和类型获取 Bean
*
* @param beanName
* @param beanClass
* @param <T>
* @return
*/
public static <T> T getBean(String beanName, Class<T> beanClass) {
return applicationContext.getBean(beanName, beanClass);
}
}

View File

@ -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, "=", "(", ")", " ");
}
}

View File

@ -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."
}
]
}

View File

@ -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

View File

@ -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: ''

View File

@ -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

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="top.peng.springbootinit.mapper.ChartMapper">
<resultMap id="BaseResultMap" type="top.peng.answerbi.model.entity.Chart">
<id property="id" column="id" jdbcType="BIGINT"/>
<result property="created_time" column="created_time" jdbcType="TIMESTAMP"/>
<result property="updated_time" column="updated_time" jdbcType="TIMESTAMP"/>
<result property="deleted_flag" column="deleted_flag" jdbcType="TINYINT"/>
<result property="user_id" column="user_id" jdbcType="BIGINT"/>
<result property="goal" column="goal" jdbcType="VARCHAR"/>
<result property="chart_data" column="chart_data" jdbcType="VARCHAR"/>
<result property="chart_type" column="chart_type" jdbcType="VARCHAR"/>
<result property="gen_chart" column="gen_chart" jdbcType="VARCHAR"/>
<result property="gen_result" column="gen_result" jdbcType="VARCHAR"/>
</resultMap>
<sql id="Base_Column_List">
id,created_time,updated_time,
deleted_flag,user_id,goal,
chart_data,chart_type,gen_chart,
gen_result
</sql>
</mapper>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="top.peng.springbootinit.mapper.PostFavourMapper">
<resultMap id="BaseResultMap" type="top.peng.answerbi.model.entity.PostFavour">
<id property="id" column="id" jdbcType="BIGINT"/>
<result property="postId" column="postId" jdbcType="BIGINT"/>
<result property="userId" column="userId" jdbcType="BIGINT"/>
<result property="createTime" column="createTime" jdbcType="TIMESTAMP"/>
<result property="updateTime" column="updateTime" jdbcType="TIMESTAMP"/>
</resultMap>
<sql id="Base_Column_List">
id,postId,userId,
createTime,updateTime
</sql>
<select id="listFavourPostByPage"
resultType="top.peng.answerbi.model.entity.Post">
select p.*
from post p
join (select postId from post_favour where userId = #{favourUserId}) pf
on p.id = pf.postId ${ew.customSqlSegment}
</select>
</mapper>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="top.peng.springbootinit.mapper.PostMapper">
<resultMap id="BaseResultMap" type="top.peng.answerbi.model.entity.Post">
<id property="id" column="id" jdbcType="BIGINT"/>
<result property="title" column="title" jdbcType="VARCHAR"/>
<result property="content" column="content" jdbcType="VARCHAR"/>
<result property="tags" column="tags" jdbcType="VARCHAR"/>
<result property="thumbNum" column="thumbNum" jdbcType="BIGINT"/>
<result property="favourNum" column="favourNum" jdbcType="BIGINT"/>
<result property="userId" column="userId" jdbcType="BIGINT"/>
<result property="createTime" column="createTime" jdbcType="TIMESTAMP"/>
<result property="updateTime" column="updateTime" jdbcType="TIMESTAMP"/>
<result property="isDelete" column="isDelete" jdbcType="TINYINT"/>
</resultMap>
<sql id="Base_Column_List">
id,title,content,tags,
thumbNum,favourNum,userId,
createTime,updateTime,isDelete
</sql>
<select id="listPostWithDelete" resultType="top.peng.answerbi.model.entity.Post">
select *
from post
where updateTime >= #{minUpdateTime}
</select>
</mapper>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="top.peng.springbootinit.mapper.PostThumbMapper">
<resultMap id="BaseResultMap" type="top.peng.answerbi.model.entity.PostThumb">
<id property="id" column="id" jdbcType="BIGINT"/>
<result property="postId" column="postId" jdbcType="BIGINT"/>
<result property="userId" column="userId" jdbcType="BIGINT"/>
<result property="createTime" column="createTime" jdbcType="TIMESTAMP"/>
<result property="updateTime" column="updateTime" jdbcType="TIMESTAMP"/>
</resultMap>
<sql id="Base_Column_List">
id,postId,
userId,createTime,updateTime
</sql>
</mapper>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="top.peng.springbootinit.mapper.UserMapper">
<resultMap id="BaseResultMap" type="top.peng.answerbi.model.entity.User">
<id property="id" column="id" jdbcType="BIGINT"/>
<result property="created_time" column="created_time" jdbcType="TIMESTAMP"/>
<result property="updated_time" column="updated_time" jdbcType="TIMESTAMP"/>
<result property="deleted_flag" column="deleted_flag" jdbcType="TINYINT"/>
<result property="user_account" column="user_account" jdbcType="VARCHAR"/>
<result property="user_password" column="user_password" jdbcType="VARCHAR"/>
<result property="username" column="username" jdbcType="VARCHAR"/>
<result property="user_avatar" column="user_avatar" jdbcType="VARCHAR"/>
<result property="user_role" column="user_role" jdbcType="VARCHAR"/>
</resultMap>
<sql id="Base_Column_List">
id,created_time,updated_time,
deleted_flag,user_account,user_password,
username,user_avatar,user_role
</sql>
</mapper>

Binary file not shown.

View File

@ -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);
}
}

View File

@ -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<Post> page =
postService.searchFromEs(postQueryRequest);
System.out.println(page);
}
@Test
void testSelect() {
System.out.println(postEsDao.count());
Page<PostEsDTO> PostPage = postEsDao.findAll(
PageRequest.of(0, 5, Sort.by("createTime")));
List<PostEsDTO> 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> postEsDTO = postEsDao.findById(1L);
System.out.println(postEsDTO);
}
@Test
void testCount() {
System.out.println(postEsDao.count());
}
@Test
void testFindByCategory() {
List<PostEsDTO> postEsDaoTestList = postEsDao.findByUserId(1L);
System.out.println(postEsDaoTestList);
}
}

View File

@ -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");
}
}

View File

@ -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<Post> page = new Page<>(2, 1);
QueryWrapper<Post> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("id", 1);
queryWrapper.like("content", "a");
IPage<Post> result = postFavourMapper.listFavourPostByPage(page, queryWrapper, 1);
Assertions.assertNotNull(result);
}
}

View File

@ -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<Post> postList = postMapper.listPostWithDelete(new Date());
Assertions.assertNotNull(postList);
}
}

View File

@ -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<Post> postQueryWrapper = new QueryWrapper<>();
postQueryWrapper.eq("id", 1L);
postFavourService.listFavourPostByPage(Page.of(0, 1), postQueryWrapper, loginUser.getId());
}
}

View File

@ -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<Post> postPage = postService.searchFromEs(postQueryRequest);
Assertions.assertNotNull(postPage);
}
}

Some files were not shown because too many files have changed in this diff Show More