Maison  >  Article  >  Java  >  Explication détaillée du didacticiel de mise à niveau pour l'enregistrement de la connexion utilisateur basé sur le modèle de développement Servlet+JSP+JavaBean

Explication détaillée du didacticiel de mise à niveau pour l'enregistrement de la connexion utilisateur basé sur le modèle de développement Servlet+JSP+JavaBean

巴扎黑
巴扎黑original
2017-08-10 15:37:212395parcourir


Nous avions l'habitude de créer des fichiers XML représentant des bases de données pour enregistrer les informations des utilisateurs. Maintenant que nous avons acquis des connaissances liées aux bases de données, nous devrions remplacer XML par des bases de données et passer aux applications de bases de données.
Lorsque nous avons copié le projet précédent, en supposant que le nom du projet précédent était day09_user, faites maintenant une copie et copiez-le, renommez le projet en day14_user, et déployez-le maintenant directement sur le serveur Tomcat, puis le day14_user JavaWeb Le répertoire virtuel mappé par l'application est toujours "/day09_user" et n'est pas mappé à un répertoire virtuel du même nom "/day14_user". C'est un problème qui est souvent négligé. Pour résoudre ce problème, vous pouvez procéder comme suit :
Explication détaillée du didacticiel de mise à niveau pour lenregistrement de la connexion utilisateur basé sur le modèle de développement Servlet+JSP+JavaBean
Explication détaillée du didacticiel de mise à niveau pour lenregistrement de la connexion utilisateur basé sur le modèle de développement Servlet+JSP+JavaBean

Mise à niveau vers l'application de base de données

  • Importer le pilote de base de données
    Explication détaillée du didacticiel de mise à niveau pour lenregistrement de la connexion utilisateur basé sur le modèle de développement Servlet+JSP+JavaBean

  • Créer des bibliothèques et des tables correspondantes pour l'application

    create database day14_user;use day14_user;create table users
    (
        id varchar(40) primary key,
        username varchar(40) not null unique,
        password varchar(40) not null,
        email varchar(100) not null unique,
        birthday date,
        nickname varchar(40) not null);
    • 1

    • 2

    • 3

    • 4

    • 5

    • 6

    • 7

    • 8

    • 9

    • 10

    • 11

  • Créez un fichier db.properties dans le répertoire src, comme indiqué ci-dessous :
    Explication détaillée du didacticiel de mise à niveau pour lenregistrement de la connexion utilisateur basé sur le modèle de développement Servlet+JSP+JavaBean
    En écriture les informations de connexion de la base de données MySQL dans db.properties Le code est le suivant :

    driver=com.mysql.jdbc.Driver
    url=jdbc:mysql://localhost:3306/day14_userusername=root
    password=yezi#driver=oracle.jdbc.driver.OracleDriver#url=jdbc:oracle:thin:@localhost:1521:orcl#username=system#password=111
    • 1

    • 2.

    • 3

    • 4

    • 5

    • 6

    • 7

    • 8

    • 9

    • 10

    • 11

    • 12

    • 13

    • 14

    • 15

    • 16

    • 17

Réécrire UserDao

Nous devons maintenant réécrire toutes les méthodes de l'interface UserDao et créer une classe d'implémentation de l'interface UserDao - UserDaoJdbcImpl - dans le cn.itcast.dao. impl package. Comme indiqué ci-dessous :
Explication détaillée du didacticiel de mise à niveau pour lenregistrement de la connexion utilisateur basé sur le modèle de développement Servlet+JSP+JavaBean
Étant donné que ce que nous avons écrit au début n'est pas le code final, nous nous concentrerons sur la méthode de connexion public User find(String username, String password).
Nous utilisons d'abord l'objet instruction pour réécrire la méthode public User find(String username, String password). Le code est le suivant :

