Ionic CLI 升级到最新版本 – tomKart – 博客园

mikel阅读(3)

来源: Ionic CLI 升级到最新版本 – tomKart – 博客园

由于Ionic 自身也在不停的更新当中,

所以开发者经常会遇到从官方的CLI 命令,在命令行窗口中执行出错的情况。

就比如我一个月之前安装的ionic 2.2.2版本,已不能使用最新的3.2.0 CLI命令。

(其实困扰也没有那么大,cmd会有提示信息,告诉你command为什么无法执行,选择相应的策略去解决就行了。)

不过这里还是简要介绍一下如何升级Ionic CLI,算是留个小笔记。

npm update -g cordova ionic

执行完成后可以看到cordova和ionic更新后的版本号:

当然最原始的方式 npm uninstall ,然后npm clean,再npm install 也是可以的。

mac终端切换用户

mikel阅读(4)

mac终端切换用户(做个小笔记)

1、mac终端切换用户到root

sudo -i即可

2、mac终端切换成普通用户

su – test(test为用户名)即可

3、mac可以设置命令别名

如ll,正常情况下,mac终端不识别ll命令,但是alias 命令设置别名后,终端即可以设别ll命令:alias ll=’ls -la’;但是没有修改系统文件,则该命令只能临时生效,下次再打开终端,还是不能识别ll命令,永久生效办法,请修改系统文件,方法有多种,请自行百度(如果不是ll命令,其他命令同理)

深度学习入门篇–手把手教你用 TensorFlow 训练模型 – 腾讯云技术社区 – 博客园

mikel阅读(8)

来源: 深度学习入门篇–手把手教你用 TensorFlow 训练模型 – 腾讯云技术社区 – 博客园

导语

