宗和玉 发表于 7 天前

记一次开发

记一次全栈开发

目录


[*]项目概述
[*]技术栈选择
[*]前端开发
[*]后端开发
[*]性能优化
[*]部署流程
[*]常见问题与解决方案
[*]维护与更新
概述

企业官网是公司的数字门面,不仅展示公司形象,还承载着品牌传播、产品展示、客户沟通等多种功能,我身为一名全栈开发又是公司的董事长必须自己开发
现在让我们开始吧,程序代码全部手敲耗时20小时,希望大家可以给各位开发者带来帮助
核心功能需求


[*]公司简介与品牌展示
[*]产品与服务介绍
[*]成功案例展示
[*]新闻动态发布
[*]团队介绍
[*]联系方式
[*]后台内容管理系统
技术栈选择

前端技术栈

技术版本用途Vue.js3.x前端框架Vite4.x构建工具Vue Router4.x路由管理Pinia2.x状态管理Element Plus2.xUI组件库TypeScript4.x类型系统SCSS-CSS预处理器后端技术栈

技术版本用途Node.js16.x运行环境Express4.xWeb框架MongoDB4.4数据库Mongoose6.xODM工具JWT-身份认证Multer-文件上传前端开发

项目结构

jishun-website/
├── public/<head>

<link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/hero-image.webp" as="image">


<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://api.example.com">
</head>   # 静态资源
├── src/
│   ├── assets/             # 资源文件
│   ├── components/         # 组件
│   │   ├── common/         # 通用组件
│   │   └── business/       # 业务组件
│   ├── directives/         # 自定义指令
│   ├── router/             # 路由配置
│   ├── services/         # API服务
│   ├── stores/             # 状态管理
│   ├── views/<head>

<link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/hero-image.webp" as="image">


<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://api.example.com">
</head># 页面视图
│   ├── App.vue             # 根组件
│   └── main.ts             # 入口文件
├── .env<head>

<link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/hero-image.webp" as="image">


<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://api.example.com">
</head>      # 环境变量
├── index.html<head>

<link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/hero-image.webp" as="image">


<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://api.example.com">
</head># HTML模板
├── package.json            # 项目依赖
├── tsconfig.json         # TypeScript配置
└── vite.config.ts          # Vite配置关键实现

响应式设计

使用媒体查询和弹性布局实现全设备适配:
// 断点变量
$breakpoints: (
'sm': 576px,
'md': 768px,
'lg': 992px,
'xl': 1200px,
'xxl': 1400px
);

// 响应式混合宏
@mixin respond-to($breakpoint) {
$value: map-get($breakpoints, $breakpoint);

@if $value {
    @media (min-width: $value) {
      @content;
    }
} @else {
    @error "Unknown breakpoint: #{$breakpoint}";
}
}

.container {
width: 100%;
padding: 0 15px;

@include respond-to('md') {
    max-width: 720px;
    margin: 0 auto;
}

@include respond-to('lg') {
    max-width: 960px;
}

@include respond-to('xl') {
    max-width: 1140px;
}
}组件化开发

基于Vue 3组合式API实现高复用性组件:
// src/components/common/ImageCarousel.vue


<template>

   
      <transition-group name="fade">
      
          <img :src="image" alt="Carousel image" />
      
      </transition-group>
   
   
    <button@click="prev">
      ❮
    </button>
   
    <button@click="next">
      ❯
    </button>
   
   
      <button
      v-for="(_, index) in images"
      :key="index"
      
      :
      @click="currentIndex = index"
      ></button>
   

</template>状态管理

我使用Pinia进行状态管理:
// src/stores/news.ts
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import type { NewsItem } from '@/types';
import { fetchNewsList, fetchNewsDetail } from '@/services/api';

