ساخت وب سرویس با PHP و MyQSL و ارتباط آن با Retrofit
در این مبحث یک وب سرویس (API) ساده تحت PHP و دیتابیس MySQL مینویسیم و ارتباط آن با یک اپلیکیشن اندرویدی که شامل یک فرم ثبت نام و یک فرم ورود هست را بررسی میکنیم. در این پروژه از کتابخانه معروف Retrofit 2 برای ارتباط بین وب سرویس و کلاینت استفاده شده است.
این بخش شامل مباحث زیر میباشد:
- معرفی مختصر زبان PHP
- معرفی سیستم مدیریت پایگاه داده MySQL
- معرفی پنل phpMyAdmin جهت مدیریت دیتابیس MySQL
- نحوه کار با لوکال سرور WampServer
- نحوه ساخت دیتابیس و جداول در پنل phpMyAdmin
- ساخت یک وب سرویس ساده تحت زبان PHP و دیتابیس MySQL
- معرفی و تشریح تابعهای بکار رفته در سورس وب سرویس
- نحوه چاپ اطلاعات با فرمت JSON در PHP
- نحوه تست و خطایابی وب سرویس توسط نرم افزار Postman
- نحوه تست و خطایابی وب سرویس بدون نرم افزار و توسط URL
- اضافه کردن کتابخانههای Retrofit و Converter Gson به پروژه اندروید
- تعریف حق دسترسی اپلیکیشن به شبکه و اینترنت
- معرفی @SerializedName کتابخانه Gson
- استفاده از متدهای @POST و @GET برای ارسال و دریافت اطلاعات بین کلاینت و API
- ساخت فرم ثبت نام و ورود توسط Fragment
- جایگزین کردن فرگمنتها توسط متد replace
- مدیریت onResponse و onFailure در متد enqueue
این مبحث در قالب PDF و در ۴۷ صفحه تهیه شده که در ادامه چند صفحهی ابتدایی مربوط به بخشهای “وب سرویس” و “پروژه اندرویدی” را مشاهده میکنید:
به نام خدا. در مبحث گذشته با کتابخانه معروف Retrofit و همچنین فرمت انتقال داده JSON آشنا شدیم. پروژه جلسه قبل بسیار ساده بود و تنها شامل دریافت اطلاعات ثابت (استاتیک) از یک فایل json میشد.
در این جلسه قصد دارم یک وب سرویس (RESTful API) بسیار ساده و سبک با استفاده از زبان سمت سرور PHP و دیتابیس MySQL بسازم که قابلیت انتقال داده بین آن و یک کلاینت وجود داشته باشد. که در اینجا کلاینت ما یک دیوایس اندرویدی است.
سپس یک پروژه اندروید ایجاد میکنم که شامل یک فرم ثبتنام (رجیستر) و یک فرم ورود (لاگین) است.
نحوه کار نیز به این صورت است که با تکمیل فرم ثبتنام توسط کاربر، اطلاعات وارد شده توسط Retrofit و در بستر HTTP به وب سرویس ارسال شده و داخل دیتابیس ذخیره میگردد. همچنین در فرم لاگین، اطلاعاتی که کاربر وارد کرده (نام کاربری و رمز عبور) با اطلاعات ذخیره شده در دیتابیس مطابقت داده شده و در صورت صحت اطلاعات وارد شده، پیغام خوشآمد گویی حاوی نام و نام کاربری شخص نمایش داده میشود.
ساخت وب سرویس با استفاده از PHP و MySQL
PHP یک زبان برنامه نویسی شیءگراست که برای ساخت برنامههای سمت سرور استفاده میشود.
MySQL یک سیستم مدیریت پایگاه داده (دیتابیس) است که از طریق آن امکان ذخیره سازی، جستجو، مرتب کردن و بازیابی دادههای دیتابیس فراهم میگردد. در حال حاضر دیتابیس تعداد زیادی از وبسایتها توسط این سیستم مدیریت میشوند. در صورتی که قبلا با سرویسهای میزبانی سایت (هاستینگ) کار کردهاید و تجربه راه اندازی یک وب سایت را دارید (مانند یک وب سایت وردپرسی) احتمال زیاد با MySQL و پنل مدیریت phpMyAdmin آشنا هستید. MySQL هم مانند SQLite رایگان بوده که تحت مالکیت شرکت Oracle قرار دارد.
در اینجا قصد آموزش زبان PHP یا تشریح کامل MySQL را ندارم. اگر با کلیّت زبانهای برنامه نویسی آشنایی مختصری داشته باشید، کدهای PHP وب سرویس را به راحتی درک میکنید. با دستورهای زبان SQL هم قبلا در مبحث SQLite آشنا شدیم.
ابتدا WampServer را اجرا میکنم. در مبحث قبل با ومپ سرور آشنا شدیم و گفتیم که به واسطه این نرم افزار میتوانیم یک وب سرور محلی روی رایانه شخصی خودمان راه اندازی کنیم.
بعد از اجرای WampServer چند ثانیه زمان لازم است تا سرویسهای آن لود شوند. در صورتی که سرویسها به درستی لود شد، آیکون نرم افزار به رنگ سبز تغییر کرده و عبارت All Services Running را نشان میدهد:
اگر آیکون سبز نشد روی آن راست کلیک و گزینه Refresh را انتخاب کنید.
با کلیک روی آیکون ومپ سرور، منوی زیر باز میشود:
گزینه Localhost صفحه اصلی WampServer را در مرورگر باز میکند که اطلاعات مربوط به ماژولهای فعال را نشان میدهد. phpMyAdmin پنل مدیریت MySQL است که برای ساخت و مدیریت دیتابیس از این پنل استفاده میکنیم. در ادامه گزینههای دیگری برای مدیریت قسمتهای مختلف قرار دارد. در صورتی که هنوز آیکون نرم افزار سبز نشده یکبار گزینه Restart All Services را بزنید.
ابتدا باید یک دیتابیس بسازم. برای اینکار روی phpMyAdmin کلیک میکنم. صفحه ورود به پنل در مرورگر باز میشود:
در WampServer یا سایر Local Server ها، مطابق تصویر بالا Username برابر با root و Password باید خالی باشد. پس از ورود، پنل به صورت زیر باز میشود:
به قسمت Databases میروم و یک دیتابیس با نام دلخواه userdata ایجاد میکنم:
پس از ساخت دیتابیس یک table با نام دلخواه info به دیتابیس اضافه میکنم. من ۴ ستون برای این جدول نیاز دارم بنابراین در قسمت Number of columns عدد ۴ قرار میدهم. این چهار قسمت برای ذخیره اطلاعات کاربران است که شامل شناسه (id)، نام فرد (name)، نام کاربری (username) و رمز عبور (password) میشود.
سطر id را از نوع INT با اندازه ۵ و مابقی سطرها از نوع VARCHAR و اندازه ۵۰ درنظر گرفتم. id میبایست برای هر کاربر منحصر بفرد بوده و به صورت خودکار با ثبت نام هر کاربر یک واحد به آن اضافه شود بنابراین برای این سطر قسمت A I (مخفف AUTO INCREMENT) را تیک میزنم:
درباره Type و Length و سایر موارد، مفصل در مبحث SQLite بحث شده است و نیازی به تکرار نیست. در اینجا من ساخت جدول و ستون را در محیط ویژوال انجام دادم اما اینکار را با نوشتن Query هم میتوان انجام داد. روی Preview SQL کلیک کنید. کوئری زیر نشان داده میشود:
یعنی اگر نوشتن کوئری را بلد باشیم با قرار دادن آن در قسمت SQL نیز میتوانیم جدول را ایجاد کنیم:
بهرحال هردو روش قابل انجام است. من در همان مرحله قبل یعنی محیط ویژوال بعد از ورود اطلاعات مربوط به سطرها، گزینه Save را میزنم و ستونها ساخته میشود:
خب! فعلا با دیتابیس کاری ندارم.
در فولدر www مربوط به WampServer یک فولدر با نام دلخواه api میسازم و فایلهایی که قبلا آماده کردهام را به اینجا منتقل میکنم:
من این وب سرویس ساده را با زبان PHP نوشتهام بنابراین پسوند فایلها php. است. کدهای مربوط به هر فایل را به صورت خلاصه توضیح میدهم تا با کلیت نحوه کارکرد این وب سرویس آشنا شوید. برای باز کردن فایلها از یک ادیتور ساده و رایگان مانند ++Notepad یا Sublime Text استفاده کنید (سورس وب سرویس در فولدر Exercises قرار دارد).
db_connect.php:
در این فایل اطلاعات مربوط به دیتابیس را وارد کردهام و سپس دستور اتصال به دیتابیس نوشته شده:
چهار متغیر با نامهای دلخواه ساختهام که اطلاعات دیتابیس را شامل میشود. دیتابیس روی همین سیستم میزبانی میشود بنابراین مقدار آن localhost است. user و password همان یوزر و پسورد ورود به phpMyAdmin است. یعنی root برای user و رشته خالی “” برای password. نام دیتابیس را هم درون متغیر dbname ذخیره کردم. حتما میدانید نامگذاری متغیرها مانند آنچه در جاوا قبلا آشنا شدهاید، دلخواه بوده و قانونی وجود ندارد.
در ادامه توسط تابع mysqli_connect() یک کانکشن برای دیتابیس ایجاد میکنم. این تابع ۴ مقدار ورودی گرفته که همان اطلاعاتی است که در ابتدای فایل تعریف شده. برای آنکه مطمئن شوم دستور اتصال را به درستی نوشتهام و ارتباط با دیتابیس برقرار است، یک if() نوشتم که اتصال یا عدم اتصال را بررسی میکند. اگر $connection برقرار باشد (یعنی mysqli_connect با اطلاعاتی که گرفته توانسته به دیتابیس متصل شود) پیغامی مبنی بر اتصال موفق و در غیر اینصورت پیغام شکست در اتصال چاپ میشود. برای بررسی نتیجه کار کافیست فایل را روی مرورگر اجرا کنم:
پیغام اول چاپ شد یعنی در اتصال به دیتابیس مشکلی وجود ندارد. پس از حصول اطمینان از صحت عملکرد اتصال به دیتابیس، دستور if را حذف یا Comment کنید تا از خروجی نهایی وب سرویس حذف شود:
بعد از غیر فعال شدن این دستور، با فراخوانی مجدد فایل در مرورگر نباید هیچ خروجی چاپ شود:
در ابتدای جلسه تصمیم گرفتیم اپلیکیشن ما یک فرم ثبت نام و یک فرم ورود داشته باشد. بنابراین دو فایل جداگانه برای مدیریت این قسمتها به وب سرویس اضافه شده.
register.php:
ابتدا توسط دستور require فایل db_connect.php فراخوانی شده است زیرا برای دریافت اطلاعات کاربر و ثبت داخل دیتابیس نیاز به اتصال به دیتابیس داریم. در فرم ثبت نام، اطلاعات شامل نام، نام کاربری و رمز عبور از کاربر دریافت میشود. سه متغیر $name، $username و $password توسط متد $_GET دادههای ارسالی از سمت کلاینت را دریافت و درون خود ذخیره میکنند.
در فرایند ثبت نامِ کاربر باید بررسی شود نام کاربری که انتخاب کرده قبلا توسط شخص دیگری انتخاب نشده باشد. بنابراین یک متغیر با نام $sql_select نوشتم که مقدار آن یک کوئری از نوع SELECT است:
SELECT * FROM info WHERE username = '$username'
این دستور (کوئری) جدول info درون دیتابیس را بررسی میکند و اگر در ستون username مقداری برابر با مقدار ذخیره شده در متغیر $username که از کلاینت (فرم ثبت نام) دریافت شده وجود داشته باشد آنرا برمیگرداند.
در متغیر $result نیز تابع mysqli_query() ابتدا به دیتابیس متصل میشود ($connection)، سپس دستور فوق را به دیتابیس ارسال میکند. در نهایت آنچه از این کوئری حاصل میشود (یعنی وجود یا عدم وجود username مشابه با آنچه کاربر فعلی قصد ثبت نام دارد) در این متغیر ذخیره میشود.
سپس یک شرط داریم. تابع mysqli_num_rows() در PHP تعداد سطرهای برگشت شده از کوئری را اعلام میکند (num مخفف number به معنی تعداد و rows به معنی سطرها).
mysqli_num_rows($result) > 0
اگر username ای که شخص وارد کرده قبلا توسط کاربر دیگری انتخاب شده باشد، $result آنرا برمیگرداند پس mysqli_num_rows() بزرگتر از ۰ خواهد بود و در نتیجه قسمت اول شرط اجرا میشود:
$status = "REGISTERED";
یعنی رشته متنی REGISTERED درون متغیر $status ذخیره میگردد.
ولی اگر کاربری قبلا با این نام کاربری ثبت نام نکرده باشد، mysqli_num_rows($result) حاصلش ۰ است بنابراین قسمت دوم شرط اجرا میشود. یعنی کد درون بدنه else.
در خط ۱۵ یک کوئری با نام $sql_insert تعریف کردم. این کوئری، مقادیر ذخیره شده در متغیرهای $name، $username و $password را درون جدول info و درسطرهای name، username و password وارد میکند. برای اینکه مطمئن شویم اطلاعات به درستی درون دیتابیس ذخیره شده یا نه، یک شرط بعد از این کوئری اضافه کردم که اگر کوئری به دیتابیس ارسال شده، عبارت SUCCESS و در غیر اینصورت عبارت ERROR در متغیر $status ذخیره شود.
پس تا اینجا متغیر $status میتوانید سه وضعیت را در خود ذخیره کند:
– نام کاربری قبلا استفاده شده و پیغام REGISTERED را برمیگرداند.
– نام کاربری آزاد است، عملیات ثبت نام با موفقیت انجام میشود و اطلاعات کاربر درون دیتابیس ذخیره میگردد و در نهایت SUCCESS را برمیگرداند.
– نام کاربری آزاد است ولی به دلایلی اطلاعات دریافتی روی دیتابیس ثبت نمیشود که ERROR را برمیگرداند.
در نهایت توسط تابع json_encode() مقدار ذخیره شده در $status در قالب یک آبجکت JSON چاپ (echo) میشود:
echo json_encode(array("response"=>$status));
خروجی این خط یک آبجکت JSON است که یک جفت نام/مقدار دارد. من نام آنرا response تعیین کردم. مقدار نیز همان $status است.
در انتها کانکشنی که به دیتابیس زده شده توسط تابع mysqli_close() بسته میشود زیرا تا عملیات بعدی به این کانکشن نیازی نداریم.
login.php:
عملیات مرتبط با لاگین کاربری که قبلا ثبت نام کرده از طریق این فایل انجام میپذیرد. ابتدا username و password ای که کاربر در فرم لاگین وارد کرده، به سرور ارسال و در متغیرهای $username و $password ذخیره میشود. سپس یک کوئری از نوع SELECT نوشتم که username و password ارسالی از سمت کلاینت را در جدول info دیتابیس جستجو میکند و در صورتی که این نام کاربری و رمز عبور در دیتابیس موجود باشد، نام (name) را برمیگرداند.
تابع $result نقشی مشابه تابع موجود در register.php دارد.
سپس یک شرط تعیین شده که اگر حاصل mysqli_num_rows($result) بزرگتر از صفر بود، توسط تابع mysqli_fetch_assoc() مقدار name از سطر مربوطه در دیتابیس درون متغیر $name ذخیره گردد. به علاوه اینکه عبارت SUCCESS به عنوان status ثبت شود. در ادامه، وضعیت (status)، نام کاربر (name) و نام کاربری وی (username) با فرمت JSON چاپ شود.
اگر حاصل mysqli_num_rows($result) برابر با ۰ باشد، وضعیت FAILED چاپ میشود.
خب! کارِ نوشتن وب سرویس تمام شد. قبل از اینکه سراغ اندروید استودیو بروم، ابتدا میخواهم وب سرویس را تست کنم تا مطمئن شوم عملیات ذخیره و بازیابی دادهها به درستی انجام میپذیرد و فرمت خروجی JSON آن صحیح و بدون نقص است.
(جهت مطالعه ادامه توضیحات وب سرویس، فایل PDF را دانلود کنید)
ساخت پروژه اندروید اتصال به وب سرویس توسط کتابخانه Retrofit
سراغ اندروید استودیو میروم و یک پروژه با نام Retrofit & PHP و اکتیویتی از نوع Empty Activity میسازم.
اولین قدم، اضافه کردن کتابخانههای Retrofit به build.gradle است:
dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.squareup.retrofit2:retrofit:2.4.0' implementation 'com.squareup.retrofit2:converter-gson:2.4.0' }
یادتان نرود برای اتصال به شبکه و اینترنت حتما حق دسترسی لازم را باید به مانیفست اضافه کنیم.
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="ir.android_studio.retrofitphp"> <uses-permission android:name="android.permission.INTERNET" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
دادههایی که از وب سرویس دریافت میکنیم response (مشترک در هر دو فرم) و همچنین name و username برای فرم ورود هستند. بنابراین یک کلاس میسازم و این سه پارامتر را تعریف میکنم. من نام این کلاس را User میگذارم:
سه متغیر داخل کلاس تعریف کردم. سپس متدهای Getter را میسازم (alt+insert)
نامگذاریهای فوق با نامهایی که در وب سرویس انتخاب کرده بودم متفاوت است. بنابراین لازم است تعیین کنیم هر متغیر مربوط به کدام مورد است. برای اینکار از annotation با نام @SerializedName استفاده میکنیم که مربوط به کتابخانه Gson است:
User.java:
package ir.android_studio.retrofitphp; import com.google.gson.annotations.SerializedName; public class User { @SerializedName("response") private String apiResposnse; @SerializedName("name") private String apiName; @SerializedName("username") private String apiUsername; public String getApiResposnse() { return apiResposnse; } public String getApiName() { return apiName; } public String getApiUsername() { return apiUsername; } }
شاید بپرسید خب چرا کار را سخت کنیم؟ میشد متغیرها را همنام با پارامترهای موجود در وب سرویس تعریف کرد تا نیاز به این عمل اضافی نباشد (مانند مبحث گذشته). انتقاد شما بجاست. اما ممکن است روال نامگذاری توسعه دهنده با آنچه از قبل در وب سرویس تعریف شده (و امکان تغییر سورس وب سرویس میسر نیست) متفاوت باشد و برنامه نویس مصمم به نامگذاری به شیوه خودش باشد. در اینجا این راهکار را کتابخانه Gson در اختیار ما قرار داده است.
برای ساخت Retrofit یک کلاس جدید با نام دلخواه API ساختم. کد آن به صورت زیر است.
API.java:
package ir.android_studio.retrofitphp; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; public class API { public static final String BASE_URL = "http://192.168.56.1/api/"; public static Retrofit myRetrofit = null; public static Retrofit getAPI() { if (myRetrofit == null) { myRetrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .build(); } return myRetrofit; } }
مانند مبحث قبل ابتدا URL مربوط به وب سرویس را تعریف کردم. سپس یک شرط تعیین شده تا بررسی کند اگر Retrofit برابر null بود آنگاه Retrofit.Builder() را بسازد (اگر null نباشد یعنی قبلا ساخته شده).
نوبت ساخت Interface است. مانند جلسه قبل یک کلاس جدید میسازم و نوع آنرا بجای Class از نوع Interface تعیین میکنم. من نام ApiInterface را روی این فایل گذاشتم.
ApiInterface.java:
package ir.android_studio.retrofitphp; import retrofit2.Call; import retrofit2.http.GET; import retrofit2.http.POST; import retrofit2.http.Query; public interface ApiInterface { @POST("register.php") Call<User> regCall(@Query("name") String Name, @Query("username") String UserName, @Query("password") String Password); @GET("login.php") Call<User> loginCall(@Query("username") String UserName, @Query("password") String Password); }
در پروژه مبحث قبل فقط یک متد @GET داشتیم اما اینجا یک متد @POST هم برای بخش عضویت کاربر استفاده میشود که دادههای کاربر باید به سمت سرور ارسال شود. البته همانطور که در قسمت کار با Postman اشاره شد، در هردو قسمت (یعنی ثبت نام و ورود) میتوان از @GET استفاده کرد چون ما مستقیما از کوئریها استفاده میکنیم. با اینحال از @POST هم استفاده کردم تا برای شما ناآشنا نباشد.
جهت مطالعه ادامه آموزش، فایل PDF را دانلود نمائید
توجه : سورس پروژه اندروید و وب سرویس درون پوشه Exercises قرار دارد
با توجه به اینکه آموزشهای پایه با قیمت پایین در اختیار کاربر قرار گرفته و درآمد حاصل صرف تامین هزینههای وب سایت و تهیه آموزشهای آتی میشود، به اشتراک گذاری این فایل با دیگران خلاف اخلاق است.
تعداد صفحات : ۴۷
حجم : ۳٫۵ مگابایت
قیمت : ۳۶ هزار تومان
توجه: صرفا در صورتی از درگاه پشتیبان استفاده کنید که قادر به پرداخت از طریق سبد دانلود نباشید.
افزودن به سبد دانلود درگاه پشتیبان
آموزش هاتون عالی هستن
سوال من اینجاست که آیا برنامه جایگزین برای postman میشناسین؟
من تو محیط لینوکس کار میکنم
ممنون. Postman برای لینوکس هم در دسترس هست:
https://dl.pstmn.io/download/latest/linux64
سلام من همین کار رو میخوام روی سروری که خریدم انجام بدم و روی هاست لوکال قرار نیست ارسال بشه.(دیتابیسmysql)
آیا خیلی باید تغییرات در کد هام انجام بدم؟
و اینکه آدرس host در فایل db_connection به چه صورت وارد کنم؟
خیر فقط آدرس تغییر میکنه. هم میتونید از دامنه برای آدرس دهی استفاده کنید هم آی پی سرور
خیلی ممنونم، میتونین یه مثالی رو ازش بنویسید که به چه شکل باید بنویسم؟
و اینکه کد های سمت سرورم رو کجا باید بنویسم؟توی خود پروژه اندروید یا سمت سرور باید کاری انجام بدم؟
مثالش میشه همین کدی که توی آموزش استفاده شده. اینکه نحوه آدرس دهی در سرور یا هاست شما به چه طریق باشه بستگی به خودتون داره دیگه. مثلا اگه دامنه domain.com رو استفاده می کنید و وب سرویس داخل پوشه api قرار گرفته آدرس میشه http://domain.com/api
سلام مهندس عزیز، وقتی با postman رجیستر میکنم پیام موفقیت میده حتی وقتی که یوزرنیم یکسان باشه، ولی چیزی در دیتابیس نشون داده نمیشه و خالیه مشکل کجاست؟
هم میتونه مشکل از سورس شما باشه که پیام موفقیت بدون بررسی نتیجه چاپ بشه هم اینکه اطلاعات به دیتابیس وارد میشه ولی شما نمیبینیدش. یک بار صفحه مربوط به دیتابیس رو ببندید و دوباره لاگین کنید
سلام استاد
یه مشکل خیلی عجیب دارم برنامه api روی شبیه ساز مشکلی نداره فایل json خالی نیست ولی توی گوشی تست میکنم responce,body رو null برمیگردونه اررو دیگه ای نشون میده
احیانا وب سرویستون روی local نیست؟
سلام من دوره رو تموم کردم و تونستم خیلی چیزا یاد بگیرم خواستم ازتون تشکر کنم هرچی باشه حق معلمی گردنمون دارید.
یاحق
سلام. خوشحالم که مفید بوده براتون. موفق و پیروز باشید
سلام خیلی ممنون بابت آموزش هاتون
یسوال داشتم من با این کتابخونه میتونم هرچی روی برنامه اندرویدی خودم دارم رو به وب سایت منتقل کنم؟طوریکه اپ من وقتی روی اندروید اجرا میشه کسایی که میخوان اون رو هم توی وب بتونن استفاده کنن میدونم میشه با وب اپلیکیشن کار کرد ولی مشکل اصلی من sdk تبلیغاتی که نمیتونم توی وب اپلیکیشن استفاده کنم
ممنون میشم راهنمایی کنید.
اگه منظورتون اینه که همزمان هم وب سایت و هم اپ از یک دیتابیس مشترک استفاده کنن بله
سلام
وقت بخیر. برای سرور واقعی میشه از هاست هایی که رایگان خدماتی رو ارائه میدن استفاده کرد؟ مثلا از ProFreeHost
اگه برای تمرین بخواید آره ولی برای نسخه نهایی اپ که بخواد در اختیار کاربر قرار بگیره اصلا
سلام موقعی که رو دکمه ثبت کلیک میکنم ارور:
Failed to connect to127.0.0.1:80 رو دارم درحال که زمپ سرورم روی همین آی پی (لوکال هاستم هستش) و پروژه api با همین آدرس روی مرورگرم بالا میاد.چطور میشه رفع کرد؟
اگه اشتباه نکنم تو یکی از مباحث فصل وب سرویس در مورد اتصال شبیه ساز به لوکال هاست توضیح داده بودم
تشکر از آموزش خوبتون سوالی که وجود داره اینکه برای معرفی لوکال هاستمون تو قسمت BASE_URL کلاس API چه آدرسی باید وارد کنیم .زمپ سرور من روی لوکال هاست سیستمم هستش.بجای آی پی آدرس چی باید قرار بدم؟
در مورد لوکال اینجا توضیح دادم:
https://android-studio.ir/retrofit-with-php-mysql-web-service
سلام روز بخیر
من این اموزش رو دیدم یه سوالی دارم
این اموزش رو برای wamp قرار دادین
توی cpanel چیکار باید بکند
فایل ها رو کجا بزارم
اون بخش www/api
public_html
سلام استاد ببخشید در اندروید درسته داده ها در قسمت ثبت نام با متد _POST رتروفیت فرستاده میشن ولی در سرور بصورت _GET دریافت میشن
اگر با _POST ارسال میشن باید با _POST هم دریافت بشن دیگه
post یعنی فرستادن
get یعنی دریافت کردن
نمیشه که با post داده دریافت بشه
سلام ببخشید توی آموزشتون خروجی از فیلدهای رکورد جدول هم نمایش میدین؟ مثلا با ایمیل و پسورد لاگین کنند و اکتیویتی بعدی نام و سن کاربر لاگین کرده را از رکورد پیدا شده نمایش دهد. این فایل آموزشیتون اینکارو توضیح میده؟ یا آموزش دیگه ای دارین که بتونم فیلدهار رکورد دیتابیس رو در اندروید استفاده کنم به عنوان ورودی؟
ممنون
خیر فقط بررسی میشه کاربر با مشخصات وارد شده در دیتابیس موجود هست یا نه
سلام بسیار ممنون از آموزشتون
فقط من چطور اسم host خودمو پیدا کنم؟ user,pass که همون یوزر و پس ورود به phpmyadmin هست ولی فکر کنم به دلیل اسم غلط هاست ارور میده موقع کانکشن
unknown mysql server host
ممنون
بالاخره هاستتون یا دامین داره یا آی پی. از هرجا هاست گرفتید اطلاعاتش رو فرستادن براتون
سلام استاد خسته نباشید ، سوالی دارم
من دو تا ادیت تکس دارم ، یک api دارم که از سمت سرور ، ۲ تا مقدار نام و نام خانوادگی رو باید حتما بگیره (میتونی خالی هم باشه ) متدم پست هست ، اطلاعات حساس هست و قراره من اونارو سرچ بکنم ، مقدار رکورد ها زیاده و استفاده از گت غیرممکن است ، باید اینو دوتا مقدار رو پست کنم و نتیجه رو دریافت کنم ، فقط هم با متد پست ، مثل وقتی که داخل نرم افزار postman از پست و body-raw استفاده میکنیم و مقادیر رو میفرستیم و جواب میگیریم
ایا همچین مبحثی رو آموزشتون پوشش میده ؟ یا میتونید به من کمکی بکنید ؟
خیلی ممنون از وقتی که میذارید
پوزش بابت تاخیر. خیر از GET استفاده شده
سلام.میشه آموزش استفاده از api لاراول رو تو اپلیکیشن اندرویدی بفرمایید..
سلام. فعلا وقتم رو روی مباحث جا مونده میذارم دوست عزیز. ممنون از شما
سلام من یک سایت دارم می خوام روی سی پنل همون سایت کد های php اپلیکیشن رو بنویسم باید تو public_html خود سایت ی php جدید ایجاد کنم اما این کارو می کنم سایت بالا نمی یاد و داخل ی پوشه می زارم کلا کار نمی کنه باید چیکار کنم تا درست شه
با پشتیبان هاستتون مطرح کنید بزرگوار
محتویاتش یه pdf هستش و سورس کد….
عنوان باکس دانلود هم همینه:
“دانلود فایل این آموزش با فرمت PDF به همراه سورس وب سرویس و پروژه”
منظورتون رو درست متوجه نمیشم
سلام خسته نباشید اموزش کامل و بی نقصیه
فقط یه مشکل
برنامه وقتی میخام رجیستر کنم ارور ssl handshake timeout همچین چیزی میده
چه کنم
سرورم روی ومپ سرور نیست روی دامنه واقعی گذاشتمش .
در ارتباط با سرور مشکل دارید. حالا دلیلش چی هست رو باید پیدا کنید
در ادامه ارور Error is use jsonreader.setleinet(true) to accept malformed JSON at line 1 column 1 path
در قسمت build پروژه اینها رو سینک کنید
implementation ‘com.squareup.okhttp3:logging-interceptor:3.8.0’
implementation ‘com.google.code.gson:gson:2.6.1’
بعد در کلاس API اینو بنویسید
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(logging);
if (myRetrofit == null) {
myRetrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build())
.build();
}
سلام بر شما و بی نهایت ممنون بابت سایت خوبتون
من برای ساخت فرم لاگین و رجیستر دقیقا همانند شما پیش رفتم و چون من سایت دارم بعد از آپلود فایل ها ، آدرس سایت رو وارد کردم ولی هنگام خروجی گرفتن در گوشی خودم با اخطار زیر مواجه می شوم :
google.gson.JsonSyntaxException : java.lang.IllegalStateException : Expected BEGIN_OBJECT but was STRING at line 1 column 1path $
تو گوگل سرچ کردم ولی چیزی دستگیرم نشد
ممنون میشم کمکم کنین
بررسی بفرمائید
https://stackoverflow.com/a/31425418
نشد بازم همون ارور رو میده
سلام دوستان عزیز هر کس با این ارور مواجه شد
UseJsonReader.setLeiient(true)to accept malformedJSON at line column 1 path $
راه حلش اینجاست
در قسمت ApiInterface این دستور رو استفاده کنید
@FormUrlEncoded
@POST(“login.php”)
Call loginCall(@Field(“username”) String UserName, @Field(“password”) String Password);
این رو تست کردم crash میده
از برنامه میپره بیرون
سلام. توی این آموزش از router برای آدرس دهی استفاده میکنید؟
خیر توسط خود کتابخانه هندل میشه
با سلام.
جناب مطهری ایا به عنوان یک برنامه نویس اندروید واقعا نیاز هست که ما ساخت یک وب سرویس رو بلد باشیم؟
در مباحث وب سرویس توضیح داده شده. در اکثر مواقع استفاده از سرویس های آماده مثل فایربیس جوابگو هست و نیازی به راه اندازی وب سرویس شخصی نیست. به خودتون بستگی داره
سلام وقت بخیر ممنون از اموزشای خوب و روانتون
من روی این پروژه تمرین کردم با این خطا روبرو شدم
java.net.UnknownServiceException:CLEARTEXT communication to 192.168.1.4 not permitted by network security policy
من گوشیم رو به لوکال هاست کامپیوترم وصل کردم و با زدم ای پی بالا به فایل های php هم از طریق گوشی دسترسی پیدا کردم حتی فایل db_connect.php رو هم تست کرد خطایی نداشت. دیگه تا اینجا خیالم از این که به لوکال هاست متصلم حل شده دسترسی به اینترنت رو هم در منیفست چک کردم درسته اما نمی دونم چرا خطا میده
ظاهرا با این IP مشکل داره. حالا نمیدونم علتش چی میتونه باشه. سرچ کردید ارور رو؟
باید دستور cleartext رو در فایل مانیفست تعریف کنی قبلا خودم این مشکل رو داشتم گوگل از API 26 به بعد اجازه دسترسی به نت رو از طریق url ناامن نمیده
سلا م و درود بر شما سید
میخواستم ببینم آموزش رایگانی نیست ک سورس رو از ۰ تا ۱۰ رو چطور روی هاست راه اندازی کنیم باشه
سورس دارم بلد نیستم راه اندازیش کنم
ممنون
فکر میکنم توضیحاتی که در مورد wamp دادم کافی هست. دقیقا چه قسمتی رو مشکل دارید؟ اگه سرچ بکنید آموزش کار با wamp حتما مطالب کاملتری باید باشه
سلام جناب مطهری و تشکر فراوان بابت آموزش
سایت من وردپرس هست و از wp-rest api برای دریافت پست ها توی اندروید استفاده میکنم.
میخواستم بدونم راهی هست که از wp-rest api و دیتابیس و جدول های پیشفرض وردپرس برای ارتباط صفحه لاگین و رجیستر استفاده و دیگه وب سرویس واسه اینکار ننوشت؟
اگه میشه یه آموزش یا سورس واسه اینکار هم قرار بدید.
سلام. در مورد wp-rest api اطلاعاتی ندارم هنوز. ان شا الله فرصت بشه در مورد وب سرویس ها در قالب آموزش های پروژه محور بیشتر کار خواهیم کرد
سلام
آیا نیازه به عنوان توسعه دهنده اندروید زبان php ر و برای ساخت سرور یاد بگیریم؟
در صورتی که بخوایم کار رو خودمون انجام بدیم؟
خیر. سرویس های متعددی هستن که نیاز ما رو به راه اندازی سرور شخصی مرتفع میکنن
خب این سرویس ها چی هستن؟
آموزشش چجوریه؟!
به عنوان مثال بخوام یه برنامه نوبت دهی دکتر بسازم چکار باید کنم؟
در ضمن میشه ایمیلتون رو بذارید
یه موردش Firebase گوگل
(در ادامه بحث ارور )بله .
http://s6.picofile.com/file/8376650850/Screenshot_59_.png
http://s7.picofile.com/file/8376650884/Screenshot_57_.png
http://s7.picofile.com/file/8376650892/Screenshot_56_.png
http://s6.picofile.com/file/8376650976/Screenshot_60_.png
مشاهده بفرمایید
حقیقتش چیزی به ذهنم نمیرسه. یه صحبت با پشتیبان هاست بکنید. بگید کد من روی لوکال جواب داده. یا اگه هاست دیگه ای در اختیار دارید تست بزنید
سلام در مورد این ارور راه حلی پیدا شد؟
Connection Error! com.google.gson.stream.MalformatJsonException:UseJsonReader.setLeiient(true)to accept malformed JSON at line column 1 path $
به کاربر قبلی گفته بودم با پشتیبان هاست مطرحش کنن. نمیدونم به جواب رسیدن یا نه
سلام . آقا مهدی من روی سایتم گذاشتم و با post man قشنگ کار می کنه . ۲۰ دفعه دستور شرطی dbconnect رو اجرا کردم و به mysql وصل میشه و با url اطلاعات وارد دیتابیس میشه.ولی توی اندروید com.google.gson.streatm.MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 path $
ارور بالا رو میده.صفحه ۴۰ رو هم دیدم که نوشتید اتصال به دیتابیس خرابه.ولی برای سایت من همه چی ردیفه.اطلاعات سمت php درسته.کد ها هم کد های شما رو کپی کردم. http://uupload.ir/files/1afi_screenshot_(56).png
مشکل از چیه پس؟
ممکنه اشکال از سمت کلاینت باشه. یعنی دیوایس مجازی یا حقیقی دسترسی به شبکه و اینترنت نداره که داخل آموزش یاداواری شده
من روی سورس شما دارم کار می کنم . دسترسی هم داره.برای تستش ی وب ویو گذاشتم و یک صفحه روباهاش باز کردم.شما برای آنلاین(لوکال نباشه) تست کردید؟
من توی استک اور این صفحه رو پیدا کردم (https://stackoverflow.com/questions/39918814/use-jsonreader-setlenienttrue-to-accept-malformed-json-at-line-1-column-1-path )
که با گذاشتن gson توی creat همون اروری که توی صفحه نوشته رو نشون میده ولی دیگه نتونستم کار کنم.
من خیلی به این حالات آنلاین نیاز دارم
بله روی هاست تست شده. کد رو روی لوکال تست کنید. اگه مشکل نداشت خروجی رو داخل مرورگر تست کنید. اگه اوکی بود مواردی که داخل لینکی که خودتون زحمتشو کشیدید (stackoverflow) رو تست کنید
من با لوکال تست کردم و کار کرد.سایتی که من دارم هاست معمولی هستش .با url یا همون post man که کار می کنه و جواب میده.ولی وقتی به جای آدرس آیپی توی اکتیویتی api آدرس سایتم رو میذارم توی اندروید این ارور رو میده.آیا باید هاست خاصی داشته باشم تا توی اندروید کار کنه؟آیا باید به جای آدرس سایت آدرس چیز دیگه ای از سایتم رو بذارم؟(خیلی ممنونم که پاسخ گو هستید)
نه اگه هاست JSON رو پشتیبانی میکنه نباید مشکلی باشه. اینکه IP باشه یا Domain تفاوتی نمیکنه. مطمئنید دامین رو درست جایگزین آیپی میکنید؟
سلام آقامهدی من رفتم سایت خریدم.به جای لوکال هسات باید آدرس ساتیم رو وارد کنم؟site.ir??
بله
جای خالی ارسال عکس و pdf و …تو این آموزش به شدت حس میشه اگه ارسال اونا رو هم یاد میدادید خیلی عالی میشد
فرمایشتون صحیح هست. برنامه فعلی تکمیل مباحث ضروری هست و ان شا الله بعد از رسیدن به این هدف، مطالب تکمیلی اضافه میشه که عموما هم رایگان خواهد بود
سلام ممنون از آموزش خوبتون.
من کل مراحل ارتباط با سرور رو طبق آموزش شما پیاده کردم.ولی به سرور متصل نمیشه و خطای :
Failed to connect to [192.168.187.2
که همون آی پی Virtual box است رو میده. با ip های مختلف هم تست کردم ولی جواب نداد مثل ۱۰٫۰٫۲٫۳ و سایت های مختلف رو هم چک کردم. علت خطا چی میتونه باشه؟ممنون میشم راهنمایی کنید.
آی پی رو داخل مرورگر هم بزنید ومپ بالا نمیاد؟ نرم افزار تغییر آی پی فعال ندارید احیانا؟
داخل مرورگر بالا میاد .خیر نرم افزار تغییر آی پی فعال نیست.
یه اسکرین شات بدید (روی سرویسی مثل uupload.ir آپلود کنید تصویر رو)
http://uupload.ir/files/oxxj_failed1.jpg
این خطاییه که روی API19 هست که البته یه کدی برای رفع خطاش پیدا کردم که فایده ایی نداشت این لینک اون کده:https://stackoverflow.com/questions/55547248/retrofit-not-working-on-specific-versions-of-android
و این هم خطا در API زیر ۱۹ هست که اپ من ازAPI16به بالا باید ساپورت شه.:
http://uupload.ir/files/jm9k_failedbelowapi19.jpg
واینکه متوجه شدم Retrofit از Api21به بالا ساپورت میشه که توی صفحه گیت هابش نوشته بود. پس راه حل چی میتونه باشه؟ لینکی که خط بالا به stackoverflow هست هم نوشتم ولی خطا همچنان طبق اسکرین شات هستش.
در ضمن خیلی ممنون از سرعت پاسخگویی تون.
اگه سورس رو دقت کرده باشید MinSDK روی ۱۹ هستش و مشکلی هم نیست. البته نمیدونم منظورتون TargetSDK هست یا MinSDK. اگه Target روی ۱۹ دارین که اصلا منطقی هم نیست. آخرین API رو نصب کنید
در مورد پیغام خطا retrofit keeps stopping موقع اجرا شبیه ساز اندروید لطف می کنید راهنمایی کنید.
باید Logcat رو بررسی کنید ببینید از چه قسمتی ارور میگیرید. ده ها دلیل میتونه داشته باشه
سلام
من wampserver رو دانلود و نصب کردم اما موقع اجرا خطای زیر رو میده:
the program cant start because MSVCR110.dll is missing from your computer
باید Visual C++ Redistributable for Visual Studio 2012 Update 4 نصب بشه روی ویندوز
https://www.microsoft.com/en-us/download/details.aspx?id=30679
سلام بر شما ببخشید من وقتی روی دکمه Register کلیک میکنم برنامه کرش میکنه و ارور زیر رو بهم میده دلیلش چیه؟
FATAL EXCEPTION: main
Process: osaj_modir.com, PID: 2249
java.lang.NullPointerException: Attempt to invoke interface method ‘retrofit2.Call osaj_modir.com.ApiInterface.regCall(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String)’ on a null object reference
سلام آقا مهدی من کد های php و که توی مرور گر لوکال هاست یا نرم افزار پست من باز میکنم چندتا اررور میده خواستم ببینم مشکل کجاست؟
عکس ارور هارو با ایمیل میفرستم براتون
بزرگوار مخصوصا گفتم اینجا مطرح کنید تا به درد بقیه هم بخوره. اینکه عکسها رو ایمیل کردید فقط شما رو به جواب میرسونه و برای بقیه عزیزانی که با این مشکل مواجه میشن باید مجدد پاسخ بدم. لطفا عکسها رو روی یه سایت آپلود فایل قرار بدید و لینکها به همراه متن سوال رو در یه کامنت جدید ارسال بفرمایید. تشکر از شما
با سلام و خسته نباشید، رتروفیت دقیقا از چه پروتکلی استفاده میکنه؟آیا میشه با ssl آنرا بهبود بخشید؟
بله ساپورت میکنه
سلام من فولدر api را از پروژه خریداری شده خارج و به فایل www متنقل میکنم موقع چک کردن فایل رجیستر به من erorr
mysqli_num_rows() expects parameter 1 to be mysqli_result, boolean given in F:\wamp64\www\api\register.php on line 11
را میدهد خواهشا علت را بفرمایید
نباید مشکلی باشه. یه بار سورس پروژه رو ایمپورت و تست کنید ببینید بازم ارور میگیرید یا نه. اگه ارور نگرفتید، مشکل مربوط به کدی هست که در پروژه خودتون زدید
سلام من از زمپ استفاده میکنم و پورت اپاچی روی ۸۱ هستش
Connection Error!java.net.SocketException:socket failed:to conect to 192.168.56.1:80
پورت رو ۸۰ هم تست کردم نشد فایل های پی اچ پی داخل پوشه
htdocs قرار داره
۱۹۲٫۱۶۸٫۵۶٫۱:۸۰ رو روی مرورگر بزنید باید روت زمپ رو نشون بده وگرنه آی پی رو اشتباه زدید
سلام وقت بخیر
من مراحل برنامه رو طبق فایل پی دی اف انجام دادم
منتها زمان اجرای برنامه این خطا رو میده، در موردش سرچ کردم ولی متاسفانه بازم موفق نشدم رفعش کنم
ممنون میشم راهنمایی بفرمایید
Connection Error!java.net.SocketException:socket failed:EACCES(Permission denied)
https://stackoverflow.com/a/38187608
احتمالا permission رو درست تعریف نکردید داخل مانیفست. چک کنید عینا مطابق آموزش باشه. تگ uses-permission باید داخل root مانیفست باشه نه تگ application
سلام وقت بخیر
من یه چن روزیه با این ارور مواجه میشم لطف میکنید بگید باید چکار کنم ؟
ERROR: Failed to resolve: androidx.room:room-runtime:2.1.0-alpha06
اگه یه لطف بکنید قبل طرح مشکل، مطلب “پرسشهای رایج” رو مطالعه بفرمایید
مهدی جان همه چی اوکی بود تست کردم هیچی مشکلی نداشت پروژه با موفقیت با wamp اوکی شد
الان میخوام به یه هاست واقعی وصل شم کلا انلاین شه همه چی
اون سه تا فایل php رو cpanel اپلود کردم
از دیتابیس هم تو phpmyadmin خروجی گرفتم فایل .sql هم تو هاست یا cpanel اپلود کردم
الان هرچی با پست من تست میکنم ERROR میده.
نمیدونم کدهای php دقیقا چه تغییراتی بدم.
لطفا اموزشی معرفی کن در این باره.
یا بگو دقیقا چی سرچ کنم
نیاز نیست کد PHP تغییر کنه. ببینید ارور چی هست رفعش کنید. سرچ کنید متن ارور رو
سلام، من در بخش رجیستر یه ارور دارم :
mysqli_num_rows() expects parameter 1 to be mysqli_result, boolean given in {inja directory oon rejistery ro migie} on line 11
باید چیکار کنم؟
با سورس اصلی مقایسه کنید ببینید کجا رو اشتباه کردید
سلام من پکیج آموزشیتون رو خریدم و به نسبت پولی که دادم واقعا راضیم ازتون. خدا خیرتون بده که ثوابش به مراتب بیشتره ارزش مالیش هست. فقط یه سوال ، برای کسایی مثل من که با #C ترجیح میدن سمت سرور بنویسن، چه نرم افزار شبیه سازی مثل wampserver برای #C هم هست که بشه کارمون رو بکنیم؟
در ضمن میشه یه لینکی ، یا خودتون توضیح بدید که با #C چطور میشه سمت سرور اندروید رو نوشت؟
ممنون. والا من کار نکردم. سرچ کنید ببینید برای ASP چطور میشه لوکال راه اندازی کرد
سلام. جناب مطهری ممنون از آموزش هاتون.
من از اندروید رو از صفر شروع کردم با سایت شما. خیلی عالی بود آموزش هاتون. ممنون.
منتظر بخش های بعدی نباشیم؟؟
ممنون از شما. منتظر باشید. هر موقع فرصت داشته باشم یه مبحث جدید آماده میکنم
سلام سپاس از شما؛آیا دلیل خاصی داره که فرم رو در یک فرگمنت طراحی کردید؟
ممنون. خیر
سلام
آدرس رجیستر رو داخل مرورگر مینویسم این ارور رو میدهNotice: Undefined index: name in C:\xampp\htdocs\userdata_androidstudio\register.php on line 5
Notice: Undefined index: username in C:\xampp\htdocs\userdata_androidstudio\register.php on line 6
Notice: Undefined index: password in C:\xampp\htdocs\userdata_androidstudio\register.php on line 7
{“response”:”REGISTERED”}
یه دقیقه وقت بذارید متن ارور رو ترجمه کنید و ببینید هر ارور آخرش به چه خطی اشاره کرده
سلام من اجرا که میکنم ارور کانکشن میده و توست میشه:
java.net unknown service exception
url را ای پی کامپیوترم دادم. و از داخل خود موزیلا شبیه ساز به ای پی دسترسی دارم ولی نمیدونم چرا کانکشن ارور میده!!!
بعید میدونم مربوط به آی پی و ارتباط باشه. ارور کانکشن واضحه (در متن تمرین هم هست)
با عرض سلام و سپاس، میخواستم بدونم دیگه فصل دهم تکمیل شد؟ یا قسمت دیگه ای هم مونده؟
بله تکمیل شده
سلام مهندس
من صفحه ۴۰ رو مطالعه کردم، خطایی که من نوشتم با خطای توضیح داده شده در صفحه ۴۰ یکسان نیست.
خطای من: Error is use jsonreader.setleinet(true) to accept malformed JSON at line 1 column 1 path $
بزرگوار ارور این همون اروره
در مورد خطای صفحه ۴۰ باید این نکته رو مد نظر داشته باشید که در اینترفیسی که ایجاد می کنید وقتی درخواست های شما به صورت جیسون در حال ارسال به سرور است حتما از دستور زیر در قسمت بالایی اینترفیس استفاده کنید
@Headers({
“Accept: application/json”,
“Content-Type: application/json”
})
سلام. مطلب شما رو خریدم و طبق نوشته های شما انجام دادم، اما خطای زیر ظاهر میشود:
com.google.gson.stream.MalformedJsonException: Use JsonReader.setLenlent(tru) to accept malformed JSON at line
صفحه ۴۰ دقیقا همین ارور رو گفتم دلیلش چی هست. مطلب رو با دقت مطالعه بفرمایید
سلام جناب مطهری
مثل همیشه عالیییییی
واقعا متشکرم……… 🙂
ممنون بزرگوار. نظر لطف شماست