SpringBoot项目中添加证书授权认证

news2024/11/24 11:52:35

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 一、项目场景
  • 二、方案思路
  • 三、实施流程
    • 1.引入库
    • 2.编写代码
  • 四、拓展


一、项目场景

在上线的项目中,需要添加一个定时授权的功能,对系统的进行授权认证,当授权过期时提示用户需要更新授权或获取授权,不让用户无限制的使用软件。


二、方案思路

在查阅相关资料进行整理后,对该场景做了一套解决方案,大致的思路如下:

  • 使用smart-license-1.0.3工具生成校验证书文件(会根据输入的时长和密码进行授权),工具已上传至百度网盘。
链接:https://pan.baidu.com/s/1OXNjw_rgPC3POW5UXTxLcQ?pwd=a0pl 
提取码:a0pl
  • 由于授权证书只允许能够在指定的服务器上使用,所以这里我将授权密码设置为指定服务器的mac地址加上一段自定义的密码,在验证时动态获取软件部署机器的mac地址进行验证(利用mac地址的唯一性)。

  • 由于该证书会自动根据授权时长自动生成结束授权时间,所以为了防止用户修改机器时间去无限使用,所以从数据库任意表读取一个最新时间作为基础时间,然后每次访问操作都去更新和比对这个时间,当发现本次操作比上次操作的时间靠前时,让证书失效。

三、实施流程

1.引入库

        <!-- swagger2 依赖-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>${knife4j.version}</version>
        </dependency>
        
        <!--许可依赖-->
        <!--smart-license 1.0.3授权-->
        <dependency>
            <groupId>org.smartboot.license</groupId>
            <artifactId>license-client</artifactId>
            <version>1.0.3</version>
        </dependency>

2.编写代码

  • 先配置一个系统的缓存,对授权证书文件等其他信息进行缓存
package com.starcone.common.syscenter;


import org.smartboot.license.client.LicenseEntity;

import java.io.File;

/**
 * @Author: Daisen.Z
 * @Date: 2021/7/13 11:41
 * @Version: 1.0
 * @Description: 系统缓存中需要存储的信息
 */
public class SysCacheInfo {

    // 系统加载的证书文件信息
    public static LicenseEntity licenseEntity  = null;

    // 最近一次系统的操作时间
    public static long latOptTimestmp;
}

  • 提供一个证书许可加载的工具类
package com.starcone.common.util;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.util.ObjectUtil;
import com.starcone.common.bean.response.ExceptionCast;
import com.starcone.common.bean.response.ResponseResult;
import com.starcone.common.syscenter.SysCacheInfo;
import org.smartboot.license.client.License;
import org.smartboot.license.client.LicenseEntity;
import org.smartboot.license.client.LicenseException;
import org.springframework.util.ResourceUtils;

import java.io.*;
import java.nio.charset.Charset;

/**
 * 许可授权拦截器
 */
public class LicenseUtil {

    // 加载授权文件的方法,该方法必须为单线程
    public synchronized static LicenseEntity loadLocalLEntity(File file) throws Exception {
        // 加载证书,在证书文件过期或无效时该方法会报错
        License license = new License();
        return  license.loadLicense(file);
    }

    public static LicenseCheckResult checkLicenseFile(File file) {
        License license = new License();
        LicenseEntity licenseEntity = null;
        try {
            licenseEntity = license.loadLicense(file);
            String s1 = Md5Util.encodeByMd5(IpUtil.getMACAddress());
            String md5 = licenseEntity.getMd5();
            if (!s1.equals(md5)) {
                // 校验md5值是否相等
                return new LicenseCheckResult(false,"证书文件不匹配");
            }
            return new LicenseCheckResult(true,"");
        } catch (LicenseException e) {
            e.printStackTrace();
            return new LicenseCheckResult(false,"证书文件无效");
        } catch (Exception e) {
            e.printStackTrace();
            return new LicenseCheckResult(false,"证书文件失效");
        }
    }

