权限验证框架之Shiro

文章目录

  • 前言
  • shiro 核心
  • 项目构建
    • 默认Session模式
      • 配置
      • 测试接口
      • Realm编写
      • 权限测试
        • 无权限测试
        • 登录测试
        • 权限测试
    • 前后端分离token
      • JWTFilter
      • 重写认证
      • 修改配置
  • 总结

前言

交替换个脑子,一直搞考研的东西,实在是无聊。所以顺便把工程上的东西,拿来遛一遛。你问我,为啥不是机器学习,深度学习,那玩意搞起来头更大,累了。权当是打游戏放松了,那么废话不多说,这里要玩玩的是Shiro,其实一开始我还是喜欢玩这个Security,不过后来,经常用这个人人开源,也就接触这个玩意了,说实话,先前用那个玩意的时候,也是习惯性的把shiro改成security,但是实话实说,太麻烦了,懒得改,所以的话,干脆就是直接使用这个Shiro。

当然关于权限验证,其实我们自己基于RBAC权限管理模型直接做一套都是可以的,基于Spring的AOP,快速做一个简单的这个是非常快的。包括,当初我写的那个WhitHoleV0.7版本其实那个用户端的权限验证都是自己做的。ok,说多了,我们来快速开始吧。

当然自己动手实现一个权限验证其实也不难,shiro只是提供了一个架子而已。后面有时间的话,我们可以直接自己写一个Shiro lite 或者security lite拿过来玩玩。

shiro 核心

ok,我们开始,首先的话,这个shiro由如下模块组成:
在这里插入图片描述

Authentication:身份认证/登录,验证用户是不是拥有相应的身份

Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能进行什么操作,如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限

Session
Management:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境,也可以是Web
环境的

Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储

Web Support:Web 支持,可以非常容易的集成到Web 环境

Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率

Concurrency:Shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去

Testing:提供测试支持

“Run As”:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问

Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了

从我们的使用角度来看,它的运行流程大致如下:
在这里插入图片描述
也是分为几个部分:

  1. subject:这个是对User信息的一些封装
  2. SecurityManager: 里面实现了对用户信息授权,认证的一些操作
  3. Realm: 和数据库打交道,比如验证用户权限,这个我们需要查表,那么这个时候,我们就需要这个玩意

也就是说,subject过来之后,通过Manager,去执行对于的执行权限的方法,在进行用户验证的时候,将使用到Realm,去读取数据,之后完成操作。

项目构建

默认Session模式

现在虽然比较流行的是这个前后端分离架构,用的是token,但是,很久以前,还没有分离的时候,还是用的这个,

那么在这边进行整合的时候是这样的:
在这里插入图片描述
然后我们导入一下,配置,我这里的话,还导入了这个web starter。这里做演示,我就不建表了。

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--引入shrio-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
        </dependency>


        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

配置

那么我们先来看到配置,看看我们看到了流程图,我们其实发现,就是说,我们的请求其实是首先到了一个过滤器,然后在这个过滤器里面进行操作,拦截的,完成权限的认证的。然后,刚刚也说到,完成认证是这样的:

  1. 拦截到请求
  2. 进入到安全管理器
  3. 管理器负责调度对应的认证,其中我们要使用到Realm,去完成这个从数据库,或者说是认证的具体实现。
  4. 然后就是责任链一路放行,比如验证成功,一路放行到资源,如果失败,就怎么怎么样,这里面有一套操作,我们通过传入到下一层的状态,来判断当前的处理器,要不要处理,然后一条链路走下来,直到走完,或者提前结束。

那么其实都说到这里了,没有接触过Shiro但是,项目写多了的朋友,都看到这个份上了,估计手写一个dome都可以了(真的!)当然,里面还是有很多的一些细节不一样是吧,但是大体大致的一定可以写出来了。

所以,首先,我们要用,就需要先写个Realm.

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

/**
 * 自定义Realm
 */
public class CustomerRealm extends AuthorizingRealm {
    //实现授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
    //实现认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        return null;
    }
}

