使用java将个人微信打造成得力助手

news2024/11/17 1:41:43

本文提供一个通过java编程将微信打造成得力助手的方案, 先看效果:

查看支持的功能与对该功能开放的用户

接入人工智能

下载BiliBili 视频

原理

这个方案最难的地方就是如何把个人账号安全的接入微信,不会被封号。网上主流的有逆向web端微信,这种方式的好处是,可以在linux 服务器上部署,不依赖第三方工具, 但是这种很容易被发现,会出现警告,甚至封号处罚的风险。

本文提供的方案是, 搭建一个特殊的外挂服务,可以监听到微信消息的到达,以及操作微信发送各种消息。这特殊的服务开放了两个端口,一个端口可以通过 socket 编程,循环接收微信到达的消息,一个端口可以接收HTTP请求,用来获取好友信息,发送消息等等。

剩下就可以用自己熟悉的语言针对这两个端口操作了:接收到特定的消息,做特定的动作。

微信外接服务

这个是整个方案中最难的环节,但是,不用自己动手, github 已有现成的工具, 只需把它集成到我们自己的服务中即可: https://github.com/ljc545w/ComWeChatRobot

java 中可以通过 JNI 方式调用 c++ 编写 dll 库,示例如下:

在java中声明dll中的已有的方法

public interface WxDll extends Library {
    // 打开微信
    int new_wechat();
    // 开始监听微信消息
    int start_listen(int a, int b);
    // 停止监听
    int stop_listen(int a);
}

初始化外界服务

public static void init(int port) throws IOException {

    int pid = getPid("WeChat.exe");
    if(pid == 0) {
        System.out.println("微信没有运行");
        System.exit(0);
        return;
    }
    String injectExe = getAbPath("driver/CWeChatRobot.exe");
    // 卸载
    Runtime.getRuntime().exec(injectExe + " /unregserver");
    // 注入
    Runtime.getRuntime().exec(injectExe + " /regserver");

    String dll64 = getAbPath("driver/wxDriver64.dll");
    String dll32  =  getAbPath("driver/wxDriver.dll");
    // 根据系统选择注入程序的版本
    WxDll driver = (WxDll) Native.load(System.getProperty("os.arch").equals("amd64") ? dll64 : dll32, WxDll.class);
    // 开启服务,接收HTTP请求
    driver.start_listen(pid, port);
}

监听微信消息

/**
 * 开放 socket,
 * @param port 端口
 */
public static void openSocket (int port) {
    Map<String, Object> params = new HashMap<>();
    params.put("port", port);
    // 发送HTTP请求,打开 socket 接口,可以用来接收消息
    BaseResult result = WebTool.post(baseUrl + ":" + Config.instance().HTTP_PORT + "/api/?type=9",params,BaseResult.class);
    assert result != null;
    if(!result.isOk()){
        throw new RuntimeException("打开socket失败");
    }
}

public static void startListenMsg() throws IOException {
    // 创建一个线程池,异步处理接收到消息
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 8,
            10L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>());

    ServerSocket server = new ServerSocket(Config.instance().SOCKET_PORT);
    while (true) {
        // 循环监听
        Socket socket = server.accept();
        //  ReceiveMsgContext 处理接收到的消息
        threadPoolExecutor.submit(new ReceiveMsgContext(socket));
    }
}

以上就是该方案的核心了,剩下的处理消息的代码,用户完全可以自定义开发了。下面介绍我编写的一种实现方案:

项目结构

入口

public class Main{

    public static void main(String[] args) throws IOException {
        // 初始化配置
        Config config = Config.loadConfig();
        // 初始化外挂
        WxTool.init(config.HTTP_PORT);
        // 开启消息监听, 即监听 socket port 端口
        WxTool.openSocket(config.SOCKET_PORT);
        // 允许从控制台输入文本,发送消息 这是根据我自己的需求,可以在控制台回复微信消息
        WxTool.receiveMsgFromConsole();

        // TODO 自动扫描, 希望以后实现包的自动扫描功能,降低代码的耦合,完全实现 OCP原则, 现在先手动注入各种消息处理器
        // 注册消息处理器
        // 简单消息处理器,近记录某些好友的消息,并显示在控制台上
        WxTool.regist(new SampleMsgHandler());
        // 帮助文档处理器,可以查看各种处理器的功能,以及使用方法,和该功能开放的用户
        WxTool.regist(new BotMsgHelperHandler());
        // 人工智能, 大家各显神通吧,我刚开始有可用的 GPT Token, 现在无了,用其他方式曲线实现的,以后再说
        WxTool.regist(new AiHandler());

        // 开始监听微信消息
        WxTool.startListenMsg();
    }
}

