# hooks.action.ts

hooks.action.ts是后端嵌入式脚本的约定文件名。

hooks.action.ts分系统级别、项目级别、应用级别,常用于监听系统事件、定义程序流脚本或自定义表达式函数。在约定的位置存在hooks.action.ts`文件时(具体见脚本文件组织),系统就会自动使用它。

ts是typescript文件,一种js的扩展语法,见 https://www.typescriptlang.org/ 最终系统会自动将ts文件编译成java的字节码在jvm中运行。

TIP

什么时候需要export函数?

  1. 要导出给其它脚本文件调用的函数。
  2. 由系统定义并在特定情况下调用的函数,比如嵌入脚本中的监听事件函数数据加工脚本节点函数等。
  3. 需要通过web请求访问的函数
  4. 需要程序流的执行后端脚本节点执行的函数

# 监听事件

/**
 * 系统级别的钩子事件函数
 *
 * 定义系统底层的一些如启动关闭、用户登录、权限判断等钩子事件,通过这些事件,二次开发者可以干预系统
 * 的一些默认行为。
 */

/**
 * 服务器启动之前调用。
 *
 * 调用此事件时,系统内部已完成初始化,但服务器启动状态还未标记为“已启动”,在此事件中的代码
 * 逻辑,将会阻塞服务器的启动过程,且事件中的异常也会导致服务器启动失败,故尽量不要在事件中
 * 做太耗费时间的事情,也不要抛出异常。
 *
 * 万一脚本开发错误,导致系统总是无法启用,可以通过添加JVM启动参数,暂时禁用启动事件,
 * 如:`-Dsucc.disableServerStartupHook=true`。
 *
 * 此事件只会执行一次。
 */
declare function onBeforeServerStartup(): void;

/**
 * 当服务器启动完成后调用。
 *
 * 调用此事件时,服务器已完成启动,启动状态也正常,用户可以正常访问服务器了。系统会使用异步
 * 线程调用此事件,也就是说在此事件中的代码逻辑,不会阻塞服务器的启动过程。
 *
 * 此事件只会执行一次。
 */
declare function onDidServerStartup(): void;

/**
 * 当服务器准备关闭时调用。
 *
 * 此事件只会执行一次。
 */
declare function onBeforeServerShutdown(): void;

/**
 * 此脚本用于扩展系统的WebAPI功能。
 *
 * 系统在`/api/...`路径上提供了丰富的webAPI,当某些个性化需求需要扩展新的API(如
 * `/api/trustedapps/my-custom-api`)时,可以使用此脚本文件。
 *
 * 当系统收到/api路径下面的请求时会优先执行系统内置的api,如果不存在系统内置的api,那么会调
 * 用这里的脚本函数。
 *
 * ```ts
 * declare function onCustomWebAPI(request: HttpServletRequest, response: HttpServletResponse): any | void {
 *     let uri = request.getRequestURI();
 *     if (uri === '/api/my-custom-api') {
 *         //实现个性化api逻辑
 *
 *     } else {
 *         response.sendError(404);
 *     }
 * }
 * ```
 *
 * @returns 返回的额内容将直接输出给客户端
 */
declare function onCustomWebAPI(request: HttpServletRequest, response: HttpServletResponse): any | void;

/**
 * 当系统接收到任意web请求时都会执行此钩子函数,脚本可以拦截Web请求并返回个性化的内容。
 *
 * 本扩展点执行的非常频繁,所有http请求都会调用本方法,所以脚本务必要高效
 *
 * 如果脚本想重定向请求到另外的页面,可以调用`response.sendRedirect("/xxx")`跳转,然后return
 * false。
 *
 * WARNING!!! 此脚本会影响系统所有网络请求,脚本开发过程中疏忽不小心写错了可能导致系统无法访
 * 问!
 *
 * 补救措施:
 *
 * 1. 添加系统启动变量-Dsucc.disableScript.filter=true,然后重启,见环境变量。
 * 2. 系统对当前当前正在编辑脚本的用户不会立即启用脚本(只有重新登录才会生效),所以他还可以
 *    继续编辑脚本,尽快把脚本改正确。在脚本修改正确之前不要退出浏览器、不要注销。
 *
 * @see <https://tomcat.apache.org/tomcat-5.5-doc/servletapi/javax/servlet/Filter.html>
 *
 * @returns 返回`false`将不处理`FilterChain`上的下一个Filter
 */
declare function onFilter(request: HttpServletRequest, response: HttpServletResponse): boolean | void

/**
 * 当有元数据项目创建成功后调用。
 *
 * @param projectName 已创建的项目的名称。
 */
declare function onDidProjectCreate(projectName: string): void;

/**
 * 当有元数据项目要被删除时调用。
 *
 * @param projectName 将要删除的项目的名称。
 */
declare function onDidProjectDelete(projectName: string): void;

/**
 * 系统日志事件准备写入到数据时调用。
 *
 * 开发者可以在这个事件函数中拦截系统默认写入逻辑将日志写入别处,或在日志入库前修改一些字段
 * 的数据。
 *
 * @param events 要写入的日志列表,背后是一个java的List对象。
 * @returns 返回false代表脚本已经处理了日志的写入,系统则不再将日志入库,其他情况系统将继续
 *          默认日志写入逻辑。
 */
declare function onPersistEvents(events: ServerEvent[]): boolean | void

/**
 * 当使用用户名密码方式登录,校验账号密码时候调用。
 *
 * 1. 当执行此回调函数时,用户还未登录成功,系统会根据此函数返回的结果决定后续的操作
 * 2. 用户ID为 `admin` 的用户登录时不受这个脚本函数的影响
 *
 * 返回值说明:
 *
 * 1. 明确返回用户信息,那么将绕过系统默认的登录验证逻辑,直接使用返回的用户信息登录。
 * 2. 抛出异常,那么将导致用户登录失败,异常信息会显示给用户,可用于进行额外的登录验证判断。
 * 3. 没有返回值,那么将进行系统默认的登录校验 
 *
 * @param request 登录请求对象
 * @param userId 用户输入的用户ID或者手机号
 * @param password 明文密码
 * @param userDirectory 用户目录。用于标识用户的来源,系统内置的区分有:`sys`表示系统用
 *        户,`external`表示外部用户
 * @returns 用户信息
 */
declare function onVerifyLoginPassword(request: HttpServletRequest, userId: string, password: string, userDirectory: string): UserInfo | void | null;

/**
 * 根据用户ID获取登录用户
 *
 * 只用于无密码登录的时候获取用户信息
 *
 * 广义需求说明: 将系统集成到第三方系统的时候,为了避免在多个地方维护用户信息,经常需要只在
 * 第三方系统存储用户,提供这个函数用于从第三方系统获取用户信息
 *
 * 系统中典型的使用场景
 *
 * 1. 使用数字证书(https://docs.succbi.com/dev/ca-token/)方式登录系统的时候,会将用户ID作
 *    为待加密内容,系统解密出用户ID后,将调用本方法用于查找用户
 *
 * 返回值说明:
 *
 * 1. 期望返回用户信息
 * 2. 如果没有返回值, 则会尝试从系统用户库中读取
 * 3. 如果返回null, 则代表没有查找到用户
 * 4. 抛出异常,则视为返回null,并在控制台打印 error级别错误日志(在控制台日志界面,将登录日
 *    志级别调整为error即可查看异常详情)
 *
 * @param userId 用户ID
 * @param userDirectory 用户目录。用于标识用户的来源,系统内置的区分有:`sys`表示系统用
 *        户,`external`表示外部用户
 * @returns 返回用户信息
 */
declare function onVerifyLoginUser(request: HttpServletRequest, userId: string, userDirectory: string): UserInfo | void;

/**
 * 用于加密用户的明文密码
 *
 * 返回值约定:
 *
 * - 在字符串的开头用 `{Alogrithm}` 用于指定所使用的加密算法,
 * - 如果不指定,则代表使用的是系统默认的加密算法(sha1)
 * - 也可以指定 `{script} `作为前缀来指定是使用此脚本函数进行加密
 * - 如果指定的加密算法为系统自带的算法(`sha1`;`md5`;`sm3`),进行校验的时候,则会使用系统
 *   内部的对应算法实现进行校验,其他的算法都会调用此脚本函数来进行校验比如 :
 *     - `{md5}afhasiohfo` 代表用 `md5` 算法加密后的字符串
 *     - `{sm3}iugbasdfjiojda` 代表用 `sm3` 算法加密后的字符串
 *     - `iugbasdfjiojda` 和 `{sha1}iugbasdfjiojda` 都代表用 `sha1` 算法加密后的字符串
 *     - `{script}sjiogjs` 代表用此脚本函数的实现来进行加密
 *     - `{sm4}wergtyhsdfg` 代表用 `sm4` 算法加密,并且实现之后的脚本
 *
 * @param str 待进行加密的秘钥字符串
 * @returns 加密后的字符串
 */
declare function onEncryptUserPassword(str: string): string;

/**
 * 用于对比输入的明文密码是否为数据库记录密码
 *
 * 当校验密码检查到记录的用户密码使用加密方式不为系统自带的加密方式( `sm3` , `sha1` , `md5`
 * )时,就会调用本函数来进行判断
 *
 * @param plainPassword 未加密前的用户密码字符串或者密码加盐字符串
 * @param encryptedPassword 期望加密后的字符串
 */
declare function onCompareUserPassword(plainPassword: string, encryptedPassword: string): boolean;

/**
 * 用于在登录过程中动态决定一个用户属于哪些用户组。
 *
 * 此函数用于登录完成后,帮助分析用户的权限
 *
 * 果此函数存在,那么系统将完全信任此函数返回的信息,默认不读取用户组用户关系表中的用户组信
 * 息
 *
 * 用户ID为 `admin` 的用户登录时不受这个脚本函数的影响
 *
 * 返回值说明:
 *
 * 1. 用户组ID数组(是一个字符串数组),代表当前用户将被分配到这些用户组里面去
 * 2. 字符串,表示用户属于哪个用户组
 * 3. undefined,或者抛出异常:表示使用系统默认的用户组逻辑
 * 4. 其它值将被忽略,系统完全信任此函数返回的信息,默认不读取用户组用户关系表中的用户组信息
 *
 * @param userInfo 用来登录的用户信息
 * @returns
 */
declare function onResolveUserGroups(userInfo: UserInfo): Array<string> | string | null | void;

/**
 * 当一个用户登录成功后,执行一次此钩子函数。
 *
 * 可以通过此钩子函数修改当前用户的登录信息(包括权限列表等)。
 *
 * 如果抛出异常,那么将导致用户登录失败,可用于进行额外的登录验证判断。
 *
 * 属于用户组`admin`的用户在登录时候,不受此函数影响
 *
 * @param request 请求对象
 * @param response 响应对象
 */
declare function onDidSignin(request: HttpServletRequest, response: HttpServletResponse, user: CurrentUser): void;

/**
 * 当使用url上带access_token参数进行登录操作时用于校验access_token
 *
 * 这个函数会在系统默认access_token库中没有找到对应access_token的情况下进行调用
 *
 * @param access_token token值
 * @returns 若没有返回值或者返回null,则视为脚本没有对access_token进行校验
 */
declare function onAccessTokenSignin(access_token: string): void | null | {
	/**
	 * 是否校验成功
	 */
	result: boolean;
	/** 
	 * 与 access_token 绑定的用户
	 *
	 * 用户信息请尽可能的完整,信息可能被展示到页面中
	 *
	 * 校验成功的情况下必须返回用户信息,否则仍然视为校验失败
	 */
	user?: UserInfo;
	/** 
	 * 校验错误码
	 *
	 * 系统内置错误码有: 
	 *
	 * 1. tokennotexists: 代表系统中没有找到对应的access_token
	 * 2. used:access_token 已经被使用过了
	 * 3. invalid:access_token 的使用方式不对,不能用来登录
	 * 4. expired:access_token 过期了
	 * 5. usernotexists: access_token绑定的用户在不存在
	 */
	errorCode?: string;
	/**
	 * 错误消息,这个将会打印在系统的控制台中,但需要设置控制台的日志查看级别为debug
	 */
	errorMessage?: string;
}

/**
 * 判断权限钩子函数
 *
 * 用户主动对系统资源发起的操作,都需要进行权限判断,当进行权限判断时,如果这个钩子函数存在,会先调
 * 用此函数进行权限判断,如果钩子函数没有处理权限判断(返回`null`或者`undefined`)才会继续进行默认的
 * 权限判断
 *
 * 用于一些登录时无法确定用户权限分配,需要根据一些动态信息才能确认用户是否有权限的情况,比如:
 *
 * 1. 一个新添加文档,用户需要即时邀请在线人员参与审核和编辑
 * 2. 期望第三方系统中修改权限后,权限能够即时作用到已经登录的用户上
 *
 * 不受此钩子函数影响的范围有:
 *
 * 1. 当前登录为系统管理员(https://docs.succbi.com/v5/permission/groups/#admin)用户:可操作任何系统资源
 * 2. 公开目录(https://docs.succbi.com/v5/permission/anonymous-and-public/#public)下的资源完全公
 *    开:查看时不需要任何权限判断
 *
 * 使用注意:
 *
 * 1. 只有已经有用户登录的情况(匿名用户登录也属于登录),才会调用到到这个函数
 * 2. 此方法仅适用于直接查看或者操作一个资源时的权限判断,比如用户是否可以查看一个spg页面,是否可以
 *    向一个模型提交数据等;对于一些需要结合多种权限分配情况才能得出结果的情况不会调用这个钩子函
 *    数,比如:默认元数据界面中展示的树型资源结构,需要同时根据所有权限分配情况去过滤出用户可以看到
 *    的资源,这种情况下仅会更根据用户本身在系统中分配的权限来进行判断
 *
 * @param path 资源的路径(元数据路径)
 * @param operation 权限操作,参考:https://docs.succbi.com/permission/operations
 * @returns 见返回类型说明
*/
declare function onCheckPermission(path: string, operation: string)
	: boolean //`true`: 代表有权限,并且没有数据范围的限制;`false`: 代表没有权限
	| undefined | null //代表需要使用系统默认的权限判断逻辑
	| succ.security.PermissionDataRange;//返回数据范围的格式,代表有权限,但是只能操作对应范围的数据

/**
 * 脚本单点登录方案定制 —— 判断访问系统所使用的app是不是当脚本单点登录方案所能支持的
 *
 * 1. 在移动端登录界面,可以尝试使用当前app所登录用户授权登录到系统中:当单点登录定制为微信单点登录
 *    时,在微信浏览器中访问系统,系统显示的根据股微信登录按钮,才能跳转,如果在钉钉中访问系统,这个
 *    微信登录按钮是没有意义的,应该隐藏
 * 2. 在 PC二维码登录界面,用户使用手机app扫描二维码,如果这个app不是满足当前定制单点登录的需要应该
 *    提示app不受支持,
 *
 * @param userAgent 服务器收到请求的请求头 User-Agent
 * @param request web请求对象
 */
declare function onSSOCheckAppSupported(userAgent: string, request: HttpServletRequest): boolean;

/**
 * 脚本单点登录方案定制 —— 构造一个URL,用于需要登录时请求访问第三方认证授权服务获取授权用户信息
 *
 * 典型场景为:
 *
 * 1. QQ手机扫码登录斗
 *    鱼:<https://xui.ptlogin2.qq.com/cgi-bin/xlogin?appid=716027609&pt_3rd_aid=101047385&daid=383&pt_skey_valid=0&style=35&s_url=http%3A%2F%2Fconnect.qq.com&refer_cgi=authorize&which=&client_id=101047385&redirect_uri=https%3A%2F%2Fpassport.douyu.com%2Findex%2Funion%2Findex%2Fqq&state=6fc429913eed76f55dc59714fd6d5d46&scope=&response_type=code&approval_prompt=force&chInfo=ch_share__chsub_CopyLink>
 * 2. 迅雷PC页面请求QQ授
 *    权:<https://graph.qq.com/oauth2.0/show?which=Login&display=pc&client_id=101164677&redirect_uri=https%3A%2F%2Flogin-i-ssl.xunlei.com%2Fthirdlogin%3FOSVersion%3DMacIntel%26appName%3DWEB-vip.xunlei.com%26appid%3D101%26clientVersion%3DNONE%26deviceModel%3Dchrome%252F87.0.4280.88%26deviceName%3DPC-Chrome%26devicesign%3Dwdi10.8c2ba76bd4d351a44e0fb0275e1eaa89dac5aba824761b9e4e8dedf8fa27fe68%26format%3Dcookie%26netWorkType%3DNONE%26platformVersion%3D1%26protocolVersion%3D300%26providerName%3DNONE%26redirectUrl%3Dhttps%253A%252F%252Fvip.xunlei.com%252F%26sdkVersion%3Dv4.5.11%26thirdAppid%3D%26thirdType%3Dqq&response_type=code>
 * 3. 获取微信授权
 *    <https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxa79ae207bd77d088&redirect_uri=http://example.succez.com/api/auth/{id}/{wechat}/redirect&response_type=code&scope=snsapi_userinfo&state=1223#wechat_redirect>
 *
 * 调用场景:
 *
 * 1. 系统设置了默认的单点登录方式为OAuth2或CAS等服务,当用户第一次访问SuccBI系统页面需要登录时系统
 *    会调用此函数构造一个授权页面的URL,用于显示第三方授权页面,并在授权成功后重定向到参数
 *    `redirect_uri`指定的页面。
 * 2. 在PC扫码登录的场景中(如用微信或支付宝扫码),手机从电脑电脑屏幕扫码后手机上会显示微信标准的授
 *    权页面,提示用户授权(如果用户之前已经授权过了,那么不会提示,微信会静默授权),授权成功后会定
 *    位到参数`redirect_uri`指定的页面。
 *
 * 注意事项:
 *
 * 1. 实现者可以根据`request`参数判断用户的设备和app
 * 2. 当使用pc扫码登录的场景时,如果返回null 则表明当前所使用浏览器不是单点登录方式所指定的app
 * 3. 系统中配置了多个单点登录方案(ssoid)都支持扫码时,如果用户用微信扫码,那么应该调用哪个实现的
 *    函数呢(支付宝也有类似问题)?系统会在用户用app扫码后(扫的是
 *    `/api/auth/qrcode?app=xxxx&id={qrcodeid}`这样的地址),以此调用所有单点登录方案的此函数,直到一
 *    个实现返回了期望的值,如`true`、一个URL地址。
 *
 * 返回值说明:
 *
 * 1. 返回`false`,表示当前访问系统的app,无法使用当前定制的单点登录方案。
 * 2. 返回`true`,则表明当前单点登录方案支持这个app登录,且脚本函数自己返回的授权页面,系统不需要显
 *    示默认的了。
 * 3. 返回一个URL地址,应该是一个可以用于重定向的完整的URL地址。请确保URL地址中的字符都有正确的编
 *    码,一些特殊字符(比如空格,[,]、中文等)可能引起服务器抛出不必要的错误
 *
 * @see https://developers.weixin.qq.com/doc/service/guide/h5/
 *
 * @param request web请求对象
 * @param response web响应对象
 * @param redirect_uri 实现者应该使用此参数构建获取授权后回跳到本系统的url中的参数,这样当收到授权验
 *        证信息信息回调请求或者根据code换取令牌的请求时系统才会自动定位到`onSSOCheckTicket`函数。
 * @returns 返回值说明
 */
declare function onSSOGetAuthRedirectURL(request: HttpServletRequest, response: HttpServletResponse, redirect_uri: string): boolean | string | void | null;

/**
 * 脚本单点登录方案定制 —— 用户授权后根据第三方系统返回的授权验证信息获取授权用户信息
 *
 * 使用场景:
 *
 * 1. OAuth2协议中,根据code获取令牌。或者CAS协议中验证票据合法性。此时前端是使用http重定向方式将授
 *    权信息传递给后台的。
 * 2. 微信小程序登录场景中,当用户授权小程序页面后,小程序会通过ajax把授权信息(加密的)发送给SuccBI
 *    后端进行解密验证
 *
 * 实现注意事项:
 *
 * 1. 当实现了`onSSOGetAuthRedirectURL`函数时,通常也需要实现此函数。
 * 2. 此函数只需要验证票据的合法性获得用户授权的用户信息,系统会自动根据系统设置中的设置决定是否允许
 *    用户登录
 * 3. 如果验证失败,那么应该抛出异常。
 *
 * 返回值说明:
 *
 *  1. 如果返回的用户信息中有明确的用户目录信息(`userDirectory`,`sys`表示内部用户,`external`表示外
 *      部用户),那么系统将自动根据用户的目录去决定使用内部用户还是外部用户进行匹配
 *  2. 返回的用户信息的属性如果在用户表中有同名字段(物理字段名同名,忽略大小写),那么在新增用户时
 *     将自动写入这些字段
 *  3. 为了以后支持多个公众号,系统会默认先找`ssoid+下划线+属性名`的字段是否存在,如果存在,优先用这
 *     个。
 *
 * @param request web请求对象,可通过`request.getParameter()`获取url上的参数,通过
 *        `request.getRequestBody()`函数获取POST方式发送的参数,如果请求的`Contect-Type`是
 *        `application/json` 那么`request.getRequestBody()`返回json对象。
 * @param response web响应对象
 * @returns 返回 验证票据所返回的用户信息
 * @throws 如果验证失败,那么应该抛出异常。
 */
declare function onSSOCheckTicket(request: HttpServletRequest, response: HttpServletResponse): UserInfo | void;

/**
 * 脚本单点登录方案的定制 -- 在本系统注销登录后,通知第三方系统也注销登录
 *
 * 系统启用单点登录的情况下,在本系统注销登录后,用户在第三方系统还是处于登录状态,此函数用于构建一
 * 个在第三方系统进行注销登录的url
 *
 * @param request web请求对象
 * @param response web响应对象
 * @param redirect_uri 为通知第三方退出登录后,期望回跳到本系统的url地址
 * @returns 请确保返回的url中使用的字符都有正确的编码,一些特殊字符(比如空格,[,]等)可能引起服务器
 *          抛出不必要的错误
 */
declare function  onSSOGetLogoutRedirectURL(request: HttpServletRequest, response: HttpServletResponse, redirect_uri: string, ssoArgs: SSOArgs): string | void | null;

# 程序流脚本

# 自定义表达式函数

自定义表达式函数

是否有帮助?
0条评论
评论