SpringBoot整合MongoDB JPA使用

news2024/10/10 8:20:58

一、整合MongoDB

SpringDataMongoDB是 SpringData家族成员之一,MongoDB的持久层框架,底层封装了 mongodb-driver。mongodb-driver 是 MongoDB官方推出的 Java连接 MongoDB的驱动包,相当于JDBC驱动。

SpringBoot整合 MongoDB,引入 SpringDataMongoDB依赖。

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

1、配置MongoDB信息

在 application.yml文件中,添加 MongoDB连接信息。

spring:
  data:
    mongodb:
      # 超级管理员是必须指定?后面的固定写法
      uri: mongodb://用户名:密码@127.0.0.1:27017/ws_sb_local?authSource=admin&authMechanism=SCRAM-SHA-1
      # 普通用户不能指定?后面的固定写法
      # uri: mongodb://luna1:密码@127.0.0.1:27017/ws_sb_local

然后启动项目就可以使用 MongoTemplate或者 MongoDB JPA框架(SpringDataMongoDB)。

2、JPA查询规范

使用 JPA查询时,我们要遵循一定的规范。

示例如下:

在这里插入图片描述

3、JPA注解

JPA常用注解如下:

@Id
用于字段级别,标记这个字段是一个主键,默认生成的名称是“_id”。

@Document
用于类,以表示这个类需要映射到数据库,您也可以指定映射到数据库的集合名称。

@Field
用于字段,并描述字段的名称,因为它将在MongoDB BSON文档中表示,允许名称与该类的字段名不同。

@CreatedDate
用途: 该注解用于标记一个字段,表示这个字段将自动保存文档首次创建时的日期和时间。
行为: 当一个新的文档被插入到MongoDB集合中时,标记了@CreatedDate的字段会被自动填充为当前的日期和时间。
数据类型: 通常该字段应为java.util.Date、java.time.LocalDateTime或java.time.Instant等日期时间类型。

@LastModifiedDate
用途: 这个注解用于标记一个字段,以记录文档最后一次被修改的日期和时间。
行为: 每当文档被更新时,这个字段会自动更新为当前的日期和时间。
数据类型: 类似于@CreatedDate,它也支持java.util.Date、java.time.LocalDateTime或java.time.Instant等类型。

@Indexed
用于字段,表示该字段需要如何创建索引。

@CompoundIndex
用于类,以声明复合索引。

@GeoSpatialIndexed
用于字段,进行地理位置索引 。

@DBRef
用于字段,以表示它将使用com.mongodb.DBRef进行存储。

@TextIndexed
用于字段,标记该字段要包含在文本索引中。

@Language
用于字段,以设置文本索引的语言覆盖属性。

@Transient
默认情况下,所有私有字段都映射到文档,此注解将会去除此字段的映射

@PersistenceConstructor
标记一个给定的构造函数,即使是一个protected修饰的,在从数据库实例化对象时使用。构造函数参数通过名称映射到检索的DBObject中的键值。

@Value
这个注解是 Spring框架的一部分。在映射框架内,它可以应用于构造函数参数。这允许您使用Spring表达式语言语句来转换在数据库中检索的键值,然后再用它来构造一个域对象。为了引用给定文档的属性,必须使用以下表达式:@Value(“#root.myProperty”),root要指向给定文档的根。

@Version
用于字段锁定,保存操作时检查修改。初始值是0,每次更新时自动触发。

二、MongoDB JPA使用

这里直接上代码。

1、DO类

/**
 * 设备Gps信息
 */
@Data
@Document("device_gps")
public class DeviceGpsDO implements Serializable {
    private static final long serialVersionUID = -9179758476973350170L;

    /**
     * 主键
     */
    @Id
    private String id;

    /**
     * 创建时间
     */
    @CreatedDate
    @Field("create_time")
    private LocalDateTime createTime;

    /**
     * 修改时间
     */
    @LastModifiedDate
    @Field("update_time")
    private LocalDateTime updateTime;

    /**
     * 设备名称
     */
    @Field("device_name")
    @Indexed
    private String deviceName;

    /**
     * 设备编号
     */
    @Field("device_no")
    private String deviceNo;

    /**
     * 设备识别VIN码
     */
    @Field("vin_no")
    private String vinNo;

    /**
     * 预警状态
     */
    @Field("warn_status")
    private Integer warnStatus;

    /**
     * 速度(千米/小时)
     */
    @Field("speed")
    private Float speed;

    /**
     * 海拔高度
     */
    @Field("altitude")
    private Double altitude;

    /**
     * 卫星定位时间
     */
    @Field("satellite_time")
    private Date satelliteTime;

    /**
     * 地理位置,用于LBS搜索
     */
    @GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)
    private GeoJsonPoint location;

    /**
     * 经度
     */
    @Field("longitude")
    private Double longitude;

    /**
     * 纬度
     */
    @Field("latitude")
    private Double latitude;

}