@Override
public User find(String username, String password) {
    Connection conn = null;
    Statement st = null; // PreparedStatement预防SQL注入
    ResultSet rs = null;
    try {
        conn = JdbcUtils.getConnection();        st = conn.createStatement();

        String sql = "select * from users where username='"+username+"' and password='"+password+"'";
        rs = st.executeQuery(sql);
        if (rs.next()) {
            User user = new User();
            user.setBirthday(rs.getDate("birthday"));
            user.setEmail(rs.getString("email"));
            user.setId(rs.getString("id"));
            user.setNickname(rs.getString("nickname"));
            user.setPassword(rs.getString("password"));
            user.setUsername(rs.getString("username"));
            return user;
        }
        return null;
    } catch (Exception e) {
        // 异常直接往上面抛,除了给上层带来麻烦,没有任何好处,所以可将异常转型然后往业务层抛
        throw new RuntimeException(e);
    } finally {
        JdbcUtils.release(conn, st, rs);
    }
}1234567891011121314151617181920212223242526272829

La gestion des exceptions en Java est vraiment ennuyeuse.
Dans la communauté académique, il y a beaucoup de querelles sur la gestion des exceptions Java. Elle est principalement divisée en trois grandes écoles :

  • Concepteur de langage Java - Commandant Gao, le Le langage Java qu'il a conçu est constitué d'exceptions au moment de la compilation et d'exceptions au moment de l'exécution. Puisque le langage Java a été conçu par lui, il estime que les exceptions au moment de la compilation doivent également exister raisonnablement.

  • L'auteur de "Thinking In Java" estime que les exceptions au moment de la compilation du langage Java sont des ordures et que les exceptions doivent être converties en exceptions d'exécution et rejetées.

  • L'auteur de Spring a adouci les opinions des deux personnes ci-dessus. Il a dit : vous avez un programme avec des exceptions. Si vous obtenez cette exception, que devez-vous faire ? Cela dépend simplement de savoir si le programme de niveau supérieur peut le gérer ? Si elle ne peut pas être gérée, elle sera convertie en exception d'exécution et levée. Si elle peut être gérée, elle sera convertie en exception de compilation et levée directement.

Il est donc préférable que nous adoptions le point de vue de l'auteur de Spring, alors notre méthode de traitement est la suivante : Vous avez une exception dans votre programme. , que dois-tu faire ? Cela dépend simplement si vous souhaitez que la couche supérieure du programme gère l'exception ? Si vous ne voulez pas que le programme de niveau supérieur le gère, afin de ne pas causer de problèmes au programme de niveau supérieur, transformez-le en exception d'exécution et lancez-le. Si vous souhaitez que le programme de niveau supérieur le gère, transformez-le en une exception au moment de la compilation et lancez-le directement. Accédez à .
Dans le développement réel, il est préférable d'écrire une exception personnalisée pour chaque couche, comme la personnalisation d'une classe d'exception - DaoException dans la couche Dao (couche d'accès aux données).
Créez la classe d'exception DaoException dans le package cn.itcast.exception, comme suit :
Explication détaillée du didacticiel de mise à niveau pour lenregistrement de la connexion utilisateur basé sur le modèle de développement Servlet+JSP+JavaBean
Le code de la classe DaoException est le suivant :

public class DaoException extends RuntimeException {    public DaoException() {        // TODO Auto-generated constructor stub
    }    public DaoException(String message) {        super(message);        // TODO Auto-generated constructor stub
    }    public DaoException(Throwable cause) {        super(cause);        // TODO Auto-generated constructor stub
    }    public DaoException(String message, Throwable cause) {        super(message, cause);        // TODO Auto-generated constructor stub
    }    public DaoException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {        super(message, cause, enableSuppression, writableStackTrace);        // TODO Auto-generated constructor stub
    }

}123456789101112131415161718192021222324252627

Nous souhaitons personnaliser une exception Dao lance Go, pourquoi ? Le plus grand avantage de la personnalisation d'une exception Dao à lancer est que je lance cette exception. Lorsque quelqu'un reçoit cette exception, dès qu'il voit le nom de classe de l'exception, il peut savoir quelle couche pose problème et il peut la localiser rapidement. couche pour trouver le problème. Il est préférable d'avoir une exception personnalisée pour chaque couche.
Modifiez ensuite la méthode public User find(String username, String password) dans la classe UserDaoJdbcImpl en :

