需求说明
办公自动化OA系统
办公自动化系统(Office Automation)是代替传统办公的解决方案
OA系统是利用软件技术构建的单位内部办公平台,用于辅助办公
利用OA系统可将办公数据数字化,可极大提高办公流程执行效率

需求介绍
慕课办公OA系统要求采用B/S架构设计开发
HR为每一位员工分配系统账户,员工用此账户登陆系统
公司采用分级定岗,从1-8依次提升,不同岗位薪资水平不同
需求介绍-请假流程
6级(含)一下员工为业务岗,对应人员执行公司业务事宜
7-8级为管理岗,其中7级为部门经理,8级为总经理
业务岗与管理岗员工可用系统功能不同,要求允许灵活配置
公司所有员工都可以使用“请假申请”功能申请休假
请假时间少于72小时,部门经理审批后直接通过
请假时间大于72小时,部门经理审批后还需总经理进行审批
部门经理只允许批准本部门员工申请
部门经理请假需直接由总经理审批
总经理提起请假申请,系统自动批准通过

搭建基础架构
框架&组件
MySQL 8 Mybatis 3.5
Alibaba Druid Servlet 3.1
Vue 3.x Element Plus









MVC模式讲解
MVC架构模式

MVC架构模式优点
软件团队分工合作,成员各司其职
分层开发,显示与数据解耦,便于维护
组件可灵活替代,互不影响
基于MVC的软件分层设计

开发MyBatisUtils工具类


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> <environments default="dev"> <environment id="dev"> <transactionManager type="JDBC"></transactionManager> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/imooc_oa?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <mappers> <mapper resource="mappers/test.xml"/> </mappers> </configuration>
|


实现MyBatisUtils工具类


1 2 3 4 5 6 7 8 9
| <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="test"> <select id="sample" resultType="String"> select 'success' </select> </mapper>
|



执行test

和test.xml文本中的一致
还可以写得更简洁

增删改类:

MyBatis整合Druid连接池





这里为什么改成driverClassName?原因如下:

运行试试:


登录与RBAC权限设计
RBAC权限底层设计
Role-Based Access Control
基于角色权限控制(RBAC)是面向企业安全策略的访问控制方式。
RBAC核心思想是将控制访问的资源与角色(Role)进行绑定。
系统的用户(User)与角色(Role)再进行绑定,用户便拥有对应权限。
RBAC底层设计

RBAC数据表解析








初识ElementPlus
https://element-plus.gitee.io/zh-CN/

1 2 3 4 5 6 7 8
| <head> <link rel="stylesheet" href="//unpkg.com/element-plus/dist/index.css" rel="external nofollow" target="_blank" /> <script src="//unpkg.com/vue@next" rel="external nofollow" ></script> <script src="//unpkg.com/element-plus" rel="external nofollow" ></script> </head>
|
实现登录功能



