springboot + vue + elementUI项目实战——简洁清新的员工管理系统(一)

springboot + vue + elementUI + mybatis + redis 清新的员工管理系统

在这里插入图片描述

前言

  从这期,项目从需求分析开始,一步步实现一个老经典的清新的员工管理系统,适合有一定 ssm、springboot、mybatis、vue+elementUI 基础的训练项目,虽然没有很复杂的业务,但也要会这些技术栈的基础才行。看下运行效果就开始了,,
适合有一定 ssm、springboot、mybatis、vue+elementUI 基础的训练项目
登录和注册页面,是在源码之家随便找的一个来用:
地址:https://www.mycodes.net/192/10205.htm
在这里插入图片描述


1、项目准备

  • 需求分析

    • 模块
    • 功能
  • 库表设计

    • 数据库、ER图
  • 详细设计

    • 流程图、伪代码等方式
  • 编码环节

    • a.环境准备,技术选型
    • b.正式进入编码环节
  • 测试

  • 部署上线(发布到生产环境)


2、技术选型

  • 前端:elementUI + vue + axios
  • 后端:SpringBoot + mysql + mybatis + Redis

3、需求分析

用户模块:

​ a. 用户登录

​ b. 用户注册

​ c. 验证码实现

​ d. 欢迎xxx用户展示

​ e. 安全退出

员工模块:

​ f. 员工列表分页展示

​ g. 员工添加

​ h. 员工删除

​ i. 员工修改

​ j. 员工列表加入redis缓存实现


4、库表设计

1、分析系统中有哪些表?

2、分析表与表之间关系?

3、分析表中字段?

用户表: e_user
在这里插入图片描述
员工表: e_emp
在这里插入图片描述
创建表:

create table t_user(
	id 				int(6)  auto_increment comment 'id主键',
    username  		varchar(60) not null   comment '用户名',
    realname  		varchar(60) 		   comment '真实名',
    password  		varchar(50) not null   comment '用户密码',
    gender    		tinyint(1)     comment '用户性别 (1 男 0 女)',
    registertime	datetime    comment '用户注册时间',
    state  			tinyint(1)  comment '用户状态(是否启用,1 启用 0 未启用)',
    primary key(id)
)engine=innodb default charset=utf8
create table t_emp(
	id 				int(6)  auto_increment comment 'id主键',
    `name`  		varchar(60) not null   comment '员工姓名',
    photopath  		varchar(1000) 		   comment '员工头像',
    salary  		decimal(20) 		   comment '员工工资',
    age    		    int(3)     comment '员工年龄',
    primary key(id)
)engine=innodb default charset=utf8

库表入库 l_emp
详细设计

ER图、系统模块结构图

在这里插入图片描述

5、编码环节

a.环境搭建

  • springboot + mybatis + mysql 引入前端页面
项目名:employee

