Android Jetpack之用Room+ViewModel+LiveData实现增删改查数据(createFromAsset())

news2024/12/26 2:55:07

文章目录

  • 一、Room简介
  • 二、用Room+ViewModel+LiveData增删改查数据
  • 三、下载源码

一、Room简介

RoomGoogle推出的数据库框架,是一个 ORM (Object Relational Mapping)对象关系映射数据库、其底层还是对SQLite的封装。

Room包含三个主要组件:

  • 数据库类(DataBase),用于保存数据库并作为应用持久性数据底层连接的主要访问点。
  • 数据实体(Entity),用于表示应用的数据库中的表。
  • 数据访问对象 (DAO),提供您的应用可用于查询、更新、插入和删除数据库中的数据的方法。
  • @Entity 表结构实体
  • @PrimaryKey 主键
  • @ColumnInfo 列/字段信息

二、用Room+ViewModel+LiveData增删改查数据

EntityDaoDatabase 操作数据库
数据库的每个表,都对应一个Entity,一个DaoDao负责增删改查操作)

项目RoomDemo,如下图:

build.gradle添加如下依赖:

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-android-extensions'
    id 'kotlin-kapt'
}

dependencies {
    def room_version = "2.3.0"
    implementation "androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version"
    kapt "androidx.room:room-compiler:$room_version"
}

首先,创建一个数据表Student。先创建包名,在com.bignerdranch.roomdemo下创建一个db包,db下创建bean包,bean包下创建Student类。

package com.bignerdranch.roomdemo.db.bean

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
import org.jetbrains.annotations.NotNull

@Entity(tableName = "student")
class Student {
    @NotNull
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id", typeAffinity = ColumnInfo.INTEGER)
    var id = 0

    @NotNull
    @ColumnInfo(name = "name", typeAffinity = ColumnInfo.TEXT)
    var name: String

    @NotNull
    @ColumnInfo(name = "age", typeAffinity = ColumnInfo.TEXT)
    var age: String

    /**
     * Room会使用这个构造器来存储数据,也就是当你从表中得到Student对象时候,Room会使用这个构造器
     */
    constructor(id: Int, name: String, age: String) {
        this.id = id
        this.name = name
        this.age = age
    }

    /**
     * 由于Room只能识别和使用一个构造器,如果希望定义多个构造器,你可以使用Ignore标签,让Room忽略这个构造器
     * 同样,@Ignore标签还可用于字段,使用@Ignore标签标记过的字段,Room不会持久化该字段的数据
     */
    @Ignore
    constructor(name: String, age: String) {
        this.name = name
        this.age = age
    }
}

这个@Entity就是表示数据库中的表,Student类对应就是Student表,@PrimaryKey表示主键,这里是idautoGenerate = true 是自增,@NonNull表示不为空。 @ColumnInfo表示表中的列名,name = "name"表示列名的值。

下面在db包下新建一个dao包,创建StudentDao,里面的代码如下:

package com.bignerdranch.roomdemo.db.dao

import androidx.lifecycle.LiveData
import androidx.room.*
import com.bignerdranch.roomdemo.db.bean.Student

@Dao
interface StudentDao {
    @Insert
    fun insertStudent(student: Student?)

    @Delete
    fun deleteStudent(student: Student?)

    @Update
    fun updateStudent(student: Student?)

    @Query("SELECT * FROM student")
    fun getStudentList(): LiveData<List<Student?>?>? //希望监听学生表的变化,为其加上LiveData

    @Query("SELECT * FROM student WHERE id = :id")
    fun getStudentById(id: Int): Student?
}

StudentDao是一个接口,主要是定义了一些方法,通过注解在编译的时候会生成实现类。
然后,新建main/assets/databases,并在其中放置student.db文件,其位置如下:

SQLite软件导入students.db文件,如下图:

然后,在MyDatabase.kt中,用createFromAsset()方法从assets/database/students.db创建Room数据库,下面是数据库的创建,在db包下新建一个MyDatabase类,继承RoomDatabase,代码如下:

package com.bignerdranch.roomdemo.db

import android.content.Context
import android.util.Log
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.bignerdranch.roomdemo.db.bean.Student
import com.bignerdranch.roomdemo.db.dao.StudentDao

@Database(entities = [Student::class], exportSchema = false, version = 1)
abstract class MyDatabase() : RoomDatabase() {
    abstract fun studentDao(): StudentDao?

    companion object {
        private val DATABASE_NAME = "my_db"
        private var databaseInstance: MyDatabase? = null

        @Synchronized //已同步
        fun getInstance(context: Context): MyDatabase? {
            if (databaseInstance == null) {
                databaseInstance = Room
                    .databaseBuilder(
                        context.applicationContext,
                        MyDatabase::class.java,
                        DATABASE_NAME
                    )
                    .createFromAsset("databases/student.db")
                    .fallbackToDestructiveMigration()
//                    .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_1_3, MIGRATION_3_4)
                    .build()
            }
            return databaseInstance
        }

        val MIGRATION_1_2: Migration = object : Migration(1, 2) {
            override fun migrate(database: SupportSQLiteDatabase) {
                //do something
                Log.d("MyDatabase", "MIGRATION_1_2")
            }
        }
        private val MIGRATION_2_3: Migration = object : Migration(2, 3) {
            override fun migrate(database: SupportSQLiteDatabase) {
                //do something
                Log.d("MyDatabase", "MIGRATION_2_3")
            }
        }
        private val MIGRATION_1_3: Migration = object : Migration(1, 3) {
            override fun migrate(database: SupportSQLiteDatabase) {
                //do something
                Log.d("MyDatabase", "MIGRATION_1_3")
            }
        }
        val MIGRATION_3_4: Migration = object : Migration(3, 4) {
            override fun migrate(database: SupportSQLiteDatabase) {
                Log.d("MyDatabase", "MIGRATION_3_4")
                database.execSQL(
                    "CREATE TABLE temp_Student (" +
                            "id INTEGER PRIMARY KEY NOT NULL," +
                            "name TEXT," +
                            "age TEXT)"
                )
                database.execSQL(
                    "INSERT INTO temp_Student (id, name, age) " +
                            "SELECT id, name, age FROM Student"
                )
                database.execSQL("DROP TABLE Student")
                database.execSQL("ALTER TABLE temp_Student RENAME TO Student")
            }
        }
    }
}

这里的@Database注解表示这个类是用来操作数据库的,entities = [Student::class]表示当前数据库中的表,只有一个Student表,多的表用应用逗号隔开。version = 1表示数据库的版本,可以做数据库的升级操作。createFromAsset()Room库中提供的。

注意这是一个抽象类,在编译时Room会帮助构建实现类。

现在运行一下,手机或者模拟器都可以。然后什么都不用去做。

可以查看到,MyDatabaseStudentDao的实现类都自动生成了。

ViewModel内:通过 Room.Database查到LiveData数据,在外部监听LiveData
Room变化时,通过ViewModel内的LiveData通知页面数据的变化,架构如下:

新建StudentViewModel类,该类继承自AndroidViewModel,其中有DatabaseLiveData,代码如下:

package com.bignerdranch.roomdemo.jetpackroomwithlivedataandviewmodeltest

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import com.bignerdranch.roomdemo.db.MyDatabase
import com.bignerdranch.roomdemo.db.bean.Student

class StudentViewModel(application: Application) : AndroidViewModel(application) {

    private val myDatabase: MyDatabase?
    val liveDataStudent: LiveData<List<Student?>?>?

    init {
        myDatabase = MyDatabase.getInstance(application)
        liveDataStudent = myDatabase!!.studentDao()!!.getStudentList()
    }

}

表操作无非就是那么几个,增删改查,但是为了更直观的显示结果,需要对UI做一些改动。

