Gitlab: PHP项目CI/CD实践

news2024/11/16 1:23:46

目录

1 说明

2 CI/CD

2.1 部署方式一:增量部署

2.1.1 目标服务器准备 

2.2.2 Gitlab及Envoy脚本

2.2 部署方式二:镜像构建与部署

2.2.1 推送到私有化容器仓库

准备工作

脚本

要点

2.2.2 推送到hub.docker.com

准备工作

脚本

3 参考:


1 说明

  • 以一个laravel blog项目为例,做dev分支的CI/CD实践
  • 结合laravel envoy工具做多个远程服务器部署,分两种方式:A. 增量部署 B.镜像构建与部署

服务器:

SiteServerIP站点目录
team1-prj2.dev.iahost001.dev.ia192.168.0.130/www/wwwroot/team1-prj2.dev.ia
team1-prj2.dev.iahost002.dev.ia192.168.0.131/www/wwwroot/team1-prj2.dev.ia

2 CI/CD

2.1 部署方式一:增量部署

通过Lavavel/Envoy和git拉取新版本文件进行部署

2.1.1 目标服务器准备 
  • php8.2, 安装所需扩展,务必在cli下测试是否正常, 有些被disable的functions要打开
  • composer self-update, 兼容php8.2
  • 使用Laravel/Envoy分发部署,确保Envoy.blade.php是utf8格式文件
  • 站点目录结构
    root@host001:/www/wwwroot/team1-prj2.dev.ia# tree -d -L 3 ./
    ./
    ├── current -> /www/wwwroot/team1-prj2.dev.ia/releases/default
    ├── releases
    │   └── default
    │       ├── app
    │       ├── bootstrap
    │       ├── config
    │       ├── database
    │       ├── public
    │       ├── resources
    │       ├── routes
    │       ├── storage -> /www/wwwroot/team1-prj2.dev.ia/storage
    │       ├── tests
    │       └── vendor
    └── storage
        ├── app
        │   └── public
        ├── framework
        │   ├── cache
        │   ├── sessions
        │   ├── testing
        │   └── views
        └── logs
    

说明:

team1-prj2.dev.ia应用目录
team1-prj2.dev.ia/releases版本发布目录,这里只设置了一个default目录,也可根据需要做日期变量发布
team1-prj2.dev.ia/current

链接到最新版本,被nginx访问的站点目录路径

current -> /www/wwwroot/team1-prj2.dev.ia/releases/default/

team1-prj2.dev.ia/storage

链接到最新版本的应用数据保存目录,如:日志,缓存等

storage -> /www/wwwroot/team1-prj2.dev.ia/storage

team1-prj2.dev.ia/.dev

.dev文件是运维人员建立的服务器定制环境文件,不进入仓库,链接到项目同名文件

.env -> /www/wwwroot/team1-prj2.dev.ia/.env*

team1-prj2.dev.ia/releases/default/.gitlab-ci.ymlgitlab 部署脚本
team1-prj2.dev.ia/releases/default/Envoy.blade.phpenvoy 部署脚本

nginx配置

server
{
    listen 80;
    server_name team1-prj2.dev.ia;
    index index.php index.html index.htm default.php default.htm default.html;
    root /www/wwwroot/team1-prj2.dev.ia/current/public;
    #CERT-APPLY-CHECK--START
    # 用于SSL证书申请时的文件验证相关配置 -- 请勿删除
    include /www/server/panel/vhost/nginx/well-known/team1-prj2.dev.ia.conf;
    #CERT-APPLY-CHECK--END

    #SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则
    #error_page 404/404.html;
    #SSL-END

    #ERROR-PAGE-START  错误页配置,可以注释、删除或修改
    #error_page 404 /404.html;
    #error_page 502 /502.html;
    #ERROR-PAGE-END

    #PHP-INFO-START  PHP引用配置,可以注释或修改
    include enable-php-82.conf;
    #PHP-INFO-END

    #REWRITE-START URL重写规则引用,修改后将导致面板设置的伪静态规则失效
    include /www/server/panel/vhost/rewrite/team1-prj2.dev.ia.conf;
    #REWRITE-END

    #禁止访问的文件或目录
    location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md)
    {
        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;
    }

    location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
    {
        expires      30d;
        error_log /dev/null;
        access_log /dev/null;
    }

    location ~ .*\.(js|css)?$
    {
        expires      12h;
        error_log /dev/null;
        access_log /dev/null;
    }
    
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
    
    access_log  /www/wwwlogs/team1-prj2.dev.ia.log;
    error_log  /www/wwwlogs/team1-prj2.dev.ia.error.log;
}
2.2.2 Gitlab及Envoy脚本

