• jenkins 持续集成部署k8s项目


    准备环境

    • 需求
    1.通过jenkins 完成k8s 项目的自动化构建需求,要求nodejs、java等不同开发语言运用不同的jenkins slave 节点构建
    2.通过权限设置管控开发人员的项目使用及构建
    
    • jenkins 节点准备
    节点名称 ip地址
    jenkins-master 10.65.91.164
    jenkins-slave-nodejs 10.65.91.52
    jenkins-slave-java 10.65.91.165
    • k8s 环境
    k8s 已完成部署,部署方式参照之前博客文档,以下是博客地址
    

    二进制方式部署高可用k8s 1.20.7 版本集群

    jenkins master 节点安装

    • 下载安装包并启动
    #下载lts 版本war包
    cd /opt/
    wget https://get.jenkins.io/war-stable/2.289.1/jenkins.war --no-check-certificate
    
    #安装git
    yum -y install git
    
    #配置java 环境
    vim /etc/profile
    
    JAVA_HOME=/usr/local/jdk1.8.0_201
    JRE_HOME=/usr/local/jdk1.8.0_201/jre
    PATH=$JAVA_HOME/bin:$PATH:$JRE_HOME/bin:/usr/local/bin
    export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
    export JAVA_HOME JRE_HOME CLASSPATH PATH
    
    #查看java 安装完成
    source  /etc/profile
    java -version
    
    #创建jenkins 普通用户
    useradd jenkins
    
    #创建jenkins 数据目录
    mkdir /export/jenkins/
    mkdir /var/log/jenkins/
    chown jenkins:jenkins /export/jenkins/ -R
    chown jenkins:jenkins /var/log/jenkins/ -R
    
    #启动jenkins
    su - jenkins
    java -Djava.awt.headless=true -DsessionTimeout=1440  -DJENKINS_HOME=/export/jenkins/  -jar /opt/jenkins.war --logfile=/var/log/jenkins/jenkins.log --httpPort=8608 --debug=5 --handlerCountMax=100 --handlerCountMaxIdle=20  &
    

    Jenkins master 节点配置

    • 安装插件
    #访问Jenkins地址
    http://10.65.91.164:8608/
    
    #安装推荐插件
    将推荐插件安装完成
    
    #安装权限插件
    Role-based Authorization Strategy
    
    #安装时间戳插件
    BUILD_TIMESTAMP
    
    #安装git参数插件
    Git Parameter
    
    #安装注册环境变量插件
    Environment Injector
    
    #安装Maven 插件
    Maven Intergration
    
    #重启jenkins
    
    • 系统配置
    #系统管理-系统配置-全局属性,增加键值对
    LANG->zh_CN.UTF-8
    
    

    #系统管理-系统配置-Build Timestamp
    Timezone->Asia/Shanghai
    Pattern->yyyyMMddHHmmss
    

    • 连接ldap
    #系统管理-全局安全配置-安全域-LDAP
    添加完ldap 配置后可以点击 Test LDAP settings,检测ldap 账号密码是否生效
    


    #系统管理-全局安全配置-授权策略
    选择Role-Based Strategy
    
    #应用-保存
    

    • 配置权限
    #系统管理-Manage and Assign Roles-Manage Roles,增加itemadmin 账号,并授权
    
    

    #系统管理-Manage and Assign Roles-Assign Roles,增加ldap 运维管理员人员权限
    出现No type prefix: admin ,但是不影响后续权限使用,是没有关系的
    
    admin 用户将不能登录jenkins,只有分配admin 权限的ldap 账户可以登录jenkins,并且拥有admin 权限
    

    jenkins nodejs slave 节点添加并构建项目

    • nodejs slave 节点系统环境配置
    #安装docker 环境
    因后续要构建镜像,因此需要有docker 环境,参照之前装docker 的文档安装docker 环境
    
    #配置docker 可以连接harbor
    cat /etc/docker/daemon.json
    {
      "oom-score-adjust": -1000,
      "registry-mirrors": [
            "https://c6ai9izk.mirror.aliyuncs.com",
            "https://docker.mirrors.ustc.edu.cn"
      ],
      "log-driver":"json-file",
      "log-opts":{ "max-size" :"100m","max-file":"1"},
      "insecure-registries": ["harbor.dev.k8s.xxx.cn","harbor.k8s.xxx.cn"],
      "storage-driver": "overlay2",
      "storage-opts": [
        "overlay2.override_kernel_check=true"
      ],
      "exec-opts": ["native.cgroupdriver=systemd"]
    }
    
    #安装git
    yum -y install git
    
    #配置java 环境
    vim /etc/profile
    
    JAVA_HOME=/usr/local/jdk1.8.0_201
    JRE_HOME=/usr/local/jdk1.8.0_201/jre
    PATH=$JAVA_HOME/bin:$PATH:$JRE_HOME/bin:/usr/local/bin
    export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
    export JAVA_HOME JRE_HOME CLASSPATH PATH
    
    #查看java 安装完成
    source  /etc/profile
    java -version
    
    #创建jenkins 普通用户
    useradd jenkins
    
    #创建jenkins 数据目录
    mkdir /export/jenkins/
    chown jenkins:jenkins /export/jenkins/ -R
    
    #将jenkins 用户添加到docker 组,可以用普通jenkins 用户运行docker 命令
    usermod -a -G jenkins,docker jenkins
    
    #下载nodejs 二进制包node-v12.19.0-linux-x64,注意千万不要使用别的机器拷贝过来的node 包,因为少软连接,npm 运行会报错的。
    cd /opt/
    wget https://nodejs.org/download/release/v12.19.0/node-v12.19.0-linux-x64.tar.gz
    tar -xf node-v12.19.0-linux-x64.tar.gz
    
    #测试node 环境是否可用
    export PATH=/opt/node-v12.19.0-linux-x64/bin/:$PATH
    node -v
    npm -v
    
    #安装kubectl 、配置连接k8s 的config 文件
    #从 k8s master节点上将Kubectl 传至nodejs 节点
    scp -qpr /usr/local/bin/kubectl   10.65.91.52:/usr/local/bin/
    
    #将k8s master 节点上的config 文件传至nodejs 节点
    cd .kube
    scp -qpr config 10.65.91.52:/root/
    
    #登录nodejs 10.65.91.52 节点
    mkdir /opt/kubernetes
    mv /root/config /opt/kubernetes
    cd /opt/kubernetes
    mv config config_prod
    
    #测试是否可以连接k8s 集群,返回node 节点信息表示可以正常连接
    /usr/local/bin/kubectl --kubeconfig=/opt/kubernetes/config_prod get node 
    
    
    # 将jenkins master 节点jenkins 普通用户的公钥传至 nodejs 普通用户jenkins 目录下,以便于jenkins master 节点的jenkins 用户可以免密登录nodejs 节点
    登录 10.65.91.164,执行
    su - jenkins
    ssh jenkins@10.65.91.52
    登录成功即可
    
    • 配置凭据 jenkins-slave 用来jenkins master节点可以通过秘钥连接至 slave 节点
    #系统管理-Manage Credentials-新创建凭据
    范围:全局(Jenkins,nodes,items,all child items,etc)
    ID:jenkins-slave
    描述:jenkins-slave
    Username: jenkins
    Private Key: 添加jenkins master jenkins 用户的私钥,因为之前已将jenkins 用户的公钥放至jenkins node slave 节点
    

    • jenkins master 节点添加 nodejs jenkins slave 节点
    系统管理-节点管理-新建节点
    名称:jks-node-1
    描述:jks-node-1
    Number of executors:2
    远程工作目录:/export/jenkins
    标签:node
    用法:只允许运行绑定到这台机器的JOb
    启动方式:Launch agents via SSH
    主机:10.65.91.52
    Credential:jenkins-slave (上面已经配置好的凭据)
    Host Key Verification Strategy:Know hosts file Verification Strategy
    可用性:尽量保持代理在线
    节点属性-环境变量:键:LANG  值:UTF-8
    启动slave 节点
    

    • 部署jenkins nodejs 项目
    #Dashboard - 新建任务文件夹 -Production (显示名称:正式环境) - 新建Item - 3Dmodel-Prod (显示名称:正式环境-3D模型分享平台-3Dmodel) -新建Item(构建一个自由风格的软件项目) - 3Dmodel_Node_Prod
    

    #增加gitlab 拉取代码的凭据
    #系统管理-Manage Credentials-新创建凭据
    范围:全局(Jenkins,nodes,items,all child items,etc)
    用户名: jenkins
    密码: 123456
    ID: 不用写
    描述:gitlab token 
    

    #具体配置
    
    #参数化构建过程
    GIT 参数:
        名称: BranchName
        参数类型: 分支
    选项参数:
        名称:domain_name
        选项:3dmodel.moviebook.cn
        描述:选择业务域名
    
    #限制项目运行的节点
        标签表达式
        jks-node-1
    
    #源码管理
    Repository URL
    http://git.xxx.com/video_ai/code/frontend.git
    
    #Credentials
    gitlab token
    
    #Branches to build
    指定分支(为空时代表any)
    ${BranchName}
    
    #构建环境
    Delete workspace before build starts  --> 勾选
    Inject environment variables to the build process  -->勾选
    Properties Content 内容:
    
    mirror_store=harbor.k8s.xxx.cn
    image_name=3dmodel/prod/000001-3dmodel/node_prod
    deploy_env=node_prod
    k8s_resource_list=threedmodel-node-prod
    namespace=3dmodel
    base_version=nginx:latest
    
    #构建
    #执行shell
    mkdir /export/jenkins/workspace/op_dockerfile/${namespace}/${deploy_env}/code/ -p
    cp -af /export/jenkins/workspace/${JOB_NAME}/. /export/jenkins/workspace/op_dockerfile/${namespace}/${deploy_env}/code/
    cd /export/jenkins/workspace/op_dockerfile/${namespace}/${deploy_env}
    
    cat > $(echo ${domain_name} |sed 's/\(.moviebook.cn\|.videoyi.com\)$//').conf << EOF
      server {
          server_name ${domain_name};
          location / {
             index index.html index.htm index.php;
             root /export/home/webroot/${namespace}/;
             proxy_read_timeout 10000;
             try_files \$uri \$uri/ /index.html;
          }
          access_log /export/home/logs/${namespace}/access.log main;
      }
    EOF
    
    #执行shell
    cd /export/jenkins/workspace/op_dockerfile/${namespace}/${deploy_env}/code/
    #echo "window.env = 'prod';" > public/env.js
    #npm install && npm run build
    source  /etc/profile
    export PATH=/opt/node-v12.19.0-linux-x64/bin/:$PATH
    node -v
    npm -v
    npm install  --unsafe-perm  && npm run build:prod
    
    
    
    #执行shell
    
    whoami
    docker_img=`docker images -a -q --filter reference=${mirror_store}/base/${base_version}`
    if [ $docker_img ] 
    then
    docker rmi ${mirror_store}/base/${base_version}
    fi
    cd /export/jenkins/workspace/op_dockerfile/${namespace}/${deploy_env}/
    cat > Dockerfile << EOF
    FROM ${mirror_store}/base/${base_version}
    ADD $(echo ${domain_name} |sed 's/\(.moviebook.cn\|.videoyi.com\)$//').conf /opt/openresty/nginx/conf/vhost/
    RUN mkdir -p /export/home/webroot/${namespace}/
    COPY code/dist/ /export/home/webroot/${namespace}/
    RUN mkdir -p /export/home/logs/${namespace}/  && chown -R nobody.nobody /export/home/webroot/${namespace}/
    WORKDIR /export/home/webroot/${namespace}/
    EXPOSE 80
    CMD ["/opt/openresty/nginx/sbin/nginx","-g","daemon off;"]
    EOF
    
    docker build . -t ${mirror_store}/${image_name}:${BUILD_TIMESTAMP}
    docker push ${mirror_store}/${image_name}:${BUILD_TIMESTAMP}
    docker rmi ${mirror_store}/${image_name}:${BUILD_TIMESTAMP}
    
    
    #执行shell
    
    mkdir -p /export/jenkins/workspace/op_dockerfile/${namespace}/${deploy_env}/yaml/
    cd /export/jenkins/workspace/op_dockerfile/${namespace}/${deploy_env}/yaml
    cat > ${k8s_resource_list}-deployment.yaml << EOF
    apiVersion: apps/v1
    kind: Deployment
    metadata:
       namespace: ${namespace}
       name: ${k8s_resource_list}
       labels:
         name: ${k8s_resource_list}
    spec:
      replicas: 1
      selector:
        matchLabels:
          name: ${k8s_resource_list}
      template:
        metadata:
         labels:
           name: ${k8s_resource_list}
        spec:
          containers:
          - name: ${k8s_resource_list}
            image: ${mirror_store}/${image_name}:${BUILD_TIMESTAMP}
            imagePullPolicy: IfNotPresent		# Always、Never、IfNotPresent
            resources:
              limits:
                cpu: "1000m"
                memory: 2048Mi
              requests:
                cpu: "500m"
                memory: 1024Mi
    EOF
    
    cat > ${k8s_resource_list}-svc.yaml << EOF
    kind: Service
    apiVersion: v1
    metadata:
      name: ${k8s_resource_list}
      namespace: ${namespace}
      labels:
        name: ${k8s_resource_list}
    spec:
      ports:
      - protocol: TCP
        port: 80
        targetPort: 80
      selector:
       name: ${k8s_resource_list}
    EOF
    
    cat > ${k8s_resource_list}-ingress.yaml << EOF          
    kind: Ingress
    apiVersion: networking.k8s.io/v1
    metadata:
      name: ${k8s_resource_list}
      namespace: ${namespace}
    spec:
      rules:
      - host: ${domain_name}
        http:
          paths:
          - pathType: Prefix
            path: /
            backend:
              service: 
                name: ${k8s_resource_list}
                port:
                  number: 80        
    EOF
    
    _kubectl='/usr/local/bin/kubectl --kubeconfig=/opt/kubernetes/config_prod'
    ${_kubectl} apply -f ${k8s_resource_list}-deployment.yaml
    ${_kubectl} apply -f ${k8s_resource_list}-svc.yaml
    ${_kubectl} apply -f ${k8s_resource_list}-ingress.yaml
    cd /export/jenkins/workspace/op_dockerfile/${namespace}/${deploy_env}/code && ls |grep -v node_modules | xargs rm -rf
    









    • k8s 查看pod 创建完成
    kubectl get pod    -n  3dmodel 
    

    权限管理与分配

    #系统管理-Manage and Assign Roles - Manage Roles - Item roles -Add
    Role to add: 3D模型分享平台-3Dmodel
    Pattern: Production/3Dmodel-Prod/.*
    
    #权限选择
    任务:Build、Cancel、Discover、Read
    SCM:Tag
    
    #应用-保存,截图如下
    

    #系统管理-Manage and Assign Roles - Assign Roles
    Global roles: 增加用户 zhao.liliang    
                  选择之前创建的 itemadmin
    
    Item roles:增加用户: zhao.liliang
                选择3D模型分享平台-3Dmodel
    
    截图如下:
    

    #退出当前账号用 zhao.liliang 账号登录jenkins,发现只有 构建权限,没有配置权限
    
    

    jenkins java slave 节点添加并构建项目

    • java slave 节点系统环境配置
    #安装docker 环境
    因后续要构建镜像,因此需要有docker 环境,参照之前装docker 的文档安装docker 环境
    
    #配置docker 可以连接harbor
    cat /etc/docker/daemon.json
    {
      "oom-score-adjust": -1000,
      "registry-mirrors": [
            "https://c6ai9izk.mirror.aliyuncs.com",
            "https://docker.mirrors.ustc.edu.cn"
      ],
      "log-driver":"json-file",
      "log-opts":{ "max-size" :"100m","max-file":"1"},
      "insecure-registries": ["harbor.dev.k8s.xxx.cn","harbor.k8s.xxx.cn"],
      "storage-driver": "overlay2",
      "storage-opts": [
        "overlay2.override_kernel_check=true"
      ],
      "exec-opts": ["native.cgroupdriver=systemd"]
    }
    
    #安装git
    yum -y install git
    
    #配置java 环境
    vim /etc/profile
    
    JAVA_HOME=/usr/local/jdk1.8.0_201
    JRE_HOME=/usr/local/jdk1.8.0_201/jre
    PATH=$JAVA_HOME/bin:$PATH:$JRE_HOME/bin:/usr/local/bin
    export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
    export JAVA_HOME JRE_HOME CLASSPATH PATH
    
    #因在后续添加slave 节点时候报错找不到java 路径,因此需要以下修改操作,重点
    cp /usr/local/jdk1.8.0_201  /usr/local/java
    ln -s /usr/local/jdk1.8.0_201/bin/java   /usr/bin/java
    
    #查看java 安装完成
    source  /etc/profile
    java -version
    
    #创建jenkins 普通用户
    useradd jenkins
    
    #创建jenkins 数据目录
    mkdir /export/jenkins/
    chown jenkins:jenkins /export/jenkins/ -R
    
    #将jenkins 用户添加到docker 组,可以用普通jenkins 用户运行docker 命令
    usermod -a -G jenkins,docker jenkins
    
    #安装kubectl 、配置连接k8s 的config 文件
    #从 k8s master节点上将Kubectl 传至nodejs 节点
    scp -qpr /usr/local/bin/kubectl   10.65.91.165:/usr/local/bin/
    
    #将k8s master 节点上的config 文件传至nodejs 节点
    cd .kube
    scp -qpr config 10.65.91.165:/root/
    
    #登录nodejs 10.65.91.165 节点
    mkdir /opt/kubernetes
    mv /root/config /opt/kubernetes
    cd /opt/kubernetes
    mv config config_prod
    
    #测试是否可以连接k8s 集群,返回node 节点信息表示可以正常连接
    /usr/local/bin/kubectl --kubeconfig=/opt/kubernetes/config_prod get node 
    
    
    # 将jenkins master 节点jenkins 普通用户的公钥传至 nodejs 普通用户jenkins 目录下,以便于jenkins master 节点的jenkins 用户可以免密登录nodejs 节点
    登录 10.65.91.164,执行
    su - jenkins
    ssh jenkins@10.65.91.165
    登录成功即可
    
    
    • jenkins master 节点添加 java jenkins slave 节点
    系统管理-节点管理-新建节点
    名称:jks-java-1
    描述:jks-java-1
    Number of executors:2
    远程工作目录:/export/jenkins
    标签:java
    用法:只允许运行绑定到这台机器的JOb
    启动方式:Launch agents via SSH
    主机:10.65.91.165
    Credential:jenkins-slave (上面已经配置好的凭据)
    Host Key Verification Strategy:Know hosts file Verification Strategy
    可用性:尽量保持代理在线
    节点属性-环境变量:键:LANG  值:UTF-8
    启动slave 节点
    


    • 部署 jenkins java 项目
    • 配置maven
    #系统管理-全局工具配置-Maven-新增maven,选择自动安装,下载两个版本的,分别是maven-3.5.0 和 maven-3.0.5,等待安装完成
    
    

    #Dashboard - 新建任务文件夹 -Production (显示名称:正式环境) - 新建Item - AGC-Video-Engine (显示名称:正式环境-AGC-Video-Engine) -新建Item(构建一个 maven 项目) - Agc-Video-Engine_Java_Prod
    
    

    #具体配置
    
    #参数化构建过程
    GIT 参数:
        名称: BranchName
        参数类型: 分支
    选项参数:
        名称:domain_name
        选项:work-order.center.xxx.cn
        描述:选择业务域名
    
    #限制项目运行的节点
        标签表达式
        jks-java-1
    
    #源码管理
    Repository URL
    http://git.xxx.com/video_ai/code/backend.git
    
    #Credentials
    gitlab token
    
    #Branches to build
    指定分支(为空时代表any)
    ${BranchName}
    
    #构建环境
    Delete workspace before build starts  --> 勾选
    Inject environment variables to the build process  -->勾选
    Properties Content 内容:
    
    mirror_store=harbor.k8s.xxx.cn
    image_name=message-center/prod/000004-message-center/java_prod
    deploy_env=java_prod
    k8s_resource_list=message-center-java-prod
    namespace=message-center
    base_version=jdk:8u201
    
    #Build
    Maven Version : maven-3.5.0
    Root Pom: pom.xml
    Goals and options:clean package  -Dmaven.test.skip=true -P prod
    
    #Post Steps
    选择 Run only if build succeeds
    
    #执行shell
    docker_img=`docker images -a -q --filter reference=${mirror_store}/base/${base_version}`
    if [ $docker_img ];then
       docker rmi ${mirror_store}/base/${base_version}
    fi
    git checkout HEAD
    artifactId=`awk '/<artifactId>[^<]+<\/artifactId>/{gsub(/<artifactId>|<\/artifactId>/,"",$1);print $1;exit;}' pom.xml`
    version=`awk '/<version>[^<]+<\/version>/{gsub(/<version>|<\/version>/,"",$1);print $1;exit;}' pom.xml`
    packaging='jar'
    
    cat > entrypoint.sh << EOF
    #!/bin/bash -
    #ln -sf /storage/resource /export/home/webroot/${namespace}/resource
    ln -sf /storage/data /data
    cd /
    java -jar app.jar >> /export/home/logs/${namespace}/${namespace}_prod.log
    EOF
    
    chmod +x entrypoint.sh
    
    echo 'center-message-system_java_prod' > prod_env
    cat > Dockerfile << EOF
    FROM ${mirror_store}/base/${base_version}
    ENV LANG en_US.utf8
    COPY target/${artifactId}-${version}.${packaging} /app.jar
    COPY prod_env /env
    RUN mkdir -p /export/home/logs/${namespace}/
    EXPOSE 80
    ADD entrypoint.sh /entrypoint.sh
    ENTRYPOINT /entrypoint.sh
    EOF
    docker build . -t ${mirror_store}/${image_name}:${BUILD_TIMESTAMP}
    docker push ${mirror_store}/${image_name}:${BUILD_TIMESTAMP}
    docker rmi ${mirror_store}/${image_name}:${BUILD_TIMESTAMP}
    
    
    #执行shell
    mkdir -p /export/jenkins/workspace/op_dockerfile/${namespace}/${deploy_env}/yaml/
    cd /export/jenkins/workspace/op_dockerfile/${namespace}/${deploy_env}/yaml
    cat > ${k8s_resource_list}-deployment.yaml << EOF
    apiVersion: apps/v1
    kind: Deployment
    metadata:
       namespace: ${namespace}
       name: ${k8s_resource_list}
       labels:
         name: ${k8s_resource_list}
    spec:
      replicas: 1
      selector:
        matchLabels:
          name: ${k8s_resource_list}
      template:
        metadata:
         labels:
           name: ${k8s_resource_list}
        spec:
          containers:
          - name: ${k8s_resource_list}
            image: ${mirror_store}/${image_name}:${BUILD_TIMESTAMP}
            imagePullPolicy: IfNotPresent		# Always、Never、IfNotPresent
            resources:
              limits:
                cpu: "1000m"
                memory: 2048Mi
              requests:
                cpu: "500m"
                memory: 1024Mi
    EOF
    
    cat > ${k8s_resource_list}-svc.yaml << EOF
    kind: Service
    apiVersion: v1
    metadata:
      name: ${k8s_resource_list}
      namespace: ${namespace}
      labels:
        name: ${k8s_resource_list}
    spec:
      ports:
      - protocol: TCP
        port: 80
        targetPort: 80
      selector:
       name: ${k8s_resource_list}
    EOF
    
    cat > ${k8s_resource_list}-ingress.yaml << EOF          
    kind: Ingress
    apiVersion: networking.k8s.io/v1
    metadata:
      name: ${k8s_resource_list}
      namespace: ${namespace}
    spec:
      rules:
      - host: ${domain_name}
        http:
          paths:
          - pathType: Prefix
            path: /
            backend:
              service: 
                name: ${k8s_resource_list}
                port:
                  number: 80        
    EOF
    
    _kubectl='/usr/local/bin/kubectl --kubeconfig=/opt/kubernetes/config_prod'
    ${_kubectl} apply -f ${k8s_resource_list}-deployment.yaml
    ${_kubectl} apply -f ${k8s_resource_list}-svc.yaml
    ${_kubectl} apply -f ${k8s_resource_list}-ingress.yaml
    
    








    • k8s 查看pod 创建完成
    kubectl get pod -n message-center
    

    至此,两个需求完成

    1.通过jenkins 完成k8s 项目的自动化构建需求,要求nodejs、java等不同开发语言运用不同的jenkins slave 节点构建
    2.通过权限设置管控开发人员的项目使用及构建

  • 相关阅读:
    关于MySql中的varchar类型
    分享15个HTML5工具
    MVC的路径查找顺序
    html5画布的旋转效果
    C#将集合快速排序
    一个不错的php验证码的类
    新学C++的for,switch和随机数
    高效率去掉js数组中重复项
    js带上框架和防止被iframe的代码
    请不要对我说“你要马上把这个小问题修改好”
  • 原文地址:https://www.cnblogs.com/lixinliang/p/15901316.html
Copyright © 2020-2023  润新知