[TOTP]android kotlin实现 totp身份验证器 类似Google身份验证器

        背景:自己或者公司用一些谷歌身份验证器或者microsoft身份验证器,下载来源不明,或者有广告,使用不安全。于是自己写一个,安全放心使用。

代码已开源:shixiaotian/sxt-android-totp: android totp authenticator (github.com)

效果图

  

此身份验证器,一共1个activity,3个fragment。

实现原理,通过线程动态触发totp算法,从加密的sqlite里读取密钥信息进行加密计算。新增密钥可通过扫描二维码,或者手动添加的方式实现。

一.添加对应的包

包括,totp算法包,zxing二维码扫描包,sqlite加密包

分别为

authenticator="1.0.0"
zxing-android-embedded="4.2.0"
database-sqlcipher="4.5.0"

androidx-authenticator = { group = "org.jboss.aerogear", name = "aerogear-otp-java", version.ref = "authenticator" }
androidx-zxing-android-embedded = { group = "com.journeyapps", name = "zxing-android-embedded", version.ref = "zxing-android-embedded" }
androidx-database-sqlcipher ={ group = "net.zetetic", name = "android-database-sqlcipher", version.ref = "database-sqlcipher" }

  文件

libs.versions.toml

[versions]
agp = "8.7.2"
kotlin = "1.9.24"
coreKtx = "1.10.1"
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
appcompat = "1.6.1"
material = "1.10.0"
activity = "1.8.0"
constraintlayout = "2.1.4"
authenticator="1.0.0"
zxing-android-embedded="4.2.0"
database-sqlcipher="4.5.0"

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
androidx-authenticator = { group = "org.jboss.aerogear", name = "aerogear-otp-java", version.ref = "authenticator" }
androidx-zxing-android-embedded = { group = "com.journeyapps", name = "zxing-android-embedded", version.ref = "zxing-android-embedded" }
androidx-database-sqlcipher ={ group = "net.zetetic", name = "android-database-sqlcipher", version.ref = "database-sqlcipher" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }

 build.gradele.kts

plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
}

android {
    namespace = "com.shixiaotian.totp.scan.application"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.shixiaotian.totp.scan.application"
        minSdk = 24
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
    kotlinOptions {
        jvmTarget = "11"
    }
}

dependencies {

    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.appcompat)
    implementation(libs.material)
    implementation(libs.androidx.activity)
    implementation(libs.androidx.constraintlayout)
    implementation(libs.androidx.authenticator)
    implementation(libs.androidx.espresso.core)
    implementation(libs.androidx.zxing.android.embedded)
    implementation(libs.androidx.database.sqlcipher)

    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.junit)
    androidTestImplementation(libs.androidx.espresso.core)

}

二.项目程序文件结构为

三.全部程序文件

MyConstant

作用:常量类,添加了如下三个参数,方便自行变换,增加安全性和防止撞库

package com.shixiaotian.totp.scan.application.common

class MyConstants {
    companion object {
        //数据库名称,记得加扰动防止重复
        const val dbName = "sxt.auth.code.0098675.db"
        // 数据库密码
        const val dbPassword = "jsdfjhkldsvcbuehuisudbekokmhshyebgqoondyasd"

        // app 首次运行标记
        const val firstRunTag = "sxt.auth.code.0098674.firstRunTag"
    }
}

DatabaseHelper

作用:数据库操作相关类,集成了数据库加密,初始化,基本插入、读取、删除等功能

package com.shixiaotian.totp.scan.application.db


import android.content.Context
import android.database.Cursor
import androidx.core.content.contentValuesOf
import com.shixiaotian.totp.scan.application.common.MyConstants
import com.shixiaotian.totp.scan.application.vo.User
import net.sqlcipher.database.SQLiteDatabase
import net.sqlcipher.database.SQLiteOpenHelper

class DatabaseHelper(context: Context) : SQLiteOpenHelper(context, MyConstants.dbName, null, 1) {

    private val dbContext:Context = context

    override fun onCreate(db: SQLiteDatabase) {

    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        // 更新数据库的时候调用
    }

    private fun openEncryptedDatabase(): SQLiteDatabase {
        val dbHelper = DatabaseHelper(dbContext)
        val db = dbHelper.getWritableDatabase(MyConstants.dbPassword)
        return db
    }

    // 写入数据
    fun insertUser(username: String, secretKey: String, issuer: String): Long {
        val db = this.openEncryptedDatabase()
        var id =db.insert("sxt_totp_users", null, contentValuesOf("username" to username, "secretKey" to secretKey, "issuer" to issuer))
        db.close()
        return id
    }

    // 删除数据
    fun deleteUser(id: Int) {
        val db = this.openEncryptedDatabase()
        db.delete("sxt_totp_users", "id = ?", arrayOf(id.toString()))
        db.close()
    }

    // 查询数据
    fun getUser(id: Int): User? {
        val db = this.openEncryptedDatabase()
        val cursor: Cursor = db.query("sxt_totp_users", null, "id = ?", arrayOf(id.toString()), null, null, null)

        var user: User? = null
        if (cursor.moveToFirst()) {
            val id = cursor.getInt(0)
            val name = cursor.getString(1)
            val secretKey = cursor.getString(2)
            val issuer = cursor.getString(3)
            // 假设User有id和name两个字段
            user = User(id, name,secretKey, issuer)
        }
        cursor.close()
        db.close()
        return user
    }

