commit 9bbb491633328ee6258ca521cc7ddc07e36b93bc Author: Ziin Date: Wed Jun 11 14:45:40 2025 +0800 消息队列存储数据 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..85544b3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,147 @@ +### @author 程序员鱼皮 ### +### @from 编程导航知识星球 ### + +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ +### Java template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### Maven template +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +# https://github.com/takari/maven-wrapper#usage-without-binary-jar +.mvn/wrapper/maven-wrapper.jar + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + diff --git a/.xcodemap/config/xcodemap-class-filter.yaml b/.xcodemap/config/xcodemap-class-filter.yaml new file mode 100644 index 0000000..1559fe0 --- /dev/null +++ b/.xcodemap/config/xcodemap-class-filter.yaml @@ -0,0 +1,23 @@ +autoDetectedPackages: +- com.yupi.springbootinit +enableAutoDetect: true +entryDisplayConfig: + excludedPathPatterns: [] + skipJsCss: true +funcDisplayConfig: + skipConstructors: false + skipFieldAccess: true + skipFieldChange: true + skipGetters: false + skipNonProjectPackages: false + skipPrivateMethods: false + skipSetters: false +ignoreSameClassCall: null +ignoreSamePackageCall: null +includedPackagePrefixes: null +includedParentClasses: null +name: xcodemap-filter +recordMode: all +sourceDisplayConfig: + color: blue +startOnDebug: false diff --git a/doc/swagger.png b/doc/swagger.png new file mode 100644 index 0000000..0518706 Binary files /dev/null and b/doc/swagger.png differ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..cd74848 --- /dev/null +++ b/pom.xml @@ -0,0 +1,152 @@ + + + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.2 + + + com.yupi + springboot-init + 0.0.1-SNAPSHOT + springboot-init + + 1.8 + + + + org.springframework.boot + spring-boot-starter-freemarker + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-aop + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 2.2.2 + + + + com.baomidou + mybatis-plus-boot-starter + 3.5.2 + + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.springframework.session + spring-session-data-redis + + + + org.springframework.boot + spring-boot-starter-data-elasticsearch + + + + com.github.binarywang + wx-java-mp-spring-boot-starter + 4.4.0 + + + + com.github.xiaoymin + knife4j-openapi2-spring-boot-starter + 4.4.0 + + + + com.qcloud + cos_api + 5.6.89 + + + + org.apache.commons + commons-lang3 + + + + com.alibaba + easyexcel + 3.1.1 + + + + cn.hutool + hutool-all + 5.8.8 + + + + org.springframework.boot + spring-boot-starter-amqp + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + mysql + mysql-connector-java + runtime + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 11 + 11 + + + + + + diff --git a/sql/create_table.sql b/sql/create_table.sql new file mode 100644 index 0000000..04e0e91 --- /dev/null +++ b/sql/create_table.sql @@ -0,0 +1,67 @@ +# 数据库初始化 +# @author 程序员鱼皮 +# @from 编程导航知识星球 + +-- 创建库 +create database if not exists my_db; + +-- 切换库 +use my_db; + +-- 用户表 +create table if not exists user +( + id bigint auto_increment comment 'id' primary key, + userAccount varchar(256) not null comment '账号', + userPassword varchar(512) not null comment '密码', + unionId varchar(256) null comment '微信开放平台id', + mpOpenId varchar(256) null comment '公众号openId', + userName varchar(256) null comment '用户昵称', + userAvatar varchar(1024) null comment '用户头像', + userProfile varchar(512) null comment '用户简介', + userRole varchar(256) default 'user' not null comment '用户角色:user/admin/ban', + createTime datetime default CURRENT_TIMESTAMP not null comment '创建时间', + updateTime datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间', + isDelete tinyint default 0 not null comment '是否删除', + index idx_unionId (unionId) +) comment '用户' collate = utf8mb4_unicode_ci; + +-- 帖子表 +create table if not exists post +( + id bigint auto_increment comment 'id' primary key, + title varchar(512) null comment '标题', + content text null comment '内容', + tags varchar(1024) null comment '标签列表(json 数组)', + thumbNum int default 0 not null comment '点赞数', + favourNum int default 0 not null comment '收藏数', + userId bigint not null comment '创建用户 id', + createTime datetime default CURRENT_TIMESTAMP not null comment '创建时间', + updateTime datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间', + isDelete tinyint default 0 not null comment '是否删除', + index idx_userId (userId) +) comment '帖子' collate = utf8mb4_unicode_ci; + +-- 帖子点赞表(硬删除) +create table if not exists post_thumb +( + id bigint auto_increment comment 'id' primary key, + postId bigint not null comment '帖子 id', + userId bigint not null comment '创建用户 id', + createTime datetime default CURRENT_TIMESTAMP not null comment '创建时间', + updateTime datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间', + index idx_postId (postId), + index idx_userId (userId) +) comment '帖子点赞'; + +-- 帖子收藏表(硬删除) +create table if not exists post_favour +( + id bigint auto_increment comment 'id' primary key, + postId bigint not null comment '帖子 id', + userId bigint not null comment '创建用户 id', + createTime datetime default CURRENT_TIMESTAMP not null comment '创建时间', + updateTime datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间', + index idx_postId (postId), + index idx_userId (userId) +) comment '帖子收藏'; diff --git a/sql/post_es_mapping.json b/sql/post_es_mapping.json new file mode 100644 index 0000000..655272d --- /dev/null +++ b/sql/post_es_mapping.json @@ -0,0 +1,52 @@ +{ + "aliases": { + "post": {} + }, + "mappings": { + "properties": { + "title": { + "type": "text", + "analyzer": "ik_max_word", + "search_analyzer": "ik_smart", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "content": { + "type": "text", + "analyzer": "ik_max_word", + "search_analyzer": "ik_smart", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "tags": { + "type": "keyword" + }, + "thumbNum": { + "type": "long" + }, + "favourNum": { + "type": "long" + }, + "userId": { + "type": "keyword" + }, + "createTime": { + "type": "date" + }, + "updateTime": { + "type": "date" + }, + "isDelete": { + "type": "keyword" + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/MainApplication.java b/src/main/java/com/yupi/springbootinit/MainApplication.java new file mode 100644 index 0000000..2090350 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/MainApplication.java @@ -0,0 +1,29 @@ +package com.yupi.springbootinit; + +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; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +/** + * 主类(项目启动入口) + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +// todo 如需开启 Redis,须移除 exclude 中的内容 +@SpringBootApplication(exclude = {RedisAutoConfiguration.class}) +@MapperScan("com.yupi.springbootinit.mapper") +@EnableScheduling +@EnableTransactionManagement +@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true) +public class MainApplication { + + public static void main(String[] args) { + SpringApplication.run(MainApplication.class, args); + } + +} diff --git a/src/main/java/com/yupi/springbootinit/annotation/AuthCheck.java b/src/main/java/com/yupi/springbootinit/annotation/AuthCheck.java new file mode 100644 index 0000000..b06c232 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/annotation/AuthCheck.java @@ -0,0 +1,26 @@ +package com.yupi.springbootinit.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 权限校验 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface AuthCheck { + + /** + * 必须有某个角色 + * + * @return + */ + String mustRole() default ""; + +} + diff --git a/src/main/java/com/yupi/springbootinit/aop/AuthInterceptor.java b/src/main/java/com/yupi/springbootinit/aop/AuthInterceptor.java new file mode 100644 index 0000000..adc3673 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/aop/AuthInterceptor.java @@ -0,0 +1,72 @@ +package com.yupi.springbootinit.aop; + +import com.yupi.springbootinit.annotation.AuthCheck; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.exception.BusinessException; +import com.yupi.springbootinit.model.entity.User; +import com.yupi.springbootinit.model.enums.UserRoleEnum; +import com.yupi.springbootinit.service.UserService; +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; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; + +/** + * 权限校验 AOP + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@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); + UserRoleEnum mustRoleEnum = UserRoleEnum.getEnumByValue(mustRole); + // 不需要权限,放行 + if (mustRoleEnum == null) { + return joinPoint.proceed(); + } + // 必须有该权限才通过 + UserRoleEnum userRoleEnum = UserRoleEnum.getEnumByValue(loginUser.getUserRole()); + if (userRoleEnum == null) { + throw new BusinessException(ErrorCode.NO_AUTH_ERROR); + } + // 如果被封号,直接拒绝 + if (UserRoleEnum.BAN.equals(userRoleEnum)) { + throw new BusinessException(ErrorCode.NO_AUTH_ERROR); + } + // 必须有管理员权限 + if (UserRoleEnum.ADMIN.equals(mustRoleEnum)) { + // 用户没有管理员权限,拒绝 + if (!UserRoleEnum.ADMIN.equals(userRoleEnum)) { + throw new BusinessException(ErrorCode.NO_AUTH_ERROR); + } + } + // 通过权限校验,放行 + return joinPoint.proceed(); + } +} + diff --git a/src/main/java/com/yupi/springbootinit/aop/LogInterceptor.java b/src/main/java/com/yupi/springbootinit/aop/LogInterceptor.java new file mode 100644 index 0000000..5b804b9 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/aop/LogInterceptor.java @@ -0,0 +1,56 @@ +package com.yupi.springbootinit.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 程序员鱼皮 + * @from 编程导航知识星球 + **/ +@Aspect +@Component +@Slf4j +public class LogInterceptor { + + /** + * 执行拦截 + */ + @Around("execution(* com.yupi.springbootinit.controller.*.*(..))") + public Object doInterceptor(ProceedingJoinPoint point) throws Throwable { + // 计时 + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + // 获取请求路径 + RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes(); + HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest(); + // 生成请求唯一 id + String requestId = UUID.randomUUID().toString(); + String url = httpServletRequest.getRequestURI(); + // 获取请求参数 + Object[] args = point.getArgs(); + String reqParam = "[" + StringUtils.join(args, ", ") + "]"; + // 输出请求日志 + log.info("request start,id: {}, path: {}, ip: {}, params: {}", requestId, url, + httpServletRequest.getRemoteHost(), reqParam); + // 执行原方法 + Object result = point.proceed(); + // 输出响应日志 + stopWatch.stop(); + long totalTimeMillis = stopWatch.getTotalTimeMillis(); + log.info("request end, id: {}, cost: {}ms", requestId, totalTimeMillis); + return result; + } +} + diff --git a/src/main/java/com/yupi/springbootinit/common/BaseResponse.java b/src/main/java/com/yupi/springbootinit/common/BaseResponse.java new file mode 100644 index 0000000..c1ca661 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/common/BaseResponse.java @@ -0,0 +1,35 @@ +package com.yupi.springbootinit.common; + +import java.io.Serializable; +import lombok.Data; + +/** + * 通用返回类 + * + * @param + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class BaseResponse implements Serializable { + + private int code; + + private T data; + + private String message; + + public BaseResponse(int code, T data, String message) { + this.code = code; + this.data = data; + this.message = message; + } + + public BaseResponse(int code, T data) { + this(code, data, ""); + } + + public BaseResponse(ErrorCode errorCode) { + this(errorCode.getCode(), null, errorCode.getMessage()); + } +} diff --git a/src/main/java/com/yupi/springbootinit/common/DeleteRequest.java b/src/main/java/com/yupi/springbootinit/common/DeleteRequest.java new file mode 100644 index 0000000..0109023 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/common/DeleteRequest.java @@ -0,0 +1,21 @@ +package com.yupi.springbootinit.common; + +import java.io.Serializable; +import lombok.Data; + +/** + * 删除请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class DeleteRequest implements Serializable { + + /** + * id + */ + private Long id; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/common/ErrorCode.java b/src/main/java/com/yupi/springbootinit/common/ErrorCode.java new file mode 100644 index 0000000..c60faad --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/common/ErrorCode.java @@ -0,0 +1,45 @@ +package com.yupi.springbootinit.common; + +/** + * 自定义错误码 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +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, "操作失败"), + QUEUE_ERROR(60001, "队列消息添加失败"), + QUEUE_CONSUMPTION_FAILURE(60001, "队列消息消费失败"); + + /** + * 状态码 + */ + private final int code; + + /** + * 信息 + */ + private final String message; + + ErrorCode(int code, String message) { + this.code = code; + this.message = message; + } + + public int getCode() { + return code; + } + + public String getMessage() { + return message; + } + +} diff --git a/src/main/java/com/yupi/springbootinit/common/PageRequest.java b/src/main/java/com/yupi/springbootinit/common/PageRequest.java new file mode 100644 index 0000000..f17de55 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/common/PageRequest.java @@ -0,0 +1,34 @@ +package com.yupi.springbootinit.common; + +import com.yupi.springbootinit.constant.CommonConstant; +import lombok.Data; + +/** + * 分页请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class PageRequest { + + /** + * 当前页号 + */ + private int current = 1; + + /** + * 页面大小 + */ + private int pageSize = 10; + + /** + * 排序字段 + */ + private String sortField; + + /** + * 排序顺序(默认升序) + */ + private String sortOrder = CommonConstant.SORT_ORDER_ASC; +} diff --git a/src/main/java/com/yupi/springbootinit/common/ResultUtils.java b/src/main/java/com/yupi/springbootinit/common/ResultUtils.java new file mode 100644 index 0000000..5544068 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/common/ResultUtils.java @@ -0,0 +1,52 @@ +package com.yupi.springbootinit.common; + +/** + * 返回工具类 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public class ResultUtils { + + /** + * 成功 + * + * @param data + * @param + * @return + */ + public static BaseResponse success(T data) { + return new BaseResponse<>(0, data, "ok"); + } + + /** + * 失败 + * + * @param errorCode + * @return + */ + public static BaseResponse error(ErrorCode errorCode) { + return new BaseResponse<>(errorCode); + } + + /** + * 失败 + * + * @param code + * @param message + * @return + */ + public static BaseResponse error(int code, String message) { + return new BaseResponse(code, null, message); + } + + /** + * 失败 + * + * @param errorCode + * @return + */ + public static BaseResponse error(ErrorCode errorCode, String message) { + return new BaseResponse(errorCode.getCode(), null, message); + } +} diff --git a/src/main/java/com/yupi/springbootinit/config/AsyncConfig.java b/src/main/java/com/yupi/springbootinit/config/AsyncConfig.java new file mode 100644 index 0000000..3314fca --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/config/AsyncConfig.java @@ -0,0 +1,23 @@ +package com.yupi.springbootinit.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; + +@Configuration +@EnableAsync +public class AsyncConfig { +@Bean(name = "taskExecutor") +public Executor taskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(4); // 核心线程数 + executor.setMaxPoolSize(8); // 最大线程数 + executor.setQueueCapacity(100); // 队列容量 + executor.setThreadNamePrefix("AsyncExecutor-"); + executor.initialize(); + return executor; + } +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/config/CorsConfig.java b/src/main/java/com/yupi/springbootinit/config/CorsConfig.java new file mode 100644 index 0000000..23184b5 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/config/CorsConfig.java @@ -0,0 +1,28 @@ +package com.yupi.springbootinit.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * 全局跨域配置 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Configuration +public class CorsConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + // 覆盖所有请求 + registry.addMapping("/**") + // 允许发送 Cookie + .allowCredentials(true) + // 放行哪些域名(必须用 patterns,否则 * 会和 allowCredentials 冲突) + .allowedOriginPatterns("*") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .exposedHeaders("*"); + } +} diff --git a/src/main/java/com/yupi/springbootinit/config/CosClientConfig.java b/src/main/java/com/yupi/springbootinit/config/CosClientConfig.java new file mode 100644 index 0000000..f01b26a --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/config/CosClientConfig.java @@ -0,0 +1,53 @@ +package com.yupi.springbootinit.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 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Configuration +@ConfigurationProperties(prefix = "cos.client") +@Data +public class CosClientConfig { + + /** + * accessKey + */ + private String accessKey; + + /** + * secretKey + */ + private String secretKey; + + /** + * 区域 + */ + private String region; + + /** + * 桶名 + */ + private String bucket; + + @Bean + public COSClient cosClient() { + // 初始化用户身份信息(secretId, secretKey) + COSCredentials cred = new BasicCOSCredentials(accessKey, secretKey); + // 设置bucket的区域, COS地域的简称请参照 https://www.qcloud.com/document/product/436/6224 + ClientConfig clientConfig = new ClientConfig(new Region(region)); + // 生成cos客户端 + return new COSClient(cred, clientConfig); + } +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/config/JsonConfig.java b/src/main/java/com/yupi/springbootinit/config/JsonConfig.java new file mode 100644 index 0000000..86ad8a8 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/config/JsonConfig.java @@ -0,0 +1,31 @@ +package com.yupi.springbootinit.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 程序员鱼皮 + * @from 编程导航知识星球 + */ +@JsonComponent +public class JsonConfig { + + /** + * 添加 Long 转 json 精度丢失的配置 + */ + @Bean + public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { + ObjectMapper objectMapper = builder.createXmlMapper(false).build(); + SimpleModule module = new SimpleModule(); + module.addSerializer(Long.class, ToStringSerializer.instance); + module.addSerializer(Long.TYPE, ToStringSerializer.instance); + objectMapper.registerModule(module); + return objectMapper; + } +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/config/MyBatisPlusConfig.java b/src/main/java/com/yupi/springbootinit/config/MyBatisPlusConfig.java new file mode 100644 index 0000000..a5f3b9b --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/config/MyBatisPlusConfig.java @@ -0,0 +1,31 @@ +package com.yupi.springbootinit.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 https://github.com/liyupi + */ +@Configuration +@MapperScan("com.yupi.springbootinit.mapper") +public class MyBatisPlusConfig { + + /** + * 拦截器配置 + * + * @return + */ + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + // 分页插件 + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + return interceptor; + } +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/config/RabbitMQConfig.java b/src/main/java/com/yupi/springbootinit/config/RabbitMQConfig.java new file mode 100644 index 0000000..4837a08 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/config/RabbitMQConfig.java @@ -0,0 +1,30 @@ +package com.yupi.springbootinit.config; + +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.Resource; + +@Configuration +public class RabbitMQConfig { + private static final String QUEUE = "HOST_INFO_QUEUE"; + + //创建队列 + //true:表示持久化 + //队列在默认情况下放到内存,rabbitmq重启后就丢失了,如果希望重启后,队列 + //数据还能使用,就需要持久化 + @Bean + public Queue hostInfoQueue(){ + return new Queue(QUEUE,true); + } + @Bean + public MessageConverter messageConverter(){ + return new Jackson2JsonMessageConverter(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/config/WxOpenConfig.java b/src/main/java/com/yupi/springbootinit/config/WxOpenConfig.java new file mode 100644 index 0000000..b0d0dc1 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/config/WxOpenConfig.java @@ -0,0 +1,51 @@ +package com.yupi.springbootinit.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 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Slf4j +@Configuration +@ConfigurationProperties(prefix = "wx.open") +@Data +public class WxOpenConfig { + + private String appId; + + private String appSecret; + + private WxMpService wxMpService; + + /** + * 单例模式(不用 @Bean 是为了防止和公众号的 service 冲突) + * + * @return + */ + public WxMpService getWxMpService() { + if (wxMpService != null) { + return wxMpService; + } + synchronized (this) { + if (wxMpService != null) { + return wxMpService; + } + WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl(); + config.setAppId(appId); + config.setSecret(appSecret); + WxMpService service = new WxMpServiceImpl(); + service.setWxMpConfigStorage(config); + wxMpService = service; + return wxMpService; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/constant/CommonConstant.java b/src/main/java/com/yupi/springbootinit/constant/CommonConstant.java new file mode 100644 index 0000000..d7a1a54 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/constant/CommonConstant.java @@ -0,0 +1,21 @@ +package com.yupi.springbootinit.constant; + +/** + * 通用常量 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public interface CommonConstant { + + /** + * 升序 + */ + String SORT_ORDER_ASC = "ascend"; + + /** + * 降序 + */ + String SORT_ORDER_DESC = " descend"; + +} diff --git a/src/main/java/com/yupi/springbootinit/constant/FileConstant.java b/src/main/java/com/yupi/springbootinit/constant/FileConstant.java new file mode 100644 index 0000000..8781d56 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/constant/FileConstant.java @@ -0,0 +1,16 @@ +package com.yupi.springbootinit.constant; + +/** + * 文件常量 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public interface FileConstant { + + /** + * COS 访问地址 + * todo 需替换配置 + */ + String COS_HOST = "https://yupi.icu"; +} diff --git a/src/main/java/com/yupi/springbootinit/constant/UserConstant.java b/src/main/java/com/yupi/springbootinit/constant/UserConstant.java new file mode 100644 index 0000000..eeddd9b --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/constant/UserConstant.java @@ -0,0 +1,34 @@ +package com.yupi.springbootinit.constant; + +/** + * 用户常量 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public interface UserConstant { + + /** + * 用户登录态键 + */ + String USER_LOGIN_STATE = "user_login"; + + // region 权限 + + /** + * 默认角色 + */ + String DEFAULT_ROLE = "user"; + + /** + * 管理员角色 + */ + String ADMIN_ROLE = "admin"; + + /** + * 被封号 + */ + String BAN_ROLE = "ban"; + + // endregion +} diff --git a/src/main/java/com/yupi/springbootinit/controller/FileController.java b/src/main/java/com/yupi/springbootinit/controller/FileController.java new file mode 100644 index 0000000..cd3b060 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/controller/FileController.java @@ -0,0 +1,108 @@ +package com.yupi.springbootinit.controller; + +import cn.hutool.core.io.FileUtil; +import com.yupi.springbootinit.common.BaseResponse; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.common.ResultUtils; +import com.yupi.springbootinit.constant.FileConstant; +import com.yupi.springbootinit.exception.BusinessException; +import com.yupi.springbootinit.manager.CosManager; +import com.yupi.springbootinit.model.dto.file.UploadFileRequest; +import com.yupi.springbootinit.model.entity.User; +import com.yupi.springbootinit.model.enums.FileUploadBizEnum; +import com.yupi.springbootinit.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 程序员鱼皮 + * @from 编程导航知识星球 + */ +@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 BaseResponse uploadFile(@RequestPart("file") MultipartFile multipartFile, + UploadFileRequest uploadFileRequest, HttpServletRequest request) { + String biz = uploadFileRequest.getBiz(); + FileUploadBizEnum fileUploadBizEnum = FileUploadBizEnum.getEnumByValue(biz); + if (fileUploadBizEnum == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + validFile(multipartFile, fileUploadBizEnum); + User loginUser = userService.getLoginUser(request); + // 文件目录:根据业务、用户来划分 + String uuid = RandomStringUtils.randomAlphanumeric(8); + String filename = uuid + "-" + multipartFile.getOriginalFilename(); + String filepath = String.format("/%s/%s/%s", fileUploadBizEnum.getValue(), loginUser.getId(), filename); + File file = null; + try { + // 上传文件 + file = File.createTempFile(filepath, null); + multipartFile.transferTo(file); + cosManager.putObject(filepath, file); + // 返回可访问地址 + return ResultUtils.success(FileConstant.COS_HOST + filepath); + } catch (Exception e) { + log.error("file upload error, filepath = " + filepath, e); + throw new BusinessException(ErrorCode.SYSTEM_ERROR, "上传失败"); + } finally { + if (file != null) { + // 删除临时文件 + boolean delete = file.delete(); + if (!delete) { + log.error("file delete error, filepath = {}", filepath); + } + } + } + } + + /** + * 校验文件 + * + * @param multipartFile + * @param fileUploadBizEnum 业务类型 + */ + private void validFile(MultipartFile multipartFile, FileUploadBizEnum fileUploadBizEnum) { + // 文件大小 + long fileSize = multipartFile.getSize(); + // 文件后缀 + String fileSuffix = FileUtil.getSuffix(multipartFile.getOriginalFilename()); + final long ONE_M = 1024 * 1024L; + if (FileUploadBizEnum.USER_AVATAR.equals(fileUploadBizEnum)) { + if (fileSize > ONE_M) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "文件大小不能超过 1M"); + } + if (!Arrays.asList("jpeg", "jpg", "svg", "png", "webp").contains(fileSuffix)) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "文件类型错误"); + } + } + } +} diff --git a/src/main/java/com/yupi/springbootinit/controller/HostInfoController.java b/src/main/java/com/yupi/springbootinit/controller/HostInfoController.java new file mode 100644 index 0000000..6ca9091 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/controller/HostInfoController.java @@ -0,0 +1,35 @@ +package com.yupi.springbootinit.controller; + +import com.yupi.springbootinit.common.BaseResponse; +import com.yupi.springbootinit.common.ResultUtils; +import com.yupi.springbootinit.model.entity.NewHosts; +import com.yupi.springbootinit.rabbitMQ.MQSender; +import com.yupi.springbootinit.service.HostInfoService; +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; + +import javax.annotation.Resource; +import java.util.List; + +/* + * @author: ziin + * @date: 2025/6/10 17:09 + */ +@RestController +@RequestMapping("/save_data") +@Slf4j +public class HostInfoController { + + @Resource + private MQSender mqSender; + + + @PostMapping("add_host") + public BaseResponse addHost(@RequestBody List newHosts){ + mqSender.send(newHosts); + return ResultUtils.success(true); + } +} diff --git a/src/main/java/com/yupi/springbootinit/controller/PostController.java b/src/main/java/com/yupi/springbootinit/controller/PostController.java new file mode 100644 index 0000000..676f173 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/controller/PostController.java @@ -0,0 +1,263 @@ +package com.yupi.springbootinit.controller; + +import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yupi.springbootinit.annotation.AuthCheck; +import com.yupi.springbootinit.common.BaseResponse; +import com.yupi.springbootinit.common.DeleteRequest; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.common.ResultUtils; +import com.yupi.springbootinit.constant.UserConstant; +import com.yupi.springbootinit.exception.BusinessException; +import com.yupi.springbootinit.exception.ThrowUtils; +import com.yupi.springbootinit.model.dto.post.PostAddRequest; +import com.yupi.springbootinit.model.dto.post.PostEditRequest; +import com.yupi.springbootinit.model.dto.post.PostQueryRequest; +import com.yupi.springbootinit.model.dto.post.PostUpdateRequest; +import com.yupi.springbootinit.model.entity.Post; +import com.yupi.springbootinit.model.entity.User; +import com.yupi.springbootinit.model.vo.PostVO; +import com.yupi.springbootinit.service.PostService; +import com.yupi.springbootinit.service.UserService; +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; + +/** + * 帖子接口 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@RestController +@RequestMapping("/post") +@Slf4j +public class PostController { + + @Resource + private PostService postService; + + @Resource + private UserService userService; + + // region 增删改查 + + /** + * 创建 + * + * @param postAddRequest + * @param request + * @return + */ + @PostMapping("/add") + public BaseResponse addPost(@RequestBody PostAddRequest postAddRequest, HttpServletRequest request) { + if (postAddRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + Post post = new Post(); + BeanUtils.copyProperties(postAddRequest, post); + List tags = postAddRequest.getTags(); + if (tags != null) { + post.setTags(JSONUtil.toJsonStr(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 BaseResponse 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 BaseResponse updatePost(@RequestBody PostUpdateRequest postUpdateRequest) { + if (postUpdateRequest == null || postUpdateRequest.getId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + Post post = new Post(); + BeanUtils.copyProperties(postUpdateRequest, post); + List tags = postUpdateRequest.getTags(); + if (tags != null) { + post.setTags(JSONUtil.toJsonStr(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 BaseResponse 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 + * @return + */ + @PostMapping("/list/page") + @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) + public BaseResponse> listPostByPage(@RequestBody PostQueryRequest postQueryRequest) { + long current = postQueryRequest.getCurrent(); + long size = postQueryRequest.getPageSize(); + Page postPage = postService.page(new Page<>(current, size), + postService.getQueryWrapper(postQueryRequest)); + return ResultUtils.success(postPage); + } + + /** + * 分页获取列表(封装类) + * + * @param postQueryRequest + * @param request + * @return + */ + @PostMapping("/list/page/vo") + public BaseResponse> listPostVOByPage(@RequestBody PostQueryRequest postQueryRequest, + HttpServletRequest request) { + long current = postQueryRequest.getCurrent(); + long size = postQueryRequest.getPageSize(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); + Page postPage = postService.page(new Page<>(current, size), + postService.getQueryWrapper(postQueryRequest)); + return ResultUtils.success(postService.getPostVOPage(postPage, request)); + } + + /** + * 分页获取当前用户创建的资源列表 + * + * @param postQueryRequest + * @param request + * @return + */ + @PostMapping("/my/list/page/vo") + public BaseResponse> listMyPostVOByPage(@RequestBody PostQueryRequest postQueryRequest, + HttpServletRequest request) { + if (postQueryRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User loginUser = userService.getLoginUser(request); + postQueryRequest.setUserId(loginUser.getId()); + long current = postQueryRequest.getCurrent(); + long size = postQueryRequest.getPageSize(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); + Page postPage = postService.page(new Page<>(current, size), + postService.getQueryWrapper(postQueryRequest)); + return ResultUtils.success(postService.getPostVOPage(postPage, request)); + } + + // endregion + + /** + * 分页搜索(从 ES 查询,封装类) + * + * @param postQueryRequest + * @param request + * @return + */ + @PostMapping("/search/page/vo") + public BaseResponse> searchPostVOByPage(@RequestBody PostQueryRequest postQueryRequest, + HttpServletRequest request) { + long size = postQueryRequest.getPageSize(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); + Page postPage = postService.searchFromEs(postQueryRequest); + return ResultUtils.success(postService.getPostVOPage(postPage, request)); + } + + /** + * 编辑(用户) + * + * @param postEditRequest + * @param request + * @return + */ + @PostMapping("/edit") + public BaseResponse editPost(@RequestBody PostEditRequest postEditRequest, HttpServletRequest request) { + if (postEditRequest == null || postEditRequest.getId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + Post post = new Post(); + BeanUtils.copyProperties(postEditRequest, post); + List tags = postEditRequest.getTags(); + if (tags != null) { + post.setTags(JSONUtil.toJsonStr(tags)); + } + // 参数校验 + postService.validPost(post, false); + User loginUser = userService.getLoginUser(request); + long id = postEditRequest.getId(); + // 判断是否存在 + Post oldPost = postService.getById(id); + ThrowUtils.throwIf(oldPost == null, ErrorCode.NOT_FOUND_ERROR); + // 仅本人或管理员可编辑 + if (!oldPost.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser)) { + throw new BusinessException(ErrorCode.NO_AUTH_ERROR); + } + boolean result = postService.updateById(post); + return ResultUtils.success(result); + } + +} diff --git a/src/main/java/com/yupi/springbootinit/controller/PostFavourController.java b/src/main/java/com/yupi/springbootinit/controller/PostFavourController.java new file mode 100644 index 0000000..767d4ef --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/controller/PostFavourController.java @@ -0,0 +1,109 @@ +package com.yupi.springbootinit.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yupi.springbootinit.common.BaseResponse; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.common.ResultUtils; +import com.yupi.springbootinit.exception.BusinessException; +import com.yupi.springbootinit.exception.ThrowUtils; +import com.yupi.springbootinit.model.dto.post.PostQueryRequest; +import com.yupi.springbootinit.model.dto.postfavour.PostFavourAddRequest; +import com.yupi.springbootinit.model.dto.postfavour.PostFavourQueryRequest; +import com.yupi.springbootinit.model.entity.Post; +import com.yupi.springbootinit.model.entity.User; +import com.yupi.springbootinit.model.vo.PostVO; +import com.yupi.springbootinit.service.PostFavourService; +import com.yupi.springbootinit.service.PostService; +import com.yupi.springbootinit.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 程序员鱼皮 + * @from 编程导航知识星球 + */ +@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 BaseResponse 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 BaseResponse> listMyFavourPostByPage(@RequestBody PostQueryRequest postQueryRequest, + HttpServletRequest request) { + if (postQueryRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User loginUser = userService.getLoginUser(request); + long current = postQueryRequest.getCurrent(); + long size = postQueryRequest.getPageSize(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); + Page postPage = postFavourService.listFavourPostByPage(new Page<>(current, size), + postService.getQueryWrapper(postQueryRequest), loginUser.getId()); + return ResultUtils.success(postService.getPostVOPage(postPage, request)); + } + + /** + * 获取用户收藏的帖子列表 + * + * @param postFavourQueryRequest + * @param request + */ + @PostMapping("/list/page") + public BaseResponse> listFavourPostByPage(@RequestBody PostFavourQueryRequest postFavourQueryRequest, + HttpServletRequest request) { + if (postFavourQueryRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + long current = postFavourQueryRequest.getCurrent(); + long size = postFavourQueryRequest.getPageSize(); + Long userId = postFavourQueryRequest.getUserId(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20 || userId == null, ErrorCode.PARAMS_ERROR); + Page postPage = postFavourService.listFavourPostByPage(new Page<>(current, size), + postService.getQueryWrapper(postFavourQueryRequest.getPostQueryRequest()), userId); + return ResultUtils.success(postService.getPostVOPage(postPage, request)); + } +} diff --git a/src/main/java/com/yupi/springbootinit/controller/PostThumbController.java b/src/main/java/com/yupi/springbootinit/controller/PostThumbController.java new file mode 100644 index 0000000..faaaa3c --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/controller/PostThumbController.java @@ -0,0 +1,56 @@ +package com.yupi.springbootinit.controller; + +import com.yupi.springbootinit.common.BaseResponse; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.common.ResultUtils; +import com.yupi.springbootinit.exception.BusinessException; +import com.yupi.springbootinit.model.dto.postthumb.PostThumbAddRequest; +import com.yupi.springbootinit.model.entity.User; +import com.yupi.springbootinit.service.PostThumbService; +import com.yupi.springbootinit.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 程序员鱼皮 + * @from 编程导航知识星球 + */ +@RestController +@RequestMapping("/post_thumb") +@Slf4j +public class PostThumbController { + + @Resource + private PostThumbService postThumbService; + + @Resource + private UserService userService; + + /** + * 点赞 / 取消点赞 + * + * @param postThumbAddRequest + * @param request + * @return resultNum 本次点赞变化数 + */ + @PostMapping("/") + public BaseResponse doThumb(@RequestBody PostThumbAddRequest postThumbAddRequest, + HttpServletRequest request) { + if (postThumbAddRequest == null || postThumbAddRequest.getPostId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + // 登录才能点赞 + final User loginUser = userService.getLoginUser(request); + long postId = postThumbAddRequest.getPostId(); + int result = postThumbService.doPostThumb(postId, loginUser); + return ResultUtils.success(result); + } + +} diff --git a/src/main/java/com/yupi/springbootinit/controller/UserController.java b/src/main/java/com/yupi/springbootinit/controller/UserController.java new file mode 100644 index 0000000..725d60a --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/controller/UserController.java @@ -0,0 +1,320 @@ +package com.yupi.springbootinit.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yupi.springbootinit.annotation.AuthCheck; +import com.yupi.springbootinit.common.BaseResponse; +import com.yupi.springbootinit.common.DeleteRequest; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.common.ResultUtils; +import com.yupi.springbootinit.config.WxOpenConfig; +import com.yupi.springbootinit.constant.UserConstant; +import com.yupi.springbootinit.exception.BusinessException; +import com.yupi.springbootinit.exception.ThrowUtils; +import com.yupi.springbootinit.model.dto.user.UserAddRequest; +import com.yupi.springbootinit.model.dto.user.UserLoginRequest; +import com.yupi.springbootinit.model.dto.user.UserQueryRequest; +import com.yupi.springbootinit.model.dto.user.UserRegisterRequest; +import com.yupi.springbootinit.model.dto.user.UserUpdateMyRequest; +import com.yupi.springbootinit.model.dto.user.UserUpdateRequest; +import com.yupi.springbootinit.model.entity.User; +import com.yupi.springbootinit.model.vo.LoginUserVO; +import com.yupi.springbootinit.model.vo.UserVO; +import com.yupi.springbootinit.service.UserService; + +import java.util.List; +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.bean.WxOAuth2UserInfo; +import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken; +import me.chanjar.weixin.mp.api.WxMpService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.util.DigestUtils; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import static com.yupi.springbootinit.service.impl.UserServiceImpl.SALT; + +/** + * 用户接口 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@RestController +@RequestMapping("/user") +@Slf4j +public class UserController { + + @Resource + private UserService userService; + + @Resource + private WxOpenConfig wxOpenConfig; + + // region 登录相关 + + /** + * 用户注册 + * + * @param userRegisterRequest + * @return + */ + @PostMapping("/register") + public BaseResponse 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 BaseResponse 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); + } + + /** + * 用户登录(微信开放平台) + */ + @GetMapping("/login/wx_open") + public BaseResponse userLoginByWxOpen(HttpServletRequest request, HttpServletResponse response, + @RequestParam("code") String code) { + WxOAuth2AccessToken accessToken; + try { + WxMpService wxService = wxOpenConfig.getWxMpService(); + accessToken = wxService.getOAuth2Service().getAccessToken(code); + WxOAuth2UserInfo userInfo = wxService.getOAuth2Service().getUserInfo(accessToken, code); + String unionId = userInfo.getUnionId(); + String mpOpenId = userInfo.getOpenid(); + if (StringUtils.isAnyBlank(unionId, mpOpenId)) { + throw new BusinessException(ErrorCode.SYSTEM_ERROR, "登录失败,系统错误"); + } + return ResultUtils.success(userService.userLoginByMpOpen(userInfo, request)); + } catch (Exception e) { + log.error("userLoginByWxOpen error", e); + throw new BusinessException(ErrorCode.SYSTEM_ERROR, "登录失败,系统错误"); + } + } + + /** + * 用户注销 + * + * @param request + * @return + */ + @PostMapping("/logout") + public BaseResponse 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 BaseResponse 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 BaseResponse addUser(@RequestBody UserAddRequest userAddRequest, HttpServletRequest request) { + if (userAddRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User user = new User(); + BeanUtils.copyProperties(userAddRequest, user); + // 默认密码 12345678 + String defaultPassword = "12345678"; + String encryptPassword = DigestUtils.md5DigestAsHex((SALT + defaultPassword).getBytes()); + user.setUserPassword(encryptPassword); + 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 BaseResponse 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 BaseResponse 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 BaseResponse 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 BaseResponse getUserVOById(long id, HttpServletRequest request) { + BaseResponse 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 BaseResponse> listUserByPage(@RequestBody UserQueryRequest userQueryRequest, + HttpServletRequest request) { + long current = userQueryRequest.getCurrent(); + long size = userQueryRequest.getPageSize(); + Page userPage = userService.page(new Page<>(current, size), + userService.getQueryWrapper(userQueryRequest)); + return ResultUtils.success(userPage); + } + + /** + * 分页获取用户封装列表 + * + * @param userQueryRequest + * @param request + * @return + */ + @PostMapping("/list/page/vo") + public BaseResponse> listUserVOByPage(@RequestBody UserQueryRequest userQueryRequest, + HttpServletRequest request) { + if (userQueryRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + long current = userQueryRequest.getCurrent(); + long size = userQueryRequest.getPageSize(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); + Page userPage = userService.page(new Page<>(current, size), + userService.getQueryWrapper(userQueryRequest)); + Page userVOPage = new Page<>(current, size, userPage.getTotal()); + List userVO = userService.getUserVO(userPage.getRecords()); + userVOPage.setRecords(userVO); + return ResultUtils.success(userVOPage); + } + + // endregion + + /** + * 更新个人信息 + * + * @param userUpdateMyRequest + * @param request + * @return + */ + @PostMapping("/update/my") + public BaseResponse updateMyUser(@RequestBody UserUpdateMyRequest userUpdateMyRequest, + HttpServletRequest request) { + if (userUpdateMyRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User loginUser = userService.getLoginUser(request); + User user = new User(); + BeanUtils.copyProperties(userUpdateMyRequest, user); + user.setId(loginUser.getId()); + boolean result = userService.updateById(user); + ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); + return ResultUtils.success(true); + } +} diff --git a/src/main/java/com/yupi/springbootinit/controller/WxMpController.java b/src/main/java/com/yupi/springbootinit/controller/WxMpController.java new file mode 100644 index 0000000..7843df0 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/controller/WxMpController.java @@ -0,0 +1,135 @@ +package com.yupi.springbootinit.controller; + +import com.yupi.springbootinit.wxmp.WxMpConstant; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.api.WxConsts.MenuButtonType; +import me.chanjar.weixin.common.bean.menu.WxMenu; +import me.chanjar.weixin.common.bean.menu.WxMenuButton; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpMessageRouter; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 微信公众号相关接口 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + **/ +@RestController +@RequestMapping("/") +@Slf4j +public class WxMpController { + + @Resource + private WxMpService wxMpService; + + @Resource + private WxMpMessageRouter router; + + @PostMapping("/") + public void receiveMessage(HttpServletRequest request, HttpServletResponse response) + throws IOException { + response.setContentType("text/html;charset=utf-8"); + response.setStatus(HttpServletResponse.SC_OK); + // 校验消息签名,判断是否为公众平台发的消息 + String signature = request.getParameter("signature"); + String nonce = request.getParameter("nonce"); + String timestamp = request.getParameter("timestamp"); + if (!wxMpService.checkSignature(timestamp, nonce, signature)) { + response.getWriter().println("非法请求"); + } + // 加密类型 + String encryptType = StringUtils.isBlank(request.getParameter("encrypt_type")) ? "raw" + : request.getParameter("encrypt_type"); + // 明文消息 + if ("raw".equals(encryptType)) { + return; + } + // aes 加密消息 + if ("aes".equals(encryptType)) { + // 解密消息 + String msgSignature = request.getParameter("msg_signature"); + WxMpXmlMessage inMessage = WxMpXmlMessage + .fromEncryptedXml(request.getInputStream(), wxMpService.getWxMpConfigStorage(), timestamp, + nonce, + msgSignature); + log.info("message content = {}", inMessage.getContent()); + // 路由消息并处理 + WxMpXmlOutMessage outMessage = router.route(inMessage); + if (outMessage == null) { + response.getWriter().write(""); + } else { + response.getWriter().write(outMessage.toEncryptedXml(wxMpService.getWxMpConfigStorage())); + } + return; + } + response.getWriter().println("不可识别的加密类型"); + } + + @GetMapping("/") + public String check(String timestamp, String nonce, String signature, String echostr) { + log.info("check"); + if (wxMpService.checkSignature(timestamp, nonce, signature)) { + return echostr; + } else { + return ""; + } + } + + /** + * 设置公众号菜单 + * + * @return + * @throws WxErrorException + */ + @GetMapping("/setMenu") + public String setMenu() throws WxErrorException { + log.info("setMenu"); + WxMenu wxMenu = new WxMenu(); + // 菜单一 + WxMenuButton wxMenuButton1 = new WxMenuButton(); + wxMenuButton1.setType(MenuButtonType.VIEW); + wxMenuButton1.setName("主菜单一"); + // 子菜单 + WxMenuButton wxMenuButton1SubButton1 = new WxMenuButton(); + wxMenuButton1SubButton1.setType(MenuButtonType.VIEW); + wxMenuButton1SubButton1.setName("跳转页面"); + wxMenuButton1SubButton1.setUrl( + "https://yupi.icu"); + wxMenuButton1.setSubButtons(Collections.singletonList(wxMenuButton1SubButton1)); + + // 菜单二 + WxMenuButton wxMenuButton2 = new WxMenuButton(); + wxMenuButton2.setType(MenuButtonType.CLICK); + wxMenuButton2.setName("点击事件"); + wxMenuButton2.setKey(WxMpConstant.CLICK_MENU_KEY); + + // 菜单三 + WxMenuButton wxMenuButton3 = new WxMenuButton(); + wxMenuButton3.setType(MenuButtonType.VIEW); + wxMenuButton3.setName("主菜单三"); + WxMenuButton wxMenuButton3SubButton1 = new WxMenuButton(); + wxMenuButton3SubButton1.setType(MenuButtonType.VIEW); + wxMenuButton3SubButton1.setName("编程学习"); + wxMenuButton3SubButton1.setUrl("https://yupi.icu"); + wxMenuButton3.setSubButtons(Collections.singletonList(wxMenuButton3SubButton1)); + + // 设置主菜单 + wxMenu.setButtons(Arrays.asList(wxMenuButton1, wxMenuButton2, wxMenuButton3)); + wxMpService.getMenuService().menuCreate(wxMenu); + return "ok"; + } +} diff --git a/src/main/java/com/yupi/springbootinit/esdao/PostEsDao.java b/src/main/java/com/yupi/springbootinit/esdao/PostEsDao.java new file mode 100644 index 0000000..8565f53 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/esdao/PostEsDao.java @@ -0,0 +1,16 @@ +package com.yupi.springbootinit.esdao; + +import com.yupi.springbootinit.model.dto.post.PostEsDTO; +import java.util.List; +import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; + +/** + * 帖子 ES 操作 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public interface PostEsDao extends ElasticsearchRepository { + + List findByUserId(Long userId); +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/exception/BusinessException.java b/src/main/java/com/yupi/springbootinit/exception/BusinessException.java new file mode 100644 index 0000000..6e7dd1c --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/exception/BusinessException.java @@ -0,0 +1,36 @@ +package com.yupi.springbootinit.exception; + +import com.yupi.springbootinit.common.ErrorCode; + +/** + * 自定义异常类 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public class BusinessException extends RuntimeException { + + /** + * 错误码 + */ + private final int code; + + public BusinessException(int code, String message) { + super(message); + this.code = code; + } + + public BusinessException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.code = errorCode.getCode(); + } + + public BusinessException(ErrorCode errorCode, String message) { + super(message); + this.code = errorCode.getCode(); + } + + public int getCode() { + return code; + } +} diff --git a/src/main/java/com/yupi/springbootinit/exception/GlobalExceptionHandler.java b/src/main/java/com/yupi/springbootinit/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..610ca49 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/exception/GlobalExceptionHandler.java @@ -0,0 +1,31 @@ +package com.yupi.springbootinit.exception; + +import com.yupi.springbootinit.common.BaseResponse; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.common.ResultUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * 全局异常处理器 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@RestControllerAdvice +@Slf4j +public class GlobalExceptionHandler { + + @ExceptionHandler(BusinessException.class) + public BaseResponse businessExceptionHandler(BusinessException e) { + log.error("BusinessException", e); + return ResultUtils.error(e.getCode(), e.getMessage()); + } + + @ExceptionHandler(RuntimeException.class) + public BaseResponse runtimeExceptionHandler(RuntimeException e) { + log.error("RuntimeException", e); + return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "系统错误"); + } +} diff --git a/src/main/java/com/yupi/springbootinit/exception/ThrowUtils.java b/src/main/java/com/yupi/springbootinit/exception/ThrowUtils.java new file mode 100644 index 0000000..e25468a --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/exception/ThrowUtils.java @@ -0,0 +1,45 @@ +package com.yupi.springbootinit.exception; + +import com.yupi.springbootinit.common.ErrorCode; + +/** + * 抛异常工具类 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public class ThrowUtils { + + /** + * 条件成立则抛异常 + * + * @param condition + * @param runtimeException + */ + public static void throwIf(boolean condition, RuntimeException runtimeException) { + if (condition) { + throw runtimeException; + } + } + + /** + * 条件成立则抛异常 + * + * @param condition + * @param errorCode + */ + public static void throwIf(boolean condition, ErrorCode errorCode) { + throwIf(condition, new BusinessException(errorCode)); + } + + /** + * 条件成立则抛异常 + * + * @param condition + * @param errorCode + * @param message + */ + public static void throwIf(boolean condition, ErrorCode errorCode, String message) { + throwIf(condition, new BusinessException(errorCode, message)); + } +} diff --git a/src/main/java/com/yupi/springbootinit/generate/CodeGenerator.java b/src/main/java/com/yupi/springbootinit/generate/CodeGenerator.java new file mode 100644 index 0000000..c379215 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/generate/CodeGenerator.java @@ -0,0 +1,128 @@ +package com.yupi.springbootinit.generate; + +import cn.hutool.core.io.FileUtil; +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateException; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.io.FileWriter; +import java.io.Writer; + +/** + * 代码生成器 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +public class CodeGenerator { + + /** + * 用法:修改生成参数和生成路径,注释掉不需要的生成逻辑,然后运行即可 + * + * @param args + * @throws TemplateException + * @throws IOException + */ + public static void main(String[] args) throws TemplateException, IOException { + // 指定生成参数 + String packageName = "com.yupi.springbootinit"; + String dataName = "用户评论"; + String dataKey = "userComment"; + String upperDataKey = "UserComment"; + + // 封装生成参数 + Map dataModel = new HashMap<>(); + dataModel.put("packageName", packageName); + dataModel.put("dataName", dataName); + dataModel.put("dataKey", dataKey); + dataModel.put("upperDataKey", upperDataKey); + + // 生成路径默认值 + String projectPath = System.getProperty("user.dir"); + // 参考路径,可以自己调整下面的 outputPath + String inputPath = projectPath + File.separator + "src/main/resources/templates/模板名称.java.ftl"; + String outputPath = String.format("%s/generator/包名/%s类后缀.java", projectPath, upperDataKey); + + // 1、生成 Controller + // 指定生成路径 + inputPath = projectPath + File.separator + "src/main/resources/templates/TemplateController.java.ftl"; + outputPath = String.format("%s/generator/controller/%sController.java", projectPath, upperDataKey); + // 生成 + doGenerate(inputPath, outputPath, dataModel); + System.out.println("生成 Controller 成功,文件路径:" + outputPath); + + // 2、生成 Service 接口和实现类 + // 生成 Service 接口 + inputPath = projectPath + File.separator + "src/main/resources/templates/TemplateService.java.ftl"; + outputPath = String.format("%s/generator/service/%sService.java", projectPath, upperDataKey); + doGenerate(inputPath, outputPath, dataModel); + System.out.println("生成 Service 接口成功,文件路径:" + outputPath); + // 生成 Service 实现类 + inputPath = projectPath + File.separator + "src/main/resources/templates/TemplateServiceImpl.java.ftl"; + outputPath = String.format("%s/generator/service/impl/%sServiceImpl.java", projectPath, upperDataKey); + doGenerate(inputPath, outputPath, dataModel); + System.out.println("生成 Service 实现类成功,文件路径:" + outputPath); + + // 3、生成数据模型封装类(包括 DTO 和 VO) + // 生成 DTO + inputPath = projectPath + File.separator + "src/main/resources/templates/model/TemplateAddRequest.java.ftl"; + outputPath = String.format("%s/generator/model/dto/%sAddRequest.java", projectPath, upperDataKey); + doGenerate(inputPath, outputPath, dataModel); + inputPath = projectPath + File.separator + "src/main/resources/templates/model/TemplateQueryRequest.java.ftl"; + outputPath = String.format("%s/generator/model/dto/%sQueryRequest.java", projectPath, upperDataKey); + doGenerate(inputPath, outputPath, dataModel); + inputPath = projectPath + File.separator + "src/main/resources/templates/model/TemplateEditRequest.java.ftl"; + outputPath = String.format("%s/generator/model/dto/%sEditRequest.java", projectPath, upperDataKey); + doGenerate(inputPath, outputPath, dataModel); + inputPath = projectPath + File.separator + "src/main/resources/templates/model/TemplateUpdateRequest.java.ftl"; + outputPath = String.format("%s/generator/model/dto/%sUpdateRequest.java", projectPath, upperDataKey); + doGenerate(inputPath, outputPath, dataModel); + System.out.println("生成 DTO 成功,文件路径:" + outputPath); + // 生成 VO + inputPath = projectPath + File.separator + "src/main/resources/templates/model/TemplateVO.java.ftl"; + outputPath = String.format("%s/generator/model/vo/%sVO.java", projectPath, upperDataKey); + doGenerate(inputPath, outputPath, dataModel); + System.out.println("生成 VO 成功,文件路径:" + outputPath); + } + + /** + * 生成文件 + * + * @param inputPath 模板文件输入路径 + * @param outputPath 输出路径 + * @param model 数据模型 + * @throws IOException + * @throws TemplateException + */ + public static void doGenerate(String inputPath, String outputPath, Object model) throws IOException, TemplateException { + // new 出 Configuration 对象,参数为 FreeMarker 版本号 + Configuration configuration = new Configuration(Configuration.VERSION_2_3_31); + + // 指定模板文件所在的路径 + File templateDir = new File(inputPath).getParentFile(); + configuration.setDirectoryForTemplateLoading(templateDir); + + // 设置模板文件使用的字符集 + configuration.setDefaultEncoding("utf-8"); + + // 创建模板对象,加载指定模板 + String templateName = new File(inputPath).getName(); + Template template = configuration.getTemplate(templateName); + + // 文件不存在则创建文件和父目录 + if (!FileUtil.exist(outputPath)) { + FileUtil.touch(outputPath); + } + + // 生成 + Writer out = new FileWriter(outputPath); + template.process(model, out); + + // 生成文件后别忘了关闭哦 + out.close(); + } +} diff --git a/src/main/java/com/yupi/springbootinit/job/cycle/IncSyncPostToEs.java b/src/main/java/com/yupi/springbootinit/job/cycle/IncSyncPostToEs.java new file mode 100644 index 0000000..9a24326 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/job/cycle/IncSyncPostToEs.java @@ -0,0 +1,57 @@ +package com.yupi.springbootinit.job.cycle; + +import com.yupi.springbootinit.esdao.PostEsDao; +import com.yupi.springbootinit.mapper.PostMapper; +import com.yupi.springbootinit.model.dto.post.PostEsDTO; +import com.yupi.springbootinit.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 cn.hutool.core.collection.CollUtil; +import org.springframework.scheduling.annotation.Scheduled; + +/** + * 增量同步帖子到 es + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +// todo 取消注释开启任务 +//@Component +@Slf4j +public class IncSyncPostToEs { + + @Resource + private PostMapper postMapper; + + @Resource + private PostEsDao postEsDao; + + /** + * 每分钟执行一次 + */ + @Scheduled(fixedRate = 60 * 1000) + public void run() { + // 查询近 5 分钟内的数据 + Date fiveMinutesAgoDate = new Date(new Date().getTime() - 5 * 60 * 1000L); + List postList = postMapper.listPostWithDelete(fiveMinutesAgoDate); + if (CollUtil.isEmpty(postList)) { + log.info("no inc post"); + return; + } + List postEsDTOList = postList.stream() + .map(PostEsDTO::objToDto) + .collect(Collectors.toList()); + final int pageSize = 500; + int total = postEsDTOList.size(); + log.info("IncSyncPostToEs start, total {}", total); + for (int i = 0; i < total; i += pageSize) { + int end = Math.min(i + pageSize, total); + log.info("sync from {} to {}", i, end); + postEsDao.saveAll(postEsDTOList.subList(i, end)); + } + log.info("IncSyncPostToEs end, total {}", total); + } +} diff --git a/src/main/java/com/yupi/springbootinit/job/once/FullSyncPostToEs.java b/src/main/java/com/yupi/springbootinit/job/once/FullSyncPostToEs.java new file mode 100644 index 0000000..e7b8793 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/job/once/FullSyncPostToEs.java @@ -0,0 +1,48 @@ +package com.yupi.springbootinit.job.once; + +import com.yupi.springbootinit.esdao.PostEsDao; +import com.yupi.springbootinit.model.dto.post.PostEsDTO; +import com.yupi.springbootinit.model.entity.Post; +import com.yupi.springbootinit.service.PostService; +import java.util.List; +import java.util.stream.Collectors; +import javax.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import cn.hutool.core.collection.CollUtil; +import org.springframework.boot.CommandLineRunner; + +/** + * 全量同步帖子到 es + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +// todo 取消注释开启任务 +//@Component +@Slf4j +public class FullSyncPostToEs implements CommandLineRunner { + + @Resource + private PostService postService; + + @Resource + private PostEsDao postEsDao; + + @Override + public void run(String... args) { + List postList = postService.list(); + if (CollUtil.isEmpty(postList)) { + return; + } + List postEsDTOList = postList.stream().map(PostEsDTO::objToDto).collect(Collectors.toList()); + final int pageSize = 500; + int total = postEsDTOList.size(); + log.info("FullSyncPostToEs start, total {}", total); + for (int i = 0; i < total; i += pageSize) { + int end = Math.min(i + pageSize, total); + log.info("sync from {} to {}", i, end); + postEsDao.saveAll(postEsDTOList.subList(i, end)); + } + log.info("FullSyncPostToEs end, total {}", total); + } +} diff --git a/src/main/java/com/yupi/springbootinit/manager/CosManager.java b/src/main/java/com/yupi/springbootinit/manager/CosManager.java new file mode 100644 index 0000000..a556cc2 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/manager/CosManager.java @@ -0,0 +1,51 @@ +package com.yupi.springbootinit.manager; + +import com.qcloud.cos.COSClient; +import com.qcloud.cos.model.PutObjectRequest; +import com.qcloud.cos.model.PutObjectResult; +import com.yupi.springbootinit.config.CosClientConfig; +import java.io.File; +import javax.annotation.Resource; +import org.springframework.stereotype.Component; + +/** + * Cos 对象存储操作 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Component +public class CosManager { + + @Resource + private CosClientConfig cosClientConfig; + + @Resource + private COSClient cosClient; + + /** + * 上传对象 + * + * @param key 唯一键 + * @param localFilePath 本地文件路径 + * @return + */ + public PutObjectResult putObject(String key, String localFilePath) { + PutObjectRequest putObjectRequest = new PutObjectRequest(cosClientConfig.getBucket(), key, + new File(localFilePath)); + return cosClient.putObject(putObjectRequest); + } + + /** + * 上传对象 + * + * @param key 唯一键 + * @param file 文件 + * @return + */ + public PutObjectResult putObject(String key, File file) { + PutObjectRequest putObjectRequest = new PutObjectRequest(cosClientConfig.getBucket(), key, + file); + return cosClient.putObject(putObjectRequest); + } +} diff --git a/src/main/java/com/yupi/springbootinit/mapper/NewHostsMapper.java b/src/main/java/com/yupi/springbootinit/mapper/NewHostsMapper.java new file mode 100644 index 0000000..9df0f32 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/mapper/NewHostsMapper.java @@ -0,0 +1,26 @@ +package com.yupi.springbootinit.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yupi.springbootinit.model.entity.NewHosts; +import java.util.List; +import org.apache.ibatis.annotations.Param; + +/* +* @author: ziin +* @date: 2025/6/10 18:54 +*/ +public interface NewHostsMapper extends BaseMapper { + int deleteByPrimaryKey(Long id); + + int insert(NewHosts record); + + int insertSelective(NewHosts record); + + NewHosts selectByPrimaryKey(Long id); + + int updateByPrimaryKeySelective(NewHosts record); + + int updateByPrimaryKey(NewHosts record); + + int batchInsert(@Param("list") List list); +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/mapper/PostFavourMapper.java b/src/main/java/com/yupi/springbootinit/mapper/PostFavourMapper.java new file mode 100644 index 0000000..c57cf5e --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/mapper/PostFavourMapper.java @@ -0,0 +1,35 @@ +package com.yupi.springbootinit.mapper; + +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yupi.springbootinit.model.entity.Post; +import com.yupi.springbootinit.model.entity.PostFavour; +import org.apache.ibatis.annotations.Param; + +/** + * 帖子收藏数据库操作 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public interface PostFavourMapper extends BaseMapper { + + /** + * 分页查询收藏帖子列表 + * + * @param page + * @param queryWrapper + * @param favourUserId + * @return + */ + Page listFavourPostByPage(IPage page, @Param(Constants.WRAPPER) Wrapper queryWrapper, + long favourUserId); + +} + + + + diff --git a/src/main/java/com/yupi/springbootinit/mapper/PostMapper.java b/src/main/java/com/yupi/springbootinit/mapper/PostMapper.java new file mode 100644 index 0000000..2975b84 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/mapper/PostMapper.java @@ -0,0 +1,25 @@ +package com.yupi.springbootinit.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yupi.springbootinit.model.entity.Post; +import java.util.Date; +import java.util.List; + +/** + * 帖子数据库操作 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public interface PostMapper extends BaseMapper { + + /** + * 查询帖子列表(包括已被删除的数据) + */ + List listPostWithDelete(Date minUpdateTime); + +} + + + + diff --git a/src/main/java/com/yupi/springbootinit/mapper/PostThumbMapper.java b/src/main/java/com/yupi/springbootinit/mapper/PostThumbMapper.java new file mode 100644 index 0000000..fe39c82 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/mapper/PostThumbMapper.java @@ -0,0 +1,18 @@ +package com.yupi.springbootinit.mapper; + +import com.yupi.springbootinit.model.entity.PostThumb; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 帖子点赞数据库操作 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public interface PostThumbMapper extends BaseMapper { + +} + + + + diff --git a/src/main/java/com/yupi/springbootinit/mapper/UserMapper.java b/src/main/java/com/yupi/springbootinit/mapper/UserMapper.java new file mode 100644 index 0000000..001b7e2 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/mapper/UserMapper.java @@ -0,0 +1,18 @@ +package com.yupi.springbootinit.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yupi.springbootinit.model.entity.User; + +/** + * 用户数据库操作 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public interface UserMapper extends BaseMapper { + +} + + + + diff --git a/src/main/java/com/yupi/springbootinit/model/dto/file/UploadFileRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/file/UploadFileRequest.java new file mode 100644 index 0000000..09654ec --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/file/UploadFileRequest.java @@ -0,0 +1,21 @@ +package com.yupi.springbootinit.model.dto.file; + +import java.io.Serializable; +import lombok.Data; + +/** + * 文件上传请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class UploadFileRequest implements Serializable { + + /** + * 业务 + */ + private String biz; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/host/HostInfoDTO.java b/src/main/java/com/yupi/springbootinit/model/dto/host/HostInfoDTO.java new file mode 100644 index 0000000..81e554f --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/host/HostInfoDTO.java @@ -0,0 +1,88 @@ +package com.yupi.springbootinit.model.dto.host; + +import lombok.Data; + +import java.util.Date; + +/* + * @author: ziin + * @date: 2025/6/10 18:58 + */ +@Data +public class HostInfoDTO { + + /** + * 主键 + */ + private Long id; + + /** + * 主播id + */ + private String hostsId; + + /** + * 主播等级 + */ + private String hostsLevel; + + /** + * 主播金币 + */ + private Integer hostsCoins; + + /** + * 邀请类型 + */ + private Integer invitationType; + + /** + * 粉丝数量 + */ + private Integer fans; + + /** + * 关注数量 + */ + private Integer fllowernum; + + /** + * 昨日金币 + */ + private Integer yesterdayCoins; + + /** + * 主播国家 + */ + private String country; + + /** + * 直播类型 娱乐,游戏 + */ + private String hostsKind; + + /** + * 租户 Id + */ + private Long tenantId; + + /** + * 入库人 + */ + private Integer creator; + + /** + * 数据插入时间 + */ + private Date createTime; + + /** + * 更新人 + */ + private String updater; + + /** + * 更新时间 + */ + private Date updateTime; +} diff --git a/src/main/java/com/yupi/springbootinit/model/dto/post/PostAddRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/post/PostAddRequest.java new file mode 100644 index 0000000..45ac49c --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/post/PostAddRequest.java @@ -0,0 +1,32 @@ +package com.yupi.springbootinit.model.dto.post; + +import java.io.Serializable; +import java.util.List; +import lombok.Data; + +/** + * 创建请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class PostAddRequest implements Serializable { + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 标签列表 + */ + private List tags; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/post/PostEditRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/post/PostEditRequest.java new file mode 100644 index 0000000..40c3888 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/post/PostEditRequest.java @@ -0,0 +1,37 @@ +package com.yupi.springbootinit.model.dto.post; + +import java.io.Serializable; +import java.util.List; +import lombok.Data; + +/** + * 编辑请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class PostEditRequest implements Serializable { + + /** + * id + */ + private Long id; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 标签列表 + */ + private List tags; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/post/PostEsDTO.java b/src/main/java/com/yupi/springbootinit/model/dto/post/PostEsDTO.java new file mode 100644 index 0000000..aa7ae46 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/post/PostEsDTO.java @@ -0,0 +1,123 @@ +package com.yupi.springbootinit.model.dto.post; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.json.JSONUtil; +import com.yupi.springbootinit.model.entity.Post; +import lombok.Data; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * 帖子 ES 包装类 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + **/ +// todo 取消注释开启 ES(须先配置 ES) +//@Document(indexName = "post") +@Data +public class PostEsDTO implements Serializable { + + private static final String DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + + /** + * id + */ + @Id + private Long id; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 标签列表 + */ + private List tags; + + /** + * 点赞数 + */ + private Integer thumbNum; + + /** + * 收藏数 + */ + private Integer favourNum; + + /** + * 创建用户 id + */ + private Long userId; + + /** + * 创建时间 + */ + @Field(index = false, store = true, type = FieldType.Date, format = {}, pattern = DATE_TIME_PATTERN) + private Date createTime; + + /** + * 更新时间 + */ + @Field(index = false, store = true, type = FieldType.Date, format = {}, pattern = DATE_TIME_PATTERN) + private Date updateTime; + + /** + * 是否删除 + */ + private Integer isDelete; + + private static final long serialVersionUID = 1L; + + /** + * 对象转包装类 + * + * @param post + * @return + */ + public static PostEsDTO objToDto(Post post) { + if (post == null) { + return null; + } + PostEsDTO postEsDTO = new PostEsDTO(); + BeanUtils.copyProperties(post, postEsDTO); + String tagsStr = post.getTags(); + if (StringUtils.isNotBlank(tagsStr)) { + postEsDTO.setTags(JSONUtil.toList(tagsStr, String.class)); + } + return postEsDTO; + } + + /** + * 包装类转对象 + * + * @param postEsDTO + * @return + */ + public static Post dtoToObj(PostEsDTO postEsDTO) { + if (postEsDTO == null) { + return null; + } + Post post = new Post(); + BeanUtils.copyProperties(postEsDTO, post); + List tagList = postEsDTO.getTags(); + if (CollUtil.isNotEmpty(tagList)) { + post.setTags(JSONUtil.toJsonStr(tagList)); + } + return post; + } +} diff --git a/src/main/java/com/yupi/springbootinit/model/dto/post/PostQueryRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/post/PostQueryRequest.java new file mode 100644 index 0000000..c9f160a --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/post/PostQueryRequest.java @@ -0,0 +1,65 @@ +package com.yupi.springbootinit.model.dto.post; + +import com.yupi.springbootinit.common.PageRequest; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 查询请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class PostQueryRequest extends PageRequest implements Serializable { + + /** + * id + */ + private Long id; + + /** + * id + */ + private Long notId; + + /** + * 搜索词 + */ + private String searchText; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 标签列表 + */ + private List tags; + + /** + * 至少有一个标签 + */ + private List orTags; + + /** + * 创建用户 id + */ + private Long userId; + + /** + * 收藏用户 id + */ + private Long favourUserId; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/post/PostUpdateRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/post/PostUpdateRequest.java new file mode 100644 index 0000000..c90bc70 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/post/PostUpdateRequest.java @@ -0,0 +1,37 @@ +package com.yupi.springbootinit.model.dto.post; + +import java.io.Serializable; +import java.util.List; +import lombok.Data; + +/** + * 更新请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class PostUpdateRequest implements Serializable { + + /** + * id + */ + private Long id; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 标签列表 + */ + private List tags; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/postfavour/PostFavourAddRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/postfavour/PostFavourAddRequest.java new file mode 100644 index 0000000..1b8cc27 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/postfavour/PostFavourAddRequest.java @@ -0,0 +1,21 @@ +package com.yupi.springbootinit.model.dto.postfavour; + +import java.io.Serializable; +import lombok.Data; + +/** + * 帖子收藏 / 取消收藏请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class PostFavourAddRequest implements Serializable { + + /** + * 帖子 id + */ + private Long postId; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/postfavour/PostFavourQueryRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/postfavour/PostFavourQueryRequest.java new file mode 100644 index 0000000..e8bf989 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/postfavour/PostFavourQueryRequest.java @@ -0,0 +1,30 @@ +package com.yupi.springbootinit.model.dto.postfavour; + +import com.yupi.springbootinit.common.PageRequest; +import com.yupi.springbootinit.model.dto.post.PostQueryRequest; +import java.io.Serializable; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 帖子收藏查询请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class PostFavourQueryRequest extends PageRequest implements Serializable { + + /** + * 帖子查询请求 + */ + private PostQueryRequest postQueryRequest; + + /** + * 用户 id + */ + private Long userId; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/postthumb/PostThumbAddRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/postthumb/PostThumbAddRequest.java new file mode 100644 index 0000000..e6209e7 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/postthumb/PostThumbAddRequest.java @@ -0,0 +1,21 @@ +package com.yupi.springbootinit.model.dto.postthumb; + +import java.io.Serializable; +import lombok.Data; + +/** + * 帖子点赞请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class PostThumbAddRequest implements Serializable { + + /** + * 帖子 id + */ + private Long postId; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/user/UserAddRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/user/UserAddRequest.java new file mode 100644 index 0000000..7c56337 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/user/UserAddRequest.java @@ -0,0 +1,36 @@ +package com.yupi.springbootinit.model.dto.user; + +import java.io.Serializable; +import lombok.Data; + +/** + * 用户创建请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class UserAddRequest implements Serializable { + + /** + * 用户昵称 + */ + private String userName; + + /** + * 账号 + */ + private String userAccount; + + /** + * 用户头像 + */ + private String userAvatar; + + /** + * 用户角色: user, admin + */ + private String userRole; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/user/UserLoginRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/user/UserLoginRequest.java new file mode 100644 index 0000000..85c241e --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/user/UserLoginRequest.java @@ -0,0 +1,20 @@ +package com.yupi.springbootinit.model.dto.user; + +import java.io.Serializable; +import lombok.Data; + +/** + * 用户登录请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class UserLoginRequest implements Serializable { + + private static final long serialVersionUID = 3191241716373120793L; + + private String userAccount; + + private String userPassword; +} diff --git a/src/main/java/com/yupi/springbootinit/model/dto/user/UserQueryRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/user/UserQueryRequest.java new file mode 100644 index 0000000..7b0e3d4 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/user/UserQueryRequest.java @@ -0,0 +1,48 @@ +package com.yupi.springbootinit.model.dto.user; + +import com.yupi.springbootinit.common.PageRequest; +import java.io.Serializable; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 用户查询请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class UserQueryRequest extends PageRequest implements Serializable { + /** + * id + */ + private Long id; + + /** + * 开放平台id + */ + private String unionId; + + /** + * 公众号openId + */ + private String mpOpenId; + + /** + * 用户昵称 + */ + private String userName; + + /** + * 简介 + */ + private String userProfile; + + /** + * 用户角色:user/admin/ban + */ + private String userRole; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/user/UserRegisterRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/user/UserRegisterRequest.java new file mode 100644 index 0000000..ded70ed --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/user/UserRegisterRequest.java @@ -0,0 +1,22 @@ +package com.yupi.springbootinit.model.dto.user; + +import java.io.Serializable; +import lombok.Data; + +/** + * 用户注册请求体 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class UserRegisterRequest implements Serializable { + + private static final long serialVersionUID = 3191241716373120793L; + + private String userAccount; + + private String userPassword; + + private String checkPassword; +} diff --git a/src/main/java/com/yupi/springbootinit/model/dto/user/UserUpdateMyRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/user/UserUpdateMyRequest.java new file mode 100644 index 0000000..f07f55a --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/user/UserUpdateMyRequest.java @@ -0,0 +1,31 @@ +package com.yupi.springbootinit.model.dto.user; + +import java.io.Serializable; +import lombok.Data; + +/** + * 用户更新个人信息请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class UserUpdateMyRequest implements Serializable { + + /** + * 用户昵称 + */ + private String userName; + + /** + * 用户头像 + */ + private String userAvatar; + + /** + * 简介 + */ + private String userProfile; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/user/UserUpdateRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/user/UserUpdateRequest.java new file mode 100644 index 0000000..6536de5 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/user/UserUpdateRequest.java @@ -0,0 +1,40 @@ +package com.yupi.springbootinit.model.dto.user; + +import java.io.Serializable; +import lombok.Data; + +/** + * 用户更新请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class UserUpdateRequest implements Serializable { + /** + * id + */ + private Long id; + + /** + * 用户昵称 + */ + private String userName; + + /** + * 用户头像 + */ + private String userAvatar; + + /** + * 简介 + */ + private String userProfile; + + /** + * 用户角色:user/admin/ban + */ + private String userRole; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/entity/NewHosts.java b/src/main/java/com/yupi/springbootinit/model/entity/NewHosts.java new file mode 100644 index 0000000..f61aad1 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/entity/NewHosts.java @@ -0,0 +1,90 @@ +package com.yupi.springbootinit.model.entity; + +import java.util.Date; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; + +/* +* @author: ziin +* @date: 2025/6/10 18:54 +*/ +@Data +public class NewHosts { + /** + * 主键 + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 主播id + */ + private String hostsId; + + /** + * 主播等级 + */ + private String hostsLevel; + + /** + * 主播金币 + */ + private Integer hostsCoins; + + /** + * 邀请类型 + */ + private Integer invitationType; + + /** + * 粉丝数量 + */ + private Integer fans; + + /** + * 关注数量 + */ + private Integer fllowernum; + + /** + * 昨日金币 + */ + private Integer yesterdayCoins; + + /** + * 主播国家 + */ + private String country; + + /** + * 直播类型 娱乐,游戏 + */ + private String hostsKind; + + /** + * 租户 Id + */ + private Long tenantId; + + /** + * 入库人 + */ + private Integer creator; + + /** + * 数据插入时间 + */ + private Date createTime; + + /** + * 更新人 + */ + private String updater; + + /** + * 更新时间 + */ + private Date updateTime; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/entity/Post.java b/src/main/java/com/yupi/springbootinit/model/entity/Post.java new file mode 100644 index 0000000..3c0e301 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/entity/Post.java @@ -0,0 +1,76 @@ +package com.yupi.springbootinit.model.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.util.Date; +import lombok.Data; + +/** + * 帖子 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@TableName(value = "post") +@Data +public class Post implements Serializable { + + /** + * id + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 标签列表 json + */ + private String tags; + + /** + * 点赞数 + */ + private Integer thumbNum; + + /** + * 收藏数 + */ + private Integer favourNum; + + /** + * 创建用户 id + */ + private Long userId; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 是否删除 + */ + @TableLogic + private Integer isDelete; + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/entity/PostFavour.java b/src/main/java/com/yupi/springbootinit/model/entity/PostFavour.java new file mode 100644 index 0000000..7626ec5 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/entity/PostFavour.java @@ -0,0 +1,49 @@ +package com.yupi.springbootinit.model.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.util.Date; +import lombok.Data; + +/** + * 帖子收藏 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + **/ +@TableName(value = "post_favour") +@Data +public class PostFavour implements Serializable { + + /** + * id + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 帖子 id + */ + private Long postId; + + /** + * 创建用户 id + */ + private Long userId; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/entity/PostThumb.java b/src/main/java/com/yupi/springbootinit/model/entity/PostThumb.java new file mode 100644 index 0000000..c22f119 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/entity/PostThumb.java @@ -0,0 +1,49 @@ +package com.yupi.springbootinit.model.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.util.Date; +import lombok.Data; + +/** + * 帖子点赞 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@TableName(value = "post_thumb") +@Data +public class PostThumb implements Serializable { + + /** + * id + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 帖子 id + */ + private Long postId; + + /** + * 创建用户 id + */ + private Long userId; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/entity/User.java b/src/main/java/com/yupi/springbootinit/model/entity/User.java new file mode 100644 index 0000000..b128c5b --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/entity/User.java @@ -0,0 +1,86 @@ +package com.yupi.springbootinit.model.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.util.Date; +import lombok.Data; + +/** + * 用户 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@TableName(value = "user") +@Data +public class User implements Serializable { + + /** + * id + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 用户账号 + */ + private String userAccount; + + /** + * 用户密码 + */ + private String userPassword; + + /** + * 开放平台id + */ + private String unionId; + + /** + * 公众号openId + */ + private String mpOpenId; + + /** + * 用户昵称 + */ + private String userName; + + /** + * 用户头像 + */ + private String userAvatar; + + /** + * 用户简介 + */ + private String userProfile; + + /** + * 用户角色:user/admin/ban + */ + private String userRole; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 是否删除 + */ + @TableLogic + private Integer isDelete; + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/enums/FileUploadBizEnum.java b/src/main/java/com/yupi/springbootinit/model/enums/FileUploadBizEnum.java new file mode 100644 index 0000000..da246eb --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/enums/FileUploadBizEnum.java @@ -0,0 +1,61 @@ +package com.yupi.springbootinit.model.enums; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.commons.lang3.ObjectUtils; + +/** + * 文件上传业务类型枚举 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public enum FileUploadBizEnum { + + USER_AVATAR("用户头像", "user_avatar"); + + private final String text; + + private final String value; + + FileUploadBizEnum(String text, String value) { + this.text = text; + this.value = value; + } + + /** + * 获取值列表 + * + * @return + */ + public static List getValues() { + return Arrays.stream(values()).map(item -> item.value).collect(Collectors.toList()); + } + + /** + * 根据 value 获取枚举 + * + * @param value + * @return + */ + public static FileUploadBizEnum getEnumByValue(String value) { + if (ObjectUtils.isEmpty(value)) { + return null; + } + for (FileUploadBizEnum anEnum : FileUploadBizEnum.values()) { + if (anEnum.value.equals(value)) { + return anEnum; + } + } + return null; + } + + public String getValue() { + return value; + } + + public String getText() { + return text; + } +} diff --git a/src/main/java/com/yupi/springbootinit/model/enums/UserRoleEnum.java b/src/main/java/com/yupi/springbootinit/model/enums/UserRoleEnum.java new file mode 100644 index 0000000..3aeb3cc --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/enums/UserRoleEnum.java @@ -0,0 +1,63 @@ +package com.yupi.springbootinit.model.enums; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.commons.lang3.ObjectUtils; + +/** + * 用户角色枚举 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public enum UserRoleEnum { + + USER("用户", "user"), + ADMIN("管理员", "admin"), + BAN("被封号", "ban"); + + private final String text; + + private final String value; + + UserRoleEnum(String text, String value) { + this.text = text; + this.value = value; + } + + /** + * 获取值列表 + * + * @return + */ + public static List getValues() { + return Arrays.stream(values()).map(item -> item.value).collect(Collectors.toList()); + } + + /** + * 根据 value 获取枚举 + * + * @param value + * @return + */ + public static UserRoleEnum getEnumByValue(String value) { + if (ObjectUtils.isEmpty(value)) { + return null; + } + for (UserRoleEnum anEnum : UserRoleEnum.values()) { + if (anEnum.value.equals(value)) { + return anEnum; + } + } + return null; + } + + public String getValue() { + return value; + } + + public String getText() { + return text; + } +} diff --git a/src/main/java/com/yupi/springbootinit/model/vo/LoginUserVO.java b/src/main/java/com/yupi/springbootinit/model/vo/LoginUserVO.java new file mode 100644 index 0000000..47ebf8f --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/vo/LoginUserVO.java @@ -0,0 +1,52 @@ +package com.yupi.springbootinit.model.vo; + +import java.io.Serializable; +import java.util.Date; +import lombok.Data; + +/** + * 已登录用户视图(脱敏) + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + **/ +@Data +public class LoginUserVO implements Serializable { + + /** + * 用户 id + */ + private Long id; + + /** + * 用户昵称 + */ + private String userName; + + /** + * 用户头像 + */ + private String userAvatar; + + /** + * 用户简介 + */ + private String userProfile; + + /** + * 用户角色:user/admin/ban + */ + private String userRole; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/vo/PostVO.java b/src/main/java/com/yupi/springbootinit/model/vo/PostVO.java new file mode 100644 index 0000000..92aa7fd --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/vo/PostVO.java @@ -0,0 +1,112 @@ +package com.yupi.springbootinit.model.vo; + +import cn.hutool.json.JSONUtil; +import com.yupi.springbootinit.model.entity.Post; +import java.io.Serializable; +import java.util.Date; +import java.util.List; +import lombok.Data; +import org.springframework.beans.BeanUtils; + +/** + * 帖子视图 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class PostVO implements Serializable { + + /** + * id + */ + private Long id; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 点赞数 + */ + private Integer thumbNum; + + /** + * 收藏数 + */ + private Integer favourNum; + + /** + * 创建用户 id + */ + private Long userId; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 标签列表 + */ + private List tagList; + + /** + * 创建人信息 + */ + private UserVO user; + + /** + * 是否已点赞 + */ + private Boolean hasThumb; + + /** + * 是否已收藏 + */ + private Boolean hasFavour; + + /** + * 包装类转对象 + * + * @param postVO + * @return + */ + public static Post voToObj(PostVO postVO) { + if (postVO == null) { + return null; + } + Post post = new Post(); + BeanUtils.copyProperties(postVO, post); + List tagList = postVO.getTagList(); + post.setTags(JSONUtil.toJsonStr(tagList)); + return post; + } + + /** + * 对象转包装类 + * + * @param post + * @return + */ + public static PostVO objToVo(Post post) { + if (post == null) { + return null; + } + PostVO postVO = new PostVO(); + BeanUtils.copyProperties(post, postVO); + postVO.setTagList(JSONUtil.toList(post.getTags(), String.class)); + return postVO; + } +} diff --git a/src/main/java/com/yupi/springbootinit/model/vo/UserVO.java b/src/main/java/com/yupi/springbootinit/model/vo/UserVO.java new file mode 100644 index 0000000..536b1f4 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/vo/UserVO.java @@ -0,0 +1,47 @@ +package com.yupi.springbootinit.model.vo; + +import java.io.Serializable; +import java.util.Date; +import lombok.Data; + +/** + * 用户视图(脱敏) + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class UserVO implements Serializable { + + /** + * id + */ + private Long id; + + /** + * 用户昵称 + */ + private String userName; + + /** + * 用户头像 + */ + private String userAvatar; + + /** + * 用户简介 + */ + private String userProfile; + + /** + * 用户角色:user/admin/ban + */ + private String userRole; + + /** + * 创建时间 + */ + private Date createTime; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/rabbitMQ/MQReceiver.java b/src/main/java/com/yupi/springbootinit/rabbitMQ/MQReceiver.java new file mode 100644 index 0000000..3973e4a --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/rabbitMQ/MQReceiver.java @@ -0,0 +1,55 @@ +package com.yupi.springbootinit.rabbitMQ; + + +import cn.hutool.core.date.DateTime; +import com.rabbitmq.client.Channel; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.exception.BusinessException; +import com.yupi.springbootinit.model.entity.NewHosts; +import com.yupi.springbootinit.service.HostInfoService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.stereotype.Service; +import javax.annotation.Resource; +import java.io.IOException; +import java.util.List; + +@Service +@Slf4j +public class MQReceiver { + @Resource + private HostInfoService hostInfoService; + + +// //方法:接收消息 +// @RabbitListener(queues = "HOST_INFO_QUEUE") +// public void receive(List hosts, Channel channel, Message message) throws IOException { +// long deliveryTag = message.getMessageProperties().getDeliveryTag(); +// try { +// hostInfoService.processHosts(hosts); +// channel.basicAck(deliveryTag,false); +// log.info("deliveryTag:{}",deliveryTag); +// log.info("接收到的消息------->" + hosts.size()); +// }catch (Exception e){ +// channel.basicNack(deliveryTag,false,true); +// log.error("消息消费失败------->消息内容大小{}",hosts.size() ); +// } +// } + + @RabbitListener(queues = "HOST_INFO_QUEUE") + public void receive(List hosts, Channel channel, Message message) throws IOException { + long deliveryTag = message.getMessageProperties().getDeliveryTag(); + try { + // 等待所有异步任务完成 + hostInfoService.processHosts(hosts).join(); // 这里会抛出异常 + channel.basicAck(deliveryTag, false); + log.info("deliveryTag:{}", deliveryTag); + log.info("{} 消息消费内容大小-------> {}",DateTime.now(),hosts.size()); + } catch (Exception e) { + channel.basicNack(deliveryTag, false, false); + log.error("消息消费失败------->消息内容大小{}", hosts.size(), e); + throw new BusinessException(ErrorCode.QUEUE_CONSUMPTION_FAILURE); + } + } + } diff --git a/src/main/java/com/yupi/springbootinit/rabbitMQ/MQSender.java b/src/main/java/com/yupi/springbootinit/rabbitMQ/MQSender.java new file mode 100644 index 0000000..24547ea --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/rabbitMQ/MQSender.java @@ -0,0 +1,37 @@ +package com.yupi.springbootinit.rabbitMQ; + +import cn.hutool.core.date.DateTime; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.exception.BusinessException; +import com.yupi.springbootinit.model.entity.NewHosts; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.List; + +@Slf4j +@Service +public class MQSender { + + @Resource + private RabbitTemplate rabbitTemplate; + + + //方法:发送消息 + public void send(List list){ + try { + log.info("{} 接收到的消息数量----------->{}", DateTime.now(),list.size()); + this.rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter()); + //指定你队列的名字 + rabbitTemplate.convertAndSend("HOST_INFO_QUEUE",list); + }catch (Exception e){ + throw new BusinessException(ErrorCode.QUEUE_ERROR); + } + + } +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/service/HostInfoService.java b/src/main/java/com/yupi/springbootinit/service/HostInfoService.java new file mode 100644 index 0000000..856985b --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/service/HostInfoService.java @@ -0,0 +1,19 @@ +package com.yupi.springbootinit.service; + + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yupi.springbootinit.model.entity.NewHosts; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/* + * @author: ziin + * @date: 2025/6/10 19:04 + */ +public interface HostInfoService extends IService { + + public CompletableFuture saveHostInfo(List newHosts); + + public CompletableFuture processHosts(List hosts); +} diff --git a/src/main/java/com/yupi/springbootinit/service/PostFavourService.java b/src/main/java/com/yupi/springbootinit/service/PostFavourService.java new file mode 100644 index 0000000..1e29151 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/service/PostFavourService.java @@ -0,0 +1,47 @@ +package com.yupi.springbootinit.service; + +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yupi.springbootinit.model.entity.Post; +import com.yupi.springbootinit.model.entity.PostFavour; +import com.yupi.springbootinit.model.entity.User; + +/** + * 帖子收藏服务 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public interface PostFavourService extends IService { + + /** + * 帖子收藏 + * + * @param postId + * @param loginUser + * @return + */ + int doPostFavour(long postId, User loginUser); + + /** + * 分页获取用户收藏的帖子列表 + * + * @param page + * @param queryWrapper + * @param favourUserId + * @return + */ + Page listFavourPostByPage(IPage page, Wrapper queryWrapper, + long favourUserId); + + /** + * 帖子收藏(内部服务) + * + * @param userId + * @param postId + * @return + */ + int doPostFavourInner(long userId, long postId); +} diff --git a/src/main/java/com/yupi/springbootinit/service/PostService.java b/src/main/java/com/yupi/springbootinit/service/PostService.java new file mode 100644 index 0000000..d938f0b --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/service/PostService.java @@ -0,0 +1,60 @@ +package com.yupi.springbootinit.service; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yupi.springbootinit.model.dto.post.PostQueryRequest; +import com.yupi.springbootinit.model.entity.Post; +import com.yupi.springbootinit.model.vo.PostVO; +import javax.servlet.http.HttpServletRequest; + +/** + * 帖子服务 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public interface PostService extends IService { + + /** + * 校验 + * + * @param post + * @param add + */ + void validPost(Post post, boolean add); + + /** + * 获取查询条件 + * + * @param postQueryRequest + * @return + */ + QueryWrapper getQueryWrapper(PostQueryRequest postQueryRequest); + + /** + * 从 ES 查询 + * + * @param postQueryRequest + * @return + */ + Page searchFromEs(PostQueryRequest postQueryRequest); + + /** + * 获取帖子封装 + * + * @param post + * @param request + * @return + */ + PostVO getPostVO(Post post, HttpServletRequest request); + + /** + * 分页获取帖子封装 + * + * @param postPage + * @param request + * @return + */ + Page getPostVOPage(Page postPage, HttpServletRequest request); +} diff --git a/src/main/java/com/yupi/springbootinit/service/PostThumbService.java b/src/main/java/com/yupi/springbootinit/service/PostThumbService.java new file mode 100644 index 0000000..db731f9 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/service/PostThumbService.java @@ -0,0 +1,32 @@ +package com.yupi.springbootinit.service; + +import com.yupi.springbootinit.model.entity.PostThumb; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yupi.springbootinit.model.entity.User; + +/** + * 帖子点赞服务 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public interface PostThumbService extends IService { + + /** + * 点赞 + * + * @param postId + * @param loginUser + * @return + */ + int doPostThumb(long postId, User loginUser); + + /** + * 帖子点赞(内部服务) + * + * @param userId + * @param postId + * @return + */ + int doPostThumbInner(long userId, long postId); +} diff --git a/src/main/java/com/yupi/springbootinit/service/UserService.java b/src/main/java/com/yupi/springbootinit/service/UserService.java new file mode 100644 index 0000000..0f83cf7 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/service/UserService.java @@ -0,0 +1,121 @@ +package com.yupi.springbootinit.service; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yupi.springbootinit.model.dto.user.UserQueryRequest; +import com.yupi.springbootinit.model.entity.User; +import com.yupi.springbootinit.model.vo.LoginUserVO; +import com.yupi.springbootinit.model.vo.UserVO; +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import me.chanjar.weixin.common.bean.WxOAuth2UserInfo; + +/** + * 用户服务 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public interface UserService extends IService { + + /** + * 用户注册 + * + * @param userAccount 用户账户 + * @param userPassword 用户密码 + * @param checkPassword 校验密码 + * @return 新用户 id + */ + long userRegister(String userAccount, String userPassword, String checkPassword); + + /** + * 用户登录 + * + * @param userAccount 用户账户 + * @param userPassword 用户密码 + * @param request + * @return 脱敏后的用户信息 + */ + LoginUserVO userLogin(String userAccount, String userPassword, HttpServletRequest request); + + /** + * 用户登录(微信开放平台) + * + * @param wxOAuth2UserInfo 从微信获取的用户信息 + * @param request + * @return 脱敏后的用户信息 + */ + LoginUserVO userLoginByMpOpen(WxOAuth2UserInfo wxOAuth2UserInfo, HttpServletRequest request); + + /** + * 获取当前登录用户 + * + * @param request + * @return + */ + User getLoginUser(HttpServletRequest request); + + /** + * 获取当前登录用户(允许未登录) + * + * @param request + * @return + */ + User getLoginUserPermitNull(HttpServletRequest request); + + /** + * 是否为管理员 + * + * @param request + * @return + */ + boolean isAdmin(HttpServletRequest request); + + /** + * 是否为管理员 + * + * @param user + * @return + */ + boolean isAdmin(User user); + + /** + * 用户注销 + * + * @param request + * @return + */ + boolean userLogout(HttpServletRequest request); + + /** + * 获取脱敏的已登录用户信息 + * + * @return + */ + LoginUserVO getLoginUserVO(User user); + + /** + * 获取脱敏的用户信息 + * + * @param user + * @return + */ + UserVO getUserVO(User user); + + /** + * 获取脱敏的用户信息 + * + * @param userList + * @return + */ + List getUserVO(List userList); + + /** + * 获取查询条件 + * + * @param userQueryRequest + * @return + */ + QueryWrapper getQueryWrapper(UserQueryRequest userQueryRequest); + +} diff --git a/src/main/java/com/yupi/springbootinit/service/impl/HostInfoServiceImpl.java b/src/main/java/com/yupi/springbootinit/service/impl/HostInfoServiceImpl.java new file mode 100644 index 0000000..3dfeae4 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/service/impl/HostInfoServiceImpl.java @@ -0,0 +1,79 @@ +package com.yupi.springbootinit.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.google.common.collect.Lists; +import com.yupi.springbootinit.mapper.NewHostsMapper; +import com.yupi.springbootinit.model.entity.NewHosts; +import com.yupi.springbootinit.service.HostInfoService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StopWatch; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/* + * @author: ziin + * @date: 2025/6/10 19:04 + */ +@Service +@Slf4j +@Transactional(rollbackFor = Exception.class) +public class HostInfoServiceImpl extends ServiceImpl implements HostInfoService { + + + @Override + @Async("taskExecutor") + + public CompletableFuture saveHostInfo(List newHosts) { + try { + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + saveBatch(newHosts); + stopWatch.stop(); + long totalTimeMillis = stopWatch.getTotalTimeMillis(); + log.info(" 存储花费: {}ms", totalTimeMillis); + return CompletableFuture.completedFuture(null); + } catch (Exception e) { + // 将异常包装到Future,使调用方能处理 + return CompletableFuture.failedFuture(e); // Java9+ + } + } + + // public void processHosts(List hosts) { +// List> futures = new ArrayList<>(); +// // 分片提交(避免单批次过大) +// Lists.partition(hosts, 1500).forEach(batch -> { +// CompletableFuture future = this.saveHostInfo(batch); +// futures.add(future); +// }); +// CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) +// .whenComplete((result, ex) -> { +// if (ex != null) { +// log.error("部分批次处理失败", ex); +// } else { +// log.info("所有批次处理完成"); +// } +// // 这里可以触发其他业务逻辑(如发送通知) +// }); +// } + public CompletableFuture processHosts(List hosts) { + List> futures = new ArrayList<>(); + // 分片提交(避免单批次过大) + Lists.partition(hosts, 1500).forEach(batch -> { + CompletableFuture future = this.saveHostInfo(batch); + futures.add(future); + }); + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .whenComplete((result, ex) -> { + if (ex != null) { + log.error("部分批次处理失败", ex); + } else { + log.info("所有批次处理完成"); + } + }); + } +} diff --git a/src/main/java/com/yupi/springbootinit/service/impl/PostFavourServiceImpl.java b/src/main/java/com/yupi/springbootinit/service/impl/PostFavourServiceImpl.java new file mode 100644 index 0000000..08abe11 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/service/impl/PostFavourServiceImpl.java @@ -0,0 +1,116 @@ +package com.yupi.springbootinit.service.impl; + +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.exception.BusinessException; +import com.yupi.springbootinit.mapper.PostFavourMapper; +import com.yupi.springbootinit.model.entity.Post; +import com.yupi.springbootinit.model.entity.PostFavour; +import com.yupi.springbootinit.model.entity.User; +import com.yupi.springbootinit.service.PostFavourService; +import com.yupi.springbootinit.service.PostService; +import javax.annotation.Resource; +import org.springframework.aop.framework.AopContext; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * 帖子收藏服务实现 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Service +public class PostFavourServiceImpl extends ServiceImpl + implements PostFavourService { + + @Resource + private PostService postService; + + /** + * 帖子收藏 + * + * @param postId + * @param loginUser + * @return + */ + @Override + public int doPostFavour(long postId, User loginUser) { + // 判断是否存在 + Post post = postService.getById(postId); + if (post == null) { + throw new BusinessException(ErrorCode.NOT_FOUND_ERROR); + } + // 是否已帖子收藏 + long userId = loginUser.getId(); + // 每个用户串行帖子收藏 + // 锁必须要包裹住事务方法 + PostFavourService postFavourService = (PostFavourService) AopContext.currentProxy(); + synchronized (String.valueOf(userId).intern()) { + return postFavourService.doPostFavourInner(userId, postId); + } + } + + @Override + public Page listFavourPostByPage(IPage page, Wrapper queryWrapper, long favourUserId) { + if (favourUserId <= 0) { + return new Page<>(); + } + return baseMapper.listFavourPostByPage(page, queryWrapper, favourUserId); + } + + /** + * 封装了事务的方法 + * + * @param userId + * @param postId + * @return + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int doPostFavourInner(long userId, long postId) { + PostFavour postFavour = new PostFavour(); + postFavour.setUserId(userId); + postFavour.setPostId(postId); + QueryWrapper postFavourQueryWrapper = new QueryWrapper<>(postFavour); + PostFavour oldPostFavour = this.getOne(postFavourQueryWrapper); + boolean result; + // 已收藏 + if (oldPostFavour != null) { + result = this.remove(postFavourQueryWrapper); + if (result) { + // 帖子收藏数 - 1 + result = postService.update() + .eq("id", postId) + .gt("favourNum", 0) + .setSql("favourNum = favourNum - 1") + .update(); + return result ? -1 : 0; + } else { + throw new BusinessException(ErrorCode.SYSTEM_ERROR); + } + } else { + // 未帖子收藏 + result = this.save(postFavour); + if (result) { + // 帖子收藏数 + 1 + result = postService.update() + .eq("id", postId) + .setSql("favourNum = favourNum + 1") + .update(); + return result ? 1 : 0; + } else { + throw new BusinessException(ErrorCode.SYSTEM_ERROR); + } + } + } + +} + + + + diff --git a/src/main/java/com/yupi/springbootinit/service/impl/PostServiceImpl.java b/src/main/java/com/yupi/springbootinit/service/impl/PostServiceImpl.java new file mode 100644 index 0000000..791c8c4 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/service/impl/PostServiceImpl.java @@ -0,0 +1,312 @@ +package com.yupi.springbootinit.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.constant.CommonConstant; +import com.yupi.springbootinit.exception.BusinessException; +import com.yupi.springbootinit.exception.ThrowUtils; +import com.yupi.springbootinit.mapper.PostFavourMapper; +import com.yupi.springbootinit.mapper.PostMapper; +import com.yupi.springbootinit.mapper.PostThumbMapper; +import com.yupi.springbootinit.model.dto.post.PostEsDTO; +import com.yupi.springbootinit.model.dto.post.PostQueryRequest; +import com.yupi.springbootinit.model.entity.Post; +import com.yupi.springbootinit.model.entity.PostFavour; +import com.yupi.springbootinit.model.entity.PostThumb; +import com.yupi.springbootinit.model.entity.User; +import com.yupi.springbootinit.model.vo.PostVO; +import com.yupi.springbootinit.model.vo.UserVO; +import com.yupi.springbootinit.service.PostService; +import com.yupi.springbootinit.service.UserService; +import com.yupi.springbootinit.utils.SqlUtils; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import cn.hutool.core.collection.CollUtil; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.search.sort.SortBuilder; +import org.elasticsearch.search.sort.SortBuilders; +import org.elasticsearch.search.sort.SortOrder; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; +import org.springframework.data.elasticsearch.core.SearchHit; +import org.springframework.data.elasticsearch.core.SearchHits; +import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; +import org.springframework.stereotype.Service; + +/** + * 帖子服务实现 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Service +@Slf4j +public class PostServiceImpl extends ServiceImpl implements PostService { + + @Resource + private UserService userService; + + @Resource + private PostThumbMapper postThumbMapper; + + @Resource + private PostFavourMapper postFavourMapper; + + @Resource + private ElasticsearchRestTemplate elasticsearchRestTemplate; + + @Override + public void validPost(Post post, boolean add) { + if (post == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + String title = post.getTitle(); + String content = post.getContent(); + String tags = post.getTags(); + // 创建时,参数不能为空 + if (add) { + ThrowUtils.throwIf(StringUtils.isAnyBlank(title, content, tags), ErrorCode.PARAMS_ERROR); + } + // 有参数则校验 + if (StringUtils.isNotBlank(title) && title.length() > 80) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "标题过长"); + } + if (StringUtils.isNotBlank(content) && content.length() > 8192) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "内容过长"); + } + } + + /** + * 获取查询包装类 + * + * @param postQueryRequest + * @return + */ + @Override + public QueryWrapper getQueryWrapper(PostQueryRequest postQueryRequest) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + if (postQueryRequest == null) { + return queryWrapper; + } + String searchText = postQueryRequest.getSearchText(); + String sortField = postQueryRequest.getSortField(); + String sortOrder = postQueryRequest.getSortOrder(); + Long id = postQueryRequest.getId(); + String title = postQueryRequest.getTitle(); + String content = postQueryRequest.getContent(); + List tagList = postQueryRequest.getTags(); + Long userId = postQueryRequest.getUserId(); + Long notId = postQueryRequest.getNotId(); + // 拼接查询条件 + if (StringUtils.isNotBlank(searchText)) { + queryWrapper.and(qw -> qw.like("title", searchText).or().like("content", searchText)); + } + queryWrapper.like(StringUtils.isNotBlank(title), "title", title); + queryWrapper.like(StringUtils.isNotBlank(content), "content", content); + if (CollUtil.isNotEmpty(tagList)) { + for (String tag : tagList) { + queryWrapper.like("tags", "\"" + tag + "\""); + } + } + queryWrapper.ne(ObjectUtils.isNotEmpty(notId), "id", notId); + queryWrapper.eq(ObjectUtils.isNotEmpty(id), "id", id); + queryWrapper.eq(ObjectUtils.isNotEmpty(userId), "userId", userId); + queryWrapper.orderBy(SqlUtils.validSortField(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC), + sortField); + return queryWrapper; + } + + @Override + public Page searchFromEs(PostQueryRequest postQueryRequest) { + Long id = postQueryRequest.getId(); + Long notId = postQueryRequest.getNotId(); + String searchText = postQueryRequest.getSearchText(); + String title = postQueryRequest.getTitle(); + String content = postQueryRequest.getContent(); + List tagList = postQueryRequest.getTags(); + List orTagList = postQueryRequest.getOrTags(); + Long userId = postQueryRequest.getUserId(); + // es 起始页为 0 + long current = postQueryRequest.getCurrent() - 1; + long pageSize = postQueryRequest.getPageSize(); + String sortField = postQueryRequest.getSortField(); + String sortOrder = postQueryRequest.getSortOrder(); + BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); + // 过滤 + boolQueryBuilder.filter(QueryBuilders.termQuery("isDelete", 0)); + if (id != null) { + boolQueryBuilder.filter(QueryBuilders.termQuery("id", id)); + } + if (notId != null) { + boolQueryBuilder.mustNot(QueryBuilders.termQuery("id", notId)); + } + if (userId != null) { + boolQueryBuilder.filter(QueryBuilders.termQuery("userId", userId)); + } + // 必须包含所有标签 + if (CollUtil.isNotEmpty(tagList)) { + for (String tag : tagList) { + boolQueryBuilder.filter(QueryBuilders.termQuery("tags", tag)); + } + } + // 包含任何一个标签即可 + if (CollUtil.isNotEmpty(orTagList)) { + BoolQueryBuilder orTagBoolQueryBuilder = QueryBuilders.boolQuery(); + for (String tag : orTagList) { + orTagBoolQueryBuilder.should(QueryBuilders.termQuery("tags", tag)); + } + orTagBoolQueryBuilder.minimumShouldMatch(1); + boolQueryBuilder.filter(orTagBoolQueryBuilder); + } + // 按关键词检索 + if (StringUtils.isNotBlank(searchText)) { + boolQueryBuilder.should(QueryBuilders.matchQuery("title", searchText)); + boolQueryBuilder.should(QueryBuilders.matchQuery("description", searchText)); + boolQueryBuilder.should(QueryBuilders.matchQuery("content", searchText)); + boolQueryBuilder.minimumShouldMatch(1); + } + // 按标题检索 + if (StringUtils.isNotBlank(title)) { + boolQueryBuilder.should(QueryBuilders.matchQuery("title", title)); + boolQueryBuilder.minimumShouldMatch(1); + } + // 按内容检索 + if (StringUtils.isNotBlank(content)) { + boolQueryBuilder.should(QueryBuilders.matchQuery("content", content)); + boolQueryBuilder.minimumShouldMatch(1); + } + // 排序 + SortBuilder sortBuilder = SortBuilders.scoreSort(); + if (StringUtils.isNotBlank(sortField)) { + sortBuilder = SortBuilders.fieldSort(sortField); + sortBuilder.order(CommonConstant.SORT_ORDER_ASC.equals(sortOrder) ? SortOrder.ASC : SortOrder.DESC); + } + // 分页 + PageRequest pageRequest = PageRequest.of((int) current, (int) pageSize); + // 构造查询 + NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder) + .withPageable(pageRequest).withSorts(sortBuilder).build(); + SearchHits searchHits = elasticsearchRestTemplate.search(searchQuery, PostEsDTO.class); + Page page = new Page<>(); + page.setTotal(searchHits.getTotalHits()); + List resourceList = new ArrayList<>(); + // 查出结果后,从 db 获取最新动态数据(比如点赞数) + if (searchHits.hasSearchHits()) { + List> searchHitList = searchHits.getSearchHits(); + List postIdList = searchHitList.stream().map(searchHit -> searchHit.getContent().getId()) + .collect(Collectors.toList()); + List postList = baseMapper.selectBatchIds(postIdList); + if (postList != null) { + Map> idPostMap = postList.stream().collect(Collectors.groupingBy(Post::getId)); + postIdList.forEach(postId -> { + if (idPostMap.containsKey(postId)) { + resourceList.add(idPostMap.get(postId).get(0)); + } else { + // 从 es 清空 db 已物理删除的数据 + String delete = elasticsearchRestTemplate.delete(String.valueOf(postId), PostEsDTO.class); + log.info("delete post {}", delete); + } + }); + } + } + page.setRecords(resourceList); + return page; + } + + @Override + public PostVO getPostVO(Post post, HttpServletRequest request) { + PostVO postVO = PostVO.objToVo(post); + long postId = post.getId(); + // 1. 关联查询用户信息 + Long userId = post.getUserId(); + User user = null; + if (userId != null && userId > 0) { + user = userService.getById(userId); + } + UserVO userVO = userService.getUserVO(user); + postVO.setUser(userVO); + // 2. 已登录,获取用户点赞、收藏状态 + User loginUser = userService.getLoginUserPermitNull(request); + if (loginUser != null) { + // 获取点赞 + QueryWrapper postThumbQueryWrapper = new QueryWrapper<>(); + postThumbQueryWrapper.in("postId", postId); + postThumbQueryWrapper.eq("userId", loginUser.getId()); + PostThumb postThumb = postThumbMapper.selectOne(postThumbQueryWrapper); + postVO.setHasThumb(postThumb != null); + // 获取收藏 + QueryWrapper postFavourQueryWrapper = new QueryWrapper<>(); + postFavourQueryWrapper.in("postId", postId); + postFavourQueryWrapper.eq("userId", loginUser.getId()); + PostFavour postFavour = postFavourMapper.selectOne(postFavourQueryWrapper); + postVO.setHasFavour(postFavour != null); + } + return postVO; + } + + @Override + public Page getPostVOPage(Page postPage, HttpServletRequest request) { + List postList = postPage.getRecords(); + Page postVOPage = new Page<>(postPage.getCurrent(), postPage.getSize(), postPage.getTotal()); + if (CollUtil.isEmpty(postList)) { + return postVOPage; + } + // 1. 关联查询用户信息 + Set userIdSet = postList.stream().map(Post::getUserId).collect(Collectors.toSet()); + Map> userIdUserListMap = userService.listByIds(userIdSet).stream() + .collect(Collectors.groupingBy(User::getId)); + // 2. 已登录,获取用户点赞、收藏状态 + Map postIdHasThumbMap = new HashMap<>(); + Map postIdHasFavourMap = new HashMap<>(); + User loginUser = userService.getLoginUserPermitNull(request); + if (loginUser != null) { + Set postIdSet = postList.stream().map(Post::getId).collect(Collectors.toSet()); + loginUser = userService.getLoginUser(request); + // 获取点赞 + QueryWrapper postThumbQueryWrapper = new QueryWrapper<>(); + postThumbQueryWrapper.in("postId", postIdSet); + postThumbQueryWrapper.eq("userId", loginUser.getId()); + List postPostThumbList = postThumbMapper.selectList(postThumbQueryWrapper); + postPostThumbList.forEach(postPostThumb -> postIdHasThumbMap.put(postPostThumb.getPostId(), true)); + // 获取收藏 + QueryWrapper postFavourQueryWrapper = new QueryWrapper<>(); + postFavourQueryWrapper.in("postId", postIdSet); + postFavourQueryWrapper.eq("userId", loginUser.getId()); + List postFavourList = postFavourMapper.selectList(postFavourQueryWrapper); + postFavourList.forEach(postFavour -> postIdHasFavourMap.put(postFavour.getPostId(), true)); + } + // 填充信息 + List postVOList = postList.stream().map(post -> { + PostVO postVO = PostVO.objToVo(post); + Long userId = post.getUserId(); + User user = null; + if (userIdUserListMap.containsKey(userId)) { + user = userIdUserListMap.get(userId).get(0); + } + postVO.setUser(userService.getUserVO(user)); + postVO.setHasThumb(postIdHasThumbMap.getOrDefault(post.getId(), false)); + postVO.setHasFavour(postIdHasFavourMap.getOrDefault(post.getId(), false)); + return postVO; + }).collect(Collectors.toList()); + postVOPage.setRecords(postVOList); + return postVOPage; + } + +} + + + + diff --git a/src/main/java/com/yupi/springbootinit/service/impl/PostThumbServiceImpl.java b/src/main/java/com/yupi/springbootinit/service/impl/PostThumbServiceImpl.java new file mode 100644 index 0000000..a0b3c06 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/service/impl/PostThumbServiceImpl.java @@ -0,0 +1,105 @@ +package com.yupi.springbootinit.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.exception.BusinessException; +import com.yupi.springbootinit.mapper.PostThumbMapper; +import com.yupi.springbootinit.model.entity.Post; +import com.yupi.springbootinit.model.entity.PostThumb; +import com.yupi.springbootinit.model.entity.User; +import com.yupi.springbootinit.service.PostService; +import com.yupi.springbootinit.service.PostThumbService; +import javax.annotation.Resource; +import org.springframework.aop.framework.AopContext; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * 帖子点赞服务实现 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Service +public class PostThumbServiceImpl extends ServiceImpl + implements PostThumbService { + + @Resource + private PostService postService; + + /** + * 点赞 + * + * @param postId + * @param loginUser + * @return + */ + @Override + public int doPostThumb(long postId, User loginUser) { + // 判断实体是否存在,根据类别获取实体 + Post post = postService.getById(postId); + if (post == null) { + throw new BusinessException(ErrorCode.NOT_FOUND_ERROR); + } + // 是否已点赞 + long userId = loginUser.getId(); + // 每个用户串行点赞 + // 锁必须要包裹住事务方法 + PostThumbService postThumbService = (PostThumbService) AopContext.currentProxy(); + synchronized (String.valueOf(userId).intern()) { + return postThumbService.doPostThumbInner(userId, postId); + } + } + + /** + * 封装了事务的方法 + * + * @param userId + * @param postId + * @return + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int doPostThumbInner(long userId, long postId) { + PostThumb postThumb = new PostThumb(); + postThumb.setUserId(userId); + postThumb.setPostId(postId); + QueryWrapper thumbQueryWrapper = new QueryWrapper<>(postThumb); + PostThumb oldPostThumb = this.getOne(thumbQueryWrapper); + boolean result; + // 已点赞 + if (oldPostThumb != null) { + result = this.remove(thumbQueryWrapper); + if (result) { + // 点赞数 - 1 + result = postService.update() + .eq("id", postId) + .gt("thumbNum", 0) + .setSql("thumbNum = thumbNum - 1") + .update(); + return result ? -1 : 0; + } else { + throw new BusinessException(ErrorCode.SYSTEM_ERROR); + } + } else { + // 未点赞 + result = this.save(postThumb); + if (result) { + // 点赞数 + 1 + result = postService.update() + .eq("id", postId) + .setSql("thumbNum = thumbNum + 1") + .update(); + return result ? 1 : 0; + } else { + throw new BusinessException(ErrorCode.SYSTEM_ERROR); + } + } + } + +} + + + + diff --git a/src/main/java/com/yupi/springbootinit/service/impl/UserServiceImpl.java b/src/main/java/com/yupi/springbootinit/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..bde1928 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/service/impl/UserServiceImpl.java @@ -0,0 +1,272 @@ +package com.yupi.springbootinit.service.impl; + +import static com.yupi.springbootinit.constant.UserConstant.USER_LOGIN_STATE; + +import cn.hutool.core.collection.CollUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.constant.CommonConstant; +import com.yupi.springbootinit.exception.BusinessException; +import com.yupi.springbootinit.mapper.UserMapper; +import com.yupi.springbootinit.model.dto.user.UserQueryRequest; +import com.yupi.springbootinit.model.entity.User; +import com.yupi.springbootinit.model.enums.UserRoleEnum; +import com.yupi.springbootinit.model.vo.LoginUserVO; +import com.yupi.springbootinit.model.vo.UserVO; +import com.yupi.springbootinit.service.UserService; +import com.yupi.springbootinit.utils.SqlUtils; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.bean.WxOAuth2UserInfo; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; +import org.springframework.util.DigestUtils; + +/** + * 用户服务实现 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Service +@Slf4j +public class UserServiceImpl extends ServiceImpl implements UserService { + + /** + * 盐值,混淆密码 + */ + public static final String SALT = "yupi"; + + @Override + public long userRegister(String userAccount, String userPassword, String checkPassword) { + // 1. 校验 + if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword)) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空"); + } + if (userAccount.length() < 4) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号过短"); + } + if (userPassword.length() < 8 || checkPassword.length() < 8) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户密码过短"); + } + // 密码和校验密码相同 + if (!userPassword.equals(checkPassword)) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "两次输入的密码不一致"); + } + synchronized (userAccount.intern()) { + // 账户不能重复 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("userAccount", userAccount); + long count = this.baseMapper.selectCount(queryWrapper); + if (count > 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号重复"); + } + // 2. 加密 + String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes()); + // 3. 插入数据 + User user = new User(); + user.setUserAccount(userAccount); + user.setUserPassword(encryptPassword); + boolean saveResult = this.save(user); + if (!saveResult) { + throw new BusinessException(ErrorCode.SYSTEM_ERROR, "注册失败,数据库错误"); + } + return user.getId(); + } + } + + @Override + public LoginUserVO userLogin(String userAccount, String userPassword, HttpServletRequest request) { + // 1. 校验 + if (StringUtils.isAnyBlank(userAccount, userPassword)) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空"); + } + if (userAccount.length() < 4) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号错误"); + } + if (userPassword.length() < 8) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码错误"); + } + // 2. 加密 + String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes()); + // 查询用户是否存在 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("userAccount", userAccount); + queryWrapper.eq("userPassword", encryptPassword); + User user = this.baseMapper.selectOne(queryWrapper); + // 用户不存在 + if (user == null) { + log.info("user login failed, userAccount cannot match userPassword"); + throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户不存在或密码错误"); + } + // 3. 记录用户的登录态 + request.getSession().setAttribute(USER_LOGIN_STATE, user); + return this.getLoginUserVO(user); + } + + @Override + public LoginUserVO userLoginByMpOpen(WxOAuth2UserInfo wxOAuth2UserInfo, HttpServletRequest request) { + String unionId = wxOAuth2UserInfo.getUnionId(); + String mpOpenId = wxOAuth2UserInfo.getOpenid(); + // 单机锁 + synchronized (unionId.intern()) { + // 查询用户是否已存在 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("unionId", unionId); + User user = this.getOne(queryWrapper); + // 被封号,禁止登录 + if (user != null && UserRoleEnum.BAN.getValue().equals(user.getUserRole())) { + throw new BusinessException(ErrorCode.FORBIDDEN_ERROR, "该用户已被封,禁止登录"); + } + // 用户不存在则创建 + if (user == null) { + user = new User(); + user.setUnionId(unionId); + user.setMpOpenId(mpOpenId); + user.setUserAvatar(wxOAuth2UserInfo.getHeadImgUrl()); + user.setUserName(wxOAuth2UserInfo.getNickname()); + boolean result = this.save(user); + if (!result) { + throw new BusinessException(ErrorCode.SYSTEM_ERROR, "登录失败"); + } + } + // 记录用户的登录态 + request.getSession().setAttribute(USER_LOGIN_STATE, user); + return getLoginUserVO(user); + } + } + + /** + * 获取当前登录用户 + * + * @param request + * @return + */ + @Override + public User getLoginUser(HttpServletRequest request) { + // 先判断是否已登录 + Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE); + User currentUser = (User) userObj; + if (currentUser == null || currentUser.getId() == null) { + throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR); + } + // 从数据库查询(追求性能的话可以注释,直接走缓存) + long userId = currentUser.getId(); + currentUser = this.getById(userId); + if (currentUser == null) { + throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR); + } + return currentUser; + } + + /** + * 获取当前登录用户(允许未登录) + * + * @param request + * @return + */ + @Override + public User getLoginUserPermitNull(HttpServletRequest request) { + // 先判断是否已登录 + Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE); + User currentUser = (User) userObj; + if (currentUser == null || currentUser.getId() == null) { + return null; + } + // 从数据库查询(追求性能的话可以注释,直接走缓存) + long userId = currentUser.getId(); + return this.getById(userId); + } + + /** + * 是否为管理员 + * + * @param request + * @return + */ + @Override + public boolean isAdmin(HttpServletRequest request) { + // 仅管理员可查询 + Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE); + User user = (User) userObj; + return isAdmin(user); + } + + @Override + public boolean isAdmin(User user) { + return user != null && UserRoleEnum.ADMIN.getValue().equals(user.getUserRole()); + } + + /** + * 用户注销 + * + * @param request + */ + @Override + public boolean userLogout(HttpServletRequest request) { + if (request.getSession().getAttribute(USER_LOGIN_STATE) == null) { + throw new BusinessException(ErrorCode.OPERATION_ERROR, "未登录"); + } + // 移除登录态 + request.getSession().removeAttribute(USER_LOGIN_STATE); + return true; + } + + @Override + public LoginUserVO getLoginUserVO(User user) { + if (user == null) { + return null; + } + LoginUserVO loginUserVO = new LoginUserVO(); + BeanUtils.copyProperties(user, loginUserVO); + return loginUserVO; + } + + @Override + public UserVO getUserVO(User user) { + if (user == null) { + return null; + } + UserVO userVO = new UserVO(); + BeanUtils.copyProperties(user, userVO); + return userVO; + } + + @Override + public List getUserVO(List userList) { + if (CollUtil.isEmpty(userList)) { + return new ArrayList<>(); + } + return userList.stream().map(this::getUserVO).collect(Collectors.toList()); + } + + @Override + public QueryWrapper getQueryWrapper(UserQueryRequest userQueryRequest) { + if (userQueryRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空"); + } + Long id = userQueryRequest.getId(); + String unionId = userQueryRequest.getUnionId(); + String mpOpenId = userQueryRequest.getMpOpenId(); + String userName = userQueryRequest.getUserName(); + String userProfile = userQueryRequest.getUserProfile(); + String userRole = userQueryRequest.getUserRole(); + String sortField = userQueryRequest.getSortField(); + String sortOrder = userQueryRequest.getSortOrder(); + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq(id != null, "id", id); + queryWrapper.eq(StringUtils.isNotBlank(unionId), "unionId", unionId); + queryWrapper.eq(StringUtils.isNotBlank(mpOpenId), "mpOpenId", mpOpenId); + queryWrapper.eq(StringUtils.isNotBlank(userRole), "userRole", userRole); + queryWrapper.like(StringUtils.isNotBlank(userProfile), "userProfile", userProfile); + queryWrapper.like(StringUtils.isNotBlank(userName), "userName", userName); + queryWrapper.orderBy(SqlUtils.validSortField(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC), + sortField); + return queryWrapper; + } +} diff --git a/src/main/java/com/yupi/springbootinit/utils/NetUtils.java b/src/main/java/com/yupi/springbootinit/utils/NetUtils.java new file mode 100644 index 0000000..88069fb --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/utils/NetUtils.java @@ -0,0 +1,55 @@ +package com.yupi.springbootinit.utils; + +import java.net.InetAddress; +import javax.servlet.http.HttpServletRequest; + +/** + * 网络工具类 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public class NetUtils { + + /** + * 获取客户端 IP 地址 + * + * @param request + * @return + */ + public static String getIpAddress(HttpServletRequest request) { + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + if (ip.equals("127.0.0.1")) { + // 根据网卡取本机配置的 IP + InetAddress inet = null; + try { + inet = InetAddress.getLocalHost(); + } catch (Exception e) { + e.printStackTrace(); + } + if (inet != null) { + ip = inet.getHostAddress(); + } + } + } + // 多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 + if (ip != null && ip.length() > 15) { + if (ip.indexOf(",") > 0) { + ip = ip.substring(0, ip.indexOf(",")); + } + } + if (ip == null) { + return "127.0.0.1"; + } + return ip; + } + +} diff --git a/src/main/java/com/yupi/springbootinit/utils/SpringContextUtils.java b/src/main/java/com/yupi/springbootinit/utils/SpringContextUtils.java new file mode 100644 index 0000000..60001f6 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/utils/SpringContextUtils.java @@ -0,0 +1,57 @@ +package com.yupi.springbootinit.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 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Component +public class SpringContextUtils implements ApplicationContextAware { + + private static ApplicationContext applicationContext; + + @Override + public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException { + SpringContextUtils.applicationContext = applicationContext; + } + + /** + * 通过名称获取 Bean + * + * @param beanName + * @return + */ + public static Object getBean(String beanName) { + return applicationContext.getBean(beanName); + } + + /** + * 通过 class 获取 Bean + * + * @param beanClass + * @param + * @return + */ + public static T getBean(Class beanClass) { + return applicationContext.getBean(beanClass); + } + + /** + * 通过名称和类型获取 Bean + * + * @param beanName + * @param beanClass + * @param + * @return + */ + public static T getBean(String beanName, Class beanClass) { + return applicationContext.getBean(beanName, beanClass); + } +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/utils/SqlUtils.java b/src/main/java/com/yupi/springbootinit/utils/SqlUtils.java new file mode 100644 index 0000000..1efeaa4 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/utils/SqlUtils.java @@ -0,0 +1,25 @@ +package com.yupi.springbootinit.utils; + +import org.apache.commons.lang3.StringUtils; + +/** + * SQL 工具 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public class SqlUtils { + + /** + * 校验排序字段是否合法(防止 SQL 注入) + * + * @param sortField + * @return + */ + public static boolean validSortField(String sortField) { + if (StringUtils.isBlank(sortField)) { + return false; + } + return !StringUtils.containsAny(sortField, "=", "(", ")", " "); + } +} diff --git a/src/main/java/com/yupi/springbootinit/wxmp/WxMpConstant.java b/src/main/java/com/yupi/springbootinit/wxmp/WxMpConstant.java new file mode 100644 index 0000000..04256b6 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/wxmp/WxMpConstant.java @@ -0,0 +1,16 @@ +package com.yupi.springbootinit.wxmp; + +/** + * 微信公众号相关常量 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + **/ +public class WxMpConstant { + + /** + * 点击菜单 key + */ + public static final String CLICK_MENU_KEY = "CLICK_MENU_KEY"; + +} diff --git a/src/main/java/com/yupi/springbootinit/wxmp/WxMpMsgRouter.java b/src/main/java/com/yupi/springbootinit/wxmp/WxMpMsgRouter.java new file mode 100644 index 0000000..a449923 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/wxmp/WxMpMsgRouter.java @@ -0,0 +1,61 @@ +package com.yupi.springbootinit.wxmp; + +import com.yupi.springbootinit.wxmp.handler.EventHandler; +import com.yupi.springbootinit.wxmp.handler.MessageHandler; +import com.yupi.springbootinit.wxmp.handler.SubscribeHandler; +import javax.annotation.Resource; +import me.chanjar.weixin.common.api.WxConsts.EventType; +import me.chanjar.weixin.common.api.WxConsts.XmlMsgType; +import me.chanjar.weixin.mp.api.WxMpMessageRouter; +import me.chanjar.weixin.mp.api.WxMpService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 微信公众号路由 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Configuration +public class WxMpMsgRouter { + + @Resource + private WxMpService wxMpService; + + @Resource + private EventHandler eventHandler; + + @Resource + private MessageHandler messageHandler; + + @Resource + private SubscribeHandler subscribeHandler; + + @Bean + public WxMpMessageRouter getWxMsgRouter() { + WxMpMessageRouter router = new WxMpMessageRouter(wxMpService); + // 消息 + router.rule() + .async(false) + .msgType(XmlMsgType.TEXT) + .handler(messageHandler) + .end(); + // 关注 + router.rule() + .async(false) + .msgType(XmlMsgType.EVENT) + .event(EventType.SUBSCRIBE) + .handler(subscribeHandler) + .end(); + // 点击按钮 + router.rule() + .async(false) + .msgType(XmlMsgType.EVENT) + .event(EventType.CLICK) + .eventKey(WxMpConstant.CLICK_MENU_KEY) + .handler(eventHandler) + .end(); + return router; + } +} diff --git a/src/main/java/com/yupi/springbootinit/wxmp/handler/EventHandler.java b/src/main/java/com/yupi/springbootinit/wxmp/handler/EventHandler.java new file mode 100644 index 0000000..61c348d --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/wxmp/handler/EventHandler.java @@ -0,0 +1,31 @@ +package com.yupi.springbootinit.wxmp.handler; + +import java.util.Map; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpMessageHandler; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.springframework.stereotype.Component; + +/** + * 事件处理器 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + **/ +@Component +public class EventHandler implements WxMpMessageHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map map, WxMpService wxMpService, + WxSessionManager wxSessionManager) throws WxErrorException { + final String content = "您点击了菜单"; + // 调用接口,返回验证码 + return WxMpXmlOutMessage.TEXT().content(content) + .fromUser(wxMpXmlMessage.getToUser()) + .toUser(wxMpXmlMessage.getFromUser()) + .build(); + } +} diff --git a/src/main/java/com/yupi/springbootinit/wxmp/handler/MessageHandler.java b/src/main/java/com/yupi/springbootinit/wxmp/handler/MessageHandler.java new file mode 100644 index 0000000..4c71641 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/wxmp/handler/MessageHandler.java @@ -0,0 +1,30 @@ +package com.yupi.springbootinit.wxmp.handler; + +import java.util.Map; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpMessageHandler; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.springframework.stereotype.Component; + +/** + * 消息处理器 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + **/ +@Component +public class MessageHandler implements WxMpMessageHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map map, + WxMpService wxMpService, WxSessionManager wxSessionManager) throws WxErrorException { + String content = "我是复读机:" + wxMpXmlMessage.getContent(); + return WxMpXmlOutMessage.TEXT().content(content) + .fromUser(wxMpXmlMessage.getToUser()) + .toUser(wxMpXmlMessage.getFromUser()) + .build(); + } +} diff --git a/src/main/java/com/yupi/springbootinit/wxmp/handler/SubscribeHandler.java b/src/main/java/com/yupi/springbootinit/wxmp/handler/SubscribeHandler.java new file mode 100644 index 0000000..44fc233 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/wxmp/handler/SubscribeHandler.java @@ -0,0 +1,31 @@ +package com.yupi.springbootinit.wxmp.handler; + +import java.util.Map; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpMessageHandler; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.springframework.stereotype.Component; + +/** + * 关注处理器 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + **/ +@Component +public class SubscribeHandler implements WxMpMessageHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map map, + WxMpService wxMpService, WxSessionManager wxSessionManager) throws WxErrorException { + final String content = "感谢关注"; + // 调用接口,返回验证码 + return WxMpXmlOutMessage.TEXT().content(content) + .fromUser(wxMpXmlMessage.getToUser()) + .toUser(wxMpXmlMessage.getFromUser()) + .build(); + } +} diff --git a/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000..46f9d16 --- /dev/null +++ b/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,34 @@ +{ + "properties": [ + { + "name": "cos.client.accessKey", + "type": "java.lang.String", + "description": "Description for cos.client.accessKey." + }, + { + "name": "cos.client.secretKey", + "type": "java.lang.String", + "description": "Description for cos.client.secretKey." + }, + { + "name": "cos.client.region", + "type": "java.lang.String", + "description": "Description for cos.client.region." + }, + { + "name": "cos.client.bucket", + "type": "java.lang.String", + "description": "Description for cos.client.bucket." + }, + { + "name": "wx.open.appId", + "type": "java.lang.String", + "description": "Description for wx.open.appId." + }, + { + "name": "wx.open.appSecret", + "type": "java.lang.String", + "description": "Description for wx.open.appSecret." + } + ] +} \ No newline at end of file diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 0000000..67c0989 --- /dev/null +++ b/src/main/resources/application-prod.yml @@ -0,0 +1,46 @@ +# 线上配置文件 +# @author 程序员鱼皮 +# @from 编程导航知识星球 +server: + port: 8101 +spring: + # 数据库配置 + # todo 需替换配置 + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/ruoyi-vue-pro?rewriteBatchedStatements=true + username: root + password: 123asd + # Redis 配置 + # todo 需替换配置 + redis: + database: 1 + host: localhost + port: 6379 + timeout: 5000 + password: 123456 + rabbitmq: + host: localhost + port: 5672 + username: guest + password: guest + listener: + simple: + acknowledge-mode: manual + # Elasticsearch 配置 + # todo 需替换配置 + elasticsearch: + uris: http://localhost:9200 + username: root + password: 123456 +mybatis-plus: + configuration: + # 生产环境关闭日志 + log-impl: '' +# 接口文档配置 +knife4j: + basic: + enable: true + username: root + password: 123456 + diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml new file mode 100644 index 0000000..fff6c02 --- /dev/null +++ b/src/main/resources/application-test.yml @@ -0,0 +1,36 @@ +# 测试配置文件 +# @author 程序员鱼皮 +# @from 编程导航知识星球 +server: + port: 8101 +spring: + # 数据库配置 + # todo 需替换配置 + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/ruoyi-vue-pro?rewriteBatchedStatements=true + username: root + password: 123asd + # Redis 配置 + # todo 需替换配置 + redis: + database: 1 + host: localhost + port: 6379 + timeout: 5000 + password: 123456 + # Elasticsearch 配置 + # todo 需替换配置 + elasticsearch: + uris: http://localhost:9200 + username: root + password: 123456 + rabbitmq: + host: localhost + port: 5672 + username: guest + password: guest + listener: + simple: + acknowledge-mode: manual + diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..3c661a2 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,113 @@ +# 公共配置文件 +# @author 程序员鱼皮 +# @from 编程导航知识星球 +spring: + application: + name: springboot-init + # 默认 dev 环境 + profiles: + active: test + # 支持 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: 123asd + jackson: + date-format: yyyy-MM-dd HH:mm:ss + # 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: false + log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl + default-executor-type: batch + + + global-config: + db-config: + logic-delete-field: isDelete # 全局逻辑删除的实体字段名 + 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 +# 接口文档配置 +knife4j: + enable: true + openapi: + title: "接口文档" + version: 1.0 + group: + default: + api-rule: package + api-rule-resources: + - com.yupi.springbootinit.controller +logging: + level: + org: + apache: + ibatis: off \ No newline at end of file diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt new file mode 100644 index 0000000..c24dbad --- /dev/null +++ b/src/main/resources/banner.txt @@ -0,0 +1,2 @@ +by 程序员鱼皮:https://github.com/liyupi +可能是最好的编程学习圈子:https://yupi.icu diff --git a/src/main/resources/mapper/NewHostsMapper.xml b/src/main/resources/mapper/NewHostsMapper.xml new file mode 100644 index 0000000..0f73a32 --- /dev/null +++ b/src/main/resources/mapper/NewHostsMapper.xml @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + + + + + + + + + + + id, hosts_id, hosts_level, hosts_coins, Invitation_type, fans, fllowernum, yesterday_coins, + country, hosts_kind, tenant_id, creator, create_time, updater, update_time + + + + + delete from new_hosts + where id = #{id,jdbcType=BIGINT} + + + + insert into new_hosts (hosts_id, hosts_level, hosts_coins, + Invitation_type, fans, fllowernum, + yesterday_coins, country, hosts_kind, + tenant_id, creator, create_time, + updater, update_time) + values (#{hostsId,jdbcType=VARCHAR}, #{hostsLevel,jdbcType=VARCHAR}, #{hostsCoins,jdbcType=INTEGER}, + #{invitationType,jdbcType=INTEGER}, #{fans,jdbcType=INTEGER}, #{fllowernum,jdbcType=INTEGER}, + #{yesterdayCoins,jdbcType=INTEGER}, #{country,jdbcType=VARCHAR}, #{hostsKind,jdbcType=VARCHAR}, + #{tenantId,jdbcType=BIGINT}, #{creator,jdbcType=INTEGER}, #{createTime,jdbcType=TIMESTAMP}, + #{updater,jdbcType=VARCHAR}, #{updateTime,jdbcType=TIMESTAMP}) + + + + insert into new_hosts + + + hosts_id, + + + hosts_level, + + + hosts_coins, + + + Invitation_type, + + + fans, + + + fllowernum, + + + yesterday_coins, + + + country, + + + hosts_kind, + + + tenant_id, + + + creator, + + + create_time, + + + updater, + + + update_time, + + + + + #{hostsId,jdbcType=VARCHAR}, + + + #{hostsLevel,jdbcType=VARCHAR}, + + + #{hostsCoins,jdbcType=INTEGER}, + + + #{invitationType,jdbcType=INTEGER}, + + + #{fans,jdbcType=INTEGER}, + + + #{fllowernum,jdbcType=INTEGER}, + + + #{yesterdayCoins,jdbcType=INTEGER}, + + + #{country,jdbcType=VARCHAR}, + + + #{hostsKind,jdbcType=VARCHAR}, + + + #{tenantId,jdbcType=BIGINT}, + + + #{creator,jdbcType=INTEGER}, + + + #{createTime,jdbcType=TIMESTAMP}, + + + #{updater,jdbcType=VARCHAR}, + + + #{updateTime,jdbcType=TIMESTAMP}, + + + + + + update new_hosts + + + hosts_id = #{hostsId,jdbcType=VARCHAR}, + + + hosts_level = #{hostsLevel,jdbcType=VARCHAR}, + + + hosts_coins = #{hostsCoins,jdbcType=INTEGER}, + + + Invitation_type = #{invitationType,jdbcType=INTEGER}, + + + fans = #{fans,jdbcType=INTEGER}, + + + fllowernum = #{fllowernum,jdbcType=INTEGER}, + + + yesterday_coins = #{yesterdayCoins,jdbcType=INTEGER}, + + + country = #{country,jdbcType=VARCHAR}, + + + hosts_kind = #{hostsKind,jdbcType=VARCHAR}, + + + tenant_id = #{tenantId,jdbcType=BIGINT}, + + + creator = #{creator,jdbcType=INTEGER}, + + + create_time = #{createTime,jdbcType=TIMESTAMP}, + + + updater = #{updater,jdbcType=VARCHAR}, + + + update_time = #{updateTime,jdbcType=TIMESTAMP}, + + + where id = #{id,jdbcType=BIGINT} + + + + update new_hosts + set hosts_id = #{hostsId,jdbcType=VARCHAR}, + hosts_level = #{hostsLevel,jdbcType=VARCHAR}, + hosts_coins = #{hostsCoins,jdbcType=INTEGER}, + Invitation_type = #{invitationType,jdbcType=INTEGER}, + fans = #{fans,jdbcType=INTEGER}, + fllowernum = #{fllowernum,jdbcType=INTEGER}, + yesterday_coins = #{yesterdayCoins,jdbcType=INTEGER}, + country = #{country,jdbcType=VARCHAR}, + hosts_kind = #{hostsKind,jdbcType=VARCHAR}, + tenant_id = #{tenantId,jdbcType=BIGINT}, + creator = #{creator,jdbcType=INTEGER}, + create_time = #{createTime,jdbcType=TIMESTAMP}, + updater = #{updater,jdbcType=VARCHAR}, + update_time = #{updateTime,jdbcType=TIMESTAMP} + where id = #{id,jdbcType=BIGINT} + + + + insert into new_hosts + (hosts_id, hosts_level, hosts_coins, Invitation_type, fans, fllowernum, yesterday_coins, + country, hosts_kind, tenant_id, creator) + values + + (#{item.hostsId,jdbcType=VARCHAR}, #{item.hostsLevel,jdbcType=VARCHAR}, #{item.hostsCoins,jdbcType=INTEGER}, + #{item.invitationType,jdbcType=INTEGER}, #{item.fans,jdbcType=INTEGER}, #{item.fllowernum,jdbcType=INTEGER}, + #{item.yesterdayCoins,jdbcType=INTEGER}, #{item.country,jdbcType=VARCHAR}, #{item.hostsKind,jdbcType=VARCHAR}, + #{item.tenantId,jdbcType=BIGINT}, #{item.creator,jdbcType=INTEGER}) + + + \ No newline at end of file diff --git a/src/main/resources/mapper/PostFavourMapper.xml b/src/main/resources/mapper/PostFavourMapper.xml new file mode 100644 index 0000000..b7985c9 --- /dev/null +++ b/src/main/resources/mapper/PostFavourMapper.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + id,postId,userId, + createTime,updateTime + + + + diff --git a/src/main/resources/mapper/PostMapper.xml b/src/main/resources/mapper/PostMapper.xml new file mode 100644 index 0000000..d57b866 --- /dev/null +++ b/src/main/resources/mapper/PostMapper.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + id,title,content,tags, + thumbNum,favourNum,userId, + createTime,updateTime,isDelete + + + + diff --git a/src/main/resources/mapper/PostThumbMapper.xml b/src/main/resources/mapper/PostThumbMapper.xml new file mode 100644 index 0000000..07909ab --- /dev/null +++ b/src/main/resources/mapper/PostThumbMapper.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + id,postId, + userId,createTime,updateTime + + diff --git a/src/main/resources/mapper/UserMapper.xml b/src/main/resources/mapper/UserMapper.xml new file mode 100644 index 0000000..e61aa90 --- /dev/null +++ b/src/main/resources/mapper/UserMapper.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + id,unionId,mpOpenId, + userName,userAvatar,userProfile, + userRole,createTime,updateTime,isDelete + + diff --git a/src/main/resources/templates/TemplateController.java.ftl b/src/main/resources/templates/TemplateController.java.ftl new file mode 100644 index 0000000..e5714b5 --- /dev/null +++ b/src/main/resources/templates/TemplateController.java.ftl @@ -0,0 +1,239 @@ +package ${packageName}.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import ${packageName}.annotation.AuthCheck; +import ${packageName}.common.BaseResponse; +import ${packageName}.common.DeleteRequest; +import ${packageName}.common.ErrorCode; +import ${packageName}.common.ResultUtils; +import ${packageName}.constant.UserConstant; +import ${packageName}.exception.BusinessException; +import ${packageName}.exception.ThrowUtils; +import ${packageName}.model.dto.${dataKey}.${upperDataKey}AddRequest; +import ${packageName}.model.dto.${dataKey}.${upperDataKey}EditRequest; +import ${packageName}.model.dto.${dataKey}.${upperDataKey}QueryRequest; +import ${packageName}.model.dto.${dataKey}.${upperDataKey}UpdateRequest; +import ${packageName}.model.entity.${upperDataKey}; +import ${packageName}.model.entity.User; +import ${packageName}.model.vo.${upperDataKey}VO; +import ${packageName}.service.${upperDataKey}Service; +import ${packageName}.service.UserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; + +/** + * ${dataName}接口 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +@RestController +@RequestMapping("/${dataKey}") +@Slf4j +public class ${upperDataKey}Controller { + + @Resource + private ${upperDataKey}Service ${dataKey}Service; + + @Resource + private UserService userService; + + // region 增删改查 + + /** + * 创建${dataName} + * + * @param ${dataKey}AddRequest + * @param request + * @return + */ + @PostMapping("/add") + public BaseResponse add${upperDataKey}(@RequestBody ${upperDataKey}AddRequest ${dataKey}AddRequest, HttpServletRequest request) { + ThrowUtils.throwIf(${dataKey}AddRequest == null, ErrorCode.PARAMS_ERROR); + // todo 在此处将实体类和 DTO 进行转换 + ${upperDataKey} ${dataKey} = new ${upperDataKey}(); + BeanUtils.copyProperties(${dataKey}AddRequest, ${dataKey}); + // 数据校验 + ${dataKey}Service.valid${upperDataKey}(${dataKey}, true); + // todo 填充默认值 + User loginUser = userService.getLoginUser(request); + ${dataKey}.setUserId(loginUser.getId()); + // 写入数据库 + boolean result = ${dataKey}Service.save(${dataKey}); + ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); + // 返回新写入的数据 id + long new${upperDataKey}Id = ${dataKey}.getId(); + return ResultUtils.success(new${upperDataKey}Id); + } + + /** + * 删除${dataName} + * + * @param deleteRequest + * @param request + * @return + */ + @PostMapping("/delete") + public BaseResponse delete${upperDataKey}(@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(); + // 判断是否存在 + ${upperDataKey} old${upperDataKey} = ${dataKey}Service.getById(id); + ThrowUtils.throwIf(old${upperDataKey} == null, ErrorCode.NOT_FOUND_ERROR); + // 仅本人或管理员可删除 + if (!old${upperDataKey}.getUserId().equals(user.getId()) && !userService.isAdmin(request)) { + throw new BusinessException(ErrorCode.NO_AUTH_ERROR); + } + // 操作数据库 + boolean result = ${dataKey}Service.removeById(id); + ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); + return ResultUtils.success(true); + } + + /** + * 更新${dataName}(仅管理员可用) + * + * @param ${dataKey}UpdateRequest + * @return + */ + @PostMapping("/update") + @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) + public BaseResponse update${upperDataKey}(@RequestBody ${upperDataKey}UpdateRequest ${dataKey}UpdateRequest) { + if (${dataKey}UpdateRequest == null || ${dataKey}UpdateRequest.getId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + // todo 在此处将实体类和 DTO 进行转换 + ${upperDataKey} ${dataKey} = new ${upperDataKey}(); + BeanUtils.copyProperties(${dataKey}UpdateRequest, ${dataKey}); + // 数据校验 + ${dataKey}Service.valid${upperDataKey}(${dataKey}, false); + // 判断是否存在 + long id = ${dataKey}UpdateRequest.getId(); + ${upperDataKey} old${upperDataKey} = ${dataKey}Service.getById(id); + ThrowUtils.throwIf(old${upperDataKey} == null, ErrorCode.NOT_FOUND_ERROR); + // 操作数据库 + boolean result = ${dataKey}Service.updateById(${dataKey}); + ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); + return ResultUtils.success(true); + } + + /** + * 根据 id 获取${dataName}(封装类) + * + * @param id + * @return + */ + @GetMapping("/get/vo") + public BaseResponse<${upperDataKey}VO> get${upperDataKey}VOById(long id, HttpServletRequest request) { + ThrowUtils.throwIf(id <= 0, ErrorCode.PARAMS_ERROR); + // 查询数据库 + ${upperDataKey} ${dataKey} = ${dataKey}Service.getById(id); + ThrowUtils.throwIf(${dataKey} == null, ErrorCode.NOT_FOUND_ERROR); + // 获取封装类 + return ResultUtils.success(${dataKey}Service.get${upperDataKey}VO(${dataKey}, request)); + } + + /** + * 分页获取${dataName}列表(仅管理员可用) + * + * @param ${dataKey}QueryRequest + * @return + */ + @PostMapping("/list/page") + @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) + public BaseResponse> list${upperDataKey}ByPage(@RequestBody ${upperDataKey}QueryRequest ${dataKey}QueryRequest) { + long current = ${dataKey}QueryRequest.getCurrent(); + long size = ${dataKey}QueryRequest.getPageSize(); + // 查询数据库 + Page<${upperDataKey}> ${dataKey}Page = ${dataKey}Service.page(new Page<>(current, size), + ${dataKey}Service.getQueryWrapper(${dataKey}QueryRequest)); + return ResultUtils.success(${dataKey}Page); + } + + /** + * 分页获取${dataName}列表(封装类) + * + * @param ${dataKey}QueryRequest + * @param request + * @return + */ + @PostMapping("/list/page/vo") + public BaseResponse> list${upperDataKey}VOByPage(@RequestBody ${upperDataKey}QueryRequest ${dataKey}QueryRequest, + HttpServletRequest request) { + long current = ${dataKey}QueryRequest.getCurrent(); + long size = ${dataKey}QueryRequest.getPageSize(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); + // 查询数据库 + Page<${upperDataKey}> ${dataKey}Page = ${dataKey}Service.page(new Page<>(current, size), + ${dataKey}Service.getQueryWrapper(${dataKey}QueryRequest)); + // 获取封装类 + return ResultUtils.success(${dataKey}Service.get${upperDataKey}VOPage(${dataKey}Page, request)); + } + + /** + * 分页获取当前登录用户创建的${dataName}列表 + * + * @param ${dataKey}QueryRequest + * @param request + * @return + */ + @PostMapping("/my/list/page/vo") + public BaseResponse> listMy${upperDataKey}VOByPage(@RequestBody ${upperDataKey}QueryRequest ${dataKey}QueryRequest, + HttpServletRequest request) { + ThrowUtils.throwIf(${dataKey}QueryRequest == null, ErrorCode.PARAMS_ERROR); + // 补充查询条件,只查询当前登录用户的数据 + User loginUser = userService.getLoginUser(request); + ${dataKey}QueryRequest.setUserId(loginUser.getId()); + long current = ${dataKey}QueryRequest.getCurrent(); + long size = ${dataKey}QueryRequest.getPageSize(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); + // 查询数据库 + Page<${upperDataKey}> ${dataKey}Page = ${dataKey}Service.page(new Page<>(current, size), + ${dataKey}Service.getQueryWrapper(${dataKey}QueryRequest)); + // 获取封装类 + return ResultUtils.success(${dataKey}Service.get${upperDataKey}VOPage(${dataKey}Page, request)); + } + + /** + * 编辑${dataName}(给用户使用) + * + * @param ${dataKey}EditRequest + * @param request + * @return + */ + @PostMapping("/edit") + public BaseResponse edit${upperDataKey}(@RequestBody ${upperDataKey}EditRequest ${dataKey}EditRequest, HttpServletRequest request) { + if (${dataKey}EditRequest == null || ${dataKey}EditRequest.getId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + // todo 在此处将实体类和 DTO 进行转换 + ${upperDataKey} ${dataKey} = new ${upperDataKey}(); + BeanUtils.copyProperties(${dataKey}EditRequest, ${dataKey}); + // 数据校验 + ${dataKey}Service.valid${upperDataKey}(${dataKey}, false); + User loginUser = userService.getLoginUser(request); + // 判断是否存在 + long id = ${dataKey}EditRequest.getId(); + ${upperDataKey} old${upperDataKey} = ${dataKey}Service.getById(id); + ThrowUtils.throwIf(old${upperDataKey} == null, ErrorCode.NOT_FOUND_ERROR); + // 仅本人或管理员可编辑 + if (!old${upperDataKey}.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser)) { + throw new BusinessException(ErrorCode.NO_AUTH_ERROR); + } + // 操作数据库 + boolean result = ${dataKey}Service.updateById(${dataKey}); + ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); + return ResultUtils.success(true); + } + + // endregion +} diff --git a/src/main/resources/templates/TemplateService.java.ftl b/src/main/resources/templates/TemplateService.java.ftl new file mode 100644 index 0000000..a688488 --- /dev/null +++ b/src/main/resources/templates/TemplateService.java.ftl @@ -0,0 +1,53 @@ +package ${packageName}.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 ${packageName}.model.dto.${dataKey}.${upperDataKey}QueryRequest; +import ${packageName}.model.entity.${upperDataKey}; +import ${packageName}.model.vo.${upperDataKey}VO; + +import javax.servlet.http.HttpServletRequest; + +/** + * ${dataName}服务 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +public interface ${upperDataKey}Service extends IService<${upperDataKey}> { + + /** + * 校验数据 + * + * @param ${dataKey} + * @param add 对创建的数据进行校验 + */ + void valid${upperDataKey}(${upperDataKey} ${dataKey}, boolean add); + + /** + * 获取查询条件 + * + * @param ${dataKey}QueryRequest + * @return + */ + QueryWrapper<${upperDataKey}> getQueryWrapper(${upperDataKey}QueryRequest ${dataKey}QueryRequest); + + /** + * 获取${dataName}封装 + * + * @param ${dataKey} + * @param request + * @return + */ + ${upperDataKey}VO get${upperDataKey}VO(${upperDataKey} ${dataKey}, HttpServletRequest request); + + /** + * 分页获取${dataName}封装 + * + * @param ${dataKey}Page + * @param request + * @return + */ + Page<${upperDataKey}VO> get${upperDataKey}VOPage(Page<${upperDataKey}> ${dataKey}Page, HttpServletRequest request); +} diff --git a/src/main/resources/templates/TemplateServiceImpl.java.ftl b/src/main/resources/templates/TemplateServiceImpl.java.ftl new file mode 100644 index 0000000..3921f10 --- /dev/null +++ b/src/main/resources/templates/TemplateServiceImpl.java.ftl @@ -0,0 +1,224 @@ +package ${packageName}.service.impl; + +import cn.hutool.core.collection.CollUtil; +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 ${packageName}.common.ErrorCode; +import ${packageName}.constant.CommonConstant; +import ${packageName}.exception.ThrowUtils; +import ${packageName}.mapper.${upperDataKey}Mapper; +import ${packageName}.model.dto.${dataKey}.${upperDataKey}QueryRequest; +import ${packageName}.model.entity.${upperDataKey}; +import ${packageName}.model.entity.${upperDataKey}Favour; +import ${packageName}.model.entity.${upperDataKey}Thumb; +import ${packageName}.model.entity.User; +import ${packageName}.model.vo.${upperDataKey}VO; +import ${packageName}.model.vo.UserVO; +import ${packageName}.service.${upperDataKey}Service; +import ${packageName}.service.UserService; +import ${packageName}.utils.SqlUtils; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * ${dataName}服务实现 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +@Service +@Slf4j +public class ${upperDataKey}ServiceImpl extends ServiceImpl<${upperDataKey}Mapper, ${upperDataKey}> implements ${upperDataKey}Service { + + @Resource + private UserService userService; + + /** + * 校验数据 + * + * @param ${dataKey} + * @param add 对创建的数据进行校验 + */ + @Override + public void valid${upperDataKey}(${upperDataKey} ${dataKey}, boolean add) { + ThrowUtils.throwIf(${dataKey} == null, ErrorCode.PARAMS_ERROR); + // todo 从对象中取值 + String title = ${dataKey}.getTitle(); + // 创建数据时,参数不能为空 + if (add) { + // todo 补充校验规则 + ThrowUtils.throwIf(StringUtils.isBlank(title), ErrorCode.PARAMS_ERROR); + } + // 修改数据时,有参数则校验 + // todo 补充校验规则 + if (StringUtils.isNotBlank(title)) { + ThrowUtils.throwIf(title.length() > 80, ErrorCode.PARAMS_ERROR, "标题过长"); + } + } + + /** + * 获取查询条件 + * + * @param ${dataKey}QueryRequest + * @return + */ + @Override + public QueryWrapper<${upperDataKey}> getQueryWrapper(${upperDataKey}QueryRequest ${dataKey}QueryRequest) { + QueryWrapper<${upperDataKey}> queryWrapper = new QueryWrapper<>(); + if (${dataKey}QueryRequest == null) { + return queryWrapper; + } + // todo 从对象中取值 + Long id = ${dataKey}QueryRequest.getId(); + Long notId = ${dataKey}QueryRequest.getNotId(); + String title = ${dataKey}QueryRequest.getTitle(); + String content = ${dataKey}QueryRequest.getContent(); + String searchText = ${dataKey}QueryRequest.getSearchText(); + String sortField = ${dataKey}QueryRequest.getSortField(); + String sortOrder = ${dataKey}QueryRequest.getSortOrder(); + List tagList = ${dataKey}QueryRequest.getTags(); + Long userId = ${dataKey}QueryRequest.getUserId(); + // todo 补充需要的查询条件 + // 从多字段中搜索 + if (StringUtils.isNotBlank(searchText)) { + // 需要拼接查询条件 + queryWrapper.and(qw -> qw.like("title", searchText).or().like("content", searchText)); + } + // 模糊查询 + queryWrapper.like(StringUtils.isNotBlank(title), "title", title); + queryWrapper.like(StringUtils.isNotBlank(content), "content", content); + // JSON 数组查询 + if (CollUtil.isNotEmpty(tagList)) { + for (String tag : tagList) { + queryWrapper.like("tags", "\"" + tag + "\""); + } + } + // 精确查询 + queryWrapper.ne(ObjectUtils.isNotEmpty(notId), "id", notId); + queryWrapper.eq(ObjectUtils.isNotEmpty(id), "id", id); + queryWrapper.eq(ObjectUtils.isNotEmpty(userId), "userId", userId); + // 排序规则 + queryWrapper.orderBy(SqlUtils.validSortField(sortField), + sortOrder.equals(CommonConstant.SORT_ORDER_ASC), + sortField); + return queryWrapper; + } + + /** + * 获取${dataName}封装 + * + * @param ${dataKey} + * @param request + * @return + */ + @Override + public ${upperDataKey}VO get${upperDataKey}VO(${upperDataKey} ${dataKey}, HttpServletRequest request) { + // 对象转封装类 + ${upperDataKey}VO ${dataKey}VO = ${upperDataKey}VO.objToVo(${dataKey}); + + // todo 可以根据需要为封装对象补充值,不需要的内容可以删除 + // region 可选 + // 1. 关联查询用户信息 + Long userId = ${dataKey}.getUserId(); + User user = null; + if (userId != null && userId > 0) { + user = userService.getById(userId); + } + UserVO userVO = userService.getUserVO(user); + ${dataKey}VO.setUser(userVO); + // 2. 已登录,获取用户点赞、收藏状态 + long ${dataKey}Id = ${dataKey}.getId(); + User loginUser = userService.getLoginUserPermitNull(request); + if (loginUser != null) { + // 获取点赞 + QueryWrapper<${upperDataKey}Thumb> ${dataKey}ThumbQueryWrapper = new QueryWrapper<>(); + ${dataKey}ThumbQueryWrapper.in("${dataKey}Id", ${dataKey}Id); + ${dataKey}ThumbQueryWrapper.eq("userId", loginUser.getId()); + ${upperDataKey}Thumb ${dataKey}Thumb = ${dataKey}ThumbMapper.selectOne(${dataKey}ThumbQueryWrapper); + ${dataKey}VO.setHasThumb(${dataKey}Thumb != null); + // 获取收藏 + QueryWrapper<${upperDataKey}Favour> ${dataKey}FavourQueryWrapper = new QueryWrapper<>(); + ${dataKey}FavourQueryWrapper.in("${dataKey}Id", ${dataKey}Id); + ${dataKey}FavourQueryWrapper.eq("userId", loginUser.getId()); + ${upperDataKey}Favour ${dataKey}Favour = ${dataKey}FavourMapper.selectOne(${dataKey}FavourQueryWrapper); + ${dataKey}VO.setHasFavour(${dataKey}Favour != null); + } + // endregion + + return ${dataKey}VO; + } + + /** + * 分页获取${dataName}封装 + * + * @param ${dataKey}Page + * @param request + * @return + */ + @Override + public Page<${upperDataKey}VO> get${upperDataKey}VOPage(Page<${upperDataKey}> ${dataKey}Page, HttpServletRequest request) { + List<${upperDataKey}> ${dataKey}List = ${dataKey}Page.getRecords(); + Page<${upperDataKey}VO> ${dataKey}VOPage = new Page<>(${dataKey}Page.getCurrent(), ${dataKey}Page.getSize(), ${dataKey}Page.getTotal()); + if (CollUtil.isEmpty(${dataKey}List)) { + return ${dataKey}VOPage; + } + // 对象列表 => 封装对象列表 + List<${upperDataKey}VO> ${dataKey}VOList = ${dataKey}List.stream().map(${dataKey} -> { + return ${upperDataKey}VO.objToVo(${dataKey}); + }).collect(Collectors.toList()); + + // todo 可以根据需要为封装对象补充值,不需要的内容可以删除 + // region 可选 + // 1. 关联查询用户信息 + Set userIdSet = ${dataKey}List.stream().map(${upperDataKey}::getUserId).collect(Collectors.toSet()); + Map> userIdUserListMap = userService.listByIds(userIdSet).stream() + .collect(Collectors.groupingBy(User::getId)); + // 2. 已登录,获取用户点赞、收藏状态 + Map ${dataKey}IdHasThumbMap = new HashMap<>(); + Map ${dataKey}IdHasFavourMap = new HashMap<>(); + User loginUser = userService.getLoginUserPermitNull(request); + if (loginUser != null) { + Set ${dataKey}IdSet = ${dataKey}List.stream().map(${upperDataKey}::getId).collect(Collectors.toSet()); + loginUser = userService.getLoginUser(request); + // 获取点赞 + QueryWrapper<${upperDataKey}Thumb> ${dataKey}ThumbQueryWrapper = new QueryWrapper<>(); + ${dataKey}ThumbQueryWrapper.in("${dataKey}Id", ${dataKey}IdSet); + ${dataKey}ThumbQueryWrapper.eq("userId", loginUser.getId()); + List<${upperDataKey}Thumb> ${dataKey}${upperDataKey}ThumbList = ${dataKey}ThumbMapper.selectList(${dataKey}ThumbQueryWrapper); + ${dataKey}${upperDataKey}ThumbList.forEach(${dataKey}${upperDataKey}Thumb -> ${dataKey}IdHasThumbMap.put(${dataKey}${upperDataKey}Thumb.get${upperDataKey}Id(), true)); + // 获取收藏 + QueryWrapper<${upperDataKey}Favour> ${dataKey}FavourQueryWrapper = new QueryWrapper<>(); + ${dataKey}FavourQueryWrapper.in("${dataKey}Id", ${dataKey}IdSet); + ${dataKey}FavourQueryWrapper.eq("userId", loginUser.getId()); + List<${upperDataKey}Favour> ${dataKey}FavourList = ${dataKey}FavourMapper.selectList(${dataKey}FavourQueryWrapper); + ${dataKey}FavourList.forEach(${dataKey}Favour -> ${dataKey}IdHasFavourMap.put(${dataKey}Favour.get${upperDataKey}Id(), true)); + } + // 填充信息 + ${dataKey}VOList.forEach(${dataKey}VO -> { + Long userId = ${dataKey}VO.getUserId(); + User user = null; + if (userIdUserListMap.containsKey(userId)) { + user = userIdUserListMap.get(userId).get(0); + } + ${dataKey}VO.setUser(userService.getUserVO(user)); + ${dataKey}VO.setHasThumb(${dataKey}IdHasThumbMap.getOrDefault(${dataKey}VO.getId(), false)); + ${dataKey}VO.setHasFavour(${dataKey}IdHasFavourMap.getOrDefault(${dataKey}VO.getId(), false)); + }); + // endregion + + ${dataKey}VOPage.setRecords(${dataKey}VOList); + return ${dataKey}VOPage; + } + +} diff --git a/src/main/resources/templates/model/TemplateAddRequest.java.ftl b/src/main/resources/templates/model/TemplateAddRequest.java.ftl new file mode 100644 index 0000000..a527ded --- /dev/null +++ b/src/main/resources/templates/model/TemplateAddRequest.java.ftl @@ -0,0 +1,33 @@ +package ${packageName}.model.dto.${dataKey}; + +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 创建${dataName}请求 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +@Data +public class ${upperDataKey}AddRequest implements Serializable { + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 标签列表 + */ + private List tags; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/resources/templates/model/TemplateEditRequest.java.ftl b/src/main/resources/templates/model/TemplateEditRequest.java.ftl new file mode 100644 index 0000000..f955c53 --- /dev/null +++ b/src/main/resources/templates/model/TemplateEditRequest.java.ftl @@ -0,0 +1,38 @@ +package ${packageName}.model.dto.${dataKey}; + +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 编辑${dataName}请求 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +@Data +public class ${upperDataKey}EditRequest implements Serializable { + + /** + * id + */ + private Long id; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 标签列表 + */ + private List tags; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/resources/templates/model/TemplateQueryRequest.java.ftl b/src/main/resources/templates/model/TemplateQueryRequest.java.ftl new file mode 100644 index 0000000..3ff9b8e --- /dev/null +++ b/src/main/resources/templates/model/TemplateQueryRequest.java.ftl @@ -0,0 +1,56 @@ +package ${packageName}.model.dto.${dataKey}; + +import ${packageName}.common.PageRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.List; + +/** + * 查询${dataName}请求 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class ${upperDataKey}QueryRequest extends PageRequest implements Serializable { + + /** + * id + */ + private Long id; + + /** + * id + */ + private Long notId; + + /** + * 搜索词 + */ + private String searchText; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 标签列表 + */ + private List tags; + + /** + * 创建用户 id + */ + private Long userId; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/resources/templates/model/TemplateUpdateRequest.java.ftl b/src/main/resources/templates/model/TemplateUpdateRequest.java.ftl new file mode 100644 index 0000000..9188baf --- /dev/null +++ b/src/main/resources/templates/model/TemplateUpdateRequest.java.ftl @@ -0,0 +1,38 @@ +package ${packageName}.model.dto.${dataKey}; + +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 更新${dataName}请求 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +@Data +public class ${upperDataKey}UpdateRequest implements Serializable { + + /** + * id + */ + private Long id; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 标签列表 + */ + private List tags; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/resources/templates/model/TemplateVO.java.ftl b/src/main/resources/templates/model/TemplateVO.java.ftl new file mode 100644 index 0000000..9f04289 --- /dev/null +++ b/src/main/resources/templates/model/TemplateVO.java.ftl @@ -0,0 +1,93 @@ +package ${packageName}.model.vo; + +import cn.hutool.json.JSONUtil; +import ${packageName}.model.entity.${upperDataKey}; +import lombok.Data; +import org.springframework.beans.BeanUtils; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * ${dataName}视图 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +@Data +public class ${upperDataKey}VO implements Serializable { + + /** + * id + */ + private Long id; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 创建用户 id + */ + private Long userId; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 标签列表 + */ + private List tagList; + + /** + * 创建用户信息 + */ + private UserVO user; + + /** + * 封装类转对象 + * + * @param ${dataKey}VO + * @return + */ + public static ${upperDataKey} voToObj(${upperDataKey}VO ${dataKey}VO) { + if (${dataKey}VO == null) { + return null; + } + ${upperDataKey} ${dataKey} = new ${upperDataKey}(); + BeanUtils.copyProperties(${dataKey}VO, ${dataKey}); + List tagList = ${dataKey}VO.getTagList(); + ${dataKey}.setTags(JSONUtil.toJsonStr(tagList)); + return ${dataKey}; + } + + /** + * 对象转封装类 + * + * @param ${dataKey} + * @return + */ + public static ${upperDataKey}VO objToVo(${upperDataKey} ${dataKey}) { + if (${dataKey} == null) { + return null; + } + ${upperDataKey}VO ${dataKey}VO = new ${upperDataKey}VO(); + BeanUtils.copyProperties(${dataKey}, ${dataKey}VO); + ${dataKey}VO.setTagList(JSONUtil.toList(${dataKey}.getTags(), String.class)); + return ${dataKey}VO; + } +} diff --git a/src/main/resources/test_excel.xlsx b/src/main/resources/test_excel.xlsx new file mode 100644 index 0000000..9e4c4c6 Binary files /dev/null and b/src/main/resources/test_excel.xlsx differ diff --git a/src/test/java/com/yupi/springbootinit/MainApplicationTests.java b/src/test/java/com/yupi/springbootinit/MainApplicationTests.java new file mode 100644 index 0000000..c1133bf --- /dev/null +++ b/src/test/java/com/yupi/springbootinit/MainApplicationTests.java @@ -0,0 +1,19 @@ +package com.yupi.springbootinit; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * 主类测试 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@SpringBootTest +class MainApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/src/test/java/com/yupi/springbootinit/manager/CosManagerTest.java b/src/test/java/com/yupi/springbootinit/manager/CosManagerTest.java new file mode 100644 index 0000000..e803a4b --- /dev/null +++ b/src/test/java/com/yupi/springbootinit/manager/CosManagerTest.java @@ -0,0 +1,23 @@ +package com.yupi.springbootinit.manager; + +import javax.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * Cos 操作测试 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@SpringBootTest +class CosManagerTest { + + @Resource + private CosManager cosManager; + + @Test + void putObject() { + cosManager.putObject("test", "test.json"); + } +} \ No newline at end of file diff --git a/src/test/java/com/yupi/springbootinit/mapper/PostFavourMapperTest.java b/src/test/java/com/yupi/springbootinit/mapper/PostFavourMapperTest.java new file mode 100644 index 0000000..96bd2f3 --- /dev/null +++ b/src/test/java/com/yupi/springbootinit/mapper/PostFavourMapperTest.java @@ -0,0 +1,33 @@ +package com.yupi.springbootinit.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 com.yupi.springbootinit.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 程序员鱼皮 + * @from 编程导航知识星球 + */ +@SpringBootTest +class PostFavourMapperTest { + + @Resource + private PostFavourMapper postFavourMapper; + + @Test + void listUserFavourPostByPage() { + IPage page = new Page<>(2, 1); + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("id", 1); + queryWrapper.like("content", "a"); + IPage result = postFavourMapper.listFavourPostByPage(page, queryWrapper, 1); + Assertions.assertNotNull(result); + } +} \ No newline at end of file diff --git a/src/test/java/com/yupi/springbootinit/mapper/PostMapperTest.java b/src/test/java/com/yupi/springbootinit/mapper/PostMapperTest.java new file mode 100644 index 0000000..b0186ba --- /dev/null +++ b/src/test/java/com/yupi/springbootinit/mapper/PostMapperTest.java @@ -0,0 +1,28 @@ +package com.yupi.springbootinit.mapper; + +import com.yupi.springbootinit.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 程序员鱼皮 + * @from 编程导航知识星球 + */ +@SpringBootTest +class PostMapperTest { + + @Resource + private PostMapper postMapper; + + @Test + void listPostWithDelete() { + List postList = postMapper.listPostWithDelete(new Date()); + Assertions.assertNotNull(postList); + } +} \ No newline at end of file diff --git a/src/test/java/com/yupi/springbootinit/service/PostFavourServiceTest.java b/src/test/java/com/yupi/springbootinit/service/PostFavourServiceTest.java new file mode 100644 index 0000000..16aa319 --- /dev/null +++ b/src/test/java/com/yupi/springbootinit/service/PostFavourServiceTest.java @@ -0,0 +1,44 @@ +package com.yupi.springbootinit.service; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yupi.springbootinit.model.entity.Post; +import com.yupi.springbootinit.model.entity.User; +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; + +/** + * 帖子收藏服务测试 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@SpringBootTest +class PostFavourServiceTest { + + @Resource + private PostFavourService postFavourService; + + private static final User loginUser = new User(); + + @BeforeAll + static void setUp() { + loginUser.setId(1L); + } + + @Test + void doPostFavour() { + int i = postFavourService.doPostFavour(1L, loginUser); + Assertions.assertTrue(i >= 0); + } + + @Test + void listFavourPostByPage() { + QueryWrapper postQueryWrapper = new QueryWrapper<>(); + postQueryWrapper.eq("id", 1L); + postFavourService.listFavourPostByPage(Page.of(0, 1), postQueryWrapper, loginUser.getId()); + } +} diff --git a/src/test/java/com/yupi/springbootinit/service/PostThumbServiceTest.java b/src/test/java/com/yupi/springbootinit/service/PostThumbServiceTest.java new file mode 100644 index 0000000..46fe0e9 --- /dev/null +++ b/src/test/java/com/yupi/springbootinit/service/PostThumbServiceTest.java @@ -0,0 +1,34 @@ +package com.yupi.springbootinit.service; + +import com.yupi.springbootinit.model.entity.User; +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; + +/** + * 帖子点赞服务测试 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@SpringBootTest +class PostThumbServiceTest { + + @Resource + private PostThumbService postThumbService; + + private static final User loginUser = new User(); + + @BeforeAll + static void setUp() { + loginUser.setId(1L); + } + + @Test + void doPostThumb() { + int i = postThumbService.doPostThumb(1L, loginUser); + Assertions.assertTrue(i >= 0); + } +} diff --git a/src/test/java/com/yupi/springbootinit/service/UserServiceTest.java b/src/test/java/com/yupi/springbootinit/service/UserServiceTest.java new file mode 100644 index 0000000..0b152c5 --- /dev/null +++ b/src/test/java/com/yupi/springbootinit/service/UserServiceTest.java @@ -0,0 +1,35 @@ +package com.yupi.springbootinit.service; + +import javax.annotation.Resource; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * 用户服务测试 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@SpringBootTest +public class UserServiceTest { + + @Resource + private UserService userService; + + @Test + void userRegister() { + String userAccount = "yupi"; + String userPassword = ""; + String checkPassword = "123456"; + try { + long result = userService.userRegister(userAccount, userPassword, checkPassword); + Assertions.assertEquals(-1, result); + userAccount = "yu"; + result = userService.userRegister(userAccount, userPassword, checkPassword); + Assertions.assertEquals(-1, result); + } catch (Exception e) { + + } + } +} diff --git a/src/test/java/com/yupi/springbootinit/utils/EasyExcelTest.java b/src/test/java/com/yupi/springbootinit/utils/EasyExcelTest.java new file mode 100644 index 0000000..b74eb9f --- /dev/null +++ b/src/test/java/com/yupi/springbootinit/utils/EasyExcelTest.java @@ -0,0 +1,34 @@ +package com.yupi.springbootinit.utils; + +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.support.ExcelTypeEnum; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.util.ResourceUtils; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.List; +import java.util.Map; + +/** + * EasyExcel 测试 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@SpringBootTest +public class EasyExcelTest { + + @Test + public void doImport() throws FileNotFoundException { + File file = ResourceUtils.getFile("classpath:test_excel.xlsx"); + List> list = EasyExcel.read(file) + .excelType(ExcelTypeEnum.XLSX) + .sheet() + .headRowNumber(0) + .doReadSync(); + System.out.println(list); + } + +} \ No newline at end of file