【原创】全自动添加防火墙vpn+JumpServer账号

使用wxbot对接华三防火墙开通vpn账号和开通JumpServer堡垒机账号,让运维如此轻松

效果


管理员激活

复制代码
申请vpn和堡垒机账号指令 /申请vpn+公司邮箱地址+访问使用说明
/申请vpn 362@qq.com test

激活指令 /激活vpn+任务id+权限id
/激活vpn e432abeb-e5b5-47f9-a4c7-a7e86b79f5f4 b6c715c5-d997-4017-a265-56d37e4a3acf

密码通过邮件发送

上代码

请配置好防火墙管理员账号和密码 、JumpServer管理员账号的ak sk

复制代码
# -*- coding:utf-8 -*-
import http.client
import ssl  # 导入证书模块
import json
import sys
import uuid
import datetime
import websocket
import time
import random
import json
import requests
import paramiko
import os
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header
import re
import uuid
import mysql.connector
import string
import logging
from httpsig.requests_auth import HTTPSignatureAuth
from bs4 import BeautifulSoup
from mysql.connector import pooling
from datetime import datetime
from httpsig.requests_auth import HTTPSignatureAuth
from datetime import datetime
from email.utils import formatdate
# 设置日志记录到文件和控制台
logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
                    handlers=[
                        logging.FileHandler("app.log"),
                        logging.StreamHandler(sys.stdout)  # 输出到控制台
                    ])
#################################申请vpn和堡垒机 和激活功能

def send_email(to_email, vpn_account, vpn_password):
    smtp_server = 'smtp.exmail.qq.com'
    smtp_port = 465
    from_email = 'f******_admin@fzda********ke.com'
    from_password = '***************************oq'  # 请确保在生产环境中妥善保管密码

    # 构建邮件内容
    subject = "root - Your VPN Account Details"
    body = f"Dear user,\n\n您的VPN和堡垒机帐户 '{vpn_account}' has been created successfully. Your password is: {vpn_password}\n\n请及时修改密码。\n\n致以最诚挚的问候,\nroot-交付运维中心\n【腾讯文档】互联网区域堡垒机使用方法v20210713https://docs.qq.com/doc/DWm*************ek1W"
    
    # 设置邮件的 MIME 类型和内容
    message = MIMEMultipart()
    message['From'] = Header("root-IT Support", 'utf-8')
    message['To'] = Header(to_email, 'utf-8')
    message['Subject'] = Header(subject, 'utf-8')
    
    message.attach(MIMEText(body, 'plain', 'utf-8'))
    
    try:
        # 使用 SSL 连接到 SMTP 服务器
        with smtplib.SMTP_SSL(smtp_server, smtp_port) as server:
            server.login(from_email, from_password)
            server.sendmail(from_email, to_email, message.as_string())
        
        logging.info(f"Email sent successfully to {to_email}")

    except smtplib.SMTPException as e:
        logging.error(f"Failed to send email to {to_email}: {str(e)}")
        
        
def generate_strong_password(length=12):
    # 确保密码包含至少一个大写字母、小写字母、数字和特殊字符
    password_characters = string.ascii_letters + string.digits + string.punctuation

    # 随机选择一个大写字母、小写字母、数字和特殊字符
    password = [
        random.choice(string.ascii_uppercase),
        random.choice(string.ascii_lowercase),
        random.choice(string.digits),
        random.choice(string.punctuation),
    ]

    # 填充剩余的密码长度
    password += [random.choice(password_characters) for _ in range(length - 4)]

    # 打乱密码字符的顺序
    random.shuffle(password)

    return ''.join(password)