export const useNewsStore = defineStore('news', () => {
const newsList = ref<NewsItem[]>([]);
const currentNews = ref<NewsItem | null>(null);
const loading = ref(false);
const error = ref<string | null>(null);

const latestNews = computed(() => {
    return [...newsList.value].sort((a, b) =>
      new Date(b.publishDate).getTime() - new Date(a.publishDate).getTime()
    ).slice(0, 5);
});

async function getNewsList() {
    loading.value = true;
    error.value = null;
   
    try {
      newsList.value = await fetchNewsList();
    } catch (err) {
      error.value = err instanceof Error ? err.message : '获取新闻列表失败';
    } finally {
      loading.value = false;
    }
}

async function getNewsDetail(id: string) {
    loading.value = true;
    error.value = null;
   
    try {
      currentNews.value = await fetchNewsDetail(id);
    } catch (err) {
      error.value = err instanceof Error ? err.message : '获取新闻详情失败';
    } finally {
      loading.value = false;
    }
}

return {
    newsList,
    currentNews,
    loading,
    error,
    latestNews,
    getNewsList,
    getNewsDetail
};
});后端开发

项目结构

jishun-website-backend/
├── src/
│   ├── config/             # 配置文件
│   ├── controllers/      # 控制器
│   ├── middlewares/      # 中间件
│   ├── models/             # 数据模型
│   ├── routes/             # 路由定义
│   ├── services/         # 业务逻辑
│   ├── utils/<head>

<link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/hero-image.webp" as="image">


<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://api.example.com">
</head># 工具函数
│   ├── app.ts<head>

<link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/hero-image.webp" as="image">


<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://api.example.com">
</head># 应用配置
│   └── index.ts            # 入口文件
├── uploads/<head>

<link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/hero-image.webp" as="image">


<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://api.example.com">
</head># 上传文件目录
├── .env<head>

<link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/hero-image.webp" as="image">


<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://api.example.com">
</head>      # 环境变量
├── package.json            # 项目依赖
└── tsconfig.json         # TypeScript配置关键实现

数据模型设计

使用Mongoose定义数据模型:
// src/models/News.ts
import mongoose, { Schema, Document } from 'mongoose';

export interface INews extends Document {
title: string;
content: string;
summary: string;
coverImage: string;
publishDate: Date;
author: string;
tags: string[];
isPublished: boolean;
viewCount: number;
createdAt: Date;
updatedAt: Date;
}

const NewsSchema: Schema = new Schema({
title: { type: String, required: true, trim: true },
content: { type: String, required: true },
summary: { type: String, required: true, trim: true },
coverImage: { type: String, required: true },
publishDate: { type: Date, default: Date.now },
author: { type: String, required: true },
tags: [{ type: String, trim: true }],
isPublished: { type: Boolean, default: false },
viewCount: { type: Number, default: 0 }
}, {
timestamps: true
});

// 添加全文搜索索引
NewsSchema.index({
title: 'text',
content: 'text',
summary: 'text',
tags: 'text'
});

export default mongoose.model<INews>('News', NewsSchema);API路由设计

RESTful API设计:
// src/routes/news.ts
import express from 'express';
import {
getAllNews,
getNewsById,
createNews,
updateNews,
deleteNews,
searchNews
} from '../controllers/newsController';
import { authenticate, authorize } from '../middlewares/auth';
import { validateNewsInput } from '../middlewares/validation';

const router = express.Router();

// 公开路由
router.get('/', getAllNews);
router.get('/search', searchNews);
router.get('/:id', getNewsById);

// 需要认证的路由
router.post('/', authenticate, authorize(['admin', 'editor']), validateNewsInput, createNews);
router.put('/:id', authenticate, authorize(['admin', 'editor']), validateNewsInput, updateNews);
router.delete('/:id', authenticate, authorize(['admin']), deleteNews);

export default router;身份验证中间件

JWT认证实现:
// src/middlewares/auth.ts
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import User from '../models/User';

interface DecodedToken {
id: string;
role: string;
}

declare global {
namespace Express {
    interface Request {
      user?: any;
    }
}
}

export const authenticate = async (req: Request, res: Response, next: NextFunction) => {
try {
    const authHeader = req.headers.authorization;
   
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      return res.status(401).json({ message: '未提供认证令牌' });
    }
   
    const token = authHeader.split(' ');
    const decoded = jwt.verify(token, process.env.JWT_SECRET as string) as DecodedToken;
   
    const user = await User.findById(decoded.id).select('-password');
   
    if (!user) {
      return res.status(401).json({ message: '用户不存在' });
    }
   
    req.user = user;
    next();
} catch (error) {
    return res.status(401).json({ message: '无效的认证令牌' });
}
};

