使用自定义过滤器-Filter--实现对权限的控制

2023-11-10

提示:大牛大佬们就没必要垂阅了,如果很幸运的被大佬翻牌了,也希望能够给出指教。3Q

关于权限控制即包含功能权限+数据权限

我们使用的方式可谓多种多样:通过自定义注解编写AOP方式或是使用第三方提供好的框架如 shiro 或 springsecurity 等。这里总结的是我自己实际项目开发中使用的自定义过滤器的方式实现权限控制(认证服务会分开说)。

为什么要使用过滤器而不使用拦截器

说明,实际两者都是可以实现权限过滤与控制,并无较大的实际区别。
intercept(拦截器)是基于反射机制实现,应用功能比较全面,访问 action 上下文对象,获取 spring 容器中的对象,在 action 生命周期中可以调用多次。这些都是 filter 无法做到或者只能做一次的。但是 filter 的有点在于可以拦截几乎一切请求,这取决于我们的配置(以往有.xml文件时候的path配置如:/,现在springboot使用注解方式的配置 @WebFilter(urlPattens="/")道理相同)。而拦截器只限拦截 action 请求,基于这点我们选择适用性更强的 Filter。

本次是微服务项目 基于springcloud+springboot+springMVC+Mybatis-Plus等技术
简单说明拦截流程

就是请求进入后台服务器之前需要先去认证服务器进行认证。通过token认证后方可继续向下执行。
我们的 filter 就是在认证之后到后台服务之间;
拦截范围是 /* (全部请求)
下面是自定义的拦截器代码

@Component
@WebFilter(urlPatterns = "/*", filterName = "ruleFilter")
public class RuleFilter implements Filter {

// 注入缓存Redis
@Autowired
private RedisUtil redisUtil;

@Override
public void init(FilterConfig filterConfig) throws ServletException {
}


@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
    // 首先转换 request 与 response
    HttpServletRequest request = (HttpServletRequest) servletRequest;
    HttpServletResponse response = (HttpServletResponse) servletResponse;

    RuleService ruleService = SpringContextHolder.getBean(RuleService.class);

    // 获取请求的URL
    String servletPath = request.getServletPath();

    // 获取请求方式 POST 需要使用流操作
    String reqMethod = request.getMethod();

    ServletRequest requestWrapper = new RuleFilterHttpServletRequestWrapper(request);

    // 判断URL权限池是否包含 当前访问的URL 如果包含 则需要判断权限 如果不包含则不需要权限访问
    boolean ruleURLList1 = redisUtil.hasKey("ruleURLList");

    List<String> ruleURLList = new ArrayList<>();

    if (ruleURLList1){

        Object ruleURLs = redisUtil.get("ruleURLList");
        if (null != ruleURLs){
            ruleURLList = (List<String>) ruleURLs;
        }
    }

    // 判断如果ruleURLList为空需要重新查询
    if (!ruleURLList1 || 0 == ruleURLList.size()){

        // 重新查询
        List<String> strings = ruleService.finAllRuleUrl();

        // 放入Redis缓存中
        redisUtil.set("ruleURLList", strings, 60*60*24*7);

        ruleURLList = strings;
    }

    if (ruleURLList.contains(servletPath)) {


        // 需要验证访问权限 两种情况 GET 或者 POST 请求
        if ("POST".equals(reqMethod)) {
            PrintWriter out = null;
            HttpServletResponse responseSecond = (HttpServletResponse) response;
            responseSecond.setCharacterEncoding("UTF-8");
            responseSecond.setContentType("application/json; charset=utf-8");


            String body = HttpHelper.getBodyString(requestWrapper);

            //如果是POST请求则需要获取 param 参数
            String param = URLDecoder.decode(body, "utf-8");

            //json串 转换为Map
            if (param != null && param.contains("=")) {
                param = param.split("=")[1];
            }
            
            Map paramMap = (Map) JSONUtils.parse(param);
            String obj_adminId =  String.valueOf(paramMap.get("adminId"));
            System.out.println("获取到的用户id为"+obj_adminId);

			/**
		     * 调用判断用户是否拥有权限方法
		     * obj_adminId 操作用户的id(其它属性也可 是缓存中的 key 唯一标识)
		     * servletPath 请求路径(每个业务方法的path都不同 以此区分权限)
		     * redisUtil 缓存对象(这里有一点下面有将 即怎样在filter中注入我们的缓存类呢)
		     * ruleService 权限的业务层接口对象
		     */
            Boolean falg = criteriaUserRule(obj_adminId, servletPath, redisUtil, ruleService);


            // 判断如果此集合包含当前servletPath则说明用户拥有此权限
            if (falg) {
                // 直接放行
                chain.doFilter(requestWrapper, response);
            } else {
                //  抛出自定义异常
                throw new NoRuleException(401, "没有访问权限");
            }

        } else if ("GET".equals(reqMethod)) {

            // 
            String adminid = request.getParameter("adminId");

            // 调用判断用户是否拥有权限方法
            Boolean falg = criteriaUserRule(adminid, servletPath, redisUtil, ruleService);

            // 判断如果此集合包含当前servletPath则说明用户拥有此权限
            if (falg) {
                // 直接放行
                chain.doFilter(request, response);

            } else {
                // 不包含说明此用户无访问当前RUL的权限 拦截并改写响应response 前面已经设置响应json格式

                throw new NoRuleException(401, "没有访问权限");
            }
        }

    } else {
        // 不需访问权限 直接放行
        chain.doFilter(requestWrapper, response);
    }

}


