用戶組織權(quán)限管理系統(tǒng)
技術(shù)棧:
- 前端:Vue + ElementUi + TypeScript
- 后端:nest.js + mysql + redis
演示地址
功能設(shè)計
BSp-blog.png
數(shù)據(jù)庫設(shè)計
用戶實體
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 500 })
name: string;
@Column({nullable: true, type: 'text'})
desc: string;
@Column({
nullable: true,
length: 100,
select: false,
})
password: string;
@Column( {select: false} )
email: string;
@Column({nullable: true})
age: string;
@Column({nullable: true})
address: string;
@Column({nullable: true})
nick: string;
@Column({default: 0})
status: number;
@ManyToOne(type => Role, role => role.users)
role: Role;
@ManyToMany( type => Organization, orientation => orientation.users)
organizations: Organization[];
@Column({default: 0})
isDelete: number;
@Column({default: '', nullable: true })
crateTime: string;
@Column({default: '', nullable: true })
updateTime: string;
@Column({default: '', nullable: true })
deleteTime: string;
}
角色實體
@Entity()
export class Role {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 500 })
name: string;
@Column('text', {nullable: true})
desc: string;
@Column()
code: string;
@ManyToMany(type => Authority, authority => authority.roles)
@JoinTable()
authority: Authority[];
@OneToMany(type => User, user => user.role)
users: User[];
@Column({default: 0})
isDelete: number;
@Column({default: '', nullable: true })
crateTime: string;
@Column({default: '', nullable: true })
updateTime: string;
@Column({default: '', nullable: true })
deleteTime: string;
}
資源實體
@Entity()
export class Authority {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 500 })
name: string;
@Column('text', {nullable: true})
desc: string;
@Column()
path: string;
@Column()
value: string;
@Column()
parentId: number;
@Column({default: '', nullable: true })
parentName: string;
@Column({nullable: true})
icon: string;
@Column({nullable: false})
system: string;
@Column()
code: string;
@ManyToMany(type => Role, role => role.authority)
roles: Role[];
@Column({default: 0})
isDelete: number;
@Column({default: '', nullable: true })
crateTime: string;
@Column({default: '', nullable: true })
updateTime: string;
@Column({default: '', nullable: true })
deleteTime: string;
}
API實現(xiàn)
數(shù)據(jù)庫配置(main.module.ts)
TypeOrmModule.forRoot(
{
type: 'mysql',
host: mysqlConfig.host,
port: 3306,
username: mysqlConfig.userName,
password: mysqlConfig.password,
database: 'b_simple_user_center',
entities: [join(__dirname, '**/**.entity{.ts,.js}')],
synchronize: true,
},
),
全局緩存
CacheModule.register({
store: redisStore,
host: redisCacheConfig.host,
port: redisCacheConfig.port,
ttl: redisCacheConfig.ttl, // seconds
max: redisCacheConfig.max, // seconds
}),
業(yè)務(wù)層實現(xiàn)
該系統(tǒng)中使用typeorm操作數(shù)據(jù)庫,常見的typeorm操作方式包Entity Manager 和Query Builder,結(jié)合系統(tǒng)多條件查詢場景,因此采用Query Builder方式,控制層和服務(wù)層為一一對應(yīng)關(guān)系,代碼內(nèi)參見src/controller、src/service
采坑點
nest多條件查詢
...
const queryConditionList = ['c.isDelete = :isDelete'];
if (query.name) {
queryConditionList.push('c.name LIKE :name');
}
const queryCondition = queryConditionList.join(' AND ');
const res = await this.productBrandRepository
.createQueryBuilder('c')
.where(queryCondition, {
name: `%${query.name}%`,
isDelete: 0,
})
.orderBy('c.name', 'ASC')
.skip((query.page - 1) * query.pageSize)
.take(query.pageSize)
.getManyAndCount();
...
typeorm實體間任意聯(lián)查
typeorm中聯(lián)查任意實體(在實體關(guān)系間沒有表關(guān)聯(lián))時,getManyAndCount無法查詢關(guān)聯(lián)字段,必須采用getRawAndEntities,typeorm文檔書寫有誤。
...
const res = await this.productAttributeRepository
.createQueryBuilder('c')
.leftJoinAndSelect(ProductAttributeCategoryEntity, 'a', 'a.id = c.product_attribute_category_id')
.where(queryCondition, {
name: `%${query.name}%`,
isDelete: 0,
productAttributeCategoryId: Number(query.cateAttrId),
type: Number(query.type),
})
.orderBy('c.name', 'ASC')
.skip((query.page - 1) * query.pageSize)
.take(query.pageSize)
.getRawAndEntities();
...
JWT用戶認證拆分單獨服務(wù)
用戶系統(tǒng)中與身份認證拆分成單獨的服務(wù)(網(wǎng)關(guān)服務(wù))