    fun getAllUser(): List<User> {
        val db = this.openEncryptedDatabase()
        val items = ArrayList<User>()
        val cursor: Cursor  = db.query("sxt_totp_users", arrayOf("id","username","secretKey", "issuer"), null, null, null, null, null)
        cursor.moveToFirst()
        while (!cursor.isAfterLast) {
            val id = cursor.getInt(0)
            val name = cursor.getString(1)
            val secretKey = cursor.getString(2)
            val issuer = cursor.getString(3)
            items.add(User(id, name,secretKey, issuer))
            cursor.moveToNext()
        }
        cursor.close()
        db.close()
        return items
    }

    fun initDB() {
        // 创建表
        val db = this.openEncryptedDatabase()
        db.execSQL("CREATE TABLE sxt_totp_users (id INTEGER PRIMARY KEY, username TEXT, secretKey TEXT, issuer TEXT)")
        db.close()
    }

    fun init() {
        this.insertUser("apple","apple", "github")
        this.insertUser("pear","pear", "steam")
        this.insertUser("apricot","apricot","wiki")
        this.insertUser("peach","peach","TK")
    }
}

CodeAddFragment

作用:令牌添加页面片段,提供手动输入令牌,和触发zxing令牌扫描功能,并回收zxing扫描后的结果,进行存储,将相关id数据传输给codeShow单独展示的页面进行展示。

package com.shixiaotian.totp.scan.application.fragments

import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import com.shixiaotian.totp.scan.application.db.DatabaseHelper
import com.shixiaotian.totp.scan.application.tools.EncodeTools
import com.google.zxing.integration.android.IntentIntegrator
import com.journeyapps.barcodescanner.CaptureActivity
import com.shixiaotian.totp.scan.application.R

// TODO: Rename parameter arguments, choose names that match
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"

/**
 * A simple [Fragment] subclass.
 * Use the [CodeAddFragment.newInstance] factory method to
 * create an instance of this fragment.
 */
class CodeAddFragment : Fragment() {
    private lateinit var saveButton: View
    private lateinit var scanButton: View
    private lateinit var nameText: TextView
    private lateinit var secretKeyText: TextView
    private lateinit var issuerText: TextView

    // TODO: Rename and change types of parameters
    private var param1: String? = null
    private var param2: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            param1 = it.getString(ARG_PARAM1)
            param2 = it.getString(ARG_PARAM2)
        }
    }

    @SuppressLint("MissingInflatedId")
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        val view = inflater.inflate(R.layout.fragment_code_add, container, false)
        val dbHelper = DatabaseHelper(requireContext())
        saveButton = view.findViewById(R.id.saveButton)

        saveButton.setOnClickListener {
            nameText = view.findViewById<TextView>(R.id.addUsernameText)
            val name = nameText.getText();
            secretKeyText = view.findViewById<TextView>(R.id.addSecretKeyText)
            val secretKey = secretKeyText.getText();
            issuerText = view.findViewById<TextView>(R.id.addIssuerText)
            val issuer = issuerText.getText();

            if(name.isEmpty()) {
                alertAddError("Name can't be blank")
            } else if(secretKey.isEmpty()) {
                alertAddError("SecretKey can't be blank")
            } else if(issuer.isEmpty()){
                alertAddError("issuer can't be blank")
            } else {

                var saveId = dbHelper.insertUser(name.toString(), secretKey.toString(), issuer.toString());
                nameText.setText("")
                secretKeyText.setText("")
                issuerText.setText("")

                val fragment = CodeShowFragment.newInstance(saveId.toString(), "")
                parentFragmentManager.beginTransaction().replace(R.id.viewPager, fragment).commit()

            }
        }

        scanButton = view.findViewById(R.id.cameraButton)

        scanButton.setOnClickListener {

            val integrator = IntentIntegrator.forSupportFragment(this)
            integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE)
            integrator.setOrientationLocked(false)
            integrator.captureActivity = CaptureActivity::class.java
            integrator.setRequestCode(5766)  //_scan为自己定义的请求码
            integrator.initiateScan()
        }

        return view
    }

    fun alertAddError(msg : String) {
        println("alertAddError : " + msg)
        val builder = AlertDialog.Builder(context)
        builder.setTitle("add error")
        builder.setMessage(msg)
        builder.setPositiveButton("OK") { dialog, _ ->
            dialog.dismiss()
        }
        builder.create().show()
    }


    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        when (requestCode) {
            //_scan为自己定义的扫码请求码
            5766 -> {
                // 跳转扫描页面返回扫描数据
                var scanResult = IntentIntegrator.parseActivityResult(
                    IntentIntegrator.REQUEST_CODE,
                    resultCode,
                    data
                );
                //  判断返回值是否为空
                if (scanResult != null) {
                    //返回条形码数据
                    var result = scanResult.contents
                    if(result == null) {
                        parentFragmentManager.beginTransaction().replace(R.id.viewPager, this).commit()
                        return
                    }
                    val user = EncodeTools.decode(result);

                    if(user == null) {
                        Toast.makeText(context, "Scan Fail", Toast.LENGTH_SHORT).show()
                        parentFragmentManager.beginTransaction().replace(R.id.viewPager, this).commit()
                        return
                    }

                    // 保存数据
                    val dbHelper = DatabaseHelper(requireContext())
                    var saveId = dbHelper.insertUser(user!!.getUsername(), user.getSecretKey(), user.getIssuer())
                    if(saveId < 0 ) {
                        Toast.makeText(context, "Save Data Fail", Toast.LENGTH_SHORT).show()
                        parentFragmentManager.beginTransaction().replace(R.id.viewPager, this).commit()
                        return
                    }

                    // 跳转
                    val fragment = CodeShowFragment.newInstance(saveId.toString(), "")
                    parentFragmentManager.beginTransaction().replace(R.id.viewPager, fragment).commit()
                } else {
                    Toast.makeText(context, "Scan Fail:ERROR", Toast.LENGTH_SHORT).show()
                    parentFragmentManager.beginTransaction().replace(R.id.viewPager, this).commit()

                }
            } else -> {
                val fragment = CodeAddFragment()
                parentFragmentManager.beginTransaction().replace(R.id.viewPager, fragment).commit()
            }
        }
    }

    companion object {
        /**
         * Use this factory method to create a new instance of
         * this fragment using the provided parameters.
         *
         * @param param1 Parameter 1.
         * @param param2 Parameter 2.
         * @return A new instance of fragment CodeAddFragment.
         */
        // TODO: Rename and change types and number of parameters
        @JvmStatic
        fun newInstance(param1: String, param2: String) =
            CodeAddFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_PARAM1, param1)
                    putString(ARG_PARAM2, param2)
                }
            }
    }
}