/**
 * 判断REDIS缓存中 此用户id是否拥有当前权限
 * 注意:如果当前Redis缓存中没有当前用户的key 实际我们应该重新查询当前用户key对应的RulesURLs 并设置时间重新放入Redis中及返回对应的Boolean
 */
public Boolean criteriaUserRule(String adminId, String url, RedisUtil redisUtil, RuleService ruleService){

    // 判断内存是否拥有当前用户权限URL集合 如果拥有直接对比 如果未拥有或者已过期 则重新查询并放入
    boolean fl = redisUtil.hasKey(adminId);
    List<String> userRuleUrls = new ArrayList<>();
    
    if (fl){
        Object obj = redisUtil.get(adminId);
        if (null == obj || "" == obj){
            fl = false;
        } else {
            userRuleUrls = (List<String>)obj;
        }
    }

    if (!fl){
        // 重新查询 并放入缓存中
        userRuleUrls = ruleService.findAllUrlByUserId(Integer.parseInt(adminId));

        redisUtil.set(adminId, userRuleUrls, 60*60*24);

        System.out.println("用户的全部权限URL是"+userRuleUrls);
    }

    // 判断如果此集合包含当前servletPath则说明用户拥有此权限
    if (userRuleUrls.contains(url)) {

        // 拥有操作权限
        return true;
    } else {
        // 没有操作权限
        return false;
    }
}

@Override
public void destroy() {

}
  }
博主在这里遇到了几个麻烦,也可能会有其他人遇到,所以拿出来说一下。
第一个问题就是关于怎样在 filter 中注入特定对象

如我们自己写的 RedisUtile (缓存操作对象) 或者我们需要进行数据库操作而使用的 UserService 等对象,如果我们使用 spring 提供的 @AutoWired 注解直接进行注入

public class RuleFilter implements Filter {
	@AutoWired
	private UserService userService;
}

使用此方式会发现,当我们使用 userService 对象时,此对象为 null 导致报空指针,无法继续向下进行操作。经过查询得知其实是在filter初始方法执行的时候,spring容器中还没有相关bean对象。如何解决呢?找了很多帖子、博客,有说在初始化方法中注入bean的,也有说先获取bean工厂,再获取bean。逻辑上这些都可行,但在博主的项目中却都行不通。最后,哈哈就是他
解决spring管理之外的类需要获取spring容器中的bean(非常感谢<作者:江南烟雨>)
通过对此文的拜读,解决了注入 spring 管理的 bean 如 UserService 注入到并不归 spring 管理的类 RedisUtil 中。代码如下:

@Component
@Lazy(false)
@Order(1)
public class SpringContextHolder implements ApplicationContextAware {

private static ApplicationContext applicationContext;

/**
 * 实现ApplicationContextAware接口的context注入函数, 将其存入静态变量.
 */
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
    SpringContextHolder.applicationContext = applicationContext; // NOSONAR
}

/**
 * 取得存储在静态变量中的ApplicationContext.
 */
public static ApplicationContext getApplicationContext() {
    checkApplicationContext();
    return applicationContext;
}

/**
 * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
 */
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) {
    checkApplicationContext();
    return (T) applicationContext.getBean(name);
}