Tensorflow在更新1.0版本之后多了很多新功能,其中放出了很多用tf框架写的深度网络结构(https://github.com/tensorflow/models ),大大降低了开发难度,利用现成的网络结构,无论fine-tuning还是重新训练方便了不少。最近笔者终于跑通TensorFlow Object Detection API的ssd_mobilenet_v1模型,这里记录下如何完整跑通数据准备到模型使用的整个过程,相信对自己和一些同学能有所帮助。

Object Detection API提供了5种网络结构的预训练的权重,全部是用COCO数据集进行训练,这五种模型分别是SSD+mobilenet、SSD+inception_v2、R-FCN+resnet101、faster RCNN+resnet101、faster RCNN+inception+resnet101。各个模型的精度和计算所需时间如下。下面及介绍下如何使用Object Detection去训练自己的模型。

这里TensorFlow的安装就不再说明了,网上的教程一大把,大家可以找到很详尽的安装TensorFlow的文档。

训练前准备:

使用protobuf来配置模型和训练参数,所以API正常使用必须先编译protobuf库,这里可以下载直接编译好的pb库(https://github.com/google/protobuf/releases ),解压压缩包后,把protoc加入到环境变量中:

$ cd tensorflow/models

$ protoc object_detection/protos/*.proto --python_out=.

 

(我是把protoc加到环境变量中,遇到找不到*.proto文件的报错,后来把protoc.exe放到models/object_detection目录下,重新执行才可以)

然后将models和slim(tf高级框架)加入python环境变量:

PYTHONPATH=$PYTHONPATH:/your/path/to/tensorflow/models:/your/path/to/tensorflow/models/slim

 

数据准备:

数据集需要转化成PASCAL VOC结构,API提供了create_pascal_tf_record.py,把VOC结构数据集转换成.record格式。不过我们发现更简单的方式,Datitran提供一种更简单生产.record格式的方法。

首先需要先要标注图像相应标签,这里可以使用labelImg工具。每标注一张样本,即生成一个xml的标注文件。然后,把这些标注的xml文件,按训练集与验证集分别放置到两个目录下,在Datitran提供了xml_to_csv.py脚本。这里只要指定标注的目录名即可。接下来,然后需要我们把对应的csv格式转换成.record格式。

def main():
    # image_path = os.path.join(os.getcwd(), 'annotations')
    image_path = r'D:\training-sets\object-detection\sunglasses\label\test'
    xml_df = xml_to_csv(image_path)
    xml_df.to_csv('sunglasses_test_labels.csv', index=None)
    print('Successfully converted xml to csv.')

 

调用generate_tfrecord.py,注意要指定–csv_input与–output_path这两个参数。执行下面命令:

python generate_tfrecord.py --csv_input=sunglasses_test_labels.csv --output_path=sunglass_test.record

 

这样就生成了训练及验证用的train.record与test.record。接下来指定标签名称,仿照models/ object_detection/data/ pet_label_map.pbtxt,重新创建一个文件,指定标签名。

item {
  id: 1
  name: 'sunglasses'
}

 

训练:

根据自己的需要,选择一款用coco数据集预训练的模型,把前缀model.ckpt放置在待训练的目录,这里meta文件保存了graph和metadata,ckpt保存了网络的weights,这几个文件表示预训练模型的初始状态。

打开ssd_mobilenet_v1_pets.config文件,并做如下修改:

  1. num_classes:修改为自己的classes num

  1. 将所有PATH_TO_BE_CONFIGURED的地方修改为自己之前设置的路径(共5处)

其他参数均保持默认参数。

准备好上述文件后就可以直接调用train文件进行训练。

python object_detection/train.py \
--logtostderr \
--pipeline_config_path= D:/training-sets /data-translate/training/ssd_mobilenet_v1_pets.config \
--train_dir=D:/training-sets/data-translate/training

 

TensorBoard监控:

通过tensorboard工具,可以监控训练过程,输入西面指令后,在浏览器输入localhost:6006(默认)即可。

tensorboard --logdir= D:/training-sets/data-translate/training

 

这里面有很多指标曲线,甚至有模型网络架构,笔者对于这里面很多指标含义还没有弄明白,不过感觉出TensorBoard这个工具应该是极其强大。不过我们可以通过Total_Loss来看整体训练的情况。

从整体上看,loss曲线确实是收敛的,整体的训练效果还是满意的。另外,TensorFlow还提供了训练过程中利用验证集验证准确性的能力,但是笔者在调用时,仍有些问题,这里暂时就不详细说明了。

Freeze Model模型导出:

查看模型实际的效果前,我们需要把训练的过程文件导出,生产.pb的模型文件。本来,tensorflow/python/tools/freeze_graph.py提供了freeze model的api,但是需要提供输出的final node names(一般是softmax之类的最后一层的激活函数命名),而object detection api提供提供了预训练好的网络,final node name并不好找,所以object_detection目录下还提供了export_inference_graph.py。

复制代码
python export_inference_graph.py \
--input_type image_tensor
--pipeline_config_path D:/training-sets /data-translate/training/ssd_mobilenet_v1_pets.config \
--trained_checkpoint_prefix D:/training-sets /data-translate/training/ssd_mobilenet_v1_pets.config /model.ckpt-* \
--output_directory D:/training-sets /data-translate/training/result
复制代码

 

导出完成后,在output_directory下,会生成frozen_inference_graph.pb、model.ckpt.data-00000-of-00001、model.ckpt.meta、model.ckpt.data文件。

调用生成模型:

目录下本身有一个调用的例子,稍微改造如下:

复制代码
import cv2
import numpy as np
import tensorflow as tf
from object_detection.utils import label_map_util
from object_detection.utils import visualization_utils as vis_util


class TOD(object):
    def __init__(self):
        self.PATH_TO_CKPT = r'D:\lib\tf-model\models-master\object_detection\training\frozen_inference_graph.pb'
        self.PATH_TO_LABELS = r'D:\lib\tf-model\models-master\object_detection\training\sunglasses_label_map.pbtxt'
        self.NUM_CLASSES = 1
        self.detection_graph = self._load_model()
        self.category_index = self._load_label_map()

    def _load_model(self):
        detection_graph = tf.Graph()
        with detection_graph.as_default():
            od_graph_def = tf.GraphDef()
            with tf.gfile.GFile(self.PATH_TO_CKPT, 'rb') as fid:
                serialized_graph = fid.read()
                od_graph_def.ParseFromString(serialized_graph)
                tf.import_graph_def(od_graph_def, name='')
        return detection_graph

    def _load_label_map(self):
        label_map = label_map_util.load_labelmap(self.PATH_TO_LABELS)
        categories = label_map_util.convert_label_map_to_categories(label_map,
                                                                    max_num_classes=self.NUM_CLASSES,
                                                                    use_display_name=True)
        category_index = label_map_util.create_category_index(categories)
        return category_index

    def detect(self, image):
        with self.detection_graph.as_default():
            with tf.Session(graph=self.detection_graph) as sess:
                # Expand dimensions since the model expects images to have shape: [1, None, None, 3]
                image_np_expanded = np.expand_dims(image, axis=0)
                image_tensor = self.detection_graph.get_tensor_by_name('image_tensor:0')
                boxes = self.detection_graph.get_tensor_by_name('detection_boxes:0')
                scores = self.detection_graph.get_tensor_by_name('detection_scores:0')
                classes = self.detection_graph.get_tensor_by_name('detection_classes:0')
                num_detections = self.detection_graph.get_tensor_by_name('num_detections:0')
                # Actual detection.
                (boxes, scores, classes, num_detections) = sess.run(
                    [boxes, scores, classes, num_detections],
                    feed_dict={image_tensor: image_np_expanded})
                # Visualization of the results of a detection.
                vis_util.visualize_boxes_and_labels_on_image_array(
                    image,
                    np.squeeze(boxes),
                    np.squeeze(classes).astype(np.int32),
                    np.squeeze(scores),
                    self.category_index,
                    use_normalized_coordinates=True,
                    line_thickness=8)

        cv2.namedWindow("detection", cv2.WINDOW_NORMAL)
        cv2.imshow("detection", image)
        cv2.waitKey(0)

if __name__ == '__main__':
    image = cv2.imread('image.jpg')
    detecotr = TOD()
    detecotr.detect(image)
复制代码

 

下面是一些图片的识别效果:

相关阅读

当强化学习遇见泛函分析

google cloud :穷人也能玩深度学习

[ I am Jarvis ] :聊聊 FaceID 背后的深度学习视觉算法

此文已由作者授权腾讯云技术社区发布,转载请注明文章出处
原文链接:https://cloud.tencent.com/community/article/351424

SSM框架整合项目 :租房管理系统 – 谁将新樽辞旧月,今月曾经照古人 – 博客园

mikel阅读(9)

来源: SSM框架整合项目 :租房管理系统 – 谁将新樽辞旧月,今月曾经照古人 – 博客园

使用ssm框架整合,oracle数据库

框架:

Spring

SpringMVC

MyBatis

导包:

1, spring

2, MyBatis

3, mybatis-spring

4, fastjson

5, aspectweaver—-AspectJ框架

6, log4j—–打印日志信息

7, ojdbc6.jar

8, jstl.jar, standard.jar—-标准标签库

9, commons-logging-1.2.jar

10,……

项目结构:

配置文件同前面:http://www.cnblogs.com/jiangwz/p/7674275.html

项目代码:

model:

复制代码
 1 package com.hanqi.model;
 2 
 3 public class House {
 4     
 5     private Integer id;
 6     private String keyword;
 7     private String area;
 8     private Integer squaremeter;
 9     private Integer rent;
10     private String renttype;
11     private String housetype;
12 
13     public House(Integer id, String keyword, String area, Integer squaremeter, Integer rent, String renttype,
14             String housetype) {
15         super();
16         this.id = id;
17         this.keyword = keyword;
18         this.area = area;
19         this.squaremeter = squaremeter;
20         this.rent = rent;
21         this.renttype = renttype;
22         this.housetype = housetype;
23     }
24 
25     public House() {
26         super();
27         // TODO Auto-generated constructor stub
28     }
29 
30     public Integer getId() {
31         return id;
32     }
33 
34     public void setId(Integer id) {
35         this.id = id;
36     }
37 
38     public String getKeyword() {
39         return keyword;
40     }
41 
42     public void setKeyword(String keyword) {
43         this.keyword = keyword;
44     }
45 
46     public String getArea() {
47         return area;
48     }
49 
50     public void setArea(String area) {
51         this.area = area;
52     }
53 
54     public Integer getSquaremeter() {
55         return squaremeter;
56     }
57 
58     public void setSquaremeter(Integer squaremeter) {
59         this.squaremeter = squaremeter;
60     }
61 
62     public Integer getRent() {
63         return rent;
64     }
65 
66     public void setRent(Integer rent) {
67         this.rent = rent;
68     }
69 
70     public String getRenttype() {
71         return renttype;
72     }
73 
74     public void setRenttype(String renttype) {
75         this.renttype = renttype;
76     }
77 
78     public String getHousetype() {
79         return housetype;
80     }
81 
82     public void setHousetype(String housetype) {
83         this.housetype = housetype;
84     }
85 
86     @Override
87     public String toString() {
88         return "House [id=" + id + ", keyword=" + keyword + ", area=" + area + ", SQUAREMETER=" + squaremeter
89                 + ", rent=" + rent + ", renttype=" + renttype + ", housetype=" + housetype + "]";
90     }
91 
92 }
复制代码

dao层:

复制代码
 1 package com.hanqi.dao;
 2 
 3 import java.util.List;
 4 
 5 import com.hanqi.model.House;
 6 
 7 public interface HouseDao {
 8 
 9     List<House> selectAll();
10     
11     int inserthouse(House house);
12 
13     int delhouse(Integer id);
14 
15     int updatehouse(House house);
16 
17     List<House> selectinfo(House house);
18 
19 }
复制代码

dao层实现方法:

复制代码
 1 <?xml version="1.0" encoding="UTF-8" ?>
 2 <!DOCTYPE mapper
 3   PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 4   "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 5 <mapper namespace="com.hanqi.dao.HouseDao">
 6 
 7     <select id="selectAll" resultType="House">
 8         select t.*from TABLE_HOUSE t
 9     </select>
10     
11     <insert id="inserthouse" parameterType="House" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
12         insert into TABLE_HOUSE values(test1.nextval,#{keyword},#{area},#{squaremeter},#{rent},#{renttype},#{housetype})
13     </insert>
14     
15     <delete id="delhouse" parameterType="Map">
16         delete TABLE_HOUSE t where t.id=#{id}
17     </delete>
18     
19     <update id="updatehouse" parameterType="Map">
20         update TABLE_HOUSE t set t.keyword=#{keyword},t.area=#{area},t.squaremeter=#{squaremeter},t.rent=#{rent},t.renttype=#{renttype},t.housetype=#{housetype} where t.id=#{id}
21     </update>
22     
23     <select id="selectinfo" resultType="House" parameterType="House">
24         select t.* from TABLE_HOUSE t
25         <where>
26             <if test="keyword!=null">
27                 and t.keyword like #{keyword}
28             </if>
29             <if test="area!=null">
30                 and t.area=#{area}
31             </if>
32             <if test="renttype!=null">
33                 and t.renttype in #{renttype}
34             </if>
35             <if test="housetype!=null">
36                 and t.housetype=#{housetype}
37             </if>
38         </where>
39         
40     </select>
41 </mapper>
复制代码

controller控制器:

复制代码
 1 package com.hanqi.controller;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 
 6 import org.springframework.beans.factory.annotation.Autowired;
 7 import org.springframework.stereotype.Controller;
 8 import org.springframework.ui.Model;
 9 import org.springframework.web.bind.annotation.RequestMapping;
10 import org.springframework.web.bind.annotation.ResponseBody;
11 import org.springframework.web.bind.annotation.SessionAttributes;
12 import org.springframework.web.servlet.ModelAndView;
13 
14 import com.alibaba.fastjson.JSONObject;
15 import com.hanqi.dao.HouseDao;
16 import com.hanqi.model.House;
17 
18 @Controller
19 @SessionAttributes("currentUser")
20 @RequestMapping("/house")
21 public class HouseController {
22 
23     @Autowired
24     private HouseDao houseDao;
25     
26     @ResponseBody
27     @RequestMapping("/selectAll")
28     public JSONObject selectAll() {
29         JSONObject jo = new JSONObject();
30         List<House> list = houseDao.selectAll();
31         jo.put("total", list.size());
32         jo.put("rows", list);
33         
34         return jo;
35     }
36     
37     @ResponseBody
38     @RequestMapping("/selectinfo")
39     public ModelAndView  selectinfo(House house){
40         
41         house.setKeyword("%"+house.getKeyword()+"%");
42         List<House> list = houseDao.selectinfo(house);
43         ModelAndView modelAndView = new ModelAndView();
44         modelAndView.setViewName("houselook3");
45         modelAndView.addObject("list",list);
46 
47         return modelAndView;
48         
49     }
50     
51     @ResponseBody
52     @RequestMapping("/selectAll1")
53     public ModelAndView selectAll1(Model model) {
54         //List<House> list = houseDao.selectAll();
55         //System.out.println(list);
56         
57         //model.addAttribute("list", list);
58 
59         ModelAndView mv = new ModelAndView("redirect:/houselook.jsp");
60         return mv;
61         
62     }
63 
64     @RequestMapping("/selectAll2")
65     public String selectAll2(Model model) {
66         List<House> list = houseDao.selectAll();
67         System.out.println(list);
68         model.addAttribute("list", list);
69 
70         return "houselook2";
71     }
72     
73     @ResponseBody
74     @RequestMapping("/addhouse")
75     public ModelAndView addhouse(House house) {
76         int i=houseDao.inserthouse(house);
77         
78         ModelAndView mv = new ModelAndView("redirect:/index.jsp");
79         return mv;
80         
81     }
82     
83     @ResponseBody
84     @RequestMapping("/delhouse")
85     public ModelAndView delhouse(Integer id) {
86         int i=houseDao.delhouse(id);
87         ModelAndView mv = new ModelAndView("redirect:/index.jsp");
88         return mv;
89     }
90     
91     @ResponseBody
92     @RequestMapping("/updatehouse")
93     public ModelAndView updatehouse(House house) {
94         int i=houseDao.updatehouse(house);
95         ModelAndView mv = new ModelAndView("redirect:/index.jsp");
96         return mv;
97     }
98     
99 }
复制代码

前台:

复制代码
  1 <%@ page language="java" contentType="text/html; charset=UTF-8"
  2     pageEncoding="UTF-8"
  3     %>
  4 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
  5 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
  6 <html>
  7 <head>
  8 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  9 <script type="text/javascript" src="js/jquery-3.2.1.min.js"></script>
 10 <script type="text/javascript"
 11     src="jquery-easyui-1.5.1/jquery.easyui.min.js"></script>
 12     <link rel="shortcut icon" href="img/logo1.jpg"/>
 13 <link type="text/css" rel="stylesheet"
 14     href="jquery-easyui-1.5.1/themes/icon.css"></link>
 15 <link type="text/css" rel="stylesheet"
 16     href="jquery-easyui-1.5.1/themes/default/easyui.css"></link>
 17 <script type="text/javascript"
 18     src="jquery-easyui-1.5.1/locale/easyui-lang-zh_CN.js"></script>
 19 <title>租房管理</title>
 20 <%
 21     String basePath = request.getContextPath();
 22 %>
 23 <!-- <script type="text/javascript" src="js/index.js"></script> -->
 24 <style type="text/css">
 25 .datagrid-btable tr {
 26     height: 30px;
 27 }
 28 </style>
 29 </head>
 30 
 31 <body class="easyui-layout">
 32     <!-- 添加商品 -->
 33     <div data-options="region:'north',split:true"
 34         style="height: 50px; background-color: cornflowerblue">
 35         
 36     </div>
 37     <!-- 对话框开始 -->
 38     <div data-options="region:'center',split:true"
 39         style="padding: 5px; background: #eee">
 40         <div id="tabs" class="easyui-tabs" style="width: 100%; height: 100%;">
 41             <div title="主页" style="">
 42                 <table id="table"></table>
 43                 <!-- 添加的表单 -->
 44                 <div id="zhong" style="display: none">
 45                     <form id="addhouse" method="post"
 46                         style="width: 600px; padding: 20px">
 47                         关键字:<input type="text" name="keyword"><br>
 48                         地区:<input type="text" name="area"><br>
 49                         面积:<input type="text" name="squaremeter"><br>
 50                         租金:<input type="text" name="rent"><br>
 51                         租赁方式:<input type="text" name="renttype"><br>
 52                         房屋类型:<input type="text" name="housetype"><br>
 53                         <input type="submit" name="" id="" value="提交" /><br>
 54                         <input type="reset" value="重置"><br>
 55                     </form>
 56                 </div>
 57                 <!-- 修改的表单 -->
 58                 <div id="gai" style="display: none">
 59                     <form id="gaihouse" action="house/updatehouse.do" method="post"
 60                         style="width: 600px; padding: 20px">
 61                         id:<input type="text" name="id"><br>
 62                         关键字:<input type="text" name="keyword"><br>
 63                         地区:<input type="text" name="area"><br>
 64                         面积:<input type="text" name="squaremeter"><br>
 65                         租金:<input type="text" name="rent"><br>
 66                         租赁方式:<input type="text" name="renttype"><br>
 67                         房屋类型:<input type="text" name="housetype"><br>
 68                         <input type="submit" name="" id="" value="提交" /><br>
 69                         <input type="reset" value="重置"><br>
 70                     </form>
 71                 </div>
 72             </div>
 73 
 74         </div>
 75     </div>
 76     <!-- 对话框结束 -->
 77     <!-- 目录开始 -->
 78     <div data-options="region:'west',split:true" width=210>
 79         <div id="aa" class="easyui-accordion"
 80             style="width: 200px; height: 543px">
 81             
 82             <div title="用户管理" style="overflow: auto; padding: 10px" >
 83                 <ul>
 84                     <li class="lis"><a id="addhouse" class="easyui-linkbutton ab"
 85                         plain="true" >添加房屋信息(先用右边按钮)</a></li>
 86                     <li class="lis"><a href="<%=basePath %>/houselook.jsp" class="easyui-linkbutton ab"
 87                         plain="true">查看租房信息2</a></li>
 88                     <li class="lis"><a href="<%=basePath %>/house/selectAll2.do" class="easyui-linkbutton ab"
 89                         plain="true">查看租房信息3</a></li>
 90                     <li class="lis"><a href="houselook3.jsp" class="easyui-linkbutton ab"
 91                         plain="true">前往租房页面</a></li>
 92                     <li class="lis"><a href="#" class="easyui-linkbutton ab"
 93                         plain="true">修改用户</a></li>
 94                 </ul>
 95             </div>
 96         </div>
 97     </div>
 98     <!-- 底部声明 -->
 99     <div data-options="region:'south',split:true"
100         style="height: 40px; line-height: 40px; vertical-align: center; text-align: center;">
101         玛雅网络版权声明</div>
102     <!-- 目录结束 -->
103 </body>
104 </html>
105 <script type="text/javascript">
106 
107     $(function() {
108         $('#addhouse').form({    
109             url:'house/addhouse.do',
110             onSubmit: function(){
111                 return $('#addhouse').form('validate');//如果有为空则返回false阻止提交
112             },
113             success:function(data){    
114                 if(data=="true"){
115                     alert("添加成功");
116                 }else if(data=="false"){
117                     alert("请检查信息正确!");
118                 }
119             }    
120         });
121         
122         $('#table').datagrid({    
123             url : 'house/selectAll.do',
124             striped:true,//显示斑马线
125             autoRowHeight:false,//定义设置行的高度,根据该行的内容。设置为false可以提高负载性能。这里不设置,css中设置的行高无效
126             singleSelect:true,//只允许选择一行
127             pagination : true,
128             pageNumber : 1,
129             pageSize : 1,
130             pageList : [ 1, 3, 5 ],
131             
132             toolbar : [{
133                 iconCls : 'icon-edit',
134                 text : "添加",
135                 handler : function() {    
136                     var a = $(this).text();
137                     
138                     $('#zhong').dialog({
139                         width : 800,
140                         height : 500,
141                         title : a,
142                         //closed : false,
143                         cache : false,
144                         modal : true
145                     });
146                     
147                     
148                 }
149             },  '-',{
150                 iconCls : 'icon-edit',
151                 text : "修改",
152                 handler : function() {
153                     var a = $(this).text();
154                     $('#gai').dialog({
155                         width : 800,
156                         height : 500,
157                         title : a,
158                         //closed : false,
159                         cache : false,
160                         modal : true
161                     });
162                     $('#gai').dialog("open");
163                     var r = $("#table").datagrid("getSelected");//获取被选中的行,返回对象
164                     $("#gaihouse").form("load", r);//将被选中的信息放到弹出的的表单中,富文本编辑器的内容无法显示
165                 }
166             }, '-',
167             {
168                 iconCls : 'icon-cancel',
169                 text : "删除",
170                 handler : function() {
171                     var id=-1;
172                     id = $('#table').datagrid("getSelected").id;
173                     if(id>-1){
174                         var r1 = confirm("确定删除编号为  "+id+" 的房屋信息吗?");
175                         if(r1) {
176                             window.location.href="house/delhouse.do?id="+id;
177                             alert("删除成功");
178                         }
179                     }else{
180                         alert("请选中需要删除的商品");
181                     }
182                     
183                 }
184             } ],
185 
186              frozenColumns : [ [ {
187                  field : '',
188                 title : '',
189                 width : 20,
190                 checkbox : true
191             } ] ], 
192             columns : [ [ {
193                 field : "id",
194                 title : "信息编号",
195                 width:65
196             },{
197                 field : "keyword",
198                 title : "关键字",
199                 width:180
200             },  
201             {
202                 field : "area",
203                 title : "地区",
204                 width:60
205             }, {
206                 field : "squaremeter",
207                 title : "面积",
208                 width:60
209             }, {
210                 field : "rent",
211                 title : "租金",
212                 width:40
213             } , {
214                 field : "renttype",
215                 title : "租赁方式",
216                 width:60
217             } ,{
218                 field : "housetype",
219                 title : "房屋类型",
220                 width : 60
221             } ] ],
222             
223         }); 
224     });
225 </script>
复制代码
复制代码
 1  <%@ page language="java" contentType="text/html; charset=UTF-8"
 2     pageEncoding="UTF-8" import="java.util.List,com.hanqi.model.House,com.hanqi.controller.HouseController"%>
 3 <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
 4 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
 5 <html>
 6 <head>
 7 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 8 <title>Insert title here</title>
 9 <script type="text/javascript" src="js/jquery-3.2.1.min.js"></script>
10 <script type="text/javascript"
11     src="jquery-easyui-1.5.1/jquery.easyui.min.js"></script>
12     <link rel="shortcut icon" href="img/logo1.jpg"/>
13 <link type="text/css" rel="stylesheet"
14     href="jquery-easyui-1.5.1/themes/icon.css"></link>
15 <link type="text/css" rel="stylesheet"
16     href="jquery-easyui-1.5.1/themes/default/easyui.css"></link>
17 <script type="text/javascript"
18     src="jquery-easyui-1.5.1/locale/easyui-lang-zh_CN.js"></script>
19 </head>
20 <body>
21 <form action="house/selectinfo.do" method="post">
22     区域:<input type="radio" name="area" id="s1" value="张店"/>
23     <label for="s1">张店</label>
24   <input type="radio" name="area" id="s2" value="淄川"/>
25     <label for="s2">淄川</label>
26   <input type="radio" name="area" id="s0" value="周村"/>
27     <label for="s0">周村</label><br>
28     租赁类型:<input type="checkbox" name="renttype" id="s3" value="整租"/>
29     <label for="s3">整租</label>
30   <input type="checkbox" name="renttype" id="s4" value="合租"/>
31     <label for="s4">合租</label>
32   <input type="checkbox" name="renttype" id="s5" value="其他"/>
33     <label for="s5">其他</label><br>
34     房屋类型:<input type="radio" name="housetype" id="s6" value="三室一厅"/>
35     <label for="s6">三室一厅</label>
36   <input type="radio" name="housetype" id="s7" value="自建房"/>
37     <label for="s7">自建房</label>
38   <input type="radio" name="housetype" id="s8" value="其他"/>
39     <label for="s8">其他</label><br>
40     关键字:<input type="text" name="keyword">
41     <input type="submit" value="查询">
42 </form>
43 
44 <%    
45     //HouseController hc=new HouseController();
46     List<House> list=(List<House>)request.getAttribute("list");
47     if(list!=null){
48         out.print("<table border='1'>");
49         for(House h:list){
50             out.print("<tr>");
51             out.print("<td>"+h.getKeyword()+"</td>");
52             out.print("<td>"+h.getArea()+"</td>");
53             out.print("<td>"+h.getSquaremeter()+"</td>");
54             out.print("<td>"+h.getRent()+"</td>");
55             out.print("<td>"+h.getRenttype()+"</td>");
56             out.print("<td>"+h.getHousetype()+"</td>");
57             out.print("</tr>");
58         }
59         out.print("</table>");
60     }
61 %>
62 </body>
63 </html>
复制代码

阿里巴巴 Java 开发规约插件初体验

mikel阅读(10)

又一次来谈《阿里巴巴 Java 开发手册》,经过这大半年的版本迭代,这本阿里工程师们总结出来避免写出那么多 Bug 的规范,对于 Java 开发者简直就是必备开发利剑了。

针对这个手册,我之前也简单写过两篇简单的解读,手册的确是清晰的说明了日常开发中很容易踩的坑,我不要脸的把链接放一下:

这一次,阿里巴巴于 10 月 14 日在杭州云栖大会上,正式发布众所期待的《阿里巴巴 Java 开发规约》扫描插件!

简单了解一下这插件

该插件由阿里巴巴 P3C 项目组研发。P3C 是世界知名的反潜机,专门对付水下潜水艇,寓意是扫描出所有潜在的代码隐患。

代码已经开源,GitHub:https://github.com/alibaba/p3c
阿里介绍文章:https://mp.weixin.qq.com/s/IbibsXlWHlM59kfXJqRvZA#rd

据说插件支持了IDEA、Eclipse,在扫描代码后,将不符合规约的代码显示出来,甚至在 IDEA 上,我们还基于 Inspection 机制提供了实时检测功能,编写代码的同时也能快速发现问题所在,还实现了批量一键修复的功能。

那我就在 IDEA 下试一下吧。

IDEA 下如何使用?

1.打开 IDEA ,选择 File – Settings – Plugins – Browse repositories
打开 IDEA,选择 File – Settings – Plugins – Browse repositories 后,输入 alibaba 选中 Alibaba Java Coding Guidelines,点击 Install,截至目前已经快有了三千的安装量了。

alibaba guidelines

2.重启IDEA,开始使用
安装后,会提示你重启 IDEA 已启用插件,重启后就可以在你的项目上疯狂点击右键吧,当然菜单栏也会出现俩按钮,它有个快捷键是Ctrl+Shift+Alt+J,你也可以试试看。

checkStart

当然,选中某一个类,或者在这个类里边右键也可以

checkStartClass

3.扫描出坏代码
扫描代码后,将不符合规约的代码按 Blocker/Critical/Major 三个等级显示在下方,双击可以定位至代码处,右侧窗口还有针对代码的批量修复功能,简直不能更 666 了。

bad code

稍微解释下Blocker/Critical/Major三个等级,在 Snoar 中对代码规则有五个级别,这是前三个,翻译下就是:崩溃/严重/重要 ,也就是说前两级别是必须要处理掉的。

Eclipse 如何安装插件?

Eclipse 版插件支持4.2(Juno,JDK1.8+)及以上版本,我们提供自主的Update Site,通过 Help >> Install New Software 然后输入https://p3c.alibaba.com/plugin/eclipse/update
即可看到安装列表。大家可以通过 Help >> Check for Udates 进行插件新版检测

我没有在 Eclipse 上体验,你们有 Eclipse 的可以试试看。

总结一下

这个插件可以说对规范代码有重大意义,可以使我们少写一些 Bug,少抛一些异常,少踩一些坑,但真正要写出健壮代码最重要的还在于我们自己,这个插件还存在很多问题,一些容易产生 NPE 的级联调用、空对象什么的,好像它并不能检查出来,依赖工具永远是不可能解决所有问题的。

当然,它的中文版对我们是无比友好的。

一张图理清ASP.NET Core启动流程 – 『圣杰』 – 博客园

mikel阅读(18)

来源: 一张图理清ASP.NET Core启动流程 – 『圣杰』 – 博客园

1. 引言

对于ASP.NET Core应用程序来说,我们要记住非常重要的一点是:其本质上是一个独立的控制台应用,它并不是必需在IIS内部托管且并不需要IIS来启动运行(而这正是ASP.NET Core跨平台的基石)。ASP.NET Core应用程序拥有一个内置的Self-Hosted(自托管)Web Server(Web服务器),用来处理外部请求。

不管是托管还是自托管,都离不开Host(宿主)。在ASP.NET Core应用中通过配置并启动一个Host来完成应用程序的启动和其生命周期的管理(如下图所示)。而Host的主要的职责就是Web Server的配置和Pilpeline(请求处理管道)的构建。

这张图描述了一个总体的启动流程,从上图中我们知道ASP.NET Core应用程序的启动主要包含三个步骤:

  1. CreateDefaultBuilder():创建IWebHostBuilder
  2. Build():IWebHostBuilder负责创建IWebHost
  3. Run():启动IWebHost

所以,ASP.NET Core应用的启动本质上是启动作为宿主的WebHost对象。
其主要涉及到两个关键对象IWebHostBuilderIWebHost,它们的内部实现是ASP.NET Core应用的核心所在。下面我们就结合源码并梳理调用堆栈来一探究竟!

2. 宿主构造器:IWebHostBuilder

在启动IWebHost宿主之前,我们需要完成对IWebHost的创建和配置。而这一项工作需要借助IWebHostBuilder对象来完成的,ASP.NET Core中提供了默认实现WebHostBuilder。而WebHostBuilder是由WebHost的同名工具类(Microsoft.AspNetCore命名空间下)中的CreateDefaultBuilder方法创建的。

从上图中我们可以看出CreateDefaultBuilder()方法主要干了六件大事:

  1. UseKestrel:使用Kestrel作为Web server。
  2. UseContentRoot:指定Web host使用的content root(内容根目录),比如Views。默认为当前应用程序根目录。
  3. ConfigureAppConfiguration:设置当前应用程序配置。主要是读取 appsettinggs.json 配置文件、开发环境中配置的UserSecrets、添加环境变量和命令行参数 。
  4. ConfigureLogging:读取配置文件中的Logging节点,配置日志系统。
  5. UseIISIntegration:使用IISIntegration 中间件。
  6. UseDefaultServiceProvider:设置默认的依赖注入容器。

创建完毕WebHostBuilder后,通过调用UseStartup()来指定启动类,来为后续服务的注册及中间件的注册提供入口。

3. 宿主:IWebHost

在ASP.Net Core中定义了IWebHost用来表示Web应用的宿主,并提供了一个默认实现WebHost。宿主的创建是通过调用IWebHostBuilderBuild()方法来完成的。那该方法主要做了哪些事情呢,我们来看下面这张【ASP.NET Core启动流程调用堆栈】中的黄色边框部分:

其核心主要在于WebHost的创建,又可以划分为三个部分:

  1. 构建依赖注入容器,初始通用服务的注册:BuildCommonService();
  2. 实例化WebHost:var host = new WebHost(…);
  3. 初始化WebHost,也就是构建由中间件组成的请求处理管道:host.Initialize();

3.1. 注册初始通用服务

BuildBuildCommonService方法主要做了两件事:

  1. 查找HostingStartupAttribute特性以应用其他程序集中的启动配置
  2. 注册通用服务
  3. 若配置了启动程序集,则发现并以IStartup类型注入到IOC容器中

3.2. 创建IWebHost

public IWebHost Build()
{
    //省略部分代码

    var host = new WebHost(
        applicationServices,
        hostingServiceProvider,
        _options,
        _config,
        hostingStartupErrors);
    }
    
    host.Initialize();

    return host;
}

3.3. 构建请求处理管道

请求管道的构建,主要是中间件之间的衔接处理。

而请求处理管道的构建,又包含三个主要部分:

  1. 注册Startup中绑定的服务;
  2. 配置IServer;
  3. 构建管道

请求管道的构建主要是借助于IApplicationBuilder,相关类图如下:

4. 启动WebHost

WebHost的启动主要分为两步:

  1. 再次确认请求管道正确创建
  2. 启动Server以监听请求
  3. 启动 HostedService

4.1. 确认请求管道的创建

从图中可以看出,第一步调用Initialize()方法主要是取保请求管道的正确创建。其内部主要是对BuildApplication()方法的调用,与我们上面所讲WebHost的构建环节具有相同的调用堆栈。而最终返回的正是由中间件衔接而成的RequestDelegate类型代表的请求管道。

4.2. 启动Server

我们先来看下类图:

从类图中我们可以看出IServer接口主要定义了一个只读的特性集合属性、一个启动和停止的方法声明。在创建宿主构造器IWebHostBuilder时我们通过调用UseKestrel()方法指定了使用KestrelServer作为默认的IServer实现。其方法申明中接收了一个IHttpApplication<TContext> application的参数,从命名来看,它代表一个Http应用程序,我们来看下具体的接口定义:

其主要定义了三个方法,第一个方法用来创建请求上下文;第二个方法用来处理请求;第三个方法用来释放上下文。而至于请求上下文,是用来携带请求和返回响应的核心参数,其贯穿与整个请求处理管道之中。ASP.NET Core中提供了默认的实现HostingApplication,其构造函数接收一个RequestDelegate _application(也就是链接中间件形成的处理管道)用来处理请求。

var httpContextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>();
var hostingApp = new HostingApplication(_application, _logger, diagnosticSource, httpContextFactory);

4.3. 启动IHostedService

IHostedService接口用来定义后台任务,通过实现该接口并注册到Ioc容器中,它会随着ASP.NET Core 程序启动而启动,终止而终止。

5. 总结

结合源码,通过对ASP.NET Core运行调用堆栈的梳理,其启动流程的总体脉络一目了然,并且了解到主要的几个关键对象:

  1. 负责创建IWebHost的宿主构造器IWebHostBuilder
  2. 代表宿主的IWebHost接口
  3. 用于构建请求管道的IApplicationBuilder
  4. 中间件衔接而成的RequestDelegate
  5. 代表Web Server的IServer接口
  6. 贯穿请求处理管道的请求上下文HttpContext
  7. 可以用来注册后台服务的IHostedService接口

这一节就先从总体上对ASP.NET Core的运行原理有个基本的认识,后续我们再一一讲解这几个核心对象来加深理解。

谈谈数据监听observable的实现 – 萧秦 – 博客园

mikel阅读(25)

来源: 谈谈数据监听observable的实现 – 萧秦 – 博客园

一、概述

数据监听实现上就是当数据变化时会通知我们的监听器去更新所有的订阅处理,如:

var vm = new Observer({a:{b:{x:1,y:2}}});
vm.watch('a.b.x',function(newVal,oldVal){
    console.log(arguments);
});
vm.a.b.x = 11; //触发watcher执行 输出 11 1

数据监听是对观察者模式的实现,也是MVVM中的核心功能。这个功能我们在很多场景中都可以用到,可以大大的简化我们的代码。

二、现有MVVM框架中的Observable是怎么实现的

先看看各MVVM框架对Observable是怎么实现的,我们分析下它们的实现原理,常见的MVVM框架有以下几种:
1、knockout,老牌的MVVM实现

<p>First name: <input data-bind="value: firstName" /></p>
<p>Last name: <input data-bind="value: lastName" /></p>
<h2>Hello, <span data-bind="text: fullName"> </span>!</h2>
var ViewModel = function(first, last) {
    this.firstName = ko.observable(first);
    this.lastName = ko.observable(last);
 
    this.fullName = ko.pureComputed(function() {
        return this.firstName() + " " + this.lastName();
    }, this);
};
 
ko.applyBindings(new ViewModel("Planet", "Earth")); 

早期微软是把每个属性转换成一个observable函数,通过函数对该属性进行取值赋值来实现的,缺点是改变了原属性,不能够像属性一样取值赋值。

2、avalon,国产框架特点是兼容IE6+

<div ms-controller="box">
    <div style=" background: #a9ea00;" ms-css-width="w" ms-css-height="h"  ms-click="click"></div>
    <p>{{ w }} x {{ h }}</p>
    <p>W: <input type="text" ms-duplex="w" data-duplex-event="change"/></p>
    <p>H: <input type="text" ms-duplex="h" /></p>
</div>
var vm = avalon.define({
 $id: "box",
  w: 100,
  h: 100,
  click: function() {
    vm.w = parseFloat(vm.w) + 10;
    vm.h = parseFloat(vm.h) + 10;
  }
});
avalon.scan()

avalon对数据监听堪称司徒的黑魔法,IE9+时利用ES5的defineProperty/defineProperties去实现,当IE不支持此方法时利用vbscript来实现。缺点是vbs定义后的对象不能够动态增删属性。

3、angular,大而全的mvvm解决方案

<div ng-app="myApp" ng-controller="myCtrl">
名: <input type="text" ng-model="firstName"><br>
姓: <input type="text" ng-model="lastName"><br>
<br>
姓名: {{firstName + " " + lastName}}
</div>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
    $scope.firstName = "John";
    $scope.lastName = "Doe";
});

ng对数据监听的实现,采用了AOP的编程思维,它对常用的dom事件xhr事件等进行封装,当这些事件被触发发,封装的方法中有去调用ng的digest流程,在此流程去检测数据变化并通知所有订阅,所以我们导致使用原生的setTimeout代替$timeout后需要自已去执行执行$digest()$apply(),缺点是需要对使用到的所有外部事件进行封装。

4、vue,现代小巧优雅(实际上是比avalon大一些)

<div id="demo">
  <p>{{message}}</p>
  <input v-model="message">
</div>
var demo = new Vue({
  el: '#demo',
  data: {
    message: 'Hello Vue.js!'
  }
})

vue对数据监听的实现就比较单一了,因为它只支持IE9+,利用Object.defineProperty一招搞定。缺点是不兼容低版本IE。

三、Observable的实现有哪些方法及思路

通过上面几个框架对比我们可以看出几种不同数据监听的实现方法,实际上还有很多的方式可以去实现的:
1、把属性转换为函数(knockout
2、IE9+使用defineProperty/definePropertiesvueavalon
3、低版本IE使用VBS(avalon
4、数据检测,对各事件进行封装,在封装的方法中调用digest(angular
5、利用__defineGetter__/__defineSetter__方法(avalon
6、把数据转换成dom对象利用IE8 dom对象的defineProperty方法或onpropertychange事件
7、利用Object.observe方法
8、利用ES6的Proxy对象
9、利用setInterval进行脏检测

那么我们就具体看下这些数据监听实现:
1、利用函数转换如ko.observable(),兼容所有

function observable(val){
    return function(newVal){
        if (arguments.length > 0){
            val = newVal;
            notifyChanges();
        }else{
            return val;
        }
    }
}
var data = {};
var data.a = observable(1);
var value = data.a() //取值
data.a(2); //赋值

2、利用defineProperty/defineProperties,兼容性IE9+

function defineReactive(obj, key, val){
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      return val;
    },
    set: function reactiveSetter(newVal) {
      val = newVal;
      notifyChanges();
    }
  });
}

3、利用__defineGetter__/__defineSetter__,兼容性一些mozilla内核的浏览器
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/__defineGetter__

function defineReactive(obj, key, val){
  obj.__defineGetter__(key, function() {
    return val;
  });
  obj.__defineSetter__(key, function(newVal) {
    val = newVal;
    notifyChanges();
  });
}

4、利用vbs,兼容性低版本的IE浏览器,IE11 edge不再支持(avalon
先window.execScript得到parseVB的方法

Function parseVB(code)
    ExecuteGlobal(code)
End Function
window.execScript(parseVB_Code);

然后处理好数据属性properties生成get/set方法放在accessors,并把notifyChanges放到get/set中,然后动态生成以下vbs代码

Class DefinePropertyClass
    Private [__data__], [__proxy__]
    Public Default Function [__const__](d1, p1)
        Set [__data__] = d1: set [__proxy__] = p1
        Set [__const__] = Me
    End Function
    Public Property Let [bbb](val1)
        Call [__proxy__](Me,[__data__], "bbb", val1)
    End Property
    Public Property Set [bbb](val1)
        Call [__proxy__](Me,[__data__], "bbb", val1)
    End Property
    Public Property Get [bbb]
    On Error Resume Next
        Set[bbb] = [__proxy__](Me,[__data__],"bbb")
    If Err.Number <> 0 Then
        [bbb] = [__proxy__](Me,[__data__],"bbb")
    End If
    On Error Goto 0
    End Property
    Public Property Let [ccc](val1)
        Call [__proxy__](Me,[__data__], "ccc", val1)
    End Property
    Public Property Set [ccc](val1)
        Call [__proxy__](Me,[__data__], "ccc", val1)
    End Property
    Public Property Get [ccc]
    On Error Resume Next
        Set[ccc] = [__proxy__](Me,[__data__],"ccc")
    If Err.Number <> 0 Then
        [ccc] = [__proxy__](Me,[__data__],"ccc")
    End If
    On Error Goto 0
    End Property
    Public Property Let [$model](val1)
        Call [__proxy__](Me,[__data__], "$model", val1)
    End Property
    Public Property Set [$model](val1)
        Call [__proxy__](Me,[__data__], "$model", val1)
    End Property
    Public Property Get [$model]
    On Error Resume Next
        Set[$model] = [__proxy__](Me,[__data__],"$model")
    If Err.Number <> 0 Then
        [$model] = [__proxy__](Me,[__data__],"$model")
    End If
    On Error Goto 0
    End Property
    Public [$id]
    Public [$render]
    Public [$track]
    Public [$element]
    Public [$watch]
    Public [$fire]
    Public [$events]
    Public [$skipArray]
    Public [$accessors]
    Public [$hashcode]
    Public [$run]
    Public [$wait]
    Public [hasOwnProperty]
End Class

Function DefinePropertyClassFactory(a, b)
    Dim o
    Set o = (New DefinePropertyClass)(a, b)
    Set DefinePropertyClassFactory = o;
End Function

执行以上两段vbs代码得到observable对象

window.parseVB(DefinePropertyClass_code);
window.parseVB(DefinePropertyClassFactory_code);
var vm = window.DefinePropertyClassFactory(accessors, VBMediator);

function VBMediator(instance, accessors, name, value) {
    var accessor = accessors[name]
    if (arguments.length === 4) {
        accessor.set.call(instance, value)
    } else {
        return accessor.get.call(instance)
    }
}

5、在事件中触发检测digest,兼容所有(angular
以发XMLHttpRequest 为例

  var _XMLHttpRequest = window.XMLHttpRequest;
  window.XMLHttpRequest = function(flags) {
      var req;
      req = new _XMLHttpRequest(flags);
      monitorXHR(req); //处理req绑定触发数据检测及notifyChanges处理
      return req;
  };

6、把数据转换成dom节点再利用defineProperty方法或onpropertychange事件,这种极端的办法主要是用来处理IE8的,因为IE8支持defineProperty但只有DOM元素才支持

function data2dom(obj,key,val){
    if (!obj instanceof HTMLElement){
        obj = document.createElement('i');
    }
    //defineProperty or onpropertychange handle
    defineProperty(obj,key,val); //内部处理notifyChanges
    return obj;
}

这种方法的成本开销是很大的

7、利用Object.observe,在Chrome 36 beta版本中出现,但很多浏览器还没有支持已从ES7草案中移除

var data = {};
Object.observe(data, function(changes){
    changes.forEach(function(change) {
        console.log(change.type, change.name, change.oldValue);
    });
});

8、利用ES6的Proxy对象,未来的解决方案
https://developer.mozilla.org/it/docs/Web/JavaScript/Reference/Global_Objects/Proxy

//语法
var p = new Proxy(target, handler);

//示例
let setter = {
  set: function(obj, prop, value) {
    obj[prop] = value;
    notifyChanges();
  }
};

let person = new Proxy({}, setter);
person.age = 28; //触发notifyChanges

9、利用脏检测,兼容所有,主要用于没有很好办法的情况下
利用脏检测实现Object.defineProperty方法

function PropertyChecker(obj, key, val, desc) {
   this.key = key;
   this.val = val;
   this.get = function () {
     var val = desc.get();
     if (this.val == val) {
       val = obj[key];
       if (this.val != val) {
         desc.set(val);
       }
     }
     return val;
   };
   this.set = desc.set;
}
var checkList = [];
Object.defineProperty = function (obj, key, desc) {
  var val = obj[key] = desc.value != undefined ? desc.value : desc.get();
   if (desc.get && desc.set) {
     var property = new PropertyChecker(obj, key, val, desc);
     checkList.push(property);
   }
};

function loopIE8() {
 for (var i = 0; i < checkList.length; i++) {
    var item = checkList[i];
    var val = item.get();
    if (item.val != val) {
      item.val = val;
      item.set(val);
    }
  }
}
setTimeout(function () {
  setInterval(loopIE8, 200);
}, 1000);

四、监听数组变化

实际上以面说的这些仅仅是对数据对象进行监听,而数据中还包括数组,如:

var data = {a:[1,2,3]};
data.a.push(4);

这种操作也会使数据产生了变化,但是仅对getter setter进行定义是捕捉不到这些变化的。所以我们要单独针对数组做一些observable的处理。

基本思路就是重写数组的这些方法
1、push
2、pop,
3、shift
4、 unshift
5、splice
6、sort
7、reverse

var arrayProto = Array.prototype;
var arrayMethods = Object.create(arrayProto);
var arrayKeys = Object.keys(arrayMethods);
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(function (method) {
  var original = arrayProto[method];
  def(arrayMethods, method, function mutator() {
    var i = arguments.length;
    var args = new Array(i);
    while (i--) {
      args[i] = arguments[i];
    }
    var result = original.apply(this, args);
    var inserted;
    switch (method) {
      case 'push':
        inserted = args;
        break;
      case 'unshift':
        inserted = args;
        break;
      case 'splice':
        inserted = args.slice(2);
        break;
    }
    if (inserted) observe(inserted);
    notifyChanges(); //通知变化
    return result;
  });
});
function def(obj, key, val, enumerable) {
  obj = Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  })
}
function protoAugment(target, src) {
  target.__proto__ = src;
}

function copyAugment(target, src, keys) {
  for (var i = 0, l = keys.length; i < l; i++) {
    var key = keys[i];
    def(target, key, src[key]);
  }
}

var _augmentArr = ('__proto__' in {})? protoAugment : copyAugment;
function augmentArr(arr){
  _augmentArr(arr, arrayMethods, arrayKeys);
};

使用时只需要调用augmentArr(arr)即可实现

五、数据监听存在哪些问题

目前主流的数据监听方案还是defineProperty + augmentArr的方式,已有不少的mvvm框架及一些observable类库,但是还存在一些问题:
1、所有的属性必须预先定义好

var data = new Observer({a:{b:1}});//这里没有定义a.c
data.$watch('a.c',function(newVal,oldVal){
    console.log(arguments);
});
data.a.c = 1; //此时,监听a.c的watcher是不生效的,因为没有提前定义c属性

2、属性被覆盖后监听失效

var data = new Observer({a:{b:1}});
data.$watch('a.b',function(newVal,oldVal){
    console.log(arguments);
});
data.a.b = 2; //生效
data.a = {b:3}; //此时b属性的原结构遭破坏,对b的监听失效

3、对数组元素的赋值是不会触发监听器更新的

var data = new Observer({a{c:[1,2,3]}});
data.$watch('a.c',function(newVal,oldVal){
    console.log(arguments);
});
data.a.c[1] = 22; //不会触发a.c的watcher

这个问题,不少框架中是提供了一个$set方法来赋值,这是个解决问题的办法,但是原生代码赋值仍是不生效的。

def(arrayProto, '$set', function $set(index, val) {
  if (index >= this.length) {
    this.length = Number(index) + 1;
  }
  return this.splice(index, 1, val)[0];
});

4、删除对象的属性也不会触发监听器更新

var data = new Observer({a:{b:1},c:'xyz'});
data.$watch('a',function(newVal,oldVal){
    console.log(arguments);
});
delete data.a; //不会触发a的watcher

同数组也可以父节点中定义一个$remove来实现

六、这些问题的解决方案

上述问题中:
1、第1、2其实是属于同一类的问题,就是因为这些notifyChanges直接在defineProperty时定义在属性中,当这个属性未定义或遭破坏时,那么对该属性的监听肯定是要失效的。对于这个问题的解决,我的思路是这样的

function Observer(data){
    this.data = data;
    var watches=[];
    //监听时,先把监听数据保存在该observer实例的watches中
    this.watch=function(path,subscriber,options){
        watches.push(new Watcher(path,subscriber,options));
    };
    //当publish时把watcher转换为subscriber绑定到对应的属性上
    this.publish = function(watch){
        var target = queryProperty(watch.path);
        var subscriber = new Subscriber(watch,target);
        target.ob.subscribes.add(subscriber );
    }   
}

每当重新赋新值时,会从根节点拉取watches重新publish,这样的话保证了赋新值时原来的监听数据不会被覆盖。

var ob = new Observer(data);
ob.watch('a.b',function(){
    console.log(arguments);
});

此watcher信息是保存在根节点的ob对象中,每一个object类型的属性都会对应一个ob对象,这样即使data.a = {b:123}重新赋值导致data.a.b的定义被覆盖,但是根节点并没有被覆盖,在它被得新赋值时我们可以重新调用父节点ob中的publish方法把watcher重新生效,这样的话这个问题就可以解决了。

2、第3个问题,其实很容易解决,比如vue中只需要修改一句代码就可以解决,也许是出于性能还其它的考虑它没有这么去做。即把数组的每个元素当做属性来定义

function observeArr(arr){
  for (var i = 0, l = arr.length; i < l; i++) {
    observeProperty(arr, i, arr[i]);
  }
}

3、第4个问题除了父节点中增加$remove方法我目前也没有想到什么好的办法,如果大家有什么好的想法可以跟我交流下。

七、我对数据监听的实现

既然研究了下这个领域的东西,也就顺便造了个轮子实现了一个数据observable的功能,用法大概如下:

var data = {a:{b:{x:1,y:2}},c:[1,2,3]};
var ob = new Observer(data);
data.$watch('a.b',function(){
    console.log(arguments);
},{deep:true})
data.a.b.x = 11;

主要是利用了es5的Object.defineProperty + augmentArr来实现的,代码400行左右。
https://github.com/liuhuisheng/actionjs/blob/master/src/observer.js

然后想支持下IE8写了个polifill,用脏检查实现了下
https://github.com/liuhuisheng/actionjs/blob/master/src/polifill.js

一直很懒终于总结了下做个笔记。

我的微型工作流引擎-功能解析及API设计 – 萧秦 – 博客园

mikel阅读(24)

来源: 我的微型工作流引擎-功能解析及API设计 – 萧秦 – 博客园

一、前言

上一篇我给大家介绍了我的工作流的模型和基本的设计,这篇我想详细说明下我这款工作流的功能及使用示例。这款工作流主要是面向开发者设计的,为了先让大家有个全局的认识,局部功能的设计实现就不细说了,后续有时间我会继续写文章向大家介绍。

二、功能详解及使用示例代码

1、配置流程引擎,一般在程序启动过程中调用(Global.asax.cs中)

//初始化流程引擎
BpmConfiguration
    .Instance()
    .Config(@"C:\Configration\BpmConfig.xml")
    .Start();

如果不指定配置文件,则默认从app.config或web.config中读取流程配置

//初始化流程引擎
BpmConfiguration
    .Instance()
    .Start();

当然还支持同时启动多个流程引擎,以提供SAAS程序的支持。

复制代码
//A租户引擎配置
BpmConfiguration
    .Instance("TenantA")
    .Config(@"C:\BpmConfigA.xml")
    .Start();

//B租户引擎配置
BpmConfiguration
    .Instance("TenantB")
    .Config(@"C:\BpmConfigB.xml")
    .Start();
复制代码

XML中的配置包括:数据库连接、日志配置文件、任务计划启动延时、任务计划执行周期、用户关系结构的映射、流程中变量类型的拓展等。

2、取得工作流上下文,即工作流的入口,所有的功能都集中在这个入口上。

var bpm = new BpmContext()
    .UseTransaction(true)
    .SetActor("萧秦");

当前对于不同的引擎实例,其上下文是不同的

var bpm = new BpmContext("TenantA");
var bpm = new BpmContext("TenantB");

不传构造参数时,返回的是默认实例。

3、事务支持,是否开启事务、提交、回滚。

bpm.UseTransaction(true);
bpm.Commit();
bpm.Rollback();

4、流程定义

复制代码
//新增流程定义
bpm.NewProcessDefinition("请假流程")
    .SetXmlFile(@"C:\Definition\demo1.xml")
    .SetCategory("分类1")
    .SetEffectDate(DateTime.Now)
    .SetExpireDate(DateTime.Now.AddDays(180))
    .SetMemo("memo1")
    .Create()   //创建流程定义
    .Parse()    //解析xml
复制代码

流程创建时,版本号是自动生成的,默认从1.0开始,当流程名称相同时,就会生成不同版本。
在xml中可定义不同的任务节点:开始节点、自动节点、人工节点、决策节点、发散节点、聚合节点、子流程节点、会签节点、等待节点、结束节点。
及连接任务节点的路由、人员分配情况、变量定义、事件动作等信息,可参照我上篇文章中的xml定义

//加载流程定义
var processDefinition = bpm.LoadProcessDefiniton("1");
processDefinition.Deploy();     //发布流程定义
processDefinition.Revoke();     //召回流程定义
processDefinition.Delete();     //删除流程定义

5、流程实例

复制代码
//发起流程实例
var process = bpm.NewProcessIntance("请假流程","萧秦");
process.SetVariable("project_id", 1399); //保存流程变量
process.Start();   //启动 
process.Suspend(); //挂起
process.Resume();  //恢复
process.Cancel();  //撤销
process.End();     //结束
复制代码

这里NewProcessInstance这个方法实例上有三个参数,第一个是流程定义ID,第二个是启动的业务ID,第三个是子流程的返回栈点ID,非子流程可以忽略。

//启动流程
var startTask = process.Start();
startTask.SetRecord("SO20150903001");       //保存表单数据(关联)
startTask.SetAttach("01", "02");            //保存附件信息(关联),可多个
startTask.SetVariable("var1", "value1");    //保存任务变量
startTask.Signal();                         //转交下一步

task.SetRecord用于保存当前表单数据id,数据本身保存在业务表中
task.SetAttach用于保存当前节点的附件id,附件信息则保存在附件管理表中
task.Signal流程流转的关键方法,根据流程定义触发token令牌离开动作

//审批任务
var task = bpm.LoadTaskInstance("00");
task.SetOpinion(SignResult.Passed, "我同意");  //设置审批意见
task.SetReceiver("颜经理");                    //设置下一步的审批人
task.Signal();                                //转交下一步

这里提供了SetReceiver的方法设置下一步的审批人,正常情况下流程定义中已经定义好了,是不需要再进行设置的,但是考虑在实际应用中可能会有把任务给指定领导审批的情况,在通达OA中也是可以设置下一步审批人,故添加了此方法,需要时可以调用,注意应用此方法会覆盖定义中对工作项owner属性的设置。

//任务委派
var task1 = bpm.LoadTaskInstance("01");
task1.AssignCandidate("小郑,小胡");              //添加任务候选人
task1.AssignCandidate("saler", ActorType.Role); //添加任务候选人
task1.AssignTo("李四");                         //把任务分配给李四

我的工作流中,对于工作任务只能有一个所有者(owner),一个实际操作者(actor),但可以拥有多个候选人(candidate)。
候选人是当前工作可分配的一范围限制或人员列表,owner是任务的拥有者,actor是owner考虑复杂委办关系后计算出的操作者。
task.AssignCandidate这个方法用于添加任务候选人,第二个参数是对象类型,可以直接添加一个角色、机构、用户组等。
task.AssignTo即分配任务,任务的分配状态包括以下几个状态

复制代码
public enum AssignStatus
{
    //未决
    Pending,

    //认领, 用户认领任务并接收任务输入数据
    Claim,

    //委办, 委派给另一个人(例如,经理)以代替他或她执行任务
    Depute,

    //到期, 没有在指定的时间段内处理批准任务
    Expire,

    //续订, 没有在给定的时间范围内处理此任务,则该任务将被续订,以便在另一时间段内执行
    Renew
}
复制代码

如果流程卡到某个节点很久,我们可以发催办消息
如果一个流程节点的确需要很久才能完成,我设计了一个当前工作进度汇报接口

//工作催办
task.Urge("很急,请经理尽快处理,在线等!");

//汇报当前工作进度
task.Report(0.6, "预计这个星期就能完成");

task.Urge会向任务实际拥有者发送一条催办通知,并生成催办历史。
task.Report会向任务的订阅者(所有关注当前流程任务的人)发送一条进度报告。

//查询变量
var var0 = process.GetVariable("project_id");
var var1 = task1.GetVariable("var1");
var var2 = task1.GetVariable<DateTime>("var2");
var var3 = task1.GetVariableObject("var3");

变量分为三种:
流程变量 会持久化,存在于整个的流程周期内
任务变量 会持久化,存在在当前的任务中
临时变量 不会持久化到数据库中,只存在于当前执行上下文中(executionContext)。
设置变量SetVariable 获取变量GetVariable
支持任意数据类型

6、中国特色审批方式,主要包括会签、加签(前加签、后加签、并加签)、减签、自由流
会签:一个任务由多个人参与共同做决策
加签:这个任务我自己觉得没有把握,想加入一个人或多个人跟我共同决策(在前加签顺序在当前决策者之前,后加签顺序在当前决策者之后,并加签不分顺序并行处理)
减签:跟加签相反,取消某人参与决策的资格
自由流:流程定义中没有,临时添加的动态路由直接把当前工作发送到指定的节点审批。
转会签:由单人决策的普通审批节点转成多人共同决策的会签节点,支持递归会签,即会签子节点可以继续转会签节点 。
转审批:由多人共同决策的会签节点转成单人决策的普通审批节点

在我在工作流中,会签设计了以下几个参数:
a 运行模式,并行时如发散节点,进入会签节点时会同时激活所有参与人的工作任务,串行时则有先后顺序,所以才有了前加签和后加签

复制代码
public enum RunMode
{
    //串行
    Serial,

    //并行
    Parallel
}
复制代码

b 决策模式,根据子节点的结果如何去决策会签节点

复制代码
public enum DecisionMode
{
    //主办人模式
    Sponsor, 
    
    //投票模式
    Vote, 
    
    //一票通过
    OnePass, 
    
    //一票否决
    OneVeto
}
复制代码

主办人模式:需要设置一个主办人,结果以主办人的决策为准,其它人的决策只是提供参考
投票模式:即设置一个通过的比例,由大家投票决定。支持设置每个人的投票权重。
一票通过:其实可以看作是投票模式通过率设置大于0%的一种。
一票否决:可以看作投票模式通过率设置100%的一种。
当然这里只是我把常用的几种模式列出来了,还可以自己拓展决策模式,只需要继承实现我定义的抽象类Decision即可。

c 是否等待,即还有人未表决,但目前已表决的情况已经可以确定会签结果的情况下,需不需要等待其它人表决完成后才继续转交下一步。

会签分两种,一种是流程定义中定义好的会签,一种是普通审批节点临时转成会签的。实际上中国式审批其实就是要灵活,如果在流程定义中定义好的,其实可以不用会签节点,用多个普通节点也可以去实现。会签节点的设计主要是为了转会签这个场景:就是当前普通审批节点的审批人觉得自己没有把握或者不想担责任,可以加入上级领导或其它更多的人一起来决策或提供参考意见。

示例:普通审批转会签 运行模式设置为并行,决策模式是权重投票,需要等待所有人都投票,通过线为65%

//转会签
var task2 = bpm.LoadTaskInstance("02"); 
task2.ToCountersign(RunMode.Parallel, DecisionMode.Vote, true, 0.65M);
task2.CountersignAdd(new Countersigner() { actor_id = "张三", vote_weight = 1 });
task2.CountersignAdd(new Countersigner() { actor_id = "李四", vote_weight = 0.5 });
task2.CountersignAdd(new Countersigner() { actor_id = "小五", vote_weight = 2 });

并行模式即为并加签,前加签、后加签的前提是串行模式,假设task2为串行、主办人模式、原审批人为萧秦

//前加签
task2.CountersignAddBefore("萧秦", new Countersigner() { actor_id = "张三"});

//后加签
task2.CountersignAddAfter("萧秦", new Countersigner() { actor_id = "李四", is_sponsor = true});

减签则相对比较简单了

//减签
task2.CountersignRemove("王五");

会签节点转普通审批,直接让一个人决策

//转审批
 task2.ToSinglesign("隔壁老王");

自由流模式,创建临时路直接跳转到指定节点进行审批

//自由流
var task3 = bpm.LoadTaskInstance("03");
task3.SetFreeRedirect("总经理审批");
task3.Signal();

7、回退机制
流程回退到指定节点

//流程实例指定任意节点回退
var process2 = bpm.LoadProcessInstance("02");
process2.Rollback("填写请假单");

任务实例回退到上一步

//当前工作任务回退到上一个审批节点
var task4 = bpm.LoadTaskInstance("04");
task4.Rollback();

8、工作委办
张三把某个任务直接委托给李四办理,支持递归委办关系,即张三委托给李四,李四再委托给王五,王五在委托给赵六…

bpm.NewDeputeService("张三", "李四")
   .ForTaskInstance("任务实例ID")
   .Depute();

把整个流程实例委托给李四,即此流程实例下所有的张三的任务都会委托给李四

bpm.NewDeputeService("张三", "李四")
   .ForProcessInstance("流程实例ID")
   .Depute();

把某个流程定义中的一个任务节点委托给李四,即所有的这个节点创建的所有任务实例如果是张三的任务都会委托给李四

bpm.NewDeputeService("张三", "李四")
   .ForTaskDefinition("任务定义ID")
   .Depute();

把某个流程定义委托给李四,即这个流程中创建的所有的任务实例,如果是张三的任务,在设置的生效期间中都会委托给李四

bpm.NewDeputeService("张三", "李四")
   .ForProcessDefinition("流程定义ID")
   .SetDateRange(DateTime.Now, DateTime.Now.AddDays(30)) //生效期间
   .SetMemo("这个月出差,这个流程都委托给李四代办")            //委托说明
   .Depute();

收回委托关系,只要将Revoke替换Depute动作即可

//收回委办工作
bpm.NewDeputeService("张三", "李四")
   .ForProcessInstance("流程实例ID")
   .Revoke();

9、关注订阅
这个功能跟委托相似,订阅后会收到流程动态或任务动态消息提醒,如:流程已创建、启动、挂起…,任务已创建、分配给谁、进度汇报、任务完成等等

复制代码
//关注订阅
bpm.NewSubscribeService("张三")
   .ForTaskInstance("任务实例ID")
   .Subscribe();

bpm.NewSubscribeService("张三")
   .ForProcessInstance("流程实例ID")
   .Subscribe();

bpm.NewSubscribeService("张三")
   .ForProcessDefinition("流程定义ID")
   .Subscribe();

bpm.NewSubscribeService("张三","李四","王五")
   .ForTaskDefinition("任务定义ID")
   .Subscribe();
复制代码

取消订阅,一样只需要把Subscribe改为Unsubscribe即可

//取消订阅
bpm.NewSubscribeService("李四")
   .ForProcessDefinition("采购流程")
   .Unsubscribe();

10、数据查询
查询我没有提供接口,直接开放数据库查询比我提供的接口会更加灵活,比如:
a 查询已发布的流程定义

select * from bpm_definition_process where state = 'Deploy'

流程定义状态包括

复制代码
public enum ProcessDefinitionState
{
    //创建
    Create,

    //解析
    Parse,

    //发布
    Deploy,

    //回收
    Revoke,

    //删除
    Delete
}
复制代码

b 我的流程

select * from bpm_instance_process where state = 'Run' and starter = '萧秦'

流程状态包括

复制代码
public enum ProcessState
{
    //创建
    Create,

    //运行
    Run,

    //挂起
    Pending,

    //终止
    Termination,

    //完成
    Complete,

    //取消
    Cancel
}
复制代码

c 我的待办任务

select * from bpm_instance_task where state = 'Run' and actor_id = '萧秦'

待办任务包括了别人委托给你的任务,如果只想看属于自己的任务则可以

select * from bpm_instance_task where state = 'Run' and owner_id = '萧秦'

任务状态包括

复制代码
public enum TaskState
{
    //创建, 任务已被创建
    Create,

    //阻塞, 到达线中有阻塞任务还未完成
    Blocking,

    //启动
    Run,
   
    //完成, 用户已经完成任务并提供了任务的输出数据
    Complete,

    //失败, 用户已经完成任务, 并且提供了错误消息
    Failure,

    //回退
    Rollback
}
复制代码

d 查询任务的候选人信息

select * from bpm_instance_assignment where task_instance_id = 'ID'

e 查询我的消息

select * from bpm_application_notify where state='Unread' and reciever_id = '萧秦'

其它查询就不再举例了

三、总结

之前我有说过我开发这个的引擎的目的是为了在做项目时,有一个体积轻巧,引入方便的单dll文件(发布后大小为1.1M)的工作流引擎,接口也简单易于二次开发,支持多数据库并且功能还算强大。从前年开始的简易版本设计开发到现在基本成形,测试也是花费了大量的时间,可能还有问题没有测到,不过现在基本稳定。接下来如果有时间我会慢慢跟大家介绍功能细节的设计和实现,还有什么功能我考虑不周全的或意见或有哪部分想详细了解的都可以给我留言。联系13606021792

我的微型工作流引擎设计 – 萧秦 – 博客园

mikel阅读(23)

来源: 我的微型工作流引擎设计 – 萧秦 – 博客园

一、前言

提到工作流很多人就会想到OA,的确OA就是典型的工作流的应用,但是工作流并不仅仅局限于OA,工作流应该算是基础框架软件,主要用于流程的重组和优化,它有广阔的应用领域。在java下有很多优秀的开源工作流可以选择比如activit5、jpbm4等,在.net下却几乎找不到令人满意的工作流引擎可用。当然不是说.net下没有开源的只是有些国产开源的但看了代码后就一点兴趣都没有了,且不说代码质量如何,还引入了一大堆的东西,想在项目中应用也是非常困难。鉴于此我还是决定自己开发一款.NET微型工作流引擎。

二、基本说明

为什么叫微型工作流引擎?就是超轻量级,以方便在项目中轻便的使用,比如只有一个类库dll,大小也就几百k到1M左右,不过我们要先回过头来看看工作流系统,它实在是太大了,它应该包括:
1、工作流引擎
2、工作流设计器
3、工作流管理系统
4、表单设计器

目前来说的我只实现了核心引擎,流程定义也只能先在xml中编辑然后读取到引擎中或者直接定义到数据库中,但整个流程是能够正常流转。至于流程设计器、表单设计器、工作流管理系统这个我有精力了再慢慢开发。这里我完成的只是很小的一块,但是是工作流的核心,可以很方便的嵌入到业务系统中应用。

引擎主要提供了对于工作流定义的解析以及流程流转的支持。工作流定义文件描述了业务的交互逻辑,工作流引擎通过解析工作流定义文件按照业务的交互逻辑进行业务的流转,工作流引擎通常通过参考某种模型来进行设计,通过调度算法来进行流程的流转(流程的启动、终止、挂起、恢复等),通过各种环节调度算法来实现对于环节的流转(环节的合并、分叉、选择、条件性的选择等)。

三、初步印象

1、从概念开始解释估计大家都会看不下去了。我们先拿一个简单实例来看看,新建一个项目,引用我的工作流引擎类库(Chitu.Bpm.dll,取名为赤兔)。
在项目启动时配置流程引擎(Global.asax.cs中),如下:

//初始化流程引擎
BpmConfiguration
    .Instance()
    .Config(@"C:\Configration\BpmConfig.xml")
    .Start();

在项目中使用时,比如新建流程定义:

复制代码
//取得工作流上下文
var bpm = new BpmContext()
    .UseTransaction(true)
    .SetActor("萧秦");

//新增流程定义
bpm.NewProcessDefinition("请假流程")
    .SetXmlFile(@"C:\Definition\demo1.xml")
    .SetCategory("分类1")
    .SetEffectDate(DateTime.Now)
    .SetExpireDate(DateTime.Now.AddDays(180))
    .SetMemo("memo1")
    .Create()  //创建流程定义,只生成bpm_definition_process表
    .Parse()   //解析xml
    .Deploy(); //发布流程
复制代码

启动流程:

//启动流程
var process = bpm.NewProcessIntance("请假流程ID", "萧秦(业务ID)");   //创建流程实例
process.SetVariable("流程变量1", "值1");                     //设置流程变量
process.Start();

人工任务节点转交下一步:

//任务完成
var task = bpm.LoadTaskInstance("任务ID");
task.SetVariable("任务变量2", "xx");
task.Signal(); //触发令牌流转

所有的操作都通过Facade模式集中到BpmContext中,操作简单方便。

2、接下来我们先看看流程定义的XML,以下是我捏造的一个流程,以便把各种节点都放进去了。

复制代码
<?xml version="1.0" encoding="UTF-8"?>

<process name="样板房装修流程">
  
  <start name="装修申请">
    <transition to="装修方案设计" >
      <action class="Namespace.MyActionHandler"></action>
    </transition>
  </start>

  <task name="装修方案设计">
    <transition to="装修方案审核">
      <action script="log.Debug('装修方案审核 action test');"></action>
    </transition>
  </task>

  <decision name="装修方案审核">
    <transitions>
      <transition to="装修筹备"     condition-expression="variable.pass == true"></transition>
      <transition to="装修方案设计" condition-expression="variable.pass != true"></transition>
    </transitions>

    <events>
      <action event="enter" class="enterHandlerClass"></action>
      <action event="leave" class="leaveHandlerClass"></action>
    </events>

    <assignments>
      <assignment owner="{process.starter}"></assignment>
    </assignments>

    <variables>
      <variable type="boolean" name="IsPass" access="read,required"></variable>
    </variables>
  </decision>
 
  <fork name="装修筹备">
    <transition to="装修合同签定"></transition>
    <transition to="等待装修工人到位"></transition>
    <transition to="装修材料预算"></transition>
  </fork>

  <sign name="装修合同签定"  necessary="false" async="true">
    <transition to="装修施工"></transition>
  </sign>

  <wait name="等待装修工人到位">
    <transition to="装修施工"></transition>
  </wait>

  <task name="装修材料预算">
    <transition to="材料采购子流程"></transition>
  </task>

  <subflow name="材料采购子流程">
    <transition to="装修施工"></transition>
  </subflow>

  <join name="装修施工">
    <transition to="施工验收/付款"></transition>
  </join>

  <auto name="施工验收/付款">
    <transition to="装修完成"></transition>
  </auto>

  <end name="装修完成">  
  </end>

  <events>
    <action event="process-start" class="TestStartHandler"></action>
    <action event="process-end" class="TestEndHandler"></action>
  </events>

  <variables>
    <variable type="string" name="start_id" access="readonly,required"></variable>
    <variable type="string" name="start_person"></variable>
  </variables>
</process>
复制代码

定义的根节点为流程(process),流程下为各个任务节点(node),任务节点分为:
start       开始节点
auto       自动节点
task       人工节点
decisioin 决策节点
fork        发散节点
join        聚合节点
sublfow  子流程节点
sign       会签节点
wait       等待节点
end        结束节点

任务节点下可以包括路由(transition)动作(action)及人员分配(assignment)变量定义(variable)
其中action包括几种类型:1、class 2、script 3、SQL 4、webservice 5、expression
在script或expression中可以直接访问process.xxx属性或task.xxx属性或variable.xxx简化了动态C#语句的使用。
当然XML定义中还有很多其它的属性定义我这里也没有都列出来,以后用到了再仔细说。

3、关于数据库设计,这里仅仅是流程流转核心所需要的表,表之间都没有拉关系
image

  四、部分功能剖析

a、我把它划分为主要的几大模块: 引擎配置、流程定义、实例流转、日志处理、计划任务
引擎配置:配置引擎实例的数据库连接、日志配置、参数设定等。
流程定义:利用xml来描述流程,主要定义任务节点,路由、动作事件、变量、人员分配等
实例流转:根据定义运行流程实例
日志处理:输出日志
任务计划:会启动一个服务,用于处理比如延时启动,任务过期等

b、流转中的关键性类设计包括:
1、流程对象(Process)
2、工作任务(Task)
3、路由(Trasition)
4、令牌(Token)
5、事件总线与动作处理 (EventBus、ActionHandler)
6、人员分配及委托机制(Assignment、Depute)
7、流程回退处理(RollbackService)
8、消息服务(NotifyService)

c、通常引擎控制流程调度流转核心的调度算法主要有FSM以及PetriNet两种,基于调度算法来完成流程的流转:
1、FSM(有限状态机)
FSM 的定义为包含一组状态集(states)、一个起始状态(start state)、一组输入符号集(alphabet)、一个映射输入符号和当前状态到下一状态的转换函数(transition function)的计算模型。当输入符号串,模型随即进入起始状态。它要改变到新的状态,依赖于转换函数。在有限状态机中,会有有许多变量,例如,状态机有很多与动作(actions)转换(Mealy机)或状态(摩尔机)关联的动作,多重起始状态,基于没有输入符号的转换,或者指定符号和状态(非定有限状态机)的多个转换,指派给接收状态(识别者)的一个或多个状态,等等。遵循FSM流程引擎通过状态的切换来完成流程的流转。
2、PetriNet
信息流的一个抽象的、形式的模型。指出一系统的静态和动态性质。PetriNet通常表示成图。遵循PetriNet流程引擎通过令牌来决定流程的流转。
我采用的是第二种PetriNet算法。用Token来表示当前实例运行的位置,也利用token在流程各个点之间的转移来表示流程的推进,如下图所示:
image001
令牌流转逻辑,我把以下类方法都做一个简化省略了路由选择及节点处理细节,好让大家明白令牌的流转:

复制代码
//令牌Token类中Signal
public void Signal() 
{
    fromTask.Leave(executeContext);
}

//任务Task类中的Leave
public void Leave(ExecutionContext executionContext)
{
    transition.Take(executionContext);
}

//路由Transition类中的Take
public void Take(ExecutionContext executionContext)
{
    toTask.Enter(executionContext);
}

//任务Task类中的Enter
public void Enter(ExecutionContext executionContext)
{
    Run(executionContext);
}
复制代码

至此令牌成功的从一个节点转移到下一个节点了,令牌的流转是工作流的关键,当然不同的节点处理是有所不同的,其中最复杂的当数发散节点及聚合节点了。这里就介绍到这里,不再给大家详细介绍了。

d、目前我引擎中实现的主要包括以下功能:
1、解释过程定义
2、控制过程实例—创建、激活、挂起、终止等
3、控制流程调度流转
4、自定义动作及事件发布
5、流程变量及工作变量处理
6、任务计划,比如延时启动,任务过期等
7、委托服务,委托代办
8、回退服务,回退到任意节点或召回
9、消息服务,比如认领通知、待办提醒、催办消息…
10、流程任务监控服务
11、日志处理及历史记录
12、任务分配与认领
13、参与者组织模型接口

五、总结

目前我的这款工作流引擎还在继续完善当中,我总结下它的优缺点:
优点:
1、它是一款超轻量极或者说是微型的工作流引擎,而且绿色无污染,它只有一个dll,大小仅1M左右。
2、它目前支持SQL Server、MySQL、Oracle、SQLite、PostgreSql等多种数据库
3、体型上来说它虽然是微型,但功能上并不算微型,它的设计结合了现代的OA及传统工作流、基本上可以实现我们大多数的功能需要。
4、它其实是面向开发者设计的,从上面初始印象中的实例代码中大家可以看到,它的接口是很集中、精简、友好的,让开发者容易理解而且使用起来更方便简单。所以它更适合嵌入到项目中开发。
缺点:
1、它现在没有流程设计器、管理系统、表单设计器等,充其量只能算是一个类库,并不是直接拿来就可以使用。
2、目前刚刚完成第一个内部版本,而且目前只在我们内部项目中使用,所以它不够成熟,虽然我们会持续的改进和完善。
3、缺乏成功应用的案例。
对于我们自己来说,这些缺点都是我们需要继续努力的地方,可能还需要大量的时间来完成。目前来说我们还不打算开源,等它慢慢稳定成熟后我们会考虑是不是开源出来。如果大家有好的建议或有哪方面的疑惑我很乐意给大家解答,或者你也在设计开发自己的工作流,我们可以相互交流下。联系13606021792

快速开发之代码生成器(asp.net mvc4 + easyui + knockoutjs) – 萧秦 – 博客园

mikel阅读(26)

来源: 快速开发之代码生成器(asp.net mvc4 + easyui + knockoutjs) – 萧秦 – 博客园

一、前言

作为一个码农这么多年,一直在想怎么提高我们的编码效率,关于如何提高编码效率,我自己的几点体会

1、清晰的项目结构,要编写代码的地方集中
2、实现相同功能的代码量少并且清晰易懂
3、重复或有规律的代码应该自动生成

在这里我就讨论下代码生成的问题。

二、关于代码生成器

刚毕业时我也非常迷信代码生成器,喜欢在网上找一些代码生成器及相关的源码,喜欢在和网友讨论哪款生成器最好用,但实际上很少真正用这些东西来开发项目,原因很简单:
1、生成出来的代码不是我们要的代码
2、生成后的代码再修修改改,其实还没有我的ctrl+c和ctrl+v速度快。
3、生成的基本上是实体类及SQL拼接代码,现在直接用linq或一些好用的orm多方便,谁还用SQLHelper加sql文拼接?
4、b/s项目中没有一个生成器能很好的能生成UI层代码及前端交互的js代码,即使能生成也是简单的页面。

所以,我劝大家不要迷信代码生成器了。它的确可以提高我们的效率,但是并不是网上你找一个生成器就行的。代码生成器它只是一个模板引擎而已,最重要的不是代码生成器本身,而是对一类功能或一类页面的代码规范,对自己代码的提炼,提炼出一个通用的模板。

比如我们常见的查询页面,录入页面等,我们只要提炼一个标准的查询页面的代码,包括前台html,前台js,后台控制器,后台数据服务。然后把它写成模板,再利用模板引擎就可以生成我们需要的代码了。

代码生成器本身就是模板引擎,所以我觉得最好的代码生成器不是网上流传的那些可以生成三层架构代码的软件,而是微软的razor引擎,非常简洁易懂,而且做过ASP.NET mvc项目的朋友应该也很熟悉。我个人觉得这是用来做代码生成最好的引擎。

三、页面模板

我们还是会想要快速开发,比如我选择了一些设定之后,就可以直接生成我想要的代码包括html及js,拷贝到项目中就可以直接运行,运行后就看到我想要的页面,基本功能都有。当然这里所说的快速开发是建立在我对页面功能的提炼模板之上的。实际上我提炼了三种模板:
1、查询页面
这个模板可以解决大部分的查询报表业务功能
image

2、编辑页面
这个编辑模板可以解决基本上所有的录入功能,因为包括了主表,及多个从表(1:N或1:1)录入,而且可以一次性在同一事务中保存。并且定义了很多触发前后事件用于写一些业务处理,并且做到差异更新。
image

3、查询编辑页面,可以查询也可以直接在grid中编辑,这个页面用于做一些简单单据或一些基础数据页面。image

四、代码生成原理

把以上页面代码做成razor模板,razor模板 + 设定选项 ==razor引擎==> 页面代码

怎么利用razor引擎,其实有以下几种方法:
1、直接利用mvc的view输出功能,以下为关键代码

var stringWriter = new StringWriter();
var viewContext = new ViewContext(controllerContext, view, viewData, TempData, stringWriter);
view.Render(viewContext, stringWriter);
var result = stringWriter.ToString();

用这种方法的优点在于不需要引入第三方类库,直接调用mvc视图的Render方法生成,而且效率很高,缺点是controllerContext及view对象的构建获取非常复杂。这种方法适用于有洁辟的码农们,我属于这一种。

2、利用第三方类库RazorEngine输出,以下为关键代码

var template = "Hello @Model.Name! Welcome to Razor!";
var viewData = new { Name = "World" });
var result = Razor.Parse(template, viewData);

这代码很清爽,一目了然,只不过要引入RazorEngine类库,而且效率不如前者。

五、代码生成页面的源码

我们模板准备好了,引擎准备好了,那么还需要一个数据输入viewData,我们做用户界面的目的也就是为了更好的定义这个viewData。
UI展现主要是用了easyui 及JQuery插件smartwizard
前端交互主要是采用了knockoutjs
table表格的行拖拉是采用JQuery插件tableDnD
后台用webapi来处理请求,代码有点长:

Index.cshtml

复制代码
@{
    ViewBag.Title = "代码生成";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

@section head{
    <link href="~/Content/js/jquery-plugins/smartwizard/smart_wizard.css" rel="stylesheet" />
    <style type="text/css">
        div#navigation{float: left;width: 180px;}
        div#wrapper{float: right;width: 100%;margin-left: -185px;}
        div#wizard{margin-left: 185px;}
        ul.anchor{margin:0 0 10px 0 !important;}
        ul li{margin-left:-16px;}
        .grid .z-txt{margin:0 -3px;width:90%;} 
        .grid input{width:90%;}
        .grid input[type=checkbox]{cursor:default;}
        .grid select{width:80%;padding:0 !important;height:22px;}
        .grid select + a{margin:5px;}
        .tDnD_whileDrag{background-color: #FBEC88 !important;}
    </style>
}

@section scripts{
    <script src="~/Content/js/jquery-plugins/smartwizard/jquery.smartWizard.js"></script>
    <script src="~/Content/js/jquery-extend/jquery.tablednd.js"></script>
    @Scripts.Render("~/Resource/Sys/Generator.js")
    <script type="text/javascript">
        $(function () {
            ko.applyBindings(new viewModel());
        });
    </script>
}

<div id="container">
    <div id="navigation">
        <div class="panel-header" style="width: 168px; border-width: 0; background: #FAFAFA;">
            代码类别 
            <input type="text" class="z-txt" data-bind="easyuiCombobox:codetype" />
            <div style="margin:1px;"></div>
            数据库名 
            <input type="text" class="z-txt" data-bind="easyuiCombobox:database" />

            <div style="margin:5px;"></div>
             <div  data-bind="autoheight:60"  style="width: 172px; border-width: 0;margin:0;padding:0; background: #FAFAFA; overflow:auto;">
                <ul data-bind="easyuiTree:tabletree"></ul>
            </div>
        </div>
    </div>
    <div id="wrapper">
        <div id="wizard" class="swMain" style="width:100%"></div>
    </div>
</div>

<script id="template-searchEdit" type="text/html">
    <ul>
        <li><a href="#step-1">
            <label class="stepNumber">1</label>
            <span class="stepDesc">设置条件部<br />
                <small>定义查询条件</small>
            </span>
        </a></li>
        <li><a href="#step-2">
            <label class="stepNumber">2</label>
            <span class="stepDesc">设置数据列<br />
                <small>定义查询显示的数据字段</small>
            </span>
        </a></li>
        <li><a href="#step-3">
            <label class="stepNumber">3</label>
            <span class="stepDesc">其它设置<br />
                <small>修改其它代码生成设置</small>
            </span>
        </a></li>
    </ul>

    <div id="step-1" class="step">
        <h2 class="StepTitle">第一步 请勾选要查询的字段</h2>
        <div>  
            <div style="width:200px;float:left;overflow:auto;" data-bind="autoheight:172">
                    <ul data-bind="easyuiTree:searchEdit.columntree"></ul>
            </div>  
            <div style="float:left;overflow:auto" data-bind="autoheight:172,autowidth:405">
                <table class="grid">
                    <thead>
                        <tr>
                            <th style="width:50px">字段</th>
                            <th style="width:120px">显示名称</th>
                            <th style="width:120px">控件类型</th>
                            @*<th >参数</th>*@
                            <th style="width:80px">查询逻辑</th>
                        </tr>
                    </thead>
                    <tbody data-bind="foreach:form.conditions">
                        <tr data-bind="attr:{id:$index}">
                            <td data-bind="text:field" style="text-align:left"></td>
                            <td><input type="text" class="z-txt" data-bind="value:title"/></td>
                            <td><select class="z-txt"  data-bind="options:$root.data.input,value:type"></select></td>
                            @*<td><input type="text" class="z-txt" data-bind="value:options"/></td>*@
                            <td><select class="z-txt"  data-bind="options:$root.data.compare,value:cp"></select></td>
                        </tr>

                    </tbody>
                </table>
            </div>  
        </div>
    </div>
    <div id="step-2" class="step">
        <h2 class="StepTitle">第二步 请勾选要显示的数据字段</h2>
        <div style="width:200px;float:left;overflow:auto;" data-bind="autoheight:172">
            <ul data-bind="easyuiTree:searchEdit.columntree2"></ul>
        </div>  
        <div style="float:left;overflow:auto" data-bind="autoheight:172,autowidth:405">
            <table class="grid">
                <thead>
                    <tr>
                        <th style="width:50px">字段</th>
                        <th style="width:100px">题头</th>
                        <th style="width:30px">隐藏</th>
                        <th style="width:30px">排序</th>
                        <th style="width:50px">对齐</th>
                        <th style="width:40px">宽度</th>
                        <th style="width:50px">格式化</th>
                        <th style="width:50px">编辑器</th>
                    </tr>
                </thead>
                <tbody data-bind="foreach:form.columns">
                    <tr data-bind="attr:{id:$index}">
                        <td data-bind="text:field" style="text-align:left"></td>
                        <td><input type="text" class="z-txt" data-bind="value:title" /></td>
                        <td><input type="checkbox" data-bind="checked:hidden"/></td>
                        <td><input type="checkbox" data-bind="checked:sortable"/></td>
                        <td><select class="z-txt"  data-bind="options:$root.data.align,value:align" ></select></td>
                        <td><input type="text" class="z-txt" data-bind="value:width" /></td>
                        <td><select class="z-txt"  data-bind="options:$root.data.formatter,optionsText:'text',optionsValue:'value',value:formatter" ></select></td>
                        <td><select class="z-txt"  data-bind="options:$root.data.editor,optionsText:'text',optionsValue:'value',value:editor" ></select></td>
                    </tr>
                </tbody>
            </table>
        </div>  
    </div>

     <div id="step-3" class="step">
        <h2 class="StepTitle">第三步 其它设置</h2>

        <div class="container_12">
            <div class="grid_1 lbl">业务区域</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.area"/></div>

            <div class="clear"></div>

            <div class="grid_1 lbl">控制器名称</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.controller"/></div>

            <div class="clear"></div>

            <div class="grid_1 lbl">生成路径</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.path"/></div>
        </div>
    </div>
</script>

<script id="template-search" type="text/html">
    <ul>
        <li><a href="#step-1">
            <label class="stepNumber">1</label>
            <span class="stepDesc">设置条件部<br />
                <small>定义查询条件</small>
            </span>
        </a></li>
        <li><a href="#step-2">
            <label class="stepNumber">2</label>
            <span class="stepDesc">设置数据列<br />
                <small>定义查询显示的数据字段</small>
            </span>
        </a></li>
        <li><a href="#step-3">
            <label class="stepNumber">3</label>
            <span class="stepDesc">其它设置<br />
                <small>修改其它代码生成设置</small>
            </span>
        </a></li>
    </ul>

    <div id="step-1" class="step">
        <h2 class="StepTitle">第一步 请勾选要查询的字段</h2>
        <div>  
            <div style="width:200px;float:left;overflow:auto;" data-bind="autoheight:172">
                    <ul data-bind="easyuiTree:searchEdit.columntree"></ul>
            </div>  
            <div style="float:left;overflow:auto" data-bind="autoheight:172,autowidth:405">
                <table class="grid">
                    <thead>
                        <tr>
                            <th style="width:50px">字段</th>
                            <th style="width:120px">显示名称</th>
                            <th style="width:120px">控件类型</th>
                            @*<th >参数</th>*@
                            <th style="width:80px">查询逻辑</th>
                        </tr>
                    </thead>
                    <tbody data-bind="foreach:form.conditions">
                        <tr data-bind="attr:{id:$index}">
                            <td data-bind="text:field" style="text-align:left"></td>
                            <td><input type="text" class="z-txt" data-bind="value:title"/></td>
                            <td><select class="z-txt"  data-bind="options:$root.data.input,value:type"></select></td>
                            @*<td><input type="text" class="z-txt" data-bind="value:options"/></td>*@
                            <td><select class="z-txt"  data-bind="options:$root.data.compare,value:cp"></select></td>
                        </tr>

                    </tbody>
                </table>
            </div>  
        </div>
    </div>
    <div id="step-2" class="step">
        <h2 class="StepTitle">第二步 请勾选要显示的数据字段</h2>
        <div style="width:200px;float:left;overflow:auto;" data-bind="autoheight:172">
            <ul data-bind="easyuiTree:searchEdit.columntree2"></ul>
        </div>  
        <div style="float:left;overflow:auto" data-bind="autoheight:172,autowidth:405">
            <table class="grid">
                <thead>
                    <tr>
                        <th style="width:50px">字段</th>
                        <th style="width:100px">题头</th>
                        <th style="width:30px">隐藏</th>
                        <th style="width:30px">排序</th>
                        <th style="width:50px">对齐</th>
                        <th style="width:40px">宽度</th>
                        <th style="width:50px">格式化</th>
                    </tr>
                </thead>
                <tbody data-bind="foreach:form.columns">
                    <tr data-bind="attr:{id:$index}">
                        <td data-bind="text:field" style="text-align:left"></td>
                        <td><input type="text" class="z-txt" data-bind="value:title" /></td>
                        <td><input type="checkbox" data-bind="checked:hidden"/></td>
                        <td><input type="checkbox" data-bind="checked:sortable"/></td>
                        <td><select class="z-txt"  data-bind="options:$root.data.align,value:align" ></select></td>
                        <td><input type="text" class="z-txt" data-bind="value:width" /></td>
                        <td><select class="z-txt"  data-bind="options:$root.data.formatter,optionsText:'text',optionsValue:'value',value:formatter" ></select></td>
                    </tr>
                </tbody>
            </table>
        </div>  
    </div>

     <div id="step-3" class="step">
        <h2 class="StepTitle">第三步 其它设置</h2>

        <div class="container_12">
            <div class="grid_1 lbl">业务区域</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.area"/></div>

            <div class="clear"></div>

            <div class="grid_1 lbl">控制器名称</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.controller"/></div>

            <div class="clear"></div>

            <div class="grid_1 lbl">生成路径</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.path"/></div>
        </div>
    </div>
</script>

<script id="template-edit" type="text/html">
    <ul>
        <li><a href="#step-1">
            <label class="stepNumber">1</label>
            <span class="stepDesc">设置主表编辑区<br />
                <small>定义主表编辑字段</small>
            </span>
        </a></li>
        <li><a href="#step-2">
            <label class="stepNumber">2</label>
            <span class="stepDesc">设置明细数据页签<br />
                <small>定义明细表及页签</small>
            </span>
        </a></li>
        <li><a href="#step-3">
            <label class="stepNumber">3</label>
            <span class="stepDesc">其它设置<br />
                <small>修改其它代码生成设置</small>
            </span>
        </a></li>
    </ul>

    <div id="step-1" class="step">
        <h2 class="StepTitle">第一步 请勾选要编辑的字段</h2>
        <div>  
            <div style="width:200px;float:left;overflow:auto;" data-bind="autoheight:172">
                    <ul data-bind="easyuiTree:searchEdit.columntree"></ul>
            </div>  
            <div style="float:left;overflow:auto" data-bind="autoheight:172,autowidth:405">
                <table class="grid">
                    <thead>
                        <tr>
                            <th style="width:20%">字段</th>
                            <th style="width:40%">标签名称</th>
                            <th style="width:30%">控件类型</th>
                            <th style="width:10%">只读</th>
                        </tr>
                    </thead>
                    <tbody data-bind="foreach:form.conditions">
                        <tr data-bind="attr:{id:$index}">
                            <td data-bind="text:field" style="text-align:left"></td>
                            <td><input type="text" class="z-txt" data-bind="value:title"/></td>
                            <td><select class="z-txt"  data-bind="options:$root.data.input,value:type" style="width:60%"></select><a href="#">高级</a></td>
                            <td><input type="checkbox" data-bind="checked:readonly"/></td>
                        </tr>
                    </tbody>
                </table>
            </div>  
        </div>
    </div>
    <div id="step-2" class="step">
        <h2 class="StepTitle">第二步 请设置页面中的tab页签</h2>
         
        <div style="float:left;overflow:auto;width:150px;" data-bind="autoheight:172">
            <a href="#" class="buttonNext" style="float:left;margin:5px 3px 5px 0" data-bind="click:edit.addTab">添加Tab页签</a>
            <table class="grid">
                <thead>
                    <tr>
                        <th style="width:30%">#</th>
                        <th style="width:70%">名称</th>
                    </tr>
                </thead>
                <tbody data-bind="foreach:form.tabs">
                    <tr data-bind="attr:{id:$index}">
                        <td><a href="#" data-bind="click:$parent.edit.removeTab">删除</a></td>
                        <td><input type="text" class="z-txt" data-bind="value:title,click:$parent.edit.clickTab" style="width:90%"/></td>
                    </tr>
                </tbody>
            </table>
        </div>  

         <div id="edit-tab-setting" style="float:left;overflow:auto;" data-bind="autoheight:172,autowidth:355,visible:edit.selectedTitle()!=null">
 
        </div> 
    </div>

     <div id="step-3" class="step">
        <h2 class="StepTitle">第三步 其它设置</h2>

        <div class="container_12">
            <div class="grid_1 lbl">业务区域</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.area"/></div>

            <div class="clear"></div>

            <div class="grid_1 lbl">控制器名称</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.controller"/></div>

            <div class="clear"></div>

            <div class="grid_1 lbl">生成路径</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.path"/></div>
        </div>
    </div>
</script>

<script type="text/html" id="template-edit-tab-setting">
     <div style="padding:8px;clear:both">
        <span>页签类型 </span>
        <select class="z-txt" style="padding:0;height:22px;" data-bind="value:edit.selectedTab.type">
            <option value="empty">empty</option>
            <option value="grid">grid</option>
            <option value="form">form</option>
        </select> 

        <span data-bind="visible:edit.selectedTab.type()!='empty'"> 数据表 </span>
        <select class="z-txt" style="padding:0;height:22px;" data-bind="options:data.table,optionsText:'text',optionsValue:'id',value:edit.selectedTab.subtable,visible:edit.selectedTab.type()!='empty'"></select>

        <span data-bind="visible:edit.selectedTab.type()!='empty'">与主表的关联</span>
        <select class="z-txt" style="padding:0;height:22px;" data-bind="options:data.tablekey,value:edit.selectedTab.relationship,visible:edit.selectedTab.type()!='empty'"></select>
    </div>

    <div style="width:180px;float:left;overflow:auto;margin-right:-18px;" data-bind="autoheight:212,visible:edit.selectedTab.type()!='empty'">
        <ul data-bind="easyuiTree:edit.columntree2"></ul>
    </div> 
    
    <div style="float:right;overflow:auto;" data-bind="autoheight:210,autowidth:535,visible:edit.selectedTab.type()!='empty'">
        <table class="grid">
            <thead>
                <tr>
                    <th style="width:50px">字段</th>
                    <th style="width:100px">题头</th>
                    <th style="width:30px" data-bind="visible:edit.selectedTab.type()=='grid'">隐藏</th>
                    <th style="width:30px" data-bind="visible:edit.selectedTab.type()=='grid'">排序</th>
                    <th style="width:50px" data-bind="visible:edit.selectedTab.type()=='grid'">对齐</th>
                    <th style="width:40px" data-bind="visible:edit.selectedTab.type()=='grid'">宽度</th>
                    <th style="width:50px" data-bind="visible:edit.selectedTab.type()=='grid'">格式化</th>
                    <th style="width:50px" data-bind="visible:edit.selectedTab.type()=='grid'">编辑器</th>
                    <th style="width:50px" data-bind="visible:edit.selectedTab.type()=='form'">控件类型</th>
                    <th style="width:10px" data-bind="visible:edit.selectedTab.type()=='form'">只读</th>
                </tr>
            </thead>
            <tbody data-bind="foreach:edit.selectedTab.columns">
                <tr data-bind="attr:{id:$index}">
                    <td data-bind="text:field" style="text-align:left"></td>
                    <td><input type="text" class="z-txt" data-bind="value:title" /></td>
                    <td data-bind="visible:$parent.edit.selectedTab.type()=='grid'"><input type="checkbox" data-bind="checked:hidden"/></td>
                    <td data-bind="visible:$parent.edit.selectedTab.type()=='grid'"><input type="checkbox" data-bind="checked:sortable"/></td>
                    <td data-bind="visible:$parent.edit.selectedTab.type()=='grid'"><select class="z-txt"  data-bind="options:$root.data.align,value:align" ></select></td>
                    <td data-bind="visible:$parent.edit.selectedTab.type()=='grid'"><input type="text" class="z-txt" data-bind="value:width" /></td>
                    <td data-bind="visible:$parent.edit.selectedTab.type()=='grid'"><select class="z-txt"  data-bind="options:$root.data.formatter,optionsText:'text',optionsValue:'value',value:formatter" ></select></td>
                    <td data-bind="visible:$parent.edit.selectedTab.type()=='grid'"><select class="z-txt"  data-bind="options:$root.data.editor,optionsText:'text',optionsValue:'value',value:editor" ></select></td>
                    <td data-bind="visible:$parent.edit.selectedTab.type()=='form'"><select class="z-txt"  data-bind="options:$root.data.input,value:type"></select></td>
                    <td data-bind="visible:$parent.edit.selectedTab.type()=='form'"><input type="checkbox" data-bind="checked:readonly"/></td>
                </tr>
            </tbody>
        </table>
    </div>
</script>
复制代码

Generator.js

复制代码
/**
* 模块名:mms viewModel
* 程序名: Generator.js
* Copyright(c) 2013 liuhuisheng [ liuhuisheng.xm@gmail.com ] 
**/

var viewModel = function () {
    var self = this;

    this.form = {
        type: '',
        database:ko.observable(),
        table: ko.observable(),
        controller: ko.observable(),
        area:ko.observable(),
        conditions: ko.observableArray(),
        columns: ko.observableArray(),
        tabs: ko.observableArray(),
        path: ko.observable("~/Generator/")
    };
 
    this.resetForm = function () {
        self.form.conditions([]);
        self.form.columns([]);
        self.form.tabs([]);
    };

    this.data = {
        codetype: [{ text: 'search', value: 'search' }, { text: 'edit', value: 'edit' }, { text: 'searchEdit', value: 'searchEdit' }],
        database: ko.observableArray(), 
        table: ko.observableArray(),
        column:ko.observableArray(),
        tablekey: ko.observableArray(),
        input: ['text', 'autocomplete', 'combobox', 'lookup','datebox','daterange'],
        compare: ['equal', 'like', 'startwith', 'endwith', 'greater', 'less', 'daterange'],
        align:['left','center','right'],
        formatter: [{text:'',value:''},{ text: '日期', value: 'com.formatDate' }, { text: '时间', value: 'com.formatTime' }, { text: '金额', value: 'com.formatMoney' }, { text: '是否', value: 'com.formatCheckbox' }],
        editor: [{text:'',value:''},{ text: '文本', value: 'text'}, { text: '整数', value: "{type: 'numberbox',options:{min: 0}}" }, { text: '两位小数', value: "{type: 'numberbox',options:{min: 0, precision: 2}}" }, { text: '下拉框', value: "{type:'combobox',options:{}}" }, { text: '弹出框', value: "{type:'lookup',options:{}}" }, { text: '日期', value: 'datebox' }]
    };

    this.initDatabase = function () {
        com.ajax({
            type: 'GET',
            async:false,
            url: '/api/sys/generator/GetConnectionStrings',
            success: function (d) {
                self.data.database(d);
            }
        });
    };

    this.initDatabase();

    this.getTableUrl = function () {
        return '/api/sys/generator/GetTables?database=' + self.form.database();
    };
    this.getColumnUrl = function (table) {
        return '/api/sys/generator/GetColumns?database=' + self.form.database() + "&table=" + table;
    }

    this.codetype = {
        showblank: true,
        width: 110,
        data: self.data.codetype,
        onSelect: function (node) {
            self.form.type = node.value;
            self.initWizard();
        }
    };

    this.database = {
        showblank: true,
        width: 110,
        data: self.data.database,
        onSelect: function (node) {
            self.form.database(node.value)
            self.form.area((node.value.split('.')[1] || node.value).replace(/(^|\s+)\w/g, function (s) { return s.toUpperCase(); }));
        }
    };

    this.tabletree = {
        method: 'GET',
        url: ko.computed(self.getTableUrl),
        loadFilter: function (d) {
            var data = utils.filterProperties(d.rows || d, ['TableName as id', 'TableName as text']);
            self.data.table(data);
            return data;
        },
        onSelect: function (node) {
            self.form.table(node.id);
            self.edit.init();
            self.resetWizard();
            self.form.controller((node.id.split('_')[1] || node.id).replace(/(^|\s+)\w/g, function (s) { return s.toUpperCase(); }));
        }
    };

    this.generator = function () {
        com.ajax({
            type:'POST',
            url: '/api/sys/generator',
            data: ko.toJSON(self.form),
            success: function (d) {
                com.message('success', "代码已生成!");
            }
        });
    };

    this.searchEdit = {};
    this.searchEdit.columntree = {
        method: 'GET',
        url: ko.computed(function () {
            return self.getColumnUrl(self.form.table());
        }),
        checkbox: true,
        loadFilter: function (d) {
            return utils.filterProperties(d.rows || d, ['ColumnName as id', 'ColumnName as text']);
        },
        onSelect: function (node) {
            var handle = node.checked ? 'uncheck' : 'check';
            $(this).tree(handle, node.target);
        },
        onCheck: function (node, checked) {
            if (checked)
                self.form.conditions.push({ field: node.id, title: node.id, type: 'text', options: '', cp: 'equal',readonly:false });
            else
                self.form.conditions.remove(function (item) { return item.field == node.id });
        },
        onLoadSuccess: self.resetForm
    };

    this.searchEdit.columntree2 = {
        method: 'GET',
        url: ko.computed(function () {
            return self.getColumnUrl(self.form.table());
        }),
        checkbox: true,
        loadFilter: function (d) {
            return utils.filterProperties(d.rows || d, ['ColumnName as id', 'ColumnName as text']);
        },
        onSelect: function (node) {
            var handle = node.checked ? 'uncheck' : 'check';
            $(this).tree(handle, node.target);
        },
        onCheck: function (node, checked) {
            var arr = self.form.columns;
            
            if (checked) {
                var item = $.grep(arr(), function (row) {return row.field == node.id;})[0];
                item || arr.push({ field: node.id, title: node.id, hidden: false, sortable: true, align: 'left', width: 80, formatter: '', editor: 'text' });
            } else
                arr.remove(function (item) { return item.field == node.id });
        }
    };

    this.edit = {};
    this.edit.selectedTab = {
        title: ko.observable(),
        type: ko.observable(),
        subtable: ko.observable(),
        relationship: ko.observable(),
        columns: ko.observableArray(),
        primaryKeys:ko.observableArray()
    };
     
    this.edit.columntree2 = {
        method: 'GET',
        url:ko.observable(),
        checkbox: true,
        loadFilter: function (d) {
            self.data.column(d);
            var list = utils.filterProperties(d.rows || d, ['ColumnName as id', 'ColumnName as text']);
            self.edit.setDefaultForm();
            self.edit.resetTableKey();
            var checkedList = [];
            for (var i in self.edit.selectedTab.columns())
                checkedList.push(self.edit.selectedTab.columns()[i].field);
            for (var i in list)
                if ($.inArray(list[i].id, checkedList) > -1) list[i].checked = true;
            
            return list
        },
        onSelect: function (node) {
            var handle = node.checked ? 'uncheck' : 'check';
            $(this).tree(handle, node.target);
        },
        onCheck: function (node, checked) {
            var arr = self.edit.selectedTab.columns;

            if (checked) {
                var item = $.grep(arr(), function (row) { return row.field == node.id; })[0];
                item || arr.push({ field: node.id, title: node.id, hidden: false, sortable: true, align: 'left', width: 80, formatter: '', editor: 'text', type: '', readonly: true });
            } else
                arr.remove(function (item) { return item.field == node.id });
        }
    }
    this.edit.init = function () {
        self.edit.selectedTitle(null);
        self.edit.selectedTab = null;
        $('#edit-tab-setting').empty();
    };
    this.edit.addTab = function () {
        var title = 'tab' + (self.form.tabs().length + 1);
        var newTab = {
            title: ko.observable(title),
            type: ko.observable('empty'),
            subtable: ko.observable(self.form.table()),
            relationship: ko.observable(),
            columns: ko.observableArray(),
            primaryKeys:ko.observableArray()
        };
        newTab.type.subscribe(function (value) {
            if (value == 'grid') {
                var item = $.grep(self.data.table(), function (row) { return row.id == self.form.table() + "Detail" })[0];
                if (item)
                    newTab.subtable(item.id);
            }
            else if (value == 'form') {
                newTab.subtable(self.form.table());
            }
        });
        newTab.columns.subscribe(self.tableDnDUpdate);
        newTab.subtable.subscribe(function (value) {
            self.edit.selectedTab.columns([]);
            self.edit.columntree2.url(self.getColumnUrl(value));
        });

        self.form.tabs.push(newTab);
    };
    
    this.edit.removeTab = function (row,event) {
        self.form.tabs.remove(row);

        if (row.title() == self.edit.selectedTitle())
            self.edit.selectedTitle(null);
    };
    this.edit.selectedTitle = ko.observable();
    this.edit.clickTab = function (row, event) {
        if (row.title() == self.edit.selectedTitle()) return;
 
        self.edit.selectedTitle(row.title());
        self.edit.selectedTab = row;
        self.edit.columntree2.url = ko.observable(self.getColumnUrl(self.edit.selectedTab.subtable()));

        var currentTr = $(event.srcElement).parent("td").parent("tr");
        currentTr.parent().find("tr.tree-node-selected").removeClass("tree-node-selected");
        currentTr.addClass("tree-node-selected");

        var tabTemplate = $('#template-edit-tab-setting').html();
        var wrapper = $('#edit-tab-setting').empty().html(tabTemplate);

        ko.cleanNode(wrapper[0]);
        ko.applyBindings(self, wrapper[0]);
        wrapper.find("table").tableDnD({ onDrop: self.tableDnDSort });
    };
    this.edit.resetTableKey = function () {
        var relationship = self.edit.selectedTab.relationship();
        self.data.tablekey([]);
        var cols = self.data.column();
        for (var i in cols)
            if (cols[i].IsIdentity || cols[i].IsPrimaryKey)
                self.data.tablekey.push(cols[i].ColumnName);

        self.edit.selectedTab.relationship(relationship);
        self.edit.selectedTab.primaryKeys(self.data.tablekey());
    };
    this.edit.setDefaultForm = function () {
        var arr = [
            { field: 'ApproveState', title: '审批状态', type: 'text', readonly: true },
            { field: 'ApproveRemark', title: '审批意见', type: 'text', readonly: true },
            { field: 'ApprovePerson', title: '审批人', type: 'text', readonly: true },
            { field: 'ApproveDate', title: '审批日期', type: 'datebox', readonly: true },
            { field: 'CreatePerson', title: '编制人', type: 'text', readonly: true },
            { field: 'CreateDate', title: '编制日期', type: 'datebox', readonly: true },
            { field: 'UpdatePerson', title: '修改人', type: 'text', readonly: true },
            { field: 'UpdateDate', title: '修改日期', type: 'datebox', readonly: true }
        ];

        var cols = self.data.column();
        var defaults = { field: '', title: '', hidden: false, sortable: true, align: 'left', width: 80, formatter: '', editor: 'text', type: '', readonly: true };
        for (var i in arr) {
            if (!$.grep(cols, function (item) { return item.ColumnName == arr[i].field; }).length)
                return;

            arr[i] = $.extend({}, defaults, arr[i]);
        }

        self.edit.selectedTab.columns(arr);

        var tree = self.edit.columntree2.$element();
        for (var i in arr) {
            var node = tree.tree('find', arr[i].field);
            if (node) tree.tree('check', node.target);
        }
    };

    this.initWizard = function () {
        var stepTemplate = $('#template-' + self.form.type);
        if (!stepTemplate.length) return;

        var wizard = $('#wizard').removeData('smartWizard').empty();
        ko.cleanNode(wizard[0]);
        
        wizard.html(stepTemplate.html());
        wizard.smartWizard({
            labelNext: '下一步',
            labelPrevious: '上一步',
            labelFinish: '生成',
            onFinish: self.generator
        });
        var resizeStep = function () {
            $(".step").height($(window).height() - 145)
                      .width($(window).width() - 205);
            $(".actionBar").width($(window).width() - 195);
            var index = wizard.smartWizard('currentStep');
            wizard.smartWizard('goToStep', index);
        };
        $(window).resize(resizeStep);
        resizeStep();
        ko.applyBindings(self, wizard[0]);
        wizard.find("table").tableDnD({ onDrop: self.tableDnDSort });

        for (var i in self.form) {
            if ($.isFunction(self.form[i]))
                if (self.form[i]() instanceof Array)
                    if (self.form[i].subscribe) 
                        self.form[i].subscribe(self.tableDnDUpdate);
        }
    };
    this.resetWizard = function () {
        var wizard = $("#wizard").smartWizard('goToStep', 1);
        for (var i = 1; i <= wizard.find(">ul>li").length; i++)
            wizard.smartWizard("disableStep", i);
    };

    this.tableDnDUpdate = function () {
        setTimeout('$("table").tableDnDUpdate()', 300);
    };
   
    this.tableDnDSort = function (table, row) {
        var name = $(table).find("tbody").attr("data-bind").replace('foreach:form.','');
        var array = self.form[name], i = 0;

        if (name == 'foreach:edit.selectedTab.columns')
            array = self.edit.selectedTab.columns;

        $("tr[id]", table).each(function () { array()[this.id].id = i++; });
        array.sort(function (left, right) { return left.id == right.id ? 0 : (left.id < right.id ? -1 : 1) });

        //for fix ko bug refresh ui
        var tempArr = array();
        array([]);
        array(tempArr);
    };
};
复制代码

razor模板
model.cshtml

复制代码
@using Zephyr.Core.Generator
using System;
using System.Collections.Generic;
using System.Text;
using Zephyr.Core;

namespace Zephyr.Web.@(@Model.Area).Models
{
    [Module("@Model.Database")]
    public class @(Model.TableName)Service : ServiceBase<@Model.TableName>
    {
       
    }

    public class @Model.TableName : ModelBase
    {
@foreach(TableSchema item in Model.Columns)
{
    if (item.IsIdentity)
    {
        @:[Identity]
    }

    if (item.IsPrimaryKey)
    {
        @:[PrimaryKey]   
    }
        @:public @item.TypeName @item.ColumnName { get; set; }
}
    }
}
复制代码

其它各页面的模板我就不一一贴出来了,大家可以查看我以前的那些关于共通viewModel的博客,查询及编辑页面我都有详细介绍过,大家也可以自己提炼出自己的一套模板,然后也可以用这同一个思路去做一个代码生成器。

六、代码生成用户界面

这个用户界面我们还是要把三种页面的定义分开看:

1、查询页面生成

第一步,选择代码类别search(查询页面),选择数据库,选择业务主表,再勾选字段即可实现查询条件部的设置,并且实现了拖拉排序功能。大家可以对照查询模板看。
image

第二步,选择grid中要显示的列,并且设置属性,格式化等
image

第三步,设置一些全局设定,主要根据这些参数确定命名空间,生成文件名等信息
image

点击生成按钮,按设定生成代码,生成后弹出文件夹,已分别生成MVC三层代码
image

mms_receive.cs
image

Index.cshtml
image

ReceiveController.cs
image

把这个代码直接拷贝到项目中直接运行,测试条件过滤都没有问题,grid会自适应高度,grid远程排序,选择分页翻页都没有问题,所有的功能都可用,
只有lookup控件弹出是空值,因为只把控制设置为了lookup但没有为它设置更详细的选项。autocomplete也是同样
即代码生成器已经生成了一个大的结构及UI,一些小细节还是要手动修改下,代码生成的UI界面如果把每个控件的选项也做进去会相当的复杂,也没有必要再细化了。
image

2、编辑页面生成
第一步,选择主表编辑区的字段及控件类型,控件类型中的高级还未实现,这个编辑的UI也可以参照编辑的模板看
image

第二步,添加tab页签,选择页签类型(grid,form,empty) grid是指跟主表N:1关系,form是指跟主表1:1关系,empty是空页签,生成后自己可以添加内容
这里我随便添加三个tab页签tab1 tab2 tab3
tab1用来放人员变动grid(跟主表关系N:1)image

tab2就选择form(跟主表关系1:1,也可以是主表本身)image

tab3也随便添些东西
image

第三步,其它设置
image

点击生成按钮,生成后自动打开文件夹
image

把这些代码拷贝到项目中直接运行
image

tab2
image

tab3,修改主表数据,tab1,tab2,tab3点保存,能保存成功,
image

审核按钮也可用,审核后单据不可修改
image

这个编辑功能基本上可以囊括很多的录入页面了,可以算是比较通用了

3、查询编辑页面(查询编辑在同一个页面内)页面生成

第一步,选择查询条件并设置控件类型image

第二步,设置grid中的数据列,及编辑器
image

第三步,其它设置
image

点击生成按钮,生成后自动打开文件夹
image

把代码直接拷贝到项目中运行,结果如下,经测试除了控件还需要进一步设置,所有按钮功能正常使用
image

 

七、后述

有了这个代码生成的功能,5分钟做一个基本的页面应该是完全没有问题的。
我这里分享下自己的代码生成的思路,权当抛砖引玉,大家有什么更好的方法欢迎留言。
如果大家感兴趣,就在右下角帮我【推荐】一下吧,谢谢大家了。