golang办公流程引擎初体验js-ojus/flow——系列三 - Go语言中文社区

golang办公流程引擎初体验js-ojus/flow——系列三


golang语言的办公工作流的包介绍——系列一

golang办公工作流workflow利用js-ojus/flow做测试——系列二

golang办公流程引擎初体验js-ojus/flow——系列三

golang办公流程引擎初体验js-ojus/flow——系列四

1.办公流程概念 

流程大致是这样的:

1.管理员定义好流程类型doctype,这个下面再分流程类型workflow1,workflow2,workflow下再具体分为节点node1,node2,

再定义通用的状态state,通用的动作action,以及根据workflow1下的节点来定义流程走向transition,

再定义contex里的用户-组-角色-权限

2.对于一个要走流程的文件document_001数据表的某个文件,先建立一个document,再给它定义多个事件event,根据workflow1下的节点多少来定义,节点数-1个事件,也就是transition的个数。这个事件就是定义将document_001里的某个文件从状态1 state1改为state2的动作action。

3.开始走流程了,用事件event来修改document_001数据表里某个文件的状态,这个状态从state1变为state2,符合node1里的state1到node2里的state2,也符合transition里的state1——action1——state2

2.测试代码 

package controllers

import (
	"database/sql"
	"github.com/astaxie/beego"
	// "strings"
	// "testing"
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"github.com/js-ojus/flow"
	// "github.com/3xxx/meritms/models"
	// _ "github.com/mattn/go-sqlite3"
	// "github.com/astaxie/beego/httplib"
	// "github.com/astaxie/beego/logs"
	"log"
)

type MainController struct {
	beego.Controller
}

func (c *MainController) Get() {
	c.Data["Website"] = "beego.me"
	c.Data["Email"] = "astaxie@gmail.com"
	c.TplName = "index.tpl"
}

// var db *sql.DB

// func init() {
// 	driver, connStr := "mysql", "root:root@/flow"
// 	tdb := fatal1(sql.Open(driver, connStr)).(*sql.DB)
// 	// flow.RegisterDB(tdb)

// 	if tdb == nil {
// 		log.Fatal("given database handle is `nil`")
// 	}
// 	db = tdb
// }

// @Title show wf list
// @Description show workflow page
// @Success 200 {object} models.GetProductsPage
// @Failure 400 Invalid page supplied
// @Failure 404 articls not found
// @router /workflow [get]
//页面
func (c *MainController) WorkFlow() {
	c.TplName = "merit/workflow.tpl"
}

