快捷搜索:

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

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

前言

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


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

编程之外

欢迎入坑哦😁😁!

经验分享 程序员 职场和发展