配置文件定义与解析

定义

# HTTP 端口
HTTP_PORT: 8000
# SOCKET 端口
SOCKET_PORT: 10808
# 基础配置
base: &base
  # 白名单用户, 直接输入好友的备注即可,支持群聊
  white_user:
    - "室友群"
    - "同学群"
    - "文件传输助手"
# AI 消息处理器的配置
AI:
  # 继承base, 只允许白名单用户使用该功能
  <<: *base

# 帮助文档处理器的配置
botHelp:
  <<: *base # 也是继承了base

# 消息记录器
msgLogger:
  white_user: # 设置这个功能对哪些用户开放
    - "室友群"
    - "同学群"
    - "张三"
    - "李四"

解析

package io.dc.util;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.dc.config.monit.FileListener;
import io.dc.config.monit.FileMonitor;
import io.dc.model.Emo;
import org.tinylog.Logger;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;

public class Config {


    // 整个配置都会被加载到 configMap 中
    public HashMap configMap = new HashMap<>();

    // 基础配置
    public  int HTTP_PORT = 8000;
    public  int SOCKET_PORT = 10808;

    private static Config config;

    // 配置应该是单例的
    public static synchronized Config instance() {
        if (config == null) {
            config = new Config();
        }
        return config;
    }

    /**
     * 程序启动时,或者配置文件发生变化时,加载配置文件
     */
    public static Config reload() throws  IOException{
        config = Config.instance();
        LoaderOptions options = new LoaderOptions();
        options.setAllowDuplicateKeys(true);
        Yaml yaml = new Yaml(options);
        // 配置文件的绝对路径
        String yamlPath = WxTool.getAbPath("./config.yml");
        InputStream inputStream = Files.newInputStream(Paths.get(yamlPath));
        config.configMap = yaml.loadAs(inputStream, HashMap.class);

        // 获取基础配置
        config.HTTP_PORT = (int)config.configMap.get("HTTP_PORT");
        config.SOCKET_PORT = (int)config.configMap.get("SOCKET_PORT");

        return config;

    }


    /**
     * 程序启动时,加载配置
     */
    public  static Config loadConfig() throws IOException {
        Config config = reload();
        // 开始监听配置文件,实现热加载
        config.monit();
        return config;
    }


    @Override
    public String toString() {
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            return objectMapper.writeValueAsString(this);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    public void monit() {
        // 监控配置文件是否修改
        new Thread(new Runnable() {
            @Override
            public void run() {
                FileMonitor fileMonitor = new FileMonitor(1000);
                try {
                    fileMonitor.monitor(WxTool.getAbPath("config.yml"), new FileListener(config));
                    fileMonitor.start();
                } catch (Exception e) {
                    Logger.error("配置文件加载失败,请检查! :{}", e.getMessage());
                }
            }
        }).start();
    }
}

消息处理器

注册

首先在 WxTool 工具类中,有存放消息处理器的容器,注册的动作就是把消息处理器添加到该容器中,并执行一些后续的动作

public class WxTool {

    // 消息处理器容器
    public static List<WxReceiveMsgHandler> msgHandlerList = new ArrayList<>();
    // 省略
    
    // 注册
    public static void regist(WxReceiveMsgHandler wxMsgHandler) {
        // 先加载该消息处理器的配置信息
        wxMsgHandler.loadConfig(Config.instance());
        // 将处理器添加到集合中
        msgHandlerList.add(wxMsgHandler);
    }
}

消息处理器的定义

package io.dc.msg.receiver;

import io.dc.model.Msg;
import io.dc.util.Config;

public interface WxReceiveMsgHandler {
    /**
     * 是否选中该处理器去处理消息
     */
    boolean select(Msg msg);

