我人生中的第一个需求——Excel数据批量上传到数据库

受光于隙见一床,受光于牗见室央,受光于庭户见一堂,受光于天下照四方。——魏源

一、写在前面

(由于考虑到公司业务和代码安全性问题,所有内容已脱敏)

二、问题场景

最近,在实习中遇到如下场景:(某个项目的一个分支,我也是刚接触,所以架构不是很懂)

2.1 功能简述

一个地区的核酸预约系统,现在有PC端和客户端。

  • PC端:超级管理员,也就是可以理解为每个小区的物业或者政府人员登录的端口。登录方式为 Session

    PC端只有查询数据功能,即根据某个字段筛选。

  • 客户端:客户端的使用群体只有普通用户和做核酸检查的医护人员。登录方式为 Token

    客户端有两个功能:

    1. 普通用户可以预约做核酸,医务人员根据用户提交的时间判断自己能不能去给他做核酸(可以通过也可以拒绝)。
    2. 医务人员可以根据你的行程码判断你需要做核酸,然后直接邀约一个时间段去给你做核酸(系统会短信通知你,为了自己的安全,你只能接收)

    上门做核酸,所以是一对一预约关系。

2.2 基本表信息

用户预约表:

字段信息字段类型其他约束
用户电话VARCHAR主键,不能为空
用户姓名VARCHAR不能为空
开始预约时间VARCHAR不能为空
结束预约时间VARCHAR不能为空
用户健康码VARCHAR不能为空
用户行程码截图VARCHAR不能为空
审批状态INT不能为空

医务人员预约表:

字段信息字段类型其他约束
医生电话VARCHAR主键,不能为空
医生姓名VARCHAR不能为空
所属医院VARCHAR不能为空
用户健康码VARCHAR不能为空
用户行程码VARCHAR不能为空

PC端展示效果:

用户电话用户姓名医生姓名医生电话医生所属医院开始预约时间结束预约时间审批状态用户健康码用户行程码
123456张三大白11201长安医院2020-06-09 10:002020-06-09 11:00待审批/check_img/001.jpg/tour_img/001.jpg
654321李四大白21202和平医院2020-06-09 08:002020-06-09 09:00已通过/check_img/002.jpg/tour_img/002.jpg

2.3 需求简述

现在需求是给PC端增加一个核酸批量预约功能,可以通过上传 Excel 文件批量预约核酸。

例如一个小区变为封控区后,小区物业会将所有用户提交到数据库,即给所有人预约核酸检测。

Excel 表模板如下:(前面的一列是)

1用户电话用户姓名开始预约时间结束预约时间用户健康码用户行程码
2123456张三2020-06-09 10:002020-06-09 11:00/check_img/001.jpg/tour_img/001.jpg
3654321李四2020-06-09 08:002020-06-09 09:00/check_img/002.jpg/tour_img/002.jpg

2.4 初步方案

因为考虑到后端解析 Excel 用的是工具类(工具类不可能调用 Service 层上传文件的方法),然后如果图片都放到 Excel 中这个数据可能有很多行,所以可能会有很多图片,一次性占用的带宽较大。

所以我们做了如下调整:

前端将用户上传的 Excel 传给后端,后端做图片以外数据的解析,解析后返回 json 给前端。前端将数据展示给PC端管理员,管理员给前端提供一个上传图片的按键,每次对应上传一个图片,将这个图片调用后端接口上传,将url填入 json 返回给后端。

然后我就开始快乐的写我的 ExcelUtil 了。

2.5 问题分析

好景不长,当我写完工具类,写好 Excel 校验以及转 json 的方法后,我该写解析 json 了,这时候才发现不对:

如果按照样表的数据插入,不知道他预约的医生是谁。

所以就查看了医生预约用户的方法,方法中有医生的参数都是从 request 中的 Token 拿取的,而 Token 中的信息是拦截器添加到 Token 中的。

于是乎我也就傻傻的从 PC 端的 request 中取医生信息。结果取不到信息,每次都插入失败。

后来才发现 PC 端和客户端的登录方式不一样,根本就不再一个系统。而且我这时候才意识到这个是管理员,和客户端可能的两张表的。

2.6 解决方案

于是乎,和产品开会,最终决议给 Excel 中增加一条字段,即医护人员的电话。

因为电话是主键,所以就可以查询到医护人员的信息了。

Excel 表模板如下:

序号医生电话用户电话用户姓名用户健康码用户行程码
1666666123456张三/check_img/001.jpg/tour_img/001.jpg
2888888654321李四/check_img/002.jpg/tour_img/002.jpg

