搭建后台springboot框架

搭建后台springboot框架

框架说明

这是以前搭建的一套单springboot的后台接口框架,没有用到微服务。这里介绍一下里面使用的模块,后面有时间再完善搭建过程。

技术选型

技术版本备注
Spring Boot2.4.2最新发布稳定版
springboot-jdbc用jdbc实现自定义SQL
Mybatis Plus3.4.2mybatis增强框架
HikariCP数据源
Knife4j2.0.8api文档生成工具
MySQLMySQL数据库连接驱动
ehcache缓存(有需要可以换成redis)
JWT3.4.0JSON WEB TOKEN
hutool-all5.5.8常用工具集
lombok注解生成Java Bean等工具
commons-lang33.9常用工具包
commons-io2.8.0IO工具包
jsoup1.13.1用于防css攻击
p6spy3.8.7SQL日志分析打印
jsoup1.13.1jsoup,用于防css攻击

自定义对象

  • 自定义返回结果
  • 自定义分页对象
  • 自定义异常

mybatis-plus

  • 配置

JDBC

使用springboot封装过的jdbc,还挺好用的

  • 封装自定义SQL查询

xss注入解决

统一异常返回

LocalDateTime 日期适配器

统一处理LocalDateTime的格式

支持swagger

  • 使用knife4j

权限校验