    /**
     * 处理消息
     */
    void deal(Msg msg);

    /**
     * 处理器的帮助文档,当用户输入 help 时,会显示该文档
     */
    String printHelper(Boolean isItMe);

    /**
     * 配置
     * config.configMap.get("xxxxx")
     * @param config 总配置信息
     */
    void loadConfig(Config config);

    // 该消息处理器于容器中的位置
    int order();
}

消息处理器的选择

package io.dc.msg.receiver;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.dc.model.Msg;
import io.dc.util.WxTool;
import org.tinylog.Logger;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

public class ReceiveMsgContext implements Runnable {
    private Socket socket;

    public ReceiveMsgContext(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
            ObjectMapper objectMapper = new ObjectMapper();
            String line = br.readLine();
            // 将收到的文本消息转为 Msg 实体
            Msg msg = objectMapper.readValue(line, Msg.class);
            // 遍历所有的消息处理器
            for (WxReceiveMsgHandler wxMsgHandler : WxTool.msgHandlerList) {
                //  找到可以处理当前消息的处理器
                if(wxMsgHandler.select(msg)){
                    // 开始处理消息
                    wxMsgHandler.deal(msg);
                }
            }
        } catch (IOException e) {
            Logger.error("",e);
            throw new RuntimeException(e);
        }
    }
}

处理器的实现

所以自定义的消息处理器,只需实现该接口即可,以 简单消息记录 为例:

package io.dc.msg.receiver;

import io.dc.model.Msg;
import io.dc.model.User;
import io.dc.util.Color;
import io.dc.util.Config;
import io.dc.util.WxTool;
import org.tinylog.Logger;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class SampleMsgHandler implements WxReceiveMsgHandler {

    static List<String> WHITE_USER = new ArrayList<>();

    @Override
    public boolean select(Msg msg) {
        // 自己发的消息不记录
        if(msg.getIsSendMsg()){
            return false;
        }

        // 根据用户ID 找到用户
        User user = WxTool.findUserById(msg.getRoomOrWxId());
        boolean next = false;
        // 只处理白名单用户
        for (String c_u : WHITE_USER) {
            if(user != null && user.getName().equals(c_u)){
                next = true;
                break;
            }
        }
        return next;
    }

    @Override
    public void deal(Msg msg) {
        User user = WxTool.findUserById(msg.getWxid());
        // 如果是群聊
        if(msg.isChatRoom()){
            User roomUser = WxTool.findUserById(msg.getRoom());
            Logger.info("{}:{}: {}",Color.none(roomUser.getName()),Color.none(user.getName()), Color.none(msg.getMessage()));
        }else{
            // 单聊
            Logger.info("{}: {}", Color.none(user.getName()), Color.none(msg.getMessage()));
        }
    }

    @Override
    public String printHelper(Boolean isItMe) {
        StringBuilder whiteUserInfo = new StringBuilder();
        if(isItMe){
            whiteUserInfo.append("\n支持的用户: ");
            for (String u : WHITE_USER) {
                whiteUserInfo.append(u).append(" ");
            }
        }

        return "指令: 无\n功能: 记录收到的消息" + whiteUserInfo;
    }

    /**
     * 获取输入本处理器的配置
     */
    @Override
    public void loadConfig(Config config) {
        Map<String, Object> botConf = (Map<String, Object>) config.configMap.get("msgLogger");
        if(botConf != null){
            // 获取白名单
            WHITE_USER = (List<String>) botConf.get("white_user");
        }
    }

    @Override
    public int order() {
        return 1;
    }
}

微信SDK

package io.dc.util;

