伯乐网基于SSM+Vue的学期项目总结

概述

        因为后半学期太多的实验课,以及繁重的学习压力还有期末项目,博客停更了很久.期末考试后睡了两天,是时候重新拿起笔了.跟同学@qdu_neymar 协作,两个人完成了学期项目.考虑到时间紧迫,学业繁忙的现实压力以及我们的人数不足,采用了增量模型的开发模式,以求在最短的时间内开发出能够运行的项目,避免因为可能的失误导致项目完全无法运行的严重后果.从0开始进行设计,到最终成功部署并进行演示,虽然因为时间匆忙,同时面临期末复习等繁重的压力,项目没有做到尽善尽美,但还是很有成就感的.因此决定将项目的开发细节记录在博客中,并将其开源.这个假期,计划将项目重构,将一些没有能来得及实现的想法弥补上.

        十分感谢@qdu_neymar对本项目的开发做出的贡献,以及@wangsz12在项目开发中给我的无私的,充满价值的建议.

功能模块划分

        本项目是一个招聘网站平台,实现了用户应聘和企业发布岗位等相关的基本业务.具体的业务模块划分参见下图:

伯乐网基于SSM+Vue的学期项目总结_第1张图片

        这些模块的划分都是在项目开发的最开始设计的,在后续的开发中得到了很好的执行,没有打折扣也基本没有偏离,这一点还是很难得的.

        项目解决的核心业务问题是:企业能够在本平台发布岗位,并接收应聘者的简历进行筛选,选择满意的应聘者进行面试,以及最终的录用.用户通过本平台浏览有意向的岗位,并选择岗位进行简历投递,与企业进行交互并等待最终结果.

基础架构

        项目数据库采用MySQL开发,使用了Redis实现简单的缓存.使用MyBatis作为ORM框架,Spring Boot+SpringMVC+Spring进行后端开发.前端采用了Vue框架以及Axios开发,实现了前后端的分离.下图可以生动展示项目的层次结构.这不是一篇推销或者自吹自擂的文章,我会倾向于挑选我们项目开发中遇到的问题以及不足来进行介绍,便于在后面的开发中作为自我反思的材料.伯乐网基于SSM+Vue的学期项目总结_第2张图片

数据库设计

        由于本项目的后端完全由我一人开发,并且急于赶进度,从设计数据字典到简历MySQL数据库仅用了2天,所以数据库的结构设计仍然是比较幼稚的,不够企业级.所幸的是本项目的业务逻辑并不复杂,没有暴露太多的问题.下图为本项目数据库的结构:

伯乐网基于SSM+Vue的学期项目总结_第3张图片

        可以看出,这次的数据库设计存在以下问题:1.不够企业级.仍然是数据库初学者的一个实体一个表单的简单思路,没有充分利用OOA的继承等机制,并且没有采用身份表或权限表等方式进行权限管理.存在很多重复的字段,浪费了空间,比如Admin,User,Company都存在的email和name等字段.2.字段不够完善.考虑到开发的重点在于业务功能,数据库中的字段秉承的原则是足够展示即可,没有设计更详细的字段.这当然不影响项目开发的质量,但是会给项目带来廉价感和小作坊感.3.主键设计出现了失误.在后期的开发中,发现项目初期开发的Message表单,采用的联合主键不能符合业务需求的问题.最初设计的联合主键是(用户id,企业邮箱,消息类型),但后来发现这样不足以唯一地标识一条消息,导致调试时频繁地出现了主键重复的错误.于是不得不从数据库层面开始为Message补上了自增主键,消耗了大量的时间用来弥补这个错误.

使用OSS进行文件管理

       由于我并不喜欢虚拟路径等方式将文件存储在本地,我最初试图采用的方式是将文件直接以Blob的形式存储在MySQL中.并且在最初的开发中,利用Java中byte[]数组可以与MySQL的Blob类型直接可以对接的特点,项目顺利地推进了下去.但是随着我决定改为采用前后端分离的开发模式,这样的文件存储方式变得不那么可靠了.首先,如何采用JSON将数据库中的Blob类型文件传递到前端,再生成为文件?其次,从数据库到前端,这样做的效率还能得到保证吗?所以我不得不考虑其它的方法.

         阿里云OSS(Object Storage Service)是一种非常优秀的文件存储方式,可以允许用户使用ali-oss的API来实现文件的在线存储.在本项目中,对用户的头像,图片,以及简历的存储全部使用了OSS,不仅避免了服务器对文件上传的带宽限制,也直接避开了Blob类型的处理难点。因为采用了OSS,在后端数据库中存储文件时,只需要存储文件的URL即可,在前端可以很容易地将URL转化为具体的文件。下图为我的OSS控制台

伯乐网基于SSM+Vue的学期项目总结_第4张图片

        下面为Vue前端框架中,用来实现OSS上传文件的工具类:

'use strict'
import { dateFormat } from '@/utils/utils'
var OSS = require('ali-oss')
const url = ''

