部分核心技术(持续更新)

news2024/11/16 5:54:27

文章目录

  • 1.Schedule(定时任务)
  • 2.高并发线程安全的解决方案
    • 2.1为什么不适用同步锁(Synchronized)?
    • 2.2 Redis的分布式锁setnx
    • 2.3 redisson分布式锁(看门狗机制)
      • 2.3.1 Redis的分布式锁setnx产生的问题
      • 2.3.2 redisson 实现锁续命
      • 2.3.3 redisson 的代码实现
  • 3.限流处理操作(并发量过大)
    • 3.1 令牌桶的实现
    • 3.1 自定义注解实现接口限流

1.Schedule(定时任务)

  • Spring的Schedule依赖包含在spring-boot-starter模块中,无需引入其他依赖。

  • 在启动类增加注解(开启定时任务):@EnableScheduling

  • Cron表达式,当方法的执行时间超过任务调度频率时,调度器会在下个周期执行

  • 注意: Spring的Schecule默认是单线程执行的,如果你定义了多个任务,那么他们将会被串行执行,会严重不满足你的预期。(如果要解决可以通过线程池的方式解决)

  • 示例: 每秒获取Redis中存储的消息(消息队列确认机制的保证,可以通过Redis保证实现),将失败的消息重新投递

@Component
public class MySchedul {
    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    RabbitMessageUtils rabbitMessageUtils;

    private static Logger logger = LoggerFactory.getLogger(MySchedul.class);

    //该定时任务就是从redis 中获取到未投递成功的消息,并且进行重新投递
    @Scheduled(cron = "* * * * * ?")
    public void getMessageAndSender() {
        logger.debug("===进入消息重新投递的定时任务===");
        //进入定时任务,获取到redis中所有的信息
        Set keys = redisTemplate.boundHashOps(RabbitKey.MESSAGE_KEY).keys();
        //获取到redis中所有的消息的key,循环所有的key
        if (keys != null && keys.size() > 0) {
            for (Object id : keys) {
                //通过该key获取到 消息本身
                Object o = redisTemplate.boundHashOps(RabbitKey.MESSAGE_KEY).get(id.toString());
                //将o转为message对象
                MyMessage message = JSONObject.parseObject(JSON.toJSONString(o), MyMessage.class);
                //判断message中的status是否为fail状态
                if (message.getStatus().equals("fail")) {
                    //调用工具类重新投递
                    rabbitMessageUtils.sendMessage(message);
                }
            }
        }
    }
}

2.高并发线程安全的解决方案

2.1为什么不适用同步锁(Synchronized)?

  • 对代码块添加同步锁(Synchronized)可以保证单个服务,在多线程状况下不能同时执行,如果项目部署集群,同一台服务器会部署多个服务,则同步锁会失效,多个服务间会出现并发

2.2 Redis的分布式锁setnx

“锁”就是一个存储在redis里的key-value对,key是把一组操作用字符串来形成唯一标识,value其实并不重要,因为只要这个唯一的key-value存在,就表示这个操作已经上锁,redis的分布式锁技术,使用的就是redis的setnx操作(在java中表现为setIfAbsent),如果有key 则加锁失败,如果没有则加锁成功,需要注意的是在加分布式锁时需要设置超时时间,用来防止死锁的产生

(1)加锁:

  • “锁”就是一个存储在redis里的key-value对,key是把一组操作用字符串来形成唯一标识,value其实并不重要,因为只要这个唯一的key-value存在,就表示这个操作已经上锁。
  • redis的分布式锁技术,使用的就是redis的setnx操作,如果有key 则加锁失败,如果没有则加锁成功
    • setIfAbsent 是java中的方法
    • setnx 是 redis命令中的方法

(2)解锁:

  • 既然key-value对存在就表示上锁,那么释放锁就自然是在redis里删除key-value对

3)阻塞、非阻塞:

  • 阻塞式的实现,若线程发现已经上锁,会在特定时间内轮询锁。
  • 非阻塞式的实现,若发现线程已经上锁,则直接返回。

(4)处理异常情况(防止死锁的产生):

  • 假设当投资操作调用其他平台接口出现等待时,自然没有释放锁,这种情况下加入锁超时机制,用redis的expire命令为key设置超时时长,过了超时时间redis就会将这个key自动删除,即强制释放锁,进而防止死锁的产生

