移动端与服务端交互安全方案

2023-11-16

系统流程图

 

验签

解决问题:

1、身份验证:是否是我规定的那个人

2、防篡改:是否被第三方劫持并篡改参数

3、防重放:是否重复请求

具体算法:

1、约定appKey,保证该调用请求是平台授权过的调用方发出的,保证请求方唯一性。

2、将appKey加入值请求参数,如:http://****?appKey=1232456&其他参数。

3、对参数进行排序(排序方法约定好即可,如:ASCII码对比),将参数名参数值拼接成字符串。

4、使用md5摘要算法获取摘要,将摘要放入请求头。

package com.zhangteng.rxhttputils.interceptor

import android.text.TextUtils
import com.google.gson.JsonParser
import com.zhangteng.rxhttputils.utils.MD5Util.md5Decode32
import okhttp3.FormBody
import okhttp3.Interceptor
import okhttp3.RequestBody
import okhttp3.Response
import okio.Buffer
import java.io.IOException
import java.nio.charset.Charset
import java.util.*
import kotlin.collections.set

/**
 * 添加签名拦截器
 * Created by Swing on 2019/10/20.
 */
class SignInterceptor(private val appKey: String) : Interceptor {

    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val requestBuilder = request.newBuilder()
        val urlBuilder = request.url().newBuilder()
        val params: MutableMap<String, Any?> =
            TreeMap()
        if (METHOD_GET == request.method()) {
            val httpUrl = urlBuilder.build()
            val paramKeys = httpUrl.queryParameterNames()
            for (key in paramKeys) {
                val value = httpUrl.queryParameter(key)
                if (!TextUtils.isEmpty(value)) params[key] = value
            }
        } else if (METHOD_POST == request.method()) {
            if (request.body() is FormBody) {
                val formBody = request.body() as FormBody?
                for (i in 0 until formBody!!.size()) {
                    params[formBody.encodedName(i)] = formBody.encodedValue(i)
                }
            } else if (request.body() is RequestBody) {
                val requestBody = request.body()
                val buffer = Buffer()
                requestBody!!.writeTo(buffer)
                var charset = Charset.forName("UTF-8")
                val contentType = requestBody.contentType()
                if (contentType != null) {
                    charset = contentType.charset()
                }
                val paramJson =
                    buffer.readString(charset ?: Charset.defaultCharset())
                val jsonObject = JsonParser().parse(paramJson).asJsonObject
                jsonObject.entrySet().forEach {
                    val jsonElement = it.value
                    if (jsonElement != null && !jsonElement.isJsonArray && !jsonElement.isJsonObject && !jsonElement.isJsonNull) {
                        val value = jsonElement.asString
                        if (!TextUtils.isEmpty(value)) params[it.key] = value
                    }
                }
            }
        }
        val sign = StringBuilder()
        sign.append(appKey)
        for (key in params.keys) {
            sign.append(key).append(params[key])
        }
        val _timestamp = System.currentTimeMillis()
        sign.append("_timestamp").append(_timestamp)
        sign.append(appKey)
        requestBuilder.addHeader("_timestamp", _timestamp.toString())
        requestBuilder.addHeader("_sign", md5Decode32(sign.toString()))
        return chain.proceed(requestBuilder.build())
    }

    companion object {
        private const val METHOD_GET = "GET"
        private const val METHOD_POST = "POST"
    }
}

AES加密

解决问题:

数据加密,防止信息截取,RSA加解密更耗时

具体算法:

AES/CBC/NoPadding

package com.zhangteng.rxhttputils.utils

import java.security.NoSuchAlgorithmException
import java.security.SecureRandom
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec

/**
 * Created by Swing on 2017/12/6.
 */
object AESUtils {
    /**
     * 随机生成秘钥
     */
    val key: String
        get() = try {
            val kg = KeyGenerator.getInstance("AES")
            kg.init(128)
            val sk = kg.generateKey()
            val b = sk.encoded
            byteToHexString(b)
        } catch (e: NoSuchAlgorithmException) {
            e.printStackTrace()
            ""
        }

