软件产品许可证书 Licence 全流程研发(使用非对称加密技术,既安全又简单)

news2025/2/22 20:35:08

本篇博客对应的代码地址:

Gitee 仓库地址:https://gitee.com/biandanLoveyou/licence

源代码百度网盘链接: https://pan.baidu.com/s/1_ZhdcENcrk2ZuL11hWDLTQ?pwd=bmxi 提取码: bmxi

1、背景介绍

公司是做软件 SAAS 服务的,一般来说软件部署有以下几种常见的模式:

1、自己研发和部署到自己的云服务器,然后有偿提供账号给客户使用。代码开发和服务运维都是本公司自己的,客户不需要关心软件的事情。这种模式只需要对提供的账号做权限管控就行。这种模式一般适用于客户没有自己的研发团队,或者客户的研发团队不涉及该领域。

2、自己研发,但是需要部署到客户的云服务器或者私有化服务器,但是不交付源代码。这种模式有一个需要考虑的点:一旦部署到客户的云服务器或者私有化服务器,系统就变得不好管控或者说失去了控制。体现在:一旦过了合同有效期,想停掉运行在客户服务器的系统,那就变得很困难,或者对方不配合,或者对方也可以找运维人员自启动(本文先不考虑客户拿到 jar 包反编译源代码的情况,如果合同有代码协议的约束,那就是违法的)。本篇博客主要针对这种模式做 Licence 研发,让私有化部署的系统变得可管控

3、自己研发,帮助客户部署到指定服务器,并且交付源代码。

2、研发思路

2.1 不可行的方案

1、合同约束或者口头约束。一般来说,两家公司不合作之后,基本上关系就变得冷淡或者陌路,跟你在一家公司离职的情况差不多。想要口头约束客户别用你们之前的系统,几乎不可能,只要还能用,客户也会偷偷用,实在用不了才会考虑替代方案,这是人之常情。

2、把“能用”和“不能用”的控制逻辑放在数据库或者配置文件中。这种方案也不太行,懂一点技术人会顺藤摸瓜,把他们的数据库或者配置文件看一下,哪些是重要信息,修改一下,就能继续用了。

因此,我们需要考虑一个万全之策,既能做到系统的管控,又能做到后期的简单维护。那就是把控制逻辑嵌入到代码中(本文先不考虑客户拿到 jar 包反编译源代码的情况,如果合同有代码协议的约束,那就是违法的)

2.2 什么是证书?

证书相当于一个许可证,各行各业都有自己的标准和形式。

  • 你毕业了,能拿到毕业证书,这是对你学历的证明。
  • 你要出国,要办理护照,这是你合法入镜的证明。
  • 你要去香港、澳门,要办理港澳通行证。
  • 你要结婚,去民政局办理结婚证书。等等

软件行业的证书也是因情况而定,可以根据公司的发展来制定属于你们公司的证书。最终解释权都属于你们公司,只要你们公司认可,那就是有效。

2.3 使用证书的方案

思路如下:

管理后台生成证书 —> 编写证书的校验逻辑并打成 jar 包 —> 把 jar 包嵌入到私有化部署的代码中 —> 考虑证书到期的时候可以方便替换(且无需重启服务) —> 考虑证书到期前的提醒

我们可以用学过的技术,把证书的方案落地。核心技术采用【非对称加密+拦截器】,旨在“让天下没有难写的代码”。

不懂非对称加密?查看我的博客:使用 Java 原生或 Hutool 工具包编写非对称加解密的工具类-CSDN博客

3、代码实现

3.1 代码结构

说明:

  1. certificate:这是存放证书的目录,在开发阶段我们方便去做演示。如果实际应用,要考虑 Linux 环境和 docker 环境(需要考虑挂载)。后续如果更换证书,直接用新证书替换旧的证书即可,无需重启服务,对业务没有任何影响
  2. client:简单的测试客户端,代码只有几行。
  3. client-offline:私有化部署时的客户端,需要把 core 包打进去,代码也是几行,做演示
  4. core:证书的核心校验包(重点)
  5. server:管理后台或者用于生成证书的服务,一般是业务系统的管理平台。