def apply_vpn_account(applicant_wxid, email, purpose):
    vpn_account = email.split('@')[0]
    vpn_password = generate_strong_password()
    application_uuid = str(uuid.uuid4())
    logging.info(f"Info: apply_vpn_account {vpn_account} with vpn_password {vpn_password} application_uuid {application_uuid}")

    # 连接MySQL数据库并保存申请记录
    connection = mysql.connector.connect(
        host="172.2.2.50",
        user="root",
        password="root",
        database="root"
    )
    cursor = connection.cursor()

    # 检查邮箱是否已经存在
    check_query = "SELECT COUNT(*) FROM fa_vpn_fw_blj WHERE email = %s"
    cursor.execute(check_query, (email,))
    (count,) = cursor.fetchone()

    if count > 0:
        logging.warning(f"Warning: Email {email} 申请过.")
        cursor.close()
        connection.close()
        return None, None, None

    # 插入新记录
    query = ("INSERT INTO fa_vpn_fw_blj (uuid, applicant_wxid, vpn_account, vpn_password, email, purpose, status) "
             "VALUES (%s, %s, %s, %s, %s, %s, %s)")
    cursor.execute(query, (application_uuid, applicant_wxid, vpn_account, vpn_password, email, purpose, '待激活'))
    connection.commit()
    
    cursor.close()
    connection.close()
    
    return application_uuid, vpn_account, vpn_password
    
def get_auth(KeyID, SecretID):
    signature_headers = ['(request-target)', 'accept', 'date']
    auth = HTTPSignatureAuth(key_id=KeyID, secret=SecretID, algorithm='hmac-sha256', headers=signature_headers)
    return auth

def get_users_groups():
    try:
        # 调用堡垒机API接口查询权限组
        url = "https://172.20.40.27/api/v1/users/groups/"
        
        # 获取当前的日期时间
        date_header = formatdate(timeval=None, localtime=False, usegmt=True)
        
        headers = {
            "Content-Type": "application/json",
            "Authorization": "Bearer your_token",
            "date": date_header
        }

        auth = get_auth("361e490f-***************-99482dc6113f", "029b1e59-aa10-*************-131a3b390ef5")
        response = requests.get(url, headers=headers, auth=auth, verify=False)
        
        logging.info(f"Response status code: {response.status_code}")
        if response.status_code == 200:
            groups = response.json()
            logging.info(f"查询堡垒机权限组成功: {groups}")
            return groups
        else:
            logging.error(f"查询堡垒机权限组失败: {response.text}")
            return None
            
    except Exception as e:
        logging.error(f"Failed to query user groups from the bastion host: {str(e)}")
        raise
def combine_id_name_to_string(groups):
    # 将每个 id 和 name 组合成一个小字符串
    id_name_strings = [f"ID: {group['id']}, Name: {group['name']}" for group in groups]
    
    # 将所有小字符串组合成一个长字符串,使用换行符分隔
    combined_string = "\n".join(id_name_strings)
    
    return combined_string

