Android GreenDao 使用全面讲解,Android组件化入门,Android GreenDao使用详解与组件化入门指南

马肤

温馨提示:这篇文章已超过455天没有更新,请注意相关的内容是否还可用!

摘要:本篇文章将全面讲解Android开发中GreenDao的使用,包括其特点、安装配置、数据模型定义、数据库操作等方面。还将介绍Android组件化的入门知识,包括组件化的概念、优势、实施方法等。通过阅读本文,读者将能够更深入地了解Android开发中数据库管理和组件化的应用,提高开发效率和代码质量。

5. 在多个线程中使用QueryBuilder

如果在多个线程中使用查询,则必须调用 forCurrentThread ()以获取当前线程的Query实例。Query的对象实例绑定到构建查询的拥有线程。

这使您可以安全地在Query对象上设置参数,而其他线程不会干扰。如果其他线程尝试在查询上设置参数或执行绑定到另一个线程的查询,则会抛出异常。像这样,您不需要同步语句。实际上,您应该避免锁定,因为如果并发事务使用相同的Query对象,这可能会导致死锁。

每次调用forCurrentThread ()时, 参数都会在使用其构建器构建查询时设置为初始参数。

2. 使用QueryBuilder进行批量删除操作

使用QueryBuilder进行批量删除操作,不会删除单个实体,但会删除符合某些条件的所有实体。要执行批量删除,请创建QueryBuilder,调用其 buildDelete ()方法,然后执行返回的 DeleteQuery。

例子:删除数据库中id大于5的所有其他数据

public boolean deleteItem(){

DaoSession daoSession = ((AserbaoApplication) getApplication()).getDaoSession();

QueryBuilder where = daoSession.queryBuilder(Student.class).where(StudentDao.Properties.Id.gt(5));

DeleteQuery deleteQuery = where.buildDelete();

deleteQuery.executeDeleteWithoutDetachingEntities();

return false;

}

5. 注解讲解

从GreenDao 3 使用注解来定义模型和实体,前面也讲过,通过注解的使用可以快速构建数据库表,包括设置主键,自增,值是否唯一等等等……

下面我们来看下注解的简单使用:

@Entity

public class Student {

@Id(autoincrement = true)

Long id;

@Unique

int studentNo;//学号

int age; //年龄

String telPhone;//手机号

String sex; //性别

String name;//姓名

String address;//家庭住址

String schoolName;//学校名字

String grade;//几年级

……getter and setter and constructor method……

}

1. @Entity注解

