微服务(三)-配置中心、迁移注册功能、创建问答模块

1 配置中心

1.1 什么是配置中心?

  所谓配置中心就是对微服务项目的配置信息进行统一管理的工具,这样多个模块需要相同配置时(例如:数据库连接配置),就不需要每个模块都编写同样的配置了,只需要在配置中心编写一次,所有模块引用即可。配置中心减少了配置冗余,提高了配置的维护性。

  我们上次课学习的Nacos就拥有配置中心的功能,配置中心支持各种格式的配置文件:properties、yaml、txt、json、xml。

1.2 Nacos配置中心结构

  • Nacos通过namespace--->group--->dataId来定位数据

  • Nacos软件中可以定义多个namespace(命名空间),不同命名空间中的配置互不干扰,注意:Nacos有一个默认命名空间"public"不能删除。

  • 每个namespace中可以定义多个group

  • 每个group中可以定义多个DataId(类似:数据库、表、数据的关系)

1.3 将数据库配置保存到注册中心

  打开Nacos,选择“配置管理”,进入“配置列表”,其中,public就是默认存在的命名空间(namespace),不可删除和编辑。

新建命名空间:

  (1)如果需要新建命名空间,选择Nacos首页的“命名空间”,出现的public为默认存在的命名空间,不可删除和编辑,点击“新建命名空间”可以新建命名空间

  (2)对命名空间进行参数设置:命名空间ID(可不填写,默认以UUID生成)、命名空间名、描述

  (3)点击确认后,完成命名空间的创建,之后可对命名空间进行查看详情、删除、编辑等操作

回到“配置管理”的“配置列表”,可以看到刚才新建的命名空间:在上方可以进行命名空间的切换,现在处于public命名空间。

新建配置:

  (1)点击右侧“+”号,可以新建配置

  (2)指定Data ID(配置文件名称)、Group(可以不选,直接默认)、描述(可以不写)、选择对应的配置格式、编写配置内容(随配置格式调整格式,注意下面从knows-portal的application.properties文件中复制过来的配置信息需要把配置门槛下面的portal改为sys)

  (3)填写完毕后,点击"发布",选择左侧的箭头返回主页,主页上出现新的配置,之后可对新建的配置进行编辑、删除等操作

1.4 SpringBoot 加载配置文件顺序

  我们现在创建的项目都是以SpringBoot框架为基础的,SpringBoot在启动时会加载很多默认配置文件,简化程序员的操作,我们一直接触的就是application.properties可以修改配置,最近又接触了application.yml,知道了这两个配置文件中的内容都会进行加载,那么他俩的关系又是什么呢?

  运行顺序上,SpringBoot启动时先解析加载yml文件,然后是properties文件。如果配置了相同的内容,properties会覆盖yml的配置。

  实际上还有一组配置文件:

  当我们的项目添加了SpringCloud依赖时,会出现一组新的配置文件,这组配置文件会先于application.yml / properties运行,它们的名字是bootstrap.yml / properties。

  最终配置文件解析顺序如上图,在bootstrap配置运行时,application还没有运行,我们需要在bootstrap配置中配置注册中心的位置,以便于程序需要application读取时能够读取到。

1.5 添加配置读取配置中心的信息

  为了在SpringBoot启动过程中,尽早的加载配置信息,我们要在knows-sys模块的bootstrap级别将nacos中的配置信息加载过来,在application级别来读取使用。

在knows-sys模块中的resources文件夹下创建bootstrap.yml配置文件:

 server:
  port: 8002
 spring:
  application:
    name: sys-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
      config:
         # 配置中心的位置
        server-addr: localhost:8848
         # 如果不配置命名空间,默认public
         # 配置group
        group: DEFAULT_GROUP
         # 配置文件类型
        file-extension: properties
         # 到此为止,没有声明配置文件的dataId
         # 默认情况下,dataId就是 应用名称+配置文件类型
         #                     sys-service.properties

SpringBoot启动类添加@EnableDiscoveryClient、@MapperScan注解:

 @SpringBootApplication
 @EnableDiscoveryClient
 @MapperScan("cn.tedu.knows.sys.mapper")
 public class KnowsSysApplication {
     public static void main(String[] args) {
         SpringApplication.run(KnowsSysApplication.class, args);
    }
 }

现在可以启动knows-sys项目,观察注册中心是否注册成功,如果注册成功,表示现有启动的配置正常。

  (1)启动注册中心nacos,只要不报错就可以,有些INFO信息较多也没关系

  (2)启动网关gateway

  (3)启动knows-sys项目

  输入网址:http://localhost:8848/nacos,访问nacos首页,nacos第一次需要登录(用户名和密码都是nacos),之后再登录时时直接访问首页。

  如上图所示,sys-service加载成功!

 

2 迁移注册功能

  为了我们实现迁移注册功能的目标,上面做了一些准备工作。下面要想实现注册,要把knows-portal项目中相关的数据访问层、业务逻辑层和控制层的代码都复制到当前项目knows-sys中。

2.1 复制数据访问层代码

  将knows-portal项目下mapper中的ClassroomMapper、UserMapper、UserRoleMapper和vo包中的RegisterVo、UserVo复制到knows-portal项目下新建的mapper、vo包中。

最终效果如下:

在IDEA中按下图添加设置,可以使idea自动导入需要的类或接口(自动导包):

  在搜索栏中输入auto,选择Auto Import,在出现的页面选择Add unambiguous imports on the fly前面打√,OK即可,之后就会自动添加确定类的依赖了。

  我们复制Vo类到vo包没有问题,但是复制Mapper接口到mapper包发生了异常,异常原因是没有找到对应的实体类,因此需要重新添加实体类。

2.2 创建通用项目knows-commons

  像实体类一样,其实还有一些类也都是所有模块都需要的类型。我们可以将实体类和这些所有模块都需要的类型放在一个新建的项目中统一管理,需要的模块直接应用这个项目作为pom的依赖即可。

创建knows-commons:设置下面参数后,什么也不用勾选,直接创建项目

父子相认:父项目knows中添加子项目module:knows-commons

 <module>knows-commons</module>

子项目knows-commons的pom.xml:进行父子相认,去除无关依赖和配置,添加mybatis-plus依赖(单纯使用,不启动)

 <?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>knows</artifactId>
         <version>0.0.1-SNAPSHOT</version>
         <relativePath/> <!-- lookup parent from repository -->
     </parent>
     <groupId>cn.tedu</groupId>
     <artifactId>knows-commons</artifactId>
     <version>0.0.1-SNAPSHOT</version>
     <name>knows-commons</name>
     <description>Demo project for Spring Boot</description>
 
     <dependencies>
         <!--单纯使用,不启动,mybatis-plus中包含实体类所需要的相关注解-->
         <dependency>
             <groupId>com.baomidou</groupId>
             <artifactId>mybatis-plus</artifactId>
         </dependency>
     </dependencies>
 
 </project>

  这个项目无需配置其它内容,刷新maven。

将knows-portal项目的model包、exception包复制到knows-commons项目下,效果如下:

  删除knows-commons项目下的SpringBoot启动类

  删除knows-commons项目下的test测试文件夹

  我们的commons项目现在就可以使用了,使用方式:在需要的模块中添加它的依赖就可以了。

  转到knows:在pom.xml中添加对cn.tedu.knows.commons依赖的版本管理

 

  转到knows-sys模块:解除pom.xml文件中对cn.tedu.knows.commons依赖的注释

  处理mapper接口中的各种编译错误,绝大多数都是需要引入正确的包,先删除全部import导入的包,IDEA会自动加载相关类或接口,加载完毕后,对仍然报错的位置进行指定包重新导入即可。

2.3 测试导入的Mapper

  Mapper接口已经迁移到knows-sys模块中,我们要测试调用其中的方法,保证所有配置在正常运作。

在knows-sys模块的test文件夹下,添加测试代码如下:

 package cn.tedu.knows.sys;
 
 import cn.tedu.knows.commons.model.User;
 import cn.tedu.knows.sys.mapper.UserMapper;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.context.SpringBootTest;
 
 import javax.annotation.Resource;
 
 @SpringBootTest
 class KnowsSysApplicationTests {
 
     @Resource
     UserMapper userMapper;
     @Test
     void contextLoads() {
         User user = userMapper.findUserByUsername("st2");
         System.out.println(user);
    }
 
 }
 

