基于Springboot2+Vue3的Partner交友项目(二)

登陆注册

集成Redis

之前存验证码,在后端用Map存,重启服务器就会消失,所以存在第三方Redis中

Redis学习三部曲

  1. 使用StringRedisTemplate
  2. 使用RedisTemplate
  3. 使用RedisUtils封装

集成Redis过程

用之前先启动 ->定义枚举获取email发送类型和做判断 -> Constants 做了email.code 的key

-> RedisConfig.java -> RedisUtil -> .yml文件中配置 -> pom中添加两个依赖(工厂和本身的配置)

pom.xml引入依赖:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency>
  <groupId>cn.dev33</groupId>
  <artifactId>sa-token-dao-redis-jackson</artifactId>
  <version>1.33.0</version>
</dependency>
<!-- 提供Redis连接池 -->
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-pool2</artifactId>
</dependency>

application.yml配置:

spring:
  redis:
  # Redis数据库索引(默认为0)
  database: 0
  # Redis服务器地址
  host: 127.0.0.1
  # Redis服务器连接端口
  port: 6379
  # Redis服务器连接密码(默认为空)
  # password:
  # 连接超时时间
  timeout: 10s
  lettuce:
    pool:
      # 连接池最大连接数
      max-active: 200
      # 连接池最大阻塞等待时间(使用负值表示没有限制)
      max-wait: -1ms
      # 连接池中的最大空闲连接
      max-idle: 10
      # 连接池中的最小空闲连接
      min-idle: 0

Redis优化

问题一:之前的优化:第一次访问接口会很慢 原因:数据库,Tomcat等都没初始化。引入Redis后的访
问接口又变慢了,原因是因为从redis拿数据,需要连接。

优化一:初始化redis连接,对Redis预先查询(和数据库初始化一样) 结果:从约1s到100ms

优化二:sendEmail方法中将某些操作异步,异步实现发邮件和写入缓存,因为用户收到验证码后在写入Redis才有意义

问题二:忘记密码时,输入数据库没有的邮箱会浪费一次发邮件的机会,且接口容易被攻击。解决:后台做验证。

问题三:虽然验证了邮箱,但请求还是发送了出去。

解决:前端将倒计时结果放在请求成功后。

集成sa-token

Sa-Token 是一个轻量级 Java 权限认证框架,主要解决:登录认证权限认证Session会话单点登录OAuth2.0微服务网关鉴权 等一系列权限相关问题。

pom.xml引入依赖:

<properties>
    <sa-token.version>1.33.0</sa-token.version>
</properties>
<!-- redis  -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 解决LocalDateTime类型数据无法序列化 -->
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.14.0</version>
</dependency>

<!-- Sa-Token 权限认证,在线文档:https://sa-token.cc -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-spring-boot-starter</artifactId>
    <version>${sa-token.version}</version>
</dependency>

<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-dao-redis-jackson</artifactId>
    <version>${sa-token.version}</version>
</dependency>
<!-- Sa-Token 整合 jwt -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-jwt</artifactId>
    <version>${sa-token.version}</version>
</dependency>
<!-- 提供Redis连接池 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

application.yml配置:

sa-token:
  token-name: Authorization
  # token有效期,单位s 默认2小时, -1代表永不过期
  timeout: 7200
  # 是否允许同一账号并发登录
  is-concurrent: true
  # 在多人登录同一账号时,是否共用一个token
  is-share: true
  # token风格
  token-style: simple-uuid
  # 是否输出操作日
  is-log: false
  # token前缀  注意必须是 Bearer {token}, Bearer后面加空格
  token-prefix: Bearer
  # jwt秘钥
  jwt-secret-key: qwertyuiop[]\';lkjhgfdsazxcvbnm,./

代码配置 (注意:redis配置除外,单独配):

SaTokenConfigure.java

import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
import cn.dev33.satoken.stp.StpLogic;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SaTokenConfigure {
    // Sa-Token 整合 jwt (Simple 简单模式)
    @Bean
    public StpLogic getStpLogicJwt() {
        return new StpLogicJwtForSimple();
    }
}

MyWebMvcConfig.java

@Configuration
public class MyWebMvcConfig extends WebMvcConfigurationSupport {

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        // 注册 Sa-Token 拦截器,校验规则为 StpUtil.checkLogin() 登录校验。
        registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
                .addPathPatterns("/**")
                .excludePathPatterns("/", "/login", "/register", "/email", "/password/reset")
                .excludePathPatterns("/swagger**/**", "/webjars/**", "/v3/**", "/doc.html", "");  // 排除 swagger拦截
    }
}

全局异常处理

@ExceptionHandler(value = NotLoginException.class)
public Result saTokenExceptionError(NotLoginException e) {
    return Result.error("401", e.getMessage());
}

使用

