纷享销客开发者手册 纷享销客开发者手册
  • APL开发手册
  • PWC开发手册
  • OpenAPI 文档
APL代码介绍
API Reference
开发工具
更新日志
  • 简体中文
  • English
APL代码介绍
API Reference
开发工具
更新日志
  • 简体中文
  • English
  • 入门

  • APL函数开放场景

  • APL类开放场景

    • 公共库

    • 电子签

    • 认证提供商

    • 可用业务类型

    • 对象Controller插件

    • 事件监听

    • 对象导出插件

    • ERP集成平台

    • 外勤类型函数校验

    • 快消订货业务插件

    • 对象业务处理器

    • 在线文档

      • 1.概述
      • 2.配置步骤
      • 3.Groovy代码示例
      • 4.常见问题
    • 自定义模型

    • 环境部署

Groovy代码示例

/**
 * @author 管理员
 * @codeName OnlineDocWpsPersonal
 * @description 新建
 * @createTime 2024-08-05
 */
class OnlineDocCustom implements OnlineDocPersonalPlugin {

    private static String CONTENT_TYPE = "application/json;charset=utf-8"

    //厂商域名
    private static String HOST = "https://{host}"
    //用户授权url
    private static String AUTH_CODE_URL = "/{auth_user_path}"
    //获取token url
    private static String QUERY_ACCESS_TOKEN_URL = "/{query_token_path}?"
    //刷新token url
    private static String REFRESH_ACCESS_TOKEN_URL = "/{refresh_token_path}?"
    //获取文件列表url
    private static String QUERY_FILE_LIST_URL = "/{query_files_path}?"
    //授权回调地址后缀
    private static String CALLBACK_URL_SUFFIX = "/custom/authRedirect"


    //保存token信息到本地缓存,wps默认24小时
    private void saveAccessToken2Cache(String tenantId, String userId, String pluginApiName, Map<String, Object> runtimeData, int expiresSecond) {
        Map tokenMap = [:]
        tokenMap.putAll(runtimeData)
        tokenMap.put("token_expires_millis", DateTime.now().toTimestamp() + (expiresSecond - 30) * 1000)

        String key = "OnlineDocCustomAccess_token_" + userId + "_" + pluginApiName
        Cache cache = Fx.cache.getDefaultCache()
        String value = Fx.json.toJson(tokenMap)
        cache.put(key, value, expiresSecond)
        log.info("saveAccessToken2Cache key:" + key)
    }

    //获取token信息从本地缓存中
    private String getAccessTokenFromCache(String tenantId, String userId, String pluginApiName) {
        String key = "OnlineDocCustomAccess_token_" + userId + "_" + pluginApiName
        Cache cache = Fx.cache.getDefaultCache()
        String value = cache.get(key) as String
        if (value == null) {
            log.info("getAccessTokenFromCache key:" + key + " value:null")
            return null
        }
        Map tokenMap = Fx.json.parse(value)
        String token = tokenMap["access_token"] as String
        Long expired = tokenMap["token_expires_millis"] as Long
        if (expired <= DateTime.now().toTimestamp()) {
            log.info("getAccessTokenFromCache key:" + key + " value:expired")
            return null
        }
        log.info("getAccessTokenFromCache key:" + key)
        return token
    }

    //获取runtimeData
    private Map getOrRefreshAccessToken(String pluginApiName, Map devInfo, Map authRuntimeData, FunctionContext context) {
        String accessTokenInCache = getAccessTokenFromCache(context.getTenantId(), context.getUserId(), pluginApiName)
        if (accessTokenInCache != null) {
            Map<String, Object> refreshMap = [:]
            refreshMap.put("errorCode", 0)
            refreshMap.put("accessToken", accessTokenInCache)
            return refreshMap
        }
        //刷新accessToken
        Map<String, Object> refreshMap = refreshAccessToken(pluginApiName, devInfo, authRuntimeData, context)
        return refreshMap
    }

    //构建header
    private Map buildHeader(String appId, String appKey, String uri, Map body) {
        //
        Map header = [:]
        header["Content-Type"] = CONTENT_TYPE
        //...
        return header
    }

