You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在TypeScript+Sequelize中通过关联RefreshToken查询Account的正确方法

TypeScript + Sequelize 一对多关联实现方案

需求说明

需要为Account与RefreshToken模型建立一对多关联,实现两个核心查询:

  • 查询指定Account关联的所有RefreshToken
  • 通过RefreshToken(实例或token字符串)查询对应的Account

现有代码问题

initModels.ts

const Account = _Account.initModel(sequelize);
const RefreshToken = _RefreshToken.initModel(sequelize);
Account.hasMany(RefreshToken);
RefreshToken.belongsTo(Account);

目标查询函数(当前写法错误)

export const getAccountByRefreshToken = (token: RefreshToken) => {
     // 此处写法错误,目标是通过RefreshToken获取对应的Account
    return Account.findOne({ where: { refreshToken: token } });
};

RefreshToken.ts(精简版)

import { DataTypes, Sequelize, Model, Optional } from "sequelize";
import sequelizeConnection from "../Database";

interface RefreshTokenAttributes {
    accountId: number; // 待关联Account模型
    token: string;
}

export type RefreshTokenOptionalAttributes = "createdAt" | "updatedAt" | "deletedAt";
export type RefreshTokenCreationAttributes = Optional<RefreshTokenAttributes, RefreshTokenOptionalAttributes>;

export class RefreshToken extends Model<RefreshTokenAttributes, RefreshTokenCreationAttributes> implements RefreshTokenAttributes {
    public accountId!: number;
    public token!: string;

    static initModel(sequelize: Sequelize): typeof RefreshToken {
        return RefreshToken.init(
            {
                accountId: {
                    type: DataTypes.INTEGER, // 待关联用户模型
                    autoIncrement: true,
                    primaryKey: true,
                },
                token: {
                    type: DataTypes.STRING,
                    allowNull: false,
                },
            },
        );
    }
}

Account.ts(精简版)

import { DataTypes, Sequelize, Model, Optional } from "sequelize";
import sequelizeConnection from "../Database";

interface AccountAttributes {
    id: number;
    email: string;
}

export type AccountOptionalAttributes = "createdAt" | "updatedAt" | "deletedAt";
export type AccountCreationAttributes = Optional<AccountAttributes, AccountOptionalAttributes>;

export class Account extends Model<AccountAttributes, AccountCreationAttributes> implements AccountAttributes {
    public id!: number;
    public email!: string;

    static initModel(sequelize: Sequelize): typeof Account {
        return Account.init(
            {
                id: {
                    type: DataTypes.INTEGER,
                    autoIncrement: true,
                    primaryKey: true,
                },
                email: {
                    type: DataTypes.STRING,
                    allowNull: false,
                },
            },
        );
    }
}

修正步骤

1. 修复模型字段定义

RefreshToken.ts 修正

  • 移除accountId的autoIncrement和primaryKey配置,添加独立主键id
  • 为accountId添加外键关联配置,指定关联Account的id字段
  • 确保init方法传入sequelize参数
  • 添加关联属性声明,让TypeScript识别关联关系
import { DataTypes, Sequelize, Model, Optional } from "sequelize";
import sequelizeConnection from "../Database";
import type { Account } from "./Account";

interface RefreshTokenAttributes {
    id: number; // 添加独立主键
    accountId: number;
    token: string;
}

export type RefreshTokenOptionalAttributes = "createdAt" | "updatedAt" | "deletedAt";
export type RefreshTokenCreationAttributes = Optional<RefreshTokenAttributes, RefreshTokenOptionalAttributes | "id">;

export class RefreshToken extends Model<RefreshTokenAttributes, RefreshTokenCreationAttributes> implements RefreshTokenAttributes {
    public id!: number;
    public accountId!: number;
    public token!: string;

    // 声明关联属性,TypeScript类型支持
    public readonly account?: Account;

