Мы рассмотрим, как создать чат-приложение XMPP, которое можно использовать в самых разных сценариях. Вы узнаете, как интегрировать внешнюю базу данных с OpenFire Jabber-сервером Ignite Realtime и как использовать библиотеку XIFF для создания пользовательских расширений XMPP, которые можно использовать для отправки пользовательских данных по сети.
Вы можете использовать это для создания стандартного стандартного приложения для чата со страницей, посвященной ему, или вы можете запустить его вместе с другим фрагментом содержимого Flash, как Kongregate делает со своими играми.
Окончательный результат предварительного просмотра
Давайте посмотрим на интерфейс конечного результата, к которому мы будем стремиться (эта демонстрация не работает как реальный клиент чата):
Вот демонстрация видео, которая показывает это в действии:
Шаг 1: Предпосылки
В этом руководстве предполагается, что у вас есть некоторый опыт работы с PHP. В вашей системе должен быть установлен Wamp Server, и вы также должны быть немного знакомы с PhpMyAdmin. Если у вас нет WAMP, вы можете скачать его здесь . Вам также необходимо загрузить Openfire Jabber Server и библиотеку API XIFF с веб-сайта Ignite Realtime . Я проведу вас через процесс установки Openfire. Наконец, вам потребуется последняя версия Java, установленная в вашей операционной системе и Flash CS4 или более поздней версии. В этом уроке я буду использовать Flash CS5 Professional. Если у вас нет последней версии Java, вы можете скачать последнюю версию здесь .
Шаг 2: Настройка нашей базы данных
Убедитесь, что на вашем компьютере работает Wamp Server, и перейдите по адресу http: // localhost / phpmyadmin / в веб-браузере.
Создайте новую базу данных с именем MyContentSite
используя MyContentSite
.
Создайте новую таблицу в MyContentSite
данных myMembers
под названием myMembers
с восемью полями.
После того, myMembers
таблица myMembers
была успешно создана PhpMyAdmin, создайте следующие поля:
- UID
- Имя
- фамилия
- my_username
- мой пароль
- Эл. адрес
- статус
- страна
Ваш экран должен выглядеть следующим образом:
Позвольте мне разбить каждое поле. Поле uid
должно иметь тип INT
. Вы можете изменить тип поля из столбца Type
. Сделайте это поле первичным индексом и установите для этого поля самоинкремент. Сделайте это, выбрав опцию PRIMARY
под столбцом INDEX
в этой строке полей. Затем установите флажок в столбце « Auto-Increment
. Это поле представляет идентификатор пользователя текущего участника нашего веб-сайта. my_username
first_name
, last_name
, my_username
, my_password
, email
и country
должны иметь тип данных или VARCHAR
а Length/Value
должно быть установлено на 255.
Примечание. Поля my_password
показанные на изображениях, содержат MD5
пароля пользователя. Вы можете хранить обычные пароли без шифрования, но для этого урока я буду использовать хешированные пароли.
Наконец, поле status_message
должно иметь тип MEDIUMTEXT
.
После того, как вы создали все поля, нажмите кнопку save
.
Теперь мы готовы создать две фиктивные учетные записи, которые мы будем использовать для входа на наш веб-сайт и присоединения к чат-комнатам позже в этом руководстве. Нажмите на вкладку Insert
. Вам будет представлена форма для создания новой строки в таблице.
Оставьте поле uid
пустым, потому что мы хотим, чтобы это поле автоматически увеличивалось . Установите first_name
для Джейн и поле last_name
для Doe . Установите для my_username
значение janedoe со всеми буквами нижнего регистра. Для поля my_password
мы будем использовать хешированное значение для нашего пароля, tutsplus во всех строчных буквах. Введите ca28ad0733a3dde9dc1f30e32718d209 в поле my_password
. Вы можете установить в поле электронной почты адрес электронной почты по вашему выбору, а в поле status_message
— все, что захотите. Установите поле country
на любую страну, которую вы хотите. Когда вы закончите, нажмите на кнопку Сохранить . Повторите этот процесс, чтобы создать учетную запись для John Doe с полем my_username
установленным в johndoe123 со всеми строчными буквами. Используйте тот же пароль, что и раньше.
Шаг 2: Установка Openfire
Как только вы загрузили Openfire с веб-сайта Ignite Realtime, запустите установочный exe- файл (или dmg- файл, если вы используете Mac).
Выберите свой язык.
Нажмите Далее, чтобы продолжить.
Примите лицензионное соглашение.
Выберите каталог и нажмите « Далее», чтобы продолжить.
Выберите папку «Пуск».
Нажмите Далее, чтобы начать установку.
После завершения установки нажмите Готово, чтобы запустить Openfire. Сервис Openfire запустится автоматически при запуске программы. Нажмите кнопку Launch Admin , когда Openfire завершит загрузку.
Теперь мы настроим Openfire. Выберите предпочитаемый язык и нажмите « Продолжить» .
Оставьте в поле « Порт консоли администратора» и в поле « Порт консоли безопасного администратора» их значения по умолчанию. Для этого урока оставьте в поле Домен также значение по умолчанию. Вы можете изменить это позже на домен вашего сайта. Нажмите Продолжить .
Выберите опцию « Стандартное соединение с базой данных» и нажмите « Продолжить» .
Под пресетами драйверов баз данных выберите MySQL . Введите com.mysql.jdbc.Driver в поле Класс драйвера JDBC . Измените [host-name]
на localhost и измените [database-name]
на mycontentsite в поле URL-адрес базы данных . Установите для имени пользователя и пароля имя пользователя и пароль базы данных MySQL. Для этого урока я использовал имя пользователя по умолчанию для MySQL, которое является root, а поле Password остается пустым. Нажмите Продолжить, чтобы продолжить.
Оставьте настройки профиля по умолчанию . Нажмите Продолжить, чтобы продолжить.
Выберите адрес электронной почты для вашей учетной записи администратора и пароль, а затем продолжите.
Мы закончили процесс установки. Теперь вы можете войти в консоль администратора. Используйте имя пользователя по умолчанию admin и пароль, который вы выбрали при установке.
Шаг 3: Настройка комнат чата
Наше приложение позволяет пользователям общаться в чатах. Но для этого наши пользователи должны иметь чаты, чтобы присоединиться. Давайте создадим чаты с помощью консоли администратора Openfire. Если вы еще этого не сделали, запустите Openfire Server. Войдите в консоль администратора Openfire. Перейдите на страницу «Комнаты группового чата», нажав на вкладку « Групповой чат ».
Нажмите Создать новую комнату в левой части экрана. Заполните детали, как вы видите их на изображении ниже.
Когда вы закончите, нажмите на кнопку Сохранить изменения . Если комната была создана успешно, вы должны увидеть сообщение и зеленую галочку.
Выполните те же шаги, чтобы создать еще две комнаты чата.
Шаг 4: Интеграция Openfire с MySQL
В этом руководстве наш искусственный веб-сайт использует базу данных MySQL для хранения данных о каждом пользователе. Openfire может быть интегрирован с внешней базой данных, в данном случае базой данных MySQL. Сначала мы должны настроить Openfire для этого.
Откройте файл openfire.xml с помощью « Блокнота» или, предпочтительно, редактора форматированного текста, такого как « Блокнот ++», как я упоминал ранее. Файл будет находиться в папке Openfire / conf / в папке Program Files на вашем ПК.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
<?xml version=»1.0″ encoding=»UTF-8″?>
<!—
This file stores bootstrap properties needed by Openfire.
Property names must be in the format: «prop.name.is.blah=value»
That will be stored as:
<prop>
<name>
<is>
<blah>value</blah>
</is>
</name>
</prop>
Most properties are stored in the Openfire database.
property viewer and editor is included in the admin console.
—>
<!— root element, all properties must be under this element —>
<jive>
<adminConsole>
<!— Disable either port by setting the value to -1 —>
<port>9090</port>
<securePort>9091</securePort>
</adminConsole>
<locale>en</locale>
<!— Network settings.
Alternatively, you can specify a specific network interfaces that the server
will listen on.
on multi-homed servers.
<!—
<network>
<interface></interface>
</network>
—>
<connectionProvider>
<className>org.jivesoftware.database.EmbeddedConnectionProvider</className>
</connectionProvider>
<database>
<defaultProvider>
<driver>com.mysql.jdbc.Driver</driver>
<serverURL>jdbc:mysql://localhost:3306/mycontentsite</serverURL>
<username>root</username>
<password/>
<testSQL>select 1</testSQL>
<testBeforeUse>true</testBeforeUse>
<testAfterUse>true</testAfterUse>
<minConnections>5</minConnections>
<maxConnections>25</maxConnections>
<connectionTimeout>1.0</connectionTimeout>
</defaultProvider>
</database>
<setup>true</setup>
</jive>
|
Вот как выглядит мой файл openfire.xml . Ваш файл openfire.xml должен выглядеть аналогично моему. Вот ссылка на Руководство по интеграции пользовательских баз данных Openfire на веб-сайте Ignite Realtime. Вы заметите, что в этом руководстве вас попросят внести изменения непосредственно в файл конфигурации openfire.xml .
Не вносите никаких изменений в этот файл, если он не похож на мой.
Примечание. Весьма вероятно, что ваш файл openfire.xml будет использовать DefaultConnectionProvider
. Если это так, у вас могут возникнуть проблемы при входе в консоль администратора. Попробуйте сначала войти в систему по умолчанию. Если пароль, указанный вами при установке, не работает, используйте пароль по умолчанию для входа в систему. Имя пользователя по умолчанию — admin
а пароль по умолчанию — admin
.
Если вы не можете войти в систему, измените DefaultConnectionProvider
на EmbeddedConnectionProvider
. Затем перезапустите Openfire и попробуйте войти снова. Если проблема не устранена, снова запустите программу установки Openfire. Измените значение тега setup
с false на true в файле openfire.xml. Затем перезапустите Openfire, чтобы снова запустить установку. Делайте это в крайнем случае — в этом не должно быть необходимости.
Я следовал инструкциям на сайте Ignite Realtime бесчисленное количество раз, чтобы потом оказаться в дыре. Одна из проблем, с которыми я столкнулся, заключалась в том, что пользователи не могли подключиться к серверу, и когда я попытался исправить проблему в консоли администратора, я не смог войти в систему. Фактически, единственное, что я могу себе представить, это может быть более неприятно чем проблемы, с которыми я столкнулся, застревал внутри ловушки пилы.
Я не хочу, чтобы вы проходили через то, что мне пришлось, поэтому, пожалуйста, внимательно выполните следующие шаги. В Openfire есть замечательный способ редактирования и создания свойств, который, на мой взгляд, намного эффективнее, чем редактирование XML-файла в вашей системе.
Войдите в консоль администратора Openfire. Нажмите на ссылку « Свойства системы» в правой части главной страницы.
Страница свойств системы вашего сервера должна выглядеть примерно так.
Важное замечание: Если на странице «Свойства системы» отсутствуют некоторые или все свойства на изображении ниже, вы можете добавить свойства вручную. Когда мы изменяем свойство в учебнике, если у вас нет свойства, которое мы изменяем, просто используйте те же шаги, что и для изменения свойства, чтобы создать его. В противном случае, если у вас уже есть свойство, которое мы создаем, просто измените свойство со значениями, которые я указываю.
Внизу экрана вы увидите раздел с заголовком Добавить новое свойство . У него есть два поля. Первое поле Имя свойства . Второе поле — Значение свойства . В поле Имя свойства введите jdbcProvider.driver, а в поле Значение свойства введите com.mysql.jdbc.Driver в поле. Нажмите на кнопку Сохранить свойство , когда вы закончите. Вы будете следовать этим шагам, чтобы создать больше свойств, а также изменить существующие свойства.
Создайте свойство с именем jdbcProvider.connectionString со значением jdbc: mysql: // localhost / mycontentsite? User = root & password = .
Теперь мы собираемся сделать нашу первую модификацию существующего свойства. Нажмите на ссылку Изменить, которая соответствует свойству provider.auth.className
. Измените его значение на org.jivesoftware.openfire.auth.JDBCAuthProvider, используя таблицу свойств редактирования . Нажмите кнопку Сохранить свойство , когда вы закончите.
Создайте новое свойство с именем jdbcAuthProvider.passwordSQL . Присвойте ему значение SELECT my_password FROM mymembers ГДЕ my_username =? , Значением этого свойства является строка запроса MySQL, которая будет использоваться для аутентификации пользователя.
Примечание: обратите внимание, что он содержит знак вопроса (?). Вопросительный знак будет заменен значением внутри поля имени пользователя.
Создайте новое свойство с именем jdbcAuthProvider.passwordType . Дайте ему значение md5 .
Примечание. Свойства jdbcAuthProvider будут скрыты, если вы правильно выполнили эти шаги.
Создайте новое свойство с именем admin.authorizedUsernames . Значение должно быть jid имени пользователя, с которым вы хотите войти в консоль администратора .
Примечание: посмотрите на изображение ниже. Обратите внимание, что jid Джейн и Джона Доу — это имена пользователей, соединенные знаком @ и доменом сервера XMPP.
Измените свойство provider.user.className
, изменив его значение на org.jivesoftware.openfire.user.JDBCUserProvider .
Создайте новое свойство с именем jdbcUserProvider.loadUserSQL со значением SELECT first_name, email FROM mymembers ГДЕ my_username =? ,
Создайте новое свойство с именем jdbcUserProvider.userCountSQL и присвойте ему значение SELECT COUNT (*) FROM mymembers .
Создайте новое свойство с именем jdbcUserProvider.allUsersSQL . Установите значение SELECT my_username ОТ mymembers .
Создайте новое свойство с именем jdbcUserProvider.searchSQL . Присвойте ему значение SELECT my_username ОТ mymembers .
Создайте новое свойство с именем usernameField . Установите его значение в my_username .
Создайте новое свойство с именем nameField . Установите его значение first_name .
Создайте новое свойство с именем emailField . Установите его значение для электронной почты .
Теперь, когда мы добавили и изменили необходимые свойства, мы можем выйти из консоли администратора . Перезапустите Openfire и попытайтесь снова войти в консоль администратора с реальным пользователем.
Доступ запрещен!
Теперь попробуйте войти с именем пользователя, admin .
Доступ снова запрещен! Что тут происходит?
Давайте посмотрим на файл openfire.xml . Ваш должен выглядеть так же, как и раньше. Нам нужно добавить модификации в XML-файл. Я обнаружил, что внесение изменений сначала в консоль администратора , а затем изменение файла openfire.xml более согласованно, чем просто внесение изменений в xml. Как я уже говорил ранее, я просто не мог войти в систему с помощью клиента или в консоли администратора после внесения этих изменений.
Измените файл openfire.xml так, чтобы он выглядел следующим образом.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
<?xml version=»1.0″ encoding=»UTF-8″?>
<!—
This file stores bootstrap properties needed by Openfire.
Property names must be in the format: «prop.name.is.blah=value»
That will be stored as:
<prop>
<name>
<is>
<blah>value</blah>
</is>
</name>
</prop>
Most properties are stored in the Openfire database.
property viewer and editor is included in the admin console.
—>
<!— root element, all properties must be under this element —>
<jive>
<adminConsole>
<!— Disable either port by setting the value to -1 —>
<port>9090</port>
<securePort>9091</securePort>
</adminConsole>
<locale>en</locale>
<!— Network settings.
Alternatively, you can specify a specific network interfaces that the server
will listen on.
on multi-homed servers.
<!—
<network>
<interface></interface>
</network>
—>
<connectionProvider>
<className>org.jivesoftware.database.DefaultConnectionProvider</className>
</connectionProvider>
<database>
<defaultProvider>
<driver>com.mysql.jdbc.Driver</driver>
<serverURL>jdbc:mysql://localhost:3306/mycontentsite?user=root&password=</serverURL>
<username>root</username>
<password/>
<testSQL>select 1</testSQL>
<testBeforeUse>true</testBeforeUse>
<testAfterUse>true</testAfterUse>
<minConnections>5</minConnections>
<maxConnections>25</maxConnections>
<connectionTimeout>1.0</connectionTimeout>
</defaultProvider>
</database>
<jdbcProvider>
<driver>com.mysql.jdbc.Driver</driver>
<connectionString>jdbc:mysql://localhost/mycontentsite?user=root&password=</connectionString>
</jdbcProvider>
<provider>
<auth>
<className>org.jivesoftware.openfire.auth.JDBCAuthProvider</className>
</auth>
<user>
<className>org.jivesoftware.openfire.user.JDBCUserProvider</className>
</user>
</provider>
<jdbcAuthProvider>
<passwordSQL>SELECT my_password FROM mymembers WHERE my_username=?</passwordSQL>
<passwordType>md5</passwordType>
</jdbcAuthProvider>
<jdbcUserProvider>
<loadUserSQL>SELECT first_name,email FROM mymembers WHERE my_username=?</loadUserSQL>
<userCountSQL>SELECT COUNT(*) FROM mymembers</userCountSQL>
<allUsersSQL>SELECT my_username FROM mymembers</allUsersSQL>
<searchSQL>SELECT my_username FROM mymembers WHERE</searchSQL>
<usernameField>my_username</usernameField>
<nameField>first_name</nameField>
<emailField>email</emailField>
</jdbcUserProvider>
<setup>true</setup>
<admin>
<authorizedUsernames>janedoe, johndoe123</authorizedUsernames>
</admin>
</jive>
|
Убедитесь, что вы используете DefaultConnectionProvider
вместо EmbeddedConnectionProvider
затем закройте Openfire и перезапустите его. Попытайтесь войти в консоль администратора в качестве члена базы данных вашего сайта. Я вошел как Джон Доу. Если все сделано правильно, вы должны вернуться в консоль администратора, а имя пользователя должно быть в верхнем правом углу домашней страницы.
Примечание. Прежде чем двигаться дальше, убедитесь, что все свойства в файле openfire.xml отображаются в системных свойствах консоли администратора. Если это не так, теперь вы знаете, как добавить их вручную.
Шаг 5: PHP
Нам нужно использовать PHP для сбора данных из базы данных MySQL и представления данных во Flash. Для тех из вас, кто плохо знаком с PHP, я кратко объясню, что выполняет каждый скрипт. Давайте начнем с класса MySQLConnection
.
Класс MySQLConnection
подключается и отключается от базы данных MySQL.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
class MySQLConnection {
private $db_host = «localhost»;
private $db_user = «root»;
private $db_pass = «»;
private $db_name = «mycontentsite»;
private $connected = 0;
public function connect() {
mysql_connect($this->db_host, $this->db_user, $this->db_pass) or die ( «Error: Script aborted. Could not connect to database.» );
mysql_select_db($this->db_name) or die ( «Error: Script aborted. No database selected.» );
$this->connected = 1;
session_start();
}
public function close() {
mysql_close();
$this->connected = 0;
}
public function get_connected() {
return $this->connected;
}
}
|
Класс LoginManager
обрабатывает логины пользователей. Пользователь может пройти аутентификацию, а затем войти и выйти с этим классом.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
require_once «MySQLConnection.php»;
class LoginManager {
public function __construct() {
}
public function login( $username, $password ) {
$username = strip_tags( $username );
$username = stripslashes( $username );
$username = mysql_real_escape_string( $username );
$passHash = md5( $password );
$connection = new MySQLConnection();
$connection->connect();
$sql = «SELECT * FROM mymembers WHERE my_username = ‘$username’ AND my_password = ‘$passHash’ LIMIT 1»;
$query = mysql_query( $sql );
if ($query) {
$count = mysql_num_rows( $query );
}
else {
die ( mysql_error() );
}
if ( $count > 0 ) {
while ( $row = mysql_fetch_array( $query ) ) {
$_SESSION[‘username’] = $username;
$_SESSION[‘pw’] = $password;
$uid = $row[‘uid’];
session_name( $username . $uid );
setcookie( session_name(), », time() + 42000, ‘/’ );
$connection->close();
die ( «login=1» );
}
die ( «login=0&error=Invalid username or password» );
}
else {
$connection->close();
die ( «login=0&error=Invalid username or password» );
}
}
public function checkLogin() {
if ( isset ( $_SESSION[‘username’] ) && isset ( $_SESSION[‘pw’] ) ) {
$user = $_SESSION[‘username’];
$pw = $_SESSION[‘pw’];
die ( «login=1&username=$user&password=$pw» );
}
else {
die ( «login=0» );
}
}
public function logout() {
setcookie(session_name(), », time() — 42000, ‘/’);
if ( isset( $_SESSION[‘username’] ) ) unset( $_SESSION[‘username’] );
if ( isset( $_SESSION[‘pw’] ) ) unset( $_SESSION[‘pw’] );
//Destroy session
session_destroy();
//return result to Flash (swf)
die («logout=1»);
}
}
|
Мы вызываем login.php для входа в систему и logout.php для выхода из системы с LoginManager
класса LoginManager
. Чтобы проверить, вошел ли пользователь в систему, мы вызываем скрипт check_login.php .
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
// login.php
require_once «classes/LoginManager.php»;
if (isset($_POST[‘username’]) && $_POST[‘password’]) {
login();
}
function login() {
$username = $_POST[‘username’];
$password = $_POST[‘password’];
unset($_POST[‘username’]);
unset($_POST[‘password’]);
$login = new LoginManager();
$login->login( $username, $password) ;
}
|
1
2
3
4
5
6
7
|
// logout.php
require_once «classes/LoginManager.php»;
$login = new LoginManager();
$login->logout();
|
01
02
03
04
05
06
07
08
09
10
|
// check_login.php
require_once «classes/LoginManager.php»;
session_start();
$login = new LoginManager();
$login->checkLogin();
exit();
|
Последний скрипт, который вызывается из ActionScript, — это скрипт grab_user_data.php, который используется для выбора данных пользователя из нашей базы данных MySQL.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
require_once «classes/MySQLConnection.php»;
if ( isset( $_POST[‘username’] ) ) {
$connection = new MySQLConnection();
$connection->connect();
$username = $_POST[‘username’];
$sql = «SELECT * FROM mymembers WHERE my_username = ‘$username’ LIMIT 1»;
$query = mysql_query( $sql );
while ( $row = mysql_fetch_array( $query ) ) {
$uid = $row[‘uid’];
$xml = ‘<user id=»‘ . $uid . ‘»>’ .
$xml .= » <firstName>» .
$xml .= » <lastName>» .
$xml .= » <country>» .
$xml .= » <statusMessage>» .
$xml .= «</user>\n»;
}
echo $xml;
$connection->close();
exit();
}
|
Эти PHP-скрипты играют очень важную роль в нашем приложении, но они очень просты.
Шаг 6: Настройка Flash
Откройте Flash Professional. Установите класс документа в ChatApp . Установите размер stage
на 550 х 400 .
Примечание. Мне нравится использовать частоту кадров 30 кадров в секунду, но в нашем приложении нет анимации, поэтому вы можете использовать любую частоту кадров, которая вам больше подходит.
Шаг 7: Создайте пользовательский интерфейс клиента
На панели « Компоненты» выберите и перетащите компонент « Button
на stage
. Расположите кнопку так, чтобы она находилась в верхнем правом углу stage
. Установите имя экземпляра кнопки logoutBtn
. Добавьте еще одну кнопку в правом нижнем углу stage
и установите имя ее экземпляра sendBtn
.
Добавить компонент List
на сцену. Поместите его прямо под logoutBtn
и измените размер компонента так, чтобы он logoutBtn
между обеими кнопками. Установите его имя экземпляра в list
.
Мы будем использовать logoutBtn
для выхода из сеанса пользователя и sendBtn
чтобы позволить нашим пользователям отправлять сообщения. List
отобразит всех онлайн-пользователей в текущей комнате чата. При щелчке элемента в списке будет загружен профиль пользователя.
Теперь нам нужен компонент, который будет отображать входящие и исходящие сообщения чата, а также текстовое поле, которое наши пользователи могут использовать для ввода новых сообщений. Добавьте компонент TextArea
на stage
. Измените размер и разместите его так, чтобы он занимал большую часть оставшейся части stage
, оставляя место для входного текстового поля в нижней части, sendBtn
высоте sendBtn
. Установите имя экземпляра для displayTxt
.
Наконец, нам нужно добавить компонент TextInput
на stage
. Расположите компонент непосредственно под displayTxt
и слева от sendBtn
. Установите имя экземпляра для inputTxt
.
Выберите все компоненты на stage
. Преобразовать выделение в символ. Символом должен быть MovieClip
именем UserInterface
. Выберите опцию « Экспорт для ActionScript» . Поле Class должно читаться как UserInterface . Установите имя экземпляра для нашего нового символа в ui
. Наконец, назовите текущий слой основного интерфейса временной шкалы. Это поможет вам лучше организовать свой проект.
Шаг 8: Экран входа
Наше приложение чата будет бесполезным, если пользователь не сможет войти в наше приложение. Давайте создадим экран входа в систему. Создайте новый слой на главной временной шкале. Назовите логин слоя.
Используя инструмент « Прямоугольник» , нарисуйте прямоугольник на stage
того же размера, что и stage
. Сток прямоугольника должен быть установлен в 0
. Прямоугольник не должен иметь линию и должен быть закрашен черным с прозрачностью 50% .
Выделите черный прямоугольник и преобразуйте его в новый символ MovieClip
под названием DarkBox . Мы будем использовать этот объект для затемнения экрана во время отображения компонентов входа. Установите имя DarkBox
объекта darkBox
.
Добавьте на InputText
два компонента InputText
, два компонента Label
и компонент Button
. Убедитесь, что вы не добавляете эти объекты поверх слоя интерфейса . Расположите компоненты, как они есть на изображении ниже, с полем имени пользователя, а затем с паролем.
Установите имя экземпляра первой userLabel
и установите имя экземпляра второй метки passLabel
. Установите имя экземпляра для первого входного текстового поля usernameTxt
и установите имя экземпляра второго входного текстового поля passwordTxt
. Установите имя экземпляра кнопки loginBtn
.
Используйте инструмент « Текст» для добавления динамического текстового поля на stage
. Установите размер текста 18 и сделайте цвет текста красным. Установите имя экземпляра в errorTxt
. Поместите errorTxt
под loginBtn
как показано ниже.
Мы собираемся преобразовать все на уровне входа в систему в один новый символ MovieClip
именем LoginScreen, но прежде чем мы сможем это сделать, нам нужно заблокировать все на уровне интерфейса, чтобы мы случайно не выбрали объект этого слоя. Заблокируйте интерфейсный слой, нажав кнопку « Слой блокировки» рядом со слоем. Когда слой заблокирован, вы увидите символ замка рядом со слоем.
Теперь вы можете безопасно выбрать все объекты в слое login
в login
который мы только что создали, и преобразовать выделение в символ со связью LoginScreen
. Установите имя экземпляра в loginScreen
.
Шаг 9: Создание окна профиля
Блокировка и скрытие всех текущих слоев на Main Timeline
. Создайте новый слой и назовите его информацией профиля .
Примечание: это просто слой, который мы будем использовать для разработки. Вы можете удалить этот слой в конце этого шага, если хотите.
Используя инструмент « Прямоугольник» , нарисуйте прямоугольник с радиусом каждого угла, равным 10,00. Прямоугольник должен иметь заливку, идентичную объекту DarkBox
(черный с прозрачностью 50%) и полностью непрозрачную белую линию со значением обводки 4.00
. На изображении ниже я установил цвет сцены «Сожженный апельсин», чтобы вы могли видеть, как все должно выглядеть более четко.
Добавьте Dynamic Text
на stage
прямо над прямоугольником. Расположите и измените размер текстового поля так, чтобы оно занимало большую часть площади прямоугольника, но оставляло место для кнопки. Установите имя экземпляра текстового поля txt
. Убедитесь, что цвет текста белый, а размер текста не менее 18px
.
Добавьте новый компонент Button
поверх прямоугольника и установите имя экземпляра closeBtn
.
Выберите все объекты на информационном слое профиля и преобразуйте их в символ MovieClip
именем ProfileWindow
. Проверьте поле Export для ActionScript, чтобы этот символ имел связь с ProfileWindow
. Теперь удалите объект ProfileWindow
со stage
. Мы будем создавать этот объект с помощью кода.
Шаг 10: наш первый код ActionScript
Создайте новый файл ActionScript и назовите его ChatApp.as . Добавьте следующие строки кода в класс.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.ErrorEvent;
import flash.display.StageScaleMode;
import flash.display.StageAlign;
import flash.events.TimerEvent;
import flash.utils.Timer;
import flash.system.Security;
import flash.external.ExternalInterface;
import org.igniterealtime.xiff.core.XMPPConnection;
import org.igniterealtime.xiff.events.ConnectionSuccessEvent;
import org.igniterealtime.xiff.events.LoginEvent;
import org.igniterealtime.xiff.events.DisconnectionEvent;
import org.igniterealtime.xiff.events.XIFFErrorEvent;
import org.igniterealtime.xiff.events.RoomEvent;
import org.igniterealtime.xiff.events.IncomingDataEvent;
import org.igniterealtime.xiff.events.OutgoingDataEvent;
public class ChatApp extends Sprite {
private static const SERVER:String = «tutorial-f5d57edaa»;//= «[host name] // Your server’s host name here
private static const PORT:Number = 5222;
private static const RESOURCE:String = «MyContentSite»;//[resource name] // Resource name ex.: ==> MyJabberApp
private static const DEFAULT_ROOM:String = «Main Lobby»;
private var grabber:LoginCredentialsGrabber;
private var userData:UserDataGrabber;
private var connection:XMPPConnection;
private var requireLogin:Boolean;
private var roomName:String;
public function ChatApp() {
super();
if (stage) init()
else addEventListener(Event.ADDED_TO_STAGE, onAdded);
}
private function init():void {
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
loginScreen.visible = false;
loginScreen.userLabel.text = «username»;
loginScreen.passLabel.text = «password»;
ui.visible = false;
UserDataExtension.enable();
grabber = new LoginCredentialsGrabber();
userData = new UserDataGrabber();
var flashVars:Object = this.loaderInfo.parameters;
if ( flashVars.hasOwnProperty( «room» ) ) {
roomName = flashVars.room;
}
checkLogin();
}
private function onAdded( e:Event ):void {
removeEventListener(Event.ADDED_TO_STAGE, onAdded);
init();
}
}
}
|
В приведенном выше коде мы проверяем, существует ли этап в конструкторе класса. Если этап не существует, мы прослушиваем событие ADDED_TO_STAGE
и при onAdded
метод обработчика события onAdded
. Метод onAdded
просто прекращает прослушивание события ADDED_TO_STAGE
и вызывает метод init
. Если этап существует, мы пропускаем этот первый шаг и просто вызываем метод init
который инициализировал наше приложение.
Мы инициализируем stage
и loginScreen
и делаем объект UserInterface
( ui
) невидимым. Вы можете заметить, что enable
метод enable
из класса UserDataExtension
. Мы напишем этот класс позже, но пока просто знайте, что очень важно помнить, чтобы всегда вызывать этот метод при создании экземпляра приложения. Метод enable
регистрирует наше пользовательское расширение (класс) с помощью класса ExtensionClassRegistry
в библиотеке XIFF. Мы поговорим об этом позже.
Создайте новый экземпляр класса LoginCredentialsGrabber
и назначьте его переменной LoginCredentialsGrabber
. Также создайте экземпляр нового экземпляра класса UserDataGrabber
и назначьте его переменной userData
. Мы напишем эти классы позже также. Когда наш SWF-файл размещается на веб-странице, мы хотим, чтобы наше приложение подключалось к определенной комнате чата, связанной с содержимым на странице. Позже мы собираемся передать имя комнаты чата, к которой должно подключиться наше приложение, в параметр flashVars во время встраивания. Но сейчас мы просто сначала проверим, существует ли переменная, а затем возьмем значение и roomName
переменной roomName
. Наконец, мы запускаем метод checkLogin
который не checkLogin
пояснений.
Шаг 11: Проверка, вошел ли пользователь в систему
Запишите метод checkLogin
в классе документов ( ChatApp
).
1
2
3
4
5
|
private function checkLogin():void {
grabber.addEventListener( Event.COMPLETE, onLoginCredentials );
grabber.grab();
}
|
Как видите, метод очень прост. Это потому, что все функциональные возможности инкапсулированы в классе LoginCredentialsGrabber
. Прослушайте событие COMPLETE
которое необходимо отправить, чтобы можно было onLoginCredentials
метод обработчика события onLoginCredentials
. Вызовите метод LoginCredentialsGrabber
объекта LoginCredentialsGrabber
. Этот метод проверяет, вошел ли пользователь в систему, или, более конкретно, он проверяет, существует ли сеанс пользователя.
Далее мы напишем метод onLoginCredentials
.
( Примечание: мы все еще в классе Document.)
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
private function onLoginCredentials( e:Event ):void {
grabber.removeEventListener( Event.COMPLETE, onLoginCredentials );
if ( grabber.isLoggedIn ) {
// Connect to Openfire
ui.visible = true;
connect( grabber.username, grabber.password );
}
else {
// Display login
displayLogin();
}
}
|
Этот метод также очень прост. LoginCredentialsGrabber
проверяет, LoginCredentialsGrabber
ли пользователь в систему, захватив куки сеанса с помощью PHP. PHP передает данные объекту LoginCredentialsGrabber
и данные анализируются.
Примечание. Далее мы будем писать класс LoginCredentialsGrabber
. Все, что нам нужно сделать сейчас, это проверить, вошел ли пользователь в систему. Если это так, мы отображаем пользовательский интерфейс и вызываем метод connect для подключения к Openfire.Мы передаем имя пользователя и пароль в connect
метод в качестве обязательных параметров. Если пользователь не вошел в систему, мы отображаем экран входа в систему .
Шаг 12: Получение учетных данных
Напишите LoginCredentialsGrabber
класс.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
package {
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.net.URLVariables; import flash.net.URLRequestMethod; public class LoginCredentialsGrabber extends EventDispatcher { private static const PASSCODE: String = "letmein123" ; private var _data:*; private var _username: String ; private var _password: String ; private var _isLoggedIn: Boolean ; public function LoginCredentialsGrabber() { super (); }
public function grab(): void { var loader:URLLoader = new URLLoader(); var req:URLRequest = new URLRequest( SOURCE + "?cb=" + new Date ().time ); loader.addEventListener( Event.COMPLETE, onComplete ); loader.load( req ); }
private function onComplete( e:Event ): void { e.target.removeEventListener( Event.COMPLETE, onComplete ); _data = e.target.data; var results:URLVariables = new URLVariables( _data.toString() ); if ( results.login == "1" ) { _isLoggedIn = true ; _username = results.username; _password = results.password; }
else {
_isLoggedIn = false ; }
dispatchEvent( new Event( Event.COMPLETE ) ); }
public function get data():* { return _data; }
public function get isLoggedIn(): Boolean { return _isLoggedIn; }
public function get username(): String { return _username; }
public function get password(): String { return _password; }
}
}
|
У нас есть две константы и четыре свойства только для чтения перед методом конструктора. SOURCE
Константа является , String
что представляет местоположение скрипта PHP , который проверяет , чтобы увидеть , если пользователь вошел в систему . Мы также хранить код доступа , необходимый для выполнения PHP — скрипта в константе. Когда вызывается метод grab, URLLoader
объект загружает PHP-скрипт, передающий код доступа, URLRequest
и скрипт возвращает данные обратно во флэш-память. Данные — это набор переменных url, которые мы можем проанализировать с помощью URLVariables
класса. Если пользователь вошел в систему, сценарий PHP предоставит нам имя пользователя и пароль, чтобы мы могли использовать эту информацию для подключения пользователя к Openfire. Наконец, мы предоставляем методы получения, чтобы предоставить доступ только для чтения к внешнему коду.
Шаг 13: Откройте экран входа
Напишите displayLogin
метод в классе Document (ChatApp.as).
1
2
3
4
5
6
|
private function displayLogin(): void { // Displays the login screen loginScreen.visible = true ; loginScreen.addEventListener( LoginManager.LOGIN, onLoggingIn ); }
|
Мы устанавливаем loginScreen
свойство visible для true
и ожидаем события loginScreen
отправки LOGIN
. Затем onLoggingIn
метод вызывается. Давайте напишем этот метод сейчас.
1
2
3
4
5
|
private function onLoggingIn( e:Event ): void { ui.visible = true ; connect( loginScreen.manager.username, loginScreen.manager.password ); }
|
Мы делаем пользовательский интерфейс видимым, а затем вызываем метод connect.
Важно: Обратите внимание , что мы используем имя пользователя и пароль от loginScreen
«s manager
объекта ( loginScreen.manager
) вместо grabber
имени пользователя и пароля , как мы это делали в объекте onLoginCredentials
метода.
Шаг 14: Подключение к Openfire
Наконец, мы можем написать connect
метод. Метод принимает два обязательных параметра: первый — это имя пользователя пользователя, а второй — пароль пользователя.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
private function connect( username: String , password: String ): void { connection = new XMPPConnection(); connection.username = username; connection.password = password; connection.server = SERVER; connection.port = PORT; connection.resource = RESOURCE; connection.addEventListener( ConnectionSuccessEvent.CONNECT_SUCCESS, onConnected ); connection.addEventListener( LoginEvent.LOGIN, onLogin ); connection.addEventListener( DisconnectionEvent.DISCONNECT, onDisconnected ); connection.addEventListener( XIFFErrorEvent.XIFF_ERROR, onXiffError ); connection.addEventListener( IncomingDataEvent.INCOMING_DATA, onIncomingData ); connection.addEventListener( OutgoingDataEvent.OUTGOING_DATA, onOutgoingData ); connection.connect( XMPPConnection.STREAM_TYPE_FLASH ); }
|
Создайте XMPPConnection
объект и назначьте его connection
переменной. Установите имя пользователя и пароль пользователя вместе с сервером ( SERVER
) и resource
( RESOURCE
). Затем добавьте прослушиватели событий в объект подключения. Наконец мы вызываем connect
метод XMPPConnection
объекта.
Напишите следующие методы:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
private function onConnected( e:ConnectionSuccessEvent ): void { trace ( "connected" ); }
private function onLogin( e:LoginEvent ): void { trace ( "logged in" ); ui.connection = connection; grabUserData(); startTimer(); }
private function onDisconnected( e:DisconnectionEvent ): void { trace ( "disconnected" ); loginScreen.visible = true ; ui.visible = false ; loginScreen.displayError( "disconnected" ); }
private function onXiffError( e:XIFFErrorEvent ): void { trace ( "Error: " + e.errorMessage); if ( loginScreen.visible ) loginScreen.displayError( e.errorMessage ); }
private function onIncomingData( e:IncomingDataEvent ): void { trace ( e.data.toString() ); }
private function onOutgoingData( e:OutgoingDataEvent ): void { trace ( e.data.toString() ); }
|
onConnected
, onIncomingData
И onOutgoingData
методы могут иметь множество различных применений , но для этого урока мы будем использовать их только для отслеживания вывода , так что мы можем отладить приложение , когда и если необходимо (особенно , если их проблема подключения к серверу). onDisconnected
Метод делает loginScreen
видимый и выводит сообщение об ошибке пользователя заявляющих им , что их связь была потеряна. onLogin
Метод подготавливает пользовательский интерфейс для XMPP чата путем присвоения XMPPConnection
объекта в connection
собственность в пределах ui
объекта. Это позволяет UserInterface
объекту вызывать методы непосредственно изXMPPConnection
объект через ссылку. Теперь, когда пользователь вошел в систему на сервере Jabber (Openfire), мы можем начать работу по регистрации пользователя в чате, но сначала нам нужно точно определить, кто наш пользователь. Мы вызываем grabUserData
метод для этого. Наконец мы вызываем startTimer
метод.
Шаг 15: Сбор пользовательских данных
К настоящему времени cookie сеанса пользователя хранятся в браузере пользователя, и пользователь регистрируется на сервере Jabber. Теперь нам нужно получить основную информацию о нашем пользователе. Мы знаем имя пользователя пользователя, поэтому мы можем использовать его для доступа к дополнительной информации о пользователе, которая хранится в нашей базе данных MySQL. Создайте grabUserData
метод в классе Document.
1
2
3
4
5
|
private function grabUserData(): void { userData.addEventListener( Event.COMPLETE, joinRoom ); userData.grab( connection.username ); }
|
Вся магия происходит в UserDataGrabber
классе. Все, что нам нужно сделать, это вызвать grab
метод и прослушать COMPLETE
событие. Обратите внимание, что grab
метод UserDataGrabber
объекта принимает один параметр: имя пользователя пользователя. Используйте имя пользователя из connection
экземпляра.
У этого класса сейчас не было бы никакой магии, потому что его еще не существует Давайте напишем этот класс сейчас. Создайте новый класс с именем, UserDataGrabber
который расширяет flash.events.EventDispatcher
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
package {
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.net.URLVariables; public class UserDataGrabber extends EventDispatcher { private static const SOURCE: String = "http://localhost/mycontentsite/scripts/grab_user_data.php" ; // Replace with your own php script private var _data:*; private var _uid: String ; private var _firstName: String ; private var _lastName: String ; private var _username: String ; private var _country: String ; private var _statusMessage: String ; public function UserDataGrabber() { super (); }
public function grab( username: String ): void { var loader:URLLoader = new URLLoader(); var req:URLRequest = new URLRequest( SOURCE + "?cb=" + new Date ().time ); var var s:URLVariables = new URLVariables(); _username = username; var s.username = username; req.data = var s; req.method = "POST" ; loader.addEventListener( Event.COMPLETE, onComplete ); loader.load( req ); }
private function onComplete( e:Event ): void { e.target.removeEventListener( Event.COMPLETE, onComplete ); _data = e.target.data; trace ( "User Data:\n" + data ); var user:XML = new XML( _data ); _uid = [email protected](); _firstName = user.firstName.toString(); _lastName = user.lastName.toString(); _country = user.country.toString(); _statusMessage = user.statusMessage.toString(); dispatchEvent( new Event( Event.COMPLETE ) ); }
public function get data():* { return _data; }
public function get uid(): String { return _uid; }
public function get firstName(): String { return _firstName; }
public function get lastName(): String { return _lastName; }
public function get username(): String { return _username; }
public function get country(): String { return _country; }
public function get statusMessage(): String { return _statusMessage; }
}
}
|
Сначала мы создаем const, который хранит местоположение PHP-скрипта в виде строки. Мы создали скрипт grab_user_data.php ранее. Это сценарий, который выполняет запрос в базе данных, используя указанное имя пользователя для извлечения и вывода данных пользователя в виде XML.
Далее мы создаем наши переменные. Я всегда помещаю подчеркивание ( _
) перед именем любой закрытой или защищенной переменной (свойства), которая будет доступна только для чтения — o, r в некоторых редких случаях только для записи . Все переменные в этом классе только для чтения . Мы используем методы получения, чтобы разрешить доступ только для чтения к каждой переменной.
Все переменные устанавливаются, когда данные xml, которые выводятся из файла php, анализируются, за исключением _username
переменной, которая задается из username
параметра grab
метода.
Теперь о grab
методе. Ничего сложного здесь нет. Создайте новый URLLoader
объект, новый URLRequest
объект и новый URLVariables
объект. URLRequest
Конструктор принимает один параметр URL , который вы хотите загрузить данные. В этом случае URL сохраняется в SOURCE
константе. Теперь я уверен, что вы заметили, что строка ? Cb =, связанная с текущим временем, была объединена с SOURCE
. Центибар выступает за кэш попойки. Это предотвращает загрузку нашего скрипта из кеша (памяти).
Инициализируйте URLRequest
объект и URLVariables
объект. URLVariables
Объект считает , что username
переменная, сценарий РНР должен выполнить запрос в базу данных. Эта переменная передается вместе с URLRequest
. Вызов URLLoader
«s load
метод и прослушивайте COMPLETE
события должны быть отправлены из , loader
так что onComplete
метод обработчика событий можно назвать.
В onComplete
методе создайте новый XML
объект. Передайте данные, назначенные от URLLoader
объекта ( e.target
в этом случае) в параметр конструктора. Установите переменные класса и отправьте COMPLETE
событие.
Шаг 16: Класс LoginScreen
До этого момента мы предполагали, что пользователь уже вошел в систему. Если пользователь не вошел в систему, мы отображаем loginScreen
. LoginScreen
Класс будет иметь методы , инкапсулированные в нем , которые обрабатывают состояние входа пользователя. Создайте класс LoginScreen. Класс должен расширять MovieClip
класс, поскольку он связан с символом библиотеки этого типа.
001
002
003
004
005
006
007
008
009
010
011
012
013
014
+015
016
+017
018
019
020
021
022
023
024
025
026
027
028
029
+030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
+055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
|
package {
import flash.display.MovieClip;
import flash.events.MouseEvent;
import flash.events.Event;
import flash.events.ErrorEvent; import flash.events.KeyboardEvent;
import flash.ui.Keyboard; public class LoginScreen extends MovieClip { public var manager:LoginManager; public function LoginScreen() { super (); manager = new LoginManager(); init();
}
public function init(): void { userLabel.text = "username:" ; passLabel.text = "password:" ; errorTxt.selectable = false ; passwordTxt.displayAsPassword = true ; loginBtn. label = "Login" ; loginBtn.addEventListener( MouseEvent.CLICK, login ); stage.addEventListener( KeyboardEvent.KEY_DOWN, login ); stage.addEventListener( Event.RESIZE, onStageResize ); }
private function login( e:Event ): void { loginBtn.removeEventListener( MouseEvent.CLICK, login ); stage.removeEventListener( KeyboardEvent.KEY_DOWN, login ); if ( e is KeyboardEvent ) { var ke:KeyboardEvent = e as KeyboardEvent; if ( ke.keyCode != Keyboard.ENTER ) { loginBtn.addEventListener( MouseEvent.CLICK, login ); stage.addEventListener( KeyboardEvent.KEY_DOWN, login ); return;
}
}
if ( usernameTxt.length > 0 && passwordTxt.length > 0 ) { if (!manager) manager = new LoginManager(); manager.addEventListener( Event.COMPLETE , onLogin ); manager.addEventListener( ErrorEvent.ERROR , onLoginError ); manager.login( usernameTxt.text, passwordTxt.text ); }
else if ( usernameTxt.length == 0 ) { // Display error errorTxt.text = "Please enter your username" ; }
else {
// Display error errorTxt.text = "Please enter your password" ; }
loginBtn.addEventListener( MouseEvent.CLICK, login ); stage.addEventListener( KeyboardEvent.KEY_DOWN, login ); }
private function onLogin( e:Event ): void { manager.removeEventListener( Event.COMPLETE , onLogin ); manager.removeEventListener( ErrorEvent.ERROR , onLoginError ); stage.removeEventListener( KeyboardEvent.KEY_DOWN, login ); visible = false ; dispatchEvent( new Event( LoginManager.LOGIN ) ); }
private function onLoginError( e:ErrorEvent ): void { manager.removeEventListener( Event.COMPLETE , onLogin ); manager.removeEventListener( ErrorEvent.ERROR , onLoginError ); errorTxt.text = e.text; loginBtn.addEventListener( MouseEvent.CLICK, login ); stage.addEventListener( KeyboardEvent.KEY_DOWN, login ); }
private function onStageResize( e:Event ): void { darkBox.width = stage.stageWidth; darkBox.height = stage.stageHeight; }
public function displayError( error: String ): void { errorTxt.text = error; }
}
}
|
Мы начинаем класс с создания нового LoginManager
объекта. Мы напишем этот класс на следующем шаге, но пока просто знайте, что этот класс управляет входом пользователя в систему.
Функционально этот класс довольно прост. Мы инициализируем отображение, и когда пользователь нажимает клавишу Enter или щелкает, loginBtn
мы проверяем, набрали ли они что-нибудь в текстовые поля. Если это так, мы используем их manager
для входа. В противном случае мы отображаем ошибку. Мы отображаем ошибки пользователю, вызывая displayError
метод и передавая сообщение в качестве String
параметра метода. Если LoginManager
объект отправляет ErrorEvent
, сообщение об ошибке будет отображаться пользователю. Напротив, если LoginManager
объект отправляет COMPLETE
событие, мы отправляем LOGIN
событие и делаем нашего LoginScreen
невидимым.
Шаг 17: Класс LoginManager
LoginManager
Класс регистрирует пользователя и выход. Создать LoginManager
класс.
001
002
003
004
005
006
007
008
009
010
011
012
013
014
+015
016
+017
018
019
020
021
022
023
024
025
026
027
028
029
+030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
+055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
|
package {
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.ErrorEvent; import flash.net.*;
import org.igniterealtime.xiff.bookmark.UrlBookmark; public class LoginManager extends EventDispatcher { private static const LOGIN_SOURCE: String = "http://localhost/mycontentsite/scripts/login.php" ; // Your website's login script location private var _data:*; private var _isLoggedIn: Boolean ; private var _username: String ; private var _password: String ; public static const LOGIN: String = "login" ; public function LoginManager() { super (); }
public function login( username: String , password: String ): void { var loader:URLLoader = new URLLoader(); var req:URLRequest = new URLRequest( LOGIN_SOURCE + "?cb=" + new Date ().time ); var var s:URLVariables = new URLVariables(); _username = username; _password = password; var s.username = username; var s.password = password; req.data = var s; req.method = URLRequestMethod.POST; loader.addEventListener( Event.COMPLETE, onLoginComplete ); loader.load( req ); }
public function logout(): void { var loader:URLLoader = new URLLoader(); var req:URLRequest = new URLRequest( LOGOUT_SOURCE ); loader.addEventListener( Event.COMPLETE, onLogoutComplete ); loader.load( req ); }
private function onLoginComplete( e:Event ): void { e.target.removeEventListener( Event.COMPLETE, onLoginComplete ); _data = e.target.data; var results:URLVariables = new URLVariables( _data.toString() ); if ( results.login == "1" ) { _isLoggedIn = true ; }
else {
_isLoggedIn = false ; dispatchEvent( new ErrorEvent( ErrorEvent.ERROR, false , false , results.error ) ); return;
}
dispatchEvent( new Event( Event.COMPLETE ) ); }
private function onLogoutComplete( e:Event ): void { e.target.removeEventListener( Event.COMPLETE, onLogoutComplete ); var req:URLRequest = new URLRequest(); navigateToURL( new URLRequest( INDEX ), "_self" ); }
public function get data():* { return _data; }
public function get isLoggedIn(): Boolean { return _isLoggedIn; }
public function get username(): String { return _username; }
public function get password(): String { return _password; }
}
}
|
Создайте константы класса и переменные экземпляра, но пока не назначайте переменным никаких значений. login
Метод должен иметь два параметра: первый является , String
что представляет имя пользователя и второй еще один , String
который представляет пароль пользователя. Создайте и инициализируйте новый URLLoader
объект, новый URLRequest
объект и новый URLVariables
объект.
Инициализируйте URLRequest
объект и URLVariables
объект. URLVariables
Объект содержит username
переменную POST и password
переменную POST , что PHP скрипт требует для выполнения. Эти переменные передаются вместе с URLRequest
. Вызов URLLoader
«s load
метод и прослушивайте COMPLETE
события должны быть отправлены из , loader
так что onLoginComplete
метод обработчика событий можно назвать.
Сценарий login.php выводит переменную url, которая задает логическое значение (0 или 1). 1 представляет true
, конечно. Если это значение равно 1, то пользователь успешно вошел в систему. Если это значение равно 0, произошла ошибка, и мы отправляем ERROR
событие, содержащее сообщение об ошибке, отображаемое нашим PHP-скриптом. Во всех таких случаях ошибка будет неверной регистрационной информацией .
В logout
метод работает так же, за исключением браузера пользователя будет перейти к главной индексной странице нашего сайта , когда logout.php успешно регистрирует пользователя вне.
Шаг 18: Поддерживай жизнь
Сервер XMPP может быть настроен на разрыв любых соединений idel, которые все еще могут быть открыты. По умолчанию Openfire сделает это. Чтобы предотвратить это, клиент чата должен отправить пинг на сервер. В библиотеке XIFF это делается путем вызова sendKeepAlive
метода XMPPConnection
объекта. Этот метод следует вызывать как минимум раз в минуту. Возможно, вы помните, что мы вызывали метод внутри класса Document ( ChatApp
) startTimer
. Этот метод не был определен в то время. Давайте создадим это сейчас.
1
2
3
4
5
6
7
|
private function startTimer(): void { var aliveTimer:Timer = new Timer( 1000 * 60 , 0 ); aliveTimer.addEventListener( TimerEvent.TIMER, keepAlive ); aliveTimer.start(); trace ( "starting alive timer" ); }
|
startTimer
Метод создает новый Timer
объект, прослушивает TIMER
событие, затем вызывает start
метод на новом Timer
объекте.
Теперь давайте создадим keepAlive
метод , который вызывается , когда aliveTimer
«S TIMER
отправляется событие.
1
2
3
4
|
private function keepAlive( e:TimerEvent ): void { connection.sendKeepAlive(); }
|
Просто вызовите метод XMPPConnection
объекта keepAlive
. Ничего больше.
Шаг 19: класс UserInterface
Создайте joinRoom
метод в классе Document.
1
2
3
4
5
6
|
private function joinRoom( e:Event ): void { userData.removeEventListener( Event.COMPLETE, joinRoom ); if ( !roomName ) roomName = DEFAULT_ROOM; ui.joinRoom( connection, userData, roomName ); }
|
Если вы не можете вспомнить, joinRoom
метод вызывается, когда пользовательские данные были загружены (когда мы знаем, кто пользователь). У UserInterface
класса также будет метод, joinRoom
который мы будем вызывать из joinRoom
метода класса Document . Прежде чем вызвать метод на ui
объекте. Мы проверяем, что roomName
переменная была установлена. Если по какой-то причине этого не произошло, мы устанавливаем номер по умолчанию, который в данном случае является главным лобби.
Создайте новый класс и назовите его UserInterface
. Убедитесь, что он расширяет класс MovieClip. Добавьте следующее в путь к классу.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
import flash.display.MovieClip;
import flash.events.Event;
import fl.data.DataProvider; import org.igniterealtime.xiff.core.XMPPConnection; import org.igniterealtime.xiff.core.EscapedJID; import fl.controls.List; import org.igniterealtime.xiff.events.RoomEvent; import org.igniterealtime.xiff.conference.Room; import org.igniterealtime.xiff.core.UnescapedJID; import flash.events.MouseEvent;
import org.igniterealtime.xiff.data.Message; import org.igniterealtime.xiff.data.Presence; import flash.events.KeyboardEvent;
import flash.ui.Keyboard; import flash.display.MovieClip;
|
Определите следующие переменные.
01
02
03
04
05
06
07
08
09
10
11
|
private var dp:DataProvider; private var profileData:Vector.< String >; private var names:Vector.< String >; private var usernames:Vector.< String >; private var items:Vector.< Object >; private var darkBox:DarkBox; private var room:Room; public var connection:XMPPConnection; public static const SERVER_NAME: String = "[ your server's name ]" ; |
Нам нужно DataProvider
отобразить всех пользователей, которые находятся в текущей комнате чата в пределах List
объекта. Будут хранить разные наборы данных в Vector
объектах. DarkBox
Ранее мы создали объект, который будет отображаться при открытии окна профиля. Последняя приватная переменная, которая нам нужна, — это org.igniterealtime.xiff.conference.Room
объект, который, на самом деле, если вы еще не догадались, это наш чат.
XMPPConnection
Объект ( connection
) был установлен в пределах класса документа в onLogin
методе обработчика событий. Это просто ссылка на XMPPConnection
объект в классе Document.
Создайте конструктор класса и init
метод.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
public function UserInterface() { super (); dp = new DataProvider(); profileData = new Vector.< String >(); names = new Vector.< String >(); usernames = new Vector.< String >(); items = new Vector.< Object >(); darkBox = new DarkBox(); init();
}
private function init(): void { UserDataExtensionManager.onUserData = onUserDataExtension; disable(); list.dataProvider = dp; list.addEventListener( Event.CHANGE, onGuestSelected ); displayTxt.editable = false ; sendBtn. label = "Send" ; logoutBtn. label = "Logout" ; darkBox.visible = false ; addChild( darkBox ); positionContents(); stage.addEventListener( Event.RESIZE, onStageResize ); logoutBtn.addEventListener( MouseEvent.CLICK, logout ); }
|
Все довольно просто в конструкторе. Просто создайте новые экземпляры каждого объекта и назначьте их соответствующей переменной. init
Метод выполняет несколько задач. Во-первых, мы должны сообщить UserDataExtensionManager
классу, что мы хотели бы получить и обработать расширение пользовательских данных в текущем экземпляре UserInterface
класса. Есть разные способы обработки входящих пользовательских расширений, но мне нравится позволять классу, связанному с пользовательским расширением, обрабатывать пользовательское расширение. Вы также можете прослушивать входящие данные, чтобы вы могли проанализировать данные самостоятельно или проверить, было ли определенное расширение присоединено к входящему XMPPStanza
(например, Message
Stanza), и извлечь расширение непосредственно из его родительского XMPPStanza
объекта. Вот пример.
Важный: два метода, которые следуют, являются только гипотетическими примерами. Реализуйте их в свои собственные сценарии по своему усмотрению.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
// example
private function onMessage( e:MessageEvent ): void { // Some block of code to display the message var msg:Message = e.data as Message; var extensions: Array = msg.getAllExtensions(); if ( extensions ) { for each ( var ext:Extension in extensions ) { switch ( ext.get_NS() ) { case "mysite:extensions:mycustomextension" : break;
default : // Handle invalid extension }
}
}
}
|
01
02
03
04
05
06
07
08
09
10
11
12
13
|
// another example private function onMessage( e:MessageEvent ): void { // Some block of code to display the message var msg:Message = e.data as Message; var ext:Extension = msg.getExtension( "myCustomExtension" ); if ( ext ) { // Handle extension code }
}
|
Но опять же, в этом уроке мы будем использовать мой метод экспресс-доставки, поскольку одновременно будет происходить только одно соединение. В противном случае мы будем реализовывать один из вышеуказанных методов. Мы будем написание UserDataExtension
и UserDataExtensionManager
классов в следующем шаге.
Изначально мы хотим, ui
чтобы функция была отключена, поэтому мы вызываем disable
метод, который отключает все компоненты интерфейса из init
метода. Наконец мы инициализируем отображение и DataProvider
объект.
Напишите следующие методы в UserInterface
классе.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
private function positionContents(): void { displayTxt.width = stage.stageWidth - list.width - displayTxt.x - 10 - 10 - 10 ; list.x = displayTxt.y + displayTxt.width + 10 ; inputTxt.width = displayTxt.width; sendBtn.x = list.x; logoutBtn.x = list.x; displayTxt.height = stage.stageHeight - inputTxt.height - 10 - 10 - 10 ; list.height = displayTxt.height - logoutBtn.height - 10 ; inputTxt.y = displayTxt.height + displayTxt.y + 10 ; sendBtn.y = inputTxt.y; logoutBtn.y = displayTxt.y; darkBox.width = stage.stageWidth; darkBox.height = stage.stageHeight; }
private function onStageResize( e:Event ): void { positionContents(); }
public function joinRoom( connection:XMPPConnection, userData:UserDataGrabber, roomName: String ): void { if ( connection.isLoggedIn() ) { trace ( "joining room..." ); var id: String = roomName.toLowerCase().replace( " " , "" ); var ext:UserDataExtension = new UserDataExtension( null , userData ); room = new Room( connection ); room.roomJID = new UnescapedJID( id + "@conference." + SERVER_NAME ); room.nickname = userData.username; room.addEventListener( RoomEvent.ROOM_JOIN, onRoomJoin ); room.addEventListener( RoomEvent.USER_JOIN, onUserJoined ); room.addEventListener( RoomEvent.GROUP_MESSAGE, onGroupMessage ); room.addEventListener( RoomEvent.USER_DEPARTURE, onUserLeave ); room.join( false , [ ext ] ); }
else {
trace ( "Must be logged in to enter a chat room." ); }
}
private function enable(): void { list.enabled = true ; sendBtn.enabled = true ; inputTxt.enabled = true ; displayTxt.enabled = true ; list.dataProvider = dp; }
private function disable(): void { list.enabled = false ; sendBtn.enabled = false ; inputTxt.enabled = false ; displayTxt.enabled = false ; }
|
Все вышеперечисленные методы довольно понятны. Мы говорили о joinRoom
методе ранее. Теперь, когда у нас есть этот метод написан, мы можем более внимательно посмотреть. После того, как мы проверим, что connection
все еще вошли в систему, мы присоединяемся к комнате. Сначала мы изменим roomName
так, чтобы мы могли сделать правильный jid, чтобы мы могли подключиться к нужной комнате. Затем мы создаем новый UserDataExtension
объект. Опять же, мы еще не создали его, но конструктор примет UserDataGrabber
объект в качестве обязательного параметра. Подробнее об этом позже.
Создайте Room
объект, передающий соединение, в качестве параметра, который требуется конструктору. В комнате UnescapedJID
должны быть установлены и псевдоним текущего пользователя. Вы можете использовать любое имя, например имя и фамилию пользователя, но я решил использовать только имя пользователя. После прослушивания различных событий мы вызываем join
метод Room
объекта.
Метод принимает два параметра. Первый — это Boolean
представление о том, хотите ли вы создать и настроить зарезервированную комнату. Установите это значение false
для, потому что мы уже создали и настроили наши чаты в Openfire. Второй параметр Array
содержит любые пользовательские расширения, которые вы хотели бы передать вместе с Присутствием пользователя при входе в чат-комнату. Передайте массив, содержащий экземпляр UserDataExtension
объекта, который мы только что создали, в этот параметр.
Давайте закончим урок.
001
002
003
004
005
006
007
008
009
010
011
012
013
014
+015
016
+017
018
019
020
021
022
023
024
025
026
027
028
029
+030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
+055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
private function addMessage( msg: String , from: String ): void { var now: Date = new Date (); var nHours: Number = now.hours; var sMin: String = now.minutes.toString(); if ( sMin.length == 1 ) sMin = "0" + sMin; var ampm: String = "AM" ; if ( nHours > 12 ) { nHours -= 12 ; ampm = "PM" ; }
var time: String = String ( nHours ) + ":" + sMin + " " + ampm; var txt: String = "[ " + from + " ] " + time + " ==> " + msg + "\n" ; displayTxt.appendText( txt ); }
private function sendMessage( e:Event ): void { if ( !visible ) return ; if ( inputTxt.length > 0 ) { if ( e is KeyboardEvent ) { var ke:KeyboardEvent = e as KeyboardEvent; if ( ke.keyCode != Keyboard.ENTER ) { return;
}
}
addMessage( inputTxt.text , room.nickname ); room.sendMessage( inputTxt.text ); inputTxt.text = "" ; }
}
private function onRoomJoin( e:RoomEvent ): void { trace ( "joined room" ); enable(); sendBtn.addEventListener( MouseEvent.CLICK, sendMessage ); stage.addEventListener( KeyboardEvent.KEY_DOWN, sendMessage ); }
private function onUserJoined( e:RoomEvent ): void { var p:Presence = e.data as Presence; trace ( "user joined" ); trace ( "Presence: " + p.getNode().toString() ); trace ( e.nickname ); }
private function onGroupMessage( e:RoomEvent ): void { var msg:Message = e.data as Message; for each ( var user: String in usernames ) { if ( e.nickname == user ) { addMessage( msg.body, e.nickname ); return;
}
}
}
private function addToList( item: Object ): void { dp.addItem( item ); trace ( "adding " + name + " to list" ); }
private function removeFromList( username: String ): void { var index: int = usernames.indexOf( username ); if ( index > - 1 ) { trace ( "removing " + names[ index ] + " from the list" ); dp.removeItem( items[ index ] ); profileData[ index ] = null ; names[ index ] = null ; usernames[ index ] = null ; items[ index ] = null ; }
}
private function onGuestSelected( e:Event ): void { var user: String = list.selectedItem.value.toString(); var index: int = usernames.indexOf( user ); trace ( "Selected: " + user ); if ( index > - 1 ) { // Display Member Information darkBox.visible = true ; var data: String = profileData[ index ]; var window:ProfileWindow = new ProfileWindow(); window.text = data; window.addEventListener( ProfileWindow.DESTROYED, onDestroyed ); addChild( window ); }
function onDestroyed( e:Event ): void { window.removeEventListener( ProfileWindow.DESTROYED, onDestroyed ); window = null ; darkBox.visible = false ; }
}
private function onUserLeave( e:RoomEvent ): void { var p:Presence = e.data as Presence; var username: String = p.from.toString().replace( room.roomName + "@" + room.conferenceServer + "/" , "" ); removeFromList( username ); }
private function onUserDataExtension( ext:UserDataExtension ) { if ( usernames.indexOf( ext.username ) > - 1 || ext.username == connection.username ) return ; var name: String = ext.firstName + " " + ext.lastName; var profileText: String = name + "\n\n" ; profileText += "Username: " + ext.username + "\n" ; profileText += "Country: " + ext.country + "\n" ; profileText += "Status: " + ext.statusMessage + "\n" ; var item: Object = {}; item. label = name; item.value = ext.username profileData.push( profileText ); names.push( name ); usernames.push( ext.username ); items.push( item ); addToList( item ); }
private function logout( e:MouseEvent ): void { var manager:LoginManager = new LoginManager(); manager.logout(); }
|
Хорошо, давайте посмотрим на то, что мы только что написали. Первый метод — это addMessage
метод. Этот метод вызывается всякий раз, когда пользователь отправляет или получает сообщение. Сообщение отображается в текстовом поле и объединяется со временем, когда сообщение было отправлено или получено. sendMessage
Метод просто отправляет сообщение напечатал пользователь для всех пользователей в текущей комнате чата. Он принимает два параметра: сообщение как String
, и имя того, от кого сообщение, как другое String
. Мы показываем время, когда сообщение было отправлено или получено, и получатель с сообщением. Надеюсь, ничего сложного.
Теперь о тех таинственных методах обработчика событий, которые вызываются в соответствии с которыми RoomEvent
было отправлено. onRoomJoined
Метод вызывается всякий раз , когда текущий пользователь успешно присоединяется к чат. Все, что мы делаем, это вызываем enable
метод, который повторно включает все ранее отключенные компоненты, чтобы пользователь мог взаимодействовать с ними.
onUserJoined
Метод вызывается всякий раз , когда пользователь успешно объединяет текущую комнату. Я создал этот метод для отладки, поэтому он отслеживает только данные. Обычно вы будете прослушивать входящие сообщения и реагировать на изменение данных, но здесь мы хотим отвечать на входящие расширения пользовательских данных через нашу службу экспресс-доставки. Опять же, я обнаружил, что экспресс-доставка здесь намного проще, но вы можете попробовать извлечь пользовательские расширения непосредственно из Presence
объекта из onUserJoined
метода.
Примечание: объект присутствия e.data as Presence
в этом случае. Вы также можете проявить творческий подход к этому методу, если хотите: возможно, при воспроизведении определенного звука или всплывающем уведомлении, когда в комнату входит другой пользователь. Просто пища для размышлений.
onGroupMessage
Метод отправляется , когда входящее сообщение будет получено от другого пользователя. Сама комната (сервер XMPP) может даже отправлять сообщения. Обычно это сообщения о конфигурации или сообщения об ошибках. Вы можете рассматривать эти сообщения как административные сообщения и запретить их отображение в TextArea
компоненте как обычное сообщение чата . Может быть, вы можете отобразить эти сообщения в каком-то окне сообщений. Просто еще немного пищи для размышлений. В нашем onGroupMessage
методе мы фактически отфильтровываем любые сообщения, поступающие с сервера. Мы проверяем, что отправитель сообщения e.nickname
(в данном случае имя пользователя отправителя) находится в usernames
векторе, а затем отображаем сообщение, используя addMessage
метод.
Теперь о addToList
методе. Добавьте новый объект к List
объекту, используя DataProvider
. Вызовите addItem
метод DataProvider
объекта и передайте его item
в параметр. item
Объект должен быть примитивным Object
и обладает свойством label
(а String
). Значение также должно быть String
. Мы будем использовать имя пользователя соответствующего пользователя в качестве значения здесь. removeFromList
Метод делает только то , что вы думаете. Удаляет указанного пользователя из list
. Ничего сложного — только полная противоположность addItem
метода.
Всякий раз, когда наш пользователь нажимает на элемент в list
, мы не хотим отображать окно профиля, содержащее информацию о выбранном пользователе. onUserSelected
Метод делает именно это. Имя пользователя выбранного пользователя — это значение выбранного элемента в list
. Мы используем их имя пользователя, чтобы получить местоположение ( index
) их данных String
в profileData
Vector
.
Примечание: мы будем добавлять данные в этот Vector
объект в onUserDataExtension
методе позже.
Индекс имени пользователя в usernames
Vector
является таким же, как индекс данных пользователя, хранящихся в profileData
Vector
. Вы можете увидеть, почему в onUserDataExtension
методе, если вы смотрите вперед. Далее создается новый ProfileWindow
и отображается для пользователя. Мы ожидаем window
отправки DESTROYED
события, чтобы мы могли удалить его с дисплея. Мы должны будем написать ProfileWindow
класс позже, но на данный момент ProfileWindow
класс — это просто связь с объектом в библиотеке, которую мы создали ранее.
onUserLeave
Метод отправляется , когда пользователь выходит из комнаты чата. Когда пользователь уходит, мы удаляем его из списка, потому что мы хотим отображать только текущих пользователей. Мы вызываем removeFromList
метод для достижения этой цели.
Сейчас мы все так долго ждали. Теперь мы рассмотрим метод получения посылки, доставленной нашей службой экспресс-доставки. onUserDataExtension
Метод принимает один параметр. Десериализованный UserDataExtension
объект доставлен только что из UserDataExtensionManager
класса. UserDataExtension
Объект содержит все данные , которые нам нужно о пользователе , который только вступил в комнату. Мы строим profileData
String
заранее, чтобы нам не нужно было хранить расширения. Вместо этого вы можете хранить расширения, если хотите, но я построил данные String
до факта. Далее нам нужно перенести все данные в соответствующие Vector
и отобразить пользователя в list
. Это так просто.
Последний метод — это logout
метод. Этот метод создает новый LoginManager
объект и вызывает его logout
метод, который выводит текущего пользователя из системы (уничтожая его сеансовые куки-файлы), а затем переходит на главную страницу индекса сайта.
Вот это да!Это было довольно тяжело. Но не слишком тяжелый, я надеюсь. Если вы нашли что-то запутанное, внимательно изучите код, пока у вас не будет четкого понимания того, что происходит, прежде чем двигаться дальше. Я говорю это, потому что это подливка по сравнению со следующими разделами, которые охватывают сериализацию и десериализацию данных. Я постараюсь сделать это как можно проще.
Шаг 20. Краткий обзор сериализации данных
Прежде чем мы создадим наш UserDataExtension
класс, который занимается сериализацией и десериализацией данных, нам нужно немного поговорить на эту тему. Сериализация в этом контексте относится к получению определенных данных и преобразованию их в форму, которую мы можем использовать для отправки данных через нашу сеть через XMPP. Мы не можем просто взять UserDataGrabber
объект и отправить его по сети как UserDataGrabber
объект и ожидать, что данные будут получены на другом конце в такте. Фактически, если мы даже попытались отправить UserDataGrabber
объект, используя метод XMPPConnection
объекта send
, мы просто получим ошибку аргумента, потому что он только исключает XMPPStanza
в качестве параметра. Метод принимает только XMPPStanza
объекты ( Message
, Presence
, IQ
) в качестве параметра.
Итак, как мы решаем эту проблему? Ответ с сериализацией данных. Мы должны преобразовать объект или данные в формат, который можно отправить вместе с разделом XMPP. Так в какой формат мы должны преобразовать UserDataGrabber
объект, чтобы мы могли отправить его по нашей сети и получить другим пользователем? Ну, ответ XML. XMPP — это просто потоковый XML. Это не только легко, но и расширяемо. Но мы должны упаковать (сериализовать) данные пользователя в формат XML, чтобы они могли быть отправлены по сети и получены другими пользователями.
Таким образом, сериализация — это процесс преобразования данных в формат, который может быть сохранен / сохранен и / или отправлен по сети. С технической точки зрения, мы не будем на самом деле сериализовывать UserDataGrabber
объект, но вместо этого мы будем сериализовать содержащиеся в нем данные, но мы вполне могли бы сделать это, если бы нам это понадобилось. Все, что нам нужно для этого проекта, это данные пользователя. Я сказал, что буду делать это как можно проще, и я. Мы просто будем сериализовать примитивные данные. Чем более примитивны данные, тем легче их сериализовать. Если бы мы пытались выполнить поиск объекта в библиотеке, скажем, MovieClip
с большим количеством графики и специфичных для экземпляра свойств, сериализация была бы намного сложнее.
Шаг 21: Как мы будем десериализовать данные
До получающего конца можно взять сериализованные данные (XML) и преобразовать их обратно в исходный формат. Мы должны полностью изменить процесс. В нашем проекте данные представлены String
значениями, поэтому нам будет легко их десериализовать.
Однако десериализация немного сложнее, чем сериализация, поскольку нам нужно сделать клон полученных данных. При получении входящего расширения (в виде данных XML) UserDataExtension
объект десериализует (преобразует) данные XML обратно в String
объекты, так что что мы можем получить к ним доступ ( только для чтения ) через UserDataExtension
объект через точечный синтаксис. Нам также нужно будет проверить и убедиться, что полученные данные действительны.
Вот основные сведения о том, что мы будем делать:
- Получать входящие данные (в данном случае XML)
- Проверьте, соответствуют ли данные типу
UserDataExtension
сериализуемых данных (другими словами, проверьте, является ли это действительным расширением пользовательских данных) - Разобрать данные и сделать клон удаленного
UserDataExtension
объекта - Отправьте расширение при экспресс-доставке текущему экземпляру
UserInterface
класса, сделав данные доступными для пользователя.
После всего сказанного и сделанного мы можем перейти к написанию UserDataExtension
класса.
Шаг 22: класс UserDataExtension
Никакое введение не требуется здесь. Давайте копаться. Создайте новый класс и назовите его UserDataExtension
. Класс должен расширяться org.igniterealtime.xiff.data.Extension
. Класс должен также реализовать org.igniterealtime.xiff.data.IExtension
и org.igniterealtime.xiff.data.ISerializable
. Идем дальше и импортируем XMLNode
класс и org.igniterealtime.xiff.data.ExtensionClassRegistry
класс вместе с предыдущими классами.
1
2
3
4
5
6
7
|
import flash.xml.XMLNode; import org.igniterealtime.xiff.data.Extension; import org.igniterealtime.xiff.data.IExtension; import org.igniterealtime.xiff.data.ISerializable; import org.igniterealtime.xiff.data.ExtensionClassRegistry; public class UserDataExtension extends Extension implements IExtension, ISerializable { |
Создайте следующие переменные и константы.
01
02
03
04
05
06
07
08
09
10
11
|
private var data:UserDataGrabber; private var _uid: String ; private var _firstName: String ; private var _lastName: String ; private var _username: String ; private var _country: String ; private var _statusMessage; private var _isDeserialized: Boolean ; public static const NS: String = "mycontentsite:xmpp:extensions:userdata" ; public static const ELEMENT_NAME = "userData" ; |
Нам нужны переменные для хранения данных, полученных от другого пользователя, и двух констант класса. Один для представления уникального пространства имен ( NS
) для расширения, а другой — для имени элемента XML.
Создайте конструктор.
1
2
3
4
5
6
|
public function UserDataExtension( parent:XMLNode = null , userData:UserDataGrabber = null ) { super ( parent ); if ( userData ) data = userData; }
|
Важное замечание: Убедитесь, что для всех параметров в конструкторе задано нулевое значение по умолчанию, поскольку при регистрации нашего пользовательского расширения в ExtensionClassRegistry
экземпляре будет создан экземпляр класса, и он не получит никаких аргументов. Если конструктор не готов к получению каких-либо параметров, будет выдано сообщение об ошибке, и ваше приложение может работать неправильно.
Также передайте parent
( XMLNode
) в конструктор базового класса ( org.igniterealtime.xiff.data.Extension
). Если userData
не ноль , присвойте это data
свойству. Мы будем сериализовать информацию, содержащуюся в этом объекте, через минуту.
Прежде чем я забуду, давайте создадим enable
метод.
1
2
3
4
|
public static function enable(): void { ExtensionClassRegistry.register( UserDataExtension ); }
|
Как видите, enable
метод прост. Это статический метод, который регистрирует класс / расширение с помощью ExtensionClassRegistry
, таким образом, активируя класс / расширение.
Мы будем сериализовать данные в формат XML, как я объяснил ранее. Чтобы быть более конкретным, мы будем создавать XMLNode
объекты из наших данных. Давайте создадим метод, чтобы упростить этот процесс для нас. Создать generateNode
метод.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
private function generateNode( nodeName: String , nodeValue: String = null , attributes: Object = null ):XMLNode { var nameNode:XMLNode = new XMLNode( 1 , nodeName ); var valueNode:XMLNode; if ( nodeValue ) { valueNode = new XMLNode( 3 , nodeValue ); nameNode.appendChild( valueNode ); }
if ( attributes ) { nameNode.attributes = attributes; }
return nameNode; }
|
Это еще один очевидный метод. На основании заданных параметров он создает XMLNode
объект, уже упакованный и готовый к работе. Давайте начнем с параметров. Первый параметр, nodeName
который String
представляет собой имя элемента узла, который мы хотим создать. Вторым является другое, String
которое представляет значение, которое должен содержать узел. Последний параметр — это Object
тот, который содержит любые атрибуты (как Strings
), которые должен содержать узел. Метод объединяет для нас узел, чтобы нам не приходилось постоянно повторять эти действия.
Теперь самое интересное.Создать serialize
метод.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
public function serialize( parent:XMLNode ): Boolean { var attributes: Object = {}; attributes.xmlns = NS; attributes.id = data.uid var firstNode:XMLNode = generateNode( "firstName" , data.firstName ); var lastNode:XMLNode = generateNode( "lastName" , data.lastName ); var userNode:XMLNode = generateNode( "username" , data.username ); var countryNode:XMLNode = generateNode( "country" , data.country ); var statusNode:XMLNode = generateNode( "statusMessage" , data.statusMessage ); var mainNode:XMLNode = generateNode( "userData" , null , attributes ); mainNode.appendChild( firstNode ); mainNode.appendChild( lastNode ); mainNode.appendChild( userNode ); mainNode.appendChild( countryNode ); mainNode.appendChild( statusNode ); setNode( mainNode ); var node:XMLNode = this .getNode(); if ( node.parentNode != parent ) { parent.appendChild( node ); }
return true;
}
|
Когда наша Extension
добавляется к XMPPStanza
, вызывается этот метод. Первое, что нам нужно сделать, это упаковать наши данные в основную XMLNode
. Обратите внимание, что все узлы являются потомками mainNode
. Кроме того, если вы еще не заметили, мы только что generateNode
применили наш метод. Мы назвали элементы и установили значения каждого элемента. Элемент firstName содержит имя пользователя, элемент statusMessage содержит сообщение о статусе пользователя и т. Д. Мы только что взяли данные пользователя и сохранили (сериализовали) их внутри XML.
Чтобы завершить процесс сериализации, вызовите setNode
метод, который является методом базового класса, чтобы установить узел. Также проверьте, parentNode
совпадает ли наш номер с параметром parent
XMLNode
. Если это не так, сделайте наш узел дочерним по отношению к параметру parent
XMLNode
.
Примечание:serialize
метод является методом интерфейса , который требует типа возвращаемого ( Boolean
). Если данные были успешно сериализованы, вы захотите вернуться true
. Если по какой-то неуклюжей причине вы имеете дело с данными, которые могут быть неправильно сериализованы, этот метод всегда должен возвращаться true
. В противном случае он должен вернуться false
.
Напишите deserialize
метод:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
public function deserialize( node:XMLNode ): Boolean { if ( node.nodeName == ELEMENT_NAME && node.attributes.hasOwnProperty( "xmlns" ) && node.attributes.xmlns == NS ) { if ( node.attributes.hasOwnProperty( "id" ) ) { _uid = node.attributes.id; }
else {
trace ( "invalid node xmlns: " + node.nodeName ); return false;
}
for each ( var child:XMLNode in node.childNodes ) { switch ( child.nodeName ) { case "firstName" : var first:XML = new XML( child.toString() ); _firstName = first; break;
case "lastName" : var last:XML = new XML( child.toString() ); _lastName = last; break;
case "username" : var user:XML = new XML( child.toString() ); _username = user; break;
case "country" : var c:XML = new XML( child.toString() ); _country = c; break;
case "statusMessage" : var msg:XML = new XML( child.toString() ); _statusMessage = msg; break;
default : trace ( "invalid node child: " + node.nodeName ); return false;
}
}
if ( _firstName && _lastName && _username && _country && _statusMessage ) { // Notify the UserDataExtensionManager Class setNode ( node ); _isDeserialized = true ; UserDataExtensionManager.registerData( this ); return true;
}
else {
trace ( "invalid missing data: " + node.nodeName ); var a: Array = [firstName, lastName, username, country, statusMessage]; for each ( var el:* in a) { trace (el); }
return false;
}
}
return false;
}
|
В отличие от serialize
метода, который вызывается при добавлении данных XMPPStanza
в deserialize
метод, метод вызывается, когда пользовательское расширение получено на другом конце. Метод принимает один параметр, XMLNode
который был потомком XMPPStanza
полученного. Наша задача — проверить, был ли узел упакован с помощью нашего собственного механизма. Поэтому, если узел является нашим пользовательским расширением, перейдите к десериализации данных узла, в противном случае верните false
.
Мы используем for each
цикл для итерации каждого дочернего узла в параметре XMLNode
. Вы можете выбрать, насколько строгим будет ваше расширение. Я решил сделать наше расширение довольно строгим. Если все данные не получены, расширение возвращается false
, сообщая библиотеке XIFF, что узел недопустим и не был упакован нашим кодом.
После того, как данные XML проанализированы, мы проверяем, установлены ли все свойства; если они были установлены, мы вызываем registerData
метод UserDataExtensionManager
класса — класс, который мы создадим следующим — чтобы отправить расширение во время экспресс-доставки нашему пользователю. Наконец мы возвращаемся true
к успеху.
Два метода интерфейса не работают, и два, чтобы пойти. Вот они:
1
2
3
4
5
6
7
8
9
|
public function getNS(): String { return NS; }
public function getElementName(): String { return ELEMENT_NAME; }
|
Очень, очень, очень, очень, просто. Эти методы требуются IExtension
интерфейсом. getNS()
Метод возвращает уникальные имена расширения и getElementName()
возвращает имя элемента внутреннего абонента. Как я уже сказал: очень, очень, очень, очень, просто.
Были почти готовы. Завершите работу класса, предоставив доступ только для чтения к данным пользователя и _isDeserialized
свойству.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
public function get uid(): String { if ( data ) { return data.uid; }
else {
return _uid; }
}
public function get firstName(): String { if ( data ) { return data.firstName; }
else {
return _firstName; }
}
public function get lastName(): String { if ( data ) { return data.lastName; }
else {
return _lastName; }
}
public function get username(): String { if ( data ) { return data.username; }
else {
return _username; }
}
public function get country(): String { if ( data ) { return data.country; }
else {
return _country; }
}
public function get statusMessage(): String { if ( data ) { return data.statusMessage; }
else {
return _statusMessage; }
}
public function get isDeserialized(): Boolean { return _isDeserialized; }
|
Шаг 23 : Служба экспресс-доставки
Как я объяснил на предыдущих этапах, могут быть разные способы обработки входящих расширений, но я предпочитаю метод, который я вызываю методом экспресс-доставки. Концепция проста. Класс Helper получает расширение и отправляет расширение непосредственно определенному методу (функции), который используется для обработки расширения, но только в том случае, если метод был зарегистрирован для этого действия. Давайте создадим UserDataExtensionManager
класс.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
package {
public class UserDataExtensionManager { public static var onUserData:Function; public static function registerData( data:UserDataExtension ) { if ( data.isDeserialized ) { if ( onUserData != null ) { onUserData( data ); }
}
}
}
}
|
Этот класс очень легкий: всего 18 строк кода. registerDate
Метод вызывается, принимая Десериализованный в UserDataExtension
качестве параметра, то onUserData
метод вызывается , если он существует. В этом случае onUserData
метод является onUserDataExtension
методом из UserInterface
класса.
Шаг 24: Класс окна профиля
Чтобы пользователь мог просматривать информацию о другом пользователе, нам нужен способ отображения информации для пользователя. Я решил использовать простое окно, содержащее текстовое поле, которое мы можем использовать для отображения информации пользователю. Давайте создадим ProfileWindow
класс.
Помните: этот класс связан с MovieClip
символом в библиотеке.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
package {
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.display.DisplayObject; public class ProfileWindow extends MovieClip { public static const DESTROYED: String = "destroyed" ; public function ProfileWindow() { super (); addEventListener( Event.ADDED_TO_STAGE, onAdded ); }
private function init(): void { txt.selectable = false ; txt.wordWrap = true ; closeBtn. label = "Close" ; closeBtn.addEventListener( MouseEvent.CLICK, destroy ); stage.addEventListener( Event.RESIZE, onStageResize ); center(); }
private function center(): void { x = ( stage.stageWidth - width ) / 2 ; y = ( stage.stageHeight - height ) / 2 ; }
private function onAdded( e:Event ): void { removeEventListener( Event.ADDED_TO_STAGE, onAdded ); init();
}
private function onStageResize( e:Event ): void { center(); }
private function destroy( e:Event ) { removeEventListener( MouseEvent.CLICK, destroy ); stage.removeEventListener( Event.RESIZE, onStageResize ); if ( this .parent ) { for ( var i: int = 0 ; i < numChildren; i++ ) { removeChild( getChildAt( i ) ); }
this .parent.removeChild( this ); dispatchEvent( new Event( DESTROYED ) ); }
}
public function set text( value: String ): void { txt.text = value; }
}
}
|
Метод конструктора добавляет и прослушиватель событий, который прослушивает ADDED_TO_STAGE
событие. Когда окно добавляется в stage
, onAdded
вызывается метод, затем слушатель удаляется и init
вызывается метод.
init
Метод инициализации текстового поля и кнопку закрытия. Это также гарантирует, что окно всегда будет находиться в центре сцены.
Вы можете заметить константу класса в начале скрипта. DESTROYED
Константа представляет собой тип события , что окно будет рассылать , когда пользователь нажимает на closeBtn
. При closeBtn
нажатии вызывается метод уничтожения, все дочерние элементы удаляются, а окно удаляется из его родительского элемента. Мы могли бы создать собственный класс событий, например ProfileWindowEvent
, расширяемый flash.events.Event
, но это не было необходимо. Вместо этого мы отправляем Event
объект с типом DESTROYED
. Это просто и хорошо выполняет свою задачу.
Наконец у нас есть метод установки set text
. Это также могло бы быть читаемым и записываемым свойством, но опять же, в этом не было необходимости. Вместо этого это только для записи, потому что нам никогда не нужно читать это свойство ни в одном из остального кода. Метод устанавливает текст текстового поля таким образом, чтобы текст отображался пользователю.
Шаг 25: последний шаг
Последнее, что нам нужно сделать, чтобы наше приложение работало, — это настроить страницу HTML. Наше приложение представляет собой динамическое веб-приложение. Ранее мы захватили roomName
переменную, которая была передана во Flash; единственная проблема здесь в том, что мы еще не передали эту переменную во Flash. Вам нужно будет реализовать это в своем коде самостоятельно. Вот мой код:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
|
<?php
$flashVars = '"room=mainlobby"' ; $image = '"mainlobby.png"' ; if ( isset( $_GET [ 'content' ] ) ) { $content = $_GET [ 'content' ]; switch ( $content ) { case "box2dgame" : $roomName = "Box2d Game" ; break;
case "platformgame" : $roomName = "Platform Game" ; break;
case "mainlobby" : case "" : case null : default : $roomName = "Main Lobby" ; break;
}
$flashVars = '"room=' . $roomName . '"' ; $image = '"' . $content . '.png"' ; }
?>
<?
<!DOCTYPE html PUBLIC «-//W3C//DTD XHTML 1.0 Strict//EN» «http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd»>
<head>
<title>My Content Site</title> <meta http-equiv=»Content-Type» content=»text/html; charset=utf-8″ />
<style type= "text/css" media= "screen" > //html, body { height:100%; background-color: #ffffff;} padding:0;
}
</style>
</head>
<body>
<div id= "myContent" > <image src=<?php echo $image ; ?> alt= "game content" align= "middle" /> </div>
<div id= "flashContent" > <object classid= "clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width= "100%" height= "50%" id= "chat" align= "middle" > <param name= "movie" value= "chat.swf" /> <param name=»quality» value=»high» />
<param name= "bgcolor" value= "#ffffff" /> <param name= "play" value= "true" /> <param name= "loop" value= "true" /> <param name= "wmode" value= "window" /> <param name= "scale" value= "showall" /> <param name=»menu» value=»true» />
<param name= "devicefont" value= "false" /> <param name= "salign" value= "" /> <param name=»allowScriptAccess» value=»sameDomain» />
<param name= "flashVars" value=<?php echo $flashVars ; ?> /> <!--[ if !IE]>--> <object type= "application/x-shockwave-flash" data= "chat.swf" width= "100%" height= "50%" > <param name= "movie" value= "chat.swf" /> <param name=»quality» value=»high» />
<param name= "bgcolor" value= "#ffffff" /> <param name= "play" value= "true" /> <param name= "loop" value= "true" /> <param name= "wmode" value= "window" /> <param name= "scale" value= "showall" /> <param name=»menu» value=»true» />
<param name= "devicefont" value= "false" /> <param name= "salign" value= "" /> <param name=»allowScriptAccess» value=»sameDomain» />
<param name= "flashVars" value=<?php echo $flashVars ; ?> /> <!—<![endif]—>
<img src= "http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif" alt= "Get Adobe Flash player" /> </a>
<!--[ if !IE]>--> </object>
<!—<![endif]—>
</object>
</div>
</body>
</html>
|
Приведенный выше PHP-скрипт является лишь примером. Ваш сайт, скорее всего, будет более оригинальным и креативным. content
Переменная GET указывает, к какой комнате чата присоединиться. Затем мы передаем эту информацию во Flash, чтобы наш код знал, к какой комнате чата присоединиться.
Шаг 26: протестируйте наше приложение
Убедитесь, что в вашей системе работают WAMP и Openfire. Если все прошло хорошо, ваше приложение должно работать нормально. Протестируйте приложение в нескольких браузерах как разные пользователи. Попросите одного пользователя следовать за другим в чат-комнату, чтобы увидеть, насколько хорошо наше приложение справляется с изменением данных. Тогда пообщайся с собой. Веселитесь с этим.
Вывод
Что ж, мы многому научились, и я рад, что поделился этим временем с вами. Библиотека XIFF абсолютно потрясающая. Я надеюсь, что этот урок побудил вас полностью изучить эту невероятную библиотеку. Ты так много можешь с этим сделать. Вы можете создать частный чат и списки пользователей в нашем текущем приложении или создать совершенно новое приложение, которое подключается к другому XMPP-серверу, такому как один из серверов, перечисленных на xmpp.org . Спасибо за настройку. Увидимся в следующий раз.
Важно: Исходные файлы были настроены на основе моей собственной машины. Не забудьте перезагрузить XIFF.swc
файл в chat.fla
файл. Также не забудьте установить настройки вашего сервера в константах класса Document (ChatApp.as).