cari
Rumahhujung hadapan webtutorial jsDari Sifar ke Etalase: Perjalanan Saya Membina Platform Penyewaan Hartanah

kandungan

  1. Pengenalan
  2. Timbunan Teknologi
  3. Ikhtisar Pantas
  4. API
  5. Hadapan
  6. Apl Mudah Alih
  7. Papan Pemuka Pentadbir
  8. Tempat Menarik
  9. Sumber

Kod sumber: https://github.com/aelassas/movinin

Demo: https://movinin.dynv6.net:3004

pengenalan

Idea itu muncul daripada keinginan untuk membina tanpa sempadan – platform penyewaan hartanah yang boleh disesuaikan dan beroperasi sepenuhnya di mana setiap aspek berada dalam kawalan anda:

  • Miliki UI/UX: Reka pengalaman pelanggan yang unik tanpa melawan had templat
  • Kawal Bahagian Belakang: Laksanakan logik perniagaan tersuai dan struktur data yang sepadan dengan keperluan
  • Master DevOps: Sebarkan, skala dan pantau aplikasi dengan alatan dan aliran kerja pilihan
  • Perluas Secara Bebas: Tambahkan ciri dan penyepaduan baharu tanpa kekangan platform atau bayaran tambahan

Timbunan Teknologi

Berikut ialah susunan teknologi yang membolehkannya:

  • Skrip Jenis
  • Node.js
  • MongoDB
  • Bertindak balas
  • MUI
  • Ekspo
  • Belang
  • Pelabuh

Keputusan reka bentuk utama telah dibuat untuk menggunakan TypeScript kerana banyak kelebihannya. TypeScript menawarkan penaipan, perkakasan dan penyepaduan yang kuat, menghasilkan kod berkualiti tinggi, berskala, lebih mudah dibaca dan boleh diselenggara yang mudah untuk nyahpepijat dan diuji.

Saya memilih React untuk keupayaan pemaparan yang berkuasa, MongoDB untuk pemodelan data yang fleksibel dan Stripe untuk pemprosesan pembayaran yang selamat.

Dengan memilih timbunan ini, anda bukan sahaja membina tapak web dan apl mudah alih – anda melabur dalam asas yang boleh berkembang mengikut keperluan anda, disokong oleh teknologi sumber terbuka yang teguh dan komuniti pembangun yang semakin berkembang.

Gambaran keseluruhan pantas

Dalam bahagian ini, anda akan melihat halaman utama bahagian hadapan, papan pemuka pentadbir dan apl mudah alih.

Bahagian hadapan

Daripada bahagian hadapan, pelanggan boleh mencari hartanah yang tersedia, memilih hartanah dan membuat pembayaran.

Di bawah ialah halaman utama bahagian hadapan di mana pelanggan boleh menentukan titik lokasi dan masa serta mencari hartanah yang tersedia.

From Zero to Storefront: My Journey Building a Property Rental Platform

Di bawah ialah hasil carian halaman utama di mana pelanggan boleh memilih hartanah untuk disewa.

From Zero to Storefront: My Journey Building a Property Rental Platform

Di bawah ialah halaman di mana pelanggan boleh melihat butiran hartanah:

From Zero to Storefront: My Journey Building a Property Rental Platform

Di bawah ialah paparan imej hartanah:

From Zero to Storefront: My Journey Building a Property Rental Platform

Di bawah ialah halaman daftar keluar di mana pelanggan boleh menetapkan pilihan sewa dan pembayaran. Jika pelanggan tidak berdaftar, dia boleh mendaftar keluar dan mendaftar pada masa yang sama. Dia akan menerima e-mel pengesahan dan pengaktifan untuk menetapkan kata laluannya jika dia belum berdaftar.

From Zero to Storefront: My Journey Building a Property Rental Platform

Di bawah ialah halaman log masuk. Pada pengeluaran, kuki pengesahan adalah httpSahaja, ditandatangani, selamat dan ketat samaSite. Pilihan ini menghalang serangan XSS, CSRF dan MITM. Kuki pengesahan dilindungi daripada serangan XST juga melalui perisian tengah tersuai.

From Zero to Storefront: My Journey Building a Property Rental Platform

Di bawah ialah halaman pendaftaran.

From Zero to Storefront: My Journey Building a Property Rental Platform

Di bawah ialah halaman di mana pelanggan boleh melihat dan mengurus tempahannya.

From Zero to Storefront: My Journey Building a Property Rental Platform

Di bawah ialah halaman di mana pelanggan boleh melihat tempahan secara terperinci.

From Zero to Storefront: My Journey Building a Property Rental Platform

Di bawah ialah halaman yang membolehkan pelanggan melihat pemberitahuannya.

From Zero to Storefront: My Journey Building a Property Rental Platform

Di bawah ialah halaman yang membolehkan pelanggan mengurus tetapannya.

From Zero to Storefront: My Journey Building a Property Rental Platform

Di bawah ialah halaman di mana pelanggan boleh menukar kata laluannya.

From Zero to Storefront: My Journey Building a Property Rental Platform

Itu sahaja. Itulah halaman utama bahagian hadapan.

Papan Pemuka Pentadbir

Tiga jenis pengguna:

  • Pentadbir: Mereka mempunyai akses penuh ke papan pemuka pentadbir. Mereka boleh melakukan segala-galanya.
  • Agensi: Mereka mempunyai akses terhad pada papan pemuka pentadbir. Mereka hanya boleh menguruskan hartanah, tempahan dan pelanggan mereka.
  • Pelanggan: Mereka mempunyai akses kepada bahagian hadapan dan apl mudah alih sahaja. Mereka tidak boleh mengakses papan pemuka pentadbir.

Platform ini direka untuk bekerjasama dengan pelbagai agensi. Setiap agensi boleh mengurus hartanah, pelanggan dan tempahannya daripada papan pemuka pentadbir. Platform ini juga boleh berfungsi dengan hanya satu agensi.

Daripada bahagian belakang, pentadbir boleh membuat dan mengurus agensi, hartanah, lokasi, pelanggan dan tempahan.