(5)示例 (以秒杀防止超卖问题为例)

 	//为了安全性继续加入redis的分布式锁 使用的就是redis的setnx操作,如果有key 则加锁失败,如果没有则加锁成功
        //设置锁的失效时间,防止死锁产生,例如设置10s的过期时间,如果到期还未释放锁,则直接将该锁进行移除
       //解决的是超卖问题
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "1", 10, TimeUnit.SECONDS);
       if(lock) {
           logger.debug("当前用户抢到了该锁,进行扣减库存操作!");
           //如果有库存,则需要进行扣减库存操作,注意要保证原子性 会返回当前减1后的剩余库存量
           Long stockConut = redisTemplate.opsForValue().decrement(RedisKey.SECKILL_KEY + goodsId);
           if (stockConut <= 0) {
               //库存没有了 删除商品信息
               redisTemplate.boundHashOps(RedisKey.SECKILL_KEY + redisKey).delete(goodsId.toString());
               //移除掉对应的库存信息
               redisTemplate.delete(RedisKey.SECKILL_KEY + goodsId);
               return new BaseResp().FAIL("商品已被抢购完!");
           } else {
               //修改剩余库存量设置到 redis中
               TbSeckillGoods tbSeckillGoods = JSONObject.parseObject(JSON.toJSONString(obj), TbSeckillGoods.class);
               tbSeckillGoods.setStockCount(stockConut.intValue());
               redisTemplate.boundHashOps(RedisKey.SECKILL_KEY + redisKey).put(goodsId.toString(), tbSeckillGoods);
               //将数据库修改为0  但是这个逻辑是错误的,因为用户点击完抢购,如果没有付款,则该商品的数量是不能被真正的排除掉的。就会出现少卖的情况,所以真正的扣减数据库的库存,需要用户支付完成后,才能真正的扣钱
               //在这里不能直接释放库存,因为用户一直不付钱,那么我们就需要进行 后续业务处理,如果用户30S不付款,则直接将该用户抢购的商品进行移除操作,预扣减的库存+1,可以通过RabbitMQ的死信队列来实现
               //防止少卖问题
             }
           //执行完逻辑后要将该锁释放
           logger.debug("抢购完成!");
           //释放分布式锁
           redisTemplate.delete("lock");

       }else{
           logger.debug("没有抢到锁,抢购失败!");
       }

2.3 redisson分布式锁(看门狗机制)

在这里插入图片描述

2.3.1 Redis的分布式锁setnx产生的问题

  • 使用Redis的分布式锁setnx,需要设置超时时间来防止死锁的产生,若某个线程抢到该锁但由于调用其他接口出现了等待,导致其业务执行总时间超过了setnx设置的超时时间,此时锁就会被释放,就会出现并发问题(例如秒杀时出现超卖问题)

2.3.2 redisson 实现锁续命

  • redisson 通过 watch dog(看门狗机制),当业务执行超时,会进行锁续命
  • redisson实现锁续命的原理: 设置了超时时间,如果当前业务代码超过了锁的失效时间,会进行锁续命,但不是无限次的续命,而是达到一定的次数、一定的时间,redisson会认为当前出现了死锁状况,会自动将该锁进行释放。(第一次默认延长30S时间)

2.3.3 redisson 的代码实现

(1)依赖

<!-- 加入redisson依赖,解决分布式锁问题-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.17.7</version>
        </dependency>

(2)通过配置文件,将redisson交由Spring容器进行管理

  • 需要结合redis
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RedissonConfig {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private String port;

    @Value("${spring.redis.database}")
    private Integer database;

    //创建redission连接客户端 并且交给spring管理
    @Bean
    public RedissonClient createRedisson(){
        //1.声明Redisson的配置
        Config config = new Config();
        //2.使用单机模式 其中也有集群模式
        config.useSingleServer()
                //redisson的连接 必须以redis://开头
                .setAddress("redis://"+host+":"+port)
                //设置使用的redis的库
                .setDatabase(database);
        //3.使用redisson的config创建出redissonClient客户端
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }
}

(3)加锁

@RestController
@RequestMapping("/lock")
public class TestLockController {

    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    ShopRepository shopRepository;
    //获取到配置文件中的Bean对象
    @Autowired
    RedissonClient redissonClient;

