• Github开源项目详解--Mall(一)


    前言

    跟着视频学了那么多技术,有没有自己尝试过做一个开源项目呢?

    下面让我们一步一步分析这个最火的前后端分离项目

    项目地址:

    https://github.com/YuyanCai/mall

    从0开始一个开源项目

    1. 看简介,知道项目是做什么的
    2. 看代码更新频率,几年没更新的最好别用
    3. 看README.md了解项目是否符合自己的技术栈
    4. 运行项目
      • 本地拉取代码
      • 看项目从整体到局部,先看项目架构 =》 看POM文件 =》看YML配置文件 =》看目录结构

    项目架构

    mall-admin-web

    mall-admin-web是一个电商后台管理系统的前端项目,基于Vue+Element实现。主要包括商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等功能。

    [外链图片转存中…(img-kDx7BIEi-1656677294008)]

    mall

    mall项目(50k+star)是一套电商系统,使用现阶段主流技术实现。涵盖了SpringBoot 2.3.0、MyBatis 3.4.6、Elasticsearch 7.6.2、RabbitMQ 3.7.15、Redis 5.0、MongoDB 4.2.5、Mysql5.7等技术,采用Docker容器化部署。

    [外链图片转存中…(img-ufkfKlgB-1656677294011)]

    从上面可以看出,这是一个前后端分离的项目

    前端项目为mall-admin-web

    后端项目为mall

    所用技术栈也比较符合我们Java工程师

    其中Mybatis不想用的话也可以用MP来代替

    ES和Mongodb没接触过的话,不用从头去学,了解下怎么使用即可~

    Github1s

    一个开源项目,能够直接在github页面通过vscode查看项目代码

    使用方法就是在项目地址中的github关键字后加上1s回车即可查看

    [外链图片转存中…(img-ZpMsKSo5-1656677294012)]

    前端代码架构

    前端看视频学的谷粒学院很相似,

    所以前端没啥大问题。

    [外链图片转存中…(img-bAG9VC0r-1656677294013)]

    后端代码架构

    mall
    ├── mall-common – 工具类及通用代码
    ├── mall-mbg – MyBatisGenerator生成的数据库操作代码
    ├── mall-security – SpringSecurity封装公用模块
    ├── mall-admin – 后台商城管理系统接口
    ├── mall-search – 基于Elasticsearch的商品搜索系统
    ├── mall-portal – 前台商城系统接口
    └── mall-demo – 框架搭建时的测试代码

    [外链图片转存中…(img-2GTDsRam-1656677294015)]

    简单了解下商品模块的功能

    这个商品列表,也就是CRUD中的查询,只不过人家查询的条目很多

    [外链图片转存中…(img-q1sJnoWk-1656677294016)]

    添加商品呢,也就是CRUD中的增

    [外链图片转存中…(img-a5MgcDkN-1656677294017)]

    商品回收站,也就是CRUD中的删

    [外链图片转存中…(img-xzOpv49T-1656677294017)]

    商品的配置就属于是优化部分了,比如打开一级菜单的时候显示属于一级菜单的二级菜单

    [外链图片转存中…(img-jMPN5B8j-1656677294018)]

    最后是一些附加项,如商品的品牌管理,库存管理,图片管理,分类管理等等的一些CRUD操作

    [外链图片转存中…(img-YdLrpeww-1656677294019)]

    总结

    实现功能如下,简单说就是针对商品的各种管理。如商品,类型,分类,品牌,订单…

    [外链图片转存中…(img-fqSHCpz2-1656677294022)]

    整体看下来,技术点是不难的。难点是细节比较多,业务逻辑可能稍微复杂,但是越难我们越要上!要对自己很有自信,一个一个的去攻破难点,这样我们才能不断的变强,与诸君共勉!

    后台架构

    mall
    ├── mall-common – 工具类及通用代码
    ├── mall-mbg – MyBatisGenerator生成的数据库操作代码
    ├── mall-security – SpringSecurity封装公用模块
    ├── mall-admin – 后台商城管理系统接口
    ├── mall-search – 基于Elasticsearch的商品搜索系统
    ├── mall-portal – 前台商城系统接口
    └── mall-demo – 框架搭建时的测试代码

    通过前面的学习我们知道了开发接口的套路

    1. 建表写sql
    2. 定义实体类
    3. dao与mapper
    4. service
    5. controller

    maven工程分析

    此项目采用maven来做包的依赖管理

    下面简单分析下maven的结构

    聚合

    项目采用maven的聚合和继承

    聚合是为了更快的构建项目,是表示项目与子项目之间的关系

    继承则是消除不同模块同种依赖节省了不必要配置

    mall这个项目,有管理商品项目、mbg项目,权限管理项目。这个时候在maven中表达这种归属关系,就可以用maven的聚合来表示,如下:

    <modules>
        <module>mall-common</module>
        <module>mall-admin</module>
        <module>mall-mbg</module>
    </modules>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    一般情况把子模块放到父模块下面,也可以在同一模块,只需要改变module的值即可

    <modules>
        <module>../mall-common</module>
        <module>../mall-admin</module>
        <module>../mall-mbg</module>
    </modules>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    继承

    spring-boot-starter-actuator可以用于检测系统的健康情况、当前的Beans、系统的缓存等

    spring-boot-starter-aop Spring Boot使用AOP

    项目的 dependencies 元素中声明该依赖,就会自动继承到子模块中

    其中spring-boot-starter-actuator、spring-boot-starter-aop…都可以自动继承到子模块

    [外链图片转存中…(img-aLxaODtX-1656677294023)]

    common模块

    此模块定义多个微服务模块公用的工具类,异常处理类,统一返回类等公共部分

    为了更好的理解,有一些前置知识需要在回顾一下:

    一、枚举

    ​ Java 枚举是一个特殊的类,一般表示一组常量,它是线程安全的,所以定义固定的常量一般把他们定义在枚举类里

    创建一个枚举类,经过编译后实际上会生成一个对应的抽象类,这个类继承了Java API中的java.lang.Enum类

    还为我们生成了两个静态方法,分别是values()和 valueOf()

    图中所举例子TEST1将会变成public static final R TEST1;

    [外链图片转存中…(img-y0ju3K0R-1656677294024)]

    项目中用到的是枚举的高级用法,向enum类添加方法与自定义属性和构造函数

    public enum ResultCode implements IErrorCode {
        SUCCESS(200, "操作成功"),
        FAILED(500, "操作失败"),
        VALIDATE_FAILED(404, "参数检验失败"),
        UNAUTHORIZED(401, "暂未登录或token已经过期"),
        FORBIDDEN(403, "没有相关权限");
    
        private long code;
        private String message;
    
        private ResultCode(long code, String message) {
            this.code = code;
            this.message = message;
        }
    
        public long getCode() {
            return code;
        }
    
        public String getMessage() {
            return message;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    二、泛型

    1.什么是泛型?

    泛型是程序语言的一种特性,指类型参数化

    2.为什么要有泛型?

    为了使代码更灵活,因为java是强类型语言(强类型语言是一种强制类型定义的语言,即一旦某一个变量被定义类型,如果不经强制转换,那么它永远就死该数据类型。),引入泛型后可以让部分代码可变,这部分代码在使用前必须声明。还有就是减少强制类型转换

    3.泛型方法

    泛型方法就是方法的返回值不是确定的类型,通过一个通配符来占位,等真正用到此方法的时候在指定返回值类型。

    4.泛型类

    同泛型方法

    在详细可看我之前发布的文章泛型篇

    强哥说Java–Java的泛型_小蜗牛耶的博客-CSDN博客_强哥说java

    pom

    这里redis先注释,等整合的时候再用

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>mall-study</artifactId>
            <groupId>com.caq.mall</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
    
        <modelVersion>4.0.0</modelVersion>
        <artifactId>mall-common</artifactId>
    
        <dependencies>
            <dependency>
                <groupId>com.github.pagehelper</groupId>
                <artifactId>pagehelper</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    <!--        <dependency>-->
    <!--            <groupId>org.springframework.boot</groupId>-->
    <!--            <artifactId>spring-boot-starter-data-redis</artifactId>-->
    <!--        </dependency>-->
            <dependency>
                <groupId>org.springframework.data</groupId>
                <artifactId>spring-data-commons</artifactId>
            </dependency>
            <dependency>
                <groupId>net.logstash.logback</groupId>
                <artifactId>logstash-logback-encoder</artifactId>
            </dependency>
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger2</artifactId>
            </dependency>
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger-ui</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-validation</artifactId>
            </dependency>
        </dependencies>
    
    </project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    统一返回类

    将后端处理好的数据以同一的格式返回给前端

    格式固定,可以根据项目需求更改

    这个项目写的这套返回类是很标准的,工作了我们也可以拿这个来写

    /**
     * 通用返回对象
     */
    public class CommonResult<T> {
        /**
         * 状态码
         */
        private long code;
        /**
         * 提示信息
         */
        private String message;
        /**
         * 数据封装
         */
        private T data;
    
        protected CommonResult() {
        }
    
        protected CommonResult(long code, String message, T data) {
            this.code = code;
            this.message = message;
            this.data = data;
        }
    
        /**
         * 成功返回结果
         *
         * @param data 获取的数据
         */
        public static <T> CommonResult<T> success(T data) {
            return new CommonResult<T>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
        }
    
        /**
         * 成功返回结果
         *
         * @param data 获取的数据
         * @param  message 提示信息
         */
        public static <T> CommonResult<T> success(T data, String message) {
            return new CommonResult<T>(ResultCode.SUCCESS.getCode(), message, data);
        }
    
        /**
         * 失败返回结果
         * @param errorCode 错误码
         */
        public static <T> CommonResult<T> failed(IErrorCode errorCode) {
            return new CommonResult<T>(errorCode.getCode(), errorCode.getMessage(), null);
        }
    
        /**
         * 失败返回结果
         * @param errorCode 错误码
         * @param message 错误信息
         */
        public static <T> CommonResult<T> failed(IErrorCode errorCode,String message) {
            return new CommonResult<T>(errorCode.getCode(), message, null);
        }
    
        /**
         * 失败返回结果
         * @param message 提示信息
         */
        public static <T> CommonResult<T> failed(String message) {
            return new CommonResult<T>(ResultCode.FAILED.getCode(), message, null);
        }
    
        /**
         * 失败返回结果
         */
        public static <T> CommonResult<T> failed() {
            return failed(ResultCode.FAILED);
        }
    
        /**
         * 参数验证失败返回结果
         */
        public static <T> CommonResult<T> validateFailed() {
            return failed(ResultCode.VALIDATE_FAILED);
        }
    
        /**
         * 参数验证失败返回结果
         * @param message 提示信息
         */
        public static <T> CommonResult<T> validateFailed(String message) {
            return new CommonResult<T>(ResultCode.VALIDATE_FAILED.getCode(), message, null);
        }
    
        /**
         * 未登录返回结果
         */
        public static <T> CommonResult<T> unauthorized(T data) {
            return new CommonResult<T>(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), data);
        }
    
        /**
         * 未授权返回结果
         */
        public static <T> CommonResult<T> forbidden(T data) {
            return new CommonResult<T>(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage(), data);
        }
    
        public long getCode() {
            return code;
        }
    
        public void setCode(long code) {
            this.code = code;
        }
    
        public String getMessage() {
            return message;
        }
    
        public void setMessage(String message) {
            this.message = message;
        }
    
        public T getData() {
            return data;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130

    分页数据封装类

    package com.caq.mall.common.api;
    
    import com.github.pagehelper.PageInfo;
    import org.springframework.data.domain.Page;
    
    import java.util.List;
    
    /**
     * 分页数据封装类
     */
    public class CommonPage<T> {
        /**
         * 当前页码
         */
        private Integer pageNum;
        /**
         * 每页数量
         */
        private Integer pageSize;
        /**
         * 总页数
         */
        private Integer totalPage;
        /**
         * 总条数
         */
        private Long total;
        /**
         * 分页数据
         */
        private List<T> list;
    
        /**
         * 将PageHelper分页后的list转为分页信息
         */
        public static <T> CommonPage<T> restPage(List<T> list) {
            CommonPage<T> result = new CommonPage<T>();
            PageInfo<T> pageInfo = new PageInfo<T>(list);
            result.setTotalPage(pageInfo.getPages());
            result.setPageNum(pageInfo.getPageNum());
            result.setPageSize(pageInfo.getPageSize());
            result.setTotal(pageInfo.getTotal());
            result.setList(pageInfo.getList());
            return result;
        }
    
        /**
         * 将SpringData分页后的list转为分页信息
         */
        public static <T> CommonPage<T> restPage(Page<T> pageInfo) {
            CommonPage<T> result = new CommonPage<T>();
            result.setTotalPage(pageInfo.getTotalPages());
            result.setPageNum(pageInfo.getNumber());
            result.setPageSize(pageInfo.getSize());
            result.setTotal(pageInfo.getTotalElements());
            result.setList(pageInfo.getContent());
            return result;
        }
    
        public Integer getPageNum() {
            return pageNum;
        }
    
        public void setPageNum(Integer pageNum) {
            this.pageNum = pageNum;
        }
    
        public Integer getPageSize() {
            return pageSize;
        }
    
        public void setPageSize(Integer pageSize) {
            this.pageSize = pageSize;
        }
    
        public Integer getTotalPage() {
            return totalPage;
        }
    
        public void setTotalPage(Integer totalPage) {
            this.totalPage = totalPage;
        }
    
        public List<T> getList() {
            return list;
        }
    
        public void setList(List<T> list) {
            this.list = list;
        }
    
        public Long getTotal() {
            return total;
        }
    
        public void setTotal(Long total) {
            this.total = total;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99

    异常处理

    分别定义自定义异常、全局异常、断言

    断言的作用是简化方法入参检测的代码

    不使用断言的情况下我们要这样写:

    public InputStream getData(String file) { 
        if (file == null || file.length() == 0|| file.replaceAll("\\s", "").length() == 0) {
            throw new IllegalArgumentException("file入参不是有效的文件地址"); 
        } 
    
    • 1
    • 2
    • 3
    • 4

    在应用 Assert 断言类后,其代码可以简化为以下的形式:

    public InputStream getData(String file){ 
        Assert.hasText(file,"file入参不是有效的文件地址"); 
    
    • 1
    • 2

    自定义异常类:ApiException

    package com.macro.mall.common.exception;
    
    import com.macro.mall.common.api.IErrorCode;
    
    /**
     * 自定义API异常
     */
    public class ApiException extends RuntimeException {
        private IErrorCode errorCode;
    
        public ApiException(IErrorCode errorCode) {
            super(errorCode.getMessage());
            this.errorCode = errorCode;
        }
    
        public ApiException(String message) {
            super(message);
        }
    
        public ApiException(Throwable cause) {
            super(cause);
        }
    
        public ApiException(String message, Throwable cause) {
            super(message, cause);
        }
    
        public IErrorCode getErrorCode() {
            return errorCode;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    断言类:Asserts

    /**
     * 断言处理类,用于抛出各种API异常
     */
    public class Asserts {
        public static void fail(String message) {
            throw new ApiException(message);
        }
    
        public static void fail(IErrorCode errorCode) {
            throw new ApiException(errorCode);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    全局异常处理类:GlobalExceptionHandler

    /**
     * 全局异常处理
     */
    @ControllerAdvice
    public class GlobalExceptionHandler {
    
        @ResponseBody
        @ExceptionHandler(value = ApiException.class)
        public CommonResult handle(ApiException e) {
            if (e.getErrorCode() != null) {
                return CommonResult.failed(e.getErrorCode());
            }
            return CommonResult.failed(e.getMessage());
        }
    
        @ResponseBody
        @ExceptionHandler(value = MethodArgumentNotValidException.class)
        public CommonResult handleValidException(MethodArgumentNotValidException e) {
            BindingResult bindingResult = e.getBindingResult();
            String message = null;
            if (bindingResult.hasErrors()) {
                FieldError fieldError = bindingResult.getFieldError();
                if (fieldError != null) {
                    message = fieldError.getField()+fieldError.getDefaultMessage();
                }
            }
            return CommonResult.validateFailed(message);
        }
    
        @ResponseBody
        @ExceptionHandler(value = BindException.class)
        public CommonResult handleValidException(BindException e) {
            BindingResult bindingResult = e.getBindingResult();
            String message = null;
            if (bindingResult.hasErrors()) {
                FieldError fieldError = bindingResult.getFieldError();
                if (fieldError != null) {
                    message = fieldError.getField()+fieldError.getDefaultMessage();
                }
            }
            return CommonResult.validateFailed(message);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    Swagger

    以下是固定写法

    /**
     * Swagger基础配置
     */
    public abstract class BaseSwaggerConfig {
    
        @Bean
        public Docket createRestApi() {
            SwaggerProperties swaggerProperties = swaggerProperties();
            Docket docket = new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo(swaggerProperties))
                .select()
                .apis(RequestHandlerSelectors.basePackage(swaggerProperties.getApiBasePackage()))
                .paths(PathSelectors.any())
                .build();
            //        if (swaggerProperties.isEnableSecurity()) {
            //            docket.securitySchemes(securitySchemes()).securityContexts(securityContexts());
            //        }
            return docket;
        }
    
        private ApiInfo apiInfo(SwaggerProperties swaggerProperties) {
            return new ApiInfoBuilder()
                .title(swaggerProperties.getTitle())
                .description(swaggerProperties.getDescription())
                .contact(new Contact(swaggerProperties.getContactName(), swaggerProperties.getContactUrl(), swaggerProperties.getContactEmail()))
                .version(swaggerProperties.getVersion())
                .build();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    Swagger自定义配置

    /**
     * Swagger自定义配置
     */
    @Data
    @EqualsAndHashCode(callSuper = false)
    @Builder
    public class SwaggerProperties {
        /**
         * API文档生成基础路径
         */
        private String apiBasePackage;
        /**
         * 是否要启用登录认证
         */
        private boolean enableSecurity;
        /**
         * 文档标题
         */
        private String title;
        /**
         * 文档描述
         */
        private String description;
        /**
         * 文档版本
         */
        private String version;
        /**
         * 文档联系人姓名
         */
        private String contactName;
        /**
         * 文档联系人网址
         */
        private String contactUrl;
        /**
         * 文档联系人邮箱
         */
        private String contactEmail;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
  • 相关阅读:
    centos 7的超详细安装教程
    labview数组精讲
    读书笔记_小米创业思考
    .NET性能优化-你应该为集合类型设置初始大小
    从局部变量说起,关于一个莫得名堂的引用和一个坑!
    JAVA实训项目之学生管理系统(JDBC+GUI)
    一,DRF入门规范
    JavaScript 浏览器元素滚动 scrollIntoView()
    浅析SR隧道路径批量构造方法
    OpenCV实战——提取视频中的前景对象
  • 原文地址:https://blog.csdn.net/qq_45714272/article/details/125565042