环境:OSE 3.0.1
本文我们创建一个复杂一些的应用,PHP + MySQL。
这里我们使用多个资源定义文件一步一步创建需要的对象,虽然麻烦,但有助于了解 OpenShift 构建应用的整个过程。
1. 为 MySQL 配置 NFS
[root@master ~]# mkdir /var/export/instructor
[root@master ~]# chown nfsnobody:nfsnobody /var/export/instructor
[root@master ~]# chmod 700 /var/export/instructor
[root@master ~]# vim /etc/exports
/var/export/instructor *(rw,async,all_squash)
[root@master ~]# exportfs -a
2. 创建 PV
[root@master ~]# oc login -u system:admin
[root@master ~]# oc create -f mysqlPV.json
mysqlPV.json 内容如下:
{
"apiVersion": "v1",
"kind": "PersistentVolume",
"metadata": {
"name": "mysqldb-instructor-volume",
"labels": {
"name": "mysqldb"
}
},
"spec": {
"capacity": {
"storage": "1Gi"
},
"accessModes": [ "ReadWriteMany" ],
"nfs": {
"path": "/var/export/instructor",
"server": "master.pod0.example.com"
}
}
}
3. 创建 project
[student@workstation ~]$ oc login -u devloper -p openshift
[student@workstation ~]$ oc new-project instructor
4. 创建 MySQL Database pod
[student@workstation ~]$ oc create -f mysqldb-pvclaim.json
mysqldb-pvclaim.json 内容如下:
{
"apiVersion": "v1",
"kind": "PersistentVolumeClaim",
"metadata": {
"name": "mysqldb-instructor-pvclaim",
"labels": {
"name": "mysqldb"
}
},
"spec": {
"accessModes": [ "ReadWriteMany" ],
"resources": {
"requests": {
"storage": "1Gi"
}
}
}
}
[student@workstation ~]$ oc create -f mysqldb-pod-pv.json
mysqldb-pod-pv.json 内容如下:
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "mysqldb",
"labels": {
"name": "mysqldb"
}
},
"spec": {
"containers": [
{
"name": "mysqldb",
"image": "openshift3/mysql-55-rhel7",
"ports": [
{
"containerPort": 3306
}
],
"env": [
{
"name": "MYSQL_USER",
"value": "instructor"
},
{
"name": "MYSQL_PASSWORD",
"value": "password"
},
{
"name": "MYSQL_DATABASE",
"value": "instructor"
}
],
"volumeMounts": [
{
"name": "data",
"mountPath": "/var/lib/mysql/data"
}
]
}
],
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "mysqldb-instructor-pvclaim"
}
}
]
}
}
说明:
(1)MYSQL_USER、MYSQL_PASSWORD、MYSQL_DATABASE 定义在 MySQL pod 上的环境变量,是在创建该 pod 时要使用的。
(2)使用 oc env pods --all --list 可以查看当前 project 中所有 pod 上定义的环境变量。
(3)注意,env 定义的环境变量是给该 pod 自己使用的,不是暴露出来给其它 pod 使用的。
[student@workstation ~]$ oc create -f mysqldb-service.json
mysqldb-service.json 内容如下:
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "mysqldb"
},
"spec": {
"ports": [
{
"port": 3306,
"targetPort": 3306
}
],
"selector": {
"name": "mysqldb"
}
}
}
说明:
(1)"port": 3306, 是 service 暴露的端口
(2)"targetPort": 3306 对应的 pod 将要暴露的端口,这个值要和 pod 中的定义一致。
(3) "selector": "name": "mysqldb",表明所有和该 selector 匹配的 pod,都将作为该 service 的一个 end point。
除了根据 service 资源文件创建 service 之外,还可以从已有 pod 创建 service,比如:
$ oc expose pod mysqldb --name=mysqldb --selector='name=mysqldb'。
另外,每一个 project 中的 service 暴露后,会释放两个环境变量给所有 pod,可以在 pod 的程序中使用。
SVCNAME_SERVICE_HOST 和 SVCNAME_SERVICE_PORT。
对于本应用,这里的两个变量是 MYSQLDB_SERVICE_HOST=172.30.15.149 和 MYSQLDB_SERVICE_PORT=3306。
5. Poplulate MySQL Database
[root@master ~]# oc get services -n instructor
NAME LABELS SELECTOR IP(S) PORT(S)
mysqldb <none> name=mysqldb 172.30.12.107 3306/TCP
[root@master ~]# mysql -uinstructor -ppassword -h172.30.12.107 < instructor.sql
instructor.sql 内容如下:
USE instructor;
--
-- Table structure for table `instructors`
--
CREATE TABLE IF NOT EXISTS `instructors` (
`instructorNumber` int(11) NOT NULL AUTO_INCREMENT,
`instructorName` varchar(50) NOT NULL,
`email` varchar(50) NOT NULL,
`city` varchar(50) NOT NULL,
`state` varchar(50) DEFAULT NULL,
`postalCode` varchar(15) DEFAULT NULL,
`country` varchar(50) NOT NULL,
PRIMARY KEY (`instructorNumber`)
);
--
-- Dumping data for table `instructors`
--
INSERT INTO `instructors` (`instructorNumber`, `instructorName`, `email`, `city`, `state`, `postalCode`, `country`) VALUES
(103, 'Douglas Silva', 'dsilva@redhat.com', 'Brasilia','DF', '72000-000', 'Brasil'),
(112, 'Fernando Lozano', 'flozano@redhat.com', 'Rio de Janeiro', 'RJ', '22021-000', 'Brasil'),
(114, 'Jim Rigsbee', 'jrigsbee@redhat.com', 'Raleigh', 'NC', '27605', 'United States'),
(123, 'Ricardo Jun Taniguchi', 'jtaniguc@redhat.com', 'Sao Paulo', 'SP', '01310-000', 'Brasil')
[root@master ~]# mysql -uinstructor -ppassword -h172.30.12.107 instructor
MySQL [instructor]> select * from instructors;
因为 master 机器可以直接访问 service,所以可以按照上述方式访问数据库。
如果在 workstation 机器上,可以使用端口转发方式访问数据库。
[student@workstation ~]$ oc port-forward -p mysqldb 13306:3306
再开一个终端
[student@workstation ~]$ mysql -uinstructor -ppassword -h127.0.0.1 -P13306 < instructor.sql
6. 使用 S2I 创建应用
[student@workstation ~]$ oc new-app php~http://workstation.pod0.example.com/instructor
[student@workstation ~]$ oc expose service instructor --hostname=instructor.cloudapps0.example.com
访问 http://instructor.cloudapps.example.com
7. PHP pod 是怎样访问 MySQL pod 的?
[student@workstation ~]$ git clone http://workstation.pod0.example.com/instructor
[student@workstation ~]$ cat instructor/services/api.php
require_once("Rest.inc.php");
class API extends REST {
public $data = "";
//const DB_SERVER = "172.30.160.232";
const DB_USER = "instructor";
const DB_PASSWORD = "password";
const DB = "instructor";
private $db = NULL;
private $mysqli = NULL;
public function __construct(){
parent::__construct(); // Init parent contructor
$this->dbConnect(); // Initiate Database connection
}
/*
* Connect to Database
*/
private function dbConnect(){
//$this->mysqli = new mysqli(self::DB_SERVER, self::DB_USER, self::DB_PASSWORD, self::DB);
$this->mysqli = new mysqli($_ENV["MYSQLDB_SERVICE_HOST"], self::DB_USER, self::DB_PASSWORD, self::DB);
}
/*
* Dynmically call the method based on the query string
*/
public function processApi(){
$func = strtolower(trim(str_replace("/","",$_REQUEST['x'])));
if((int)method_exists($this,$func) > 0)
$this->$func();
else
$this->response('',404); // If the method not exist with in this class "Page not found".
}
private function login(){
if($this->get_request_method() != "POST"){
$this->response('',406);
}
$email = $this->_request['email'];
$password = $this->_request['pwd'];
if(!empty($email) and !empty($password)){
if(filter_var($email, FILTER_VALIDATE_EMAIL)){
$query="SELECT uid, name, email FROM users WHERE email = '$email' AND password = '".md5($password)."' LIMIT 1";
$r = $this->mysqli->query($query) or die($this->mysqli->error.__LINE__);
if($r->num_rows > 0) {
$result = $r->fetch_assoc();
// If success everythig is good send header as "OK" and user details
$this->response($this->json($result), 200);
}
$this->response('', 204); // If no records "No Content" status
}
}
$error = array('status' => "Failed", "msg" => "Invalid Email address or Password");
$this->response($this->json($error), 400);
}
private function instructors(){
if($this->get_request_method() != "GET"){
$this->response('',406);
}
$query="SELECT distinct c.instructorNumber, c.instructorName, c.email, c.city, c.state, c.postalCode, c.country FROM instructors c order by c.instructorNumber desc";
$r = $this->mysqli->query($query) or die($this->mysqli->error.__LINE__);
if($r->num_rows > 0){
$result = array();
while($row = $r->fetch_assoc()){
$result[] = $row;
}
$this->response($this->json($result), 200); // send user details
}
$this->response('',204); // If no records "No Content" status
}
private function instructor(){
if($this->get_request_method() != "GET"){
$this->response('',406);
}
$id = (int)$this->_request['id'];
if($id > 0){
$query="SELECT distinct c.instructorNumber, c.instructorName, c.email, c.city, c.state, c.postalCode, c.country FROM instructors c where c.instructorNumber=$id";
$r = $this->mysqli->query($query) or die($this->mysqli->error.__LINE__);
if($r->num_rows > 0) {
$result = $r->fetch_assoc();
$this->response($this->json($result), 200); // send user details
}
}
$this->response('',204); // If no records "No Content" status
}
private function insertInstructor(){
if($this->get_request_method() != "POST"){
$this->response('',406);
}
$instructor = json_decode(file_get_contents("php://input"),true);
$column_names = array('instructorName', 'email', 'city', 'country');
$keys = array_keys($instructor);
$columns = '';
$values = '';
foreach($column_names as $desired_key){ // Check the instructor received. If blank insert blank into the array.
if(!in_array($desired_key, $keys)) {
$$desired_key = '';
}else{
$$desired_key = $instructor[$desired_key];
}
$columns = $columns.$desired_key.',';
$values = $values."'".$$desired_key."',";
}
$query = "INSERT INTO instructors(".trim($columns,',').") VALUES(".trim($values,',').")";
if(!empty($instructor)){
$r = $this->mysqli->query($query) or die($this->mysqli->error.__LINE__);
$success = array('status' => "Success", "msg" => "Instructor Created Successfully.", "data" => $instructor);
$this->response($this->json($success),200);
}else
$this->response('',204); //"No Content" status
}
private function updateInstructor(){
if($this->get_request_method() != "POST"){
$this->response('',406);
}
$instructor = json_decode(file_get_contents("php://input"),true);
$id = (int)$instructor['id'];
$column_names = array('instructorName', 'email', 'city', 'country');
$keys = array_keys($instructor['instructor']);
$columns = '';
$values = '';
foreach($column_names as $desired_key){ // Check the instructor received. If key does not exist, insert blank into the array.
if(!in_array($desired_key, $keys)) {
$$desired_key = '';
}else{
$$desired_key = $instructor['instructor'][$desired_key];
}
$columns = $columns.$desired_key."='".$$desired_key."',";
}
$query = "UPDATE instructors SET ".trim($columns,',')." WHERE instructorNumber=$id";
if(!empty($instructor)){
$r = $this->mysqli->query($query) or die($this->mysqli->error.__LINE__);
$success = array('status' => "Success", "msg" => "Instructor ".$id." Updated Successfully.", "data" => $instructor);
$this->response($this->json($success),200);
}else
$this->response('',204); // "No Content" status
}
private function deleteInstructor(){
if($this->get_request_method() != "DELETE"){
$this->response('',406);
}
$id = (int)$this->_request['id'];
if($id > 0){
$query="DELETE FROM instructors WHERE instructorNumber = $id";
$r = $this->mysqli->query($query) or die($this->mysqli->error.__LINE__);
$success = array('status' => "Success", "msg" => "Successfully deleted one record.");
$this->response($this->json($success),200);
}else
$this->response('',204); // If no records "No Content" status
}
/*
* Encode array into JSON
*/
private function json($data){
if(is_array($data)){
return json_encode($data);
}
}
}
// Initiiate Library
$api = new API;
$api->processApi();
?>
本文我们创建一个复杂一些的应用,PHP + MySQL。
这里我们使用多个资源定义文件一步一步创建需要的对象,虽然麻烦,但有助于了解 OpenShift 构建应用的整个过程。
1. 为 MySQL 配置 NFS
[root@master ~]# mkdir /var/export/instructor
[root@master ~]# chown nfsnobody:nfsnobody /var/export/instructor
[root@master ~]# chmod 700 /var/export/instructor
[root@master ~]# vim /etc/exports
/var/export/instructor *(rw,async,all_squash)
[root@master ~]# exportfs -a
2. 创建 PV
[root@master ~]# oc login -u system:admin
[root@master ~]# oc create -f mysqlPV.json
mysqlPV.json 内容如下:
{
"apiVersion": "v1",
"kind": "PersistentVolume",
"metadata": {
"name": "mysqldb-instructor-volume",
"labels": {
"name": "mysqldb"
}
},
"spec": {
"capacity": {
"storage": "1Gi"
},
"accessModes": [ "ReadWriteMany" ],
"nfs": {
"path": "/var/export/instructor",
"server": "master.pod0.example.com"
}
}
}
3. 创建 project
[student@workstation ~]$ oc login -u devloper -p openshift
[student@workstation ~]$ oc new-project instructor
4. 创建 MySQL Database pod
[student@workstation ~]$ oc create -f mysqldb-pvclaim.json
mysqldb-pvclaim.json 内容如下:
{
"apiVersion": "v1",
"kind": "PersistentVolumeClaim",
"metadata": {
"name": "mysqldb-instructor-pvclaim",
"labels": {
"name": "mysqldb"
}
},
"spec": {
"accessModes": [ "ReadWriteMany" ],
"resources": {
"requests": {
"storage": "1Gi"
}
}
}
}
[student@workstation ~]$ oc create -f mysqldb-pod-pv.json
mysqldb-pod-pv.json 内容如下:
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "mysqldb",
"labels": {
"name": "mysqldb"
}
},
"spec": {
"containers": [
{
"name": "mysqldb",
"image": "openshift3/mysql-55-rhel7",
"ports": [
{
"containerPort": 3306
}
],
"env": [
{
"name": "MYSQL_USER",
"value": "instructor"
},
{
"name": "MYSQL_PASSWORD",
"value": "password"
},
{
"name": "MYSQL_DATABASE",
"value": "instructor"
}
],
"volumeMounts": [
{
"name": "data",
"mountPath": "/var/lib/mysql/data"
}
]
}
],
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "mysqldb-instructor-pvclaim"
}
}
]
}
}
说明:
(1)MYSQL_USER、MYSQL_PASSWORD、MYSQL_DATABASE 定义在 MySQL pod 上的环境变量,是在创建该 pod 时要使用的。
(2)使用 oc env pods --all --list 可以查看当前 project 中所有 pod 上定义的环境变量。
(3)注意,env 定义的环境变量是给该 pod 自己使用的,不是暴露出来给其它 pod 使用的。
[student@workstation ~]$ oc create -f mysqldb-service.json
mysqldb-service.json 内容如下:
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "mysqldb"
},
"spec": {
"ports": [
{
"port": 3306,
"targetPort": 3306
}
],
"selector": {
"name": "mysqldb"
}
}
}
说明:
(1)"port": 3306, 是 service 暴露的端口
(2)"targetPort": 3306 对应的 pod 将要暴露的端口,这个值要和 pod 中的定义一致。
(3) "selector": "name": "mysqldb",表明所有和该 selector 匹配的 pod,都将作为该 service 的一个 end point。
除了根据 service 资源文件创建 service 之外,还可以从已有 pod 创建 service,比如:
$ oc expose pod mysqldb --name=mysqldb --selector='name=mysqldb'。
另外,每一个 project 中的 service 暴露后,会释放两个环境变量给所有 pod,可以在 pod 的程序中使用。
SVCNAME_SERVICE_HOST 和 SVCNAME_SERVICE_PORT。
对于本应用,这里的两个变量是 MYSQLDB_SERVICE_HOST=172.30.15.149 和 MYSQLDB_SERVICE_PORT=3306。
5. Poplulate MySQL Database
[root@master ~]# oc get services -n instructor
NAME LABELS SELECTOR IP(S) PORT(S)
mysqldb <none> name=mysqldb 172.30.12.107 3306/TCP
[root@master ~]# mysql -uinstructor -ppassword -h172.30.12.107 < instructor.sql
instructor.sql 内容如下:
USE instructor;
--
-- Table structure for table `instructors`
--
CREATE TABLE IF NOT EXISTS `instructors` (
`instructorNumber` int(11) NOT NULL AUTO_INCREMENT,
`instructorName` varchar(50) NOT NULL,
`email` varchar(50) NOT NULL,
`city` varchar(50) NOT NULL,
`state` varchar(50) DEFAULT NULL,
`postalCode` varchar(15) DEFAULT NULL,
`country` varchar(50) NOT NULL,
PRIMARY KEY (`instructorNumber`)
);
--
-- Dumping data for table `instructors`
--
INSERT INTO `instructors` (`instructorNumber`, `instructorName`, `email`, `city`, `state`, `postalCode`, `country`) VALUES
(103, 'Douglas Silva', 'dsilva@redhat.com', 'Brasilia','DF', '72000-000', 'Brasil'),
(112, 'Fernando Lozano', 'flozano@redhat.com', 'Rio de Janeiro', 'RJ', '22021-000', 'Brasil'),
(114, 'Jim Rigsbee', 'jrigsbee@redhat.com', 'Raleigh', 'NC', '27605', 'United States'),
(123, 'Ricardo Jun Taniguchi', 'jtaniguc@redhat.com', 'Sao Paulo', 'SP', '01310-000', 'Brasil')
[root@master ~]# mysql -uinstructor -ppassword -h172.30.12.107 instructor
MySQL [instructor]> select * from instructors;
因为 master 机器可以直接访问 service,所以可以按照上述方式访问数据库。
如果在 workstation 机器上,可以使用端口转发方式访问数据库。
[student@workstation ~]$ oc port-forward -p mysqldb 13306:3306
再开一个终端
[student@workstation ~]$ mysql -uinstructor -ppassword -h127.0.0.1 -P13306 < instructor.sql
6. 使用 S2I 创建应用
[student@workstation ~]$ oc new-app php~http://workstation.pod0.example.com/instructor
[student@workstation ~]$ oc expose service instructor --hostname=instructor.cloudapps0.example.com
访问 http://instructor.cloudapps.example.com
7. PHP pod 是怎样访问 MySQL pod 的?
[student@workstation ~]$ git clone http://workstation.pod0.example.com/instructor
[student@workstation ~]$ cat instructor/services/api.php
require_once("Rest.inc.php");
class API extends REST {
public $data = "";
//const DB_SERVER = "172.30.160.232";
const DB_USER = "instructor";
const DB_PASSWORD = "password";
const DB = "instructor";
private $db = NULL;
private $mysqli = NULL;
public function __construct(){
parent::__construct(); // Init parent contructor
$this->dbConnect(); // Initiate Database connection
}
/*
* Connect to Database
*/
private function dbConnect(){
//$this->mysqli = new mysqli(self::DB_SERVER, self::DB_USER, self::DB_PASSWORD, self::DB);
$this->mysqli = new mysqli($_ENV["MYSQLDB_SERVICE_HOST"], self::DB_USER, self::DB_PASSWORD, self::DB);
}
/*
* Dynmically call the method based on the query string
*/
public function processApi(){
$func = strtolower(trim(str_replace("/","",$_REQUEST['x'])));
if((int)method_exists($this,$func) > 0)
$this->$func();
else
$this->response('',404); // If the method not exist with in this class "Page not found".
}
private function login(){
if($this->get_request_method() != "POST"){
$this->response('',406);
}
$email = $this->_request['email'];
$password = $this->_request['pwd'];
if(!empty($email) and !empty($password)){
if(filter_var($email, FILTER_VALIDATE_EMAIL)){
$query="SELECT uid, name, email FROM users WHERE email = '$email' AND password = '".md5($password)."' LIMIT 1";
$r = $this->mysqli->query($query) or die($this->mysqli->error.__LINE__);
if($r->num_rows > 0) {
$result = $r->fetch_assoc();
// If success everythig is good send header as "OK" and user details
$this->response($this->json($result), 200);
}
$this->response('', 204); // If no records "No Content" status
}
}
$error = array('status' => "Failed", "msg" => "Invalid Email address or Password");
$this->response($this->json($error), 400);
}
private function instructors(){
if($this->get_request_method() != "GET"){
$this->response('',406);
}
$query="SELECT distinct c.instructorNumber, c.instructorName, c.email, c.city, c.state, c.postalCode, c.country FROM instructors c order by c.instructorNumber desc";
$r = $this->mysqli->query($query) or die($this->mysqli->error.__LINE__);
if($r->num_rows > 0){
$result = array();
while($row = $r->fetch_assoc()){
$result[] = $row;
}
$this->response($this->json($result), 200); // send user details
}
$this->response('',204); // If no records "No Content" status
}
private function instructor(){
if($this->get_request_method() != "GET"){
$this->response('',406);
}
$id = (int)$this->_request['id'];
if($id > 0){
$query="SELECT distinct c.instructorNumber, c.instructorName, c.email, c.city, c.state, c.postalCode, c.country FROM instructors c where c.instructorNumber=$id";
$r = $this->mysqli->query($query) or die($this->mysqli->error.__LINE__);
if($r->num_rows > 0) {
$result = $r->fetch_assoc();
$this->response($this->json($result), 200); // send user details
}
}
$this->response('',204); // If no records "No Content" status
}
private function insertInstructor(){
if($this->get_request_method() != "POST"){
$this->response('',406);
}
$instructor = json_decode(file_get_contents("php://input"),true);
$column_names = array('instructorName', 'email', 'city', 'country');
$keys = array_keys($instructor);
$columns = '';
$values = '';
foreach($column_names as $desired_key){ // Check the instructor received. If blank insert blank into the array.
if(!in_array($desired_key, $keys)) {
$$desired_key = '';
}else{
$$desired_key = $instructor[$desired_key];
}
$columns = $columns.$desired_key.',';
$values = $values."'".$$desired_key."',";
}
$query = "INSERT INTO instructors(".trim($columns,',').") VALUES(".trim($values,',').")";
if(!empty($instructor)){
$r = $this->mysqli->query($query) or die($this->mysqli->error.__LINE__);
$success = array('status' => "Success", "msg" => "Instructor Created Successfully.", "data" => $instructor);
$this->response($this->json($success),200);
}else
$this->response('',204); //"No Content" status
}
private function updateInstructor(){
if($this->get_request_method() != "POST"){
$this->response('',406);
}
$instructor = json_decode(file_get_contents("php://input"),true);
$id = (int)$instructor['id'];
$column_names = array('instructorName', 'email', 'city', 'country');
$keys = array_keys($instructor['instructor']);
$columns = '';
$values = '';
foreach($column_names as $desired_key){ // Check the instructor received. If key does not exist, insert blank into the array.
if(!in_array($desired_key, $keys)) {
$$desired_key = '';
}else{
$$desired_key = $instructor['instructor'][$desired_key];
}
$columns = $columns.$desired_key."='".$$desired_key."',";
}
$query = "UPDATE instructors SET ".trim($columns,',')." WHERE instructorNumber=$id";
if(!empty($instructor)){
$r = $this->mysqli->query($query) or die($this->mysqli->error.__LINE__);
$success = array('status' => "Success", "msg" => "Instructor ".$id." Updated Successfully.", "data" => $instructor);
$this->response($this->json($success),200);
}else
$this->response('',204); // "No Content" status
}
private function deleteInstructor(){
if($this->get_request_method() != "DELETE"){
$this->response('',406);
}
$id = (int)$this->_request['id'];
if($id > 0){
$query="DELETE FROM instructors WHERE instructorNumber = $id";
$r = $this->mysqli->query($query) or die($this->mysqli->error.__LINE__);
$success = array('status' => "Success", "msg" => "Successfully deleted one record.");
$this->response($this->json($success),200);
}else
$this->response('',204); // If no records "No Content" status
}
/*
* Encode array into JSON
*/
private function json($data){
if(is_array($data)){
return json_encode($data);
}
}
}
// Initiiate Library
$api = new API;
$api->processApi();
?>
没有评论:
发表评论