1 celery 执行异步任务,延迟任务,定时任务
| |
| 任务.delay(参数) |
| |
| |
| 任务.apply_async(args=[参数], eta=时间对象) |
| print(datetime.now()) |
| print(datetime.utcnow()) |
| |
| add_task.py |
| |
| |
| from datetime import datetime, timedelta |
| |
| |
| |
| |
| |
| |
| |
| |
| eta = datetime.utcnow() + timedelta(seconds=60) |
| |
| res = send_sms.delay('4894984','9849841') |
| print(res) |
| res = send_sms.apply_async(args=['15312216797', '8888'], eta=eta) |
| print(res) |
| |
| |
| |
| -需要启动beat 和 启动worker |
| -beat 定时提交任务的进程---> 配置在app.conf.beat_schedule的任务 |
| -worker 执行任务的 |
| |
| |
| |
| |
| app.conf.timezone = 'Asia/Shanghai' |
| |
| app.conf.enable_utc = False |
| |
| app.conf.beat_schedule = { |
| 'send_sms': { |
| 'task': 'celery_task.user_task.send_sms', |
| |
| |
| 'schedule': crontab(hour=9, minute=43), |
| 'args': ('18888888', '6666'), |
| |
| }, |
| } |
| |
| |
| |
| |
| |
| celery -A celery_task beat -l info |
| |
| |
| celery -A celery_task worker -l info -P eventlet |
| |
| |
| |
| 1 启动命令的执行位置,如果时包结构,一定要在包这一层 |
| 2 include=['celery_task.order_task'],路径从包名下开始导入,因为我们在包这层执行的命令 |
2 django中使用celery
| |
| APSchedule:https://blog.csdn.net/qq_41341757/article/details/118759836 |
| |
| |
| |
| -1 把咱们写的包, 复制到项目目录下 |
| -luffy_api |
| -celert_task |
| -luffy_api |
| |
| -2 在使用提交异步任务的位置,导入使用即可 |
| -视图函数中使用,导入任务 |
| -任务.delay() |
| |
| |
| -3 启动worker,如果有定时任务,启动beat |
| |
| |
| -4 等待任务被worker执行 |
| |
| |
| -5 在视图函数中,查询任务执行的结果 |
2.1 秒杀功能
| |
| 1 前端秒杀按钮,用户点击---》发送ajax请求到后端 |
| 2 视图函数---》提交秒杀任务---》借助于celery,提交到中间件中了 |
| 3 当次秒杀的请求,就回去了,携带者任务id号在前端 |
| 4 前端开启定时任务,每隔3s钟,带着任务,向后端发送请求,查看是否秒杀成功 |
| 5 后端的情况 |
| 1 任务还在等待被执行----》返回给前端,前端继续每隔3s发送一次请求 |
| 2 任务执行完了,秒杀成功了---》返回给前端,恭喜您秒杀成功--》关闭前端定时器 |
| 3 任务执行完了,秒杀失败了---》返回给前端,秒杀失败--》关闭前端定时器 |
2.2 django 中使用celery
| -1 把咱们写的包,复制到项目目录下 |
| -luffy_api |
| -celery_task |
| celery.py |
| import os |
| os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffy_api.settings.dev') |
| -luffy_api |
| |
| -2 在使用提交异步任务的位置,导入使用即可 |
| -视图函数中使用,导入任务 |
| -任务.delay() |
| |
| |
| |
| -3 启动worker,如果有定时任务,启动beat |
| |
| |
| |
| -4 等待任务被worker执行 |
| |
| |
| |
| -5 在视图函数中,查询任务执行的结果 |
3 轮播图接口加缓存
| |
| |
| |
| |
| 1 轮播图接口请求来了,先去缓存中看,如果有,直接返回 |
| 2 如果没有,查数据库,然后把轮播图数据,放到redis中,缓存起来 |
| |
| |
| |
| |
| |
| def list(self, request, *args, **kwargs): |
| |
| banner_list = cache.get('banner_list') |
| if banner_list: |
| print('走了缓存') |
| return APIResponse(data=banner_list) |
| else: |
| print('走了数据库') |
| res = super().list(request, *args, **kwargs) |
| |
| cache.set('banner_list', res.data) |
| return APIResponse(data=res.data) |
4 双写一致性
| |
| mysql 和 缓存数据库 数据不一致 |
| |
| |
| |
| 写入mysql,redis没动,数据不一致存在问题 |
| |
| |
| |
| 1 修改数据,删除缓存 |
| 2 修改数据,更新缓存 |
| 3 定时更新缓存 ----> 实时性差 |
| |
| |
| |
5 首页轮播图定时更新
| |
| app.conf.beat_schedule = { |
| 'update_banner': { |
| 'task': 'celery_task.home_task.update_banner', |
| 'schedule': timedelta(seconds=3), |
| }, |
| } |
| |
| |
| |
| |
| |
| from home.models import Banner |
| from home.serializer import BannerSerializer |
| from django.core.cache import cache |
| from django.conf import settings |
| @app.task |
| def update_banner(): |
| |
| banners = Banner.objects.all().filter(is_delete=False, is_show=True).order_by('orders') |
| ser = BannerSerializer(instance=banners, many=True) |
| for item in ser.data: |
| item['image'] = settings.BACKEND_URL + item['image'] |
| |
| cache.set('banner_list', ser.data) |
| return True |
6 课程前端页面
| |
| -不要钱的,免费的 |
| -实战课:python面向对象 39.9 |
| -线下课程完整版的视频 19999 |
| |
| |
| 1 新建3个页面组件 |
| FreeCourserView |
| ActualCourserView |
| LightCourseView |
| 2 配置路由 |
| { |
| path: '/free-course', |
| name: 'free', |
| component: FreeCourserView |
| }, |
| { |
| path: '/actual-course', |
| name: 'actual-course', |
| component: ActualCourserView |
| }, |
| { |
| path: '/light-course', |
| name: 'light-course', |
| component: LightCourseView |
| }, |
| 3 复制代码 |
| |
| <template> |
| <div class="course"> |
| <Header></Header> |
| <div class="main"> |
| <!-- 筛选条件 --> |
| <div class="condition"> |
| <ul class="cate-list"> |
| <li class="title">课程分类:</li> |
| <li class="this">全部</li> |
| <li>Python</li> |
| <li>Linux运维</li> |
| <li>Python进阶</li> |
| <li>开发工具</li> |
| <li>Go语言</li> |
| <li>机器学习</li> |
| <li>技术生涯</li> |
| </ul> |
| |
| <div class="ordering"> |
| <ul> |
| <li class="title">筛 选:</li> |
| <li class="default this">默认</li> |
| <li class="hot">人气</li> |
| <li class="price">价格</li> |
| </ul> |
| <p class="condition-result">共21个课程</p> |
| </div> |
| |
| </div> |
| <!-- 课程列表 --> |
| <div class="course-list"> |
| <div class="course-item"> |
| <div class="course-image"> |
| <img src="@/assets/img/course-cover.jpeg" alt=""> |
| </div> |
| <div class="course-info"> |
| <h3>Python开发21天入门 <span><img src="@/assets/img/avatar1.svg" alt="">100人已加入学习</span></h3> |
| <p class="teather-info">Alex 金角大王 老男孩Python教学总监 <span>共154课时/更新完成</span></p> |
| <ul class="lesson-list"> |
| <li><span class="lesson-title">01 | 第1节:初识编码</span> <span class="free">免费</span></li> |
| <li><span class="lesson-title">01 | 第1节:初识编码初识编码</span> <span class="free">免费</span></li> |
| <li><span class="lesson-title">01 | 第1节:初识编码</span></li> |
| <li><span class="lesson-title">01 | 第1节:初识编码初识编码</span></li> |
| </ul> |
| <div class="pay-box"> |
| <span class="discount-type">限时免费</span> |
| <span class="discount-price">¥0.00元</span> |
| <span class="original-price">原价:9.00元</span> |
| <span class="buy-now">立即购买</span> |
| </div> |
| </div> |
| </div> |
| <div class="course-item"> |
| <div class="course-image"> |
| <img src="@/assets/img/course-cover.jpeg" alt=""> |
| </div> |
| <div class="course-info"> |
| <h3>Python开发21天入门 <span><img src="@/assets/img/avatar1.svg" alt="">100人已加入学习</span></h3> |
| <p class="teather-info">Alex 金角大王 老男孩Python教学总监 <span>共154课时/更新完成</span></p> |
| <ul class="lesson-list"> |
| <li><span class="lesson-title">01 | 第1节:初识编码</span> <span class="free">免费</span></li> |
| <li><span class="lesson-title">01 | 第1节:初识编码初识编码</span> <span class="free">免费</span></li> |
| <li><span class="lesson-title">01 | 第1节:初识编码</span></li> |
| <li><span class="lesson-title">01 | 第1节:初识编码初识编码</span></li> |
| </ul> |
| <div class="pay-box"> |
| <span class="discount-type">限时免费</span> |
| <span class="discount-price">¥0.00元</span> |
| <span class="original-price">原价:9.00元</span> |
| <span class="buy-now">立即购买</span> |
| </div> |
| </div> |
| </div> |
| <div class="course-item"> |
| <div class="course-image"> |
| <img src="@/assets/img/course-cover.jpeg" alt=""> |
| </div> |
| <div class="course-info"> |
| <h3>Python开发21天入门 <span><img src="@/assets/img/avatar1.svg" alt="">100人已加入学习</span></h3> |
| <p class="teather-info">Alex 金角大王 老男孩Python教学总监 <span>共154课时/更新完成</span></p> |
| <ul class="lesson-list"> |
| <li><span class="lesson-title">01 | 第1节:初识编码</span> <span class="free">免费</span></li> |
| <li><span class="lesson-title">01 | 第1节:初识编码初识编码</span> <span class="free">免费</span></li> |
| <li><span class="lesson-title">01 | 第1节:初识编码</span></li> |
| <li><span class="lesson-title">01 | 第1节:初识编码初识编码</span></li> |
| </ul> |
| <div class="pay-box"> |
| <span class="discount-type">限时免费</span> |
| <span class="discount-price">¥0.00元</span> |
| <span class="original-price">原价:9.00元</span> |
| <span class="buy-now">立即购买</span> |
| </div> |
| </div> |
| </div> |
| <div class="course-item"> |
| <div class="course-image"> |
| <img src="@/assets/img/course-cover.jpeg" alt=""> |
| </div> |
| <div class="course-info"> |
| <h3>Python开发21天入门 <span><img src="@/assets/img/avatar1.svg" alt="">100人已加入学习</span></h3> |
| <p class="teather-info">Alex 金角大王 老男孩Python教学总监 <span>共154课时/更新完成</span></p> |
| <ul class="lesson-list"> |
| <li><span class="lesson-title">01 | 第1节:初识编码</span> <span class="free">免费</span></li> |
| <li><span class="lesson-title">01 | 第1节:初识编码初识编码</span> <span class="free">免费</span></li> |
| <li><span class="lesson-title">01 | 第1节:初识编码</span></li> |
| <li><span class="lesson-title">01 | 第1节:初识编码初识编码</span></li> |
| </ul> |
| <div class="pay-box"> |
| <span class="discount-type">限时免费</span> |
| <span class="discount-price">¥0.00元</span> |
| <span class="original-price">原价:9.00元</span> |
| <span class="buy-now">立即购买</span> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| <Footer></Footer> |
| </div> |
| </template> |
| |
| <script> |
| import Header from "@/components/Header" |
| import Footer from "@/components/Footer" |
| |
| export default { |
| name: "Course", |
| data() { |
| return { |
| category: 0, |
| } |
| }, |
| components: { |
| Header, |
| Footer, |
| } |
| } |
| </script> |
| |
| <style scoped> |
| .course { |
| background: |
| } |
| |
| .course .main { |
| width: 1100px; |
| margin: 35px auto 0; |
| } |
| |
| .course .condition { |
| margin-bottom: 35px; |
| padding: 25px 30px 25px 20px; |
| background: |
| border-radius: 4px; |
| box-shadow: 0 2px 4px 0 |
| } |
| |
| .course .cate-list { |
| border-bottom: 1px solid |
| border-bottom-color: rgba(51, 51, 51, .05); |
| padding-bottom: 18px; |
| margin-bottom: 17px; |
| } |
| |
| .course .cate-list::after { |
| content: ""; |
| display: block; |
| clear: both; |
| } |
| |
| .course .cate-list li { |
| float: left; |
| font-size: 16px; |
| padding: 6px 15px; |
| line-height: 16px; |
| margin-left: 14px; |
| position: relative; |
| transition: all .3s ease; |
| cursor: pointer; |
| color: |
| border: 1px solid transparent; /* transparent 透明 */ |
| } |
| |
| .course .cate-list .title { |
| color: |
| margin-left: 0; |
| letter-spacing: .36px; |
| padding: 0; |
| line-height: 28px; |
| } |
| |
| .course .cate-list .this { |
| color: |
| border: 1px solid |
| border-radius: 30px; |
| } |
| |
| .course .ordering::after { |
| content: ""; |
| display: block; |
| clear: both; |
| } |
| |
| .course .ordering ul { |
| float: left; |
| } |
| |
| .course .ordering ul::after { |
| content: ""; |
| display: block; |
| clear: both; |
| } |
| |
| .course .ordering .condition-result { |
| float: right; |
| font-size: 14px; |
| color: |
| line-height: 28px; |
| } |
| |
| .course .ordering ul li { |
| float: left; |
| padding: 6px 15px; |
| line-height: 16px; |
| margin-left: 14px; |
| position: relative; |
| transition: all .3s ease; |
| cursor: pointer; |
| color: |
| } |
| |
| .course .ordering .title { |
| font-size: 16px; |
| color: |
| letter-spacing: .36px; |
| margin-left: 0; |
| padding: 0; |
| line-height: 28px; |
| } |
| |
| .course .ordering .this { |
| color: |
| } |
| |
| .course .ordering .price { |
| position: relative; |
| } |
| |
| .course .ordering .price::before, |
| .course .ordering .price::after { |
| cursor: pointer; |
| content: ""; |
| display: block; |
| width: 0px; |
| height: 0px; |
| border: 5px solid transparent; |
| position: absolute; |
| right: 0; |
| } |
| |
| .course .ordering .price::before { |
| border-bottom: 5px solid |
| margin-bottom: 2px; |
| top: 2px; |
| } |
| |
| .course .ordering .price::after { |
| border-top: 5px solid |
| bottom: 2px; |
| } |
| |
| .course .course-item:hover { |
| box-shadow: 4px 6px 16px rgba(0, 0, 0, .5); |
| } |
| |
| .course .course-item { |
| width: 1100px; |
| background: |
| padding: 20px 30px 20px 20px; |
| margin-bottom: 35px; |
| border-radius: 2px; |
| cursor: pointer; |
| box-shadow: 2px 3px 16px rgba(0, 0, 0, .1); |
| /* css3.0 过渡动画 hover 事件操作 */ |
| transition: all .2s ease; |
| } |
| |
| .course .course-item::after { |
| content: ""; |
| display: block; |
| clear: both; |
| } |
| |
| /* 顶级元素 父级元素 当前元素{} */ |
| .course .course-item .course-image { |
| float: left; |
| width: 423px; |
| height: 210px; |
| margin-right: 30px; |
| } |
| |
| .course .course-item .course-image img { |
| width: 100%; |
| } |
| |
| .course .course-item .course-info { |
| float: left; |
| width: 596px; |
| } |
| |
| .course-item .course-info h3 { |
| font-size: 26px; |
| color: |
| font-weight: normal; |
| margin-bottom: 8px; |
| } |
| |
| .course-item .course-info h3 span { |
| font-size: 14px; |
| color: |
| float: right; |
| margin-top: 14px; |
| } |
| |
| .course-item .course-info h3 span img { |
| width: 11px; |
| height: auto; |
| margin-right: 7px; |
| } |
| |
| .course-item .course-info .teather-info { |
| font-size: 14px; |
| color: |
| margin-bottom: 14px; |
| padding-bottom: 14px; |
| border-bottom: 1px solid |
| border-bottom-color: rgba(51, 51, 51, .05); |
| } |
| |
| .course-item .course-info .teather-info span { |
| float: right; |
| } |
| |
| .course-item .lesson-list::after { |
| content: ""; |
| display: block; |
| clear: both; |
| } |
| |
| .course-item .lesson-list li { |
| float: left; |
| width: 44%; |
| font-size: 14px; |
| color: |
| padding-left: 22px; |
| /* background: url("路径") 是否平铺 x轴位置 y轴位置 */ |
| background: url("/src/assets/img/play-icon-gray.svg") no-repeat left 4px; |
| margin-bottom: 15px; |
| } |
| |
| .course-item .lesson-list li .lesson-title { |
| /* 以下3句,文本内容过多,会自动隐藏,并显示省略符号 */ |
| text-overflow: ellipsis; |
| overflow: hidden; |
| white-space: nowrap; |
| display: inline-block; |
| max-width: 200px; |
| } |
| |
| .course-item .lesson-list li:hover { |
| background-image: url("/src/assets/img/play-icon-yellow.svg"); |
| color: |
| } |
| |
| .course-item .lesson-list li .free { |
| width: 34px; |
| height: 20px; |
| color: |
| vertical-align: super; |
| margin-left: 10px; |
| border: 1px solid |
| border-radius: 2px; |
| text-align: center; |
| font-size: 13px; |
| white-space: nowrap; |
| } |
| |
| .course-item .lesson-list li:hover .free { |
| color: |
| border-color: |
| } |
| |
| .course-item .pay-box::after { |
| content: ""; |
| display: block; |
| clear: both; |
| } |
| |
| .course-item .pay-box .discount-type { |
| padding: 6px 10px; |
| font-size: 16px; |
| color: |
| text-align: center; |
| margin-right: 8px; |
| background: |
| border: 1px solid |
| border-radius: 10px 0 10px 0; |
| float: left; |
| } |
| |
| .course-item .pay-box .discount-price { |
| font-size: 24px; |
| color: |
| float: left; |
| } |
| |
| .course-item .pay-box .original-price { |
| text-decoration: line-through; |
| font-size: 14px; |
| color: |
| margin-left: 10px; |
| float: left; |
| margin-top: 10px; |
| } |
| |
| .course-item .pay-box .buy-now { |
| width: 120px; |
| height: 38px; |
| background: transparent; |
| color: |
| font-size: 16px; |
| border: 1px solid |
| border-radius: 3px; |
| transition: all .2s ease-in-out; |
| float: right; |
| text-align: center; |
| line-height: 38px; |
| } |
| |
| .course-item .pay-box .buy-now:hover { |
| color: |
| background: |
| border: 1px solid |
| } |
| </style> |
| |
| |
7 课程功能表分析
| |
| -所有课程使用一个表 通过类型区分,但是可能出现字段不一样,数据量越来越多,导致表查询速度慢 |
| -一种课程一个表 |
| |
| |
| |
| -课程分类表 一个分类下有多个课程跟课程一对多 |
| -实战课表 一个实战课,会有多个章节,跟章节一对多 |
| -章节表 一个章节下有多个课时,章节和课时一对多 |
| -课时表 |
| -老师表 跟实战课一对多 |
| |
| -正常课程 有评论,需要建立评论表,评论板块没写 这个表就不需要写了 |
| from django.db import models |
| from utils.common_model import BaseModel |
| |
| |
| |
| |
| |
| |
| |
| class CourseCategory(BaseModel): |
| """分类""" |
| name = models.CharField(max_length=64, unique=True, verbose_name="分类名称") |
| |
| class Meta: |
| db_table = "luffy_course_category" |
| verbose_name = "分类" |
| verbose_name_plural = verbose_name |
| |
| def __str__(self): |
| return "%s" % self.name |
| |
| |
| |
| class Course(BaseModel): |
| """课程""" |
| course_type = ( |
| (0, '付费'), |
| (1, 'VIP专享'), |
| (2, '学位课程') |
| ) |
| level_choices = ( |
| (0, '初级'), |
| (1, '中级'), |
| (2, '高级'), |
| ) |
| status_choices = ( |
| (0, '上线'), |
| (1, '下线'), |
| (2, '预上线'), |
| ) |
| name = models.CharField(max_length=128, verbose_name="课程名称") |
| course_img = models.ImageField(upload_to="courses", max_length=255, verbose_name="封面图片", blank=True, null=True) |
| course_type = models.SmallIntegerField(choices=course_type, default=0, verbose_name="付费类型") |
| brief = models.TextField(max_length=2048, verbose_name="详情介绍", null=True, blank=True) |
| level = models.SmallIntegerField(choices=level_choices, default=0, verbose_name="难度等级") |
| pub_date = models.DateField(verbose_name="发布日期", auto_now_add=True) |
| period = models.IntegerField(verbose_name="建议学习周期(day)", default=7) |
| attachment_path = models.FileField(upload_to="attachment", max_length=128, verbose_name="课件路径", blank=True,null=True) |
| status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="课程状态") |
| |
| students = models.IntegerField(verbose_name="学习人数", default=0) |
| |
| sections = models.IntegerField(verbose_name="总课时数量", default=0) |
| pub_sections = models.IntegerField(verbose_name="课时更新数量", default=0) |
| price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程原价", default=0) |
| |
| |
| teacher = models.ForeignKey("Teacher", on_delete=models.DO_NOTHING, null=True, blank=True, verbose_name="授课老师") |
| course_category = models.ForeignKey("CourseCategory", on_delete=models.SET_NULL, db_constraint=False, null=True,blank=True, verbose_name="课程分类") |
| class Meta: |
| db_table = "luffy_course" |
| verbose_name = "课程" |
| verbose_name_plural = "课程" |
| |
| def __str__(self): |
| return "%s" % self.name |
| |
| |
| |
| class CourseChapter(BaseModel): |
| """章节""" |
| |
| |
| course = models.ForeignKey("Course", related_name='coursechapters', on_delete=models.CASCADE, verbose_name="课程名称") |
| chapter = models.SmallIntegerField(verbose_name="第几章", default=1) |
| name = models.CharField(max_length=128, verbose_name="章节标题") |
| summary = models.TextField(verbose_name="章节介绍", blank=True, null=True) |
| pub_date = models.DateField(verbose_name="发布日期", auto_now_add=True) |
| |
| class Meta: |
| db_table = "luffy_course_chapter" |
| verbose_name = "章节" |
| verbose_name_plural = verbose_name |
| |
| def __str__(self): |
| return "%s:(第%s章)%s" % (self.course, self.chapter, self.name) |
| |
| |
| |
| class CourseSection(BaseModel): |
| """课时""" |
| section_type_choices = ( |
| (0, '文档'), |
| (1, '练习'), |
| (2, '视频') |
| ) |
| |
| chapter = models.ForeignKey("CourseChapter", related_name='coursesections', on_delete=models.CASCADE,verbose_name="课程章节") |
| name = models.CharField(max_length=128, verbose_name="课时标题") |
| orders = models.PositiveSmallIntegerField(verbose_name="课时排序") |
| section_type = models.SmallIntegerField(default=2, choices=section_type_choices, verbose_name="课时种类") |
| section_link = models.CharField(max_length=255, blank=True, null=True, verbose_name="课时链接",help_text="若是video,填vid,若是文档,填link") |
| duration = models.CharField(verbose_name="视频时长", blank=True, null=True, max_length=32) |
| pub_date = models.DateTimeField(verbose_name="发布时间", auto_now_add=True) |
| free_trail = models.BooleanField(verbose_name="是否可试看", default=False) |
| |
| class Meta: |
| db_table = "luffy_course_section" |
| verbose_name = "课时" |
| verbose_name_plural = verbose_name |
| |
| def __str__(self): |
| return "%s-%s" % (self.chapter, self.name) |
| |
| |
| |
| class Teacher(BaseModel): |
| """导师""" |
| role_choices = ( |
| (0, '讲师'), |
| (1, '导师'), |
| (2, '班主任'), |
| ) |
| name = models.CharField(max_length=32, verbose_name="导师名") |
| role = models.SmallIntegerField(choices=role_choices, default=0, verbose_name="导师身份") |
| title = models.CharField(max_length=64, verbose_name="职位、职称") |
| signature = models.CharField(max_length=255, verbose_name="导师签名", help_text="导师签名", blank=True, null=True) |
| image = models.ImageField(upload_to="teacher", null=True, verbose_name="导师封面") |
| brief = models.TextField(max_length=1024, verbose_name="导师描述") |
| |
| class Meta: |
| db_table = "luffy_teacher" |
| verbose_name = "导师" |
| verbose_name_plural = verbose_name |
| |
| def __str__(self): |
| return "%s" % self.name |
| |
| |
| |
| |
| |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构