Apabila agensi baharu dibuat, mereka menerima e-mel yang menggesa mereka membuat akaun mereka untuk mengakses papan pemuka pentadbir supaya mereka boleh mengurus hartanah, pelanggan dan tempahan mereka.

Di bawah ialah halaman log masuk papan pemuka pentadbir.

From Zero to Storefront: My Journey Building a Property Rental Platform

Di bawah ialah halaman papan pemuka tempat pentadbir dan agensi boleh melihat dan mengurus tempahan.

From Zero to Storefront: My Journey Building a Property Rental Platform

Jika status tempahan berubah, pelanggan yang berkaitan akan menerima pemberitahuan dan e-mel.

Di bawah ialah halaman di mana sifat dipaparkan dan boleh diurus.

From Zero to Storefront: My Journey Building a Property Rental Platform

Di bawah ialah halaman di mana pentadbir dan agensi boleh membuat hartanah baharu dengan menyediakan imej dan maklumat hartanah. Untuk pembatalan secara percuma, tetapkan kepada 0. Jika tidak, tetapkan harga pilihan atau biarkan ia kosong jika anda tidak mahu memasukkannya.

From Zero to Storefront: My Journey Building a Property Rental Platform

Di bawah ialah halaman tempat pentadbir dan agensi boleh mengedit sifat.

From Zero to Storefront: My Journey Building a Property Rental Platform

Di bawah ialah halaman tempat pentadbir boleh mengurus pelanggan.

From Zero to Storefront: My Journey Building a Property Rental Platform

Di bawah ialah halaman untuk membuat tempahan jika agensi ingin membuat tempahan daripada papan pemuka pentadbir. Jika tidak, tempahan dibuat secara automatik apabila proses pembayaran selesai dari bahagian hadapan atau apl mudah alih.

From Zero to Storefront: My Journey Building a Property Rental Platform

Di bawah ialah halaman untuk mengedit tempahan.

From Zero to Storefront: My Journey Building a Property Rental Platform

Di bawah ialah halaman untuk mengurus agensi.

From Zero to Storefront: My Journey Building a Property Rental Platform

Di bawah ialah halaman untuk membuat agensi baharu.

From Zero to Storefront: My Journey Building a Property Rental Platform

Di bawah ialah halaman untuk mengedit agensi.

From Zero to Storefront: My Journey Building a Property Rental Platform

Di bawah ialah halaman untuk melihat hartanah agensi.

From Zero to Storefront: My Journey Building a Property Rental Platform

Di bawah ialah halaman untuk melihat tempahan pelanggan.

From Zero to Storefront: My Journey Building a Property Rental Platform

Di bawah ialah halaman tempat pentadbir dan agensi boleh mengurus tetapan mereka.

From Zero to Storefront: My Journey Building a Property Rental Platform

Ada halaman lain tetapi ini adalah halaman utama papan pemuka pentadbir.

Itu sahaja. Itu adalah halaman utama papan pemuka admin.

API

From Zero to Storefront: My Journey Building a Property Rental Platform

API mendedahkan semua fungsi yang diperlukan untuk papan pemuka pentadbir, bahagian hadapan dan apl mudah alih. API mengikut corak reka bentuk MVC. JWT digunakan untuk pengesahan. Terdapat beberapa fungsi yang memerlukan pengesahan seperti fungsi yang berkaitan dengan mengurus hartanah, tempahan dan pelanggan, dan lain-lain yang tidak memerlukan pengesahan seperti mendapatkan semula lokasi dan hartanah yang tersedia untuk pengguna yang tidak disahkan:

  • ./api/src/models/ folder mengandungi model MongoDB.
  • ./api/src/routes/ folder mengandungi laluan Ekspres.
  • ./api/src/controllers/ folder mengandungi pengawal.
  • ./api/src/middlewares/ folder mengandungi middlewares.
  • ./api/src/config/env.config.ts mengandungi konfigurasi dan takrif jenis TypeScript.
  • ./api/src/lang/ folder mengandungi penyetempatan.
  • ./api/src/app.ts ialah pelayan utama tempat laluan dimuatkan.
  • ./api/index.ts ialah titik masuk utama API.

index.ts ialah titik masuk utama API:

import 'dotenv/config'
import process from 'node:process'
import fs from 'node:fs/promises'
import http from 'node:http'
import https, { ServerOptions } from 'node:https'
import app from './app'
import * as databaseHelper from './common/databaseHelper'
import * as env from './config/env.config'
import * as logger from './common/logger'