2、Dao类

/**
 * 设备Gps信息
 * 继承 MongoRepository,指定实体和主键的类型作为泛型
 */
public interface DeviceGpsDao extends MongoRepository<DeviceGpsDO, String> {

    /**
     * JPA语法查询根据速度大于等于和预警状态查询
     *
     * @param speed      速度
     * @param warnStatus 预警状态
     * @return
     */
    List<DeviceGpsDO> findBySpeedGreaterThanEqualAndWarnStatusEquals(Float speed, Integer warnStatus);

    /**
     * @Query注解查询根据速度大于等于和预警状态查询
     *
     * @param speed      速度
     * @param warnStatus 预警状态
     * @return
     */
    @Query("{ speed: {$gte : ?0}, warn_status: ?1  }")
    List<DeviceGpsDO> getBySpeedAndWarnStatus(Float speed, Integer warnStatus);

}

3、Service类

(1)接口

public interface DeviceGpsService {

    /**
     * 分页查询
     *
     * @param pageQueryRequest
     * @return
     */
    BasePageResult pageQuery(DeviceGpsPageQueryRequest pageQueryRequest);

    /**
     * 新增
     *
     * @param deviceGpsDO
     */
    BaseOperateResult insert(DeviceGpsDO deviceGpsDO);

    /**
     * 新增或者修改
     *
     * @param deviceGpsDO
     */
    BaseOperateResult insertOrUpdate(DeviceGpsDO deviceGpsDO);

    /**
     * 根据 id删除
     *
     * @param id
     */
    BaseOperateResult deleteById(String id);

    /**
     * 根据 id查询
     *
     * @param id
     * @return
     */
    DeviceGpsDO findById(String id);

    /**
     * 查询所有
     *
     * @return
     */
    List<DeviceGpsDO> findAll();

    /**
     * JPA语法查询根据速度大于等于和预警状态查询
     *
     * @param speed      速度
     * @param warnStatus 预警状态
     * @return
     */
    List<DeviceGpsDO> findBySpeedGreaterThanEqualAndWarnStatusEquals(Float speed, Integer warnStatus);

    /**
     * @Query注解查询根据速度大于等于和预警状态查询
     *
     * @param speed      速度
     * @param warnStatus 预警状态
     * @return
     */
    List<DeviceGpsDO> getBySpeedAndWarnStatus(Float speed, Integer warnStatus);


}

(2)实现类

@Slf4j
@Service
@RequiredArgsConstructor
public class DeviceGpsServiceImpl implements DeviceGpsService {

    private final DeviceGpsDao deviceGpsDao;

    @Override
    public BasePageResult pageQuery(DeviceGpsPageQueryRequest pageQueryRequest) {
        BasePageResult pageResult = new BasePageResult();

        String deviceName = pageQueryRequest.getDeviceName();
        Integer warnStatus = pageQueryRequest.getWarnStatus();
        // 创建查询条件的实例
        DeviceGpsDO queryDO = new DeviceGpsDO();
        if (StringUtils.isNoneBlank(deviceName)) {
            queryDO.setDeviceName(deviceName);
        }
        if (warnStatus != null) {
            queryDO.setWarnStatus(warnStatus);
        }

        // 设置ExampleMatcher来定义匹配规则
        ExampleMatcher matcher = ExampleMatcher.matching()
                .withIgnoreCase() // 忽略大小写
                .withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING); // 包含