输出结果如下:

  如果运行顺利,表示一切正常!

报错如下:每次测试均出现这个错误,可能是某些资源未关闭,但也不影响后面运行!!!

2.4 迁移控制层

在knows-sys项目下创建controller包,包中创建一个类用于测试,AuthController代码如下:

 package cn.tedu.knows.sys.controller;
 
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
 @RestController
 @RequestMapping("/v1/auth")
 public class AuthController {
     @GetMapping("/demo")//测试路径:http://localhost:8002/v1/auth/demo,输出下面测试返回内容
     public String demo(){
         return "sys:Hello World!!!";
    }
 }
 

  启动服务,输入上面控制器方法的路径,登录之后就可以显示返回的字符串了(登录时用户名:user、密码:idea控制台随机生成的密码)。(启动的服务有:nacos、gateway、knows-sys)

  登录页面:

 

  测试结果:

2.5 网关路由配置

  我们上面的测试使用的是8002端口,正式开发时需要使用网关路由,下面配置网关路由:

转到gateway模块:在application.yml文件中修改如下:

 server:
  port: 9000
 spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      discovery:
        locator:
          enabled: true  #是否与服务注册与发现组件结合,通过 serviceId 转发到具体的服务
          lower-case-service-id: true # 忽略大小写服务器名称
      routes:  # 开始路由配置
        - id: gateway-sys #路由配置的名称和具体服务无关 【此次配置的】
          uri: lb://sys-service  # 必须和微服务项目名称一致
           # 路由的路径设置
           # 表示如果访问localhost:9000/v1/xxxxxxx
           # 那么就相当于访问sys-service服务中的内容了!
          predicates:
            - Path=/v1/**  # 路由特征
        - id: gateway-resource #路由配置的名称和具体服务无关
           # resource-server路由的服务器的名称
           # lb: 是 Load Balance(负载均衡)的缩写
          uri: lb://resource-server  # 必须和微服务项目名称一致
           # 路由的路径设置
           # 表示如果访问localhost:9000/image/xxxxxxx
           # 那么就相当于访问resource-server服务中的内容了!
          predicates:
            - Path=/image/**  # 路由特征

  经过这个配置,我们现在就可以通过localhost:9000来访问knows-sys模块中的控制器方法了。具体路径如下:http://localhost:9000/v1/auth/demo如果要求登录,就继续输入用户名:user、密码:idea控制台中的密码,登录之后访问。

测试效果如下:(启动的服务有:nacos、gateway、knows-sys)

  由于每次测试还要登录太麻烦了,我们可以编写一个Spring-Security框架的放行设置:

转到knows-sys模块:创建security包,包中创建SecurityConfig类,类中编写放行全部请求的代码如下:

 package cn.tedu.knows.sys.security;
 
 import org.springframework.context.annotation.Configuration;
 import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 
 @Configuration
 @EnableGlobalMethodSecurity(prePostEnabled = true) //启动权限管理功能
 public class SecurityConfig extends WebSecurityConfigurerAdapter {
     //设置放行
     //现阶段我们的Spring-Security全部放行
     @Override
     protected void configure(HttpSecurity http) throws Exception {
         http.csrf().disable() //防跨域攻击
            .authorizeRequests()
                .anyRequest().permitAll();//任何请求,全部放行
    }
 }
 

  再次启动服务,访问资源就不需要登录了!(启动的服务有:nacos、gateway、knows-sys)

2.6 迁移业务逻辑层

  Mapper测试通过,控制器也路由配置也成功了,下面要开始迁移service,复制knows-portal项目中service包中UserServiceImpl、IUserService到knows-sys的对应路径,最终效果如下:

IUserService复制过来直接重新导包即可,UserServiceImpl类有些代码需要删除,最后保留方法如下:

 package cn.tedu.knows.sys.service.Impl;
 
 
 import cn.tedu.knows.commons.exception.ServiceException;
 import cn.tedu.knows.commons.model.Classroom;
 import cn.tedu.knows.commons.model.User;
 import cn.tedu.knows.commons.model.UserRole;
 import cn.tedu.knows.sys.mapper.ClassroomMapper;
 import cn.tedu.knows.sys.mapper.UserMapper;
 import cn.tedu.knows.sys.mapper.UserRoleMapper;
 import cn.tedu.knows.sys.service.IUserService;
 import cn.tedu.knows.sys.vo.RegisterVo;
 import cn.tedu.knows.sys.vo.UserVo;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.time.LocalDateTime;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * <p>
  * 服务实现类
  * </p>
  *
  * @author tedu.cn
  * @since 2021-08-23
  */
 @Service //作用1:保存到Spring容器中;作用2:语义化,代表业务逻辑层
 public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
     //Ctrl+Alt+B 可以从接口直接跳转到它的实现类(唯一实现类)
     //注册用户必须使用userMapper进行最后的新增,获得user信息
     @Autowired
     UserMapper userMapper;
 
     //要验证邀请码是否正确,需要利用ClassroomMapper进行查询班级邀请码
     @Autowired
     private ClassroomMapper classroomMapper;
 
     //新增学生之后,还要新增学生和role表的关系,要使用UserRoleMapper
     @Autowired
     private UserRoleMapper userRoleMapper;
 
     @Override //重写接口中的方法
     @Transactional //开启事务管理
     public void registerStudent(RegisterVo registerVo) {
         //1.根据邀请码查询班级信息
         QueryWrapper<Classroom> query = new QueryWrapper<>();
         query.eq("invite_code",registerVo.getInviteCode());
         Classroom classroom = classroomMapper.selectOne(query);//一个邀请码对应一个班级,返回一个对象
         //2.如果班级不存在,抛出异常
         if(classroom==null){
             //抛出异常
             throw new ServiceException("邀请码错误!");//throw有return的作用,下面代码不再执行
        }
         //3.根据用户输入的手机号查询用户信息
         User user = userMapper.findUserByUsername(registerVo.getPhone());//手机号是用户名,利用手机号进行注册
         //4.如果用户存在,抛出异常
         if(user!=null){
             throw new ServiceException("手机号已经被注册!");
        }
         //5.利用bcrypt加密算法对密码进行加密
         PasswordEncoder encoder = new BCryptPasswordEncoder();
         String pwd = "{bcrypt}" + encoder.encode(registerVo.getPassword());//利用bcrypt算法对注册密码进行加密
         //验证密码在前端页面已经处理
         //6.实例化User对象,进行相关属性的赋值
         User u = new User();
         u.setUsername(registerVo.getPhone());//注意:是用phone进行注册的,phone就是用户名
         u.setPassword(pwd);//用加密后的密码作为用户密码
         u.setNickname(registerVo.getNickname());
         u.setClassroomId(classroom.getId());//从班级中获得班级id
         u.setCreatetime(LocalDateTime.now());//获取当前时间:年月日时分秒
         u.setEnabled(1);//可用
         u.setLocked(0);//不锁定
         u.setType(0);//学生类型为0
         //7.将用户新增到数据库
         //返回受影响的行数:增删改 执行后,主键id自增,自动赋值到user表的id中,因此不用赋值user的id属性
         int num = userMapper.insert(u);
         //num是增删改操作对数据库产生影响的行数,新增一般是1,如果返回0表示新增失败
         if(num==0){//或者num!=1
             throw new ServiceException("数据库异常");
        }
         //8.新增用户和角色的关系
         UserRole userRole = new UserRole();
         //UserRole有两个属性:user_id、role_id
         userRole.setUserId(u.getId());//注意:此处是u而非user
         userRole.setRoleId(2);//学生角色为2
         num = userRoleMapper.insert(userRole);
         //num是增删改操作对数据库产生影响的行数,新增一般是1,如果返回0表示新增失败
         if(num==0){
             throw new ServiceException("数据库异常");
        }
    }
 
     //创建缓存对象
     private List<User> teachers = new CopyOnWriteArrayList<>();//线程安全,存对象
     private Map<String,User> teacherMap = new ConcurrentHashMap<>();//线程安全,存名称和对象
 
     //查询所有讲师
     @Override
     public List<User> getTeachers() {
         if(teachers.isEmpty()){
             synchronized (teachers){
                 if(teachers.isEmpty()){
                     QueryWrapper<User> query = new QueryWrapper<>(); 
                   query.eq("type",1);//学生类型为0,老师类型为1 
                   List<User> list = userMapper.selectList(query); 
                   teachers.addAll(list);//将查询结果存到线程安全的集合中 
                   for (User u:list) {//遍历list,存储Map 
                       teacherMap.put(u.getNickname(),u);//注意:选老师选昵称 
                  } 
              } 
          } 
      } 
       return teachers; 
  } 
