访问地址:用户登录
代码获取:基于Spring实现博客项目: Spring项目写博客项目
1.登录页面: 根据用户名和密码,进行登录
2.博客列表页:显示所有博客列表,以及显示登录用户的个人信息 3.博客详情页:显示当前博客的详细信息,以及作者信息
4.博客插入/编辑页
1.登录接口(根据用户名和密码,来判断是否登录成功) 2.根据用户ID,获取用户相关信息
3.获取所有的博客列表
4.根据博客ID,获取博客的详情信息(作者ID) 5.根据博客ID,更新博客内容
6.插入博客
7.删除博客
--建表sql
create database if not exists `java_blog_spring` charset utf8mb4;
--用户表
drop table if exists `java_blog_spring`.`user`;
create table `java_blog_spring`.`user` (
`id` int not null auto_increment,
`user_name` varchar(128) not null,
`password` varchar(128) not null,
`github_url` varchar(128) null,
`delete_flag` tinyint(4) null default 0,
`create_time` timestamp null default current_timestamp(),
primary key (`id`),
unique index `user_name_unique` (`user_name` asc))
engine = innodb default character set = utf8mb4 comment = '⽤户表';
--博客表
drop table if exists `java_blog_spring`.`blog`;
create table `java_blog_spring`.`blog` (
`id` int not null auto_increment,
`title` varchar(200) null,
`content` text null,
`user_id` int(11) null,
`delete_flag` tinyint(4) null default 0,
`create_time` timestamp null default current_timestamp(),
primary key (`id`))
engine = innodb default charset = utf8mb4 comment = '博客表';
--新增用户信息
insert into `java_blog_spring`.`user` (`user_name`, `password`,`github_url`)
values("zhangsan","123456","https://gitee.com/bubble-fish666/class-java4
5");
insert into `java_blog_spring`.`user` (`user_name`, `password`,`github_url
`)values("lisi","123456","https://gitee.com/bubble-fish666/class-java45");
insert into `java_blog_spring`.`blog` (`title`,`content`,`user_id`) values
("第一篇博客","111我是博客正文我是博客正文我是博客正文",1);
insert into `java_blog_spring`.`blog` (`title`,`content`,`user_id`) values
("第二篇博客","222我是博客正文我是博客正文我是博客正文",2);
用户实体类
@Data
public class User {
private Integer id;
private String userName;
private String password;
private String githubUrl;
private Byte deleteFlag;
private Date createTime;
}
博客实体类
@Data
public class Blog {
private Integer id;
private String title;
private String content;
private Integer userId;
private Byte deleteFlag;
private Date createTime;
}
userMapper.java
@Mapper
public interface UserMapper {
@Select("select * from user where id=#{id}")
User selectById(Integer id);
@Select("select * from user where user_name=#{name}")
User selectByName(String name);
}
userMapper.xml
blogMapper.java
@Mapper
public interface BlogMapper {
@Select("select * from blog where delete_flag=0")
List selectAll();
@Select("select * from blog where id=#{id} and delete_flag=0")
Blog selectById(Integer id);
Integer updateBlog(Blog blog);
@Insert("insert into blog(content,title,user_id) values(#{content},#{title},#{userId})")
Integer insertBlog(Blog blog);
}
blogMapper.xml
update blog
title=#{title},
content=#{content},
user_id=#{userId},
delete_flag=#{deleteFlag},
where id=#{id};
application.yml
spring:
profiles:
active: dev
# 日志信息
logging:
file:
path: logs/
level:
root: info
application-dev.yml
server:
port: 8080
# 数据库连接配置
spring:
datasource:
url: jdbc:mysql://localhost:13306/java_blog_spring?characterEncoding=utf8&useSSL=false
username: root
password: woaini520
driver-class-name: com.mysql.cj.jdbc.Driver
# mybatis xml 配置路径
mybatis:
mapper-locations: classpath:mapper/**Mapper.xml
configuration: # 配置打印 MyBatis 执行的 SQL
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true #驼峰转换
application-prod.yml
server:
port: 8080
# 数据库连接配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/java_blog_spring?characterEncoding=utf8&useSSL=false
username: root
password: woaini520
driver-class-name: com.mysql.cj.jdbc.Driver
# mybatis xml 配置路径
mybatis:
mapper-locations: classpath:mapper/**Mapper.xml
configuration:
map-underscore-to-camel-case: true
userMapper
@SpringBootTest
@Slf4j
class UserMapperTest {
@Autowired
UserMapper userMapper;
@Test
void selectById() {
User user = userMapper.selectById(1);
log.info(user.toString());
}
@Test
void selectByName() {
User user = userMapper.selectByName("zhangsan");
log.info(user.toString());
}
}
blogMapper
@SpringBootTest
@Slf4j
class BlogMapperTest {
@Autowired
BlogMapper blogMapper;
@Test
void selectAll() {
List blogs = blogMapper.selectAll();
log.info(blogs.toString());
}
@Test
void selectById() {
Blog blog = blogMapper.selectById(1);
log.info(blog.toString());
}
@Test
void updateBlog() {
Blog blog = new Blog();
blog.setId(1);
blog.setTitle("测试的第一篇博客");
blog.setTitle("测试的第一篇博客的正文内容");
blogMapper.updateBlog(blog);
}
@Test
void insertBlog() {
Blog blog = new Blog();
blog.setTitle("第三篇博客");
blog.setContent("第三篇博客的正文内容");
blog.setUserId(1);
blogMapper.insertBlog(blog);
}
}
@Data
public class Result {
//业务处理状态码 200成功 <=0表示失败 (注意与请求状态码区分)
private Integer code;
//业务返回信息
private String msg;
//业务数据
private Object data;
/**
* 业务处理失败
*
* @param code
* @param message
* @return
*/
public static Result fail(Integer code, String message) {
Result result = new Result();
result.setCode(code);
result.setMsg(message);
result.setData("");
return result;
}
public static Result fail(Integer code, String message, Object data) {
Result result = new Result();
result.setCode(code);
result.setMsg(message);
result.setData(data);
return result;
}
public static Result success(Object data) {
Result result = new Result();
result.setCode(200);
result.setMsg("");
result.setData(data);
return result;
}
public static Result success(String message, Object data) {
Result result = new Result();
result.setCode(200);
result.setMsg(message);
result.setData(data);
return result;
}
}
@ControllerAdvice
public class ErrorAdvice {
@ExceptionHandler
public Result error(Exception e) {
return Result.fail(-1, e.getMessage());
}
}
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof Result) {
return body;
}
if (body instanceof String) {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(Result.success(body));
}
return Result.success(body);
}
}
前端代码在这取:基于Spring实现博客项目: Spring项目写博客项目
[请求]
/blog/getlist
[响应]
[
{
blogId: 1,
title: "第⼀篇博客",
content: "博客正⽂",
userId: 1,
postTime: "2021-07-07 12:00:00"
},
{
blogId: 2,
title: "第⼆篇博客",
content: "博客正⽂",
userId: 1,
postTime: "2021-07-07 12:10:00"
},
UserService
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private BlogMapper blogMapper;
public User selectById(Integer id) {
return userMapper.selectById(id);
}
public User selectByName(String name) {
return userMapper.selectByName(name);
}
public User getAuthorInfoByBlogId(Integer id) {
Blog blog = blogMapper.selectById(id);
if (blog == null || blog.getUserId() < 0) {
return null;
}
return userMapper.selectById(blog.getUserId());
}
}
BlogService
@Service
public class BlogService {
@Autowired
private BlogMapper blogMapper;
public List getAll() {
return blogMapper.selectAll();
}
public Blog getBlogById(Integer id) {
return blogMapper.selectById(id);
}
public Integer updateBlog(Blog blog) {
return blogMapper.updateBlog(blog);
}
public Integer addBlog(Blog blog) {
return blogMapper.insertBlog(blog);
}
}
BlogController
@RestController
@RequestMapping("/blog")
public class BlogController {
@Autowired
BlogService blogService;
@RequestMapping("/getlist")
public List getBlogList() {
return blogService.getAll();
}
@RequestMapping("/getBlogDetail")
public Result getBlogDetail(Integer id, HttpSession httpSession) {
if (id == null || id < 0) {
return Result.fail(-1, "非法的参数");
}
Blog blog = blogService.getBlogById(id);
User user = (User) httpSession.getAttribute(Constants.USER_INFO_SESSION);
if (blog.getUserId() == user.getId()) {
blog.setIsLoginUser(true);
}
return Result.success(blog);
}
@RequestMapping("/add")
public Result addBlog(String title, String content, HttpSession httpSession) {
if (!StringUtils.hasLength(title) || !StringUtils.hasLength(content)) {
return Result.fail(-1, "内容不能为空");
}
User user = (User) httpSession.getAttribute(Constants.USER_INFO_SESSION);
if (user == null || user.getId() < 0) {
return Result.fail(-1, "用户不存在");
}
Blog blog = new Blog();
blog.setContent(content);
blog.setTitle(title);
blog.setUserId(user.getId());
blogService.addBlog(blog);
return Result.success(true);
}
@RequestMapping("/update")
public Result updateBlog(Blog blog) {
if (!StringUtils.hasLength(blog.getContent()) || !StringUtils.hasLength(blog.getTitle()) || blog.getId() == null) {
return Result.fail(-1, "内容不能为空");
}
blogService.updateBlog(blog);
return Result.success(true);
}
@RequestMapping("/delete")
public Result deleteBlog(@RequestParam("id") Integer blogId) {
if (blogId == null || blogId < 0) {
return Result.fail(-1, "博客id不合法或者为空");
}
Blog blog = new Blog();
blog.setId(blogId);
blog.setDeleteFlag((byte) 1);
blogService.updateBlog(blog);
return Result.success(true);
}
}
UserController
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
UserService userService;
@RequestMapping("/login")
public Result login(HttpServletRequest request, String username, String password) {
//参数校验
if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
return Result.fail(-2, "账号或密码不能为空");
}
//密码校验
User user = userService.selectByName(username);
if (user == null || !user.getPassword().equals(password)) {
return Result.fail(-3, "用户名不存在或者密码错误");
}
//参数返回
user.setPassword("");
HttpSession session = request.getSession(true);
session.setAttribute(Constants.USER_INFO_SESSION, user);
return Result.success("登陆成功");
}
@RequestMapping("/getUserInfo")
public Result getUserInfo(HttpSession session) {
if (session == null || session.getAttribute(Constants.USER_INFO_SESSION) == null) {
return Result.fail(-1, "用户未登录");
}
return Result.success(session.getAttribute(Constants.USER_INFO_SESSION));
}
@RequestMapping("/getAuthorInfo")
public Result getAuthorInfoByBlogId(@RequestParam("id") Integer blogId) {
if (blogId == null || blogId < 0) {
return Result.fail(-1, "id参数错误");
}
User user = userService.getAuthorInfoByBlogId(blogId);
if (user == null) {
return Result.fail(-1, "不存在文章作者");
}
user.setPassword("");
return Result.success(user);
}
@RequestMapping("/logout")
public Result logout(HttpSession session) {
session.removeAttribute(Constants.USER_INFO_SESSION);
return Result.success(true);
}
}
@Component
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(Constants.USER_INFO_SESSION) == null) {
response.setStatus(401);
return false;
}
return true;
}
}
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
private final List excludes = Arrays.asList(
"/**/*.html",
"/blog-editormd/**",
"/css/**",
"/js/**",
"/pic/**",
"/user/login"
);
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor).
addPathPatterns("/**"). //拦截所有路径
excludePathPatterns(excludes);
}
}
在blog_list.html和blog_detail的ajax请求添加
error: function (error) {
if (error != null && error.status == 401) {
location.assign("/blog_login.html");
}
}
有些代码重复出现,可以抽象为一个函数,我们放在./js/common.js中
function logout() {
$("#logout").click(function () {
$.ajax({
type: "get",
url: "/user/logout",
success: function (result) {
if (result != null && result.data == true) {
location.assign("/blog_login.html")
}
},
})
})
}
先来个前置知识:日期格式化
方式一:将日期格式改为(java.sql.Date适用)
private Timestamp createTime;
然后引入js包
function formatDate(time) {
var date = new Date(time);
var year = date.getFullYear(),
month = date.getMonth() + 1,//月份是从0开始的
day = date.getDate(),
hour = date.getHours(),
min = date.getMinutes(),
sec = date.getSeconds();
var newTime = year + '-' +
(month < 10 ? '0' + month : month) + '-' +
(day < 10 ? '0' + day : day) + ' ' +
(hour < 10 ? '0' + hour : hour) + ':' +
(min < 10 ? '0' + min : min) + ':' +
(sec < 10 ? '0' + sec : sec);
return newTime;
}
直接进行转化即可
formatDate(blog.createTime)
方式二:(java.util.Date适用)
工具类
public class DateUtils {
public static String formatDate(Date date) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return simpleDateFormat.format(date);
}
}
博客实体类
@Data
public class Blog {
private Integer id;
private String title;
private String content;
private Integer userId;
private Byte deleteFlag;
private Date createTime;
public String getCreateTime() {
return DateUtils.formatDate(createTime);
}
}
此时就不需要多余的操作了
finalHtml += ''+blog.createTime+'';
之后javascrip代码
假设我们的数据库被黑客进行劫持,那么用户的一些重要信息(比如密码,身份证号)就可能被获取,这样对用户是十分不利的,因此我们需要对数据库中一些重要的信息进行加密存储.
目前有许多的加密算法,包括可逆加密和非可逆加密,可逆加密在http和https的时候就有使用,通过密钥就可以使明文变为密文,同时也可以让密文变为明文,但是非可逆加密就不同了,非可逆加密只能使明文加密为密文,不能从密文变为明文,一般在数据库中我们采用非可逆加密.
常用的非可逆加密有MD5加密,通过对明文加密,可以得到长度固定的MD5值,MD5加密就可以得到32位或者16位的加密后的结果.
Java中使用MD5加密
@SpringBootTest
class BlogSpringApplicationTests {
@Test
void contextLoads() {
String finalPassword = DigestUtils.md5DigestAsHex("123456".getBytes());
System.out.println("第一次加密:" + finalPassword);
finalPassword = DigestUtils.md5DigestAsHex("123456".getBytes());
System.out.println("第二次加密:" + finalPassword);
finalPassword = DigestUtils.md5DigestAsHex("123456".getBytes());
System.out.println("第三次加密:" + finalPassword);
}
}
可以观察到:MD5加密后得到的值是都是一样的,并且无论进行多少次MD5加密(对产生的MD5值多次进行加密),得到的结果都是一样的.
那么这样会不会产生安全问题呢?当然会!虽然说MD5加密是不可逆的加密,但是可以通过提前生成对应的明文和密文对应的表,然后通过需要破解的密文与之前生成表的密文进行对应,从而找到相应的明文,但是这种方法是十分低效的,但是也可以进行破解明文,尤其对于密码不复杂且短的很容易破解,那么怎么样可以避免这种情况呢?
首先可以在用户端进行,我们在设置的密码的时候,通常网站都要求需要字母与数字的组合,并且长度都要求大于一定的值,这样密码的安全等级很高,不容易被破解,(比如纯数字组合就10种,字母加数字就有26+10种,大小写字母加数字组合就有26*2+10种,长度越长组合自然也就越多).
接下来通过服务端通过加盐的方式存储密码.
假设用户的密码安全很低,如果在存储的时候将密码与随机的字符串的进行拼接,然后再通过MD5加密的方式进行存储的话,同样也可以时密码难以破解,并且可以通过生成多个不同的随机字符串,可以解决MD5不能实现多次加密的问题.随机生成的字符串也要存储到数据库中,通常与生成的MD5数值拼接存储.
代码展示,通过UUID生成随机的字符串.
@Test
void contextLoads() {
String salt = UUID.randomUUID().toString();
String finalPassword = DigestUtils.md5DigestAsHex(("123456" + salt).getBytes());
System.out.println("第一次salt:" + salt);
System.out.println("第一次加密:" + finalPassword);
salt = UUID.randomUUID().toString();
finalPassword = DigestUtils.md5DigestAsHex(("123456" + salt).getBytes());
System.out.println("第二次salt:" + salt);
System.out.println("第二次加密:" + finalPassword);
salt = UUID.randomUUID().toString();
finalPassword = DigestUtils.md5DigestAsHex(("123456" + salt).getBytes());
System.out.println("第三次salt:" + salt);
System.out.println("第三次加密:" + finalPassword);
}
我们不希望产生的salt含有特殊字符,因此我们可以通过以下的方式将"-"去掉
@Test
void contextLoads() {
String salt = UUID.randomUUID().toString().replace("-", "");
String finalPassword = DigestUtils.md5DigestAsHex(("123456" + salt).getBytes());
System.out.println("第一次salt:" + salt);
System.out.println("第一次加密:" + finalPassword);
salt = UUID.randomUUID().toString().replace("-", "");
finalPassword = DigestUtils.md5DigestAsHex(("123456" + salt).getBytes());
System.out.println("第二次salt:" + salt);
System.out.println("第二次加密:" + finalPassword);
salt = UUID.randomUUID().toString().replace("-", "");
finalPassword = DigestUtils.md5DigestAsHex(("123456" + salt).getBytes());
System.out.println("第三次salt:" + salt);
System.out.println("第三次加密:" + finalPassword);
}
public class SecurityUtils {
/**
* 将密码进行加密
* 根据明文,返回密文(salt+MD5)
*
* @param password
* @return
*/
public static String encry(String password) {
//生成盐值
String salt = UUID.randomUUID().toString().replace("-", "");
//进行加密
String finalPassword = DigestUtils.md5DigestAsHex((password + salt).getBytes());
return salt + finalPassword;
}
/**
* @param inputPassword 输入的密码
* @param finalPassword 数据库存储的密码
* @return 输入的密码和存储的密码是否相同
*/
public static boolean decrypt(String inputPassword, String finalPassword) {
if (!StringUtils.hasLength(inputPassword) || !StringUtils.hasLength(finalPassword)) {
return false;
}
if (finalPassword.length() != 64) {
return false;
}
String salt = finalPassword.substring(0, 32);
String password = DigestUtils.md5DigestAsHex((inputPassword + salt).getBytes());
return (salt + password).equals(finalPassword);
}
}
修改密码验证的接口
@RequestMapping("/login")
public Result login(HttpServletRequest request, String username, String password) {
//参数校验
if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
return Result.fail(-2, "账号或密码不能为空");
}
//密码校验
User user = userService.selectByName(username);
if (user == null || !SecurityUtils.decrypt(password, user.getPassword())) {
return Result.fail(-3, "用户名不存在或者密码错误");
}
//参数返回
user.setPassword("");
HttpSession session = request.getSession(true);
session.setAttribute(Constants.USER_INFO_SESSION, user);
return Result.success("登陆成功");
}
dev
dev
true
prod
prod
false
application.yml文件
此时打包即可
可以将sql语句变成一个文件,然后执行下面的
source /root/java78/create.sql
打包完成之后,将jar包拖拽到linux服务器上
后台启动项目
nohup java -jar Blog_Spring-0.0.1-SNAPSHOT.jar &
查看日志
cd logs/
tail -f spring.log
终止当前的服务
ps -ef | grep [ ]
注意:如果开启多个服务,需要开端口,给防火墙添加端口号
查看防火墙状态(如果没开启,建议开启,不开启可以直接访问,开启了需要进行已下的操作访问)
systemctl status firewalld
启动防火墙和关闭防火墙
systemctl start firewalld
systemctl stop firewalld
查看开放的端口号
firewall-cmd --list-ports
开启8080端口
firewall-cmd --permanent --add-port=8080/tcp
重启防火墙
firewall-cmd --reload
设置开机启动
systemctl enable firewalld
添加安全组
否则无法正常访问