數據庫——JavaWEB數據庫連接


一、數據庫連接的發展

1.數據庫連接

  用戶每次請求都需要向數據庫獲得鏈接,而數據庫創建連接通常需要消耗相對較大的資源,創建時間也較長。假設網站一天10萬訪問量,數據庫服務器就需要創建10萬次連接,極大的浪費數據庫的資源,並且極易造成數據庫服務器內存溢出、拓機。如下圖所示:

   

 

 

2.解決創建數據庫連接花費系統開銷浪費系統資源和可能產生數據庫服務器內存溢出和宕機的辦法

  ①數據庫連接池

    數據庫連接池負責分配,管理和釋放數據庫連接,它允許應用程序重復使用一個現有的數據庫連接,而不是重新建立一個

    

   數據庫連接池在初始化時將創建一定數量的數據庫連接放到連接池中, 這些數據庫連接的數量是由最小數據庫連接數來設定的.無論這些數據庫連接是否被使用,連接池都將一直保證至少擁有這么多的連接數量.連接池的最大數據庫連接數量限定了這個連接池能占有的最大連接數,當應用程序向連接池請求的連接數超過最大連接數量時,這些請求將被加入到等待隊列中.

  連接數量對服務器相應連接請求性能的影響:

  1. 最小連接數:是連接池一直保持的數據庫連接,所以如果應用程序對數據庫連接的使用量不大,將會有大量的數據庫連接資源被浪費.
  2. 最大連接數:是連接池能申請的最大連接數,如果數據庫連接請求超過次數,后面的數據庫連接請求將被加入到等待隊列中,這會影響以后的數據庫操作
  3. 如果最小連接數與最大連接數相差很大:那么最先連接請求將會獲利,之后超過最小連接數量的連接請求等價於建立一個新的數據庫連接.不過,這些大於最小連接數的數據庫連接在使用完不會馬上被釋放,他將被放到連接池中等待重復使用或是空間超時后被釋放.

  ②數據庫連接池的實現

    編寫連接池需實現java.sql.DataSource接口。DataSource接口中定義了兩個重載的  getConnection方法:

  • Connection getConnection()
  • Connection getConnection(String username, String password)

  實現DataSource接口,並實現連接池功能的步驟:

  1. 在DataSource構造函數中批量創建與數據庫的連接,並把創建的連接加入LinkedList對象中。
  2. 實現getConnection方法,讓getConnection方法每次調用時,從LinkedList中取一個Connection返回給用戶。
  3. 當用戶使用完Connection,調用Connection.close()方法時,Collection對象應保證將自己返回到LinkedList中,而不要把conn還給數據庫。Collection保證將自己返回到LinkedList中是此處編程的難點

  實現一(動態代理技術進行實現):

  

