若依缓存使用浅析

news2025/1/23 13:14:48

配置

这块主要涉及两个类

  • FastJson2JsonRedisSerializer : 继承 RedisSerializer 接口自定义使用 fastjson 进行序列化和反序列化
  • RedisConfig:配置使用 StringRedisSerializer 来进行key的序列化与反序列,使用刚才我们 FastJson2JsonRedisSerializer 来进行 value 的序列化与反序列

下面贴下相关代码

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
{
    @Bean
    @SuppressWarnings(value = { "unchecked", "rawtypes" })
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
    {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);

        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        serializer.setObjectMapper(mapper);

        template.setValueSerializer(serializer);
        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
}
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
{
    @SuppressWarnings("unused")
    private ObjectMapper objectMapper = new ObjectMapper();

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;

    static
    {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    }

    public FastJson2JsonRedisSerializer(Class<T> clazz)
    {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException
    {
        if (t == null)
        {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException
    {
        if (bytes == null || bytes.length <= 0)
        {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);

        return JSON.parseObject(str, clazz);
    }

    public void setObjectMapper(ObjectMapper objectMapper)
    {
        Assert.notNull(objectMapper, "'objectMapper' must not be null");
        this.objectMapper = objectMapper;
    }

    protected JavaType getJavaType(Class<?> clazz)
    {
        return TypeFactory.defaultInstance().constructType(clazz);
    }
}

简单提一嘴 ruoyi 的模块拆分,common 与 framework 的拆分思想很值得学习,之前我自己拆分的时候没有拆出 framework 这一层,就导致很多与具体业务模块无关的内容与业务模块代码耦合过于严重

这两个配置类放在了 framework 模块中,然后所有对 redistemplate 的操作封装在了 common 的工具类 RedisCache

应用场景浅析

我们通过全局搜索 RedisCache,可以很方便的找到 redis 在项目中的使用,下面我们一个一个走一遍

image-20221215120602216

登录验证码与登录token存储鉴权

这几个比较类型放在一起说

验证码这块,在获取验证码的时候会把 生成的验证码放到 redis 缓存,并设置 过期时间

redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);

简单提一下验证码工具,调用验证码工具类会生成一个图片和一个对应的code,我们在生成个uuid标识这次的结果,和短信登录意思差不多,只不过把手机号变成了 uuid,后面校验登录时传来的 uuid 和 code 是否匹配

后面看login这块,就是 token 存储的使用了

/**
     * 登录验证
     * 
     * @param username 用户名
     * @param password 密码
     * @param code 验证码
     * @param uuid 唯一标识
     * @return 结果
     */
    public String login(String username, String password, String code, String uuid)
    {
      // <1> 根据 uuid 和 code 去 redis 中找,并且删除掉这个验证码的code
        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
        String captcha = redisCache.getCacheObject(verifyKey);
        redisCache.deleteObject(verifyKey);
        if (captcha == null)
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
            throw new CaptchaExpireException();
        }
        if (!code.equalsIgnoreCase(captcha))
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
            throw new CaptchaException();
        }
        // 用户验证
        Authentication authentication = null;
        try
        {
            //<2> 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager
                    .authenticate(new UsernamePasswordAuthenticationToken(username, password));
        }
        catch (Exception e)
        {
            if (e instanceof BadCredentialsException)
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                throw new UserPasswordNotMatchException();
            }
            else
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
                throw new CustomException(e.getMessage());
            }
        }
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
      // <3> 拿到刚才的 LoginUser(UserDetail实现类)
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        // <4> 生成token
        return tokenService.createToken(loginUser);
    }

<1> 处看下注释即可

<2> 这块调用 UserDetailsServiceImpl.loadUserByUsername,调用自定义的函数,判断账号密码是否匹配,匹配后返回 UserDetails 的自定义实现类,这步还可以一起返回该用户所有权限

<3> <4> 调用 createToken方法在 redis 中记录 token(基于用户信息用 jwt 生成,但是也是基于持久化存储的,这种实现是最灵活且安全的)

springsecurity 不清楚的可以看这篇文章 芋道 Spring Boot 安全框架 Spring Security 入门 | 芋道源码 —— 纯源码解析博客 (iocoder.cn)

接着看后面这块实现 TokenService