// @Title get wf list
// @Description get workflowlist by page
// @Success 200 {object} models.GetProductsPage
// @Failure 400 Invalid page supplied
// @Failure 404 data not found
// @router /flowtype [get]
//管理员定义流程类型doctype、流程状态state、流程节点node、
//流程动作action、流程流向transition、流程事件event
func (c *MainController) FlowType() {
	// func init() {
	// orm.RegisterDriver("mysql", orm.DRMySQL)//注册驱动
	// orm.RegisterModel(new(Model))//注册 model
	// orm.RegisterDataBase("default", "mysql", "test:123456@/test?charset=utf8",30,30)//注册默认数据库
	//orm.RegisterDataBase("default", "mysql", "test:@/test?charset=utf8")//密码为空格式
	// }

	driver, connStr := "mysql", "travis@/flow?charset=utf8&parseTime=true"
	tdb := fatal1(sql.Open(driver, connStr)).(*sql.DB)
	if tdb == nil {
		log.Fatal("given database handle is `nil`")
	}
	db := tdb
	tx, _ := db.Begin()
	db.Close()
	//定义流程类型
	dtID1, err := flow.DocTypes.New(tx, "图纸设计流程")
	if err != nil {
		fmt.Println(err)
	}
	dtID2, err := flow.DocTypes.New(tx, "合同评审流程")
	if err != nil {
		fmt.Println(err)
	}
	beego.Info(dtID2)
	dtID3, err := flow.DocTypes.New(tx, "变更立项流程")
	if err != nil {
		fmt.Println(err)
	}
	beego.Info(dtID3)
	//定义流程状态
	dsID1, err := flow.DocStates.New(tx, "设计中...")
	if err != nil {
		fmt.Println(err)
	}
	dsID2, err := flow.DocStates.New(tx, "校核中...")
	if err != nil {
		fmt.Println(err)
	}
	dsID3, err := flow.DocStates.New(tx, "审查中...")
	if err != nil {
		fmt.Println(err)
	}
	flow.DocStates.New(tx, "批准中...")
	flow.DocStates.New(tx, "申报中...")
	flow.DocStates.New(tx, "评估中...")
	flow.DocStates.New(tx, "审批中...")
	//定义流程动作类型
	daID1, err := flow.DocActions.New(tx, "设计完成后提交", false) //改变状态设计中...为校核中...
	if err != nil {
		fmt.Println(err)
	}
	daID2, err := flow.DocActions.New(tx, "校核完成后提交", false)
	if err != nil {
		fmt.Println(err)
	}
	daID3, err := flow.DocActions.New(tx, "审查完成后提交", false)
	if err != nil {
		fmt.Println(err)
	}
	daID4, err := flow.DocActions.New(tx, "核定完成后提交", true)
	if err != nil {
		fmt.Println(err)
	}
	daID5, err := flow.DocActions.New(tx, "编制完成后提交", true)
	if err != nil {
		fmt.Println(err)
	}
	daID6, err := flow.DocActions.New(tx, "审批完成后提交", false)
	if err != nil {
		fmt.Println(err)
	}
	daID7, err := flow.DocActions.New(tx, "立项完成后提交", false)
	if err != nil {
		fmt.Println(err)
	}

	//添加流程规则1:oldstate1 action1 newstate2
	err = flow.DocTypes.AddTransition(tx, dtID1, dsID1, daID1, dsID2)
	if err != nil {
		beego.Error(err)
	}
	//添加流程规则2:oldstate2 action2 newstate3
	err = flow.DocTypes.AddTransition(tx, dtID1, dsID2, daID2, dsID3)
	if err != nil {
		beego.Error(err)
	}

	//定义流程类型doctype下的流程类型workflow
	workflowID1, err := flow.Workflows.New(tx, "图纸设计-三级校审流程", dtID1, dsID1) //初始状态是“设计中...”——校核——审查——完成
	if err != nil {
		fmt.Println(err)
	}
	beego.Info(workflowID1)
	workflowID2, err := flow.Workflows.New(tx, "图纸设计-二级校审流程", dtID1, dsID1) //初始状态是“设计中...”-“校核”——完成
	if err != nil {
		fmt.Println(err)
	}
	beego.Info(workflowID2)
	//定义合同评审下的流程类型:部门合同流程,总院合同流程
	//略

	//定义用户、组、角色、权限集合
	accessContextID1, err := flow.AccessContexts.New(tx, "Context")
	if err != nil {
		beego.Error(err)
	}

	//定义流程类型workflow下的具体每个节点node,用户对文件执行某个动作(event里的action)后,会沿着这些节点走
	// AddNode maps the given document state to the specified node.  This
	// map is consulted by the workflow when performing a state transition
	// of the system.nodeID1
	_, err = flow.Workflows.AddNode(tx, dtID1, dsID1, accessContextID1, workflowID1, "图纸设计-三级校审流程-设计", flow.NodeTypeBegin)
	if err != nil {
		fmt.Println(err)
	}
	_, err = flow.Workflows.AddNode(tx, dtID1, dsID2, accessContextID1, workflowID1, "图纸设计-三级校审流程-校核", flow.NodeTypeLinear)
	if err != nil {
		fmt.Println(err)
	}
	_, err = flow.Workflows.AddNode(tx, dtID1, dsID3, accessContextID1, workflowID1, "图纸设计-三级校审流程-审查", flow.NodeTypeEnd)
	if err != nil {
		fmt.Println(err)
	}
	//定义用户-组-角色-权限关系
	res, err := tx.Exec(`INSERT INTO users_master(first_name, last_name, email, active)
			VALUES('秦', '晓川-1', 'email1@example.com', 1)`)
	if err != nil {
		log.Fatalf("%vn", err)
	}
	uid, _ := res.LastInsertId()
	uID1 := flow.UserID(uid)
	_, err = flow.Groups.NewSingleton(tx, uID1)

	res, err = tx.Exec(`INSERT INTO users_master(first_name, last_name, email, active)
			VALUES('秦', '晓川-2', 'email2@example.com', 1)`)
	if err != nil {
		log.Fatalf("%vn", err)
	}
	uid, _ = res.LastInsertId()
	uID2 := flow.UserID(uid)
	_, err = flow.Groups.NewSingleton(tx, uID2)

	res, err = tx.Exec(`INSERT INTO users_master(first_name, last_name, email, active)
			VALUES('秦', '晓川-3', 'email3@example.com', 1)`)
	if err != nil {
		log.Fatalf("%vn", err)
	}
	uid, _ = res.LastInsertId()
	uID3 := flow.UserID(uid)
	_, err = flow.Groups.NewSingleton(tx, uID3)

	res, err = tx.Exec(`INSERT INTO users_master(first_name, last_name, email, active)
			VALUES('秦', '晓川-4', 'email4@example.com', 1)`)
	if err != nil {
		log.Fatalf("%vn", err)
	}
	uid, _ = res.LastInsertId()
	uID4 := flow.UserID(uid)
	_, err = flow.Groups.NewSingleton(tx, uID4)

	gID1 := fatal1(flow.Groups.New(tx, "设计人员组", "G")).(flow.GroupID)
	gID2 := fatal1(flow.Groups.New(tx, "校核人员组", "G")).(flow.GroupID)
	fatal0(flow.Groups.AddUser(tx, gID1, uID1))
	fatal0(flow.Groups.AddUser(tx, gID1, uID2))
	fatal0(flow.Groups.AddUser(tx, gID1, uID3))

	fatal0(flow.Groups.AddUser(tx, gID2, uID2))
	fatal0(flow.Groups.AddUser(tx, gID2, uID3))
	fatal0(flow.Groups.AddUser(tx, gID2, uID4))
	roleID1 := fatal1(flow.Roles.New(tx, "设计人员角色")).(flow.RoleID)
	roleID2 := fatal1(flow.Roles.New(tx, "校核人员角色")).(flow.RoleID)
	//给角色role赋予action权限
	fatal0(flow.Roles.AddPermissions(tx, roleID1, dtID1, []flow.DocActionID{daID1, daID2, daID3, daID4}))
	fatal0(flow.Roles.AddPermissions(tx, roleID2, dtID1, []flow.DocActionID{daID1, daID2, daID3, daID4, daID5, daID6, daID7}))
	//给用户组group赋予角色role
	err = flow.AccessContexts.AddGroupRole(tx, accessContextID1, gID1, roleID1)
	if err != nil {
		beego.Error(err)
	}
	//将group和role加到accesscontext里——暂时不理解
	err = flow.AccessContexts.AddGroupRole(tx, accessContextID1, gID2, roleID2)
	if err != nil {
		beego.Error(err) //UNIQUE constraint failed: wf_ac_group_roles.ac_id已修补
	}

	tx.Commit() //这个必须要!!!!!!

	c.Data["json"] = "ok"
	c.ServeJSON()
}