    /**
     * 使用指定的字符串生成秘钥
     */
    fun getKeyByPass(keyRaw: String): String {
        return try {
            val kg = KeyGenerator.getInstance("AES")
            // kg.init(128);//要生成多少位,只需要修改这里即可128, 192或256
            //SecureRandom是生成安全随机数序列,password.getBytes()是种子,只要种子相同,序列就一样,所以生成的秘钥就一样。
            kg.init(128, SecureRandom(keyRaw.toByteArray()))
            val sk = kg.generateKey()
            val b = sk.encoded
            byteToHexString(b)
        } catch (e: NoSuchAlgorithmException) {
            e.printStackTrace()
            ""
        }
    }

    /**
     * byte数组转化为16进制字符串
     *
     * @param bytes
     * @return
     */
    fun byteToHexString(bytes: ByteArray): String {
        val sb = StringBuffer()
        for (i in bytes.indices) {
            val strHex = Integer.toHexString(bytes[i].toInt())
            if (strHex.length > 3) {
                sb.append(strHex.substring(6))
            } else {
                if (strHex.length < 2) {
                    sb.append("0$strHex")
                } else {
                    sb.append(strHex)
                }
            }
        }
        return sb.toString()
    }

    //加密
    @Throws(Exception::class)
    fun encrypt(data: String, key: String, iv: String): String? {
        val cipher = Cipher.getInstance("AES/CBC/NoPadding")
        val blockSize = cipher.blockSize
        val dataBytes = data.toByteArray()
        var plaintextLength = dataBytes.size
        if (plaintextLength % blockSize != 0) {
            plaintextLength = plaintextLength + (blockSize - plaintextLength % blockSize)
        }
        val plaintext = ByteArray(plaintextLength)
        System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.size)
        val keyspec =
            SecretKeySpec(key.toByteArray(), "AES")
        val ivspec =
            IvParameterSpec(iv.toByteArray())
        cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec)
        val encrypted = cipher.doFinal(plaintext)
        return Base64Utils.encode(encrypted)
    }

    //解密
    @Throws(Exception::class)
    fun decrypt(data: String, key: String, iv: String): String {
        val encrypted1 = Base64Utils.decode(data)
        val cipher = Cipher.getInstance("AES/CBC/NoPadding")
        val keyspec =
            SecretKeySpec(key.toByteArray(), "AES")
        val ivspec =
            IvParameterSpec(iv.toByteArray())
        cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec)
        val original = cipher.doFinal(encrypted1)
        return String(original)
    }
}

RSA加密

解决问题:

AES秘钥交换困难

具体算法:

RSA/ECB/PKCS1Padding

package com.zhangteng.rxhttputils.utils

import android.util.Base64
import java.security.KeyFactory
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.NoSuchAlgorithmException
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
import java.util.*
import javax.crypto.Cipher

/**
 * 字符串格式的密钥在未在特殊说明情况下都为BASE64编码格式<br></br>
 * 由于非对称加密速度极其缓慢,一般文件不使用它来加密而是使用对称加密,<br></br>
 * 非对称加密算法可以用来对对称加密的密钥加密,这样保证密钥的安全也就保证了数据的安全
 */
object RSAUtils {
    /**
     * 非对称加密密钥算法
     */
    const val RSA = "RSA"

    /**
     * 加密填充方式
     */
    const val ECB_PKCS1_PADDING = "RSA/ECB/PKCS1Padding"

    /**
     * 秘钥默认长度
     */
    const val DEFAULT_KEY_SIZE = 1024

    /**
     * 当要加密的内容超过bufferSize,则采用partSplit进行分块加密
     */
    val DEFAULT_SPLIT = "#PART#".toByteArray()

    /**
     * 当前秘钥支持加密的最大字节数
     */
    const val DEFAULT_BUFFERSIZE = DEFAULT_KEY_SIZE / 8 - 11

    /**
     * 随机生成RSA密钥对
     *
     * @param keyLength 密钥长度,范围:512~2048
     * 一般1024
     * @return
     */
    fun generateRSAKeyPair(keyLength: Int): KeyPair? {
        return try {
            val kpg =
                KeyPairGenerator.getInstance(RSA)
            kpg.initialize(keyLength)
            kpg.genKeyPair()
        } catch (e: NoSuchAlgorithmException) {
            e.printStackTrace()
            null
        }
    }