然后呢,我们还要写过Config,给到容器,这里的话,我们使用了这个shiro-starter。所以写完Config之后的话,我们可以就是说可以和SpringBoot一起启动,或者一起注入到Servlet里面,完成运行。

package com.huterox.shirodome.config;

import com.huterox.shirodome.Shiro.CustomerRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {
    //ShiroFilter过滤所有请求
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //给ShiroFilter配置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //配置那些需要放行,需要拦截,需要怎么怎么样
        /*这里有对应的注解
        *anon:无需认证就可以访问
        *authc:必须认证了才能让问
        *user:必须拥有记住我功能才能用
        *perms:拥有对某个资源的权限才能访问[角色:操作];
        *roLe:拥有某个角色权限才能访问[角色]
    
        * @RequiresAuthentication:必须经过认证才能访问
          @RequiresUser:必须经过认证,并且有记住我功能才能访问
          @RequiresPermissions("permission:operation"):需要拥有指定权限才能访问,其中permission为资源名,operation为操作名
          @RequiresRoles("roleName"):需要拥有指定角色(roleName)才能访问
        * */
        Map<String, String> map = new HashMap<String, String>();
        map.put("/hello","anon");
        map.put("/admin","authc");
//        map.put("/superAdmin","perms[s:p]");
        //去登陆接口,没有通过验证进入
        shiroFilterFactoryBean.setLoginUrl("toLogin");
        shiroFilterFactoryBean.setUnauthorizedUrl("/noauthor");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }
    //创建安全管理器
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        return securityManager;
    }
    //创建自定义Realm
    @Bean
    public Realm getRealm() {
        CustomerRealm realm = new CustomerRealm();
        return realm;
    }
    // 对Shiro注解的支持
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;

    }
}


测试接口

ok,那么看完了这个,我们来看到,我们这边准备了那些接口。

package com.huterox.shirodome.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
public class ShiroHelloController {

    @RequestMapping("/hello")
    public String hello(){
        return "Hello";
    }

    @RequestMapping("/admin")
    public String admin(){
        return "Admin";
    }

    @RequestMapping("/toLogin")
    public String toLogin(){
        return "toLogin";
    }

    @RequestMapping("/noauthor")
    public String noauthor(){
        return "木有权限";
    }


    @RequestMapping("/superAdmin")
    @RequiresPermissions("s:p")
    public String SP(){
        return "高贵的SP你好";
    }



    @RequestMapping("/login")
    public String login(String username,String password){
        //获取到用户对象,并且封装起来,方便后面shiro使用
        Subject subject = SecurityUtils.getSubject();
//        如果这里还要采用md5”加密“的话
//        String salt= "Huterox";
//        String passwordSalt = new SimpleHash("MD5", password, salt, 2).toString();
//        UsernamePasswordToken token = new UsernamePasswordToken(username,passwordSalt);
        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
        try {
            subject.login(token);
            System.out.println("登录成功!!!");
            return "OK";
        } catch (UnknownAccountException e) {
            System.out.println("用户错误!!!");
        } catch (IncorrectCredentialsException e) {
            System.out.println("密码错误!!!");
        }
        return "NO";
    }
}

因为我们这边是在做认证和授权,所以的话,我们这边有,公共接口,登录接口,拥有特殊授权才能访问的接口。当然还有未授权返回的接口。

Realm编写

在我们的这个Shiro当中,最重要的其实就是这个玩意的实现,在这里,我们要完成的是用户的Authentication和Authorization。

在这里的话,我们这边有两个角色,一个是admin,还有一个是superAdmin。密码都是admin。其他的话,在代码里面有很详细的注释:

package com.huterox.shirodome.Shiro;

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

/**
 * 自定义Realm
 */
