Android NFC开发详解:NFC读卡实例解析及总结

news2024/11/18 19:40:21

文章目录

  • 前言
  • 一、什么是NFC?
  • 二、基础知识
  • 1.什么是NDEF?
  • 2.NFC技术的操作模式
  • 3.标签的技术类型
  • 4.实现方式的分类
  • 5.流程
  • 三、获取标签内容
  • 1.检查环境
  • 2.获取NFC标签
  • 2.1 Manifest中注册的方式获取Tag
  • 2.1 前台Activity捕获的方式获取Tag
  • 四、解析标签数据
  • 1. M1卡解析
  • 2. iso15693卡解析
  • 总结

一、什么是NFC?

NFC是目前Android手机一个主流的配置硬件项,全称是Near Field Communication,中为近场通信,也叫做近距离无线通信技术。使用了NFC技术的设备(例如移动电话)可以在彼此靠近的情况下进行数据交换,是由非接触式射频识别(RFID)及互连互通技术整合演变而来。

二、基础知识

开始开发之前必须要知道的知识

1.什么是NDEF?

存储在NFC标签中的数据可以采用多种格式编写,但许多 Android 框架 API 都基于名为 NDEF(NFC 数据交换格式)的 NFC Forum 标准。。

简单说就是一种普遍的数据格式标准

2.NFC技术的操作模式

(1) 读取器/写入器模式:支持 NFC 设备读取和/或写入被动 NFC 标签和贴纸。
(2)点对点模式:支持 NFC 设备与其他 NFC 对等设备交换数据;Android Beam 使用的就是此操作模式。
(3)卡模拟模式:支持 NFC 设备本身充当 NFC 卡。然后,可以通过外部 NFC 读取器(例如 NFC 销售终端)访问模拟 NFC 卡。

本篇案例使用的主要是读写卡,就是正常的读写卡需求,后面如果有机会接触到点对点和卡模拟的需求会在此篇做补充

3.标签的技术类型

通常情况下每种分类的标签(卡片)都支持一种或多重技术,
对应关系如下

技术描述卡种
NfcA提供NFC-A(ISO 14443-3A)的性能和I / O操作的访问。M1卡
NfcB提供NFC-B (ISO 14443-3B)的性能和I / O操作的访问。
NfcF提供 NFC-F (JIS 6319-4)的性能和I / O操作的访问。
NfcV提供 NFC-V (ISO 15693)的性能和I / O操作的访问。15693卡
IsoDep提供 ISO-DEP (ISO 14443-4)的性能和I / O操作的访问。CPU卡
Ndef提供NFC标签已被格式化为NDEF的数据和操作的访问。
NdefFormatable提供可能被格式化为NDEF的 formattable的标签。
MifareClassic如果此Android设备支持MIFARE,提供访问的MIFARE Classic性能和I / O操作。m1卡
MifareUltralight如果此Android设备支持MIFARE,提供访问的MIFARE 超轻性能和I / O操作。

如下图,这是Demo 显示得NFC标签的信息。
其中被我圈起来的部分是这个NFC标签支持的技术,这些后面解析数据的时候会用到,得到这些后就可以使用对应的类来解析标签数据。
 


开发中我们有对应的方法来获取此标签支持的解析方式,后面我会介绍。

4.实现方式的分类

(1)Manifest注册方式:这种方式主要是在Manifest文件对应的activity下,配置过滤器,以响应不同类型NFC Action。使用这种方式,在刷卡时,如果手机中有多个应用都存在该NFC实现方案,系统会弹出能响应NFC事件的应用列表供用户选择,用户需要点击目标应用来响应本次NFC刷卡事件。

(2)前台响应方式,无需Manifest重配置过滤器,直接使用前台activity来捕获NFC事件进行响应。

区别如下:
响应方式不同:Manifest注册的NFC事件由系统分发,需要选择应用去响应事件
       前台响应方式由前台activity来捕获NFC事件进行响应
优先级不同:前台响应方式的优先级更高于Manifest注册的方式
     (如果安装多个Manifest注册的的App 和一个处于前台捕获方式的App,刷卡后 优先级最高的为前台捕获的,如果前台相应方式的App没有打开,那么将弹出列表让用户选择Manifest中注册了的符合条件的App)

