SpringBoot:整合Druid、MyBatis


SpringBoot 整合Druid、MyBatis

簡介

對於數據訪問層,無論是 SQL(關系型數據庫) 還是 NOSQL(非關系型數據庫),Spring Boot 底層都是采用 Spring Data 的方式進行統一處理。

Spring Boot 底層都是采用 Spring Data 的方式進行統一處理各種數據庫,Spring Data 也是 Spring 中與 Spring Boot、Spring Cloud 等齊名的知名項目。

Sping Data 官網

數據庫相關的啟動器 : 可以參考官方文檔

JDBC

導入依賴

<!--jdbc-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--web-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mysql驅動-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

連接數據庫

首先我們需要連接數據庫,我們直接使用IDEA連接即可

在SpringBoot中,我們需要連接數據庫,只需要簡單的配置即可

在 application.yaml 中:

spring:
  datasource:
    username: root
    password: root
    # serverTimezone=UTC:MySQL 8+以上使用
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=ture&characterEncoding=UTF-8&serverTimezone=UTC
     # mysql 8+以上使用  
    driver-class-name: com.mysql.cj.jdbc.Driver    

20200227084116.png

配置完這一些東西后,我們就可以直接去使用了,因為SpringBoot已經默認幫我們進行了自動配置;我們去測試類測試一下

@SpringBootTest
class Springboot03DataApplicationTests {

    @Autowired
    DataSource dataSource;

    @Test
    void contextLoads() throws SQLException {
        // 查看一下默認的數據源
        System.out.println(dataSource.getClass());
        System.out.println("==========");
				// 獲得連接
        Connection connection = dataSource.getConnection();
        System.out.println(connection);

        // 歸還連接
        connection.close();
    }
}

輸出結果:

class com.zaxxer.hikari.HikariDataSource

2020-02-24 16:46:56.963  INFO 19388 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2020-02-24 16:46:59.953  INFO 19388 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
HikariProxyConnection@1336418989 wrapping com.mysql.cj.jdbc.ConnectionImpl@597f0937

從輸出結果中可以看出,SpringBoot默認的數據源是:com.zaxxer.hikari.HikariDataSource,我們並沒有手動配置。

Spring Boot 2.1.7 默認使用 com.zaxxer.hikari.HikariDataSource 數據源,而以前版本,如 Spring Boot 1.5 默認使用 org.apache.tomcat.jdbc.pool.DataSource 作為數據源;

HikariDataSource 號稱 Java WEB 當前速度最快的數據源,相比於傳統的 C3P0 、DBCP、Tomcat jdbc 等連接池更加優秀;

CRUD操作

  1. 有了數據源(com.zaxxer.hikari.HikariDataSource),就可以拿到數據庫連接(java.sql.Connection),有了連接,就可以使用原生的 JDBC 語句來操作數據庫。
  2. 即使不使用第三方的數據庫操作框架,如MyBatis等,Spring 本身也對原生的 JDBC 做了輕量級的封裝,即:org.springframework.jdbc.core.JdbcTemplate
  3. 數據庫操作的所有 CRUD 方法都在 JdbcTemplate 中。
  4. Spring Boot 不僅提供了默認的數據源,同時默認已經配置好了 JdbcTemplate 放在了容器中,程序員只需自己注入即可使用
  5. JdbcTemplate 的自動配置原理是依賴 org.springframework.boot.autoconfigure.jdbc 包下的 org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration

JdbcTemplate主要提供以下幾類方法:

  • execute方法:可以用於執行任何SQL語句,一般用於執行DDL語句;
  • update方法及batchUpdate方法:update方法用於執行新增、修改、刪除等語句;batchUpdate方法用於執行批處理相關語句;
  • query方法及queryForXXX方法:用於執行查詢相關語句;
  • call方法:用於執行存儲過程、函數相關語句。

測試:

@RestController
public class JdbcController {
    // JdbcTemplate 是 core 包的核心類,用於簡化 JDBC操作,還能避免一些常見的錯誤,如忘記關閉數據庫連接
    // Spring Boot 默認提供了數據源,默認提供了 org.springframework.jdbc.core.JdbcTemplate
    // JdbcTemplate 中會自己注入數據源,使用起來也不用再自己來關閉數據庫連接
    @Autowired
    JdbcTemplate jdbcTemplate;

    @GetMapping("/userList")
    public List<Map<String, Object>> userList() {

        String sql = "select id, name, pwd from mybatis.user";

        List<Map<String, Object>> list_maps = jdbcTemplate.queryForList(sql);

        return list_maps;
    }


    @GetMapping("/updUser/{id}")
    public String updateUser(@PathVariable("id") Integer id) {
        String sql = "update mybatis.user set name = ?, pwd = ? where id = " + id;

        // 數據
        Object[] objects = new Object[2];
        objects[0] = "王五";
        objects[1] = "helloworld";

        jdbcTemplate.update(sql, objects);

        return "update-ok";
    }

    @GetMapping("/delUser/{id}")
    public String deleteUser(@PathVariable("id") Integer id) {
        String sql = "delete from mybatis.user where id = ?";

        jdbcTemplate.update(sql, id);
        return "delete-ok";
    }

    @GetMapping("/addUser")
    public String addUser() {
        String sql = "insert into mybatis.user (name, pwd) values (?, ?)";

        // 數據
        Object[] objects = new Object[2];
        objects[0] = "李華";
        objects[1] = "123klajfld";

        jdbcTemplate.update(sql, objects);

        return "insert-ok";
    }
}

原理探究:

org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration 數據源配置類作用 :根據邏輯判斷之后,添加數據源;

DataSourceConfiguration DataSourceAutoConfiguration 實際導入的數據源配置類。

tomcat的jdbc連接池:

1582543534506.png

Hikari:

1582543573049.png

dbcp2:

1582543595694.png

自定義數據源:

1582543630501.png

因為SpringBoot 2.x 默認數據源是:com.zaxxer.hikari.HikariDataSource,而其他我們沒有導入相應的類,所有會爆紅!

SpringBoot默認支持以下數據源:

  • com.zaxxer.hikari.HikariDataSource (Spring Boot 2.0 以上,默認使用此數據源)

  • org.apache.tomcat.jdbc.pool.DataSource

  • org.apache.commons.dbcp2.BasicDataSource

可以使用 spring.datasource.type 指定自定義的數據源類型,值為 要使用的連接池實現的完全限定名。默認情況下,它是從類路徑自動檢測的。

/**
 * 通用數據源配置
 */
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type")
static class Generic {

   @Bean
   DataSource dataSource(DataSourceProperties properties) {
      return properties.initializeDataSourceBuilder().build();
   }

}

自定義數據源 DruidDataSource

Druid 簡介

Druid 是阿里巴巴開源平台上一個數據庫連接池實現,結合了 C3P0、DBCP、PROXOOL 等 DB 池的優點,同時加入了日志監控。

Druid 可以很好的監控 DB 池連接和 SQL 的執行情況,天生就是針對監控而生的 DB 連接池。

Spring Boot 2.0 以上默認使用 Hikari 數據源,可以說 Hikari 與 Driud 都是當前 Java Web 上最優秀的數據源,我們來重點介紹 Spring Boot 如何集成 Druid 數據源,如何實現數據庫監控。

Druid 的GitHub官網

配置數據源

Druid依賴:

<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.12</version>
</dependency>

現在我們去切換數據源;

之前已經說過 SpringBoot 2.0以上默認使用 com.zaxxer.hikari.HikariDataSource 數據源,但可以通過 spring.datasource.type 指定數據源

20200227083928.png

數據源切換之后,同理可以注入 DataSource,測試類不用變,一測便知!

1582543375247.png

Druid數據庫連接池可以配置一些參數:

spring:
  datasource:
    username: root
    password: root
    # serverTimezone=UTC:MySQL 8+以上使用
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=ture&characterEncoding=UTF-8&serverTimezone=UTC
    # mysql 8+以上使用
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 切換數據源
    type: org.springframework.jdbc.datasource.DriverManagerDataSource

    #Spring Boot 默認是不注入這些屬性值的,需要自己綁定
    #druid 數據源專有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

    #配置監控統計攔截的filters,stat:監控統計、log4j:日志記錄、wall:防御sql注入
    #如果允許時報錯  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #則導入 log4j 依賴即可,Maven 地址: https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

log4j日志依賴

<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

現在需要程序員自己為 com.alibaba.druid.pool.DruidDataSource 綁定到全局配置文件中的參數,再添加到容器中,而不再使用 Spring Boot 的自動生成了;

我們需要 自己添加 DruidDataSource 組件到容器中,並綁定屬性;

package com.rainszj.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DruidConfig {

    /*
    將自定義的 Druid數據源添加到容器中,不再讓 Spring Boot 自動創建
    綁定全局配置文件中的 druid 數據源屬性到 com.alibaba.druid.pool.DruidDataSource從而讓它們生效
    @ConfigurationProperties(prefix = "spring.datasource"):作用就是將 全局配置文件中
    前綴為 spring.datasource的屬性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名參數中
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDataSource() {

        return new DruidDataSource();
    }

}

去測試類中測試一下;看是否成功!

@SpringBootTest
class Springboot03DataApplicationTests {

    @Autowired
    DataSource dataSource;

    @Test
    void contextLoads() throws SQLException {
        // 查看一下驅動類
        System.out.println(dataSource.getClass());
        System.out.println("==========");

        Connection connection = dataSource.getConnection();
        System.out.println(connection);

        DruidDataSource druidDataSource = (DruidDataSource) this.dataSource;

        System.out.println("最大連接數為:" + druidDataSource.getMaxActive());

        System.out.println("數據源初始化連接數:" + druidDataSource.getInitialSize());

        // 歸還連接
        connection.close();
    }
}

打印結果:

1582544191550.png

配置 Druid 數據源監控

Druid 數據源具有監控的功能,並提供了一個 web 界面方便用戶查看,類似安裝 路由器 時,人家也提供了一個默認的 web 頁面。

所以第一步需要設置 Druid 的后台管理頁面,比如 登錄賬號、密碼 等;配置后台管理

需要在 DruidConfig 中配置如下內容:

// 配置 Druid 監控管理后台的Servlet;
// 內置 Servler 容器時沒有web.xml文件,所以使用 SpringBoot的注冊 Servlet 方式
@Bean
public ServletRegistrationBean statViewServlet() {
  			// 創建Servlet注冊實體
  			// /druid/*:后台訪問的路徑
        ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");

        Map<String, String> initParams = new HashMap<>();
        initParams.put("loginUsername", "admin"); //后台管理界面的登錄賬號
        initParams.put("loginPassword", "123456"); //后台管理界面的登錄密碼

        //后台允許誰可以訪問
        //initParams.put("allow", ""127.0.0.1""):表示只有本機可以訪問
        //initParams.put("allow", ""):為空或者為null時,表示允許所有訪問
        initParams.put("allow", "");
        //deny:Druid 后台拒絕誰訪問
        //initParams.put("deny", "192.168.1.20");表示禁止此ip訪問

        //設置初始化參數
        bean.setInitParameters(initParams);
        return bean;
    }

這些參數可以在 com.alibaba.druid.support.http.StatViewServlet 的父類 com.alibaba.druid.support.http.ResourceServlet 中找到

1582545305207.png

配置完畢后,我們可以選擇訪問 : http://localhost:8080/druid/login.html

登錄界面:

1582545508418.png

首頁:

1582545554152.png

配置 Druid web 監控 filter

這個過濾器的作用就是統計 web 應用請求中所有的數據庫信息,比如 發出的 sql 語句,sql 執行的時間、請求次數、請求的 url 地址、以及seesion 監控、數據庫表的訪問次數 等等

需要在 DruidConfig 中配置如下內容:

//配置 Druid 監控 之  web 監控的 filter
//WebStatFilter:用於配置Web和Druid數據源之間的管理關聯監控統計
@Bean
public FilterRegistrationBean filterRegistrationBean() {

    FilterRegistrationBean bean = new FilterRegistrationBean(new WebStatFilter());

    // 設置過濾器的過濾路徑
    bean.addUrlPatterns("/*");
    // bean.setUrlPatterns(Arrays.asList("/*"));

    Map<String, String> initParameters = new HashMap<String, String>();
    // 過濾這些東西不進行統計
    initParameters.put("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");

    bean.setInitParameters(initParameters);

    return bean;
}

這些參數在 WebStatFilter類中

1582546547783.png

整合MyBatis

Maven倉庫

1582548052252.png

版本要求:

1582722712885.png

第一步:導入Spring Boot 整合 MyBatis的 依賴:

<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.1</version>
</dependency>

<!--mysql驅動-->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <scope>runtime</scope>
</dependency>

<!--jdbc-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

第二步:配置數據庫連接信息

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver

加載類 com.mysql.jdbc.Driver。不推薦使用。新的驅動程序類為 com.mysql.cj.jdbc.Driver。通過SPI自動注冊驅動程序,通常不需要手動加載驅動程序類。

這里使用默認的數據源,測試數據庫連接是否成功:

@SpringBootTest
class Springboot03DataApplicationTests {

    @Autowired
    DataSource dataSource;

    @Test
    void contextLoads() throws SQLException {
        // 查看一下驅動類
        System.out.println(dataSource.getClass());
        System.out.println("==========");

        Connection connection = dataSource.getConnection();
        System.out.println(connection);
        System.out.println(connection.getMetaData().getURL());

        // 歸還連接
        connection.close();
    }
}

連接成功,OK!

1582548052252.png

第三步:創建實體類:

package com.rainszj.pojo;


public class User {

    private int id;
    private String name;
    private String pwd;


    public User() {
    }

    public User(int id, String name, String pwd) {
        this.id = id;
        this.name = name;
        this.pwd = pwd;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
}

第四步:配置Mapper接口,也就是dao層:

package com.rainszj.mapper;

import com.rainszj.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

import java.util.List;


/**
 * @Mapper:標記這個類是MyBatis的Mapper,等價於Spring整合MyBatis時的Mapper接口
 */
@Mapper
/**
 * @Rpository:將這個接口添加進Spring容器中,和 @Component等價
 */
@Repository
public interface UserMapper {

    // 查詢所有用戶
    List<User> selectAllUsers();

    // 根據id查找用戶
    User findUserById(@Param("uid")Integer id);

    // 修改用戶
    int updateUser(User user);

    // 添加用戶
    int addUser(User user);

    // 刪除用戶
    int deleteUser(Integer id);

}

第五步:在application.yaml中添加MyBatis的配置

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver

# 整合MyBatis
mybatis:
  # 配置別名
  type-aliases-package: com.rainszj.pojo
  # 綁定Mapper配置文件
  # 注意:這里的 classpath代指:resources目錄
  mapper-locations: classpath:mybatis/mapper/*.xml

1582722125809.png

spring boot 官方並沒有提供 myBaits 的啟動器,是 myBatis 官方提供的開發包來適配的 spring boot,從 pom.xml 文件中的依賴包名也能看出來,並非是以 spring-boot 開頭的;

同理上面全局配置文件中的這兩行配置也是以 mybatis 開頭 而非 spring 開頭也充分說明這些都是 myBatis 官方提供的

可以從 org.mybatis.spring.boot.autoconfigure.MybatisProperties 中查看所有配置項

也可以看:mybatis-spring-boot官網

/**
 * Configuration properties for MyBatis.
 *
 * @author Eddú Meléndez
 * @author Kazuki Shimizu
 */
@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)
public class MybatisProperties {

