前面我们已经完成了,首页文章的加载,登录,注册,退出等各个小功能,接下来我们来看一些具体的情况
1. 涉及到的表和对应的映射表
~~~sql
CREATE TABLE `blog`.`ms_comment` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
`content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`create_date` bigint(0) NOT NULL,
`article_id` int(0) NOT NULL,
`author_id` bigint(0) NOT NULL,
`parent_id` bigint(0) NOT NULL,
`to_uid` bigint(0) NOT NULL,
`level` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `article_id`(`article_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
~~~
~~~java
package com.mszlu.blog.dao.pojo;
import lombok.Data;
@Data
public class Comment {
private Long id;
private String content;
private Long createDate;
private Long articleId;
private Long authorId;
private Long parentId;
private Long toUid;
private Integer level;
}
~~~
2. 接口说明
接口url:/comments/article/{id}
请求方式:GET
请求参数:
| 参数名称 | 参数类型 | 说明 |
| -------- | -------- | ------------------ |
| id | long | 文章id(路径参数) |
| | | |
| | | |
返回数据:
~~~json
{
"success": true,
"code": 200,
"msg": "success",
"data": [
{
"id": 53,
"author": {
"nickname": "李四",
"avatar": "http://localhost:8080/static/img/logo.b3a48c0.png",
"id": 1
},
"content": "写的好",
"childrens": [
{
"id": 54,
"author": {
"nickname": "李四",
"avatar": "http://localhost:8080/static/img/logo.b3a48c0.png",
"id": 1
},
"content": "111",
"childrens": [],
"createDate": "1973-11-26 08:52",
"level": 2,
"toUser": {
"nickname": "李四",
"avatar": "http://localhost:8080/static/img/logo.b3a48c0.png",
"id": 1
}
}
],
"createDate": "1973-11-27 09:53",
"level": 1,
"toUser": null
}
]
}
~~~
3. 配置Controller
## 1.2 Controller
~~~java
import com.mszlu.blog.service.CommentsService;
import com.mszlu.blog.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("comments")
public class CommentsController {
@Autowired
private CommentsService commentsService;
@GetMapping("article/{id}")
public Result comments(@PathVariable("id") Long articleId){
return commentsService.commentsByArticleId(articleId);
}
}
4.编写对应的Service和ServiceImpl类
## 1.3 Service
~~~java
package com.mszlu.blog.service;
import com.mszlu.blog.vo.Result;
public interface CommentsService {
Result commentsByArticleId(Long articleId);
}
~~~
~~~java
package com.mszlu.blog.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mszlu.blog.dao.mapper.CommentMapper;
import com.mszlu.blog.dao.pojo.Comment;
import com.mszlu.blog.service.CommentsService;
import com.mszlu.blog.service.SysUserService;
import com.mszlu.blog.vo.CommentVo;
import com.mszlu.blog.vo.Result;
import com.mszlu.blog.vo.UserVo;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class CommentsServiceImpl implements CommentsService {
@Autowired
private CommentMapper commentMapper;
@Autowired
private SysUserService sysUserService;
public CommentVo copy(Comment comment){
CommentVo commentVo = new CommentVo();
BeanUtils.copyProperties(comment,commentVo);
//时间格式化
commentVo.setCreateDate(new DateTime(comment.getCreateDate()).toString("yyyy-MM-dd HH:mm"));
Long authorId = comment.getAuthorId();
UserVo userVo = sysUserService.findUserVoById(authorId);
commentVo.setAuthor(userVo);
//评论的评论
List<CommentVo> commentVoList = findCommentsByParentId(comment.getId());
commentVo.setChildrens(commentVoList);
if (comment.getLevel() > 1) {
Long toUid = comment.getToUid();
UserVo toUserVo = sysUserService.findUserVoById(toUid);
commentVo.setToUser(toUserVo);
}
return commentVo;
}
private List<CommentVo> findCommentsByParentId(Long id) {
LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Comment::getParentId,id);
queryWrapper.eq(Comment::getLevel,2);
List<Comment> comments = this.commentMapper.selectList(queryWrapper);
return copyList(comments);
}
public List<CommentVo> copyList(List<Comment> commentList){
List<CommentVo> commentVoList = new ArrayList<>();
for (Comment comment : commentList) {
commentVoList.add(copy(comment));
}
return commentVoList;
}
@Override
public Result commentsByArticleId(Long articleId) {
LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Comment::getArticleId,articleId);
queryWrapper.eq(Comment::getLevel,1);
List<Comment> comments = commentMapper.selectList(queryWrapper);
return Result.success(copyList(comments));
}
}
~~~
这里补充一下这个CommentVo和UserVo类为:
@Data
public class CommentVo {
private Long id;
private UserVo author;
private String content;
private List childrens;
private String createDate;
private Integer level;
private UserVo toUser;
}
@Data
public class UserVo {
private String nickname;
private String avatar;
private Long id;
}
5. 然后就是在SYSUserService里面添加方法
~~~java
UserVo findUserVoById(Long id);
~~~
~~~java
@Override
public UserVo findUserVoById(Long id) {
SysUser sysUser = sysUserMapper.selectById(id);
if (sysUser == null){
sysUser = new SysUser();
sysUser.setId(1L);
sysUser.setAvatar("/static/img/logo.b3a48c0.png");
sysUser.setNickname("码神之路");
}
UserVo userVo = new UserVo();
userVo.setAvatar(sysUser.getAvatar());
userVo.setNickname(sysUser.getNickname());
userVo.setId(sysUser.getId());
return userVo;
}
~~~
1. 就在本项目blog-parent项目中创建blog-admin项目
2. 导入依赖
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>blog-parent2artifactId>
<groupId>com.mszlugroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>blog-adminartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-loggingartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-log4j2artifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-mailartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.76version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
dependency>
<dependency>
<groupId>commons-collectionsgroupId>
<artifactId>commons-collectionsartifactId>
<version>3.2.2version>
dependency>
<dependency>
<groupId>commons-codecgroupId>
<artifactId>commons-codecartifactId>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.3version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>joda-timegroupId>
<artifactId>joda-timeartifactId>
<version>2.10.10version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
dependencies>
project>
~~~
3. application.properties文件的配置
~~~properties
server.port=8889
spring.application.name=mszlu_admin_blog
#数据库的配置
# datasource
spring.datasource.url=jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=UTF-8&serverTimeZone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#mybatis-plus
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.global-config.db-config.table-prefix=ms_
# 上传文件总的最大值
spring.servlet.multipart.max-request-size=20MB
# 单个文件的最大值
spring.servlet.multipart.max-file-size=2MB
4. 编写之前一样的Mybatis-plus和启动类
~~~java
package com.mszlu.blog.admin.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.mszlu.blog.admin.mapper")
public class MybatisPlusConfig {
//分页插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
~~~
## 1.3 启动类
~~~java
package com.mszlu.blog.admin;
import com.alibaba.fastjson.annotation.JSONField;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AdminApp {
public static void main(String[] args) {
SpringApplication.run(AdminApp.class,args);
}
}
5. 创建表和对应的类
~~~sql
CREATE TABLE `blog`.`ms_admin` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
~~~
~~~sql
CREATE TABLE `blog`.`ms_permission` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
~~~
~~~sql
CREATE TABLE `blog`.`ms_admin_permission` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
`admin_id` bigint(0) NOT NULL,
`permission_id` bigint(0) NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
~~~
@Data
public class Permission {
//为什么这里又加了注解了呢?
//因为我们一般在编写权限表的时候,是不需要使用分布式Id的,所有这里设置为自增长
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private String path;
private String description;
}
~~~
6. 编写统一返回参数的类Result
package com.mszlu.blog.admin.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Result {
private boolean success;
private int code;
private String msg;
private Object data;
public static Result success(Object data){
return new Result(true,200,"success",data);
}
public static Result fail(int code, String msg){
return new Result(false,code,msg,null);
}
}
7. 导入前端代码在Resource下面的static
8. 由于根据前端代码可知,本案例中不能直接返回Result,编写本例返回参数
@Data
public class PageResult<T> {
private List<T> list;
private Long total;
}
~~~
1. 编写Controller
@RestController
@RequestMapping("admin")
public class AdminController {
@Autowired
private PermissionService permissionService;
@PostMapping("permission/permissionList")
public Result permissionList(@RequestBody PageParam pageParam){
return permissionService.listPermission(pageParam);
}
@PostMapping("permission/add")
public Result add(@RequestBody Permission permission){
return permissionService.add(permission);
}
@PostMapping("permission/update")
public Result update(@RequestBody Permission permission){
return permissionService.update(permission);
}
@GetMapping("permission/delete/{id}")
public Result delete(@PathVariable("id") Long id){
return permissionService.delete(id);
}
}
本例中涉及到的参数类
@Data
public class PageParam {
private Integer currentPage;
private Integer pageSize;
private String queryString;
}
~~~
2. 编写Mapper层
public interface PermissionMapper extends BaseMapper<Permission> {
}
3. 编写Service层
@Service
public class PermissionService {
@Autowired
private PermissionMapper permissionMapper;
public Result listPermission(PageParam pageParam){
Page<Permission> page = new Page<>(pageParam.getCurrentPage(),pageParam.getPageSize());
LambdaQueryWrapper<Permission> queryWrapper = new LambdaQueryWrapper<>();
if (StringUtils.isNotBlank(pageParam.getQueryString())) {
queryWrapper.eq(Permission::getName,pageParam.getQueryString());
}
Page<Permission> permissionPage = this.permissionMapper.selectPage(page, queryWrapper);
PageResult<Permission> pageResult = new PageResult<>();
pageResult.setList(permissionPage.getRecords());
pageResult.setTotal(permissionPage.getTotal());
return Result.success(pageResult);
}
public Result add(Permission permission) {
this.permissionMapper.insert(permission);
return Result.success(null);
}
public Result update(Permission permission) {
this.permissionMapper.updateById(permission);
return Result.success(null);
}
public Result delete(Long id) {
this.permissionMapper.deleteById(id);
return Result.success(null);
}
}
4. 测试
1. 添加依赖
org.springframework.boot
spring-boot-starter-security
2. 然后就是配置
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//这个不用说,自然是配置密码的
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
//什么密码哪些呀,你要存储的时候,在这里暂时给你
public static void main(String[] args) {
//加密策略 MD5 不安全 彩虹表 MD5 加盐
String mszlu = new BCryptPasswordEncoder().encode("mszlu");
System.out.println(mszlu);
}
//这个暂时忽略
@Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
//接下来就一系列的配置了
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() //开启登录认证
// .antMatchers("/user/findAll").hasRole("admin") //访问接口需要admin的角色
.antMatchers("/css/**").permitAll()
.antMatchers("/img/**").permitAll()
.antMatchers("/js/**").permitAll()
.antMatchers("/plugins/**").permitAll()
.antMatchers("/admin/**").access("@authService.auth(request,authentication)") //自定义service 来去实现实时的权限认证
.antMatchers("/pages/**").authenticated()//登录后即可访问
.and().formLogin()
.loginPage("/login.html") //自定义的登录页面
.loginProcessingUrl("/login") //登录处理接口
.usernameParameter("username") //定义登录时的用户名的key 默认为username
.passwordParameter("password") //定义登录时的密码key,默认是password
.defaultSuccessUrl("/pages/main.html")
.failureUrl("/login.html")
.permitAll() //通过 不拦截,更加前面配的路径决定,这是指和登录表单相关的接口 都通过
.and().logout() //退出登录配置
.logoutUrl("/logout") //退出登录接口
.logoutSuccessUrl("/login.html")
.permitAll() //退出登录的接口放行
.and()
.httpBasic()
.and()
.csrf().disable() //csrf关闭 如果自定义登录 需要关闭
.headers().frameOptions().sameOrigin();
}
}
1. 编写Hr类
@Data
public class Admin {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String password;
}
2. 编写上面类的Service
@Service
public class AdminService {
@Autowired
private AdminMapper adminMapper;
@Autowired
private PermissionMapper permissionMapper;
public Admin findAdminByUserName(String username){
LambdaQueryWrapper<Admin> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Admin::getUsername,username).last("limit 1");
Admin adminUser = adminMapper.selectOne(queryWrapper);
return adminUser;
}
public List<Permission> findPermissionsByAdminId(Long adminId){
return permissionMapper.findPermissionsByAdminId(adminId);
}
}
3. 还有Mapper层
这里的使用的Select注解进行编写的SQL语句,也可以创建Mapper.xml文件
public interface AdminMapper extends BaseMapper<Admin> {
@Select("select * from ms_permission where id in (select permission_id from ms_admin_permission where admin_id=#{adminId})")
List<Permission> findPermissionsByAdminId(Long adminId);
}
4. 编写我们要的SecurityUserService
@Component
@Slf4j
public class SecurityUserService implements UserDetailsService {
@Autowired
private AdminService adminService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("username:{}",username);
//当用户登录的时候,springSecurity 就会将请求 转发到此
//根据用户名 查找用户,不存在 抛出异常,存在 将用户名,密码,授权列表 组装成springSecurity的User对象 并返回
Admin adminUser = adminService.findAdminByUserName(username);
if (adminUser == null){
throw new UsernameNotFoundException("用户名不存在");
}
ArrayList<GrantedAuthority> authorities = new ArrayList<>();
UserDetails userDetails = new User(username,adminUser.getPassword(), authorities);
//剩下的认证 就由框架帮我们完成
return userDetails;
}
public static void main(String[] args) {
System.out.println(new BCryptPasswordEncoder().encode("123456"));
}
}
5. 和对应的Mapper
public interface PermissionMapper extends BaseMapper<Permission> {
public interface PermissionMapper extends BaseMapper<Permission> {
@Select("select * from ms_permission where id in (select permission_id from ms_admin_permission where admin_id=#{adminId})")
List<Permission> findPermissionsByAdminId(Long adminId);
}
}
6. 编写AuthService的(先暂时这样)
@Service
public class authService {
public boolean auth(HttpServletRequest request, List<GrantedAuthority> authorities) {
return true;
}
}
7.这里总结一下,这个逻辑实现
1.编写AuthService的
@Service
@Slf4j
public class AuthService {
@Autowired
private AdminService adminService;
public boolean auth(HttpServletRequest request, Authentication authentication){
String requestURI = request.getRequestURI();
log.info("request url:{}", requestURI);
//true代表放行 false 代表拦截
Object principal = authentication.getPrincipal();
if (principal == null || "anonymousUser".equals(principal)){
//未登录
return false;
}
UserDetails userDetails = (UserDetails) principal;
String username = userDetails.getUsername();
Admin admin = adminService.findAdminByUserName(username);
if (admin == null){
return false;
}
if (admin.getId() == 1){
//认为是超级管理员
return true;
}
List<Permission> permissions = adminService.findPermissionsByAdminId(admin.getId());
requestURI = StringUtils.split(requestURI,'?')[0];
for (Permission permission : permissions) {
if (requestURI.equals(permission.getPath())){
log.info("权限通过");
return true;
}
}
return false;
}
}
2. 编写MySimpleGrantedAuthority类
public class MySimpleGrantedAuthority implements GrantedAuthority {
private String authority;
private String path;
public MySimpleGrantedAuthority(){}
public MySimpleGrantedAuthority(String authority){
this.authority = authority;
}
public MySimpleGrantedAuthority(String authority,String path){
this.authority = authority;
this.path = path;
}
@Override
public String getAuthority() {
return authority;
}
public String getPath() {
return path;
}
}
1. JWT技术 (本次使用的是JWT+Redis)
token令牌的登录方式,访问认证的速度快,session共享,安全性Redis做了令牌和用户信息进行对应的管理,
1.进一步增加了安全性
2. 登录用户做了缓存,
3. 3.灵活操控
2. threadLocal使用了保存用户信息,请求的线程之内,可以随之获取登录的用户,做了线程隔离
3. 在使用完ThreadLocal之后,做了Value删除,防止了内存泄漏
4. 线程安全:update table set value= newVale where id= 1 and value = oldValue
5. 线程池,应用非常关,面试七个核心参数(对当前主业务流程无影响的,放入线程池中加快速度),还有比如
1).记录日志
6. 权限系统呀,在这个权限系统中,基本也可以投入使用的
7. 还有什么同样异常,同一缓存,同一日志处理。