407 lines
15 KiB
C#
407 lines
15 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 _updateEmployeeUrl;
|
|||
|
private readonly string _deptListUrl;
|
|||
|
|
|||
|
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";
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
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 new List<YSERPEmployee>();
|
|||
|
}
|
|||
|
|
|||
|
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 [];
|
|||
|
}
|
|||
|
|
|||
|
response.EnsureSuccessStatusCode();
|
|||
|
var content = await response.Content.ReadAsStringAsync();
|
|||
|
var data = JsonSerializer.Deserialize<YSERPResponse<YSERPEmployee>>(content);
|
|||
|
if (!new[] { "200", "00000" }.Contains(data.code))
|
|||
|
{
|
|||
|
var errorMsg = data.message;
|
|||
|
Logger.Error($"API业务错误: {errorMsg}");
|
|||
|
throw new Exception(errorMsg);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
var recordList = data.data.recordList;
|
|||
|
if (recordList?.Count == 0)
|
|||
|
{
|
|||
|
Logger.Info("没有更多员工记录");
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
allEmployees.AddRange(data.data.recordList);
|
|||
|
|
|||
|
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<bool> UpdateEmployeesAsync(List<Dictionary<string, string>> employeeUpdates)
|
|||
|
{
|
|||
|
var token = await GetValidTokenAsync();
|
|||
|
if (string.IsNullOrEmpty(token))
|
|||
|
{
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
//_logger.LogInformation($"YS更新内容: {JsonSerializer.Serialize(employeeUpdates)}");
|
|||
|
|
|||
|
try
|
|||
|
{
|
|||
|
var request = new HttpRequestMessage(HttpMethod.Post, $"{_updateEmployeeUrl}?access_token={token}")
|
|||
|
{
|
|||
|
Content = new StringContent(
|
|||
|
JsonSerializer.Serialize(new { data = employeeUpdates }),
|
|||
|
Encoding.UTF8,
|
|||
|
"application/json")
|
|||
|
};
|
|||
|
|
|||
|
var response = await _httpClient.SendAsync(request);
|
|||
|
//_logger.LogInformation($"YS更新响应状态: {response.StatusCode}");
|
|||
|
|
|||
|
var content = await response.Content.ReadAsStringAsync();
|
|||
|
//_logger.LogInformation($"YS更新响应内容: {content}");
|
|||
|
|
|||
|
response.EnsureSuccessStatusCode();
|
|||
|
var data = JsonSerializer.Deserialize<JsonElement>(content);
|
|||
|
|
|||
|
if (data.GetProperty("code").GetString() == "00000")
|
|||
|
{
|
|||
|
//_logger.LogInformation($"成功更新 {employeeUpdates.Count} 名员工信息");
|
|||
|
|
|||
|
if (data.TryGetProperty("data", out var resultData))
|
|||
|
{
|
|||
|
//_logger.LogInformation($"处理总数: {GetPropertyValueOrDefault(resultData, "count", 0)}");
|
|||
|
//_logger.LogInformation($"成功数: {GetPropertyValueOrDefault(resultData, "successCount", 0)}");
|
|||
|
//_logger.LogInformation($"失败数: {GetPropertyValueOrDefault(resultData, "failCount", 0)}");
|
|||
|
|
|||
|
if (resultData.TryGetProperty("messages", out var messages))
|
|||
|
{
|
|||
|
foreach (var msg in messages.EnumerateArray())
|
|||
|
{
|
|||
|
//_logger.LogWarning($"失败信息: {msg}");
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
var errorMsg = data.GetProperty("message").GetString();
|
|||
|
Logger.Error($"更新失败: {errorMsg}");
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
Logger.Error( $"更新员工信息失败,{ex}");
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
public async Task<List<YSERPDepartment>> GetAllDepartmentsAsync()
|
|||
|
{
|
|||
|
var token = await GetValidTokenAsync();
|
|||
|
if (string.IsNullOrEmpty(token))
|
|||
|
{
|
|||
|
return [];
|
|||
|
}
|
|||
|
var allDepts = new List<YSERPDepartment>();
|
|||
|
|
|||
|
//Logger.Info($"员工信息API: {_employeeListUrl}");
|
|||
|
|
|||
|
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 (!new[] { "200", "00000" }.Contains(data.code))
|
|||
|
{
|
|||
|
var errorMsg = data.message;
|
|||
|
Logger.Error($"API业务错误: {errorMsg}");
|
|||
|
throw new Exception(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;
|
|||
|
}
|
|||
|
|
|||
|
private string ConvertGender(string gender)
|
|||
|
{
|
|||
|
return gender switch
|
|||
|
{
|
|||
|
"男" => "1",
|
|||
|
"女" => "2",
|
|||
|
_ => gender
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
private static string GetPropertyValueOrEmpty(JsonElement element, string propertyName)
|
|||
|
{
|
|||
|
return element.TryGetProperty(propertyName, out var property) ? property.GetString() ?? "" : "";
|
|||
|
}
|
|||
|
|
|||
|
private static int GetPropertyValueOrDefault(JsonElement element, string propertyName, int defaultValue)
|
|||
|
{
|
|||
|
return element.TryGetProperty(propertyName, out var property) ? property.GetInt32() : defaultValue;
|
|||
|
}
|
|||
|
|
|||
|
public void Dispose()
|
|||
|
{
|
|||
|
_tokenLock?.Dispose();
|
|||
|
_httpClient?.Dispose();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|