【第3期】Springboot集成SpringSecurity+RSA+ECS免密登录

news2024/11/30 0:50:22

本期简介

RSA是非常安全的非对称加解密算法,单纯的RSA的原理和使用网络资料较多,本期不细讲RSA的原理,主要讲解实战,如何与Springboot+SpringSecurity集成起来,做到在安全框架基础上,对用户的密码进行加密存储,解密认证。同时,平时我们登录ECS服务器大多数情况都是账号密码登录形式,麻烦且容易忘记密码,本期最后会讲如何通过RSA的密钥来实现ECS免密自动登录。

  • 本期要点:
  1. 如何生成RSA公钥文件和私钥文件
  2. 获取公钥文件和私钥文件转换成JDK的密钥文件对象
  3. 简单的验证加密和解密
  4. 与Springboot、SpringSecurity如何集成
  5. 集成后验证用户注册的加密存储
  6. 集成后验证用户登录的解密验证
  7. 实现ECS的免密登录

一、如何生成RSA公钥文件和私钥文件

方式一:通过命令行生成

  • 命令:ssh-keygen -t -rsa -C xxxxx@mail.com
  • 说明:-t rsa表示生成的密钥使用的算法为RSA,-C表示用户邮箱号,会追加在公钥文件文本后
    在这里插入图片描述
    查看生成结果,可以看到生成了new_id_rsa私钥文件和new_id_rsa.pub公钥文件
    如果前面一步执行命令一路回车生成的默认密钥路径分别是

    /Users/本机用户名/.ssh/id_rsa
    /Users/本机用户名/.ssh/id_rsa.pub
    在这里插入图片描述

方式二:通过代码自动生成

  • 首先创建RsaUtils工具类方法
    /**
     * 根据密文,生存RSA公钥和私钥,并写入指定文件
     *
     * @param publicKeyFilename  公钥文件路径
     * @param privateKeyFilename 私钥文件路径
     * @param password           生成密钥的密码
     */
    public static void generateKey(String publicKeyFilename, String privateKeyFilename, String password, int keySize) throws Exception {
        // 创建KeyPairGenerator对象,指定算法为RSA
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
        if (password == null) {
            // 初始化KeyPairGenerator对象,设置密钥长度为2048位
            keyPairGenerator.initialize(Math.max(keySize, DEFAULT_KEY_SIZE), JCAUtil.getSecureRandom());
        } else {
            SecureRandom secureRandom = new SecureRandom(password.getBytes(StandardCharsets.UTF_8));
            // 初始化KeyPairGenerator对象,设置密钥长度为2048位
            keyPairGenerator.initialize(Math.max(keySize, DEFAULT_KEY_SIZE), secureRandom);
        }

        // 生成KeyPair对象,即公钥和私钥
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        // 获取公钥并写到文件
        byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
        publicKeyBytes = Base64.getEncoder().encode(publicKeyBytes);
        writeFile(publicKeyFilename, publicKeyBytes);

        // 获取私钥并写到文件
        byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
        privateKeyBytes = Base64.getEncoder().encode(privateKeyBytes);
        writeFile(privateKeyFilename, privateKeyBytes);
    }

    public static void generateKey(String pubKeyFileName, String priKeyFileName) throws Exception {
        generateKey(pubKeyFileName, priKeyFileName, null, DEFAULT_KEY_SIZE);
    }

    private static byte[] readBytesFromFile(String fileName) throws Exception {
        return Files.readAllBytes(new File(fileName).toPath());
    }

    private static void writeFile(String destPath, byte[] bytes) throws IOException {
        File dest = new File(destPath);
        if (dest.exists()) {
            Files.write(dest.toPath(), bytes);
            return;
        }
        boolean created = dest.createNewFile();
        if (!created) {
            log.warn("写入密钥内容到文件{}失败,请检查!", destPath);
        }
        Files.write(dest.toPath(), bytes);
    }

    public static void main(String[] args) throws Exception {
        RsaUtils.generateKey("/Users/本机用户名/.ssh/id_rsa.pub", "/Users/本机用户名/.ssh/id_rsa");
    }

从上方代码实现的功能就是ssh-keygen -t rsa功能,执行该main方法:
从输出日志看,成功生成了公钥和私钥文件
在这里插入图片描述
从本机对应目录检查下生成结果:
在这里插入图片描述

