news 2026/9/10 2:32:42

Django在线学习平台生产级骨架:多APP架构与部署实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Django在线学习平台生产级骨架:多APP架构与部署实战

简介:本资源是一套完整的基于Django框架开发的在线学习平台毕业设计项目,面向计算机类专业本科生及初学者,解决课程设计、毕业设计选题难、工程实践缺范例、Web全栈开发流程不清晰等实际问题。压缩包共783个文件,涵盖97个Python后端逻辑文件、118个HTML前端页面、182个JS交互脚本、201张PNG界面截图与54个CSS样式文件,辅以数据库文件(.db)、国际化语言包(.po/.mo)及基础静态资源(字体、图标、动画库),整体体积仅5.33MB,结构规范、模块完整。已有152人下载学习,适合直接用于毕设答辩、课设交付或二次开发——项目已通过本地测试运行,含详细使用说明文档,并预置Bootstrap、Video.js、Font Awesome等主流前端组件,便于快速理解MVT架构、用户权限管理、课程发布与学习记录等核心功能实现逻辑。

1. 这不是又一个“Django博客模板”,而是一个可直接部署的在线学习平台生产级骨架

你打开 ZIP 包,看到manage.pyrequirements.txttemplates/和一堆.css文件时,第一反应可能是“又一个课程设计Demo”。但真正解压后运行python manage.py runserver,登录后台看到「课程管理」「用户学习进度统计」「视频播放页自动记录观看时长」——这些不是占位符,而是已连通数据库、带权限控制、含前端交互逻辑的真实模块。这个项目不是为展示 Django ORM 写法而存在,它解决的是毕业设计中最棘手的三类问题:如何把零散功能(用户注册、课程发布、视频播放、作业提交)组织成闭环业务流;如何让 Bootstrap + Font Awesome 的静态资源与 Django 模板系统不打架;以及怎样在无运维经验前提下,用最小改动完成本地调试→宝塔部署→Nginx 反向代理的全链路验证。适合软件工程、计科、人工智能等专业学生直接用于毕设答辩,也适合刚学完 Django MTV 架构、正卡在“写完视图却不会配 URL 和模板继承”的开发者,它把urls.py路由分组、settings.py静态文件路径、base.html{% block content %}的嵌套层级,全部固化为可复用的结构。


2. Django 项目结构解析:从 manage.py 到多 APP 协同机制

2.1 项目目录层级与核心 APP 职责划分

解压后目录结构呈现典型的 Django 多 APP 设计模式:

online_learning/ ├── manage.py ├── online_learning/ # 主配置包(含 settings.py, urls.py) ├── users/ # 用户模块(注册、登录、个人信息) ├── courses/ # 课程模块(分类、详情、章节、视频) ├── video/ # 视频播放模块(播放器集成、进度记录) ├── homework/ # 作业模块(提交、批改、成绩) ├── static/ # 前端资源总入口(含你提到的 bootstrap.css 等) ├── templates/ # 全局模板基座 └── db.sqlite3 # 开发环境默认数据库

提示:不要手动修改online_learning/settings.py中的INSTALLED_APPS顺序。该项目已按依赖关系排序:users必须在courses之前(因课程表外键关联用户),video依赖courses(视频属于某课程章节)。若新增 APP,需插入到其依赖项之后,否则python manage.py makemigrations会报RelatedObjectDoesNotExist错误。

2.2 settings.py 关键配置项深度拆解

该配置文件已针对毕业设计场景做了三项关键预设,远超官方默认值:

2.2.1 静态资源路径与 CDN 兼容性配置
# online_learning/settings.py STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') # 用于 collectstatic 打包 STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static'), # 开发时直接读取此目录 ] # 关键:Bootstrap 和 Font Awesome 的 CSS 已被正确归入 static/css/ # 无需在 template 中写 <link href="/static/css/bootstrap.min.css"> —— # Django 模板中统一使用 {% static 'css/bootstrap.min.css' %}