if (
  await databaseHelper.connect(env.DB_URI, env.DB_SSL, env.DB_DEBUG) 
  && await databaseHelper.initialize()
) {
  let server: http.Server | https.Server

  if (env.HTTPS) {
    https.globalAgent.maxSockets = Number.POSITIVE_INFINITY
    const privateKey = await fs.readFile(env.PRIVATE_KEY, 'utf8')
    const certificate = await fs.readFile(env.CERTIFICATE, 'utf8')
    const credentials: ServerOptions = { key: privateKey, cert: certificate }
    server = https.createServer(credentials, app)

    server.listen(env.PORT, () => {
      logger.info('HTTPS server is running on Port', env.PORT)
    })
  } else {
    server = app.listen(env.PORT, () => {
      logger.info('HTTP server is running on Port', env.PORT)
    })
  }

  const close = () => {
    logger.info('Gracefully stopping...')
    server.close(async () => {
      logger.info(`HTTP${env.HTTPS ? 'S' : ''} server closed`)
      await databaseHelper.close(true)
      logger.info('MongoDB connection closed')
      process.exit(0)
    })
  }

  ['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach((signal) => process.on(signal, close))
}

Ini ialah fail TypeScript yang memulakan pelayan menggunakan Node.js dan Express. Ia mengimport beberapa modul termasuk dotenv, proses, fs, http, https, mongoose dan aplikasi. Ia kemudian menyemak sama ada pembolehubah persekitaran HTTPS ditetapkan kepada benar, dan jika ya, mencipta pelayan HTTPS menggunakan modul https dan kunci peribadi serta sijil yang disediakan. Jika tidak, ia mencipta pelayan HTTP menggunakan modul http. Pelayan mendengar pada port yang dinyatakan dalam pembolehubah persekitaran PORT.

Fungsi tutup ditakrifkan untuk menghentikan pelayan dengan anggun apabila isyarat penamatan diterima. Ia menutup pelayan dan sambungan MongoDB, dan kemudian keluar dari proses dengan kod status 0. Akhir sekali, ia mendaftarkan fungsi tutup untuk dipanggil apabila proses menerima isyarat SIGINT, SIGTERM atau SIGQUIT.

app.ts ialah titik masuk utama api:

import express from 'express'
import compression from 'compression'
import helmet from 'helmet'
import nocache from 'nocache'
import cookieParser from 'cookie-parser'
import i18n from './lang/i18n'
import * as env from './config/env.config'
import cors from './middlewares/cors'
import allowedMethods from './middlewares/allowedMethods'
import agencyRoutes from './routes/agencyRoutes'
import bookingRoutes from './routes/bookingRoutes'
import locationRoutes from './routes/locationRoutes'
import notificationRoutes from './routes/notificationRoutes'
import propertyRoutes from './routes/propertyRoutes'
import userRoutes from './routes/userRoutes'
import stripeRoutes from './routes/stripeRoutes'
import countryRoutes from './routes/countryRoutes'
import * as helper from './common/helper'

const app = express()

app.use(helmet.contentSecurityPolicy())
app.use(helmet.dnsPrefetchControl())
app.use(helmet.crossOriginEmbedderPolicy())
app.use(helmet.frameguard())
app.use(helmet.hidePoweredBy())
app.use(helmet.hsts())
app.use(helmet.ieNoOpen())
app.use(helmet.noSniff())
app.use(helmet.permittedCrossDomainPolicies())
app.use(helmet.referrerPolicy())
app.use(helmet.xssFilter())
app.use(helmet.originAgentCluster())
app.use(helmet.crossOriginResourcePolicy({ policy: 'cross-origin' }))
app.use(helmet.crossOriginOpenerPolicy())

app.use(nocache())
app.use(compression({ threshold: 0 }))
app.use(express.urlencoded({ limit: '50mb', extended: true }))
app.use(express.json({ limit: '50mb' }))

app.use(cors())
app.options('*', cors())
app.use(cookieParser(env.COOKIE_SECRET))
app.use(allowedMethods)

app.use('/', agencyRoutes)
app.use('/', bookingRoutes)
app.use('/', locationRoutes)
app.use('/', notificationRoutes)
app.use('/', propertyRoutes)
app.use('/', userRoutes)
app.use('/', stripeRoutes)
app.use('/', countryRoutes)

i18n.locale = env.DEFAULT_LANGUAGE

helper.mkdir(env.CDN_USERS)
helper.mkdir(env.CDN_TEMP_USERS)
helper.mkdir(env.CDN_PROPERTIES)
helper.mkdir(env.CDN_TEMP_PROPERTIES)
helper.mkdir(env.CDN_LOCATIONS)
helper.mkdir(env.CDN_TEMP_LOCATIONS)

export default app

Pertama sekali, kami mendapatkan semula rentetan sambungan MongoDB, kemudian kami mewujudkan sambungan dengan pangkalan data MongoDB. Kemudian kami mencipta aplikasi Express dan memuatkan perisian tengah seperti cors, mampatan, topi keledar dan nocache. Kami menyediakan pelbagai langkah keselamatan menggunakan perpustakaan middleware topi keledar. kami juga mengimport pelbagai fail laluan untuk bahagian aplikasi yang berbeza seperti Laluan pembekal, Laluan Tempahan, Laluan lokasi, Laluan pemberitahuan, Laluan hartanah dan Laluan pengguna. Akhir sekali, kami memuatkan laluan Ekspres dan apl eksport.

Terdapat 8 laluan dalam API. Setiap laluan mempunyai pengawal sendiri mengikut corak reka bentuk MVC dan prinsip SOLID. Di bawah adalah laluan utama:

  • userRoutes: Menyediakan fungsi REST yang berkaitan dengan pengguna
  • agencyRoutes: Menyediakan fungsi REST yang berkaitan dengan agensi
  • countryRoutes: Menyediakan fungsi REST yang berkaitan dengan negara
  • Laluan lokasi: Menyediakan fungsi REST yang berkaitan dengan lokasi
  • propertyRoutes: Menyediakan fungsi REST yang berkaitan dengan hartanah
  • laluan tempahan: Menyediakan fungsi REST yang berkaitan dengan tempahan
  • Laluan pemberitahuan: Menyediakan fungsi REST yang berkaitan dengan pemberitahuan
  • stripeRoutes: Menyediakan fungsi REST yang berkaitan dengan gerbang pembayaran Stripe

Kami tidak akan menerangkan setiap laluan satu demi satu. Kami akan mengambil, sebagai contoh, propertyRoutes dan melihat cara ia dibuat. Anda boleh menyemak imbas kod sumber dan melihat semua laluan.

Inilah propertyRoutes.ts:

import express from 'express'
import multer from 'multer'
import routeNames from '../config/propertyRoutes.config'
import authJwt from '../middlewares/authJwt'
import * as propertyController from '../controllers/propertyController'

const routes = express.Router()

routes.route(routeNames.create).post(authJwt.verifyToken, propertyController.create)
routes.route(routeNames.update).put(authJwt.verifyToken, propertyController.update)
routes.route(routeNames.checkProperty).get(authJwt.verifyToken, propertyController.checkProperty)
routes.route(routeNames.delete).delete(authJwt.verifyToken, propertyController.deleteProperty)
routes.route(routeNames.uploadImage).post([authJwt.verifyToken, multer({ storage: multer.memoryStorage() }).single('image')], propertyController.uploadImage)
routes.route(routeNames.deleteImage).post(authJwt.verifyToken, propertyController.deleteImage)
routes.route(routeNames.deleteTempImage).post(authJwt.verifyToken, propertyController.deleteTempImage)
routes.route(routeNames.getProperty).get(propertyController.getProperty)
routes.route(routeNames.getProperties).post(authJwt.verifyToken, propertyController.getProperties)
routes.route(routeNames.getBookingProperties).post(authJwt.verifyToken, propertyController.getBookingProperties)
routes.route(routeNames.getFrontendProperties).post(propertyController.getFrontendProperties)

export default routes

Pertama sekali, kami mencipta Penghala Ekspres. Kemudian, kami mencipta laluan menggunakan nama, kaedah, perisian tengah dan pengawalnya.

Nama laluan mengandungi nama laluan propertyRoutes:

const routes = {
  create: '/api/create-property',
  update: '/api/update-property',
  delete: '/api/delete-property/:id',
  uploadImage: '/api/upload-property-image',
  deleteTempImage: '/api/delete-temp-property-image/:fileName',
  deleteImage: '/api/delete-property-image/:property/:image',
  getProperty: '/api/property/:id/:language',
  getProperties: '/api/properties/:page/:size',
  getBookingProperties: '/api/booking-properties/:page/:size',
  getFrontendProperties: '/api/frontend-properties/:page/:size',
  checkProperty: '/api/check-property/:id',
}

export default routes

propertyController mengandungi logik perniagaan utama berkenaan lokasi. Kami tidak akan melihat semua kod sumber pengawal kerana ia agak besar tetapi kami akan mengambil fungsi pengawal sebagai contoh.

Di bawah ialah model Hartanah:

import { Schema, model } from 'mongoose'
import * as movininTypes from ':movinin-types'
import * as env from '../config/env.config'

const propertySchema = new Schema<env.property>(
  {
    name: {
      type: String,
      required: [true, "can't be blank"],
    },
    type: {
      type: String,
      enum: [
        movininTypes.PropertyType.House,
        movininTypes.PropertyType.Apartment,
        movininTypes.PropertyType.Townhouse,
        movininTypes.PropertyType.Plot,
        movininTypes.PropertyType.Farm,
        movininTypes.PropertyType.Commercial,
        movininTypes.PropertyType.Industrial,
      ],
      required: [true, "can't be blank"],
    },
    agency: {
      type: Schema.Types.ObjectId,
      required: [true, "can't be blank"],
      ref: 'User',
      index: true,
    },
    description: {
      type: String,
      required: [true, "can't be blank"],
    },
    available: {
      type: Boolean,
      default: true,
    },
    image: {
      type: String,
    },
    images: {
      type: [String],
    },
    bedrooms: {
      type: Number,
      required: [true, "can't be blank"],
      validate: {
        validator: Number.isInteger,
        message: '{VALUE} is not an integer value',
      },
    },
    bathrooms: {
      type: Number,
      required: [true, "can't be blank"],
      validate: {
        validator: Number.isInteger,
        message: '{VALUE} is not an integer value',
      },
    },
    kitchens: {
      type: Number,
      default: 1,
      validate: {
        validator: Number.isInteger,
        message: '{VALUE} is not an integer value',
      },
    },
    parkingSpaces: {
      type: Number,
      default: 0,
      validate: {
        validator: Number.isInteger,
        message: '{VALUE} is not an integer value',
      },
    },
    size: {
      type: Number,
    },
    petsAllowed: {
      type: Boolean,
      required: [true, "can't be blank"],
    },
    furnished: {
      type: Boolean,
      required: [true, "can't be blank"],
    },
    minimumAge: {
      type: Number,
      required: [true, "can't be blank"],
      min: env.MINIMUM_AGE,
      max: 99,
    },
    location: {
      type: Schema.Types.ObjectId,
      ref: 'Location',
      required: [true, "can't be blank"],
    },
    address: {
      type: String,
    },
    price: {
      type: Number,
      required: [true, "can't be blank"],
    },
    hidden: {
      type: Boolean,
      default: false,
    },
    cancellation: {
      type: Number,
      default: 0,
    },
    aircon: {
      type: Boolean,
      default: false,
    },
    rentalTerm: {
      type: String,
      enum: [
        movininTypes.RentalTerm.Monthly,
        movininTypes.RentalTerm.Weekly,
        movininTypes.RentalTerm.Daily,
        movininTypes.RentalTerm.Yearly,
      ],
      required: [true, "can't be blank"],
    },
  },
  {
    timestamps: true,
    strict: true,
    collection: 'Property',
  },
)

const Property = model<env.property>('Property', propertySchema)

export default Property
</env.property></env.property>

Di bawah ialah jenis Harta:

export interface Property extends Document {
  name: string
  type: movininTypes.PropertyType
  agency: Types.ObjectId
  description: string
  image: string
  images?: string[]
  bedrooms: number
  bathrooms: number
  kitchens?: number
  parkingSpaces?: number,
  size?: number
  petsAllowed: boolean
  furnished: boolean
  minimumAge: number
  location: Types.ObjectId
  address?: string
  price: number
  hidden?: boolean
  cancellation?: number
  aircon?: boolean
  available?: boolean
  rentalTerm: movininTypes.RentalTerm
}

Hartanah terdiri daripada:

  • Nama
  • Sejenis (Apartmen, Komersial, Ladang, Rumah, Perindustrian, Plot, Rumah Bandar)
  • Rujukan kepada agensi yang menciptanya
  • Penerangan
  • Imej utama
  • Imej tambahan
  • Bilangan bilik tidur
  • Bilangan bilik air
  • Bilangan dapur
  • Bilangan tempat letak kereta
  • Saiz A
  • Umur minimum untuk sewa
  • Lokasi
  • Alamat (pilihan)
  • Harga
  • Tempoh sewaan (Bulanan, Mingguan, Harian, Tahunan)
  • Harga pembatalan (tetapkan kepada 0 untuk disertakan secara percuma, biarkan kosong jika anda tidak mahu memasukkannya, atau tetapkan harga untuk pembatalan)
  • Bendera yang menunjukkan sama ada haiwan peliharaan dibenarkan atau tidak
  • Bendera yang menunjukkan sama ada harta itu dilengkapi atau tidak
  • Bendera yang menunjukkan sama ada harta itu tersembunyi atau tidak
  • Bendera yang menunjukkan sama ada penyaman udara tersedia atau tidak
  • Bendera yang menunjukkan sama ada hartanah itu tersedia untuk disewa atau tidak

Di bawah ialah cipta fungsi pengawal:

import 'dotenv/config'
import process from 'node:process'
import fs from 'node:fs/promises'
import http from 'node:http'
import https, { ServerOptions } from 'node:https'
import app from './app'
import * as databaseHelper from './common/databaseHelper'
import * as env from './config/env.config'
import * as logger from './common/logger'

if (
  await databaseHelper.connect(env.DB_URI, env.DB_SSL, env.DB_DEBUG) 
  && await databaseHelper.initialize()
) {
  let server: http.Server | https.Server

  if (env.HTTPS) {
    https.globalAgent.maxSockets = Number.POSITIVE_INFINITY
    const privateKey = await fs.readFile(env.PRIVATE_KEY, 'utf8')
    const certificate = await fs.readFile(env.CERTIFICATE, 'utf8')
    const credentials: ServerOptions = { key: privateKey, cert: certificate }
    server = https.createServer(credentials, app)

    server.listen(env.PORT, () => {
      logger.info('HTTPS server is running on Port', env.PORT)
    })
  } else {
    server = app.listen(env.PORT, () => {
      logger.info('HTTP server is running on Port', env.PORT)
    })
  }

  const close = () => {
    logger.info('Gracefully stopping...')
    server.close(async () => {
      logger.info(`HTTP${env.HTTPS ? 'S' : ''} server closed`)
      await databaseHelper.close(true)
      logger.info('MongoDB connection closed')
      process.exit(0)
    })
  }

  ['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach((signal) => process.on(signal, close))
}

Bahagian hadapan

Halaman hadapan ialah aplikasi web yang dibina dengan Node.js, React, MUI dan TypeScript. Dari bahagian hadapan, pelanggan boleh mencari kereta yang tersedia bergantung pada tempat pengambilan dan penghantaran serta masa, pilih kereta dan teruskan ke pembayaran:

  • ./frontend/src/assets/ folder mengandungi CSS dan imej.
  • ./frontend/src/pages/ folder mengandungi halaman React.
  • ./frontend/src/komponen/ folder mengandungi komponen React.
  • ./frontend/src/services/ mengandungi perkhidmatan klien api.
  • ./frontend/src/App.tsx ialah Apl React utama yang mengandungi laluan.
  • ./frontend/src/index.tsx ialah titik masuk utama bahagian hadapan.

Takrif jenis TypeScript ditakrifkan dalam pakej ./packages/movinin-types.

App.tsx ialah Apl tindak balas utama:

import express from 'express'
import compression from 'compression'
import helmet from 'helmet'
import nocache from 'nocache'
import cookieParser from 'cookie-parser'
import i18n from './lang/i18n'
import * as env from './config/env.config'
import cors from './middlewares/cors'
import allowedMethods from './middlewares/allowedMethods'
import agencyRoutes from './routes/agencyRoutes'
import bookingRoutes from './routes/bookingRoutes'
import locationRoutes from './routes/locationRoutes'
import notificationRoutes from './routes/notificationRoutes'
import propertyRoutes from './routes/propertyRoutes'
import userRoutes from './routes/userRoutes'
import stripeRoutes from './routes/stripeRoutes'
import countryRoutes from './routes/countryRoutes'
import * as helper from './common/helper'

const app = express()

app.use(helmet.contentSecurityPolicy())
app.use(helmet.dnsPrefetchControl())
app.use(helmet.crossOriginEmbedderPolicy())
app.use(helmet.frameguard())
app.use(helmet.hidePoweredBy())
app.use(helmet.hsts())
app.use(helmet.ieNoOpen())
app.use(helmet.noSniff())
app.use(helmet.permittedCrossDomainPolicies())
app.use(helmet.referrerPolicy())
app.use(helmet.xssFilter())
app.use(helmet.originAgentCluster())
app.use(helmet.crossOriginResourcePolicy({ policy: 'cross-origin' }))
app.use(helmet.crossOriginOpenerPolicy())

app.use(nocache())
app.use(compression({ threshold: 0 }))
app.use(express.urlencoded({ limit: '50mb', extended: true }))
app.use(express.json({ limit: '50mb' }))

app.use(cors())
app.options('*', cors())
app.use(cookieParser(env.COOKIE_SECRET))
app.use(allowedMethods)

app.use('/', agencyRoutes)
app.use('/', bookingRoutes)
app.use('/', locationRoutes)
app.use('/', notificationRoutes)
app.use('/', propertyRoutes)
app.use('/', userRoutes)
app.use('/', stripeRoutes)
app.use('/', countryRoutes)

i18n.locale = env.DEFAULT_LANGUAGE

helper.mkdir(env.CDN_USERS)
helper.mkdir(env.CDN_TEMP_USERS)
helper.mkdir(env.CDN_PROPERTIES)
helper.mkdir(env.CDN_TEMP_PROPERTIES)
helper.mkdir(env.CDN_LOCATIONS)
helper.mkdir(env.CDN_TEMP_LOCATIONS)

export default app

Kami menggunakan React lazy loading untuk memuatkan setiap laluan.

Kami tidak akan menutup setiap halaman bahagian hadapan, tetapi anda boleh menyemak imbas kod sumber dan melihat setiap satu.

Apl Mudah Alih

Platform ini menyediakan apl mudah alih asli untuk Android dan iOS. Apl mudah alih dibina dengan React Native, Expo dan TypeScript. Seperti untuk bahagian hadapan, apl mudah alih membolehkan pelanggan mencari kereta yang tersedia bergantung pada tempat pengambilan dan penghantaran serta masa, pilih kereta dan teruskan ke pembayaran.

Pelanggan menerima pemberitahuan tolak jika tempahannya dikemas kini dari bahagian belakang. Pemberitahuan tolak dibina dengan Node.js, SDK Pelayan Ekspo dan Firebase.

  • ./mudah alih/aset/ folder mengandungi imej.
  • ./mudah alih/skrin/ folder mengandungi skrin React Native utama.
  • ./mudah alih/komponen/ folder mengandungi komponen React Native.
  • ./mobile/services/ mengandungi perkhidmatan pelanggan api.
  • ./mobile/App.tsx ialah Apl Asli React yang utama.

Takrif jenis TypeScript ditakrifkan dalam:

  • ./mobile/types/index.d.ts
  • ./mobile/types/env.d.ts
  • ./mobile/miscellaneous/movininTypes.ts

./mobile/types/ dimuatkan dalam ./mobile/tsconfig.json seperti berikut:

import 'dotenv/config'
import process from 'node:process'
import fs from 'node:fs/promises'
import http from 'node:http'
import https, { ServerOptions } from 'node:https'
import app from './app'
import * as databaseHelper from './common/databaseHelper'
import * as env from './config/env.config'
import * as logger from './common/logger'

if (
  await databaseHelper.connect(env.DB_URI, env.DB_SSL, env.DB_DEBUG) 
  && await databaseHelper.initialize()
) {
  let server: http.Server | https.Server

  if (env.HTTPS) {
    https.globalAgent.maxSockets = Number.POSITIVE_INFINITY
    const privateKey = await fs.readFile(env.PRIVATE_KEY, 'utf8')
    const certificate = await fs.readFile(env.CERTIFICATE, 'utf8')
    const credentials: ServerOptions = { key: privateKey, cert: certificate }
    server = https.createServer(credentials, app)

    server.listen(env.PORT, () => {
      logger.info('HTTPS server is running on Port', env.PORT)
    })
  } else {
    server = app.listen(env.PORT, () => {
      logger.info('HTTP server is running on Port', env.PORT)
    })
  }

  const close = () => {
    logger.info('Gracefully stopping...')
    server.close(async () => {
      logger.info(`HTTP${env.HTTPS ? 'S' : ''} server closed`)
      await databaseHelper.close(true)
      logger.info('MongoDB connection closed')
      process.exit(0)
    })
  }

  ['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach((signal) => process.on(signal, close))
}

App.tsx ialah pintu masuk utama apl React Native:

import 'react-native-gesture-handler'
import React, { useCallback, useEffect, useRef, useState } daripada 'react'
import { RootSiblingParent } daripada 'react-native-root-siblings'
import { NavigationContainer, NavigationContainerRef } daripada '@react-navigation/native'
import { StatusBar sebagai ExpoStatusBar } daripada 'expo-status-bar'
import { SafeAreaProvider } daripada 'react-native-safe-area-context'
import { Provider } daripada 'react-native-paper'
import * sebagai SplashScreen daripada 'expo-splash-screen'
import * sebagai Pemberitahuan daripada 'pemberitahuan ekspo'
import { StripeProvider } daripada '@stripe/stripe-react-native'
import DrawerNavigator daripada './components/DrawerNavigator'
import * sebagai pembantu daripada './common/helper'
import * sebagai NotificationService daripada './services/NotificationService'
import * sebagai UserService daripada './services/UserService'
import { GlobalProvider } daripada './context/GlobalContext'
import * sebagai env daripada './config/env.config'

Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: benar,
    shouldPlaySound: benar,
    shouldSetBadge: benar,
  }),
})

