七、Springboot 整合Nutz

本章节基于: 六、Springboot 整合Shiro---03权限控制

Nutz 可以做什么?

  • Dao -- 针对 JDBC 的薄封装,事务模板,无缓存
  • Ioc -- JSON 风格的配置文件,声明时切片支持
  • Mvc -- 注解风格的配置,内置多文件上传功能
  • Json -- 解析和渲染
  • Castors -- Java 对象类型转换
  • Lang -- 更简洁的 Java 函数以及更丰富的反射支持
  • Aop -- 轻便快速的切面编程支持
  • Resource -- 资源扫描

它所有的功能均不强制依赖第三方 jar 包

这就意味着:

  • 如果一个 Web 应用,你在 WEB-INF/lib 下只 需要放置一个 nutz.jar 就够了
  • 当然你要使用连接池,数据库驱动, websocket等功能,还需要自行添置 jar 包。

支持的环境

  • JDK5+, 推荐JDK8
  • 任意SQL数据库,例如MySQL,Oracle,SqlServer等等
  • 任意支持servlet 2.5的web容器, 推荐Tomcat 8.5+/Jetty 9.2+

Nutz 为谁而设计?

  • 如果你觉得 Hibernate 控制比较繁琐,iBatis 编写SQL又比较麻烦,Nutz.Dao 专为你设计。
  • 如果你觉得在多个服务器部署或者修改 Spring 配置文件很麻烦,Nutz.Ioc 专为你设计
  • 如果你觉得直接写 XML 配置文件很麻烦,可视化编辑器又没控制感,Nutz.Mvc 专为你设计
  • 如果你觉得 JSON 转换很麻烦(要写超过一行以上的代码),Nutz.Json 专为你设计
  • 如果你觉得 Java 语法不如 Ruby 便捷, Nutz.Castor 以及 Nutz.Lang 专为你设计
  • 如果你以前根本没接触过 SSH ,只使用 JDBC 编程, 整个 Nutz 专门为你设计

Nutz 的质量

截至到现在为止,Nutz 的 JUnit 用例覆盖率大概是这样的

并且这个数字还在不断增加。

在一个功能告一段落以后,我通常会花1-2个晚上在一边咂着廉价的红酒一边颇有成就感的书写JUnit测试。 通常我会用 JUnit 把我自己击溃,紧接着的那几天我都努力让那个该死红条变绿,之后,又想方设法写出 新的Junit测试试图让它再度变红。并且我还要保证所做的修改不能让代码膨胀,这的确让我死掉了不少脑 细胞。这些测试中,不仅涵盖各种功能上的测试,也涵盖了一些跨越线程的测试。在以后,我会针对代码执 行的效率加入一些新的测试。

Springboot整合Nutz(这里只用到nutz的Dao):

一、在pom文件中引入依赖:

        <!--Springboot整合Nutz-->
        <dependency>
            <groupId>org.nutz</groupId>
            <artifactId>nutz-plugins-spring-boot-starter</artifactId>
            <version>1.r.66</version>
        </dependency>
        <!--阿里的数据连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.9</version>
        </dependency>
        <!--数据库驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

二、在application.yml中配置数据库链接信息和nutz

server:
  #设置程序启动端口号
  port: 7000
beetl:
  #模板路径
  templatesPath: templates
oauth:
  qq:
    #你的appid
    client_id: 123456
    #你的appkey
    client_secret: aaaaaaaaa
    #你接收响应code码地址
    redirect_uri: http://localhost:7000/authorize/qq
    #腾讯获取code码地址
    code_callback_uri: https://graph.qq.com/oauth2.0/authorize
    #腾讯获取access_token地址
    access_token_callback_uri: https://graph.qq.com/oauth2.0/token
    #腾讯获取openid地址
    openid_callback_uri: https://graph.qq.com/oauth2.0/me
    #腾讯获取用户信息地址
    user_info_callback_uri: https://graph.qq.com/user/get_user_info
spring:
  aop:
  #开启aop代理
    auto: true
    proxy-target-class: true
  datasource:
     driver-class-name: com.mysql.jdbc.Driver
     username: root
     password: 123456
     url: jdbc:mysql://127.0.0.1:3306/example?characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false&maxReconnects=10&useSSL=false
     type: com.alibaba.druid.pool.DruidDataSource
     druid:
       # 初始连接数
       initial-size: 5
       # 最大激活数
       max-active: 50
       # 最大等待时间
       max-wait: 3000
       # 是否启用非公平锁
       use-unfair-lock: true
       # mysql 用 false | oracle 用 true
       pool-prepared-statements: false
nutz:
  json:
    auto-unicode: false
    quote-name: true
    ignore-null: true
    null-as-emtry: true
    enabled: true
    mode: compact
  dao:
    runtime:
      create: true #自动创建表
      migration: false #根据bena自动更新表结构
      basepackage: com.xslde.model.mapped  #扫描bean
    sqlmanager:
      paths:
        - sqls  #sql文件存放位置

三、创建三个实体类外加一个工具类:

1、用户:

package com.xslde.model.mapped;

import org.nutz.dao.entity.annotation.*;

import java.io.Serializable;
import java.util.List;

/**
 * @Author xslde
 * @Description
 * @Date 2018/7/20 16:23
 */
@Table("xslde_user")//批量见表时候,扫描到该注解会创建一个表名称为”s_user“的表
public class User implements Serializable {

    @Id
    private Integer id;
    //用户名称
    @Column//该注解表示,在nutz创建表时会创建该字段
    @Comment("用户名称")//这个注解是数据库表中对应字段的说明
    private String username;

    @Column
    @Comment("昵称")
    private String nickname;

    //用户密码
    @Column
    private String password;

    //密码加盐
    @Column
    private String salt;

    //用户是否可用
    @Column
    private Integer available;

    @ManyMany(relation = "xslde_user_roles",from = "user_id",to = "role_id")
    private List<Role> roles;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    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 String getSalt() {
        return salt;
    }

    public void setSalt(String salt) {
        this.salt = salt;
    }

    public Integer getAvailable() {
        return available;
    }

    public void setAvailable(Integer available) {
        this.available = available;
    }

    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }
}

2、角色:

package com.xslde.model.mapped;

import org.nutz.dao.entity.annotation.*;

import java.io.Serializable;
import java.util.List;

/**
 * Created by xslde on 2018/7/23
 */
@Table("xslde_role")
public class Role implements Serializable {

    @Id
    private Integer id;

    @Column
    private String role;

    @Column
    private String describes;

    @ManyMany(relation = "xslde_role_permis",from = "role_id",to = "permis_id")
    private List<Permission> permissions;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }

    public String getDescribes() {
        return describes;
    }

    public void setDescribes(String describes) {
        this.describes = describes;
    }

    public List<Permission> getPermissions() {
        return permissions;
    }

    public void setPermissions(List<Permission> permissions) {
        this.permissions = permissions;
    }
}

3、权限:

package com.xslde.model.mapped;

import org.nutz.dao.entity.annotation.Id;
import org.nutz.dao.entity.annotation.Table;

import java.io.Serializable;

/**
 * Created by xslde on 2018/7/23
 */
@Table("xslde_permission")
public class Permission implements Serializable {
    @Id
    private Integer id;

    //角色
    private String permission;

    //描述
    private String describes;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getPermission() {
        return permission;
    }

    public void setPermission(String permission) {
        this.permission = permission;
    }

    public String getDescribes() {
        return describes;
    }

    public void setDescribes(String describes) {
        this.describes = describes;
    }
}

 4、密码加密工具类:

package com.xslde.utils;

import org.apache.shiro.crypto.hash.SimpleHash;

/**
 * Created by xslde on 2018/7/23
 */
public class PasswordUtils {

    /**
     *
     * @param salt 盐
     * @param password 明文密码
     * @return
     */
    public static String getPassword(String salt,String password){
        String hashAlgorithmName = "md5";//加密类型
        Integer iteration = 2;//迭代次数
        return new SimpleHash(hashAlgorithmName,password,salt,iteration).toHex();
    }

}

四、对LoginAction.class改动:

    //Nutz dao
    @Autowired
    Dao dao;

    //接收回调地址带过来的code码
    @GetMapping("/authorize/qq")
    public String authorizeQQ(Map<String, String> msg, String code) {
        HashMap<String, Object> params = new HashMap<>();
        params.put("code", code);
        params.put("grant_type", "authorization_code");
        params.put("redirect_uri", oauth.getQQ().getRedirect_uri());
        params.put("client_id", oauth.getQQ().getClient_id());
        params.put("client_secret", oauth.getQQ().getClient_secret());
        //获取access_token如:access_token=9724892714FDF1E3ED5A4C6D074AF9CB&expires_in=7776000&refresh_token=9E0DE422742ACCAB629A54B3BFEC61FF
        //TODO 其实整合NUTZ后,可以不使用HttpUtils工具类,并去掉pom中httpasyncclient、httpmime依赖,使用NUTZ自带HTTP工具
        String result = HttpsUtils.doGet(oauth.getQQ().getAccess_token_callback_uri(), params);
        //对拿到的数据进行切割字符串
        String[] strings = result.split("&");
        //切割好后放进map
        Map<String, String> reulsts = new HashMap<>();
        for (String str : strings) {
            String[] split = str.split("=");
            if (split.length > 1) {
                reulsts.put(split[0], split[1]);
            }
        }
        //到这里access_token已经处理好了
        //下一步获取openid,只有拿到openid才能拿到用户信息
        String openidContent = HttpsUtils.doGet(oauth.getQQ().getOpenid_callback_uri() + "?access_token=" + reulsts.get("access_token"));
        //接下来对openid进行处理
        //截取需要的那部分json字符串
        String openid = openidContent.substring(openidContent.indexOf("{"), openidContent.indexOf("}") + 1);
        Gson gson = new Gson();
        //将返回的openid转换成DTO
        QQOpenidDTO qqOpenidDTO = gson.fromJson(openid, QQOpenidDTO.class);
        User existUser = dao.fetch(User.class, Cnd.where("username", "=", qqOpenidDTO.getOpenid()));
        if (existUser == null) {
            params.clear();
            params.put("access_token", reulsts.get("access_token"));//设置access_token
            params.put("openid", qqOpenidDTO.getOpenid());//设置openid
            params.put("oauth_consumer_key", qqOpenidDTO.getClient_id());//设置appid
            String userInfo = HttpsUtils.doGet(oauth.getQQ().getUser_info_callback_uri(), params);
            QQDTO qqDTO = gson.fromJson(userInfo, QQDTO.class);//json转换成dto
            User user = new User();
            String salt = UUID.randomUUID().toString();
            user.setNickname(qqDTO.getNickname());
            user.setUsername(qqOpenidDTO.getOpenid());
            user.setPassword(PasswordUtils.getPassword(salt,qqOpenidDTO.getOpenid()));
            user.setSalt(salt);
            user.setAvailable(1);
            User insert = dao.insert(user);//向数据库插入数据
            ArrayList<Role> roles = new ArrayList<>();
            Role role = new Role();
            role.setId(1);//id = 1 是普通用户
            roles.add(role);
            insert.setRoles(roles);//将角色集合存入用户实体类
            dao.insertRelation(insert,"roles");//将用户和角色关联起来
            return QQLogin(msg, qqOpenidDTO);
        } else {
            return QQLogin(msg, qqOpenidDTO);
        }

        //这里拿用户昵称,作为用户名,openid作为密码(正常情况下,在开发时候用openid作为用户名,再自己定义个密码就可以了)
    }

    private String QQLogin(Map<String, String> msg, QQOpenidDTO qqOpenidDTO) {
        try {
            SecurityUtils.getSubject().login(new UsernamePasswordToken(qqOpenidDTO.getOpenid(), qqOpenidDTO.getOpenid()));
        } catch (Exception e) {
            msg.put("msg", "第三方登陆失败,请联系管理!");
            logger.error(e.getMessage());
            return "login.html";
        }
        return "redirect:/index";
    }

五、对ShiroRealm.class改动:

package com.xslde.configurer;

import com.xslde.model.mapped.Permission;
import com.xslde.model.mapped.Role;
import com.xslde.model.mapped.User;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.json.Json;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;
import java.util.UUID;

/**
 * @Author xslde
 * @Description
 * @Date 2018/7/20 16:30
 */
public class ShiroRealm extends AuthorizingRealm {


    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    Dao dao;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //给当前用户授权的权限(功能权限、角色)
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        //xslde用户拥有user角色
        User user = (User) principals.getPrimaryPrincipal();
        List<Role> roles = user.getRoles();

        if (roles!=null&&roles.size()>0){
            //遍历所有用户拥有的角色添加到授权信息中
            for (Role role:roles){
                authorizationInfo.addRole(role.getRole());
                //查询角色拥有的所有权限,并重新复制给角色
                role = dao.fetchLinks(role,"permissions");
                logger.info("角色详情"+Json.toJson(role));
                if (role.getPermissions()!=null&&role.getPermissions().size()>0){
                    //遍历所有角色拥有的权限添加到授权信息中
                    for (Permission permis:role.getPermissions()){
                        authorizationInfo.addStringPermission(permis.getPermission());
                    }
                }
            }
        }
        return authorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取用户名
        String username = (String) token.getPrincipal();
        //根据用户名查询用户信息和用户角色
        User existUser = dao.fetchLinks(dao.fetch(User.class, Cnd.where("username", "=", username)),"roles");
        logger.info("用户详情"+Json.toJson(existUser));
        if (existUser==null){
            throw new UnknownAccountException("用户不存在!");
        }
        if (existUser.getAvailable()!=1){
            throw  new LockedAccountException("账户已被锁定");
        }
        return  new SimpleAuthenticationInfo(existUser, existUser.getPassword(), ByteSource.Util.bytes(existUser.getSalt()), getName());
    }

    //生成一个加盐密码
    public static void main(String[] args) {
/*        String hashAlgorithmName = "md5";//加密类型
        Integer iteration = 2;//迭代次数
        String password = "123456";
        String salt = "abcd";
        String s = new SimpleHash(hashAlgorithmName,password,salt,iteration).toHex();
        System.out.println(s);
        //加密后的密码*/
        //0caf568dbf30f5c33a13c56b869259fc

        for (int i=0;i<=10;i++){
            System.out.println(UUID.randomUUID().toString().replace("-",""));
        }
    }
}

5、配置好数据库、启动项目完成自动建表成功,执行下面sql命令:

INSERT INTO `xslde_user`(`id`, `username`, `nickname`, `password`, `salt`, `available`) VALUES (1, 'xslde', 'xslde', '0caf568dbf30f5c33a13c56b869259fc', 'abcd', 1);
INSERT INTO `xslde_user`(`id`, `username`, `nickname`, `password`, `salt`, `available`) VALUES (2, 'admin', '超级管理员', '0caf568dbf30f5c33a13c56b869259fc', 'abcd', 1);
INSERT INTO `xslde_role`(`id`, `role`, `describes`) VALUES (1, 'user', '普通用户');
INSERT INTO `xslde_role`(`id`, `role`, `describes`) VALUES (2, 'admin', '超级管理员');
INSERT INTO `xslde_permission`(`id`, `permission`, `describes`) VALUES (1, 'user:query', '普通用户查看权限');
INSERT INTO `xslde_permission`(`id`, `permission`, `describes`) VALUES (2, 'admin:delete', '超级管理员删除权限');
INSERT INTO `xslde_user_roles`(`user_id`, `role_id`) VALUES ('1', '1');
INSERT INTO `xslde_user_roles`(`user_id`, `role_id`) VALUES ('2', '2');
INSERT INTO `xslde_role_permis`(`role_id`, `permis_id`) VALUES ('1', '1');
INSERT INTO `xslde_role_permis`(`role_id`, `permis_id`) VALUES ('2', '2');


 6、打开浏览器访问:http://localhost:7000/login,用xslde和admin用户登录就会进入数据库查询,如果是第三方QQ登录,第一次登录会插入第三方登录用户信息,之后的每次登录,都是通过openid查询。

数据库已存在用户数据,登录后台打印sql执行如下:

当第一次访问受权限保护资源时候,后台打印执行sql如下:

 项目地址:springboot-example07

猜你喜欢

转载自blog.csdn.net/xslde_com/article/details/81147678