    /**
     *
     *
     * 公钥加密
     *
     *
     * @param data      源数据
     * @param publicKey 公钥(BASE64编码)
     * @return
     * @throws Exception
     */
    @Throws(Exception::class)
    fun encryptByPublicKey(data: String, publicKey: String?): String {
        return String(
            Base64.encode(
                encryptByPublicKey(
                    data.toByteArray(),
                    Base64.decode(publicKey, 0)
                ), 0
            )
        ).replace("\n", "").replace("\\", "")
    }

    /**
     * 用公钥对字符串进行加密
     *
     * @param data 原文
     */
    @Throws(Exception::class)
    fun encryptByPublicKey(
        data: ByteArray?,
        publicKey: ByteArray?
    ): ByteArray {
        // 得到公钥
        val keySpec =
            X509EncodedKeySpec(publicKey)
        val kf = KeyFactory.getInstance(RSA)
        val keyPublic = kf.generatePublic(keySpec)
        // 加密数据
        val cp = Cipher.getInstance(ECB_PKCS1_PADDING)
        cp.init(Cipher.ENCRYPT_MODE, keyPublic)
        return cp.doFinal(data)
    }

    /**
     *
     *
     * 私钥加密
     *
     *
     * @param data      源数据
     * @param publicKey 公钥(BASE64编码)
     * @return
     * @throws Exception
     */
    @Throws(Exception::class)
    fun encryptByPrivateKey(data: String, publicKey: String?): String {
        return String(
            Base64.encode(
                encryptByPrivateKey(
                    data.toByteArray(),
                    Base64.decode(publicKey, 0)
                ), 0
            )
        ).replace("\n", "").replace("\\", "")
    }

    /**
     * 私钥加密
     *
     * @param data       待加密数据
     * @param privateKey 密钥
     * @return byte[] 加密数据
     */
    @Throws(Exception::class)
    fun encryptByPrivateKey(
        data: ByteArray?,
        privateKey: ByteArray?
    ): ByteArray {
        // 得到私钥
        val keySpec =
            PKCS8EncodedKeySpec(privateKey)
        val kf = KeyFactory.getInstance(RSA)
        val keyPrivate = kf.generatePrivate(keySpec)
        // 数据加密
        val cipher =
            Cipher.getInstance(ECB_PKCS1_PADDING)
        cipher.init(Cipher.ENCRYPT_MODE, keyPrivate)
        return cipher.doFinal(data)
    }

    /**
     *
     *
     * 公钥解密
     *
     *
     * @param encryptedData 已加密数据(BASE64编码)
     * @param publicKey     公钥(BASE64编码)
     * @return
     * @throws Exception
     */
    @Throws(Exception::class)
    fun decryptByPublicKey(
        encryptedData: String?,
        publicKey: String?
    ): String {
        return String(
            decryptByPublicKey(
                Base64.decode(encryptedData, 0),
                Base64.decode(publicKey, 0)
            )
        )
    }

    /**
     * 公钥解密
     *
     * @param data      待解密数据
     * @param publicKey 密钥
     * @return byte[] 解密数据
     */
    @Throws(Exception::class)
    fun decryptByPublicKey(
        data: ByteArray?,
        publicKey: ByteArray?
    ): ByteArray {
        // 得到公钥
        val keySpec =
            X509EncodedKeySpec(publicKey)
        val kf = KeyFactory.getInstance(RSA)
        val keyPublic = kf.generatePublic(keySpec)
        // 数据解密
        val cipher =
            Cipher.getInstance(ECB_PKCS1_PADDING)
        cipher.init(Cipher.DECRYPT_MODE, keyPublic)
        return cipher.doFinal(data)
    }

    /**
     * <P>
     * 私钥解密
    </P> *
     *
     * @param encryptedData 已加密数据(BASE64编码)
     * @param privateKey    私钥(BASE64编码)
     * @return
     * @throws Exception
     */
    @Throws(Exception::class)
    fun decryptByPrivateKey(
        encryptedData: String?,
        privateKey: String?
    ): String {
        return String(
            decryptByPrivateKey(
                Base64.decode(encryptedData, 0),
                Base64.decode(privateKey, 0)
            )
        )
    }