在工程的build.gradle中增加repositories闭包中增加jitpack库。

然后在appbuild.gradle中的dependencies{}比包中增加

implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.6'

1. 修改布局
Sync一下,下面修改一下页面的布局文件activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/btnInsertStudent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_margin="12dp"
        android:text="Add a Student"
        android:textAllCaps="false" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

在这里插入图片描述

这里就是一个按钮和一个列表,下面创建列表的item布局
layout下新建一个list_item_student.xml布局,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:paddingTop="12dp"
    android:paddingBottom="12dp">

    <TextView
        android:id="@+id/tvId"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center" />

    <TextView
        android:id="@+id/tvName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center" />

    <TextView
        android:id="@+id/tvAge"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center" />

</LinearLayout>

在这里插入图片描述

新建dialog_layout_student.xml,布局如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <EditText
        android:id="@+id/etName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:autofillHints=""
        android:hint="Name" />

    <EditText
        android:id="@+id/etAge"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:autofillHints=""
        android:hint="Age" />

</LinearLayout>

在这里插入图片描述

com.bignerdranch.roomdemo下新建一个adapter包,包下新建StudentAdapter类,作为列表数据的适配器。代码如下:
2. 列表适配器

package com.bignerdranch.roomdemo.adapter

import com.bignerdranch.roomdemo.R
import com.bignerdranch.roomdemo.db.bean.Student
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.viewholder.BaseViewHolder
import kotlinx.android.synthetic.main.list_item_student.view.*

class StudentAdapter(layoutResId: Int = R.layout.list_item_student) :
    BaseQuickAdapter<Student, BaseViewHolder>(layoutResId) {

    override fun convert(holder: BaseViewHolder, item: Student) {

        holder.itemView.run {
            tvId.text = item.id.toString()
            tvName.text = item.name
            tvAge.text = item.age
        }

    }
}

然后,在MainActivity中初始化List,实例化StudentViewModel并监听其LiveData的变化,代码如下:

package com.bignerdranch.roomdemo

import android.content.DialogInterface
import android.os.AsyncTask
import android.os.Bundle
import android.text.TextUtils
import android.view.View
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.bignerdranch.roomdemo.adapter.StudentAdapter
import com.bignerdranch.roomdemo.db.MyDatabase
import com.bignerdranch.roomdemo.db.bean.Student
import com.bignerdranch.roomdemo.jetpackroomwithlivedataandviewmodeltest.StudentViewModel
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.listener.OnItemLongClickListener
import kotlinx.android.synthetic.main.activity_main.recyclerView

/**
 *
 * @date
 */
class MainActivity : AppCompatActivity(), OnItemLongClickListener {

    private val mStudentAdapter by lazy {
        StudentAdapter().apply {
            setOnItemLongClickListener(this@MainActivity)
        }
    }

    private var myDatabase: MyDatabase? = null
    private var studentList: MutableList<Student>? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById<View>(R.id.btnInsertStudent).setOnClickListener {
            openAddStudentDialog()
        }
        studentList = ArrayList()

        val layoutManager = LinearLayoutManager(this@MainActivity)
        layoutManager.orientation = LinearLayoutManager.VERTICAL
        recyclerView.layoutManager = layoutManager
        recyclerView.adapter = mStudentAdapter
//        mStudentAdapter.setList(studentList)