CodeListFragment

作用:令牌列表界面,提供了定时器刷新整个列表的功能,和每个令牌单独点击触发的功能

package com.shixiaotian.totp.scan.application.fragments

import android.annotation.SuppressLint
import android.os.Bundle
import android.os.Handler
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ListView
import android.widget.TextView
import com.shixiaotian.totp.scan.application.CodeListAdapter
import com.shixiaotian.totp.scan.application.R
import com.shixiaotian.totp.scan.application.db.DatabaseHelper
import com.shixiaotian.totp.scan.application.tools.MyTimeUtils
import com.shixiaotian.totp.scan.application.vo.User

// TODO: Rename parameter arguments, choose names that match
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"

/**
 * A simple [Fragment] subclass.
 * Use the [CodeListFragment.newInstance] factory method to
 * create an instance of this fragment.
 */
class CodeListFragment : Fragment() {
    private var start: Long = 30
    private lateinit var adapter: CodeListAdapter
    private val handler = Handler()
    private var runnable: Runnable? = null

    // TODO: Rename and change types of parameters
    private var param1: String? = null
    private var param2: String? = null
    private var userList: List<User>? =null

    private val data = listOf("apple","pear","apricot","peach","grape","banana","pineapple","plum","watermelon","orange","lemon","mango","strawberry",
        "medlar","mulberry","nectarine","cherry","pomegranate","fig","persimmon")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            param1 = it.getString(ARG_PARAM1)
            param2 = it.getString(ARG_PARAM2)
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // 获取视图
        val view = inflater.inflate(R.layout.fragment_code_list, container, false)
        val listView = view.findViewById<ListView>(R.id.listView)
        var textView3 = view.findViewById<TextView>(R.id.textView3)
        // 查询数据库
        val dbHelper = DatabaseHelper(requireContext())
        userList = dbHelper.getAllUser();
        //获取当前分钟秒数
        // 启动定时器
        timer(textView3)

        // 创建ArrayAdapter,将数据源传递给它
        adapter = CodeListAdapter(requireContext(), R.layout.code_item, userList!!)

        // 将适配器与ListView关联
        listView.adapter = adapter

        listView.setOnItemClickListener { parent, view, position, id ->
            val textView = view.findViewById<TextView>(R.id.user_id);

            val fragment = CodeShowFragment.newInstance(textView.text.toString(), "")
            // 执行跳转
            parentFragmentManager.beginTransaction().replace(R.id.viewPager, fragment).commit()
            val codeShowFragment = CodeShowFragment()
            switchFragment(codeShowFragment)

        }

        return view
    }

    // 刷新数据
    private fun refresh() {
        if(userList != null) {
            adapter.notifyDataSetChanged()
        }

    }

    // 定时器
    private fun timer(textView : TextView) {
        // 动态计算当前秒数
        start = MyTimeUtils.getCurrentSec()

        runnable = Runnable {

            val formattedNumber = String.format("%02d",start/1000)
            textView.setText(formattedNumber + "s")
            start = start -100
            if(start <0) {
                refresh()
                start= MyTimeUtils.getCurrentSec()
            }
            // 在这里设置下一次循环的延时时间,例如1秒
            handler.postDelayed(runnable!!, 100)
        }

        // 初始化计时器
        handler.postDelayed(runnable!!, 50) // 延时1秒后开始循环

    }



    @SuppressLint("SuspiciousIndentation")
    private fun switchFragment(fragment: Fragment) {
        val transaction = parentFragmentManager.beginTransaction()

        transaction.show(fragment)

        transaction.commit()
    }

    companion object {
        /**
         * Use this factory method to create a new instance of
         * this fragment using the provided parameters.
         *
         * @param param1 Parameter 1.
         * @param param2 Parameter 2.
         * @return A new instance of fragment CodeListFragment.
         */
        // TODO: Rename and change types and number of parameters
        @JvmStatic
        fun newInstance(param1: String, param2: String) =
            CodeListFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_PARAM1, param1)
                    putString(ARG_PARAM2, param2)
                }
            }
    }

    override fun onDestroyView() {
        println("onDestroyView")
        handler.removeCallbacks(runnable!!)
        super.onDestroyView()
    }
}

CodeShowFragment