参数说明STATICFILES_DIRS是开发阶段资源查找路径,STATIC_ROOT是生产环境collectstatic命令输出目录。若你后续用宝塔部署,需在宝塔站点设置中将staticfiles目录映射为/static/URL 前缀,否则所有 CSS/JS 404。

2.2.2 数据库配置与 SQLite 迁移兼容性
# online_learning/settings.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } # 但已预留 MySQL 配置模板(注释状态): # 'ENGINE': 'django.db.backends.mysql', # 'NAME': 'online_learning_db', # 'USER': 'root', # 'PASSWORD': 'your_password', # 'HOST': '127.0.0.1', # 'PORT': '3306', # 'OPTIONS': {'charset': 'utf8mb4'},

注意:SQLite 在开发阶段足够,但答辩演示或部署到服务器时,必须切换为 MySQL。切换步骤:① 创建 MySQL 数据库CREATE DATABASE online_learning_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;;② 取消settings.py中 MySQL 配置段注释;③ 执行python manage.py migrate --database=default(首次迁移需先python manage.py makemigrations);④ 若已有 SQLite 数据,用django-dbbackup导出再导入 MySQL(非必需,新项目可跳过)。

2.2.3 模板加载器与 Bootstrap 继承链
# online_learning/settings.py TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [BASE_DIR / 'templates'], # 全局模板根目录 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', 'django.template.context_processors.static', # 必须启用,否则 {% static %} 失效 ], }, }, ]

逻辑说明DIRS指向templates/,所有 APP 的模板均继承自templates/base.html。例如courses/templates/courses/course_detail.html中第一行是{% extends "base.html" %},而base.html已预置<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">{% load static %}标签。这意味着你添加新页面时,只需专注业务逻辑,无需重复写导航栏和 CSS 引入。

2.3 URL 路由分层设计:避免 urlpatterns 堆砌

项目采用 Django 推荐的include()分层路由,主urls.py仅做入口分发:

# online_learning/urls.py from django.contrib import admin from django.urls import path, include from django.conf import settings from django.conf.urls.static import static urlpatterns = [ path('admin/', admin.site.urls), path('users/', include('users.urls')), # 用户相关路由 path('courses/', include('courses.urls')), # 课程相关路由 path('video/', include('video.urls')), # 视频播放路由 path('homework/', include('homework.urls')), # 作业路由 path('', include('home.urls')), # 首页路由(未在摘要中列出,但实际存在) ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

为什么这样设计?

  • 避免单个urls.py文件超过 200 行导致维护困难;
  • 每个 APP 自己管理urls.py,例如courses/urls.py中定义path('course/<int:pk>/', views.CourseDetailView.as_view(), name='course_detail'),外部调用时用reverse('courses:course_detail', kwargs={'pk': 1})
  • + static(...)是开发阶段必需,否则runserver无法提供静态文件服务(生产环境由 Nginx 处理)。

3. 前端资源集成实战:Bootstrap 5 + Font Awesome 6 与 Django 模板协同

3.1 CSS 文件定位与版本验证

项目提供的 CSS 文件列表(bootstrap.css,bootstrap.min.css,font-awesome.css,font-awesome.min.css)实际对应以下技术栈:

文件名实际版本用途是否必须
bootstrap.min.cssBootstrap 5.3.x响应式栅格、按钮、表单样式✅ 必须
font-awesome.min.cssFont Awesome 6.4.x图标字体(如fa-solid fa-play✅ 必须
animate.cssv4.1.0页面元素入场动画(如课程卡片淡入)⚠️ 可删减
aui.cssAUI 2.0(阿里内部 UI 库)已被注释,未实际引用❌ 可删除

验证方法:在浏览器开发者工具中,打开任意页面(如http://127.0.0.1:8000/courses/),检查<head>中加载的 CSS 路径。若看到http://127.0.0.1:8000/static/css/bootstrap.min.css返回 200,则说明STATICFILES_DIRS配置正确;若返回 404,检查static/css/目录是否存在该文件,且文件名大小写是否匹配(Linux 系统区分大小写)。

3.2 模板中 Bootstrap 组件的标准化用法

以课程列表页为例(courses/templates/courses/course_list.html),关键代码如下:

<!-- courses/templates/courses/course_list.html --> {% extends "base.html" %} {% load static %} {% block content %} <div class="container mt-4"> <h2 class="text-center mb-4">全部课程</h2> <div class="row"> {% for course in course_list %} <div class="col-md-4 mb-4"> <div class="card h-100 shadow-sm"> <img src="{{ course.cover.url }}" class="card-img-top" alt="{{ course.title }}" style="height: 180px; object-fit: cover;"> <div class="card-body"> <h5 class="card-title">{{ course.title }}</h5> <p class="card-text text-muted small">{{ course.description|truncatewords:20 }}</p> <div class="d-flex justify-content-between align-items-center"> <span class="badge bg-primary">{{ course.category.name }}</span> <span class="text-success"><i class="fas fa-clock me-1"></i>{{ course.total_duration }}分钟</span> </div> </div> <div class="card-footer"> <a href="{% url 'courses:course_detail' pk=course.pk %}" class="btn btn-outline-primary w-100"> <i class="fas fa-book-open me-1"></i>进入学习 </a> </div> </div> </div> {% endfor %} </div> </div> {% endblock %}

参数说明与踩坑点

  • {{ course.cover.url }}coverImageField,Django 自动拼接MEDIA_URL(需在settings.py中配置MEDIA_URL = '/media/'MEDIA_ROOT = os.path.join(BASE_DIR, 'media'));
  • <i class="fas fa-book-open">fas表示 Font Awesome Solid 图标,fa-book-open是图标名,必须确保font-awesome.min.css已加载;
  • class="col-md-4":Bootstrap 5 栅格系统,md表示中屏及以上宽度每行 3 列;若想改为每行 2 列,改col-md-6即可;
  • style="height: 180px; object-fit: cover;":强制图片高度并裁剪,避免封面图比例不一导致卡片高度错乱。

3.3 视频播放模块与 video-js.min.css 的联动配置

video/APP 使用video-js播放器(而非 HTML5 原生<video>),其 CSS 文件video-js.min.css已预置在static/css/。播放页模板关键代码:

<!-- video/templates/video/player.html --> {% extends "base.html" %} {% load static %} {% block content %} <div class="container mt-4"> <div class="row"> <div class="col-12"> <div class="video-js vjs-default-skin vjs-big-play-centered" ># users/models.py from django.contrib.auth.models import AbstractUser from django.db import models class CustomUser(AbstractUser): phone = models.CharField(max_length=11, blank=True, null=True, verbose_name="手机号") avatar = models.ImageField(upload_to='avatars/', blank=True, null=True, verbose_name="头像") student_id = models.CharField(max_length=20, blank=True, null=True, verbose_name="学号") # 毕设专用 is_student = models.BooleanField(default=True, verbose_name="是否为学生") # 区分学生/教师角色

为什么不用 Profile 模型?
项目选择直接继承AbstractUser,因为毕业设计需在注册表单中一次性收集学号、手机号,若用OneToOneField关联 Profile,则需额外处理表单保存逻辑(user.profile.phone = ...),增加复杂度。当前设计下,forms.py可直接继承UserCreationForm并添加字段。

注册视图users/views.py关键逻辑:

# users/views.py from django.contrib.auth import login from django.shortcuts import render, redirect from django.contrib.auth.forms import UserCreationForm from .forms import CustomUserCreationForm def register(request): if request.method == 'POST': form = CustomUserCreationForm(request.POST) if form.is_valid(): user = form.save() # 此处已保存 student_id, phone 等字段 login(request, user) # 注册后自动登录 return redirect('home:index') else: form = CustomUserCreationForm() return render(request, 'users/register.html', {'form': form})

参数说明CustomUserCreationFormusers/forms.py中重写了Meta.model = CustomUser,并显式声明fields = ('username', 'email', 'password1', 'password2', 'student_id', 'phone')。若你删除student_id字段,需同步修改fields元组,否则表单渲染报错。

4.2 课程学习进度的实时记录与查询

video/models.py定义了进度模型,支持断点续播:

# video/models.py from django.db import models from django.contrib.auth import get_user_model from courses.models import Chapter User = get_user_model() class UserProgress(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) chapter = models.ForeignKey(Chapter, on_delete=models.CASCADE) last_watch_time = models.IntegerField(default=0, verbose_name="最后观看时间(秒)") is_completed = models.BooleanField(default=False, verbose_name="是否完成") updated_at = models.DateTimeField(auto_now=True) class Meta: unique_together = ('user', 'chapter') # 防止重复记录

对应视图video/views.py处理 AJAX 进度提交:

# video/views.py from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator import json @method_decorator(csrf_exempt, name='dispatch') class ProgressView(View): def post(self, request): try: data = json.loads(request.body) chapter_id = data.get('chapter_id') current_time = data.get('current_time', 0) progress, created = UserProgress.objects.update_or_create( user=request.user, chapter_id=chapter_id, defaults={'last_watch_time': current_time} ) # 当前观看时间超过视频总时长 90%,标记为完成 chapter = Chapter.objects.get(id=chapter_id) if current_time > chapter.video_duration * 0.9 and not progress.is_completed: progress.is_completed = True progress.save() return JsonResponse({'status': 'success'}) except Exception as e: return JsonResponse({'status': 'error', 'message': str(e)}, status=400)

关键点解析

  • @method_decorator(csrf_exempt, name='dispatch'):因前端用fetch发送 POST,且X-CSRFToken已传递,此处可保留 CSRF 保护,但为简化演示,项目采用csrf_exempt;生产环境应改用ensure_csrf_cookie+ 前端读取 cookie;
  • update_or_create:避免并发写入时创建重复记录;
  • chapter.video_duration需在Chapter模型中定义(项目已实现),单位为秒,由管理员上传视频时手动填写或通过 FFmpeg 后台任务自动提取。

4.3 作业模块(homework)的提交与批改闭环

homework/models.py设计支持多类型作业(编程题、简答题、文件上传):

# homework/models.py class Homework(models.Model): title = models.CharField(max_length=200) description = models.TextField() course = models.ForeignKey(Course, on_delete=models.CASCADE) due_date = models.DateTimeField() class Submission(models.Model): homework = models.ForeignKey(Homework, on_delete=models.CASCADE) student = models.ForeignKey(User, on_delete=models.CASCADE) content = models.TextField(blank=True) # 简答题文本 file = models.FileField(upload_to='homework_files/', blank=True, null=True) # 附件 submitted_at = models.DateTimeField(auto_now_add=True) grade = models.DecimalField(max_digits=4, decimal_places=1, null=True, blank=True) # 教师打分 feedback = models.TextField(blank=True) # 教师评语

教师批改视图homework/views.py提供批量操作:

# homework/views.py from django.contrib.auth.decorators import user_passes_test from django.db.models import Q @user_passes_test(lambda u: u.is_staff or u.is_student is False) # 仅教师可访问 def teacher_grade_list(request): submissions = Submission.objects.filter( homework__course__teacher=request.user ).select_related('homework', 'student').order_by('-submitted_at') return render(request, 'homework/teacher_grade_list.html', {'submissions': submissions})

权限控制要点

  • user_passes_test检查用户是否为 staff 或is_student=False(即教师角色);
  • select_related预加载关联对象,避免 N+1 查询(若不加,模板中每行{{ submission.student.username }}都触发一次 DB 查询);
  • filter(homework__course__teacher=...)利用 Django 双下划线语法跨表查询,Course模型中需有teacher = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)字段(项目已实现)。

5. 本地调试到生产部署:宝塔面板一键上线实操指南

5.1 本地开发环境快速启动(Windows/macOS/Linux 通用)

步骤 1:创建虚拟环境并安装依赖

# 解压 ZIP 后进入项目根目录 cd online_learning # 创建虚拟环境(推荐 Python 3.8+) python -m venv venv source venv/bin/activate # Linux/macOS # venv\Scripts\activate # Windows # 安装 requirements.txt(已包含 django==4.2.7, pillow, pytz 等) pip install -r requirements.txt # 创建超级用户(用于 admin 登录) python manage.py createsuperuser # 运行开发服务器 python manage.py runserver 0.0.0.0:8000

参数说明0.0.0.0:8000允许局域网内其他设备访问(如手机浏览器输入http://192.168.x.x:8000),方便真机测试响应式布局;若仅本机访问,用127.0.0.1:8000更安全。

步骤 2:初始化数据库并加载示例数据

# 执行迁移(生成数据表) python manage.py migrate # 加载初始数据(项目提供 fixtures/initial_data.json) python manage.py loaddata fixtures/initial_data.json # 启动服务器后,访问 http://127.0.0.1:8000/admin/,用 createsuperuser 创建的账号登录 # 在 admin 中可管理课程、用户、作业等所有内容

注意fixtures/initial_data.json包含 3 门示例课程、5 个测试用户、10 个章节,避免你从零开始录入。若修改模型后migrate报错,先python manage.py makemigrations --empty your_app_name创建空迁移,再手动编辑迁移文件修正 SQL。

5.2 宝塔面板部署全流程(CentOS 7/8, Ubuntu 20.04)

步骤 1:上传代码并配置 Python 项目

  • 登录宝塔 → 网站 → 添加站点(域名或 IP)→ PHP 版本选“纯静态”(因 Django 用 Gunicorn);
  • 在站点根目录(如/www/wwwroot/online-learning)上传解压后的全部文件;
  • 宝塔左侧菜单 → Python 项目 → 添加项目:
    • 项目名称:online_learning
    • 项目路径:/www/wwwroot/online-learning
    • Python 版本:3.8(需提前在宝塔软件商店安装)
    • 启动文件:/www/wwwroot/online-learning/manage.py
    • 启动命令:gunicorn online_learning.wsgi:application -c /www/wwwroot/online-learning/gunicorn.conf.py
    • 端口:8000(Gunicorn 监听端口)

步骤 2:配置 gunicorn.conf.py(项目已提供)

# gunicorn.conf.py import multiprocessing bind = "127.0.0.1:8000" bind_mode = "tcp" workers = multiprocessing.cpu_count() * 2 + 1 worker_class = "sync" worker_connections = 1000 max_requests = 1000 max_requests_jitter = 100 timeout = 30 keepalive = 2 preload = True daemon = False raw_env = ["DJANGO_SETTINGS_MODULE=online_learning.settings"] pidfile = "/www/wwwroot/online-learning/gunicorn.pid" loglevel = "info" access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"' accesslog = "/www/wwwroot/online-learning/logs/access.log" errorlog = "/www/wwwroot/online-learning/logs/error.log"

关键参数解释

  • bind = "127.0.0.1:8000":Gunicorn 仅监听本地回环,由 Nginx 反向代理;
  • workers:根据 CPU 核数动态计算,避免过多进程耗尽内存;
  • raw_env:设置 Django 环境变量,确保settings.py能正确加载;
  • pidfile:记录进程 ID,便于宝塔管理启停。

步骤 3:Nginx 反向代理配置(宝塔自动生成)

宝塔创建 Python 项目时,会自动在站点配置中添加以下反向代理规则:

location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # 静态文件由 Nginx 直接服务,不经过 Django location /static/ { alias /www/wwwroot/online-learning/staticfiles/; } location /media/ { alias /www/wwwroot/online-learning/media/; }

验证方法:部署完成后,访问你的域名(如https://your-domain.com),若看到首页课程列表,且https://your-domain.com/static/css/bootstrap.min.css返回 CSS 内容,则 Nginx 代理和静态资源配置成功。

5.3 生产环境必改的三项安全配置

部署后立即执行以下操作,避免毕业答辩时被评委指出安全隐患:

5.3.1 关闭 DEBUG 模式
# online_learning/settings.py DEBUG = False # 必须改为 False ALLOWED_HOSTS = ['your-domain.com', 'www.your-domain.com', '123.45.67.89'] # 替换为你的域名/IP

为什么必须改?
DEBUG=True时,Django 会显示详细错误堆栈(含数据库密码、路径信息),一旦暴露给公网,等于交出服务器钥匙。

5.3.2 设置 SECRET_KEY 为强随机值
# online_learning/settings.py # 替换为 50 位随机字符串(用 python -c "import secrets; print(secrets.token_urlsafe(50))" 生成) SECRET_KEY = 'your_50_char_random_string_here'

注意SECRET_KEY用于加密 session、CSRF token 等,若多人共用同一密钥,会导致 session 冲突。每次部署新环境都应生成新密钥。

5.3.3 配置日志轮转防止磁盘占满
# online_learning/settings.py LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'file': { 'level': 'INFO', 'class': 'logging.handlers.RotatingFileHandler', 'filename': '/www/wwwroot/online-learning/logs/django.log', 'maxBytes': 1024*1024*5, # 5MB 'backupCount': 5, }, }, 'loggers': { 'django': { 'handlers': ['file'], 'level': 'INFO', 'propagate': True, }, }, }

效果:当日志文件超过 5MB,自动重命名为django.log.1,旧日志最多保留 5 份,避免logs/目录无限膨胀。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 2:31:49

pip十大高级用法:Python包管理实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 2:31:42

强化学习代码实战:从PPO最小实现到Stable-Baselines3调试指南

简介&#xff1a;这份强化学习算法代码大全汇集了当前主流强化学习算法的实现&#xff0c;覆盖从基础的Q学习、SARSA到深度强化学习中的DQN、Dueling DQN、策略梯度、演员评论家、A3C、SAC、DDPG、TD3、TRPO、PPO及多智能体DDPG等十余种算法&#xff0c;从表格型方法到深度强化…

作者头像 李华
网站建设 2026/9/10 2:31:22

轻量离线Markdown编辑器v2.0:单文件部署与WebView渲染实践

简介&#xff1a;mdeditor Markdown编辑器v2.0是一套开箱即用的前端源码包&#xff0c;面向计算机专业学生、毕业设计开发者及建站需求者&#xff0c;解决Markdown内容创作与集成落地难题。资源共25个文件&#xff0c;含5个核心JS脚本&#xff08;实现编辑逻辑与实时预览&#…

作者头像 李华
网站建设 2026/9/10 2:30:08

掌银碰一碰收银:重构小店经营确定性的技术底座

1. 为什么“收银”成了新手店主的第一道生死线&#xff1f;“开店容易守店难”&#xff0c;这话在餐饮、零售、美业这些小本生意里&#xff0c;不是比喻&#xff0c;是血淋淋的日常。我见过太多人&#xff1a;租好铺子、装修完、朋友圈发了开业海报、第一批顾客也来了——结果第…

作者头像 李华
网站建设 2026/9/10 2:30:01

全球1° XCO₂栅格数据集:GOSAT与OCO-2融合的实践解析

全球1 XCO₂浓度栅格数据集&#xff08;2009–2020&#xff09;&#xff1a;GOSATOCO-2融合、日尺度与月尺度GeoTIFF的完整实践解析做碳循环研究这几年&#xff0c;我一直在跟卫星XCO₂数据打交道。说实话&#xff0c;找一份称心如意的全球二氧化碳浓度栅格数据集&#xff0c;并…

作者头像 李华