export const authorize = (roles: string[]) => {
return (req: Request, res: Response, next: NextFunction) => {
    if (!req.user) {
      return res.status(401).json({ message: '未认证的用户' });
    }
   
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({ message: '没有权限执行此操作' });
    }
   
    next();
};
};性能优化

前端性能优化


[*]代码分割与懒加载
// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router';

const routes = [
{
    path: '/',
    component: () => import('../views/Home.vue')
},
{
    path: '/about',
    component: () => import('../views/About.vue')
},
{
    path: '/news',
    component: () => import('../views/News.vue')
},
{
    path: '/news/:id',
    component: () => import('../views/NewsDetail.vue')
},
];

const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition;
    } else {
      return { top: 0 };
    }
}
});

export default router;
[*]图片优化
// src/directives/lazyload.ts
import { DirectiveBinding } from 'vue';

export default {
mounted(el: HTMLImageElement, binding: DirectiveBinding) {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
      if (entry.isIntersecting) {
          el.src = binding.value;
          observer.unobserve(el);
      }
      });
    });
   
    observer.observe(el);
}
};

// 使用方式
// <img v-lazy="'/path/to/image.jpg'" alt="Lazy loaded image">
[*]资源预加载
<head>

<link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/assets/hero-image.webp" as="image">


<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://api.example.com">
</head>后端性能优化


[*]数据库查询优化
// 添加适当的索引
NewsSchema.index({ publishDate: -1 });
NewsSchema.index({ tags: 1 });

// 使用投影只返回需要的字段
const newsList = await News.find({ isPublished: true })
.select('title summary coverImage publishDate author')
.sort({ publishDate: -1 })
.limit(10);
[*]API响应缓存
// src/middlewares/cache.ts
import { Request, Response, NextFunction } from 'express';
import NodeCache from 'node-cache';

const cache = new NodeCache({ stdTTL: 60 }); // 默认缓存60秒

export const cacheMiddleware = (duration: number = 60) => {
return (req: Request, res: Response, next: NextFunction) => {
    // 只缓存GET请求
    if (req.method !== 'GET') {
      return next();
    }
   
    const key = `__express__${req.originalUrl || req.url}`;
    const cachedBody = cache.get(key);
   
    if (cachedBody) {
      res.send(cachedBody);
      return;
    }
   
    const originalSend = res.send;
   
    res.send = function(body): Response {
      cache.set(key, body, duration);
      return originalSend.call(this, body);
    };
   
    next();
};
};

// 使用方式
// app.use('/api/news', cacheMiddleware(300), newsRoutes);部署流程

宝塔面板部署

1. 安装宝塔面板

# CentOS系统
yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh

# Ubuntu/Debian系统
wget -O install.sh http://download.bt.cn/install/install-ubuntu_6.0.sh && sudo bash install.sh2. 安装必要软件

通过宝塔面板安装:

[*]Nginx 1.20
[*]Node.js 16.x
[*]MongoDB 4.4
[*]PM2管理器
3. 前端部署

# 克隆代码库
git clone https://github.com/your-repo/jishun-website.git /www/wwwroot/jishunkeji.cn/jishun-website

# 安装依赖并构建
cd /www/wwwroot/jishunkeji.cn/jishun-website
npm install
npm run build4. 后端部署

# 克隆代码库
git clone https://github.com/your-repo/jishun-website-backend.git /www/wwwroot/jishunkeji.cn/jishun-website-backend

# 安装依赖并构建
cd /www/wwwroot/jishunkeji.cn/jishun-website-backend
npm install
npm run build

# 创建PM2配置文件
cat > ecosystem.config.js << 'EOL'
module.exports = {
apps: [{
    name: "jishun-backend",
    script: "./dist/index.js",
    instances: 2,
    exec_mode: "cluster",
    env: {
      NODE_ENV: "production",
      PORT: 5001
    },
    max_memory_restart: "300M"
}]
}
EOL

# 启动服务
pm2 start ecosystem.config.js本文档详细记录了企业网站的开发与部署流程,从技术选型到最终上线。通过合理的架构设计、性能优化和部署配置,我成功构建了一个高性能、易维护的企业官网系统,希望能给各位开发者帮助吧
那么下面请欣赏我的报错





感谢浏览,能否给我一个赞和评论呢让我们共同进步


来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除
页: [1]
查看完整版本: 记一次开发