/**
 * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
 */
public static <T> T getBean(Class<T> clazz) {
    checkApplicationContext();
    return (T) applicationContext.getBean(clazz);
}

/**
 * 清除applicationContext静态变量.
 */
public static void cleanApplicationContext() {
    applicationContext = null;
}

private static void checkApplicationContext() {
    if (applicationContext == null) {
        throw new IllegalStateException("applicaitonContext未注入,请在applicationContext.xml中定义SpringContextHolder");
    }
}
}

使用此工具类之后就可以通过下面这种方式在 filter 中使用 userService 等 spring 管理的对象

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain){
	// 这里是为了具体说明
	UserService userService = SpringContextHolder.getBean(UserService .class);
}
第二个问题是现在很多项目前后端分离开发(交互数据基本都为json,POST请求居多)

这种情况下就涉及一个流转换的问题,因为以往我们参数如果在get请求中会通过请求头携带传递,这样我们在 filter 中获取相关参数,然后判断是否放行等没有问题。
但是如果是在 post 请求,那么参数等都在请求体中,会以流的方式进入后台接收的 filter ,我们获取相关参数后,进行判断,那么问题来了。流 只要被读取过就会被消耗了(即消失了没有了)我们继续执行放行的 dofilter 方法中的 request 中就无法获取参数了。这里我们通过一个辅助类+一个继承了 HttpServletRequestWrapper 的类来解决:
感谢方法提供者:<代码老中医> 很详细,作为新手很容易看懂
通过这两个类 我们不仅可以获取请求中的各项参数,还能将读取后的流重新组织为新的流传递给下一层,保证了程序的正常正确运行。当然为了防止连接失效,本文也会贴上我自己的代码,如下:
辅助类

public class HttpHelper {
/**
 * 获取请求Body
 * @param request
 * @return
 */
public static String getBodyString(ServletRequest request) {
    StringBuilder sb = new StringBuilder();
    InputStream inputStream = null;
    BufferedReader reader = null;
    try {
        // 重新组织输入流 将request中获取到的流中的信息重新写入到新的流中 保证下一层的request流不为null
        inputStream = request.getInputStream();
        reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
        String line = "";
        while ((line = reader.readLine()) != null) {
            sb.append(line);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    return sb.toString();
}
}
继承 Wrapper 的类

public class RuleFilterHttpServletRequestWrapper extends HttpServletRequestWrapper {

private final byte[] body;

public RuleFilterHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
    super(request);
    // 首先读取输入流 获取到Request对象及流中的信息
    Enumeration e = request.getHeaderNames()   ;
    while(e.hasMoreElements()){
        String name = (String) e.nextElement();
        String value = request.getHeader(name);
        System.out.println(name+" = "+value);

    }
    // 借助辅助类 HttpHelper 重新组织输入流 使保持与之前流相同 输入到控制层中
    body = HttpHelper.getBodyString(request).getBytes(Charset.forName("UTF-8"));
}

@Override
public BufferedReader getReader() throws IOException {
    return new BufferedReader(new InputStreamReader(getInputStream()));
}

@Override
public ServletInputStream getInputStream() throws IOException {

    final ByteArrayInputStream bais = new ByteArrayInputStream(body);

    return new ServletInputStream() {
        @Override
        public boolean isFinished() {
            return false;
        }

        @Override
        public boolean isReady() {
            return false;
        }

        @Override
        public void setReadListener(ReadListener listener) {

        }

        @Override
        public int read() throws IOException {
            return bais.read();
        }
    };
}

@Override
public String getHeader(String name) {
    return super.getHeader(name);
}

@Override
public Enumeration<String> getHeaderNames() {
    return super.getHeaderNames();
}

@Override
public Enumeration<String> getHeaders(String name) {
    return super.getHeaders(name);
}
}

这里如果有同学找不到辅助类中那个 JOSNHelp 类,请不要着急,这个类就是一个Json 与 Object 转换的类,我们可以使用其它包提供的类,或者自己编写的类同样可以。比如博主就是用的 JSONUtils (并且其中的parse方法相差无几)

我的权限表其中一个字段 url 就是每个方法的访问 URL ,设计初衷也是想通过这种方式控制权限,这种方式适用性更好一些,后期维护成本也相对较低。

到这里大概说完了,好像有遗漏的,刚写博客,求原谅 后续会补充。
这里感谢大家的支持,java 之所以强,离不开大家无私的奉献,作为一个新手会再接再厉的,随时欢迎交流。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用自定义过滤器-Filter--实现对权限的控制 的相关文章