    //企业认证 暂时未用到
    public EnterpriseAuth.Result enterpriseAuth(EnterpriseAuth.Arg arg, FunctionContext context) {
        log.info("enterpriseAuth arg:" + arg)
        String appId = arg.getDevInfo().get("appId") as String
        String appKey = arg.getDevInfo().get("appKey") as String
        String pluginApiName = arg.getPluginApiName()
        //获取company token
        Map tokenMap = queryCompanyToken(pluginApiName, appId, appKey)
        String companyToken = tokenMap["companyToken"] as String
        int errorCode = tokenMap["errorCode"] as Integer
        String errorMessage = tokenMap["errorMessage"] as String
        if (errorCode != 0) {
            Fx.message.throwException(errorMessage)
        }
        EnterpriseAuth.Result result = new EnterpriseAuth.Result()
        //result.setErrorCode(0)
        //result.setErrorMessage(errorMessage)
        log.info("enterpriseAuth result:" + result)
        return result
    }

    //刷新access token
    private Map<String, Object> refreshAccessToken(String pluginApiName, Map<String, String> devInfo, Map<String, Object> authRuntimeData, FunctionContext context) {
        log.info("refreshAccessToken arg:" + pluginApiName)
        Map authMap = authRuntimeData
        if (authMap.isEmpty()) {
            //从未授权,模拟金山错误码,下发授权url
            Map<String, Object> resultMap = [:]
            resultMap.put("errorCode", 100050)
            resultMap.put("errorMessage", "never to auth")
            log.info("refreshAccessToken failed resultMap:" + resultMap)
            return resultMap
        }
        String appId = devInfo.get("appId")
        String appKey = devInfo.get("appKey")
        String refreshToken = authMap.get("refresh_token")
        String queryRefreshTokenUrl = HOST + REFRESH_ACCESS_TOKEN_URL + "appid=" + appId + "&appkey=" + appKey + "&refresh_token=" + refreshToken
        Map headers = ["Content-Type": CONTENT_TYPE]
        Map requestBody = [:]
        log.info("refreshAccessToken request begin:" + queryRefreshTokenUrl)
        def (Boolean error, HttpResult httpResult, String errorMessage) = Fx.http.post(queryRefreshTokenUrl, headers, requestBody, 10000, false, 0)
        log.info("refreshAccessToken request finish:" + httpResult)
        //检查请求错误
        if (error || httpResult == null) {
            log.info("refreshAccessToken request null error :" + errorMessage)
            Map<String, Object> resultMap = [:]
            resultMap.put("errorCode", -1)
            resultMap.put("errorMessage", "request error:" + errorMessage)
            return resultMap
        }
        Map contentMap = null
        if (httpResult.statusCode == 200) {
            contentMap = httpResult.content as Map
        } else {
            contentMap = Fx.json.parse(httpResult.content as String)
        }
        Integer bizErrorCode = contentMap.get("result") as Integer
        String bizErrorMessage = contentMap.get("msg") as String
        //检查请求业务错误
        if (bizErrorCode != 0) {
            Map<String, Object> resultMap = [:]
            resultMap.put("errorCode", bizErrorCode)
            resultMap.put("errorMessage", bizErrorMessage)
            log.info("refreshAccessToken request biz failed error:" + contentMap)
            return resultMap
        }
        //补充过期时间戳
        Map tokenMap = contentMap["token"] as Map
        String accessToken = tokenMap["access_token"] as String
        Integer expiresSecond = tokenMap["expires_in"] as Integer
        //保存到本地缓存
        saveAccessToken2Cache(context.getTenantId(), context.getUserId(), pluginApiName, tokenMap, expiresSecond)
        //返回结果
        Map<String, Object> resultMap = [:]
        resultMap.put("errorCode", 0)
        resultMap.put("accessToken", accessToken)
        log.info("refreshAccessToken finish success:" + resultMap.size())
        return resultMap
    }

