|
@@ -1,20 +1,455 @@
|
|
|
|
|
+#!/usr/bin/env python
|
|
|
|
|
+# -*- encoding: utf-8 -*-
|
|
|
|
|
+'''
|
|
|
|
|
+@Contact : liuyuqi.gov@msn.cn
|
|
|
|
|
+@Time : 2023/05/17 21:17:34
|
|
|
|
|
+@License : Copyright © 2017-2022 liuyuqi. All Rights Reserved.
|
|
|
|
|
+@Desc : 摩点打卡核心类
|
|
|
|
|
+'''
|
|
|
|
|
+
|
|
|
import requests
|
|
import requests
|
|
|
|
|
+import json
|
|
|
|
|
+import logging
|
|
|
|
|
+from datetime import datetime
|
|
|
|
|
+from typing import Optional, Dict, Any, List
|
|
|
|
|
+
|
|
|
from crawl_modian import api
|
|
from crawl_modian import api
|
|
|
|
|
|
|
|
|
|
+
|
|
|
class Modian(object):
|
|
class Modian(object):
|
|
|
- ''''''
|
|
|
|
|
- def __init__(self):
|
|
|
|
|
- self.sess= requests.Session()
|
|
|
|
|
|
|
+ '''摩点打卡客户端'''
|
|
|
|
|
+
|
|
|
|
|
+ def __init__(self, params: dict = None) -> None:
|
|
|
|
|
+ '''初始化摩点客户端
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ params: 参数字典,可能包含:
|
|
|
|
|
+ - username: 用户名
|
|
|
|
|
+ - password: 密码
|
|
|
|
|
+ - token: 登录令牌
|
|
|
|
|
+ - cookie: Cookie字符串
|
|
|
|
|
+ - log: 日志文件路径
|
|
|
|
|
+ - debug: 是否开启调试模式
|
|
|
|
|
+ '''
|
|
|
|
|
+ self.params = params or {}
|
|
|
|
|
+ self.sess = requests.Session()
|
|
|
self.sess.headers.update(api.headers)
|
|
self.sess.headers.update(api.headers)
|
|
|
|
|
|
|
|
- def run(self):
|
|
|
|
|
- pass
|
|
|
|
|
|
|
+ # 配置日志
|
|
|
|
|
+ self._setup_logging()
|
|
|
|
|
+
|
|
|
|
|
+ # 初始化登录状态
|
|
|
|
|
+ self.is_logged_in = False
|
|
|
|
|
+ self.user_info = None
|
|
|
|
|
+
|
|
|
|
|
+ # 尝试使用已有的认证信息
|
|
|
|
|
+ self._init_auth()
|
|
|
|
|
+
|
|
|
|
|
+ def _setup_logging(self) -> None:
|
|
|
|
|
+ '''配置日志'''
|
|
|
|
|
+ log_level = logging.DEBUG if self.params.get('debug', False) else logging.INFO
|
|
|
|
|
+ log_file = self.params.get('log', None)
|
|
|
|
|
+
|
|
|
|
|
+ logging.basicConfig(
|
|
|
|
|
+ filename=log_file,
|
|
|
|
|
+ level=log_level,
|
|
|
|
|
+ format='%(asctime)s %(levelname)s %(message)s',
|
|
|
|
|
+ datefmt='%Y-%m-%d %H:%M:%S'
|
|
|
|
|
+ )
|
|
|
|
|
+ self.logger = logging.getLogger(__name__)
|
|
|
|
|
+
|
|
|
|
|
+ def _init_auth(self) -> None:
|
|
|
|
|
+ '''初始化认证信息'''
|
|
|
|
|
+ # 检查是否提供了 cookie
|
|
|
|
|
+ cookie = self.params.get('cookie', None)
|
|
|
|
|
+ if cookie:
|
|
|
|
|
+ self._set_cookie(cookie)
|
|
|
|
|
+ self.is_logged_in = True
|
|
|
|
|
+ self.logger.info("使用提供的Cookie进行认证")
|
|
|
|
|
+
|
|
|
|
|
+ # 检查是否提供了 token
|
|
|
|
|
+ token = self.params.get('token', None)
|
|
|
|
|
+ if token:
|
|
|
|
|
+ self.sess.headers.update({'Authorization': f'Bearer {token}'})
|
|
|
|
|
+ self.is_logged_in = True
|
|
|
|
|
+ self.logger.info("使用提供的Token进行认证")
|
|
|
|
|
+
|
|
|
|
|
+ def _set_cookie(self, cookie_str: str) -> None:
|
|
|
|
|
+ '''设置Cookie
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ cookie_str: Cookie字符串,格式如 "key1=value1; key2=value2"
|
|
|
|
|
+ '''
|
|
|
|
|
+ cookie_dict = {}
|
|
|
|
|
+ for item in cookie_str.split(';'):
|
|
|
|
|
+ if '=' in item:
|
|
|
|
|
+ key, value = item.strip().split('=', 1)
|
|
|
|
|
+ cookie_dict[key.strip()] = value.strip()
|
|
|
|
|
+
|
|
|
|
|
+ self.sess.cookies.update(cookie_dict)
|
|
|
|
|
+ self.logger.debug("Cookie已设置")
|
|
|
|
|
|
|
|
- def get_project_info(self, project_id):
|
|
|
|
|
- '''get project info'''
|
|
|
|
|
- self.sess.get(api.project_info_url+"/{}.html".format(project_id)
|
|
|
|
|
- , headers=api.headers)
|
|
|
|
|
|
|
+ def login(self, username: str = None, password: str = None) -> bool:
|
|
|
|
|
+ '''登录摩点账号
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ username: 用户名/手机号/邮箱
|
|
|
|
|
+ password: 密码
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ bool: 登录是否成功
|
|
|
|
|
+ '''
|
|
|
|
|
+ username = username or self.params.get('username')
|
|
|
|
|
+ password = password or self.params.get('password')
|
|
|
|
|
+
|
|
|
|
|
+ if not username or not password:
|
|
|
|
|
+ self.logger.error("用户名或密码为空")
|
|
|
|
|
+ return False
|
|
|
|
|
+
|
|
|
|
|
+ self.logger.info(f"尝试登录账号: {username}")
|
|
|
|
|
+
|
|
|
|
|
+ # 这里需要根据摩点实际的登录API来实现
|
|
|
|
|
+ # 由于摩点登录可能涉及验证码等复杂机制,
|
|
|
|
|
+ # 我们提供一个基础框架,实际使用时可能需要抓包分析
|
|
|
|
|
+
|
|
|
|
|
+ login_url = api.api_endpoints['login']
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ # 先访问登录页面获取必要的token
|
|
|
|
|
+ response = self.sess.get(login_url)
|
|
|
|
|
+ response.raise_for_status()
|
|
|
|
|
+
|
|
|
|
|
+ # 这里可能需要从页面中提取_csrf token等
|
|
|
|
|
+ # 实际实现时需要根据摩点的具体登录机制调整
|
|
|
|
|
+
|
|
|
|
|
+ login_data = {
|
|
|
|
|
+ 'username': username,
|
|
|
|
|
+ 'password': password,
|
|
|
|
|
+ # 可能需要添加其他字段如 _csrf, captcha 等
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ response = self.sess.post(login_url, data=login_data)
|
|
|
|
|
+ response.raise_for_status()
|
|
|
|
|
+
|
|
|
|
|
+ # 检查登录是否成功
|
|
|
|
|
+ # 这里需要根据实际返回结果判断
|
|
|
|
|
+ if response.status_code == 200:
|
|
|
|
|
+ self.is_logged_in = True
|
|
|
|
|
+ self.logger.info("登录成功")
|
|
|
|
|
+ return True
|
|
|
|
|
+ else:
|
|
|
|
|
+ self.logger.error(f"登录失败,状态码: {response.status_code}")
|
|
|
|
|
+ return False
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ self.logger.error(f"登录过程中发生错误: {str(e)}")
|
|
|
|
|
+ return False
|
|
|
|
|
|
|
|
- def get_project_rank(self, project_id):
|
|
|
|
|
- '''get project rank'''
|
|
|
|
|
- pass
|
|
|
|
|
|
|
+ def check_in(self) -> Dict[str, Any]:
|
|
|
|
|
+ '''执行打卡操作
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ Dict: 打卡结果,包含成功状态和消息
|
|
|
|
|
+ '''
|
|
|
|
|
+ if not self.is_logged_in:
|
|
|
|
|
+ self.logger.warning("未登录,无法执行打卡")
|
|
|
|
|
+ return {
|
|
|
|
|
+ 'success': False,
|
|
|
|
|
+ 'message': '未登录,请先登录',
|
|
|
|
|
+ 'timestamp': datetime.now().isoformat()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ self.logger.info("开始执行打卡操作")
|
|
|
|
|
+
|
|
|
|
|
+ check_in_url = api.api_endpoints['check_in']
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ response = self.sess.post(check_in_url)
|
|
|
|
|
+ response.raise_for_status()
|
|
|
|
|
+
|
|
|
|
|
+ # 尝试解析JSON响应
|
|
|
|
|
+ try:
|
|
|
|
|
+ result = response.json()
|
|
|
|
|
+ self.logger.info(f"打卡响应: {json.dumps(result, ensure_ascii=False)}")
|
|
|
|
|
+
|
|
|
|
|
+ # 根据实际返回结构判断是否成功
|
|
|
|
|
+ success = result.get('success', False) or result.get('code', 0) == 0
|
|
|
|
|
+ message = result.get('message', '打卡完成')
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ 'success': success,
|
|
|
|
|
+ 'message': message,
|
|
|
|
|
+ 'data': result,
|
|
|
|
|
+ 'timestamp': datetime.now().isoformat()
|
|
|
|
|
+ }
|
|
|
|
|
+ except json.JSONDecodeError:
|
|
|
|
|
+ # 如果不是JSON响应,检查响应内容
|
|
|
|
|
+ content = response.text
|
|
|
|
|
+ self.logger.debug(f"打卡响应内容: {content[:200]}")
|
|
|
|
|
+
|
|
|
|
|
+ # 根据实际情况判断
|
|
|
|
|
+ if '成功' in content or 'success' in content.lower():
|
|
|
|
|
+ return {
|
|
|
|
|
+ 'success': True,
|
|
|
|
|
+ 'message': '打卡成功',
|
|
|
|
|
+ 'data': {'raw_response': content},
|
|
|
|
|
+ 'timestamp': datetime.now().isoformat()
|
|
|
|
|
+ }
|
|
|
|
|
+ else:
|
|
|
|
|
+ return {
|
|
|
|
|
+ 'success': False,
|
|
|
|
|
+ 'message': '打卡失败,请检查响应',
|
|
|
|
|
+ 'data': {'raw_response': content},
|
|
|
|
|
+ 'timestamp': datetime.now().isoformat()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ self.logger.error(f"打卡过程中发生错误: {str(e)}")
|
|
|
|
|
+ return {
|
|
|
|
|
+ 'success': False,
|
|
|
|
|
+ 'message': f'打卡异常: {str(e)}',
|
|
|
|
|
+ 'timestamp': datetime.now().isoformat()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ def get_check_in_status(self) -> Dict[str, Any]:
|
|
|
|
|
+ '''获取打卡状态
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ Dict: 打卡状态信息
|
|
|
|
|
+ '''
|
|
|
|
|
+ if not self.is_logged_in:
|
|
|
|
|
+ self.logger.warning("未登录,无法获取打卡状态")
|
|
|
|
|
+ return {
|
|
|
|
|
+ 'success': False,
|
|
|
|
|
+ 'message': '未登录,请先登录',
|
|
|
|
|
+ 'checked_in': False,
|
|
|
|
|
+ 'timestamp': datetime.now().isoformat()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ self.logger.info("获取打卡状态")
|
|
|
|
|
+
|
|
|
|
|
+ status_url = api.api_endpoints['check_in_status']
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ response = self.sess.get(status_url)
|
|
|
|
|
+ response.raise_for_status()
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ result = response.json()
|
|
|
|
|
+ self.logger.info(f"打卡状态: {json.dumps(result, ensure_ascii=False)}")
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ 'success': True,
|
|
|
|
|
+ 'message': '获取状态成功',
|
|
|
|
|
+ 'data': result,
|
|
|
|
|
+ 'checked_in': result.get('checked_in', False) or result.get('todayChecked', False),
|
|
|
|
|
+ 'continuous_days': result.get('continuous_days', 0) or result.get('continuousCheckInDays', 0),
|
|
|
|
|
+ 'total_days': result.get('total_days', 0) or result.get('totalCheckInDays', 0),
|
|
|
|
|
+ 'timestamp': datetime.now().isoformat()
|
|
|
|
|
+ }
|
|
|
|
|
+ except json.JSONDecodeError:
|
|
|
|
|
+ content = response.text
|
|
|
|
|
+ self.logger.debug(f"状态响应内容: {content[:200]}")
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ 'success': True,
|
|
|
|
|
+ 'message': '获取状态成功',
|
|
|
|
|
+ 'data': {'raw_response': content},
|
|
|
|
|
+ 'checked_in': '已打卡' in content or 'checked' in content.lower(),
|
|
|
|
|
+ 'timestamp': datetime.now().isoformat()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ self.logger.error(f"获取打卡状态时发生错误: {str(e)}")
|
|
|
|
|
+ return {
|
|
|
|
|
+ 'success': False,
|
|
|
|
|
+ 'message': f'获取状态异常: {str(e)}',
|
|
|
|
|
+ 'checked_in': False,
|
|
|
|
|
+ 'timestamp': datetime.now().isoformat()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ def get_user_info(self) -> Dict[str, Any]:
|
|
|
|
|
+ '''获取用户信息
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ Dict: 用户信息
|
|
|
|
|
+ '''
|
|
|
|
|
+ if not self.is_logged_in:
|
|
|
|
|
+ self.logger.warning("未登录,无法获取用户信息")
|
|
|
|
|
+ return {
|
|
|
|
|
+ 'success': False,
|
|
|
|
|
+ 'message': '未登录,请先登录',
|
|
|
|
|
+ 'timestamp': datetime.now().isoformat()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ self.logger.info("获取用户信息")
|
|
|
|
|
+
|
|
|
|
|
+ user_info_url = api.api_endpoints['user_info']
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ response = self.sess.get(user_info_url)
|
|
|
|
|
+ response.raise_for_status()
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ result = response.json()
|
|
|
|
|
+ self.user_info = result
|
|
|
|
|
+ self.logger.info(f"用户信息获取成功")
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ 'success': True,
|
|
|
|
|
+ 'message': '获取用户信息成功',
|
|
|
|
|
+ 'data': result,
|
|
|
|
|
+ 'timestamp': datetime.now().isoformat()
|
|
|
|
|
+ }
|
|
|
|
|
+ except json.JSONDecodeError:
|
|
|
|
|
+ content = response.text
|
|
|
|
|
+ return {
|
|
|
|
|
+ 'success': True,
|
|
|
|
|
+ 'message': '获取用户信息成功',
|
|
|
|
|
+ 'data': {'raw_response': content},
|
|
|
|
|
+ 'timestamp': datetime.now().isoformat()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ self.logger.error(f"获取用户信息时发生错误: {str(e)}")
|
|
|
|
|
+ return {
|
|
|
|
|
+ 'success': False,
|
|
|
|
|
+ 'message': f'获取用户信息异常: {str(e)}',
|
|
|
|
|
+ 'timestamp': datetime.now().isoformat()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ def get_project_info(self, project_id: int) -> Dict[str, Any]:
|
|
|
|
|
+ '''获取项目信息
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ project_id: 项目ID
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ Dict: 项目信息
|
|
|
|
|
+ '''
|
|
|
|
|
+ self.logger.info(f"获取项目信息: {project_id}")
|
|
|
|
|
+
|
|
|
|
|
+ project_url = api.api_endpoints['project_detail'].format(project_id=project_id)
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ response = self.sess.get(project_url)
|
|
|
|
|
+ response.raise_for_status()
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ result = response.json()
|
|
|
|
|
+ return {
|
|
|
|
|
+ 'success': True,
|
|
|
|
|
+ 'message': '获取项目信息成功',
|
|
|
|
|
+ 'data': result,
|
|
|
|
|
+ 'timestamp': datetime.now().isoformat()
|
|
|
|
|
+ }
|
|
|
|
|
+ except json.JSONDecodeError:
|
|
|
|
|
+ content = response.text
|
|
|
|
|
+ return {
|
|
|
|
|
+ 'success': True,
|
|
|
|
|
+ 'message': '获取项目信息成功',
|
|
|
|
|
+ 'data': {'raw_response': content},
|
|
|
|
|
+ 'timestamp': datetime.now().isoformat()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ self.logger.error(f"获取项目信息时发生错误: {str(e)}")
|
|
|
|
|
+ return {
|
|
|
|
|
+ 'success': False,
|
|
|
|
|
+ 'message': f'获取项目信息异常: {str(e)}',
|
|
|
|
|
+ 'timestamp': datetime.now().isoformat()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ def get_project_rank(self, project_id: int) -> Dict[str, Any]:
|
|
|
|
|
+ '''获取项目排名/支持者列表
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ project_id: 项目ID
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ Dict: 排名信息
|
|
|
|
|
+ '''
|
|
|
|
|
+ self.logger.info(f"获取项目排名: {project_id}")
|
|
|
|
|
+
|
|
|
|
|
+ # 这里需要根据摩点实际的API来实现
|
|
|
|
|
+ # 排名通常在项目详情中,或者有专门的接口
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ # 先尝试获取项目详情,可能包含排名信息
|
|
|
|
|
+ project_info = self.get_project_info(project_id)
|
|
|
|
|
+
|
|
|
|
|
+ if project_info['success']:
|
|
|
|
|
+ return {
|
|
|
|
|
+ 'success': True,
|
|
|
|
|
+ 'message': '获取项目排名成功',
|
|
|
|
|
+ 'data': project_info.get('data', {}),
|
|
|
|
|
+ 'timestamp': datetime.now().isoformat()
|
|
|
|
|
+ }
|
|
|
|
|
+ else:
|
|
|
|
|
+ return project_info
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ self.logger.error(f"获取项目排名时发生错误: {str(e)}")
|
|
|
|
|
+ return {
|
|
|
|
|
+ 'success': False,
|
|
|
|
|
+ 'message': f'获取项目排名异常: {str(e)}',
|
|
|
|
|
+ 'timestamp': datetime.now().isoformat()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ def run(self) -> Dict[str, Any]:
|
|
|
|
|
+ '''执行主流程:登录 -> 检查打卡状态 -> 打卡
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ Dict: 执行结果汇总
|
|
|
|
|
+ '''
|
|
|
|
|
+ self.logger.info("开始执行摩点打卡主流程")
|
|
|
|
|
+
|
|
|
|
|
+ results = {
|
|
|
|
|
+ 'timestamp': datetime.now().isoformat(),
|
|
|
|
|
+ 'steps': {}
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # 步骤1:检查登录状态
|
|
|
|
|
+ if not self.is_logged_in:
|
|
|
|
|
+ # 尝试使用用户名密码登录
|
|
|
|
|
+ if self.params.get('username') and self.params.get('password'):
|
|
|
|
|
+ login_result = self.login()
|
|
|
|
|
+ results['steps']['login'] = login_result
|
|
|
|
|
+ if not login_result:
|
|
|
|
|
+ results['success'] = False
|
|
|
|
|
+ results['message'] = '登录失败'
|
|
|
|
|
+ return results
|
|
|
|
|
+ else:
|
|
|
|
|
+ results['success'] = False
|
|
|
|
|
+ results['message'] = '未提供认证信息(Cookie/Token或用户名密码)'
|
|
|
|
|
+ return results
|
|
|
|
|
+ else:
|
|
|
|
|
+ results['steps']['login'] = {'success': True, 'message': '已登录'}
|
|
|
|
|
+
|
|
|
|
|
+ # 步骤2:获取打卡状态
|
|
|
|
|
+ status_result = self.get_check_in_status()
|
|
|
|
|
+ results['steps']['check_status'] = status_result
|
|
|
|
|
+
|
|
|
|
|
+ # 步骤3:如果今天还没打卡,执行打卡
|
|
|
|
|
+ if not status_result.get('checked_in', False):
|
|
|
|
|
+ check_in_result = self.check_in()
|
|
|
|
|
+ results['steps']['check_in'] = check_in_result
|
|
|
|
|
+
|
|
|
|
|
+ if check_in_result.get('success', False):
|
|
|
|
|
+ results['success'] = True
|
|
|
|
|
+ results['message'] = '打卡成功'
|
|
|
|
|
+ else:
|
|
|
|
|
+ results['success'] = False
|
|
|
|
|
+ results['message'] = check_in_result.get('message', '打卡失败')
|
|
|
|
|
+ else:
|
|
|
|
|
+ results['success'] = True
|
|
|
|
|
+ results['message'] = '今日已打卡'
|
|
|
|
|
+ results['steps']['check_in'] = {'success': True, 'message': '今日已打卡,无需重复打卡'}
|
|
|
|
|
+
|
|
|
|
|
+ # 步骤4:获取用户信息(可选)
|
|
|
|
|
+ user_info_result = self.get_user_info()
|
|
|
|
|
+ results['steps']['user_info'] = user_info_result
|
|
|
|
|
+
|
|
|
|
|
+ self.logger.info(f"打卡流程完成,结果: {json.dumps(results, ensure_ascii=False, default=str)}")
|
|
|
|
|
+
|
|
|
|
|
+ return results
|