    static initModel(sequelize: Sequelize): typeof RefreshToken {
        return RefreshToken.init(
            {
                id: {
                    type: DataTypes.INTEGER,
                    autoIncrement: true,
                    primaryKey: true,
                },
                accountId: {
                    type: DataTypes.INTEGER,
                    allowNull: false,
                    references: {
                        model: "accounts", // 对应Account模型的表名(默认复数)
                        key: "id",
                    },
                    onDelete: "CASCADE", // 可选:删除Account时同步删除关联的RefreshToken
                },
                token: {
                    type: DataTypes.STRING,
                    allowNull: false,
                    unique: true, // 建议token设为唯一
                },
            },
            {
                sequelize,
                tableName: "refreshTokens", // 可选:显式指定表名
            }
        );
    }
}

Account.ts 补充关联声明

添加关联属性声明,让TypeScript识别关联的RefreshToken集合:

import { DataTypes, Sequelize, Model, Optional } from "sequelize";
import sequelizeConnection from "../Database";
import type { RefreshToken } from "./RefreshToken";

interface AccountAttributes {
    id: number;
    email: string;
}

export type AccountOptionalAttributes = "createdAt" | "updatedAt" | "deletedAt";
export type AccountCreationAttributes = Optional<AccountAttributes, AccountOptionalAttributes>;

export class Account extends Model<AccountAttributes, AccountCreationAttributes> implements AccountAttributes {
    public id!: number;
    public email!: string;

    // 声明关联属性,TypeScript类型支持
    public readonly refreshTokens?: RefreshToken[];

    static initModel(sequelize: Sequelize): typeof Account {
        return Account.init(
            {
                id: {
                    type: DataTypes.INTEGER,
                    autoIncrement: true,
                    primaryKey: true,
                },
                email: {
                    type: DataTypes.STRING,
                    allowNull: false,
                    unique: true,
                },
            },
            {
                sequelize,
                tableName: "accounts",
            }
        );
    }
}

2. 完善关联配置(initModels.ts)

显式指定关联的外键和别名,避免Sequelize自动推断出错:

import { Sequelize } from "sequelize";
import { Account as _Account } from "./Account";
import { RefreshToken as _RefreshToken } from "./RefreshToken";

export const initModels = (sequelize: Sequelize) => {
    const Account = _Account.initModel(sequelize);
    const RefreshToken = _RefreshToken.initModel(sequelize);

    // 一对多关联:一个Account对应多个RefreshToken
    Account.hasMany(RefreshToken, {
        foreignKey: "accountId", // 指定外键字段
        as: "refreshTokens", // 关联别名,对应Account模型里的refreshTokens属性
    });

    // 多对一关联:一个RefreshToken属于一个Account
    RefreshToken.belongsTo(Account, {
        foreignKey: "accountId",
        as: "account", // 关联别名,对应RefreshToken模型里的account属性
    });

    return { Account, RefreshToken };
};

3. 修正查询函数

方式1:通过RefreshToken实例查询Account

import { RefreshToken } from "./RefreshToken";

export const getAccountByRefreshToken = async (tokenInstance: RefreshToken) => {
    // 使用Sequelize自动生成的关联方法获取Account
    return tokenInstance.getAccount();
};

方式2:通过token字符串查询对应的Account

import { RefreshToken } from "./RefreshToken";

export const getAccountByRefreshTokenString = async (token: string) => {
    // 先查询RefreshToken,再关联获取Account
    const refreshToken = await RefreshToken.findOne({
        where: { token },
        include: ["account"], // 关联查询Account
    });
    return refreshToken?.account;
};

查询Account关联的所有RefreshToken

import { Account } from "./Account";

export const getRefreshTokensByAccount = async (accountId: number) => {
    const account = await Account.findByPk(accountId, {
        include: ["refreshTokens"], // 关联查询所有RefreshToken
    });
    return account?.refreshTokens;
};

关键说明

  1. Sequelize识别关联的逻辑:

    • 通过hasMany和belongsTo方法建立关联关系,指定外键后,Sequelize会自动处理表之间的关联查询逻辑
    • 模型初始化时必须传入sequelize实例,否则无法将模型与数据库连接绑定
    • 外键字段配置references可在数据库层面建立外键约束,保证数据一致性(可选但推荐)
  2. TypeScript类型支持:

    • 在模型类中声明关联属性(如public readonly account?: Account),让TypeScript能识别关联方法和属性
    • 使用as指定关联别名,保持模型属性与关联配置一致,避免类型报错

内容的提问来源于stack exchange,提问作者plutownium

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 05:35:23