之前弄的权限校验用的是shiro、JWT、Redis,后来觉得还不如自己写,就用JWT和ehcache写了一个权限校验的,自己定义比较灵活。不用Redis是因为有时候项目比较小用不到,用ehcache不用部署,直接用就可以,后续有需要换redis也比较简单。

  • 定义HandlerInterceptor拦截器

    @Slf4j
    public class AuthenticationInterceptor implements HandlerInterceptor {
    
        // jwt的私钥
        @Value("${custom.jwt.secret}")
        private String secret;
    
        @Autowired
        private UserTokenEhcaheService userTokenEhcaheService;
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
            String token = request.getHeader("token");// 从 http 请求头中取出 token
            // 如果不是映射到方法直接通过
            if (!(object instanceof HandlerMethod)) {
                return true;
            }
            HandlerMethod handlerMethod = (HandlerMethod) object;
            Method method = handlerMethod.getMethod();
    
            //检查有没有需要用户权限的注解
            if (method.isAnnotationPresent(PassToken.class)) {
                PassToken passToken = method.getAnnotation(PassToken.class);
                if (passToken.required()) {
                    return true;
                }
            }
    
             执行认证
            // token是否传入
            if (StringUtils.isBlank(token)) {
                throw new LoginResultException("请传入token");
            }
    
            // 验证 token
            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(secret)).build();
            try {
                // 校验token
                jwtVerifier.verify(token);
                // 获取token中的userId
                String userId = JWT.decode(token).getClaim("userId").asString();
                // 获取token中的登录类型
                String loginType = JWT.decode(token).getClaim("loginType").asString();
                UserTokenReturn userToken = userTokenEhcaheService.findByToken(userId, loginType);
                if (userToken == null) {
                    throw new LoginResultException("token已过期");
                } else {
                    // 验证与缓存中的是否是同一个token(保证单点登录)
                    if (userToken.getToken().equals(token)) {
                        log.info("token可用,用户信息是{}",userToken.toString());
                        // 在当前线程中放入用户id,在其他地方使用
                        RequestDetailThreadLocal.getRequestDetail().setUserId(userId);
                        return true;
                    } else {
                        throw new LoginResultException("token已过期");
                    }
                }
            } catch (Exception e) {
                throw new LoginResultException("token校验失败");
            }
        }
    
        @Override
        public void postHandle(HttpServletRequest httpServletRequest,
                               HttpServletResponse httpServletResponse,
                               Object o, ModelAndView modelAndView) throws Exception {
    
        }
    
        @Override
        public void afterCompletion(HttpServletRequest httpServletRequest,
                                    HttpServletResponse httpServletResponse,
                                    Object o, Exception e) throws Exception {
        }
    }
    
    
  • 定义一个service存储token在缓存中

    @Slf4j
    @Service
    @CacheConfig(cacheNames = "user_token_cache")
    public class UserTokenEhcaheService {
    
        @CachePut(key = "#user.userId+'-'+#user.loginType")
        public UserTokenReturn saveToken(UserTokenReturn user){
            log.info("将token存入缓存");
            return user;
        }
    
        @Cacheable(key = "#userId+'-'+#loginType")
        public UserTokenReturn findByToken(String userId,String loginType){
            log.info("获取用户信息失败,没有找到缓存");
            return null;
        }
    
        @CacheEvict(key = "#userId+'-'+#loginType")
        public void deleteByToken(String userId,String loginType) {
            log.info("删除缓存中的token");
        }
    }
    
  • 添加拦截器

    @Configuration
    public class InterceptorConfig implements WebMvcConfigurer {
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry
                    // 鉴权拦截器
                    .addInterceptor(authenticationInterceptor())
                    // 添加需要拦截的路径
                    .addPathPatterns("/**")
                    // 添加需要忽略的路径
                    .excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v2/**", "/error","/swagger-ui.html/**");
    
            // 可以继续添加拦截器
        }
    
        /**
         * 鉴权拦截器
         * @return
         */
        @Bean
        public AuthenticationInterceptor authenticationInterceptor(){
            return new AuthenticationInterceptor();
        }
    
    }
    
  • 增加用户权限的接口类,实现登录和注销

    @RestController
    @RequestMapping("/authentication")
    @Api(tags = "Test001-用户权限")
    public class AuthenticationController {
    
        // jwt的私钥
        @Value("${custom.jwt.secret}")
        private String secret;
    
        @Autowired
        private UserTokenService userTokenService;
    
        @Autowired
        private UserTokenEhcaheService userTokenEhcaheService;
    
        @Autowired
        private UserService userService;
    
        @PassToken
        @PostMapping("/login")
        @ApiOperation("使用账号密码登录")
        public UserTokenReturn login(@RequestBody UserTokenReq userTokenReq) {
            User user = userTokenService.findUserByLoginName(userTokenReq.getLoginName());
            if (user != null && user.getPassword().equals(userTokenReq.getPassword())) {//验证用户名密码,如果登录成功
                // 用户信息
                UserTokenReturn userTokenReturn = new UserTokenReturn();
                userTokenReturn.setUserId(user.getId());
                userTokenReturn.setName(user.getName());
                userTokenReturn.setLoginName(user.getLoginName());
    
                //使用随机数可以多地登录,需要单一登录需要传入登录类型
                userTokenReturn.setLoginType(String.valueOf(new Random().nextInt(1000)));
                // 生成token,token不过期,过期时间由缓存控制(也可生成一个随机字符串也是一样的)
                String token = JWT.create()
                        // 用户id
                        .withClaim("userId", userTokenReturn.getUserId())
                        .withClaim("loginType", userTokenReturn.getLoginType())
                        // 使用随机数,使生成的token不一致
                        .withClaim("random", new Random().nextInt(1000))
                        .sign(Algorithm.HMAC256(secret));
                userTokenReturn.setToken(token);
                // 放入缓存,过期时间由ehcahe的timeToIdleSeconds闲置时间控制,一直访问可以一直不过期
                userTokenEhcaheService.saveToken(userTokenReturn);
                return userTokenReturn;
            }else {
                // 登录失败
               throw new BaseResultException("登录失败,账号或密码错误");
            }
        }
    
        @PostMapping("/logout")
        @ApiOperation("用户注销登录,删除缓存中的token")
        public String logout(@RequestHeader(required = false) String token){
            if(StringUtils.isEmpty(token)) throw new BaseResultException("token不存在");
            // 验证 token
            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(secret)).build();
            try {
                // 校验token
                jwtVerifier.verify(token);
                // 获取token中的userId
                String userId = JWT.decode(token).getClaim("userId").asString();
                // 获取token中的登录类型
                String loginType = JWT.decode(token).getClaim("loginType").asString();
                userTokenEhcaheService.deleteByToken(userId,loginType);
            } catch (Exception e) {
                throw new BaseResultException("token校验失败");
            }
            return "注销登录成功";
        }
    
        @GetMapping("/check")
        @ApiOperation("获取用户信息,测试是否已经登录")
        public User check() {
            RequestDetail requestDetail = RequestDetailThreadLocal.getRequestDetail();
            User user = userService.findById(requestDetail.getUserId());
            return user;
        }
    }
    
  • 启动类加上注解@EnableCaching

  • 将用户信息放在线程中,方便取用,获取用其他方法,比如注解

目录格式

目录格式按照模块分类,每个模块中都有controller、service等,好处是方便复用,坏处是接口类存放位置不统一;这次尝试用这种方式。

----config
----modules
--------example
------------controller
----------------ExampleController
------------dao
----------------mapper
--------------------ExampleMapper
----------------xml
--------------------ExampleMapper.xml
----------------ExampleDao
------------entity
----------------ExampleEntity
------------model
----------------ExampleModel
------------service
----------------ExampleService
----util

因为controller放到模块中,@RestControllerAdvice(basePackages = "xx.xx.xx")定义的基础路径要包含全部接口