import com.sun.jna.Native;
import io.dc.WxDll;
import io.dc.exception.NotFoundUserException;
import io.dc.model.BaseResult;
import io.dc.model.User;
import io.dc.model.UserListResponse;
import io.dc.msg.receiver.ReceiveMsgContext;
import io.dc.msg.sender.SampleSendMsg;
import io.dc.msg.receiver.WxReceiveMsgHandler;
import org.tinylog.Logger;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class WxTool {

    final static String baseUrl = "http://127.0.0.1";
    public static List<WxReceiveMsgHandler> msgHandlerList = new ArrayList<>();
    // 好友列表
    private static List<User> friendList = new ArrayList<>();

    // 微信用户ID与用户对应关系,便于快速查找
    private static Map<String, User> wxIdUserMap = new HashMap<>();


    /**
     * 开放 socket,
     * @param port 端口
     */
    public static void openSocket (int port) {
        Map<String, Object> params = new HashMap<>();
        params.put("port", port);
        BaseResult result = WebTool.post(baseUrl + ":" + Config.instance().HTTP_PORT + "/api/?type=9",params,BaseResult.class);
        assert result != null;
        if(!result.isOk()){
            throw new RuntimeException("打开socket失败");
        }
    }


    public static void startListenMsg() throws IOException {
        msgHandlerList.sort(new Comparator<WxReceiveMsgHandler>() {
            @Override
            public int compare(WxReceiveMsgHandler o1, WxReceiveMsgHandler o2) {
                return o1.order() - o2.order();
            }
        });


        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 8,
                10L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());

        ServerSocket server = new ServerSocket(Config.instance().SOCKET_PORT);
        while (true) {
            Socket socket = server.accept();
            threadPoolExecutor.submit(new ReceiveMsgContext(socket));
        }
    }


    public static void regist(WxReceiveMsgHandler wxMsgHandler) {
        wxMsgHandler.loadConfig(Config.instance());
        msgHandlerList.add(wxMsgHandler);
    }


    // 获取在资源的绝对路径
    public static String getAbPath(String path) throws IOException {
        File directory = new File("");
        String author =directory.getAbsolutePath();
        return String.join(File.separator, author, path);
    }

    // 获取微信进程号
    private static int getPid(String appName) throws IOException {
        String line;
        int pid = 0;
        Process p = Runtime.getRuntime().exec(System.getenv("windir") +"\\system32\\"+"tasklist.exe");
        BufferedReader input =
                new BufferedReader(new InputStreamReader(p.getInputStream()));
        while ((line = input.readLine()) != null) {

            if(line.contains(appName)) {
                pid = Integer.parseInt(line.split("\\s+")[1]);
                break;
            }
        }
        input.close();
        return pid;
    }

    
    public static void init(int port) throws IOException {

        int pid = getPid("WeChat.exe");
        if(pid == 0) {
            System.out.println("微信没有运行");
            System.exit(0);
            return;
        }
        String injectExe = getAbPath("driver/CWeChatRobot.exe");
        // 卸载
        Runtime.getRuntime().exec(injectExe + " /unregserver");
        // 注入
        Runtime.getRuntime().exec(injectExe + " /regserver");

        String dll64 = getAbPath("driver/wxDriver64.dll");
        String dll32  =  getAbPath("driver/wxDriver.dll");
        WxDll driver = (WxDll) Native.load(System.getProperty("os.arch").equals("amd64") ? dll64 : dll32, WxDll.class);
        driver.start_listen(pid, port);
    }


    // 获取所有的微信好友
    public static List<User> getFriends() {
        UserListResponse result = WebTool.post(baseUrl + ":" + Config.instance().HTTP_PORT + "/api/?type=15",null, UserListResponse.class);
        return result.getData();
    }

   // 发消息
    public static BaseResult sendMsgByWxId(String wxId, String msg) {
        Map<String, Object> params = new HashMap<>();
        params.put("wxid",wxId);
        params.put("msg",msg);
        return WebTool.post(baseUrl + ":" + Config.instance().HTTP_PORT + "/api/?type=2",params, BaseResult.class);
    }

    public static BaseResult sendMsgByName(String name, String msg) {
        return sendMsgByWxId(findUserByName(name).getWxid(), msg);
    }

    public static User findUserByName(String name) {
        if(friendList.isEmpty()){
            friendList = getFriends();
        }
        for (User user : friendList) {
            if(user.getWxRemark().equals(name)){
                return user;
            }
        }
        for (User user : friendList) {
            if(user.getWxNickName().equals(name)){
                return user;
            }
        }
        for (User user : friendList) {
            if(user.getWxid().equals(name)){
                return user;
            }
        }

        throw new NotFoundUserException();
    }


    public static User findUserById(String wxid) {
        if(wxIdUserMap.isEmpty()){
            if(friendList.isEmpty()){
                friendList = getFriends();
            }

            for (User user : friendList) {
                wxIdUserMap.put(user.getWxid(), user);
            }
        }
        return  wxIdUserMap.get(wxid);
    }


}