// @Title post wf state
// @Description post workflow state
// @Success 200 {object} models.GetProductsPage
// @Failure 400 Invalid page supplied
// @Failure 404 data not found
// @router /flowdocevent [get]
//对具体文件进行流程初始化,对具体文件进行定义动作事件
func (c *MainController) FlowDocEvent() {
	//连接数据库
	driver, connStr := "mysql", "travis@/flow?charset=utf8&parseTime=true"
	tdb := fatal1(sql.Open(driver, connStr)).(*sql.DB)
	if tdb == nil {
		log.Fatal("given database handle is `nil`")
	}
	db := tdb
	tx, err := db.Begin()
	if err != nil {
		beego.Error(err)
	}

	//查询预先定义的doctype流程类型
	dtID1, err := flow.DocTypes.GetByName("图纸设计")
	if err != nil {
		beego.Error(err)
	}
	beego.Info(dtID1)
	//查询预先定义的docstate状态1
	dsID1, err := flow.DocStates.GetByName("设计中...")
	if err != nil {
		fmt.Println(err)
	}
	beego.Info(dsID1)
	//查询预先定义的docstate状态2
	dsID2, err := flow.DocStates.GetByName("校核中...")
	if err != nil {
		fmt.Println(err)
	}
	beego.Info(dsID2)
	//查询预先定义的docstate状态3
	dsID3, err := flow.DocStates.GetByName("审查中...")
	if err != nil {
		fmt.Println(err)
	}
	beego.Info(dsID3)
	//查询预先定义的action动作1
	daID1, err := flow.DocActions.GetByName("提交设计")
	if err != nil {
		fmt.Println(err)
	}
	beego.Info(daID1)
	//查询预先定义的action动作2
	daID2, err := flow.DocActions.GetByName("校核") //应该叫"提交校核"
	if err != nil {
		fmt.Println(err)
	}
	beego.Info(daID2)
	//查询预先定义的action动作3
	daID3, err := flow.DocActions.GetByName("审查") //应该叫"提交审查"
	if err != nil {
		fmt.Println(err)
	}
	beego.Info(daID3)
	//查询预先定义的流程类型workflow,这个相当于doctype下面再分很多种流程
	//比如doctype为图纸设计流程,下面可以分为二级校审流程,三级校审流程,四级校审流程
	myWorkflow, err := flow.Workflows.GetByName("图纸设计-三级校审流程")
	if err != nil {
		beego.Error(err)
	}
	beego.Info(myWorkflow)
	//查询context——这个应该是管理用户-组-权限的
	accessContextID1, err := flow.AccessContexts.List("Context", 0, 0)
	if err != nil {
		beego.Error(err)
	}
	beego.Info(accessContextID1[0].ID)

	beego.Info(flow.GroupID(1))
	//开始为具体一个文件设立流程-此处是新建一个文件。对于旧文件应该怎么操作来着?
	docNewInput := flow.DocumentsNewInput{
		DocTypeID:       dtID1.ID,                //属于图纸设计类型的流程
		AccessContextID: accessContextID1[0].ID,  //所有用户权限符合这个contex的要求
		GroupID:         11,                      //groupId,初始状态下的用户组,必须是个人用户组(一个用户也可以成为一个独特的组,因为用户无法赋予角色,所以必须将用户放到组里)
		Title:           "厂房布置图",                 //这个文件的名称
		Data:            "设计、制图: 秦晓川1, 校核: 秦晓川2", //文件的描述
	}
	// flow.Documents.New(tx, &docNewInput)
	DocumentID1, err := flow.Documents.New(tx, &docNewInput)
	if err != nil {
		beego.Error(err)
	}
	// tx.Commit() //new后面一定要跟commit
	beego.Info(DocumentID1)

	beego.Info(daID2)
	beego.Info(flow.GroupID(12))

	//针对具体一个文件定义动作事件,从"校核中……"状态通过动作"校核"将它修改为"审查中……"
	docEventInput := flow.DocEventsNewInput{
		DocTypeID:   dtID1.ID, //flow.DocTypeID(1),
		DocumentID:  DocumentID1,
		DocStateID:  dsID1.ID, //document state must be this state,文档的现状状态
		DocActionID: daID2.ID, //flow.DocActionID(2),
		GroupID:     12,       //必须是个人用户组
		Text:        "校核",
	}

	docEventID1, err := flow.DocEvents.New(tx, &docEventInput)
	if err != nil {
		beego.Error(err)
	}
	tx.Commit() //一个函数里只能有一个commit,所以,这个是提前定义好的!!!!
	beego.Info(docEventID1)
	c.Data["json"] = "OK"
	c.ServeJSON()
}

