0, preface

Umi3 and ANTD PRO5 are used to realize the whole stack in the background management system from zero

0-1, involving technology stack

Front-end: TS, React, React Hooks, umi3, ANTD-Pro5 Back-end: Express, mongodb, JWT **

0-2. Functions implemented

  • Back-end user authentication
  • Front-end rights Management
  • User Password Encryption
  • Encapsulate a set of general popover form components to achieve new, modify, detail functions
  • User login registration (for the first time, the backend needs to add a user login information)
  • The back-end uses expressJWT to implement interface authentication and add whitelists
  • Back-end logging function
  • The backend encapsulation method uniformly handles the returned information
  • List of filter, sort, delete, delete in batches
  • Implement new, modify, view details

1. Initialize the front-end project

Umi official website for coding

yarn create umi myapp
npm i 
npm run dev
Copy the code

1. Set the proxy in Config

  dev: {
    '/api/': {
      target: "http://localhost:3000".changeOrigin: true.pathRewrite: { A '^': ' '}},},Copy the code

2, login

Example Change the SRC /service/login.ts interface to/API /user/login

export async function fakeAccountLogin(params: LoginParamsType) {
  return request<API.LoginStateType>('/api/user/login', {
    method: 'POST'.data: params,
  });
}
Copy the code
Token storage pages/user/login/index. The TSX
localStorage.setItem('token' , msg.token)
Copy the code

The use of token services/user. Ts