  public static final String MYBATIS_PREFIX = "mybatis";

  private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();

  /**
   * Location of MyBatis xml config file.
   */
  private String configLocation;

  /**
   * Locations of MyBatis mapper files.
   */
  private String[] mapperLocations;

  /**
   * Packages to search type aliases. (Package delimiters are ",; \t\n")
   */
  private String typeAliasesPackage;

  /**
   * The super class for filtering type alias. If this not specifies, the MyBatis deal as type alias all classes that
   * searched from typeAliasesPackage.
   */
  private Class<?> typeAliasesSuperType;

  /**
   * Packages to search for type handlers. (Package delimiters are ",; \t\n")
   */
  private String typeHandlersPackage;

  /**
   * Indicates whether perform presence check of the MyBatis xml config file.
   */
  private boolean checkConfigLocation = false;

  /**
   * Execution mode for {@link org.mybatis.spring.SqlSessionTemplate}.
   */
  private ExecutorType executorType;

  /**
   * The default scripting language driver class. (Available when use together with mybatis-spring 2.0.2+)
   */
  private Class<? extends LanguageDriver> defaultScriptingLanguageDriver;

  /**
   * Externalized properties for MyBatis configuration.
   */
  private Properties configurationProperties;

  /**
   * A Configuration object for customize default settings. If {@link #configLocation} is specified, this property is
   * not used.
   */
  @NestedConfigurationProperty
  private Configuration configuration;
}