    // 校验缓存中的licens信息
    public static LicenseCheckResult checkLicense(LicenseEntity licenseEntity) {
        // 授权缓存为空时,先将文件加载至缓存
        if (ObjectUtil.isEmpty(licenseEntity)) {
            return new LicenseCheckResult(false,"未加载证书");
        } else {
            // 校验授权是否被修改
            try {
                String s1 = Md5Util.encodeByMd5(IpUtil.getMACAddress());
                String md5 = licenseEntity.getMd5();
                if (!s1.equals(md5)) {
                    // 校验md5值是否相等
                    return new LicenseCheckResult(false,"证书文件不匹配");
                }
                // 校验授权是否过期
                long expireTime = licenseEntity.getExpireTime(); // 到期时间
                if (System.currentTimeMillis() > expireTime) { // 当前系统时间大于到期时间,说明已经过期
                    return new LicenseCheckResult(false,"证书已过期");
                }
                return new LicenseCheckResult(true,"");
            } catch (LicenseException e) {
                e.printStackTrace();
                return new LicenseCheckResult(false,"证书文件失效");
            } catch (Exception e) {
                e.printStackTrace();
                return new LicenseCheckResult(false,"证书文件失效");
            }
        }
    }

    // 加载授权文件的方法,该方法必须为单线程
    public synchronized static LicenseEntity loadLicenseToCache() throws Exception {
        // 加载证书文件
        ClassPathResource classPathResource = new ClassPathResource("license.txt");
        File file = classPathResource.getFile();
        // 加载证书,在证书文件过期或无效时该方法会报错
        License license = new License();
        SysCacheInfo.licenseEntity = license.loadLicense(file);
        return  SysCacheInfo.licenseEntity;
    }