export default {
    /**
     * 创建随机字符串
     * @param num
     * @returns {string}
     */
    randomString(num) {
        const chars = [
            '0', '1', '2', '3', '4', '5', '6', '7', '8',
            '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
            'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
            'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
        ]
        let res = ''
        for (let i = 0; i < num; i++) {
            var id = Math.ceil(Math.random() * 35)
            res += chars[id]
        }
        return res
    },

    /**
     * 创建oss客户端对象
     * @returns {*}
     */
    createOssClient() {
        return new Promise((resolve, reject) => {
            const client = new OSS({
                region: 'oss-cn-qingdao',
                accessKeyId: 'LTAI5tGPHhu18Ns6THrqDn97',
                accessKeySecret: 'jwN6RPl8QfzdYt56uBQ7UbEe0NMlDy',
                bucket: 'aaronwu-first-bucket',
                secure: true // 上传链接返回支持https
            })
            resolve(client)
        })
    },
    /**
     * 文件上传
     */
    ossUploadFile(option) {
        const file = option.file
        const self = this
        // var url = '';
        return new Promise((resolve, reject) => {
            const date = dateFormat(new Date(), 'yyyyMMdd') // 当前时间
            const dateTime = dateFormat(new Date(), 'yyyyMMddhhmmss') // 当前时间
            const randomStr = self.randomString(4) //  4位随机字符串
            const extensionName = file.name.substr(file.name.indexOf('.')) // 文件扩展名
            const fileName = 'image/' + date + '/' + dateTime + '_' + randomStr + extensionName // 文件名字(相对于根目录的路径 + 文件名)
            // 执行上传
            self.createOssClient().then(client => {
                // 异步上传,返回数据
                resolve({
                    fileName: file.name,
                    fileUrl: fileName
                })
                // 上传处理
                // 分片上传文件
                client
                    .multipartUpload(fileName, file, {
                        progress: function(p) {
                            const e = {}
                            e.percent = Math.floor(p * 100)
                            // console.log('Progress: ' + p)
                            option.onProgress(e)
                        }
                    })
                    .then(
                        val => {
                            window.url = val
                            console.info('woc', url)
                            if (val.res.statusCode === 200) {
                                option.onSuccess(val)
                                return val
                            } else {
                                option.onError('上传失败')
                            }
                        },
                        err => {
                            option.onError('上传失败')
                            reject(err)
                        }
                    )
            })
        })
    }
}

 邮箱验证功能

        项目实现了利用邮箱验证进行密码修改的功能,但是在这个方面没有进一步深入探索。

        伯乐网基于SSM+Vue的学期项目总结_第5张图片

伯乐网基于SSM+Vue的学期项目总结_第6张图片

         可以看到,这条验证码邮件是纯文本,并不美观,可以设法通过H5的方式设计出较为美观和友好的邮件页面,来带给用户更好的感受。此外,邮箱验证不仅可以用来验证用户身份,在业务流程中,也可以多多使用这个功能。

        下面为后端实现发送邮件的工具类,邮箱和授权码已经隐去,不同的邮箱客户端,开启服务的方式不同,这里不多赘述.

/**
 * 实现邮箱验证的工具类
 *
 * @author AaronWu
 */
public class SendMail {
    /*发件人的邮箱账号如:[email protected]*/
    public static String sendEmailAccount = "";
    /**发件人的邮箱的授权码(自己在邮箱服务器中开启并设置)*/
    public static String sendEmailPassword = "";

    /**发件人邮箱的SMTP服务器地址,如:smtp.163.com,我使用的是qq邮箱所以如下:*/
    public static String sendEmailSMTPHost = "smtp.qq.com";
    /**收件人的邮箱账号*/
    public static String receiveMailAccount = "";