    //请求access token
    private Map<String, Object> queryAccessToken(String pluginApiName,
                                                 String appId,
                                                 String appKey,
                                                 String code,
                                                 FunctionContext context) {
        String queryTokenUrl = HOST + QUERY_ACCESS_TOKEN_URL + "appid=" + appId + "&appkey=" + appKey + "&code=" + code
        Map headers = ["Content-Type": CONTENT_TYPE]
        log.info("queryAccessToken request begin:" + queryTokenUrl)
        def (Boolean error, HttpResult httpResult, String errorMessage) = Fx.http.get(queryTokenUrl, headers, 10000, false, 0)
        log.info("queryAccessToken request finish:" + httpResult)
        //检查请求错误
        if (error || httpResult == null) {
            log.info("queryAccessToken request null error :" + errorMessage)
            Map resultMap = [:]
            resultMap["errorCode"] = -1
            resultMap["errorMessage"] = "request error:" + errorMessage
            return resultMap
        }
        Map contentMap = null
        if (httpResult.statusCode == 200) {
            contentMap = httpResult.content as Map
        } else {
            contentMap = Fx.json.parse(httpResult.content as String)
        }
        Integer bizErrorCode = contentMap.get("result") as Integer
        String bizErrorMessage = contentMap.get("msg") as String
        //检查请求业务错误
        if (bizErrorCode != 0) {
            log.info("queryAccessToken request biz failed error:" + contentMap)
            Map resultMap = [:]
            resultMap["errorCode"] = bizErrorCode
            resultMap["errorMessage"] = bizErrorMessage
            return resultMap
        }
        //补充过期时间戳
        Map tokenMap = contentMap["token"] as Map
        Integer expiresSecond = tokenMap["expires_in"] as Integer
        //保存到本地缓存
        saveAccessToken2Cache(context.getTenantId(), context.getUserId(), pluginApiName, tokenMap, expiresSecond)
        //返回结果
        Map resultMap = [:]
        resultMap["errorCode"] = 0
        resultMap["runtimeData"] = tokenMap
        resultMap["expiredTime"] = DateTime.now().toTimestamp() + 90 * 24 * 3600 * 1000L
        return resultMap
    }

    //获取个人授权url
    public GetPersonalAuthUrl.Result getPersonalAuthUrl(GetPersonalAuthUrl.Arg arg, FunctionContext context) {
        //标准日志,请勿删除
        log.info("getPersonalAuthUrl arg:" + arg)

        /*此处开始加业务*/
        String appId = arg.getDevInfo().get("appId")
        String callbackUrl = arg.getDevInfo().get("callbackUrl") + CALLBACK_URL_SUFFIX

        //拼装纷享业务参数
        Map<String, String> authMap = [:]
        authMap.put("pluginApiName", arg.getPluginApiName())
        authMap.put("enterpriseId", context.getTenantId())
        authMap.put("employeeId", context.getUserId())
        //拼接如state中
        String state = Fx.crypto.base64.encode(Fx.json.toJson(authMap) as byte[])
        String authUrl = HOST + AUTH_CODE_URL + "&appid=" + appId + "&redirect_uri=" + callbackUrl + "&state=" + state

        GetPersonalAuthUrl.Result result = new GetPersonalAuthUrl.Result()
        result.setErrorCode(0)
        result.setAuthUrl(authUrl)
        //标准日志,请勿删除
        log.info("getPersonalAuthUrl result:" + result)
        return result
    }