二、获取公钥文件和私钥文件转换成JDK的密钥文件对象

到目前为止已经生成了公钥文件和私钥文件,要实现加解密,还需要将公钥文件和私钥文件读取到jvm内存并转换成对应的公钥文件对象和私钥文件对象

获取公钥文件

公钥文件类java.security.PublicKey是属于jdk里面的类,把公钥文件转换成该对象实例

    /**
     * 从文件中读取公钥为PublicKey对象
     *
     * @param filename 公钥保存路径,相对于classpath
     * @return 公钥对象
     * @throws Exception 读取公钥抛出的异常类型
     */
    public static PublicKey getPublicKey(String filename) throws Exception {
        byte[] bytes = readBytesFromFile(filename);
        byte[] decodeBytes = Base64.getDecoder().decode(bytes);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(decodeBytes);
        KeyFactory factory = KeyFactory.getInstance(ALGORITHM);
        return factory.generatePublic(spec);
    }

获取私钥文件

私钥文件类java.security.PrivateKey是属于jdk里面的类,把私钥文件转换成该对象实例

    /**
     * 从文件中读取私钥为PrivateKey对象
     *
     * @param filename 私钥保存路径,相对于classpath
     * @return 私钥对象
     * @throws Exception 读取私钥抛出的异常类型
     */
    public static PrivateKey getPrivateKey(String filename) throws Exception {
        byte[] bytes = readBytesFromFile(filename);
        byte[] decodeBytes = Base64.getDecoder().decode(bytes);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decodeBytes);
        KeyFactory factory = KeyFactory.getInstance(ALGORITHM);
        return factory.generatePrivate(spec);
    }

完成这两个方法后便具备了通过代码进行加密和解密的功能,下面进行加解密的验证。

三、简单的验证加密和解密

  • RSA算法是公钥加密、私钥解密,所以公钥可以分发,私钥不能分发,一旦私钥泄漏,将是一场灾难

加密

加密方法,指定加密明文和公钥文件的路径

    /**
     * RSA公钥加密
     *
     * @param plainText     明文
     * @param publicKeyPath 公钥文件路径
     * @return 密文
     * @throws Exception 加密过程中的异常信息
     */
    public static String encrypt(String plainText, String publicKeyPath) throws Exception {
        // base64编码的公钥
        PublicKey publicKey = getPublicKey(publicKeyPath);
        // RSA加密
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] cipherBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(cipherBytes);
    }

解密

解密方法,指定解密密文和私钥文件的路径

    /**
     * RSA私钥解密
     *
     * @param cipherText     密文
     * @param privateKeyPath 私钥文件路径
     * @return 明文
     */
    public static String decrypt(String cipherText, String privateKeyPath) throws Exception {
        // 64位解码加密后的字符串
        byte[] inputBytes;
        inputBytes = Base64.getDecoder().decode(cipherText.getBytes(StandardCharsets.UTF_8));
        PrivateKey privateKey = getPrivateKey(privateKeyPath);
        // RSA解密
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return new String(cipher.doFinal(inputBytes));
    }

验证加解密

编写main方法,对明文先加密,再从密文解密,对比加解密前后的一致性

    public static void main(String[] args) throws Exception {
        String publicKeyPath = "/Users/本机用户名/.ssh/id_rsa.pub";
        String privateKeyPath = "/Users/本机用户名/.ssh/id_rsa";
        System.out.printf("公钥文件路径:%s\n", publicKeyPath);
        System.out.printf("私钥文件路径:%s\n\n", privateKeyPath);

        String plainText = "pass123456@2023!";

        String cipherText = encrypt(plainText, publicKeyPath);
        System.out.printf("加密前明文是%s\n", plainText);
        System.out.printf("加密后密文是%s\n\n", cipherText);

        String plainTextRecovery = decrypt(cipherText, privateKeyPath);
        System.out.printf("解密前密文是%s\n", cipherText);
        System.out.printf("解密后明文是%s\n", plainTextRecovery);
        
        System.out.printf("密码前后一致性:%s\n\n", plainText.equals(plainTextRecovery));
    }

验证结果:
从结果来看,是符合预期的,接下来将RSA集成到Springboot和SpringSecurity实现加解密注册登录验证
在这里插入图片描述