//
// Cegah skrin percikan asli daripada menyorok secara automatik sebelum pengisytiharan komponen Apl
//
SplashScreen.preventAutoHideAsync()
  .then((result) => console.log(`SplashScreen.preventAutoHideAsync() berjaya: ${result}`))
  .catch(console.warn) // adalah baik untuk menangkap dan memeriksa sebarang ralat secara eksplisit

const App = () => {
  const [appIsReady, setAppIsReady] = useState(false)

  const responseListener = useRef<pemberitahuan.langganan>()
  const navigationRef = useRef<navigationcontainerref>>(null)

  useEffect(() => {
    daftar const = tak segerak () => {
      const loggedIn = tunggu UserService.loggedIn()
      jika (log masuk) {
        const currentUser = menunggu UserService.getCurrentUser()
        jika (Pengguna semasa?._id) {
          tunggu helper.registerPushToken(currentUser._id)
        } lain {
          helper.error()
        }
      }
    }

    //
    // Daftar token pemberitahuan tolak
    //
    daftar()

    //
    // Pendengar ini dipecat apabila pengguna mengetik atau berinteraksi dengan pemberitahuan (berfungsi apabila apl di latar depan, berlatar belakang atau dimatikan)
    //
    responseListener.current = Notifications.addNotificationResponseReceivedListener(async (respons) => {
      cuba {
        jika (navigationRef.current) {
          const { data } = respons.notification.request.content

          jika (data.booking) {
            if (data.user && data.notification) {
              tunggu NotificationService.markAsRead(data.user, [data.notification])
            }
            navigationRef.current.navigate('Booking', { id: data.booking })
          } lain {
            navigationRef.current.navigate('Pemberitahuan', {})
          }
        }
      } tangkap (err) {
        helper.error(err, false)
      }
    })

    pulangan () => {
      Notifications.removeNotificationSubscription(responseListener.current!)
    }
  }, [])

  setTimeout(() => {
    setAppIsReady(benar)
  }, 500)

  const onReady = useCallback(async () => {
    jika (appIsReady) {
      //
      // Ini memberitahu skrin percikan untuk bersembunyi serta-merta! Jika kita memanggil ini selepas
      // `setAppIsReady`, maka kita mungkin melihat skrin kosong semasa apl itu
      // memuatkan keadaan awalnya dan memaparkan piksel pertamanya. Jadi sebaliknya,
      // kami menyembunyikan skrin percikan sebaik sahaja kami mengetahui paparan akar sudah ada
      // susun atur yang dilakukan.
      //
      tunggu SplashScreen.hideAsync()
    }
  }, [appIsReady])

  jika (!appIsReady) {
    kembali null
  }

  kembali (
    <penyedia global>
      <safeareaprovider>
        <pembekal>
          <stripeprovider publishablekey="{env.STRIPE_PUBLISHABLE_KEY}" merchantidentifier="{env.STRIPE_MERCHANT_IDENTIFIER}">
            <rootsiblingparent>
              <navigationcontainer ref="{navigationRef}" onready="{onReady}">
                <expostatusbar>



<p>Kami tidak akan menutup setiap skrin apl mudah alih, tetapi anda boleh menyemak imbas kod sumber dan melihat setiap satu.</p>

<h2>
  
  
  Papan Pemuka Pentadbir
</h2>

<p>Papan pemuka pentadbir ialah aplikasi web yang dibina dengan Node.js, React, MUI dan TypeScript. Dari bahagian belakang, pentadbir boleh membuat dan mengurus pembekal, kereta, lokasi, pelanggan dan tempahan. Apabila pembekal baharu dibuat dari bahagian belakang, mereka akan menerima e-mel yang menggesa mereka membuat akaun untuk mengakses papan pemuka pentadbir dan mengurus armada dan tempahan kereta mereka.</p>

<ul>
<li>./backend/assets/ folder mengandungi CSS dan imej.</li>
<li>./backend/pages/ folder mengandungi halaman React.</li>
<li>./backend/komponen/ folder mengandungi komponen React.</li>
<li>./backend/services/ mengandungi perkhidmatan pelanggan api.</li>
<li>./backend/App.tsx ialah Apl Reaksi utama yang mengandungi laluan.</li>
<li>./backend/index.tsx ialah titik masuk utama papan pemuka pentadbir.</li>
</ul>

<p>Takrif jenis TypeScript ditakrifkan dalam pakej ./packages/movinin-types.</p>

<p>App.tsx papan pemuka pentadbir mengikut logik yang serupa seperti App.tsx bahagian hadapan.</p>

<p>Kami tidak akan menutup setiap halaman papan pemuka pentadbir tetapi anda boleh menyemak imbas kod sumber dan melihat setiap satu.</p>

<h2>
  
  
  Tempat Menarik
</h2>

<p>Membina apl mudah alih dengan React Native dan Expo adalah sangat mudah. Ekspo menjadikan pembangunan mudah alih dengan React Native sangat mudah.</p>

<p>Menggunakan bahasa yang sama (TypeScript) untuk pembangunan hujung belakang, bahagian hadapan dan mudah alih adalah sangat mudah.</p>

<p>TypeScript ialah bahasa yang sangat menarik dan mempunyai banyak kelebihan. Dengan menambahkan penaipan statik pada JavaScript, kami boleh mengelakkan banyak pepijat dan menghasilkan kod berkualiti tinggi, berskala, lebih mudah dibaca dan boleh diselenggara yang mudah untuk nyahpepijat dan diuji.</p>

<p>Itu sahaja! Saya harap anda seronok membaca artikel ini.</p>
<h2>
  
  
  Sumber
</h2>

<ol>
<li>Ikhtisar</li>
<li>Seni Bina</li>
<li>Memasang (Dihoskan sendiri)</li>
<li>Memasang (VPS)</li>
<li>
Memasang (Docker)

<ol>
<li>Imej Docker</li>
<li>SSL</li>
</ol>


</li>

<li>Sediakan Jalur</li>

<li>Bina Apl Mudah Alih</li>

<li>

Pangkalan Data Demo

<ol>
<li>Windows, Linux dan macOS</li>
<li>Pelabuh</li>
</ol>


</li>

<li>Larikan dari Sumber</li>

<li>

Jalankan Apl Mudah Alih

<ol>
<li>Prasyarat</li>
<li>Arahan</li>
<li>Pemberitahuan Tolak</li>
</ol>


</li>

<li>Tukar Mata Wang</li>

<li>Tambah Bahasa Baharu</li>

<li>Ujian Unit dan Liputan</li>

<li>Balak</li>

</ol>


          

            
        </expostatusbar></navigationcontainer></rootsiblingparent></stripeprovider></pembekal></safeareaprovider></penyedia></navigationcontainerref></pemberitahuan.langganan>

Atas ialah kandungan terperinci Dari Sifar ke Etalase: Perjalanan Saya Membina Platform Penyewaan Hartanah. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
JavaScript dan Web: Fungsi teras dan kes penggunaanJavaScript dan Web: Fungsi teras dan kes penggunaanApr 18, 2025 am 12:19 AM

Penggunaan utama JavaScript dalam pembangunan web termasuk interaksi klien, pengesahan bentuk dan komunikasi tak segerak. 1) kemas kini kandungan dinamik dan interaksi pengguna melalui operasi DOM; 2) pengesahan pelanggan dijalankan sebelum pengguna mengemukakan data untuk meningkatkan pengalaman pengguna; 3) Komunikasi yang tidak bersesuaian dengan pelayan dicapai melalui teknologi Ajax.

