51-基于GitHubActions的CI实战

 

在Go项目开发中,我们要频繁地执行静态代码检查、测试、编译、构建等操作。如果每一步我们都手动执行,效率低不说,还容易出错。所以,我们通常借助CI系统来自动化执行这些操作。

当前业界有很多优秀的CI系统可供选择,例如 CircleCI、TravisCI、Jenkins、CODING、GitHub Actions 等。这些系统在设计上大同小异,为了减少你的学习成本,我选择了相对来说容易实践的GitHub Actions,来给你展示如何通过CI来让工作自动化。

这一讲,我会先介绍下GitHub Actions及其用法,再向你展示一个CI示例,最后给你演示下IAM是如何构建CI任务的。

GitHub Actions的基本用法

GitHub Actions是GitHub为托管在github.com站点的项目提供的持续集成服务,于2018年10月推出。

GitHub Actions具有以下功能特性:

  • 提供原子的actions配置和组合actions的workflow配置两种能力。
  • 全局配置基于YAML配置,兼容主流CI/CD工具配置。
  • Actions/Workflows基于事件触发,包括Event restrictions、Webhook events、Scheduled events、External events。
  • 提供可供运行的托管容器服务,包括Docker、VM,可运行Linux、macOS、Windows主流系统。
  • 提供主流语言的支持,包括Node.js、Python、Java、Ruby、PHP、Go、Rust、.NET。
  • 提供实时日志流程,方便调试。
  • 提供平台内置的Actions与第三方提供的Actions,开箱即用。

GitHub Actions的基本概念

在构建持续集成任务时,我们会在任务中心完成各种操作,比如克隆代码、编译代码、运行单元测试、构建和发布镜像等。GitHub把这些操作称为Actions。

Actions在很多项目中是可以共享的,GitHub允许开发者将这些可共享的Actions上传到GitHub的官方Actions市场,开发者在Actions市场中可以搜索到他人提交的 Actions。另外,还有一个 awesome actions 的仓库,里面也有不少的Action可供开发者使用。如果你需要某个 Action,不必自己写复杂的脚本,直接引用他人写好的 Action 即可。整个持续集成过程,就变成了一个 Actions 的组合。

Action其实是一个独立的脚本,可以将Action存放在GitHub代码仓库中,通过<userName>/<repoName>的语法引用 Action。例如,actions/checkout@v2表示https://github.com/actions/checkout这个仓库,tag是v2。actions/checkout@v2也代表一个 Action,作用是安装 Go编译环境。GitHub 官方的 Actions 都放在 github.com/actions 里面。

GitHub Actions 有一些自己的术语,下面我来介绍下。

  • workflow(工作流程):一个 .yml 文件对应一个 workflow,也就是一次持续集成。一个 GitHub 仓库可以包含多个 workflow,只要是在 .github/workflow 目录下的 .yml 文件都会被 GitHub 执行。
  • job(任务):一个 workflow 由一个或多个 job 构成,每个 job 代表一个持续集成任务。
  • step(步骤):每个 job 由多个 step 构成,一步步完成。
  • action(动作):每个 step 可以依次执行一个或多个命令(action)。
  • on:一个 workflow 的触发条件,决定了当前的 workflow 在什么时候被执行。

workflow文件介绍

GitHub Actions 配置文件存放在代码仓库的.github/workflows目录下,文件后缀为.yml,支持创建多个文件,文件名可以任意取,比如iam.yml。GitHub 只要发现.github/workflows目录里面有.yml文件,就会自动运行该文件,如果运行过程中存在问题,会以邮件的形式通知到你。

workflow 文件的配置字段非常多,如果你想详细了解,可以查看官方文档。这里,我来介绍一些基本的配置字段。

  1. name

name字段是 workflow 的名称。如果省略该字段,默认为当前 workflow 的文件名。

name: GitHub Actions Demo
  1. on

on字段指定触发 workflow 的条件,通常是某些事件。

on: push

上面的配置意思是,push事件触发 workflow。on字段也可以是事件的数组,例如:

on: [push, pull_request]

上面的配置意思是,push事件或pull_request事件都可以触发 workflow。

想了解完整的事件列表,你可以查看官方文档。除了代码库事件,GitHub Actions 也支持外部事件触发,或者定时运行。

  1. on.<push|pull_request>.<tags|branches>