第一种更适合APP需要刷卡调用起来,并且设备没有多个响应NFC标签程序的物联网设备(因为普通安卓手机中自带的卡包APP、微信等优先级都比较高,当弹出列表选择响应的App时,操作会边得繁琐)

第二种更适合前台界面中的读卡,且多个应用的时候
根据自己的项目需求选择适合的实现方式。

5.流程

首先设备要支持NFC权限开启的前提下 不论哪种方式,都是先刷卡,等待系统分发响应的Activity 拿到Tag或者 前台Activity捕获到TAG 。然后根据这个标签支持的技术去解析数据。

三、获取标签内容

1.检查环境

首先 Manifest中添加权限

    <uses-permission android:name="android.permission.NFC" />

判断是否支持NFC、且打开功能

 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
        if (null == adapter) {
            Toast.makeText(this, "不支持NFC功能", Toast.LENGTH_SHORT).show();
        } else if (!adapter.isEnabled()) {
            Intent intent = new Intent(Settings.ACTION_NFC_SETTINGS);
            // 根据包名打开对应的设置界面
            startActivity(intent);
        }

 

2.获取NFC标签

2.1 Manifest中注册的方式获取Tag

这里要介绍三种意图过滤器
前面【实现方式的分类】中对这种方式的特征做了介绍,这种由标签调度系统分发的方式需要在Manifest定义固定的意图过滤器。标签调度系统定义了三种 Intent,按优先级从高到低列出如下:

ACTION_NDEF_DISCOVERED:如果扫描到包含 NDEF 负载的标签,并且可识别其类型,则使用此 Intent 启动 Activity。这是优先级最高的 Intent,标签调度系统会尽可能尝试使用此 Intent 启动 Activity,在行不通时才会尝试使用其他 Intent。

ACTION_TECH_DISCOVERED:如果没有登记要处理 ACTION_NDEF_DISCOVERED Intent 的 Activity,则标签调度系统会尝试使用此 Intent 来启动应用。此外,如果扫描到的标签包含无法映射到 MIME 类型或 URI 的 NDEF 数据,或者该标签不包含 NDEF 数据,但它使用了已知的标签技术,那么也会直接启动此 Intent(无需先启动 ACTION_NDEF_DISCOVERED)。

ACTION_TAG_DISCOVERED:如果没有处理 ACTION_NDEF_DISCOVERED 或者 ACTION_TECH_DISCOVERED Intent 的 Activity,则使用此 Intent 启动 Activity。

添加意图过滤器
这是第一种 最简单和优先级最高的一种,已经满足需求了

        <activity
            android:name=".NfcActivity"
            android:exported="false">
            <intent-filter>
                <action android:name="android.nfc.action.NDEF_DISCOVERED" />
            </intent-filter>
        </activity>

当然也可以选择第二种

        <activity
            android:name=".NfcActivity"
            android:exported="false">
            <intent-filter>
                <action android:name="android.nfc.action.TECH_DISCOVERED" />
            </intent-filter>
            <meta-data android:name="android.nfc.action.TECH_DISCOVERED"
                android:resource="@xml/filter_nfc" />
        </activity>

filter_nfc
这个文件就是TECH_DISCOVERED需要配置的,其中,tech-list之间是逻辑或关系,tech之间是逻辑与关系,与方案2中的techLists原理以及用途是类似。

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
 
    <tech-list>
        <tech>android.nfc.tech.Ndef</tech>
        <tech>android.nfc.tech.NfcA</tech>
    </tech-list>
 
    <tech-list>
        <tech>android.nfc.tech.NfcB</tech>
    </tech-list>
    <tech-list>
        <tech>android.nfc.tech.NfcF</tech>
    </tech-list>
</resources>

还剩最后一种

        <activity
            android:name=".NfcActivity"
            android:exported="false">
            <intent-filter>
                <action android:name="android.nfc.action.TAG_DISCOVERED"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>

这种一般用不到 感觉意义不大

然后在对应Activity的onCreate方法中就可以拿标签了

class NfcActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_nfc)
        val adapter = NfcAdapter.getDefaultAdapter(this)
        if (null == adapter) {
            Toast.makeText(this, "不支持NFC功能", Toast.LENGTH_SHORT).show()
        } else if (!adapter.isEnabled) {
            val intent = Intent(Settings.ACTION_NFC_SETTINGS)
            // 根据包名打开对应的设置界面
            startActivity(intent)
        }
        val tag = intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
    }
}

2.1 前台Activity捕获的方式获取Tag

class MainActivity : AppCompatActivity() {
    var mNfcAdapter: NfcAdapter? = null
    var pIntent: PendingIntent? = null

    override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       initNfc()
 

    }
    private fun initNfc() {
        mNfcAdapter = M1CardUtils.isNfcAble(this)
        pIntent = PendingIntent.getActivity(this, 0,  
        //在Manifest里或者这里设置当前activity启动模式,否则每次响应NFC事件,activity会重复创建
        Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0)
    }
    
    override fun onResume() {
        super.onResume()
        mNfcAdapter?.let {
            val ndef = IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED)
            val tag = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
            val tech = IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED)
            val filters = arrayOf(ndef, tag, tech)
            val techList = arrayOf(
                arrayOf(
                    "android.nfc.tech.Ndef",
                    "android.nfc.tech.NfcA",
                    "android.nfc.tech.NfcB",
                    "android.nfc.tech.NfcF",
                    "android.nfc.tech.NfcV",
                    "android.nfc.tech.NdefFormatable",
                    "android.nfc.tech.MifareClassic",
                    "android.nfc.tech.MifareUltralight",
                    "android.nfc.tech.NfcBarcode"
                )
            )
            it.enableForegroundDispatch(this, pIntent, filters, techList)
            XLog.d("开始捕获NFC数据")
        }
    }
    override fun onPause() {
        super.onPause()
        mNfcAdapter?.disableForegroundDispatch(this)
    }
    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        //这里必须setIntent,set  NFC事件响应后的intent才能拿到数据
        setIntent(intent)
        val tag = getIntent().getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
        //M1CardUtils 我后面会贴出来的
        if (M1CardUtils.isMifareClassic(tag)) {
            try {
                val reader = M1CardUtils.readCard(tag)
                XLog.d("读卡内容:$reader")
                val data = reader.split("|")
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
    }
}

四、解析标签数据

不论使用哪种方式,当我们获取到TAG标签后,解析方式都是相同的,需要根据不同的卡类型选择对应的解析方式
 


如图 我们能拿到卡片的信息,如图,括起来的部分分别对应的是:
支持的技术类型
MifareClassic 类型
扇区存储空间
扇区数
扇区中的块数

1. M1卡解析

这里说一下基础知识,不论是NFC还是读卡模块读,解析流程都是先寻卡,然后验证扇区的密码,取扇区的数据,比如已知要读的数据在2扇区,那么寻卡后验证时把要验证的扇区号、扇区的密码,和扇区的验证密码类型A/B传过去验证通过后,就可以读取数据了。


import android.app.Activity
import android.nfc.NfcAdapter
import android.nfc.Tag
import com.hjq.toast.ToastUtils
import kotlin.Throws
import android.nfc.tech.MifareClassic
import com.elvishew.xlog.XLog
import java.io.IOException
import java.lang.StringBuilder
import java.nio.charset.Charset


object M1CardUtils {
    /**
     * 判断是否支持NFC
     *
     * @return
     */
    fun isNfcAble(mContext: Activity?): NfcAdapter? {
        val mNfcAdapter = NfcAdapter.getDefaultAdapter(mContext)
        if (mNfcAdapter == null) {
            ToastUtils.show("设备不支持NFC!")
        }
        if (!mNfcAdapter!!.isEnabled) {
            ToastUtils.show("请在系统设置中先启用NFC功能!")
        }
        return mNfcAdapter
    }

    /**
     * 监测是否支持MifareClassic
     *
     * @param tag
     * @return
     */
    fun isMifareClassic(tag: Tag): Boolean {
        val techList = tag.techList
        var haveMifareUltralight = false
        for (tech in techList) {
            if (tech.contains("MifareClassic")) {
                haveMifareUltralight = true
                break
            }
        }
        if (!haveMifareUltralight) {
            ToastUtils.show("不支持MifareClassic")
            return false
        }
        return true
    }

    /**
     * 读取卡片信息
     *
     * @return
     */
    @Throws(IOException::class)
    fun readCard(tag: Tag?): String {
        val mifareClassic = MifareClassic.get(tag)
        return try {
            mifareClassic.connect()
            val metaInfo = StringBuilder()
            val gbk = Charset.forName("gbk")

            // 获取TAG中包含的扇区数
            val sectorCount = mifareClassic.sectorCount
            //            for (int j = 0; j < sectorCount; j++) {
            val bCount: Int //当前扇区的块数
            var bIndex: Int //当前扇区第一块
            if (m1Auth(mifareClassic, 2)) {
                bCount = mifareClassic.getBlockCountInSector(2)
                bIndex = mifareClassic.sectorToBlock(2)
                var length = 0
                for (i in 0 until bCount) {
                    val data = mifareClassic.readBlock(bIndex)
                    for (i1 in data.indices) {
                        if (data[i1] == 0.toByte()) {
                            length = i1
                        }
                    }
                    val dataString = String(data, 0, length, gbk).trim { it <= ' ' }
                    metaInfo.append(dataString)
                    bIndex++
                }
            } else {
                XLog.e("密码校验失败")
            }
            //            }
            metaInfo.toString()
        } catch (e: IOException) {
            throw IOException(e)
        } finally {
            try {
                mifareClassic.close()
            } catch (e: IOException) {
                throw IOException(e)
            }
        }
    }

    /**
     * 改写数据
     *
     * @param block
     * @param blockbyte
     */
    @Throws(IOException::class)
    fun writeBlock(tag: Tag?, block: Int, blockbyte: ByteArray?): Boolean {
        val mifareClassic = MifareClassic.get(tag)
        try {
            mifareClassic.connect()
            if (m1Auth(mifareClassic, block / 4)) {
                mifareClassic.writeBlock(block, blockbyte)
                XLog.e("writeBlock", "写入成功")
            } else {
                XLog.e("密码是", "没有找到密码")
                return false
            }
        } catch (e: IOException) {
            throw IOException(e)
        } finally {
            try {
                mifareClassic.close()
            } catch (e: IOException) {
                throw IOException(e)
            }
        }
        return true
    }

    /**
     * 密码校验
     *
     * @param mTag
     * @param position
     * @return
     * @throws IOException
     */
    @Throws(IOException::class)
    fun m1Auth(mTag: MifareClassic, position: Int): Boolean {
        if (mTag.authenticateSectorWithKeyA(position, MifareClassic.KEY_DEFAULT)) {
            return true
        } else if (mTag.authenticateSectorWithKeyB(position, MifareClassic.KEY_DEFAULT)) {
            return true
        }
        return false
    }


}

2. iso15693卡解析

本案例中没有用到这种,只是需要M1所以不需要这个,这是别的大佬封装的类发出来供参考

import android.nfc.tech.NfcV;
 
import com.haiheng.core.util.ByteUtils;
 
import java.io.IOException;
 
/**
 * NfcV(ISO 15693)读写操作
 *   用法
 *  NfcV mNfcV = NfcV.get(tag);
 *  mNfcV.connect();
 * <p>
 *  NfcVUtils mNfcVutil = new NfcVUtils(mNfcV);
 *  取得UID
 *  mNfcVutil.getUID();
 *  读取block在1位置的内容
 *  mNfcVutil.readOneBlock(1);
 *  从位置7开始读2个block的内容
 *  mNfcVutil.readBlocks(7, 2);
 *  取得block的个数
 *  mNfcVutil.getBlockNumber();
 *  取得1个block的长度
 *  mNfcVutil.getOneBlockSize();
 *  往位置1的block写内容
 *  mNfcVutil.writeBlock(1, new byte[]{0, 0, 0, 0})
 *
 * @author Kelly
 * @version 1.0.0
 * @filename NfcVUtils.java
 * @time 2018/10/30 10:29
 * @copyright(C) 2018 song
 */
public class NfcVUtils {
    private NfcV mNfcV;
    /**
     * UID数组行式
     */
    private byte[] ID;
    private String UID;
    private String DSFID;
    private String AFI;
    /**
     * block的个数
     */
    private int blockNumber;
    /**
     * 一个block长度
     */
    private int oneBlockSize;
    /**
     * 信息
     */
    private byte[] infoRmation;
 
    /**
     *  * 初始化
     *  * @param mNfcV NfcV对象
     *  * @throws IOException
     *  
     */
    public NfcVUtils(NfcV mNfcV) throws IOException {
        this.mNfcV = mNfcV;
        ID = this.mNfcV.getTag().getId();
        byte[] uid = new byte[ID.length];
        int j = 0;
        for (int i = ID.length - 1; i >= 0; i--) {
            uid[j] = ID[i];
            j++;
        }
        this.UID = ByteUtils.byteArrToHexString(uid);
        getInfoRmation();
    }
 
    public String getUID() {
        return UID;
    }
 
    /**
     *  * 取得标签信息 
     *  
     */
    private byte[] getInfoRmation() throws IOException {
        byte[] cmd = new byte[10];
        cmd[0] = (byte) 0x22; // flag
        cmd[1] = (byte) 0x2B; // command
        System.arraycopy(ID, 0, cmd, 2, ID.length); // UID
        infoRmation = mNfcV.transceive(cmd);
        blockNumber = infoRmation[12];
        oneBlockSize = infoRmation[13];
        AFI = ByteUtils.byteArrToHexString(new byte[]{infoRmation[11]});
        DSFID = ByteUtils.byteArrToHexString(new byte[]{infoRmation[10]});
        return infoRmation;
    }
 
    public String getDSFID() {
        return DSFID;
    }
 
    public String getAFI() {
        return AFI;
    }
 
    public int getBlockNumber() {
        return blockNumber + 1;
    }
 
    public int getOneBlockSize() {
        return oneBlockSize + 1;
    }
 
    /**
     *  * 读取一个位置在position的block
     *  * @param position 要读取的block位置
     *  * @return 返回内容字符串
     *  * @throws IOException
     *  
     */
    public String readOneBlock(int position) throws IOException {
        byte cmd[] = new byte[11];
        cmd[0] = (byte) 0x22;
        cmd[1] = (byte) 0x20;
        System.arraycopy(ID, 0, cmd, 2, ID.length); // UID
        cmd[10] = (byte) position;
        byte res[] = mNfcV.transceive(cmd);
        if (res[0] == 0x00) {
            byte block[] = new byte[res.length - 1];
            System.arraycopy(res, 1, block, 0, res.length - 1);
            return ByteUtils.byteArrToHexString(block);
        }
        return null;
    }
 
    /**
     *  * 读取从begin开始end个block
     *  * begin + count 不能超过blockNumber
     *  * @param begin block开始位置
     *  * @param count 读取block数量
     *  * @return 返回内容字符串
     *  * @throws IOException
     *  
     */
    public String readBlocks(int begin, int count) throws IOException {
        if ((begin + count) > blockNumber) {
            count = blockNumber - begin;
        }
        StringBuffer data = new StringBuffer();
        for (int i = begin; i < count + begin; i++) {
            data.append(readOneBlock(i));
        }
        return data.toString();
    }
 
 
    /**
     *  * 将数据写入到block,
     *  * @param position 要写内容的block位置
     *  * @param data 要写的内容,必须长度为blockOneSize
     *  * @return false为写入失败,true为写入成功
     *  * @throws IOException 
     *  
     */
    public boolean writeBlock(int position, byte[] data) throws IOException {
        byte cmd[] = new byte[15];
        cmd[0] = (byte) 0x22;
        cmd[1] = (byte) 0x21;
        System.arraycopy(ID, 0, cmd, 2, ID.length); // UID
        //block
        cmd[10] = (byte) position;
        //value
        System.arraycopy(data, 0, cmd, 11, data.length);
        byte[] rsp = mNfcV.transceive(cmd);
        if (rsp[0] == 0x00)
            return true;
        return false;
    }
}

总结

以上就是今天要讲的内容,文章中如有错误或者需要改进的地方欢迎补充指正,本文仅介绍了NFC的使用和M1卡的读取解析场景,关于NFC的历史、卡片类型、Intent filter类型详细描述,其他使用场景等可以参考更多文档,这里贴出来几个我看到的对我很有帮助的文章,也欢迎大家多做参考,

NFC 各种卡类型、区别、历史介绍
https://zhuanlan.zhihu.com/p/344426747
各种官方资料中文说明
https://blog.csdn.net/u013164293/article/details/124474247?spm=1001.2014.3001.5506

 

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

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

相关文章

笔记37:全卷积网络FCN结构详解

本地笔记&#xff1a;D:\work_file\DeepLearning_Learning\03_个人笔记\FCN学习 a a a a a a a a a a a a a a a a a a a a a a a

五眼联盟指的是什么?台湾社会中的“特助”指的是什么?计算机组成原理人工智能

目录 五眼联盟指的是什么&#xff1f; 台湾社会中的“特助”指的是什么&#xff1f; 计算机组成原理 人工智能 落土八分命 「一部悲劇用喜劇呈現&#xff0c;那才是真正的悲劇。」 那个声音到底是什么呢&#xff1f; 被困在了大佛中 五眼联盟指的是什么&#xff1f; 五…

该选择什么行情时做上证50ETF期权?

期权交易概述 上证50ETF期权是基于上海证券交易所上证50ETF基金而衍生的金融衍生品,作为金融市场的重要工具,为投资者提供了丰富多样的交易机会&#xff0c;下文介绍该选择什么行情时做上证50ETF期权&#xff1f;本文来自&#xff1a;期权酱 随着全球经济的不断发展和资本市场的…

2024年研究生网上报名各类问题类似参考解答系列之—社保类疑问

研究生网上报名的流程看似简单&#xff0c;但由于考生众多&#xff0c;情况各异&#xff0c;也会出现各类在政策中并不能直接找到答案的问题。杭州达立易考教育通过2023届研究生网报过程中考生所反映的一些问题以及官方给予的参考答案和解决思路&#xff0c;帮助大家做个梳理&a…

css 写带三角形的对话框,空心的三角形边框

首先&#xff0c;我们要会先实现一个小三角形&#xff1b; 思路&#xff1a;利用元素的 border 属性&#xff0c;将其三个方向的 border-color 值设为透明色&#xff08;或者和其父元素的背景色一致&#xff0c;形成视觉差&#xff0c;俗称障眼法&#xff09;&#xff0c;剩下…

C400/A8/1/1/1/00 MAX-4/11/03/128/99/1/0/00

C400/A8/1/1/1/00 MAX-4/11/03/128/99/1/0/00 MakerBot CloudPrint简化了3D打印工作流程&#xff0c;提高了生产率&#xff0c;同时减少了项目之间的打印机停机时间。主要特性包括: 打印准备:用户可以直接从浏览器切片和准备3D打印。全新的全功能打印准备视图允许轻松定位和预…

vue js 实现页面在浏览器全屏切换

需求&#xff1a; 在浏览器中点击按钮实现页面的全屏与非全屏的切换。 如图&#xff1a; 全屏前&#xff1a; 全屏后&#xff1a; 具体实现代码如下&#xff1a; html&#xff1a; <template><div class"development-history" id"echarts-wrap&quo…

vscode 调试使用 make 编译的项目

1、首先点击运行 --> 启动调试&#xff1a; 2、选择g或gcc生成和调试活动文件&#xff1a; 3、出现下面提示是正常的&#xff0c;点击仍要调试&#xff1a; 点击打开“launch.json”&#xff1a; 4、此时会在项目工作目录下生成tsak.josn和launch.json文件&#xff1a; 如…

支持多种格式照片处理软件Lightroom Classic 2022 mac中文功能特点

Lightroom Classic 2022 mac是一款专业级数字图像处理软件&#xff0c;主要用于数字照片的后期处理和管理。它提供了丰富的工具和功能&#xff0c;可以帮助用户对照片进行调整、修饰、管理和分享。 Lightroom Classic 2022 mac软件功能和特点 RAW格式支持&#xff1a;Lightroo…

网络安全(骇客)—技术学习

1.网络安全是什么 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 2.网络安全市场 一、是市场需求量高&#xff1b; 二、则是发展相对成熟入…

JavaScript进阶(二十三):立即执行函数(匿名函数)( ( ) { } ( ) )含义解析

文章目录 一、前言二、立即执行函数2.1 立即执行函数使用的场景 三、拓展阅读 一、前言 前端项目改造过程中&#xff0c;引入的工具类实现如下&#xff1a; var tensquared(function(x) {return x*x; }(10)); 拆解以上语句如下&#xff1a; var tensquared xx; 这是赋值语句…

Redis学习2——String数据类型的操作

Redis常用数据类型 String 数据结构 有点像动态分区按边界对齐。 基本操作 也可以一次设置多个 得到范围值 setrange和append的区别是宏观上看append是从尾部追加而setrange是从设置的起始位置开始的。 也可以在set时就对key设置过期时间&#xff0c; 最后一个是

九小场所安全隐患排查—线上隐患上报、整改

为进一步加强治安管理工作&#xff0c;严格落实安全责任&#xff0c;扎实筑牢“安全防火墙”&#xff0c;营造和谐、稳定、文明的社会环境。我们可借助凡尔码搭建九小场所安全码&#xff0c;实现消防安全监督管理&#xff0c;落实消防安全责任&#xff0c;形成九小场所网格化监…

5 款漏洞扫描工具:实用、强力、全面(含开源)

引言 漏洞扫描是一种安全检测行为&#xff0c;更是一类重要的网络安全技术&#xff0c;它能够有效提高网络的安全性&#xff0c;而且漏洞扫描属于主动的防范措施&#xff0c;可以很好地避免黑客攻击行为&#xff0c;做到防患于未然。那么好用的漏洞扫描工具有哪些&#xff1f; …

防止SQL注入攻击的综合解决方案

文章目录 摘要背景和危害性防御措施示例代码&#xff08;Java&#xff09;示例代码&#xff08;PHP&#xff09;示例MySQL命令示例代码&#xff08;Python&#xff09;示例代码&#xff08;C#&#xff0c;使用Entity Framework&#xff09; 进一步防御SQL注入攻击的措施使用ORM…

SpringCloud组件Ribbon的IRule的问题排查

最近很久没有写文章啦&#xff0c;刚好遇到了一个问题&#xff0c;其实问题也挺简单&#xff0c;但是还是得对源码有一定了解才能够发现。 最近在实现一个根据请求流量的标签&#xff0c;将请求转发到对应的节点&#xff0c;其实和俗称的灰度请求有点相似&#xff0c; 实现思…

Redis安装教程

官网地址 地址链接&#xff1a;传送门 安装步骤 这里有更多版本的选择 进去根据自己的需要选择版本&#xff0c;我这里用的7系列的稳定版。

软件企业找第三方软件测评机构做确认测试有什么优势?

软件确认测试是一个在软件开发过程中十分重要的环节。它确保了软件的功能符合预期&#xff0c;达到了用户的需求和期望。确认测试主要验证软件的功能、性能、易用性、稳定性等方面&#xff0c;旨在发现和修复潜在的问题和缺陷。通过进行全面的确认测试&#xff0c;软件企业可以…

操作系统学习笔记--进程与线程

进程 概念 不同的角度有不同的定义 进程是程序的一次执行过程进程是一个程序及其数据在处理机上顺序执行时所发生的活动进程是具有独立功能的程序在一个数据集合上运行的过程&#xff0c;它是系统进行资源分配和调度的一个独立单位 进程&#xff1a;是动态的&#xff0c;是…

Flink-SQL join 优化 -- MiniBatch + local-global

背景 问题1. 近期在开发flink-sql期间&#xff0c;发现数据在启动后&#xff0c;任务总是进行重试&#xff0c;运行一段时间后&#xff0c;container心跳超时&#xff0c;内存溢出&#xff0c;作业无法进行正常工作 023-10-07 14:53:30,408 | INFO | [flink-akka.actor.defa…