2.7 其他问题

但是到最后,我发现这个 json 的格式不好解析。

我之前是打算让前端传个 Excel 文件,然后继续解析 Excel 文件为 set 集合,然后遍历插入数据库。

{
    "code": 200,
    "message": null,
    "data": {1”: {
        	“医生电话”: "66666666",
        	“用户电话”: "张三",
        	“用户健康码”: "1234567989",
        	“用户行程码”: "/abc.jpg"
    	},2”: {
        	“医生电话”: "8888888",
        	“用户电话”: "张三",
        	“用户健康码”: "1234567989",
        	“用户行程码”: "/abc.jpg"
    	},
		....50”: {
        	“医生电话”: "...",
        	“用户电话”: "张三",
        	“用户健康码”: "1234567989",
        	“用户行程码”: "/abc.jpg"
    	}
    }
}

因为封装太严重,导致直接解析需要Map<String,<Map<String,String>>>,也就是Map<“1”,<Map<“字段”,“字符串”>>>的形式,然后一层层解析。

昨天在咨询 背影 好(da)友(lao)这个业务场景,他给出了他的建议:封装一个新的实体类,继承以前的实体类多加一个字段即可。然后封装为 Data 集合,最后 Controller 层进行接收。

三、原本代码逻辑

3.1 controller层

/**
 * 社区预约
 */
@RestController
@RequestMapping("/community")
@Validated
public class CommunityReserveController {

    @Resource
    private CommunityService communityService;
    
    @RequestMapping(value = "/analyzeExcel")
    public BaseResponse analyzeExcel(MultipartHttpServletRequest multiparthttpservletrequest) {
        return communityService.analyzeExcel(multiparthttpservletrequest);
    }
    
    @RequestMapping(value = "/createReserve")
    public BaseResponse createReserve(MultipartHttpServletRequest multiparthttpservletrequest) {
        return communityService.createReserve(multiparthttpservletrequest);
    }
    
}

3.2 service 层

接口

/**
 * 社区预约接口
 */
public interface CommunityService {

    /**
    * 解析并校验 Excel 到 Json
    */
    BaseResponse communityService(MultipartHttpServletRequest multiparthttpservletrequest);
    
    /**
    * 将 Excel 信息插入到数据库
    */
    BaseResponse createReserve(MultipartHttpServletRequest multiparthttpservletrequest);
    
    
}

接口实现类

/**
 * 社区预约实现类
 */
@Service
public class CommunityServiceImpl implements CommunityService {

    @Override
    JSONObject communityService(MultipartHttpServletRequest multiparthttpservletrequest) {
        JSONObject infos = new JSONObject();
        // 解析文件逻辑略
        return ResultUtils.success(infos);
    }
    
    @Override
    JSONObject communityService(MultipartHttpServletRequest multiparthttpservletrequest) {
        JSONObject infos = new JSONObject();
        // 解析文件逻辑略
        // 插入数据逻辑略
        return ResultUtils.success(infos);
    }
    
}

3.3 dao层

实体类代码如下:

import lombok.Data;

import javax.validation.constraints.NotNull;


/**
 * 医生创建用户预约 
 */
@Data
public class DoctoReserveReq implements Serializable {

    /**
     * 用户电话
     */
    @NotNull(message = "用户电话 不能为空")
    private String phone;
    
    /**
     * 用户姓名
     */
    @NotNull(message = "用户姓名 不能为空")
    private String username;
    
    /**
     * 开始预约时间
     */
    @NotNull(message = "开始预约时间 不能为空")
    private String start_time;
    
    /**
     * 结束预约时间
     */
    @NotNull(message = "结束预约时间 不能为空")
    private String end_time;
    
    /**
     * 用户健康码截图
     */
    @NotNull(message = "用户健康码截图 不能为空")
    private String health_img;
    
    /**
     * 用户行程码截图
     */
    @NotNull(message = "用户行程码截图 不能为空")
    private String travel_img;

}

3.4 common层

通用返回类:

import lombok.Data;

import java.io.Serializable;

/**
 * 通用返回类
 *
 * @param <T>
 */
@Data
public class BaseResponse<T> implements Serializable {

    private int code;

    private T data;

    private String message;

    private String description;

    public BaseResponse(int code, T data, String message, String description) {
        this.code = code;
        this.data = data;
        this.message = message;
        this.description = description;
    }

    public BaseResponse(int code, T data, String message) {
        this(code, data, message, "");
    }

