• 项目01-JavaWeb网上书城01之工具类


    1.项目中用到的小工具

    1.CommonUtils

      CommonUtils类就两个方法:

        1.String uuid():生成长度32的随机字符,通常用来做实体类的ID。底层使用了UUID类完成;

        2.T toBean(Map, Class<T>):把Map转换成指定类型的Bean对象。通常用来获取表单数据(request.getParameterMap())封装到JavaBean中,底层使用了common-beanutils。注意,本方法要求map中键的名称要与Bean的属性名称相同才能完成映射,否则不能完成映射;

      1.源码

     1 import java.util.Map;
     2 import java.util.UUID;
     3 
     4 import org.apache.commons.beanutils.BeanUtils;
     5 import org.apache.commons.beanutils.ConvertUtils;
     6 
     7 /**
     8  * 小工具
     9  * 10  *
    11  */
    12 public class CommonUtils {
    13     /**
    14      * 返回一个不重复的字符串
    15      * @return
    16      */
    17     public static String uuid() {
    18         return UUID.randomUUID().toString().replace("-", "").toUpperCase();
    19     }
    20 
    21     /**
    22      * 把map转换成对象
    23      * @param map
    24      * @param clazz
    25      * @return
    26      * 
    27      * 把Map转换成指定类型
    28      */
    29     @SuppressWarnings("rawtypes")
    30     public static <T> T toBean(Map map, Class<T> clazz) {
    31         try {
    32             /*
    33              * 1. 通过参数clazz创建实例
    34              * 2. 使用BeanUtils.populate把map的数据封闭到bean中
    35              */
    36             T bean = clazz.newInstance();
    37             ConvertUtils.register(new DateConverter(), java.util.Date.class);
    38             BeanUtils.populate(bean, map);
    39             return bean;
    40         } catch(Exception e) {
    41             throw new RuntimeException(e);
    42         }
    43     }
    44 }

      2.测试代码

     1     /**
     2      * 随机生成32位长的字符串,通常用来做实体类的ID
     3      */
     4     @Test
     5     public void testUuid() {
     6         String s = CommonUtils.uuid();//生成随机32位长的字符串
     7         System.out.println(s);
     8     }
     9     
    10     /**
    11      * 把Map类型映射成Bean类型。
    12      * 要求map中键的名称与Person类的属性名称相同。
    13      * 即map的key分别为:pid、name、age、birthday,person的属性名称也是pid、name、age、birthday
    14      */
    15     @Test
    16     public void testToBean() {
    17         Map<String,String> map = new HashMap<String,String>();
    18         /*
    19          * map的key:pid、age、birthday、myname
    20          * person的属性:pid、age、birthday、name
    21          * map中没有名为name的键值,而多出一个名为myname的键值,所以映射后的person对象的name属性值为null。
    22          * map中的age和birthday都是字符串类型,而person的age是int类型、birthday是Date类型,但toBean()方法会自动对Map中值进行类型转换。
    23          */
    24         map.put("pid", CommonUtils.uuid());
    25         map.put("age", "23");
    26         map.put("birthday", "2014-01-30");
    27         map.put("myname", "张三");
    28         
    29         Person p = CommonUtils.toBean(map, Person.class);
    30         System.out.println(p);
    31     }

    =========================================================================================================================================

    2.JdbcUtils

    JdbcUtils用来获取Connection对象,以及开启和关闭事务。

      l  Connection getConnection():从c3p0连接池获取Connection对象,所以需要提供c3p0-config.xml配置文件;

      l  beginTransaction():为当前线程开启事务;

      l  commitTransaction():提交当前线程的事务;

      l  rollbackTransaction():回滚当前线程的事务;

      l  releaseConnection(Connection):如果参数连接对象不是当前事务的连接对象,那么关闭它,否则什么都不做;

      1.源码

     1 package cn.itcast.jdbc;
     2 
     3 import java.sql.Connection;
     4 import java.sql.SQLException;
     5 
     6 import javax.sql.DataSource;
     7 
     8 import com.mchange.v2.c3p0.ComboPooledDataSource;
     9 
    10 /**
    11  * 使用本类的方法,必须提供c3p0-copnfig.xml文件
    12  * @author qdmmy6
    13  */
    14 public class JdbcUtils {
    15     // 饿汉式
    16     private static DataSource ds = new ComboPooledDataSource();
    17     
    18     /**
    19      * 它为null表示没有事务
    20      * 它不为null表示有事务
    21      * 当开启事务时,需要给它赋值
    22      * 当结束事务时,需要给它赋值为null
    23      * 并且在开启事务时,让dao的多个方法共享这个Connection
    24      */
    25     private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
    26     
    27     public static DataSource getDataSource() {
    28         return ds;
    29     }
    30     
    31     /**
    32      * dao使用本方法来获取连接
    33      * @return
    34      * @throws SQLException
    35      */
    36     public static Connection getConnection() throws SQLException {
    37         /*
    38          * 如果有事务,返回当前事务的con
    39          * 如果没有事务,通过连接池返回新的con
    40          */
    41         Connection con = tl.get();//获取当前线程的事务连接
    42         if(con != null) return con;
    43         return ds.getConnection();
    44     }
    45     
    46     /**
    47      * 开启事务
    48      * @throws SQLException 
    49      */
    50     public static void beginTransaction() throws SQLException {
    51         Connection con = tl.get();//获取当前线程的事务连接
    52         if(con != null) throw new SQLException("已经开启了事务,不能重复开启!");
    53         con = ds.getConnection();//给con赋值,表示开启了事务
    54         con.setAutoCommit(false);//设置为手动提交
    55         tl.set(con);//把当前事务连接放到tl中
    56     }
    57     
    58     /**
    59      * 提交事务
    60      * @throws SQLException 
    61      */
    62     public static void commitTransaction() throws SQLException {
    63         Connection con = tl.get();//获取当前线程的事务连接
    64         if(con == null) throw new SQLException("没有事务不能提交!");
    65         con.commit();//提交事务
    66         con.close();//关闭连接
    67         con = null;//表示事务结束!
    68         tl.remove();
    69     }
    70     
    71     /**
    72      * 回滚事务
    73      * @throws SQLException 
    74      */
    75     public static void rollbackTransaction() throws SQLException {
    76         Connection con = tl.get();//获取当前线程的事务连接
    77         if(con == null) throw new SQLException("没有事务不能回滚!");
    78         con.rollback();
    79         con.close();
    80         con = null;
    81         tl.remove();
    82     }
    83     
    84     /**
    85      * 释放Connection
    86      * @param con
    87      * @throws SQLException 
    88      */
    89     public static void releaseConnection(Connection connection) throws SQLException {
    90         Connection con = tl.get();//获取当前线程的事务连接
    91         if(connection != con) {//如果参数连接,与当前事务连接不同,说明这个连接不是当前事务,可以关闭!
    92             if(connection != null &&!connection.isClosed()) {//如果参数连接没有关闭,关闭之!
    93                 connection.close();
    94             }
    95         }
    96     }
    97 }

     

     2.测试代码

    c3p0-config.xml

     1 <?xml version="1.0" encoding="UTF-8" ?>
     2 <c3p0-config>
     3     <default-config> 
     4         <property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb1</property>
     5         <property name="driverClass">com.mysql.jdbc.Driver</property>
     6         <property name="user">root</property>
     7         <property name="password">root</property>
     8  
     9         <property name="acquireIncrement">3</property>
    10         <property name="initialPoolSize">10</property>
    11         <property name="minPoolSize">2</property>
    12         <property name="maxPoolSize">10</property>
    13     </default-config>
    14 </c3p0-config>
     1 /**
     2  * 测试JdbcUtils类
     3  * 
     4  *
     5  */
     6 public class JdbcUtilsTest {
     7     /**
     8      * 通过C3P0连接池获取连接对象
     9      * @throws SQLException
    10      */
    11     @Test
    12     public void testGetConnection() throws SQLException {
    13         Connection con = JdbcUtils.getConnection();//获取连接
    14         System.out.println(con);
    15         JdbcUtils.releaseConnection(con);//如果参数con不是当前线程的连接对象,那么关闭之
    16     }
    17     
    18     /**
    19      * 当开始事务后,调用getConnection()会为当前线程创建Connection,而且多次调用getConnection()返回的是同一个对象
    20      * @throws SQLException 
    21      */
    22     @Test
    23     public void testTansaction() throws SQLException {
    24         JdbcUtils.beginTransaction();//开启事务
    25         Connection c1 = JdbcUtils.getConnection();//第一次获取当前线程的事务连接对象
    26         Connection c2 = JdbcUtils.getConnection();//第二次获取当前线程的事务连接对象
    27         Assert.assertEquals(true, c1 == c2);//比较两次是否相同
    28         JdbcUtils.commitTransaction();//提交事务
    29     }
    30 }

    =========================================================================================================================================

    3.TxQueryRunner

    TxQueryRunner类是common-dbutils下QueryRunner类的子类,用来简化JDBC操作。TxQueryRunner类内部使用了JdbcUtils.getConnection()类来获取连接对象,以及使用JdbcUtils.releaseConnection()关闭连接。

      l  int[] batch(String sql, Object[][] params):执行批处理,参数sql是SQL语句模板,params为参数;

      l  T query(String sql, ResultSetHandler<T> rh):执行查询,执行查询,参数sql为要执行的查询语句模板,rh是结果集处理,用来把结果集映射成你想要的结果;

      l  T query(String sql, ResultSetHandler<T> rh, Object… params):执行查询,参数sql为要执行的查询语句模板,rh是结果集处理,用来把结果集映射成你想要的结果,params是sql语句的参数;

      l  int update(String sql):执行增、删、改语句,参数sql是要执行的SQL语句;

      l  int update(Stringsql, Object param):执行增、删、改语句,参数sql是要执行的SQL语句,参数param是参数(一个参数);

      l  int update(String sql, Object… params):执行增、删、改语句,参数sql是要执行的SQL语句,参数params是参数(多个参数);

    为了测试TxQueryRunner,我们在mydb1数据库下创建t_person表,然后再创建Person实体类,以及PersonDao类,最后测试PersonDao类中的方法。

      1.源码

     1 package cn.itcast.jdbc;
     2 
     3 import java.sql.Connection;
     4 import java.sql.SQLException;
     5 
     6 import org.apache.commons.dbutils.QueryRunner;
     7 import org.apache.commons.dbutils.ResultSetHandler;
     8 
     9 public class TxQueryRunner extends QueryRunner {
    10 
    11     @Override
    12     public int[] batch(String sql, Object[][] params) throws SQLException {
    13         Connection con = JdbcUtils.getConnection();
    14         int[] result = super.batch(con, sql, params);
    15         JdbcUtils.releaseConnection(con);
    16         return result;
    17     }
    18 
    19     @Override
    20     public <T> T query(String sql, ResultSetHandler<T> rsh, Object... params)
    21             throws SQLException {
    22         Connection con = JdbcUtils.getConnection();
    23         T result = super.query(con, sql, rsh, params);
    24         JdbcUtils.releaseConnection(con);
    25         return result;
    26     }
    27     
    28     @Override
    29     public <T> T query(String sql, ResultSetHandler<T> rsh) throws SQLException {
    30         Connection con = JdbcUtils.getConnection();
    31         T result = super.query(con, sql, rsh);
    32         JdbcUtils.releaseConnection(con);
    33         return result;
    34     }
    35 
    36     @Override
    37     public int update(String sql) throws SQLException {
    38         Connection con = JdbcUtils.getConnection();
    39         int result = super.update(con, sql);
    40         JdbcUtils.releaseConnection(con);
    41         return result;
    42     }
    43 
    44     @Override
    45     public int update(String sql, Object param) throws SQLException {
    46         Connection con = JdbcUtils.getConnection();
    47         int result = super.update(con, sql, param);
    48         JdbcUtils.releaseConnection(con);
    49         return result;
    50     }
    51 
    52     @Override
    53     public int update(String sql, Object... params) throws SQLException {
    54         Connection con = JdbcUtils.getConnection();
    55         int result = super.update(con, sql, params);
    56         JdbcUtils.releaseConnection(con);
    57         return result;
    58     }
    59 }

      2.测试代码

    Person.java

    1 public class Person {
    2     private String pid;
    3     private String name;
    4     private int age;
    5     private Date birthday;
    6 7 }

    PersonDao.java

     1 /**
     2  * 测试TxQueryRunner
     3  *  4  *
     5  */
     6 public class PersonDao {
     7     private QueryRunner qr = new TxQueryRunner();
     8     
     9     public void add(Person person) throws SQLException {
    10         String sql = "insert into t_person values(?,?,?,?)";
    11         Object[] params = {person.getPid(), 
    12                 person.getName(), 
    13                 person.getAge(), 
    14                 new java.sql.Date(person.getBirthday().getTime())};
    15         qr.update(sql, params);
    16     }
    17     
    18     public void edit(Person person) throws SQLException {
    19         String sql = "update t_person set name=?,age=?,birthday=? where pid=?";
    20         Object[] params = { 
    21                 person.getName(), 
    22                 person.getAge(), 
    23                 new java.sql.Date(person.getBirthday().getTime()),
    24                 person.getPid()};
    25         qr.update(sql, params);
    26     }
    27     
    28     public void delete(String pid) throws SQLException {
    29         String sql = "delete from t_person where pid=?";
    30         qr.update(sql, pid);
    31     }
    32     
    33     public Person load(String pid) throws SQLException {
    34         String sql = "select * from t_person where pid=?";
    35         return qr.query(sql, new BeanHandler<Person>(Person.class), pid);
    36     }
    37     
    38     public List<Person> findAll() throws SQLException {
    39         String sql = "select * from t_person";
    40         return qr.query(sql, new BeanListHandler<Person>(Person.class));
    41     }
    42 }

    PersonDaoTest.java

     1 public class PersonDaoTest {
     2     @Test
     3     public void testAdd() throws SQLException {
     4         Person p1 = new Person(CommonUtils.uuid(), "张三", 18, new Date());
     5         Person p2 = new Person(CommonUtils.uuid(), "李四", 81, new Date());
     6         Person p3 = new Person(CommonUtils.uuid(), "王五", 66, new Date());
     7         
     8         PersonDao dao = new PersonDao();
     9         dao.add(p1);
    10         dao.add(p2);
    11         dao.add(p3);
    12     }
    13     
    14     @Test
    15     public void testEdit() throws SQLException {
    16         PersonDao dao = new PersonDao();
    17         Person person = dao.load("2F371BE415984DE89781CCCA7B8734CB");
    18         person.setAge(88);
    19         dao.edit(person);
    20     }
    21     
    22     @Test
    23     public void testDelete() throws SQLException {
    24         PersonDao dao = new PersonDao();
    25         dao.delete("2F371BE415984DE89781CCCA7B8734CB");
    26     }
    27     
    28     @Test
    29     public void testFindAll() throws SQLException {
    30         PersonDao dao = new PersonDao();
    31         List<Person> list = dao.findAll();
    32         System.out.println(list);
    33     }
    34 }

    =========================================================================================================================================

    4.MailUtils

    MailUtils是用来发邮件的小工具,底层使用JavaMail完成,依赖mail.jar和activaion.jar。

      1.源码

    MailUtils.java

     1 package cn.itcast.mail;
     2 
     3 import java.io.IOException;
     4 import java.util.List;
     5 import java.util.Properties;
     6 
     7 import javax.mail.Authenticator;
     8 import javax.mail.Message.RecipientType;
     9 import javax.mail.MessagingException;
    10 import javax.mail.PasswordAuthentication;
    11 import javax.mail.Session;
    12 import javax.mail.Transport;
    13 import javax.mail.internet.InternetAddress;
    14 import javax.mail.internet.MimeBodyPart;
    15 import javax.mail.internet.MimeMessage;
    16 import javax.mail.internet.MimeMultipart;
    17 import javax.mail.internet.MimeUtility;
    18 
    19 /**
    20  * 
    21  * @author itcast 本类只有这么一个方法,用来发邮件!
    22  */
    23 public class MailUtils {
    24     public static Session createSession(String host, final String username, final String password) {
    25         Properties prop = new Properties();
    26         prop.setProperty("mail.host", host);// 指定主机
    27         prop.setProperty("mail.smtp.auth", "true");// 指定验证为true
    28 
    29         // 创建验证器
    30         Authenticator auth = new Authenticator() {
    31             public PasswordAuthentication getPasswordAuthentication() {
    32                 return new PasswordAuthentication(username, password);
    33             }
    34         };
    35         
    36         // 获取session对象
    37         return Session.getInstance(prop, auth);
    38     }
    39     
    40     /**
    41      * 发送指定的邮件
    42      * 
    43      * @param mail
    44      */
    45     public static void send(Session session, final Mail mail) throws MessagingException,
    46             IOException {
    47 
    48         MimeMessage msg = new MimeMessage(session);// 创建邮件对象
    49         msg.setFrom(new InternetAddress(mail.getFrom()));// 设置发件人
    50         msg.addRecipients(RecipientType.TO, mail.getToAddress());// 设置收件人
    51 
    52         // 设置抄送
    53         String cc = mail.getCcAddress();
    54         if (!cc.isEmpty()) {
    55             msg.addRecipients(RecipientType.CC, cc);
    56         }
    57 
    58         // 设置暗送
    59         String bcc = mail.getBccAddress();
    60         if (!bcc.isEmpty()) {
    61             msg.addRecipients(RecipientType.BCC, bcc);
    62         }
    63 
    64         msg.setSubject(mail.getSubject());// 设置主题
    65 
    66         MimeMultipart parts = new MimeMultipart();// 创建部件集对象
    67 
    68         MimeBodyPart part = new MimeBodyPart();// 创建一个部件
    69         part.setContent(mail.getContent(), "text/html;charset=utf-8");// 设置邮件文本内容
    70         parts.addBodyPart(part);// 把部件添加到部件集中
    71         
    72         ///////////////////////////////////////////
    73 
    74         // 添加附件
    75         List<AttachBean> attachBeanList = mail.getAttachs();// 获取所有附件
    76         if (attachBeanList != null) {
    77             for (AttachBean attach : attachBeanList) {
    78                 MimeBodyPart attachPart = new MimeBodyPart();// 创建一个部件
    79                 attachPart.attachFile(attach.getFile());// 设置附件文件
    80                 attachPart.setFileName(MimeUtility.encodeText(attach
    81                         .getFileName()));// 设置附件文件名
    82                 String cid = attach.getCid();
    83                 if(cid != null) {
    84                     attachPart.setContentID(cid);
    85                 }
    86                 parts.addBodyPart(attachPart);
    87             }
    88         }
    89 
    90         msg.setContent(parts);// 给邮件设置内容
    91         Transport.send(msg);// 发邮件
    92     }
    93 }

    Mail.java  

      1 package cn.itcast.mail;
      2 
      3 import java.util.ArrayList;
      4 import java.util.List;
      5 
      6 /**
      7  * 表示邮件类,你需要设置:账户名和密码、收件人、抄送(可选)、暗送(可选)、主题、内容,以及附件(可选)
      8  * 
      9  * 在创建了Mail对象之后
     10  * 可以调用它的setSubject()、setContent(),设置主题和正文
     11  * 也可以调用setFrom()和 addToAddress(),设置发件人,和添加收件人。
     12  * 也可以调用addAttch()添加附件
     13  * 创建AttachBean:new AttachBean(new File("..."), "fileName");
     14  */
     15 public class Mail {
     16     private String from;//发件人
     17     private StringBuilder toAddress = new StringBuilder();//收件人
     18     private StringBuilder ccAddress = new StringBuilder();//抄送
     19     private StringBuilder bccAddress = new StringBuilder();//暗送
     20     
     21     private String subject;//主题
     22     private String content;//正文
     23     
     24     // 附件列表
     25     private List<AttachBean> attachList = new ArrayList<AttachBean>();
     26     
     27     public Mail() {}
     28     
     29     public Mail(String from, String to) {
     30         this(from, to, null, null);
     31     }
     32     
     33     public Mail(String from, String to, String subject, String content) {
     34         this.from = from;
     35         this.toAddress.append(to);
     36         this.subject = subject;
     37         this.content = content;
     38     }
     39     
     40     /**
     41      * 返回发件人
     42      * @return
     43      */
     44     public void setFrom(String from) {
     45         this.from = from;
     46     }
     47     
     48     /**
     49      * 返回发件人
     50      * @return
     51      */
     52     public String getFrom() {
     53         return from;
     54     }
     55     
     56     /**
     57      * 返回主题
     58      */
     59     public String getSubject() {
     60         return subject;
     61     }
     62 
     63     /**
     64      * 设置主题
     65      */
     66     public void setSubject(String subject) {
     67         this.subject = subject;
     68     }
     69 
     70     /**
     71      * 获取主题内容
     72      */
     73     public String getContent() {
     74         return content;
     75     }
     76 
     77     /**
     78      * 设置主题内容
     79      * @param content
     80      */
     81     public void setContent(String content) {
     82         this.content = content;
     83     }
     84 
     85     /**
     86      * 获取收件人
     87      * @return
     88      */
     89     public String getToAddress() {
     90         return toAddress.toString();
     91     }
     92 
     93     /**
     94      * 获取抄送
     95      * @return
     96      */
     97     public String getCcAddress() {
     98         return ccAddress.toString();
     99     }
    100 
    101     /**
    102      * 获取暗送
    103      * @return
    104      */
    105     public String getBccAddress() {
    106         return bccAddress.toString();
    107     }
    108 
    109     /**
    110      * 添加收件人,可以是多个收件人
    111      * @param to
    112      */
    113     public void addToAddress(String to) {
    114         if(this.toAddress.length() > 0) {
    115             this.toAddress.append(",");
    116         }
    117         this.toAddress.append(to);
    118     }
    119 
    120     /**
    121      * 添加抄送人,可以是多个抄送人
    122      * @param cc
    123      */
    124     public void addCcAddress(String cc) {
    125         if(this.ccAddress.length() > 0) {
    126             this.ccAddress.append(",");
    127         }
    128         this.ccAddress.append(cc);
    129     }
    130 
    131     /**
    132      * 添加暗送人,可以是多个暗送人
    133      * @param bcc
    134      */
    135     public void addBccAddress(String bcc) {
    136         if(this.bccAddress.length() > 0) {
    137             this.bccAddress.append(",");
    138         }
    139         this.bccAddress.append(bcc);
    140     }
    141     
    142     /**
    143      * 添加附件,可以添加多个附件
    144      * @param attachBean
    145      */
    146     public void addAttach(AttachBean attachBean) {
    147         this.attachList.add(attachBean);
    148     }
    149     
    150     /**
    151      * 获取所有附件
    152      * @return
    153      */
    154     public List<AttachBean> getAttachs() {
    155         return this.attachList;
    156     }
    157 }

    2.测试代码

     1     /**
     2      * 测试发送普通邮件
     3      * @throws IOException 
     4      * @throws MessagingException 
     5      */
     6     @Test
     7     public void fun() throws MessagingException, IOException {
     8         Session session = MailUtils.createSession("smtp.163.com", "itcast_cxf", "itcastitcast"); 
     9         Mail mail = new Mail("itcast_cxf@163.com", "itcast_cxf@126.com", "测试MailUtils", "这是正文!"); 
    10         MailUtils.send(session, mail); 
    11     }

    =========================================================================================================================================

    5.BaseServlet

      

      BaseServlet是用来作为其它Servlet父类的,它有如下两个优点:

      一个Servlet多个处理方法

        BaseServlet的作用是用来简化Servlet。通过我们需要为每个功能编写一个Servlet,例如用户注册写一个RegistServlet,用户登录写一个LoginServlet。如果使用BaseServlet,那么我们可以只写一个UserServlet,然后让UserServlet去继承BaseServlet,然后在UserServlet给出两个请求处理方法,一个方法叫regist(),一个叫login()。

        BaseServlet来简化了Servlet中请求转发和重定向的代码。

      简化了请求转发和重定向的代码

        BaseServlet中的请求处理方法有一个String类型的返回值,返回值表示转发或重定向的目标页面。例如:

        l  f:/index.jsp:其中f:表示转发,即forward的意思,/index.jsp表示转发到/index.jsp页面;

        l  r:/index.jsp:其中r:表示重定向,即redirect的意思,/index.jsp表示重定向到/index.jsp页面。

        l  null:表示不转发也不重定向;

        因为BaseServlet中可以有多个请求处理方法,所以在访问BaseServlet时一定要给出名为method的参数来指定要请求的方法名称。

    你还有很多未完成的梦,你有什么理由停下脚步
  • 相关阅读:
    AspNetCore打造一个“最安全”的api接口
    efcore分表分库原理解析
    对于经常接触的分页你确定你真的会吗
    Monitor的扩展支持string的超时锁
    Excel导出
    搭建私有Git服务器-GitLab
    C# 爬取网易Buff进行购买
    .Net Core 使用弹性和瞬态故障处理库Polly
    JS Table表格添加多选框
    JS 用户头像展示
  • 原文地址:https://www.cnblogs.com/quanziheng/p/13436127.html
Copyright © 2020-2023  润新知