泛微OA地址外发、MySQL操作、Laravel入门

前言

泛微OA使用的Laravel

这是其对接数据库的文档

https://laravelacademy.org/post/22012

位置

项目位置

D:\e-office_server_11.0\www

外部脚本位置

D:\e-office_server_11.0\www\eoffice\server\ext

假如我的模块的位置

D:\e-office_server_11.0\www\eoffice\server\ext\ruku\add_product.php

则访问的地址为

http://60.205.244.163:8010/eoffice/server/ext/ruku/add_product.php

外发地址可以配置为

/eoffice/server/ext/ruku/add_product.php

测试

add_product.php

1
2
3
<?php
phpinfo();
?>

日志

1
2
3
4
5
6
7
// 生成日志文件存放目录的路径,示例: D:\e-office_server_11.0\www\eoffice10\server\storage\logs\
$logDir = base_path('/storage/') . 'logs/';
if(empty($data)) {
// 没有获取到外发数据,在日志 D:\e-office_server_11.0\www\eoffice10\server\storage\logs\ruku_log.txt 里记录出错信息
file_put_contents($logDir."ruku_log.txt","数据错误,外发失败。",FILE_APPEND);
exit();
}

打印请求参数到日志

为了方便我们知道参数,我们在log中打印所有的参数

1
2
3
4
5
6
7
8
9
10
11
12
<?php
require __DIR__ . '/../../bootstrap/app.php';
// 默认用法,引入数据库接口
use Illuminate\Support\Facades\DB;
header("Content-Type:text/html;charset=gb2312");

// 通过 $_REQUEST 的方式,获取所有被发送到这个页面的数据。
$data = (isset($_REQUEST) && !empty($_REQUEST)) ? $_REQUEST : [];
$logDir = base_path('/storage/') . 'logs/';
file_put_contents($logDir."ruku_paras_log.txt",json_encode($data));
echo "参数保存成功";
?>

注意

设置请求头编码为utf8,并且转换一下输出的编码。

文件本身编码不要修改为utf8。

输出中文乱码

注意以下两种方式都可以,但是切记不要修改文件本身编码为UTF-8。

1
2
header("Content-Type:text/html;charset=utf8");
echo iconv("GB2312","UTF-8","参数保存成功");

或者

1
2
header("Content-Type:text/html;charset=gb2312");
echo "参数保存成功";

可取参数

JSON解析

流程中的无论表单提交,还是后续节点,外发的时候数据都会包含表单的所有值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
{
"data_id": "9",
"creator": "admin",
"DATA_6": "6",
"DATA_6_TEXT": "管城区市政基础能力提升工程项目-A包",
"DATA_1": "5",
"DATA_1_TEXT": "方圆项目",
"DATA_3": {
"data_id": [
"11",
"12"
],
"id": [
"11",
"12"
],
"form_data_id": [
"9",
"9"
],
"parent_data_id": [
"9",
"9"
],
"DATA_4": [
"4",
"5"
],
"DATA_4_TEXT": [
"金士顿U盘-128G",
"金士顿U盘-256G"
],
"DATA_5": [
"",
""
],
"DATA_7": [
"10",
"20"
],
"DATA_8": [
"10",
"20"
],
"DATA_9": [
"",
""
]
},
"flow_id": "100",
"run_id": "511",
"form_id": "570",
"outsend_id": "214",
"run_name": "入库单",
"node_id": "387",
"process_name": "发起人",
"userInfo": {
"user_id": "admin",
"user_name": "管理员",
"user_accounts": "admin",
"dept_name": "其他",
"dept_id": "62",
"role_name": "[\"OA\\u7ba1\\u7406\\u5458\",\"OA\\u7ba1\\u7406\\u5458\\u6d41\\u7a0b\\u8bbe\\u7f6e\\u7279\\u6743\"]",
"role_id": "[1,68]"
}
}

其中

  • run_id 流程id 对应的表是 flow_run
  • node_id 流程当前节点
  • form_id 表单ID 那么对应的表就是 form_data_570 子表是 form_data_570_data_3
  • data_id 表的主键ID
  • DATA_6是下拉菜单,就会产生两个值 DATA_6和DATA_6_TEXT
  • DATA_3是明细布局 会产生子表 明细的数据都会以数组呈现