  • bash: /opt/ros/kinetic/setup.bash: 没有那个文件或目录

    我安的是ROS melodic版本 但是使用的是kinetic版本的安装教程 这就导致了在有一步 添加环境变量时 使用echo指令 忘了将教程中的kinetic改成melodic 所以出现了以下的现象 当我source bashrc的时候
  • Java学习笔记 -- 包 (package)

    Java 面向对象篇 笔记首页 序号 内容 链接地址 1 面向对象概述 https blog csdn net weixin 44141495 article details 107999131 2 类与对象 https blog csdn
  • 2022蓝桥杯JAVA研究生组--我写了几个

    试题 A 排列字母 本题总分 5 分 问题描述 小蓝要把一个字符串中的字母按其在字母表中的顺序排列 例如 LANQIAO 排列后为 AAILNOQ 又如 GOODGOODSTUDYDAYDAYUP 排列后为 AADDDDDGGOOOOPST
  • 晶振是如何起振的?

    01 皮尔斯晶体振荡器 目前工作中用得最多的就是皮尔斯晶体振荡器 也就是下面这个结构 CL1 CL2为匹配电容 Rext通常为串联的几百欧姆电阻 有时也不加 上面这个结构可能看着不是很熟悉 我们把它转换一下 变成下面这个就熟悉些 上图中把R
  • TestNG 开源自动化测试框架

    摘要 TestNG是一个开源自动化测试框架 TestNG表示下一代 TestNG是类似于JUnit 特别是JUnit 4 但它不是一个JUnit扩展 它的灵感来源于JUnit 它的目的是优于JUnit的 尤其是当测试集成的类 主要内容 Te
  • 如何在Java中将GIF图像转换为PNG等图像格式?试试Aspose

    通常 GIF图像用于描述动画 动画GIF是按特定顺序组合在一起的帧的集合 但是 在某些情况下 必须将GIF图像转换为其他光栅图像格式 对于这种情况 本文演示了如何使用Java将GIF图像转换为PNG JPEG BMP和TIFF格式 在Jav
  • 【upload-labs】————12、Pass-11

    闯关界面 前后端检测判断 查看源代码 这里采用了白名单判断 但是 img path直接拼接 所以可以先上传一个符合白名单检测的jpg文件 之后再burpsuite中使用 00截断保存路径即可 截断保存路径 浏览器中访问
  • C++项目中调用C#库

    最近有个功能在实现的时候要么要求 C 版本比较高 C 17 要么要求的 Qt 版本比较高 要么要求 windows 版本比较高 而且也没有比较好的第三方 C 库 无意间发现 C 早就实现了该功能 故准备使用 C 实现具体功能 在 C 项目中
  • 差帧法——学习

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 引言 思路 伪代码 代码 详解 函数详解 二值化 高斯滤波 引言 个人理解 差帧法 就是相邻两帧之间图象的差值 通过对差值进行高斯滤波或者腐蚀 膨胀操作减小图象噪声
  • Vue基础

    Vue 是什么 Vue 读音 vju 类似于 view 是一套用于构建用户界面的渐进式框架 vue 的核心库只关注视图层 不仅易于上手 还便于与第三方库或既有项目整合 使用Vue将helloworld 渲染到页面上 指令 本质就是自定义属性
  • 操作系统复习总结(五)

    10 18 14 30 15 00 第五章 主存管理 主存储器 主存 or 内存 中央处理器为可直接访问的存储器 计算机系统的一个关键性资源 理想中的存储器 更大更快 更便宜的非易失性存储器 存储管理的功能 方式 连续分配方式 一个进程分配
  • LINUX-LVM简单配置

    1 在编辑设置中为虚拟机添加另一块新硬盘 2 fdisk dev sdb gt n新建分区 gt p选择默认主分区 gt 回车 gt 回车 gt 回车 gt t为新建分区分配id gt 8e gt 回车 3 pvcreate dev sdb
  • 字符串有关习题