​ 
   //查询所有讲师的Map 
   @Override 
   public Map<String, User> getTeacherMap() { 
       if(teacherMap.isEmpty()){//保证效率 
           getTeachers();//通过添加list进而给map赋值 
      } 
       return teacherMap; 
  } 
​ 
   //根据用户名查询用户信息面板 
   @Override 
   public UserVo getUserVo(String username) { 
       // 根据用户名获得UserVo对象 
       UserVo userVo=userMapper.findUserVoByUsername(username); 
       // 查询当前登录用户的问题数 
       // 为userVo对象的问题数属性赋值 
       // 查询当前登录用户的收藏数 
       // 为userVo对象的收藏数属性赋值 
       return userVo; 
  } 

2.7 迁移控制层

  复制knows-portal项目下controller包下的ExceptionControllerAdvice、UserController到knows-sys项目下的controller包中,最终效果如下:

  复制完毕后,我们只需要从新导包就可以解决所有编译错误。

我们要实现注册功能,需要从之前knows-portal项目的SystemController控制器中复制注册代码到UserController中:

 package cn.tedu.knows.sys.controller;
 
 
 import cn.tedu.knows.commons.exception.ServiceException;
 import cn.tedu.knows.commons.model.User;
 import cn.tedu.knows.sys.service.IUserService;
 import cn.tedu.knows.sys.vo.RegisterVo;
 import cn.tedu.knows.sys.vo.UserVo;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.core.annotation.AuthenticationPrincipal;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.validation.BindingResult;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.util.List;
 
 /**
  * <p>
  * 前端控制器
  * </p>
  *
  * @author tedu.cn
  * @since 2021-08-23
  */
 @RestController
 //为今后微服务项目方便起见,根据微服务标准,
 //将@RequestMapping("/portal/user")改为@RequestMapping("/v1/users")
 @RequestMapping("/v1/users")
 @Slf4j
 public class UserController {
     //控制层调用业务逻辑层
     @Autowired
     private IUserService userService;
 
     //查询所有讲师的控制层方法
     @GetMapping("/master")
     public List<User> master(){
         List<User> list = userService.getTeachers();
         return list;
    }
 
     //根据当前登录用户查询用户的面板信息
     //同步请求路径为 localhost:8080/v1/users/me 5
     @GetMapping("/me")
     public UserVo me(@AuthenticationPrincipal UserDetails user){
         UserVo userVo = userService.getUserVo(user.getUsername());
         return userVo;
    }
 
     //注册方法,是post请求
     @PostMapping("/register")//http://localhost:9000/v1/users/register
     public String registerStudent(
             //@Validated表示激活后面类型的Spring Validation验证
             //在需要验证的方法参数后添加指定类型(BindingResult)的参数
             //验证结果会自动赋值到这个参数中
             @Validated RegisterVo registerVo,
             BindingResult result){
         log.debug("收到注册信息:{}",registerVo);
         // 判断验证有没有错误
         if(result.hasErrors()){
             //获得错误信息
             String msg=result.getFieldError().getDefaultMessage();
             //当有验证不通过时,直接返回错误信息到axios
             return msg;
        }
         //Ctrl+Alt+T可以快速添加try-catch结构
         try {
             //调用业务逻辑层方法实现注册功能
             userService.registerStudent(registerVo);
             return "ok";
        } catch (ServiceException e) {
             //e.printStackTrace()是将错误信息输出到控制台
             //可以保留,但是也要知道,控制台的错误信息是我们主动输出的
             e.printStackTrace();
             return e.getMessage();
        }
    }
 }

  这样knows-sys模块UserController中注册方法的Url为:localhost:9000/v1/users/register,我们要启动前端项目向这个路径发送请求,实现注册。

