版本1.0
在高中时,尝试建起了个人博客,但那时候是jsp+servlet写的,数据库也是单纯的jdbc,增删该查,前端也是没用到过其他技术,图已经早不到了,想想1.0这个版本非常磕碜,而且代码编辑器是一个富文本,具体名字还给忘了,那时候也没怎么写文章,在服务器上面安静的躺了半年。
版本2.0
在大一开学前,学了SpringBoot+Mybatis+Thymeleaf等知识,匆匆忙忙用他完成了2.0版本,效果图如下,貌似也挺磕碜的,磕碜到只写了10篇文章,后来在学习其他东西,也没有管过他,又在服务器上面安静的躺了1年,但是技术用到了很多,如bootstrap做了响应式,实现三种不同布局,layui做了后端管理界面,并且也有文章评价等功能,算是一个完完整整的博客,但是唯一感到不好的是编辑器用了百度的Ueditor富文本。不知道现在有没有人用了。去问别的博主,人家用的Emlog、Wordpress、Typecho等建站系统,没为找编辑器发愁过。当时也考虑用人家开源的,但是想想也学不到啥,就放弃了。
版本3.0
今年假期,开始做3.0,2.0实现看不下去了,并且新学的技术也能用上,打算使用前后端分离,静态页使用nginx处理,然后通过ajax请求后端Tomcat,同样后端框架是Spring Boot+Mybatis。3.0非常简单,可以说比前两个代码量小了,暂时只有4个界面,真正的以简为主了。这回编辑器使用了Markdown,走向了正轨。
可以到http://blog.houxinlin.com/体验。gif有点卡,但是也很多缺点,没有去其他浏览器上测试,可能存在些兼容问题,只在谷歌火狐中测试过,手机上就更别说了,响应一点没做,这个到后续在加。更没有其他什么花样功能。
开发过程
以上的效果真的可以在1天内完成,只要你坐的住,并且有Spring Boot、Mybatis、Tomcat、Nginx、Ajax等基础。
一、文章编辑页面
首先是到https://pandao.github.io/editor.md/下载开源Markdown编辑器。然后引入并使用以下代码初始化。
<div id="layout" style="background: #f6f6f6;">
<header>
</header>
<div id="editormd">
<textarea style="display:none;"></textarea>
</div>
</div>
var zeptoEditor;
$(function () {
zeptoEditor = editormd("editormd", {
width: "90%",
height: 520,
path: 'lib/md/editor/lib/',
codeFold: true,
searchReplace: true,
saveHTMLToTextarea: true,
imageUpload: true,
imageUploadURL: "saasdf",
htmlDecode: "style,script,iframe|on*",
emoji: true,
taskList: true,
tocm: true,
tex: true,
flowChart: true,
sequenceDiagram: true,
onload: function () {
console.log("onload =>", this, this.id, this.settings);
}
});
});
效果如下,但是文章免不了上传图片,他也支持,但是要做一些配置,并且后端要写图片上传接口,并返回图片的地址,而我没有写,因为考虑到我的服务器带宽小,加载图片实在慢,反而让用户体验差。所以我从别的平台写好,把Markdown复制过来在我的博客中发布即可,如简书,CSDN,这样我的服务器就可以不用承担图片资源,加载也快,索性就不做了。
并且做一个保存界面,如下。通过ajax提交到服务器。
二、首页
这部分主要动画多,都是通过transform来完成。也用不了多长时间,最后写好文章列表模板就可以通过ajax请求json数据,并使用vue渲染。
function requert(url, method, successCallback, errorCallback) {
showLoadingDialog(true);
$.ajax({
type: method,
url: host + url,
success: result => {
showLoadingDialog(false);
successCallback(result)
},
error: (error) => {
showLoadingDialog(false);
errorCallback(error)
},
})
}
比如当点击分类文章的时候,代码如下,其中currentBlogList变量是绑定在vue上的。这部分可以做一个缓存,不用每次点击都去请求数据。
function getClassifyBlog(classify) {
requert("blog/getBlogByClassify?title=" + classify, "GET", (result) => {
if (result.code = 10000) {
currentBlogList.datas = result.data
return;
};
showDialog("出错了呢."+result.msg)
}, () => { showDialog("出错了呢."+result.msg) })
}
function onNavClick(li) {
clearSelectTag();
$("#classify-ul .select").removeClass("select")
li.classList.add("select")
var title = $("#classify-ul .select").html();
getClassifyBlog(title);
}
还有其他细节和功能就不写了,比如请求数据时候的loading动画,和数据出错的提示对话框。
后端
首先是设计数据表,目前只有两张表,一张文章分类表,一张博客表。
Mybatis也使用了Mybatis-plus,导致连sql也不用写了,比如获取指定文章内容:
@GetMapping("detail")
public R detail(@RequestParam("id") int id){
TbBlog tbBlog = mTbBlogService.getById(id);
if (tbBlog==null){
return GeneratorResponseUtils.fail(ResponseCodeEnums.REQUEST_ERROR,"无效ID");
}
mTbBlogService.getBaseMapper().addWatchCount(id);
return GeneratorResponseUtils.success(tbBlog);
}
获取前10个浏览最多的文章作为推荐。
@Override
public List<TbBlog> listRecommend() {
QueryWrapper<TbBlog> tbBlogQueryWrapper = new QueryWrapper<>();
tbBlogQueryWrapper.orderByDesc(TbBlog.WATCH_COUNT).last("limit 10");
return list(tbBlogQueryWrapper);
}
还有其他返回文章列表数据等也非常简单了。也全都是传说中的CURD操作,没其他难点。
但是要注意跨域问题,和Cookie携带问题。
public class CoreFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req =(HttpServletRequest) request ;
HttpServletResponse res =(HttpServletResponse) response;
String origin =req.getHeader("Origin");
if (StringUtils.isNotEmpty(origin)){
res.addHeader("Access-Control-Allow-Origin",origin);
res.addHeader("Access-Control-Allow-Methods","*");
res.addHeader("Access-Control-Allow-Age","3600");
res.addHeader("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With");
res.addHeader("Access-Control-Allow-Credentials","true");
}
chain.doFilter(request,response);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
public class CorsInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
// // 如果是OPTIONS则结束请求
if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {
response.setStatus(HttpStatus.NO_CONTENT.value());
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
@Bean
public FilterRegistrationBean someFilterRegistration1() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter( new CoreFilter());
registration.addUrlPatterns("/*");
return registration;
}
@Override
protected void addInterceptors(InterceptorRegistry registry) {
super.addInterceptors(registry);
registry.addInterceptor(new CorsInterceptor()).addPathPatterns("/**");
}
}
发布
SpringBoot内嵌Tomcat,可以直接打包成jar,但是我还是习惯了war,放到Tomcat中,可以使用Tomcat自带的manager发布,或是scp命令上传,或者其他办法,放入webapps下就可以了。idea中配置一下可以一键发布,但是要先配置下Tomcat。
接下来就是发布静态页到Nginx,打包所有静态页到压缩包。scp上传到服务器Nginx的80工作目录下。为了方便,写了个自动化发布脚本。可以到http://blog.houxinlin.com/detail.html?id=26下观看。
这样,绝大部分工作就已完成,接下来就是完成文章评价,等花里胡哨的功能。