    public BaseResponse(int code, String message, String description) {
        this(code, null, message, description);
    }

    public BaseResponse(int code, T data) {
        this(code, data, "", "");
    }

    public BaseResponse(ErrorCode errorCode) {
        this(errorCode.getCode(), null, errorCode.getMessage(), errorCode.getDescription());
    }


}

返回工具类:

/**
 * 返回工具类
 */
public class ResultUtils {

    /**
     * 成功
     *
     * @param data
     * @param <T>
     * @return
     */
    public static <T> BaseResponse<T> success(T data) {
        return new BaseResponse<>(0, data, "ok");
    }

    /**
     * 失败
     *
     * @param errorCode
     * @param <T>
     * @return
     */
    public static <T> BaseResponse<T> error(ErrorCode errorCode) {
        return new BaseResponse<>(errorCode);
    }

    /**
     * 失败
     *
     * @param code
     * @param message
     * @param description
     * @param <T>
     * @return
     */
    public static <T> BaseResponse<T> error(int code, String message, String description) {
        return new BaseResponse<T>(code, message, description);
    }

    /**
     * 失败
     *
     * @param errorCode
     * @param <T>
     * @return
     */
    public static <T> BaseResponse<T> error(ErrorCode errorCode, String message, String description) {
        return new BaseResponse<T>(errorCode.getCode(), message, description);
    }

    /**
     * 失败
     *
     * @param errorCode
     * @param <T>
     * @return
     */
    public static <T> BaseResponse<T> error(ErrorCode errorCode, String description) {
        return new BaseResponse<T>(errorCode.getCode(), errorCode.getMessage(), description);
    }


}

错误码枚举类:

/**
 * 错误码
 */
public enum ErrorCode {

    SUCCESS(0, "ok", ""),
    PARAM_ERROR(40000, "请求参数错误", ""),
    NULL_ERROR(40001, "请求数据为空", ""),
    NO_LOGIN(40100, "未登录", ""),
    NO_AUTH(40101, "无权限", ""),
    SYSTEM_ERROR(50000, "系统内部异常", "");

    private final int code;
    /**
     * 状态码信息
     */
    private final String message;
    /**
     * 状态码描述()详细
     */
    private final String description;

    ErrorCode(int code, String message, String description) {
        this.code = code;
        this.message = message;
        this.description = description;
    }

    public int getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

    public String getDescription() {
        return description;
    }
}

四、新增代码逻辑

和前端沟通传过来的 Json 格式 :

[
    {
        “医生电话”: "66666666",
        “用户电话”: "张三",
        “用户健康码”: "1234567989",
        “用户行程码”: "/abc.jpg"
    },
    {
        “医生电话”: "8888888",
        “用户电话”: "张三",
        “用户健康码”: "1234567989",
        “用户行程码”: "/abc.jpg"
    },
    ...
    {
        “医生电话”: "...",
        “用户电话”: "张三",
        “用户健康码”: "1234567989",
        “用户行程码”: "/abc.jpg"
    }
]

4.1 controller层

/**
 * 社区预约
 */
@RestController
@RequestMapping("/community")
@Validated
public class CommunityReserveController {

    @Resource
    private CommunityService communityService;
    
    @RequestMapping(value = "/analyzeExcel")
    public BaseResponse analyzeExcel(MultipartHttpServletRequest multiparthttpservletrequest) {
        return communityService.analyzeExcel(multiparthttpservletrequest);
    }
    
    @RequestMapping(value = "/createReserve")
    public BaseResponse createReserve(@Validated @RequestBody ArrayList<DoctoReserveReq> list) {
        return communityService.createReserve(list);
    }
   
}

4.2 service 层

接口

/**
 * 社区预约接口
 */
public interface CommunityService {

    /**
    * 解析并校验 Excel 到 Json
    */
    BaseResponse communityService(MultipartHttpServletRequest multiparthttpservletrequest);
    
    /**
    * 将 Excel 信息插入到数据库
    */
    BaseResponse createReserve(ArrayList<DoctoReserveReq> list);
    
    
}

接口实现类

/**
 * 社区预约实现类
 */
@Service
public class CommunityServiceImpl implements CommunityService {

    @Override
    JSONObject communityService(MultipartHttpServletRequest multiparthttpservletrequest) {
        JSONObject infos = new JSONObject();
        // 解析文件逻辑略
        return ResultUtils.success(infos);
    }
    