public class CustomerRealm extends AuthorizingRealm {
    //实现授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("授权当中");
        String userName = (String) principalCollection.getPrimaryPrincipal();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        if(userName.equals("superAdmin")){
            //只有SuperAdmin才有S:P权限
            info.addStringPermission("s:p");
            //添加角色也可以
//            info.addRole("s");
        }else {
            info.addStringPermission("");
        }
        return info;
    }
    //实现认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("认证当中");
        UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
        //注意,这里假设的是查表得到的username,password,可能是加密了的
        String username = "admin";
        String password = "admin";
        if (token.getUsername().equals(username) || token.getUsername().equals("superAdmin")) {
            //这里完成密码匹配,内部会进行处理,一般情况下,获取到的password是明文,或者“自欺欺人”前端对称加密后的东西
            //所以,在controller里面,我们要在加密一下,然后,和这里从数据库里面的password进行对比
            //ByteSource credentialsSalt = ByteSource.Util.bytes("Huterox");//上面添加账号时候生成的加密盐
            //SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username,password,credentialsSalt, getName());
            //这里我把token.getUsername()传入进去了,实际上,你可以传入任何对象,然后,接下来在授权部分获取到,这个玩意
            //进入下一步的解析
            return new SimpleAuthenticationInfo(token.getUsername(),password,"");
        }
        return null;
    }
}

权限测试

ok,现在我们的dome,代码写完了,那么接下来,我们要来看看这个具体的执行过程吧。

无权限测试

首先我们来看到的是第一个接口。

    @RequestMapping("/hello")
    public String hello(){
        return "Hello";
    }

在配置里面,我们写了这个:
在这里插入图片描述
这个玩意是不需要权限的,所以,此时我们进行一个访问:
在这里插入图片描述
一切正常。

登录测试

那么现在,我们来访问admin接口,这个接口,是需要登录才能访问的,现在不登录,进行访问。
在这里插入图片描述
此时发现这里需要进入登录页面。这里我没有写html,懒得写了,就给了个提示。

在这里,我们配置了没有登录要调用的接口,和没有授权,或者权限不够要调用的接口
在这里插入图片描述

现在,我们登录一下:
在这里插入图片描述
登录成功,那么接下来,我们再访问一下:
在这里插入图片描述
可以看到成功。

权限测试

ok,接下来,我们来看到权限测试。
现在我们去访问需要超级管理员才能访问的页面。
在这里插入图片描述
这里报错了,在终端可以看到是没有权限的错:
在这里插入图片描述

这里需要注意的是,我在接口处使用的是注解模式,如果你是在配置里面写好了:
在这里插入图片描述
那么就可以跳转到/noauthor里面。
这个时候,我们就需要使用到全局异常处理器了,拦截这些Controller的错误,这里还是在Session模式下,还不是用token的,也就是前后端分离的,所以这里这样很正常。
现在登录超级管理员:
在这里插入图片描述
可以看到一切正常:
在这里插入图片描述

前后端分离token

现在我们来用用前后端分离的,这里的话,我们需要做的就是结合jwt,然后进行处理了,操作和security是类似的,其实。

我们要做的其实就是在基础上集成JWT。

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.2.0</version>
</dependency>

然后的话,我们修改一下过滤器。
这里JWT是啥,怎么用,后面怎么用我就不说了,这个需要结合你实际的项目,而且默认你是有基础的,只是想要玩玩shiro,而已。

JWTFilter