        myDatabase = MyDatabase.getInstance(this)
        val studentViewModel: StudentViewModel =
            ViewModelProvider(this)[StudentViewModel::class.java]
        studentViewModel.liveDataStudent!!.observe(this) { students ->
            (studentList as ArrayList<Student?>).clear()
            (studentList as ArrayList<Student?>).addAll(students!!)
//            studentAdapter2!!.notifyDataSetChanged()
            mStudentAdapter.setList(studentList)
            mStudentAdapter.notifyDataSetChanged()
        }
    }

    private fun updateOrDeleteDialog(student: Student?) {
        val options = arrayOf("更新", "删除")
        AlertDialog.Builder(this@MainActivity)
            .setTitle("")
            .setItems(options) { _, which ->
                if (which == 0) {
                    openUpdateStudentDialog(student)
                } else if (which == 1) {
                    if (student != null) {
                        DeleteStudentTask(student).execute()
                    }
                }
            }.show()
    }

    private fun openAddStudentDialog() {
        val customView: View = this.layoutInflater.inflate(R.layout.dialog_layout_student, null)
        val etName = customView.findViewById<EditText>(R.id.etName)
        val etAge = customView.findViewById<EditText>(R.id.etAge)
        val builder = AlertDialog.Builder(this@MainActivity)
        val dialog = builder.create()
        dialog.setTitle("Add Student")
        dialog.setButton(DialogInterface.BUTTON_POSITIVE, "OK") { dialog, which ->
            if (TextUtils.isEmpty(etName.text.toString()) || TextUtils.isEmpty(etAge.text.toString())) {
                Toast.makeText(this@MainActivity, "输入不能为空", Toast.LENGTH_SHORT).show()
            } else {
                InsertStudentTask(etName.text.toString(), etAge.text.toString()).execute()
            }
        }
        dialog.setButton(
            DialogInterface.BUTTON_NEGATIVE,
            "CANCEL"
        ) { dialog, which -> dialog.dismiss() }
        dialog.setView(customView)
        dialog.show()
    }

    private fun openUpdateStudentDialog(student: Student?) {
        if (student == null) {
            return
        }
        val customView: View = this.layoutInflater.inflate(R.layout.dialog_layout_student, null)
        val etName = customView.findViewById<EditText>(R.id.etName)
        val etAge = customView.findViewById<EditText>(R.id.etAge)
        etName.setText(student.name)
        etAge.setText(student.age)
        val builder = AlertDialog.Builder(this@MainActivity)
        val dialog = builder.create()
        dialog.setTitle("Update Student")
        dialog.setButton(DialogInterface.BUTTON_POSITIVE, "OK") { dialog, which ->
            if (TextUtils.isEmpty(etName.text.toString()) || TextUtils.isEmpty(etAge.text.toString())) {
                Toast.makeText(this@MainActivity, "输入不能为空", Toast.LENGTH_SHORT).show()
            } else {
                UpdateStudentTask(
                    student.id,
                    etName.text.toString(),
                    etAge.text.toString()
                ).execute()
            }
        }
        dialog.setButton(
            DialogInterface.BUTTON_NEGATIVE,
            "CANCEL"
        ) { dialog, which -> dialog.dismiss() }
        dialog.setView(customView)
        dialog.show()
    }

    private inner class InsertStudentTask(var name: String, var age: String) :
        AsyncTask<Void?, Void?, Void?>() {
        override fun doInBackground(vararg params: Void?): Void? {
            myDatabase!!.studentDao()!!.insertStudent(Student(name, age))
            return null
        }
    }

    private inner class UpdateStudentTask(var id: Int, var name: String, var age: String) :
        AsyncTask<Void?, Void?, Void?>() {
        override fun doInBackground(vararg params: Void?): Void? {
            myDatabase!!.studentDao()!!.updateStudent(Student(id, name, age))
            return null
        }
    }

    private inner class DeleteStudentTask(var student: Student) : AsyncTask<Void?, Void?, Void?>() {
        override fun doInBackground(vararg params: Void?): Void? {
            myDatabase!!.studentDao()!!.deleteStudent(student)
            return null
        }
    }

    override fun onItemLongClick(
        adapter: BaseQuickAdapter<*, *>,
        view: View,
        position: Int
    ): Boolean {
        updateOrDeleteDialog((studentList as ArrayList<Student?>)[position])
        return true
    }
}