/**
     * 创建令牌
     *
     * @param loginUser 用户信息
     * @return 令牌
     */
    public String createToken(LoginUser loginUser)
    {
      // <1> 用 uuid 作为key,这个token只是叫 token ,但其实是 key
        String token = IdUtils.fastUUID();
        loginUser.setToken(token);
        setUserAgent(loginUser);
      // <2> 把loginUser缓存到 redis
        refreshToken(loginUser);

      // <3> 生成 jwt 的 token 
        Map<String, Object> claims = new HashMap<>();
        claims.put(Constants.LOGIN_USER_KEY, token);
        return createToken(claims);
    }

/**
     * 刷新令牌有效期
     *
     * @param loginUser 登录信息
     */
    public void refreshToken(LoginUser loginUser)
    {
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
        // 根据uuid将loginUser缓存
        String userKey = getTokenKey(loginUser.getToken());
        redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
    }

/**
     * 从数据声明生成令牌
     *
     * @param claims 数据声明
     * @return 令牌
     */
    private String createToken(Map<String, Object> claims)
    {
        String token = Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512, secret).compact();
        return token;
    }

image-20221215173219187

查看redis 可以看到我们序列化的信息

image-20221215173338131

debug下,看到我们把生成的uuid再使用jwt加密返回,然后 uuid 关联用户信息的json序列化后存储到 redis

image-20221215173437579

还是要debug啊

然后把生成的token返回给前端,后面的请求前端会带上这个 token

下面来讲下token是如何被解析的,也就是debug需要校验是否登录的接口,这块是JwtAuthenticationTokenFilter中实现的

@Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException
    {
      // <1>
        LoginUser loginUser = tokenService.getLoginUser(request);
        if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication()))
        {
          // <2>
            tokenService.verifyToken(loginUser);
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }
        chain.doFilter(request, response);
    }

// tokenService
 /**
     * 获取用户身份信息
     *
     * @return 用户信息
     */
    public LoginUser getLoginUser(HttpServletRequest request)
    {
        // 获取请求携带的令牌
        String token = getToken(request);
        if (StringUtils.isNotEmpty(token))
        {
            Claims claims = parseToken(token);
            // 解析对应的权限以及用户信息
            String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
            String userKey = getTokenKey(uuid);
            LoginUser user = redisCache.getCacheObject(userKey);
            return user;
        }
        return null;
    }

/**
     * 验证令牌有效期,相差不足20分钟,自动刷新缓存
     *
     * @param loginUser
     * @return 令牌
     */
    public void verifyToken(LoginUser loginUser)
    {
        long expireTime = loginUser.getExpireTime();
        long currentTime = System.currentTimeMillis();
        if (expireTime - currentTime <= MILLIS_MINUTE_TEN)
        {
            refreshToken(loginUser);
        }
    }

<1> 从 header 中取出 token,用 jwt 解密出来 uuid,看下这个uuid是否在redis中存在

<2> 判断 token 时效性,刷新 token

在线用户统计与签退

下面来看在线用户的实现

@PreAuthorize("@ss.hasPermi('monitor:online:list')")
    @GetMapping("/list")
    public TableDataInfo list(String ipaddr, String userName)
    {
        Collection<String> keys = redisCache.keys(Constants.LOGIN_TOKEN_KEY + "*");
        List<SysUserOnline> userOnlineList = new ArrayList<SysUserOnline>();
        for (String key : keys)
        {
            LoginUser user = redisCache.getCacheObject(key);
            if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(userName))
            {
                if (StringUtils.equals(ipaddr, user.getIpaddr()) && StringUtils.equals(userName, user.getUsername()))
                {
                    userOnlineList.add(userOnlineService.selectOnlineByInfo(ipaddr, userName, user));
                }
            }
            else if (StringUtils.isNotEmpty(ipaddr))
            {
                if (StringUtils.equals(ipaddr, user.getIpaddr()))
                {
                    userOnlineList.add(userOnlineService.selectOnlineByIpaddr(ipaddr, user));
                }
            }
            else if (StringUtils.isNotEmpty(userName) && StringUtils.isNotNull(user.getUser()))
            {
                if (StringUtils.equals(userName, user.getUsername()))
                {
                    userOnlineList.add(userOnlineService.selectOnlineByUserName(userName, user));
                }
            }
            else
            {
                userOnlineList.add(userOnlineService.loginUserToUserOnline(user));
            }
        }
        Collections.reverse(userOnlineList);
        userOnlineList.removeAll(Collections.singleton(null));
        return getDataTable(userOnlineList);
    }

就是拿到所有在线用户的key,模糊查询,然后取set

强退用户

