springboot + vue + elementUI + mybatis + redis 清新的员工管理系统
从这期,项目从需求分析开始,一步步实现一个老经典的清新的员工管理系统,适合有一定 ssm、springboot、mybatis、vue+elementUI 基础的训练项目,虽然没有很复杂的业务,但也要会这些技术栈的基础才行。看下运行效果就开始了,,
适合有一定 ssm、springboot、mybatis、vue+elementUI 基础的训练项目
登录和注册页面,是在源码之家随便找的一个来用:
地址:https://www.mycodes.net/192/10205.htm
需求分析
库表设计
详细设计
编码环节
测试
部署上线(发布到生产环境)
用户模块:
a. 用户登录
b. 用户注册
c. 验证码实现
d. 欢迎xxx用户展示
e. 安全退出
员工模块:
f. 员工列表分页展示
g. 员工添加
h. 员工删除
i. 员工修改
j. 员工列表加入redis缓存实现
1、分析系统中有哪些表?
2、分析表与表之间关系?
3、分析表中字段?
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图、系统模块结构图
项目名: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
<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>
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
Message 消息提示:https://element.eleme.cn/#/zh-CN/component/message#fang-fa
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">
  男<input type="radio" v-model="user.gender" value="true">
  女<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"> 登录</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);
}
除了使用浏览器的 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 支持一下吧!
欢迎入坑哦!