子表form_data_570_data_3的格式类似于

image-20230711175502965

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
require __DIR__ . '/../../bootstrap/app.php';
// 默认用法,引入数据库接口
use Illuminate\Support\Facades\DB;

// 通过 $_REQUEST 的方式,获取所有被发送到这个页面的数据。
$data = (isset($_REQUEST) && !empty($_REQUEST)) ? $_REQUEST : [];
// 生成日志文件存放目录的路径,示例: D:\e-office_server_11.0\www\eoffice10\server\storage\logs\
$logDir = base_path('/storage/') . 'logs/';
if(empty($data)) {
// 没有获取到外发数据,在日志 D:\e-office_server_11.0\www\eoffice10\server\storage\logs\ruku_log.txt 里记录出错信息
file_put_contents($logDir."ruku_log.txt","数据错误,外发失败。",FILE_APPEND);
exit();
}
// 获取外发数据示例:
// 流程id
$run_id = isset($data["run_id"]) ? $data["run_id"] : "";
// 流程当前节点
$node_id = isset($data["node_id"]) ? $data["node_id"] : "";
?>

表通用字段

主键

data_id

时间字段

image-20230712151554454

其中deleted_atnull的时候是可用的,泛微没有单独标记删除的字段。

SQL操作

1
2
3
4
5
6
7
<?php
require __DIR__ . '/../../bootstrap/app.php';
use Illuminate\Support\Facades\DB;
header('Content-type: application/json');
$list = DB::table('log_customer')->limit(10)->get();
echo json_encode($list);
?>

日期

1
2
use Carbon\Carbon;
Carbon::now()

SQL基本查询

运行 Select 查询

运行一个最基本的查询,可以使用 DB 门面的 select 方法:

1
$users = DB::select('select * from users where active = ?', [1]);

除了使用 ? 占位符来代表参数绑定外,还可以使用命名绑定来执行查询:

1
$results = DB::select('select * from users where id = :id', ['id' => 1]);

运行插入语句

使用 DB 门面的 insert 方法执行插入语句。和 select 一样,该方法将原生 SQL 语句作为第一个参数,将参数绑定作为第二个参数:

1
DB::insert('insert into users (id, name) values (?, ?)', [1, '学院君']);

运行更新语句

update 方法用于更新数据库中已存在的记录,该方法返回受更新语句影响的行数:

1
$affected = DB::update('update users set votes = 100 where name = ?', ['学院君']);

运行删除语句

delete 方法用于删除数据库中已存在的记录,和 update 一样,该语句返回被删除的行数:

1
$deleted = DB::delete('delete from users');

使用 deleteupdate 语句时,需要非常小心,因为条件设置不慎,导致的后果有可能是无法挽回的,比如不带条件的 delete 语句删除的将是数据表的所有记录!这些都是有血淋淋的教训的。

运行一个通用语句

有些数据库语句不返回任何值,比如新增表,修改表,删除表等,对于这种类型的操作,可以使用 DB 门面的 statement 方法:

1
DB::statement('drop table users');

SQL查询构建器

单条数据

1
2
$user = DB::table('users')->where('name', '学院君')->first();
echo $user->name;

多个Where

1
2
3
4
$users = DB::table('users')
->where('name', '=', 'John')
->where('age', '>', 18)
->first();

查询列表

1
$users = DB::table('users')->select('name', 'email as user_email')->get();

distinct 方法允许你强制查询返回不重复的结果集:

1
$users = DB::table('users')->distinct()->get();

原生表达式

有时候你希望在查询中使用原生表达式,这些表达式将会以字符串的形式注入到查询中,所以要格外小心避免 SQL 注入。想要创建一个原生表达式,可以使用 DB::raw 方法:

1
2
3
4
5
$users = DB::table('users')
->select(DB::raw('count(*) as user_count, status'))
->where('status', '<>', 1)
->groupBy('status')
->get();

左连接/右连接

1
2
3
4
5
6
7
$users = DB::table('users')
->leftJoin('posts', 'users.id', '=', 'posts.user_id')
->get();