/**
     * 强退用户
     */
    @PreAuthorize("@ss.hasPermi('monitor:online:forceLogout')")
    @Log(title = "在线用户", businessType = BusinessType.FORCE)
    @DeleteMapping("/{tokenId}")
    public AjaxResult forceLogout(@PathVariable String tokenId)
    {
        redisCache.deleteObject(Constants.LOGIN_TOKEN_KEY + tokenId);
        return AjaxResult.success();
    }

在 redis 里面把这条登录的记录删除掉

系统设置的缓存

这块在用的时候最直观的就是项目启动时会发现有很多查询sys_config的sql的debug信息,其中第一条就是加载系统配置啦,下面的就是加载数据字典,放在后面说

image-20221215174919761

/**
     * 项目启动时,初始化参数到缓存
     */
    @PostConstruct
    public void init()
    {
        List<SysConfig> configsList = configMapper.selectConfigList(new SysConfig());
        for (SysConfig config : configsList)
        {
            redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue());
        }
    }

SysConfigServiceImpl 中配置了这个,会在项目启动时初始化,拿到所有 sys_config,加载到redis

image-20221215175010582

看下查询配置的实现

/**
     * 根据键名查询参数配置信息
     * 
     * @param configKey 参数key
     * @return 参数键值
     */
    @Override
    public String selectConfigByKey(String configKey)
    {
      // <1> 首先从缓存中取,存在返回缓存中的
        String configValue = Convert.toStr(redisCache.getCacheObject(getCacheKey(configKey)));
        if (StringUtils.isNotEmpty(configValue))
        {
            return configValue;
        }
      // <2> 不存在的话,查数据库,数据库中存在,写缓存并返回
        SysConfig config = new SysConfig();
        config.setConfigKey(configKey);
        SysConfig retConfig = configMapper.selectConfig(config);
        if (StringUtils.isNotNull(retConfig))
        {
            redisCache.setCacheObject(getCacheKey(configKey), retConfig.getConfigValue());
            return retConfig.getConfigValue();
        }
        return StringUtils.EMPTY;
    }

新增,修改与删除,看下是否存在缓存与db不一致的情况

/**
     * 新增参数配置
     * 
     * @param config 参数配置信息
     * @return 结果
     */
    @Override
    public int insertConfig(SysConfig config)
    {
        int row = configMapper.insertConfig(config);
        if (row > 0)
        {
            redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue());
        }
        return row;
    }

    /**
     * 修改参数配置
     * 
     * @param config 参数配置信息
     * @return 结果
     */
    @Override
    public int updateConfig(SysConfig config)
    {
        int row = configMapper.updateConfig(config);
        if (row > 0)
        {
            redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue());
        }
        return row;
    }

    /**
     * 批量删除参数信息
     * 
     * @param configIds 需要删除的参数ID
     * @return 结果
     */
    @Override
    public int deleteConfigByIds(Long[] configIds)
    {
        for (Long configId : configIds)
        {
            SysConfig config = selectConfigById(configId);
            if (StringUtils.equals(UserConstants.YES, config.getConfigType()))
            {
                throw new CustomException(String.format("内置参数【%1$s】不能删除 ", config.getConfigKey()));
            }
        }
        int count = configMapper.deleteConfigByIds(configIds);
        if (count > 0)
        {
            Collection<String> keys = redisCache.keys(Constants.SYS_CONFIG_KEY + "*");
            redisCache.deleteObject(keys);
        }
        return count;
    }

新增与修改,删除,先写数据库再写redis,如果写表成功,写redis,都是基于这个原则,可以思考下为什么不能先写 redis(缓存不一致问题)

数据字典

数据字典的键和值是分开存储的

image-20221215180312694

image-20221215180336103

根据 dict_code 关联存储

还是看下初始化代码

/**
     * 项目启动时,初始化字典到缓存
     */
    @PostConstruct
    public void init()
    {
        List<SysDictType> dictTypeList = dictTypeMapper.selectDictTypeAll();
        for (SysDictType dictType : dictTypeList)
        {
            List<SysDictData> dictDatas = dictDataMapper.selectDictDataByType(dictType.getDictType());
            DictUtils.setDictCache(dictType.getDictType(), dictDatas);
        }
    }

// DictUtils
/**
     * 设置字典缓存
     * 
     * @param key 参数键
     * @param dictDatas 字典数据列表
     */
    public static void setDictCache(String key, List<SysDictData> dictDatas)
    {
        SpringUtils.getBean(RedisCache.class).setCacheObject(getCacheKey(key), dictDatas);
    }

先查主表,然后关联查询子表