1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>办公OA系统</title> <link rel="stylesheet" type="text/css" href="assets/element-plus/index.css"/> <script src="assets/vue/vue.global.js"></script> <script src="assets/element-plus/index.full.js"></script> <script src="assets/axios/axios.js"></script> <style> .login-box{ border: 1px solid #dcdfe6; width: 350px; margin: 180px auto; padding: 35px 35px 15px 35px; border-radius: 5px; -webkit-border-radius: 5px; -moz-border-radius: 5px; box-shadow: 0 0 25px #909300; } </style> </head> <body> <div id="app"> <el-form ref="loginForm" label-width="80px" class="login-box">
</el-form> </div> <script> const Main = { data(){ return{
} } }; const app = Vue.createApp(Main); app.use(ElementPlus); app.mount("#app") </script> </body> </html>
|

绘制登录页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>办公OA系统</title> <link rel="stylesheet" type="text/css" href="assets/element-plus/index.css"/> <script src="assets/vue/vue.global.js"></script> <script src="assets/element-plus/index.full.js"></script> <script src="assets/axios/axios.js"></script> <style> .login-box { border: 1px solid #dcdfe6; width: 350px; margin: 180px auto; padding: 35px 35px 15px 35px; border-radius: 5px; -webkit-border-radius: 5px; -moz-border-radius: 5px; box-shadow: 0 0 25px #909399; } .login-title{ text-align: center; margin: 0 auto 40px auto; color: #303133; } </style> </head> <body> <div id="app"> <el-form ref="loginForm" label-width="80px" :rules="rules" :model="form" class="login-box"> <h2 class="login-title">OA办公系统</h2> <el-form-item label="账号" prop="username"> <el-input type="text" placeholder="请输入账号" v-model="form.username"></el-input> </el-form-item> <el-form-item label="密码" prop="password"> <el-input type="password" placeholder="请输入密码" v-model="form.password"></el-input> </el-form-item> <el-form-item> <el-button type="primary" v-on:click="onSubmit('loginForm')" style="width: 200px">登录</el-button> </el-form-item> </el-form> </div> <script> const Main = { data() { return { form: { username: '' , password: '' } , rules: { username: [ {required: true, message: '账号不能为空', trigger: 'blur'} ], password: [ {required: true, message: '密码不能为空', trigger: 'blur'} ] } } } , methods: { onSubmit(formName) { const form = this.$refs[formName]; form.validate((valid) => { if (valid) { console.info("表单校验成功,准备提交数据"); } }); } } }; const app = Vue.createApp(Main); app.use(ElementPlus); app.mount("#app") </script> </body> </html>
|

实现用户登录Model层




1 2 3 4 5 6 7 8 9
| <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="usermapper"> <select id="selectByUsername" parameterType="String" resultType="org.example.oa.entity.User"> select * from sys_user where username = #{value} </select> </mapper>
|


1 2 3 4 5 6
| public class UserMapper { public User selectByUsername(String username){ User user = (User)MybatisUtils.executeQuery(sqlSession -> sqlSession.selectOne("usermapper.selectByUsername", username)); return user; } }
|

1 2 3 4 5
| public class LoginException extends RuntimeException{ public LoginException(String message){ super(message); } }
|

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class UserService { private UserMapper userMapper = new UserMapper();
public User checkLogin(String username, String password){ User user = userMapper.selectByUsername(username); if (user == null){ throw new LoginException("用户名不存在"); } if (!password.equals(user.getPassword())){ throw new LoginException("密码错误"); } return user; } }
|
测试:








实现用户登录Controller层



1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| @WebServlet("/api/login") public class LoginServlet extends HttpServlet { private UserService userService = new UserService();
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); }
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); resp.setContentType("application/json;charset=utf-8"); String username = req.getParameter("username"); String password = req.getParameter("password"); Map result = new LinkedHashMap<>(); try { User user = userService.checkLogin(username, password); result.put("code", "0"); result.put("message", "success"); } catch (Exception e) { e.printStackTrace(); result.put("code", e.getClass().getSimpleName()); result.put("message", e.getMessage()); } ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); String json = objectMapper.writeValueAsString(result); resp.getWriter().println(json); } }
|
别忘记加入依赖:







实现用户登录View层
回到login.html



登录成功后跳转页面

账号输错,会在上端报错

密码输错,也会在上端报错

封装ResponseUtils工具类


最后面加上

对put()创建test对象

1 2 3 4 5 6 7 8 9
| public class ResponseUtilsTest {
@Test public void put() { ResponseUtils resp = new ResponseUtils("LoginException", "密码错误").put("class", "XXXClass").put("name", "mooc"); String json = resp.toJsonString(); System.out.println(json); } }
|