Memahami Enjin JavaScript: Butiran PelaksanaanMemahami Enjin JavaScript: Butiran PelaksanaanApr 17, 2025 am 12:05 AM

Memahami bagaimana enjin JavaScript berfungsi secara dalaman adalah penting kepada pemaju kerana ia membantu menulis kod yang lebih cekap dan memahami kesesakan prestasi dan strategi pengoptimuman. 1) aliran kerja enjin termasuk tiga peringkat: parsing, penyusun dan pelaksanaan; 2) Semasa proses pelaksanaan, enjin akan melakukan pengoptimuman dinamik, seperti cache dalam talian dan kelas tersembunyi; 3) Amalan terbaik termasuk mengelakkan pembolehubah global, mengoptimumkan gelung, menggunakan const dan membiarkan, dan mengelakkan penggunaan penutupan yang berlebihan.

Python vs JavaScript: Keluk Pembelajaran dan Kemudahan PenggunaanPython vs JavaScript: Keluk Pembelajaran dan Kemudahan PenggunaanApr 16, 2025 am 12:12 AM

Python lebih sesuai untuk pemula, dengan lengkung pembelajaran yang lancar dan sintaks ringkas; JavaScript sesuai untuk pembangunan front-end, dengan lengkung pembelajaran yang curam dan sintaks yang fleksibel. 1. Sintaks Python adalah intuitif dan sesuai untuk sains data dan pembangunan back-end. 2. JavaScript adalah fleksibel dan digunakan secara meluas dalam pengaturcaraan depan dan pelayan.

