苍穹外卖项目总结(二)

本篇对苍穹外卖后半部分进行介绍,重点是redis缓存的使用以及微信小程序客户端开发。

目录

一、菜品管理

1.1新增菜品

 1.2菜品的分页查询

 1.3删除菜品

1.4修改菜品

1.5设置营业状态

二、微信小程序客户端的开发

三、Redis的基本使用

常用命令:

缓存套餐:


一、菜品管理

1.1新增菜品

首先进行需求分析:

 注意口味属性也是一个单独的表,要与这个菜品相对应,记录菜品的id,所以涉及到多个表的操作,一个是菜品表一个是口味表,所以要使用@Transactonal开启事务管理

整体流程如下:

    在dishcontroller中还是正常的使用dishDTO来接受前端传过来的数据,dishDTO中有flavors数组,前端传过来的口味数组可以用这个数组来接收,但是dish中没有这个属性,然后在service中因为要涉及到两个表的操作,一个是dish表(dish表用于存储菜品)一个是dish_flavor表(这个表用于存放口味,其中有dish_id属性),所以此时要保证事务的原子性,要么都成功,要么都失败所以要在前面添加上@Transactional注解,然后向dish表中插入数据,插入之后再向dish_flavor表中插入数据(创建一个dishflavormapper),dish_flavor表中有dish_id,因为dish_id是自增的,前端没有传过来,那如何得到这个dish_id呢,可以想到前面刚插入一个dish数据,可以通过主键回显来得到这个dish_id,然后在dishMapper.xml文件中是使用usegeneratedkeys然后使用keyproperty=id,赋值给id,然后通过dish。getid即可得到这个值,如果不用主键回显是得不到这个值的。由于flavors是一个数组,所以要给这个数组中每一个元素赋值dish_id,然后再进行批量插入即可,使用foreach。最终完成操作。

界面如下: 

整体代码如下: 

//Controller:
@PostMapping
    @ApiOperation("新增菜品")
    public Result save(@RequestBody DishDTO dishDTO){
        log.info("新增菜品:"+dishDTO);
        dishService.saveWithFlavor(dishDTO);

        //清理缓存数据:
        String key="dish_"+dishDTO.getCategoryId();
        redisTemplate.delete(key);
        return Result.success();

    }
//Service:
 @Transactional//需要开启事务管理,可以看到启动类那里已经开启了,添加了这个注解:@EnableTransactionManagement //开启注解方式的事务管理
    public void saveWithFlavor(DishDTO dishDTO) {
        //向菜品表添加一条数据
        //Dish类中没有falvor属性flavor类中有dish_id属性,dishdto类中有List<DishFlavor> flavors属性
        Dish dish=new Dish();
        BeanUtils.copyProperties(dishDTO,dish);
        dish.setStatus(StatusConstant.DISABLE);
        dishMapper.insert(dish);
        //向dish_flavor表中插入数据需要用到dish_id,但是怎么得到dish_id,这时候要用到主键回显,在dishMapper中使用到了usergeneratedkeys
        // 然后返回给这个对象的id属性,然后这个时候使用dish.getId才会有值,如果不使用主键回显是得不到的。
        Long dish_id = dish.getId();
        //向口味表添加n条数据,因为口味可能有多个,也可能没有
        List<DishFlavor> flavors = dishDTO.getFlavors();
        if(flavors.size()>0&&flavors!=null){
            //这里要进行赋值,dish_id:
            flavors.forEach(dish_flavor->{
                dish_flavor.setDishId(dish_id);
            });
            //注意此处flavors是一个数组,想进行插入操作可以批量插入:
            dishFlavorMapper.insertBach(flavors);
        }
    }
