Results show

github

My moment – server making the address

My moment making address

My moment – the manage making address

articles

Talk about graduation project series — project introduction

Talk about graduation design series-system implementation

preface

In the last article, I mainly introduced the project, and made a general introduction to the system analysis and system design. Then the following article will introduce the implementation of the system, mainly to select some of the main modules or can be taken out to share with you. All right, let’s get to the point

MongoDB

On the server side, Express framework is used, MongoDB is used for database, and the database is operated by Mongoose module. Here is mainly to make an introduction to MongoDB, of course, if you understand the officer, go directly to ~~~~~~

Before the start of the project to ensure that the computer is installed mongoDB, download me, image tool Robo 3T point me, download good specific configuration also ask Baidu or Google bar, this article does not introduce ha. Note: after installing mongoDB, open the mongod server in the lib directory

MongoDB is a database based on distributed file storage, which is an open source product between relational database and non-relational database. It is the most functional non-relational database, and also the most similar to relational database. But unlike relational databases, MongoDB has no concept of tables and rows. Instead, it is a collection oriented, document oriented database. The Document is a key-value pair, using BSON(Binary Serialized Document Format). BSON is a storage Format similar to JSON in Binary form, and BSON has the extension to represent data types, so it supports very rich data. Two of MongoDB’s most important data types are embedded documents and arrays, and arrays can be embedded with other documents so that a single record can represent very complex relationships.

Mongoose is an object model tool for simple operation of MongoDB in node.js asynchronous environment. It can extract any information from the database and use object-oriented methods to read and write data, so that it is very convenient to operate MongoDB database. There are three very important concepts in Mongoose, namely, Schema, Model and Entity.

  1. Schema: A database model skeleton that is stored as a file and does not have the operation capability of a database. The procedure for creating a Schema is the same as the procedure for creating a table in a relational database.
//Schema
const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const UserSchema = new Schema({
    token: String,
    is_banned: {type: Boolean, default: false}, // Whether to forbid speechenable: { type: Boolean, default: true}, // whether the user is valid is_actived: {type: Boolean, default: false}, //email: String, password: String, email: String, //email unique code: String, email_time: {type: Date},
    phone: {type: String},
    description: { type: String, default: "This man is lazy and has nothing left..." },
    avatar: { type: String, default: "http://p89inamdb.bkt.clouddn.com/default_avatar.png" },
    bg_url: { type: String, default: "http://p89inamdb.bkt.clouddn.com/FkagpurBWZjB98lDrpSrCL8zeaTU"},
    ip: String,
    ip_location: { type: Object },
    agent: { type: String}, // user UA last_login_time: {type: Date },
    .....
});
Copy the code
  1. Model: A Model generated by a Schema publication, a database action object with abstract properties and behavior
Const User = mongoose. Model (const User = mongoose."User", UserSchema); Module.exports = User; // Exports = users; // exports = exports; // exports = exports; // exports = exports; // exports = exports; // exports = exports; // exports = exports; // exports = exports; // exports = exports;Copy the code
  1. Entity: An Entity created by Model whose operations also affect the database, but which is less capable of manipulating the database than Model