@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter {
    // 如果请求头带有token,则对token进行检查;否则,直接放行
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        // 判断请求头是否带有 token
        if (isLoginAttempt(request, response)) {
            // 如果存在 token ,则进入executeLogin()方法执行登入,并检测 token 的正确性
            try {
                executeLogin(request, response);
            } catch (Exception e) {
                log.error("Error! {}", e.getMessage());
                responseError(response, e.getMessage());
            }
        }
        // 如果不存在 token ,则可能是执行登录操作/游客访问状态,所以直接放行
        return true;
    }

    // 检测 header中是否包含 token
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        return getTokenFromRequest(request) != null;
    }

   // 执行登入操作
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        String token = getTokenFromRequest(request);
        JwtToken jwtToken = new JwtToken(token);
        // 提交给 realm 进行登入,如果错误,会抛出异常并捕获
        getSubject(request, response).login(jwtToken);
        // 如果没有抛出异常,则代表登入成功,返回 true
        return true;
    }

    // 从请求中获取 token
    private String getTokenFromRequest(ServletRequest request) {
        HttpServletRequest req = (HttpServletRequest) request;
        return req.getHeader("Token");
    }

    // 非法请求将跳转到 "/unauthorized/**"
    private void responseError(ServletResponse response, String message) {
        try {
            HttpServletResponse resp = (HttpServletResponse) response;
            // 设置编码,否则中文字符在重定向时会变为空字符串
            message = URLEncoder.encode(message, "UTF-8");
            resp.sendRedirect("/noauthori/" + message);
        } catch (UnsupportedEncodingException e) {
            log.error("Error! {}", e.getMessage());
        } catch (IOException e) {
            log.error("Error! {}", e.getMessage());
        }
    }
}

这里的话,我们还可以再对JwtToken封装一下,方便后面拿东西。

public class JwtToken implements AuthenticationToken {
    private String token;
    
    public JwtToken(String token) {
        this.token = token;
    }
    
    @Override
    public Object getPrincipal() {
        return token;
    }
    @Override
    public Object getCredentials() {
        return token;
    }
}

重写认证

 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
       
        // 这里的 token从 JWTFilter 的 executeLogin() 方法传递过来,先前我们封装了jwttoken
        //如果验证通过,我们把这个JwtToken往下传递了
        String token = (String) authenticationToken.getCredentials();
     	//然后这里还是查表那一套

        return new SimpleAuthenticationInfo(token, token, getName());
    }

同样的授权也是一样的。

那么之后的话,我们的流程就是,登录完之后,前端拿到token,我们设置需要验证的地方,就会通过我们的过滤器,然后执行这一套逻辑。

修改配置

最后我们重新修改配置:


@Configuration
public class ShiroConfig {

    //ShiroFilter过滤所有请求
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //给ShiroFilter配置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //配置那些需要放行,需要拦截,需要怎么怎么样
        /*这里有对应的注解
        *anon:无需认证就可以访问
        *authc:必须认证了才能让问
        *user:必须拥有记住我功能才能用
        *perms:拥有对某个资源的权限才能访问[角色:操作];
        *roLe:拥有某个角色权限才能访问[角色]

        * @RequiresAuthentication:必须经过认证才能访问
          @RequiresUser:必须经过认证,并且有记住我功能才能访问
          @RequiresPermissions("permission:operation"):需要拥有指定权限才能访问,其中permission为资源名,operation为操作名
          @RequiresRoles("roleName"):需要拥有指定角色(roleName)才能访问
        * */

        // 设置自定义的拦截器
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        filterMap.put("jwt", new JwtFilter());
        shiroFilterFactoryBean.setFilters(filterMap);

        Map<String, String> map = new HashMap<String, String>();
        map.put("/hello","anon");
        map.put("/admin","authc");
//        map.put("/superAdmin","perms[s:p]");
        //去登陆接口,没有通过验证进入
        shiroFilterFactoryBean.setLoginUrl("/toLogin");
        shiroFilterFactoryBean.setUnauthorizedUrl("/noauthor");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

    //创建安全管理器
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        // 关闭 shiro 自带的 session
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator evaluator = new DefaultSessionStorageEvaluator();
        evaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(evaluator);
        securityManager.setSubjectDAO(subjectDAO);
        return securityManager;
    }
    //创建自定义Realm
    @Bean
    public Realm getRealm() {
        CustomerRealm realm = new CustomerRealm();
        return realm;
    }


    // 对Shiro注解的支持
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;

    }


}


总结

okey,这些就是全部内容了。没啥东西其实,就是简单换个脑子,过过。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/28980.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

探索Redis内部数据结构