@Override
public User find(String username, String password) {
    Connection conn = null;
    Statement st = null; // PreparedStatement预防SQL注入
    ResultSet rs = null;
    try {
        conn = JdbcUtils.getConnection();        st = conn.createStatement();

        String sql = "select * from users where username='"+username+"' and password='"+password+"'";
        rs = st.executeQuery(sql);
        if (rs.next()) {
            User user = new User();
            user.setBirthday(rs.getDate("birthday"));
            user.setEmail(rs.getString("email"));
            user.setId(rs.getString("id"));
            user.setNickname(rs.getString("nickname"));
            user.setPassword(rs.getString("password"));
            user.setUsername(rs.getString("username"));
            return user;
        }
        return null;
    } catch (Exception e) {
        // 异常直接往上面抛,除了给上层带来麻烦,没有任何好处,所以可将异常转型然后往业务层抛        /*
         * 自定义一个Dao异常抛出去,为什么呢?
         * 自定义一个Dao异常抛出去最大的好处就是我把这个异常抛出去,人家收到这个异常,一看到这个异常的类名,
         * 就能知道到底是哪一层出问题了,他就可以快速定位到这一层来找问题。最好每一层都要有一个自定义异常。
         */
        throw new DaoException(e);
    } finally {
        JdbcUtils.release(conn, st, rs);
    }
}1234567891011121314151617181920212223242526272829303132333435

实现service层和dao层的解耦

我们以前在开发service层(service层对web层提供所有的业务服务)时,编写UserService接口的具体实现类——UserServiceImpl时,业务逻辑层和数据访问层是紧密联系在一起的,所以业务逻辑层和数据访问层要解耦(希望底层Dao层代码换了,业务逻辑层的代码一行都不改,这时要用到工厂设计模式),要解耦,有两种方法:

  • 工厂模式

  • Spring

现在我们重点关注工厂模式。这时我们要定义一个Dao工厂,新建一个cn.itcast.factory包,在包中创建一个Dao工厂——DaoFactory,如下所示:
Explication détaillée du didacticiel de mise à niveau pour lenregistrement de la connexion utilisateur basé sur le modèle de développement Servlet+JSP+JavaBean
工厂一般要设计成单例的,为什么呢?
答:工厂设计成单例的,工厂的对象在内存中只有一个,目的是希望所有的Dao都由一个工厂来生产。假设不把工厂设计成单例的,将来Dao由不同的工厂来生产,你觉得所有的Dao由一个工厂来生产好,还是由不同的工厂来生产好?答案显然是由一个工厂来生产好,将来维护起来好维护。
我们先编写DaoFactory类的代码为:

public class DaoFactory {    private Properties daoConfig = new Properties();    private DaoFactory() {}    private static DaoFactory instance = new DaoFactory();    public static DaoFactory getInstance() {        return instance;
    }    //                      UserDao.class(接口类型)    //                      DepartmentDao.class(接口类型)       public <T> T createDao(Class<T> clazz) {        // clazz.getName(); // 返回UserDao接口的完整名称:cn.itcast.dao.UserDao
        String name = clazz.getSimpleName(); // 返回UserDao接口的简单名称:UserDao

        DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties");
        ......
    }
}123456789101112131415161718192021

以上的代码好吗?显然这样做并不好,每次别人调用createDao方法,都会去读一下配置文件dao.properties,但此配置文件在整个系统里面只要读取一次就好,没必要老去读,即以下这行代码只需运行一次。

DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties");1

那么这行代码可以放到静态代码块里。
现在将以上代码修改为:

public class DaoFactory {    static {
        DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties");
    }    private Properties daoConfig = new Properties();    private DaoFactory() {}    private static DaoFactory instance = new DaoFactory();    public static DaoFactory getInstance() {        return instance;
    }    //                      UserDao.class(接口类型)    //                      DepartmentDao.class(接口类型)       public <T> T createDao(Class<T> clazz) {        // clazz.getName(); // 返回UserDao接口的完整名称:cn.itcast.dao.UserDao
        String name = clazz.getSimpleName(); // 返回UserDao接口的简单名称:UserDao        // DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties");
        ......
    }
}12345678910111213141516171819202122232425

