前言
相信很多人同我一样,喜欢给自己制定一些TODO List,以及一些打卡任务,例如我给自己制定了这么一个打卡任务:记单词100个,目标打卡天数为100天,任务周期为2023年这一整年。这时候就需要有一个工具来帮我们记录打卡任务的完成进度了。我相信市面上肯定有很多这种小工具,但自己亲手实现一个,会更加的灵活,且意义非凡。
JUST DO IT。?
每日打卡
先来展示一下成果吧,下方是每日打卡APP内的几张截图。
每日任务 | 添加计划 | 每日任务展示 |
---|---|---|
![]() |
![]() |
![]() |
年度计划 | 当日计划展示 | 计划总展示 |
---|---|---|
![]() |
![]() |
![]() |
一开始,我只想做一个本地应用,在APP内搭建数据库来存储数据,心想这样更加的安全(主要我不会后端技术?)。但随着我个人的实际使用体验下来,不联网的弊端十分明显,哪天要是不小心将APP删除了,那么你的所有打卡数据就都无了。在我将每日打卡APP误删除后,痛定思痛,决定研究一下后端开发。
所以本篇文章,我主要想分享一下我的后端生涯的第一行”HELLO WORLD”
代码。
上手实践
我采用的是 Spring Boot + MySql + MyBatis
技术栈,先来看看项目结构?。
需求分析
对于客户端来说,我们有添加任务、修改任务、删除任务以及UI交互上的这些需求,但站在后端角度,这就是一个很简单的增删改查操作,只需要处理一下数据,然后输出几个接口供客户端调用即可。
创建表
表的内容要包含:唯一标识id、任务名称、打卡目标天数、打卡进度、创建日期、结束日期、是否完成打卡任务。
确定好内容以后,我们就可以编写创建表的Sql语句了。
CREATE TABLE `year_task` (
`id` bigint NOT NULL AUTO_INCREMENT,
`task_name` varchar(100) NOT NULL,
`target` int NOT NULL,
`progress` int NOT NULL,
`create_date` date DEFAULT NULL,
`finish_date` date DEFAULT NULL,
`is_finish` tinyint DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8mb3
创建完成后,我们使用 desc year_task;
语句来展示一下 year_task
数据库表结构。
mysql> desc year_task;
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| id | bigint | NO | PRI | NULL | auto_increment |
| task_name | varchar(100) | NO | | NULL | |
| target | int | NO | | NULL | |
| progress | int | NO | | NULL | |
| create_date | date | YES | | NULL | |
| finish_date | date | YES | | NULL | |
| is_finish | tinyint | YES | | 0 | |
+-------------+--------------+------+-----+---------+----------------+
7 rows in set (0.00 sec)
OK,表创建好了,接下来就是在项目中定义一个对应的Bean对象YearTaskBean
。
public class YearTaskBean {
private Long id;
@NotBlank(message = "taskName is not blank")
private String taskName;
@NotNull(message = "target not null")
private Integer target;
@NotNull(message = "progress not null")
private Integer progress;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date createDate;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date finishDate;
private Boolean isFinish;
}
字段说明:
id
:唯一标识。taskName
:任务名称。如:练习篮球。target
:打卡的目标天数。如:60,标识打卡的目标天数为60天。progress
:打卡进度。如:15,标识打卡了15天。createDate
:创建日期。如:2023-06-12。finishDate
:结束日期。如:2023-09-19isFinish
:表示任务是否结束。
所有的代码下载地址会在下方给出,这里我只展示核心代码。
Service
我们先定义一个 YearTaskService
接口。
public interface YearTaskService {
/**
* 获取所有的任务
*
* @return
*/
List<YearTaskBean> getAllYearTasks();
/**
* 通过ID获取指定的任务
*
* @param id
* @return
*/
YearTaskBean getYearTaskById(Long id);
/**
* 通过打卡任务名来获取指定的任务列表
*
* @param taskName
* @return
*/
List<YearTaskBean> getYearTaskByName(String taskName);
/**
* 通过指定的条件来获取相对应的任务
*
* @param conditionMap
* @return
*/
YearTaskBean getYearTaskByMap(Map<String, Object> conditionMap);
/**
* 添加任务
*
* @param yearTaskBean 有客户端请求参数反序列化而来
*/
void insertYearTask(YearTaskBean yearTaskBean);
/**
* 根据给定的条件插入Task。
*
* @param insertConditionMap 例如:[taskName=百词斩记单词, target=21]
*/
void insertYearTaskByMap(Map<String, Object> insertConditionMap);
/**
* 更新任务
*
* @param yearTaskBean
*/
void updateYearTask(YearTaskBean yearTaskBean);
/**
* 通过ID删除指定任务
*
* @param id
*/
void deleteYearTaskById(Long id);
}
然后先建一个YearTaskServiceImpl
类来具体实现该接口。
@Service
public class YearTaskServiceImpl implements YearTaskService {
Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private YearTaskMapper yearTaskMapper;
@Override
public List<YearTaskBean> getAllYearTasks() {
return yearTaskMapper.getAllYearTasks();
}
@Override
public YearTaskBean getYearTaskById(Long id) {
return yearTaskMapper.getYearTaskById(id);
}
@Override
public List<YearTaskBean> getYearTaskByName(String taskName) {
return yearTaskMapper.getYearTaskByName(taskName);
}
@Override
public YearTaskBean getYearTaskByMap(Map<String, Object> conditionMap) {
return yearTaskMapper.getYearTaskByMap(conditionMap);
}
@Override
public void insertYearTask(YearTaskBean yearTaskBean) {
yearTaskMapper.insertYearTask(yearTaskBean);
}
@Override
public void insertYearTaskByMap(Map<String, Object> insertConditionMap) {
int target = Integer.parseInt(insertConditionMap.get("target").toString());
int progress = 0;
if (insertConditionMap.get("progress") != null) {
progress = Integer.parseInt(insertConditionMap.get("progress").toString());
}
boolean isFinish = false;
Date finishDate = null;
if (progress >= target) {
isFinish = true;
finishDate = new Date();
}
YearTaskBean yearTaskBean = new YearTaskBean(null,
insertConditionMap.get("taskName").toString(),
target,
progress,
new Date(),
finishDate,
isFinish);
yearTaskMapper.insertYearTask(yearTaskBean);
}
@Override
public void updateYearTask(YearTaskBean yearTaskBean) {
yearTaskMapper.updateYearTask(yearTaskBean);
}
@Override
public void deleteYearTaskById(Long id) {
yearTaskMapper.deleteYearTaskById(id);
}
}
在YearTaskServiceImpl
类中可以看到,其方法里面是都是调用YearTaskMapper
里面的方法。而这个Mapper
正是与数据库的连接桥,我们需要定义相对应的SQL语句在其中。
Mapper
先先建一个YearTaskMapper
接口定义好所有的方法。
public interface YearTaskMapper {
/**
* 查询所有 year task
*
* @return
*/
List<YearTaskBean> getAllYearTasks();
/**
* 通过指定id来查询 year task
*
* @param id
* @return
*/
YearTaskBean getYearTaskById(Long id);
/**
* 通过指定taskName来查询 year task
*
* @param taskName
* @return
*/
List<YearTaskBean> getYearTaskByName(String taskName);
/**
* 根据指定条件来查询 year task,例如:使用 taskName 与 createDate 来查询
*
* @param conditionMap
* @return
*/
YearTaskBean getYearTaskByMap(Map<String, Object> conditionMap);
/**
* 插入指定 year task
*
* @param yearTaskBean
*/
void insertYearTask(YearTaskBean yearTaskBean);
/**
* 更新 year task
*
* @param yearTaskBean
*/
void updateYearTask(YearTaskBean yearTaskBean);
/**
* 通过 id 来删除指定 year task
*
* @param id
*/
void deleteYearTaskById(Long id);
}
然后创建一个YearTaskMapper.xml
文件来定义具体的SQL语句。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.day_record.server.mapper.YearTaskMapper">
<select id="getAllYearTasks" resultType="com.day_record.server.bean.YearTaskBean">
SELECT * FROM year_task
</select>
<select id="getYearTaskById" resultType="com.day_record.server.bean.YearTaskBean">
SELECT * FROM year_task WHERE id=#{id}
</select>
<select id="getYearTaskByName" resultType="com.day_record.server.bean.YearTaskBean">
SELECT * FROM year_task WHERE task_name=#{taskName}
</select>
<select id="getYearTaskByMap" parameterType="java.util.Map" resultType="com.day_record.server.bean.YearTaskBean">
SELECT * FROM year_task WHERE task_name=#{taskName} AND create_date=#{createDate}
</select>
<insert id="insertYearTask" parameterType="com.day_record.server.bean.YearTaskBean">
insert into year_task (id, task_name, target, progress, create_date, finish_date, is_finish)
value (#{id}, #{taskName}, #{target}, #{progress}, #{createDate}, #{finishDate}, #{isFinish})
</insert>
<update id="updateYearTask">
update year_task set task_name=#{taskName}, target=#{target}, progress=#{progress}, create_date=#{createDate}, finish_date=#{finishDate}, is_finish=#{isFinish} where id=#{id}
</update>
<delete id="deleteYearTaskById">
delete from year_task where id=#{id}
</delete>
</mapper>
写好了SQL语句,拿到了数据,接下来就是定义对外的API了,供客户端使用。
Controller
一般会以Controller为后缀来命名,如:TaskController
。在该类中,我们会通过@Autowired
注解注入YearTaskService
,然后调用其方法。
以获取所有任务来举例:
@GetMapping("/year/get_all_year_tasks")
public BaseBean<List<YearTaskBean>> getAllYearTasks() {
List<YearTaskBean> allYearTasks = yearTaskService.getAllYearTasks();
return new BaseBean<>(200, allYearTasks, "success");
}
我们运行代码,然后再Postman中进行调试接口。
再来一个POST
请求,以插入任务来举例:
@PostMapping("/year/insert_year_task")
public BaseBean<String> insertYearTask(YearTaskBean yearTaskBean) {
String msg;
logger.info("insertYear Task -> " + yearTaskBean.toString());
try {
yearTaskService.insertYearTask(yearTaskBean);
msg = "insert (" + yearTaskBean.getTaskName() + ") success";
} catch (Exception exception) {
msg = "insert (" + yearTaskBean.getTaskName() + ") error: " + exception.getCause().getMessage();
}
return new BaseBean<>(msg);
}
同样在Postman上调试一下。
Nice,终于有一天,我们前端同学也可以给自己写接口了?。
总结
本文主要是分享作者作为前端同学,在初学后端技术后,输出的第一行代码。
本文涉及的所有代码,都已开源,如有需要点击这里自取。创作分享不易,如果有帮助到你,希望可以给我点个Star,十分感谢。
如果屏幕前的你同样对后端技术感兴趣,欢迎一起探讨学习呀。
到此文章就结束啦~
其实分享文章的最大目的正是等待着有人指出我的错误,如果你发现哪里有错误,请毫无保留的指出即可,虚心请教。
另外,如果你觉得文章不错,对你有所帮助,请给我点个赞,就当鼓励,谢谢~Peace~✌️!