微服务[学成在线] day17:基于Zuul网关实现路由转发、过滤器

医学图像跨模态转换:配准伪配对与扩散模型训练流程 医学图像合成与跨模态转换常被视为条件生成问题,其核心在于学习不同成像模态间的非线性映射。由于CT、MRI等模态在灰度、分辨率和采集几何上差异显著,严格配对数据稀缺,直接训练生成模型容易造成解剖结构漂移。借助图像配准、互信息或MIND相似度度量与级联形变,可构建伪配对样本,为条件扩散模型提供空间对应约束;潜在扩散模型进一步在低维空间去噪,提高细节保真度并降低计算成本。该思路适用于单模态数据集补全、T1/T2合成、CT-MRI转换及下游分割增强等场景。围绕“配准造伪配对+扩散条件生成+成对训练组织”的流水线,正 阅读详情

😎 知识点概览

为了方便后续回顾该项目时能够清晰的知道本章节讲了哪些内容,并且能够从该章节的笔记中得到一些帮助,所以在完成本章节的学习后在此对本章节所涉及到的知识点进行总结概述。

本章节为【学成在线】项目的 day17 的内容

  • 构建用户中心服务,并基于 Spring Security Oauth2 以及 jwt 令牌实现用户认证的完整流程。
  • 完成门户网站的用户登入、登出接口、前端页面的开发以及调试。
  • 基于 Zuul 构建网关服务,以及使用 Zuul 网关实现基本的路由转发、过滤器、身份校验等功能。

目录

内容会比较多,小伙伴们可以根据目录进行按需查阅。

一、用户认证

0x01 用户认证流程分析

用户认证流程如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QeiDrDHT-1595567384585)(https://qnoss.codeyee.com/20200704_MTc=/image1.png)]

业务流程说明如下:

1、客户端请求认证服务进行认证。

2、认证服务认证通过向浏览器 cookie 写入 token (身份令牌)

认证服务请求用户中心查询用户信息。

认证服务请求 Spring Security 申请令牌。

认证服务将 token (身份令牌)和 jwt 令牌存储至 redis 中。

认证服务向cookie写入 token (身份令牌)。

3**、前端携带token请求认证服务获取**jwt令牌

前端获取到 jwt 令牌并存储在 sessionStorage。

前端从jwt令牌中解析中用户信息并显示在页面。

前端如何解析?还是认证服务返回明文数据

4**、前端携带cookie中的token身份令牌及jwt令牌访问资源服务**

前端请求资源服务需要携带两个token,一个是cookie中的身份令牌,一个是http header中的jwt令牌

前端请求资源服务前在http header上添加jwt请求资源

5、网关校验 token的合法性

用户请求必须携带 token 身份令牌和jwt令牌

网关校验redis中 token 是否合法,已过期则要求用户重新登录

6、资源服务校验jwt的合法性并完成授权

资源服务校验jwt令牌,完成授权,拥有权限的方法正常执行,没有权限的方法将拒绝访问。

0x02 认证服务查询数据库

需求分析

  • 认证服务根据数据库中的用户信息去校验用户的身份,即校验账号和密码是否匹配。

  • 认证服务不直接连接数据库,而是通过用户中心服务去查询用户中心数据库。

image-20200527084040941

完整的流程图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fQ7m6VrN-1595567384594)(https://qnoss.codeyee.com/20200704_MTc=/image3.png)]

搭建环境

1、创建用户中心数据库

用户中心负责用户管理,包括:用户信息管理、角色管理、权限管理等。

创建 xc_user 数据库(MySQL)

导入 xc_user.sql (已导入不用重复导入)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-017D2qLJ-1595567384600)(https://qnoss.codeyee.com/20200704_MTc=/image4.png)]

2、创建用户中心工程

导入“资料”-》xc-service-ucenter.zip

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F5bvUFkv-1595567384604)(https://qnoss.codeyee.com/20200704_MTc=/image5.png)]

完成用户中心根据账号查询用户信息接口功能。

查询用户接口开发

1、Api接口

用户中心对外提供如下接口

1)响应数据类型

此接口将来被用来查询用户信息及用户权限信息,所以这里定义扩展类型

package com.xuecheng.framework.domain.ucenter.response.ext;
import com.xuecheng.framework.domain.ucenter.XcMenu;
import com.xuecheng.framework.domain.ucenter.XcUser;
import lombok.Data;
import lombok.ToString;

import java.util.List;

@Data
@ToString
public class XcUserExt extends XcUser {
    //权限信息
    private List<XcMenu> permissions;
    //企业信息
    private String companyId;
}

2)根据账号查询用户信息

package com.xuecheng.api.ucenter;

import com.xuecheng.framework.domain.ucenter.response.ext.XcUserExt;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

@Api(value = "用户中心",description = "用户中心管理")
public interface UcenterControllerApi {
    
    @ApiOperation("获取用户信息")
    public XcUserExt getUserext(String username);
}

2、DAO

添加 XcUser、XcCompantUser 两个表的Dao ,对于一些简单的sql操作,我们使用 Spring Data JPA 实现

public interface XcUserRepository extends JpaRepository<XcUser, String> {
    XcUser findXcUserByUsername(String username);
} 

public interface XcCompanyUserRepository extends JpaRepository<XcCompanyUser,String> {
    //根据用户id查询所属企业id
    XcCompanyUser findByUserId(String userId);
}
3、Service

@Service
public class UserServiceImpl implements UserService {

    //对xc_user表的相关操作
    @Autowired
    XcUserRepository xcUserRepository;

    //对xc_company_user表的相关操作
    @Autowired
    XcCompanyUserRepository xcCompanyUserRepository;

    /**
     * 根据用户名查询用户信息的实现
     * @param username
     * @return
     */
    @Override
    public XcUser findXcUserByUsername(String username) {
        return xcUserRepository.findXcUserByUsername(username);
    }

    /**
     * 根据用户名获取用户权限的实现
     * @param username 用户名
     * @return
     */
    @Override
    public XcUserExt getUserExt(String username) {
        //查询用户信息
        XcUser xcUser = this.findXcUserByUsername(username);
        if(xcUser ==null) return null;

        XcUserExt xcUserExt = new XcUserExt();
        BeanUtils.copyProperties(xcUser,xcUserExt);

        //根据用户id查询用所属公司
        String xcUserId = xcUser.getId();
        XcCompanyUser xcCompanyUser = xcCompanyUserRepository.findByUserId(xcUserId);
        if(xcCompanyUser!=null){
            String companyId = xcCompanyUser.getCompanyId();
            xcUserExt.setCompanyId(companyId);
        }

        //返回XcUserExt对象
        return xcUserExt;
    }
}

4、Controller
@RestController
@RequestMapping("/ucenter")
public class UcenterController implements UcenterControllerApi {
    @Autowired
    UserService userService;
    @Override
    @GetMapping("/getuserext")
    public XcUserExt getUserext(@RequestParam("username") String username) {
        XcUserExt xcUser = userService.getUserExt(username);
        return xcUser;
    }
}
5、可能出现的一些问题

如果 ucenter 服务出现接口需要认证才能访问的情况,考虑可能是继承了 model 工程的 oauth2 依赖导致开启了认证拦截。

解决方案:在 model 工程下的 oauth2 依赖加上 <optional>true</optional> 标签,该标签可以防止本工程下的依赖包传递到其他工程。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
    <optional>true</optional>
</dependency>
6、测试

使用 Swagger-ui 或 postman 测试用户信息查询接口

GET http://localhost:40300/ucenter/getuserext

