|
@@ -5,7 +5,6 @@
|
|
|
* - 负责飞书OAuth2登录用户的获取或创建
|
|
* - 负责飞书OAuth2登录用户的获取或创建
|
|
|
* - SessionToken管理
|
|
* - SessionToken管理
|
|
|
* - 用户信息同步和更新
|
|
* - 用户信息同步和更新
|
|
|
- * - 支持多公司/组织用户管理
|
|
|
|
|
*
|
|
*
|
|
|
* @module user-manager
|
|
* @module user-manager
|
|
|
* @author fmode
|
|
* @author fmode
|
|
@@ -29,7 +28,6 @@ export interface UserSessionInfo {
|
|
|
mobile?: string;
|
|
mobile?: string;
|
|
|
email?: string;
|
|
email?: string;
|
|
|
avatar: string;
|
|
avatar: string;
|
|
|
- company: any;
|
|
|
|
|
sessionToken: string;
|
|
sessionToken: string;
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
@@ -61,16 +59,9 @@ export class FeishuUserManager {
|
|
|
*/
|
|
*/
|
|
|
async getUserInfoSessionToken(userInfo: FeishuUserInfo, appId: string): Promise<UserSessionInfo> {
|
|
async getUserInfoSessionToken(userInfo: FeishuUserInfo, appId: string): Promise<UserSessionInfo> {
|
|
|
try {
|
|
try {
|
|
|
- const appConfig = this.configManager.getAppConfig(appId);
|
|
|
|
|
- const company = appConfig.company;
|
|
|
|
|
|
|
+ console.log(`处理飞书用户登录,unionId: ${userInfo.unionId}, openId: ${userInfo.openId}`);
|
|
|
|
|
|
|
|
- if (!company) {
|
|
|
|
|
- throw new Error(`应用配置中缺少company信息: ${appId}`);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- console.log(`处理飞书用户登录,unionId: ${userInfo.unionId}, openId: ${userInfo.openId}, company: ${company}`);
|
|
|
|
|
-
|
|
|
|
|
- let user = await this.findOrCreateUser(userInfo, company);
|
|
|
|
|
|
|
+ let user = await this.findOrCreateUser(userInfo);
|
|
|
|
|
|
|
|
const sessionToken = await this.generateSessionToken(user);
|
|
const sessionToken = await this.generateSessionToken(user);
|
|
|
|
|
|
|
@@ -88,7 +79,6 @@ export class FeishuUserManager {
|
|
|
mobile: user.get('mobile'),
|
|
mobile: user.get('mobile'),
|
|
|
email: user.get('email'),
|
|
email: user.get('email'),
|
|
|
avatar: user.get('avatar'),
|
|
avatar: user.get('avatar'),
|
|
|
- company: user.get('company'),
|
|
|
|
|
sessionToken: sessionToken.get('sessionToken'),
|
|
sessionToken: sessionToken.get('sessionToken'),
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
@@ -104,18 +94,17 @@ export class FeishuUserManager {
|
|
|
*
|
|
*
|
|
|
* @async
|
|
* @async
|
|
|
* @param userInfo - 飞书用户信息
|
|
* @param userInfo - 飞书用户信息
|
|
|
- * @param company - 公司ID
|
|
|
|
|
* @returns Promise<any> Parse用户对象
|
|
* @returns Promise<any> Parse用户对象
|
|
|
* @throws {Error} 查找或创建失败时抛出错误
|
|
* @throws {Error} 查找或创建失败时抛出错误
|
|
|
*/
|
|
*/
|
|
|
- async findOrCreateUser(userInfo: FeishuUserInfo, company: string): Promise<any> {
|
|
|
|
|
|
|
+ async findOrCreateUser(userInfo: FeishuUserInfo): Promise<any> {
|
|
|
try {
|
|
try {
|
|
|
- let username = `${company}_${userInfo.openId}`;
|
|
|
|
|
|
|
+ let username = `feishu_${userInfo.openId}`;
|
|
|
|
|
|
|
|
- let user = await this.findUserByUnionId(userInfo.unionId, company);
|
|
|
|
|
|
|
+ let user = await this.findUserByUnionId(userInfo.unionId);
|
|
|
|
|
|
|
|
if (!user) {
|
|
if (!user) {
|
|
|
- user = await this.findUserByOpenId(userInfo.openId, company);
|
|
|
|
|
|
|
+ user = await this.findUserByOpenId(userInfo.openId);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (!user) {
|
|
if (!user) {
|
|
@@ -123,7 +112,7 @@ export class FeishuUserManager {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (!user && userInfo.mobile) {
|
|
if (!user && userInfo.mobile) {
|
|
|
- user = await this.findUserByMobile(userInfo.mobile, company);
|
|
|
|
|
|
|
+ user = await this.findUserByMobile(userInfo.mobile);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (user) {
|
|
if (user) {
|
|
@@ -133,7 +122,7 @@ export class FeishuUserManager {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
console.log(`创建新用户,unionId: ${userInfo.unionId}`);
|
|
console.log(`创建新用户,unionId: ${userInfo.unionId}`);
|
|
|
- return await this.createNewUser(userInfo, company);
|
|
|
|
|
|
|
+ return await this.createNewUser(userInfo);
|
|
|
|
|
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error('查找或创建用户失败:', error);
|
|
console.error('查找或创建用户失败:', error);
|
|
@@ -159,10 +148,9 @@ export class FeishuUserManager {
|
|
|
*
|
|
*
|
|
|
* @async
|
|
* @async
|
|
|
* @param unionId - 飞书用户unionId
|
|
* @param unionId - 飞书用户unionId
|
|
|
- * @param company - 公司ID
|
|
|
|
|
* @returns Promise<any | null>
|
|
* @returns Promise<any | null>
|
|
|
*/
|
|
*/
|
|
|
- async findUserByUnionId(unionId: string, company: string): Promise<any | null> {
|
|
|
|
|
|
|
+ async findUserByUnionId(unionId: string): Promise<any | null> {
|
|
|
try {
|
|
try {
|
|
|
const Parse = (global as any).Parse;
|
|
const Parse = (global as any).Parse;
|
|
|
if (!Parse) {
|
|
if (!Parse) {
|
|
@@ -171,11 +159,6 @@ export class FeishuUserManager {
|
|
|
|
|
|
|
|
const query = new Parse.Query("_User");
|
|
const query = new Parse.Query("_User");
|
|
|
query.equalTo("data.feishu.unionId", unionId);
|
|
query.equalTo("data.feishu.unionId", unionId);
|
|
|
- query.equalTo("company", {
|
|
|
|
|
- __type: 'Pointer',
|
|
|
|
|
- className: 'Company',
|
|
|
|
|
- objectId: company
|
|
|
|
|
- });
|
|
|
|
|
|
|
|
|
|
return await query.first({ useMasterKey: true });
|
|
return await query.first({ useMasterKey: true });
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
@@ -189,10 +172,9 @@ export class FeishuUserManager {
|
|
|
*
|
|
*
|
|
|
* @async
|
|
* @async
|
|
|
* @param openId - 飞书用户openId
|
|
* @param openId - 飞书用户openId
|
|
|
- * @param company - 公司ID
|
|
|
|
|
* @returns Promise<any | null>
|
|
* @returns Promise<any | null>
|
|
|
*/
|
|
*/
|
|
|
- async findUserByOpenId(openId: string, company: string): Promise<any | null> {
|
|
|
|
|
|
|
+ async findUserByOpenId(openId: string): Promise<any | null> {
|
|
|
try {
|
|
try {
|
|
|
const Parse = (global as any).Parse;
|
|
const Parse = (global as any).Parse;
|
|
|
if (!Parse) {
|
|
if (!Parse) {
|
|
@@ -201,11 +183,6 @@ export class FeishuUserManager {
|
|
|
|
|
|
|
|
const query = new Parse.Query("_User");
|
|
const query = new Parse.Query("_User");
|
|
|
query.equalTo("data.feishu.openId", openId);
|
|
query.equalTo("data.feishu.openId", openId);
|
|
|
- query.equalTo("company", {
|
|
|
|
|
- __type: 'Pointer',
|
|
|
|
|
- className: 'Company',
|
|
|
|
|
- objectId: company
|
|
|
|
|
- });
|
|
|
|
|
|
|
|
|
|
return await query.first({ useMasterKey: true });
|
|
return await query.first({ useMasterKey: true });
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
@@ -243,10 +220,9 @@ export class FeishuUserManager {
|
|
|
*
|
|
*
|
|
|
* @async
|
|
* @async
|
|
|
* @param mobile - 手机号
|
|
* @param mobile - 手机号
|
|
|
- * @param company - 公司ID
|
|
|
|
|
* @returns Promise<any | null>
|
|
* @returns Promise<any | null>
|
|
|
*/
|
|
*/
|
|
|
- async findUserByMobile(mobile: string, company: string): Promise<any | null> {
|
|
|
|
|
|
|
+ async findUserByMobile(mobile: string): Promise<any | null> {
|
|
|
try {
|
|
try {
|
|
|
const Parse = (global as any).Parse;
|
|
const Parse = (global as any).Parse;
|
|
|
if (!Parse) {
|
|
if (!Parse) {
|
|
@@ -255,11 +231,6 @@ export class FeishuUserManager {
|
|
|
|
|
|
|
|
const query = new Parse.Query("_User");
|
|
const query = new Parse.Query("_User");
|
|
|
query.equalTo("mobile", mobile);
|
|
query.equalTo("mobile", mobile);
|
|
|
- query.equalTo("company", {
|
|
|
|
|
- __type: 'Pointer',
|
|
|
|
|
- className: 'Company',
|
|
|
|
|
- objectId: company
|
|
|
|
|
- });
|
|
|
|
|
|
|
|
|
|
return await query.first({ useMasterKey: true });
|
|
return await query.first({ useMasterKey: true });
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
@@ -315,11 +286,10 @@ export class FeishuUserManager {
|
|
|
*
|
|
*
|
|
|
* @async
|
|
* @async
|
|
|
* @param userInfo - 飞书用户信息
|
|
* @param userInfo - 飞书用户信息
|
|
|
- * @param company - 公司ID
|
|
|
|
|
* @returns Promise<any> Parse用户对象
|
|
* @returns Promise<any> Parse用户对象
|
|
|
* @throws {Error} 创建失败时抛出错误
|
|
* @throws {Error} 创建失败时抛出错误
|
|
|
*/
|
|
*/
|
|
|
- async createNewUser(userInfo: FeishuUserInfo, company: string): Promise<any> {
|
|
|
|
|
|
|
+ async createNewUser(userInfo: FeishuUserInfo): Promise<any> {
|
|
|
try {
|
|
try {
|
|
|
const Parse = (global as any).Parse;
|
|
const Parse = (global as any).Parse;
|
|
|
if (!Parse) {
|
|
if (!Parse) {
|
|
@@ -329,7 +299,7 @@ export class FeishuUserManager {
|
|
|
const UserClass = Parse.Object.extend("_User");
|
|
const UserClass = Parse.Object.extend("_User");
|
|
|
const user = new UserClass();
|
|
const user = new UserClass();
|
|
|
|
|
|
|
|
- const username = `${company}_${userInfo.openId}`;
|
|
|
|
|
|
|
+ const username = `feishu_${userInfo.openId}`;
|
|
|
const defaultPassword = this.generateDefaultPassword(username);
|
|
const defaultPassword = this.generateDefaultPassword(username);
|
|
|
|
|
|
|
|
user.set("username", username);
|
|
user.set("username", username);
|
|
@@ -340,12 +310,6 @@ export class FeishuUserManager {
|
|
|
user.set("type", 'user');
|
|
user.set("type", 'user');
|
|
|
user.set("status", 'normal');
|
|
user.set("status", 'normal');
|
|
|
|
|
|
|
|
- user.set("company", {
|
|
|
|
|
- __type: 'Pointer',
|
|
|
|
|
- className: 'Company',
|
|
|
|
|
- objectId: company
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
user.set("data", {
|
|
user.set("data", {
|
|
|
feishu: {
|
|
feishu: {
|
|
|
unionId: userInfo.unionId,
|
|
unionId: userInfo.unionId,
|
|
@@ -372,7 +336,7 @@ export class FeishuUserManager {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 生成sessionToken
|
|
|
|
|
|
|
+ * 生成sessionToken(优化版:复用未过期的token)
|
|
|
*
|
|
*
|
|
|
* @async
|
|
* @async
|
|
|
* @param user - Parse用户对象
|
|
* @param user - Parse用户对象
|
|
@@ -386,14 +350,41 @@ export class FeishuUserManager {
|
|
|
throw new Error('Parse不可用');
|
|
throw new Error('Parse不可用');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const SessionClass = Parse.Object.extend('ession');
|
|
|
|
|
|
|
+ // 查询用户现有的有效 sessionToken
|
|
|
|
|
+ const query = new Parse.Query('_Session');
|
|
|
|
|
+ query.equalTo('user', {
|
|
|
|
|
+ __type: 'Pointer',
|
|
|
|
|
+ className: '_User',
|
|
|
|
|
+ objectId: user.id
|
|
|
|
|
+ });
|
|
|
|
|
+ query.greaterThan('expiresAt', new Date());
|
|
|
|
|
+ query.descending('expiresAt');
|
|
|
|
|
+
|
|
|
|
|
+ const existingSession = await query.first({ useMasterKey: true });
|
|
|
|
|
+
|
|
|
|
|
+ // 如果存在有效的 session,检查剩余有效期
|
|
|
|
|
+ if (existingSession) {
|
|
|
|
|
+ const expiresAt = existingSession.get('expiresAt');
|
|
|
|
|
+ const now = new Date();
|
|
|
|
|
+ const remainingTime = expiresAt.getTime() - now.getTime();
|
|
|
|
|
+ const twoHoursInMs = 2 * 60 * 60 * 1000; // 2小时的毫秒数
|
|
|
|
|
+
|
|
|
|
|
+ // 如果剩余有效期大于等于2小时,复用现有的 sessionToken
|
|
|
|
|
+ if (remainingTime >= twoHoursInMs) {
|
|
|
|
|
+ console.log(`复用现有SessionToken for user: ${user.id}, 剩余有效期: ${(remainingTime / 1000 / 60 / 60).toFixed(2)}小时`);
|
|
|
|
|
+ return existingSession;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 创建新的 sessionToken
|
|
|
|
|
+ const SessionClass = Parse.Object.extend('_Session');
|
|
|
const session = new SessionClass();
|
|
const session = new SessionClass();
|
|
|
|
|
|
|
|
const salt = user.id + '_' + (new Date().getTime() / 1000).toFixed();
|
|
const salt = user.id + '_' + (new Date().getTime() / 1000).toFixed();
|
|
|
const md5 = crypto.createHash('md5').update(salt, 'utf8').digest('hex');
|
|
const md5 = crypto.createHash('md5').update(salt, 'utf8').digest('hex');
|
|
|
const sessionToken = "r:" + md5;
|
|
const sessionToken = "r:" + md5;
|
|
|
|
|
|
|
|
- console.log(`生成SessionToken: ${sessionToken} for user: ${user.id}`);
|
|
|
|
|
|
|
+ console.log(`生成新SessionToken: ${sessionToken} for user: ${user.id}`);
|
|
|
|
|
|
|
|
session.set("user", {
|
|
session.set("user", {
|
|
|
__type: 'Pointer',
|
|
__type: 'Pointer',
|