转到knows-client项目:在register.js文件中修改axios的请求路径,然后启动knows-client服务执行注册。

 let app = new Vue({
     el:'#app',
     data:{
         inviteCode:'',
         phone:'',
         nickname:'',
         password:'',
         confirm:'',
         message: '',
         hasError: false
    },
     methods:{
         register:function () {
             console.log('Submit');/*浏览器控制台输出*/
             let form=new FormData();/*实例化表单对象*/
             form.append("inviteCode",this.inviteCode);/*添加值:参数1:register.js name属性*/
             form.append("phone",this.phone);
             form.append("nickname",this.nickname);
             form.append("password",this.password);
             form.append("confirm",this.confirm);
             console.log(form);
             if(this.password != this.confirm){
                 this.message = "确认密码不一致!";
                 this.hasError = true;
                 return;
            }
             axios({
                 method:'post',
                 url:'http://localhost:9000/v1/users/register',
                 data:form
            })
                .then(function(r) {/*r与response一样,写什么都可以*/
                     console.log("|"+r.status+"|"+OK+"|");
                     /*与SystemController中实现注册后返回的“ok”对应*/
                     if(r.data == "ok"){/*r.status改为r.data,状态200表示ok*/
                         console.log("注册成功");
                         console.log(r.data);
                         app.hasError = false;
                         location.href = '/login.html?register';
                    }else{/*状态不是200,提示错误信息*/
                         console.log(r.data);
                         app.hasError = true;
                         app.message = r.data;
                    }
                });
        }
    }
 });

  访问http://localhost:8080/register.html注册页(自己的为8888,8080被占用,需要在knows-client项目的application.properties中配置端口)

  填写注册信息后没有反应,但数据库新增了刚才注册的数据(不确定),F12后查看控制台输出的错误信息:

  当我们注册时会发生上面的错误信息,这个信息表示跨域请求失败,是一定会发生的异常。