def activate_vpn_account(application_uuid, users_groupsID , activator_wxid):
    try:
        # 连接MySQL数据库并获取申请记录
        connection = mysql.connector.connect(
            host="172.2.2.50",
            user="root",
            password="root",
            database="root"
        )
        cursor = connection.cursor()
        
        query = ("SELECT vpn_account, vpn_password, email FROM fa_vpn_fw_blj WHERE uuid=%s AND status='待激活'")
        cursor.execute(query, (application_uuid,))
        result = cursor.fetchone()
        
        if result:
            vpn_account, vpn_password, email = result
            logging.info(f"Activating VPN account for {vpn_account} with email {email} by {activator_wxid}")

            try:
                # 连接防火墙,执行VPN账号添加功能
                ssh = paramiko.SSHClient()
                ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
                ssh.connect("172.50.0.1", port=22, username="*****************", password="****************")
                
                cmd = f"""
                sys
                user-group psy_system_auto_add
                identity-member user {vpn_account}
                exit
                local-user {vpn_account} class network
                password simple {vpn_password}
                access-limit 1
                service-type sslvpn
                group psy_system_auto_add
                authorization-attribute user-role network-operator
                authorization-attribute sslvpn-policy-group SSLVPNZIYUA
                identity-group psy_system_auto_add
                description {vpn_account}-{email}
                exit
                save
                y
                """
                stdin, stdout, stderr = ssh.exec_command(cmd)
                stdout.channel.recv_exit_status()  # 确保命令执行完成
                ssh.close()
                logging.info(f"VPN account {vpn_account} added to the firewall successfully.")
            
            except Exception as e:
                logging.error(f"Failed to add VPN account to the firewall: {str(e)}")
                return(f"Failed to add VPN account to the firewall: {str(e)}")
                raise  # 重新抛出异常,以便进一步处理
            logging.info(f"堡垒机账号 {vpn_account} 密码{vpn_password}。")
            try:
                # 调用堡垒机API接口添加账号
                url = "https://172.20.40.27/api/v1/users/users/"
                
                # 获取当前的日期时间
                date_header = formatdate(timeval=None, localtime=False, usegmt=True)
                
                headers = {
                    "Content-Type": "application/json",
                    "Authorization": "Bearer your_token",
                    "date": date_header
                }
                data = {
                    "id": application_uuid,
                    "name": vpn_account,
                    "username": vpn_account,
                    "password": vpn_password,
                    "email": email,
                    "role": "User",
                    "groups": [f"{users_groupsID}"],
                    "password_strategy": "custom"
                }
                auth = get_auth("361e490f-************-99482dc6113f", "029b1e59-aa10-********************1a3b390ef5")
                response = requests.post(url, headers=headers, json=data, auth=auth ,verify=False)
                
                if response.status_code == 201:
                    # 更新数据库状态为已激活,记录激活操作人wxid和激活时间
                    activation_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
                    update_query = """
                    UPDATE fa_vpn_fw_blj 
                    SET status=%s, activator_wxid=%s, activation_time=%s 
                    WHERE uuid=%s
                    """
                    cursor.execute(update_query, ('已激活', activator_wxid, activation_time, application_uuid))
                    connection.commit()
                    logging.info(f"堡垒机账号 {vpn_account} 添加成功,并已激活。")
                    send_email(email, vpn_account, vpn_password) 
                    return(f"堡垒机账号 {vpn_account} 添加成功,并已激活。账号和密码信息请查收由fzdatalake_admin@fzdatalake.com发送的邮件信息")
                else:
                    logging.error(f"堡垒机账号添加失败: {response.text}")
                    return(f"堡垒机账号添加失败: {response.text}")
            
            except Exception as e:
                logging.error(f"Failed to add account to the bastion host: {str(e)}")
                raise  # 重新抛出异常,以便进一步处理
        
        else:
            logging.warning(f"没有待定的VPN激活 for UUID: {application_uuid}")
            return(f"没有待定的VPN激活/或已经激活过 for UUID: {application_uuid}")
    
    except mysql.connector.Error as err:
        logging.error(f"Database error: {str(err)}")
    
    except Exception as e:
        logging.error(f"An error occurred: {str(e)}")
    
    finally:
        if cursor:
            cursor.close()
        if connection:
            connection.close()

#################################申请vpn和堡垒机 和激活功能
def output(msg):
    now = time.strftime("%Y-%m-%d %X")
    logging.info(f'[{now}]: {msg} - {script_name}')

# 获取当前脚本的名称  
script_name = os.path.basename(__file__)  
output(f"当前脚本的名称是: {script_name}")

ip = '172.2.2.50'
port = 5555

SERVER = f'ws://{ip}:{port}'
HEART_BEAT = 5005
RECV_TXT_MSG = 1
RECV_TXT_CITE_MSG = 49
RECV_PIC_MSG = 3
USER_LIST = 5000
GET_USER_LIST_SUCCSESS = 5001
GET_USER_LIST_FAIL = 5002
TXT_MSG = 555
PIC_MSG = 500
AT_MSG = 550
CHATROOM_MEMBER = 5010
CHATROOM_MEMBER_NICK = 5020
PERSONAL_INFO = 6500
DEBUG_SWITCH = 6000
PERSONAL_DETAIL = 6550
DESTROY_ALL = 9999
JOIN_ROOM = 10000

 

########################################################################################

def match_keyword(keywords, text):
    for keyword in keywords.keys():
        if keyword in text:
            output(keyword)
            return keyword
    return None

def getid():
    return time.strftime("%Y%m%d%H%M%S")

################################### HTTP ################################################
def send(uri, data):
    base_data = {
        'id': getid(),
        'type': 'null',
        'roomid': 'null',
        'wxid': 'null',
        'content': 'null',
        'nickname': 'null',
        'ext': 'null',
    }
    base_data.update(data)
    url = f'http://{ip}:{port}/{uri}'
    try:
        res = requests.post(url, json={'para': base_data}, timeout=5)
        res.raise_for_status()
        return res.json()
    except requests.exceptions.RequestException as e:
        output(f"HTTP Request failed: {e}")
        return None