作用:令牌单独展示页面的片段代码,用于页面操作功能处理。提供了定时器进行倒计时刷新令牌,和令牌删除功能。

package com.shixiaotian.totp.scan.application.fragments

import android.app.AlertDialog
import android.os.Bundle
import android.os.Handler
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ProgressBar
import android.widget.TextView
import com.shixiaotian.totp.scan.application.R
import com.shixiaotian.totp.scan.application.db.DatabaseHelper
import com.shixiaotian.totp.scan.application.tools.EncodeTools
import com.shixiaotian.totp.scan.application.tools.MyTimeUtils

// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"

/**
 * A simple [Fragment] subclass.
 * Use the [CodeShowFragment.newInstance] factory method to
 * create an instance of this fragment.
 */
class CodeShowFragment : Fragment() {
    private var codeView: TextView? =null

    // TODO: Rename and change types of parameters
    private var param1: String? = null
    private var param2: String? = null

    private var start: Long = 30000
    private val handler = Handler()
    private var runnable: Runnable? = null
    private var secretKey: String =""
    private lateinit var progressBar: ProgressBar

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            param1 = it.getString(ARG_PARAM1)
            param2 = it.getString(ARG_PARAM2)
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment

        var id: String? = param1
        if(id == null) {
            id = "0"
        }
        // 查询数据库
        val dbHelper = DatabaseHelper(requireContext())
        val user = dbHelper.getUser(id.toInt())
        val view = inflater.inflate(R.layout.fragment_code_show, container, false)
        // 初始化进度条
        progressBar = view.findViewById<ProgressBar>(R.id.progressBar)

        // 设置进度条的最大值
        progressBar.max = 30000
        // 设置当前进度
        progressBar.progress = 30000
        // 显示进度条
        progressBar.visibility = ProgressBar.VISIBLE

        val showIssuerTextView = view.findViewById<TextView>(R.id.showIssuerTextView)
        val usernameView = view.findViewById<TextView>(R.id.showUsernameTextView)
        codeView = view.findViewById<TextView>(R.id.showCodeView)
        val timeView3 = view.findViewById<TextView>(R.id.showTimeView)
        if(user != null) {
            secretKey = user!!.getSecretKey();
            // 开启个线程,动态计算密钥,并更新到ui界面
            showIssuerTextView.setText(user.getIssuer())
            usernameView.setText(user.getUsername())
            if (codeView != null) {
                codeView!!.setText(EncodeTools.encode(user.getSecretKey()))
            }
            timer(timeView3)
        }

        // 删除按钮
        val deleteButton = view.findViewById<TextView>(R.id.deleteButton)
        deleteButton.setOnClickListener {
            showDeleteConfirmationDialog(id)
        }

        return view
    }

    private fun refresh() {
        codeView!!.setText(EncodeTools.encode(secretKey))
    }

    private fun timer(textView : TextView) {
        // 动态计算当前秒数
        start = MyTimeUtils.getCurrentSec()

        runnable = Runnable {

            val formattedNumber = String.format("%02d",start/1000)
            textView.setText(formattedNumber + "s")
            progressBar.setProgress(start.toInt());
            start = start -100
            if(start < 0) {
                start= MyTimeUtils.getCurrentSec()
                var refreshRunnable =  Runnable {
                    refresh()
                }
                Thread(refreshRunnable).start()

            }
            // 在这里设置下一次循环的延时时间,例如1秒
            handler.postDelayed(runnable!!, 100)
        }

        // 初始化计时器
        handler.postDelayed(runnable!!, 50) // 延时1秒后开始循环

    }

    fun showDeleteConfirmationDialog(deleteId : String) {
        val builder = AlertDialog.Builder(context)
        builder.setMessage("确定要删除吗?")
            .setPositiveButton("Yes") { dialog, id ->
                // 删除操作
                val dbHelper = DatabaseHelper(requireContext())
                dbHelper.deleteUser(deleteId.toInt())
                val codeListFragment = CodeListFragment()
                parentFragmentManager.beginTransaction().replace(R.id.viewPager, codeListFragment).commit()
            }
            .setNegativeButton("No") { dialog, id ->
                // 取消操作,对话框不会被关闭
            }
            .setCancelable(false)
        val alert = builder.create()
        alert.show()
    }

    companion object {
        /**
         * Use this factory method to create a new instance of
         * this fragment using the provided parameters.
         *
         * @param param1 Parameter 1.
         * @param param2 Parameter 2.
         * @return A new instance of fragment CodeShowFragment.
         */
        // TODO: Rename and change types and number of parameters
        @JvmStatic
        fun newInstance(param1: String, param2: String) =
            CodeShowFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_PARAM1, param1)
                    putString(ARG_PARAM2, param2)
                }
            }
    }

    override fun onDestroyView() {
        if(handler!= null && runnable != null) {
            handler.removeCallbacks(runnable!!)
        }
        super.onDestroyView()
    }

}

EncodeTools

作用:软件核心功能,调用jboss包的otp算法,对密钥进行运算,得出动态令牌。解析二维码扫描出的totp链接信息,转换成user实体提供软件运行

package com.shixiaotian.totp.scan.application.tools

import com.shixiaotian.totp.scan.application.vo.User
import org.jboss.aerogear.security.otp.Totp


class EncodeTools {

