Cursor 编辑器界面 ┌────────────────────────────────────────────┐ │ 顶部菜单栏（File / 编辑 / 选择 / 查看...）│ ├────────────────────────────────────────────┤ │ 左侧侧边栏（文件树 / 项目 / Git 等） │ ├────────────────────────────────────────────┤ │ 中间主编辑区（代码编辑） │ ├────────────────────────────────────────────┤ │ 右侧 AI 面板（Cursor 智能助手 / Chat） │ ├────────────────────────────────────────────┤ │ 底部状态栏（分支 / 文件类型 / 行列信息等） │ └────────────────────────────────────────────┘ 如何使用 Cursor 的 AI 功能 功能 作用 快捷键（默认） 🧠 Inline Edit 选中一段代码 → Ctrl + K（或右键 “Ask Cursor”）→ 输入自然语言指令（如“优化正则表达式”） Ctrl + K 🤖 Agent 面板 打开一个“聊天窗口”，直接和 Cursor 对话（如“帮我写个登录验证函数”） Ctrl + I ✨ Tab 补全 在代码中输入时，Cursor 自动预测后续内容 Tab 🔁 重新生成建议 当灰色提示出现但不理想时，按 `Alt + ``（或右键 “Regenerate”） `Alt + `` 选择一段代码+CTRL+K 修改指定的代码，直接在代码编辑区弹出修改窗口调用大模型。CTRL+ENTER 或者鼠标点击 应用修改 CTRL+N 或者 **鼠标点击 **拒绝修改 CTRL+I 1）图中设置的Auto表示自动选择模型； 2）@ 可以添加文件、文件夹到当前对话上下文，如果你在代码编辑区选择一段代码再 CTRL+I 或者 CTRL+L，也会自动将它添加进来。还可以直接拖拽资源管理器中的文件到AI对话框中。 3）+Browser 模式可以让模型看到整个项目文件 任务 你可以直接问 Cursor 找函数定义 “verifyPassword 函数定义在哪个文件？” 查找引用 “verifyCode 被调用了多少次？” 理解架构 “帮我总结整个项目的模块结构。” 改代码 “把所有 JS 文件里的 var 改成 let。” 生成文档 “根据这个项目代码生成一份 README。” 4）对话模式 Agent模式，默认的对话模式，功能最全，AI 不仅能回答，还会“主动思考、规划和执行”。可以理解为：“帮我干活”的模式。 Plan 模式，它可以浏览整个项目文件，但不会直接改代码，而是会：把你的需求拆分成多个步骤；告诉你“下一步该干什么”；有时生成一份修改计划或脚本方案。 Ask 模式：问答模式，和网页版使用大模型一样。 TAB 一般注释起手，如果出现灰色预测文字时，直接 Tab 就能接受 AI 自动补全 ，按ESC可以拒绝这个建议；否则就是普通的缩进。 触发逻辑 传统 IDE（比如 VS Code、IntelliJ） 的补全是词法或语法级触发： 你输入 “pri” → 提示 “print()”； 它只是基于关键字匹配。 而 Cursor 的补全 是“语义级”的： AI 根据上下文 推断你下一步可能在做什么。 它会综合考虑： 当前光标前后的代码； 注释 / 函数签名； 文件名； 项目中的其他定义； 甚至最近你在这个 Tab 里做的事。 阶段 触发动作 AI 响应 检测阶段 监听输入变化（按键、换行、注释等） 分析上下文语义树（AST） 预测阶段 生成“意图向量” 模型推断用户下一步目标 展示阶段 在光标后显示灰色 ghost text 等待你按 Tab 接受 触发时机 类型 触发条件 示例 行为 函数触发 输入 function / def function check... 预测参数和函数体 控制语句触发 输入 if / for 等 if (phone) 预测条件或逻辑块 注释触发 输入 //、#、/** // 校验邮箱格式 按注释生成实现 模板触发 class / import / try class User 预测类体、导入内容 上下文模仿 类似已有代码块 第二个函数定义 模仿风格 文件结构触发 同名模块、utils utils. 补出函数名 空行预测 新行、缩进后 空一行 猜测下一个合逻辑语句 Index&Docs Codebase Indexing（代码索引）： Cursor 会扫描整个项目文件夹； 提取文件路径、函数定义、类名、依赖关系； 生成语义向量（embeddings）并存储（只保存索引，不上传源代码）。 界面说明：索引完成度100%；已被索引的文件数量为 78； Index New Folders： 当你创建或添加新文件夹时，Cursor 是否自动索引它。 Ignore Files in .cursorignore： 告诉 Cursor 哪些文件不要被索引。类似.gitignore Docs（文档索引）： 可以手动添加官方文档、API 文档、开发指南，让它当作参考知识。 添加一个 README.md 或内部 API 规范； 或添加外部文档（如一个 SDK 的文档网站）； 之后在 Chat 里用 @doc 引用 如何撤销修改 在AI编辑窗口提问，应用修改，点击Accept 应用完之后，若想回退版本，可以点击提问右边的回撤箭头。 整体思路（重要） 1）先打开项目，然后用browser模式，让AI对这个项目生成一个 .cursorignore 文件，自动忽略哪些文件无需添加进上下文。 2）打开设置，Index&docs，重新索引一下。 3）把项目相关的需求文档、接口文档、技术文档，全部录入到docs中，作为知识库以作备用。这与项目代码文件夹解耦的。 4）在项目根目录下创建 .cursorrules 文件，不需要 @ 引用，自动生效。 .cursorrules 就是 Cursor 的 AI 指令与代码风格规则文件，用来告诉 AI：在这个项目里写代码、命名、注释、格式化时都按照这些规则来做。是一个 系统级提示词 。 eg: ai_behavior: - 所有回答必须使用中文 - 代码中添加必要的注释 style: indentation: 4 spaces naming_convention: variables: camelCase AI 背后接收到的系统指令：你是一个 Java 开发助手。 遵循以下项目规则： 所有回答必须使用中文。 代码中添加必要的注释。 使用 4 空格缩进。 变量名使用 camelCase。 用户请求： 「写一个计算阶乘的 Java 函数」。
Kubernetes (K8S) Kubernetes 是一个开源的容器编排平台，用于自动化部署、扩缩容（Pod 数量多了/少了自动调整）和自愈（坏掉的 Pod 会被自动重建） 基本架构 一个 Kubernetes 集群 (Cluster) = 控制平面 + 工作节点 的组合。要使用K8S，要先搭建集群，deployment.yaml 和 service.yaml 要提交到 K8S 集群，由它调度和运行。 控制平面 (Control Plane) 负责管理整个集群，不直接跑应用。 组件 作用 比喻 API Server 接收命令（kubectl）、提供统一接口 前台服务员 etcd 存储集群所有状态（配置、Pod 列表等） 账本/数据库 Scheduler 决定 Pod 跑在哪个 Node 上 领班 Controller Manager 保证实际状态符合预期（补 Pod、重建失败的 Pod） 经理 工作节点 (Worker Nodes) 实际跑应用容器。 组件 作用 Kubelet 负责跟 API Server 沟通，并启动/管理容器 容器运行时 运行容器（Docker、containerd） Kube-Proxy 管理网络规则，实现 Service 的负载均衡 graph TD A[控制平面] --> B[Node 1] A --> C[Node 2] A --> D[Node 3] B --> E[Pod] B --> F[Pod] C --> G[Pod] D --> H[Pod] K8S 核心概念 Pod 最小运行单元，一个 Pod 里可以有 1 个或多个容器。 共享网络和存储空间 Deployment 管 Pod 的「副本数」和「版本」。 支持滚动更新、回滚 Service 提供稳定的网络入口（因为 Pod IP 会变）。 负载均衡到一组 Pod。 ConfigMap & Secret ConfigMap：存储配置信息（非敏感）。 Secret：存储敏感信息（密码、Token），会加密。 基本使用方式 Deployment（定义 Pod 模板 + 副本数） apiVersion: apps/v1 kind: Deployment metadata: name: my-app spec: replicas: 3 # 要 3 个 Pod selector: matchLabels: app: my-app template: # Pod 模板 metadata: labels: app: my-app #给 Pod 打标签，Service 用 selector 找到这些 Pod 做负载均衡 spec: containers: - name: my-app #Pod名 image: my-registry/my-app:v1 # ← 指向 CI 推送后的镜像（仓库/名称:标签） ports: - containerPort: 8080 #容器对外提供服务的端口 Pod名可能是：my-app-6f7c9d7c5d-abcde，它里面的容器名：my-app 映射关系： kind: Deployment → 创建一个「Pod 管理器」 replicas: 3 → 保证始终有 3 个 Pod template → Pod 的样子（运行哪个镜像、开哪个端口） Service（提供统一访问入口） apiVersion: v1 kind: Service metadata: name: my-service spec: selector: app: my-app # 找到所有带 app=my-app 标签的 Pod ports: - port: 80 # Service 入口端口 targetPort: 8080 # Pod 里容器的端口 type: LoadBalancer # 暴露给外部访问 映射关系： selector → 绑定到 Deployment 创建的 Pod，根据 Pod 的标签（my-app） ports → 用户访问 80 端口，会转发到 Pod 的 8080 type: LoadBalancer → 提供一个外部 IP 供用户访问 典型工作流程 sequenceDiagram 开发者->>Git: 提交代码 Git->>CI/CD: 触发构建 CI/CD->>镜像仓库: 构建Docker镜像 CI/CD->>K8S: kubectl apply K8S->>K8S: 创建/更新Pod K8S->>K8S: 健康检查 K8S->>用户: 服务可用 sequenceDiagram 开发者->>API Server: kubectl apply -f xxx.yaml API Server->>etcd: 写入期望状态 API Server->>Scheduler: 请求调度 Scheduler->>Node: 分配 Pod 到合适的节点 Node->>Kubelet: 启动容器 Controller Manager->>Cluster: 监控状态并修复 Service->>用户: 提供统一访问入口 👉 过程总结： 提交 YAML → API Server 接收并存到 etcd。 Scheduler 决定 Pod 去哪个 Node。 Node 上的 Kubelet 把容器启动。 Controller Manager 盯着数量，缺了就补。 Service 统一出口，用户不用关心 Pod IP。 常用操作 前提是1：集群必须已经搭好（minikube/kind/k3s/云厂商的 K8S）。 2：kubectl 已经配置好 kubeconfig 3：镜像能拉取到 如果是公有镜像（比如 Docker Hub），直接写就行。 如果是私有仓库，需要配 imagePullSecrets。 # 部署应用 kubectl apply -f deployment.yaml kubectl apply -f service.yaml # 查看集群资源 kubectl get pods -o wide kubectl get deployments kubectl get services # 扩缩容 kubectl scale deployment/my-app --replicas=5 # 滚动更新 kubectl set image deployment/my-app my-app=my-registry/my-app:v2 # 回滚 kubectl rollout undo deployment/my-app
CI/CD 自动部署项目 使用Gitea Actions 1）启用Gitea Actions： 编辑你的Gitea服务器配置文件 app.ini（通常位于 /etc/gitea/或 Gitea 安装目录），添加： [actions] ENABLED = true 然后重启Gitea服务。 2）在部署服务器上安装Runner： # 1. 下载并安装 act_runner (需要sudo) sudo wget https://gitea.com/gitea/act_runner/releases/download/v0.2.10/act_runner-0.2.10-linux-amd64 -O /usr/local/bin/act_runner sudo chmod +x /usr/local/bin/act_runner # 2. 为Runner创建专用工作目录（推荐使用绝对路径） sudo mkdir -p /opt/gitea-runner sudo chown $USER:$USER /opt/gitea-runner # 将目录所有权给当前用户 cd /opt/gitea-runner # 3. 注册Runner（重要：必须添加labels标签） act_runner register --no-interactive \ --instance https://你的gitea域名.com \ --token <你的令牌> \ --name my-runner \ --labels self-hosted,linux # 这个标签必须与工作流文件中的runs-on匹配 # 4. 启动Runner（后台运行） nohup act_runner daemon > runner.log 2>&1 & # 5. 查看运行状态和日志 tail -f runner.log ps aux | grep act_runner 3）令牌获取： 如果你想给整个实例用 → 站点管理员设置 → Actions → Runners。 如果只想给某个仓库用 → 进入该仓库 → Settings → Actions → Runners。 点击 New Runner，系统会生成一个 TOKEN。 例如：abcdef1234567890 还是设置整个实例好！！ 4）将act_runner注册为服务自启动 sudo vim /etc/systemd/system/act_runner.service 写入以下内容： [Unit] Description=Gitea Actions Runner After=network.target [Service] ExecStart=/usr/local/bin/act_runner daemon WorkingDirectory=/opt/gitea-runner User=zy123 Restart=always RestartSec=5 [Install] WantedBy=multi-user.target ExecStart 指向你安装的 act_runner 路径（你已经放在 /usr/local/bin/ 了）。 WorkingDirectory 可以写你打算存放 Runner 缓存的目录（比如 /home/zy123/act_runner，你已经在这里 register 过了）。 User=zy123 保证 Runner 以你当前用户运行，而不是 root。 重新加载 systemd 配置： sudo systemctl daemon-reexec 启动并开机自启: sudo systemctl enable --now act_runner 查看运行状态: systemctl status act_runner 5）在后端仓库根目录下，新建目录和文件： .gitea/workflows/deploy.yml 写入以下内容： name: Deploy Backend on: push: branches: [ "master" ] # master 分支推送时触发 jobs: build-and-deploy: runs-on: self-hosted steps: - name: Checkout code uses: actions/checkout@v3 - name: Create application-local.yml run: | mkdir -p src/main/resources/ cat << 'EOF' > src/main/resources/application-local.yml ${{ secrets.APPLICATION_LOCAL_YML }} EOF - name: Build and restart smile-picture run: | cd docs/tags/picture-v1.0 docker compose -f docker-compose-app-v1.0.yml build smile-picture-backend docker compose -f docker-compose-app-v1.0.yml up -d smile-picture-backend - name: Cleanup dangling images run: docker image prune -f 特别注意,application-local没有用git管理，把它存在了Actions的密钥中： 然后deploy.yml中可以把里面的内容复制过去。 6）工作原理 1.Runner 接收任务 当你 push 到 main 分支时： Gitea 会触发 workflow（.gitea/workflows/deploy.yml）。 服务器上的 act_runner 会接收到这个任务，并开始执行 jobs.build-and-deploy。 2.actions/checkout@v3 这一行的作用是：它会把你刚刚 push 的最新代码 完整克隆 到 Runner 的工作目录。 /home/<你的用户>/actions-runner/_work/<repo名>/<repo名>/ 所以，这时候你的项目在 Runner 的临时目录里已经是最新代码了，不需要 git pull。 3.后续 cd docs/tag/group-buy-v3.0 都是在 这个临时目录 里执行的。 相当于你在服务器上的临时副本中： 用 docker compose build 构建镜像 用 docker compose up -d 启动/更新容器 4.Runner 每次执行 workflow 时，工作目录都会 重新 checkout 最新代码。 默认不会自动清理旧目录，但可以在 workflow 里加 actions/checkout 的参数来控制，比如 clean: true。. 7）git push之后
mermaid画图 graph TD A[多智能体随机网络结构分析] --> B[多智能体协同学习与推理] A --> A1["谱参数实时估算"] A1 --> A11["卡尔曼滤波"] A1 --> A12["矩阵扰动理论"] A1 --> A13["输出：谱参数"] A --> A2["网络拓扑重构"] A2 --> A21["低秩分解重构"] A2 --> A22["聚类量化"] A2 --> A23["输出：邻接矩阵、特征矩阵"] graph TD B[多智能体协同学习与推理] B --> B1["联邦学习、强化学习"] B1 --> B11["谱驱动学习率调整"] B1 --> B12["自适应节点选择策略"] B --> B2["动态图神经网络"] B2 --> B21["动态图卷积设计"] B2 --> B22["一致性推理"] graph TD %% 颜色和样式定义 classDef startEnd fill:#e6ffe6,stroke:#333,stroke-width:2px classDef operation fill:#fff,stroke:#000,stroke-width:1px classDef decision fill:#ffcccc,stroke:#000,stroke-width:1px classDef update fill:#ccffcc,stroke:#000,stroke-width:1px %% 节点定义（严格按图片顺序） A([开始]):::startEnd B[交易信息\n外部订单号]:::operation C{判断是否为锁单订单}:::decision D[查询拼团组队信息]:::operation E[更新订单详情\n状态为交易完成]:::update F[更新拼团组队进度]:::update G{拼团组队完结\n目标量判断}:::decision H[写入回调任务表]:::operation I([结束]):::startEnd %% 流程连接（完全还原图片走向） A --> B B --> C C -->|是| D D --> E E --> F F --> G G -->|是| H H --> I C -->|否| I G -->|否| I %% 保持原图连接线样式 linkStyle 0,1,2,3,4,5,6,7,8 stroke-width:1px graph TD A[用户发起退单请求] --> B{检查拼团状态} B -->|拼团未完成| C1[场景1:拼团中退单] C1 --> D1{是否已支付?} D1 -->|未支付| E1[取消订单] E1 --> F1[更新订单状态为2] F1 --> G1[通知拼团失败] G1 --> H1[退单完成] D1 -->|已支付| I1[发起退款] I1 --> F1 B -->|拼团已完成| C2[场景2:完成后退单] C2 --> D2{是否超时限?} D2 -->|未超时| E2[发起退款] E2 --> F2[更新订单状态] F2 --> H1 D2 -->|超时| G2[退单失败] style A fill:#f9f,stroke:#333 style B fill:#66f,stroke:#333 style C1 fill:#fbb,stroke:#f66 style C2 fill:#9f9,stroke:#090 flowchart LR %% ===================== 左侧：模板模式块 ===================== subgraph Template["设计模式 - 模板"] direction TB SM["StrategyMapper 策略映射器"] SH["StrategyHandler 策略处理器"] ASR["AbstractStrategyRouter<T, D, R> 策略路由抽象类"] SM -->|实现| ASR SH -->|实现| ASR end %% ===================== 右侧：策略工厂与支持类 ===================== DASFactory["DefaultActivityStrategyFactory 默认的拼团活动策略工厂"] AGMS["AbstractGroupBuyMarketSupport 功能服务支撑类"] DASFactory --> AGMS AGMS -->|继承| ASR %% ===================== 业务节点链路 ===================== Root["RootNode 根节点"] Switch["SwitchRoot 开关节点"] Market["MarketNode 营销节点"] End["EndNode 结尾节点"] Other["其他节点"] AGMS --> Root Root --> Switch Switch --> Market Market --> End Switch -.-> Other Other --> End %% ===================== 样式（可选） ===================== classDef green fill:#DFF4E3,stroke:#3B7A57,stroke-width:1px; classDef red fill:#E74C3C,color:#fff,stroke:#B03A2E; classDef purple fill:#7E60A2,color:#fff,stroke:#4B3B6B; classDef blue fill:#3DA9F5,color:#fff,stroke:#1B6AA5; class SM,SH,Root,Switch,Market,End,Other green; class DASFactory red; class AGMS purple; class ASR blue; style Template stroke-dasharray: 5 5; sequenceDiagram participant A as 启动时 participant B as BeanPostProcessor participant C as 管理后台 participant D as Redis Pub/Sub participant E as RTopic listener participant F as Bean 字段热更新 A->>B: 扫描 @DCCValue 标注的字段 B->>B: 写入默认值 / 读取 Redis B->>B: 注入字段值 B->>B: 缓存 key→Bean 映射 A->>A: Bean 初始化完成 C->>D: publish("myKey,newVal") D->>E: 订阅频道 "dcc_update" E->>E: 收到消息，更新 Redis E->>E: 从 Map 找到 Bean E->>E: 反射注入新值到字段 E->>F: Bean 字段热更新完成 sequenceDiagram participant A as 后台/系统 participant B as Redis Pub/Sub participant C as DCC监听器 participant D as Redis数据库 participant E as 反射更新字段 participant F as Bean实例 A->>B: 发布消息 ("cutRange:50") B->>D: 将消息 "cutRange:50" 写入 Redis B->>C: 触发订阅者接收消息 C->>D: 更新 Redis 中的 "cutRange" 配置值 C->>F: 根据映射找到对应的 Bean C->>E: 通过反射更新 Bean 中的字段 E->>C: 更新成功，字段值被同步 C->>A: 配置变更更新完成 flowchart LR A[请求进入链头 Head] --> B[节点1: 日志LogLink] B -->|继续| C[节点2: 权限AuthLink] B -->|直接返回/终止| R1[返回结果] C -->|通过→继续| D[节点3: 审批ApproveLink] C -->|不通过→终止| R2[返回失败结果] D --> R3[返回成功结果] classDef node fill:#eef,stroke:#669; classDef ret fill:#efe,stroke:#393; class A,B,C,D node; class R1,R2,R3 ret; flowchart LR subgraph mall["小型支付商城"] style mall fill:#ffffff,stroke:#333,stroke-width:2 A[AliPayController 发起退单申请]:::blue C[订单状态扭转 退单中]:::grey E[RefundSuccessTopicListener 接收MQ消息 执行退款和订单状态变更]:::green end subgraph pdd["拼团系统"] style pdd fill:#ffffff,stroke:#333,stroke-width:2 B[MarketTradeController 接收退单申请]:::yellow D[TradeRefundOrderService 退单策略处理]:::red F[TradeRepository 发送MQ消息]:::purple G([MQ消息队列 退单成功消息]):::orange H[RefundSuccessTopicListener 接收MQ消息 恢复库存]:::green end A -- "1. 发起退单请求" --> B B -- "2. 处理退单" --> D D -- "3. 发送MQ消息" --> F F -- "4. 发布消息 (异步+本地消息表补偿)" --> G F -- "5. 返回结果" --> C G -- "6. 消费消息 (恢复库存)" --> H G -. "7. 消费消息 (执行退款)" .-> E classDef blue fill:#dbe9ff,stroke:#6fa1ff,stroke-width:1; classDef grey fill:#e5e5e5,stroke:#9e9e9e,stroke-width:1; classDef green fill:#d6f2d6,stroke:#76b076,stroke-width:1; classDef yellow fill:#fef3cd,stroke:#f5c700,stroke-width:1; classDef red fill:#f8d7da,stroke:#e55353,stroke-width:1; classDef purple fill:#e4dbf9,stroke:#9370db,stroke-width:1; classDef orange fill:#ffecca,stroke:#ffa500,stroke-width:1; sequenceDiagram participant Client as 前端 participant WS as WebSocket 服务器 participant Auth as 权限校验 participant Dispatcher as 消息分发器 participant Handler as 消息处理器 Client->>WS: 请求建立 WebSocket 连接 WS->>Auth: 校验用户权限 Auth-->>WS: 校验通过，保存用户和图片信息 WS-->>Client: 连接成功 Client->>WS: 发送消息（包含消息类型） WS->>Dispatcher: 根据消息类型分发 Dispatcher->>Handler: 执行对应的消息处理逻辑 Handler-->>Dispatcher: 返回处理结果 Dispatcher-->>WS: 返回处理结果 WS-->>Client: 返回处理结果给客户端 Client->>WS: 断开连接 WS-->>Client: 删除 WebSocket 会话，释放资源 sequenceDiagram participant Client as Client(浏览器) participant WS as WebSocket Endpoint participant Producer as PictureEditEventProducer participant RB as RingBuffer participant Worker as PictureEditEventWorkHandler participant Handler as PictureEditHandler Client->>WS: 发送 PictureEditRequestMessage WS->>Producer: publishEvent(msg, session, user, pictureId) Producer->>RB: next() 获取序号，写入事件字段 Producer->>RB: publish(sequence) 发布 RB-->>Worker: 回调 onEvent(event) Worker->>Worker: 解析 type -> PictureEditMessageTypeEnum alt ENTER_EDIT Worker->>Handler: handleEnterEditMessage(...) else EXIT_EDIT Worker->>Handler: handleExitEditMessage(...) else EDIT_ACTION Worker->>Handler: handleEditActionMessage(...) else 其他/异常 Worker->>WS: sendMessage(ERROR 响应) end Worker-->>Client: 业务处理后的响应（通过 WS） sequenceDiagram participant Client as WebSocket Client participant IO as WebSocket I/O线程 participant Biz as 业务逻辑(耗时) Client->>IO: 收到消息事件(onMessage) IO->>Biz: 执行业务逻辑(耗时3s) Biz-->>IO: 返回结果 IO->>Client: 发送响应 Note over IO: I/O线程被业务阻塞3s 不能处理其他连接的消息 sequenceDiagram participant Client as WebSocket Client participant IO as WebSocket I/O线程 participant Disruptor as RingBuffer队列 participant Worker as Disruptor消费者线程 participant Biz as 业务逻辑(耗时) Client->>IO: 收到消息事件(onMessage) IO->>Disruptor: 发布事件(快速) Disruptor-->>IO: 立即返回 IO->>Client: (继续处理其他连接消息) Worker->>Biz: 异步执行业务逻辑(耗时3s) Biz-->>Worker: 返回结果 Worker->>Client: 通过WebSocket发送响应 flowchart TD A[客户端发起WebSocket连接] --> B[HTTP握手阶段] B --> C[WsHandshakeInterceptor.beforeHandshake] C -->|校验失败| D[拒绝握手 连接关闭] C -->|校验成功| E[建立WebSocket连接] E --> F[PictureEditHandler] F --> G[处理WebSocket消息 收发数据] flowchart TD A([接收请求]) --> B{查询本地缓存 Caffeine} B -- 命中 --> C[返回本地缓存数据] C --> End1(((结束))) B -- 未命中 --> D{查询分布式缓存 Redis} D -- 命中 --> E[更新本地缓存] E --> F[返回 Redis 缓存数据] F --> End2(((结束))) D -- 未命中 --> G[查询数据库] G --> H[更新本地缓存和 Redis 缓存] H --> I[返回数据库数据] I --> End3(((结束))) classDiagram class ImageSearchApiFacade { +searchImage(localImagePath) } class GetImagePageUrlApi { +getImagePageUrl(localImagePath) } class GetImageFirstUrlApi { +getImageFirstUrl(imagePageUrl) } class GetImageListApi { +getImageList(imageFirstUrl) } ImageSearchApiFacade --> GetImagePageUrlApi : Calls ImageSearchApiFacade --> GetImageFirstUrlApi : Calls ImageSearchApiFacade --> GetImageListApi : Calls erDiagram 用户 { BIGINT 用户ID VARCHAR 用户名 } 角色 { BIGINT 角色ID VARCHAR 角色名称 VARCHAR 描述 } 权限 { BIGINT 权限ID VARCHAR 权限名称 VARCHAR 描述 } 用户 }o--o{ 角色 : 拥有 角色 }o--o{ 权限 : 赋予 classDiagram class Collection { <<interface>> +add() +remove() +clear() +size() } class Set { <<interface>> } class List { <<interface>> } class Queue { <<interface>> } class Map { <<interface>> } class HashSet { <<class>> } class TreeSet { <<class>> } class ArrayList { <<class>> } class LinkedList { <<class>> } class PriorityQueue { <<class>> } class HashMap { <<class>> } class TreeMap { <<class>> } Collection <|-- Set Collection <|-- List Collection <|-- Queue Set <|-- HashSet Set <|-- TreeSet List <|-- ArrayList List <|-- LinkedList Queue <|-- LinkedList Queue <|-- PriorityQueue Map <|-- HashMap Map <|-- TreeMap sequenceDiagram participant U as 用户 participant O as 下单服务 participant P as 拼团/优惠服务 participant R as 风控/库存校验 U ->> O: 请求锁单(userId, goodsId, activityId, teamId) activate O %% Step 1 幂等查询 O ->> P: 幂等查询(out_trade_no 是否已有锁单) P -->> O: 存在则直接返回该条记录 %% Step 2 拼团人数校验 O ->> P: 校验拼团人数(再次拉取，避免前端滞后) P -->> O: 校验结果 %% Step 3 优惠试算 O ->> P: 优惠试算(activityId, goodsId) P -->> O: 返回拼团优惠价格 %% Step 4 人群限定 O ->> R: 校验是否在目标人群范围 R -->> O: 校验结果(非目标人群直接拒绝) %% Step 5 锁单责任链 O ->> P: 活动有效性校验 O ->> P: 用户参与次数校验 O ->> R: 剩余库存校验 O -->> U: 返回锁单结果(成功/失败) deactivate O flowchart TD A[initialize @PostConstruct] --> B[fetchAllPictureTableNames] B -->|查询 SpaceService| C[组装所有表名 picture + picture_xxx] C --> D[updateShardingTableNodes] D --> E[getContextManager] E --> F[获取 ShardingSphereRuleMetaData] F --> G[更新 ShardingRuleConfiguration.actual-data-nodes] G --> H[alterRuleConfiguration + reloadDatabase] subgraph 动态建表 I[createSpacePictureTable] --> J{space 是否旗舰团队?} J -- 否 --> K[不建表] J -- 是 --> L[SqlRunner 创建新表 picture_xxx] L --> D end flowchart TD %% 定义节点 Publisher[Publisher 消息发布者] Exchange[fanout Exchange 扇形交换机] Queue1[Queue1] Queue2[Queue2] Queue3[Queue3] Consumer1[Consumer1] Consumer2[Consumer2] Consumer3[Consumer3] Msg[msg] %% 消息流向 Publisher -->|发布消息| Exchange Exchange -->|广播消息| Queue1 Exchange -->|广播消息| Queue2 Exchange -->|广播消息| Queue3 Queue1 -->|投递消息| Consumer1 Queue2 -->|投递消息| Consumer2 Queue3 -->|投递消息| Consumer3 %% 确认回执 Consumer1 -->|ack 成功处理，删除消息| Queue1 Consumer2 -->|nack 处理失败，重新投递| Queue2 Consumer3 -->|reject 处理失败并拒绝，删除消息| Queue3 %% 样式定义 classDef publisher fill:#e1f5fe,stroke:#01579b; classDef exchange fill:#d1c4e9,stroke:#4527a0; classDef queue fill:#f8bbd0,stroke:#880e4f; classDef consumer fill:#c8e6c9,stroke:#1b5e20; class Publisher publisher; class Exchange exchange; class Queue1,Queue2,Queue3 queue; class Consumer1,Consumer2,Consumer3 consumer; flowchart TD subgraph 运营 A1[配置拼团] end subgraph 用户 B1[查看商品] end subgraph 用户A/B C1[参与拼团] end %% 运营流程 A1 --> A2[拼团折扣] A2 --> A3[团长优惠] A3 --> A4[人群标签] %% 用户查看拼团 B1 --> B2[查看拼团] B2 --> B3[优惠试算] B3 --> B4[展示拼团] %% 用户参与拼团 C1 --> D1[商品支付 / 折扣支付] D1 --> D2[展示拼团 + 分享] D1 --> D3[拼团系统] D3 --> D4[记录拼团/多人拼团] D4 --> D5[团购回调/回调地址] D3 --> D6[拼团超时/拼团失败] D6 --> D7[发起退单] D7 --> D8[团购回调/回调地址] %% 拼团后逻辑 D1 --> E1[拼团订单 暂不发货] E1 --> E2[免拼下单 直接成单] E2 --> E3[拼团完成 商品发货] D6 --> F1[拼团失败 商品退单] E1 --> E4[直接购买 放弃拼团] E4 --> F1 E3 -->|成功| End1([结束]) F1 -->|失败| End2([结束]) flowchart TD A[Throwable] --> B[Error] A --> C[Exception] B --> D[虚拟机错误 VirtualMachineError] B --> E[内存溢出错误 OutOfMemoryError] B --> F[栈溢出错误 StackOverflowError] C --> G[IOException] C --> H[RuntimeException] C --> I[检查异常 Checked Exception （除RuntimeException外的Exception）] G --> J[FileNotFoundException] G --> K[EOFException] H --> L[空指针异常 NullPointerException] H --> M[数组越界异常 ArrayIndexOutOfBoundsException] H --> N[类型转换异常 ClassCastException] H --> O[算术异常 ArithmeticException] flowchart TD A[请求进入方法] --> B{@DCCValue("rateLimiterSwitch:open") == "close"?} B -- 是 --> Z[直接放行 不执行限流逻辑] --> END[方法正常返回] B -- 否 --> C[@RateLimiterAccessInterceptor 拦截方法] C --> D[解析注解参数 获取限流维度 key] D --> E[反射提取方法参数中的字段值 如 userId] E --> F[生成 Redis Key rl:limiter:userId] F --> G{是否在黑名单中?} G -- 是 --> H[调用 fallbackMethod 执行降级逻辑] --> END G -- 否 --> I[通过 RRateLimiter 尝试获取令牌] I -- 成功获取令牌 --> J[执行目标方法] --> END I -- 未获取令牌 --> K[记录拒绝次数 RAtomicLong 自增] K --> L{拒绝次数 > blacklistCount?} L -- 是 --> M[加入黑名单 设置 Key: rl:bl:userId 过期24小时] --> N[调用 fallbackMethod] L -- 否 --> N[调用 fallbackMethod] N --> END[降级逻辑返回结果]
matlab笔记 命令行窗口 clc：清屏（命令行窗口） clear all:把命名的变量删掉，不是命令行窗口 命名规则： 变量命名以字母开头，不可以下划线，变量是区分字母大小写的 脚本 %% xxx 注释（百分号+一个空格） % xxx 也是注释 s='a' '"aaaa",字符串 abs(s) 字符s的ascii码,为97 char(97), 输出'a' numtostr(65) ans='65'，数字转字符串 length(str),字符串的长度 矩阵 A=[1 2 3 ;4 5 6 ;7 8 9] 分号换行 B=A‘ ，矩阵转置 C=A(:) ,将矩阵拉成一列，按列存储，第一列拼接第二列拼接第三列 D=inv(A) 求逆矩阵 E=zeros(10,5,3) 生成10行5列3维0矩阵 元胞数组 ​ A=cell(1,6)，生成1行6列的小格子，每个小格子可以存放各种数据 eye(3),生成3x3的单位阵 ​ A{2}=eye(3),matlab数组从1开始，不是0
llinux服务器（以debian为例） 预先准备 域名购买与解析 购买：Low-Cost Domain Names & Hosting from $0.99 | NameSilo 教程：【服务器、域名购买】Namesilo优惠码和域名解析教程（附带服务器购买推荐和注意事项） | 爱玩实验室 DNS解析：可能需等待几分钟生效 买的域名的r2studying.top 这里的HOSTNAME相当于二级域名，如npm.r2studying.top 现改为CloudFlare来做DNS解析，不用namesilo，它有两种模式： Proxied（橙色云朵）：当你将某个 DNS 记录设置为 Proxied 时，Cloudflare 会作为反向代理服务器处理该域名的 HTTP/HTTPS 流量。这样做有几个效果： 隐藏真实 IP：用户访问时看到的是 Cloudflare 的 Anycast IP，而不是你服务器的真实 IP，从而提高安全性。 提供 CDN 加速：Cloudflare 会缓存静态资源，并通过全球节点加速内容传输，提升网站响应速度。 附加安全防护：包括 DDoS 防护和 Web 应用防火墙等功能。 DNS Only（灰色云朵）：如果你设置为 DNS Only，Cloudflare 只负责 DNS 解析，不会对流量进行代理或处理。也就是说，用户访问时会直接获取并连接到你服务器的真实 IP。 安全组设置 登录云服务器的控制台设置 入站规则 入站规则（Inbound Rules）是指防火墙中用来控制外部流量进入服务器的规则。通过这些规则，你可以指定允许或拒绝哪些类型的网络流量（例如特定协议、端口号、IP地址等）进入你的服务器。 类型 协议端口 源地址 描述 IPv4 TCP : 22 0.0.0.0/0 允许外部访问安全组内实例的SSH(22)端口，用于远程登录Linux实例。 IPv4 TCP : 3389 0.0.0.0/0 允许外部访问安全组内实例的RDP(3389)端口，用于远程登录Windows实例。 IPv4 TCP : 80 0.0.0.0/0 允许外部访问安全组内实例的HTTP(80)端口，用于通过HTTP协议访问网站。 IPv4 TCP : 443 0.0.0.0/0 允许外部访问安全组内实例的HTTPS(443)端口，用于通过HTTPS协议访问网站。 IPv4 TCP : 20-21 0.0.0.0/0 允许通过FTP上传和下载文件。 IPv4 ICMP: 全部 0.0.0.0/0 允许外部使用ping命令验证安全组内实例的网络连通性。 出站规则 出站规则（Outbound Rules）则是指控制服务器向外部发送流量的规则。你可以通过出站规则来限制服务器发出的数据包，比如限制服务器访问某些外部服务或 IP 地址。 凡是服务正常启动但是浏览器上无法访问的，都首先想想防火墙端口是否打开！！！ 常用的命令 cat 用于查看文本文件的内容 head 查看前n行 head -n 100 文件名 tail 查看后n行 tail -n 3 file wc (word count )统计行数、单词数 Hello world Linux is great wc -l file #统计行数: 2 file wc -w file #统计单词数：5 file touch 新建文本文件，如touch /home/hello.py 将在home 文件夹下新建hello.py ls 列出所有文件，但默认只是显示出最基础的文件和文件夹，如果需要更详细的信息，则使用ls -la，这将列出包括隐藏文件在内的所有文件和文件夹，并且给出对应的权限、大小和日期等信息。 zy123@hcss-ecs-588d:~$ ls -la total 44 drwxr-xr-x 6 zy123 zy123 4096 Feb 26 08:53 . drwxr-xr-x 3 root root 4096 Feb 24 16:33 .. -rw------- 1 zy123 zy123 6317 Feb 25 19:41 .bash_history -rw-r--r-- 1 zy123 zy123 220 Feb 24 16:33 .bash_logout -rw-r--r-- 1 zy123 zy123 3526 Feb 24 16:33 .bashrc drwx------ 3 zy123 zy123 4096 Feb 24 16:35 .config drwxr-xr-x 3 zy123 zy123 4096 Feb 24 16:36 .local -rw-r--r-- 1 zy123 zy123 807 Feb 24 16:33 .profile drwxr-xr-x 5 zy123 zy123 4096 Feb 26 08:53 zbparse drwxr-xr-x 3 root root 4096 Feb 26 08:54 zbparse_output 权限与文件类型（第一列）： 第一个字符表示文件类型： “d”表示目录（directory） “-”表示普通文件（regular file） “l”表示符号链接（symbolic link） 后续的9个字符分为3组，每组三个字符，分别代表所有者、所属组和其他用户的权限（读、写、执行）。 硬链接数（第二列）： 表示指向该文件的硬链接数量。对于目录来说，这个数字通常会大于1，因为“.”和“..”也算在内。 所有者（第三列）： 显示该文件或目录的拥有者用户名。 所属组（第四列）： 显示该文件或目录所属的用户组。 文件大小（第五列）： 以字节为单位显示文件的大小。对于目录，通常显示的是目录文件占用的磁盘空间（一般为4096字节）。 最后修改日期和时间（第六列）： 显示文件最后一次修改的日期和时间（可能包含月、日和具体时间或年份）。 文件名或目录名（第七列）： 显示文件或目录的名称。 cd 进入指定文件夹，如cd /home 将进入home目录。返回上层目录的命令是 cd .. ，返回刚才操作的目录的命令是 cd - mkdir 新建文件夹，如 mkdir /home/Python 将在home 文件夹下新建一个Python 文件夹。 mv 移动文件和文件夹，也可以用来重命名，如： mv /home/hello.py /home/helloworld.py 将上文的hello.py重命名为helloworld.py， mv /home/helloworld.py /home/Python/helloworld.py 将helloworld.py 由home文件夹移动到了次级的Python文件夹。 mv /home/hello.py . 将/home/hello.py 移动到当前目录下 cp 复制文件 cp /home/Python/hellowrold.py /home/Python/HelloWorld.py 将helloworld.py复制为HelloWolrd.py。注意：Linux系统严格区分大小写，helloworld.py和HelloWolrd.py是两个文件。 rm 删除，即江湖传说中 rm -rf ，r为递归，可以删除文件夹中的文件，f为强制删除。rm /home/Python/helloworld.py 可以删除刚才的helloworld.py 文件，而想删除包括Python 在内的所有文件，则是 rm -rf /home/Python 。 grep 是用于在文件或标准输入中搜索符合条件的行的命令。 #test.txt Hello World hello linux Grep is useful HELLO GREP # 精确匹配大小写 grep "Hello" test.txt 输出：Hello World -i忽略大小写 -n显示行号 # 忽略大小写匹配 grep -i "hello" test.txt 输出： Hello World hello linux HELLO GREP awk 是一个功能强大的文本处理工具，它可以对文本文件进行分列处理、模式匹配和报告生成。它的语法类似一种简单的脚本语言。 awk 'pattern { action }' filename pattern：用于匹配文本的条件（可以省略，默认对所有行生效）。 action：在匹配的行上执行的操作。 $0：代表整行内容$1+ $2+ $3+ ...。 $1, $2, ...：代表各个列（默认分隔符是空白字符，可以通过 -F 参数指定其他分隔符）。 对于： A B C 1 Alice 25 Engineer 2 Bob 30 Designer awk '{print $1, $3}' filename #印指定列 输出： Alice Engineer Bob Designer sed sed -n '1000,$p' file 对于 filename这个文件，请只打印从第1000行开始一直到文件末尾的所有内容。$代表结尾，p是print打印。 管道 | 是将一个命令的输出直接传递给另一个命令作为输入的一种机制。 示例：将 grep 与 awk 联合使用：假设有一个日志文件 access.log，需要先用 grep 过滤出包含 "ERROR" 的行，再用 awk 提取时间字段： 127.0.0.1 27/Feb/2025:10:30:25 "GET /index.html HTTP/1.1" 200 1024 192.168.1.1 27/Feb/2025:10:31:45 "POST /login HTTP/1.1" 302 512 10.0.0.5 27/Feb/2025:10:32:10 "GET /error_page HTTP/1.1" 500 2048 ERROR grep 'ERROR' access.log | awk '{print $2}' 输出：27/Feb/2025:10:32:10 usermode 修改用户账户信息的命令 sudo usermod -aG docker zy123 #-aG一起用，添加zy123到group组 chmod 命令用于修改文件或目录的权限 数字方式:数字方式使用三个（或四个）数字来表示所有者、组用户和其他用户的权限。每个数字代表读 (4)、写 (2)、执行 (1) 权限的和。 chmod 644 filename #所有者：读 + 写 = 6 组用户：读 = 4 其他用户：读 = 4 符号方式 chmod [用户类别][操作符][权限] filename 用户类别： u：所有者（user） g：组用户（group） o：其他用户（others） a：所有用户（all），等同于 u+g+o 操作符： +：增加权限 -：去掉权限 =：直接设置权限 权限： r：读权限 w：写权限 x：执行权限 chmod u+x filename #为所有者增加执行权限 系统分析 df -h 查看磁盘空间使用情况 lsof"List Open Files"显示系统中当前打开的文件。 -i会显示所有正在使用网络连接的进程 lsof -i :80 #查看 80 端口上的进程,或者判断80端口是否被占用！ netstat -ano | findstr :8080这是windows cmd下检查端口占用的命令 top 用于实时查看系统进程和资源占用情况。（CPU、内存、PID、用户等）。 top -p <pid> 只监控某个进程。 交互操作（进入 top 后按键）：P → 按 CPU 占用率排序；M → 按 内存占用率排序 q → 退出 ps 用于查看当前系统中的进程快照。 ps -ef 查看所有进程的详细信息（常用）。 ps -aux 以 BSD 风格显示进程（带 CPU、内存占用率）。 ps -u <username> 查看某个用户的进程。 ps -p <pid> 查看某个进程的详细信息。 文本编辑器 nano：Debian 11自带了简便易用的nano文本编辑器 nano /etc/apt/sources.list #打开sources.list文件 Ctrl+O:保存修改 ->弹出询问-> Y则保存，N则不保存,ctrl+c 取消操作。 Ctrl+X:退出 vim 普通模式（Normal Mode）： 打开 Vim 后默认进入普通模式。在该模式下，可以执行移动光标、删除、复制、粘贴等命令。 光标移动： 0：跳到当前行行首 $：跳到当前行行尾 gg: 将光标移到文件开头 文本操作： dd：删除（剪切）整行 yy：复制（拷贝）整行 p：在光标后粘贴（把剪切或复制的内容贴上） u：撤销上一步操作 Ctrl + r：重做上一步撤销的操作 插入模式（Insert Mode）： 在普通模式下按 i、I、a、A 等键可进入插入模式，此时可以像普通编辑器那样输入文本。 ​ 按 ESC 键退出插入模式，返回普通模式。 可视模式（Visual Mode）： 用于选中一段文本，自动进入可视模式。选中文本后可以进行复制、剪切等操作。 ​ 在可视模式下选中后按 d 删除 ​ 按 y 复制选中区域 命令行模式（Command-Line Mode）： 在普通模式下，输入 : 进入命令行模式，可以执行保存、退出、查找等命令。 ​ :w：保存当前文件 ​ :q：退出 Vim（如果有未保存的修改会警告） ​ :wq 或 :x：保存文件并退出 ​ :q!：不保存强制退出 ​ :set number：显示行号 ​ :set paste：适用于代码或格式敏感的文本，确保粘贴操作不会破坏原有的布局和缩进。 ​ :%d: 删除全文 抓包 sudo tcpdump -nn -i any port 1000 //查看请求端口1000的源 IP 地址 SSH 1. 生成密钥对 使用以下命令生成安全的 SSH 密钥对： ssh-keygen -t rsa -b 4096 -C "your_email@example.com" 生成的文件默认保存在用户主目录的 ~/.ssh/文件夹中： 私钥 (id_rsa)：必须妥善保存在本地，严禁泄露。这是证明身份的核心凭证。 公钥 (id_rsa.pub)：需要提供给服务端用于身份验证。 2. 核心概念：理解客户端与服务端 SSH 认证是双向的，关键在于分清每次连接中谁是客户端（主动发起连接的一方），谁是服务端（被动接受连接的一方）。 场景一：从服务器拉取 GitHub 代码 角色分配 客户端：你的 Linux 服务器（执行 git clone/push命令的机器） 服务端：GitHub 代码托管平台 认证流程 在客户端（你的服务器）生成 SSH 密钥对 将客户端的公钥内容添加到 GitHub 账户的 SSH Keys 设置中 当服务器执行 git clone git@github.com:...时： GitHub 会用存储的公钥发起挑战 你的服务器用本地私钥完成验证 本质：让你的服务器向 GitHub 证明其合法身份 场景二：通过 FinalShell 连接 Linux 服务器 角色分配 客户端：你的本地 Windows 电脑（运行 FinalShell 的机器） 服务端：目标 Linux 服务器 认证流程 在客户端（你的电脑）生成 SSH 密钥对 将客户端的公钥内容添加到服务端的 ~/.ssh/authorized_keys文件中 当 FinalShell 连接时： Linux 服务器用 authorized_keys中的公钥发起挑战 FinalShell 用本地私钥完成验证 本质：让你的电脑向 Linux 服务器证明你的用户身份 文件系统 在 Linux 系统中，整个文件系统从根目录 / 开始，下面简单介绍一些主要目录及其存放的文件类型： /bin 存放系统启动和运行时必需的用户二进制可执行文件，如常用的 shell 命令（例如 ls、cp、mv 等）。 /boot 包含启动加载器（如 GRUB）的配置文件和内核映像（kernel image），这些文件用于系统启动过程。 /dev 包含设备文件，这些文件代表系统中的各种硬件设备（例如硬盘、终端、USB 设备等），以及一些伪设备。 /etc 存放系统范围内的配置文件，例如网络配置、用户账户信息、服务配置等。这些文件通常由管理员维护。 /home 为普通用户提供的家目录，每个用户在这里有一个独立的子目录，存放个人文件和配置。 /lib 和 /lib64 存放系统和应用程序所需的共享库文件，这些库支持 /bin、/sbin 及其他程序的运行。 /media 通常用于挂载移动介质，如光盘、U 盘或其他可移动存储设备。 /mnt 提供一个临时挂载点，一般供系统管理员在需要手动挂载文件系统时使用。 /opt 用于安装附加的应用程序软件包，通常是第三方提供的独立软件，不与系统核心软件混合。 /proc 这是一个虚拟文件系统，提供内核和进程信息，例如系统资源使用情况、硬件信息、内核参数等。这里的文件不占用实际磁盘空间。 /root 系统管理员（root 用户）的家目录，与普通用户的 /home 分开存放。 /run 存储系统启动后运行时产生的临时数据，比如 PID 文件、锁文件等，系统重启后会清空该目录。 /sbin 存放系统管理和维护所需的二进制文件（系统级命令），例如网络配置、磁盘管理工具等，这些通常只由 root 用户使用。 /srv 用于存放由系统提供的服务数据，如 FTP、HTTP 服务的数据目录等。 /tmp 用于存放临时文件，许多应用程序在运行时会将临时数据写入这里，系统重启后通常会清空该目录。 /usr 存放大量用户应用程序和共享资源，其子目录包括： /usr/bin：大部分用户命令和应用程序。 /usr/sbin：非基本系统维护工具，主要供系统管理员使用。 /usr/lib：程序库文件。 /usr/share：共享数据，如文档、图标、配置样本等。 /var 存放经常变化的数据，如日志文件、缓存、邮件、打印队列和临时应用数据等。 Bash #!/bin/bash # 定义变量，注意等号两边不能有空格 name="World" # 如果脚本传入了参数，则使用第一个参数覆盖默认值 if [ $# -ge 1 ]; then # $# 表示传入脚本的参数个数 name=$1 # $1 表示第一个参数。 fi # 输出问候语 echo "Hello, $name!" #变量引用时要用 $ 符号，如 $name。 # 循环示例：遍历1到5的数字 for i in {1..5}; do echo "当前数字：$i" done # 定义一个函数，函数名为greet greet() { echo "函数内问候: Hello, $1!" } # 调用函数，并传入变量name作为参数 greet $name 如何运行？ 赋予可执行权限 chmod +x hello_world.sh 执行 ./hello_world.sh 或者 ./hello_world.sh Alice #传参 在 Linux 系统中，默认情况下当前目录（.）并不包含在 PATH 环境变量中。这意味着，如果你在终端中直接输入脚本名（例如 hello_world.sh），系统不会在当前目录下查找这个脚本，而是只在 PATH 中指定的目录中查找可执行程序。使用 ./hello_world.sh 表示“在当前目录下执行 hello_world.sh”，从而告诉系统正确的路径。 如何设置定时任务？ sudo crontab -e #在里面添加： 10 0 * * * /path/toyour/xx.sh #让gpt写 反向代理神器——Nginx Proxy Manager 【Docker系列】一个反向代理神器——Nginx Proxy Manager-我不是咕咕鸽 概念 正向代理是代理客户端的行为，即代理服务器代表客户端向目标服务器发出请求。客户端将自己的请求先发送给代理服务器，由代理服务器转发给真正的目标服务器，然后再将返回结果传递给客户端。 特点： 保护访问者（客户端）的信息：目标服务器只会看到代理服务器的请求，无法直接获知真正发起请求的客户端是谁。 应用场景：当客户端出于隐私、访问控制或跨越网络限制的目的，需要隐藏自己的真实IP或身份时，会使用正向代理。 反向代理是代理服务器代表目标服务器接收客户端请求，并将请求转发给内部的真实服务器，然后将结果返回给客户端。客户端只与代理服务器通信，而不知道背后实际处理请求的服务器。 特点： 保护服务器端的信息：客户端看不到真正的服务器细节，只知道代理服务器。这样可以隐藏真实服务器的 IP 和其他内部结构信息，从而增强安全性和负载均衡等功能。 应用场景：在大型网站或应用中，为了防止恶意攻击或实现负载均衡，通常会在真实服务器前部署一个反向代理服务器。 docker 部署 version: '3' services: app: image: 'jc21/nginx-proxy-manager:latest' restart: unless-stopped ports: - '80:80' # 保持默认即可，不建议修改左侧的80 - '81:81' # 冒号左边可以改成自己服务器未被占用的端口 - '443:443' # 保持默认即可，不建议修改左侧的443 volumes: - ./data:/data # 冒号左边可以改路径，现在是表示把数据存放在在当前文件夹下的 data 文件夹中 - ./letsencrypt:/etc/letsencrypt # 冒号左边可以改路径，现在是表示把数据存放在在当前文件夹下的 letsencrypt 文件夹中 NPM后台管理网站运行在81号端口，NPM服务本身监听80(http)和443(https)端口 工作原理 用户访问网站（80/443端口） 当用户访问 https://blog.test.com 时，请求到达服务器的443端口。 Nginx根据域名匹配代理规则（由NPM配置），将请求转发到后端服务（如 192.168.1.100:8080）。 管理员访问后台（81端口） 管理员通过 http://服务器IP:81 访问NPM管理界面，配置代理规则。 配置完成后，NPM会自动生成Nginx配置文件并重启Nginx服务，使新规则生效。 两种方法实现SSL安全连接 1. 开启橙云+Cloudflare Origin CA：网站SSL证书自动续期又又又失败了？试试CloudFlare的免费证书，15年有效期！-我不是咕咕鸽 cloudflare解析DNS，开启橙云 Cloudflare Full (strict)模式(灵活模式下无需以下步骤) 在 Nginx Proxy Manager（NPM）添加 Cloudflare Origin CA 配置 Proxy Host 使其使用 选择刚刚添加的 Cloudflare Origin CA 证书 选择 Force SSL（强制 HTTPS） 启用 HTTP/2 支持 Cloudflare Origin CA作用:实现cloudflare与源服务器之间的流量加密 2.通配符SSL证书：【Docker系列】反向代理神器NginxProxyManager——通配符SSL证书申请-我不是咕咕鸽 更推荐第二种！！！目前正在使用，但是3个月续签一次证书！ 浏览器 → (HTTPS) → NPM → (HTTP 或 HTTPS) → Gitea 这就是反向代理常见的工作流程。 默认经常是 NPM 做 SSL 终止（内网用 HTTP 转发给 Gitea）。如果你想内外全程加密，就要让 NPM -> Gitea 这段也走 HTTPS，并在 Gitea 上正确配置证书。 正向代理（用于拉取镜像） 下载Clash客户端：Release Clash-Premium · DustinWin/proxy-tools 或者Index of /Linux/centOS/ 我是windows上下载clashpremium-release-linux-amd64.tar.gz 然后FTP上传到linux服务器上。 下载配置文件config.yaml：每个人独一无二的 wget -O /home/zy123/VPN/config.yaml "https://illo1.no-mad-world.club/link/2zXAEzExPjAi6xij?clash=3" 类似这样： port: 7890 socks-port: 7891 allow-lan: false mode: Rule //Global log-level: info external-controller: 0.0.0.0:9090 unified-delay: true hosts: time.facebook.com: 17.253.84.125 time.android.com: 17.253.84.125 dns: enable: true use-hosts: true nameserver: - 119.29.29.29 - 223.5.5.5 - 223.6.6.6 - tcp://223.5.5.5 - tcp://223.6.6.6 - tls://dns.google:853 - tls://8.8.8.8:853 - tls://8.8.4.4:853 - tls://dns.alidns.com - tls://223.5.5.5 - tls://223.6.6.6 - tls://dot.pub - tls://1.12.12.12 - tls://120.53.53.53 - https://dns.google/dns-query - https://8.8.8.8/dns-query - https://8.8.4.4/dns-query - https://dns.alidns.com/dns-query - https://223.5.5.5/dns-query - https://223.6.6.6/dns-query - https://doh.pub/dns-query - https://1.12.12.12/dns-query - https://120.53.53.53/dns-query default-nameserver: - 119.29.29.29 - 223.5.5.5 - 223.6.6.6 - tcp://119.29.29.29 - tcp://223.5.5.5 - tcp://223.6.6.6 proxies: - {name: 🇭🇰 香港Y01, server: qvhh1-g03.hk01-ae5.entry.v50307shvkaa.art, port: 19273, type: ss, cipher: aes-256-gcm, password: 6e4124c4-456e-36a3-b144-c0e1a618d04c, udp: true} 注意，魔戒vpn给的是一个订阅地址！！！还需要解码 简便方法：windows上将订阅链接导入，自动解析成yaml配置文件，然后直接把该文件传到服务器上！ 启动Clash ./CrashCore -d . & //后台启动 为Clash创建服务 1.创建 systemd 服务文件 sudo vim /etc/systemd/system/clash.service 2.在文件中添加以下内容： [Unit] Description=Clash Proxy Service After=network.target [Service] ExecStart=/home/zy123/VPN/CrashCore -d /home/zy123/VPN WorkingDirectory=/home/zy123/VPN Restart=always User=zy123 Group=zy123 [Install] WantedBy=multi-user.target 这段配置将 Clash 配置为： 在网络服务启动后运行。 在启动时自动进入后台，执行 CrashCore 服务。 如果服务意外停止，它将自动重启。 以 zy123 用户身份运行 Clash。 启动服务： sudo systemctl start clash 停止服务： sudo systemctl stop clash 查看服务状态： sudo systemctl status clash 配置YACD YACD 是一个基于 Clash 的 Web 管理面板，用于管理您的 Clash 配置、查看流量和节点信息等。 直接用现成的：https://yacd.haishan.me/ 服务器上部署：目前是npm手动构建安装启动的。 下载yacd git clone https://github.com/haishanh/yacd.git 安装npm 构建yacd cd ~/VPN/yacd pnpm install pnpm build 启动yacd nohup pnpm serve --host 0.0.0.0 & //如果不是0.0.0.0 不能在windows上打开 停止进程 ps aux | grep pnpm kill xxx 通过http://124.71.159.195:4173/ 访问yacd控制面板。手动添加crash服务所在的ip:端口。 如果连不上：yacd面板和crash都是http协议就行了。 连接测试 直连： curl -v https://www.google.com 使用代理：curl -x http://127.0.0.1:7890 https://www.google.com File Browser 文件分享 https://github.com/filebrowser/filebrowser Docker 部署 File Browser 文件管理系统_filebrowser docker-CSDN博客 1.创建数据目录 mkdir -p /data/filebrowser/{srv,config,db} 2.目录授权 chmod -R 777 /data/filebrowser/ 3.编辑 docker-compose.yaml 文件 version: '3' services: filebrowser: image: filebrowser/filebrowser:s6 container_name: filebrowser restart: always ports: - "2000:80" # 将容器的80端口映射到宿主机2000端口 volumes: - /data/filebrowser/srv:/srv #保存用户上传的文件 - /data/filebrowser/config:/config # 配置文件存储路径 - /data/filebrowser/db:/database #数据库存储路径 调用API实现文件上传与下载 登录： # 登录 → 获取 JWT token（原始字符串） curl -X POST "yourdomain/api/login" \ -H "Content-Type: application/json" \ -d '{ "username": "admin", "password": "123456" }' 响应: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…" //jwt令牌 上传： # 先把 token 保存到环境变量（假设你已经执行过 /api/login 并拿到了 JWT） export TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…" # 定义要上传的本地文件和目标名称 FILE_PATH="/path/to/local/photo.jpg" OBJECT_NAME="photo.jpg" # 对远程路径做 URL 编码（保留斜杠） REMOTE_PATH="store/${OBJECT_NAME}" ENCODED_PATH=$(printf "%s" "${REMOTE_PATH}" \ | jq -sRr @uri \ | sed 's/%2F/\//g') # 发起上传请求（raw body 模式） curl -v -X POST "https://yourdomain/api/resources/${ENCODED_PATH}?override=true" \ -H "X-Auth: ${TOKEN}" \ -H "Content-Type: image/jpeg" \ # 根据文件后缀改成 image/png 等 --data-binary "@${FILE_PATH}" 获取分享链接url： # 假设已经执行过 /api/login 并把 JWT 保存在环境变量 TOKEN # 同样复用之前算好的 ENCODED_PATH 和 REMOTE_PATH # 调用分享接口（注意：POST，body 为空 JSON "{}"） curl -s -X POST "https://yourdomain/api/share/${ENCODED_PATH}" \ -H "X-Auth: ${TOKEN}" \ -H "Cookie: auth=${TOKEN}" \ -H "Content-Type: text/plain;charset=UTF-8" \ -d '{}' \ | jq -r '.hash' \ | xargs -I{} printf "https://yourdomain/api/public/dl/%s/%s\n" {} "${REMOTE_PATH}" Gitea Gitea Docker 安装与使用详解：轻量级自托管 Git 服务教程-CSDN博客 version: "3" services: gitea: image: gitea/gitea:latest container_name: gitea environment: - USER_UID=1000 - USER_GID=1000 restart: always ports: - "3000:3000" # 将宿主机的3000端口映射到容器的3000端口 volumes: - /data/gitea:/data # 持久化存储Gitea数据（包括仓库、配置、日志等） EasyImage 【好玩儿的Docker项目】10分钟搭建一个简单图床——Easyimage-我不是咕咕鸽 github地址：icret/EasyImages2.0: 简单图床 - 一款功能强大无数据库的图床 2.0版 sudo -i # 切换到root用户 apt update -y # 升级packages apt install wget curl sudo vim git # Debian系统比较干净，安装常用的软件 version: '3.3' services: easyimage: image: ddsderek/easyimage:latest container_name: easyimage ports: - '1000:80' environment: - TZ=Asia/Shanghai - PUID=1000 - PGID=1000 volumes: - '/root/data/docker_data/easyimage/config:/app/web/config' - '/root/data/docker_data/easyimage/i:/app/web/i' restart: unless-stopped 网页打开显示bug： cd /data/easyimage/config/config.php 这里添加上https 网站域名 图片域名设置可以改变图片的url：IP或域名 picgo安装： Releases · Molunerfinn/PicGo 1.右下角小窗打开 2.插件设置，搜索web-uploader 1.1.1 （自定义web图床） 旧版有搜索不出来的情况！建议直接安装最新版！ 3.配置如下，API地址从easyimage-设置-API设置中获取 typora设置 左上角文件-偏好设置-图像-插入图片时{ 上传图片-picgo服务器-填写picgo安装路径 } ps:还可以选择上传到./assets，每个md文件独立 或者上传到指定路径如/image，多个md文件共享 py脚本1：将所有md文件中的图片路径改为本地，统一保存到本地output文件夹中 py脚本2：将每个md文件及其所需图片单独保存，保存到本地，但每个md文件有自己独立的assets文件夹 py脚本3：将本地图片上传到easyimage图床并将链接返回替换md文件中的本地路径 Typecho 【好玩儿的Docker项目】10分钟搭建一个Typecho博客｜太破口！念念不忘，必有回响！-我不是咕咕鸽 typecho:https://github.com/typecho/typecho/ 注意：nginx一定要对typecho目录有操作权限！ sudo chmod 755 -R ./typecho services: nginx: image: nginx ports: - "4000:80" # 左边可以改成任意没使用的端口 restart: always environment: - TZ=Asia/Shanghai volumes: - ./typecho:/var/www/html - ./nginx:/etc/nginx/conf.d - ./logs:/var/log/nginx depends_on: - php networks: - web php: build: php restart: always expose: - "9000" # 不暴露公网，故没有写9000:9000 volumes: - ./typecho:/var/www/html environment: - TZ=Asia/Shanghai depends_on: - mysql networks: - web pyapp: build: ./markdown_operation # Dockerfile所在的目录 restart: "no" networks: - web env_file: - .env depends_on: - mysql mysql: image: mysql:5.7 restart: always environment: - TZ=Asia/Shanghai expose: - "3306" # 不暴露公网，故没有写3306:3306 volumes: - ./mysql/data:/var/lib/mysql - ./mysql/logs:/var/log/mysql - ./mysql/conf:/etc/mysql/conf.d env_file: - mysql.env networks: - web networks: web: 卸载： sudo -i # 切换到root cd /root/data/docker_data/typecho # 进入docker-compose所在的文件夹 docker-compose down # 停止容器，此时不会删除映射到本地的数据 cd ~ rm -rf /root/data/docker_data/typecho # 完全删除映射到本地的数据 主题 Joe主题：https://github.com/HaoOuBa/Joe Joe再续前缘主题 - 搭建本站同款网站 - 易航博客 自定义文章详情页的上方信息（如更新日期/文章字数第）： /typecho/usr/themes/Joe/functions.php中定义art_count，统计字数(粗略)。 原始 Markdown → [1] 删除代码块/行内代码/图片/链接标记/标题列表标记/强调符号 → [2] strip_tags() + html_entity_decode() → [3] 正则保留 “文字+数字+标点”，去除其它 → 结果用 mb_strlen() 得到最终字数 typecho/usr/themes/Joe/module/single/batten.php <?php if (!defined('__TYPECHO_ROOT_DIR__')) { http_response_code(404); exit; } ?> <?php $this->title() ?> <a href="<?php $this->author->permalink(); ?>"> <img width="38" height="38" class="avatar lazyload" src="<?php joe\getAvatarLazyload(); ?>" data-src="<?php joe\getAvatarByMail($this->author->mail) ?>" alt="<?php $this->author(); ?>" /> <a class="link" href="<?php $this->author->permalink(); ?>" title="<?php $this->author(); ?>"><?php $this->author(); ?> <?php echo $this->date('Y-m-d'); ?> / <?php $this->commentsNum('%d'); ?> 评论 / <?php echo joe\getAgree($this); ?> 点赞 / <?php echo joe\getViews($this); ?> 阅读 / <?php echo art_count($this->cid); ?> 字 最后更新于 <?php echo date('m-d', $this->modified); ?> 修改代码块背景色： typecho/usr/themes/Joe/assets/css/joe.global.css .joe_detail__article code:not([class]) { border-radius: var(--radius-inner, 4px); /* 可以设置一个默认值 */ background: #f5f5f5; /* 稍微偏灰的背景色 */ color: #000000; /* 黑色字体 */ padding: 2px 6px; /* 内边距可以适当增大 */ font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; word-break: break-word; font-weight: normal; -webkit-text-size-adjust: 100%; -webkit-font-smoothing: antialiased; white-space: pre-wrap; /* 保持代码换行 */ font-size: 0.875em; margin-inline-start: 0.25em; margin-inline-end: 0.25em; } 大坑：{x}会显示为勾选框，无法正常进行latex公式解析，因为typecho/usr/themes/Joe/public/short.php中设置了短代码替换，在文章输出前对 $content 中的特定标记或短代码进行搜索和替换，从而实现一系列自定义功能。现已全部注释。 typecho/usr/themes/Joe/assets/js/joe.single.js原版： 显示弹窗，点叉后消失 { document.querySelector('.joe_detail__article').addEventListener('copy', () => { autolog.log(`本文版权属于 ${Joe.options.title} 转载请标明出处！`, 'warn', false); }); } 显示5秒后消失： document.querySelector('.joe_detail__article').addEventListener('copy', () => { // 显示 autolog 消息 autolog.log(`本文版权属于 ${Joe.options.title} 转载请标明出处！`, 'warn', false); // 5 秒后删除该消息 setTimeout(() => { const warnElem = document.querySelector('.autolog-warn'); if (warnElem) { warnElem.remove(); // 或者使用 warnElem.style.display = 'none'; } }, 5000); }); markdown编辑与解析 确保代码块```后面紧跟着语言，如```java，否则无法正确显示。 markdown编辑器插件：https://xiamp.net/archives/aaeditor-is-another-typecho-editor-plugin.html '开启公式解析！' markdown解析器插件：mrgeneralgoo/typecho-markdown: A markdown parse plugin for typecho. 关闭公式解析，仅开启代码解析！ slug为页面缩略名，在新增文章时可以传入，默认是index数字。 qBittorrent 【好玩的Docker项目】10分钟搭建你专属的下载神器——qbittorrent-我不是咕咕鸽 docker pull linuxserver/qbittorrent cd ~ mkdir /root/data/docker_data/qBittorrent #创建qbitorrent数据文件夹 cd /root/data/docker_data/qBittorrent mkdir config downloads #创建配置文件目录与下载目录 nano docker-compose.yml #创建并编辑文件 services: qbittorrent: image: linuxserver/qbittorrent container_name: qbittorrent environment: - PUID=1000 - PGID=1000 - TZ=Asia/Shanghai # 你的时区 - UMASK_SET=022 - WEBUI_PORT=8081 # 将此处修改成你欲使用的 WEB 管理平台端口 volumes: - ./config:/config # 绝对路径请修改为自己的config文件夹 - ./downloads:/downloads # 绝对路径请修改为自己的downloads文件夹 ports: # 要使用的映射下载端口与内部下载端口，可保持默认，安装完成后在管理页面仍然可以改成其他端口。 - 6881:6881 - 6881:6881/udp # 此处WEB UI 目标端口与内部端口务必保证相同，见问题1 - 8081:8081 restart: unless-stopped 有个bug！qBittorrent 登录遇到 unauthorized的解决方法： 1、先停止容器docker compose down 2、进入配置文件夹config，编辑qBittorrent.conf在文末加上： WebUI\HostHeaderValidation=false WebUI\CSRFProtection=false 3.如果还是进不去，在地址后端添加'/' 内网穿透 使用服务提供商 网址：https://natapp.cn 1.隧道查看 2.隧道配置 配置映射关系： 3.客户端下载与配置 https://natapp.cn/#download ，参考官方文档，配置本地token，启动本地客户端。 自己搭建FRP 数据流转流程 外部请求到达 frp-server 你在 frps.ini（服务端配置）里为某个 proxy 分配了一个 remote_port（或 custom_domains）。 当外部用户（浏览器、移动端、其他服务）访问 http://your.frps.ip:remote_port/... 时，流量首先打到你部署在公网的 frp-server 上。 frp-server 转发到 frp-client frp-server 维护着一个与每个 frp-client 的长连接（control connection）。 收到外部连接后，frp-server 会基于这个 control 通道告诉对应的 frp-client： “有一个新连接，请你去拿 proxy(name) 对应的后端服务数据。” frp-server 不自己去连 Java 后端，它只是做信令和数据的“管道”管理。 frp-client 建立到本地 Java 服务的连接 frp-client 收到信令后，内部根据 proxies 配置： proxies name = "my-java-app" type = "tcp" local_ip = "127.0.0.1" local_port = 8080 remote_port= 18080 它会在容器或宿主机内部打开一个新的 TCP 连接，指向 127.0.0.1:8080（即你的 Java 服务） frp-server frps.toml: # https://github.com/fatedier/frp/blob/dev/conf/frps_full_example.toml [common] # 监听端口 bind_port = 7000 # 面板端口 dashboard_port = 7500 # 登录面板的账号密码（修改成自己的） dashboard_user = admin dashboard_pwd = admin # token = docker-compose: version: '3.9' services: frps: image: fatedier/frps:v0.60.0 hostname: frps container_name: frps volumes: - "./config/frps.toml:/frps.toml" command: - "-c" - "/frps.toml" network_mode: "host" frp-client frpc.toml: # 服务端地址 https://github.com/fatedier/frp/blob/dev/conf/frpc_full_example.toml serverAddr = "124.71.159.195" # 服务端配置的bindPort serverPort = 7000 # token = proxies # 代理应用名称，根据自己需要进行配置 name = "smile-dev-tech-01" # 代理类型 有tcp\udp\stcp\p2p type = "tcp" # 客户端代理应用IP localIP = "host.docker.internal" # 客户端代理应用端口 localPort = 8234 # 服务端反向代理端口；提供给外部访问 remotePort = 8234 proxies # 代理应用名称，根据自己需要进行配置 name = "smile-dev-tech-02" # 代理类型 有tcp\udp\stcp\p2p type = "tcp" # 客户端代理应用IP localIP = "host.docker.internal" # 客户端代理应用端口 localPort = 9001 # 服务端反向代理端口；提供给外部访问 remotePort = 9001 docker-compose: # 命令执行 docker-compose -f docker-compose.yml up -d version: '3.9' services: frpc: image: fatedier/frpc:v0.60.0 hostname: frpc container_name: frpc volumes: - "./config/frpc.toml:/frpc.toml" command: - "-c" - "/frpc.toml" network_mode: "host" frp-server必须部署在有公网ip的服务器，frp-client客户端有两种部署方式： 1.docker部署，如上 2.下载windows客户端https://github.com/fatedier/frp/releases/tag/v0.60.0 **注意：**如果java后端和client都部署在docker同一网络中，frpc.toml 中 localIP = "localhost" 如果client在docker容器，java在idea中启动，那么 localIP = "host.docker.internal" 如果client在windows下启动，java在idea中启动， localIP = "localhost" 总之，关键是让client和java互通；client和server互通比较容易！！！ 公网ip:7500 可以查看面板。
Git linux上安装Git sudo apt update sudo apt install git Git Bash:与linux风格接近，使用最多，推荐 Git CMD:windows风格 Git GUI:图形界面，不推荐 查看配置 git config -l :查看所有配置 git config --global --list 查看用户配置的 git config --system --list :系统配置的 核心原理 index是暂存区 respository 是本地代码仓库，保存着本地的各个版本代码 remote是远程仓库，通常是github/gitee码云 如何克隆别人已有的项目 获取项目地址http 打开本地文件系统，选择你需要保存项目的位置并打开cmd git clone xxx,这个过程可能需要验证身份，输入用户名密码 如何将本地仓库与远程仓库连接？从零开始 法1： 首先在Github上新建一个代码仓库，拷贝地址 eg:https://github.com/zhangww-web/JianShu.git 在你本地文件夹下鼠标右键git bash here git init 新建本地仓库，生成.git隐藏文件，点进去有head文件。 git remote add origin url，可以绑定远程仓库 继续输入git pull origin master（若你远程仓库为空，不需要这一步） git add .(.表示所有的,注意这个‘点’)—将本地文件提交至暂存区 git commit -m '提交信息' git push origin master 法2： 第2种方法比较简单，直接用把远程仓库拉到本地，然后再把自己本地的项目拷贝到仓库中去。然后push到远程仓库上去即可。要求远程仓库为新建的空仓库！！！ 在空文件夹中git bash here（不用init!） 首先git clone https://github.com/zhangww-web/JianShu.git 然后复制自己项目的所有文件到刚刚克隆下来的仓库中 git push -u origin master 法3：在android studio中集成 配置Git 关联自己的github 对于任何安卓项目，前两步都是通用的！！！ 这里的Remote填远程仓库地址，eg： https://github.com/zhangww-web/JianShu.git IDEA 方法类似，先init本地仓库， 点击顶部菜单栏的 Git -> Manage Remotes。 如果Share project on github一直失败，可以这样： 前面步骤不变，现在去github上新建一个空白仓库，获得一个remote url，然后本地仓库提交到该空白仓库，即可建立链接。 迁移代码仓库 法一（推荐）：访问令牌（Access Token）获取：点击右上角头像，选择“Settings”（设置）。 在左侧菜单中找到“Developer settings”（开发者设置），然后点击“Personal access tokens”（个人访问令牌）。点击“Generate new token”（生成新令牌），按照需要选择相应的权限（通常建议勾选 repo、read:org 等，根据你的实际需求）。生成后将令牌复制下来，填入 Gitea 的迁移界面中。 直接推完整git信息，推荐 法二： 本地有完整的代码已经git提交记录，不想丢失，推送到新的远程仓库： 在本地文件夹下git bash here git remote add origin <新仓库地址> git push -u origin --all 注意：只会将你本地仓库已经 checkout 出来的所有分支（即本地存在的分支）推送到新远程仓库中。 Git 常用命令 提交代码 git add--> git commit --> git push git add . 提交所有文件到暂存区，此时git status显示changes to be commited git commit -m "describe描述性内容" 提交到本地仓库 分支操作 git banch -a 可以查看所有分支 git branch dev 可以新建dev分支 git checkout dev 可以切换到dev分支 git branch -d dev 删除dev分支 git branch -m dev cs :将dev分支修改名称为cs分支 push失败问题 首先，科学上网一定能打开github的官网，但是idea中仍然无法push 此时查看你的代理服务器的端口，发现是7890 然后打开git bash,输入以下代码： 使用http代理 git config --global http.proxy http://127.0.0.1:7890 git config --global https.proxy https://127.0.0.1:7890 使用socks5代理 git config --global http.proxy socks5://127.0.0.1:7890 git config --global https.proxy socks5://127.0.0.1:7890 取消代理 git config --global --unset http.proxy git config --global --unset https.proxy 或者使用国产的gitee或者自己服务器上搭建gitea进行代码托管！！！ 拉取代码与解决冲突 git pull 命令用于从远程仓库拉取（fetch）并合并（merge）最新的更改到本地仓库。它实际上执行了两个操作： git fetch: 这个操作会从远程仓库下载最新的更改到本地仓库，但不会自动合并到当前分支。它将远程仓库的更改存储在本地仓库中，使你能够查看它们，但不会更改你当前工作目录中的文件。 git merge: 这个操作将远程仓库的更改合并到当前分支。如果有冲突，你需要解决冲突后再次提交合并的结果 情况1：本地修改代码未提交，拉取代码的时候就会报错： 错误：您对下列文件的本地修改将被合并操作覆盖： docker-compose.yaml 请在合并前提交或贮藏您的修改。 正在终止 暂存未提交的更改（git stash） 如果还不想提交本地修改，可以将更改暂存起来： git stash git pull git stash pop git stash 会将当前工作区的改动保存到一个栈中，拉取完成后通过 git stash pop 恢复修改。如果恢复过程中出现冲突，同样需要手动解决。 舍弃本地修改 如果确定不需要这些修改，可以放弃它们： git reset --hard git pull 但是推荐先提交本地的代码！！！ git add . git commit -m "描述本次修改的提交信息" git pull 情况2：权限校验问题 一般可以保持git网页端账号的登录状态，再pull，会有弹窗出来输入git的用户名和密码，成功后即可拉取。 情况3 合并冲突，以pycharm为例 1.触发冲突解决界面 当你执行 git pull 后如果出现冲突，PyCharm 会在右下角或版本控制工具窗口中提醒有冲突文件。你可以点击提示信息或在版本控制面板中找到冲突文件。 2.启动合并工具 双击冲突文件后，PyCharm 会自动打开三方合并工具。界面通常分为三部分： 左侧：本地修改（当前分支的更改） 右侧：远程修改（要合并进来的更改） 中间：合并结果（你需要编辑的区域） 3.手动选择并合并代码 在合并工具中，你可以逐个查看冲突部分： 点击左侧或右侧的按钮来选择保留哪部分内容。 如果需要，你也可以手动编辑中间的合并结果区域，直接输入合适的代码。 合并工具通常会有跳转到下一个冲突的按钮，方便你逐个解决。 4.保存合并结果并标记解决 合并完成后，点击工具窗口上的“Apply”或“Accept Merge”按钮，保存你的修改。此时，冲突文件会标记为已解决。 5.提交合并后的更改 返回主界面后，你可以在版本控制面板中看到已解决的文件。检查确认无误后，通过 VCS 菜单或直接点击工具栏中的提交按钮，将合并结果提交到仓库。 其他Git相关 SSH公私钥 公私钥生成 在linux中，使用账号密码链接github报错如下： remote: Support for password authentication was removed on August 13, 2021. remote: Please see https://docs.github.com/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication. 致命错误：'https://github.com/zhangww-web/reptile.git/' 鉴权失败 原因是linux不支持账号密码链接！！ 配置ssh，可以在git push的时候直接推送，github会通过ssh来验证你的身份。 如何在linux中配置私钥？ 生成 SSH 密钥： 如果你还没有 SSH 密钥，可以使用以下命令生成： ssh-keygen -t rsa -b 4096 -C "your_email@example.com" 按照提示保存密钥文件。 添加 SSH 密钥到 GitHub： 复制生成的公钥内容（通常在 ~/.ssh/id_rsa.pub 文件中）。 登录到 GitHub。 进入 SSH and GPG keys 页面。 点击 "New SSH key" 按钮，粘贴公钥内容并保存。 使用 SSH URL 克隆仓库： git clone git@github.com:zhangww-web/reptile.git SSH 连接 GitHub 并触发身份验证，流程如下： GitHub 发送一个随机挑战（Challenge） GitHub 服务器会向你的 Linux 服务器发送一个随机字符串，并用 你的公钥 进行加密。 你的 Linux 服务器用私钥解密 你的 SSH 客户端（ssh 命令或 git）会自动使用本地的 私钥（id_rsa） 进行解密。如果解密成功，证明你拥有匹配的私钥。 返回解密后的数据 你的客户端将解密后的数据返回给 GitHub。 GitHub 验证解密结果 GitHub 服务器检查解密结果是否匹配它最初发送的随机挑战。如果匹配，则认证成功。 身份验证逻辑：GitHub 发送加密数据 → 你的私钥解密 → 返回结果 → GitHub 确认一致性 → 认证成功。 如果避免每次git pull都要验证身份？ git config --global credential.helper store //将凭据保存到磁盘上（明文存储）： .gitignore（忽略某些文件） .gitignore 的生效规则 对未跟踪的文件： 被标记的文件会被忽略，不会出现在 Git 提交列表中（IDEA 中会显示为灰色或隐藏）。 对已跟踪的文件： 如果 application-local.yml ​之前已经被 Git 跟踪过​（即曾经提交过），.gitignore 不会自动将其从版本控制中移除。它仍会出现在提交列表中。 如果不小心commit了或者git add（暂存但未提交）如何撤销？ 例：如果在添加.gitignore文件前不小心提交了.idea文件夹，到项目根目录，git bash here git rm -r --cached -f .idea git commit -m "Remove .idea from tracking" git rm：从版本控制中删除文件 -r：递归删除指定目录下的所有文件。 --cached：关键选项，指定操作只针对暂存区，不动工作目录 为什么.gitignore文件不放在.git文件夹中？ 便于版本控制：.gitignore文件放在项目的根目录中，可以和项目代码一起被版本控制，这样其他协作开发者也能看到和使用相同的忽略规则。如果把.gitignore放在.git文件夹中，它就不会被版本控制系统追踪到。 撤销Git版本控制 1.直接把项目文件夹中的.git文件夹删除即可(开启查看隐藏文件夹可看到) 2：使用 Git 命令（保留文件，仅移除版本控制） git init # 重新初始化（可选，非必须） git rm -r --cached . # 移除所有文件的跟踪状态 添加协作者 协作者权限 如果不使用组织的话，你也可以单独为每个仓库添加协作者。这样做的话，公钥仍然应该添加到你的个人设置中，但是你可以在每个仓库的设置中单独管理协作者访问权限。 设置步骤包括： 打开你想要添加协作者的仓库。 导航到仓库设置中的“Manage access”（管理访问）或“Collaborators”（协作者）部分。 添加协作者的GitHub用户名，并设置他们的访问级别。
Anaconda基础命令 cuda版本 12.3.1 驱动版本 546.26 打开anaconda prompt（普通命令行cmd也可以）： 查看版本和环境 conda -V 查看版本 conda env list 查看已安装的环境 *代表当前环境 环境管理 conda create -n 新环境名字 python=3.7 （若只有python则下载最新版python） conda activate 新环境名字 可以切换环境 conda deactivate 退出环境到base conda remove -n 新环境名字 --all 删除创建的环境（先deactivate退出） 包管理 注：包管理操作前请先激活目标环境。 conda list 列出当前环境所安装的包 conda search numpy 可以查看numpy有哪些版本 conda install numpy 可以指定版本，默认最新版 pip install -r requirements.txt （使用 pip 安装依赖包列表） conda remove numpy 删除numpy以及所有依赖关系的包 查看激活的环境的python版本 python --version 结合 PyCharm 使用 conda 环境 在 conda 中创建好虚拟环境 如上文所示，使用 conda create -n 新环境名字 python=版本 创建。 在 PyCharm 中使用已有的 conda 环境 打开 PyCharm，进入 File > Settings > Project: YourProject > Python Interpreter 点击右侧的 Show All，可以看到 PyCharm 已经检测到的所有解释器。 若没有显示目标 conda 环境，可以点击右侧的加号（+）添加现有 conda 环境作为解释器。 这是添加conda镜像 conda config --add channels http://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ conda config --add channels http://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/mysys2/ conda config --set show_channel_urls yes 这是添加Pypi镜像，适用于pip安装 清华：https://pypi.tuna.tsinghua.edu.cn/simple 阿里云：https://mirrors.aliyun.com/pypi/simple/ 中国科技大学: https://pypi.mirrors.ustc.edu.cn/simple/ 华中理工大学：https://pypi.hustunique.com/ 山东理工大学：https://pypi.sdutlinux.org/ 豆瓣：https://pypi.douban.com/simple/ pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple 在conda中导出pip requirements.txt: pip freeze > requirements.txt Conda环境与Pycharm环境的大坑 如果你的pycharm中使用conda环境，那么你在pycharm的终端中所用的可能不是conda环境！ 解决办法： shell path改为 cmd.exe 这样虚拟环境就默认设置为conda环境了 如果命令行cd到项目根目录，所用的也并不是conda环境！这里用的是conda的默认环境？ 正确方法： 1.使用anaconda prompt打开 2.conda activate env 激活环境 3.cd到项目根目录 4.输入命令
JavaWeb JavaWeb学习路线 前后端分离开发 需求分析：首先我们需要阅读需求文档，分析需求，理解需求。 接口定义：查询接口文档中关于需求的接口的定义，包括地址，参数，响应数据类型等等 前后台并行开发：各自按照接口文档进行开发，实现需求 测试：前后台开发完了，各自按照接口文档进行测试 前后段联调测试：前段工程请求后端工程，测试功能 Html/CSS 标签速记： 不闭合标签: 空格占位符： 正文格式：line-height:设置行高；text-indent:设置首行缩进；text-align:规定文本的水平对齐方式 CSS引入方式 名称 语法描述 示例 行内样式 在标签内使用style属性，属性值是css属性键值对 中国新闻网 内嵌样式 定义 外联样式 定义 标签，通过href属性引入外部css文件 CSS选择器 1.元素（标签）选择器： 选择器的名字必须是标签的名字 作用：选择器中的样式会作用于所有同名的标签上 元素名称 { css样式名:css样式值； } 例子如下： div{ color: red; } 2.id选择器: 选择器的名字前面需要加上# 作用：选择器中的样式会作用于指定id的标签上，而且有且只有一个标签（由于id是唯一的） #id属性值 { css样式名:css样式值； } 例子如下： #did { color: blue; } 3.类选择器： 选择器的名字前面需要加上 . 作用：选择器中的样式会作用于所有class的属性值和该名字一样的标签上，可以是多个 .class属性值 { css样式名:css样式值； } 例子如下： .cls{ color: green; } 这里使用了第二种CSS引入方式，内嵌样式， 新浪政务 > 正文 焦点访谈：中国底气 新思想夯实大国粮仓 2023年03月02日 21:50 央视网 页面布局 盒子模型，盒子部分指的是border及以内的部分，不包括margin 布局标签：实际开发网页中，会大量频繁的使用 div 和 span 这两个没有语义的布局标签。 标签： 特点： div标签： 一行只显示一个（独占一行） 宽度默认是父元素的宽度，高度默认由内容撑开 可以设置宽高（width、height） span标签： 一行可以显示多个 宽度和高度默认由内容撑开 不可以设置宽高（width、height） box-sizing: border-box，此时指定width height为盒子的高宽，而不是content的高宽 表格标签 table> : 用于定义整个表格, 可以包裹多个 ， 常用属性如下： border：规定表格边框的宽度 width：规定表格的宽度 cellspacing: 规定单元之间的空间 : 表格的行，可以包裹多个 : 表格单元格(普通)，可以包裹内容 , 如果是表头单元格，可以替换为 ,th具有加粗居中展示的效果 表单标签 表单场景: 表单就是在网页中负责数据采集功能的，如：注册、登录的表单。 表单标签: 表单属性: action: 规定表单提交时，向何处发送表单数据，表单提交的URL。 method: 规定用于发送表单数据的方式，常见为： GET、POST。 GET：表单数据是拼接在url后面的， 如： xxxxxxxxxxx?username=Tom&age=12，url中能携带的表单数据大小是有限制的。 POST： 表单数据是在请求体（消息体）中携带的，大小没有限制。 表单项标签: 不同类型的input元素、下拉列表、文本域等。 input: 定义表单项，通过type属性控制输入形式 type取值 描述 text 默认值，定义单行的输入字段 password 定义密码字段 radio 定义单选按钮 checkbox 定义复选框 file 定义文件上传按钮 date/time/datetime-local 定义日期/时间/日期时间 number 定义数字输入框 email 定义邮件输入框 hidden 定义隐藏域 submit / reset / button 定义提交按钮 / 重置按钮 / 可点击按钮 select: 定义下拉列表 textarea: 定义文本域 HTML-表单项标签 姓名: 密码: 性别: 男 女 爱好: java game sing 图像: 生日: 时间: 日期时间: 邮箱: 年龄: 学历: ----------- 请选择 ----------- 大专 本科 硕士 博士 描述: name="gender"：这个属性定义了单选按钮组的名称，它们被分为同一个组，因此只能选择其中的一个按钮。在这种情况下，所有具有相同 name 属性值的单选按钮都被视为同一组。 value="1" 和 value="2"：这些是单选按钮的值。当用户选择某个单选按钮时，该单选按钮的值将被提交到服务器。在这种情况下，value="1" 表示选择男性，而 value="2" 表示选择女性。 用户在浏览器中看到的文本内容是 "男" 和 "女"。 女 这里的label意味着用户不仅可以点击单选按钮本身来选择选项，当用户单击 "女" 这个标签文本时，与之关联的单选按钮也会被选中。 JavaScript JS引入方式 **第一种方式：**内部脚本，将JS代码定义在HTML页面中 JavaScript代码必须位于 标签之间 在HTML文档中，可以在任意地方，放置任意数量的 **第二种方式：**外部脚本将， JS代码定义在外部 JS文件中，然后引入到 HTML页面中 外部JS文件中，只包含JS代码，不包含 注意：demo.js中只有js代码，没有 document.getElementsByTagName() : 根据标签的名字获取标签对象，同名的标签有很多，所以返回值是数组。重点！ var divs = document.getElementsByTagName('div'); for (let i = 0; i < divs.length; i++) { alert(divs[i]); } DOM修改 同上面的例子： 你想要如何操作获取到的DOM元素，你需要查阅手册，看它支持的属性 var divs = document.getElementsByClassName('cls'); var div1 = divs[0]; div1.innerHTML = "传智教育666"; JS事件 JavaScript对于事件的绑定提供了2种方式： 方式1：通过html标签中的事件属性进行绑定 方式2：通过DOM中Element元素的事件属性进行绑定 常见事件： 事件属性名 说明 onclick 鼠标单击事件 onblur 元素失去焦点 onfocus 元素获得焦点 onload 某个页面或图像被完成加载 onsubmit 当表单提交时触发该事件 onmouseover 鼠标被移到某元素之上 onmouseout 鼠标从某元素移开 VUE VUE简介 我们引入了一种叫做MVVM(Model-View-ViewModel)的前端开发思想，即让我们开发者更加关注数据，而非数据绑定到视图这种机械化的操作。那么具体什么是MVVM思想呢？ MVVM:其实是Model-View-ViewModel的缩写，有3个单词，具体释义如下： Model: 数据模型，特指前端中通过请求从后台获取的数据 View: 视图，用于展示数据的页面，可以理解成我们的html+css搭建的页面，但是没有数据 ViewModel: 数据绑定到视图，负责将数据（Model）通过JavaScript的DOM技术，将数据展示到视图（View）上 基于上述的MVVM思想，其中的Model我们可以通过Ajax来发起请求从后台获取;对于View部分，我们将来会学习一款ElementUI框架来替代HTML+CSS来更加方便的搭建View;而今天我们要学习的就是侧重于ViewModel部分开发的vue前端框架，用来替代JavaScript的DOM操作，让数据展示到视图的代码开发变得更加的简单。 VUE快速上手 第一步：在VS Code中创建名为12. Vue-快速入门.html的文件，并且在html文件同级创建js目录，将资料/vue.js文件目录下得vue.js拷贝到js目录 第二步：然后编写 第三步：在js代码区域定义vue对象,代码如下： 在创建vue对象时，有几个常用的属性： el: 用来指定哪儿些标签受 Vue 管理。 该属性取值 #app 中的 app 需要是受管理的标签的id属性值 data: 用来定义数据模型 methods: 用来定义函数。这个我们在后面就会用到 第四步：在html区域编写视图，其中{{}}是插值表达式，用来将vue对象中定义的model展示到页面上的 {{message}} Vue指令 **指令：**HTML 标签上带有 v- 前缀的特殊属性，不同指令具有不同含义。 指令 作用 v-bind 为HTML标签绑定属性值，如设置 href , css样式等 v-model 在表单元素上创建双向数据绑定 v-on 为HTML标签绑定事件 v-if 条件性的渲染某元素，判定为true时渲染,否则不渲染 v-else v-else-if v-show 根据条件展示某元素，区别在于切换的是display属性的值 v-for 列表渲染，遍历容器的元素或者对象的属性 V-bind和v-model v-bind: 为HTML标签绑定属性值，如设置 href , css样式等。当vue对象中的数据模型发生变化时，标签的属性值会随之发生变化。单向绑定！ v-model： 在表单元素上创建双向数据绑定。什么是双向？ vue对象的data属性中的数据变化，视图展示会一起变化 视图数据发生变化，vue对象的data属性中的数据也会随着变化。 data属性中数据变化，我们知道可以通过赋值来改变，但是视图数据为什么会发生变化呢？只有表单项标签！所以双向绑定一定是使用在表单项标签上的。 链接1 链接2 v-on v-on: 用来给html标签绑定事件的 简写： script： v-if和v-show 年龄 经判定,为: <span v-if="age <= 35">年轻人(35及以下) 35 && age < 60">中年人(35-60) 老年人(60及以上) 年龄 经判定,为: <span v-show="age <= 35">年轻人(35及以下) 35 && age < 60">中年人(35-60) = 60">老年人(60及以上) v-show和v-if的作用效果是一样的，只是原理不一样。v-if指令，不满足条件的标签代码直接没了，而v-show指令中，不满足条件的代码依然存在，只是添加了css样式来控制标签不去显示。 vue-for v-for: 从名字我们就能看出，这个指令是用来遍历的。其语法格式如下： {{addr}} {{index}} : {{addr}} index从0开始 Vue生命周期 状态 阶段周期 beforeCreate 创建前 created 创建后 beforeMount 挂载前 mounted 挂载完成 beforeUpdate 更新前 updated 更新后 beforeDestroy 销毁前 destroyed 销毁后 其中我们需要重点关注的是**mounted,**其他的我们了解即可。 与methods平级 mounted：挂载完成，Vue初始化成功，HTML页面渲染成功。以后我们一般用于页面初始化自动的ajax请求后台数据 Ajax-Axios Ajax: 全称Asynchronous JavaScript And XML，异步的JavaScript和XML。其作用有如下2点： 与服务器进行数据交换：通过Ajax可以给服务器发送请求，并获取服务器响应的数据。 异步交互：可以在不重新加载整个页面的情况下，与服务器交换数据并更新部分网页的技术，如：搜索联想、用户名是否可用的校验等等。 现在Ajax已经淘汰，用Axios了，是对Ajax的封装 Axios快速上手 Axios的使用比较简单，主要分为2步： 引入Axios文件 使用Axios发送请求，并获取响应结果，官方提供的api很多，此处给出2种，如下 发送 get 请求 axios({ method:"get", url:"http://localhost:8080/ajax-demo1/aJAXDemo1?username=zhangsan" }).then(function (resp){ alert(resp.data); }) 发送 post 请求 axios({ method:"post", url:"http://localhost:8080/ajax-demo1/aJAXDemo1", data:"username=zhangsan" }).then(function (resp){ alert(resp.data); }); 推荐以下方法！ 方法 描述 axios.get(url [, config]) 发送get请求 axios.delete(url [, config]) 发送delete请求 axios.post(url [, data[, config]]) 发送post请求 axios.put(url [, data[, config]]) 发送put请求 axios.get("http://yapi.smart-xwork.cn/mock/169327/emp/list").then(result => { console.log(result.data); }) axios.post("http://yapi.smart-xwork.cn/mock/169327/emp/deleteById","id=1").then(result => { console.log(result.data); }) axios使用步骤： 步骤： 首先创建文件，提前准备基础代码，包括表格以及vue.js和axios.js文件的引入 我们需要在vue的mounted钩子函数中发送ajax请求，获取数据 拿到数据，数据需要绑定给vue的data属性 在 标签上通过v-for指令遍历数据，展示数据，这里同Vue中的步骤。 Vue中先定义emps空数组，再axios将数据取到里面 this.emps=xxxx Nginx docker-compose.yml version: '3.8' networks: group-buy-network: external: true services: # 前端（保持不变） group-buy-market-front: image: nginx:alpine ports: ['86:80'] volumes: - ./nginx/html:/usr/share/nginx/html - ./nginx/conf/nginx.conf:/etc/nginx/nginx.conf:ro networks: - group-buy-network # 后端实例1 group-buying-sys-1: image: smile/group-buying-sys:latest ports: ['8091:8091'] networks: - group-buy-network # 后端实例2 group-buying-sys-2: image: smile/group-buying-sys:latest # 使用相同镜像 ports: ['8092:8091'] # 宿主机端口不同，容器内部端口相同 networks: - group-buy-network nginx.conf # 全局配置块 user nginx; # 以nginx用户身份运行worker进程（安全性考虑） worker_processes auto; # 自动根据CPU核心数设置工作进程数量（优化性能） error_log /var/log/nginx/error.log warn; # 错误日志路径，只记录警告及以上级别的错误 pid /var/run/nginx.pid; # 存储Nginx主进程ID的文件位置（用于进程管理） # 事件处理模块配置 events { worker_connections 1024; # 每个worker进程能处理的最大并发连接数 # 总并发量 = worker_processes * worker_connections } # HTTP服务器配置 http { # 基础文件类型支持 include /etc/nginx/mime.types; # 包含MIME类型定义文件（扩展名与Content-Type映射） default_type application/octet-stream; # 默认MIME类型（当无法识别文件类型时使用） # 性能优化参数 sendfile on; # 启用高效文件传输模式（零拷贝技术） keepalive_timeout 65; # 客户端保持连接的超时时间（秒），减少TCP握手开销 # 上游服务器组定义（负载均衡） upstream gbm_backend { # 负载均衡配置（两个后端实例） server group-buying-sys-1:8091 weight=3; # 后端实例1，权重为3（获得60%流量） server group-buying-sys-2:8091 weight=2; # 后端实例2，权重为2（获得40%流量） keepalive 32; # 保持到后端服务器的长连接数（提升性能） } # 支付服务上游（单实例） upstream pay_backend { server pay-mall:8092; # 支付服务地址（Docker服务名） } # 虚拟主机配置（一个server代表一个网站） server { # 监听配置 listen 80 default_server; # 监听IPv4的80端口，作为默认服务器 listen [::]:80 default_server; # 监听IPv6的80端口 server_name groupbuy.bitday.top; # 绑定的域名（支持多个，用空格分隔） # 静态文件服务配置 root /usr/share/nginx/html; # 前端静态文件根目录（Docker需挂载此目录） index index.html; # 默认访问的索引文件 # 前端路由处理（适配Vue/React等单页应用） location / { try_files $uri $uri/ /index.html; # 尝试顺序：匹配文件→匹配目录→回退到index.html } # 组购系统API代理配置 location /api/v1/gbm/ { proxy_pass http://gbm_backend; # 请求转发到负载均衡组 proxy_set_header Host $host; # 传递原始域名 proxy_set_header X-Real-IP $remote_addr; # 传递客户端真实IP proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 代理链IP信息 } # 支付/登录API代理（正则匹配） location ~ ^/api/v1/(alipay|login)/ { proxy_pass http://pay_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } } 注意事项： 1.由于是docker部署，要区别宿主机端口与容器内端口！比如 server group-buying-sys-1:8091 weight=3; 这里的8091就是 group-buying-sys-1 这个容器内端口。类似地，nginx也是docker部署，通过宿主机端口86映射到group-buy-market-front这个容器内的80端口。nginx.conf配置中的端口一定是和容器内挂钩的，而不是和宿主机！！！ 2.如果不是docker部署，比如在本地windows运行nginx.exe ，那么 就没有 ports: ['8091:8091'] 这种映射关系，你的java项目中写的端口是多少，那么nginx.conf就配置多少 3.docker部署，如果 nginx 与 group-buying-sys 位于同一网络中，可以服务名:容器内端口 等价于 宿主机ip:端口。比如 group-buying-sys-2 中 ports: ['8092:8091'] ，假设服务器ip地址为124.71.159.xxx ，那么 upstream gbm_backend { server group-buying-sys-2:8091 weight=2; #推荐！ nginx->后端容器 } upstream gbm_backend { server 124.71.159.xxx:8092 weight=2; #非常不推荐！相关nginx->宿主机->后端容器 } 两种效果一样，但推荐第一种！！！ 4.Nginx 反向代理的核心配置 location /api/v1/gbm/ { proxy_pass http://gbm_backend; # 请求转发到负载均衡组 proxy_set_header Host $host; # 传递原始域名 proxy_set_header X-Real-IP $remote_addr; # 传递客户端真实IP proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 代理链IP信息 } 将所有匹配 /api/v1/gbm/ 路径的请求转发到 gbm_backend 这个后端容器（可负载均衡为多台）。 请求 http://nginx-server/api/v1/gbm/order → 转发到 http://gbm_backend/api/v1/gbm/order **注意，**如果 location /api/v1/gbm/ { proxy_pass http://gbm_backend/; # 注意末尾的斜杠 } 请求 http://nginx-server/api/v1/gbm/order → 转发到 http://gbm_backend/order（/api/v1/gbm/ 被移除） 5.如果配置了反向代理（第4点内容），那么前端调用后端接口时，HTML、JS中无需写具体的后端服务器所在地址（ip:端口），而且如果做了负载均衡，有多台，这里也不好指定哪一台。 为什么？ 比如前端发送请求fetch(/api/v1/gbm/index/query_group_buy_market_config) 发现匹配 /api/v1/gbm ，nginx根据配置就发给http://gbm_backend负载均衡组，组里有group-buying-sys-1和 group-buying-sys-2，根据负载均衡规则转发到其中一台后端服务器。 6.静态文件挂载 server { ... # 静态文件服务配置 root /usr/share/nginx/html; # 前端静态文件根目录（Docker需挂载此目录） index index.html; ... } root 要指定静态文件根目录，如果是docker部署，一定要写容器内的位置；index.html 这个文件是/usr/share/nginx/html目录下的。 坑点： 如果要上传文件，可能会报413错误：单次请求的 整体 body 大小超限(默认1MB) 若后端需要解析比较耗时，还可能报503错误：超时间限制（默认60秒） 业务涉及 大文件上传 或 长时间任务，务必调整这些配置！如： location /api/ { proxy_pass http://ai-rag-knowledge-app:8095; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; client_max_body_size 50M; # 允许上传 50MB # 添加代理超时设置（单位：秒） proxy_connect_timeout 3600s; proxy_send_timeout 3600s; proxy_read_timeout 3600s; }
Docker docker基础知识 镜像和容器 Docker中有几个重要的概念： 镜像（Image）：Docker将应用程序及其所需的依赖、函数库、环境、配置等文件打包在一起，称为镜像，是只读的。 容器（Container）：镜像中的应用程序运行后形成的进程就是容器，只是Docker会给容器进程做隔离，对外不可见。因此一个镜像可以启动多次，形成多个容器进程。 一切应用最终都是代码组成，都是硬盘中的一个个的字节形成的文件。只有运行时，才会加载到内存，形成进程。 Docker为了解决依赖的兼容问题的，采用了两个手段： 将应用的Libs（函数库）、Deps（依赖）、配置与应用一起打包 将每个应用放到一个隔离容器去运行，避免互相干扰 这样打包好的应用包中，既包含应用本身，也保护应用所需要的Libs、Deps，无需在操作系统上安装这些，自然就不存在不同应用之间的兼容问题了。 DockerHub http://dockerhub.com/ 开源应用程序非常多，打包这些应用往往是重复的劳动。为了避免这些重复劳动，人们就会将自己打包的应用镜像，例如Redis、MySQL镜像放到网络上，共享使用，就像GitHub的代码共享一样。 DockerHub：DockerHub是一个官方的Docker镜像的托管平台。这样的平台称为Docker Registry。 国内也有类似于DockerHub 的公开服务，比如 网易云镜像服务、阿里云镜像库等。 注意：很多国内的镜像现在也不能用了！需要换源！ Docker架构 我们要使用Docker来操作镜像、容器，就必须要安装Docker。 Docker是一个CS架构的程序，由两部分组成： 服务端(server)：Docker守护进程，负责处理Docker指令，管理镜像、容器等 客户端(client)：通过命令或RestAPI向Docker服务端发送指令。可以在本地或远程向服务端发送指令。 如图： 镜像操作 docker push，将本地镜像上传到远程仓库（例如 Docker Hub） docker login #docker hub登录 # 假设已有本地镜像 myimage，需要先打上标签： docker tag myimage yourusername/myimage:latest # 上传镜像到远程仓库： docker push yourusername/myimage:latest docker pull ，从远程仓库拉取镜像到本地。 docker pull yourusername/myimage:latest docker save，将本地镜像保存为 tar 文件，方便备份或传输 docker save -o myimage.tar yourusername/myimage:latest docker load，从 tar 文件中加载镜像到本地 Docker。 docker load -i myimage.tar docker images ，查看本地镜像 docker images docker build ，构建镜像 -t后面跟镜像名 docker build -t yourusername/myimage:latest . 清理悬空、无名镜像 docker image prune 容器操作 1.docker run 创建并运行一个新容器 ​ -d:以后台模式运行容器，不会占用当前终端。 ​ --name <容器名> :为容器指定一个自定义名称，便于后续管理。 ​ -p <宿主机端口>:<容器端口> : 将容器内部的端口映射到宿主机，使外部可以访问容器提供的服务，如果不写的话，只有容器内部网络能访问它比如mysql，如果写''-p 3307:3306',那么可以用navicat连接localhost:3307访问这个数据库。 ​ --restart <策略> :设置容器的重启策略，如 no（默认不重启）、on-failure（失败时重启）、always（总是重启）或 unless-stopped(最重要！docker服务重启时，它也跟着重启，但它不会读取最新的compose文件，只会恢复中断前容器内元数据)。 ​ -v <宿主机目录>:<容器目录>或--volume ： 如 -v /host/data:/app/data docker run --name test-container -d test:latest 2.docker exec 在正在运行的 test-container 内执行命令 ​ -it : 给当前进入的容器创建一个标准输入、输出终端 docker exec -it test-container sh 3.docker logs ，查看 test-container 的日志输出： docker logs --since 1h test-container #查看最近1h docker logs --since 5m test-container #查看最近5分钟 4.docker stop 停止正在运行的 test-container： docker stop test-container 5.docker start 启动一个已停止的 test-container： docker start test-container 6.docker cp 复制文件（或目录）到容器内部，先cd到文件所在目录 docker cp localfile.txt test-container:/target_dir/ 7.docker stats ，查看docker中运行的所有容器的运行状态（CPU 内存占用） docker stats 8.docker container ls，查看运行容器的创建时间、端口映射等 docker container ls 9.docker ps 查看 Docker 容器的状态,默认情况下，它只显示正在运行的容器 docker ps -a #查看所有容器，包括已经停止或启动失败的容器 数据卷操作 **数据卷（volume）**是一个虚拟目录，指向宿主机文件系统中的某个目录。 一旦完成数据卷挂载，对容器的一切操作都会作用在数据卷对应的宿主机目录了。 这样，我们操作宿主机的/var/lib/docker/volumes/html目录，就等于操作容器内的/usr/share/nginx/html目录了 有两种挂载方式： 绑定挂载（Bind Mounts）更加推荐！ 原理：直接将宿主机上的一个目录或文件挂载到容器内。 特点 数据存储在宿主机上，容器可以直接访问宿主机的文件系统。 适合需要在开发过程中频繁修改代码或数据共享的场景。 依赖宿主机的目录结构，移植性较低。 示例：将宿主机的 /path/on/host 挂载到容器内的 /app/data： docker run -v /path/on/host:/app/data your_image 命名卷（Docker Volumes） 原理：由 Docker 管理的数据卷，存储在 Docker 的默认目录（通常在 /var/lib/docker/volumes/），或通过 Docker 卷插件存储到其他位置。 特点 Docker 负责管理这些卷，提供更好的隔离和数据持久性。 与宿主机的具体目录结构无关，便于迁移和备份。 常用于生产环境中数据的持久化。 示例：创建并挂载名为 my_volume 的卷到容器内的 /app/data： docker run -v my_volume:/app/data your_image 创建命名卷 docker volume create my_volume 查看命名卷，这将列出所有 Docker 管理的卷。 docker volume ls 显示该命名卷的详细信息 zy123@hcss-ecs-588d:~/zbparse$ sudo docker volume inspect html [ { "CreatedAt": "2025-02-25T18:46:10+08:00", "Driver": "local", "Labels": null, "Mountpoint": "/var/lib/docker/volumes/html/_data", "Name": "html", "Options": null, "Scope": "local" } ] Mountpoint是宿主机上的路径，也就是 Docker 存储该数据卷数据的实际位置 docker网络 Docker 网络的主要作用是实现容器之间的通信和隔离，同时也能控制容器与外部主机或网络的连接。通过创建自定义网络，你可以让属于同一网络的容器通过名称互相访问，而不必暴露所有服务到外部网络，这既提升了安全性又简化了容器间的交互。 举例说明 假设你有两个容器，一个运行 MySQL 数据库，另一个运行 Web 应用程序。你希望 Web 应用能够通过数据库容器的别名来访问 MySQL，而不需要硬编码 IP 地址。 1.创建自定义网络 ，名为 app-net docker network create app-net 2.启动 MySQL 容器，并加入 app-net 网络，同时为其指定别名 db docker run -d --name mysql \ --network app-net \ --network-alias db \ -e MYSQL_ROOT_PASSWORD=my-secret-pw \ mysql:latest Docker CLI 命令（如 docker ps、docker exec）需要使用 container_name 来操作容器。 但如果 另一个容器需要访问它，不能直接用 mysql容器名，而要用 IP 地址或 network-alias。 --network-alias db（网络别名），只在特定网络中生效 注意：不使用docker-compose就没有服务名，可以通过容器名，网络别名实现同网络的容器间通信 3.启动 Web 应用容器，加入同一个 app-net 网络 docker run -d --name webapp \ --network app-net \ your_webapp_image:latest 4.验证容器间通信，进入 Web 应用容器，尝试通过别名 db 连接 MySQL： docker exec -it webapp bash # 在 webapp 容器内执行，比如使用 ping 测试网络连通性： ping db 举个例子，如果你的 Java 应用运行在容器 B 中，而数据库容器 A 已经通过 --network-alias db 起了别名，那么在 Java 应用中，你只需要写： String dbUrl = "jdbc:mysql://db:3306/your_database"; 而不必关心数据库容器的实际 IP 地址。 否则： String dbUrl = "jdbc:mysql://<宿主机IP或localhost>:3306/your_database"; 因为会通过宿主机IP映射到容器内的IP 5.连接一个正在运行或已创建的容器到网络 接时可以使用 --alias 参数为容器在该网络中设置别名 docker network connect app-net mysql --alias db 6.断开连接 docker network disconnect app-net mysql 7.删除网络 需要注意的是，只有当网络中没有容器连接时才能删除。 docker network rm app-net docker network prune #删除所有未使用的网络 docker安装： 1.卸载旧版 首先如果系统中已经存在旧的Docker，则先卸载： sudo apt-get remove docker docker-engine docker.io containerd runc 说明：该命令会删除系统中现有的 Docker 相关包，但不会删除 Docker 镜像、容器、卷等数据。如果需要彻底清理，可以手动删除相关目录。 2.安装 Docker 依赖 在安装新版 Docker 前，需要更新 apt 源并安装一些依赖包，以便能够通过 HTTPS 协议访问 Docker 官方仓库。 sudo apt-get update sudo apt-get install apt-transport-https ca-certificates curl gnupg lsb-release 说明：这些软件包包括用于 HTTPS 传输的支持库、CA 证书、curl 工具、GPG 密钥管理工具以及 Debian 版本识别工具。 3.添加 Docker 官方 GPG 密钥 curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg 说明：此命令从 Docker 官方获取 GPG 密钥并保存为二进制格式，供后续验证软件包使用。 4.设置 Docker 仓库 使用以下命令将 Docker 稳定版仓库添加到 apt 源列表中： 这是最关键的一步，配置docker官方地址，往往很难下载！！！ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null 推荐使用以下阿里云的镜像加速源 echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/debian $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null cat /etc/apt/sources.list.d/docker.list 可以查看是否配置成功 5.安装 Docker Engine 更新 apt 缓存后，安装最新版的 Docker Engine、CLI 工具和 containerd： sudo apt update #如果是docker官方，这一步可能失败！ sudo apt install docker-ce docker-ce-cli containerd.io 6.启动和校验 # 启动Docker systemctl start docker # 停止Docker systemctl stop docker # 重启 systemctl restart docker # 设置开机自启 systemctl enable docker # 执行docker ps命令，如果不报错，说明安装启动成功 docker ps 以上docker安装，最关键的就是第4步和第5步 linux vim： finalshell中粘贴会出现粘贴的文本一行比一行靠右，看起来乱成一团。比较快的解决办法是，在粘贴文档前，在命令行模式下，输入 ：set paste 删除全文： 控制模式下 %d docker配置代理 sudo vim /etc/systemd/system/docker.service.d/http-proxy.conf [Service] Environment="HTTP_PROXY=http://127.0.0.1:7890" Environment="HTTPS_PROXY=http://127.0.0.1:7890" Environment="NO_PROXY=localhost,127.0.0.1" sudo systemctl daemon-reload //加载配置文件，更新环境变量 sudo systemctl restart docker systemctl show --property=Environment docker //验证是否配置成功 经验总结：貌似配置代理+启动VPN没卵用，如果拉取不到镜像，还是老老实实配置国内镜像吧，另外注意GPT给出的镜像:tag是否真的存在！！！有时候可能是虚假的，根本拉不到。 docker配置镜像： 1.编辑 Docker 配置文件,如果该文件不存在，可以创建一个新的文件。 sudo vim /etc/docker/daemon.json 2.添加多个镜像仓库配置 { "registry-mirrors": [ "http://hub-mirror.c.163.com", "https://mirrors.tuna.tsinghua.edu.cn", "https://ustc-edu-cn.mirror.aliyuncs.com", "https://rnsxnws9.mirror.aliyuncs.com", "https://registry.docker-cn.com", "https://reg-mirror.qiniu.com" ] } 3.重启 Docker 服务以应用 sudo systemctl restart docker 4.验证配置 docker info Dockerfile语法 我们只需要告诉Docker，我们的镜像的组成，需要哪些BaseImage、需要拷贝什么文件、需要安装什么依赖、启动脚本是什么，将来Docker会帮助我们构建镜像。 而描述上述信息的文件就是Dockerfile文件 。 EXPOSE 8090 是一个声明性的指令，EXPOSE 本身不会进行端口映射 在 Dockerfile 中，RUN 指令用于在构建镜像的过程中执行命令，这些命令会在镜像的一个临时容器中执行，然后将执行结果作为新的镜像层保存下来。常见的用途包括安装软件包、修改系统配置、编译代码等。 RUN cd $JAVA_DIR \ && tar -xf ./jdk8.tar.gz \ && mv ./jdk1.8.0_144 ./java8 减少重复构建镜像 当你修改原镜像时，只需使用相同的镜像名执行： docker build -t zbparse . Docker 会根据 Dockerfile 和上下文的变化来判断哪些层需要重建，只重建受影响的部分，而未变的层会使用缓存。 优化建议： 把变化较少的步骤（如 FROM 和设置工作目录和requirements.txt）放在前面。 将容易变化的步骤（比如 COPY . .）放在后面。 这样，即使修改了 项目的代码，其他层仍可复用缓存，从而减少重复构建的开销。 这样会有一个问题，如果新镜像与旧镜像名字一致，那么旧的镜像名会变成none！ 以下方法可以删除none镜像。 # 查找无标签镜像 docker images -f "dangling=true" # 删除无标签镜像 docker rmi $(docker images -f "dangling=true" -q) 在构建命令中使用 --no-cache 选项可以强制 Docker 重新执行所有步骤，这在某些情况下是必要的，但通常应避免使用它以利用缓存。 docker启动服务全流程 编写dockerfile文件（假设你已经有一个打包好的 app.jar：） # 使用官方 OpenJDK 作为基础镜像 FROM openjdk:17-jdk-slim # 设置工作目录 WORKDIR /app # 复制 jar 包到容器中 COPY app.jar app.jar # 暴露应用端口（假设应用监听 8080） EXPOSE 8080 # 启动应用 CMD ["java", "-jar", "app.jar"] 构造镜像 -t后面是镜像名 ，最后的点号 (.) 代表当前目录 docker build -t my-java-app . 运行容器 -p后面，第一个8080代表宿主机端口，第二个8080代表容器内端口 ，my-java-container为创建的容器名，my-java-app是使用的镜像名字 docker run -d -p 8080:8080 --name my-java-container my-java-app 查看日志 ，若无报错贼容器正常启动 docker logs -f my-java-container docker logs --since 1h [容器ID或名称] 查看最近1小时的日志 停止和删除容器，先停止后删除 docker stop zbparse-container docker rm zbparse-container 查看镜像 docker images 删除镜像 docker rmi zbparse 保存与加载镜像 docker save -o my-java-app.tar my-java-app docker load -i my-java-app.tar 上传到 Docker Hub docker login docker tag my-java-app yourusername/my-java-app:latest docker push yourusername/my-java-app:latest 进入容器（查看容器内数据） docker exec -it my-java-container /bin/bash 注意！denied: requested access to the resource is denied 原因：docker hub上只能使用一个命名空间，也就是说 docker tag my-java-app 646228430smile/my-java-app:latest 这里的646228430smile是用户名，保持不变 Docker部署可能遇到的问题 镜像拉取失败 排除镜像站的问题，如果拉取不到镜像，很有可能是你使用的TAG不正确！！ 直接去maven Tags | Docker Hub上看看你输入的TAG是否存在，比如： Docker Hub 如果你输入的都是个错误的TAG，那肯定拉不下来。 端口占用问题 Error response from daemon: Ports are not available: exposing port TCP 0.0.0.0:5672 -> 0.0.0.0:0: listen tcp 0.0.0.0:5672: bind: An attempt was made to access a socket in a way forbidden by its access permissions. 先查看是否端口被占用： netstat -aon | findstr 5672 如果没有被占用，那么就是windows的bug，在CMD使用管理员权限重启NAT网络服务即可 net stop winnat net start winnat windows下部署，端口安全问题 Windows 对低端口（<1024）的特殊保护，非管理员或没有对应防火墙放行的进程，可能被系统直接拦截或拒绝绑定。因此这种需要手动在防火墙开个入站规则（类似linux云服务器的设置开放端口），或者干脆用一个高端口（>1024）来映射。 构建镜像失败 RuntimeError: can‘t start new thread。 解释原因：线程资源限制： Docker 容器可能有默认或显式设置的资源限制，如 CPU 限制、内存限制和可用线程数限制。在这种情况下，pip 的进度条尝试创建新线程来处理进度条更新，这可能会超出容器允许的线程数上限。 解决方法：在dockerfile文件中增加一行关闭pip进度条展示 RUN pip config set global.progress_bar off => [flask_app internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 982B 0.0s => ERROR [flask_app internal] load metadata for docker.io/library/python:3.8-slim 60.4s ------ > [flask_app internal] load metadata for docker.io/library/python:3.8-slim: ------ failed to solve: python:3.8-slim: failed to resolve source metadata for docker.io/library/python:3.8-slim: unexpected status from HEAD request to https://ustc-edu-cn.mirror.aliyuncs.com/v2/library/python/manifests/3.8-slim?ns=docker.io: 403 Forbidden exit status 1 原因：在构建镜像时，Docker 在尝试从 ustc-edu-cn 镜像站获取 python:3.8-slim 镜像的元数据时被拒绝了（403 Forbidden） 解决方法：1.单独拉取该镜像 2.添加别的镜像源（可能清华源镜像有问题！！） docker运行权限问题 OpenBLAS blas_thread_init: pthread_create failed for thread 1 of 4: Operation not permitted OpenBLAS blas_thread_init: RLIMIT_NPROC -1 current, -1 max 解决方法： docker run --name zbparse-container --security-opt seccomp=unconfined zbparse Docker-Compose docker-compose安装： **方式1：**从 Docker 20.10 开始，Docker 官方就将 Docker Compose 作为插件集成在 Docker Engine 中，所以在安装 Docker Engine 时，它也会一并安装 Docker Compose 插件。无需额外安装 验证安装 docker compose version **方式2：**安装独立版 Docker Compose 二进制文件 下载二进制文件（或者下载别人的镜像复制到服务器中的/usr/local/bin下） sudo curl -L "https://mirrors.aliyun.com/docker-ce/linux/static/stable/x86_64/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose 赋予执行权限 sudo chmod +x /usr/local/bin/docker-compose 验证安装 docker-compose --version 这两者功能基本一致，大部分命令和参数都是相同的，只是命令前缀不同。 docker-compose.yml语法 1.services 常见子配置： image：指定服务使用的镜像。 build：指定构建上下文或 Dockerfile 路径。 ports：映射容器端口到主机端口。 environment：设置环境变量。 volumes：挂载主机目录或数据卷到容器。 depends_on：定义服务启动顺序的依赖关系。 2.networks 定义和管理自定义网络，方便容器间通信。 3.volumes 定义数据卷，用于数据持久化或者在多个容器间共享数据。 version: '3' services: web_app: build: context: ./web_app # 指定web_app的上下文 dockerfile: Dockerfile # 在./web_app下寻找Dockerfile container_name: web_app ports: - "8080:80" # 将主机8080端口映射到容器的80端口 environment: - DATABASE_HOST=db - DATABASE_USER=root - DATABASE_PASSWORD=root - DATABASE_NAME=my_database depends_on: - db networks: - my_network db: image: mysql:8 container_name: mysql_db environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: my_database ports: - "3306:3306" volumes: - db_data:/var/lib/mysql # 将命名数据卷挂载到MySQL数据目录 networks: - my_network restart: always networks: my_network: driver: bridge # 使用桥接网络驱动 volumes: db_data: build: context: ./web_app dockerfile: Dockerfile build: ./web_app #两种写法是等效的 容器名、服务名 1：默认 当使用 docker-compose build 构建镜像时，镜像的标签格式通常是 项目名_服务名 docker-compose up生成的容器名默认是 项目名_服务名_索引号 索引号（Index Number）：这是一个用于区分多个相同服务实例的数字索引。第一次启动时为 1，后续实例依次递增。 服务名是指你在docker-compose中设置的服务名称（services:下的名称）。 项目名默认是当前目录名，如果你使用了 -p 选项指定项目名，则使用指定的项目名,如 docker-compose -p my_custom_project up -d 2.在docker-compose.yml中指定容器名 version: '3' services: web: build: . container_name: my_custom_web_container restart: always 设置自动启动 ports: - "5005:5005" db: image: mysql:5.7 container_name: my_custom_db_container environment: MYSQL_ROOT_PASSWORD: example docker-compose常用命令 **1)构建镜像：**这个命令根据 docker-compose.yml 中各服务的配置构建镜像。如果你修改了 Dockerfile 或者项目代码需要打包进镜像时，就需要运行该命令来构建新的镜像。 docker-compose build 2)启动容器： docker-compose up -d 第一次执行 会根据 docker-compose.yml 中的 build: 或 image: 信息来 构建镜像 或 拉取镜像，然后启动容器。 再次执行 如果 docker-compose.yml 文件或依赖的配置没有变，默认不会重建镜像，只是重用已有的镜像和容器。 如果配置和现有容器不一致，compose 会删除旧容器并基于镜像重新创建新容器。 但 镜像本身不会重建,除非： Dockerfile 或构建上下文有变化； 你显式加了 --build 参数； 或者你手动先 docker-compose build。 -d 参数 正确：表示容器在后台运行（detached mode）。 3)进入容器： docker compose exec -it filebrowser sh 注意！一般进入数据卷挂载，直接在宿主机上操作容器内部就可以了！！！！！ 4)启动或更新指定服务 镜像不存在或需重建时才构建，配置变动（或镜像变动）则重建容器，都没变则一动不动 docker compose up -d pyapp ！！注意在使用docker-compose命令时，需要指定服务名，不能使用容器名！！！ 5)查看所有服务状态 docker-compose ps 6)查看服务的日志输出(flask_app为服务名) docker-compose logs flask_app --since 1h #只显示最近 1 小时 7)停止运行的容器 只停止容器，容器还在磁盘上，docker compose start或docker compose up时容器会带着之前的状态继续运行 docker-compose stop docker-compose stop flask_app #指定某个服务 8)删除停止的容器 默认不会删除网络、卷 docker-compose rm # 删除所有已停止的服务容器（会交互式询问要不要删除） docker-compose rm flask_app # 只删除指定服务的容器 9)停止并删除所有由 docker-compose 启动的容器、网络等（默认不影响挂载卷）。 下次再 up，就得重新创建容器、网络，但镜像不受影响（除非你显式用 --rmi 删除镜像）。 docker-compose down #不能单独指定 10)启动服务 docker-compose start #启动所有停止的.. docker-compose start flask_app 11)重启服务（停止+启动） docker-compose restart docker-compose restart flask_app #指定某个服务 Docker Compose 使用与最佳实践 核心说明 服务名和网络别名 在同一个网络中的容器之间，可以通过 服务名（如 mysql、web_app）进行通信，Docker 会自动注册 DNS 记录。 容器名虽然也能用于通信，但极不推荐，因为容器名容易变化、可移植性差。 服务名的用途 在 docker compose up、docker compose logs、docker compose restart 等 compose 命令里，可以用服务名来操作。 容器名的用途 容器名主要用于 docker 命令，例如： docker ps → 查看容器列表 docker logs <容器名> → 查看日志 docker exec -it <容器名> bash → 进入容器 默认网络 一个 docker-compose.yml 中的所有服务，默认会被挂到同一个网络（<项目名>_default），因此能通过服务名互相访问。 镜像复用 不同的 Compose 文件可以使用相同的镜像（如 mysql:8.0），Docker 会自动复用镜像，而不会重复下载。 问题类型 可能的冲突 解决方案 端口冲突 容器监听相同的宿主机端口（如 3306:3306） 在不同 docker-compose.yml 中映射不同端口（如 3307:3306） 数据卷冲突 多个 MySQL 实例共享相同的 /var/lib/mysql 使用不同的 named volume（如 mysql-data-app1、mysql-data-app2） 网络冲突 默认网络可能导致 DNS 解析失败 在 docker-compose.yml 里创建独立的 network 最佳实践1——集中式数据库 部署一个单独的 MySQL 服务（独立 Compose 文件或 Compose 中的单独服务）。 所有需要访问该 MySQL 的应用加入同一个共享网络。 在数据库内部用 不同的库和用户 来隔离各应用。 数据卷 mysql-data 仍然只对应一个实例，数据完全由该实例管控。 适合 小团队或测试环境，运维更轻松。 最佳实践2——应用 + 数据库成对部署 每个应用和它专属的 MySQL 一起定义在同一个 docker-compose.yml 文件中。 这样应用和数据库天然在同一网络中，互相隔离。 注意事项： 独立数据卷：每个 MySQL 实例用不同的卷名，避免数据冲突。 不同端口映射：宿主机上不能都用 3306:3306，比如 3307:3306、3308:3306。 适合 多应用强隔离场景，但会占用更多资源。（我基本用这种） 实践：部署微服务集群 需求：将之前学习的cloud-demo微服务集群利用DockerCompose部署 实现思路： ① 查看课前资料提供的cloud-demo文件夹，里面已经编写好了docker-compose文件 ② 修改自己的cloud-demo项目，将数据库、nacos地址都命名为docker-compose中的服务名 ③ 使用maven打包工具，将项目中的每个微服务都打包为app.jar ④ 将打包好的app.jar拷贝到cloud-demo中的每一个对应的子目录中 ⑤ 将cloud-demo上传至虚拟机，利用 docker-compose up -d 来部署 compose文件 查看课前资料提供的cloud-demo文件夹，里面已经编写好了docker-compose文件，而且每个微服务都准备了一个独立的目录： 内容如下： version: "3.2" services: nacos: image: nacos/nacos-server environment: MODE: standalone ports: - "8848:8848" mysql: image: mysql:5.7.25 environment: MYSQL_ROOT_PASSWORD: 123 volumes: - "$PWD/mysql/data:/var/lib/mysql" - "$PWD/mysql/conf:/etc/mysql/conf.d/" userservice: build: ./user-service orderservice: build: ./order-service gateway: build: ./gateway ports: - "10010:10010" 可以看到，其中包含5个service服务： nacos：作为注册中心和配置中心 image: nacos/nacos-server： 基于nacos/nacos-server镜像构建 environment：环境变量 MODE: standalone：单点模式启动 ports：端口映射，这里暴露了8848端口 mysql：数据库 image: mysql:5.7.25：镜像版本是mysql:5.7.25 environment：环境变量 MYSQL_ROOT_PASSWORD: 123：设置数据库root账户的密码为123 volumes：数据卷挂载，这里挂载了mysql的data、conf目录，其中有我提前准备好的数据 userservice、orderservice、gateway：都是基于Dockerfile临时构建的 查看mysql目录，可以看到其中已经准备好了cloud_order、cloud_user表： 查看微服务目录，可以看到都包含Dockerfile文件： 内容如下： FROM java:8-alpine COPY ./app.jar /tmp/app.jar ENTRYPOINT java -jar /tmp/app.jar 修改微服务配置 因为微服务将来要部署为docker容器，而容器之间互联不是通过IP地址，而是通过容器名。这里我们将order-service、user-service、gateway服务的mysql、nacos地址都修改为基于服务名的访问。 如下所示： spring: datasource: url: jdbc:mysql://mysql:3306/cloud_order?useSSL=false username: root password: 123 driver-class-name: com.mysql.jdbc.Driver application: name: orderservice cloud: nacos: server-addr: nacos:8848 # nacos服务地址 打包 接下来需要将我们的每个微服务都打包。因为之前查看到Dockerfile中的jar包名称都是app.jar，因此我们的每个微服务都需要用这个名称。 可以通过修改pom.xml中的打包名称来实现，每个微服务都需要修改： <build> <finalName>app</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> 打包后： 拷贝jar包到部署目录 编译打包好的app.jar文件，需要放到Dockerfile的同级目录中。注意：每个微服务的app.jar放到与服务名称对应的目录，别搞错了。 user-service： order-service： gateway： 部署 最后，我们需要将文件整个cloud-demo文件夹上传到虚拟机中，理由DockerCompose部署。 上传到任意目录： 部署： 进入cloud-demo目录，然后运行下面的命令： docker-compose up -d