项目包结构:
	src/main/java
		com.ling.xxx
				.util
				.pojo
				.dao
				.service
				.controller
				...
	src/main/resource
		application.yaml
		application-dev.yaml
		application-prod.yaml
		mapper/*.xml	存放mybatis的mapper配置文件
		sql         	用来存放项目中数据库文件
		static      	用来存放静态资源
		
项目编码:UTF-8
  • 引入Druid数据源和 commons-fileupload 依赖
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.20</version>
</dependency>

<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>  <!--文件上传下载-->
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>

  • 配置yaml
server:
  servlet:
    context-path: /ems_vue
  port: 8081

spring:
  application:
    name: employee
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/l_emp?characterEncoding=UTF-8
    username: root
    password: 123456

# 集成mybatis
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.ling.pojo

# 配置日志
logging:
  level:
    root: info
  • 导入静态资源(登录和注册页面,懒得自己写了)到static目录下
    在这里插入图片描述
  • 启动项目,此时可通过 http://localhost:8081/ems_vue/login.html 访问到登录页面

6、模块化编码

Message 消息提示:https://element.eleme.cn/#/zh-CN/component/message#fang-fa

a. 用户模块

验证码

1、前端引入 vue 和 axios

<!-- vue 和 axios  -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<!-- import JavaScript -->
<!--包含 this.$message.success("登录成功!!!"); 等js操作-->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<!--挂载vue-->
<script>
    // Vue.prototype.$http = axios;
    // axios.defaults.baseURI = "http://localhost:8081/ems_vue";
    // 自定义配置:新建一个 axios 实例
    const $http = axios.create({
        baseURL: 'http://localhost:8081/ems_vue',
        timeout: 1000,
        headers: {'X-Custom-Header': 'foobar'}
    });
    let app = new Vue({
        el: "#app",
        data: {
            
        },
        created() {
        },
        methods: {
            
        }
    })
</script>

========================================================================

<!-- Element Ui -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<script src="https://unpkg.com/element-ui/lib/index.js"></script>

验证码工具类:

验证码工具类有很多,我这个也是在网上随便找的一个拿来用,忘记保留出处了~

package com.ling.utils;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

public class VerifyUtil {
    // 验证码字符集
    private static final char[] 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',
            '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'};
    // 字符数量
    private static final int SIZE = 4;
    // 干扰线数量
    private static final int LINES = 3;
    // 宽度
    private static final int WIDTH = 80;
    // 高度
    private static final int HEIGHT = 35;
    // 字体大小
    private static final int FONT_SIZE = 25;

    /**
     * 生成随机验证码及图片
     */
    public static Map<String, Object> createImageCode() {
        StringBuffer sb = new StringBuffer();
        // 1.创建空白图片
        BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
        // 2.获取图片画笔
        Graphics graphic = image.getGraphics();
        // 3.设置画笔颜色
        graphic.setColor(Color.LIGHT_GRAY);
        // 4.绘制矩形背景
        graphic.fillRect(0, 0, WIDTH, HEIGHT);
        // 5.画随机字符
        Random ran = new Random();
        for (int i = 0; i < SIZE; i++) {
            // 取随机字符索引
            int n = ran.nextInt(CHARS.length);
            // 设置随机颜色
            graphic.setColor(getRandomColor());
            // 设置字体大小
            graphic.setFont(new Font(null, Font.BOLD + Font.ITALIC, FONT_SIZE));
            // 画字符
            graphic.drawString(CHARS[n] + "", i * WIDTH / SIZE, HEIGHT * 2 / 3);
            // 记录字符
            sb.append(CHARS[n]);
        }
        // 6.画干扰线
        for (int i = 0; i < LINES; i++) {
            // 设置随机颜色
            graphic.setColor(getRandomColor());
            // 随机画线
            graphic.drawLine(ran.nextInt(WIDTH), ran.nextInt(HEIGHT), ran.nextInt(WIDTH), ran.nextInt(HEIGHT));
        }
        // 7.返回验证码和图片
        Map<String, Object> map = new HashMap<>();
        //验证码
        map.put("code", sb.toString());
        //图片
        map.put("image", image);
        return map;
    }

    /**
     * 随机取色
     */
    public static Color getRandomColor() {
        Random ran = new Random();
        return new Color(ran.nextInt(256), ran.nextInt(256), ran.nextInt(256));
    }
}

编写一个验证码接口: getImage

@RestController
@CrossOrigin   //允许跨域
public class UserController {

    //生成验证码图片==》响应一个 base64 字符串
    @GetMapping("/getImage")
    public String getImageCode(HttpServletRequest request) throws IOException {
        //1.使用工具类生成验证码(包括image和code)
        Map<String, Object> imageCode = VerifyUtil.createImageCode();
        String code = (String) imageCode.get("code");

        //2.将验证码放入servletContext作用域(前后端分离是没有session概念的)
        request.getServletContext().setAttribute("code", code);

        BufferedImage image = (BufferedImage) imageCode.get("image");
        //3.将图片转化为base64
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        //5.响应给浏览器  ImageIO :工具类
        ImageIO.write(image, "png", outputStream);
        String encode = Base64Utils.encodeToString(outputStream.toByteArray());

        return encode;
    }

}

前端请求接口并接收数据

let app = new Vue({
    el: "#app",
    data: {
        imgCode: "",
    },
    created() {
        this.getCodeImage()
    },
    methods: {
        //获取验证码
        async getCodeImage() {
            const {data: res} = await $http.get("/getImage");
            // console.log("解构前:" + res.data);
            console.log("后端返回的base64数据:", res);
            this.imgCode = "data:image/png;base64," + res;
        }
    }
})

