首页
斐讯设备
疯言疯语
系统运维
编程语言
网站建设
友情连接
推荐
autojs
Search
1
charles破解注册码(含windows和mac)最新安装教程「亲测有效」
65 阅读
2
mac下的好用工具小身材大本领-NeatDownloadManager-ndm下载器
57 阅读
3
【N1和Armbian】一、N1刷Linux系统armbian
43 阅读
4
docker更新现有容器为自动重启
41 阅读
5
aris常用命令收集
39 阅读
登录
Search
标签搜索
JAVA
JAVA学习系列
docker
Linux
js
N1
git
模块二
模块一
端口
模块五
数据库
模块九
模块四
镜像
模块三
模块六
MySQL
百度网盘
nginx
DaiMaFengZi
累计撰写
598
篇文章
累计收到
15
条评论
首页
栏目
斐讯设备
疯言疯语
系统运维
编程语言
网站建设
页面
友情连接
推荐
autojs
搜索到
598
篇与
的结果
2026-09-11
悟空 AI CRM 部署指南,与宝塔共存解决端口占用,重置管理密码
悟空 AI CRM 部署指南,与宝塔共存解决端口占用,重置管理密码本文档介绍如何在服务器上快速部署悟空 AI CRM,支持 Docker 一键启动,也可与宝塔面板共存。一、环境要求Docker ≥ 20.10Docker Compose ≥ 2.0最少 2C4G 内存,10G 磁盘空间国内服务器推荐搭配华为云 SWR 镜像源(已内置)二、克隆仓库并启动git clone https://github.com/WuKongOpenSource/Wukong-AICRM.git cd Wukong-AICRM/docker docker-compose up -d启动完成后访问 http://localhost 即可。三、初始密码重置首次登录后请立即修改默认密码。方式一:动态生成 BCrypt Hash(推荐)HASH=$(docker run --rm httpd:2.4 htpasswd -bnBC 10 "" admin123 | tr -d ':\n' | sed 's/\$2y/\$2a/') echo "Generated hash: $HASH" docker exec -it WeKnora-postgres psql -U postgres -d WeKnora \ -c "UPDATE manager_user SET password='$HASH' WHERE username='admin';"方式二:硬编码固定 Hash如果上面的命令因 $ 转义问题执行失败,可直接使用以下命令:docker exec -it WeKnora-postgres psql -U postgres -d WeKnora \ -c "UPDATE manager_user SET password='\$2a\$10\$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy' WHERE username='admin';"验证密码是否更新成功docker exec -it WeKnora-postgres psql -U postgres -d WeKnora \ -c "SELECT username, password FROM manager_user WHERE username='admin';"看到新的 $2a$10$... 格式的 hash 即表示成功。无需重启容器。用户名密码adminadmin123四、Docker Compose 配置以下为与宝塔面板共存的完整 docker-compose.yaml。services: frontend: image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/wechatopenai/weknora-ui:v0.3.6 container_name: WeKnora-frontend volumes: - ./nginx/default.conf.template:/etc/nginx/templates/default.conf.template - ./nginx/cert:/etc/nginx/cert ports: - "${FRONTEND_PORT:-80}:80" environment: - MAX_FILE_SIZE_MB=${MAX_FILE_SIZE_MB:-50} - BASE_PATH=${CRM_BASE_PATH:-/} depends_on: app: condition: service_healthy crm: condition: service_healthy networks: - crm-network restart: unless-stopped app: image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/wechatopenai/weknora-app:v0.3.6 container_name: WeKnora-app ports: - "${APP_PORT:-8080}:8080" volumes: - data-files:/data/files healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] interval: 30s timeout: 10s retries: 3 start_period: 60s environment: - COS_SECRET_ID=${COS_SECRET_ID:-} - COS_SECRET_KEY=${COS_SECRET_KEY:-} - COS_REGION=${COS_REGION:-} - COS_BUCKET_NAME=${COS_BUCKET_NAME:-} - COS_APP_ID=${COS_APP_ID:-} - COS_PATH_PREFIX=${COS_PATH_PREFIX:-} - COS_ENABLE_OLD_DOMAIN=${COS_ENABLE_OLD_DOMAIN:-} - GIN_MODE=${GIN_MODE:-release} - DISABLE_REGISTRATION=${DISABLE_REGISTRATION:-false} - DB_DRIVER=postgres - DB_HOST=postgres - DB_PORT=5432 - DB_USER=${DB_USER:-} - DB_PASSWORD=${DB_PASSWORD:-} - DB_NAME=${DB_NAME:-} - TZ=Asia/Shanghai - OTEL_EXPORTER_OTLP_ENDPOINT=jaeger:4317 - OTEL_SERVICE_NAME=WeKnora - OTEL_TRACES_EXPORTER=otlp - OTEL_METRICS_EXPORTER=none - OTEL_LOGS_EXPORTER=none - OTEL_PROPAGATORS=tracecontext,baggage - RETRIEVE_DRIVER=${RETRIEVE_DRIVER:-} - DOCREADER_ADDR=docreader:50051 - STORAGE_TYPE=${STORAGE_TYPE:-} - LOCAL_STORAGE_BASE_DIR=${LOCAL_STORAGE_BASE_DIR:-} - AUTO_RECOVER_DIRTY=${AUTO_RECOVER_DIRTY:-true} - MINIO_ENDPOINT=minio:9000 - MINIO_ACCESS_KEY_ID=${MINIO_ACCESS_KEY_ID:-minioadmin} - MINIO_SECRET_ACCESS_KEY=${MINIO_SECRET_ACCESS_KEY:-minioadmin} - MINIO_BUCKET_NAME=${MINIO_BUCKET_NAME:-} - OLLAMA_BASE_URL=${OLLAMA_BASE_URL:-http://host.docker.internal:11434} - STREAM_MANAGER_TYPE=${STREAM_MANAGER_TYPE:-} - REDIS_ADDR=redis:6379 - REDIS_PASSWORD=${REDIS_PASSWORD:-} - REDIS_DB=${REDIS_DB:-} - REDIS_PREFIX=${REDIS_PREFIX:-} - ENABLE_GRAPH_RAG=${ENABLE_GRAPH_RAG:-} - CONCURRENCY_POOL_SIZE=${CONCURRENCY_POOL_SIZE:-5} - JWT_SECRET=${JWT_SECRET:-} - INIT_LLM_MODEL_NAME=${INIT_LLM_MODEL_NAME:-} - INIT_LLM_MODEL_BASE_URL=${INIT_LLM_MODEL_BASE_URL:-} - INIT_LLM_MODEL_API_KEY=${INIT_LLM_MODEL_API_KEY:-} - INIT_EMBEDDING_MODEL_NAME=${INIT_EMBEDDING_MODEL_NAME:-} - INIT_EMBEDDING_MODEL_BASE_URL=${INIT_EMBEDDING_MODEL_BASE_URL:-} - INIT_EMBEDDING_MODEL_API_KEY=${INIT_EMBEDDING_MODEL_API_KEY:-} - INIT_EMBEDDING_MODEL_DIMENSION=${INIT_EMBEDDING_MODEL_DIMENSION:-} - INIT_EMBEDDING_MODEL_ID=${INIT_EMBEDDING_MODEL_ID:-} - INIT_RERANK_MODEL_NAME=${INIT_RERANK_MODEL_NAME:-} - INIT_RERANK_MODEL_BASE_URL=${INIT_RERANK_MODEL_BASE_URL:-} - INIT_RERANK_MODEL_API_KEY=${INIT_RERANK_MODEL_API_KEY:-} - MAX_FILE_SIZE_MB=${MAX_FILE_SIZE_MB:-50} depends_on: redis: condition: service_started postgres: condition: service_healthy docreader: condition: service_healthy networks: - crm-network restart: unless-stopped extra_hosts: - "host.docker.internal:host-gateway" docreader: image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/wechatopenai/weknora-docreader:v0.3.6 build: context: . dockerfile: docker/Dockerfile.docreader container_name: WeKnora-docreader ports: - "${DOCREADER_PORT:-50051}:50051" environment: - MINIO_ENDPOINT=minio:9000 - MINIO_PUBLIC_ENDPOINT=http://${HOST_IP}:${MINIO_PORT:-9000} - MINERU_ENDPOINT=${MINERU_ENDPOINT:-} - MAX_FILE_SIZE_MB=${MAX_FILE_SIZE_MB:-} healthcheck: test: ["CMD", "grpc_health_probe", "-addr=:50051"] interval: 30s timeout: 10s retries: 3 start_period: 60s networks: - crm-network restart: unless-stopped extra_hosts: - "host.docker.internal:host-gateway" postgres: image: paradedb/paradedb:v0.21.4-pg17 container_name: WeKnora-postgres environment: - POSTGRES_USER=${DB_USER} - POSTGRES_PASSWORD=${DB_PASSWORD} - POSTGRES_DB=${DB_NAME} ports: - "15432:5432" volumes: - postgres-data:/var/lib/postgresql/data - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql networks: - crm-network healthcheck: test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"] interval: 10s timeout: 10s retries: 3 start_period: 30s restart: unless-stopped stop_grace_period: 1m redis: image: redis:7.0-alpine container_name: WeKnora-redis command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD} restart: always healthcheck: test: [ "CMD-SHELL", "redis-cli -a ${REDIS_PASSWORD} ping | grep -q PONG", ] interval: 10s timeout: 30s retries: 5 start_period: 10s networks: - crm-network minio: image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/minio/minio:RELEASE.2024-10-02T17-50-41Z container_name: WeKnora-minio ports: - "${MINIO_PORT:-9000}:9000" - "${MINIO_CONSOLE_PORT:-9001}:9001" environment: - MINIO_ROOT_USER=${MINIO_ACCESS_KEY_ID:-minioadmin} - MINIO_ROOT_PASSWORD=${MINIO_SECRET_ACCESS_KEY:-minioadmin} - MINIO_IDENTITY_OPENID_CONFIG_URL=http://${HOST_IP}/.well-known/openid-configuration - MINIO_IDENTITY_OPENID_CLIENT_ID=minio-console - MINIO_IDENTITY_OPENID_CLIENT_SECRET=minio-console-secret-key-2024 - MINIO_IDENTITY_OPENID_CLAIM_NAME=policy - MINIO_IDENTITY_OPENID_SCOPES=openid,profile,email - MINIO_IDENTITY_OPENID_REDIRECT_URI=http://${HOST_IP}:9001/oauth_callback - MINIO_IDENTITY_OPENID_DISPLAY_NAME=AI CRM Login command: server --console-address ":9001" /data volumes: - minio_data:/data healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] interval: 30s timeout: 20s retries: 3 depends_on: crm: condition: service_healthy frontend: condition: service_started networks: - crm-network crm: build: context: .. dockerfile: Dockerfile.crm image: wk_ai_crm:local container_name: crm platform: linux/amd64 ports: - "8088:8088" restart: always volumes: - ./aicrm/logs:/opt/wk_ai_crm/logs - ./aicrm/config:/opt/wk_ai_crm/config depends_on: redis: condition: service_healthy postgres: condition: service_healthy environment: - LANG=zh_CN.UTF-8 - LANGUAGE=zh_CN:zh - LC_ALL=zh_CN.UTF-8 - HOST_IP=${HOST_IP} - BASE_PATH=${CRM_BASE_PATH:-/} - MAIL_CREDENTIAL_ENCRYPTION_KEY=${MAIL_CREDENTIAL_ENCRYPTION_KEY:-aicrm-single-mail-key-change-me} - MAIL_PROXY_ENABLED=${MAIL_PROXY_ENABLED:-false} - MAIL_PROXY_URL=${MAIL_PROXY_URL:-} healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8088/"] interval: 30s timeout: 60s retries: 3 networks: - crm-network networks: crm-network: driver: bridge volumes: postgres-data: data-files: minio_data:五、环境变量配置 (.env)在 docker/ 目录下创建 .env 文件:# ========== 基础配置 ========== GIN_MODE=debug # debug(开发) / release(生产) DISABLE_REGISTRATION=false # 生产环境建议设为 true # Ollama(可选,本地大模型) OLLAMA_BASE_URL=http://host.docker.internal:11434 # ========== 存储配置 ========== DB_DRIVER=postgres RETRIEVE_DRIVER=postgres # 向量存储:postgres / elasticsearch_v7 / qdrant STORAGE_TYPE=local # 文件存储:local / minio / cos STREAM_MANAGER_TYPE=redis # ========== 端口配置 ========== APP_PORT=8080 FRONTEND_PORT=8082 DOCREADER_PORT=50051 # ========== 数据库 ========== DB_USER=postgres DB_PASSWORD=postgres123!@# DB_NAME=WeKnora # ========== Redis ========== REDIS_PASSWORD=redis123!@# REDIS_DB=0 REDIS_PREFIX=stream: # ========== 存储路径 ========== LOCAL_STORAGE_BASE_DIR=/data/files AUTO_RECOVER_DIRTY=true TENANT_AES_KEY=weknorarag-api-key-secret-secret # ========== 功能开关 ========== ENABLE_GRAPH_RAG=false # 知识图谱(构建耗时较长,需调用大模型) # ========== 安全配置 ========== JWT_SECRET=weknora-jwt-secret CONCURRENCY_POOL_SIZE=5 # Embedding 并发(429 错误时调小) HOST_IP=127.0.0.1 CRM_BASE_PATH=/六、与宝塔面板共存(Nginx 反向代理配置)若服务器已部署宝塔,将以下配置加入站点 Nginx 配置即可。server { listen 80; server_name abc.dxw123.top abc.dacaishen.eu.cc; index index.html index.htm; root /www/wwwroot/abc.dxw123.top; #CERT-APPLY-CHECK--START include /www/server/panel/vhost/nginx/well-known/abc.dxw123.top.conf; #CERT-APPLY-CHECK--END include /www/server/panel/vhost/nginx/extension/abc.dxw123.top/*.conf; error_page 404 /404.html; #REWRITE-START include /www/server/panel/vhost/rewrite/abc.dxw123.top.conf; #REWRITE-END # ========== 悟空 AI CRM 反向代理 START ========== # AI CRM 后端 API location ^~ /api/ { proxy_pass http://127.0.0.1:8088; 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; proxy_buffering off; proxy_cache off; proxy_read_timeout 300s; proxy_send_timeout 300s; client_max_body_size 100m; } # WeKnora App API location ^~ /weknora/ { proxy_pass http://127.0.0.1:8080; 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; proxy_buffering off; proxy_cache off; proxy_read_timeout 300s; client_max_body_size 100m; } # 全局上传限制(配合 MAX_FILE_SIZE_MB=50) client_max_body_size 60m; # AI CRM 前端 location / { proxy_pass http://127.0.0.1:8082; 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; } # ========== 悟空 AI CRM 反向代理 END ========== # 禁止访问敏感文件 location ~* (\.user.ini|\.htaccess|\.htpasswd|\.env.*|\.gitignore|\.DS_Store|\.bashrc|README\.md|Dockerfile|docker-compose\.yml|\.sql(\.gz)?)$ { return 404; } # 禁止访问敏感目录 location ~* /(\.git|\.svn|\.vscode|\.idea|\.ssh|\.github|node_modules|runtime)/ { return 404; } # SSL 证书验证 location ~ \.well-known { allow all; } if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) { return 403; } access_log /www/wwwlogs/abc.dxw123.top.log; error_log /www/wwwlogs/abc.dxw123.top.error.log; }代理路径说明路径代理目标说明/api/127.0.0.1:8088AI CRM 后端 API/weknora/127.0.0.1:8080WeKnora App API/127.0.0.1:8082AI CRM 前端七、登录用户名密码adminadmin123直接访问绑定的域名或 IP 即可登录管理系统。
2026年09月11日
0 阅读
0 评论
0 点赞
2026-09-06
手把手教你用SH1107驱动1.3寸OLED屏:从点亮第一个像素到显示自定义图片
从零构建SH1107 OLED驱动:点亮像素到图像显示的实战指南当一块1.3寸OLED屏幕首次连接到开发板时,许多嵌入式开发者会面临相似的困惑——如何让那些微小的像素点按照预期亮起?SH1107作为一款广泛应用的OLED驱动芯片,其寄存器配置和内存寻址模式往往成为初学者的第一道门槛。本文将采用"问题驱动"的教学方式,通过实际案例演示如何从单个像素控制逐步实现复杂图形显示,过程中会特别关注那些容易导致显示异常的技术细节。1. 硬件基础与初始化配置1.1 SH1107的物理内存布局SH1107驱动芯片采用分页式内存架构,这对于刚接触OLED开发的工程师来说是个关键概念。我们以常见的64x128分辨率屏幕为例:页(Page):16个逻辑页(Page0-Page15)行(Row):每页包含8行,共128行(16页×8行)列(Column):64列(实际芯片支持128列,但屏幕物理限制为64)内存寻址时需要特别注意列地址的拆分方式。以下是典型的初始化命令序列:// 初始化命令序列示例 OLED_WR_Byte(0xAE, OLED_CMD); // 关闭显示 OLED_WR_Byte(0xD5, OLED_CMD); // 设置时钟分频 OLED_WR_Byte(0x80, OLED_CMD); // 建议值 OLED_WR_Byte(0xA8, OLED_CMD); // 设置多路复用率 OLED_WR_Byte(0x3F, OLED_CMD); // 对应128行 OLED_WR_Byte(0x20, OLED_CMD); // 设置内存模式注意:0x20命令设置的是页地址模式(Page Addressing Mode),这种模式下写入数据后列地址自动递增,但页地址需要手动切换。1.2 电气连接与通信验证在开始编程前,确保硬件连接正确至关重要。SH1107通常支持I2C和SPI接口,以下是I2C连接的典型引脚配置:引脚名称连接目标备注VCC3.3V绝对不要超过3.3VGND地线 SCLMCU的SCL引脚需接上拉电阻(4.7kΩ)SDAMCU的SDA引脚需接上拉电阻(4.7kΩ)验证通信是否正常的最简单方法是发送一个基本命令并检查ACK信号:# Python示例:使用smbus库检测设备 import smbus bus = smbus.SMBus(1) # 树莓派使用I2C-1 try: bus.write_byte(0x3C, 0xAE) # 尝试关闭显示 print("SH1107响应正常") except IOError: print("通信失败,检查连接")2. 像素级控制实战2.1 点亮单个像素的原理理解SH1107如何控制单个像素是掌握OLED编程的基础。每个像素对应内存中的一个bit,但写入时需要以列为单位,每列8个像素(D0-D7)。以下是点亮第2页第16列全部像素的代码:OLED_WR_Byte(0xB1, OLED_CMD); // 选择Page2(0xB0 + 页号) OLED_WR_Byte(0x0F, OLED_CMD); // 列低地址(0x0F = 第16列) OLED_WR_Byte(0x10, OLED_CMD); // 列高地址 OLED_WR_Byte(0xFF, OLED_DATA); // 写入数据(全亮)这个操作涉及三个关键点:页地址命令的高4位固定为1011,低4位表示页号列地址需要拆分为高4位和低4位分别发送数据0xFF表示该列8个像素全部点亮2.2 坐标定位函数封装为了提高代码可重用性,我们可以封装一个设置坐标的函数:void OLED_Set_Pos(uint8_t x, uint8_t y) { OLED_WR_Byte(0xB0 + y, OLED_CMD); OLED_WR_Byte(((x & 0xF0) >> 4) | 0x10, OLED_CMD); OLED_WR_Byte(x & 0x0F, OLED_CMD); }这个函数处理了列地址的拆分逻辑:(x & 0xF0) >> 4 获取高4位x & 0x0F 获取低4位| 0x10 是因为列高地址命令的前导位是00013. 字符显示的实现3.1 字模提取与存储显示字符需要预先准备好字模数据。以8x16英文字符为例,使用PCtoLCD2002软件生成字模:设置取模方式:逐列式、高位在前字符大小:宽8像素,高16像素生成的字模数据会按列排列,每字符16字节存储字模通常使用常量数组:const uint8_t F8X16[] = { // 字符'E'的字模示例 0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00, 0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00, // 其他字符... };3.2 字符显示函数实现基于字模数据显示字符的函数需要考虑跨页处理:void OLED_ShowChar(uint8_t x, uint8_t y, uint8_t chr, uint8_t size) { uint8_t i = 0; if(size == 16) { OLED_Set_Pos(x, y); for(i=0; i<8; i++) OLED_WR_Byte(F8X16[chr*16+i], OLED_DATA); OLED_Set_Pos(x, y+1); for(i=0; i<8; i++) OLED_WR_Byte(F8X16[chr*16+i+8], OLED_DATA); } else { // 8x6字体 OLED_Set_Pos(x, y); for(i=0; i<6; i++) OLED_WR_Byte(F6x8[chr][i], OLED_DATA); } }提示:对于中文显示,通常需要16x16点阵,这意味着每个汉字需要跨两个页和32字节的字模数据。4. 高级图形显示技术4.1 位图显示原理显示自定义图像需要将位图转换为适合OLED的二进制格式。这个过程称为"取模",关键参数包括:图像宽度:必须与OLED列数对齐(如64像素)图像高度:必须是8的倍数(因为每页8行)颜色深度:单色(1位深度)使用图像处理软件生成的数据格式如下:const uint8_t logo_bmp[] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0, // 更多数据... };4.2 位图显示函数优化高效的位图显示函数需要考虑内存访问模式:void OLED_DrawBMP(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, const uint8_t BMP[]) { uint16_t j = 0; uint8_t x, y; for(y = y0; y < y1; y++) { OLED_Set_Pos(x0, y); for(x = x0; x < x1; x++) { OLED_WR_Byte(BMP[j++], OLED_DATA); } } }实际使用示例:// 显示从(0,0)到(64,16)的图像 OLED_DrawBMP(0, 0, 64, 16, logo_bmp);4.3 动态效果实现技巧通过结合定时器和部分刷新技术,可以实现流畅的动画效果:局部刷新:只更新发生变化的部分区域双缓冲:在内存中准备完整帧后再一次性显示滚动效果:利用SH1107内置的垂直滚动命令// 设置垂直滚动区域 OLED_WR_Byte(0x29, OLED_CMD); // 连续垂直滚动 OLED_WR_Byte(0x00, OLED_CMD); // 虚拟页开始 OLED_WR_Byte(0x07, OLED_CMD); // 滚动时间间隔 OLED_WR_Byte(0x0F, OLED_CMD); // 虚拟页结束 OLED_WR_Byte(0x2F, OLED_CMD); // 启动滚动5. 性能优化与调试5.1 常见问题排查当显示出现异常时,可以按照以下步骤排查:全屏测试:先尝试点亮所有像素(写入0xFF)单页测试:单独测试每一页的显示通信验证:用逻辑分析仪检查I2C/SPI信号电源检查:确保供电稳定无噪声5.2 帧率优化技巧提高刷新率的关键技术:优化方法实施手段预期效果部分区域刷新只更新变化区域减少数据传输量命令流水线批量发送命令和数据减少通信开销使用硬件SPI替代软件模拟I2C提高传输速度内存缓冲在MCU端维护显示缓存实现双缓冲5.3 低功耗设计考虑OLED应用的功耗优化策略:// 进入睡眠模式 OLED_WR_Byte(0xAE, OLED_CMD); // 关闭显示 // 唤醒时重新初始化 void OLED_WakeUp() { OLED_Init(); // 重新初始化 OLED_WR_Byte(0xAF, OLED_CMD); // 开启显示 }实际项目中,可以根据内容更新频率动态调整刷新率,静态显示时降低到1-2Hz可显著节省功耗。
2026年09月06日
0 阅读
0 评论
0 点赞
2026-09-01
麒麟系统升级 OpenSSH 完整指南(.run 与 .rpm 两种方式)
麒麟系统升级 OpenSSH 完整指南(.run 与 .rpm 两种方式)适用系统:银河麒麟桌面版 / 麒麟 V10(服务器版 SP1~SP3)升级目标:将系统自带的低版本 OpenSSH 升级到安全修复后的高版本软件形态:本文覆盖 .run 自解压安装包(x86_64) 与 .rpm 软件包(aarch64) 两种升级方式目录一、为什么要升级 OpenSSH二、升级前必读(安全警告)三、升级前的准备工作四、两种安装包的区别五、方式一:.run 安装包(x86_64 架构)六、方式二:.rpm 安装包(aarch64 架构)七、通用验证步骤八、升级失败如何回退九、常见问题 FAQ十、总结一、为什么要升级 OpenSSHOpenSSH 是 Linux/麒麟系统中最常用的远程登录与文件传输服务(sshd、ssh、sftp、scp)。系统自带的 OpenSSH 版本往往较低,存在已公开的安全漏洞(如命令注入、身份认证绕过、信息泄露等),在等保测评、安全扫描中常被判定为「高危漏洞」并要求整改。通过升级到官方修复版本(如本文涉及的 OpenSSH 10.3p1、9.9sp1),可以:修复已知 CVE 安全漏洞,通过安全测评;提升加密算法的强度与兼容性;满足客户/监管对 SSH 版本的最低要求。二、升级前必读(安全警告)⚠️ 这条必须放在最前面,务必牢记:请勿关闭当前的 SSH 连接!升级 OpenSSH 会重启 sshd 服务。如果升级过程中出现问题(配置错误、版本不兼容、服务启动失败等),当前的 SSH 会话是你唯一的「后门」。正确的做法是:保留当前 SSH 会话不关闭;升级并重启服务后,另开一个新的 SSH 连接进行验证;只有当新连接能正常登录、且版本验证通过后,才考虑关闭旧会话;如果新连接失败,立即用当前旧会话进行回退(恢复备份配置 / 降级包)。简单记:「升级不留旧路,出事只能重装系统」。三、升级前的准备工作3.1 查看系统架构不同架构对应不同的安装包格式,先确认 CPU 架构:uname -m # 或者 arch输出结果架构对应安装包x86_64Intel / AMD / 兆芯 / 海光 等.run 自解压安装包aarch64鲲鹏 920 / 飞腾 等 ARM 芯片.rpm 软件包3.2 查看当前 OpenSSH 版本ssh -V记录下当前版本号,便于升级前后对比。3.3 准备安装包将对应的安装包(.run 或 .rpm)上传到服务器,并解压/整理到独立目录,例如:# 示例:将压缩包上传后解压 tar -xvf 麒麟V10SP1-3-openssh-10.3p1.tar.gz # 得到目录:麒麟V10SP1-3-openssh-10.3p1/四、两种安装包的区别对比项.run 安装包.rpm 软件包适用架构x86_64(Intel / AMD / 兆芯 / 海光)aarch64(鲲鹏 / 飞腾等 ARM)本质自解压、自动安装的可执行脚本Red Hat 系标准软件包管理格式安装方式chmod +x 授权后 ./xxx.run 直接执行rpm -Uvh 升级安装依赖处理脚本内部自动处理,基本无需干预依赖系统 rpm 包管理器典型文件pkgsUpdate-ssh10.3p1-ky10.x86_64-20260427.runopenssh-*.rpm(多个 rpm 文件)优缺点一键式、省心,官方封装更标准,可用 rpm -qa 查询管理选择原则:看架构,不看喜好。x86_64 用 .run,aarch64 用 .rpm。五、方式一:.run 安装包(x86_64 架构)适用于 Intel / AMD / 兆芯 / 海光 等 x86_64 架构的麒麟 V10 系统。5.1 重载 systemd 配置systemctl daemon-reload5.2 备份原 SSH 配置sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_configbak备份文件 sshd_configbak 是升级失败后回退的关键,务必保留。5.3 进入新版目录cd 麒麟V10SP1-3-openssh-10.3p1/5.4 赋予安装脚本执行权限chmod +x pkgsUpdate-ssh10.3p1-ky10.x86_64-20260427.run5.5 开始安装./pkgsUpdate-ssh10.3p1-ky10.x86_64-20260427.run安装脚本会自动完成卸载旧版本、安装新版本、处理依赖等操作,等待执行结束即可。5.6 测试配置是否有误sudo sshd -t该命令用于校验 sshd_config 配置文件语法是否正确。无输出 = 配置正确;有报错 = 根据提示修改配置后重试。5.7 启动 / 重启 SSH 服务sudo systemctl start sshd sudo systemctl restart sshd.service sudo systemctl status sshd.service观察 status 输出中是否为 active (running),确保服务正常启动。5.8 验证版本ssh -V应能看到类似 OpenSSH_10.3p1 的新版本信息。六、方式二:.rpm 安装包(aarch64 架构)适用于鲲鹏 920 / 飞腾等 aarch64(ARM64)架构的银河麒麟系统。📌 特别说明:银河麒麟在重启 SSH 服务时,需要多执行一步 systemctl daemon-reload。即便前面没有手动执行,系统在重启服务时通常也会有相关提示,按提示操作即可。6.1 重载 systemd 配置systemctl daemon-reload6.2 备份原 SSH 配置sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_configbak6.3 进入 rpm 包目录cd openssh9.9sp1-aarch646.4 升级安装(rpm -Uvh)sudo rpm -Uvh openssh-*.rpm参数说明:-U:升级(Upgrade),若软件已安装则升级,未安装则安装;-v:显示详细信息;-h:显示安装进度条(#)。openssh-*.rpm 通配符会一次性安装/升级目录下所有相关 rpm 包(openssh 主程序、客户端、服务端等)。6.5 测试配置是否有误sudo sshd -t6.6 启动 / 重启 SSH 服务sudo systemctl start sshd sudo systemctl restart sshd.service sudo systemctl status sshd.service⚠️ 如果这里提示需要 daemon-reload,先执行 systemctl daemon-reload 再重启。6.7 验证版本ssh -V应能看到类似 OpenSSH_9.9p1 的新版本信息。7、通用验证步骤无论采用哪种方式升级,升级完成后都建议按以下顺序完整验证:7.1 验证版本ssh -V确认输出为升级后的目标版本。7.2 验证服务状态sudo systemctl status sshd.service确认服务处于 active (running) 状态。7.3 验证监听端口sudo netstat -tlnp | grep sshd # 或 sudo ss -tlnp | grep sshd确认 sshd 正在监听默认端口 22。7.4 新开 SSH 连接验证(关键)务必新开一个终端(或新 SSH 会话)登录服务器,验证升级后的 SSH 能正常提供服务。新连接能正常登录 → 升级成功,此时才可关闭旧会话;新连接失败 → 立即返回旧会话,进入「回退」流程。8、升级失败如何回退如果升级后服务无法启动、或新 SSH 连接失败,通过保留的旧会话执行回退:8.1 恢复备份配置sudo cp /etc/ssh/sshd_configbak /etc/ssh/sshd_config sudo systemctl restart sshd8.2 若配置恢复仍无效,回退软件版本.rpm 方式:找到旧版本 rpm 包,用 rpm -Uvh --oldpackage 或 rpm -e + 重新安装旧包进行降级;.run 方式:使用官方提供的旧版本 .run 脚本重新安装,或从系统备份镜像恢复。回退操作同样依赖当前仍可用的 SSH 会话,这再次印证了「升级前务必保留旧会话」的重要性。9、常见问题 FAQQ1:.run 和 .rpm 到底怎么选?A:看架构。x86_64 用 .run,aarch64 用 .rpm。用 uname -m 确认。Q2:sudo sshd -t 报错怎么办?A:该命令是校验 sshd_config 语法。根据报错提示修改 /etc/ssh/sshd_config 后再次执行,直到无输出为止,再重启服务。Q3:重启 sshd 时提示需要 daemon-reload?A:执行 systemctl daemon-reload 后,再重新执行 restart。这在银河麒麟(尤其 aarch64 版)上较常见,属正常现象。Q4:升级后新 SSH 连不上了,怎么办?A:不要慌,用升级前保留的旧 SSH 会话执行回退(恢复 sshd_configbak 配置或降级软件包)。这就是强调「不要关闭旧会话」的原因。Q5:ssh -V 显示的版本没变化?A:可能安装了多个 openssh 客户端,或命令路径不一致。尝试用 rpm -qa | grep openssh(rpm 方式)确认已安装版本,或 which ssh 查看命令路径。Q6:升级会影响当前已登录的会话吗?A:一般不会,已建立的连接在重启服务时通常保持,但绝不能依赖这一点——务必新开连接验证。10、总结步骤.run(x86_64).rpm(aarch64)重载配置systemctl daemon-reloadsystemctl daemon-reload备份配置cp /etc/ssh/sshd_config /etc/ssh/sshd_configbakcp /etc/ssh/sshd_config /etc/ssh/sshd_configbak进入目录cd 麒麟V10SP1-3-openssh-10.3p1/cd openssh9.9sp1-aarch64安装chmod +x xxx.run && ./xxx.runsudo rpm -Uvh openssh-*.rpm测试配置sudo sshd -tsudo sshd -t重启服务systemctl restart sshd.servicesystemctl restart sshd.service验证版本ssh -Vssh -V核心要点回顾:架构决定方式:x86_64 → .run,aarch64 → .rpm;先备份再动手:sshd_config 配置必须提前备份;绝不断旧路:升级全程保留旧 SSH 会话,新开会话验证成功后再关闭;验证三步走:ssh -V 看版本 → systemctl status 看状态 → 新会话登录看连通;出问题能回退:恢复配置 / 降级包,全部依赖那条「活着的旧会话」。📝 免责声明:升级 OpenSSH 属于系统底层服务变更,请在测试环境验证后再于生产环境执行,并务必做好配置与数据备份。本文仅供技术参考,因操作不当导致的损失由操作者自行承担。
2026年09月01日
0 阅读
0 评论
0 点赞
2026-08-31
免费域名+Cloudflare后缀.eu.cc免费注册教程,0元注册直接托管
免费域名 + Cloudflare:后缀 .eu.cc 免费注册教程,0 元注册直接托管如果你最近刚好想折腾个人博客、导航站、项目演示站,或者只是想给自己的小项目弄一个好记的域名,那么今天这个可以留意一下。之前给大家分享过免费的 eu.org 域名,不过它有一个比较明显的问题:域名比较长,而且注册后还需要等待审核。这次发现的这个就简单多了。👉 .eu.cc 免费域名最大的特点就是:注册门槛低、域名比较短,而且普通账号就可以免费注册。🌐 这个域名有什么特别?.eu.cc 本质上是一个域名后缀。比如:abc.eu.ccblog.eu.ccai.eu.cc对于个人博客、工具站、导航站、开源项目展示页来说,长度还是比较舒服的。更重要的是,目前 GNAME 正在提供 .eu.cc 的免费注册活动,普通用户可以获得 3 个免费注册名额,不需要额外完成复杂任务。所以如果你只是想体验一下免费域名,还是挺方便的。🎁 免费注册是怎么实现的?这里需要注意一个细节。它并不是直接显示「价格为 0 元」,而是通过全额抵扣优惠券的方式实现免费注册。进入官方的免费注册活动页面后,领取对应的 .eu.cc 抵扣券,然后选择符合条件的域名注册即可。也就是说:正常查询 → 领取免费额度 → 使用抵扣券 → 0 元注册目前普通账号最多可以免费注册 3 个 .eu.cc 域名。⚠️ 有一个坑,一定要提前知道并不是所有你喜欢的域名都能够免费注册。一些比较特殊的域名会被划分为溢价域名,例如:双字母域名三字母域名一些品牌名称热门行业词其他注册局定义的溢价域名这些域名不能直接使用免费抵扣券。所以看到一个特别漂亮的短域名,不要马上以为可以白嫖,先查询价格再决定。 官方也明确说明了这一点。🔄 免费域名可以一直用吗?这也是很多人最关心的问题。目前活动规则支持免费续费,但有一个非常重要的时间限制:必须进入域名到期前 90 天的时间范围,才能申请免费续费。而且千万别直接去「我的域名」里面续费。官方规定:免费续费必须从「免费注册」活动页面提交。如果你在普通域名管理页面续费,或者开启自动续费,并不能享受这项免费续费活动。所以建议大家注册之后,把到期时间记下来。到了续费周期,再去活动页面操作。☁️ 能不能搭配 Cloudflare?可以。如果你平时喜欢折腾 Cloudflare,那么这个域名也比较适合拿来做一些个人项目。例如:用途说明博客 / 导航站个人内容聚合个人主页简历、作品集展示在线工具小工具、查询类站点项目展示页开源项目落地页短链接站自建短链服务测试站开发联调、演示环境注册完成后,可以按照正常域名的方式进行 DNS 管理,也可以根据自己的需求接入 Cloudflare。这样一来,免费域名 + Cloudflare 免费服务,就可以组合出一个基本零成本的小网站。💡 我觉得它更适合哪些人?如果你是:👉 想搭一个个人博客👉 想做一个自己的导航站👉 经常测试开源项目👉 喜欢折腾 Cloudflare👉 想给自己的项目准备几个备用域名👉 不想一开始就花钱买域名那么 .eu.cc 可以先收藏起来。当然,如果你准备做一个长期商业项目,我还是更建议使用自己购买的正规顶级域名。免费域名最大的优势是降低试错成本,而不是完全替代付费域名。📌 最后总结这次这个 .eu.cc,我认为最大的优势就是:短 + 免费 + 注册简单 + 支持免费续费。普通账号目前有 3 个免费注册名额,但要注意避开品牌词和溢价域名。另外最容易忘记的一点就是:续费一定要在到期前 90 天内,并且必须从「免费注册」活动页面提交。如果你准备拿它搭个人博客、导航站或者测试项目,可以先去看看有没有自己喜欢的域名。项目地址: 美国地址生成器
2026年08月31日
0 阅读
0 评论
0 点赞
2026-08-31
免费领取4个永久域名?Stackryze Domains 开发者福利来了!
免费领取 4 个域名?Stackryze Domains 开发者福利来了!对于喜欢搭建网站、测试项目、部署工具的小伙伴来说,一个免费域名一直都是非常实用的资源。今天分享一个开源免费的域名项目 —— Stackryze Domains 🎁通过简单操作,可以领取 4 个免费域名,有效期一年,并且支持提前免费续约,非常适合个人项目、测试站点和开发学习使用。🌟 Stackryze Domains 是什么?Stackryze Domains 是一个面向开发者的免费域名服务项目。无需购买商业域名,就可以拥有自己的专属域名,用于:✅ 个人主页✅ 开源项目展示✅ 测试网站✅ API 服务✅ 技术博客✅ 学习建站🎁 免费领取 4 个域名目前支持领取以下域名后缀:🔥 .indevs.in🔥 .sryze.cc🔥 .ryzedns.org🔥 .nx.kg每个域名有效期:✅ 1 年免费使用✅ 到期前 60 天可以免费续约不需要支付费用,通过续约可以长期用于个人项目。🌐 域名支持 Cloudflare 托管领取后的域名可以自定义 Nameservers。其中:✅ .indevs.in 支持托管 Cloudflare✅ .sryze.cc 支持托管 Cloudflare可以享受:能力说明☁️ Cloudflare DNS稳定快速的域名解析🚀 全球 CDN 加速访问速度更快🔒 免费 HTTPS SSL一键开启 HTTPS🛡️ 安全防护抵御常见攻击而:⚠️ .ryzedns.org 和 .nx.kg 暂时不支持 Cloudflare 托管,后续是否支持可以关注项目更新。⭐ 如何领取?项目采用 GitHub 支持方式:方式一(推荐)⭐ 注册 GitHub 账号,并给作者项目点一个 Star,即可领取更多域名。方式二📧 使用邮箱注册,只能领取 1 个免费域名。推荐支持作者的 GitHub 项目,让优秀的免费服务持续维护。💡 可以用这些免费域名做什么?你可以搭建:🚀 个人博客🚀 在线工具网站🚀 开源项目主页🚀 API 接口🚀 Demo 演示站🚀 学习测试环境搭配 Cloudflare、Vercel、Netlify 等免费平台,可以实现低成本甚至零成本建站。⚡ 总结Stackryze Domains 是一个非常适合开发者收藏的免费域名项目:✅ 免费领取 4 个域名✅ 一年有效期✅ 提前 60 天免费续约✅ 两个后缀支持 Cloudflare✅ 适合个人项目和测试使用如果你正好在折腾个人项目或者需要一个测试域名,不妨去试试。领取链接: 美国地址生成器:
2026年08月31日
0 阅读
0 评论
0 点赞
2026-08-28
Android App 脱壳方案完整报告(2025–2026)
Android App 脱壳方案完整报告(2025–2026)作者/整理:安全研究团队 更新时间:2026-08-28 适用场景:移动安全研究、漏洞挖掘、恶意样本分析、授权合规渗透测试一、背景概述1.1 加固现状指标数据Google Play TOP 1000 应用加固率≈ 98%企业级应用平均采用加固方案数2.8 种金融类 App 加固强度(对比普通应用)5 倍以上2025 年安卓应用被逆向破解案例占比68.3%SO 文件被提取分析比例45%1.2 主流加固厂商厂商产品特点腾讯乐固(Tencent Mobile Security)社交/游戏场景优化,字符串混淆,反调试多层防护360360 加固保免费 + 企业版,DEX/SO 加固,反模拟器梆梆安全梆梆加固企业级方案,VMP + Dex2C 深度混合网易网易易盾游戏与高对抗加固专精阿里阿里聚安全金融级安全防护,强反 Frida 及动态插桩爱加密爱加密中小开发者友好,生态覆盖广几维安全几维加固开源生态兼容性好,主打 Dex2C其他顶象、通付盾、启明星辰、天磊卫士等针对银行、政企等垂直行业定制加固二、加固壳分类与技术演进2.1 壳的代际分类代际名称原理脱壳难度一代壳DEX 整体加壳将完整 DEX 加密存储于 APK(如 assets/),启动时解密并动态加载★☆☆☆☆二代壳函数抽取壳(内存加载)DEX 不落地,函数体在内存中按需解密,方法入口初始指向空代码或桩代码★★★☆☆三代壳VMP 虚拟机保护将 Dalvik/ART 字节码转为自定义字节码,由私有 VM 解释器执行★★★★☆四代壳Dex2C / Java2C将 Java 方法直接编译为 Native C/C++ 代码,通过 JNI 注册调用★★★★★混合壳VMP + Dex2C + SO 加壳多层嵌套与多策略混合,核心方法 Dex2C,关键逻辑走 VMP★★★★★2.2 壳的通用运行流程flowchart TD A[APK 启动] --> B["壳 Application.attachBaseContext() 触发"] B --> C["加载壳 Native SO (libshell.so / libprotect.so)"] C --> D["执行反调试/环境自检并解密 DEX 或函数体"] D --> E["通过 DexClassLoader / InMemoryDexClassLoader 加载目标类"] E --> F["替换 Application 上下文 / 修复 ClassLoader"] F --> G[执行原始业务代码逻辑]三、脱壳方案总览(按技术路线分类)方案一:免 Root 自动脱壳工具工具原理适用场景限制BlackDex利用 DexFile Cookie 机制,在虚拟容器内加载目标 APK 并 Dump 内存中的 DEX一代壳 / 部分二代壳支持 Android 5.0–12;对强反调试壳无效DumpDex利用多进程加载机制 Dump DEX一代壳兼容性一般VirtualXposed + 脱壳插件在免 Root 虚拟环境中运行目标 App + Xposed 模块 Hook一代 / 二代壳依赖 VirtualXposed 环境稳定性分身框架(团团分身等)+ 遥光云模块通过分身虚拟化框架注入脱壳逻辑二代壳部分新版本机型不兼容操作流程(以 BlackDex 为例):安装 BlackDex APK。打开应用,选择目标应用(支持已安装或未安装的 APK 安装包)。点击 「脱壳」。等待数秒,生成脱壳 DEX 文件:输出路径: /sdcard/BlackDex/output/获取 classes.dex, classes2.dex ...使用 jadx-gui 或 JEB 打开验证反编译代码。方案二:Frida 动态插桩脱壳(主流方案)2.1 基础架构PC 端 (控制端) 手机端 (目标端) ┌───────────────────────┐ USB / WiFi ┌───────────────────────┐ │ • frida CLI │ <══════════════════> │ • frida-server │ │ • Python 控制脚本 │ (TCP:27042) │ (需 Root 权限) │ │ • 动态注入 JS 脚本 │ │ • 注入到目标 App 进程 │ └───────────────────────┘ └───────────────────────┘2.2 核心工具链工具用途frida (PC)客户端核心库,需与移动端 server 版本严格一致frida-server (手机)部署于 /data/local/tmp/,需 Root 运行frida-dexdump快速扫描内存段的 DEX 头部魔数(dex\n035 等),自动批量 DumpStrongR-Frida消除 Frida 特征字符串(如 frida:rpc 等),对抗商业壳检测hluda-server深度定制版 Frida Server,去除 libfrida-agent.so 模块名与特征符号frida-fartFrida 与 FART 方案结合,支持函数级主动调用与修复2.3 典型脱壳脚本(多阶段 Hook 示例)// 阶段 1:捕获 DexFile 构造加载 Java.perform(() => { const DexFile = Java.use("dalvik.system.DexFile"); DexFile.init.overload('java.lang.String', 'java.lang.String', 'int') .implementation = function(cookie, path) { console.log("[+] DexFile loaded: " + path); return this.init(cookie, path); }; }); // 阶段 2:Hook InMemoryDexClassLoader(二代内存不落地壳关键点) Java.perform(() => { const InMemoryDexClassLoader = Java.use("dalvik.system.InMemoryDexClassLoader"); InMemoryDexClassLoader.init.overload('java.nio.ByteBuffer', 'java.lang.ClassLoader') .implementation = function(buf, parent) { console.log("[+] InMemoryDex size: " + buf.remaining()); // 可在此将 buf 二进制内容直接写出为 dump.dex 文件 return this.init(buf, parent); }; }); // 阶段 3:遍历内存只读段搜索 DEX 魔数 (dex\n035) Process.enumerateRanges('r--').forEach(range => { try { const buf = range.base.readByteArray(4); if (buf && new Uint8Array(buf).join(',') === '100,101,120,10') { // "dex\n" console.log("[Found DEX] Base: " + range.base + ", Size: " + range.size); } } catch(e) {} });2.4 反检测与对抗策略检测手段对抗方式扫描 /proc/self/maps 中的 frida-agent使用 hluda / StrongR-Frida 魔改版,消除 agent 路径与名称检测默认 27042 端口启动时更换监听端口:frida-server -l 0.0.0.0:8888检测线程名 gum-js-loop修改 Frida 源码重新编译线程命名,或使用 Magisk 模块掩盖扫描 /data/local/tmp/frida-server 文件更改二进制文件名与存放目录,随机命名主动调用 ptrace(PTRACE_TRACEME)使用 Zygisk / Native Hook 拦截 ptrace 系统调用时间差检测(检测断点导致的时间漂移)Hook clock_gettime / gettimeofday 实现时间平滑方案三:FART(ART 环境主动调用脱壳)3.1 原理FART (Frida Android Repackaging Tool) 通过修改 AOSP 系统的 ART 运行时源码,在 ART 虚拟机执行方法或加载类时,主动遍历并触发所有方法的调用执行,诱使抽取壳将函数体解密回填到 CodeItem 中,再将完整的 DEX 内存写出到磁盘。3.2 适用场景二代壳(函数抽取/代码填空型):主流解决方案,效果极佳。360 加固、腾讯乐固、爱加密等主流企业抽取壳。需 Root 环境,推荐配合定制 ROM(Pixel 3/4/6 系列)或 Magisk/KernelSU 模块。3.3 操作流程刷入基于 AOSP (Android 9/10/11) 编译的 FART 定制 ROM,或挂载 FART 模块。安装并启动目标 App。执行 fart 脱壳脚本:adb shell su cd /data/fart/ chmod +x fart.sh ./fart.sh <target_package_name>提取脱壳数据:输出目录: /sdcard/fart_output/获取修复后的 *_dex_fix.dex。使用 010 Editor 检查 DEX Header,使用 jadx 验证函数体是否还原。3.4 Native 版 FART (frida-fart)无需刷写整机 ROM,通过 Frida 脚本直接在 Native 层 Hook art::ArtMethod::Invoke,遍历 Class 列表并强行调用以触发解密,将解密后的内存 Dump 回写。方案四:内核态方案(2025–2026 演进趋势)4.1 KernelSU / APatch + 脱壳模块组件作用KernelSU基于 Linux 内核的 Root 方案,用户态完全无 su 二进制与环境特征APatch类似 KernelSU,通过内核补丁提供安全挂载与 Root 权限ZygiskNextZygisk 独立实现,配合 KernelSU 在 fork 子进程阶段完成无感注入脱壳模块以 Zygisk 插件形式运行,在 App 初始化前 Hook 关键函数核心优势:用户态无 /proc/self/maps、/data/local/tmp 挂载痕迹。不占用 ptrace,规避多进程互相 ptrace 保护。完美过掉绝大多数商业壳的用户态反调试。4.2 eBPF 方案架构内核态 (Kernel Space) 用户态 (User Space) ┌─────────────────────────────────┐ ┌─────────────────────────────────┐ │ eBPF 探针程序 │ │ 用户态收集服务 │ │ • kprobe/sys_enter_mmap │ ────────> │ • 监听 ring buffer 事件 │ │ • kprobe/sys_enter_openat │ perf / │ • 捕获 DEX 映射基址与长度 │ │ • tracepoint/sys_enter_execve │ ring_buf │ • 读取进程内存并写入磁盘 │ │ • tracepoint/sys_enter_write │ │ • 自动完成 DEX/SO Dump │ └─────────────────────────────────┘ └─────────────────────────────────┘适用场景:具有超强反调试能力的加固壳(任何用户态 Hook 均会被检测崩溃)。无感知的取证分析与行为链路监控。捕捉加固壳动态生成或解密写入临时文件的精确瞬间。方案五:针对 VMP / Dex2C 的专项方案5.1 VMP(虚拟机指令级保护)方法说明指令级 Trace使用 Frida Stalker 或 Unicorn Engine 逐条记录 VM 解释器的执行流程Unidbg 模拟执行在 PC 端模拟 Android ARM JNI 运行环境,直接黑盒调用被保护的 Native 函数IDA Pro 逆向分析逆向分析 VM 分发器(Dispatcher Handler 表),还原 Opcode 映射逻辑AI 辅助语义还原利用大语言模型与符号执行工具,自动化还原混淆后的控制流图(CFG)5.2 Dex2C / Java2C方法说明IDA Pro + Hex-Rays逆向分析编译生成的 Native SO 文件,分析 JNI_OnLoad 与动态方法注册Unidbg对转换后的 C 语言实现进行脱离手机环境的模拟调用Frida Hook JNI 注册Hook RegisterNatives,截获 Java 方法名与其对应的 Native 函数内存地址QEMU 全系统模拟全系统仿真执行,配合动态污点分析提取核心算法方案六:SO 加壳与内存 Dump工具 / 方法适用场景与操作Frida + dlopen Hook在壳调用 dlopen / dlsym 解密加载真实 SO 后,暂停进程并执行 Dump/proc/pid/maps + dd读取目标 SO 内存映射段,通过 dd 命令直接复制出内存镜像IDA 内存挂钩与快照在真实入口点下断点,导出内存快照并对比静态加密文件FEDD (Fuck ELF Dex Dump)自动化扫描并提取内存中 ELF 结构体SoFixer针对 Dump 出的 SO 修复 Program Header、Section 表及重定位信息四、工具对照表(按壳类型选择)壳类型推荐首选方案备选方案预计脱壳成功率一代壳(整体加密)BlackDex / frida-dexdump基础内存 Dump 工具> 95%二代壳(函数抽取)FART / frida-fartFrida 主动遍历调用 + 补丁修复80% – 90%三代壳(VMP 虚拟机)Frida Stalker + Unidbg 模拟执行IDA 手工逆向 + AI 辅助还原30% – 60%四代壳(Dex2C/Java2C)Unidbg + IDA 反编译QEMU 全系统模拟 / 污点分析20% – 40%多层混合壳组合分阶段分析方案无单一通用工具,需视架构定制视具体方案而定强反调试企业壳KernelSU + eBPF / 魔改 Frida定制内核 ROM + 硬件断点调试70% – 85%五、实验环境搭建推荐5.1 硬件与测试机选择方案类别推荐设备说明首选方案Google Pixel 6 / 7 / 8(刷入 KernelSU)AOSP 原生支持好,内核模块兼容性最佳次选方案一加 9 / 10 / 11(已解锁 Bootloader)驱动开放,第三方 ROM 和 Magisk 生态成熟模拟器雷电模拟器 9 / MuMu 模拟器 12仅适合弱壳与普通应用,主流商业壳易被环境检测免 Root 环境任意 Android 5.0–12 真机 + BlackDex仅适用于一代加固壳5.2 推荐软件与工具链配置安全分析工作台 ├── 权限框架: KernelSU / APatch(首选)/ Magisk(次选) ├── 模块框架: ZygiskNext + LSPosed ├── 动态插桩: Frida 16.x(hluda 定制去特征版) ├── 脱壳工具: FART / frida-dexdump / BlackDex ├── 静态反编译: jadx-gui / JEB 4.x / GDA ├── Native 逆向: IDA Pro 8.x + Hex-Rays / Ghidra ├── 模拟执行: Unidbg / Unicorn Engine ├── 流量抓包: mitmproxy / Burp Suite + Frida SSL Unpinning 脚本 └── 辅助分析: 010 Editor (DEX/ELF 模板) / MT 管理器 / adb 命令行六、实战分析标准流程(SOP)flowchart TD S1["Step 1: 侦察与资产识别<br/>• 解包查看 APK 结构与 assets 资源<br/>• MT 管理器 / PKID 查壳特征<br/>• jadx 尝试反编译初步定性"] --> S2["Step 2: 匹配脱壳方案<br/>• 一代整体壳 → BlackDex / frida-dexdump<br/>• 二代抽取壳 → FART / frida-fart<br/>• 三/四代 VMP/Dex2C → Unidbg + IDA"] S2 --> S3["Step 3: 运行环境准备<br/>• 配置 KernelSU / Magisk 隐身环境<br/>• 启动定制版 frida-server / FART ROM<br/>• 配置反反调试规则"] S3 --> S4["Step 4: 执行脱壳与 Dump<br/>• 启动目标 App 并触发关键页面<br/>• 运行动态 Hook 脚本抓取内存<br/>• 收集并归类 Dump 文件"] S4 --> S5["Step 5: 验证与文件修复<br/>• 010 Editor 校验 DEX Header 与 Magic<br/>• jadx 反编译排查是否存在空函数桩<br/>• 必要时借助 smali 工具回填 CodeItem"] S5 --> S6["Step 6: 深度业务逆向<br/>• 还原核心业务调用链<br/>• 加密算法定位与 RPC 调用<br/>• 输出安全评估与分析报告"]七、2025–2026 技术演进趋势趋势方向核心说明AI 辅助逆向分析借助大模型(LLM)理解反编译后的复杂代码,自动完成混淆变量重命名与控制流平坦化还原。eBPF 内核监控常态化借助内核层 eBPF 探针进行无感知内存捕捉,绕过所有应用层反调试手段。KernelSU 全面普及传统 Magisk 特征逐渐被商业壳精准识别,无痕内核 Root 成为逆向研究标配。云真机自动化脱壳集群结合云手机群控与自动化脚本,应对复杂设备指纹与风控对抗。RISC-V 指令集 VMP部分顶级加固厂商开始尝试将关键字节码编译为 RISC-V 指令在自定义解释器中执行,提升分析门槛。运行时代码多态自修改核心算法在每次执行时在内存中动态重构解密,执行完毕立即擦除,增加静态 Dump 难度。TEE / TrustZone 保护支付与风控级核心密钥运算下沉至安全硬件环境执行,阻断纯软件层内存提取。八、合规声明与法律提醒[!WARNING]⚠️ 重要合规与法律声明合法研究范围:本文所涉及的技术与工具仅可用于正当安全学术研究、软件漏洞挖掘、恶意样本分析、经授权的渗透测试与自有应用安全防护评估。严禁非法利用:严禁利用上述技术对未授权的商业软件实施逆向破解、盗版分发、外挂制作或侵犯他人知识产权。数据隐私合规:在逆向分析过程中如涉及个人敏感信息或未脱敏数据,应严格遵守国家网络安全及数据保护法规。隔离测试环境:针对未知来源或恶意软件样本,务必在沙箱或隔离网络环境中运行,避免样本恶意逃逸。九、开源项目与参考资源资源名称链接与说明BlackDexhttps://github.com/CodingGay/BlackDex - Android 免 Root 快速脱壳工具FARThttps://github.com/hanbinglengyue/FART - 基于 ART 运行时的主动调用脱壳框架Fridahttps://frida.re - 跨平台动态代码插桩与 Hook 框架frida-dexdumphttps://github.com/hluwa/frida-dexdump - 基于 Frida 的内存 DEX 快速扫描与导出工具Unidbghttps://github.com/zhkl0228/unidbg - 跨平台的 Android ARM JNI 模拟执行框架KernelSUhttps://github.com/tiann/KernelSU - 基于 Linux 内核的无感 Root 解决方案看雪安全论坛https://bbs.kanxue.com - 移动安全与加固逆向技术交流社区吾爱破解论坛https://52pojie.cn - 软件安全与逆向分析技术交流社区本文仅供技术研究与学习交流。
2026年08月28日
0 阅读
0 评论
0 点赞
2026-08-21
Redis 内存防崩溃优化方案
Redis 内存防崩溃优化方案目标:让 Redis 永远有硬上限,系统永远不进 Swap,事故提前告警而非事后重启。⚠️ 全文命令中的密码已替换为占位符 你的密码,执行时请换成真实密码;redis-cli 路径每台服务器不同,请先按【附一】查询替换。附一、先查询 redis-cli 路径(每台服务器不同,纯查询、不影响系统)下面都是只读查询命令,不会改动任何东西,放心执行:# 1. 看系统 PATH 里是否已能直接调用(最常用) which redis-cli # 2. 看正在运行的 redis-server 二进制在哪,redis-cli 通常在同目录的 bin 下 ps -ef | grep redis-server # 3. 常见安装目录直查(哪个有输出就用哪个) ls -l /usr/local/redis/bin/redis-cli 2>/dev/null ls -l /usr/bin/redis-cli 2>/dev/null ls -l /opt/redis/bin/redis-cli 2>/dev/null # 4. 全盘找(只读扫描,不影响系统;结果里取第一个存在的路径) find / -name redis-cli -type f 2>/dev/null | head查到后,把下文命令里的 /usr/local/redis/bin/redis-cli 整体换成你查到的实际路径即可。一、根因结论(一句话)Redis 没有有效的内存上限(或上限过大)→ 内存无限增长 → 物理内存耗尽 → Swap 打满(100%) → 页面置换风暴 → CPU 99% → 系统假死。典型崩溃时间线:Redis 吃满内存 → Swap 96% → Swap 100% → 触发事件,CPU 2%→42% → CPU 99% / 高负载 / 假死 → 人工重启。核心矛盾不是"Redis 用了多少",而是"它能不能无限用"。 只要给它一个硬上限并留足系统余量,同样的负载绝不会再把机器拖死。二、设计三原则#原则作用1给 Redis 设硬上限 maxmemory满则"驱逐旧数据",而非"撑爆系统内存"2系统永远留余量,绝不进 SwapSwap 一旦打满,Redis 延迟暴涨、CPU 被置换吞掉3合理监控水位,提前感知风险把事故消灭在苗头,而不是 100% 假死说明:原则 2 的"绝不进 Swap"是靠原则 1 把 Redis 框在合理上限内实现的(详见第三节),不需要去动系统内核参数。三、maxmemory 设置(最关键的一步)3.1 计算公式(务必满足)maxmemory ≤ (物理内存 × 0.85) − 其他进程常驻内存例:物理 31G,其他常驻进程(应用、数据库、Agent 等)合计约 6G。安全上限:31 × 0.85 − 6 ≈ 20G。即 Redis 最多给到 20G 也不会逼系统用 Swap。3.2 推荐值按"当前工作集 × 1.5"留余量防突增。若当前稳定 8G,则 maxmemory = 12G(12884901888 字节)。12G + 6G 其他 ≈ 18G,远小于 31G,系统永远有空闲 → 永不 Swap。3.3 设置命令(路径请先按【附一】查询替换)# 临时生效(立即止血) /usr/local/redis/bin/redis-cli -a '你的密码' CONFIG SET maxmemory 12884901888 # 验证 /usr/local/redis/bin/redis-cli -a '你的密码' CONFIG GET maxmemory3.4 ⚠️ 持久化(极易踩坑)若 redis 启动时没有带配置文件参数(如 redis-server 0.0.0.0:6379),它是用默认配置启动的,CONFIG REWRITE 可能新建文件但重启仍会丢配置。必须找到真正的启动入口并写进去(以下为只读查询,定位用):# 看是不是 systemd 托管 systemctl cat redis 2>/dev/null systemctl cat redis-server 2>/dev/null # 或看 init 脚本 / 启动脚本里有没有 redis-server ls -l /etc/init.d/ | grep -i redis grep -r "redis-server" /etc/rc.local /root/*.sh /home/*/*.sh 2>/dev/null找到后,在启动配置里显式加一行 maxmemory 12884901888(或指向一个含该项的 redis.conf),再重启验证 CONFIG GET maxmemory 是否持久生效。不要只依赖 CONFIG REWRITE。四、淘汰策略(maxmemory-policy)选择4.1 策略对比策略淘汰范围适用场景风险noeviction不淘汰,写报错不允许丢数据写阻塞,不可用allkeys-lru全部 key,按最近最少用纯缓存永久 key 也会被删allkeys-lfu全部 key,按最不常用纯缓存(更优)永久 key 也会被删volatile-lru/lfu仅有 TTL 的 key缓存+持久混放无 TTL 的 key 永不淘汰,可能涨爆volatile-ttl仅即将过期的 key同 volatile同上选型建议:纯缓存实例用 allkeys-lfu(比 LRU 更懂"常用",缓存命中更好);若缓存与不可重建的持久数据混放,应改用 volatile-lfu 并给缓存 key 补 TTL,或干脆把持久数据拆到独立实例。五、OOM 防护(可选兜底,保护关键进程)本节为"最后兜底",非必须;主防线仍是 maxmemory + 不进 Swap。以下只是调整 OOM 优先级(不改内核行为),风险低。万一再次发生内存争抢,确保关键业务进程(数据库 / 应用)不被 OOM killer 误杀,而让 Redis 优先被回收:# 降低关键进程的 OOM 被杀概率(数值越小越安全,-1000 几乎永不被杀) echo -500 > /proc/<关键进程_pid>/oom_score_adj # 提高 redis 被杀优先级(让它先被回收,而不是拖死整机) echo 500 > /proc/<redis_pid>/oom_score_adj # 换成实际 redis PID六、架构层面(长期根治,均为建议、非强制命令)缓存 / 持久数据分离:Redis 只放可重建的缓存;Session、锁、业务状态搬到其他存储或独立实例。服务器图形界面:生产服务器通常不需要图形界面,如确认无用,可禁用其自启动以省内存(不要盲目卸载,避免牵连依赖)。多实例隔离:若多种业务共用一台 Redis,按业务拆实例,单实例故障域更小、上限更好控。七、应急响应 Runbook(若再次假死)不要反复重启:重启会重新加载/重建数据,可能二次冲击。先确认 Swap 状态。快速查看(只读):free -h; swapon --show # 看 Swap 是否打满若 Redis 已无响应且内存失控:可 redis-cli SHUTDOWN NOSAVE(避免写坏 RDB),再按本方案设好 maxmemory 后拉起。拉起后第一时间 CONFIG GET maxmemory + INFO memory 验证上限已生效。八、落地检查清单[ ] 先按【附一】查到本机 redis-cli 真实路径,替换全文命令[ ] 确认缓存实例是否纯缓存(决定 allkeys-lfu 还是 volatile-lfu)[ ] CONFIG SET maxmemory 12884901888(12G,立即生效)[ ] 找到 redis 真实启动入口,把 maxmemory 写进配置/启动参数(持久化)[ ] 切换/确认 maxmemory-policy(纯缓存→allkeys-lfu)[ ] 配置 OOM 优先级,保护关键进程(可选)[ ] 验证:重启 redis 后 CONFIG GET maxmemory 仍为 12G附:命令安全提示命令行用 -a 明文密码会触发告警且密码进 history。建议改用环境变量(把 你的密码 换成真实密码):export REDISCLI_AUTH='你的密码' /usr/local/redis/bin/redis-cli CONFIG GET maxmemory附:检查脚本#!/bin/bash # redis_health_check.sh # 用途:巡检 Redis 内存水位 + 系统 Swap,超标即打印告警(可接邮件/钉钉) # 用法:crontab 每 5 分钟执行一次,输出追加入日志 # */5 * * * * /usr/local/redis/redis_health_check.sh >> /var/log/redis_health.log 2>&1 # ===== 配置区(按需修改)===== # 密码:执行前请换成真实密码(或改用 export REDISCLI_AUTH 方式,避免明文) PASS='你的密码' HOST=127.0.0.1 PORT=6379 # redis-cli 路径:每台服务器不一样,优先用 which 自动查找;找不到再手动改成实际路径 # 也可先手动查询: which redis-cli # ls -l /usr/local/redis/bin/redis-cli 2>/dev/null # ps -ef | grep redis-server # find / -name redis-cli -type f 2>/dev/null | head REDIS_CLI=$(command -v redis-cli 2>/dev/null) if [ -z "$REDIS_CLI" ]; then REDIS_CLI="/usr/local/redis/bin/redis-cli" # 自动找不到时,改成你查到的真实路径 fi TS=$(date '+%Y-%m-%d %H:%M:%S') echo "===== [$TS] Redis 健康巡检 =====" # ---- 1. Redis 内存使用率 ---- USED=$($REDIS_CLI -h $HOST -p $PORT -a "$PASS" INFO memory 2>/dev/null | awk -F: '/^used_memory:/{print $2}' | tr -d '\r') MAX=$($REDIS_CLI -h $HOST -p $PORT -a "$PASS" INFO memory 2>/dev/null | awk -F: '/^maxmemory:/{print $2}' | tr -d '\r') EVI=$($REDIS_CLI -h $HOST -p $PORT -a "$PASS" INFO stats 2>/dev/null | awk -F: '/^evicted_keys:/{print $2}' | tr -d '\r') if [ -n "$MAX" ] && [ "$MAX" -gt 0 ] 2>/dev/null; then RATIO=$(awk "BEGIN{printf \"%.2f\", $USED/$MAX*100}") echo "Redis 内存使用率: ${RATIO}% (used=$USED max=$MAX)" # 超过 85% 逼近 maxmemory,即将触发驱逐 if awk "BEGIN{exit !($RATIO>85)}"; then echo " [告警] 内存使用率大于85%,逼近 maxmemory,即将触发 key 驱逐!" fi else echo " [错误] maxmemory 未设置或为 0 —— Redis 可无限吃内存,存在崩溃风险!" fi echo "累计驱逐 key 数 (evicted_keys): ${EVI:-未知}" # ---- 2. 系统 Swap 使用率 ---- SWAP_TOTAL=$(free | awk '/Swap:/{print $2}') SWAP_USED=$(free | awk '/Swap:/{print $3}') if [ -n "$SWAP_TOTAL" ] && [ "$SWAP_TOTAL" -gt 0 ] 2>/dev/null; then SWAP_RATIO=$(awk "BEGIN{printf \"%.2f\", $SWAP_USED/$SWAP_TOTAL*100}") echo "系统 Swap 使用率: ${SWAP_RATIO}%" if awk "BEGIN{exit !($SWAP_RATIO>50)}"; then echo " [严重] Swap 使用率大于50%,系统已进入危险区,立即排查内存占用!" fi else echo "系统未启用 Swap(或未检测到)—— 无需关注 Swap 水位" fi echo ""
2026年08月21日
0 阅读
0 评论
0 点赞
2026-08-13
达梦数据库 dexp/dimp 实战指南:模式克隆、REMAP_SCHEMA 与特殊字符踩坑记录
达梦数据库 dexp/dimp 实战指南:模式克隆、REMAP_SCHEMA 与特殊字符踩坑记录在日常的数据库运维和开发测试中,我们经常需要对某个模式(Schema)进行完整备份,或者将其"克隆"一份用于测试环境。在达梦数据库(DMDB)中,dexp(逻辑导出)和 dimp(逻辑导入)是完成这项工作的利器。本文将结合实战经验,分享如何使用 dexp/dimp 进行模式导出与重命名导入(REMAP_SCHEMA),并记录一个由密码特殊字符引发的"导出 0 张表"的隐蔽 Bug。一、核心踩坑:密码中的 .. 导致导出 0 张表诡异的现象在使用 dexp 导出 V5_EAM 模式时,命令行执行后瞬间结束,日志中显示:[WARNING]invalid object:V5_EAM export total 0 TABLE schema[V5_EAM] export terminate.....查询 DBA_TABLES 确认该模式下明明有 218 张表,但导出结果却是 0。根因分析经过排查,发现该数据库 SYSDBA 用户的密码为 woshimima..(末尾包含连续的两个点 ..)。当在命令行直接拼接连接串时:# 错误示范 ./dexp USERID=SYSDBA/woshimima..@localhost:5236 SCHEMAS=V5_EAM ...连续点号 .. 在 Shell 解析或 dexp 内部参数解析时,被错误地识别为路径跳转或特殊指令,导致 dexp 内部验证 Schema 属主时发生异常,从而跳过了所有对象的导出。解决方案方案 A(推荐):使用交互式密码输入不在命令行中明文传递密码,执行命令后根据提示输入:./dexp USERID=SYSDBA@localhost:5236 SCHEMAS=V5_EAM FILE=... LOG=...方案 B:使用单引号包裹整个 USERID利用单引号屏蔽 Shell 的特殊字符解析:./dimp 'USERID=SYSDBA/woshimima..@localhost:5236' ...二、实战:导出模式 (dexp)确认连接方式后,执行完整的模式导出。建议加上 PARALLEL 参数提升导出速度。cd /data/dmdbms/bin/ ./dexp USERID=SYSDBA@localhost:5236 \ SCHEMAS=V5_EAM \ FILE=/data/dmexport/V5_EAM.dmp \ LOG=/data/dmexport/V5_EAM_export.log \ PARALLEL=4执行后按提示输入密码。看到 terminate export success without warning 即代表完美导出。三、实战:导入并重命名模式 (dimp + REMAP_SCHEMA)很多时候,我们导出的数据是为了在同一实例下建立一份测试副本。这时就需要用到 REMAP_SCHEMA 参数,将源模式的数据导入到目标模式中。⚠️ 核心注意点:目标模式必须提前创建dimp 的 REMAP_SCHEMA 不会自动创建目标模式!如果目标模式不存在,导入会直接报错。Step 1:创建目标模式/data/dmdbms/bin/disql SYSDBA@localhost:5236 -e "CREATE SCHEMA V5_EAM_TEST_0813;"Step 2:执行导入cd /data/dmdbms/bin/ ./dimp USERID=SYSDBA@localhost:5236 \ FILE=/data/dmexport/V5_EAM.dmp \ LOG=/data/dmexport/V5_EAM_TEST_0813.log \ REMAP_SCHEMA=V5_EAM:V5_EAM_TEST_0813 \ TABLE_PARALLEL=4参数解析:REMAP_SCHEMA=源模式:目标模式。此操作对源模式 V5_EAM 是只读的,绝对不会影响原数据。四、总结密码安全与兼容性:强烈建议使用交互式密码输入来调用 dexp/dimp,既能避免密码泄露在 history 中,又能完美规避特殊字符(如 @、..、# 等)导致的解析 Bug。REMAP_SCHEMA 前置条件:永远记得在导入前,先 CREATE SCHEMA 目标模式。数据完整性:导入完成后,务必通过 SELECT COUNT(*) FROM DBA_TABLES WHERE OWNER='目标模式'; 核对表数量,确保数据无损迁移。💻 第二部分:自动化交互式 Shell 脚本将以下代码保存为 dm_migrate.sh,赋予执行权限(chmod +x dm_migrate.sh)后运行。脚本包含了环境检查、交互式菜单、密码隐藏输入和自动创建 Schema 等完善功能。脚本修正说明(发布前已订正):原稿在 Markdown 渲染中丢失了部分语法符号,已在此处还原/修正:还原变量符号 ${VAR}(原稿误为 {VAR});还原颜色转义 \033(原稿误为 033);修正命令替换 $(...) 与退出码 $?;disql 检查/建模式语句改用 双引号包裹密码(user/"pass"@host:port),与文章主旨一致,规避 .. 等特殊字符被解析的坑。#!/bin/bash # ============================================================================== # 达梦数据库 dexp/dimp 交互式迁移脚本 # 作者: DBA 运维助手 # 描述: 支持模式导出、模式重命名导入(REMAP_SCHEMA),自动处理前置校验 # ============================================================================== # ================= 基础配置区 (可根据实际环境修改) ================= DM_BIN_DIR="/data/dmdbms/bin" # 达梦 bin 目录路径 EXPORT_BASE_DIR="/data/dmexport" # 导出文件默认存放目录 DEFAULT_HOST="localhost" # 默认数据库 IP DEFAULT_PORT="5236" # 默认数据库端口 DEFAULT_USER="SYSDBA" # 默认执行用户 # ===================================================================== # 颜色定义 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # 检查达梦工具是否存在 check_env() { if [ ! -f "${DM_BIN_DIR}/dexp" ] || [ ! -f "${DM_BIN_DIR}/dimp" ]; then echo -e "${RED}[ERROR] 未找到 dexp 或 dimp 工具,请检查 DM_BIN_DIR 配置: ${DM_BIN_DIR}${NC}" exit 1 fi mkdir -p "${EXPORT_BASE_DIR}" } # 获取数据库连接参数 get_db_conn() { read -p "请输入数据库 IP [默认 ${DEFAULT_HOST}]: " DB_HOST DB_HOST=${DB_HOST:-$DEFAULT_HOST} read -p "请输入数据库端口 [默认 ${DEFAULT_PORT}]: " DB_PORT DB_PORT=${DB_PORT:-$DEFAULT_PORT} read -p "请输入数据库用户名 [默认 ${DEFAULT_USER}]: " DB_USER DB_USER=${DB_USER:-$DEFAULT_USER} echo -n "请输入 ${DB_USER} 的密码 (输入时不可见): " read -s DB_PASS echo "" if [ -z "${DB_PASS}" ]; then echo -e "${RED}[ERROR] 密码不能为空!${NC}" exit 1 fi # dexp/dimp:用单引号包裹整个 USERID,防止密码中的特殊字符(如 .. @ #)被 Shell 解析 CONN_STR="'USERID=${DB_USER}/${DB_PASS}@${DB_HOST}:${DB_PORT}'" # disql:用双引号包裹密码(达梦语法 user/"pass"@host:port),同样规避特殊字符 DISQL_CONN="${DB_USER}/\"${DB_PASS}\"@${DB_HOST}:${DB_PORT}" } # 导出模式 do_export() { echo -e "\n${BLUE}========== 模式导出 (dexp) ==========${NC}" get_db_conn read -p "请输入要导出的源模式名 (如 V5_EAM): " SRC_SCHEMA if [ -z "${SRC_SCHEMA}" ]; then echo -e "${RED}[ERROR] 模式名不能为空!${NC}" return fi TIMESTAMP=$(date +%Y%m%d_%H%M%S) DMP_FILE="${EXPORT_BASE_DIR}/${SRC_SCHEMA}_${TIMESTAMP}.dmp" LOG_FILE="${EXPORT_BASE_DIR}/${SRC_SCHEMA}_${TIMESTAMP}_exp.log" echo -e "${YELLOW}[INFO] 开始导出 ${SRC_SCHEMA} 到 ${DMP_FILE} ...${NC}" # 使用 eval 执行带有单引号的连接串(去掉外层单引号后整体作为一个参数传给 dexp) eval "${DM_BIN_DIR}/dexp ${CONN_STR} SCHEMAS=${SRC_SCHEMA} FILE=${DMP_FILE} LOG=${LOG_FILE} PARALLEL=4" if [ $? -eq 0 ]; then echo -e "${GREEN}[SUCCESS] 导出完成!请检查日志: ${LOG_FILE}${NC}" else echo -e "${RED}[ERROR] 导出失败,请查看日志排查原因: ${LOG_FILE}${NC}" fi } # 导入模式 do_import() { echo -e "\n${BLUE}========== 模式导入 (dimp + REMAP) ==========${NC}" get_db_conn read -p "请输入 DMP 文件绝对路径: " DMP_FILE if [ ! -f "${DMP_FILE}" ]; then echo -e "${RED}[ERROR] 文件不存在: ${DMP_FILE}${NC}" return fi read -p "请输入 DMP 文件中的源模式名 (如 V5_EAM): " SRC_SCHEMA read -p "请输入要导入的目标模式名 (如 V5_EAM_TEST_0813): " DEST_SCHEMA if [ -z "${SRC_SCHEMA}" ] || [ -z "${DEST_SCHEMA}" ]; then echo -e "${RED}[ERROR] 模式名不能为空!${NC}" return fi # 检查目标模式是否存在,不存在则提示创建 echo -e "${YELLOW}[INFO] 检查目标模式 ${DEST_SCHEMA} 是否存在...${NC}" CHECK_SQL="SELECT COUNT(*) FROM DBA_USERS WHERE USERNAME='${DEST_SCHEMA}';" # 使用 disql 检查(密码用双引号包裹,规避特殊字符) USER_EXISTS=$(echo "${CHECK_SQL}" | ${DM_BIN_DIR}/disql ${DISQL_CONN} -e 2>/dev/null | grep -oE '[0-9]+' | head -1) if [ "${USER_EXISTS}" != "1" ]; then echo -e "${YELLOW}[WARNING] 目标模式 ${DEST_SCHEMA} 不存在!dimp 不会自动创建模式。${NC}" read -p "是否立即通过 SYSDBA 创建该模式? (y/n): " CREATE_CHOICE if [[ "${CREATE_CHOICE}" =~ ^[Yy] ]]; then ${DM_BIN_DIR}/disql ${DISQL_CONN} -e "CREATE SCHEMA ${DEST_SCHEMA};" if [ $? -eq 0 ]; then echo -e "${GREEN}[SUCCESS] 模式 ${DEST_SCHEMA} 创建成功!${NC}" else echo -e "${RED}[ERROR] 模式创建失败,请检查权限或名称是否合法。${NC}" return fi else echo -e "${RED}[INFO] 取消导入。请先手动创建目标模式。${NC}" return fi fi TIMESTAMP=$(date +%Y%m%d_%H%M%S) LOG_FILE="${EXPORT_BASE_DIR}/${DEST_SCHEMA}_${TIMESTAMP}_imp.log" echo -e "${YELLOW}[INFO] 开始导入 ${SRC_SCHEMA} -> ${DEST_SCHEMA} ...${NC}" eval "${DM_BIN_DIR}/dimp ${CONN_STR} FILE=${DMP_FILE} LOG=${LOG_FILE} REMAP_SCHEMA=${SRC_SCHEMA}:${DEST_SCHEMA} TABLE_PARALLEL=4" if [ $? -eq 0 ]; then echo -e "${GREEN}[SUCCESS] 导入完成!请检查日志: ${LOG_FILE}${NC}" else echo -e "${RED}[ERROR] 导入出现异常,请查看日志排查原因: ${LOG_FILE}${NC}" fi } # 主菜单 main_menu() { check_env while true; do echo -e "\n${GREEN}========================================${NC}" echo -e "${GREEN} 达梦数据库 dexp/dimp 迁移工具箱${NC}" echo -e "${GREEN}========================================${NC}" echo "1. 导出模式 (Export Schema)" echo "2. 导入模式 (Import & REMAP_SCHEMA)" echo "0. 退出" echo -e "${GREEN}----------------------------------------${NC}" read -p "请选择操作 [0-2]: " CHOICE case ${CHOICE} in 1) do_export ;; 2) do_import ;; 0) echo -e "${BLUE}再见!${NC}"; exit 0 ;; *) echo -e "${RED}[ERROR] 无效的选择,请重新输入。${NC}" ;; esac done } # 执行主菜单 main_menu💡 脚本使用指南创建脚本文件:vi /data/dm_migrate.sh粘贴上述代码并保存。赋予执行权限:chmod +x /data/dm_migrate.sh运行脚本:./dm_migrate.sh脚本亮点隐藏密码输入:使用 read -s,密码不会在屏幕上显示,也不会留在命令历史中。自动处理特殊字符:脚本内部自动对 dexp/dimp 用单引号包裹连接串('USERID=...')、对 disql 用双引号包裹密码(user/"pass"@host:port),完美解决你之前遇到的 .. 密码解析 Bug。智能校验:在选择导入时,脚本会自动调用 disql 检查目标模式是否存在。如果不存在,会主动询问并帮你创建,避免了 dimp 因找不到目标 Schema 而报错的问题。自动命名:导出文件和日志自动加上 年月日_时分秒 时间戳,防止覆盖历史备份。
2026年08月13日
20 阅读
0 评论
0 点赞
1
2
...
75