Fxiaoke Developer Manual Fxiaoke Developer Manual
  • APL Development Manual
  • PWC Development Manual
  • OpenAPI Documentation
APL Code Introduction
API Reference
Development Tools
Release Notes
  • 简体中文
  • English
APL Code Introduction
API Reference
Development Tools
Release Notes
  • 简体中文
  • English
  • Getting Started

  • APL Function Open Scenarios

  • APL Class Open Scenarios

    • Common Library

    • Electronic Signature

    • Authentication Provider

    • Available Business Types

    • Object Controller Plugin

    • Event Listener

    • Object Export Plugin

    • ERP Integration Platform

    • Field Service Type Function Validation

    • FMCG Order Business Plugin

    • Object Business Handler

    • Online Documentation

      • 1.Overview
      • 2.Configuration Steps
      • 3.Groovy Code Examples
      • 4.FAQ
    • Custom Model

    • Environment Deployment

Groovy Code Example

/**  
 * @author Administrator  
 * @codeName OnlineDocWpsPersonal  
 * @description Create New  
 * @createTime 2024-08-05  
 */  
class OnlineDocCustom implements OnlineDocPersonalPlugin {  

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

    // Vendor domain  
    private static String HOST = "https://{host}"  
    // User authorization URL  
    private static String AUTH_CODE_URL = "/{auth_user_path}"  
    // Get token URL  
    private static String QUERY_ACCESS_TOKEN_URL = "/{query_token_path}?"  
    // Refresh token URL  
    private static String REFRESH_ACCESS_TOKEN_URL = "/{refresh_token_path}?"  
    // Get file list URL  
    private static String QUERY_FILE_LIST_URL = "/{query_files_path}?"  
    // Authorization callback URL suffix  
    private static String CALLBACK_URL_SUFFIX = "/custom/authRedirect"  

    // Save token info to local cache (WPS defaults to 24 hours)  
    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)  
    }  

    // Get token info from local cache  
    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  
    }  

    // Get or refresh 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  
        }  
        // Refresh accessToken  
        Map<String, Object> refreshMap = refreshAccessToken(pluginApiName, devInfo, authRuntimeData, context)  
        return refreshMap  
    }  

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

    // Enterprise authentication (currently unused)  
    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()  
        // Get 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  
    }  

    // Refresh 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()) {  
            // Never authorized, simulate WPS error code and return auth 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)  
        // Check request error  
        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  
        // Check business error  
        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  
        }  
        // Add expiration timestamp  
        Map tokenMap = contentMap["token"] as Map  
        String accessToken = tokenMap["access_token"] as String  
        Integer expiresSecond = tokenMap["expires_in"] as Integer  
        // Save to local cache  
        saveAccessToken2Cache(context.getTenantId(), context.getUserId(), pluginApiName, tokenMap, expiresSecond)  
        // Return result  
        Map<String, Object> resultMap = [:]  
        resultMap.put("errorCode", 0)  
        resultMap.put("accessToken", accessToken)  
        log.info("refreshAccessToken finish success:" + resultMap.size())  
        return resultMap  
    }  

    // Request 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)  
        // Check request error  
        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  
        // Check business error  
        if (bizErrorCode != 0) {  
            log.info("queryAccessToken request biz failed error:" + contentMap)  
            Map resultMap = [:]  
            resultMap["errorCode"] = bizErrorCode  
            resultMap["errorMessage"] = bizErrorMessage  
            return resultMap  
        }  
        // Add expiration timestamp  
        Map tokenMap = contentMap["token"] as Map  
        Integer expiresSecond = tokenMap["expires_in"] as Integer  
        // Save to local cache  
        saveAccessToken2Cache(context.getTenantId(), context.getUserId(), pluginApiName, tokenMap, expiresSecond)  
        // Return result  
        Map resultMap = [:]  
        resultMap["errorCode"] = 0  
        resultMap["runtimeData"] = tokenMap  
        resultMap["expiredTime"] = DateTime.now().toTimestamp() + 90 * 24 * 3600 * 1000L  
        return resultMap  
    }  

    // Get personal authorization URL  
    public GetPersonalAuthUrl.Result getPersonalAuthUrl(GetPersonalAuthUrl.Arg arg, FunctionContext context) {  
        // Standard log (do not remove)  
        log.info("getPersonalAuthUrl arg:" + arg)  

        /* Business logic starts here */  
        String appId = arg.getDevInfo().get("appId")  
        String callbackUrl = arg.getDevInfo().get("callbackUrl") + CALLBACK_URL_SUFFIX  

        // Construct Fxiaoke business parameters  
        Map<String, String> authMap = [:]  
        authMap.put("pluginApiName", arg.getPluginApiName())  
        authMap.put("enterpriseId", context.getTenantId())  
        authMap.put("employeeId", context.getUserId())  
        // Encode into state parameter  
        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)  
        // Standard log (do not remove)  
        log.info("getPersonalAuthUrl result:" + result)  
        return result  
    }  

    // Process callback data  
    public ActionCallback.Result actionCallback(ActionCallback.Arg arg, FunctionContext context) {  
        // Standard log (do not remove)  
        log.info("actionCallback arg:" + arg)  

        /* Business logic starts here */  
        String pluginApiName = arg.getPluginApiName()  
        String code = arg.getCode()  
        String appId = arg.getDevInfo().get("appId")  
        String appKey = arg.getDevInfo().get("appKey")  
        // Get user authorization 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)  
        // Standard log (do not remove)  
        log.info("actionCallback result:" + result)  
        return result  
    }  

    // Query file list  
    public QueryFileList.Result queryFileList(QueryFileList.Arg arg, FunctionContext context) {  
        // Standard log (do not remove)  
        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  
        // Get or refresh 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 failure result  
            return buildQueryFileListFailedResult(arg.getPluginApiName(), errorCode, errorMessage, arg.getDevInfo(), context)  
        }  
        // Query files  
        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  
        }  
        // Enter selected folder  
        if (arg.getFolder() != null) {  
            parentId = arg.getFolder().getFileId()  
        }  
        // Request file list  
        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 failure result  
            return buildQueryFileListFailedResult(arg.getPluginApiName(), errorCode, errorMessage, arg.getDevInfo(), context)  
        }  
        // Return result  
        QueryFileList.Result result = new QueryFileList.Result()  
        result.setErrorCode(errorCode)  
        result.setErrorMessage(errorMessage)  
        result.setFileList(fileList)  
        result.setHasMore(fileListMap["hasMore"] as boolean)  
        result.setExtraInfo(extraInfo)  
        // Standard log (do not remove)  
        log.info("queryFileList result:" + result)  
        return result  
    }  

    private QueryFileList.Result buildQueryFileListFailedResult(String pluginApiName,  
                                                              int errorCode,  
                                                              String errorMessage,  
                                                              Map devInfo,  
                                                              FunctionContext context) {  
        // These errors require user re-authorization  
        if (errorCode == 1234567890) {  
            // Check if authorization is needed  
            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)  
    }  

    // Query file list (pageNumber starts from 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)  
2.Configuration Steps
4.FAQ

← 2.Configuration Steps 4.FAQ→

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