用户注册

编写user实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String id;//String 类型的api相当多,方便处理
    private String name;
    private String realname;
    private String password;
    //前端和sql语句传的是true或false,存入数据库的是1或0
    private boolean gender; //用户性别 (1 男 0 女)
    private Date registertime;
    //数据库一个表中有一个tinyint类型的字段,值为0或者1,如果取出来的话,0会变成false,1会变成true。
    private boolean state;  //用户状态(是否启用,1 启用 0 未启用)
}

注册业务实现流程

================1、UserDao=========================
@Mapper
@Repository
public interface UserDao {
    
    // 注册前先判断该用户名是否已存在
    // (该方法不需要再新建一个service接口,直接在UserServiceImpl的注册方法中调用该方法即可)
    // 用户登录可复用该方法
    public User findByUserName(String Username);
    //用户注册
    public void saveUser(User user);
}

===============2、UserMapper.xml=========================
<!-- namespace=绑定一个对应的Dao接口 -->
<mapper namespace="com.ling.dao.UserDao">
    
    <!--注册前先判断该用户名是否已存在,
    登录可复用该方法(根据前端传入的user.name,若结果不为空在比对密码是否正确)-->
    -->
    <select id="findByUserName" resultType="User">
        select * from t_user where username=#{username};
    </select>

    <!-- saveUser对应dao层的方法名,parameterType:参数类型 -->
    <insert id="saveUser" parameterType="User">
        insert into t_user (username,password,gender,registertime,state)
        values (#{username},#{password},#{gender},#{registertime},#{state});
    </insert>

</mapper>

================3、UserService=========================
public interface UserService {
    //用户注册
    public void saveUser(User user);
}

===============4、UserServiceImpl=========================
@Service
@Transactional
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    //用户注册
    @Override
    public void saveUser(User user) {
        //0.根据前端输入的用户名,判断用户是否已存在
        User byUserName = userDao.findByUserName(user.getUsername());
        if (byUserName==null){
            //1.设置用户注册时间
            user.setRegistertime(new Date());
            //2.生成用户状态
            user.setState(false);
            //3.调用dao
            userDao.saveUser(user);
        }else {
            throw new RuntimeException("该用户已存在!");
        }
        
    }
    
}

================5、UserController=========================
@RestController
@CrossOrigin   //允许跨域
public class UserController {

    ......
    
    @Autowired
    UserService userService;

    //用户注册
    // 这里必须加 @RequestBody 注解才能拿到前端传递的user对象,
    // 因为前端的 axios 传递数据使用的是 json 格式,这里使用@RequestBody将json字符串转换为对象
    // code:前端输入的验证码,request:为了拿到之前servletContext中存放的验证码yCode对象
    @PostMapping("/register")
    public Map<String,Object> register(@RequestBody User user,String code,HttpServletRequest request){
        System.out.println(user);
        System.out.println(code);

        Map<String, Object> map = new HashMap<>();
        // try/catch捕捉异常 快捷键 CTRL+ALT+T
        try {
            String yCode = (String) request.getServletContext().getAttribute("yCode");
            if (yCode.equalsIgnoreCase(code)){
                //调用业务方法
                userService.saveUser(user);
                map.put("state",true);
                map.put("msg","提示:注册成功!");
            }else {
                throw new RuntimeException("验证码错误");
            }
        } catch (Exception e) {
            e.printStackTrace();
            map.put("state",false);
            // map.put("msg","提示:注册失败!");
            // e.getMessage() 可拿到对应的异常信息,
            // 包括serviceimpl中抛出的异常(该用户已存在!)
            map.put("msg","提示:"+e.getMessage());
        }
        return map;
    }

}

前端异步请求并传值/取值

注意:整个过程中 axios 异步通信是不用提交表单的,所以表单的 name 属性没有任何意义,可直接删除,通过vue的 v-model=“user.username” 双向绑定来获取输入的数据

所以提交按钮类型也不需要 submit 而 是用 button 绑定一个点击事件即可

<!DOCTYPE html>
<html lang="en">
<head>
    ......
    <!--和 element-ui/lib/index.js 一起可使用 this.$message.success()-->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