$users = DB::table('users')
->rightJoin('posts', 'users.id', '=', 'posts.user_id')
->get();

Where 子句

whereBetween/orWhereBetween

whereBetween 方法验证列值是否在给定值之间:

1
2
3
$users = DB::table('users')
->whereBetween('votes', [1, 100])
->get();

whereNotBetween/orWhereNotBetween

whereNotBetween 方法验证列值不在给定值之间:

1
2
3
$users = DB::table('users')
->whereNotBetween('votes', [1, 100])
->get();

whereIn/whereNotIn/orWhereIn/orWhereNotIn

whereIn 方法验证给定列的值是否在给定数组中:

1
2
3
$users = DB::table('users')
->whereIn('id', [1, 2, 3])
->get();

whereNotIn 方法验证给定列的值不在给定数组中:

1
2
3
$users = DB::table('users')
->whereNotIn('id', [1, 2, 3])
->get();

注:如果要添加非常大的整型数组绑定到查询,可以使用 whereIntegerInRawwhereIntegerNotInRaw 方法来降低内存开销。

whereNull/whereNotNull/orWhereNull/orWhereNotNull

whereNull 方法验证给定列的值为 NULL

1
2
3
$users = DB::table('users')
->whereNull('updated_at')
->get();

whereNotNull 方法验证给定列的值不是 NULL

1
2
3
$users = DB::table('users')
->whereNotNull('updated_at')
->get();

whereDate/whereMonth/whereDay/whereYear/whereTime

whereDate 方法用于比较字段值和日期:

1
2
3
$users = DB::table('users')
->whereDate('created_at', '2019-12-31')
->get();

whereMonth 方法用于比较字段值和一年中的指定月份:

1
2
3
$users = DB::table('users')
->whereMonth('created_at', '12')
->get();

whereDay 方法用于比较字段值和一月中的指定日期:

1
2
3
$users = DB::table('users')
->whereDay('created_at', '31')
->get();

whereYear 方法用于比较字段值和指定年:

1
2
3
$users = DB::table('users')
->whereYear('created_at', '2019')
->get();

whereTime 方法用于比较字段值和指定时间:

1
2
3
$users = DB::table('users')
->whereTime('created_at', '=', '11:20:45')
->get();

whereColumn/orWhereColumn

whereColumn 方法用于验证两个字段是否相等:

1
2
3
$users = DB::table('users')
->whereColumn('first_name', 'last_name')
->get();

还可以传递一个比较运算符到该方法:

1
2
3
$users = DB::table('users')
->whereColumn('updated_at', '>', 'created_at')
->get();

还可以传递多条件数组到 whereColumn 方法,这些条件通过 and 操作符进行连接:

1
2
3
4
5
$users = DB::table('users')
->whereColumn([
['first_name', '=', 'last_name'],
['updated_at', '>', 'created_at']
])->get();

插入(Insert)

查询构建器还提供了 insert 方法用于插入记录到数据表。insert 方法接收数组形式的字段名和字段值进行插入操作:

1
2
3
DB::table('users')->insert(
['email' => 'john@example.com', 'votes' => 0]
);

你甚至可以一次性通过传入多个数组来插入多条记录,每个数组代表要插入数据表的记录:

1
2
3
4
DB::table('users')->insert([
['email' => 'taylor@example.com', 'votes' => 0],
['email' => 'dayle@example.com', 'votes' => 0]
]);

insertOrIgnore 方法会在插入记录到数据库时忽略重复记录错误:

1
2
3
4
DB::table('users')->insertOrIgnore([
['id' => 1, 'email' => 'taylor@example.com'],
['id' => 2, 'email' => 'dayle@example.com']
]);

自增 ID

如果数据表有自增 ID,使用 insertGetId 方法来插入记录并返回ID值:

1
2
3
$id = DB::table('users')->insertGetId(
['email' => 'john@example.com', 'votes' => 0]
);

注:当使用 PostgresSQL 时 insertGetId 方法默认自增列被命名为 id,如果你想要从其他“序列”获取ID,可以将序列名作为第二个参数传递到 insertGetId 方法。