子表的数据集合整个序列化成一个json,举一个dict为例看下存储在 redis 里面的 value(加 @ 是防止关键字冲突?)

image-20221215180810604
[{"@type":"com.ruoyi.common.core.domain.entity.SysDictData","createBy":"admin","createTime":1658803658000,"default":false,"dictCode":147,"dictLabel":"工业","dictSort":1,"dictType":"attributes","dictValue":"1","isDefault":"N","params":{"@type":"java.util.HashMap"},"status":"0"},{"@type":"com.ruoyi.common.core.domain.entity.SysDictData","createBy":"admin","createTime":1658803659000,"default":false,"dictCode":148,"dictLabel":"商业","dictSort":2,"dictType":"attributes","dictValue":"2","isDefault":"N","params":{"@type":"java.util.HashMap"},"status":"0"}]

后面的分析比较类似,大家有兴趣自己 debug 把(其实是因为懒,哈哈)

后记

其实写这个系列完全是兴趣,因为 redis 的一些东西看了好多,但是不知道具体是怎么用的, 索性就拿若依来看看吧

后面想到啥了再补充把,先写到这里

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

通过WSL2运行GUI程序

上次我写过一篇VGPU和WSL2&#xff0c;这回我打算写一篇关于《通过WSL2运行GUI程序》的笔记。 起因 总所周知&#xff0c;KVM in Linux支持GPU passthrough(GPU直通)功能的&#xff0c;就是配置稍稍多了一点。最初想试试如何在Hyper-V中尝试安装VM&#xff0c;但是vgpu的文档…

2022-12-14 jedis

jedis 简介 编程语言与redis java语言连接redis服务 jedis SpringDataRedis Lettuce HelloWorld 客户端连接redis maven下载安装 配置maven环境变量 库的种类和彼此的关系 maven标准目录结构 核心代码部分配置文件部分测试代码部分测试配置文件 maven常用的命令 mvn…

nodejs+vue083新生报到服务管理系统

目 录 摘 要 I 目 录 III 第一章 概述 1 1.1研究背景 1 1.2 开发意义 1 1.3 研究现状 1 1.4 研究内容 2 1.5 论文结构 2 第二章 开发技术介绍 1 2.5 B/S架构 2 第三章 系统分析 1 3.1 可行性分析 1 3.1.1技术可行性 1 3.1.2操作可…

python绘制圣诞树、烟花、爱心及节日倒计时

说明&#xff1a; 该博客主要完成以下几个方面的功能: 1.元旦节日倒计时代码的实现 2.使用python源码“绘制圣诞树” 3.使用python绘制“跨年烟花” 4.使用python 绘制“爱心” 呈现方式&#xff1a;代码和截图 1.元旦倒计时代码&#xff1a; 输入相应的节日时间&#…

Seata - @GlobalTransactional源码解析

脑图 核心 Seata三大角色 TC &#xff1a;事务协调者&#xff0c;netty server(服务器)TM &#xff1a;事务管理器&#xff0c;netty client(客户端)RM&#xff1a; 资源管理器&#xff0c;netty client(客户端) GlobalTransactional(name "fsp-create-order" ro…

【静脉检测】手指静脉图像检测【含Matlab源码 1654期】

⛄一、简介 手指静脉识别系统的性能非常依赖于采集图像的质量,但是采集设备在成像和传输时产生的各类噪声,以及开放式使用场景下设备镜面上存在脏污、用户手指存在蜕皮情况等因素都会对图像质量造成极大的影响,增大后续特征提取的难度,最终影响整个系统的识别性能。针对目前现…

Zabbix 6.2 监控 PostgreSQL13 数据库

Zabbix 6.2 监控 PostgreSQL13 数据库 文章目录Zabbix 6.2 监控 PostgreSQL13 数据库官方模版地址1.pgsql新建监控用户2. 编辑 pg_hba.conf 文件并重启3.拷贝监控脚本到var/lib/zabbix下4.zabbix监控导入模版5.主机配置模版6.验证监控数据官方模版地址 https://git.zabbix.com…

一文带你读懂何为 macOS App 公证,以及如何自动化实现

前言 在上篇文章「macOS App 自动化分发 App Store 探索与实践」中讲解了如何通过 Shell 脚本实现 macOS App 自动化分发 App Store。相信&#xff0c;看过的同学都或多或少对 macOS App 构建、分发 App Store 相关的知识都具备了一定的认知。 而对于开发者来说&#xff0c;我…

RCE(远程代码/命令执行漏洞)原理及靶场练习

