Java后端接口开发
1、前言
从零开始搭建一个项目骨架,最好选择合适,熟悉的技术,并且在未来易拓展,适合微服务化体系等。所以一般以Springboot作为我们的框架基础,这是离不开的了。
然后数据层,我们常用的是Mybatis,易上手,方便维护。但是单表操作比较困难,特别是添加字段或减少字段的时候,比较繁琐,所以这里我推荐使用Mybatis Plus(mp.baomidou.com/),为简化开发而生,只… CRUD 操作,从而节省大量时间。
作为一个项目骨架,权限也是我们不能忽略的,Shiro配置简单,使用也简单,所以使用Shiro作为我们的的权限。
考虑到项目可能需要部署多台,这时候我们的会话等信息需要共享,Redis是现在主流的缓存中间件,也适合我们的项目。
然后因为前后端分离,所以我们使用jwt作为我们用户身份凭证。
ok,我们现在就开始搭建我们的项目脚手架!
技术栈:
- SpringBoot
- mybatis plus
- shiro
- lombok
- redis
- hibernate validatior
- jwt
2、新建Springboot项目
这里,我们使用IDEA来开发我们项目,新建步骤比较简单,我们就不截图了。
开发工具与环境:
- idea
- mysql
- jdk 8
- maven3.3.9
新建好的项目结构如下,SpringBoot版本使用的目前最新的2.2.6.RELEASE版本
pom的jar包导入如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
- devtools:项目的热加载重启插件
- lombok:简化代码的工具
3、整合mybatis plus
接下来,我们来整合mybatis plus,让项目能完成基本的增删改查操作。
第一步:导入jar包
pom中导入mybatis plus的jar包,因为后面会涉及到代码生成,所以我们还需要导入页面模板引擎,这里我们用的是freemarker。
<!--mp-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--mp代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.2.0</version>
</dependency>
第二步:然后去写配置文件
# DataSource Config
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/vueblog?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: admin
mybatis-plus:
mapper-locations: classpath*:/mapper/**Mapper.xml
上面除了配置数据库的信息,还配置了myabtis plus的mapper的xml文件的扫描路径,这一步不要忘记了。 第三步:开启mapper接口扫描,添加分页插件
新建一个包:通过@mapperScan注解指定要变成实现类的接口所在的包,然后包下面的所有接口在编译之后都会生成相应的实现类。PaginationInterceptor是一个分页插件。
- com.markerhub.config.MybatisPlusConfig
@Configuration
@EnableTransactionManagement
@MapperScan("com.markerhub.mapper")
public class MybatisPlusConfig {
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
return paginationInterceptor;
}
}
第四步:代码生成
如果你没再用其他插件,那么现在就已经可以使用mybatis plus了,官方给我们提供了一个代码生成器,然后我写上自己的参数之后,就可以直接根据数据库表信息生成entity、service、mapper等接口和实现类。
- com.markerhub.CodeGenerator
因为代码比较长,就不贴出来了,在代码仓库上看哈!
首先我在数据库中新建了一个user表:
CREATE TABLE `m_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(64) DEFAULT NULL,
`avatar` varchar(255) DEFAULT NULL,
`email` varchar(64) DEFAULT NULL,
`password` varchar(64) DEFAULT NULL,
`status` int(5) NOT NULL,
`created` datetime DEFAULT NULL,
`last_login` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `UK_USERNAME` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `m_blog` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) NOT NULL,
`title` varchar(255) NOT NULL,
`description` varchar(255) NOT NULL,
`content` longtext,
`created` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP,
`status` tinyint(4) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4;
INSERT INTO `vueblog`.`m_user` (`id`, `username`, `avatar`, `email`, `password`, `status`, `created`, `last_login`) VALUES ('1', 'markerhub', 'https://image-1300566513.cos.ap-guangzhou.myqcloud.com/upload/images/5a9f48118166308daba8b6da7e466aab.jpg', NULL, '96e79218965eb72c92a549dd5a330112', '0', '2020-04-20 10:44:01', NULL);
运行CodeGenerator的main方法,输入表名:m_user,生成结果如下:
得到:
简洁!方便!经过上面的步骤,基本上我们已经把mybatis plus框架集成到项目中了。
ps:额,注意一下m_blog表的代码也生成一下哈。
在UserController中写个测试:
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
UserService userService;
@GetMapping("/{id}")
public Object test(@PathVariable("id") Long id) {
return userService.getById(id);
}
}
3、统一结果封装
这里我们用到了一个Result的类,这个用于我们的异步统一返回的结果封装。一般来说,结果里面有几个要素必要的
- 是否成功,可用code表示(如200表示成功,400表示异常)
- 结果消息
- 结果数据
所以可得到封装如下:
- com.markerhub.common.lang.Result
@Data
public class Result implements Serializable {
private String code;
private String msg;
private Object data;
public static Result succ(Object data) {
Result m = new Result();
m.setCode("0");
m.setData(data);
m.setMsg("操作成功");
return m;
}
public static Result succ(String mess, Object data) {
Result m = new Result();
m.setCode("0");
m.setData(data);
m.setMsg(mess);
return m;
}
public static Result fail(String mess) {
Result m = new Result();
m.setCode("-1");
m.setData(null);
m.setMsg(mess);
return m;
}
public static Result fail(String mess, Object data) {
Result m = new Result();
m.setCode("-1");
m.setData(data);
m.setMsg(mess);
return m;
}
}
4、整合shiro+jwt,并会话共享
考虑到后面可能需要做集群、负载均衡等,所以就需要会话共享,而shiro的缓存和会话信息,我们一般考虑使用redis来存储这些数据,所以,我们不仅仅需要整合shiro,同时也需要整合redis。在开源的项目中,我们找到了一个starter可以快速整合shiro-redis,配置简单,这里也推荐大家使用。
而因为我们需要做的是前后端分离项目的骨架,所以一般我们会采用token或者jwt作为跨域身份验证解决方案。所以整合shiro的过程中,我们需要引入jwt的身份验证过程。
那么我们就开始整合:
我们使用一个shiro-redis-spring-boot-starter的jar包,
第一步:导入shiro-redis的starter包:还有jwt的工具包,以及为了简化开发,我引入了hutool工具包。
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis-spring-boot-starter</artifactId>
<version>3.2.1</version>
</dependency>
<!-- hutool工具类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.3</version>
</dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
第二步:编写配置:
ShiroConfig
- com.markerhub.config.ShiroConfig
/**
* shiro启用注解拦截控制器
*/
@Configuration
public class ShiroConfig {
@Autowired
JwtFilter jwtFilter;
@Bean
public SessionManager sessionManager(RedisSessionDAO redisSessionDAO) {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisSessionDAO);
return sessionManager;
}
@Bean
public DefaultWebSecurityManager securityManager(AccountRealm accountRealm,
SessionManager sessionManager,
RedisCacheManager redisCacheManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(accountRealm);
securityManager.setSessionManager(sessionManager);
securityManager.setCacheManager(redisCacheManager);
/*
* 关闭shiro自带的session,详情见文档
*/
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
securityManager.setSubjectDAO(subjectDAO);
return securityManager;
}
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/**", "jwt"); // 主要通过注解方式校验权限
chainDefinition.addPathDefinitions(filterMap);
return chainDefinition;
}
@Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,