    //1.加锁
    //2.判断数据库的数量是否大于1
    //3.如果大于等于1 则修改数量 进行减1操作
    //4.释放锁资源
    //使用自定义注解 ,我们需要定义该注解的作用是什么
    @AccessLimit
    @RequestMapping("/sekill/{id}")
    @Transactional
    public String testRedis(@PathVariable("id")Integer id) throws InterruptedException {

        // 进行加锁,并且修改数据库的库存 模拟秒杀
        //1.key  value setIfAbsent 特点为:如果已经存在该key值,则不会设置成功
       // Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent("lock", "lock");
        //2.使用redisson进行加锁的操作
        RLock lock = redissonClient.getLock("lock");
        //3.判断锁是否可用尝试加锁 并且设置1秒钟的失效时间 如果加锁成功则设置为true
        try {
            boolean b = lock.tryLock(1, TimeUnit.SECONDS);
            if (b) {
                System.out.println("获取锁开始执行");
                //2.如果获取到这把锁,设置失效时间 失效时间设置为5秒时间
                //redisTemplate.expire("lock", 1, TimeUnit.SECONDS);
                //3.设置完成后执行自己的业务逻辑。从数据库查询该商品的数量是否大于等于1
                Optional<TbShop> byId = shopRepository.findById(id);
                if (byId.isPresent()) {
                    //在执行当前逻辑是,超过了默认的锁失效时间,那么就会出现超卖
                    //将当前的线程休眠两秒钟的时间 业务执行的时间越长,则超卖的商品就越多
                    //当使用到Ression后,休眠两秒钟的时间,默认设置的该锁的时间为1秒,那么redssion
                    //会自动的将该锁进行续命操作,防止出现并发操作。只有当前线程执行完成,才会讲该锁进行释放,不管
                    //线程执行的时间长短,当达到固定时间时,才会释放防止死锁的产生
                    Thread.sleep(2000);
                    if (byId.get().getNum() >= 1) {
                        shopRepository.updateNum(id);
                    }

                }
                //释放锁
                //redisTemplate.delete("lock");
                //使用Redisson释放锁.先获取到锁,才能进行解锁操作
                lock.unlock();
            }

        }catch (Exception e){
            return "秒杀失败!";
        }
        return "秒杀失败";
    }
}

3.限流处理操作(并发量过大)

  • 使用RabbitMQ设置队列的最大存储量,对请求进行限流
  • 使用限流技术,比如令牌桶

在这里插入图片描述

3.1 令牌桶的实现

  • 推荐使用google提供的guava工具包中的RateLimiter进行实现,其内部是基于令牌桶算法进行限流计算

(1)依赖

<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>28.0-jre</version>
</dependency>

(2)测试

public static void main(String[] args) {
        //允许每秒通过三个请求
        RateLimiter rateLimiter = RateLimiter.create(3.0);
        //获取令牌,如果获取到则为true 否则为false
        boolean b = rateLimiter.tryAcquire();
        //开启线程池进行测试
        ExecutorService executor = Executors.newFixedThreadPool(100);

        for (int i = 0; i < 10; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    //获取令牌桶中一个令牌,最多等待10秒
                    if (rateLimiter.tryAcquire(1, 10, TimeUnit.SECONDS)) {
                        System.out.println(Thread.currentThread().getName()+" "+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                    }
                }
            });
        }

        executor.shutdown();
    }

3.1 自定义注解实现接口限流

  • 基于令牌桶

  • 实现方式: 自定义注解 + AOP

(1)依赖

<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>28.0-jre</version>
</dependency>

(2)自定义限流注解

注解定义用@interface关键字修饰

@Target注解,是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的
@Retention注解,翻译为持久力、保持力。即用来修饰自定义注解的生命周期
@Documented注解,是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中
@Inherited注解,是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解

@Inherited
@Documented
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {}

(2)自定义切面类,扫描加入了该注解的方法

import com.alibaba.fastjson.JSONObject;
import com.google.common.util.concurrent.RateLimiter;
import com.qf.springbootrediscrud.pojo.BaseResp;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;

/**
 * 1.aop的业务使用:
 *  1.1 日志记录
 *  1.2 事务
 *  1.3 扫描自定义注解,进行业务增强
 */
@Aspect
@Component
public class AccessLimitAop {

    @Autowired
    private HttpServletResponse httpServletResponse;

    //声明令牌桶,每秒放行20个请求
    private RateLimiter rateLimiter=RateLimiter.create(1.0);

    private static final Logger logger = LoggerFactory.getLogger(AccessLimitAop.class);

    //1.定义对于哪些方法进行增强。声明切点。我们现在需要的是扫描自定义注解
    //excution 定义切点对于哪些方法进行增强
    @Pointcut(value = "@annotation(com.qf.springbootrediscrud.annoration.AccessLimit)")
    public void pt1(){}

