手写一个博客平台 ~ 第六天

作者:fyupeng
技术专栏:☞ https://github.com/fyupeng
项目地址:☞ https://github.com/fyupeng/distributed-blog-system-api
项目预览地址:☞ 博客第六天


留给读者

你们可能想不到,其实博客已经最后一天了,因为前面该搭建的都搭建完了,后面只是业务代码,但为了能照顾大部分同学,我决定还是承诺应允大家做到从规范到代码,从代码到运行,从运行到项目部署,从部署到项目上线这几个过程。

一些业务中的规范直接按照项目来就可以了,就可以避免碰到许多坑了。

想入坑的有下面这么几个,大家可以不要看推文自己试试,能不能躺到坑位,嘟嘟噜。

  • 打包不了的如何解决?用那个依赖就可以解决了?

  • mongodbbean没法依赖,springboot启动后报空指针异常

  • spring1.02.0遇到的配置文件信息变动,RedisMongoDB随之变动出现的问题

  • 高版本swagger2出现的页面不显示问题

所有分布式服务开启后,可以清晰地从Nacos注册中心看到

image-20220819194436670

swagger2展示用户的接口

image-20220819195320588

有一点电脑性能建议的,开启6个进程很消耗内存,我直接飙到了12G,电脑内存16G,不知道你们能不能扛得住,CPU要求比较低,我只有双内核,也就是4个处理器。

重要的事说三遍,不要在项目直接配置数据库或注册中心,为了提供更好的调试和开发体验,将rpc需要配置的信息以及博客自定义配置信息支持到Jar启动外置中配置,一般推荐项目中resource资源下配置信息只配置与用户名和数据密码敏感无关信息,这在支持外部配置的application.properties文件中会重写覆盖,目前rpc-netty-framework只支持配置文件整体覆盖,暂时没有实现局部key-value信息覆盖。

在摸索数据库安全路上碰壁留下的许多经验,就送给大家吧,只要大家能支持我,我满足了。

先说安全可做的方面:

  • 系统账户权限

由于数据库存储类软件,设计到存储功能权限,是需要系统赋予一个存储数据的权限的,所有需要申请账户,有别于系统的root账户

默认会赋予存储的权限。

所以我们可以通过从以下几个方面去加强安全性

  • nologin设置

怎么nologin设置?

如果你玩过linux或者云服务器,你会发现home目录下会有几个用户目录,如下图

image-20220905153149344

而如果想切换到对应的用户,例如使用命令su mongodb,想要实现下面这种做法

image-20220905153951405

只需要一行命令

usermod -s /sbin/nologin mysql
su mysql
This account is currently not available.

这样做的好处就是,如果数据库被攻击导致泄露用户信息,那数据库中涉及到系统的存储权限无法直接使用shell方式登录,那系统中其他信息就相当安全了,不会因为数据库的问题而引发其他问题。

  • 访问源地址限制

这一般在防火墙中设置,而如果使用云服务器设置就相对简单多了,就对阿里云来讲,进入到云控制台中,打开你的实例服务器的安全组,对某个端口设置源地址限制即可。

你可以通过这种方式设置数据库开放端口给内部其他服务器,而不直接开放到外网,这样即便暴露了数据库端口和用户登录信息,对方也无法登录。

当然如果你已经泄露过了用户信息,你不妨看看数据库之前是否开启过日志,或者从系统日志中入手,看看是哪个ip对你的数据库进行了攻击,将其拉入黑名单即可。

  • 端口设置

一般不设置默认端口,因为大多数情况下,数据库攻击手段是通过默认端口和默认用户登录信息进行攻击的,也有些脚本是获取公网中如GitHub上泄露的用户信息进行攻击,这里提供一个工具,如果你有经常做笔记发布到githubgitee的习惯,推荐你使用这一款网页版的监控敏感信息:https://www.gitguardian.com/,有敏感信息会通过邮箱告知你,自己完全不用操心,只需在监控到敏感信息后,自己手动处理即可。

  • 使用代理

使用代理是将设计到数据存储的服务器地址和端口隐藏不对外开发,而通过另一台服务器开发端口,由它来做代理服务。

做完这方面,可以看看架构上的设计,是如何巧妙通过代理、分布式、缓冲层、鉴权、反射调用来一步步加强安全和联系的。

1. 软件架构

软件架构说明

  • 项目分布式前后端代理架构设计:

分布式前后分离架构

  • 项目RPC架构设计:

分布式博客微服务架构

下面就着手开发吧,相信各位都能以我的项目作为地基,二次开发出一个更有高度的项目!

2. 编写代码

代理服务器编写控制层代码,编写真实服务的接口,而不去实现接口

主要分为五个模块来编写代码,为什么呢,从设计模式的角度出发,能够解耦,也就是耦合性更低

image-20220904234056717

  • 控制层结构:

image-20220904234226667

  • 服务接口:

    image-20220904234247558

  • mapper层:

保留,没有用到,已经分离了,一开始测试时会用到,后面就删了

  • pojo类:

image-20220904234401862

  • 公用包:

image-20220904234439177

堆积这么多张图?是水文???

其实是想让大家看看进度,有没有跟不上进度或遗漏的地方

2.1 配置文件
  • application.properties
############################################################
#
# Server Port
#
############################################################
server.port=8080

############################################################
# Server - tomcat
############################################################
# Tomcat URI EncodingiJ1eK1gN4pE0pG1f
server.tomcat.uri-encoding=UTF-8

pagehelper.helperDialect=mysql
pagehelper.supportMethodsArguments=true
pagehelper.params=count=countSql

# springboot 1.5
#spring.http.multipart.maxFileSize=150Mb
#spring.http.multipart.maxRequestSize=1000Mb
# springboot 2.0
spring.servlet.multipart.max-file-size=150MB
spring.servlet.multipart.max-request-size=1000MB

############################################################
#
# Redis
#
############################################################

# Redis default use dataBase
#spring.redis.database=0

## Redis Host
spring.redis.host=localhost
## Redis Port
spring.redis.port=6379

# Redis password
spring.redis.password=your_redis_password

#spring.redis.pool.max-active=300
spring.redis.jedis.pool.max-active=300
#spring.redis.pool.max-wait=10000
spring.redis.jedis.pool.max-wait=10000
#spring.redis.pool.maxIdle=300
spring.redis.jedis.pool.max-idle=300
#spring.redis.pool.minIdle=6
spring.redis.jedis.pool.min-idle=6
spring.redis.timeout=0
  • logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!--%date{HH:mm:ss.SSS} %c -->
            <pattern>%date{HH:mm:ss.SSS} %c [%t] - %m%n</pattern>
        </encoder>
    </appender>



    <!--<logger name="org.springframework.security.web.FilterChainProxy" level="DEBUG" additivity="false">
        <appender-ref ref="STDOUT"/>
    </logger>
    <logger name="org.springframework.security.web.access.intercept.FilterSecurityInterceptor" level="DEBUG" additivity="false">
        <appender-ref ref="STDOUT"/>
    </logger>-->

    <!--<logger name="org.springframework.security.web" level="DEBUG" additivity="false">
        <appender-ref ref="STDOUT"/>
    </logger>-->
<!--    <logger name="c" level="info" additivity="false">-->
<!--        <appender-ref ref="STDOUT"/>-->
<!--    </logger>-->

    <root level="info">
        <appender-ref ref="console"/>
    </root>
</configuration>
  • resource.properties
cn.fyupeng.nacos.register-addr=localhost:8848
2.2 启动器和文档接口配置
  • 启动器
package cn.fyupeng;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@Slf4j
@SpringBootApplication
@ComponentScan(basePackages = {"cn.fyupeng", "org.n3r.idworker"})
public class FyupengBlogApplication {

    public static void main(String[] args) {
        SpringApplication.run(FyupengBlogApplication.class, args);
    }

}
  • 文件接口配置
package cn.fyupeng;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class Swagger2 {

    @Bean
    public Docket createWebApi(){
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("userApi")
                .apiInfo(webApiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("cn.fyupeng.controller.user"))
                //.paths(Predicates.and(PathSelectors.regex("/.*")))
                .paths(PathSelectors.any())
                .build();
    }

    @Bean
    public Docket createAdminApi(){
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("adminApi")
                .apiInfo(adminApiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("cn.fyupeng.controller.admin"))
                // .paths(Predicates.and(PathSelectors.regex("/admin/.*")))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo webApiInfo(){
        return new ApiInfoBuilder()
                //设置页面标题
                .title("使用swagger2构建RPC分布式博客管理平台后端user-api接口文档")
                .contact(new Contact("distributed-blog-api - 仓库","git@github.com:fyupeng/distributed-blog-api/blob/main/README.md","fyp010311@163.com"))
                .description("欢迎访问RPC分布式博客管理平台接口文档,本文档描述了博客服务接口定义")
                .version("1.0.1")
                .build();
    }

    private ApiInfo adminApiInfo(){
        return new ApiInfoBuilder()
                //设置页面标题
                .title("使用swagger2构建RPC分布式博客管理平台后端admin-api接口文档")
                .contact(new Contact("distributed-blog-api - 仓库","git@github.com:fyupeng/distributed-blog-api/blob/main/README.md","fyp010311@163.com"))
                .description("欢迎访问RPC分布式博客管理平台接口文档,本文档描述了博客服务接口定义")
                .version("1.0.1")
                .build();
    }

}
2.3 拦截器
package cn.fyupeng.interceptor;

import com.auth0.jwt.JWT;
import com.auth0.jwt.exceptions.JWTDecodeException;
import cn.fyupeng.controller.BasicController;
import cn.fyupeng.annotion.PassToken;
import cn.fyupeng.annotion.UserLoginToken;
import cn.fyupeng.pojo.User;
import cn.fyupeng.service.UserService;
import cn.fyupeng.utils.RedisUtils;
import cn.fyupeng.utils.TokenUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

/**
 * @Auther: fyp
 * @Date: 2022/8/18
 * @Description:
 * @Package: cn.fyupeng.controller.interceptor
 * @Version: 1.0
 */
@Component
public class LoginInterceptor extends BasicController implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("token");// 从 http 请求头中取出 token
        // 如果不是映射到方法直接通过
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();

        //检查方法是否有passtoken注解,有则跳过认证,直接通过
        if (method.isAnnotationPresent(PassToken.class)) {
            PassToken passToken = method.getAnnotation(PassToken.class);
            if (passToken.required()) {
                return true;
            }
        }
        //检查有没有需要用户权限的注解
        if (method.isAnnotationPresent(UserLoginToken.class)) {
            UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
            if (userLoginToken.required()) {
                // 执行认证
                if (token == null) {
                    throw new RuntimeException("无token,请重新登录");
                }
                // 获取 token 中的 user id
                String userId;
                String userName;
                String password;
                try {
                    userId = JWT.decode(token).getClaim("userId").asString();
                    userName = JWT.decode(token).getClaim("username").asString();
                    password = JWT.decode(token).getClaim("password").asString();
                } catch (JWTDecodeException j) {
                    throw new RuntimeException("The token is incorrect, please do not create the token by illegal means");
                }
                //查询数据库,看看是否存在此用户,方法要自己写
                UserService userServiceProxy = rpcClientProxy.getProxy(UserService.class);
                // password 为 MD5 加密密文
                User user = userServiceProxy.queryUserForLogin(userName, password);
                if (user == null) {
                    throw new RuntimeException("User does not exist, please log in again");
                }

                // 验证 token
                if (TokenUtils.verify(token)) {
                    String userRedisSession = RedisUtils.getUserRedisSession(userId);
                    if(redis.get(userRedisSession) != null)
                        return true;
                    return false;
                } else {
                    throw new RuntimeException("Token expired or incorrect, please log in again");
                }

            }
        }
        throw new RuntimeException("Annotation without permission will not pass");
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    }
}
2.4 过滤器
  • Sensitiveilter过滤类
package cn.fyupeng.filter;

import java.io.IOException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * @Auther: fyp
 * @Date: 2022/4/13
 * @Description:
 * @Package: com.crop.interceptor
 * @Version: 1.0
 */
//敏感词过滤器:利用DFA算法  进行敏感词过滤
public class SensitiveFilter {

    private SensitiveWordInit sensitiveWordInit;

