# 致信后端开发文档
# 一、搭建A8教程
# 1、从 git 上下载 A8 的环境代码
在同级目录将JDK 复制过来(交接文档中有),目录结构为:
# 2、连接数据库
# 2.1 首先通过svn 拿到最新的数据库ALL-IN-ONE 的SQL
# 2.2 打开Navicat ,新建连接,输入数据库的IP 、 端口 、 用户名 和密码
# 2.3 新建数据库a8_trunk ,选择UTF-8
# 2.4 右键数据库a8_trunk ,选择运行SQL 文件,将之前下载的ALL-IN-ONE 文件运行
# 2.5 执行完成后,点击关闭
# 2.6 打开A8\v5-resource\conf 的SeeyonConfig.cmd 文件
# 2.7 选择第二个页签(数据库连接),修改数据库相关参数,然后点击确定进行保存
# 3、配置致信环境
打开A8\v5-resource\bin 目录,将交接文档中的app.txt 复制到此目录
# 4、配置调试端口
打开A8\v5-resource\bin 目录的catalina.bat 文件,在图中文件增加一行调试信息
# 5、启动A8
运行A8\v5-resource\bin 目录的startup.bat 文件,启动A8
# 二、开发搭建教程
# 1、环境搭建
# 1.1 安装 JDK 1.8
教程网址:https://blog.csdn.net/lduzhenlin/article/details/105425719(注意记得要配置JAVA_HOME 和Path ,要不然IDEA不一定能识别。)
# 1.2 安装IDEA
直接去官网,下载社区版IDEA(免费版)
# 1.3 配置Maven
# 1.3.1 Maven安装
教程网址:https://www.runoob.com/maven/maven-setup.html
# 1.3.2 IDEA 配置 Maven
(1) 将交接文档中的settings-seeyon.xml 文件放到Maven 的conf 目录下。 (2) 打开IDEA 的File-Settings 菜单,修改Maven 相关配置
# 1.4 配置远程 Debug
# 1.4.1 点击 Add Configuration 这个按钮
# 1.4.2 点击+号,选择Remote
# 1.4.3 输入A8 的地址和调试端口,点击OK
# 1.4.4 最后点击此按钮开启调试,此时A8 必须是启动完成状态
# 2、源码使用
# 2.1 下载源码
8.0SP1 及以前版本从 svn 上下载代码,8.0SP2 及以后版本从 git 上下载代码
# 2.2 导入源码到工程
# 2.2.1 svn 方式
打开Idea ,新建一个 Empty Project ,通过svn 或者git 的方式(根据A8 版本自行选择)将代码导入到新建的目录中
# 2.2.2 git 方式
通过自带的命令行工具(推荐)或者界面化工具把代码下载到工程目录。打开IDEA,新建Module ,将代码导入工程中。
# 3、代码开发
# 3.1 Spring 规范
# 3.1.1 Spring 配置文件
在controller 、 manager 、 dao 层创建的时候,需要在对应的spring-zx-rong-Shape52 Shape53 Shape54 controller.xml 、 spring-zx-rong-manager.xml 、 spring-zx-rong-dao.xml 文件中新增定义。
# 3.1.1 Spring Bean使用
- log--群操作日志数据库操作ZxAuthDao
- ZxAuthDaoImpl--人员授权数据库操作
- ZxBroadcastIssueBodyDao--老版本小广播代码--废弃
- ZxBroadcastIssueBodyDaoImpl--老版本小广播代码--废弃
- ZxBroadcastIssueDao--老版本小广播代码--废弃
- ZxBroadcastIssueDaoImpl--老版本小广播代码--废弃
- ZxBroadcastIssueScopeDao--老版本小广播代码--废弃
- ZxBroadcastIssueScopeDaoImpl--老版本小广播代码--废弃
- ZxBroadcastManagerDao--老版本小广播代码--废弃
- ZxBroadcastManagerDaoImpl--老版本小广播代码--废弃
- ZxBroadcastManagerGroupDao--老版本小广播代码--废弃
- ZxBroadcastManagerGroupDaoImpl--老版本小广播代码--废弃
- ZxBroadcastManagerScopeDao--老版本小广播代码--废弃
- ZxBroadcastManagerScopeDaoImpl--老版本小广播代码--废弃
- ZxConfigCacheImpl--致信相关配置缓存ZxConfigDao
- ZxConfigDaoImpl--致信相关配置数据库操作
- ZxGroupDao
- ZxGroupDaoImpl--群组数据库操作ZxRongCache
- ZxRongCacheImpl--致信相关缓存
- ZxTokenDao
- ZxTokenDaoImpl--token数据库操作enums--相关枚举
- event--致信创建头像监听exception--致信自定义异常io--融云的sdk
- job--定时任务
- listener--人员离职、部门群变化等监听manager
- auth--致信人员授权
- chatmessage--合并聊天记录config--致信相关配置
- doc--文档中心保存合并聊天记录extra--创建群组头像
- file--致信文件操作groupauth--群文件权限job--定时任务
- jrmf--红包littlebroadcast--小广播lock--分布式锁
- log--群操作日志手机messagecard--消息卡片online--在线状态
- org--多维组织shortmenu--磁贴
- ticket--双击头像ticket管理
- ctp
- usermessage--业务消息获取videoauth--音视频
- xiaoz--智能问答S2SRongManager
- S2SRongManagerImpl--和融云sdk集成
- UcOrgManager
- UcOrgManagerImpl--致信组织架构(大部分已废弃) ZxGroupManager
- ZxGroupManagerImpl--群组管理
- ZxManager ZxManagerImpl--废弃ZxMsgSwitchManager
- ZxMsgSwitchManagerImpl--system业务消息接收设置ZxPcMessagePipeline--消息通道(已废弃) ZxRestManager
- ZxRestManagerImpl--部分组织架构获取ZxRobotManager ZxRobotManagerImpl--机器人消息ZxRongManager
- ZxRongManagerImpl--token管理models--老版本的一些bean
- po--数据库对象dangmessage--小广播对象groupauth--群文件权限对象log--群操作日志对象
- ZxAuthUser--致信人员授权对象ZxBroadcastIssue--老版本小广播代码--废弃ZxBroadcastIssueBody--老版本小广播代码--废弃ZxBroadcastIssueScope--老版本小广播代码--废弃ZxBroadcastManager--老版本小广播代码--废弃
- ZxBroadcastManagerGroup--老版本小广播代码--废弃ZxBroadcastManagerScope--老版本小广播代码--废弃ZxConfigItem--致信配置对象
- ZxGroupInfo--群组对象ZxGroupMember--群成员对象ZxToken--token对象
- service--一些老代码sso
- ZxSSOLoginHandshakeImpl--双击头像打开A8的检验(重点)
- utils--工具类vo--展示类
- ZxInitializer--致信插件启动入口ZxPluginInitializer
- UcResource--rest接口,主要是提供systemConfig UcRongResource--rest接口,给m3提供相关接口
- s2s--老版本代码目录--废弃
# 3.1.2 SQL 提交流程
(1) 提交协同表单 (2) 所属工程组选apps-uc (3) 选择SQL 变更类型
- 如果选择增加表,需要填写表名和相关字段,下面举例了新增表的格式 表名通过下划线来分割,如ZX_DES。其中主键定义为ID ,字段类型是BIGINT ,设置为主键。
- 若字段是字符串类型,定义为VARCHAR(255)。
- 若字段是日期类型,定义为DATETIME
- 若字段是数字类型(比如定义状态值),定义为SMALLINT sql示例: (4) 提交flyway 在工程目录apps-uc 下的sql_files 文件夹下有mysql 、 oracle 、 postgresql 、 sqlserver 目录,需要在每个目录下都得新建一个SQL 文件,名字为Vx standard_V8.1_apps-uc.sql ,其中x 指的是版本号,如果这个目录下没有文件,定义x 为2;如果目录下已经有SQL 文件,则x 为当前SQL文件,不同SQL数据库 对应的SQL 都可以在上面协同单子里自动生成。
# 3.1.3 PO 类和 HBM 文件定义
(1) PO 类命名规范和表名映射,每个单词的首字母大写,如包含下划线,则取消下划线,把下划线右边的第一个字母大写。如表名为ZX_DES ,则类名为ZxDes (2) 字段映射:
- BIGINT 类型对应Long 类型
- VARCHAR 类型对应String 类型
- DATETIME 类型对应java.sql.Timestamp
- SMALLINT 类型对应Integer ZxDes 类声明(写在po 包里):
//继承BasePo,可以不用声明主键,在BasePo中已经定义,并且提供生成主键的方法public class ZxDes extends BasePO {
//定义字段
/**
描述
*/
private String desName;
/**
创建日期
*/
private Timestamp createDate;
/**
状态
*/
private int desStatus;
//声明get/set方法
public String getDesName() { return desName;
}
public void setDesName(String desName) { this.desName = desName;
}
public Timestamp getCreateDate() { return createDate;
}
public void setCreateDate(Timestamp createDate) { this.createDate = createDate;
}
public int getDesStatus() { return desStatus;
}
public void setDesStatus(int desStatus) { this.desStatus = desStatus;
}
}
- HBM 文件命名规范是PO类名.hbm.xml如类名为ZxDes ,则类名为ZxDes.hbm.xml
字段映射:
- Long 类型对应long 类型
- String 类型对应string 类型
- java.sql.Timestamp 类型对应timestamp
- Integer 类型对应integer ZxDes 类的HBM 文件声明(和ZxDes 类在同级目录):
<class
name="com.seeyon.apps.zx.po.ZxDes" table="zx_des"
lazy="false"
<id
name="id" type="long" column="id" length="20"
<property
name="desName" type="string" column="des_name" length="255"
/>
<property
name="createDate" type="timestamp" column="create_date" length="19"
/>
<property
name="desStatus" type="integer" column="DES_STATUS"
# 3.1.4 DAO 定义
以消息免打扰数据操作为例,展示下如何使用
- 声明接口
- 生成实现类并且继承BaseHibernateDao ,其中t 代表数据库操作对象
- 利用BaseHibernateDao 的内置方法来简化开发常用方法:对于使用BaseHibernateDao 无法解决的需求,可以使用DbAgent 进行编写sql处理常用代码:
# 3.2 缓存使用规范
# 3.2.1 声明缓存变量
以缓存用户id和ticket为例:
- 首先在ZxRongCache类中定义下缓存的类型和名字,一般类型就是CacheMap<K,V>
- 在init方法中通过factory.createMap()的方式创建对象
缓存有全量加载缓存和按需加载缓存两种:
- 全量加载缓存需要在初始化的时候就把所有数据加载到缓存中使用factory.createMap("zxUserTicketCache", null);的方式来创建缓存
- 按需加载缓存不需要在初始化的时候把数据加载到缓存中,但是需要实现MapDataLoader这个类来做到自动从数据库加载的过程
# 3.2.2 缓存使用
# 3.2.3 跨模块调用
对于需要使用到非当前模块的功能(如组织架构、会议、文件等),需要根据不同模块使用下面两种方 案来调用。
- 公共模块:如组织架构、文件服务,这种模块在maven 中与apps-uc 进行了相互依赖,所以apps-uc 工程可以直接调用到此模块的类,如OrgManager 、 FileManager 、 AttachmentManager
- 独立模块:如会议、文档中心、小智,这种模块没有在Maven 中和apps-uc 进行依赖,所以需要调用公共的依赖模块apps-api 来相互调用。在你需要调用他们的接口的时候,可以使用如MeetingApi 、 DocAPi 、 XiaozQaApi 等接口来进行调用。如果你需要给他们提供接口调用,可以在ZxApi 和ZxApiImpl 中分别编写接口和实现类,供各个模块使用。
# 3.3 Controller 使用规范
以ZxRestController 为例,举例说明:
- Controller接口的前缀路径是 http(s)😕/ip:port/seeyon
- 因为在spring-zx-rong-controller.xml文件中,映射路径为/uc/rest.do,所以这个类的路径是 http(s)😕/ip:port/seeyon/uc/rest.do 比如要获取群组信息,使用groupInfo方法进行获取,这个方法的路径就是http(s)😕/ip:port/seeyon/uc/rest.do?method=groupInfo 参数读取方式 url 传参:如http(s)😕/ip:port/seeyon/uc/rest.do? body 传参:仅适合POST 请求
public ModelAndView createLittleBroadcast(HttpServletRequest request, HttpServletResponse response) {
Map<String, Object> result = Maps.newHashMap(); try {
Long currentMemberId = getCurrentMemberId();
//使用ZxUtils.getRequestBodyMap(request)的方式获取
Map<String, Object> condition = ZxUtils.getRequestBodyMap(request); String content = (String) condition.get("content");
String files = condition.get("files") == null ? "" : (String) condition.get("files");
String receiverMemberIds = (String) condition.get("receiverMemberIds");
zxLittleBroadcastManager.createLittleBroadcast(currentMemberId, content, files, receiverMemberIds);
result.put(ZxConstants.STATUS, ZxConstants.SUCCESSED);
} catch (Throwable e) { log.error(e.getMessage(), e);
result.put(ZxConstants.STATUS, ZxConstants.FAILED);
}
outResult(response, result); return null;
}
参数返回:调用outResult() 方法,将需要返回的数据填入
# 3.4 Rest 使用规范
以UcRongResource 为例,举例说明:
- rest 接口的前缀路径是http(s)😕/ip:port/seeyon/rest
- 在Resource 类上增加@Path 注解来声明这个类的路径,如uc/rong
- 在Resource 类上增加@Produces 注解来声明这个类的方法的返回值类型,如MediaType.APPLICATION_JSON 代表是json
- 在方法上增加@Path 注解来声明这个方法的路径,如getToken
- 在方法上增加@GET 或者@POST 注解来声明这个方法的请求类型最终url 就是http(s)😕/ip:port/seeyon/rest/uc/rong/getToken
@Path("uc/rong")
@Produces({ MediaType.APPLICATION_JSON })
public class UcRongResource extends BaseResource {
private static Log log = LogFactory.getLog(UcRongResource.class);
//获取对象的过程中,使用AppContext.getBean("beanName");的方式获取对象private ZxRongManager zxRongManager = (ZxRongManager)
AppContext.getBean("zxRongManager");
@Context HttpServletRequest req; @Context HttpServletResponse resp;
/**
@Description: 获取用户融云 token
@return Map[code:状态值,token:用户的融云token]
*/ @GET
@Path("getToken")
@Produces({ MediaType.APPLICATION_JSON }) public Response getRongToken() {
Map<String, Object> result = new HashMap<String, Object>();
参数获取方式:
- 路径参数:如http(s)😕/ip:port/seeyon/rest/uc/rong/groups/bymid/1234567
- url 传参:如http(s)😕/ip:port/seeyon/rest/uc/rong/upload/avatar?avatarType=1
//通过req.getParameter()的方式获取参数数据String avatarType =Optional.of(req.getParameter("avatarType")).get();
AvatarType avatarTypeEnum = AvatarType.valueOf(avatarType.toUpperCase());
Long groupId = -1L;
String groupIdStr = req.getParameter("groupId"); if (Strings.isNotBlank(groupIdStr)) {
groupId = Long.valueOf(groupIdStr);
}
CommonsMultipartResolver resolver = (CommonsMultipartResolver) Optional.of(AppContext.getBean("multipartResolver")).get();
HttpServletRequest req2 = resolver.resolveMultipart(req); V3XFile v3xFile = Optional.of(zxFileManager.uploadAvatar(req2,
avatarTypeEnum, groupId, user.getId())).get(); result.put("file", v3xFile);
result.put(ZxConstants.STATUS, ZxConstants.SUCCESSED);
} catch (Exception e) {
result.put(ZxConstants.STATUS, ZxConstants.FAILED);
}
return ok(result);
}
body 传参:仅适合POST 请求 参数返回:调用ok() 方法,将需要返回的数据填入
# 4、名词解释
- Controller:控制层,一般只负责处理参数和返回参数,不负责业务逻辑Manager:业务层,处理业务逻辑
- Dao:数据层,和数据库交互
- PO:数据库表映射类,一张表对应一个实体PO类
- VO:展示类,包装后返回给前端
- BO:模块与模块之间调用传输的类
# 5、致信功能详解
# 5.1 致信登陆
客户端调用apps-common 包的MainController 类的login4Ucpc3() 方法进行身份校验(如用户名密码、AD域、二维码扫描、自动登录) login4Ucpc3() 方法内部调用ZxApi 的afterComplete() 方法获取用户的信息(如memberId 、 token 等数据,封装到response 中) 常见问题: 3401:超过致信授权最大数 3402:致信服务授权已过期! 3404:未知错误 看日志(一般是token无效),检查appkey和appsecret是否有效,检查网络是否可以ping通 3405:人员未授权
# 5.2 致信双击头像打开 A8
客户端调用ZxRestController 类的getTicket() 方法,获取到ticket 打开浏览器,访问ZxSSOLoginHandshakeImpl 类的handshake() 方法进行校验
# 5.3 致信穿透业务消息
客户端拼接消息url ,将verification 参数拼接到后面( verification 参数在调用登录接口的时候从返回值里获取) 打开浏览器,访问ZxRestController 类的commonPierce() 方法进行穿透 通过UcLoginSecurityManager 类校验verification 是否有效 解析参数enc ,转义成真实的跳转url 父页面跳转到pierce.jsp ,内部iframe 对真实url 进行跳转
# 5.4 致信请求通讯录
访问ZxRestController 类的() childAccounts() 方法获取所有的单位 访问ZxRestController 类的() firstDepts() 方法获取当前单位的第一级部门 访问ZxRestController 类的() subDeptInfo() 方法获取子部门(非一级部门)
# 5.5 群组操作
获取群组信息: ZxRestController 类的() groupInfo() 方法 修改群组信息: ZxRestController 类的() updateGroupInfo() 方法 群组加人: ZxRestController 类的() joinMember() 方法 群组减人: ZxRestController 类的() removeGroup() 方法 退出群组: ZxRestController 类的() quitGroup() 方法 解散群组: ZxRestController 类的() dismissGroup() 方法
# 5.6 读取加密狗信息
在ZxConfigManagerImpl 类的loadingZxInfo() 方法中读取了加密狗中的参数,并加载到缓存中; 通过 Class<?> c1 = MclclzUtil.ioiekc("com.seeyon.ctp.product.ProductInfo"); 来定义加密狗配置类 用 MclclzUtil.invoke(c1, "方法名"); 的方式获取数据,其中方法名需要加密狗开发人员告知
# 5.7 离职人员的退群等相关逻辑
在MemberTokenEventListener 类的listenEventUpdateMember() 方法和listenEventDeleteMember(), 方法中对人员的修改和删除进行了监听。当人员发生离职的情况时会触发UpdateMemberEvent;当人员被删除时会触发DeleteMemberEvent;在判断出人员发生离职时,调用ZxAuthManagerImpl 类的blockUser() 方法进行处理离职人员。
- 封禁用户:公有云封禁三个月(最多只能三个月),私有云永久
- 删除用户的token 数据
- 对在线用户进行强行踢掉的处理
- 删除群关系:查询这个人所有的群,进行踢出操作
# 5.8 机器人消息卡片逻辑
接收前端传的参数: title-标题 、 content-内容 、 noticeMemberId-@人id 、 talkType-接收对象(0:个人 1:群组) 、 talkId-接收对象id 、 pierceUrl-穿透url 在ZxRobotManagerImpl 类的sendRobotMsg() 方法中对参数进行封装,调用发送消息接口发送消息;在发送消息的时候要注意如果接收人是自己,那么isIncludeSender 要传 0 ,其他情况传 1 ,这个问题在代码中已经提取出通用方法
# 5.9 群组权限、文件收发控制逻辑
# 5.9.1 全局设置:
在单位管理员-致信管理-群文件权限管理 中可以对人员进行全局文件权限控制 在ZxGroupAuthManagerImpl 类的saveGroupAuthMsg() 方法中对人员的文件收发权限进行了设置, 设置后会在zx_groupauth 和zx_groupauth_group 表会进行保存,其中zx_groupauth 表记录每个人的权限, zx_groupauth_group 表记录分组;还会在ZxRongCacheImpl 类的zxGroupAuthCoarseCache 缓存变量中存储数据,提高查询速度
# 5.9.2 单个群组设置:
在uc_group_members 表中,每个群成员都有控制是否允许发送、接收文件的数据。在创建群组的时候,每个群成员会到zx_groupauth 表去查询这个人是否在全局设置过群文件权限,如果设置过就使用设置的权限,没有设置过就默认全部可以收发。在群组中也可以对每个群成员设置每个人权限,设置后不再受全局设置影响
# 5.10 第三方消息接收逻辑
对于第三方消息,之前的老版本是受系统管理员-致信管理-消息接收设置 影响,没有在此设置中的消息无法接收。新版本在判断消息类型是否接收时,对于不在系统预制消息类型中的消息进行放行,代码在ZxUserMessageManagerImpl类的filterMessage()方法中。
ZxConfigItem configItem = zxConfigCache.getZxConfigItem(ZxConstants.ZX_MESSAGE_SWITCH_ALLOW);
if (configItem != null) {
allowString = configItem.getConfigValue();
}
String[] allowArray =allowString.split(",");
Set allCache = Sets.newHashSet(); allCache.addAll(Arrays.asList(allowArray));
List result = Lists.newArrayList(); for (UserHistoryMessage userHistoryMessage : data) {
if (allCache.contains(userHistoryMessage.getMessageCategory().toString()) ||
!isBasicMessage(userHistoryMessage.getMessageCategory().toString()) || isNewAddMessage(userHistoryMessage.getMessageCategory())) {
result.add(userHistoryMessage);
}
}
return result;
}
/**
新增类型消息 不受消息接收设置 也不是nc消息 进行放行
@param messageCategory
@return
*/
private boolean isNewAddMessage(int messageCategory) {
if (ApplicationCategoryEnum.cap4Form.getKey() == messageCategory) { return true;}
return false;
}
/**
是否是基础消息(非nc)
@param messageCategory
@return
*/
private boolean isBasicMessage(String messageCategory) {
String[] messageCategorys = ZxConstants.totalAllowString.split(","); for (String category : messageCategorys) {
if (category.equals(messageCategory)){ return true;}
}
return false;
}
# 5.11 唤醒致信客户端、参数传递相关
各个组通过使用chat.js 文件中的 wakeZX(appCategory, appId, isForward, params) 方法对致信进行唤醒。 appCategory:应用组编号(ApplicationCategoryEnum) appId:主键id isForward:是否允许转发params:额外参数 比如在协同详情页面对致信进行唤醒并且转发卡片到群里,那么appCategory 是 1 , appId 是此封协同的id , isForward 是 1 代表允许转发, params 不传在方法对这些参数进行整合后发送给客户端,参数有以下:
- memberid:人员id loginticket:自动登录的ticket currentUserLocale:国际化语言protocol:协议(http、https) host:IP
- port:端口 contextPath:前缀,默认是seeyon,在协同云环境会变化成别的前缀productCode:组织码(协同云环境专属)
- type:autoLogin代表自动登录,sendAppCard代表分享卡片到群里params:额外参数
- ran:一个随机参数,浏览器打开url会在后面加/ 所以后面拼接个没用的参数防止客户端切割参数失败
# 5.12 应用中心模块调用逻辑
# 5.12.1 系统设置:
通过ZxShortMenuManagerImpl 类的getInitData() 方法设置预设应用组为快捷新建、协同办公、企业文化、我的工具 通过ZxShortMenuManagerImpl 类的saveShortMenuGroup() 方法新建应用组 通过门户的磁贴组件shortMenuContent.jsp 文件的openSelectMenu() 方法,将设置后的数据和当前应用组id 传给portal
# 5.12.2 所有磁贴:
通过ZxRestController 类的showShortMenuGroupContent() 方法获取磁贴数据 先获取所有应用组,遍历每个应用组,通过应用组id 向portalApi 的getSelectedPorletsZx() 方法请求每个组的所有磁贴,封装成VO 返回到前端
# 5.13 轻协作相关接口逻辑
在设置群面板后,调用ZxRestController 类的updateGroupInfo() 方法对群面板进行保存。参数格式:
# 5.13.1群任务:
获取群任务的计数: ZxRestController 类的getGroupTaskCount() 方法获取群任务: ZxRestController 类的getGroupTasks() 方法。
# 5.14 卡片消息实现逻辑:
- 在应用页面通过使用chat.js 文件中的 wakeZX(appCategory, appId, isForward, params)方法对致信进行唤醒客户端。具体详情见#5.11、唤醒致信客户端、参数传递相关。
- 客户端在选择需要发送的个人或群组后,调用ZxRestController 类的sendAppCard() 方法,进行发送卡片逻辑
- 在ZxMessageCardManagerImpl 类的sendAppCard() 方法中,通过messageCategory 拿到应用的ShareDataManager 的实现类
- 调用实现类的getZxMessageCard() 方法拿到ZxMessageCardBO 对象
- 致信创建卡片消息类,将ZxMessageCardBO 对象设置后,发送消息,客户端接收到消息后进行页面卡片展示。
附:在ctp-common 中定义了一个接口ShareDataManager ,各个应用组如果想支持卡片消息,需要实现这个ShareDataManager 接口,在内部的getZxMessageCard() 方法中定义消息卡片样式。
# 5.15 调用融云api向各端进行消息推送的逻辑:
首先定义一个自定义消息类,继承BaseMessage ,然后实现getType() 和toString() 方法,然后声 明id(消息id) 、 content(标题,离线推送会用) 、 user(接收人信息) 三个通用参数,最后定义自己的自定义类
//继承BaseMessage
public class ZxRobotMessage extends BaseMessage {
/**
消息id
*/
private String id;
/**
标题
可能会走离线推送
*/
private String content;
/**
发送人信息
*/
private ZxMessageUser user;
/**
自定义内容
*/
private ZxRobotVO zxRobotVO;
//消息类型@Override
public String getType() { return "OA:OARobotMsg";}
//转换Json的方式@Override
public String toString() {
return GsonUtil.toJson(this, ZxRobotMessage.class);
}
//各种get/set方法 public String getId() {
return id;
}
public void setId(String id) { this.id = id;}
public String getContent() { return content }
public void setContent (String content) {
this.content = content
}
public ZxRobotVO getZxRobotVO () {
return zxRobotVO;
}
public void setZxRobotVO (ZxRobotVO zxRobotVO) {
this.zxRobotVO = zxRobotVO;
}
public ZxMessageUser getUser () {
return user;
}
public void setUser (ZxMessageUser user) {
this.user = user;
}
}
调用S2SRongManager的publishPrivate()方法和publishGroup()方法发送到个人、群组中
# 5.16 致信封装并穿透打开 A8 应用的两种方式
# 5.16.1 通过消息类型来封装、解析应用链接
代码位置: ZxUserMessageManagerImpl 类的getPcLinkUrlByApp() 方法
# 5.16.1.1 封装、穿透逻辑:
通过上述接口获取到加密的url ,例: http://{ip}/seeyon/uc/rest.do?rification=o8iJxsg 穿透后通过ZxRestController 类的pierce() 方法解析url ,把enc 解析
String linkType = encMap.get("L");
String path = encMap.get("P");
if (Strings.isNotBlank(linkType)) {
//去message-link.properties文件中找真实url
如/collaboration/collaboration.do?method=summary&openFrom=listDone&affairId=
{0}&contentAnchor={1}
link = UserMessageUtil.getMessageLinkType().get(linkType);
if (link == null) {
mv.addObject("ExceptionKey", "mail.read.alert.wuxiao"); return mv;
}
//填充url中的参数 如{0}、{1}
String[] linkParams = request.getParameterValues("P"); for (int i = 0; i < linkParams.length; i++) {
linkParams[i] = Strings.toHTML(linkParams[i]);
}
java.text.MessageFormat formatter = new java.text.MessageFormat(link); int formatsCount = formatter.getFormats().length;
if (linkParams != null) {
if (formatsCount > linkParams.length) { String[] params = new String[formatsCount]; for (int i = 0; i < params.length; i++) {
if (i < linkParams.length) { params[i] = linkParams[i];
} else {
params[i] = "";
}
}
link = formatter.format(params);
} else {
link = formatter.format(linkParams);
}
}
this.pierceVerify(mv, request, response, link, openFrom, linkType);
return mv;
}
# 5.16.1.2 通过消息类型去message-link.properties 配置文件中寻找具体url
解析后进行拼接后在ZxRestController 类的pierceVerify() 方法中进行封装到ModelAndView 跳转到pierce.jsp ,然后在iframe 中进行跳转
# 5.16.1.3 通过消息 url 来解析应用链接
代码位置: ZxUserMessageManagerImpl 类的getShortMenuUrl() 方法
# 三、客户常见问题以及解决方案
# 1、A8群成员与融云群成员不同步
产生原因: A8 在操作人员退群时( ZxGroupManagerImpl 类的transQuitGroup() 方法),会同时修改A8数据库uc_group_members 表的数据和融云的群成员映射关系,分别是ZxGroupManagerImpl 类的quitLocalGroup() 和quitCloudGroup() 方法。老版本在操作的时候代码出现失误,把融云退群和发送消息绑定到了一个条件里,导致如果设置不发送 消息就会导致融云退群失败,但是A8退群成功,最终的现象就是在群里找不到这个人,但是可以发送和 接收消息。 老代码: 改造后: 解决方案: 可以参考新版本的ZxGroupManagerImpl 类的synGroup() 方法中的下面这一段,逻辑是分别获取群组中A8和融云的成员,进行对比以及修复
# 2、群成员异常退群
产生原因: 客户部署了多套A8 (数据库相互拷贝)且共用了一套Appkey 致信后台默认有定时任务ZxSynGroupJob ,每天凌晨二点进行同步( ZxjobManagerImpl 类的startSynGroupJob() 方法负责开启),将本地和融云数据不一样的群组进行修复。当不同的A8环境操作同一个群组时,数据库因为进行了隔离所以不会有数据问题,但是因为共用一套融 云环境导致融云数据出现错乱,定时任务的时候会以最后一个完成定时任务的机器数据为准,就会出现 人员异常退群(现象是人还在群里,但是发不了消息) 解决方案: 任选其一:
- 停掉其他A8 环境停掉致信
- 替换Appkey
# 3、部门群问题
产生原因: 部门群需要根据人员的状态变化监听进行相应调整群里的成员,因为人员变化监听非常多(人员的新建、离职、调岗、增加兼职、更新兼职等),且准确率不高,之前 使用针对不同的人员变化进行特殊处理的方式很容易出BUG。 解决方案: 合并出一个刷新部门群的方法( ZxGroupManagerImpl 类的synDepGroupMember() 方法),这个方法的逻辑是:
- 获取当前部门群的群成员
- 获取当前部门的最新人员组成
- 两者进行对比,多则退出,少则增加
只要触发人员的监听,就会调用这个方法进行刷新,保证了数据的准确性
# 4、穿透A8报错,提示无效用户名密码(username is not available)
产生原因: 老版本在通过双击头像打开A8的过程( ZxSSOLoginHandshakeImpl 类的handshake() 方法)中,是通过用户名、密码的方式进行校验身份,这种方法在一些特殊环境会有问题,比如用户登录用的是AD 域 、 CA 等方式,导致客户端拿到的密码并不是真实的登录密码,还有用户名是中文的情况下有时也会导致用户名解析出问题,最终导致检验通不过,返回 username is not available 解决方案: 致信使用了一次性ticket 的方式来替代传统的A8用户名、密码 校验,解决了以上的问题步骤:
- 致信客户端在双击头像打开A8 前先向服务器请求一个一次性的tikcet ,在ZxRongCacheImpl 缓存类的zxUserTicketCache 和zxTicketUserCache 变量分别存储了ticket 和memberId 的映射关系,区别是一个是key 为memberId , value 为ticket ,一个是key 为ticket , value 为memberId
- 客户端拿到tikcet 后打开浏览器,访问ZxSSOLoginHandshakeImpl 类的handshake() 方法, 在方法里对ticket 进行校验,当通过ticket 可以拿到memberId 的时候,代表这个ticket 是有效的,将这个tikcet 从两个缓存中分别移除,保证ticket 只有一次性有效
# 5、后台自动授权人员功能异常
产生原因: 首先阐明下融云实际没有授权这个概念,只有对用户进行封禁和解封两个状态
- 对于融云来说,当一个用户第一次请求token 的时候,就默认给他一个注册数(授权) 所以A8 在区分公有云和私有云的时候,其实是控制请求token来做的
- 公有云在A8 启动的时候会给所有人请求token ,代表全部授权
- 私有云在A8 启动的时候不会进行请求token ,只有当授权的时候才会对授权人进行请求token
私有云自动授权功能:
- 当用户登录致信的时候,会对用户的授权状态进行判断
- 当用户已经授权,则判断token 是否存在,存在直接返回token ,不存在向融云请求token 后返回
- 当用户未授权,则判断是否开启自动授权功能
- 如果开启了自动授权,则自动把他授权且向融云请求token并返回
- 如果没有开启自动授权则返回未授权
所以如果自动授权功能异常,问题大概是以下几点:
- 注册数满了,无法授权(包含A8 注册数满和融云注册数满)
- 向融云请求token 失败
解决方案:
- 检查注册数是否满了(包含A8 注册数满和融云注册数满)
- 在system 管理员致信管理-人员信息管理 菜单进行刷新token,然后查看 zx.log
# 6、A8启动异常或宕机,日志报出与致信相关的信息
# 6.1 生产环境:
因为致信初始化加载全程用try-catch 捕获,所以不会出现生产环境启动异常的问题
# 6.2 开发环境:
# 6.2.1 启动异常:
产生原因:
- Spring 加载问题:比如你在Spring 配置文件中声明了bean的加载,但是实际并没有提交这个bean的源文件,就会导致启动异常
- 数据库加载异常:比如你新增了个一张表,并且针对这个表编写了PO 类和HBM 文件,但是由于SQL 脚本没有提交,导致环境中没有这张表,那么在Hibernate 进行类和表映射的过程中就会导致报错
- 本地代码和A8 部署环境对不上:比如你的开发代码是最新的,且在你的代码中引了别人的项目的 新类,但是你的A8 部署环境代码比较老,还没有这个新类,就会导致启动失败
解决方案:
- 查看ctp.log
- 保证代码和数据库SQL 一起提交
- 保证开发环境和A8 部署环境代码一致
# 6.2.2 运行异常:
产生原因: 目前遇到过的是老版本因为每 30s 全量获取一次业务消息导致OOM ,主要原因是每次对全部消息进行去toJson 的时候消耗过大,导致了内存溢出 解决方案: 老版本解决方案是降低每次全量获取业务消息的条数,如固定每次只取 150 条,降低toJson 的压力新版本使用时间戳的方式增量获取最新消息
# 7、集团版A8如何能实现一次授权所有单位人员
使用system管理员的 致信管理-私有云设置 菜单的自动授权的功能可以做到间接性一次授权所有单位人员
# 四、Nginx 搭建
# 1、Nginx 安装
Nginx 及安装过程中需要的依赖程序的下载链接参考如下: Nginx,参考下载链接(建议下载stable version):http://nginx.org/en/download.html nginx-sticky-module,参考下载链接:https://bitbucket.org/nginx-goodies/nginx-sticky-modul e-ng/downloads/ pcre,参考下载链接:https://ftp.pcre.org/pub/pcre/ 所有的安装程序位于/home/soft 下, nginx 的安装目录位于/home/nginx 下,依次执行以下命令进行nginx 的安装(命令中标红的安装包名称,以实际下载的为准): 安装: 以实际下载的文件格式选择tar或zip解压 解压tar压缩包
tar –zxvf nginx-1.12.1.tar.gz tar –zxvf pcre-8.40.tar.gz
解压zip压缩包
unzip nginx-goodies-nginx-sticky-module-ng-08a395c66e42.zip
``` # 修改解压后文件夹名,便于后续安装
```shell
mv nginx1.12.1 nginx mv pcre-8.40 pcre
mv nginx-goodies-nginx-sticky-module-ng-08a395c66e42 nginx-sticky-module
``` # 赋权文件夹
```shell
chmod –R 777 nginx
进行nginx的configure ```shell cd nginx
注意以下configure命令为一行
```shell
./configure --prefix=/home/nginx --with-http_stub_status_module --with- http_ssl_module --with-http_realip_module --with-http_sub_module --with- http_flv_module --with-http_mp4_module --with-http_random_index_module --with- http_gzip_static_module --with-pcre=/home/soft/pcre --add- module=/home/soft/nginx-sticky-module
编译、安装 make make install
# 2、Nginx 配置
client_max_body_size 10240M;
gzip on;# 开启gzip
1KB以下不进行gzip
gzip_min_length 1k;
设置gzip的buffer
gzip_buffers 4 16k;
设置gzip的等级,等级越高压缩比例越大,越消耗cpu gzip_comp_level 3; 设置gzip的压缩类型
gzip_types text/xml text/plain text/css text/javascript application/x- javascript application/javascript application/xml application/json;
ie6及以下版本浏览器不进行gzip gzip_disable "MSIE [1-6]."; 定义一个upstream,名称(seeyon_v5_cluster,可更改)、模式(必须为sticky)及其服务 集(server,192.168.0.1为服务ip,80为http端口,以实际情况进行修改)
upstream seeyon_v5_cluster{ sticky;
server 192.168.0.1:80 max_fails=5 fail_timeout=20s; server 192.168.0.2:80 max_fails=5 fail_timeout=20s;
}
定义一个server server {
设置监听端口
listen 80; # 设置server名称
server_name localhost; # 设置字符集
charset utf-8; # 设置location location / {
转发请求至seeyon_v5_cluster,该名称与upstream的名称一致
proxy_pass http://seeyon_v5_cluster; # 设置header的host包含host及port
proxy_set_header Host $host:$server_port; # 设置header的客户端ip为实际ip proxy_set_header X-Real-IP $remote_addr; # 设置header的协议为实际使用的协议
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;
关闭redirect
proxy_redirect off;
设置超时时间
proxy_connect_timeout 300;
proxy_read_timeout 300;
proxy_send_timeout 300;
}
# 3、Nginx 启动
以nginx 安装在/home/nginx 下为例, nginx 的启动脚本为/home/nginx/sbin/nginx 。启动示例如下:
# 4、参数调优
参数优化需依据nginx的运行情况,及服务器负载情况进行调整。常见的优化参数有以下内容:
- worker_processes:nginx的进程数,一般为cpu的倍数,可以为1倍
- worker_rlimit_nofile:nginx的进程打开文件数,可以与ulimit –u的值一致
- worker_connections:每个进程允许的最多连接数 keepalive_timeout:客户端超时时间,单位秒
- client_max_body_size:客户端连接的最大请求实体,影响协同系统的上传附件大小,建议设置大 于或等于运行附件上传的最大值
- access_log:请求日志,建议无需调试时关闭(off)
Nginx 可优化的参数还有很多,建议依据系统实际的运行需求,选择性优化
# 5、启用https配置
启用https 需要购买ca证书(放在/home/nginx/ssl 下),在购买了证书后,对nginx配置文件(nginx.conf )的server 段进行以下调整:
快速跳转
- 致信后端开发文档
- 一、搭建A8教程
- 二、开发搭建教程
- 三、客户常见问题以及解决方案
- 四、Nginx 搭建