Mapper:
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
        insert into dish(name, category_id, price, image, status,description, create_time, update_time, create_user, update_user)
            values(#{name},#{categoryId},#{price},#{image},#{status},#{description},#{createTime},#{updateTime},#{createUser},#{updateUser})
    </insert>
!--注意此处虽然dish_flavor中有dish_id属性,但是前端还没有把这个dish_id传过来,因为主键一般是自增的,
并且还在上传菜品中还没有提交此时还没有值呢,那如何获取dish_id呢,可以想到DishServiceImpl中在dish表插入了一条数据,可以进行主键回显,然后得到这个dish_id这个值,-->
    <insert id="insertBach">
    insert into dish_flavor(dish_id,name,value)values
    <foreach collection="flavors" item="df" separator=",">
        (#{df.dishId},#{df.name},#{df.value})
    </foreach>
    </insert>

 1.2菜品的分页查询

需求分析:需要返回以下数据,需要返回分类名称,所以还需要前端传递分类id,根据分类id去查询分类表得到分类名称,需要连表进行查询,最终设计一个VO封装返回数据。

整体代码如下:

需要注意的就是需要联合查询返回分类名称,这一步容易忽略,此处采用左外连接以及动态查询的方式来获取到categoryname然后封装到dishVO中;注意此处采用左外连接查询,查询dish表的所有加category表中的name,利用category的id相同;

另外就是使用分页插件,PageHelper.startPage(dishPageQueryDTO.getPage(), dishPageQueryDTO.getPageSize()) 这一步的作用就是:​​​​​​

  1. 设置分页参数:设置了分页的参数,包括当前请求的页码(getPage())和每页显示的记录数(getPageSize())。

  2. 拦截后续查询:在调用 startPage 方法后,PageHelper 会拦截紧随其后的第一次 MyBatis 查询操作,并自动为其添加分页的 SQL 语句。

  3. 自动分页:不需要手动编写分页的 SQL 语句,PageHelper 会自动处理分页逻辑,包括计算总页数、总记录数等。

//Controller
/**
     * 分类查询
     * @param dishPageQueryDTO
     * @return
     */
    @ApiOperation("菜品分页查询:")
    @GetMapping("/page")
    public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO){
        PageResult pageResult=dishService.page(dishPageQueryDTO);
        return Result.success(pageResult);
    }
//Service
public PageResult page(DishPageQueryDTO dishPageQueryDTO) {
        PageHelper.startPage(dishPageQueryDTO.getPage(),dishPageQueryDTO.getPageSize());
        Page<DishVO> page=dishMapper.select(dishPageQueryDTO);
        long total = page.getTotal();
        List result = page.getResult();
        return new PageResult(total,result);
    }
//DAO:
Page<DishVO> select(DishPageQueryDTO dishPageQueryDTO);
<select id="select" resultType="com.sky.vo.DishVO">
        select d.*,c.name as categoryName from dish d left outer join category c on d.category_id=c.id
        <where>
            <if test="name!=null">
                and d.name like concat('%',#{name},'%')
            </if>
            <if test="status!=null">
                and d.status=#{status}
            </if>
            <if test="categoryId!=null">
                and d.category_id=#{categoryId}
            </if>
        </where>
        order by d.create_time desc
    </select>

 1.3删除菜品

需求分析:需求分析是很重要的,需要考虑的点也很多,业务层需要做很多的逻辑判断,并且还需要连带口味表中的数据也需要删除。

需要判断菜品是否起售,需要根据id去查询菜品的状态,如果起售状态无法删除。然后需要去判断菜品是否被套餐关联,可以根据菜品id去套餐表中查询数量,如果数量大于零说明已经被关联,无法删除。

需要注意的是前端可以传递多个菜品id批量删除,所以需要用一个LIst接收参数,并且使用@RequestParam注解,前端传过来的是String类型的,1,2,3这种类型的,所以正常来说要用String来接受,然后split分隔成数组,但是现在可以用springmvc来自动解析,然后转换成一个集合。

此外,也需要操作多个表,菜品表和口味表都需要进行删除操作,所以需要开启事务。

整体代码如下:

//Controller
@DeleteMapping
    @ApiOperation("删除菜品")
    public Result delete(@RequestParam List<Long> ids){//注意此处前端传过来的是String类型的,1,2,3这种类型的,所以正常来说要用String来接受,然后split分隔成数组,但是现在可以用springmvc来自动解析,然后转换成一个集合,如果这样的话,需要用一个注解!
        dishService.deleteBatch(ids);

        //清理缓存:此处将所有缓存数据都要删除,首先要获取所有的key
        Set keys = redisTemplate.keys("dish_*");//此处表示获取所有以dish_开头的key
        redisTemplate.delete(keys);//支持集合参数
        return Result.success();
    }
//Service:
@Transactional
    public void deleteBatch(List<Long> ids) {
        //首先要判断能否删除,起售中的菜品无法被删除:
        for(Long id:ids){
            Dish dish=dishMapper.getById(id);
            if(dish.getStatus()== StatusConstant.ENABLE){
                throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
            }
        }
        //被套餐关联的菜品不能被删除:
        int count=setmealDishMapper.getCount(ids);
        if(count>0){
            throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
        }
        dishMapper.delete(ids);
        dishFlavorMapper.delete(ids);
    }
//DAO:
 <delete id="delete">
        delete from dish where id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </delete>
 <delete id="delete">
        delete from dish_flavor where dish_id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </delete>

1.4修改菜品

首先需要进行查询操作回显,然后再进行更新操作。需要注意的是回显的时候会涉及到多个表,例如口味表等 。

但是此处口味表的修改非常复杂,因为修改的时候可能添加可能删除可能修改,所以一个方法就是直接删除掉该菜品联合的口味,然后再重新新增。

@ApiOperation("根据id查询菜品用于修改菜品回显")
    @GetMapping("/{id}")
    public Result<DishVO> getById(@PathVariable Long id){
        log.info("根据id查询菜品:"+id);
        DishVO dishvo=dishService.getById(id);
        return Result.success(dishvo);
    }
    @ApiOperation("修改菜品")
    @PutMapping
    public Result update(@RequestBody DishDTO dishDTO){
        log.info("修改菜品"+dishDTO);
        dishService.update(dishDTO);
        //清理缓存:此处将所有缓存数据都要删除,首先要获取所有的key
        Set keys = redisTemplate.keys("dish_*");//此处表示获取所有以dish_开头的key
        redisTemplate.delete(keys);//支持集合参数
        return Result.success();
    }
//根据id查询菜品
    public DishVO getById(Long id) {
        Dish dish=dishMapper.getById(id);
        List<DishFlavor> flavors=dishFlavorMapper.getById(id);
        DishVO dishVO=new DishVO();
        BeanUtils.copyProperties(dish,dishVO);
        dishVO.setFlavors(flavors);
        return dishVO;
    }
    //修改菜品
    public void update(DishDTO dishDTO) {
        Dish dish=new Dish();
        BeanUtils.copyProperties(dishDTO,dish);
        dishMapper.update(dish);
        //此处口味的修改非常复杂,因为可能会删除可能会新增还可能不修改,所以一种方法是直接都删掉,然后新增:
        List<Long> list=new ArrayList();
        list.add(dishDTO.getId());
        dishFlavorMapper.delete(list);
        List<DishFlavor> flavors = dishDTO.getFlavors();
        if(flavors.size()>0&&flavors!=null){
            //注意此处flavors是一个数组,想进行插入操作可以批量插入:
            flavors.forEach(dish_flavor->{
                //此处仍然需要设置dish_id,原来的都删除掉了,然后新增之后没有dish_id了,还需要在此处设置一下
                dish_flavor.setDishId(dishDTO.getId());
            });
            dishFlavorMapper.insertBach(flavors);
        }

    }

1.5设置营业状态

本来可以使用一个设置接口就可以,管理端和用户端共用这个接口,但是在本项目中用户端的发送请求都是/admin为前缀,用户端都是/user为前缀,所以此处写了两个查询接口。

需求分析:

因为定义了两个shopcontroller,会有冲突,所以可以分别在user和admin中的RestController注解中写上对应名称。

二、微信小程序客户端的开发

首先介绍一下HttpClient:

其中有几个核心API:

httpclient是一个接口可以发送http请求,httpclients可以创建一个httpclient,CloseableHttpClient是一个实现类,实现了HttpClient接口。HttpGet请求和HttpPost请求。

测试get和post请求,调用我们的接口:

@SpringBootTest
public class HttpClientTest {
    /**
     * 通过HttpClient发送get方式请求
     */
    @Test
    public void testGet() throws IOException {
        //创建httpclient对象
        CloseableHttpClient httpClient= HttpClients.createDefault();
        //创建请求对象
        HttpGet httpGet = new HttpGet("http://localhost:8080/user/shop/status");//去访问user端的接口
        //发送请求,接收响应结果
        CloseableHttpResponse response=httpClient.execute(httpGet);
        //获取服务端响应过来的状态码
        int statusCode = response.getStatusLine().getStatusCode();
        System.out.println("服务端发送过来的状态码:"+statusCode);
        //获取响应过来的值:
        HttpEntity entity = response.getEntity();
        String s = EntityUtils.toString(entity);
        System.out.println("服务端响应回来的值:"+s);
        //关闭资源
        response.close();
        httpClient.close();
    }
    /**
     * 通过HttpClient发送post方式请求
     */
    @Test
    public void testPost() throws IOException {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost("http://localhost:8080/admin/employee/login");//这是一个post请求,请求的是管理端的登录接口,需要传过去json格式的数据
        JSONObject jsonObject=new JSONObject();
        jsonObject.put("username","admin");
        jsonObject.put("password","123456");
        StringEntity entity=new StringEntity(jsonObject.toString());
        //指定请求的编码方式:
        entity.setContentEncoding("utf-8");
        //数据格式:
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        //发送请求
        CloseableHttpResponse response = httpClient.execute(httpPost);
        //解析结果:
        int statusCode = response.getStatusLine().getStatusCode();
        System.out.println("响应码为:"+statusCode);
        HttpEntity entity1=response.getEntity();
        String body = EntityUtils.toString(entity1);
        System.out.println("响应数据为"+body);
        //关闭资源:
        response.close();
        httpClient.close();


    }
}

 

微信登录即可完成登录,如果是新用户,自动注册将用户保存到数据库中,要实现微信登陆,就需要获取授权码,小程序先wx.login获取到授权码(code) 然后发送请求到服务端,服务端接收code后发送http请求到微信接口服务,然后微信接口返回session_key和openid。然后服务端接收之后,需要给用户返回令牌,令牌中包含用户的唯一标识。

同样需要配置客户端的jwt登录校验:

@Configuration
@Slf4j
public class WebMvcConfiguration extends WebMvcConfigurationSupport {

    @Autowired
    private JwtTokenAdminInterceptor jwtTokenAdminInterceptor;
    @Autowired
    private JwtTokenUserInterceptor jwtTokenUserInterceptor;

    /**
     * 注册自定义拦截器
     *
     * @param registry
     */
    protected void addInterceptors(InterceptorRegistry registry) {
        log.info("开始注册自定义拦截器...");
        registry.addInterceptor(jwtTokenAdminInterceptor)
                .addPathPatterns("/admin/**")
                .excludePathPatterns("/admin/employee/login");
        registry.addInterceptor(jwtTokenUserInterceptor)
                .addPathPatterns("/user/**")
                .excludePathPatterns("/user/user/login")
                .excludePathPatterns("/user/shop/status");
    }

接口设计:

注意路径中第一个user是user表示用户端,然后第二个user代表的是用户模块。

返回的数据中data中id指的是数据库中这个用户的id,openid表示这个微信用户在小程序中的唯一标识。

 首先是controller层:

需要写好接口,小程序端发送请求到这个接口,发送了授权码(code),然后服务端接收,接收之后调用service中的代码,通过httpclient发送请求到微信接口服务,使用封装好的httpclientutil发送get请求并携带appid、secret以及授权码等参数返回的结果是String类型的json数据,然后需要解析json数据得到openid,判断openid是否为空,为空登陆失败,不为空继续调用mapper中的根据openid查询代码,看是否为新用户,如果是新用户自动注册,调用insert方法添加到数据库,最终返回给controller层这个对象。然后controller层接收这个对象之后,需要给这个用户生成jwt令牌:

创建好jwt令牌之后,需要添加拦截器进行jwt验证,最后配置在webmvcconfiguration中。

 @ApiOperation(value="员工登录")//添加说明
    @PostMapping("/login")
    public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
        log.info("员工登录:{}", employeeLoginDTO);

        Employee employee = employeeService.login(employeeLoginDTO);

        //登录成功后,生成jwt令牌
        Map<String, Object> claims = new HashMap<>();
        claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
        String token = JwtUtil.createJWT(
                jwtProperties.getAdminSecretKey(),
                jwtProperties.getAdminTtl(),
                claims);

        EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()
                .id(employee.getId())
                .userName(employee.getUsername())
                .name(employee.getName())
                .token(token)
                .build();

        return Result.success(employeeLoginVO);
    }

三、Redis的基本使用

本项目中使用到了redis的基本用法,主要是进行增删改查操作,并没有实现setnx加锁等稍微困难的操作,练手还是挺可以的。

Redis是一个基于内存的key-value结构数据库,其直接存储到内存中;mysql数据库是存储到磁盘中,查询操作是磁盘io操作。

下面是一些基础的启动操作:

启用通过以下命令即可,这个是server端:

然后再打开一个命令行窗口,client端:使用这个命令会自动连接到本地的redis,如果想连接其他地方的redis,可以用下面的命令,其中-h表示ip地址,-p表示端口号:

 下面介绍Redis中五种基本数据类型

字符串 string

哈希hash

列表 list

集合set

有序集合zset(一般可以用来做排行榜相关)

常用命令:

字符串类型:

注意setnx一般当作锁来使用

哈希类型:

列表类型:

lpush lpop rpush rpop既可以模拟栈也可以模拟队列

集合类型:

有序集合:


通用命令:

测试如下:

Redis在java中的使用:

Spring Data Redis是Spring的一部分,对Redis底层开发进行了高度封装,在spring项目中,可以使用spring data redis来简化redis操作。

使用的操作步骤如下:

1.导入spring data redis的maven坐标:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.配置redis数据源

application-dev.yml中配置:

sky:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    host: localhost
    port: 3306
    database: sky_take_out
    username: root
    password: dir99
  redis:
    host: Localhost
    port: 6379
    password: dir99
    database: 1
  wechat:
    appid: wx02bddf1e8f6f1036
    secret: d3ed0a340ec836f1131f8f1b582531edy
引用配置:

3.编写配置类,创建RedisTemplate对象


@Configuration
@Slf4j
public class RedisConfiguration {
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
        log.info("开始创建redis模板对象...");
        RedisTemplate redisTemplate=new RedisTemplate();
        //设置redis的连接工厂对象:
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //设置redis key的序列化器,可以将key在redis可视化界面中显示出正常的字符串类型,不会出现看起来乱码的情况
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

下面是具体的使用:

 如果一段时间内大量用户访问并且点菜,菜品都是存放在数据库中的,就要频繁访问数据库,会导致性能下降,卡顿,点击一个分类后可能要几秒钟之后才能显示出数据。所以可以使用缓存,把这些商品数据缓存到redis中,提高性能。

那么一个菜品加入一个缓存数据还是多个呢?可以想到小程序展示菜品的时候是根据分类来展示的,所以每一类保存一个缓存数据,每一个类构建一个key。

实现思路如下:

每一类可以保存一个缓存,在redis中是通过key-value来保存数据的,所以可以通过以下方式来保存,dish后面加一个动态数值,表示分类id,然后将java中list集合,转换为String类型(注意,此处java中的类型和redis中的类型不太一样,所以对list进行序列化然后转换成redis中的String类型)保存到redis中。但是如果菜品有变更,例如,管理端更改价格,要及时清理缓存数据,因为缓存没有同步更新过来,不清理的话会导致数据不一致性,所以要清理缓存数据。

@GetMapping("/list")
    @ApiOperation("根据分类id查询菜品")
    public Result<List<DishVO>> list(Long categoryId) {
        /**
         * 使用缓存:
         */
        //先构造key进行查询:dish_分类id格式:
        String key="dish_"+categoryId;
        //查询缓存中是否存在菜品数据  注意此处下方放进去是什么类型取出来就是什么类型,下面用的List<DishVO>,这里强转以下就行
        List<DishVO> list = (List<DishVO>)redisTemplate.opsForValue().get(key);
        if(list!=null&& list.size()>0){
            //如果存在,直接返回,无需查询数据库
            return Result.success(list);
        }
        //如果不存在,查询数据库,将查询的结果返回并且还要存到缓存中
        Dish dish = new Dish();
        dish.setCategoryId(categoryId);
        dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品
        list = dishService.listWithFlavor(dish);
        redisTemplate.opsForValue().set(key,list);
        return Result.success(list);
    }

更改完之后,需要再继续更改,添加清理缓存操作,保证数据一致性:

分析当删除,新增,修改,起售停售菜品操作都需要清理缓存:

以上操作都在管理端进行,所以要在管理端进行修改:

清理缓存并不需要删除掉所有的缓存,只需要对更改的进行清理即可,哪一个分类被修改,清理这一个类即可。像新增操作改的哪个类删除哪个缓存数据即可,但是删除操作,前端传过来的是一个数组,里面可能包含多个菜品,这些菜品可能属于一个类也可能属于多个类,所以简单来看直接将所有的缓存删除即可。

注意修改操作:普通的修改像改名字,价格只设计一个分类,但是如果要更改分类的话,就要涉及两个分类,这一个类少一个那一个类多一个所以此处直接同样删除所有类的缓存数据。

起售停售菜品也是,前端传过来的有status以及id,如果想删除单个类的缓存也可以,需要根据菜品id查询分类进行查询操作然后得到这个类的key然后进行删除,但是需要进行查询操作有点浪费资源所以不如直接删除。

缓存套餐:

Spring cache(重要!!!)

有些类似AOP,只需要在需要缓存的方法加上注解即可。

在启动类上加上@EnableCaching注解来开启缓存注解功能。

如果想换为Caffeine只需要导入这个坐标,删除redis坐标即可。

后三个注解都是加在方法上的,cachePut与Cacheable的区别就是cacheable既可以取也可以存,另一个只能存不能取。

spring cache demo:

首先创建一个user表:

然后在pom文件中配置坐标,上图有;

@CachePut注解的使用

对于这个save方法,也就是插入操作,新增一个新用户,想插入数据库的同时保存到redis中一份,之前是通过redisTemplete然后set方法来实现,用了springcache之后,里面有一个注解就是@CachePut注解,可以直接将方法返回值加到缓存中。

#user.id,这个#后面的需要和方法中的形参名一致。但是要在mapper中使用主键回显!

在redis可视化软件中:

是根据冒号来确定的树形结构,完整的key是a:b:c:d

@Cacheable注解使用

对于这个getbyid方法,想在调用mapper之前先判断缓存中是否有这个数据,如果有的话直接返回缓存中的数据,如果没有的话,再去查询数据库返回。之前也是用的redisTemplete中get方法进行判断。先设置key然后get。使用SpringCache之后可以用@Cacheable注解

加上注解之后:和上面的putCache注解类似,存到redis中的key为cacheNames::key,#

后面还需要和方法参数一致。

加上这个注解之后,其实在controller层的getById方法执行之前先有一个代理对象去缓存中查找,如果查找到了直接就返回缓存中的数据。这个方法其实没有被调用。如果没有查到通过反射来调用controller层中的这个方法。最后将返回结果放到redis中。

@CacheEvict注解的使用

这样可以删除一个缓存数据

这种方式将userCache下的键值缓存数据对全部都删除

得不偿失所以直接都删除掉。

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

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

相关文章

MyBatisPlus简介及入门案例

一、简介 官网&#xff1a;https://baomidou.com/introduce/ 1.简介 MyBatisPlus只做增强&#xff0c;不做改变&#xff0c;为简化开发、提高效率而生 2.特性 (1)无侵入 只做增强不做改变&#xff0c;引入它不会对现有工程产生影响&#xff0c;如丝般顺滑 (2)损耗小 启动…

欧拉(Euler 22.03)安装ProxySQL

下载离线安装包 proxysql-2.0.8-1-centos7.x86_64.rpm 链接: https://pan.baidu.com/s/1R-SJiVUEu24oNnPFlm9wRw 提取码: sa2w离线安装proxysql yum localinstall -y proxysql-2.0.8-1-centos7.x86_64.rpm 启动proxysql并检查状态 systemctl start proxysql 启动proxysql syste…

电子应用设计方案96:智能AI充电器系统设计

智能 AI 充电器系统设计 一、引言 智能 AI 充电器系统旨在为各种电子设备提供高效、安全、智能的充电解决方案&#xff0c;通过融合人工智能技术&#xff0c;实现自适应充电、优化充电效率和保护电池寿命。 二、系统概述 1. 系统目标 - 自适应识别不同设备的充电需求&#xf…

TongESB7.1.0.0如何使用dockercompose运行镜像(by lqw)

文章目录 安装准备安装 安装准备 1.安装好docker和dockercompose&#xff1a; docker、docker-compose安装教程&#xff0c;很详细 2.上传好安装相关文件 安装 使用以下命令导入管理端镜像和运行时镜像 docker load -i tongesb_manage_7100.tar docker load -i tongesb_se…

Python 模拟真人鼠标轨迹算法 - 防止游戏检测

一.简介 鼠标轨迹算法是一种模拟人类鼠标操作的程序&#xff0c;它能够模拟出自然而真实的鼠标移动路径。 鼠标轨迹算法的底层实现采用C/C语言&#xff0c;原因在于C/C提供了高性能的执行能力和直接访问操作系统底层资源的能力。 鼠标轨迹算法具有以下优势&#xff1a; 模拟…

微软Win10 RP 19045.5435(KB5050081)预览版发布!

系统之家1月20日最新报道&#xff0c;微软面向Release Preview频道的Windows Insider项目成员&#xff0c;发布了适用于Windows10 22H2版本的KB5050081更新&#xff0c;更新后系统版本号将升至19045.5435。本次更新增加了对GB18030-2022标准的支持&#xff0c;同时新版日历将为…

【VRChat · 改模】Unity工程导入人物模型;并添加着色器教程;

一、Unity工程导入人物模型 1.创建一个新的工程文件&#xff08;使用 VRChat 官方的开发工具 VCC&#xff09; 不添加着色器的时候&#xff0c;模型是粉色的 2.导入人物模型 在工程文件的 Assets 目录下&#xff0c;创建一个新的目录&#xff0c;可以起名为你的模型的名字 …

Django简介与虚拟环境安装Django

目录 1.Django简介 1.1 Django 的核心特点 1.2 Django 的核心组件 1.3 Django 的应用场景 1.4 总结 2.基础环境建立 2.1 创建虚拟环境 2.1.1 使用 virtualenv 创建虚拟环境 2.1.2 使用 venv 创建虚拟环境 2.2 激活虚拟环境 2.2.1 在 Windows 上 2.2.2 在 macOS 或 …

使用 ChatGPT 生成和改进你的论文

文章目录 零、前言一、操作引导二、 生成段落或文章片段三、重写段落四、扩展内容五、生成大纲内容六、提高清晰度和精准度七、解决特定的写作挑战八、感受 零、前言 我是虚竹哥&#xff0c;目标是带十万人玩转ChatGPT。 ChatGPT 是一个非常有用的工具&#xff0c;可以帮助你…

宝塔面板修改端口号后无法访问?

解决办法&#xff1a; 1、查询端口是否修改 cat www/server/panel/data/port.pl 显示 当前的端口例如&#xff1a;12123 2.查询防火墙的开放的端口 命令&#xff1a;firewall-cmd --list-ports 3. 使用firewall-cmd --query-port12123/tcp 如显示yes 表示开通&#xff0c;…

Open3D 最小二乘拟合平面(直接求解法)【2025最新版】

目录 一、算法原理二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫与GPT。 博客长期更新,本文最近更新时间为:2025年1月18日。 一、算法原理 平面方程的一般表达式为:

docker的数据卷与dockerfile自定义镜像

docker的数据卷与dockerfile自定义镜像 一. docker的数据卷数据卷容器 二. dockerfile自定义镜像2.1 dockerfile的命令格式镜像的操作命令add和copy的区别 容器启动的命令 2.2 run命令2.3 其它端口映射 三. 练习 一. docker的数据卷 容器于宿主机之间&#xff0c;或者容器和容…

项目练习:若依后台管理系统-后端服务开发步骤(springboot单节点版本)

文章目录 1、用Maven搭建项目脚手架&#xff0c;父子工程依赖。2、引入SpringBoot Web容器依赖3、引入Mybatisdruid依赖4、实现接口查询数据5、整合logback日志功能 1、用Maven搭建项目脚手架&#xff0c;父子工程依赖。 root模块的pom添加plugin配置 <build><plugins…

前端开发Web

Ajax 概念:Asynchronous JavaScriptAnd XML&#xff0c;异步的JavaScript和XML 作用: 数据交换:通过Ajax可以给服务器发送请求&#xff0c;并获取服务器响应的数据。 异步交互:可以在不重新加载整个页面的情况下&#xff0c;与服务器交换数据并更新部分网页的…

debian中apt的配置与解析

引言 在系统使用过程中&#xff0c;我们可能会遭遇 apt update 操作出现问题&#xff0c;或者 apt upgrade 速度迟缓的情况。这往往是由于所使用软件源本身存在诸如服务器性能不佳、维护不及时等质量问题&#xff0c;同时&#xff0c;软件源服务器与我们所处地理位置的距离较远…

Docker 实现MySQL 主从复制

一、拉取镜像 docker pull mysql:5.7相关命令&#xff1a; 查看镜像&#xff1a;docker images 二、启动镜像 启动mysql01、02容器&#xff1a; docker run -d -p 3310:3306 -v /root/mysql/node-1/config:/etc/mysql/ -v /root/mysql/node-1/data:/var/lib/mysql -e MYS…

day03_开发前准备和匹配类标签

文章目录 day03_开发前准备和匹配类标签一、标签体系(了解)二、数据导入(操作)1、背景介绍(重要)2、创建Hive表2.1 dwm_sold_goods_sold_dtl_i2.2 dwm_sell_o2o_order_i**2.3 dwd_mem_member_union_i**2.4 dwm_mem_member_behavior_day_i**2.5 dwm_mem_first_buy_i**3、数…

Video-RAG:一种将视频RAG新框架

1. 摘要及主要贡献点 摘要&#xff1a; 检索增强生成&#xff08;RAG&#xff09;是一种强大的策略&#xff0c;通过检索与查询相关的外部知识并将其整合到生成过程中&#xff0c;以解决基础模型生成事实性错误输出的问题。然而&#xff0c;现有的RAG方法主要集中于文本信息&…

VSCode的配置与使用(C/C++)

从0开始教你在vscode调试一个C文件 一.首先是配置你的编译环境&#xff0c;添加到环境变量&#xff08;默认你是全新的电脑&#xff0c;没有安装vs2019之类的&#xff09; 原因&#xff1a;因为相比于vs2019&#xff0c;vscode只是个代码编辑器&#xff0c;相当于一个彩色的、…

MongoDB基本操作

一、实验目的 1. 熟悉MongoDB的基本操作&#xff0c;包括CRUD&#xff08;增加、读取、更新、删除&#xff09;。 2. 理解MongoDB的文档型数据库特性和Shell的使用。 3. 培养学生通过命令行操作数据库的能力。 4. 强化数据库操作的实际应用能力。 二、实验环境准备 1.…