参数为 username

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IFnI31Zd-1595567384606)(https://qnoss.codeyee.com/20200704_MTc=/image6.png)]

7、思考一些问题

在上述测试过程中,通过 GET 请求调用 http://localhost:40300/ucenter/getuserext 接口可以获取到一个用户的详细信息,但是考虑到用户数据的安全问题,这个接口不应该直接暴露给普通的用户,只适合服务间的调用,并需要经过授权的服务才可以调用。

答:后期配置微服务间认证后可以解决上述的问题。

调用查询用户的接口

1、创建 client

认证服务需要远程调用用户中心服务查询用户,在 认证服务 中创建Feign客户端

@FeignClient(value = XcServiceList.XC_SERVICE_UCENTER)
public interface UserClient {
    @GetMapping("/ucenter/getuserext")
    public XcUserExt getUserext(@RequestParam("username") String username)
}
2、UserDetailsService

认证服务调用 spring security 接口申请令牌,spring security 接口会调用 UserDetailsServiceImpl 从数据库查询用户,如果查询不到则返回 NULL,表示不存在;在UserDetailsServiceImpl 中将正确的密码返回, spring security 会自动去比对输入密码的正确性。

修改 UserDetailsServiceImpl 的 loadUserByUsername 方法,调用 Ucenter服务的查询用户接口

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    ClientDetailsService clientDetailsService;

    //用户中心服务客户端
    @Autowired
    UserClient userClient;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //取出身份,如果身份为空说明没有认证
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        //没有认证统一采用httpbasic认证,httpbasic中存储了client_id和client_secret
        //开始认证client_id和client_secret
        if(authentication==null){
            ClientDetails clientDetails = clientDetailsService.loadClientByClientId(username);
            if(clientDetails!=null){
                //密码
                String clientSecret = clientDetails.getClientSecret();
                return new User(username,clientSecret,AuthorityUtils.commaSeparatedStringToAuthorityList(""));
            }
        }
        if (StringUtils.isEmpty(username)) {
            return null;
        }

        //请求ucenter查询用户
        XcUserExt userext = userClient.getUserext(username);
        if(userext == null) return null; //如果获取到的用信息为空,则返回null,spring security则会抛出异常

        //设置用户的认证和权限信息
        userext.setUsername("itcast");
        userext.setPassword(new BCryptPasswordEncoder().encode("123"));
        userext.setPermissions(new ArrayList<XcMenu>());  //这里授权部分还没完成,所以先填写静态的
        if(userext == null){
            return null;
        }

        //从数据库查询用户正确的密码,Spring Security会去比对输入密码的正确性
        String password = userext.getPassword();
        String user_permission_string = "";

        //设置用户信息到userDetails对象
        UserJwt userDetails = new UserJwt(
                username,
                password,
                AuthorityUtils.commaSeparatedStringToAuthorityList(user_permission_string));
        //用户id
        userDetails.setId(userext.getId());
        //用户名称
        userDetails.setName(userext.getName());
        //用户头像
        userDetails.setUserpic(userext.getUserpic());
        //用户所属企业id
        userDetails.setCompanyId(userext.getCompanyId());

        //返回用信息给到Spring Security进行处理
        return userDetails;
    }
}
3、BCryptPaswordEncoder

早期使用md5对密码进行编码,每次算出的md5值都一样,这样非常不安全,Spring Security推荐使用
BCryptPasswordEncoder对密码加随机盐,每次的Hash值都不一样,安全性高 。

1)BCryptPasswordEncoder测试程序如下

@Test
public void testPasswrodEncoder(){
    String password = "111111";
    PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
    for(int i=0;i<10;i++) {
        //每个计算出的Hash值都不一样
        String hashPass = passwordEncoder.encode(password);
        System.out.println(hashPass);
        //虽然每次计算的密码Hash值不一样但是校验是通过的
        boolean f = passwordEncoder.matches(password, hashPass);
        System.out.println(f);
    }
}

2)在 AuthorizationServerConfig 配置类中配置 BCryptPasswordEncoder

原教程中已经在 WebSecurityConfig 中进行了配置,这个在哪里配置都无所谓,本质上都是向spring注入一个bean

//采用bcrypt对密码进行Hash
@Bean
public PasswordEncoder passwordEncoder() {
	return new BCryptPasswordEncoder();
}

3)测试

请求 http://localhost:40400/auth/userlogin,输入正常的账号和密码进行测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tui8jCcF-1595567384607)(https://qnoss.codeyee.com/20200704_MTc=/image7.png)]

4、解析申请令牌错误信息

当账号输入错误应该返回用户不存在的信息,当密码错误要返回用户名或密码错误信息,业务流程图如下:

image-20200527103015749

修改申请令牌的程序解析返回的错误:

由于 restTemplate 收到400或401的错误会抛出异常,而 spring security 针对账号不存在及密码错误会返回 400 及 401,所以在代码中控制针对 400 或 401 的响应不要抛出异常。

修改 AuthServiceImpl 的 appleToken 方法

//向Oauth2服务申请令牌
private AuthToken appleToken(String username, String password, String clientId, String clientSecret){
    //采用客户端负载均衡的方式从eureka获取认证服务的ip和端口
    ServiceInstance serviceInstance = loadBalancerClient.choose("XC-SERVICE-UCENTER-AUTH");
    URI uri = serviceInstance.getUri();
    String authUrl = uri + "/auth/oauth/token";

    //使用LinkedMultiValueMap储存多个header信息
    LinkedMultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
    //设置basic认证信息
    String basicAuth = this.getHttpBasic(clientId, clientSecret);
    headers.add("Authorization",basicAuth);

    //设置请求中的body信息
    LinkedMultiValueMap<String, String> body = new LinkedMultiValueMap<>();
    body.add("grant_type","password");
    body.add("username",username);
    body.add("password",password);
    HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(body, headers);

    //凭证信息错误时候, 指定restTemplate当遇到400或401响应时候也不要抛出异常,也要正常返回值
    restTemplate.setErrorHandler(new DefaultResponseErrorHandler(){
        @Override
        public void handleError(ClientHttpResponse response) throws IOException {
            //当响应的值为400或者401时也要正常响应,不要抛出异常
            if(response.getRawStatusCode()!=400 && response.getRawStatusCode()!=401){
                super.handleError(response);
            }
        }
    });

    Map map = null;
    try {
        ((RestTemplate) restTemplate).setErrorHandler(new DefaultResponseErrorHandler() {
            @Override
            public void handleError(ClientHttpResponse response) throws IOException {
                // 设置 当响应400和401时照常响应数据,不要报错
                if (response.getRawStatusCode() != 400 && response.getRawStatusCode() != 401 ) {
                    super.handleError(response);
                }
            }
        });

        //http请求spring security的申请令牌接口
        ResponseEntity<Map> mapResponseEntity = restTemplate.exchange(authUrl, HttpMethod.POST, new
                                                                      HttpEntity<MultiValueMap<String, String>>(body, headers), Map.class);
        map = mapResponseEntity.getBody();
    } catch (Exception e) {
        e.printStackTrace();
        LOGGER.error("request oauth_token_password error: {}",e.getMessage());
        e.printStackTrace();
        ExceptionCast.cast(AuthCode.AUTH_LOGIN_APPLYTOKEN_FAIL);
    }
    if(map == null ||map.get("access_token") == null ||
       map.get("refresh_token") == null ||
       map.get("jti") == null){//jti是jwt令牌的唯一标识作为用户身份令牌
        //获取spring security返回的错误信息
        String error_description = (String) map.get("error_description");
        if(StringUtils.isNotEmpty(error_description)){
            if(error_description.equals("坏的凭证")){
                ExceptionCast.cast(AuthCode.AUTH_CREDENTIAL_ERROR);
            }else if(error_description.indexOf("UserDetailsService returned null")>=0){
                ExceptionCast.cast(AuthCode.AUTH_ACCOUNT_NOTEXISTS);
            }
        } ExceptionCast.cast(AuthCode.AUTH_LOGIN_APPLYTOKEN_FAIL);
    }

    //拼装authToken并返回
    AuthToken authToken = new AuthToken();
    //访问令牌(jwt)
    String access_token = (String) map.get("access_token");
    //刷新令牌(jwt)
    String refresh_token = (String) map.get("refresh_token");
    //jti,作为用户的身份标识,也就是后面我们用于返回给到用户前端的凭证
    String jwt_token = (String) map.get("jti");

    authToken.setAccess_token(access_token);
    authToken.setRefresh_token(refresh_token);
    authToken.setJwt_token(jwt_token);
    return authToken;
}

用户不存在:

image-20200527103916066

密码错误:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TLHi2xpZ-1595567384612)(https://qnoss.codeyee.com/20200704_MTc=/image10.png)]

5、测试

使用postman请求http://localhost:40400/auth/userlogin

1、输入正确的账号和密码进行测试

从数据库找到测试账号,本课程所提供的用户信息初始密码统一为123

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mQ7FceNs-1595567384613)(https://qnoss.codeyee.com/20200704_MTc=/image11.png)]

2、输入错误的账号和密码进行测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EyTQYJHb-1595567384614)(https://qnoss.codeyee.com/20200704_MTc=/image12.png)]

0x03 用户登录前端

需求分析

点击用户登录固定跳转到用户中心前端的登录页面,如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QCUYwPmR-1595567384615)(https://qnoss.codeyee.com/20200704_MTc=/image13.png)]

输入账号和密码,登录成功,跳转到首页。

用户中心前端(xc-ui-pc-learning工程)提供登录页面,所有子系统连接到此页面。

说明:

  • 页面有 “登录|注册” 链接的前端系统有:门户系统、搜索系统、用户中心。

  • 本小节修改门户系统的页头,其它三处可参考门户修改。

Api方法

在 xc-ui-pc-leanring/src/base/api/login.js 下配置该api方法,用于请求后端登录接口

/*登陆*/
export const login = params => {
    //let loginRequest = querystring.stringify(params)
    let loginRequest = qs.stringify(params);
    return http.requestPostForm('/openapi/auth/userlogin',loginRequest);
}

页面

1、登录页面

进入用户中心前端 xc-ui-pc-leanring/src/module/home/page/,找到登录页面 loginpage.vue:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z3QsUv87-1595567384616)(https://qnoss.codeyee.com/20200704_MTc=/image14.png)]

loginpage.vue 导入了 loginForm.vue 组件,loginForm.vue 页面包括了登录表单

<template>
  <div>
    <p-head></p-head>

    <login-form></login-form>

    <p-foot></p-foot>
  </div>
</template>
<script>
import PHead from '@/base/components/head.vue';
import PFoot from '@/base/components/foot.vue';
import loginForm from '@/base/components/loginForm.vue';
...

在 xc-ui-pc-leanring/src/base/components 下我们可以看到一个 loginForm.vue 的页面文件,主要为登录表单的页面实现,部分页面代码如下

<template>
  <div>
        <el-row class="container" style="width: 470px">
          <div id="body">
            <div class="g-center login-page" @keyup.enter="login">
              <el-tabs v-model="activeName" >
                <el-tab-pane label="用户登陆" name="login">
              <el-form :model="loginForm" label-width="80px" :rules="loginRules" ref="loginForm" class="login-form">
                <el-form-item label="账号" prop="username">
                  <el-input v-model="loginForm.username" auto-complete="off" ></el-input>
                </el-form-item>
                <el-form-item label="密码" prop="password">
                  <el-input v-model="loginForm.password" auto-complete="off" ></el-input>
                </el-form-item>
                <el-form-item >
                  <el-button type="primary"  @click.native="login" :loading="editLoading">登陆</el-button>
                  <el-button type="primary"  @click="resetForm('loginForm')">重置</el-button>
                </el-form-item>
              </el-form>
                </el-tab-pane>
                <el-tab-pane label="用户注册" name="register">
                  建设中..
                </el-tab-pane>
              </el-tabs>
            </div>

        </div>
        </el-row>
  </div>
</template>

2、路由配置

来到 xc-ui-pc-leanring/src/module/home/router 下配置home模块的路由:

import Home from '@/module/home/page/home.vue';
import Login from '@/module/home/page/loginpage.vue';
import Denied from '@/module/home/page/denied.vue';
import Logout from '@/module/home/page/logout.vue';
import order_pay from '@/module/order/page/order_pay.vue';
export default [{
    path: '/',
    component: Home,
    name: '个人中心',
    hidden: true
},
{
	path: '/login',
	component: Login,
	name: 'Login',
 	hidden: true
},
   .....

3、登录后跳转

请求登录页面需携带 returnUrl 参数,要求此参数使用 Base64 编码。

登录成功后将跳转到 returnUrl,loginForm.vue 组件的登录方法如下:

login: function () {
    this.$refs.loginForm.validate((valid) => {
        if (valid) {
            this.editLoading = true;
            let para = Object.assign({}, this.loginForm);
            loginApi.login(para).then((res) => {
                this.editLoading = false;
                if(res.success){
                    this.$message('登陆成功');
                    //刷新 当前页面
                    // alert(this.returnUrl)
                    console.log(this.returnUrl)
                    if(this.returnUrl!='undefined' && this.returnUrl!=''
                       && !this.returnUrl.includes("/userlogout")
                       && !this.returnUrl.includes("/userlogin")){
                        window.location.href = this.returnUrl;
                    }else{
                        //跳转到首页
                        window.location.href = 'http://www.xuecheng.com/'
                    }
                }else{
                    if(res.message){
                        this.$message.error(res.message);
                    }else{
                        this.$message.error('登陆失败');
                    }
                }
            },
                                      (res) => {
                this.editLoading = false;
            });
        }
    });
},

点击登录页面

在门户的页头点击“登录|注册”连接到用户中心的登录页面,并且携带 returnUrl。
修改门户的 header.html,代码如下:

<a href="javascript:;" @click="showlogin" v-if="logined == false">登陆&nbsp;|&nbsp;注册</a>

配置 showlogin 方法

showlogin: function(){
    //this.loginFormVisible = true;
    window.location = "http://ucenter.xuecheng.com/#/login?returnUrl="+
        Base64.encode(window.location)
}

测试

测试之前修改认证服务的配置:

修改 application.yml 中 cookie 域名

cookieDomain: xuecheng.com  

测试流程如下:

1、输入www.xuecheng.com进入系统(需要在hosts文件配置)

2、输入正确的账号和密码,提交

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-on3yU8OE-1595567384617)(https://qnoss.codeyee.com/20200704_MTc=/image15.png)]

3、输入错误的账号和密码,提交

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CxZJBHh2-1595567384617)(https://qnoss.codeyee.com/20200704_MTc=/image16.png)]

登录成功,观察 cookie 是否存储成功:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yDiddGRF-1595567384618)(https://qnoss.codeyee.com/20200704_MTc=/image17.png)]

二、前端显示当前用户

0x01 需求分析

用户登录成功在页头显示当前登录的用户名称。

数据流程如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KWYS1v9Y-1595567384619)(https://qnoss.codeyee.com/20200704_MTc=/image18.png)]

1、用户请求认证服务,登录成功。

2、用户登录成功,认证服务向 cookie 写入身份令牌,向 redis 写入 user_token(身份令牌及授权jwt授权令牌)

3、客户端携带 cookie 中的身份令牌请求认证服务获取 jwt 令牌。

4、客户端解析 jwt 令牌,并将解析的用户信息存储到 sessionStorage 中。jwt令牌中包括了用户的基本信息,客户端解析jwt令牌即可获取用户信息。

5、客户端从sessionStorage中读取用户信息,并在页头显示。

sessionStorage 是H5的一个会话存储对象,在 SessionStorage中保存的数据只在同一窗口或同一标签页中有效,在关闭窗口之后将会删除SessionStorage中的数据。seesionStorage 的存储方式采用key/value的方式,可保存5M左右的数据(不同的浏览器会有区别)

sessionStorage 是H5的一个会话存储对象,在 SessionStorage中保存的数据只在同一窗口或同一标签页中有效,
在关闭窗口之后将会删除SessionStorage中的数据。

seesionStorage的存储方式采用key/value的方式,可保存5M左右的数据(不同的浏览器会有区别)

0x02 jwt查询接口

该接口我们在 ucenter-auth 服务下进行开发

需求分析

认证服务对外提供jwt查询接口,流程如下:

1、客户端携带 cookie 中的身份令牌请求认证服务获取 jwt

2、认证服务根据身份令牌从 redis 中查询 jwt 令牌并返回给客户端。

API

在认证模块定义 jwt 查询接口:

@Api(value = "jwt查询接口",description = "客户端查询jwt令牌内容")
public interface AuthControllerApi {
    @ApiOperation("查询userjwt令牌")
    public JwtResult userjwt();
    ....

Dao

无

Service

在AuthService中定义方法如下:

//从redis查询令牌
public AuthToken getUserToken(String token){
    String userToken = "user_token:"+token;
    String userTokenString = stringRedisTemplate.opsForValue().get(userToken);
    if(userToken!=null){
        AuthToken authToken = null;
        try {
            authToken = JSON.parseObject(userTokenString, AuthToken.class);
        } catch (Exception e) {
            LOGGER.error("getUserToken from redis and execute JSON.parseObject error{}",e.getMessage());
            e.printStackTrace();
        } 
        return authToken;
    } 
    return null;
}

Controller

@Override
@GetMapping("/userjwt")
public JwtResult userjwt() {
    //获取cookie中的令牌
    String access_token = getTokenFormCookie();
    //根据令牌从redis查询jwt
    AuthToken authToken = authService.getUserToken(access_token);
    if(authToken == null){
        return new JwtResult(CommonCode.FAIL,null);
    } 
    return new JwtResult(CommonCode.SUCCESS,authToken.getJwt_token());
} 
//从cookie中读取访问令牌
private String getTokenFormCookie(){
    Map<String, String> cookieMap = CookieUtil.readCookie(request, "uid");
    String access_token = cookieMap.get("uid");
    return access_token;
}

WebSecurityConfig 配置放行 userjwt

class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/userlogin","/userlogout","/getjwt","/swagger-ui.html");
    }

测试

使用 postman 测试

1、请求 /auth/userlogin

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aUD1884D-1595567384620)(https://qnoss.codeyee.com/20200704_MTc=/image19.png)]

观察 cookie 是否已存入用户身份令牌

2、get请求jwt

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JvxP5Aqf-1595567384621)(https://qnoss.codeyee.com/20200704_MTc=/image20.png)]

0x03 前端请求jwt

需求分析

前端需求如下:

用户登录成功,前端请求认证服务获取jwt令牌。

前端解析jwt令牌的内容,得到用户信息,并将用户信息存储到 sessionStorage。

从 sessionStorage 取出用户信息在页头显示用户名称。

以下操作我们在门户工程进行

API方法

在login.js中定义getjwt方法:

/*获取jwt令牌*/
const getjwt = () => {
	return requestGet('/openapi/auth/userjwt');
}

页面

修改 include/header.html

1、页面视图

<span v-if="logined == true">欢迎{{this.user.username}}</span>
<a href="javascript:;" @click="logout" v-if="logined == true">退出</a>
<a href="http://ucenter.xuecheng.com/" class="personal" target="_blank">我的学习</a>
<a href="javascript:;" @click="showlogin" v-if="logined == false">登陆&nbsp;|&nbsp;注册</a>
<a href="http://teacher.xuecheng.com/" class="personal" target="_blank">教学提供方</a>
<a href="http://system.xuecheng.com/" class="personal" target="_blank">系统后台</a>

用户登录成功设置数据对象 logined 为 true,设置数据对象 user 为当前用户信息。

数据对象定义如下

user:{
    userid:'',
    username: '',
    userpic: ''
},
logined:false

2、解析jwt令牌

在 util.js 中定义解析jwt令牌方法:

//解析jwt令牌,获取用户信息
var getUserInfoFromJwt = function (jwt) {
    if(!jwt){
        return ;
    } 
    var jwtDecodeVal = jwt_decode(jwt);
    if (!jwtDecodeVal) {
        return ;
    } 
    let activeUser={}
    //console.log(jwtDecodeVal)
    activeUser.utype = jwtDecodeVal.utype || '';
    activeUser.username = jwtDecodeVal.name || '';
    activeUser.userpic = jwtDecodeVal.userpic || '';
    activeUser.userid = jwtDecodeVal.userid || '';
    activeUser.authorities = jwtDecodeVal.authorities || '';
    activeUser.uid = jwtDecodeVal.jti || '';
    activeUser.jwt = jwt;
    return activeUser;
}

3、refresh_user()

在 mounted 钩子方法中调用 refresh_user 获取当前用户信息,并将用户信息存储到 sessionStorage

mounted(){
//刷新当前用户
this.refresh_user()
}

refresh_user() 方法如下:

refresh_user:function(){
    //从sessionStorage中取出当前用户
    let activeUser= getActiveUser();
    //取出cookie中的令牌
    let uid = getCookie("uid")
        //console.log(activeUser)
        if(activeUser && uid && uid == activeUser.uid){
            this.logined = true
                this.user = activeUser;
        }else{
            if(!uid){
                return ;
            } 
            //请求查询jwt
            getjwt().then((res) => {
                if(res.success){
                    let jwt = res.jwt;
                    let activeUser = getUserInfoFromJwt(jwt)
                        if(activeUser){
                            this.logined = true
                                this.user = activeUser;
                            setUserSession("activeUser",JSON.stringify(activeUser))
                        }
                }
            })
        }
}

配置代理转发

上边实现在首页显示当前用户信息,首页需要通过 Nginx 代理请求认证服务,所以需要在 www 域下的虚拟主机上配置代理路径:

#认证
location ^~ /openapi/auth/ {
	proxy_pass http://auth_server_pool/auth/;
}

注意:其它前端系统要接入认证要请求认证服务也需要配置上边的代理路径。

测试

登录成功后自动跳转回到门户主站,并显示用户的信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-odgVGynq-1595567384622)(https://qnoss.codeyee.com/20200704_MTc=/image21.png)]

三、用户退出

0x01 需求分析

操作流程如下:

1、用户点击退出,弹出退出确认窗口,点击确定

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EsJacfkM-1595567384624)(https://qnoss.codeyee.com/20200704_MTc=/image22.png)]

2、退出成功

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CWdNrEct-1595567384627)(https://qnoss.codeyee.com/20200704_MTc=/image23.png)]

用户退出要完成以下动作:

1、删除 redis 中的 token

2、删除 cookie 中的 token

0x02 API

认证服务对外提供退出接口。

@ApiOperation("退出")
public ResponseResult logout();

0x03 服务端

认证服务提供退出接口。

DAO

无

Service

/**
     * 删除指定usertoken在redis中的jwt信息
     * @param uid usertoken
     * @return
     */
@Override
public Boolean delToken(String uid) {
    String key = "user_token:" + uid;
    Boolean delete = stringRedisTemplate.delete(key);
    return delete;
}

Controller

@PostMapping("/userlogout")
@Override
public ResponseResult logout() {
    // 取出用户身份令牌
    String uid = getTokenFormCookie();
    //删除用户在redis中的身份信息
    Boolean delToken = authService.delToken(uid);
    //通过修改返回的response来实现用户前端收到响应后删除浏览器的cookie信息
    clearCookie(uid);
    if(delToken){
        return new ResponseResult(CommonCode.SUCCESS);
    }
    return new ResponseResult(CommonCode.FAIL);
}

//清除cookie
private void clearCookie(String token){
    HttpServletResponse response = ((ServletRequestAttributes)
                                    RequestContextHolder.getRequestAttributes()).getResponse();
    // 设置maxAge为实现删除cookie
    CookieUtil.addCookie(response, cookieDomain, "/", "uid", token, 0, false);
}

退出URL放行

认证服务默认都要校验用户的身份信息,这里需要将退出url放行。

在 WebSecurityConfig 类中重写 configure (WebSecurity web)方法,如下:

@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers("/userlogin","/userlogout","/userjwt","/swagger-ui.html");
}

0x04 前端

需求分析

在用户中心前端工程(xc-ui-pc-learning)开发退出页面。

Api方法定义

在用户中心工程增加退出的 api 方法

在 base 模块的 login.js 增加方法如下:

/*退出*/
export const logout = params => {
    return http.requestPost('/openapi/auth/userlogout');
}

退出页面

1、在用户中心工程创建退出页面

参考: xc-ui-pc-leanring/src/module/home/page/logout.vue

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NTqDwYqq-1595567384629)(https://qnoss.codeyee.com/20200704_MTc=/image24.png)]

2、路由配置

import Logout from '@/module/home/page/logout.vue';
import order_pay from '@/module/order/page/order_pay.vue';
// import LoginMini from '@/module/home/page/login_mini.vue';
export default [{
    path: '/',
    component: Home,
    name: '个人中心',
    hidden: true
},
{
    path: '/login',
    component: Login,
    name: 'Login',
    hidden: true
},
{
    path: '/logout',
    component: Logout,
    name: 'Logout',
    hidden: true
},
....

3、退出方法

退出成功清除页面的 sessionStorage

参考 logout.vue

在 created 钩子方法请求退出方法

created(){
    loginApi.logout({}).then((res) => {
        if(res.success){
            sessionStorage.removeItem('activeUser');
            this.$message('退出成功');
            this.logoutsuccess = true
        }else if(res.code == 11111){
            // 用户的登录信息已在redis过期
            this.logoutsuccess = false
            this.$message('用户凭证过期, 在这之前已经退出登陆');
        } else{
            this.logoutsuccess = false
        }
        //清空用户的缓存信息
        sessionStorage.clear()
    },
                             (res) => {
        this.logoutsuccess = false
    });
},

链接到退出页面

修改门户主站 xc-ui-pc-static-portal 的 include/header.html

<a href="javascript:;" @click="logout" v-if="logined == true">退出</a>

在 include/header.html 中添加 element-ui 库,将此js加到 head 的最下边

否则可能会出现无法加载element-ui组件的问题

<script src="/css/el/index.js"></script>

logout 方法如下:

logout: function () {
    this.$confirm('确认退出吗?', '提示', {
    }).then(() => {
        //跳转到用户中心的登出页面
        window.location = "http://ucenter.xuecheng.com/#/logout"
    }).catch(() => {
        
    });
},

测试效果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7QecR8fm-1595567384630)(https://qnoss.codeyee.com/20200704_MTc=/image25.png)]

一些问题

下述的一些问题在我上面的代码中其实已经修复,但部分读者可能跳过了上述的步骤,仍然使用的是原教程中所给到的代码案例,所以这里的一些问题我单独列出来。

1、用户的登录信息已在redis过期,返回操作的状态码,前端没有识别为已登出的状态

增加对 11111 状态码的判断

created(){
    loginApi.logout({}).then((res) => {
        if(res.success){
            sessionStorage.removeItem('activeUser');
            this.$message('退出成功');
            this.logoutsuccess = true
        }else if(res.code == 11111){
            // 用户的登录信息已在redis过期
            this.logoutsuccess = false
            this.$message('用户凭证过期, 在这之前已经退出登陆');
        } else{
            this.logoutsuccess = false
        }
    },
                             (res) => {
        this.logoutsuccess = false
    });
},

2、登出成功后,返回主页后仍然显示用户的信息

在发送登出请求后,使用 sessionStorage.clear() 清空用户的缓存信息

created(){
    loginApi.logout({}).then((res) => {
        if(res.success){
            sessionStorage.removeItem('activeUser');
            this.$message('退出成功');
            this.logoutsuccess = true
        }else if(res.code == 11111){
            // 用户的登录信息已在redis过期
            this.logoutsuccess = false
            this.$message('用户凭证过期, 在这之前已经退出登陆');
        } else{
            this.logoutsuccess = false
        }
        //清空用户的缓存信息
        sessionStorage.clear()
    },
                             (res) => {
        this.logoutsuccess = false
    });
},

四、Zuul 网关

0x01 需求分析

网关的作用相当于一个过虑器、拦截器,它可以拦截多个系统的请求。

本章节要使用网关校验用户的身份是否合法。

0x02 Zuul 介绍

什么是Zuul?

Spring Cloud Zuul 是整合 Netflix 公司的 Zuul 开源项目实现的微服务网关,它实现了 请求路由、负载均衡、校验过虑 等功能。

官方:https://github.com/Netflix/zuul

什么是网关?

服务网关是在微服务前边设置一道屏障,请求先到服务网关,网关会对请求进行 过虑、校验、路由 等处理。有了服务网关可以提高微服务的安全性,网关校验请求的合法性,请求不合法将被拦截,拒绝访问。

Zuul 与 Nginx 怎么配合使用?

Zuul与 Nginx 在实际项目中需要配合使用,如下图,Nginx 的作用是反向代理、负载均衡,Zuul 的作用是保障微服务的安全访问,拦截微服务请求,校验合法性及负载均衡。

image-20200531170255074

0x03 搭建网关工程

创建网关工程(xc-govern-gateway):

1、创建 xc-govern-gateway 工程

导入 资料/xc-govern-gateway.zip

2、@EnableZulProxy

注意在启动类上使用 @EnableZuulProxy 注解标识此工程为 Zuul 网关,启动类代码如下:

@SpringBootApplication
@EnableZuulProxy
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

0x04 路由配置

需求分析

Zuul 网关具有代理的功能,根据请求的url转发到微服务,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7eAF3GPr-1595567384633)(https://qnoss.codeyee.com/20200704_MTc=/image27.png)]

  • 客户端请求网关 /api/learning,通过路由转发到 /learning

  • 客户端请求网关 /api/course,通过路由转发到 /course

路由配置

在 appcation.yml 中配置:

zuul:
  routes:
	manage-course: #路由名称,名称任意,保持所有路由名称唯一
      path: /course/**
      serviceId: xc-service-manage-course #指定服务id,从Eureka中找到服务的ip和端口
      #url: http://localhost:31200 #也可指定url
      strip-prefix: false #true:代理转发时去掉前缀,false:代理转发时不去掉前缀
      sensitiveHeaders: #默认zuul会屏蔽cookie,cookie不会传到下游服务,这里设置为空则取消默认的黑名单,如果设置了具体的头信息则不会传到下游服务
	  # ignoredHeaders: Authorization
  • serviceId:推荐使用 serviceId,zuul会从 Eureka 中找到服务 id 对应的 ip 和端口。

  • strip-prefix: false或者true,设置为 true 时代理转发时去掉前缀,false 则代理转发时不去掉前缀

    例如,设置为 true 请求 /course/coursebase/get/.. ,代理转发到 /coursebase/get/,如果为false则代理直接转发到原来的url

  • sensitiveHeaders:敏感头设置,默认会过虑掉cookie,这里设置为空表示不过虑

  • ignoredHeaders:可以设置过虑的头信息,默认为空表示不过虑任何头

测试

http://localhost:50201/api 是网关地址,通过路由转发到 xc-service-manage-course 服务。

打算使用课程图片信息获取的 API 进行测试我,这里的课程图片信息获取的URL为 /course/coursepic/get ,所以由于课程管理已经添加了授课拦截,这里为了测试网关功能暂时将 url /course/coursepic/get排除认证。在课程管理服务的 ResourceServerConfig 类中添加 "/course/coursepic/get/*" 代码如下:

@Override
public void configure(HttpSecurity http) throws Exception {
    //所有请求必须认证通过
    http.authorizeRequests()
        //下边的路径放行
        .antMatchers("/v2/api-docs", "/swagger-resources/configuration/ui",
                     "/swagger-resources","/swagger-resources/configuration/security",
                     "/swagger-ui.html","/course/coursepic/list/*")
        .permitAll()
        .anyRequest().authenticated();
}

而我之前的课程中将需要排除的 url 改写成了从配置文件中读取,如下代码,如果是按原教程的配置的,可以忽略下述的代码, 直接阅读测试的环节。

@Value("${oauth2.urlMatchers}")
String urlMatchers;

//Http安全配置,对每个到达系统的http请求链接进行校验
@Override
public void configure(HttpSecurity http) throws Exception {
    if(urlMatchers.equals("")){
        //如果urlMatchers未指定,则所有url都需要授权后才能被访问
        http.authorizeRequests().anyRequest().authenticated();
    }else{
        //放行 urlMatchers 中指定的url条目, 未指定的url仍需授权后才能访问
        String[] split = urlMatchers.split(",");
        http.authorizeRequests()
            //下边的路径放行
            .antMatchers(split).permitAll()
            .anyRequest().authenticated();
    }
}

在 appliaction.yml 中配置 oauth2.urlMatchers

oauth2:
  urlMatchers: /v2/api-docs,/swagger-resources/configuration/ui,/swagger-resources,/swagger-resources/configuration/security,/swagger-ui.html,/webjars/**,"/course/coursepic/get/*"

请求请求到如下连接,进行 查询课程图片信息

GET:http://localhost:50201/api/course/coursepic/list/4028e58161bd22e60161bd23672a0001

测试结果如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QBrG9oRd-1595567384634)(https://qnoss.codeyee.com/20200704_MTc=/image28.png)]

完整的路由配置

zuul:
  routes:
    xc-service-learning: #路由名称,名称任意,保持所有路由名称唯一
      path: /learning/**
      serviceId: xc-service-learning #指定服务id,从Eureka中找到服务的ip和端口
      strip-prefix: false
      sensitiveHeaders:
    manage-course:
      path: /course/**
      serviceId: xc-service-manage-course
      strip-prefix: false
      sensitiveHeaders:
    manage-cms:
      path: /cms/**
      serviceId: xc-service-manage-cms
      strip-prefix: false
    	sensitiveHeaders:
    manage-sys:
      path: /sys/**
      serviceId: xc-service-manage-cms
      strip-prefix: false
      sensitiveHeaders:
    service-ucenter:
    	path: /ucenter/**
      serviceId: xc-service-ucenter
      sensitiveHeaders:
      strip-prefix: false
    xc-service-manage-order:
      path: /order/**
      serviceId: xc-service-manage-order
      sensitiveHeaders:
      strip-prefix: false

0x05 过滤器

Zuul的核心就是过虑器,通过过虑器实现请求过虑,身份校验等

配置ZuulFilter

自定义过虑器需要继承 ZuulFilter,ZuulFilter 是一个抽象类,需要覆盖它的四个方法,如下:

  • shouldFilter:返回一个 Boolean 值,判断该过滤器是否需要执行。返回true表示要执行此过虑器,否则不执
    行。

  • run:过滤器的业务逻辑。

  • filterType:返回字符串代表过滤器的类型,如下

    • pre:请求在被路由之前执行

    • routing:在路由请求时调用

    • post:在 routing 和 errror 过滤器之后调用

    • error:处理请求时发生错误调用

  • filterOrder:此方法返回整型数值,通过此数值来定义过滤器的执行顺序,数字越小优先级越高。

测试

过虑所有请求,判断头部信息是否有 Authorization,如果没有则拒绝访问,否则转发到微服务。

定义过虑器,使用 @Component 标识为 bean。

在网关工程下构建一个 filter 包,创建一个 LoginFilterTest 并继承于 ZuulFilter

public class LoginFilterTest extends ZuulFilter {
    private static final Logger LOG = LoggerFactory.getLogger(LoginFilterTest.class);
    @Override
    public String filterType() {
        return "pre";
    }
    @Override
    public int filterOrder() {
        return 2;//int值来定义过滤器的执行顺序,数值越小优先级越高
    }
    @Override
    public boolean shouldFilter() {// 该过滤器需要执行
        return true;
    }
    @Override
    public Object run() {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletResponse response = requestContext.getResponse();         //获取响应对象
        HttpServletRequest request = requestContext.getRequest();           //获取请求对象
        //取出头部信息Authorization
        String authorization = request.getHeader("Authorization");
        //判断用户的请求中是否带有 Authorization 字段,如果没有则表示未认证用户
        if(StringUtils.isEmpty(authorization)){
            requestContext.setSendZuulResponse(false);// 拒绝访问
            requestContext.setResponseStatusCode(200);// 设置响应状态码
            ResponseResult unauthenticated = new ResponseResult(CommonCode.UNAUTHENTICATED);
            String jsonString = JSON.toJSONString(unauthenticated);
            requestContext.setResponseBody(jsonString);
            requestContext.getResponse().setContentType("application/json;charset=UTF-8");
            return null;
        }
        return null;
    }
}

测试请求:

http://localhost:50201/api/course/coursebase/get/4028e581617f945f01617f9dabc40000 查询课程信息

1、Header 中不设置 Authorization

响应结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-foka2jYr-1595567384635)(https://qnoss.codeyee.com/20200704_MTc=/image29.png)]

2、Header 中设置 Authorization

成功响应课程信息。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wfpPJjzD-1595567384637)(https://qnoss.codeyee.com/20200704_MTc=/image30.png)]

五、身份校验

0x01 需求分析

本小节实现网关连接 Redis 校验令牌:

1、从 cookie 查询用户身份令牌是否存在,不存在则拒绝访问

2、从 http header 查询jwt令牌是否存在,不存在则拒绝访问

3、从 Redis 查询 user_token 令牌是否过期,过期则拒绝访问

0x02 业务实现

1、配置 application.yml

配置 redis链接参数:

spring:
  application:
    name: xc‐govern‐gateway
  redis:
    host: ${REDIS_HOST:127.0.0.1}
    port: ${REDIS_PORT:6379}
    timeout: 5000 #连接超时 毫秒
    jedis:
      pool:
        maxActive: 3
        maxIdle: 3
        minIdle: 1
        maxWait: ‐1  #连接池最大等行时间 ‐1没有限制

2、使用 StringRedisTemplate 查询 key 的有效期

在 service 包下定义 AuthService 类:

@Service
public class AuthService {
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    //查询身份令牌
    public String getTokenFromCookie(HttpServletRequest request){
        Map<String, String> cookieMap = CookieUtil.readCookie(request, "uid");
        String access_token = cookieMap.get("uid");
        if(StringUtils.isEmpty(access_token)){
            return null;
        } 
        return access_token;
    } 
    //从header中查询jwt令牌
    public String getJwtFromHeader(HttpServletRequest request){
        String authorization = request.getHeader("Authorization");
        if(StringUtils.isEmpty(authorization)){
            //拒绝访问
            return null;
        }
        if(!authorization.startsWith("Bearer ")){
            //拒绝访问
            return null;
        } 
        return authorization;
    } 
    //查询令牌的有效期
        public long getExpire(String access_token) {
        //token在redis中的key
        String key = "user_token:"+access_token;
        Long expire = stringRedisTemplate.getExpire(key);
        return expire;
    }
}

3、定义LoginFilter

0x03 测试

1、配置代理

通过 nginx 转发到 gateway,在 www.xuecheng.com 虚拟主机来配置

#微服务网关
upstream api_server_pool{
	server 127.0.0.1:50201 weight=10;
} 
#微服务网关
location /api {
	proxy_pass http://api_server_pool;
}

使用 postman 测试:

GET 请求:http://www.xuecheng.com/api/course/coursepic/get/4028e581617f945f01617f9dabc40000

注意:这里通过网关请求了 course/coursepic/get 地址,课程管理 url 根据自己的开发情况去配置放行。

正常流程测试

1、执行登录使之向 cookie 写入身份令牌 uid

Post请求:http://ucenter.xuecheng.com/openapi/auth/userlogin

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b32tsQNj-1595567384638)(https://qnoss.codeyee.com/20200704_MTc=/image31.png)]

并从redis获取jwt令牌的内容

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JJmBYKsu-1595567384640)(https://qnoss.codeyee.com/20200704_MTc=/image32.png)]

2、手动在postman添加header

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pRuFsjlD-1595567384642)(https://qnoss.codeyee.com/20200704_MTc=/image33.png)]

成功查询:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U5cQNnO4-1595567384643)(https://qnoss.codeyee.com/20200704_MTc=/image34.png)]

这里要注意的是,如果这里出现 token验证失败,那就是你的课程管理管理服务的 resources 下的公钥文件于认证服务的私钥不匹配

异常流程测试

手动删除 header 或清除 cookie 观察测试结果。

image-20200531201145872

😁 认识作者

作者:👦 LCyee ,全干型代码🐕

自建博客:https://www.codeyee.com

记录学习以及项目开发过程中的笔记与心得,记录认知迭代的过程,分享想法与观点。

优快云 博客:https://blog.youkuaiyun.com/codeyee

记录和分享一些开发过程中遇到的问题以及解决的思路。

欢迎加入微服务练习生的队伍,一起交流项目学习过程中的一些问题、分享学习心得等,不定期组织一起刷题、刷项目,共同见证成长。

小曾WRF自学日记(3)渐入佳境 ——WRF实例-数据下载与WPS前处理 ww 阅读详情

相关推荐

cuda安装时出现错误 Unable to locate package cuda-toolkit-12-4

cuda安装时出现错误 Unable to locate package cuda-toolkit-12-4

4867

Java的新项目学成在线笔记-day17(八)

3 用户退出 3.1 需求分析 操作流程如下: 1、用户点击退出,弹出退出确认窗口,点击确定 用户退出要以下动作: 1、删除redis中的token 2、删除cookie中的token 3.2 API 认证服务对外提供退出接口。 [AppleScript]纯文本查看复制代码 ? 1 @ApiOperation("退出") public Respo...

czbkzmj的博客 177

300Kg载重履带式无人车底盘定制开发技术详解

通过精准的需求分析、科学的方案设计、严格的研发与测试以及完善的交付与培训流程,可以打造出符合客户需求的优质产品。300Kg载重履带式无人车底盘的定制开发,旨在打造一款承载能力强、行驶稳定、适应复杂地形的无人运输平台。根据应用场景选择合适的动力源,并进行性能优化,确保无人车在载重情况下仍能保持稳定的行驶速度和动力输出。根据需求分析结果,设计无人车底盘的总体方案,包括动力系统选择、结构设计、负载管理策略等。进行详细的研发工作,包括零部件的选型与采购、样机的制作与调试等。

专注多旋翼、复合翼无人机技术研究,从事低空经济行业。 958

java 路由转发_学习微服务的路由网关zuul——路由转发和过滤器

zuul具有路由转发和过滤的功能,类似于nginx的功能。首先新建一个项目service-zuul来实现路由转发的功能。1.路由转发功能build.gradle文件buildscript {ext {springBootVersion = '2.0.4.RELEASE'}repositories {mavenCentral()}dependencies {classpath("org.spring...

weixin_29230805的博客 962

ChatGPT and Claude国内使用站点

这篇博客介绍了不同的AI聊天平台和定价信息,主要对比了ChatGPT和Claude两款产品。文章列出了多个提供聊天服务的AI平台,包括RawChat、ChatGPTPlus、ChatOpens、Wildcard和2233.ai等,并提供了每个平台的网页链接和定价详情。特别关注了Claude平台,介绍了其普通账号与会员账号的不同,以及相关的定价信息。此外,还提到了一些虚拟卡定价和服务细节,为用户选择合适的AI服务提供了多种参考。

nisidjndn的博客 1万+

AI人工智能领域多模态大模型的数字孪生应用

本文旨在系统性地介绍多模态大模型在数字孪生领域的最新应用进展,探讨其技术原理、实现方法和应用价值。内容范围涵盖从基础概念到前沿技术,从理论分析到实践案例。文章首先介绍多模态大模型和数字孪生的基本概念,然后深入分析两者的结合点,接着通过具体案例展示应用实践,最后探讨未来发展趋势和挑战。多模态大模型:能够同时处理和融合多种数据模态(如文本、图像、视频、音频等)的大型人工智能模型数字孪生:物理实体或系统的虚拟副本,通过实时数据同步实现虚实交互跨模态融合:将不同数据模态的信息进行有效整合和关联的技术。

AI云原生与云计算技术学院 1325

JAVAEE细细看 框架32 - SpringCloud 微服务(八) 网关 - Gateway

Gateway-概述 Spring Cloud Gateway是Spring官网基于Spring 5.0、 Spring Boot 2.0、Project Reactor等技术开发的网关服 务。 Spring Cloud Gateway基于Filter链提供网关基本功能:安全、监控/埋点、限流等。 Spring Cloud Gateway为微服务架构提供简单、有效且统一的API路由管理方式。 Spring Cloud Gateway是替代Netflix Zuul的一套解决方案。 Spring Cloud

ZMW_IOS的博客 639

Multisim 14.0元件库下载权威指南:官方与社区资源

详解Multisim元件库下载的官方途径与可靠社区资源,帮助用户快速获取Multisim 14.0所需元件模型,提升电路仿真效率。

weixin_32925455的博客 748

Java的新项目学成在线笔记-day17(十一)

.4.2 路由配置 在appcation.yml中配置: [AppleScript]纯文本查看复制代码 ? 1 2 3 4 5 6 zuul: routes: manage‐course: #路由名称,名称任意,保持所有路由名称唯一 path: /co...

czbkzmj的博客 274

服务器卡死排查实战:从现象分类到根因定位的完整手册

服务器卡死是运维中最令人头疼的故障之一,从游戏圈吐槽的“土豆服务器”到生产环境中的Linux系统无响应、rsync复制挂起,本质都是系统或进程失去正常响应能力。其原理通常涉及CPU、内存、磁盘IO或网络资源耗尽,以及内核和驱动异常。掌握系统化的排查方法,能够帮助工程师快速定位根因、缩短故障时间,避免盲目重启。本文从资源、层级、场景等多维度分类,结合top、dmesg、strace等工具,梳理一套从现象确认、指标收集、日志分析到进程定位的实战排查流程,并覆盖常见高频场景与预防治理建议。无论你是运维新手还是老手

weixin_33851177的博客 375

ZYNQ 网络通信的四种实现方案

一,ZYNQ MIO以RGMII 接口连接外部PHY芯片实现LWIP网络通信。 二,ZYNQ PS 的 ENET0/ENET1 通过 EMIO 的方式扩展至 PL,在 PL 中通过 RGMII 接口连接扩展子卡实现 LWIP 网络通信。 在PL中搭建了1个AXI 1G/2.5G Ethernet Subsystem以及1个AXI Direct Memory Access IP 核。这两个 IP核均通过AXI 总线经S_AXI_HP0 口与PS 连接,PS 通过AXI 总线对其进行配置和控..

wangjie36的博客 1万+

Java的新项目学成在线笔记-day17(十二)

4.5 过虑器Zuul的核心就是过虑器,通过过虑器实现请求过虑,身份校验等。4.5.1 ZuulFilter自定义过虑器需要继承 ZuulFilter,ZuulFilter是一个抽象类,需要覆盖它的四个方法,如下:1、 shouldFilter:返回一个Boolean值,判断该过滤器是否需要执行。返回true表示要执行此过虑器,否则不执 行。2、 run:过滤器的业务逻辑。3、 filter...

czbkzmj的博客 170

基于GEC6818开发板+Linux+Qt设计的智能养老院出入管理系统(195)

当前,传统的养老院出入管理模式普遍存在信息更新滞后、数据统计繁琐、实时监控能力不足等问题,难以满足现代养老机构对安全管理与服务质量提升的需求。本系统设计正是针对这些问题,通过集成先进的硬件设备如高性能GEC6818开发板以及RFID刷卡模块,结合稳定高效的嵌入式Linux操作系统和QT开发框架,构建一套功能齐全、操作直观、响应迅速的智能出入管理系统。 该系统能自动记录并实时显示老人的进出时间,方便管理人员及时了解每位老人的活动轨迹,同时具备强大的后台管理功能,支持管理员便捷地进行老人信息的增删改查操作。通

1069

Java的新项目学成在线笔记-day17(十三)

5 身份校验 5.1 需求分析 本小节实现网关连接Redis校验令牌: 1、从cookie查询用户身份令牌是否存在,不存在则拒绝访问 2、从http header查询jwt令牌是否存在,不存在则拒绝访问 3、从Redis查询user_token令牌是否过期,过期则拒绝访问 5.2 编码代码 1、配置application.yml 配置 redis链接参数: [AppleScr...

czbkzmj的博客 200

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

FOCUS模型:视觉-语言统一架构在多模态AI中的应用

多模态AI技术通过结合视觉与语言理解,实现了更智能的图像处理与生成。其核心原理在于将视觉信息与自然语言指令对齐,利用Transformer架构进行跨模态特征融合。这种技术在图像分割、编辑等任务中展现出显著优势,如提升处理精度和保持上下文一致性。FOCUS模型作为突破性解决方案,通过端到端训练和双分支视觉编码器设计,有效解决了传统多模型串联的割裂问题。在电商图像编辑、影视后期等应用场景中,该技术大幅提升了效率与质量,成为AI工程实践的重要突破。

weixin_33748818的博客 1040

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

Java的新项目学成在线笔记-day17(一)

1.1 用户认证流程分析 用户认证流程如下: 业务流程说明如下: 1、客户端请求认证服务进行认证。 2、认证服务认证通过向浏览器cookie写入token(身份令牌) 认证服务请求用户中心查询用户信息。 认证服务请求Spring Security申请令牌。 认证服务将token(身份令牌)和jwt令牌存储至redis中。 认证服务向cookie写入 token(身份令牌)。 3、前端携带tok...

czbkzmj的博客 184

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

Spring Cloud教程 | 第六篇:路由网关(Zuul的路由转发及过滤器用法)

题记: 在微服务架构中,需要几个基础的服务治理组件,包括服务注册与发现、服务消费、负载均衡、断路器、智能路由、配置管理等,由这几个基础组件相互协作,共同组建了一个简单的微服务系统。一个简单的微服务系统如下图: 在Spring Cloud微服务系统中,一种常见的负载均衡方式是,客户端的请求首先经过负载均衡(zuul、Ngnix),再到达服务网关(zuul集群),然后再到具体的服。服务统...

刘军的博客 9019

Java的新项目学成在线笔记-day1(三)

3.3 CMS页面管理需求 1、这些页面的管理流程是什么?1)创建站点:一个网站有很多子站点,比如:学成在线有主门户、学习中心、问答系统等子站点。具体的哪个页面是归属于具体 的站点,所以要管理页面,先要管理页面所属的站点。2)创建模板:页面如何创建呢?比如电商网站的商品详情页面,每个页面的内容布局、板式是相同的,不同的只是内容,这个页 面的布局、板式就是页面模板,模板+数据就组成一个完整的页面,最...

weixin_33969116的博客 221

12.微服务与分布式—2.SpringCloud

1.系统架构演变 随着互联网的发展,网站应用的规模不断扩大,需求的激增,随之而来的是技术上的压力。系统架构也因此不断的演进、升级、迭代。从单一应用,到垂直拆分,到分布式服务,到SOA,以及现在火热的微服务架构。 1.1. 集中式架构 当网站流量很小时,只需要一个应用,将所有的功能都部署在一起,以减少部署节点和成本。 优点: 系统开发速度快 维护成本低 适用于并发要求较低的系统 缺点: 代码耦合度高,后期维护困难 无法针对不同模块进行优化 无法水平扩展 单点容错率低,并发能力差

不放手,不错过的博客 762

布隆过滤器与一致性哈希算法

五、一致性哈希的基本概念 一致性Hash算法也是使用取模的方法,只是,刚才描述的取模法是对服务器的数量进行取模,而一致性Hash算法是对2^32取模,什么意思呢?简单来说,一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为0-2^32-1(即哈希值是一个32位无符号整形),整个哈希环如下: 整个空间按顺时针方向组织,圆环的正上方的点代表0,0点右侧的第一个...

wwxy1995的博客 629

微服务[学成在线] day20:项目总结

???? 写在前面 我为什么要花时间去写这个笔记? 由于在学习该项目的过程中发现了原讲义存在的一些问题,所以该笔记基于「学成在线」微服务项目 PDF 讲义进行编写,并且投入了时间去优化了笔记的格式、代码的高亮、重点的标记等。 以及一些原讲义中所描述的一些知识点使我无法理解的内容,我会对这些内容的表达方式进行修改或者提出一些问题,并且用我自己所理解的一些想法去重新的解释这个问题。 总结一下原 PDF 讲义中已知的一些问题: 从 PDF 中复制出来的代码,部分特殊符号的编码有问题,并且不易被发现,例如横杠

通往体面生活的路上 3261

微服务[学成在线] day18:基于oauth2实现RBAC认证授权、微服务间认证实现

???? 知识点概览 为了方便后续回顾该项目时能够清晰的知道本章节讲了哪些内容,并且能够从该章节的笔记中得到一些帮助,所以在完成本章节的学习后在此对本章节所涉及到的知识点进行总结概述。 本章节为【学成在线】项目的 day18 的内容 基于方法的权限校验 基于 RBAC 进行用户权限配置以及动态查询。 根据教师所属的公司来实现课程信息查询的细粒度授权。也就是 A 公司的老师只能查询到 A 公司下的课程。 使用 Feign 拦截器实现获取前端请求中的 header 信息,并将 header 中带有

通往体面生活的路上 2004

微服务[学成在线] day13:使用FFmpeg进行格式转换以及m3u8文件生成、文件分块上传接口实现

???? 知识点概览 为了方便后续回顾该项目时能够清晰的知道本章节讲了哪些内容,并且能够从该章节的笔记中得到一些帮助,所以在完成本章节的学习后在此对本章节所涉及到的知识点进行总结概述。 本章节为【学成在线】项目的 day13 的内容 FFmpeg 的基本使用 使用 m3u8 和 video.js技术实现视频的在线播放 搭建媒资服务工程实现文件的分块储存 目录 内容会比较多,小伙伴们可以根据目录进行按需查阅。 文章目录???? 知识点概览目录一、在线学习需求分析0x01 需求描述0x02 视频点

通往体面生活的路上 1724

微服务[学成在线] day10:课程发布、ElasticSearch

???? 知识点概览 为了方便后续回顾该项目时能够清晰的知道本章节讲了哪些内容,并且能够从该章节的笔记中得到一些帮助,所以在完成本章节的学习后在此对本章节所涉及到的知识点进行总结概述。 本章节为【学成在线】项目的 day10 的内容 课程发布功能开发 ElasticsSearch 安装部署 ElasticsSearch 快速入门、IK 分词器、映射、索引。 目录 内容会比较多,小伙伴们可以根据目录进行按需查阅。 文章目录???? 知识点概览目录一、课程发布0x01 需求分析0x02 CMS一键

通往体面生活的路上 1205

微服务[学成在线] day05:消息中间件 RabbitMQ

???? 知识点概览 为了方便后续回顾该项目时能够清晰的知道本章节讲了哪些内容,并且能够从该章节的笔记中得到一些帮助,所以在完成本章节的学习后在此对本章节所涉及到的知识点进行总结概述。 本章节为【学成在线】项目的 day05 的内容 RabbitMQ 的基本应用场景 使用原生RabbitMQ 库构建生产者与消费者模型 整合 Springboot 实现 RabbitMQ 生产者与消费者模型 目录 内容会比较多,小伙伴们可以根据目录进行按需查阅。 文章目录???? 知识点概览目录一、需求分析二、初

通往体面生活的路上 846

微服务[学成在线] day20:项目部署与持续集成(DevOps)

???? 知识点概览 为了方便后续回顾该项目时能够清晰的知道本章节讲了哪些内容,并且能够从该章节的笔记中得到一些帮助,所以在完成本章节的学习后在此对本章节所涉及到的知识点进行总结概述。 本章节为【学成在线】项目的 day20 的内容。 原讲义中对该章节的 gitlab 与 jenkins 安装步骤以及部署容器的内容和步骤描述得不够详细,在本文中做出了补充,详细的描述每个步骤。 本章节建议还是尝试看笔记来完成一次操作,然后再去看视频资料,这样能理解得更深刻。 目录 内容会比较多,可以根据目录进行按

通往体面生活的路上 812
上一篇: 微服务[学成在线] day16:基于Spring Security Oauth2开发认证服务
下一篇: 微服务[学成在线] day18:基于oauth2实现RBAC认证授权、微服务间认证实现
cory_lin
博客等级 码龄7年 22粉丝 24原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值