@Entity是GreenDao必不可少的注解,只有在实体类中使用了@Entity注解GreenDao才会创建对应的表。当然我们也可以使用@Entity配置一些细节:

  • schema:如果你有多个架构,你可以告诉GreenDao当前属于哪个架构。
  • active:标记一个实体处于活跃状态,活动实体有更新、删除和刷新方法。
  • nameInDb:在数据中使用的别名,默认使用的是实体的类名。
  • indexes:标记如果DAO应该创建数据库表(默认为true),如果您有多个实体映射到一个表,或者表的创建是在greenDAO之外进行的,那么将其设置为false。
  • createInDb:标记创建数据库表。
  • generateGettersSetters:如果缺少,是否应生成属性的getter和setter方法。

    @Entity(

    schema = “myschema”,

    active = true,

    nameInDb = “AWESOME_USERS”,

    indexes = {

    @Index(value = “message DESC”, unique = true)

    },

    createInDb = false,

    generateConstructors = true,

    generateGettersSetters = true

    )

    public class Student{

    ……

    }

    2. 基础属性注解(@Id,@Property,@NotNull,@Transient)

    @Id @Id注解选择 long / Long属性作为实体ID。在数据库方面,它是主键。参数autoincrement = true 表示自增,id不给赋值或者为赋值为null即可(这里需要注意,如果要实现自增,id必须是Long,为long不行!)。

    @Entity

    public class Student {

    @Id(autoincrement = true)

    Long id;

    ……

    }

    @Property 允许您定义属性映射到的非默认列名。如果不存在,GreenDAO将以SQL-ish方式使用字段名称(大写,下划线而不是camel情况,例如 name将成为 NAME)。注意:您当前只能使用内联常量来指定列名。

    @Entity

    public class Student {

    @Id(autoincrement = true)

    Long id;

    @Property (nameInDb=“name”) //设置了,数据库中的表格属性名为"name",如果不设置,数据库中表格属性名为"NAME"

    String name;

    ……

    }

    @NotNull :设置数据库表当前列不能为空 。

    @Transient :添加次标记之后不会生成数据库表的列。标记要从持久性中排除的属性。将它们用于临时状态等。或者,您也可以使用Java中的transient关键字。

    3. 索引注解

    • @Index:使用@Index作为一个属性来创建一个索引,通过name设置索引别名,也可以通过unique给索引添加约束。
    • @Unique:向索引添加UNIQUE约束,强制所有值都是唯一的。

      @Entity

      public class Student {

      @Id(autoincrement = true)

      Long id;

      @Property(nameInDb=“name”)

      @Index(unique = true)

      String name;

      ……

      }

      注意: 上面这种情况,约定name为唯一值,向数据库中通过insert方法继续添加已存在的name数据,会抛异常:

      10-08 20:59:46.274 31939-31939/com.example.aserbao.aserbaosandroid E/AndroidRuntime: FATAL EXCEPTION: main

      Process: com.example.aserbao.aserbaosandroid, PID: 31939

      android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: STUDENT.name (Sqlite code 2067), (OS error - 2:No such file or directory)

      ……

      若使用insertOrReplace()方法添加数据,当前数据库中不会有重复的数据,但是重复的这条数据的id会被修改!若项目中有用到id字段进行排序的话,这一点需要特别注意。

      4. 关系注解

      关系型注解GreenDao中主要就两个:

      • @ToOne:定义与另一个实体(一个实体对象)的关系
      • @ToMany:定义与多个实体对象的关系 至于如何使用,我们马上就讲。

        6. 一对一,一对多,多对多关系表的创建

        平常项目中,我们经常会使用到多表关联,如文章开头所说的数据库表结构设置的那样!接下来我们来讲如何通过GreenDao实现多表关联。

        1. 一对一

        一个学生对应一个身份证号: 做法:

        1. 我们在Student中设置一个注解@ToOne(joinProperty = “name”)
        2. 在创建Student的时候,将对应的数据传递给IdCard; 代码部分:

        学生Student代码:

        @Entity

        public class Student {

        @Id(autoincrement = true)

        Long id;

        @Unique

        int studentNo;//学号

        int age; //年龄

        String telPhone;//手机号

        String sex; //性别

        String name;//姓名

        String address;//家庭住址

        String schoolName;//学校名字

        String grade;//几年级

        @ToOne(joinProperty = “name”)

        IdCard student;

        ……getter and setter ……

        }

        身份证IdCard代码:

        @Entity

        public class IdCard {

        @Id

        String userName;//用户名

        @Unique

        String idNo;//身份证号

        ……getter and setter ……

        }

        insert一组数据:

        public void addStudent(){

        DaoSession daoSession = ((AserbaoApplication) getApplication()).getDaoSession();

        Student student = new Student();

        student.setStudentNo(i);

        int age = mRandom.nextInt(10) + 10;

        student.setAge(age);

        student.setTelPhone(RandomValue.getTel());

        String chineseName = RandomValue.getChineseName();

        student.setName(chineseName);

        if (i % 2 == 0) {

        student.setSex(“男”);

        } else {

        student.setSex(“女”);

        }

        student.setAddress(RandomValue.getRoad());

        student.setGrade(String.valueOf(age % 10) + “年纪”);

        student.setSchoolName(RandomValue.getSchoolName());

        daoSession.insert(student);

        //插入对应的IdCard数据

        IdCard idCard = new IdCard();

        idCard.setUserName(userName);

        idCard.setIdNo(RandomValue.getRandomID());

        daoSession.insert(idCard);

        }

        ok,数据可以了!现在数据库表插入完成了。

        2. 一对多

        一个人拥有多个信用卡 做法:

        1. 在我们在Student中设置@ToMany(referencedJoinProperty = “studentId”);
        2. 我们在CreditCard中设置编写对应的id主键;

        Student的代码:

        @Entity

        public class Student {

        @Id(autoincrement = true)

        Long id;

        @Unique

        int studentNo;//学号

        int age; //年龄

        String telPhone;//手机号

        String sex; //性别

        String name;//姓名

        String address;//家庭住址

        String schoolName;//学校名字

        String grade;//几年级

        @ToMany(referencedJoinProperty = "studentId) // 这个studentId是对应在CreditCard中的studentId

        List creditCardsList;

        ……getter and setter ……

        }

        CreditCard的代码:

        @Entity

        public class CreditCard {

        @Id

        Long id;

        Long studentId;

        Long teacherId;

        String userName;//持有者名字

        String cardNum;//卡号

        String whichBank;//哪个银行的

        int cardType;//卡等级,分类 0 ~ 5

        ……getter and setter ……

        }

        添加数据代码:

        public void addStudent(){

        DaoSession daoSession = ((AserbaoApplication) getApplication()).getDaoSession();

        Student student = new Student();

        student.setStudentNo(i);

        int age = mRandom.nextInt(10) + 10;

        student.setAge(age);

        student.setTelPhone(RandomValue.getTel());

        String chineseName = RandomValue.getChineseName();

        student.setName(chineseName);

        if (i % 2 == 0) {

        student.setSex(“男”);

        } else {

        student.setSex(“女”);

        }

        student.setAddress(RandomValue.getRoad());

        student.setGrade(String.valueOf(age % 10) + “年纪”);

        student.setSchoolName(RandomValue.getSchoolName());

        daoSession.insert(student);

        //插入对应的CreditCard数据

        for (int j = 0; j

        CreditCard creditCard = new CreditCard();

        creditCard.setUserId(id);

        creditCard.setUserName(userName);

        creditCard.setCardNum(String.valueOf(random.nextInt(899999999) + 100000000) + String.valueOf(random.nextInt(899999999) + 100000000));

        creditCard.setWhichBank(RandomValue.getBankName());

        creditCard.setCardType(random.nextInt(10));

        daoSession.insert(creditCard);

        }

        }

        3. 多对多

        一个学生有多个老师,老师有多个学生。 做法:

        1. 我们需要创建一个学生老师管理器(StudentAndTeacherBean),用来对应学生和老师的ID;

        2. 我们需要在学生对象中,添加注解:

        @ToMany @JoinEntity(entity = StudentAndTeacherBean.class,sourceProperty = “studentId”,targetProperty = “teacherId”) List teacherList;

        1. 我们需要在老师对象中,添加注解:@ToMany

        @JoinEntity(entity = StudentAndTeacherBean.class,sourceProperty = “teacherId”,targetProperty = “studentId”) List studentList;

        StudentAndTeacherBean代码:

        @Entity

        public class StudentAndTeacherBean {

        @Id(autoincrement = true)

        Long id;

        Long studentId;//学生ID

        Long teacherId;//老师ID

        ……getter and setter ……

        }

        Student 代码:

        @Entity

        public class Student {

        @Id(autoincrement = true)

        Long id;

        @Unique

        int studentNo;//学号

        int age; //年龄

        String telPhone;//手机号

        String sex; //性别

        String name;//姓名

        String address;//家庭住址

        String schoolName;//学校名字

        String grade;//几年级

        @ToMany

        @JoinEntity(entity = StudentAndTeacherBean.class,sourceProperty = “studentId”,targetProperty = “teacherId”)

        List teacherList;

        ……getter and setter ……

        }

        Teacher代码:

        @Entity

        public class Teacher {

        @Id(autoincrement = true)

        Long id;

        @Unique

        int teacherNo;//职工号

        int age; //年龄

        String sex; //性别

        String telPhone;

        String name;//姓名

        String schoolName;//学校名字

        String subject;//科目

        @ToMany

        @JoinEntity(entity = StudentAndTeacherBean.class,sourceProperty = “teacherId”,targetProperty = “studentId”)

        List studentList;

        ……getter and setter ……

        }

        数据添加:

        public void addData(){

        Student student = new Student();

        student.setStudentNo(i);

        int age = mRandom.nextInt(10) + 10;

        student.setAge(age);

        student.setTelPhone(RandomValue.getTel());

        String chineseName = RandomValue.getChineseName();

        student.setName(chineseName);

        if (i % 2 == 0) {

        student.setSex(“男”);

        } else {

        student.setSex(“女”);

        }

        student.setAddress(RandomValue.getRoad());

        student.setGrade(String.valueOf(age % 10) + “年纪”);

        student.setSchoolName(RandomValue.getSchoolName());

        daoSession.insert(student);

        Collections.shuffle(teacherList);

        for (int j = 0; j

        if(j

        Teacher teacher = teacherList.get(j);

        StudentAndTeacherBean teacherBean = new StudentAndTeacherBean(student.getId(), teacher.getId());

        daoSession.insert(teacherBean);

        }

        }

        }

        好了,成功;

        7. 数据库的升级

        GreenDao的OpenHelper下有个 onUpgrade(Database db, int oldVersion, int newVersion)方法,当设置的数据库版本改变时,在数据库初始化的时候就会回调到这个方法,我们可以通过继承OpenHelper重写onUpgrade方法来实现数据库更新操作:

        GreenDao的升级思路:

        1. 创建临时表TMP_,复制原来的数据库到临时表中;
        2. 删除之前的原表;
        3. 创建新表;
        4. 将临时表中的数据复制到新表中,最后将TMP_表删除掉;

        ok,思路就是这样, 总共两个类: 一个MyDaoMaster(OpenHelper继承类),一个MigrationHelper(数据库操作类) 下面是代码编写:

        修改Application中的DaoMaster的创建:

        MyDaoMaster helper = new MyDaoMaster(this, “aserbaos.db”);

        // DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, “aserbao.db”);

        SQLiteDatabase db = helper.getWritableDatabase();

        DaoMaster daoMaster = new DaoMaster(db);

        daoSession = daoMaster.newSession();

        MyDaoMaster代码:

        public class MyDaoMaster extends OpenHelper {

        private static final String TAG = “MyDaoMaster”;

        public MyDaoMaster(Context context, String name) {

        super(context, name);

        }

        public MyDaoMaster(Context context, String name, SQLiteDatabase.CursorFactory factory) {

        super(context, name, factory);

        }

        @Override

        public void onUpgrade(Database db, int oldVersion, int newVersion) {

        super.onUpgrade(db, oldVersion, newVersion);

        MigrationHelper.migrate(db, new MigrationHelper.ReCreateAllTableListener() {

        @Override

        public void onCreateAllTables(Database db, boolean ifNotExists) {

        DaoMaster.createAllTables(db, ifNotExists);

        }

        @Override

        public void onDropAllTables(Database db, boolean ifExists) {

        DaoMaster.dropAllTables(db, ifExists);

        }

        },ThingDao.class);

        Log.e(TAG, "onUpgrade: " + oldVersion + " newVersion = " + newVersion);

        }

        }

        MigrationHelper 代码:

        public final class MigrationHelper {

        public static boolean DEBUG = false;

        private static String TAG = “MigrationHelper”;

        private static final String SQLITE_MASTER = “sqlite_master”;

        private static final String SQLITE_TEMP_MASTER = “sqlite_temp_master”;

        private static WeakReference weakListener;

        public interface ReCreateAllTableListener{

        void onCreateAllTables(Database db, boolean ifNotExists);

        void onDropAllTables(Database db, boolean ifExists);

        }

        public static void migrate(SQLiteDatabase db, Class>… daoClasses) {

        printLog(“【The Old Database Version】” + db.getVersion());

        Database database = new StandardDatabase(db);

        migrate(database, daoClasses);

        }

        public static void migrate(SQLiteDatabase db, ReCreateAllTableListener listener, Class>… daoClasses) {

        weakListener = new WeakReference(listener);

        migrate(db, daoClasses);

        }

        public static void migrate(Database database, ReCreateAllTableListener listener, Class>… daoClasses) {

        weakListener = new WeakReference(listener);

        migrate(database, daoClasses);

        }

        public static void migrate(Database database, Class>… daoClasses) {

        printLog(“【Generate temp table】start”);

        generateTempTables(database, daoClasses);

        printLog(“【Generate temp table】complete”);

        ReCreateAllTableListener listener = null;

        if (weakListener != null) {

        listener = weakListener.get();

        }

        if (listener != null) {

        listener.onDropAllTables(database, true);

        printLog(“【Drop all table by listener】”);

        listener.onCreateAllTables(database, false);

        printLog(“【Create all table by listener】”);

        } else {

        dropAllTables(database, true, daoClasses);

        createAllTables(database, false, daoClasses);

        }

        printLog(“【Restore data】start”);

        restoreData(database, daoClasses);

        printLog(“【Restore data】complete”);

        }

        private static void generateTempTables(Database db, Class>… daoClasses) {

        for (int i = 0; i

        String tempTableName = null;

        DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);

        String tableName = daoConfig.tablename;

        if (!isTableExists(db, false, tableName)) {

        printLog(“【New Table】” + tableName);

        continue;

        }

        try {

        tempTableName = daoConfig.tablename.concat(“_TEMP”);

        StringBuilder dropTableStringBuilder = new StringBuilder();

        dropTableStringBuilder.append(“DROP TABLE IF EXISTS “).append(tempTableName).append(”;”);

        db.execSQL(dropTableStringBuilder.toString());

        StringBuilder insertTableStringBuilder = new StringBuilder();

        insertTableStringBuilder.append(“CREATE TEMPORARY TABLE “).append(tempTableName);

        insertTableStringBuilder.append(” AS SELECT * FROM “).append(tableName).append(”;”);

        db.execSQL(insertTableStringBuilder.toString());

        printLog(“【Table】” + tableName +“\n —Columns–>”+getColumnsStr(daoConfig));

        printLog(“【Generate temp table】” + tempTableName);

        } catch (SQLException e) {

        Log.e(TAG, “【Failed to generate temp table】” + tempTableName, e);

        }

        }

        }

        private static boolean isTableExists(Database db, boolean isTemp, String tableName) {

        if (db == null || TextUtils.isEmpty(tableName)) {

        return false;

        }

        String dbName = isTemp ? SQLITE_TEMP_MASTER : SQLITE_MASTER;

        String sql = “SELECT COUNT(*) FROM " + dbName + " WHERE type = ? AND name = ?”;

        Cursor cursor=null;

        int count = 0;

        try {

        cursor = db.rawQuery(sql, new String[]{“table”, tableName});

        if (cursor == null || !cursor.moveToFirst()) {

        return false;

        }

        count = cursor.getInt(0);

        } catch (Exception e) {

        e.printStackTrace();

        } finally {

        if (cursor != null)

        cursor.close();

        }

        return count > 0;

        }

        private static String getColumnsStr(DaoConfig daoConfig) {

        if (daoConfig == null) {

        return “no columns”;

        }

        StringBuilder builder = new StringBuilder();

        for (int i = 0; i

        builder.append(daoConfig.allColumns[i]);

        builder.append(“,”);

        }

        if (builder.length() > 0) {

        builder.deleteCharAt(builder.length() - 1);

        }

        return builder.toString();

        }

        private static void dropAllTables(Database db, boolean ifExists, @NonNull Class>… daoClasses) {

        reflectMethod(db, “dropTable”, ifExists, daoClasses);

        printLog(“【Drop all table by reflect】”);

        }

        private static void createAllTables(Database db, boolean ifNotExists, @NonNull Class>… daoClasses) {

        reflectMethod(db, “createTable”, ifNotExists, daoClasses);

        printLog(“【Create all table by reflect】”);

        }

        /**

        • dao class already define the sql exec method, so just invoke it

          */

          private static void reflectMethod(Database db, String methodName, boolean isExists, @NonNull Class>… daoClasses) {

          if (daoClasses.length

          return;

          }

          try {

          for (Class cls : daoClasses) {

          Method method = cls.getDeclaredMethod(methodName, Database.class, boolean.class);

          method.invoke(null, db, isExists);

          }

          } catch (NoSuchMethodException e) {

          e.printStackTrace();

          } catch (InvocationTargetException e) {

          e.printStackTrace();

          } catch (IllegalAccessException e) {

          e.printStackTrace();

          }

          }

          自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

          深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

          因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

          Android GreenDao 使用全面讲解,Android组件化入门,Android GreenDao使用详解与组件化入门指南 第1张

          Android GreenDao 使用全面讲解,Android组件化入门,Android GreenDao使用详解与组件化入门指南 第2张

          Android GreenDao 使用全面讲解,Android组件化入门,Android GreenDao使用详解与组件化入门指南 第3张

          Android GreenDao 使用全面讲解,Android组件化入门,Android GreenDao使用详解与组件化入门指南 第4张

          由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

          如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)

          Android GreenDao 使用全面讲解,Android组件化入门,Android GreenDao使用详解与组件化入门指南 第5张

          最后

          我见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了5、6年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。

          其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。

          不断奔跑,你就知道学习的意义所在!

          Android GreenDao 使用全面讲解,Android组件化入门,Android GreenDao使用详解与组件化入门指南 第6张

          《Android高级架构师面试指导+2021大厂面试真题》免费领取

          Android GreenDao 使用全面讲解,Android组件化入门,Android GreenDao使用详解与组件化入门指南 第7张

          0496929727)]

          [外链图片转存中…(img-LQOF2APo-1710496929728)]

          [外链图片转存中…(img-cl3jhkK5-1710496929728)]

          由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

          如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)

          [外链图片转存中…(img-OOvPzGfA-1710496929729)]

          最后

          我见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了5、6年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。

          其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。

          不断奔跑,你就知道学习的意义所在!

          [外链图片转存中…(img-jID6NbIM-1710496929730)]

          《Android高级架构师面试指导+2021大厂面试真题》免费领取

          [外链图片转存中…(img-SPuLaUMS-1710496929730)]