3.2 管理后台(服务端)代码实现

管理后台代码结构:

3.2.1 证书模型介绍

证书的模型(实体)我们写在 LicenceEntity 这个实体类,核心字段如下:

public class LicenceEntity implements Serializable {

    private static final long serialVersionUID = -4048081970386334457L;

    /**
     * 证书 ID
     */
    private String licenceId;

    /**
     * 证书名称
     */
    private String licenceName;

    /**
     * 客户端机器的网卡物理地址
     */
    private String mac;

    /**
     * 秘钥(指的是公钥)
     */
    private String key;

    /**
     * 证书生效开始日期,格式:yyyy-MM-dd
     */
    private String effectStartDate;

    /**
     * 证书生效结束日期,格式:yyyy-MM-dd
     */
    private String effectEndDate;

    /**
     * 颁发证书联系人
     */
    private String contactName;

    /**
     * 颁发证书人的联系方式
     */
    private String contactWay;

    /**
     * 证书所有者
     */
    private String owner;

    /**
     * 加密的内容。这个字段是将其它字段加密后的完整内容
     */
    private String content;

}

说明

1、mac:这是客户端的网卡物理地址,每台机器的物理地址都不一样,基本上可以保证你们公司服务的客户是唯一的。这个 mac 地址,一般是负责部署的人员去获取,或者让客户自己提供。这个字段的意义在于:如果你们公司要进行严格的证书校验,必须是一个证书只允许在一台服务器上使用,那这个字段就显得非常重要。当然,你可以换成自己喜欢的字段名。

2、effectStartDate、effectEndDate:证书的生效起止时间字段。这两个字段是证书的核心字段,用来判断证书是否在有效期内。

3、key:公钥。根据非对称加密的内容,我们需要把公钥对外,客户端拿到公钥后,可以解密我们用私钥加密的内容。

4、content:证书数据的加密内容。这个字段,是把证书实体转成 JSON 字符串后,再进行非对称加密后的数据。为什么要有这个字段呢?考虑的因素是:①充分利用了非对称加解密的技术,私钥加密的内容只允许公钥解密。如果这个数据被篡改,证书就失效。②其它字段的明文,比如:证书的生效起止时间、证书的联系人等,方便客户拿到证书后,直观的看到这些信息。

5、以上证书的字段,可以根据自己的业务需要去拓展。但是核心的几个字段,最好能保留。

3.2.2 核心实现类

管理后台的核心,就是准确的生成证书文件 licence.txt,核心代码如下:

package com.study.service.impl;

import com.study.constant.CommonKeys;
import com.study.entity.LicenceEntity;
import com.study.service.LicenceService;
import com.study.util.LicenceJsonUtil;
import com.study.util.NativeSecurityUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

/**
 * @author CSDN 流放深圳
 * @description 证书生成核心实现类
 * @create 2024-04-13 15:55
 * @since 1.0.0
 */
@Service
public class LicenceServiceImpl implements LicenceService {

    private static Logger log = LoggerFactory.getLogger(LicenceServiceImpl.class);

    /**
     * 获取配置文件的私钥
     */
    @Value("${licence.privateKey}")
    private String privateKey;

    /**
     * 获取配置文件的公钥
     */
    @Value("${licence.publicKey}")
    private String publicKey;


    /**
     * 创建证书 licence 内容
     * @param dtoEntity
     * @return
     */
    @Override
    public LicenceEntity createLicence(LicenceEntity dtoEntity) {
        LicenceEntity entity = new LicenceEntity();
        //赋值相同属性
        BeanUtils.copyProperties(dtoEntity, entity);
        //content 和 key 需要额外处理
        if(StringUtils.isEmpty(privateKey) || StringUtils.isEmpty(publicKey)) return null;
        entity.setKey(publicKey);//把公钥放进去,否则客户端无法获取公钥,就无法解密
        //把实体转成字符串
        String json = LicenceJsonUtil.objectToStr(entity);
        //把整个字符串加密
        String content = NativeSecurityUtil.encryptByPrivateKey(json, privateKey);
        //把加密后的字符串赋值给 content
        entity.setContent(content);
        return entity;
    }