    companion object {
        @JvmStatic
        fun encode(
            secretKey: String,
            timeStep: Long = 30,
            digits: Int = 6,
            algorithm: String = "SHA1"
        ): String? {

            if (secretKey == null || secretKey.isBlank()) {
                return ""
            }
            try {
                val totp = Totp(secretKey);
                var result = totp.now();
                return result;
            } catch (e: Exception) {
                return "ERROR SK"
            }
        }

        @JvmStatic
        fun decode(uri: String): User? {

            if(uri.isEmpty()) {
                return null
            }

            if(!uri.startsWith("otpauth://totp/")) {
                return null
            }
            try {

                var uriContentIndex = uri.indexOf("otpauth://totp/");

                var uriContent = uri.subSequence(15, uri.length);
                val secContents = uriContent.split(":");

                var issuer = secContents.get(0);
                var otherContent = secContents.get(1)
                val secOtherContent= otherContent.split("?")
                var username = secOtherContent.get(0)

                var thOtherContent = secOtherContent.get(1)
                val fthOtherContent = thOtherContent.split("&")

                var secretKeyContent = fthOtherContent.get(0)
                var secretKey = secretKeyContent.split("=").get(1)
                var user = User(0, username, secretKey, issuer)
                return user
            }catch (e: Exception) {
                return null
            }
        }
    }

}

FirstRunTools

作用:检测软件是否为第一次安装使用,该段代码不完全适用,建议使用者进行改造,或者移除

package com.shixiaotian.totp.scan.application.tools

import android.content.Context
import android.content.SharedPreferences
import com.shixiaotian.totp.scan.application.common.MyConstants

class FirstRunTools {

    companion object {
        @JvmStatic fun isFirstRun(context: Context): Boolean {
            val prefs: SharedPreferences = context.getSharedPreferences(MyConstants.firstRunTag, Context.MODE_PRIVATE)
            val isFirstTime = prefs.getBoolean(MyConstants.firstRunTag + "isFirstTime", true)
            if (isFirstTime) {
                val editor = prefs.edit()
                editor.putBoolean(MyConstants.firstRunTag + "isFirstTime", false)
                editor.apply()
                return true
            }
            return false
        }

    }
}

MyTimeUtils

作用:时间工具,因为totp是每30秒计算一次,而每次进入软件的时间不同,该功能用于纠正进入的时间差,让令牌刷新倒计时进入精确的时间区间。

package com.shixiaotian.totp.scan.application.tools

import android.icu.util.Calendar

class MyTimeUtils {

    companion object {
        @JvmStatic fun getCurrentSec(): Long {
            // 获取当前时间的毫秒数
            val currentTimeMillis = System.currentTimeMillis()

            // 创建Calendar实例
            val calendar = Calendar.getInstance()

            // 设置Calendar的时间为当前时间
            calendar.timeInMillis = currentTimeMillis

            // 将秒和毫秒字段重置为0
            calendar.set(Calendar.SECOND, 0)
            calendar.set(Calendar.MILLISECOND, 0)

            // 当前分钟的开始时间的毫秒数
            val startOfCurrentMinuteMillis = calendar.timeInMillis

            // 已过去的毫秒数
            val elapsedMillis = currentTimeMillis - startOfCurrentMinuteMillis

            // 已过去的秒数
            //val elapsedSeconds = elapsedMillis / 1000

            if(elapsedMillis > 30000) {
                return 60000 - elapsedMillis;
            } else {
                return 30000 - elapsedMillis;
            }

        }
    }
}

User

作用:作为数据传输和存储的实体,存储用户的令牌等相关信息

package com.shixiaotian.totp.scan.application.vo

class User {

    private var id : Int = 0
    private var username : String=""
    private var secretKey : String=""
    private var issuer : String=""
    private var code : String=""

    constructor(id: Int, username: String, secretKey: String, issuer: String) {
        this.id = id
        this.username = username
        this.secretKey = secretKey
        this.issuer = issuer
    }

    fun getId() : Int {
        return id
    }
    fun setId(id : Int) {
        this.id = id
    }

    fun getUsername() : String {
        return username
    }
    fun setUsername(username : String) {
        this.username = username
    }

    fun getSecretKey() : String {
        return secretKey
    }
    fun setSecretKey(secretKey : String) {
        this.secretKey = secretKey
    }

    fun getCode() : String {
        return code
    }
    fun setCode(code : String) {
        this.code = code
    }

    fun getIssuer() : String {
        return issuer
    }
    fun setIssuer(issuer : String) {
        this.issuer = issuer
    }
}

CodeListAdapter

作用:适配列表内每一个数据,为令牌动态计算提供适配

package com.shixiaotian.totp.scan.application

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.TextView
import com.shixiaotian.totp.scan.application.tools.EncodeTools
import com.shixiaotian.totp.scan.application.vo.User

/**
 * 动态码列表内容适配器
 */
class CodeListAdapter (context: Context, val resourceId: Int, data: List<User>) : ArrayAdapter<User>(context, resourceId, data) {

    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        val view = LayoutInflater.from(context).inflate(resourceId, parent, false)
        val userId: TextView = view.findViewById(R.id.user_id)
        val issuer: TextView = view.findViewById(R.id.user_issuer)
        val username: TextView = view.findViewById(R.id.user_username)
        val userSecretKey: TextView = view.findViewById(R.id.user_secretKey)
        val userCode: TextView = view.findViewById(R.id.user_code)

        val user = getItem(position)

        if (user!=null){

            userId.text = user.getId().toString()
            issuer.text = user.getIssuer()
            username.text = user.getUsername()
            userSecretKey.text = user.getSecretKey()

            var code = EncodeTools.encode(user.getSecretKey()) as String
            user.setCode(code)
            userCode.text = user.getCode()
        }
        return view
    }
}

