Kod sumber: https://github.com/aelassas/wexcommerce
Demo: https://wexcommerce.dynv6.net:8002
Bagi pembangun yang menghargai kebebasan kreatif dan kawalan teknikal, platform e-Dagang tradisional seperti Shopify boleh berasa terhad. Walaupun templat Shopify menawarkan persediaan pantas, dan API Storefront mereka memberikan sedikit fleksibiliti, kedua-dua penyelesaian tidak memberikan kebebasan seni bina lengkap yang diidamkan oleh pembangun moden.
Idea itu muncul daripada keinginan untuk membina tanpa sempadan – tapak eDagang yang boleh disesuaikan sepenuhnya di mana setiap aspek berada dalam kawalan anda:
Berikut ialah susunan teknologi yang membolehkannya:
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 Next.js kerana keupayaan pemaparan yang berkuasa, MongoDB untuk pemodelan data fleksibel dan Stripe untuk pemprosesan pembayaran yang selamat.
Dengan memilih tindanan ini, anda bukan sekadar membina kedai – anda melabur dalam asas yang boleh berkembang mengikut keperluan anda, disokong oleh teknologi sumber terbuka yang teguh dan komuniti pembangun yang semakin berkembang.
Membina tapak dengan Next.js menyediakan asas yang kukuh untuk menskalakan perniagaan. Fokus pada prestasi, keselamatan dan pengalaman pengguna sambil mengekalkan kualiti dan dokumentasi kod. Kemas kini dan pemantauan yang kerap akan memastikan platform kekal kompetitif dan boleh dipercayai.
Next.js menonjol sebagai pilihan yang sangat baik kerana:
Dari bahagian hadapan, pengguna boleh mencari produk yang tersedia, menambah produk ke troli dan pembayaran.
Di bawah ialah halaman pendaratan bahagian hadapan:
Di bawah ialah halaman carian bahagian hadapan:
Di bawah ialah contoh halaman produk:
Di bawah ialah paparan skrin penuh imej produk:
Di bawah ialah halaman troli:
Di bawah ialah halaman pembayaran:
Di bawah ialah halaman log masuk:
Di bawah ialah halaman pendaftaran:
Di bawah ialah halaman di mana pengguna boleh melihat pesanannya:
Itu sahaja! Itu adalah halaman utama bahagian hadapan.
Daripada papan pemuka pentadbir, pentadbir boleh mengurus kategori, produk, pengguna dan pesanan.
Pentadbir juga boleh mengurus tetapan berikut:
Di bawah ialah halaman log masuk:
Di bawah ialah halaman papan pemuka dari mana pentadbir boleh melihat dan mengurus pesanan:
Di bawah ialah halaman dari mana pentadbir menguruskan kategori:
Di bawah ialah halaman dari mana pentadbir boleh melihat dan mengurus produk:
Di bawah ialah halaman dari mana pentadbir mengedit produk:
Di bawah ialah paparan skrin penuh imej produk:
Di bawah ialah halaman tetapan:
Itu sahaja. Itu adalah halaman utama papan pemuka admin.
Api ialah aplikasi pelayan Node.js yang mendedahkan API RESTful menggunakan Express yang memberikan akses kepada pangkalan data MongoDB.
Api digunakan oleh bahagian hadapan, papan pemuka pentadbir dan akan digunakan oleh apl mudah alih juga.
Api mendedahkan semua fungsi yang diperlukan untuk papan pemuka pentadbir dan bahagian hadapan. Api mengikut corak reka bentuk MVC. JWT digunakan untuk pengesahan. Terdapat beberapa fungsi yang memerlukan pengesahan seperti fungsi yang berkaitan dengan menguruskan produk dan pesanan, dan lain-lain yang tidak memerlukan pengesahan seperti mendapatkan semula kategori dan produk yang tersedia untuk pengguna yang tidak disahkan:
index.ts berada dalam pelayan utama:
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 * as env from './config/env.config' import * as databaseHelper from './common/databaseHelper' import app from './app' 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 mewujudkan sambungan dengan pangkalan data MongoDB. 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 userRoutes from './routes/userRoutes' import categoryRoutes from './routes/categoryRoutes' import productRoutes from './routes/productRoutes' import cartRoutes from './routes/cartRoutes' import orderRoutes from './routes/orderRoutes' import notificationRoutes from './routes/notificationRoutes' import deliveryTypeRoutes from './routes/deliveryTypeRoutes' import paymentTypeRoutes from './routes/paymentTypeRoutes' import settingRoutes from './routes/settingRoutes' import stripeRoutes from './routes/stripeRoutes' import wishlistRoutes from './routes/wishlistRoutes' 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('/', userRoutes) app.use('/', categoryRoutes) app.use('/', productRoutes) app.use('/', cartRoutes) app.use('/', orderRoutes) app.use('/', notificationRoutes) app.use('/', deliveryTypeRoutes) app.use('/', paymentTypeRoutes) app.use('/', settingRoutes) app.use('/', stripeRoutes) app.use('/', wishlistRoutes) i18n.locale = env.DEFAULT_LANGUAGE await helper.mkdir(env.CDN_USERS) await helper.mkdir(env.CDN_TEMP_USERS) await helper.mkdir(env.CDN_CATEGORIES) await helper.mkdir(env.CDN_TEMP_CATEGORIES) await helper.mkdir(env.CDN_PRODUCTS) await helper.mkdir(env.CDN_TEMP_PRODUCTS) export default app
Pertama sekali, kami mencipta apl Ekspres 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 productRoutes, orderRoutes, categoryRoutes, notificationRoutes, userRoutes. Akhir sekali, kami memuatkan laluan Ekspres dan apl eksport.
Terdapat 11 laluan dalam api. Setiap laluan mempunyai pengawal sendiri mengikut corak reka bentuk MVC dan prinsip SOLID. Di bawah adalah laluan utama:
Kami tidak akan menerangkan setiap laluan satu demi satu. Kami akan mengambil, sebagai contoh, kategoriLaluan dan melihat cara ia dibuat:
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 * as env from './config/env.config' import * as databaseHelper from './common/databaseHelper' import app from './app' 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)) }
Pertama sekali, kami mencipta Penghala Ekspres. Kemudian, kami mencipta laluan menggunakan namanya, kaedahnya, perisian tengah dan pengawalnya.
Nama laluan mengandungi kategoriNama laluan:
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 userRoutes from './routes/userRoutes' import categoryRoutes from './routes/categoryRoutes' import productRoutes from './routes/productRoutes' import cartRoutes from './routes/cartRoutes' import orderRoutes from './routes/orderRoutes' import notificationRoutes from './routes/notificationRoutes' import deliveryTypeRoutes from './routes/deliveryTypeRoutes' import paymentTypeRoutes from './routes/paymentTypeRoutes' import settingRoutes from './routes/settingRoutes' import stripeRoutes from './routes/stripeRoutes' import wishlistRoutes from './routes/wishlistRoutes' 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('/', userRoutes) app.use('/', categoryRoutes) app.use('/', productRoutes) app.use('/', cartRoutes) app.use('/', orderRoutes) app.use('/', notificationRoutes) app.use('/', deliveryTypeRoutes) app.use('/', paymentTypeRoutes) app.use('/', settingRoutes) app.use('/', stripeRoutes) app.use('/', wishlistRoutes) i18n.locale = env.DEFAULT_LANGUAGE await helper.mkdir(env.CDN_USERS) await helper.mkdir(env.CDN_TEMP_USERS) await helper.mkdir(env.CDN_CATEGORIES) await helper.mkdir(env.CDN_TEMP_CATEGORIES) await helper.mkdir(env.CDN_PRODUCTS) await helper.mkdir(env.CDN_TEMP_PRODUCTS) export default app
categoryController mengandungi logik perniagaan utama mengenai kategori. Kami tidak akan melihat semua kod sumber pengawal kerana ia agak besar tetapi kami akan mengambil fungsi pengawal sebagai contoh.
Di bawah ialah model Kategori:
import express from 'express' import multer from 'multer' import routeNames from '../config/categoryRoutes.config' import authJwt from '../middlewares/authJwt' import * as categoryController from '../controllers/categoryController' const routes = express.Router() routes.route(routeNames.validate).post(authJwt.verifyToken, categoryController.validate) routes.route(routeNames.checkCategory).get(authJwt.verifyToken, categoryController.checkCategory) routes.route(routeNames.create).post(authJwt.verifyToken, categoryController.create) routes.route(routeNames.update).put(authJwt.verifyToken, categoryController.update) routes.route(routeNames.delete).delete(authJwt.verifyToken, categoryController.deleteCategory) routes.route(routeNames.getCategory).get(authJwt.verifyToken, categoryController.getCategory) routes.route(routeNames.getCategories).get(categoryController.getCategories) routes.route(routeNames.getFeaturedCategories).get(categoryController.getFeaturedCategories) routes.route(routeNames.searchCategories).get(authJwt.verifyToken, categoryController.searchCategories) routes.route(routeNames.createImage).post([authJwt.verifyToken, multer({ storage: multer.memoryStorage() }).single('image')], categoryController.createImage) routes.route(routeNames.updateImage).post([authJwt.verifyToken, multer({ storage: multer.memoryStorage() }).single('image')], categoryController.updateImage) routes.route(routeNames.deleteImage).post(authJwt.verifyToken, categoryController.deleteImage) routes.route(routeNames.deleteTempImage).post(authJwt.verifyToken, categoryController.deleteTempImage) export default routes
Kategori mempunyai berbilang nilai. Satu nilai bagi setiap bahasa. Secara lalai, bahasa Inggeris dan Perancis disokong.
Di bawah ialah model Nilai:
export default { validate: '/api/validate-category', checkCategory: '/api/check-category/:id', create: '/api/create-category', update: '/api/update-category/:id', delete: '/api/delete-category/:id', getCategory: '/api/category/:id/:language', getCategories: '/api/categories/:language/:imageRequired', getFeaturedCategories: '/api/featured-categories/:language/:size', searchCategories: '/api/search-categories/:language', createImage: '/api/create-category-image', updateImage: '/api/update-category-image/:id', deleteImage: '/api/delete-category-image/:id', deleteTempImage: '/api/delete-temp-category-image/:image', }
Nilai mempunyai kod bahasa (ISO 639-1) dan nilai rentetan.
Di bawah ialah cipta fungsi pengawal:
import { Schema, model } from 'mongoose' import * as env from '../config/env.config' const categorySchema = new Schema<env.Category>({ values: { type: [Schema.Types.ObjectId], ref: 'Value', validate: (value: any) => Array.isArray(value), }, image: { type: String, }, featured: { type: Boolean, default: false, }, }, { timestamps: true, strict: true, collection: 'Category', }) const Category = model<env.Category>('Category', categorySchema) export default Category
Dalam fungsi ini, kami mendapatkan semula kandungan permintaan, kami mengulangi nilai yang disediakan dalam kandungan (satu nilai setiap bahasa) dan kami mencipta Nilai. Akhir sekali, kami mencipta kategori bergantung pada nilai yang dicipta dan fail imej.
Halaman hadapan ialah aplikasi web yang dibina dengan Next.js dan MUI. Dari bahagian hadapan, pengguna boleh mencari produk yang tersedia, menambahkannya pada troli dan meneruskan pembayaran bergantung pada penghantaran dan kaedah pembayaran yang tersedia.
Halaman hadapan telah dibuat dengan create-next-app:
import { Schema, model } from 'mongoose' import * as env from '../config/env.config' const locationValueSchema = new Schema<env.Value>( { language: { type: String, required: [true, "can't be blank"], index: true, trim: true, lowercase: true, minLength: 2, maxLength: 2, }, value: { type: String, required: [true, "can't be blank"], index: true, trim: true, }, }, { timestamps: true, strict: true, collection: 'Value', }, ) const Value = model<env.Value>('Value', locationValueSchema) export default Value
Dalam Next.js, halaman ialah Komponen Reaksi yang dieksport daripada fail .js, .jsx, .ts atau .tsx dalam direktori halaman. Setiap halaman dikaitkan dengan laluan berdasarkan nama failnya.
Secara lalai, Next.js memprapaparkan setiap halaman. Ini bermakna Next.js menjana HTML untuk setiap halaman terlebih dahulu, dan bukannya melakukan semuanya oleh JavaScript pihak pelanggan. Prapemarahan boleh menghasilkan prestasi dan SEO yang lebih baik.
Setiap HTML yang dijana dikaitkan dengan kod JavaScript minimum yang diperlukan untuk halaman tersebut. Apabila halaman dimuatkan oleh penyemak imbas, kod JavaScriptnya berjalan dan menjadikan halaman itu interaktif sepenuhnya. (Proses ini dipanggil penghidratan.)
bahagian hadapan menggunakan Rendering Sisi Pelayan untuk pengoptimuman SEO supaya produk boleh diindeks oleh enjin carian.
Papan pemuka pentadbir ialah aplikasi web yang dibina dengan Next.js dan MUI. Daripada papan pemuka pentadbir, pentadbir boleh mengurus kategori, produk, pesanan dan pengguna. Apabila pesanan baharu dibuat, pengguna pentadbir mendapat pemberitahuan dan menerima e-mel.
Papan pemuka pentadbir juga telah dibuat dengan create-next-app:
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 * as env from './config/env.config' import * as databaseHelper from './common/databaseHelper' import app from './app' 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)) }
Itu sahaja! Saya harap anda seronok membaca.
Atas ialah kandungan terperinci Dari Sifar ke Etalase: Perjalanan Saya Membina Tapak E-dagang. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!