Python vs JavaScript: Komuniti, Perpustakaan, dan SumberPython vs JavaScript: Komuniti, Perpustakaan, dan SumberApr 15, 2025 am 12:16 AM

Python dan JavaScript mempunyai kelebihan dan kekurangan mereka sendiri dari segi komuniti, perpustakaan dan sumber. 1) Komuniti Python mesra dan sesuai untuk pemula, tetapi sumber pembangunan depan tidak kaya dengan JavaScript. 2) Python berkuasa dalam bidang sains data dan perpustakaan pembelajaran mesin, sementara JavaScript lebih baik dalam perpustakaan pembangunan dan kerangka pembangunan depan. 3) Kedua -duanya mempunyai sumber pembelajaran yang kaya, tetapi Python sesuai untuk memulakan dengan dokumen rasmi, sementara JavaScript lebih baik dengan MDNWebDocs. Pilihan harus berdasarkan keperluan projek dan kepentingan peribadi.

Dari C/C ke JavaScript: Bagaimana semuanya berfungsiDari C/C ke JavaScript: Bagaimana semuanya berfungsiApr 14, 2025 am 12:05 AM

Peralihan dari C/C ke JavaScript memerlukan menyesuaikan diri dengan menaip dinamik, pengumpulan sampah dan pengaturcaraan asynchronous. 1) C/C adalah bahasa yang ditaip secara statik yang memerlukan pengurusan memori manual, manakala JavaScript ditaip secara dinamik dan pengumpulan sampah diproses secara automatik. 2) C/C perlu dikumpulkan ke dalam kod mesin, manakala JavaScript adalah bahasa yang ditafsirkan. 3) JavaScript memperkenalkan konsep seperti penutupan, rantaian prototaip dan janji, yang meningkatkan keupayaan pengaturcaraan fleksibiliti dan asynchronous.