export async function queryCurrent() {
  return request<API.CurrentUser>('/api/currentUser'.headers: {
      Authorization :  'Bearer ' + `The ${localStorage.getItem('token')}`}}Copy the code

Each request carries the token SRC /app.tsx


export const request: RequestConfig = {
  errorHandler,
  headers: { 
    Authorization :  'Bearer ' + `The ${localStorage.getItem('token')}`}};Copy the code

Exit RightContent/AvatarDropdown TSX

 localStorage.removeItem('token')
Copy the code

3. Pro5 Reference document

Procomponents. Ant. The design/components /…

4, to achieve a user management

5. List page

pages/ListTableList/index.tsx

import { PlusOutlined } from '@ant-design/icons';
import { Button, Divider, message, Avatar } from 'antd';
import React, { useState, useRef } from 'react';
import { PageContainer, FooterToolbar } from '@ant-design/pro-layout';
import ProTable, { ProColumns, ActionType } from '@ant-design/pro-table';
import HandleForm from './components/HandleForm';
import { TableListItem } from './data.d';
import { queryRule, updateRule, addRule, removeRule } from './service';
import moment from 'moment';
/** * Operation commit *@param fields* /
const handleSubmit = async(_id? : string, fields? : TableListItem) => {let title = _id ? 'change' : 'new';
  const hide = message.loading(` is${title}`);
  try {
    if (_id) {
      awaitupdateRule({ _id, ... fields, }); }else {
      awaitaddRule({ ... fields }); } hide(); message.success(`${title}Successful `);
    return true;
  } catch (error) {
    hide();
    message.error(`${title}Failure `);
    return false; }};/** * Delete node *@param selectedRows* /
const handleRemove = async (selectedRows: string[], _id: string) => {
  // console.log(selectedRows,_id,'selectedRows>>>>')
  const hide = message.loading('Deleting');
  // return
  try {
    await removeRule({
      _id: _id ? _id : selectedRows,
    });
    hide();
    message.success('Deleted successfully');
    return true;
  } catch (error) {
    hide();
    message.error('Delete failed');
    return false; }};const TableList: React.FC<{}> = () = > {
  const [modalVisible, handleModalVisible] = useState<boolean>(false);
  const [currentInfo, handleSaveCurrentInfo] = useState<TableListItem | null> (null);
  const [isDetail, setDetail] = useState<boolean>(false);
  const actionRef = useRef<ActionType>();
  const [selectedRowsState, setSelectedRows] = useState<any>([]);
  const columns: ProColumns<TableListItem>[] = [
    {
      title: 'Username'.dataIndex: 'username'}, {title: 'password'.dataIndex: 'password'.hideInDescriptions: true.// The details page is not displayed
      hideInTable: true}, {title: The 'role'.dataIndex: 'access'.search: false.filters: [{text: 'Ordinary user'.value: 'user' },
        { text: 'Administrator'.value: 'admin'},].valueEnum: {
        user: { text: 'Ordinary user' },
        admin: { text: 'Administrator'}},}, {title: '_id'.dataIndex: '_id'.sorter: true.hideInForm: true.search: false}, {title: 'avatar'.dataIndex: 'avatar'.search: false.hideInForm: true.render: (dom, entity) = > {
        return <Avatar src={entity.avatar} alt="" />; }, {},title: 'email'.dataIndex: 'email'}, {title: 'Update Time'.dataIndex: 'updatedAt'.sorter: true.hideInForm: true.search: false.renderText: (val: string) = > {
        if(! val)return ' ';
        return moment(val).fromNow(); // Absolute time is converted to relative time}, {},title: 'Creation time'.dataIndex: 'createdAt'.sorter: true.hideInForm: true.search: false.valueType: 'dateTime'}, {title: 'operation'.dataIndex: 'option'.valueType: 'option'.render: (_, record) = > (
        <>
          <a
            href="javascript:;"
            onClick={()= >{ handleModalVisible(true), handleSaveCurrentInfo(record); }} > modification</a>
          <Divider type="vertical" />
          <a
            href="javascript:;"
            onClick={()= >{ handleModalVisible(true), handleSaveCurrentInfo(record), setDetail(true); }} > for details</a>
          <Divider type="vertical" />
          <a
            href="javascript:;"
            onClick={async() = >{ await handleRemove([], record._id as 'string'); / / refresh actionRef. Current? .reloadAndRest? . (); }} > delete</a>
        </>),},];return (
    <PageContainer>
      <ProTable<TableListItem>ActionRef ={actionRef} rowKey="_id" search={{labelWidth: 120,}} toolBarRender={() => [<Button type="primary" onClick={()= > handleModalVisible(true)}>
            <PlusOutlined />new</Button>,
        ]}
        request={(params, sorter, filter) => queryRule({ ...params, sorter, filter })}
        columns={columns}
        form={{
          submitter: false,
        }}
        pagination={{ defaultPageSize: 5 }}
        rowSelection={{
          onChange: (selected, selectedRows) => {
            setSelectedRows(selected);
          },
        }}
      />
      <HandleForm
        onCancel={()= >{ handleModalVisible(false), handleSaveCurrentInfo({}), setDetail(false); }} modalVisible={modalVisible} values={currentInfo} isDetail={isDetail} onSubmit={async (values) => { const success = await handleSubmit(currentInfo? ._id, values); if (success) { handleModalVisible(false); if (actionRef.current) { actionRef.current.reload(); }}}} ></HandleForm>{selectedRowsState? .length > 0 && (<FooterToolbar
          extra={
            <div>Has chosen<a style={{ fontWeight: 600}} >{selectedRowsState.length}</a></div>
          }
        >
          <Button
            onClick={async() = >{ await handleRemove(selectedRowsState, ''); setSelectedRows([]); actionRef.current? .reloadAndRest? . (); }} > Batch delete</Button>
        </FooterToolbar>
      )}
    </PageContainer>
  );
};

export default TableList;
Copy the code

pages/ListTableList/data.d.ts

exportinterface TableListItem { _id? : string; username? : string; password? : string; avatar? : string; access? : string; email? : string; }export interface TableListPagination {
  total: number;
  pageSize: number;
  current: number;
}

export interface TableListData {
  list: TableListItem[];
  pagination: Partial<TableListPagination>;
}

exportinterface TableListParams { _id? : string; username? : string; password? : string; avatar? : string; access? : string; email? : string; pageSize? : number; currentPage? : number; filter? : { [key: string]: any[] }; sorter? : { [key: string]: any }; }Copy the code

pages/ListTableList/service.ts

import { request } from 'umi';
import { TableListParams } from './data.d';

export async function queryRule(params? : TableListParams) {
  return request('/api/user/account', {
    params,
  });
}

export async function removeRule(params: { _id: string|string[] }) {
  return request('/api/user/account', {
    method: 'DELETE'.data: params
  });
}

export async function addRule(params: TableListParams) {
  return request('/api/user/account', {
    method: 'POST'.data: {
      ...params
    },
  });
}

export async function updateRule(params: TableListParams) {
  return request(`/api/user/account? _id=${params._id}`, {
    method: 'PUT'.data: {
      ...params
    },
  });
}
Copy the code

6. Popover form components

pages/ListTableList/components/HandleForm.d.ts
import React from 'react';
import { Modal } from 'antd';
import ProForm, { ProFormText, ProFormRadio } from '@ant-design/pro-form';
import { TableListItem } from '.. /data';

export interface FormValueType extendsPartial<TableListItem> { username? : string; password? : string; type? : string; time? : string; frequency? : string; }export interface CreateFormProps {
  onCancel: (flag? : boolean, formVals? : FormValueType) = > void;
  onSubmit: (values? : FormValueType) = > Promise<void>;
  modalVisible: boolean;
  values: Partial<TableListItem> | null; isDetail? : boolean; }const CreateForm: React.FC<CreateFormProps> = ({ isDetail, onCancel, modalVisible, values, onSubmit, }) = > {
  if(values? .password) values.password ='* * * * * *';
  return (
    <Modal
      destroyOnClose
      title={! values? 'New User': isDetail? 'User Details':'Update user '}visible={modalVisible}
      onCancel={()= > onCancel()}
      footer={null}
      width={840}
    >
      <ProForm
        initialValues={values as TableListItem}
        onFinish={async (values: Partial<TableListItem>) = > {! isDetail && onSubmit(values); }} {... (isDetail && { submitter: false })} ><ProFormText
          rules={[{ required: true.message:'Please enter a user name! '}}]disabled={isDetail}
          label="Username"
          name="username"
        />
        <ProFormText
          rules={[{ required: true.message:'Please enter your password! '}}]disabled={isDetail}
          label="Password"
          name="password"
        />
        <ProFormText
          rules={[{ required: true.message:'Please enter email! '}}]disabled={isDetail}
          label="Email"
          name="email"
        />
        <ProFormRadio.Group
          name="access"
          disabled={isDetail}
          label="Role"
          rules={[{ required: true.message:'Please select a role! '}}]options={[
            {
              label:'Administrator ',value: 'admin'}, {label:'users',value: 'user'}},] / >
        <ProFormText
          // rules={[{ required: true.message:'Please fill in your avatar! '}}]disabled={isDetail}
          label="Avatar"
          name="avatar"
        />
      </ProForm>
    </Modal>
  );
};

export default CreateForm;
Copy the code

Login and user information Services

pages/services/login.ts

import { request } from 'umi';

export interface LoginParamsType {
  username: string;
  password: string;
  mobile: string;
  captcha: string;
  type: string;
}

export async function fakeAccountLogin(params: LoginParamsType) {
  return request<API.LoginStateType>('/api/user/login', {
    method: 'POST'.data: params
  });
}

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

export async function outLogin() {
  return request('/api/login/outLogin');
}
Copy the code

pages/services/user.ts

import { request } from 'umi';

export async function query() {
  return request<API.CurrentUser[]>('/api/users');
}

export async function queryCurrent() {
  return request<API.CurrentUser>('/api/currentUser', {
    headers: { 
      Authorization :  'Bearer ' + `The ${localStorage.getItem('token')}`}}); }export async function queryNotices() :Promise<any> {
  return request<{ data: API.NoticeIconData[] }>('/api/notices');
}
Copy the code

2. Initialize the server project

See link for an example of implementing a linked database ˘

0. Realized functions

  • Handle POST requests (body-parser)
  • Processing across domains (CORS)
  • Cookies (cookies – parser)
  • Printing logs (Morgan)
  • Set the token information and resolve the token information (JsonWebToken)
  • Global validation JWT (Express-JWT)
  • Implement login return token, global authentication token
  • Implement registration password encryption (MD5)
npm i express mongoose body-parser jsonwebtoken http-status-codes -S
Copy the code

1. Entry file app.js

var createError = require("http-errors");
let express = require("express");
let bodyParser = require("body-parser");
let app = express();
var cors = require("cors");
var logger = require("morgan");
var cookieParser = require("cookie-parser");
const expressJWT = require("express-jwt");
const config = require("./config");
let { userRouter } = require("./routes/index");

// Process the POST request
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

// Prints logs
app.use(logger("dev"));
// Handle cross-domain
app.use(cors());

/ / log
app.use(logger("dev"));

/ / using cookies
app.use(cookieParser());

// Verify the token to obtain the Authorization token in headers before route loading and after static resources
app.use(
  expressJWT({
    secret: config.Secret,
    algorithms: ["HS256"].credentialsRequired: true,
  }).unless({
    path: ["/api/user/register"."/api/login"."/api/user/account"].// The list is empty, except for the address in the list, all other URLS need to be verified})); app.use("/api", userRouter);

// catch 404 and forward to error handler
app.use(function (req, res, next) {
  next(createError(404));
});

// error handler
app.use(function (err, req, res, next) {
  if (err.name === "UnauthorizedError") {
    // This needs to be handled according to your own business logic
    res.status(401).send({ code: -1.msg: "Token verification failed" });
  } else {
    // set locals, only providing error in development
    res.locals.message = err.message;
    res.locals.error = req.app.get("env") = = ="development" ? err : {};
    // render the error page
    res.status(err.status || 500);
    res.render("error"); }}); app.listen(3000.function () {
  console.log("Service started at 3000");
});
Copy the code

2. Start the mongodb database

// No /data/db needs to be created
cd /usr/local/mongodb/bin
sudo ./mongod  -dbpath /data/db/
Copy the code

3. Configure config.js

module.exports={
    dbUrl:'mongodb://localhost:27017/pro5App'.screct:'pro5'.EXPIRESD:60*60*24
}
Copy the code

4. Routes

routes/index.js

const { userRouter } = require("./user");
module.exports = {
  userRouter,
};
Copy the code

routers/user.js

let express = require("express");
let userRouter = express.Router();
const { UserModel } = require(".. /model/index");
let jwt = require("jsonwebtoken");
let config = require(".. /config");
const { SuccessModel, ErrorModel } = require(".. /utils/resModule");

// User registration interface
userRouter.post("/user/register".async function (req, res) {
  await UserModel.create(req.body);
  res.json(new SuccessModel("Registration successful"));
});

// Login interface
userRouter.post("/user/login".async function (req, res) {
  let { username, password } = req.body;
  let query = { username, password };
  try {
    let result = await UserModel.findOne(query);
    let resultJSON = result.toJSON();
    let token = jwt.sign(resultJSON, config.Secret,{expiresIn:config.EXPIRESD});
    res.json(new SuccessModel(token));
  } catch (error) {
    res.json(new ErrorModel("Login failed")); }});// Interface for querying current user information
userRouter.get("/user/currentInfo".async function (req, res) {
  let authorization = req.headers["authorization"];
  let token = authorization.split("") [1];
  let result = jwt.verify(token, config.Secret);
  res.json(new SuccessModel(result, "Registration successful"));
});

// Query information about all users
userRouter.get("/user/account".async function (req, res) {
  try {
    let result = await UserModel.find();
    res.json(new SuccessModel(result, "Query successful"));
  } catch (error) {
    res.json(newErrorModel(error)); }});// Delete user information
userRouter.delete("/user/account".async function (req, res) {
  let hasRes = await UserModel.findOne(req.body);
  if (hasRes) {
    let { deletedCount } = await UserModel.remove(req.body);
    if (deletedCount) {
      res.json(new SuccessModel("Deleted successfully")); }}else {
    res.json(new ErrorModel("Delete failed")); }});// Modify user information
userRouter.put("/user/account".async function (req, res) {
  let { nModified } = await UserModel.update(
    req.query,
    { $set: req.body },
    { multi: true});if (nModified) {
    res.json(new SuccessModel("Modified successfully"));
  } else {
    res.json(new ErrorModel("Modification failed")); }});module.exports = {
  userRouter,
};
Copy the code

5

model/index.js

const mongoose = require("mongoose");
const config = require(".. /config");
const { UserSchema } = require("./user");
/ / register
let connection = mongoose.createConnection(config.dbUrl, {
  useNewUrlParser: true.useUnifiedTopology: true});// Connect to the database
const UserModel = connection.model("User", UserSchema);
module.exports = {
  UserModel,
};
Copy the code

model/user.js

let mongoose = require("mongoose");
const Schema = mongoose.Schema;
// Define the data structure
let UserSchema = new Schema({
  username: { type: String },
  email: { type: String },
  password: { type: String },
  avatar: { type: String },
  access: { type: String}});module.exports = {
    UserSchema
};
Copy the code

Utility function utils

utils/resModule.js

class BaseModel {
    constructor(data,message) {
        if(typeof data === 'string') {
            this.message = data
            data = null
            message = null
        }
        if(data){
            this.data = data
        }
        if(message){
            this.message = message
        }
    }
}

class SuccessModel extends BaseModel {
    constructor(data,message){
        super(data,message)
        this.errno = 0
        this.code = 200
        this.type = "success"}}class ErrorModel extends BaseModel {
    constructor(data,message,code){
        super(data,message)
        this.errno = -1
        this.type = "error"
        this.code = code
    }
}

module.exports = {
    SuccessModel,
    ErrorModel
}
Copy the code

3. Project address and reference link

The front end address: rockshang.coding.net/public/reac… The server address: rockshang.coding.net/public/reac… Umi website pro5 reference documentation: procomponents. Ant. The design/components /…