    //我们对接口进行增强方法,拦截到加了自定义注解的接口,首先来执行增强方法
    //判断当前的请求是否可以放行,是否可以从令牌桶中获取到令牌
    @Around("pt1()")
    public Object arround(ProceedingJoinPoint proceedingJoinPoint){
        //判断是否该请求获取到了令牌
        logger.debug("进入了令牌桶的判断是否放行");
        //尝试从令牌桶中获取令牌
        boolean b = rateLimiter.tryAcquire();
        String name = Thread.currentThread().getName();
        Object proceed = null;
            try {
                //判断是否获取到该令牌,则继续执行业务逻辑
                if (b){
                    logger.debug("===获取到令牌继续执行业务逻辑==当前线程:{}"+name);

                    proceed = proceedingJoinPoint.proceed();
                    return proceed;
                }else{
                    logger.debug("===获取令牌失败!=="+name);
                    //没有获取到令牌需要返回 ,不再继续请求
                    String result = JSONObject.toJSONString(new BaseResp().FAIL("请求量过大,请稍后再试!"));
                    //通过httpservletResponse进行返回
                    httpServletResponse.setContentType("application/json");
                    httpServletResponse.setCharacterEncoding("utf-8");
                    //使用response进行返回数据
                    ServletOutputStream outputStream = httpServletResponse.getOutputStream();
                    //将错误结果进行返回操作
                    outputStream.write(result.getBytes(StandardCharsets.UTF_8));
                    outputStream.close();
                }
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }

            return proceed;
    }
}

(3)在接口中加入@AccessLimit注解,进行限流

 /**
     * 开始秒杀
     */
    //在秒杀中加入 限流注解 对当前接口进行保护
    @AccessLimit
    @RequestMapping("/add")
    public BaseResp add(@RequestParam("id")Integer goodsId, @RequestParam("date")String date, HttpServletRequest request){
        return sekillService.add(goodsId,date,request);
    }

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

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

相关文章

保姆级入门nest笔记

使用 NEXT 搭建后台服务接口 https://docs.nestjs.com/ # 准备工作 安装 node 全局安装 nest npm i -给nestjs/cli nest --version # 创建项目 创建项目next new 启动项目npm run start 或 npm run start:dev 访问接口 localhost:3000 获取命令解释 next g -h # 快速创建…

Pixracer接线图 及电调调参 BLheliSuite

Pixracer接线指南 pixracer官方链接 正反面引脚定义 接口含义 BLheliSuite调参软件 官方下载&#xff1a; https://www.mediafire.com/folder/dx6kfaasyo24l/BLHeliSuite 我使用了如下软件https://www.mediafire.com/file/9uccf1zy3wqb1w5/BLHeliSuite32_32.9.0.3.zip/fil…

Bio-Net:编解码器结构的循环双向连接网络

目录 摘要 方法 循环双向跳跃连接 前向跳跃连接 后向跳跃连接 递归的推断训练 BiO-Net网络结构 总结 摘要 对UNet以前的扩展主要集中对现有模块的改进或者提出新的模块来提高性能。因此这些变量通常会导致模型的复杂性不可忽视的增加。为了解决这种复杂性的问题。在本…

redis cluster 集群安装

redis cluster 集群安装 redis集群方案 哨兵集群 如图&#xff0c;实际上还是一个节点对外提供服务&#xff0c;所以虽然是三台机器&#xff0c;但是还是一台机器的并发量&#xff0c;而且master挂了之后&#xff0c;整个集群不能对外提供服务 cluster集群 多个主从集群节点…

五、伊森商城 前端基础-Vue 整合ElementUI快速开发 p28

目录 一、安装 1、安装ElementUI 2、在main.js文件中引入 2.1、引入ElementUI组件 2.2、让Vue使用ElementUI组件 二、使用 1、在hello.vue组件使用单选框 2、使用ElementUI快速搭建后台管理系统 2.1、修改App.vue 3、修改功能成动态显示 3.1、编写快速生成组件的模板 3…

java计算机毕业设计ssm学习互助平台网站8f554(附源码、数据库)

java计算机毕业设计ssm学习互助平台网站8f554&#xff08;附源码、数据库&#xff09; 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff0…

C++11之引用

文章目录目的为啥要引入右值引用什么是右值引用右值引用作用移动构造函数移动语义 std::move移动语义注意事项完美转发博客目的 了解对应左值引用&#xff0c; 右值引用&#xff0c;移动语义&#xff0c; 完美转发含义。 右值引用&#xff08;及其支持的移动语义Move semanti…

1562_AURIX_TC275_电源监控

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) 这一次的学习笔记内容比较少&#xff0c;因为有几页的文档内容跟之前看过的DataSheet内容雷同。因此&#xff0c;相应的学习笔记不再整理。 之前的学习笔记&#xff1a; (56条消息) 1451_…