        // 使用条件对象和匹配器创建Example实例
        Example<DeviceGpsDO> example = Example.of(queryDO, matcher);

        // 构造分页对象, 从 0 开始
        int currentPage = pageQueryRequest.getCurrentPage().intValue();
        int pageSize = pageQueryRequest.getPageSize().intValue();
        PageRequest pageRequest = PageRequest.of(currentPage - 1, pageSize);
        // 分页查询
        /**
         * Example 这种方式更适合于属性匹配(如字符串的模糊匹配)。对于比较运算(如大于、小于等),使用Spring Data MongoDB提供的Criteria和Query对象来构建更复杂的查询条件。
         */
        Page<DeviceGpsDO> doPage = deviceGpsDao.findAll(example, pageRequest);

        log.info("doPage.getNumber() = {}", doPage.getNumber());
        log.info("doPage.getNumberOfElements() = {}", doPage.getNumberOfElements());
        log.info("doPage.getSize() = {}", doPage.getSize());
        log.info("doPage.getTotalElements() = {}", doPage.getTotalElements());
        log.info("doPage.getTotalPages() = {}", doPage.getTotalPages());

        pageResult.setPaginator(new Paginator(doPage.getNumber() + 1, doPage.getSize(), doPage.getTotalElements(), doPage.getTotalPages()));
        pageResult.setPageList(doPage.getContent());
        pageResult.setSuccess(true);
        return pageResult;
    }

    @Override
    public BaseOperateResult insert(DeviceGpsDO deviceGpsDO) {
        BaseOperateResult operateResult = new BaseOperateResult();
        // 新增
        Double longitude = deviceGpsDO.getLongitude();
        Double latitude = deviceGpsDO.getLatitude();
        if (longitude != null || latitude != null) {
            // 经纬度 GeoJsonPoint
            deviceGpsDO.setLocation(new GeoJsonPoint(longitude, latitude));
        }
        deviceGpsDao.insert(deviceGpsDO);

        operateResult.setSuccess(Boolean.TRUE);
        return operateResult;
    }

    @Override
    public BaseOperateResult insertOrUpdate(DeviceGpsDO deviceGpsDO) {
        BaseOperateResult operateResult = new BaseOperateResult();
        // 新增或者修改
        Double longitude = deviceGpsDO.getLongitude();
        Double latitude = deviceGpsDO.getLatitude();
        if (longitude != null || latitude != null) {
            // 经纬度 GeoJsonPoint
            deviceGpsDO.setLocation(new GeoJsonPoint(longitude, latitude));
        }
        // MongoDB会清除字段为 null的字段。
        deviceGpsDao.save(deviceGpsDO);

        operateResult.setSuccess(Boolean.TRUE);
        return operateResult;
    }

    @Override
    public BaseOperateResult deleteById(String id) {
        BaseOperateResult operateResult = new BaseOperateResult();
        deviceGpsDao.deleteById(id);

        operateResult.setSuccess(Boolean.TRUE);
        return operateResult;
    }

    @Override
    public DeviceGpsDO findById(String id) {
        return deviceGpsDao.findById(id).get();
    }

    @Override
    public List<DeviceGpsDO> findAll() {
        return deviceGpsDao.findAll();
    }

    @Override
    public List<DeviceGpsDO> findBySpeedGreaterThanEqualAndWarnStatusEquals(Float speed, Integer warnStatus) {
        List<DeviceGpsDO> list = deviceGpsDao.findBySpeedGreaterThanEqualAndWarnStatusEquals(speed, warnStatus);
        return list;
    }

    @Override
    public List<DeviceGpsDO> getBySpeedAndWarnStatus(Float speed, Integer warnStatus) {
        List<DeviceGpsDO> list = deviceGpsDao.getBySpeedAndWarnStatus(speed, warnStatus);
        return list;
    }

}

然后,单元测试调用ok。

4、MongoDB的UTC时差问题

SpringDataMongoDB框架会自动处理 MongoDB的UTC时差问题。我们正常在服务端代码中保存和查询操作即可。但是,对于 Controller接口返回是不一样的,与使用 LocalDateTime和 Date类型有关。

MongoDB数据库记录:
在这里插入图片描述