    //处理callback回来的数据
    public ActionCallback.Result actionCallback(ActionCallback.Arg arg, FunctionContext context) {
        //标准日志,请勿删除
        log.info("actionCallback arg:" + arg)

        /*此处开始加业务*/
        String pluginApiName = arg.getPluginApiName()
        String code = arg.getCode()
        String appId = arg.getDevInfo().get("appId")
        String appKey = arg.getDevInfo().get("appKey")
        //获取用户授权token
        Map accessTokenResultMap = queryAccessToken(pluginApiName, appId, appKey, code, context)
        Integer errorCode = accessTokenResultMap["errorCode"] as Integer
        String errorMessage = accessTokenResultMap["errorMessage"] as String
        if (errorCode != 0) {
            ActionCallback.Result result = new ActionCallback.Result()
            result.setErrorCode(errorCode)
            result.setErrorMessage(errorMessage)
            return result
        }
        ActionCallback.Result result = new ActionCallback.Result()
        result.setErrorCode(errorCode)
        result.setRuntimeData(accessTokenResultMap["runtimeData"] as Map)
        result.setExpiredTime(accessTokenResultMap["expiredTime"] as Long)
        //标准日志,请勿删除
        log.info("actionCallback result:" + result)
        return result
    }

    //请求文件列表
    public QueryFileList.Result queryFileList(QueryFileList.Arg arg, FunctionContext context) {
        //标准日志,请勿删除
        log.info("queryFileList arg:" + arg)
        String pluginApiName = arg.getPluginApiName()
        String appId = arg.getDevInfo().get("appId") as String
        String appKey = arg.getDevInfo().get("appKey") as String
        Map authRuntimeData = arg.getAuthRuntimeData()
        Map pluginRuntimeData = arg.getPluginRuntimeData()
        Integer errorCode = 0
        String errorMessage = null
        //获取or更新accessToken
        Map accessTokenMap = getOrRefreshAccessToken(pluginApiName, arg.getDevInfo(), authRuntimeData, context)
        errorCode = accessTokenMap["errorCode"] as Integer
        errorMessage = accessTokenMap["errorMessage"] as String
        String accessToken = accessTokenMap["accessToken"] as String
        if (errorCode != 0) {
            //返回失败结果
            return buildQueryFileListFailedResult(arg.getPluginApiName(), errorCode, errorMessage, arg.getDevInfo(), context)
        }
        //查询文件
        String parentId = null
        Integer offset = extraInfo.getOrDefault("next_offset", 0) as Integer
        Integer pageNumber = extraInfo.getOrDefault("pageNumber", 0) as Integer
        if (arg.isNeedNextPage()) {
            pageNumber += 1
        }
        //进入选择的文件夹
        if (arg.getFolder() != null) {
            parentId = arg.getFolder().getFileId()
        }
        //请求文件列表清单
        Map fileListMap = queryFiles(arg.getPluginApiName(), appId, appKey, companyToken, accessToken, parentId, offset, pageNumber)
        errorCode = fileListMap.get("errorCode") as Integer
        errorMessage = fileListMap.get("errorMessage") as String
        List<Map> fileList = fileListMap.get("fileList") as List
        extraInfo["next_offset"] = fileListMap["next_offset"] as Integer
        extraInfo["pageNumber"] = pageNumber
        if (errorCode != 0) {
            //返回失败结果
            return buildQueryFileListFailedResult(arg.getPluginApiName(), errorCode, errorMessage, arg.getDevInfo(), context)
        }
        //返回结果
        QueryFileList.Result result = new QueryFileList.Result()
        result.setErrorCode(errorCode)
        result.setErrorMessage(errorMessage)
        result.setFileList(fileList)
        result.setHasMore(fileListMap["hasMore"] as boolean)
        result.setExtraInfo(extraInfo)
        //标准日志,请勿删除
        log.info("queryFileList result:" + result)
        return result
    }

    private QueryFileList.Result buildQueryFileListFailedResult(String pluginApiName,
                                                                int errorCode,
                                                                String errorMessage,
                                                                Map devInfo,
                                                                FunctionContext context) {
        //以下错误需要用户重新授权
        if (errorCode == 1234567890) {
            //检查是否需要授权
            GetPersonalAuthUrl.Arg authUrlArg = new GetPersonalAuthUrl.Arg()
            authUrlArg.setPluginApiName(pluginApiName)
            authUrlArg.setDevInfo(devInfo)
            GetPersonalAuthUrl.Result authUrlResult = getPersonalAuthUrl(authUrlArg, context)
            //
            QueryFileList.Result result = new QueryFileList.Result()
            result.setErrorCode(errorCode)
            result.setErrorMessage(errorMessage)
            result.setAuthUrl(authUrlResult.getAuthUrl())
            return result
        }
        Fx.message.throwException(errorMessage)
    }