Python学习基础笔记四十——os模块

os模块是与操作系统交互的一个接口。 os的方法Linux命令备注os.getcwd()pwd获取当前工作目录路径os.chdir()cd切换当前工作目录os.makedirs(dirname1/dirname2)mkdir -p dirname1/dirname2生成多级目录os.removedirs(dirname1)rmdir删除多级目录os.mkdir(dirname)mkdir dirnam…

JAVA入门零基础小白教程day04-数组

day04_java基础 课程目标 1. 【掌握】 IDEA的基本使用 2. 【理解】 什么是数组 3. 【掌握】 数组的定义及初始化 4. 【理解】 数组的内存图 6. 【理解】 数组常见的问题 7. 【掌握】 数组的案例 8. 【理解】 二维数组开发工具 一维数组 什么是数组 数组就是存储数据长度固定…

【Linux】源码安装Apache、Mysql、PHP以及LAMP部署验证

文章目录源码安装相关理论源代码安装特点源码包安装步骤一、源码安装Apache1、编译安装依赖包 apr2、编译安装依赖包 apr-util3、编译安装依赖包 pcre4、编译安装 Apache5、重启 apache 服务6、修改网页显示内容7、访问测试二、源码安装Mysql1、把系统自带的 boost 库卸载&…

【mmdetection系列】mmdetection之loss讲解

目录 1.configs 2.具体实现 3.调用 3.1 注册 3.2 调用 配置部分在configs/_base_/models目录下&#xff0c;具体实现在mmdet/models/loss目录下。 1.configs 有的时候写在head中作为参数&#xff0c;有的时候head内部进行默认调用。 我们以为例&#xff08;这里没有直接…

linux timer浅析

linux timer 1、数据结构 1.1 timer_list struct timer_list {struct hlist_node entry;unsigned long expires;void (*function)(struct timer_list *);u32 flags;#ifdef CONFIG_LOCKDEPstruct lockdep_map lockdep_map; #endif };entry:定时器保存到哈希表中的节点&am…

QT+Python停车场车牌识别计费管理系统

程序示例精选 Python停车场车牌识别计费管理系统 如需安装运行环境或远程调试&#xff0c;见文章底部微信名片&#xff01; 前言 QTPython是非常经典的窗体编程组合&#xff0c;功能完善&#xff0c;可视化界面美观易维护&#xff0c;这篇博客针对停车场车牌识别计费方面编写代…

JavaScript前端实用的工具函数封装

这篇文章主要为大家介绍了JavaScript前端实用的一些工具函数的封装&#xff0c;有需要的朋友可以借鉴参考下&#xff0c;希望能够有所帮助! 1.webpack里面配置自动注册组件 第一个参数是匹配路径,第二个是深度匹配,第三个是匹配规则 const requireComponent require.contex…

20-Django REST framework-Serializer序列化器

Serializer序列化器前言序列化器作用定义Serializer定义方法字段与选项创建Serializer对象序列化使用基本使用增加额外字段关联对象序列化反序列使用模型类序列化器ModelSerializer指定字段前言 本篇来学习Serializer序列化器知识 序列化器作用 进行数据的校验对数据对象进行…

[附源码]计算机毕业设计基于VUE的网上订餐系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【代码审计-JAVA】基于javaweb框架开发的

目录 一、javaweb三大框架 1、Spring&#xff08;开源分层的框架&#xff09; 2、Struts&#xff08;MVC设计模式&#xff09; 3、Hibernate&#xff08;开源的对象关系映射框架&#xff09; 二、特征 1、结构 2、Servlet 三、重要文件 1、web.xml 2、pom.xml 3、web…

【文献研究】班轮联盟下合作博弈的概念

前言&#xff1a;以下是本人做学术研究时搜集整理的资料&#xff0c;供有相同研究需求的人员参考。 1. 合作博弈的一些概念 合作博弃中比较重要的问题是共赢状态下的利润分配问题&#xff0c;这关系到联盟的合作机制能否长期有效。这里首先介绍几个重要的概念&#xff1a; &…

174.Django中文件上传和下载

1. 文件上传和下载环境搭建 创建django项目和子应用urls中包含子应用&#xff0c;在子应用中创建urls.py配置数据库sqlite3&#xff08;默认就是&#xff0c;无需配置&#xff09;配置settings&#xff0c;上传文件目录编写模型代码&#xff08;下面给出&#xff09;模型的预迁…