超详细!利用SpringBoot+SpringCloud做一个问答项目(八)

目录

一、使用网关验证登录并授权【续】

7.使用Spring Security实现登录

二、完善注册功能

三、使用自定义的登录页面

四、标签列表---持久层

五、标签列表---业务层

六、关于Redis

附(一):关于Slf4j

附(二):相关软件

附(三):关于序列化

一、使用网关验证登录并授权【续】


7.使用Spring Security实现登录

在使用Spring Security实现登录验证时,如果需要使用 UserDetailsService ,必须自定义一个 WebSecurityConfigurerAdapter 的子类实现配置,则在 cn.tedu.straw.gateway.security 包中创 建 WebSecurityConfigurer 类进行配置:

然后,还需要在 StrawGatewayApplication 启动类上添加 @EnableWebSecurity 注解:

完成后,启动当前项目,通过 http://localhost/login 即可测试登录,正确的用户名和密码就是当前数据 表中的用户名和密码!

二、完善注册功能


由于注册的具体数据处理都是在 straw-api-user 项目中实现的(控制器层、业务层、持久层的功能,全 部在该功能中),该项目是添加了Spring Security的依赖,并且没有作过任何与Spring Security相关的配置(此前在练习时写过配置类,但是已经去除注解,将不会生效),在默认情况下,所有的访问都是 要求登录的,如果没有登录,则不允许访问该服务器,虽然可能已经通过网关实现了登录,但是,它会认为是在 straw-gateway 的服务器登录的,与 straw-api-user 的服务器无关,所以,如果尝试注册,将失败!

为了实现“通过 straw-gateway 的项目能够提交请求到 straw-api-user 的项目的控制器”,需要在 straw-api-user 项目中关闭跨域攻击(否则会导致401错误),并且允许所有访问(无论是注册,还是 后续的任何功能,都直接允许,因为是否允许访问应该在网关进行控制,只要能够通过网关,后续的服 务器是不需要验证登录或其它身份的)。

由于“注册”时将提交异步请求,所以,先在 straw-gateway 项目中关闭跨域攻击,并且,如果仅关闭跨域攻击,会导致任何请求都不需要登录,则还需要设置验证请求并使用登录表单,在 straw-gateway 的 WebSecurityConfigurer 中添加配置:

然后在 straw-api-user 的 cn.tedu.straw.api.user.security 包中创建 WebSecurityConfigurer 类进行配置:

并确保在 StrawApiUserApplication 类的声明之前存在 @EnableWebSecurity 注解:

完成后,先重启 straw-api-user 项目,再重启 straw-gateway 项目,打开浏览器,先通过 http://local host 或 http://localhost/login 登录,然后,再通过 http://localhost/register.html 即可实现注册! 目前,在执行“注册”之前还需要先“登录”是不合理的,应该将这些页面设置为不需要登录即可访问!所有 的访问控制可在 straw-gateway 进行配置,所以,在 straw-gateway 的 WebSecurityConfigurer 中补 充:

完成后,重启 straw-gateway 项目,推荐清除浏览器缓存,通过 http://localhost/register.html 即可打 开注册页面,并且可以成功使用注册功能。

三、使用自定义的登录页面


Spring Security是内置登录页面的,也可以自行指定登录页面,在 WebSecurityConfigurerAdapter 的 子类中进行配置时,可以在“启用登录页面”之后,调用相关的方法进行设置即可!

关于Spring Security应用的登录页面,必须:

  • 必须使用 <form action="/login" method="post"> 表单配置;
  • 用户名和密码必须使用 username 和 password 作为请求参数名称。

四、标签列表---持久层


如果要查询所有的标签的数据,得到标签列表,需要执行的SQL语句大致是:

SELECT id, name FROM tag ORDER BY id;

 接下来,创建新的子模块项目 straw-api-question ,将使用该功能来开发:

其实,直接在原本的 straw-api-user 或其它项目中开发该功能是完全可以的,为了更加真实的模 拟实际的分布式开发,所以,将“问题”相关的功能使用专门的项目来开发,以暴露更多开发分布式 项目可能出现的问题,并解决相关问题。

当项目创建出来后,调整 pom.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>cn.tedu</groupId>
        <artifactId>straw</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <groupId>cn.tedu</groupId>
    <artifactId>straw-api-question</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>straw-api-question</name>

    <dependencies>
        <!-- Straw Commons -->
        <dependency>
            <groupId>cn.tedu</groupId>
            <artifactId>straw-commons</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-security</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- Lombok:通过注解简化开发 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
        <!-- druid:alibaba的数据库连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>
        <!-- Mybatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <!-- MySQL -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- web:允许项目启动在Tomcat -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 单元测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

</project>

然后,在 application.properties 中添加配置:

# 显式的配置当前项目部署到服务器时运行在哪个端口号
server.port=8081

# 应用程序名称
spring.application.name=api-question
        
