1.前端项目最佳实践 #

1. 工具选择 #

类别 选择
框架 react
JS语言 TypeScript
CSS语言 css-modules+less+postcss
JS编译 babel
模块打包 webpack全家桶
单元测试 jest+enzyme+puppteer+jsdom
路由 react-router
数据流 dva+redux生态
代码风格 eslint+prettier
JS压缩 TerserJS
CSS压缩 cssnano
请求库 umi-request
UI AntDesign+AntDesignPro
国际化 react-intl
hooks库 umi-hooks
静态文档 docz
微前端 qiankun
图表库 antv

2.技术栈选型 #

2.1 固定化 #

2.2 配置化 #

2.2.1 编译态配置 #
2.2.2 运行态配置 #

2.3 约定化 #

1.3 理念 #

2.前端 #

2.1. Ant Design Pro项目初始化 #

2.2 启动项目 #

2.2.1 安装 #

npm config set python "C:/Python38/python.exe"
yarn create umi

2.2.2 目录结构 #

├── config                   # umi 配置,包含路由,构建等配置
├── mock                     # 本地模拟数据
├── public
│   └── favicon.png          # Favicon
├── src
│   ├── assets               # 本地静态资源
│   ├── components           # 业务通用组件
│   ├── e2e                  # 集成测试用例
│   ├── layouts              # 通用布局
│   ├── models               # 全局 dva model
│   ├── pages                # 业务页面入口和常用模板
│   ├── services             # 后台接口服务
│   ├── utils                # 工具库
│   ├── locales              # 国际化资源
│   ├── global.less          # 全局样式
│   └── global.ts            # 全局 JS
├── tests                    # 测试工具
├── README.md
└── package.json

2.2.3 本地开发 #

安装依赖

git init
npm install

启动项目

npm start

2.3 用户注册 #

2.3.1 先配置区块下载地址 #

config.ts

