一、需求分析
1.用户注册
2.用户登录
3.用户的文章列表管理页(查询列表,文章删除)
4.添加文章
5.修改文章
6.所有用户的文章列表(带分页的查询功能)
7.文章的详情页(多个查询功能和一个修改功能)
二、设计数据库
用户表:
主键、登录名、密码、昵称、网站地址、头像、状态
文章表:
主键、标题、简介、正文、作者id(用户id)、发表时间、修改时间、阅读量、状态
在分析的时候我们尽量分析全面,然后根据实际情况进行分析,去到MySQL创建数据库和表结构。
三、创建程序框架
使用技术:
Spring Boot + Spring MVC(Spring Web) + MyBatis + MySQL + Redis + (HTML + CSS + JS)
四、项目创建的初始化,添加相关的依赖(Spring Boot项目创建)
五、设置配置文件(application.properties)
配置数据库连接的信息及MyBatis XML存放路径和命名格式
#设置数据的相关连接信息
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mycnblog?characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=*****
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#设置 MyBatis XML 存放路径和命名格式
mybatis.mapper-locations=classpath:mybatis/*Mapper.xml
#配置 MyBatis 执行时打印SQL(可选配置)
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
logging.level.com.example.demo=debug
根据里面的存放路径在resource目录下创建mybatis目录
六、将前端的文件添加到项目中(static目录下)
七、创建后端项目的分层
控制器层——controller
服务层——service
数据持久层——dao(mapper)
实体层——model
配置层——config
工具层——common
八、编写基本类
创建实体类
package com.example.demo.model;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class UserInfo {
private int id;
private String username;
private String password;
private String photo;
private LocalDateTime createtime;
private LocalDateTime updatetime;
private int state;
}
package com.example.demo.model;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class ArtcileInfo {
private int id;
private String title;
private String content;
private LocalDateTime createtime;
private LocalDateTime updatetime;
private int uid;
private int rcount;
private int state;
}
创建每一张表对应的每一层的类
package com.example.demo.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
}
package com.example.demo.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/articl")
public class ArticleController {
}
package com.example.demo.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
}
package com.example.demo.service;
import org.springframework.stereotype.Service;
@Service
public class ArticleService {
}
package com.example.demo.dao;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper {
}
package com.example.demo.dao;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ArticleMapper {
}
因为我们在设置数据持久层的时候,都需要给每个类添加@Mapper注解,所以我们可以在启动类里添加@MapperScan(“com.examle.demo.dao”)说明此包下所有的类都是mapper
九、实现前后端的每个功能
在开始实现前后端的每个功能之前,要先明白前后端交互的关键:AJAX(异步局部提交)
写法:
1.原生写法
2.框架jQuery ajax(简单、通用性好)
1.注册功能实现
在前端注册页面中写参数的获取以及传递参数的方式及参数校验。在本次注册页面中,我要向后端传递的就只有两个值,用户名和密码。
通过提交信息按钮所以我们给该按钮绑定事件。
1.1 前端分析:提交用户注册信息
(1)参数校验(非空校验——》保证数据提交给后端是有效的)——》获取数据
需要获取的控件(用户名、密码、注册按钮)
定义变量建议使用var,不推荐let
$ vs jQuery:使用$会出现多个js中存在有$的别名,会报错,所以推荐使用jQuery。
我们使用jQuery写法,不使用原生写法。实现获取参数进行非空、去空格、定位到该输入框
function mysub() {
// 1.参数校验
// 获取元素
//原生获取方式
//var username = document.getElementById("username").value;
var username = jQuery("#username");
var password = jQuery("#password");
var password2 = jQuery("#password2");
// 校验非空、去空格、让判断了非空后光标定位到该输入框
if(username.val().trim() == "") {
alert("用户名不能为空!");
username.focus();
return false;
}
if(password.val().trim() == "") {
alert("密码不能为空!");
username.focus();
return false;
}
if(password2.val().trim() == "") {
alert("确认密码不能为空!");
username.focus();
return false;
}
}
校验两次密码是否一致
if(password.val() != password2.val()) {
alert("两次密码输入不一致!");
return false;
}
参数校验完成后,开始向后端传递数据
(2)将数据提交给后端,后端返回结果展示给用户
// 2.将数据提交给后端
jQuery.ajax({
url:"/user/reg",
type:"POST",
data:{
// key值要保证和对象的属性值一致
"username":username.val().trim(),
"password":password.val().trim()
},
success:function (res) {
// 3.将后端返回的结果展示给用户
if(res.code == 200 && res.data == 1) {
// 注册成功
alert("注册成功!");
// 跳转到登陆页面
location.href = "login.html";
}else {
alert("注册失败!" + res.msg);
}
}
})
1.2 后端分析,接收前端发来的请求数据处理
在UserController中我们开始写实现注册的方法,然后我们这里呢需要接收前端传过来的值,但是不建议使用值直接传递,我们传递整个对象要好一点,方便后期添加参数的时候,好维护。
又因为在任何时候我们都要保证前后端进行交互时候返回的都是一个统一的对象,所以我们新建一个类ResultAjax(统一的前后端返回对象)。
在该类中搞两个方法(一个成功,一个失败)
package com.example.demo.common;
import lombok.Data;
/**
* 前后端交互统一的返回对象
*/
@Data
public class ResultAjax {
private int code;
private String msg;
private Object data;
// 成功情况
public static ResultAjax succ(Object data) {
ResultAjax result = new ResultAjax();
result.setCode(200);
result.setMsg("");
result.setData(data);
return result;
}
// 有些时候成功了我也是需要去设置code,msg,方法重载即可
public static ResultAjax succ(Object data,int code,String msg) {
ResultAjax result = new ResultAjax();
result.setCode(code);
result.setMsg(msg);
result.setData(data);
return result;
}
// 失败情况,通常不需要返回data
public static ResultAjax fail(int code,String msg) {
ResultAjax result = new ResultAjax();
result.setCode(code);
result.setMsg(msg);
return result;
}
// 如果要返回data,同样的也是弄一个方法重载
public static ResultAjax fail(int code,String msg,Object data) {
ResultAjax result = new ResultAjax();
result.setCode(code);
result.setMsg(msg);
result.setData(data);
return result;
}
}
然后回到UserController中编写业务代码。
(1)参数校验
StringUtils是校验非空的
// 1.参数校验
// StringUtils判断非空(布尔类型)
if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
|| !StringUtils.hasLength(userInfo.getPassword())) {
// 参数异常
return ResultAjax.fail(-1,"非法参数!");
}
(2)请求service进行添加操作
package com.example.demo.service;
import com.example.demo.dao.UserMapper;
import com.example.demo.model.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
// 注册功能
public int reg(UserInfo userInfo) {
return userMapper.reg(userInfo);
}
}
package com.example.demo.dao;
import org.apache.ibatis.annotations.Mapper;
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.Insert;
@Mapper
public interface UserMapper {
@Insert("insert into userinfo(username,password) values(#{username},#{password})")
int reg(UserInfo userInfo);
}
(3)将结果返回给前端
return ResultAjax.succ(result);
至此就完成了注册功能的实现。
2.总结前后端交互三步骤
3.统一返回数据格式,统一异常处理实现
我们在common包下新建一个类ResponseAdvice,实现ResponseBodyAdvice接口底下的supports、beforeBodyWrite方法。使用注解@ControllerAdvice。
package com.example.demo.common;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/**
* 保底统一返回值处理
*/
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Autowired
private ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if(body instanceof ResultAjax) {
return body;
}
// 处理String
if(body instanceof String) {
// jackson
ResultAjax resultAjax = ResultAjax.succ(body);
try {
return objectMapper.writeValueAsString(resultAjax);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
return ResultAjax.succ(body);
}
}
同样的在common包下建立ExceptionAdvice类
package com.example.demo.common;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 统一异常处理
*/
//@RestControllerAdvice
public class ExceptionAdvice {
@ExceptionHandler(Exception.class)
public ResultAjax doException(Exception e) {
return ResultAjax.fail(-1,e.getMessage());
}
}
统一异常处理一开始不急着处理,因为如果一旦开始处理,那么就会出现有很多错误信息我们是捕获不到的。
4.登录业务逻辑的实现
这里要考虑的问题就是如果我们想要在登录页面中添加额外的属性验证,而不是从数据库中拿出去验证,这里就涉及到对象类型。
对象类型
1.基础对象:对象中的属性和数据库中的字段是一样的,Userinfo/Articleinfo
2.扩展对象:对基本对象的升级——》VO(View Object)UserinfoVO/ArticleinfoVO(还有其它的衍生属性比如,验证码)
package com.example.demo.model.vo;
import com.example.demo.model.UserInfo;
import lombok.Data;
/**
* userInfo扩展类
*/
@Data
public class UserinofVO extends UserInfo {
// 验证码
private String checkCode;
}
4.1登陆验证的思路
(1)根据用户输入的用户名+密码去查询用户表验证
(2)根据用户名查询对象验证(推荐方式——》灵活度高)
结果:如果查询到对象,就用查询到的对象和传递过来的密码进行对比,相同的话表示登陆成功,不同表示登陆失败。
继续在UserController中进行业务处理。
/**
* 登录功能验证
*/
@RequestMapping("/login")
public ResultAjax login(UserinofVO userinofVO) {
// 1.参数校验
// 2.根据用户名查询对象
// 3.使用对象中的密码和用户输入的密码进行比较
// 4.比较如果相同,那么就将对象存储到session中
// 5.将结果返回给用户
return null;
}
// 1.参数校验
if(userinofVO == null || !StringUtils.hasLength(userinofVO.getUsername()) || !StringUtils.hasLength(userinofVO.getPassword())) {
// 非法登录
return ResultAjax.fail(-1,"参数有误!");
}
// 2.根据用户名查询对象(调用service)
UserInfo userInfo = userService.getUserByName(userinofVO.getUsername());
// 如果输入的用户名不存在数据库中
if(userInfo == null || userInfo.getId() == 0) {
// id=0证明不含有该用户的存在
return ResultAjax.fail(-2,"用户名或密码不存在!"); //不要直接写该用户不存在,提示信息不要太细致
}
// 3.使用对象中的密码和用户输入的密码进行比较
if(userinofVO.getPassword().equals(userInfo.getPassword())) {
// 密码错误
return ResultAjax.fail(-2,"用户名或密码错误!");
}
// 4.比较如果相同,那么就将对象存储到session中
// 这里的话就需要有http请求了,所以在此方法上在添加一个参数
// public ResultAjax login(UserinofVO userinofVO, HttpServletRequest request)
HttpSession session = request.getSession();
// 考虑把session值存入到全局变量中,创建一个AppVariable类,专门用来存储全局变量的
session.setAttribute(AppVariable.SESSION_USERINFO_KEY,userInfo);
// 5.将结果返回给用户
return ResultAjax.succ(1);
然后去到前端页面编写前端传入数据逻辑
function login() {
// 1.参数校验
var username = jQuery("#username");
var password = jQuery("#password");
if(username.val().trim() == "") {
alert("用户名不能为空!");
username.focus();
return false;
}
if(password.val().trim() == "") {
alert("密码不能为空!");
password.focus();
return false;
}
// 2.将数据提交给后端
jQuery.ajax({
url:"/user/login",
type:"GET",
data:{
"username":username,
"password":password
},
// 3.将后端返回的结果展示给用户
success:function(res) {
if(res.code == 200 && res.data == 1) {
alert("登录成功!");
// 跳转到我的文章管理页
location.href = "myblog_list.html";
}else{
alert("登陆失败!" + res.msg);
}
}
})
}
至此登陆验证功能完成。
5.根据登录的用户查询自己的文章列表页
因为在articleinfo表中存在用户uid,而我们的uid又是保存在session中的,查询的sql语句就是
select * from articleinfo where uid = uid and id = id;
我们去到前端页面生成上述样式的格式,初始化页面,同时把值传递给后端进行处理。
<script>
// 页面初始化
function init() {
jQuery.ajax({
url:"/art/mylist",
type:"GET",
data:{},
success:function(res) {
if(res.code == 200) {
// 请求成功,可以去拼接信息(有文章情况)
var createHtml = "";
var artlist = res.data;
if(artlist == null || artlist.length == 0) {
// 未发表文章的
createHtml += "<h3 style='margin-left:50px; margin-top:30px;'><a href='blog_add.html'>添加</a>暂无文章,请先添加文章!</h3>";
}else {
// 去循环列表区域的内容
for(var i = 0;i < artlist.length;i++) {
var art = artlist[i];
createHtml += '<div class="blog">';
createHtml += '<div class="title">' + art.title + '</div>';
createHtml += '<div class="date">' + art.createtime + '</div>';
createHtml += '<div class="desc">';
createHtml += art.content;
createHtml += '</div>';
createHtml += '<a href="blog_content.html?aid=' + art.id +
'" class="detail">查看全文 >></a> ';
createHtml += '<a href="blog_edit.html?aid=' + art.id +
'" class="detail">修改 >></a> ';
createHtml += '<a href="javascript:del('+art.id +')" class="detail">删除 >></a>';
createHtml += '</div>';
}
}
}else if(res.code == -1) {
// 未登录
alert("请先登录!")
location.href = "login.html";
}else{
alert("操作失败!" + res.msg);
}
jQuery("#artListDiv").html(createHtml);
}
});
}
init();
</script>
回到后端ArticleController处理如下
我们要首先要拿到作者的id,当我们登录成功时候我们是把这个值存储到session中的,所以我们可以专门建立一个SessionUtils类,用作session的工具类,负责从中判断登陆状态,从而拿到作者id信息。
// 得到当前登录用户的文章列表
@RequestMapping("/mylist")
public ResultAjax myList(HttpServletRequest request) {
// 1.得到当前用户
UserInfo userInfo = SessionUtils.getUser(request);
// 判断是否是登录的
if(userInfo == null) {
// 没有登录
return ResultAjax.fail(-1,"请先登录!");
}
package com.example.demo.common;
import com.example.demo.model.UserInfo;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
* session工具类
*/
public class SessionUtils {
// 后端从session中拿到作者id
// 得到当前用户
public static final UserInfo getUser(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if(session != null && session.getAttribute(AppVariable.SESSION_USERINFO_KEY) != null) {
// 说明是登录状态
return (UserInfo) session.getAttribute(AppVariable.SESSION_USERINFO_KEY);
}
return null;
}
}
根据此用户的id查询它名下的所有文章。(调用service)在ArticleController中注入
public interface ArticleMapper {
// 根据用户id查询他的所有文章列表页,最先发布的在前面,所以是降序
@Select("select * from articleinfo where uid = #{uid} order by createtime desc")
List<ArtcileInfo> getListByUid(@Param("uid") int uid);
}
@Service
public class ArticleService {
@Autowired
private ArticleMapper articleMapper;
// 查询文章列表
public List<ArtcileInfo> getListByUid(int uid) {
return articleMapper.getListByUid(uid);
}
由于在列表显示的时候只需要展示简介部分,这里设置一个简介的长度,然后通过多线程的方式去处理实现(parallel()并发执行)
// 2.根据用户id查询此用户所发表的所有文章(调用service)
List<ArtcileInfo> list = articleService.getListByUid(userInfo.getId());
// 使用多线程方式,处理list将文章正文变成简介
if(list != null && list.size() > 0) {
// 使用parallel()并行流处理方式
list.stream().parallel().forEach((art) -> {
// 截取一定长度作为简介内容,可一定一个全局变量
if(art.getContent().length() > _DESC_LENGTH) {
art.setContent(art.getContent().substring(0,_DESC_LENGTH));
}
});
}
在上述代码中,使用了并行流parallel() 去并行处理列表中的元素,针对每一个元素的content都进行了截取操作,然后使用Lambda表达式定义一个匿名函数,该匿名函数接收一个参数art,表示文章列表中的每个元素,通过去调用getContent获取元素的内容,同时判断长度,使用substring()方法进行截取操作,截取完成将新的内容赋值给content属性。
最后就是将获得的列表结果返回给前端
// 3.将数据返回给前端
return ResultAjax.succ(list);
至此就完成了根据登录的用户查询自己的文章列表页的功能实现。
但是在这里还存在一个细节问题,就是时间格式的问题
6.处理时间格式:
全局设置:
#设置时间格式
spring.jackson.data-format=yyyy-MM-dd HH:mm
spring.jackson.time-zone=GMT+8
可以发现还是没有设置成功,原因是因为这种方式只对Date类型有效。
解决办法:局部设置,使用注解方式
(pattern = "yyyy-MM-dd HH:mm",timezone = "GMT+8")
7.退出功能实现
由于每个页面都有一个退出功能,所以我们可以封装成独立的js文件,在每个页面引用即可
// 注销退出功能
function logout() {
if(confirm("是否确认注销?")) {
// 1.先去后端删除session
jQuery.ajax({
url:"/user/logout",
type:"POST",
data:{},
success:function(res) {}
});
// 2.跳转到登陆页面或者首页
location.href = "login.html";
}
}
回到后端UserController类中进行处理,通过调用session.removeAttribute()
方法,可以将session值从会话中移除,以达到清除用户信息的目的。
// 注销功能实现
@RequestMapping("/logout")
public ResultAjax logout(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if(session != null && session.getAttribute(AppVariable.SESSION_USERINFO_KEY)!= null) {
session.removeAttribute(AppVariable.SESSION_USERINFO_KEY);
}
return ResultAjax.succ(1);
}
8.删除操作
根据个人自己的列表页进行删除指定文章
转到ArticleController进行删除操作
(1)参数校验
// 个人列表页删除操作
@RequestMapping("/del")
public ResultAjax del(Integer aid,HttpServletRequest request) {
// 1.参数校验
if(aid == null || aid <= 0) {
return ResultAjax.fail(-1,"参数错误!");
}
(2)得到当前用户
// 2.得到当前登录用户(所以需要在方法上传递一个请求参数)
UserInfo userInfo = SessionUtils.getUser(request);
if(userInfo == null) {
return ResultAjax.fail(-1,"请先登录!");
}
(3)判断文章的归属人
(4)删除操作
// 根据当前登陆用户的id删除所属指定的文章
@Delete("delete from articleinfo where id = #{aid} and uid = #{uid}")
int del(@Param("aid") Integer aid,@Param("uid") int uid);
// 删除操作
public int del(Integer aid,int uid) {
return articleMapper.del(aid,uid);
}
// 3.判断文章的归属人(调用service)4.删除操作 where id = aid and uid = uid
int result = articleService.del(aid, userInfo.getId());
(5)将结果返回给前端
// 5.将结果返回给前端
return ResultAjax.succ(result);
处理前端逻辑
// 删除功能,根据文章id进行删除操作
function del(aid) {
// 1.参数校验
if(aid == "" || aid <= 0) {
alert("参数错误!");
return false;
}
// 2.将数据提交给后端进行删除操作
jQuery.ajax({
url:"/art/del",
type:"POST",
data:{
"aid":aid
},
success:function(res) {
// 3.将后端返回的结果展示给用户
if(res.code == 200 && res.data == 1) {
alert("删除成功!");
// 刷新当前页面
location.href = location.href;
}else {
// 删除失败
alert("删除失败!" + res.msg);
}
}
})
}
可以在继续处理给一个提示是否要删除
// if(confirm("确定要删除该篇文章吗?")) {
// // 用户确认点击了确认
// alert("删除成功!");
// // 刷新当前页面
// location.href = location.href;
// }else {
// // 用户取消删除
// alert("删除操作已取消!");
// }
9.添加文章操作
9.1前端操作:
1.参数校验
function mysub(){
// alert(editor.getValue()); // 获取值
// editor.setValue("#123") // 设置值
// 1.参数校验(非空验证)
var title = jQuery("#title");
if(title.val().trim() == "") {
alert("请先输入标题!");
return false;
}
if(editor.getValue() == "") {
alert("请先输入正文!");
return false;
}
}
2.将数据传递给后端
3.将后端返回的结果展示给用户
// 2.将数据提交给后端
jQuery.ajax({
url:"/art/add",
type:"POST",
data:{
"title":title.val().trim(),
"content":editor.getValue()
},
success:function(res) {
// 3.将后端返回的结果展示给用户
if(res.code == 200 && res.data == 1) {
// 文章添加成功
if(confirm("添加文章成功!是否继续添加?")) {
// 继续添加
// 刷新当前页面
location.href = location.href;
}else {
// 跳转到个人文章管理页
location.href = "myblog_list.html";
}
}else {
alert("文章添加失败!" + res.msg);
}
}
})
9.2后端操作
去到后端ArticleController(插入文章操作)
1.参数校验
// 添加文章操作
@RequestMapping("/add")
public ResultAjax add(ArtcileInfo artcileInfo,HttpServletRequest request) {
// 1.参数校验
if (artcileInfo == null || !StringUtils.hasLength(artcileInfo.getContent()) || !StringUtils.hasLength(artcileInfo.getTitle())) {
return ResultAjax.fail(-1,"参数非法!");
}
2.组装数据,把用户uid加入到articleinfo中
// 2.组装数据,将uid加入到articleinfo里面
UserInfo userInfo = SessionUtils.getUser(request);
if(userInfo == null) {
return ResultAjax.fail(-2,"请先登录!");
}
artcileInfo.setUid(userInfo.getId());
3.将前端传来的数据添加到数据库中
// 添加文章操作
@Insert("insert into articleinfo(title,content,uid) values (#{title},#{content},#{uid})")
int add(ArtcileInfo artcileInfo);
// 添加文章操作
public int add(ArtcileInfo artcileInfo) {
return articleMapper.add(artcileInfo);
}
// 3.将前端传来的数据保存到数据库
int result = articleService.add(artcileInfo);
4.将结果返回给前端
// 4.将结果返回给前端
return ResultAjax.succ(result);
10.修改文章功能
在修改文章之前,当我们点击修改按钮后就要跳到对应的修改页面,那么此时也就会把这篇文章的相关属性也对应得带过去,所以在进行修改操作之前我们需要先根据登录用户以及当前用户的文章id去将对应的文章信息显示在修改页面上。也就是初始化修改页。
去到前端页面进行操作。记得定义传过来的文章id(aid),可以定义成全局的。
var aid = getParamValue("aid");
// 初始化修改页面,将从个人文章列表页指定文章信息传过来显示
function init() {
// 1.参数校验
// 看传过来得aid是否合法
if(aid == null || aid <= 0) {
alert("非法参数请求!");
return false;
}
// 2.查询文章的详情
jQuery.ajax({
url:"/art/update_init",
type:"GET",
data:{
"aid":aid
},
// 3.将后端传来的数据展示给用户
success:function(res) {
if(res.code == 200 || res.data != null && res.data.id > 0) {
// 查询到了有效的信息
jQuery("#title").val(res.data.title);
initEdit(res.data.content);
}else if(res.code == -1) {
// 未登录!
alert("当前未登录!" + res.msg);
location.href = "login.html";
}else {
alert("查询失败!" + res.msg);
}
}
})
}
回到后端ArticleController中进行处理
// 修改文章前得初始化页面操作,将对应的内容显示在页面上
@RequestMapping("/update_init")
public ResultAjax updateInit(Integer aid,HttpServletRequest request) {
// 1.参数校验
if(aid == null || aid <= 0) {
return ResultAjax.fail(-1,"参数错误!");
}
// 得到当前登录用户,从session中去,要在方法上添加一个http请求参数
UserInfo userInfo = SessionUtils.getUser(request);
if(userInfo == null) {
return ResultAjax.fail(-2,"请先登录!");
}
// 已经登陆了
// 3.查询文章并校验权限 select * from articleinfo where id = #{aid} and uid = #{uid};
ArtcileInfo artcileInfo = articleService.getArticleByIdAndUid(aid, userInfo.getId());
// 4.将结果返回给前端
return ResultAjax.succ(artcileInfo);
}
// 修改文章之前初始化修改页面,将要修改的文章信息传递在页面(查询对应作者文章)
@Select("select * from articleinfo where id = #{aid} and uid = #{uid}")
ArtcileInfo getArticleByIdAndUid(@Param("aid") int aid,@Param("uid") int uid);
// 修改页面初始化(查询对应作者文章)
public ArtcileInfo getArticleByIdAndUid(int aid,int uid) {
return articleMapper.getArticleByIdAndUid(aid,uid);
}
用户自己修改自己的文章,那么当点击修改按钮的时候,将会跳转到修改页面,在去到这个修改页面时候,需要把要修改的文章id也一同传过去。
因为我们是通过url地址链接过去得,所以我们要在前端中去解析这个url路径上的参数和值,通过使用js得内置函数(location.search)获取 **?**后面的所有参数。
新建一个urlutils.js文件
// 获取url中的参数值
function getParamValue(key) {
// 1.得到当前url得参数部分
var params = location.search;
// 2.去除 ”?“
if(params.indexOf("?") >= 0) {
params = params.substring(1);
// 3.根据”&“将参数分割成多个数组
var paramArray = params.split("&");
// 4.循环对比 key,并返回要查询的 value
if(paramArray.length >= 1) {
for(var i = 0; i < paramArray.length; i++) {
var item = paramArray[i].split("=");
if(item[0] == key) {
return item[1];
}
}
}
}
// 没有找到,返回null
return null;
}
在编辑页面中进行引入,编写前端代码
function doupdate() {
// 1.参数校验(非空验证)
var title = jQuery("#title");
if(title.val().trim() == "") {
alert("请先输入标题!");
return false;
}
if(editor.getValue() == "") {
alert("请先输入正文!");
return false;
}
// 2.将数据提交给后端
jQuery.ajax({
url:"/art/update",
type:"POST",
data:{
//从url路径中获取到文章id(aid)传递后端
"id":aid,
"title":title.val(),
"content":editor.getValue()
},
// 将后端传来的数据展示给用户
success:function(res) {
if(res.code == 200 && res.data == 1) {
// 修改成功
// 跳转到我的文章列表页中
location.href = "myblog_list.html";
}else if(res.code == -2) {
// 未登录
alert("请先登录!");
location.href = "login.html";
}
else {
// 修改失败!
alert("修改失败!" + res.msg);
}
}
})
}
然后回到后端ArticleController中编写业务代码
(1)参数校验
// 修改文章操作
@RequestMapping("/update")
public ResultAjax update(ArtcileInfo artcileInfo,HttpServletRequest request) {
// 1.参数校验
if(artcileInfo == null || !StringUtils.hasLength(artcileInfo.getTitle()) || !StringUtils.hasLength(artcileInfo.getContent())) {
// 参数错误
return ResultAjax.fail(-1,"非法参数!");
}
(2)获取当前登录用户
从session中取值,所以方法上要加一个参数
// 2.获取当前登录用户
UserInfo userInfo = SessionUtils.getUser(request);
if(userInfo == null) {
// 没有登陆
return ResultAjax.fail(-2,"请先登录!");
}
如果是登录的,那么就要设置articleinfo表中的uid
// 登陆了那么就要设置articleInfo中的uid
artcileInfo.setUid(userInfo.getId());
(3)执行修改操作(调用service——mapper)
// 修改文章操作
@Update("update articleinfo set title = #{title},content = #{content} where id = #{id} and uid = #{uid}")
int update(ArtcileInfo articleInfo);
// 修改文章操作
public int update(ArtcileInfo artcileInfo) {
return articleMapper.update(artcileInfo);
}
// 3.执行修改操作
int result = articleService.update(artcileInfo);
(4)将结果返回给前端
// 4.将结果返回给前端
return ResultAjax.succ(result);
11.详情页显示功能
在我的列表页以及主页的列表页点击查看全文即可进入文章的详细页面,查看详情页根据查用户名,以及查询文章的详细信息即可进入详情页面。所以这里可以用线程池的方式去实现,一个负责根据uid查询用户的详细信息,一个根据uid查询用户发表的总文章数(显示在我的列表页)
将线程池引入,把它放在config包下,使用Bean注解的方式加入到容器中,这样需要用直接调用即可。
package com.example.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
/**
* 全局配置线程池
*/
@Configuration
public class ThreadPoolConfig {
@Bean
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(10000);//任务队列
executor.setThreadNamePrefix("MyThread-");
executor.initialize();
return executor;
}
}
去到ArticleController中编写业务代码,将线程池注入
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
(1)参数校验
/**
* 查询文章详细信息
*/
@RequestMapping("/detail")
public ResultAjax detail(Integer aid) {
// 1.参数校验
// 看传过来的文章aid是不是有效的参数
if(aid == null || aid <= 0) {
return ResultAjax.fail(-1,"非法参数!");
}
(2)查询文章的详细信息
// 查询文章的详细信息
@Select("select * from articleinfo where id = #{aid}")
ArtcileInfo getDeatilById(@Param("aid") int aid);
// 查询文章的详细信息
public ArtcileInfo getDetailById(int aid) {
return articleMapper.getDeatilById(aid);
}
// 2.查询文章的详细信息
ArtcileInfo artcileInfo = articleService.getDetailById(aid);
if(artcileInfo == null || artcileInfo.getId() <= 0) {
return ResultAjax.fail(-1,"非法参数!");
}
(3)根据uid查询用户的详细信息
使用线程池方式实现
// 3.根据uid查询用户的详细信息(使用线程池方式实现)
FutureTask<UserinfoVO> userTask = new FutureTask<>(()-> {
// 把用户服务注入
return userService.getUserById(artcileInfo.getUid());
});
// 将这个任务加入到线程池中
taskExecutor.submit(userTask);
// 根据uid查询用户的详情信息
@Select("select * from userinfo where id = #{uid}")
UserinfoVO getUserById(@Param("uid") int uid);
// 根据用户uid查询用户的详细信息
public UserinfoVO getUserById(int uid) {
return userMapper.getUserById(uid);
}
(4)根据uid查询用户发表的总文章数
使用线程池方式实现
// 4.根据uid查询用户发表的总文章数
FutureTask<Integer> artCountTask = new FutureTask<>(()-> {
// return null;
return articleService.getArtCountByUid(artcileInfo.getUid());
});
// 将这个任务加入到线程池中
taskExecutor.submit(artCountTask);
// 根据用户uid查询用户发表的总文章数
@Select("select count(*) form articleinfo where uid = #{uid}")
ArtcileInfo getArtCountByUid(@Param("uid") Integer uid);
// 根据uid查询用户发表的总文章数
public ArtcileInfo getArtCountByUid(Integer uid) {
return articleMapper.getArtCountByUid(uid);
}
(5)组装数据
在userinfoVO类中定义总的发布文章数
// 5.组装数据
// 要等待线程执行完成
UserinfoVO userinfoVO = userTask.get();
int artCount = artCountTask.get();
userinfoVO.setArtCount(artCount);
HashMap<String,Object> result = new HashMap<>();
result.put("user",userinfoVO);
result.put("art",artcileInfo);
(6)将结果返回给前端
在前端我的文章列表中(blog_content.html)
// 6.将结果返回给前端
return ResultAjax.succ(result);
回到前端页面中进行数据传输处理
// 初始化页面
function init() {
// 1.参数校验
// 对aid进行校验
if(aid == null || aid <= 0) {
// 说明传递过来的参数是无效的
alert("参数有误!");
return false;
}
// 2.将数据传递给后端
jQuery.ajax({
url:"/art/detail",
type:"GET",
data:{
"aid":aid
},
// 3.将后端返回来的数据展示给用户
success:function(res) {
if(res.code == 200 || res.data != null) {
var user = res.data.user;
var art = res.data.art;
if(user != null) {
// 给对象设置值
if(user.photo != null) {
jQuery("#photo").attr("src",user.photo);
}
jQuery("#username").html(user.username);
jQuery("#artcount").html(user.arcCount);
}else {
alert("查询失败!" + res.msg);
}
if(art != null) {
jQuery("#title").html(art.title);
jQuery("#createtime").html(art.createtime);
jQuery("#rcount").html(art.rcount); //阅读量
initEdit(art.content);
}else {
alert("查询失败!" + res.msg);
}
}else {
alert("查询失败!" + res.msg);
}
}
})
}
init();
效果如下
我们可以看到那个阅读量,也就是访问量,这个访问量是没有权限的,只要点进去看就能增加阅读量,所以我们在前端再写一个方法,让这个阅读量每次点进去的时候(每次刷新页面)数量+1。
// 访问量+1
function incrementRcount() {
// 1.校验参数
if(aid == null || aid <= 0) {
alert("参数有误!");
return false;
}
// 2.将数据传递给后端
jQuery.ajax({
url:"/art/increment_rcount",
type:"POST",
data:{
"aid":aid
},
// 3.不用返回数据给前端
success:function(res) {}
})
}
incrementRcount();
去到后端ArticleController中处理业务
// 每次点击去查看文章阅读量+1
@RequestMapping("/increment_rcount")
public ResultAjax incrementRcount(Integer aid) {
// 1.校验参数
if(aid == null || aid <= 0) {
return ResultAjax.fail(-1,"参数有误!");
}
// 2.将数据传给数据库中进行修改(调用service)
int result = articleService.incrementRcount(aid);
// 3.将数据返回给前端
return ResultAjax.succ(result);
}
// 每次点击查看全文进行阅读量+1
@Update("update articleinfo set rcount = (rcount + 1) where id = #{aid}")
int incrementRcount(@Param("aid") int aid);
// 每次点击查看全文阅读量加1
public int incrementRcount(int aid) {
return articleMapper.incrementRcount(aid);
}
12.首页所有文章列表的显示分页功能
观察页面要获取显示的有所有文章,标题,日期,页码。所以我们也可以通过使用多线程方式来实现并发查找。
分页核心参数:
1.页码(当前在第几页)
2.每页显示的最大条数(前端灵活的控制分页功能)
后端分页返回:
1.当前页面的文章列表
2.返回的总页数
12.1在此之前推导一些分页公式。
所以我们关键的就是传这个偏移量,那么这个偏移量我们是需要计算好了在传过去,不然的话传给后端处理,在mysql查询时候会失效。
(1)加工参数
如果是只有一页,或者没有,那么默认的页数是第一页,页面显示的数据是2条。
// 查询分页功能
@RequestMapping("/getlistbypage")
public ResultAjax getListByPage(Integer pindex,Integer psize) {
// pindex:当前第几页
// psize:每页显示条数
if(pindex == null || pindex < 1) {
// 参数校正,默认显示第一页
pindex = 1;
}
if(psize == null || psize < 1) {
// 参数校正,默认显示2条数据
psize = 2;
}
return null;
}
(2)并发进行文章列表和总页数的查询
查询分页:
在Lambda表达式中使用的变量应该是final或者是事实上的final。因为Lambda表达式可以访问外部作用域中的变量,但是这些变量必须是不可变的或者在Lambda表达式中没有被修改。所以我们要把这个当前页和每页显示的条数定义在方法外面。
// 查询分页
int finalPsize = psize;
int finalPindex = pindex;
int finalOffset = finalPsize * (finalPindex - 1);
FutureTask<List<ArtcileInfo>> listFutureTask = new FutureTask<>(() -> {
// 计算偏移量,分页公式
// int offset = psize * (pindex - 1);
// return articleService.getListByPage(psize,offset);
return articleService.getListByPage(finalPsize,finalOffset);
});
计算总页数:
总的页数计算 = 总的条数 / 每页显示的条数(向上取整)
为什么向上取整?因为当我是总共5条记录数据,我每页显示3条数据,那么总的页数(5/3 = 1…)如果不选择向上取整的话那么就是一页显然是不行的。并且在这里还要注意的是对数据类型的处理,像这种有余数的我们不管有没有余数,给每一个数都*1.0转换为浮点类型,都统一的使用double类型的去接收计算的值,然后再向上取整,最后强转为int类型。因为就比如(5/3 = 1…)默认是int接收,那么结果就为1,那么也是一样的向上取整后还是1,显然是不正确的。
// 计算总页数,总的文章id数
FutureTask<Integer> integerFutureTask = new FutureTask<>(() -> {
// 1.得到总的记录条数
int totalCount = articleService.getCount();
// 计算总的页数 = 总的记录条数 / 每页显示的条数
double sizeTemp = (totalCount * 1.0) / (finalPsize * 1.0);
return (int)Math.ceil(sizeTemp);
});
(3)将两个任务加入到线程池中
// 将两个任务加入到线程池中
taskExecutor.submit(listFutureTask);
taskExecutor.submit(integerFutureTask);
(4)组装数据
// 4.组装数据
// get()等到任务执行完成返回结果
List<ArtcileInfo> list = listFutureTask.get(); //分页列表
int size = integerFutureTask.get(); //总页数
HashMap<String,Object> map = new HashMap<>();
map.put("list",list);
map.put("size",size);
(5)将结果返回给前端
// 5.将结果返回给前端
return ResultAjax.succ(map);
至此就完成了分页功能的后端操作,下面跳转到要显示的前端页面中进行处理
12.2前端操作
(1)首先先初始化一下页面(根据前端布局)
// 每页显示条数
var psize = 2;
// 当前页码(默认第一页)
var pindex = 1;
// 总页码
var totalpage = 1;
// 初始化数据
function init() {
// 1.先从url中得到分页参数
// 每页显示的条数
psize = getParamValue("psize"); //引用urlutils.js
if(psize == null) {
psize = 2;
}
// 当前第几页
pindex = getParamValue("pindex");
if(pindex == null) {
pindex = 1;
}
// 2.请求后端接口
jQuery.ajax({
url:"/art/getlistbypage",
type:"GET",
data:{
"pindex":pindex,
"psize":psize
},
// 3.将结果展示给用户
success:function(res) {
if(res == 200 || res.data != null) {
var createHtml = ""; //定义html标签变量进行拼接html
if(res.data.list != null && res.data.list.length > 0) {
// 说明有文章
totalpage = res.data.size;
var artlist = res.data.list;
for(var i = 0; i < artlist.length; i++) {
var art = artlist[i];
createHtml += '<div class="blog" >';
createHtml += '<div class="title">' + art.title + '</div>';
createHtml += '<div class="date">' + art.createtime + '</div>';
createHtml += '<div class="desc">' + art.content + '</div>';
createHtml += '<a href="blog_content.html?aid=' + art.id + '" class="detail">查看全文 >></a>';
createHtml += '</div>';
}
}else {
// 没有文章
createHtml += '<h3 style="margin-left:50px;margin-top:50px;"><a href="blog_add.html">点击添加文章</a>暂无文章!</h3>';
}
jQuery("#articleDiv").html(createHtml);
}else {
alert("请求失败!" + res.msg);
}
}
});
}
init();
查询默认显示第一页,2条数据
实现四个按钮触发功能
// 点击首页
function doFirst() {
// 1.判断当前页是否是首页
// 如果传过来的pindex是1,那么就是首页,不是的话就要进行跳转
if(pindex <= 1) {
alert("当前已是第一页!");
return false;
}
// 2.跳转到首页
location.href = "blog_list.html";
}
// 点击末页
function doLast() {
// 1.判断当前页是否是末页
if(pindex >= totalpage) {
alert("当前已是最后一页!");
return false;
}
// 2.跳转到末页
location.href = "blog_list.html?pindex=" + totalpage;
}
// 点击上一页
function doBefore() {
// 看上一页还有没有,有就pindex - 1,没有就是首页
if(pindex <= 1) {
alert("当前已是第一页!");
return false;
}
location.href = "blog_list.html?pindex=" + parseInt(pindex - 1);
}
// 点击下一页
function doNext() {
// 看下一页有咩有,有就pindex + 1,没有就是首页
if(pindex >= totalpage) {
alert("当前已是最后一页!");
return false;
}
location.href = "blog_list.html?pindex=" + parseInt(pindex + 1);
}
为了方便看当前第几页,共有几页,我们可以在前端页面中添加这样的属性
13.加盐操作(对数据库中的密码进行加密处理)
新建一个PasswordUtils类
package com.example.demo.common;
import org.springframework.util.DigestUtils;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
/**
* 加盐加密
*/
public class PasswordUtils {
public static String encrypt(String password) {
// 1.盐值
String salt = UUID.randomUUID().toString().replace("-",""); //变成32位
// 2.将盐值 + 密码进行md5 得到最终密码
String finalPassword = DigestUtils.md5DigestAsHex((salt + password).getBytes(StandardCharsets.UTF_8));
// 3.将盐值和最终密码进行返回
return salt + "$" + finalPassword;
}
public static void main(String[] args) {
System.out.println(encrypt("123"));
}
}
测试一下得到结果:
/**
* 解密
* password:待验证密码
* dbPassword:数据库中的密码 = 盐值 + 分隔符 + 最终密码
* 对比密码是否相等,返回值布尔
* @param args
*/
// 解密
public static boolean decrypt(String password,String dbPassword) {
// 验证是否正确
if(!StringUtils.hasLength(password) || !StringUtils.hasLength(dbPassword) || dbPassword.length() != 65) {
// 65 = 32(uuid)+32(md5) + 字符$
return false;
}
// 1.得到盐值
String[] dbPasswordArray = dbPassword.split("\\$"); //将$进行转义
if(dbPasswordArray.length != 2) {
return false;
}
// 得到盐值
String salt = dbPasswordArray[0];
// 最终正确密码
String dbFinalPassword = dbPasswordArray[1];
// 2.加密待验证的密码
String finalPassword = DigestUtils.md5DigestAsHex((salt + password).getBytes(StandardCharsets.UTF_8));
// 3.比较是否一致
if(finalPassword.equals(dbFinalPassword)) {
return true;
}
return false;
}
修改UserControl注册密码进行加密
修改UserController中的密码登录密码验证
演示:去注册一个用户:大化,456
查看数据库,会发现密码是被加盐处理过的
此时去到登录页,使用原本admin,123登录发现是登录不了的,因为在此之前admin并没有被加盐处理过,而现在登录页的已经被处理了,所以登录不上去。
换成大化,456,就可以登陆成功了。
14.将session中的值存入到Redis中
使用Redis好处是可以支持分布式部署。
首先在pom.xml中添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
然后去配置application.properties,添加Redis的配置
#redis配置
#设置Redis的连接相关信息
spring.redis.host=写自己的
spring.redis.port=6379
spring.redis.password=
#有16个库,不写的话就是第0个,这里用第一个
spring.redis.database=1
spring.session.store-type=redis
#session 过期时间
server.servlet.session.timeout=1800
spring.session.redis.flush-mode=on_save
spring.session.redis.namespace=spring:session
登录信息的代码不用改变,就可以实现了将登陆的信息存储到Redis中,而不是session里面。我们可以通过Redis客户端进行登录查看。
15.实现拦截器
通过拦截器实现在未登录环境下访问权限的页面是不可以访问的,只能跳转到登录页面。
首先我们已经有了一个全局变量的类
/**
* 全局变量
*/
public class AppVariable {
public static final String SESSION_USERINFO_KEY = "SESSION_USERINFO_KEY";
}
再定义一个普通的类实现自定义拦截器
/**
* 自定义拦截器
*/
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession(false);
if(session != null && session.getAttribute(AppVariable.SESSION_USERINFO_KEY) != null) {
// 说明用户已经登录
return true;
}
return false;
}
}
最后的就是拦截器的规则实现
@Configuration
public class MyLoginConfig implements WebMvcConfigurer {
// 将拦截器注入进来
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
// 拦截所有请求
.addPathPatterns("/**")
// 排除不拦截的
.excludePathPatterns("/editor.md/*")
.excludePathPatterns("/img/*")
.excludePathPatterns("/css/*")
.excludePathPatterns("/js/*")
.excludePathPatterns("/blog_list.html")
.excludePathPatterns("/login.html")
.excludePathPatterns("/reg.html")
.excludePathPatterns("/user/login")
.excludePathPatterns("/user/reg")
.excludePathPatterns("/art/getlistbypage")
.excludePathPatterns("/art/detail");
}
出现的问题,序列化问题,所以我们需要在UserInfo和ArticleInfo中分别实现Serializable接口
至此就完成了博客系统的基本功能实现。
十、将项目部署到云服务器
这里使用的是腾讯云服务器,然后安装的是Centos7版本。
在部署项目之前我们先分析我们在这个项目中使用到了哪些环境搭建,然后通过Xshell连接到服务器,配置相对应的环境。在这个项目中我们需要配置的是首先就是Xshell新建会话,连接至服务器,然后连接成功就可以开始啦,要先安装JDK环境,Tomcat,然后安装MySQL(为了方便,我们这里选择安装的是MariaDB,它和MySQL是兼容的),以及Redis。下面将分步来安装。
1.安装JDK
yum list | grep jdk
然后我们会看到出来好多版本,这里我们选择安装下面的版本。
yum install java-1.8.0-openjdk-devel.x86_64
出现Complete代表成功。
我们也可以通过使用命令查看当前Java版本看是否真成功了。
java -version
至此JDK安装完成。
2.安装Tomcat(前提:JDK安装完毕)
Tomcat使用手动安装,在本地机子上下载好Tomcat的安装包。Tomcat下载
同样的下载好是一个压缩包,在服务器主机上可以新建一个目录tomcat,然后利用zomde功能直接将安装包拖入即可。会自动使用unzip进行解压缩,如果没有那么就需要自己下载。
yum install unzip
将tomcat的启动脚本加上可执行权限,进入该包名目录下,找到bin目录进去
.sh后缀的是Llinux环境下的启动和停止程序
给启动脚本添加可执行权限
可以看见变成了绿色,代表成功。
然后去查看Tomcat的运行状态,这里的话我也不太明白为什么我这个会出现这个原因,在执行命令的时候会报以下信息。我使用的是 ./startup.sh
[root-16-6-centos bin]# ./startup.sh
Cannot find ./catalina.sh
The file is absent or does not have execute permission
This file is needed to run this program
这个问题讲的是我要执行启动的脚本,但是这个脚本它需要catalina.sh的文件,然后我去看目录是有这个文件的,原因就是这个文件没有可执行权限,所以这里也必须先给这个文件添加可执行权限。
添加完成再次查看运行状态,发现是可以的。
./startup.sh
netstat -anp | grep 8080
可以看见Tomcat是已经启动了的。此时我们也可以通过外网去访问Tomcat的页面,这里用的是8080端口,然后可能会出现问题,原因是因为8080端口默认情况下是被防火墙保护起来的,我们需要在服务器控制台将8080端口设置开发即可。
然后去访问是可以访问得到的。
主机ip:8080
3.安装MariaDB
MariaDB是一个开源的关系型数据库,它是MySQL的一个分支,与MySQL兼容。我们这里使用yum进行安装,这个方式安装的话要便捷得多,但是也是需要去配置的。
创建一个目录(可创可不创)
mkdir mysql
进入该目录
cd mysql
使用yum命令开始安装
先安装mariadb服务
yum install -y mariadb-server
安装mariadb命令客户端
yum install -y mariadb
安装mariadb C libaray(C语言客户端库)允许开发者使用C语言编写应用程序,与MariaDB数据库进行交互,执行增删改查等数据库操作。
yum install -y mariadb-libs
安装mariadb开发包
yum install -y mariadb-devel
相关工具安装完成,现在我们可以开始启动服务
systemctl start mariadb
可以设置服务开机自动
systemctl enable mariadb
查看服务状态
systemctl status mariadb
看见绿色的字(active),就说明服务启动已经完成。下面可以开始连接数据库进行操作了。
mysql -uroot //默认密码是为空的
查看版本
select version()
查看数据库
show databases;
这里要注意的是我们为了能够在数据库中去存储中文,我们需要在创建数据库时候设置一下数据库的字符编码为utf8mb4。
create database 自己的数据库名 charset utf8mb4;
4.安装Redis
这里可以去看我的另一篇博客链接哦。
Redis安装使用
准备工作就需完成,我们就可以开始去正式部署我们的项目了。
5.开始部署
首先就是先对自己的代码进行一个小改变。这里主要改变的就是这个数据库密码以及我在这里改变了Redis的端口号(因为如果我使用默认的6379进行部署的话,会报错,所以就选择修改了端口号)
这里的端口号也是需要去到服务器控制台去开放。
改变好之后,我们通过Maven去将项目进行打包成jar包。(默认打包就是jar包),点击demo——》Llifecycle——》双击package即可打包成jar包,可以在target目录底下查看。
出现下面表示证明打包成功
可以在target目录下看见生成的jar包,那么在打包成jar包时候通常会生成两个文件,只有.jar后缀名的是可执行的JAR文件,.jar.original是Spring Boot DevTools的一部分,通常是位于开发环境中的,这个通常不需要上传到服务器,所以我们只需要将可执行的.jar文件进行上传即可。
此刻我们找到该jar包的所在路径,然后进入到Xshell中,连接好服务器,进行操作,我这里的话选择新建了一个目录(springboot1),然后进入该目录底下,直接将jar包拖入进去(zmode功能)。
传输完成,我们在输入ll命令可以看到jar包已经在里面了
所以我们的部署工作就完成了,直接使用命令运行这个Spirng Boot项目就行。
java -jar xxx.jar
然后服务器主机地址:8080/项目的网页目录访问即可。至此我们的部署也就完成了。