    public static LicenseCheckResult checkLocalLicenseFile() {
        // 校验文件是否有效
        ClassPathResource classPathResource = new ClassPathResource("license.txt");
        File file = null;
        try {
            file = classPathResource.getFile();
            String absolutePath = ResourceUtils.getFile("classpath:license.txt").getAbsolutePath();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return checkLicenseFile(file);
    }


    // 校验缓存中的licens信息
    public static LicenseCheckResult checkLocalLicenseCache() {
        return checkLicense(SysCacheInfo.licenseEntity);
    }

    public static class LicenseCheckResult{
        private boolean checkResult;
        private String msg;

        public LicenseCheckResult(boolean checkResult, String msg) {
            this.checkResult = checkResult;
            this.msg = msg;
        }

        public LicenseCheckResult() {
        }

        public boolean getCheckResult() {
            return checkResult;
        }

        public void setCheckResult(boolean checkResult) {
            this.checkResult = checkResult;
        }

        public String getMsg() {
            return msg;
        }

        public void setMsg(String msg) {
            this.msg = msg;
        }
    }

    // 更新本地文件
    public static void updateLocalLicense(File file) throws IOException {
        BufferedInputStream inputStream = FileUtil.getInputStream(file);
        InputStreamReader streamReader = new InputStreamReader(inputStream);
        BufferedReader reader = new BufferedReader(streamReader);
        String line;
        StringBuilder stringBuilder = new StringBuilder();
        while ((line = reader.readLine()) != null) {
            stringBuilder.append(line);
        }
        reader.close();

        ClassPathResource classPathResource = new ClassPathResource("license.txt");
        File file1 = null;
        try {
            file1 = classPathResource.getFile();
            String absolutePath = ResourceUtils.getFile("classpath:license.txt").getAbsolutePath();
            FileUtil.writeString(String.valueOf(stringBuilder),file1, Charset.forName("UTF-8"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

  • 前后端交互的Controller类
package com.starcone.web.controller;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import com.starcone.common.bean.response.ExceptionCast;
import com.starcone.common.bean.response.ResponseResult;
import com.starcone.common.syscenter.SysCacheInfo;
import com.starcone.common.util.IpUtil;
import com.starcone.common.util.LicenseUtil;
import com.starcone.common.util.LogHelper;
import com.starcone.common.util.Md5Util;
import com.starcone.service.SysUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.smartboot.license.client.License;
import org.smartboot.license.client.LicenseEntity;
import org.smartboot.license.client.LicenseException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.nio.charset.Charset;
import java.util.Date;
import java.util.UUID;

/**
 * @Author: Daisen.Z
 * @Date: 2024/1/17 15:15
 * @Version: 1.0
 * @Description:
 */
@RestController
@RequestMapping("/licensManager")
@Api(value = "LicensController", tags = {"授权管理接口"})
public class LicensManagerController {

    @Autowired
    private SysUserService sysUserService;

    @Autowired
    private LogHelper logHelper;

    @GetMapping("/getEuqMac")
    public ResponseResult addDemo() throws Exception {
        ;return ResponseResult.success(IpUtil.getMACAddress());
    }

    // http://localhost:8080/track/licensManager/reloadLicens
    @ApiOperation(value = "重新加载Licens和校准时钟", notes = "授权管理接口", produces = "application/json")
    @GetMapping("/reloadLicens")
    public ResponseResult reloadLicens() throws Exception {
        // 校准一下时钟信息
        SysCacheInfo.latOptTimestmp = System.currentTimeMillis();
        LicenseUtil.loadLicenseToCache();
        ;return ResponseResult.success("过期时间"+ DateUtil.format(new Date(SysCacheInfo.licenseEntity.getExpireTime()),"yyyy-MM-dd HH:mm:ss"));
    }



    /**
     * 基站信息上传
     * @return
     * @throws IOException
     */
    @PostMapping("/uploadLicens")
    public ResponseResult upload(MultipartFile file){
        File file1 = FileUtil.createTempFile(new File(""));
        try {
            file.transferTo(file1);
        } catch (IOException e) {
            logHelper.failLog("更新授权","文件上传异常,"+file.getOriginalFilename());
            return ResponseResult.error(503,e.getMessage());
        }
        LicenseUtil.LicenseCheckResult licenseCheckResult = LicenseUtil.checkLicenseFile(file1);
        if (!licenseCheckResult.getCheckResult()){ // 如果证书无效
            logHelper.failLog("更新授权","证书无效");
            return ResponseResult.error(503,licenseCheckResult.getMsg());
        }

        // 校验通过后更新本地文件
        try {
            LicenseUtil.updateLocalLicense(file1);
        } catch (IOException e) {
            logHelper.failLog("更新授权","本地授权文件更新异常"+file.getOriginalFilename());
            return ResponseResult.error(503,"文件更新异常");
        }
        // 加载授权文件至本地缓存
        try {
            LicenseUtil.loadLicenseToCache();
        } catch (Exception e) {
            logHelper.failLog("更新授权","本地授权文件加载异常"+file.getOriginalFilename());
            return ResponseResult.error(503,"加载本地文件异常");
        }
        logHelper.successdLog("更新授权","更新成功"+file.getOriginalFilename()+",授权截至日期"+ DateUtil.format(new Date(SysCacheInfo.licenseEntity.getExpireTime()),"yyyy-MM-dd HH:mm:ss"));
        if (FileUtil.isNotEmpty(file1)){
            FileUtil.del(file1);
        }
        return ResponseResult.success();
    }

}

  • 提供一个可以获取软件部署的服务器mac地址的接口(工具类)
package com.starcone.common.util;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.Enumeration;

/**
 * IP地址相关工具类
 */
public class IpUtil {

    private static final Log logger = LogFactory.getLog(IpUtil.class);

    public static String getIpAddr(HttpServletRequest request) {
        String ipAddress;
        try {
            ipAddress = request.getHeader("x-forwarded-for");
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getRemoteAddr();
                if (ipAddress.equals("127.0.0.1")) {
                    // 根据网卡取本机配置的IP
                    InetAddress inet = null;
                    try {
                        inet = InetAddress.getLocalHost();
                    } catch (UnknownHostException e) {
                        logger.error(e.getMessage(), e);
                    }
                    ipAddress = inet.getHostAddress();
                }
            }
            // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
            if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
                // = 15
                if (ipAddress.indexOf(",") > 0) {
                    ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
                }
            }
        } catch (Exception e) {
            ipAddress = "";
        }

        return ipAddress;
    }


    // 获取MAC地址的方法
    public static String getMACAddress() throws Exception {
        // 获得网络接口对象(即网卡),并得到mac地址,mac地址存在于一个byte数组中。
        InetAddress ia = InetAddress.getLocalHost();
        byte[] mac = NetworkInterface.getByInetAddress(ia).getHardwareAddress();
        // 下面代码是把mac地址拼装成String
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < mac.length; i++) {
            if (i != 0) {
                sb.append("-");
            }
            // mac[i] & 0xFF 是为了把byte转化为正整数
            String s = Integer.toHexString(mac[i] & 0xFF);
            // System.out.println("--------------");
            // System.err.println(s);
            sb.append(s.length() == 1 ? 0 + s : s);
        }
        // 把字符串所有小写字母改为大写成为正规的mac地址并返回
        return sb.toString().toUpperCase();
    }
}

  • 根据mac地址生成证书文件
    在这里插入图片描述
    输入授权时间,校验密码(主机mac加上自定义密码)
    在这里插入图片描述
    生成的授权文件
    在这里插入图片描述

  • 将证书文件放到项目的resource目录
    在这里插入图片描述

  • 编写启动类,在项目启动时加载证书信息,并读取数据库最新的时间作为基础时间,防止修改系统时间和文件篡改

package com.starcone.common.task;

import cn.hutool.core.util.ObjectUtil;
import com.starcone.common.bean.response.ExceptionCast;
import com.starcone.common.syscenter.SysCacheInfo;
import com.starcone.common.util.LicenseUtil;
import com.starcone.domain.SysLog;
import com.starcone.service.SysLogService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.Date;

/**
 * @Author: Daisen.Z
 * @Date: 2021/7/13 9:55
 * @Version: 1.0
 * @Description: 系统启动时要执行的任务
 */
@Component
public class SysStartTask {


    private Logger logger = LogManager.getLogger();

    @Autowired
    private SysLogService sysLogService;

    @PostConstruct
    public void init() {
        logger.info("****************执行系统启动初始化****************");
        // 加载认证证书文件信息
        if (SysCacheInfo.licenseEntity == null){
            try {
                LicenseUtil.loadLicenseToCache();
            } catch (Exception e) {
                ExceptionCast.cast("License load to Cache Exception");
            }
        }


        // 从数据库读取一个最新的时间到缓存中
        SysLog sysLog = sysLogService.queryOneByMaxTime();
        if (ObjectUtil.isEmpty(sysLog)){
            SysCacheInfo.latOptTimestmp = System.currentTimeMillis();
        }else {
            Date addTime = sysLog.getAddTime();
            SysCacheInfo.latOptTimestmp = addTime.getTime();
        }
    }


}

  • 编写拦截器,在访问系统接口时,进行证书校验,并校验系统时间是否被修改
    拦截器:
package com.starcone.common.config.auth;

import com.starcone.common.syscenter.SysCacheInfo;
import com.starcone.common.util.LicenseUtil;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @Author: Daisen.Z
 * @Date: 2024/1/17 18:57
 * @Version: 1.0
 * @Description:
 */
@Configuration
public class JarAuthInterceptor implements HandlerInterceptor {

    /**
     * 在请求处理之前进行调用(Controller方法调用之前)
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String uri = request.getRequestURI();
        if (uri.endsWith("login") || uri.endsWith("licens")) {
            return true;
        }
        // 校验证书文件
        boolean checkResult = LicenseUtil.checkLocalLicenseCache().getCheckResult();
        long timeMillis = System.currentTimeMillis();
        boolean timeFlag =  ( timeMillis+ 180000) > SysCacheInfo.latOptTimestmp;
        if (checkResult && timeFlag){ // 操作时间不能比最近上一次操作系统的时间小超过3分钟
            // 更新最近一次操作的时间
            SysCacheInfo.latOptTimestmp = System.currentTimeMillis();
            return true;
        } else {
            if (!timeFlag) {// 跳转到请不要修改服务器时钟的页面
                if ("XMLHttpRequest".equals (request.getHeader ("X-Requested-With"))) { // ajax跳转
                        //告诉ajax我是重定向
                    response.setHeader ("REDIRECT", "REDIRECT");
                    //告诉ajax我重定向的路径
                    response.setHeader ("CONTENTPATH", "/licensDate");
                    response.setStatus (HttpServletResponse.SC_FORBIDDEN);
                } else {
                    // 如果不是ajax请求,直接跳转
                    response.sendRedirect (request.getContextPath ( ) + "/licensDate");
                }
            }else {
                if ("XMLHttpRequest".equals (request.getHeader ("X-Requested-With"))) {// ajax跳转
                    //告诉ajax我是重定向
                    response.setHeader ("REDIRECT", "REDIRECT");
                    //告诉ajax我重定向的路径
                    response.setHeader ("CONTENTPATH", "/licens");
                    response.setStatus (HttpServletResponse.SC_FORBIDDEN);
                } else {
                    response.sendRedirect (request.getContextPath ( ) + "/licens");
                }
            }
            return false;
        }
    }
}

配置拦截器生效:

package com.starcone.common.config;

import com.starcone.common.config.auth.JarAuthInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: Daisen.Z
 * @Date: 2024/1/17 18:58
 * @Version: 1.0
 * @Description:
 */
@Configuration
public class SignAuthConfiguration implements WebMvcConfigurer {
    @Autowired
    public JarAuthInterceptor jarAuthInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        //注册TestInterceptor拦截器
        InterceptorRegistration registration = registry.addInterceptor(jarAuthInterceptor);
        registration.addPathPatterns("/**");                      //所有路径都被拦截
        List<String> excludePath = new ArrayList<>();
        excludePath.add("/login");
        excludePath.add("/licens");
        excludePath.add("/licensDate");
        excludePath.add("/licensManager/**");
        excludePath.add("/dologin");
        excludePath.add("/libs/**");
        excludePath.add("/static/**");
        excludePath.add("/src/**");
        excludePath.add("/js/**");
        excludePath.add("/icon/**");
        // 许可授权拦截器
        registration.excludePathPatterns(excludePath);
    }
}

四、拓展

可以根据项目需求进行适当修改,开搞吧!

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

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

相关文章

Springboot 子工程构建完后无法找到springboot依赖

问题: 构建完子工程后无法找到SpringBootTest 解决方案: 最好用这个构建 https://www.cnblogs.com/he-wen/p/16735239.html 1.先观察项目目录 是否正确 2.观察子工程目录 3.看pom.xml中是否引用springboot依赖 4.检查代码 查看父项目是否包含子模块 查看子模块的父项目是否…

OB SQL引擎和存储引擎

文章目录 一 SQL引擎1.1 双模共存1.2 基本操作1.3 查看SQL的执行计划 二 存储引擎2.1 传统数据库存在的问题2.2 LSM-Tree存储2.3 OceanBase转储和合并2.4 控制内存数据落盘2.5 LSMTree存储压缩 三 备份恢复3.1 物理备份系统架构3.2 物理恢复系统架构 一 SQL引擎 1.1 双模共存 …

网安-入门永恒之蓝/黑

永恒之蓝 实验环境&#xff1a;win7&#xff0c;kali 实验目的&#xff1a;拿到win7管理员权限 扫描该网段 nmap -sP 192.168.164.0/24&#xff0c;查看win7ip&#xff0c;也可在win7上查询 扫描端口&#xff0c;445&#xff0c;永恒之蓝是通过445端口进行攻击的 masscan -…

个人网站制作 Part 8 添加电子邮件通知与社交媒体集成 | Web开发项目

文章目录 &#x1f469;‍&#x1f4bb; 基础Web开发练手项目系列&#xff1a;个人网站制作&#x1f680; 添加电子邮件通知&#x1f528;使用Nodemailer&#x1f527;步骤 1: 安装Nodemailer &#x1f680; 社交媒体集成&#x1f528;使用社交媒体API&#x1f527;步骤 2: 集成…

多输入多输出 | Matlab实现基于LightGBM多输入多输出预测

多输入多输出 | Matlab实现基于LightGBM多输入多输出预测 目录 多输入多输出 | Matlab实现基于LightGBM多输入多输出预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Matlab实现基于LightGBM多输入多输出预测&#xff08;完整源码和数据&#xff09; 1.data为数据集&a…

使用企业订货软件的担忧与考虑|网上APP订货系统

使用企业订货软件的担忧与考虑|网上APP订货系统 网上订货系统担心出现的问题 1&#xff0c;如果在订货系统中定错(多)货物了该怎么办 其实这也是很多人在网购或者是现实中经常会犯的一个错误&#xff0c;但是网上订货平台为大家提供了很多的解决方案&#xff0c;其中对于订单的…

腾讯云 腾讯云服务器 - 腾讯云 产业智变·云启未来

腾讯云服务器CVM提供安全可靠的弹性计算服务&#xff0c;腾讯云明星级云服务器&#xff0c;弹性计算实时扩展或缩减计算资源&#xff0c;支持包年包月、按量计费和竞价实例计费模式&#xff0c;CVM提供多种CPU、内存、硬盘和带宽可以灵活调整的实例规格&#xff0c;提供9个9的数…

jmeter-线程数设置为1,循环10次没问题,循环100次出现异常

一、多次尝试&#xff0c;发现出现异常的接口大致相同。 解决办法&#xff1a;在第一个出现异常的接口下添加超时时间&#xff0c;固定定时器&#xff1a;2000ms&#xff0c;再次运行就没问题了。 二、压力机自身存在的问题 1&#xff09;在网络编程中&#xff0c;特别是在短…

IOS-数据持久化UserDefaults简单使用-Swift

UserDefaults通过key-value的一种持久化方案&#xff0c;以键值对的形式存储基本类型数据&#xff0c;类似与安卓的SharePreferences。 使用方式&#xff0c;首先就是要获取standerd let userDefaultUserDefaults.standard存取字符串 //存取字符串 var greeting "Hello…

一款 StarRocks 客户端工具,支持可视化建表、数据编辑

什么是 StarRocks&#xff1f; StarRocks 是新一代极速全场景 MPP (Massively Parallel Processing) 数据库。StarRocks 的愿景是能够让用户的数据分析变得更加简单和敏捷。用户无需经过复杂的预处理&#xff0c;就可以用 StarRocks 来支持多种数据分析场景的极速分析。 为了…

循环异步调取接口使用数组promiseList保存,Promise.all(promiseList)获取不到数组内容,then()返回空数组

在使用 vue vant2.13.2 技术栈的项目中&#xff0c;因为上传文件的接口是单文件上传&#xff0c;当使用批量上传时&#xff0c;只能循环调取接口&#xff1b;然后有校验内容&#xff1a;需要所有文件上传成功后才能保存&#xff0c;在文件上传不成功时点击保存按钮&#xff0c…

django电影推荐系统

电影推荐 启动 ./bin/pycharm.shdjango-admin startproject movie_recommendation_projectcd movie_recommendation_project/python manage.py movie_recommendation_apppython manage.py startapp movle_recommendation_applspython manage.py runserver Using the URLconf d…

CSS||引入方式

目录 CSS引入方式 行内样式表&#xff08;行内式&#xff09; 内部样式表&#xff08;嵌入式&#xff09; 外部样式表&#xff08;链接式&#xff09; 引入外部样式表 CSS引入方式 CSS&#xff08;层叠样式表&#xff09;是一种用来描述文档样式的样式表语言&#xff0c;它…

【备战蓝桥杯】探索Python内置标准库collections的使用

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-q0zvWxZtAIdSGZ8R {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

高密数据中心卓越运维,更灵活助力企业 AI 就绪

AIGC的高速发展将企业对基础架构的需求推上了新的层次&#xff0c;根据中国通服数字基建产业研究院发布的《中国数据中心产业发展白皮书&#xff08;2023&#xff09;》报告&#xff0c;互联网行业客户对单机柜功率密度的要求较高&#xff0c;一般在6-8kW&#xff0c;金融行业处…

centos7 arm服务器编译安装gcc 8.2

前言 当前电脑的gcc版本为4.8.5&#xff0c;但是在编译其他依赖包的时候&#xff0c;出现各种奇怪的问题&#xff0c;会莫名其妙的中断编译。本地文章讲解如何自编译安装gcc&#xff0c;替换系统自带的gcc。 环境准备 gcc 需要 8.2&#xff1a;下载地址 开始编译 1、解压gcc…

Azure Machine Learning - 聊天机器人构建

目录 聊天机器人架构概述消耗成本环境准备打开开发环境部署和运行将聊天应用部署到 Azure使用聊天应用从 PDF 文件获取答案使用聊天应用设置更改答复行为 本文介绍如何部署和运行适用于 Python 的企业聊天应用示例。 此示例使用 Python、Azure OpenAI 服务和 Azure AI 搜索中的…

【记录】解决 git 仓库突然出现连接失败

问题描述 今天在 push 代码代码的时候突然发现无法 push(但是我可以正常打开 Gihub)&#xff0c;这可不行&#xff0c;我可是 git 的重度使用者&#x1f60d;&#xff0c;我所有的代码都托管在了 Github 上&#xff0c;没有它我的日子怎么活啊&#xff01;&#xff01;&#x…

通讯录(C语言版)(静态通讯录)

✨欢迎来到脑子不好的小菜鸟的文章✨ &#x1f388;创作不易&#xff0c;麻烦点点赞哦&#x1f388; 所属专栏&#xff1a;项目 我的主页&#xff1a;脑子不好的小菜鸟 文章特点&#xff1a;关键点和步骤讲解放在 代码相应位置 引言&#xff1a; 1.菜单 通讯录也如同游戏&…

【史上最全】前端页面深入浅出浏览器渲染原理

前言 浏览器的核心组件&#xff0c;即通常所说的浏览器内核&#xff0c;是支撑整个浏览器运行的关键性底层软件架构&#xff0c;它由两个关键组成部分构成&#xff1a;一个是负责网页内容解析和渲染的渲染引擎&#xff0c;另一个则是用于执行JavaScript代码的JS引擎。各浏览器厂…