</head>
<body>
......
    <!-- Form Panel    -->
    <div class="col-lg-6 bg-white">
        <div class="form d-flex align-items-center">
            <div class="content">
                <div class="form-group">
                    <input id="register-username" v-model="user.username" class="input-material" type="text"
                           placeholder="请输入用户名/姓名">
                    <div class="invalid-feedback">
                        用户名必须在2~10位之间
                    </div>
                </div>
                <div class="form-group">
                    <input id="register-password" v-model="user.password" class="input-material"
                           type="password"
                           placeholder="请输入密码">
                    <div class="invalid-feedback">
                        密码必须在6~10位之间
                    </div>
                </div>
                <div class="form-group">
                    <input id="register-passwords" class="input-material" type="password"
                           placeholder="确认密码">
                    <div class="invalid-feedback">
                        两次密码必须相同 且在6~10位之间
                    </div>
                </div>
                <div class="form-group">
                    &emsp;&emsp;<input type="radio" v-model="user.gender" value="true">
                    &emsp;&emsp;<input type="radio" v-model="user.gender" value="false">
                </div>
                <div class="form-group" style="display: flex">
                    <img :src="imgCode" id="img-vcode"
                         @click="getCodeImage" alt="验证码" style="padding: 0 10px">
       <input class="input-material" v-model="code" type="text"
                           placeholder="输入验证码">
                </div>
     <div class="form-group">
       <button id="regbtn" type="button" @click="register" class="btn btn-primary">
                 注册
           </button>
      </div>
             <small>已有账号?</small><a href="login.html" class="signup">&nbsp;登录</a>
            </div>
.......

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
            
<!--<script src="https://unpkg.com/vue/dist/vue.js"></script>-->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
            
<!-- 和element-ui/lib/theme-chalk/index.css 一起可使用 this.$message.success("登录成功!"); 等操作-->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<!--挂载vue-->
<script>
    // Vue.prototype.$http = axios;
    // axios.defaults.baseURI = "http://localhost:8081/ems_vue";
    // 自定义配置:新建一个 axios 实例
    const $http = axios.create({
        baseURL: 'http://localhost:8081/ems_vue',
        timeout: 1000,
        headers: {'X-Custom-Header': 'foobar'}
    });
    let app = new Vue({
        el: "#app",
        data: {
            imgCode: "",
            user: {
                // value="true",传值默认为男,前端传true或false,存入数据库的是1或0
                gender: true
            },
            code: ""
        },
        created() {
            this.getCodeImage()
        },
        methods: {
            //获取验证码
            async getCodeImage() {
                const {data: res} = await $http.get("/getImage");
                // console.log("解构前:" + res.data);
                console.log("后端返回的base64数据:", res);
                this.imgCode = "data:image/png;base64," + res;
            },
            // 注册
            async register() {
                const {data: res} = await $http.post("/register?code=" + this.code, this.user);
                console.log("后端返回的数据:", res);
                if (res.state) {
                    // this.$message.success("注册成功!");
                    alert(res.msg + "点击确定跳转至登录页面");
                    location.href = './login.html';
                } else {
                    this.$message.error("注册失败");
                    // 该用户已存在!或 验证码错误
                    alert(res.msg)
                }
            }
        }
    })
</script>
......
</body>
</html>

用户登录

======service层接口=======
//用户登录
public User login(User user);

======service层实现类=======
//登录
@Override
public User login(User user) {
    //1.根据前端输入的用户名,判断用户是否已存在
    User userByName = userDao.findByUserName(user.getUsername());
    //或 if (!ObjectUtils.isEmpty(userByName))  对象不为空
    if (userByName!=null){
        //用户存在,比对密码
        if (userByName.getPassword().equals(user.getPassword())){
            return userByName;
        }else {
            throw new RuntimeException("密码不正确!");
        }
    }else {
        throw new RuntimeException("用户不存在!");
    }
}

===============Controller层=======================
//登录
@PostMapping("/login")
    public Map<String, Object> login(@RequestBody User user) {
        System.out.println("前端传来的user:"+user);
        Map<String, Object> map = new HashMap<>();
        try {
            User userDB = userService.login(user);
            map.put("userDB",userDB);
            map.put("state",true);
            map.put("msg","登录成功!");
        } catch (Exception e) {
            e.printStackTrace();
            map.put("state",false);
            map.put("msg",e.getMessage());
        }
        return map;
    }