还可以再简写为:
1 2 3 4
| @Test public void put2() { System.out.println(new ResponseUtils("LoginException", "密码错误").put("class", "XXXClass").put("name", "mooc").toJsonString()); }
|
修改LoginServlet内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| @WebServlet("/api/login") public class LoginServlet extends HttpServlet { private UserService userService = new UserService();
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); }
@Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); response.setContentType("application/json;charset=utf-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); ResponseUtils resp = null; try { User user = userService.checkLogin(username, password); Map data = new LinkedHashMap(); data.put("user",user); resp = new ResponseUtils().put("user",user); } catch (Exception e) { e.printStackTrace(); resp = new ResponseUtils(e.getClass().getSimpleName(), e.getMessage()); } response.getWriter().println(resp.toJsonString()); } }
|



封装Md5Utils加密工具类
MD5摘要算法
MD5信息摘要算法是广泛应用的密码散列函数
MD5可以产生出128位的散列值用于标识源数据
项目中通常使用MD5作为敏感数据的加密算法
http://md5.chahuo.com/

Apache Commons Codec
Commons-Codec是Apache提供的编码/解码组件
通过Commons-Codec可轻易生成源数据的MD5摘要
MD5摘要方法: String md5 = DigestUtils.md5Hex(源数据)
https://commons.apache.org/proper/commons-codec/
在pom.xml中加入依赖

别忘记把依赖加入lib



生成测试用例类:



但是这还不是很安全,如可以利用https://www.cmd5.com/

所以这时候就要用到盐值(salt)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Md5Utils { public static String md5Digest(String source){ return DigestUtils.md5Hex(source); }
public static String md5Digest(String source, Integer salt){ char[] chars = source.toCharArray(); for (int i = 0; i < chars.length; i++){ chars[i] = (char) (chars[i] + salt); } String target = new String(chars); System.out.println(target); String md5 = DigestUtils.md5Hex(target); return md5; } }
|



完整实现登录功能

这些密码就是进过加盐的。
修改UserServlet


测试看看:

正确密码:


错误密码:




注意把返回数据设为null,防止被抓包。


这样password和salt就消失不见了。
实现后台首页




1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>办公OA系统</title> <link rel="stylesheet" type="text/css" href="assets/element-plus/index.css"/> <script src="assets/vue/vue.global.js"></script> <script src="assets/element-plus/index.full.js"></script> <script src="assets/axios/axios.js"></script> <style> .el-header{ background-color: rgb(238, 241, 246); color: #333; line-height: 60px; } </style> </head> <body> <div id="app"> <el-container style="height:100%;border: 1px solid #eee"> <el-header> 1112321321 </el-header> </el-container> </div> <script> const Main = { data(){ return{} } }; const app = Vue.createApp(Main); app.use(ElementPlus); app.mount("#app"); </script> </body> </html>
|

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>办公OA系统</title> <link rel="stylesheet" type="text/css" href="assets/element-plus/index.css"/> <script src="assets/vue/vue.global.js"></script> <script src="assets/element-plus/index.full.js"></script> <script src="assets/axios/axios.js"></script> <style> .el-header{ background-color: rgb(238, 241, 246); color: #333; line-height: 60px; } </style> </head> <body> <div id="app"> <el-container style="height:100%;border: 1px solid #eee"> <el-header> 1112321321 </el-header> <el-container> <el-aside width="200px" style="max-height: 100%;background-color: rgb(238,241,246)" > 我是功能区 </el-aside> <el-main> 我是页面显示区 </el-main> </el-container> </el-container> </div> <script> const Main = { data(){ return{} } }; const app = Vue.createApp(Main); app.use(ElementPlus); app.mount("#app"); </script> </body> </html>
|


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>办公OA系统</title> <link rel="stylesheet" type="text/css" href="assets/element-plus/index.css"/> <script src="assets/vue/vue.global.js"></script> <script src="assets/element-plus/index.full.js"></script> <script src="assets/axios/axios.js"></script> <style> .el-header{ background-color: rgb(238, 241, 246); color: #333; line-height: 60px; } html,body,#app,el-container{ padding: 0px; margin: 0px; height: 100%; max-height: 100%; } </style> </head> <body> <div id="app"> <el-container style="height:100%;border: 1px solid #eee"> <el-header> 1112321321 </el-header> <el-container> <el-aside width="200px" style="max-height: 100%;background-color: rgb(238,241,246)" > 我是功能区 </el-aside> <el-main> 我是页面显示区 </el-main> </el-container> </el-container> </div> <script> const Main = { data(){ return{} } }; const app = Vue.createApp(Main); app.use(ElementPlus); app.mount("#app"); </script> </body> </html>
|

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <div id="app"> <el-container style="height:100%;border: 1px solid #eee"> <el-header> 我是Header </el-header> <el-container> <el-aside width="200px" style="max-height: 100%;background-color: rgb(238,241,246)" > 我是功能区 </el-aside> <el-main> <iframe id="main" name="main" src="http://www.baidu.com" style="width: 100%;height: 95%;border: 0px"></iframe> </el-main> </el-container> </el-container> </div>
|

开发RBACModel层
查询员工可用功能。





1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="rbacmapper"> <select id="selectNodeByUserId" parameterType="Long" resultType="org.example.oa.entity.Node"> select DISTINCT n.* from sys_role_user ru, sys_role_node rn ,sys_node n where ru.role_id = rn.role_id and rn.node_id = n.node_id and ru.user_id = #{value} order by n.node_code </select> </mapper>
|


1 2 3 4 5 6
| public class RbacMapper { public List<Node> selectNodeByUserId(Long userId){ List list = (List) MybatisUtils.executeQuery(sqlSession -> sqlSession.selectList("rbacmapper.selectNodeByUserId", userId)); return list; } }
|

1 2 3 4 5 6
| public class RbacService { private RbacMapper rbacMapper = new RbacMapper(); public List<Node> selectNodeByUserId(Long userId){ return rbacMapper.selectNodeByUserId(userId); } }
|

1 2 3 4 5 6 7 8 9 10
| public class RbacServiceTest { private RbacService rbacService = new RbacService(); @Test public void selectNodeByUserId() { List<Node> nodes = rbacService.selectNodeByUserId(3l); for (Node n:nodes){ System.out.println(n.getNodeName()); } } }
|
记得selectNodeByUserId(3l)里传入的是长整型,后面加l。

开发RBACController层


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @WebServlet("/api/user_info") public class UserInfoServlet extends HttpServlet { private RbacService rbacService = new RbacService(); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String uid = request.getParameter("uid"); List<Node> nodes = rbacService.selectNodeByUserId(Long.parseLong(uid)); List<Map> treeList = new ArrayList<>(); Map module = null; for (Node node : nodes){ if (node.getNodeType() == 1){ module = new LinkedHashMap(); module.put("node",node); module.put("children", new ArrayList()); treeList.add(module); } else if (node.getNodeType() == 2) { List children = (List) module.get("children"); children.add(node); } } String json = new ResponseUtils().put("nodeList", treeList).toJsonString(); response.setContentType("application/json;charset=utf-8"); response.getWriter().println(json); }
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doPost(req, resp); } }
|

uid=1为管理岗

实现RBACView层
先暂时注释掉跳转页面,方便调试:



再次登录成功后:

关闭浏览器后,这里的就被清除了。
本地存储(Local Storage)的是长期存储的
会话存储(Session Storage)的是短期存储的
来到index.html

打开index.html控制台

再次回到代码中:


push()将内容填充到了上面的nodeList:[]
导栏菜单:

该源代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
| <template> <el-row class="tac"> <el-col :span="12"> <h5 class="mb-2">Default colors</h5> <el-menu default-active="2" class="el-menu-vertical-demo" @open="handleOpen" @close="handleClose" > <el-sub-menu index="1"> <template #title> <el-icon><location /></el-icon> <span>Navigator One</span> </template> <el-menu-item-group title="Group One"> <el-menu-item index="1-1">item one</el-menu-item> <el-menu-item index="1-2">item two</el-menu-item> </el-menu-item-group> <el-menu-item-group title="Group Two"> <el-menu-item index="1-3">item three</el-menu-item> </el-menu-item-group> <el-sub-menu index="1-4"> <template #title>item four</template> <el-menu-item index="1-4-1">item one</el-menu-item> </el-sub-menu> </el-sub-menu> <el-menu-item index="2"> <el-icon><icon-menu /></el-icon> <span>Navigator Two</span> </el-menu-item> <el-menu-item index="3" disabled> <el-icon><document /></el-icon> <span>Navigator Three</span> </el-menu-item> <el-menu-item index="4"> <el-icon><setting /></el-icon> <span>Navigator Four</span> </el-menu-item> </el-menu> </el-col> <el-col :span="12"> <h5 class="mb-2">Custom colors</h5> <el-menu active-text-color="#ffd04b" background-color="#545c64" class="el-menu-vertical-demo" default-active="2" text-color="#fff" @open="handleOpen" @close="handleClose" > <el-sub-menu index="1"> <template #title> <el-icon><location /></el-icon> <span>Navigator One</span> </template> <el-menu-item-group title="Group One"> <el-menu-item index="1-1">item one</el-menu-item> <el-menu-item index="1-2">item two</el-menu-item> </el-menu-item-group> <el-menu-item-group title="Group Two"> <el-menu-item index="1-3">item three</el-menu-item> </el-menu-item-group> <el-sub-menu index="1-4"> <template #title>item four</template> <el-menu-item index="1-4-1">item one</el-menu-item> </el-sub-menu> </el-sub-menu> <el-menu-item index="2"> <el-icon><icon-menu /></el-icon> <span>Navigator Two</span> </el-menu-item> <el-menu-item index="3" disabled> <el-icon><document /></el-icon> <span>Navigator Three</span> </el-menu-item> <el-menu-item index="4"> <el-icon><setting /></el-icon> <span>Navigator Four</span> </el-menu-item> </el-menu> </el-col> </el-row> </template>
<script lang="ts" setup> import { Document, Menu as IconMenu, Location, Setting, } from '@element-plus/icons-vue' const handleOpen = (key: string, keyPath: string[]) => { console.log(key, keyPath) } const handleClose = (key: string, keyPath: string[]) => { console.log(key, keyPath) } </script>
|
来到index.html中的功能区




换个低级用户试试:





回顾Mapper接口开发过程
回到index.html头部


再往右上角显示登录人:


再往数据库中加载该sql,补充部门和员工信息。







注意这里namespace用了接口名。
1 2 3 4 5 6 7 8 9
| <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.example.oa.mapper.EmployeeMapper"> <select id="selectById" parameterType="Long" resultType="org.example.oa.entity.Employee"> select * from adm_employee where employee_id = #{value} </select> </mapper>
|


别忘了注册:


再测试一下




1 2 3 4 5 6 7 8 9
| public class EmployeeServlet { public Employee selectById(Long employeeId){ Employee employee = (Employee) MybatisUtils.executeQuery(sqlSession -> { EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); return mapper.selectById(employeeId); }); return employee; } }
|
来到之前的controller中:




接下来绑定页面
来到index.html

来到上面进行双向绑定:


轮到注销了

清空数据,回到登录页面:

开发多级审批流程
请假流程

设计约束
每一个单位对应一个审批流程
请假单创建后,按业务规则生成部门经理、总经理审批任务
审批任务的经办人只能审批自己辖区内的请假申请
所以审批任务”通过”,代表请假已经批准
任意审批任务”驳回”操作,其余审批任务取消,请假申请被驳回
请假流程中任意节点产生的操作都要生成对应的系统通知
请假流程数据库设计
设计表



开发请假申请功能


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>请假申请单</title> <link rel="stylesheet" type="text/css" href="/assets/element-plus/index.css"> <script src="/assets/vue/vue.global.js"></script> <script src="/assets/element-plus/index.full.js"></script> <script src="/assets/element-plus/locale/zh-cn.js"></script> <script src="/assets/axios/axios.js"></script>
<style> .el-form { border: 1px solid #DCDFE6; width: 600px; margin: 180px auto; padding: 35px 35px 15px 35px; border-radius: 5px; -webkit-border-radius: 5px; -moz-border-radius: 5px; box-shadow: 0 0 25px #909399; } </style>
</head> <body> <div id="app"> <el-form ref="leaveForm" :model="form" :rules="rules" label-width="80px"> <el-descriptions title="请假申请单" :column="1" border> <el-descriptions-item label="部门">研发部</el-descriptions-item> <el-descriptions-item label="申请人">王美美[高级研发工程师] </el-descriptions-item> <el-descriptions-item label="请假类型">
<el-select v-model="form.formType" style="width: 100%"> <el-option label="事假" value="1"></el-option> <el-option label="病假" value="2"></el-option> <el-option label="工伤假" value="3"></el-option> <el-option label="婚嫁" value="4"></el-option> <el-option label="产假" value="5"></el-option> <el-option label="丧假" value="6"></el-option> </el-select>
</el-descriptions-item> <el-descriptions-item label="请假时间"> <el-form-item prop="timeRange" label-width="0px"> <div class="block"> <el-date-picker v-model="form.timeRange" type="datetimerange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"> </el-date-picker> </div> </el-form-item> </el-descriptions-item> <el-descriptions-item label="请假原因"> <el-form-item prop="reason" label-width="0px"> <el-input type="text" placeholder="请输入请假原因" v-model="form.reason"/> </el-form-item> </el-descriptions-item>
</el-descriptions> <div style="text-align: center;padding-top: 30px"> <el-button type="primary" >立即申请</el-button> </div> </el-form>
</div>
<script>
var Main = { data() { return { form: { formType: "1", timeRange: "", startTime: "", endTime: "", reason: "", eid: "", }, rules: { timeRange: [ {required: true, message: '请选择请假时间', trigger: 'blur'} ], reason: [ {required: true, message: '请填写请假原因', trigger: 'blur'} ] } } } }; ElementPlus.locale(ElementPlus.lang.zhCn); const app = Vue.createApp(Main); app.use(ElementPlus, ElementPlus.lang.zhCn); app.mount("#app") </script> </body> </html>
|
开发请假申请Mapper层


利用setter and getter和toString方法。




这里可以利用一条垃圾数据来生成insert语句方便写入。

再进行修改,记得这里用不到form_id,将其删去
1 2 3 4 5 6
| <mapper namespace="org.example.oa.mapper.LeaveFormMapper"> <insert id="insert" parameterType="org.example.oa.entity.LeaveForm"> INSERT INTO `imooc_oa`.`adm_leave_form` ( `employee_id`, `form_type`, `start_time`, `end_time`, `reason`, `create_time`, `state`) VALUES ( #{employeeId}, #{formType}, #{startTime}, #{endTime}, #{reason}, #{createTime}, #{state}); </insert> </mapper>
|
再完善一下,加入主键。
1 2 3 4 5 6 7
| <mapper namespace="org.example.oa.mapper.LeaveFormMapper"> <insert id="insert" parameterType="org.example.oa.entity.LeaveForm" useGeneratedKeys="true" keyProperty="formId" keyColumn="form_id"> INSERT INTO `imooc_oa`.`adm_leave_form` ( `employee_id`, `form_type`, `start_time`, `end_time`, `reason`, `create_time`, `state`) VALUES ( #{employeeId}, #{formType}, #{startTime}, #{endTime}, #{reason}, #{createTime}, #{state}); </insert> </mapper>
|
记得注册:

测试一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @Test public void insert() { MybatisUtils.executeUpdate(sqlSession -> { LeaveFormMapper mapper = sqlSession.getMapper(LeaveFormMapper.class); LeaveForm form = new LeaveForm(); form.setEmployeeId(4l); form.setFormType(1); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date startTime = null; Date endTime = null; try { startTime = sdf.parse("2020-03-25 08:00:00"); endTime = sdf.parse("2020-04-01 18:00:00"); } catch (ParseException e) { e.printStackTrace(); } form.setStartTime(startTime); form.setEndTime(endTime); form.setReason("回家探亲"); form.setCreateTime(new Date()); form.setState("processing"); mapper.insert(form); return null; }); }
|


form_id它会自动加一。

同之前一样,setter getter and toString



insert语句还是从数据库中复制,并修改。
process_id和对应值要去掉。
1 2 3 4 5 6 7 8 9 10 11
| <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.example.oa.mapper.ProcessFormMapper"> <insert id="insert" parameterType="org.example.oa.entity.ProcessFlow" useGeneratedKeys="true" keyProperty="processId" keyColumn="process_id"> INSERT INTO `imooc_oa`.`adm_process_flow` ( `form_id`, `operator_id`, `action`, `result`, `reason`, `create_time`, `audit_time`, `order_no`, `state`, `is_last`) VALUES (#{formId}, #{operatorId}, #{action}, #{result}, #{reason}, #{createTime}, #{auditTime}, #{orderNo}, #{state}, #{isLast}); </insert> </mapper>
|
记得注册:

进行测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class ProcessFormMapperTest {
@Test public void insert() { MybatisUtils.executeUpdate(sqlSession -> { ProcessFormMapper mapper = sqlSession.getMapper(ProcessFormMapper.class); ProcessFlow processFlow = new ProcessFlow(); processFlow.setFormId(3l); processFlow.setOperatorId(2l); processFlow.setAction("audit"); processFlow.setResult("approved"); processFlow.setReason("同意"); processFlow.setCreateTime(new Date()); processFlow.setAuditTime(new Date()); processFlow.setOrderNo(1); processFlow.setState("ready"); processFlow.setIsLast(1); mapper.insert(processFlow); return null; });
|


再处理notice表

这里不仅要get set toSting方法,还要含参无参构造方法

这里含参的是这两个参数,再修改一下,删去Date参数。如下:




1 2 3 4 5 6 7 8 9 10 11
| <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.example.oa.mapper.NoticeMapper"> <insert id="insert" parameterType="org.example.oa.entity.Notice" useGeneratedKeys="true" keyProperty="noticeId" keyColumn="notice_id"> INSERT INTO `sys_notice` (`receiver_id`, `content`, `create_time`) VALUES (#{receiverId}, #{content}, #{createTime}); </insert> </mapper>
|
注册:
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13
|
public class NoticeMapperTest { @Test public void insert() { MybatisUtils.executeUpdate(sqlSession -> { NoticeMapper mapper = sqlSession.getMapper(NoticeMapper.class); mapper.insert(new Notice(2l, "测试消息")); return null; }); } }
|


开发请假申请Service层



1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.example.oa.mapper.EmployeeMapper"> <select id="selectById" parameterType="Long" resultType="org.example.oa.entity.Employee"> select * from adm_employee where employee_id = #{value} </select> <select id="selectByParams" parameterType="java.util.Map" resultType="org.example.oa.entity.Employee"> select * from adm_employee where 1=1 <if test="level != null"> and level = #{level} </if> <if test="departmentId != null"> and department_id = #{department} </if> <if test="title != null"> and title = #{title} </if> </select> </mapper>
|
都暂时先返回null,进行测试:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class EmployeeService { public Employee selectById(Long employeeId){ Employee employee = (Employee) MybatisUtils.executeQuery(sqlSession -> { EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); return mapper.selectById(employeeId); }); return employee; }
public Employee selectLeader(Long employeeId){ MybatisUtils.executeQuery(sqlSession -> { EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); Employee employee = mapper.selectById(employeeId); if (employee.getLevel() < 7){ }else if (employee.getLevel() == 7){ }else if (employee.getLevel() == 8){ } return null; }); return null; } }
|

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public class LeaveFormService {
public LeaveForm createLeaveForm(LeaveForm form){ MybatisUtils.executeUpdate(sqlSession -> { EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class); Employee employee = employeeMapper.selectById(form.getEmployeeId()); if (employee.getLevel() == 8){ form.setState("approved"); }else { form.setState("processing"); } LeaveFormMapper leaveFormMapper = sqlSession.getMapper(LeaveFormMapper.class); leaveFormMapper.insert(form); ProcessFlowMapper processFlowMapper = sqlSession.getMapper(ProcessFlowMapper.class); ProcessFlow flow1 = new ProcessFlow(); flow1.setFormId(form.getFormId()); flow1.setOperatorId(employee.getEmployeeId()); flow1.setAction("apply"); flow1.setCreateTime(new Date()); flow1.setOrderNo(1); flow1.setState("complete"); flow1.setIsLast(0); processFlowMapper.insert(flow1); return null; }); return null; } }
|
测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @Test public void selectByParams1() { Map params = new HashMap<>(); params.put("level", 7); params.put("department", 2); MybatisUtils.executeQuery(sqlSession -> { EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class); List<Employee> employees = employeeMapper.selectByParams(params); return employees; }); }
@Test public void selectByParams2() { Map params = new HashMap<>(); params.put("level", 8); MybatisUtils.executeQuery(sqlSession -> { EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class); List<Employee> employees = employeeMapper.selectByParams(params); return employees; }); }
|
对于测试1:

对于测试2:

再回到EmployeeService补充完整
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import org.example.oa.entity.Employee; import org.example.oa.mapper.EmployeeMapper; import org.example.oa.utils.MybatisUtils;
import java.util.HashMap; import java.util.List; import java.util.Map; public class EmployeeService { public Employee selectById(Long employeeId){ Employee employee = (Employee) MybatisUtils.executeQuery(sqlSession -> { EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); return mapper.selectById(employeeId); }); return employee; }
public Employee selectLeader(Long employeeId){ Employee l = (Employee)MybatisUtils.executeQuery(sqlSession -> { EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class); Employee employee = mapper.selectById(employeeId); Map params = new HashMap<>(); Employee leader = null; if (employee.getLevel() < 7){ params.put("level", 7); params.put("departmentId" ,employee.getDepartmentId()); List<Employee> employees = mapper.selectByParams(params); leader = employees.get(0); }else if (employee.getLevel() == 7){ params.put("level", 8); List<Employee> employees = mapper.selectByParams(params); leader = employees.get(0); }else if (employee.getLevel() == 8){ leader = employee; } return leader; }); return l; } }
|
将LeaveFormService也补充完整
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
| import org.example.oa.entity.Employee; import org.example.oa.entity.LeaveForm; import org.example.oa.entity.ProcessFlow; import org.example.oa.mapper.EmployeeMapper; import org.example.oa.mapper.LeaveFormMapper; import org.example.oa.mapper.ProcessFlowMapper; import org.example.oa.utils.MybatisUtils;
import java.util.Date;
public class LeaveFormService { private EmployeeService employeeService = new EmployeeService();
public LeaveForm createLeaveForm(LeaveForm form){ LeaveForm f = (LeaveForm) MybatisUtils.executeUpdate(sqlSession -> { EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class); Employee employee = employeeMapper.selectById(form.getEmployeeId()); if (employee.getLevel() == 8){ form.setState("approved"); }else { form.setState("processing"); } LeaveFormMapper leaveFormMapper = sqlSession.getMapper(LeaveFormMapper.class); leaveFormMapper.insert(form); ProcessFlowMapper processFlowMapper = sqlSession.getMapper(ProcessFlowMapper.class); ProcessFlow flow1 = new ProcessFlow(); flow1.setFormId(form.getFormId()); flow1.setOperatorId(employee.getEmployeeId()); flow1.setAction("apply"); flow1.setCreateTime(new Date()); flow1.setOrderNo(1); flow1.setState("complete"); flow1.setIsLast(0); processFlowMapper.insert(flow1); if (employee.getLevel() < 7){ Employee dmanager = employeeService.selectLeader(employee.getEmployeeId()); ProcessFlow flow2 = new ProcessFlow(); flow2.setFormId(form.getFormId()); flow2.setOperatorId(dmanager.getEmployeeId()); flow2.setAction("audit"); flow2.setCreateTime(new Date()); flow2.setOrderNo(2); flow2.setState("process"); long diff = form.getEndTime().getTime() - form.getStartTime().getTime(); float hours = diff / (1000 * 60 * 60) * 1f; if (hours >= 72) { flow2.setIsLast(0); processFlowMapper.insert(flow2); Employee manager = employeeService.selectLeader(dmanager.getEmployeeId()); ProcessFlow flow3 = new ProcessFlow(); flow3.setFormId(form.getFormId()); flow3.setOperatorId(manager.getEmployeeId()); flow3.setAction("audit"); flow3.setCreateTime(new Date()); flow3.setState("ready"); flow3.setOrderNo(3); flow3.setIsLast(1); processFlowMapper.insert(flow3); }else { flow2.setIsLast(1); processFlowMapper.insert(flow2); } } else if (employee.getLevel() == 7) { Employee manager = employeeService.selectLeader(employee.getEmployeeId()); ProcessFlow flow2 = new ProcessFlow(); flow2.setFormId(form.getFormId()); flow2.setOperatorId(manager.getEmployeeId()); flow2.setAction("audit"); flow2.setCreateTime(new Date()); flow2.setState("process"); flow2.setOrderNo(2); flow2.setIsLast(1); processFlowMapper.insert(flow2); } else if (employee.getLevel() == 8){ ProcessFlow flow2 = new ProcessFlow(); flow2.setFormId(form.getFormId()); flow2.setOperatorId(employee.getEmployeeId()); flow2.setAction("audit"); flow2.setResult("approved"); flow2.setReason("自动通过"); flow2.setCreateTime(new Date()); flow2.setAuditTime(new Date()); flow2.setState("complete"); flow2.setOrderNo(2); flow2.setIsLast(1); processFlowMapper.insert(flow2); }
return form; }); return f; } }
|
测试一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class LeaveFormServiceTest { LeaveFormService leaveFormService = new LeaveFormService(); @Test public void createLeaveForm() throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH"); LeaveForm form = new LeaveForm(); form.setEmployeeId(8l); form.setStartTime(sdf.parse("2020032608")); form.setEndTime(sdf.parse("2020040118")); form.setFormType(1); form.setReason("市场部员工请假单(72小时以上)"); form.setCreateTime(new Date()); LeaveForm savedForm = leaveFormService.createLeaveForm(form); System.out.println(savedForm.getFormId()); } }
|
输出了:10001


1 2 3 4 5 6 7 8 9 10 11 12 13
| @Test public void createLeaveForm2() throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH"); LeaveForm form = new LeaveForm(); form.setEmployeeId(8l); form.setStartTime(sdf.parse("2020032608")); form.setEndTime(sdf.parse("2020032718")); form.setFormType(1); form.setReason("市场部员工请假单(72小时以内)"); form.setCreateTime(new Date()); LeaveForm savedForm = leaveFormService.createLeaveForm(form); System.out.println(savedForm.getFormId()); }
|


1 2 3 4 5 6 7 8 9 10 11 12 13
| @Test public void createLeaveForm3() throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH"); LeaveForm form = new LeaveForm(); form.setEmployeeId(2l); form.setStartTime(sdf.parse("2020032608")); form.setEndTime(sdf.parse("2020040118")); form.setFormType(1); form.setReason("研发部部门经理请假单"); form.setCreateTime(new Date()); LeaveForm savedForm = leaveFormService.createLeaveForm(form); System.out.println(savedForm.getFormId()); }
|

1 2 3 4 5 6 7 8 9 10 11 12 13
| @Test public void createLeaveForm4() throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHH"); LeaveForm form = new LeaveForm(); form.setEmployeeId(1l); form.setStartTime(sdf.parse("2020032608")); form.setEndTime(sdf.parse("2020040118")); form.setFormType(1); form.setReason("总经理请假单"); form.setCreateTime(new Date()); LeaveForm savedForm = leaveFormService.createLeaveForm(form); System.out.println(savedForm.getFormId()); }
|

开发请假Controller层

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| @WebServlet("/api/leave/*") public class LeaveFormServlet extends HttpServlet { private LeaveFormService leaveFormService = new LeaveFormService(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); }
@Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); response.setContentType("application/json;charset=utf-8"); String url = request.getRequestURI(); String methodName = url.substring(url.lastIndexOf("/") + 1); if (methodName.equals("create")){ this.create(request, response); } else if (methodName.equals("list")) { } else if (methodName.equals("audit")) { } } private void create(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String strEmployeeId = request.getParameter("eid"); String formType = request.getParameter("formType"); String startTime = request.getParameter("startTime"); String endTime = request.getParameter("endTime"); String reason = request.getParameter("reason"); LeaveForm form = new LeaveForm(); form.setEmployeeId(Long.parseLong(strEmployeeId)); form.setStartTime(new Date(Long.parseLong(startTime))); form.setEndTime(new Date(Long.parseLong(endTime))); form.setFormType(Integer.parseInt(formType)); form.setReason(reason); form.setCreateTime(new Date()); ResponseUtils resp = null; try { leaveFormService.createLeaveForm(form); resp = new ResponseUtils(); }catch (Exception e){ e.printStackTrace(); resp = new ResponseUtils(e.getClass().getSimpleName(), e.getMessage()); } response.getWriter().println(resp.toJsonString()); } }
|
开发请假申请View层
来到leave_form.html
在