使用springboot快速构建一个单体应用

原创
2019/07/29 21:48
阅读数 276

使用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的同学不友好,全是代码没有解释

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部