使用springboot快速构建一个单体web应用服务端
快速集成HibernateJPA, Swagger2, Mysql5.7
项目架构
1:使用idea或者在https://start.spring.io/官网上使用Spring Initializr构建一个springboot项目
2:基础环境
pom.xml
<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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.geek.calvin</groupId>
<artifactId>boot-fast-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>boot-fast-service</name>
<description>Spring Boot Fast Service</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- web依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- swagger支持-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.4.0</version>
</dependency>
<!-- mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- jpa依赖-->
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId>
<version>1.0.0.Final</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- fast-json-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.40</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
spring:
jpa:
database: mysql
show-sql: true
database-platform: org.hibernate.dialect.MySQL57InnoDBDialect #方言
hibernate:
ddl-auto: update
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl #物理命名策略
implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl #隐式命名策略
datasource:
type: com.zaxxer.hikari.HikariDataSource
username: root
password: 123456
url: jdbc:mysql://localhost:3306/fast-service?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=true
hikari:
driver-class-name: com.mysql.cj.jdbc.Driver #最新版本的依赖包
max-lifetime: 10
minimum-idle: 5
maximum-pool-size: 10
idle-timeout: 30000
pool-name: default-pool
SwaggerConfig.java
/**
* <p> 配置Swagger</p>
*
* @author Calvin
* @date 2019/07/29
* @since
*/
@Configuration
@EnableSwagger2
public class SwaggerConfig {
/**
* 配置swagger2,任意路径,任意请求
*/
@Bean
public Docket swagger(){
return new Docket(DocumentationType.SWAGGER_2)
.select()
.paths(PathSelectors.any())
.apis(RequestHandlerSelectors.any())
.build();
}
}
3:全局封装
FastServiceException.java 自定义异常
/**
* <p> 项目中自定义的异常</p>
* @author Calvin
* @date 2019/07/29
* @since
*/
public class FastServiceException extends RuntimeException {
/**
* 错误码
*/
private int code;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public FastServiceException() {
super();
}
public FastServiceException(int code, String message) {
super(message);
this.code = code;
}
public FastServiceException(String message, Throwable cause) {
super(message, cause);
}
public FastServiceException(Throwable cause) {
super(cause);
}
protected FastServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
FastExceptionType.java 定义异常Code
/**
* <p>定义异常Code </p>
*
* @author Calvin
* @date 2019/07/29
* @since
*/
public class FastExceptionType {
/**
* 验证码不正确
*/
public final static int KAPTCHA_CODE_NOT_MATCH = 2001;
/**
* 没有该用户
*/
public final static int USER_NOT_FOUND = 2002;
/**
* 密码不匹配
*/
public final static int PASSWORD_NOT_MATCH = 2003;
/**
* 资源未请求到 eq 404
*/
public final static int SOURCE_NOT_FOUND = 2004;
/**
* 其他系统级异常
*/
public final static int SYS_ERROR = 2099;
}
FastServiceExceptionHandler.java 进行全局异常处理
/**
* <p> 全局的异常处理</p>
*
* @author Calvin
* @date 2019/07/29
* @since
*/
@ControllerAdvice
@ResponseBody
public class FastServiceExceptionHandler {
/**
* 处理自定义异常,此处有code
* @param exception
* @return
*/
@ExceptionHandler(FastServiceException.class)
public ResultEntity handleFastServiceException(FastServiceException exception){
int code = exception.getCode();
String message = exception.getMessage();
ResultEntity entity = new ResultEntity();
entity.setCode(code);
entity.setMsg(message);
return entity;
}
/**
* 文件或文件流异常
* @return
*/
@ExceptionHandler(IOException.class)
public ResultEntity handleIOException(IOException exception){
String message = exception.getMessage();
ResultEntity entity = new ResultEntity();
entity.setMsg(message);
entity.setCode(3000);
return entity;
}
/**
* 处理运行时异常,此处基本为系统级别异常
* @param exception
* @return
*/
@ExceptionHandler(RuntimeException.class)
public ResultEntity handleRuntimeException(RuntimeException exception){
String message = exception.getMessage();
ResultEntity entity = new ResultEntity();
entity.setMsg(message);
entity.setCode(FastExceptionType.SYS_ERROR);
return entity;
}
}
ReultEntity.java 全局返回值处理
/**
* <p> 全局返回值</p>
* @author Calvin
* @date 2019/07/29
* @since
*/
public class ResultEntity {
private int code;
private String msg;
private Object data;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
ResultHandlerSupport.java
/**
* <p> 统一的全局返回值处理 </p>
*
* @author Calvin
* @date 2019/07/29
* @since
*/
@RestControllerAdvice
public class ResultHandlerSupport implements ResponseBodyAdvice<Object> {
/**
* 判断方法是否需要重写,这里所有都要重写
* @param methodParameter
* @param aClass
* @return
*/
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
Method method = methodParameter.getMethod();
//排除Swagger要使用的两个Controller
if(method.getDeclaringClass().equals(Swagger2Controller.class)
|| method.getDeclaringClass().equals(ApiResourceController.class)){
return false;
}
return true;
}
/**
* 进行方法重写,这里简单处理
* @return
*/
@Override
public Object beforeBodyWrite(Object t, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
ResultEntity result = new ResultEntity();
result.setCode(200);
result.setMsg("success");
result.setData(t);
return result;
}
}
图片验证码依赖
<!-- 图片验证码-->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
KaptchaUtils.java生成图片验证码
/**
* <p> 创建图片验证码 工具类</p>
* @author Calvin
* @date 2019/07/29
* @since
*/
public class KaptchaUtils {
/**
* 获取验证码
* @return
*/
public static DefaultKaptcha getKaptcha(){
DefaultKaptcha kaptcha = new DefaultKaptcha();
Properties properties = new Properties();
// 图片边框
properties.setProperty("kaptcha.border", "yes");
// 边框颜色
properties.setProperty("kaptcha.border.color", "105,179,90");
// 字体颜色
properties.setProperty("kaptcha.textproducer.font.color", "red");
// 图片宽
properties.setProperty("kaptcha.image.width", "110");
// 图片高
properties.setProperty("kaptcha.image.height", "40");
// 字体大小
properties.setProperty("kaptcha.textproducer.font.size", "30");
// session key
properties.setProperty("kaptcha.session.key", "code");
// 验证码长度
properties.setProperty("kaptcha.textproducer.char.length", "4");
// 字体
properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
Config config = new Config(properties);
kaptcha.setConfig(config);
return kaptcha;
}
}
4:业务代码编写
LoginController.java
/**
* <p>登录用的Controller </p>
* @author Calvin
* @date 2019/07/29
* @since
*/
@RestController
@RequestMapping("/login")
@ResponseBody
@ApiModel
public class LoginController {
@Autowired
private LoginService loginService;
/**
* 登录
* @param userName 用户名
* @param password 密码
* @param code 验证码
* @return
*/
@ApiOperation(value = "登录",notes = "系统登录")
@GetMapping("/login")
public SysUser login(String userName, String password, String code){
String kaptcha = (String) SpringContextUtils.getSession().getAttribute("code");
if(kaptcha == null || code == null || !kaptcha.equals(code)){
throw new FastServiceException(FastExceptionType.KAPTCHA_CODE_NOT_MATCH,"验证码有误");
}
SysUser user = loginService.login(userName);
if(user == null){
throw new FastServiceException(FastExceptionType.USER_NOT_FOUND, "该用户不存在");
}
if(password == null || user.getPassword() == null || !MD5Utils.generator(password).equals(user.getPassword())){
throw new FastServiceException(FastExceptionType.PASSWORD_NOT_MATCH, "密码不正确");
}
return user;
}
/**
* 生成图形验证码并且返回一个图片
* @param response
* @throws IOException
*/
@GetMapping("/getKaptcha")
@ApiOperation(value = "获取验证码",notes = "获取图形验证码,可直接铺在屏幕上")
public void getKaptcha(HttpServletResponse response) throws IOException {
byte[] imageBytes = null;
ByteArrayOutputStream imageOutputStream = new ByteArrayOutputStream();
try {
DefaultKaptcha kaptcha = KaptchaUtils.getKaptcha();
String text = kaptcha.createText();
SpringContextUtils.getSession().setAttribute("code", text);
BufferedImage image = kaptcha.createImage(text);
ImageIO.write(image, "jpg", imageOutputStream);
} catch (IOException e) {
try {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
} catch (IOException ex) {
ex.printStackTrace();
}
e.printStackTrace();
}
imageBytes = imageOutputStream.toByteArray();
response.setHeader("Cache-Control", "no-store");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
response.setContentType("image/jpeg");
ServletOutputStream responseOutputStream = response.getOutputStream();
responseOutputStream.write(imageBytes);
responseOutputStream.flush();
responseOutputStream.close();
}
}
LoginService.java 调用方法
/**
* <p> 登录服务的Service</p>
* @author Calvin
* @date 2019/07/29
* @since
*/
public interface LoginService {
/**
* 登录的时候根据用户登录名查询是否有该用户
* @param userName
* @return
*/
SysUser login(String userName);
}
LoginServiceImpl.java
/**
* <p>LoginService 的 实现类 </p>
*
* @author Calvin
* @date 2019/07/29
* @since
* @see com.geek.calvin.controller.LoginController
*/
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private SysUserRepository userRepository;
@Override
public SysUser login(String userName) {
return userRepository.getUserByLoginName(userName);
}
}
UserRepository.java持久层
/**
* <p> SysUser 持久层</p>
* @author Calvin
* @date 2019/07/29
* @since
*/
@Repository
public interface SysUserRepository extends CrudRepository<SysUser, Long> {
/**
* 根据登录名查询用户
* @param loginName
* @return
*/
@Query("select user from SysUser user where user.loginName = :loginName")
public SysUser getUserByLoginName(String loginName);
}
创建SysUser.java
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.Date;
/**
* <p> 用户类 </p>
* @author Calvin
* @date 2019/07/29
* @since
*/
@Entity
@Table(name = "sys_user")
public class SysUser {
/**
* id UUID 自增
*/
@Id()
@GenericGenerator(name = "uuid", strategy = "uuid")
@Column(length = 40)
private String id;
/**
* 登录用户名
*/
@Column(name = "login_name",length = 20)
private String loginName;
/**
* 登录后显示的用户名
*/
@Column(name = "nick_name",length = 20)
private String nickName;
/**
* 密码
*/
@Column(name = "password",length = 40)
private String password;
/**
* 最后登录时间
*/
@Column(name = "last_login")
private Date lastLogin;
/**
* 是否管理员
*/
@Column(name = "is_admin")
private int isAdmin;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Date getLastLogin() {
return lastLogin;
}
public void setLastLogin(Date lastLogin) {
this.lastLogin = lastLogin;
}
public int getIsAdmin() {
return isAdmin;
}
public void setIsAdmin(int isAdmin) {
this.isAdmin = isAdmin;
}
}
5:工具类
SpringContextUtils.java
/**
* <p> Spring上下文工具类</p>
* @author Calvin
* @date 2019/07/29
* @since
*/
public class SpringContextUtils {
/**
* 获取session中存在的用户信息
* @return
*/
public static Object getCurrentUser(){
SysUser user = (SysUser) getSession().getAttribute("user");
if(user != null){
return user;
}else{
throw new FastServiceException(401, "该用户尚未登录,请先登录");
}
}
/**
* 往session当中设置属性
* @param key
* @param value
*/
public static void setSessionAttribute(String key, Object value){
getSession().setAttribute(key, value);
}
/**
* 获取Request
* @return
*/
public static HttpServletRequest getRequest(){
return ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest();
}
/**
* 获取session
* @return
*/
public static HttpSession getSession(){
return getRequest().getSession();
}
}
MD5Utils.java
/**
* <p> MD5工具类</p>
* @author jiawentao
* @date 2019/07/29
* @since
*/
public class MD5Utils {
/**
* 转换一个MD5
* @param source
* @return
*/
public static String generator(String source){
return DigestUtils.md5DigestAsHex(source.getBytes());
}
}
6:结果图:
启动
1: console log
Connected to the target VM, address: '127.0.0.1:61978', transport: 'socket'
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.6.RELEASE)
2019-07-29 16:34:05.265 INFO 11164 --- [ main] com.geek.calvin.FastServiceApplication : Starting FastServiceApplication on DESKTOP-L4NIJHA with PID 11164 (started by jiawentao in E:\公司项目\boot-fast-service)
2019-07-29 16:34:05.267 INFO 11164 --- [ main] com.geek.calvin.FastServiceApplication : No active profile set, falling back to default profiles: default
2019-07-29 16:34:06.002 INFO 11164 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data repositories in DEFAULT mode.
2019-07-29 16:34:06.053 INFO 11164 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 44ms. Found 1 repository interfaces.
2019-07-29 16:34:06.360 INFO 11164 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$1544b7db] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2019-07-29 16:34:06.561 INFO 11164 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2019-07-29 16:34:06.577 INFO 11164 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2019-07-29 16:34:06.578 INFO 11164 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.21]
2019-07-29 16:34:06.671 INFO 11164 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2019-07-29 16:34:06.671 INFO 11164 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1375 ms
2019-07-29 16:34:06.820 INFO 11164 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [
name: default
...]
2019-07-29 16:34:06.875 INFO 11164 --- [ main] org.hibernate.Version : HHH000412: Hibernate Core {5.3.10.Final}
2019-07-29 16:34:06.877 INFO 11164 --- [ main] org.hibernate.cfg.Environment : HHH000206: hibernate.properties not found
2019-07-29 16:34:06.999 INFO 11164 --- [ main] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {5.0.4.Final}
2019-07-29 16:34:07.109 WARN 11164 --- [ main] com.zaxxer.hikari.HikariConfig : default-pool - maxLifetime is less than 30000ms, setting to default 1800000ms.
2019-07-29 16:34:07.110 INFO 11164 --- [ main] com.zaxxer.hikari.HikariDataSource : default-pool - Starting...
2019-07-29 16:34:07.314 INFO 11164 --- [ main] com.zaxxer.hikari.HikariDataSource : default-pool - Start completed.
2019-07-29 16:34:07.323 INFO 11164 --- [ main] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.MySQL57InnoDBDialect
Hibernate: create table sys_user (id varchar(40) not null, is_admin integer, last_login datetime(6), login_name varchar(20), nick_name varchar(20), password varchar(40), primary key (id)) engine=InnoDB
2019-07-29 16:34:07.975 INFO 11164 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2019-07-29 16:34:08.230 INFO 11164 --- [ main] o.h.h.i.QueryTranslatorFactoryInitiator : HHH000397: Using ASTQueryTranslatorFactory
2019-07-29 16:34:08.459 WARN 11164 --- [ main] aWebConfiguration$JpaWebMvcConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2019-07-29 16:34:08.628 INFO 11164 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2019-07-29 16:34:08.759 INFO 11164 --- [ main] d.s.w.p.DocumentationPluginsBootstrapper : Context refreshed
2019-07-29 16:34:08.771 INFO 11164 --- [ main] d.s.w.p.DocumentationPluginsBootstrapper : Found 1 custom documentation plugin(s)
2019-07-29 16:34:08.779 INFO 11164 --- [ main] s.d.s.w.s.ApiListingReferenceScanner : Scanning for api listing references
2019-07-29 16:34:08.914 INFO 11164 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2019-07-29 16:34:08.915 INFO 11164 --- [ main] com.geek.calvin.FastServiceApplication : Started FastServiceApplication in 3.968 seconds (JVM running for 4.67)
正常启动,并且表自动建立
2:数据库操作:新增一条记录
{
id: "94ea7676292a4951b4506d10eed33e19",
login_name: "admin",
nick_name: "系统管理员",
password: "e10adc3949ba59abbe56e057f20f883e",
last_login: "2019-07-29T15:53:54.000+0000",
is_admin: 1
}
3:浏览器请求: 图片验证码: http://localhost:8080/login/getKaptcha
同一个浏览器登录:http://localhost:8080/login/login?userName=admin&password=123456&code=2b7g
访问Swagger页面 http://localhost:8080/swagger-ui.html
总结
1: 最快形式搭建springboot + hibernateJpa项目,快速的开始一个后台服务
2:融入swagger2,搭建接口文档服务器
3: 简单创建图片验证码
4:简单实现登录功能
待完善:
1:缺少了拦截器
2:登录功能未使用框架
3:对于不熟悉springboot和Hibernate的同学不友好,全是代码没有解释