更新(Update)

当然,除了插入记录到数据库,查询构建器还可以通过使用 update 方法更新已有记录。update 方法和 insert 方法一样,接收字段名和字段值的键值对数组,对应字段名就是要更新的列,你可以通过 where 子句来对 update 查询进行约束:

1
2
3
DB::table('users')
->where('id', 1)
->update(['votes' => 1]);

更新或插入

有时候你可能想要更新数据库中已存在的某条记录,如果对应记录不存在的话,则插入这条记录。在这种场景下,可以使用 updateOrInsert 方法。

该方法接收两个参数:用于查询记录的条件数组和用于更新的列值对数组。

updateOrInsert 方法首先会尝试使用第一个参数的列值对匹配对应的数据库记录,

如果记录存在,则通过第二个参数来更新它。

如果记录不存在,则会合并这两个参数数组然后通过合并后的数组插入一条新纪录:

1
2
3
4
5
DB::table('users')
->updateOrInsert(
['email' => 'john@example.com', 'name' => 'John'],
['votes' => '2']
);

SQL技巧

null置0

1
select IFNULL((select DATA_5 from form_data_568_data_3 where data_id=10086 limit 1),0) as num;

打印SQL

一般使用链式操作来对数据库进行相关的增删改查。那么如何查看我们执行的sql 呢?
对于查询语句来说; 我们可以在链式操作后面加上->toSql();来打印执行的sql 语句。

但是,对于其他的就不适用了。所以可以采用以下的办法:

1
2
3
4
5
6
7
8
9
DB::enableQueryLog();

DB::table('form_data_568_data_3')
->updateOrInsert(
['DATA_4' => $DATA_4[$i], 'parent_data_id' => $DATA_6],
['DATA_5' => $DATA_8[$i],'created_at'=> ]
);

file_put_contents($logDir . "ruku_log.txt", json_encode(DB::getQueryLog()), FILE_APPEND);

开启日志

1
DB::enableQueryLog();

运行SQL后查看日志

1
json_encode(DB::getQueryLog())

实战

我这里form_data_568_data_3

1
select ifnull((select IFNULL(DATA_5,0) from form_data_568_data_3 where DATA_4 =  '#DATA_4#' and parent_data_id =  '#DATA_6#'),0);

仓库 form_data_568_data_3

  • DATA_4 产品ID
  • DATA_5 数量
  • parent_data_id 仓库ID

入库单form_data_570

  • 仓库ID DATA_6

入库单子表 form_data_570_data3

  • 产品ID DATA_4
  • 入库后数量 DATA_8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
require __DIR__ . '/../../bootstrap/app.php';
// 默认用法,引入数据库接口
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;

// 通过 $_REQUEST 的方式,获取所有被发送到这个页面的数据。
$data = (isset($_REQUEST) && !empty($_REQUEST)) ? $_REQUEST : [];
// 生成日志文件存放目录的路径,示例: D:\e-office_server_11.0\www\eoffice10\server\storage\logs\
$logDir = base_path('/storage/') . 'logs/';
if (empty($data)) {
// 没有获取到外发数据,在日志 D:\e-office_server_11.0\www\eoffice10\server\storage\logs\ruku_log.txt 里记录出错信息
file_put_contents($logDir . "ruku_log.txt", "数据错误,外发失败。", FILE_APPEND);
exit();
}
// 获取外发数据示例:
// 父表的主键
$data_id = isset($data["data_id"]) ? $data["data_id"] : "";
// 流程当前节点
$node_id = isset($data["node_id"]) ? $data["node_id"] : "";
//仓库ID
$DATA_6 = isset($data["DATA_6"]) ? $data["DATA_6"] : "";
$DATA_4 = $data["DATA_3"]["DATA_4"];
$DATA_4_TEXT = $data["DATA_3"]["DATA_4_TEXT"];
$DATA_8 = $data["DATA_3"]["DATA_8"];
$num = count($DATA_4); //count()为数组统计函数
DB::enableQueryLog();
for ($i = 0; $i < $num; ++$i) {
DB::table('form_data_568_data_3')
->updateOrInsert(
['DATA_4' => $DATA_4[$i], 'parent_data_id' => $DATA_6],
['form_data_id' => $DATA_6, 'DATA_4_TEXT' => $DATA_4_TEXT[$i], 'DATA_5' => $DATA_8[$i], 'created_at' => Carbon::now(), 'updated_at' => Carbon::now()]
);
file_put_contents($logDir . "ruku_log.txt", json_encode(DB::getQueryLog()), FILE_APPEND);
}
?>