proxyConn = (Connection) Proxy.newProxyInstance(this.getClass().getClassLoader(),conn.getClass()
.getInterfaces(),new InvocationHandler(){ //內部類,當colse方法被調用時將conn還回池中,其他方法直接執行 public Object invoke(Object proxy,Method method,Object[] args)throws Throwable{    if(method.getName().equals("close")){   pool.addLast(conn);     return null;     }   return method.invoke(conn,args);   } }

  完整案例

  

import java.io.InputStram;
import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflet.Method;
import java.lang.reflet.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.Properties;
import java.sql.DataSource;

Public class JdbcPool implements DataSource{
  //LinkedList存儲數據庫連接對象
  private static LinkedList<Connection> listConnections = new LinkedList<Connection>();
  static{
    //在靜態代碼塊中加載db.properties數據庫配置文件
    InputStream in =
      JdbcPool.class.getClassLoader().getResourceAsStream("db.properties");
    Properties prop = new Properties();
    try{
      prop.load(in);
      String driver = prop.getProperty("driver);
      String url = prop.getProperty("url");
      String username = prop.getProperty("username");
      String password = prop.getProperty("password");
    //初始化連接池的大小
    int jdbcPoolInitSize = Interger.parseInt(prop.getProperty("JdbcInitialSize");
    //加載數據庫驅動
    class.forName(driver);
    for(int i = 0; i < jdbcPoolSize; i++){
      Connection conn = Drivermanager.getConnection(url,username,password);
      listConnections.add(conn);
    }
  }catch(Exception e){
    throw new ExceptionInInitializerError(e);
  }
 }
  @Override
58 public PrintWriter getLogWriter() throws SQLException {
59 // TODO Auto-generated method stub
60 return null;
61 }
62
63 @Override
64 public void setLogWriter(PrintWriter out) throws SQLException {
65 // TODO Auto-generated method stub
66
67 }
68
69 @Override
70 public void setLoginTimeout(int seconds) throws SQLException {
71 // TODO Auto-generated method stub
72
73 }
74
75 @Override
76 public int getLoginTimeout() throws SQLException {
77 // TODO Auto-generated method stub
78 return 0;
79 }
80
81 @Override
82 public <T> T unwrap(Class<T> iface) throws SQLException {
83 // TODO Auto-generated method stub
84 return null;
85 }
86
87 @Override
88 public boolean isWrapperFor(Class<?> iface) throws SQLException {
89 // TODO Auto-generated method stub
90 return false;
91 }

  //獲取數據庫連接
  @Override
  public Connection getConnection() throws SQLExcepion{
  //如果數據庫連接池中的連接對象的個數大於零
  if(listconnection.size()>0){
    final Connection conn = listConnections.removeFirst();
    System.out.println("listConnections連接池中還有”+listConnections.size();
    //返回Connection對象的代理對象
    return (Connection) Proxy.newProxyInstance(Jdbc.calss.getClassLoader(),
      conn.getClass().getInterfaces(),new InvocationHandler(){
    @Overrite
    pulbic Object invoke(Object proxy, Method method,Object[] args)throws Throwables{
      if(!method.getName().equals("close")){
        return method.invoke(conn,args);

      }else{
        //如果調用的是connection對象的close方法,就把conn還給數據庫連接池
        listConnections.add(conn);
        System.out.println("listConnections對象個數”+listConnections.seze());
        return null;
      }
  }
});
}else{
  throw new RuntimeException("對不起,數據庫庫忙”);
}
  @Overrite
  public Connection getConnection(String username,String password)
    throws SQLException{
    return null;
  }
}
}
1driver=com.mysql.jdbc.Driver
2 url=jdbc:mysql://localhost:3306/jdbcStudy
3 username=root
4 password=XDP
5 
6 jdbcPoolInitSize=10
3 import java.sql.Connection;
 4 import java.sql.ResultSet;
 5 import java.sql.SQLException;
 6 import java.sql.Statement;
 7 import me.gacl.demo.JdbcPool;
 8 
 9 public class JdbcUtil {
10     
11     /**
12     * @Field: pool
13     *          數據庫連接池
14     */ 
15     private static JdbcPool pool = new JdbcPool();
24     public static Connection getConnection() throws SQLException{
25         return pool.getConnection();
26     }
38     public static void release(Connection conn,Statement st,ResultSet rs){
39         if(rs!=null){
40             try{
41                 //關閉存儲查詢結果的ResultSet對象
42                 rs.close();
43             }catch (Exception e) {
44                 e.printStackTrace();
45             }
46             rs = null;
47         }
48         if(st!=null){
49             try{
50                 //關閉負責執行SQL命令的Statement對象
51                 st.close();
52             }catch (Exception e) {
53                 e.printStackTrace();
54             }
55         }
56         
57         if(conn!=null){
58             try{
59                 //關閉Connection數據庫連接對象
60                 conn.close();
61             }catch (Exception e) {
62                 e.printStackTrace();
63             }
64         }
65     }
66 }

二、支持數據庫連接的開源庫

1.DBCP和C3P0

   現在很多WEB服務器(Weblogic, WebSphere, Tomcat)都提供了DataSoruce的實現,即連接池的實現。通常我們把DataSource的實現,按其英文含義稱之為數據源,數據源中都包含了數據庫連接池的實現。
  也有一些開源組織提供了數據源的獨立實現:

  1.DBCP數據庫連接池

  2.C3P0數據庫連接池

  在使用了數據庫連接池之后,在項目的實際開發中就不需要編寫連接數據庫的代碼了,直接從數據源獲得數據庫的連接。

  ①DBCP數據源

  BCP 是 Apache 軟件基金組織下的開源連接池實現,要使用DBCP數據源,需要應用程序應在系統中增加如下兩個 jar 文件:

  Commons-dbcp.jar:連接池的實現

  Commons-pool.jar:連接池實現的依賴庫;

  Tomcat 的連接池正是采用該連接池來實現的。該數據庫連接池既可以與應用服務器整合使用,也可由應用程序獨立使用。

2.DBCP和C3P0的實現

  ①導入相關jar包

  comons-dbcp.jar、commons-pool.jar

  在類目錄下假如dbcp的配置文件:dbcpconfig.properties;

  

#連接設置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcstudy
username=root
password=XDP

#<!-- 初始化連接 -->
initialSize=10

#最大連接數量
maxActive=50

#<!-- 最大空閑連接 -->
maxIdle=20

#<!-- 最小空閑連接 -->
minIdle=5

#<!-- 超時等待時間以毫秒為單位 6000毫秒/1000等於60秒 -->
maxWait=60000


#JDBC驅動建立連接時附帶的連接屬性屬性的格式必須為這樣:[屬性名=property;] 
#注意:"user" 與 "password" 兩個屬性會被明確地傳遞,因此這里不需要包含他們。
connectionProperties=useUnicode=true;characterEncoding=UTF8

#指定由連接池所創建的連接的自動提交(auto-commit)狀態。
defaultAutoCommit=true

#driver default 指定由連接池所創建的連接的只讀(read-only)狀態。
#如果沒有設置該值,則“setReadOnly”方法將不被調用。(某些驅動並不支持只讀模式,如:Informix)
defaultReadOnly=

#driver default 指定由連接池所創建的連接的事務級別(TransactionIsolation)。
#可用值為下列之一:(詳情可見javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED

②在獲取數據庫連接的工具類(jdbcUTILS)的靜態代碼塊中創建池

import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Properties;
import java.sql.DataSources;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;

public class JdbcUtils_DBCP{
    private static DataSource ds = null;
    //在靜態代碼塊中創建連接池
   static{
            try{
          InputStram in = JdbcUtils_DBCP.class.getClassLoader()
            .getResourceAsStream("dbcpconfig.properties");
          Properties prop = new Properties();
          prop.load(in);
          //創建數據源
          ds = BasicDataSourceFactory.createDataSource(prop);
}catch(Exception e){
  throw new ExceptionInitialixerError(e);
} }
public static Connection getConnection() throws SQLException{
  //從數據源中獲得連接對象connectino
  return ds.getConnection();
}
//釋放資源:ResultSet,Statement,Connection;
public static void release(Connection conn,Statement st,ResultSet rs)
if(rs!=null){
      try{
        re.close();
}catch(Exception e){
  e.printStackTrace();
}
rs = null;
}


          if(sta!=null){
      try{
        re.close();
}catch(Exception e){
  e.printStackTrace();
}
sta= null;
}



          if(conn!=null){
      try{
        re.close();
}catch(Exception e){
  e.printStackTrace();
}
conn= null;
}
}
} }

 測試數據源

import JdbcUtils_DBCP;


public class DataSourceTest{
@Test
public void dbcpDataSourceTest(){
  Connection conn = null;
  PreparedStatement st = null;
  ResultSet rs = null;
  try{
    Connection conn = JdbcUtils_DBCP.getConnection();
    String sql = "insert into test1(name) values(?)";
    st = conn.getPreparedStatement(sql);
    st.setString(1,"values");
    //獲取數據庫自動生成的主鍵
    re = st.getGeneratedkeys();
    if(rs.next()){
      SysO(rs.getInt(1));
    }
    
}catch(Exception e){
  e.printStackTrace();
}fianlly{
JdbcUtils_CBCP.release(conn,st,re);
} } }

 ③C3P0數據源

  C3P0是一個開源的JDBC連接池,它實現了數據源和JNDI綁定,支持JDBC2的標准擴展

 目前使用它的開源項目有Hibernate.Spring等。C3P0數據源在項目開發中使用的比較多。

   c3p0與dbcp的區別是:dbcp沒有自動回收空閑連接的功能,C3P0有自動回收空閑連接的功能。

④C3P0數據源的實現

  1.導入相關jar包:c3p0-0.9.2-pre1.jar、mchange-commons-0.2.jar,

  如果是操作的是Oracle數據庫,還需要導入:c3p0-oracle-thin-extras-0.9.2-pre1.jar

   2.在類目錄下假如C3P0的配置文件:c3p0-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--
c3p0-config.xml必須位於類路徑下面
private static ComboPooledDataSource ds;
static{
    try {
        ds = new ComboPooledDataSource("MySQL");
    } catch (Exception e) {
        throw new ExceptionInInitializerError(e);
    }
}
-->

<c3p0-config>
    <!--
    C3P0的缺省(默認)配置,
    如果在代碼中“ComboPooledDataSource ds = new ComboPooledDataSource();”這樣寫就表示使用的是C3P0的缺省(默認)配置信息來創建數據源
    -->
    <default-config>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy</property>
        <property name="user">root</property>
        <property name="password">XDP</property>
        
        <property name="acquireIncrement">5</property>
        <property name="initialPoolSize">10</property>
        <property name="minPoolSize">5</property>
        <property name="maxPoolSize">20</property>
    </default-config>

    <!--
    C3P0的命名配置,
    如果在代碼中“ComboPooledDataSource ds = new ComboPooledDataSource("MySQL");”這樣寫就表示使用的是name是MySQL的配置信息來創建數據源
    -->
    <named-config name="MySQL">
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy</property>
        <property name="user">root</property>
        <property name="password">XDP</property>
        
        <property name="acquireIncrement">5</property>
        <property name="initialPoolSize">10</property>
        <property name="minPoolSize">5</property>
        <property name="maxPoolSize">20</property>
    </named-config>

</c3p0-config>

 

 

import java.sql.connection;
import java.sql.Statement;
import java.sql.SQLException;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class JdbcUtils_C3P0 { private static ComboPooledDataSource ds = null; //在靜態代碼塊中創建數據庫連接池 static{ try{ //通過代碼創建C3P0數據庫連接池 /*ds = new ComboPooledDataSource(); ds.setDriverClass("com.mysql.jdbc.Driver"); ds.setJdbcUrl("jdbc:mysql://localhost:3306/jdbcstudy"); ds.setUser("root"); ds.setPassword("XDP"); ds.setInitialPoolSize(10); ds.setMinPoolSize(5); ds.setMaxPoolSize(20);*/ //通過讀取C3P0的xml配置文件創建數據源,C3P0的xml配置文件c3p0-config.xml必須放在src目錄下 //ds = new ComboPooledDataSource();//使用C3P0的默認配置來創建數據源 ds = new ComboPooledDataSource("MySQL");//使用C3P0的命名配置來創建數據源 }catch (Exception e) { throw new ExceptionInInitializerError(e); } } /** * @Method: getConnection * @Description: 從數據源中獲取數據庫連接 * @Anthor:孤傲蒼狼 * @return Connection * @throws SQLException */ public static Connection getConnection() throws SQLException{ //從數據源中獲取數據庫連接 return ds.getConnection(); } /** * @Method: release * @Description: 釋放資源, * 釋放的資源包括Connection數據庫連接對象,負責執行SQL命令的Statement對象,存儲查詢結果的ResultSet對象 * @Anthor:孤傲蒼狼 * * @param conn * @param st * @param rs */ public static void release(Connection conn,Statement st,ResultSet rs){ if(rs!=null){ try{ //關閉存儲查詢結果的ResultSet對象 rs.close(); }catch (Exception e) { e.printStackTrace(); } rs = null; } if(st!=null){ try{ //關閉負責執行SQL命令的Statement對象 st.close(); }catch (Exception e) { e.printStackTrace(); } } if(conn!=null){ try{ //將Connection連接對象還給數據庫連接池 conn.close(); }catch (Exception e) { e.printStackTrace(); } } } }

測試,包括獲取數據源和釋放資源

import java.sql.connection;
import java.sql.statement;
import java.sql.ResultSet;
import org.junit.Test;
import c3p0,Dbcp;
public class DataSourceTest { @Test public void c3p0DataSourceTest() { Connection conn = null; PreparedStatement st = null; ResultSet rs = null; try{ //獲取數據庫連接 conn = JdbcUtils_C3P0.getConnection(); String sql = "insert into test1(name) values(?)"; st = conn.prepareStatement(sql); st.setString(1, "gacl"); st.executeUpdate(); //獲取數據庫自動生成的主鍵 rs = st.getGeneratedKeys(); if(rs.next()){ System.out.println(rs.getInt(1)); } }catch (Exception e) { e.printStackTrace(); }finally{ //釋放資源 JdbcUtils_C3P0.release(conn, st, rs); } } }

三、配置tomcat數據源

  1.在實際開發中,我們有時候還會使用服務器提供給我們的數據庫連接池,比如我們希望Tomcat服務器在啟動的時候可以幫我們創建一個數據庫連接池,那么我們在應用程序中就不需要手動去創建數據庫連接池,直接使用Tomcat服務器創建好的數據庫連接池即可。要想讓Tomcat服務器在啟動的時候幫我們創建一個數據庫連接池,那么需要簡單配置一下Tomcat服務器。

   Tomcat服務器創建的數據源是以JNDI資源的形式發布的,所以說在Tomat服務器中配置一個數據源實際上就是在配置一個JNDI資源,通過查看Tomcat文檔,我們知道使用如下的方式配置tomcat服務器的數據源:

  <Context>

    <Resource name = "jdbc/datasource" auth="Container"

          type = "javax.sql.DataSource" usernaem="root" password= "XDP"

          driverClassName="com.mysql.jdbc.Driver"

          url="jdbc:mysql://localhost:3306/jdbcs..."

          maxActive="8" maxIdle="4"/>

  </Context>

   服務器創建好數據源之后,我們的應用程序又該怎么樣得到這個數據源呢,Tomcat服務器創建好數據源之后是以JNDI的形式綁定到一個JNDI容器中的,我們可以把JNDI想象成一個大大的容器,我們可以往這個容器中存放一些對象,一些資源,JNDI容器中存放的對象和資源都會有一個獨一無二的名稱,應用程序想從JNDI容器中獲取資源時,只需要告訴JNDI容器要獲取的資源的名稱,JNDI根據名稱去找到對應的資源后返回給應用程序。我們平時做javaEE開發時,服務器會為我們的應用程序創建很多資源,比如request對象,response對象,服務器創建的這些資源有兩種方式提供給我們的應用程序使用:第一種是通過方法參數的形式傳遞進來,比如我們在Servlet中寫的doPost和doGet方法中使用到的request對象和response對象就是服務器以參數的形式傳遞給我們的。第二種就是JNDI的方式,服務器把創建好的資源綁定到JNDI容器中去,應用程序想要使用資源時,就直接從JNDI容器中獲取相應的資源即可。

  對於上面的name="jdbc/datasource"數據源資源,在應用程序中可以用如下的代碼去獲取

   Context initCtx = new Initialcontext();

  Context envCtx = (Context)initCtx.lookup("java:comp/env");

  dataSource = (DataSource)envCtx.lookup("jdbc/datasource"):

   此種配置下,數據庫的驅動jar文件需放置在tomcat的lib下

  

  2.配置tomcat數據源的實現

  ①在Web項目的WebRoot目錄下的META-INF目錄創建一個context.xml文件

  如下圖所示:

  

  配置Context.xml文件如下

1 <Context>
 2    <Resource 
 3        name="jdbc/datasource" 
 4        auth="Container"
 5        type="javax.sql.DataSource" 
 6        username="root" 
 7        password="XDP"
 8        driverClassName="com.mysql.jdbc.Driver" 
 9        url="jdbc:mysql://localhost:3306/jdbcstudy"
10        maxActive="8" 
11        maxIdle="4"/>
12 </Context>

  導入數據庫驅動的jar文件

  

獲取數據庫連接的工具類(如jdbcUtils)的靜態代碼塊中獲取JNDI容器中的數據源

  

package me.gacl.util;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

/**
* @ClassName: JdbcUtils_DBCP
* @Description: 數據庫連接工具類
* @author: 孤傲蒼狼
* @date: 2014-10-4 下午6:04:36
*
*/ 
public class JdbcUtils_JNDI {
    
    private static DataSource ds = null;
    //在靜態代碼塊中創建數據庫連接池
    static{
        try{
             //初始化JNDI
            Context initCtx = new InitialContext();
             //得到JNDI容器
            Context envCtx = (Context) initCtx.lookup("java:comp/env");
             //從JNDI容器中檢索name為jdbc/datasource的數據源
            ds = (DataSource)envCtx.lookup("jdbc/datasource");
        }catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }
    
    /**
    * @Method: getConnection
    * @Description: 從數據源中獲取數據庫連接
    * @Anthor:孤傲蒼狼
    * @return Connection
    * @throws SQLException
    */ 
    public static Connection getConnection() throws SQLException{
        //從數據源中獲取數據庫連接
        return ds.getConnection();
    }
    
    /**
    * @Method: release
    * @Description: 釋放資源,
    * 釋放的資源包括Connection數據庫連接對象,負責執行SQL命令的Statement對象,存儲查詢結果的ResultSet對象
    * @Anthor:孤傲蒼狼
    *
    * @param conn
    * @param st
    * @param rs
    */ 
    public static void release(Connection conn,Statement st,ResultSet rs){
        if(rs!=null){
            try{
                //關閉存儲查詢結果的ResultSet對象
                rs.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
            rs = null;
        }
        if(st!=null){
            try{
                //關閉負責執行SQL命令的Statement對象
                st.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
        
        if(conn!=null){
            try{
                //將Connection連接對象還給數據庫連接池
                conn.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

測試配置和數據源

package me.gacl.test;

import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import me.gacl.util.JdbcUtils_JNDI;

public class JNDITest extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;
        try{
            //獲取數據庫連接
            conn = JdbcUtils_JNDI.getConnection();
            String sql = "insert into test1(name) values(?)";
            st = conn.prepareStatement(sql);
            st.setString(1, "gacl");
            st.executeUpdate();
            //獲取數據庫自動生成的主鍵
            rs = st.getGeneratedKeys();
            if(rs.next()){
                System.out.println(rs.getInt(1));
            }
        }catch (Exception e) {
            e.printStackTrace();
        }finally{
            //釋放資源
            JdbcUtils_JNDI.release(conn, st, rs);
        }
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }

}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM