批量备份当前docker所有容器

还得是chatgpt

#!/bin/bash

# 获取所有正在运行的容器ID
container_ids=$(sudo docker ps -q)

# 循环遍历容器ID并执行备份
for container_id in $container_ids; do
    # 获取容器名称
    container_name=$(sudo docker inspect --format='{{.Name}}' $container_id)

    # 去掉容器名称中的斜杠字符
    container_name=${container_name:1}

    # 指定备份文件名(可根据需求自行更改)
    backup_file="${container_name}_backup"

    # 备份容器
    sudo docker commit $container_id $backup_file

    # 保存容器
    sudo docker save $backup_file -o "${backup_file}.tar"
    echo "Container $container_name backed up as $backup_file"
done

注意映射后的文件需要单独备份

nginx用域名http://xx.com/aaa/代理一个网页http://ff.com但是请求资源时发生404

哎,还得是chatgpt,难道就没有人有这种使用场景吗?没查到一个配置是有效的。

  • 我:

    我配置了nginx反向代理,用域名http://xx.com/aaa/代理一个网页http://ff.com,
    但是请求资源时发生404,如何解决,我能够正确获取到dom

  • gpt-4-1106-preview:

    如果你已经成功配置了 Nginx 以使用 http://xx.com/aaa/ 代理到 http://ff.com,但在访问资源时遇到了 404 错误,可能的原因之一是资源的相对路径问题。当你使用子路径代理到另一个网站时,目标网站页面中的资源可能会因为路径不匹配而加载失败。

    例如,如果目标网站(http://ff.com)中有一张图片,其路径为 /images/pic.jpg,则该请求将在代理服务器上转换为 http://xx.com/aaa/images/pic.jpg。但是,如果 Nginx 配置中没有正确地重写资源的路径,资源请求会被发向错误的 URL,导致 404 错误。

    为了解决这个问题,你需要确保 Nginx 的配置能够正确处理路径重写。下面是一个基本的例子,展示如何为 Nginx 设置代理并重写 URL 路径:

    server {
        listen 80;
        server_name xx.com;
    
        location /aaa/ {
            proxy_pass http://ff.com/; # 确保末尾有斜杠
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
    
            # 重写路径:移除 aaa
            rewrite ^/aaa/(.*) /$1 break;
    
            # 如果需要,处理 HTML 内嵌资源的路径问题
            sub_filter 'src="/' 'src="/aaa/';
            sub_filter 'href="/' 'href="/aaa/';
            sub_filter_once off;
       }
    }

    在这个配置中,rewrite 指令用于从传入的请求 URL 中删除 /aaa/ 子路径,保持后端请求的正确性。sub_filter 指令用于处理返回的 HTML 内容,将资源的路径前缀修改为 /aaa/,使它们在反向代理环境中有效。请注意,这可能需要 Nginx 的 ngx_http_sub_module 模块支持。

    确保重写规则和替换操作符合你的实际需求,它们可能需要根据实际网站的结构进行调整。

    最后,不要忘记重新加载或重新启动 Nginx 以应用更新后的配置:

    sudo nginx -s reload
    # 或者
    sudo systemctl reload nginx

    一定要仔细检查网站的 HTML 和相关资源请求,确保所有的资源路径都能正确反映反向代理的结构。

    剩余的问题

    • 对于*.svg文件请求依然是404,不看图又不会死,直接不解决。
    • 对于api请求是404,哎懒得折腾了,直接把api请求单独重定向到一个同名路由成功访问,出现冲突了再说吧。
      像这样:

         location /api/ {
              proxy_pass http://127.0.0.1:8085/api/;
          }

【azure笔记 1】容器实例管理python sdk封装

容器实例管理python sdk封装

说明

  • 这是根据我的需求写的,所以有些参数是写死的,比如cpu核数和内存,你可以根据你的需要自行修改。
  • 前置条件:
    当前环境已安装python3.8以上版本和azure cli并且已经登陆到你的账户

    依赖安装

    requirments.txt

    azure-mgmt-resource
    azure-identity
    azure-mgmt-containerinstance
pip install -r requirments.txt

PS: 安装不上就多安几次或使用魔法

containerhelpor.py

from azure.identity import DefaultAzureCredential
from azure.mgmt.containerinstance import ContainerInstanceManagementClient
from azure.mgmt.containerinstance.models import ContainerGroup, Container, ContainerPort, IpAddress, ResourceRequests, ResourceRequirements

class ContainerHelpor:

    def create(subscription_id:str, resource_group:str, location:str, container_name:str, container_image:str, no_port:int, protocol: str='TCP'):

        # 使用默认 Azure 凭据进行验证
        credentials = DefaultAzureCredential()

        # 创建容器实例管理客户端实例
        container_client = ContainerInstanceManagementClient(credentials, subscription_id)

        # 容器端口和 IP 地址配置
        port1 = ContainerPort(port=no_port, protocol=protocol)
        ports = [port1]
        # port2 = ContainerPort(port=no_port + 1, protocol=protocol)
        # ports = [port1, port2]
        ip_address = IpAddress(ports=ports, type='Public')

        # 容器资源请求
        requests = ResourceRequests(memory_in_gb=1.0, cpu=1.0)
        requirements = ResourceRequirements(requests=requests)

        # 创建容器
        container = Container(
            name=container_name,
            image=container_image,
            resources=requirements,
            ports=ports
        )

        # 容器组(容器实例)
        container_group = ContainerGroup(
            location=location,
            containers=[container],
            os_type='Linux',
            ip_address=ip_address
        )

        # 创建容器实例
        container_group_result = container_client.container_groups.begin_create_or_update(resource_group, container_name, container_group)
        print(f"Container instance {container_name} created successfully.")
        return container_group_result

    def stop(subscription_id:str, resource_group:str, container_name:str):

        # 使用默认凭证进行身份验证
        credential = DefaultAzureCredential()

        # 创建容器实例管理客户端
        client = ContainerInstanceManagementClient(credential, subscription_id)

        # 停止容器实例
        client.container_groups.stop(resource_group, container_name)

        print(f"Container instance {container_name} stoped successfully.")

    def start(subscription_id:str, resource_group:str, container_name:str):

        # 使用默认凭证进行身份验证
        credential = DefaultAzureCredential()

        # 创建容器实例管理客户端
        client = ContainerInstanceManagementClient(credential, subscription_id)

        # 停止容器实例
        client.container_groups.begin_start(resource_group, container_name)

        print(f"Container instance {container_name} started successfully.")

    def remove(subscription_id:str, resource_group:str, container_name:str):
        # 创建 Azure 认证凭证
        credentials = DefaultAzureCredential()

        # 创建 ContainerInstanceManagementClient
        container_client = ContainerInstanceManagementClient(credentials, subscription_id)

        # 删除容器实例
        container_client.container_groups.begin_delete(resource_group, container_name)
        print(f"Container instance {container_name} deleted successfully.")

    def query_ip(subscription_id:str, resource_group:str, container_name:str):
        credential = DefaultAzureCredential()
        client = ContainerInstanceManagementClient(credential, subscription_id)

        container_group = client.container_groups.get(resource_group, container_name)

        return  container_group.ip_address

测试代码

import sys
import os
import time

# 此处根据你的项目选择包含目录以及导入导出模块
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
sys.path.append(parent_dir)
from core.containerhelpor import ContainerHelpor

def main():
    print("containerhelpor test begin")
    # 这里填你的订阅id
    sub_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    container_name = 'test' # 你的容器名称
    resource_group = 'jp' # 你的资源组组名
    print("ContainerHelpor.create begin")
    ContainerHelpor.create(subscription_id=sub_id,
    no_port=8080,
    resource_group=resource_group,
    location = 'Japan West',
    container_name = container_name,
    # 镜像名称以及tag默认指向dockerhub的latset
    container_image = '' 
    )
    print("ContainerHelpor.stop begin")
    ContainerHelpor.stop(sub_id,resource_group,container_name)

    print("ContainerHelpor.start begin")
    is_not_start = True
    retry_cnt = 0

    while is_not_start:
        try:
            ContainerHelpor.start(sub_id,resource_group,container_name)
            is_not_start = False
        except Exception as e: 
            time.sleep(2)
            retry_cnt += 1
            if retry_cnt > 3:
                print(e)
                ContainerHelpor.remove(sub_id,resource_group,container_name)
                print("containerhelpor test failed")
                return

    print("ContainerHelpor.query_ip begin")
    retry_cnt = 0
    ip = None
    while ip is None:
        ipaddr = ContainerHelpor.query_ip(sub_id,resource_group,container_name)
        ip = ipaddr.ip
        time.sleep(2)
        retry_cnt += 1
        if retry_cnt > 10:
            print("query_ip failed, try it later")
            break
    print(ip)
    print("ContainerHelpor.remove begin")
    ContainerHelpor.remove(sub_id,resource_group,container_name)

    print("containerhelpor test pass")
if __name__ == "__main__":
    main()

【CPP】【函数装饰器宏】cpp11利用宏、模板以及lambda表达式实现python like的装饰器语义

【CPP】【函数装饰器宏】cpp11利用宏、模板以及lambda表达式实现python like的装饰器语义

前言

python函数的装饰器是一个轻量级的函数功能拓展和复用工具,相比传统的派生重写虚函数的方式来说,避免了继承带来的耦合负担,控制也相对更加的简单,但cpp并没有相关的原生关键字支持,我参考了一些知乎和CSDN上的文章,但始终没有达到我想要的效果于是准备自己也动动手。
cpp11的lambda表达式提供了最基本的特性,基于此特性,我实现了类似语义的宏,跟python的函数装饰器相比创建形式并不相同,但是功能是相似的。
我事先在CSDN上发了一版,这一版是更新优化后的版本,装饰语法更加简洁,并且提供了cpp11兼容void返回值的解决方案。

用法

style1

int CCar::oilfeed(int degress)
DECORATE(logging,,
DECORATE(novoid,ARGS(),//对于装饰器的使用来说,如果没有参数,那么需要一个ARGS()占位或留空,留空看上去就是两个连续的','
DECORATE(maybe_void,,
//使用UNDECORATE宏可以单点禁用一个装饰器,原理是去掉了该层的 [&](){}包装器并悬空checkRotateSpeed以及ARGS里面的东西只保留代码块
UNDECORATE(checkRotateSpeed, ARGS(this, __FUNCTION__), //传递多个参数
{
    m_n_rotat_speed += degress;
    return m_n_rotat_speed;
})))) //用了多少个装饰器后面就要多少个),没办法cpp只能这样了,想不出更好的办法,本质是宏嵌套

style2

void CCar::brake(int degress)
DECORATE(logging, ARGS(__FUNCTION__),
//DECORATE(novoid,ARGS(), 使用novoid装饰器无法直接通过编译,因为不支持 void 作为返回值的偏特化
DECORATE(maybe_void,,
//推荐用仿函数的方式实现装饰器,通过构造函数传参封装性更好,如果舍弃函数类型的装饰器的话,就可以少一个宏参数占位了不用打奇怪的两个逗号了,
DECORATE(CheckRotateSpeed(this, __FUNCTION__),,
{
    m_n_rotat_speed -= degress;
})))

msvc2015 x64执行结果

完整测试代码

decorator.h

#ifndef DECORATOR_H
#define DECORATOR_H
#include <functional>

template<bool T>
//!
//! \brief The bool_trait struct
//! \usecase bool_trait<静态表达式->bool> 用于模拟模板偏特化
//!
struct bool_trait{};

template <typename T>
class func_caller final
{
public:
    using return_type = typename std::result_of<T&()>::type;
    using void_trait = bool_trait<std::is_same_v<void, return_type>>;

    explicit func_caller(T const &func)
        : stack(nullptr)
    {
        init(void_trait());
        call(func, void_trait());
    }

    ~func_caller()
    {
        release(void_trait());
    }

    return_type ret() const
    {
        return ret(void_trait());
    }

private:

    void* operator new(std::size_t) = delete;

    func_caller(func_caller const &) = delete;

    func_caller& operator=(func_caller const &) = delete;

    void init(bool_trait<true> const&)
    {
    }

    void init(bool_trait<false> const&)
    {
        stack = new return_type;
    }

    void release(bool_trait<true> const&)
    {

    }

    void release(bool_trait<false> const&)
    {
         delete stack;
    }

    void call(T &func, bool_trait<true> const&)
    {
        func();
    }

    void call(T const &func, bool_trait<false> const&)
    {
        *stack = std::move(func());//尽量调用移动构造函数
    }

    void ret(bool_trait<true> const&) const
    {

    }

    return_type ret(bool_trait<false> const&) const
    {
        return *stack;
    }

    return_type* stack;
};

//打包参数的宏,仿函数装饰器可以不使用
#define ARGS(...)\
    ,__VA_ARGS__

//启用装饰器语义宏定义
#ifndef NO_DECORATOR

//装饰宏,其实就是用 [&](){}把代码块包起来
#define DECORATE(decorator, args, codeblock) \
{\
    return decorator( [&]() codeblock args);\
    }
//单独禁用某个装饰器
#define UNDECORATE(decorator, args, codeblock) \
    codeblock
#else

//禁用所有装饰器
#define DECORATE(decorator, args, codeblock) \
    codeblock
#define UNDECORATE(decorator, args, codeblock) \
    codeblock
#endif // NO_DECORATOR

//隐藏模板语义,定义一个装饰器模板函数
#define DEF_DECIRATOR(name, .../*args*/) \
    template <typename T>\
    auto name(T const &func, __VA_ARGS__)

//实现临时快速禁用一个装饰器模板函数的功能,修改原装饰器为直接回调,然后将原本的逻辑添加到一个起始参数为void*的同名函数里面
#define DIS_DECIRATOR(name, .../*args*/) \
    template <typename T>\
    auto name(T const &func, __VA_ARGS__) \
    { return func(); }\
    template <typename T>\
    auto name(void *, T const &func, __VA_ARGS__)

//简化func_caller的使用方法
#define CALL() \
    func_caller<decltype(func)>(func)

#endif // DECORATOR_H

rm_decorator.h

#ifndef RM_DECORATOR_H
#define RM_DECORATOR_H

//禁用所有装饰器相关宏
#undef ARGS
#undef DECORATE
#undef UNDECORATE
#undef CALL
#undef DEF_DECIRATOR
#undef DIS_DECIRATOR
#endif // RM_DECORATOR_H

main.cpp

#include <functional>
#include <iostream>
#include <chrono>
#include <string>
//#define NO_DECORATOR
#include "base/decorator.h"
class CCar{
public:
    explicit CCar(){

    }

    int oilfeed(int degress);

    void brake(int degress);
    int rotat_speed() const {return m_n_rotat_speed;}
private:
    int m_n_rotat_speed = 2000;
};

/**
 * 注:此装饰器原理实为创建引用捕获的lambda表达式并调用,所以性能肯定会有所下降,但是增加了可读性,减少了维护难度
**/

//用原汁原味的写法定义装饰器函数,但是不能用过宏快速禁用,因为快速禁用实际是用重载实现的,所以只能增加代码分支进行控制如下面的写法
template <typename T>
auto checkRotateSpeed(T const &func/*第一个参数必须为函数*/, CCar const *obj, std::string const &funcname)
{
#if 1
    std::cout << "checkRotateSpeed before " << funcname  << ", rotat_speed:" << obj->rotat_speed()  << std::endl;
    auto const &caller = func_caller<decltype(func)>(func);//用func_caller模板来处理返回值为void的情况,缺点是非void返回值会多一些内存拷贝,所以条件允许的话对于大对象尽量使用智能指针作为返回值
    std::cout << "checkRotateSpeed after "  << funcname  << ", rotat_speed:" << obj->rotat_speed() << std::endl;
    return caller.ret();
#else
    return func();
#endif
}

//如果不用考虑返回值为 void,装饰器函数直接这么写就可以了,即简单性能又好
template <typename T>
auto novoid(T const &func)
{
    std::cout << "novoid before " << std::endl;
    auto const &ret = func();//直接调用被包装的函数获取结果,不用再返回一个包装器了是多此一举,包装器我是用装饰宏 DECORATE 封装了 [&]() 语句实现的,从这点看比python要简洁
    std::cout << "novoid after " << std::endl;
    return ret;
}

DEF_DECIRATOR //用宏语义快速定义装饰器函数
//DIS_DECIRATOR //快速禁用装饰器函数,原理为自动替换实现为 return func();
(logging, std::string const &funcname = "unknow func", int others = 0)
{
    (void) others;
    //在调用被装饰函数之前的操作
    std::cout <<"log before " << funcname  << std::endl;
    auto start = std::chrono::high_resolution_clock::now();
    auto const &caller = CALL();//使用CALL宏精简代码 func_caller<decltype(func)>(func)
    auto const &end = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double> time_span = std::chrono::duration_cast<std::chrono::duration<double>>(end - start);
    std::cout << "log after " << funcname << ": elapsed time(s): " << time_span.count() << std::endl;
    return caller.ret();
}

//兼容 void 返回值,不用 func_caller 的高性能的写法,考虑兼容c++11的话缺点是要把实现拷贝一份,有代码冗余不好维护,而且比较复杂要用一些奇技淫巧
//如果您的环境是c++17就不用看这个 maybe_void 了,直接用if constexpr吧
template<typename T>
auto maybe_void(T const &func)
{
    using ReturnType = typename std::result_of<T&()>::type;
    using IsVoidReturn = bool_trait<std::is_same_v<void, ReturnType>>;
    return maybe_void(func, IsVoidReturn());
}

using ReturnIsVoid = bool_trait<true>;
using ReturnIsntVoid = bool_trait<false>;

// 对于非void返回值的重载
template<typename T>
auto maybe_void(T const &func, ReturnIsntVoid const &)
{
    std::cout << "maybe_void before " << std::endl;
    auto const &ret = func();
    std::cout << "maybe_void after " << std::endl;
    return ret;
}

// 对于void返回值的重载
template<typename T>
void maybe_void(T const &func, ReturnIsVoid const &)
{
    std::cout << "maybe_void before " << std::endl;
    func();
    std::cout << "maybe_void after " << std::endl;
}

//定义一个仿函数装饰器,无论是函数装饰器还是仿函数装饰器,第一个参数都得是std::function<T ()> 类型否则装饰器宏无法正常展开
class CheckRotateSpeed
{
public:
    CheckRotateSpeed(CCar const *obj, std::string const &funcname)
        :m_p_obj(obj)
        ,m_str_funcname(funcname)
    {

    }

    //仿函数同样适用快速定义或禁用宏,模板的风格请参考 novoid 和 novoid_
    DEF_DECIRATOR
    //DIS_DECIRATOR
    (operator ())
    {
        //在调用被装饰函数之前的操作
        std::cout << "checkRotateSpeed before " << m_str_funcname  << ", rotat_speed:"<< m_p_obj->rotat_speed()  << std::endl;
        auto const &caller = CALL();
        //在调用被装饰函数之前的操作
        std::cout << "checkRotateSpeed after " << m_str_funcname  << ", rotat_speed:"<< m_p_obj->rotat_speed()  << std::endl;
        return caller.ret();
    }

private:

    CCar const *m_p_obj;
    std::string m_str_funcname;
};

int CCar::oilfeed(int degress)
DECORATE(logging,,
DECORATE(novoid,ARGS(),//对于装饰器的使用来说,如果没有参数,那么需要一个ARGS()占位或留空,留空看上去就是两个连续的','
DECORATE(maybe_void,,
//使用UNDECORATE宏可以单点禁用一个装饰器,原理是去掉了该层的 [&](){}包装器并悬空checkRotateSpeed以及ARGS里面的东西只保留代码块
UNDECORATE(checkRotateSpeed, ARGS(this, __FUNCTION__), //传递多个参数
{
    m_n_rotat_speed += degress;
    return m_n_rotat_speed;
})))) //用了多少个装饰器后面就要多少个),没办法cpp只能这样了,想不出更好的办法,本质是宏嵌套

void CCar::brake(int degress)
DECORATE(logging, ARGS(__FUNCTION__),
//DECORATE(novoid,ARGS(), 使用novoid装饰器无法直接通过编译,因为不支持 void 作为返回值的偏特化
DECORATE(maybe_void,,
//推荐用仿函数的方式实现装饰器,通过构造函数传参封装性更好,如果舍弃函数类型的装饰器的话,就可以少一个宏参数占位了不用打奇怪的两个逗号了,
DECORATE(CheckRotateSpeed(this, __FUNCTION__),,
{
    m_n_rotat_speed -= degress;
})))

//移除decorator相关宏,如果后文遇到重名的宏的话
#include "base/rm_decorator.h"

int main(int , char *[])
{
    CCar car;
    car.oilfeed(45);
    car.brake(1000);
    return 0;
}

各位如果觉得有用,请把 “有用” 打到评论区🤪

pyqt5 pyqtProperty的便捷封装,用于打包读写函数和值改变信号

[toc]

cpp版戳这里

背景

  • 项目用到了pyqt,需要使用qt动态属性来暴露控件的一些样式属性,以便于在qss中一次性解决主题问题

    传统的pyqtProperty定义方式

    • 如下,非常的繁琐,大量都是相同逻辑的重复代码,属性一旦多了就非常不美观,如果是C++ 用Qt Creator还能通过编辑器自动生成一下,pyqt暂时没有找到类似的便捷方法只能手敲,而且python也没有宏这样的代码替换工具,于是以此为动机需要找一个方法来封装冗余的代码块
 class Meal(QObject):

    def __int__(self):
        super(Meal, self).__int__()
        self.temperature = 0

    def setTemperature(self, value: int):
        if value== self.temperature:
            return
        self.temperature = value
        self.temperatureChanged.emit(self.temperature)

    def temperature(self)
        return self.temperature 

    temperatureChanged = pyqtSignal(int)
    temperature = pyqtProperty(type=int, fget=temperature, fset=setTemperature)

网上关于pyqtProperty的描述很少,看来看去就几篇帖子,还是从官方的简陋文档翻译的,看得我头疼,索性就自己研究一下。

实现思路

  1. 从pyqtProperty派生,将setter和getter以及信号都封装到该类中,然后像这样去使用;

    class Meal(QObject):
    
    def __int__(self):
        super(Meal, self).__int__()
    
    temperature = QtNotifyProperty(int)

    世界都清净了,比C++的定义都清晰。

    适用场景

  • 凡是需要定义setter getter signal这三要素的属性的地方,一行代码替代
  • 在同类实例较少的情况下使用,如UI类,单例类等;这是因为实现使用的是Python的Dict,并且是定义在类的静态空间中;一个该类实例对应一个属性,如果同类实例过多的话会影响效率。

    源码

from PyQt5.QtCore import *

class QtNotifyProperty(pyqtProperty):
    class PropertyObject(QObject):
        changed = pyqtSignal(QVariant)

        def __call__(self, *args, **kwargs):
            return self._value

        def __init__(self, PropertyType):
            super(QtNotifyProperty.PropertyObject, self).__init__()
            self._value = PropertyType()

        def set(self, value):
            if value == self._value:
                return
            self._value = value
            self.changed.emit(self._value)

        def bind(self, obj: QObject, propertyName: str, onChange: 'def (src: srcType)->destType' = None):
            if onChange is None:
                self.changed.connect(lambda value: obj.setProperty(propertyName, value))
            else:
                self.changed.connect(lambda value: obj.setProperty(propertyName, onChange(value)))

        def get(self):
            return self._value

    def __set__(self, instance, value):
        self.setPropertyValue(instance, value)

    def __get__(self, instance, owner) -> PropertyObject:
        return self.getPropertyObj(instance)

    def __init__(self, propertyType):
        self._properties: dict[QObject, QtNotifyProperty.PropertyObject] = {}

        def _getPropertyObj(owner: QObject) -> QtNotifyProperty.PropertyObject:
            try:
                return self._properties[owner]
            except KeyError:
                self._properties[owner] = \
                    self.PropertyObject(propertyType)

                def removeProperty():
                    del self._properties[owner]
                    print("remove")

                owner.destroyed.connect(removeProperty)
                return self._properties[owner]

        def _getPropertyValue(owner: QObject):
            return _getPropertyObj(owner)()

        def _setPropertyValue(owner: QObject, value) -> None:
            _getPropertyObj(owner).set(value)

        self.getPropertyObj = _getPropertyObj
        self.setPropertyValue = _setPropertyValue
        super(QtNotifyProperty, self).__init__(type=propertyType, fget=_getPropertyValue,
                                               fset=_setPropertyValue)

class Meal(QObject):

    def __int__(self):
        super(Meal, self).__int__()

    temperature = QtNotifyProperty(int)
    calorie = QtNotifyProperty(int)

def main():
    a = QCoreApplication([])
    meal = Meal()
    meal2 = Meal()
    # 直接 属性.信号名 的方式获取信号
    meal.temperature.changed.connect(lambda value: print("meal on temperature changed:", value))
    meal.calorie.changed.connect(lambda value: print("meal on calorie changed:", value))
    meal2.temperature.changed.connect(lambda value: print("meal2 on temperature changed:", value))
    meal2.calorie.changed.connect(lambda value: print("meal2 on calorie changed:", value))
    # 属性绑定,将meal2和meal对象进行默认的属性绑定
    meal.temperature.bind(meal2, "temperature")
    meal.calorie.bind(meal2, "calorie")
    # setProperty的方式触发值改变信号
    meal.setProperty("temperature", 50)
    meal.setProperty("calorie", 1000)
    # property的方式访问属性值
    print("temperature: ", meal.property("temperature"))
    print("calorie: ", meal.property("calorie"))
    # 赋值的方式触发值改变信号
    meal.temperature = 60
    meal.calorie = 2000
    # 获取属性值的另外两种方式 .get() 和 ()
    print("temperature", meal.temperature())
    print("calorie", meal.calorie.get())

if __name__ == '__main__':
    main()

执行结果

利用宏简化Q_PROPERTY动态属性的定义

[toc]

实现历程

传统定义方式

using type = int;
class Any: public QObject
{
    Q_OBJECT
    Q_PROPERTY(type name READ name WRITE setname NOTIFY nameChanged)
public:
    void setname(const type& name)
    {
        if (m_name == name) return;
        m_name = name;
        emit nameChanged();
    }

    type name() const
    {
        return m_name;
    }
signals:
    void nameChanged();
private: type m_name; 
}

关于Q_PROPERTY这个宏我就不赘述了,传统方式可以写好Q_PROPERTY后用qtcreator右键菜单重构选项生成实现代码,但是换一个编辑器或者要修改的时候就有点恼火了。
简化地思路其实很简单,相信很多人应该跟我有同样的想法,将传统定义的setter和getter以及信号一起打包到一个宏里边去就行了。

预想的方式

#define NOTIFY_PROPERTY(type, name) \
 //读写函数以及信号的定义...

class Any: public QObject
{
    Q_OBJECT
    NOTIFY_PROPERTY(type, name)
}

测试通过的例程

mainwindow.h

#ifndef DEF_NOTIFY_CONNECT
#include <functional>

template<typename T>
struct ptr_traits;

template<typename className>
//!
//! \brief The ptr_traits struct
//! 可以像这样使用 T* p = new ptr_traits<T*>::class_name; 用于提取指针的类型
//!
struct ptr_traits<className*>
{
    using class_name = className;
};

#define DEF_GETTER(type, name, ...)\
    public: type name() const\
{\
    __VA_ARGS__\
    return m_##name;\
    }

#define DEF_SETTER(type, name, ...)\
    public: void set##name(type value)\
{\
    if (m_##name == value)\
    return;\
    m_##name = value;\
    __VA_ARGS__\
    }

#define DEF_MEMBER(type, name, ...)\
    private:\
    int m_##name;\
    __VA_ARGS__

#define DEF_PROPERTY(type, name, ...) \
    DEF_MEMBER(type, name)\
    DEF_GETTER(type, name)\
    DEF_SETTER(type, name)\
    __VA_ARGS__

#define DEF_NOTIFY_CONNECT(type, name)\
    public:\
    template<typename T>\
    void on_##name##_changed(T slotFunc)\
{\
    connect(this, &ptr_traits<decltype(this)>::class_name::name##Changed, slotFunc);\
    }\
    template<typename T>\
    void on_##name##_changed(QObject* recever,T slotFunc)\
{\
    connect(this, &ptr_traits<decltype(this)>::class_name::name##Changed, recever, slotFunc);\
    }

//signals关键字不会被元编译器识别但Q_SIGNAL可以
#define DEF_NOTIFY_PROPERTY(type,  name, ...) \
    Q_SIGNAL void name##Changed();\
    DEF_MEMBER(type, name)\
    DEF_GETTER(type, name)\
    DEF_SETTER(type, name, emit this->name##Changed();)\
    Q_PROPERTY(type name READ name WRITE set##name NOTIFY name##Changed)\
    DEF_NOTIFY_CONNECT(type,  name)\
    __VA_ARGS__\
    static_assert(true,"request a ';'")
#endif // DEF_NOTIFY_PROPERTY

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QDebug>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:

//最终用法
    DEF_NOTIFY_PROPERTY(int, name);
    DEF_NOTIFY_PROPERTY(int, name2);

    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    Ui::MainWindow *ui;

};
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

main.cpp

#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    w.on_name_changed(&w, [&w]()//简化版的connect指定接收者
    {
        qDebug() << QStringLiteral("测试1:") << w.name();
    });
    w.on_name2_changed([&]()//简化版的connect不指定接收者
    {
        qDebug() << QStringLiteral("测试2:") << w.name2();
        w.setProperty("name", 3);
    });
    w.setProperty("name", 1);
    w.setProperty("name2", 2);

    return a.exec();
}

执行结果

Qt6

qt6似乎有其他的解决方案感兴趣戳这里可能需要魔法

更新

经评论区老哥提醒发现把signals关键字替换为Q_SIGNAL后,可以定义到我们的宏里完成我预想的用法 。

记录目前使用vue3.2.0&&vite4.2.0时遇到的一些坑-持续更新

[toc]

vue3安装markdown-it

vue ts使用markdown-it需要使用以下命令安装才行

npm install --save markdown-it @vue/compiler-sfc

来源https://juejin.cn/s/vue3%20markdown-it

link中引用node_modules中css样式打包后丢失

在main.ts中导入该样式重新打包即可,猜测应该是因为路径引用并不会触发vite的打包逻辑

在link中引用css最好还是直接用生产域名直接方便有效,vite似乎可以配置环境变量来区分生产环境和测试环境,不过我的项目不复杂,直接用配置文件来区分就好了

arm平台npm run dev时报错 at Object.networkInterfaces

error when starting dev server:
SystemError [ERR_SYSTEM_ERROR]: A system error occurred: uv_interface_addresses returned Unknown system error 13 (Unknown system error 13)
    at Object.networkInterfaces (node:os:277:16)
    at resolveServerUrls (file:///root/work/product/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:12512:28)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async Object.listen (file:///root/work/product/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:65106:39)
    at async CAC.<anonymous> (file:///root/work/product/node_modules/vite/dist/node/cli.js:776:9)

修改 /vite.config.ts 从文件首行添加以下代码

//解决arm平台报错问题
try {
  require('os').networkInterfaces()
} catch (e) {
  require('os').networkInterfaces = () => ({})
}

打包发布到nginx的子路由时无法访问,如https://xxx.com/product/

  1. 修改 /<项目路径>/router/index.ts文件如下,原理尚未可知

     const router = createRouter({
      history: createWebHashHistory(import.meta.env.VITE_BASE_PATH),
      routes,
    })
    
  2. 修改 /vite.config.ts 如下添加base属性为你的项目真实域名https://xxx.com/product/

    export default defineConfig(({ command, mode }) => {
      return {
        base: "https://xxx.com/product/"
        }
    }
  3. niginx配置,任意server 中添加一下配置

    location /product {
            alias <你的网站根目录>;  # 网站根目录 如 /www/xxx/product
            index index.html;   # 默认首页文件
    }

关于属性绑定界面不自动刷新问题、

使用以下两种方式创建的属性在修改后可以稳定触发控件的重新渲染

  1. 对于用于对象的绑定需使用reactive()包裹,如下

    let items = reactive(Array<any>())
    //创建后正常修改即可触发界面刷新
  2. 对于用于值的绑定须使用ref()进行创建,如下

    //创建方法
    const height = ref(window.innerHeight - heightOffset)
    //设置新的值方法
    height.value = window.innerHeight - heightOffset

ref()获取DOM元素对象的方法

<template>
<div ref="test">
</div>
</template>
<script lang='ts' setup>
const test = ref()

function dosomething(){
//确保dom渲染完成后调用
const element = test.value
}
</script>

$on的最轻量级替代方法使用document.dispatchEvent()

//创建自定义事件
const streamDoneEvent = new Event('eventName')||new CustomEvent('eventName',{detail:{/*any obj*/}})
//发射自定义事件
document.dispatchEvent(streamDoneEvent)
//监听自定义事件
document.addEventListener('streamDone', onSteamDone, false)

chatui changelog

[toc]

chatui changelog

特别声明

  • 虽然目前我托管的服务器并没有采集用户的输入信息,但是不能保证服务器平台和openai不会,所以请不要填写私密涉密信息,否则后果自付
  • 由于没有开发任何缓存机制,如果您想要保存当前对话结果建议直接使用Ctrl+S保存整个网页。
  • 目前采用浏览器缓存加密保存历史数据,更换浏览器或清除缓存会导致历史数据丢失该功能还不稳定可能出现异常导致历史数据完全丢失的情况,如果结果很重要请自行备份!!!
  • 建议默认使用3.5模型,对于关键问题再使用gpt-4。
  • 应用使用中的问题或建议可以在改日志评论区发表评论,我会适当修复和采纳。

开发计划

  • 功能清单
    • add:保存对话历史记录到浏览器缓存
    • add:更换为最新的gpt-4模型选项
    • change:取消卡片点击动态效果
    • add:添加直接复制卡片未格式化文本内容到剪切板
    • add:添加导出历史记录功能
    • add:历史记录自定义命名
    • 设置窗口()暂时搁置
    • add:异常提示气泡弹窗
    • add:显示余额
    • add:计算token并显示预计消耗金额
  • 修复清单
    • bug:偶尔会出现乱码问题
    • bug:从空回答重新回答失败问题
    • bug:月份顺序颠倒
    • bug:编辑中不允许选中其他卡片
    • 切换分支不移动到对话末尾
    • ctrl回车发送无视限制问题
    • bug:对于用户输入信息卡片不使用markdown渲染
      • bug:追加pre标签包裹内容,使换行标签等字符串不要转译

最近版本

  • 新增功能清单
名称 描述 注意事项
历史记录列表 点击页面左上角按钮展示历史对话记录 缓存的大小根据不同的浏览器不同,请定期自行清除整理,可能会出现异常导致历史记录完全丢失
新建对话 点击历史列表/今天折叠栏/右上角按钮 如果已经有了新对话则不会重新创建,删除记录后会自动创建并选择
删除对话 选择对话记录/后点击X按钮 目前删除没有提醒,删除后不可恢复,请谨慎操作
加载历史 单击记录后会加载历史对话信息

历史版本

  • 历史功能清单 v 0.0.1
名称 描述 注意事项
模型选择 通过点击对话标签选择模型 目前仅开放gpt-3.5-turbo和gpt-4(8k)gpt-4-1106-preview,目前友情测试运行请尽量使用3.5的模型,对于关键问题再使用gpt-4,账户约有5$用完即止
对话 与选定的AI模型进行问答对话 目前限制单次只能输入1024个字符,因为上下文是增量消费的规则,所以请尽量使用简短的对话上下文完成目标,以节约使用成本,上文过长时建议使用重新生成开启对话分支或删除当前对话分支,该请求设置了频率上限,请不要频繁请求,如果长时间未响应请等待几十秒再次尝试
重新生成当前对话 对当前生成结果不满意支持点击当前结果卡片右下角按钮一键重新生成 同上
编辑对话卡片并重新生成内容 单击用户对话卡片右下角显示有编辑按钮 同上
删除对话分支 单击用户对话卡片右下角显示有删除按钮 删除后会自动切换到之前的历史对话分支上
支持展示所有的对话历史分支 单击用户对话卡片左下角显示当前在那一个分支以及分支切换按钮 分支切换后会展示之前的对话分支上的记录

关于photoshop的Generative Fill功能体验

[toc]

先来看效果

替换照片背景中的陌生人

实现思路

  • 照片左边的人是分别切割为许多个小块然后逐步使用Generative Fill功能加上关键词remove 【something】实现,
  • 右边则主要是使用的左边的镜像和PS的修补工具实现,
  • 当然抠图是基操,用时一个工作日,其中抠图和修补是最耗时的,
  • 主要是因为右边没有生成我想要的背景,
  • 如下从花坛变成了花圃😂,其实花圃也不是不行,没去过的人肯定不知到那边是什么,在这个基础上把自动生成的人再去掉效果也不错。

Generative Fill 如何使用

  1. 安装photoshop beta版,网上的绿色版或官方版都行
  2. 科学上网,因为必须登录adobe账号才能使用
  3. 打开项目使用矩形选择工具(或其他选择工具)选择要替换的区域,区域比较大的时候分割为多个小区域效果会比较好否则就会出现一些不可预料的情况参见上图
  4. 选取好后会出现一个悬浮工具栏,点击 generative fill按钮(如果没有科学上网的话这个按钮是灰的不能点击)
  5. 输入关键词
  6. 默认会生成3张图,点击左右切换按钮进行切换选择效果最好的那一张或者重新生成

yuv422转rgb565

[toc]

大概流程

出队后,根据以获得的index读出对映的YUV数据即buffers[index]中的数据
//Yuv422所占内存为w*h*2Byte
//每两个像素点共享一个色度(U,V),因此每次循环处理两个像素,即每次处理4Byte
For(in = 0; in < w*h*2;in +=4)
{
//通过位运算分别得到无符号整型的Y0,Y1,U,V值
//转rgb565
}

//放大数据使其适应屏幕,一对多copy,w:160->480 h:120->272

YUV转RGB565

将YUV422转RGB565:
//Argments:
short Y,U,V
short R,G,B;
//转RGB888,未量化
R = Y +((360 * (V - 128)) >> 8);
G = Y - (((88*(U - 128) + 184*(V-128))) >> 8);
B = Y + ((455 * (U - 128)) >> 8);
//取0~255之间的值
……
//转RGB565
//忽略后3位左移11位,屏蔽后11位
R = ((R >> 3)) << 11) & 0xF800;
//忽略后2位左移5位,屏蔽前5位和后5位
G = ((G >> 2) << 5) & 0x07E0;
//忽略后3位,屏蔽前11位
B = (B >> 3) & 0x001F;
return (unsigned short)(R | G | B);