四、与Springboot、SpringSecurity如何集成

编写配置类

用于系统启动自动读取公私钥文件的路径

@Data
@Configuration
@ConfigurationProperties(prefix = "rsa.key")
public class RsaKeyProperties {

    private String pubKeyFile;
    private String priKeyFile;
}

对应application-env.yml的配置:
在这里插入图片描述

启动自动加载公私钥

我们需要在系统启动的时候自动加载公私钥路径,并且将公私钥文件转换为PublicKey和privateKey的实例

@Data
@Configuration
@ConfigurationProperties(prefix = "rsa.key")
public class RsaKeyProperties {

    private String pubKeyFile;
    private String priKeyFile;

    private PublicKey publicKey;
    private PrivateKey privateKey;

    /**
     * 系统启动的时候触发,将公钥文件从本机文件加载为公私钥对象
     * @throws Exception 公私钥加载异常
     */
    @PostConstruct
    public void createRsaKey() throws Exception {
        publicKey = RsaUtils.getPublicKey(pubKeyFile);
        privateKey = RsaUtils.getPrivateKey(priKeyFile);
    }
}

SpringSecurity集成RSA

SpringSecurity提供了一个PasswordEncoder接口,我们通过实现这个接口来创建自定义的 RSA加解密:


import com.snycedu.platform.auth.config.RsaKeyProperties;
import com.snycedu.platform.auth.util.RsaUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.crypto.password.PasswordEncoder;

@Slf4j
public class RSACryptPasswordEncoder implements PasswordEncoder {
    private RsaKeyProperties prop;

    public RSACryptPasswordEncoder(RsaKeyProperties prop) {
        this.prop = prop;
    }

    @Override
    public String encode(CharSequence plainText) {
        try {
            return RsaUtils.encrypt(plainText.toString(), prop.getPublicKey());
        } catch (Exception exception) {
            log.error("加密异常:{}", exception.getMessage(), exception);
        }
        return "";
    }

    public String decode(CharSequence cipherText) {
        try {
            return RsaUtils.decrypt(cipherText.toString(), prop.getPrivateKey());
        } catch (Exception exception) {
            log.error("解密异常:{}", exception.getMessage(), exception);
        }
        return "";
    }

    @Override
    public boolean matches(CharSequence plainText, String cipherText) {
        String decryptedPlainText = decode(cipherText);
        return StringUtils.equals(plainText, decryptedPlainText);
    }
}

在前面几期提到要创建SpringSecurity的安全配置类,现在在WebSecurityConfig中注册一个beanpasswordEncoder,其为RSACryptPasswordEncoder的对象实例

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public RSACryptPasswordEncoder passwordEncoder() {
        return new RSACryptPasswordEncoder(prop);
    }
}

在WebSecurityConfig重写configure(AuthenticationManagerBuilder auth) 方法,指定密码认证管理器为我们注册的自定义RSA加解密的bean,这样SpringSecurity就和RSA集成起来了。

    // 指定认证对象的来源
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
    }

SpringSecurity如何进行账号密码认证:
通过用户的账号和密码创建了UsernamePasswordAuthenticationToken认证实例,authenticationManager.authenticate(authRequest)进行账号密码认证,这一步就需要用到前面的加解密的bean

    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        UserPo sysUser = null;
        try {
            sysUser = new ObjectMapper().readValue(request.getInputStream(), UserPo.class);

            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getPassword());
            return authenticationManager.authenticate(authRequest);
        } catch (Exception exception) {
            // 这里省略了登录认证失败的逻辑,前几期有提到,可以翻看下
        }
        return null;
    }

五、集成后验证用户注册的加密存储

注册接口

注意:注册登录接口无需进行权限相关的验证,需从SpringSecurity中配置为白名单,前期有讲

    @Operation(tags = "用户注册")
    @PostMapping("/api/v1/register")
    public Response<LoginUser> register(@RequestBody @Validated RegisterRequest user) {
        LoginUser loginUser = registerService.register(user);
        return ResponseResult.success(loginUser);
    }

注册业务层的实现