MainActivity

作用:添加主页面上基本的按钮监听

package com.shixiaotian.totp.scan.application

import android.os.Bundle
import android.view.View
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.FragmentManager
import com.shixiaotian.totp.scan.application.fragments.CodeAddFragment
import com.shixiaotian.totp.scan.application.fragments.CodeListFragment
import com.shixiaotian.totp.scan.application.db.DatabaseHelper
import com.shixiaotian.totp.scan.application.tools.FirstRunTools
import net.sqlcipher.database.SQLiteDatabase

class MainActivity : AppCompatActivity() {

    private lateinit var listButton: View
    private lateinit var addButton: View
    private lateinit var fragmentManager: FragmentManager

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_main)
        // 初始化预处理
        init()

        val codeAddFragment = CodeAddFragment()
        val codeListFragment = CodeListFragment()

        fragmentManager = supportFragmentManager
        fragmentManager.beginTransaction().replace(R.id.viewPager, codeListFragment).commit()

        // 菜单按钮监听
        listButton = findViewById(R.id.menuButton)
        listButton.setOnClickListener {
            fragmentManager.beginTransaction().replace(R.id.viewPager, codeListFragment).commit()

        }

        // 添加按钮监听
        addButton = findViewById(R.id.addButton)
        addButton.setOnClickListener {
            fragmentManager.beginTransaction().replace(R.id.viewPager, codeAddFragment).commit()

        }

        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
    }

    private fun init() {
        SQLiteDatabase.loadLibs(this);
        println("---开始初始化")
        // 判断是否首次运行
        if(FirstRunTools.isFirstRun(this)) {
            println("---首次运行触发")
            val dbHelper = DatabaseHelper(this)
            // 初始化数据库
            dbHelper.initDB()
            dbHelper.init()
        }
    }



}

activity_main.xml

作用:主页面布局

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#3F5CB5"
    tools:context=".MainActivity">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <LinearLayout
            android:id="@+id/mainFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <FrameLayout
                android:id="@+id/viewPager"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                >
            </FrameLayout>
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="200px"
            android:layout_alignParentBottom="true"
            android:background="#ffffff"
            android:orientation="horizontal">

            <ImageView
                android:id="@+id/menuButton"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:background="#20212E"
                android:gravity="center_horizontal"
                android:src="@android:drawable/ic_menu_search"
                android:layout_marginRight="5px"
                android:textSize="30sp" />

            <ImageView
                android:id="@+id/addButton"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:background="#20212E"
                android:gravity="center_horizontal"
                android:src="@android:drawable/ic_menu_add"
                android:layout_marginLeft="5px"

                android:textSize="30sp" />
        </LinearLayout>
    </RelativeLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

code_item.xml

作用:令牌列表每一个令牌的布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/User"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical" >
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >
        <TextView
            android:id="@+id/user_id"
            android:layout_width="0px"
            android:layout_height="0px"
            android:layout_gravity="left"
            android:visibility="invisible"
            />
        <TextView
            android:id="@+id/user_secretKey"
            android:layout_width="0px"
            android:layout_height="0px"
            android:layout_gravity="center_vertical"
            android:visibility="invisible"
            />
        <TextView
            android:id="@+id/user_issuer"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceListItemSmall"
            android:gravity="center_vertical"
            android:paddingStart="?android:attr/listPreferredItemPaddingStart"
            android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
            android:minHeight="?android:attr/listPreferredItemHeightSmall"
            android:textSize="35sp"
            android:text="apple"
            />
        <TextView
            android:id="@+id/user_username"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceListItemSmall"
            android:gravity="center_vertical"
            android:paddingStart="?android:attr/listPreferredItemPaddingStart"
            android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
            android:minHeight="?android:attr/listPreferredItemHeightSmall"
            android:textSize="25sp"
            android:text="1234567@qq.com"
            />
        <TextView
            android:id="@+id/user_code"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center_vertical"
            android:layout_gravity="center"
            android:textColor="#000000"
            android:text="665277"
            android:textSize="55sp"
            android:textStyle="bold"
            />

    </LinearLayout>
</LinearLayout>

fragment_code_add.xml

作用:添加令牌页面,手动添加或者,触发zxing扫码添加

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffff"
    tools:context=".fragments.CodeAddFragment">

    <!-- TODO: Update blank fragment layout -->


    <LinearLayout
        android:layout_margin="100px"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/addNameView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="40sp"
            android:textStyle="bold"
            android:text="Username" />

        <EditText
            android:id="@+id/addUsernameText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:maxLength="10"
            android:inputType="text"
            android:textSize="30sp"

            />

        <TextView
            android:id="@+id/addSecretKeyView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="40sp"
            android:textStyle="bold"
            android:text="SecretKey" />

        <EditText
            android:id="@+id/addSecretKeyText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:maxLength="100"
            android:textSize="30sp"
            android:inputType="text" />
        <TextView
            android:id="@+id/addIssuerView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="40sp"
            android:textStyle="bold"
            android:text="Issuer" />

        <EditText
            android:id="@+id/addIssuerText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:maxLength="10"
            android:inputType="text"
            android:textSize="30sp"

            />

        <TextView
            android:id="@+id/saveButton"
            android:layout_width="match_parent"
            android:layout_height="100sp"
            android:background="#A62641"
            android:gravity="center"
            android:layout_marginTop="100px"
            android:textColor="#ffffff"
            android:text="Save"
            android:textSize="50sp" />

        <TextView
            android:id="@+id/cameraButton"
            android:layout_width="match_parent"
            android:layout_height="100sp"
            android:layout_marginTop="100px"
            android:background="#20212E"
            android:gravity="center"
            android:text="Scan"
            android:textColor="#ffffff"
            android:textSize="50sp" />
    </LinearLayout>