0
收藏0
文章版权声明:除非注明,否则均为VPS857原创文章,转载或复制请以超链接形式并注明出处。

相关阅读

  • 【研发日记】Matlab/Simulink自动生成代码(二)——五种选择结构实现方法,Matlab/Simulink自动生成代码的五种选择结构实现方法(二),Matlab/Simulink自动生成代码的五种选择结构实现方法详解(二)
  • 超级好用的C++实用库之跨平台实用方法,跨平台实用方法的C++实用库超好用指南,C++跨平台实用库使用指南,超好用实用方法集合,C++跨平台实用库超好用指南,方法与技巧集合
  • 【动态规划】斐波那契数列模型(C++),斐波那契数列模型(C++实现与动态规划解析),斐波那契数列模型解析与C++实现(动态规划)
  • 【C++】,string类底层的模拟实现,C++中string类的模拟底层实现探究
  • uniapp 小程序实现微信授权登录(前端和后端),Uniapp小程序实现微信授权登录全流程(前端后端全攻略),Uniapp小程序微信授权登录全流程攻略,前端后端全指南
  • Vue脚手架的安装(保姆级教程),Vue脚手架保姆级安装教程,Vue脚手架保姆级安装指南,Vue脚手架保姆级安装指南,从零开始教你如何安装Vue脚手架
  • 如何在树莓派 Raspberry Pi中本地部署一个web站点并实现无公网IP远程访问,树莓派上本地部署Web站点及无公网IP远程访问指南,树莓派部署Web站点及无公网IP远程访问指南,本地部署与远程访问实践,树莓派部署Web站点及无公网IP远程访问实践指南,树莓派部署Web站点及无公网IP远程访问实践指南,本地部署与远程访问详解,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南。
  • vue2技术栈实现AI问答机器人功能(流式与非流式两种接口方法),Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法探究,Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法详解
  • 发表评论

    快捷回复:表情:
    评论列表 (暂无评论,0人围观)

    还没有评论,来说两句吧...

    目录[+]

    取消
    微信二维码
    微信二维码
    支付宝二维码