注入BeanpasswordEncoder,用于注册时对用户的明文密码进行加密存储

    @Autowired
    RSACryptPasswordEncoder passwordEncoder;

    @Autowired
    UserService userService;
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public LoginUser register(RegisterRequest user) {
        // 先判定账号是否被占用
        int exits = userService.existsTheLoginName(user.getLoginName());
        if (exits > 0) {
            throw new BusinessException("此账号已被占用,请更换");
        }
        UserPo userPo = JsonUtils.copy(user, UserPo.class);
        
        userPo.setId(SnowflakeIdWorker.nextId());

        userPo.setPassword(passwordEncoder.encode(user.getPassword()));
        userPo.setCreator(user.getLoginName());
        userPo.setCreateTime(LocalDateTime.now());
        userPo.setStatus(LoginStatusEnum.INIT);

        UserPo registerUser = userService.add(userPo);
        return JsonUtils.copy(registerUser, LoginUser.class);
    }

调用注册接口注册并验证数据存储结果

  • 通过postman调用注册接口
    在这里插入图片描述
  • 断点调试加密前获取到的注册信息,可以看到是明文的密码
    在这里插入图片描述
  • 断点调试加密后的用户密码,可以看到已经是加密的密文了
    在这里插入图片描述

从插入sql的日志查看写入情况

2023-12-16 11:26:06.737 [http-nio-8080-exec-9] [INFO] [com.snycedu.platform.common.interceptor.MybatisSqlInterceptor:75] - 
========================user:user   pwd:123456  db:jdbc:mysql://127.0.0.1:33061/snycedu?serverTimezone=CTT&useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&autoReconnect=true
[    SQL   ID]:com.snycedu.platform.auth.mapper.UserMapper.insert                                                       
[         SQL]:insert into common_user ( id, login_name, password, status, phone, born, fail_count, creator, create_time, modifier, modify_time ) values (16137259137024000, 'zhangsan', 'cO6gJvbweUnq5Tk3nLKTCdcnCw2ifXUpqlD0MMWWZzdNwC4RK+Rw6r/0xq73QgoJhxdVbxtk1VTNPbGrMj1oI8+Fx0I7Ir4K4XxcFB1haEYwHHvgLE0nicNYg9AFEygiZwDwW+O3da9kRtLX8445XqHrcIo51zPgWCWadEIfmfWLTzxgky5Omzhb5/rFpBtqVc3944MdOV8Si0JH1laOOo8GmICdxg2SHXvYWtcTWMhWhNu3eKydyLna+m31vv6FLhUUvUt1LLEgk7Sgthe2OyV2aKyG2qQudR5IU+v1y5EjcgLQCXuqjUhVWFk7Jaw3uzk1W9a243ag0LNtFhDY9Q==', INIT,,,, 'zhangsan', 2023-12-16T12:45:12.435, , 2023-12-16T12:45:12.439 )
[RETURN TOTAL]:                                                                                                         
[RETURN  DATA]:1                                                                                                        
[WASTE   TIME]:31(ms)                                                                                                   
========================================================================================================================
 
2023-12-16 11:26:06.739 [http-nio-8080-exec-9] [INFO] [com.snycedu.platform.component.mybatis.base.service.impl.BaseServiceImpl:43] - 成功插入1条数据 

从数据库看,也是成功插入注册的信息
在这里插入图片描述

六、集成后验证用户登录的解密验证

登录接口

前期提到,SpringSecurity默认实现了登录接口,我们只需要把登录验证逻辑写在Filter中即可

  • 调用登录接口
    在这里插入图片描述

  • 登录UserLoginFilter断点查看账号密码如何认证的
    从下面断点可以看到,认证前登录传入的还是明文密码,数据库的密码是密文,如何进行密码匹配的:前面提到通过用户名和密码构造了UsernamePasswordAuthenticationToken的是实例,然后通过认证管理器beanauthenticationManager进行认证
    在这里插入图片描述

  • 认证管理器和密码解密器
    从下图可以看出,账号密码认证管理器中的加解密器就是我们自定义的RSACryptPasswordEncoder,继续断点,查看明文密码和密文是如何进行验证的,细心的读者其实已经发现了
    在这里插入图片描述

  • 明文密码与密文密码匹配机制
    从下图很明显可以看出,密码的验证就是在我们的RSACryptPasswordEncoder类中的matches方法,方法的参数1就是登录传入的明文密码,参数2就是数据库读取到的加密密文,将密文进行解密与明文密码进行匹配即可验证密码
    在这里插入图片描述

  • 登录验证
    不出意外,登录就可以成功了
    在这里插入图片描述