    /**
     * 下载证书文件
     * @param dtoEntity
     * @param response
     */
    @Override
    public void downLoadLicence(LicenceEntity dtoEntity, HttpServletResponse response) {
        LicenceEntity licenceEntity = createLicence(dtoEntity);
        if(null == licenceEntity){
            log.error("证书的秘钥未配置!");
            return;
        }
        //把实体转为字符串
        String result = LicenceJsonUtil.objectToStr(licenceEntity);
        BufferedOutputStream out = null;
        try {
            //证书文件名
            String fileName = CommonKeys.CERTIFICATE_FILE;
            response.setContentType("application/octet-stream");
            response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
            response.setCharacterEncoding("UTF-8");
            out = new BufferedOutputStream(response.getOutputStream());
            out.write(result.getBytes("UTF-8"));
            out.flush();
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }

        //TODO 另外可以保存一份到指定目录下,方便测试(这是使用异步的形式)。上线的时候记得注释掉
        saveLicenceToCertificate(result);

    }

    /**
     * 异步保存证书文件到指定目录
     * @param result
     */
    @Async
    public void saveLicenceToCertificate(String result){
        try{
            // 创建证书目录(如果尚未创建)
            Path directory = Paths.get(CommonKeys.CERTIFICATE_DIRECTORY);
            Files.createDirectories(directory);
            // 构建证书文件的完整路径
            Path filePath = directory.resolve(CommonKeys.CERTIFICATE_FILE);
            // 检查文件是否存在,存在则删掉
            Files.deleteIfExists(filePath);
            //创建文件(如果文件已经存在,此步骤可能会抛出 FileAlreadyExistsException)
            Files.createFile(filePath);
            //写入内容到文件,使用 StandardOpenOption.APPEND 可以追加内容而不是覆盖
            Files.write(filePath, result.getBytes("UTF-8"), StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
        }catch (Exception e){
            log.error(e.getMessage(), e);
        }
    }

}

说明:

1、我们采用私钥加密的方式,把实体转为 JSON 字符串加密后赋值给 content 字段。

2、对外暴露公钥,用于给客户端拿到证书后,通过公钥解密出 content,然后做校验。

3.2.3 业务层

案例中的业务层 ServerController 比较简单,可以根据实际业务中去拓展,这里只做了模拟数据:

package com.study.controller;

import com.study.entity.LicenceEntity;
import com.study.service.LicenceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import java.util.UUID;

/**
 * @author CSDN 流放深圳
 * @description 控制层
 * @create 2024-04-13 15:55
 * @since 1.0.0
 */
@RestController
@RequestMapping("/server/licence")
public class ServerController {

    @Autowired
    private LicenceService licenceService;

    /**
     * 下载证书文件
     * 实际项目可以根据前端传递的参数来创建证书字段属性。这里为了测试,直接写测试数据
     * @param response
     */
    @PostMapping("/downLoadLicence")
    public void downLoadLicence(HttpServletResponse response){
        LicenceEntity dtoEntity = createEntity();
        licenceService.downLoadLicence(dtoEntity, response);
    }