2.8 什么是跨域?

  首先我们要先了解一个概念---同源策略。

  同源策略:它是由Netscape提出的一个著名的安全策略,现在所有支持JavaScript 的浏览器都会使用这个策略。所谓同源是指域名,协议,端口相同目的是出于安全考虑,防止js脚本随意调用其他网站的资源。

  所谓同源,我们做注册时当前网站源为:http://localhost:8080

       我们要访问的注册功能路径:http://localhost:9000

  当违反同源策略时,请求可以发送到控制器,但是客户端不能接收到控制器返回的响应,因为接收不到响应,所以保护了客户端安全。要想在非同源的情况下实现正常的请求和响应,就需要使用"跨域"。而跨域的设置可以是多方的:

  1. 前端代码可以实现跨域

  1. 后端代码也可以实现跨域

我们学习使用后端代码实现跨域,java方面实现跨域也有很多办法:

  • 过滤器

  • 拦截器

  • SpringMvc配置:接下来通过SpringMvc配置实现跨域设置

  • 控制器跨域注解等

在knows-sys模块security包中创建一个类WebConfig,代码如下:

 package cn.tedu.knows.sys.security;
 
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.servlet.config.annotation.CorsRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
 //使用SpringMvc框架功能配置跨域
 @Configuration
 public class WebConfig implements WebMvcConfigurer {//实现WebMvcConfigurer接口
 
     @Override
     public void addCorsMappings(CorsRegistry registry) {
         registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("*")
                .allowedHeaders("*");//*、**均表示所有
    }
 }
 

  重新启动knows-sys项目,再次访问http://localhost:8080/register.html注册页,可以注册成功,同时页面表单数据也会对注册信息进行验证。

  注册成功后,响应登录页面:

  数据库新增数据:

 

 