    //敏感词过滤器:利用DFA算法  进行敏感词过滤
    private Map sensitiveWordMap = null;

    // 最小匹配规则
    public static int minMatchType = 1;

    // 最大匹配规则
    public static int maxMatchType = 2;

    // 单例
    private static SensitiveFilter instance = null;

    // 构造函数,初始化敏感词库
    private SensitiveFilter() throws IOException {
        sensitiveWordMap = new SensitiveWordInit().initKeyWord();
    }

    // 获取单例
    public static SensitiveFilter getInstance() throws IOException {
        if (null == instance) {
            instance = new SensitiveFilter();
        }
        return instance;
    }

    // 获取文字中的敏感词
    public Set<String> getSensitiveWord(String txt, int matchType) {
        Set<String> sensitiveWordList = new HashSet<String>();
        for (int i = 0; i < txt.length(); i++) {
            // 判断是否包含敏感字符
            int length = CheckSensitiveWord(txt, i, matchType);
            // 存在,加入list中
            if (length > 0) {
                sensitiveWordList.add(txt.substring(i, i + length));
                // 减1的原因,是因为for会自增
                i = i + length - 1;
            }
        }
        return sensitiveWordList;
    }
    // 替换敏感字字符
    public String replaceSensitiveWord(String txt, int matchType,
                                       String replaceChar) {
        String resultTxt = txt;
        // 获取所有的敏感词
        Set<String> set = getSensitiveWord(txt, matchType);
        Iterator<String> iterator = set.iterator();
        String word = null;
        String replaceString = null;
        while (iterator.hasNext()) {
            word = iterator.next();
            replaceString = getReplaceChars(replaceChar, word.length());
            resultTxt = resultTxt.replaceAll(word, replaceString);
        }
        return resultTxt;
    }

    /**
     * 获取替换字符串
     *
     * @param replaceChar
     * @param length
     * @return
     */
    private String getReplaceChars(String replaceChar, int length) {
        String resultReplace = replaceChar;
        for (int i = 1; i < length; i++) {
            resultReplace += replaceChar;
        }
        return resultReplace;
    }

    /**
     * 检查文字中是否包含敏感字符,检查规则如下:<br>
     * 如果存在,则返回敏感词字符的长度,不存在返回0
     * @param txt
     * @param beginIndex
     * @param matchType
     * @return
     */
    public int CheckSensitiveWord(String txt, int beginIndex, int matchType) {
        // 敏感词结束标识位:用于敏感词只有1位的情况
        boolean flag = false;
        // 匹配标识数默认为0
        int matchFlag = 0;
        Map nowMap = sensitiveWordMap;
        for (int i = beginIndex; i < txt.length(); i++) {
            char word = txt.charAt(i);
            // 获取指定key
            nowMap = (Map) nowMap.get(word);
            // 存在,则判断是否为最后一个
            if (nowMap != null) {
                // 找到相应key,匹配标识+1
                matchFlag++;
                // 如果为最后一个匹配规则,结束循环,返回匹配标识数
                if ("1".equals(nowMap.get("isEnd"))) {
                    // 结束标志位为true
                    flag = true;
                    // 最小规则,直接返回,最大规则还需继续查找
                    if (SensitiveFilter.minMatchType == matchType) {
                        break;
                    }
                }
            }
            // 不存在,直接返回
            else {
                break;
            }
        }

        if (SensitiveFilter.maxMatchType == matchType){
            if(matchFlag < 2 || !flag){        //长度必须大于等于1,为词
                matchFlag = 0;
            }
        }
        if (SensitiveFilter.minMatchType == matchType){
            if(matchFlag < 2 && !flag){        //长度必须大于等于1,为词
                matchFlag = 0;
            }
        }
        return matchFlag;
    }
}
  • SensitiveWordInit单词缓存类
package cn.fyupeng.filter;

import org.springframework.core.io.ClassPathResource;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @Auther: fyp
 * @Date: 2022/4/13
 * @Description:
 * @Package: com.crop.interceptor
 * @Version: 1.0
 */
//屏蔽敏感词初始化
@SuppressWarnings({ "rawtypes", "unchecked" })
public class SensitiveWordInit {
    // 字符编码
    private String ENCODING = "UTF-8";
    // 初始化敏感字库
    public Map initKeyWord() throws IOException {
        // 读取敏感词库 ,存入Set中
        Set<String> wordSet = readSensitiveWordFile();
        // 将敏感词库加入到HashMap中//确定有穷自动机DFA
        return addSensitiveWordToHashMap(wordSet);
    }

    // 读取敏感词库 ,存入HashMap中
    private Set<String> readSensitiveWordFile() throws IOException {
        Set<String> wordSet = null;
        ClassPathResource classPathResource = new ClassPathResource("static/SensitiveWordList.txt");
        InputStream inputStream = classPathResource.getInputStream();
        //敏感词库
        try {
            // 读取文件输入流
            InputStreamReader read = new InputStreamReader(inputStream, ENCODING);
            // 文件是否是文件 和 是否存在
            wordSet = new HashSet<String>();
            // StringBuffer sb = new StringBuffer();
            // BufferedReader是包装类,先把字符读到缓存里,到缓存满了,再读入内存,提高了读的效率。
            BufferedReader br = new BufferedReader(read);
            String txt = null;
            // 读取文件,将文件内容放入到set中
            while ((txt = br.readLine()) != null) {
                wordSet.add(txt);
            }
            br.close();
            // 关闭文件流
            read.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return wordSet;
    }
    // 将HashSet中的敏感词,存入HashMap中
    private Map addSensitiveWordToHashMap(Set<String> wordSet) {
        // 初始化敏感词容器,减少扩容操作
        Map wordMap = new HashMap(wordSet.size());
        for (String word : wordSet) {
            Map nowMap = wordMap;
            for (int i = 0; i < word.length(); i++) {
                // 转换成char型
                char keyChar = word.charAt(i);
                // 获取
                Object tempMap = nowMap.get(keyChar);
                // 如果存在该key,直接赋值
                if (tempMap != null) {
                    nowMap = (Map) tempMap;
                }
                // 不存在则,则构建一个map,同时将isEnd设置为0,因为他不是最后一个
                else {
                    // 设置标志位
                    Map<String, String> newMap = new HashMap<>();
                    newMap.put("isEnd", "0");
                    // 添加到集合
                    nowMap.put(keyChar, newMap);
                    nowMap = newMap;
                }
                // 最后一个
                if (i == word.length() - 1) {
                    nowMap.put("isEnd", "1");
                }
            }
        }
        return wordMap;
    }
}
2.5 配置类
  • InterceptorConfig拦截器配置类
package cn.fyupeng.config;

import cn.fyupeng.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @Auther: fyp
 * @Date: 2022/8/18
 * @Description: 拦截器
 * @Package: cn.fyupeng.controller.config
 * @Version: 1.0
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        String[] addPathPatterns= {
                "/user/**",
                "/admin/**"
        };
        String[] excludePathPatterns={
                "/user/login",
                "/user/regist"
        };
        registry.addInterceptor(loginInterceptor).addPathPatterns(addPathPatterns).excludePathPatterns(excludePathPatterns);
    }
}
  • WebMvc配置类

用于图片的url映射获取

package cn.fyupeng.config;

import cn.fyupeng.controller.BasicController;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


/**
 * @Auther: fyp
 * @Date: 2022/8/29
 * @Description:
 * @Package: cn.fyupeng.config
 * @Version: 1.0
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler(BasicController.URL_SPACE).addResourceLocations("file:" + BasicController.FILE_SPACE);
        WebMvcConfigurer.super.addResourceHandlers(registry);
    }
}
2.6 自定义注解
  • PassToken免校验注解
package cn.fyupeng.annotion;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Auther: fyp
 * @Date: 2022/8/18
 * @Description: 通过Token注解
 * @Package: cn.fyupeng.controller.annotion
 * @Version: 1.0
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
    boolean required() default true;
}
  • UserLoginToken用户校验注解
package cn.fyupeng.annotion;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Auther: fyp
 * @Date: 2022/8/18
 * @Description: 用户登录Token注解
 * @Package: cn.fyupeng.controller.annotion
 * @Version: 1.0
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
   boolean required() default true;
}

2.7 控制器
  • 基类控制器
package cn.fyupeng.controller;

import cn.fyupeng.loadbalancer.RoundRobinLoadBalancer;
import cn.fyupeng.net.netty.client.NettyClient;
import cn.fyupeng.proxy.RpcClientProxy;
import cn.fyupeng.serializer.CommonSerializer;
import cn.fyupeng.utils.RedisOperator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;


@RestController
public class BasicController {
    private static final RoundRobinLoadBalancer roundRobinLoadBalancer = new RoundRobinLoadBalancer();
    private static final NettyClient nettyClient = new NettyClient(roundRobinLoadBalancer, CommonSerializer.KRYO_SERIALIZER);

    protected RpcClientProxy rpcClientProxy = new RpcClientProxy(nettyClient);

    @Autowired
    public RedisOperator redis;

    public  static final String DATA_NAME = "distributed-blog-data";

    //文件保存的命名空间
    public static final String FILE_SPACE =
            System.getProperties().getProperty("user.home") + File.separator + "webapps" + File.separator + DATA_NAME + File.separator;

    public static final String URL_SPACE =
             "/" + DATA_NAME + "/" + "**";
    //每页分页的记录数
    public static final Integer ARTICLE_PAGE_SIZE = 6;
    // 评论分页
    public static final Integer COMMENT_PAGE_SIZE = 10;
    // 图片分页
    public static final Integer PICTURE_PAGE_SIZE = 10;

    public static final Integer SEARCH_SIZE = 10;

    public static final Long ONE_WEEK = 604800000L;

    public static final Long TWO_WEEK = 1209600000L;

    public static final Long ONE_MONTH = 2592000000L; // 30天

    public static final Long ONE_YEAR = 31536000000L;

}
  • 用户文章控制器
package cn.fyupeng.controller.user;

import cn.fyupeng.controller.BasicController;
import cn.fyupeng.annotion.PassToken;
import cn.fyupeng.annotion.UserLoginToken;
import cn.fyupeng.filter.SensitiveFilter;
import cn.fyupeng.pojo.Article;
import cn.fyupeng.pojo.Classfication;
import cn.fyupeng.pojo.vo.ArticleVO;
import cn.fyupeng.service.ArticleService;
import cn.fyupeng.service.ClassficationService;
import cn.fyupeng.service.TagService;
import cn.fyupeng.service.UserService;
import cn.fyupeng.utils.BlogJSONResult;
import cn.fyupeng.utils.PagedResult;
import cn.fyupeng.utils.RedisUtils;
import io.swagger.annotations.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.Cursor;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.*;

/**
 * @Auther: fyp
 * @Date: 2022/4/2
 * @Description: 文章 Controller
 * @Package: com.crop.controller
 * @Version: 1.0
 */

@CrossOrigin
@Slf4j
@RestController
@Api(value = "文章相关业务的接口", tags = {"文章相关业务的controller"})
@RequestMapping(value = "/user/article")
public class UserArticleController extends BasicController {

    private TagService tagServiceProxy = rpcClientProxy.getProxy(TagService.class);
    private UserService userServiceProxy = rpcClientProxy.getProxy(UserService.class);
    private ArticleService articleServiceProxy = rpcClientProxy.getProxy(ArticleService.class);
    private ClassficationService classficationServiceProxy = rpcClientProxy.getProxy(ClassficationService.class);


