前言
文本基于用友 BIP V5 版本,其它版本展现的功能、接口未必于此一样。
代码是此前写的了,现在分享一下
前置配置
飞书配置
简答略过:
- 在 飞书开放平台 创建自建应用

默认你已经懂得创建企业自建应用并获取到appId、appSecret,后续 yml 配置中需要用到
用友配置
1、左上角四片花瓣点击 --> 搜索 "集成认证中心"

然后新建一个,选择飞书,然后得到一个 集成认证编码,后续用来填写到yml配置文件中

2、新建一个 API 访问令牌,继续四个花瓣搜索 API,选择API调用

然后赋予以下 API 权限 :


代码
yml 配置文件
yml
# 用友ERP对接配置(dev 环境)
yonbip:
# 用友ERP基础URL
base-url: http://bipdev.xxx.com.cn
# 用友ERP应用凭证(API 调用处创建的)
app:
key: 78272d5c82884987b89fdc319b8
secret: 95c549744f9394368ba9533af72409
# 用友ERP第三方集成认证中心编码
integration-auth-code: s5j0j94r # 填写上面创建的
# 用友ERP单点登录配置
sso:
# 飞书自建应用应用凭证(用于SSO)
feishu-app-id: cli_a9b3e.......
feishu-app-secret: RsU2DGK0V.....
# 用友ERP SSO登录路径
sso-suffix-path: /cas/thirdOauth2CodeLogin
# 单点登录URL(可选)
sso-url: ${yonbip.base-url}${yonbip.sso.sso-suffix-path}
Java 代码
配置类用于从 yml 读取配置
java
@Data
@Configuration
@ConfigurationProperties(prefix = "yonbip.sso")
public class YonBipSsoConfig {
/**
* 飞书应用 AppId(用于获取飞书用户信息)
*/
private String feishuAppId;
/**
* 飞书应用 Secret(用于获取飞书用户信息)
*/
private String feishuAppSecret;
/**
* 用友 ERP SSO 登录路径后缀(如:/cas/thirdOauth2CodeLogin)
*/
private String ssoSuffixPath;
/**
* 用友 ERP SSO 登录完整地址(可在 yml 中通过占位符拼接生成)
*/
private String ssoUrl;
}
我用 Interface (自带 final static)存储常量
java
public interface YonBipSsoConst {
/**
* 用友ERP获取第三方登录临时code的接口路径
*/
String GET_THIRD_LOGIN_CODE_PATH = "/iuap-api-gateway/yonbip/yht/getThirdLoginCode";
/**
* 授权类型:授权码模式
*/
String GRANT_TYPE_AUTHORIZATION_CODE = "authorization_code";
/**
* 成功响应码
*/
Integer SUCCESS_CODE = 200;
/**
* Session中存储飞书用户ID的key
*/
String SESSION_FEISHU_USER_ID_KEY = "yonbip:sso:feishu:userId";
/**
* Session中存储state参数的key
*/
String SESSION_STATE_KEY = "yonbip:sso:state";
}
Controller 接口
java
@Slf4j
@RestController
@RequestMapping("/yonbip/sso")
@Tag(name = "用友ERP单点登录")
public class YonBipSsoController {
@Resource
private YonBipSsoService yonBipSsoService;
/**
* 飞书授权回调接口,处理单点登录到用友ERP
* <p>
* 使用说明:
* 1. 在飞书开放平台配置此接口为OAuth回调地址
* 2. 用户在飞书中打开应用后,飞书会携带code参数回调此接口
* 3. 本接口获取飞书用户信息后,自动跳转到用友ERP系统
* <p>
* 参数说明:
* - code: 飞书平台返回的授权码,5分钟有效,每个code只能使用一次
* - state: 可选参数,Base64编码的目标页面URL,登录成功后跳转到该页面
* 如果不传,则跳转到配置文件中的defaultLoginUrl
*
* @param code 飞书授权码
* @param state Base64编码的目标URL(可选)
*/
@NoNeedLogin
@GetMapping("/pc-login")
@Operation(summary = "飞书授权回调-单点登录到用友ERP")
public void feiShuCallback(
@RequestParam("code") String code,
@RequestParam(value = "state", required = false) String state) {
log.info("[用友SSO Controller]接收飞书回调请求 - code: {}, state: {}", code, state);
// 调用Service处理单点登录逻辑
yonBipSsoService.handleFeiShuSsoToYonBip(code, state);
}
/**
* 测试接口:检查配置是否正确
*
* @return 配置信息摘要
*/
@GetMapping("/config-check")
@Operation(summary = "检查SSO配置")
public String checkConfig() {
return "用友ERP单点登录配置检查接口,请查看日志输出";
}
}
HTTP声明接口(使用 Forest:官网):
请求飞书有关的:
java
/**
* 飞书SSO认证HTTP客户端
* 使用Forest框架调用飞书开放平台API
*
* @author lyk
* @date 2025-12-22
*/
public interface FeiShuSsoHttpClient {
/**
* 获取飞书tenant_access_token
* 文档: https://open.feishu.cn/document/server-docs/authentication-management/access-token/tenant_access_token_internal
*
* @param requestBody 请求体,包含app_id和app_secret
* @return 飞书响应
*/
@Post(url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
contentType = "application/json")
ForestResponse<String> getTenantAccessToken(@Body Map<String, String> requestBody);
/**
* 获取飞书user_access_token
* 文档: https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/authen-v1/oidc-access_token/create
*
* @param authorization Authorization header,格式: "Bearer {tenant_access_token}"
* @param requestBody 请求体,包含grant_type和code
* @return 飞书响应
*/
@Post(url = "https://open.feishu.cn/open-apis/authen/v1/oidc/access_token",
contentType = "application/json")
ForestResponse<String> getUserAccessToken(
@Header("Authorization") String authorization,
@Body Map<String, String> requestBody);
/**
* 获取飞书用户信息
* 文档: https://open.feishu.cn/document/server-docs/authentication-management/login-state-management/get
*
* @param authorization Authorization header,格式: "Bearer {user_access_token}"
* @return 飞书响应
*/
@Get(url = "https://open.feishu.cn/open-apis/authen/v1/user_info")
ForestResponse<String> getUserInfo(@Header("Authorization") String authorization);
}
请求用友有关的:
java
public interface YonBipSsoHttpClient {
/**
* 获取用友ERP第三方登录临时code
* 调用用友接口获取一次性登录凭证
*
* @param accessToken 用友ERP的access_token
* @param requestBody 请求体,包含thirdUcId和userId
* @return 用友ERP响应
*/
@Post(url = "${yonbipBaseUrl}" + "/iuap-api-gateway/yonbip/yht/getThirdLoginCode",
contentType = "application/json")
ForestResponse<String> getThirdLoginCode(
@Query(YonBipConst.ACCESS_TOKEN_KEY) String accessToken,
@Body Map<String, String> requestBody);
}
调用飞书相关接口的逻辑:
java
@Slf4j
@Service
public class FeiShuSsoAuthService {
@Resource
private YonBipSsoConfig yonBipSsoConfig;
@Resource
private FeiShuSsoHttpClient feiShuSsoHttpClient;
/**
* 获取飞书tenant_access_token
* 文档: https://open.feishu.cn/document/server-docs/authentication-management/access-token/tenant_access_token_internal
*
* @return tenant_access_token
*/
public String getTenantAccessToken() {
try {
log.info("[飞书SSO认证]开始获取tenant_access_token");
Map<String, String> requestBody = new HashMap<>();
requestBody.put("app_id", yonBipSsoConfig.getFeishuAppId());
requestBody.put("app_secret", yonBipSsoConfig.getFeishuAppSecret());
ForestResponse<String> response = feiShuSsoHttpClient.getTenantAccessToken(requestBody);
if (!response.isSuccess() || StringUtils.isBlank(response.getResult())) {
throw new BusinessException("[飞书SSO认证]获取tenant_access_token失败,HTTP状态码: " + response.getStatusCode());
}
JSONObject jsonResult = JSON.parseObject(response.getResult());
if (jsonResult == null || jsonResult.getInteger("code") != 0) {
String errMsg = jsonResult != null ? jsonResult.getString("msg") : "响应为空";
throw new BusinessException("[飞书SSO认证]获取tenant_access_token失败: " + errMsg);
}
String tenantAccessToken = jsonResult.getString("tenant_access_token");
if (StringUtils.isBlank(tenantAccessToken)) {
throw new BusinessException("[飞书SSO认证]tenant_access_token为空");
}
log.info("[飞书SSO认证]成功获取tenant_access_token");
return tenantAccessToken;
} catch (BusinessException ex) {
throw ex;
} catch (Exception ex) {
log.error("[飞书SSO认证]获取tenant_access_token异常", ex);
throw new BusinessException("[飞书SSO认证]获取tenant_access_token异常: " + ex.getMessage());
}
}
/**
* 使用飞书授权码获取user_access_token
* 文档: https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/authen-v1/oidc-access_token/create
*
* @param code 飞书授权码
* @param tenantAccessToken tenant_access_token
* @return user_access_token
*/
public String getUserAccessToken(String code, String tenantAccessToken) {
try {
log.info("[飞书SSO认证]开始获取user_access_token, code={}", code);
Map<String, String> requestBody = new HashMap<>();
requestBody.put("grant_type", "authorization_code");
requestBody.put("code", code);
String authorization = "Bearer " + tenantAccessToken;
ForestResponse<String> response = feiShuSsoHttpClient.getUserAccessToken(authorization, requestBody);
String result = response.getResult();
if (!response.isSuccess() || StringUtils.isBlank(response.getResult())) {
throw new BusinessException("[飞书SSO认证]获取user_access_token失败,HTTP状态码: " + response.getStatusCode());
}
JSONObject jsonResult = JSON.parseObject(response.getResult());
if (jsonResult == null || jsonResult.getInteger("code") != 0) {
String errMsg = jsonResult != null ? jsonResult.getString("msg") : "响应为空";
throw new BusinessException("[飞书SSO认证]获取user_access_token失败: " + errMsg);
}
JSONObject dataObj = jsonResult.getJSONObject("data");
if (dataObj == null) {
throw new BusinessException("[飞书SSO认证]user_access_token数据为空");
}
String userAccessToken = dataObj.getString("access_token");
if (StringUtils.isBlank(userAccessToken)) {
throw new BusinessException("[飞书SSO认证]user_access_token为空");
}
log.info("[飞书SSO认证]成功获取user_access_token");
return userAccessToken;
} catch (BusinessException ex) {
throw ex;
} catch (Exception ex) {
log.error("[飞书SSO认证]获取user_access_token异常", ex);
throw new BusinessException("[飞书SSO认证]获取user_access_token异常: " + ex.getMessage());
}
}
/**
* 根据user_access_token获取飞书用户ID
* 文档: https://open.feishu.cn/document/server-docs/authentication-management/login-state-management/get
*
* @param userAccessToken user_access_token
* @return 飞书用户ID
*/
public String getFeishuUserId(String userAccessToken) {
try {
log.info("[飞书SSO认证]开始获取飞书用户信息");
String authorization = "Bearer " + userAccessToken;
ForestResponse<String> response = feiShuSsoHttpClient.getUserInfo(authorization);
String result = response.getResult();
if (!response.isSuccess() || StringUtils.isBlank(response.getResult())) {
throw new BusinessException("[飞书SSO认证]获取用户信息失败,HTTP状态码: " + response.getStatusCode());
}
JSONObject jsonResult = JSON.parseObject(response.getResult());
if (jsonResult == null || jsonResult.getInteger("code") != 0) {
String errMsg = jsonResult != null ? jsonResult.getString("msg") : "响应为空";
throw new BusinessException("[飞书SSO认证]获取用户信息失败: " + errMsg);
}
JSONObject dataObj = jsonResult.getJSONObject("data");
if (dataObj == null) {
throw new BusinessException("[飞书SSO认证]用户信息数据为空");
}
String userId = dataObj.getString("user_id");
if (StringUtils.isBlank(userId)) {
throw new BusinessException("[飞书SSO认证]用户ID为空");
}
log.info("[飞书SSO认证]成功获取飞书用户ID: {}", userId);
return userId;
} catch (BusinessException ex) {
throw ex;
} catch (Exception ex) {
log.error("[飞书SSO认证]获取飞书用户信息异常", ex);
throw new BusinessException("[飞书SSO认证]获取飞书用户信息异常: " + ex.getMessage());
}
}
/**
* 完整流程:通过飞书授权码获取飞书用户ID
*
* @param code 飞书授权码
* @return 飞书用户ID
*/
public String getFeishuUserIdByCode(String code) {
// 1. 获取tenant_access_token
String tenantAccessToken = getTenantAccessToken();
// 2. 使用授权码获取user_access_token
String userAccessToken = getUserAccessToken(code, tenantAccessToken);
// 3. 使用user_access_token获取用户ID
return getFeishuUserId(userAccessToken);
}
}
处理 SSO 的Service:
java
@Slf4j
@Service
public class YonBipSsoService {
@Resource
private YonBipConfig yonBipConfig;
@Resource
private FeiShuSsoAuthService feiShuSsoAuthService;
@Resource
private YonBipTokenService yonBipTokenService;
@Resource
private YonBipSsoHttpClient yonBipSsoHttpClient;
/**
* 处理飞书授权回调,并单点登录到用友ERP
* <p>
* 流程说明:
* 1. 从飞书获取授权码code
* 2. 使用code获取飞书用户信息(user_id)
* 3. 获取用友ERP的access_token
* 4. 调用用友接口获取第三方登录临时code
* 5. 构建用友ERP登录URL并重定向
*
* @param code 飞书授权码
* @param state Base64编码的目标页面URL(可选)
*/
public void handleFeiShuSsoToYonBip(String code, String state) {
log.info("[用友SSO]==================== 飞书授权后单点登录到用友ERP流程开始 ====================");
log.info("[用友SSO]接收参数 - code: {}, state: {}", code, state);
// 获取当前请求和响应对象
HttpServletRequest request = ContextHolderUtils.getRequest();
HttpServletResponse response = ContextHolderUtils.getResponse();
// 参数校验
if (StringUtils.isBlank(code)) {
log.error("[用友SSO]飞书授权码code为空");
writeErrorResponse(response, "飞书授权码code为空");
return;
}
// 创建Session并保存state
HttpSession session = request.getSession(true);
if (StringUtils.isNotBlank(state)) {
session.setAttribute(YonBipSsoConst.SESSION_STATE_KEY, state);
}
// 步骤1: 获取飞书用户ID
String feishuUserId;
try {
feishuUserId = feiShuSsoAuthService.getFeishuUserIdByCode(code);
log.info("[用友SSO]步骤1完成 - 成功获取飞书用户ID: {}", feishuUserId);
} catch (Exception e) {
log.error("[用友SSO]获取飞书用户ID失败", e);
writeErrorResponse(response, "获取飞书用户信息失败: " + e.getMessage());
return;
}
if (StringUtils.isBlank(feishuUserId)) {
log.error("[用友SSO]飞书用户ID为空");
writeErrorResponse(response, "获取飞书用户ID为空");
return;
}
// 保存用户ID到Session
session.setAttribute(YonBipSsoConst.SESSION_FEISHU_USER_ID_KEY, feishuUserId);
// 步骤2: 获取用友ERP的access_token
String yonBipAccessToken;
try {
yonBipAccessToken = yonBipTokenService.getAccessToken();
log.info("[用友SSO]步骤2完成 - 成功获取用友ERP access_token");
} catch (Exception e) {
log.error("[用友SSO]获取用友ERP access_token失败", e);
writeErrorResponse(response, "获取用友ERP access_token失败: " + e.getMessage());
return;
}
if (StringUtils.isBlank(yonBipAccessToken)) {
log.error("[用友SSO]用友ERP access_token为空");
writeErrorResponse(response, "用友ERP access_token为空");
return;
}
// 步骤3: 调用用友接口获取第三方登录临时code
String yonBipLoginCode;
try {
yonBipLoginCode = getYonBipThirdLoginCode(yonBipAccessToken, feishuUserId);
log.info("[用友SSO]步骤3完成 - 成功获取用友ERP一次性登录code");
} catch (Exception e) {
log.error("[用友SSO]获取用友ERP一次性登录code失败", e);
writeErrorResponse(response, "获取用友ERP登录code失败: " + e.getMessage());
return;
}
if (StringUtils.isBlank(yonBipLoginCode)) {
log.error("[用友SSO]用友ERP登录code为空");
writeErrorResponse(response, "用友ERP登录code为空");
return;
}
// 步骤4: 构建用友ERP登录URL
String yonBipLoginUrl;
try {
yonBipLoginUrl = buildYonBipLoginUrl(yonBipLoginCode, state);
log.info("[用友SSO]步骤4完成 - 成功构建用友ERP登录URL: {}", yonBipLoginUrl);
} catch (Exception e) {
log.error("[用友SSO]构建用友ERP登录URL失败", e);
writeErrorResponse(response, "构建用友ERP登录URL失败: " + e.getMessage());
return;
}
// 步骤5: 重定向到用友ERP
try {
log.info("[用友SSO]步骤5 - 开始重定向到用友ERP");
response.sendRedirect(yonBipLoginUrl);
log.info("[用友SSO]==================== 飞书授权后单点登录到用友ERP流程结束 ====================");
} catch (IOException e) {
log.error("[用友SSO]重定向到用友ERP失败", e);
writeErrorResponse(response, "重定向到用友ERP失败: " + e.getMessage());
}
}
/**
* 调用用友接口获取第三方登录临时code
*
* @param accessToken 用友ERP的access_token
* @param userId 飞书用户ID
* @return 用友ERP一次性登录code
*/
private String getYonBipThirdLoginCode(String accessToken, String userId) {
try {
// 构建请求体
Map<String, String> requestBody = new HashMap<>();
requestBody.put("thirdUcId", yonBipConfig.getIntegrationAuthCode());
requestBody.put("userId", userId);
log.info("[用友SSO]调用用友接口获取登录code, 请求体: {}", JSON.toJSONString(requestBody));
ForestResponse<String> response = yonBipSsoHttpClient.getThirdLoginCode(accessToken, requestBody);
String result = response.getResult();
if (!response.isSuccess() || StringUtils.isBlank(response.getResult())) {
throw new BusinessException("[用友SSO]调用用友获取登录code接口失败,HTTP状态码: " + response.getStatusCode());
}
log.info("[用友SSO]用友接口返回: {}", response.getResult());
JSONObject jsonResult = JSON.parseObject(response.getResult());
if (jsonResult == null) {
throw new BusinessException("[用友SSO]用友接口响应解析失败");
}
Integer code = jsonResult.getInteger("code");
if (!YonBipSsoConst.SUCCESS_CODE.equals(code)) {
String errMsg = jsonResult.getString("message");
throw new BusinessException("[用友SSO]用友接口返回失败,code=" + code + ", message=" + errMsg);
}
JSONObject dataObj = jsonResult.getJSONObject("data");
if (dataObj == null) {
throw new BusinessException("[用友SSO]用友接口返回数据为空");
}
String loginCode = dataObj.getString("code");
if (StringUtils.isBlank(loginCode)) {
throw new BusinessException("[用友SSO]用友登录code为空");
}
return loginCode;
} catch (BusinessException ex) {
throw ex;
} catch (Exception ex) {
log.error("[用友SSO]调用用友获取登录code接口异常", ex);
throw new BusinessException("[用友SSO]调用用友获取登录code接口异常: " + ex.getMessage());
}
}
/**
* 构建用友ERP OAuth2登录URL
*
* @param yonBipLoginCode 用友一次性登录code
* @param state Base64编码的目标URL(可选)
* @return 完整的用友ERP登录URL
*/
private String buildYonBipLoginUrl(String yonBipLoginCode, String state) throws UnsupportedEncodingException {
// 确定最终跳转的URL
String targetUrl = yonBipConfig.getBaseUrl();
log.info("[用友SSO]使用默认目标URL: {}", targetUrl);
// 构建service参数(用友要求的回调地址)
String security = URLEncoder.encode(targetUrl, StandardCharsets.UTF_8);
String serviceUrl = yonBipConfig.getBaseUrl() + "/login?service=" + security;
String serviceParam = URLEncoder.encode(serviceUrl, StandardCharsets.UTF_8);
// 构建完整的用友ERP OAuth2登录URL
String loginUrl = yonBipConfig.getBaseUrl() + "/cas/thirdOauth2CodeLogin"
+ "?thirdUCId=" + yonBipConfig.getIntegrationAuthCode()
+ "&code=" + yonBipLoginCode
+ "&service=" + serviceParam;
return loginUrl;
}
/**
* 向响应中写入错误信息
*
* @param response HttpServletResponse
* @param errorMsg 错误信息
*/
private void writeErrorResponse(HttpServletResponse response, String errorMsg) {
try {
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write("<html><body><h3>登录失败</h3><p>" + errorMsg + "</p></body></html>");
response.getWriter().flush();
} catch (IOException e) {
log.error("[用友SSO]写入错误响应失败", e);
}
}
}
飞书补充配置
1、开启网页应用
配置网页地址,最终用户在飞书工作台点击应用会调用到上述提到的 Controller 接口
网页链接配置示例:
text
https://open.feishu.cn/open-apis/authen/v1/authorize?app_id=cli_a9b3e69ab4395cd9&response_type=code&state=1&redirect_uri=http%3A%2F%2F142.221.188.217%3A11024%2Fapi%2Fyonbip%2Fsso%2Fpc-login&scope=contact:contact.base:readonly%20contact:user.employee:readonly%20im:message
注意:上述redirect_uri对应的就是你的 Controller 接口地址,我只是随便写了一个 IP 地址做示例
2、赋予权限
无所吊谓了,我给了下面这么多权限,其它地方也要用到这个应用
json
{
"scopes": {
"tenant": [
"aily:message:read",
"aily:message:write",
"app_engine:approval:read",
"app_engine:approval:write",
"application:application.app_message_stats.overview:readonly",
"application:application.bot.operator_name:readonly",
"application:bot.menu:readonly",
"application:bot.menu:write",
"approval:approval",
"approval:approval.list:readonly",
"approval:approval:readonly",
"approval:definition",
"approval:external_approval",
"approval:external_instance",
"approval:instance.comment",
"cardkit:template:read",
"contact:contact.base:readonly",
"contact:department.base:readonly",
"contact:department.organize:readonly",
"contact:functional_role:readonly",
"contact:group:readonly",
"contact:job_family:readonly",
"contact:job_level:readonly",
"contact:job_title:readonly",
"contact:unit:readonly",
"contact:user.base:readonly",
"contact:user.department:readonly",
"contact:user.email:readonly",
"contact:user.employee:readonly",
"contact:user.employee_id:readonly",
"contact:user.employee_number:read",
"contact:user.gender:readonly",
"contact:user.id:readonly",
"contact:user.job_family:readonly",
"contact:user.job_level:readonly",
"contact:user.phone:readonly",
"corehr:contract.period:read",
"corehr:contract.period:write",
"corehr:employee.all_bp:read",
"corehr:employee.bp:read",
"corehr:employee:read",
"corehr:employee:write",
"corehr:employment.archive_cpst_plan:read",
"corehr:employment.assignment:read",
"corehr:employment.assignment_pay_group:read",
"corehr:employment.contract_type:read",
"corehr:employment.custom_field:read",
"corehr:employment.custom_org_field:read",
"corehr:employment.custom_org_field:write",
"corehr:employment.job:read",
"corehr:employment.job_grade:read",
"corehr:employment.job_grade:write",
"corehr:employment.job_level:read",
"corehr:employment.job_level:write",
"corehr:employment.offboarding_reason:read",
"corehr:employment.pay_group:read",
"corehr:employment.position:read",
"corehr:employment.position:write",
"corehr:job.job_level:read",
"corehr:job_data.compensation_type:read",
"corehr:job_data.service_company:read",
"corehr:job_data.work_shift:read",
"corehr:person.address:read",
"corehr:person.address:write",
"corehr:person.bank_account:read",
"corehr:person.bank_account:write",
"corehr:person.born_country_region:read",
"corehr:person.born_country_region:write",
"corehr:person.custom_field:read",
"corehr:person.custom_field:write",
"corehr:person.date_entered_workforce:read",
"corehr:person.date_entered_workforce:write",
"corehr:person.date_of_birth:read",
"corehr:person.date_of_birth:write",
"corehr:person.dependent:read",
"corehr:person.dependent:write",
"corehr:person.education:read",
"corehr:person.education:write",
"corehr:person.email:read",
"corehr:person.email:write",
"corehr:person.emergency_contact:read",
"corehr:person.emergency_contact:write",
"corehr:person.entry_leave_time:read",
"corehr:person.gender:read",
"corehr:person.gender:write",
"corehr:person.hukou:read",
"corehr:person.hukou:write",
"corehr:person.is_disabled:read",
"corehr:person.is_disabled:write",
"corehr:person.is_old_alone:read",
"corehr:person.is_old_alone:write",
"corehr:person.legal_name:read",
"corehr:person.legal_name:write",
"corehr:person.marital_status:read",
"corehr:person.marital_status:write",
"corehr:person.martyr_family:read",
"corehr:person.martyr_family:write",
"corehr:person.national_id:read",
"corehr:person.national_id:write",
"corehr:person.nationality:read",
"corehr:person.nationality:write",
"corehr:person.native_region:read",
"corehr:person.native_region:write",
"corehr:person.personal_profile:read",
"corehr:person.personal_profile:write",
"corehr:person.phone.search:read",
"corehr:person.phone:read",
"corehr:person.phone:write",
"corehr:person.political_affiliation:read",
"corehr:person.political_affiliation:write",
"corehr:person.race:read",
"corehr:person.religion:read",
"corehr:person.religion:write",
"corehr:person.resident_tax:read",
"corehr:person.resident_tax:write",
"corehr:person.resident_tax_custom_field:read",
"corehr:person.resident_tax_custom_field:write",
"corehr:person.work_experience:read",
"corehr:person.work_experience:write",
"directory:employee:search",
"ehr:employee:readonly",
"im:app_feed_card:write",
"im:chat.access_event.bot_p2p_chat:read",
"im:chat.members:bot_access",
"im:message",
"im:message.group_at_msg:readonly",
"im:message.group_msg",
"im:message.p2p_msg:readonly",
"im:message.pins:read",
"im:message.pins:write_only",
"im:message.reactions:read",
"im:message.reactions:write_only",
"im:message.urgent",
"im:message.urgent.status:write",
"im:message.urgent:phone",
"im:message.urgent:sms",
"im:message:readonly",
"im:message:recall",
"im:message:send_as_bot",
"im:message:send_multi_depts",
"im:message:send_multi_users",
"im:message:send_sys_msg",
"im:message:update"
],
"user": [
"aily:message:read",
"aily:message:write",
"contact:contact.base:readonly",
"contact:department.base:readonly",
"contact:user.employee:readonly",
"contact:user.employee_id:readonly",
"contact:user.id:readonly",
"directory:employee.base.base:read",
"directory:employee.base.email:read",
"directory:employee.base.is_resigned:read",
"directory:employee.base.mobile:read",
"directory:employee.base.name.name:read",
"directory:employee:search",
"im:chat.access_event.bot_p2p_chat:read",
"im:message",
"im:message.pins:read",
"im:message.pins:write_only",
"im:message.reactions:read",
"im:message.reactions:write_only",
"im:message.urgent.status:write",
"im:message:readonly",
"im:message:recall",
"search:message"
]
}
}
大概就这么个流程,可能中途描述的东西有所缺失,毕竟做了好几个月了,现在抽时间记录一下,方便有需要的伙伴参考。因为我们当时想找用友要代码,他们说是要收钱的,就自己写了一个。有遗漏的不完善的请大家自行琢磨一下,代码肯定是可以跑的