以上这样做并没有不好,但是对于现在而言还有一种做法,DaoFactory这个类被设计成单例的,所以其构造函数仅执行一次,所以可放到DaoFactory这个类的构造函数里面。所以最后完整的代码如下:

public class DaoFactory {    /*
    static {
        DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties");
    }
    */    private Properties daoConfig = new Properties();    private DaoFactory() {
        InputStream in = DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties");        try {
            daoConfig.load(in);
        } catch (IOException e) {            throw new RuntimeException(e);
        }
    }    private static DaoFactory instance = new DaoFactory();    public static DaoFactory getInstance() {        return instance;
    }    //                      UserDao.class(接口类型)    //                      DepartmentDao.class(接口类型)       public <T> T createDao(Class<T> clazz) {        // clazz.getName(); // 返回UserDao接口的完整名称:cn.itcast.dao.UserDao
        String name = clazz.getSimpleName(); // 返回UserDao接口的简单名称:UserDao        // DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties");

        String className = daoConfig.getProperty(name);        try {
            T dao = (T) Class.forName(className).newInstance(); // 创建实例对象            return dao;
        } catch (Exception e) {            throw new RuntimeException(e);
        }
    }
}123456789101112131415161718192021222324252627282930313233343536373839

然后在src目录下创建一个dao.properties文件,如下图所示:
Explication détaillée du didacticiel de mise à niveau pour lenregistrement de la connexion utilisateur basé sur le modèle de développement Servlet+JSP+JavaBean  
在db.properties中内容如下所示:

UserDao=cn.itcast.dao.impl.UserDaoJdbcImpl1

实现service层和dao层的解耦,UserService接口的具体实现类——UserServiceImpl只须修改为:

// 对web层提供所有的业务服务public class BusinessServiceImpl {    /*
     * 业务逻辑层和数据访问层要解耦——希望底层Dao层代码换了,业务逻辑层的代码一行都不改,这时要用到工厂设计模式
     * 要解耦,有两张方法:
     * 1. 工厂模式
     * 2. spring
     */    // private UserDao dao = new UserDaoJdbcImpl();    private UserDao dao = DaoFactory.getInstance().createDao(UserDao.class);    // 对web层提供注册服务    public void register(User user) throws UserExistException {        // 先判断当前要注册的用户是否存在        boolean b = dao.find(user.getUsername());        if(b) {            /*
             * service层是由web层来调用的,
             * 发现当前要注册的用户已存在,要提醒给web层,web层给用户一个友好提示
             * 希望web层一定要处理,处理之后给用户一个友好提示,所以抛一个编译时异常,
             * 抛运行时异常是不行的,因为web层可处理可不处理
             */            throw new UserExistException(); // 发现要注册的用户已存在,则给web层抛一个编译时异常,提醒web层处理这个异常,给用户一个友好提示。
        } else {
            user.setPassword(ServiceUtils.md5(user.getPassword()));
            dao.add(user);
        }
    }    // 对web层提供登录服务    public User login(String username, String password) { // aaa 123
        password = ServiceUtils.md5(password); // 要把密码md5一把再找        return dao.find(username, password);
    }
}123456789101112131415161718192021222324252627282930313233343536

防范sql注入攻击

SQL注入是用户利用某些系统没有对输入数据进行充分的检查,从而进行恶意破坏的行为。
例如,statement存在sql注入攻击问题,假如登录用户名采用' or 1=1 or name='
Explication détaillée du didacticiel de mise à niveau pour lenregistrement de la connexion utilisateur basé sur le modèle de développement Servlet+JSP+JavaBean

对于防范SQL注入,可以采用PreparedStatement取代Statement。

PreparedStatement对象介绍

PreperedStatement是Statement的孩子,它的实例对象可以通过调用Connection.preparedStatement()方法获得,相对于Statement对象而言:

  • PreperedStatement可以避免SQL注入的问题。

  • Statement会使数据库频繁编译SQL,可能造成数据库缓冲区溢出。PreparedStatement可对SQL进行预编译,从而提高数据库的执行效率。

  • 并且PreperedStatement对于sql中的参数,允许使用占位符的形式进行替换,简化sql语句的编写。