    @PassToken
    @PostMapping(value = "/getAllArticles")
    @ApiOperation(value = "查找文章信息", notes = "查找文章信息的接口")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "searchKey", value = "搜索关键字", required = true, dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "page", value = "当前页", required = false, dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "pageSize", value = "页大小", required = false, dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "userId", value = "用户id", required = true, dataType = "String", paramType = "query")
    })
    public BlogJSONResult getAllArticles(String searchKey, Integer page, Integer pageSize, String userId) {
        if (StringUtils.isBlank(userId) || StringUtils.isBlank(searchKey)) {
            return BlogJSONResult.errorMsg("用户id和搜索关键字不能为空");
        }

        boolean userIsExist = userServiceProxy.queryUserIdIsExist(userId);
        if (!userIsExist) {
            return BlogJSONResult.errorMsg("用户id不存在");
        }

        //非法敏感词汇判断
        SensitiveFilter filter = null;
        try {
            filter = SensitiveFilter.getInstance();
        } catch (IOException e) {
            e.printStackTrace();
        }
        int n = filter.CheckSensitiveWord(searchKey,0,1);
        if(n > 0){ //存在非法字符
            log.info("用户[{}]使用非法字符[{}]进行检索--",userId, searchKey);
            Set<String> sensitiveWord = filter.getSensitiveWord(searchKey, 1);
            return BlogJSONResult.errorMsg("捕捉敏感关键字: " + sensitiveWord);
        }

        // 进行 热度 维护
        incrementArticleScore(searchKey);

        boolean addTrue = addSearchHistory(userId, searchKey);
        if (!addTrue) {
            log.info("已存在key:{}",searchKey);
        }

        //前端不传该参时会初始化
        if(page == null){
            page = 1;
        }
        //前端不传该参时会初始化
        if(pageSize == null){
            pageSize = ARTICLE_PAGE_SIZE;
        }

        List<Article> articleList = null;

        Classfication classfication = new Classfication();
        classfication.setName(searchKey);

        Classfication cf = classficationServiceProxy.queryClassfication(classfication);

        Article article = new Article();
        if (cf != null) {
            article.setClassId(cf.getId());
        } else {
            article.setTitle(searchKey);
            article.setSummary(searchKey);
            article.setContent(searchKey);
        }
        // 优先 匹配 分类 id
        // 第二 优先 匹配 标题
        // 第三 匹配 摘要
        // 第四 优先 匹配 内容
        PagedResult pageResult = articleServiceProxy.queryArticleSelective(article, page,pageSize);

        return BlogJSONResult.ok(pageResult);
    }

    /**
     * 策略:
     *      1. 查询范围:最近一周 数据
     *      2. 排序方式:时间 最近排序
     * @param page
     * @param pageSize
     * @return
     */
    @PassToken
    @PostMapping(value = "/getRecentArticles")
    @ApiOperation(value = "获取最近文章", notes = "获取最近文章的接口")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "page", value = "当前页", dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "pageSize", value = "页大小", dataType = "String", paramType = "query"),
    })
    public BlogJSONResult getRecentArticles(Integer page, Integer pageSize) {

        //前端不传该参时会初始化
        if(page == null){
            page = 1;
        }
        //前端不传该参时会初始化
        if(pageSize == null){
            pageSize = ARTICLE_PAGE_SIZE;
        }

        // 最大 匹配 一 礼拜前的 文章
        Long timeDifference = ONE_WEEK; // 一周

        PagedResult pageResult = articleServiceProxy.queryArticleByTime(timeDifference,page,pageSize);

        return BlogJSONResult.ok(pageResult);
    }

    /**
     * 策略:
     *      1. 更新频率: 每小时
     *      2. 更新策略: 用户搜索率最高(过滤敏感信息)
     * @param page
     * @param pageSize
     * @return
     */
    @PassToken
    @PostMapping(value = "/getHotArticles")
    @ApiOperation(value = "获取推荐文章", notes = "获取推荐文章的接口")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "page", value = "当前页", dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "pageSize", value = "页大小", dataType = "String", paramType = "query")
    })
    public BlogJSONResult getHotArticles(Integer page, Integer pageSize) {

        List<String> hotSearchKeyList = getHotSearchKey(null, null);
         随机从十条数据中拿出一条来搜索
        //Random random = new Random();
        //int index = random.nextInt(hotSearchKeyList.size());
        /**
         * 保证 1小时 之内查询的数据一致,1小时更新一次
         */
        int seed = Calendar.HOUR_OF_DAY;
        // 保证 不越过 数组长度
        if (hotSearchKeyList.size() == 0) {
            return BlogJSONResult.ok("缓存数据库中无缓存");
        }
        seed %= hotSearchKeyList.size();
        String searchKey = hotSearchKeyList.get(seed);

        //前端不传该参时会初始化
        if(page == null){
            page = 1;
        }
        //前端不传该参时会初始化
        if(pageSize == null){
            pageSize = ARTICLE_PAGE_SIZE;
        }

        List<Article> articleList = null;

        Classfication classfication = new Classfication();
        classfication.setName(searchKey);

        Classfication cf = classficationServiceProxy.queryClassfication(classfication);

        Article article = new Article();
        if (cf != null) {
            article.setClassId(cf.getId());
        } else {
            article.setTitle(searchKey);
            article.setSummary(searchKey);
            article.setContent(searchKey);
        }
        // 优先 匹配 分类 id
        // 第二 优先 匹配 标题
        // 第三 匹配 摘要
        // 第四 优先 匹配 内容
        PagedResult pageResult = articleServiceProxy.queryArticleSelective(article, page,pageSize);

        return BlogJSONResult.ok(pageResult);
    }

    @UserLoginToken
    @PostMapping(value = "/getSearchHistory")
    @ApiOperation(value = "获取搜索历史记录", notes = "获取搜索历史记录的接口")
    @ApiImplicitParam(name = "userId",  value = "用户id",required = true, dataType = "String", paramType = "query")
    public BlogJSONResult getSearchHistory(String userId) {
        List<String> searchHistoryList = querySearchHistory(userId);
        return BlogJSONResult.ok(searchHistoryList);
    }

    @UserLoginToken
    @PostMapping(value = "/removeHistory")
    @ApiOperation(value = "删除搜索历史记录", notes = "删除搜索历史记录的接口")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "userId",  value = "用户id",required = true, dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "searchKey",  value = "搜索关键字",required = true, dataType = "String", paramType = "query")
    })
    public BlogJSONResult removeHistory(String userId, String searchKey) {

        if (StringUtils.isBlank(userId) || StringUtils.isBlank(searchKey)) {
            return BlogJSONResult.errorMsg("用户id和搜索关键字不能为空");
        }

        Long history = delSearchHistory(userId, searchKey);
        return BlogJSONResult.ok(history);
    }

    /**
     * 添加个人 历史数据
     * @param userId
     * @param searchKey
     * @return false: 已存在 - true : 添加成功
     */
    private boolean addSearchHistory(String userId, String searchKey) {

        String searchHistoryKey = RedisUtils.getSearchHistoryKey(userId);
        boolean keyIsExist = redis.hasKey(searchHistoryKey);
        if (keyIsExist) {
            String hk = redis.hget(searchHistoryKey, searchKey);
            // 关键字 key 存在
            if (hk != null) {
                return false;
            // 关键 key 不存在
            } else {
                redis.hset(searchHistoryKey, searchKey, "1");
            }
        // 首次 会 创建 包含 userId 的 key
        } else {
            redis.hset(searchHistoryKey, searchKey, "1");
        }
        return true;
    }

    /**
     * 获取个人 历史数据
     * @param userId
     * @return null : 没有数据 - List<String> ${ketList}
     */
    private List<String> querySearchHistory(String userId) {
        String searchHistoryKey = RedisUtils.getSearchHistoryKey(userId);
        boolean keyExist = redis.hasKey(searchHistoryKey);
        if (keyExist) {
            Cursor<Map.Entry<Object, Object>> cursor = redis.hscan(searchHistoryKey);
            List<String> keyList = new ArrayList<>();
            while (cursor.hasNext()) {
                Map.Entry<Object, Object> current = cursor.next();
                String key = current.getKey().toString();
                keyList.add(key);
            }
            return keyList;
        }
        return null;
    }
    /**
     * 删除个人 历史数据
     * @param userId
     * @param searchKey
     * @return
     */
    private Long delSearchHistory(String userId, String searchKey) {

        String searchHistoryKey = RedisUtils.getSearchHistoryKey(userId);
        return redis.hdel(searchHistoryKey, searchKey);
    }

    private void incrementArticleScore(String searchKey) {
        /**
         * 规则: key: search-score
         *       value: java-1, linux-2 python-3
         */
        String searchScoreKey = RedisUtils.getSearchScoreKey();
        Long now = System.currentTimeMillis();

        // 只需第一次设置 时间戳
        if (redis.zScore(searchScoreKey, searchKey) == null) {
            // 统计 时间戳
            /**
             * 规则: key: search-score:java
             *       value: 时间戳
             */
            String keyWithSearchKey = RedisUtils.getSearchScoreKeyWithSearchKey(searchKey);
            redis.set(keyWithSearchKey, String.valueOf(now));
        }
        // 统计 点击量
        redis.zIncrementScore(searchScoreKey, searchKey, 1);
    }

    @PassToken
    @PostMapping(value = "/getHotSearchKey")
    @ApiOperation(value = "获取关键字热度", notes = "获取关键字热度的接口")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "searchKey", value = "搜索关键字-缺省则匹配热度", dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "size", value = "检索数-缺省为10", dataType = "String", paramType = "query")
    })
    public BlogJSONResult getHotPot(String searchKey, Integer size) {
        if (StringUtils.isNotBlank(searchKey)) {
            //非法敏感词汇判断
            SensitiveFilter filter = null;
            try {
                filter = SensitiveFilter.getInstance();
            } catch (IOException e) {
                e.printStackTrace();
            }
            int n = filter.CheckSensitiveWord(searchKey,0,1);
            if(n > 0){ //存在非法字符
                log.info("使用非法字符[{}]进行检索热度--",searchKey);
                Set<String> sensitiveWord = filter.getSensitiveWord(searchKey, 1);
                return BlogJSONResult.ok(sensitiveWord);
            }
        }
        List<String> hotSearchKeyList = getHotSearchKey(searchKey, size);
        return BlogJSONResult.ok(hotSearchKeyList);
    }

    /**
     * 搜索引擎
     * @param searchKey
     * @param size
     * @return
     */
    public List<String> getHotSearchKey(String searchKey, Integer size) {

        if (size == null) {
            size = SEARCH_SIZE;
        }

        Long now = System.currentTimeMillis();

        String searchScoreKey = RedisUtils.getSearchScoreKey();
        Set<String> allKeys = redis.zRevRangeByScore(searchScoreKey, 0, Double.MAX_VALUE);

        List<String> resultList = new ArrayList<>();
        if (!StringUtils.isBlank(searchKey)) {
            for (String key : allKeys) {
                // 在所有的 key 中包含 用户输入的 ${searchKey}
                if (StringUtils.containsIgnoreCase(key, searchKey)) {
                    // 记录数 已达 期待数,停止检索
                    if (resultList.size() >= size) {
                        break;
                    }
                    // 规则:与 新增 关键字 时做的 时间戳 key 值相对应
                    String searchScoreKeyWithSearchKey = RedisUtils.getSearchScoreKeyWithSearchKey(key);
                    String timeStamp = redis.get(searchScoreKeyWithSearchKey);
                    if (timeStamp != null) {
                        Long time = Long.valueOf(timeStamp);
                        // 查询 最近一个礼拜的 数据
                        if ((now - time) < 604800000L) {
                            resultList.add(key);
                        } else {
                            redis.zset(searchScoreKey, key, 0);
                        }
                    }
                }
            }
        } else {
            for (String key : allKeys) {
                // 在所有的 key 中包含 用户输入的 ${searchKey}
                // 记录数 已达 期待数,停止检索
                if (resultList.size() >= size) {
                    break;
                }
                // 规则:与 新增 关键字 时做的 时间戳 key 值相对应
                String searchScoreKeyWithSearchKey = RedisUtils.getSearchScoreKeyWithSearchKey(key);
                String timeStamp = redis.get(searchScoreKeyWithSearchKey);
                if (timeStamp != null) {
                    Long time = Long.valueOf(timeStamp);
                    // 查询 最近一个礼拜的 数据
                    if ((now - time) < 604800000L) {
                        resultList.add(key);
                    } else {
                        redis.zset(searchScoreKey, key, 0);
                    }
                }
            }
        }

        return resultList;
    }


    @PassToken
    @PostMapping(value = "/getArticleDetail")
    @ApiOperation(value = "获取文章详细信息", notes = "获取文章详细信息的接口")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "articleId", value = "文章id", required = true, dataType = "String", paramType = "query"),
            //@ApiImplicitParam(name = "request",value = "请求", dataType = "HttpServletRequest", readOnly = true)
    })

    public BlogJSONResult getArticleDetail(String articleId, @ApiParam(hidden = true)HttpServletRequest request) {

        if (StringUtils.isBlank(articleId)) {
            return BlogJSONResult.errorMsg("文章id不能为空");
        }

        Article article = new Article();
        article.setId(articleId);

        ArticleVO articleVO = articleServiceProxy.queryArticleDetail(articleId);
        // 文章 id 不存在
        if (articleVO == null) {
            return BlogJSONResult.ok("articleId不存在");
        }

        String articleView = RedisUtils.getIdView(articleVO.getId(), request);
        String articleViewCount = RedisUtils.getIdViewCount(articleVO.getId());
        //用户短时间 已 访问,redis.get(userView) 返回值是 对象,不能 通过 ""判断
        if (redis.get(articleView) != null) {
            return BlogJSONResult.ok(articleVO);
        }
        /**
         * 统计量 一小时 内 同一个 ip 地址 只能算 1 次 阅读
         */
        redis.set(articleView, "1", 60 * 60);
        /**
         * articleViewCount 如果 未 初始化,redis 会 初始化为 0
         */
        redis.incr(articleViewCount,1);

        return BlogJSONResult.ok(articleVO);
    }

    @UserLoginToken
    @PostMapping(value = "/saveArticle")
    @ApiOperation(value = "保存文章信息 - id 字段请忽略", notes = "保存文章信息的接口")
    @ApiImplicitParam(name = "article", value = "文章", required = true, dataType = "Article", paramType = "body")
    public BlogJSONResult saveArticle(@RequestBody Article article) {


        if(StringUtils.isBlank(article.getUserId()) || StringUtils.isBlank(article.getTitle())
                || StringUtils.isBlank(article.getSummary() ) || StringUtils.isBlank(article.getClassId())
                || StringUtils.isBlank(article.getContent())){
            return BlogJSONResult.errorMsg("用户id、标题、摘要、分类id和内容不能为空");
        }

        boolean userIdIsExist = userServiceProxy.queryUserIdIsExist(article.getUserId());

        if (!userIdIsExist) {
            return BlogJSONResult.errorMsg("用户id不存在");
        }

        boolean classficationIsExist = classficationServiceProxy.queryClassficationIdIsExist(article.getClassId());

        if (!classficationIsExist) {
            return BlogJSONResult.errorMsg("分类id不存在");
        }

        boolean saveIsTrue = articleServiceProxy.save(article);

        return saveIsTrue ? BlogJSONResult.ok() : BlogJSONResult.errorMsg("内部错误导致保存失败");
    }

    @UserLoginToken
    @PostMapping(value = "/updateArticle")
    @ApiOperation(value = "更新文章信息", notes = "更新文章信息的接口")
    @ApiImplicitParam(name = "article", value = "文章", required = true, dataType = "Article", paramType = "body")
    public BlogJSONResult updateArticle(@RequestBody  Article article) {

        if (StringUtils.isBlank(article.getId()) || StringUtils.isBlank(article.getUserId())) {
            return BlogJSONResult.errorMsg("articleId 和 userId 不能为空");
        }

        Article articleWithIdAndUserId =  new Article();
        articleWithIdAndUserId.setId(article.getId());
        articleWithIdAndUserId.setUserId(article.getUserId());
        // 文章id 属于 用户id
        boolean articleIsUser = articleServiceProxy.queryArticleIsUser(articleWithIdAndUserId);
        if (articleIsUser) {
            Article ac = new Article();
            ac.setId(article.getId());
            ac.setUserId(article.getUserId());
            // 文章 标题 要更新
            if (StringUtils.isNotBlank(article.getTitle())) {
                ac.setTitle(article.getTitle());
            }
            // 文章 摘要 要更新
            if (StringUtils.isNotBlank(article.getSummary())) {
                ac.setSummary(article.getSummary());
            }
            // 文章 内容 要更新
            if (StringUtils.isNotBlank(article.getContent())) {
                ac.setContent(article.getContent());
            }
            // 文章 分类 要更新
            if (StringUtils.isNotBlank(article.getClassId())) {
                boolean classficationIdIsExist = classficationServiceProxy.queryClassficationIdIsExist(article.getClassId());
                if (!classficationIdIsExist)
                    return BlogJSONResult.errorMsg("分类id不存在");
                ac.setClassId(article.getClassId());
            }
            boolean updateIsTrue = articleServiceProxy.saveWithIdAndUserId(ac);

            return updateIsTrue ? BlogJSONResult.ok() : BlogJSONResult.errorMsg("内部错误更新失败");
        }

        return BlogJSONResult.errorMsg("用户 id 与 articleId 不存在或匹配失败");

    }

    @UserLoginToken
    @PostMapping(value = "/removeArticle")
    @ApiOperation(value = "删除文章", notes = "删除文章的接口")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "articleId", value = "文章id", required = true, dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "userId", value = "用户id", required = true, dataType = "String", paramType = "query")
    })

    public BlogJSONResult removeArticle(String articleId, String userId) {
        if (StringUtils.isBlank(articleId) || StringUtils.isBlank(userId)) {
            return BlogJSONResult.errorMsg("articleId或userId不能为空");
        }

        Article article = new Article();
        article.setId(articleId);
        article.setUserId(userId);
        boolean articleIsUser = articleServiceProxy.queryArticleIsUser(article);

        if (!articleIsUser) {
            return BlogJSONResult.errorMsg("articleId不存在或者userId与commentId约束的userId不同");
        }

        /**
         * 删除文章 以及删除 与 文章 id 关联的 其他表数据
         */
        articleServiceProxy.removeArticle(articleId);

        return BlogJSONResult.ok();
    }

    @PassToken
    @PostMapping(value = "/getAllClassfications")
    @ApiOperation(value = "获取文章分类信息", notes = "获取文章分类信息的接口")
    public BlogJSONResult getAllClassfications() {

        List<Classfication> classficationList = classficationServiceProxy.queryAllClassfications();

        return BlogJSONResult.ok(classficationList);
    }

}
  • 用户控制器