WxTool 指用到两个api, 获取微信好友和发送简单的文本消息。微信外接服务还提供了很多接口,需要开发者自己查询原始仓库的文档。以及代码用到的 CWeChatRobot.exewxDriver64.dll 注入工具,需要开发者下载到项目 driver 目录下, 不再详述 https://github.com/ljc545w/ComWeChatRobot

整体逻辑和项目核心结构就是上述这样,项目完整代码暂时还未开源,等我再完善一下 😃

联系方式: dccmmtop@foxmail.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/810225.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

KY258 日期累加

一、题目 二、代码 #include <iostream> using namespace std; class Date {public:Date(int year 0, int month 0, int day 0) {_year year;_month month;_day day;}Date(const Date& _d);int GetDay(int year, int month);Date& operator(int d);Date o…

this is incompatible with sql_mode=only_full_group_by

查看配置 select global.sql_mode 在sql命令行中输入select sql_mode 能够看到sql_mode配置,如果有ONLY_FULL_GROUP_BY&#xff0c;则需要修改 在mysql5.7.5后&#xff0c;ONLY_FULL_GROUP_BY是默认选项&#xff0c;所以就会导致group by的问题 set sql_mode‘复制去掉ONLY_F…

Python(五十)获取列表中指定的元素

❤️ 专栏简介&#xff1a;本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中&#xff0c;我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 &#xff1a;本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…

嵌入式linux之OLED显示屏SPI驱动实现(SH1106,ssd1306)

周日业余时间太无聊&#xff0c;又不喜欢玩游戏&#xff0c;大家的兴趣爱好都是啥&#xff1f;我觉得敲代码也是一种兴趣爱好。正巧手边有一块儿0.96寸的OLED显示屏&#xff0c;一直在吃灰&#xff0c;何不把玩一把&#xff1f;于是说干就干&#xff0c;最后在我的imax6ul的lin…

BUU [BJDCTF2020]The mystery of ip

BUU [BJDCTF2020]The mystery of ip 再hint的源码里面找到这个东西。 这题一定和IP有关系&#xff0c;试了一下伪造IP还真是。 分析一下&#xff0c;这题可能存在SSTI漏洞&#xff0c;先用模板算式子{{9*‘9’}}测一下 那SSTI稳了&#xff0c;应该是Twig模板。 但是报错测出来是…

Android 面试题 线程间通信 六

&#x1f525; 主线程向子线程发送消息 Threadhandler&#x1f525; 子线程中定义Handler&#xff0c;Handler定义在哪个线程中&#xff0c;就跟那个线程绑定&#xff0c;在线程中绑定Handler需要调用Looper.prepare(); 方法&#xff0c;主线程中不调用是因为主线程默认帮你调用…

编写脚本,使用mysqldump实现分库分表备份。

一、实现分库备份&#xff1a; #!/bin/bash #分库备份 bak_userroot-----------备份用户 bak_password513721ykp--------备份密码 bak_path/backup/db_bak---------备份路径 bak_cmd"-u$bak_user -p$bak_password"-------登录命令&#xff0c;以便后面重复编写 exc_…

分布式锁漫谈

简单解释一下个人理解的分布式锁以及主要的实现手段。 文章目录 什么是分布式锁常用分布式锁实现 什么是分布式锁 以java应用举例&#xff0c;如果是单应用的情况下&#xff0c;我们通常使用synchronized或者lock进行线程锁&#xff0c;主要为了解决多线程或者高并发场景下的共…

3ds MAX绘制摄像机动画

之前&#xff0c;我们已经绘制了山地、山间小路、以及树林&#xff1a; 这里我们添加一个自由摄像机&#xff1a;&#xff08;前视图&#xff09; 在动作窗口&#xff0c;给摄像机添加一个按路径移动的设定&#xff1a; 这样&#xff0c;我们只要把指定的路径绘制出来&#xff…

