最新xx学校图书馆管理系统.docx

上传人:1595****071 文档编号:47524060 上传时间:2022-10-02 格式:DOCX 页数:293 大小:1.69MB
返回 下载 相关 举报
最新xx学校图书馆管理系统.docx_第1页
第1页 / 共293页
最新xx学校图书馆管理系统.docx_第2页
第2页 / 共293页
点击查看更多>>
资源描述

《最新xx学校图书馆管理系统.docx》由会员分享,可在线阅读,更多相关《最新xx学校图书馆管理系统.docx(293页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。

1、Four short words sum up what has lifted most successful individuals above the crowd: a little bit more.-author-datexx学校图书馆管理系统xx学校图书馆管理系统开发背景和需求分析例001 图书馆管理系统1.1 开发背景高校拥有一个小型图书馆,为全校师生提供一个阅读、学习的空间。近年来,随着生源不断扩大,图书馆的规模也随之扩大,图书数量也相应地大量增加,有关图书的各种信息成倍增加。面对如此庞大的信息量,校领导决定使用一套合理、有效、规范、实用的图书馆管理系统,对校内图书资料进行统一、

2、集中的管理。笔者受该高校的委托,开发一个图书馆管理系统,开发宗旨是实现图书管理的系统化、规范化和自动化,达成图书资料集中、统一管理的目标。1.2 需求分析图书馆管理系统是图书馆管理工作中不可缺少的部分,对于图书馆的管理者和使用者来说都非常重要,但长期以来,人们使用传统的手工方式或性能较低的图书馆管理系统管理图书馆的日常事务,操作流程比较繁琐,效率相当低。而一个成功的图书馆管理系统应提供快速的图书信息检索功能、快捷的图书借阅、归还流程,为管理者与读者提供充足的信息和快捷的数据处理手段。笔者通过对一些典型图书馆管理系统的考察,从读者与图书馆管理员的角度出发,本着以读者借书、还书快捷、方便的原则,要

3、求本系统应具有以下特点。 具有良好的系统性能,友好的用户界面。 较高的处理效率,便于使用和维护。 采用成熟技术开发,使系统具有较高的技术水平和较长的生命周期。 系统尽可能简化图书馆管理员的重复工作,提高工作效率。 简化数据查询、统计难度。系统设计视频001 系统功能概述例001 图书馆管理系统1.1 系统目标根据以上的需求分析以及与用户的沟通,该系统应达到以下目标。 界面设计友好、美观。 数据存储安全、可靠。 信息分类清晰、准确。 强大的查询功能,保证数据查询的灵活性。 操作简单易用、界面清晰大方。 系统安全、稳定。 开发技术先进、功能完备、扩展性强。 占用资源少、对硬件要求低。 提供灵活、方

4、便的权限设置功能,使整个系统的管理分工明确。1.2 系统功能结构图书馆管理系统分为4大功能模块,分别为“基础数据维护”、“图书借阅管理”、“新书订购管理”、“系统维护”。本系统各个部分及其包括的具体功能模块如图1所示。图Error! Bookmark not defined. 图书馆管理系统功能结构1.3 系统流程图图书馆管理系统的系统流程如图2所示。图2 图书馆管理系统流程图1.4 系统预览图书馆管理系统由多个程序界面组成,下面仅列出几个典型界面。读者相关信息添加界面如图3所示,该界面用于将读者相关信息添加至数据表中;读者信息修改与删除界面如图4所示,该界面用于展示读者相关信息,并且提供了修

5、改与删除功能。图3 读者相关信息添加界面图4 读者信息修改与删除界面新书订购管理界面如图5所示,主要实现新书订购功能。图书验收界面如图6所示,主要实现新书验收功能。图5 新书订购管理界面图6 图书验收界面1.5 构建开发环境在开发图书馆管理系统时,需要具备下面的开发环境。 操作系统:Windows 2003。 Java开发包:JDK 6.0。 数据库:SQL Server 2000。 开发工具:Eclipse 3.2。1.6 文件夹组织结构在编写代码之前,可以将系统中可能用到的文件夹先创建出来,这样不但方便以后的开发工作,也可以规范系统的整体架构。笔者在开发图书馆管理系统时,设计了如图7所示的

6、文件夹架构图。在开发时将所创建的文件保存在相应的文件夹中即可。图7 图书馆管理系统文件夹组织结构数据库设计视频002 数据库设计视频003 创建项目例001 图书馆管理系统1.1 数据库分析SQL Server 2000具有很强的完整性与可伸缩性,具有较低的价格比与性能比,考虑到本系统的稳定性与可靠性以及开发程序与用户需求,笔者决定在设计该系统时选择SQL Server 2000数据库来满足系统的需求。1.2 数据库概念设计根据以上对系统所作的需求分析、系统设计,规划出本系统中使用的数据库实体分别为图书信息实体、图书分类实体、图书订购实体、读者信息实体、操作员信息实体、图书借阅信息实体、库存信

7、息实体。其中图书信息实体与图书订购实体、图书分类实体、图书订购实体、图书借阅信息实体、库存信息实体都具有关系,而读者信息实体与图书借阅信息实体同样具有关系。下面将介绍几个关键实体的E-R图。 图书信息实体图书信息实体包括图书编号、图书类别编号、书名、作者、译者、出版社、价格、出版时间等属性。其中图书编号为图书信息实体的主键,图书类别编号为图书信息实体的外键,与图书类别实体具有外键关系。图书信息实体的E-R图如图8所示。图8 图书信息实体E-R图 读者信息实体读者信息实体包括条形码、姓名、性别、年龄、电话、押金、生日、职业、证件类型、办证日期、最大借书数量、证件号码等属性。读者条形码作为本实体的

8、唯一标识。其中,在性别属性标识信息中,“1”代表此读者为男性,“2”代表此读者为女性;最大借书数量属性设置默认值为3;而在证件属性标识信息中,“0”代表身份证,“1”代表军人证,“2”代表学生证,“3”代表工作证。读者信息实体的E-R图如图9所示。图9 读者信息实体E-R图 图书借阅信息实体图书借阅信息实体包括编号、图书编号、读者编号、操作员编号、是否归还、借阅日期、归还日期等属性。编号作为图书借阅信息实体的唯一标识,它包括两个外键,分别为图书编号与读者编号,图书借阅信息实体以这两个外键与图书信息实体、读者信息实体建立了关系。图书借阅信息实体的E-R图如图10所示。图10 图书借阅信息实体E-

9、R图 图书分类实体图书分类实体包括编号、类别名称等属性。图书分类实体与图书信息实体以图书类别编号建立了关系。图书分类实体的E-R图如图11所示。 图书订购实体图书订购实体主要包括图书编号、订购日期、订购数量、操作员、是否验收和折扣等属性。图书订购实体以图书编号与图书信息实体建立了关系。图书订购实体的E-R图如图12所示。图11 图书分类实体E-R图图12 图书订购实体E-R图 操作员信息实体操作员信息实体主要包括编号、姓名、性别、年龄、身份证号、工作日期、电话、是否为管理员和密码等属性。其中,性别属性信息中“1”代表男性,“2”代表女性;是否为管理员属性信息中“0”代表当前用户不是管理员,“1

10、”代表当前用户是管理员。操作员信息实体的E-R图如图13所示。图13 操作员信息实体E-R图 库存信息实体库存信息实体主要包括编号、库存数量等属性。库存信息实体以库存编号与图书信息实体建立了关系。库存信息实体的E-R图如图14所示。图14 库存信息实体E-R图1.3 使用PowerDesigner建模在数据库概念设计中已经分析了本系统中主要的数据库实体对象,通过这些实体可以得出数据表结构的基本模型,最终这些实体将被创建成数据表,形成完整的数据结构。笔者使用PowerDesigner软件对数据进行建模操作,创建完成的数据库模型如图15所示。图15 图书馆管理系统的数据库模型公共模块设计视频004

11、 编写公共类例001 图书馆管理系统在开发过程中,经常会用到一些公共模块,如数据库连接及操作的类、限制文本框输入长度的类以及描述组合框索引与内容的类等,因此在开发系统前首先需要设计这些公共模块。下面将具体介绍图书馆管理系统中公共模块的设计过程。1.1 数据库连接及操作类的编写数据库连接及操作类通常包括连接数据库的方法getConnection()、执行查询语句的方法executeQuery()、执行更新操作的方法executeUpdate()、关闭数据库连接的方法close()。下面将详细介绍如何编写图书馆管理系统中的数据库连接及操作的类Dao.java。步骤如下:(1)指定类Dao.java

12、保存的包,并导入所需的类包,本例将其保存到com.wsy.dao包中。关键代码如下:package com.wsy.dao;/指定类的包名称import java.sql.Connection;/导入进行数据库连接时所使用的java.sql.Connection类import java.sql.DriverManager;/导入进行数据库连接时所使用的java.sql.DriverManager类import java.sql.ResultSet;/导入进行数据表查询时所使用的java.sql.ResultSet类import java.sql.SQLException;/导入进行数据库操作时

13、捕捉异常使用的java.sql.SQLException类注意:包语句以关键字package后面紧跟一个包名称,然后以分号“;”结束;包语句必须出现在import语句之前;一个.java文件只能有一个包语句。(2)在Dao.java类的构造方法中创建数据库连接操作。在此类中首先定义数据库连接驱动包名、数据库连接路径、数据库连接用户名、密码等静态变量,然后在构造函数中实现数据库连接操作。在数据库连接代码中需要添加trycatch关键字,捕捉数据库连接时可能抛出的异常。关键代码如下:/定义驱动包名称protected static String dbClassName = com.microsof

14、t.jdbc.sqlserver.SQLServerDriver;protected static String dbUrl = jdbc:microsoft:sqlserver:/localhost:1433;+ DatabaseName=db_library;SelectMethod=Cursor;/定义数据库连接路径protected static String dbUser = sa;/定义数据库连接用户名protected static String dbPwd = ;/定义数据库连接密码protected static String second = null;private st

15、atic Connection conn = null;/定义一个数据库连接private Dao() try /捕捉数据库连接异常if (conn = null) /如果连接为空Class.forName(dbClassName).newInstance();/装载SQL Server驱动conn = DriverManager.getConnection(dbUrl, dbUser, dbPwd); /获取数据库连接else/如果连接不为空return;/返回 catch (Exception ee) ee.printStackTrace();/捕捉数据库连接异常(3)创建执行查询语句的方

16、法executeQuery,其返回值为ResultSet结果集。首先需要初始化Dao对象,调用构造函数,从而获取数据库连接。有一点值得注意,就是在创建数据库连接之前首先判断数据库连接是否为空,如果为空再创建数据库连接,避免造成程序资源的浪费。executeQuery方法的代码如下:private static ResultSet executeQuery(String sql) try /捕捉数据库操作异常if(conn=null)/数据库连接如果为空unew Dao();return conn.createStatement(vResultSet.TYPE_SCROLL_SENSITIVE,

17、wResultSet.CONCUR_UPDATABLE).executeQuery(sql);/返回一个ResultSet结果集 catch (SQLException e) e.printStackTrace();/捕捉异常return null; finally u 调用构造函数创建数据库连接。v ResultSet.TYPE_SCROLL_SENSITIVE:常量允许记录指针向前或向后移动,且当ResultSet对象变动记录指针时,会影响记录指针的位置。这种类型的设置使结果集受到其他用户所作更改的影响。例如当一个用户正在浏览记录时,其他用户的操作使数据库中的数据发生了变化,这时当前用户所

18、获取的记录集中的数据也会同步发生改变。w ResultSet.CONCUR_UPDATABLE:这种类型的设置支持对ResultSet的动态更新。(4)创建执行更新操作的方法executeUpdate(),它的返回值为int型的整数,此返回值代表数据表更新操作是否成功,返回1代表成功,返回-1代表没有成功。executeUpdate ()方法的关键代码如下:private static int executeUpdate(String sql) try /捕捉数据库操作异常if(conn=null)/如果数据库连接为空new Dao();/获取数据库连接return conn.createSt

19、atement().executeUpdate(sql);/进行数据库更新操作 catch (SQLException e) System.out.println(e.getMessage();/打印捕捉的异常return -1;/返回-1 finally (5)为了避免运行程序时资源的浪费,优化项目运行速度,需要在完成数据库操作后,关闭数据库连接,所以笔者在Dao.java类中创建了关闭数据库连接的方法close()。为了使数据库连接在程序结束后确定会被关闭,在close()方法中加入了finally字段,在finally块中将数据库连接置空。关键代码如下:public static voi

20、d close() try /捕捉异常conn.close();/关闭数据库连接 catch (SQLException e) e.printStackTrace();/捕捉异常finallyconn = null;/在最终执行块中将数据库连接置空1.2 MenuActions类的编写通常激活同一个命令有多种方式,用户可以通过工具栏中按钮、菜单选择特定的功能。在本系统中,最常用的命令就是弹出内部窗体,笔者将本系统中需要弹出的内部窗体命令统一放入MenuActions类中,这样触发任何一种组件事件时,都会按照统一的方式处理。Swing包提供了一个非常有用的机制,用来封装命令,并将其连接到多个事件

21、源,这种机制就是Action接口。Action接口有如下方法: public void actionPerformed(ActionEvent e) public Object getValue(String key) public void putValue(String key, Object value) public boolean isEnabled() public void setEnabled(boolean b) public void addPropertyChangeListener(PropertyChangeListener listener) public void

22、 removePropertyChangeListener(PropertyChangeListener listener)其中第一个方法在实现ActionListener接口的程序中经常看到,实际上Action接口扩展了ActionListener接口。getValue()与putValue()方法用来存储与提取动作对象的预定义名称与值。例如:action.putValue(Action.SMALL_ICON,new ImageIcon(*.gif);/将图标存储到动作对象中表1列举了几种常用的动作对象的预定义名称。表1 动作对象的预定义名称名 称值NAME名称,显示在按钮或菜单上SMALL

23、_ICON小图标,显示在按钮或菜单上SHORT_DESCRIPTION简单提示说明,当鼠标放在按钮或菜单上出现提示LONG_DESCRIPTION详细提示说明setEnabled()方法用于开启或禁用动作对象,isEnabled()方法用于检查动作是否启用。实现Action接口需要将接口中的所有方法都实现,所以在通常情况下都使用实现该接口的AbstractAction类,本系统中的MenuActions类正是继承了AbstractAction类,在MenuActions类中只要重写AbstractAction类中的actionPerformed()方法即可。下面以系统中的“更改密码”菜单项为例

24、说明MenuActions类的编写。以下代码是在MenuActions类中创建一个内部类,这个内部类用于创建菜单栏中“更改密码”菜单项的动作对象,在此类的构造函数中创建组件的提示说明,在actionPerformed()方法执行“更改密码”窗体的弹出操作。关键代码如下:private static class PasswordModiAction extends AbstractAction PasswordModiAction() putValue(Action.NAME,更改密码);/将菜单项名称设置为“更改密码”putValue(Action.LONG_DESCRIPTION, 修改当前

25、用户密码);putValue(Action.SHORT_DESCRIPTION, 更换密码);/在“更改密码”菜单项显示的提示文字/putValue(Action.SMALL_ICON,CreatecdIcon.add(bookAddtb.jpg);/将图标存储到动作对象中/setEnabled(false);/使动作禁用public void actionPerformed(ActionEvent e) if (!frames.containsKey(更改密码)|frames.get(更改密码).isClosed() GengGaiMiMa iframe=new GengGaiMiMa();

26、/初始化更改密码内部窗体frames.put(更改密码, iframe);Library.addIFame(frames.get(更改密码);/将内部窗体添加到外部窗体中将此内部类的对象作为MenuActions类的成员变量,然后再使用static定义一个静态区域进行初始化。类在被加载时,首先执行static定义的静态区域内部的代码,且只会被执行一次。关键代码如下:public static PasswordModiAction MODIFY_PASSWORD; /修改密码窗体动作static MODIFY_PASSWORD = new PasswordModiAction();/初始化修改密

27、码内部类对象同理,菜单栏中其他菜单项与子菜单中的菜单项也是以相同方式被封装到MenuActions类中。当某个组件需要使用这个动作对象时,以按钮为例,可以使用如下代码实现:JButton button=new JButton(MenuActions.MODIFY_PASSWORD);1.3 限制文本框长度类的编写在Swing语言创建的窗体中,当JTextField组件创建时,可以指定文本框的宽度。例如:JPanel panel=new JPanel();/创建面板JTextField textField=new JTextField(20);/创建文本框panel.add(textField)

28、;/将文本框添加到面板中但在JTextField的构造器中设定的宽度并不是用户能输入的字符个数上限,用户可以在文本框中输入一个更长的字符串,此时需要限制用户输入字符串的长度,笔者创建了限制文本框输入长度的类MyDocument.java。创建此类的步骤如下:(1)创建MyDocument.java类,此类继承PlainDocument类。关键代码如下:public class MyDocument extends PlainDocument(2)在MyDocument.java类中创建两个构造函数,其中一个是有参数的,另一个是无参数的。关键代码如下:public MyDocument(int

29、newMaxLength)/设置文本框的最大长度 super(); /执行父类构造方法 maxLength = newMaxLength; /将参数赋予类成员变量 public MyDocument() /无参的构造函数 this(10); /将数值10赋予类成员变量(3)重载父类方法insertString(),在此方法中限定文本框允许输入字符串长度。关键代码如下:public void insertString(int offset, String str, AttributeSet a)throws BadLocationException if (getLength() + str.l

30、ength() maxLength) /这里假定限制长度为10 return;/返回 else super.insertString(offset, str, a);(4)在程序设计中,当需要限制用户输入字符串长度时,可以使用如下代码。JTextField textField = new JTextField(请输入13位书号,13);/初始化文本框textField.setDocument(new MyDocument(13); /设置“书号”文本框最大输入值为13位1.4 描述组合框索引与内容类的编写在程序编写的过程中,经常会遇到组合框组件的应用。有时要在窗体中的组合框中显示具体内容,通常

31、需要在数据库中存储此组合框的索引值,这时便需要使用一种数据结构将组合框中的内容与索引值联系在一起。java.util.Map形式是比较好的选择,可以使用Map接口中的put()方法将索引值与具体内容放入集合中,当得到索引值时获取具体内容可以使用Map接口中的get(key)方法。描述组合框索引与内容类的编写步骤如下:(1)创建组合框组件的索引值与其所对应的内容的Item.java类,这个类中不仅包含代表组合框索引的成员变量id和代表组合框内容的成员变量name,还包括这两个成员变量的setXXX()、getXXX()方法。关键代码如下:package com.wsy.JComPz;public

32、 class Item upublic String id;/组合框索引值vpublic String name;/组合框内容public String getId() /id对应的getXXX()方法return id;public void setId(String id) / id对应的setXXX()方法this.id = id;public String getName() /name对应的getXXX()方法return name;public void setName(String name) /name对应的setXXX()方法this.name = name;public S

33、tring toString() /重写Object类中的toString()方法return getName();u id:表示组合框中索引值的变量。v name:表示组合框中具体内容的变量。(2)创建MapPz.java类,使用Map关联组合框的索引与组合框的具体内容。这里以图书类别编号与图书类别创建组合框为例,首先在此类中初始化Map集合,取图书类别相关内容,将图书类别相关内容放入Item类中;然后将图书类别编号与图书类别名称放入Map集合中,可以使用put()方法;最后返回类型为Map的集合。关键代码如下:package com.wsy.JComPz;public class MapP

34、z static Map map = new HashMap();/初始化Map接口public static Map getMap() List list = Dao.selectBookCategory();/获取图书类别相关内容for (int i = 0; i list.size(); i+) /循环操作BookType booktype = (BookType) list.get(i);/取得集合中的值Item item = new Item();/初始化Item对象item.setId(booktype.getId();/将图书类别编号放入Item类中item.setName(bo

35、oktype.getTypeName();/将图书类别名称放入Item类中map.put(item.getId(), item);/将图书类别编号与item对象放入mapreturn map;/返回集合(3)上述代码中用到了Dao.java中的selectBookCategory()方法,此方法用于查询图书类别相关信息,首先将数据库查询的相关信息放入JavaBean中,然后将JavaBean对象添加到list集合中,最终将结果以List形式返回。关键代码如下:public static List selectBookCategory() List list=new ArrayList();/初

36、始化List对象uString sql = select * from tb_bookType;/查询图书类别表SQL语句vResultSet rs = Dao.executeQuery(sql);/执行SQL语句,返回ResultSet对象try while (rs.next() /循环结果集wBookType bookType=new BookType();/初始化BookType对象bookType.setId(rs.getString(id);/将数据库中查询id值赋予到JavaBean中/将数据库中查询typeName值赋予到JavaBean中bookType.setTypeName

37、(rs.getString(typeName);bookType.setDays(rs.getString(days);/将数据库中查询days值赋予到JavaBean中bookType.setFk(rs.getString(fk);/将数据库中查询fk值赋予到JavaBean中list.add(bookType);/将JavaBean对象添加到list中 catch (Exception e) e.printStackTrace();/捕捉异常Dao.close();/关闭数据库连接return list;/将集合返回u sql:查询tb_bookType(图书类别表)的全部内容。v rs:

38、执行SQL语句后返回的ResultSet结果集。w bookType:实例化BookType类对象。1.5 在JLable上添加图片类的编写为了美化窗体,通常需要在窗体上添加图片。一般情况下使用如下方式添加图片: 在窗体上添加Jpanel。 在JPanel上添加Jlable。 将图片初始化为ImageIcon对象。 使用JLabel.setIcon(ImageIcon)代码实现在窗体上添加图片功能。在这里笔者将上述操作封装在公共类中,命名为CreatecdIcon.java类,在此类中定义一个返回ImageIcon类对象的方法,此方法以当前图片的文件名称为参数初始化一个ImageIcon类对象

39、。关键代码如下:package com.wsy.util;public class CreatecdIcon public static ImageIcon add(String ImageName)/返回ImageIcon类型的对象URL IconUrl = Library.class.getResource(/+ImageName);/当前图片的路径ImageIcon icon=new ImageIcon(IconUrl);/将路径封装到ImageIcon对象中return icon;/返回icon对象说明:Library.class.getResource(/1.jpg)指代的图片为项目

40、名称下的res文件下的图片,实际上“/”指代的路径为项目名称中的res文件。当需要在JLable中添加图片时,可以使用如下代码。final JLabel headLogo = new JLabel();/创建Jlable对象/使用CreadedIcon类中的add()方法返回一个ImageIcon对象ImageIcon bookModiAndDelIcon=CreatecdIcon.add(bookModiAndDel.jpg);headLogo.setIcon(bookModiAndDelIcon);/设置Jlable图片主窗体设计视频005 主窗体设计例001 图书馆管理系统1.1 主窗体

41、概述管理员通过“系统登录”模块的验证后,可以登录到图书馆管理系统的主窗体。系统主窗体主要包括菜单栏、工具栏。用户在菜单栏中单击任一菜单项即可执行相应的功能;工具栏为用户提供了经常使用的功能按钮。主窗体运行效果如图1所示。图1 图书馆管理系统主窗体的运行效果1.2 主窗体技术分析系统主窗体主要包括菜单栏与工具栏。单击窗体顶端的菜单栏中的某一菜单项,可以打开下拉菜单,其中包含菜单项与子菜单项。当用户单击下拉菜单中某一菜单项时,窗体中所有的菜单都会被关闭。图2展示了一个典型的菜单栏。图2 主窗体的菜单栏在主窗体的设计中,需要创建菜单栏与工具栏,此时用到了JMenuBar类与JToolBar类来创建菜单栏与工具栏。菜单栏的创建比较简单,使用JMenuBar的构造函数初始化菜单栏即可。一般情况下将菜单栏显示在框架顶部。例如:JMenuBar menuBar=new JMenuBar();/创建菜单栏frame.setJMenu=new JMenu(menuBar);/将菜单栏放入顶层框架对于每个菜单,需要创建一个对象,实际上就是菜单项名称。例如:JMenu editMenu=new JMenu(图书类别管理);/在菜单栏中创建顶层菜单最后将顶层菜单添加到菜单栏中,可以使用JMenuBar类的add()方法进行添加。例如:menuBar.add(editMenu);/将顶层菜单添加到菜

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 教育专区 > 高考资料

本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知淘文阁网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

工信部备案号:黑ICP备15003705号© 2020-2023 www.taowenge.com 淘文阁