package cn.fyupeng.controller.user;

import cn.fyupeng.controller.BasicController;
import cn.fyupeng.annotion.PassToken;
import cn.fyupeng.annotion.UserLoginToken;
import cn.fyupeng.pojo.User;
import cn.fyupeng.pojo.UserInfo;
import cn.fyupeng.pojo.vo.UserForUpdateVO;
import cn.fyupeng.pojo.vo.UserInfoVO;
import cn.fyupeng.service.UserService;
import cn.fyupeng.utils.BlogJSONResult;
import cn.fyupeng.utils.MD5Utils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

@CrossOrigin
@RestController
@RequestMapping(value = "/user")
@Api(value = "用户相关业务的接口", tags = {"用户相关业务的controller"})
public class UserController extends BasicController {

    UserService userServiceProxy = rpcClientProxy.getProxy(UserService.class);

    @PassToken
    @GetMapping(value = "/pingNetWork")
    @ApiOperation(value = "测试网络环境", notes = "测试网络环境的接口")
    public BlogJSONResult pingNetWork() {
        return BlogJSONResult.build(200, "ping successful!", null);
    }

    @PassToken
    @ApiOperation(value = "查询用户信息", notes = "查询用户信息的接口")
    @ApiImplicitParam(name = "userId", value = "用户id", required = true, dataType = "String", paramType = "query")
    @PostMapping(value = "/query")
    public BlogJSONResult query(String userId) {

        if(StringUtils.isBlank(userId)){
            return BlogJSONResult.errorMsg("用户id不能为空");
        }

        UserInfo userInfo = userServiceProxy.queryUserInfo(userId);
        UserInfoVO userInfoVO = new UserInfoVO();

        if (userInfo != null) {
            BeanUtils.copyProperties(userInfo,userInfoVO);
            User user = userServiceProxy.queryUser(userId);
            userInfoVO.setPermission(user.getPermission());
        }


        return BlogJSONResult.ok(userInfoVO);
    }


    @UserLoginToken
    @ApiOperation(value = "完善个人信息 - id字段请忽略", notes = "完善个人信息的接口")
    @ApiImplicitParam(name = "userInfo", value = "用户详情", required = true, dataType = "UserInfo", paramType = "body")
    @PostMapping(value = "/completeUserInfo")
    public BlogJSONResult completeUserInfo(@RequestBody UserInfo userInfo) {

        if (StringUtils.isBlank(userInfo.getUserId())) {
            return BlogJSONResult.errorMsg("用户id不能为空");
        }

        UserInfo userInfoByUserId = userServiceProxy.queryUserInfo(userInfo.getUserId());
        if (userInfoByUserId == null) {
            return BlogJSONResult.errorMsg("用户id不存在");
        }

        // id 是唯一标识符,不可更改
        userInfo.setId(userInfoByUserId.getId());

        userServiceProxy.updateUserInfo(userInfo);

        return BlogJSONResult.ok();
    }


    @UserLoginToken
    @ApiOperation(value = "用户上传头像", notes = "用户上传头像的接口")
    @ApiImplicitParam(name = "userId", value = "用户id", required = true, dataType = "String", paramType = "form")
    @PostMapping(value = "/uploadFace", headers = "content-type=multipart/form-data")
    public BlogJSONResult uploadFace(@RequestParam(value = "userId") String userId,
                                     @RequestParam(value = "file") /* 这两个注解不能搭配使用,会导致 文件上传按钮失效*/
                                     /*@ApiParam(value = "头像")*/ MultipartFile file) throws Exception{

        if(StringUtils.isBlank(userId)){
            return BlogJSONResult.errorMsg("用户id不能为空");
        }

        if (!userServiceProxy.queryUserIdIsExist(userId)) {
            return BlogJSONResult.errorMsg("用户id不存在");
        }

        //保存到数据库中的相对路径
        String uploadPathDB = "/" + userId + "/face";


        FileOutputStream fileOutputStream = null;
        InputStream inputStream = null;


        try {
            if(file != null){
                String fileName = file.getOriginalFilename();
                if (StringUtils.isNotBlank(fileName)) {
                    //文件上传的最终保存路径
                    String finalFacePath = FILE_SPACE + uploadPathDB + "/" + fileName;
                    //设置数据库保存的路径
                    uploadPathDB += ("/" + fileName);

                    File outFile = new File(finalFacePath);
                    //创建用户文件夹
                    if (outFile.getParentFile() != null && !outFile.getParentFile().isDirectory()) {
                        //创建父文件夹
                        outFile.getParentFile().mkdirs();
                    }

                    fileOutputStream = new FileOutputStream(outFile);
                    inputStream = file.getInputStream();
                    IOUtils.copy(inputStream, fileOutputStream);//把输入流赋值给输出流,就是把图片复制到输出流对应的路径下
                }
            }else {
                return BlogJSONResult.errorMsg("上传出错1....");
            }
        } catch (IOException e) {
            e.printStackTrace();
            return BlogJSONResult.errorMsg("上传出错2....");
        }finally {
            if(fileOutputStream != null){
                fileOutputStream.flush();
                fileOutputStream.close();
            }
        }


        /**
         * User 与 UserInfo 是 一一对应的关系,UserInfo 有两个候选键 id 和 userId
         */

        UserInfo userInfo = new UserInfo();
        userInfo.setUserId(userId);
        userInfo.setAvatar(uploadPathDB);

        userServiceProxy.updateUserInfo(userInfo);

        return BlogJSONResult.ok(uploadPathDB);
    }

    @UserLoginToken
    @ApiOperation(value = "用户修改密码", notes = "用户修改密码的接口")
    @ApiImplicitParam(name = "userVO", value = "用户id", required = true, dataType = "UserForUpdateVO", paramType = "body")
    @PostMapping(value = "/updatePassword")
    public BlogJSONResult updatePassword(@RequestBody UserForUpdateVO user) throws Exception {

        if (StringUtils.isBlank(user.getUsername()) || StringUtils.isBlank(user.getOldPassword())) {
            return BlogJSONResult.errorMsg("用户名和原密码不能为空");
        }

        if (StringUtils.isBlank(user.getNewPassword())) {
            return BlogJSONResult.errorMsg("新密码不能为空");
        }

        User userResult = userServiceProxy.queryUserForLogin(user.getUsername(), MD5Utils.getMD5Str(user.getOldPassword()));

        if (userResult == null) {
            BlogJSONResult.errorMsg("用户名或原密码不正确");
        }

        User userForUpdate = new User();
        userForUpdate.setId(userResult.getId());
        userForUpdate.setUsername(user.getUsername());
        userForUpdate.setPassword(MD5Utils.getMD5Str(user.getNewPassword()));
        userForUpdate.setPermission(userResult.getPermission());

        boolean updateTrue = userServiceProxy.updateUser(userForUpdate);

        userForUpdate.setPassword("");

        return updateTrue ? BlogJSONResult.ok(userForUpdate) : BlogJSONResult.errorMsg("修改失败");
    }

}
  • 用户标签控制器