    @Override
    JSONObject communityService(ArrayList<DoctoReserveReq> list) {
        JSONObject infos = new JSONObject();
        // 遍历 list 逻辑 校验数据逻辑略
        // 遍历 list 逻辑 插入数据逻辑略
        return ResultUtils.success(infos);
    }
    
}

4.3 dao层

根据需求新建实体类:

import lombok.Data;

import javax.validation.constraints.NotNull;

/**
 * 社区预约
 */
@Data
public class CommunityReserveReq extends DoctoReserveReq {
    /**
     * 医生电话
     */
    @NotNull(message = "医生电话 不能为空")
    private String doctor_phone;

}

五、总结反思

  1. 自己对 Json 的不够熟悉,只是停留在简单的了解和写入,对解析过程不够熟练
  2. 对接收前端传过来的信息的接收不够了解,对 @Validated@RequestBody 注解的理解和应用较少
  3. coding 前对整个框架的运行逻辑不够熟悉,导致 coding 过程出现了发现PC端没Session等问题而影响效率
  4. 沉迷于造轮子,没有和前端沟通好传过来的数据的类型,导致浪费了部分时间

六、写在后面

欢迎关注,实现期间会经常发一些工作中遇到的问题。

欢迎随时留言讨论,知无不答!

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Python是一种强大的编程语言,可以轻松地将Excel数据数据按照自定义的规则批量导入到数据库。实现这个功能需要使用Python的开源库——pandas、xlrd和sqlalchemy。 首先,我们需要安装这些库。在Python环境使用pip命令进行安装。 接下来,我们需要导入这些库,并使用pandas读取Excel数据数据。使用read_excel方法读取Excel文件,并将数据存储到DataFrame对象。 然后,我们需要使用SQLAlchemy创建数据库连接,并创建需要将数据导入的数据表。这可以通过使用create_engine函数创建SQLAlchemy Engine对象,并使用pandas提供的to_sql方法将数据写入数据库。 最后,我们需要在代码实现逻辑来确定如何读取Excel数据数据,以及如何将其映射到数据库的列。这可以通过使用pandas的DataFrame和SQLAlchemy的Table对象来实现。 总的来说,使用Python将Excel数据批量导入到数据库是一个很容易的任务,只需要使用一些简单的函数和库,就可以完成这个任务。这个过程可以帮助用户快速地导入数据,从而简化其重复性工作。 ### 回答2: Python是一种经常用于数据处理和分析的编程语言,它提供了许多现成的库和模块来方便地将不同格式的数据导入数据库。而在导入数据时,Excel数据表通常是一种常见的格式,这种格式的数据可以通过Python来批量导入到数据库。 为了实现这一目的,需要使用Python的pandas库来读取Excel数据表,并将其转换为DataFrame格式,然后使用pandas库的to_sql()方法将DataFrame数据批量插入到数据库。要执行此操作,需要安装和配置相应的数据库连接器,如MySQLdb或psycopg2等。 首先,需要通过Python的pandas库创建一个DataFrame对象来加载Excel数据表。可以使用read_excel()方法读取Excel文件数据,并将其转换为DataFrame格式。必要时,可以通过pandas库的方法来处理数据,例如清除或填充缺失值,并对数据进行格式转换。 然后,可以使用pandas库的to_sql()方法创建一个数据库连接,并将DataFrame数据批量插入到数据库。需要提供有关数据库连接的详细信息,例如主机名称、端口、数据库名称、用户名和密码等。此外,还需要指定要将数据插入的表名和数据列名。 这样,Excel数据表的数据就可以通过使用Python和pandas库批量导入到数据库。这个过程可以自动化,并在每次需要更新数据库数据时使用。这可以大大减少手动处理数据的工作量,并提高数据的处理效率和准确性。 ### 回答3: 在进行数据处理过程,Python的excel模块可以很好地对Excel数据表进行读取和处理。通过使用Python的pandas模块,我们可以轻松地将Excel数据数据批量导入到数据库。 首先,我们需要使用pandas读取Excel文件,并将数据存储在一个pandas的dataframe对象。然后,我们可以使用Python的SQLAlchemy模块来连接我们的数据库,并在其创建一个表格。接下来,我们将使用dataframe对象数据将其插入到数据库。 在这个过程,我们可以使用一些技巧将数据转换为数据库需要的格式,例如使用pandas的DatetimeIndex来将日期时间格式化为数据库正确的格式。 总体来说,使用Python将Excel数据数据批量导入数据库是非常简单和高效的。这个过程可以大大提高数据处理的效率,并支持更好的数据分析和可视化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值