指定触发事件时,我们可以限定分支或标签。

on:
  push:
    branches:
      - master

上面的配置指定,只有master分支发生push事件时,才会触发 workflow。

  1. jobs.<job_id>.name

workflow 文件的主体是jobs字段,表示要执行的一项或多项任务。

jobs字段里面,需要写出每一项任务的job_id,具体名称自定义。job_id里面的name字段是任务的说明。

jobs:
  my_first_job:
    name: My first job
  my_second_job:
    name: My second job

上面的代码中,jobs字段包含两项任务,job_id分别是my_first_jobmy_second_job

  1. jobs.<job_id>.needs

needs字段指定当前任务的依赖关系,即运行顺序。

jobs:
  job1:
  job2:
    needs: job1
  job3:
    needs: [job1, job2]

上面的代码中,job1必须先于job2完成,而job3等待job1job2完成后才能运行。因此,这个 workflow 的运行顺序为:job1job2job3

  1. jobs.<job_id>.runs-on

runs-on字段指定运行所需要的虚拟机环境,它是必填字段。目前可用的虚拟机如下:

  • ubuntu-latest、ubuntu-18.04或ubuntu-16.04。
  • windows-latest、windows-2019或windows-2016。
  • macOS-latest或macOS-10.14。

下面的配置指定虚拟机环境为ubuntu-18.04

runs-on: ubuntu-18.04
  1. jobs.<job_id>.steps

steps字段指定每个 Job 的运行步骤,可以包含一个或多个步骤。每个步骤都可以指定下面三个字段。

  • jobs.<job_id>.steps.name:步骤名称。
  • jobs.<job_id>.steps.run:该步骤运行的命令或者 action。
  • jobs.<job_id>.steps.env:该步骤所需的环境变量。

下面是一个完整的 workflow 文件的范例:

name: Greeting from Mona
on: push

jobs:
  my-job:
    name: My Job
    runs-on: ubuntu-latest
    steps:
    - name: Print a greeting
      env:
        MY_VAR: Hello! My name is
        FIRST_NAME: Lingfei
        LAST_NAME: Kong
      run: |
        echo $MY_VAR $FIRST_NAME $LAST_NAME.

上面的代码中,steps字段只包括一个步骤。该步骤先注入三个环境变量,然后执行一条 Bash 命令。

  1. uses

uses 可以引用别人已经创建的 actions,就是上面说的 actions 市场中的 actions。引用格式为userName/repoName@verison,例如uses: actions/setup-go@v1

  1. with

with 指定actions的输入参数。每个输入参数都是一个键/值对。输入参数被设置为环境变量,该变量的前缀为 INPUT_,并转换为大写。

这里举个例子:我们定义 hello_world 操作所定义的三个输入参数(first_namemiddle_name 和 last_name),这些输入变量将被 hello-world 操作作为 INPUT_FIRST_NAMEINPUT_MIDDLE_NAME 和 INPUT_LAST_NAME 环境变量使用。

jobs:
  my_first_job:
    steps:
      - name: My first step
        uses: actions/hello_world@master
        with:
          first_name: Lingfei
          middle_name: Go
          last_name: Kong
  1. run

run指定执行的命令。可以有多个命令,例如:

- name: Build
      run: |
      go mod tidy
      go build -v -o helloci .
  1. id

id是step的唯一标识。

GitHub Actions的进阶用法

上面,我介绍了GitHub Actions的一些基本知识,这里我再介绍下GitHub Actions的进阶用法。

为工作流加一个Badge

在action的面板中,点击Create status badge就可以复制Badge的Markdown内容到README.md中。

之后,我们就可以直接在README.md中看到当前的构建结果:

图片

使用构建矩阵

如果我们想在多个系统或者多个语言版本上测试构建,就需要设置构建矩阵。例如,我们想在多个操作系统、多个Go版本下跑测试,可以使用如下workflow配置:

name: Go Test

on: [push, pull_request]

jobs:

  helloci-build:
    name: Test with go ${{ matrix.go_version }} on ${{ matrix.os }}
    runs-on: ${{ matrix.os }}

    strategy:
      matrix:
        go_version: [1.15, 1.16]
        os: [ubuntu-latest, macOS-latest]

    steps:

      - name: Set up Go ${{ matrix.go_version }}
        uses: actions/setup-go@v2
        with:
          go-version: ${{ matrix.go_version }}
        id: go