    0 请问下面代码有没有毛病 为什么 1 gt gt gt input I love FishC com 2 gt gt gt print input 3 I love FishC com 答 有问题 给变量命名要避免与内置函数名字冲突 1
  • 安装老版本flash - 解决”正尝试安装的adobe flash player不是最新版本“的办法

    安装Flash player时提示 正尝试安装的adobe flash player不是最新版本 解决的办法是在运行中输入regedit 在注册表中找到 HKEY LOCAL MACHINE SOFTWARE Macromedia Flas
  • c语言中关于时间的函数

    本文从介绍基础概念入手 探讨了在C C 中对日期和时间操作所用到的数据结构和函数 并对计时 时间的获取 时间的计算和显示格式等方面进行了阐述 本文还通过大量的实例向你展示了time h头文件中声明的各种函数和数据结构的详细使用方法 关键字
  • 抓蓝牙数据包_蓝牙协议分析工具Wireshark/Frontline/Ellisys的使用

    一 声明 本专栏文章我们会以连载的方式持续更新 本专栏计划更新内容如下 第一篇 蓝牙综合介绍 主要介绍蓝牙的一些概念 产生背景 发展轨迹 市面蓝牙介绍 以及蓝牙开发板介绍 第二篇 Transport层介绍 主要介绍蓝牙协议栈跟蓝牙芯片之前的
  • JAVA String.intern()方法

    感谢原作者的分享 转载只为方便知识点 原文地址 http www runoob com java java string intern html 莫洛的笔记 尽管在输出中调用intern方法并没有什么效果 但是实际上后台这个方法会做一系列的
  • Java面试准备——计算机网络

    计算机网络相关面试重点整理 本文学习自GitHub上的JavaGuide项目 感谢大佬的资源 此处为自我学习与整理 原项目链接 JavaGuide OSI和TCP IP各层的结构和功能 都有哪些协议 我们平时学习计算机网络使用五层结构 比较
  • openlayers 3 之 getPixelFromCoordinate 为空

    在地图容器发生变化后 再调用其方法进行定位 暂时 报错setPosition的错误 跟踪源代码 发现是map getPixelFromCoordinate为null值 查找资料 发现是map还没有渲染完成 所以报错 解决办法 1 添加pos

随机推荐

  • Istio Pilot源码学习(三):xDS的异步分发

    本文基于Istio 1 18 0版本进行源码学习 5 xDS的异步分发 DiscoveryService主要包含下述逻辑 启动GRPC Server并接收来自Envoy端的连接请求 接收Envoy端的xDS请求 从ConfigControl
  • C#数组 一维、二维以及交错数组 C#学习杂记(五)

    1 一维数组基本概念 拥有连续的内存空间 存储一组相同类型的数据 数组长度不可更改 数组下标从0开始 2 数组基本使用 int arr 声明 arr new int 4 1 2 3 4 赋值并且初始化 string str str new
  • STM32F103ZET6【标准库函数开发】------05.通用定时器TIM4四个通道输出PWM信号

    STM32有四个通用定时器 现在介绍TIM4输出4路PWM的方法 TIM4可以选择不用重映射或者重映射 一 没有重映射 下面展示主要的time c main c函数的代码 include timer h void TIM4 PWM Init
  • linux LCD驱动实验

    文章目录 一 linux下LCD驱动解析 1 Framebuffer设备 2 LCD驱动解析 二 硬件原理图分析 三 LCD驱动程序编写 1 LCD 屏幕 IO 配置 2 LCD 屏幕参数节点信息修改 3 LCD 屏幕背光节点信息 四 运行
  • 深度学习(一)

    目录 1 深度学习要解决的问题 2 深度学习应用领域 3 计算机视觉任务 4 视觉任务中遇到的问题 1 深度学习要解决的问题 机器学习流程 数据获取 特征工程 建立模型 评估与应用 深度学习跟人工智能更贴切 机器学习中的一部分 特征工程的作
  • Arduino MQTT客户端库PubSubClient快速入门

    文章目录 目的 基础说明 示例代码 总结 目的 MQTT是比较常用在物联网设备中的通讯协议 这篇文章将使用 Arudino ESP32 作为MQTT客户端进行通讯使用演示 目前Arduino的MQTT客户端库中最常使用的是 PubSubCl
  • Swin Transformer:层次化视觉Transformer

    目录 论文下载地址 代码下载地址 论文作者 模型讲解 背景介绍 模型解读 总体结构 Swin Transformer模块 非重叠窗口的自注意力 连续Swin Transformer块中的移位窗口分区 移位的高效批量计算 网络结构 结果分析
  • python 函数