    //查询文件列表 pageNumber从0开始
    private Map<String, Object> queryFiles(String pluginApiName, String appId, String appKey,
                                           String companyToken, String accessToken,
                                           String parentId, int offset, int pageNumber) {
        int pageSize = 20
        int tmpOffset = pageNumber > 0 ? pageNumber * pageSize + 1 : 0
        String bizUrl = QUERY_FILE_LIST_URL
        bizUrl += "access_token=" + accessToken + "&company_token=" + companyToken + "&offset=" + tmpOffset + "&count=" + pageSize
        if (parentId != null) {
            bizUrl += "&parent_id=" + parentId
        }
        String url = HOST + bizUrl
        Map headers = buildHeader(appId, appKey, bizUrl, null)
        log.info("queryFiles request begin:" + url)
        def (Boolean error, HttpResult httpResult, String errorMessage) = Fx.http.get(url, headers, 10000, false, 0)
        log.info("queryFiles request finish:" + httpResult)
        //检查请求错误
        if (error || httpResult == null) {
            log.info("queryFiles request null error :" + errorMessage)
            Map<String, Object> resultMap = [:]
            resultMap.put("errorCode", -1)
            resultMap.put("errorMessage", "request error:" + errorMessage)
            return resultMap
        }
        Map contentMap = null
        if (httpResult.statusCode == 200) {
            contentMap = httpResult.content as Map
        } else {
            contentMap = Fx.json.parse(httpResult.content as String)
        }
        Integer bizErrorCode = contentMap.get("result") as Integer
        String bizErrorMessage = contentMap.get("msg") as String
        //检查请求业务错误
        if (bizErrorCode != 0) {
            log.info("queryFiles request biz failed error:" + contentMap)
            Map<String, Object> resultMap = [:]
            resultMap.put("errorCode", bizErrorCode)
            resultMap.put("errorMessage", bizErrorMessage)
            return resultMap
        }
        List<Map> fileList = contentMap["files"] as List
        int nextOffset = contentMap["next_offset"] as int

        Map<String, Object> resultMap = [:]
        resultMap.put("errorCode", 0)
        resultMap.put("fileList", convert2FileInfo(pluginApiName, fileList))
        resultMap.put("hasMore", nextOffset > 0)
        resultMap.put("next_offset", nextOffset)
        return resultMap
    }

    //转换成规范结构
    private List<Map> convert2FileInfo(String pluginApiName, List<Map> fileList) {
        List<Map> fileInfoList = []
        fileList.each() { item ->
            Map fileInfo = [:]
            fileInfo.put("fileId", item.get("xx_file_id"))
            fileInfo.put("fileName", item.get("xx_file_name"))
            fileInfo.put("fileExt", findFileExtension(item.get("xx_file_name") as String))
            fileInfo.put("fileType", convertFileType(item.get("xx_file_type") as String))
            fileInfo.put("fileSize", item.get("xx_file_size"))
            fileInfo.put("createTime", 1000 * (item.get("xx_ctime") as long))
            fileInfo.put("updateTime", 1000 * (item.get("xx_mtime") as long))
            fileInfo.put("pluginApiName", pluginApiName)
            fileInfoList.add(fileInfo)
        }
        return fileInfoList
    }

    private String findFileExtension(String fileName) {
        def parts = fileName.split("\\.")
        if (parts.size() > 1) {
            return parts[parts.size() - 1]
        }
        return null
    }

    //检查文件类型,只支持文件夹、文件、未知
    private String convertFileType(String fileType) {
        switch (fileType) {
            case "file":
            case "sharefile":
                return "file"
            case "folder":
            case "linkfolder":
                return "folder"
            default:
                return "unknown"
        }
    }

    static void main(String[] args) {

    }

}

2.配置步骤
4.常见问题

← 2.配置步骤 4.常见问题→

  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式