UE4/5C++多线程插件制作(0.简介)

目录 插件介绍 插件效果 插件使用 English 插件介绍 该插件制作&#xff0c;将从零开始&#xff0c;由一个空白插件一点点的制作&#xff0c;从写一个效果到封装&#xff0c;层层封装插件&#xff0c;简单粗暴的对插件进行了制作&#xff1a; 插件效果 更多的是在cpp中去…

Cpp04 — 默认成员函数

前言&#xff1a;本文章主要用于个人复习&#xff0c;追求简洁&#xff0c;感谢大家的参考、交流和搬运&#xff0c;后续可能会继续修改和完善。 因为是个人复习&#xff0c;会有部分压缩和省略。 一、默认成员函数 当类里面成员函数什么都不写的时候&#xff0c;编译器会自动…

AutoSAR系列讲解(实践篇)10.3-BswM配置

目录 一、ECU State Handing(ESH) 二、Module Initialization 三、Communication Control 说起BswM的配置,其实博主问过很多朋友了,大家基本都只用自动配置;很少有用到手动配置的时候,对于刚刚入门的大家来说,掌握自动配置基 本也就足够了。 一、ECU State Handing(…

【雕爷学编程】MicroPython动手做(12)——掌控板之Hello World

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

内网隧道代理技术(十四)之 Earthworm的使用(一级代理)

Earthworm的使用(一级代理) ew 全称是EarchWorm,是一套轻量便携且功能强大的网络穿透工具,基于标准C开发,具有socks5代理、端口转发和端口映射三大功能,可在复杂网络环境下完成网络穿透,且支持全平台(Windows/Linux/Mac)。该工具能够以“正向”、“反向”、“多级级联”…

谷粒商城第七天-商品服务之分类管理下的删除、新增以及修改商品分类

目录 一、总述 1.1 前端思路 1.2 后端思路 二、前端部分 2.1 删除功能 2.2 新增功能 2.3 修改功能 三、后端部分 3.1 删除接口 3.2 新增接口 3.3 修改接口 四、总结 一、总述 1.1 前端思路 删除和新增以及修改的前端无非就是点击按钮&#xff0c;就向后端发送请求…

动脑学院Jetpack Compose学习笔记

最近b站学习了一下Compose相关内容&#xff0c;整理了相关笔记&#xff0c;仅供大家参考。 资源链接如下&#xff0c;象征性收取1个积分 https://download.csdn.net/download/juliantem/88125198

C数据结构——无向图(邻接表方式) 创建与基本使用

源码注释 // // Created by Lenovo on 2022-05-17-下午 4:37. // 作者&#xff1a;小象 // 版本&#xff1a;1.0 //#include <stdio.h> #include <malloc.h>#define TRUE 1 #define FALSE 0#define MAX_ALVNUMS 100 // 最大顶点数/** 定义链队*/ typedef int QEle…

服务器部署Go项目

最近在研究服务器部署项目&#xff0c;用了好几种办法成功部署。这些方法互有利弊&#xff0c;本文就逐一详细演示说明&#xff1a; 目录 1.服务器下载Go环境&#xff0c;直接将项目代码放到服务器上运行 2.服务器不下载Go环境&#xff0c;本地将项目打包成可执行的二进制…

【小程序】快来开发你的第一个微信小游戏(详细流程)

&#x1f973; 作者&#xff1a;伯子南 &#x1f60e; 坚信&#xff1a; 好记性不如乱笔头&#xff0c;独乐乐不如众乐乐 &#x1f4aa; 个人主页&#xff1a;https://blog.csdn.net/qq_34577234?spm1010.2135.3001.5421 &#x1f46c;&#x1f3fb; 觉得博主文章不错的话&…

java基础String类的特性

文章目录 1 String的特性2 String的内存结构1&#xff1a;拼接相关2&#xff1a;new相关3&#xff1a;intern() 1 String的特性 java.lang.String 类代表字符串。Java程序中所有的字符串文字&#xff08;例如"hello" &#xff09;都可以看作是实现此类的实例。 字符…