| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649 |
- import { Injectable } from '@angular/core';
- import { FmodeParse, FmodeObject } from 'fmode-ng/parse';
- import * as ww from '@wecom/jssdk';
- import { WxworkCorp } from 'fmode-ng/core';
- const Parse = FmodeParse.with('nova');
- export interface WxworkCurrentChat {
- type: 'chatId' | 'userId';
- id?: string;
- contact?: any;
- follow_user?: any;
- group?: any;
- }
- /**
- * 企微SDK服务
- * 封装企业微信JSSDK功能
- */
- @Injectable({
- providedIn: 'root'
- })
- export class WxworkSDKService {
- // 企业配置映射
- private companyMap: any = {
- 'cDL6R1hgSi': { // 映三色
- corpResId: 'SpL6gyD1Gu'
- }
- };
- // 应用套件映射
- private suiteMap: any = {
- 'crm': {
- suiteId: 'dk2559ba758f33d8f5'
- },
- 'project': { // 🔥 添加project应用的配置(使用相同的suiteId)
- suiteId: 'dk2559ba758f33d8f5'
- }
- };
- cid: string = '';
- appId: string = '';
- corpId: string = '';
- wecorp: WxworkCorp | null = null;
- ww = ww;
- registerUrl: string = '';
- constructor() {}
- /**
- * 初始化SDK
- */
- async initialize(cid: string, appId: string): Promise<void> {
- this.cid = cid;
- this.appId = appId;
- this.wecorp = new WxworkCorp(cid);
- await this.registerCorpWithSuite();
- }
- /**
- * 注册企业微信JSAPI
- */
- async registerCorpWithSuite(apiList?: string[]): Promise<boolean> {
- console.log('🔍 [registerCorpWithSuite] 开始注册JSSDK...');
- console.log('🔍 [registerCorpWithSuite] 平台检测:', this.platform());
-
- if (this.platform() !== 'wxwork') {
- console.warn('⚠️ [registerCorpWithSuite] 非企业微信环境,跳过注册');
- return false;
- }
- // 如果URL未变化且已注册,直接返回
- if (!apiList?.length && this.registerUrl === location.href) {
- console.log('✅ [registerCorpWithSuite] URL未变化,使用缓存的注册状态');
- return true;
- }
- apiList = apiList || this.getDefaultApiList();
- console.log('🔍 [registerCorpWithSuite] API列表:', apiList);
- try {
- console.log('🔍 [registerCorpWithSuite] 获取企业配置,CID:', this.cid);
- const corpConfig = await this.getCorpByCid(this.cid);
- console.log('🔍 [registerCorpWithSuite] 企业配置:', corpConfig);
-
- const suiteId = this.suiteMap[this.appId]?.suiteId;
- console.log('🔍 [registerCorpWithSuite] 套件ID:', suiteId);
- const now = new Date();
- return new Promise((resolve) => {
- console.log('🔍 [registerCorpWithSuite] 调用ww.register...');
- console.log('🔍 [registerCorpWithSuite] corpId:', corpConfig.corpId);
- console.log('🔍 [registerCorpWithSuite] agentId:', corpConfig.agentId);
- console.log('🔍 [registerCorpWithSuite] suiteId:', suiteId);
- console.log('🔍 [registerCorpWithSuite] url:', location.href);
-
- // 🔥 添加15秒超时机制
- const timeout = setTimeout(() => {
- console.warn('⚠️ [registerCorpWithSuite] 注册超时(15秒),可能原因:');
- console.warn(' 1. 回调函数未被触发');
- console.warn(' 2. 企业微信JSSDK加载失败');
- console.warn(' 3. 不在正确的群聊会话中');
- console.warn(' 4. 应用权限配置错误');
- resolve(false);
- }, 15000);
-
- ww.register({
- corpId: corpConfig.corpId,
- suiteId: suiteId,
- agentId: corpConfig.agentId,
- jsApiList: apiList!,
- getAgentConfigSignature: async () => {
- console.log('🔍 [registerCorpWithSuite] 获取签名...');
- const jsapiTicket = await this.wecorp!.ticket.get();
- console.log('🔍 [registerCorpWithSuite] Ticket:', jsapiTicket?.substring(0, 20) + '...');
-
- const signature = ww.getSignature({
- ticket: jsapiTicket,
- nonceStr: '666',
- timestamp: (now.getTime() / 1000).toFixed(0),
- url: location.href
- });
- console.log('🔍 [registerCorpWithSuite] 签名生成完成:', {
- nonceStr: '666',
- timestamp: (now.getTime() / 1000).toFixed(0),
- url: location.href
- });
- return signature;
- },
- onAgentConfigSuccess: () => {
- clearTimeout(timeout);
- console.log('✅ [registerCorpWithSuite] AgentConfig注册成功!');
- this.registerUrl = location.href;
- resolve(true);
- },
- onAgentConfigFail: (err: any) => {
- clearTimeout(timeout);
- console.error('❌ [registerCorpWithSuite] AgentConfig注册失败:', err);
- console.error('❌ 错误详情:', JSON.stringify(err, null, 2));
- console.error('❌ 请检查:');
- console.error(' 1. agentId是否正确');
- console.error(' 2. 应用是否已发布');
- console.error(' 3. jsapi_ticket是否有效');
- resolve(false);
- },
- onConfigFail: (err: any) => {
- clearTimeout(timeout);
- console.error('❌ [registerCorpWithSuite] Config注册失败:', err);
- console.error('❌ 错误详情:', JSON.stringify(err, null, 2));
- console.error('❌ 请检查:');
- console.error(' 1. corpId是否正确');
- console.error(' 2. suiteId是否正确');
- console.error(' 3. 企业是否已授权');
- resolve(false);
- }
- });
- });
- } catch (error) {
- console.error('❌ [registerCorpWithSuite] 注册过程出错:', error);
- return false;
- }
- }
- /**
- * 获取当前聊天对象
- */
- async getCurrentChatObject(): Promise<{
- GroupChat?: FmodeObject;
- Contact?: FmodeObject;
- currentChat: WxworkCurrentChat | null;
- }> {
- const currentChat = await this.getCurrentChat();
- if (!currentChat) {
- return { currentChat: null };
- }
- let GroupChat: FmodeObject | undefined;
- let Contact: FmodeObject | undefined;
- try {
- if (currentChat.type === 'chatId' && currentChat.group) {
- GroupChat = await this.syncGroupChat(currentChat.group);
- } else if (currentChat.type === 'userId' && currentChat.id) {
- const contactInfo = await this.wecorp!.externalContact.get(currentChat.id);
- Contact = await this.syncContact(contactInfo);
- }
- } catch (error) {
- console.error('getCurrentChatObject error:', error);
- }
- return { GroupChat, Contact, currentChat };
- }
- /**
- * 获取当前聊天场景
- */
- async getCurrentChat(): Promise<WxworkCurrentChat | null> {
- const isRegister = await this.registerCorpWithSuite();
- if (!isRegister) return null;
- try {
- const context = await ww.getContext();
- const entry = context?.entry;
- let type: 'chatId' | 'userId';
- let id: string | undefined;
- let contact: any;
- let chat: any;
- if (entry === 'group_chat_tools') {
- type = 'chatId';
- id = (await ww.getCurExternalChat())?.chatId;
- chat = await this.wecorp!.externalContact.groupChat.get(id!);
- } else if (entry === 'contact_profile' || entry === 'single_chat_tools') {
- type = 'userId';
- id = (await ww.getCurExternalContact())?.userId;
- contact = await this.wecorp!.externalContact.get(id!);
- } else {
- return null;
- }
- return {
- type,
- id,
- group: chat?.group_chat,
- contact: contact?.external_contact,
- follow_user: contact?.follow_user
- };
- } catch (error) {
- console.error('getCurrentChat error:', error);
- return null;
- }
- }
- /**
- * 获取当前用户信息
- */
- async getCurrentUser(): Promise<FmodeObject | null> {
- const userInfo = await this.getUserinfo();
- if (!userInfo) return null;
- return await this.getContactOrProfile(userInfo);
- }
- /**
- * 获取用户信息
- */
- async getUserinfo(code?: string): Promise<any> {
- // 优先检查缓存
- if (!code) {
- const userInfoStr = localStorage.getItem(`${this.cid}/USERINFO`);
- if (userInfoStr) {
- const userInfo = JSON.parse(userInfoStr);
- userInfo.cid = this.cid;
- return userInfo;
- }
- }
- // 从URL获取code
- const url = new URL(location.href);
- code = url.searchParams.get('code') || code;
- if (!code) return null;
- const result = await this.wecorp!.auth.getuserinfo(code);
- if (result?.errcode) {
- console.error(result?.errmsg);
- return null;
- }
- // 补全外部用户信息
- if (result?.external_userid) {
- const euser = await this.wecorp!.externalContact.get(result.external_userid);
- if (euser?.external_contact) {
- Object.assign(result, euser.external_contact);
- }
- }
- result.cid = this.cid;
- // 缓存用户信息
- localStorage.setItem(`${this.cid}/USERINFO`, JSON.stringify(result));
- return result;
- }
- /**
- * 同步群聊信息
- */
- async syncGroupChat(groupInfo: any): Promise<FmodeObject> {
- let query = new Parse.Query('GroupChat');
- query.equalTo('chat_id', groupInfo?.chat_id);
- let group = await query.first();
- if (!group?.id) {
- group = new Parse.Object('GroupChat');
- }
- // 生成入群方式
- if (!group?.get('joinUrl')) {
- const config_id1 = (await this.wecorp!.externalContact.groupChat.addJoinWay({
- scene: 1,
- chat_id_list: [groupInfo.chat_id]
- }))?.config_id;
- const joinUrl = (await this.wecorp!.externalContact.groupChat.getJoinWay(config_id1))?.join_way;
- group.set('joinUrl', joinUrl);
- }
- if (!group?.get('joinQrcode')) {
- const config_id2 = (await this.wecorp!.externalContact.groupChat.addJoinWay({
- scene: 2,
- chat_id_list: [groupInfo.chat_id]
- }))?.config_id;
- const joinQrcode = (await this.wecorp!.externalContact.groupChat.getJoinWay(config_id2))?.join_way;
- group.set('joinQrcode', joinQrcode);
- }
- // 更新群聊数据
- let needSave = false;
- if (group.get('chat_id') !== groupInfo.chat_id) needSave = true;
- if (group.get('name') !== groupInfo.name) needSave = true;
- if (group.get('owner') !== groupInfo.owner) needSave = true;
- if (group.get('notice') !== groupInfo.notice) needSave = true;
- if (group.get('member_version') !== groupInfo.member_version) {
- needSave = true;
- group.set('member_list', groupInfo.member_list);
- group.set('member_version', groupInfo.member_version);
- }
- group.set({
- chat_id: groupInfo.chat_id,
- name: groupInfo.name,
- owner: groupInfo.owner,
- notice: groupInfo.notice
- });
- if (this.cid) {
- group.set('company', { __type: 'Pointer', className: 'Company', objectId: this.cid });
- }
- if (needSave) {
- group = await group.save();
- }
- return group;
- }
- /**
- * 同步联系人信息
- */
- async syncContact(contactInfo: any): Promise<FmodeObject> {
- const externalContact = contactInfo.external_contact || contactInfo;
- const externalUserId = externalContact.external_userid;
- let query = new Parse.Query('ContactInfo');
- query.equalTo('external_userid', externalUserId);
- const Company = new Parse.Object('Company');
- Company.id = this.cid;
- query.equalTo('company', Company);
- let contact = await query.first();
- if (!contact?.id) {
- contact = new Parse.Object('ContactInfo');
- if (Company?.id) {
- contact.set('company', Company.toPointer());
- }
- }
- const name = externalContact.name || '';
- const mobile = externalContact.mobile || '';
- const data: any = {
- ...externalContact,
- follow_user: contactInfo.follow_user || externalContact.follow_user || []
- };
- let needSave = false;
- if (contact.get('external_userid') !== externalUserId) needSave = true;
- if (contact.get('name') !== name && name) needSave = true;
- if (contact.get('mobile') !== mobile && mobile) needSave = true;
- const oldData = contact.get('data');
- if (JSON.stringify(oldData) !== JSON.stringify(data)) needSave = true;
- contact.set('external_userid', externalUserId);
- contact.set('name', name);
- contact.set('mobile', mobile);
- contact.set('data', data);
- if (needSave) {
- contact = await contact.save();
- }
- return contact;
- }
- /**
- * 获取Profile或UserSocial
- */
- async getContactOrProfile(userInfo: any): Promise<FmodeObject> {
- let UserType: string;
- if (userInfo.openid || userInfo.external_userid) {
- UserType = 'UserSocial';
- } else if (userInfo.userid) {
- UserType = 'Profile';
- } else {
- throw new Error('Invalid user info');
- }
- // 构建查询条件
- const userCondition: any[] = [];
- const prefix = UserType === 'UserSocial' ? 'data.' : '';
- if (userInfo.openid) userCondition.push({ [`${prefix}openid`]: { $regex: userInfo.openid } });
- if (userInfo.userid) userCondition.push({ [`${prefix}userid`]: { $regex: userInfo.userid } });
- if (userInfo.mobile) userCondition.push({ [`${prefix}mobile`]: { $regex: userInfo.mobile } });
- if (userInfo.email) userCondition.push({ [`${prefix}email`]: { $regex: userInfo.email } });
- if (userInfo.external_userid) {
- userCondition.push({ [`${prefix}external_userid`]: { $regex: userInfo.external_userid } });
- }
- const query = Parse.Query.fromJSON(UserType, {
- where: {
- $or: userCondition
- }
- });
- query.equalTo('company', this.cid);
- let thisUser = await query.first();
- if (!thisUser?.id) {
- thisUser = new Parse.Object(UserType);
- }
- // 关联当前登录用户
- const current = Parse.User.current();
- if (current?.id && !thisUser?.get('user')?.id) {
- thisUser.set('user', current.toPointer());
- }
- return thisUser;
- }
- /**
- * 创建群聊
- */
- async createGroupChat(options: {
- groupName: string;
- userIds?: string[];
- externalUserIds?: string[];
- }): Promise<any> {
- const isRegister = await this.registerCorpWithSuite();
- if (!isRegister) return null;
- return new Promise((resolve, reject) => {
- ww.createCorpGroupChat({
- groupName: options.groupName,
- userIds: options.userIds,
- externalUserIds: options.externalUserIds,
- success: (data: any) => {
- resolve(data);
- },
- fail: (err: any) => {
- reject(err);
- }
- });
- });
- }
- /**
- * 添加成员到群聊
- */
- async addUserToGroup(options: {
- chatId: string;
- userIds?: string[];
- externalUserIds?: string[];
- }): Promise<any> {
- const isRegister = await this.registerCorpWithSuite();
- if (!isRegister) return null;
- return new Promise((resolve, reject) => {
- (ww as any).updateCorpGroupChat({
- chatId: options.chatId,
- userIds: options.userIds,
- externalUserIds: options.externalUserIds,
- success: (data:any) => {
- resolve(data);
- },
- fail: (err:any) => {
- reject(err);
- }
- });
- });
- }
- /**
- * 打开指定群聊
- */
- async openChat(chatId: string): Promise<void> {
- const isRegister = await this.registerCorpWithSuite();
- if (!isRegister) return;
- return new Promise((resolve, reject) => {
- ww.openEnterpriseChat({
- externalUserIds: [],
- groupName: '',
- chatId: chatId,
- success: () => {
- resolve();
- },
- fail: (err: any) => {
- reject(err);
- }
- });
- });
- }
- /**
- * 选择企业联系人
- */
- async selectEnterpriseContact(options?: {
- mode?: 'single' | 'multi';
- type?: Array<'department' | 'user'>;
- }): Promise<any> {
- const isRegister = await this.registerCorpWithSuite();
- if (!isRegister) return null;
- return new Promise((resolve, reject) => {
- (ww as any).selectEnterpriseContact({
- fromDepartmentId: -1,
- mode: options?.mode || 'multi',
- type: options?.type || ['department', 'user'],
- success: (data:any) => {
- resolve(data);
- },
- fail: (err:any) => {
- reject(err);
- }
- });
- });
- }
- /**
- * 获取企业配置
- */
- private async getCorpByCid(cid: string): Promise<any> {
- if (this.corpId) return { corpId: this.corpId };
- const query = new Parse.Query('CloudResource');
- const res = await query.get(this.companyMap[cid]?.corpResId);
- const config: any = res.get('config');
- return config;
- }
- /**
- * 判断平台
- */
- private platform(): string {
- const ua = navigator.userAgent.toLowerCase();
- if (ua.indexOf('wxwork') > -1) return 'wxwork';
- if (ua.indexOf('wechat') > -1) return 'wechat';
- return 'h5';
- }
- /**
- * 发送消息到当前聊天窗口
- */
- async sendChatMessage(options: {
- msgtype: 'text' | 'image' | 'news';
- text?: { content: string };
- news?: { link: string; title: string; desc: string; imgUrl: string };
- }): Promise<void> {
- console.log('🔍 [sendChatMessage] ========== 开始发送消息 ==========');
- console.log('🔍 [sendChatMessage] 消息类型:', options.msgtype);
- console.log('🔍 [sendChatMessage] 消息内容:', JSON.stringify(options, null, 2));
-
- console.log('🔍 [sendChatMessage] 开始注册JSSDK...');
- const isRegister = await this.registerCorpWithSuite(['sendChatMessage']);
- console.log('🔍 [sendChatMessage] JSSDK注册结果:', isRegister);
-
- if (!isRegister) {
- console.error('❌ [sendChatMessage] JSSDK注册失败');
- console.error('❌ 可能原因:');
- console.error(' 1. 企业微信配置错误(corpId, suiteId, agentId)');
- console.error(' 2. ticket获取失败');
- console.error(' 3. URL签名错误');
- console.error(' 4. 网络问题');
- throw new Error('JSSDK注册失败');
- }
- console.log('🔍 [sendChatMessage] 调用ww.sendChatMessage...');
- return new Promise((resolve, reject) => {
- ww.sendChatMessage({
- ...options,
- success: (res: any) => {
- console.log('✅ [sendChatMessage] 消息发送成功!');
- console.log('✅ [sendChatMessage] 响应数据:', res);
- resolve();
- },
- fail: (err: any) => {
- console.error('❌ [sendChatMessage] 消息发送失败!');
- console.error('❌ [sendChatMessage] 错误详情:', err);
- console.error('❌ [sendChatMessage] 错误消息:', err?.errMsg);
- console.error('❌ [sendChatMessage] 错误代码:', err?.errCode);
-
- // 提供详细的错误提示
- if (err?.errMsg?.includes('no permission')) {
- console.error('❌ 权限不足!请在企业微信管理后台开启sendChatMessage权限');
- } else if (err?.errMsg?.includes('not in session')) {
- console.error('❌ 不在聊天会话中!请从群聊工具或客户资料入口打开应用');
- }
-
- reject(err);
- }
- } as any);
- });
- }
- /**
- * 获取默认API列表
- */
- private getDefaultApiList(): string[] {
- return [
- 'getContext',
- 'getCurExternalChat',
- 'getCurExternalContact',
- 'createCorpGroupChat',
- 'updateCorpGroupChat',
- 'openEnterpriseChat',
- 'selectEnterpriseContact',
- 'checkJsApi',
- 'chooseImage',
- 'previewImage',
- 'uploadImage',
- 'downloadImage',
- 'getLocation',
- 'openLocation',
- 'scanQRCode',
- 'closeWindow',
- 'sendChatMessage' // 🔥 添加发送消息权限
- ];
- }
- }
|