Redis支持多种数据结构&#xff0c;每种数据结构都有其特定的用途。下面对Redis支持的主要数据结构进行详细阐述&#xff1a; 一、字符串&#xff08;String&#xff09; 字符串是Redis最基本的数据结构&#xff0c;可以存储一个字符串或者二进制数据&#xff0c;例如图片、序…

HID协议学习

HID协议学习 0. 文档资料 USB_HID协议中文版_USB接口HID设备_AUJsRmB9kg.pdf HID报告描述符精细说明_mgCxM8_ci9.pdf hut1_22_U3cvnwn_ZZ.pdf 1. 基本概念 HID协议是一种基于USB的通讯协议&#xff0c;用于在计算机和输入设备之间进行数据传输。HID协议定义了标准的数据格…

如何实现在线书签内容替换

书签广泛应用于企业的各种办公自动化业务场景中。例如&#xff1a;在范式合同模板中将甲乙方书签自动替换成具体的公司名称&#xff1b;在红头文件模板中将红头标题书签替换成具体的行政指令&#xff1b;在各种协议模板中将协议日期书签替换为当前日期&#xff1b;等等。 在这…

【Elacticsearch】 原理/数据结构/面试经典问题整理

对Elacticsearch 原理/数据结构/面试经典问题整理的文章&#xff1b; 映射 | Elasticsearch: 权威指南 | Elastic Elacticsearch介绍 Elasticsearch,这里简称ES。ES是一个开源的高可用高扩展的分布式全文搜索与分析引擎&#xff0c;可以提供PB级近实时的数据存储和检索能力&am…

《离散数学》:集合、关系和函数

〇、前言 这章将会对集合、以及集合之上的关系、以及两个集合之间的映射情况做一个细致的讨论。集合作为数学和其他领域中的基础概念&#xff0c;具有广泛的应用和重要的地位。它为数学建立了基本的体系和推理方法&#xff0c;为各个领域的研究和应用提供了一种统一的描述和分…

基于web漏洞扫描及分析系统设计_kaic

基于web漏洞扫描及分析系统设计 摘 要 随着信息技术的发展和网络应用在我国的普及&#xff0c;针对我国境内信息系统的恶意网络攻击也越来越多&#xff0c;并且随着黑客攻击技术的不断地更新&#xff0c;网络犯罪行为变得越来越难以应对&#xff0c;用户日常访问的网站是否安全…

Mysql主从复制及读写分离

&#x1f353; 简介&#xff1a;java系列技术分享(&#x1f449;持续更新中…&#x1f525;) &#x1f353; 初衷:一起学习、一起进步、坚持不懈 &#x1f353; 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正&#x1f64f; &#x1f353; 希望这篇文章对你有所帮助,欢…

LaTeX插入参考文献

接着上一篇&#xff0c;用EndNote组织参考文献&#xff0c;然后再导入到LeTex中感觉不太好用&#xff0c;然后就学习了一下BibTeX来管理参考文献&#xff0c;发现还可以&#xff0c;这里记录一下&#xff0c;方便以后查阅。 LaTeX插入参考文献 thebibliographyBibTeX参考资料 t…

前端 sentry 接入钉钉机器人

sentry 接入钉钉机器人 打开钉钉,添加机器人 此时会得到Webhook地址,记录一下,以后会用到 sentry 端设置 看看这里有木有钉钉插件,有的话开启插件,并配置这里我说一下没有的情况下,我们何如设置 这里需要填写webhook url 这个的url 需要是一个公网的地址,不可以是本地…

使用Unity开发一个独立的区块链

Arouse Blockchain [Unity独立区块链] ❗️千万别被误导&#xff0c;上图内容虽然都在项目中可寻&#xff0c;但与目前区块链的业务代码关联不大&#xff0c;仅供宣传作用(总得放些图看着好看)。之所以有以上内容是项目有个目标功能是希望每个用户在区块链上都有一个独一无二的…

View UI Plus (iview)表格单选实现教程

