Sails 模型设置(Model Settings)完全指南:从属性定义到数据库迁移策略

Sails 模型设置(Model Settings)完全指南:从属性定义到数据库迁移策略 Sails 模型设置Model Settings完全指南从属性定义到数据库迁移策略【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址: https://gitcode.com/gh_mirrors/sa/sailsSails 是一个基于 Node.js 的实时 MVC 框架而模型Model则是其 ORMWaterline层的核心抽象。模型定义文件如api/models/User.js中的顶层属性被称为模型设置Model settings它们决定了模型如何描述数据、连接哪个数据库、如何迁移表结构以及如何序列化记录。本文以官方文档 docs/concepts/ORM/model-settings.md 为主体结合仓库源码与配置样板逐一剖析 Sails 支持的全部模型设置项并给出可直接落地的配置示例与最佳实践。读完本文你将掌握如何在config/models.js中设置全局默认模型参数如何针对单个模型覆盖这些参数以及attributes、tableName、migrate、schema、datastore、dataEncryptionKeys等各项设置的语义、默认值与适用场景。概述模型设置是什么模型设置允许你定制 Sails 应用中模型的行为。它们既可以作为模型定义中的顶层属性逐模型指定也可以作为应用级默认值写在sails.config.models中按惯例放在config/models.js配置文件里。修改默认模型设置要修改应用中所有模型共享的默认模型设置编辑config/models.js即可。例如生成一个新应用时Sails 会自动在config/models.js中内置三个默认属性id、createdAt和updatedAt。如果你希望所有模型都使用一个稍作定制的id属性只需在config/models.js中覆盖attributes: { id: { ... } }即可// config/models.js module.exports.models { attributes: { id: { type: number, autoIncrement: true }, createdAt: { type: number, autoCreatedAt: true }, updatedAt: { type: number, autoUpdatedAt: true }, }, };为特定模型覆盖设置要进一步针对某个模型定制设置可以在该模型的定义文件如api/models/User.js中把它们写成顶层属性。同名属性会覆盖默认模型设置。例如如果你在某个模型定义api/models/UploadedFile.js中加入fetchRecordsOnUpdate: true那么该模型的.update()调用将返回被更新的记录而其余模型不受影响仍会使用默认值除非你修改了默认值否则fetchRecordsOnUpdate默认是false。// api/models/UploadedFile.js module.exports { attributes: { /* ... */ }, fetchRecordsOnUpdate: true, };如何选择配置方式在日常开发中你最常打交道的模型设置是attributes——几乎每个模型定义都会用到它且config/models.js中也包含默认属性。以下是几条经验法则tableName永远按模型单独指定应用级的表名没有意义因为表是物理层的概念必须逐模型映射。datastore通常无需全局配置开箱即用已有一个名为default的内置 datastore。但某些场景下你可能想为特定模型覆盖它——比如默认 datastore 是 PostgreSQL但你希望CachedBloodworkReport模型的数据落在 Redis 里。migrate与schema只建议作为应用级默认值不要逐模型指定以免迁移策略和模式不一致。attributes模型属性的定义集一个模型的全部属性定义。这是模型定义中最核心、最常用的设置。attributes: { /* ... */ }类型示例默认值((dictionary))见下方示例{}大多数情况下你在各自的模型定义api/models/下中声明属性但也可以在config/models.js中指定默认属性。这样可以在一个地方定义全局属性集合Sails 会隐式地让所有模型共享它们无需重复编写。默认属性也可以被逐模型覆盖——在相关模型定义中声明一个同名替换属性即可。attributes: { id: { type: number, autoIncrement: true }, createdAt: { type: number, autoCreatedAt: true }, updatedAt: { type: number, autoUpdatedAt: true }, }从源码角度看Sails 的核心 ORM 层Waterline正是通过这些属性定义来构建查询、执行校验与关联解析的属性中的type、required、defaultsTo、autoIncrement、columnName等子设置共同决定了数据库列字段的形态。属性本身的完整定义方式与用法参见 Concepts ORM Attributes。customToJSON自定义记录序列化一个函数用于定制模型记录序列化为 JSON 的方式。customToJSON: function() { /*...*/ }类型示例默认值((function))见下方示例n/a给模型加上customToJSON设置会改变该模型记录的字符串化stringified行为——即每当这些记录被传入JSON.stringify()时都会先执行你注入的自定义逻辑。它最常见的用途是实现保险机制确保用户密码等敏感数据不会被意外包含在响应中因为res.send()以及 actions2 在发送数据前可能会先执行字符串化。customToJSON函数不接收任何参数但可以通过this变量访问当前记录。这样你可以剔除敏感数据并返回净化后的结果JSON.stringify()会使用该返回值生成 JSON 字符串。例如customToJSON: function() { // 返回这条记录的浅拷贝并移除 password 与 ssn 字段。 return _.omit(this, [password, ssn]) }请注意customToJSON被设计为不支持异步能力这保证了核心中的同步代码路径保持同步从而为整个系统提供更好的稳定性。⚠️customToJSON中的this是对实际记录对象的直接引用务必小心不要修改它。也就是说避免写delete this.password这类代码应改用_.omit()或_.pick()获取记录的副本或者直接构造并返回一个新的字典如return { foo: this.foo }。tableName物理表集合名映射模型存储、检索记录时所用的 SQL 表/MongoDB 集合名称其记录对应行/MongoDB 文档。tableName: some_preexisting_table类型示例默认值((string))some_preexisting_table与模型 identity 相同tableName让你自定义某个模型应使用的底层物理模型名称——换句话说它控制模型在数据库中存储、检索记录的位置同时不影响控制器动作 / helpers 中的代码。默认情况下Sails 使用模型的 identity即文件名小写去扩展名来决定表名await User.find(); // SELECT * FROM user;这是一种推荐约定多数场景下无需更改。但如果你要共享一个由其他平台如 Python、C#编写的既有应用遗留数据库或者团队偏好不同的表命名规范那么自定义这个映射就很有用。回到上面的例子如果你在api/models/User.js中设置tableName: foo_bar查询结果会变成await User.find(); // SELECT * FROM foo_bar;关于tableName的命名在 MySQL、PostgreSQL 等数据库中它指字面意义上的 table在 MongoDB 中则指 collection。这只是用语习惯的差异——table 换个名字查询起来并无不同。在仓库的测试夹具中也能看到这一用法test/integration/fixtures/sampleapp/api/models/User.js与Pet.js均通过tableName指定了对应的物理表并被test/integration/hook.blueprints.*.test.js等用例用于验证路由与查询行为。migrate自动迁移策略Sails 每次加载应用时会运行的自动迁移策略。migrate: alter类型示例默认值((string))alter启动时会提示你选择。注意生产环境下始终为safe。migrate控制应用的自动迁移策略简而言之它告诉 Sails 是否要尝试自动重建数据库中的表/集合/set 等物理结构。数据库迁移的两种模式开发过程中你几乎总会对数据库结构做出至少一两次破坏性变更breaking change。何为破坏性变更取决于所用数据库例如你向模型定义新增了一个属性——如果该模型使用 MongoDB这毫无影响可以继续开发但如果使用 MySQL则多了一个步骤必须向对应表添加一列否则.create()等模型方法将无法工作。因此对使用 MySQL 的模型而言新增属性就是对数据库 schema 的破坏性变更。即便所有模型都使用 MongoDB仍有一些破坏性 schema 变更需要留意。例如给某个属性加上unique: true就必须在 MongoDB 中创建对应的唯一索引。在 Sails 中数据库迁移有两种操作模式手动迁移Manual migrations手工更新数据库表/集合/set 的艺术。例如编写 SQL 语句新增列或发送 MongoDB 命令创建唯一索引。如果数据库中含有你在意的数据比如生产环境你必须仔细考虑这些数据是否需要随新 schema 调整必要时编写脚本迁移。虽然存在一些优秀的开源迁移工具与托管服务但官方推荐全部手动迁移配合sails run执行。自动迁移Auto-migrationsSails 内置的便捷特性允许开发期间迭代修改模型定义而不必担心后果。自动迁移绝不应在连接含重要数据的数据库时启用只应配合假数据或可轻易重建的缓存数据使用。需要向生产数据库应用破坏性变更时务必使用手动迁移而在本地开发或运行自动化测试时自动迁移能省下大量时间。自动迁移的工作原理在开发环境 lift 你的 Sails 应用例如在一个全新应用中运行sails lift时配置的自动迁移策略就会执行使用migrate: safe不会有额外动作使用drop或alterSails 会把开发数据库中的每条记录加载进内存然后丢弃并重建数据的物理层表示表/集合/set 等这样模型定义中的任何破坏性变更如移除唯一性约束都会被自动应用到开发数据库使用alterSails 还会尝试用之前保存的记录重新填充新生成的结构。自动迁移策略说明safe绝不自动迁移我的数据库我自己手工处理。alter自动迁移列/字段但尝试保留已有数据实验性。drop每次 lift Sails 都清空/丢弃全部数据并重建模型。注意使用alter或drop策略时你上次 lift 应用以来对数据库所做的任何手工修改都可能丢失包括自定义索引、外键约束、列顺序与注释等。一般而言自动迁移创建的表不保证在物理列细节上保持一致只保证列名、类型含指定的字符集/编码和唯一性。能在生产环境使用自动迁移吗drop和alter是 Sails 为开发期与自动化测试提供的便利特性并非为承载重要数据而设计。请务必不要对生产数据集使用drop或alter。作为防止误操作的保险任何时候在生产环境 lift 应用Sails总是使用migrate: safe无论你配置了什么。从仓库源码可以印证这一点lib/app/configuration/load.js 中处理了--safe、--alter、--drop三个命令行快捷方式分别将models.migrate映射为safe、alter、drop而 lib/app/load.js 则检查 Sails 环境是否为production若NODE_ENV未同步设为production会直接抛出E_INVALID_NODE_ENV错误。这意味着生产模式下的数据安全有框架层面的双重保障。另外许多托管平台检测到 Node.js 应用时会自动把NODE_ENV设为production。即便如此也不要只依赖这层保险请采取常规措施保护用户数据任何一次连接含既有生产数据的数据库Sails 或其他工具/框架都要先做一次试运行dry run尤其是第一次。生产数据敏感、珍贵且多数不可再生。最佳实践是除非 100% 确定运行在生产环境否则绝不要用生产数据库凭据 lift 或部署应用。业界常见的组织级解法是根本不要把生产数据库凭据提交到源码仓库所有敏感凭据都通过环境变量注入对受监管约束的应用或代码库可访问人数众多的情况尤其推荐。自动迁移会很慢吗如果开发/测试数据量相对较大alter策略可能在启动阶段耗时较长。如果你发现npm test、sails console或sails lift疑似卡住考虑缩减开发数据集规模。记住Sails 自动迁移只应用于本地开发机且只用于小型开发数据集。schema是否强制记录符合属性集模型是否期望记录符合一组特定的属性。schema: true类型示例默认值((boolean))true取决于所用 adapterschema设置用于在 schemaless无模式与 schemaful有模式之间切换模型。更具体地说它约束.create()、.update()等方法的写入行为正常情况下只要 adapter 支持你可以在记录中存储任意数据但启用schema: true后只有与模型attributes对应的属性才会被真正存储。此设置仅对使用无模式数据库如 MongoDB的模型有意义。接入 MySQL、PostgreSQL 等关系型数据库时模型实际上永远是schema: true因为底层数据库只能在预先建好的表和列中存储数据。在sails.config.models的默认值表中该设置的默认值是false若设为trueORM 会切换到 schemaful 模式传入.create()、.createEach()或.update()的不对应属性在保存前会被剔除。datastore模型使用的数据存储模型用于查找、创建记录等操作时所使用的 datastore 配置名称。datastore: legacyECommerceDb类型示例默认值((string))legacyECommerceDbdefault这让你指定该模型读写数据的数据库。除非另有指定应用中每个模型都使用名为default的内置 datastore——每个新 Sails 应用都开箱自带。这让你可以方便地配置应用主数据库同时仍允许为任何特定模型覆盖datastore。datastore 的完整配置方式参见 Reference Configuration Datastores。dataEncryptionKeys数据加密密钥一组用于解密数据的密钥。除非另行配置default数据加密密钥简称 DEK总是用于加密。dataEncryptionKeys: { default: tVdQbq2JptoPp4oXGT94kKqF72iV0VKY/cnp7SjL7Ik }除非你的场景需要密钥轮换key rotation否则default密钥就足够了。default之外的任何数据加密密钥只是为了能够解密那些用旧密钥加密的历史数据。密钥轮换Key Rotation要退役一个数据加密密钥你需要给它一个新的密钥 id如2028然后创建新的default密钥用于之后的所有新加密。例如假设你的 Sails 应用在 2028 年发布密钥按年轮换那么次年你的dataEncryptionKeys可能长这样dataEncryptionKeys: { default: DZ7MslaooGub3pS/0O734yeyPTAeZtd0Lrgeswwlt0s, 2028: C5QAkA46HD9pK0m7293V2CzEVlJeSUXgwmxBAQVjxU }再下一年2030 年 1 月更换 default 密钥后你可能会得到dataEncryptionKeys: { default: tVdQbq2JptoPp4oXGT94kKqF72iV0VKY/cnp7SjL7Ik, 2029: DZ7MslaooGub3pS/0O734yeyPTAeZtd0Lrgeswwlt0s, 2028: C5QAkA46HD9pK0m7293V2CzEVlJeSUXgwmxBAQVjxU }密钥轮换机制保证了加密数据的平滑过渡新数据用新default密钥加密旧数据仍可用保留的旧密钥解密。cascadeOnDestroy级联删除开关是否在使用该模型调用.destroy()时总是表现得像设置了cascade: true。cascadeOnDestroy: true类型示例默认值((boolean))truefalse出于性能考虑默认是关闭的。你可以通过这个模型设置开启它也可以在单次查询层面用.meta({cascade: true})控制。dontUseObjectIds禁用 MongoDB ObjectID 主键_此特性仅用于sails-mongoadapter。_设为true时模型将不使用自动生成的 MongoDB ObjectID 对象作为主键。这允许你使用sails-mongoadapter 创建主键为任意字符串或数字而非长长的 UUID 样式值的模型。注意设为true意味着你必须在每次.create()或.createEach()调用中自行提供id的值。类型示例默认值((boolean))truefalse同样出于性能考虑默认关闭可在模型设置中开启或按查询用.meta({dontUseObjectIds: true})控制。极少使用的设置以下底层设置仅出于完整性收录实践中应很少甚至从不改动。primaryKey主键属性名模型主键属性的名称。你绝不需要修改这个设置。如需自定义主键请给 id 属性设置自定义columnName。primaryKey: id类型示例默认值((string))idid按惯例它就是id是 Sails v1.0 起生成的新应用在config/models.js中自动包含的默认属性。修改模型主键的最佳方式就是定制该默认属性的columnName。例如设想一个 User 模型需要对接既有 MySQL 数据库中的一张表该表的主键列不叫 id 而叫 email_address。要让模型尊重该主键你可以在模型定义中对id属性做如下覆盖id: { type: string, columnName: email_address, required: true }这样在你的应用代码里依然按主键查找用户而所有生成的 SQL 查询都会自动映射到email_addressawait User.find({ id: req.param(emailAddress) });附带说明如果你是 MongoDB 重度用户可在config/models.js中先给默认 id 属性设置columnName: _id然后像平常一样使用 Sails 和 Waterline一切正常。但如果你希望把 id 属性本身的名字也改成 _id例如想用_.destroy({ _id: ba8319abd-13810-ab31815 })这种语法可以在config/models.js中设置primaryKey: _id并把默认 id 属性改名为 _id。不过这样做有一些值得再三考虑的理由——官方文档明确建议优先采用columnName方案因为直接改属性名会影响所有内置模型方法调用处的写法。identity模型小写唯一标识模型的小写唯一标识符。模型的identity是只读的。它由系统自动推导绝不应手工设置。Something.identity;类型示例((string))purchase在 Sails 中模型的identity通过把文件名转为小写并去掉扩展名自动推导。例如api/models/Purchase.js的 identity 是purchase可通过sails.models.purchase访问若启用了蓝图路由还可以用GET /purchase、PATCH /purchase/1等请求访问它。assert(Purchase.identity purchase); assert(sails.models.purchase.identity purchase); assert(Purchase sails.models.purchase);globalId模型全局唯一标识模型的全局唯一标识符同时决定其对应全局变量如果启用的名称。模型的globalId是只读的。它由系统自动推导绝不应手工设置。Something.globalId;类型示例((string))PurchaseglobalId的主要用途是决定 Sails 自动暴露的全局变量的名字——除非模型的全局化已被禁用。Sails 会从文件名自动推导globalId例如api/models/Purchase.js的 globalId 是Purchase。assert(Purchase.globalId Purchase); assert(sails.models.purchase.globalId Purchase); if (sails.config.globals.models) { assert(sails.models.purchase Purchase); } else { assert(typeof Purchase undefined); }配置优先级小结综合全文Sails 模型设置遵循一套清晰的优先级规则应用级默认值写在config/models.js的sails.config.models中作用于全部模型参见 sails.config.models 属性表包括attributes、migrate、schema、datastore、primaryKey、archiveModelIdentity等。模型级覆盖写在api/models/*.js的顶层覆盖同名的全局默认值如tableName、customToJSON、cascadeOnDestroy、dontUseObjectIds、dataEncryptionKeys。查询级元数据部分行为如cascade、dontUseObjectIds还可以通过.meta({...})在单次查询中临时开启。环境级强约束生产环境下migrate被强制为safe框架层面兜底保护数据安全。理解了这四层规则你就能精准地在正确的位置放置每一项设置全局的放config/models.js模型专属的放模型定义文件临时的放查询 meta安全兜底交给框架——从而写出结构清晰、行为可控的 Sails 数据层代码。延伸阅读模型Models概念模型的声明、查询方法与自定义模型方法属性Attributes属性类型的完整定义关联Associations模型间关系建模sails.config.models参考应用级模型默认配置属性表Datastores 配置参考数据存储连接配置【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址: https://gitcode.com/gh_mirrors/sa/sails创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考