## 导入插件 ### main/controller/ImportController **说明** 以后可能支持其他格式,目前是excel,以下使用excel代替 **ajax统一返回格式** `isSuccess` `boolean` 成功与否 `msg` `string` 如果失败,则返回错误信息,如果成功,随你喜欢 `data` `array` 如果失败,返回空数组,如果成功,返回所需数组 #### main/import/sheet POST请求 **说明** 打开导入面板,上传好文件后,前端会立刻发起这个请求,请求结束时返回excel的`sheet`名等 **POST参数** `url` `string` 上传完成后的文件的`url` `tpl` `string` 模版标识 `module` `string` 模块英文名 `sheet` `integer` 第几个`sheet`,从**0**开始 `init` `integer` 是否是打开面板后立即请求的,如果是,则值设置为**1**,如果是在切换`sheet`的时候请求的,设置为**0** **返回data** `sheetnames` `array` `sheet` 名字列表 `sheet` `integer` 原样返回 #### main/import/settingColumns POST请求 **说明** 选择sheet后,下一步将发起这个请求,用以设置excel里字段和模版字段的对应关系 **POST参数** 无 所需参数在前一次请求里保存在`session`里了 **返回data** `fieldArray` `array` excel里的字段列表 `tplFieldArray` `array` 模版里的字段列表 `tplconfig` `array` 模版配置数组 #### main/import/import POST请求 **说明** 导入请求 **POST参数** `op` `string` 标识start、continue `per` `integer` 每次per条数据,默认10 `times` `integer` 第times批数据 **返回data** `op` `string` `start`之后是`continue`,`continue`之后一直`continue`,暂时没有`end` `queue` `array` `status` 成功或者失败标识 `text` 文字 `i` 内部计数器 第某批的`i`个数据,`continue`时有 `times` `integer` 第`times`批 `success` `integer` 成功条数 `failed` `integer` 失败条数 `time` `float` 一批花费总时间 ### Import类开发说明(以user模块的Import类作为举例) 这里需要说明一下关于循环的解释 导入包含了两种循环 第一种是一批数据一批数据循环 第二种是某一批数据的一条一条循环 **以下用“条”和“批”说明这两类循环,因此这里“条”是一个一维数组,“批”是一个二维数组** #### ImportParent类构造函数 **说明** 构造函数会初始化`import`属性里的各类数组,调用方式是:`$this->import->属性` 下面说明一下各个属性的作用 - `relation` 模版字段和实际字段的关系 - `per` 每批`per`条记录,默认10 - `times` 第`times`批,默认0 - `data` 一**批**导入的数据,键:实际字段名,值:真实值 - `fortmat` 通过模版字段转化后的一**批**导入的数据,键:模版字段名,值:真实值 - `i` 循环一批时,第`i`**条**记录 - `insert` 根据唯一字段检测出来的需要插入的第`i`条记录的一**批**数组,键:`i`,值:目前暂时为空 - `update` 根据唯一字段检测出来的需要更新的第`i`条记录的一**批**数组,键:`i`,值:数组(键:表,值:`pk`主键,`rowid`找出来的记录主键值,`row`,找出来的记录) - `importData` 第`i`**条**准备导入的记录,键:模版字段名,值:真实值。根据上述说明,`format`的第`i`条就是`importData` - `saveData` 这个属性是唯一一个可能经过子类处理后的数组,一**批**导入的数组,键:模版字段名,值:真实值 另外也会设置`tpl`和`session`两个属性 ImportParent类的其他属性 - `error` 键:`status`,0或者1,成功标识。text,提示信息。data,这行的数据,为`importData`的值 - `tpl` 模版标识,在构造函数设置 - `rules` 目前有以下规则:`unique`、`required`、`mobile`、`email`、`datetime`。每个默认值是空数组,在导入时子类用`rules`方法设置带前缀的字段数组 - `session` 在构造函数设置,值为Session组件 #### Import开发 #### 模版标识 `$this->tpl` #### 构造函数 ``` public function __construct( $tpl ) { parent::__construct( $tpl ); } ``` #### 实现ImportInterface接口 **application\modules\main\utils\ImportInterface** `public function rules();` `public function field();` `public function table();` `public function pk();` `public function config();` #### 继承ImportParent类 `application\modules\main\utils\ImportParent` #### 完成数组配置(table举例) ``` public function table() { $table = array( 'user' => array( 'u' => '{{user}}', 'up' => '{{user_profile}}', 'p' => '{{position}}', 'pr' => '{{position_related}}', 'd' => '{{department}}', 'dr' => '{{department_related}}', 'r' => '{{role}}', 'rr' => '{{role_related}}', ), ); return parent::returnArray( $table ); } ``` **说明** - `user` 是模版标识 - `table` 表前缀和表名对应关系 - `pk` 表前缀和主键对应关系 - `rules` 带前缀的表字段和规则,如`array( array( 'u.mobile' ), array( 'mobile', 'unique', 'required', ), ),` 表示的是字段u.mobile有手机号规则(mobile),唯一规则(unique),必填规则(required) 规则可以自己定义,后面再说明 - `field` 模版字段名和带前缀表字段关系 - `config` `module` 这里定义excel上传的文件夹,在data/attachment下的module文件夹 其中data/attachment可以在后台上传设置配置 `type` 这里定义excel上传使用的是哪个模块下的Attach类,该类放在components文件夹 设定type为common,则对应CommonAttach类,默认common。以后可能取消 `name` 模版中文描述 `filename` 模版实际文件名,带格式,如user_import.xls `fieldline` excel中设置第fieldline行为字段行,以后可能取消 `line` excel中line行数据需要被舍去,也就是说从line+1行开始才是数据行 #### import方法 写死就好 ``` public function import() { return parent::import(); } ``` #### beforeFormatData( &$data )方法(可选) **说明** - 一**条**记录里在格式化之前处理数据 比如必须指定某个记录中的uid,但是excel里是不可能让用户去填uid,那么这里就让用户用手机号去代替uid 这里就需要在格式化之前有一个手机号转化uid的操作 - 不参与循环数据库操作 **参数** `data` `array` 需要进行**预**格式化的数据,键为带前缀表字段,值为excel数据 #### formatData( &$data, $isInsert )方法(可选) **说明** 格式化excel里的数据,如果不重写父类的这个方法,导入也成功,只是数据可能是错的(怪我?你自己没有处理数据的格式) **参数** `data` `array` 需要进行格式化的数据,键为带前缀表字段,值为excel数据 `isInsert` `boolean` 插入或者更新的标识 **提供两种方式设置** - 直接设置 ``` $data['u.gender'] = $data['u.gender'] == '男' ? 1 : 0; ``` - 使用匿名函数 如,这里需要给user_profile表的uid设置为user表的uid,则如下写法 ``` $data['up.uid'] = function($data) { return $data['u.uid']; }; ``` 匿名函数的方式主要是为了解决新插入的表的主键并不存在于导入数组里的问题 - 特例isInsert 在isInsert为false时,也就是更新操作时,匿名函数还有第二个参数,这个就是找出来的那条记录,也是带前缀格式 如 ``` $data['u.password'] = function($data, $row) { return md5( md5( $data['u.password'] ) . $row['u.salt'] ); }; ``` 注意: - 里面其实也包含了各个表的主键,只是如果涉及到该主键的字段因为都是空的导致根本不插入这张表的记录,则,这个主键将不存在。举个栗子,在用户导入中,由于岗位字段不是必填字段,如果一条记录的岗位没有填,同时,因为岗位表在用户导入中,只有“岗位”一个字段,所以,这时候数据里并没有岗位的主键,也就是 `isset( $data['p.positionid'] )`是不成立的 #### afterHandleData( $connection )方法(可选) **说明** 一**批**循环后,处理数据之后的操作,其中 `$connection = Ibos::app()->db` 这里的插入更新查找删除方法请使用事务的相关函数 `$connection->schema->commandBuilder->createFindCommand` `$connection->schema->commandBuilder->createInsertCommand` `$connection->schema->commandBuilder->createUpdateCommand` `$connection->schema->commandBuilder->createDeleteCommand` 其他函数自己看yii手册(prpr) 数据从`$this->import->saveData`里获取,如 ``` $this->import->saveData['u.uid'] = 1;//插入user数据后,把返回的uid也加入数组 $this->import->saveData['u.password'] = 'a2f6669d9ec7504c5f1fc99f8c854b61';//经过加密后的密码,在formatData处理过 ``` #### start()方法(可选) **说明** 在导入最开始的时候处理的事务 #### end()方法(可选) **说明** 在导入最结尾的时候处理的事务 #### force()方法(可选) **说明** 该方法用以“强制某个表的插入”,配置格式和`table()`等方法类似。情况见上方提到的“岗位”的例子,如果要强制添加某个表的数据,即便该表的数据是空的,就配置这个方法,表格式是花括号的表 ### 函数扩展 #### 自定义规则扩展 默认的规则有 | 规则名 | 说明 | 备注 | |--|--|--| | unique | 唯一值 | 由于会查询数据库,这个规则会在最后执行 | | required | 必填项 | | | mobile | 手机格式 | | | email | 邮箱格式 | | | datetime | 日期格式 | | | url | url格式 | 即将 | | ip | ip格式 | 即将 | `protected function customRules( $rules )` 以及`check函数` 在构造函数中,调用customRules,用来定义规则名字,自定义规则尽可能不要使用常用的关键字,比如number。接收数组或者逗号字符串 比如,现在要定义一个规则`isNum`是“检测是否为数字” ``` public function __construct( $tpl ) { $this->customRules( 'isNum' ); parent::__construct( $tpl ); } ``` 然后需要在Import类(即子类里)定义一个函数叫`checkIsNum` ``` public function checkIsNum( $data, $dataFieldName ){ $i = $this->import->i; if( !empty( $data ) && !is_numeric( $data ) ){ $this->error[$i]['status'] = false; $this->error[$i]['text'] .= $dataFieldName . '格式不正确;'; } } ``` **解释** `data` `string` 用以检测的数据 `dataFieldName` `string` 用以检测的数据的字段名,对应excel里的字段名,不是模版的字段名 `$this->import->i` `integer` 某一批的第**i**个数据 `this->error` `array` 用以设置错误提示的数组 不用返回值,返回了也没有用 **存在一个问题** 假设导入数据对应到了两张表A和B,唯一字段a在A表,B表没有定义唯一字段,假设存在b字段 在选择创建新纪录选项导入数据后,然后修改b字段的值,再导入一次 再选择覆盖旧记录选项导入数据时,由于a并没有改变,在唯一性检测时,会找到刚才导入的记录 但是B表里并没有唯一字段,所以在找的时候,却找不到原来的记录(或者说不去找) 所以这个时候并不会更新b字段的值 所以这样的修改其实是不允许的:唯一字段不变,改变非唯一字段的值,然后再次覆盖导入,那么这次的导入会因为唯一字段这种优先级高的没有改变值而不改变整个导入的值 **建议** - 虽然配置里提供了field和line去定义字段行号和过滤的行数,建议都设置成1,也就是第一行是字段,第二行开始数据的模版,理解起来会简单一点 - 建议用csv格式,因为解析会更快,占用内存会少