View UI Plus 是 View Design 设计体系中基于 Vue.js 3 的一套 UI 组件库&#xff0c;主要用于企业级中后台系统 View UI&#xff0c;即原先的 iView&#xff0c;从 2019 年 10 月起正式更名为 View UI&#xff0c;并使用全新的 Logo View UI Plus 实现表格单选&#xff0c;这…

首次使用云服务器搭建网站(二)

书接上文&#xff0c;我们已经完成了服务器的租赁&#xff0c;宝塔面板的下载与安装。 接下来我们将正式开始网站搭建。 一、网站创建 点击网站、添加站点 输入网站域名、数据库选择MySQL数据库&#xff0c;选择utf8&#xff0c;数据库账号密码会自动生成。无论你要创建什么样…

互联网行业-镭速文件传输系统方案

互联网行业是一个快速变化和高度竞争的行业&#xff0c;这一行业需要传输大量的数据、代码和文件。在互联网企业的生产和运营过程中&#xff0c;需要传输各种敏感和大型的文件&#xff0c;例如业务报告、数据分析、软件代码等。这些文件需要在不同的部门、不同的地点之间高效地…

用敏捷工具Leangoo领歌做敏捷需求管理

传统的瀑布工作模式使用详细的需求说明书来表达需求&#xff0c;需求人员负责做需求调研&#xff0c;根据调研情况编制详细的需求说明书&#xff0c;进行需求评审&#xff0c;评审之后签字确认交给研发团队设计开发。在这样的环境下&#xff0c;需求文档是信息传递的主体&#…

小雉系统U盘安装包制作

​ 本文原地址: http://www.feitianzhi.com/boke/index.php/archives/57/ 概述 小雉系统可从线上系统制作安装包到U盘&#xff0c;制作的安装包可用于新系统的安装&#xff1b; 小雉系统只提供升级包&#xff0c;对应的安装包均是客户在应用升级包后按本文或http://www.f…

vite预渲染 vite-plugin-prerender 大坑记录

本文部分配置转自&#xff1a;vite预渲染怎么实现_猿耳盗铃的博客-CSDN博客 懒得重新写&#xff0c;贴下版本和自己踩的各种坑吧 以下为版本&#xff0c;本文只给vite vue3的建议&#xff0c;不一定适用&#xff0c;因为正常情况能build成功&#xff0c;我昨天中午之前一直没…

招商基金资深架构师教你如何搭建统一监控平台

随着数字化进程的加速和业务的高速发展&#xff0c;系统的复杂程度日益升级&#xff0c;为确保业务系统的连续性和稳定性&#xff0c;越来越多的企业想要建设统一的监控平台&#xff0c;但却不知道从哪里开始着手。比如&#xff1a; 有些企业会直接将监控系统页面集成到统一监…

Jmeter实现Dubbo接口测试

目录 前言&#xff1a; 一、准备 二、编写我们的测试工程 三、Jmeter来测试这个工程 前言&#xff1a; JMeter可以用来测试Dubbo接口的性能和负载。Dubbo是阿里巴巴的高性能RPC框架&#xff0c;常用于分布式服务的调用。为了测试Dubbo接口&#xff0c;需要使用JMeter提供的…

为什么说2023年最难招聘的岗位是高性能计算工程师?

随着毕业季的临近&#xff0c;高校毕业生将进入就业关键阶段。据统计&#xff0c;2023届全国高校毕业生预计达到1158万人&#xff0c;同比增加82万人&#xff0c;再创新高。尽管有千万的大学毕业生&#xff0c;但是企业反馈依然很难招聘到合适的高性能计算工程师。 这主要归因于…

「OceanBase 4.1 体验」OceanBase:解读领先的分布式数据库系统,功能与体验全解析

文章目录 前言一、关于 【OceanBase 4.1】征文活动&#xff08;可跳过&#xff09;二、OceanBase 产品了解2.1 初识 OceanBase2.2 什么是 OceanBase2.3 OceanBase 相关链接2.4 OceanBase 与传统数据库对比有何特别之处2.5 OceanBase 相关概念以及术语2.5.1 OceanBase 基本概念2…