# 连接数据库的配置信息
spring.datasource.url=jdbc:mysql://localhost:3306/straw?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.druid.initial-size=2
spring.datasource.druid.max-active=100
spring.datasource.druid.min-idle=2

# 配置SQL语句的XML文件的位置
mybatis.mapper-locations=classpath:mapper/*.xml

先在straw-commons中创建封装本次查询结果的VO类,在cn.tedu.straw.commons.vo包中创建TagVO类,并在这个类中声明idname这2个属性:

package cn.tedu.straw.commons.vo;

import lombok.Data;
import lombok.experimental.Accessors;

import java.io.Serializable;

@Data
@Accessors(chain = true)
public class TagVO implements Serializable {

    private Integer id;
    private String name;

}

straw-api-questioncn.tedu.straw.api.question.mapper创建TagMapper持久层接口文件,在接口中声明“查询标签列表”的抽象方法:

package cn.tedu.straw.api.question.mapper;

import cn.tedu.straw.commons.vo.TagVO;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface TagMapper {

    /**
     * 查询所有的标签数据
     *
     * @return 所有标签的集合
     */
    List<TagVO> findAll();

}

然后,在启动类的声明之前添加@MapperScan注解,配置接口文件所在的包:

然后,在resources下创建mapper文件夹,并在这个文件夹中创建(或复制粘贴得到)TagMapper.xml文件,配置以上接口中的抽象方法映射的SQL语句:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.tedu.straw.api.question.mapper.TagMapper">

    <select id="findAll" resultType="cn.tedu.straw.commons.vo.TagVO">
        SELECT id, name FROM tag ORDER BY id
    </select>

</mapper>

完成后,先在application.properties中设置日志的显示级别:

# 日志的显示级别
logging.level.cn.tedu.straw=trace

test文件夹(不存在则创建)下的cn.tedu.straw.api.question.mapper包中创建TagMapperTests测试类,测试以上方法:

package cn.tedu.straw.api.question.mapper;

import cn.tedu.straw.commons.vo.TagVO;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
@Slf4j
public class TagMapperTests {

    @Autowired
    TagMapper mapper;

    @Test
    void findAll() {
        List<TagVO> tags = mapper.findAll();
        log.debug("标签数量:" + tags.size());
        for (TagVO tag : tags) {
            log.debug(">>> " + tag);
        }
    }

}

五、标签列表---业务层


cn.tedu.straw.api.question.service包下创建ITagService接口,并在接口中定义业务的抽象方法:

package cn.tedu.straw.api.question.service;

import cn.tedu.straw.commons.vo.TagVO;

import java.util.List;

public interface ITagService {

    /**
     * 查询所有的标签数据
     *
     * @return 所有标签的集合
     */
    List<TagVO> getTagList();

}

然后,在cn.tedu.straw.api.question.service.impl包下创建TagServiceImpl类,在类的声明之前添加@Service注解,实现ITagService 接口,重写业务方法:

package cn.tedu.straw.api.question.service.impl;

import cn.tedu.straw.api.question.mapper.TagMapper;
import cn.tedu.straw.api.question.service.ITagService;
import cn.tedu.straw.commons.vo.TagVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class TagServiceImpl implements ITagService {
    
    @Autowired
    TagMapper tagMapper;
    
    @Override
    public List<TagVO> getTagList() {
        return tagMapper.findAll();
    }
    
}

完成后,在testcn.tedu.straw.api.question.service包下创建TagServiceTests测试类,编写并执行单元测试:

package cn.tedu.straw.api.question.service;

import cn.tedu.straw.commons.vo.TagVO;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
@Slf4j
public class TagServiceTests {

    @Autowired
    ITagService service;

    @Test
    void getTagList() {
        List<TagVO> tags = service.getTagList();
        log.debug("标签数量:{}", tags.size());
        for (TagVO tag : tags) {
            log.debug(">>> {}", tag);
        }
    }

}

六、关于Redis


Redis是一个基于内存缓存数据的NoSQL服务器。

把数据在Redis服务器中也存储一份,当需要查询数据时,不再从MySQL服务器查询,而是从Redis服务器查询,可以非常有效的提高查询效率!

注意:缓存的数据可能是不准确的,所以,一般,只有不要求十分精准,并且访问频率高的数据,才会放在缓存中!

在安装Redis时,建议勾选:

并保持勾选“在防火墙中添加例外”:

安装完成后,重启整个IDEA软件,在IDEA的终端中,输入 redis-cli (表示redis client)即可登录 Redis控制台,并且,可以输入 ping 指定以测试Redis服务是否处于正常运行状态,将得到 PONG 反馈:

关于Redis的常用指令可参考:https://www.cnblogs.com/cxxjohnson/p/9072383.html

附(一):关于Slf4j


Slf4j是项目中用于在控制台输入日志的,当项目中添加了Lombok依赖后,在任何类的声明之前添加@Slf4j,就在可以当前类的任意位置使用log属性调用相关方法来输出日志(该属性不需要声明,Lombok会自动像添加SET/GET方法一样,在编译时介入,并声明该属性),例如:

package cn.tedu.straw.api.question.mapper;

import cn.tedu.straw.commons.vo.TagVO;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
@Slf4j
public class TagMapperTests {

    @Autowired
    TagMapper mapper;

    @Test
    void findAll() {
        List<TagVO> tags = mapper.findAll();
        log.debug("标签数量:" + tags.size());
        for (TagVO tag : tags) {
            log.debug(">>> " + tag);
        }
    }

}

在Slf4j中,日志区分为几个级别,从轻微到严重分别是:

  • trace:跟踪信息;
  • debug:调试;
  • info:一般信息;
  • warn:警告;
  • error:错误;
  • fatal:崩溃。

默认情况下,项目中的日志级别是info,则较低级别tracedebug的日志不会被显示在控制台!而较高级别的默认是显示的,也就是infowarnerror级别的日志将被显示!

将日志划分级别的好处在于可以随时输出各种信息,包括涉及项目的运行流程、关键数据的信息,这些信息可能只在开发阶段用于调试或测试功能时需要观察,在项目开发完成之后这些信息就不应该被显示出来了!如果使用System.out.println()输出语句来显示这些信息的话,无论何时这些信息都将被显示!但是,使用Slf4j的话,可以将开发阶段的日志通过debug级别进行输出,在开发完成后,设置为“仅显示info或更高级别”即可。

在项目的application.properties中,可以通过logging.level来配置日志的显示级别,例如:

# 日志的显示级别
logging.level.cn.tedu.straw=trace

以上代码就表示将cn.tedu.straw这个包及其子孙包中所有类输出的日志的显示级别都设置为trace,则trace级别及比它更高的级别的日志都将被输出!

如果只要某个类需要配置日志的显示级别,可以将以上配置的属性精确到该类:

# 日志的显示级别
logging.level.cn.tedu.straw.api.question.mapper.TagMapper=trace

另外,warn或更高级别都意味着程序中有明显的问题需要关注,所以,是默认始终显示的!

附(二):相关软件


Redis、Elasticsearch、Kafka

以下链接是我的百度云盘,可以自行下载

链接: https://pan.baidu.com/s/1R_5YFXns-IzmrsiYjrW89Q

提取码: kw9p

附(三):关于序列化


在Java中,如果创建的某个类就只是在其中声明一些属性,并定义对应的SET/GET方法,或继续添加hashCode()equals()toString()方法,这样的类都应该实现Serializable接口。

序列化的根本原因在于CPU的单次处理数据的能力非常低下!目前,主流的CPU是64位的CPU,这里的“64位”表示CPU的“步长”,是CPU运行一次可以处理的二进制数的长度,也就是说,64位的CPU每运行一次,可以处理64个二进制位,也就是8个字节,可以换算成2个int值,或1个long值,或4个char值……由此可见,CPU的单次处理数据的能力是非常弱的,之所以现在的CPU可以表现出很强的性能,是CPU的工作频繁很高,例如2.4G完整的表示应该是2.4GHz,也就是说,虽然一次处理不了多少数据,但是,运算频率非常高,所以,整体性能很强!

如果数据需要传输或克隆,由于CPU极有可能无法一次性完成处理,就需要将这些数据进行分批处理,在分批传输或克隆时,就需要关注传输的顺序,例如某个User类型的数据中包含String usernameString passwordint age这几个属性及值,在传输或克隆时,到底是先处理String username还是int age就是个问题!

其实,无论是先处理String username 还是先处理int age都是可行的,只要传输或克隆的起始位置和接收位置保存一致即可!也就是说:发送方先发出String username,接收方收到数据后将这部分数据视为String username属性的值即可,不要出现发出的是String username却被作为String password的值来处理就可以了!

所以,具体的先后顺序并不重要,但是,必须存在这个顺序的约定,以保证发送与接收是一致的(毕竟计算机中没有“自动识别”相关的机制)!这个约定可以理解为是一套“序列化的方案”,一旦制定了该标签,发送方和接收方都按这个标准来执行,就可以保证传输或克隆的数据不会出错,不会出现发出String username却把值放到String password中这种张冠李戴的问题!

由于一个项目中可能存在多种这样的类型,而每一种类型中,无论多少个对象,序列化的方案可以使用同一个!所以,应该为每一套序列化方案给出一个唯一标识,以便于Java直接使用!

所以,在Java中,实现Serializable接口,起到的作用就是作一个“标识”,明确的表示这个类是“可以被序列化的”,而自动生成的序列化版本uid就是这种类序列化方案的唯一标识!

在Java中,关于序列化的处理,除了应该实现Serializable接口并生成序列化版本uid(当类的成员发生变化后应该重新生成该uid)以外,是不需要手动处理的,全部由Java自动完成!(当然,这一点也不一定是优点,在一些硬件性能非常弱的智能设备中,可能会设计其它的手动序列化机制)。

 

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值