    /**
     * 创建测试实体
     * @return
     */
    private LicenceEntity createEntity(){
        LicenceEntity entity = new LicenceEntity();
        entity.setLicenceId(UUID.randomUUID().toString().replaceAll("-", ""));//证书 ID
        entity.setLicenceName("我的 Licence 证书");//证书名称
        entity.setMac("");//客户端机器的网卡物理地址,要么留空,要么输入客户端的 Mac 地址
        entity.setEffectStartDate("2024-04-15");//证书生效开始日期,格式:yyyy-MM-dd
        entity.setEffectEndDate("2025-04-15");//证书生效结束日期,格式:yyyy-MM-dd
        entity.setContactName("CSDN 流放深圳");//联系人
        entity.setContactWay("https://blog.csdn.net/BiandanLoveyou");//联系方式
        entity.setOwner("CSDN 流放深圳");//证书所有者
        return entity;
    }

}

说明:

1、管理后台的业务流程一般是先生成证书文件,然后把证书文件下载,给到客户端。

2、如果需要严格的校验一个证书对应一台服务器,那就需要赋值 mac 字段

3、其它字段可以按需赋值

更多具体详情,请查看源代码。

3.3 证书校验核心 Jar 代码实现

代码结构:

代码说明:

1、CheckLicence:证书注解类。一般来说,都是全部的接口都要校验证书是否有效,但是在一些场景下,只需要一部分接口校验证书是否有效,比如:可以让客户使用基础的功能,如果涉及到核心的功能,就需要证书授权。在业务层(Controller)加了这个注解,表示该方法需要校验证书是否有效。当然,还可以通过 yml 配置文件来配置是否全量校验证书。详细看代码。

2、DirectoryInitializer:初始化证书的目录。在项目启动后,会在项目下创建用来存放证书文件的目录,方便程序去找到证书文件来解析。

3、LicenceInterceptor:web拦截器。在请求进入业务层(Controller)做一个前置的拦截,用来判断证书是否有效,有效才放行。

4、LicenceWebConfig:WebMVC配置类,用来配置 LicenceInterceptor 拦截器。拦截所有的请求。

5、CommonKeys:定义常量类。

6、LicenceEntity:证书模型实体类。与管理后台的模型字段保持一致。

7、LicenceEnum:证书校验信息枚举类。证书校验会有很多种类的异常,可以在这里统一定义。

8、LicenceExceptionAdvance:全局异常捕捉类。这是企业级开发基本会有的内容,这里只捕捉了运行时异常。而全局异常捕捉,应该交给外层去处理。

9、LicenceRuntimeException:运行时异常。用来给使用 jar 包的程序抛出运行时异常信息。

10、LicenceJob:证书校验的定时任务类。目前设定 10 分钟执行一次,判断证书是否有效,并把有效(或者无效)的信息放入到内存(LicenceInterceptor 拦截器使用)中。避免每次请求都去读取证书文件再判断,那样的话性能急剧降低。10分钟的频率还可以接受,如果要更换证书文件,最长的时间窗口就是等待 10 分钟。如果旧的证书还没过期,更换新的证书,那就没有等待期。

11、LicenceCheckServiceImpl:证书校验的核心实现类。详细看代码,备注齐全!

12、LicenceCheckService:证书校验的接口类。

13、CallResult:接口调用统一返回对象。

14、LicenceDateUtil:Java原生的日期处理工具类。

15、LicenceJsonUtil:Java原生的 JSON 工具类。

16、LicenceSecurityUtil:Java原生的非对称加、解密工具类。

17、MachineAddrUtil:Java原生的机器信息获取工具类。

说明:这里的工具类全部用 Java 原生的代码编写,主要是避免依赖第三方组件。如果依赖第三方组件,那就要把第三方组件的 Jar 包也打进来,就会导致“胖 Jar 包”,显得臃肿。一般来说,优秀的开源组件,都是“瘦 Jar 包”,自己封装 Java 原生的代码自己使用,极少引入第三方组件。

pom.xml

    <dependencies>
        <!-- web支持,注意:请一定要在引入该 jar 包的主程序中增加 web 支持(2.x 以上的版本),否则拦截器将失效!! -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

注意:

因为 core 核心包使用到 web 拦截器,所以需要 web 组件的依赖。本项目使用的是 SpringBoot 框架,如果是 SpringMVC 框架则不适用或者 SSH 框架则不适用。

在 core 的 pom 里加这个依赖,主要是提醒使用者,需要在外层的应用中有这个依赖。此依赖的版本要求是 SpringBoot 2.X 以上。

如果没有加这个依赖,core 的核心代码就会失效,也就是无法校验证书。

更多详情,想看代码。

3.4 客户端代码实现及注意事项

3.4.1 client 客户端

代码结构:

说明:

1、pom.xml 主要是使用 maven 的方式依赖了 core 包,这种方式一般是项目都在同一个父级中开发,直接使用 maven 坐标就可以找到 core 包。