package cn.fyupeng.controller.user;

import cn.fyupeng.annotion.UserLoginToken;
import cn.fyupeng.pojo.Articles2tags;
import cn.fyupeng.pojo.Tag;
import cn.fyupeng.pojo.vo.ArticleVO;
import cn.fyupeng.pojo.vo.Articles2tagsVO;
import cn.fyupeng.pojo.vo.TagVO;
import cn.fyupeng.service.*;
import cn.fyupeng.controller.BasicController;
import cn.fyupeng.utils.BlogJSONResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * @Auther: fyp
 * @Date: 2022/4/8
 * @Description:
 * @Package: com.crop.user.controller
 * @Version: 1.0
 */

@CrossOrigin
@Slf4j
@RestController
@Api(value = "标签相关业务的接口", tags = {"标签相关业务的controller"})
@RequestMapping(value = "/user/tag")
public class UserTagController extends BasicController {

    private TagService tagServiceProxy = rpcClientProxy.getProxy(TagService.class);
    private UserService userServiceProxy = rpcClientProxy.getProxy(UserService.class);
    private ArticleService articleServiceProxy = rpcClientProxy.getProxy(ArticleService.class);

    @UserLoginToken
    @PostMapping(value = "/getTag")
    @ApiOperation(value = "获取标签", notes = "获取标签的接口")
    @ApiImplicitParam(name = "tagId", value = "标签id", required = true, dataType = "String", paramType = "query")
    public BlogJSONResult getTag(String tagId) {

        if (StringUtils.isBlank(tagId)) {
            return BlogJSONResult.errorMsg("标签id不能为空");
        }

        Tag tagList = tagServiceProxy.queryTag(tagId);

        return BlogJSONResult.ok(tagList);
    }

    @UserLoginToken
    @PostMapping(value = "/getAllTags")
    @ApiOperation(value = "获取所有标签", notes = "获取所有标签的接口")
    @ApiImplicitParam(name = "userId", value = "用户id", required = true, dataType = "String", paramType = "query")
    public BlogJSONResult getAllTags(String userId) {
        Tag tagWithUserId = new Tag();
        tagWithUserId.setUserId(userId);

        List<TagVO> tagVOList = tagServiceProxy.queryAllTags(tagWithUserId);

        return BlogJSONResult.ok(tagVOList);
    }

    @UserLoginToken
    @PostMapping(value = "/saveTag")
    @ApiOperation(value = "保存标签 - id字段请忽略", notes = "保存标签的接口")
    @ApiImplicitParam(name = "tag", value = "标签", required = true, dataType = "Tag", paramType = "body")
    public BlogJSONResult saveTag(@RequestBody Tag tag) {

        if (StringUtils.isBlank(tag.getName()) || StringUtils.isBlank(tag.getUserId())) {
            return BlogJSONResult.errorMsg("标签名或用户id不能为空");
        }

        tag.setId(null);
        /**
         * id 是候选键 - 标签名 + userId 也是候选键,能唯一 识别 标签
         */
        if (tagServiceProxy.queryTagIsExist(tag)) {
            return BlogJSONResult.errorMsg("标签名已存在");
        }

        boolean saveIsTrue = tagServiceProxy.saveTag(tag);

        return saveIsTrue ? BlogJSONResult.ok(): BlogJSONResult.errorMsg("内部错误导致保存失败");
    }

    @UserLoginToken
    @PostMapping(value = "/updateTag")
    @ApiOperation(value = "更新标签 - userId字段请忽略", notes = "更新标签的接口")
    @ApiImplicitParam(name = "tag", value = "标签", required = true, dataType = "Tag", paramType = "body")
    public BlogJSONResult updateTag(@RequestBody Tag tag) {

        if (StringUtils.isBlank(tag.getId()) || StringUtils.isBlank(tag.getName())) {
            return BlogJSONResult.errorMsg("标签id或标签名不能为空");
        }

        // 用來 防止 其他 用户 对非本地 标签的 更改
        if (StringUtils.isBlank(tag.getUserId())) {
            return BlogJSONResult.errorMsg("用户id不能为空");
        }

        Tag tagWithIdAndUserId = new Tag();
        tagWithIdAndUserId.setId(tag.getId());
        tagWithIdAndUserId.setUserId(tag.getUserId());

        if (!tagServiceProxy.queryTagIsExist(tagWithIdAndUserId)) {
            return BlogJSONResult.errorMsg("该用户没有该标签");
        }

        boolean updateIsTrue = tagServiceProxy.updateTag(tag);

        return updateIsTrue ? BlogJSONResult.ok() : BlogJSONResult.errorMsg("内部错误");
    }

    @UserLoginToken
    @PostMapping(value = "/removeTag")
    @ApiOperation(value = "删除标签 - 连同已标记的文章标签一并删除", notes = "删除标签签的接口")
    @ApiImplicitParam(name = "tagId", value = "标签id", required = true, dataType = "String", paramType = "query")
    public BlogJSONResult removeTag(String tagId) {

        if (StringUtils.isBlank(tagId)) {
            return BlogJSONResult.errorMsg("tagId不能为空");
        }

        // 1. 移除 Tag
        // 2. 移除 Articles2Tags
        boolean delTagIsTrue = tagServiceProxy.deleteTagAndArticleTagWithTagId(tagId);

        if (!delTagIsTrue) {
            return BlogJSONResult.errorMsg("标签id不存在或内部错误导致删除失败");
        }

        return BlogJSONResult.ok();
    }

    @UserLoginToken
    @PostMapping(value = "/getArticleWithNoneTag")
    @ApiOperation(value = "获取无标签文章", notes = "获取无标签文章的接口")
    @ApiImplicitParam(name = "userId", value = "用户id", required = true, dataType = "String", paramType = "query")
    public BlogJSONResult getArticleWithNoneTag(String userId) {

        if (StringUtils.isBlank(userId)) {
            return BlogJSONResult.errorMsg("用户id不能为空");
        }

        if (!userServiceProxy.queryUserIdIsExist(userId)) {
            return BlogJSONResult.errorMsg("用户id不存在");
        }

        List<ArticleVO> articleVOList = articleServiceProxy.queryArticleWithNoneTagByUser(userId);

        return BlogJSONResult.ok(articleVOList);
    }

    @UserLoginToken
    @PostMapping(value = "/getArticleTag")
    @ApiOperation(value = "获取标签文章", notes = "获取标签文章的接口")
    @ApiImplicitParam(name = "tagId", value = "标签id", required = true, dataType = "String", paramType = "query")
    public BlogJSONResult getArticleTag(String tagId) {

        if (StringUtils.isBlank(tagId)) {
            return BlogJSONResult.errorMsg("标签id不能为空");
        }

        Articles2tags articles2tagsWithTagId = new Articles2tags();
        articles2tagsWithTagId.setTagId(tagId);

        List<Articles2tagsVO> articles2tagsVOList = tagServiceProxy.queryArticleTag(articles2tagsWithTagId);

        return BlogJSONResult.ok(articles2tagsVOList);
    }

    @UserLoginToken
    @PostMapping(value = "/markArticleTag")
    @ApiOperation(value = "标记文章标签 - id字段请忽略", notes = "标记文章标签的接口")
    @ApiImplicitParam(name = "articles2tags", value = "文章标签关联", required = true, dataType = "Articles2tags", paramType = "body")
    public BlogJSONResult markArticleTag(@RequestBody Articles2tags articles2tags) {

        if (StringUtils.isBlank(articles2tags.getArticleId()) || StringUtils.isBlank(articles2tags.getTagId())) {
            return BlogJSONResult.errorMsg("文章id或标签id不能为空");
        }

        boolean articleIsExist = articleServiceProxy.queryArticleIsExist(articles2tags.getArticleId());

        Tag tagWithId = new Tag();
        tagWithId.setId(articles2tags.getTagId());
        boolean tagIsExist = tagServiceProxy.queryTagIsExist(tagWithId);

        if (!articleIsExist || !tagIsExist) {
            return BlogJSONResult.errorMsg("文章id或标签id不存在");
        }

        articles2tags.setId(null);
        // 已标记 的 不能 重复标记 - 文章id和标签id 也是一组 候选键
        if (tagServiceProxy.queryArticleTagIsExist(articles2tags)) {
            return BlogJSONResult.errorMsg("关联id已存在,不可重复标记");
        }

        // 标记 Articles2Tags
        boolean saveIsTrue = tagServiceProxy.saveArticleTag(articles2tags);

        return saveIsTrue ? BlogJSONResult.ok() : BlogJSONResult.errorMsg("内部错误导致保存失败");
    }

    @UserLoginToken
    @PostMapping(value = "/reMarkArticleTag")
    @ApiOperation(value = "重新标记文章标签", notes = "重新标记文章标签的接口")
    @ApiImplicitParam(name = "articles2tags", value = "文章标签关联", required = true, dataType = "Articles2tags", paramType = "body")
    public BlogJSONResult reMarkArticleTag(@RequestBody Articles2tags articles2tags) {

        if (StringUtils.isBlank(articles2tags.getId())) {
            return BlogJSONResult.errorMsg("文章标签关联id不能为空");
        }

        // 重新标记 Articles2Tags
        if (StringUtils.isBlank(articles2tags.getArticleId()) || StringUtils.isBlank(articles2tags.getTagId())) {
            return BlogJSONResult.errorMsg("文章id或标签id不能为空");
        }

        boolean articleIsExist = articleServiceProxy.queryArticleIsExist(articles2tags.getArticleId());

        Tag tagWithId = new Tag();
        tagWithId.setId(articles2tags.getTagId());
        boolean tagIsExist = tagServiceProxy.queryTagIsExist(tagWithId);

        if (!articleIsExist || !tagIsExist) {
            return BlogJSONResult.errorMsg("文章id或标签id不存在");
        }

        // 已标记 的 才可以更新
        if (!tagServiceProxy.queryArticleTagIsExist(articles2tags.getId())) {
            return BlogJSONResult.errorMsg("关联id不存在");
        }

        // 标记 Articles2Tags
        boolean saveIsTrue = tagServiceProxy.updateArticleTag(articles2tags);

        return saveIsTrue ? BlogJSONResult.ok() : BlogJSONResult.errorMsg("内部错误导致保存失败");
    }


}
  • 用户评论控制器
package cn.fyupeng.controller.user;

import cn.fyupeng.annotion.PassToken;
import cn.fyupeng.annotion.UserLoginToken;
import cn.fyupeng.enums.CommentStatus;
import cn.fyupeng.pojo.Comment;
import cn.fyupeng.pojo.User;
import cn.fyupeng.service.*;
import cn.fyupeng.utils.BlogJSONResult;
import cn.fyupeng.utils.PagedResult;
import cn.fyupeng.controller.BasicController;
import cn.fyupeng.service.ArticleService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;


/**
 * @Auther: fyp
 * @Date: 2022/4/8
 * @Description:
 * @Package: com.crop.user.controller
 * @Version: 1.0
 */

@CrossOrigin
@Slf4j
@RestController
@Api(value = "评论相关业务的接口", tags = {"评论相关业务的controller"})
@RequestMapping(value = "/user/comment")
public class UserCommentController extends BasicController {

    private TagService tagServiceProxyProxy = rpcClientProxy.getProxy(TagService.class);
    private UserService userServiceProxy = rpcClientProxy.getProxy(UserService.class);
    private ArticleService articleServiceProxy = rpcClientProxy.getProxy(ArticleService.class);
    private ClassficationService classficationServiceProxy = rpcClientProxy.getProxy(ClassficationService.class);
    private CommentService commentServiceProxy = rpcClientProxy.getProxy(CommentService.class);