因为将xml和Mapper类都放到没模块中,入口函数上的注解@MapperScan不能在用,需要在每个Mapper类上使用注解@Mapper,xml的位置也需要在配置文件中定义,建议配置两个,包含原来的路径

mapper-locations: classpath:mapper/*.xml,classpath:xx/xx/xx/modules/**/dao/xml/*.xml

由于xml放在模块中,打包会导致xml没打进去,需要修改打包的配置

<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
        </resource>
        <resource>
            <directory>src/main/resources/lib</directory>
            <targetPath>/BOOT-INF/lib/</targetPath>
            <includes>
                <include>**/*.jar</include>
            </includes>
        </resource>
    </resources>
</build>

文件上传下载

  • 编写一个简单的文件上传下载功能,存放在本地
  • 有需要可以使用minio,也比较简单

参考文章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
核心功能 文章/图片/视频发布、喜欢、统计阅读次数。 文章标签tag功能、支持按tag分类 文章支持ueditor/markdown编辑器切换(后台配置) 评论功能,支持回复,支持表情。 第三方(微博、QQ)登录。 lucene实现的站内搜索。 响应式布局 支持用户订阅 先看效果图 SpringBoot开发非常美观的java博客系统(包含后台管理功能) SpringBoot开发非常美观的java博客系统(包含后台管理功能) SpringBoot开发非常美观的java博客系统(包含后台管理功能) http://localhost:8080/admin/group/list SpringBoot开发非常美观的java博客系统(包含后台管理功能) SpringBoot开发非常美观的java博客系统(包含后台管理功能)SpringBoot开发非常美观的java博客系统(包含后台管理功能) 技术选型: JDK8 数据库MySQL 主框架 (Spring-boot、Spring-data-jpa) 安全权限 Shiro 搜索工具 Lucene 缓存 Ehcache 视图模板 Freemarker 其它 Jsoup、fastjson jQuery、Seajs Bootstrap 前端框架 UEditor/Markdown编辑器 font-Awesome 字体/图标 准备工作(sql文件在项目里面) 安装 Jdk8 安装 Maven 准备 IDE (如果你不看源码,可以忽略下面的步骤,直接通过Maven编译war包:mvn clean package -DskipTests) IDE 需要配置的东西 编码方式设为UTF-8 配置Maven 设置Jdk8 关于这些配置,网上有一大把的资料,所以此处不再重复。 获取代码导入到IDE 下载代码 导入到IDE的时候请选择以Maven的方式导入 项目配置参考 系统配置手册 配置完毕 启动项目,在控制台看到Mblog加载完毕的信息后,表示启动成功 打开浏览器输入:http//localhost/mblog/ (此处仅是示例,具体具体端口因人而异),访问成功即部署完毕 后台管理的地址是 /admin, 如果你是管理员账号点导航栏的头像会看到"后台管理" 启动成功后,你应该去后台的系统配置里配置你的网站信息等。 常见问题总结 进入系统后, 菜单加载不出来, 那应该是你没有导 db_init.sql 点标签显示乱码, 请设置Tomcat的 URIEncoding 为 UTF-8 项目截图 SpringBoot开发非常美观的java博客系统(包含后台管理功能) 转自:https://gitee.com/mtons/mblog SpringBoot开发非常美观的java博客系统(包含后台管理功能) 注意: 一、java main方式运行mblog-web下的BootApplication.java时抛出异常的解决方案 Unable to start EmbeddedWebApplicationContext due to missing EmbeddedServletContainerFactory bean. SpringBoot开发非常美观的java博客系统(包含后台管理功能) 注释掉后下面图片的这段后,记得maven要重新reimport SpringBoot开发非常美观的java博客系统(包含后台管理功能) SpringBoot开发非常美观的java博客系统(包含后台管理功能) 否则maven依赖不生效还是会抛出以上的异常 二、第三方登录点击后无响应,那是因为第三方开放平台回调的url失效导致,需要你去对应的第三方开放平台注册app后获取对应的oauth帐号 SpringBoot开发非常美观的java博客系统(包含后台管理功能) 三、idea以maven项目导入该项目后,发现没有maven的依赖包时,需要对每个maven module进行clear和install,并且注意maven的依赖顺序 SpringBoot开发非常美观的java博客系统(包含后台管理功能) SpringBoot开发非常美观的java博客系统(包含后台管理功能) 四、访问地址是http://localhost:8080 登录时,帐号,密码只要自己找个密码,然后md5下在更新到db中即可登录成功。 比如:zuidaima 111111,md5后密码是 3931MUEQD1939MQMLM4AISPVNE,md5的java类 SpringBoot开发非常美观的java博客系统(包含后台管理功能) SpringBoot开发非常美观的java博客系统(包含后台管理功能)

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值