def get_member_nick(roomid, wxid):
    uri = 'api/getmembernick'
    data = {
        'type': CHATROOM_MEMBER_NICK,
        'wxid': wxid,
        'roomid': roomid or 'null'
    }
    respJson = send(uri, data)
    if respJson:
        return json.loads(respJson['content'])['nick']
    return None

def get_personal_info():
    uri = '/api/get_personal_info'
    data = {
        'id': getid(),
        'type': PERSONAL_INFO,
        'content': 'op:personal info',
        'wxid': 'null',
    }
    respJson = send(uri, data)
    output(respJson)

################################### websocket ################################################
def get_chat_nick_p(roomid):
    qs = {
        'id': getid(),
        'type': CHATROOM_MEMBER_NICK,
        'content': roomid,
        'wxid': 'ROOT',
    }
    return json.dumps(qs)

def debug_switch():
    qs = {
        'id': getid(),
        'type': DEBUG_SWITCH,
        'content': 'off',
        'wxid': 'ROOT',
    }
    return json.dumps(qs)

def handle_nick(j):
    data = j.content
    for d in data:
        output(f'nickname: {d.nickname}')

def hanle_memberlist(j):
    data = j.content
    for d in data:
        output(f'roomid: {d.roomid}')

def get_chatroom_memberlist():
    qs = {
        'id': getid(),
        'type': CHATROOM_MEMBER,
        'wxid': 'null',
        'content': 'op:list member',
    }
    return json.dumps(qs)

def get_personal_detail(wxid):
    qs = {
        'id': getid(),
        'type': PERSONAL_DETAIL,
        'content': 'op:personal detail',
        'wxid': wxid,
    }
    return json.dumps(qs)

def send_wxuser_list():
    qs = {
        'id': getid(),
        'type': USER_LIST,
        'content': 'user list',
        'wxid': 'null',
    }
    return json.dumps(qs)

def handle_wxuser_list(j):
    output('启动完成')

###################################################################################
def heartbeat(msgJson):
    output(msgJson['content'])

def on_open(ws):
    ws.send(send_wxuser_list())

def on_error(ws, error):
    output(f'on_error: {error}')

def on_close(ws):
    output("closed")

###################################################################################
def destroy_all():
    qs = {
        'id': getid(),
        'type': DESTROY_ALL,
        'content': 'none',
        'wxid': 'node',
    }
    return json.dumps(qs)

def send_msg(msg, wxid='null', roomid=None, nickname='null'):
    if msg.endswith('png'):
        msg_type = PIC_MSG
        if roomid:
            wxid = roomid
            roomid = None
            nickname = 'null'
    elif roomid:
        msg_type = AT_MSG
    else:
        msg_type = TXT_MSG
    if roomid is None:
        roomid = 'null'
    qs = {
        'id': getid(),
        'type': msg_type,
        'roomid': roomid,
        'wxid': wxid,
        'content': msg,
        'nickname': nickname,
        'ext': 'null'
    }
    output(f'发送消息: {qs}')
    return json.dumps(qs)

###################################################################################
def welcome_join(msgJson):
    output(f'收到消息: {msgJson}')
    if '邀请' in msgJson['content']['content']:
        roomid = msgJson['content']['id1']
        nickname = msgJson['content']['content'].split('"')[-2]
        #ws.send(send_msg(f'欢迎新进群', roomid=roomid, wxid='null', nickname=nickname))

def handleMsg_cite(msgJson):
    msgXml = msgJson['content']['content'].replace('&amp;', '&').replace('&lt;', '<').replace('&gt;', '>')
    soup = BeautifulSoup(msgXml, 'lxml')
    msgJson = {
        'content': soup.select_one('title').text,
        'id': msgJson['id'],
        'id1': msgJson['content']['id2'],
        'id2': 'wxid_fys2fico9put22',
        'id3': '',
        'srvid': msgJson['srvid'],
        'time': msgJson['time'],
        'type': msgJson['type'],
        'wxid': msgJson['content']['id1']
    }
    handle_recv_msg(msgJson)



