Squid在windows下安装使用一、Squid安装下载地址 进入官网：https://squid.diladele.com/ ，点击下图中的按钮下载即可。 安装向导点击next 点击勾选，再点击next 选择安装路径，再点击next 点击install 下载完成之后，桌面出现这两个图标 防火墙设置打开防火墙，点击入站规则，再点击新建规则 选择端口，点击下一步 输入端口，与squid配置中的端口一致，默认为3128，点击下一步 选择允许连接，点击下一步 默认全选，点击下一步 输入名称，点击下一步 常用命令 进入命令窗口，可以双击打开安装squid时，在桌面生成的Squid Terminal；或者使用cmd进入安装目录下的bin目录。 squid -k parse 检查配置文件语法问题 curl -x 代理电脑的内网地址:3128 -L www.baidu.com 查看是否代理成功 测试正常访问既代理配置成功windows 客户机代理配置 开始菜单（邮件）-> 网络连接 -> 代理（滚动到底部） window10 与 11 界面上略有区别 自己去找对应的地方即可到此，内网的代理配置已经完成了
我们经常会遇到一些情况，在转换昵称做图片海报的时候，出现字体库内资源不够导致字体无法转化的清理，搜遍全网也没有一个很好的解决办法，于是就自动动手封装了一个，原理还是逃脱不掉字体库，于是我封装了一些常用的字体库(且支持中日韩和一些常用的符号图形)使用说明引入扩展 go get gitee.com/archn/act-red-go加载依赖 go mod tidy导入方法 import "gitee.com/archn/act-red-go/text-to-img/toimage"使用方法：toimage.RenderTextToPNG(text string, fontPaths []string, outputOrFile any, args ...TextToPngArgs) error文字转换为图片方法，用于常规海报生成等fontPaths 默认使用内置字体 如果要新增请注意 排序靠前的优先匹配outputOrFile 可以是文件名称路径（绝对路径请已/开头）、也可以是文件句柄os.File用于内容写入args 为可选参数，用于配置文字的颜色大小图片大小等基本信息详情请关注对应结构体，（默认12px|黑色|自适应）toimage.ConvertRemoteImageToPNG(imageURL string, output *os.File) error任意图片转png方法使用例子package main import "gitee.com/archn/act-red-go/text-to-img/toimage" func main() { err := toimage.RenderTextToPNG("新ღღ你不认识吗？A中国🐂나사랑해요12abα🌸double 🇨🇳 🧧ৡღ ℒℴѵℯ依·訫ꦿ⸙ ঞ 不留遗憾 ོꦿ ࿐ ₅₂ₒ ꧁哥 ໊是ꕥ电ꦿ工꧂", nil, "abc.png", toimage.TextToPngArgs{Size: 30}) if err != nil { return } } 包共享地址act-red-go 如有需要请自行扩展或提供宝贵建议
go进程独占文件的实现，可用于单进程锁等其它逻辑
在网上搜了一大圈，找到了一个独占文件的逻辑用了下，发出来和大家分享一下，写的还是不错的，我试了其它的一些方法，比如说 _, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY, os.ModeExclusive) 这些乱起八糟的说明还是不能行，结果这套代码可以，那么就分享一下吧！好吧直接上代码package main import ( "flag" "fmt" "os" "syscall" "time" ) var filename string var openSec int64 func main() { flag.Int64Var(&openSec, "openSec", 60, "独占的秒数") flag.StringVar(&filename, "filename", "D:/dd.txt", "需要独占打开得文件") flag.Parse() lockFile(filename, openSec) } func lockFile(filename string, openSec int64) error { _, err := os.Stat(filename) if err != nil { return fmt.Errorf("打开文件[%s]失败,%v", filename, err) } p, err := syscall.UTF16PtrFromString(filename) if err != nil { return err } fd, err := syscall.CreateFile(p, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, // syscall.CREATE_ALWAYS syscall.FILE_ATTRIBUTE_NORMAL, 0, ) if err != nil { return err } time.Sleep(time.Second * time.Duration(openSec)) syscall.Close(fd) return nil }以上代码是别的写的，那么我拿他来做了个函数封装，用于我的程序单进程锁其实下面这些封装是不够完美的，相当于跑了一个独立线程来锁死文件，由于go还是不怎么熟练先凑合用了，如果你有更合适的方案欢迎发过来一起学习一下func main() { executablePath, _ := os.Executable() path = filepath.Dir(executablePath) if !lockProcess() { s := "仅能执行一个进程" writeToLogFile(s) fmt.Println(s) return } // 业务逻辑... } // 判断是否与其它实例并行 func lockProcess() bool { filePath := filepath.Join(path, ".lock") file, err := os.OpenFile(filePath, os.O_CREATE, 0666) if err != nil { return false } file.Close() go func() { err := lockFile(filePath) if err != nil { return } for { time.Sleep(time.Hour * 1000) } }() return true } func lockFile(filename string) error { _, err := os.Stat(filename) if err != nil { return fmt.Errorf("打开文件[%s]失败,%v", filename, err) } p, err := syscall.UTF16PtrFromString(filename) if err != nil { return err } _, err = syscall.CreateFile(p, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, // syscall.CREATE_ALWAYS syscall.FILE_ATTRIBUTE_NORMAL, 0, ) if err != nil { return err } return nil }
说明：这是基于swoole_process封装的一个并发执行类，代码实现您可以做下参考！ 个人在项目上用还是有挺好用的，使用时需要注意的是如果您使用的不是laravels框架，那么要注意以下两个地址根据您的框架做下调整：DB::disconnect(); 看您的框架使用哪个方法，意思是每创建一个进程需要断开重连一个mysql否则可能会造成报错，当然如果你的并发中不牵扯到sql查询可以注释掉这个Cache:: 缓存支持，由于通道支持处理的字符有限，我们通过缓存来实现数据的交换，根据您的框架支持或干脆使用redis实现数据交换即可，这里您可以自行改造。好了我们直接上代码： /** * 并发请求类 - 不支持并发将自动使用串行 * @return object {"getKey":"获取key",“getCount”:"获取并发数","add":"添加并发任务","run":"开始运行任务并返回结果","onError":"监听错误信息","onSuccess":"监听成功信息"} * @example $P = cdpProcess(); * $P->add( * function(){ * return 任务结果 * }, * "[可选]自定义id" //返回任务结果键名将为此值 * ); * $data = $P->run(); //开始任务 */ function cdpProcess() { return new class { private $uuid; private $installed = 0; private $worker = []; private $errCall = []; private $dataCall = []; private $index = 0; public function __construct() { if (function_exists("swoole_version")) { $this->installed = 1; } $this->uuid = uniqid(mt_rand(1, 100)); } public function getKey(): string { return $this->uuid; } public function getCount(): int { return count($this->worker); } /** * 添加执行方法进入队列 * @param callable $callback 任务逻辑回调 * @param int|string $index_name 可选任务自定义id否则 * @return $this */ public function Add(callable $callback, $index_name = null) { $key = $this->getKey()."_".$this->index; if ($this->installed) { $pro = new \swoole_process(function (\swoole_process $work) use ($callback, $index_name, $key) { try { DB::disconnect(); //始终使用新连接规避并发冲突 $d = $callback($index_name); Cache::put( $index = 'Process:' . $key, json_encode($d, 320), 5 ); $work->write('{"key":"' . $index . '","index_name":"' . $index_name . '"}'); } catch (\Exception $e) { $work->write(json_encode(['err' => $e->getMessage()])); } $work->exit(0); }, true); $this->worker[] = $pro; } else { $this->worker[] = new class($callback, $index_name) { private $callback; private $index_name; public function __construct(callable $callback, $index_name = null) { $this->callback = $callback; $this->index_name = $index_name; } public function start() { try { $d = ($this->callback)($this->index_name); } catch (\Exception $e) { $d = ['err' => $e->getMessage()]; } return [$d, $this->index_name]; } }; } $this->index++; return $this; } /** * 错误回调 * @param callable $errCallBack * @return $this */ public function onError(callable $errCallBack) { $this->errCall[] = $errCallBack; return $this; } /** * 完成数据回调 * @param callable $dataCallBack * @return $this */ public function onSuccess(callable $dataCallBack) { $this->dataCall[] = $dataCallBack; return $this; } /** * 启动并返回结果 * @return array */ public function run(): array { $data = $err = []; foreach ($this->worker as $i => $pro) { $s = $pro->start(); if (!$this->installed) { empty($s[1]) || $i = $s[1]; if (!empty($s[0]['err'])) $err[$i] = $s[0]['err']; else $data[$i] = $s[0]; } } if ($this->installed) { foreach ($this->worker as $i => $pro) { if (!empty($d = $pro->read())) { $d = json_decode($d, 1); empty($d["index_name"]) || $i = $d["index_name"]; if (!empty($d['err'])) $err[$i] = $d['err']; else { $data[$i] = json_decode(Cache::pull($d['key'], null), 1); } } unset($this->worker[$i]); } while (\swoole_process::wait()); } empty($err) && $this->ErrCallback($err); $this->DataCallback($data); return $data; } private function ErrCallback(array $err) { foreach ($this->errCall as $fun) { try { $fun($err); } catch (\Exception $e) { } } } private function DataCallback(array $data) { foreach ($this->dataCall as $fun) { try { $fun($data); } catch (\Exception $e) { } } } }; }
HTML 屏蔽F12及右键的通用方法
此方法自己尝试过可以借鉴参考，升级改造请自便package sse import ( "fmt" "github.com/gin-gonic/gin" "net/http" "time" ) // - SSE 相关封装 ----------------------- /* 输出说明： 1. Data等于空 不输出 2. Data+Event与上一个输出一致 不重复输出 3. 业务逻辑报错会向前端发出发送一个close事件 需要前端收到后主动close()请求 前端示例： const es = new EventSource(url+'auth-token='+token), listener = function (event) { console.log(event); var div = document.createElement("div");var type = event.type; div.appendChild(document.createTextNode(type + ": " + (type === "message" ? event.data : es.url)));document.body.appendChild(div); }; es.addEventListener("open", listener); es.addEventListener("message", listener); es.addEventListener("error", listener); es.addEventListener("close", function(ret){ es.close(); listener(ret) }); */ // MessageEvent 定义 SSE 事件 type MessageEvent struct { Id string Event string // Data+Event与前一个输出一致 不输出 Data string // Data等于空 不输出 } // 实现 SSE 事件的 String() 方法 func (e MessageEvent) String() string { s := "" if e.Id != "" { s += "id:" + e.Id + "\n" } if e.Event != "" { s += "event:" + e.Event + "\n" } s += fmt.Sprintf("data:%s\n\n", e.Data) return s } // SseOut see协议输出 // // fun(i 循环计数器)(事件结构体,下一个循环延迟时间) 该函数执行业务逻辑并将结果写入通道 func SseOut(c *gin.Context, T func(i int64) (MessageEvent, time.Duration, error)) { defer func(){}() messageChan := make(chan MessageEvent, 5) c.Header("Content-Type", "text/event-stream") c.Header("Cache-Control", "no-cache") c.Header("Connection", "keep-alive") w := c.Writer w.WriteHeader(http.StatusOK) flusher, _ := w.(http.Flusher) temp := "" //结果比对临时变量 var Err error // 监听客户端信号 closeNotify := c.Request.Context().Done() go func() { defer func() { if err := recover(); err != nil { // 捕获到 panic return } }() <-closeNotify close(messageChan) return }() // 执行业务逻辑 go func() { defer func() { if Err != nil { close(messageChan) return } if err := recover(); err != nil { // 捕获到 panic return } }() // 通道活性探测利用业务空闲时间独立执行 i := int64(0) for { e, t, err := T(i) if err != nil { Err = err break } //--两次结果一致的数据阻止输出 if temp != e.Data+e.Event { temp = e.Data + e.Event } else { e.Data = "" } if e.Data != "" { i++ messageChan <- e } else { messageChan <- MessageEvent{ Event: "__ping__", } } if t != 0 { time.Sleep(t) } } }() // SSE 监听并输出 for { if message, ok := <-messageChan; ok && Err == nil { if message.Event == "__ping__" { continue } fmt.Fprintf(w, "%s", &message) flusher.Flush() } else { if Err != nil { // 向客户端发送 SSE 事件 fmt.Fprintf(w, "%s", &MessageEvent{ Id: "-500", Event: "close", Data: Err.Error(), }) // 刷新 response buffer flusher.Flush() } break } } } 引用其它业务逻辑自行根据业务需求写入.... sse.SseOut(c, func(index int64) (Row sse.MessageEvent, Sleep time.Duration, Err error) { // 下一轮循环延迟时间 Sleep = time.Second * 3 // 数据 Row.Data = "" Row.Id = fmt.Sprintf("%d", index) Row.Event = action //--登录状态事件 if index > 0 { return } return }) ....
laravel-admin 提交表单时遇到 “Call to a member function getAttribute() on null” 报错的解决
先看下报错内容：{ "message": "Call to a member function getAttribute() on null", "exception": "Error", "file": "D:\\WWW\\src\\vendor\\encore\\laravel-admin\\src\\Form.php", "line": 413, "trace": [ { "file": "D:\\WWW\\src\\vendor\\encore\\laravel-admin\\src\\Form.php", "line": 393, "function": "applayFieldDisplay", "class": "Encore\\Admin\\Form", "type": "->" }, { "file": "D:\\WWW\\src\\vendor\\encore\\laravel-admin\\src\\Form.php", "line": 352, "function": "ajaxResponse", "class": "Encore\\Admin\\Form", "type": "->" }, ... }这是一个快捷表单提交问题的报错问题，问题困扰了我好长时间，起初以为是我哪里我弄错类型了，反复尝试无果，最终顺着错误来源去撸了一遍，发现“src/vendor/encore/laravel-admin/src/Form.php”的 413行 $newValue = $this->model->fresh()->getAttribute($field->column());的fresh返回null 所以导致 “getAttribute” 报错，好了我们寻找下 fresh 看是做啥用的Reload a fresh model instance from the database.这么一看没问题，这是从数据库加载实例用的，那我就无解了，只能怀疑是 “$this->exists” 了，继续撸，于是我找到了这个方法，马上顿悟！报错是因为没有执行到 $this->exists = true; 导致实例为空,我看了下注释解决思路来了，原来表设计的时候我的id主键是非自增的，需要我在模型中明确为非自增解决:找到我的model，新增一行 public $incrementing=false; 搞定
laravel-admin中单独定义某页面模板 如“footer.blade.php”
关于laravel-admin仅仅修改个版权的事儿在网上搜了一圈，想只想改改版权，不想动别的，但是结果基本上都是官网提供的帮助文档的方法直接修改所有模板的路径copy模板，后面再升级了万一遇上个啥结构变化都要再改一遍属实有些坑啊，于是咱们就找到了laravel的视图的基本监听方法composer，结果解决了问题。好了直接上代码:还是老办法 打开/app/Admin/bootstrap.php，添加下面代码/** * 定制单页面模板 */ app('view')->composer('admin::*', function (Illuminate\View\View $view) { $views=[ 'admin::partials.footer'=>[ 'path' => resource_path('views/footer.blade.php') ] ]; if(!empty($views[$index=$view->getName()])){ $view->setPath($views[$index]['path']); } });搞个模板/resources/views/footer.blade.phpp @if(config('admin.show_environment')) Env {!! config('app.env') !!} @endif @if(config('admin.show_version')) Version {!! \Encore\Admin\Admin::VERSION !!} @endif Powered by 技术支持 大功告成 ::(茶杯)
lumen或laravel 将日志上报至kafka大家都清楚 Lumen 算是一个精简版的 Laravel，针对速度优化了框架的加载方式，多余的功能需要自行开启。那么在精简版中日志的上报配置方面lumen也做了精简，本文中将分别介绍这两者日志上报的基本方法，咱们主要还是依赖hhxsv5/laravel-kafka-logger，下面是方法和步骤（Laravel 和 lumen的安装咱们就略过了）咱们先安装php的rdkafka扩展要求依赖要求php-rdkafka>=4.0.0安装方法git clone --depth 1 https://github.com/edenhill/librdkafka.git /tmp/librdkafka && cd /tmp/librdkafka && ./configure && make -j$(nproc) && make install && rm -rf /tmp/librdkafka pecl install rdkafka安装 laravel-kafka-logger在项目目录,根据您的项目版本运行如下脚本# Laravel 5.x composer require "hhxsv5/laravel-kafka-logger:~1.0.0" # Laravel 6.x & 7.x composer require "hhxsv5/laravel-kafka-logger:~2.0.0"lavael 上配置修改配置文件 config/logging.phpreturn [ 'channels' => [ //..., 'kafka' => Hhxsv5\LKL\KafkaLogger::getDefinition(['topic' => env('LOG_KAFKA_TOPIC', 'laravel-logs')]), ], ];lumen 上配置在app/Providers目录新增日志扩展类LogServiceProvider.php<?php namespace App\Providers; use Hhxsv5\LKL\KafkaLogHandler; use Illuminate\Support\ServiceProvider; use RdKafka\Conf; use Monolog\Logger; class LogServiceProvider extends ServiceProvider { /** * Configure logging on boot. * * @return void */ public function boot() { $this->createLogger([ 'driver' => 'custom', 'via' => static::class, 'name' => config('app.name'), 'topic' => env('LOG_KAFKA_TOPIC', 'php-logs'), 'fallback' => 'daily', 'flush_timeout_ms' => 5000, 'settings' => [ // see https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md 'metadata.broker.list' => env('LOG_KAFKA_BROKER_LIST', '127.0.0.1:9092'), 'queue.buffering.max.ms' => 3000, // < flush_timeout_ms, send messages ASAP 'socket.keepalive.enable' => true, 'log_level' => LOG_WARNING, ], // 'tap' => [App\Library\LogFormatter::class], //--格式化日志 ]); } /** * Register the log service. * * @return void */ public function register() { } private function createLogger($config){ if (!isset($config['fallback'])) { $config['fallback'] = 'daily'; } if (!isset($config['flush_timeout_ms'])) { $config['flush_timeout_ms'] = 60000; } if (!isset($config['level'])) { $config['level'] = Logger::DEBUG; } if (!isset($config['bubble'])) { $config['bubble'] = true; } $producerConf = new Conf(); foreach ($config['settings'] as $key => $value) { $producerConf->set($key, $value); } $producerConf->setLogCb(function ($kafka, $level, $facility, $message) use ($config) { app('log')->channel($config['fallback'])->info(sprintf('KafkaLogger %s: %s (level: %d)', $facility, $message, $level)); }); $producerConf->setErrorCb(function ($kafka, $err, $reason) use ($config) { app('log')->channel($config['fallback'])->error(sprintf('KafkaLogger error: %s (reason: %s)', rd_kafka_err2str($err), $reason)); }); $producerConf->setDrMsgCb(function ($kafka, $message) use ($config) { if ($message->err) { app('log')->channel($config['fallback'])->error(sprintf('KafkaLogger delivery fail: %s', $message->errstr())); } }); $handler = new KafkaLogHandler($config['name'], $producerConf, $config['topic'], null, $config['fallback'], $config['flush_timeout_ms'], $config['level'], $config['bubble']); $this->app['log']->setHandlers([$handler]); return $this->app['log']; } }注册类到bootstrap/app.php$app->register(\App\Providers\LogServiceProvider::class);最后步骤修改或新增.env配置文件如下内容LOG_CHANNEL=kafka # 注意：这一行lumen不要加的，否则白给 LOG_KAFKA_BROKER_LIST=kafka:9092 # 多个用英文逗号间隔 LOG_KAFKA_TOPIC=laravel-logs测试Log::info('测试日志', ['ok-uid-hhxsv5']);{dotted startColor="#ff6c6c" endColor="#1989fa"/}大功告成，祝顺利！
在网上搜集到的一个很实用的js版本的类hahs算法，无论多少字符串，计算其为唯一的id的取值用非常实用，分享给大家 /** * 计算字符串的hash值 * @param {string} str 字符串 * @param {boolean} caseSensitive [可选]忽略大小写 * @returns */ const getHashCode = function (str,caseSensitive) { str = str.toString(); if(!caseSensitive){ str = str.toLowerCase(); } var hash = 1315423911, i, ch; for (i = str.length - 1; i >= 0; i--) { ch = str.charCodeAt(i); hash ^= ((hash << 5) + ch + (hash >> 2)); } return (hash & 0x7FFFFFFF); }