    @PassToken
    @PostMapping(value = "/getAllComments")
    @ApiOperation(value = "获取文章所有评论", notes = "获取文章所有评论的接口")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "articleId", value = "文章id", required = true, dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "page", value = "当前页", dataType = "Integer", paramType = "query"),
            @ApiImplicitParam(name = "pageSize", value = "页数", dataType = "Integer", paramType = "query"),
            @ApiImplicitParam(name = "sort", value = "排序-1-缺省[正序时间]-2[倒序时间]", dataType = "Integer", paramType = "query")
    })
    public BlogJSONResult getAllComments(String articleId, Integer page, Integer pageSize, Integer sort) {

        if (StringUtils.isBlank(articleId)) {
            return BlogJSONResult.errorMsg("文章id不能为空");
        }

        //前端不传该参时会初始化
        if(page == null){
            page = 1;
        }
        //前端不传该参时会初始化
        if(pageSize == null){
            pageSize = COMMENT_PAGE_SIZE;
        }

        if (sort == null) {
            sort = 1;
        }

        PagedResult pageResult = commentServiceProxy.queryAllComments(articleId, page, pageSize, sort);

        return BlogJSONResult.ok(pageResult);

    }

    @UserLoginToken
    @PostMapping(value = "saveComment")
    @ApiOperation(value = "发表文章评论", notes = "发表文章评论的接口")
    @ApiImplicitParam(name = "comment", value = "评论", required = true, dataType = "Comment", paramType = "body")
    public BlogJSONResult saveComment(@RequestBody Comment comment) {

        if (StringUtils.isBlank(comment.getArticleId())) {
            return BlogJSONResult.errorMsg("articleId不能为空");
        }

        if (StringUtils.isBlank(comment.getFromUserId())) {
            return BlogJSONResult.errorMsg("留言者userId不能为空");
        }

        if (StringUtils.isBlank(comment.getComment())) {
            return BlogJSONResult.errorMsg("评论内容comment不能为空");
        }

        // fateherCommentId 是可以为 null 但是 不能是 空串 或 空白串
        if (comment.getFatherCommentId() != null && StringUtils.isBlank(comment.getFatherCommentId())) {
            return BlogJSONResult.errorMsg("不允许fatherCommentId为空串");
        }
        // toUserId 是可以为 null 但是 不能是 空串 或 空白串
        if (comment.getToUserId() != null && StringUtils.isBlank(comment.getToUserId())) {
            return BlogJSONResult.errorMsg("不允许toUserId为空串");
        }

        if (StringUtils.isNotBlank(comment.getToUserId()) && comment.getToUserId().equals(comment.getFromUserId())) {
            return BlogJSONResult.errorMsg("不能回复toUserId为fromUserId");
        }

        boolean articleIsExist = articleServiceProxy.queryArticleIsExist(comment.getArticleId());
        boolean userIdIsExist = userServiceProxy.queryUserIdIsExist(comment.getFromUserId());

        if (!articleIsExist || !userIdIsExist) {
            return BlogJSONResult.errorMsg("文章id不存在或留言者id不存在");
        }

        // 父 评论 验证
        if (StringUtils.isNotBlank(comment.getFatherCommentId())) {
            boolean fatherCommentIdIsExist = commentServiceProxy.queryCommentIsExist(comment.getFatherCommentId());
            if (!fatherCommentIdIsExist) {
                return BlogJSONResult.errorMsg("父评论id不存在");
            }
        }
        // 被 回复用户验证
        if (StringUtils.isNotBlank(comment.getToUserId())) {
            boolean toUserIsExist = userServiceProxy.queryUserIdIsExist(comment.getToUserId());
            if (!toUserIsExist) {
                return BlogJSONResult.errorMsg("被回复用户id不存在");
            }
        }

        boolean saveIsTrue = commentServiceProxy.saveComment(comment);

        return saveIsTrue ? BlogJSONResult.ok() : BlogJSONResult.errorMsg("内部错误导致保存失败");

    }

    @UserLoginToken
    @PostMapping(value = "/updateMyComment")
    @ApiOperation(value = "更新评论", notes = "更新评论的接口")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "commentId", value = "评论id", required = true, dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "userId", value = "用户id", required = true, dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "content", value = "更新内容", required = true, dataType = "String", paramType = "query")
    })
    public BlogJSONResult updateMyComment(String commentId, String userId, String content) {

        if (StringUtils.isBlank(commentId) || StringUtils.isBlank(userId)) {
            return BlogJSONResult.errorMsg("commentId或userId不能为空");
        }

        Comment comment = commentServiceProxy.queryComment(commentId);

        if (comment == null || !comment.getFromUserId().equals(userId)) {
            return BlogJSONResult.errorMsg("commentId不存在或者userId与commentId约束的userId不同");
        }

        comment.setComment(content);

        boolean commentIsUpdate = commentServiceProxy.updateComment(comment);

        return commentIsUpdate ? BlogJSONResult.ok() : BlogJSONResult.errorMsg("内部错误导致更新失败");
    }

    /**
     * 该方法 已被 弃用 建议使用 public com.crop.utils.BlogJSONResult rollbackMyComment(String commentId, String userId)
     * @param commentId
     * @param userId
     * @return
     */
    @Deprecated
    @UserLoginToken
    @PostMapping(value = "/removeMyComment")
    @ApiOperation(value = "删除评论 - 已废弃", notes = "删除评论的接口")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "commentId", value = "评论id", required = true, dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "userId", value = "用户id", required = true, dataType = "String", paramType = "query")
    })
    public BlogJSONResult removeMyComment(String commentId, String userId) {

        if (StringUtils.isBlank(commentId) || StringUtils.isBlank(userId)) {
            return BlogJSONResult.errorMsg("commentId或userId不能为空");
        }

        Comment comment = commentServiceProxy.queryComment(commentId);

        if (comment == null || !comment.getFromUserId().equals(userId)) {
            return BlogJSONResult.errorMsg("commentId不存在或者userId与commentId约束的userId不同");
        }

        // 如果评论 已经被 追评,则不可撤销
        boolean commentWithFatherCommentIsExist = commentServiceProxy.queryCommentWithFatherCommentIsExist(commentId);

        if (commentWithFatherCommentIsExist) {
            return BlogJSONResult.errorMsg("有子评论约束,普通用户无权限");
        }

        commentServiceProxy.removeCommentById(commentId);

        return BlogJSONResult.ok();
    }

    @UserLoginToken
    @PostMapping(value = "/rollbackMyComment")
    @ApiOperation(value = "撤回评论", notes = "撤回评论的接口")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "commentId", value = "评论id", required = true, dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "userId", value = "用户id", required = true, dataType = "String", paramType = "query")
    })
    public BlogJSONResult rollbackMyComment(String commentId, String userId) {

        if (StringUtils.isBlank(commentId)) {
            return BlogJSONResult.errorMsg("评论id不能为空");
        }

        if (StringUtils.isBlank(userId)) {
            return BlogJSONResult.errorMsg("用户id不能为空");
        }

        Comment comment = commentServiceProxy.queryComment(commentId);
        if (comment == null) {
            return BlogJSONResult.errorMsg("评论id不存在");
        }

        User user = userServiceProxy.queryUser(userId);
        if (user == null) {
            return BlogJSONResult.errorMsg("用户id不存在");
        }

        if (!comment.getFromUserId().equals(user.getId())) {
            return BlogJSONResult.errorMsg("非本用户评论无权撤回");
        }

        commentServiceProxy.setCommentStatusWithFatherId(comment, CommentStatus.BLOCKED);

        return BlogJSONResult.ok();

    }

}
  • 用户注册与登录控制器
package cn.fyupeng.controller.user;

import cn.fyupeng.annotion.PassToken;
import cn.fyupeng.controller.BasicController;
import cn.fyupeng.annotion.UserLoginToken;
import cn.fyupeng.pojo.User;
import cn.fyupeng.pojo.vo.UserVO;
import cn.fyupeng.service.UserService;
import cn.fyupeng.utils.BlogJSONResult;
import cn.fyupeng.utils.MD5Utils;
import cn.fyupeng.utils.RedisUtils;
import cn.fyupeng.utils.TokenUtils;
import com.auth0.jwt.JWT;
import com.sun.corba.se.spi.servicecontext.UEInfoServiceContext;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import jdk.nashorn.internal.parser.Token;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.*;

/**
 * @Auther: fyp
 * @Date: 2022/8/17
 * @Description: 注册和登录 控制器
 * @Package: cn.fyupeng.controller
 * @Version: 1.0
 */

@CrossOrigin
@RestController
@RequestMapping(value = "/user")
@Api(value = "用户注册登录的接口", tags = {"注册和登录的controller"})
public class UserRegisterAndLoginController extends BasicController {

    private UserService userServiceProxy = rpcClientProxy.getProxy(UserService.class);

    /**
     * 拦截器不拦截注册,所以不需要注解 @PassToken
     * @param user
     * @return
     * @throws Exception
     */
    @ApiOperation(value = "用户注册", notes = "用户注册的接口")
    @ApiImplicitParam(name = "user", value = "用户", required = true, dataType = "User", paramType = "body")
    @PostMapping(value = "/regist")
    public BlogJSONResult regist(@RequestBody User user) throws Exception {

        //1. 判断用户名和密码必须不为空
        if(StringUtils.isBlank(user.getUsername()) || StringUtils.isBlank(user.getPassword())){
            return BlogJSONResult.errorMsg("用户名和密码不能为空");
        }

        //2. 判断用户是否存在
        boolean usernameIsExist = userServiceProxy.queryUsernameIsExist(user.getUsername());
        //3. 保存用户,注册信息
        if(!usernameIsExist){
            user.setPassword(MD5Utils.getMD5Str(user.getPassword()));
            // 防止 被 注入
            user.setPermission(2);
            userServiceProxy.saveUser(user);
        }else {
            return BlogJSONResult.errorMsg("用户名已存在");
        }
        user.setPassword("");

        //UserVO userVO = setUserRedisSessionToken(user);

        return BlogJSONResult.ok(user);
    }

    public UserVO setUserRedisSessionToken(User userModel) throws Exception {
        //String uniqueToken = UUID.randomUUID().toString();
        String token = TokenUtils.token(userModel.getId(), userModel.getUsername(), userModel.getPassword());

        String userRedisSession = RedisUtils.getUserRedisSession(userModel.getId());

        redis.set(userRedisSession, token,  60 * 5);

        UserVO usersVO = new UserVO();
        BeanUtils.copyProperties(userModel, usersVO);
        usersVO.setUserToken(token);
        return usersVO;
    }
    /**
     * 拦截器不拦截登录,所以不需要注解 @PassToken
     * @param user
     * @return
     * @throws Exception
     */
    @ApiOperation(value = "用户登录", notes = "用户登录的接口")
    @ApiImplicitParam(name = "user", value = "用户", required = true, dataType = "User", paramType = "body")
    @PostMapping(value = "/login")
    public BlogJSONResult login(@RequestBody User user) throws Exception {
        String username = user.getUsername();
        String password = user.getPassword();

        if(StringUtils.isBlank(username) || StringUtils.isBlank(password)){
            return BlogJSONResult.errorMsg("用户名或密码不能为空....");
        }

        User userResult = userServiceProxy.queryUserForLogin(username, MD5Utils.getMD5Str(user.getPassword()));

        if(userResult == null || userResult.getPermission() != 2){
            return BlogJSONResult.errorMsg("用户名密码不正确,或为非用户登录");
        }else {
            UserVO usersVO = setUserRedisSessionToken(userResult);
            usersVO.setPassword("");
            return BlogJSONResult.ok(usersVO);

        }
    }

    @UserLoginToken
    @ApiOperation(value = "更新秘钥", notes = "用户更新秘钥的接口")
    @ApiImplicitParam(name = "token", value = "秘钥", required = true, dataType = "String", paramType = "query")
    @PostMapping(value = "/updateToken")
    public BlogJSONResult updateToken(String token) throws Exception {

        String userId;
        String username;
        String password;

        System.out.println(token);

        userId = JWT.decode(token).getClaim("userId").asString();
        username = JWT.decode(token).getClaim("username").asString();
        password = JWT.decode(token).getClaim("password").asString();

        if(StringUtils.isBlank(username) || StringUtils.isBlank(password)){
            return BlogJSONResult.errorMsg("用户名或密码不能为空....");
        }

        User userResult = userServiceProxy.queryUserForLogin(username, password);

        if(userResult == null){
            return BlogJSONResult.errorMsg("Illegal key, update failed");
        }else {
            User tokenUser = new User();
            tokenUser.setId(userId);
            tokenUser.setUsername(username);
            tokenUser.setPassword(password);

            UserVO usersVO = setUserRedisSessionToken(tokenUser);
            usersVO.setPassword("");
            return BlogJSONResult.ok(usersVO);
        }
    }