// @Title post wf state
// @Description post workflow state
// @Success 200 {object} models.GetProductsPage
// @Failure 400 Invalid page supplied
// @Failure 404 data not found
// @router /flownext [get]
//对具体文件修改状态
func (c *MainController) FlowNext() {
	//连接数据库
	driver, connStr := "mysql", "travis@/flow?charset=utf8&parseTime=true"
	tdb := fatal1(sql.Open(driver, connStr)).(*sql.DB)
	if tdb == nil {
		log.Fatal("given database handle is `nil`")
	}
	db := tdb
	tx, err := db.Begin()
	if err != nil {
		beego.Error(err)
	}

	myDocEvent, err := flow.DocEvents.Get(16)
	if err != nil {
		beego.Error(err)
	}
	beego.Info(myDocEvent)

	//给出接受的组groupids
	groupIds := []flow.GroupID{flow.GroupID(13)}
	beego.Info(groupIds)
	//查询workflow
	myWorkflow, err := flow.Workflows.GetByName("图纸设计-三级校审流程")
	if err != nil {
		beego.Error(err)
	}
	beego.Info(myWorkflow)
	newDocStateId, err := myWorkflow.ApplyEvent(tx, myDocEvent, groupIds)
	if err != nil {
		beego.Error(err)
	}
	tx.Commit() //一个函数里只能有一个commit!!!!
	fmt.Println("newDocStateId=", newDocStateId, err)

	c.Data["json"] = "OK"
	c.ServeJSON()
}

// beego.Info(wflist)

