strapi v4版本建立与users_permissions联系的注意实现

在strapi v4中最好不要直接建立一个collection与users_permissions的联系。在strapi v4版本中建立了一个逻辑:如果一个用户没有权限对某个collection做'find'操作。那么该用户也无法间接的对该collection做任意操作。

在v4版本的@strapi/utils/lib/sanitize/visitors/remove-restricted-relations.js文件建立了一套逻辑。在所有间接写操作中,如果用户对被写的那个collection 没有'find'权限,那么这个间接写操作,将会被移除。

我们显然不希望用户可以对users_permissions这么重要集合有那么高的读权限。因此,如果我们非要建立一个集合和用户的关系时,可以仅仅保存用户id即可。不需要建立集合的联系。如果是考虑到性能的话,可以再建立一个集合,用来优化。

strapi在给某个collection添加新字段后可能会引发的一种问题

实际案例是给已经运行的应用中的某个collection添加新的字段,在strapi中可能并没有在这种情况下对原先已有的数据做migrate操作,而是放任不管。所以,这就会产生一个潜在的风险。例如看下面代码:

entities = await strapi.services.model.find(ctx.query, ['categories', 'keywords', 'products']);

假设,products字段是model中后添加的,旧model中的数据并不存在products这个字段。此时,这个指令的执行就会出错,提示:

$in requires an array as a second argument, found: missing

解决方案:

  1. 在旧数据比较少的情况下,我们可以通过strapi的后台管理页面给model的旧数据设置products字段(设定一个指定值,然后再删除)。
  2. 如果旧数据比较多,就要想办法直接做数据库操作,给旧的model数据批量添加products字段了。

strapi上传具有依赖关系文件的一种解决方案

3D模型文件,不同于一般的网络资源文件。这些3D模型文件很多是具有相互依赖关系的。例如obj,依赖于mtl,然后一个mtl文件可能还依赖好几个图形文件。这是3D系统的特点所决定的,因为3D模型通常包含几何数据,材质数据,然后还有各种纹理数据。这些都可能位于不同文件。

这一个特征对于上传3D资源并备用,就稍微有点麻烦了。因为很多cms系统默认上传文件后,为了便于管理都会重新命名文件。上传之后,再到下载时所有的url就与文件名不同了。主流的cms系统几乎都是这么工作的。例如wordpress, strapi都会为每个文件生成一个唯一的文件名,然后保存在某个地方的文件系统。

因此,我们需要保留文件原名,但是又要避免未来文件太多而导致的文件重名。创建一个唯一的目录(这是针对local策略,如果使用七牛,亚马逊云啊之类的策略也有相类似的方式),然后再在该目录下保存文件是一种便利的解决方案。strapi允许用户通过extensions定制插件,而且允许用户创建provider,我们可以利用这点,定制upload插件,然后修改一下provider来实现此目的。

通过以下几个具体的步骤即可实现此方案:

  1. 规定,所有的上传文件操作,新添加一个keepname字段。如果此字段为"true"则,让服务器做特殊对待。
  2. 重写extensions/upload/service/Upload.js文件,修改upload函数中的内容,里面对meta.keepname做一个判断,如果此值为"true",则让后面所有的provider.upload和provider.delete操作的参数都添加一个updir字段。updir字段的值可以由uuidv4来生成。
  3. 上一步中的updir需要持久化保存到数据库中,因此还需要修改extensions/upload/model/File.setting.json文件。为其中添加updir字段。
  4. 修改或重写strapi-provider-upload-local插件,为其中的upload和delete函数增加处理updir的功能。并且注意修改其url的赋值。我已经开源了一个provider插件来实现此功能。

strapi模型配置的private声明

private声明可以指定模型的某个属性为私有属性,不会被返回服务器返回。而且这个声明在其他模型进行population的时候仍然有效。例如

{
"connection": "default",
"collectionName": "name",
"info": {
"name": "myName"
},
"options": {
"increments": true,
"timestamps": true
},
"attributes": {
"atr1": {
"collection": "aCollection",
"via": "related",
"plugin": "upload",
"required": false,
"autoPopulate": false,
"private": true
}
}
}

做如上方式的声明时,能避免直接的population,也能避免任何方式的返回atr1

使用python的requests库向strapi上传文件

requests库在处理multipart/form-data上传时有好几个“便利地”陷阱。看似通过如下的方式调用post是最方便的:

files = {"key", "value"}
requests.post('url_to_server', files = files, ...)

但实则,这样调用post的api陷阱重重。

首先,通过这样的形式上传文件时,没法指定数据的mime类型。例如:

files = {"name": open("path_to_file", "rb")} #这样无法指定数据的content-type

应该这样files = {"name": ("filename", open("path_to_file", "rb", "multipart/form-data"))}

然而,这样写仍然无法解决同一个名称,多个文件的特殊情况。更强大的方式是这样:
files = [
("name",("filename", open("path_to_file", "rb"), "application/octet-stream")), ("name", ("filename",open("path_to_file2"), "rb", "text/plain"))]

这里,两个元素的name可以相等。

那么还有更普遍的情况,上述样本中,filename所在参数还可以为None。这样可以消掉filename的属性(用在非文件数据上传的情况)。

files = [
("name1": (None, "my_value")),
("name2": ("filename", open("path_to_file", "rb"), "multipart/form-data"))]

strapi的populate应用

populate主要应用于数据库检索时跨collection的检索信息。但是在实际工程应用中,为了提升性能,我们不需要跨collection收集信息。在strapi应用中可以采用这样的方法做到。

第一,在相关模型的setting里面的相关relation里配置autoPopulation为false。例如:

in file: /api/category/models/category.settings.json

{                                                                                                                                                              
  "connection": "default",                                                                                                                                     
  "collectionName": "categories",                                                                                                                              
  "info": {                                                                                                                                                      
    "name": "category"                                                                                                                                         
  },   
...                                                                                                                                                                                                                                                                                                     
  "attributes": {                                                                                                                                                
    "name": {                                                                                                                                                      
      "type": "string",                                                                                                                                            
      "default": "unnamed",                                                                                                                                        
      "unique": true,                                                                                                                                              
      "required": true,                                                                                                                                            
      "minLength": 1,                                                                                                                                              
      "maxLength": 128                                                                                                                                           
    }, 
...                                                                                                                                                          
    "models": {                                                                                                                                                    
      "via": "categories",                                                                                                                                         
      "collection": "model",                                                                                                                                       
      "autoPopulate": false                                                                                                                                      
    }                                                                                                                                                          
  }                                                                                                                                                          }    

在此示例中,即关闭了检索category时对models的自动populate操作,除非显示的给query函数指定populate = ['models']

第二,如果在配置了autoPopulate为false之后,你需要对该relation做populate操作,那么比较好的实践方法是重写该模型的controller,然后在controller中对service调用的时候,把populate作为参数传递给query函数。例如:

await strapi.services.category.find(parm, ['models'])

以上代码则显式的把['models']参数作为populate参数传递给了service里面的query函数

strapi中service vs config/function

在strapi中,service和config/function的定义有些相近,都是定义一些公用的函数共其他模块调用。但这两则其实还是有应用上的不同。

  1. service侧重于对数据库访问和api中的应用。
  2. config/function则不同,相比而言config/function更泛用一些,当然也更侧重于配置一些。