文章
Flutter项目快速换图标
安装依赖 flutter pub add --dev flutter_launcher_icons 初始化配置文件,将自动在项目根目录创建一个名为flutter_launcher_icons.yaml的文件,位于你的 flutter 项目的根目录。 dart run flutter_launcher_icons:generate 修改项目根目录的flutter_launcher_icons.yaml,移除不需要的平台之类的,如果改过了这个文件就不用再去pubspec.yaml单独加一个配置项了,直接运行替换即可。
Flutter项目快速重命名
全局安装 flutter pub global activate rename 查看帮助 flutter pub global run rename help 运行重命名
Python+Flask实现便捷SMTP邮件HTTP在线接口.md
from flask import Blueprint, jsonify, request from api.send_email import send_html_email email_bp = Blueprint("email_bp", __name__, url_prefix="/api/email") @email_bp.route("/send", methods=["POST"]) def send_email_api(): data = request.json result = send_html_email( subject=data["subject"], html_content=data["content"], recipients=data["recipients"], nickname="Flask通知", ) return jsonify({"code": 200, "msg": result}) import smtplib from email.mime.text import MIMEText from email.header import Header from email.utils import formataddr from config.init import SMTP_HOST, SMTP_PORT, SENDER_EMAIL, SENDER_PSW def send_html_email( # 邮件主题 subject, # 邮件内容 html_content, # 收件人邮箱地址 recipients, # 发件人昵称 nickname, # 发送超时 timeout=20, ): # 创建HTML邮件对象 msg = MIMEText(html_content, "html", "utf-8") # 修复From头格式(核心修复) msg["From"] = formataddr((nickname, SENDER_EMAIL)) msg["To"] = Header(",".join(recipients), "utf-8") msg["Subject"] = Header(subject, "utf-8") # 建立SMTP连接 try: server = smtplib.SMTP_SSL(host=SMTP_HOST, port=SMTP_PORT, timeout=timeout) # 调试 # print("开始连接...") # server.set_debuglevel(1) # print("开始登录...") server.login(user=SENDER_EMAIL, password=SENDER_PSW) # print("开始发送...") server.sendmail( from_addr=SENDER_EMAIL, to_addrs=recipients, msg=msg.as_string() ) return f"邮件发送成功" except smtplib.SMTPException as err: return f"邮件发送失败: {err}" finally: server.quit()
Syntax组件示例
MD 示例 修复问题 仅在_index.md 页面配置,避免同级上一篇下一篇顺序错误 cascade: params: reversePagination: false 标签 JSONYAMLTOML JSON: JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax. YAML: YAML is a human-readable data serialization language. TOML: TOML aims to be a minimal configuration file format that’s easy to read due to obvious semantics. 代码块 main.gopackage main import "fmt" func main() { fmt.Println("Hello, World!") } 起司 Badge Badge Badge Badge Badge Badge Badge Badge Badge
Tailwind+Vue滚动视差
循环多图 <template> <!-- 父容器必须指定relative --> <div class="relative h-[500vh]"> <!-- 滚动视差背景容器 --> <div v-for="(image, index) in images" :key="`bg-parallax-${index}`" :class="['h-screen', 'bg-cover bg-center bg-fixed']" :style="{ backgroundImage: `url(${image})`, }" /> </div> </template> <script setup lang="ts"> const images = [ 'https://picsum.photos/id/1000/1600/1600', 'https://picsum.photos/id/1002/1600/1600', 'https://picsum.photos/id/1021/1600/1600', 'https://picsum.photos/id/1022/1600/1600', 'https://picsum.photos/id/1023/1600/1600', ] </script> 单图 需要注意控制z-index
本地SSL证书生成
下载地址 让系统信任 CMD./mkcert -install 生成证书 CMD./mkcert example.com localhost 127.0.0.1 192.168.xx.xx 查询证书路径 CMD./mkcert -CAROOT
部署手机短信转发APP(SmsForwarder)
安装 最新版本:下载地址 备注 以需要转发短信的设备作为服务端,APP内置内网穿透等工具,可供多个远程客户端控制,如远程查询设备通话记录或短信历史、远程发短信等功能 配置 必要权限 ❗建议手动进入APP设置,检查是否开启允许后台高耗电 短信权限 关闭验证码安全保护 基础开关 进入侧边栏-通用设置 划到转发功能,开启转发短信广播 划到保活措施,开启开机启动、忽略电池优化、在最近人物列表中隐藏 划到个性设置,点击刷新按钮设备名称和SIM主键 个性化推送内容排版 建议这样,比较清楚干净 {{SMS}} 发信号:{{FROM}}({{PHONE_AREA}}) 发信名:{{CONTACT_NAME}} 收信号:{{CARD_SLOT}} 设备电量:{{BATTERY_PCT}} 设备网络:{{NET_TYPE}} {{RECEIVE_TIME}} 消息推送渠道 按个人需求添加,配置保持默认即可
对应用(.exe、.dll等)进行签名防杀
原文:https://stackoverflow.com/questions/84847/how-do-i-create-a-self-signed-certificate-for-code-signing-on-windows 创建自签名证书颁发机构 (CA) 打开Visual Studio终端 makecert -r -pe -n "CN=My CA" -ss CA -sr CurrentUser -a sha256 -cy authority -sky signature -sv MyCA.pvk MyCA.cer 信任/导入 CA 证书 certutil -user -addstore Root MyCA.cer 创建代码签名证书(SPC) makecert -pe -n "CN=My SPC" -a sha256 -cy end -sky signature -ic MyCA.cer -iv MyCA.pvk -sv MySPC.pvk MySPC.cer 将证书和密钥转换为 PFX 文件 pvk2pfx -pvk MySPC.pvk -spc MySPC.cer -pfx MySPC.pfx 使用证书对代码进行签名 signtool sign /v /f MySPC.pfx /fd SHA256 /t http://timestamp.comodoca.com/authenticode "要签名的程序路径.dll" 可自行选择时间戳服务,这里有一些免费的 CMDhttp://timestamp.verisign.com/scripts/timstamp.dll http://timestamp.globalsign.com/scripts/timstamp.dll http://timestamp.comodoca.com/authenticode http://timestamp.digicert.com
解决nuxt、better-sqlite3安装报错问题
报错信息 搜索到相关ISSUE : 看到是pnpm绑定错误 ERROR Cannot start nuxt: Could not locate the bindings file. Tried: 15:58:33 → D:\project\Demo-Nuxt\node_modules\.pnpm\better-sqlite3@12.4.1\node_modules\better-sqlite3\build\better_sqlite3.node → D:\project\Demo-Nuxt\node_modules\.pnpm\better-sqlite3@12.4.1\node_modules\better-sqlite3\build\Debug\better_sqlite3.node → D:\project\Demo-Nuxt\node_modules\.pnpm\better-sqlite3@12.4.1\node_modules\better-sqlite3\build\Release\better_sqlite3.node → D:\project\Demo-Nuxt\node_modules\.pnpm\better-sqlite3@12.4.1\node_modules\better-sqlite3\out\Debug\better_sqlite3.node → D:\project\Demo-Nuxt\node_modules\.pnpm\better-sqlite3@12.4.1\node_modules\better-sqlite3\Debug\better_sqlite3.node → D:\project\Demo-Nuxt\node_modules\.pnpm\better-sqlite3@12.4.1\node_modules\better-sqlite3\out\Release\better_sqlite3.node → D:\project\Demo-Nuxt\node_modules\.pnpm\better-sqlite3@12.4.1\node_modules\better-sqlite3\Release\better_sqlite3.node → D:\project\Demo-Nuxt\node_modules\.pnpm\better-sqlite3@12.4.1\node_modules\better-sqlite3\build\default\better_sqlite3.node → D:\project\Demo-Nuxt\node_modules\.pnpm\better-sqlite3@12.4.1\node_modules\better-sqlite3\compiled\22.20.0\win32\x64\better_sqlite3.node → D:\project\Demo-Nuxt\node_modules\.pnpm\better-sqlite3@12.4.1\node_modules\better-sqlite3\addon-build\release\install-root\better_sqlite3.node → D:\project\Demo-Nuxt\node_modules\.pnpm\better-sqlite3@12.4.1\node_modules\better-sqlite3\addon-build\debug\install-root\better_sqlite3.node → D:\project\Demo-Nuxt\node_modules\.pnpm\better-sqlite3@12.4.1\node_modules\better-sqlite3\addon-build\default\install-root\better_sqlite3.node → D:\project\Demo-Nuxt\node_modules\.pnpm\better-sqlite3@12.4.1\node_modules\better-sqlite3\lib\binding\node-v127-win32-x64\better_sqlite3.node at bindings (node_modules\.pnpm\bindings@1.5.0\node_modules\bindings\bindings.js:126:9) at new Database (node_modules\.pnpm\better-sqlite3@12.4.1\node_modules\better-sqlite3\lib\database.js:48:64) at getDB (/D:/project/Demo-Nuxt/node_modules/.pnpm/db0@0.3.2_better-sqlite3@12.4.1/node_modules/db0/dist/connectors/better-sqlite3.mjs:20:11) at Object.exec (/D:/project/Demo-Nuxt/node_modules/.pnpm/db0@0.3.2_better-sqlite3@12.4.1/node_modules/db0/dist/connectors/better-sqlite3.mjs:27:20) at getLocalDatabase (/D:/project/Demo-Nuxt/node_modules/.pnpm/@nuxt+content@3.7.1_better-sqlite3@12.4.1_magicast@0.3.5/node_modules/@nuxt/content/dist/module.mjs:35 at async processCollectionItems (/D:/project/Demo-Nuxt/node_modules/.pnpm/@nuxt+content@3.7.1_better-sqlite3@12.4.1_magicast@0.3.5/node_modules/@nuxt/content/dist/module.mjs:2976:14) at async /D:/project/Demo-Nuxt/node_modules/.pnpm/@nuxt+content@3.7.1_better-sqlite3@12.4.1_magicast@0.3.5/node_modules/@nuxt/content/dist/module.mjs:2953:20 at async initNuxt (/D:/project/Demo-Nuxt/node_modules/.pnpm/nuxt@4.1.3_@parcel+watcher@_07ff22dce285dd87314808f367da6350/node_modules/nuxt/dist/index.mjs:5859:3) at async NuxtDevServer._load (/D:/project/Demo-Nuxt/node_modules/.pnpm/@nuxt+cli@3.29.3_magicast@0.3.5/node_modules/@nuxt/cli/dist/chunks/index.mjs:241:5) at async NuxtDevServer.load (/D:/project/Demo-Nuxt/node_modules/.pnpm/@nuxt+cli@3.29.3_magicast@0.3.5/node_modules/@nuxt/cli/dist/chunks/index.mjs:168:7) at async initialize (/D:/project/Demo-Nuxt/node_modules/.pnpm/@nuxt+cli@3.29.3_magicast@0.3.5/node_modules/@nuxt/cli/dist/chunks/index.mjs:470:3) at async Object.run (/D:/project/Demo-Nuxt/node_modules/.pnpm/@nuxt+cli@3.29.3_magicast@0.3.5/node_modules/@nuxt/cli/dist/chunks/dev.mjs:454:43) at async runCommand (/D:/project/Demo-Nuxt/node_modules/.pnpm/citty@0.1.6/node_modules/citty/dist/index.mjs:316:16) at async runCommand (/D:/project/Demo-Nuxt/node_modules/.pnpm/citty@0.1.6/node_modules/citty/dist/index.mjs:307:11) at async runMain (/D:/project/Demo-Nuxt/node_modules/.pnpm/citty@0.1.6/node_modules/citty/dist/index.mjs:445:7) 解决方案1 运行下方命令重新启动项目即可,此命令将添加相关onlyBuiltDependencies到package.json
实用pb_hooks模板
创建自定义Payload的JWT /// <reference path="../pb_data/types.d.ts" /> routerAdd( "GET", "/api/customAuth", (e) => { // 常规读取请求url中的参数 // let name = e.request?.pathValue("name"); // 读取环境变量密钥-用trim避免ubuntu下额外的换行符 const JWT_SIGNING_KEY = $os.getenv("SECRET_PB_ROLE_JWT").trim(); // jwt密钥 if (!JWT_SIGNING_KEY) { return e.json(500, { code: 500, message: "服务端未配置自定义认证密钥", }); } if (JWT_SIGNING_KEY.length !== 32) { return e.json(500, { code: 500, message: "服务端自定义认证密钥配置错误,长度必须是32字节", }); } // 如果前端在请求头Authorization传递token,则此处会返回用户完整数据 let userInfo = e.auth; // 初始化日期对象 const date = new Date(); // 当前服务器时间(时间戳格式) const curTimestamp = date.getTime(); // 当前服务器时间(ISO8601格式) const curTime = date.toISOString(); // 设置JWT有效期(单位:秒) const JWT_SEC_DURATION = 604800; // 计算出JWT过期时间(ISO8601格式) const jwtExpireTime = new Date( curTimestamp + JWT_SEC_DURATION * 1000 ).toISOString(); // jwt自定义载荷 const JWT_PAYLOAD = { id: userInfo?.get("id"), role: userInfo?.get("role"), }; // 生成自定义JWT const roleJwt = $security.createJWT( JWT_PAYLOAD, JWT_SIGNING_KEY, JWT_SEC_DURATION ); return e.json(200, { code: 200, message: "获取权限JWT成功", data: { // userInfo, token: roleJwt, expireTime: jwtExpireTime, }, curTime: curTime, }); }, // 需要在请求头携带users表token才允许访问 $apis.requireAuth("users") ); AES加密 /// <reference path="../pb_data/types.d.ts" /> // 路由-字符串加密/解密 routerAdd( "POST", "/api/crypto", (e) => { // 读取环境变量密钥-用trim避免ubuntu下额外的换行符 const SECRET_AES = $os.getenv("SECRET_AES_KEY").trim(); // 服务端AES密钥 if (!SECRET_AES) { return e.json(500, { code: 500, message: "服务端未配置自定义认证密钥", }); } if (SECRET_AES.length !== 32) { return e.json(500, { code: 500, message: "服务端密钥配置错误,长度必须是32字节", }); } // 请求体读取示例(无自动类型校验) // const reqBody = e.requestInfo().body; // 请求体读取示例(自动类型校验) let reqBody; reqBody = new DynamicModel({ type: "", data: "", }); e.bindBody(reqBody); // console.log("请求体:", toString(reqBody)); // 加密字符串数据 if (reqBody.type === "encrypt") { try { const ciphertext = $security.encrypt(reqBody.data, SECRET_AES); return e.json(200, { code: 200, message: "操作成功", result: ciphertext, }); } catch (err) { return e.json(500, { code: 500, message: `服务端错误: ${err.message}`, }); } } // 解密字符串数据 else if (reqBody.type === "decrypt") { try { const payloadStr = $security.decrypt(reqBody.data, SECRET_AES); return e.json(200, { code: 200, message: "操作成功", result: payloadStr, }); } catch (err) { return e.json(500, { code: 500, message: `服务端错误: ${err.message}`, }); } } else { return e.json(400, { code: 400, message: "操作类型错误", }); } }, // 需要在请求头携带users表token才允许访问 // $apis.requireAuth("users") ); SMTP自定义邮件 /// <reference path="../pb_data/types.d.ts" /> routerAdd( "GET", "/api/mail", (e) => { // console.log("test", $app.settings()); if (!$app.settings().smtp.enabled) { return e.json(400, { code: 400, message: "未开启邮件功能", }); } /** 从查询参数读取收件人邮箱列表 */ const recipients = e.request?.url?.query().get("recipients"); if (!recipients) { return e.json(400, { code: 400, message: "未填写邮箱列表", }); } const mailConfig = new MailerMessage({ from: { address: e.app.settings().meta.senderAddress, name: e.app.settings().meta.senderName, }, to: [{ address: recipients }], subject: "YOUR_SUBJECT...", html: "YOUR_HTML_BODY...", // bcc, cc and custom headers are also supported... }); e.app.newMailClient().send(mailConfig); return e.json(200, { code: 200, message: "发送邮件成功", }); }, // 需要在请求头携带users表token才允许访问 $apis.requireAuth("users") ); 规则触发邮件通知 /// <reference path="../pb_data/types.d.ts" /> onRecordCreate((e) => { const recipients = "sos@unmei.fun"; const userId = e.record?.get("sender"); const userLocation = e.record?.get("location"); const userMsgContent = e.record?.get("msgContent"); if (e.record?.tableName() === "msgBoard") { const mailConfig = new MailerMessage({ from: { address: e.app.settings().meta.senderAddress, name: e.app.settings().meta.senderName, }, to: [{ address: recipients }], subject: "新的待审核留言", html: ` <p>用户${userId}(${userLocation})提交了一条新留言:</p> <p>${userMsgContent}</p> `, // bcc, cc and custom headers are also supported... }); e.app.newMailClient().send(mailConfig); } e.next(); }); IP日志 /// <reference path="../pb_data/types.d.ts" /> routerAdd("GET", "/api/visit", (e) => { // ---ip转地名请求部分 // ip转地名结果 let location = ""; const getIPReq = { url: "https://ip.unmei.fun/api/region", method: "POST", // 请求体json示例 JSON.stringify({"test": 123}) // 请求体表单示例 new FormData() body: JSON.stringify([e.realIP()]), // 测试解析ip地址请求 // body: JSON.stringify(["121.34.33.120"]), headers: { Authorization: "4444", "content-type": "application/json", }, // 超时时间(单位:秒) timeout: 120, }; // console.log("请求参数", JSON.stringify(getIPReq)); // 发出请求给ip地址解析接口,可抛出超时或网络连接错误 const locationReq = $http.send(getIPReq); // 取出第一个数据体结构 const locationRes = locationReq.json.data[0].region; // 第一个数据体中,如果存在'region'字段则判断为解析成功 if (locationRes) { // console.log("成功解析地名", location); location = `${locationRes.country}${locationRes.province}${locationRes.city}`; } else { console.log("无法解析地名"); } // ---ip转地名请求部分 // 1. 获取请求信息 const data = new DynamicModel({ ip: e.realIP(), location: location, url: e.request?.url?.path || "", uid: e.auth?.id || "", fringerPrint: e.request?.header.get("X-Fingerprint") || "", created: new DateTime(), }); // console.log("请求数据", JSON.stringify(data)); const table = $app.db(); // 2. 创建一条新记录并保存到 'logs' 表 try { table .newQuery( "INSERT INTO logs (ip,location, url, uid, fringerPrint,created) VALUES ({:ip}, {:location}, {:url}, {:uid}, {:fringerPrint}, {:created})" ) .bind(data) .execute(); } catch (error) { console.log("Failed to create visit record: " + error); // 即使记录创建失败,我们仍然尝试返回总数 } // 3. 查询 'logs' 表中的总记录数 const result = new DynamicModel({ count: 0 }); // 用于接收查询结果 try { $app .db() .newQuery("SELECT COUNT(*) as count FROM logs") // SQL 查询语句 .one(result); // 将查询结果的第一行填充到 result 对象中[6](@ref) } catch (queryError) { $app.logger().error("Failed to query total logs: " + queryError); // 如果查询失败,返回一个错误响应 return e.json(500, { error: "Internal server error: could not retrieve count", }); } // 4. 返回总记录数 return e.json(200, { total_visit: result, // 返回总计数 }); }); 返回日志数据 /// <reference path="../pb_data/types.d.ts" /> routerAdd("GET", "/api/visit", (e) => { const total_visit = new DynamicModel({ count: 1 }); $app .logQuery() .select("count(*) as count") .andWhere($dbx.exp("json_extract(data, '$.url') = '/api/visit'")) .one(total_visit); return e.json(200, { total_visit: total_visit, }); });