    函数是组织好的 可重复使用的 用来实现单一 或相关联功能的代码段 函数能提高应用的模块性 和代码的重复利用率 Python提供了许多内置函数 比如print 我们也可以自己创建函数 这被叫做用户自定义函数 定义函数的语法 def 函数名 参
  • 201632位matlab下载_Matlab R2016a x32

    之前小编为大家提供了matlab 2016a x64的下载 本来以为新版本不再支持32位系统了 不过官方还是很负责任地推出了支持xp win7等系统的matlab 2016a 32位版 新版本新增了模拟机器人系统 原型机器人算法以及机器人学
  • matlab常用分布极大似然估计函数用法(含例子实现)

    本博文源于matlab概率论运用 学过 概率论 的同学都知道 极大似然估计一般用于区间估计的 而matlab已经将其封装好由我们自己调用 参数估计的MATLAB函数 函数 功能 mu sigma muci sigmaci normfit x
  • 使用ScrollView实现下拉刷新(一)

    转载自 http blog csdn net a6920502 article details 8759244 使用ListView来做下拉刷新有很多例子 而且封装的很好 ListView有 header 但是如果不使用ListView的下
  • 使用C++完成以栈为基础的简易计算器,并使用Qt5制作可视化界面

    使用C 完成以栈为基础的简易计算器 并使用Qt5制作可视化界面 一 计算器内部逻辑构造 在完成计算器时选用栈数据结构 自己编写的和标准模板库中的都可 要求支持加减乘除和逻辑与或非运算 可计算整数和小数 这一部分有很多思路都可以实现 我的写法
  • C语言-操作环境变量

    20180216 c语言操作环境变量 使用C语言操作环境变量 获取环境变量内容 char getenv char name 参数 name欲获取的环境变量名称 返回值 环境变量值 NULL表示没有找到环境变量 设置环境变量 int pute
  • 【直播预告】Stable Diffusion篇

    08月23日 18 30 19 30 大淘宝技术与DataFun联合策划了本次活动 邀请阿里巴巴大淘宝技术应用开发工程师赤燕老师分享 Stable Diffusion WebUI 从零基础到入门 欢迎大家按时收看直播 赤燕 淘天集团 大淘宝
  • Windows系统打完补丁之后,CRM异步服务没了?

    此部署中缺少一个或多个服务器角色 昨天应公司安全部门要求要给windows系统打补丁 做安全测试 自从CRM安装之后 windows的补丁就没有安装过 就先拿测试环境试试 顺风顺水的打完补丁 登录CRM访问正常 登录服务器 服务都正常启动了
  • 【Linux】Linux8安装docker图文详细步骤以及常用指令

    Docker 是一个开源的应用容器引擎 让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中 然后发布到任何流行的Linux或Windows操作系统的机器上 也可以实现虚拟化 切换到源目录 备份原来的源 root localhost d
  • 【Unity实用小方法】鼠标双击的判断

    using UnityEngine using System Collections public class DoubleClick MonoBehaviour 计时器 在一定的时间内双击有效 private float time 0f
  • 耗时两周手撸了一个 RPC 轮子,是驴子是马拉出来遛遛

    手撸 RPC 轮子系列文章目录 从零开始造 RPC 轮子系列 01 我为什么要去造一个轮子 从零开始造 RPC 轮子系列 02 演示轮子 是驴是马拉出来遛遛 TODO 从零开始造 RPC 轮子系列 03 完事具备 只差一个环境搭建 TODO
  • 《软件测试》第二章 软件开发的过程

    软件测试 第二章 软件开发的过程 2 1 产品的组成部分 2 1 1 软件产品需要多少投入 2 1 2 软件产品由哪些部分组成 2 2 软件项目成员 2 3 软件开发生命周期模式 2 3 1 大爆炸模式 2 3 2 边写边改模式 2 3 3
  • 使用自定义过滤器-Filter--实现对权限的控制

    提示 大牛大佬们就没必要垂阅了 如果很幸运的被大佬翻牌了 也希望能够给出指教 3Q 关于权限控制即包含功能权限 数据权限 我们使用的方式可谓多种多样 通过自定义注解编写AOP方式或是使用第三方提供好的框架如 shiro 或 springse