    @UserLoginToken
    @ApiOperation(value = "用户注销", notes = "用户注销的接口")
    @ApiImplicitParam(name = "userId", value = "用户id", required = true, dataType = "String", paramType = "query")
    @PostMapping(value = "/logout")
    public BlogJSONResult logout(String userId) throws Exception {

        String userRedisSession = RedisUtils.getUserRedisSession(userId);
        redis.del(userRedisSession);

        return BlogJSONResult.ok("注销成功");

    }

}
  • 管理注册与登录控制器
package cn.fyupeng.controller.admin;

import cn.fyupeng.annotion.PassToken;
import cn.fyupeng.annotion.UserLoginToken;
import cn.fyupeng.controller.BasicController;
import cn.fyupeng.pojo.User;
import cn.fyupeng.pojo.vo.UserVO;
import cn.fyupeng.service.UserService;
import cn.fyupeng.utils.BlogJSONResult;
import cn.fyupeng.utils.MD5Utils;
import cn.fyupeng.utils.RedisUtils;
import cn.fyupeng.utils.TokenUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.*;

import java.util.UUID;


@CrossOrigin
@SuppressWarnings("all")
@RestController
@RequestMapping(value = "/admin")
@Api(value = "管理员注册登录的接口", tags = {"管理员注册和登录的controller"})
public class AdminRegistLoginController extends BasicController {
    
    private UserService userServiceProxy = rpcClientProxy.getProxy(UserService.class);

    @UserLoginToken
    @ApiOperation(value = "管理员注册", notes = "用管理员注册的接口")
    @ApiImplicitParam(name = "user", value = "用户", required = true, dataType = "User", paramType = "body")
    @PostMapping(value = "/regist")
    public BlogJSONResult regist(@RequestBody User user) throws Exception{

        //1. 判断用户名和密码必须不为空
        if(StringUtils.isBlank(user.getUsername()) || StringUtils.isBlank(user.getPassword())){
            return BlogJSONResult.errorMsg("用户名和密码不能为空");
        }

        //2. 判断用户是否存在
        boolean usernameIsExist = userServiceProxy.queryUsernameIsExist(user.getUsername());
        //3. 保存用户,注册信息
        if(!usernameIsExist){
            user.setPassword(MD5Utils.getMD5Str(user.getPassword()));
            // 防止 被 注入
            user.setPermission(3);
            userServiceProxy.saveUser(user);
        }else {
            return BlogJSONResult.errorMsg("用户名已存在");
        }
        user.setPassword("");
        //String uniqueToken = UUID.randomUUID().toString();
        //redis.set(USER_REDIS_SESSION + ":" + user.getId(), uniqueToken, 1000 * 60 * 30);
        //
        //UsersVO usersVO = new UsersVO();
        //BeanUtils.copyProperties(user, usersVO);
        //usersVO.setUserToken(uniqueToken);

        //UserVO userVO = setUserRedisSessionToken(user);
        user.setPassword("");

        return BlogJSONResult.ok(user);
    }

    public UserVO setUserRedisSessionToken(User userModel) throws Exception {
        //String uniqueToken = UUID.randomUUID().toString();
        //userModel.getPassword() 已经是加密的了
        String token = TokenUtils.token(userModel.getId(), userModel.getUsername(), userModel.getPassword());

        String userRedisSession = RedisUtils.getUserRedisSession(userModel.getId());

        redis.set(userRedisSession, token,  60 * 5);

        UserVO usersVO = new UserVO();
        BeanUtils.copyProperties(userModel, usersVO);
        usersVO.setUserToken(token);
        return usersVO;
    }

    @PassToken
    @ApiOperation(value = "管理员登录", notes = "管理员登录的接口")
    @ApiImplicitParam(name = "user", value = "用户", required = true, dataType = "User", paramType = "body")
    @PostMapping(value = "/login")
    public BlogJSONResult login(@RequestBody User user) throws Exception {
        String username = user.getUsername();
        String password = user.getPassword();


        if(StringUtils.isBlank(username) || StringUtils.isBlank(password)){
            return BlogJSONResult.ok("用户名或密码不能为空....");
        }

        User userResult = userServiceProxy.queryUserForLogin(username, MD5Utils.getMD5Str(user.getPassword()));

        if(userResult == null || userResult.getPermission() != 3){
            return BlogJSONResult.errorMsg("用户名密码不正确,或为非管理员登录");
        }else {
            UserVO usersVO = setUserRedisSessionToken(userResult);
            userResult.setPassword("");
            return BlogJSONResult.ok(usersVO);

        }
    }

    @ApiOperation(value = "管理员注销", notes = "管理员注销的接口")
    @ApiImplicitParam(name = "userId", value = "用户id", required = true, dataType = "String", paramType = "query")
    @PostMapping(value = "/logout")
    public BlogJSONResult logout(String userId) throws Exception {

        String userRedisSession = RedisUtils.getAdminRedisSession(userId);
        redis.del(userRedisSession);

        return BlogJSONResult.ok("注销成功");

    }

}
  • 管理员文章控制器
package cn.fyupeng.controller.admin;

import cn.fyupeng.annotion.UserLoginToken;
import cn.fyupeng.controller.BasicController;
import cn.fyupeng.pojo.Article;
import cn.fyupeng.pojo.Classfication;
import cn.fyupeng.pojo.User;
import cn.fyupeng.service.ArticleService;
import cn.fyupeng.service.ClassficationService;
import cn.fyupeng.service.UserService;
import cn.fyupeng.service.TagService;
import cn.fyupeng.utils.BlogJSONResult;
import cn.fyupeng.utils.PagedResult;
import cn.fyupeng.utils.RedisUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @Auther: fyp
 * @Date: 2022/4/5
 * @Description:
 * @Package: com.crop.admin.controller
 * @Version: 1.0
 */

@CrossOrigin
@Slf4j
@RestController
@RequestMapping(value = "/admin/article")
@Api(value = "文章相关业务的接口", tags = {"文章相关业务的controller"})
public class AdminArticleController extends BasicController {

    private TagService tagServiceProxy = rpcClientProxy.getProxy(TagService.class);
    private UserService userServiceProxy = rpcClientProxy.getProxy(UserService.class);
    private ArticleService articleServiceProxy = rpcClientProxy.getProxy(ArticleService.class);
    private ClassficationService classficationServiceProxy = rpcClientProxy.getProxy(ClassficationService.class);

    private static ScheduledExecutorService executor;


    @UserLoginToken
    @PostMapping(value = "/removeClassfication")
    @ApiOperation(value = "删除文章分类 - 注意: 文章分类为所有用户公共的分类,方便查询,私有分类情使用标签", notes = "删除文章分类的接口")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "classficationId", value = "文章分类id", required = true, dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "userId", value = "用户id", required = true, dataType = "String", paramType = "query")

    })
    public BlogJSONResult removeClassfication(String classficationId, String userId) {

        if (StringUtils.isBlank(userId)) {
            return BlogJSONResult.errorMsg("用户id不能为空");
        }

        User user = userServiceProxy.queryUser(userId);
        // 用户不存在 或 无权 删除
        if (user == null || user.getPermission() != 3) {
            return BlogJSONResult.errorMsg("用户不存在或你无权执行该操作");
        }

        Article article = new Article();
        article.setClassId(classficationId);
        PagedResult pagedResult = articleServiceProxy.queryArticleSelective(article, 1, 1);

        if (pagedResult.getRecords() != 0) {
            return BlogJSONResult.errorMsg("删除失败!存在文章绑定了分类id: " + classficationId);
        }

        boolean deleteClassficationIsTrue = classficationServiceProxy.deleteClassfication(classficationId);

        return deleteClassficationIsTrue ? BlogJSONResult.ok() : BlogJSONResult.errorMsg("分类id不存在或内部错误");
    }

    @UserLoginToken
    @PostMapping(value = "/updateClassfication")
    @ApiOperation(value = "更新文章分类", notes = "更新文章分类的接口")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "classfication", value = "文章分类", required = true, dataType = "Classfication", paramType = "body"),
            @ApiImplicitParam(name = "userId", value = "用户id", required = true, dataType = "String", paramType = "query")

    })
    public BlogJSONResult updateClassfication(@RequestBody Classfication classfication, String userId) {

        if (StringUtils.isBlank(userId)) {
            return BlogJSONResult.errorMsg("用户id不能为空");
        }

        User user = userServiceProxy.queryUser(userId);
        // 用户不存在 或 无权 删除
        if (user == null || user.getPermission() != 3) {
            return BlogJSONResult.errorMsg("用户不存在或你无权执行该操作");
        }

        boolean updateClassficationIsTrue = classficationServiceProxy.updateClassfication(classfication);

        return updateClassficationIsTrue ? BlogJSONResult.ok() : BlogJSONResult.errorMsg("分类id不存在或内部错误导致更新失败");
    }

    @UserLoginToken
    @PostMapping(value = "/saveClassfication")
    @ApiOperation(value = "新建文章分类", notes = "新建文章分类的接口")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "classficationName", value = "分类名", required = true, dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "userId", value = "用户id", required = true, dataType = "String", paramType = "query")
    })

    public BlogJSONResult saveClassFication(String classficationName, String userId) {

        if (StringUtils.isBlank(classficationName)) {
            return BlogJSONResult.errorMsg("分类名不能为空");
        }

        User user = userServiceProxy.queryUser(userId);
        // 用户不存在 或 无权 删除
        if (user == null || user.getPermission() != 3) {
            return BlogJSONResult.errorMsg("用户不存在或你无权执行该操作");
        }

        Classfication classfication = new Classfication();
        classfication.setName(classficationName);

        Classfication classficationIsExist = classficationServiceProxy.queryClassfication(classfication);
        if (classficationIsExist != null) {
            return BlogJSONResult.errorMsg("分类名已存在");
        }
        boolean saveIsTrue = classficationServiceProxy.saveClassfication(classfication);

        return saveIsTrue ? BlogJSONResult.ok() : BlogJSONResult.errorMsg("内部错误导致保存失败");
    }


    @UserLoginToken
    @PostMapping(value = "/startTimeTask")
    @ApiOperation(value = "开启任务 - 自动更新阅读量", notes = "开启任务的接口")
    @ApiImplicitParam(name = "userId", value = "用户id", required = true, dataType = "String", paramType = "query")
    public BlogJSONResult startTimeTask(String userId) {

        if (StringUtils.isBlank(userId)) {
            return BlogJSONResult.errorMsg("用户Id不能为空");
        }

        User user = userServiceProxy.queryUser(userId);
        if (user == null || user.getPermission() != 3) {
            return BlogJSONResult.errorMsg("用户Id不存在或无权限");
        }

        if (executor != null && !executor.isShutdown()) {
            return BlogJSONResult.errorMsg("任务已启动,无需重启");
        }
        executor = Executors.newScheduledThreadPool(2);
        log.info("正在开启任务线程池...");
        long initialDelay = 1 * 1000;
        long fiveMinute = 5 * 60 * 1000;
        executor.scheduleAtFixedRate(() -> {
            /**
             * 定时任务 - 更新 阅读量
             */
            // 获取 所有用户 对Id的 阅读量
            String viewCount = RedisUtils.getViewCount();
            List<String> keys = redis.getKeysByPrefix(viewCount);
            /**
             * key格式: "crop:viewCount:2204057942HA6Z7C"
             * 获取 articleId : 2204057942HA6Z7C
             */
            List<String> articleIdKeys = new ArrayList<>();
            Map<String, String> articleMap = new HashMap<>();



            for (String k : keys) {
                // 匹配 最后一个 : 到结束
                String tempArticleId = k.substring(k.lastIndexOf(":") + 1);
                articleIdKeys.add(tempArticleId);
            }

            List<String> articleIdCounts = redis.multiGet(keys);

            for (int i = 0; i < articleIdKeys.size(); i++) {
                articleMap.put(articleIdKeys.get(i), articleIdCounts.get(i));
            }

            articleServiceProxy.multiUpdateArticleReadCounts(articleIdKeys, articleMap);
            log.info("完成一次周期任务 - 任务正常");

        }, initialDelay, fiveMinute, TimeUnit.MILLISECONDS);

        return BlogJSONResult.ok("任务启动成功");

    }

    @UserLoginToken
    @PostMapping(value = "/stopTimeTask")
    @ApiOperation(value = "关闭任务", notes = "关闭任务的接口")
    @ApiImplicitParam(name = "userId", value = "用户id", required = true, dataType = "String", paramType = "query")
    public BlogJSONResult stopTimeTask(String userId) {

        if (StringUtils.isBlank(userId)) {
            return BlogJSONResult.errorMsg("用户Id不能为空");
        }

        User user = userServiceProxy.queryUser(userId);
        if (user == null || user.getPermission() != 3) {
            return BlogJSONResult.errorMsg("用户Id不存在或无权限");
        }

        if (executor == null || executor.isTerminated()) {
            return BlogJSONResult.errorMsg("任务未启动");
        }
        executor.shutdown();
        try {
            executor.awaitTermination(10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            log.error("任务线程池关闭失败: ",e);
            executor.shutdownNow();
            //e.printStackTrace();
        }
        log.info("任务线程池已成功关闭");
        return BlogJSONResult.ok("任务关闭成功");
    }

}
  • 管理员评论控制器