.gitlab-ci.yml

# default:
#  image: edbizarro/gitlab-ci-pipeline-php:7.4
#default:
# image: bennybi/php8.2
# image: bennybi/php7.4

stages:
  - test
  - deploy

.init_ssh: &init_ssh |
  which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )
  eval $(ssh-agent -s)
  echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
  mkdir -p ~/.ssh
  chmod 700 ~/.ssh
  [[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config

cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - vendor/

unit_test:
  stage: test
  tags:
    - php
  script:
    - cp .env.test .env
    - composer install
    - composer global require "laravel/envoy"
    - php artisan key:generate
    - php artisan migrate
    - vendor/bin/phpunit

deploy_dev:
  stage: deploy
  tags:
    - php
  environment:
    name: dev
    url: http://team1-prj2.dev.ia
  script:
    - *init_ssh
    - vendor/bin/envoy run deploy --branch="$CI_COMMIT_BRANCH" --commit="$CI_COMMIT_SHA"
  rules:
    - if: $CI_COMMIT_BRANCH == "dev"

deploy_live:
  stage: deploy
  tags:
    - php
  script:
    - *init_ssh
    - vendor/bin/envoy run deploy --commit="$CI_COMMIT_SHA"
  environment:
    name: live
    url: http://team1-prj2.dev.ia
  when: manual
  rules:
    - if: $CI_COMMIT_BRANCH == "live"

Envoy.blade.php

@servers(['local' => 'deployer@host001.dev.ia','staging' => 'deployer@host002.dev.ia'])

@setup
    $repository = 'git@host001.dev.ia:dev1/team1-prj2.git';
    $releases_dir = '/www/wwwroot/team1-prj2.dev.ia/releases';
    $app_dir = '/www/wwwroot/team1-prj2.dev.ia';
    $release = 'default';
    $new_release_dir = $releases_dir .'/'. $release;
    $user = get_current_user();
@endsetup

@story('deploy', ['on' => ['local','staging']])
    sync_repository
    run_composer
    update_symlinks
@endstory

@task('sync_repository')
    echo "Current User: {{$user}}, branch:{{$branch}}, commit:{{$commit}}"
    if [ -d "{{$new_release_dir}}" ]; then
        echo 'Pulling repository'
        cd {{ $new_release_dir }}
        git checkout {{ $branch }}
        git fetch
        git reset --hard HEAD
        git merge origin/{{ $branch }}
    else
        echo 'Cloning repository'
        [ -d {{ $releases_dir }} ] || mkdir {{ $releases_dir }}
        git clone --branch {{ $branch }}  --single-branch --depth 1 {{ $repository }} {{ $new_release_dir }}
        cd {{ $new_release_dir }}
        git reset --hard {{ $commit }}
        git config --global --add safe.directory '*'
    fi
@endtask

@task('run_composer')
    echo "Starting deployment ({{ $release }})"
    cd {{ $new_release_dir }}
    composer install --prefer-dist --no-scripts -q -o
@endtask

@task('update_symlinks')
    if [ ! -d "{{ $app_dir }}/current" ]; then
        echo "Linking storage directory"
        {{-- rm -rf {{ $new_release_dir }}/storage  --}}
        mv {{ $new_release_dir }}/storage {{ $app_dir }}
        ln -nfs {{ $app_dir }}/storage {{ $new_release_dir }}/storage
        
        echo 'Linking .env file'
        ln -nfs {{ $app_dir }}/.env {{ $new_release_dir }}/.env
        
        echo 'Linking current release'
        ln -nfs {{ $new_release_dir }} {{ $app_dir }}/current
        
        chmod 775 -Rf {{ $releases_dir }}
    fi
    chmod 775 -Rf {{ $new_release_dir }}/storage
    {{-- chown deployer:www -Rf {{ $new_release_dir }}/.git  --}}
@endtask
2.2 部署方式二:镜像构建与部署
说明:
  1. 应用文件在build_image阶段,打包在镜像文件中,并推送到私有化的项目镜像仓库
  2. lavavel应用所需.env,不进入代码仓库,而是通过 -v 映射到具体路径部署,如示例代码中的 "-v /data0/Projects/team1-prj1/.env:/app/.env",防止敏感参数外泄
  3. 实例化后作为微服务容器,暴露8181端口提供外部访问
2.2.1 推送到私有化容器仓库
准备工作

-  新建项目team1-prj1,初始化git 仓库为:http://host001.dev.ia:18181/dev1/team1-prj1.git

-  建好容器仓库(见前文相关部分)

-  需要在Admin Area->CI/CD->Variables添加docker访问用户变量

$LOCAL_REGISTRY_LOGIN = your docker username
$LOCAL_REGISTRY_PASSWORD
脚本

 项目中添加Dockerfile 文件,这里用到的原型镜像是我之前定制的php8.2

FROM bennybi/php8.2:latest
 
WORKDIR /app

RUN apt-get update -y && apt-get install -y openssl zip unzip git
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# RUN docker-php-ext-install pdo mbstring

COPY .env.example .env
COPY . /app
RUN composer install --no-interaction --prefer-dist --optimize-autoloader
 
# Run any additional commands specific to Laravel
RUN php artisan config:cache
RUN php artisan route:cache
RUN php artisan view:cache
 
CMD php artisan serve --host=0.0.0.0 --port=8181
EXPOSE 8181

 .gitlab-ci.yml

default:
  image: docker:19.03.8
  services:
    - mysql:5.7
  before_script:
    - docker info

variables:
  # DOCKER_IMAGE_TAG: 'bennybi/team1-prj1'
  APP_NAME: 'team1-prj1'
  REGISTRY_URL: 'http://host001.dev.ia:5050'
  DOCKER_IMAGE_TAG: 'host001.dev.ia:5050/dev1/team1-prj1:1.0.0'
  # DOCKER_IMAGE_TAG: 'host001.dev.ia:5050/dev1/team1-prj1'
  CONTAINER_IMAGE_NAME: ${DOCKER_IMAGE_TAG}
  # MYSQL_DATABASE: test
  # MYSQL_ROOT_PASSWORD: fa843c707ce26702
  # DB_HOST: host001.dev.ia
  # DB_USERNAME: developer

stages:
  - test
  - build
  - deploy

unit_test:
  stage: test
  tags:
    - php
  script:
    - cp .env.test .env
    - composer install
    - php artisan key:generate
    - php artisan migrate
    - vendor/bin/phpunit

build_image:
  stage: build
  tags:
    - php
  script:
    - docker login ${REGISTRY_URL} -u $LOCAL_REGISTRY_LOGIN -p $LOCAL_REGISTRY_PASSWORD
    - docker build --output type=registry,oci-mediatypes=false --cache-from "${DOCKER_IMAGE_TAG}" -t "${DOCKER_IMAGE_TAG}" --push --provenance=false .
    # - docker push ${DOCKER_IMAGE_TAG}:latest
    - docker push ${DOCKER_IMAGE_TAG}
  rules:
    - if: $CI_COMMIT_BRANCH == "dev"

deploy_dev:
  stage: deploy
  tags:
    - php
  script:
    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
    - eval $(ssh-agent -s)
    - ssh-add <(echo "$SSH_PRIVATE_KEY")
    - mkdir -p ~/.ssh
    - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
    - ssh deployer@host002.dev.ia "docker login ${REGISTRY_URL} -u $LOCAL_REGISTRY_LOGIN -p $LOCAL_REGISTRY_PASSWORD && docker pull $CONTAINER_IMAGE_NAME && docker stop ${APP_NAME} || true && docker rm ${APP_NAME} || true && docker run --name ${APP_NAME} -d -p 8181:8181  -v /data0/Projects/${APP_NAME}/.env:/app/.env ${CONTAINER_IMAGE_NAME} && docker exec ${APP_NAME} php artisan config:cache || true"
  environment:
    name: dev
    url: http://team1-prj1.dev.ia
  # when: manual
  rules:
    - if: $CI_COMMIT_BRANCH == "dev"
要点

- 注意build时所需的参数,缺少会诱发错误: “Invalid tag: missing manifest digest”

We used the buildx flag “–output type=registry,oci-mediatypes=false” to generate a Docker v2.2 manifest list.  We could set the value to “true” and generate an OCI manifest index, but the GitLab UI will incorrectly display “Invalid tag: missing manifest digest”. 

 推送结果图示:

2.2.2 推送到hub.docker.com
准备工作

-  新建项目team1-prj1,初始化git 仓库为:http://host001.dev.ia:18181/dev1/team1-prj1.git

-  构建的镜像将push到hub.docker.com,因此需要在Admin Area->CI/CD->Variables添加docker访问用户变量

$DOCKER_LOGIN = your docker username
$DOCKER_PASSWORD 
脚本

Dockerfile 

同上 ...

.gitlab-ci.yml

default:
  image: docker:19.03.8
  services:
    - mysql:5.7
  before_script:
    - docker info

variables:
  MYSQL_DATABASE: test
  MYSQL_ROOT_PASSWORD: fa843c707ce26702
  DB_HOST: host001.dev.ia
  DB_USERNAME: developer

stages:
  - test
  - build
  # - deploy

unit_test:
  stage: test
  tags:
    - php
  script:
    - cp .env.test .env
    - composer install
    - php artisan key:generate
    - php artisan migrate
    - vendor/bin/phpunit

build_image:
  stage: build
  variables:
    DOCKER_IMAGE_TAG: 'bennybi/team1-prj1'
  tags:
    - php
  script:
    - docker build --cache-from "${DOCKER_IMAGE_TAG}" -t "${DOCKER_IMAGE_TAG}" .
    - docker login --username $DOCKER_LOGIN --password $DOCKER_PASSWORD
    # - docker run my-docker-image /script/to/run/tests
    - docker push ${DOCKER_IMAGE_TAG}:latest
  rules:
    - if: $CI_COMMIT_BRANCH == "dev"

# deploy_dev:
#   stage: deploy
#   tags:
#     - php
#   script:
#     - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
#     - eval $(ssh-agent -s)
#     - ssh-add <(echo "$SSH_PRIVATE_KEY")
#     - mkdir -p ~/.ssh
#     - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
#     # - ~/.composer/vendor/bin/envoy run deploy --commit="$CI_COMMIT_SHA"
#   environment:
#     name: dev
#     url: http://team1-prj1.dev.ia
#   when: manual
#   rules:
#     - if: $CI_COMMIT_BRANCH == "dev"

3 参考:

- GitLab: automated build and publish of multi-platform container image with GitLab pipeline | Fabian Lee : Software Engineer

- https://jaumemule.medium.com/build-a-php8-0-fpm-gitlab-ci-cd-application-docker-google-cloud-5f2868e8370

- https://docs.gitlab.com/ee/ci/docker/using_docker_build.html

 - https://github.com/papertank/envoy-deploy/blob/master/readme.md

- https://warrickbayman.medium.com/zero-downtime-laravel-deployments-with-envoy-version-2-227c8259e31c

- Test and deploy Laravel applications with GitLab CI/CD and Envoy | GitLab

- GitLab CI/CD examples | GitLab

- 使用GitLab Runner为基于Laravel的PHP项目进行部署 

- GitLab CI/CD for Beginners [FREE Course] - DEV Community

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1499975.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Take-home questions——L3

Match the spatial domain image to the Fourier magnitude image 1—D 2—B 3—A 4—E 5—C

一分钟安装使用教程,无需服务器,一台电脑就可使用!全网最快速便捷使用Claude 3方法!

随着AI的应用变广&#xff0c;各类AI程序已逐渐普及&#xff0c;尤其是在一些日常办公、学习等与撰写/翻译文稿密切相关的场景&#xff0c;大家都希望找到一个适合自己的稳定可靠的ChatGPT软件来使用。 ChatGPT-Next-Web就是一个很好的选择。它是一个Github上超人气的免费开源…

如果用户被诱导添加了证书甚至根证书,那哪怕用了 HTTPS,使用中间人攻击就可以窃取用户所有信息了?

最近突然想到一个问题&#xff0c;HTTPS 真的足够安全吗&#xff1f;在一些对安全性要求比较高的项目里&#xff0c;能只依赖 HTTPS 来保证数据安全吗&#xff1f; 我们也用过fiddler 、whistle 等代理软件&#xff0c;也知道要能劫持流量后能看到请求和返回的内容需要添加证书…

ChatGPT 升级出现「我们未能验证您的支付方式/we are unable to authenticate」怎么办?

ChatGPT 升级出现「我们未能验证您的支付方式/we are unable to authenticate」怎么办&#xff1f; 在订阅 ChatGPT Plus 时&#xff0c;有时候会出现以下报错 &#xff1a; We are unable to authenticate your payment method. 我们未能验证您的支付方式。 出现 unable to a…

Apollo决策规划 - EM planner

旨在对b站老王所讲的百度Apollo - EM planner算法做浓缩版总结 0 决策规划背景 基于图搜索 优点&#xff1a; 可以得到全局层面最优解&#xff0c;适用于比较低维数的规划问题 缺点&#xff1a; 规划问题维数较高时&#xff0c;面临指数爆炸问题 基于采样 优点&#xff1a;…

各大厂商常用的弱口令集合

Oms呼叫中心 KXTsoft2010 Glodon控制台 admin TRENDnet趋势网络摄像头 admin/admin MOBOTIX-视频监控 admin/meinsm 思科Cisco 没有原始密码, 第一次登录时需要创建 DRS admin/1234 Honeywell admin/1234 安迅士Axis root/pass, 新安迅士摄像头在用户第一登录时要求创建…

Winform窗体随着屏幕的DPI缩放,会引起窗体变形及字体变形,superTabControl标签字体大小不匹配

一、前言 superTabControl做的浏览器标签(cefsharp)在缩放比例(125%,150%时字体不协调) 物联网浏览器,定制浏览器,多媒体浏览器(支持H264)参考栏目文章即可 二、配置参数 app.manifest参数 dpiAware =true <application xmlns="urn:schemas-microsoft-c…

保持自律,改变形象,认真对待自己

不知道一年365天里究竟有多少个节日&#xff0c;昨天网络上刚过了“女生节”&#xff0c;今天又来了“三八妇女节”。笔者从来都是女权支持者&#xff0c;但也主张切勿支持过了头而形成“女尊男卑”&#xff0c;认为应当遵循“顺其自然”这一规律为好。 有鉴于此&#xff0c;本…

LORA: LOW-RANK ADAPTATION OF LARGE LAN-GUAGE MODELS

TOC 1 前言2 方法2.1 LOW-RANK-PARAMETRIZED UPDATE MATRICES 1 前言 1) 提出背景 大模型时代&#xff0c;通常参数都是上亿级别的&#xff0c;若对于每个具体任务都要去对大模型进行全局微调&#xff0c;那么算力和资源的浪费是巨大的。 根据流形学习思想&#xff0c;对于数…

基于SSM的房客源信息管理系统设计与实现

目 录 摘 要 I Abstract II 引 言 1 1 相关技术 3 1.1 SSM框架 3 1.2 Vue框架 3 1.3 ECharts 3 1.4 JQuery技术 3 1.5 本章小结 4 2系统分析 5 2.1 需求分析 5 2.2 非功能需求 8 2.3 本章小节 8 3 系统设计 9 3.1 系统总体设计 9 3.1.1 系统体系结构 9 3.1.2 系统目录结构 9 3…

高效办公-浏览器基本操作

日常我们使用电脑&#xff0c;其实很大部分是用于网络功能&#xff0c;这里面除了客户端程序剩余的就是通过我们的浏览器获取信息或者使用业务系统了&#xff0c;这里就简单学习下浏览器基本常识与操作。 一、浏览器是什么&#xff1f; 白话讲浏览器就是一个软件&#xff0c;我…

Docker安装Redis (全网最详细教程!!!)

一、Redis简介 二、简易版本启动&#xff08;学习版&#xff09; 1、一条命令直接搞定 2、docker ps 命令&#xff0c;查看本机docker运行的容器 3、docker logs 查看日志 4、测试连接 5、优缺点 三、生产版本启动 四、Docker 停止、删除、重启、启动容器 一、Redis简介…

mysql的语法学习总结3(一些常见的问题)

执行后&#xff0c;MySQL 会重新加载授权表并更新权限。 FLUSH PRIVILEGES; 怎么检查自己的电脑端口3306有没有被占用&#xff1f; ESTABLISHED表示被占用&#xff0c;LISTENING表示端口正在被监听&#xff0c;22696是占用该端口的进程的PID&#xff08;进程标识符&#xff0…

手持气象站的优势

【TH-SQ5】手持气象站作为一种便携式的气象监测设备&#xff0c;具有许多显著的优势&#xff0c;主要体现在以下几个方面&#xff1a; 便携性&#xff1a;手持气象站设计精巧&#xff0c;重量轻&#xff0c;易于携带&#xff0c;这使得用户能够轻松地在不同地点进行气象观测&a…

常见数据类型

目录 数据类型 字符串 char nchar varchar varchar2 nvarchar 数字 number integer binary_float binary_double float 日期 date timestamp 大文本数据 大对象数据 Oracle从入门到总裁:https://blog.csdn.net/weixin_67859959/article/details/135209645 数…

通俗深入的理解Sora的架构原理

作者公众号 大数据与AI杂谈 &#xff08;TalkCheap&#xff09;&#xff0c;转载请标明出处 开始之前&#xff0c;先解释一下为什么深入和通俗这两个看似自相矛盾的词可以并列在这里。因为后续的一些文章可能也是类似的思路&#xff0c;所以先简单做一下文章定位的说明。要看正…

it-tools工具箱

it-tools 是一个在线工具集合&#xff0c;包含各种实用的开发工具、网络工具、图片视频工具、数学工具等 github地址&#xff1a;https://github.com/CorentinTh/it-tools 部署 docker run -d --name it-tools --restart unless-stopped -p 8080:80 corentinth/it-tools:lat…

ConGMC

employ I I I(A, B) to represent mutual information Z u v f u → v ( Z u ) Z_{uv}f_{u\to v}(Z_u) Zuv​fu→v​(Zu​) 辅助信息 作者未提供代码

STM32 学习10 PWM输出

STM32 学习10 PWM输出 一、PWM简介1. PWM的概念2. PWM的工作原理3. PWM 常用的应用场景 二、一些概念1. 频率2. 占空比 三、STM32F1 PWM介绍1. 定时器与寄存器&#xff08;1&#xff09;**自动重装载寄存器&#xff08;ARR&#xff09;**&#xff1a;&#xff08;2&#xff09;…

leetcode刷题(2):链表

文章目录 1. 两数相加1.1 解题思路1. 2 c 实现 2 删除排序链表中的重复元素 ||2.1 解题思路2.2 c 实现 3 旋转链表3.1 解题思路3.2 c 实现 4 剑指 Offer 06: 从尾到头打印链表4.1 解题思路4.2 c 实现 5 剑指 Offer 24. 反转链表5.1 解题思路5.2 c实现 21. 合并两个有序链表解题…