但是这样的话创建时间 在更新的时候也会被更新,所以不满足我们的要求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?php
require __DIR__ . '/../../bootstrap/app.php';
// 默认用法,引入数据库接口
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;

// 通过 $_REQUEST 的方式,获取所有被发送到这个页面的数据。
$data = (isset($_REQUEST) && !empty($_REQUEST)) ? $_REQUEST : [];
// 生成日志文件存放目录的路径,示例: D:\e-office_server_11.0\www\eoffice10\server\storage\logs\
$logDir = base_path('/storage/') . 'logs/';
if (empty($data)) {
// 没有获取到外发数据,在日志 D:\e-office_server_11.0\www\eoffice10\server\storage\logs\ruku_log.txt 里记录出错信息
file_put_contents($logDir . "ruku_log.txt", "数据错误,外发失败" . PHP_EOL, FILE_APPEND);
exit();
}
// 获取外发数据示例:
// 父表的主键
$data_id = isset($data["data_id"]) ? $data["data_id"] : "";
// 流程当前节点
$node_id = isset($data["node_id"]) ? $data["node_id"] : "";
//仓库ID
$DATA_6 = isset($data["DATA_6"]) ? $data["DATA_6"] : "";
$DATA_4 = $data["DATA_3"]["DATA_4"];
$DATA_4_TEXT = $data["DATA_3"]["DATA_4_TEXT"];
$DATA_8 = $data["DATA_3"]["DATA_8"];
$num = count($DATA_4); //count()为数组统计函数
DB::enableQueryLog();
for ($i = 0; $i < $num; ++$i) {
$rukuDetail = DB::table('form_data_568_data_3')
->where('DATA_4', '=', $DATA_4[$i])
->where('parent_data_id', '=', $DATA_6)
->first();
file_put_contents($logDir . "ruku_log.txt", json_encode(DB::getQueryLog()) . PHP_EOL, FILE_APPEND);
if ($rukuDetail == NULL) {
//添加
DB::table('form_data_568_data_3')->insert(
[
'DATA_4' => $DATA_4[$i],
'parent_data_id' => $DATA_6,
'form_data_id' => $DATA_6,
'DATA_4_TEXT' => $DATA_4_TEXT[$i],
'DATA_5' => $DATA_8[$i],
'created_at' => Carbon::now(),
'updated_at' => Carbon::now()
]
);
} else {
//更新
DB::table('form_data_568_data_3')
->where('DATA_4', '=', $DATA_4[$i])
->where('parent_data_id', '=', $DATA_6)
->update([
'DATA_4_TEXT' => $DATA_4_TEXT[$i],
'DATA_5' => $DATA_8[$i],
'updated_at' => Carbon::now()
]);
}

file_put_contents($logDir . "ruku_log.txt", json_encode(DB::getQueryLog()) . PHP_EOL, FILE_APPEND);
}
?>

字符串转数字

泛微表单的输入框就算类型是数字,返回的依旧是字符串,所以我们要字符串转数字。

使用3个具体类型的转换函数,intval()floatval()strval()

image-20230711200253613

自定义选择器

数据源

1
2
3
4
select data_id,concat( DATA_1,'-',guige) as name,DATA_1,guige from form_data_566 where deleted_at is null
-- ***模糊查询选项(必须)***
<search> and DATA_1 like '%@value%' </search>
<id>and data_id in (@value)</id>

定义参数

其中@DATA_6@为参数

1
2
3
select DATA_4 as id,DATA_4_TEXT as name from form_data_568_data_3 where deleted_at is null and form_data_id='@DATA_6@'
<search> and DATA_4_TEXT like '%@value%' </search>
<id> and DATA_4 in (@value)</id>