上面的workflow配置,通过strategy.matrix配置了该工作流程运行的环境矩阵(格式为go_version.os):ubuntu-latest.1.15ubuntu-latest.1.16macOS-latest.1.15macOS-latest.1.16。也就是说,会在4台不同配置的服务器上执行该workflow。

使用Secrets

在构建过程中,我们可能需要用到ssh或者token等敏感数据,而我们不希望这些数据直接暴露在仓库中,此时就可以使用secrets

我们在对应项目中选择Settings-> Secrets,就可以创建secret,如下图所示:

图片

配置文件中的使用方法如下:

name: Go Test
on: [push, pull_request]
jobs:
  helloci-build:
    name: Test with go
    runs-on: [ubuntu-latest]
    environment:
      name: helloci
    steps:
      - name: use secrets
        env:
          super_secret: ${{ secrets.YourSecrets }}

secret name不区分大小写,所以如果新建secret的名字是name,使用时用 secrets.name 或者 secrets.Name 都是可以的。而且,就算此时直接使用 echo 打印 secret , 控制台也只会打印出*来保护secret。
这里要注意,你的secret是属于某一个环境变量的,所以要指明环境的名字:environment.name。上面的workflow配置中的secrets.YourSecrets属于helloci环境。

使用Artifact保存构建产物

在构建过程中,我们可能需要输出一些构建产物,比如日志文件、测试结果等。这些产物可以使用Github Actions Artifact 来存储。你可以使用action/upload-artifact 和 download-artifact 进行构建参数的相关操作。

这里我以输出Jest测试报告为例来演示下如何保存Artifact产物。Jest测试后的测试产物是coverage:

steps:
      - run: npm ci
      - run: npm test

      - name: Collect Test Coverage File
        uses: actions/upload-artifact@v1.0.0
        with:
          name: coverage-output
          path: coverage

执行成功后,我们就能在对应action面板看到生成的Artifact:

图片

GitHub Actions实战

上面,我介绍了GitHub Actions的用法,接下来我们就来实战下,看下使用GitHub Actions的6个具体步骤。

第一步,创建一个测试仓库。

登陆GitHub官网,点击New repository创建,如下图所示:

图片

这里,我们创建了一个叫helloci的测试项目。

第二步,将新的仓库 clone 下来,并添加一些文件:

$ git clone https://github.com/marmotedu/helloci

你可以克隆marmotedu/helloci,并将里面的文件拷贝到你创建的项目仓库中。

第三步,创建GitHub Actions workflow配置目录:

$ mkdir -p .github/workflows                     

第四步,创建GitHub Actions workflow配置。

.github/workflows目录下新建helloci.yml文件,内容如下:

name: Go Test

on: [push, pull_request]

jobs:

  helloci-build:
    name: Test with go ${{ matrix.go_version }} on ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    environment:
      name: helloci

    strategy:
      matrix:
        go_version: [1.16]
        os: [ubuntu-latest]

    steps:

      - name: Set up Go ${{ matrix.go_version }}
        uses: actions/setup-go@v2
        with:
          go-version: ${{ matrix.go_version }}
        id: go

      - name: Check out code into the Go module directory
        uses: actions/checkout@v2

      - name: Tidy
        run: |
          go mod tidy

      - name: Build
        run: |
          go build -v -o helloci .

      - name: Collect main.go file
        uses: actions/upload-artifact@v1.0.0
        with:
          name: main-output
          path: main.go

      - name: Publish to Registry
        uses: elgohr/Publish-Docker-GitHub-Action@master
        with:
          name: ccr.ccs.tencentyun.com/marmotedu/helloci:beta  # docker image 的名字
          username: ${{ secrets.DOCKER_USERNAME}} # 用户名
          password: ${{ secrets.DOCKER_PASSWORD }} # 密码
          registry: ccr.ccs.tencentyun.com # 腾讯云Registry
          dockerfile: Dockerfile # 指定 Dockerfile 的位置
          tag_names: true # 是否将 release 的 tag 作为 docker image 的 tag

上面的workflow文件定义了当GitHub仓库有pushpull_request事件发生时,会触发GitHub Actions工作流程,流程中定义了一个任务(Job)helloci-build,Job中包含了多个步骤(Step),每个步骤又包含一些动作(Action)。