3 创建问答模块

  我们下一个目标是在首页显示所有标签,这样我们就需要创建问答模块,因为标签输入问题相关内容。创建项目knows-faq,这个模块负责首页问题列表、标签列表、提问、回答、评论等业务。

在父项目knows上右键新建module,进行如下参数设置,其它什么也不选,完成knows-faq项目的创建:

父子相认:在父项目knows的pom.xml上添加子项目module

 <module>knows-faq</module>

子项目knows-faq的pom.xml:删除无关配置、添加依赖(复制knows-sys的依赖,额外添加pagehelper分页依赖,因为问题列表需要分页)

 <?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>knows</artifactId>
         <version>0.0.1-SNAPSHOT</version>
         <relativePath/> <!-- lookup parent from repository -->
     </parent>
     <groupId>cn.tedu</groupId>
     <artifactId>knows-faq</artifactId>
     <version>0.0.1-SNAPSHOT</version>
     <name>knows-faq</name>
     <description>Demo project for Spring Boot</description>
 
     <dependencies>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
         <dependency>
             <groupId>com.alibaba.cloud</groupId>
             <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
         </dependency>
         <dependency>
             <groupId>cn.tedu</groupId>
             <artifactId>knows-commons</artifactId>
         </dependency>
         <dependency>
             <groupId>com.baomidou</groupId>
             <artifactId>mybatis-plus-boot-starter</artifactId>
         </dependency>
         <dependency>
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-java</artifactId>
         </dependency>
         <!-- nacos配置中心的依赖 -->
         <dependency>
             <groupId>com.alibaba.cloud</groupId>
             <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
         </dependency>
         <!-- 验证框架 -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-validation</artifactId>
         </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-security</artifactId>
         </dependency>
         <!-- faq模块需要进行分页查询 -->
         <dependency>
             <groupId>com.github.pagehelper</groupId>
             <artifactId>pagehelper-spring-boot-starter</artifactId>
         </dependency>
     </dependencies>
 
 </project>

在application.properties添加配置信息:

 # 问答模块访问端口
 server.port=8001
 
 # 微服务项目名称
 spring.application.name=faq-service
 
 # 服务地址
 spring.cloud.nacos.discovery.server-addr=localhost:8848
 
 # 连接数据库
 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
 spring.datasource.url=jdbc:mysql://localhost:3306/knows?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
 spring.datasource.username=root
 spring.datasource.password=root
 
 # 设置日志输出级别
 logging.level.cn.tedu.knows.faq=debug

SpringBoot启动类:

 @SpringBootApplication
 @EnableDiscoveryClient //注册到nacos
 @MapperScan("cn.tedu.knows.faq.mapper") //扫描包
 public class KnowsFaqApplication {
     public static void main(String[] args) {
         SpringApplication.run(KnowsFaqApplication.class, args);
    }
 }

  启动knows-faq项目,在这之前先打开nacos、gateway,观察knows-faq是否成功注册,输出效果如下:

  由上面结果可知,faq-service注册成功!

3.1 迁移数据访问层

  将knows-portal项目中mapper下面的QuestionMapper、QuestionTagMapper、TagMapper、UserQuestionMapper复制到knows-faq项目项目下新建的mapper文件夹下,最终效果如下:

  每个接口重新导入需要的类即可解决编译错误,推荐测试一下,测试mapper代码如下:

 @Autowired
 TagMapper tagMapper;
 @Test
 void contextLoads() {
     List<Tag> tags=tagMapper.selectList(null);
     for(Tag t: tags){
         System.out.println(t);
    }
 }