export default {
  plugins,
+ block: {
+   defaultGitUrl: 'https://github.com/ant-design/pro-blocks',
+ },

2.3.2 区块列表 #

umi block list
UserRegister (https://preview.pro.ant.design/https://preview.pro.ant.design/user/register)

UserRegisterResult  (https://preview.pro.ant.design/https://preview.pro.ant.design/user/register/result)
请输入输出安装区块的路径 /user/register-result

2.3.3 user\register\index.tsx #

src\pages\user\register\index.tsx

  onGetCaptcha = () => {
+    const { dispatch, form } = this.props;
+    const mobile = form.getFieldValue('mobile');
+    dispatch({
+      type: 'login/getCaptcha',
+      payload: mobile,
+    })
    let count = 59;
    this.setState({ count });
    this.interval = window.setInterval(() => {
      count -= 1;
      this.setState({ count });
      if (count === 0) {
        clearInterval(this.interval);
      }
    }, 1000);
  };

+   <FormItem>
+            {getFieldDecorator('currentAuthority', {
+              rules: [
+                {
+                  required: true,
+                  message: formatMessage({ id: 'userandregister.currentAuthority.required' }),
+                }
+              ],
+            })(
+              <Select placeholder={formatMessage({ id: 'userandregister.currentAuthority.placeholder' })}>
+                <Option value="user">普通用户</Option>
+                <Option value="admin">管理员</Option>
+              </Select>
+            )}
+  </FormItem>

2.3.4 user\register\locales\zh-CN.ts #

src\pages\user\register\locales\zh-CN.ts

+  'userandregister.currentAuthority.placeholder': '角色',
+  'userandregister.currentAuthority.required': '请输入邮箱地址!',

2.3.5 user\register\service.ts #

src\pages\user\register\service.ts

import request from '@/utils/request';
import { UserRegisterParams } from './index';

export async function fakeRegister(params: UserRegisterParams) {
+  return request('/server/api/register', {
    method: 'POST',
    data: params,
  });
}

2.3.6 src\services\login.ts #

src\services\login.ts

export async function getFakeCaptcha(mobile: string) {
+  return request(`/server/api/login/captcha?mobile=${mobile}`);
}

2.4. 用户登录 #

2.4.1 services\login.ts #

src\services\login.ts

export async function fakeAccountLogin(params: LoginParamsType) {
+  return request('/server/api/login/account', {
    method: 'POST',
    data: params,
  });
}

2.4.2 src\services\user.ts #

src\services\user.ts

export async function queryCurrent(): Promise<any> {
+  return request('/server/api/currentUser');
}

2.5. 权限菜单 #

2.5.1 src\models\login.ts #

src\models\login.ts

if (response.status === 'ok') {
+        if (response.token) {
+          localStorage.setItem('token', response.token);
+        }
        const urlParams = new URL(window.location.href);

2.5.2 src\utils\request.ts #

src\utils\request.ts

const request = extend({
  errorHandler, // 默认错误处理
  credentials: 'include', // 默认请求是否带上cookie
});
+const baseURL = 'http://localhost:4000';
+request.interceptors.request.use((url: any, options: any) => {
+  if (localStorage.getItem('token')) {
+    options.headers.Authorization = 'Bearer ' + localStorage.getItem('token')
+  }
+  if (url.startsWith('/server')) {
+    url = baseURL + url.slice(7);
+  }
+  return { url, options };
+});

export default request;

2.6 docker #

2.6.1 Dockerfile #

FROM nginx
LABEL name="antdesign-front"
LABEL version="1.0"
COPY  ./dist/ /usr/share/nginx/html/
COPY ./antdesign-front.conf /etc/nginx/conf.d/
EXPOSE 80

2.6.2 .dockerignore #

.git
node_modules
package-lock.json
Dockerfile
.dockerignore

2.6.3 antdesign-front.conf #

antdesign-front.conf

server {
    listen       80;
    server_name  47.104.204.74;
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        try_files $uri $uri/ /index.html;
    }
}

3.后端 #

3.1 api.js #

let express = require("express");
let bodyParser = require("body-parser");
let jwt = require('jwt-simple');
let cors = require("cors");
let Models = require('./db');
let sendCode = require('./sms');
let session = require("express-session");
let MongoStore = require('connect-mongo')(session);
let config = process.env.NODE_ENV == 'production' ? require('./config/config.prod') : require('./config/config.dev');
let app = express();
app.use(
    cors({
        origin: config.origin,
        credentials: true,
        allowedHeaders: "Content-Type,Authorization",
        methods: "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS"
    })
);
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(
    session({
        secret: config.secret,
        resave: false,
        saveUninitialized: true,
        store: new MongoStore({
            url: config.dbUrl,
            mongoOptions: {
                useNewUrlParser: true,
                useUnifiedTopology: true
            }
        })
    })
);
app.get('/', async (req, res) => {
    res.json({ code: 0, data: `hello` });
});
app.get('/api/login/captcha', async (req, res) => {
    let mobile = req.query.mobile;
    let captcha = rand();
    req.session.captcha = captcha;
    await sendCode(mobile, captcha);
    res.json({ code: 0, data: `[仅限测试环境验证码]: ${captcha}` });
});
app.post('/api/register', async (req, res) => {
    let user = req.body;
    if (user.captcha != req.session.captcha) {
        return res.json({ code: 1, error: '验证码不正确' });
    }
    let avatarValue = require('crypto').createHash('md5').update(user.mail).digest('hex');
    user.avatar = `https://secure.gravatar.com/avatar/${avatarValue}?s=48`;
    user = await Models.UserModel.create(user);
    res.send({ status: 'ok', currentAuthority: 'user' });
});
app.post('/api/login/account', async (req, res) => {
    let user = req.body;
    let query = {};
    if (user.type == 'account') {
        query.mail = user.userName;
    } else if (user.type == 'mobile') {
        query.mobile = user.mobile;
        if (user.captcha != req.session.captcha) {
            return res.send({
                status: 'error',
                type: user.type,
                currentAuthority: 'guest',
            });
        }
    }
    let dbUser = await Models.UserModel.findOne(query);
    if (dbUser) {
        dbUser.userid = dbUser._id;
        dbUser.name = dbUser.mail;
        let token = jwt.encode(dbUser, config.secret);
        res.send({ status: 'ok', token, type: user.type, currentAuthority: dbUser.currentAuthority });
    } else {
        return res.send({
            status: 'error',
            type: user.type,
            currentAuthority: 'guest',
        });
    }
});

app.get('/api/currentUser', async (req, res) => {
    let authorization = req.headers['authorization'];
    if (authorization) {
        try {
            let user = jwt.decode(authorization.split(' ')[1], config.secret);
            user.userid = user._id;
            user.name = user.mail;
            res.json(user);
        } catch (err) {
            res.status(401).send({});
        }
    } else {
        res.status(401).send({});
    }

});
app.listen(4000, () => {
    console.log('服务器在4000端口启动!');
});

function rand() {
    let min = 1000, max = 9999;
    return Math.floor(Math.random() * (max - min)) + min;
}

3.2 db.js #

db.js

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const ObjectId = Schema.Types.ObjectId;
let configs = process.env.NODE_ENV == 'production' ? require('./config/config.prod') : require('./config/config.dev');
const conn = mongoose.createConnection(configs.dbUrl, { useNewUrlParser: true, useUnifiedTopology: true });
const UserModel = conn.model('User', new Schema({
    userid: { type: String },//邮箱
    name: { type: String },
    mail: { type: String, required: true },//邮箱
    password: { type: String, required: true },//密码
    mobile: { type: String, required: true },//手机号
    avatar: { type: String, required: true },//头像
    currentAuthority: { type: String, required: true }//当前用户的权限
}));

module.exports = {
    UserModel
}

3.3 config.prod.js #

config\config.prod.js

module.exports = {
    secret: 'zhufengcms',
    dbUrl: "mongodb://antdesign-mongo:27017/zhufengcms",
    origin: ["http://47.104.204.74", "http://47.104.204.74:8000"]
}

3.4 config\config.dev.js #

config\config.dev.js

module.exports = {
    secret: 'zhufengcms',
    dbUrl: "mongodb://localhost:27017/zhufengcms",
    origin: ["http://localhost:8000"]
}

3.5 sms.js #

sms.js

const axios = require('axios');
const smsConfig = require('./smsConfig');
//http://docs.ucpaas.com/doku.php?id=%E7%9F%AD%E4%BF%A1:sendsms
module.exports = async (mobile, captcha) => {
    const url = 'https://open.ucpaas.com/ol/sms/sendsms';
    let result = await axios({
        method: 'POST',
        url,
        data: {
            sid: smsConfig.sid,
            token: smsConfig.token,
            appid: smsConfig.appid,
            templateid: smsConfig.templateid,
            param: captcha,
            mobile
        },
        headers: {
            "Content-Type": "application/json;charset=utf-8",
            "Accept": "application/json"
        }
    })
    return result;
}

3.6 smsConfig.js #

smsConfig.js

module.exports = {
    sid: '32548fb951ac0df279db0e6e9a515566',      //开发者账号id
    token: 'aa0309c08920ca38201de69eb3c745b6',    //开发者token
    appid: '16129d504b7c484c9e8f09b4ec929983',    //应用id
    templateid: '387675'                          //短信模板id
}

3.7 package.json #

package.json

  "scripts": {
    "start": "node api.js"
  },

3.8 Dockerfile #

Dockerfile

FROM node
ENV NODE_ENV production
LABEL name="antdesign-server"
LABEL version="1.0"
COPY . /app
WORKDIR /app
RUN npm install
EXPOSE 4000
CMD npm start

3.9 .dockerignore #

.dockerignore

.git
node_modules
package-lock.json
Dockerfile
.dockerignore

4.服务器布署 #

4.1 安装配置服务器 #

#升级所有包同时也升级软件和系统内核
yum update  -y
#只升级所有包,不升级软件和系统内核
yum upgrade 

4.2 docker是什么? #

dockercontainer

4.3 安装docker #

yum install -y yum-utils   device-mapper-persistent-data   lvm2
yum-config-manager \
    --add-repo \
    https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum install -y docker-ce docker-ce-cli containerd.io

4.4 阿里云加速 #

mkdir -p /etc/docker
tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://fwvjnv59.mirror.aliyuncs.com"]
}
EOF
# 重载所有修改过的配置文件
systemctl daemon-reload
systemctl restart docker

4.5 安装git #

yum install git -y

4.6 安装node和npm #

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.1/install.sh | bash

source /root/.bashrc
nvm ls-remote
nvm install v12.13.1
npm i cnpm -g

4.7 创建docker网络 #

docker network create --driver bridge antdesign
docker network connect  antdesign antdesign-mongo
docker network inspect antdesign

4.8 启动mongodb #

4.8.1 启动mongo的docker容器 #

docker pull mongo:latest
docker images
docker run -d --name antdesign-mongo -p 27017:27017 --net antdesign mongo
docker ps
docker exec -it antdesign-mongo bash
mongo

4.8.2 本地安装mongodb #

4.8.2.1 配置MongoDB的yum源 #
vim /etc/yum.repos.d/mongodb-org-4.0.repo
#添加以下内容:
[mongodb-org-4.0]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/4.0/x86_64/
gpgcheck=0
enabled=1
gpgkey=https://www.mongodb.org/static/pgp/server-4.0.asc

#这里可以修改 gpgcheck=0, 省去gpg验证
[root@localhost ~]# yum makecache
4.8.2.2 安装MongoDB #
yum -y install mongodb-org
whereis mongod
vim /etc/mongod.conf
4.8.2.3 启动MongoDB #
systemctl start mongod.service #启动
systemctl stop mongod.service #停止
systemctl status mongod.service #查看状态
4.8.2.4 外网访问 #
systemctl stop firewalld.service #停止firewall
systemctl disable firewalld.service #禁止firewall开机启动

4.9 启动后台服务器 #

git clone https://gitee.com/zhufengpeixun/antdesign-server.git
cd antdesign-server
docker build -t antdesign-server .
docker image ls
docker run  -d --name antdesign-server -p 4000:4000 --net antdesign antdesign-server
curl http://localhost:4000

4.10 启动前台服务 #

git clone https://gitee.com/zhufengpeixun/antdesign-front.git
cd antdesign-front
cnpm install
cnpm run build

docker build -t antdesign-front .
docker run -d --name antdesign-front -p 80:80  --net antdesign antdesign-front