Enjin JavaScript: Membandingkan PelaksanaanEnjin JavaScript: Membandingkan PelaksanaanApr 13, 2025 am 12:05 AM

Enjin JavaScript yang berbeza mempunyai kesan yang berbeza apabila menguraikan dan melaksanakan kod JavaScript, kerana prinsip pelaksanaan dan strategi pengoptimuman setiap enjin berbeza. 1. Analisis leksikal: Menukar kod sumber ke dalam unit leksikal. 2. Analisis Tatabahasa: Menjana pokok sintaks abstrak. 3. Pengoptimuman dan Penyusunan: Menjana kod mesin melalui pengkompil JIT. 4. Jalankan: Jalankan kod mesin. Enjin V8 mengoptimumkan melalui kompilasi segera dan kelas tersembunyi, Spidermonkey menggunakan sistem kesimpulan jenis, menghasilkan prestasi prestasi yang berbeza pada kod yang sama.

Beyond the Browser: JavaScript di dunia nyataBeyond the Browser: JavaScript di dunia nyataApr 12, 2025 am 12:06 AM

Aplikasi JavaScript di dunia nyata termasuk pengaturcaraan sisi pelayan, pembangunan aplikasi mudah alih dan Internet of Things Control: 1. Pengaturcaraan sisi pelayan direalisasikan melalui node.js, sesuai untuk pemprosesan permintaan serentak yang tinggi. 2. Pembangunan aplikasi mudah alih dijalankan melalui reaktnatif dan menyokong penggunaan silang platform. 3. Digunakan untuk kawalan peranti IoT melalui Perpustakaan Johnny-Five, sesuai untuk interaksi perkakasan.