    /**
     * 使用私钥进行解密
     */
    @Throws(Exception::class)
    fun decryptByPrivateKey(
        encrypted: ByteArray?,
        privateKey: ByteArray?
    ): ByteArray {
        // 得到私钥
        val keySpec =
            PKCS8EncodedKeySpec(privateKey)
        val kf = KeyFactory.getInstance(RSA)
        val keyPrivate = kf.generatePrivate(keySpec)

        // 解密数据
        val cp = Cipher.getInstance(ECB_PKCS1_PADDING)
        cp.init(Cipher.DECRYPT_MODE, keyPrivate)
        return cp.doFinal(encrypted)
    }
}

okhttp拦截器实现加解密

package com.zhangteng.rxhttputils.interceptor

import android.text.TextUtils
import com.google.gson.JsonParser
import com.zhangteng.rxhttputils.http.HttpUtils
import com.zhangteng.rxhttputils.http.OkHttpClient
import com.zhangteng.rxhttputils.utils.AESUtils
import com.zhangteng.rxhttputils.utils.DiskLruCacheUtils
import com.zhangteng.rxhttputils.utils.RSAUtils
import com.zhangteng.rxhttputils.utils.SPUtils
import okhttp3.*
import okio.Buffer
import java.io.IOException
import java.nio.charset.Charset

/**
 * 添加加解密拦截器
 * Created by Swing on 2019/10/20.
 */
class EncryptionInterceptor(private val publicKeyUrl: HttpUrl) : Interceptor {
    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
        val request = chain.request()
        val headers = request.headers()
        if (headers.names().contains(SECRET) && "true" == headers[SECRET]) {
            var secretRequest: Request? = buildRequest(request) ?: return errorSecretResponse
            var secretResponse = chain.proceed(secretRequest!!)
            val secretResponseBody = secretResponse.body()
            val secretResponseStr =
                if (secretResponseBody != null) secretResponseBody.string() else ""
            val jsonObject = JsonParser().parse(
                secretResponseStr.substring(
                    0,
                    secretResponseStr.lastIndexOf("}") + 1
                )
            ).asJsonObject
            val jsonElement = jsonObject["status"]
            if (jsonElement != null
                && !jsonElement.isJsonArray
                && !jsonElement.isJsonObject
                && !jsonElement.isJsonNull
                && "2100" == jsonElement.asString
            ) {
                SPUtils.put(HttpUtils.getInstance().getContext()!!, SPUtils.FILE_NAME, SECRET, "")
                DiskLruCacheUtils.remove(publicKeyUrl)
                DiskLruCacheUtils.flush()
                secretRequest = buildRequest(request)
                if (secretRequest == null) {
                    return errorSecretResponse
                }
                secretResponse = chain.proceed(secretRequest)
            } else {
                val mediaType =
                    if (secretResponseBody != null) secretResponseBody.contentType() else MediaType.parse(
                        "application/json;charset=UTF-8"
                    )
                val newResonseBody = ResponseBody.create(mediaType, secretResponseStr)
                secretResponse = secretResponse.newBuilder().body(newResonseBody).build()
            }
            return secretResponse
        }
        return chain.proceed(request)
    }

    /**
     * 构建加密请求
     *
     * @param request 原请求
     */
    @Throws(IOException::class)
    private fun buildRequest(request: Request): Request? {
        if (TextUtils.isEmpty(
                SPUtils[HttpUtils.getInstance()
                    .getContext()!!, SPUtils.FILE_NAME, SECRET, ""].toString()
            )
        ) {
            val secretResponse = OkHttpClient.getInstance().client.newCall(
                Request.Builder().url(publicKeyUrl).build()
            ).execute()
            val secretResponseString = secretResponse.body()?.string()
            if (secretResponse.code() == 200) {
                val jsonObject = JsonParser().parse(secretResponseString).asJsonObject
                val jsonElement = jsonObject["result"].asJsonObject["publicKey"]
                SPUtils.put(
                    HttpUtils.getInstance().getContext()!!,
                    SPUtils.FILE_NAME,
                    SECRET,
                    jsonElement.asString
                )
            } else {
                return null
            }
        }
        val aesRequestKey: String = AESUtils.key
        val requestBuilder = request.newBuilder()
        requestBuilder.removeHeader(SECRET)
        try {
            requestBuilder.addHeader(
                SECRET,
                RSAUtils.encryptByPublicKey(
                    aesRequestKey,
                    SPUtils[HttpUtils.getInstance()
                        .getContext()!!, SPUtils.FILE_NAME, SECRET, publicKey] as String
                )
            )
        } catch (e: Exception) {
            return null
        }
        if (METHOD_GET == request.method()) {
            val url = request.url().url().toString()
            val paramsBuilder = url.substring(url.indexOf("?") + 1)
            try {
                val encryptParams =
                    AESUtils.encrypt(paramsBuilder, aesRequestKey, aesRequestKey.substring(0, 16))
                requestBuilder.url(url.substring(0, url.indexOf("?")) + "?" + encryptParams)
            } catch (e: Exception) {
                return null
            }
        } else if (METHOD_POST == request.method()) {
            val requestBody = request.body()
            if (requestBody != null && aesRequestKey.length >= 16) {
                if (requestBody is FormBody) {
                    val formBody = request.body() as FormBody?
                    val bodyBuilder = FormBody.Builder()
                    try {
                        if (formBody != null) {
                            for (i in 0 until formBody.size()) {
                                val value = formBody.encodedValue(i)
                                if (!TextUtils.isEmpty(value)) {
                                    val encryptParams = AESUtils.encrypt(
                                        value,
                                        aesRequestKey,
                                        aesRequestKey.substring(0, 16)
                                    )
                                    bodyBuilder.addEncoded(formBody.encodedName(i), encryptParams)
                                }
                            }
                            requestBuilder.post(bodyBuilder.build())
                        }
                    } catch (e: Exception) {
                        return null
                    }
                } else {
                    val buffer = Buffer()
                    requestBody.writeTo(buffer)
                    var charset =
                        Charset.forName("UTF-8")
                    val contentType = requestBody.contentType()
                    if (contentType != null) {
                        charset = contentType.charset()
                    }
                    val paramsRaw =
                        buffer.readString(charset ?: Charset.defaultCharset())
                    if (!TextUtils.isEmpty(paramsRaw)) {
                        try {
                            val encryptParams = AESUtils.encrypt(
                                paramsRaw,
                                aesRequestKey,
                                aesRequestKey.substring(0, 16)
                            )
                            requestBuilder.post(
                                RequestBody.create(
                                    requestBody.contentType(),
                                    encryptParams
                                )
                            )
                        } catch (e: Exception) {
                            return null
                        }
                    }
                }
            }
        }
        return requestBuilder.build()
    }

    /**
     * 获取加密失败响应
     */
    private val errorSecretResponse: okhttp3.Response
        private get() {
            val failureResponseBuilder = okhttp3.Response.Builder()
            failureResponseBuilder.body(
                ResponseBody.create(
                    MediaType.parse("application/json;charset=UTF-8"),
                    "{\"message\": \"移动端加密失败\",\"status\": ${SECRET_ERROR}}"
                )
            )
            return failureResponseBuilder.build()
        }

    companion object {
        private const val METHOD_GET: String = "GET"
        private const val METHOD_POST: String = "POST"
        const val SECRET: String = "_secret"
        const val SECRET_ERROR: Int = 2100
        const val publicKey: String = ""
    }
}

package com.zhangteng.rxhttputils.interceptor

import android.text.TextUtils
import com.zhangteng.rxhttputils.http.HttpUtils
import com.zhangteng.rxhttputils.interceptor.EncryptionInterceptor.Companion.SECRET
import com.zhangteng.rxhttputils.interceptor.EncryptionInterceptor.Companion.SECRET_ERROR
import com.zhangteng.rxhttputils.utils.AESUtils.decrypt
import com.zhangteng.rxhttputils.utils.RSAUtils
import com.zhangteng.rxhttputils.utils.SPUtils
import okhttp3.Interceptor
import okhttp3.MediaType
import okhttp3.Response
import okhttp3.ResponseBody
import java.io.IOException

/**
 * 添加加解密拦截器
 * Created by Swing on 2019/10/20.
 */