// 第1步,先登录上
StpUtil.login(dbUser.getUid());
// 设置用户对象到缓存
StpUtil.getSession().set("userInfo", dbUser);
// 第2步,获取 Token  相关参数
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
log.info("tokenInfo: {}", tokenInfo);
// 获取 token
String tokenValue = tokenInfo.getTokenValue();

// 获取当前登录的用户信息
User userInfo = StpUtil.getSession().getModel("userInfo", User.class);

问题集

问题二:输入账号密码登录时也出现了权限验证。报错:未获取有效的token。

解决:因为登陆时没有返回token,二是返回接口时没加token。token是一个加密的字符串,像一个令
牌,有他就可以获取后台被保护的数据。前后端项目不能使

问题三:用内存里的token,重启后之前的token就失效了。

解决:应该用第三方存储,Redis。

问题四:待解决:登陆跳转主页,注册不跳转主页。原因: user hasUser 都没取到值 均为undefined
解决:数据的结构不一样了,包含了两层。在前端存数据时需要定义宁外的值

在这里插入图片描述

​ 上述添加setLoginInfo,问题还是没被解决。前端debugger一下,发现是后端,注册返回的接口不对还是res。逻辑上注册和登录是两个逻辑,注册后还是需要用户自己登录,这里就不实现注册后自动登录的功能了。注册也就不返 回信息了。token的发放都放在登录里面,只有通过登录才能拿到token,而注册拿不到。

问题五:

在这里插入图片描述

解决:前端格式错误

在这里插入图片描述

问题六:用户信息的创建时间和修改时间是数组格式,应该为一个时间戳

解决:redis序列化初始化问题。创建 LDTConfig.java。前端拿到时间戳可以转化为任意时间类型。代码生成器改了后重新生成,但原来的代码记得备份一份。User / UserController /… 上传到git来的,也可以点击还原,但只能还原到上一次写之前,新添的没保存的不能恢复。

在这里插入图片描述

密码加密

// 1 AES对称加密
String key = "QWER+TYU/=IO_PAL=SKDJF.GHZMXNCBV";
String ciphertext = SaSecureUtil.aesEncrypt(key, "123");
System.out.println("AES加密后:" + ciphertext);
// 解密
String text2 = SaSecureUtil.aesDecrypt(key, ciphertext);
System.out.println("AES解密后:" + text2);

// 2 BCrypt加密
user.setPassword(BCrypt.hashpw(user.getPassword())); 
dbUser.setPassword(BCrypt.hashpw(newPass));
if (!BCrypt.checkpw(user.getPassword(), dbUser.getPassword())) { // 明文 数据库中的加密密码
    throw new ServiceException("用户名或密码错误");
}

数据库清空,ID重置从一开始

TRUNCATE sys_user;

接口限流

接口暴露后被第三方工具等恶意请求,会造成接口的线程阻塞,新的用户就没法请求。启动项目后一定记得启动redis。

过滤器实现 MyFilter.java。软件Apifox测试是否拦截成功。注意并发问题。

package com.partner.boot.common;

import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicInteger;

/**
 *  限流操作
 *
 * @author 小熊
 * @since 2023-03-07
 */
@Component // 才能加入到spring的组件里才能生效
@Slf4j
public class MyFilter implements Filter {
    // 时间窗口

    // 1秒 之内只允许通过  2个  请求

    private static volatile long startTime = System.currentTimeMillis(); // volatile 关键字去更新内存最近的值

    private static final long windowTime = 1000L;

    private static final int door = 100;

    private static final AtomicInteger bear = new AtomicInteger(0); // 并发桶

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        int count = bear.incrementAndGet();  // 只要来了一个人,就+1 而不是5个线程来了都是0+1
        if (count == 1) {   // 并发安全的   //  3个请求计算都是2000 ms
            startTime = System.currentTimeMillis();
        }

        // 发生了请求
        long now = System.currentTimeMillis();
        log.info("拦截了请求, count: {}", count);
        //  0 -> 1  1 -> 2  2 -> 3
        log.info("时间窗口: {}ms, count: {}", (now - startTime), count);

        if (now - startTime <= windowTime) {
            if (count > door) {  // 超过了阈值
                // 就要进行限制  限流操作
                log.info("拦截了请求, count: {}", count);
                HttpServletResponse response = (HttpServletResponse) servletResponse;
                response.setStatus(HttpStatus.OK.value());
                response.setContentType(MediaType.APPLICATION_JSON_VALUE);
                response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
                response.getWriter().print(JSONUtil.toJsonStr(Result.error("402", "接口请求太频繁")));
                return;   // 超过两个请求不让访问
            }
        } else {
            // 重新进入下一个窗口
            startTime = System.currentTimeMillis();
            bear.set(1);
        }