def get_type_ollama(operation_content):
    url = "http://192.168.250.249:11434/api/generate"
    payload = json.dumps({
        "model": "qwen2:7b",
        "stream": False,
        "system": "你是一个运维事件记录员,你的名字叫小湖,你只要识别出事件的类型。服务器、应用程序、交换机、防火墙策略、其他这5种类型,选择其中一个简短明了回答是什么类型",
        "prompt": f"{operation_content} ,这个操作事件内容是'服务器、应用程序、交换机、防火墙策略、其他'中是哪种操作?直接回答类型"
    })
    headers = {
        'Content-Type': 'application/json'
    }

    retries = 3
    for attempt in range(retries):
        try:
            response = requests.post(url, headers=headers, data=payload)
            response.raise_for_status()
            result = response.json()
            output(f"识别的类型: {result['response']}")
            return result['response']
        except requests.exceptions.RequestException as e:
            output(f"AI请求失败 (尝试 {attempt + 1}/{retries}): {e}")
            time.sleep(2)
    output("AI服务不可用,使用默认类型")
    return "其他"  # 默认类型或错误处理

def handle_recv_msg(msgJson):
    logging.info(f'Received message: {msgJson}')

    # 获取消息内容
    keyword = msgJson['content'].replace('\u2005', '').strip()

    # 匹配申请VPN指令
    apply_vpn_match = re.match(r'/申请vpn\s+(\S+)\s+(.+)', keyword)
    # 匹配激活VPN指令
    activate_vpn_match = re.match(r'/激活vpn\s+(\S+)\s+(\S+)', keyword)

    # 获取房间ID和发送者ID
    roomid = msgJson['wxid'] if '@chatroom' in msgJson['wxid'] else None
    senderid = msgJson['id1'] if roomid else msgJson['wxid']

    # 获取发送者昵称
    nickname = get_member_nick(roomid, senderid) if roomid else 'null'
    applicant = nickname

    if apply_vpn_match:
        email = apply_vpn_match.group(1)
        purpose = apply_vpn_match.group(2)
        logging.info(f"申请VPN: Email={email}, Purpose={purpose}")

        uuid, vpn_account, vpn_password = apply_vpn_account(senderid, email, purpose)

        if uuid is None:
            logging.warning(f"Warning: Email {email} 申请过.")
            ws.send(send_msg(f"Warning: Email {email} 该邮箱已经申请过", roomid=roomid, wxid=senderid, nickname=nickname))
        else:
            # 处理正常情况
            ws.send(send_msg(f"申请成功: VPN 账号 {vpn_account}-激活任务{uuid}", roomid=roomid, wxid=senderid, nickname=nickname))
            groups = get_users_groups()
            if groups:
                combined_string = combine_id_name_to_string(groups)
                logging.info(f"Combined Group String: {combined_string}")
                ws.send(send_msg(f"\n|权限组明细,请管理员分配|\n\n|管理员激活指令 /激活vpn 任务id 权限id|\n{combined_string}", roomid=roomid, wxid=senderid, nickname=nickname))

    elif activate_vpn_match:
        if msgJson['id1']=="p****yuan" or msgJson['id1']=="wxid_y*****omg222" or msgJson['id1']=="ho******05232": ##这里设置管理员的wxid,只有有权限的wxid才可以做激活操作
            uuid = activate_vpn_match.group(1)
            group_id = activate_vpn_match.group(2)
            logging.info(f"激活VPN: UUID={uuid}, Group ID={group_id}")

            record_msg = activate_vpn_account(uuid, group_id,senderid)
            ws.send(send_msg(record_msg, roomid=roomid, wxid=senderid, nickname=nickname))
        else: 
            ws.send(send_msg("无权限", roomid=roomid, wxid=senderid, nickname=nickname))

    else:
        logging.warning("未识别的指令")

