You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
553 lines
22 KiB
553 lines
22 KiB
using Furion;
|
|
using System;
|
|
using GDZZ.Core;
|
|
using GDZZ.Core.OAuth;
|
|
using Furion.EventBus;
|
|
using GDZZ.Core.Entity;
|
|
using Furion.DataEncryption;
|
|
using GDZZ.Application.Entity;
|
|
using Furion.FriendlyException;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using System.Collections.Generic;
|
|
using Microsoft.Extensions.Options;
|
|
using Furion.DependencyInjection;
|
|
using Furion.DynamicApiController;
|
|
using Microsoft.AspNetCore.Http;
|
|
using GDZZ.Application.Help;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Mapster;
|
|
using GDZZ.Core.Service;
|
|
using TencentCloud.Sms.V20210111.Models;
|
|
using TencentCloud.Common;
|
|
using Polly;
|
|
using System.IO;
|
|
using System.Diagnostics.Eventing.Reader;
|
|
using System.Diagnostics;
|
|
|
|
namespace GDZZ.Application.Service.Auth
|
|
{
|
|
[ApiDescriptionSettings("Application", Name = "Auth", Order = 1)]
|
|
public class AuthService : IAuthService, IDynamicApiController, ITransient
|
|
{
|
|
#region 仓储
|
|
private readonly SqlSugarRepository<BaseUser> Baseuser; // wx用户仓储
|
|
private readonly SqlSugarRepository<SysUser> _sysUserRep; // 用户表仓储
|
|
private readonly SqlSugarRepository<SysTenant> _sysTenantRep; //租户仓储
|
|
private readonly SqlSugarRepository<SeIF> Self; //职业仓储
|
|
private readonly SqlSugarRepository<Company> CompanyRep;
|
|
private readonly SqlSugarRepository<MiniPayTake> payTakeRep; //支付仓储
|
|
private readonly SqlSugarRepository<SysConfig> _sysConfigRep; // 参数配置表仓储
|
|
private readonly UploadFileOptions _options;
|
|
#endregion
|
|
|
|
#region 服务
|
|
private readonly ISysCacheService _sysCacheService; //缓存
|
|
private readonly ICacheService cacheService;
|
|
private readonly WechatOAuth _wechatOAuth; //微信权限服务
|
|
private readonly IHttpContextAccessor _httpContextAccessor; //http服务
|
|
private readonly IEventPublisher _eventPublisher; //事件写入服务
|
|
private readonly SqlSugarRepository<InviteUserPos> invitaitionRey;
|
|
private readonly SqlSugarRepository<MiniRecharge> rechargeRep; //充值仓储
|
|
#endregion
|
|
/// <summary>
|
|
/// 获取配置文件
|
|
/// </summary>
|
|
private readonly ThirdParty _oauthConfig;
|
|
|
|
|
|
|
|
public AuthService(
|
|
IOptions<OAuthOptions> options,
|
|
SqlSugarRepository<BaseUser> Baseuser,
|
|
SqlSugarRepository<SysTenant> sysTenantRep,
|
|
SqlSugarRepository<SysUser> sysUserRep,
|
|
SqlSugarRepository<SeIF> Self,
|
|
SqlSugarRepository<Company> CompanyRep,
|
|
SqlSugarRepository<MiniPayTake> payTakeRep,
|
|
SqlSugarRepository<SysConfig> sysConfigRep,
|
|
SqlSugarRepository<InviteUserPos> invitaitionRey,
|
|
SqlSugarRepository<MiniRecharge> rechargeRep,
|
|
IOptions<UploadFileOptions> UFoptions,
|
|
ISysCacheService sysCacheService,
|
|
ICacheService cacheService,
|
|
WechatOAuth wechatOAuth,
|
|
IEventPublisher eventPublisher,
|
|
IHttpContextAccessor httpContextAccessor)
|
|
{
|
|
this.CompanyRep= CompanyRep;
|
|
this._eventPublisher= eventPublisher;
|
|
this._sysUserRep = sysUserRep;
|
|
this._sysTenantRep = sysTenantRep;
|
|
this.Baseuser = Baseuser;
|
|
this._httpContextAccessor = httpContextAccessor;
|
|
this.cacheService = cacheService;
|
|
this.Self = Self;
|
|
this.payTakeRep = payTakeRep;
|
|
this._sysCacheService= sysCacheService;
|
|
this._sysConfigRep= sysConfigRep;
|
|
this.invitaitionRey = invitaitionRey;
|
|
this.rechargeRep= rechargeRep;
|
|
_wechatOAuth = wechatOAuth;
|
|
_oauthConfig = options.Value.Wechat;
|
|
this._options = UFoptions.Value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 手机端登录(一键登录)
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
[HttpPost("/Mini/SignIn")]
|
|
[AllowAnonymous]
|
|
public async Task<AuthUserOut> SignInAsync(PhoneModel phoneModel)
|
|
{
|
|
AuthUserOut authUserOut = new AuthUserOut();
|
|
|
|
Company company = new Company();
|
|
|
|
//读取凭证
|
|
var tokenModel = await this._wechatOAuth.GetCode2SessionAsync(phoneModel.Code);
|
|
//解析电话
|
|
var phoneInfo = MiniProgramUtil.AESDecrypt(phoneModel.EncryptedDataStr, tokenModel.SessionKey, phoneModel.Iv);
|
|
|
|
|
|
//查询系统用户
|
|
var sysUser = this._sysUserRep.AsQueryable()
|
|
.Filter("TenantId", true)
|
|
.First(x => x.Phone == phoneInfo.PhoneNumber);
|
|
|
|
var wxUser = await this.Baseuser.AsQueryable()
|
|
.Filter("TenantId", true)
|
|
.Where(x => x.OpenID == tokenModel.OpenId).SingleAsync();
|
|
|
|
|
|
|
|
//账号不存在 生成系统账号
|
|
if (sysUser.IsEmpty())
|
|
{
|
|
sysUser = await this._sysUserRep.InsertReturnEntityAsync(new SysUser()
|
|
{
|
|
Account = phoneInfo.PurePhoneNumber,
|
|
AdminType = AdminType.None,
|
|
Avatar = "https://gdzongzhi.com/assets/img/logo.png",
|
|
Birthday = DateTime.Now,
|
|
CreatedTime = DateTime.Now,
|
|
CreatedUserId = null,
|
|
CreatedUserName = null,
|
|
Sex = Gender.UNKNOWN,
|
|
Status = CommonStatus.ENABLE,
|
|
Email = null,
|
|
IsDeleted = false,
|
|
Name = phoneInfo.PhoneNumber,
|
|
Password = MD5Encryption.Encrypt("123456"),
|
|
TenantId = 392820661919813,
|
|
Phone = phoneInfo.PhoneNumber,
|
|
NickName = "",
|
|
Tel = null,
|
|
});
|
|
}
|
|
|
|
if (wxUser.IsEmpty())
|
|
{
|
|
wxUser = await this.Baseuser.InsertReturnEntityAsync(new BaseUser()
|
|
{
|
|
UnionId = tokenModel.Unionid,
|
|
CreatedUserId = sysUser.Id,
|
|
CreatedTime = DateTime.Now,
|
|
CreatedUserName = sysUser.Name,
|
|
AvatarUrl = "https://gdzongzhi.com/assets/img/logo.png",
|
|
Status = (int)CommonStatus.ENABLE,
|
|
OpenID = tokenModel.OpenId,
|
|
UserName = phoneModel.Phone.ToString(),
|
|
});
|
|
}
|
|
//区分账号类型
|
|
switch (phoneModel.LogInType)
|
|
{
|
|
case (int)UserEnum.JOB:
|
|
wxUser.Type = (int)UserEnum.JOB;
|
|
await this.Baseuser.UpdateAsync(wxUser);
|
|
break;
|
|
case (int)UserEnum.HEADHUNTERS:
|
|
wxUser.Type = (int)UserEnum.HEADHUNTERS;
|
|
await this.Baseuser.UpdateAsync(wxUser);
|
|
break;
|
|
case (int)UserEnum.ADVERTISE:
|
|
wxUser.Type = (int)UserEnum.ADVERTISE;
|
|
await this.Baseuser.UpdateAsync(wxUser);
|
|
//获取公司信息
|
|
company = await this.CompanyRep.FirstOrDefaultAsync(x => x.Id == wxUser.CompanyID);
|
|
authUserOut.companyDto = company.Adapt<CompanyDto>();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
|
|
if (wxUser.IsEmpty() || sysUser.IsEmpty())
|
|
throw Oops.Oh(ErrorCode.xg1002);
|
|
//判断是否存在邀请
|
|
if(phoneModel.Scene != null)
|
|
{
|
|
//判断当前用户是否被邀请过
|
|
var invi = await this.invitaitionRey.FirstOrDefaultAsync(x => x.UserID == UserManager.UserId);
|
|
if (invi.IsNullOrZero())
|
|
{
|
|
var invres = await this.invitaitionRey.FirstOrDefaultAsync(x => x.InviteID == phoneModel.Scene);
|
|
//未被邀请
|
|
var invrey = await this.invitaitionRey.InsertAsync(new InviteUserPos()
|
|
{
|
|
UserID = UserManager.UserId,
|
|
InviteUserID = (long)phoneModel.Scene,
|
|
InviteID = invres.InviteID
|
|
|
|
});
|
|
if (invrey > 0)
|
|
{
|
|
//附加奖励给邀请人
|
|
UtilService utilService = new UtilService(this.rechargeRep);
|
|
utilService.Reward((long)phoneModel.Scene, 1);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
var Self = await this.Self.FirstOrDefaultAsync(x => x.CreatedUserId == sysUser.Id);
|
|
|
|
// 获取加密后的密码
|
|
var encryptPassword = MD5Encryption.Encrypt(sysUser.Password);
|
|
|
|
// 验证账号是否被冻结
|
|
if (sysUser.Status == CommonStatus.DISABLE)
|
|
throw Oops.Oh(ErrorCode.D1017);
|
|
//获取对应租户
|
|
var tenant = this._sysTenantRep.Single(sysUser.TenantId);
|
|
|
|
if (tenant.IsNullOrZero())
|
|
throw Oops.Oh(ErrorCode.F1001);
|
|
|
|
|
|
// 生成Token令牌
|
|
authUserOut.Token = JWTEncryption.Encrypt(new Dictionary<string, object>
|
|
{
|
|
{ClaimConst.CLAINM_USERID, sysUser.Id},
|
|
{ClaimConst.TENANT_ID, sysUser.TenantId},
|
|
{ClaimConst.CLAINM_ACCOUNT, sysUser.Account},
|
|
{ClaimConst.CLAINM_NAME, sysUser.Name},
|
|
{ClaimConst.CLAINM_SUPERADMIN, sysUser.AdminType},
|
|
{ ClaimConst.CLAINM_TENANT_TYPE, tenant.TenantType },
|
|
{ ClaimConst.CLAINM_TENANT_NAME, tenant.Name },
|
|
});
|
|
|
|
// 设置Swagger自动登录
|
|
_httpContextAccessor.HttpContext.SigninToSwagger(authUserOut.Token);
|
|
|
|
// 生成刷新Token令牌
|
|
var refreshToken = JWTEncryption.GenerateRefreshToken(authUserOut.Token, 30);
|
|
|
|
// 设置刷新Token令牌
|
|
_httpContextAccessor.HttpContext.Response.Headers["x-access-token"] = refreshToken;
|
|
|
|
var httpContext = App.HttpContext;
|
|
await _eventPublisher.PublishAsync(new ChannelEventSource("Update:UserLoginInfo",
|
|
new SysUser { Id = sysUser.Id, LastLoginIp = httpContext.GetLocalIpAddressToIPv4(), LastLoginTime = DateTime.Now }));
|
|
|
|
authUserOut.Avatar = sysUser.Avatar;
|
|
authUserOut.Phone = sysUser.Phone;
|
|
authUserOut.Sex = sysUser.Sex;
|
|
authUserOut.UserId = sysUser.Id;
|
|
authUserOut.UserName = sysUser.Name;
|
|
authUserOut.Self = Self.IsEmpty() ? null : Self.Name;
|
|
authUserOut.Type = (UserEnum)wxUser.Type;
|
|
authUserOut.OpenID = tokenModel.OpenId;
|
|
authUserOut.Tenant = tenant.Adapt<TenantOutput>();
|
|
authUserOut.Describe = wxUser.Describe;
|
|
|
|
await this.cacheService.SetUserInfoAsync(authUserOut, authUserOut.UserId);
|
|
return authUserOut;
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 手机端登录(验证码登录)
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
[HttpPost("/Mini/SignInCode")]
|
|
[AllowAnonymous]
|
|
public async Task<AuthUserOut> SignInCodeAsync(PhoneModel phoneModel)
|
|
{
|
|
Company company = new Company();
|
|
AuthUserOut authUserOut = new AuthUserOut();
|
|
//验证电话和验证码一致
|
|
var verIfy = await this.cacheService.GetVerifyCode(phoneModel.Phone);
|
|
if (verIfy != phoneModel.CheckingCode)
|
|
throw new Exception("验证码错误");
|
|
|
|
//查询系统用户
|
|
var sysUser = this._sysUserRep.AsQueryable()
|
|
.Filter("TenantId", true)
|
|
.First(x => x.Phone == phoneModel.Phone.ToString());
|
|
|
|
|
|
//账号不存在 生成系统账号
|
|
if (sysUser.IsEmpty())
|
|
{
|
|
sysUser = await this._sysUserRep.InsertReturnEntityAsync(new SysUser()
|
|
{
|
|
Account = phoneModel.Phone.ToString(),
|
|
AdminType = AdminType.None,
|
|
Avatar = "https://gdzongzhi.com/assets/img/logo.png",
|
|
Birthday = DateTime.Now,
|
|
CreatedTime = DateTime.Now,
|
|
CreatedUserId = null,
|
|
CreatedUserName = null,
|
|
Sex = Gender.UNKNOWN,
|
|
Status = CommonStatus.ENABLE,
|
|
Email = null,
|
|
IsDeleted = false,
|
|
Name = phoneModel.Phone.ToString(),
|
|
Password = MD5Encryption.Encrypt("123456"),
|
|
TenantId = 392820661919813,
|
|
Phone = phoneModel.Phone.ToString(),
|
|
NickName = "",
|
|
Tel = null,
|
|
});
|
|
}
|
|
|
|
|
|
//读取凭证
|
|
var tokenModel = await this._wechatOAuth.GetCode2SessionAsync(phoneModel.Code);
|
|
|
|
var wxUser = await this.Baseuser.AsQueryable()
|
|
.Filter("TenantId", true)
|
|
.Where(x => x.OpenID == tokenModel.OpenId).SingleAsync();
|
|
|
|
if (wxUser.IsEmpty())
|
|
{
|
|
wxUser = await this.Baseuser.InsertReturnEntityAsync(new BaseUser()
|
|
{
|
|
UnionId = tokenModel.Unionid,
|
|
CreatedUserId = sysUser.Id,
|
|
CreatedTime = DateTime.Now,
|
|
CreatedUserName = sysUser.Name,
|
|
AvatarUrl = "https://gdzongzhi.com/assets/img/logo.png",
|
|
Status = (int)CommonStatus.ENABLE,
|
|
OpenID = tokenModel.OpenId,
|
|
UserName = phoneModel.Phone.ToString(),
|
|
});
|
|
}
|
|
//区分账号类型
|
|
switch (phoneModel.LogInType)
|
|
{
|
|
case (int)UserEnum.JOB :
|
|
wxUser.Type = (int)UserEnum.JOB;
|
|
await this.Baseuser.UpdateAsync(wxUser);
|
|
break;
|
|
case (int)UserEnum.HEADHUNTERS:
|
|
wxUser.Type = (int)UserEnum.HEADHUNTERS;
|
|
await this.Baseuser.UpdateAsync(wxUser);
|
|
break;
|
|
case (int)UserEnum.ADVERTISE:
|
|
wxUser.Type = (int)UserEnum.ADVERTISE;
|
|
await this.Baseuser.UpdateAsync(wxUser);
|
|
//获取公司信息
|
|
company = await this.CompanyRep.FirstOrDefaultAsync(x => x.Id == wxUser.CompanyID);
|
|
authUserOut.companyDto = company.Adapt<CompanyDto>();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
|
|
|
|
if (wxUser.IsEmpty() || sysUser.IsEmpty())
|
|
throw Oops.Oh(ErrorCode.xg1002);
|
|
|
|
|
|
var Self = await this.Self.FirstOrDefaultAsync(x => x.CreatedUserId == sysUser.Id);
|
|
|
|
// 获取加密后的密码
|
|
var encryptPassword = MD5Encryption.Encrypt(sysUser.Password);
|
|
|
|
// 验证账号是否被冻结
|
|
if (sysUser.Status == CommonStatus.DISABLE)
|
|
throw Oops.Oh(ErrorCode.D1017);
|
|
//获取对应租户
|
|
var tenant = this._sysTenantRep.Single(sysUser.TenantId);
|
|
|
|
if (tenant.IsNullOrZero())
|
|
throw Oops.Oh(ErrorCode.F1001);
|
|
|
|
|
|
// 生成Token令牌
|
|
authUserOut.Token = JWTEncryption.Encrypt(new Dictionary<string, object>
|
|
{
|
|
{ClaimConst.CLAINM_USERID, sysUser.Id},
|
|
{ClaimConst.TENANT_ID, sysUser.TenantId},
|
|
{ClaimConst.CLAINM_ACCOUNT, sysUser.Account},
|
|
{ClaimConst.CLAINM_NAME, sysUser.Name},
|
|
{ClaimConst.CLAINM_SUPERADMIN, sysUser.AdminType},
|
|
{ ClaimConst.CLAINM_TENANT_TYPE, tenant.TenantType },
|
|
{ ClaimConst.CLAINM_TENANT_NAME, tenant.Name },
|
|
});
|
|
|
|
// 设置Swagger自动登录
|
|
_httpContextAccessor.HttpContext.SigninToSwagger(authUserOut.Token);
|
|
|
|
// 生成刷新Token令牌
|
|
var refreshToken = JWTEncryption.GenerateRefreshToken(authUserOut.Token, 30);
|
|
|
|
// 设置刷新Token令牌
|
|
_httpContextAccessor.HttpContext.Response.Headers["x-access-token"] = refreshToken;
|
|
|
|
var httpContext = App.HttpContext;
|
|
await _eventPublisher.PublishAsync(new ChannelEventSource("Update:UserLoginInfo",
|
|
new SysUser { Id = sysUser.Id, LastLoginIp = httpContext.GetLocalIpAddressToIPv4(), LastLoginTime = DateTime.Now }));
|
|
|
|
authUserOut.Avatar = sysUser.Avatar;
|
|
authUserOut.Phone = sysUser.Phone;
|
|
authUserOut.Sex = sysUser.Sex;
|
|
authUserOut.UserId = sysUser.Id;
|
|
authUserOut.UserName = sysUser.Name;
|
|
authUserOut.Self = Self.IsEmpty() ? null : Self.Name;
|
|
authUserOut.Type = (UserEnum)wxUser.Type;
|
|
authUserOut.OpenID = tokenModel.OpenId;
|
|
authUserOut.Tenant = tenant.Adapt<TenantOutput>();
|
|
authUserOut.Describe = wxUser.Describe;
|
|
|
|
await this.cacheService.SetUserInfoAsync(authUserOut, authUserOut.UserId);
|
|
return authUserOut;
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 发送验证码
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
[HttpGet("/Mini/SendTextMessage")]
|
|
[AllowAnonymous]
|
|
public async Task SendTextMessage(string phone)
|
|
{
|
|
string code = "";
|
|
//生成随机数字
|
|
Random rand = new Random();
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
code += rand.Next(0, 9).ToString();
|
|
}
|
|
|
|
Credential credential = new Credential()
|
|
{
|
|
SecretId = await GetConfigCache("TENCENT_SMS_SECRET_ID"),
|
|
SecretKey =await GetConfigCache("TENCENT_SMS_SECRET_KEY")
|
|
};
|
|
|
|
SendSmsRequest sendSmsRequest = new SendSmsRequest()
|
|
{
|
|
SignName = await GetConfigCache("TENCENT_SMS_SIGN"),
|
|
SmsSdkAppId = await GetConfigCache("TENCENT_SMS_SDK_APP_ID"),
|
|
PhoneNumberSet = new string[] { "+86" + phone },
|
|
TemplateId = await GetConfigCache("TENCENT_SMS_TEMPLATEID"),
|
|
TemplateParamSet = new string[] { code }
|
|
};
|
|
TencentCloudExamples.SendTextMessage(sendSmsRequest,credential);
|
|
await this.cacheService.SetVerifyCode(phone, code);
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// 获取当前登录用户信息
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
[HttpGet("/Mini/GetLoginUser")]
|
|
public async Task<AuthUserOut> GetLoginUserAsync()
|
|
{
|
|
return await this.cacheService.GetUserInfoAsync(UserManager.UserId);
|
|
}
|
|
/// <summary>
|
|
/// 获取AccessToken
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
[HttpGet("/Mini/GetAccessToken")]
|
|
public async Task<string> GetAccessTokenAsync()
|
|
{
|
|
var value = await cacheService.GetAccessTokenAsync();
|
|
if(value == null)
|
|
{
|
|
var token = this._wechatOAuth.GetTokenAsync();
|
|
await cacheService.SetAccessTokenAsync(token.Result.AccessToken);
|
|
value = token.Result.AccessToken;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 获取二维分享码
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
[HttpGet("/Mini/GetShareCode")]
|
|
public async Task<string> GetShareCode(string scene, string page)
|
|
{
|
|
|
|
var url = await this.cacheService.GetQRCodeAsync(UserManager.UserId);
|
|
if(url != null)
|
|
return url;
|
|
var value = await cacheService.GetAccessTokenAsync();
|
|
if (value == null)
|
|
{
|
|
var token = this._wechatOAuth.GetTokenAsync();
|
|
await cacheService.SetAccessTokenAsync(token.Result.AccessToken);
|
|
value = token.Result.AccessToken;
|
|
}
|
|
var res = await this._wechatOAuth.GetShareCodeAsync(value,scene, page);
|
|
var fileName = Path.Combine("/" + UserManager.UserId + ".png");
|
|
|
|
|
|
try
|
|
{
|
|
var filePath = Path.Combine(App.WebHostEnvironment.WebRootPath, _options.QRCode.path);
|
|
if (!Directory.Exists(filePath))
|
|
Directory.CreateDirectory(filePath);
|
|
MemoryStream ms = new MemoryStream(res);
|
|
FileStream fs = new FileStream(filePath + fileName, FileMode.OpenOrCreate);
|
|
ms.WriteTo(fs);
|
|
ms.Close();
|
|
fs.Close();
|
|
await this.cacheService.SetQRCodeAsync(UserManager.UserId,fileName);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw Oops.Oh(e.Message);
|
|
}
|
|
return fileName;
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// 获取配置信息
|
|
/// </summary>
|
|
/// <param name="code"></param>
|
|
/// <returns></returns>
|
|
private async Task<dynamic> GetConfigCache(string code)
|
|
{
|
|
var value = await _sysCacheService.GetAsync(code);
|
|
if (string.IsNullOrEmpty(value))
|
|
{
|
|
var config = await _sysConfigRep.FirstOrDefaultAsync(u => u.Code == code);
|
|
value = config != null ? config.Value : "";
|
|
await _sysCacheService.SetAsync(code, value);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
}
|