class DecryptionInterceptor : Interceptor {

    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        val response = chain.proceed(chain.request())
        if (!response.isSuccessful || response.code() != 200) {
            return response
        }
        val responseBuilder = response.newBuilder()
        val responseBody = response.body()
        val responseHeaders = response.headers()
        for (name in responseHeaders.names()) {
            if (SECRET.contains(name!!) && !TextUtils.isEmpty(responseHeaders[name])) {
                return try {
                    val encryptKey = responseHeaders[name]
                    val aesResponseKey: String = RSAUtils.decryptByPublicKey(
                        encryptKey,
                        SPUtils[HttpUtils.getInstance()
                            .getContext()!!, SPUtils.FILE_NAME, SECRET, EncryptionInterceptor.publicKey] as String
                    )
                    val mediaType =
                        if (responseBody != null) responseBody.contentType() else MediaType.parse(
                            "application/json;charset=UTF-8"
                        )
                    val responseStr =
                        if (responseBody != null) responseBody.string() else ""
                    val rawResponseStr = decrypt(
                        responseStr,
                        aesResponseKey,
                        aesResponseKey.substring(0, 16)
                    )
                    responseBuilder.body(ResponseBody.create(mediaType, rawResponseStr))
                    responseBuilder.build()
                } catch (e: Exception) {
                    val failureResponse = Response.Builder()
                    failureResponse.body(
                        ResponseBody.create(
                            MediaType.parse("application/json;charset=UTF-8"),
                            "{\"message\": \"移动端解密失败${e.message}\",\"status\": ${SECRET_ERROR}}"
                        )
                    )
                    failureResponse.build()
                }
            }
        }
        return response
    }
}

优化

存在问题:

客户端获取服务端RSA公钥时明文,泄露服务器公钥,造成服务器响应数据泄露

解决方法:

1、客户端获取服务端RSA公钥;

2、客户端随机生成RSA密钥对

3、客户端使用服务器公钥加密客户端公钥后交于服务端;

4、客户端与服务端使用客户端公私钥加解密数据完成安全秘钥交换。

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

移动端与服务端交互安全方案 的相关文章