前端

<!DOCTYPE html>
<html lang="en">
<head>
    ......

    <!--和 element-ui/lib/index.js 一起可使用 this.$message.success()-->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">

</head>
<body>
<div class="page login-page" id="app">
    ...
          <form method="post" action="#" class="form-validate" id="loginFrom">
             <div class="form-group">
      <input id="login-username" type="text" required data-msg="请输入用户名"
                        placeholder="用户名" v-model="user.username" value="admin"
                                           class="input-material">
                                </div>
                                <div class="form-group">
       <input id="login-password" v-model="user.password" type="password" required
                                           data-msg="请输入密码"
                                           placeholder="密码" class="input-material">
                                </div>
       <button id="login" type="button" @click="login" class="btn btn-primary">登录</button>
...

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

<!--<script src="https://unpkg.com/vue/dist/vue.js"></script>-->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 和element-ui/lib/theme-chalk/index.css 一起可使用 this.$message.success("登录成功!"); 等操作-->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<!--挂载vue-->
<script>
    const $http = axios.create({
        baseURL: 'http://localhost:8081/ems_vue',
        timeout: 1000,
        headers: {'X-Custom-Header': 'foobar'}
    });
    let app = new Vue({
        el: "#app",
        data: {
            //用户信息,请求提交参数
            user: {}
        },
        created() {
        },
        methods: {
            // 用户登录
            async login() {
                const {data: res} = await $http.post("/login", this.user);
                console.log("后端返回的数据:", res);
                if (res.state) {
                    this.$message.success('登录成功');
                    localStorage.setItem("user",JSON.stringify(res.userDB));
                    location.href = './home.html';
                } else {
                    this.$message.error("登录失败!");
                }
            }
        }
    })
</script>
</body>
</html>

问题:登录成功时应将后,端返回的用户信息保存在浏览器中,方便后面其他业务使用

前后端分离的项目用session保存,存取比较麻烦,可以使用localStorage保存

Local Storage 以key :value 形式,可以在浏览器的内存中存储一些信息
在这里插入图片描述

if (res.state) {
 this.$message.success('登录成功');
 // 将后端返回的用户信息保存在浏览器的 Local Storage 中(key:value),方便后面业务使用
 // 这样保存的是一个对象,不方便使用,使用javascript中的 JSON.stringify() 将对象转化为json字符串
 // localStorage.setItem("user",res.userDB);
 
 localStorage.setItem("user",JSON.stringify(res.userDB));
 console.log("存入localStorage的user:"+localStorage.getItem("user"));
 
console.log("将json字符串转换成json对象的user:"+JSON.parse(localStorage.getItem("user")));   
 
} else {
 this.$message.error(res.msg);
}

在这里插入图片描述

拓展 sessionStorage

除了使用浏览器的 localStorage保存外,也可以使用浏览器的 sessionStorage 来保存用户对象,sessionStorage 也是 key :value 形式保存,以key取值,和 **localStorage **用法相同

// JSON.stringify(res.userDB) 将userDB对象转化为json字符串
sessionStorage.setItem("user", JSON.stringify(res.userDB));


// JSON.parse() 将一个json字符串转化为对象
console.log("存入sessionStorage的user:", JSON.parse(sessionStorage.getItem("user")));


===========================拓展:安全退出时可用到========================
// 清除localStorage中的数据
localStorage.clear();
//移除user
localStorage.removeItem("user");
//刷新页面(已淘汰)
location.reload(true)

END

  这期先更这些,后面主要是员工管理模块的CRUD,还有文件上传,以及elementUI的一些技术点,比较重要,后面的会写详细些,更新完了就将项目上传至码云。并首先在个人公众号遇见0和1发布,获取资料、学习更多的编程以及编程之外的知识,欢迎关注哦😁😁😁

在这里插入图片描述

编程之外

项目源码已,上传至码云:https://gitee.com/lingstudy/small_employee
编写不易,若对你有帮助的话,点个 Star 支持一下吧!

欢迎入坑哦😁😁!
在这里插入图片描述

  • 29
    点赞
  • 199
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值