        //  将请求放行
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        filterChain.doFilter(servletRequest, servletResponse);
        log.info("接口请求的路径:{}", request.getServletPath());
    }

    @Override
    public void destroy() {

        Filter.super.destroy();
    }

}


并发请求测试类

package com.partner.boot;

import cn.hutool.http.HttpUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

@Slf4j
public class HttpTest {

    @Test
    public void test() throws InterruptedException {

    }

    public static void main(String[] args) throws InterruptedException {
        log.info("开始执行");
        String json = "{\"username\": \"xx\", \"password\": \"1234\"}";
        int count = 5;
        CountDownLatch countDownLatch = new CountDownLatch(count);
        List<String> list = new ArrayList<>();
        // 模拟并发请求
        for (int i = 0; i < count; i++) {
            int finalI = i;
            new Thread(() -> {
                String res = HttpUtil.post("http://localhost:9090/login", json);
                list.add(LocalDateTime.now() + "第" + finalI + "次执行: " + res);
                try {
                    TimeUnit.MILLISECONDS.sleep(20);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                countDownLatch.countDown();
            }).start();
        }
        countDownLatch.await();
        list.forEach(System.out::println);
    }

}

问题:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Spring Boot是一种用于构建Java应用程序的开发框架,而Vue.js是一个用于构建用户界面的JavaScript框架。Spring Boot和Vue.js的结合使用可以实现前后端分离的项目开发,其中包括实战pdf的生成和展示功能。 首先,我们可以使用Spring Boot来搭建后端接口服务。通过使用Spring Boot的Web模块,我们可以很方便地创建RESTful API,用于接收前端请求并处理数据。在这个项目中,我们可以设计一个用于生成pdf的API,在接收到相关请求时,利用Java的pdf生成库(如iText)来生成pdf文档,并将生成的pdf保存到服务器或返回给前端。 接下来,我们需要利用Vue.js来创建前端界面,并与后端的API进行交互。我们可以使用Vue.js的组件化和路由功能,创建多个页面来展示pdf文档。对于pdf的展示,可以使用Vue.js的插件或引入第三方pdf阅读器库(如pdf.js),来实现在前端浏览器中展示pdf文档的功能。 在前端界面中,我们可以设计一个上传pdf的功能,用户可以选择本地的pdf文件进行上传,并通过调用后端的API来将pdf保存到服务器,然后在页面中展示出来。 除了生成和展示pdf文档的功能之外,我们还可以通过Spring Boot和Vue.js的结合来实现其他的功能,比如用户登录、权限控制、pdf搜索和标注等等。通过使用Spring Boot和Vue.js这样的前后端分离的开发方式,我们可以更好地实现项目的模块化、可扩展性和易维护性。 总结来说,通过结合使用Spring Boot和Vue.js,我们可以实现一个具有pdf生成和展示功能的项目。使用Spring Boot来搭建后端接口服务,并利用Java的pdf生成库来生成pdf文档;使用Vue.js来创建前端界面,并通过调用后端的API来展示和处理pdf文档。这样的开发方式可以更好地满足项目的需求,同时提高开发效率和用户体验。 ### 回答2: Spring Boot 是一个 Java 开发框架,用于简化和加速 Spring 应用程序的开发。Vue 是一个流行的 JavaScript 框架,用于构建用户界面。Spring Boot 和 Vue 可以一起使用,实现全栈开发。 PDF 是一种常见的文件格式,用于在不同平台上展示、共享和打印文档。在一个实践项目中使用 Spring Boot 3 和 Vue 3 来处理 PDF 文件是可行的。 首先,可以使用 Spring Boot 来创建一个 RESTful API,用于处理 PDF 文件的上传、下载和其他操作。Spring Boot 提供了丰富的库和功能,可以轻松处理文件上传和下载的逻辑。 然后,可以使用 Vue 3 来构建一个用户界面,用于展示 PDF 文件的列表和操作。Vue 3 提供了许多强大的工具和组件,可以方便地处理用户界面的交互和数据传输。 在实际项目中,可以使用 Spring Boot 来处理用户上传的 PDF 文件,并将其保存到服务器上的指定位置。同时,可以使用 Vue 3 构建一个界面,展示已上传的 PDF 文件列表,并提供下载和删除等功能。 为了实现这个功能,可以使用 Spring Boot 的文件上传功能来处理用户上传的 PDF 文件,并将其保存到指定的目录。然后,可以使用 Vue 3 的组件和路由来构建一个用户界面,用于展示和操作已上传的 PDF 文件。 总结来说,Spring Boot 3 和 Vue 3 可以一起使用,实现一个实战项目,用于处理 PDF 文件的上传、下载和其他操作。使用 Spring Boot 来处理文件上传和下载的逻辑,使用 Vue 3 来构建用户界面,展示和操作已上传的 PDF 文件。这样可以实现一个功能完善的 PDF 文件处理项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值