使用PreparedStatement改写UserDaoJdbcImpl类的public User find(String username, String password)方法。

@Override
public User find(String username, String password) {
    Connection conn = null;
    PreparedStatement st = null; // PreparedStatement预防SQL注入
    ResultSet rs = null;
    try {
        conn = JdbcUtils.getConnection();
        String sql = "select * from users where username=? and password=?";        st = conn.prepareStatement(sql); // 预编译这条sql语句        st.setString(1, username);        st.setString(2, password);

        rs = st.executeQuery();
        if (rs.next()) {
            User user = new User();
            user.setBirthday(rs.getDate("birthday"));
            user.setEmail(rs.getString("email"));
            user.setId(rs.getString("id"));
            user.setNickname(rs.getString("nickname"));
            user.setPassword(rs.getString("password"));
            user.setUsername(rs.getString("username"));
            return user;
        }
        return null;
    } catch (Exception e) {
        // 异常直接往上面抛,除了给上层带来麻烦,没有任何好处,所以可将异常转型然后往业务层抛
        throw new DaoException(e);
    } finally {
        JdbcUtils.release(conn, st, rs);
    }
}12345678910111213141516171819202122232425262728293031

到此为止,我们就可以完整地写出UserDaoJdbcImpl类了,其完整代码如下:

public class UserDaoJdbcImpl implements UserDao {    @Override    public void add(User user) {
        Connection conn = null;        // Statement st = null;
        PreparedStatement st = null;
        ResultSet rs = null;        try {
            conn = JdbcUtils.getConnection();
            String sql = "insert into users(id,username,password,email,birthday,nickname) values(?,?,?,?,?,?)";
            st = conn.prepareStatement(sql);
            st.setString(1, user.getId());
            st.setString(2, user.getUsername());
            st.setString(3, user.getPassword());
            st.setString(4, user.getEmail());
            st.setDate(5, new java.sql.Date(user.getBirthday().getTime()));
            st.setString(6, user.getNickname());            // st = conn.createStatement();            // String sql = "insert into users(id,username,password,email,birthday,nickname) values(&#39;"+user.getId()+"&#39;,&#39;"+user.getUsername()+"&#39;,&#39;"+user.getPassword()+"&#39;,&#39;"+user.getEmail()+"&#39;,&#39;"+user.getBirthday().toLocaleString()+"&#39;,&#39;"+user.getNickname()+"&#39;)";            // int num = st.executeUpdate(sql);            int num = st.executeUpdate();            if(num < 1) {                throw new RuntimeException("注册用户失败!!!");
            }
        } catch (Exception e) {            // 异常直接往上面抛,除了给上层带来麻烦,没有任何好处,所以可将异常转型然后往业务层抛            /*
             * 自定义一个Dao异常抛出去,为什么呢?
             * 自定义一个Dao异常抛出去最大的好处就是我把这个异常抛出去,人家收到这个异常,一看到这个异常的类名,
             * 就能知道到底是哪一层出问题了,他就可以快速定位到这一层来找问题。最好每一层都要有一个自定义异常。
             */            /*
             * gosling    Thinking In Java   Spring
             * Spring作者——你这个有程序有异常,你拿到这个异常,怎么做呢?就看上一层程序能不能处理?
             *            如果不能处理,就转为运行时异常抛出去,如果能处理,就转为编译时异常直接往上抛出去。
             *            
             * 我们的处理方法——你这个有程序有异常,你拿到这个异常,怎么做呢?就看异常你希不希望上一层程序处理?
             *            如果你不希望上一层程序处理,免得给上一层程序带来麻烦,就转为运行时异常抛出去,如果你希望上一层程序处理,就转为编译时异常直接往上抛出去。
             */            // throw new RuntimeException(e);            throw new DaoException(e);
        } finally {
            JdbcUtils.release(conn, st, rs);
        }
    }    // username=&#39;or 1=1 or username=&#39;   password=""    @Override    public User find(String username, String password) {
        Connection conn = null;
        PreparedStatement st = null; // PreparedStatement预防SQL注入
        ResultSet rs = null;        try {
            conn = JdbcUtils.getConnection();
            String sql = "select * from users where username=? and password=?";
            st = conn.prepareStatement(sql); // 预编译这条sql语句
            st.setString(1, username);
            st.setString(2, password);

            rs = st.executeQuery();            if (rs.next()) {
                User user = new User();
                user.setBirthday(rs.getDate("birthday"));
                user.setEmail(rs.getString("email"));
                user.setId(rs.getString("id"));
                user.setNickname(rs.getString("nickname"));
                user.setPassword(rs.getString("password"));
                user.setUsername(rs.getString("username"));                return user;
            }            return null;
        } catch (Exception e) {            // 异常直接往上面抛,除了给上层带来麻烦,没有任何好处,所以可将异常转型然后往业务层抛            throw new DaoException(e);
        } finally {
            JdbcUtils.release(conn, st, rs);
        }
    }    @Override    public boolean find(String username) {
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;        try {
            conn = JdbcUtils.getConnection();
            String sql = "select * from users where username=?";
            st = conn.prepareStatement(sql);
            st.setString(1, username);

            rs = st.executeQuery();            if (rs.next()) {                return true;
            }            return false;
        } catch (Exception e) {            // 异常直接往上面抛,除了给上层带来麻烦,没有任何好处,所以可将异常转型然后往业务层抛            throw new DaoException(e);
        } finally {
            JdbcUtils.release(conn, st, rs);
        }
    }

}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108