Controller接口返回DO数据:

在这里插入图片描述

结论:

  • 如果使用 Date类型,接口返回存在 8小时时差,请使用 @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")注解并且必须指定时区解决。
  • 如果使用 LocalDateTime,接口返回不存在时差问题,序列化时也可以使用 @JsonFormat注解。
  • 推荐使用 LocalDateTime

在这里插入图片描述

5、“_class” 字段问题

SpringBoot整合mongodb时,发现添加文档会自动添加一个_class的字段。

在这里插入图片描述

有或者没有_class列,对于我们使用 MongoDB进行任何操作及序列化实体类都没有任何影响。

所以,我们可以使用下面方法,禁用 “_class” 字段。

新建 MongoDB配置类。

//@EnableMongoAuditing   // 开启审计功能
@Configuration
public class MongoDBConfig implements InitializingBean {

    @Resource
    private MappingMongoConverter mappingMongoConverter;

    @Override
    public void afterPropertiesSet() {
        // 禁用 "_class" 字段
        mappingMongoConverter.setTypeMapper(new DefaultMongoTypeMapper(null));
    }

}

6、开启审计功能

使用 @EnableMongoAuditing注解开启审计功能。

我们在启动类或者 上面配置类上添加它之后,@CreatedDate和 @LastModifiedDate注解就会生效了。

注意:@CreatedDate:id为空时生效。

三、@Query注解使用

在 Dao类中,我们可以通过使用 @Query 注解手写查询语句。

1、简单参数传参

我们需要使用 ?数字占位符 表达式来取出参数中指定位置的值,占位符从 0开始。

    @Query("{ speed: {$gte : ?0}, warn_status: ?1  }")
    List<DeviceGpsDO> getBySpeedAndWarnStatus(Float speed, Integer warnStatus);

2、对象参数传参

我们需要使用 SpEL 表达式,格式:?#{[]}

括号中使用 [下标] 来取出指定位置的参数,比如:?#{[0]} 则取出第一个参数。之后直接取出参数中的指定属性即可,比如如 ?#{[1].age} 就是取出第二个参数的age属性。

    @Query("{ speed: {$gte : ?#{[0].speed}}, warn_status: ?#{[0].warnStatus} }")
    List<DeviceGpsDO> getBySpeedAndWarnStatus2(DeviceGpsDO deviceGpsDO);

3、投影查询

如果我们只想获取指定的字段,可以使用 @Query注解的 fields 来进行投影查询。

    @Query(value= "{ speed: {$gte : ?#{[0].speed}}, warn_status: ?#{[0].warnStatus} }",
            fields = "{ speed:1, warn_status:1 }")
    List<DeviceGpsDO> getBySpeedAndWarnStatus3(DeviceGpsDO deviceGpsDO);

测试:

    @Override
    public List<DeviceGpsDO> getBySpeedAndWarnStatus(Float speed, Integer warnStatus) {
        List<DeviceGpsDO> list = deviceGpsDao.getBySpeedAndWarnStatus(speed, warnStatus);

        DeviceGpsDO deviceGpsDO = new DeviceGpsDO();
        deviceGpsDO.setSpeed(0.0F);
        deviceGpsDO.setWarnStatus(0);
        List<DeviceGpsDO> list2 = deviceGpsDao.getBySpeedAndWarnStatus2(deviceGpsDO);
        List<DeviceGpsDO> list3 = deviceGpsDao.getBySpeedAndWarnStatus3(deviceGpsDO);
        log.info("list = {} ", list);
        log.info("list2 = {} ", list2);
        log.info("list3 = {} ", list3);
        return list;
    }

在这里插入图片描述

总结:

  • 对于保存、修改、删除等操作,JPA语法使用非常友好,推荐使用
  • 对于动态条件查询,JPA语法与@Query注解处理不够友好,不推荐使用。
  • 对于动态条件查询,推荐使用 Criteria对象构建动态参数,然后使用 mongoTemplate查询。
  • 项目中,通常会 JPA语法与 mongoTemplate两者结合使用。

– 求知若饥,虚心若愚。

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

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

相关文章

MySQL数据库—MHA高可用配置及故障切换