// wflist, err = flow.DocStates.List(0, 0)
// if err != nil {
// 	beego.Error(err)
// }
// // beego.Info(wflist1)
// wflist, err = DocActions.List(0, 0)
// if err != nil {
// 	beego.Error(err)
// }
// wflist1, err = flow.Workflows.List(0, 0)
// if err != nil {
// 	beego.Error(err)
// }
// fatal1 expects a value and an error value as its arguments.
func fatal1(val1 interface{}, err error) interface{} {
	if err != nil {
		fmt.Println("%v", err)
	}
	return val1
}

// error0 expects only an error value as its argument.
func error0(err error) error {
	if err != nil {
		fmt.Println("%v", err)
	}
	return err
}

// error1 expects a value and an error value as its arguments.
func error1(val1 interface{}, err error) interface{} {
	if err != nil {
		fmt.Println("%v", err)
		return nil
	}
	return val1
}

// fatal0 expects only an error value as its argument.
func fatal0(err error) {
	if err != nil {
		fmt.Println("%v", err)
	}
}

3.flow自身的测试

安装mysql

建立flow数据库的用户:

CREATE USER 'travis'@'localhost' IDENTIFIED BY '';

在Navicat里新建一个flow数据库,选择utf8mb4——它源码里有个地方交代了。

开始数据库里的表下面是没有内容的,与下图不同。

然后给用户travis赋权。

必须将setup_db.sh文件拷贝到sql文件夹上一级,否则会出现下列错误:

 

将sql文件里的setup_db.sh和setup_blob_dirs.sh拷贝到上一级目录里(flow文件夹里),然后cmd窗口进入这个文件夹,运行

setup_db.sh -t

setup_blob_dirs.sh

go test

4.mysql和sqlite数据库问题

Flow里的sql语句是mysql的,和sqlite差别很大。从数据表初始化的sh文件开始就不同,一直到flow的go文件里的数据表插入数据,查询数据,jion连接表格,unique多列集合的约束,枚举数据类型,生成view数据表视图等等,太多的不同了。

在sqlite里,新建表用sh文件,也可以用改造好的beego自动建表,

package models

import (
	"fmt"
	"github.com/astaxie/beego/orm"
	"time"
)

// CREATE TABLE users_master (
//     id INT NOT NULL AUTO_INCREMENT,
//     first_name VARCHAR(30) NOT NULL,
//     last_name VARCHAR(30) NOT NULL,
//     email VARCHAR(100) NOT NULL,
//     active TINYINT(1) NOT NULL,
//     PRIMARY KEY (id),
//     UNIQUE (email)
// );
type users_master struct {
	Id         int64
	First_name string `orm:"size(30)"`
	Last_name  string `orm:"size(30)"`
	Email      string `orm:"unique;size(100)"`
	Active     bool
}

// CREATE TABLE wf_ac_group_hierarchy (
//     id INT NOT NULL AUTO_INCREMENT,
//     ac_id INT NOT NULL,
//     group_id INT NOT NULL,
//     reports_to INT NOT NULL,
//     PRIMARY KEY (id),
//     FOREIGN KEY (ac_id) REFERENCES wf_access_contexts(id),
//     FOREIGN KEY (group_id) REFERENCES wf_groups_master(id),
//     FOREIGN KEY (reports_to) REFERENCES wf_groups_master(id),
//     UNIQUE (ac_id, group_id)
// );
type wf_ac_group_hierarchy struct {
	Id         int64
	Ac         *wf_access_contexts `orm:"rel(fk);unique"`
	Group      *wf_groups_master   `orm:"rel(fk);unique"`
	Reports_to *wf_groups_master   `orm:"rel(fk);column(reports_to)"`
}

// CREATE TABLE wf_ac_group_roles (
//     id INT NOT NULL AUTO_INCREMENT,
//     ac_id INT NOT NULL,
//     group_id INT NOT NULL,
//     role_id INT NOT NULL,
//     PRIMARY KEY (id),
//     FOREIGN KEY (ac_id) REFERENCES wf_access_contexts(id),
//     FOREIGN KEY (group_id) REFERENCES wf_groups_master(id),
//     FOREIGN KEY (role_id) REFERENCES wf_roles_master(id)
// );
type wf_ac_group_roles struct {
	Id    int64
	Ac    *wf_access_contexts `orm:"rel(fk)"`
	Group *wf_groups_master   `orm:"rel(fk)"`
	Role  *wf_roles_master    `orm:"rel(fk)"`
}

……

……