运行后,当LiveData数据变化时,更新UI即可,而不需要每次增删改后都必须用QueryStudentTask() 来手动查询一次数据库,简化了系统,效果如下:

三、下载源码

下载源码github地址:https://github.com/caobin10/RoomDemo

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

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

相关文章

软考学习五部曲

视频学知识 学习知识环节看视频看书都可以&#xff0c;书很厚一本。如果要看完的话要很多时间&#xff0c;所以我觉得还是看视频更快一点&#xff0c;而且视频还可以倍速。我看的那个视频我觉得非常不错&#xff0c;但是我看的视频b站已经下架了看不到了。其他的视频没仔细去看…

掀起全新的互联网直播风潮

随着科技的不断进步和智能手机的普及&#xff0c;无人直播作为一种全新的互联网直播方式&#xff0c;在近些年迅速崛起&#xff0c;并引起了广泛关注。本文将围绕手机无人直播展开探讨&#xff0c;探究其背后的原因以及对社会生活带来的影响。 首先&#xff0c;我们需要明确什…

商业银行数据资产估值白皮书,推荐系统源码下载

2022年12月2日&#xff0c;中共中央、国务院印发《关于构建数据基础制度更好发挥数据要素作用的意见》&#xff08;以下简称“《数据二十条》”&#xff09;&#xff0c;指出要充分发挥海量数据规模和丰富应用场景优势&#xff0c;同时明确提出探索数据资产入表新模式。2023年8…

sql学习笔记(四)

1. 避免字段名与关键字冲突 1️⃣ 2️⃣ as 设置别名 2. 同比与环比 「同比」&#xff1a;与历史「同时期&#xff3d;比较&#xff0c;例如2011年3月份与2010年3月份相比&#xff0c;叫「同比」。 「环比」&#xff1a;与「上一个」统计周期比较&#xff0c;例如2011年4…

大数据HCIE成神之路之数据预处理(3)——数值离散化

数值离散化 1.1 无监督连续变量的离散化 – 聚类划分1.1.1 实验任务1.1.1.1 实验背景1.1.1.2 实验目标1.1.1.3 实验数据解析 1.1.2 实验思路1.1.3 实验操作步骤1.1.4 结果验证 1.2 无监督连续变量的离散化 – 等宽划分1.2.1 实验任务1.2.1.1 实验背景1.2.1.2 实验目标1.2.1.3 实…

macOS 安装 oh-my-zsh 后 node 报错 command not found : node

最近为了让终端中显示 git 分支的名称&#xff0c;安装了 oh-my-zsh &#xff0c;安装之后呢&#xff0c;我原先安装的 Volta、 node 都没法用了&#xff0c;报错如下&#xff1a; 这时候粗略判断应该是系统变量出了问题&#xff0c;oh-my-zsh 的变量文件是 ~/.zshrc&#xff0…

Postman测试文件上传接口

文章目录 一、Postman测试文件上传接口1、创建一个请求→选择请求方式→填写请求的URL→填写请求的参数值2、选择 "Body" → "form-data" → "Test" → "File"3、点击 "Select Files"4、选择要上传的文件5、点击 Send 进行测…

解决 Hive 外部表分隔符问题的实用指南

简介&#xff1a; 在使用 Hive 外部表时&#xff0c;分隔符设置不当可能导致数据导入和查询过程中的问题。本文将详细介绍如何解决在 Hive 外部表中正确设置分隔符的步骤。 问题描述&#xff1a; 在使用Hive外部表时&#xff0c;可能会遇到分隔符问题。这主要是因为Hive在读…

神经网络学习小记录76——Tensorflow2设置随机种子Seed来保证训练结果唯一

神经网络学习小记录76——Tensorflow2设置随机种子Seed来保证训练结果唯一 学习前言为什么每次训练结果不同什么是随机种子训练中设置随机种子 学习前言 好多同学每次训练结果不同&#xff0c;最大的指标可能会差到3-4%这样&#xff0c;这是因为随机种子没有设定导致的&#x…

