376 lines
14 KiB
C#
376 lines
14 KiB
C#
using SqlSugar;
|
||
using System.Collections.Generic;
|
||
using System.Security.Cryptography;
|
||
using System.Text;
|
||
using System.Text.Json;
|
||
using System.Web;
|
||
using VOL.Core.Services;
|
||
using VOL.YSErp.Models;
|
||
using VOL.YSErp.Models.Biz;
|
||
using static Dm.net.buffer.ByteArrayBuffer;
|
||
|
||
namespace VOL.YSErp.Services.Biz
|
||
{
|
||
public class YSERPService
|
||
{
|
||
private readonly string _apiUrl;
|
||
private readonly string _appKey;
|
||
private readonly string _appSecret;
|
||
private string _accessToken;
|
||
private DateTime? _tokenExpiry;
|
||
private readonly SemaphoreSlim _tokenLock = new SemaphoreSlim(1, 1);
|
||
private SystemToken _token;
|
||
private readonly HttpClient _httpClient;
|
||
|
||
// YS API URLs
|
||
private readonly string _tokenUrl;
|
||
private readonly string _employeeListUrl;
|
||
private readonly string _deptListUrl;
|
||
private readonly string _empInfoUrl;
|
||
private static readonly string[] resposeSuccessCodes = ["200", "00000"];
|
||
|
||
public YSERPService(SystemToken token, YSConfig config)
|
||
{
|
||
_token = token;
|
||
_apiUrl = config.ApiUrl.TrimEnd('/');
|
||
_appKey = config.Appkey;
|
||
_appSecret = config.AppSecret;
|
||
_httpClient = new HttpClient();
|
||
|
||
_tokenUrl = $"{_apiUrl}/iuap-api-auth/open-auth/selfAppAuth/getAccessToken";
|
||
_employeeListUrl = $"{_apiUrl}/iuap-api-gateway/yonbip/hrcloud/staff/listdetailmdd";
|
||
//_updateEmployeeUrl = $"{_apiUrl}/iuap-api-gateway/yonbip/hrcloud/batchInsertOrUpdateDetailNew";
|
||
_deptListUrl = $"{_apiUrl}/iuap-api-gateway/yonbip/digitalModel/basedoc/dept/list";
|
||
_empInfoUrl = $"{_apiUrl}/iuap-api-gateway/yonbip/hrcloud/HRCloud/getStaffDetail";
|
||
}
|
||
|
||
|
||
private string GenerateSignature(Dictionary<string, string> parameters)
|
||
{
|
||
try
|
||
{
|
||
var filteredParams = new Dictionary<string, string>();
|
||
foreach (var param in parameters)
|
||
{
|
||
if (param.Key != "signature")
|
||
{
|
||
filteredParams[param.Key] = param.Value;
|
||
}
|
||
}
|
||
|
||
var sortedParams = new SortedDictionary<string, string>(filteredParams);
|
||
var paramStr = string.Join("", sortedParams.Select(p => p.Key + p.Value));
|
||
|
||
using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(_appSecret)))
|
||
{
|
||
var signatureBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(paramStr));
|
||
return Convert.ToBase64String(signatureBytes);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
//_logger.LogError(ex, "生成签名时出错");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
private async Task<(string Token, DateTime Expiry)?> GetAccessTokenAsync()
|
||
{
|
||
try
|
||
{
|
||
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString();
|
||
var parameters = new Dictionary<string, string>
|
||
{
|
||
["appKey"] = _appKey,
|
||
["timestamp"] = timestamp
|
||
};
|
||
|
||
var signature = GenerateSignature(parameters);
|
||
if (string.IsNullOrEmpty(signature))
|
||
{
|
||
return null;
|
||
}
|
||
|
||
parameters["signature"] = signature;
|
||
|
||
var queryString = string.Join("&", parameters.Select(p =>
|
||
$"{p.Key}={HttpUtility.UrlEncode(p.Value)}"));
|
||
var fullUrl = $"{_tokenUrl}?{queryString}";
|
||
|
||
var response = await _httpClient.GetAsync(fullUrl);
|
||
if (!response.IsSuccessStatusCode)
|
||
{
|
||
//_logger.LogError($"获取access_token失败: HTTP {response.StatusCode}");
|
||
return null;
|
||
}
|
||
|
||
var content = await response.Content.ReadAsStringAsync();
|
||
var data = JsonSerializer.Deserialize<JsonElement>(content);
|
||
|
||
if (data.GetProperty("code").GetString() != "00000")
|
||
{
|
||
var errorMsg = data.GetProperty("message").GetString();
|
||
//_logger.LogError($"获取access_token失败: {errorMsg}");
|
||
return null;
|
||
}
|
||
|
||
var tokenData = data.GetProperty("data");
|
||
var accessToken = tokenData.GetProperty("access_token").GetString();
|
||
var expireSeconds = tokenData.GetProperty("expire").GetInt32();
|
||
|
||
return (accessToken, DateTime.Now.AddSeconds(expireSeconds - 300));
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
//_logger.LogError(ex, "获取access_token时出错");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
private async Task<string> GetValidTokenAsync()
|
||
{
|
||
await _tokenLock.WaitAsync();
|
||
try
|
||
{
|
||
if (string.IsNullOrEmpty(_token.Token) || !_token.IsTokenExpiry())
|
||
{
|
||
//_logger.LogInformation("YS access_token已过期,重新获取...");
|
||
var result = await GetAccessTokenAsync();
|
||
if (result.HasValue)
|
||
{
|
||
(_accessToken, _tokenExpiry) = result.Value;
|
||
_token.Token = _accessToken;
|
||
_token.TokenEndTime = _tokenExpiry ?? DateTime.Now;
|
||
}
|
||
else
|
||
{
|
||
//_logger.LogError("无法获取有效的YS access_token");
|
||
return null;
|
||
}
|
||
}
|
||
return _token.Token;
|
||
}
|
||
finally
|
||
{
|
||
_tokenLock.Release();
|
||
}
|
||
}
|
||
|
||
public async Task<List<YSERPEmployee>> GetAllEmployeesAsync()
|
||
{
|
||
var token = await GetValidTokenAsync();
|
||
if (string.IsNullOrEmpty(token))
|
||
{
|
||
return [];
|
||
}
|
||
|
||
var allEmployees = new List<YSERPEmployee>();
|
||
var pageIndex = 1;
|
||
const int pageSize = 50;
|
||
|
||
//Logger.Info($"员工信息API: {_employeeListUrl}");
|
||
|
||
while (true)
|
||
{
|
||
try
|
||
{
|
||
//Logger.Info($"获取YS员工数据,第 {pageIndex} 页...");
|
||
|
||
var payload = new
|
||
{
|
||
pageIndex,
|
||
pageSize,
|
||
};
|
||
|
||
var request = new HttpRequestMessage(HttpMethod.Post, $"{_employeeListUrl}?access_token={token}")
|
||
{
|
||
Content = new StringContent(
|
||
JsonSerializer.Serialize(payload),
|
||
Encoding.UTF8,
|
||
"application/json")
|
||
};
|
||
|
||
var response = await _httpClient.SendAsync(request);
|
||
|
||
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||
{
|
||
Logger.Error("认证失败: 无效的API令牌或权限不足");
|
||
_accessToken = null;
|
||
//return [];
|
||
throw new Exception("认证失败: 无效的YS ERP API令牌或权限不足");
|
||
}
|
||
|
||
response.EnsureSuccessStatusCode();
|
||
var content = await response.Content.ReadAsStringAsync();
|
||
var data = JsonSerializer.Deserialize<YSERPPagedResponse<YSERPEmployee>>(content);
|
||
if (data == null || !resposeSuccessCodes.Contains(data.code))
|
||
{
|
||
var errorMsg = data?.message;
|
||
Logger.Error($"API业务错误: {errorMsg}");
|
||
throw new Exception($"API业务错误: 【{errorMsg}】");
|
||
}
|
||
|
||
|
||
var recordList = data.data.recordList;
|
||
if (recordList?.Count == 0)
|
||
{
|
||
//Logger.Info("没有更多员工记录");
|
||
break;
|
||
}
|
||
|
||
var toAdd = recordList.Where(r => !allEmployees.Any(e => e.id == r.id)).ToList();
|
||
allEmployees.AddRange(toAdd);
|
||
|
||
var haveNext = data.data.haveNextPage;
|
||
var pageCount = data.data.pageCount;
|
||
|
||
if (!haveNext || pageIndex >= pageCount)
|
||
{
|
||
break;
|
||
}
|
||
//Logger.Info($"第 {pageIndex}/{pageCount} 页,获取 {recordList.Count} 条员工记录");
|
||
pageIndex++;
|
||
await Task.Delay(500); // 避免请求过快
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Logger.Error($"获取员工数据失败,{ex}");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
//_logger.LogInformation($"共获取 {allEmployees.Count} 条YS员工记录");
|
||
return allEmployees;
|
||
}
|
||
|
||
public async Task<List<YSERPDepartment>> GetAllDepartmentsAsync()
|
||
{
|
||
var token = await GetValidTokenAsync();
|
||
if (string.IsNullOrEmpty(token))
|
||
{
|
||
return [];
|
||
}
|
||
var allDepts = new List<YSERPDepartment>();
|
||
|
||
|
||
try
|
||
{
|
||
var payload = new
|
||
{
|
||
data = new
|
||
{
|
||
code = new List<string>(),
|
||
pubts = new List<string>() { "1900-01-01 00:00:00" }, // 必须带一个参数,不然接口返回的数据格式有问题 2025年9月26日
|
||
}
|
||
};
|
||
|
||
var request = new HttpRequestMessage(HttpMethod.Post, $"{_deptListUrl}?access_token={token}")
|
||
{
|
||
Content = new StringContent(
|
||
JsonSerializer.Serialize(payload),
|
||
Encoding.UTF8,
|
||
"application/json")
|
||
};
|
||
|
||
var response = await _httpClient.SendAsync(request);
|
||
|
||
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||
{
|
||
Logger.Error("认证失败: 无效的API令牌或权限不足");
|
||
_accessToken = null;
|
||
return [];
|
||
}
|
||
|
||
response.EnsureSuccessStatusCode();
|
||
var content = await response.Content.ReadAsStringAsync();
|
||
var data = JsonSerializer.Deserialize<YSERPListResponse<YSERPDepartment>>(content);
|
||
if (data == null || !resposeSuccessCodes.Contains(data.code))
|
||
{
|
||
var errorMsg = data?.message;
|
||
Logger.Error($"API业务错误: {errorMsg}");
|
||
throw new Exception($"API业务错误:【{errorMsg}】");
|
||
}
|
||
|
||
var recordList = data.data;
|
||
|
||
if(recordList?.Count == 0)
|
||
{
|
||
//Logger.Info("没有更多部门记录");
|
||
return allDepts;
|
||
}
|
||
else
|
||
{
|
||
allDepts.AddRange(recordList ?? []);
|
||
}
|
||
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Logger.Error($"获取部门数据失败,{ex}");
|
||
throw;
|
||
}
|
||
//_logger.LogInformation($"共获取 {allEmployees.Count} 条YS员工记录");
|
||
return allDepts;
|
||
}
|
||
|
||
public async Task<YSERPEmployeeInfo> GetEmpInfoById(string empId)
|
||
{
|
||
var token = await GetValidTokenAsync();
|
||
if (string.IsNullOrEmpty(token))
|
||
{
|
||
return new YSERPEmployeeInfo();
|
||
}
|
||
|
||
try
|
||
{
|
||
var payload = new
|
||
{
|
||
id = empId
|
||
};
|
||
|
||
var request = new HttpRequestMessage(HttpMethod.Post, $"{_empInfoUrl}?access_token={token}")
|
||
{
|
||
Content = new StringContent(
|
||
JsonSerializer.Serialize(payload),
|
||
Encoding.UTF8,
|
||
"application/json")
|
||
};
|
||
|
||
var response = await _httpClient.SendAsync(request);
|
||
|
||
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||
{
|
||
Logger.Error("认证失败: 无效的API令牌或权限不足");
|
||
_accessToken = null;
|
||
//return new YSERPEmployeeInfo();
|
||
throw new Exception("认证失败: 无效的YS ERP API令牌或权限不足");
|
||
}
|
||
|
||
response.EnsureSuccessStatusCode();
|
||
var content = await response.Content.ReadAsStringAsync();
|
||
var data = JsonSerializer.Deserialize<YSERPResponse<YSERPEmployeeInfo>>(content);
|
||
if (data==null || !resposeSuccessCodes.Contains(data.code))
|
||
{
|
||
var errorMsg = data?.message;
|
||
Logger.Error($"API业务错误: {errorMsg}");
|
||
throw new Exception($"API业务错误: 【{errorMsg}】");
|
||
}
|
||
|
||
return data.data;
|
||
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Logger.Error($"获取部门数据失败,{ex}");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
public void Dispose()
|
||
{
|
||
_tokenLock?.Dispose();
|
||
_httpClient?.Dispose();
|
||
}
|
||
}
|
||
}
|