###################################################################################
def on_message(ws, message):
    j = json.loads(message)
    resp_type = j['type']

    if resp_type == CHATROOM_MEMBER_NICK:
        handle_nick(j)
    elif resp_type == PERSONAL_DETAIL:
        handle_recv_msg(j)
    elif resp_type == AT_MSG:
        handle_recv_msg(j)
    elif resp_type == DEBUG_SWITCH:
        handle_recv_msg(j)
    elif resp_type == PERSONAL_INFO:
        handle_recv_msg(j)
    elif resp_type == TXT_MSG:
        handle_recv_msg(j)
    elif resp_type == PIC_MSG:
        handle_recv_msg(j)
    elif resp_type == CHATROOM_MEMBER:
        hanle_memberlist(j)
    elif resp_type == RECV_PIC_MSG:
        handle_recv_msg(j)
    elif resp_type == RECV_TXT_MSG:
        handle_recv_msg(j)
    elif resp_type == RECV_TXT_CITE_MSG:
        handleMsg_cite(j)
    elif resp_type == HEART_BEAT:
        heartbeat(j)
    elif resp_type == USER_LIST:
        handle_wxuser_list(j)
    elif resp_type == GET_USER_LIST_SUCCSESS:
        handle_wxuser_list(j)
    elif resp_type == GET_USER_LIST_FAIL:
        handle_wxuser_list(j)
    elif resp_type == JOIN_ROOM:
        welcome_join(j)
    else:
        output(f"Unknown message type: {resp_type}")

while True:
    try:
        ws = websocket.WebSocketApp(SERVER, on_open=on_open, on_message=on_message, on_error=on_error, on_close=on_close)
        ws.run_forever()
    except Exception as e:
        output(f"WebSocket connection failed: {e}. Reconnecting in 5 seconds...")
        time.sleep(5)

wxbot 如果搭建请github找工具

hook监听到的内容

复制代码
Received message: {'content': '***', 'id': '20240826191727', 'id1': 'to***598', 'id2': '', 'id3': '', 'srvid': 1, 'time': '2024-08-26 19:17:27', 'type': 1, 'wxid': '715****56@chatroom'}

数据库字段表结构

复制代码
# Host: localhost  (Version: 5.7.26)
# Date: 2024-08-26 19:35:07
# Generator: MySQL-Front 5.3  (Build 4.234)

/*!40101 SET NAMES utf8 */;

#
# Structure for table "fa_vpn_fw_blj"
#

DROP TABLE IF EXISTS `fa_vpn_fw_blj`;
CREATE TABLE `fa_vpn_fw_blj` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `uuid` varchar(255) NOT NULL,
  `applicant_wxid` varchar(255) NOT NULL,
  `vpn_account` varchar(255) NOT NULL,
  `vpn_password` varchar(255) NOT NULL,
  `email` varchar(255) NOT NULL,
  `purpose` varchar(255) NOT NULL,
  `application_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `status` varchar(50) DEFAULT '待激活',
  `activator_wxid` varchar(255) DEFAULT NULL,
  `activation_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

结合fastadmin 管理后台

相关推荐
码农学院36 分钟前
基于运维监控体系的网络品牌推广方案:从架构设计到技术实现
运维·网络
REDcker8 小时前
显示分辨率标准对照详解
前端·网络·分辨率·显示·屏幕
ANSIOT10 小时前
安朔科技蓝牙无感开锁方案,靠近自动解锁
网络·科技·物联网
晏宁科技YaningAI10 小时前
VoIP系统的工程实现模型:从信令控制到媒体传输的完整架构解析
网络·人工智能·架构·系统架构·信息与通信
神奇霸王龙13 小时前
Claude Code屠榜:MiMo与Grok紧追Codex
服务器·网络·人工智能·gpt·ai·ai编程
数智化管理手记15 小时前
账龄分析手工统计易遗漏?自动账龄分析工具怎么搭建
大数据·网络·数据库·人工智能·数据挖掘
LabVIEW开发15 小时前
NI Package Manager安装LabVIEW时InternalServerError错误
网络·数据库·labview·labview知识·labview功能·labview程序
RD_daoyi16 小时前
外链权重暴跌至13%,品牌提及反超传统链接——2026年不做Digital PR,你的独立站等于隐形
运维·网络·学习·机器学习·搜索引擎
张小姐的猫16 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
国科安芯16 小时前
星间光链路:AS32S601型抗辐射MCU在空间激光通信终端控制中的技术实现
服务器·网络·单片机·嵌入式硬件·物联网·安全·信息与通信