目录 一、MHA概述 1.什么是 MHA 2.MHA 的组成 &#xff08;1&#xff09;MHA Node&#xff08;数据节点&#xff09; &#xff08;2&#xff09;MHA Manager&#xff08;管理节点&#xff09; (3)MHA 的特点 二、MHA的一主两从部署 实验设计 实验具体操作 1.配置主…

一次breach1靶机的渗透测试

1.端口扫描和信息收集 2.CMS后台信息收集 3.解密HTTPS流量 4.tomcat的后台利用 5.提权 1.端口扫描和信息收集&#xff1a; 首先进行主机发现&#xff0c;找到目标机器&#xff1a; nmap -sP 192.168.110.1/24 找到目标机器&#xff0c;进行端口扫描&#xff1a; nmap -T4 …

CS144 Lab3 TCPSender复盘

一.基础概念 1.TCPSender在TCPSocket中的地位与作用 Lab0中实现了基于内存模拟的流控制-字节流&#xff08;ByteStream&#xff09;&#xff0c;底层使用std::deque实现&#xff0c;根据最大容量Capacity进行容量控制。个人理解它相当于应用层的输入输出缓存区&#xff0c;用户…

【opencv - C++ - Ubuntu】putText 显示中文最快方法

话不多说&#xff0c;直接上代码 #include <iostream> #include <opencv2/opencv.hpp> #include <opencv2/freetype.hpp>using namespace std; using namespace cv;int main(void) {Mat image(1000, 1800, CV_8UC3, Scalar(200,162,33));Ptr<freetype::F…

如何找到合适的Python第三方库?

找合适的Python库其实很简单&#xff0c;按照以下三步法&#xff0c;你能找到90%的Python库。 1、百度谷歌搜索 明确自己的需求&#xff0c;用Python来干什么&#xff0c;力求简短明了。比如定位“数据分析”&#xff0c;然后去搜索关键词【Python数据分析第三方库】&#xf…

【嵌入式Linux】i.MX6ULL 外部中断服务函数的初始化

文章目录 1. Cortex-A7 中断系统1.1 分析1.2 具体处理流程 2. 外部中断服务函数的初始化2.1 基本流程分析2.2 具体代码分析2.2.1. 定义中断处理类型和结构体2.2.2. 初始化中断系统2.2.3. 注册中断处理函数2.2.4. 具体的中断处理逻辑2.2.5. 默认的中断处理函数 3. 完整代码 本文…

django学习入门系列之第三点《案例 小米商城二级菜单》

文章目录 样例划分区域搭建骨架logo区域完整代码 小结往期回顾 样例 划分区域 搭建骨架 <!-- 二级菜单部分 --> <div class"sub-header"><div class"container"><div class"logo">1</div><div class"sea…

[word] Word表格怎么填充序列号? #微信#微信#笔记

Word表格怎么填充序列号&#xff1f; Word表格怎么填充序列号&#xff1f;在Excel中填充序列号是很轻松的事情&#xff0c;在Word表格中填充序列号就没那么简单&#xff0c;但是还是有小技巧&#xff0c;可以实现Word表格序号填充&#xff0c;还能自动更新。 1、插入序号 先…

JAVAEE之网络原理_传输控制协议(TCP)的滑动窗口、流量控制、拥塞控制、延迟应答、捎带应答机制

前言 在前面几节&#xff0c;我们讲解了TCP协议的基本概念、报文格式。还介绍了确认应答机制、超时重传、连接管理机制&#xff0c;在本节中 我们将会继续介绍TCP协议的其他机制。 一、滑动窗口机制&#xff08;效率机制&#xff09; 在前面的章节中我们讨论了确认应答策略&…

C++ ─── vector的实现

知识点&#xff1a; ① 因为vector是模版&#xff0c;所以声明和定义都放在.h中&#xff0c;防止出现编译错误 .h不会被编译&#xff0c;在预处理中.h在.cpp中展开所以在编译时只有.cpp 而 .cpp顺序编译&#xff0c;只会进行向上查找&#xff0c;因此至少有函数的声明。 ②memc…

【Linux】进程 | 控制块pcb | task_struct | 创建子进程fork

