发布时间:2024-01-19 14:00
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 支持一下吧!
欢迎入坑哦!