用友 OpenAPI
1. OpenAPI含义
1.1 什么是OpenAPI
Open API即开放API,也称开放平台。 所谓的开放API(OpenAPI)是服务型网站常见的一种应用,网站的服务商将自己的网站服务封装成一系列API(Application Programming Interface,应用编程接口)开放出去,供第三方开发者使用,这种行为就叫做开放网站的API,所开放的API就被称作OpenAPI(开放API)。
网站提供开放平台的API后,可以吸引一些第三方的开发人员在该平台上开发商业应用,平台提供商可以获得更多的流量与市场份额,第三方开发者不需要庞大的硬件与技术投资就可以轻松快捷的创业,从而达到双赢的目的,开放API是大平台发展、共享的途径,让开发者开发一个有价值应用,付出的成本更少,成功的机会更多。今天,OpenAPI作为互联网在线服务的发展基础,已经成为越来越多互联网企业发展服务的必然选择。
1.2 Uap实现openAPI
就现在互联网上Open API的形态来看,主要分成两种:标准REST和类REST(也可以叫做RPC形态)。
REST形态主要有这么几点特点:
1.服务地址就是资源定位地址。
2.服务操作就是Http请求中的方法类型(GET,POST,DELETE,PUT),这其实是抽象现实当中对于服务的增删改查操作。
Restlet项目为“建立REST概念与Java类之间的映射”提供了一个轻量级而全面的框架。
UAP在Restlet框架之上,选择了官方JAX-RS扩展,并且在扩展的基础上与NC进行了集成。
2. OpenAPI的开发
2.1 开发前准备工作
请使用平台提供的nchome,并且准备如下两个步骤:
- nchome中modules/uapfw路径下包含pubuapfw_restframeworkLevel-1.jar
- nchome/hotwebs/nccloud/WEB-INF/web.xml中增加配置,配置如下所示:
<context-param>
<param-name>org.restlet.application</param-name>
<param-value>uap.ws.rest.core.UAPRestJaxRsApplication</param-value>
</context-param>
<filter>
<filter-name>openCloudSecurityFilter</filter-name>
<filter-class>nccloud.ws.opm.core.filter.OpenCloudSecurityFilter</filter-class>
</filter>
<!-- opm login-->
<filter>
<filter-name>openCloudLoginFilter</filter-name>
<filter-class>nccloud.ws.opm.core.filter.OpenCloudLoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>openCloudSecurityFilter</filter-name>
<url-pattern>/api/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>openCloudLoginFilter</filter-name>
<url-pattern>/opm/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>RestletServlet</servlet-name>
<servlet-class>uap.ws.rest.servlet.UAPRSServerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RestletServlet</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
2.2 OpenAPI开发步骤
2.2.1 创建资源
创建资源类xxResource继承AbstractNCCRestResource
package nccloud.openapi.uapbd.currtype.currtype;
import java.util.ArrayList;
import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Produces;
import javax.ws.rs.Path;
import org.json.JSONString;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import nc.bs.logging.Logger;
import nc.vo.bd.currtype.CurrtypeVO;
import nccloud.api.rest.utils.NCCRestUtils;
import nccloud.api.rest.utils.ResultMessageUtil;
import nccloud.impl.platform.common.util.QueryUtil;
import nccloud.ws.rest.resource.AbstractNCCRestResource;
@Path("uapbd/currtype/currtype")
public class CurrtypeResource extends AbstractNCCRestResource{
@POST
@Path("queryCurrtypeByCode")
@Consumes("application/json")
@Produces("application/json")
public JSONString queryCurrtypeByCode(JSONString json) {
if (json == null) {
Logger.error("输入数据异常");
return ResultMessageUtil.exceptionToJSON(new IllegalArgumentException("输入数据异常"));
}
JSONArray jsonarray = JSONObject.parseArray(json.toJSONString());
if (jsonarray == null || jsonarray.size() == 0) {
return ResultMessageUtil.exceptionToJSON(new IllegalArgumentException("输入数据异常"));
}
// 异常信息
List<String> msglist = new ArrayList<String>();
// 查询出的所有数据
List<CurrtypeVO> allData = new ArrayList<CurrtypeVO>();
String currTypeCode = "";
for (int i = 0; i < jsonarray.size(); i++) {
JSONObject jObject = jsonarray.getJSONObject(i);
currTypeCode = jObject.getString("code");
}
String sql = " code='" + currTypeCode + "'";
CurrtypeVO[] vos = QueryUtil.queryVOByCond(CurrtypeVO.class, sql, null);
if(vos == null || vos.length == 0) {
return NCCRestUtils.toJSONString("123123");
}
else {
return NCCRestUtils.toJSONString(vos);
}
}
@Override
public String getModule() {
return "uapbd";
}
}
2.2.2 注册资源(rest文件)
<?xml version="1.0" encoding='gb2312'?>
<module>
<rest>
<resource classname="nccloud.openapi.uapbd.currtype.currtype.CurrtypeResource" exinfo=""/>
</rest>
</module>
2.2.3 添加接口说明(MD文件)
openAPI开发完成以后,需要对外提供接口的使用方法,包括接口能力、调用方式、需要的参数、返回的数据(数据说明,json)。参照的格式如下所示:
# 根据会计期间主键删除会计期间
## 1.接口说明
根据会计期间主键删除会计期间
不支持批量删除
## 2.使用场景
根据会计期间主键删除会计期间
## 3.接口调用说明
### 3.1请求类型
post
### 3.2请求示例
> http://IP:port/nccloud/api/uapbd/accperiodmanage/accperiod/deleteAccPeriodByPk
### 3.3请求URL参数说明
|参数|类型|是否必填|描述|
|------|------|------|------|
|pk|string[]|是|会计期间主键|
> JSON示例
```
{
"pk": ["123"]
}
```
### 3.4返回参数说明
#### 3.4.1正确返回示例
>JSON示例:
```
[
{
"true"
}
]
```
2.2.4 OpenAPI注册以及接口测试
2.2.4.1 OpenAPI的注册
完成Java类以及rest文件编写之后,还需要进行一步网上资源的注册。步骤如下:
访问地址[http://ip:port/nccloud/resources/opm,并通过管理员登录。](javascript:void(0))
在API维护页签当中新增加相关的API,其中需要注意一下几个点:
URI路径如下所示:/nccloud/api/uapbd/currtype/currtype/queryCurrtypeByCode
其中uapbd/currtype/currtype对应于资源类的Path,而queryCurrtypeByCode对应于相关方法的Path注解。
在第三方应用管理当中进行应用的注册并且分配相关的小应用,然后就可以使用注册app_id,app_secret,加密类型以及公钥字段进行API的访问了。
注册API的步骤如下图所示:
注册第三方应用如下图所示:
2.2.4.1 OpenAPI的接口测试
API的测试或者请求流程如下所示:
第一步请求获取相应的token,请求的URL路径如下:[http://127.0.0.1/nccloud/opm/accesstoken](javascript:void(0))?
biz_center=1&grant_type=client_credentials&signature=3ca73b3bb506a34059e2bce1ce3bfe128e4e9f
b6cefb7325396094f7816a01d9&client_secret=Su1s4kk0pQhhgAupsDajkqSFWeWxUEAy78yYh84wTH
t1UPyC2ZV3CD7%2BP12XB897owyaVFQRJd2g%0D%0AfZcPwkvlxUgq3yrp1PBYxZ1TJ89oLf4Wicn
%2BsDVAi57pTlsHHZZqQqLow5zdQjNP3Wm04ewszLhu%0D%0AasoViTdspzujiPAmwxY%3D%0D%
0A&client_id=wqch
其中各个参数的含义如下:
biz_center:访问的nccloud系统的账套code
grant_type:授权模式,此处为client_credentials
client_id:对应于在第三方应用注册当中的app_id
client_secret:对应于第三方应用注册当中的app_secret
signature:请求加签,其算法为SHA256Util.getSHA256(client_id + client_secret + pubKey)
其中pubKey为第三方应用注册当中的公钥字段
第二步为最终请求的OpenAPI,其中URL地址如下所示:[http://127.0.0.1/nccloud/api/uapbd/currtype/](javascript:void(0))
currtype/queryCurrtypeByCode
其中在请求头当中包含了相关的参数,如下所示:
access_token:即上一步当中获取到的token。
ucg_flag:为Y。
signature:对请求体进行加签,算法如下:SHA256Util.getSHA256(client_id+requestBody+pubkey)
其中pubKey为第三方应用注册当中的公钥字段。
下面是一段调用接口的Java代码及其配置:
package nccloud.api.testcase.base;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.lang3.StringUtils;
import com.google.gson.Gson;
import nccloud.api.test.utils.CompressUtil;
import nccloud.api.test.utils.Decryption;
import nccloud.api.test.utils.Encryption;
import nccloud.api.test.utils.ResultMessageUtil;
import nccloud.api.test.utils.SHA256Util;
/**
* 1.从resources/config.properties中读取测试api相关的数据 2.运行程序,测试查看测试结果
*
* @author lizhmf
* @date 2019年6月20日上午10:53:11
*/
public class Test {
private static String client_secret = null;
private static String pubKey = null;
private static String client_id = null;
private static String username = null;
private static String pwd = null;
private static String busi_center = null;
// 获取token方式
private static String grant_type = null;
// 服务器ip:port
private static String baseUrl = null;
private static String secret_level = null;
private static String requestBody = null;
// openapi请求路径
private static String apiUrl = null;
public static String token = null;
public static String repeat_check = null;
public static String busi_id = null;
public static void main(String[] args) {
try {
// 初始化数据
init();
// 请求token
token = getToken();
System.out.println("getTokenData:" + token);
if (token != null) {
// 测试openapi
testApi(token);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 通过refresh_token重新获取token
*
* @param refresh_token
* @return
* @throws UnsupportedEncodingException
* @throws Exception
*/
private static String getTokenByRefreshToken(String refresh_token) throws UnsupportedEncodingException, Exception {
Map<String, String> paramMap = new HashMap<String, String>();
// 密码模式认证
paramMap.put("grant_type", "refresh_token");
// 第三方应用id
paramMap.put("client_id", client_id);
// 第三方应用secret 公钥加密
paramMap.put("client_secret", URLEncoder.encode(Encryption.pubEncrypt(pubKey, client_secret), "utf-8"));
// 签名
String sign = SHA256Util.getSHA256(client_id + client_secret + refresh_token + pubKey);
paramMap.put("signature", sign);
String url = baseUrl + "/nccloud/opm/accesstoken";
String mediaType = "application/x-www-form-urlencoded";
String token = doPost(url, paramMap, mediaType, null, "");
return token;
}
private static String getToken() throws Exception {
String token = null;
if ("password".equals(grant_type)) {
// 密码模式
token = getTokenByPWD();
} else if ("client_credentials".equals(grant_type)) {
// 客户端模式
token = getTokenByClient();
} else if ("authorization_code".equals(grant_type)) {
// TODO 页面跳转
// 授权码模式
}
return token;
}
/**
* 客户端模式获取token
*
* @return
* @throws Exception
*/
private static String getTokenByClient() throws Exception {
Map<String, String> paramMap = new HashMap<String, String>();
// 密码模式认证
paramMap.put("grant_type", "client_credentials");
// 第三方应用id
paramMap.put("client_id", client_id);
// 第三方应用secret 公钥加密
paramMap.put("client_secret", URLEncoder.encode(Encryption.pubEncrypt(pubKey, client_secret), "utf-8"));
// 账套编码
paramMap.put("biz_center", busi_center);
// // TODO 传递数据源和ncc登录用户
// paramMap.put("dsname", "TM_0614");
// paramMap.put("usercode", "1");
// 签名
String sign = SHA256Util.getSHA256(client_id + client_secret + pubKey);
paramMap.put("signature", sign);
String url = baseUrl + "/nccloud/opm/accesstoken";
String mediaType = "application/x-www-form-urlencoded";
String token = doPost(url, paramMap, mediaType, null, "");
return token;
}
/**
* 密码模式获取token
*
* @return
* @throws Exception
*/
@SuppressWarnings("unused")
private static String getTokenByPWD() throws Exception {
Map<String, String> paramMap = new HashMap<String, String>();
// 密码模式认证
paramMap.put("grant_type", "password");
// 第三方应用id
paramMap.put("client_id", client_id);
// 第三方应用secret 公钥加密
paramMap.put("client_secret", URLEncoder.encode(Encryption.pubEncrypt(pubKey, client_secret), "utf-8"));
// ncc用户名
paramMap.put("username", username);
// 密码 公钥加密
paramMap.put("password", URLEncoder.encode(Encryption.pubEncrypt(pubKey, pwd), "utf-8"));
// 账套编码
paramMap.put("biz_center", busi_center);
// 签名
String sign = SHA256Util.getSHA256(client_id + client_secret + username + pwd + pubKey);
paramMap.put("signature", sign);
String url = baseUrl + "/nccloud/opm/accesstoken";
String mediaType = "application/x-www-form-urlencoded";
String token = doPost(url, paramMap, mediaType, null, "");
return token;
}
/**
* 请求openapi
*
* @param token
* @param security_key
* 请求body参数加密压缩用的key
* @throws Exception
*/
private static void testApi(String token) throws Exception {
// token转对象,获取api访问所用token和secret
ResultMessageUtil returnData = new Gson().fromJson(token, ResultMessageUtil.class);
Map<String, String> data = (Map<String, String>) returnData.getData();
String access_token = data.get("access_token");
String security_key = data.get("security_key");
String refresh_token = data.get("refresh_token");
System.out.println("【ACCESS_TOKEN】:" + access_token);
// 请求路径
String url = baseUrl + apiUrl;
// header 参数
Map<String,String> headermap = new HashMap<>();
headermap.put("access_token", access_token);
headermap.put("client_id", client_id);
StringBuffer sb = new StringBuffer();
sb.append(client_id);
if (StringUtils.isNotBlank(requestBody)) {
// sb.append(requestBody.replaceAll("\\s*|\t|\r|\n", "").trim());
sb.append(requestBody);
}
sb.append(pubKey);
String sign = SHA256Util.getSHA256(sb.toString());
headermap.put("signature", sign);
if (StringUtils.isNotBlank(busi_id)) {
headermap.put("busi_id", busi_id);
}
if (StringUtils.isNotBlank(repeat_check)) {
headermap.put("repeat_check", repeat_check);
}
headermap.put("ucg_flag", "y");
String mediaType = "application/json;charset=utf-8";
// 表体数据json
// 根据安全级别选择加密或压缩请求表体参数
String json = dealRequestBody(requestBody, security_key, secret_level);
// 返回值
String result = doPost(url, null, mediaType, headermap, json);
String result2 = dealResponseBody(result, security_key, secret_level);
System.out.println("【RESULT】:" + result);
// System.out.println("result解密:" + result2);
}
private static String dealResponseBody(String source, String security_key, String level) throws Exception {
String result = null;
if (StringUtils.isEmpty(level) || SecretConst.LEVEL0.equals(level)) {
result = source;
} else if (SecretConst.LEVEL1.equals(level)) {
result = Decryption.symDecrypt(security_key, source);
} else if (SecretConst.LEVEL2.equals(level)) {
result = CompressUtil.gzipDecompress(source);
} else if (SecretConst.LEVEL3.equals(level)) {
result = CompressUtil.gzipDecompress(Decryption.symDecrypt(security_key, source));
} else if (SecretConst.LEVEL4.equals(level)) {
result = Decryption.symDecrypt(security_key, CompressUtil.gzipDecompress(source));
} else {
throw new Exception("无效的安全等级");
}
return result;
}
/**
* 初始化参数
*/
private static void init() {
// TODO Auto-generated method stub
Properties properties = new Properties();
String filepath = "config.properties";
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
InputStream inputStream = classloader.getResourceAsStream(filepath);
try {
InputStreamReader reader = new InputStreamReader(inputStream, "UTF-8");
properties.load(reader);
client_secret = new String(properties.getProperty("client_secret").getBytes("utf-8"), "utf-8");
client_id = properties.getProperty("client_id");
pubKey = properties.getProperty("pubKey");
username = properties.getProperty("username");
pwd = properties.getProperty("pwd");
busi_center = properties.getProperty("busi_center");
baseUrl = properties.getProperty("baseUrl");
requestBody = new String(properties.getProperty("requestBody").getBytes("utf-8"), "utf-8");
apiUrl = properties.getProperty("apiUrl");
grant_type = properties.getProperty("grant_type");
secret_level = properties.getProperty("secret_level");
repeat_check = properties.getProperty("repeat_check");
busi_id = properties.getProperty("busi_id");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// 根据安全级别设置,表体是否加密或压缩
private static String dealRequestBody(String source, String security_key, String level) throws Exception {
String result = null;
if (StringUtils.isEmpty(level) || SecretConst.LEVEL0.equals(level)) {
result = source;
} else if (SecretConst.LEVEL1.equals(level)) {
result = Encryption.symEncrypt(security_key, source);
} else if (SecretConst.LEVEL2.equals(level)) {
result = CompressUtil.gzipCompress(source);
} else if (SecretConst.LEVEL3.equals(level)) {
result = Encryption.symEncrypt(security_key, CompressUtil.gzipCompress(source));
} else if (SecretConst.LEVEL4.equals(level)) {
result = CompressUtil.gzipCompress(Encryption.symEncrypt(security_key, source));
} else {
throw new Exception("无效的安全等级");
}
return result;
}
/**
* 发送post请求
*
* @param baseUrl
* @param paramMap
* @param mediaType
* @param headers
* @param json
* @return
*/
private static String doPost(String baseUrl, Map<String, String> paramMap, String mediaType, Map<String, String> headers, String json) {
HttpURLConnection urlConnection = null;
InputStream in = null;
OutputStream out = null;
BufferedReader bufferedReader = null;
String result = null;
try {
StringBuffer sb = new StringBuffer();
sb.append(baseUrl);
if (paramMap != null) {
sb.append("?");
for (Map.Entry<String, String> entry : paramMap.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
sb.append(key + "=" + value).append("&");
}
baseUrl = sb.toString().substring(0, sb.toString().length() - 1);
}
URL urlObj = new URL(baseUrl);
urlConnection = (HttpURLConnection) urlObj.openConnection();
urlConnection.setConnectTimeout(50000);
urlConnection.setRequestMethod("POST");
urlConnection.setDoOutput(true);
urlConnection.setDoInput(true);
urlConnection.setUseCaches(false);
urlConnection.addRequestProperty("content-type", mediaType);
if (headers != null) {
for (String key : headers.keySet()) {
urlConnection.addRequestProperty(key, headers.get(key));
}
}
out = urlConnection.getOutputStream();
out.write(json.getBytes("utf-8"));
out.flush();
int resCode = urlConnection.getResponseCode();
if (resCode == HttpURLConnection.HTTP_OK || resCode == HttpURLConnection.HTTP_CREATED || resCode == HttpURLConnection.HTTP_ACCEPTED) {
in = urlConnection.getInputStream();
} else {
in = urlConnection.getErrorStream();
}
bufferedReader = new BufferedReader(new InputStreamReader(in, "utf-8"));
StringBuffer temp = new StringBuffer();
String line = bufferedReader.readLine();
while (line != null) {
temp.append(line).append("\r\n");
line = bufferedReader.readLine();
}
String ecod = urlConnection.getContentEncoding();
if (ecod == null) {
ecod = Charset.forName("utf-8").name();
}
result = new String(temp.toString().getBytes("utf-8"), ecod);
} catch (Exception e) {
System.out.println(e);
} finally {
if (null != bufferedReader) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != out) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != in) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
urlConnection.disconnect();
}
return result;
}
class SecretConst {
/**
* LEVEL0 不压缩、不加密
*/
public static final String LEVEL0 = "L0";
/**
* LEVEL1 只加密、不压缩
*/
public static final String LEVEL1 = "L1";
/**
* LEVEL2 只压缩、不加密
*/
public static final String LEVEL2 = "L2";
/**
* LEVEL3 先压缩、后加密
*/
public static final String LEVEL3 = "L3";
/**
* LEVEL4 先加密、后压缩
*/
public static final String LEVEL4 = "L4";
}
}
相关的资源文件:
#####不变参数
client_id=wqch
client_secret=c02397ac7d49417aaa7c
pubKey=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCLuDvkKNHs0C0+LHJks/0QVGyLWFSqXPvMEJ6jC17ebft+LMrFvSAcFLiE8TGdFoUyMbqB3XDIK78o/VvoRDhHIGmFJXRs/sfgpPvJPPsAwlHHNFR7Wm1Na/MvqlKnGOVhOTCKzQR8kQ7LUIiYrKHgU+IPtSPa7cA5gMG8YrfAYQIDAQAB
secret_level=L0
## 账套code
busi_center=1
#################################################################################################
#####可变参数
## 获取token方式
grant_type=client_credentials
#grant_type=password
#重复校验
#busi_id=1234
#repeat_check=Y
## 服务器地址
baseUrl=http://127.0.0.1
## 请求参数
requestBody=[{\"code\":\"CNY\"}]
## api访问路径
apiUrl=/nccloud/api/uapbd/currtype/currtype/queryCurrtypeByCode
2.3 OpenAPI开发规范
新建业务组件:openapi/src/public
资源包命名规范:nccloud.api.模块.业务组件.资源
示例:nccloud.api.aum.borrow.apply(借用申请)
资源类命名规范:业务组件+资源+Resources(驼峰命名)
示例:BorrowApplyResources
uri定义:nccloud/api/模块/业务组件/资源/动词
(增:add;删除:delete;查询:query;修改:update (其他业务动词自定义))
示例:[http://ip:port/nccloud/api/aum/borrow/apply/query(查询借用申请)。](javascript:void(0))
Md文档参考demo中的格式书写(注:遵循md文档的语法格式)
Md文档的位置:
1、在openapi组件下META-INF同级目录下创建hotwebs文件夹。
2、在资源包的根目录下创建包名为api.modules.模块.组件.md的包
3、在上一步的包下创建相应的md文档
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步