【JavaEE】进阶 · 个人博客系统(4)
文章目录
- 【JavaEE】进阶 · 个人博客系统(4)
- 1. 增加博文
- 1.1 预期效果
- 1.1 约定前后端交互接口
- 1.2 后端代码
- 1.3 前端代码
- 1.4 测试
- 2. 我的博客列表页
- 2.1 期待效果
- 2.2 显示用户信息以及博客信息
- 2.2.1 约定前后端交互接口
- 2.2.2 后端代码
- 2.2.3 前端代码
- 2.2.4 测试
- 2.3 删除文章
- 2.3.1 约定前后端交互接口
- 2.3.2 后端代码
- 2.3.3 前端代码
- 2.3.4 测试
- 2.4 退出登录
- 2.4.1 约定前后端交互接口
- 2.4.2 后端代码
- 2.4.3 前端代码
- 2.4.4 测试
- 3. 修改文章
- 3.1 页面初始化
- 3.1.1 约定前后端接口
- 3.1.2 后端代码
- 3.1.3 前端代码
- 3.1.4 测试
- 3.2 修改文章
- 3.2.1 约定前后端交互接口
- 3.2.2 后端代码
- 3.2.3 前端代码
- 3.2.4 测试
- 4. 博客详情页
- 4.1 期待效果
- 4.2 约定前后端交换接口
- 4.3 后端代码
- 4.4 前端代码
- 4.5 测试
用户在网页中编写标题和正文,点击提交,选择
提交成功后,选择
后端:
前端:
由于经常需要对字符串进行检查,我封装了一个方法:
- 为什么前端检验完了,后端还检验呢?
- 千万别相信“前端”,因为这个“前端”,可能不是浏览器正常的流程,也可能是通过postman等方式发送的请求,这个就可以绕开前端代码的校验~
- 不用担心,因为
public class APPUtils { /** * 字符串全部都有长度才返回true * @param strings * @return */ public static boolean hasLength(String... strings) { for(String x : strings) { if(!StringUtils.hasLength(x)) { return false; } } return true; } }
修改:
@RequestMapping("/publish")
public CommonResult publish(@RequestBody ArticleInfo articleInfo, HttpServletRequest request) {
// 1. 获取当前用户详信息
UserInfo userInfo = SessionUtils.getUser(request);
articleInfo.setUid(userInfo.getId());
articleInfo.setPhoto(userInfo.getPhoto());
// 2. 校验参数
if(!APPUtils.hasLength(articleInfo.getContent(), articleInfo.getSummary(), articleInfo.getTitle())) {
return CommonResult.fail(-1, "非法参数!");
}
// 3. 提交到数据库中
int rows = articleService.publish(articleInfo);
// 4. 返回
return CommonResult.success(rows);
}
@Autowired
private ArticleMapper articleMapper;
public int publish(ArticleInfo articleInfo) {
return articleMapper.insert(articleInfo);
}
@Insert("insert into articleinfo (title, content, summary, uid, photo) values (#{title}, #{content}, #{summary}, #{uid}, #{photo})")
int insert(ArticleInfo articleInfo);
function publish() {
var title = jQuery("#text");
var content = jQuery("#content");
// 1. 参数校验
if (title.val().trim() == "") {
alert("标题不能为空!");
title.focus();
return false;
}
if (content.val().trim() == "") {
alert("正文不能为空!");
content.focus();
return false;
}
// 2. 输入摘要
var summary = prompt("请输入摘要:");
if(summary == "") {
return false;
}
// 3. 发送请求
jQuery.ajax({
url: "/art/publish",
method: "POST",
contentType: "application/json; charset=utf8",
data: JSON.stringify({
title: title.val().trim(),
content: content.val().trim(),
summary: summary.val().trim(),
}),
// 3. 处理响应
success: function (body) {
if (body.code == 200 && body.data == 1) {
if(confirm("发布成功!请问是否继续创作?")) {
location.href = location.href;
}else {
location.href = "myblog_lists.html";
}
} else {
alert("发布失败:" + body.msg);
}
},
});
}
为了避免写文章过程中session过去,我将session设置为永不过期:
后端:
前端:
@RequestMapping("/get_mylist")
public CommonResult getMylist(HttpServletRequest request) {
// 1. 获取当前登录用户
UserInfo userInfo = SessionUtils.getUser(request);
// 2. 通过此用户发布的所有文章
List<ArticleInfo> list = articleService.getListByUid(userInfo.getId());
// 3. 标题 / 正文太长 处理
ArticleUtils.substringList(list);
// 4. 返回给前端
Map<String, Object> map = new HashMap<>();
map.put("user", userInfo);
map.put("list", list);
return CommonResult.success(map);
}
// 文章工具类 public class ArticleUtils { //标题截取长度 private static final int _TITLE_LENGTH = 40; //摘要截取长度 private static final int _SUMMARY_LENGTH = 160; public static void substringList(List<ArticleInfo> list) { if(list != null && list.size() != 0) { // 并发处理 list 集合 list.stream().parallel().forEach((art) -> { //标题截取 if(art.getTitle().length() > _TITLE_LENGTH) { art.setTitle(art.getTitle().substring(0, _TITLE_LENGTH) + "..."); } //摘要截取 if(art.getSummary().length() > _SUMMARY_LENGTH) { art.setSummary(art.getSummary().substring(0, _SUMMARY_LENGTH) + "..."); } }); } } }
public List<ArticleInfo> getListByUid(int uid) {
return articleMapper.getListByUid(uid);
}
@Select("select * from articleinfo where uid = #{uid} order by id desc")
List<ArticleInfo> getListByUid(@Param("uid") int uid); //越晚发布排在越前
可以接受数据库时间的类型一般是:
网络资料
LocalDateTime和Date是Java中表示日期和时间的两种不同的类,它们有一些区别和特点。
类型:LocalDateTime是Java 8引入的新类型,属于Java 8日期时间API(java.time包)。而Date是旧版Java日期时间API(java.util包)中的类。
不可变性:LocalDateTime是不可变的类型,一旦创建后,其值是不可变的。而Date是可变的类型,可以通过方法修改其值。
线程安全性:LocalDateTime是线程安全的,多个线程可以同时访问和操作不同的LocalDateTime实例。而Date是非线程安全的,如果多个线程同时访问和修改同一个Date实例,可能会导致不可预期的结果。
时间精度:LocalDateTime提供了纳秒级别的时间精度,可以表示更加精确的时间。而Date只能表示毫秒级别的时间精度。
时区处理:LocalDateTime默认不包含时区信息,表示的是本地日期和时间。而Date则包含时区信息,它的实际值会受到系统默认时区的影响。
而TimeStamp就是long类型的时间戳的包装~
对于时间格式的控制:
json的构造本身是通过getter去获取的,所以可以重写getter来控制显示效果
全局配置:
但是这只适合jdk8之前的Date类型
局部配置:
左:
右:
jQuery.ajax({
type: "get",
url: "/art/get_mylist",
success: function (body) {
if (body.code == 200) {
// 1. 改变左侧窗口
jQuery(".card img").attr("src", body.data.user.photo);
jQuery(".card h3").text(body.data.user.name);
if(body.data.user.git.trim() != "") {
jQuery(".card a").attr("href", body.data.user.git);
}
jQuery("#count").text(body.data.list.length);
// 2. 显示文章,构造博客html元素
for (var blog of body.data.list) {
console.log(body.title);
var art =
'";
// 3. 追加到div.article
jQuery(".article").append(jQuery(art));
}
}
},
});
你也可以,以标签为单位去设置属性以及嵌套,这有逻辑的构建;而我这里是单纯的拼接字符串,用jQuery(str),构造html元素
后端:
前端:
@RequestMapping("/delete")
public CommonResult delete(@RequestBody ArticleInfo articleInfo, HttpServletRequest request) {
// 1. 获取当前登录用户的id
int uid = SessionUtils.getUser(request).getId();
// 2. 设置到文章对象里
articleInfo.setUid(uid);
// 3. 删除
int rows = articleService.delete(articleInfo);
// 4. 返回
return CommonResult.success(rows);
}
public int delete(ArticleInfo articleInfo) {
return articleMapper.delete(articleInfo);
}
@Delete("delete from articleinfo where id = #{id} and uid = #{uid}")
// 查找文章和检测权限在一步搞定
int delete(ArticleInfo articleInfo);
function del(aid) {
// 0. 参数校验
if (parseInt(aid) == NaN || aid <= 0) {
return false;
}
jQuery.ajax({
method: "post",
url: "/art/delete",
contentType: "application/json; charset=utf8",
data: JSON.stringify({
id: aid,
}),
success: function (body) {
if (body.code == 200 && body.data == 1) {
location.href = location.href;
} else {
alert("删除失败!\n");
}
},
});
}
后端:
前端:
@RequestMapping("/logout")
public void logout(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 设置为null也可以,但这是因为我们的判断原理的原因
//SessionUtils.setUser(request, null);
// 调用工具类里的注销方法
SessionUtils.remove(request);
response.sendRedirect("blog_login.html");
}
/** * 注销 * @param request */ public static void remove(HttpServletRequest request) { HttpSession session = request.getSession(false); if(session != null && session.getAttribute(ApplicationVariable.SESSION_KEY) != null) { session.removeAttribute(ApplicationVariable.SESSION_KEY); } }
<a href="/user/logout">退出登录a>
预期效果就是:原有数据显示出来,供用户修改
后端:
前端:
@RequestMapping("/get_art")
public CommonResult getArt(@RequestBody ArticleInfo articleInfo, HttpServletRequest request) {
// 1. 获取当前登录用户的id
int uid = SessionUtils.getUser(request).getId();
// 2. 设置到文章对象里
articleInfo.setUid(uid);
// 3. 查询文章
ArticleInfo art = articleService.getArt(articleInfo);
// 4. 返回(查询不到一个对象,是null;如果查询不到对象集合,返回的是空集合)
return art == null ? CommonResult.fail(-1, "查询不到!") : CommonResult.success(art);
}
public ArticleInfo getArt(ArticleInfo articleInfo) {
return articleMapper.getArticleCheck(articleInfo);
}
@Select("select * from articleinfo where id = #{id} and uid = #{uid}")
ArticleInfo getArticleCheck(ArticleInfo articleInfo);//检查权限的查询文章
@Select("select * from articleinfo where id = #{id}")
ArticleInfo getArticle(ArticleInfo articleInfo);
<script>
var aid = getParamValue("aid");
// 1. 校验参数
function init() {
if (aid == null || aid <= 0) {
alert("非法参数!");
location.href = "myblog_lists.html";
return false;
}
// 2. 查询文章
jQuery.ajax({
url: "/art/get_art",
method: "post",
contentType: "application/json; charset=utf8",
data: JSON.stringify({
id: aid,
}),
success: function (body) {
if (body.code == 302) {
location.href = body.msg;
return false;
}
if (body.code == 200) {
jQuery("#text").val(body.data.title);
jQuery("#content").val(body.data.content);
jQuery("#summary").val(body.data.summary);//用隐藏输入框保存摘要信息
} else {
alert("发布失败:" + body.msg);
}
},
});
}
init();
script>
注意:
后端:
前端:
@RequestMapping("/update")
public CommonResult update(@RequestBody ArticleInfo articleInfo, HttpServletRequest request) {
// 0. 确认用户
int uid = SessionUtils.getUser(request).getId();
articleInfo.setUid(uid);
// 1. 校验参数
if(!APPUtils.hasLength(articleInfo.getContent(), articleInfo.getSummary(), articleInfo.getTitle())) {
return CommonResult.fail(-1, "非法参数!");
}
// 2. 修改
int rows = articleService.update(articleInfo);
// 3. 返回
return CommonResult.success(rows);
}
public int update(ArticleInfo articleInfo) {
return articleMapper.updateArticle(articleInfo);
}
@Update("update articleinfo set content = #{content}, title = #{title}, summary = #{summary}, updatetime = now() where id = #{id} and uid = #{uid}")
int updateArticle(ArticleInfo articleInfo);
function update() {
if (aid == null || aid <= 0) {
alert("非法参数!");
location.href = "myblog_lists.html";
return false;
}
var title = jQuery("#text");
var content = jQuery("#content");
// 1. 参数校验
if (title.val().trim() == "") {
alert("标题不能为空!");
title.focus();
return false;
}
if (content.val().trim() == "") {
alert("正文不能为空!");
content.focus();
return false;
}
// 2. 输入摘要
var summary = prompt("请输入摘要:", jQuery("#summary").val());
if (summary.trim() == "") {
return false;
}
jQuery("#summary").val(summary);
// 3. 发送请求
jQuery.ajax({
url: "/art/update",
method: "POST",
contentType: "application/json; charset=utf8",
data: JSON.stringify({
id: aid,
title: title.val().trim(),
content: content.val().trim(),
summary: summary.trim(),
}),
// 3. 处理响应
success: function (body) {
if (body.code == 302) {
location.href = body.msg;
return false;
}
if (body.code == 200 && body.data == 1) {
location.href = "myblog_lists.html";
} else {
alert("修改失败:" + body.msg);
}
},
});
}
这样的复杂查询可以用到并发编程:
后端:
前端:
@Autowired
private ArticleService articleService;
@Autowired
private UserService userService;
@RequestMapping("/detail")
public CommonResult detail(@RequestBody ArticleInfo articleInfo, HttpServletRequest request) throws ExecutionException, InterruptedException {
// 1. 查询文章信息
ArticleInfo art = articleService.getArtByAid(articleInfo);
// 2. 校验文章是否存在
if(art == null) {
return CommonResult.fail(-1, "非法参数!");
}
// 3. 根据uid查询用户总文章数的任务
FutureTask<Integer> task1 = new FutureTask<Integer>(() -> {
return articleService.getArtNumberByUid(art.getUid());
});
// 4. 根据uid查询用户信息的任务
FutureTask<UserInfo> task2 = new FutureTask<UserInfo>(() -> {
return userService.getUserByUid(art.getUid());
});
// 5. 根据aid更新阅读量的任务
FutureTask<Integer> task3 = new FutureTask<Integer>(() -> {
return articleService.incrementRCount(art.getId());
});
// 6. 线程池执行任务
APPUtils.THREAD_POOL.submit(task1);
APPUtils.THREAD_POOL.submit(task2);
APPUtils.THREAD_POOL.submit(task3);
// 7. 构造响应数据,并返回
Map<String, Object> map = new HashMap<>();
map.put("login", SessionUtils.getUser(request) != null);
map.put("count", task1.get());
map.put("user", task2.get());
map.put("art", art);
return CommonResult.success(map);
}
ArticleService:
public int getArtNumberByUid(int uid) {
return articleMapper.getArtNumberByUid(uid);
}
public int incrementRCount(int aid) {
return articleMapper.incrementRCount(aid);
}
UserService:
public UserInfo getUserByUid(int uid) {
return userMapper.getUserByUid(uid);
}
ArticleMapper:
@Select("select count(*) from articleinfo where uid=#{uid}")
int getArtNumberByUid(@Param("uid") int uid);
@Update("update articleinfo set rcount = rcount + 1 where id = #{aid}")
int incrementRCount(@Param("aid") int aid);
UserMapper:
@Select("select * from userinfo where id = #{uid}")
UserInfo getUserByUid(@Param("uid") int uid);
- 导航栏
- 网页图标
- 右侧用户卡片
- 右侧文章信息
var aid = getParamValue("aid");
function init() {
if (aid == null || aid <= 0) {
alert("非法参数!");
return false;
}
jQuery.ajax({
method: "post",
url: "/art/detail",
contentType: "application/json; charset=utf8",
data: JSON.stringify({
id: aid,
}),
success: function (body) {
if (body.code == 200) {
// 1. 导航栏显示
if (body.data.login == false) {
jQuery("#icon").attr("href", "img/logo2.png");
jQuery(".navigation img").attr("src", " img/logo2.png");
jQuery(".navigation .space").css("width", "75%");
jQuery(".title").text("未登录");
jQuery("#add").hide();
jQuery("#logout").text("登录");
jQuery("#logout").attr("href", "blog_login.html");
}
// 2. 文章数显示
jQuery("#count").text(body.data.count);
// 3. 用户信息显示
jQuery(".card img").attr("src", body.data.user.photo);
jQuery(".card h3").text(body.data.user.name);
if (body.data.user.git.trim() != "") {
jQuery(".card a").attr("href", body.data.user.git);
}
// 4. 文章信息显示
jQuery(".article h3").text(body.data.art.title);
jQuery(".article .date").text(
body.data.art.createtime + " 阅读量:" + body.data.art.rcount
);
editormd.markdownToHTML("pc", {
markdown: body.data.art.content,
});
} else {
alert("查看失败:" + body.msg);
}
},
});
}
init();
文章到此结束!谢谢观看
可以叫我 小马,我可能写的不好或者有错误,但是一起加油鸭!代码:myblog_system/src · 游离态/马拉圈2023年9月 - 码云 - 开源中国 (gitee.com)