随机推荐

  • C语言回调函数学习

    作者 杨硕 华清远见嵌入式学院讲师 对指针的应用是C语言编程的精髓所在 而回调函数就是C语言里面对函数指针的高级应用 简而言之 回调函数是一个通过函数指针调用的函数 如果你把函数指针 函数的入口地址 传递给另一个函数 当这个函数指针被用来调
  • FBX SDK 开发环境配置 visual studio 2022

    FBX Adaptable File Formats for 3D Animation Software Autodesk 下载windows的sdk并安装 创建一个c console 工程 设置include目录 添加预处理宏 FBX S
  • 【前端】html+js+css开发入门超详细介绍

    文章目录 一 HTML 1 1 第一个页面 1 2 所有标签都来一遍 1 3 超链接 1 4 发邮件 1 5 description list描述列表 1 6 blockquote块引用 1 7 linequote 1 8 address
  • dns改成什么网速快_这个DNS服务器不仅更快而且安全

    DNS也就是域名解析服务器 这个东西的存在 使我们上网变得非常方便 再也不需要去记下复杂的IP了 而同样 DNS也影响着我们的网速 那么今天 小编就给大家推荐一个DNS服务器 这个DNS服务器不仅更快 而且更加安全 一起来看看吧 中国互联网
  • gson反序列化成data class时的坑

    前言 在Android开发中 gson是很常用的用来处理json的三方库 它是由Google维护的 一直以来都比较稳定 至少在使用Java开发时是这样的 但是 gson对Kotlin的data class的支持就不是很完善了 会有一些坑 下
  • Java中的多线程(Thread)(一)概念篇

    学习目标 知识点 要求 多线程介绍 了解 线程的创建 掌握 线程的使用 掌握 线程的优先级 掌握 守护线程 掌握 线程同步 掌握 线程并发协作 掌握 一 多线程介绍 多线程的基本概念 程序 Program 是一个静态的概念 一般对应于操作系
  • Hudi 0.12.0 搭建——集成 Hive3.1 与 Spark3.2

    Hudi 搭建 https blog csdn net weixin 46389691 article details 128276527 环境准备 一 安装 Maven 1 解压 2 配置环境变量 3 修改 Maven 下载源 二 安装
  • gunicorn常用参数命令

    Gunicorn 是一个 Python 的 WSGI HTTP 服务器 具有实现简单 轻量级 高性能等特点 更多介绍内容参考官网 这里介绍几个常用参数 安装 pip3 install gunicorn 通过输入gunicorn v查看版本
  • “元宇宙”,究竟离我们有多远?(上)

    目录 引言 1 雀斑公主 U 世界的燃泪青春 元宇宙 的大门 1 1 元宇宙版 美女与野兽 1 2 U 世界中皮囊之下的温柔内心 2 腾讯研究院 Metaverse 互联网的未来是虚拟时空 2 1 从GTA到Metaverse的关键技术分析
  • 我的世界服务器物品箱子,我的世界:使用箱子储存物品居多,难不成他们很“鸡肋”?...

    原标题 我的世界 使用箱子储存物品居多 难不成他们很 鸡肋 箱子 是储存物品的利器 固然多数玩家使用其进行物品的储存 为何与箱子具有类似功能的物品却不受众 难道他们真的很 鸡肋 吗 为何箱子是最为受众的储存工具 箱子储存如此受众 是因为它能
  • Android应用层View绘制流程与源码分析

    转自https blog csdn net yanbober article details 46128379 1 背景 还记得前面 Android应用setContentView与LayoutInflater加载解析机制源码分析 这篇文章
  • MyBatis日常记录之sql片段的抽取

  • python在VScode中中文输出乱码的解决方案

    一 确定python编码 如果不是就点击这个位置会弹出 选择 通过编码重新打开 UTF 8 然后尝试运行 如果问题没有解决进行第二步 二 修改json文件 文件 首选项 设置 搜索 code runner executorMap 点击 在s
  • 16 【跨域】

    16 跨域 1 什么是跨域 跨域 是指浏览器不能执行其他网站的脚本 它是由浏览器的同源策略造成的 是浏览器对JavaScript实施的安全限制 浏览器从一个域名的网页去请求另一个域名的资源时 出现域名 端口 协议任一不同 都属于跨域 同源策
  • 芯片测试(3)——DC测试

    芯片测试 3 DC测试 一 电源电压测试 二 地引脚测试 三 I V测试 四 电阻测试 五 对应测试项 六 简述及测试方法描述 6 1 VIL VIH 加流测压 FIMV 6 2 VOL VOH 加流测压 FIMV 6 3 IIL IIH
  • pip : 无法将“pip”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。

    换了新电脑 在新电脑中安装了pycharm 但是在使用pip install下载相关库时 出现pip 无法将 pip 项识别为 cmdlet 函数 脚本文件或可运行程序的名称 请检查名称的拼写 如果包括路径 请确保路径正确 然后再试一次 的
  • 给锂电池充电,充电器的输出电压

    目录 老旧充电器的一点小问题 指示灯不亮 拆 丢弃 给锂电池充电 指示灯无法显示充电状态 如何判断电池是否充满电了 电池反向充电 总结 老旧充电器的一点小问题 指示灯不亮 今天遇到一个有意思的问题 我买了两个12V的锂电池 DC接口 于是我
  • 2023年高校大数据实验室建设方案

    大数据实验室建设方案具体内容包括 人才培养方案建设 课程资源建设 师资建设 实验室建设 教学服务建设 泰迪打造国内领先的大数据人工智能及课程资源 包括 商务数据分析实训管理平台 云计算资源管理平台 大数据编程实训平台 商务数据分析编程实训平
  • Unity ScrollView拖不动

    今天再用Unity的imgui的ScrollView的时候 发现UI拖不动 找了好半天 终于招到了原因 在此记录下 代码如下 public Vector2 scrollPosition Vector2 zero void OnGUI scr
  • 移动端与服务端交互安全方案

    系统流程图 验签 解决问题 1 身份验证 是否是我规定的那个人 2 防篡改 是否被第三方劫持并篡改参数 3 防重放 是否重复请求 具体算法 1 约定appKey 保证该调用请求是平台授权过的调用方发出的 保证请求方唯一性 2 将appKey