目录 Ⅰ. 进程的概念&#xff08;Process&#xff09; 1. 什么是进程&#xff1f; 2. 多进程管理 3. 进程控制块&#xff08;PCB&#xff09; task_struct 的结构 Ⅱ. 进程查看与管理 1. 使用指令查看进程 2. /proc 查看进程信息 3. 获取进程 ID 4. 创建子进程 原因…

在Ubuntu22.04 使用stable-diffusion-webui 秋叶整合包

背景 众所周知&#xff0c;赛博菩萨已经发布了windows下的整合包&#xff0c;开箱即用&#xff0c;且集成度较高。 那我为啥非要在Ubuntu下使用呢&#xff1f; 当然是因为主力机就是Ubuntu系统啦。而且涉及到sd webui API 的调用&#xff0c;在Ubuntu 下调试更加方便一点。 那…

PG实践|内置函数之GENERATE_SERIES之深入理解

&#x1f4eb; 作者简介&#xff1a;「六月暴雪飞梨花」&#xff0c;专注于研究Java&#xff0c;就职于科技型公司后端工程师 &#x1f3c6; 近期荣誉&#xff1a;华为云云享专家、阿里云专家博主、腾讯云优秀创作者、ACDU成员 &#x1f525; 三连支持&#xff1a;欢迎 ❤️关注…

2024年第十五届蓝桥杯青少组大赛8月24日开启

据蓝桥杯青少组官网显示&#xff0c;2024年第十五届蓝桥杯青少组大赛8月24日开启。 蓝桥杯青少组历届题库地址&#xff1a;http://www.6547.cn/question/cat/2 蓝桥杯青少组历届真题下载&#xff1a;http://www.6547.cn/wenku/list/10

【神经网络】CNN网络:深入理解卷积神经网络

&#x1f388;个人主页&#xff1a;豌豆射手^ &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进步&#xff01; CNN网络&#xff1a;深入理解…

VideoLLaMA 2:多模态视频理解新突破,音频理解能力再升级,挑战 GPT-4V

前言 近年来&#xff0c;人工智能技术飞速发展&#xff0c;尤其是大模型的出现&#xff0c;为视频理解和生成领域带来了前所未有的机遇。然而&#xff0c;现有的视频大模型&#xff08;Video-LLM&#xff09;在处理视频中复杂的时空信息和音频信息方面仍存在不足&#xff0c;例…

【C++11(二)】lambda表达式和可变参数模板

一、可变参数模板 C11的新特性可变参数模板 能够让您创建可以接受 可变参数的函数模板和类模板 // Args是一个模板参数包&#xff0c;args是一个函数形参参数包 // 声明一个参数包Args...args&#xff0c;这个参数包中可以包含0到任意个模板参数。 template <class ...Arg…

笔记101:OSQP求解器的底层算法 -- ADMM算法

前言1&#xff1a;这篇博客仅限于介绍拉格朗日乘子法&#xff0c;KKT条件&#xff0c;ALM算法&#xff0c;ADMM算法等最优化方法的使用以及简版代码实现&#xff0c;但不会涉及具体的数学推导&#xff1b;不过在下面我会给出具体数学推导的相关文章和截图&#xff0c;供学有余力…

Elasticsearch:使用 Llamaindex 的 RAG 与 Elastic 和 Llama3

这篇文章是对之前的文章 “使用 Llama 3 开源和 Elastic 构建 RAG” 的一个补充。我们可以在本地部署 Elasticsearch&#xff0c;并进行展示。我们将一步一步地来进行配置并展示。你还可以参考我之前的另外一篇文章 “Elasticsearch&#xff1a;使用在本地计算机上运行的 LLM 以…

在线epub阅读器epub;在线图书阅读器;专门为epub定制的阅读器;免费在线电子图书epub阅读器

背景&#xff1a;不记得某时某刻了&#xff0c;就是当时想要使用电脑阅读epub图书&#xff0c;也找了好些个在线epub阅读器&#xff0c;但总有一些不如意的地方&#xff0c;如某些功能需要会员之类的&#xff0c;突发临想的就想到自己开发一个&#xff0c;就此&#xff0c;一个…