package cn.fyupeng.controller.admin;

import cn.fyupeng.annotion.UserLoginToken;
import cn.fyupeng.controller.BasicController;
import cn.fyupeng.enums.CommentStatus;
import cn.fyupeng.pojo.Comment;
import cn.fyupeng.pojo.User;
import cn.fyupeng.pojo.vo.CommentVO;
import cn.fyupeng.service.*;
import cn.fyupeng.utils.BlogJSONResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

/**
 * @Auther: fyp
 * @Date: 2022/4/8
 * @Description:
 * @Package: com.crop.admin.controller
 * @Version: 1.0
 */

@CrossOrigin
@Slf4j
@RestController
@RequestMapping(value = "/admin/comment")
@Api(value = "评论相关业务的接口", tags = {"评论相关业务的controller"})
public class AdminCommentController extends BasicController {

    private UserService userServiceProxy = rpcClientProxy.getProxy(UserService.class);
    private CommentService commentServiceProxy = rpcClientProxy.getProxy(CommentService.class);


    @UserLoginToken
    @PostMapping(value = "removeComment")
    @ApiOperation(value = "强制删除评论", notes = "强制删除评论的接口")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "commentId", value = "评论id", required = true, dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "userId", value = "用户id", required = true, dataType = "String", paramType = "query")
    })
    public BlogJSONResult removeComment(String commentId, String userId) {

        if (StringUtils.isBlank(commentId) || StringUtils.isBlank(userId)) {
            return BlogJSONResult.errorMsg("commentId或userId不能为空");
        }

        boolean commentIsExist = commentServiceProxy.queryCommentIsExist(commentId);

        if (!commentIsExist) {
            return BlogJSONResult.errorMsg("commentId不存在");
        }

        User identifyUser = userServiceProxy.queryUser(userId);
        if (identifyUser == null || identifyUser.getPermission() != 3) {
            return BlogJSONResult.errorMsg("用户Id不存在或无权限");
        }

        commentServiceProxy.removeCommentById(commentId);
        commentServiceProxy.removeCommentWithFatherCommentId(commentId);

        return BlogJSONResult.ok();
    }


    @UserLoginToken
    @PostMapping(value = "/filterComments")
    @ApiOperation(value = "过滤查询评论", notes = "过滤查询评论的接口")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "aPattern", value = "文章匹配", dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "cPattern", value = "评论匹配", dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "userId", value = "用户id", dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "startTime", value = "评论开始时间 - yyyy-MM-dd HH:mm:ss", required = true, dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "endTime", value = "评论结束时间 - yyyy-MM-dd HH:mm:ss", required = true, dataType = "String", paramType = "query")
    })
    public BlogJSONResult filterComments(String aPattern, String cPattern, String userId, String startTime, String endTime) {

        if (StringUtils.isBlank(aPattern) && StringUtils.isBlank(cPattern)) {
            return BlogJSONResult.errorMsg("至少指定文章匹配或评论匹配");
        }

        if (!(StringUtils.isNotBlank(startTime) && StringUtils.isNotBlank(endTime))) {
            return BlogJSONResult.errorMsg("必须指定时间跨度");
        }

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date startDate = null;
        Date endDate = null;
        try {
            startDate = sdf.parse(startTime);
            endDate = sdf.parse(endTime);
        } catch (ParseException e) {
            e.printStackTrace();
            return BlogJSONResult.errorMsg("时间格式不正确");
        }

        List<CommentVO> result = null;
        if (StringUtils.isBlank(userId)) {
            result = commentServiceProxy.queryAllComments(aPattern, cPattern, null, startDate, endDate);
        } else {
            result = commentServiceProxy.queryAllComments(aPattern, cPattern, userId, startDate, endDate);
        }

        return BlogJSONResult.ok(result);

    }

    @UserLoginToken
    @PostMapping(value = "/setCommentStatus")
    @ApiOperation(value = "屏蔽评论 - 1 正常 - 2 屏蔽", notes = "屏蔽评论的接口")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "commentId", value = "评论id", dataType = "Integer", paramType = "query"),
            @ApiImplicitParam(name = "status", value = "评论状态", dataType = "String", paramType = "query"),
            @ApiImplicitParam(name = "userId", value = "用户id", dataType = "String", paramType = "query"),
    })
    public BlogJSONResult setCommentStatus(String commentId, Integer status, String userId) {

        if (StringUtils.isBlank(commentId) || status == null) {
            return BlogJSONResult.errorMsg("评论id或状态不能为空");
        }

        if (StringUtils.isBlank(userId)) {
            return BlogJSONResult.errorMsg("用户id不能为空");
        }

        Comment comment = commentServiceProxy.queryComment(commentId);
        if (comment == null) {
            return BlogJSONResult.errorMsg("评论id不存在");
        }

        User user = userServiceProxy.queryUser(userId);
        if (user == null || user.getPermission() == 2) {
            return BlogJSONResult.errorMsg("用户id不存在或无权限访问");
        }

        if (status == 1) {
            commentServiceProxy.setCommentStatusWithFatherId(comment, CommentStatus.NORMAL);

        } else if(status == 2) {
            commentServiceProxy.setCommentStatusWithFatherId(comment, CommentStatus.BLOCKED);
        } else {
            return BlogJSONResult.errorMsg("无其他状态可操作");
        }

        return BlogJSONResult.ok();

    }

}
2.8 服务接口
  • 文章服务
package cn.fyupeng.service;

import cn.fyupeng.pojo.Article;
import cn.fyupeng.pojo.vo.ArticleVO;
import cn.fyupeng.utils.PagedResult;

import java.util.List;
import java.util.Map;

/**
 * @Auther: fyp
 * @Date: 2022/4/2
 * @Description:
 * @Package: com.crop.service
 * @Version: 1.0
 */
public interface ArticleService {

    boolean queryArticleIsExist(String articleId);

    boolean queryArticleIsUser(Article article);

    boolean save(Article article);

    PagedResult queryArticleSelective(Article article, Integer page, Integer pageSize);

    ArticleVO queryArticleDetail(String articleId);

    boolean saveWithIdAndUserId(Article article);

    void multiUpdateArticleReadCounts(List<String> articleIdKeys, Map<String, String> articleMap);

    void removeArticle(String articleId);

    PagedResult queryArticleByTime(Long timeDifference, Integer page, Integer pageSize);

    List<ArticleVO> queryArticleWithNoneTagByUser(String userId);
}
  • 评论服务
package cn.fyupeng.service;


import cn.fyupeng.enums.CommentStatus;
import cn.fyupeng.pojo.Comment;
import cn.fyupeng.pojo.vo.CommentVO;
import cn.fyupeng.utils.PagedResult;

import java.util.Date;
import java.util.List;

/**
 * @Auther: fyp
 * @Date: 2022/4/3
 * @Description:
 * @Package: com.crop.service
 * @Version: 1.0
 */
public interface CommentService {

    boolean saveComment(Comment comment);


    void removeCommentById(String commentId);

    Comment queryComment(String commentId);

    boolean queryCommentIsExist(String commentId);

    boolean updateComment(Comment comment);

    PagedResult queryAllComments(String articleId, Integer page, Integer pageSize, Integer sort);

    boolean queryCommentWithFatherCommentIsExist(String commentId);

    void removeCommentWithFatherCommentId(String fatherCommentId);

    List<CommentVO> queryAllComments(String aPattern, String cPattern, String userId, Date startTime, Date endTime);

    void setCommentStatusWithFatherId(Comment comment, CommentStatus commentStatus);
}
  • 用户服务
package cn.fyupeng.service;

import cn.fyupeng.pojo.User;
import cn.fyupeng.pojo.UserInfo;

/**
 * @Auther: fyp
 * @Date: 2022/8/17
 * @Description: 用户业务服务接口
 * @Package: cn.fyupeng.service
 * @Version: 1.0
 */
public interface UserService {

    /**
     * @Description: 判断用户名是否存在
     * @param username
     * @return
     */
    public boolean queryUsernameIsExist(String username);

    /**
     * @Description: 用户登录,根据用户名和密码查询用户
     * @param username
     * @param password
     * @return
     */
    public User queryUserForLogin(String username, String password);

    /**
     * 查询用户信息
     * @param userId
     * @return
     */
    User queryUser(String userId);

    /**
     * 查询用户详细信息
     * @param userId
     * @return
     */
    public UserInfo queryUserInfo(String userId);

    /**
     * 查询用户是否存在
     * @param userId
     * @return
     */
    boolean queryUserIdIsExist(String userId);
    /**
     * @Description: 用户修改信息
     * @param user
     */
    public boolean updateUser(User user);

    /**
     * @Description: 用户修改详细信息
     * @param user
     */

    /**
     * @Description: 保存用户(注册用户)
     * @param user
     */
    public void saveUser(User user);

    public void updateUserInfo(UserInfo user);

}
  • 标签服务
package cn.fyupeng.service;


import cn.fyupeng.pojo.Articles2tags;
import cn.fyupeng.pojo.Tag;
import cn.fyupeng.pojo.vo.Articles2tagsVO;
import cn.fyupeng.pojo.vo.TagVO;

import java.util.List;

/**
 * @Auther: fyp
 * @Date: 2022/4/8
 * @Description:
 * @Package: com.crop.service
 * @Version: 1.0
 */
public interface TagService {

    boolean queryTagIsExist(Tag tag);

    boolean queryArticleTagIsExist(String id);

    boolean queryArticleTagIsExist(Articles2tags articles2tags);

    List<TagVO> queryAllTags(Tag tag);

    boolean saveTag(Tag tag);

    boolean updateTag(Tag tag);

    boolean deleteTag(String tagId);

    void delArticleTag(String tagId);

    boolean deleteTagAndArticleTagWithTagId(String tagId);

    Tag queryTag(String tagId);

    List<Articles2tagsVO> queryArticleTag(Articles2tags articles2tags);

    boolean saveArticleTag(Articles2tags articles2tags);

    boolean updateArticleTag(Articles2tags articles2tags);

    boolean deleteTagAndArticleTagWithArticleId(String articleId);
}
  • 分类服务
package cn.fyupeng.service;

import cn.fyupeng.pojo.Classfication;

import java.util.List;

/**
 * @Auther: fyp
 * @Date: 2022/4/3
 * @Description:
 * @Package: com.crop.service
 * @Version: 1.0
 */
public interface ClassficationService {
    boolean queryClassficationIdIsExist(String classId);

    boolean saveClassfication(Classfication classfication);

    Classfication queryClassfication(Classfication classfication);

    List<Classfication> queryAllClassfications();

    boolean deleteClassfication(String classficationId);

    boolean updateClassfication(Classfication clssfication);
}

项目链接地址:https://github.com/fyupeng/distributed-blog-system-api

那么第六天的项目就到这里,大家觉得很赞不妨给个关注或者收藏吧,谢谢各位!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嗝屁小孩纸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值