如何创建自定义API终结点?

时间:2016-09-18 作者:EndenDragon

我是WordPress插件开发新手。我目前正在尝试开发一个插件,以API端点为例。com/api/元数据。如何将新的url端点添加到wordpress安装中,以便插件可以接收“POST”请求并相应地返回数据?非常感谢你!

1 个回复
最合适的回答,由SO网友:Ahmed Fouad 整理而成

我正在与您共享我为正在开发的插件创建的API,代码是开源的,可以根据您的需要进行修改。这应该给你一个基本的想法,让你开始。

这个API文件本身允许我远程查询服务器,当然,您需要一个db表来存储/验证API访问令牌和每个方法的类,例如获取用户、设置角色等。

<?php

if ( ! defined( \'ABSPATH\' ) ) {
    exit; // Exit if accessed directly
}

/**
 * MC_API class
 */
class MC_API {

    private     $pretty_print = false;
    private     $is_valid_request = false;

    public      $key_data = null;
    public      $permissions = null;

    private     $log_requests = true;
    private     $data = array();

    public      $endpoint;
    public      $api_vars;

    /**
     * Constructor
     */
    public function __construct() {

        add_action( \'init\',         array( $this, \'add_endpoint\'    ) );
        add_action( \'wp\',           array( $this, \'process_query\'   ), -1 );
        add_filter( \'query_vars\',   array( $this, \'query_vars\'      ) );

        $this->pretty_print = defined( \'JSON_PRETTY_PRINT\' ) ? JSON_PRETTY_PRINT : null;

        // Allow API request logging to be turned off
        $this->log_requests = apply_filters( \'mc_api_log_requests\', $this->log_requests );
    }

    /**
     * Add API endpoint
     */
    public static function add_endpoint() {
        add_rewrite_endpoint( \'mc-api\', EP_ALL );
    }

    /**
     * Determines the kind of query requested and also ensure it is a valid query
     */
    private function set_query_mode() {
        global $wp_query;

        // Whitelist our query options
        $accepted = apply_filters( \'mc_api_valid_query_modes\', array(
            \'get_user\',
            \'update_user\',
            \'update_usermeta\',
            \'delete_user\',
            \'bulk_delete_users\',
            \'generate_key\',
            \'revoke_key\',
            \'get_permissions\',
            \'set_role\',
            \'set_credits\',
            \'add_credits\',
            \'deduct_credits\',
            \'transfer_credits\',
            \'set_user_status\',
            \'friend_request\',
            \'friend_cancel\',
            \'friend_approve\',
            \'friend_reject\',
            \'friend_delete\',
            \'follow\',
            \'unfollow\',
            \'get_info\'
        ) );

        $query = isset( $wp_query->query_vars[\'mc-api\'] ) ? $wp_query->query_vars[\'mc-api\'] : null;

        // Make sure our query is valid
        if ( ! in_array( $query, $accepted ) ) {
            $this->send_error( \'invalid_query\' );
        }

        $this->endpoint = $query;
    }

    /**
     * Registers query vars for API access
     */
    public function query_vars( $vars ) {

        $this->api_vars = array(
            \'format\',
            \'consumer_key\',
            \'consumer_secret\',
            \'user\',
            \'users\',
            \'user1\',
            \'user2\',
            \'fields\',
            \'values\',
            \'id\',
            \'limit\',
            \'permissions\',
            \'role\',
            \'amount\',
            \'status\'
        );

        $this->api_vars = apply_filters( \'mc_api_query_vars\', $this->api_vars );

        foreach( $this->api_vars as $var ) {
            $vars[] = $var;
        }

        return $vars;
    }

    /**
     * Validate the API request
     */
    private function validate_request() {
        global $wp_query;

        $consumer_key       = isset( $wp_query->query_vars[\'consumer_key\'] ) ? $wp_query->query_vars[\'consumer_key\'] : null;
        $consumer_secret    = isset( $wp_query->query_vars[\'consumer_secret\'] ) ? $wp_query->query_vars[\'consumer_secret\'] : null;

        if ( ! $consumer_key || ! $consumer_secret ) {
            $this->send_error( \'missing_auth\' );
        }

        $user = $this->get_user_by_consumer_key( $consumer_key );
        if ( ! $user ) {
            $this->send_error( \'invalid_auth\', 401 );
        }

        // Compare provided hash with stored database hash
        if ( ! hash_equals( $user->consumer_secret, $consumer_secret ) ) {
            $this->send_error( \'invalid_auth\', 401 );
        }

        // Check that user did not exceed API limit
        if ( $user->access_limit && $user->queries >= $user->access_limit ) {
            $this->send_error( \'exceeded_limit\', 401 );
        }

        /**
         * User does not have API manager capability, so we need to ensure that he is querying an endpoint that
         * is possible with his API key permissions
         */
        $can_read = array(
            \'get_user\',
        );

        if ( $user->permissions == \'read\' && ! in_array( $this->endpoint, $can_read ) ) {
            $this->send_error( \'invalid_permissions\', 401 );
        }

        // This is a valid request
        $this->is_valid_request = true;
        $this->key_data         = $user;

        $this->update_last_access();
    }

    /**
     * Get user data and API key information by provided consumer key
     */
    private function get_user_by_consumer_key( $consumer_key ) {
        global $wpdb;

        $user = $wpdb->get_row( $wpdb->prepare("SELECT * FROM {$wpdb->prefix}mc_api_keys WHERE consumer_key = %s", $consumer_key ) );

        if ( is_object( $user ) ) {
            $user->user_id          = absint( $user->user_id );
            $user->key_id           = absint( $user->key_id );
            $user->access_limit     = absint( $user->access_limit );
            $user->queries          = absint( $user->queries );
        }

        return $user;
    }

    /**
     * Updated API Key last access datetime.
     */
    private function update_last_access() {
        global $wpdb;

        $key_id     = $this->key_data->key_id;
        $queries    = $this->key_data->queries + 1;

        $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->prefix}mc_api_keys SET last_access = %s, queries = %d WHERE key_id = %d", current_time( \'mysql\' ), $queries, $key_id ) );
    }

    /**
     * Listens for the API and then processes the API requests
     */
    public function process_query() {
        global $wp_query;

        // Check if user is not querying API
        if ( ! isset( $wp_query->query_vars[\'mc-api\'] ) )
            return;

        // Check for API var. Get out if not present
        if ( empty( $wp_query->query_vars[\'mc-api\'] ) ) {
            $this->send_error( \'invalid_query\' );
        }

        // Determine the kind of query
        $this->set_query_mode();

        // Check for a valid user and set errors if necessary
        $this->validate_request();

        // Only proceed if no errors have been noted
        if( ! $this->is_valid_request ) {
            $this->send_error( \'invalid_auth\', 401 );
        }

        // Tell WP we are doing API request
        if( ! defined( \'MC_DOING_API\' ) ) {
            define( \'MC_DOING_API\', true );
        }

        // Need to collect $this->data before sending it
        $data = array();
        $class_name = str_replace(\' \', \'_\', ucwords( str_replace(\'_\', \' \', $this->endpoint ) ) );
        $class_name = "MC_API_" . $class_name;
        $data = new $class_name();
    }

    /**
     * Before we send the data to output function
     */
    public function send_data( $data ) {
        global $wp_query, $wpdb;

        $this->data = apply_filters( \'mc_api_output_data\', $data, $this->endpoint, $this );

        // In case we do not have any data even after filtering
        if ( count( (array) $this->data ) == 0 ) {
            $this->data = array(
                \'message\'           => __( \'Your API request returned no data.\', \'mojocommunity\' ),
                \'queried_endpoint\'  => $this->endpoint
            );
        }

        // Log this API request
        $this->log_request();

        $this->output();
    }

    /**
     * Log a successful API request
     */
    private function log_request() {
        global $wp_query;

        if ( ! $this->log_requests )
            return;

        $log = new MC_API_Log();
    }

    /**
     * The query data is outputted as JSON by default
     */
    private function output( $status_code = 200 ) {

        $format = $this->get_output_format();

        status_header( $status_code );

        do_action( \'mc_api_output_before\', $this->data, $this, $format );

        switch ( $format ) :

            case \'json\' :
                header( \'Content-Type: application/json\' );
                if ( ! empty( $this->pretty_print ) )
                    echo json_encode( $this->data, $this->pretty_print );
                else
                    echo json_encode( $this->data );
                break;

            default :
                // Allow other formats to be added via extensions
                do_action( \'mc_api_output_\' . $format, $this->data, $this );
                break;

        endswitch;

        do_action( \'mc_api_output_after\', $this->data, $this, $format );

        die();
    }

    /**
     * Generate API key.
     */
    public function generate_api_key( $args = array() ) {
        global $wpdb;

        $user_id            = ( isset( $args[\'user_id\'] ) ) ? absint( $args[\'user_id\'] ) : null;
        $description        = ( isset( $args[\'description\'] ) ) ? $args[\'description\'] : __(\'Generated via the API\', \'mojocommunity\' );
        $permissions        = ( isset( $args[\'permissions\'] ) && in_array( $args[\'permissions\'], array( \'read\', \'write\', \'read_write\' ) ) ) ? $args[\'permissions\'] : \'read\';
        $access_limit       = ( isset( $args[\'access_limit\'] ) ) ? absint( $args[\'access_limit\'] ) : 0;
        $consumer_key       = \'ck_\' . mc_rand_hash();
        $consumer_secret    = \'cs_\' . mc_rand_hash();
        $queries            = 0;

        if ( ! $user_id )
            return false;

        $data = array(
            \'user_id\'           => $user_id,
            \'description\'       => $description,
            \'permissions\'       => $permissions,
            \'consumer_key\'      => $consumer_key,
            \'consumer_secret\'   => $consumer_secret,
            \'truncated_key\'     => substr( $consumer_key, -7 ),
            \'access_limit\'      => $access_limit,
            \'queries\'           => $queries
        );

        $wpdb->insert(
            $wpdb->prefix . \'mc_api_keys\',
            $data,
            array(
                \'%d\',
                \'%s\',
                \'%s\',
                \'%s\',
                \'%s\',
                \'%s\',
                \'%d\',
                \'%d\'
            )
        );

        $data = array(
            \'user_id\'           => $user_id,
            \'consumer_key\'      => $consumer_key,
            \'consumer_secret\'   => $consumer_secret,
            \'permissions\'       => $permissions,
            \'access_limit\'      => $access_limit
        );

        return $data;
    }

    /**
     * Revokes API access
     */
    public function revoke_api_key( $key_id = 0 ) {
        global $wpdb;

        $key = $wpdb->get_row( $wpdb->prepare( "SELECT user_id, truncated_key FROM {$wpdb->prefix}mc_api_keys WHERE key_id = %d;", $key_id ) );

        if ( ! $key ) {
            return new MC_Error( \'invalid_api_key_id\', __( \'The specified API key identifier is invalid.\', \'mojocommunity\' ) );
        }

        $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}mc_api_keys WHERE key_id = %d;", $key->key_id ) );

        $data = array(
            \'user_id\'                   => $key->user_id,
            \'consumer_key_ending_in\'    => $key->truncated_key,
            \'success\'                   => __( \'The API access has been revoked.\', \'mojocommunity\' )
        );

        return $data;
    }

    /**
     * Retrieve the output format
     */
    private function get_output_format() {
        global $wp_query;

        $format = isset( $wp_query->query_vars[\'format\'] ) ? $wp_query->query_vars[\'format\'] : \'json\';

        return apply_filters( \'mc_api_output_format\', $format );
    }

    /**
     * Returns a customized error message for the API query
     */
    public function send_error( $error = \'\', $code = 400 ) {

        switch( $error ) {
            default :
                break;
            case \'invalid_query\' :
                $error = __( \'The requested API method is invalid or missing parameters.\', \'mojocommunity\' );
                break;
            case \'missing_auth\' :
                $error = __( \'Your API request could not be authenticated due to missing credentials.\', \'mojocommunity\' );
                break;
            case \'invalid_auth\' :
                $error = __( \'Your API request could not be authenticated due to invalid credentials.\', \'mojocommunity\' );
                break;
            case \'exceeded_limit\' :
                $error = __( \'You have exceeded your API usage limit for this key.\', \'mojocommunity\' );
                break;
            case \'invalid_permissions\' :
                $error = __( \'Your API request could not be authenticated due to invalid permissions.\', \'mojocommunity\' );
                break;
        }

        $this->data = array(
            \'error\'             => $error,
            \'error_code\'        => $code
        );

        $this->output( $code );
    }

}
为了补充上述代码,这里有一个示例API调用/类,比如get\\u users query/endpoint。

/**
 * MC_API_Get_User class
 */
class MC_API_Get_User {

        /**
         * Constructor
         */
        public function __construct() {
            global $wpdb, $wp_query;

            $api = MC()->api;

            $user       = ( isset( $wp_query->query_vars[\'user\'] ) ) ? $wp_query->query_vars[\'user\'] : null;
            $fields     = ( isset( $wp_query->query_vars[\'fields\'] ) ) ? $wp_query->query_vars[\'fields\'] : null;

            if ( ! $user ) {
                $api->send_error( \'invalid_query\' );
            }

            $data = new MC_User( $user, $fields );

            $api->send_data( $data );
        }

    }
请注意我是如何使用原始api中定义的$api->send\\u data()方法将数据作为json发送/输出的。我忘了说你需要

一个包含api密钥、访问限制、分配用户(基本内容)的表,如果您愿意记录请求详细信息,则可以使用自定义的帖子类型(最好知道谁偷偷进入您的api

The above code does multiple verifications which are

<确保用户提供真正有效的公钥和密钥。

验证是否未达到API密钥访问限制

确保API密钥具有执行指定任务的权限(读/写)。例如,您可以设置一个只读API键,该键可以只调用公共方法,例如获取用户信息,并生成另一个具有读写权限的键用于更新/编辑。

Here\'s the database table structure I use for storing API keys hopefully will save you some time doing your own:

CREATE TABLE {$wpdb->prefix}mc_api_keys (
    key_id bigint(20) NOT NULL auto_increment,
    user_id bigint(20) NOT NULL,
    access_limit bigint(20) NOT NULL DEFAULT 0,
    queries bigint(20) NOT NULL DEFAULT 0,
    description longtext NULL,
    permissions varchar(10) NOT NULL,
    consumer_key char(64) NOT NULL,
    consumer_secret char(64) NOT NULL,
    truncated_key char(7) NOT NULL,
    last_access datetime NULL DEFAULT null,
    PRIMARY KEY  (key_id),
    KEY consumer_key (consumer_key),
    KEY consumer_secret (consumer_secret)
) $collate;
这是logger类。负责将每个请求作为日志插入数据库。

/**
 * MC_API_Log class
 */
class MC_API_Log {

    protected   $api = null;
    public      $log_id = 0;

    /**
     * Constructor
     */
    public function __construct( $log_id = 0 ) {

        $this->log_id = $log_id;

        if ( $this->log_id > 0 ) {
            $this->init_meta();
        } else {

            $this->api = MC()->api;
            $this->send();
        }
    }

    /**
     * Init all post meta
     */
    public function init_meta() {
        $meta               = get_post_meta( $this->log_id );

        $this->consumer_key = ( isset( $meta[\'consumer_key\'][0] ) ) ? $meta[\'consumer_key\'][0] : null;
        $this->key_id       = ( isset( $meta[\'key_id\'][0] ) )       ? absint( $meta[\'key_id\'][0] ) : null;
        $this->user_id      = ( isset( $meta[\'user_id\'][0] ) )      ? absint( $meta[\'user_id\'][0] ) : 0;
        $this->endpoint     = ( isset( $meta[\'endpoint\'][0] ) )     ? $meta[\'endpoint\'][0] : null;
        $this->user_ip      = ( isset( $meta[\'user_ip\'][0] ) )      ? $meta[\'user_ip\'][0] : \'127.0.0.1\';
        $this->time         = get_the_time( \'j M Y g:ia\', $this->log_id );
    }

    /**
     * Get truncated key
     */
    public function get_key_html() {
        return ( $this->consumer_key ) ? \'<a href="#">\' . \'&hellip;\' . esc_html( substr( $this->consumer_key, -7 ) ) . \'</a>\' : __( \'Invalid key\', \'mojocommunity\' );
    }

    /**
     * Get user html
     */
    public function get_user_html() {
        $userdata = get_userdata( $this->user_id );
        if ( false === $userdata ) {
            return \'\';
        }
        return \'<a href="\' . get_edit_user_link( $this->user_id ) . \'">\' . $userdata->user_login . \'</a>\';
    }

    /**
     * Get endpoint html
     */
    public function get_endpoint_html() {
        return ( $this->endpoint ) ? \'<code>\' . $this->endpoint . \'</code>\' : null;
    }

    /**
     * Get ID
     */
    public function get_id() {
        return $this->log_id;
    }

    /**
     * Get IP
     */
    public function get_ip() {
        return $this->user_ip;
    }

    /**
     * Get date/time of a request
     */
    public function get_time() {
        return $this->time;
    }

    /**
     * Insert the log
     */
    public function insert_log() {
        global $wp_query;

        if ( ! empty( $this->error ) )
            return;

        $query = array();
        foreach( $this->api->api_vars as $var ) {
            if ( isset( $wp_query->query_vars[ $var ] ) && ! empty( $wp_query->query_vars[ $var ] ) && ! in_array( $var, array( \'consumer_key\', \'consumer_secret\' ) ) ) {
                $query[ $var ] = $wp_query->query_vars[ $var ];
            }
        }

        if ( http_build_query( $query ) ) {
            $query = \'?\' . http_build_query( $query );
        } else {
            $query = null;
        }

        $this->post_args = apply_filters( \'mc_new_api_request_args\', array(
            \'post_author\'       => absint( $this->api->key_data->user_id ),
            \'post_status\'       => \'publish\',
            \'post_type\'         => \'log\',
            \'comment_status\'    => \'closed\'
        ) );

        $this->post_meta = apply_filters( \'mc_new_api_request_meta\', array(
            \'key_id\'        => absint( $this->api->key_data->key_id ),
            \'user_id\'       => absint( $this->api->key_data->user_id ),
            \'consumer_key\'  => $this->api->key_data->consumer_key,
            \'user_ip\'       => mc_get_ip(),
            \'endpoint\'      => $this->api->endpoint . $query
        ) );

        do_action( \'mc_before_insert_api_log\', $this->post_meta );

        $this->log_id = mc_insert_post( $this->post_args, $this->post_meta );

        do_action( \'mc_after_insert_api_log\', $this->log_id, $this->post_meta );
    }

    /**
     * Send the log
     */
    public function send() {

        $this->insert_log();

    }

}
这是mc_insert_post 用于插入日志(或其他帖子类型)的函数

/**
 * A wrapper function for inserting posts in database
 */
function mc_insert_post( $postarr = array(), $meta_input = array() ) {

    do_action( \'mc_before_insert_post\', $postarr, $meta_input );

    if ( ! empty( $meta_input ) && is_array( $meta_input ) )
        $postarr = array_merge( $postarr, array( \'meta_input\' => $meta_input ) );

    $post_id = wp_insert_post( $postarr );

    do_action( \'mc_after_insert_post\', $post_id, $postarr, $meta_input );

    return $post_id;
}

相关推荐