• j2ee课程设计—基于activiti的请休假系统


    前言

    课设基于SSM框架,数据库采用mysql,主要业务交给activiti,版本控制利用github。
    参考资料:

    activiti学习小记

    基于0中Activiti就是这么简单,做些笔记。
    当前环境:按照Intellij 部署SSM框架中部署完成SSM框架,能tomcat启动,连接上数据库。

    快速入门

    在testjavamlshylocks下新建一个class做activiti测试用。

    pom.xml中加入以下代码,以便maven从中央仓库push activiti所需要的包

     <dependency>
          <groupId>org.activiti</groupId>
          <artifactId>activiti-engine</artifactId>
          <version>5.22.0</version>
        </dependency>
    
        <dependency>
          <groupId>org.activiti</groupId>
          <artifactId>activiti-spring</artifactId>
          <version>5.22.0</version>
        </dependency>
        <dependency>
          <groupId>org.codehaus.groovy</groupId>
          <artifactId>groovy-all</artifactId>
          <version>2.4.3</version>
        </dependency>
    

    1.4.1activiti.cfg.xml放入resource文件夹下,同时创建工作流也在同文件夹下:

    测试1.4.3deploy()

    在函数开头加入ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    所需要import的包为:

    点击函数右侧

    即可快速运行。
    运行成功结果:

    可能出现的错误为.bpmn文件错误命名、放置。
    查询数据库,没有查到任何结果…?

    本地mysql连接有问题,所以用的是云服务器上的mysql数据库,首先检测jdbc连接没有问题,再检测activit.cfg.xml,发现数据库字段不是自定义的库,而是复制的activitiDB。
    再次查询activitiDB库,得到结果:

    查询为何出错的同时,查找的helpful博文:烟火_:activiti入门
    将acitivit用到的事务放在before里:

    测试1.4.4startProcess()1.4.5queryTask()

    导入processinstance包:

    注意将processDefiKey改为创建的工作流id,不然会出现以下错误:

    要是在deploy()执行后执行startProcess(),注意工作流id的一致,后续更改是无效的。
    执行queryTask(),查询数据库,表项已添加:

    测试1.4.6compileTask()

    注意taskId是刚刚queryTask()执行的id,直接复制代码会出现异常。
    执行后数据库表项:

    小结

    经过以上学习,activiti的大致流程如OA一样,由申请人发起事务,审批人审批事务,最终结束。
    所有数据库端操作均由底层mybatis执行,只需要调用接口,可以大大简化操作流程。
    延伸:Activiti工作流引擎主要表结构

    概要设计

    • 设计草图
      系统总功能图:

      模块图:

      流程图:

      对于以上,可以抽象出以下业务:

    Contorller类


    对于校园用户体系来说,高权限用户对低权限用户具有管理权限,但由于activiti的局限性,无法建立班级——班主任,系(专业)——辅导员这样的一对一关系,想要做出完整的校园请假系统应建立对应的数据表,数据操作是利用联合查询。
    班主任用户组与辅导员用户组,区别在于请假业务中学生请假时长不同,分配到的用户不同,权限实际上是相同的。
    由图中可以看出,不同权限组可以访问的页面存在高度的耦合性,所以整合为以下页面:

    Service类

    为了将界面层与业务逻辑层分离,将所有对数据的操作包装在service类中

    事务建模

    请假事务:

    节假日去向/寒暑假申请:

    详细设计

    基础部分略去,只列出实现过程中遇到的各种问题。

    Model

    • 在使用mybatis封装的mapper对数据库进行操作时,创建的mapper总是提示NullPointerError,由于mapper是在applicationContext.xml中自动扫描的,手动加载没有问题,谷歌无数后才解决:

      • 在*mapper.java中,interface前加入@Repository:
      • 在*Controller.java创建mapper前加入@Autowired:
    • 由于activiti的数据表设计,用户的用户组管理(act_id_membership)与用户基本信息(act_id_user)不在一张表中,对于用户管理来说很不方便,需要将act_id_membership、act_id_user、act_id_group三表联合查询。mybatis对于数据库的操作封装在*Mapper.xml中,利用mybatis-generator生成三张表的相应mapper,在Actidusermapper.xml的resultmap标签中加入

    <association property="group" javaType="ml.shylocks.entity.ActIdGroup" >
          <id column="ID_" property="id" jdbcType="VARCHAR" />
          <result column="REV_" property="rev" jdbcType="INTEGER" />
          <result column="NAME_" property="name" jdbcType="VARCHAR" />
          <result column="TYPE_" property="type" jdbcType="VARCHAR" />
        </association>
        <collection property="membership" javaType="ml.shylocks.entity.ActIdMembershipKey">
          <id column="USER_ID_" property="userId" jdbcType="VARCHAR" />
          <id column="GROUP_ID_" property="groupId" jdbcType="VARCHAR" />
        </collection>
    

    在mapper标签中加入

    <select id="getUser" resultMap="BaseResultMap">
         select *
         from act_id_user A
         join act_id_membership B on A.id_=B.user_id_
         join act_id_group C on B.group_id_=C.id_
      </select>
      <select id="getUserByGroup" resultMap="BaseResultMap" parameterType="java.lang.String">
         select *
         from act_id_user A
         join act_id_membership B on A.id_=B.user_id_
         join act_id_group C on B.group_id_=C.id_
         where B.group_id_=#{groupid,jdbcType=VARCHAR}
      </select>
    

    在actiduser.java中加入:

     public ActIdGroup group;
        public ActIdMembershipKey membership;
       public ActIdGroup getGroup() {
            return group;
        }
    
        public void setGroup(ActIdGroup group) {
            this.group = group;
        }
    
        public ActIdMembershipKey getMembership() {
            return membership;
        }
    
        public void setMembership(ActIdMembershipKey membership) {
            this.membership = membership;
        }
    

    将actidusermapper.java改为:

    @Repository
    public interface UserMapper {
        List<User> getUser();
        List<User> getUserByGroup(String groupid);
    }
    

    View

    • 原本计划是前后端分离,利用vue.js写前端的,不过一个人完成整个项目时间不够,最后还是用jsp写的。在不使用iframe,framework的情况下,j2ee中想要实现类似django中的extends使用的是http://www.rapid-framework.org.cn/rapid,将父框架放入base.jsp,其他子页面利用rapid:block对父框架中相应内容进行重写。但是这种复写方式会导致利用Model写入父框架的java元素在子框架中无法显示,所以子页面利用$.load()加载。
      注意子页面中的js代码应当使用规范标签,加入type="text/javascript"。
    • html5中input新增了date的类型可以代替datetimepicker

    Controller

    • 对于寒暑假登记、节假日去向事务来说,不应该将其声明为学生用户组,这样只会创建一个流程实例,应当在发布时对所有学生申明。
      • 前端部分使用select2插件选择学生:

        由于spring mvc 无法对多个同名 RequestParam 进行解析,在提交表单时将学生id数组以字符串形式传递:
    if (data['candidate'] == "students") {
                let c = $("#select2").val();
                for (let x = 0; x < c.length; ++x) {
                    data['candidateList'] += '"' + c[x] + '",';
                }
                data['candidateList'] += "]";
            }
    
    - 后端接受时,利用jsonarray对其进行解析
    
    JSONArray jsonArray = JSONArray.fromObject(form.get("candidateList"));
                List<String> candidateList = new ArrayList<String>();
                for (int i = 0; i < jsonArray.size(); ++i) candidateList.add(jsonArray.optString(i));
    
    - 注意在`task.setAssignee()`后保存当前任务`taskservice.saveTask(task)`
    
    • 由于设计的业务流程中,学生填写/提出申请并未指定用户/用户组,所以当审批不通过时,应人工指定流程中当前任务的assignee,注意runtimeservice中只保存当前任务,找到第一个userTask的assignee应使用historyService:
    Task nowTask = taskService.createTaskQuery().executionId(task.getExecutionId()).singleResult();
                if (nowTask != null) {
                    HistoryService historyService = processEngine.getHistoryService();
                    HistoricTaskInstance historicTaskInstance = historyService.
                            createHistoricTaskInstanceQuery().
                            executionId(defId).list().get(0);
                    nowTask.setAssignee(historicTaskInstance.getAssignee());
                    nowTask.setCategory(historicTaskInstance.getCategory());
                    taskService.saveTask(nowTask);
                }
    
  • 相关阅读:
    Java SE入门(十三)——高级API
    Java SE入门(十二)——修饰符与内部类
    Java SE入门(十一)——接口与多态
    数据结构与算法(一)——绪论
    break与continue的区别以及终止函数的return false
    文字超出隐藏显示省略号及鼠标放上去显示全部文字信息的写法
    组件有新增修改两种状态该怎么使用
    Math常用的属性和方法
    模块化开发及import用法
    element时间日期选择器组件设置默认时间
  • 原文地址:https://www.cnblogs.com/shy-/p/10046648.html
Copyright © 2020-2023  润新知