    <dependencies>
        <!-- core 包的依赖 -->
        <dependency>
            <groupId>com.study</groupId>
            <artifactId>core</artifactId>
            <version>1.0.0.RELEASE</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

2、业务层只是简单校验是否可以访问。因为 core 里面使用了【拦截器】来校验证书,所以只需要一个 Controller 就可以测试证书的有效性了。

    @GetMapping("/hello")
    public CallResult hello() {
        return CallResult.success("证书验证通过!If you can see this message, it means your licence is effective.");
    }

3.4.2 client-offline

代码结构:

说明:

offline-client 跟 client 差不多。但是需要注意的是,离线版的客户端,需要我们把 core 包打好,复制到 resource 下的 lib 文件夹,然后作为第三方库加入进来使用。

offline-client 的 pom.xml 代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>licence</artifactId>
        <groupId>com.study</groupId>
        <version>1.0.0.RELEASE</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>client-offline</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.study</groupId>
            <artifactId>core</artifactId>
            <version>1.0.0.RELEASE</version>
            <!-- 引用一个本地的 JAR 文件,而不是从 Maven 的中央仓库或其他远程仓库中获取 -->
            <scope>system</scope>
            <!-- 指定该 JAR 文件的路径 -->
            <systemPath>${basedir}/src/main/resources/lib/core-1.0.0.RELEASE.jar</systemPath>
        </dependency>
    </dependencies>

    <!-- 构建 -->
    <build>
        <plugins>
            <!-- 打包插件 -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <!-- 把 scope 为 system 的包也打进来 -->
                <configuration>
                    <includeSystemScope>true</includeSystemScope>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

3.5 使用过程详解(保姆式)

3.5.1 启动管理后台,生成证书

启动好管理后台,使用 POST 方式调用接口(使用 Postman 或者 Apipost 工具都可以):

http://127.0.0.1:9000/server/licence/downLoadLicence

Apipost 测试结果(点击右边箭头处可以下载文档): 

另外,可以在项目下的 certificate 目录看到 licence.txt 证书文件

3.5.2 启动 client 客户端

可以看到项目启动后,就马上执行 core 包里的定时任务 LicenceJob 里面校验证书的方法。

访问接口:http://127.0.0.1:8000/client/licence/hello

证书校验通过。

3.5.3 处理  client-offline 客户端

client-offline 是通过导入 core 的 jar 包方式的,然后再通过 maven 坐标依赖进去,所以需要单独处理。

首先把 core 包打出来。在 IDEA 工具右侧 Maven,找到 core 包下的 package,双击:

结果:

去对应的目录下,找到该 jar 包:

把打好的 core-1.0.0.RELEASE.jar 复制出来,粘贴到 client-offline 的 resource 下的 lib 目录下。

这时候 pom.xml 里的配置就会读取到放在 resource 下的 lib 目录下的 jar 包。

启动 client-offline 服务:

 访问接口:http://127.0.0.1:8888/offline/hello

结果(中文乱码不要紧,测试而已):

至此,完整流程搞定。

剩下还有几个内容可以自己去验证:

1、mac 地址,验证一台机器是否对应一个 licence

2、验证证书的有效起止时间

3、@CheckLicence 注解的验证,看下非全量验证的情况下,加与不加 @CheckLicence 注解是否正常放行。

4、如果客户端不加 core 包必要的 web 依赖,证书验证是否生效

5、修改证书的部分内容(特别是 content)部分,并且等到下一个定时任务运行,看下证书的校验是否通过。

Gitee 仓库地址:https://gitee.com/biandanLoveyou/licence

源代码百度网盘链接: https://pan.baidu.com/s/1_ZhdcENcrk2ZuL11hWDLTQ?pwd=bmxi 提取码: bmxi

—  end —

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

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

相关文章

《操作系统导论》第26章读书笔记:并发:介绍

《操作系统导论》第26章读书笔记&#xff1a;并发&#xff1a;介绍 —— 杭州 2024-04-18 夜 文章目录 《操作系统导论》第26章读书笔记&#xff1a;并发&#xff1a;介绍0.前言1.实例&#xff1a;线程创建(略)2.为什么更糟糕&#xff1a;共享数据(略)3.核心问题&#xff1a;不…

基于Springboot+Vue的Java项目-企业客户管理系统开发实战(附演示视频+源码+LW)

大家好&#xff01;我是程序员一帆&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &am…

uiautomation、pytest、schedule实现桌面程序自动化(初级)02

一&#xff1a;安装uiAutomation 前置条件:安装python、pycharm 命令行安装 Pip install uiautomation2.0.17 #指定版本 二&#xff1a;安装辅助工具&#xff1a;inspect.exe和、Accessibility Insights For Windows定位元素工具 辅助工具介绍 步骤中提到…

【每天一个linux小知识】tailf 和 tail -f

目录 典型使用场景区别 典型使用场景 如果我们实时查看文件的末尾内容&#xff0c;特别是当文件持续写入时&#xff0c;可以使用tailf 和 tail -f。两者都可以显示文件的最后几行内容&#xff0c;并且在文件被追加新内容时&#xff0c;会实时显示这些新内容。 tailf演示 区别 …

逆向案例二十八——红某点集登录接口逆向序

网址&#xff1a;aHR0cHM6Ly93d3cuaHJkanl1bi5jb20vIy9sb2dpbj9yZWRpcmVjdD0lMkZyZWFsVGltZUxpdmluZw 登录接口&#xff0c;发现两个参数加密&#xff0c;分别是pwd和sig,t很明显是时间戳。 观察pwd,发现很像md5加密&#xff0c;我输入的密码是123456&#xff0c;在在线加密网…

mac安装nvm详细教程

0. 前提 清除电脑上原有的node (没有装过的可以忽略)1、首先查看电脑上是否安装的有node,查看node版本node -v2、如果有node就彻底删除nodesudo rm -rf /usr/local/{bin/{node,npm},lib/node_modules/npm,lib/node,share/man/*/node.*}2、保证自己的电脑上有安装git,不然下载n…

Python基于Django的旅游城市关键词分析和提取,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

基于Java+SpringBoot+Mybaties-plus+Vue+elememt 小区物业管理系统 的设计与实现

一.项目介绍 系统分为管理员 和 业主 两块&#xff1a; 管理员点击进入到系统操作界面&#xff0c;可以对首页、业主信息管理、管理员信息管理、 楼栋和房屋信息管理、物业费管理、地下停车位管理、公告信息管理、报修信息管理、 投诉管理以及个人信息等功能模块 …

数字逻辑课程实验环境配置与使用说明

文章目录 I.虚拟机搭建1.1 Vmware安装1.2 Win XP安装1.3 xftp7安装 I. Quartus II安装II. 使用说明2.1 新建工程2.2 在工程中加入代码2.3 代码编译波形仿真 I.虚拟机搭建 1.1 Vmware安装 Vmware17安装教程 1.2 Win XP安装 Win XP安装教程 1.3 xftp7安装 给虚拟机添加FTP …

部署wordpress

查看别名type ll ll 是 ls -l --colorauto 的别名 设置别名alias alias ymyum install -y 使用别名ym nginx 取消别名unalias ym 基于LNMP做一个wordpress nginx mysql 5.7 PHP 7.4 1、linux基本环境 修改主机名 hostnamectl set-hostname $name 关闭防火墙及selinux …

python爬豆瓣top250电影

文章目录 前言分析与实现1.对豆瓣网网站进行Ajax分析2.发送请求3.进一步筛选&#xff08;提取&#xff09; 完整代码 前言 通过这个项目&#xff0c;可以让小白对爬虫有一个初步认识&#xff0c;爬取豆瓣top250是一个初学者学爬虫的必经之路&#xff0c;话不多说&#xff0c;我…

【缓存常见问题】

在使用缓存时特别是在高并发场景下会遇到很多问题&#xff0c;常用的问题有缓存穿透、缓存击穿、缓存雪崩以及缓存一致性问题。 1、缓存穿透 首先&#xff0c;什么是缓存穿透呢&#xff1f; 缓存穿透是指请求一个不存在的数据&#xff0c;缓存层和数据库层都没有这个数据&…

图片各种格式区别介绍:

图片各种格式区别介绍&#xff1a; JPEG格式&#xff08;Joint Photographic Experts Group&#xff09; JPEG格式一种有损压缩格式&#xff0c;能够将图像压缩在很小的储存空间&#xff0c;图像中重复或不重要的资料会被丢失&#xff0c;因此容易造成图像数据的损伤。尤其是…

目标检测——防护装备数据集

一、重要性及意义 防护装备中的头盔和背心检测具有至关重要的重要性和深远的意义&#xff0c;主要体现在以下几个方面&#xff1a; 首先&#xff0c;它们对于保护工作人员的人身安全起着至关重要的作用。在各类工作环境中&#xff0c;尤其是那些涉及高空作业、机械操作或交通…

Python教学入门:数字类型与字符串

数字类型&#xff08;Numeric Types&#xff09;&#xff1a; 整数&#xff08;int&#xff09;&#xff1a; 在 Python 中&#xff0c;整数是不可变的&#xff0c;可以表示正整数、负整数和零。 Python 中整数的大小仅受限于计算机的内存。 # 定义整数变量 num1 10 num2 -…

【Linux系统编程】第五弹---基本指令(三)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、grep指令 2、zip/unzip指令 3、tar指令 4、bc指令 5、uname指令 6、重要的几个热键 7、拓展指令 总结 1、grep指令 …

Pulsar【部署 01】消息队列PULSAR在Linux环境的主程序安装使用(小内存服务器问题处理)

官方网站&#xff1a;https://pulsar.apache.org/ 对于本地开发和测试&#xff0c;您可以在机器上以单机模式运行Pulsar。单机模式在单个Java虚拟机(JVM)进程中运行所有组件。本地单机版安装手册&#xff1a; 消息队列PULSAR在Linux环境的主程序安装使用 1.Prerequisites1.1 Pu…

四种算法(麻雀搜索算法SSA、螳螂搜索算法MSA、红尾鹰算法RTH、霸王龙优化算法TROA)求解机器人路径规划(提供MATLAB代码)

一、机器人路径规划介绍 移动机器人&#xff08;Mobile robot&#xff0c;MR&#xff09;的路径规划是 移动机器人研究的重要分支之&#xff0c;是对其进行控制的基础。根据环境信息的已知程度不同&#xff0c;路径规划分为基于环境信息已知的全局路径规划和基于环境信息未知或…

Flink KafkaSink分区配置的不同版本对比

Flink KafkaSink分区配置的不同版本对比 在不同版本的Flink中&#xff0c;KafkaSink 分区默认配置方式可能会有一些变化。以下是摘自Flink官方文档不同版本的原文&#xff1a; 1. Flink版本&#xff1a;1.12~1.19 Sink 分区 # 配置项 sink.partitioner 指定了从 Flink 分区到 …

12个可能未使用过的Python特性

大多数程序员不知道的令人难以置信的功能列表。 Python 是顶级编程语言之一&#xff0c;它具有许多程序员从未使用过的许多隐藏功能。在这篇文章中&#xff0c;我将分享你可能从未使用过的13 个 Python 特性。 1.列表Stepping 这是一个 step 参数&#xff0c;可以通过采取几…