面试题:Statement和PreparedStatement的区别。
答:

  1. PreparedStatement是Statement的孩子。

  2. PreparedStatement可以防止sql注入的问题。

  3. PreparedStatement会对sql语句进行预编译,以减轻数据库服务器的压力。

就像xxx.javaxxx.classJVM执行一样,sql语句编译数据库执行
开发完数据访问层,一定要对程序已编写好的部分代码进行测试,做一步,测试一步,以免整个程序完成后由于页面太多或者是代码量太大给查找错误造成更大的负担!所以,在juint.test包下创建了一个UserDaoJdbcTest类。  
Explication détaillée du didacticiel de mise à niveau pour lenregistrement de la connexion utilisateur basé sur le modèle de développement Servlet+JSP+JavaBean
UserDaoJdbcTest类的具体代码如下:

public class UserDaoJdbcTest {
    @Test    public void testAdd() {
        User user = new User();
        user.setBirthday(new Date());
        user.setEmail("bb@sina.com");
        user.setId("2142354354");
        user.setNickname("李子");
        user.setUsername("bbbb");
        user.setPassword("123");

        UserDao dao = new UserDaoJdbcImpl();
        dao.add(user);
    }

    @Test    public void testFind() {
        UserDao dao = new UserDaoJdbcImpl();
        User user = dao.find("bbbb", "123"); // 在断点模式Watch
        System.out.println(user);
    }

    @Test    public void testFindByUsername() {
        UserDao dao = new UserDaoJdbcImpl();
        System.out.println(dao.find("bbbb"));
    }
}12345678910111213141516171819202122232425262728

经测试,没发现任何错误。同样也要对业务逻辑层已编写好的部分代码进行测试,在juint.test包下创建了一个ServiceTest类。
Explication détaillée du didacticiel de mise à niveau pour lenregistrement de la connexion utilisateur basé sur le modèle de développement Servlet+JSP+JavaBean
ServiceTest类的具体代码如下:

public class ServiceTest {

    @Test    public void testRegister() {
        User user = new User();
        user.setBirthday(new Date());
        user.setEmail("bb@sina.com");
        user.setId("2142354354");
        user.setNickname("李子");
        user.setUsername("lizi");
        user.setPassword("123");

        BusinessServiceImpl service = new BusinessServiceImpl();        try {
            service.register(user);
            System.out.println("注册成功!!!");
        } catch (UserExistException e) {
            System.out.println("用户已存在");
        }
    }

    @Test    public void testLogin() {
        BusinessServiceImpl service = new BusinessServiceImpl();
        User user = service.login("lizi", "123");
        System.out.println(user);
    }
}12345678910111213141516171819202122232425262728

经测试,没发现任何错误。
到此,对基于Servlet+JSP+JavaBean开发模式的用户登录注册的升级改造圆满完成。


Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn