Springboot + Mybatis + Mysql实战后台开发

Springboot的相关介绍
1.Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。【来自百度】
2.SpringBoot所具备的特征有:
(1)可以创建独立的Spring应用程序,并且基于其Maven或Gradle插件,可以创建可执行的JARs和WARs;
(2)内嵌Tomcat或Jetty等Servlet容器;
(3)提供自动配置的“starter”项目对象模型(POMS)以简化Maven配置;
(4)尽可能自动配置Spring容器;
(5)提供准备好的特性,如指标、健康检查和外部化配置;
(6)绝对没有代码生成,不需要XML配置。
【来自百度】

IDEA新建一个springboot项目初始化
1.Springboot + Mybatis + Mysql实战后台开发_第1张图片
2.Springboot + Mybatis + Mysql实战后台开发_第2张图片
3.Springboot + Mybatis + Mysql实战后台开发_第3张图片
3.接下来根据自己的相关需求修改名称等 一路next
4.这里因为数据库的需要 我选择的是web + mysql + mybatis
Web:支持相关的webkaifa
MySql Driver:mysql的驱动
JDBC API:连接mysql的jdbc工具
Mybatis:mybatis的相关支持
Springboot + Mybatis + Mysql实战后台开发_第4张图片
5.打开Project的相关文件 (其中的src文件是我们主要操作的)
src:
main:文件中包含java文件 ,是主要java相关类的编写
resource:主要放置一些资源文件和sprinboot的相关配置
text:
里面有一些项目的测试文件,项目用来进行测试的相关类

springboot + mybatis + mysql的相关配置(需要连网)
1.添加pom.xml文件的相关依赖,在pom.xml加入如下代码


        
            mysql
            mysql-connector-java
            5.1.47
            runtime
        

其中version指定的是版本,一般是自动导入,也可根据自己的具体需要进行自主修改,我这里指定的是5.1.47版本。
2.在application.properties配置文件中添加连接自己数据库的相关信息

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/h?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=lv2016.

注意:
我的Mysql数据库版本为5.7,选择的驱动器如下spring.datasource.driver-class-name=com.mysql.jdbc.Driver
高版本的Mysql数据库如8以上,驱动器的形式应为spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
Url的配置也发生了变化spring.datasource.url=jdbc:mysql://localhost:3306/(需要连接的数据库名称)? useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
一般情况下没有用到SSL,需要将useSSL设置成false。

后台开发实战项目总结
1.数据库表可以与实体类进行一一对应,可以开启驼峰命名转换等一些mybatis的相关功能,此时需要在resource文件下新建一个mybatis-config.xml的配置文件。




    
        
        
        
    

要使mybatis-config.xml生效,需要在application.properties配置文件中配置它的地址,添加如下代码。

mybatis.config-location=classpath:mybatis-config.xml
mybatis.mapper-locations=classpath*:/mapper/**.xml

2.可以实现自动建表功能。
需要在application.properties配置文件中添加sql语句的地址,添加如下代码。

spring.jpa.hibernate.ddl-auto=none
spring.datasource.initialization-mode=always
spring.datasource.schema=classpath:sql/schema.sql
spring.datasource.data=classpath:sql/data.sql

这里的是将springboot的一些默认设置改变,为我们建表提供支持。

spring.jpa.hibernate.ddl-auto=none
spring.datasource.initialization-mode=always

配置文件完成后,在resource文件下就建立sql目录。
sql:
schema.sql文件主要是对表的建立.

-- ------------
-- 建立管理员表
-- ------------

drop table if exists `user`;
create table `user`(
    `user_id` int(10) not null auto_increment,
    `user_email` varchar(200) not null ,
    `user_name` varchar(200) not null ,
    `user_password` varchar(200) not null ,
    `user_create_time` date default null,
    `user_lastEditTime` date default null,
    `user_login`int(10) default 0,
    `user_limit`int(10) default 0,
    primary key (`user_id`),
    unique `USER` (`user_email`)
)engine = InnoDB auto_increment=1 default charset = utf8;

data.sql文件主要对表的添加信息

insert into user (user_email,user_name,user_password,user_limit)
values ("22222","lth","123456",1);
insert into user (user_email,user_name,user_password)
values  ("1","ds","1");

配置完成后运行就可以自动建表。
3.实体类的相关信息要与表中的相关信息一一对应
4.dao层编写完成后,实现对数据库数据的增删改查可以有两种方式:
(1)直接在dao层interface接口编写。

@Mapper//给一个标签让编译器知道
@Component
public interface ShiPinDao {

    //插入
    @Insert({"insert into shipins (name,lujing,url) values (#{name},#{lujing},#{url})"})
    public int insertUrl(@Param("name")String name,@Param("lujing")String lujing,@Param("url")String url);

    //查询
    @Select("select * from shipins")
    public List selectShipin();
}

(2) resource文件中编写mapper文件,新建xxDao.xml文件对数据库进行操作。




    
    
    
    
    
        insert into user(user_email, user_name, user_password, user_create_time, user_lastEditTime)
        VALUES
        (#{userEmail},#{userName},#{userPassword},#{userCreateTime},#{userLastEditTime})
    
    
        update user
        set user_name=#{userName},user_password=#{userPassword},user_lastEditTime=#{userLastEditTime},user_login=#{userLogin}
        where user_email=#{userEmail}
    
    
        DELETE FROM user
        WHERE user_id=#{userId}
    

注意:
(1)resultType="com.feidian.demo.enitity.User"返回值要具体设置,一般为具体的实体类。像一些insert、update等没有返回值类型的,一般是返回自增的主键或者是改变的行数。
(2)使用这种方法,在启动类前要加入相关标识。@MapperScan("com.feidian.demo.dao")

@SpringBootApplication
@MapperScan("com.feidian.demo.dao")
public class{
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

这样才能够使编译器知道相关的dao层和mapper文件。
5.图片处理功能
(1)对上传图片的处理。将前端上传的图片保存到本地并映射成可访问的url返回。(工具类)

@Component
public class ImgUtil {
    @Value("${upload_imgs_folder}")
    private String UPLOAD_FOLDER;
    @Value("${ip}")
    private String IP;

    //上传文件夹路径
    String path;
    //新命名
    String newName;
    //文件后缀
    String suffix;
    //获取系统分隔符
    private static String separator = System.getProperty("file.separator");

    public Img ImageUploadUtil(MultipartFile file, Img img) {
        PathUtil(file, img);

        if ((img.getImgTime()).isEmpty()) {
            img.setImgPath(path + newName + suffix);
            img.setImgUrl(IP + "/" + img.getImgFlag() + "/Images/" + newName + suffix);
        } else {
            img.setImgPath(path + newName + suffix);
            img.setImgUrl(IP + "/" + img.getImgFlag() + "/Images/" + img.getImgTime() + "/" + newName + suffix);
            //System.out.println("file:" + UPLOAD_FOLDER + "team/");
        }
        return img;
    }

    public void PathUtil(MultipartFile file, Img img) {
        if ((img.getImgTime()).isEmpty()) {
            path = UPLOAD_FOLDER + "work" + "/";
        } else {
            path = UPLOAD_FOLDER + "team" + "/" + img.getImgTime() + "/";
        }
        String[] paths = path.split("/");
        StringBuffer fullPath = new StringBuffer();
        for (int i = 0; i < paths.length; i++) {
            fullPath.append(paths[i]).append("/");
            File tempDir = new File(fullPath.toString());
            if (!tempDir.exists()) {
                tempDir.mkdir();
            }
        }
        //文件流操作
        BufferedOutputStream out = null;
        try {
            out = new BufferedOutputStream(
                    new FileOutputStream(path + file.getOriginalFilename()));
            out.write(file.getBytes());
            out.flush();
            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        //用当前时间重命名文件
        String fileName = file.getOriginalFilename();
        suffix = fileName.substring(fileName.lastIndexOf("."));
        File oldFile = new File(path + file.getOriginalFilename());
        Date now = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");//修改日期格式
        newName = dateFormat.format(now);
        File newFile = new File(path + newName + suffix);
        oldFile.renameTo(newFile);
    }

    /**
     * 获取存放图片路径
     */
    public static String getImgBasePath() {
        // 获取操作系统的信息
        String os = System.getProperty("os.name");
        String basePath = "";
        // 如果是window操作系统
        if (os.toLowerCase().startsWith("win")) {
            basePath = "D:/eclipse/tyron/image"; // Windows系统
        } else {
            basePath = "/home/tyron/image"; // 除了Windows系统
        }

        // 更换分隔符
        basePath = basePath.replace("/", separator);
        return basePath;
    }
}

(2)映射成可访问的url的配置
配置类:

@Configuration
public class UrlConfig implements WebMvcConfigurer {
    @Value("${upload_imgs_folder}")
    private String UPLOAD_FOLDER;

    //配置访问路由对静态资源的映射
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/work/Images/**").addResourceLocations("file:" + UPLOAD_FOLDER + "work/");
        registry.addResourceHandler("/team/Images/**").addResourceLocations("file:" + UPLOAD_FOLDER + "team/");
        registry.addResourceHandler("/member/Images/**").addResourceLocations("file:" + UPLOAD_FOLDER + "member/");
    }
}

配置文件:

#"file:"将本地路径设置为静态资源路径,路径最后必须以"/"结尾
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,\
  file:${upload_imgs_folder}

(3)pom.xml的依赖


            javax.servlet
            javax.servlet-api
            3.1.0
        

(4)对图片的删除的思路:
将图片进行编码,增加标识,换成我们所需要的具体类型。通过java文件、流等方式将图片下载并保存到本地的特定目录,并将图片映射成可以直接访问的url(这个在config中映射),变换成url的同时,数据库要插入该图片的url(返回给前端调用和显示)、Path(保存在后台)。Path是该图片的绝对路径。
保存Path绝对路径是为了在删除图片的时候不仅要删除数据库里该图片的相关信息,而且要将保存在本地的图片也一并删除,实现真正的删除要删除的图片。这里需要调出图片在本地的绝对路径,调用java的delete()方法File imgToDelete = new File(imgToDeleteUrl.getImgPath()); imgToDelete.delete();,对图片进行删除。同理,如果要修改图片,则应该先删除该图片在进行下载插入。
6.关于token拦截器的一些问题
(1)token拦截器的设计思路:
拦截器是实现在未登录的路由向后台数据库请求资源后访问页面时对它进行拦截,以防资源的泄露。
设计思路:进入登录界面,输入相关信息验证登录。验证成功,后台给该用户生成一个令牌token,存放在数据库中,这个token是仅属于该用户的,并给前端返回这个token。前端获取到该用户的token后,每次向后台请求资源时,必须带着后台给它颁发的这个令牌(token),将token和相关请求信息一并发给后端,后端拿到其token后,对该token进行解码,解码成功后与数据库里该用户的token进行比较,一致则放行,允许其请求资源,并返回给它所需要的相关数据。若没有接收到token或信息不匹配,则不会给它资源,让它重新进行登录获得token。
注意:
首次登录创建token,以后每次登录对token进行修改。
合理设置token过期时间,过期后要求重新登录,更新token。
(2)token的实现
生成jwt:token

public static final String JWT_SECERT = "0000df7f12334e888889999123c0005d";
 public static SecretKey generalKey() {
        byte[] encodedKey = Base64.decode(JWT_SECERT);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }

这里的String JWT_SECERT, SecretKey generalKey()是设定好的,不能变动。与下文一致。

/**
     * 生成token(格式为token:设备-加密的用户名-时间-六位随机数)
     * @param username 用户登录名
     * @return
     */
    public String creatToken(Long userId, Date date) {
        SecretKey secretKey = generalKey();
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT") // 设置header
                                 .setHeaderParam("alg", "HS256").setIssuedAt(date) // 设置签发时间
                                 .setExpiration(new Date(date.getTime() + 1000 * 60 * 60))
                                 .claim("userId",String.valueOf(userId) ) // 设置内容
                                 .setIssuer("lws")// 设置签发人
                                 .signWith(signatureAlgorithm, secretKey); // 签名,需要算法和key
        String jwt = builder.compact();
        return jwt;
    }

对token的解密:

/**
 * token拦截器
 *
 * @author sqc
 * @date
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {

    //@Autowired
    //private TokenDao tokenDao;
/**
 * 注入DAO层的方法
 * */
    @Autowired
    private TokenDao tokenDao;

    private static TokenDao tokenDao1;

    public @PostConstruct
    void init(){
        tokenDao1 = tokenDao;
    }

    //提供查询
    @Override
    public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
            throws Exception {}
    @Override
    public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
            throws Exception {}
    @Override
    public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception {
        //System.out.println("拦截器");
        //此处为不需要登录的接口放行"/member/Images","/culture/imgs","/work/Images","/team/Images"
        if (arg0.getRequestURI().contains("/api/login") || arg0.getRequestURI().contains("/Images") || arg0.getRequestURI().contains("/culture/imgs") || arg0.getRequestURI().contains("/static")) {
            return true;
        }
        //权限路径拦截
        //PrintWriter resultWriter = arg1.getOutputStream();
        // TODO: 有时候用PrintWriter 回报 getWriter() has already been called for this response
        //换成ServletOutputStream就OK了
        arg1.setContentType("text/html;charset=utf-8");
        ServletOutputStream resultWriter = arg1.getOutputStream();

        final String headerToken=arg0.getHeader("token");
        //判断请求信息
        if(null==headerToken||headerToken.trim().equals("")){
            resultWriter.write("你没有token,需要登录".getBytes());
            arg1.setStatus(401);
            resultWriter.flush();
            resultWriter.close();
            return false;
        }

        SecretKey secretKey = TokenUtil.generalKey();
        //解析Token信息
        try {

            Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(headerToken).getBody();
            //System.out.println("claims"+claims);
            String tokenUserId=(String)claims.get("userId");
            //System.out.println("提取的id"+tokenUserId);

            Long iTokenUserId = Long.parseLong(tokenUserId);
            //System.out.println(iTokenUserId);
            //根据userId查找数据库Token
            Token myToken= tokenDao1.findByUserId(iTokenUserId);

            //System.out.println();

            //System.out.println(myToken.getToken());
            //数据库没有Token记录
            if(null==myToken) {
                resultWriter.write("我没有你的token?,需要登录".getBytes());
                arg1.setStatus(401);
                resultWriter.flush();
                resultWriter.close();
                return false;
            }
            //数据库Token与客户Token比较
            if( !headerToken.equals(myToken.getToken()) ){
                resultWriter.print("你的token修改过?,需要登录");
                arg1.setStatus(401);
                resultWriter.flush();
                resultWriter.close();
                return false;
            }
            //判断Token过期
            //Date tokenDate= claims.getExpiration();
//            Date date = new Date();
//            String result1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
//            String result2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(myToken.getBuildTime()*1000);
//            System.out.println(result1 + result2);
//
//            System.out.println(date.getTime() + "   " +myToken.getBuildTime());
            int overTime=(int)( new Date().getTime()- myToken.getBuildTime()*1000)/1000;

            //System.out.println(overTime);

            //设置token过期的时间
            if(overTime>60*30){
                resultWriter.write("你的token过期了?,需要登录".getBytes());
                arg1.setStatus(401);
                resultWriter.flush();
                resultWriter.close();
                return false;
            }

        } catch (Exception e) {
            resultWriter.write("反正token不对,需要登录".getBytes());
            arg1.setStatus(401);
            resultWriter.flush();
            resultWriter.close();
            return false;
        }
        //最后才放行
        return true;
    }

}

特别注意:

  1. 除service层外,其他类想要注入dao层的方法要加上@Component标签
  2. 如果加入该标签后注入仍然为空的话,可以试下下面这个方法
/**
 1. 注入DAO层的方法
 2. */
    @Autowired
    private TokenDao tokenDao;

    private static TokenDao tokenDao1;

    public @PostConstruct
    void init(){
        tokenDao1 = tokenDao;
    }
  1. 这里需要特别注意时间的格式问题,并设置时间的统一性,合理设置好token过期时间
int overTime=(int)( new Date().getTime()- myToken.getBuildTime()*1000)/1000;

            //System.out.println(overTime);

            //设置token过期的时间
            if(overTime>60*30){
                resultWriter.write("你的token过期了?,需要登录".getBytes());
                arg1.setStatus(401);
                resultWriter.flush();
                resultWriter.close();
                return false;
            }

7.异常处理
可以为每一种异常定制一种处理方式,在impl文件中抛出异常,最后实现一个全局异常处理类接收异常,统一返回给前端错误信息。

@ControllerAdvice
public class GlobalExceptionHandler {
     @ExceptionHandler(value = Exception.class)
     @ResponseBody
     private Map returnErrorData(HttpServletRequest request, Exception e){
         Map modelMap=new HashMap();
         modelMap.put("errno",-1);
         modelMap.put("errMsg",e.getMessage());
         return modelMap;
    }
 }

前后端联调经验

  1. 后端可以在postman上测试,测试的时候注意数据的格式,请求的类型(GET/POST)等。
  2. formdata、json等数据类型要区分开,看自己的具体需求。(后端可以在参数中加上@RequestBody来接收一整个对象)
  3. 看前端具体需求返回给前端具体的参数,在这里我用的是这种类型:
    Map modelMap=new HashMap(); modelMap.put("errno",-1); modelMap.put("errMsg",e.getMessage());

总结
springboot功能强大,还需要多多了解!继续加油!

你可能感兴趣的:(经验总结)