diff --git a/Controllers/ConfigController.cs b/Controllers/ConfigController.cs index e98b567..3e0587f 100644 --- a/Controllers/ConfigController.cs +++ b/Controllers/ConfigController.cs @@ -18,12 +18,14 @@ namespace dy.net.Controllers private readonly CommonService commonService; private readonly QuartzJobService quartzJobService; + private readonly DyQrCodeService _qrCodeService; - public ConfigController(DyCookieService dyCookieService, CommonService commonService,QuartzJobService quartzJobService) + public ConfigController(DyCookieService dyCookieService, CommonService commonService, QuartzJobService quartzJobService, DyQrCodeService qrCodeService) { this.dyCookieService = dyCookieService; this.commonService = commonService; this.quartzJobService = quartzJobService; + _qrCodeService = qrCodeService; } @@ -198,6 +200,84 @@ namespace dy.net.Controllers } + /// + /// 获取抖音扫码登录二维码 + /// + [HttpGet("qrcode")] + [Authorize] + public async Task GetQrCode() + { + try + { + var (token, qrBase64) = await _qrCodeService.GetQrCodeAsync(); + return Ok(new { code = 0, data = new { token, qrBase64 } }); + } + catch (Exception ex) + { + Serilog.Log.Error($"获取二维码失败: {ex.Message}"); + return Ok(new { code = -1, error = "获取二维码失败:" + ex.Message }); + } + } + + /// + /// 轮询扫码状态(前端每2秒调一次) + /// status: 1=待扫码, 2=已扫码待确认, 3=登录成功, -1=已过期 + /// + [HttpGet("qrstatus")] + [Authorize] + public async Task GetQrStatus([FromQuery] string token) + { + try + { + var (status, cookies, secUserId) = await _qrCodeService.CheckStatusAsync(token); + return Ok(new { code = 0, data = new { status, hasCookies = !string.IsNullOrEmpty(cookies), secUserId } }); + } + catch (Exception ex) + { + Serilog.Log.Error($"查询扫码状态失败: {ex.Message}"); + return Ok(new { code = -1, error = "查询失败:" + ex.Message }); + } + } + + /// + /// 扫码成功后,将 Cookie 绑定到指定账号 + /// + [HttpPost("qrbind")] + [Authorize] + public async Task BindQrCode([FromQuery] string token, [FromQuery] string id) + { + try + { + var (cookies, secUserId) = _qrCodeService.GetSessionResult(token); + if (string.IsNullOrEmpty(cookies)) + return Ok(new { code = -1, error = "扫码尚未完成或已过期" }); + + var account = await dyCookieService.GetByIdAsync(id); + if (account == null) + return Ok(new { code = -1, error = "账号不存在" }); + + account.Cookies = cookies; + if (!string.IsNullOrEmpty(secUserId)) + account.SecUserId = secUserId; + account.Status = 1; + + var result = await dyCookieService.UpdateAsync(account); + _qrCodeService.RemoveSession(token); + + if (result) + { + await ReStartJob(); + return Ok(new { code = 0, data = new { secUserId } }); + } + return Ok(new { code = -1, error = "保存失败" }); + } + catch (Exception ex) + { + Serilog.Log.Error($"绑定Cookie失败: {ex.Message}"); + return Ok(new { code = -1, error = "绑定失败:" + ex.Message }); + } + } + private async Task ReStartJob() { var config= commonService.GetConfig(); diff --git a/app/src/pages/cok/CookieTable.vue b/app/src/pages/cok/CookieTable.vue index 032789f..986543d 100644 --- a/app/src/pages/cok/CookieTable.vue +++ b/app/src/pages/cok/CookieTable.vue @@ -4,7 +4,7 @@ import { FormInstance } from 'ant-design-vue'; import { reactive, ref, onMounted } from 'vue'; import dayjs from 'dayjs'; import { Dayjs } from 'dayjs'; -import { EditFilled } from '@ant-design/icons-vue'; +import { EditFilled, QrcodeOutlined } from '@ant-design/icons-vue'; import { useApiStore } from '@/store'; import type { UnwrapRef } from 'vue'; @@ -220,6 +220,77 @@ const removeRow = (index) => { form.upSecUserIdsJson.splice(index, 1); }; const rowCount = 4; + +// 扫码登录 +const showQrModal = ref(false); +const qrToken = ref(''); +const qrBase64 = ref(''); +const qrStatus = ref(0); // 0=加载中, 1=待扫码, 2=已扫码待确认, 3=登录成功, -1=已过期 +const qrAccountId = ref(''); +const qrAccountName = ref(''); +const qrBindSuccess = ref(false); +let qrPollingTimer: ReturnType | null = null; + +const openQrModal = async (record: DataItem) => { + qrAccountId.value = record.id; + qrAccountName.value = record.userName; + qrStatus.value = 0; + qrBase64.value = ''; + qrToken.value = ''; + qrBindSuccess.value = false; + showQrModal.value = true; + try { + const res = await useApiStore().QrCodeGet(); + if (res.code === 0) { + qrToken.value = res.data.token; + qrBase64.value = res.data.qrBase64; + qrStatus.value = 1; + startQrPolling(); + } else { + qrStatus.value = -1; + } + } catch { + qrStatus.value = -1; + } +}; + +const startQrPolling = () => { + stopQrPolling(); + qrPollingTimer = setInterval(async () => { + if (!qrToken.value) return; + try { + const res = await useApiStore().QrCodeStatus(qrToken.value); + if (res.code === 0) { + qrStatus.value = res.data.status; + if (res.data.status === 3) { + stopQrPolling(); + const bindRes = await useApiStore().QrCodeBind(qrToken.value, qrAccountId.value); + if (bindRes.code === 0) { + qrBindSuccess.value = true; + setTimeout(() => { + showQrModal.value = false; + GetRecords(); + }, 1500); + } + } else if (res.data.status === -1) { + stopQrPolling(); + } + } + } catch { /* 忽略轮询网络错误 */ } + }, 2000); +}; + +const stopQrPolling = () => { + if (qrPollingTimer) { + clearInterval(qrPollingTimer); + qrPollingTimer = null; + } +}; + +const closeQrModal = () => { + stopQrPolling(); + showQrModal.value = false; +}; 删除 + + + 扫码登录 +
{{ text }} @@ -381,6 +458,28 @@ const rowCount = 4; + +
+
账号:{{ qrAccountName }}
+
加载中...
+
+
二维码已过期
+ 刷新二维码 +
+
+ 登录成功,Cookie 已更新! +
+
+ +
+ 请用抖音 App 扫描二维码 + 已扫码,请在手机上确认登录 + 登录成功,正在保存... +
+
+
+
+