// CREATE TABLE wf_workflows (
//     id INT NOT NULL AUTO_INCREMENT,
//     name VARCHAR(100) NOT NULL,
//     doctype_id INT NOT NULL,
//     docstate_id INT NOT NULL,
//     active TINYINT(1) NOT NULL,
//     PRIMARY KEY (id),
//     FOREIGN KEY (doctype_id) REFERENCES wf_doctypes_master(id),
//     FOREIGN KEY (docstate_id) REFERENCES wf_docstates_master(id),
//     UNIQUE (name),
//     UNIQUE (doctype_id)
// );
type wf_workflows struct {
	Id       int64
	Name     string               `orm:"size(100);unique"`
	Doctype  *wf_doctypes_master  `orm:"rel(fk);unique"`
	Docstate *wf_docstates_master `orm:"rel(fk)"`
	Active   bool
}

func init() {
	orm.RegisterModel(new(users_master), new(wf_ac_group_hierarchy), new(wf_ac_group_roles))
	orm.RegisterModel(new(wf_access_contexts), new(wf_docactions_master), new(wf_docevent_application))
	orm.RegisterModel(new(wf_docevents), new(wf_docstate_transitions), new(wf_docstates_master))
	orm.RegisterModel(new(wf_doctypes_master), new(wf_document_blobs), new(wf_document_tags))
	orm.RegisterModel(new(wf_group_users), new(wf_groups_master), new(wf_mailboxes))
	orm.RegisterModel(new(wf_messages), new(wf_role_docactions), new(wf_roles_master))
	orm.RegisterModel(new(wf_workflow_nodes), new(wf_workflows))
}

func InitFlow() {
	sql := fmt.Sprintf("CREATE VIEW wf_ac_perms_v AS " +
		"SELECT wf_ac_group_roles.ac_id, wf_ac_group_roles.group_id, wf_group_users.user_id, wf_ac_group_roles.role_id, wf_role_docactions.doctype_id, wf_role_docactions.docaction_id " +
		"FROM wf_ac_group_roles " +
		"JOIN wf_group_users ON wf_ac_group_roles.group_id = wf_group_users.group_id " +
		"JOIN wf_role_docactions ON wf_ac_group_roles.role_id = wf_role_docactions.role_id;")

	sql2 := fmt.Sprintf("INSERT INTO wf_docstates_master(name) VALUES('__RESERVED_CHILD_STATE__');")

	sql3 := fmt.Sprintf("INSERT INTO wf_roles_master(name) VALUES('SUPER_ADMIN');")

	sql4 := fmt.Sprintf("INSERT INTO wf_roles_master(name) VALUES('ADMIN');")

	sql5 := fmt.Sprintf("CREATE VIEW wf_users_master AS SELECT id, first_name, last_name, email, active FROM users_master;")

	o := orm.NewOrm()
	res, err := o.Raw(sql).Exec()
	if err == nil {
		num, _ := res.RowsAffected()
		fmt.Println("mysql row affected nums: ", num)
	} else {
		o.Rollback() // beego.Info("插入t_studentInfo表出错,事务回滚")
	}
	res, err = o.Raw(sql2).Exec()
	if err == nil {
		num, _ := res.RowsAffected()
		fmt.Println("mysql row affected nums: ", num)
	} else {
		o.Rollback() // beego.Info("插入t_studentInfo表出错,事务回滚")
	}
	res, err = o.Raw(sql3).Exec()
	if err == nil {
		num, _ := res.RowsAffected()
		fmt.Println("mysql row affected nums: ", num)
	} else {
		o.Rollback() // beego.Info("插入t_studentInfo表出错,事务回滚")
	}
	res, err = o.Raw(sql4).Exec()
	if err == nil {
		num, _ := res.RowsAffected()
		fmt.Println("mysql row affected nums: ", num)
	} else {
		o.Rollback() // beego.Info("插入t_studentInfo表出错,事务回滚")
	}
	res, err = o.Raw(sql5).Exec()
	if err == nil {
		num, _ := res.RowsAffected()
		fmt.Println("mysql row affected nums: ", num)
	} else {
		o.Rollback() // beego.Info("插入t_studentInfo表出错,事务回滚")
	}
}

解决golang:unsupported Scan, storing driver.Value type []uint8 into type *time.Time

https://blog.csdn.net/han0373/article/details/81698713

在open连接后拼接参数:parseTime=true 即可
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/hotqin888/article/details/86328163
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-02-02 17:56:32
  • 阅读 ( 1098 )
  • 分类:前端

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