hello,大家好呀🥰!相信大家学习Java也有一段时间了,不知道你在学习Java的过程中是不是有这样的困扰:Java的概念好抽象啊!好琐碎啊!感觉都是学完后面的忘记前面的。
那我们怎么应对这种情况呢?当然是想办法把所学的用起来呀!可能你会说:我才刚学Java,就我现在的水平能用Java干点什么呢?别着急,只要你有了前面类和对象的基础知识,咱们还真能搞出一个小项目来练练手。
😎快来看看我们这个项目有那些功能吧!
一、界面显示
1、管理员菜单操作
🏀 删除图书,也是一样的,程序根据书名来从书架上删除我们要删除的那本书
🍑 之后就是退出系统了
🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟
2、用户菜单操作
😎**刚才我们借阅了西游记,那么接下来让我们在查找一下西游记这本书,看看他是不是真的被借出了,嘻嘻 **
那么接下来我们再归还一下
📝 这个小练习大致的操作就这么几个,让我们一起来看看这些功能是怎么实现的吧
二、 核心思想
首先与面向过程的语言不同,面向对象的各种操作其实就分为这几步:
📝**找对象——》创建对象——》使用对象 **
📝从我们刚才的演示也不难发现,其实我们主要的对象就有两个:一个是书,另一个就是用户,无非就是在这些对象外多了各种各样的操作罢了🤔。
🍑我们这个项目主要分为四部分 :
- 对书籍的存储和定义
- 对书的各种操作(借阅归还等等)
- ** 用户(管理员和普通用户)**
- Main方法(程序的入口)
温馨提示:下面这几段是我们这个程序的核心思想和方法实现 (现在看不懂也没关系,你可以在看完我的代码实现后再看,相信你会有不一样的感受的)😊
📝首先从我们刚才的演示也可以看到,咱们这个图书小系统是分权限的,管理员是一个菜单、普通用户又是一个菜单。既然是面向对象那么我们可以把管理员当做一个类,普通用户当做一个类。但问题是:我们怎么根据用户不同的输入来进行不同的菜单选择呢?
📝答案就是通过多态,我们知道普通用户和管理员肯定都有共性,那么我们就可以把从中提取出来一个User的父类,里面有一下基本的属性如年龄和一些共性的操作。我们可以让管理类和普通类都作为User类的子类
📝我们前面说了:子类可以重写父类中的方法,让后通过向上转型(即父类引用指向不同的子类对象)这样通过父类引用所访问的方法,会根据引用对象的不同表现出不同的形态——这就是多态。
🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟
🍑解决了该调用管理员还普通用户的问题之后,新的问题又来了。我们怎么知道该调用具体的哪个操作呢?如果每一个操作我们都把他当成一个类,那么我们该用什么来存储这一大堆操作呢?
🍑我们能不能也用多态的思想来 实现呢?
🍑但要实现多态是有条件的
- 存在继承关系
- 存在向上转型
- 子类重写父类的方法
📝那么好,在这些操作中我们能把能抽象出来一个父类呢?然后再创建一个数组,数组中每一个元素都是一个父类引用,我们再根据权限的不同,再管理员类和普通成员类里给该数组赋值,将不同的子类操作对象赋值给数组中的父类引用,这不就是向上转型吗?
📝然后不同的子类操作再重写父类的方法,只要我们不就可以根据数组中的父类引用来实现子类不同操作了吗?
三、 具体的实现方法
1、Book包下面的书类和书架类
📝既然是图书管理系统,书籍肯定是重中之重,我们不妨专门建一个书类——》来储存书籍
🌰书类
package Book; // Book类和书架类BookList都在Book这个包的底下
public class Book {
private String name; // 体现封装性
private String author; // 接下来再该类中要提供getter和setter方法,以便再其他类中也能访问这些成员属性
private int price;
private String type;
private boolean isBorrowed; // 在Java中成员变量会自动进行初始化,布尔类型的默认初始化为flase
// 构造方法,用来给新增的图书初始化
public Book(String name, String author, int price, String type) {
this.name = name;
this.author = author;
this.price = price;
this.type = type;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public boolean isBorrowed() {
return isBorrowed;
}
public void setBorrowed(boolean borrowed) {
isBorrowed = borrowed;
}
// 如果不重写,直接打印引用变量,打印出来的是地址
// 重写toString方法,这样当我们打印(指向书的这个对象)的引用变量就可以直接打印出来书籍的各种信息了
@Override
public String toString() {
return "Book{" +
"bookname='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
", type='" + type + '\'' +
((isBorrowed == true) ? " 已借出" : " 未借出")+
'}';
}
}
🍑上面是书类,但我们的书类好像只有每本书的详细信息,那么我们该如何实现一个有很多书的图书管理系统呢🤔?
🍑嘻嘻,当然有办法了,我们不妨定义一个书架类来存放各种书籍——同时接下来对书籍的各种操作也可以通过这个书架类来进行具体操作
🌰书架类
package Book; // 和我们之前定义的书类都放在Book这个包底下
// 对书架上的书进行操作的书架类BookList
public class BookList {
// 用数组来存这些书籍,并且数组中每一个元素都是一个Book类的引用类型
// 也就是说:数组中的每一个元素都能指向一个被实例化的书籍对象
private Book[] books = new Book[10]; // 暂定为十本书,还可以扩容
private int usedsize; // 当前书架上书的数量
public BookList() {
// 因为判断图书是否被借出的布尔类型默认是false,那么我们就将默认的false视为为借出,借出了在改为true
// 这样因为对应初始化的图书来说默认都是未借出的,所以我们不用初始化布尔类型
books[0] = new Book("三国演义", "罗贯中", 23, "小说");
books[1] = new Book("西游记","吴承恩",78,"小说");
books[2] = new Book("红楼梦","曹雪芹",89,"小说");
usedsize = 3; // 对书籍上的书进行初始化
}
// 在其他类中也要及时的获取当下书架上有多少本书了
public int getUsedsize() {
return usedsize;
}
public void setUsedsize(int usedsize) {
this.usedsize = usedsize;
}
// 在这个里面我们可以放一下对书籍的常规和基本操作,比如打印指定的某本书
// 接下来在我们的操作类中也会用到这些方法
/**
* 打印数组books[i]中的元素
* @param i
*/
public void printbooks(int i) {
System.out.println(books[i]);
}
/**
* 获取pos下标的书籍
* @param pos
* @return
*/
public Book getPos(int pos) {
return books[pos];
}
/**
* 给books数组的pos位置放一本书籍
* @param book
*/
public void setBooks(int pos, Book book) {
books[pos] = book;
}
}
🏀好了,我们的书架定义完成了。讲道理我们可以直接在这个类里面完成各种对书籍的操作🤔,但我们没有这样干,我们只是在这里面定义了一些对书籍的最基本的操作和方法😂。
📝我们不妨把各个操作都分离抽象出来,分别都定义一个类,在各个类中实现对书架的操作。至于这有什么好处,能我们的项目快完成的时候你就知道了(其实这样能省去很多逻辑判断的),嘻嘻。
🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻🌻
📝接下来我们再考虑用户这个对象,不管是管理员还是普通用户,他们都属于是用户,是有相似点的。我们不如就定义一个User父类,再父类里面写一些用户基本的属性和操作,然后管理员和普通用户再分别继承User父类,并在他们自己的类中定义他们自己独特的属性和方法
🍑**至于为啥呀,这样其实是体现了Java中一个很重要的思想的——多态的实现 **
**🐟你可能现在不理解,不要紧,耐着性子往下看就好了😂 **
⛽
2、user包下面的用户类
🌰User父类
package user; // User父类、管理员类、普通用户类都被放在user这个包底下
import Book.BookList; // 我们的操作离不开对书架类
import Operatation.IOperatation;
// IOperation接口是我们所有对数组操作的公共的标准和规范
public abstract class User {
protected String name;
//父类类的 protected 成员是包内可见的,并且对子类可见;
//若子类与基类不在同一包中,那么在子类中,子类实例可以访问其从基类继承而来的protected方法,而不能访问基类实例的protected方法。
public User(String name) {
this.name = name; // 构造方法初始化name
}
//在Main方法中,我们是通过User来访问被子类重写的不同方法的,在父类User中也要有menu方法😊
public abstract int menu();
// 用类型是IOperation的数组来储存菜单中不同的操作
// 因为对于IOperation数组中的每一个元素都相当于一个父类引用,我们之后在给数组元素赋值时,
// 可以把子类对象的各种操作来赋值给父类引用,这就相当于父类引用了子类对象——》即向上转型,那么之后我们在用父类IOperation中的work方法时,
// 会根据当前父类IOperation引用的是那个子类对象(操作方法),来实现当前子类重写work方法后的各种不同的操作
//即这样就实现了多态😁
public IOperatation[] iOperatations;
// user父类中的DoOperation方法就是所有对图书操作的总方法🤔,所有对图书的操作方法都在这里面实现
public void DoOperation(int choice, BookList bookList) {
iOperatations[choice].work(bookList);
// iOperatioon里面存的就是针对不同操作所对应的不同对象😉,里面有新增对图书的各种操作,
// 在给对管理员类实例化的时候就已经完成了对iOperation数组的初始化(通过管理员类的构造方法)
}
}
// choice解决的正是调用的是那个操作的问题😎,比如管理员菜单中操作2代表新增图书
📝那么我们的iOperation[2]这个接口类引用对象就指向了——》新增图书这个操作类所实例化的对象—>
即发生了向上转型🤔,那么当我们iOperation[2].work(bookList)来调用我们被重写的work方法时😂,
就发生了多态,执行的就是我们新增图书类中的work操作😎
🍑对与我们这个小项目来说,这个User类有很大的作用😮,我们接下来的很多操作都是通过User父类调用他子类重写的方法,比如User的菜单方法就被管理员子类和普通用户子类重写了😎,当我们调用这些被重写方法的时候,根据父类引用指向的子类对象的不同🤔,就会调用不同子类的重写方法——实现了多态。
🌰管理员类的代码
package user; // User父类、管理员类、普通用户类都被放在user这个包底下
import Operatation.*; // 导入接下来包含我们各种操作类的包
import java.util.Scanner;
public class AdminUser extends User{
public AdminUser(String name) {
super(name); // 要先完成对父类的构造方法,再进行子类的构造
// 如果放在构造方法这个地方,当你构造数组的时候就把iOperatations这个数组给初始化了
this.iOperatations = new IOperatation[] {
// 用数组储存你所要进行的操作
new Exceptionbook(),
new FindBook(), // 注意这里的数组下标要和菜单中用户的选择一致
new AddBook(), //比如菜单中的2是新增图书,你数组的2下标的位置就应该放实例化的AddBook对象
new DeleteBook(),
new ShowBooks(),
};
}
// 对User类中的menu方法进行重写,以便实现多态,当在g
public int menu() {
System.out.println("hello,管理员:" + this.name + ",欢迎来到图书小练习系统");
System.out.println("===========管理员菜单============");
System.out.println("1.查找图书");
System.out.println("2.新增图书");
System.out.println("3.删除图书");
System.out.println("4.显示图书");
System.out.println("0.退出系统");
System.out.println("===========管理员菜单============");
System.out.println("请输入你的操作:");
Scanner scanner = new Scanner(System.in);
return scanner.nextInt();
}
}
🍑相信大家对管理员类中的menu方法不会感到惊讶,毕竟管理员和普通用户的菜单就是不一样呀!当然要重写User父类中的menu方法呀!
📝但在管理员类中的构造方法中为啥会出现iOperatations这个东东,好像在User类中我们也有这个小家伙,不信你看:
public IOperatation[] iOperatations;
IOperatation是一个接口,是我们接下来对图书的所有操作的一个公共的接口,即即我们操作的规范和标准,而iOperatations是这个接口类型的数组名,数组里面存的每个元素都是一个接口的引用。
🤔 咱回头看看咱们的代码
// 如果放在构造方法这个地方,当你构造数组的时候就把iOperatations这个数组给初始化了
this.iOperatations = new IOperatation[] {
// 用数组储存你所要进行的操作
new Exceptionbook(), // 数组的下标分别对应菜单中操作的选择
new FindBook(), 大家知道吗在给数组赋值的过程中就发生了向上转型
new AddBook(), 为啥?因为在后面你会看到我们
new DeleteBook(),
new ShowBooks(),
};
大家知道吗在给数组赋值的过程中就发生了向上转型吗😎
📝为啥?因为在后面你会看到我们所有对图书的操作都实现了IOperation这个接口
而数组中的每一个元素都相当于是一个对该接口的引用,所以我们在赋值过程中就相当于
我们用不同的类实现了该接口引用🤔,并对该接口引用分别进行了不同的重写😉
📝一个类实现了一个接口其实就可以理解为一个子类继承了一个父类,这个类可以调用接口中的方法并进行重写。
其实上面对数组的操作过程就相当于继承中的父类引用指向子类对象(向上转型)🤔
🍑所以呀!大家可千万不要小看这个小家伙,他可是体现了咱这个项目的核心思想——多态哈😊!
🏀可能现在你还看不出来,等我们把Main方法和各种操作方法拿上来,你就知道了😁
🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟🐟
🌰普通用户类
package user; // // User父类、管理员类、普通用户类都被放在user这个包底下
import Operatation.*; // 我们的用户肯定是要对书进行操作的(借书还书),导入操作这个包
import java.util.Scanner;
public class NormalUser extends User {
public NormalUser(String name) { // 子类的构造方法
super(name);
//父类的构造方法是带一个参数的构造方法,编译器不会默认生成,所以在子类构造方法实现前,先要用super实现父类的构造方法
this.iOperatations = new IOperatation[] { // 给数组赋值,实现向上转型
new Exceptionbook(),
new FindBook(),
new BorrowOperation(),
new ReturnOperation(),
};
}
// 对User父类中的menu菜单方法重写
public int menu() {
System.out.println("hello,用户:" + this.name + ",欢迎来到图书小练习系统");
System.out.println("===========用户菜单============");
System.out.println("1.查找图书");
System.out.println("2.借阅图书");
System.out.println("3.归还图书");
System.out.println("0.退出系统");
System.out.println("===========用户菜单============");
System.out.println("请输入你的操作:");
Scanner scanner = new Scanner(System.in);
return scanner.nextInt();
}
}
🍑大家不难看出来,普通用户类和管理员类有很多操作都是相似的,嘻嘻😁。
📝但不知道大家在这个代码中发现一个问题没有😂?我们的menu菜单方法好像把我们所选择的对图书的操作给返回了🤔。大家不要小看这个返回值,正是这个返回值告诉了我们应该具体调用菜单的哪个方法😎
🤔不信大家看程序的入口Main方法就知道了
3、Main方法,程序的入口
🌰Main方法
import Book.BookList; // 在Main方法中,咱们这个项目的各种类几乎都要用到,所以要疯狂导包😂
import user.AdminUser;
import user.NormalUser;
import user.User;
import java.util.Scanner;
public class Main {
public static User login() {
System.out.println("请输入你的姓名:");
Scanner sc = new Scanner(System.in);
String name = sc.nextLine();
System.out.println("请选择你的身份:1 -》管理员,0 -》普通用户");
int key = sc.nextInt();
if (key == 1) {
return new AdminUser(name); // 根据选择的不同,返回不同的子类对象
}
else {
return new NormalUser(name);
}
}
public static void main(String[] args) {
// 当我们实例化书架这个对象后,通过书架的构造方法其实就已经初始化了书架😮,也就是说书架上现在已经有了几本书了
BookList bookList = new BookList();
//通过login的返回值确定了当前调用的是谁的操作,是管理员,还是普通用户😊
User user = login();
// 因为在login中父类引用所引用的子类对象不同,我们是把不同的实例化的对象返回并赋值给了user父类引用🤔
// 就相当于已经把该对象所对应的IOperation数组给初始了,此时该IOperation数组中按menu菜单的顺序已经存了不同的操作对象😮
while (true) { // 为啥要用while死循环🤔,因为你的对操作不只一下啊,直到你选择退出系统前,你的程序其实都在运行着
int choice = user.menu(); // 通过对菜单的引用的返回值已经确定了用户当前选择的菜单操作
关于DoOperation这个方法是所有图书操作的一个总方法,我们在User父类中已做了详细的讲解,大家可以在去上面再瞅瞅😉
user.DoOperation(choice, bookList);
// 上面User已经通过login确定了当前是调用的是普通用户的菜单还是管理员的菜单
// 而choice解决的正是我们要调用的是菜单的哪个操作的问题😉
}
}
}
📝看了Main方法后,相信你对这个小项目已经有了一定的认识😊,不知道你体会到那种多态的思想了吗?
😁让我们再来看看最后一个方面-》各种对图书的操作类
4、Operation包下面的各种操作类
🌰操作类抽象出来的一个公共接口
package Operatation; // 接口IOperation和其他操作类都在Operation这个包底下
import Book.BookList;
// 接口IOperation
// 我们会发现我们各种操作其实都只是对书架上的书进行操作
// 行为标准都是一样的,我们可以抽象出来一个接口
// 各个操作再对这个接口进行不同方式的实现,以此来实现多态
public abstract interface IOperatation {
public abstract void work(BookList bookList);
// 接下来实现该接口的类会对work方法进行不同方式的重写,以此来实现他们各自的操作
}
接口本身就是一个抽象类型,是抽象方法的集合,一个非抽象类如果实现了该接口,必须实现接口内所描述的所有方法
🏀为啥要有这样一个接口呢?接口里面又没有什么具体的实现方法,我们要这个接口有什么用呢?
📝当然是有用的,通过定义接口你难道没有体会出一种多态的思想吗?就和之前我们把管理员类和普通用户类抽象出来一个父类是一样的道理?
📝如果我们将用所有的操作方法放入operation包中,所有的方法都用接口来实现,这样我们就避免了再bookList书架类中使用一大堆的 if else (你总不会想在bookList书架类中使用一大堆逻辑来确定调用的是那个具体的操作吧😂)
🌰新增图书
package Operatation; // 接口IOperation和其他操作类都在Operation这个包底下
import Book.Book;
import Book.BookList; // 你是对书架类操作的,要注意导包
import java.util.Scanner;
public class AddBook implements IOperatation{
// 当前AddBook类实现IOperation接口
// 对接口IOperation中的work抽象方法进行重写,以便实现多态
@Override
public void work(BookList bookList) {
Scanner sc = new Scanner(System.in);
System.out.println("增加图书");
System.out.print("请输入你要新增图书的书名:");
String bookname = sc.nextLine();
System.out.print("请输入读书的作者:");
String author = sc.nextLine();
System.out.print("请输入图书的类型:");
String type = sc.nextLine();
System.out.print("请输入图书的价格:");
int price = sc.nextInt();
// nextInt不要放在nextLine前面,因为当你把nextInt放在前面的而nextLine在后面的话,
// 当你输入了一个整数然后回车,你的回车也是一个字符、会被当成nextLine的输入
Book book = new Book(bookname, author, price, type);
// 增加数组默认是在数组books的最后增加书籍
bookList.setBooks(bookList.getUsedsize(), book);
// 新增后你要给当前书架上的书籍数量UsedSize加一
bookList.setUsedsize(bookList.getUsedsize() + 1);
// 因为和书架没在同一个类中,Usedsize还被private修饰,所以要借用getterUsedsize方法
System.out.println("新增成功");
}
}
🌰查找图书
package Operatation;
import Book.Book;
import Book.BookList;
import java.util.Scanner;
// 对接口的实现
public class FindBook implements IOperatation{
@Override
public void work(BookList bookList) {
System.out.println("查找图书");
Scanner sc = new Scanner(System.in);
System.out.print("请输入你要查找的书籍的名称;");
String name = sc.nextLine();
int flag = 0;
for (int i = 0; i < bookList.getUsedsize(); i++) {
Book book = bookList.getPos(i);// 这里用到了我们在bookList类中定义的对书籍的简单操作方法
if (book.getName().equals(name)) { // 用String中的equals方法来判断字符串是否相等
System.out.println("找到这本书了");
bookList.printbooks(i); // 打印Books数组中i下标的书籍信息,printbooks方法我们已经在bookList类中实现了
flag = 1;
break;
}
}
if (flag == 0) {
System.out.println("没有找到你想要的书");
}
}
}
🌰删除图书
package Operatation;
import Book.Book;
import Book.BookList;
import java.util.Scanner;
// 对接口的实现
public class DeleteBook implements IOperatation{
@Override
public void work(BookList bookList) {
System.out.println("删除图书");
System.out.print("请输入你要删除图书的名称:");
Scanner sc = new Scanner(System.in);
String name = sc.nextLine();
int idex = -1;
for (int i = 0; i < bookList.getUsedsize(); i++) {
Book book = bookList.getPos(i);
if (book.getName().equals(name)) {// 通过equals方法找到要删除的这本书
idex = i;
break;
}
}
if (idex == -1) {
System.out.println("没有你要找删除的书籍");
return;
}
// 因为我们的书籍是在数组中存放的,所有删除数组中某一个元素后要进行覆盖
for (int i = idex; i < bookList.getUsedsize() - 1; ++i) {
Book book = bookList.getPos(i + 1);
bookList.setBooks(i, book);
}
// 下面的很多方法都在我们的bookList类中,有不理解的可以回到bookList类中再看看
bookList.setBooks(bookList.getUsedsize() - 1, null); // 把数组中多余的元素赋值为null,便于让垃圾回收器回收,减少内存空间的消耗
bookList.setUsedsize(bookList.getUsedsize() - 1); // 对数组中存在的书籍数目减一
System.out.println("删除结束");
}
}
🌰显示图书
package Operatation;
import Book.BookList;
public class ShowBooks implements IOperatation{
@Override
public void work(BookList bookList) {
System.out.println("显示图书");
for (int i = 0; i < bookList.getUsedsize(); i++) {
bookList.printbooks(i);
// 调用bookList类中实现的打印指定下标的图书的方法
}
}
}
🌰借阅图书
package Operatation;
import Book.Book;
import Book.BookList;
import java.util.Scanner;
public class BorrowOperation implements IOperatation {
@Override
public void work(BookList bookList) {
// 对抽象类IOperation类中的方法进行重写,以便实现多态
System.out.println("借阅图书");
Scanner sc = new Scanner(System.in);
System.out.print("请输入你要借阅的图书的书名:");
String name = sc.nextLine();
for (int i = 0; i < bookList.getUsedsize(); i++) {
Book book = bookList.getPos(i);
if (book.getName().equals(name) && book.isBorrowed() == false) {
book.setBorrowed(true);
System.out.println("借阅成功!");
return;
}
}
System.out.println("借阅失败,没有这本书或该书已被借出!");
}
}
🌰归还图书
package Operatation;
import Book.Book;
import Book.BookList;
import java.util.Scanner;
// 对接口的实现
public class ReturnOperation implements IOperatation{
@Override
// 对抽象类IOperation类中的方法进行重写,以便实现多态
public void work(BookList bookList) {
System.out.println("归还图书");
System.out.print("请输入你要归还的图书的书名:");
Scanner sc = new Scanner(System.in);
String name = sc.nextLine();
for (int i = 0; i < bookList.getUsedsize(); i++) {
Book book = bookList.getPos(i);
if (book.getName().equals(name)) {
book.setBorrowed(false);
System.out.println("归还成功!");
return;
}
}
System.out.println("图书名字输入错误,归还失败");
}
}
🌰退出系统
package Operatation;
import Book.BookList;
// 对接口的实现
public class Exceptionbook implements IOperatation{
@Override
public void work(BookList bookList) {
System.out.println("正在退出系统");
for (int i = 0; i < bookList.getUsedsize(); i++) {
bookList.setBooks(i, null);
}
System.out.println("退出成功!");
System.exit(0);
}
}
😁哈哈,咱们图书管理系统到这里就大功告成了。
🍑别看这个小项目功能很简单,但可以说这个项目几乎用到了我们之前学过的所有的Java基础知识,是一个不错的练手小项目,大家下去可以自己试试呀!大家还可以尝试自己再添加一些功能呢,嘻嘻。
📝在这里放上我这个项目的gitee地址:
图书小练习代码gitee地址
🍑好了,这篇一万多字的博客终于写完了😂,码字不易,大家都看到这里了,点个赞再走好吗🥰
版权归原作者 是小鱼儿哈 所有, 如有侵权,请联系我们删除。