目录 PHP-RCE涉及函数 基础命令符 靶场练习 PHP-RCE涉及函数 代码注入 eval() 把字符串 code 作为PHP代码执行 assert() 检查一个断言是否为 false preg_replace() 执行一个正则表达式的搜索和替换 create_function() 创建一个匿名函数并且返回函数名创 call_user_func()/ca…

Android进程启动流程

一.Android 系统架构图 ​虽然 Android 系统非常庞大且错综复杂&#xff0c;但整体架构设计清晰。Android 底层内核空间以 Linux Kernel 作为基石&#xff0c;上层用户空间由 Native系统库、虚拟机运行环境、框架层组成&#xff0c;通过系统调用(Syscall)连通系统的内核空间 与…

浅谈hudi 的callback回调机制

浅谈hudi 的callback回调机制 关于hudi的write operations,hudi有4种类型,分别为upsert/insert/bulk_insert/delete[软删除/硬删除]。 了解hudi的都知道,hudi有一个核心的机制就是timeline,hudi的instantDTO包含action(动作),ts(时间),state(状态)。 action主要包括: commits…

Linux常用命令总结(建议收藏)

文章目录一、文件管理1、cat&#xff1a;查看文件内容案例1&#xff1a;输出内容行数2、chmod&#xff1a;是控制用户对文件的权限的命令案例1&#xff1a;&#xff1a;将user文件修改成用户、组、其他用户都可以读写可执行的权限3、diff&#xff1a;用于比较文件的差异4、find…

ELK (一)部署ELK+Filebeat日志收集分析系统

说明&#xff1a;此安装流程只适用于8.0.0以下的版本 1. ElasticSearch 部署 1.1 下载ElasticSearch的wget指令&#xff1a; wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.13.4-linux-x86_64.tar.gz1.2 解压安装包到指定目录 指定解压缩到 …

【指纹识别】指纹识别【含GUI Matlab源码 586期】

⛄一、指纹识别简介 1 指纹识别的引入和原理 1.1 指纹的基本知识 指纹&#xff0c;由于其具有终身不变性、唯一性和方便性&#xff0c;已几乎成为生物特征识别的代名词。指纹是指人的手指末端正面皮肤上凸凹不平产生的纹线。纹线有规律的排列形成不同的纹型。纹线的起点、终点…

SpringBoot+Vue实现前后端分离的旅游网站

文末获取源码 开发语言&#xff1a;Java 使用框架&#xff1a;spring boot 前端技术&#xff1a;JavaScript、Vue.js 、css3 开发工具&#xff1a;IDEA/MyEclipse/Eclipse、Visual Studio Code 数据库&#xff1a;MySQL 5.7/8.0 数据库管理工具&#xff1a;phpstudy/Navicat JD…

【Effective_Objective-C_2对象,消息,运行期1】

文章目录前言6 理解”属性“这一概念定义变量不兼容现象的出现解决不兼容现象-Property使用属性更便捷属性特质原子性读写权限内存管理语义方法名原子性和非原子性要点总结7 在对象内部尽量直接访问实例变量要点总结8 理解“对象等同性” 和 isEqual“” 判断的依据“isEuqalTo…

RTL8380M/82M管理型交换机系统软件操作指南四:QoS/服务质量

接下来对QoS进行详细的描述&#xff0c;主要包括以下七大内容&#xff1a;QoS概述、功能简介、拥塞管理、策略分类、调度方式、优先级映射配置、QoS端口配置. 1.1 QoS概述 QoS&#xff08;Quality of Service&#xff0c;服务质量&#xff09;是用各种手段解决网络延迟和阻塞等…

[附源码]Python计算机毕业设计SSM基于Java动漫论坛系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

业务模型设计

业务模型设计业务模型设计统一语言、术语统一单词业务数据表模型规范数据库范式几个经验业务模型索引主键&#xff1a; 自增id、雪花id、和uuid 差别创建表字符集设置myisam 和 innodb 区别业务模型设计 统一语言、术语 定义&#xff1a;需求分析的过程&#xff08;系统目标、…

001、【C语言编程题目】猴子吃桃问题

001、【题目】猴子吃桃问题 猴子吃桃问题&#xff1a;猴子第一天吃了若干个桃子&#xff0c;当即吃了一半&#xff0c;还不解馋&#xff0c;又多吃了一个&#xff1b; 第二天&#xff0c;吃剩下的桃子的一半&#xff0c;还不过瘾&#xff0c;又多吃了一个&#xff1b;以后每天…