输出结果:

 Tag(id=1, name=Java基础, createby=admin, createtime=2021-03-09T14:39:48)
 Tag(id=2, name=Java OOP, createby=admin, createtime=2021-03-09T23:30:09)
 Tag(id=3, name=Java SE, createby=admin, createtime=2021-03-09T23:32:13)
 Tag(id=4, name=WebServer, createby=admin, createtime=2021-03-09T23:32:50)
 Tag(id=5, name=二进制, createby=admin, createtime=2021-03-09T23:33:18)
 Tag(id=6, name=Web, createby=admin, createtime=2021-03-09T23:33:58)
 Tag(id=7, name=MySQL, createby=admin, createtime=2021-03-09T23:34:20)
 Tag(id=8, name=Servlet, createby=admin, createtime=2021-03-09T23:34:40)
 Tag(id=9, name=Spring, createby=admin, createtime=2021-03-09T23:34:58)
 Tag(id=10, name=SpringMVC, createby=admin, createtime=2021-03-09T23:35:17)
 Tag(id=11, name=MyBatis, createby=admin, createtime=2021-03-09T23:35:38)
 Tag(id=12, name=Ajax, createby=admin, createtime=2021-03-09T23:36:02)
 Tag(id=13, name=SpringBoot, createby=admin, createtime=2021-03-09T23:36:22)
 Tag(id=14, name=SpringCloud, createby=admin, createtime=2021-03-09T23:36:43)
 Tag(id=15, name=面试题, createby=admin, createtime=2021-03-09T23:37:28)
 Tag(id=16, name=搜索引擎, createby=admin, createtime=2021-03-09T23:40:47)
 Tag(id=17, name=Docker, createby=admin, createtime=2021-03-10T17:19:05)
 Tag(id=18, name=Linux, createby=admin, createtime=2021-03-16T14:44:04)
 Tag(id=19, name=CentOS, createby=admin, createtime=2021-03-16T14:44:22)
 Tag(id=20, name=Dubbo, createby=admin, createtime=2021-03-19T09:52:09)
 Tag(id=21, name=微服务, createby=jerry, createtime=2021-08-28T15:15:15)

3.2 迁移业务逻辑层

  我们先解决显示所有标签的业务,所以只迁移tag,将knows-portal项目中service包下的ITagService、TagServiceImpl复制到knows-faq项目对应路径下,最终效果如下:

3.3 迁移控制层

  将knows-portal项目中controller包下的TagController复制到knows-faq项目对应路径下,最终效果如下:

  控制器类上原来的@RequestMapping("/v1/tags")更改为("/v2/tags"):

 @RestController
 //@RequestMapping("/portal/tag")
 //@RequestMapping("/v1/tags") //满足微服务的要求/标准
 @RequestMapping("/v2/tags") //满足微服务的要求/标准,v2代表问答模块
 public class TagController {
     @Autowired
     private ITagService tagService;
 
     //查询所有标签的控制方法
     //@GetMapping("")意思是不添加任何额外路径
     //访问这个控制层方法的路径就是http://localhost:8001/v2/tags
     @GetMapping("")
     public List<Tag> tags(){
         List<Tag> list = tagService.getTags();
         return list;
    }
 }
 

  因为也添加了Spring-Security的依赖,也要设置放行。因为客户端也是跨域访问,需要添加跨域的设置,直接从knows-sys模块复制即可,复制knows-sys项目下的security包到knows-faq对应路径下,最终效果如下:

  复制过来直接使用,不报错。

  启动knows-faq服务,发一个同步请求测试业务是否可用,访问路径为http://localhost:8001/v2/tags,输出效果如下:

3.4 配置网关路由

  转到gateway项目:添加faq模块的路由配置,application.yml文件中添加:

 - id: gateway-faq #路由配置的名称和具体服务无关
  uri: lb://faq-service  # 必须和微服务项目名称faq-service一致
   # 路由的路径设置
   # 表示如果访问localhost:9000/v2/xxxxxxx
   # 那么就相当于访问faq-service服务中的内容了!
  predicates:
    - Path=/v2/**  # 路由特征

  重新启动网关后,访问路径http://localhost:9000/v2/tags测试,输出效果如下:

3.5 client请求修改

  转到knows-client项目:找到tags_nav.js文件,把文件中的axios请求路径修改为

 axios({
     url:'http://localhost:9000/v2/tags',
     method:'GET'
 })

  重新启动knows-client服务,输入路径:localhost:8080/index_student.html,就能显示所有标签了,测试效果如下:

 

posted @ 2021-09-09 00:25  Coder_Cui  阅读(136)  评论(0编辑  收藏  举报