七、实现ECS的免密登录

既然主要围绕RSA讲,我们平时登录ECS大多是通过账号密码进行登录,每次都要输入密码,现在用RSA的公私钥进行自动验证登录

  • 正常密码登录

在这里插入图片描述

  • ECS开启公钥免密登录

    sudo vim /etc/ssh/sshd_config

修改下面2个配置远程ECS开启公钥免密登录及公钥文件位置
在这里插入图片描述

  • 本地生成免密公钥authorized_keys文件

cp id_rsa.pub authorized_keys

  • 免密登录
    在这里插入图片描述
    可以看到,直接ssh root@xx.xxx.xx.xx 即可免密登录到远程ECS

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

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

相关文章

(2)Linux 操作系统||基本创建与操作

本章将浅谈一下 "操作系统是什么" 的问题&#xff0c;随后通过讲解一些 Linux 下的基本指令&#xff0c;显示目录内容、跳转操作和文件的创建与删除。在讲解的同时我会穿插一些知识点&#xff0c;比如 Linux 隐藏文件、路径等基础知识。 了解操作系统 什么是操作系统…

电子学会C/C++编程等级考试2021年09月(六级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:双端队列 定义一个双端队列,进队操作与普通队列一样,从队尾进入。出队操作既可以从队头,也可以从队尾。编程实现这个数据结构。 时间限制:1000 内存限制:65535输入 第一行输入一个整数t,代表测试数据的组数。 每组数据的…

桌面概率长按键盘无法连续输入问题

问题描述&#xff1a;概率性长按键盘无法连续输入文本 问题定位&#xff1a; 系统按键流程分析 图一 系统按键流程 按键是由X Server接收的&#xff0c;这一点只要明白了X Window的工作机制就不难理解了。X Server在接收到按键后&#xff0c;会转发到相应程序的窗口中。在窗…

海洋可视化大屏,Photoshop源文件

数据大屏通过实时的数据展示&#xff0c;可及时发现数据的变化和异常&#xff0c;以便及时采取措施。现分享海洋动力大数据监控、海洋数据监控系统、科技感海洋监控系统大屏模版的UI源文件&#xff0c;供UI设计师们快速获取PSD源文件完成工作 若需更多 大屏组件&#xff0c;请…

基于linux系统的Tomcat+Mysql+Jdk环境搭建(一)vmare centos7 设置静态ip和连接MobaXterm

特别注意&#xff0c;Windows10以上版本操作系统需要下载安装VMware Workstation Pro16及以上版本&#xff0c;安装方式此处略。 (可忽略 my*** 记录设置的vamare centos7 账号root/aaa 密码&#xff1a;Aa123456 ) 1、命令行和图形界面切换 如果使用的是VMware虚拟机&…

用Java实现根据数据库中的数量,生成年月份+序号递增

在日常开发中&#xff0c;经常会遇到根据年月日和第几号文件生成对应的编号&#xff0c;今天给大家提供一个简单的工具类 public static final Long CODE1L;/*** param select 数据库中数据总数* return*/public static String SubjectNo(Long select){// 在总数的基础上1&…

C#有望成为2023年的编程语言之王

前言 TIOBE 2023年12月编程语言指数头条新闻&#xff1a;C#有望成为2023年的编程语言之王。 TIOBE是什么&#xff1f; 访问地址&#xff1a;https://www.tiobe.com/tiobe-index/ TIOBE是一个编程社区指数&#xff0c;用于衡量不同编程语言的受欢迎程度。TIOBE指数基于全球范围…

t-SNE高维数据可视化实例

t-SNE&#xff1a;高维数据分布可视化 实例1&#xff1a;自动生成一个S形状的三维曲线 实例1结果&#xff1a; 实例1完整代码&#xff1a; import matplotlib.pyplot as plt from sklearn import manifold, datasets """对S型曲线数据的降维和可视化"&q…

根据星历文件实现卫星的动态运行模拟matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 MATLAB2022a 3.部分核心程序 .................................................................................... % …

算法竞赛备赛进阶之树形DP训练

目录 1.树的最长路径 2.树的中心 3.数字转换 4.二叉苹果树 5.战略游戏 6.皇宫守卫 树形DP是一种动态规划方法&#xff0c;主要用于解决树形结构的问题。在树形DP中&#xff0c;通常会使用动态规划的思想来求解最优化问题。其核心在于通过不断地分解问题和优化子问题来解决…

CSS篇之圆角梯形

附上一篇文章&#xff1a;梯形tab按钮-基于clip-path path函数实现 - JSRUN.NET 他这个区别在于&#xff0c;收尾两侧都是直角的&#xff0c;如图 下面这个是圆角&#xff1a; 思路&#xff1a; 代码如下&#xff1a; <template><div class"wrap"><…

Kali Linux安装Xrdp远程桌面工具结合内网穿透实现远程访问Kali桌面

文章目录 前言1. Kali 安装Xrdp2. 本地远程Kali桌面3. Kali 安装Cpolar 内网穿透4. 配置公网远程地址5. 公网远程Kali桌面连接6. 固定连接公网地址7. 固定地址连接测试 前言 Kali远程桌面的好处在于&#xff0c;它允许用户从远程位置访问Kali系统&#xff0c;而无需直接物理访…

Lit官方入门示例

陈拓 2023/12/17-2023/12/17 1. 简介 在《用Vite构建Lit项目》 https://blog.csdn.net/chentuo2000/article/details/134831884?spm1001.2014.3001.5501 一文中我们介绍了怎样用Vite构建Lit项目。 本文我们介绍不依赖Vite的Lit入门示例。 我的开发环境还是和上文相同。 …

Vue3+hooks快速接入Lodop打印插件

文章目录 前言一、下载并修改LodopFuncs.js1.1 调整LodopFuncs.js代码&#xff0c; 暴露 getLodop 二、自定义useLodop hooks抽取共用的lodop逻辑CheckLodopIsOkgetPrinterArrprintLabelprintA4Paper 总结 前言 上面文章《Vue快速接入菜鸟打印组件》讲了vue3如何快速集成菜鸟打…

蓝桥杯专题-真题版含答案-【骑士走棋盘】【阿姆斯壮数】【Shell 排序法 - 改良的插入排序】【合并排序法】

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列ChatGPT和AIGC &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分…

字符迷宫(期末考模拟题)

很有趣的一道题 难点主要在于对于 * 的处理 题目描述的是可以多次匹配相同的字母&#xff0c;这就涉及到两个方面&#xff1a; 一是这个匹配的相同的字母如何储存 二是当你’ * ‘位置递归结束的时候&#xff0c;你该什么时候变回‘ * ’号 这里给出我的思路&#xff0c;如…

索尼(ILCE-7M3)MP4文件只能播放前两分钟修复案例

索尼的ILCE-7M3是一款经典设备&#xff0c;其HEVC编码效果是比较不错的&#xff0c;因此受到很多专业人士的青睐。之前我们说过很多索尼摄像机断电生成RSV文件修复的案例&#xff0c;今天来讲一个特殊的&#xff0c;文件已经正常封装但仅能播放前两分钟多一点的画面。 故障文件…

用23种设计模式打造一个cocos creator的游戏框架----(十六)亨元模式

1、模式标准 模式名称&#xff1a;亨元模式 模式分类&#xff1a;结构型 模式意图&#xff1a;运用共享技术有效地支持大量细粒度的对象 结构图&#xff1a; 适用于&#xff1a; 1、一个应用程序使用了大量的对象. 2、完全由于使用大量的对象&#xff0c;造成很大的存储开…

信号与线性系统预备训练3——MATLAB软件在信号与系统中的应用初步

信号与线性系统预备训练3——MATLAB软件在信号与系统中的应用初步 The Preparatory training3 of Signals and Linear Systems 对应教材&#xff1a;《信号与线性系统分析&#xff08;第五版&#xff09;》高等教育出版社&#xff0c;吴大正著 一、目的 1.熟悉和回顾MATLAB…

MYSQL练题笔记-高级字符串函数 / 正则表达式 / 子句-简单3题

这个系列先写了三题&#xff0c;比较简单写在一起。 1.修复表中的名字相关的表和题目如下 看题目就知道是有关字符串函数的&#xff0c;于是在书里查询相关的函数&#xff0c;如下图&#xff0c;但是没有完全对口的函数&#xff0c;所以我还是去百度了。 然后发现结合上面的4个…