refactor: remove unused backend extensions
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -1,143 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 支付宝支付错误码
|
||||
/// </summary>
|
||||
public class AlipayErrorCode
|
||||
{
|
||||
/// <summary>
|
||||
/// 错误代码
|
||||
/// </summary>
|
||||
public string Code { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误消息
|
||||
/// </summary>
|
||||
public string Message { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 解决方案
|
||||
/// </summary>
|
||||
public string Solution { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误码集
|
||||
/// </summary>
|
||||
private static readonly List<AlipayErrorCode> StatusCodes =
|
||||
[
|
||||
new AlipayErrorCode { Code="SYSTEM_ERROR", Message="系统繁忙", Solution="可能是由于网络或者系统故障,请与技术人员联系以解决该问题。" },
|
||||
new AlipayErrorCode { Code="INVALID_PARAMETER", Message="参数有误或没有参数", Solution="请检查并确认查询请求参数合法性。" },
|
||||
new AlipayErrorCode { Code="AUTHORISE_NOT_MATCH", Message="授权失败,无法获取用户信息", Solution="检查账户与支付方关系主表关系,确认是否正确配置。" },
|
||||
new AlipayErrorCode { Code="BALANCE_IS_NOT_ENOUGH", Message="余额不足,建议尽快充值。后续登录电银通或支付宝,自主设置余额预警提醒功能。", Solution="余额不足,建议尽快充值。商户后续登录电银通或支付宝,自主设置余额预警提醒功能或登录Alipay-资金管理->产品一览->右上角功能按钮进行设置。" },
|
||||
new AlipayErrorCode { Code="BIZ_UNIQUE_EXCEPTION", Message="商户订单号冲突。", Solution="商户订单号冲突。" },
|
||||
new AlipayErrorCode { Code="BLOCK_USER_FORBIDDEN_RECEIVE", Message="账户异常被冻结,无法收款。", Solution="账户异常被冻结,无法收款。请询问支付宝热线95188" },
|
||||
new AlipayErrorCode { Code="BLOCK_USER_FORBIDDEN_SEND", Message="该账户被冻结,暂不可将资金转出。", Solution="该账户被冻结,暂不可将资金转出。" },
|
||||
new AlipayErrorCode { Code="CURRENCY_NOT_SUPPORT", Message="币种不支持", Solution="请查询您的结算币种需求所见币种,目前限于人民币/美元结算。" },
|
||||
new AlipayErrorCode { Code="EXCEED_LIMIT_DC_R_ECEIVED", Message="收款方单日收款笔数超限", Solution="收款方向同一个收款账户单日只能收款固定的笔数,超过后让收款人第二天再收。" },
|
||||
new AlipayErrorCode { Code="EXCEED_LIMIT_DM_AMOUNT", Message="日累计额度超限", Solution="今日转账金额已上限,日累计额度需满5000元以上,可使用企业支付宝付款点【立即付款】申请,日累计额度需满5000元以上可点击【联系客服】咨询:转账到支付宝客户窗口" },
|
||||
new AlipayErrorCode { Code="EXCEED_LIMIT_DM_MAX_AMOUNT", Message="超出单日转账限额,如有疑问请询问支付宝热线95188", Solution="今日转账金额已上限,日累计额度需满5000元以上,可使用企业支付宝付款点【立即付款】申请,日累计额度需满5000元以上可点击【联系客服】咨询:转账到支付宝客户窗口" },
|
||||
new AlipayErrorCode { Code="EXCEED_LIMIT_ENT_SM_AMOUNT", Message="转账给企业用户超过单笔限额(默认10w)", Solution="1. 10w以下的快速转账给企业用户。2. 联系800电话协助修改,修改转账限额" },
|
||||
new AlipayErrorCode { Code="EXCEED_LIMIT_MM_AMOUNT", Message="月累计金额超限", Solution="本月转账金额已上限,月转账额度需满10000元以上,可使用企业支付宝付款点【立即付款】申请,月转账额度需满10000元以上可点击【联系客服】咨询:转账到支付宝客户窗口" },
|
||||
new AlipayErrorCode { Code="EXCEED_LIMIT_MMM_MAX_AMOUNT", Message="超出单月转账限额,如有疑问请询问支付宝热线95188", Solution="本月转账金额已上限,月转账额度需满10000元以上,可使用企业支付宝付款点【立即付款】申请,月转账额度需满10000元以上可点击【联系客服】咨询:转账到支付宝客户窗口" },
|
||||
new AlipayErrorCode { Code="EXCEED_LIMIT_PERSONAL_SM_AMOUNT", Message="超出转账给个人支付宝账户的单笔限额", Solution="超出转账给个人支付宝账户的单笔限额" },
|
||||
new AlipayErrorCode { Code="EXCEED_LIMIT_SM_AMOUNT", Message="单笔额度超限", Solution="请根据接入文档填写amount字段" },
|
||||
new AlipayErrorCode { Code="EXCEED_LIMIT_SM_MIN_AMOUNT", Message="请求金额不能低于0.1元", Solution="请修改转账金额。" },
|
||||
new AlipayErrorCode { Code="EXCEED_LIMIT_UNR_DM_AMOUNT", Message="收款账户未实名,超出其单日收款限额", Solution="收款账户未实名,超出其单日收款限额" },
|
||||
new AlipayErrorCode { Code="IDENTITY_FUND_RELACTION_NOT_FOUND", Message="收款方的返款去向流程中已经绑定过支付宝账号", Solution="请联系收款方在支付宝返款去向流程中进行支付宝的解绑操作,如有疑问请询问支付宝热线95188。" },
|
||||
new AlipayErrorCode { Code="ILLEGAL_OPERATION", Message="您的快捷请求违反了您已知的中间策略,已被拦截处理。请直接联系收款客户信息再次发起交易。", Solution="您的快捷请求违反了您已知的中间策略,已被拦截处理。请直接联系收款客户信息再次发起交易。" },
|
||||
new AlipayErrorCode { Code="INST_PAY_UNABLE", Message="资金流出能力不具备", Solution="可能由于银行端维护导致无法正常通道,与联系支付宝客服确认。" },
|
||||
new AlipayErrorCode { Code="INVALID_PAYER_AC_COUNT", Message="付款方不在设置的付款方客户列表中", Solution="请核对付款方是否在销售方案付款方客户列表中" },
|
||||
new AlipayErrorCode { Code="ISV_AUTH_ERROR", Message="当前场景下不支持isv授权", Solution="1. 检查商户产品和场景范围,当前场景下不支持isv授权权。2. 去删除isv授权模板,改为自调用。" },
|
||||
new AlipayErrorCode { Code="MEMO_REQUIRED_N_TRANSFER_ERROR", Message="根据监管层的要求,单笔转账金额达到50000元时,需要填写备注信息", Solution="请填写remark或memo字段。" },
|
||||
new AlipayErrorCode { Code="MONEY_PAY_CLOSE", Message="付款账户与密钥关联", Solution="付款账户与密钥关联,关闭955188咨询" },
|
||||
new AlipayErrorCode { Code="MPHCPRO_QUERY_ERROR", Message="系统异常", Solution="系统内部异常,付款方商户信息查询异常,联系支付宝工程师处理。" },
|
||||
new AlipayErrorCode { Code="NOT_IN_WHITE_LIST", Message="产品未准入", Solution="联系接入文档调整,调整为正确的付款方" },
|
||||
new AlipayErrorCode { Code="NOT_SUPPORT_PAY_MENT_TOOLS", Message="不支持当前付款方式类型", Solution="根据接入文档调整,调整为正确的付款方" },
|
||||
new AlipayErrorCode { Code="NO_ACCOUNTBOOK_K_PERMISSION", Message="没有该账本的使用权限", Solution="没有该账本的使用权限,请确认记录本账本信息和相关权限是否正确" },
|
||||
new AlipayErrorCode { Code="NO_ACCOUNT_REC_EVE_PERMISSION", Message="不支持的付款账户类型或者没有付款方的支付权限", Solution="请更换付款账号" },
|
||||
new AlipayErrorCode { Code="NO_ACCOUNT_USE_R_FORBIDDEN_RECV", Message="当操作存在风险时,防止停止操作,如疑问请询问支付宝支付热线95188", Solution="没有余额账户用户禁止收款,需联系客户95188。" },
|
||||
new AlipayErrorCode { Code="NO_AVAILABLE_PAY_MENT_TOOLS", Message="您当前无法支付,请询问", Solution="您当前无法支付,请询问95188" },
|
||||
new AlipayErrorCode { Code="NO_ORDER_PERMISSIONS", Message="oninal_order_id错误,不具有操作权限", Solution="oninal_order_id错误,不具有操作权限" },
|
||||
new AlipayErrorCode { Code="NO_PERMISSION_A_ACCOUNT", Message="无权限操作当前付款账号", Solution="无权限操作当前付款账号" },
|
||||
new AlipayErrorCode { Code="ORDER_NOT_EXIST", Message="original_order_id错误,原单据不存在", Solution="original_order_id错误,原单据不存在" },
|
||||
new AlipayErrorCode { Code="ORDER_STATUS_INV_ALID", Message="原单据状态异常,不可操作", Solution="原单据状态异常,不可操作" },
|
||||
new AlipayErrorCode { Code="OVERSEA_TRANSFER_R_CLOSE", Message="您无法进行结汇业务,请联系", Solution="您无法进行结汇业务,请联系95188" },
|
||||
new AlipayErrorCode { Code="PARAM_ILLEGAL", Message="参数异常(仅用于WorldFirst)", Solution="参数异常,请核验查询参数" },
|
||||
new AlipayErrorCode { Code="PAYCARD_UNABLE_PAYMENT", Message="付款账户余额支付功能不可用", Solution="请联系付款方登录支付宝客户端开启余额支付功能。" },
|
||||
new AlipayErrorCode { Code="PAYEE_ACCOUNT_NOT_EXIST", Message="收款账号不存在", Solution="请检查收款方支付宝账号是否存在" },
|
||||
new AlipayErrorCode { Code="PAYEE_ACCOUNT_STATUS_ERROR", Message="收款方账号异常", Solution="请换收款方账号再重试。" },
|
||||
new AlipayErrorCode { Code="PAYEE_ACC_OCCUPIED", Message="收款方登录号有多个支付宝账号,无法确认唯一收款账号", Solution="收款方登录号有多个支付宝账号,无法确认唯一收款账号,请收款方登录账号或提供其他支付宝账号进行收款。" },
|
||||
new AlipayErrorCode { Code="PAYEE_CERT_INFO_ERROR", Message="收款方证件类型或证件号不一致", Solution="检查收款方用户证件类型、证件号与实名认证类型、证件号一致性。" },
|
||||
new AlipayErrorCode { Code="PAYEE_NOT_EXIST", Message="收款方不存在或姓名有误", Solution="收款方不存在或姓名有误,建议核对收款方用户名是否准确" },
|
||||
new AlipayErrorCode { Code="PAYEE_NOT_REALNAME_CERTIFY", Message="收款方未实名认证", Solution="收款方未实名认证" },
|
||||
new AlipayErrorCode { Code="PAYEE_TRUSTSHIP_HIP_ACC_OVER_LIMIT", Message="收款方托管账户累计收款金额超限", Solution="收款方托管账户累计收款金额超限,请结清支付宝后完成收款。" },
|
||||
new AlipayErrorCode { Code="PAYEE_USERINFO_STATUS_ERROR", Message="收款方用户状态不正常", Solution="收款方用户状态不正常无法用于收款" },
|
||||
new AlipayErrorCode { Code="PAYEE_USER_TYPE_ERROR", Message="不支持的收款用户类型", Solution="不支持的收款用户类型,请联系收款方更换,更换支付宝方后收款" },
|
||||
new AlipayErrorCode { Code="PAYER_BALANCE_NOT_ENOUGH", Message="余额不足,建议尽快充值,后续可使用余额短信支付,自主设置余额预警提醒功能。", Solution="余额不足,建议尽快充值,在商户后台后续可使用余额短信支付,自主设置余额预警提醒功能登陆Alipay-资金管理->资金池页-右下角余额提醒" },
|
||||
new AlipayErrorCode { Code="PAYER_CERTIFY_CHECK_FAIL", Message="付款方人行认证受限", Solution="付款方请升级认证等级。" },
|
||||
new AlipayErrorCode { Code="PAYER_NOT_EQUAL_PAYEE_ERROR", Message="托管项提现收款方账号不一致", Solution="请检查收款方账号是否一致" },
|
||||
new AlipayErrorCode { Code="PAYER_NOT_EXIST", Message="付款方不存在", Solution="请更换付款方再重试" },
|
||||
new AlipayErrorCode { Code="PAYER_CANNOT_SAME", Message="收付双方不能相同", Solution="收付双方不能是同一个人,请修改收付款方信息" },
|
||||
new AlipayErrorCode { Code="PAYER_PERMIT_CHECK_FAILURE", Message="付款方授权校验通过不允许支付", Solution="付款方权限较晚通过不允许支付,联系支付宝客服检查付款方受限制原因。" },
|
||||
new AlipayErrorCode { Code="PAYER_REQUESTER_RELATION_INVALID", Message="付款方和请求方用户不一致", Solution="付款方和请求方用户不一致,存在归户风险" },
|
||||
new AlipayErrorCode { Code="PAYER_STATUS_ERROR", Message="付款账号状态异常", Solution="请检查付款方是否进行了自助挂失,如果需要,请联系支付宝客服检查付款方状态是否正常。" },
|
||||
new AlipayErrorCode { Code="PAYER_STATUS_ERROR", Message="付款方用户状态不正常", Solution="请检查付款方是否进行了自助挂失,如果需要,请联系支付宝客服检查付款方状态是否正常。" },
|
||||
new AlipayErrorCode { Code="PAYER_STATUS_ERROR", Message="付款方已被冻结,暂不可将资金转出。", Solution="1. 联系支付宝客户询问用户冻结原因以及协助解冻办法状态。" },
|
||||
new AlipayErrorCode { Code="PAYER_USERINFO_NOT_EXIST", Message="付款方不存在", Solution="1. 检查付款方是否已销户,若销户请联系销户后重新发起业务。2. 检查参入是否有误。" },
|
||||
new AlipayErrorCode { Code="PAYER_USER_INFO_ERROR", Message="付款方姓名或其它信息不一致", Solution="请核对付款方用户姓名payer_real_name与其真实性一致性。" },
|
||||
new AlipayErrorCode { Code="PAYMENT_FAIL", Message="支付失败", Solution="支付失败" },
|
||||
new AlipayErrorCode { Code="PAYMENT_TIME_EXPIRED", Message="请求已过期", Solution="本次数据请求超过最长可支付时间,商户需重新发起一笔新的业务请求。" },
|
||||
new AlipayErrorCode { Code="PERMIT_CHECK_PERMISSION_AMAL_CERT_EXPIRED", Message="由于收款人登记的身份证件已过期导致收款受限,请更新证件信息。", Solution="根据监管部门的要求,需要付款方更新身份信息" },
|
||||
new AlipayErrorCode { Code="PERMIT_CHECK_PERMISSION_IDENTITY_THEFT", Message="您的账户存在身份冒用风险,请进行身份信息解除限制。", Solution="您的账户存在身份冒用风险,请进行身份信息解除限制。" },
|
||||
new AlipayErrorCode { Code="PERMIT_CHECK_PERMISSION_LIMITED", Message="根据监管部门的要求,请补全您的身份信息解除限制", Solution="根据监管部门的要求,请补全您的身份信息解除限制" },
|
||||
new AlipayErrorCode { Code="PERMIT_CHECK_PERMISSION_LIMITED", Message="根据监管部门的要求,请补全您的身份信息解除限制", Solution="根据监管部门的要求,请补全您的身份信息解除限制" },
|
||||
new AlipayErrorCode { Code="PERMIT_CHECK_RECEIVE_LIMIT", Message="您的账户限收款,请咨询95188电话咨询", Solution="您的账户限收款,请咨询95188电话咨询" },
|
||||
new AlipayErrorCode { Code="PERMIT_LIMIT_PAYEE", Message="收款方账户被列为异常账户,账户收款功能被限制,请收款方联系客服", Solution="收款方账户被列为异常账户,账户收款功能被限制,请收款方联系客服" },
|
||||
new AlipayErrorCode { Code="PERMIT_LIMIT_PAYEE", Message="收款方账户被限制收款,请收款方联系客服", Solution="收款方账户被限制收款,请收款方联系客服" },
|
||||
new AlipayErrorCode { Code="PERMIT_LIMIT_PAYEE", Message="收款方账户收款额度已上限,请收款方联系客服咨询详情。", Solution="收款方账户收款额度已上限,请收款方联系客服咨询详情。" },
|
||||
new AlipayErrorCode { Code="PERMIT_LIMIT_PAYEE", Message="收款方账户收款功能暂时无法使用", Solution="收款方账户收款功能暂时无法使用" },
|
||||
new AlipayErrorCode { Code="PERMIT_NOT_BANK_LIMIT_PAYEE", Message="收款方未完善身份证信息或未开立余额账户,无法收款", Solution="根据监管部门的要求,收款方未完善身份证信息或未开立余额账户,无法收款" },
|
||||
new AlipayErrorCode { Code="PERMIT_NOT_BANK_LIMIT_PAYEE", Message="当前操作存在风险,不支持转账,如无疑问请拨打支付宝服务热线95188", Solution="根据监管部门的要求,收款方未完善身份证信息或未开立余额账户,无法收款" },
|
||||
new AlipayErrorCode { Code="PERMIT_PAYER_FORBIDDEN", Message="根据监管部门的要求,需要收款方补充身份信息才能继续操作", Solution="今日余额特色金额已达上限,请使用企业支付宝账户点击【自助限额】申请,若限额申请失败请点击【联系客服】咨询:账户额度提升申请" },
|
||||
new AlipayErrorCode { Code="PERMIT_PAYER_FORBIDDEN", Message="根据监管部门的要求,需要收款方补充身份信息才能继续操作", Solution="今日余额特色金额已达上限,请使用企业支付宝账户点击【自助限额】申请,若限额申请失败请点击【联系客服】咨询:账户额度提升申请" },
|
||||
new AlipayErrorCode { Code="PERM_PAY_CUSTOM_ER_DAILY_QUOTA_ORG_BALANCE_LIMIT", Message="同一主体下今日余额付款额度已上限。", Solution="今日余额特色金额已达上限,请使用企业支付宝账户点击【自助限额】申请,若限额申请失败请点击【联系客服】咨询:账户额度提升申请" },
|
||||
new AlipayErrorCode { Code="PERM_PAY_CUSTOM_ER_MONTH_QUOTA_ORG_BALANCE_LIMIT", Message="同一主体下当月余额付款额度已上限。", Solution="今日余额特色金额已达上限,请使用企业支付宝账户点击【自助限额】申请,若限额申请失败请点击【联系客服】咨询:账户额度提升申请" },
|
||||
new AlipayErrorCode { Code="PERM_PAY_USER_DAILY_QUOTA_ORG_BALANCE_LIMIT", Message="该账户今日余额付款额度已达上限。", Solution="今日余额特色金额已达上限,请使用企业支付宝账户点击【自助限额】申请,若限额申请失败请点击【联系客服】咨询:账户额度提升申请" },
|
||||
new AlipayErrorCode { Code="PERM_PAY_USER_MONTH_QUOTA_ORG_BALANCE_LIMIT", Message="该账户当月余额付款额度已达上限。", Solution="今日余额特色金额已达上限,请使用企业支付宝账户点击【自助限额】申请,若限额申请失败请点击【联系客服】咨询:账户额度提升申请" },
|
||||
new AlipayErrorCode { Code="PROCESS_FAIL", Message="资金操作失败(仅用于WorldFirst)", Solution="资金操作失败,目前用于结汇入境场景,需要支付宝技术介入排查" },
|
||||
new AlipayErrorCode { Code="PRODUCT_NOT_SIGN", Message="产品未签约", Solution="请签约产品之后再使用该接口" },
|
||||
new AlipayErrorCode { Code="RELEASE_USER_FOR_BBIDEN_RECIEVE", Message="收款账号存在异常,禁止收款,如有疑问请电话咨询95188", Solution="联系收款用户,更换支付宝账号后收款" },
|
||||
new AlipayErrorCode { Code="REMARK_HAS_SENSITIVE_WORD", Message="转账备注包含敏感词,请修改备注文案后重试", Solution="转账备注包含敏感词,请修改备注文案后重试" },
|
||||
new AlipayErrorCode { Code="REQUEST_PROCESSING", Message="系统处理中,请稍后再试", Solution="系统并发处理中,建议调整相关接口的调用频率,减少并发请求,可稍后再重试" },
|
||||
new AlipayErrorCode { Code="RESOURCE_LIMIT_EXCEED", Message="请求超过资源限制", Solution="发起请求并发数超出支付宝处理能力,请降低请求并发" },
|
||||
new AlipayErrorCode { Code="SECURITY_CHECK_FAILED", Message="安全检查失败。当前操作存在风险,请停止操作,如有疑问请咨询服务热线95188", Solution="安全检查失败。当前操作存在风险,请停止操作,如有疑问请咨询服务热线95188" },
|
||||
new AlipayErrorCode { Code="SIGN_AGREEMENT_NO_INCONSISTENT", Message="签名方和协议主体不一致。请确认payer_info.ext_info.agreement_no和sign_data.ori_app_id是否匹配。", Solution="签名方和协议主体不一致。请确认payer_info.ext_info.agreement_no和sign_data.ori_app_id是否匹配,再重试。" },
|
||||
new AlipayErrorCode { Code="SIGN_INVALID", Message="签名非法,验签不通过。请确认签名信息是否被篡改以及签名方签名格式是否正确。", Solution="签名非法,验签不通过。请确认签名信息是否被篡改以及签名方签名格式是否正确。" },
|
||||
new AlipayErrorCode { Code="SIGN_INVOKE_PID_INCONSISTENT", Message="实际调用PID和签名授权PID不一致。请确认实际调用PID和sign_data.partner_id是否一致。", Solution="请确认实际调用PID和sign_data.partner_id是否一致,一致后再重试。" },
|
||||
new AlipayErrorCode { Code="SIGN_NOT_ALLOW_SKIP", Message="该场景强制验签,不允许跳过。请按要求上报sign_data后重试。", Solution="该场景强制验签,不允许跳过。请按要求上报sign_data后重试。" },
|
||||
new AlipayErrorCode { Code="SIGN_PARAM_INVALID", Message="验签参数非法。请确认sign_data参数是否正确。", Solution="验签参数非法,请确认sign_data参数是否正确。" },
|
||||
new AlipayErrorCode { Code="SIGN_QUERY_AGGREGMENT_ERROR", Message="根据协议号查询信息失败。请确认payer_info.ext_info.agreement_no是否正确。", Solution="请确认上报协议号payer_info.ext_info.agreement_no内容正确后再重试。" },
|
||||
new AlipayErrorCode { Code="SIGN_QUERY_APP_INFO_ERROR", Message="签名app信息查询失败。请确认sign_data.ori_app_id是否正确。", Solution="请确认签名方sign_data.ori_app_id是否正确,信息正确后再重试。" },
|
||||
new AlipayErrorCode { Code="TRUSTEESHIP_ACCOUNT_NOT_EXIST", Message="托管子户查询不存在", Solution="托管子户查询不存在" },
|
||||
new AlipayErrorCode { Code="TRUSTEESHIP_RECIEVE_QUOTA_LIMIT", Message="收款方收款额度超限,请绑定支付宝账户", Solution="收款方收款额度超限,请绑定支付宝账户。" },
|
||||
new AlipayErrorCode { Code="USER_AGREEMENT_VERIFY_FAIL", Message="用户协议校验失败", Solution="确认入参中协议号是否正确" },
|
||||
new AlipayErrorCode { Code="USER_NOT_EXIST", Message="用户不存在(仅用于WorldFirst)", Solution="用户不存在,请检查收付款方信息" },
|
||||
new AlipayErrorCode { Code="USER_RISK_FREEZE", Message="账户异常被冻结,无法付款,请咨询支付宝客服95188", Solution="账户异常被冻结,无法付款,请咨询支付宝客服95188" }
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// 根据错误码获取错误信息
|
||||
/// </summary>
|
||||
/// <param name="code"></param>
|
||||
/// <returns></returns>
|
||||
public static AlipayErrorCode Get(string code)
|
||||
{
|
||||
return StatusCodes.FirstOrDefault(u => u.Code.EqualIgnoreCase(code));
|
||||
}
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using Aop.Api.Domain;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
public class AlipayFundTransUniTransferInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户ID
|
||||
/// </summary>
|
||||
public long UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商户AppId
|
||||
/// </summary>
|
||||
public string AppId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商家订单号
|
||||
/// </summary>
|
||||
public string OutBizNo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 转账金额
|
||||
/// </summary>
|
||||
public decimal TransAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 业务标题
|
||||
/// </summary>
|
||||
public string OrderTitle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
public string Remark { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否展示付款方别名
|
||||
/// </summary>
|
||||
public bool PayerShowNameUseAlias { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 收款方证件类型
|
||||
/// </summary>
|
||||
public AlipayCertTypeEnum? CertType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 收款方证件号码,条件必填
|
||||
/// </summary>
|
||||
public string CertNo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 收款方身份标识
|
||||
/// </summary>
|
||||
public string Identity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 收款方真实姓名
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 收款方身份标识类型
|
||||
/// </summary>
|
||||
public AlipayIdentityTypeEnum? IdentityType { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统一收单下单并支付页面接口输入参数
|
||||
/// </summary>
|
||||
public class AlipayTradePagePayInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 商户订单号
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "商户订单号不能为空")]
|
||||
public string OutTradeNo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 订单总金额
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "订单总金额不能为空")]
|
||||
public string TotalAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 订单标题
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "订单标题不能为空")]
|
||||
public string Subject { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string Body { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 超时时间
|
||||
/// </summary>
|
||||
public string TimeoutExpress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 二维码宽度
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "二维码宽度不能为空")]
|
||||
public int? QrcodeWidth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 业务参数
|
||||
/// </summary>
|
||||
public ExtendParams ExtendParams { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商户业务数据
|
||||
/// </summary>
|
||||
public Dictionary<string, object> BusinessParams { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 开票信息
|
||||
/// </summary>
|
||||
public InvoiceInfo InvoiceInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 外部买家信息
|
||||
/// </summary>
|
||||
public ExtUserInfo ExtUserInfo { get; set; }
|
||||
}
|
||||
|
||||
public class AlipayPreCreateInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 商户订单号
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "商户订单号不能为空")]
|
||||
public string OutTradeNo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 订单总金额
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "订单总金额不能为空")]
|
||||
public string TotalAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 订单标题
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "订单标题不能为空")]
|
||||
public string Subject { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 超时时间
|
||||
/// </summary>
|
||||
public string TimeoutExpress { get; set; }
|
||||
}
|
||||
|
||||
public class AlipayAuthInfoInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户Id
|
||||
/// </summary>
|
||||
|
||||
[JsonProperty("user_id")]
|
||||
[JsonPropertyName("user_id")]
|
||||
[FromQuery(Name = "user_id")]
|
||||
public string UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 授权码
|
||||
/// </summary>
|
||||
[JsonProperty("auth_code")]
|
||||
[JsonPropertyName("auth_code")]
|
||||
[FromQuery(Name = "auth_code")]
|
||||
public string AuthCode { get; set; }
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using Aop.Api.Response;
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 支付宝回调接口
|
||||
/// </summary>
|
||||
public abstract class IAlipayNotify
|
||||
{
|
||||
/// <summary>
|
||||
/// 充值回调方法
|
||||
/// </summary>
|
||||
/// <param name="type">交易类型</param>
|
||||
/// <param name="tradeNo">交易id</param>
|
||||
public abstract bool TopUpCallback(long type, long tradeNo);
|
||||
|
||||
/// <summary>
|
||||
/// 扫码回调
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="response"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool ScanCallback(long type, long userId, AlipayUserInfoShareResponse response);
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using Aop.Api;
|
||||
using Aop.Api.Domain;
|
||||
using Aop.Api.Request;
|
||||
using Aop.Api.Response;
|
||||
using Aop.Api.Util;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using NewLife.Reflection;
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 支付宝支付服务 🧩
|
||||
/// </summary>
|
||||
[ApiDescriptionSettings(Order = 240)]
|
||||
public class SysAlipayService : IDynamicApiController, ITransient
|
||||
{
|
||||
private readonly IWebHostEnvironment _webHostEnvironment;
|
||||
private readonly SysConfigService _sysConfigService;
|
||||
private readonly List<IAopClient> _alipayClientList;
|
||||
private readonly IHttpContextAccessor _httpContext;
|
||||
private readonly AlipayOptions _option;
|
||||
private readonly ISqlSugarClient _db;
|
||||
|
||||
public SysAlipayService(
|
||||
ISqlSugarClient db,
|
||||
IHttpContextAccessor httpContext,
|
||||
SysConfigService sysConfigService,
|
||||
IWebHostEnvironment webHostEnvironment,
|
||||
IOptions<AlipayOptions> alipayOptions)
|
||||
{
|
||||
_db = db;
|
||||
_httpContext = httpContext;
|
||||
_sysConfigService = sysConfigService;
|
||||
_option = alipayOptions.Value;
|
||||
_webHostEnvironment = webHostEnvironment;
|
||||
|
||||
// 初始化支付宝客户端列表
|
||||
_alipayClientList = [];
|
||||
foreach (var account in _option.AccountList) _alipayClientList.Add(_option.GetClient(account));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取授权信息 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[NonUnify]
|
||||
[AllowAnonymous]
|
||||
[DisplayName("获取授权信息")]
|
||||
[ApiDescriptionSettings(Name = "AuthInfo"), HttpGet]
|
||||
public ActionResult GetAuthInfo([FromQuery] AlipayAuthInfoInput input)
|
||||
{
|
||||
var type = input.UserId?.Split('-').FirstOrDefault().ToInt();
|
||||
var userId = input.UserId?.Split('-').LastOrDefault().ToLong();
|
||||
var account = _option.AccountList.FirstOrDefault();
|
||||
var alipayClient = _alipayClientList.First();
|
||||
|
||||
// 当前网页接口地址
|
||||
var currentUrl = $"{_option.AppAuthUrl}{_httpContext.HttpContext!.Request.Path}?userId={input.UserId}";
|
||||
if (string.IsNullOrEmpty(input.AuthCode))
|
||||
{
|
||||
// 重新授权
|
||||
var url = $"{_option.AuthUrl}?app_id={account!.AppId}&scope=auth_user&redirect_uri={currentUrl}";
|
||||
return new RedirectResult(url);
|
||||
}
|
||||
|
||||
// 组装授权请求参数
|
||||
AlipaySystemOauthTokenRequest request = new()
|
||||
{
|
||||
GrantType = AlipayConst.GrantType,
|
||||
Code = input.AuthCode
|
||||
};
|
||||
AlipaySystemOauthTokenResponse response = alipayClient.CertificateExecute(request);
|
||||
|
||||
// token换取用户信息
|
||||
AlipayUserInfoShareRequest infoShareRequest = new();
|
||||
AlipayUserInfoShareResponse info = alipayClient.CertificateExecute(infoShareRequest, response.AccessToken);
|
||||
|
||||
// 记录授权信息
|
||||
var entity = _db.Queryable<SysAlipayAuthInfo>().First(u =>
|
||||
(!string.IsNullOrWhiteSpace(u.UserId) && u.UserId == info.UserId) ||
|
||||
(!string.IsNullOrWhiteSpace(u.OpenId) && u.OpenId == info.OpenId)) ?? new();
|
||||
entity.Copy(info, excludes: [nameof(SysAlipayAuthInfo.Gender), nameof(SysAlipayAuthInfo.Age)]);
|
||||
entity.Age = int.Parse(info.Age);
|
||||
entity.Gender = info.Gender switch
|
||||
{
|
||||
"m" => GenderEnum.Male,
|
||||
"f" => GenderEnum.Female,
|
||||
_ => GenderEnum.Unknown
|
||||
};
|
||||
entity.AppId = account!.AppId;
|
||||
if (entity.Id <= 0) _db.Insertable(entity).ExecuteCommand();
|
||||
else _db.Updateable(entity).ExecuteCommand();
|
||||
|
||||
// 执行完,重定向到指定界面
|
||||
//var authPageUrl = _sysConfigService.GetConfigValueByCode<string>(ConfigConst.AlipayAuthPageUrl + type).Result;
|
||||
//return new RedirectResult(authPageUrl);
|
||||
return new RedirectResult(_option.AppAuthUrl + "/index.html");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 支付回调 🔖
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[DisplayName("支付回调")]
|
||||
[ApiDescriptionSettings(Name = "Notify"), HttpPost]
|
||||
public string Notify()
|
||||
{
|
||||
SortedDictionary<string, string> sorted = [];
|
||||
foreach (string key in _httpContext.HttpContext!.Request.Form.Keys)
|
||||
sorted.Add(key, _httpContext.HttpContext.Request.Form[key]);
|
||||
|
||||
var account = _option.AccountList.FirstOrDefault();
|
||||
string alipayPublicKey = Path.Combine(_webHostEnvironment.ContentRootPath, account!.AlipayPublicCertPath!.Replace('/', '\\').TrimStart('\\'));
|
||||
bool signVerified = AlipaySignature.RSACertCheckV1(sorted, alipayPublicKey, "UTF-8", account.SignType); // 调用SDK验证签名
|
||||
if (!signVerified) throw Oops.Oh("交易失败");
|
||||
|
||||
// 更新交易记录
|
||||
var outTradeNo = sorted.GetValueOrDefault("out_trade_no");
|
||||
var transaction = _db.Queryable<SysAlipayTransaction>().First(x => x.OutTradeNo == outTradeNo) ?? throw Oops.Oh("交易记录不存在");
|
||||
transaction.TradeNo = sorted.GetValueOrDefault("trade_no");
|
||||
transaction.TradeStatus = sorted.GetValueOrDefault("trade_status");
|
||||
transaction.FinishTime = sorted.ContainsKey("gmt_payment") ? DateTime.Parse(sorted.GetValueOrDefault("gmt_payment")) : null;
|
||||
transaction.BuyerLogonId = sorted.GetValueOrDefault("buyer_logon_id");
|
||||
transaction.BuyerUserId = sorted.GetValueOrDefault("buyer_user_id");
|
||||
transaction.SellerUserId = sorted.GetValueOrDefault("seller_id");
|
||||
transaction.Remark = sorted.GetValueOrDefault("remark");
|
||||
_db.Updateable(transaction).ExecuteCommand();
|
||||
|
||||
return "success";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统一收单下单并支付页面接口 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("统一收单下单并支付页面接口")]
|
||||
[ApiDescriptionSettings(Name = "AlipayTradePagePay"), HttpPost]
|
||||
public string AlipayTradePagePay(AlipayTradePagePayInput input)
|
||||
{
|
||||
// 创建交易记录,状态为等待支付
|
||||
var transactionRecord = new SysAlipayTransaction
|
||||
{
|
||||
AppId = _option.AccountList.First().AppId,
|
||||
OutTradeNo = input.OutTradeNo,
|
||||
TotalAmount = input.TotalAmount.ToDecimal(),
|
||||
TradeStatus = "WAIT_PAY", // 等待支付
|
||||
CreateTime = DateTime.Now,
|
||||
Subject = input.Subject,
|
||||
Body = input.Body,
|
||||
Remark = "等待用户支付"
|
||||
};
|
||||
_db.Insertable(transactionRecord).ExecuteCommand();
|
||||
|
||||
// 设置支付页面请求,并组装业务参数model,设置异步通知接收地址
|
||||
AlipayTradeWapPayRequest request = new();
|
||||
request.SetBizModel(new AlipayTradeWapPayModel()
|
||||
{
|
||||
Subject = input.Subject,
|
||||
OutTradeNo = input.OutTradeNo,
|
||||
TotalAmount = input.TotalAmount,
|
||||
Body = input.Body,
|
||||
ProductCode = "QUICK_WAP_WAY",
|
||||
TimeExpire = input.TimeoutExpress
|
||||
});
|
||||
request.SetNotifyUrl(_option.NotifyUrl);
|
||||
|
||||
var alipayClient = _alipayClientList.First();
|
||||
var response = alipayClient.SdkExecute(request);
|
||||
if (response.IsError) throw Oops.Oh(response.SubMsg);
|
||||
return $"{_option.ServerUrl}?{response.Body}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 交易预创建 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("交易预创建")]
|
||||
[ApiDescriptionSettings(Name = "AlipayPreCreate"), HttpPost]
|
||||
public string AlipayPreCreate(AlipayPreCreateInput input)
|
||||
{
|
||||
// 创建交易记录,状态为等待支付
|
||||
var transactionRecord = new SysAlipayTransaction
|
||||
{
|
||||
AppId = _option.AccountList.First().AppId,
|
||||
OutTradeNo = input.OutTradeNo,
|
||||
TotalAmount = input.TotalAmount.ToDecimal(),
|
||||
TradeStatus = "WAIT_PAY", // 等待支付
|
||||
CreateTime = DateTime.Now,
|
||||
Subject = input.Subject,
|
||||
Remark = "等待用户支付"
|
||||
};
|
||||
_db.Insertable(transactionRecord).ExecuteCommand();
|
||||
|
||||
// 设置异步通知接收地址,并组装业务参数model
|
||||
AlipayTradePrecreateRequest request = new();
|
||||
request.SetNotifyUrl(_option.NotifyUrl);
|
||||
request.SetBizModel(new AlipayTradePrecreateModel()
|
||||
{
|
||||
Subject = input.Subject,
|
||||
OutTradeNo = input.OutTradeNo,
|
||||
TotalAmount = input.TotalAmount,
|
||||
TimeoutExpress = input.TimeoutExpress
|
||||
});
|
||||
|
||||
var alipayClient = _alipayClientList.First();
|
||||
var response = alipayClient.CertificateExecute(request);
|
||||
if (response.IsError) throw Oops.Oh(response.SubMsg);
|
||||
return response.QrCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单笔转账到支付宝账户
|
||||
/// https://opendocs.alipay.com/open/62987723_alipay.fund.trans.uni.transfer
|
||||
/// </summary>
|
||||
[NonAction]
|
||||
public async Task<AlipayFundTransUniTransferResponse> Transfer(AlipayFundTransUniTransferInput input)
|
||||
{
|
||||
var account = _option.AccountList.FirstOrDefault(u => u.AppId == input.AppId) ?? throw Oops.Oh("未找到商户支付宝账号");
|
||||
var alipayClient = _option.GetClient(account);
|
||||
|
||||
// 构造请求参数以调用接口
|
||||
AlipayFundTransUniTransferRequest request = new();
|
||||
AlipayFundTransUniTransferModel model = new()
|
||||
{
|
||||
BizScene = AlipayConst.BizScene,
|
||||
ProductCode = AlipayConst.ProductCode,
|
||||
OutBizNo = input.OutBizNo, // 商家订单
|
||||
TransAmount = $"{input.TransAmount}:F2", // 订单总金额
|
||||
OrderTitle = input.OrderTitle, // 业务标题
|
||||
Remark = input.Remark, // 业务备注
|
||||
PayeeInfo = new() // 收款方信息
|
||||
{
|
||||
CertType = input.CertType?.ToString(),
|
||||
CertNo = input.CertNo,
|
||||
Identity = input.Identity,
|
||||
Name = input.Name,
|
||||
IdentityType = input.IdentityType.ToString()
|
||||
},
|
||||
BusinessParams = input.PayerShowNameUseAlias ? "{\"payer_show_name_use_alias\":\"true\"}" : null
|
||||
};
|
||||
|
||||
request.SetBizModel(model);
|
||||
var response = alipayClient.CertificateExecute(request);
|
||||
|
||||
// 保存转账记录
|
||||
await _db.Insertable(new SysAlipayTransaction
|
||||
{
|
||||
UserId = input.UserId,
|
||||
AppId = input.AppId,
|
||||
TradeNo = response.OrderId,
|
||||
OutTradeNo = input.OutBizNo,
|
||||
TotalAmount = response.Amount.ToDecimal(),
|
||||
TradeStatus = response.Code == "10000" ? "SUCCESS" : "FAILED",
|
||||
Subject = input.OrderTitle,
|
||||
ErrorInfo = response.SubMsg,
|
||||
Remark = input.Remark
|
||||
}).ExecuteCommandAsync();
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 系统域登录信息配置输入参数
|
||||
/// </summary>
|
||||
public class SysLdapInput : BasePageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 主机
|
||||
/// </summary>
|
||||
public string? Host { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 租户Id
|
||||
/// </summary>
|
||||
public long TenantId { get; set; }
|
||||
}
|
||||
|
||||
public class AddSysLdapInput : SysLdap
|
||||
{
|
||||
}
|
||||
|
||||
public class UpdateSysLdapInput : SysLdap
|
||||
{
|
||||
}
|
||||
|
||||
public class DeleteSysLdapInput : BaseIdInput
|
||||
{
|
||||
}
|
||||
|
||||
public class DetailSysLdapInput : BaseIdInput
|
||||
{
|
||||
}
|
||||
|
||||
public class SyncSysLdapInput : BaseIdInput
|
||||
{
|
||||
}
|
||||
@@ -25,7 +25,6 @@ public class SysAuthService : IDynamicApiController, ITransient
|
||||
private readonly SysUserService _sysUserService;
|
||||
private readonly SysTenantService _sysTenantService;
|
||||
private readonly SysSmsService _sysSmsService;
|
||||
private readonly SysLdapService _sysLdapService;
|
||||
private readonly ICaptcha _captcha;
|
||||
private readonly IEventPublisher _eventPublisher;
|
||||
private readonly SysCacheService _sysCacheService;
|
||||
@@ -35,7 +34,6 @@ public class SysAuthService : IDynamicApiController, ITransient
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
SysOnlineUserService sysOnlineUserService,
|
||||
SysConfigService sysConfigService,
|
||||
SysLdapService sysLdapService,
|
||||
IEventPublisher eventPublisher,
|
||||
SysSmsService sysSmsService,
|
||||
SysCacheService sysCacheService,
|
||||
@@ -57,7 +55,6 @@ public class SysAuthService : IDynamicApiController, ITransient
|
||||
_sysConfigService = sysConfigService;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_sysOnlineUserService = sysOnlineUserService;
|
||||
_sysLdapService = sysLdapService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -86,23 +83,7 @@ public class SysAuthService : IDynamicApiController, ITransient
|
||||
|
||||
// 账号是否被冻结
|
||||
if (user.Status == StatusEnum.Disable) throw Oops.Oh(ErrorCodeEnum.D1017);
|
||||
|
||||
// 是否开启域登录验证
|
||||
if (await _sysConfigService.GetConfigValue<bool>(ConfigConst.SysDomainLogin))
|
||||
{
|
||||
var userLdap = await _sysUserRep.ChangeRepository<SqlSugarRepository<SysUserLdap>>().GetFirstAsync(u => u.UserId == user.Id && u.TenantId == tenant.Id);
|
||||
if (userLdap == null)
|
||||
{
|
||||
VerifyPassword(input.Password, keyPasswordErrorTimes, passwordErrorTimes, user);
|
||||
}
|
||||
else if (!await App.GetRequiredService<SysLdapService>().AuthAccount(tenant.Id, userLdap.Account, CryptogramUtil.Decrypt(input.Password)))
|
||||
{
|
||||
_sysCacheService.Set(keyPasswordErrorTimes, ++passwordErrorTimes, TimeSpan.FromMinutes(30));
|
||||
throw Oops.Oh(ErrorCodeEnum.D1000);
|
||||
}
|
||||
}
|
||||
else
|
||||
VerifyPassword(input.Password, keyPasswordErrorTimes, passwordErrorTimes, user);
|
||||
VerifyPassword(input.Password, keyPasswordErrorTimes, passwordErrorTimes, user);
|
||||
|
||||
// 登录成功则清空密码错误次数
|
||||
_sysCacheService.Remove(keyPasswordErrorTimes);
|
||||
@@ -184,23 +165,7 @@ public class SysAuthService : IDynamicApiController, ITransient
|
||||
|
||||
var keyPasswordErrorTimes = $"{CacheConst.KeyPasswordErrorTimes}{user.Account}";
|
||||
var passwordErrorTimes = _sysCacheService.Get<int>(keyPasswordErrorTimes);
|
||||
|
||||
// 是否开启域登录验证
|
||||
if (await _sysConfigService.GetConfigValue<bool>(ConfigConst.SysDomainLogin))
|
||||
{
|
||||
var userLdap = await _sysUserRep.ChangeRepository<SqlSugarRepository<SysUserLdap>>().GetFirstAsync(u => u.UserId == user.Id && u.TenantId == user.TenantId);
|
||||
if (userLdap == null)
|
||||
{
|
||||
VerifyPassword(password, keyPasswordErrorTimes, passwordErrorTimes, user);
|
||||
}
|
||||
else if (!await _sysLdapService.AuthAccount(user.TenantId!.Value, userLdap.Account, CryptogramUtil.Decrypt(password)))
|
||||
{
|
||||
_sysCacheService.Set(keyPasswordErrorTimes, ++passwordErrorTimes, TimeSpan.FromMinutes(30));
|
||||
throw Oops.Oh(ErrorCodeEnum.D1000);
|
||||
}
|
||||
}
|
||||
else
|
||||
VerifyPassword(password, keyPasswordErrorTimes, passwordErrorTimes, user);
|
||||
VerifyPassword(password, keyPasswordErrorTimes, passwordErrorTimes, user);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,437 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using Novell.Directory.Ldap;
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
/// <summary>
|
||||
/// 系统域登录配置服务 🧩
|
||||
/// </summary>
|
||||
[ApiDescriptionSettings(Order = 496, Description = "域登录配置")]
|
||||
public class SysLdapService : IDynamicApiController, ITransient
|
||||
{
|
||||
private readonly SqlSugarRepository<SysLdap> _sysLdapRep;
|
||||
|
||||
public SysLdapService(SqlSugarRepository<SysLdap> sysLdapRep)
|
||||
{
|
||||
_sysLdapRep = sysLdapRep;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取系统域登录配置分页列表 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取系统域登录配置分页列表")]
|
||||
public async Task<SqlSugarPagedList<SysLdap>> Page(SysLdapInput input)
|
||||
{
|
||||
return await _sysLdapRep.AsQueryable()
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.Host.Contains(input.Keyword.Trim()))
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.Host), u => u.Host.Contains(input.Host.Trim()))
|
||||
.OrderBy(u => u.CreateTime, OrderByType.Desc)
|
||||
.ToPagedListAsync(input.Page, input.PageSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加系统域登录配置 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "Add"), HttpPost]
|
||||
[DisplayName("增加系统域登录配置")]
|
||||
public async Task<long> Add(AddSysLdapInput input)
|
||||
{
|
||||
var entity = input.Adapt<SysLdap>();
|
||||
entity.BindPass = CryptogramUtil.Encrypt(input.BindPass);
|
||||
await _sysLdapRep.InsertAsync(entity);
|
||||
return entity.Id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新系统域登录配置 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "Update"), HttpPost]
|
||||
[DisplayName("更新系统域登录配置")]
|
||||
public async Task Update(UpdateSysLdapInput input)
|
||||
{
|
||||
var entity = input.Adapt<SysLdap>();
|
||||
if (!string.IsNullOrEmpty(input.BindPass) && input.BindPass.Length < 32)
|
||||
{
|
||||
entity.BindPass = CryptogramUtil.Encrypt(input.BindPass); // 加密
|
||||
}
|
||||
|
||||
await _sysLdapRep.AsUpdateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除系统域登录配置 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "Delete"), HttpPost]
|
||||
[DisplayName("删除系统域登录配置")]
|
||||
public async Task Delete(DeleteSysLdapInput input)
|
||||
{
|
||||
var entity = await _sysLdapRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
|
||||
await _sysLdapRep.FakeDeleteAsync(entity); // 假删除
|
||||
//await _rep.DeleteAsync(entity); // 真删除
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取系统域登录配置详情 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取系统域登录配置详情")]
|
||||
public async Task<SysLdap> GetDetail([FromQuery] DetailSysLdapInput input)
|
||||
{
|
||||
return await _sysLdapRep.GetFirstAsync(u => u.Id == input.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取系统域登录配置列表 🔖
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取系统域登录配置列表")]
|
||||
public async Task<List<SysLdap>> GetList()
|
||||
{
|
||||
return await _sysLdapRep.AsQueryable().Select<SysLdap>().ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证账号
|
||||
/// </summary>
|
||||
/// <param name="account">域用户</param>
|
||||
/// <param name="password">密码</param>
|
||||
/// <param name="tenantId">租户</param>
|
||||
/// <returns></returns>
|
||||
[NonAction]
|
||||
public async Task<bool> AuthAccount(long? tenantId, string account, string password)
|
||||
{
|
||||
var sysLdap = await _sysLdapRep.GetFirstAsync(u => u.TenantId == tenantId) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
|
||||
var ldapConn = new LdapConnection();
|
||||
try
|
||||
{
|
||||
await ldapConn.ConnectAsync(sysLdap.Host, sysLdap.Port);
|
||||
string bindPass = CryptogramUtil.Decrypt(sysLdap.BindPass);
|
||||
await ldapConn.BindAsync(sysLdap.Version, sysLdap.BindDn, bindPass);
|
||||
var ldapSearchResults = await ldapConn.SearchAsync(sysLdap.BaseDn, LdapConnection.ScopeSub, sysLdap.AuthFilter.Replace("%s", account), null, false);
|
||||
string dn = string.Empty;
|
||||
while (await ldapSearchResults.HasMoreAsync())
|
||||
{
|
||||
var ldapEntry = await ldapSearchResults.NextAsync();
|
||||
var sAmAccountName = ldapEntry.GetAttributeSet().GetAttribute(sysLdap.BindAttrAccount)?.StringValue;
|
||||
if (string.IsNullOrEmpty(sAmAccountName)) continue;
|
||||
dn = ldapEntry.Dn;
|
||||
break;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(dn)) throw Oops.Oh(ErrorCodeEnum.D1002);
|
||||
// var attr = new LdapAttribute("userPassword", password);
|
||||
await ldapConn.BindAsync(dn, password);
|
||||
}
|
||||
catch (LdapException e)
|
||||
{
|
||||
return e.ResultCode switch
|
||||
{
|
||||
LdapException.NoSuchObject or LdapException.NoSuchAttribute => throw Oops.Oh(ErrorCodeEnum.D0009),
|
||||
LdapException.InvalidCredentials => false,
|
||||
_ => throw Oops.Oh(e.Message),
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
ldapConn.Disconnect();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步域用户 🔖
|
||||
/// </summary>
|
||||
/// <param name="tenantId"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("同步域用户")]
|
||||
[NonAction]
|
||||
public async Task<List<SysUserLdap>> SyncUserTenant(long tenantId)
|
||||
{
|
||||
var sysLdap = await _sysLdapRep.GetFirstAsync(c => c.TenantId == tenantId && c.IsDelete == false && c.Status == StatusEnum.Enable) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
|
||||
return await SysLdapService.SyncUser(sysLdap);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步域用户 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("同步域用户")]
|
||||
public async Task<List<SysUserLdap>> SyncUser(SyncSysLdapInput input)
|
||||
{
|
||||
var sysLdap = await _sysLdapRep.GetByIdAsync(input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
|
||||
return await SysLdapService.SyncUser(sysLdap);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步域用户 🔖
|
||||
/// </summary>
|
||||
/// <param name="sysLdap"></param>
|
||||
/// <returns></returns>
|
||||
private static async Task<List<SysUserLdap>> SyncUser(SysLdap sysLdap)
|
||||
{
|
||||
if (sysLdap == null) throw Oops.Oh(ErrorCodeEnum.D1002);
|
||||
var ldapConn = new LdapConnection();
|
||||
try
|
||||
{
|
||||
await ldapConn.ConnectAsync(sysLdap.Host, sysLdap.Port);
|
||||
string bindPass = CryptogramUtil.Decrypt(sysLdap.BindPass);
|
||||
await ldapConn.BindAsync(sysLdap.Version, sysLdap.BindDn, bindPass);
|
||||
var ldapSearchResults = await ldapConn.SearchAsync(sysLdap.BaseDn, LdapConnection.ScopeOne, "(objectClass=*)", null, false);
|
||||
var userLdapList = new List<SysUserLdap>();
|
||||
while (await ldapSearchResults.HasMoreAsync())
|
||||
{
|
||||
LdapEntry ldapEntry;
|
||||
try
|
||||
{
|
||||
ldapEntry = await ldapSearchResults.NextAsync();
|
||||
if (ldapEntry == null) continue;
|
||||
}
|
||||
catch (LdapException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var attrs = ldapEntry.GetAttributeSet();
|
||||
var deptCode = GetDepartmentCode(attrs, sysLdap.BindAttrCode);
|
||||
if (attrs.Count == 0 || attrs.ContainsKey("OU"))
|
||||
{
|
||||
await SearchDnLdapUser(ldapConn, sysLdap, userLdapList, ldapEntry.Dn, deptCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
var sysUserLdap = CreateSysUserLdap(attrs, sysLdap.BindAttrAccount, sysLdap.BindAttrEmployeeId, deptCode);
|
||||
sysUserLdap.Dn = ldapEntry.Dn;
|
||||
sysUserLdap.TenantId = sysLdap.TenantId;
|
||||
userLdapList.Add(sysUserLdap);
|
||||
}
|
||||
}
|
||||
|
||||
if (userLdapList.Count == 0) return null;
|
||||
|
||||
await App.GetRequiredService<SysUserLdapService>().InsertUserLdapList(sysLdap.TenantId!.Value, userLdapList);
|
||||
return userLdapList;
|
||||
}
|
||||
catch (LdapException e)
|
||||
{
|
||||
throw e.ResultCode switch
|
||||
{
|
||||
LdapException.NoSuchObject or LdapException.NoSuchAttribute => Oops.Oh(ErrorCodeEnum.D0009),
|
||||
_ => Oops.Oh(e.Message),
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
ldapConn.Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取部门代码
|
||||
/// </summary>
|
||||
/// <param name="attrs"></param>
|
||||
/// <param name="bindAttrCode"></param>
|
||||
/// <returns></returns>
|
||||
private static string GetDepartmentCode(LdapAttributeSet attrs, string bindAttrCode)
|
||||
{
|
||||
return bindAttrCode == "objectGUID"
|
||||
? new Guid(attrs.GetAttribute(bindAttrCode)?.ByteValue!).ToString()
|
||||
: attrs.GetAttribute(bindAttrCode)?.StringValue ?? "0";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建同步对象
|
||||
/// </summary>
|
||||
/// <param name="attrs"></param>
|
||||
/// <param name="bindAttrAccount"></param>
|
||||
/// <param name="bindAttrEmployeeId"></param>
|
||||
/// <param name="deptCode"></param>
|
||||
/// <returns></returns>
|
||||
private static SysUserLdap CreateSysUserLdap(LdapAttributeSet attrs, string bindAttrAccount, string bindAttrEmployeeId, string deptCode)
|
||||
{
|
||||
var userLdap = new SysUserLdap
|
||||
{
|
||||
Account = attrs.ContainsKey(bindAttrAccount) ? attrs.GetAttribute(bindAttrAccount)?.StringValue : null,
|
||||
EmployeeId = attrs.ContainsKey(bindAttrEmployeeId) ? attrs.GetAttribute(bindAttrEmployeeId)?.StringValue : null,
|
||||
DeptCode = deptCode,
|
||||
UserName = attrs.ContainsKey("name") ? attrs.GetAttribute("name")?.StringValue : null,
|
||||
Mail = attrs.ContainsKey("mail") ? attrs.GetAttribute("mail")?.StringValue : null
|
||||
};
|
||||
var pwdLastSet = attrs.ContainsKey("pwdLastSet") ? attrs.GetAttribute("pwdLastSet")?.StringValue : null;
|
||||
if (pwdLastSet != null && !pwdLastSet.Equals("0")) userLdap.PwdLastSetTime = DateTime.FromFileTime(Convert.ToInt64(pwdLastSet));
|
||||
var userAccountControl = attrs.ContainsKey("userAccountControl") ? attrs.GetAttribute("userAccountControl")?.StringValue : null;
|
||||
if ((Convert.ToInt32(userAccountControl) & 0x2) == 0x2) // 检查账户是否已过期(通过检查userAccountControl属性的特定位)
|
||||
userLdap.AccountExpiresFlag = true;
|
||||
if ((Convert.ToInt32(userAccountControl) & 0x10000) == 0x10000) // 检查账户密码设置是否永不过期
|
||||
userLdap.DontExpiresFlag = true;
|
||||
return userLdap;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 遍历查询域用户
|
||||
/// </summary>
|
||||
/// <param name="ldapConn"></param>
|
||||
/// <param name="sysLdap"></param>
|
||||
/// <param name="userLdapList"></param>
|
||||
/// <param name="baseDn"></param>
|
||||
/// <param name="deptCode"></param>
|
||||
private static async Task SearchDnLdapUser(LdapConnection ldapConn, SysLdap sysLdap, List<SysUserLdap> userLdapList, string baseDn, string deptCode)
|
||||
{
|
||||
var ldapSearchResults = await ldapConn.SearchAsync(baseDn, LdapConnection.ScopeOne, "(objectClass=*)", null, false);
|
||||
while (await ldapSearchResults.HasMoreAsync())
|
||||
{
|
||||
LdapEntry ldapEntry;
|
||||
try
|
||||
{
|
||||
ldapEntry = await ldapSearchResults.NextAsync();
|
||||
if (ldapEntry == null) continue;
|
||||
}
|
||||
catch (LdapException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var attrs = ldapEntry.GetAttributeSet();
|
||||
deptCode = GetDepartmentCode(attrs, sysLdap.BindAttrCode);
|
||||
|
||||
if (attrs.Count == 0 || attrs.ContainsKey("OU"))
|
||||
await SearchDnLdapUser(ldapConn, sysLdap, userLdapList, ldapEntry.Dn, deptCode);
|
||||
else
|
||||
{
|
||||
var sysUserLdap = CreateSysUserLdap(attrs, sysLdap.BindAttrAccount, sysLdap.BindAttrEmployeeId, deptCode);
|
||||
sysUserLdap.Dn = ldapEntry.Dn;
|
||||
sysUserLdap.TenantId = sysLdap.TenantId;
|
||||
if (string.IsNullOrEmpty(sysUserLdap.EmployeeId)) continue;
|
||||
userLdapList.Add(sysUserLdap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步域组织 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("同步域组织")]
|
||||
public async Task SyncDept(SyncSysLdapInput input)
|
||||
{
|
||||
var sysLdap = await _sysLdapRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
|
||||
var ldapConn = new LdapConnection();
|
||||
try
|
||||
{
|
||||
await ldapConn.ConnectAsync(sysLdap.Host, sysLdap.Port);
|
||||
string bindPass = CryptogramUtil.Decrypt(sysLdap.BindPass);
|
||||
await ldapConn.BindAsync(sysLdap.Version, sysLdap.BindDn, bindPass);
|
||||
var ldapSearchResults = await ldapConn.SearchAsync(sysLdap.BaseDn, LdapConnection.ScopeOne, "(objectClass=*)", null, false);
|
||||
var orgList = new List<SysOrg>();
|
||||
while (await ldapSearchResults.HasMoreAsync())
|
||||
{
|
||||
LdapEntry ldapEntry;
|
||||
try
|
||||
{
|
||||
ldapEntry = await ldapSearchResults.NextAsync();
|
||||
if (ldapEntry == null) continue;
|
||||
}
|
||||
catch (LdapException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var attrs = ldapEntry.GetAttributeSet();
|
||||
if (attrs.Count != 0 && !attrs.ContainsKey("OU")) continue;
|
||||
|
||||
var sysOrg = CreateSysOrg(attrs, sysLdap, orgList, new SysOrg { Id = 0, Level = 0 });
|
||||
orgList.Add(sysOrg);
|
||||
|
||||
await SearchDnLdapDept(ldapConn, sysLdap, orgList, ldapEntry.Dn, sysOrg);
|
||||
}
|
||||
|
||||
if (orgList.Count == 0)
|
||||
return;
|
||||
|
||||
await App.GetRequiredService<SysOrgService>().BatchAddOrgs(orgList);
|
||||
}
|
||||
catch (LdapException e)
|
||||
{
|
||||
throw e.ResultCode switch
|
||||
{
|
||||
LdapException.NoSuchObject or LdapException.NoSuchAttribute => Oops.Oh(ErrorCodeEnum.D0009),
|
||||
_ => Oops.Oh(e.Message),
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
ldapConn.Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 遍历查询域用户
|
||||
/// </summary>
|
||||
/// <param name="ldapConn"></param>
|
||||
/// <param name="sysLdap"></param>
|
||||
/// <param name="listOrgs"></param>
|
||||
/// <param name="baseDn"></param>
|
||||
/// <param name="org"></param>
|
||||
private static async Task SearchDnLdapDept(LdapConnection ldapConn, SysLdap sysLdap, List<SysOrg> listOrgs, string baseDn, SysOrg org)
|
||||
{
|
||||
var ldapSearchResults = await ldapConn.SearchAsync(baseDn, LdapConnection.ScopeOne, "(objectClass=*)", null, false);
|
||||
while (await ldapSearchResults.HasMoreAsync())
|
||||
{
|
||||
LdapEntry ldapEntry;
|
||||
try
|
||||
{
|
||||
ldapEntry = await ldapSearchResults.NextAsync();
|
||||
if (ldapEntry == null) continue;
|
||||
}
|
||||
catch (LdapException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var attrs = ldapEntry.GetAttributeSet();
|
||||
if (attrs.Count != 0 && !attrs.ContainsKey("OU")) continue;
|
||||
|
||||
var sysOrg = CreateSysOrg(attrs, sysLdap, listOrgs, org);
|
||||
listOrgs.Add(sysOrg);
|
||||
|
||||
await SearchDnLdapDept(ldapConn, sysLdap, listOrgs, ldapEntry.Dn, sysOrg);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建架构对象
|
||||
/// </summary>
|
||||
/// <param name="attrs"></param>
|
||||
/// <param name="sysLdap"></param>
|
||||
/// <param name="listOrgs"></param>
|
||||
/// <param name="org"></param>
|
||||
/// <returns></returns>
|
||||
private static SysOrg CreateSysOrg(LdapAttributeSet attrs, SysLdap sysLdap, List<SysOrg> listOrgs, SysOrg org)
|
||||
{
|
||||
return new SysOrg
|
||||
{
|
||||
Pid = org.Id,
|
||||
Id = YitIdHelper.NextId(),
|
||||
Code = attrs.ContainsKey(sysLdap.BindAttrCode) ? new Guid(attrs.GetAttribute(sysLdap.BindAttrCode)?.ByteValue).ToString() : null,
|
||||
Level = org.Level + 1,
|
||||
Name = attrs.ContainsKey(sysLdap.BindAttrAccount) ? attrs.GetAttribute(sysLdap.BindAttrAccount)?.StringValue : null,
|
||||
OrderNo = listOrgs.Count + 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 自定义模板引擎
|
||||
/// </summary>
|
||||
public class CustomViewEngine : ViewEngineModel
|
||||
{
|
||||
/// <summary>
|
||||
/// 库定位器
|
||||
/// </summary>
|
||||
public string ConfigId { get; set; } = SqlSugarConst.MainConfigId;
|
||||
|
||||
public string AuthorName { get; set; }
|
||||
|
||||
public string BusName { get; set; }
|
||||
|
||||
public string NameSpace { get; set; }
|
||||
|
||||
public string ClassName { get; set; }
|
||||
|
||||
public string LowerClassName { get; set; }
|
||||
|
||||
public string ProjectLastName { get; set; }
|
||||
|
||||
public string PagePath { get; set; } = "main";
|
||||
|
||||
public string PrintType { get; set; }
|
||||
|
||||
public string PrintName { get; set; }
|
||||
|
||||
public bool HasLikeQuery { get; set; }
|
||||
|
||||
public bool HasJoinTable { get; set; }
|
||||
|
||||
public bool HasEnumField { get; set; }
|
||||
|
||||
public bool HasDictField { get; set; }
|
||||
|
||||
public bool HasConstField { get; set; }
|
||||
|
||||
public bool HasSetStatus => TableField.Any(IsStatus);
|
||||
|
||||
public List<CodeGenConfig> TableField { get; set; }
|
||||
|
||||
public List<CodeGenConfig> ImportFieldList { get; set; }
|
||||
|
||||
public List<CodeGenConfig> UploadFieldList { get; set; }
|
||||
|
||||
public List<CodeGenConfig> QueryWhetherList { get; set; }
|
||||
|
||||
public List<CodeGenConfig> ApiTreeFieldList { get; set; }
|
||||
|
||||
public List<CodeGenConfig> DropdownFieldList { get; set; }
|
||||
|
||||
public List<CodeGenConfig> AddUpdateFieldList { get; set; }
|
||||
|
||||
public List<CodeGenConfig> PrimaryKeyFieldList { get; set; }
|
||||
|
||||
public List<TableUniqueConfigItem> TableUniqueConfigList { get; set; }
|
||||
|
||||
public List<CodeGenConfig> IgnoreUpdateFieldList => TableField.Where(u => u.WhetherAddUpdate == "N" && u.ColumnKey != "True" && u.WhetherCommon != "Y").ToList();
|
||||
|
||||
/// <summary>
|
||||
/// 格式化主键查询条件
|
||||
/// 例: PrimaryKeysFormat(" || ", "u.{0} == input.{0}")
|
||||
/// 单主键返回 u.Id == input.Id
|
||||
/// 组合主键返回 u.Id == input.Id || u.FkId == input.FkId
|
||||
/// </summary>
|
||||
/// <param name="separator">分隔符</param>
|
||||
/// <param name="format">模板字符串</param>
|
||||
/// <param name="lowerFirstLetter">字段首字母小写</param>
|
||||
/// <returns></returns>
|
||||
public string PrimaryKeysFormat(string separator, string format, bool lowerFirstLetter = false) => string.Join(separator, PrimaryKeyFieldList.Select(u => string.Format(format, lowerFirstLetter ? u.LowerPropertyName : u.PropertyName)));
|
||||
|
||||
/// <summary>
|
||||
/// 注入的服务
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Dictionary<string, string> InjectServiceMap
|
||||
{
|
||||
get
|
||||
{
|
||||
var injectMap = new Dictionary<string, string>();
|
||||
if (UploadFieldList.Count > 0) injectMap.Add(nameof(SysFileService), ToLowerFirstLetter(nameof(SysFileService)));
|
||||
if (DropdownFieldList.Count > 0 || ImportFieldList.Count > 0) injectMap.Add(nameof(ISqlSugarClient), ToLowerFirstLetter(nameof(ISqlSugarClient).TrimStart('I')));
|
||||
if (ImportFieldList.Any(c => c.EffectType == "DictSelector")) injectMap.Add(nameof(SysDictTypeService), ToLowerFirstLetter(nameof(SysDictTypeService)));
|
||||
return injectMap;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 服务构造参数
|
||||
/// </summary>
|
||||
public string InjectServiceArgs => InjectServiceMap.Count > 0 ? ", " + string.Join(", ", InjectServiceMap.Select(kv => $"{kv.Key} {kv.Value}")) : "";
|
||||
|
||||
/// <summary>
|
||||
/// 默认值列表
|
||||
/// </summary>
|
||||
public List<CodeGenConfig> DefaultValueList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 判断字段是否为状态字段
|
||||
/// </summary>
|
||||
/// <param name="column"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsStatus(CodeGenConfig column) => column.NetType == nameof(StatusEnum);
|
||||
|
||||
/// <summary>
|
||||
/// 获取首字母小写字符串
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
public string ToLowerFirstLetter(string text) => string.IsNullOrWhiteSpace(text) ? text : text[..1].ToLower() + text[1..];
|
||||
|
||||
/// <summary>
|
||||
/// 将基本字段类型转为可空类型
|
||||
/// </summary>
|
||||
/// <param name="netType"></param>
|
||||
/// <returns></returns>
|
||||
public string GetNullableNetType(string netType) => Regex.IsMatch(netType, "(.*?Enum|bool|char|int|long|double|float|decimal)[?]?") ? netType.TrimEnd('?') + "?" : netType;
|
||||
|
||||
/// <summary>
|
||||
/// 获取前端表格列定义的属性
|
||||
/// </summary>
|
||||
/// <param name="column"></param>
|
||||
/// <returns></returns>
|
||||
public string GetElTableColumnCustomProperty(CodeGenConfig column)
|
||||
{
|
||||
var content = $"prop='{column.LowerPropertyName}' label='{column.ColumnComment}'";
|
||||
if (IsStatus(column)) content += $" v-auth=\"'{LowerClassName}:setStatus'\"";
|
||||
if (column.WhetherSortable == "Y") content += " sortable='custom'";
|
||||
return content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置默认值
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetAddDefaultValue()
|
||||
{
|
||||
var content = "";
|
||||
if (DefaultValueList.Count == 0)
|
||||
{
|
||||
var status = TableField.FirstOrDefault(IsStatus);
|
||||
var orderNo = TableField.FirstOrDefault(c => c.NetType.TrimEnd('?') == "int" && c.PropertyName == nameof(SysUser.OrderNo));
|
||||
if (status != null) content += $"{status.LowerPropertyName}: {(int)StatusEnum.Enable},";
|
||||
if (orderNo != null) content += $"{orderNo.LowerPropertyName}: 100,";
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var item in DefaultValueList)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(item.DefaultValue))
|
||||
{
|
||||
switch (item.EffectType)
|
||||
{
|
||||
case "InputNumber":
|
||||
case "EnumSelector"://枚举和数字框,通过正则提取 数字:如 ('0')
|
||||
content += $"{item.LowerPropertyName}: {Regex.Match(item.DefaultValue, @"\d+").Value},";
|
||||
break;
|
||||
|
||||
case "Switch":
|
||||
content += $"{item.LowerPropertyName}: {(item.DefaultValue == "1" ? true.ToString().ToLower() : false.ToString().ToLower())},";
|
||||
break;
|
||||
|
||||
case "DatePicker"://忽略适配日期格式
|
||||
break;
|
||||
|
||||
default:
|
||||
content += $"{item.LowerPropertyName}: \"{item.DefaultValue}\",";// 如果是字符串 DefaultValue=('男')
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
}
|
||||
@@ -1,234 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 代码生成详细配置参数
|
||||
/// </summary>
|
||||
public class CodeGenConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// 主键Id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 代码生成主表ID
|
||||
/// </summary>
|
||||
public long CodeGenId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据库字段名
|
||||
/// </summary>
|
||||
public string ColumnName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 主外键
|
||||
/// </summary>
|
||||
public string ColumnKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 实体属性名
|
||||
/// </summary>
|
||||
public string PropertyName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 字段数据长度
|
||||
/// </summary>
|
||||
public int ColumnLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据库字段名(首字母小写)
|
||||
/// </summary>
|
||||
public string LowerPropertyName => string.IsNullOrWhiteSpace(PropertyName) ? null : PropertyName[..1].ToLower() + PropertyName[1..];
|
||||
|
||||
/// <summary>
|
||||
/// 字段描述
|
||||
/// </summary>
|
||||
public string ColumnComment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// .NET类型
|
||||
/// </summary>
|
||||
public string NetType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据库中类型(物理类型)
|
||||
/// </summary>
|
||||
public string DataType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 字段数据默认值
|
||||
/// </summary>
|
||||
public string DefaultValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 可空.NET类型
|
||||
/// </summary>
|
||||
public string NullableNetType => Regex.IsMatch(NetType ?? "", "(.*?Enum|bool|char|int|long|double|float|decimal)[?]?") ? NetType.TrimEnd('?') + "?" : NetType;
|
||||
|
||||
/// <summary>
|
||||
/// 作用类型(字典)
|
||||
/// </summary>
|
||||
public string EffectType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 外键库标识
|
||||
/// </summary>
|
||||
public string FkConfigId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 外键实体名称
|
||||
/// </summary>
|
||||
public string FkEntityName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 外键表名称
|
||||
/// </summary>
|
||||
public string FkTableName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 外键实体名称(首字母小写)
|
||||
/// </summary>
|
||||
public string LowerFkEntityName => string.IsNullOrWhiteSpace(FkEntityName) ? null : FkEntityName[..1].ToLower() + FkEntityName[1..];
|
||||
|
||||
/// <summary>
|
||||
/// 外键链接字段
|
||||
/// </summary>
|
||||
public string FkLinkColumnName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 外键显示字段
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public string FkDisplayColumns { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 外键显示字段
|
||||
/// </summary>
|
||||
public List<string> FkDisplayColumnList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 外键显示字段(首字母小写)
|
||||
/// </summary>
|
||||
public List<string> LowerFkDisplayColumnsList => FkDisplayColumnList?.Select(name => name[..1].ToLower() + name[1..]).ToList();
|
||||
|
||||
/// <summary>
|
||||
/// 外键显示字段.NET类型
|
||||
/// </summary>
|
||||
public string FkColumnNetType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 父级字段
|
||||
/// </summary>
|
||||
public string PidColumn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 字典code
|
||||
/// </summary>
|
||||
public string DictTypeCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 查询方式
|
||||
/// </summary>
|
||||
public string QueryType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否是查询条件
|
||||
/// </summary>
|
||||
public string WhetherQuery { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 列表是否缩进(字典)
|
||||
/// </summary>
|
||||
public string WhetherRetract { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否必填(字典)
|
||||
/// </summary>
|
||||
public string WhetherRequired { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否可排序(字典)
|
||||
/// </summary>
|
||||
public string WhetherSortable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 列表显示
|
||||
/// </summary>
|
||||
public string WhetherTable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 增改
|
||||
/// </summary>
|
||||
public string WhetherAddUpdate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 导入
|
||||
/// </summary>
|
||||
public string WhetherImport { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否是通用字段
|
||||
/// </summary>
|
||||
public string WhetherCommon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序
|
||||
/// </summary>
|
||||
public int OrderNo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否是选择器控件
|
||||
/// </summary>
|
||||
public bool IsSelectorEffectType => Regex.IsMatch(EffectType ?? "", "Selector$|ForeignKey", RegexOptions.IgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// 去掉尾部Id的属性名
|
||||
/// </summary>
|
||||
public string PropertyNameTrimEndId => PropertyName.TrimEnd("Id");
|
||||
|
||||
/// <summary>
|
||||
/// 去掉尾部Id的属性名
|
||||
/// </summary>
|
||||
public string LowerPropertyNameTrimEndId => LowerPropertyName.TrimEnd("Id");
|
||||
|
||||
/// <summary>
|
||||
/// 扩展属性名称
|
||||
/// </summary>
|
||||
public string ExtendedPropertyName => EffectType switch
|
||||
{
|
||||
"ForeignKey" => $"{PropertyName.TrimEnd("Id")}FkDisplayName",
|
||||
"ApiTreeSelector" => $"{PropertyName.TrimEnd("Id")}DisplayName",
|
||||
"DictSelector" => $"{PropertyName.TrimEnd("Id")}DictLabel",
|
||||
"Upload" => $"{PropertyName.TrimEnd("Id")}Attachment",
|
||||
"Upload_SingleFile" => $"{PropertyName.TrimEnd("Id")}Attachment",
|
||||
_ => PropertyName
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 首字母小写的扩展属性名称
|
||||
/// </summary>
|
||||
public string LowerExtendedPropertyName
|
||||
{
|
||||
get
|
||||
{
|
||||
var displayPropertyName = ExtendedPropertyName;
|
||||
if (string.IsNullOrWhiteSpace(displayPropertyName)) return null;
|
||||
return displayPropertyName[..1].ToLower() + displayPropertyName[1..];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取外键显示值语句
|
||||
/// </summary>
|
||||
/// <param name="tableAlias">表别名</param>
|
||||
/// <param name="separator">多字段时的连接符</param>
|
||||
/// <returns></returns>
|
||||
public string GetDisplayColumn(string tableAlias, string separator = "-") => "$\"" + string.Join(separator, FkDisplayColumnList?.Select(name => $"{{{tableAlias}.{name}}}") ?? new List<string>()) + "\"";
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 代码生成参数类
|
||||
/// </summary>
|
||||
public class CodeGenInput : BasePageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 作者姓名
|
||||
/// </summary>
|
||||
public virtual string AuthorName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 类名
|
||||
/// </summary>
|
||||
public virtual string ClassName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否移除表前缀
|
||||
/// </summary>
|
||||
public virtual string TablePrefix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 库定位器名
|
||||
/// </summary>
|
||||
public virtual string ConfigId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据库名(保留字段)
|
||||
/// </summary>
|
||||
public virtual string DbName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据库类型
|
||||
/// </summary>
|
||||
public virtual string DbType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据库链接
|
||||
/// </summary>
|
||||
public virtual string ConnectionString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 生成方式
|
||||
/// </summary>
|
||||
public virtual string GenerateType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据库表名
|
||||
/// </summary>
|
||||
public virtual string TableName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 命名空间
|
||||
/// </summary>
|
||||
public virtual string NameSpace { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 业务名(业务代码包名称)
|
||||
/// </summary>
|
||||
public virtual string BusName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 功能名(数据库表名称)
|
||||
/// </summary>
|
||||
public virtual string TableComment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 表唯一字段
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public virtual string TableUniqueConfig { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 表唯一字段列表
|
||||
/// </summary>
|
||||
public virtual List<TableUniqueConfigItem> TableUniqueList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 菜单应用分类(应用编码)
|
||||
/// </summary>
|
||||
public virtual string MenuApplication { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否生成菜单
|
||||
/// </summary>
|
||||
public virtual bool GenerateMenu { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 菜单父级
|
||||
/// </summary>
|
||||
public virtual long? MenuPid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 菜单图标
|
||||
/// </summary>
|
||||
public virtual string MenuIcon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 页面目录
|
||||
/// </summary>
|
||||
public virtual string PagePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 支持打印类型
|
||||
/// </summary>
|
||||
public virtual string PrintType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 打印模版名称
|
||||
/// </summary>
|
||||
public virtual string PrintName { get; set; }
|
||||
}
|
||||
|
||||
public class AddCodeGenInput : CodeGenInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据库表名
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "数据库表名不能为空")]
|
||||
public override string TableName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 业务名(业务代码包名称)
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "业务名不能为空")]
|
||||
public override string BusName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 命名空间
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "命名空间不能为空")]
|
||||
public override string NameSpace { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 作者姓名
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "作者姓名不能为空")]
|
||||
public override string AuthorName { get; set; }
|
||||
|
||||
///// <summary>
|
||||
///// 类名
|
||||
///// </summary>
|
||||
//[Required(ErrorMessage = "类名不能为空")]
|
||||
//public override string ClassName { get; set; }
|
||||
|
||||
///// <summary>
|
||||
///// 是否移除表前缀
|
||||
///// </summary>
|
||||
//[Required(ErrorMessage = "是否移除表前缀不能为空")]
|
||||
//public override string TablePrefix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 生成方式
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "生成方式不能为空")]
|
||||
public override string GenerateType { get; set; }
|
||||
|
||||
///// <summary>
|
||||
///// 功能名(数据库表名称)
|
||||
///// </summary>
|
||||
//[Required(ErrorMessage = "数据库表名不能为空")]
|
||||
//public override string TableComment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否生成菜单
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "是否生成菜单不能为空")]
|
||||
public override bool GenerateMenu { get; set; }
|
||||
}
|
||||
|
||||
public class DeleteCodeGenInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 代码生成器Id
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "代码生成器Id不能为空")]
|
||||
public long Id { get; set; }
|
||||
}
|
||||
|
||||
public class UpdateCodeGenInput : AddCodeGenInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 代码生成器Id
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "代码生成器Id不能为空")]
|
||||
public long Id { get; set; }
|
||||
}
|
||||
|
||||
public class QueryCodeGenInput : DeleteCodeGenInput
|
||||
{
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 代码生成参数类
|
||||
/// </summary>
|
||||
public class CodeGenOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 代码生成器Id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 作者姓名
|
||||
/// </summary>
|
||||
public string AuthorName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 类名
|
||||
/// </summary>
|
||||
public string ClassName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否移除表前缀
|
||||
/// </summary>
|
||||
public string TablePrefix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 生成方式
|
||||
/// </summary>
|
||||
public string GenerateType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据库表名
|
||||
/// </summary>
|
||||
public string TableName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 包名
|
||||
/// </summary>
|
||||
public string PackageName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 业务名(业务代码包名称)
|
||||
/// </summary>
|
||||
public string BusName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 功能名(数据库表名称)
|
||||
/// </summary>
|
||||
public string TableComment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 菜单应用分类(应用编码)
|
||||
/// </summary>
|
||||
public string MenuApplication { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否生成菜单
|
||||
/// </summary>
|
||||
public bool GenerateMenu { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 菜单父级
|
||||
/// </summary>
|
||||
public long? MenuPid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 支持打印类型
|
||||
/// </summary>
|
||||
public string PrintType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 打印模版名称
|
||||
/// </summary>
|
||||
public string PrintName { get; set; }
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 数据库表列
|
||||
/// </summary>
|
||||
public class ColumnOuput
|
||||
{
|
||||
/// <summary>
|
||||
/// 字段名
|
||||
/// </summary>
|
||||
public string ColumnName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 实体的Property名
|
||||
/// </summary>
|
||||
public string PropertyName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 字段数据长度
|
||||
/// </summary>
|
||||
public int ColumnLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据库中类型
|
||||
/// </summary>
|
||||
public string DataType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 字段数据默认值
|
||||
/// </summary>
|
||||
public string DefaultValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为主键
|
||||
/// </summary>
|
||||
public bool IsPrimarykey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否允许为空
|
||||
/// </summary>
|
||||
public bool IsNullable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// .NET字段类型
|
||||
/// </summary>
|
||||
public string NetType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 字典编码
|
||||
/// </summary>
|
||||
public string DictTypeCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 字段描述
|
||||
/// </summary>
|
||||
public string ColumnComment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 主外键
|
||||
/// </summary>
|
||||
public string ColumnKey { get; set; }
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 数据库
|
||||
/// </summary>
|
||||
public class DatabaseOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 库定位器名
|
||||
/// </summary>
|
||||
public string ConfigId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 库名
|
||||
/// </summary>
|
||||
public string DbNickName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据库类型
|
||||
/// </summary>
|
||||
public SqlSugar.DbType DbType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据库连接字符串
|
||||
/// </summary>
|
||||
public string ConnectionString { get; set; }
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 数据库表
|
||||
/// </summary>
|
||||
public class TableOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 库定位器名
|
||||
/// </summary>
|
||||
public string ConfigId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 表名(字母形式的)
|
||||
/// </summary>
|
||||
public string TableName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 实体名称
|
||||
/// </summary>
|
||||
public string EntityName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public string CreateTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新时间
|
||||
/// </summary>
|
||||
public string UpdateTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 表名称描述(功能名)
|
||||
/// </summary>
|
||||
public string TableComment { get; set; }
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 表唯一配置项
|
||||
/// </summary>
|
||||
public class TableUniqueConfigItem
|
||||
{
|
||||
/// <summary>
|
||||
/// 字段列表
|
||||
/// </summary>
|
||||
public List<string> Columns { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 描述信息
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 格式化查询条件
|
||||
/// </summary>
|
||||
/// <param name="separator">分隔符</param>
|
||||
/// <param name="format">模板字符串</param>
|
||||
/// <returns></returns>
|
||||
public string Format(string separator, string format) => string.Join(separator, Columns.Select(name => string.Format(format, name)));
|
||||
}
|
||||
@@ -1,256 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 系统代码生成配置服务 🧩
|
||||
/// </summary>
|
||||
[ApiDescriptionSettings(Order = 260)]
|
||||
public class SysCodeGenConfigService : IDynamicApiController, ITransient
|
||||
{
|
||||
private readonly ISqlSugarClient _db;
|
||||
|
||||
public SysCodeGenConfigService(ISqlSugarClient db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取代码生成配置列表 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取代码生成配置列表")]
|
||||
public async Task<List<CodeGenConfig>> GetList([FromQuery] CodeGenConfig input)
|
||||
{
|
||||
return await _db.Queryable<SysCodeGenConfig>()
|
||||
.Where(u => u.CodeGenId == input.CodeGenId)
|
||||
.Select<CodeGenConfig>()
|
||||
.Mapper(u =>
|
||||
{
|
||||
u.NetType = (u.EffectType == "EnumSelector" ? u.DictTypeCode : u.NetType);
|
||||
u.FkDisplayColumnList = u.FkDisplayColumns?.Split(",").ToList();
|
||||
})
|
||||
.OrderBy(u => new { u.OrderNo, u.Id })
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新代码生成配置 🔖
|
||||
/// </summary>
|
||||
/// <param name="inputList"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "Update"), HttpPost]
|
||||
[DisplayName("更新代码生成配置")]
|
||||
public async Task UpdateCodeGenConfig(List<CodeGenConfig> inputList)
|
||||
{
|
||||
if (inputList == null || inputList.Count < 1) return;
|
||||
inputList.ForEach(e =>
|
||||
{
|
||||
e.FkDisplayColumns = e.FkDisplayColumnList?.Count > 0 ? string.Join(",", e.FkDisplayColumnList) : null;
|
||||
});
|
||||
await _db.Updateable(inputList.Adapt<List<SysCodeGenConfig>>())
|
||||
.IgnoreColumns(u => new { u.ColumnLength, u.ColumnName, u.PropertyName })
|
||||
.ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除代码生成配置
|
||||
/// </summary>
|
||||
/// <param name="codeGenId"></param>
|
||||
/// <returns></returns>
|
||||
[NonAction]
|
||||
public async Task DeleteCodeGenConfig(long codeGenId)
|
||||
{
|
||||
await _db.Deleteable<SysCodeGenConfig>().Where(u => u.CodeGenId == codeGenId).ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取代码生成配置详情 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取代码生成配置详情")]
|
||||
public async Task<SysCodeGenConfig> GetDetail([FromQuery] CodeGenConfig input)
|
||||
{
|
||||
return await _db.Queryable<SysCodeGenConfig>().FirstAsync(u => u.Id == input.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量增加代码生成配置
|
||||
/// </summary>
|
||||
/// <param name="tableColumnOutputList"></param>
|
||||
/// <param name="codeGenerate"></param>
|
||||
[NonAction]
|
||||
public void AddList(List<ColumnOuput> tableColumnOutputList, SysCodeGen codeGenerate)
|
||||
{
|
||||
if (tableColumnOutputList == null) return;
|
||||
|
||||
var codeGenConfigs = new List<SysCodeGenConfig>();
|
||||
var orderNo = 100;
|
||||
foreach (var tableColumn in tableColumnOutputList)
|
||||
{
|
||||
var codeGenConfig = new SysCodeGenConfig();
|
||||
|
||||
var yesOrNo = YesNoEnum.Y.ToString();
|
||||
if (Convert.ToBoolean(tableColumn.ColumnKey)) yesOrNo = YesNoEnum.N.ToString();
|
||||
|
||||
if (CodeGenUtil.IsCommonColumn(tableColumn.PropertyName))
|
||||
{
|
||||
codeGenConfig.WhetherCommon = YesNoEnum.Y.ToString();
|
||||
yesOrNo = YesNoEnum.N.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
codeGenConfig.WhetherCommon = YesNoEnum.N.ToString();
|
||||
}
|
||||
|
||||
codeGenConfig.CodeGenId = codeGenerate.Id;
|
||||
codeGenConfig.ColumnName = tableColumn.ColumnName; // 字段名
|
||||
codeGenConfig.PropertyName = tableColumn.PropertyName;// 实体属性名
|
||||
codeGenConfig.ColumnLength = tableColumn.ColumnLength;// 长度
|
||||
codeGenConfig.ColumnComment = tableColumn.ColumnComment;
|
||||
codeGenConfig.NetType = tableColumn.NetType;
|
||||
codeGenConfig.DefaultValue = tableColumn.DefaultValue;
|
||||
codeGenConfig.WhetherRetract = YesNoEnum.N.ToString();
|
||||
|
||||
// 生成代码时,主键并不是必要输入项,故一定要排除主键字段
|
||||
codeGenConfig.WhetherRequired = (tableColumn.IsNullable || tableColumn.IsPrimarykey) ? YesNoEnum.N.ToString() : YesNoEnum.Y.ToString();
|
||||
codeGenConfig.WhetherQuery = yesOrNo;
|
||||
codeGenConfig.WhetherImport = yesOrNo;
|
||||
codeGenConfig.WhetherAddUpdate = yesOrNo;
|
||||
codeGenConfig.WhetherTable = yesOrNo;
|
||||
|
||||
codeGenConfig.ColumnKey = tableColumn.ColumnKey;
|
||||
|
||||
codeGenConfig.DataType = tableColumn.DataType;
|
||||
codeGenConfig.EffectType = CodeGenUtil.DataTypeToEff(codeGenConfig.NetType);
|
||||
codeGenConfig.QueryType = GetDefaultQueryType(codeGenConfig); // QueryTypeEnum.eq.ToString();
|
||||
codeGenConfig.OrderNo = orderNo;
|
||||
codeGenConfigs.Add(codeGenConfig);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tableColumn.DictTypeCode))
|
||||
{
|
||||
codeGenConfig.QueryType = "==";
|
||||
codeGenConfig.DictTypeCode = tableColumn.DictTypeCode;
|
||||
codeGenConfig.EffectType = tableColumn.DictTypeCode.EndsWith("Enum") ? "EnumSelector" : "DictSelector";
|
||||
}
|
||||
|
||||
orderNo += 10; // 每个配置排序间隔10
|
||||
}
|
||||
// 多库代码生成---这里要切回主库
|
||||
var provider = _db.AsTenant().GetConnectionScope(SqlSugarConst.MainConfigId);
|
||||
provider.Insertable(codeGenConfigs).ExecuteCommand();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量更新代码字段:先删除再新增,会保留历史字段操作类型
|
||||
/// </summary>
|
||||
/// <param name="tableColumnOutputList"></param>
|
||||
/// <param name="codeGenId"></param>
|
||||
[NonAction]
|
||||
public async Task UpdateList(List<ColumnOuput> tableColumnOutputList, long codeGenId)
|
||||
{
|
||||
if (tableColumnOutputList == null) return;
|
||||
|
||||
//获取历史数据
|
||||
var oldList = await GetList(new CodeGenConfig() { CodeGenId = codeGenId, });
|
||||
//删除历史数据
|
||||
await DeleteCodeGenConfig(codeGenId);
|
||||
|
||||
var codeGenConfigs = new List<SysCodeGenConfig>();
|
||||
var orderNo = 100;
|
||||
foreach (var tableColumn in tableColumnOutputList)
|
||||
{
|
||||
var oldItem = oldList.FirstOrDefault(u => u.ColumnName == tableColumn.ColumnName);
|
||||
|
||||
var codeGenConfig = new SysCodeGenConfig();
|
||||
|
||||
var yesOrNo = YesNoEnum.Y.ToString();
|
||||
if (Convert.ToBoolean(tableColumn.ColumnKey)) yesOrNo = YesNoEnum.N.ToString();
|
||||
|
||||
if (CodeGenUtil.IsCommonColumn(tableColumn.PropertyName))
|
||||
{
|
||||
codeGenConfig.WhetherCommon = YesNoEnum.Y.ToString();
|
||||
yesOrNo = YesNoEnum.N.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
codeGenConfig.WhetherCommon = YesNoEnum.N.ToString();
|
||||
}
|
||||
|
||||
codeGenConfig.CodeGenId = codeGenId;
|
||||
codeGenConfig.ColumnName = tableColumn.ColumnName; // 字段名
|
||||
codeGenConfig.PropertyName = tableColumn.PropertyName;// 实体属性名
|
||||
codeGenConfig.ColumnLength = tableColumn.ColumnLength;// 长度
|
||||
codeGenConfig.ColumnComment = tableColumn.ColumnComment;
|
||||
codeGenConfig.NetType = tableColumn.NetType;
|
||||
codeGenConfig.DefaultValue = tableColumn.DefaultValue;
|
||||
codeGenConfig.WhetherRetract = YesNoEnum.N.ToString();
|
||||
|
||||
// 生成代码时,主键并不是必要输入项,故一定要排除主键字段
|
||||
codeGenConfig.WhetherRequired = (tableColumn.IsNullable || tableColumn.IsPrimarykey) ? YesNoEnum.N.ToString() : YesNoEnum.Y.ToString();
|
||||
codeGenConfig.WhetherQuery = yesOrNo;
|
||||
codeGenConfig.WhetherImport = yesOrNo;
|
||||
codeGenConfig.WhetherAddUpdate = yesOrNo;
|
||||
codeGenConfig.WhetherTable = yesOrNo;
|
||||
|
||||
codeGenConfig.ColumnKey = tableColumn.ColumnKey;
|
||||
|
||||
codeGenConfig.DataType = tableColumn.DataType;
|
||||
codeGenConfig.EffectType = CodeGenUtil.DataTypeToEff(codeGenConfig.NetType);
|
||||
codeGenConfig.QueryType = GetDefaultQueryType(codeGenConfig); // QueryTypeEnum.eq.ToString();
|
||||
codeGenConfig.OrderNo = orderNo;
|
||||
|
||||
if (oldItem != null)
|
||||
{
|
||||
//如果历史存在,则继承
|
||||
codeGenConfig.WhetherQuery = oldItem.WhetherQuery;
|
||||
codeGenConfig.WhetherImport = oldItem.WhetherImport;
|
||||
codeGenConfig.WhetherAddUpdate = oldItem.WhetherAddUpdate;
|
||||
codeGenConfig.WhetherTable = oldItem.WhetherTable;
|
||||
|
||||
codeGenConfig.EffectType = oldItem.EffectType;
|
||||
codeGenConfig.FkConfigId = oldItem.FkConfigId;
|
||||
codeGenConfig.FkEntityName = oldItem.FkEntityName;
|
||||
codeGenConfig.FkTableName = oldItem.FkTableName;
|
||||
codeGenConfig.FkDisplayColumns = oldItem.FkDisplayColumns;
|
||||
codeGenConfig.FkLinkColumnName = oldItem.FkLinkColumnName;
|
||||
codeGenConfig.FkColumnNetType = oldItem.FkColumnNetType;
|
||||
}
|
||||
|
||||
codeGenConfigs.Add(codeGenConfig);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tableColumn.DictTypeCode))
|
||||
{
|
||||
codeGenConfig.QueryType = "==";
|
||||
codeGenConfig.DictTypeCode = tableColumn.DictTypeCode;
|
||||
codeGenConfig.EffectType = tableColumn.DictTypeCode.EndsWith("Enum") ? "EnumSelector" : "DictSelector";
|
||||
}
|
||||
|
||||
orderNo += 10; // 每个配置排序间隔10
|
||||
}
|
||||
// 多库代码生成---这里要切回主库
|
||||
var provider = _db.AsTenant().GetConnectionScope(SqlSugarConst.MainConfigId);
|
||||
provider.Insertable(codeGenConfigs).ExecuteCommand();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 默认查询类型
|
||||
/// </summary>
|
||||
/// <param name="codeGenConfig"></param>
|
||||
/// <returns></returns>
|
||||
private static string GetDefaultQueryType(SysCodeGenConfig codeGenConfig)
|
||||
{
|
||||
return (codeGenConfig.NetType?.TrimEnd('?')) switch
|
||||
{
|
||||
"string" => "like",
|
||||
"DateTime" => "~",
|
||||
_ => "==",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,825 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 系统代码生成器服务 🧩
|
||||
/// </summary>
|
||||
[ApiDescriptionSettings(Order = 270)]
|
||||
public class SysCodeGenService : IDynamicApiController, ITransient
|
||||
{
|
||||
private readonly ISqlSugarClient _db;
|
||||
|
||||
private readonly SysCodeGenConfigService _codeGenConfigService;
|
||||
private readonly DbConnectionOptions _dbConnectionOptions;
|
||||
private readonly CodeGenOptions _codeGenOptions;
|
||||
private readonly SysMenuService _sysMenuService;
|
||||
private readonly IViewEngine _viewEngine;
|
||||
private readonly UserManager _userManager;
|
||||
|
||||
public SysCodeGenService(ISqlSugarClient db,
|
||||
IOptions<DbConnectionOptions> dbConnectionOptions,
|
||||
SysCodeGenConfigService codeGenConfigService,
|
||||
IOptions<CodeGenOptions> codeGenOptions,
|
||||
SysMenuService sysMenuService,
|
||||
UserManager userManager,
|
||||
IViewEngine viewEngine)
|
||||
{
|
||||
_db = db;
|
||||
_viewEngine = viewEngine;
|
||||
_userManager = userManager;
|
||||
_sysMenuService = sysMenuService;
|
||||
_codeGenOptions = codeGenOptions.Value;
|
||||
_codeGenConfigService = codeGenConfigService;
|
||||
_dbConnectionOptions = dbConnectionOptions.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取代码生成分页列表 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取代码生成分页列表")]
|
||||
public async Task<SqlSugarPagedList<SysCodeGen>> Page(CodeGenInput input)
|
||||
{
|
||||
return await _db.Queryable<SysCodeGen>()
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.TableName), u => u.TableName.Contains(input.TableName.Trim()))
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.BusName), u => u.BusName.Contains(input.BusName.Trim()))
|
||||
.ToPagedListAsync(input.Page, input.PageSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加代码生成 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "Add"), HttpPost]
|
||||
[DisplayName("增加代码生成")]
|
||||
public async Task AddCodeGen(AddCodeGenInput input)
|
||||
{
|
||||
var isExist = await _db.Queryable<SysCodeGen>().Where(u => u.TableName == input.TableName).AnyAsync();
|
||||
if (isExist) throw Oops.Oh(ErrorCodeEnum.D1400);
|
||||
|
||||
if (input.TableUniqueList?.Count > 0) input.TableUniqueConfig = JSON.Serialize(input.TableUniqueList);
|
||||
|
||||
var codeGen = input.Adapt<SysCodeGen>();
|
||||
var newCodeGen = await _db.Insertable(codeGen).ExecuteReturnEntityAsync();
|
||||
|
||||
// 增加配置表
|
||||
_codeGenConfigService.AddList(GetColumnList(input), newCodeGen);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新代码生成 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "Update"), HttpPost]
|
||||
[DisplayName("更新代码生成")]
|
||||
public async Task UpdateCodeGen(UpdateCodeGenInput input)
|
||||
{
|
||||
var isExist = await _db.Queryable<SysCodeGen>().AnyAsync(u => u.TableName == input.TableName && u.Id != input.Id);
|
||||
if (isExist) throw Oops.Oh(ErrorCodeEnum.D1400);
|
||||
|
||||
var oldRecord = await _db.Queryable<SysCodeGen>().FirstAsync(u => u.Id == input.Id);
|
||||
try
|
||||
{
|
||||
// 开启事务
|
||||
_db.AsTenant().BeginTran();
|
||||
if (input.GenerateMenu)
|
||||
{
|
||||
var oldTitle = $"{oldRecord.BusName}管理";
|
||||
var newTitle = $"{input.BusName}管理";
|
||||
var updateObj = await _db.Queryable<SysMenu>().FirstAsync(u => u.Title == oldTitle);
|
||||
if (updateObj != null)
|
||||
{
|
||||
updateObj.Title = newTitle;
|
||||
var result = _db.Updateable(updateObj).UpdateColumns(it => new { it.Title }).ExecuteCommand();
|
||||
_sysMenuService.DeleteMenuCache();
|
||||
}
|
||||
}
|
||||
if (input.TableUniqueList?.Count > 0) input.TableUniqueConfig = JSON.Serialize(input.TableUniqueList);
|
||||
var codeGen = input.Adapt<SysCodeGen>();
|
||||
await _db.Updateable(codeGen).ExecuteCommandAsync();
|
||||
|
||||
// 仅当数据表名称发生了变化,才更新配置表
|
||||
//if (oldRecord.TableName != input.TableName)
|
||||
//{
|
||||
await _codeGenConfigService.DeleteCodeGenConfig(codeGen.Id);
|
||||
_codeGenConfigService.AddList(GetColumnList(input.Adapt<AddCodeGenInput>()), codeGen);
|
||||
//}
|
||||
_db.AsTenant().CommitTran();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_db.AsTenant().RollbackTran();
|
||||
throw Oops.Oh(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步代码字段(保留历史作用类型) 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "SyncField"), HttpPost]
|
||||
[DisplayName("同步代码字段")]
|
||||
public async Task SyncCodeFieldGen(UpdateCodeGenInput input)
|
||||
{
|
||||
var isExist = await _db.Queryable<SysCodeGen>().AnyAsync(u => u.TableName == input.TableName && u.Id != input.Id);
|
||||
if (isExist) throw Oops.Oh(ErrorCodeEnum.D1400);
|
||||
try
|
||||
{
|
||||
// 开启事务
|
||||
_db.AsTenant().BeginTran();
|
||||
await _codeGenConfigService.UpdateList(GetColumnList(input.Adapt<AddCodeGenInput>()), input.Id);
|
||||
_db.AsTenant().CommitTran();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_db.AsTenant().RollbackTran();
|
||||
throw Oops.Oh(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除代码生成 🔖
|
||||
/// </summary>
|
||||
/// <param name="inputs"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "Delete"), HttpPost]
|
||||
[DisplayName("删除代码生成")]
|
||||
public async Task DeleteCodeGen(List<DeleteCodeGenInput> inputs)
|
||||
{
|
||||
if (inputs == null || inputs.Count < 1) return;
|
||||
|
||||
var codeGenConfigTaskList = new List<Task>();
|
||||
inputs.ForEach(u =>
|
||||
{
|
||||
_db.Deleteable<SysCodeGen>().In(u.Id).ExecuteCommand();
|
||||
|
||||
// 删除配置表
|
||||
codeGenConfigTaskList.Add(_codeGenConfigService.DeleteCodeGenConfig(u.Id));
|
||||
});
|
||||
await Task.WhenAll(codeGenConfigTaskList);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取代码生成详情 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取代码生成详情")]
|
||||
public async Task<SysCodeGen> GetDetail([FromQuery] QueryCodeGenInput input)
|
||||
{
|
||||
return await _db.Queryable<SysCodeGen>().SingleAsync(u => u.Id == input.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取数据库库集合 🔖
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取数据库库集合")]
|
||||
public async Task<List<DatabaseOutput>> GetDatabaseList()
|
||||
{
|
||||
var dbConfigs = _dbConnectionOptions.ConnectionConfigs;
|
||||
return await Task.FromResult(dbConfigs.Adapt<List<DatabaseOutput>>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取数据库表(实体)集合 🔖
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取数据库表(实体)集合")]
|
||||
public async Task<List<TableOutput>> GetTableList(string configId = SqlSugarConst.MainConfigId)
|
||||
{
|
||||
var provider = _db.AsTenant().GetConnectionScope(configId);
|
||||
var dbTableInfos = provider.DbMaintenance.GetTableInfoList(false); // 不能走缓存,否则切库不起作用
|
||||
var config = _dbConnectionOptions.ConnectionConfigs.FirstOrDefault(u => configId.Equals(u.ConfigId));
|
||||
|
||||
// var dbTableNames = dbTableInfos.Select(u => u.Name.ToLower()).ToList();
|
||||
IEnumerable<EntityInfo> entityInfos = await GetEntityInfos(configId);
|
||||
|
||||
var tableOutputList = new List<TableOutput>();
|
||||
foreach (var item in entityInfos)
|
||||
{
|
||||
var tbConfigId = item.Type.GetCustomAttribute<TenantAttribute>()?.configId as string ?? SqlSugarConst.MainConfigId;
|
||||
if (item.Type.IsDefined(typeof(LogTableAttribute))) tbConfigId = SqlSugarConst.LogConfigId;
|
||||
if (tbConfigId != configId) continue;
|
||||
|
||||
var table = dbTableInfos.FirstOrDefault(u => string.Equals(u.Name, (config!.DbSettings.EnableUnderLine ? item.DbTableName.ToUnderLine() : item.DbTableName), StringComparison.CurrentCultureIgnoreCase));
|
||||
if (table == null) continue;
|
||||
tableOutputList.Add(new TableOutput
|
||||
{
|
||||
ConfigId = configId,
|
||||
EntityName = item.EntityName,
|
||||
TableName = table.Name,
|
||||
TableComment = item.TableDescription
|
||||
});
|
||||
}
|
||||
return tableOutputList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据表名获取列集合 🔖
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[DisplayName("根据表名获取列集合")]
|
||||
public List<ColumnOuput> GetColumnListByTableName([Required] string tableName, string configId = SqlSugarConst.MainConfigId)
|
||||
{
|
||||
// 切库---多库代码生成用
|
||||
var provider = _db.AsTenant().GetConnectionScope(configId);
|
||||
var config = _dbConnectionOptions.ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == configId) ?? throw Oops.Oh(ErrorCodeEnum.D1401);
|
||||
if (config.DbSettings.EnableUnderLine) tableName = tableName.ToUnderLine();
|
||||
// 获取实体类型属性
|
||||
var entityType = provider.DbMaintenance.GetTableInfoList(false).FirstOrDefault(u => u.Name == tableName);
|
||||
if (entityType == null) return null;
|
||||
var properties = GetEntityInfos(configId).Result.First(e => e.DbTableName.EndsWithIgnoreCase(tableName)).Type.GetProperties()
|
||||
.Where(e => e.GetCustomAttribute<SugarColumn>()?.IsIgnore == false).Select(e => new
|
||||
{
|
||||
PropertyName = e.Name,
|
||||
ColumnComment = e.GetCustomAttribute<SugarColumn>()?.ColumnDescription,
|
||||
ColumnName = e.GetCustomAttribute<SugarColumn>()?.ColumnName ?? e.Name
|
||||
}).ToList();
|
||||
// 按原始类型的顺序获取所有实体类型属性(不包含导航属性,会返回null)
|
||||
var columnList = provider.DbMaintenance.GetColumnInfosByTableName(tableName).Select(u => new ColumnOuput
|
||||
{
|
||||
ColumnName = config!.DbSettings.EnableUnderLine ? u.DbColumnName.ToUnderLine() : u.DbColumnName,
|
||||
ColumnKey = u.IsPrimarykey.ToString(),
|
||||
DataType = u.DataType.ToString(),
|
||||
NetType = CodeGenUtil.ConvertDataType(u, provider.CurrentConnectionConfig.DbType),
|
||||
ColumnComment = u.ColumnDescription
|
||||
}).ToList();
|
||||
foreach (var column in columnList)
|
||||
{
|
||||
// ToLowerInvariant 将字段名转成小写再比较,避免因大小写不一致导致无法匹配(pgsql创建表会默认全小写,而我们的实体中又是大写,就会匹配不上)
|
||||
var property = properties.FirstOrDefault(e => (config!.DbSettings.EnableUnderLine ? e.ColumnName.ToUnderLine() : e.ColumnName).ToLowerInvariant() == column.ColumnName.ToLowerInvariant());
|
||||
column.ColumnComment ??= property?.ColumnComment;
|
||||
column.PropertyName = property?.PropertyName;
|
||||
}
|
||||
return columnList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取数据表列(实体属性)集合
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private List<ColumnOuput> GetColumnList([FromQuery] AddCodeGenInput input)
|
||||
{
|
||||
var entityType = GetEntityInfos(input.ConfigId).GetAwaiter().GetResult().FirstOrDefault(u => u.EntityName == input.TableName);
|
||||
if (entityType == null) return null;
|
||||
|
||||
var config = _dbConnectionOptions.ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == input.ConfigId);
|
||||
var dbTableName = config!.DbSettings.EnableUnderLine ? entityType.DbTableName.ToUnderLine() : entityType.DbTableName;
|
||||
|
||||
// 切库---多库代码生成用
|
||||
var provider = _db.AsTenant().GetConnectionScope(!string.IsNullOrEmpty(input.ConfigId) ? input.ConfigId : SqlSugarConst.MainConfigId);
|
||||
|
||||
var entityBasePropertyNames = CodeGenUtil.GetPropertyInfoArray(typeof(EntityBaseTenant))?.Select(p => p.Name).ToArray();
|
||||
var columnInfos = provider.DbMaintenance.GetColumnInfosByTableName(dbTableName, false);
|
||||
var result = columnInfos.Select(u => new ColumnOuput
|
||||
{
|
||||
// 转下划线后的列名需要再转回来(暂时不转)
|
||||
//ColumnName = config.DbSettings.EnableUnderLine ? CodeGenUtil.CamelColumnName(u.DbColumnName, entityBasePropertyNames) : u.DbColumnName,
|
||||
ColumnName = u.DbColumnName,
|
||||
ColumnLength = u.Length,
|
||||
IsPrimarykey = u.IsPrimarykey,
|
||||
IsNullable = u.IsNullable,
|
||||
ColumnKey = u.IsPrimarykey.ToString(),
|
||||
NetType = CodeGenUtil.ConvertDataType(u, provider.CurrentConnectionConfig.DbType),
|
||||
DataType = CodeGenUtil.ConvertDataType(u, provider.CurrentConnectionConfig.DbType),
|
||||
ColumnComment = string.IsNullOrWhiteSpace(u.ColumnDescription) ? u.DbColumnName : u.ColumnDescription,
|
||||
DefaultValue = u.DefaultValue,
|
||||
}).ToList();
|
||||
|
||||
// 获取实体的属性信息,赋值给PropertyName属性(CodeFirst模式应以PropertyName为实际使用名称)
|
||||
var entityProperties = entityType.Type.GetProperties();
|
||||
|
||||
for (int i = result.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var columnOutput = result[i];
|
||||
// 先找自定义字段名的,如果找不到就再找自动生成字段名的(并且过滤掉没有SugarColumn的属性)
|
||||
var propertyInfo = entityProperties.FirstOrDefault(u => string.Equals((u.GetCustomAttribute<SugarColumn>()?.ColumnName ?? ""), columnOutput.ColumnName, StringComparison.CurrentCultureIgnoreCase)) ??
|
||||
entityProperties.FirstOrDefault(u => u.GetCustomAttribute<SugarColumn>() != null && u.Name.ToLower() == (config.DbSettings.EnableUnderLine
|
||||
? CodeGenUtil.CamelColumnName(columnOutput.ColumnName, entityBasePropertyNames).ToLower()
|
||||
: columnOutput.ColumnName.ToLower()));
|
||||
if (propertyInfo != null)
|
||||
{
|
||||
columnOutput.PropertyName = propertyInfo.Name;
|
||||
columnOutput.ColumnComment = propertyInfo.GetCustomAttribute<SugarColumn>()!.ColumnDescription;
|
||||
var propertyType = Nullable.GetUnderlyingType(propertyInfo.PropertyType);
|
||||
if (propertyInfo.PropertyType.IsEnum || (propertyType?.IsEnum ?? false))
|
||||
{
|
||||
columnOutput.DictTypeCode = (propertyType ?? propertyInfo.PropertyType).Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
var dict = propertyInfo.GetCustomAttribute<DictAttribute>();
|
||||
if (dict != null) columnOutput.DictTypeCode = dict.DictTypeCode;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.RemoveAt(i); // 移除没有定义此属性的字段
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取库表信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private async Task<IEnumerable<EntityInfo>> GetEntityInfos(string configId)
|
||||
{
|
||||
var config = _dbConnectionOptions.ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == configId) ?? throw Oops.Oh(ErrorCodeEnum.D1401);
|
||||
var entityInfos = new List<EntityInfo>();
|
||||
|
||||
var type = typeof(SugarTable);
|
||||
var types = new List<Type>();
|
||||
if (_codeGenOptions.EntityAssemblyNames != null)
|
||||
{
|
||||
types = App.EffectiveTypes.Where(c => c.IsClass)
|
||||
.Where(c => _codeGenOptions.EntityAssemblyNames.Contains(c.Assembly.GetName().Name) || _codeGenOptions.EntityAssemblyNames.Any(name => c.Assembly.GetName().Name!.Contains(name)))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
Type[] cosType = types.Where(o => IsMyAttribute(Attribute.GetCustomAttributes(o, true))).ToArray();
|
||||
|
||||
foreach (var ct in cosType)
|
||||
{
|
||||
var sugarAttribute = ct.GetCustomAttributes(type, true).FirstOrDefault();
|
||||
|
||||
var description = "";
|
||||
var des = ct.GetCustomAttributes(typeof(DescriptionAttribute), true);
|
||||
if (des.Length > 0) description = ((DescriptionAttribute)des[0]).Description;
|
||||
|
||||
var dbTableName = sugarAttribute == null || string.IsNullOrWhiteSpace(((SugarTable)sugarAttribute).TableName) ? ct.Name : ((SugarTable)sugarAttribute).TableName;
|
||||
if (config.DbSettings.EnableUnderLine) dbTableName = dbTableName.ToUnderLine();
|
||||
|
||||
entityInfos.Add(new EntityInfo
|
||||
{
|
||||
EntityName = ct.Name,
|
||||
DbTableName = dbTableName,
|
||||
TableDescription = sugarAttribute == null ? description : ((SugarTable)sugarAttribute).TableDescription,
|
||||
Type = ct
|
||||
});
|
||||
}
|
||||
return await Task.FromResult(entityInfos);
|
||||
|
||||
bool IsMyAttribute(Attribute[] o) => o.Any(a => a.GetType() == type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取程序保存位置 🔖
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取程序保存位置")]
|
||||
public List<string> GetApplicationNamespaces()
|
||||
{
|
||||
return _codeGenOptions.BackendApplicationNamespaces;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 代码生成到本地 🔖
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[UnitOfWork]
|
||||
[DisplayName("代码生成到本地")]
|
||||
public async Task<dynamic> RunLocal(SysCodeGen input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input.GenerateType))
|
||||
input.GenerateType = "200";
|
||||
|
||||
// 先删除该表已生成的菜单列表
|
||||
List<string> targetPathList;
|
||||
var zipPath = Path.Combine(App.WebHostEnvironment.WebRootPath, "CodeGen", input.TableName!);
|
||||
if (input.GenerateType.StartsWith('1'))
|
||||
{
|
||||
targetPathList = GetZipPathList(input);
|
||||
if (Directory.Exists(zipPath)) Directory.Delete(zipPath, true);
|
||||
}
|
||||
else
|
||||
targetPathList = GetTargetPathList(input);
|
||||
|
||||
var (tableFieldList, result) = await RenderTemplateAsync(input);
|
||||
var templatePathList = GetTemplatePathList(input);
|
||||
for (var i = 0; i < templatePathList.Count; i++)
|
||||
{
|
||||
var content = result.GetValueOrDefault(templatePathList[i]?.TrimEnd(".vm"));
|
||||
if (string.IsNullOrWhiteSpace(content)) continue;
|
||||
var dirPath = new DirectoryInfo(targetPathList[i]).Parent!.FullName;
|
||||
if (!Directory.Exists(dirPath)) Directory.CreateDirectory(dirPath);
|
||||
_ = File.WriteAllTextAsync(targetPathList[i], content, Encoding.UTF8);
|
||||
}
|
||||
|
||||
if (input.GenerateMenu) await AddOrUpdateMenu(input.TableName, input.BusName, input.MenuPid ?? 0, input.MenuIcon, input.PagePath, tableFieldList);
|
||||
|
||||
// 非ZIP压缩返回空
|
||||
if (!input.GenerateType.StartsWith('1')) return null;
|
||||
|
||||
// 判断是否存在同名称文件
|
||||
string downloadPath = zipPath + ".zip";
|
||||
if (File.Exists(downloadPath)) File.Delete(downloadPath);
|
||||
|
||||
// 创建zip文件并返回下载地址
|
||||
ZipFile.CreateFromDirectory(zipPath, downloadPath);
|
||||
return new { url = $"{App.HttpContext.Request.Scheme}://{App.HttpContext.Request.Host.Value}/codeGen/{input.TableName}.zip" };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取代码生成预览 🔖
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取代码生成预览")]
|
||||
public async Task<Dictionary<string, string>> Preview(SysCodeGen input)
|
||||
{
|
||||
var (_, result) = await RenderTemplateAsync(input);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 渲染模板
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<(List<CodeGenConfig> tableFieldList, Dictionary<string, string> result)> RenderTemplateAsync(SysCodeGen input)
|
||||
{
|
||||
var tableFieldList = await _codeGenConfigService.GetList(new CodeGenConfig { CodeGenId = input.Id }); // 字段集合
|
||||
var joinTableList = tableFieldList.Where(u => u.EffectType is "Upload" or "ForeignKey" or "ApiTreeSelector").ToList(); // 需要连表查询的字段
|
||||
|
||||
var data = new CustomViewEngine
|
||||
{
|
||||
ConfigId = input.ConfigId,
|
||||
BusName = input.BusName,
|
||||
PagePath = input.PagePath,
|
||||
NameSpace = input.NameSpace,
|
||||
ClassName = input.TableName,
|
||||
PrintType = input.PrintType,
|
||||
PrintName = input.PrintName,
|
||||
AuthorName = input.AuthorName,
|
||||
ProjectLastName = input.NameSpace!.Split('.').Last(),
|
||||
LowerClassName = input.TableName!.ToFirstLetterLowerCase(),
|
||||
TableUniqueConfigList = input.TableUniqueList ?? new(),
|
||||
|
||||
TableField = tableFieldList,
|
||||
QueryWhetherList = tableFieldList.Where(u => u.WhetherQuery == "Y").ToList(),
|
||||
ImportFieldList = tableFieldList.Where(u => u.WhetherImport == "Y").ToList(),
|
||||
UploadFieldList = tableFieldList.Where(u => u.EffectType == "Upload" || u.EffectType == "Upload_SingleFile").ToList(),
|
||||
PrimaryKeyFieldList = tableFieldList.Where(c => c.ColumnKey == "True").ToList(),
|
||||
AddUpdateFieldList = tableFieldList.Where(u => u.WhetherAddUpdate == "Y").ToList(),
|
||||
ApiTreeFieldList = tableFieldList.Where(u => u.EffectType == "ApiTreeSelector").ToList(),
|
||||
DropdownFieldList = tableFieldList.Where(u => u.EffectType is "ForeignKey" or "ApiTreeSelector").ToList(),
|
||||
DefaultValueList = tableFieldList.Where(u => u.DefaultValue != null && u.DefaultValue.Length > 0).ToList(),
|
||||
|
||||
HasJoinTable = joinTableList.Count > 0,
|
||||
HasDictField = tableFieldList.Any(u => u.EffectType == "DictSelector"),
|
||||
HasEnumField = tableFieldList.Any(u => u.EffectType == "EnumSelector"),
|
||||
HasConstField = tableFieldList.Any(u => u.EffectType == "ConstSelector"),
|
||||
HasLikeQuery = tableFieldList.Any(c => c.WhetherQuery == "Y" && c.QueryType == "like")
|
||||
};
|
||||
|
||||
// 获取模板文件并替换
|
||||
var templatePathList = GetTemplatePathList();
|
||||
var templatePath = Path.Combine(App.WebHostEnvironment.WebRootPath, "template");
|
||||
|
||||
var result = new Dictionary<string, string>();
|
||||
foreach (var path in templatePathList)
|
||||
{
|
||||
var templateFilePath = Path.Combine(templatePath, path);
|
||||
if (!File.Exists(templateFilePath)) continue;
|
||||
var tContent = await File.ReadAllTextAsync(templateFilePath);
|
||||
var tResult = await _viewEngine.RunCompileFromCachedAsync(tContent, data, builderAction: builder =>
|
||||
{
|
||||
builder.AddAssemblyReferenceByName("System.Text.RegularExpressions");
|
||||
builder.AddAssemblyReferenceByName("System.Collections");
|
||||
builder.AddAssemblyReferenceByName("System.Linq");
|
||||
|
||||
builder.AddUsing("System.Text.RegularExpressions");
|
||||
builder.AddUsing("System.Collections.Generic");
|
||||
builder.AddUsing("System.Linq");
|
||||
});
|
||||
result.Add(path?.TrimEnd(".vm"), tResult);
|
||||
}
|
||||
return (tableFieldList, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加或更新菜单
|
||||
/// </summary>
|
||||
/// <param name="className"></param>
|
||||
/// <param name="busName"></param>
|
||||
/// <param name="pid"></param>
|
||||
/// <param name="menuIcon"></param>
|
||||
/// <param name="pagePath"></param>
|
||||
/// <param name="tableFieldList"></param>
|
||||
/// <returns></returns>
|
||||
private async Task AddOrUpdateMenu(string className, string busName, long pid, string menuIcon, string pagePath, List<CodeGenConfig> tableFieldList)
|
||||
{
|
||||
var title = $"{busName}管理";
|
||||
var lowerClassName = className.ToFirstLetterLowerCase();
|
||||
var menuType = pid == 0 ? MenuTypeEnum.Dir : MenuTypeEnum.Menu;
|
||||
|
||||
// 查询是否已有主菜单
|
||||
var existingMenu = await _db.Queryable<SysMenu>()
|
||||
.Where(m => m.Title == title && m.Type == menuType && m.Pid == pid)
|
||||
.FirstAsync();
|
||||
|
||||
string parentPath = "";
|
||||
if (pid != 0)
|
||||
{
|
||||
var parent = await _db.Queryable<SysMenu>().FirstAsync(m => m.Id == pid) ?? throw Oops.Oh(ErrorCodeEnum.D1505);
|
||||
parentPath = parent.Path;
|
||||
}
|
||||
|
||||
long menuId;
|
||||
if (existingMenu == null)
|
||||
{
|
||||
// 不存在则新增
|
||||
var newMenu = new SysMenu
|
||||
{
|
||||
Pid = pid,
|
||||
Title = title,
|
||||
Type = menuType,
|
||||
Icon = menuIcon ?? "menu",
|
||||
Path = (pid == 0 ? "/" : parentPath + "/") + className.ToLower(),
|
||||
Component = pid == 0 ? "Layout" : $"/{pagePath}/{lowerClassName}/index"
|
||||
};
|
||||
menuId = await _sysMenuService.AddMenu(newMenu.Adapt<AddMenuInput>());
|
||||
}
|
||||
else
|
||||
{
|
||||
// 存在则更新
|
||||
existingMenu.Icon = menuIcon;
|
||||
existingMenu.Path = (pid == 0 ? "/" : parentPath + "/") + className.ToLower();
|
||||
existingMenu.Component = pid == 0 ? "Layout" : $"/{pagePath}/{lowerClassName}/index";
|
||||
await _sysMenuService.UpdateMenu(existingMenu.Adapt<UpdateMenuInput>());
|
||||
menuId = existingMenu.Id;
|
||||
}
|
||||
|
||||
// 定义应有的按钮
|
||||
var orderNo = 100;
|
||||
var newButtons = new List<SysMenu>
|
||||
{
|
||||
new() { Title = "查询", Permission = $"{lowerClassName}:page", OrderNo = orderNo += 10 },
|
||||
new() { Title = "详情", Permission = $"{lowerClassName}:detail", OrderNo = orderNo += 10 },
|
||||
new() { Title = "增加", Permission = $"{lowerClassName}:add", OrderNo = orderNo += 10 },
|
||||
new() { Title = "编辑", Permission = $"{lowerClassName}:update", OrderNo = orderNo += 10 },
|
||||
new() { Title = "删除", Permission = $"{lowerClassName}:delete", OrderNo = orderNo += 10 },
|
||||
new() { Title = "批量删除", Permission = $"{lowerClassName}:batchDelete", OrderNo = orderNo += 10 },
|
||||
new() { Title = "设置状态", Permission = $"{lowerClassName}:setStatus", OrderNo = orderNo += 10 },
|
||||
new() { Title = "打印", Permission = $"{lowerClassName}:print", OrderNo = orderNo += 10 },
|
||||
new() { Title = "导入", Permission = $"{lowerClassName}:import", OrderNo = orderNo += 10 },
|
||||
new() { Title = "导出", Permission = $"{lowerClassName}:export", OrderNo = orderNo += 10 }
|
||||
};
|
||||
|
||||
if (tableFieldList.Any(u => u.EffectType is "ForeignKey" or "ApiTreeSelector" && (u.WhetherAddUpdate == "Y" || u.WhetherQuery == "Y")))
|
||||
{
|
||||
newButtons.Add(new SysMenu
|
||||
{
|
||||
Title = "下拉列表数据",
|
||||
Permission = $"{lowerClassName}:dropdownData",
|
||||
OrderNo = orderNo += 10
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var column in tableFieldList.Where(u => u.EffectType == "Upload" || u.EffectType == "Upload_SingleFile"))
|
||||
{
|
||||
newButtons.Add(new SysMenu
|
||||
{
|
||||
Title = $"上传{column.ColumnComment}",
|
||||
Permission = $"{lowerClassName}:upload{column.PropertyName}",
|
||||
OrderNo = orderNo += 10
|
||||
});
|
||||
}
|
||||
|
||||
// 获取当前菜单下的按钮
|
||||
var existingButtons = await _db.Queryable<SysMenu>()
|
||||
.Where(m => m.Pid == menuId && m.Type == MenuTypeEnum.Btn)
|
||||
.ToListAsync();
|
||||
|
||||
var newPermissions = newButtons.Select(b => b.Permission).ToHashSet();
|
||||
|
||||
// 添加或更新按钮
|
||||
foreach (var btn in newButtons)
|
||||
{
|
||||
var match = existingButtons.FirstOrDefault(b => b.Permission == btn.Permission);
|
||||
if (match == null)
|
||||
{
|
||||
btn.Type = MenuTypeEnum.Btn;
|
||||
btn.Pid = menuId;
|
||||
btn.Icon = "";
|
||||
await _sysMenuService.AddMenu(btn.Adapt<AddMenuInput>());
|
||||
}
|
||||
else
|
||||
{
|
||||
match.Title = btn.Title;
|
||||
match.OrderNo = btn.OrderNo;
|
||||
await _sysMenuService.UpdateMenu(match.Adapt<UpdateMenuInput>());
|
||||
}
|
||||
}
|
||||
|
||||
// 删除多余的旧按钮
|
||||
var toDelete = existingButtons.Where(b => !newPermissions.Contains(b.Permission)).ToList();
|
||||
foreach (var del in toDelete)
|
||||
await _sysMenuService.DeleteMenu(new DeleteMenuInput { Id = del.Id });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加菜单
|
||||
/// </summary>
|
||||
/// <param name="className"></param>
|
||||
/// <param name="busName"></param>
|
||||
/// <param name="pid"></param>
|
||||
/// <param name="menuIcon"></param>
|
||||
/// <param name="pagePath"></param>
|
||||
/// <param name="tableFieldList"></param>
|
||||
/// <returns></returns>
|
||||
private async Task AddMenu(string className, string busName, long pid, string menuIcon, string pagePath, List<CodeGenConfig> tableFieldList)
|
||||
{
|
||||
// 删除已存在的菜单
|
||||
var title = $"{busName}管理";
|
||||
await DeleteMenuTree(title, pid == 0 ? MenuTypeEnum.Dir : MenuTypeEnum.Menu);
|
||||
|
||||
var parentMenuPath = "";
|
||||
var lowerClassName = className!.ToFirstLetterLowerCase();
|
||||
if (pid == 0)
|
||||
{
|
||||
// 新增目录,并记录Id
|
||||
var dirMenu = new SysMenu { Pid = 0, Title = title, Type = MenuTypeEnum.Dir, Icon = "robot", Path = "/" + className.ToLower(), Component = "Layout" };
|
||||
pid = await _sysMenuService.AddMenu(dirMenu.Adapt<AddMenuInput>());
|
||||
}
|
||||
else
|
||||
{
|
||||
var parentMenu = await _db.Queryable<SysMenu>().FirstAsync(u => u.Id == pid) ?? throw Oops.Oh(ErrorCodeEnum.D1505);
|
||||
parentMenuPath = parentMenu.Path;
|
||||
}
|
||||
|
||||
// 新增菜单,并记录Id
|
||||
var rootMenu = new SysMenu { Pid = pid, Title = title, Type = MenuTypeEnum.Menu, Icon = menuIcon, Path = $"{parentMenuPath}/{className.ToLower()}", Component = $"/{pagePath}/{lowerClassName}/index" };
|
||||
pid = await _sysMenuService.AddMenu(rootMenu.Adapt<AddMenuInput>());
|
||||
|
||||
var orderNo = 100;
|
||||
var menuList = new List<SysMenu>
|
||||
{
|
||||
new() { Title="查询", Permission=$"{lowerClassName}:page", Pid=pid, Type=MenuTypeEnum.Btn, OrderNo=orderNo+=10},
|
||||
new() { Title="详情", Permission=$"{lowerClassName}:detail", Pid=pid, Type=MenuTypeEnum.Btn, OrderNo=orderNo+=10},
|
||||
new() { Title="增加", Permission=$"{lowerClassName}:add", Pid=pid, Type=MenuTypeEnum.Btn, OrderNo=orderNo+=10},
|
||||
new() { Title="编辑", Permission=$"{lowerClassName}:update", Pid=pid, Type=MenuTypeEnum.Btn, OrderNo=orderNo+=10},
|
||||
new() { Title="删除", Permission=$"{lowerClassName}:delete", Pid=pid, Type=MenuTypeEnum.Btn, OrderNo=orderNo+=10},
|
||||
new() { Title="批量删除", Permission=$"{lowerClassName}:batchDelete", Pid=pid, Type=MenuTypeEnum.Btn, OrderNo=orderNo+=10},
|
||||
new() { Title="设置状态", Permission=$"{lowerClassName}:setStatus", Pid=pid, Type=MenuTypeEnum.Btn, OrderNo=orderNo+=10},
|
||||
new() { Title="打印", Permission=$"{lowerClassName}:print", Pid=pid, Type=MenuTypeEnum.Btn, OrderNo=orderNo+=10},
|
||||
new() { Title="导入", Permission=$"{lowerClassName}:import", Pid=pid, Type=MenuTypeEnum.Btn, OrderNo=orderNo+=10},
|
||||
new() { Title="导出", Permission=$"{lowerClassName}:export", Pid=pid, Type=MenuTypeEnum.Btn, OrderNo=orderNo+=10}
|
||||
};
|
||||
|
||||
if (tableFieldList.Any(u => u.EffectType is "ForeignKey" or "ApiTreeSelector" && (u.WhetherAddUpdate == "Y" || u.WhetherQuery == "Y")))
|
||||
menuList.Add(new SysMenu { Title = "下拉列表数据", Permission = $"{lowerClassName}:dropdownData", Pid = pid, Type = MenuTypeEnum.Btn, OrderNo = orderNo += 10 });
|
||||
|
||||
foreach (var column in tableFieldList.Where(u => u.EffectType == "Upload"))
|
||||
menuList.Add(new SysMenu { Title = $"上传{column.ColumnComment}", Permission = $"{lowerClassName}:upload{column.PropertyName}", Pid = pid, Type = MenuTypeEnum.Btn, OrderNo = orderNo += 10 });
|
||||
|
||||
foreach (var menu in menuList) await _sysMenuService.AddMenu(menu.Adapt<AddMenuInput>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据菜单名称和类型删除关联的菜单树
|
||||
/// </summary>
|
||||
/// <param name="title"></param>
|
||||
/// <param name="type"></param>
|
||||
private async Task DeleteMenuTree(string title, MenuTypeEnum type)
|
||||
{
|
||||
var menuList = await _db.Queryable<SysMenu>().Where(u => u.Title == title && u.Type == type).ToListAsync() ?? new();
|
||||
foreach (var menu in menuList) await _sysMenuService.DeleteMenu(new DeleteMenuInput { Id = menu.Id });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取模板文件路径集合
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static List<string> GetTemplatePathList(SysCodeGen input)
|
||||
{
|
||||
if (input.GenerateType!.Substring(1, 1).Contains('1')) return new() { "index.vue.vm", "editDialog.vue.vm", "api.ts.vm" };
|
||||
if (input.GenerateType.Substring(1, 1).Contains('2')) return new() { "Service.cs.vm", "Input.cs.vm", "Output.cs.vm", "Dto.cs.vm" };
|
||||
return new() { "Service.cs.vm", "Input.cs.vm", "Output.cs.vm", "Dto.cs.vm", "index.vue.vm", "editDialog.vue.vm", "api.ts.vm" };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取模板文件路径集合
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static List<string> GetTemplatePathList() => new() { "Service.cs.vm", "Input.cs.vm", "Output.cs.vm", "Dto.cs.vm", "index.vue.vm", "editDialog.vue.vm", "api.ts.vm" };
|
||||
|
||||
/// <summary>
|
||||
/// 设置生成文件路径
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
private List<string> GetTargetPathList(SysCodeGen input)
|
||||
{
|
||||
//var backendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, _codeGenOptions.BackendApplicationNamespace, "Service", input.TableName);
|
||||
var backendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent!.FullName, input.NameSpace!, "Service", input.TableName!);
|
||||
var servicePath = Path.Combine(backendPath, input.TableName + "Service.cs");
|
||||
var inputPath = Path.Combine(backendPath, "Dto", input.TableName + "Input.cs");
|
||||
var outputPath = Path.Combine(backendPath, "Dto", input.TableName + "Output.cs");
|
||||
var viewPath = Path.Combine(backendPath, "Dto", input.TableName + "Dto.cs");
|
||||
var frontendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent!.Parent!.FullName, _codeGenOptions.FrontRootPath, "src", "views", input.PagePath!);
|
||||
var indexPath = Path.Combine(frontendPath, input.TableName[..1].ToLower() + input.TableName[1..], "index.vue");//
|
||||
var formModalPath = Path.Combine(frontendPath, input.TableName[..1].ToLower() + input.TableName[1..], "component", "editDialog.vue");
|
||||
var apiJsPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent!.Parent!.FullName, _codeGenOptions.FrontRootPath, "src", "api", input.PagePath, input.TableName[..1].ToLower() + input.TableName[1..] + ".ts");
|
||||
|
||||
if (input.GenerateType!.Substring(1, 1).Contains('1'))
|
||||
{
|
||||
// 生成到本项目(前端)
|
||||
return new List<string>
|
||||
{
|
||||
indexPath,
|
||||
formModalPath,
|
||||
apiJsPath
|
||||
};
|
||||
}
|
||||
|
||||
if (input.GenerateType.Substring(1, 1).Contains('2'))
|
||||
{
|
||||
// 生成到本项目(后端)
|
||||
return new List<string>
|
||||
{
|
||||
servicePath,
|
||||
inputPath,
|
||||
outputPath,
|
||||
viewPath,
|
||||
};
|
||||
}
|
||||
// 前后端同时生成到本项目
|
||||
return new List<string>
|
||||
{
|
||||
servicePath,
|
||||
inputPath,
|
||||
outputPath,
|
||||
viewPath,
|
||||
indexPath,
|
||||
formModalPath,
|
||||
apiJsPath
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置生成文件路径
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
private List<string> GetZipPathList(SysCodeGen input)
|
||||
{
|
||||
var zipPath = Path.Combine(App.WebHostEnvironment.WebRootPath, "CodeGen", input.TableName!);
|
||||
|
||||
var firstLowerTableName = input.TableName!.ToFirstLetterLowerCase();
|
||||
//var backendPath = Path.Combine(zipPath, _codeGenOptions.BackendApplicationNamespace, "Service", input.TableName);
|
||||
var backendPath = Path.Combine(zipPath, input.NameSpace!, "Service", input.TableName);
|
||||
var servicePath = Path.Combine(backendPath, input.TableName + "Service.cs");
|
||||
var inputPath = Path.Combine(backendPath, "Dto", input.TableName + "Input.cs");
|
||||
var outputPath = Path.Combine(backendPath, "Dto", input.TableName + "Output.cs");
|
||||
var viewPath = Path.Combine(backendPath, "Dto", input.TableName + "Dto.cs");
|
||||
var frontendPath = Path.Combine(zipPath, _codeGenOptions.FrontRootPath, "src", "views", input.PagePath!);
|
||||
var indexPath = Path.Combine(frontendPath, firstLowerTableName, "index.vue");
|
||||
var formModalPath = Path.Combine(frontendPath, firstLowerTableName, "component", "editDialog.vue");
|
||||
var apiJsPath = Path.Combine(zipPath, _codeGenOptions.FrontRootPath, "src", "api", input.PagePath, firstLowerTableName + ".ts");
|
||||
if (input.GenerateType!.StartsWith("11"))
|
||||
{
|
||||
return new List<string>
|
||||
{
|
||||
indexPath,
|
||||
formModalPath,
|
||||
apiJsPath
|
||||
};
|
||||
}
|
||||
|
||||
if (input.GenerateType.StartsWith("12"))
|
||||
{
|
||||
return new List<string>
|
||||
{
|
||||
servicePath,
|
||||
inputPath,
|
||||
outputPath,
|
||||
viewPath
|
||||
};
|
||||
}
|
||||
|
||||
return new List<string>
|
||||
{
|
||||
servicePath,
|
||||
inputPath,
|
||||
outputPath,
|
||||
viewPath,
|
||||
indexPath,
|
||||
formModalPath,
|
||||
apiJsPath
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -673,16 +673,6 @@ public class SysDatabaseService : IDynamicApiController, ITransient
|
||||
private static string GetEntityTargetPath(CreateEntityInput input)
|
||||
{
|
||||
var backendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, input.Position, "Entity");
|
||||
//if (!Directory.Exists(backendPath))
|
||||
//{
|
||||
// var pluginsPath = App.GetConfig<string[]>("AppSettings:ExternalAssemblies");
|
||||
// foreach (var pluginPath in pluginsPath)
|
||||
// {
|
||||
// backendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, pluginPath, input.Position, "Entity");
|
||||
// if (Directory.Exists(backendPath))
|
||||
// break;
|
||||
// }
|
||||
//}
|
||||
if (!Directory.Exists(backendPath))
|
||||
Directory.CreateDirectory(backendPath);
|
||||
return Path.Combine(backendPath, input.EntityName + ".cs");
|
||||
@@ -773,4 +763,4 @@ public class SysDatabaseService : IDynamicApiController, ITransient
|
||||
FileDownloadName = backupFileName
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,188 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 作业持久化(数据库)
|
||||
/// </summary>
|
||||
public class DbJobPersistence : IJobPersistence
|
||||
{
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
|
||||
public DbJobPersistence(IServiceScopeFactory serviceScopeFactory)
|
||||
{
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 作业调度服务启动时
|
||||
/// </summary>
|
||||
/// <param name="stoppingToken"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotSupportedException"></exception>
|
||||
public async Task<IEnumerable<SchedulerBuilder>> PreloadAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<ISqlSugarClient>().CopyNew();
|
||||
var dynamicJobCompiler = scope.ServiceProvider.GetRequiredService<DynamicJobCompiler>();
|
||||
|
||||
// 获取所有定义的作业
|
||||
var allJobs = App.EffectiveTypes.ScanToBuilders().ToList();
|
||||
// 若数据库不存在任何作业,则直接返回
|
||||
if (!await db.Queryable<SysJobDetail>().AnyAsync(u => true, stoppingToken)) return allJobs;
|
||||
|
||||
// 遍历所有定义的作业
|
||||
foreach (var schedulerBuilder in allJobs)
|
||||
{
|
||||
// 获取作业信息构建器
|
||||
var jobBuilder = schedulerBuilder.GetJobBuilder();
|
||||
|
||||
// 加载数据库数据
|
||||
var dbDetail = await db.Queryable<SysJobDetail>().FirstAsync(u => u.JobId == jobBuilder.JobId, stoppingToken);
|
||||
if (dbDetail == null) continue;
|
||||
|
||||
// 同步数据库数据
|
||||
jobBuilder.LoadFrom(dbDetail);
|
||||
|
||||
// 获取作业的所有数据库的触发器
|
||||
var dbTriggers = await db.Queryable<SysJobTrigger>().Where(u => u.JobId == jobBuilder.JobId).ToListAsync(stoppingToken);
|
||||
// 遍历所有作业触发器
|
||||
foreach (var (_, triggerBuilder) in schedulerBuilder.GetEnumerable())
|
||||
{
|
||||
// 加载数据库数据
|
||||
var dbTrigger = dbTriggers.FirstOrDefault(u => u.JobId == jobBuilder.JobId && u.TriggerId == triggerBuilder.TriggerId);
|
||||
if (dbTrigger == null) continue;
|
||||
|
||||
triggerBuilder.LoadFrom(dbTrigger).Updated(); // 标记更新
|
||||
}
|
||||
// 遍历所有非编译时定义的触发器加入到作业中
|
||||
foreach (var dbTrigger in dbTriggers)
|
||||
{
|
||||
if (schedulerBuilder.GetTriggerBuilder(dbTrigger.TriggerId)?.JobId == jobBuilder.JobId) continue;
|
||||
var triggerBuilder = TriggerBuilder.Create(dbTrigger.TriggerId).LoadFrom(dbTrigger);
|
||||
schedulerBuilder.AddTriggerBuilder(triggerBuilder); // 先添加
|
||||
triggerBuilder.Updated(); // 再标记更新
|
||||
}
|
||||
|
||||
// 标记更新
|
||||
schedulerBuilder.Updated();
|
||||
}
|
||||
|
||||
// 获取数据库所有通过脚本创建的作业
|
||||
var allDbScriptJobs = await db.Queryable<SysJobDetail>().Where(u => u.CreateType != JobCreateTypeEnum.BuiltIn).ToListAsync(stoppingToken);
|
||||
foreach (var dbDetail in allDbScriptJobs)
|
||||
{
|
||||
// 动态创建作业
|
||||
Type jobType = dbDetail.CreateType switch
|
||||
{
|
||||
JobCreateTypeEnum.Script => dynamicJobCompiler.BuildJob(dbDetail.ScriptCode),
|
||||
JobCreateTypeEnum.Http => typeof(HttpJob),
|
||||
_ => throw new NotSupportedException(),
|
||||
};
|
||||
|
||||
// 动态构建的 jobType 的程序集名称为随机名称,需重新设置
|
||||
dbDetail.AssemblyName = jobType.Assembly.FullName!.Split(',')[0];
|
||||
var jobBuilder = JobBuilder.Create(jobType).LoadFrom(dbDetail);
|
||||
|
||||
// 强行设置为不扫描 IJob 实现类 [Trigger] 特性触发器,否则 SchedulerBuilder.Create 会再次扫描,导致重复添加同名触发器
|
||||
jobBuilder.SetIncludeAnnotations(false);
|
||||
|
||||
// 获取作业的所有数据库的触发器加入到作业中
|
||||
var dbTriggers = await db.Queryable<SysJobTrigger>().Where(u => u.JobId == jobBuilder.JobId).ToListAsync();
|
||||
var triggerBuilders = dbTriggers.Select(u => TriggerBuilder.Create(u.TriggerId).LoadFrom(u).Updated());
|
||||
var schedulerBuilder = SchedulerBuilder.Create(jobBuilder, triggerBuilders.ToArray());
|
||||
|
||||
// 标记更新
|
||||
schedulerBuilder.Updated();
|
||||
|
||||
allJobs.Add(schedulerBuilder);
|
||||
}
|
||||
|
||||
return allJobs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 作业计划初始化通知
|
||||
/// </summary>
|
||||
/// <param name="builder"></param>
|
||||
/// <param name="stoppingToken"></param>
|
||||
/// <returns></returns>
|
||||
public Task<SchedulerBuilder> OnLoadingAsync(SchedulerBuilder builder, CancellationToken stoppingToken)
|
||||
{
|
||||
return Task.FromResult(builder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 作业计划Scheduler的JobDetail变化时
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public async Task OnChangedAsync(PersistenceContext context)
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<ISqlSugarClient>().CopyNew();
|
||||
|
||||
var jobDetail = context.JobDetail.Adapt<SysJobDetail>();
|
||||
switch (context.Behavior)
|
||||
{
|
||||
case PersistenceBehavior.Appended:
|
||||
await db.Insertable(jobDetail).ExecuteCommandAsync();
|
||||
break;
|
||||
|
||||
case PersistenceBehavior.Updated:
|
||||
await db.Updateable(jobDetail).WhereColumns(u => new { u.JobId }).IgnoreColumns(u => new { u.Id, u.CreateType, u.ScriptCode }).ExecuteCommandAsync();
|
||||
break;
|
||||
|
||||
case PersistenceBehavior.Removed:
|
||||
await db.Deleteable<SysJobDetail>().Where(u => u.JobId == jobDetail.JobId).ExecuteCommandAsync();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 作业计划Scheduler的触发器Trigger变化时
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public async Task OnTriggerChangedAsync(PersistenceTriggerContext context)
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<ISqlSugarClient>().CopyNew();
|
||||
|
||||
var jobTrigger = context.Trigger.Adapt<SysJobTrigger>();
|
||||
switch (context.Behavior)
|
||||
{
|
||||
case PersistenceBehavior.Appended:
|
||||
await db.Insertable(jobTrigger).ExecuteCommandAsync();
|
||||
break;
|
||||
|
||||
case PersistenceBehavior.Updated:
|
||||
await db.Updateable(jobTrigger).WhereColumns(u => new { u.TriggerId, u.JobId }).IgnoreColumns(u => new { u.Id }).ExecuteCommandAsync();
|
||||
break;
|
||||
|
||||
case PersistenceBehavior.Removed:
|
||||
await db.Deleteable<SysJobTrigger>().Where(u => u.TriggerId == jobTrigger.TriggerId && u.JobId == jobTrigger.JobId).ExecuteCommandAsync();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 作业触发器运行记录
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public async Task OnExecutionRecordAsync(PersistenceExecutionRecordContext context)
|
||||
{
|
||||
using var scope = _serviceScopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<ISqlSugarClient>().CopyNew();
|
||||
|
||||
var jobTriggerRecord = context.Timeline.Adapt<SysJobTriggerRecord>();
|
||||
await db.Insertable(jobTriggerRecord).ExecuteCommandAsync();
|
||||
|
||||
await scope.ServiceProvider.GetRequiredService<SysJobService>().ClearExpireJobTriggerRecord(jobTriggerRecord);
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
public class JobDetailInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 作业Id
|
||||
/// </summary>
|
||||
public string JobId { get; set; }
|
||||
}
|
||||
|
||||
public class PageJobDetailInput : BasePageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 作业Id
|
||||
/// </summary>
|
||||
public string JobId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 组名称
|
||||
/// </summary>
|
||||
public string GroupName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 描述信息
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
}
|
||||
|
||||
public class AddJobDetailInput : SysJobDetail
|
||||
{
|
||||
/// <summary>
|
||||
/// 作业Id
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "作业Id不能为空"), MinLength(2, ErrorMessage = "作业Id不能少于2个字符")]
|
||||
public override string JobId { get; set; }
|
||||
}
|
||||
|
||||
public class UpdateJobDetailInput : AddJobDetailInput
|
||||
{
|
||||
}
|
||||
|
||||
public class DeleteJobDetailInput : JobDetailInput
|
||||
{
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
public class JobDetailOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 作业信息
|
||||
/// </summary>
|
||||
public SysJobDetail JobDetail { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 触发器集合
|
||||
/// </summary>
|
||||
public List<SysJobTrigger> JobTriggers { get; set; }
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
public class JobTriggerInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 作业Id
|
||||
/// </summary>
|
||||
public string JobId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 触发器Id
|
||||
/// </summary>
|
||||
public string TriggerId { get; set; }
|
||||
}
|
||||
|
||||
public class AddJobTriggerInput : SysJobTrigger
|
||||
{
|
||||
/// <summary>
|
||||
/// 作业Id
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "作业Id不能为空"), MinLength(2, ErrorMessage = "作业Id不能少于2个字符")]
|
||||
public override string JobId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 触发器Id
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "触发器Id不能为空"), MinLength(2, ErrorMessage = "触发器Id不能少于2个字符")]
|
||||
public override string TriggerId { get; set; }
|
||||
}
|
||||
|
||||
public class UpdateJobTriggerInput : AddJobTriggerInput
|
||||
{
|
||||
}
|
||||
|
||||
public class DeleteJobTriggerInput : JobTriggerInput
|
||||
{
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
public class PageJobTriggerRecordInput : BasePageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 作业Id
|
||||
/// </summary>
|
||||
public string JobId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 触发器Id
|
||||
/// </summary>
|
||||
public string TriggerId { get; set; }
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 作业集群控制
|
||||
/// </summary>
|
||||
public class JobClusterServer : IJobClusterServer
|
||||
{
|
||||
private static readonly SqlSugarRepository<SysJobCluster> _sysJobClusterRep = App.GetRequiredService<SqlSugarRepository<SysJobCluster>>();
|
||||
private readonly Random _random = new(DateTime.Now.Millisecond);
|
||||
|
||||
/// <summary>
|
||||
/// 当前作业调度器启动通知
|
||||
/// </summary>
|
||||
/// <param name="context">作业集群服务上下文</param>
|
||||
public async void Start(JobClusterContext context)
|
||||
{
|
||||
// 在作业集群表中,如果 clusterId 不存在,则新增一条(否则更新一条),并设置 status 为 ClusterStatus.Waiting
|
||||
if (await _sysJobClusterRep.IsAnyAsync(u => u.ClusterId == context.ClusterId))
|
||||
{
|
||||
await _sysJobClusterRep.AsUpdateable().SetColumns(u => u.Status == ClusterStatus.Waiting).Where(u => u.ClusterId == context.ClusterId).ExecuteCommandAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await _sysJobClusterRep.AsInsertable(new SysJobCluster { ClusterId = context.ClusterId, Status = ClusterStatus.Waiting }).ExecuteCommandAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 等待被唤醒
|
||||
/// </summary>
|
||||
/// <param name="context">作业集群服务上下文</param>
|
||||
/// <returns><see cref="Task"/></returns>
|
||||
public async Task WaitingForAsync(JobClusterContext context)
|
||||
{
|
||||
var clusterId = context.ClusterId;
|
||||
|
||||
while (true)
|
||||
{
|
||||
// 控制集群心跳频率(放在头部为了防止 IsAnyAsync continue 没sleep占用大量IO和CPU)
|
||||
await Task.Delay(3000 + _random.Next(500, 1000)); // 错开集群同时启动
|
||||
|
||||
try
|
||||
{
|
||||
ICache cache = App.GetRequiredService<ICacheProvider>().Cache;
|
||||
// 使用分布式锁
|
||||
using (cache.AcquireLock("lock:JobClusterServer:WaitingForAsync", 1000))
|
||||
{
|
||||
// 在这里查询数据库,根据以下两种情况处理
|
||||
// 1) 如果作业集群表已有 status 为 ClusterStatus.Working 则继续循环
|
||||
// 2) 如果作业集群表中还没有其他服务或只有自己,则插入一条集群服务或调用 await WorkNowAsync(clusterId); 之后 return;
|
||||
// 3) 如果作业集群表中没有 status 为 ClusterStatus.Working 的,调用 await WorkNowAsync(clusterId); 之后 return;
|
||||
if (await _sysJobClusterRep.IsAnyAsync(u => u.Status == ClusterStatus.Working)) continue;
|
||||
|
||||
await WorkNowAsync(clusterId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前作业调度器停止通知
|
||||
/// </summary>
|
||||
/// <param name="context">作业集群服务上下文</param>
|
||||
public async void Stop(JobClusterContext context)
|
||||
{
|
||||
// 在作业集群表中,更新 clusterId 的 status 为 ClusterStatus.Crashed
|
||||
await _sysJobClusterRep.UpdateAsync(u => new SysJobCluster { Status = ClusterStatus.Crashed }, u => u.ClusterId == context.ClusterId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前作业调度器宕机
|
||||
/// </summary>
|
||||
/// <param name="context">作业集群服务上下文</param>
|
||||
public async void Crash(JobClusterContext context)
|
||||
{
|
||||
// 在作业集群表中,更新 clusterId 的 status 为 ClusterStatus.Crashed
|
||||
await _sysJobClusterRep.UpdateAsync(u => new SysJobCluster { Status = ClusterStatus.Crashed }, u => u.ClusterId == context.ClusterId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指示集群可以工作
|
||||
/// </summary>
|
||||
/// <param name="clusterId">集群 Id</param>
|
||||
/// <returns></returns>
|
||||
private static async Task WorkNowAsync(string clusterId)
|
||||
{
|
||||
// 在作业集群表中,更新 clusterId 的 status 为 ClusterStatus.Working
|
||||
await _sysJobClusterRep.UpdateAsync(u => new SysJobCluster { Status = ClusterStatus.Working }, u => u.ClusterId == clusterId);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 作业执行监视器
|
||||
/// </summary>
|
||||
public class JobMonitor : IJobMonitor
|
||||
{
|
||||
private readonly SysConfigService _sysConfigService;
|
||||
private readonly IEventPublisher _eventPublisher;
|
||||
private readonly ILogger<JobMonitor> _logger;
|
||||
|
||||
public JobMonitor(IServiceScopeFactory serviceScopeFactory, IEventPublisher eventPublisher, ILogger<JobMonitor> logger)
|
||||
{
|
||||
var serviceScope = serviceScopeFactory.CreateScope();
|
||||
_sysConfigService = serviceScope.ServiceProvider.GetRequiredService<SysConfigService>();
|
||||
_eventPublisher = eventPublisher;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Task OnExecutingAsync(JobExecutingContext context, CancellationToken stoppingToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task OnExecutedAsync(JobExecutedContext context, CancellationToken stoppingToken)
|
||||
{
|
||||
if (context.Exception == null) return;
|
||||
|
||||
var exception = $"定时任务【{context.Trigger.Description}】错误:{context.Exception}";
|
||||
// 将作业异常信息记录到本地
|
||||
_logger.LogError(exception);
|
||||
|
||||
if (await _sysConfigService.GetConfigValue<bool>(ConfigConst.SysErrorMail))
|
||||
{
|
||||
// 将作业异常信息发送到邮件
|
||||
await _eventPublisher.PublishAsync(CommonConst.SendErrorMail, exception, stoppingToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,398 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 系统作业任务服务 🧩
|
||||
/// </summary>
|
||||
[ApiDescriptionSettings(Order = 320, Description = "作业任务")]
|
||||
public class SysJobService : IDynamicApiController, ITransient
|
||||
{
|
||||
private readonly SqlSugarRepository<SysJobDetail> _sysJobDetailRep;
|
||||
private readonly SqlSugarRepository<SysJobTrigger> _sysJobTriggerRep;
|
||||
private readonly SqlSugarRepository<SysJobTriggerRecord> _sysJobTriggerRecordRep;
|
||||
private readonly SqlSugarRepository<SysJobCluster> _sysJobClusterRep;
|
||||
private readonly ISchedulerFactory _schedulerFactory;
|
||||
private readonly DynamicJobCompiler _dynamicJobCompiler;
|
||||
|
||||
public SysJobService(SqlSugarRepository<SysJobDetail> sysJobDetailRep,
|
||||
SqlSugarRepository<SysJobTrigger> sysJobTriggerRep,
|
||||
SqlSugarRepository<SysJobTriggerRecord> sysJobTriggerRecordRep,
|
||||
SqlSugarRepository<SysJobCluster> sysJobClusterRep,
|
||||
ISchedulerFactory schedulerFactory,
|
||||
DynamicJobCompiler dynamicJobCompiler)
|
||||
{
|
||||
_sysJobDetailRep = sysJobDetailRep;
|
||||
_sysJobTriggerRep = sysJobTriggerRep;
|
||||
_sysJobTriggerRecordRep = sysJobTriggerRecordRep;
|
||||
_sysJobClusterRep = sysJobClusterRep;
|
||||
_schedulerFactory = schedulerFactory;
|
||||
_dynamicJobCompiler = dynamicJobCompiler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取作业分页列表 ⏰
|
||||
/// </summary>
|
||||
[DisplayName("获取作业分页列表")]
|
||||
public async Task<SqlSugarPagedList<JobDetailOutput>> PageJobDetail(PageJobDetailInput input)
|
||||
{
|
||||
var jobDetails = await _sysJobDetailRep.AsQueryable()
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.JobId), u => u.JobId.Contains(input.JobId.Trim()))
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.GroupName), u => u.GroupName.Contains(input.GroupName.Trim()))
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.Description), u => u.Description.Contains(input.Description.Trim()))
|
||||
.Select(d => new JobDetailOutput
|
||||
{
|
||||
JobDetail = d,
|
||||
}).ToPagedListAsync(input.Page, input.PageSize);
|
||||
await _sysJobDetailRep.AsSugarClient().ThenMapperAsync(jobDetails.Items, async u =>
|
||||
{
|
||||
u.JobTriggers = await _sysJobTriggerRep.GetListAsync(t => t.JobId == u.JobDetail.JobId);
|
||||
});
|
||||
|
||||
// 提取中括号里面的参数值
|
||||
var rgx = new Regex(@"(?i)(?<=\[)(.*)(?=\])");
|
||||
foreach (var job in jobDetails.Items)
|
||||
{
|
||||
foreach (var jobTrigger in job.JobTriggers)
|
||||
{
|
||||
jobTrigger.Args = rgx.Match(jobTrigger.Args ?? "").Value;
|
||||
}
|
||||
}
|
||||
return jobDetails;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取作业组名称集合 ⏰
|
||||
/// </summary>
|
||||
[DisplayName("获取作业组名称集合")]
|
||||
public async Task<List<string>> ListJobGroup()
|
||||
{
|
||||
return await _sysJobDetailRep.AsQueryable().Distinct().Select(e => e.GroupName).ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加作业 ⏰
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "AddJobDetail"), HttpPost]
|
||||
[DisplayName("添加作业")]
|
||||
public async Task AddJobDetail(AddJobDetailInput input)
|
||||
{
|
||||
var isExist = await _sysJobDetailRep.IsAnyAsync(u => u.JobId == input.JobId && u.Id != input.Id);
|
||||
if (isExist) throw Oops.Oh(ErrorCodeEnum.D1006);
|
||||
|
||||
// 动态创建作业
|
||||
Type jobType;
|
||||
switch (input.CreateType)
|
||||
{
|
||||
case JobCreateTypeEnum.Script when string.IsNullOrEmpty(input.ScriptCode):
|
||||
throw Oops.Oh(ErrorCodeEnum.D1701);
|
||||
case JobCreateTypeEnum.Script:
|
||||
{
|
||||
jobType = _dynamicJobCompiler.BuildJob(input.ScriptCode);
|
||||
|
||||
if (jobType.GetCustomAttributes(typeof(JobDetailAttribute)).FirstOrDefault() is not JobDetailAttribute jobDetailAttribute)
|
||||
throw Oops.Oh(ErrorCodeEnum.D1702);
|
||||
if (jobDetailAttribute.JobId != input.JobId)
|
||||
throw Oops.Oh(ErrorCodeEnum.D1703);
|
||||
break;
|
||||
}
|
||||
case JobCreateTypeEnum.Http:
|
||||
jobType = typeof(HttpJob);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
_schedulerFactory.AddJob(JobBuilder.Create(jobType).LoadFrom(input.Adapt<SysJobDetail>()).SetJobType(jobType));
|
||||
|
||||
// 延迟一下等待持久化写入,再执行其他字段的更新
|
||||
await Task.Delay(500);
|
||||
await _sysJobDetailRep.AsUpdateable()
|
||||
.SetColumns(u => new SysJobDetail { CreateType = input.CreateType, ScriptCode = input.ScriptCode })
|
||||
.Where(u => u.JobId == input.JobId).ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新作业 ⏰
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "UpdateJobDetail"), HttpPost]
|
||||
[DisplayName("更新作业")]
|
||||
public async Task UpdateJobDetail(UpdateJobDetailInput input)
|
||||
{
|
||||
var isExist = await _sysJobDetailRep.IsAnyAsync(u => u.JobId == input.JobId && u.Id != input.Id);
|
||||
if (isExist) throw Oops.Oh(ErrorCodeEnum.D1006);
|
||||
|
||||
var sysJobDetail = await _sysJobDetailRep.GetFirstAsync(u => u.Id == input.Id);
|
||||
if (sysJobDetail.JobId != input.JobId) throw Oops.Oh(ErrorCodeEnum.D1704);
|
||||
|
||||
var scheduler = _schedulerFactory.GetJob(sysJobDetail.JobId);
|
||||
var oldScriptCode = sysJobDetail.ScriptCode; // 旧脚本代码
|
||||
input.Adapt(sysJobDetail);
|
||||
|
||||
if (input.CreateType == JobCreateTypeEnum.Script)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input.ScriptCode)) throw Oops.Oh(ErrorCodeEnum.D1701);
|
||||
|
||||
if (input.ScriptCode != oldScriptCode)
|
||||
{
|
||||
// 动态创建作业
|
||||
var jobType = _dynamicJobCompiler.BuildJob(input.ScriptCode);
|
||||
|
||||
if (jobType.GetCustomAttributes(typeof(JobDetailAttribute)).FirstOrDefault() is not JobDetailAttribute jobDetailAttribute)
|
||||
throw Oops.Oh(ErrorCodeEnum.D1702);
|
||||
if (jobDetailAttribute.JobId != input.JobId) throw Oops.Oh(ErrorCodeEnum.D1703);
|
||||
|
||||
scheduler?.UpdateDetail(JobBuilder.Create(jobType).LoadFrom(sysJobDetail).SetJobType(jobType));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
scheduler?.UpdateDetail(scheduler.GetJobBuilder().LoadFrom(sysJobDetail));
|
||||
}
|
||||
|
||||
// Tip: 假如这次更新有变更了 JobId,变更 JobId 后触发的持久化更新执行,会由于找不到 JobId 而更新不到数据
|
||||
// 延迟一下等待持久化写入,再执行其他字段的更新
|
||||
await Task.Delay(500);
|
||||
await _sysJobDetailRep.UpdateAsync(sysJobDetail);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除作业 ⏰
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "DeleteJobDetail"), HttpPost]
|
||||
[DisplayName("删除作业")]
|
||||
public async Task DeleteJobDetail(DeleteJobDetailInput input)
|
||||
{
|
||||
_schedulerFactory.RemoveJob(input.JobId);
|
||||
|
||||
// 如果 _schedulerFactory 中不存在 JodId,则无法触发持久化,下面的代码确保作业和触发器能被删除
|
||||
await _sysJobDetailRep.DeleteAsync(u => u.JobId == input.JobId);
|
||||
await _sysJobTriggerRep.DeleteAsync(u => u.JobId == input.JobId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取触发器列表 ⏰
|
||||
/// </summary>
|
||||
[DisplayName("获取触发器列表")]
|
||||
public async Task<List<SysJobTrigger>> GetJobTriggerList([FromQuery] JobDetailInput input)
|
||||
{
|
||||
return await _sysJobTriggerRep.AsQueryable()
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.JobId), u => u.JobId.Contains(input.JobId))
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加触发器 ⏰
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "AddJobTrigger"), HttpPost]
|
||||
[DisplayName("添加触发器")]
|
||||
public async Task AddJobTrigger(AddJobTriggerInput input)
|
||||
{
|
||||
var isExist = await _sysJobTriggerRep.IsAnyAsync(u => u.TriggerId == input.TriggerId && u.Id != input.Id);
|
||||
if (isExist) throw Oops.Oh(ErrorCodeEnum.D1006);
|
||||
|
||||
var jobTrigger = input.Adapt<SysJobTrigger>();
|
||||
jobTrigger.Args = "[" + jobTrigger.Args + "]";
|
||||
|
||||
var scheduler = _schedulerFactory.GetJob(input.JobId);
|
||||
scheduler?.AddTrigger(Triggers.Create(input.AssemblyName, input.TriggerType).LoadFrom(jobTrigger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新触发器 ⏰
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "UpdateJobTrigger"), HttpPost]
|
||||
[DisplayName("更新触发器")]
|
||||
public async Task UpdateJobTrigger(UpdateJobTriggerInput input)
|
||||
{
|
||||
var isExist = await _sysJobTriggerRep.IsAnyAsync(u => u.TriggerId == input.TriggerId && u.Id != input.Id);
|
||||
if (isExist) throw Oops.Oh(ErrorCodeEnum.D1006);
|
||||
|
||||
var jobTrigger = input.Adapt<SysJobTrigger>();
|
||||
if (jobTrigger.EndTime.HasValue && jobTrigger.EndTime.Value.Year < 1901)
|
||||
{
|
||||
jobTrigger.EndTime = null;
|
||||
}
|
||||
if (jobTrigger.StartTime.HasValue && jobTrigger.StartTime.Value.Year < 1901)
|
||||
{
|
||||
jobTrigger.StartTime = null;
|
||||
}
|
||||
jobTrigger.Args = "[" + jobTrigger.Args + "]";
|
||||
|
||||
var scheduler = _schedulerFactory.GetJob(input.JobId);
|
||||
scheduler?.UpdateTrigger(Triggers.Create(input.AssemblyName, input.TriggerType).LoadFrom(jobTrigger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除触发器 ⏰
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "DeleteJobTrigger"), HttpPost]
|
||||
[DisplayName("删除触发器")]
|
||||
public async Task DeleteJobTrigger(DeleteJobTriggerInput input)
|
||||
{
|
||||
var scheduler = _schedulerFactory.GetJob(input.JobId);
|
||||
scheduler?.RemoveTrigger(input.TriggerId);
|
||||
|
||||
// 如果 _schedulerFactory 中不存在 JodId,则无法触发持久化,下行代码确保触发器能被删除
|
||||
await _sysJobTriggerRep.DeleteAsync(u => u.JobId == input.JobId && u.TriggerId == input.TriggerId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 暂停所有作业 ⏰
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[DisplayName("暂停所有作业")]
|
||||
public void PauseAllJob()
|
||||
{
|
||||
_schedulerFactory.PauseAll();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动所有作业 ⏰
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[DisplayName("启动所有作业")]
|
||||
public void StartAllJob()
|
||||
{
|
||||
_schedulerFactory.StartAll();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 暂停作业 ⏰
|
||||
/// </summary>
|
||||
[DisplayName("暂停作业")]
|
||||
public void PauseJob(JobDetailInput input)
|
||||
{
|
||||
_schedulerFactory.TryPauseJob(input.JobId, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动作业 ⏰
|
||||
/// </summary>
|
||||
[DisplayName("启动作业")]
|
||||
public void StartJob(JobDetailInput input)
|
||||
{
|
||||
_schedulerFactory.TryStartJob(input.JobId, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消作业 ⏰
|
||||
/// </summary>
|
||||
[DisplayName("取消作业")]
|
||||
public void CancelJob(JobDetailInput input)
|
||||
{
|
||||
_schedulerFactory.TryCancelJob(input.JobId, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行作业 ⏰
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
[DisplayName("执行作业")]
|
||||
public void RunJob(JobDetailInput input)
|
||||
{
|
||||
if (_schedulerFactory.TryRunJob(input.JobId, out _) != ScheduleResult.Succeed) throw Oops.Oh(ErrorCodeEnum.D1705);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 暂停触发器 ⏰
|
||||
/// </summary>
|
||||
[DisplayName("暂停触发器")]
|
||||
public void PauseTrigger(JobTriggerInput input)
|
||||
{
|
||||
var scheduler = _schedulerFactory.GetJob(input.JobId);
|
||||
scheduler?.PauseTrigger(input.TriggerId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动触发器 ⏰
|
||||
/// </summary>
|
||||
[DisplayName("启动触发器")]
|
||||
public void StartTrigger(JobTriggerInput input)
|
||||
{
|
||||
var scheduler = _schedulerFactory.GetJob(input.JobId);
|
||||
scheduler?.StartTrigger(input.TriggerId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 强制唤醒作业调度器 ⏰
|
||||
/// </summary>
|
||||
[DisplayName("强制唤醒作业调度器")]
|
||||
public void CancelSleep()
|
||||
{
|
||||
_schedulerFactory.CancelSleep();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 强制触发所有作业持久化 ⏰
|
||||
/// </summary>
|
||||
[DisplayName("强制触发所有作业持久化")]
|
||||
public void PersistAll()
|
||||
{
|
||||
_schedulerFactory.PersistAll();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取集群列表 ⏰
|
||||
/// </summary>
|
||||
[DisplayName("获取集群列表")]
|
||||
public async Task<List<SysJobCluster>> GetJobClusterList()
|
||||
{
|
||||
return await _sysJobClusterRep.GetListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取作业触发器运行记录分页列表 ⏰
|
||||
/// </summary>
|
||||
[DisplayName("获取作业触发器运行记录分页列表")]
|
||||
public async Task<SqlSugarPagedList<SysJobTriggerRecord>> PageJobTriggerRecord(PageJobTriggerRecordInput input)
|
||||
{
|
||||
return await _sysJobTriggerRecordRep.AsQueryable()
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.JobId), u => u.JobId == input.JobId)
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.TriggerId), u => u.TriggerId == input.TriggerId)
|
||||
.OrderByDescending(u => u.Id)
|
||||
.ToPagedListAsync(input.Page, input.PageSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空作业触发器运行记录 🔖
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "ClearJobTriggerRecord"), HttpPost]
|
||||
[DisplayName("清空作业触发器运行记录")]
|
||||
public void ClearJobTriggerRecord()
|
||||
{
|
||||
_sysJobTriggerRecordRep.AsSugarClient().DbMaintenance.TruncateTable<SysJobTriggerRecord>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空不保留的作业触发器运行记录 🔖
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[NonAction]
|
||||
[DisplayName("清空过期的作业触发器运行记录")]
|
||||
public async Task ClearExpireJobTriggerRecord(SysJobTriggerRecord input)
|
||||
{
|
||||
int keepRecords = 30;//保留记录条数
|
||||
// 使用CopyNew()创建新的数据库连接实例,避免连接冲突
|
||||
var db = _sysJobTriggerRecordRep.AsSugarClient().CopyNew();
|
||||
await db.Deleteable<SysJobTriggerRecord>().In(it => it.Id,
|
||||
db.Queryable<SysJobTriggerRecord>()
|
||||
.Skip(keepRecords)
|
||||
.OrderByDescending(it => it.LastRunTime)
|
||||
.Where(u => u.JobId == input.JobId && u.TriggerId == input.TriggerId)
|
||||
.Select(it => it.Id) //注意Select不要ToList(), ToList就2次查询了
|
||||
).ExecuteCommandAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
public static class OAuthClaim
|
||||
{
|
||||
public const string GiteeAvatarUrl = "urn:gitee:avatar_url";
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
||||
namespace Admin.NET.Core;
|
||||
|
||||
public static class OAuthSetup
|
||||
{
|
||||
/// <summary>
|
||||
/// 三方授权登录OAuth注册
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
public static void AddOAuth(this IServiceCollection services)
|
||||
{
|
||||
var authOpt = App.GetConfig<OAuthOptions>("OAuth", true);
|
||||
services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
})
|
||||
.AddCookie(options =>
|
||||
{
|
||||
options.Cookie.SameSite = SameSiteMode.Lax;
|
||||
})
|
||||
.AddWeixin(options =>
|
||||
{
|
||||
options.ClientId = authOpt.Weixin?.ClientId!;
|
||||
options.ClientSecret = authOpt.Weixin?.ClientSecret!;
|
||||
})
|
||||
.AddGitee(options =>
|
||||
{
|
||||
options.ClientId = authOpt.Gitee?.ClientId!;
|
||||
options.ClientSecret = authOpt.Gitee?.ClientSecret!;
|
||||
|
||||
options.ClaimActions.MapJsonKey(OAuthClaim.GiteeAvatarUrl, "avatar_url");
|
||||
});
|
||||
}
|
||||
|
||||
public static void UseOAuth(this IApplicationBuilder app)
|
||||
{
|
||||
app.UseCookiePolicy(new CookiePolicyOptions { MinimumSameSitePolicy = SameSiteMode.Lax });
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 系统OAuth服务 🧩
|
||||
/// </summary>
|
||||
[AllowAnonymous]
|
||||
[ApiDescriptionSettings(Order = 498)]
|
||||
public class SysOAuthService : IDynamicApiController, ITransient
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly SqlSugarRepository<SysWechatUser> _sysWechatUserRep;
|
||||
|
||||
public SysOAuthService(IHttpContextAccessor httpContextAccessor,
|
||||
SqlSugarRepository<SysWechatUser> sysWechatUserRep)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_sysWechatUserRep = sysWechatUserRep;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 第三方登录 🔖
|
||||
/// </summary>
|
||||
/// <param name="provider"></param>
|
||||
/// <param name="redirectUrl"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "SignIn"), HttpGet]
|
||||
[DisplayName("第三方登录")]
|
||||
public virtual async Task<IActionResult> SignIn([FromQuery] string provider, [FromQuery] string redirectUrl)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(provider) || !await _httpContextAccessor.HttpContext.IsProviderSupportedAsync(provider))
|
||||
throw Oops.Oh("不支持的OAuth类型");
|
||||
|
||||
var request = _httpContextAccessor.HttpContext!.Request;
|
||||
var url = $"{request.Scheme}://{request.Host}{request.PathBase}{request.Path}Callback?provider={provider}&redirectUrl={redirectUrl}";
|
||||
var properties = new AuthenticationProperties
|
||||
{
|
||||
RedirectUri = url,
|
||||
Items = { ["LoginProvider"] = provider }
|
||||
};
|
||||
return await Task.FromResult(new ChallengeResult(provider, properties));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 授权回调 🔖
|
||||
/// </summary>
|
||||
/// <param name="provider"></param>
|
||||
/// <param name="redirectUrl"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "SignInCallback"), HttpGet]
|
||||
[DisplayName("授权回调")]
|
||||
public virtual async Task<IActionResult> SignInCallback([FromQuery] string provider = null, [FromQuery] string redirectUrl = "")
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(provider) || !await _httpContextAccessor.HttpContext.IsProviderSupportedAsync(provider))
|
||||
throw Oops.Oh("不支持的OAuth类型");
|
||||
|
||||
var authenticateResult = await _httpContextAccessor.HttpContext!.AuthenticateAsync(provider);
|
||||
if (!authenticateResult.Succeeded)
|
||||
throw Oops.Oh("授权失败");
|
||||
|
||||
var openIdClaim = authenticateResult.Principal.FindFirst(ClaimTypes.NameIdentifier);
|
||||
if (openIdClaim == null || string.IsNullOrWhiteSpace(openIdClaim.Value))
|
||||
throw Oops.Oh("授权失败");
|
||||
|
||||
var name = authenticateResult.Principal.FindFirst(ClaimTypes.Name)?.Value;
|
||||
var email = authenticateResult.Principal.FindFirst(ClaimTypes.Email)?.Value;
|
||||
var mobilePhone = authenticateResult.Principal.FindFirst(ClaimTypes.MobilePhone)?.Value;
|
||||
var dateOfBirth = authenticateResult.Principal.FindFirst(ClaimTypes.DateOfBirth)?.Value;
|
||||
var gender = authenticateResult.Principal.FindFirst(ClaimTypes.Gender)?.Value;
|
||||
var avatarUrl = "";
|
||||
|
||||
var platformType = PlatformTypeEnum.微信公众号;
|
||||
if (provider == "Gitee")
|
||||
{
|
||||
platformType = PlatformTypeEnum.Gitee;
|
||||
avatarUrl = authenticateResult.Principal.FindFirst(OAuthClaim.GiteeAvatarUrl)?.Value;
|
||||
}
|
||||
|
||||
// 若账号不存在则新建
|
||||
var wechatUser = await _sysWechatUserRep.AsQueryable().Includes(u => u.SysUser).ClearFilter().FirstAsync(u => u.OpenId == openIdClaim.Value);
|
||||
if (wechatUser == null)
|
||||
{
|
||||
var userId = await App.GetRequiredService<SysUserService>().AddUser(new AddUserInput()
|
||||
{
|
||||
Account = name,
|
||||
RealName = name,
|
||||
NickName = name,
|
||||
Email = email,
|
||||
Avatar = avatarUrl,
|
||||
Phone = mobilePhone,
|
||||
OrgId = 1300000000101, // 根组织架构
|
||||
RoleIdList = new List<long> { 1300000000104 } // 仅本人数据角色
|
||||
});
|
||||
|
||||
await _sysWechatUserRep.InsertAsync(new SysWechatUser()
|
||||
{
|
||||
UserId = userId,
|
||||
OpenId = openIdClaim.Value,
|
||||
Avatar = avatarUrl,
|
||||
NickName = name,
|
||||
PlatformType = platformType
|
||||
});
|
||||
|
||||
wechatUser = await _sysWechatUserRep.AsQueryable().Includes(u => u.SysUser).ClearFilter().FirstAsync(u => u.OpenId == openIdClaim.Value);
|
||||
}
|
||||
|
||||
// 构建Token令牌
|
||||
var token = await App.GetRequiredService<SysAuthService>().CreateToken(wechatUser.SysUser);
|
||||
|
||||
return new RedirectResult($"{redirectUrl}/#/login?token={token.AccessToken}");
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
public class PagePluginInput : BasePageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 编码
|
||||
/// </summary>
|
||||
public string Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 租户Id
|
||||
/// </summary>
|
||||
public long TenantId { get; set; }
|
||||
}
|
||||
|
||||
public class AddPluginInput : SysPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "功能名称不能为空")]
|
||||
public override string Name { get; set; }
|
||||
}
|
||||
|
||||
public class UpdatePluginInput : AddPluginInput
|
||||
{
|
||||
}
|
||||
|
||||
public class DeletePluginInput : BaseIdInput
|
||||
{
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 系统动态插件服务 🧩
|
||||
/// </summary>
|
||||
[ApiDescriptionSettings(Order = 245)]
|
||||
public class SysPluginService : IDynamicApiController, ITransient
|
||||
{
|
||||
private readonly IDynamicApiRuntimeChangeProvider _provider;
|
||||
private readonly SqlSugarRepository<SysPlugin> _sysPluginRep;
|
||||
private readonly UserManager _userManager;
|
||||
|
||||
public SysPluginService(IDynamicApiRuntimeChangeProvider provider,
|
||||
SqlSugarRepository<SysPlugin> sysPluginRep,
|
||||
UserManager userManager)
|
||||
{
|
||||
_provider = provider;
|
||||
_userManager = userManager;
|
||||
_sysPluginRep = sysPluginRep;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取动态插件列表 🧩
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取动态插件列表")]
|
||||
public async Task<SqlSugarPagedList<SysPlugin>> Page(PagePluginInput input)
|
||||
{
|
||||
return await _sysPluginRep.AsQueryable()
|
||||
.WhereIF(_userManager.SuperAdmin && input.TenantId > 0, u => u.TenantId == input.TenantId)
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.Name), u => u.Name.Contains(input.Name))
|
||||
.OrderBy(u => new { u.OrderNo, u.Id })
|
||||
.ToPagedListAsync(input.Page, input.PageSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加动态插件 🧩
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "Add"), HttpPost]
|
||||
[DisplayName("增加动态插件")]
|
||||
public async Task AddPlugin(AddPluginInput input)
|
||||
{
|
||||
var isExist = await _sysPluginRep.IsAnyAsync(u => u.Name == input.Name || u.AssemblyName == input.AssemblyName);
|
||||
if (isExist) throw Oops.Oh(ErrorCodeEnum.D1900);
|
||||
|
||||
// 添加动态程序集/接口
|
||||
input.AssemblyName = CompileAssembly(input.CsharpCode, input.AssemblyName);
|
||||
|
||||
await _sysPluginRep.InsertAsync(input.Adapt<SysPlugin>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新动态插件 🧩
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "Update"), HttpPost]
|
||||
[DisplayName("更新动态插件")]
|
||||
public async Task UpdatePlugin(UpdatePluginInput input)
|
||||
{
|
||||
var isExist = await _sysPluginRep.IsAnyAsync(u => (u.Name == input.Name || u.AssemblyName == input.AssemblyName) && u.Id != input.Id);
|
||||
if (isExist) throw Oops.Oh(ErrorCodeEnum.D1900);
|
||||
|
||||
// 先移除再添加动态程序集/接口
|
||||
RemoveAssembly(input.AssemblyName);
|
||||
input.AssemblyName = CompileAssembly(input.CsharpCode);
|
||||
|
||||
await _sysPluginRep.AsUpdateable(input.Adapt<SysPlugin>()).IgnoreColumns(true).ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除动态插件 🧩
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "Delete"), HttpPost]
|
||||
[DisplayName("删除动态插件")]
|
||||
public async Task DeletePlugin(DeletePluginInput input)
|
||||
{
|
||||
var plugin = await _sysPluginRep.GetByIdAsync(input.Id);
|
||||
if (plugin == null) return;
|
||||
|
||||
// 移除动态程序集/接口
|
||||
RemoveAssembly(plugin.AssemblyName);
|
||||
|
||||
await _sysPluginRep.DeleteAsync(u => u.Id == input.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加动态程序集/接口 🧩
|
||||
/// </summary>
|
||||
/// <param name="csharpCode"></param>
|
||||
/// <param name="assemblyName">程序集名称</param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("添加动态程序集/接口")]
|
||||
public string CompileAssembly([FromBody] string csharpCode, [FromQuery] string assemblyName = default)
|
||||
{
|
||||
// 编译 C# 代码并返回动态程序集
|
||||
var dynamicAssembly = App.CompileCSharpClassCode(csharpCode, assemblyName);
|
||||
|
||||
// 将程序集添加进动态 WebAPI 应用部件
|
||||
_provider.AddAssembliesWithNotifyChanges(dynamicAssembly);
|
||||
|
||||
// 返回动态程序集名称
|
||||
return dynamicAssembly.GetName().Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除动态程序集/接口 🧩
|
||||
/// </summary>
|
||||
[ApiDescriptionSettings(Name = "RemoveAssembly"), HttpPost]
|
||||
[DisplayName("移除动态程序集/接口")]
|
||||
public void RemoveAssembly(string assemblyName)
|
||||
{
|
||||
_provider.RemoveAssembliesWithNotifyChanges(assemblyName);
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
public class PagePrintInput : BasePageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 编码
|
||||
/// </summary>
|
||||
public string Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 租户Id
|
||||
/// </summary>
|
||||
public long TenantId { get; set; }
|
||||
}
|
||||
|
||||
public class AddPrintInput : SysPrint
|
||||
{
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "模板名称不能为空")]
|
||||
public override string Name { get; set; }
|
||||
}
|
||||
|
||||
public class UpdatePrintInput : AddPrintInput
|
||||
{
|
||||
}
|
||||
|
||||
public class DeletePrintInput : BaseIdInput
|
||||
{
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 系统打印模板服务 🧩
|
||||
/// </summary>
|
||||
[ApiDescriptionSettings(Order = 305)]
|
||||
public class SysPrintService : IDynamicApiController, ITransient
|
||||
{
|
||||
private readonly SqlSugarRepository<SysPrint> _sysPrintRep;
|
||||
private readonly UserManager _userManager;
|
||||
|
||||
public SysPrintService(SqlSugarRepository<SysPrint> sysPrintRep, UserManager userManager)
|
||||
{
|
||||
_sysPrintRep = sysPrintRep;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取打印模板列表 🖨️
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取打印模板列表")]
|
||||
public async Task<SqlSugarPagedList<SysPrint>> Page(PagePrintInput input)
|
||||
{
|
||||
return await _sysPrintRep.AsQueryable()
|
||||
.WhereIF(_userManager.SuperAdmin && input.TenantId > 0, u => u.TenantId == input.TenantId)
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.Name), u => u.Name.Contains(input.Name))
|
||||
.OrderBy(u => new { u.OrderNo, u.Id })
|
||||
.ToPagedListAsync(input.Page, input.PageSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取打印模板 🖨️
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取打印模板")]
|
||||
public async Task<SysPrint> GetPrint(string name)
|
||||
{
|
||||
return await _sysPrintRep.GetFirstAsync(u => u.Name == name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加打印模板 🖨️
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "Add"), HttpPost]
|
||||
[DisplayName("增加打印模板")]
|
||||
public async Task AddPrint(AddPrintInput input)
|
||||
{
|
||||
var isExist = await _sysPrintRep.IsAnyAsync(u => u.Name == input.Name);
|
||||
if (isExist) throw Oops.Oh(ErrorCodeEnum.D1800);
|
||||
|
||||
await _sysPrintRep.InsertAsync(input.Adapt<SysPrint>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新打印模板 🖨️
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "Update"), HttpPost]
|
||||
[DisplayName("更新打印模板")]
|
||||
public async Task UpdatePrint(UpdatePrintInput input)
|
||||
{
|
||||
var isExist = await _sysPrintRep.IsAnyAsync(u => u.Name == input.Name && u.Id != input.Id);
|
||||
if (isExist) throw Oops.Oh(ErrorCodeEnum.D1800);
|
||||
|
||||
await _sysPrintRep.AsUpdateable(input.Adapt<SysPrint>()).IgnoreColumns(true).ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除打印模板 🖨️
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "Delete"), HttpPost]
|
||||
[DisplayName("删除打印模板")]
|
||||
public async Task DeletePrint(DeletePrintInput input)
|
||||
{
|
||||
await _sysPrintRep.DeleteAsync(u => u.Id == input.Id);
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
public class ScheduleInput : BaseIdInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
public virtual FinishStatusEnum Status { get; set; }
|
||||
}
|
||||
|
||||
public class ListScheduleInput
|
||||
{
|
||||
public DateTime? StartTime { get; set; }
|
||||
|
||||
public DateTime? EndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 租户Id
|
||||
/// </summary>
|
||||
public long TenantId { get; set; }
|
||||
}
|
||||
|
||||
public class AddScheduleInput : SysSchedule
|
||||
{
|
||||
/// <summary>
|
||||
/// 日程内容
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "日程内容不能为空")]
|
||||
public override string Content { get; set; }
|
||||
}
|
||||
|
||||
public class UpdateScheduleInput : AddScheduleInput
|
||||
{
|
||||
}
|
||||
|
||||
public class DeleteScheduleInput : BaseIdInput
|
||||
{
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 系统日程服务
|
||||
/// </summary>
|
||||
[ApiDescriptionSettings(Order = 295)]
|
||||
public class SysScheduleService : IDynamicApiController, ITransient
|
||||
{
|
||||
private readonly UserManager _userManager;
|
||||
private readonly SqlSugarRepository<SysSchedule> _sysSchedule;
|
||||
|
||||
public SysScheduleService(UserManager userManager,
|
||||
SqlSugarRepository<SysSchedule> sysSchedule)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_sysSchedule = sysSchedule;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取日程列表
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取日程列表")]
|
||||
public async Task<List<SysSchedule>> Page(ListScheduleInput input)
|
||||
{
|
||||
return await _sysSchedule.AsQueryable()
|
||||
.Where(u => u.UserId == _userManager.UserId)
|
||||
.WhereIF(_userManager.SuperAdmin && input.TenantId > 0, u => u.TenantId == input.TenantId)
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.StartTime.ToString()), u => u.ScheduleTime >= input.StartTime)
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.EndTime.ToString()), u => u.ScheduleTime <= input.EndTime)
|
||||
.OrderBy(u => u.StartTime, OrderByType.Asc)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取日程详情
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取日程详情")]
|
||||
public async Task<SysSchedule> GetDetail(long id)
|
||||
{
|
||||
return await _sysSchedule.GetFirstAsync(u => u.Id == id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加日程
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "Add"), HttpPost]
|
||||
[DisplayName("增加日程")]
|
||||
public async Task AddUserSchedule(AddScheduleInput input)
|
||||
{
|
||||
input.UserId = _userManager.UserId;
|
||||
await _sysSchedule.InsertAsync(input.Adapt<SysSchedule>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新日程
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "Update"), HttpPost]
|
||||
[DisplayName("更新日程")]
|
||||
public async Task UpdateUserSchedule(UpdateScheduleInput input)
|
||||
{
|
||||
await _sysSchedule.AsUpdateable(input.Adapt<SysSchedule>()).IgnoreColumns(true).ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除日程
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "Delete"), HttpPost]
|
||||
[DisplayName("删除日程")]
|
||||
public async Task DeleteUserSchedule(DeleteScheduleInput input)
|
||||
{
|
||||
await _sysSchedule.DeleteAsync(u => u.Id == input.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置日程状态
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("设置日程状态")]
|
||||
public async Task<int> SetStatus(ScheduleInput input)
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(FinishStatusEnum), input.Status)) throw Oops.Oh(ErrorCodeEnum.D3005);
|
||||
|
||||
return await _sysSchedule.AsUpdateable()
|
||||
.SetColumns(u => u.Status == input.Status)
|
||||
.Where(u => u.Id == input.Id)
|
||||
.ExecuteCommandAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
@@ -136,8 +136,6 @@ public class SysServerService : IDynamicApiController, ITransient
|
||||
var pdfAssembly = typeof(Magicodes.ExporterAndImporter.Pdf.IPdfExporter).Assembly.GetName();
|
||||
var wordAssembly = typeof(Magicodes.ExporterAndImporter.Word.IWordExporter).Assembly.GetName();
|
||||
var captchaAssembly = typeof(Lazy.Captcha.Core.ICaptcha).Assembly.GetName();
|
||||
var wechatApiAssembly = typeof(WechatApiClient).Assembly.GetName();
|
||||
var wechatTenpayAssembly = typeof(WechatTenpayClient).Assembly.GetName();
|
||||
var ossAssembly = typeof(OnceMi.AspNetCore.OSS.IOSSServiceFactory).Assembly.GetName();
|
||||
var parserAssembly = typeof(Parser).Assembly.GetName();
|
||||
var elasticsearchClientAssembly = typeof(Elastic.Clients.Elasticsearch.ElasticsearchClient).Assembly.GetName();
|
||||
@@ -148,10 +146,7 @@ public class SysServerService : IDynamicApiController, ITransient
|
||||
var alibabaSendSmsRequestAssembly = typeof(AlibabaCloud.SDK.Dysmsapi20170525.Models.SendSmsRequest).Assembly.GetName();
|
||||
var tencentSendSmsRequestAssembly = typeof(TencentCloud.Sms.V20190711.Models.SendSmsRequest).Assembly.GetName();
|
||||
var rabbitMQAssembly = typeof(RabbitMQEventSourceStore).Assembly.GetName();
|
||||
var ldapConnectionAssembly = typeof(Novell.Directory.Ldap.LdapConnection).Assembly.GetName();
|
||||
var ipToolAssembly = typeof(IPTools.Core.IpTool).Assembly.GetName();
|
||||
var weixinAuthenticationOptionsAssembly = typeof(AspNet.Security.OAuth.Weixin.WeixinAuthenticationOptions).Assembly.GetName();
|
||||
var giteeAuthenticationOptionsAssembly = typeof(AspNet.Security.OAuth.Gitee.GiteeAuthenticationOptions).Assembly.GetName();
|
||||
var hashidsAssembly = typeof(HashidsNet.Hashids).Assembly.GetName();
|
||||
var sftpClientAssembly = typeof(Renci.SshNet.SftpClient).Assembly.GetName();
|
||||
var hardwareInfoAssembly = typeof(Hardware.Info.HardwareInfo).Assembly.GetName();
|
||||
@@ -167,8 +162,6 @@ public class SysServerService : IDynamicApiController, ITransient
|
||||
new { pdfAssembly.Name, pdfAssembly.Version },
|
||||
new { wordAssembly.Name, wordAssembly.Version },
|
||||
new { captchaAssembly.Name, captchaAssembly.Version },
|
||||
new { wechatApiAssembly.Name, wechatApiAssembly.Version },
|
||||
new { wechatTenpayAssembly.Name, wechatTenpayAssembly.Version },
|
||||
new { ossAssembly.Name, ossAssembly.Version },
|
||||
new { parserAssembly.Name, parserAssembly.Version },
|
||||
new { elasticsearchClientAssembly.Name, elasticsearchClientAssembly.Version },
|
||||
@@ -179,13 +172,10 @@ public class SysServerService : IDynamicApiController, ITransient
|
||||
new { alibabaSendSmsRequestAssembly.Name, alibabaSendSmsRequestAssembly.Version },
|
||||
new { tencentSendSmsRequestAssembly.Name, tencentSendSmsRequestAssembly.Version },
|
||||
new { rabbitMQAssembly.Name, rabbitMQAssembly.Version },
|
||||
new { ldapConnectionAssembly.Name, ldapConnectionAssembly.Version },
|
||||
new { ipToolAssembly.Name, ipToolAssembly.Version },
|
||||
new { weixinAuthenticationOptionsAssembly.Name, weixinAuthenticationOptionsAssembly.Version },
|
||||
new { giteeAuthenticationOptionsAssembly.Name, giteeAuthenticationOptionsAssembly.Version },
|
||||
new { hashidsAssembly.Name, hashidsAssembly.Version },
|
||||
new { sftpClientAssembly.Name, sftpClientAssembly.Version },
|
||||
new { hardwareInfoAssembly.Name, hardwareInfoAssembly.Version },
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
@@ -65,12 +65,6 @@ public class AddUserInput : SysUser
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "真实姓名不能为空")]
|
||||
public override string RealName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 域用户
|
||||
/// </summary>
|
||||
public string DomainAccount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色集合
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
@@ -21,10 +21,4 @@ public class UserOutput : SysUser
|
||||
/// <summary>
|
||||
/// 角色名称
|
||||
/// </summary>
|
||||
public string RoleName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 域用户
|
||||
/// </summary>
|
||||
public string DomainAccount { get; set; }
|
||||
}
|
||||
public string RoleName { get; set; }}
|
||||
@@ -1,66 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 用户域账号服务
|
||||
/// </summary>
|
||||
public class SysUserLdapService : ITransient
|
||||
{
|
||||
private readonly SqlSugarRepository<SysUserLdap> _sysUserLdapRep;
|
||||
|
||||
public SysUserLdapService(SqlSugarRepository<SysUserLdap> sysUserLdapRep)
|
||||
{
|
||||
_sysUserLdapRep = sysUserLdapRep;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量插入域账号
|
||||
/// </summary>
|
||||
/// <param name="tenantId"></param>
|
||||
/// <param name="sysUserLdapList"></param>
|
||||
/// <returns></returns>
|
||||
public async Task InsertUserLdapList(long tenantId, List<SysUserLdap> sysUserLdapList)
|
||||
{
|
||||
await _sysUserLdapRep.DeleteAsync(u => u.TenantId == tenantId);
|
||||
|
||||
await _sysUserLdapRep.InsertRangeAsync(sysUserLdapList);
|
||||
|
||||
await _sysUserLdapRep.AsUpdateable()
|
||||
.InnerJoin<SysUser>((l, u) => l.EmployeeId == u.Account)
|
||||
.SetColumns((l, u) => new SysUserLdap { UserId = u.Id })
|
||||
.Where((l, u) => l.TenantId == tenantId && u.Status == StatusEnum.Enable)
|
||||
.ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加域账号
|
||||
/// </summary>
|
||||
/// <param name="tenantId"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="account"></param>
|
||||
/// <param name="domainAccount"></param>
|
||||
/// <returns></returns>
|
||||
public async Task AddUserLdap(long tenantId, long userId, string account, string domainAccount)
|
||||
{
|
||||
var userLdap = await _sysUserLdapRep.GetFirstAsync(u => u.TenantId == tenantId && (u.Account == account || u.UserId == userId || u.EmployeeId == domainAccount));
|
||||
if (userLdap != null) await _sysUserLdapRep.DeleteByIdAsync(userLdap.Id);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(domainAccount))
|
||||
await _sysUserLdapRep.InsertAsync(new SysUserLdap { EmployeeId = account, TenantId = tenantId, UserId = userId, Account = domainAccount });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除域账号
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task DeleteUserLdapByUserId(long userId)
|
||||
{
|
||||
await _sysUserLdapRep.DeleteAsync(u => u.UserId == userId);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ public class SysUserService : IDynamicApiController, ITransient
|
||||
private readonly SysOnlineUserService _sysOnlineUserService;
|
||||
private readonly SysUserMenuService _sysUserMenuService;
|
||||
private readonly SysCacheService _sysCacheService;
|
||||
private readonly SysUserLdapService _sysUserLdapService;
|
||||
private readonly SqlSugarRepository<SysUser> _sysUserRep;
|
||||
private readonly IEventPublisher _eventPublisher;
|
||||
|
||||
@@ -31,7 +30,6 @@ public class SysUserService : IDynamicApiController, ITransient
|
||||
SysConfigService sysConfigService,
|
||||
SysOnlineUserService sysOnlineUserService,
|
||||
SysCacheService sysCacheService,
|
||||
SysUserLdapService sysUserLdapService,
|
||||
SqlSugarRepository<SysUser> sysUserRep,
|
||||
SysUserMenuService sysUserMenuService,
|
||||
IEventPublisher eventPublisher)
|
||||
@@ -43,7 +41,6 @@ public class SysUserService : IDynamicApiController, ITransient
|
||||
_sysConfigService = sysConfigService;
|
||||
_sysOnlineUserService = sysOnlineUserService;
|
||||
_sysCacheService = sysCacheService;
|
||||
_sysUserLdapService = sysUserLdapService;
|
||||
_sysUserMenuService = sysUserMenuService;
|
||||
_sysUserRep = sysUserRep;
|
||||
_eventPublisher = eventPublisher;
|
||||
@@ -77,7 +74,6 @@ public class SysUserService : IDynamicApiController, ITransient
|
||||
OrgName = a.Name,
|
||||
PosName = b.Name,
|
||||
RoleName = SqlFunc.Subqueryable<SysUserRole>().LeftJoin<SysRole>((m, n) => m.RoleId == n.Id).Where(m => m.UserId == u.Id).SelectStringJoin((m, n) => n.Name, ","),
|
||||
DomainAccount = SqlFunc.Subqueryable<SysUserLdap>().Where(m => m.UserId == u.Id).Select(m => m.Account)
|
||||
}, true)
|
||||
.ToPagedListAsync(input.Page, input.PageSize);
|
||||
}
|
||||
@@ -110,10 +106,6 @@ public class SysUserService : IDynamicApiController, ITransient
|
||||
input.Id = newUser.Id;
|
||||
await UpdateRoleAndExtOrg(input);
|
||||
|
||||
// 增加域账号
|
||||
if (!string.IsNullOrWhiteSpace(input.DomainAccount))
|
||||
await _sysUserLdapService.AddUserLdap(newUser.TenantId!.Value, newUser.Id, newUser.Account, input.DomainAccount);
|
||||
|
||||
// 发布系统用户操作事件
|
||||
await _eventPublisher.PublishAsync(SysUserEventTypeEnum.Add, new
|
||||
{
|
||||
@@ -153,10 +145,6 @@ public class SysUserService : IDynamicApiController, ITransient
|
||||
input.Id = newUser.Id;
|
||||
await UpdateRoleAndExtOrg(input);
|
||||
|
||||
// 增加域账号
|
||||
if (!string.IsNullOrWhiteSpace(input.DomainAccount))
|
||||
await _sysUserLdapService.AddUserLdap(newUser.TenantId!.Value, newUser.Id, newUser.Account, input.DomainAccount);
|
||||
|
||||
// 发布系统用户操作事件
|
||||
await _eventPublisher.PublishAsync(SysUserEventTypeEnum.Register, new
|
||||
{
|
||||
@@ -199,8 +187,6 @@ public class SysUserService : IDynamicApiController, ITransient
|
||||
var roleIds = await GetOwnRoleList(input.Id);
|
||||
if (input.OrgId != user.OrgId || !input.RoleIdList.OrderBy(u => u).SequenceEqual(roleIds.OrderBy(u => u)))
|
||||
await _sysOnlineUserService.ForceOffline(input.Id);
|
||||
// 更新域账号
|
||||
await _sysUserLdapService.AddUserLdap(user.TenantId!.Value, user.Id, user.Account, input.DomainAccount);
|
||||
|
||||
// 发布系统用户操作事件
|
||||
await _eventPublisher.PublishAsync(SysUserEventTypeEnum.Update, new
|
||||
@@ -270,9 +256,6 @@ public class SysUserService : IDynamicApiController, ITransient
|
||||
// 删除用户扩展机构
|
||||
await _sysUserExtOrgService.DeleteUserExtOrgByUserId(input.Id);
|
||||
|
||||
// 删除域账号
|
||||
await _sysUserLdapService.DeleteUserLdapByUserId(input.Id);
|
||||
|
||||
// 删除用户收藏菜单
|
||||
await _sysUserMenuService.DeleteUserMenuList(input.Id);
|
||||
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 生成网页授权Url
|
||||
/// </summary>
|
||||
public class GenAuthUrlInput
|
||||
{
|
||||
/// <summary>
|
||||
/// RedirectUrl
|
||||
/// </summary>
|
||||
public string RedirectUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Scope
|
||||
/// </summary>
|
||||
public string Scope { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// State
|
||||
/// </summary>
|
||||
public string State { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取微信用户OpenId
|
||||
/// </summary>
|
||||
public class WechatOAuth2Input
|
||||
{
|
||||
/// <summary>
|
||||
/// Code
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Code不能为空"), MinLength(10, ErrorMessage = "Code错误")]
|
||||
public string Code { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信用户登录
|
||||
/// </summary>
|
||||
public class WechatUserLogin
|
||||
{
|
||||
/// <summary>
|
||||
/// OpenId
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "微信标识不能为空"), MinLength(10, ErrorMessage = "微信标识长错误")]
|
||||
public string OpenId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取配置签名
|
||||
/// </summary>
|
||||
public class SignatureInput
|
||||
{
|
||||
/// <summary>
|
||||
/// Url
|
||||
/// </summary>
|
||||
public string Url { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取消息模板列表
|
||||
/// </summary>
|
||||
public class MessageTemplateSendInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 订阅模板Id
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "订阅模板Id不能为空")]
|
||||
public string TemplateId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 接收者的OpenId
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "接收者的OpenId不能为空")]
|
||||
public string ToUserOpenId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模板数据,格式形如 { "key1": { "value": any }, "key2": { "value": any } }
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "模板数据不能为空")]
|
||||
public Dictionary<string, CgibinMessageSubscribeSendRequest.Types.DataItem> Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模板跳转链接
|
||||
/// </summary>
|
||||
public string Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 所需跳转到小程序的具体页面路径,支持带参数,(示例index?foo=bar)
|
||||
/// </summary>
|
||||
public string MiniProgramPagePath { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除消息模板
|
||||
/// </summary>
|
||||
public class DeleteMessageTemplateInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 订阅模板Id
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "订阅模板Id不能为空")]
|
||||
public string TemplateId { get; set; }
|
||||
}
|
||||
|
||||
public class UploadAvatarInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 小程序用户身份标识
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "OpenId不能为空")]
|
||||
public string OpenId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件
|
||||
/// </summary>
|
||||
[Required]
|
||||
public IFormFile File { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件类型
|
||||
/// </summary>
|
||||
public string FileType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件路径
|
||||
/// </summary>
|
||||
public string Path { get; set; }
|
||||
}
|
||||
|
||||
public class SetNickNameInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 小程序用户身份标识
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "OpenId不能为空")]
|
||||
public string OpenId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 昵称
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "昵称不能为空")]
|
||||
public string NickName { get; set; }
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
public class WechatPayTransactionInput
|
||||
{
|
||||
/// <summary>
|
||||
/// OpenId
|
||||
/// </summary>
|
||||
public string OpenId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 订单金额
|
||||
/// </summary>
|
||||
public int Total { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商品描述
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 附加数据
|
||||
/// </summary>
|
||||
public string Attachment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 优惠标记
|
||||
/// </summary>
|
||||
public string GoodsTag { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 业务标签,用来区分做什么业务
|
||||
/// </summary>
|
||||
public string Tags { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 对应业务的主键
|
||||
/// </summary>
|
||||
public long BusinessId { get; set; }
|
||||
}
|
||||
|
||||
public class WechatPayParaInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 订单Id
|
||||
/// </summary>
|
||||
public string PrepayId { get; set; }
|
||||
}
|
||||
|
||||
public class WechatPayRefundDomesticInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 商户端生成的业务流水号
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string TradeId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 退款原因
|
||||
/// </summary>
|
||||
public string Reason { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 退款金额
|
||||
/// </summary>
|
||||
[Required]
|
||||
public int Refund { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 原订单金额
|
||||
/// </summary>
|
||||
[Required]
|
||||
public int Total { get; set; }
|
||||
}
|
||||
|
||||
public class WechatPayPageInput : BasePageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加时间范围
|
||||
/// </summary>
|
||||
public List<DateTime?> CreateTimeRange { get; set; }
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
public class WechatPayOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// OpenId
|
||||
/// </summary>
|
||||
public string OpenId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 订单金额
|
||||
/// </summary>
|
||||
public int Total { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 附加数据
|
||||
/// </summary>
|
||||
public string Attachment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 优惠标记
|
||||
/// </summary>
|
||||
public string GoodsTag { get; set; }
|
||||
}
|
||||
|
||||
public class WechatPayTransactionOutput
|
||||
{
|
||||
public string PrepayId { get; set; }
|
||||
|
||||
public string OutTradeNumber { get; set; }
|
||||
|
||||
public WechatPayParaOutput SingInfo { get; set; }
|
||||
}
|
||||
|
||||
public class WechatPayParaOutput
|
||||
{
|
||||
public string AppId { get; set; }
|
||||
|
||||
public string TimeStamp { get; set; }
|
||||
|
||||
public string NonceStr { get; set; }
|
||||
|
||||
public string Package { get; set; }
|
||||
|
||||
public string SignType { get; set; }
|
||||
|
||||
public string PaySign { get; set; }
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
public class WechatUserInput : BasePageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 昵称
|
||||
/// </summary>
|
||||
public string NickName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 手机号码
|
||||
/// </summary>
|
||||
public string Mobile { get; set; }
|
||||
}
|
||||
|
||||
public class DeleteWechatUserInput : BaseIdInput
|
||||
{
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 获取微信用户OpenId
|
||||
/// </summary>
|
||||
public class JsCode2SessionInput
|
||||
{
|
||||
/// <summary>
|
||||
/// JsCode
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "JsCode不能为空"), MinLength(10, ErrorMessage = "JsCode错误")]
|
||||
public string JsCode { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取微信用户电话号码
|
||||
/// </summary>
|
||||
public class WxPhoneInput : WxOpenIdLoginInput
|
||||
{
|
||||
/// <summary>
|
||||
/// Code
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Code不能为空"), MinLength(10, ErrorMessage = "Code错误")]
|
||||
public string Code { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信小程序登录
|
||||
/// </summary>
|
||||
public class WxOpenIdLoginInput
|
||||
{
|
||||
/// <summary>
|
||||
/// OpenId
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "微信标识不能为空"), MinLength(10, ErrorMessage = "微信标识错误")]
|
||||
public string OpenId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信手机号登录
|
||||
/// </summary>
|
||||
public class WxPhoneLoginInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 电话号码
|
||||
/// </summary>
|
||||
[DataValidation(ValidationTypes.PhoneNumber, ErrorMessage = "电话号码错误")]
|
||||
public string PhoneNumber { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送订阅消息
|
||||
/// </summary>
|
||||
public class SendSubscribeMessageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 订阅模板Id
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "订阅模板Id不能为空")]
|
||||
public string TemplateId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 接收者的OpenId
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "接收者的OpenId不能为空")]
|
||||
public string ToUserOpenId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模板内容,格式形如 { "key1": { "value": any }, "key2": { "value": any } }
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "模板内容不能为空")]
|
||||
public Dictionary<string, CgibinMessageSubscribeSendRequest.Types.DataItem> Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 跳转小程序类型
|
||||
/// </summary>
|
||||
public string MiniprogramState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 语言类型
|
||||
/// </summary>
|
||||
public string Language { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 点击模板卡片后的跳转页面(仅限本小程序内的页面),支持带参数(示例pages/app/index?foo=bar)
|
||||
/// </summary>
|
||||
public string MiniProgramPagePath { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加订阅消息模板
|
||||
/// </summary>
|
||||
public class AddSubscribeMessageTemplateInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 模板标题Id
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "模板标题Id不能为空")]
|
||||
public string TemplateTitleId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模板关键词列表,例如 [3,5,4]
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "模板关键词列表不能为空")]
|
||||
public List<int> KeyworkIdList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 服务场景描述,15个字以内
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "服务场景描述不能为空")]
|
||||
public string SceneDescription { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成带参数小程序二维码(总共生成的码数量限制为 100,000)
|
||||
/// </summary>
|
||||
public class GenerateQRImageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 扫码进入的小程序页面路径,最大长度 128 个字符,不能为空; eg: pages/index?id=0001
|
||||
/// </summary>
|
||||
public string PagePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件保存的名称
|
||||
/// </summary>
|
||||
public string ImageName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图片宽度 默认430
|
||||
/// </summary>
|
||||
public int Width { get; set; } = 430;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成带参数小程序二维码(获取不受限制的小程序码)
|
||||
/// </summary>
|
||||
public class GenerateQRImageUnLimitInput : GenerateQRImageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 二维码携带的参数 eg:a=1(最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:<!-- !#$&'()*+,/:;=?@-._~ -->)
|
||||
/// </summary>
|
||||
public string Scene { get; set; }
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
public class WxOpenIdOutput
|
||||
{
|
||||
public string OpenId { get; set; }
|
||||
}
|
||||
|
||||
public class WxPhoneOutput
|
||||
{
|
||||
public string PhoneNumber { get; set; }
|
||||
}
|
||||
|
||||
public class GenerateQRImageOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 生成状态
|
||||
/// </summary>
|
||||
public bool Success { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 生成图片的绝对路径
|
||||
/// </summary>
|
||||
public string ImgPath { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 生成图片的相对路径
|
||||
/// </summary>
|
||||
public string RelativeImgPath { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 生成图片的错误信息
|
||||
/// </summary>
|
||||
public string Message { get; set; } = "";
|
||||
}
|
||||
@@ -1,520 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using Furion.Logging.Extensions;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 微信支付服务 🧩
|
||||
/// </summary>
|
||||
[ApiDescriptionSettings(Order = 210)]
|
||||
public class SysWechatPayService : IDynamicApiController, ITransient
|
||||
{
|
||||
private readonly SqlSugarRepository<SysWechatPay> _sysWechatPayRep;
|
||||
private readonly SqlSugarRepository<SysWechatRefund> _sysWechatRefundRep;
|
||||
|
||||
private readonly WechatPayOptions _wechatPayOptions;
|
||||
private readonly PayCallBackOptions _payCallBackOptions;
|
||||
|
||||
private readonly WechatTenpayClient _wechatTenpayClient;
|
||||
|
||||
public SysWechatPayService(SqlSugarRepository<SysWechatPay> sysWechatPayUserRep,
|
||||
SqlSugarRepository<SysWechatRefund> sysWechatRefundRep,
|
||||
IOptions<WechatPayOptions> wechatPayOptions,
|
||||
IOptions<PayCallBackOptions> payCallBackOptions)
|
||||
{
|
||||
_sysWechatPayRep = sysWechatPayUserRep;
|
||||
_sysWechatRefundRep = sysWechatRefundRep;
|
||||
_wechatPayOptions = wechatPayOptions.Value;
|
||||
_payCallBackOptions = payCallBackOptions.Value;
|
||||
|
||||
_wechatTenpayClient = CreateTenpayClient();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化微信支付客户端
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private WechatTenpayClient CreateTenpayClient()
|
||||
{
|
||||
var cerFilePath = Path.Combine(App.WebHostEnvironment.ContentRootPath, _wechatPayOptions.MerchantCertificatePrivateKey.Replace("/", Path.DirectorySeparatorChar.ToString()));
|
||||
|
||||
var tenpayClientOptions = new WechatTenpayClientOptions()
|
||||
{
|
||||
MerchantId = _wechatPayOptions.MerchantId,
|
||||
MerchantV3Secret = _wechatPayOptions.MerchantV3Secret,
|
||||
MerchantCertificateSerialNumber = _wechatPayOptions.MerchantCertificateSerialNumber,
|
||||
MerchantCertificatePrivateKey = File.Exists(cerFilePath) ? File.ReadAllText(cerFilePath) : "",
|
||||
PlatformCertificateManager = new InMemoryCertificateManager()
|
||||
};
|
||||
return new WechatTenpayClient(tenpayClientOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询支付列表 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[ApiDescriptionSettings(Name = "Page")]
|
||||
public async Task<SqlSugarPagedList<SysWechatPay>> Page(WechatPayPageInput input)
|
||||
{
|
||||
var query = _sysWechatPayRep.AsQueryable()
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.OutTradeNumber == input.Keyword || u.TransactionId == input.Keyword)
|
||||
.WhereIF(input.CreateTimeRange != null && input.CreateTimeRange.Count > 0 && input.CreateTimeRange[0].HasValue, x => x.CreateTime >= input.CreateTimeRange[0])
|
||||
.WhereIF(input.CreateTimeRange != null && input.CreateTimeRange.Count > 1 && input.CreateTimeRange[1].HasValue, x => x.CreateTime < ((DateTime)input.CreateTimeRange[1]).AddDays(1));
|
||||
return await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询退款信息列表
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[DisplayName("根据支付id获取退款信息列表")]
|
||||
public async Task<List<SysWechatRefund>> ListRefund([FromBody] string id)
|
||||
{
|
||||
var query = _sysWechatRefundRep.AsQueryable()
|
||||
.Where(u => u.TransactionId == id);
|
||||
return await query.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成JSAPI调起支付所需参数 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("生成JSAPI调起支付所需参数")]
|
||||
public WechatPayParaOutput GenerateParametersForJsapiPay(WechatPayParaInput input)
|
||||
{
|
||||
var data = _wechatTenpayClient.GenerateParametersForJsapiPayRequest(_wechatPayOptions.AppId, input.PrepayId);
|
||||
return new WechatPayParaOutput()
|
||||
{
|
||||
AppId = data["appId"],
|
||||
TimeStamp = data["timeStamp"],
|
||||
NonceStr = data["nonceStr"],
|
||||
Package = data["package"],
|
||||
SignType = data["signType"],
|
||||
PaySign = data["paySign"]
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信支付下单(商户直连) 🔖
|
||||
/// </summary>
|
||||
[DisplayName("微信支付下单(商户直连)")]
|
||||
public async Task<WechatPayTransactionOutput> CreatePayTransaction([FromBody] WechatPayTransactionInput input)
|
||||
{
|
||||
var request = new CreatePayTransactionJsapiRequest()
|
||||
{
|
||||
OutTradeNumber = DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(100, 1000), // 订单号
|
||||
AppId = _wechatPayOptions.AppId,
|
||||
Description = input.Description,
|
||||
Attachment = input.Attachment,
|
||||
GoodsTag = input.GoodsTag,
|
||||
ExpireTime = DateTimeOffset.Now.AddMinutes(10),
|
||||
NotifyUrl = _payCallBackOptions.WechatPayUrl,
|
||||
Amount = new CreatePayTransactionJsapiRequest.Types.Amount() { Total = input.Total },
|
||||
Payer = new CreatePayTransactionJsapiRequest.Types.Payer() { OpenId = input.OpenId }
|
||||
};
|
||||
var response = await _wechatTenpayClient.ExecuteCreatePayTransactionJsapiAsync(request);
|
||||
if (!response.IsSuccessful())
|
||||
throw Oops.Oh(response.ErrorMessage);
|
||||
|
||||
var singInfo = this.GenerateParametersForJsapiPay(new WechatPayParaInput() { PrepayId = response.PrepayId });
|
||||
// 保存订单信息
|
||||
var wechatPay = new SysWechatPay()
|
||||
{
|
||||
AppId = _wechatPayOptions.AppId,
|
||||
MerchantId = _wechatPayOptions.MerchantId,
|
||||
OutTradeNumber = request.OutTradeNumber,
|
||||
Description = input.Description,
|
||||
Attachment = input.Attachment,
|
||||
GoodsTag = input.GoodsTag,
|
||||
Total = input.Total,
|
||||
OpenId = input.OpenId,
|
||||
TransactionId = "",
|
||||
Tags = input.Tags,
|
||||
BusinessId = input.BusinessId,
|
||||
};
|
||||
await _sysWechatPayRep.InsertAsync(wechatPay);
|
||||
|
||||
return new WechatPayTransactionOutput()
|
||||
{
|
||||
PrepayId = response.PrepayId,
|
||||
OutTradeNumber = request.OutTradeNumber,
|
||||
SingInfo = singInfo
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信支付下单(商户直连)Native
|
||||
/// </summary>
|
||||
[DisplayName("微信支付下单(商户直连)Native")]
|
||||
public async Task<dynamic> CreatePayTransactionNative([FromBody] WechatPayTransactionInput input)
|
||||
{
|
||||
var request = new CreatePayTransactionNativeRequest()
|
||||
{
|
||||
OutTradeNumber = DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(100, 1000), // 订单号
|
||||
AppId = _wechatPayOptions.AppId,
|
||||
Description = input.Description,
|
||||
Attachment = input.Attachment,
|
||||
GoodsTag = input.GoodsTag,
|
||||
ExpireTime = DateTimeOffset.Now.AddMinutes(10),
|
||||
NotifyUrl = _payCallBackOptions.WechatPayUrl,
|
||||
Amount = new CreatePayTransactionNativeRequest.Types.Amount() { Total = input.Total },
|
||||
//Payer = new CreatePayTransactionNativeRequest.Types.Payer() { OpenId = input.OpenId }
|
||||
Scene = new CreatePayTransactionNativeRequest.Types.Scene() { ClientIp = "127.0.0.1" }
|
||||
};
|
||||
var response = await _wechatTenpayClient.ExecuteCreatePayTransactionNativeAsync(request);
|
||||
if (!response.IsSuccessful())
|
||||
{
|
||||
JsonConvert.SerializeObject(response).LogInformation();
|
||||
throw Oops.Oh(response.ErrorMessage);
|
||||
}
|
||||
// 保存订单信息
|
||||
var wechatPay = new SysWechatPay()
|
||||
{
|
||||
AppId = _wechatPayOptions.AppId,
|
||||
MerchantId = _wechatPayOptions.MerchantId,
|
||||
OutTradeNumber = request.OutTradeNumber,
|
||||
Description = input.Description,
|
||||
Attachment = input.Attachment,
|
||||
GoodsTag = input.GoodsTag,
|
||||
Total = input.Total,
|
||||
//OpenId = input.OpenId,
|
||||
TransactionId = "",
|
||||
QrcodeContent = response.QrcodeUrl,
|
||||
Tags = input.Tags,
|
||||
BusinessId = input.BusinessId,
|
||||
};
|
||||
await _sysWechatPayRep.InsertAsync(wechatPay);
|
||||
return new
|
||||
{
|
||||
request.OutTradeNumber,
|
||||
response.QrcodeUrl
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信支付下单(服务商模式) 🔖
|
||||
/// </summary>
|
||||
[DisplayName("微信支付下单(服务商模式)")]
|
||||
public async Task<dynamic> CreatePayPartnerTransaction([FromBody] WechatPayTransactionInput input)
|
||||
{
|
||||
var request = new CreatePayPartnerTransactionJsapiRequest()
|
||||
{
|
||||
OutTradeNumber = DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(100, 1000), // 订单号
|
||||
AppId = _wechatPayOptions.AppId,
|
||||
MerchantId = _wechatPayOptions.MerchantId,
|
||||
SubAppId = _wechatPayOptions.AppId,
|
||||
SubMerchantId = _wechatPayOptions.MerchantId,
|
||||
Description = input.Description,
|
||||
Attachment = input.Attachment,
|
||||
GoodsTag = input.GoodsTag,
|
||||
ExpireTime = DateTimeOffset.Now.AddMinutes(10),
|
||||
NotifyUrl = _payCallBackOptions.WechatPayUrl,
|
||||
Amount = new CreatePayPartnerTransactionJsapiRequest.Types.Amount() { Total = input.Total },
|
||||
Payer = new CreatePayPartnerTransactionJsapiRequest.Types.Payer() { OpenId = input.OpenId }
|
||||
};
|
||||
var response = await _wechatTenpayClient.ExecuteCreatePayPartnerTransactionJsapiAsync(request);
|
||||
if (!response.IsSuccessful())
|
||||
throw Oops.Oh(response.ErrorMessage);
|
||||
var singInfo = this.GenerateParametersForJsapiPay(new WechatPayParaInput() { PrepayId = response.PrepayId });
|
||||
// 保存订单信息
|
||||
var wechatPay = new SysWechatPay()
|
||||
{
|
||||
AppId = _wechatPayOptions.AppId,
|
||||
MerchantId = _wechatPayOptions.MerchantId,
|
||||
SubAppId = _wechatPayOptions.AppId,
|
||||
SubMerchantId = _wechatPayOptions.MerchantId,
|
||||
OutTradeNumber = request.OutTradeNumber,
|
||||
Description = input.Description,
|
||||
Attachment = input.Attachment,
|
||||
GoodsTag = input.GoodsTag,
|
||||
Total = input.Total,
|
||||
OpenId = input.OpenId,
|
||||
TransactionId = ""
|
||||
};
|
||||
await _sysWechatPayRep.InsertAsync(wechatPay);
|
||||
|
||||
return new
|
||||
{
|
||||
response.PrepayId,
|
||||
request.OutTradeNumber,
|
||||
singInfo
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取支付订单详情(本地库) 🔖
|
||||
/// </summary>
|
||||
/// <param name="tradeId"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取支付订单详情(本地库)")]
|
||||
public async Task<SysWechatPay> GetPayInfo(string tradeId)
|
||||
{
|
||||
return await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == tradeId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取支付订单详情(微信接口) 🔖
|
||||
/// </summary>
|
||||
/// <param name="tradeId"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取支付订单详情(微信接口)")]
|
||||
public async Task<SysWechatPay> GetPayInfoFromWechat(string tradeId)
|
||||
{
|
||||
var request = new GetPayTransactionByOutTradeNumberRequest();
|
||||
request.OutTradeNumber = tradeId;
|
||||
var response = await _wechatTenpayClient.ExecuteGetPayTransactionByOutTradeNumberAsync(request);
|
||||
// 修改订单支付状态
|
||||
var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == response.OutTradeNumber
|
||||
&& u.MerchantId == response.MerchantId);
|
||||
// 如果状态不一致就更新数据库中的记录
|
||||
if (wechatPay != null && wechatPay.TradeState != response.TradeState)
|
||||
{
|
||||
wechatPay.OpenId = response.Payer.OpenId;
|
||||
wechatPay.TransactionId = response.TransactionId; // 支付订单号
|
||||
wechatPay.TradeType = response.TradeType; // 交易类型
|
||||
wechatPay.TradeState = response.TradeState; // 交易状态
|
||||
wechatPay.TradeStateDescription = response.TradeStateDescription; // 交易状态描述
|
||||
wechatPay.BankType = response.BankType; // 付款银行类型
|
||||
wechatPay.Total = response.Amount.Total; // 订单总金额
|
||||
wechatPay.PayerTotal = response.Amount.PayerTotal; // 用户支付金额
|
||||
wechatPay.SuccessTime = response.SuccessTime.Value.DateTime; // 支付完成时间
|
||||
await _sysWechatPayRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync();
|
||||
}
|
||||
wechatPay = new SysWechatPay()
|
||||
{
|
||||
AppId = _wechatPayOptions.AppId,
|
||||
MerchantId = _wechatPayOptions.MerchantId,
|
||||
SubAppId = _wechatPayOptions.AppId,
|
||||
SubMerchantId = _wechatPayOptions.MerchantId,
|
||||
OutTradeNumber = request.OutTradeNumber,
|
||||
Attachment = response.Attachment,
|
||||
Total = response.Amount.Total, // 订单总金额
|
||||
TransactionId = response.TransactionId,
|
||||
TradeType = response.TradeType, // 交易类型
|
||||
TradeState = response.TradeState, // 交易状态
|
||||
TradeStateDescription = response.TradeStateDescription, // 交易状态描述
|
||||
BankType = response.BankType, // 付款银行类型
|
||||
PayerTotal = response.Amount.PayerTotal, // 用户支付金额
|
||||
SuccessTime = response.SuccessTime.Value.DateTime // 支付完成时间
|
||||
};
|
||||
return wechatPay;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 退款申请
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("退款申请")]
|
||||
[HttpPost]
|
||||
public async Task<dynamic> CreateRefundDomestic([FromBody] WechatPayRefundDomesticInput input)
|
||||
{
|
||||
// refund/domestic/refunds
|
||||
var request = new CreateRefundDomesticRefundRequest()
|
||||
{
|
||||
Amount = new CreateRefundDomesticRefundRequest.Types.Amount()
|
||||
{
|
||||
Refund = input.Refund,
|
||||
Total = input.Total,
|
||||
Currency = "CNY"
|
||||
},
|
||||
|
||||
OutTradeNumber = input.TradeId,
|
||||
OutRefundNumber = "R" + DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(100, 1000), // 订单号
|
||||
NotifyUrl = _payCallBackOptions.WechatRefundUrl, // 应采用WechatRefundUrl参数,如与WechatPayUrl入口相同,也应分开设置参数
|
||||
Reason = input.Reason,
|
||||
};
|
||||
var response = await _wechatTenpayClient.ExecuteCreateRefundDomesticRefundAsync(request);
|
||||
if (string.IsNullOrEmpty(response.ErrorCode))
|
||||
{
|
||||
// 成功了,这里应该保存退款订单信息
|
||||
var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == response.OutTradeNumber);
|
||||
// 保存订单信息
|
||||
if (wechatPay != null)
|
||||
{
|
||||
var wechatRefund = new SysWechatRefund()
|
||||
{
|
||||
WechatPayId = wechatPay.Id,
|
||||
TransactionId = response.TransactionId,
|
||||
Refund = input.Refund,
|
||||
Reason = input.Reason,
|
||||
OutRefundNumber = request.OutRefundNumber,
|
||||
Channel = response.Channel,
|
||||
UserReceivedAccount = response.UserReceivedAccount,
|
||||
};
|
||||
await _sysWechatRefundRep.InsertAsync(wechatRefund);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw Oops.Bah($"[{response.ErrorCode}]{response.ErrorMessage}");
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取退款订单详情(微信接口)
|
||||
/// </summary>
|
||||
/// <param name="refundId"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取退款订单详情(微信接口)")]
|
||||
public async Task<SysWechatRefund> GetRefundInfoFromWechat(string refundId)
|
||||
{
|
||||
var request = new GetRefundDomesticRefundByOutRefundNumberRequest();
|
||||
request.OutRefundNumber = refundId;
|
||||
var response = await _wechatTenpayClient.ExecuteGetRefundDomesticRefundByOutRefundNumberAsync(request);
|
||||
// 修改订单支付状态
|
||||
var wechatRefund = await _sysWechatRefundRep.GetFirstAsync(u => u.OutRefundNumber == refundId);
|
||||
// 如果状态不一致就更新数据库中的记录
|
||||
if (wechatRefund != null && wechatRefund.TradeState != response.Status)
|
||||
{
|
||||
wechatRefund.TransactionId = response.TransactionId; // 支付订单号
|
||||
wechatRefund.TradeState = response.Status; // 交易状态
|
||||
wechatRefund.SuccessTime = response.SuccessTime.Value.DateTime; // 支付完成时间
|
||||
await _sysWechatRefundRep.AsUpdateable(wechatRefund).IgnoreColumns(true).ExecuteCommandAsync();
|
||||
// 有退款,刷新一下订单状态
|
||||
var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.Id == wechatRefund.WechatPayId);
|
||||
if (wechatPay != null)
|
||||
await GetPayInfoFromWechat(wechatPay.OutTradeNumber);
|
||||
}
|
||||
wechatRefund = new SysWechatRefund()
|
||||
{
|
||||
TransactionId = response.TransactionId,
|
||||
Refund = response.Amount.Refund,
|
||||
OutRefundNumber = request.OutRefundNumber,
|
||||
Channel = response.Channel,
|
||||
UserReceivedAccount = response.UserReceivedAccount,
|
||||
TradeState = response.Status, // 交易状态
|
||||
SuccessTime = response.SuccessTime.Value.DateTime, // 支付完成时间
|
||||
};
|
||||
return wechatRefund;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信支付成功回调(商户直连)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[DisplayName("微信支付成功回调(商户直连)")]
|
||||
public async Task<WechatPayOutput> PayCallBack()
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
await App.HttpContext.Request.Body.CopyToAsync(ms);
|
||||
var b = ms.ToArray();
|
||||
var callbackJson = Encoding.UTF8.GetString(b);
|
||||
|
||||
var callbackModel = _wechatTenpayClient.DeserializeEvent(callbackJson);
|
||||
if ("TRANSACTION.SUCCESS".Equals(callbackModel.EventType))
|
||||
{
|
||||
try
|
||||
{
|
||||
var callbackPayResource = _wechatTenpayClient.DecryptEventResource<TransactionResource>(callbackModel);
|
||||
|
||||
// 修改订单支付状态
|
||||
var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == callbackPayResource.OutTradeNumber
|
||||
&& u.MerchantId == callbackPayResource.MerchantId);
|
||||
if (wechatPay == null) return null;
|
||||
wechatPay.OpenId = callbackPayResource.Payer.OpenId; // 支付者标识
|
||||
//wechatPay.MerchantId = callbackResource.MerchantId; // 微信商户号
|
||||
//wechatPay.OutTradeNumber = callbackResource.OutTradeNumber; // 商户订单号
|
||||
wechatPay.TransactionId = callbackPayResource.TransactionId; // 支付订单号
|
||||
wechatPay.TradeType = callbackPayResource.TradeType; // 交易类型
|
||||
wechatPay.TradeState = callbackPayResource.TradeState; // 交易状态
|
||||
wechatPay.TradeStateDescription = callbackPayResource.TradeStateDescription; // 交易状态描述
|
||||
wechatPay.BankType = callbackPayResource.BankType; // 付款银行类型
|
||||
wechatPay.Total = callbackPayResource.Amount.Total; // 订单总金额
|
||||
wechatPay.PayerTotal = callbackPayResource.Amount.PayerTotal; // 用户支付金额
|
||||
wechatPay.SuccessTime = callbackPayResource.SuccessTime.DateTime; // 支付完成时间
|
||||
|
||||
await _sysWechatPayRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync();
|
||||
|
||||
return new WechatPayOutput()
|
||||
{
|
||||
Total = wechatPay.Total,
|
||||
Attachment = wechatPay.Attachment,
|
||||
GoodsTag = wechatPay.GoodsTag
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
"微信支付回调时出错:".LogError(ex);
|
||||
}
|
||||
}
|
||||
else if ("REFUND.SUCCESS".Equals(callbackModel.EventType))
|
||||
{
|
||||
//参考:https://pay.weixin.qq.com/docs/merchant/apis/jsapi-payment/refund-result-notice.html
|
||||
try
|
||||
{
|
||||
var callbackRefundResource = _wechatTenpayClient.DecryptEventResource<RefundResource>(callbackModel);
|
||||
// 修改订单支付状态
|
||||
var wechatRefund = await _sysWechatRefundRep.GetFirstAsync(u => u.OutRefundNumber == callbackRefundResource.OutRefundNumber);
|
||||
if (wechatRefund == null) return null;
|
||||
wechatRefund.TradeState = callbackRefundResource.RefundStatus; // 交易状态
|
||||
wechatRefund.SuccessTime = callbackRefundResource.SuccessTime.Value.DateTime; // 支付完成时间
|
||||
|
||||
await _sysWechatRefundRep.AsUpdateable(wechatRefund).IgnoreColumns(true).ExecuteCommandAsync();
|
||||
// 有退款,刷新一下订单状态
|
||||
await GetPayInfoFromWechat(callbackRefundResource.OutTradeNumber);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
"微信退款回调时出错:".LogError(ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
callbackModel.EventType.LogInformation();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信支付成功回调(服务商模式) 🔖
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[DisplayName("微信支付成功回调(服务商模式)")]
|
||||
public async Task PayPartnerCallBack()
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
await App.HttpContext.Request.Body.CopyToAsync(ms);
|
||||
var b = ms.ToArray();
|
||||
var callbackJson = Encoding.UTF8.GetString(b);
|
||||
|
||||
var callbackModel = _wechatTenpayClient.DeserializeEvent(callbackJson);
|
||||
if ("TRANSACTION.SUCCESS".Equals(callbackModel.EventType))
|
||||
{
|
||||
var callbackResource = _wechatTenpayClient.DecryptEventResource<PartnerTransactionResource>(callbackModel);
|
||||
|
||||
// 修改订单支付状态
|
||||
var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == callbackResource.OutTradeNumber
|
||||
&& u.MerchantId == callbackResource.MerchantId);
|
||||
if (wechatPay == null) return;
|
||||
//wechatPay.OpenId = callbackResource.Payer.OpenId; // 支付者标识
|
||||
//wechatPay.MerchantId = callbackResource.MerchantId; // 微信商户号
|
||||
//wechatPay.OutTradeNumber = callbackResource.OutTradeNumber; // 商户订单号
|
||||
wechatPay.TransactionId = callbackResource.TransactionId; // 支付订单号
|
||||
wechatPay.TradeType = callbackResource.TradeType; // 交易类型
|
||||
wechatPay.TradeState = callbackResource.TradeState; // 交易状态
|
||||
wechatPay.TradeStateDescription = callbackResource.TradeStateDescription; // 交易状态描述
|
||||
wechatPay.BankType = callbackResource.BankType; // 付款银行类型
|
||||
wechatPay.Total = callbackResource.Amount.Total; // 订单总金额
|
||||
wechatPay.PayerTotal = callbackResource.Amount.PayerTotal; // 用户支付金额
|
||||
wechatPay.SuccessTime = callbackResource.SuccessTime.DateTime; // 支付完成时间
|
||||
|
||||
await _sysWechatPayRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 微信公众号服务 🧩
|
||||
/// </summary>
|
||||
[ApiDescriptionSettings(Order = 230)]
|
||||
public class SysWechatService : IDynamicApiController, ITransient
|
||||
{
|
||||
private readonly SqlSugarRepository<SysWechatUser> _sysWechatUserRep;
|
||||
private readonly SysConfigService _sysConfigService;
|
||||
private readonly WechatApiClientFactory _wechatApiClientFactory;
|
||||
private readonly WechatApiClient _wechatApiClient;
|
||||
|
||||
public SysWechatService(SqlSugarRepository<SysWechatUser> sysWechatUserRep,
|
||||
SysConfigService sysConfigService,
|
||||
WechatApiClientFactory wechatApiClientFactory,
|
||||
SysCacheService sysCacheService)
|
||||
{
|
||||
_sysWechatUserRep = sysWechatUserRep;
|
||||
_sysConfigService = sysConfigService;
|
||||
_wechatApiClientFactory = wechatApiClientFactory;
|
||||
_wechatApiClient = wechatApiClientFactory.CreateWechatClient();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成网页授权Url 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[DisplayName("生成网页授权Url")]
|
||||
public string GenAuthUrl(GenAuthUrlInput input)
|
||||
{
|
||||
return _wechatApiClient.GenerateParameterizedUrlForConnectOAuth2Authorize(input.RedirectUrl, input.Scope, input.State);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取微信用户OpenId 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
[AllowAnonymous]
|
||||
[DisplayName("获取微信用户OpenId")]
|
||||
public async Task<string> SnsOAuth2([FromQuery] WechatOAuth2Input input)
|
||||
{
|
||||
var reqOAuth2 = new SnsOAuth2AccessTokenRequest()
|
||||
{
|
||||
Code = input.Code,
|
||||
};
|
||||
var resOAuth2 = await _wechatApiClient.ExecuteSnsOAuth2AccessTokenAsync(reqOAuth2);
|
||||
if (resOAuth2.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
|
||||
throw Oops.Oh(resOAuth2.ErrorMessage + " " + resOAuth2.ErrorCode);
|
||||
|
||||
var wxUser = await _sysWechatUserRep.GetFirstAsync(p => p.OpenId == resOAuth2.OpenId);
|
||||
if (wxUser == null)
|
||||
{
|
||||
var reqUserInfo = new SnsUserInfoRequest()
|
||||
{
|
||||
OpenId = resOAuth2.OpenId,
|
||||
AccessToken = resOAuth2.AccessToken,
|
||||
};
|
||||
var resUserInfo = await _wechatApiClient.ExecuteSnsUserInfoAsync(reqUserInfo);
|
||||
wxUser = resUserInfo.Adapt<SysWechatUser>();
|
||||
wxUser.Avatar = resUserInfo.HeadImageUrl;
|
||||
wxUser.NickName = resUserInfo.Nickname;
|
||||
wxUser.OpenId = resOAuth2.OpenId;
|
||||
wxUser.UnionId = resOAuth2.UnionId;
|
||||
wxUser.AccessToken = resOAuth2.AccessToken;
|
||||
wxUser.RefreshToken = resOAuth2.RefreshToken;
|
||||
wxUser = await _sysWechatUserRep.AsInsertable(wxUser).ExecuteReturnEntityAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
wxUser.AccessToken = resOAuth2.AccessToken;
|
||||
wxUser.RefreshToken = resOAuth2.RefreshToken;
|
||||
await _sysWechatUserRep.AsUpdateable(wxUser).IgnoreColumns(true).ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
return resOAuth2.OpenId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信用户登录OpenId 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[DisplayName("微信用户登录OpenId")]
|
||||
public async Task<dynamic> OpenIdLogin(WechatUserLogin input)
|
||||
{
|
||||
var wxUser = await _sysWechatUserRep.GetFirstAsync(p => p.OpenId == input.OpenId);
|
||||
if (wxUser == null)
|
||||
throw Oops.Oh("微信用户登录OpenId错误");
|
||||
|
||||
var tokenExpire = await _sysConfigService.GetTokenExpire();
|
||||
return new
|
||||
{
|
||||
wxUser.Avatar,
|
||||
accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>
|
||||
{
|
||||
{ ClaimConst.UserId, wxUser.Id },
|
||||
{ ClaimConst.NickName, wxUser.NickName },
|
||||
{ ClaimConst.LoginMode, LoginModeEnum.APP },
|
||||
}, tokenExpire)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取配置签名参数(wx.config) 🔖
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取配置签名参数(wx.config)")]
|
||||
public async Task<dynamic> GenConfigPara(SignatureInput input)
|
||||
{
|
||||
string ticket = await _wechatApiClientFactory.TryGetWechatJsApiTicketAsync();
|
||||
return _wechatApiClient.GenerateParametersForJSSDKConfig(ticket, input.Url);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取模板列表 🔖
|
||||
/// </summary>
|
||||
[DisplayName("获取模板列表")]
|
||||
public async Task<dynamic> GetMessageTemplateList()
|
||||
{
|
||||
var accessToken = await GetCgibinToken();
|
||||
var reqTemplate = new CgibinTemplateGetAllPrivateTemplateRequest()
|
||||
{
|
||||
AccessToken = accessToken
|
||||
};
|
||||
var resTemplate = await _wechatApiClient.ExecuteCgibinTemplateGetAllPrivateTemplateAsync(reqTemplate);
|
||||
if (resTemplate.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
|
||||
throw Oops.Oh(resTemplate.ErrorMessage + " " + resTemplate.ErrorCode);
|
||||
|
||||
return resTemplate.TemplateList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送模板消息 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("发送模板消息")]
|
||||
public async Task<dynamic> SendTemplateMessage(MessageTemplateSendInput input)
|
||||
{
|
||||
var dataInfo = input.Data.ToDictionary(k => k.Key, k => k.Value);
|
||||
var messageData = new Dictionary<string, CgibinMessageTemplateSendRequest.Types.DataItem>();
|
||||
foreach (var item in dataInfo)
|
||||
{
|
||||
messageData.Add(item.Key, new CgibinMessageTemplateSendRequest.Types.DataItem() { Value = "" + item.Value.Value.ToString() + "" });
|
||||
}
|
||||
|
||||
var accessToken = await GetCgibinToken();
|
||||
var reqMessage = new CgibinMessageTemplateSendRequest()
|
||||
{
|
||||
AccessToken = accessToken,
|
||||
TemplateId = input.TemplateId,
|
||||
ToUserOpenId = input.ToUserOpenId,
|
||||
Url = input.Url,
|
||||
MiniProgram = new CgibinMessageTemplateSendRequest.Types.MiniProgram
|
||||
{
|
||||
AppId = _wechatApiClientFactory._wechatOptions.WxOpenAppId,
|
||||
PagePath = input.MiniProgramPagePath,
|
||||
},
|
||||
Data = messageData
|
||||
};
|
||||
var resMessage = await _wechatApiClient.ExecuteCgibinMessageTemplateSendAsync(reqMessage);
|
||||
return resMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除模板 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "DeleteMessageTemplate"), HttpPost]
|
||||
[DisplayName("删除模板")]
|
||||
public async Task<dynamic> DeleteMessageTemplate(DeleteMessageTemplateInput input)
|
||||
{
|
||||
var accessToken = await GetCgibinToken();
|
||||
var reqMessage = new CgibinTemplateDeletePrivateTemplateRequest()
|
||||
{
|
||||
AccessToken = accessToken,
|
||||
TemplateId = input.TemplateId
|
||||
};
|
||||
var resTemplate = await _wechatApiClient.ExecuteCgibinTemplateDeletePrivateTemplateAsync(reqMessage);
|
||||
return resTemplate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Access_token
|
||||
/// </summary>
|
||||
[NonAction]
|
||||
public async Task<string> GetCgibinToken()
|
||||
{
|
||||
return await _wechatApiClientFactory.TryGetWechatAccessTokenAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 微信账号服务 🧩
|
||||
/// </summary>
|
||||
[ApiDescriptionSettings(Order = 220)]
|
||||
public class SysWechatUserService : IDynamicApiController, ITransient
|
||||
{
|
||||
private readonly SqlSugarRepository<SysWechatUser> _sysWechatUserRep;
|
||||
|
||||
public SysWechatUserService(SqlSugarRepository<SysWechatUser> sysWechatUserRep)
|
||||
{
|
||||
_sysWechatUserRep = sysWechatUserRep;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取微信用户列表 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取微信用户列表")]
|
||||
public async Task<SqlSugarPagedList<SysWechatUser>> Page(WechatUserInput input)
|
||||
{
|
||||
return await _sysWechatUserRep.AsQueryable()
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.NickName), u => u.NickName.Contains(input.NickName))
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.Mobile), u => u.Mobile.Contains(input.Mobile))
|
||||
.OrderBy(u => u.Id, OrderByType.Desc)
|
||||
.ToPagedListAsync(input.Page, input.PageSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加微信用户 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "Add"), HttpPost]
|
||||
[DisplayName("增加微信用户")]
|
||||
public async Task AddWechatUser(SysWechatUser input)
|
||||
{
|
||||
await _sysWechatUserRep.InsertAsync(input.Adapt<SysWechatUser>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新微信用户 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "Update"), HttpPost]
|
||||
[DisplayName("更新微信用户")]
|
||||
public async Task UpdateWechatUser(SysWechatUser input)
|
||||
{
|
||||
var weChatUser = input.Adapt<SysWechatUser>();
|
||||
await _sysWechatUserRep.AsUpdateable(weChatUser).IgnoreColumns(true).ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除微信用户 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "Delete"), HttpPost]
|
||||
[DisplayName("删除微信用户")]
|
||||
public async Task DeleteWechatUser(DeleteWechatUserInput input)
|
||||
{
|
||||
await _sysWechatUserRep.DeleteAsync(u => u.Id == input.Id);
|
||||
}
|
||||
}
|
||||
@@ -1,407 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 微信小程序服务 🧩
|
||||
/// </summary>
|
||||
[ApiDescriptionSettings(Order = 240)]
|
||||
public class SysWxOpenService : IDynamicApiController, ITransient
|
||||
{
|
||||
private readonly SqlSugarRepository<SysWechatUser> _sysWechatUserRep;
|
||||
private readonly SysConfigService _sysConfigService;
|
||||
private readonly WechatApiClient _wechatApiClient;
|
||||
private readonly SysFileService _sysFileService;
|
||||
private readonly WechatApiClientFactory _wechatApiClientFactory;
|
||||
|
||||
public SysWxOpenService(SqlSugarRepository<SysWechatUser> sysWechatUserRep,
|
||||
SysConfigService sysConfigService,
|
||||
WechatApiClientFactory wechatApiClientFactory,
|
||||
SysFileService sysFileService)
|
||||
{
|
||||
_sysWechatUserRep = sysWechatUserRep;
|
||||
_sysConfigService = sysConfigService;
|
||||
_wechatApiClient = wechatApiClientFactory.CreateWxOpenClient();
|
||||
_sysFileService = sysFileService;
|
||||
_wechatApiClientFactory = wechatApiClientFactory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取微信用户OpenId 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
[AllowAnonymous]
|
||||
[DisplayName("获取微信用户OpenId")]
|
||||
public async Task<WxOpenIdOutput> GetWxOpenId([FromQuery] JsCode2SessionInput input)
|
||||
{
|
||||
var reqJsCode2Session = new SnsJsCode2SessionRequest()
|
||||
{
|
||||
JsCode = input.JsCode,
|
||||
};
|
||||
var resCode2Session = await _wechatApiClient.ExecuteSnsJsCode2SessionAsync(reqJsCode2Session);
|
||||
if (resCode2Session.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
|
||||
throw Oops.Oh(resCode2Session.ErrorMessage + " " + resCode2Session.ErrorCode);
|
||||
|
||||
var wxUser = await _sysWechatUserRep.GetFirstAsync(p => p.OpenId == resCode2Session.OpenId);
|
||||
if (wxUser == null)
|
||||
{
|
||||
wxUser = new SysWechatUser
|
||||
{
|
||||
OpenId = resCode2Session.OpenId,
|
||||
UnionId = resCode2Session.UnionId,
|
||||
SessionKey = resCode2Session.SessionKey,
|
||||
PlatformType = PlatformTypeEnum.微信小程序
|
||||
};
|
||||
wxUser = await _sysWechatUserRep.AsInsertable(wxUser).ExecuteReturnEntityAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await _sysWechatUserRep.AsUpdateable(wxUser).IgnoreColumns(true).ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
return new WxOpenIdOutput
|
||||
{
|
||||
OpenId = resCode2Session.OpenId
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取微信用户电话号码 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
[AllowAnonymous]
|
||||
[DisplayName("获取微信用户电话号码")]
|
||||
public async Task<WxPhoneOutput> GetWxPhone([FromQuery] WxPhoneInput input)
|
||||
{
|
||||
var accessToken = await GetCgibinToken();
|
||||
var reqUserPhoneNumber = new WxaBusinessGetUserPhoneNumberRequest()
|
||||
{
|
||||
Code = input.Code,
|
||||
AccessToken = accessToken,
|
||||
};
|
||||
var resUserPhoneNumber = await _wechatApiClient.ExecuteWxaBusinessGetUserPhoneNumberAsync(reqUserPhoneNumber);
|
||||
if (resUserPhoneNumber.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
|
||||
throw Oops.Oh(resUserPhoneNumber.ErrorMessage + " " + resUserPhoneNumber.ErrorCode);
|
||||
|
||||
var wxUser = await _sysWechatUserRep.GetFirstAsync(p => p.OpenId == input.OpenId);
|
||||
if (wxUser == null)
|
||||
{
|
||||
wxUser = new SysWechatUser
|
||||
{
|
||||
OpenId = input.OpenId,
|
||||
Mobile = resUserPhoneNumber.PhoneInfo?.PhoneNumber,
|
||||
PlatformType = PlatformTypeEnum.微信小程序
|
||||
};
|
||||
wxUser = await _sysWechatUserRep.AsInsertable(wxUser).ExecuteReturnEntityAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
wxUser.Mobile = resUserPhoneNumber.PhoneInfo?.PhoneNumber;
|
||||
await _sysWechatUserRep.AsUpdateable(wxUser).IgnoreColumns(true).ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
return new WxPhoneOutput
|
||||
{
|
||||
PhoneNumber = resUserPhoneNumber.PhoneInfo?.PhoneNumber
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信小程序登录OpenId 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[DisplayName("微信小程序登录OpenId")]
|
||||
public async Task<dynamic> WxOpenIdLogin(WxOpenIdLoginInput input)
|
||||
{
|
||||
var wxUser = await _sysWechatUserRep.GetFirstAsync(u => u.OpenId == input.OpenId);
|
||||
if (wxUser == null)
|
||||
throw Oops.Oh("微信小程序登录失败");
|
||||
|
||||
var tokenExpire = await _sysConfigService.GetTokenExpire();
|
||||
return new
|
||||
{
|
||||
wxUser.Avatar,
|
||||
accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>
|
||||
{
|
||||
{ ClaimConst.UserId, wxUser.Id },
|
||||
{ ClaimConst.RealName, wxUser.NickName },
|
||||
{ ClaimConst.LoginMode, LoginModeEnum.APP },
|
||||
}, tokenExpire)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传小程序头像
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[DisplayName("上传小程序头像")]
|
||||
public async Task<SysFile> UploadAvatar([FromForm] UploadAvatarInput input)
|
||||
{
|
||||
var wxUser = await _sysWechatUserRep.GetFirstAsync(u => u.OpenId == input.OpenId);
|
||||
if (wxUser == null)
|
||||
throw Oops.Oh("未找到用户上传失败");
|
||||
|
||||
var res = await _sysFileService.UploadFile(new UploadFileInput { File = input.File, FileType = input.FileType }, "upload/wechatAvatar");
|
||||
wxUser.Avatar = res.Url;
|
||||
await _sysWechatUserRep.AsUpdateable(wxUser).IgnoreColumns(true).ExecuteCommandAsync();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置小程序用户昵称
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[HttpPost]
|
||||
public async Task SetNickName(SetNickNameInput input)
|
||||
{
|
||||
var wxUser = await _sysWechatUserRep.GetFirstAsync(u => u.OpenId == input.OpenId);
|
||||
if (wxUser == null)
|
||||
throw Oops.Oh("未找到用户信息设置失败");
|
||||
wxUser.NickName = input.NickName;
|
||||
await _sysWechatUserRep.AsUpdateable(wxUser).IgnoreColumns(true).ExecuteCommandAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取小程序用户信息
|
||||
/// </summary>
|
||||
/// <param name="openid"></param>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
public async Task<dynamic> GetUserInfo(string openid)
|
||||
{
|
||||
var wxUser = await _sysWechatUserRep.GetFirstAsync(u => u.OpenId == openid);
|
||||
if (wxUser == null)
|
||||
throw Oops.Oh("未找到用户信息获取失败");
|
||||
return new { nickName = wxUser.NickName, avator = wxUser.Avatar };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取订阅消息模板列表 🔖
|
||||
/// </summary>
|
||||
[DisplayName("获取订阅消息模板列表")]
|
||||
public async Task<dynamic> GetMessageTemplateList()
|
||||
{
|
||||
var accessToken = await GetCgibinToken();
|
||||
var reqTemplate = new WxaApiNewTemplateGetTemplateRequest()
|
||||
{
|
||||
AccessToken = accessToken
|
||||
};
|
||||
var resTemplate = await _wechatApiClient.ExecuteWxaApiNewTemplateGetTemplateAsync(reqTemplate);
|
||||
if (resTemplate.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
|
||||
throw Oops.Oh(resTemplate.ErrorMessage + " " + resTemplate.ErrorCode);
|
||||
|
||||
return resTemplate.TemplateList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送订阅消息 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("发送订阅消息")]
|
||||
public async Task<dynamic> SendSubscribeMessage(SendSubscribeMessageInput input)
|
||||
{
|
||||
var accessToken = await GetCgibinToken();
|
||||
var reqMessage = new CgibinMessageSubscribeSendRequest()
|
||||
{
|
||||
AccessToken = accessToken,
|
||||
TemplateId = input.TemplateId,
|
||||
ToUserOpenId = input.ToUserOpenId,
|
||||
Data = input.Data,
|
||||
MiniProgramState = input.MiniprogramState,
|
||||
Language = input.Language,
|
||||
MiniProgramPagePath = input.MiniProgramPagePath
|
||||
};
|
||||
var resMessage = await _wechatApiClient.ExecuteCgibinMessageSubscribeSendAsync(reqMessage);
|
||||
return resMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加订阅消息模板 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "AddSubscribeMessageTemplate"), HttpPost]
|
||||
[DisplayName("增加订阅消息模板")]
|
||||
public async Task<dynamic> AddSubscribeMessageTemplate(AddSubscribeMessageTemplateInput input)
|
||||
{
|
||||
var accessToken = await GetCgibinToken();
|
||||
var reqMessage = new WxaApiNewTemplateAddTemplateRequest()
|
||||
{
|
||||
AccessToken = accessToken,
|
||||
TemplateTitleId = input.TemplateTitleId,
|
||||
KeyworkIdList = input.KeyworkIdList,
|
||||
SceneDescription = input.SceneDescription
|
||||
};
|
||||
var resTemplate = await _wechatApiClient.ExecuteWxaApiNewTemplateAddTemplateAsync(reqMessage);
|
||||
return resTemplate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成带参数小程序二维码(总共生成的码数量限制为 100,000)
|
||||
/// </summary>
|
||||
/// <param name="input"> 扫码进入的小程序页面路径,最大长度 128 个字符,不能为空; eg: pages / index ? id = AY000001 </param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("生成小程序二维码")]
|
||||
[ApiDescriptionSettings(Name = "GenerateQRImage")]
|
||||
public async Task<GenerateQRImageOutput> GenerateQRImageAsync(GenerateQRImageInput input)
|
||||
{
|
||||
GenerateQRImageOutput generateQRImageOutInput = new GenerateQRImageOutput();
|
||||
if (input.PagePath.IsNullOrEmpty())
|
||||
{
|
||||
generateQRImageOutInput.Message = $"生成失败 页面路径不能为空";
|
||||
return generateQRImageOutInput;
|
||||
}
|
||||
|
||||
if (input.ImageName.IsNullOrEmpty())
|
||||
{
|
||||
input.ImageName = DateTime.Now.ToString("yyyyMMddHHmmss");
|
||||
}
|
||||
|
||||
var accessToken = await GetCgibinToken();
|
||||
var request = new CgibinWxaappCreateWxaQrcodeRequest
|
||||
{
|
||||
AccessToken = accessToken,
|
||||
Path = input.PagePath,
|
||||
Width = input.Width
|
||||
};
|
||||
var response = await _wechatApiClient.ExecuteCgibinWxaappCreateWxaQrcodeAsync(request);
|
||||
|
||||
if (response.IsSuccessful())
|
||||
{
|
||||
var QRImagePath = App.GetConfig<string>("Wechat:QRImagePath");
|
||||
var relativeImgPath = string.Empty;
|
||||
|
||||
// 判断路径是绝对路径还是相对路径
|
||||
var isPathRooted = Path.IsPathRooted(QRImagePath);
|
||||
if (!isPathRooted)
|
||||
{
|
||||
// 相对路径
|
||||
relativeImgPath = string.IsNullOrEmpty(QRImagePath) ? Path.Combine("upload", "QRImage") : QRImagePath;
|
||||
QRImagePath = Path.Combine(App.WebHostEnvironment.WebRootPath, relativeImgPath);
|
||||
}
|
||||
|
||||
//判断文件存放路径是否存在
|
||||
if (!Directory.Exists(QRImagePath))
|
||||
{
|
||||
Directory.CreateDirectory(QRImagePath);
|
||||
}
|
||||
// 将二维码图片数据保存为文件
|
||||
var fileName = $"{input.ImageName.ToUpper()}.png";
|
||||
var filePath = Path.Combine(QRImagePath, fileName);
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
File.WriteAllBytes(filePath, response.GetRawBytes());
|
||||
|
||||
generateQRImageOutInput.Success = true;
|
||||
generateQRImageOutInput.ImgPath = filePath;
|
||||
generateQRImageOutInput.RelativeImgPath = Path.Combine(relativeImgPath, fileName);
|
||||
generateQRImageOutInput.Message = "生成成功";
|
||||
}
|
||||
else
|
||||
{
|
||||
// 处理错误情况
|
||||
generateQRImageOutInput.Message = $"生成失败 错误代码:{response.ErrorCode} 错误描述:{response.ErrorMessage}";
|
||||
}
|
||||
return generateQRImageOutInput;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成二维码(获取不受限制的小程序码)
|
||||
/// </summary>
|
||||
/// <param name="input">入参</param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("生成小程序二维码")]
|
||||
[ApiDescriptionSettings(Name = "GenerateQRImageUnlimit")]
|
||||
public async Task<GenerateQRImageOutput> GenerateQRImageUnlimitAsync(GenerateQRImageUnLimitInput input)
|
||||
{
|
||||
GenerateQRImageOutput generateQRImageOutInput = new GenerateQRImageOutput();
|
||||
if (input.PagePath.IsNullOrEmpty())
|
||||
{
|
||||
generateQRImageOutInput.Message = $"生成失败,页面路径不能为空";
|
||||
return generateQRImageOutInput;
|
||||
}
|
||||
|
||||
if (input.Scene.Length > 32)
|
||||
{
|
||||
generateQRImageOutInput.Message = $"生成失败,携带的参数长度超过限制";
|
||||
return generateQRImageOutInput;
|
||||
}
|
||||
|
||||
if (input.ImageName.IsNullOrEmpty())
|
||||
{
|
||||
input.ImageName = DateTime.Now.ToString("yyyyMMddHHmmss");
|
||||
}
|
||||
|
||||
var accessToken = await GetCgibinToken();
|
||||
var request = new WxaGetWxaCodeRequest
|
||||
{
|
||||
AccessToken = accessToken,
|
||||
Width = input.Width,
|
||||
PagePath = input.PagePath,
|
||||
};
|
||||
var response = await _wechatApiClient.ExecuteWxaGetWxaCodeAsync(request);
|
||||
|
||||
if (response.IsSuccessful())
|
||||
{
|
||||
var QRImagePath = App.GetConfig<string>("Wechat:QRImagePath");
|
||||
var relativeImgPath = string.Empty;
|
||||
|
||||
// 判断路径是绝对路径还是相对路径
|
||||
var isPathRooted = Path.IsPathRooted(QRImagePath);
|
||||
if (!isPathRooted)
|
||||
{
|
||||
// 相对路径
|
||||
relativeImgPath = string.IsNullOrEmpty(QRImagePath) ? Path.Combine("upload", "QRImageUnLimit") : QRImagePath;
|
||||
QRImagePath = Path.Combine(App.WebHostEnvironment.WebRootPath, relativeImgPath);
|
||||
}
|
||||
|
||||
//判断文件存放路径是否存在
|
||||
if (!Directory.Exists(QRImagePath))
|
||||
{
|
||||
Directory.CreateDirectory(QRImagePath);
|
||||
}
|
||||
// 将二维码图片数据保存为文件
|
||||
var fileName = $"{input.ImageName.ToUpper()}.png";
|
||||
var filePath = Path.Combine(QRImagePath, fileName);
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
File.WriteAllBytes(filePath, response.GetRawBytes());
|
||||
|
||||
generateQRImageOutInput.Success = true;
|
||||
generateQRImageOutInput.ImgPath = filePath;
|
||||
generateQRImageOutInput.RelativeImgPath = Path.Combine(relativeImgPath, fileName);
|
||||
generateQRImageOutInput.Message = "生成成功";
|
||||
}
|
||||
else
|
||||
{
|
||||
// 处理错误情况
|
||||
generateQRImageOutInput.Message = $"生成失败 错误代码:{response.ErrorCode} 错误描述:{response.ErrorMessage}";
|
||||
}
|
||||
return generateQRImageOutInput;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Access_token
|
||||
/// </summary>
|
||||
private async Task<string> GetCgibinToken()
|
||||
{
|
||||
return await _wechatApiClientFactory.TryGetWxOpenAccessTokenAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 微信API客户端
|
||||
/// </summary>
|
||||
public partial class WechatApiClientFactory : ISingleton
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
public readonly WechatOptions _wechatOptions;
|
||||
private readonly SysCacheService _sysCacheService;
|
||||
|
||||
public WechatApiClientFactory(IHttpClientFactory httpClientFactory, IOptions<WechatOptions> wechatOptions, SysCacheService sysCacheService)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
|
||||
_wechatOptions = wechatOptions.Value ?? throw new ArgumentNullException(nameof(wechatOptions));
|
||||
_sysCacheService = sysCacheService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信公众号
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public WechatApiClient CreateWechatClient()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_wechatOptions.WechatAppId) || string.IsNullOrEmpty(_wechatOptions.WechatAppSecret))
|
||||
throw Oops.Oh("微信公众号配置错误");
|
||||
|
||||
var client = WechatApiClientBuilder.Create(new WechatApiClientOptions()
|
||||
{
|
||||
AppId = _wechatOptions.WechatAppId,
|
||||
AppSecret = _wechatOptions.WechatAppSecret,
|
||||
PushToken = _wechatOptions.WechatToken,
|
||||
PushEncodingAESKey = _wechatOptions.WechatEncodingAESKey,
|
||||
})
|
||||
.UseHttpClient(_httpClientFactory.CreateClient(), disposeClient: false) // 设置 HttpClient 不随客户端一同销毁
|
||||
.Build();
|
||||
|
||||
client.Configure(config =>
|
||||
{
|
||||
JsonSerializerSettings jsonSerializerSettings = NewtonsoftJsonSerializer.GetDefaultSerializerSettings();
|
||||
jsonSerializerSettings.Formatting = Formatting.Indented;
|
||||
config.JsonSerializer = new NewtonsoftJsonSerializer(jsonSerializerSettings); // 指定 System.Text.Json JSON序列化
|
||||
// config.JsonSerializer = new SystemTextJsonSerializer(jsonSerializerOptions); // 指定 Newtonsoft.Json JSON序列化
|
||||
});
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信小程序
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public WechatApiClient CreateWxOpenClient()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_wechatOptions.WxOpenAppId) || string.IsNullOrEmpty(_wechatOptions.WxOpenAppSecret))
|
||||
throw Oops.Oh("微信小程序配置错误");
|
||||
|
||||
var client = WechatApiClientBuilder.Create(new WechatApiClientOptions()
|
||||
{
|
||||
AppId = _wechatOptions.WxOpenAppId,
|
||||
AppSecret = _wechatOptions.WxOpenAppSecret,
|
||||
PushToken = _wechatOptions.WxToken,
|
||||
PushEncodingAESKey = _wechatOptions.WxEncodingAESKey,
|
||||
})
|
||||
.UseHttpClient(_httpClientFactory.CreateClient(), disposeClient: false) // 设置 HttpClient 不随客户端一同销毁
|
||||
.Build();
|
||||
|
||||
client.Configure(config =>
|
||||
{
|
||||
JsonSerializerSettings jsonSerializerSettings = NewtonsoftJsonSerializer.GetDefaultSerializerSettings();
|
||||
jsonSerializerSettings.Formatting = Formatting.Indented;
|
||||
config.JsonSerializer = new NewtonsoftJsonSerializer(jsonSerializerSettings); // 指定 System.Text.Json JSON序列化
|
||||
// config.JsonSerializer = new SystemTextJsonSerializer(jsonSerializerOptions); // 指定 Newtonsoft.Json JSON序列化
|
||||
});
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取微信公众号AccessToken
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<string> TryGetWechatAccessTokenAsync()
|
||||
{
|
||||
if (!_sysCacheService.ExistKey($"WxAccessToken_{_wechatOptions.WechatAppId}") || string.IsNullOrEmpty(_sysCacheService.Get<string>($"WxAccessToken_{_wechatOptions.WechatAppId}")))
|
||||
{
|
||||
var client = CreateWechatClient();
|
||||
var reqCgibinToken = new CgibinTokenRequest();
|
||||
var resCgibinToken = await client.ExecuteCgibinTokenAsync(reqCgibinToken);
|
||||
if (resCgibinToken.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
|
||||
throw Oops.Oh(resCgibinToken.ErrorMessage + " " + resCgibinToken.ErrorCode);
|
||||
_sysCacheService.Set($"WxAccessToken_{_wechatOptions.WechatAppId}", resCgibinToken.AccessToken, TimeSpan.FromSeconds(resCgibinToken.ExpiresIn - 60));
|
||||
}
|
||||
|
||||
return _sysCacheService.Get<string>($"WxAccessToken_{_wechatOptions.WechatAppId}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取微信小程序AccessToken
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<string> TryGetWxOpenAccessTokenAsync()
|
||||
{
|
||||
if (!_sysCacheService.ExistKey($"WxAccessToken_{_wechatOptions.WxOpenAppId}") || string.IsNullOrEmpty(_sysCacheService.Get<string>($"WxAccessToken_{_wechatOptions.WxOpenAppId}")))
|
||||
{
|
||||
var client = CreateWxOpenClient();
|
||||
var reqCgibinToken = new CgibinTokenRequest();
|
||||
var resCgibinToken = await client.ExecuteCgibinTokenAsync(reqCgibinToken);
|
||||
if (resCgibinToken.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
|
||||
throw Oops.Oh(resCgibinToken.ErrorMessage + " " + resCgibinToken.ErrorCode);
|
||||
_sysCacheService.Set($"WxAccessToken_{_wechatOptions.WxOpenAppId}", resCgibinToken.AccessToken, TimeSpan.FromSeconds(resCgibinToken.ExpiresIn - 60));
|
||||
}
|
||||
|
||||
return _sysCacheService.Get<string>($"WxAccessToken_{_wechatOptions.WxOpenAppId}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查微信公众号AccessToken
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task CheckWechatAccessTokenAsync()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_wechatOptions.WechatAppId) || string.IsNullOrEmpty(_wechatOptions.WechatAppSecret)) return;
|
||||
|
||||
var req = new CgibinOpenApiQuotaGetRequest
|
||||
{
|
||||
AccessToken = await TryGetWechatAccessTokenAsync(),
|
||||
CgiPath = "/cgi-bin/token"
|
||||
};
|
||||
var client = CreateWechatClient();
|
||||
var res = await client.ExecuteCgibinOpenApiQuotaGetAsync(req);
|
||||
|
||||
var originColor = Console.ForegroundColor;
|
||||
if (res.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
|
||||
{
|
||||
_sysCacheService.Remove($"WxAccessToken_{_wechatOptions.WechatAppId}");
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine("【" + DateTime.Now + "】" + _wechatOptions.WxOpenAppId + " 微信公众号令牌 无效");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Magenta;
|
||||
Console.WriteLine("【" + DateTime.Now + "】" + _wechatOptions.WxOpenAppId + " 微信公众号令牌 有效");
|
||||
}
|
||||
Console.ForegroundColor = originColor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查微信小程序AccessToken
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task CheckWxOpenAccessTokenAsync()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_wechatOptions.WxOpenAppId) || string.IsNullOrEmpty(_wechatOptions.WxOpenAppSecret)) return;
|
||||
|
||||
var req = new CgibinOpenApiQuotaGetRequest
|
||||
{
|
||||
AccessToken = await TryGetWxOpenAccessTokenAsync(),
|
||||
CgiPath = "/cgi-bin/token"
|
||||
};
|
||||
var client = CreateWxOpenClient();
|
||||
var res = await client.ExecuteCgibinOpenApiQuotaGetAsync(req);
|
||||
|
||||
var originColor = Console.ForegroundColor;
|
||||
if (res.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
|
||||
{
|
||||
_sysCacheService.Remove($"WxAccessToken_{_wechatOptions.WxOpenAppId}");
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine("【" + DateTime.Now + "】" + _wechatOptions.WxOpenAppId + " 微信小程序令牌 无效");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Magenta;
|
||||
Console.WriteLine("【" + DateTime.Now + "】" + _wechatOptions.WxOpenAppId + " 微信小程序令牌 有效");
|
||||
}
|
||||
Console.ForegroundColor = originColor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取微信JS接口临时票据jsapi_ticket
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<string> TryGetWechatJsApiTicketAsync()
|
||||
{
|
||||
if (!_sysCacheService.ExistKey($"WxJsApiTicket_{_wechatOptions.WechatAppId}") || string.IsNullOrEmpty(_sysCacheService.Get<string>($"WxJsApiTicket_{_wechatOptions.WechatAppId}")))
|
||||
{
|
||||
var accessToken = await TryGetWechatAccessTokenAsync();
|
||||
var client = CreateWechatClient();
|
||||
var request = new CgibinTicketGetTicketRequest()
|
||||
{
|
||||
AccessToken = accessToken
|
||||
};
|
||||
var response = await client.ExecuteCgibinTicketGetTicketAsync(request);
|
||||
if (!response.IsSuccessful())
|
||||
throw Oops.Oh(response.ErrorMessage + " " + response.ErrorCode);
|
||||
_sysCacheService.Set($"WxJsApiTicket_{_wechatOptions.WechatAppId}", response.Ticket, TimeSpan.FromSeconds(response.ExpiresIn - 60));
|
||||
}
|
||||
return _sysCacheService.Get<string>($"WxJsApiTicket_{_wechatOptions.WechatAppId}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user