上面的workflow配置会按顺序执行下面的6个步骤。

  1. 准备一个Go编译环境。
  2. 从marmotedu/helloci下载源码。
  3. 添加或删除缺失的依赖包。
  4. 编译Go源码。
  5. 上传构建产物。
  6. 构建镜像,并将镜像push到ccr.ccs.tencentyun.com/marmotedu/helloci:beta

第五步,在push代码之前,我们需要先创建DOCKER_USERNAMEDOCKER_PASSWORD secret。

其中,DOCKER_USERNAME保存腾讯云镜像服务(CCR)的用户名,DOCKER_PASSWORD保存CCR的密码。我们将这两个secret保存在helloci Environments中,如下图所示:

图片

第六步,将项目push到GitHub,触发workflow工作流:

$ git add .
$ git push origin master

打开我们的仓库 Actions 标签页,可以发现GitHub Actions workflow正在执行:

图片

等workflow执行完,点击 Go Test 进入构建详情页面,在详情页面能够看到我们的构建历史:

图片

然后,选择其中一个构建记录,查看其运行详情(具体可参考chore: update step name Go Test #10):

图片

你可以看到,Go Test工作流程执行了6个Job,每个Job执行了下面这些自定义Step:

  1. Set up Go 1.16。
  2. Check out code into the Go module directory。
  3. Tidy。
  4. Build。
  5. Collect main.go file。
  6. Publish to Registry。

其他步骤是GitHub Actions自己添加的步骤:Setup JobPost Check out code into the Go module directoryComplete job。点击每一个步骤,你都能看到它们的详细输出。

IAM GitHub Actions实战

接下来,我们再来看下IAM项目的GitHub Actions实战。

假设IAM项目根目录为 ${IAM_ROOT},它的workflow配置文件为:

$ cat ${IAM_ROOT}/.github/workflows/iamci.yaml
name: IamCI

on:
  push:
    branchs:
    - '*'
  pull_request:
    types: [opened, reopened]

jobs:

  iamci:
    name: Test with go ${{ matrix.go_version }} on ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    environment:
      name: iamci

    strategy:
      matrix:
        go_version: [1.16]
        os: [ubuntu-latest]

    steps:

      - name: Set up Go ${{ matrix.go_version }}
        uses: actions/setup-go@v2
        with:
          go-version: ${{ matrix.go_version }}
        id: go

      - name: Check out code into the Go module directory
        uses: actions/checkout@v2

      - name: Run go modules Tidy
        run: |
          make tidy

      - name: Generate all necessary files, such as error code files
        run: |
          make gen

      - name: Check syntax and styling of go sources
        run: |
          make lint

      - name: Run unit test and get test coverage
        run: |
          make cover

      - name: Build source code for host platform
        run: |
          make build

      - name: Collect Test Coverage File
        uses: actions/upload-artifact@v1.0.0
        with:
          name: main-output
          path: _output/coverage.out

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v1

      - name: Login to DockerHub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build docker images for host arch and push images to registry
        run: |
          make push

上面的workflow依次执行了以下步骤:

  1. 设置Go编译环境。
  2. 下载IAM项目源码。
  3. 添加/删除不需要的Go包。
  4. 生成所有的代码文件。
  5. 对IAM源码进行静态代码检查。
  6. 运行单元测试用例,并计算单元测试覆盖率是否达标。
  7. 编译代码。
  8. 收集构建产物_output/coverage.out
  9. 配置Docker构建环境。
  10. 登陆DockerHub。
  11. 构建Docker镜像,并push到DockerHub。

IamCI workflow运行历史如下图所示:

图片

IamCI workflow的其中一次工作流程运行结果如下图所示:

图片

总结

在Go项目开发中,我们需要通过CI任务来将需要频繁操作的任务自动化,这不仅可以提高开发效率,还能减少手动操作带来的失误。这一讲,我选择了最易实践的GitHub Actions,来给你演示如何构建CI任务。

GitHub Actions支持通过push事件来触发CI流程。一个CI流程其实就是一个workflow,workflow中包含多个任务,这些任务是可以并行执行的。一个任务又包含多个步骤,每一步又由多个动作组成。动作(Action)其实是一个命令/脚本,用来完成我们指定的任务,如编译等。

因为GitHub Actions内容比较多,这一讲只介绍了一些核心的知识,更详细的GitHub Actions教程,你可以参考 官方中文文档。

课后练习

  1. 使用CODING实现IAM的CI任务,并思考下:GitHub Actions和CODING在CI任务构建上,有没有本质的差异?
  2. 这一讲,我们借助GitHub Actions实现了CI,请你结合前面所学的知识,实现IAM的CD功能。欢迎提交Pull Request。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

✌2024/4/3—力扣—无重复字符的最长子串

代码实现&#xff1a; 解法一&#xff1a;暴力法 int lengthOfLongestSubstring(char *s) {int hash[256] {0};int num 0;for (int i 0; i < strlen(s); i) {int count 0;for (int j i; j < strlen(s); j) {if (hash[s[j]] 0) {hash[s[j]];count;num num > cou…

基于 WebRTC 实现的点对点文件传输和音视频聊天工具 | 开源日报 No.220

tl-open-source/tl-rtc-file Stars: 2.1k License: MIT tl-rtc-file 是一个基于 WebRTC 的文件传输工具&#xff0c;支持跨终端、不限平台的在线文件传输。它提供了丰富的功能和特性&#xff1a; 分片传输&#xff1a;支持大型文件的分片传输&#xff0c;确保高效稳定地完成上…

OSPF的P2P和Broadcast

OSPF为什么会有P2P和BROADCAST两种类型 OSPF&#xff08;开放最短路径优先&#xff09;协议中存在P2P&#xff08;点对点&#xff09;和BROADCAST&#xff08;广播多路访问&#xff09;两种网络类型&#xff0c;主要是为了适应不同类型的网络环境和需求。具体分析如下&#xf…

Prototype 原型

意图 用原型实例指定创建对象的种类&#xff0c;并且通过复制这些原型创建新的对象。 结构 Prototype声明一个复制自身的接口。ConcretePrototype实现一个复制自身的操作。Client让一个原型复制自身从而创建一个新的对象。 适用性 当一个系统应该独立于他的产品创建、构成和…

设备基础命令,路由基础

直连路由 静态路由 动态路由 根据路由器学习路由信息、生成并维护路由表的方法包括直连路由(Direct)、静态路由(Static)和动态路由(Dynamic)。直连路由&#xff1a;路由器接口所连接的子网的路由方式称为直连路由&#xff1b;非直连路由&#xff1a;通过路由协议从别的路由器…

docker exec 命令提示:Error: No such container: /bin/bash

虽然是低级错误&#xff0c;但是还是记录一下吧。。。。。。。。 这个容器运行起来了&#xff0c;docker ps 是可以查询到的 但是 我想进入 容器内部时就出现了&#xff1a; docker exec -it /bin/bash e51b4dcdf51a Error: No such container: /bin/bash 开始以为是容器内部…

C语言 | Leetcode C语言题解之第22题括号生成

题目&#xff1a; 题解&#xff1a; // 回溯法求解 #define MAX_SIZE 1430 // 卡特兰数: 1, 1, 2, 5, 14, 42, 132, 429, 1430 void generate(int left, int right, int n, char *str, int index, char **result, int *returnSize) {if (index 2 * n) { // 当前长度已达2nre…

多线程的入门(五)线程池的保活策略

线程池是如何保活的呢&#xff1f;通过对源码的分析得出&#xff0c;线程池通过阻塞队列&#xff0c;与关闭工作线程后新生成空闲线程实现的保活策略源代码如下&#xff1a; runkworker&#xff08;&#xff09;方法的getTask&#xff08;&#xff09;方法中有这样一段代码&…

FMix: Enhancing Mixed Sample Data Augmentation 论文阅读

1 Abstract 近年来&#xff0c;混合样本数据增强&#xff08;Mixed Sample Data Augmentation&#xff0c;MSDA&#xff09;受到了越来越多的关注&#xff0c;出现了许多成功的变体&#xff0c;例如MixUp和CutMix。通过研究VAE在原始数据和增强数据上学习到的函数之间的互信息…

避免使用第三方工具完成电脑环境检测

0. 简介 在之前配置各种深度学习环境的时候经常需要先检测一下电脑的软硬件环境&#xff0c;其实整个过程比较重复和固定&#xff0c;所以我们是否有可能一键检测Python版本、PIP版本、Conda版本、CUDA版本、电脑系统、CPU核数、CPU频率、内存、硬盘等内容这是很多Deepper苦恼…

Nginx+Keepalived Kubernetes 负载均衡

部署NginxKeepalived高可用负载均衡器 kube-apiserver高可用架构图&#xff1a; Nginx是一个主流Web服务和反向代理服务器&#xff0c;这里用四层实现对apiserver实现负载均衡。Keepalived是一个主流高可用软件&#xff0c;基于VIP绑定实现服务器双机热备&#xff0c;在上述拓…

关于部署ELK和EFLKD的相关知识

文章目录 一、ELK日志分析系统1、ELK简介1.2 ElasticSearch1.3 Logstash1.4 Kibana&#xff08;展示数据可视化界面&#xff09;1.5 Filebeat 2、使用ELK的原因3、完整日志系统的基本特征4、ELK的工作原理 二、部署ELK日志分析系统1、服务器配置2、关闭防火墙3、ELK ElasticSea…

React + three.js 3D模型骨骼绑定

系列文章目录 React 使用 three.js 加载 gltf 3D模型 | three.js 入门React three.js 3D模型骨骼绑定React three.js 3D模型面部表情控制 项目代码(github)&#xff1a;https://github.com/couchette/simple-react-three-skeleton-demo 项目代码(gitcode)&#xff1a;https:…

这几个方面需要注意,减少服务器被入侵

网络时代&#xff0c;服务器和计算机不时地遭受入侵和攻击&#xff0c;给人们带来了无法预料的重大损失。诸如服务器入侵、数据盗窃和勒索软件等事件频繁发生&#xff0c;这令许多企业和游戏开发团队备受困扰。通过总结经验和吸取教训&#xff0c;我们必须汲取教益&#xff0c;…

Linux C应用编程:MQTT物联网

1 MQTT通信协议 MQTT&#xff08;Message Queuing Telemetry Transport&#xff0c;消息队列遥测传 输&#xff09;是一种基于客户端-服务端架构的消息传输协议&#xff0c;如今&#xff0c;MQTT 成为了最受欢迎的物联网协议&#xff0c;已广泛应用于车联网、智能家居、即时聊…

pycharm debug 的时候 waiting for process detach

当你使用pycharm debug或者run的时候&#xff0c;突然出现了点不动&#xff0c;然后一直显示&#xff1a;waiting for process detach 可能是以下问题&#xff1a; 1、需要设置Gevent compatible pycharm一直没显示运行步骤&#xff0c;只是出现waiting for process detach-C…

正则表达式---【Python版】

目录 前言 一.正则表达式概括 1.1简介 1.2使用场景 二.正则表达式语法 2.1基本匹配 2.2元字符 2.2.1点运算符. 2.2.2字符类[] 2.2.3否定字符类 2.2.4*号 2.2.5号 2.2.6&#xff1f;号 2.2.7{}号 2.2.8()号 2.2.9|或运算 2.2.10转码特殊字符\ 2.2.11^和$ 2.3简…

【论文阅读】Digging Into Self-Supervised Monocular Depth Estimation

论文&#xff1a;https://arxiv.org/pdf/1806.01260.pdf 代码&#xff1a;https://github.com/nianticlabs/monodepth2 Q: 这篇论文试图解决什么问题&#xff1f; A: 这篇论文试图解决的问题是如何提高仅使用单目图像进行深度估计的性能。具体来说&#xff0c;它关注的是如何…

Django开发:计划表网页全流程

Hello , 我是"小恒不会java"。考虑到django官网案例的代码对新手不太友好 那我将一个案例从思路到代码都简单完整的摆出来&#xff0c; 使用过django的各位可cv即可&#xff0c;不会django跟着走操作就能跑起来 项目展示 本案例在GitHub已经开源&#xff0c;可在后台…

云HIS医院管理系统源码 SaaS模式 B/S架构 基于云计算技术

一、系统概述 云HIS系统源码是一款满足基层医院各类业务需要的健康云产品。该系统能帮助基层医院完成日常各类业务&#xff0c;提供病患预约挂号支持、收费管理、病患问诊、电子病历、开药发药、住院检查、会员管理、财务管理、统计查询、医生工作站和护士工作站等一系列常规功…