第六步:編寫對應的Mapper.xml配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.rainszj.mapper.UserMapper">

    <select id="selectAllUsers" resultType="user">
        select * from mybatis.user
    </select>
    
    <select id="findUserById" resultType="user">
        select * from mybatis.user where id = #{uid}
    </select>
    
    <update id="updateUser" parameterType="user">
        update mybatis.user 
        set name = #{name},
            pwd = #{pwd}
        where id = #{id}
    </update>


    <update id="addUser" parameterType="user">
        insert into mybatis.user values (#{id},#{name},#{pwd})
    </update>

    <delete id="deleteUser">
        delete from mybatis.user where id = #{id}
    </delete>



</mapper>

第七步:service層:

package com.rainszj.service;

import com.rainszj.mapper.UserMapper;
import com.rainszj.pojo.User;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public List<User> selectAllUsers() {
        return userMapper.selectAllUsers();
    }

    // 根據id查找用戶
    public User findUserById(Integer id) {
        return userMapper.findUserById(id);
    }

    // 修改用戶
    public int updateUser(User user) {
        return userMapper.updateUser(user);
    }

    // 添加用戶
    public int addUser(User user) {
        return userMapper.addUser(user);
    }

    // 刪除用戶
    public int deleteUser(Integer id) {
        return userMapper.deleteUser(id);
    }


}

第八步:controller層:

package com.rainszj.controller;

import com.rainszj.pojo.User;
import com.rainszj.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class UserController {

    @Autowired
    private UserService userService;


    @GetMapping("/selectAllUsers")
    public List<User> selectAllUsers() {

        return userService.selectAllUsers();
    }


    @GetMapping("/findUserById/{id}")
    public String findUserById(@PathVariable Integer id) {
        User user = userService.findUserById(id);
        System.out.println(user);

        return "ok";
    }

    @GetMapping("/addUser")
    public String addUser() {
        userService.addUser(new User(4, "張三", "123"));
        return "ok";
    }


    @GetMapping("/updateUser")
    public String updateUser() {
        userService.updateUser(new User(4, "李四", "123"));

        return "ok";
    }

    @GetMapping("/deleteUser")
    public String deleteUser(@RequestParam("uid") Integer id) {
        userService.deleteUser(id);
        return "ok";
    }

}

測試一下就搞定 OK!


免責聲明!

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



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