</FrameLayout>

fragment_code_list.xml

作用:提供令牌快速查看列表,和选择单个令牌进行操作的功能

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/code_list"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".fragments.CodeListFragment">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <TextView
            android:id="@+id/textView3"
            android:layout_width="match_parent"
            android:layout_height="101dp"
            android:gravity="center"
            android:background="#312F2F"
            android:text="30s"
            android:textColor="#ffffff"
            android:textColorLink="#FFFFFF"
            android:textSize="60sp" />

        <ListView
            android:id="@+id/listView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:background="#ffffff"
            android:divider="#000000"
            android:dividerHeight="1dp" />


        <View
            android:layout_width="match_parent"
            android:layout_height="200px"
            android:background="#666666"
            >
        </View>

    </LinearLayout>
</FrameLayout>

fragment_code_show.xml

作用:动态令牌展示页面,提供令牌查看和删除功能

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/code_show"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#312F2F"
    tools:context=".fragments.CodeShowFragment">

    <!-- TODO: Update blank fragment layout -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">


            <TextView
                android:id="@+id/showIssuerTextView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center_horizontal"
                android:layout_marginTop="100px"
                android:text="steam"
                android:textColor="#ffffff"
                android:textColorLink="#FFFFFF"
                android:textSize="75sp" />
        <TextView
            android:id="@+id/showUsernameTextView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal"
            android:layout_marginTop="30px"
            android:text="54526322@qq.com"
            android:textColor="#ffffff"
            android:textColorLink="#FFFFFF"
            android:textSize="25sp" />

            <TextView
                android:id="@+id/showTimeView"
                android:layout_width="match_parent"
                android:layout_height="101dp"
                android:gravity="center"
                android:background="#312F2F"
                android:text="30s"
                android:textColor="#ffffff"
                android:textColorLink="#FFFFFF"
                android:layout_marginTop="20px"
                android:textSize="70sp" />
            <ProgressBar
                android:id="@+id/progressBar"
                style="?android:attr/progressBarStyleHorizontal"
                android:layout_width="match_parent"
                android:layout_height="45dp"
                android:max="100"
                android:progress="10"
                android:progressDrawable="@drawable/progress_bar_color" />
            <TextView
                android:id="@+id/showCodeView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:freezesText="false"
                android:gravity="center_horizontal"
                android:layout_marginTop="50px"
                android:text="9TXTSY"
                android:textColor="#ffffff"
                android:textColorLink="#FFFFFF"
                android:textSize="80sp" />

        <TextView
            android:id="@+id/deleteButton"
            android:layout_alignParentBottom="true"
            android:layout_width="match_parent"
            android:layout_height="100sp"
            android:background="#20212E"
            android:gravity="center"
            android:layout_margin="50px"
            android:textColor="#ffffff"
            android:text="Delete"
            android:textSize="50sp" />
    </LinearLayout>

</FrameLayout>

AndroidManifest.xml

作用:添加相机权限,设定zxing相机扫码activity相机方向等相关数据

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.CAMERA" />
    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@android:drawable/ic_lock_lock"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyApplication"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name="com.journeyapps.barcodescanner.CaptureActivity"
                android:screenOrientation="fullSensor"
            tools:replace="screenOrientation" />
    </application>

</manifest>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

耳切法简述

耳切法简述 将简单多边形分解成三角形称为多边形的三角剖分。对n个顶点的简单多边形的任何三角剖分都有n-2个三角形。其中最简单的算法&#xff0c;称为耳切法&#xff08;EarClipping&#xff09;。 耳的定义 多边形的一个 “耳” 是由 V i 0 V_{i_{0}} Vi0​​、 V i 1 V_…

国内外大模型以及部署

国内15家AI大模型应用盘点 AI大模型 秘塔AI搜索 秘塔AI搜索免登录&#xff0c;免费的问答大模型。 开源大模型 Ollama Ollama是一个专注于提供 大语言模型&#xff08;LLM&#xff09; 本地化部署和运行的工具和资源的平台。它旨在帮助用户轻松地在自己的设备上运行和定制…

2024年终总结:非常充实的一年

一、业务方面 2024年是业务全面拓展与技术深耕的一年。从日常的开发维护到新产品研发&#xff0c;从降本增效到业务创新&#xff0c;每一步都在不断累积成长。以下是我的年度业务总结&#xff1a; 日常工作&#xff1a;聚焦于软件开发、维护、运营和售后工作&#xff0c;同时…

UE5材质节点VertexNormalWs/PixelNormalWS

VertexNormalWs顶点法线方向&#xff0c;此节点可以做物体上积雪、青苔等效果 PixelNormalWS像素法线方向

MAC环境安装(卸载)软件

MAC环境安装&#xff08;卸载&#xff09;软件 jdknode安装node&#xff0c;并实现不同版本的切换背景 卸载node从node官网下载pkg安装的node卸载用 homebrew 安装的node如果你感觉删的不够干净&#xff0c;可以再细分删除验证删除结果 jdk 1.下载jdk 先去官网下载自己需要的版…

玩具租赁系统设计与实现(文末附源码)

博主介绍&#xff1a;✌全网粉丝50W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HLM…

C403 unity打开方法

1 unity hub右键以管理员方式打开。 2 注册登录账户 如果出现 如果还是不行&#xff0c;把地址栏的网址复制&#xff0c;在google浏览器中打开 如果出现安全策略&#xff0c;就不勾选安全防护 尝试方案1 把unityhub在任务管理器中关闭 如果验证码发送成功&#xff0c;还是进不…

log4j2的Strategy、log4j2的DefaultRolloverStrategy、删除过期文件

文章目录 一、DefaultRolloverStrategy1.1、DefaultRolloverStrategy节点1.1.1、filePattern属性1.1.2、DefaultRolloverStrategy删除原理 1.2、Delete节点1.2.1、maxDepth属性 二、知识扩展2.1、DefaultRolloverStrategy与Delete会冲突吗&#xff1f;2.1.1、场景一&#xff1a…

【记录】vue 添加全局 dialog 弹框

页面展示 代码 /components/GlobalDialog/index.vue <template><div class"global_dialog" v-if"isVisible"><div class"global_dialog_header"><div class"global_dialog_header_title">{{ title }}</d…

QT------模型/视图

一、模型/视图结构概述 基本原理&#xff1a; Qt 的模型/视图&#xff08;Model/View&#xff09;架构将数据的存储和显示分离&#xff0c;提高了代码的可维护性和复用性。模型&#xff08;Model&#xff09;&#xff1a;负责存储和管理数据&#xff0c;提供数据的访问接口&am…

【YOLO 项目实战】(12)红外/可见光多模态目标检测

欢迎关注『youcans动手学模型』系列 本专栏内容和资源同步到 GitHub/youcans 【YOLO 项目实战】&#xff08;10&#xff09;YOLO8 环境配置与推理检测 【YOLO 项目实战】&#xff08;11&#xff09;YOLO8 数据集与模型训练 【YOLO 项目实战】&#xff08;12&#xff09;红外/可…

HTML——38.Span标签和字符实体

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>span标签和字符实体</title><style type"text/css">h1{text-align: center;}p{text-indent: 2em;}span{color: red;}</style></head><…

太速科技-688-基于 VM1302的双路100G光纤PCIe4.0X16加速计算卡

基于 VM1302的双路100G光纤PCIe4.0X16加速计算卡 一、产品概述 基于Xilinx芯片方案基础上研发的一款双口100 G FPGA光纤以太网PCI-Express v4.0 x16智能加速计算卡&#xff0c;该智能卡拥有高吞吐量、低延时的网络处理能力以及辅助CPU进行网络功能卸载的能力&#xff0c…

KMP 2024 年总结,Kotlin 崛起的一年

2024 Google I/O 上正式官宣了 KMP&#xff08;Kotlin Multiplatform&#xff09;项目&#xff0c;它是 Google Workspace 团队的一项长期「投资」项目&#xff0c;由 JetBrains 开发维护和开源的项目&#xff0c;简单来说&#xff0c;JetBrains 主导&#xff0c;Google Worksp…

企业数字化转型的构念及实现路径

引言 随着信息技术的飞速发展&#xff0c;数字化转型已成为企业持续竞争力的关键。企业数字化转型不仅仅是技术的更新换代&#xff0c;更是一场涉及组织结构、业务流程、企业文化等多方面的深刻变革。本文将探讨企业数字化转型的构念&#xff0c;并提出实现路径。 企业数字化转…

精密制造动力箱行业需要哪种多功能电表

一、在精密制造动力箱行业&#xff0c;多功能电表的选择需要考虑以下几个方面&#xff1a; 电力参数测量 多功能电表应能够测量单相或三相的电流、电压、有功功率、无功功率、视在功率、频率、功率因数等电力参数。这些参数对于监控和管理动力箱的运行状态至关重要。 电能计量…

《鸿蒙HarmonyOS应用开发从入门到精通(第2版)》学习笔记——HarmonyOS架构介绍

1.3 架构介绍 HarmonyOS整体遵从分层架构设计&#xff0c;从下向上依次为&#xff1a;内核层、系统服务层、框架层和应用层。系统功能按照“系统 > 子系统 > 組件”逐级展开&#xff0c;在多设备部署场景下&#xff0c;支持根据实际需求裁剪某些非必要的组件。HarmonyOS…

电脑有杂音滋滋滋响怎么处理?电脑有杂音解决指南

在日常使用电脑的过程中&#xff0c;您是否曾遭遇过这样令人困扰的问题&#xff1a;无论是播放音乐、观看视频还是进行语音通话时&#xff0c;电脑总会时不时地发出“滋滋滋”的电脑有杂音&#xff0c;严重影响了听觉体验和工作效率。这种现象并非无解&#xff0c;本文将为您提…

html+css+js网页设计 美食 家美食1个页面

htmlcssjs网页设计 美食 家美食1个页面 网页作品代码简单&#xff0c;可使用任意HTML辑软件&#xff08;如&#xff1a;Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作&#xff09;。 获取源码 1&#xf…

穷举vs暴搜vs深搜vs回溯vs剪枝系列一>

题目&#xff1a; 解析&#xff1a; 决策树&#xff1a; 代码设计&#xff1a; 代码&#xff1a; 写法一&#xff1a;path为全局变量 private int ret,path,aim;public int findTargetSumWays(int[] nums, int target) {aim target;dfs(nums,0);return ret;}private void…