Const newUser = new UserModel({//UserModel is the exported User email: req.body.email, code: getCode(), email_time: Date.now() });Copy the code

The fact that Mongoose is okay with populate is that it’s easy for him to populate with another set. As follows, the user collection can be associated with the article collection and the user collection itself, depending on the nature of the embedded document, so that it can embed sub-documents that have nested sub-documents, so that the data it returns is extremely rich.

const user = await UserModel.findOne({_id: req.query._id, is_actived: true}, {password: 0}).populate({
                path: 'image_article',
                model: 'ImageArticle',
                populate: {
                    path: 'author',
                    model: 'User'
                }
            }).populate({
                path: 'collection_film_article',
                model: 'FilmArticle',
            }).populate({
                path: 'following_user',
                model: 'User',
            }).populate({
                path: 'follower_user',
                model: 'User',
            }).exec();
Copy the code

The server is mainly used to operate the database, add, delete, change and check the database (CRUD). The interfaces in the project, the various methods of Mongoose are not going to be described in detail here, but you can check out the Mongoose documentation.

User identity authentication implementation

introduce

The user identity authentication mechanism of the system uses JSON Web Token (JWT), which is a lightweight authentication specification and also used for interface authentication. As we know, HTTP is a stateless protocol, which means that each request is independent. When a user provides a user name and password to authenticate our application, the user needs to authenticate the application again on the next request, because according to HTTP, We do not know which user issued the request, the system uses the token authentication mechanism. Access-control-allow-origin: * This token must be passed to the server on every request. It should be stored in the header of the request. In addition, the server must support the CORS policy.

There are many ways to authenticate users. The most common ones are cookies and sessions. So what’s the difference between them? Here are two articles that are quite comprehensive.

  • Understand cookies, sessions, and tokens in HTTP short connections
  • Session and Token in HTTP short connections

The difference between token and session is that it is different from the traditional session authentication mechanism and does not require the server to retain user authentication information or session information. Once the system is large, it will adopt the cluster of machines to do load balancing, which requires multiple machines. As the session is stored in the server, it is necessary to consider which server the user logs in on, which is a great burden.

So the question is, you have such a small system, why don’t you use the traditional session mechanism? Ha ~ because my previous projects usually use session for login, never use token, I want to try to enter the pit ~~ ha ~ ha ~

Implementation approach

The main implementation ideas of JWT are as follows:

  1. The token created upon successful login is stored in the database and returned to the client.

  2. Each subsequent request from the client should carry token, add Authorization in the request header, and add token.

  3. The server verifies the validity of the token and returns the 200 status code after the token expires and the 401 status code after the token expires

As shown below:

The module jSONWebToken is mainly used in Node to create JWT. Please refer to the jSONWebToken document for the usage of JSONWebToken. The middleware createToken in the project to create the token is as follows

/** * createToken.js */ const jwt = require('jsonwebtoken'); // add jsonWebToken const secret = 'I am the key' Module.exports = function(user_id){const token = jwt.sign({user_id: JWT){module.exports = function(user_id){const token = jwt.sign({user_id: JWT); User_id}, secret, {// key expiresIn: '24h' // set the expiration time to 24h. The expiration time obtained when decode the token is: token creation time + set value}); return token; };Copy the code

Token that is returned Similar eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. EyJ1c2VyX2lkIjoiYWRtaW4iLCJpYXQiOjE1MzQ2ODQwNzAsImV4cCI6MTUzNDc3MDQ3MH0. Y3kaglqW9 Fpe1YxF_uF7zwTV224W4W97MArU0aI0JgM. We look closely at the string, divided into three sections separated by a “.”. Now let’s base64 decode the first two segments as follows:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9  ===> {"alg":"HS256"."typ":"JWT"Name} alg is encryption algorithm, typ is type eyJ1c2VyX2lkIjoiYWRtaW4iLCJpYXQiOjE1MzQ2ODQwNzAsImV4cCI6MTUzNDc3MDQ3MH0 = = = > {"user_id":"admin"."iat": 1534684070,"exp":1534770470} where name is the content we stored, iAT created timestamp, exp expiration timestamp. Y3kaglqW9Fpe1YxF_uF7zwTV224W4W97MArU0aI0JgM = = = > the last paragraph is by the preceding two paragraphs, string HS256 encrypted. So any of the previous field changes will result in an encrypted string mismatch.Copy the code

After obtaining the token according to the user id, we need to return the token to the client, and the client saves the token in the localStorage. Each request from the client should bring the token, add Authorization in the request header, and add token. The server verifies the token validity. So how do we verify the validity of the token? Therefore, we need the middleware checkToken to check the validity of token.

/** * checkToken */ const jwt = require('jsonwebtoken'); Const secret = 'module.exports = async (req, res, next) => {const authorization = req.get(' authorization '); if (! authorization) { res.status(401).end(); Return} const token = authorization.split(' ')[1]; try { let tokenContent = await jwt.verify(token, secret); // If the token expires or validation fails, an error is thrown next(); } catch (err) {console.log(err) res.status(401).end(); // Token expired or authentication failed return 401 status code}}Copy the code

So now we just need to add checkToken middleware on the interface that requires user authentication before operating data, as follows:

// Update user information router.post('/updateUserInfo', checkToken, user.updateUserinfo) // If the checkToken test is unsuccessful, it returns the 401 status code and does nothing to User.updateUserinfo. To process user.updateUserInfoCopy the code

How can we ensure that each request can add Authorization and token in the request header, which requires Axios request interception and response interception, because after the server returns the 401 status code, it should execute the logout operation to know the local token storage, and the specific code is as follows:

/ / request interceptor
instance.interceptors.request.use(
    config= > {
        // Check whether tokens exist locally before each request is sent, and send the request to the server
        if(localStorage.getItem('token')) {if (config.url.indexOf('upload-z0.qiniup.com/putb64') > - 1){
                config.headers.Authorization = config.headers['UpToken'];  // Add the seven niuyun upload token
            }
            else {
                config.headers.Authorization = `token ${localStorage.getItem('token')}`.replace(/(^\")|(\"$)/g.' ');  // Add the system interface token}}console.log('config',config)
        return config;
    },
    err => {
        console.log('err',err)
        return Promise.reject(err); });/ / response interceptors
instance.interceptors.response.use(
    response= > {
        return response;
    },
    error => { // By default, everything except 2XX is wrong
        if(error.response){
            switch(error.response.status){
                case 401:
                    console.log(error.response)
                    store.dispatch('ADMIN_LOGINOUT'); // The token may have expired. Clear it
                    router.replace({ // Jump to the login page
                        path: '/login'.query: { redirect: '/dashboard' } // Use path as a parameter to switch to this route after successful login}); }}return Promise.reject(error.response); });Copy the code

The “if else” is because the pictures, audio and video of the system are placed in Qiliuyun. When qiliuyun uploads base64 pictures, the token is placed in the request header. Normal picture uploads are not placed in the request header, so the token is separated here.

Seven ox cloud access

The system of pictures, audio and video is placed in seven niuyun, so the need to access seven niuyun. If you want to upload a base64 image or audio file, you can upload the Content Type of the image or audio file. {‘ content-type ‘:’multipart/form-data’}domain = domain=’https://upload-z0.qiniup.com’, headers:{‘ content-type ‘: domain=’https://upload-z0.qiniup.com’ :’application/octet-stream’}domain = domain=’https://upload-z0.qiniup.com/putb64/-1′ Base64 is placed in request header Authorization as mentioned above, and normally in form-data. The server obtains the Qiuniuyun upload token through the interface request, and the client obtains the Qiuniuyun token and brings the token with it through different schemes.

  1. Base64 upload:headers:{'Content-Type':'application/octet-stream'}domain='https://upload-z0.qiniup.com/putb64/-1'The token is placed in the request headerAuthorizationIn the.
  2. Normal upload of pictures and audio and video:headers: {'Content-Type':'multipart/form-data'}anddomain='https://upload-z0.qiniup.com'Token inform-dataIn the.

The server creates tokens through the module Qiniu. The server code is as follows:

@class QN */ const qiniu = require(const qiniu = require('qiniu'// import qiniu const config = require('.. /config'Class QN {/** * Creates an instance of QN. * @param {string} accessKey * @param {string} secretKey @param {string} bucket - user name * @param {string} origin - user name */ constructor (optional) bucket, Ak = accessKey this.sk = secretKey this.bucket = bucket this.origin = origin} /** * get the file upload certificate * @param {number} time - 7 Niuyun certificate expiration time, in seconds. If the value is empty, the default value is 7200. */ upToken (time) {const MAC = new qiniu.auth.digest.mac (this.ak, this.sk) const options = {scope: */ upToken (time) {const MAC = new qiniu.auth.digest.mac (this.ak, this.sk) const options = {scope: this.bucket, expires: time || 7200 } const putPolicy = new qiniu.rs.PutPolicy(options) const uploadToken = putPolicy.uploadToken(mac)return uploadToken
    }
}

exports.QN = QN;

exports.upToken = () => {
    returnnew QN(config.qiniu.accessKey, config.qiniu.secretKey, config.qiniu.bucket, Config.qiniu.origin).uptoken () // Each call creates a token}Copy the code
Const {upToken} = require(const {upToken} = require('.. /utils/qiniu')

app.get('/api/uploadToken', (req, res, next) => {
        const token = upToken()
        res.send({
            status: 1,
            message: 'Uploading credentials obtained successfully',
            upToken: token,
        })
    })
Copy the code

As the Content Type and domain are different for normal image, audio and video uploading and Base64 image uploading, the location of token request is different. Therefore, to distinguish between them, the client call upload code is as follows:

UploadToken uploadFile(formdata, domain=) uploadToken uploadFile(formdata, domain='https://upload-z0.qiniup.com',config={headers:{'Content-Type':'multipart/form-data'}}){
        console.log(domain)
        console.log(formdata)
        returnUploadToken uploadToken uploadToken uploadToken uploadToken uploadToken uploadToken uploadToken uploadToken uploadToken uploadToken uploadToken uploadToken token, domain ='https://upload-z0.qiniup.com/putb64/-1', config = {
        headers: {
            'Content-Type': 'application/octet-stream',
        },
    }){
        const pic = base64.split(', ') [1]; config.headers['UpToken'] = `UpToken ${token}`
        return instance.post(domain, pic, config)
    },

Copy the code
functionUpload (Vue, data, callbackSuccess callbackFail) {/ / upload access token after processing the Vue. Prototype. Axios. GetUploadToken (). Then (res = > {if (typeof data === 'string') {/ / if it is base64 const token = res. Data. UpToken Vue. Prototype. Axios. UploadBase64File (data, token). Then (res = > {if (res.status === 200){
                    callbackSuccess && callbackSuccess({
                        data: res.data,
                        result_url: `http://p89inamdb.bkt.clouddn.com/${res.data.key}`
                    })
                }
            }).catch((error) => {
                callbackFail && callbackFail({
                    error
                })
            })
        }
        else if (data instanceof FormData){  //如果是FormData
            data.append('token', res.data.upToken)
            data.append('key', `moment${Date.now()}${Math.floor(Math.random() * 100)}`)
            Vue.prototype.axios.uploadFile(data).then(res => {
                if (res.status === 200){
                    callbackSuccess && callbackSuccess({
                        data: res.data,
                        result_url: `http://p89inamdb.bkt.clouddn.com/${res.data.key}`
                    })
                }
            }).catch((error) => {
                callbackFail && callbackFail({
                    error
                })
            })
        }
        else{const formData = new formData () // Create formData formData.append ()'token', res.data.upToken)
            formdata.append('file', data.file || data)
            formdata.append('key', `moment${Date.now()}${Math.floor(Math.random() * 100)}.${data.file.type.split('/')[1]}') // Upload the file to console. Log ('formdata',formdata)
            Vue.prototype.axios.uploadFile(formdata).then(res => {
                console.log('res',res)
                if (res.status === 200){
                    callbackSuccess && callbackSuccess({
                        data: res.data,
                        result_url: `http://p89inamdb.bkt.clouddn.com/${res.data.key}Catch ((error) => {console.log(error) callbackFail && callbackFail({error})})}})}export default upload
Copy the code

Route permission module

The background management of the system is for co-authors and administrators, involving two roles, so it is necessary to do permission management. Different permissions correspond to different routes, and the menu in the sidebar also needs to be generated asynchronously according to different permissions. Different from the past, the server directly returns to the routing table, which is dynamically generated by the front end. Next, the idea of login and permission verification is introduced:

  1. Login: After the user fills in the account and password, the server verifies that the password is correct. After the authentication is successful, the server returns a token. After receiving the token, the front-end pulls a getAdminInfo interface based on the token to obtain the user’s details (such as user permission, user name, etc.).

  2. Permission authentication: Obtains the user’s role through the token, dynamically calculates the route to the user’s permission based on the user’s role, performs global front-guard on vue-router’s beforeEach, and dynamically mounts these routes through router.addRoutes.

The code is a little bit too much, here directly put the flow chart ha ~~

Recently, I happened to be working on a middle and background project in the company. In the middle and background project of the company, the routing table is generated by the server and the front end carries out direct rendering. After all, the whole set of business of the company is relatively mature. However, we are wondering if the front end can maintain the routing table, so that the project does not have to iterate, and the front end has to ask the server brother to configure the routing and permissions every time the page is added, of course, the premise may be small project.

Account module

Account module is the most basic module in the business, which undertakes all account related functions of the whole system. The system realizes the functions of user registration, user login, password modification and password retrieval.

The account module of the system uses the mail service, and the registration of ordinary users uses the mail service to send the verification code, as well as the modification of the password and other operations have adopted the mail service. Node.js mainly uses Nodemailer, Nodemailer is a simple and easy to use Node.js mail sending component, its use can touch me touch me touch me, through this module to send mail. You might ask, why not use SMS? Ha ~ because SMS service charges money, ha ha ha

/* */ const nodemailer = require('nodemailer');
const smtpTransport = require('nodemailer-smtp-transport');
const config = require('.. /config')

const transporter = nodemailer.createTransport(smtpTransport({
    host: 'smtp.qq.com',
    secure: true, port: 465, // SMTP port auth: {user: config.emmail. account, pass: config.emmail. password //let clientIsValid = false;
const verifyClient = () => {
    transporter.verify((error, success) => {
        if (error) {
            clientIsValid = false;
            console.warn('Mail client failed to initiate connection, will retry in one hour');
            setTimeout(verifyClient, 1000 * 60 * 60);
        } else {
            clientIsValid = true;
            console.log('Mail client initializes connection successful, ready to send mail'); }}); }; verifyClient(); const sendMail = mailOptions => {if(! clientIsValid) { console.warn('Mail client delivery rejected due to uninitialized success');
        return false;
    }
    mailOptions.from = '"ShineTomorrow" <[email protected]>'
    transporter.sendMail(mailOptions, (error, info) => {
        if (error) return console.warn('Message sending failed', error);
        console.log('Email sent successfully', info.messageId, info.response);
    });
};

exports.sendMail = sendMail;
Copy the code

After filling in the email address, Nodemailer will send a verification code containing the validity period to complete the registration. After filling in the verification code, nickname and password, Secure Hash Algorithm is used to encrypt the password for security. Account login is performed using the account or email number plus password, and the JSON Web Token (JWT) authentication mechanism mentioned above is used to achieve the mapping between users and user login status data.

Real-time message push

When users are followed, comments are replied and liked by others, and other social operations, after data storage is completed, the server should timely push messages to users to remind them. IO encapsulates Websocket. If websocket is not supported, it also provides demoted AJAX polling. With complete functions and elegant design, it is the only means to develop real-time two-way communication.

With socket. IO, every time a user opens a page, a connection is established between the page and the server. A connection page can be matched on the server side by the ID attribute of the connected socket. Therefore, there is a one-to-many relationship between the user ID and the socket ID. That is, a user may open multiple pages after login. While socket. IO does not provide the function of sending messages to a user individually from the server, nor does it provide the function of pushing messages to all pages opened by a user. But socket. IO provides the concept of a room, or group. When establishing a Websocket, the client can choose to join a room. If the room does not exist, it automatically creates a new room. Otherwise, the server can push messages to all clients in a room.

According to this feature, the design takes the user ID as the name of room. When a user opens the page to establish a connection, he or she will choose to join the room with his or her user ID as the name. In this way, in the room with the user ID as the name, all the connections are made by the page opened by the user. To push a message to a user, you can directly send a message to a room named after the user’s ID, which will push to all the pages the user opens.

IO/vue/socket. IO/vue/socket. IO/vue/socket. IO/vue/socket. IO

/* * app.js */ const server = require('http').createServer(app);
const io = require('socket.io')(server); global.io = io; // set the IO value globally, because other modules use IO. On ('connection'.function (socket) {
    // setTimeout(()=>{
    //     socket.emit('nodeEvent', { hello: 'world' });
    // }, 5000)
    socket.on('login_success', (data) => {// Accept the login_success event triggered by the client // use user_id as the room number socket.join(data.user_id); console.log('login_success',data);
    });
});
io.on('disconnect'.function (socket) {
    socket.emit('user disconnected');
});


server.listen(config.port, () => {
    console.log(`The server is running at http://localhost:${config.port}`);
});
Copy the code
/ * * * / / / a business module, for example, a comment articles increase IO. In (newMusicArticle. Author. User_id. _id) emit ('receive_message', newMessage); / / inform client receive_message real-time event sendMail ({/ / send emails to: newMusicArticle. Author. User_id. Email, the subject: ` Moment | you have unread message oh ~ `, text: ` la la la, I'm selling small expert ~ ~ 🤔 `, HTML: emailTemplate.com ment (sender, newMusicArticle, content,!!!!! req.body.reply_to_id) })Copy the code

Customer service code:

<script>
    export default {
        name: 'App'.data () {
            return {
            }
        },
        sockets:{
            connect(){}, receive_message(val){// Receives events triggered by the server and updates data on the client in real timeif (val){
                    console.log('Real-time Server communication', val)
                    this.$notify(val.content)
                    console.log('this method was fired by the socket server. eg: io.emit("customEmit", data)')
                }
            }
        },
        mixins: [mixin],
        mounted() {if(!!!!! JSON.parse(window.localStorage.getItem('user_info'))){
                this.$socket.emit('login_success', {/ / login_success event notification service end, incoming id user_id: JSON. Parse (window. LocalStorage. The getItem ('user_info'))._id
                })
            }
        },
    }
</script>
Copy the code

Comments on the module

The comment module is designed to provide users with some operations about comments under the article of WebApp on mobile terminal. The system has realized the comment on the article, the comment like function, the hot comment top and the comment reply function. There are various security issues with comments, such as XSS attacks (Cross Site Scripting) and sensitive words. XSS module is used to prevent XSS attacks, and text- Censor module is used to filter sensitive words.

Some think

  1. Interface data problem

This problem is often encountered in development, the interface data problem. Sometimes the data returned by the server is not what we want, and the front end has to take another step of processing the data.

For example, if a field returned by the server is null or the data structure returned by the server is too deep, the front end needs to constantly determine whether the data structure really does return the correct thing, instead of null or undefined~

This is how our front-end handles filtering:

<div class="author"> article / {{(musicArticleInfo. Author && musicArticleInfo. Author. User_id)? musicArticleInfo.author.user_id.username :'That's my name.'}}
</div>
Copy the code

This leads to a thought:

Further encapsulation of the data will inevitably lead to performance issues, and we will always have to worry about data return. If applied to the company’s business, how should we deal with it?

  1. Page performance optimization and SEO issues

The first screen rendering problem has always been a pain point for single-page applications, so what other methods do we have to optimize besides the usual performance optimization? Although this project is for mobile users, there may be no SEO problem. If it is made into PC, SEO is a must for applications like articles. The solution to the problem mentioned above is the emergence of Node, which is often referred to as the Node middle tier. Of course, there is no Node middle tier in this project, but it is directly used as a back-end language to handle the database.

Since most corporate backends are either PHP or Java, Node is not used directly as a backend language, and if it is used at all, it is usually in the form of an intermediate tier.

To solve the first problem: we can do interface forwarding in the middle layer, and do data processing in the process of forwarding. You don’t have to worry about the data coming back.

The solution to the second problem: with the Node middle layer, we can leave the first screen rendering to NodeJS and the second screen rendering to the previous browser rendering. With Node middle layer, the new architecture is as follows:

Functions of front and rear ends:

conclusion

I have graduated for a period of time, and I am writing this article to review it. I am average level, forgive me. The realization of this product, a person to carry, in which acted as a variety of roles, to have a little product thinking, to have a little design ideas, to be able to database design, back-end development, quite tedious. The most difficult point of the individual feels or database design, database to start design is complete, otherwise to tian tian at the back of a lot more, will be very mess is a mess, of course, this is based on the product to be very clear, at first the product may be a vague definition, in my heart think about is, and began to make ~ ~ cause behind in database design is not very satisfactory. Due to time constraints, some small modules in the current product have not been completed, but most of the functional structure has been completed, it is a molding product, of course, it is not tested hahaha, if there is a test, then hahaha you know ~~~.

The road ahead is long, I will search up and down


After the

Thank you ~ ~