Membina aplikasi SaaS Multi-penyewa dengan Next.js (Integrasi Backend)Membina aplikasi SaaS Multi-penyewa dengan Next.js (Integrasi Backend)Apr 11, 2025 am 08:23 AM

Saya membina aplikasi SaaS multi-penyewa berfungsi (aplikasi edTech) dengan alat teknologi harian anda dan anda boleh melakukan perkara yang sama. Pertama, apakah aplikasi SaaS multi-penyewa? Aplikasi SaaS Multi-penyewa membolehkan anda melayani beberapa pelanggan dari Sing

See all articles

Alat AI Hot

Undresser.AI Undress

Undresser.AI Undress

Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover

AI Clothes Remover

Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool

Undress AI Tool

Gambar buka pakaian secara percuma

Clothoff.io

Clothoff.io

Penyingkiran pakaian AI

AI Hentai Generator

AI Hentai Generator

Menjana ai hentai secara percuma.

Artikel Panas

R.E.P.O. Kristal tenaga dijelaskan dan apa yang mereka lakukan (kristal kuning)
1 bulan yang laluBy尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Tetapan grafik terbaik
1 bulan yang laluBy尊渡假赌尊渡假赌尊渡假赌
Akan R.E.P.O. Ada Crossplay?
1 bulan yang laluBy尊渡假赌尊渡假赌尊渡假赌

Alat panas

VSCode Windows 64-bit Muat Turun

VSCode Windows 64-bit Muat Turun

Editor IDE percuma dan berkuasa yang dilancarkan oleh Microsoft

Notepad++7.3.1

Notepad++7.3.1

Editor kod yang mudah digunakan dan percuma

MinGW - GNU Minimalis untuk Windows

MinGW - GNU Minimalis untuk Windows

Projek ini dalam proses untuk dipindahkan ke osdn.net/projects/mingw, anda boleh terus mengikuti kami di sana. MinGW: Port Windows asli bagi GNU Compiler Collection (GCC), perpustakaan import yang boleh diedarkan secara bebas dan fail pengepala untuk membina aplikasi Windows asli termasuk sambungan kepada masa jalan MSVC untuk menyokong fungsi C99. Semua perisian MinGW boleh dijalankan pada platform Windows 64-bit.

Versi Mac WebStorm

Versi Mac WebStorm

Alat pembangunan JavaScript yang berguna

SublimeText3 Linux versi baharu

SublimeText3 Linux versi baharu

SublimeText3 Linux versi terkini