    /**
     * 把发送邮件封装为函数,参数为收件人的邮箱账号和要发送的内容
     * @param receiveMailAccount 收邮件的人的邮件
     * @param mailContent 邮件内容
     */
    public void sendMail(String receiveMailAccount, String mailContent) {
        // 创建用于连接邮件服务器的参数配置
        Properties props = new Properties();
        // 设置使用SMTP协议
        props.setProperty("mail.transport.protocol", "smtp");
        // 设置发件人的SMTP服务器地址
        props.setProperty("mail.smtp.host", sendEmailSMTPHost);
        // 设置需要验证
        props.setProperty("mail.smtp.auth", "true");

        // 根据配置创建会话对象, 用于和邮件服务器交互
        Session session = Session.getInstance(props);
        // 设置debug模式,便于查看发送过程所产生的日志
        session.setDebug(true);

        try {
            // 创建一封邮件
            MimeMessage message = createMimeMessage(session, sendEmailAccount, receiveMailAccount, mailContent);

            // 根据 Session 获取邮件传输对象
            Transport transport = session.getTransport();

            transport.connect(sendEmailAccount, sendEmailPassword);

            // 发送邮件, 发到所有的收件地址, 通过message.getAllRecipients() 可以获取到在创建邮件对象时添加的所有收件人
            transport.sendMessage(message, message.getAllRecipients());

            // 关闭连接
            transport.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     *
     * @param session 和服务器交互的会话
     * @param sendMail 发件人邮箱
     * @param receiveMail 收件人邮箱
     * @throws Exception
     */
    public static MimeMessage createMimeMessage(Session session, String sendMail, String receiveMail,
                                                String mailContent) throws Exception {
        // 创建一封邮件
        MimeMessage message = new MimeMessage(session);

        // 设置发件人姓名和编码格式
        message.setFrom(new InternetAddress(sendMail, "伯乐网-邮箱验证", "UTF-8"));

        // 收件人
        message.setRecipient(MimeMessage.RecipientType.TO, new InternetAddress(receiveMail, "尊敬的用户", "UTF-8"));

        // 设置邮件主题
        message.setSubject("找回密码提醒", "UTF-8");

        // 设置邮件正文
        message.setContent(mailContent, "text/html;charset=UTF-8");

        // 设置发件时间
        message.setSentDate(new Date());

        // 保存设置
        message.saveChanges();

        return message;
    }
}

 安全问题

        平台的安全问题一度给我带来了巨大的困惑.最开始我尝试过使用Redis+Token鉴权的方式,但是出现了跨域失败的问题.然后我使用了Spring Security,但存在多次跨域的问题.只能说第一次跨域,没什么经验.所以最终只能采用了Vue Router的导航卫士,实现纯前端的权限控制.例如在没有登录的情况下,通过修改url强行进入用户端页面,就会出现下图的403错误页面:

伯乐网基于SSM+Vue的学期项目总结_第7张图片

         原理是在用户登录时,系统会生成用户信息的localStorage存储在客户端浏览器,当用户退出登录时删除,从而做到对未登录的用户权限进行限制.然而,在我看来,这种存储在客户端的鉴权方式是始终存在隐患的,对于这个最终解决方案我并不满意.

        下面为Vue Router 导航卫士的源码:

// 导入路由表
import router from "./router";

/**
 * 只要哈希值更改立即执行此方法,内部是一个三参回调
 * to:终点哈希
 * from:起点哈希
 * next:执行过程
 */
router.beforeEach((to, from, next) => {
  /** 拿取用户保存在浏览器中的信息*/

  const user = localStorage.getItem("uEmail");
  const admin = localStorage.getItem("aEmail");
  const company = localStorage.getItem("cEmail");
  /*如果目标路路径是登录或者注册模块,不进行拦截*/
  if(['/#/', '/#/hr_login', '/#/user_login', '/#/hr_register', '/#/user_register',"/#/hr/home"].includes(to.path)){
    console.warn("include------------")
    console.warn(to.path)
  }
  /*登录账户为空但是想访问用户页面,直接拦截到首页去*/
  else if(to.path.startsWith("/user/")&&!user){
    console.warn("user------------")
    console.warn(to.path)
    next("/403")
  }
  else if(to.path.startsWith("/hr/")&&!company){
    console.warn("hr------------")
    console.warn(to.path)
    next("/403")
  }
  else if(to.path.startsWith("/admin/")&&!admin){
    console.warn("admin------------")
    console.warn(to.path)
    next("/403");
  }else{
    next();
    console.warn("else------------")
    console.warn(to.path)
  }
});

        非常容易理解,无非就是将除了首页和登录页面以外的任何页面跳转添加一条判定,加入localStorage不存在就拒绝访问跳转到403页面。注意,导航卫士的编写过程中非常容易出现跳转的死循环,也就是从一个页面跳转到另一个页面,由于编写逻辑的问题又跳转回去,进入无休止的相互跳转。这种问题的表现就是页面显示异常,在浏览器控制台中出现类似于爆栈的错误。注意编写时理清各个页面的逻辑关系。

项目页面概览

首页

伯乐网基于SSM+Vue的学期项目总结_第8张图片

 伯乐网基于SSM+Vue的学期项目总结_第9张图片

 登录页面

用户首页

伯乐网基于SSM+Vue的学期项目总结_第10张图片

审批简历

伯乐网基于SSM+Vue的学期项目总结_第11张图片

浏览岗位

伯乐网基于SSM+Vue的学期项目总结_第12张图片

数据统计(Apache Echarts) 

伯乐网基于SSM+Vue的学期项目总结_第13张图片

项目代码仓库开源

后端仓库:CODING | 一站式软件研发管理平台https://e.coding.net/hulianwangxiangmuqdu/2021NIITProjectOfAutumnTerm/2021AutumnTermNIITProject.git前端仓库:

CODING | 一站式软件研发管理平台https://e.coding.net/hulianwangxiangmuqdu/2021NIITProjectOfAutumnTerm/2021AutumnTermNIITProject_foreBack.git

总结

        这次开发经历是我比较用心,同时也比较顺利的一次开发经历。虽然项目的质量不够完美,但在客观的学习压力下,在有限的时间内,我认为还是非常满意的。同时也多亏了与我一起开发的@qdu_neymar足够的用心与专注,以及令人惊叹的前端开发天赋,保证了项目开发的速度与质量。本项目前端的布局,网站Logo的设计以及各种样式的设计,全部来源于他的创意,而不是来自网络上的东拼西凑。

        能够将学习过的知识,尽最大的努力转化为有价值的结晶,是一件多么让人快乐的事情。

你可能感兴趣的:(项目经历,vue.js,前端,javascript,ssm)