项目进度管理:掌握进度管理技巧,保障项目不延期

项目进度问题通常在小型团队中并不显著&#xff0c;因为这类团队通常人数较少&#xff0c;成员间的沟通成本相对较低。这使得他们在执行项目时更加团结和协作。 然而&#xff0c;当团队规模扩大到上百人时&#xff0c;这使得沟通变得更为复杂&#xff0c;在这个庞大的团队中&…

数据可视化---离群值展示

内容导航 类别内容导航机器学习机器学习算法应用场景与评价指标机器学习算法—分类机器学习算法—回归机器学习算法—聚类机器学习算法—异常检测机器学习算法—时间序列数据可视化数据可视化—折线图数据可视化—箱线图数据可视化—柱状图数据可视化—饼图、环形图、雷达图统…

docker 部署kafka

随笔记录 目录 1. 安装zookeeper 2. 安装Kafka 2.1 拉取kafka image 2.2 查询本地docker images 2.3 查看本地 容器&#xff08;docker container&#xff09; 2.3.1 查看本地已启动的 docker container 2.3.2 查看所有容器的列表&#xff0c;包括已停止的容器。 2.4 …

ssm基于SSM的个人事务管理系统的设计和实现论文

摘 要 使用旧方法对个人事务管理信息进行系统化管理已经不再让人们信赖了&#xff0c;把现在的网络信息技术运用在个人事务管理信息的管理上面可以解决许多信息管理上面的难题&#xff0c;比如处理数据时间很长&#xff0c;数据存在错误不能及时纠正等问题。 这次开发的个人事…

表格实现合并单元格

实现的效果 一、列合并 此需求的列合并比较简单, 直接使用el-table-column包括即可 <el-table-column align"center" sortable label"目标"><el-table-column prop"target1" sortable label"预设目标" /><el-table-c…

红队打靶练习:SOLIDSTATE: 1

信息收集 1、arp ┌──(root㉿ru)-[~/kali] └─# arp-scan -l Interface: eth0, type: EN10MB, MAC: 00:0c:29:69:c7:bf, IPv4: 192.168.12.128 Starting arp-scan 1.10.0 with 256 hosts (https://github.com/royhills/arp-scan) 192.168.12.1 00:50:56:c0:00:08 …

pip的基本命令与使用

一、pip简介 pip是Python的包管理器&#xff0c;类似于其他编程语言中的包管理器&#xff0c;如Ruby的gem或Node.js的npm。它可以帮助你轻松地安装、更新和卸载Python包&#xff08;库或工具&#xff09;。你可以把pip想象成一个应用商店&#xff0c;你可以从中获取你需要的Py…

网络安全:专科及普通本科的温柔乡

当代普通大学生的现状是卷又卷不过、躺又躺不平&#xff0c;把大把的青春都荒废在了思考我应该做什么才能有前途的问题上面。当然&#xff0c;这里说的是那些普通学历且对自己的职业生涯甚至是人生没有规划的大学生&#xff0c;包括专科、普通一本二本&#xff0c;并非985、211…

如何在 JavaScript 中过滤嵌套对象?

概述 在 JavaScript 中&#xff0c;嵌套对象是一个简单的对象&#xff0c;它被括在大括号中&#xff0c;要使嵌套对象成为一个对象&#xff0c;就必须继承它自己的对象。因此&#xff0c;为了在 JavaScript 中过滤对象&#xff0c;JavaScript 提供了名为 "filter() "…

基于CTF探讨Web漏洞的利用与防范

写在前面 Copyright © [2023] [Myon⁶]. All rights reserved. 基于自己之前在CTF中Web方向的学习&#xff0c;总结出与Web相关的漏洞利用方法&#xff0c;主要包括&#xff1a;密码爆破、文件上传、SQL注入、PHP伪协议、反序列化漏洞、命令执行漏洞、文件包含漏洞、Vim…