17. [TD]: تأمين خادم الويب / jSON للانتخابات
الكلمات الرئيسية: بنية متعددة الطبقات، Spring، حقن التبعيات، خدمة ويب / jSON آمنة، عميل / خادم
نطبق الآن ما تعلمناه في الفصل السابق على TD للانتخابات. ستكون البنية كما يلي:
![]() |
سيتم التقدم على النحو التالي:
- كتابة الخادم؛
- كتابة العميل بدون طبقة [ui] ولكن مع اختبار JUnit لطبقة [métier]؛
- كتابة العميل مع الطبقة [ui]؛
17.1. Support
![]() | ![]() |
ستجد مشاريع هذا الفصل في المجلد [support / chap-17]. يستخدم البرنامج النصي SQL لإنشاء قاعدة البيانات اللازمة للاختبارات.
17.2. قاعدة البيانات
يجب أن تحتوي قاعدة بيانات الخادم الآمن الآن على الجداول [USERS] و [ROLES] و [USERS_ROLES]:
![]() | ![]() |
يتوفر البرنامج النصي SQL الخاص بإنشاء قاعدة البيانات في دعم المستند.
17.3. الخادم الآمن
لإعداد الخادم الآمن للانتخابات، يكفي نسخ ولصق مشروع المثال [intro-spring-security-server-01]:
![]() |
المهمة المطلوبة: إعادة تسمية الحزم كما هو موضح في [1].
بعد ذلك، نقوم بتعديل الملف [pom.xml] بالطريقة التالية:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.elections</groupId>
<artifactId>elections-security-webjson-metier-dao-spring-data</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>elections-security-webjson-metier-dao-spring-data</name>
<description>elections spring security</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>istia.st.elections</groupId>
<artifactId>elections-webjson-metier-dao-spring-data</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- أمان Spring -->
...
</dependencies>
<!-- المكونات الإضافية -->
<build>
...
</build>
</project>
- في الأسطر 23-27، استبدل التبعية للمشروع [intro-server-webjson-01] بالتبعية للمشروع [elections-webjson-metier-dao-spring-data] الذي تمت دراسته في الفقرة 12؛
- في الأسطر 4-7، أدخل خصائص المشروع الجديد؛
يبقى لنا تعديل فئات التكوين للمشروع:
[DaoConfig]
package elections.security.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@EnableJpaRepositories(basePackages = { "elections.security.repositories" })
@ComponentScan(basePackages = { "elections.security.dao" })
@Import({ elections.dao.config.DaoConfig.class })
public class DaoConfig {
// الثوابت
final static private String[] ENTITIES_PACKAGES = { "elections.dao.entities", "elections.security.entities" };
@Bean
public String[] packagesToScan() {
return ENTITIES_PACKAGES;
}
}
التعديلات موجودة في الأسطر التالية:
- الأسطر 8 و9 و14: أدخل أسماء الحزم الصحيحة؛
- السطر 10: الفئة المستوردة هي الآن [elections.dao.config.DaoConfig] من المشروع [elections-webjson-metier-dao-spring-data]؛
ملاحظة: كما ذكرنا سابقًا، لا تعمل هذه التهيئة إلا إذا كانت الفئة [elections.dao.config.DaoConfig] تحتوي على التعليق التوضيحي [@Configuration]. يرجى التحقق من هذه النقطة.
[SecurityConfig]
package elections.security.config;
...
@EnableWebSecurity
@ComponentScan(basePackages = { "elections.security.service" })
@Import({ WebConfig.class, DaoConfig.class })
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
}
التعديلات موجودة في الأسطر التالية:
- السطر 6: قم بتغيير اسم الحزمة؛
هذا كل شيء. قم بإجراء اختبار أولي عن طريق تشغيل الاختبار JUnit []:
![]() |
قم بتشغيل فئة التمهيد [Boot] ثم باستخدام ملحق Chrome [Advanced Rest Client] قم بإجراء الاختبار التالي:
![]() |
في [1]، الطلب. في [3]، الرد. في [2]، الرأس HTTP هو رأس مصادقة المستخدم [admin, admin]: Authorization:Basic YWRtaW46YWRtaW4=
اطلب الآن، القوائم المتنافسة:
![]() |
17.4. عميل الخادم الآمن بدون الطبقة [ui]
![]() |
انسخ/الصق المشروع [elections-ui-metier-dao-webjson] في المشروع [elections-metier-dao-security-webjson].
![]() |
المهمة المطلوبة: في [1]، قم بإعادة تسمية الحزم إذا لزم الأمر وحذف تلك الموجودة في الطبقة [ui] وفئات البدء [boot].
سنقوم الآن بالعمل كما تم في الفقرة 16.5.
17.4.1. تكوين Maven
يتغير فقط جزء تعريف المشروع:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.elections</groupId>
<artifactId>elections-metier-dao-security-webjson</artifactId>
<version>0.0.1-SNAPSHOT</version>
<description>Client jUnit du serveur web / jSON</description>
<name>elections-metier-dao-security-webjson</name>
...
17.4.2. إعادة هيكلة الطبقة [métier]
![]() |
تتطور واجهة [IElectionsMetier] على النحو التالي:
package elections.security.client.metier;
import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;
public interface IElectionsMetier {
// المصادقة
public void authenticate(User user);
// الحصول على قوائم المنافسة
public ListeElectorale[] getListesElectorales(User user);
// عدد المقاعد الشاغرة
public int getNbSiegesAPourvoir(User user);
// الحد الأدنى للانتخاب
public double getSeuilElectoral(User user);
// تسجيل النتائج
public void recordResultats(User user, ListeElectorale[] listesElectorales);
// حساب المقاعد
public ListeElectorale[] calculerSieges(User user, ListeElectorale[] listesElectorales);
}
يكون المعلمة الأولى لجميع الطرق هي المستخدم الذي يرغب في استخدام موارد الخادم الآمن.
يتطور التنفيذ [ElectionsMetier] على النحو التالي:
package elections.security.client.metier;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import elections.security.client.dao.IClientDao;
import elections.security.client.entities.ElectionsConfig;
import elections.security.client.entities.ElectionsException;
import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;
@Component
public class ElectionsMetier implements IElectionsMetier {
@Autowired
private IClientDao dao;
@Autowired
private ApplicationContext context;
// تكوين الانتخابات
private ElectionsConfig electionsConfig;
private ElectionsConfig getElectionsConfig(User user) {
if(electionsConfig!=null){
return electionsConfig;
}
// أدوات التعيين jSON
ObjectMapper mapperResponse = context.getBean(ObjectMapper.class);
try {
// الاستعلام
Response<ElectionsConfig> response = mapperResponse.readValue(dao.getResponse(user, "/getElectionsConfig", null),
new TypeReference<Response<ElectionsConfig>>() {
});
// خطأ؟
if (response.getStatus() != 0) {
// يتم إلقاء استثناء واحد
throw new ElectionsException(response.getStatus(), response.getMessages());
} else {
electionsConfig = response.getBody();
return electionsConfig;
}
} catch (ElectionsException e1) {
throw e1;
} catch (IOException | RuntimeException e2) {
throw new ElectionsException(100, e2);
}
}
@Override
public ListeElectorale[] getListesElectorales(User user) {
...
}
@Override
public int getNbSiegesAPourvoir(User user) {
return getElectionsConfig(user).getNbSiegesAPourvoir();
}
@Override
public double getSeuilElectoral(User user) {
return getElectionsConfig(user).getSeuilElectoral();
}
@Override
public void recordResultats(User user, ListeElectorale[] listesElectorales) {
...
}
@Override
public ListeElectorale[] calculerSieges(User user, ListeElectorale[] listesElectorales) {
...
}
@Override
public void authenticate(User user) {
dao.getResponse(user, "/authenticate", null);
}
}
المهمة المطلوبة: أكمل الكود أعلاه.
17.4.3. تكوين Spring
![]() |
تتطور الفئة [MetierConfig] على النحو التالي:
package elections.security.client.config;
...
@ComponentScan({ "elections.security.client.dao", "elections.security.client.metier" })
public class MetierConfig {
السطر 5، يجب تحديث الحزم المطلوب استكشافها.
17.4.4. اختبار JUnit للطبقة [métier]
![]() |
تتطور فئة الاختبار [Test01] على النحو التالي:
package elections.security.client.metier.junit;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import elections.security.client.config.MetierConfig;
import elections.security.client.entities.ElectionsException;
import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;
import elections.security.client.metier.IElectionsMetier;
@SpringApplicationConfiguration(classes = MetierConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class Test01 {
// طبقة [electionsMetier]
@Autowired
private IElectionsMetier electionsMetier;
// مُخطِط jSON
private final ObjectMapper mapper = new ObjectMapper();
// المستخدمون
static private User admin;
static private User user;
static private User unknown;
@BeforeClass
public static void initTest() {
admin = new User("admin", "admin");
user = new User("user", "user");
unknown = new User("x", "y");
}
@Test()
public void checkUserUser() {
ElectionsException se = null;
try {
electionsMetier.authenticate(user);
} catch (ElectionsException e) {
se = e;
}
Assert.assertNotNull(se);
Assert.assertEquals("403 Forbidden", se.getErreurs().get(0));
}
@Test()
public void checkUserUnknown() {
ElectionsException se = null;
try {
electionsMetier.authenticate(unknown);
} catch (ElectionsException e) {
se = e;
}
Assert.assertNotNull(se);
Assert.assertEquals("401 Unauthorized", se.getErreurs().get(0));
}
@Test()
public void checkUserAdmin() {
ElectionsException se = null;
try {
electionsMetier.authenticate(admin);
} catch (ElectionsException e) {
se = e;
}
Assert.assertNull(se);
}
/**
* vérification 1 : méthode de calcul des sièges on fixe en dur les listes
*/
@Test
public void calculSieges1() {
// يتم إنشاء جدول القوائم السبع المرشحة
ListeElectorale[] listes = new ListeElectorale[7];
listes[0] = new ListeElectorale("A", 32000, 0, false);
listes[1] = new ListeElectorale("B", 25000, 0, false);
listes[2] = new ListeElectorale("C", 16000, 0, false);
listes[3] = new ListeElectorale("D", 12000, 0, false);
listes[4] = new ListeElectorale("E", 8000, 0, false);
listes[5] = new ListeElectorale("F", 4500, 0, false);
listes[6] = new ListeElectorale("G", 2500, 0, false);
// يتم حساب المقاعد لكل قائمة
listes = electionsMetier.calculerSieges(admin,listes);
// التحقق من النتائج
Assert.assertEquals(2, listes[0].getSieges());
Assert.assertFalse(listes[0].isElimine());
Assert.assertEquals(2, listes[1].getSieges());
Assert.assertFalse(listes[1].isElimine());
Assert.assertEquals(1, listes[2].getSieges());
Assert.assertFalse(listes[2].isElimine());
Assert.assertEquals(1, listes[3].getSieges());
Assert.assertFalse(listes[3].isElimine());
Assert.assertEquals(0, listes[4].getSieges());
Assert.assertFalse(listes[4].isElimine());
Assert.assertEquals(0, listes[5].getSieges());
Assert.assertTrue(listes[5].isElimine());
Assert.assertEquals(0, listes[6].getSieges());
Assert.assertTrue(listes[6].isElimine());
}
/**
* vérification 2 : méthode de calcul des sièges on demande les listes à la couche [metier] puis on fixe en dur les
* voix
*/
@Test
public void calculSieges2() {
// يتم إنشاء جدول القوائم المرشحة السبع
ListeElectorale[] listes = electionsMetier.getListesElectorales(admin);
// يتم تثبيت الأصوات
listes[0].setVoix(32000);
listes[1].setVoix(25000);
listes[2].setVoix(16000);
listes[3].setVoix(12000);
listes[4].setVoix(8000);
listes[5].setVoix(4500);
listes[6].setVoix(2500);
// يتم حساب المقاعد التي حصلت عليها كل قائمة
listes = electionsMetier.calculerSieges(admin,listes);
// يتم التحقق من النتائج
Assert.assertEquals(2, listes[0].getSieges());
Assert.assertFalse(listes[0].isElimine());
Assert.assertEquals(2, listes[1].getSieges());
Assert.assertFalse(listes[1].isElimine());
Assert.assertEquals(1, listes[2].getSieges());
Assert.assertFalse(listes[2].isElimine());
Assert.assertEquals(1, listes[3].getSieges());
Assert.assertFalse(listes[3].isElimine());
Assert.assertEquals(0, listes[4].getSieges());
Assert.assertFalse(listes[4].isElimine());
Assert.assertEquals(0, listes[5].getSieges());
Assert.assertTrue(listes[5].isElimine());
Assert.assertEquals(0, listes[6].getSieges());
Assert.assertTrue(listes[6].isElimine());
}
/**
* vérification 3 méthode de calcul des sièges on provoque une exception
*/
@Test(expected = ElectionsException.class)
public void calculSieges3() {
// يتم إنشاء جدول يضم 24 قائمة مرشحة مع صوت واحد لكل منها
ListeElectorale[] listes = new ListeElectorale[25];
// ستحصل القوائم الـ 25 على نفس عدد الأصوات (4%)
for (int i = 0; i < listes.length; i++) {
listes[i] = new ListeElectorale("Liste" + (i + 1), 1, 0, false);
}
// حساب المقاعد - عادةً يجب أن يكون لدينا ElectionsException
// مع عتبة انتخابية تبلغ 5%
electionsMetier.calculerSieges(admin,listes);
}
/**
* enregistrement des résultats de l'élection
*
* @throws JsonProcessingException
*/
@Test
public void ecritureResultatsElections() throws JsonProcessingException {
// يتم إنشاء جدول القوائم المرشحة السبع
ListeElectorale[] listes = electionsMetier.getListesElectorales(admin);
// يتم تحديد الأصوات بشكل ثابت
listes[0].setVoix(32000);
listes[1].setVoix(25000);
listes[2].setVoix(16000);
listes[3].setVoix(12000);
listes[4].setVoix(8000);
listes[5].setVoix(4500);
listes[6].setVoix(2500);
// يتم حساب المقاعد التي حصلت عليها كل قائمة
listes = electionsMetier.calculerSieges(admin,listes);
// يتم عرض النتائج
for (int i = 0; i < listes.length; i++) {
System.out.println(mapper.writeValueAsString(listes[i]));
}
// يتم تسجيل النتائج في قاعدة البيانات
electionsMetier.recordResultats(admin,listes);
// يتم التحقق من النتائج
listes = electionsMetier.getListesElectorales(admin);
// يتم عرض النتائج
for (int i = 0; i < listes.length; i++) {
System.out.println(mapper.writeValueAsString(listes[i]));
}
Assert.assertEquals(2, listes[0].getSieges());
Assert.assertFalse(listes[0].isElimine());
Assert.assertEquals(2, listes[1].getSieges());
Assert.assertFalse(listes[1].isElimine());
Assert.assertEquals(1, listes[2].getSieges());
Assert.assertFalse(listes[2].isElimine());
Assert.assertEquals(1, listes[3].getSieges());
Assert.assertFalse(listes[3].isElimine());
Assert.assertEquals(0, listes[4].getSieges());
Assert.assertFalse(listes[4].isElimine());
Assert.assertEquals(0, listes[5].getSieges());
Assert.assertTrue(listes[5].isElimine());
Assert.assertEquals(0, listes[6].getSieges());
Assert.assertTrue(listes[6].isElimine());
}
}
المهمة المطلوبة: فهم هذا الاختبار والتحقق من نجاحه.
17.5. عميل الخادم الآمن مع طبقة [console]
![]() |
باستخدام Netbeans، نفتح مشاريع Maven [elections-metier-dao-security-webjson] و [elections-ui-metier-dao-webjson] ثم نقوم بإنشاء مشروع جديد [elections-console-metier-dao-security-webjson] عن طريق نسخ ولصق مشروع [elections-ui-metier-dao-webjson]:
![]() |
تأتي معظم فئات المشروع الجديد [3] من المشروع [elections-ui-metier-dao-webjson] [2] الذي كان عميل خادم الويب / jSON غير الآمن:
![]() |
اتبع الخطوات التالية لإنشاء مشروع Maven الجديد:
- انسخ/الصق المشروع [elections-ui-metier-dao-webjson] في المشروع [elections-ui-metier-dao-security-webjson]؛
- في المشروع الجديد:
- احذف الحزم [elections.client.dao, elections.client.metier, elections.client.entities]؛
- في الحزمة [elections.client.ui]، احتفظ فقط بالفئة console [ElectionsConsole] وواجهتها [IElectionsUI]؛
- أعد تسمية الحزم كما هو موضح في [3]؛
- قم بحل مشاكل الاستيراد في الفئات المختلفة باستخدام [clic droit / Fix Imports]؛
في هذه المرحلة، يحتوي المشروع الجديد على العديد من الأخطاء.
17.5.1. تكوين Maven
ملف [pom.xml] للمشروع الجديد هو التالي:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.elections</groupId>
<artifactId>elections-console-metier-dao-security-webjson</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>elections-console-metier-dao-security-webjson</name>
<description>couche console du client web / jSON</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>istia.st.elections</groupId>
<artifactId>elections-metier-dao-security-webjson</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- الأسطر 4-8: هوية Maven للمشروع الجديد؛
- الأسطر 22-26: التبعية لمشروع [elections-metier-dao-security-webjson] الذي أنشأناه للتو في الفقرة 17.4، والذي يوفر الطبقات [DAO] و [métier]؛
17.5.2. تكوين Spring
![]() |
الفئة [UiConfig] هي كما يلي:
package elections.security.client.config;
import elections.security.client.entities.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
@Import(MetierConfig.class)
@ComponentScan(basePackages = {"elections.security.client.console","elections.security.client.swing"})
public class UiConfig {
// المسؤول
private final User ADMIN = new User("admin", "admin");
@Bean
private User admin() {
return ADMIN;
}
}
- السطر 8: يتم استيراد الفئة [elections.security.client.config.MetierConfig] من المشروع [elections-metier-dao-security-webjson] الذي تمت دراسته في الفقرة 17.4؛
- السطر 9: يتم إعلان الحزم التي توجد بها حبوب Spring؛
- الأسطر 12-18: bean [admin] هو المستخدم [admin, admin] الذي ستستخدمه تطبيق وحدة التحكم. نتذكر أنه المستخدم الوحيد المصرح له بالاستعلام عن خادم الويب الآمن / jSON؛
17.5.3. تطبيق تشغيل وحدة التحكم
![]() |
تتطور الفئة [ElectionsConsole] على النحو التالي:
package elections.security.client.console;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import elections.security.client.entities.ElectionsException;
import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;
import elections.security.client.metier.IElectionsMetier;
@Component
public class ElectionsConsole implements IElectionsUI {
@Autowired
private IElectionsMetier electionsMetier;
@Autowired
private User admin;
@Override
public void run() {
// القوائم المتنافسة
ListeElectorale[] listes;
// إدخال البيانات
try (Scanner clavier = new Scanner(System.in)) {
// طلب القوائم المتنافسة من الطبقة [metier]
listes = electionsMetier.getListesElectorales(admin);
// يتم إدخال الأصوات
System.out.println("Il y a " + listes.length
+ " listes en compétition. Veuillez indiquer le nombre de voix de chacune d'elles :");
for (int i = 0; i < listes.length; i++) {
boolean saisieOK = false;
while (!saisieOK) {
System.out.print("Nombre de voix de la liste [" + listes[i].getNom() + "] : ");
try {
listes[i].setVoix(Integer.parseInt(clavier.nextLine()));
saisieOK = true;
} catch (ElectionsException | NumberFormatException ex) {
System.out.println("Nombre de voix incorrect. Veuillez recommencer");
}
}
}
}
// يتم حساب المقاعد
listes=electionsMetier.calculerSieges(admin,listes);
// يتم تسجيل النتائج
electionsMetier.recordResultats(admin,listes);
// يتم فرز القوائم حسب ترتيب الأصوات التنازلي
Arrays.sort(listes, new CompareListesElectorales());
// يتم عرضها
System.out.println("\nRésultats de l'élection\n");
for (int i = 0; i < listes.length; i++) {
System.out.println(listes[i]);
}
}
// فئة مقارنة القوائم الانتخابية
class CompareListesElectorales implements Comparator<ListeElectorale> {
// مقارنة قائمتين مرشحتين حسب عدد الأصوات
@Override
public int compare(ListeElectorale listeElectorale1, ListeElectorale listeElectorale2) {
// مقارنة الأصوات بين هاتين القائمتين
int nbVoix1 = listeElectorale1.getVoix();
int nbVoix2 = listeElectorale2.getVoix();
if (nbVoix1 < nbVoix2) {
return +1;
} else {
if (nbVoix1 > nbVoix2)
return -1;
else
return 0;
}
}
}
}
لا تتغير الفئة ElectionsConsole إلا قليلاً. يجب فقط تذكر أن طرق الطبقة [métier] تتطلب الآن مستخدمًا كمعلمة أولى (الأسطر 31 و49 و51). ويتم توفير هذا المستخدم عن طريق الحقن في الأسطر 21-22. المستخدم الذي تم إدخاله هو المستخدم المصرح له بالاستعلام عن خادم الويب / jSON.
المهمة المطلوبة: اختبر تطبيق وحدة التحكم (تذكر تشغيل الخادم الآمن مسبقًا).
17.6. عميل الخادم الآمن مع طبقة [swing]
![]() |
نقوم بإنشاء مشروع Netbeans جديد [elections-swing-metier-dao-security-webjson] عن طريق نسخ ولصق مشروع [elections-ui-metier-dao-webjson]:
![]() |
في المشروع الجديد [2]:
- احذف الحزم [elections.client.dao, elections.client.metier, elections.client.entities]؛
- في الحزمة [elections.client.ui]، احذف الفئات [ElectionsConsole, IElectionsUI]؛
- أعد تسمية الحزم كما هو موضح في [2]؛
- في الحزمة [elections.security.client.swing]، قم بتغيير اسم الفئة [AbstractElectionsSwing] التي تنفذ واجهة المستخدم الرسومية إلى [AbstractElectionsMainForm] والفئة [ElectionsSwing] التي تنفذ معالجات أحداثالواجهة الرسومية إلى [ElectionsMainForm]؛
- قم بحل مشاكل الاستيراد في الفئات المختلفة باستخدام [clic droit / Fix Imports]؛
في هذه المرحلة، هناك العديد من الأخطاء.
17.6.1. تكوين Maven
يتطور الملف [pom.xml] على النحو التالي:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.elections</groupId>
<artifactId>elections-swing-metier-dao-security-webjson</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>elections-swing-metier-dao-security-webjson</name>
<description>couche swing du client web / jSON</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>istia.st.elections</groupId>
<artifactId>elections-console-metier-dao-security-webjson</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- الأسطر 4-8: هوية Maven للمشروع الجديد؛
- الأسطر 22-26: تبعية لمشروع console الذي درسناه للتو في الفقرة 17.5؛
17.6.2. تكوين Spring
يستخدم هذا المشروع تكوين Spring الخاص بمشروع console الذي يستورده (انظر الفقرة 17.5.2).
17.6.3. طرق عرض تطبيق Swing
![]() |
سيستخدم هذا المشروع عرضين:
![]() |
- طريقة العرض [1] الخاصة بتسجيل الدخول هي طريقة عرض جديدة. وهي تُستخدم لمصادقة المستخدم الذي يرغب في استخدام التطبيق. وهي تستخدم الفئات:
- [AbstractElectionsConnectForm] التي تنفذ العرض؛
- [ElectionsConnectForm] التي تدير أحداث العرض؛
- الطريقة [2] معروفة. وهي الطريقة المستخدمة حتى الآن. وهي تستخدم الفئات:
- [AbstractElectionsMainForm] التي تنفذ العرض؛
- [ElectionsMainForm] التي تدير أحداث العرض؛
17.6.4. الجلسة
![]() |
عندما تحتوي تطبيقات Swing على عدة طرق عرض، يجب إيجاد وسيلة تسمح لطريقة عرض ما بنقل المعلومات إلى طريقة عرض أخرى. نستخدم مفهومًا مستخدمًا في تطوير الويب: الجلسة التي تسمح لطرق العرض المرتبطة بمستخدم معين بمشاركة المعلومات. سيتم تنفيذ هذا المفهوم هنا باستخدام Spring singleton الذي سيتم إدخاله في الفئتين اللتين تديران أحداث طريقتي العرض:
package elections.security.client.swing;
import elections.security.client.entities.User;
import org.springframework.stereotype.Component;
@Component
public class UiSession {
// المستخدم المسجل
private User user;
// مُستردات ومُعيّنات
...
}
- السطر 6: الفئة [UiSession] هي مكون Spring؛
- السطر 10: لا تخدم سوى غرض واحد: حفظ المستخدم الذي يتصل بالعرض [1]. العرض [2] الذي يحتاج إلى معرفة هذا المستخدم سيبحث عنه هناك؛
17.6.5. فئة التمهيد
![]() |
فئة التمهيد للتطبيق الرسومي هي التالية:
package elections.security.client.boot;
import elections.security.client.console.IElectionsUI;
public class BootElectionsSwing extends AbstractBootElections {
public static void main(String[] arguments) {
new BootElectionsSwing().run();
}
@Override
protected IElectionsUI getUI() {
return ctx.getBean("electionsConnectForm", IElectionsUI.class);
}
}
- السطر 5: فئة [BootElectionsSwing] توسع فئة [AbstractBootElections] المحددة في مشروع وحدة التحكم الموجود في تبعيات المشروع؛
- الأسطر 10-13: ستقوم الفئة [BootElectionsSwing] بعرض طريقة عرض تسجيل الدخول [ElectionsConnectForm]؛
- الأسطر 6-8: ستتم تنفيذ الطريقة [run] لهذه الفئة؛
17.6.6. الفئة [ElectionsMainForm]
الفئة [ElectionsMainForm] هي التي كانت تسمى سابقًا [ElectionsSwing]. وهي تدير أحداث العرض [AbstractElectionsMainForm]. يتطور كودها على النحو التالي:
package elections.security.client.swing;
import elections.security.client.console.IElectionsUI;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.util.ArrayList;
import java.util.List;
import javax.swing.DefaultListModel;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import elections.security.client.entities.ElectionsException;
import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;
import elections.security.client.metier.IElectionsMetier;
@Component
public class ElectionsMainForm extends AbstractElectionsMainForm implements IElectionsUI {
private static final long serialVersionUID = 1L;
// مرجع على الطبقة [métier]
@Autowired
private IElectionsMetier metier;
// الجلسة UI
@Autowired
private UiSession uiSession;
// المستخدم المتصل
private User user;
// قوالب القوائم JList
private DefaultListModel<String> modèleNomsVoix = null;
private DefaultListModel<String> modèleRésultats = null;
// القوائم المتنافسة
private ListeElectorale[] listes;
// القوائم التي أدخلها المستخدم
private final List<ListeElectorale> listesSaisies = new ArrayList<>();
private ListeElectorale[] tListesSaisies;
// التهيئة
@Override
protected void init() {
// إنشاء المكونات بواسطة الفئة الأصلية
super.init();
// التهيئات المحلية
modèleNomsVoix = new DefaultListModel<>();
jListNomsVoix.setModel(modèleNomsVoix);
modèleRésultats = new DefaultListModel<>();
jListResultats.setModel(modèleRésultats);
String info;
boolean erreur = false;
try {
// يتم طلب القوائم من الطبقة [métier]
listes = metier.getListesElectorales(user);
// ربط أسماء القوائم بالمنسدل jComboBoxNomsListes
for (int i = 0; i < listes.length; i++) {
jComboBoxNomsListes.addItem(String.format("%s - %s", listes[i].getId(), listes[i].getNom()));
}
// وكذلك معلمات الانتخابات
int nbSiegesAPourvoir = metier.getNbSiegesAPourvoir(user);
double seuilElectoral = metier.getSeuilElectoral(user);
// يتم تهيئة التسميات المرتبطة بهاتين المعلومتين
jLabelSAP.setText(jLabelSAP.getText() + nbSiegesAPourvoir);
jLabelSE.setText(jLabelSE.getText() + seuilElectoral);
// رسالة نجاح
info = "Source de données lue avec succès";
} catch (ElectionsException ex1) {
// يتم تسجيل الخطأ
info = getInfoForException("Les erreurs suivantes se sont produites :", ex1);
erreur = true;
} catch (RuntimeException ex2) {
// يتم تسجيل الخطأ
info = getInfoForException("Les erreurs suivantes se sont produites :", ex2);
erreur = true;
}
// خطأ؟
if (erreur) {
// عرض المعلومات
jTextPaneMessages.setText(info);
jTextPaneMessages.setCaretPosition(0);
return;
} else {
jTextPaneMessages.setText("");
}
// حالة النموذج
Utilitaires.setEnabled(new JLabel[]{jLabelAjouter, jLabelCalculer, jLabelEnregistrer, jLabelSupprimer}, false);
Utilitaires.setEnabled(
new JMenuItem[]{jMenuItemAjouter, jMenuItemCalculer, jMenuItemEnregistrer, jMenuItemSupprimer}, false);
// توسيط النافذة
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
Dimension frameSize = getSize();
if (frameSize.height > screenSize.height) {
frameSize.height = screenSize.height;
}
if (frameSize.width > screenSize.width) {
frameSize.width = screenSize.width;
}
setLocation((screenSize.width - frameSize.width) / 2, (screenSize.height - frameSize.height) / 2);
}
...
@Override
public void run() {
// تخزين بيانات المستخدم
user = uiSession.getUser();
// تهيئة النافذة
init();
setVisible(true);
}
}
- السطور 32-33: إدخال جلسة التطبيق؛
- السطور 112-119: سيتم تنشيط العرض كما في السابق بواسطة طريقة [run]؛
- السطر 115: يتم استرداد المستخدم في الجلسة. وقد تم إدراجه هناك بواسطة كود عرض تسجيل الدخول [ElectionsConnectForm]. ثم يتم تعديل الكود بحيث يكون المعلمة الأولى لنداءات الطبقة [métier] هو هذا المستخدم (السطور 63 و69-70 على سبيل المثال)؛
17.6.7. عرض [AbstractElectionsConnectForm]
![]() |
قم بإنشاء النموذج التالي باستخدام Netbeans:
![]() |
لن تقوم الفئة بنفسها بتنفيذ الطريقة التي تدير النقر على خيار القائمة [Connexion]. بل ستستدعي طريقة [doConnecter] المعلنة على أنها مجردة، وستُعلن فئة العرض نفسها على أنها مجردة. سيتم حذف الطريقة الثابتة [main] التي تم إنشاؤها تلقائيًا.
17.6.8. إدارة أحداث عرض تسجيل الدخول
![]() |
تنفذ الفئة [ElectionsConnectForm] إدارة أحداث عرض الاتصال بالطريقة التالية:
package elections.security.client.swing;
import elections.security.client.console.IElectionsUI;
import elections.security.client.entities.ElectionsException;
import elections.security.client.entities.User;
import elections.security.client.metier.IElectionsMetier;
import java.awt.Dimension;
import java.awt.Toolkit;
import javax.swing.SwingUtilities;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class ElectionsConnectForm extends AbstractElectionsConnectForm implements IElectionsUI {
private static final long serialVersionUID = 1L;
// مرجع على الطبقة [métier]
@Autowired
private IElectionsMetier metier;
// المستخدم المتصل
private User user;
// النموذج الرئيسي
@Autowired
private ElectionsMainForm electionsMainForm;
// جلسة UI
@Autowired
private UiSession uiSession;
@Override
protected void doConnect() {
String info = null;
try {
if (isPageValid()) {
// مصادقة المستخدم
metier.authenticate(user);
// يتم حفظ المستخدم في الجلسة
uiSession.setUser(user);
// يتم إخفاء شاشة تسجيل الدخول
setVisible(false);
// يتم عرض الشاشة الرئيسية
electionsMainForm.run();
}
} catch (ElectionsException ex1) {
// تسجيل الخطأ
info = getInfoForException("Les erreurs suivantes se sont produites :", ex1);
} catch (RuntimeException ex2) {
// يتم تسجيل الخطأ
info = getInfoForException("Les erreurs suivantes se sont produites :", ex2);
}
// خطأ؟
if (info != null) {
// يتم عرض المعلومات
jTextPaneErreurs.setText(info);
jTextPaneErreurs.setCaretPosition(0);
}
}
// عمليات التهيئة
@Override
protected void init() {
// إنشاء المكونات بواسطة الفئة الأصلية
super.init();
// التهيئة المحلية
...
// توسيط النافذة
...
}
@Override
public void run() {
// يتم عرض واجهة المستخدم الرسومية
SwingUtilities.invokeLater(new Runnable() {
public void run() {
init();
setVisible(true);
}
});
}
private boolean isPageValid() {
...
}
private String getInfoForException(String message, ElectionsException ex) {
...
}
private String getInfoForException(String message, RuntimeException ex) {
...
}
}
- الأسطر 73-82: الطريقة [run] التي سيتم استدعاؤها بواسطة فئة التمهيد [BootElectionsSwing]؛
- الأسطر 26-27: إدراج مرجع إلى العرض رقم 2 الذي يجب عرضه إذا تم التعرف على المستخدم؛
- السطور 30-31: إدراج جلسة عمل التطبيق؛
- السطر 84: تقوم الطريقة [isPageValid] بأمرين:
- تتحقق من أن المعرف ليس فارغًا (يمكن أن تكون كلمة المرور فارغة)؛
- تقوم بإنشاء مثيل لحقل User في السطر 23 باستخدام المدخلات؛
![]() | ![]() |
![]() | ![]() |
المهمة المطلوبة: أكمل كود الفئة.
المهمة المطلوبة: اختبر التطبيق.































